From bc576a904f25d5f49b3364b3b179d5c3b6416503 Mon Sep 17 00:00:00 2001 From: Julien Mathis Date: Mon, 10 Jul 2006 10:11:06 +0000 Subject: [PATCH 001/948] Created folder remotely git-svn-id: http://svn.centreon.com/oreon-1.3@1 6bcd3966-0018-0410-8128-fd23d134de7e From 9fa9c503a676f22cf0ec16d503e8f5c4219f08b6 Mon Sep 17 00:00:00 2001 From: Julien Mathis Date: Mon, 1 Mar 2010 22:29:46 +0000 Subject: [PATCH 002/948] init repository git-svn-id: http://svn.modules.centreon.com/centreon-dsm/trunk@1 2223b321-f9ee-48cb-83f9-124db436e5d3 From a74e3e4ebf99e775f4220db009ab84721fdcc310 Mon Sep 17 00:00:00 2001 From: Julien Mathis Date: Mon, 1 Mar 2010 22:31:06 +0000 Subject: [PATCH 003/948] init repo git-svn-id: http://svn.modules.centreon.com/centreon-dsm/trunk@2 2223b321-f9ee-48cb-83f9-124db436e5d3 From 087e7b944eecebb5db832603af2c6c635f93eece Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 3 Jul 2019 17:49:51 +0200 Subject: [PATCH 004/948] initial version --- gorgone/centreon/centreond/clientzmq.pm | 204 +++++++ gorgone/centreon/centreond/common.pm | 539 +++++++++++++++++ gorgone/centreon/misc/db.pm | 304 ++++++++++ gorgone/centreon/misc/http/backend/curl.pm | 419 ++++++++++++++ .../misc/http/backend/curlconstants.pm | 33 ++ gorgone/centreon/misc/http/backend/lwp.pm | 299 ++++++++++ .../centreon/misc/http/backend/useragent.pm | 50 ++ gorgone/centreon/misc/http/http.pm | 238 ++++++++ gorgone/centreon/misc/lock.pm | 167 ++++++ gorgone/centreon/misc/logger.pm | 209 +++++++ gorgone/centreon/misc/misc.pm | 230 ++++++++ gorgone/centreon/misc/objects/host.pm | 60 ++ gorgone/centreon/misc/objects/object.pm | 82 +++ gorgone/centreon/misc/objects/organization.pm | 44 ++ gorgone/centreon/script.pm | 138 +++++ gorgone/centreon/script/centreondcore.pm | 542 ++++++++++++++++++ gorgone/centreond | 57 ++ gorgone/config/centreond-poller.ini | 53 ++ gorgone/config/centreond-poller2.ini | 31 + gorgone/config/centreond.ini | 99 ++++ gorgone/docs/guide.rst | 521 +++++++++++++++++ gorgone/keys/central/privkey.pem | 51 ++ gorgone/keys/central/pubkey.crt | 14 + gorgone/keys/poller/privkey.pem | 51 ++ gorgone/keys/poller/pubkey.crt | 14 + gorgone/modules/centreondacl/class.pm | 442 ++++++++++++++ gorgone/modules/centreondacl/hooks.pm | 246 ++++++++ gorgone/modules/centreondaction/class.pm | 252 ++++++++ gorgone/modules/centreondaction/hooks.pm | 154 +++++ gorgone/modules/centreondcron/class.pm | 136 +++++ gorgone/modules/centreondcron/hooks.pm | 154 +++++ gorgone/modules/centreondproxy/class.pm | 226 ++++++++ gorgone/modules/centreondproxy/hooks.pm | 475 +++++++++++++++ gorgone/modules/centreondpull/hooks.pm | 163 ++++++ gorgone/modules/scom/class.pm | 312 ++++++++++ gorgone/modules/scom/hooks.pm | 250 ++++++++ gorgone/packages/CryptX-0.064.tar.gz | Bin 0 -> 1619390 bytes .../perl-CryptX-0.064-1.el7.x86_64.rpm | Bin 0 -> 720852 bytes gorgone/packages/perl-CryptX.spec | 46 ++ gorgone/schema/centreond_database.sql | 31 + gorgone/test-client.pl | 183 ++++++ 41 files changed, 7519 insertions(+) create mode 100644 gorgone/centreon/centreond/clientzmq.pm create mode 100644 gorgone/centreon/centreond/common.pm create mode 100644 gorgone/centreon/misc/db.pm create mode 100644 gorgone/centreon/misc/http/backend/curl.pm create mode 100644 gorgone/centreon/misc/http/backend/curlconstants.pm create mode 100644 gorgone/centreon/misc/http/backend/lwp.pm create mode 100644 gorgone/centreon/misc/http/backend/useragent.pm create mode 100644 gorgone/centreon/misc/http/http.pm create mode 100644 gorgone/centreon/misc/lock.pm create mode 100644 gorgone/centreon/misc/logger.pm create mode 100644 gorgone/centreon/misc/misc.pm create mode 100644 gorgone/centreon/misc/objects/host.pm create mode 100644 gorgone/centreon/misc/objects/object.pm create mode 100644 gorgone/centreon/misc/objects/organization.pm create mode 100644 gorgone/centreon/script.pm create mode 100644 gorgone/centreon/script/centreondcore.pm create mode 100644 gorgone/centreond create mode 100644 gorgone/config/centreond-poller.ini create mode 100644 gorgone/config/centreond-poller2.ini create mode 100644 gorgone/config/centreond.ini create mode 100644 gorgone/docs/guide.rst create mode 100644 gorgone/keys/central/privkey.pem create mode 100644 gorgone/keys/central/pubkey.crt create mode 100644 gorgone/keys/poller/privkey.pem create mode 100644 gorgone/keys/poller/pubkey.crt create mode 100644 gorgone/modules/centreondacl/class.pm create mode 100644 gorgone/modules/centreondacl/hooks.pm create mode 100644 gorgone/modules/centreondaction/class.pm create mode 100644 gorgone/modules/centreondaction/hooks.pm create mode 100644 gorgone/modules/centreondcron/class.pm create mode 100644 gorgone/modules/centreondcron/hooks.pm create mode 100644 gorgone/modules/centreondproxy/class.pm create mode 100644 gorgone/modules/centreondproxy/hooks.pm create mode 100644 gorgone/modules/centreondpull/hooks.pm create mode 100644 gorgone/modules/scom/class.pm create mode 100644 gorgone/modules/scom/hooks.pm create mode 100644 gorgone/packages/CryptX-0.064.tar.gz create mode 100644 gorgone/packages/perl-CryptX-0.064-1.el7.x86_64.rpm create mode 100644 gorgone/packages/perl-CryptX.spec create mode 100644 gorgone/schema/centreond_database.sql create mode 100644 gorgone/test-client.pl diff --git a/gorgone/centreon/centreond/clientzmq.pm b/gorgone/centreon/centreond/clientzmq.pm new file mode 100644 index 00000000000..1a4188bcd11 --- /dev/null +++ b/gorgone/centreon/centreond/clientzmq.pm @@ -0,0 +1,204 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package centreon::centreond::clientzmq; + +use strict; +use warnings; +use centreon::centreond::common; +use ZMQ::LibZMQ4; +use ZMQ::Constants qw(:all); + +my $connectors = {}; +my $callbacks = {}; +my $sockets = {}; +my $polls = {}; + +sub new { + my ($class, %options) = @_; + my $connector = {}; + $connector->{logger} = $options{logger}; + $connector->{identity} = $options{identity}; + $connector->{cipher} = $options{cipher}; + $connector->{vector} = $options{vector}; + $connector->{symkey} = undef; + $connector->{pubkey} = centreon::centreond::common::loadpubkey(pubkey => $options{pubkey}); + $connector->{target_type} = $options{target_type}; + $connector->{target_path} = $options{target_path}; + $connector->{ping} = defined($options{ping}) ? $options{ping} : -1; + $connector->{ping_timeout} = defined($options{ping_timeout}) ? $options{ping_timeout} : 30; + $connector->{ping_progress} = 0; + $connector->{ping_time} = time(); + $connector->{ping_timeout_time} = time(); + + $connectors->{$options{identity}} = $connector; + bless $connector, $class; + return $connector; +} + +sub init { + my ($self, %options) = @_; + + $self->{handshake} = 0; + $sockets->{$self->{identity}} = centreon::centreond::common::connect_com(zmq_type => 'ZMQ_DEALER', name => $self->{identity}, + logger => $self->{logger}, + type => $self->{target_type}, + path => $self->{target_path}); + $callbacks->{$self->{identity}} = $options{callback} if (defined($options{callback})); +} + +sub close { + my ($self, %options) = @_; + + zmq_close($sockets->{$self->{identity}}); +} + +sub is_connected { + my ($self, %options) = @_; + + # Should be connected (not 100% sure) + if ($self->{handshake} == 2) { + return (0, $self->{ping_time}); + } + return -1; +} + +sub ping { + my ($self, %options) = @_; + my $status = 0; + + if ($self->{ping} > 0 && $self->{ping_progress} == 0 && + time() - $self->{ping_time} > $self->{ping}) { + $self->{ping_progress} = 1; + $self->{ping_timeout_time} = time(); + my $action = defined($options{action}) ? $options{action} : 'PING'; + $self->send_message(action => $action, data => $options{data}, json_encode => $options{json_encode}); + $status = 1; + } + if ($self->{ping_progress} == 1 && + time() - $self->{ping_timeout_time} > $self->{ping_timeout}) { + $self->{logger}->writeLogError("no ping response") if (defined($self->{logger})); + $self->{ping_progress} = 0; + zmq_close($sockets->{$self->{identity}}); + $self->init(); + push @{$options{poll}}, $self->get_poll(); + $status = 1; + } + + push @{$options{poll}}, $self->get_poll(); + return $status; +} + +sub get_poll { + my ($self, %options) = @_; + + $polls->{$sockets->{$self->{identity}}} = { + socket => $sockets->{$self->{identity}}, + events => ZMQ_POLLIN, + callback => sub { + event(identity => $self->{identity}); + } + }; + return $polls->{$sockets->{$self->{identity}}}; +} + +sub event { + my (%options) = @_; + + # We have a response. So it's ok :) + if ($connectors->{$options{identity}}->{ping_progress} == 1) { + $connectors->{$options{identity}}->{ping_progress} = 0; + } + $connectors->{$options{identity}}->{ping_time} = time(); + while (1) { + my $message = centreon::centreond::common::zmq_dealer_read_message(socket => $sockets->{$options{identity}}); + + # in progress + if ($connectors->{$options{identity}}->{handshake} == 0 || $connectors->{$options{identity}}->{handshake} == 1) { + my ($status, $symkey, $hostname) = centreon::centreond::common::client_get_secret(pubkey => $connectors->{$options{identity}}->{pubkey}, + message => $message); + if ($status == -1) { + $connectors->{$options{identity}}->{handshake} = 0; + return ; + } + $connectors->{$options{identity}}->{symkey} = $symkey; + $connectors->{$options{identity}}->{handshake} = 2; + if (defined($connectors->{$options{identity}}->{logger})) { + $connectors->{$options{identity}}->{logger}->writeLogInfo("Client connected successfuly to '" . $connectors->{$options{identity}}->{target_type} . '//' . $connectors->{$options{identity}}->{target_path}); + } + } else { + my ($status, $data) = centreon::centreond::common::uncrypt_message( + message => $message, + cipher => $connectors->{$options{identity}}->{cipher}, + vector => $connectors->{$options{identity}}->{vector}, + symkey => $connectors->{$options{identity}}->{symkey} + ); + + if ($status == -1 || $data !~ /^\[(.+?)\]\s+\[(.*?)\]\s+(?:\[(.*?)\]\s*(.*)|(.*))$/m) { + $connectors->{$options{identity}}->{handshake} = 0; + return ; + } + + if (defined($callbacks->{$options{identity}})) { + $callbacks->{$options{identity}}->(identity => $options{identity}, data => $data); + } + } + + last unless (centreon::centreond::common::zmq_still_read(socket => $sockets->{$options{identity}})); + } +} + +sub send_message { + my ($self, %options) = @_; + + if ($self->{handshake} == 0) { + my $message = '[HELO] [' . $self->{identity} . ']'; + my ($status, $ciphertext) = centreon::centreond::common::client_helo_encrypt( + pubkey => $self->{pubkey}, + message => $message + ); + if ($status == -1) { + return (-1, 'crypt handshake issue'); + } + $self->{handshake} = 1; + + zmq_sendmsg($sockets->{$self->{identity}}, $ciphertext); + zmq_poll([$self->get_poll()], 10000); + } + + if ($self->{handshake} == 1) { + $self->{handshake} = 0; + return (-1, 'Handshake timeout'); + } + if ($self->{handshake} == 0) { + return (-1, 'Handshake issue'); + } + + centreon::centreond::common::zmq_send_message( + socket => $sockets->{$self->{identity}}, + cipher => $self->{cipher}, + symkey => $self->{symkey}, + vector => $self->{vector}, + %options + ); + return 0; +} + +1; diff --git a/gorgone/centreon/centreond/common.pm b/gorgone/centreon/centreond/common.pm new file mode 100644 index 00000000000..ef048689e74 --- /dev/null +++ b/gorgone/centreon/centreond/common.pm @@ -0,0 +1,539 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package centreon::centreond::common; + +use strict; +use warnings; +use ZMQ::LibZMQ4; +use ZMQ::Constants qw(:all); +use JSON; +use File::Basename; +use Config::IniFiles; +use Crypt::OpenSSL::RSA; +use Crypt::OpenSSL::Random; +use Crypt::CBC; +use Data::Dumper; + +my %zmq_type = ('ZMQ_ROUTER' => ZMQ_ROUTER, 'ZMQ_DEALER' => ZMQ_DEALER); +my $privkey; + +sub read_config { + my (%options) = @_; + my %config; + + tie %config, 'Config::IniFiles', (-file => $options{config_file}); + if (@Config::IniFiles::errors) { + $options{logger}->writeLogError("Parsinig extra config file error:"); + $options{logger}->writeLogError(join("\n", @Config::IniFiles::errors)); + exit(1); + } + return \%config; +} + +####################### +# Handshake functions +####################### + +sub loadpubkey { + my (%options) = @_; + my $string_key = ''; + + if (!open FILE, "<" . $options{pubkey}) { + $options{logger}->writeLogError("Cannot read file '$options{pubkey}': $!"); + exit(1); + } + while () { + $string_key .= $_; + } + close FILE; + + my $pubkey; + eval { + $pubkey = Crypt::OpenSSL::RSA->new_public_key($string_key); + $pubkey->use_pkcs1_padding(); + }; + if ($@) { + $options{logger}->writeLogError("Cannot load privkey '$options{pubkey}': $@"); + exit(1); + } + + return $pubkey; +} + +sub loadprivkey { + my (%options) = @_; + my $string_key = ''; + + if (!open FILE, "<" . $options{privkey}) { + $options{logger}->writeLogError("Cannot read file '$options{privkey}': $!"); + exit(1); + } + while () { + $string_key .= $_; + } + close FILE; + + eval { + $privkey = Crypt::OpenSSL::RSA->new_private_key($string_key); + $privkey->use_pkcs1_padding(); + }; + if ($@) { + $options{logger}->writeLogError("Cannot load privkey '$options{privkey}': $@"); + exit(1); + } +} + +sub zmq_core_key_response { + my (%options) = @_; + + if (defined($options{identity})) { + zmq_sendmsg($options{socket}, pack('H*', $options{identity}), ZMQ_NOBLOCK | ZMQ_SNDMORE); + } + my $crypttext; + eval { + $crypttext = $privkey->private_encrypt("[KEY] [$options{hostname}] [" . $options{symkey} . "]"); + }; + if ($@) { + $options{logger}->writeLogError("Encoding issue: " . $@); + return -1; + } + zmq_sendmsg($options{socket}, $crypttext, ZMQ_NOBLOCK); + return 0; +} + +sub zmq_core_response { + my (%options) = @_; + my $msg; + my $response_type = defined($options{response_type}) ? $options{response_type} : 'ACK'; + + if (defined($options{identity})) { + zmq_sendmsg($options{socket}, pack('H*', $options{identity}), ZMQ_NOBLOCK | ZMQ_SNDMORE); + } + + my $data = json_encode(data => { code => $options{code}, data => $options{data} }); + # We add 'target' for 'PONG', 'CONSTATUS'. Like that 'centreond-proxy can get it + $msg = '[' . $response_type . '] [' . (defined($options{token}) ? $options{token} : '') . '] ' . ($response_type eq 'PONG' ? '[] ' : '') . $data; + + if (defined($options{cipher})) { + my $cipher = Crypt::CBC->new(-key => $options{symkey}, + -keysize => length($options{symkey}), + -cipher => $options{cipher}, + -iv => $options{vector}, + -header => 'none', + -literal_key => 1 + ); + $msg = $cipher->encrypt($msg); + } + zmq_sendmsg($options{socket}, $msg, ZMQ_NOBLOCK); +} + +sub uncrypt_message { + my (%options) = @_; + my $plaintext; + + my $cipher = Crypt::CBC->new(-key => $options{symkey}, + -keysize => length($options{symkey}), + -cipher => $options{cipher}, + -iv => $options{vector}, + -header => 'none', + -literal_key => 1 + ); + eval { + $plaintext = $cipher->decrypt($options{message}); + }; + if ($@) { + if (defined($options{logger})) { + $options{logger}->writeLogError("Sym encrypt issue: " . $@); + } + return (-1, $@); + } + return (0, $plaintext); +} + +sub generate_token { + my (%options) = @_; + + my $token = Crypt::OpenSSL::Random::random_bytes(256); + return unpack('H*', $token); +} + +sub generate_symkey { + my (%options) = @_; + + my $random_key = Crypt::OpenSSL::Random::random_bytes($options{keysize}); + return (0, $random_key); +} + +sub client_get_secret { + my (%options) = @_; + my $plaintext; + + eval { + $plaintext = $options{pubkey}->public_decrypt($options{message}); + }; + if ($@) { + return (-1, "Decoding issue: $@"); + } + + $plaintext = unpack('H*', $plaintext); + + if ($plaintext !~ /^5b(.*?)5d(.*?)5b(.*?)5d(.*?)5b(.*)5d$/i) { + return (-1, 'Wrong protocol'); + } + + my $hostname = pack('H*', $3); + my $symkey = pack('H*', $5); + return (0, $symkey, $hostname); +} + +sub client_helo_encrypt { + my (%options) = @_; + my $ciphertext; + + eval { + $ciphertext = $options{pubkey}->encrypt($options{message}); + }; + if ($@) { + return (-1, "Decoding issue: $@"); + } + + return (0, $ciphertext); +} + +sub is_client_can_connect { + my (%options) = @_; + my $plaintext; + + eval { + $plaintext = $privkey->decrypt($options{message}); + }; + if ($@) { + $options{logger}->writeLogError("Decoding issue: " . $@); + return -1; + } + + if ($plaintext !~ /\[HELO\]\s+\[(.+)\]/) { + $options{logger}->writeLogError("Decoding issue. Protocol not good"); + return -1; + } + + $options{logger}->writeLogInfo("Connection from $1"); + return 0; +} + +sub is_handshake_done { + my (%options) = @_; + + my ($status, $sth) = $options{dbh}->query("SELECT `key` FROM centreond_identity WHERE identity = " . $options{dbh}->quote($options{identity}) . " ORDER BY id DESC"); + return if ($status == -1); + if (my $row = $sth->fetchrow_hashref()) { + return (1, pack('H*', $row->{key})); + } + return 0; +} + +####################### +# internal functions +####################### + +sub constatus { + my (%options) = @_; + + if (defined($options{centreond}->{modules_register}->{ $options{centreond}->{modules_id}->{$options{centreond_config}->{centreondcore}{proxy_name}} })) { + my $name = $options{centreond_config}->{$options{centreond_config}->{centreondcore}{proxy_name}}{module}; + my $method; + if (defined($name) && ($method = $name->can('get_constatus_result'))) { + return (0, { action => 'constatus', mesage => 'ok', data => $method->() }, 'CONSTATUS'); + } + } + + return (1, { action => 'constatus', mesage => 'cannot get value' }, 'CONSTATUS'); +} + +sub ping { + my (%options) = @_; + + #my $status = add_history(dbh => $options{centreond}->{db_centreond}, + # token => $options{token}, logger => $options{logger}, code => 0); + return (0, { action => 'ping', mesage => 'ping ok', id => $options{id} }, 'PONG'); +} + +sub putlog { + my (%options) = @_; + + my $data; + eval { + $data = JSON->new->utf8->decode($options{data}); + }; + if ($@) { + return (1, { mesage => 'request not well formatted' }); + } + + my $status = add_history(dbh => $options{centreond}->{db_centreond}, + etime => $data->{etime}, token => $data->{token}, data => json_encode(data => $data->{data}, logger => $options{logger}), code => $data->{code}); + if ($status == -1) { + return (1, { mesage => 'database issue' }); + } + return (0, { mesage => 'message inserted' }); +} + +sub getlog { + my (%options) = @_; + + my $data; + eval { + $data = JSON->new->utf8->decode($options{data}); + }; + if ($@) { + return (1, { mesage => 'request not well formatted' }); + } + + my %filters = (); + my ($filter, $filter_append) = ('', ''); + + foreach ((['id', '>'], ['token', '='], ['ctime', '>='], ['etime', '>'], ['code', '='])) { + if (defined($data->{${$_}[0]}) && $data->{${$_}[0]} ne '') { + $filter .= $filter_append . ${$_}[0] . ' ' . ${$_}[1] . ' ' . $options{centreond}->{db_centreond}->quote($data->{${$_}[0]}); + $filter_append = ' AND '; + } + } + + if ($filter eq '') { + return (1, { mesage => 'need at least one filter' }); + } + + my ($status, $sth) = $options{centreond}->{db_centreond}->query("SELECT * FROM centreond_history WHERE " . $filter); + if ($status == -1) { + return (1, { mesage => 'database issue' }); + } + + return (0, { action => 'getlog', result => $sth->fetchall_hashref('id'), id => $options{centreond}->{id} }); +} + +sub kill { + my (%options) = @_; + +} + +####################### +# Database functions +####################### + +sub update_identity { + my (%options) = @_; + + my ($status, $sth) = $options{dbh}->query("UPDATE centreond_identity SET `ctime` = " . $options{dbh}->quote(time()) . " WHERE `identity` = " . $options{dbh}->quote($options{identity})); + return $status; +} + +sub add_identity { + my (%options) = @_; + + my ($status, $sth) = $options{dbh}->query("INSERT INTO centreond_identity (`ctime`, `identity`, `key`) VALUES (" . + $options{dbh}->quote(time()) . ", " . $options{dbh}->quote($options{identity}) . ", " . $options{dbh}->quote(unpack('H*', $options{symkey})) . ")"); + return $status; +} + +sub add_history { + my (%options) = @_; + + if (defined($options{data}) && defined($options{json_encode})) { + return -1 if (!($options{data} = json_encode(data => $options{data}, logger => $options{logger}))); + } + if (!defined($options{ctime})) { + $options{ctime} = time(); + } + if (!defined($options{etime})) { + $options{etime} = time(); + } + + my @names = (); + my @values = (); + foreach (('data', 'token', 'ctime', 'etime', 'code')) { + if (defined($options{$_})) { + push @names, $_; + push @values, $options{dbh}->quote($options{$_}); + } + } + my ($status, $sth) = $options{dbh}->query("INSERT INTO centreond_history (" . join(',', @names) . ") VALUES (" . + join(',', @values) . ")"); + return $status; +} + +####################### +# Misc functions +####################### + +sub json_encode { + my (%options) = @_; + + my $data; + eval { + $data = JSON->new->utf8->encode($options{data}); + }; + if ($@) { + if (defined($options{logger})) { + $options{logger}->writeLogError("Cannot encode json data: $@"); + } + return undef; + } + + return $data; +} + +####################### +# Global ZMQ functions +####################### + +sub connect_com { + my (%options) = @_; + + my $context = zmq_init(); + my $socket = zmq_socket($context, $zmq_type{$options{zmq_type}}); + if (!defined($socket)) { + $options{logger}->writeLogError("Can't setup server: $!"); + exit(1); + } + + zmq_setsockopt($socket, ZMQ_IDENTITY, $options{name}); + zmq_setsockopt($socket, ZMQ_LINGER, defined($options{linger}) ? $options{linger} : 0); # 0 we discard + zmq_connect($socket, $options{type} . '://' . $options{path}); + return $socket; +} + +sub create_com { + my (%options) = @_; + + my $context = zmq_init(); + my $socket = zmq_socket($context, $zmq_type{$options{zmq_type}}); + if (!defined($socket)) { + $options{logger}->writeLogError("Can't setup server: $!"); + exit(1); + } + + zmq_setsockopt($socket, ZMQ_IDENTITY, $options{name}); + zmq_setsockopt($socket, ZMQ_LINGER, 0); # we discard + if ($options{type} eq 'tcp') { + zmq_bind($socket, 'tcp://' . $options{path}); + } elsif ($options{type} eq 'ipc') { + if (zmq_bind($socket, 'ipc://' . $options{path}) == -1) { + $options{logger}->writeLogError("Cannot bind ipc '$options{path}': $!"); + # try create dir + $options{logger}->writeLogError("Maybe directory not exist. We try to create it!!!"); + if (!mkdir(dirname($options{path}))) { + zmq_close($socket); + exit(1); + } + if (zmq_bind($socket, 'ipc://' . $options{path}) == -1) { + $options{logger}->writeLogError("Cannot bind ipc '$options{path}': $!"); + zmq_close($socket); + exit(1); + } + } + } else { + $options{logger}->writeLogError("zmq type '$options{type}' not managed"); + zmq_close($socket); + exit(1); + } + + return $socket; +} + +sub build_protocol { + my (%options) = @_; + my $data = $options{data}; + my $token = defined($options{token}) ? $options{token} : ''; + my $action = defined($options{action}) ? $options{action} : ''; + my $target = defined($options{target}) ? $options{target} : ''; + + if (defined($data)) { + if (defined($options{json_encode})) { + $data = json_encode(data => $data, logger => $options{logger}); + } + } else { + $data = json_encode(data => {}, logger => $options{logger}); + } + + return '[' . $action . '] [' . $token . '] [' . $target . '] ' . $data; +} + +sub zmq_send_message { + my (%options) = @_; + my $message = $options{message}; + + if (!defined($message)) { + $message = build_protocol(%options); + } + if (defined($options{identity})) { + zmq_sendmsg($options{socket}, $options{identity}, ZMQ_NOBLOCK | ZMQ_SNDMORE); + } + if (defined($options{cipher})) { + my $cipher = Crypt::CBC->new(-key => $options{symkey}, + -keysize => length($options{symkey}), + -cipher => $options{cipher}, + -iv => $options{vector}, + -header => 'none', + -literal_key => 1 + ); + $message = $cipher->encrypt($message); + } + zmq_sendmsg($options{socket}, $message, ZMQ_NOBLOCK); +} + +sub zmq_dealer_read_message { + my (%options) = @_; + + # Process all parts of the message + my $message = zmq_recvmsg($options{socket}); + my $data = zmq_msg_data($message); + + return $data; +} + +sub zmq_read_message { + my (%options) = @_; + + # Process all parts of the message + my $message = zmq_recvmsg($options{socket}); + my $identity = zmq_msg_data($message); + $message = zmq_recvmsg($options{socket}); + my $data = zmq_msg_data($message); + + return (unpack('H*', $identity), $data); +} + +sub zmq_still_read { + my (%options) = @_; + + return zmq_getsockopt($options{socket}, ZMQ_RCVMORE); +} + +sub add_zmq_pollin { + my (%options) = @_; + + push @{$options{poll}}, { + socket => $options{socket}, + events => ZMQ_POLLIN, + callback => $options{callback}, + }; +} + +1; diff --git a/gorgone/centreon/misc/db.pm b/gorgone/centreon/misc/db.pm new file mode 100644 index 00000000000..1bc10d44db9 --- /dev/null +++ b/gorgone/centreon/misc/db.pm @@ -0,0 +1,304 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package centreon::misc::db; + +use strict; +use warnings; +use DBI; + +sub new { + my ($class, %options) = @_; + my %defaults = + ( + logger => undef, + db => undef, + dsn => undef, + host => "localhost", + user => undef, + password => undef, + port => 3306, + force => 0, + type => "mysql" + ); + my $self = {%defaults, %options}; + $self->{type} = 'mysql' if (!defined($self->{type})); + + # strip double-quotes + if (defined($self->{dsn})) { + $self->{dsn} =~ s/^\s*"//; + $self->{dsn} =~ s/"\s*$//; + } + + $self->{instance} = undef; + $self->{args} = []; + bless $self, $class; + return $self; +} + +# Getter/Setter DB name +sub type { + my $self = shift; + if (@_) { + $self->{type} = shift; + } + return $self->{type}; +} + +# 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; + } else { + $self->{instance}->{AutoCommit} = 1; + } +} + +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)"); + } + } +} + +# Connection initializer +sub connect() { + my $self = shift; + my ($status, $count) = (0, 0); + + while (1) { + $self->{port} = 3306 if (!defined($self->{port}) && $self->{type} eq 'mysql'); + if (defined($self->{dsn})) { + $self->{instance} = DBI->connect( + "DBI:".$self->{dsn}, $self->{user}, $self->{password}, + { RaiseError => 0, PrintError => 0, AutoCommit => 1 } + ); + } elsif ($self->{type} =~ /SQLite/i) { + $self->{instance} = DBI->connect( + "DBI:".$self->{type} + .":".$self->{db}, + $self->{user}, + $self->{password}, + { RaiseError => 0, PrintError => 0, AutoCommit => 1 } + ); + } else { + $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; + $self->{logger}->writeLogError("MySQL error : cannot connect to database " . $self->{db} . ": " . $DBI::errstr . " (caller: $package:$filename:$line) (try: $count)"); + if ($self->{force} == 0 || ($self->{force} == 2 && $count == 1)) { + $status = -1; + last; + } + sleep(1); + $count++; + } + 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"); +SQL error: $error (caller: $package:$filename:$line) +Query: $query +EOE + $self->disconnect(); + $self->{instance} = undef; +} + +sub query { + my $self = shift; + my $query = shift; + my (%options) = @_; + my ($status, $count) = (0, -1); + 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} != 1) { + $self->{args} = []; + last; + } + } + + $count++; + $statement_handle = $self->{instance}->prepare($query); + if (!defined($statement_handle)) { + $self->error($self->{instance}->errstr, $query); + $status = -1; + last if ($self->{force} == 0 || ($self->{force} == 2 && $count == 1)); + sleep(1); + next; + } + + if (defined($options{prepare_only})) { + return ($status, $statement_handle); + } + + my $rv = $statement_handle->execute; + if (!$rv) { + $self->error($statement_handle->errstr, $query); + $status = -1; + last if ($self->{force} == 0 || ($self->{force} == 2 && $count == 1)); + sleep(1); + next; + } + last; + } + return ($status, $statement_handle); +} + +1; diff --git a/gorgone/centreon/misc/http/backend/curl.pm b/gorgone/centreon/misc/http/backend/curl.pm new file mode 100644 index 00000000000..eb7348a82bd --- /dev/null +++ b/gorgone/centreon/misc/http/backend/curl.pm @@ -0,0 +1,419 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package centreon::misc::http::backend::curl; + +use strict; +use warnings; +use URI; +use centreon::plugins::misc; + +sub new { + my ($class, %options) = @_; + my $self = {}; + bless $self, $class; + + $self->{logger} = $options{logger}; + + return $self; +} + +sub check_options { + my ($self, %options) = @_; + + if (centreon::plugins::misc::mymodule_load( + logger => $self->{logger}, module => 'Net::Curl::Easy', + error_msg => "Cannot load module 'Net::Curl::Easy'." + ) == 1) { + return 1; + } + if (centreon::plugins::misc::mymodule_load( + logger => $self->{logger}, module => 'centreon::misc::http::backend::curlconstants', + error_msg => "Cannot load module 'centreon::misc::http::backend::curlconstants'." + ) == 1) { + return 1; + } + $self->{constant_cb} = \¢reon::misc::http::backend::curlconstants::get_constant_value; + + if (!defined($options{request}->{curl_opt})) { + $options{request}->{curl_opt} = []; + } +} + +my $http_code_explained = { + 100 => 'Continue', + 101 => 'Switching Protocols', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => '(Unused)', + 307 => 'Temporary Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', +}; + +sub cb_debug { + my ($easy, $type, $data, $uservar) = @_; + + my $msg = ''; + if ($type == $uservar->{constant_cb}->(name => 'CURLINFO_TEXT')) { + $msg = sprintf("== Info: %s", $data); + } + if ($type == $uservar->{constant_cb}->(name => 'CURLINFO_HEADER_OUT')) { + $msg = sprintf("=> Send header: %s", $data); + } + if ($type == $uservar->{constant_cb}->(name => 'CURLINFO_DATA_OUT')) { + $msg = sprintf("=> Send data: %s", $data); + } + if ($type == $uservar->{constant_cb}->(name => 'CURLINFO_SSL_DATA_OUT')) { + $msg = sprintf("=> Send SSL data: %s", $data); + } + if ($type == $uservar->{constant_cb}->(name => 'CURLINFO_HEADER_IN')) { + $msg = sprintf("=> Recv header: %s", $data); + } + if ($type == $uservar->{constant_cb}->(name => 'CURLINFO_DATA_IN')) { + $msg = sprintf("=> Recv data: %s", $data); + } + if ($type == $uservar->{constant_cb}->(name => 'CURLINFO_SSL_DATA_IN')) { + $msg = sprintf("=> Recv SSL data: %s", $data); + } + + $uservar->{logger}->writeLogDebug($msg); + return 0; +} + +sub curl_setopt { + my ($self, %options) = @_; + + eval { + $self->{curl_easy}->setopt($options{option}, $options{parameter}); + }; + if ($@) { + $self->{logger}->writeLogError("curl setopt error: '" . $@ . "'."); + } +} + +sub set_method { + my ($self, %options) = @_; + + if ($options{request}->{method} eq 'GET') { + return ; + } + + if ($options{content_type_forced} == 1) { + $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_POSTFIELDS'), parameter => $options{request}->{query_form_post}) + if (defined($options{request}->{query_form_post}) && $options{request}->{query_form_post} ne ''); + } elsif (defined($options{request}->{post_params})) { + my $uri_post = URI->new(); + $uri_post->query_form($options{request}->{post_params}); + push @{$options{headers}}, 'Content-Type: application/x-www-form-urlencoded'; + $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_POSTFIELDS'), parameter => $uri_post->query); + } + + if ($options{request}->{method} eq 'POST') { + $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_POST'), parameter => 1); + } + if ($options{request}->{method} eq 'PUT') { + $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_CUSTOMREQUEST'), parameter => $options{request}->{method}); + } + if ($options{request}->{method} eq 'DELETE') { + $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_CUSTOMREQUEST'), parameter => $options{request}->{method}); + } +} + +sub set_auth { + my ($self, %options) = @_; + + if (defined($options{request}->{credentials})) { + if (defined($options{request}->{basic})) { + $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_HTTPAUTH'), parameter => $self->{constant_cb}->(name => 'CURLAUTH_BASIC')); + } elsif (defined($options{request}->{ntlmv2})) { + $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_HTTPAUTH'), parameter => $self->{constant_cb}->(name => 'CURLAUTH_NTLM')); + } else { + $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_HTTPAUTH'), parameter => $self->{constant_cb}->(name => 'CURLAUTH_ANY')); + } + $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_USERPWD'), parameter => $options{request}->{username} . ':' . $options{request}->{password}); + } + + if (defined($options{request}->{cert_file}) && $options{request}->{cert_file} ne '') { + $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_SSLCERT'), parameter => $options{request}->{cert_file}); + $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_SSLKEY'), parameter => $options{request}->{key_file}); + $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_KEYPASSWD'), parameter => $options{request}->{cert_pwd}); + } + + $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_SSLCERTTYPE'), parameter => "PEM"); + if (defined($options{request}->{cert_pkcs12})) { + $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_SSLCERTTYPE'), parameter => "P12"); + } +} + +sub set_proxy { + my ($self, %options) = @_; + + if (defined($options{request}->{proxyurl}) && $options{request}->{proxyurl} ne '') { + if ($options{request}->{proxyurl} =~ /^(?:http|https):\/\/(.*?):(.*?)@/) { + $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_PROXYUSERNAME'), parameter => $1); + $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_PROXYPASSWORD'), parameter => $2); + $options{request}->{proxyurl} =~ s/\/\/$1:$2@//; + } + + $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_PROXY'), parameter => $options{request}->{proxyurl}); + } + + if (defined($options{request}->{proxypac}) && $options{request}->{proxypac} ne '') { + $self->{logger}->writeLogError('Unsupported proxypac option'); + } +} + +sub set_extra_curl_opt { + my ($self, %options) = @_; + + my $fields = { key => '', value => '' }; + foreach (@{$options{request}->{curl_opt}}) { + ($fields->{key}, $fields->{value}) = split /=>/; + foreach my $label ('key', 'value') { + $fields->{$label} = centreon::plugins::misc::trim($fields->{$label}); + if ($fields->{$label} =~ /^CURLOPT|CURL/) { + $fields->{$label} = $self->{constant_cb}->(name => $fields->{$label}); + } + } + + $self->curl_setopt(option => $fields->{key}, parameter => $fields->{value}); + } +} + +sub cb_get_header { + my ($easy, $header, $uservar) = @_; + + $header =~ s/[\r\n]//g; + if ($header =~ /^[\r\n]*$/) { + $uservar->{nheaders}++; + } else { + $uservar->{response_headers}->[$uservar->{nheaders}] = {} + if (!defined($uservar->{response_headers}->[$uservar->{nheaders}])); + if ($header =~ /^(\S(?:.*?))\s*:\s*(.*)/) { + my $header_name = lc($1); + $uservar->{response_headers}->[$uservar->{nheaders}]->{$header_name} = [] + if (!defined($uservar->{response_headers}->[$uservar->{nheaders}]->{$header_name})); + push @{$uservar->{response_headers}->[$uservar->{nheaders}]->{$header_name}}, $2; + } else { + $uservar->{response_headers}->[$uservar->{nheaders}]->{response_line} = $header; + } + } + + return length($_[1]); +} + +sub request { + my ($self, %options) = @_; + + $self->{curl_easy} = Net::Curl::Easy->new(); + + if ($self->{logger}->is_debug()) { + $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_DEBUGFUNCTION'), parameter => \&cb_debug); + $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_DEBUGDATA'), parameter => $self); + $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_VERBOSE'), parameter => 1); + } + + if (defined($options{request}->{timeout})) { + $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_TIMEOUT'), parameter => $options{request}->{timeout}); + } + if (defined($options{request}->{cookies_file})) { + $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_COOKIEFILE'), parameter => $options{request}->{cookies_file}); + } + + $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_FOLLOWLOCATION'), parameter => 1); + if (defined($options{request}->{no_follow})) { + $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_FOLLOWLOCATION'), parameter => 0); + } + + my $url; + if (defined($options{request}->{full_url})) { + $url = $options{request}->{full_url}; + } elsif (defined($options{request}->{port}) && $options{request}->{port} =~ /^[0-9]+$/) { + $url = $options{request}->{proto}. "://" . $options{request}->{hostname} . ':' . $options{request}->{port} . $options{request}->{url_path}; + } else { + $url = $options{request}->{proto}. "://" . $options{request}->{hostname} . $options{request}->{url_path}; + } + + if (defined($options{request}->{http_peer_addr}) && $options{request}->{http_peer_addr} ne '') { + $url =~ /^(?:http|https):\/\/(.*?)(\/|\:|$)/; + $self->{curl_easy}->pushopt($self->{constant_cb}->(name => 'CURLOPT_RESOLVE'), + [$1 . ':' . $options{request}->{port_force} . ':' . $options{request}->{http_peer_addr}]); + } + + my $uri = URI->new($url); + if (defined($options{request}->{get_params})) { + $uri->query_form($options{request}->{get_params}); + } + + $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_URL'), parameter => $uri); + + my $headers = []; + my $content_type_forced = 0; + foreach my $key (keys %{$options{request}->{headers}}) { + push @$headers, $key . ':' . $options{request}->{headers}->{$key}; + if ($key =~ /content-type/i) { + $content_type_forced = 1; + } + } + + $self->set_method(%options, content_type_forced => $content_type_forced, headers => $headers); + + if (scalar(@$headers) > 0) { + $self->{curl_easy}->pushopt($self->{constant_cb}->(name => 'CURLOPT_HTTPHEADER'), $headers); + } + + if (defined($options{request}->{cacert_file}) && $options{request}->{cacert_file} ne '') { + $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_CAINFO'), parameter => $options{request}->{cacert_file}); + } + + $self->set_auth(%options); + $self->set_proxy(%options); + $self->set_extra_curl_opt(%options); + + $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_FILE'), parameter => \$self->{response_body}); + $self->{nheaders} = 0; + $self->{response_headers} = [{}]; + $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_HEADERDATA'), parameter => $self); + $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_HEADERFUNCTION'), parameter => \&cb_get_header); + + eval { + $self->{curl_easy}->perform(); + }; + if ($@) { + $self->{logger}->writeLogError('curl perform error : ' . $@); + return 1; + } + + $self->{response_code} = $self->{curl_easy}->getinfo($self->{constant_cb}->(name => 'CURLINFO_RESPONSE_CODE')); + + return (0, $self->{response_body}); +} + +sub get_headers { + my ($self, %options) = @_; + + my $headers = ''; + foreach (keys %{$self->{response_headers}->[$options{nheader}]}) { + next if (/response_line/); + foreach my $value (@{$self->{response_headers}->[$options{nheader}]->{$_}}) { + $headers .= "$_: " . $value . "\n"; + } + } + + return $headers; +} + +sub get_first_header { + my ($self, %options) = @_; + + if (!defined($options{name})) { + return $self->get_headers(nheader => 0); + } + + return undef + if (!defined($self->{response_headers}->[0]->{ lc($options{name}) })); + return wantarray ? @{$self->{response_headers}->[0]->{ lc($options{name}) }} : $self->{response_headers}->[0]->{ lc($options{name}) }->[0]; +} + +sub get_header { + my ($self, %options) = @_; + + if (!defined($options{name})) { + return $self->get_headers(nheader => -1); + } + + return undef + if (!defined($self->{response_headers}->[-1]->{ lc($options{name}) })); + return wantarray ? @{$self->{response_headers}->[-1]->{ lc($options{name}) }} : $self->{response_headers}->[-1]->{ lc($options{name}) }->[0]; +} + +sub get_code { + my ($self, %options) = @_; + + return $self->{response_code}; +} + +sub get_message { + my ($self, %options) = @_; + + return $http_code_explained->{$self->{response_code}}; +} + +1; + +__END__ + +=head1 NAME + +HTTP Curl backend layer. + +=head1 SYNOPSIS + +HTTP Curl backend layer. + +=head1 BACKEND CURL OPTIONS + +=over 8 + +=item B<--curl-opt> + +Set CURL Options (--curl-opt="CURLOPT_SSL_VERIFYPEER => 0" --curl-opt="CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1_1" ). + +=back + +=head1 DESCRIPTION + +B. + +=cut diff --git a/gorgone/centreon/misc/http/backend/curlconstants.pm b/gorgone/centreon/misc/http/backend/curlconstants.pm new file mode 100644 index 00000000000..9cbe53abddd --- /dev/null +++ b/gorgone/centreon/misc/http/backend/curlconstants.pm @@ -0,0 +1,33 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package centreon::misc::http::backend::curlconstants; + +use strict; +use warnings; +use Net::Curl::Easy qw(:constants); + +sub get_constant_value { + my (%options) = @_; + + return eval $options{name}; +} + +1; diff --git a/gorgone/centreon/misc/http/backend/lwp.pm b/gorgone/centreon/misc/http/backend/lwp.pm new file mode 100644 index 00000000000..54e58540501 --- /dev/null +++ b/gorgone/centreon/misc/http/backend/lwp.pm @@ -0,0 +1,299 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package centreon::misc::http::backend::lwp; + +use strict; +use warnings; +use centreon::misc::http::backend::useragent; +use URI; +use IO::Socket::SSL; +use centreon::misc::misc; + +sub new { + my ($class, %options) = @_; + my $self = {}; + bless $self, $class; + + $self->{logger} = $options{logger}; + $self->{ua} = undef; + $self->{debug_handlers} = 0; + + return $self; +} + +sub check_options { + my ($self, %options) = @_; + + $self->{ssl_context} = ''; + if (!defined($options{request}->{ssl_opt})) { + $options{request}->{ssl_opt} = []; + } + if (defined($options{request}->{ssl}) && $options{request}->{ssl} ne '') { + push @{$options{request}->{ssl_opt}}, 'SSL_version => ' . $options{request}->{ssl}; + } + if (defined($options{request}->{cert_file}) && !defined($options{request}->{cert_pkcs12})) { + push @{$options{request}->{ssl_opt}}, 'SSL_use_cert => 1'; + push @{$options{request}->{ssl_opt}}, 'SSL_cert_file => "' . $options{request}->{cert_file} . '"'; + push @{$options{request}->{ssl_opt}}, 'SSL_key_file => "' . $options{request}->{key_file} . '"' + if (defined($options{request}->{key_file})); + push @{$options{request}->{ssl_opt}}, 'SSL_ca_file => "' . $options{request}->{cacert_file} . '"' + if (defined($options{request}->{cacert_file})); + } + my $append = ''; + foreach (@{$options{request}->{ssl_opt}}) { + if ($_ ne '') { + $self->{ssl_context} .= $append . $_; + $append = ', '; + } + } +} + +sub set_proxy { + my ($self, %options) = @_; + + if (defined($options{request}->{proxypac}) && $options{request}->{proxypac} ne '') { + if (centreon::misc::misc::mymodule_load( + logger => $self->{logger}, module => 'HTTP::ProxyPAC', + error_msg => "Cannot load module 'HTTP::ProxyPAC'." + ) == 1) { + return 1; + } + my ($pac, $pac_uri); + eval { + if ($options{request}->{proxypac} =~ /^(http|https):\/\//) { + $pac_uri = URI->new($options{request}->{proxypac}); + $pac = HTTP::ProxyPAC->new($pac_uri); + } else { + $pac = HTTP::ProxyPAC->new($options{request}->{proxypac}); + } + }; + if ($@) { + $self->{logger}->writeLogError('issue to load proxypac: ' . $@); + return 1; + } + my $res = $pac->find_proxy($options{url}); + if (defined($res->direct) && $res->direct != 1) { + my $proxy_uri = URI->new($res->proxy); + $proxy_uri->userinfo($pac_uri->userinfo) if (defined($pac_uri->userinfo)); + $self->{ua}->proxy(['http', 'https'], $proxy_uri->as_string); + } + } + if (defined($options{request}->{proxyurl}) && $options{request}->{proxyurl} ne '') { + $self->{ua}->proxy(['http', 'https'], $options{request}->{proxyurl}); + } +} + +sub request { + my ($self, %options) = @_; + + my $request_options = $options{request}; + if (!defined($self->{ua})) { + $self->{ua} = centreon::plugins::backend::http::useragent->new( + keep_alive => 1, protocols_allowed => ['http', 'https'], timeout => $request_options->{timeout}, + credentials => $request_options->{credentials}, username => $request_options->{username}, password => $request_options->{password}); + if (defined($request_options->{cookies_file})) { + if (centreon::misc::misc::mymodule_load( + logger => $self->{logger}, module => 'HTTP::Cookies', + error_msg => "Cannot load module 'HTTP::Cookies'." + ) == 1) { + return 1; + } + $self->{ua}->cookie_jar(HTTP::Cookies->new(file => $request_options->{cookies_file}, + autosave => 1)); + } + } + + if ($self->{logger}->is_debug() && $self->{debug_handlers} == 0) { + $self->{debug_handlers} = 1; + $self->{ua}->add_handler("request_send", sub { + my ($response, $ua, $handler) = @_; + + $self->{logger}->writeLogDebug("======> request send"); + $self->{logger}->writeLogDebug($response->as_string); + return ; + }); + $self->{ua}->add_handler("response_done", sub { + my ($response, $ua, $handler) = @_; + + $self->{logger}->writeLogDebug("======> response done"); + $self->{logger}->writeLogDebug($response->as_string); + return ; + }); + } + + if (defined($request_options->{no_follow})) { + $self->{ua}->requests_redirectable(undef); + } else { + $self->{ua}->requests_redirectable([ 'GET', 'HEAD', 'POST' ]); + } + if (defined($request_options->{http_peer_addr})) { + push @LWP::Protocol::http::EXTRA_SOCK_OPTS, PeerAddr => $request_options->{http_peer_addr}; + } + + my ($req, $url); + if (defined($request_options->{full_url})) { + $url = $request_options->{full_url}; + } elsif (defined($request_options->{port}) && $request_options->{port} =~ /^[0-9]+$/) { + $url = $request_options->{proto}. "://" . $request_options->{hostname} . ':' . $request_options->{port} . $request_options->{url_path}; + } else { + $url = $request_options->{proto}. "://" . $request_options->{hostname} . $request_options->{url_path}; + } + + my $uri = URI->new($url); + if (defined($request_options->{get_params})) { + $uri->query_form($request_options->{get_params}); + } + $req = HTTP::Request->new($request_options->{method}, $uri); + + my $content_type_forced; + foreach my $key (keys %{$request_options->{headers}}) { + if ($key !~ /content-type/i) { + $req->header($key => $request_options->{headers}->{$key}); + } else { + $content_type_forced = $request_options->{headers}->{$key}; + } + } + + if ($request_options->{method} eq 'POST') { + if (defined($content_type_forced)) { + $req->content_type($content_type_forced); + $req->content($request_options->{query_form_post}); + } else { + my $uri_post = URI->new(); + if (defined($request_options->{post_params})) { + $uri_post->query_form($request_options->{post_params}); + } + $req->content_type('application/x-www-form-urlencoded'); + $req->content($uri_post->query); + } + } + + if (defined($request_options->{credentials}) && defined($request_options->{ntlmv2})) { + if (centreon::misc::misc::mymodule_load( + logger => $self->{logger}, module => 'Authen::NTLM', + error_msg => "Cannot load module 'Authen::NTLM'." + ) == 1) { + return 1; + } + Authen::NTLM::ntlmv2(1); + } + + if (defined($request_options->{credentials}) && defined($request_options->{basic})) { + $req->authorization_basic($request_options->{username}, $request_options->{password}); + } + + $self->set_proxy(request => $request_options, url => $url); + + if (defined($request_options->{cert_pkcs12}) && $request_options->{cert_file} ne '' && $request_options->{cert_pwd} ne '') { + eval "use Net::SSL"; die $@ if $@; + $ENV{HTTPS_PKCS12_FILE} = $request_options->{cert_file}; + $ENV{HTTPS_PKCS12_PASSWORD} = $request_options->{cert_pwd}; + } + + if (defined($self->{ssl_context}) && $self->{ssl_context} ne '') { + my $context = new IO::Socket::SSL::SSL_Context(eval $self->{ssl_context}); + IO::Socket::SSL::set_default_context($context); + } + + $self->{response} = $self->{ua}->request($req); + + $self->{headers} = $self->{response}->headers(); + return (0, $self->{response}->content); +} + +sub get_headers { + my ($self, %options) = @_; + + my $headers = ''; + foreach ($options{response}->header_field_names()) { + $headers .= "$_: " . $options{response}->header($_) . "\n"; + } + + return $headers; +} + +sub get_first_header { + my ($self, %options) = @_; + + my @redirects = $self->{response}->redirects(); + if (!defined($options{name})) { + return $self->get_headers(response => defined($redirects[0]) ? $redirects[0] : $self->{response}); + } + + return + defined($redirects[0]) ? + $redirects[0]->headers()->header($options{name}) : + $self->{headers}->header($options{name}) + ; +} + +sub get_header { + my ($self, %options) = @_; + + if (!defined($options{name})) { + return $self->get_headers(response => $self->{response}); + } + return $self->{headers}->header($options{name}); +} + +sub get_code { + my ($self, %options) = @_; + + return $self->{response}->code(); +} + +sub get_message { + my ($self, %options) = @_; + + return $self->{response}->message(); +} + +1; + +__END__ + +=head1 NAME + +HTTP LWP backend layer. + +=head1 SYNOPSIS + +HTTP LWP backend layer. + +=head1 BACKEND LWP OPTIONS + +=over 8 + +=item B<--ssl-opt> + +Set SSL Options (--ssl-opt="SSL_version => TLSv1" --ssl-opt="SSL_verify_mode => SSL_VERIFY_NONE"). + +=item B<--ssl> + +Set SSL version (--ssl=TLSv1). + +=back + +=head1 DESCRIPTION + +B. + +=cut diff --git a/gorgone/centreon/misc/http/backend/useragent.pm b/gorgone/centreon/misc/http/backend/useragent.pm new file mode 100644 index 00000000000..45f6386f5c2 --- /dev/null +++ b/gorgone/centreon/misc/http/backend/useragent.pm @@ -0,0 +1,50 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package centreon::misc::http::backend::useragent; + +use strict; +use warnings; +use base 'LWP::UserAgent'; + +sub new { + my ($class, %options) = @_; + my $self = {}; + bless $self, $class; + + $self = LWP::UserAgent::new(@_); + $self->agent('centreon::misc::http::backend::useragent'); + + $self->{credentials} = $options{credentials} if defined($options{credentials}); + $self->{username} = $options{username} if defined($options{username}); + $self->{password} = $options{password} if defined($options{password}); + + return $self; +} + +sub get_basic_credentials { + my($self, $realm, $uri, $proxy) = @_; + return if $proxy; + return $self->{username}, $self->{password} if $self->{credentials} and wantarray; + return $self->{username}.":".$self->{password} if $self->{credentials}; + return undef; +} + +1; diff --git a/gorgone/centreon/misc/http/http.pm b/gorgone/centreon/misc/http/http.pm new file mode 100644 index 00000000000..4fab790ea37 --- /dev/null +++ b/gorgone/centreon/misc/http/http.pm @@ -0,0 +1,238 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package centreon::misc::http::http; + +use strict; +use warnings; +use centreon::misc::misc; + +sub new { + my ($class, %options) = @_; + my $self = {}; + bless $self, $class; + + if (centreon::misc::misc::mymodule_load( + logger => $options{logger}, module => 'centreon::misc::http::backend::lwp', + error_msg => "Cannot load module 'centreon::misc::http::backend::lwp'." + ) == 0) { + $self->{backend_lwp} = centreon::misc::http::backend::lwp->new(%options); + } + + if (centreon::misc::misc::mymodule_load( + logger => $options{logger}, module => 'centreon::misc::http::backend::curl', + error_msg => "Cannot load module 'centreon::misc::http::backend::curl'." + ) == 0) { + $self->{backend_curl} = centreon::misc::http::backend::curl->new(%options); + } + + $self->{logger} = $options{logger}; + $self->{options} = { + proto => 'http', + url_path => '/', + timeout => 5, + method => 'GET', + }; + + $self->{add_headers} = {}; + return $self; +} + +sub set_options { + my ($self, %options) = @_; + + $self->{options} = { %{$self->{options}} }; + foreach (keys %options) { + $self->{options}->{$_} = $options{$_} if (defined($options{$_})); + } +} + +sub add_header { + my ($self, %options) = @_; + + $self->{add_headers}->{$options{key}} = $options{value}; +} + +sub check_options { + my ($self, %options) = @_; + + $options{request}->{http_backend} = 'curl' + if (!defined($options{request}->{http_backend}) || $options{request}->{http_backend} eq ''); + $self->{http_backend} = $options{request}->{http_backend}; + if ($self->{http_backend} !~ /^\s*lwp|curl\s*$/i) { + $self->{logger}->writeLogError("Unsupported http backend specified '" . $self->{http_backend} . "'."); + return 1; + } + + if (($options{request}->{proto} ne 'http') && ($options{request}->{proto} ne 'https')) { + $self->{logger}->writeLogError("Unsupported protocol specified '" . $self->{option_results}->{proto} . "'."); + return 1; + } + if (!defined($options{request}->{hostname})) { + $self->{logger}->writeLogError("Please set the hostname option"); + return 1; + } + if ((defined($options{request}->{credentials})) && (!defined($options{request}->{username}) || !defined($options{request}->{password}))) { + $self->{logger}->writeLogError("You need to set --username= and --password= options when --credentials is used"); + return 1; + } + if ((defined($options{request}->{cert_pkcs12})) && (!defined($options{request}->{cert_file}) && !defined($options{request}->{cert_pwd}))) { + $self->{logger}->writeLogError("You need to set --cert-file= and --cert-pwd= options when --pkcs12 is used"); + return 1; + } + + $options{request}->{port_force} = $self->get_port(); + + $options{request}->{headers} = {}; + if (defined($options{request}->{header})) { + foreach (@{$options{request}->{header}}) { + if (/^(.*?):(.*)/) { + $options{request}->{headers}->{$1} = $2; + } + } + } + foreach (keys %{$self->{add_headers}}) { + $options{request}->{headers}->{$_} = $self->{add_headers}->{$_}; + } + + foreach my $method (('get', 'post')) { + if (defined($options{request}->{$method . '_param'})) { + $options{request}->{$method . '_params'} = {}; + foreach (@{$options{request}->{$method . '_param'}}) { + if (/^([^=]+)={0,1}(.*)$/) { + my $key = $1; + my $value = defined($2) ? $2 : 1; + if (defined($options{request}->{$method . '_params'}->{$key})) { + if (ref($options{request}->{$method . '_params'}->{$key}) ne 'ARRAY') { + $options{request}->{$method . '_params'}->{$key} = [ $options{request}->{$method . '_params'}->{$key} ]; + } + push @{$options{request}->{$method . '_params'}->{$key}}, $value; + } else { + $options{request}->{$method . '_params'}->{$key} = $value; + } + } + } + } + } + + $self->{'backend_' . $self->{http_backend}}->check_options(%options); + return 0; +} + +sub get_port { + my ($self, %options) = @_; + + my $port = ''; + if (defined($self->{options}->{port}) && $self->{options}->{port} ne '') { + $port = $self->{options}->{port}; + } else { + $port = 80 if ($self->{options}->{proto} eq 'http'); + $port = 443 if ($self->{options}->{proto} eq 'https'); + } + + return $port; +} + +sub get_port_request { + my ($self, %options) = @_; + + my $port = ''; + if (defined($self->{options}->{port}) && $self->{options}->{port} ne '') { + $port = $self->{options}->{port}; + } + return $port; +} + +sub request { + my ($self, %options) = @_; + + my $request_options = { %{$self->{options}} }; + foreach (keys %options) { + $request_options->{$_} = $options{$_} if (defined($options{$_})); + } + $self->check_options(request => $request_options); + + return $self->{'backend_' . $self->{http_backend}}->request(request => $request_options); +} + +sub get_first_header { + my ($self, %options) = @_; + + return $self->{'backend_' . $self->{http_backend}}->get_first_header(%options); +} + +sub get_header { + my ($self, %options) = @_; + + return $self->{'backend_' . $self->{http_backend}}->get_header(%options); +} + +sub get_code { + my ($self, %options) = @_; + + return $self->{'backend_' . $self->{http_backend}}->get_code(); +} + +sub get_message { + my ($self, %options) = @_; + + return $self->{'backend_' . $self->{http_backend}}->get_message(); +} + +1; + +__END__ + +=head1 NAME + +HTTP abstraction layer. + +=head1 SYNOPSIS + +HTTP abstraction layer for lwp and curl backends + +=head1 HTTP GLOBAL OPTIONS + +=over 8 + +=item B<--http-peer-addr> + +Set the address you want to connect (Useful if hostname is only a vhost. no ip resolve) + +=item B<--proxyurl> + +Proxy URL + +=item B<--proxypac> + +Proxy pac file (can be an url or local file) + +=item B<--http-backend> + +Set the backend used (Default: 'lwp') +For curl: --http-backend=curl + +=back + +=head1 DESCRIPTION + +B. + +=cut diff --git a/gorgone/centreon/misc/lock.pm b/gorgone/centreon/misc/lock.pm new file mode 100644 index 00000000000..a2dfc40db2b --- /dev/null +++ b/gorgone/centreon/misc/lock.pm @@ -0,0 +1,167 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package centreon::misc::lock; + +use strict; +use warnings; + +sub new { + my ($class, $name, %options) = @_; + my %defaults = (name => $name, pid => $$, 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::misc::lock::file; + +use base qw(centreon::misc::lock); + +sub new { + my $class = shift; + my $self = $class->SUPER::new(@_); + + if (!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::misc::lock::sql; + +use base qw(centreon::misc::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,pid,time_launch FROM cron_operation WHERE name LIKE '$self->{name}'" + ); + + return 1 if ($status == -1); + 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}; + $data->{pid} = -1 if (!defined($data->{pid})); + $self->{pid} = $data->{pid}; + $self->{previous_launch_time} = $data->{time_launch}; + if (defined $data->{running} && $data->{running} == 1) { + my $line = `ps -ef | grep -v grep | grep $self->{pid} | grep $self->{name}`; + return 0 if !length $line; + 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}', pid = '$self->{pid}' +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', pid = '-1' +WHERE id = '$self->{id}' +EOQ + } +} + +1; diff --git a/gorgone/centreon/misc/logger.pm b/gorgone/centreon/misc/logger.pm new file mode 100644 index 00000000000..89b7fc54e36 --- /dev/null +++ b/gorgone/centreon/misc/logger.pm @@ -0,0 +1,209 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package centreon::misc::logger; + +=head1 NOM + +centreon::misc::logger - Simple logging module + +=head1 SYNOPSIS + + #!/usr/bin/perl -w + + use strict; + use warnings; + + use centreon::polling; + + my $logger = new centreon::misc::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, + 4 => LOG_DEBUG); + +sub new { + my $class = shift; + + my $self = bless + { + file => 0, + filehandler => undef, + # 0 = nothing, 1 = critical, 3 = info, 7 = debug + severity => 3, + old_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 (defined($self->{filehandler})) { + $self->{filehandler}->close(); + } + if (open($self->{filehandler}, ">>", $file)){ + $self->{log_mode} = 1; + $self->{filehandler}->autoflush(1); + $self->{file_name} = $file; + return 1; + } + $self->{filehandler} = undef; + print STDERR "Cannot open file $file: $!\n"; + return 0; +} + +sub is_file_mode { + my $self = shift; + + if ($self->{log_mode} == 1) { + return 1; + } + return 0; +} + +sub is_debug { + my $self = shift; + + if (($self->{severity} & 4) == 0) { + return 0; + } + return 1; +} + +sub syslog_mode($$$) { + my ($self, $logopt, $facility) = @_; + + $self->{log_mode} = 2; + openlog($0, $logopt, $facility); + return 1; +} + +# For daemons +sub redirect_output { + my $self = shift; + + if ($self->is_file_mode()) { + open my $lfh, '>>', $self->{file_name}; + open STDOUT, '>&', $lfh; + open STDERR, '>&', $lfh; + } +} + +sub set_default_severity { + my $self = shift; + + $self->{"severity"} = $self->{"old_severity"}; +} + +# Getter/Setter Log severity +sub severity { + my $self = shift; + if (@_) { + my $save_severity = $self->{"severity"}; + if ($_[0] =~ /^[012347]$/) { + $self->{"severity"} = $_[0]; + } elsif ($_[0] eq "none") { + $self->{"severity"} = 0; + } elsif ($_[0] eq "error") { + $self->{"severity"} = 1; + } elsif ($_[0] eq "info") { + $self->{"severity"} = 3; + } elsif ($_[0] eq "debug") { + $self->{"severity"} = 7; + } else { + $self->writeLogError("Wrong severity value set."); + return -1; + } + $self->{"old_severity"} = $save_severity; + } + 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 writeLogDebug { + shift->writeLog(4, @_); +} + +sub writeLogInfo { + shift->writeLog(2, @_); +} + +sub writeLogError { + shift->writeLog(1, @_); +} + +sub DESTROY { + my $self = shift; + + if (defined $self->{filehandler}) { + $self->{filehandler}->close(); + } +} + +1; diff --git a/gorgone/centreon/misc/misc.pm b/gorgone/centreon/misc/misc.pm new file mode 100644 index 00000000000..e5a17921b66 --- /dev/null +++ b/gorgone/centreon/misc/misc.pm @@ -0,0 +1,230 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package centreon::misc::misc; + +use strict; +use warnings; +use vars qw($centreon_config); +use POSIX ":sys_wait_h"; + +sub reload_db_config { + my ($logger, $config_file, $cdb, $csdb) = @_; + my ($cdb_mod, $csdb_mod) = (0, 0); + + unless (my $return = do $config_file) { + $logger->writeLogError("couldn't parse $config_file: $@") if $@; + $logger->writeLogError("couldn't do $config_file: $!") unless defined $return; + $logger->writeLogError("couldn't run $config_file") unless $return; + return -1; + } + + if (defined($cdb)) { + if ($centreon_config->{centreon_db} ne $cdb->db() || + $centreon_config->{db_host} ne $cdb->host() || + $centreon_config->{db_user} ne $cdb->user() || + $centreon_config->{db_passwd} ne $cdb->password() || + $centreon_config->{db_port} ne $cdb->port()) { + $logger->writeLogInfo("Database centreon config had been modified"); + $cdb->db($centreon_config->{centreon_db}); + $cdb->host($centreon_config->{db_host}); + $cdb->user($centreon_config->{db_user}); + $cdb->password($centreon_config->{db_passwd}); + $cdb->port($centreon_config->{db_port}); + $cdb_mod = 1; + } + } + + if (defined($csdb)) { + if ($centreon_config->{centstorage_db} ne $csdb->db() || + $centreon_config->{db_host} ne $csdb->host() || + $centreon_config->{db_user} ne $csdb->user() || + $centreon_config->{db_passwd} ne $csdb->password() || + $centreon_config->{db_port} ne $csdb->port()) { + $logger->writeLogInfo("Database centstorage config had been modified"); + $csdb->db($centreon_config->{centstorage_db}); + $csdb->host($centreon_config->{db_host}); + $csdb->user($centreon_config->{db_user}); + $csdb->password($centreon_config->{db_passwd}); + $csdb->port($centreon_config->{db_port}); + $csdb_mod = 1; + } + } + + return (0, $cdb_mod, $csdb_mod); +} + +sub get_all_options_config { + my ($extra_config, $centreon_db_centreon, $prefix) = @_; + + my $save_force = $centreon_db_centreon->force(); + $centreon_db_centreon->force(0); + + my ($status, $stmt) = $centreon_db_centreon->query("SELECT `key`, `value` FROM options WHERE `key` LIKE " . $centreon_db_centreon->quote($prefix . "_%") . " LIMIT 1"); + if ($status == -1) { + $centreon_db_centreon->force($save_force); + return ; + } + while ((my $data = $stmt->fetchrow_hashref())) { + if (defined($data->{value}) && length($data->{value}) > 0) { + $data->{key} =~ s/^${prefix}_//; + $extra_config->{$data->{key}} = $data->{value}; + } + } + + $centreon_db_centreon->force($save_force); +} + +sub get_option_config { + my ($extra_config, $centreon_db_centreon, $prefix, $key) = @_; + my $data; + + my $save_force = $centreon_db_centreon->force(); + $centreon_db_centreon->force(0); + + my ($status, $stmt) = $centreon_db_centreon->query("SELECT value FROM options WHERE `key` = " . $centreon_db_centreon->quote($prefix . "_" . $key) . " LIMIT 1"); + if ($status == -1) { + $centreon_db_centreon->force($save_force); + return ; + } + if (($data = $stmt->fetchrow_hashref()) && defined($data->{value})) { + $extra_config->{$key} = $data->{value}; + } + + $centreon_db_centreon->force($save_force); +} + +sub check_debug { + my ($logger, $key, $cdb, $name) = @_; + + my $request = "SELECT value FROM options WHERE `key` = " . $cdb->quote($key); + my ($status, $sth) = $cdb->query($request); + return -1 if ($status == -1); + my $data = $sth->fetchrow_hashref(); + if (defined($data->{'value'}) && $data->{'value'} == 1) { + if (!$logger->is_debug()) { + $logger->severity("debug"); + $logger->writeLogInfo("Enable Debug in $name"); + } + } else { + if ($logger->is_debug()) { + $logger->set_default_severity(); + $logger->writeLogInfo("Disable Debug in $name"); + } + } + return 0; +} + +sub backtick { + my %arg = ( + command => undef, + arguments => [], + timeout => 30, + wait_exit => 0, + redirect_stderr => 0, + @_, + ); + my @output; + my $pid; + my $return_code; + + my $sig_do; + if ($arg{wait_exit} == 0) { + $sig_do = 'IGNORE'; + $return_code = undef; + } else { + $sig_do = 'DEFAULT'; + } + local $SIG{CHLD} = $sig_do; + $SIG{TTOU} = 'IGNORE'; + $| = 1; + + if (!defined($pid = open( KID, "-|" ))) { + $arg{logger}->writeLogError("Cant fork: $!"); + return (-1000, "cant fork: $!"); + } + + if ($pid) { + eval { + local $SIG{ALRM} = sub { die "Timeout by signal ALARM\n"; }; + alarm( $arg{timeout} ); + while () { + chomp; + push @output, $_; + } + + alarm(0); + }; + if ($@) { + if ($pid != -1) { + kill -9, $pid; + } + + alarm(0); + return (-1000, "Command too long to execute (timeout)...", -1); + } else { + if ($arg{wait_exit} == 1) { + # We're waiting the exit code + waitpid($pid, 0); + $return_code = ($? >> 8); + } + close KID; + } + } else { + # child + # set the child process to be a group leader, so that + # kill -9 will kill it and all its descendents + # We have ignore SIGTTOU to let write background processes + setpgrp(0, 0); + + if ($arg{redirect_stderr} == 1) { + open STDERR, ">&STDOUT"; + } + if (scalar(@{$arg{arguments}}) <= 0) { + exec($arg{command}); + } else { + exec($arg{command}, @{$arg{arguments}}); + } + # Exec is in error. No such command maybe. + exit(127); + } + + return (0, join("\n", @output), $return_code); +} + +sub mymodule_load { + my (%options) = @_; + my $file; + ($file = ($options{module} =~ /\.pm$/ ? $options{module} : $options{module} . '.pm')) =~ s{::}{/}g; + + eval { + local $SIG{__DIE__} = 'IGNORE'; + require $file; + $file =~ s{/}{::}g; + $file =~ s/\.pm$//; + }; + if ($@) { + $options{logger}->writeLogError($options{error_msg} . ' - ' . $@); + return 1; + } + return wantarray ? (0, $file) : 0; +} + +1; diff --git a/gorgone/centreon/misc/objects/host.pm b/gorgone/centreon/misc/objects/host.pm new file mode 100644 index 00000000000..6cfee7ebb99 --- /dev/null +++ b/gorgone/centreon/misc/objects/host.pm @@ -0,0 +1,60 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +package centreon::misc::objects::host; + +use strict; +use warnings; + +use base qw(centreon::misc::objects::object); + +sub new { + my ($class, %options) = @_; + my $self = $class->SUPER::new(%options); + + bless $self, $class; + return $self; +} + +# special options: +# 'organization_name' or 'organization_id' +# 'with_services' +sub get_hosts_by_organization { + my ($self, %options) = @_; + + my %defaults = (request => 'SELECT', tables => ['cfg_hosts'], fields => ['*']); + if (defined($options{organization_name})) { + $defaults{tables} = ['cfg_hosts', 'cfg_organizations']; + $defaults{where} = 'cfg_organizations.name = ' . $self->{db_centreon}->quote($options{organization_name}); + } elsif (defined($options{organization_id})) { + $defaults{where} = 'cfg_hosts.organization_id = ' . $self->{db_centreon}->quote($options{organization_id}); + } else { + $self->{logger}->writeLogError("Please specify 'organization_name' or 'organization_id' parameter."); + return (-1, undef); + } + if (defined($options{with_services})) { + push @{$defaults{tables}}, 'cfg_hosts_services_relations', 'cfg_services'; + $defaults{where} .= ' AND cfg_hosts.host_id = cfg_hosts_services_relations.host_host_id AND cfg_hosts_services_relations.service_service_id = cfg_services.service_id'; + } + + my $options_builder = {%defaults, %options}; + return $self->execute(%$options_builder); +} + +1; diff --git a/gorgone/centreon/misc/objects/object.pm b/gorgone/centreon/misc/objects/object.pm new file mode 100644 index 00000000000..73dd822c8d4 --- /dev/null +++ b/gorgone/centreon/misc/objects/object.pm @@ -0,0 +1,82 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package centreon::misc::objects::object; + +use strict; +use warnings; + +sub new { + my ($class, %options) = @_; + my $self = {}; + $self->{logger} = $options{logger}; + $self->{db_centreon} = $options{db_centreon}; + + bless $self, $class; + return $self; +} + +sub builder { + my ($self, %options) = @_; + + my $where = defined($options{where}) ? ' WHERE ' . $options{where} : ''; + my $extra_suffix = defined($options{extra_suffix}) ? $options{extra_suffix} : ''; + my $request = $options{request} . " " . join(', ', @{$options{fields}}) . + " FROM " . join(', ', @{$options{tables}}) . $where . $extra_suffix; + return $request; +} + +sub do { + my ($self, %options) = @_; + my $mode = defined($options{mode}) ? $options{mode} : 0; + + my ($status, $sth) = $self->{db_centreon}->query($options{request}); + if ($mode == 0) { + return ($status, $sth); + } elsif ($mode == 1) { + my $result = $sth->fetchall_hashref($options{keys}); + if (!defined($result)) { + $self->{logger}->writeLogError("Cannot fetch database data: " . $sth->errstr . " [request = $options{request}]"); + return (-1, undef); + } + return ($status, $result); + } + my $result = $sth->fetchall_arrayref(); + if (!defined($result)) { + $self->{logger}->writeLogError("Cannot fetch database data: " . $sth->errstr . " [request = $options{request}]"); + return (-1, undef); + } + return ($status, $result); +} + +sub custom_execute { + my ($self, %options) = @_; + + return $self->do(%options); +} + +sub execute { + my ($self, %options) = @_; + + my $request = $self->builder(%options); + return $self->do(request => $request, %options); +} + +1; diff --git a/gorgone/centreon/misc/objects/organization.pm b/gorgone/centreon/misc/objects/organization.pm new file mode 100644 index 00000000000..6b9887372ce --- /dev/null +++ b/gorgone/centreon/misc/objects/organization.pm @@ -0,0 +1,44 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package centreon::misc::objects::organization; + +use strict; +use warnings; + +use base qw(centreon::misc::objects::object); + +sub new { + my ($class, %options) = @_; + my $self = $class->SUPER::new(%options); + + bless $self, $class; + return $self; +} + +sub get_organizations { + my ($self, %options) = @_; + + my %defaults = (request => 'SELECT', tables => ['cfg_organizations'], fields => ['*'], where => "active = '1'"); + my $options_builder = {%defaults, %options}; + return $self->execute(%$options_builder); +} + +1; diff --git a/gorgone/centreon/script.pm b/gorgone/centreon/script.pm new file mode 100644 index 00000000000..d6ce8564d5d --- /dev/null +++ b/gorgone/centreon/script.pm @@ -0,0 +1,138 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package centreon::script; + +use strict; +use warnings; +use FindBin; +use Getopt::Long; +use Pod::Usage; +use centreon::misc::logger; +use centreon::misc::db; +use centreon::misc::lock; + +use vars qw($centreon_config); + +$SIG{__DIE__} = sub { + my $error = shift; + print "Error: $error"; + exit 1; +}; + +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, + severity => "info", + noconfig => 0, + noroot => 0 + ); + my $self = {%defaults, %options}; + + bless $self, $class; + $self->{name} = $name; + $self->{logger} = centreon::misc::logger->new(); + $self->{options} = { + "config=s" => \$self->{config_file}, + "logfile=s" => \$self->{log_file}, + "severity=s" => \$self->{severity}, + "help|?" => \$self->{help} + }; + return $self; +} + +sub init { + my $self = shift; + + if (defined $self->{log_file}) { + $self->{logger}->file_mode($self->{log_file}); + } + $self->{logger}->severity($self->{severity}); + + if ($self->{noroot} == 1) { + # Stop exec if root + if ($< == 0) { + $self->{logger}->writeLogError("Can't execute script as root."); + die("Quit"); + } + } + + if ($self->{centreon_db_conn}) { + $self->{cdb} = centreon::misc::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::misc::lock::sql->new($self->{name}, dbc => $self->{cdb}); + $self->{lock}->set(); + } + if ($self->{centstorage_db_conn}) { + $self->{csdb} = centreon::misc::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(-exitval => 1, -input => $FindBin::Bin . "/" . $FindBin::Script) if $self->{help}; + if ($self->{noconfig} == 0) { + require $self->{config_file}; + $self->{centreon_config} = $centreon_config; + } +} + +sub run { + my $self = shift; + + $self->parse_options(); + $self->init(); +} + +1; diff --git a/gorgone/centreon/script/centreondcore.pm b/gorgone/centreon/script/centreondcore.pm new file mode 100644 index 00000000000..51cd5a8b562 --- /dev/null +++ b/gorgone/centreon/script/centreondcore.pm @@ -0,0 +1,542 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package centreon::script::centreondcore; + +use strict; +use warnings; +use POSIX ":sys_wait_h"; +use Sys::Hostname; +use ZMQ::LibZMQ4; +use ZMQ::Constants qw(:all); +use centreon::centreond::common; +use centreon::misc::db; +use centreon::script; + +my ($centreond, $centreond_config); + +use base qw(centreon::script); + +my $VERSION = '1.0'; +my %handlers = (TERM => {}, HUP => {}, CHLD => {}); + +sub new { + my $class = shift; + my $self = $class->SUPER::new('centreond', + centreon_db_conn => 0, + centstorage_db_conn => 0, + noconfig => 1 + ); + + bless $self, $class; + $self->add_options( + 'config-extra:s' => \$self->{opt_extra}, + ); + + $self->{opt_extra} = ''; + $self->{return_child} = {}; + $self->{stop} = 0; + $self->{internal_register} = {}; + $self->{modules_register} = {}; + $self->{modules_events} = {}; + $self->{modules_id} = {}; + $self->{sessions_timer} = time(); + $self->{kill_timer} = undef; + + return $self; +} + +sub init { + my $self = shift; + $self->SUPER::init(); + + # redefine to avoid out when we try modules + $SIG{__DIE__} = undef; + + ## load config ini + if (! -f $self->{opt_extra}) { + $self->{logger}->writeLogError("Can't find extra config file '$self->{opt_extra}'"); + exit(1); + } + $centreond_config = centreon::centreond::common::read_config(config_file => $self->{opt_extra}, + logger => $self->{logger}); + if (defined($centreond_config->{centreondcore}{external_com_type}) && $centreond_config->{centreondcore}{external_com_type} ne '') { + centreon::centreond::common::loadprivkey(logger => $self->{logger}, privkey => $centreond_config->{centreondcore}{privkey}); + } + + # Database connections: + # We add in centreond database + $centreond->{db_centreond} = centreon::misc::db->new( + type => $centreond_config->{centreondcore}{centreond_db_type}, + db => $centreond_config->{centreondcore}{centreond_db_name}, + host => $centreond_config->{centreondcore}{centreond_db_host}, + port => $centreond_config->{centreondcore}{centreond_db_port}, + user => $centreond_config->{centreondcore}{centreond_db_user}, + password => $centreond_config->{centreondcore}{centreond_db_password}, + force => 2, + logger => $centreond->{logger} + ); + $centreond->{db_centreond}->set_inactive_destroy(); + if ($centreond->{db_centreond}->connect() == -1) { + $centreond->{logger}->writeLogInfo("Cannot connect. We quit!!"); + exit(1); + } + + $self->{hostname} = $centreond_config->{centreondcore}{hostname}; + if (!defined($self->{hostname}) || $self->{hostname} eq '') { + $self->{hostname} = hostname(); + } + + $self->{id} = $centreond_config->{centreondcore}{id}; + if (!defined($self->{hostname}) || $self->{hostname} eq '') { + #$self->{id} = get_poller_id(dbh => $dbh, name => $self->{hostname}); + } + + $self->load_modules(); + + $self->set_signal_handlers(); +} + +sub set_signal_handlers { + my $self = shift; + + $SIG{TERM} = \&class_handle_TERM; + $handlers{TERM}->{$self} = sub { $self->handle_TERM() }; + $SIG{HUP} = \&class_handle_HUP; + $handlers{HUP}->{$self} = sub { $self->handle_HUP() }; + $SIG{CHLD} = \&class_handle_CHLD; + $handlers{CHLD}->{$self} = sub { $self->handle_CHLD() }; +} + +sub class_handle_TERM { + foreach (keys %{$handlers{TERM}}) { + &{$handlers{TERM}->{$_}}(); + } +} + +sub class_handle_HUP { + foreach (keys %{$handlers{HUP}}) { + &{$handlers{HUP}->{$_}}(); + } +} + +sub class_handle_CHLD { + foreach (keys %{$handlers{CHLD}}) { + &{$handlers{CHLD}->{$_}}(); + } +} + +sub handle_TERM { + my $self = shift; + $self->{logger}->writeLogInfo("$$ Receiving order to stop..."); + $self->{stop} = 1; + + foreach my $name (keys %{$self->{modules_register}}) { + $self->{modules_register}->{$name}->{gently}->(logger => $self->{logger}); + } + $self->{kill_timer} = time(); +} + +sub handle_HUP { + my $self = shift; + $self->{logger}->writeLogInfo("$$ Receiving order to reload..."); + # TODO +} + +sub handle_CHLD { + my $self = shift; + my $child_pid; + + while (($child_pid = waitpid(-1, &WNOHANG)) > 0) { + $self->{return_child}->{$child_pid} = time(); + } + + $SIG{CHLD} = \&class_handle_CHLD; +} + +sub load_modules { + my $self = shift; + + foreach my $section (keys %{$centreond_config}) { + next if (!defined($centreond_config->{$section}{module})); + + my $name = $centreond_config->{$section}{module}; + (my $file = "$name.pm") =~ s{::}{/}g; + require $file; + $self->{logger}->writeLogInfo("Module '$section' is loading"); + $self->{modules_register}->{$name} = {}; + + foreach my $method_name (('register', 'routing', 'kill', 'kill_internal', 'gently', 'check', 'init')) { + unless ($self->{modules_register}->{$name}->{$method_name} = $name->can($method_name)) { + $self->{logger}->writeLogError("No function '$method_name' for module '$section'"); + exit(1); + } + } + + my ($events, $id) = $self->{modules_register}->{$name}->{register}->( + config => $centreond_config->{$section}, + config_core => $centreond_config->{centreondcore}, + config_db_centreon => $centreond_config->{db_centreon}, + config_db_centstorage => $centreond_config->{db_centstorage} + ); + $self->{modules_id}->{$id} = $name; + foreach my $event (@{$events}) { + $self->{modules_events}->{$event} = [] if (!defined($self->{modules_events}->{$event})); + push @{$self->{modules_events}->{$event}}, $name; + } + + $self->{logger}->writeLogInfo("Module '$section' is loaded"); + } + + # Load internal functions + foreach my $method_name (('putlog', 'getlog', 'kill', 'ping', 'constatus')) { + unless ($self->{internal_register}->{$method_name} = centreon::centreond::common->can($method_name)) { + $self->{logger}->writeLogError("No function '$method_name'"); + exit(1); + } + } +} + +sub message_run { + my ($self, %options) = @_; + + if ($options{message} !~ /^\[(.+?)\]\s+\[(.*?)\]\s+\[(.*?)\]\s+(.*)$/) { + return (undef, 1, { mesage => 'request not well formatted' }); + } + my ($action, $token, $target, $data) = ($1, $2, $3, $4); + if ($action !~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS)$/ && !defined($self->{modules_events}->{$action})) { + centreon::centreond::common::add_history( + dbh => $self->{db_centreond}, + code => 1, token => $token, + data => { msg => "action '$action' is not known" }, + json_encode => 1 + ); + return (undef, 1, { message => "action '$action' is not known" }); + } + if (!defined($token) || $token eq '') { + $token = centreon::centreond::common::generate_token(); + } + + if ($self->{stop} == 1) { + centreon::centreond::common::add_history( + dbh => $self->{db_centreond}, + code => 1, token => $token, + data => { msg => 'centreond is stopping/restarting. Not proceed request.' }, + json_encode => 1 + ); + return ($token, 1, { message => "centreond is stopping/restarting. Not proceed request." }); + } + + # Check Routing + if (defined($target) && $target ne '') { + # Check if not myself ;) + if ($target ne $self->{id}) { + $self->{modules_register}->{ $self->{modules_id}->{$centreond_config->{centreondcore}{proxy_name}} }->{routing}->( + socket => $self->{internal_socket}, dbh => $self->{db_centreond}, logger => $self->{logger}, + action => $1, token => $token, target => $target, data => $data, + hostname => $self->{hostname} + ); + return ($token, 0); + } + } + + if ($action =~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS)$/) { + my ($code, $response, $response_type) = $self->{internal_register}->{lc($action)}->( + centreond => $self, + centreond_config => $centreond_config, + id => $self->{id}, + data => $data, + token => $token, + logger => $self->{logger} + ); + return ($token, $code, $response, $response_type); + } else { + foreach (@{$self->{modules_events}->{$action}}) { + $self->{modules_register}->{$_}->{routing}->( + socket => $self->{internal_socket}, + dbh => $self->{db_centreond}, logger => $self->{logger}, + action => $1, token => $token, target => $target, data => $data, + hostname => $self->{hostname} + ); + } + } + return ($token, 0); +} + +sub router_internal_event { + while (1) { + my ($identity, $message) = centreon::centreond::common::zmq_read_message(socket => $centreond->{internal_socket}); + my ($token, $code, $response, $response_type) = $centreond->message_run(message => $message); + centreon::centreond::common::zmq_core_response( + socket => $centreond->{internal_socket}, + identity => $identity, response_type => $response_type, + data => $response, code => $code, + token => $token + ); + last unless (centreon::centreond::common::zmq_still_read(socket => $centreond->{internal_socket})); + } +} + +sub handshake { + my ($self, %options) = @_; + + my ($identity, $message) = centreon::centreond::common::zmq_read_message(socket => $self->{external_socket}); + my ($status, $key) = centreon::centreond::common::is_handshake_done(dbh => $self->{db_centreond}, identity => $identity); + + if ($status == 1) { + ($status, my $response) = centreon::centreond::common::uncrypt_message( + cipher => $centreond_config->{centreondcore}{cipher}, + message => $message, + symkey => $key, + vector => $centreond_config->{centreondcore}{vector} + ); + if ($status == 0 && $response =~ /^\[.*\]/) { + centreon::centreond::common::update_identity(dbh => $self->{db_centreond}, identity => $identity); + return ($identity, $key, $response); + } + + # Maybe he want to redo a handshake + $status = 0; + } + + if ($status == -1) { + centreon::centreond::common::zmq_core_response( + socket => $self->{external_socket}, + identity => $identity, + code => 1, + data => { message => 'Database issue' } + ); + return undef; + } elsif ($status == 0) { + # We try to uncrypt + if (centreon::centreond::common::is_client_can_connect(message => $message, + logger => $self->{logger}) == -1) { + centreon::centreond::common::zmq_core_response( + socket => $self->{external_socket}, identity => $identity, + code => 1, data => { message => 'handshake issue' } + ); + } + my ($status, $symkey) = centreon::centreond::common::generate_symkey( + logger => $self->{logger}, + cipher => $centreond_config->{centreondcore}{cipher}, + keysize => $centreond_config->{centreondcore}{keysize} + ); + if ($status == -1) { + centreon::centreond::common::zmq_core_response( + socket => $self->{external_socket}, identity => $identity, + code => 1, data => { message => 'handshake issue' } + ); + } + if (centreon::centreond::common::add_identity(dbh => $self->{db_centreond}, identity => $identity, symkey => $symkey) == -1) { + centreon::centreond::common::zmq_core_response( + socket => $self->{external_socket}, identity => $identity, + code => 1, data => { message => 'handshake issue' } + ); + } + + if (centreon::centreond::common::zmq_core_key_response(logger => $self->{logger}, socket => $self->{external_socket}, identity => $identity, + hostname => $self->{hostname}, symkey => $symkey) == -1) { + centreon::centreond::common::zmq_core_response( + socket => $self->{external_socket}, identity => $identity, + code => 1, data => { message => 'handshake issue' } + ); + } + return undef; + } +} + +sub router_external_event { + while (1) { + my ($identity, $key, $message) = $centreond->handshake(); + if (defined($message)) { + my ($token, $code, $response, $response_type) = $centreond->message_run(message => $message); + centreon::centreond::common::zmq_core_response( + socket => $centreond->{external_socket}, + identity => $identity, response_type => $response_type, + cipher => $centreond_config->{centreondcore}{cipher}, + vector => $centreond_config->{centreondcore}{vector}, + symkey => $key, + token => $token, code => $code, + data => $response + ); + } + last unless (centreon::centreond::common::zmq_still_read(socket => $centreond->{external_socket})); + } +} + +sub waiting_ready_pool { + my (%options) = @_; + + my $time = time(); + # We wait 10 seconds + while (time() - $time < 10) { + foreach my $pool_id (keys %{$options{pool}}) { + return 1 if ($options{pool}->{$pool_id}->{ready} == 1); + } + zmq_poll($centreond->{poll}, 5000); + } + foreach my $pool_id (keys %{$options{pool}}) { + return 1 if ($options{pool}->{$pool_id}->{ready} == 1); + } + + return 0; +} + +sub waiting_ready { + my (%options) = @_; + + return 1 if (${$options{ready}} == 1); + + my $time = time(); + # We wait 10 seconds + while (${$options{ready}} == 0 && + time() - $time < 10) { + zmq_poll($centreond->{poll}, 5000); + } + + if (${$options{ready}} == 0) { + return 0; + } + + return 1; +} + +sub clean_sessions { + my ($self, %options) = @_; + + if ($self->{sessions_timer} - time() > $centreond_config->{centreondcore}{purge_sessions_time}) { + $self->{logger}->writeLogInfo("purge sessions in progress..."); + $self->{db_centreond}->query("DELETE FROM centreond_identity WHERE `ctime` < " . $self->{db_centreond}->quote(time() - $centreond_config->{centreondcore}{sessions_time})); + $self->{sessions_timer} = time(); + } +} + +sub quit { + my ($self, %options) = @_; + + $self->{logger}->writeLogInfo("Quit main process"); + zmq_close($self->{internal_socket}); + if (defined($centreond_config->{centreondcore}{external_com_type}) && $centreond_config->{centreondcore}{external_com_type} ne '') { + zmq_close($self->{external_socket}); + } + exit(0); +} + +sub run { + $centreond = shift; + + $centreond->SUPER::run(); + $centreond->{logger}->redirect_output(); + + $centreond->{logger}->writeLogDebug("centreond launched...."); + $centreond->{logger}->writeLogDebug("PID: $$"); + + if (centreon::centreond::common::add_history(dbh => $centreond->{db_centreond}, + code => 0, + data => { msg => 'centreond is starting...' }, + json_encode => 1) == -1) { + $centreond->{logger}->writeLogInfo("Cannot write in history. We quit!!"); + exit(1); + } + + $centreond->{internal_socket} = centreon::centreond::common::create_com( + type => $centreond_config->{centreondcore}{internal_com_type}, + path => $centreond_config->{centreondcore}{internal_com_path}, + zmq_type => 'ZMQ_ROUTER', name => 'router-internal', + logger => $centreond->{logger} + ); + if (defined($centreond_config->{centreondcore}{external_com_type}) && $centreond_config->{centreondcore}{external_com_type} ne '') { + $centreond->{external_socket} = centreon::centreond::common::create_com( + type => $centreond_config->{centreondcore}{external_com_type}, + path => $centreond_config->{centreondcore}{external_com_path}, + zmq_type => 'ZMQ_ROUTER', name => 'router-external', + logger => $centreond->{logger} + ); + } + + # Initialize poll set + $centreond->{poll} = [ + { + socket => $centreond->{internal_socket}, + events => ZMQ_POLLIN, + callback => \&router_internal_event, + } + ]; + + if (defined($centreond->{external_socket})) { + push @{$centreond->{poll}}, { + socket => $centreond->{external_socket}, + events => ZMQ_POLLIN, + callback => \&router_external_event, + }; + } + + # init all modules + foreach my $name (keys %{$centreond->{modules_register}}) { + $centreond->{logger}->writeLogInfo("Call init function from module '$name'"); + $centreond->{modules_register}->{$name}->{init}->( + logger => $centreond->{logger}, id => $centreond->{id}, + poll => $centreond->{poll}, + external_socket => $centreond->{external_socket}, + internal_socket => $centreond->{internal_socket}, + dbh => $centreond->{db_centreond} + ); + } + + $centreond->{logger}->writeLogInfo("[Server accepting clients]"); + + while (1) { + my $count = 0; + my $poll = [@{$centreond->{poll}}]; + + foreach my $name (keys %{$centreond->{modules_register}}) { + $count += $centreond->{modules_register}->{$name}->{check}->( + logger => $centreond->{logger}, + dead_childs => $centreond->{return_child}, + internal_socket => $centreond->{internal_socket}, + dbh => $centreond->{db_centreond}, + poll => $poll + ); + } + + if ($centreond->{stop} == 1) { + # No childs + if ($count == 0) { + $centreond->quit(); + } + + # Send KILL + if (time() - $centreond->{kill_timer} > $centreond_config->{centreondcore}{timeout}) { + foreach my $name (keys %{$centreond->{modules_register}}) { + $centreond->{modules_register}->{$name}->{kill_internal}->(logger => $centreond->{logger}); + } + $centreond->quit(); + } + } + + zmq_poll($poll, 5000); + + $centreond->clean_sessions(); + } +} + +1; + +__END__ diff --git a/gorgone/centreond b/gorgone/centreond new file mode 100644 index 00000000000..48801b10668 --- /dev/null +++ b/gorgone/centreond @@ -0,0 +1,57 @@ +#!/usr/bin/perl +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use warnings; +use centreon::script::centreondcore; +use FindBin; +use lib "$FindBin::Bin"; + +centreon::script::centreondcore->new()->run(); + +__END__ + +=head1 NAME + +centreond - a daemon to handle so many things. + +=head1 SYNOPSIS + +centreond [options] + +=head1 OPTIONS + +=over 8 + +=item B<--config-extra> + +Specify the path to the centreonesxd configuration file (default: /etc/centreon/centreon_esxd.pm). + +=item B<--help> + +Print a brief help message and exits. + +=back + +=head1 DESCRIPTION + +B will + +=cut diff --git a/gorgone/config/centreond-poller.ini b/gorgone/config/centreond-poller.ini new file mode 100644 index 00000000000..cea899e09cc --- /dev/null +++ b/gorgone/config/centreond-poller.ini @@ -0,0 +1,53 @@ +[centreondcore] +internal_com_type=ipc +internal_com_path=/tmp/centreond/routing-poller.ipc +; in seconds before sending kill signals (not gently) +timeout=50 +centreond_db_type=SQLite +centreond_db_name=dbname=/tmp/centreond_poller.sdb +centreond_db_host= +centreond_db_port= +centreond_db_user= +centreond_db_password= +; If not set. Use 'hostname' function. +hostname= +; If not set. Try from 'hostname' in database +; Set 'none', if you don't need it (for poller in push mode) +id=120 +; crypt options +;privkey=keys/privkey.pem +;cipher=Crypt::OpenSSL::AES +; in bytes +;keysize=32 +; 16 bytes for AES +;vector=0123456789012345 +; in seconds +sessions_time=86400 +purge_sessions_time=3600 + +[centreondpull] +module=modules::centreondpull::hooks +; ID used (should be the poller ID) +target_type=tcp +target_path=127.0.0.1:5555 +linger=5000 +; crypt options +pubkey=keys/central/pubkey.crt +cipher=Cipher::AES +; in bytes +keysize=32 +; 16 bytes for AES +vector=0123456789012345 +; ping +ping=60 +ping_timeout=30 + +[centreondaction] +module=modules::centreondaction::hooks +disable_command_event=0 +disable_enginecomand_event=0 +enginecommand_timeout=30 +command_timeout=60 +; characters to delete in commands +paranoid_mode=1 +paranoid_characters= diff --git a/gorgone/config/centreond-poller2.ini b/gorgone/config/centreond-poller2.ini new file mode 100644 index 00000000000..fd602ba7088 --- /dev/null +++ b/gorgone/config/centreond-poller2.ini @@ -0,0 +1,31 @@ +[centreondcore] +internal_com_type=ipc +internal_com_path=/tmp/centreond/routing-poller.ipc +external_com_type=tcp +external_com_path=*:5556 +; in seconds before sending kill signals (not gently) +timeout=50 +centreond_db_type=SQLite +centreond_db_name=dbname=/tmp/centreond_poller2.sdb +centreond_db_host= +centreond_db_port= +centreond_db_user= +centreond_db_password= +; If not set. Use 'hostname' function. +hostname= +; If not set. Try from 'hostname' in database +; Set 'none', if you don't need it (for poller in push mode) +id=140 +; crypt options +privkey=keys/poller/privkey.pem +cipher=Cipher::AES +; in bytes +keysize=32 +; 16 bytes for AES +vector=0123456789012345 +; in seconds +sessions_time=86400 +purge_sessions_time=3600 + +[centreondaction] +module=modules::centreondaction::hooks diff --git a/gorgone/config/centreond.ini b/gorgone/config/centreond.ini new file mode 100644 index 00000000000..ab086046489 --- /dev/null +++ b/gorgone/config/centreond.ini @@ -0,0 +1,99 @@ +; Database Centreon configuration +[db_centreon] +dsn="mysql:host=localhost;dbname=centreon" +username=centreon +password=centreon + +; Database centreon_storage configuration +[db_centstorage] +dsn="mysql:host=localhost;dbname=centreon_storage" +username=centreon +password=centreon + +; centreond +[centreondcore] +internal_com_type=ipc +internal_com_path=/tmp/centreond/routing.ipc +external_com_type=tcp +external_com_path=*:5555 +; in seconds before sending kill signals (not gently) +timeout=50 +centreond_db_type=SQLite +centreond_db_name=dbname=/tmp/centreond.sdb +centreond_db_host= +centreond_db_port= +centreond_db_user= +centreond_db_password= +; If not set. Use 'hostname' function. +hostname= +; If not set. Try from 'hostname' in database +; Set 'none', if you don't need it (for poller in push mode) +id=none +; crypt options +privkey=keys/central/privkey.pem +cipher=Cipher::AES +; in bytes +keysize=32 +; 16 bytes for AES +vector=0123456789012345 +; in seconds +sessions_time=86400 +purge_sessions_time=3600 +; shouldn't be changed +proxy_name=centreondproxy + +;[centreondacl] +;module=modules::centreondacl::hooks +;on_demand=1 +;; How much to keep open in seconds without event received +;on_demand_time=60 +;; in seconds - do purge for organizations also +;check_organizations_time=3600 +;; in seconds - do a resync of the organizations +;resync_time=28800 +;; in seconds - random windows (to avoid resync at the same time) +;resync_random_windows=7200 +;; set to 1 to disable - if you want to do it by a cron +;resync_auto_disable=0 +;sql_fetch=10000 +;sql_bulk=2000 + +[centreondcron] +module=modules::centreondcron::hooks + +;[centreondproxy] +;module=modules::centreondproxy::hooks +;pool=5 +;; sync history each 5 minutes +;synchistory_time=300 +;; how much time before the response is in timeout +;synchistory_timeout=120 +;; ping each X seconds +;ping=60 + +;[centreondaction] +;module=modules::centreondaction::hooks + +[scom] +module=modules::scom::hooks + +; in seconds - do purge for container also +check_containers_time=3600 +dsmclient_bin=/usr/share/centreon/bin/dsmclient.pl +centcore_cmd=/var/lib/centreon/centcore.cmd +containers=toto,tutu + +toto_url=http://scomserver +toto_username=toto +toto_password=pass +toto_resync_time=300 +toto_dsmhost=centreon +toto_dsmslot=slot-% +toto_dsmmacro=ALARM_ID +toto_dsmalertmessage=%{monitoringobjectdisplayname} %{name} +toto_dsmrecoverymessage=slot ok + +tutu_url=http://scomserver2/ +tutu_username=toto2 +tutu_password=toto2 +tutu_resync_time=600 diff --git a/gorgone/docs/guide.rst b/gorgone/docs/guide.rst new file mode 100644 index 00000000000..e26a322e18a --- /dev/null +++ b/gorgone/docs/guide.rst @@ -0,0 +1,521 @@ +*********** +Description +*********** + +"centreond" is a daemon which handles some tasks. You can plug-in some modules: + +* centreond-action: execute commands, send files/directories +* centreond-cron: schedule tasks +* centreond-acl: manage centreon ACL +* centreond-proxy: push tasks (to another "centreond" instance) or execute (through SSH) + +The daemon is installed on centreon central server and also poller server. +It uses zeromq library. + +************ +Installation +************ + +Daemon uses following Perl modules: + +* ZMQ::LibZMQ4: repository 'centreon-stable' +* JSON: repository 'centos base' +* Config::IniFiles: repository 'centos base' +* DBD::SQLite: repository 'centos base' +* DBD::mysql: repository 'centos base' +* UUID: repository 'centreon-stable' +* Crypt::OpenSSL::RSA: repository 'centos base' +* Crypt::CBC: repository 'centos base' +* Schedule::Cron: in EPEL +* Crypt::Cipher::AES: in attachment + +Execute following commands: + + :: + + # yum install 'perl(Schedule::Cron)' 'perl(Crypt::CBC)' 'perl(ZMQ::LibZMQ4)' 'perl(JSON)' 'perl(Config::IniFiles)' 'perl(DBD::SQLite)' 'perl(DBD::mysql)' 'perl(UUID)' 'perl(Crypt::OpenSSL::RSA)' + # yum install perl-CryptX-0.064-1.el7.x86_64 + +Create sqlite database: + + :: + + # sqlite3 -init schema/centreond_database.sql /tmp/centreond.sdb + +To execute the daemon: + + :: + + # perl centreond --config-extra=centreond.ini --severity=debug + +****************** +centreond protocol +****************** + +"centreond-core" (main mandatory module) can have 2 interfaces: + +* internal: uncrypted dialog (used by internal modules. Commonly in ipc) +* external: crypted dialog (used by third-party clients. Commonly in tcp) + +.. _handshake-scenario: + +================== +Handshake scenario +================== + +Third-party clients connected had to use the zeromq library and the following process: + +* client : need to create an uniq identity (will be used in "zmq_setsockopt" and "ZMQ_IDENTITY") +* client -> server : send the following message crypted with the public key of the server: + +:: + + [HELO] [HOSTNAME] + +* server: uncrypt the client message: + + * If uncrypted message result is not "HELO", server refused the connection and send it back: + + :: + + [ACK] [] { "code" => 1, "data" => { "message" => "handshake issue" } } + + * If uncrypted message result is "HELO", server accepts the connection. It creates symmetric key and send the following message crypted with its private key: + + :: + + [KEY] [HOSTNAME] [symmetric key] + +* client: uncrypt the server message with the public key of the server. +* client and server uses the symmetric key to dialog + +The server keeps sessions for 24 hours since the last message of the client. Otherwise, it purges the identity/symmetric-key of the client. +If a third-party client with the same identity try to open a new session, the server deletes the old identity/symmetric-key. + +.. Warning:: + Be sure to have the same parameters to crypt/uncrypt with the symmetric key. Commonly: 'AES' cipher, keysize of 32 bytes, vector '0123456789012345', + +============== +Client request +============== + +After a successful handshake, client requests uses the following syntax: +:: + + [ACTION] [TOKEN] [TARGET] DATA + +* ACTION: the request. For example: COMMAND, ENGINECOMMAND,... It depends of the target server capabilites +* TOKEN: Can be used to create some "sessions". If empty, the server creates an uniq token for each requests +* TARGET: which "centreond" must execute the request. With the following option, you can execute a command to a specific server through another. The poller id is needed. If empty, the server (which is connected with the client) is the target. +* DATA: json stream. It depends of the request + +For each client requests, the server get an immediate response: +:: + + [ACK] [TOKEN] { "code" => x, "data" => { "message" => "xxxxx" } } + +* TOKEN: a uniq ID to follow the request +* DATA: a json stream + + * 0 : OK + * 1 : NOK + +There are some exceptions for 'CONSTATUS' and 'GETLOG' requests. + +============= +Core requests +============= + +--------- +CONSTATUS +--------- + +The following request gives you a table with the last ping response of "centreond" nodes connected to the server. +The command is useful to know if some pollers are disconnected. + +The client request: +:: + + [CONSTATUS] [] [] + +The server response: +:: + + [ACK] [token_id] DATA + +An example of the json stream: +:: + + { + code => 1, + data => { + action => 'constatus', + mesage => 'ok', + data => { + last_ping => xxxx, + entries => { + 1 => xxx, + 2 => xxx, + ... + } + } + } + } + +'last_ping' and 'entries' values are unix timestamp in seconds. The 'last_ping' is the date when the daemon had launched a ping broadcast to the poller connected. +'entries' values are the last time the poller had responded to the ping broadcast. + +------ +GETLOG +------ + +The following request gives you the capability to follow your requests. "centreond" protocol is asynchronous. +An example: when you request a command execution, the server gives you a direct response and a token. These token can be used to know what happened to your command. + +The client request: +:: + + [GETLOG] [TOKEN] [TARGET] { code => 'xx', ctime => 'xx', etime => 'xx', token => 'xx', id => 'xx' } + +At least one of the 5 values must be defined: + +* code: get logs if code = value +* token: get logs if token = value +* ctime: get logs if creation time in seconds >= value +* etime: get logs if event time in seconds >= value +* id: get logs if id > value + +The 'etime' is when the event had occured. The 'ctime' is when the server had stored the log in its database. + +The server response: +:: + + [ACK] [token_id] DATA + +An example of the json stream: +:: + + { + code => 1, + data => { + action => 'getlog', + mesage => 'ok', + result => { + 10 => { + id => 10, + token => 'xxxx', + code => 1, + etime => 1419252684, + ctime => 1419252686, + data => xxxxx, + }, + 100 => { + id => 100, + token => 'xxxx', + code => 1, + etime => 1419252688, + ctime => 1419252690, + data => xxxxx, + }, + ... + } + } + } + +Each 'centreond' nodes store its logs. But every 5 minutes (by default), the central server gets the new logs of its connected nodes and stores it. +A client can force a synchronization with the following request: +:: + + [GETLOG] [] [target_id] + +The client have to set the poller id. + +------ +PUTLOG +------ + +The request shouldn't be used by third-party program. It's commonly used by the internal modules. +The client request: +:: + + [PUTLOG] [TOKEN] [TARGET] { code => xxx, etime => xxx, token => xxxx, data => { some_datas } } + +=============== +module requests +=============== + +------------- +centreond-acl +------------- + +Common code responses: + +* 100: problem. It stopped (read the message) +* 101: action proceed +* 105: problem (read the message) +* 106: action had been finished + +ACLADDHOST +^^^^^^^^^^ + +Example: +:: + + [ACLADDHOST] [] [] { organization_id => XX, host_id => XX } + +ACLDELHOST +^^^^^^^^^^ + +Example: +:: + + [ACLDELHOST] [] [] { organization_id => XX, host_id => XX } + +ACLADDSERVICE +^^^^^^^^^^^^^ + +Example: +:: + + [ACLADDSERVICE] [] [] { organization_id => XX, service_id => XX } + +ACLDELSERVICE +^^^^^^^^^^^^^ + +Example: +:: + + [ACLDELSERVICE] [] [] { organization_id => XX, service_id => XX } + +ACLUPDATETAG +^^^^^^^^^^^^ + +Example: +:: + + [ACLUPDATETAG] [] [] { organization_id => XX, tag_id => XX, tag_type => X, resource_id => X, action => X } + +The following action should be used when you assign/unassign a tag: + +* tag_type: '1' (host tag), '2' (service tag), '3' (ba tag) +* resource_id: host_id, service_id or ba_id (depends of the tag_type) +* action: '1' (assign), '2' (unassign) + +ACLDELTAG +^^^^^^^^^ + +Example: +:: + + [ACLDELTAG] [] [] { organization_id => XX, tag_id => XX, tag_type => X } + +The following action should be used when you delete a tag: + +* tag_type: '1' (host tag), '2' (service tag), '3' (ba tag) + +ACLUPDATEDOMAIN +^^^^^^^^^^^^^^^ + +Example: +:: + + [ACLUPDATEDOMAIN] [] [] { organization_id => XX, domain_id => XX, service_id => XX, action => X } + +The following action should be used when you assign/unassign a domain to a service. + +ACLDELDOMAIN +^^^^^^^^^^^^ + +Example: +:: + + [ACLDELDOMAIN] [] [] { organization_id => XX, domain_id => XX } + +ACLUPDATEENVIRONMENT +^^^^^^^^^^^^^^^^^^^^ + +Example: +:: + + [ACLUPDATEENVIRONMENT] [] [] { organization_id => XX,, environment_id => XX, environment_type => X, resource_id => X, action => X } + +The following action should be used when you assign/unassign a environment: + +* environment_type: '1' (host), '2' (service), '3' (ba) +* resource_id: host_id, service_id (depends of the environment_type) +* action: '1' (assign), '2' (unassign) + +ACLDELENVIRONMENT +^^^^^^^^^^^^^^^^^ + +Example: +:: + + [ACLDELENVIRONMENT] [] [] { organization_id => XX, environment_id => XX } + +ACLUPDATEPOLLER +^^^^^^^^^^^^^^^ + +Example: +:: + + [ACLUPDATEPOLLER] [] [] { organization_id => XX, poller_id => XX, host_id => XX, action => X } + +The following action should be used when you assign/unassign a poller to a host: + +* action: '1' (assign), '2' (unassign) + +ACLDELPOLLER +^^^^^^^^^^^^ + +Example: +:: + + [ACLDELPOLLER] [] [] { organization_id => XX, poller_id => XX } + +ACLPURGEORGANIZATION +^^^^^^^^^^^^^^^^^^^^ + +Example: +:: + + [ACLPURGEORGANIZATION] [] [] { organization_id => XX } + +The following action should be used when you delete a organization. + +ACLRESYNC +^^^^^^^^^ + +Example: +:: + + [ACLRESYNC] [] [] { organization_id => XX, acl_resource_id => XX } + +The following action should be used when you want to rebuild an entire organization. +You can rebuild a specific 'acl_resource' group if you set it. + +---------------- +centreond-action +---------------- + +COMMAND +^^^^^^^ + +With the following request, you can execute shell commands. +A client example: +:: + + [COMMAND] [] [target_id] { command => 'ls /' } + +The code responses: + +* x0: problem. It stopped (read the message) +* 31: command proceed +* 32: command proceed end +* 35: problem. It stopped (read the message) +* 36: command had been finished + +With the code 36, you can get following attributes: +:: + + { code => 36, stdout => 'xxxxx', exit_code => xxx } + +ENGINECOMMAND +^^^^^^^^^^^^^ + +With the following request, you can submit external commands to the scheduler like "centreon-engine". +A client example: +:: + + [ENGINECOMMAND] [] [target_id] { command => '[1417705150] ENABLE_HOST_CHECK;host1', engine_pipe => '/var/lib/centreon-engine/rw/centengine.cmd' + +The code responses: + +* x0: problem. It stopped (read the message) +* 31: command proceed +* 32: command proceed end +* 35: problem. It stopped (read the message) +* 36: command had been submitted + +You only have the message to get informations (it tells you if there are some permission problems or file missing). + +*** +FAQ +*** + + + +=============================== +Which modules should i enable ? +=============================== + +A poller with centreond should have the following modules: + +* centreond-action +* centreond-pull: if the connection to the central should be opened by the poller + +A central with centreond should have the following modules: + +* centreond-acl +* centreond-action +* centreond-proxy +* centreond-cron + +================================================= +I want to create a client. How should i proceed ? +================================================= + +First, you must choose a language which can used zeromq library and have some knowledge about zeromq. +I recommend following scenarios: + +* Create a ZMQ_DEALER +* Manage the handshake with the server. See :ref:`handshake-scenario` +* Do a request: + + * if you don't need to get the result: close the connection + * if you need to get the result: + + 1. get the token + 2. if you have used a target, force a synchronization with 'GETLOG' + 3. do a 'GETLOG' request with the token to get the result + 4. repeat actions 2 and 3 if you don't have a result (you should stop after X retries) + +You can see the code from 'test-client.pl'. + +*************** +Database scheme +*************** + +:: + + CREATE TABLE IF NOT EXISTS `centreond_identity` ( + `id` INTEGER PRIMARY KEY, + `ctime` int(11) DEFAULT NULL, + `identity` varchar(2048) DEFAULT NULL, + `key` varchar(4096) DEFAULT NULL + ); + + CREATE INDEX IF NOT EXISTS idx_centreond_identity_identity ON centreond_identity (identity); + + CREATE TABLE IF NOT EXISTS `centreond_history` ( + `id` INTEGER PRIMARY KEY, + `token` varchar(255) DEFAULT NULL, + `code` int(11) DEFAULT NULL, + `etime` int(11) DEFAULT NULL, + `ctime` int(11) DEFAULT NULL, + `data` TEXT DEFAULT NULL + ); + + CREATE INDEX IF NOT EXISTS idx_centreond_history_id ON centreond_history (id); + CREATE INDEX IF NOT EXISTS idx_centreond_history_token ON centreond_history (token); + CREATE INDEX IF NOT EXISTS idx_centreond_history_etime ON centreond_history (etime); + CREATE INDEX IF NOT EXISTS idx_centreond_history_code ON centreond_history (code); + CREATE INDEX IF NOT EXISTS idx_centreond_history_ctime ON centreond_history (ctime); + + CREATE TABLE IF NOT EXISTS `centreond_synchistory` ( + `id` int(11) DEFAULT NULL, + `ctime` int(11) DEFAULT NULL, + `last_id` int(11) DEFAULT NULL + ); + + CREATE INDEX IF NOT EXISTS idx_centreond_synchistory_id ON centreond_synchistory (id); diff --git a/gorgone/keys/central/privkey.pem b/gorgone/keys/central/privkey.pem new file mode 100644 index 00000000000..72d6ae80b9d --- /dev/null +++ b/gorgone/keys/central/privkey.pem @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/gorgone/keys/central/pubkey.crt b/gorgone/keys/central/pubkey.crt new file mode 100644 index 00000000000..7fb3f963e9c --- /dev/null +++ b/gorgone/keys/central/pubkey.crt @@ -0,0 +1,14 @@ +-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuQ0EjIm2FXh6Z/JtjBkJ +1PHFdZcw1QBss0KQ1/NIYfg0dAl1X1SSDYGVTVvqr2Z5AiE5rwpDMZjUjxh2WLTz +VpVbLAWIzsmc54RtaEYbB2QCi/p+uvOr7JGzf5PVRIgA+McnghSYmcqZsyWVi6sR +2LhTLA4ndlNH32tJDKQ6lnXM43EAvd9BiKDEzp4CzDehg8HWSaC36wv8OPCQ9EbO +FmIDWU4kL6xE3ThyHbRPGfEKFykE5PmPCuYKWmPGVkbB2OipBmwOuJ6C/MgIYhQB +LH61e7aXnytoLw5NG/xb4IXyOOvVU8gtmo4bjxeEmGBzIoiOXxTeb643rJpPLkjv +ub0PRlYvgHzIuBt3Zj3pBhfmlm7V1mNwba0PAxJ6AU4sXskBxYEeWq6gNWOpGIxi +4+fAF2MQ35OTjEBJp/rv5zY2weCSKYby3xO6TrGRCZZyxmrYErcUFIqnfZNkfe7H +UUEx6dCKA/wfmGucaHI+I2z/+iJNbi3n59Rj6rQvf0PsETmiahjIGhDt0y+/F7dz +CsKfMVsA07vO+uFWpF6uYmjoB5ZhzBjn7BiIdpCc7tnJE4L2ctc4YQRNi5xqu0O2 +VtfqGwtb3fy6dEPge/Femp7/NGgjbbGloLXiHCnujXpPHoaib9T0ryIGAMrRPPYr +gGv+od1qZfDlw3UpnrodLe0CAwEAAQ== +-----END PUBLIC KEY----- diff --git a/gorgone/keys/poller/privkey.pem b/gorgone/keys/poller/privkey.pem new file mode 100644 index 00000000000..72d6ae80b9d --- /dev/null +++ b/gorgone/keys/poller/privkey.pem @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/gorgone/keys/poller/pubkey.crt b/gorgone/keys/poller/pubkey.crt new file mode 100644 index 00000000000..7fb3f963e9c --- /dev/null +++ b/gorgone/keys/poller/pubkey.crt @@ -0,0 +1,14 @@ +-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuQ0EjIm2FXh6Z/JtjBkJ +1PHFdZcw1QBss0KQ1/NIYfg0dAl1X1SSDYGVTVvqr2Z5AiE5rwpDMZjUjxh2WLTz +VpVbLAWIzsmc54RtaEYbB2QCi/p+uvOr7JGzf5PVRIgA+McnghSYmcqZsyWVi6sR +2LhTLA4ndlNH32tJDKQ6lnXM43EAvd9BiKDEzp4CzDehg8HWSaC36wv8OPCQ9EbO +FmIDWU4kL6xE3ThyHbRPGfEKFykE5PmPCuYKWmPGVkbB2OipBmwOuJ6C/MgIYhQB +LH61e7aXnytoLw5NG/xb4IXyOOvVU8gtmo4bjxeEmGBzIoiOXxTeb643rJpPLkjv +ub0PRlYvgHzIuBt3Zj3pBhfmlm7V1mNwba0PAxJ6AU4sXskBxYEeWq6gNWOpGIxi +4+fAF2MQ35OTjEBJp/rv5zY2weCSKYby3xO6TrGRCZZyxmrYErcUFIqnfZNkfe7H +UUEx6dCKA/wfmGucaHI+I2z/+iJNbi3n59Rj6rQvf0PsETmiahjIGhDt0y+/F7dz +CsKfMVsA07vO+uFWpF6uYmjoB5ZhzBjn7BiIdpCc7tnJE4L2ctc4YQRNi5xqu0O2 +VtfqGwtb3fy6dEPge/Femp7/NGgjbbGloLXiHCnujXpPHoaib9T0ryIGAMrRPPYr +gGv+od1qZfDlw3UpnrodLe0CAwEAAQ== +-----END PUBLIC KEY----- diff --git a/gorgone/modules/centreondacl/class.pm b/gorgone/modules/centreondacl/class.pm new file mode 100644 index 00000000000..1655967c0e5 --- /dev/null +++ b/gorgone/modules/centreondacl/class.pm @@ -0,0 +1,442 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package modules::centreondacl::class; + +use strict; +use warnings; +use centreon::centreond::common; +use ZMQ::LibZMQ4; +use ZMQ::Constants qw(:all); +use JSON; +use centreon::misc::objects::organization; +use centreon::misc::objects::host; +use centreon::misc::objects::object; +use Data::Dumper; + +my %handlers = (TERM => {}, HUP => {}); +my ($connector, $socket); + +sub new { + my ($class, %options) = @_; + $connector = {}; + $connector->{logger} = $options{logger}; + $connector->{organization_id} = $options{organization_id}; + $connector->{config} = $options{config}; + $connector->{config_core} = $options{config_core}; + $connector->{config_db_centreon} = $options{config_db_centreon}; + $connector->{stop} = 0; + + bless $connector, $class; + $connector->set_signal_handlers; + return $connector; +} + +sub set_signal_handlers { + my $self = shift; + + $SIG{TERM} = \&class_handle_TERM; + $handlers{TERM}->{$self} = sub { $self->handle_TERM() }; + $SIG{HUP} = \&class_handle_HUP; + $handlers{HUP}->{$self} = sub { $self->handle_HUP() }; +} + +sub handle_HUP { + my $self = shift; + $self->{reload} = 0; +} + +sub handle_TERM { + my $self = shift; + $self->{logger}->writeLogInfo("centreond-acl $$ Receiving order to stop..."); + $self->{stop} = 1; +} + +sub class_handle_TERM { + foreach (keys %{$handlers{TERM}}) { + &{$handlers{TERM}->{$_}}(); + } +} + +sub class_handle_HUP { + foreach (keys %{$handlers{HUP}}) { + &{$handlers{HUP}->{$_}}(); + } +} + +sub acl_resource_add_filters { + my ($self, %options) = @_; + + my %filters = ( hosts => [], services => [], extra_labels => [] ); + if (defined($options{resource_config}->{filter_host_tags}) and scalar(@{$options{resource_config}->{filter_host_tags}}) > 0) { + push @{$filters{hosts}}, 'cfg_hosts.host_id = cfg_thf.resource_id AND cfg_thf.tag_id IN (' . join(', ', @{$options{resource_config}->{filter_host_tags}}) . ')'; + push @{$filters{extra_tables}}, 'cfg_tags_hosts as cfg_thf'; + } + if (defined($options{resource_config}->{filter_environnement}) and scalar(@{$options{resource_config}->{filter_environnement}}) > 0) { + push @{$filters{hosts}}, "cfg_hosts.environment_id IN (" . join(', ', @{$options{resource_config}->{filter_environnement}}) . ")"; + # Not yet. + #push @{$filters{services}}, "cfg_services.environment_id IN (" . join(', ', @{$options{resource_config}->{filter_environnement}}) . ")"; + } + if (defined($options{resource_config}->{filter_pollers}) and scalar(@{$options{resource_config}->{filter_pollers}}) > 0) { + push @{$filters{hosts}}, "cfg_hosts.poller_id IN (" . join(', ', @{$options{resource_config}->{filter_pollers}}) . ")"; + } + if (defined($options{resource_config}->{filter_domains}) and scalar(@{$options{resource_config}->{filter_domains}}) > 0) { + push @{$filters{services}}, "cfg_services.domain_id IN (" . join(', ', @{$options{resource_config}->{filter_domains}}) . ")"; + } + if (defined($options{resource_config}->{filter_service_tags}) and scalar(@{$options{resource_config}->{filter_service_tags}}) > 0) { + push @{$filters{services}}, 'cfg_services.service_id = cfg_tsfilter.resource_id AND cfg_tsfilter.tag_id IN (' . join(', ', @{$options{resource_config}->{filter_service_tags}}) . ')'; + push @{$filters{extra_tables}}, 'cfg_tags_services as cfg_tsfilter'; + } + + foreach (keys %filters) { + $options{filters}->{$_} = ' AND ' . join(' AND ', @{$filters{$_}}) if (scalar(@{$filters{$_}}) > 0); + } +} + +sub acl_resource_list_hs { + my ($self, %options) = @_; + + my $filters = { hosts => '', services => '', extra_tables => '' }; + my $requests = []; + $self->acl_resource_add_filters(filters => $filters, %options); + # Manage hosts + if (defined($options{resource_config}->{host_alls}) && $options{resource_config}->{host_alls} == 1) { + push @{$requests}, "(SELECT host_id, service_id FROM cfg_hosts, cfg_hosts_services_relations, cfg_services" . $filters->{extra_tables} . ' WHERE ' . + 'cfg_hosts.organization_id = ' . $self->{organization_id} . $filters->{hosts} . + ' AND cfg_hosts.host_id = cfg_hosts_services_relations.host_host_id AND cfg_hosts_services_relations.service_service_id = cfg_services.service_id' . + $filters->{services} . ')'; + } else { + if (defined($options{resource_config}->{host_ids}) and scalar(@{$options{resource_config}->{host_ids}}) > 0) { + push @{$requests}, "(SELECT host_id, service_id FROM cfg_hosts, cfg_hosts_services_relations, cfg_services" . $filters->{extra_tables} . ' WHERE ' . + 'cfg_hosts.organization_id = ' . $self->{organization_id} . ' AND cfg_hosts.host_id IN (' . join(', ', @{$options{resource_config}->{host_ids}}) . ') ' . $filters->{hosts} . + ' AND cfg_hosts.host_id = cfg_hosts_services_relations.host_host_id AND cfg_hosts_services_relations.service_service_id = cfg_services.service_id' . + $filters->{services} . ')'; + } + if (defined($options{resource_config}->{host_tags}) and scalar(@{$options{resource_config}->{host_tags}}) > 0) { + push @{$requests}, "(SELECT host_id, service_id FROM cfg_hosts, cfg_hosts_services_relations, cfg_services, cfg_tags_hosts" . $filters->{extra_tables} . ' WHERE ' . + 'cfg_hosts.organization_id = ' . $self->{organization_id} . ' AND cfg_hosts.host_id = cfg_tags_hosts.resource_id AND cfg_tags_hosts.tag_id IN (' . join(', ', @{$options{resource_config}->{host_tags}}) . ')' . $filters->{hosts} . + ' AND cfg_hosts.host_id = cfg_hosts_services_relations.host_host_id AND cfg_hosts_services_relations.service_service_id = cfg_services.service_id' . + $filters->{services} . ')'; + } + } + + # Manage services + if (defined($options{resource_config}->{service_ids}) and scalar(@{$options{resource_config}->{service_ids}}) > 0) { + push @{$requests}, "(SELECT host_id, service_id FROM cfg_hosts, cfg_hosts_services_relations, cfg_services" . $filters->{extra_tables} . ' WHERE ' . + 'cfg_services.organization_id = ' . $self->{organization_id} . ' AND cfg_services.service_id IN (' . join(', ', @{$options{resource_config}->{service_ids}}) . ') ' . $filters->{services} . + ' AND cfg_services.service_id = cfg_hosts_services_relations.service_service_id AND cfg_hosts_services_relations.host_id = cfg_hosts.host_id' . + $filters->{hosts} . ')'; + } + if (defined($options{resource_config}->{service_tags}) and scalar(@{$options{resource_config}->{service_tags}}) > 0) { + push @{$requests}, "(SELECT host_id, service_id FROM cfg_hosts, cfg_hosts_services_relations, cfg_services, cfg_tags_services" . $filters->{extra_tables} . ' WHERE ' . + 'cfg_services.organization_id = ' . $self->{organization_id} . ' AND cfg_services.service_id = cfg_tags_services.resource_id AND cfg_tags_services.tag_id IN (' . join(', ', @{$options{resource_config}->{service_tags}}) . ')' . $filters->{services} . + ' AND cfg_services.service_id = cfg_hosts_services_relations.service_service_id AND cfg_hosts_services_relations.host_id = cfg_hosts.host_id' . + $filters->{hosts} . ')'; + } + + $self->{logger}->writeLogDebug("centreondacl: request: " . join(' UNION ALL ', @{$requests})); + return 2 if (scalar(@{$requests}) == 0); + return $self->{class_object}->custom_execute(request => join(' UNION ALL ', @{$requests}), mode => 0); +} + +sub set_default_resource_config { + my ($self, %options) = @_; + + $options{config}->{$options{acl_resource_id}} = { + host_ids => [], host_alls => 0, host_tags => [], + service_ids => [], service_tags => [], + filter_domains => [], filter_pollers => [], + filter_environnement => [], + filter_host_tags => [], filter_service_tags => [] + }; +} + +sub acl_get_resources_config { + my ($self, %options) = @_; + + # filter + my $filter = 'cfg_acl_resources.organization_id = ' . $self->{organization_id}; + if (defined($options{acl_resource_id})) { + $filter .= ' AND cfg_acl_resources.acl_resource_id = ' . $options{acl_resource_id}; + } + + my $resource_configs = {}; + + # Get all_hosts + my $request = 'SELECT cfg_acl_resources.acl_resource_id, all_hosts FROM cfg_acl_resources, cfg_acl_resources_hosts_params WHERE ' . $filter . ' AND cfg_acl_resources.acl_resource_id = cfg_acl_resources_hosts_params.acl_resource_id'; + my ($status, $datas) = $self->{class_object}->custom_execute(request => $request, mode => 2); + return 1 if ($status == -1); + foreach (@{$datas}) { + $self->set_default_resource_config(config => $resource_configs, acl_resource_id => $$_[0]) if (!defined($resource_configs->{$$_[0]})); + $resource_configs->{$$_[0]}->{host_alls} = $$_[1]; + } + + # Get hosts + $request = 'SELECT cfg_acl_resources.acl_resource_id, host_id, type FROM cfg_acl_resources, cfg_acl_resources_hosts_relations WHERE ' . $filter . ' AND cfg_acl_resources.acl_resource_id = cfg_acl_resources_hosts_relations.acl_resource_id'; + ($status, $datas) = $self->{class_object}->custom_execute(request => $request, mode => 2); + return 1 if ($status == -1); + foreach (@{$datas}) { + $self->set_default_resource_config(config => $resource_configs, acl_resource_id => $$_[0]) if (!defined($resource_configs->{$$_[0]})); + # 0 = inclus, 2 => exclude (pas de filtre pour les hôtes) + push @{$resource_configs->{$$_[0]}->{host_ids}}, $$_[1] if ($$_[2] == 0); + } + + # Get host tags + $request = 'SELECT cfg_acl_resources.acl_resource_id, tag_id, type FROM cfg_acl_resources, cfg_acl_resources_tags_hosts_relations WHERE ' . $filter . ' AND cfg_acl_resources.acl_resource_id = cfg_acl_resources_tags_hosts_relations.acl_resource_id'; + ($status, $datas) = $self->{class_object}->custom_execute(request => $request, mode => 2); + return 1 if ($status == -1); + foreach (@{$datas}) { + $self->set_default_resource_config(config => $resource_configs, acl_resource_id => $$_[0]) if (!defined($resource_configs->{$$_[0]})); + # 0 = inclus, 1 => filter, 2 => exclude + push @{$resource_configs->{$$_[0]}->{host_tags}}, $$_[1] if ($$_[2] == 0); + push @{$resource_configs->{$$_[0]}->{filter_host_tags}}, $$_[1] if ($$_[2] == 1); + } + + # Get services + $request = 'SELECT cfg_acl_resources.acl_resource_id, service_id, type FROM cfg_acl_resources, cfg_acl_resources_services_relations WHERE ' . $filter . ' AND cfg_acl_resources.acl_resource_id = cfg_acl_resources_services_relations.acl_resource_id'; + ($status, $datas) = $self->{class_object}->custom_execute(request => $request, mode => 2); + return 1 if ($status == -1); + foreach (@{$datas}) { + $self->set_default_resource_config(config => $resource_configs, acl_resource_id => $$_[0]) if (!defined($resource_configs->{$$_[0]})); + # 0 = inclus, 2 => exclude (pas de filtre pour les services) + push @{$resource_configs->{$$_[0]}->{service_ids}}, $$_[1] if ($$_[2] == 0); + } + + # Get Environment + $request = 'SELECT cfg_acl_resources.acl_resource_id, environment_id, type FROM cfg_acl_resources, cfg_acl_resources_environments_relations WHERE ' . $filter . ' AND cfg_acl_resources.acl_resource_id = cfg_acl_resources_environments_relations.acl_resource_id'; + ($status, $datas) = $self->{class_object}->custom_execute(request => $request, mode => 2); + return 1 if ($status == -1); + foreach (@{$datas}) { + $self->set_default_resource_config(config => $resource_configs, acl_resource_id => $$_[0]) if (!defined($resource_configs->{$$_[0]})); + # 0 = inclus, 2 => exclude (pas de filtre pour les services) + push @{$resource_configs->{$$_[0]}->{filter_environnement}}, $$_[1]; #if ($$_[2] == 0); + } + + # Get Domains + $request = 'SELECT cfg_acl_resources.acl_resource_id, domain_id, type FROM cfg_acl_resources, cfg_acl_resources_domains_relations WHERE ' . $filter . ' AND cfg_acl_resources.acl_resource_id = cfg_acl_resources_domains_relations.acl_resource_id'; + ($status, $datas) = $self->{class_object}->custom_execute(request => $request, mode => 2); + return 1 if ($status == -1); + foreach (@{$datas}) { + $self->set_default_resource_config(config => $resource_configs, acl_resource_id => $$_[0]) if (!defined($resource_configs->{$$_[0]})); + # 0 = inclus, 2 => exclude (pas de filtre pour les services) + push @{$resource_configs->{$$_[0]}->{filter_domains}}, $$_[1]; #if ($$_[2] == 0); + } + + return (0, $resource_configs); +} + +sub insert_execute { + my ($self, %options) = @_; + + if (!(my $rv = $options{sth}->execute(@{$options{bind}}))) { + $self->{logger}->writeLogError('SQL error: ' . $options{sth}->errstr); + $self->{db_centreon}->rollback(); + return 1; + } + + return 0; +} + +sub insert_result { + my ($self, %options) = @_; + my ($status, $sth); + + $self->{db_centreon}->transaction_mode(1); + if (defined($options{first_request})) { + ($status) = $self->{db_centreon}->query($options{first_request}); + if ($status == -1) { + $self->{db_centreon}->rollback(); + return 1; + } + } + if (!defined($options{hs_sth})) { + $self->{db_centreon}->commit(); + return 0; + } + + # In Oracle 11: IGNORE_ROW_ON_DUPKEY_INDEX + # MS SQL: IGNORE_DUP_KEY + # Postgres: ??? + my $prepare_str = '(' . $self->{organization_id} . ', ' . $options{acl_resource_id} . ', ?, ?)'; + for (my $i = 1; $i < $self->{config}{sql_bulk}; $i++) { + $prepare_str .= ', (' . $self->{organization_id} . ', ' . $options{acl_resource_id} . ', ?, ?)'; + } + ($status, $sth) = $self->{db_centreon}->query('INSERT IGNORE INTO cfg_acl_resources_cache VALUES ' . $prepare_str, prepare_only => 1); + my $rows = []; + my %host_insert = (); + my $i = 0; + my @array_binds = (); + while (my $row = ( shift(@$rows) || # get row from cache, or reload cache: + shift(@{$rows = $options{hs_sth}->fetchall_arrayref(undef, $self->{config}{sql_fetch})||[]})) ) { + + if (!defined($host_insert{$$row[0]})) { + $host_insert{$$row[0]} = 1; + push @array_binds, 1, $$row[0]; + $i++; + if ($i == $self->{config}{sql_bulk}) { + return 1 if ($self->insert_execute(sth => $sth, bind => \@array_binds)); + $i = 0; + @array_binds = (); + } + } + + push @array_binds, 2, $$row[1]; + $i++; + if ($i == $self->{config}{sql_bulk}) { + return 1 if ($self->insert_execute(sth => $sth, bind => \@array_binds)); + $i = 0; + @array_binds = (); + } + } + + if (scalar(@array_binds) > 0) { + my $query = 'INSERT IGNORE INTO cfg_acl_resources_cache VALUES (' . $self->{organization_id} . ', ' . $options{acl_resource_id} . ', ?, ?)'; + my $num = scalar(@array_binds) / 2; + for (my $i = 1; $i < $num; $i++) { + $query .= ', (' . $self->{organization_id} . ', ' . $options{acl_resource_id} . ', ?, ?)'; + } + ($status, $sth) = $self->{db_centreon}->query($query, prepare_only => 1); + return 1 if ($self->insert_execute(sth => $sth, bind => \@array_binds)); + } + + $self->{db_centreon}->commit(); + return 0; +} + +sub action_aclresync { + my ($self, %options) = @_; + my ($status, $sth, $resource_configs); + + $self->{logger}->writeLogDebug("centreondacl: organization $self->{organization_id} : begin resync"); + ($status, $resource_configs) = $self->acl_get_resources_config(); + if ($status) { + return 1; + } + + foreach my $acl_resource_id (sort keys %{$resource_configs}) { + $self->{logger}->writeLogDebug("centreondacl: organization $self->{organization_id} acl resource $acl_resource_id : begin resync"); + ($status, $sth) = $self->acl_resource_list_hs(resource_config => $resource_configs->{$acl_resource_id}); + if ($status == -1) { + return 1; + } + + $status = $self->insert_result(acl_resource_id => $acl_resource_id, hs_sth => $sth, first_request => "DELETE FROM cfg_acl_resources_cache WHERE organization_id = '" . $self->{organization_id} . "' AND acl_resource_id = " . $acl_resource_id); + $self->{logger}->writeLogDebug("centreondacl: organization $self->{organization_id} acl resource $acl_resource_id : finished resync (status: $status)"); + if ($status == 1) { + return 1; + } + } + + return 0; +} + +sub event { + while (1) { + my $message = centreon::centreond::common::zmq_dealer_read_message(socket => $socket); + + $connector->{logger}->writeLogDebug("centreondacl: class: $message"); + if ($message =~ /^\[(.*?)\]/) { + if ((my $method = $connector->can('action_' . lc($1)))) { + $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; + my ($action, $token) = ($1, $2); + my $data = JSON->new->utf8->decode($3); + while ($method->($connector, token => $token, data => $data)) { + # We block until it's fixed!! + sleep(5); + } + } + } + + # Function examples + #print Data::Dumper::Dumper($connector->{class_organization}->get_organizations(mode => 1, keys => 'organization_id')); + # Get all hosts + #print Data::Dumper::Dumper($connector->{class_host}->get_hosts_by_organization(organization_id => $connector->{organization_id}, mode => 2, fields => ['host_id', 'host_name'])); + # Get all hosts with services + #print Data::Dumper::Dumper($connector->{class_host}->get_hosts_by_organization(organization_id => $connector->{organization_id}, with_services => 1, + # mode => 1, keys => ['host_id', 'service_id'], fields => ['host_id', 'host_name', 'service_id'])); + + # we try an open one: + # print Data::Dumper::Dumper($connector->{class_host}->get_hosts_by_organization(organization_id => $connector->{organization_id}, with_services => 1, + # mode => 1, keys => ['host_id', 'service_id'], fields => ['host_id', 'host_name', 'service_id'])); + + last unless (centreon::centreond::common::zmq_still_read(socket => $socket)); + } +} + +sub run { + my ($self, %options) = @_; + my $on_demand = (defined($options{on_demand}) && $options{on_demand} == 1) ? 1 : 0; + my $on_demand_time = time(); + + # Database creation. We stay in the loop still there is an error + $self->{db_centreon} = centreon::misc::db->new( + dsn => $self->{config_db_centreon}{dsn}, + user => $self->{config_db_centreon}{username}, + password => $self->{config_db_centreon}{password}, + force => 2, + logger => $self->{logger} + ); + ##### Load objects ##### + #$self->{class_organization} = centreon::misc::objects::organization->new(logger => $self->{logger}, db_centreon => $self->{db_centreon}); + #$self->{class_host} = centreon::misc::objects::host->new(logger => $self->{logger}, db_centreon => $self->{db_centreon}); + $self->{class_object} = centreon::misc::objects::object->new(logger => $self->{logger}, db_centreon => $self->{db_centreon}); + + # Connect internal + $socket = centreon::centreond::common::connect_com( + zmq_type => 'ZMQ_DEALER', name => 'centreondacl-' . $self->{organization_id}, + logger => $self->{logger}, + type => $self->{config_core}{internal_com_type}, + path => $self->{config_core}{internal_com_path} + ); + centreon::centreond::common::zmq_send_message(socket => $socket, + action => 'ACLREADY', data => { organization_id => $self->{organization_id} }, + json_encode => 1); + $self->{poll} = [ + { + socket => $socket, + events => ZMQ_POLLIN, + callback => \&event, + } + ]; + while (1) { + # we try to do all we can + my $rev = zmq_poll($self->{poll}, 5000); + if (defined($rev) && $rev == 0 && $self->{stop} == 1) { + $self->{logger}->writeLogInfo("centreond-acl $$ has quit"); + zmq_close($socket); + exit(0); + } + + # Check if we need to quit + if ($on_demand == 1) { + if (defined($rev) && $rev == 0) { + if (time() - $on_demand_time > $self->{config}{on_demand_time}) { + $self->{logger}->writeLogInfo("centreond-acl $$ has quit"); + zmq_close($socket); + exit(0); + } + } else { + $on_demand_time = time(); + } + } + } +} + +1; diff --git a/gorgone/modules/centreondacl/hooks.pm b/gorgone/modules/centreondacl/hooks.pm new file mode 100644 index 00000000000..199516a4a8d --- /dev/null +++ b/gorgone/modules/centreondacl/hooks.pm @@ -0,0 +1,246 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package modules::centreondacl::hooks; + +use warnings; +use strict; +use centreon::script::centreondcore; +use modules::centreondacl::class; +use centreon::misc::db; + +my ($config_core, $config); +my $config_db_centreon; +my $module_id = 'centreondacl'; +my $events = [ + 'ACLREADY', + 'ACLPURGEORGANIZATION', 'ACLRESYNC', + 'ACLADDHOST', 'ACLDELHOST', 'ACLADDSERVICE', 'ACLDELSERVICE', + 'ACLUPDATETAG', 'ACLDELTAG', + 'ACLUPDATEDOMAIN', 'ACLDELDOMAIN', + 'ACLUPDATEENVIRONMENT', 'ACLDELENVIRONMENT', + 'ACLUPDATEPOLLER', 'ACLDELPOLLER', +]; + +my $last_organizations = {}; # Last values from centreon database +my $organizations = {}; +my $organizations_pid = {}; +my $stop = 0; +my $timer_check = time(); +my $config_check_organizations_time; +my $on_demand = 0; +my ($resync_auto_disable, $resync_time, $resync_random_windows) = (0, 28800, 7200); + +sub register { + my (%options) = @_; + + $config = $options{config}; + $config_core = $options{config_core}; + $config_db_centreon = $options{config_db_centreon}; + $config_check_organizations_time = defined($config->{check_organizations_time}) ? $config->{check_organizations_time} : 3600; + $on_demand = defined($config->{on_demand}) && $config->{on_demand} == 1 ? 1 : 0; + $resync_auto_disable = defined($config->{resync_auto_disable}) && $config->{resync_auto_disable} == 1 ? 1 : 0; + $resync_time = defined($config->{resync_time}) && $config->{resync_time} > 0 ? $config->{resync_time} : 28800; + $resync_random_windows = defined($config->{resync_random_windows}) && $config->{resync_random_windows} > 0 ? $config->{resync_random_windows} : 7200; + return ($events, $module_id); +} + +sub init { + my (%options) = @_; + + $last_organizations = get_organizations(logger => $options{logger}); + # We quit + return if ($on_demand == 1); + foreach my $organization_id (keys %{$last_organizations}) { + create_child(organization_id => $organization_id, logger => $options{logger}); + } +} + +sub routing { + my (%options) = @_; + + my $data; + eval { + $data = JSON->new->utf8->decode($options{data}); + }; + if ($@) { + $options{logger}->writeLogError("Cannot decode json data: $@"); + centreon::centreond::common::add_history(dbh => $options{dbh}, + code => 100, token => $options{token}, + data => { message => 'centreondacl: cannot decode json' }, + json_encode => 1); + return undef; + } + + if ($options{action} eq 'ACLREADY') { + $organizations->{$data->{organization_id}}->{ready} = 1; + return undef; + } + + if (!defined($data->{organization_id}) || !defined($last_organizations->{$data->{organization_id}})) { + centreon::centreond::common::add_history(dbh => $options{dbh}, + code => 100, token => $options{token}, + data => { message => 'centreondacl: need a valid organization id' }, + json_encode => 1); + return undef; + } + + if ($on_demand == 1) { + if (!defined($organizations->{$data->{organization_id}})) { + create_child(organization_id => $data->{organization_id}, logger => $options{logger}, on_demand => 1); + } + } + + if (centreon::script::centreondcore::waiting_ready(ready => \$organizations->{$data->{organization_id}}->{ready}) == 0) { + centreon::centreond::common::add_history(dbh => $options{dbh}, + code => 100, token => $options{token}, + data => { message => 'centreondacl: still no ready' }, + json_encode => 1); + return undef; + } + + centreon::centreond::common::zmq_send_message(socket => $options{socket}, identity => 'centreondacl-' . $data->{organization_id}, + action => $options{action}, data => $options{data}, token => $options{token}, + ); +} + +sub gently { + my (%options) = @_; + + $stop = 1; + # They stop from themself in 'on_demand' mode + return if ($on_demand == 1); + foreach my $organization_id (keys %{$organizations}) { + $options{logger}->writeLogInfo("centreond-acl: Send TERM signal for organization '" . $organization_id . "'"); + if ($organizations->{$organization_id}->{running} == 1) { + CORE::kill('TERM', $organizations->{$organization_id}->{pid}); + } + } +} + +sub kill_internal { + my (%options) = @_; + + foreach (keys %{$organizations}) { + if ($organizations->{$_}->{running} == 1) { + $options{logger}->writeLogInfo("centreond-acl: Send KILL signal for organization '" . $_ . "'"); + CORE::kill('KILL', $organizations->{$_}->{pid}); + } + } +} + +sub kill { + my (%options) = @_; + + +} + +sub check { + my (%options) = @_; + + if ($timer_check - time() > $config_check_organizations_time) { + sync_organization_childs(logger => $options{logger}); + $timer_check = time(); + } + + my $count = 0; + foreach my $pid (keys %{$options{dead_childs}}) { + # Not me + next if (!defined($organizations_pid->{$pid})); + + # If someone dead, we recreate + delete $organizations->{$organizations_pid->{$pid}}; + delete $organizations_pid->{$pid}; + delete $options{dead_childs}->{$pid}; + if ($stop == 0 && $on_demand == 0) { + # Need to check if we need to recreate (can be a organization destruction)!!! + sync_organization_childs(logger => $options{logger}); + } + } + + foreach (keys %{$organizations}) { + if ($organizations->{$_}->{running} == 1) { + $count++; + # Test resync + if ($resync_auto_disable == 0 && defined($last_organizations->{$_}) && time() > $last_organizations->{$_}) { + routing(dbh => $options{dbh}, socket => $options{internal_socket}, logger => $options{logger}, + action => 'ACLRESYNC', data => '{ "organization_id": ' . $_ . ' } ', + token => 'internal_action_aclresync_' . $_ . ''); + $last_organizations->{$_} = time() + $resync_time + int(rand($resync_random_windows)); + } + } + } + + return $count; +} + +# Specific functions +sub get_organizations { + my (%options) = @_; + + my $db = centreon::misc::db->new( + dsn => $config_db_centreon->{dsn}, + user => $config_db_centreon->{username}, + password => $config_db_centreon->{password}, + force => 1, + logger => $options{logger} + ); + my ($status, $sth) = $db->query("SELECT organization_id FROM cfg_organizations WHERE active = '1'"); + my $org = {}; + while ((my $row = $sth->fetchrow_arrayref())) { + $org->{$$row[0]} = time() + $resync_time + int(rand($resync_random_windows)); + } + return $org; +} + +sub sync_organization_childs { + my (%options) = @_; + + $last_organizations = get_organizations(logger => $options{logger}); + foreach my $organization_id (keys %{$last_organizations}) { + if (!defined($organizations->{$organization_id}) && $on_demand == 0) { + create_child(organization_id => $organization_id, logger => $options{logger}); + } + } + + # TODO. Check Orga ID in my tables with a distinct to get what i need to destroy +} + +sub create_child { + my (%options) = @_; + + $options{logger}->writeLogInfo("Create centreondacl for organization id '" . $options{organization_id} . "'"); + my $child_pid = fork(); + if ($child_pid == 0) { + my $module = modules::centreondacl::class->new(logger => $options{logger}, + config_core => $config_core, + config => $config, + config_db_centreon => $config_db_centreon, + organization_id => $options{organization_id} + ); + $module->run(on_demand => $options{on_demand}); + exit(0); + } + $options{logger}->writeLogInfo("PID $child_pid for centreondacl for organization id '" . $options{organization_id} . "'"); + $organizations->{$options{organization_id}} = { pid => $child_pid, ready => 0, running => 1 }; + $organizations_pid->{$child_pid} = $options{organization_id}; +} + +1; diff --git a/gorgone/modules/centreondaction/class.pm b/gorgone/modules/centreondaction/class.pm new file mode 100644 index 00000000000..7d7e4dbb2c5 --- /dev/null +++ b/gorgone/modules/centreondaction/class.pm @@ -0,0 +1,252 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package modules::centreondaction::class; + +use strict; +use warnings; +use centreon::centreond::common; +use centreon::misc::misc; +use ZMQ::LibZMQ4; +use ZMQ::Constants qw(:all); + +my %handlers = (TERM => {}, HUP => {}); +my ($connector, $socket); + +sub new { + my ($class, %options) = @_; + $connector = {}; + $connector->{logger} = $options{logger}; + $connector->{config} = $options{config}; + $connector->{config_core} = $options{config_core}; + $connector->{stop} = 0; + + $connector->{enginecommand_timeout} = defined($connector->{config}{enginecommand_timeout}) ? $connector->{config}{enginecommand_timeout} : 30; + $connector->{command_timeout} = defined($connector->{config}{command_timeout}) ? $connector->{config}{command_timeout} : 30; + + bless $connector, $class; + $connector->set_signal_handlers; + return $connector; +} + +sub set_signal_handlers { + my $self = shift; + + $SIG{TERM} = \&class_handle_TERM; + $handlers{TERM}->{$self} = sub { $self->handle_TERM() }; + $SIG{HUP} = \&class_handle_HUP; + $handlers{HUP}->{$self} = sub { $self->handle_HUP() }; +} + +sub handle_HUP { + my $self = shift; + $self->{reload} = 0; +} + +sub handle_TERM { + my $self = shift; + $self->{logger}->writeLogInfo("centreond-action $$ Receiving order to stop..."); + $self->{stop} = 1; +} + +sub class_handle_TERM { + foreach (keys %{$handlers{TERM}}) { + &{$handlers{TERM}->{$_}}(); + } +} + +sub class_handle_HUP { + foreach (keys %{$handlers{HUP}}) { + &{$handlers{HUP}->{$_}}(); + } +} + +sub action_command { + my ($self, %options) = @_; + + if (!defined($options{data}->{command}) || $options{data}->{command} eq '') { + centreon::centreond::common::zmq_send_message(socket => $options{socket_log}, + action => 'PUTLOG', data => { code => 35, etime => time(), token => $options{token}, data => { message => "need command argument" } }, + json_encode => 1); + return -1; + } + + my ($error, $stdout, $return_code) = centreon::misc::misc::backtick(command => $options{data}->{command}, + #arguments => [@$args, $sub_cmd], + timeout => $self->{command_timeout}, + wait_exit => 1, + redirect_stderr => 1, + logger => $self->{logger}); + if ($error <= -1000) { + centreon::centreond::common::zmq_send_message(socket => $options{socket_log}, + action => 'PUTLOG', data => { code => 35, etime => time(), token => $options{token}, data => { message => "command '$options{data}->{command}' execution issue: $stdout" } }, + json_encode => 1); + return -1; + } + + centreon::centreond::common::zmq_send_message(socket => $options{socket_log}, + action => 'PUTLOG', data => { code => 36, etime => time(), token => $options{token}, data => { message => "command '$options{data}->{command}' had finished", stdout => $stdout, exit_code => $return_code } }, + json_encode => 1); + return 0; +} + +sub action_enginecommand { + my ($self, %options) = @_; + + if (!defined($options{data}->{engine_pipe}) || $options{data}->{engine_pipe} eq '') { + centreon::centreond::common::zmq_send_message(socket => $options{socket_log}, + action => 'PUTLOG', data => { code => 35, etime => time(), token => $options{token}, data => { message => "need engine_pipe argument" } }, + json_encode => 1); + return -1; + } + if (! -e $options{data}->{engine_pipe}) { + centreon::centreond::common::zmq_send_message(socket => $options{socket_log}, + action => 'PUTLOG', data => { code => 35, etime => time(), token => $options{token}, data => { message => "command '$options{data}->{command}' - engine_pipe '$options{data}->{engine_pipe}' must exist" } }, + json_encode => 1); + return -1; + } + if (! -p $options{data}->{engine_pipe}) { + centreon::centreond::common::zmq_send_message(socket => $options{socket_log}, + action => 'PUTLOG', data => { code => 35, etime => time(), token => $options{token}, data => { message => "command '$options{data}->{command}' - engine_pipe '$options{data}->{engine_pipe}' must be a pipe file" } }, + json_encode => 1); + return -1; + } + if (! -w $options{data}->{engine_pipe}) { + centreon::centreond::common::zmq_send_message(socket => $options{socket_log}, + action => 'PUTLOG', data => { code => 35, etime => time(), token => $options{token}, data => { message => "command '$options{data}->{command}' - engine_pipe '$options{data}->{engine_pipe}' must be writeable" } }, + json_encode => 1); + return -1; + } + + $self->{logger}->writeLogDebug("centreond-action: class: submit engine command '$options{data}->{command}'"); + my $fh; + eval { + local $SIG{ALRM} = sub { die "Timeout command\n" }; + alarm $self->{enginecommand_timeout}; + open($fh, ">", $options{data}->{engine_pipe}) or die "cannot open '$options{data}->{engine_pipe}': $!"; + print $fh $options{data}->{command} . "\n"; + close $fh; + alarm 0; + }; + if ($@) { + close $fh if (defined($fh)); + $self->{logger}->writeLogError("centreond-action: class: submit engine command '$options{data}->{command}' issue: $@"); + centreon::centreond::common::zmq_send_message(socket => $options{socket_log}, + action => 'PUTLOG', data => { code => 35, etime => time(), token => $options{token}, data => { message => "submit command issue '$options{data}->{command}': $@" } }, + json_encode => 1); + return undef; + } + + centreon::centreond::common::zmq_send_message(socket => $options{socket_log}, + action => 'PUTLOG', data => { code => 36, etime => time(), token => $options{token}, data => { message => "command '$options{data}->{command}' had been submitted" } }, + json_encode => 1); + return 0; +} + +sub action_run { + my ($self, %options) = @_; + + my $socket_log = centreon::centreond::common::connect_com(zmq_type => 'ZMQ_DEALER', name => 'centreondaction-'. $$, + logger => $self->{logger}, linger => 5000, + type => $self->{config_core}{internal_com_type}, + path => $self->{config_core}{internal_com_path}); + if ($options{action} eq 'COMMAND') { + $self->action_command(%options, socket_log => $socket_log); + } elsif ($options{action} eq 'ENGINECOMMAND') { + $self->action_enginecommand(%options, socket_log => $socket_log); + } + + centreon::centreond::common::zmq_send_message(socket => $socket_log, + action => 'PUTLOG', data => { code => 32, etime => time(), token => $options{token}, data => { message => "proceed action end" } }, + json_encode => 1); + zmq_close($socket_log); +} + +sub create_child { + my ($self, %options) = @_; + + $self->{logger}->writeLogInfo("Create centreondaction sub-process"); + $options{message} =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; + + my ($action, $token) = ($1, $2); + my $data = JSON->new->utf8->decode($3); + + my $child_pid = fork(); + if (!defined($child_pid)) { + centreon::centreond::common::zmq_send_message(socket => $socket, + action => 'PUTLOG', data => { code => 30, etime => time(), token => $token, data => { message => "cannot fork: $!" } }, + json_encode => 1); + return undef; + } + + if ($child_pid == 0) { + $self->action_run(action => $action, token => $token, data => $data); + exit(0); + } else { + centreon::centreond::common::zmq_send_message(socket => $socket, + action => 'PUTLOG', data => { code => 31, etime => time(), token => $token, data => { message => "proceed action" } }, + json_encode => 1); + } +} + +sub event { + while (1) { + my $message = centreon::centreond::common::zmq_dealer_read_message(socket => $socket); + + $connector->{logger}->writeLogDebug("centreondaction: class: $message"); + + if ($message !~ /^\[ACK\]/) { + $connector->create_child(message => $message); + } + + last unless (centreon::centreond::common::zmq_still_read(socket => $socket)); + } +} + +sub run { + my ($self, %options) = @_; + + # Connect internal + $socket = centreon::centreond::common::connect_com(zmq_type => 'ZMQ_DEALER', name => 'centreondaction', + logger => $self->{logger}, + type => $self->{config_core}{internal_com_type}, + path => $self->{config_core}{internal_com_path}); + centreon::centreond::common::zmq_send_message(socket => $socket, + action => 'ACTIONREADY', data => { }, + json_encode => 1); + $self->{poll} = [ + { + socket => $socket, + events => ZMQ_POLLIN, + callback => \&event, + } + ]; + while (1) { + # we try to do all we can + my $rev = zmq_poll($self->{poll}, 5000); + if ($rev == 0 && $self->{stop} == 1) { + $self->{logger}->writeLogInfo("centreond-action $$ has quit"); + zmq_close($socket); + exit(0); + } + } +} + +1; diff --git a/gorgone/modules/centreondaction/hooks.pm b/gorgone/modules/centreondaction/hooks.pm new file mode 100644 index 00000000000..c15c0e77142 --- /dev/null +++ b/gorgone/modules/centreondaction/hooks.pm @@ -0,0 +1,154 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package modules::centreondaction::hooks; + +use warnings; +use strict; +use centreon::script::centreondcore; +use modules::centreondaction::class; + +my $config_core; +my $config; +my $module_id = 'centreondaction'; +my $events = [ + 'ACTIONREADY', +]; +my $action = {}; +my $stop = 0; + +sub register { + my (%options) = @_; + + $config = $options{config}; + $config_core = $options{config_core}; + if (!defined($config->{disable_command_event}) || $config->{disable_command_event} != 1) { + push @{$events}, 'COMMAND'; + } + if (!defined($config->{disable_enginecommand_event}) || $config->{disable_enginecommand_event} != 1) { + push @{$events}, 'ENGINECOMMAND'; + } + return ($events, $module_id); +} + +sub init { + my (%options) = @_; + + create_child(logger => $options{logger}); +} + +sub routing { + my (%options) = @_; + + my $data; + eval { + $data = JSON->new->utf8->decode($options{data}); + }; + if ($@) { + $options{logger}->writeLogError("Cannot decode json data: $@"); + centreon::centreond::common::add_history(dbh => $options{dbh}, + code => 30, token => $options{token}, + data => { msg => 'centreondaction: cannot decode json' }, + json_encode => 1); + return undef; + } + + if ($options{action} eq 'ACTIONREADY') { + $action->{ready} = 1; + return undef; + } + + if (centreon::script::centreondcore::waiting_ready(ready => \$action->{ready}) == 0) { + centreon::centreond::common::add_history(dbh => $options{dbh}, + code => 30, token => $options{token}, + data => { msg => 'centreondaction: still no ready' }, + json_encode => 1); + return undef; + } + + centreon::centreond::common::zmq_send_message(socket => $options{socket}, identity => 'centreondaction', + action => $options{action}, data => $options{data}, token => $options{token}, + ); +} + +sub gently { + my (%options) = @_; + + $stop = 1; + $options{logger}->writeLogInfo("centreond-action: Send TERM signal"); + if ($action->{running} == 1) { + CORE::kill('TERM', $action->{pid}); + } +} + +sub kill { + my (%options) = @_; + + if ($action->{running} == 1) { + $options{logger}->writeLogInfo("centreond-action: Send KILL signal for pool"); + CORE::kill('KILL', $action->{pid}); + } +} + +sub kill_internal { + my (%options) = @_; + +} + +sub check { + my (%options) = @_; + + my $count = 0; + foreach my $pid (keys %{$options{dead_childs}}) { + # Not me + next if ($action->{pid} != $pid); + + $action = {}; + delete $options{dead_childs}->{$pid}; + if ($stop == 0) { + create_child(logger => $options{logger}); + } + } + + $count++ if (defined($action->{running}) && $action->{running} == 1); + + return $count; +} + +# Specific functions +sub create_child { + my (%options) = @_; + + $options{logger}->writeLogInfo("Create centreondaction process"); + my $child_pid = fork(); + if ($child_pid == 0) { + $0 = 'centreond-action'; + my $module = modules::centreondaction::class->new(logger => $options{logger}, + config_core => $config_core, + config => $config, + ); + $module->run(); + exit(0); + } + $options{logger}->writeLogInfo("PID $child_pid centreondaction"); + $action = { pid => $child_pid, ready => 0, running => 1 }; +} + +1; diff --git a/gorgone/modules/centreondcron/class.pm b/gorgone/modules/centreondcron/class.pm new file mode 100644 index 00000000000..2c2c768a153 --- /dev/null +++ b/gorgone/modules/centreondcron/class.pm @@ -0,0 +1,136 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package modules::centreondcron::class; + +use strict; +use warnings; +use centreon::centreond::common; +use ZMQ::LibZMQ4; +use ZMQ::Constants qw(:all); +use Schedule::Cron; + +my %handlers = (TERM => {}, HUP => {}); +my ($connector, $socket); + +sub new { + my ($class, %options) = @_; + $connector = {}; + $connector->{logger} = $options{logger}; + $connector->{config} = $options{config}; + $connector->{config_core} = $options{config_core}; + $connector->{stop} = 0; + + bless $connector, $class; + $connector->set_signal_handlers; + return $connector; +} + +sub set_signal_handlers { + my $self = shift; + + $SIG{TERM} = \&class_handle_TERM; + $handlers{TERM}->{$self} = sub { $self->handle_TERM() }; + $SIG{HUP} = \&class_handle_HUP; + $handlers{HUP}->{$self} = sub { $self->handle_HUP() }; +} + +sub handle_HUP { + my $self = shift; + $self->{reload} = 0; +} + +sub handle_TERM { + my $self = shift; + $self->{logger}->writeLogInfo("centreond-action $$ Receiving order to stop..."); + $self->{stop} = 1; +} + +sub class_handle_TERM { + foreach (keys %{$handlers{TERM}}) { + &{$handlers{TERM}->{$_}}(); + } +} + +sub class_handle_HUP { + foreach (keys %{$handlers{HUP}}) { + &{$handlers{HUP}->{$_}}(); + } +} + +sub event { + while (1) { + my $message = centreon::centreond::common::zmq_dealer_read_message(socket => $socket); + + $connector->{logger}->writeLogDebug("centreondcron: class: $message"); + + last unless (centreon::centreond::common::zmq_still_read(socket => $socket)); + } +} + +sub cron_sleep { + my $rev = zmq_poll($connector->{poll}, 1000); + if ($rev == 0 && $connector->{stop} == 1) { + $connector->{logger}->writeLogInfo("centreond-cron $$ has quit"); + zmq_close($socket); + exit(0); + } +} + +sub dispatcher { + my ($options) = @_; + + $options->{logger}->writeLogInfo("Job is starting"); +} + +sub run { + my ($self, %options) = @_; + + # Connect internal + $socket = centreon::centreond::common::connect_com( + zmq_type => 'ZMQ_DEALER', name => 'centreondcron', + logger => $self->{logger}, + type => $self->{config_core}{internal_com_type}, + path => $self->{config_core}{internal_com_path} + ); + centreon::centreond::common::zmq_send_message( + socket => $socket, + action => 'CRONREADY', data => { }, + json_encode => 1 + ); + $self->{poll} = [ + { + socket => $socket, + events => ZMQ_POLLIN, + callback => \&event, + } + ]; + my $cron = new Schedule::Cron(\&dispatcher, nostatus => 1, nofork => 1); + $cron->add_entry('* * * * *', \&dispatcher, { logger => $self->{logger}, plop => 1 }); + + # Each cron should have an ID in centreon DB. And like that, you can delete some not here. + #print Data::Dumper::Dumper($cron->list_entries()); + + $cron->run(sleep => \&cron_sleep); + zmq_close($socket); + exit(0); +} + +1; diff --git a/gorgone/modules/centreondcron/hooks.pm b/gorgone/modules/centreondcron/hooks.pm new file mode 100644 index 00000000000..d715273e2cb --- /dev/null +++ b/gorgone/modules/centreondcron/hooks.pm @@ -0,0 +1,154 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package modules::centreondcron::hooks; + +use warnings; +use strict; +use centreon::script::centreondcore; +use modules::centreondcron::class; + +my $config_core; +my $config; +my $module_id = 'centreondcron'; +my $events = [ + 'CRONREADY', 'RELOADCRON', +]; +my $cron = {}; +my $stop = 0; + +sub register { + my (%options) = @_; + + $config = $options{config}; + $config_core = $options{config_core}; + return ($events, $module_id); +} + +sub init { + my (%options) = @_; + + create_child(logger => $options{logger}); +} + +sub routing { + my (%options) = @_; + + my $data; + eval { + $data = JSON->new->utf8->decode($options{data}); + }; + if ($@) { + $options{logger}->writeLogError("Cannot decode json data: $@"); + centreon::centreond::common::add_history( + dbh => $options{dbh}, + code => 10, token => $options{token}, + data => { message => 'centreondcron: cannot decode json' }, + json_encode => 1 + ); + return undef; + } + + if ($options{action} eq 'CRONREADY') { + $cron->{ready} = 1; + return undef; + } + + if (centreon::script::centreondcore::waiting_ready(ready => \$cron->{ready}) == 0) { + centreon::centreond::common::add_history( + dbh => $options{dbh}, + code => 10, token => $options{token}, + data => { message => 'centreondcron: still no ready' }, + json_encode => 1 + ); + return undef; + } + + centreon::centreond::common::zmq_send_message( + socket => $options{socket}, identity => 'centreondcron', + action => $options{action}, data => $options{data}, token => $options{token}, + ); +} + +sub gently { + my (%options) = @_; + + $stop = 1; + $options{logger}->writeLogInfo("centreond-cron: Send TERM signal"); + if ($cron->{running} == 1) { + CORE::kill('TERM', $cron->{pid}); + } +} + +sub kill { + my (%options) = @_; + + if ($cron->{running} == 1) { + $options{logger}->writeLogInfo("centreond-cron: Send KILL signal for pool"); + CORE::kill('KILL', $cron->{pid}); + } +} + +sub kill_internal { + my (%options) = @_; + +} + +sub check { + my (%options) = @_; + + my $count = 0; + foreach my $pid (keys %{$options{dead_childs}}) { + # Not me + next if ($cron->{pid} != $pid); + + $cron = {}; + delete $options{dead_childs}->{$pid}; + if ($stop == 0) { + create_child(logger => $options{logger}); + } + } + + $count++ if (defined($cron->{running}) && $cron->{running} == 1); + + return $count; +} + +# Specific functions +sub create_child { + my (%options) = @_; + + $options{logger}->writeLogInfo("Create centreondcron process"); + my $child_pid = fork(); + if ($child_pid == 0) { + $0 = 'centreond-cron'; + my $module = modules::centreondcron::class->new( + logger => $options{logger}, + config_core => $config_core, + config => $config, + ); + $module->run(); + exit(0); + } + $options{logger}->writeLogInfo("PID $child_pid centreondcron"); + $cron = { pid => $child_pid, ready => 0, running => 1 }; +} + +1; diff --git a/gorgone/modules/centreondproxy/class.pm b/gorgone/modules/centreondproxy/class.pm new file mode 100644 index 00000000000..75cf3d466e0 --- /dev/null +++ b/gorgone/modules/centreondproxy/class.pm @@ -0,0 +1,226 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package modules::centreondproxy::class; + +use strict; +use warnings; +use centreon::centreond::common; +use centreon::centreond::clientzmq; +use ZMQ::LibZMQ4; +use ZMQ::Constants qw(:all); + +my %handlers = (TERM => {}, HUP => {}); +my ($connector, $socket); + +sub new { + my ($class, %options) = @_; + $connector = {}; + $connector->{logger} = $options{logger}; + $connector->{core_id} = $options{core_id}; + $connector->{pool_id} = $options{pool_id}; + $connector->{config} = $options{config}; + $connector->{config_core} = $options{config_core}; + $connector->{stop} = 0; + $connector->{clients} = {}; + + bless $connector, $class; + $connector->set_signal_handlers; + return $connector; +} + +sub set_signal_handlers { + my $self = shift; + + $SIG{TERM} = \&class_handle_TERM; + $handlers{TERM}->{$self} = sub { $self->handle_TERM() }; + $SIG{HUP} = \&class_handle_HUP; + $handlers{HUP}->{$self} = sub { $self->handle_HUP() }; +} + +sub handle_HUP { + my $self = shift; + $self->{reload} = 0; +} + +sub handle_TERM { + my $self = shift; + $self->{logger}->writeLogInfo("centreond-proxy $$ Receiving order to stop..."); + $self->{stop} = 1; +} + +sub class_handle_TERM { + foreach (keys %{$handlers{TERM}}) { + &{$handlers{TERM}->{$_}}(); + } +} + +sub class_handle_HUP { + foreach (keys %{$handlers{HUP}}) { + &{$handlers{HUP}->{$_}}(); + } +} + +sub get_client_information { + my ($self, %options) = @_; + + # TODO DATABASE. Si database marche pas. On fait un PUTLOG. + my $result = { type => 1, target_type => 'tcp', target_path => 'localhost:5556', + pubkey => 'keys/poller/pubkey.crt', cipher => 'Cipher::AES', + keysize => '32', vector => '0123456789012345', class => undef, delete => 0 }; + return $result; +} + +sub read_message { + my (%options) = @_; + + return undef if (!defined($options{identity}) || $options{identity} !~ /^proxy-(.*?)-(.*?)$/); + + my ($client_identity) = ($2); + if ($options{data} =~ /^\[PONG\]/) { + if ($options{data} !~ /^\[(.+?)\]\s+\[(.*?)\]\s+\[(.*?)\]/m) { + return undef; + } + my ($action, $token) = ($1, $2); + + centreon::centreond::common::zmq_send_message(socket => $socket, + action => 'PONG', token => $token, target => '', + data => { code => 0, data => { message => 'ping ok', action => 'ping', id => $client_identity } }, + json_encode => 1); + } + elsif ($options{data} =~ /^\[ACK\]\s+\[(.*?)\]\s+(.*)/m) { + my $data; + eval { + $data = JSON->new->utf8->decode($2); + }; + if ($@) { + return undef; + } + + if (defined($data->{data}->{action}) && $data->{data}->{action} eq 'getlog') { + centreon::centreond::common::zmq_send_message(socket => $socket, + action => 'SETLOGS', token => $1, target => '', + data => $2); + } + return undef; + } +} + +sub connect { + my ($self, %options) = @_; + + if ($options{entry}->{type} == 1) { + $options{entry}->{class} = centreon::centreond::clientzmq->new(identity => 'proxy-' . $self->{core_id} . '-' . $options{id}, + cipher => $options{entry}->{cipher}, + vector => $options{entry}->{vector}, + pubkey => $options{entry}->{pubkey}, + target_type => $options{entry}->{target_type}, + target_path => $options{entry}->{target_path}, + logger => $self->{logger} + ); + $options{entry}->{class}->init(callback => \&read_message); + } +} + +sub proxy { + my (%options) = @_; + + if ($options{message} !~ /^\[(.+?)\]\s+\[(.*?)\]\s+\[(.*?)\]\s+(.*)$/m) { + return undef; + } + my ($action, $token, $target, $data) = ($1, $2, $3, $4); + + my $entry; + if (!defined($connector->{clients}->{$target})) { + $entry = $connector->get_client_information(id => $target); + return if (!defined($entry)); + + $connector->connect(id => $target, entry => $entry); + $connector->{clients}->{$target} = $entry; + } else { + $entry = $connector->{clients}->{$target}; + } + + if ($entry->{type} == 1) { + my ($status, $msg) = $entry->{class}->send_message(action => $action, token => $token, + target => '', data => $data); + if ($status != 0) { + # error we put log and we close (TODO the log) + $connector->{logger}->writeLogError("centreondproxy: class: send message problem for '$target': $msg"); + $connector->{clients}->{$target}->{delete} = 1; + } + } + + $connector->{logger}->writeLogDebug("centreondproxy: class: [action = $action] [token = $token] [target = $target] [data = $data]"); +} + +sub event_internal { + while (1) { + my $message = centreon::centreond::common::zmq_dealer_read_message(socket => $socket); + + proxy(message => $message); + last unless (centreon::centreond::common::zmq_still_read(socket => $socket)); + } +} + +sub run { + my ($self, %options) = @_; + + # Connect internal + $socket = centreon::centreond::common::connect_com(zmq_type => 'ZMQ_DEALER', name => 'centreondproxy-' . $self->{pool_id}, + logger => $self->{logger}, + type => $self->{config_core}{internal_com_type}, + path => $self->{config_core}{internal_com_path}); + centreon::centreond::common::zmq_send_message(socket => $socket, + action => 'PROXYREADY', data => { pool_id => $self->{pool_id} }, + json_encode => 1); + my $poll = { + socket => $socket, + events => ZMQ_POLLIN, + callback => \&event_internal, + }; + while (1) { + my $polls = [$poll]; + foreach (keys %{$self->{clients}}) { + if ($self->{clients}->{$_}->{delete} == 1) { + $self->{clients}->{$_}->{class}->close(); + delete $self->{clients}->{$_}; + next; + } + if ($self->{clients}->{$_}->{type} == 1) { + push @{$polls}, $self->{clients}->{$_}->{class}->get_poll(); + } + } + + # we try to do all we can + my $rev = zmq_poll($polls, 5000); + + # Sometimes (with big message) we have a undef ??!!! + next if (!defined($rev)); + + if ($rev == 0 && $self->{stop} == 1) { + $self->{logger}->writeLogInfo("centreond-proxy $$ has quit"); + zmq_close($socket); + exit(0); + } + } +} + +1; diff --git a/gorgone/modules/centreondproxy/hooks.pm b/gorgone/modules/centreondproxy/hooks.pm new file mode 100644 index 00000000000..fdc3542ded4 --- /dev/null +++ b/gorgone/modules/centreondproxy/hooks.pm @@ -0,0 +1,475 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package modules::centreondproxy::hooks; + +use warnings; +use strict; +use centreon::script::centreondcore; +use centreon::centreond::common; +use modules::centreondproxy::class; + +my $config_core; +my $config; +my $module_id = 'centreondproxy'; +my $events = [ + 'PROXYREADY', 'SETLOGS', 'PONG', 'REGISTERNODE', 'UNREGISTERNODE', # internal. Shouldn't be used by third party clients + 'ADDPOLLER', +]; + +my $synctime_error = 0; +my $synctime_pollers = {}; # get last time retrieved +my $synctime_lasttime; +my $synctime_option; +my $synctimeout_option; +my $ping_option; +my $ping_time = 0; + +my $last_pong = {}; +my $register_pollers = {}; +my $last_pollers = {}; # Last values from centreon database and the type +my $pools = {}; +my $pools_pid = {}; +my $poller_pool = {}; +my $rr_current = 0; +my $stop = 0; +my ($external_socket, $internal_socket, $core_id); + +sub register { + my (%options) = @_; + + $config = $options{config}; + $config_core = $options{config_core}; + return ($events, $module_id); +} + +sub init { + my (%options) = @_; + + $synctime_lasttime = time(); + $synctime_option = defined($config->{synchistory_time}) ? $config->{synchistory_time} : 300; + $synctimeout_option = defined($config->{synchistory_timeout}) ? $config->{synchistory_timeout} : 120; + $ping_option = defined($config->{ping}) ? $config->{ping} : 60; + + $core_id = $options{id}; + $external_socket = $options{external_socket}; + $internal_socket = $options{internal_socket}; + $last_pollers = get_pollers(dbh => $options{dbh}); + for my $pool_id (1..$config->{pool}) { + create_child(pool_id => $pool_id, logger => $options{logger}); + } +} + +sub routing { + my (%options) = @_; + + my $data; + eval { + $data = JSON->new->utf8->decode($options{data}); + }; + if ($@) { + $options{logger}->writeLogError("Cannot decode json data: $@"); + centreon::centreond::common::add_history(dbh => $options{dbh}, + code => 20, token => $options{token}, + data => { message => 'centreondproxy: cannot decode json' }, + json_encode => 1); + return undef; + } + + if ($options{action} eq 'PONG') { + return undef if (!defined($data->{data}->{id}) || $data->{data}->{id} eq ''); + $last_pong->{$data->{data}->{id}} = time(); + $options{logger}->writeLogInfo("centreond-proxy: pong received from '" . $data->{data}->{id} . "'"); + return undef; + } + + if ($options{action} eq 'UNREGISTERNODE') { + $options{logger}->writeLogInfo("centreond-proxy: poller '" . $data->{id} . "' is unregistered"); + if (defined($register_pollers->{$data->{id}})) { + delete $register_pollers->{$data->{id}}; + delete $synctime_pollers->{$data->{id}}; + } + return undef; + } + + if ($options{action} eq 'REGISTERNODE') { + $options{logger}->writeLogInfo("centreond-proxy: poller '" . $data->{id} . "' is registered"); + $register_pollers->{$data->{id}} = 1; + $last_pong->{$data->{id}} = 0 if (!defined($last_pong->{$data->{id}})); + if ($synctime_error == 0 && !defined($synctime_pollers->{$options{target}}) && + !defined($synctime_pollers->{$data->{id}})) { + $synctime_pollers->{$data->{id}} = { ctime => 0, in_progress => 0, in_progress_time => -1, last_id => 0 }; + } + return undef; + } + + if ($options{action} eq 'PROXYREADY') { + $pools->{$data->{pool_id}}->{ready} = 1; + return undef; + } + + if ($options{action} eq 'SETLOGS') { + setlogs(dbh => $options{dbh}, data => $data, token => $options{token}, logger => $options{logger}); + return undef; + } + + if (!defined($options{target}) || + (!defined($last_pollers->{$options{target}}) && !defined($register_pollers->{$options{target}}))) { + centreon::centreond::common::add_history(dbh => $options{dbh}, + code => 20, token => $options{token}, + data => { message => 'centreondproxy: need a valid poller id' }, + json_encode => 1); + return undef; + } + + if ($options{action} eq 'GETLOG') { + if ($synctime_error == -1 || get_sync_time(dbh => $options{dbh}) == -1) { + centreon::centreond::common::add_history(dbh => $options{dbh}, + code => 20, token => $options{token}, + data => { message => 'centreondproxy: problem to getlog' }, + json_encode => 1); + return undef; + } + + if ($synctime_pollers->{$options{target}}->{in_progress} == 1) { + centreon::centreond::common::add_history(dbh => $options{dbh}, + code => 20, token => $options{token}, + data => { message => 'centreondproxy: getlog already in progress' }, + json_encode => 1); + return undef; + } + if (defined($last_pollers->{$options{target}}) && $last_pollers->{$options{target}}->{type} == 2) { + centreon::centreond::common::add_history(dbh => $options{dbh}, + code => 20, token => $options{token}, + data => { message => "centreondproxy: can't get log a ssh target" }, + json_encode => 1); + return undef; + } + + # We put the good time to get + my $ctime = $synctime_pollers->{$options{target}}->{ctime}; + my $last_id = $synctime_pollers->{$options{target}}->{last_id}; + $options{data} = centreon::centreond::common::json_encode(data => { ctime => $ctime, id => $last_id }); + $synctime_pollers->{$options{target}}->{in_progress} = 1; + $synctime_pollers->{$options{target}}->{in_progress_time} = time(); + } + + # Mode zmq pull + if (defined($register_pollers->{$options{target}})) { + pull_request(%options, data_decoded => $data); + return undef; + } + + if (centreon::script::centreondcore::waiting_ready_pool(pool => $pools) == 0) { + centreon::centreond::common::add_history(dbh => $options{dbh}, + code => 20, token => $options{token}, + data => { message => 'centreondproxy: still none ready' }, + json_encode => 1); + return undef; + } + + my $identity; + if (defined($poller_pool->{$options{target}})) { + $identity = $poller_pool->{$options{target}}; + } else { + $identity = rr_pool(); + $poller_pool->{$options{target}} = $identity; + } + + centreon::centreond::common::zmq_send_message(socket => $options{socket}, identity => 'centreondproxy-' . $identity, + action => $options{action}, data => $options{data}, token => $options{token}, + target => $options{target} + ); +} + +sub gently { + my (%options) = @_; + + $stop = 1; + foreach my $pool_id (keys %{$pools}) { + $options{logger}->writeLogInfo("centreond-proxy: Send TERM signal for pool '" . $pool_id . "'"); + if ($pools->{$pool_id}->{running} == 1) { + CORE::kill('TERM', $pools->{$pool_id}->{pid}); + } + } +} + +sub kill { + my (%options) = @_; + + foreach (keys %{$pools}) { + if ($pools->{$_}->{running} == 1) { + $options{logger}->writeLogInfo("centreond-proxy: Send KILL signal for pool '" . $_ . "'"); + CORE::kill('KILL', $pools->{$_}->{pid}); + } + } +} + +sub kill_internal { + my (%options) = @_; + +} + +sub check { + my (%options) = @_; + + my $count = 0; + foreach my $pid (keys %{$options{dead_childs}}) { + # Not me + next if (!defined($pools_pid->{$pid})); + + # If someone dead, we recreate + my $pool_id = $pools_pid->{$pid}; + delete $pools->{$pools_pid->{$pid}}; + delete $pools_pid->{$pid}; + delete $options{dead_childs}->{$pid}; + if ($stop == 0) { + create_child(pool_id => $pool_id, logger => $options{logger}); + } + } + + foreach (keys %{$pools}) { + $count++ if ($pools->{$_}->{running} == 1); + } + + # We put synclog request in timeout + foreach (keys %{$synctime_pollers}) { + if ($synctime_pollers->{$_}->{in_progress} == 1 && + time() - $synctime_pollers->{$_}->{in_progress_time} > $synctimeout_option) { + centreon::centreond::common::add_history(dbh => $options{dbh}, + code => 20, + data => { message => "centreondproxy: getlog in timeout for '$_'" }, + json_encode => 1); + $synctime_pollers->{$_}->{in_progress} = 0; + } + } + + # We check if we need synclogs + if ($stop == 0 && + ($synctime_error == 0 || get_sync_time(dbh => $options{dbh}) == 0) && + time() - $synctime_lasttime > $synctime_option) { + $synctime_lasttime = time(); + full_sync_history(dbh => $options{dbh}); + } + + if ($stop == 0 && + time() - $ping_time > $ping_option) { + $options{logger}->writeLogInfo("centreond-proxy: send pings"); + $ping_time = time(); + ping_send(dbh => $options{dbh}); + } + + return $count; +} + +# Specific functions +sub setlogs { + my (%options) = @_; + + if (!defined($options{data}->{data}->{id}) || $options{data}->{data}->{id} eq '') { + centreon::centreond::common::add_history(dbh => $options{dbh}, + code => 20, token => $options{token}, + data => { message => 'centreondproxy: need a id to setlogs' }, + json_encode => 1); + return undef; + } + if ($synctime_pollers->{$options{data}->{data}->{id}}->{in_progress} == 0) { + centreon::centreond::common::add_history(dbh => $options{dbh}, + code => 20, token => $options{token}, + data => { message => 'centreondproxy: skip setlogs response. Maybe too much time to get response. Retry' }, + json_encode => 1); + return undef; + } + + $options{logger}->writeLogInfo("centreondproxy: hooks: received setlogs for '$options{data}->{data}->{id}'"); + + $synctime_pollers->{$options{data}->{data}->{id}}->{in_progress} = 0; + + my $ctime_recent = 0; + my $last_id = 0; + # Transaction + $options{dbh}->transaction_mode(1); + my $status = 0; + foreach (keys %{$options{data}->{data}->{result}}) { + $status = centreon::centreond::common::add_history(dbh => $options{dbh}, + etime => $options{data}->{data}->{result}->{$_}->{etime}, + code => $options{data}->{data}->{result}->{$_}->{code}, + token => $options{data}->{data}->{result}->{$_}->{token}, + data => $options{data}->{data}->{result}->{$_}->{data}); + last if ($status == -1); + $ctime_recent = $options{data}->{data}->{result}->{$_}->{ctime} if ($ctime_recent < $options{data}->{data}->{result}->{$_}->{ctime}); + $last_id = $options{data}->{data}->{result}->{$_}->{id} if ($last_id < $options{data}->{data}->{result}->{$_}->{id}); + } + if ($status == 0 && update_sync_time(dbh => $options{dbh}, id => $options{data}->{data}->{id}, last_id => $last_id, ctime => $ctime_recent) == 0) { + $options{dbh}->commit(); + $synctime_pollers->{$options{data}->{data}->{id}}->{last_id} = $last_id if ($last_id != 0); + $synctime_pollers->{$options{data}->{data}->{id}}->{ctime} = $ctime_recent if ($ctime_recent != 0); + } else { + $options{dbh}->rollback(); + } + $options{dbh}->transaction_mode(0); +} + +sub ping_send { + my (%options) = @_; + + foreach my $id (keys %{$last_pollers}) { + if ($last_pollers->{$id}->{type} == 1) { + routing(socket => $internal_socket, action => 'PING', target => $id, data => '{}', dbh => $options{dbh}); + } + } + + foreach my $id (keys %{$register_pollers}) { + routing(action => 'PING', target => $id, data => '{}', dbh => $options{dbh}); + } +} + +sub full_sync_history { + my (%options) = @_; + + foreach my $id (keys %{$last_pollers}) { + if ($last_pollers->{$id}->{type} == 1) { + routing(socket => $internal_socket, action => 'GETLOG', target => $id, data => '{}', dbh => $options{dbh}); + } + } + + foreach my $id (keys %{$register_pollers}) { + routing(action => 'GETLOG', target => $id, data => '{}', dbh => $options{dbh}); + } +} + +sub update_sync_time { + my (%options) = @_; + + # Nothing to update (no insert before) + return 0 if ($options{ctime} == 0); + + my $status; + if ($synctime_pollers->{$options{id}}->{last_id} == 0) { + ($status) = $options{dbh}->query("INSERT INTO centreond_synchistory (`id`, `ctime`, `last_id`) VALUES (" . $options{dbh}->quote($options{id}) . ", " . $options{dbh}->quote($options{ctime}) . ", " . $options{dbh}->quote($options{last_id}) . ")"); + } else { + ($status) = $options{dbh}->query("UPDATE centreond_synchistory SET `ctime` = " . $options{dbh}->quote($options{ctime}) . ", `last_id` = " . $options{dbh}->quote($options{last_id}) . " WHERE `id` = " . $options{dbh}->quote($options{id})); + } + return $status; +} + +sub get_sync_time { + my (%options) = @_; + + my ($status, $sth) = $options{dbh}->query("SELECT * FROM centreond_synchistory"); + if ($status == -1) { + $synctime_error = -1; + return -1; + } + $synctime_error = 0; + + while (my $row = $sth->fetchrow_hashref()) { + $synctime_pollers->{$row->{id}} = { ctime => $row->{ctime}, in_progress => 0, in_progress_time => -1, last_id => $row->{last_id} }; + } + + return 0; +} + +sub get_pollers { + my (%options) = @_; + # TODO: 1 for 'zmq', 2 for 'ssh' + + my $pollers = {}; + foreach (([1, 1], [2, 1], [10, 1], [166, 2], [140, 1])) { + $pollers->{${$_}[0]} = { type => ${$_}[1] }; + $synctime_pollers->{${$_}[0]} = { ctime => 0, in_progress => 0, in_progress_time => -1, last_id => 0 }; + $last_pong->{${$_}[0]} = 0 if (!defined($last_pong->{${$_}[0]})); + } + + get_sync_time(dbh => $options{dbh}); + + return $pollers; +} + +sub rr_pool { + my (%options) = @_; + + while (1) { + $rr_current = $rr_current % $config->{pool}; + if ($pools->{$rr_current + 1}->{ready} == 1) { + $rr_current++; + return $rr_current; + } + $rr_current++; + } +} + +sub create_child { + my (%options) = @_; + + $options{logger}->writeLogInfo("Create centreondproxy for pool id '" . $options{pool_id} . "'"); + my $child_pid = fork(); + if ($child_pid == 0) { + $0 = 'centreond-proxy'; + my $module = modules::centreondproxy::class->new(logger => $options{logger}, + config_core => $config_core, + config => $config, + pool_id => $options{pool_id}, + core_id => $core_id + ); + $module->run(); + exit(0); + } + $options{logger}->writeLogInfo("PID $child_pid centreondproxy for pool id '" . $options{pool_id} . "'"); + $pools->{$options{pool_id}} = { pid => $child_pid, ready => 0, running => 1 }; + $pools_pid->{$child_pid} = $options{pool_id}; +} + +sub pull_request { + my (%options) = @_; + + # No target anymore. We remove it. + my $message = centreon::centreond::common::build_protocol(action => $options{action}, data => $options{data}, token => $options{token}, + target => '' + ); + my ($status, $key) = centreon::centreond::common::is_handshake_done(dbh => $options{dbh}, identity => unpack('H*', $options{target})); + if ($status == 0) { + centreon::centreond::common::add_history(dbh => $options{dbh}, + code => 20, token => $options{token}, + data => { message => "centreondproxy: node '" . $options{target} . "' had never been connected" }, + json_encode => 1); + return undef; + } + + # Should call here the function to transform data and do some put logs. A loop (because it will also be used in sub proxy process) + # Catch some actions call and do some transformation (on file copy) + # TODO + + centreon::centreond::common::zmq_send_message(socket => $external_socket, + cipher => $config_core->{cipher}, + vector => $config_core->{vector}, + symkey => $key, + identity => $options{target}, + message => $message); +} + +sub get_constatus_result { + my (%options) = @_; + + my $result = { last_ping => $ping_time, entries => $last_pong }; + return $result; +} + +1; diff --git a/gorgone/modules/centreondpull/hooks.pm b/gorgone/modules/centreondpull/hooks.pm new file mode 100644 index 00000000000..47a25c54a6b --- /dev/null +++ b/gorgone/modules/centreondpull/hooks.pm @@ -0,0 +1,163 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package modules::centreondpull::hooks; + +use warnings; +use strict; +use centreon::centreond::clientzmq; + +my $config_core; +my $config; +my $module_id = 'centreondpull'; +my $events = [ +]; +my $stop = 0; +my $client; +my $socket_to_internal; +my $logger; + +sub register { + my (%options) = @_; + + $config = $options{config}; + $config_core = $options{config_core}; + return ($events, $module_id); +} + +sub init { + my (%options) = @_; + + $logger = $options{logger}; + # Connect internal + $socket_to_internal = centreon::centreond::common::connect_com(zmq_type => 'ZMQ_DEALER', name => 'centreondpull', + logger => $options{logger}, + type => $config_core->{internal_com_type}, + path => $config_core->{internal_com_path}, + linger => $config->{linger} + ); + $client = centreon::centreond::clientzmq->new(identity => $config_core->{id}, + cipher => $config->{cipher}, + vector => $config->{vector}, + pubkey => $config->{pubkey}, + target_type => $config->{target_type}, + target_path => $config->{target_path}, + logger => $options{logger}, + ping => $config->{ping}, + ping_timeout => $config->{ping_timeout} + ); + $client->init(callback => \&read_message); + + $client->send_message(action => 'REGISTERNODE', data => { id => $config_core->{id} }, + json_encode => 1); + centreon::centreond::common::add_zmq_pollin(socket => $socket_to_internal, + callback => \&from_router, + poll => $options{poll}); +} + +sub routing { + my (%options) = @_; + +} + +sub gently { + my (%options) = @_; + + $stop = 1; + $client->send_message(action => 'UNREGISTERNODE', data => { id => $config_core->{id} }, + json_encode => 1); + $client->close(); + return 0; +} + +sub kill { + my (%options) = @_; + + return 0; +} + +sub kill_internal { + my (%options) = @_; + + return 0; +} + +sub check { + my (%options) = @_; + + if ($stop == 0) { + # If distant server restart, it's a not problem. It save the key. + # But i don't have the registernode anymore. The ping is the 'registernode' for pull mode. + $client->ping(poll => $options{poll}, action => 'REGISTERNODE', data => { id => $config_core->{id} }, json_encode => 1); + } + return 0; +} + +####### specific + +sub transmit_back { + my (%options) = @_; + + if ($options{message} =~ /^\[ACK\]\s+\[(.*?)\]\s+(.*)/m) { + my $data; + eval { + $data = JSON->new->utf8->decode($2); + }; + if ($@) { + return $options{message}; + } + + if (defined($data->{data}->{action}) && $data->{data}->{action} eq 'getlog') { + return '[SETLOGS] [' . $1 . '] [] ' . $2; + } + return undef; + } elsif ($options{message} =~ /^\[PONG\]/) { + return $options{message}; + } + return undef; +} + +sub from_router { + while (1) { + my $message = transmit_back(message => centreon::centreond::common::zmq_dealer_read_message(socket => $socket_to_internal)); + # Only send back SETLOGS and PONG + if (defined($message)) { + $logger->writeLogDebug("centreond-pull: hook: read message from internal: $message"); + $client->send_message(message => $message); + } + last unless (centreon::centreond::common::zmq_still_read(socket => $socket_to_internal)); + } +} + +sub read_message { + my (%options) = @_; + + # We skip. Dont need to send it in centreond-core + if ($options{data} =~ /^\[ACK\]/) { + return undef; + } + + $logger->writeLogDebug("centreond-pull: hook: read message from external: $options{data}"); + centreon::centreond::common::zmq_send_message(socket => $socket_to_internal, + message => $options{data}); +} + + +1; diff --git a/gorgone/modules/scom/class.pm b/gorgone/modules/scom/class.pm new file mode 100644 index 00000000000..65a42111bb9 --- /dev/null +++ b/gorgone/modules/scom/class.pm @@ -0,0 +1,312 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package modules::scom::class; + +use strict; +use warnings; +use centreon::centreond::common; +use centreon::misc::objects::object; +use centreon::misc::http::http; +use ZMQ::LibZMQ4; +use ZMQ::Constants qw(:all); +use MIME::Base64; +use JSON::XS; +use Data::Dumper; + +my %handlers = (TERM => {}, HUP => {}); +my ($connector, $socket); + +sub new { + my ($class, %options) = @_; + $connector = {}; + $connector->{logger} = $options{logger}; + $connector->{container_id} = $options{container_id}; + $connector->{config} = $options{config}; + $connector->{config_core} = $options{config_core}; + $connector->{config_scom} = $options{config_scom}; + $connector->{config_db_centstorage} = $options{config_db_centstorage}; + $connector->{stop} = 0; + + $connector->{dsmhost} = $options{config_scom}->{dsmhost}; + $connector->{dsmslot} = $options{config_scom}->{dsmslot}; + $connector->{dsmmacro} = $options{config_scom}->{dsmmacro}; + $connector->{dsmalertmessage} = $options{config_scom}->{dsmalertmessage}; + $connector->{dsmrecoverymessage} = $options{config_scom}->{dsmrecoverymessage}; + $connector->{resync_time} = $options{config_scom}->{resync_time}; + $connector->{last_resync_time} = time() - $connector->{resync_time}; + $connector->{centcore_cmd} = + defined($connector->{config}->{centcore_cmd}) && $connector->{config}->{centcore_cmd} ne '' ? $connector->{config}->{centcore_cmd} : '/var/lib/centreon/centcore.cmd'; + + $connector->{scom_session_id} = undef; + + $connector->{dsmclient_bin} = + defined($connector->{config}->{dsmclient_bin}) ? $connector->{config}->{dsmclient_bin} : '/usr/share/centreon/bin/dsmclient.pl'; + + bless $connector, $class; + $connector->set_signal_handlers(); + return $connector; +} + +sub set_signal_handlers { + my $self = shift; + + $SIG{TERM} = \&class_handle_TERM; + $handlers{TERM}->{$self} = sub { $self->handle_TERM() }; + $SIG{HUP} = \&class_handle_HUP; + $handlers{HUP}->{$self} = sub { $self->handle_HUP() }; +} + +sub handle_HUP { + my $self = shift; + $self->{reload} = 0; +} + +sub handle_TERM { + my $self = shift; + $self->{logger}->writeLogInfo("scom $$ Receiving order to stop..."); + $self->{stop} = 1; +} + +sub class_handle_TERM { + foreach (keys %{$handlers{TERM}}) { + &{$handlers{TERM}->{$_}}(); + } +} + +sub class_handle_HUP { + foreach (keys %{$handlers{HUP}}) { + &{$handlers{HUP}->{$_}}(); + } +} + +sub json_encode { + my ($self, %options) = @_; + + my $encoded_arguments; + eval { + $encoded_arguments = JSON::XS->new->utf8->encode($options{argument}); + }; + if ($@) { + $self->{logger}->writeLogError("scom: container $self->{container_id}: scom $options{method} - cannot encode json: $@"); + return 1; + } + + return (0, $encoded_arguments); +} + +sub http_check_error { + my ($self, %options) = @_; + + if ($options{status} == 1) { + $self->{logger}->writeLogError("scom: container $self->{container_id}: scom $options{method} issue"); + return 1; + } + + my $code = $self->{http}->get_code(); + if ($code !~ /^2/) { + $self->{logger}->writeLogError("scom: container $self->{container_id}: scom $options{method} issue - " . $self->{http}->get_message()); + return 1; + } + + return 0; +} + +sub scom_authenticate { + my ($self, %options) = @_; + + my ($status) = $self->{http}->request( + method => 'POST', hostname => '', + full_url => $self->{config_scom}->{url} . '/OperationsManager/authenticate', + credentials => 1, username => $self->{config_scom}->{username}, password => $self->{config_scom}->{password}, ntlmv2 => 1, + query_form_post => '"' . MIME::Base64::encode_base64('Windows') . '"', + header => [ + 'Content-Type: application/json; charset=utf-8', + ], + curl_opt => ['CURLOPT_SSL_VERIFYPEER => 0'], + ); + + return 1 if ($self->http_check_error(status => $status, method => 'authenticate') == 1); + + my $header = $self->{http}->get_header(name => 'Set-Cookie'); + if (defined($header) && $header =~ /SCOMSessionId=([^;]+);/i) { + $connector->{scom_session_id} = $1; + } else { + $self->{logger}->writeLogError("scom: container $self->{container_id}: scom authenticate issue - error retrieving cookie"); + return 1; + } + + return 0; +} + +sub get_realtime_scom_alerts { + my ($self, %options) = @_; + + $self->{scom_realtime_alerts} = {}; + if (!defined($connector->{scom_session_id})) { + return 1 if ($self->scom_authenticate() == 1); + } + + my $arguments = { + 'classId' => '', + 'criteria' => "((ResolutionState <> '255') OR (ResolutionState <> '254'))", + 'displayColumns' => [ + 'id', 'severity', 'resolutionState', 'monitoringobjectdisplayname', 'name', 'age', 'repeatcount', 'lastModified', + ] + }; + my ($status, $encoded_argument) = $self->json_encode(argument => $arguments); + return 1 if ($status == 1); + + ($status, my $response) = $self->{http}->request( + method => 'POST', hostname => '', + full_url => $self->{config_scom}->{url} . '/OperationsManager/data/alert', + query_form_post => $encoded_argument, + header => [ + 'Accept-Type: application/json; charset=utf-8', + 'Content-Type: application/json; charset=utf-8', + 'Cookie: SCOMSessionId=' . $self->{scom_session_id} . ';', + ], + curl_opt => ['CURLOPT_SSL_VERIFYPEER => 0'], + ); + + return 1 if ($self->http_check_error(status => $status, method => 'data/alert') == 1); + + $self->{scom_realtime_alerts} = {}; + print Data::Dumper::Dumper($response); + + return 0; +} + +sub get_realtime_slots { + my ($self, %options) = @_; + + $self->{realtime_slots} = []; + my $request = " + SELECT hosts.instance_id, hosts.name, services.description, services.state, cv.name, cv.value + FROM hosts, services + LEFT JOIN customvariables cv ON services.host_id = cv.host_id AND services.service_id = cv.service_id AND cv.name = '$self->{dsmmacro}' + WHERE hosts.name = '$self->{dsmhost}' AND hosts.host_id = services.host_id AND services.enabled = '1' AND services.description LIKE '$self->{dsmslot}'; + "; + my ($status, $datas) = $self->{class_object}->custom_execute(request => $request, mode => 2); + return 1 if ($status == -1); + foreach (@$datas) { + my ($name, $id) = split('##', $$_[5]); + push @{$self->{realtime_slots}}, { + host_name => $$_[1], + description => $$_[2], + state => $$_[3], + id => $id, + instance_id => $$_[0], + }; + } +} + +sub action_scomresync { + my ($self, %options) = @_; + my ($status, $sth, $resource_configs); + + $self->{logger}->writeLogDebug("scom: container $self->{container_id}: begin resync"); + + $self->get_realtime_slots(); + if (scalar(@{$self->{realtime_slots}}) <= 0) { + $self->{logger}->writeLogError("scom: container $self->{container_id}: cannot find realtime slots"); + return 1; + } + + if ($self->get_realtime_scom_alerts() == 0) { + $self->{logger}->writeLogError("scom: container $self->{container_id}: cannot get scom realtime alerts"); + return 1; + } + + return 0; +} + +sub event { + while (1) { + my $message = centreon::centreond::common::zmq_dealer_read_message(socket => $socket); + + $connector->{logger}->writeLogDebug("scom: class: $message"); + if ($message =~ /^\[(.*?)\]/) { + if ((my $method = $connector->can('action_' . lc($1)))) { + $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; + my ($action, $token) = ($1, $2); + my $data = JSON::XS->new->utf8->decode($3); + while ($method->($connector, token => $token, data => $data)) { + # We block until it's fixed!! + sleep(5); + } + } + } + + last unless (centreon::centreond::common::zmq_still_read(socket => $socket)); + } +} + +sub run { + my ($self, %options) = @_; + + # Database creation. We stay in the loop still there is an error + $self->{db_centstorage} = centreon::misc::db->new( + dsn => $self->{config_db_centstorage}{dsn}, + user => $self->{config_db_centstorage}{username}, + password => $self->{config_db_centstorage}{password}, + force => 2, + logger => $self->{logger} + ); + ##### Load objects ##### + $self->{class_object} = centreon::misc::objects::object->new(logger => $self->{logger}, db_centreon => $self->{db_centstorage}); + $self->{http} = centreon::misc::http::http->new(logger => $self->{logger}); + + # Connect internal + $socket = centreon::centreond::common::connect_com( + zmq_type => 'ZMQ_DEALER', name => 'scom-' . $self->{container_id}, + logger => $self->{logger}, + type => $self->{config_core}{internal_com_type}, + path => $self->{config_core}{internal_com_path} + ); + centreon::centreond::common::zmq_send_message( + socket => $socket, + action => 'SCOMREADY', data => { container_id => $self->{container_id} }, + json_encode => 1 + ); + $self->{poll} = [ + { + socket => $socket, + events => ZMQ_POLLIN, + callback => \&event, + } + ]; + while (1) { + # we try to do all we can + my $rev = zmq_poll($self->{poll}, 5000); + if (defined($rev) && $rev == 0 && $self->{stop} == 1) { + $self->{logger}->writeLogInfo("scom $$ has quit"); + zmq_close($socket); + exit(0); + } + + if (time() - $self->{resync_time} > $self->{last_resync_time}) { + $self->{last_resync_time} = time(); + $self->action_scomresync(); + } + } +} + +1; diff --git a/gorgone/modules/scom/hooks.pm b/gorgone/modules/scom/hooks.pm new file mode 100644 index 00000000000..b474bff0a87 --- /dev/null +++ b/gorgone/modules/scom/hooks.pm @@ -0,0 +1,250 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package modules::scom::hooks; + +use warnings; +use strict; +use centreon::script::centreondcore; +use modules::scom::class; + +my ($config_core, $config); +my $config_db_centstorage; +my $module_id = 'scom'; +my $events = [ + 'SCOMREADY', + 'SCOMRESYNC', +]; + +my $last_containers = {}; # Last values from config ini +my $containers = {}; +my $containers_pid = {}; +my $stop = 0; +my $timer_check = time(); +my $config_check_containers_time; + +sub register { + my (%options) = @_; + + $config = $options{config}; + $config_core = $options{config_core}; + $config_db_centstorage = $options{config_db_centstorage}; + $config_check_containers_time = defined($config->{check_containers_time}) ? $config->{check_containers_time} : 3600; + return ($events, $module_id); +} + +sub init { + my (%options) = @_; + + $last_containers = get_containers(logger => $options{logger}); + foreach my $container_id (keys %$last_containers) { + create_child(container_id => $container_id, logger => $options{logger}); + } +} + +sub routing { + my (%options) = @_; + + my $data; + eval { + $data = JSON->new->utf8->decode($options{data}); + }; + if ($@) { + $options{logger}->writeLogError("Cannot decode json data: $@"); + centreon::centreond::common::add_history( + dbh => $options{dbh}, + code => 200, token => $options{token}, + data => { message => 'scom: cannot decode json' }, + json_encode => 1 + ); + return undef; + } + + if ($options{action} eq 'SCOMREADY') { + $containers->{$data->{container_id}}->{ready} = 1; + return undef; + } + + if (!defined($data->{container_id}) || !defined($last_containers->{$data->{container_id}})) { + centreon::centreond::common::add_history( + dbh => $options{dbh}, + code => 200, token => $options{token}, + data => { message => 'scom: need a valid container id' }, + json_encode => 1 + ); + return undef; + } + + if (centreon::script::centreondcore::waiting_ready(ready => \$containers->{$data->{container_id}}->{ready}) == 0) { + centreon::centreond::common::add_history( + dbh => $options{dbh}, + code => 200, token => $options{token}, + data => { message => 'scom: still no ready' }, + json_encode => 1 + ); + return undef; + } + + centreon::centreond::common::zmq_send_message( + socket => $options{socket}, identity => 'scom-' . $data->{container_id}, + action => $options{action}, data => $options{data}, token => $options{token}, + ); +} + +sub gently { + my (%options) = @_; + + $stop = 1; + foreach my $container_id (keys %$containers) { + $options{logger}->writeLogInfo("scom: Send TERM signal for container '" . $container_id . "'"); + if ($containers->{$container_id}->{running} == 1) { + CORE::kill('TERM', $containers->{$container_id}->{pid}); + } + } +} + +sub kill_internal { + my (%options) = @_; + + foreach (keys %$containers) { + if ($containers->{$_}->{running} == 1) { + $options{logger}->writeLogInfo("scom: Send KILL signal for container '" . $_ . "'"); + CORE::kill('KILL', $containers->{$_}->{pid}); + } + } +} + +sub kill { + my (%options) = @_; + + +} + +sub check { + my (%options) = @_; + + if ($timer_check - time() > $config_check_containers_time) { + sync_container_childs(logger => $options{logger}); + $timer_check = time(); + } + + my $count = 0; + foreach my $pid (keys %{$options{dead_childs}}) { + # Not me + next if (!defined($containers_pid->{$pid})); + + # If someone dead, we recreate + delete $containers->{$containers_pid->{$pid}}; + delete $containers_pid->{$pid}; + delete $options{dead_childs}->{$pid}; + if ($stop == 0) { + # Need to check if we need to recreate (can be a container destruction)!!! + sync_container_childs(logger => $options{logger}); + } + } + + return $count; +} + +# Specific functions +sub get_containers { + my (%options) = @_; + + my $containers = {}; + return $containers if (!defined($config->{containers})); + foreach my $container_id (split /,/, $config->{containers}) { + next if ($container_id eq '' || !defined($config->{$container_id . '_url'})); + + if (!defined($config->{$container_id . '_dsmhost'}) || $config->{$container_id . '_dsmhost'} eq '') { + $options{logger}->writeLogError("scom: cannot load container '" . $container_id . "' - please set dsmhost option"); + next; + } + if (!defined($config->{$container_id . '_dsmslot'}) || $config->{$container_id . '_dsmslot'} eq '') { + $options{logger}->writeLogError("scom: cannot load container '" . $container_id . "' - please set dsmslot option"); + next; + } + + $containers->{$container_id} = { + url => $config->{$container_id . '_url'}, + username => $config->{$container_id . '_username'}, + password => $config->{$container_id . '_password'}, + username => $config->{$container_id . '_username'}, + resync_time => + (defined($config->{$container_id . '_resync_time'}) && $config->{$container_id . '_resync_time'} =~ /(\d+)/) ? + $1 : 300, + dsmhost => $config->{$container_id . '_dsmhost'}, + dsmslot => $config->{$container_id . '_dsmslot'}, + dsmmacro => defined($config->{$container_id . '_dsmmacro'}) ? $config->{$container_id . '_dsmmacro'} : 'ALARM_ID', + dsmalertmessage => defined($config->{$container_id . '_dsmalertmessage'}) ? $config->{$container_id . '_dsmalertmessage'} : '%{monitoringobjectdisplayname} %{name}', + dsmrecoverymessage => defined($config->{$container_id . '_dsmrecoverymessage'}) ? $config->{$container_id . '_dsmrecoverymessage'} : 'slot ok', + }; + } + + return $containers; +} + +sub sync_container_childs { + my (%options) = @_; + + $last_containers = get_containers(logger => $options{logger}); + foreach my $container_id (keys %$last_containers) { + if (!defined($containers->{$container_id})) { + create_child(container_id => $container_id, logger => $options{logger}); + } + } + + # Check if need to delete on containers + foreach my $container_id (keys %$containers) { + next if (defined($last_containers->{$container_id})); + + if ($containers->{$container_id}->{running} == 1) { + $options{logger}->writeLogInfo("scom: Send KILL signal for container '" . $container_id . "'"); + CORE::kill('KILL', $containers->{$container_id}->{pid}); + } + + delete $containers_pid->{ $containers->{$container_id}->{pid} }; + delete $containers->{$container_id}; + } +} + +sub create_child { + my (%options) = @_; + + $options{logger}->writeLogInfo("Create scom for container '" . $options{container_id} . "'"); + my $child_pid = fork(); + if ($child_pid == 0) { + $0 = 'centreond-scom'; + my $module = modules::scom::class->new( + logger => $options{logger}, + config_core => $config_core, + config => $config, + config_db_centstorage => $config_db_centstorage, + config_scom => $last_containers->{$options{container_id}}, + container_id => $options{container_id}, + ); + $module->run(); + exit(0); + } + $options{logger}->writeLogInfo("PID $child_pid scom for container '" . $options{container_id} . "'"); + $containers->{$options{container_id}} = { pid => $child_pid, ready => 0, running => 1 }; + $containers_pid->{$child_pid} = $options{container_id}; +} + +1; diff --git a/gorgone/packages/CryptX-0.064.tar.gz b/gorgone/packages/CryptX-0.064.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..31e49f5a5a75fdcde5e9dd9d6c25ed78c4bbac6a GIT binary patch literal 1619390 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%0DROT2M12jGnd%&0iVRQgJ%oOEW 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 z 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

-BO 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

l#O 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⁡f}n?0yhDPYa#vK%u$pufh#?A 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�)Tqn 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�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�DtEg8jmb@`|%Ts~^L4&2YsLJ3I>eE+d`UrT4s z=^ivW3)%waf zPc{ukmu`b10c)|YCoF=vqj~W%k!{z8SR8SI{?RW`P25)J)&^xeN0w2jf400AnmrTX zH}-s)Zm)!lI>ju=STN}%9O?3RKCnkP16C1zUNfy6mlRSRSCq?{POv?BbI(e8VJWuqbof9-A zGLVQC{R#Vd1+C4m+2aro4G}EkTQw^}^JbD9hm-@O%^+YTWGrYH7J^xAEJ-Y70Fyv$ zzieg`2L;drJLb^NdxmD$MY=mN%6q9&<@H8982>B7yC~^MPwCqy41HNXF2+u~=xC|X z+BkO>NJ`pfXXXtLG)0N%v9q8hxeO<_GdFAUVeY1=G&8>3`^GLRC+GU#;*H9oHSKb$XO%S3iod6Ih0t z0TD@$gEr?iIi0D3j&(>jl1V<|A=S*}KsuQ@?e2EHrYqU~g>h%j!_--Od{=o7OsVq+ ze#e$)EAMwjJ^s`ypW~%mhMep{)dcQLWaf?> z3wCrU;seGk;gBygYYn+y%?x>vlcqGl7+EOtIf#dH5*g)d3Lc-_?);Rh_9?P`jruN*TpD-l7&0tLv=6EcamTEq z

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
xrw;YV6X97SPh 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 literal 0 HcmV?d00001 diff --git a/gorgone/packages/perl-CryptX-0.064-1.el7.x86_64.rpm b/gorgone/packages/perl-CryptX-0.064-1.el7.x86_64.rpm new file mode 100644 index 0000000000000000000000000000000000000000..2e2ab421854ea8ffa5173ce85c85d41a96d474c6 GIT binary patch literal 720852 zcmeFa2YeOP*Y7={6GHDO5PCooPMK4NCJ;jJq4zm6b083skc5u(qS6#Zq=SG|X@V3% zkd9Po(iQ2w2#D18yHC!+5R>S0Klgp^^S_tqV$SS6vuDrVYp?ZNzqONdd+D7sX(fLA zVvN|x;LzB9G4ZW~O(7<`9Bd9TA|0ac-~RYX>62bM{_AUg-ZD|w9I1+AL~0^AcyLyZ z84&TqwP~+$Oox>Bp5Njqt~ViEpY|?CQC|)5(@d(YSWFhxX*k@v&EZhYZnr}-Tb+i* zWp$}G)n*uGhnZ_7$^Yf1&R0U49-Q@P?UJwdjXm(8=|`z>ojlZd`t<76tCf7_8IqK! z-!@5_6GYMb2>qB-fTOrZM=~Me$JcN19%;Pi*Ex#wY~J%5-gEIhalLr2%t#^c`EBoc zC-3jpC>OB{2bDm+W_uQW&*Jo<&JRlY^ z-^i65MSt{!^HHK-;@t3_H}Iajz2}X+=aJs?rrz^-jy!*qC>PJ~m2mzsWuEii9C@E{ zGZMw42g(}57CUM_*f%4O5Df$NYcA& zwc1S@1V+aOR*#L3h>MTVq?$%=BQiRs2bWf8;OnB~du|USUe#jMsF3K`E)`;;^$H1n zlp-Uddi9n1I_;h8vZv+B3|9$03H|v(lCp1MI7f0EjA=Q-XX>ROrfQ!gp)ri*ZV`Tap6)@CxISoiPg7C%!kj4_kAui!ndTg@|BCs}!Z*7L}Z)J02D`h2Vb-Y?x1){hVLQ-kO2T7=XQefiNanN+C6oYLwHLER1m$<~)FL_Q!T+evHts z%+I`nUH8W(i}}s`9C?U5Kz>K)r*B>}KjE{u_mTI=7-hbNh_=K%qV6v5L>mDLQqe2o zb5cQn0*ZPi(0hD`V{RlD!dQ8v3`ZgP5pi7+q!{8W7jq*%i@FL4lIb|d_mFbDQ%1%B z@gM7zRvhW8|43vMBJ`TE5uda29vQ_r97WO~%taROxQI5g#jsd|C8-S0DZ88Yr}Msh zyn;?-X8tm-XFYMugid7Ige*Y5M3&N42HHPS{kHgT=={I_^PTyQf7Se(PycIoCD(FF zExlQu_$=|~q4VE76X&AD^Y0t)o7}ViGs~p6KmXT^0RH;!Ye^fHp+j;au1Q>;^gMCh z|L)KKozMEWegCiaDYffUs!OR{^f9$MU%l@daV)syzE`euyoKEG9>w`hL>xt#ki?&p zI~VsSZX@wEqFl7+Ys+`^mHUoKpON@FUwh&j-*uu*pZLnfQIv@`eIo7^Wr?pzT<3d^ zuU+3!T;prccfYt+l#BD^`jq$?zIMg+Nk99_68F`2jqm7trmsIYFfeY1Y_~fUlgp~x zOorQGcU!DBlWum|6th`3Om0;+sTRB4PC}vTRmm9_26$TC}-Mn%Ph-R+B}y7$%d$th-drFeJB4b;>S_=Cs>* zMV(r+YA7z3(`I#Pc9n}=4yWpLnGJ`-=GJ7FW_1`2r|5`mQ!TP&ahol?pUp-4vToQk z6E9=7xDDOpuo-sQPMOJMC|0-CE!*g~Zegq~R;yXJIxG&IX>b`Xx5=U?c7r-znzxoM zF2!s!TqeWivNF78o9?zbEf&?Vxfmjg&MmUdX>u@kk}5Mcy2C2lRVz=`6(&$tWnFf- zOeULMRoxDg(;>TEvZ1+LCYjN-Y8FE^xoo=2kX1=nRExr+>{hpKwYnT8he;Q0*<7ks zHoNSK!W}NR%fvX?Y??!6M0K-aSIkZ`qXPz2tPG#U>@rXs-6|_Q%E1Wh^xSOM-EJA( zvFV)ilCs^TTbvGYzt!$=G0ZMSV)&ej=&I&+yELc8;WW&O*>07cHj~rL8<}OdtT<&= zLHFF6!|t#cR>N-BWxK5L?vid;c{8i3yH!JWI~3aH7P~_=IaQa_Zgw(gCe_6RSR8KE zZFbW&RkzD-x272ur%jSkD~HqRFl?&b?N$}5;jp{V42NuWXbSw5Mb%9v)Y|5htrmw( zcfw^sXt`zdA0?Gg8BJxvH8Ue&H@lpy3YtWbbjr;x-D))vFT4W7H(=;)(Zsyx?yA(!?M;f|pwOO4^h*f8d86alUq0*M-aGQA%t0dmU!IdVP z&FQw7%yt)TyG@4XR!zEMa@)*S+LRSTF*$9r)9!ZKoy?Av0arEE>XsC{LpGfB%&D_x z4U^Td$r{VWs%kdPtXWt*E_Bdgw^?j%u5qa<3Tm?2%nYZ?sYq57+F^0KomPv}iptxa ziV0h!sZNzUWZh{nVa%$|n<~7EP1T%Mu~N`7U2&Twi|o{$HmnQ_PPGWTWt9!L%n@jWR&=x3;c(Jzi`infI$W|@w^$S=(WR(N2W=}V`mUft zvd!jlxUohGR#A1Kn;1w;jLYoQ?G8=WHB2$%XLH${F2io33e98$6sIdzkQrr8`WUC}hRg3+|054wt;DT>p{lGQCX%_Y{RVo07T#zx5w-Q;#) zx||M$73#z?uuL#>*kNIoEvnh+bXhes*4Kf9!Q^We*F(0@=#iZ+)Ido3d7<7~55PlDn%JZ!X zjhG6wXaDm_q9EpYQ`(oCAkZnyGUVhRkq+Xgt%M2~DX8$OR06R>Z`Y4z_VsF7Nl%p5LI-UQPLzAD*_8{IPpN( zR5!-N>DDmKLWgkLvcZ^(Rt(&QS*#kH4JC5RczCyB($C#lktQnpL)GPF9{`H8bJ3OOt_ZQrxU&%#wsr zP|OCiD&zYZPq)G9#`L4Ss%A3D78L`2r?8cjEvTC;5@x^WnFc9!zGcjWdih$?VRPokseo%HpH3RQa zSL_jZ&@5@1i%#K|Oe_E@%ytka^JD=M0lqDoj86nY;CDe`Cai%9cHti0khiko7!=jU z_~W%OdyK6>_24BP3yfyl@U%9JwHf?lv7tfOE})*(0QF+66}P0J2Ljcx$XL68O`wp? zW_KF~HrXXJR|bH^4A>EX$wF|!t)NaT7S;?PkW^h@32YdP%MHq)8H?3y!MCE-HYebh z?4%u!W@qi;OjuJuR{#q0tQxv55#;C?MXQR6;L$A#xWi-?i_=MbM4yOP3`{eY&SiIL z4m=1J4@bwuSX8SfDZG@6fWZOa#b+3b2vB%yk{Nb4o>PQPW;_*BVdpdOLsmef2Ik0u zzh(?T&5RpC5!PE4Mj0yte!-;^qgXK>Oe6qQRnZB+87s+T;6N}A7$Lhu!V}tEZZqCf zfI~A#$?bCR36qCia-*^ej!j2DSr|+k_5#ac1~m)YhCQ%>l zmz`qtN)yo^=2_D~3M?}|o6MM2+%~IM0@=Bp+)c>fVz}t5%=k0u4iV+4bioWvcW9Ug zgD}(18kGTSgmDB1;BX00Lipvv-T+Ev+yK_aiZNg{foMU5xIwgCI9HdGRm$4I>VS7F zW<|paN-mR)yfkmcoVj=kDBEcvR1Zg1|~F1Qkf3(V9jqX;xILqN{jC!ZQH^hU;CV;R(SPv68!wI5hE;NgRU(xA0 z4>Fid6jO4M+`}>9Fu_znJdCJDaEmp?ZHs7=o(ubG$aI6q!@zX&ZafeagdLHHLUBVD z&5F7LOsxVS0=v)xf!A4cc9t}XWG2u+k3oG5qyyW5)(|%`vytQjIRC?6bUVkz12+5}2& zno*pZ0C2>lBK)=jG>PKe8bAjM!KT1_@FpU(b>S6&)#x;fRu-|JW?(Fw_-X?u4yA#; zV$IM?tC?66l%=yp!iDDrg@Md5-h@&nml=qo6QK*2Y_{U$MT}roiFB~pSR0wBL@Ygr zoiTtt&`b~=!~-B>Yz~nDn2LanR?R4phPD!LVUaLe1VT)P8(#|K0uM+OIK>*M&gUl@<4S3oD05sWoC*YfK!B*X+ za3VmaA{+=5AM}c9XMj`zw?*(`Fvj3P=rG+)%mfjl+3g-G33I@+7%1ilU3ThNE<0fx z)B`TmEa^5D86XC9Lr?`4Caxzea$uo>GX&^1yPasxZoz$+h(Daz9jn!(h(@rC*fd}# zig9+vR zAhH}boFxVh-QlT%-NY0KzKO9l@RK+bXdlUm!hvaN8{h|&0dS)q!t|qHL>~n63eX3K zXT#>}z$^GjOfp?_sBYCP5i6oG;1I$hm<#TMa-$b`5Y{_H6>d#Xl&B)c1Z#m#fmW|7AY;*+Ar-*#US29B zF9~o7$bwD+MPs zFyeLyC4v$HC>$Zrcew3L6XY;ZnNXTH1ajM4k_2-}>`3TA$S!zPC&2?tg}BvhSVWM> z6bW#x5GJ7g1k^T|RYA&Obm3dzMd_-H;Q=F zf+N9iGKIvBXdDp&W&-?+Stkw$=dmiV?^ebJsD_0WlSNRDTJj!%01*=cF@Z#^Xw)9i zuHcb)O*a4>rN=-BAC0R3P7_zM4DodiXaE^kW?@+}lSFcaJvs`B$%nNNtDQATENf+B zAt4|yfN25hJo~g$XAdRG1IqW(+(pOH8W>HweTgKE?A`g`ZUf zQqsURF1uv5&=>Rr?2nTXS^<#f4MEz1B4k8R0hS0V0MSTjXd#j@TQwpGL2dw32m(9- zyN)`FgbcP7;3*6W;Sa>FXLNW0Mg_%yW;79x*l~d1T2*mM4xlX9kwwk&fl`vOZO~{i z1XykpkdY)1Q%qBYQdS!|7r`P!&bR=c8l0Xmf-s*%th^}PEup#@N`c@ImRNvJOa?d% zcPw%NhQcU$0KbSo-8e9b2oBoE%#sor3jDhXGQ!2k3dCW-=fN$pvWe1cI3Sq_45hLP zI)%4{hJb~@=33k~s|}4N(!pSW_^D&1q)J4Jbr#p9EECR>6#cK?9*Z7=Md|Te6Alg~f@| z;B5)LV0pn3glrZjNaW*~bR2;b3RldeT|*h5zRY6i4N2t=`pcNo18^y!EnF3mF;H1F z34KeLD60$r@LX06m8<|{8BpIvjz@uwAy9z^lbI(Ge)C{r3IZ(GvJp(H5-JKbW+C$; zktA?pHZW6UKWr+gNys7QgBS&d6-2^vVk`);MHYgF0pXy20HG7hP0q_ACIudf5Ec7D zrpP4tA+tkJXizMmUa`X5o9J&S~$057$0)pKp8AyP^SO^kQWamVr2G`^e&l33u(xP}t z=t+sUL}`RUzyrI@qvE?!`HdMJt_t?;792h*B1omgB1&IW* zfk9Adob~{%Ko=*55tIi?Aoj&^%MLr3S%Hy)St2D0LjW^L7(>2^p$3XT$N`N(QiA?* zqdjECHG?S@xYq>86C6Aa24`XtYz}Bopdbu^P&tzguV!Zw;Bei%xkxM$JBmC3?TJtu zV2chDFYp$Smx6JY$XBt37-OPvk%tqmgym?3juq(%&;}M)%7(HReK>9KbA=7&=577&~}Us6z}UsbBH}1YV$Dq5(ED zKtTeHME0E+6}U;#NtPwE;2r5U(GnD|NW_ag0gSN7b+c+%W~AO!Gck-n*61K^lt2O7 zE^w;>WENf&y&#Q^4QDVRH0UmTr%T2Li~I{(hUPGDU^1|&h!Nn+fofL4iLqyaxEnP^ zy#Yl;Q3i;a$PR2_aWUsCWtyjb*0+<=CuBMGr*0Ke!FQ;snr zLl0Qy2RaG@)CDKv37ya(m~i4R+ya3DNDbW)xfTsfh{Yvjal*xb39>L?~f~NeAnRoE}~Z2Zw1? zSlR@qs@TvXHm)dG2`FEYEY=*nr-+KsMD&=Ln5>V(BFO?n3ad!6g>YVkQ9wpC0i!Rh zot-5?yg>F1+L!nVL;2|F9R}+0UQoWUt}?8(NInrq5M}@ki7^CdLLW$o;(1(v zS6&=UXV*n+4o#{_Q1h&2ounfB3^1fDaufvu&JAsg&n7L1X$Sw)JqPo~ivr=G3BgN( zwU8KR%!6>DWK;p)U?*}`+z#r|;Dntl9~KZ1IM9%Q4%*5N;A3@KF$juPB4!bpVfanJ z99$z$1hf&tu{VU!5;Z0ugae24!qN%+MjV0R08EJ;2)ab11c#2@AdrWm5D8o|(@Zli z0`eU`+#*O#m|_v?!I9z6L@tac5{HoY6Ql^+L{Jcr4~QBqhgT;p#I6XaCKfUfn8*QO zPrydv1LlV7SeE=?O(j?`vl})Ky%)&{T#|{UM)*i*fpuf@uvL&wEGdE7T-XEfwAgY% zh=8_8#F@I--oiV=u|dt6z++m0TiYB*Ai_y$m!~ z7InhTLAeR9$8-ta1g0F_g2H35!+>MvVG+zkKNiRpOs~WYz(NravKZlt0BCL-X3Yel zhr+?S;lQXQ3%lHgFd*q&XxRU=3#lr-t=lyhR+w9yRuI;qEY;7-BN4@M2`f z;Df{sFaw})Y>CL|VND6#@L^~@y&0gc26$BoK~p=Y$}rF)&~3(7}G?xKP4%|rr#LrEl0 z1n|IEq17-zy4eW{4B%q{l68|2JM0btD?yytqe5&YB0zi>EV)~vyXY#R6&(|t9~m-u z73`c{1S@R(!Q+v>bP)-PNS2fqX@3|&*l!>%Bm%4l`|*f*Fh+vR6FY`;$5*RkHv(y!gGiK7ejADztLFyG`pHm9sDGrJIk8Hw^-g-LBWi$h#i^ zAu9wzV22R7H-5>{32ekP%UDOU$aE8qiua_JVE`6DYB?bbi6^n0k_~SQGa3_7jQ^Zi~brYu$m1rbD_Uatan)FeX#%FM>BGW614*db|^_ z>t@3ep&dv8)nZ`~8H#K;)`g%C-)M&ja1m62ieU8EjYo#V!lcd!p8IN5Ge*o3|M_A1_?$J)`h)n1jT3&2GX-J3hfb_Q86O|xe@_` zQoyq!iNF#eB>)(MIg#xW%8e}mkXu2l@aBvs9vDn7cIg1z#6BTltS4EBS4CGq%4C9| zK~ds_3^rs77X#gi=PhiAbl`@~;6PXz(rsiyG4HH%p~!@}SZTvWkj9=g3_c(W`Ui&q zjQ~~zSF_;Q|3Z)rp)5Ae;R^wzIDY6#o*_&o7Lu?O&JOcM4jngZC9Y*h6m+P@_7d`W8jKz}L7ccCJ&5w)xNy#>7Oob5K->zg zgF$1z68bN493t{y;{!Yw#U{4xGC8b8xOoC@)+78j_W;Sn_8zg@7nF%Sg<0d z#g;)li)Qf6?77As;iwn^K>@P!O_(;e9nb`u@^l+pqs$Pw3?SKe72a`oo$qh*bwFyy#>ai7Da6!ZqEIv>L941@4p;8$r_9vs`f&vD> ziDWJtrm=|*79L3n!4ZJMAV0{#iG8E&{Snt z$1p%RC^VZ@LPJHcg6Sj>WigOrglxvf3jhn5j1iGA_IMUj9IP5F3d127 zI}4tiSd;WKp*A*%EUDOs$F3<>4Pi4L2h2hmS(Wg%&?SI0k|ZK<0^tFFAhQ`{8v_Mp z0`HFDMs3CK4-gl#H$Z@C52s4_kEdsIDx=IE0TSsRMIm(p`WESP_Pc?O2;ATh1?m$d zvks6JTODB~44IrA=nL|ab&l?{V}rF$5H2!L0A|6w5kvsiF{PLV{02lhx&@ZP;FFqR zlO7=w5f?Y&+f6ZPM(nr|F$XWe)^f3}mS1RagW3tFMUELZ zlI^&710Xj*1GWyRY-JW;JlNX>pAAG4w%r7C!xlTt3{)O8Fa#PK3V1cbMv{qy2WY4# zmS8Nw2N*@Mo1OhBc1Uuu?b-q!)j>zN34&&R?F3W>m;iVIBp`~hD=;>s8^j{SJF}IM zO>6LLxCcU9esuwlB*Gwm^@PA5OC{3`%1eHmnPY>Z2(9o(1VG}~0Ej@uwhX7GkK zu{R54CT3yx3egBA865#3K!@1b;19Yb3rLto+EnZem#`;n6CwbIDnuF42rMQSV{kZv zhzO1F{A^$V-V5K0oLIf6j>y}R{}732C^VFS zyTz^-vBjSKR%j?-TdZM-Oaf~ZLhNn=mxweOYX&Nw&a(he1hJumCC_~VuhT1$>>`D% zNg@{j_y$d~?SU-`7JxS!KtXX%)(naRY$bjZdw|)>2$X{D0pXH0z~-|{NBl+#UYlSR zAPa@WD8v8Y#6k0L6J!Vk?*K+3smKVE8^RsHy_*0tKoO!gwhai19DV{T0Q1i7e6Ty= zH~5@{2m9}Vt!z2MR_A}4 zE%Ps+D|TNY%SnVPfRD)Ti98lNC2*Hq$zZZ)mVJio6d*1k6DBebqyfaHW_}NfsUSt_ z;VIxV1Q-HD6O2Nv65fk#8;l8vp3oOCKt2MHPBcg@FH1re8^ikKhusgf2>k{wAOS1V z$?QTPfzDnL&+f7RYkmv%?Tfq}f!8Cz-$3yEO`^BQ|KZ~h8yy`lz5VCy7`)Ykx69zI zDR^rM-&(@Amhk@(OPJ`d0Ve+IqakszT1ae64~f6c`S$1S2)rGE|6wB_)@_jIFIQHq zSiM(#*RUw9tdvY)#fqV!4O6P=s)lxj8)_0A*-vcPOyRPy>aA0#shwiejYDg^c~59p zL+c*bt4H#syn`NT#9A#WRfjeU{aa;3Ojjc|$qk+|S_=zLp(3i6Dkf;yV3plHTkC z7o^_FxK8|MVOWcm1BqgmZ1VGV1hIL6-UltwtAI>q5js3!51{P2g@scufOCdHBEt`r|gU5aBW z>QYvPgxZv*%Tt?r%PCG4_x?j&s=eagPN}_b{Uk=+H?hrG-m&biPkw+~b&ZIPjN$KY zzgb=1=vUK-jp(h$M!0pMuwh;>}hP)g*C$o|;-pGMCqBSUohE;>O7rH%Xz` zh>XzGB(BBt%;aH*r>b>mjigVGh)epe(YleOca(s4{&s6ZajhhXq+vpF^Jd945|$Ld zBwXD%X(LVQzj=3)`V}kItdmSxcoJ_eE({Az@{*#k+25{g)~I%p1}Wh^z345ux?0h( z@x7vw0Fox2+nR+YF-Hl-;n6NsGpX-QsB5Ff#zyyf^Eu)C6?V0UuNTSJ{q0e{o0{>P zY7z0t-xRJ!#(_yws7$t(zGlOt*3{}kQhVrMbqO$3jg5J8HvfBKfdr%b=94ilNeYrREB{p^ z^1o~?k}PeXu7>rEVFAAmZd!*oi00QpV!d4#-=yIE7#ANKq4^e4A2l{ABB~4K9pVN> z2?~ww5ff{~@x2SRA|j1OL@9sOiIRj7`m3yo+Aop^iK@6dYHy=Stl^I6OLOs&dQj~~ zEjrby9*%AEw(h;UZd~jBVyUF~V+}Ed@SQf29AXbKd5+>=$rfVqoS4la;;(w9{!I^o z>?Pwb$?+?G)`aJ2dEo~G;^`5q4OeLE80Um@{57M}0w$jdPw!uMB51(1S>`2HZi>q6o?O{BsI z-)SN7T^LdzQVc1Mlt4-%r4YUgLn?#tT^EwDROJx9-@@}fS9~9Z6oT-b6rS(7!Z1nv z9SBL785yx5{M{r;{6PTz=8VMOK$iGk4yh7S8R0uhJl|pUKF4ZEb)*Ioiqu5HkXlG> zqz+OSsfW}@8Xyf3zS~4V{7aU)%ju1EyZ4e5?VB0Z2OBpTuSIi#LQEE0#rBfXH`NFSsx(hup63_u1V zA0UGez5_%Wf(%85A;Xao$Vg-qG8*|1`3U(K8H4bhBGNcyJTd|K1o;%1h)hC0Lnb4i zBU6wskg3QtWI8efnTgCoW+QWuxyYBuJY+ty0Qm}8h%7=DBTJB_$TH+>WI3_|S&6Jd zRwLgaYml|bx5zqVJ+cAWh~ z06B;pLJlK8AxDs-$T8$Nasv4oIf<}tIteXV)2#xQ=U{rU6vF?%%zY3B&+vx zHjdem9B<`6V+?v5QF?T2C&4ZyeBq?z&4BxkA#7YB?d>@v$#IJ^658TG_F5cBRVQ4S zkf4@)?35_`gBo?2^{CW=} z0W3a>bgwujBzHZZJgHpqDIswyK6%r$;v-4fgoCGX60YJbA-($glP8&)=*%wvb>c~s zCO-96k_7dXCZs$)AH4}q@!6Zs6dy(MQXCUVB=12KB+64JJ|$n0_>{y=;!`pzf1P^r zi*KCTO^HuQL%ngD(6T2lD9T@F0TZ1hT=F`@_g9Vg!DR158M(TIL&B)~@^GT;b=K{# z6Ynd%&ZvnRGD~Lf(c4W*ycdd$Rl;kFPa?zQy^_xf9rTnZzQ)@<%HQ~u=w8Yb-Cf_K zO};#nX#RCpNu0cqOA-YTqC&(+@4Wr>F;0A}rHFq;CP*Ab>PH+!uE%psNbYz(dN+}X z&mw&zjtyZ~Jx5Y4lE|Ql6K`E;t%POQyqPG3K@|t`E8@_kKL3jkHS2i(4fkjo=@M~9 zg2ZzT+^Hn;6XL{|3lX2axew2!p$Q}G%^HZJgj|OBl#n&>-kRv@gye+hiiA{z=VPKK zUZ)vEg=Y(n$DD`+gOre8kVr3xO0`#fwD$_%g>l}3*SmH?tT8=8y7W(|e_aX#jde$58teMZ4r|%e!Y1oh1+-+kL^ryx5tboy~Vp9g$#d6H3?QW zxwgpecz5umaIa?zPfB$@^PGIk!eXab<1ymN*Lv)F@>O0_pF(AV^-sRi>kyK!^!bS7 zYlYiLsm7LKjXi}LufIvYK~H6Z3rh6@98YTX363iDD^hMkxU&@Q^?JAzYJE;G`D&jZ zOup9R8sDr+@RCWol2G_MVJ|k#{KFN(O(fUwUu_8Ik@^+Fk)(D-g7^K0EB>LUp8Y-l z@RXF_CBc&?--ExLd#d#*j?-TdAhr8b9wPx1lD|`6hm>lPE-l~8dqIW8nZliafsW)` zPN+|LB>#dY$=?sIApQ1+1M+o^DgjVZu1s-=1&B#e3q_>|y`|vI?3MS+ZL2; zr58)T*`9ARPC{3b+~=$O`{Q2Mc(w+maGQ5;P)fBvjGlal{(|j^>t7?}zwHn5J*7gg zxY!D8r?ATv1mj?eV;6OO(hPzy0}tX9NW2{`TiTa|C=jk)%iQHi^rUo+qyR-~IW&^I8A4@Bh_4 zrFMNvbt#pLKBiXZtM^@#aCJg1GHJf@?fKtxxc?)@M32A$=CW^iZlY_ zqI<<^Mqs?rH(m;BP%98`%UjCOV7#`1v)8`sMR_BLYUKmmc~z@YFp z;VsLS57hfbsXh2TjL67-f%xd|hF(6fMz4rSy>88RG-B>hg)bW5FPZUI!T8J9ZvNIK zUoaxRu#i7a$zNO)e;$oL$jmo0807;25)m;mhAsgp-0r~W*g$p}gEKU?8gmj671N8p zR1XiWTi4U+z@RSuBVq!z*SnDebv0fsALx#a?h)7`Dxz=sz#eMf*Y%#?a`EYJ;-i=& zkpmXF+Pug+i1;an1R*AI!jBWFg4952BMp$INK2$0(it(3Zb&pD_PL3DZDJqWaO6Wo z>`N2-(8RtovCnKS@)aWXjfs6?Vqci}5qrV5B4U4+*vBRIYl(eX7m*vtedG!93R6mc z-jAHKUmk?M`xQsZBJ|tOfmB7p5c=yEjtV8Kb+qzWC5~_ z=kVjl{xv`LtNF1v&2Kxh7deE`H$V1``CUM+BDcNQKjrfaOobo9euXrQaT<{u=O;~0 zgt1Oj7%7cZ@Rl>iY3Oen`kRLSrlG%S=&$%mL!Z+aNCeUY>5240#29;j(ulG4eHLT> z_UAuy1k%!N^!jfi-bU>I@a^dk=jhgdi->25K8ff5pAzw|zGsWRh#ygw+*lzgP~tzNY0dneyL z;u%?x#O;Xlq-6oglx0n(EL$>V*^?>DkxW_6WXf_SQ%^EPEi05v**nRUy_-x~;bh8+BvV#2nX)q5|sJ$AhCXx^p=ac z7UhX`qEsq%rBkUZlS*AsDs^R3sVkRC-FvCjl~1KEIF-7PRO%|EQfEq~&YVh}C6zjB zDs^%yb+%OM?5Wf_QmJ#MQm3R+S22~kN~zRUPNl9&Ds@#;se3<_x@xJ^RZpd^Mk;lo z-a28Md^WgdD%XdlQdcWcTZz|k?L_6iXVjrg*z2V0x^4>P^-?IWpF(+q6v`W>P~Iqo z^2RBYH%XzqDdobJ3A-%pmhd&gw+LS%e24H0K40k@i)NJhK8N#J-0M4Up5(kmlJk~H z&RZopZ_T;rkFUKp-gD76;Tx0orEQXW;oFj4)9znf)Bayv)4_X9()K%2o_L%R+xx${PZN}Mv`-PlJhP}&buZ#kKkO)kMQ}vXLR$Hy?!p|0{;mt zCh(rXb^@;nEGIBxk?C=jFUG|`4{B1i@V5QeR{CPJ_o>eK>eX|>Z{&F4=---v-v7e0 zfVpX;Vt%hiqb)2z*}U0As_e(%G{hcd3bb0O>d_XDaF+<$#asdt9|*=p4{^GshJRNQBz zb~z?YiaGvhe~k_4>JFDnhMfJ@?^Map2cF84IIPcOcG*YZv# z$LinPoX+;;_Ko@0cRIEwW3OSqEO@#kFlb|%xf3Qg{eDdRgzshy+j#0#_&1$PeR6Bi z@G*-ze6sw9IgJZdxMc}>QZ1yw+3jo6+Sl!hf4bK`;o-vvC)XdJynJ!^lb!oVSIcs1 z?20wbN_Y8W#`6bhgZ1yarmZqN_nz1X*7m!8_0Ja`aR2ltriii^KF;)6t$^9pK6)^= z=h#0_58n94?|FXjdZ)+t@4sw*Wu5KIoE2IG<-h*Kug&f8mEXJ4_D0huQ!k`hKQvE| z0pW$S-yPeq=8n@(@~mjrCpswI-R(Dz-DaEQ_I&rX;$WTT)XQ}|BUD{{JDSod-+d{e&^4F8B5#ZbG_HB|9ACXeU|o2)eGNV z-nVS&$AwxqdA@1j?JwIu^&j@2 z9$cUNbM3}whIalX;=+-UEl+*+b;Iwo)=0bi!>)T;ZCP?lZoYXz+FTv{w|?C9-n>&! zUbg#Uc)LG;du8drxcuEKUsy6$p0p+Id?xuoo5yp$PuHjM!p7S!t!TG);qcj|?ce0= zdm(sFpF`t5kElBGRoIKEQyxF4F=PJ_ZEKb^_w`HBFGGi~_#pO|O-jMO+0Im)v~&2p z`c;R_dXV;m`CYfHAKv*vk;D6@^q6h=!QQ;et#H zHl=USXWR5z4c439&$w~Sgb&SQvd*}9_s*5H%D|6WmAX;2*`SFxJDWbV{YC>7wfEdMK2g*@*nZ!l)k-Yxu0j0yW8;Od{v(uU#aNEcWB7d7*L`;ElM*v0 z4hUJi`eugRt#VadlI`xkwilY+yIXMhH%yoHW*04X-h$QFersdgeR1(* zjZ&-j?O9%Vd*96USFG65F{tO%8_$o#m#@5Y=97=}&uX52>33(!Wf=V2IkUw2$2pb3 z^_K23*BkrijL+vjA9^w5YNIPz*0$_aY-hLk2c8&wtx6ip=buiWI(YIIp=+i#EqZcs z?)+iDy#JI{%p#r;8V*cy?WX8=B3tizIeaHh8jC6 z4zAg<*6w{@P2W;3qC)V7>z6|>4_~%JD<@ajVA}ohfSTDW z44V|6r@-f#KJ0p;>h_JB)_xSwg zz>LDXbN{-u(U>Dg%M_bEbLhKg_HK^-WzJBgPv@FhH$T)q?eg`xOh%tHE1z{=oo3$H zFRvb$vMr=^=jV^c&9xWU?YDTcbfv(ci<1@}TwG~x|CY6j1Xq2sdc^a1rOiU6SU|@t zJ93x0)biE18nxp;kGiq{#-cmVA|f)ij_KbaSJVA-^9Q}#?B~;GHg4G-vor6(wLxJ$ z8{M?|Z!Y*%@vQ}Zd6>T1ll6Sr--++;FStKq)|OXimGjf0Yh)f>qH*mjO&`tQ*fKhI zr>l-VTVA$ZJ)ljw{ZCANrDCi4)DGEF;q1hRzrC}m(#gw{_7*xgfeZ11KGt(G;}9XZVD*Y|k#`H^`}E$tt^ z=Z*Qc)UrAzC0*T{&rU|3>G0%My8?GlT>3Rle3rf6nr|I=*ll9ThxfA;|0ws3=ll0O z9?+)B#bZ0Q-udq4SMGdn$zLYt%efD#_bOHG$l9G*r3=MN7wpnwYpc*_?E}`lKl<_B z&0l|zee;Jee?B?C*t&T8fI(%CbZC^mrm2KleO$JFmKRw|+&mXHEYGoyEstcWY+GNl zxw~yqzkH^tW1pYiJf&>Fm|xZvojPS$7XO_WGuJCy^|R@>>;D|}s7t-*v1MntUkyIZtUhp)FTs-&EUO8ueR;6Qxv?B_Z9}s!$Zu|G8N*xW$d#lD;%;$2Z zD?9I2x?K$mjGNQpRoOAUCrvAvVRw+b$)`WpE?uzHlflQ-NEhl_guT3 z-BogGzhmcf!%NelUMMy3)$7nZpL3ET^|D zzVKHq^SBd#-U^yuX0_I^>4Vmlntk_p=jJazig;f6M26z)XP)`C^`oARGhQ_&c3HHw z+ug8YmC9`?cXWQ*;kB}@9nfLam7C)yH!9XYcgQ)v@S{@mRnKnMn|E0I{`|&6a-YMl z=VOm`PuK8xm-8<>W&d^V)SZjnHCDz)EGbju_j`l4M)hiu_raQa>sDtwalA*GRTFck zU-j*b<+tao|0=9Qj#(RT&Az&7ey=klvmWXZ#3GQDQO zSD8buO4lmKU3mY4%v&cNY};(~sGrAekLX*j%Fq$hhRo=HJm1sV9UfJUd#~{Q7N#%9 ze0BKR>3#3{$uBxoue0&?eb=ucoSx9!ZjrP_(|`Cbklbo-rMo#XFMyx=;2FoU^| zzjbnr`~7ldJlX8sQt!0Qx9Rw|Ul(}UKITv-$Enz|XD1EMH?jH{yU{7w$lUdt+dt2I zP@?=F1*&hYmB&`n=%Ae$_~Ejht*Y;v@S;JR7G?Hy88PE{jvZ^Rd@a4~o1<>f!gGt8 zj-228+TOK;P1iGw+STyO&zpZUYQ)vr?^ND=A#(NnjK_yw9@eNw+NHJ;k(nlydbnz6 zsmH^MobL8fp~3IkLica$8j!WrCyNhGc23gUWOTdTVi zbzT|qYJ1j6>&NKVv(-5@rpY&xhpyOPZrg?V6K*b^@>Soh{i0Cp|CKtWec{bLz!hJ746JwZGOL znX6VvjwTBhRd{xKO!@^4+y|z7>I%#oUcbcLMTLGpboHIP%NC3a3iy6=)jN8UXGo(s!e_RbR&;Q+ zRc%@h?DYP!ylDg0Exh`?$e7+=ES?fvdd`e`TXUvc_vidkhr)`+UNO!r+}m!%`D_i$ z@y`a-X|ObF`RnSebMr2r**iDx__nbPdQK~R;n=Mo)Fo?rZ!X>9ML-wF#&a{8HC(=_ zdes$e>g-!`V_D16xgGHr>i_XY(f96l{i4RtnQxuw);HI+^LM2Y*H_k`H|p4y&U1S< z-sYn5w#@vQrdp65}|g-X7wN674N=fPorVG23`n$KGJ{mPrse0|MOLC z?92L7w~w1w^`{LNKi^j_I=_Epw^85bX>vMv&CtjVABL4%k!{ktpZo3oruOxlnZ950 zwbDH#U+=b6gQl;2F}G3RlMm)(IXC+8#d~R2-zeVr{*j>S!LwHW(DYectIV4oSzC<`3vPf=~bfJ>1o@B+}#`;6mieL(fukHHpQ=)buVzhhC0z*{BIO# zc4PH~i8DLp9e;VD^VGM)K3{u0o#cA8Xyv!#%3Q1(()0Mu>OJe%SuuO|j5%@k7R_#s zn}2$KD_5@!Hb>`TGm3BeT`#gLpy!|Ick8FN6*(~Q`zxKk$+@NO#ucC3*ctnD|GDx# zMtq}8|9X<8dXIgvb3V+!_j$`Thr4F@sonkiB|rP1K#zHyC;j%5|A&tUj%(U=Yw66j zl<1Piy4>@p{Tf%efV1=N6+Z?KpRi+9nu9wR>7#y$|G9?N?z8qi4m9X{JomH1HS!KU zbH2r~@8><7mc5XXZQaZFKDijspm&ME*(cR$bU$so4J}5E`Z4#N5)0ma?@__LtMbKn zom4hpz0|(vvt7>`Pj6H}ZJj=@YsDLl+U0&MIcq-boN>*z%lkh6QEGEyQ^cKVWdl05 z+;F($W4{-TXAY=v;k_SMpICAu!_D9p5hqUOh+ols=AFeY`rW9O*R*uZA0s~9a%xan z>HLj>n`>5H)9$Mrcdj&EeYxYr7gqw)Jh5C|agMW6k0la>$TvX?s8_q#D0-EH^x{Ip){ zGaqH?QSkU5ua+FnwWw&i&`M>NW)B&ebL7d-a$M{lJhofV$u-pqH~4u>OwNqAw*LA_ znm;ek{L1ZD>BfWI+k36Ae&@sGMMK*atG&0(vsse|=E|_J!zbU3-+1=q(F;Q|T-&a0 zslIO3)nTLNtSo0f8#3a#{nFBBgPXlu@Ys>9-S2-fea?0F=Q+|moa0w3#15&&Mb$)yy`#YH$HGi*Z&(I^E z7hThT|EsWVl`h?U>5%2MbC>u&EO0`HravFrV7a2@2|oFWrI=rwxzDg=MPBu~5?6luqL2yOAJu&j zJn-P{W|xYmdq0CV?D2cn=d(wz7}&B-P}hl(XAgACUNYC0lMa5qx%c??PxZ+ox<8s( zS=%#wz~>j+`ro=U=S;nv-6kIh3TQ6pd9boevu#r=?Je>2ooad79m@RZZvS0}i;b^Z zwb|F#v*j&(Xmc;fGt@upGJ>b~mQTCexZBXD?mZ z7qI?X+j$ckn1Y^P4rUZtosC??{KGl?FWRUAlhl z=miI6=RDZ{&qB?*Uw#tyZ2I0#ArYGRX+3PT*?oFl|Nh#8 zAG&wj-Me1X*{9{3Pun=M2Cs5_{LuB_@XVS$D$n_8@0te1^0cqHpkv{s-JYhY^z}pS zmKIa9b@Zh%3s<{>zCLxK{84v@+i8d2d$~Md|A7xa8<^Yt_2J*GGkQKq|8m(6QwBd7 z_1n-Z`VV)y@2yqz*NdkwT4$Dtd}r7%#s8?;^6|CUqt{v=+2fvJXe(OlRsX|N6(AprTU>e0)CLx0BCS zxMwftm#y)Rz#{KoJXSLDg8`pDeSqg&dtkzaNnbw5SMETGUyjasaeB_BYTtBOaN}tC z6T52F&JMdhb=$93KATnf zOGlv6bS$WOA zb@bQcicWApm~^&fqlgqqEZ^ON;)hk~PB~tRX-9dEo;8q z-uu|(KfB~w8$S7Q?Z$_#ihtoQ@!C5h3xC|QU7s1FdjEK?>g=^=OHZsAxix)*ear8) z4_&bP{=0X#hR+-|XxonTHsVesNdNY0EP%nP0EBrTsS_ zEh+Qh)$yfguU5AG8W??LWre$YruV8as732`X9D(p_RujfU|afPf#ZM3{qvq`HI7c| z^6586r|dp@<@=!vZjGEfc-p~>dFOY@c75VE-`wg{pz`QnPA;ij-oB*x*~hb*^xHgh zP5GX=ubC>V(@(|rZuF>b-8Jc}?ya%CVT(=v4Mw(j95p4nPra^9HfBse(f+$Wq4kQh zE5ld#`RAI}y4#m?;;;8@RHt0vz1ht(Z!g>Gvr6xF+R$d4{CrxrUvn<{ZG4@%kL3@G zZ207(d|e7WtI_MrV@IrcdiQ&Jd*Z~prH9x4;&z*=vsdY#$GtoEqgGv(e|vo7vN6{_ zoAK`Xa}}qY7`!I<)SVWihm`w1yzrAC9BQx+eH$mge2oS&h{bE);sX^p5l7<*Re5_Q-#A|DnTvNUY2IspuD-|bzh5rz zyJwyI{A0e#e`#yPANhYVooh1uOpRRw>VI12T5D65!5RA2_%U7j*}o2cukeGhfp?r2 zIyS17X;0aK<2M=$DC$iL)VMQNu-9;r!YnQ3 zV$BLikkI7^NN^)7d6BrtbS~&q}pWsT|YO?wMr%utlw?Y}2JK?i`l~SN(dc^#1`RK-#}t zQxA)DIj$b}CGM2Gb9Mu0I(E-YesK1M0gQcst47O6FxgPXwKElAgJg=$?NZ!aq9h);j4fy-pl| zQm!ZaIOe8f&1KdDYq|;~GBv8HS(=fly2h2j`&_r;&Y~eYuchbgk9P9cvj6Hh1+wx|RLpLhxBut+Mcf@w$^v=>B!a&R)R_BPN_Z zsHuk+mzA%^h8@v~)_t_UvnpTf^(I*_A`>{mpTV-Cyx=s4D00Vv&v7sNo`h#MXKann zE!Qz$LUkbqlu%j`I-E|Oa~WZB_%L=Z;{PUt`=bpte0^!{**kg^*G*5@>({T5ifo#kpWT)1UDl zHm?;jtHzniAgRswSi9sb139YWPIXKaJ$di_#|nncc4tCI*_(kk)YM{5WoMfZnnV23Cv`SrEBd9Q+dN5D{pY~ z#L-^MnuyyCLZvs%Txui>j3Pw=T2{+NH_2R<+UZnRhlGR-)IWA(`P1AT6$688L%7(x zm_bruy|`No+(@}A8v|lindfy<;i#vH zSSZrIId+WHHhDD61%(KI&QoRbw2^9oL(~H+qn;8Z7tnbhOof_yWSb~qS9ni6g?u?Y zNR(u03q6K5M6n2+M{DIYT5@?=SLUsAhuuRF(>vyel8ukJ2k`Vl83hwKz$P~ZgD)1- z-dox8sLB}RzI+5>Db)1QwrB$^!EpHWfS*C-MeE?nRDUz z9ECI`kyajtg;l2`e);Sx{Sua6Ew=|{&eRftw&|%ESiJDwG$UcH^ugd5lHja^RbVG~ z!B}JtC1%q9^Jgg*Z|pFo*Y}-^l5D;%EJPzohe64Ba#TyQlyphoW;$kcMm2Sp0~Hu7!RceB?c*wXeRgI8|~EaxK73$Vd*G z+KbxU#FsyCe9nzJ0eVI_KXuGtuKKo( zm6fr{>u1gw8Ck1xscTzIj8?yrh!o+-)FJvQk4n4iZhS{;vESL2=tW~~#~wI5P5I5h z0eFm$7|hG{4B+CG1{|36@H+|tL*7>^Aa z?9#fe$DME*6j=%+r+5m%!Up_3T0;!5Usdxj-xqzgRtMj*$6z!V1j>#Th6p6>VjF z8X=(yNn=UrIxm+D!!nXnG2h##BP;j`*3pBxm%ToGXB6Tw*M`7BcWjfC%MflD5k1vMH=U1ZWr}Bb zZO_I$!I_ab^{xt-#@6IZwR~h!PZ=l#l0tJzPr>}RY||$o<$L6Xt3qoKcq`)ud^s?( zDmZNWktE!H+J+XxL2vzJ{i2Ufo^rgdA65cIO1%j);%WB~!!GqDJvevKWW~FBnVzB% z&RqOcy#tJ>t7Qs-z{@O}i{pjBxK4ivv`#Ib&l-T54hY*}w_)o6$=x+jfL#9I%JLU2 zOuH@w+*D|d|ADJ8R>{Qvp`lJu_^`o^ZE1*W&W>?7WT1(fV1Acf%+E3(Iy7ijYWY`2 zXA*#LI5}s$eyO4rXAl2|H4U#^W2AKvR;uTYyDca$f!sL(OEN^+3euEqivI_+)4N{D zU{2R0Yf2MFawL*OgGiACyP~qm37#7E_W1?12=9taMBo`oaLDC#(f{lPm5N>g7TWCC zIy1P%@S-)-a{wY_W!&vOidVRZTL0hI-fz%PSS80a0Tz5l_0(}YHx4NQR8aOSWr9r$ z-zp`x-)+lQVtgb$uh*>@Yf0hw-K>>Jzv>Wx=yez0mZ(qRLFAWw45qa~0pi9C1%68e z)Kg64MXYgqG?aTR$rA&(g7pyjZ)$8jv7>4j`Wm!TENegrR)-+wc}xQ0nsV`U-^dI( z`(|IEhyPWfGQUqM3?0$5cYCZhebPUX8RZ0pDM$l}pIh|S?zW$d zJ5ug^&1|qc5Qw}Q=h43aXvFyz6l)yM_!>hlR$mROKuBEj-02iN$PV(-&c%Z`f+G{O ze`z@!X5Q38A3lqcez+grS?ShTdo70EN2XguQ8Mt;-xMgZftr%`a%L=U{}Y@3 z*wTgK3%Wku+)-M|P3UP}3J2NV3Ac{*k_Jqd&amuABJ&lmx}Ps8lRJg|H2iUrte!Z( zx~9?xx%`vL57Bd&T)3iVgb^Z?p#o~8lf&TG#PlLMoO(153lt_8ddgS078HGqp_uV) z)ATF4Bv1VoStATtEMs5HZW_eYYr`wK>7SEif?1jB+{1fW{q0K;R`>=1L245&-nIg? z{)N=WS_jQlp?kXpi@o*}=zX^Y{OSlQzQX%HTGXJD803M5kN)>Wj@XSOv7a@hZMM2E zO1h&fPj&4&)+!4;U|c_M6;i(Urb7MK;G3d8SF^2pPjwOsDozGWYjU|9^aKG_{9rde zHt2?~<@y&|8-nw7!(dlCc9O50IYxRe^=j16kZkolQ4}U5&#IL@v#rFbz#V>=9y~b1 zJ;+1UI7rL7ADLS@;bej0E~}YkNY^dBaBJEO@`9fld7Ui`JuLV*!mCwjdr5N4=G|Bj z9M`3}EHzRYnNoI#$P_|RWJkVbu>wg zta1BppS7#V9smpiK~sg;;mp5>RC-lhZG19gpf621+V@mslG$&lOr`K9Z8n}zX5P@} zfCK=97<>D(U!ca6xsB1muOODB^%8D}nBPX+*gic@Hr(9Io%ZW@0n3sF9J==?;i46FjA0X|9*?Vn)$ z6`B^&qZiF$RapF!ZH>N^4J3_zn~{Cga`@Z(31!TYB-$Ab+JqJ&b>*-S#B%oLQR5rf zmgXXU;C!l5-HkEz)2Z4hs;@IjKfv1ZbHSAKW<5L+-y2xUu;OUvTh<)rJfz%!olvpL?T76EVVR>9E7@2pPFpZ zyDr9POO0n&>5TYcP~UdoHUIi_E*rtJ2tq$%TqAR5)rrG3KD zEt5$;-g7@Fdy@PieM#n)UVVir{L+l~m>sj{O8oqKyHIc{Y#O@4jkB68iBR-r~Jumo~$Q?K9lyu-5fb)=u2wT6VnNwhLJO_&R zbZrykT+GYBcM&tcs|1A5ao{7IMBMv2VmM{Wq;eF@%;hnDFO+(hU_GRWb;1^XLa3*5 zS?SsbSV|Nym4SNxv>A1ueY2V*PcQ4V(Jfaxi$$NcP(YXA!wp5cdU~zu6eWt=paaIL z4nxwih?)p}^y5Xy1=mnHH3Sk0!Lwb%0_VgkqAGc|Niuo^U}X!4qEa4ud36MGk1+Ug zSWS%vtvm;%`5WY3-tGcOx>plu55NNu?j5-$B&t>q1{o7@3(-hc(hE+GbUB2s&O_ef zcOWN}Xig8Tf!$M(DDIUhSjZmFIu!}x9I8*7^pEw^)3G;-cVu74EC2B=VlZ0EWu~?( z*Hi5xR#m|>D+40WLylLl7JRs#3#Ul|GV-=e>#i>|^s><+bv+s~vUiTzDyFE?g6CYC z1Dp5VKzUCJf6!yf*}X|$m|fkkOr9HBAXC2o}(Q$BE$4J-=ve&F4?IavwLG7zOYUmwV}#oZu0 z4P?TO-ML4;d!hXnR_y_xj9{WklJ`CDi{&Yy_;Ls+52dy#elU0)`Y;4o$bTbYk%{9^ zG;x$p{bpo9q4jPS~^cvNMW)bBHSyTjI?Rn-z5Y}vZi^0pBK@EpgiT%QAYu6$a z1gd)NjHm=xX6RqnyFb_2>^vqCt}Vmh{VlhWo&^kBzJgdwWY^Uod*V8!444A-gSWqr zXlLTq9(ei+jp@Y+V+1`6Or9sDIsb!+ct*iw9 zyPLHx%-|DHTP-WZ9Vd8W$JNey8}Axf6n$tEM`tM4!94vt9NR$( z2FF78dw%d59Z@tsZr6G;@bV@OPfhkem0w^^N40rqjqHgj=d*tc+T>r^5Q{f1usTtD zd5_m8$GGs6ZbD&gqC;dGycdN_T$9ePo_|=AmFBEw=biccd4?%(y#=;uai2;8GWH(? zrbu8GwfgqL$=X=f!=S#bj51lR>khwCr(5+d{}V-qCN<{VCf5NIJ;{B-D=i66PDKVO z!3F#D>Z^L=e{jT#Ayqb?5vVPMmF$(g_Pe+>9xzb0y7E?yyMlnJmOrptqg3QMTve(S z<~TKdPC;$G;uo~M`9bW)CAM{e4D08DhY~=$ z59JM#UcE-h=iiIcb+c(mTh38%GY#weaV|bY1-(=emV2Wj47s+xd2vXNM{{|@ogk@x zOLmpEh6+fcy-?fpa!b-hITH#w+h9`ZOEt^U<6AT+ z0x@cYpq1zaU+5nGJ%qkis~qGWt->9ZlY6(1HdULnpD?kI5w$snIhJO)*@WnA%6p7anHQ_n@0KJsd+C&87lFVne%eZI@E}QaK;>rYA~=V z6u$QuXK|5!f-_->?Vx7*bRq{MqyPRb!KI5lgWA@;sGYMFa$}(sK2nW}ju355aMsT*ks7%2uq4oy??m`HEg`(~lvH6Se;?m)OKUF?XA zEbO{%%_@-BK2ueHaZTxmk_MTvv5T*+&wxCW_{#6@fct0C>EClFP|2eF2Rtb~bOKGYEs zkyX=ORM)HbQ$27pZS^h~e7iC+?bb&wfR zDXd9lc<|>l6vJ7(f%}{KWqiC;8`kI1Xc^+F#h^ zyr^H(c}?*8X<7(3P=uJrFW>Y=Z9C}hUPjPdBPwdU5P2#92-v-cR{_w}?MBe~Di>lb zw)>*u{>y&}GkH+2PB0|+yN3U6JKqGwmjVIYY4dBGLnD8RQ!}zD>nwiJ3`{&X^)4ue z^%6K);goHx7m*4H5(g?fzP0?VFK!(Sw^p!*dowa$6dcT4{Ar`=M$9?*loMbyX>iTr zRY4-|J5OF5OKB!&0+ouF23<kUs#3{yo@y!;v4gXw>& z-g1hm7R?2n=gvnUVFf?HTn{N_6#<~ylPKVztFT|Go=NZ8%LEmBVB+-GZ>mf$G0g&6w)^l@5Lwys_cZ_>le+)La;&7)W#RcL zB!=*_@$UtaVd7|GT-OeCh1M_CmRXMTqKVZ}Vj3>qhOck7G{d_ulRd=NG~wK$3`bzb zii)6*m`n?TE{9%q<`&3We@Zy*bv?^th3+`40yr{q-Q>xz@(5!$`oUBhSj(!^Us__1 z5NK-gbQz{2I^NevyV%HJlP5sYvio3m9@GfRJiaa&0s_Ln{0yUoww=pBu6Jl5F=M4( zG+v#_S4X@86qKxK0AB4VWDDJxHTQN^WOXlJ%qog}Ig17|xF9Dnd9dS1?xwC{Q4@v6J$g~fYppnQ+6RR7#*c=e zg+it-bwn+OGE?W)Y3syw&+WWl;e?!}yiHC+7w5qf{z&WGoEFLXA zg1x-y((#QwHDL&|3hss=rI{1)Asmzm=N;q~uz{!Coi*^%R(V^3vqVdS2AbXZbBS|x zI_%+S?4Z(dLxj*6eKe+GB>0LJb87D~)7ev@oevWz$gmqN{eK$4f&kuK)API@gd&Se z{Sb!QDJQl|L_BdT?4u=k-KG07Am-yf(o{(N!0B{&^EM9)EEgHopjNYw_14?QWWU6N z15G#WQ7Tzxbi_^GjkD(xtYbm0$`MQrve@4LV~#T;`pSl<;+7?>VSUY|ES8CQfi&Rc zB%!)TK`gW}_X6YAk8ojlKlvBq0FL9>K%L(0F1b zkj){@-0?_Q>1PtFOD{*%eG2V4$rpkukLhrPx$Tx>5&05u71DS7GC;yQ{!s0bDTIL- zQUy+N%)UL3k_vO0n_2oh!bY)!d*8KPCjUOiIWj1~d@ktahIXnSDVH~19M~#fuso~s z@`^Dv=dY_!UrI&4lFO)SZ=g ztZEDFJjTNNn3aX@W1(P+2$?DMgvXbijM-4h+#DY(cDMK0qAA(yo!nUZm>~$m|ESf% zCno{qw~mUmCFX~^nPc=`{ESM>!Z~4rn2JCPB$M+hu*9hyW67iSVq2*NlVd3;hu|po zii3z1xcaKX22_IpE9g0*DO;VMngS-8Be3av1fe2Pj&s_pPk21|Dh1#FA}{dPKKu+y z5FOUB9*OP!+JO~z-Pgpx16sdDMR6~4U&8`dmy-v%)bIL?_T5p)jXPiCi6ZP2ihga< zIMP(OjIv*U%!|G4o|z;>Ljj;1vjdMzP3Ln6F9u~44~^4-CCq9|n_xAhnoa}=>60@t zSSFs4h!l8B%crE(k%y%;W#NX!KQL~nC?yL?l`ypcN|!VIHov9{2QIDAIGyBtTHI^D zQq9vg5y^Nl#}pG0^A_jsNT_BzP)n@&9V^BH8lS>+=S25KR7RaY`kDJX=a+dSxU%Wg zTc{5E%%NM}IedYUAyH>ulwml^X1n6A@CNqTwt*0^b*dV#AA={=Ii2@xx>xUiO%U;Z z;R83lJk7*pNQU&UqS}CAd-$Lkz)KUNVAp_9VDu+fuaf(9U4d~SQe8E(mhS|gmAwqv z4p@K@a1h$AUfB1IZ@@CS@y*<=YvtWN4s!;4e$!N^k~A8xnv9bhb4J45W<_gi@_^~o z2-K9!``bWv`P1-xhReaAs4ir;>5ej)3LE$5bippZOch9(LP_al<1i!-VV>cU;>*9o z;E6N#dkUM%G*?2xa_K_}rdtUjXjNt2_gjgiI`hSOymo2(8-PW4we7b8PO<9$)NaMQMQ6xqAD zHi2lc;Nf^^J+TA$<92Qhr0rrJQ!SK&s_~-5e~t1R{L{;A)-vx-MPtWgO}GoO_Vwf& zDc^2;-$ojUJ;N0)v=%fV%h@uA%7P!GIC1uF8Nw71kW_X=4I8Adm%(~d_Z)glz6;L& zLfO%WM`(7{`1*UJ=uufNl0=)aCT@>cI~Y->#H{cyYRY&M7q9cHNA3$GfoV5esgnY>x83qqk$V@6$M zaJdkV3++-AO9QQ)2#5`171K^KzB?>8KshcoGjb!WxLk}+eKc+z{HPo+lt{2CLxSj; z8_Ky4>YKQOJKOsYq2=`7|5)H5XJG|KV|}9GDDa1Hn{mV`A`6bA7$0Ay!g#M-=Km)c z5{>KGc9-fy3ts2 zQOV7wEZX0yYi2XCKnY`wNVH?zw0FpGc`zZN=0!Y-wKp6uK8TIASCEhWq--nhkgI-k z`^-p`_<-cvr!9l9Z>(zUX5{7fVaB*a!;}fB)6D4z56U2!Dg58HJLZh;Z1<>ri~~1A7UNJZh+!J=gt^j)Oi3V+-bGc=IKwa%3v}kAoL{IUfS1M`}-3 zyy71%-~3DN(wkf8P(tusPF6|o>q!8MOP4ji(&Z1E3CgV-$Da1JYr&S%Xgw)&q_Bk; z4e4mX{Yc8>&^?g}X5zhn{S|b1dvQ{gCdx#S%HyTf($qnn zVLO#ST^=`>c_5U;%4XI%dJ)ej;fQp;&8Qzq=RdWT=J)9O^kHx*+*r3-3`81 z(Q4P=m%&5D;L61*6Rc0}f5smgb6)o64ME|1WY^n?zX!iQ%q@GKqg%5!-1W8K+vV@O zYgC3h{gkY>)L}*#muW||h0!+oHUhprL-`N-HFxXAjcj=5J#*TW-uO8_1}+Q|s3rTR zBr3a8KI z>Mo<0%nT`DX*xxX@5i}m(P=s4`h*!Fk^7nq=1!rR9AZlFjD##k$}K?>N_HEKBXmhk9rAxQ7{J1*o+-#@O!Bhfpzs`zw=dY z6`|VyNPi<&4hUON=#bFv#e#ORoD0*~isJ2tgF^@@jPCcimX7suYMX1ewAW_YZpDx$ z@ouNrmL}K`hSp~3@k)I0CX|KQ4?rwE^ITK`77$8{*cgy{cCP*>i0So(W%Sn|6&yt$eG}y4P+flc8GP=&;)kTY)X}`6c1M^rVMUdGg;KCrE;1IZOA2#@(Q;K z7UC2;U(S`SIf_^h)rcIaQ+@5fRnbCr#Bb?vzQ6VTi~W06Sln!*AW0J$$1NTH5%Uv9 z%w4H?77M9hvm~~q?A!K}!2_gBVU56|KY{|XREO&^f~W(TwVRIhB%)1kFE!wcPAs^G zW}6qEHhERXHMjah=qh-h#~rd|dWR2T=iKtP! za4*{!J)k4ufl(NvsSd)4OyG7E!+=I^9+)GzR=yeiLDBOq75yIkTRUs=y;Z{gMAPDU z&9NS(`=@gc8a#@R@> zUD)GJ)%RQ}=g@?L$o*M|(b})Y&XM*He+VfEqAF6pcyA5A882e3GsL7W!_4Y8xq=d7 z58>F`EXjwQ6wb0MZcX?F>)zG2=3M!bQ7iPQUX*I7W!KxF&giNWV6ly7i7Z`l`aWY* zjI}qh$yRl)be+y=lKAljK*gts^RUl@`Z$&*6nr__= zF3>y#Wj)3lxQ0>Y4=dnPT{htAqjz~<<`a5`(g8aq`r{P+r=^Qm*iTLf74&9izW1iw zW5_(_D+oU*(-o?fdo+-7*F$=n0f1h9bj7e=UFO)soDbDm6`T8P!cd_=KzlZ{K}=QM z3Ch1svq0SIM-Ik@)OF3*N_(Hg)xg>Jmexub>yYp@p1qK!s2c=3xPr=MnR-Z&BwV%j zkmHwf27prd=plo~riS%jG)JeIt6T4=!|V_$u&1DmrRTIk2Li)jeckKWlGTMdu_2hkoBHkR5Krr8BtVL-;PF*&_^$Z993n zZ@^^qjYAMvg9cT)4wc6>S%~QzcWSN`^L)Wx!W7kGoX>0=Bss~4lx54N1%T{J&DlQQ z?QOtVYxVakhIq3@_<^Rv3=&|r{JA&0_wPW`Hlz=oeCLtSiy1f$FD@8Em^?wMmUtU7 z+IMdt@twfBKTFL;c#aHhP*QmR2E;KGTg(;;At+y_gJRx`q?G@{@JRnfI_g33RbWe; zgA1IPJZThjjN*CK2TVf%zYtGjF(IzF2*~64-|?^sQIZ+~k~IDGcuw(%eCjPotRQ3X z)FzXrLq_eBUtB5Cp=l(UKb2@TVc`Qw01L?uxcZM&+>evF|8tbwe;9AxT4Sqk^H5W6 zThuKYY~xaIy`Aa^di2!>9s#CQ9Zj~a_~lz!9}vk~WAa70Xr1W;{=S5j5}&&_CQOmm10oI(jLJhjL|C^U$=r zrt|vkBUl%SD{#bUd$l+7`Ig8>(Q#s0h^E`*JmA>(?i^LPU| zNHcUvLw;AKRx)S5^QPXe468|UeC;%Nw&_KRn%bWfaU&S`4fy);S9V!`m68S*YfQ1} zUT3R(q6IqQ#C5i49OIX2-Q_1cpCFHE7=c|*fD-o(@qVw=fJgP`mD!-HeKcd{z~%C6 zekAI1FeHKUS70@{SnFu$YRAcJve-C&u0g_~cP&+o1wzBrK~egB9_|;1PA9H%n2KAD z2MPC-SWj_bcgkGg+27V%cHv|rO-IRRONhKJ64g3ag-+Q3MiOOL_a=ue?*wZa8n*^z z<5hpDB*0Y)Tx9#-Mk8w@P(k;m^v)u=J!(>EyUjRG+L16#?a^iQdIY;oN07FalQ?iQ z-sCZ{=+*yN`dwkd0L7jS;t`XI|ZsiKe(fwHsI{t6Gy294`iVY``ue8 zpflO2^wuHD1^lVMc!aO-nj`(q;s~|XTCVO|nK&GGy%h4Obr-ks(<7h| zeX#gToe3hwcjfInMXuM^dSHdaJ0FLDf^El2@bpivks|B{`DW9^$M~sqHmotUZ==q8 zV%KPJ28;B*`JlNP2S`(eBVUhbNdPa63>W%TtgL<$A#uE^;kaG_^Ni`dvp$by822mq z3wO|GJtZiLh^Xlv7rtDhB-dP&)*7ZU4jm50=*iZcD=_W3u3e$yLyZUap?hs+np1IS z)R-q0_V})LIrM|_1y&n^T}IW%8;pKg3dEm_Yy(%?-?-4zTCFE z-cy+E2v!vzp!Ww!k(JI_$P&FNNjT@bvICkwr$kn@0!X!TjA`J)J6gviPNXIjezyd+ zG=-~~rtPpE{KhVpks&zt($fZ?P|eXzipE%q9(f+NcAh^R3#1(`l4t&qij*d{kS*!4wltNW;?xK!K7Lxg0lR|5PSU@hk&`q`1) zWg+e2mFF$=LPTLQvEyQ7y_p}>m7Zca_njTRqV+G_v1(k%|B|xlYs!a7wB8fT?Z08!sVng4?v1PWA
mNsLzk#REw6+$RDLwi}8X-*bxSL3Mje;lccANZT6&uAR5nL z$w=74+)=&~kpmSH@{tRU>pQ=+9?mTH+*J>Rcc3i#xK?8|eo)6ehdt1F*K^ky63v%Q^#xUz02?MO zcqS#R80qew;T^OHdwZV7l$Vh&MgJcS@$m%(;Uk~SUx(`tXq{pgDM$*RE*3&m7b{U0 z?;=s%oM)+s3%ZZ-^$%B$-`Mi=+bqPIMC0}w1YGZzhp5_S&paN1=+moUS!Vd4 z=g(KqD4BWfqrR*ZZBMa^HLlAm-c1XvU|7YP(|m5C5^2qDuxqHfblT_8<%ohH@;)KlMK*=%(nXKOs=fm96lLWHC$O` zKK&^c$}w?c1(q6gt=xQh3d zAab*40kI_Z!93hnZf^W%lev(jfco%hKuzUSoqmrX&313TklMG23owfup~ z)bhQPi(C#Ol2^tJ;K9+7OwJ~=J}Bt(o9z>#6y=v!_qvujMrW7OW{sA({&M9tg_fO} z(0$j`ZCY*LEk&(MrFp1Ni;MERC6pi4(H4V-v@{0D%90yL!qZ=-NwyvB*2W8Qx;sHW zUt6mI`iR;EmfqOlLV-34J_x%L3vGh1&Hk@&cZ3K%4{}5v-|3G;B@~2Fu+XgMVt7*< z7a)IKk#%&AP4f-O zNpZzyB_?waEFbMSYCRR<$;@LLTxJsRRcbl!beT-{5;U)u3mq7j_oX)(DVwe9<}Yfe z{%(fjDN^i>1I+Ktzm|x9bMo>X7i^g6v4OCH;hTUU`u2#t)J7#4 z&XOF?{&#ekF0bM&bv6>cldy-`6#TSyszksT@t@=0JVZ(`k zENJKiE6bdK^!NfBzJp?qR+lHyn(?g{lDdy%HC6~kzPBC*J(JN5E3Ni*E|Ix>L{5T5A= zL2_ALn#VA*q=dZtzvs`pGUHQnV2s^k^Z2QB7%m=1H!|$$@5Zn8l6mU3D_`;gIeXCo zjjp)7s42z~>$(dzR%Z^~V8eL*+*+1Q(gt@+JQlLy5@&q>eHf}0&V#$~7{$j;N-@hr zxAmXwJs~P4ezpHdY_n=;>|ENsm_rD=xr`bVn(OC;D%U)z;2!8U7H;*#!NB}%k?9x) zv#R5`BlqBlurOY?PwD=@92KOH&-Rfr_`oN-TPlOYg}u3lgb&zr-XxRINo+g;8r$Sv47<3+-K@7T8t2i+x@X?gvusnht>;52G= z-=nwb(0L@|I+X*V&yi5`%Ee@*K)^o=iJgRG3`>NV4nfzUqNvXkB*P@<>0UH6d?Nne zRwG85+)8h+{P*2D3+xXyAt0n2ly_61z>4J9+iue==dh6+nV6sK;LOcMiJO!U={|T~ z#G;Ra{s~3bz1lJyECH&%wjq$Dr4Enb9(nvBai<_hF@_mV;gj0SZHCmu_Y3y3b=8*7 z%MDq4qysk^*?M3*)Qnh5;MruCF-}FoU{6CSpJ}Wz5Zaow+0SHcGkopb0t>&7Re!MtWCe~C$`gZ)wm2qc zq))*JgRAIEiZw&&NlsDF%^3s!RxHe{=o!T$DuZ+dA0!aX&3?zF8AoJcIBBUDw>6!E z23YrIB>h5gL3qBVf9;}$`bDpZEecGM{jpfO)Li|vy`Tm|5b_9PrwN#QcU!kpxqoiP zDaC_hcK^2Cj&2jG#qR(GiJRsB-Lfjl+WNQ(^3&FaqO)m-nSKpFskD#VC3JaJS^6Zk zggfGE)#>(u1a)6t04|D9qx%xXV%n$IG;sz)o{MpR(czY25QUc~3P)*6KiP3F-Dd>1 z18Bq3ogpoQ9rIW}`)B7fYJ2TOq@FfVEP3>6lLU7Q8Y{vYg0H`9g}2vTxc=UXQMDvx z8InraXbG)B4+Nw$JCOb@3&kPGC@*Q)U-eQ&I`)dGa?UDnhJh%iZ+}Y9XbqHk$90@8 zhviX<5F5HmAe=tmUabJlUPo+lP^lTo;Ht3TxG>~OFlwcu**?^E`~HQ8_DzK8e-A!P zIVAdMy&?w4*Ue1fKMkU^hOp6f(b2+#D&86ay@j;jvhbS+0L_DHaw?+Eoa5rQ}$7bZRm{dR@N|lnDsA8_M zsMez>EjU7yWtZ9UdZW zGJ^Yv2T62>o_H1}*DHS0u}7r_a7_DNXz;a^9rb91qEpo?o;a|!R2!lg(R^x?7$>uW zs^!vE)CC?-A62O8TM{*u^I}seb!ROD*4~V6qOE|Iw&Z0zt~8H!tL*W#giXVvbzX>=|M zq=Sbe22dLG4aTq+hiWtd7Qb;LB<)Fy@^r>*G18G91B#S$2ZvDd#{3O0#}x))Q*Tu~ z*Y9m%yBti!q;15Pz{os+WeqvkvcX@ZkVDcgqQ;+UJJz1Ai{fErYv|NV!?r3#y=cau zUQ9^7SI{gma3iUx4Iao%Z_6I#P47FkzB*}1ktk|5F7q{vMw?@&c5hM$Q&*sGr8?Bz zf$YsiQA3#^^lVL96{d^niOry)fWB%QG+RGl=<1;NKtunyTKe^@SLlhTcIG8edzV+< zhP4W;y~;z4oUaM(W4;lA+ooRQKRf;m?uxxokAws>-h;}**P})cc};rZLTTCXWvwfH|vHuXB+h# z*5_j!MzJan0SQYOB#pm;`6GDM!qRT~1ZQ z)~xV&?=+dRRUIr=M-&>i7Sl~k>;aI0?L_k`%uWh;s+V(jFceT++OOQ?KtBZ$g zYT8(o9Bt!o+Z;3G0(&qfP6*Q90j(6fE`Cajn9~@Yo>GCR`LqIQNRF*jNi-v-m3WJr z3*ZKAU&zOjx@jZ@uZo*?8$_SFujW{%nLHxuw8yG!2fuC{zcjxxtBXdLSxQ;7(q11S zris?)Y_(Bz;uxH9r<^ZF)HI5{_bpjPxFL3P$~EB#eFo$6Q`um-XOzx^152jO4_bp@Gq^W7_`SjRA?64Uy=66 z=XYBTTg4Hi5o%gE7up<|#3$WYG@o#z1=S3C^E-Qf^)fnG5-|4ATtQK#*@{SSOmdk* zcj>fP;_pNEO) zz@bitqwT>svxUhEB`Qn?p)pAExMa!qO{5DLo1Swx-DW&WxF z!97ih<)odMc+Ef@AvO#0M4fmPs1qI8NPjfc6koBza;FI%jy*YxYKa~43LY7HF zG2@8jNd8FM3bLa30tjSksD zF!u)$u3wzx#)Z$_jP=5S0Ty7vCc+kQ$yEsDsotz)OjA8j&j9$XyiLZe=<@wg^DPag z+-U$Rk!cGyZ-9O+fSwn5x0H^?K^&vU1~%IlfPCoqyk+;1R2<0Y2Y^ewcy?0GCoM&k zE3Vc$1@&CK4i((4kl#e-3FVN*mR{6Zj5p$xmwSd$!iiS4PN|Cv2Z#k};G&SV-AEL_ zx|zj4&ntix32ocWeQzD5$zrcDlJ8%&dyuGyP=?x8EpD!E)t^q=vT4ahw#9wEFZ&?E zQ3Og^dSj_eKKod(axH1qchI$FL`d|(C2e?JrOU$hRBO{I|K|yX0iFho$UX=UyHNB6 z-qj3_BH%g_%8Pltc{le~sqmO3_h1&CMJ+^t7v1V@G`qk8??#imcwyf}?--{(59{bB_!MW;te&Wi(?wkT*pPmyRm8?7@Urv9i*uY3^rbMOncv@YaUU)fdx(Y?uBU>~ z)VRt(`xa)g!&|+yd2geJTT9=Ah3hWL*oTr!+-{+eP*j98(9}qx+Pfpc`27#in1Z+3 zB31_|Mm#$=Z6OP@J}hSB+^&er&B5Cp=2cp`SKg;@aKlVi6}AzRhu=|B4H&#M-ol!9hhj!wqrveket>-J~M8LmYtrn#eP3 zY!+o^4EvuCksFWA;4$BEP%7(sNOZ6%zu@zCqlQkf+?io-*{{}MT zsG0)r7byI7SQL^^gjp!+4SYVnKW8rO4>nh>!m~U+A^SW6f;^GncS0BPA*pc3XNRkp zBn{KyYC;N1oHsJ5QU-@8(tc99HdAq` zEN0!?RhN zx!O7Hf3#2HE8VgzQ>K!dCeAta$@FsMJv6#Aef3Bq|iw&E@=>tr(JS zf11y}nh(#t@YY#+woN{)Y3b&{n-)6v!Zb-xm`b%TKl|ojP!{PD8?!E2f!zk_O}~s> zxk9y~{uGoKA{jq|ztW+F(-jCOqvRZ>Lcb`tOR+t@_{2m&ghAnb!Lm9?fL z&eOXz{7$c6Z-OSBJ`PjF>F`QDi|*n91{_YSUm)=pX=PP#y8_+WnvIq3GxL|077K7E zr`klA4<_>vC$=^yJ!clu#z$&z0C0sE5tczrzRzCFw&v;Rvdl+7*T{Sbp%a0f;}>Qd zu*AE;$qGc>BkvtsigtZ{}xzohUvz+Jf=6k>K3-e-w*kT!*{#R5J} z(&^`YpY1*MRzPI`T~Ke2jz!NoBV8)fs=T1hXPKEPBk56^+6GFm9wykt26y#216aYNAi=Lf6Cz;#6!K#|>df*zqC zmMq#jLX_clDJb(iYJUzCt3XRYsH-M6K1C01XaWEw&ON!qxKa}^{7i$+b%KP1B256$ zKXw6CInuqJMYRJlJB!Dy_A_#z=9>3$A}7I30icm>B&~ttg<3-R5_={UJ;romQSm%* z?K?NRYp{)HLP+Q=216U*ygW`p@k?T|wFV$fHrtQ}Hf7N@ z1M+MW6Arl(X3qrTmAIv&_=zmRylhrPg~$Mg6`g`5J;-f-yzGes!4r-Z;Iy*UnkKRo zndf`x+A2ZbdY4UAYCk+H9447)k2dt7(LEbhD>wRT_dtIyW zQ358I`*lMrY@R%;MYL?YYMG@=Q-TjwB^;1_{yyjEcXg=q;@Nqo!}Z+H>mP-2c#@Ux zGdwTjBMOgsKdA$w=h{7h^rroHev>L_-kTWeYeAx_J-#@Iv{}T>KzAnH4!A%wUR zqRSTW*<)hxO!wp4 zE^RKWGZTqJ#s7V?rj{aZn=y3K8m@4`MUO*XE~PF!DRs^Yvu4sZ%FY=2tkZXwBhnY2 zznJO8?2F}JC|-C*<_P3vnw7a#g>8`u;Kh)YocPJ*{Rz_~bvn7pCG(=;g2lAscOBB$ zicEbCxmnxr#Q}B(EYuVN!=+0{`2w~MJY#g!Niygoq&Q^qYS~<}nVefAju73T^jQ1T zkB)ZY$m9EG_4U)>iCsm{^JD2og`gt#GI!n26jwaC@WPRoZn~S&uqve64AXzy&cQpM zOTTN(zo-{jD<-DZIi!>w}Qv^5#lHQR|#_y5N zY1il-XHnY@cUv+`X9*_|Ma6|Yad?UZZ1-lUra|iB{ZTbxu=*n`TKtnXap4fW^uC0J z?hr#}AfstrrZ4eBJz@2<1jsrK(Qw1kcp(1czmSV$)@PEGawk5$bM5k8{7qH${v(FZ z+9Lq-);m>O&7xYJtiJ6;|4mN4jPb}8b6(sNFPo&J5(%%2Xx--*KQ?xpk!9ly42WRM z6ZId+i%ULQ49Y@1L4n!opRNXpE~yZl!TJxDL7?c>c7++TE`ZO&(UVS+F*^IG2k)5k zcZ=$L)K(R9phqvfM^AU_sgGu(^&0oZ_e!EQTk0(xR5tob@7=HOae-6;%L;Cy_Ok2u zHysPNHthKe@Olsf`-az2 z0CGLnoWYWpH44(Lrd~)escFa=K;^9*L6GB|Gn`SQ7;1YW;gz9a79a0@mL z9(d4i9&*T4yB}1zSzESGT>$eoNyRxWDBMsR>*>HJ zdMs@+GE5=CylhnXXW!JCT%6?}iB*b&iVQfFDsuGmzIoBWUyN69E&%??6S|EI?OU06 z8Zt#T`;Zv65VU({>J7T16~-7M)BSMB+#s1KBZ_)a{e2JAPG3b~r`|vqZ~rE9%8%?b z+}qB>#AXyG?g!dnZ0ToHz{QV)x*h`1Iy)#SExl?*x>8YGVHWH3*p1(KPvRKQ0sgCde`BX`ypDhuTa?J>7vE`#7XR_nzDYlXSD=-`Zyyrq_OBX; z!kU?~@q(*LWq4#4PKgw6f;l5;Yo`6(!t&d(>u%qHsslsNn4W2voVv6*Ex2|M`uJnM zmEp{l+CD+*Q|Uk+w~pD@5s@hPu0-)aonBuTnB-jyBsZ>pV0hVfda3}+{ zwqY+=N=R_pKiF6E&4BKjf+RJ(e^P%vl0B9l>!!=4_F&)pXG`Z@)5d@Ep<+!tuEOIV zzbKZ7k2lqTimn}JTx&UNqjL}>c}fR3!fnZP3P`IkD^?c01~T8h@o`Cu0fGKiAX z_t6hMJ*B_65!)>c@RZQuU`JV?KBszUvj**$*17_=fL7c{&oHM)f|Na}ke86%CWyv7 zen*0#B4%~YCj5w#&Ydr8hu=KuGe9G`)=@vmIF5NkBWJ<;OADNXhYfX{mmRKRXMW?_ z)9F-VSzj3Wat^;`%cTqPgQV6|d!sdu>_CIS>-c8yyi8?kMk;-(A1SKJ>1CGg=aJ3f zoQ(g!@Ot6D$6y8SSp)gTvUb#wX$A2Hpf8ia7rwlw(zRAeyz>gN(z%1{0xiVV;xZ$f zb!C;+UV>_I0?$GFf1&Ns^a9@nB=EP5E*jcSgMqyhG6Uwy+%p}3V%*;1(10jdQEwqbZj=hQn*fK8XX@bvP(rDgW?0^BkHCrjKJONH>`SG{Gs5v?G&`8{05tQ zM>n5h=3IR{U_O4DR{h9VKl6o#hI|`>)9qVcbPBkVRMsfRi~+OrJ} zcS9NUI)s%f#hS9wdA|=SVemH#^gy|JJ88%MdKU;BAzR_9m?H0bSZZ6$S<7JaSkKl6o(2tQ_)A!I@ z(j>38dcZKV=l~5_J518lk3BpkG>nD|lnL3?0nR5+SnV{Yr_Hr}^qQY^$Xt-OpG>m} zXhb?jV-rN&+BeG++LW#=Nr`@KXg&2ceJ7^6&3p}jv_bGXkMns#bm3gAhM5lmpV;3_ zY!TwEUeGJD4*UMn@elhV%FdFteU2duILGp60_uHsOmMcGxc{*%)Xpw}E&)D<3@=U0 z3$)dZdTu@1@8FBScWaixpl~1r)*s!_)`TPx8&gnOkrQj}o(w)@oL1!f-f|upU^3f9 zzis@x2h>$le8jdQKc?E{7>?$fnJwd!yVPtpkEWJ#+w^&L)#APY$ZPNGZM{k#{xoSo zMR>E$?OsA1vR%;e@RfjDAANsYUe^KC{K@LT+4EVfp$&i`j!8j3#P1utzW#WnoAE4Z zB2)a!wHA_+d=i<$9ha zl_mi>985}kCEe-iS~~a{U(U!k&i%Z2ukw>v1}tw6O~&Z6?>qH|W);GocT-+q4n-h@ zq`wB_3Zj~R>r6`Z8E~L#PJ38v5bgtZS+5d?GoeuJvQdPlr?yqQ$-QOk?^iV89;9(i zIe|mwrDK##lL;LHd@9-54Gf#jCJ~0>?3TNYF|NE$fdfnK;cwqxb!= zMl34CSA?F+@`7r!b1m#Qd9eIbMoQyd|jqG5GRTWFiWt8guc5@?Q2 zZ*Is*gCrMEle3)ognqO$m(Q-i>wc%L0&+WX7i8qLv{uCU=(O2Q0V5?yHUJ)e3_gK> zEx}^S?Tl*D!jyt_R6I;DuY*_WtLK66Z$c)mNfW}a1q%o=itlJ)ctgjHnca|jF*N^B zRdzwvkuRh+T-vaQ2aun0WhT@fDIknX)g!EPyZ@swQN5b#;%7f-Il!}9r? zaNE1zt}aQg1Z(4#a*Mg*2lLo zNxgGSiuqLz+rPZtqtA)tS*RH(INIF+k{S)9DANF7D^yr9jSMeGG&ErkzX)R)NwDzx zhH4gMnwi^+Q~pGGzQJ#to*dQTVZAakNevt+g=86F=xR9(yrQ`btwSGG=ZY&o1`WL= z@{)jAzK*ynvPooyqK>vm=0;@Q^>Ix#Y0Pe-b2eo_|Nog&5O!9tvpsxIqvgEe%3x$h zf@dApPy`VxMrM7$|4spxU`JOeaMdYv;5#^?JMION211eGtDL}r*5PMi+V#QX_e&of zH4suZju+nRxIEwuBPa|r)NP~m;XJHnC1B;^j*r|zN(JT4qU0`#=b|rBU_Vyqi&cOH z;WAg6J98PW@W#K#vh2NB$iEe~sz0 zlAd_(pN8dVh!0bgE4iw@pdTF~GjF9^&1kklS@OA}fSZm#o>D&S6`VtSL+xv>Vc2*> zdP!UEnax1U`ga0zx0hBAEq0J)j1X(d?mQ2 zp*2uad?yvSgRzA*XALlQFMfc9v1d!xsQq5~1NQ2py-;)tZTO$(CxJ}lQzQzHk3rzO zmBQjFAthP4sDV9Vxs3H?cd;{jX@#`< z%maOc3Vhw7_S1CC)2a=Q5U5qZZT;wZOjIUA%Fj#A+r?m!7eFt@k3gt{~{d# zy5R>fl}_}H%$ScLhYFVDJ^RQpt5-EX3|tv5eM-B|!O<)lo0rtp8$WPWGZVceD`dY0 z&L*-Z;`;>~6*s97GMBb!JNWRx@Z5EcIjj$rViUeoC9>i`xJQ@EB}oO?L$cNFfL-yN zkqw>pQ-N9%B%G4G6=*QN%G*vY(TR~apipx=-~I7dKPr3GkZP;r(;NXy$fh8+_ElfW++fT$#;479~H|(ZTvCb`^p)TGsUC8e!zafSVqzy6__BG zbs+eZPo?!^x+s&j#aU|lwdrp_4&re@&s7Q<_ZbT_4!!?T#EtWy%=z4N=o&n>RZhvecfN2CixEtdkeGB$Y>xfx|67*Q zIo=|DT=`HbKSnVUB7Pn@Be;h?4_>r(Wu|)oJB3vekRaR`)FoKHkV=?e#;MEk8%a_w zHR!#S$q5S}KK4t7DE(85eQ>Iln<{HMP>ceyl8qe*0x16`&MS&JF@^iLFCQI4H0Uvm zYSs5bR>PFu$vIu{+!LRKqRYEUw;YD$gHDzO`cS0O<;o_({*&J@b_4i%&$)526MIqA zWmIp%`8nWw&pB&!zNY+%tF(wEq_R_TD&Qr4=<;65uO{L2rxQ&R?rf+Gb(p`jK|-2=tG zBR4shxn^F)8wUzrPbZz0=T&lskCEVEItX(xsKZ-vB(b}$wgSqI3YM5Sl zpRNY=TG4qc3tXnLaN(pfZKyCLUf=}TWZYH=0)Lplx7D!>C0b;nFjUfBI!H+*c)h7S z_V%I5M$lQPg|S3~7sj1*^;{Dmtk7K_gJ5=;8IQm?McZ0&ci%8Jv#77c`M+4g)l!b5 z`8(MPq{M3iv5{-?l?9qPzBFCeUaI7H+3d?}$*fF(I7(5Wr2m`8T5$o^7D{O7M?I)@ zAd+eV+xi|JjnBo$A2B|I4)e+pkGQ+7wxz@F;%($P;8--j8MSiNf?WEaWoqooEtMk0 z(Y7r+Keyh8Dbb?SUTDcxpcggoXPU~m%u_97UP;>W^vzVtbFYF$%jJdj^n^0eWFU&UBAj~9W8OD9>EZ-o=w5|o& z*DG>w4cu@V0Vi=KA<6Qpnugl2{?#ovyX!t9oyTl18lFbW*D+Rr`tKy!BH@Ri1SQtb zb_G6ml_n}Bv}gs_Ya4Nps*$*2Biuk4TUs%m&nrSXg4bF^Io_*Pv-Zo<=gK$lF_fbl(qxMujl3TtA1Tq?4am(=24K_ zM7WMOI_=iI1nk8xrfY)})z_|hm4UN8RJCqm z=D{^XHdSo4UY{%GIHF{|y+`N=kW&QxeRzUIC0xkX zcdQ!dNn&Fu;qd9>J|JW0?@+@!sT0GsJ7~vk!n?|v?6cc^7jkV;Gx@+5WqM07a&fYx z5-KpVKT6hpPW5<-BRusOeLhSgvTKl zTtre+b4%mWFpwq?_%P*fb+DsPeP|8>cjA#h8fhAPraZ7D$86i+xtd#Fe6?4ruD`8( zTeFeIrMIFrN3#Q{?}|=Z!xCaxe~|PMn{*d*d5DP1&3f!2?A?*#*MA*}G12t{lnH2D zEv*M+Y4)^Ym zyM)T-?VSefv4Oi9;q^gjD!c1BC8oxe+6|H9M_b>g zx6d2d&~DN+VMcb=7eZ|0b(cqiqH;|vQhRw?U#aP@pS;cQAHx9m#2~`ky7)?@KjOz{ zg?H2PUk>)QKn~`+W6)q8HR-*)23vMFt2nrTq=3K;Y1jW%t;^_afH7AB7hr}g+g7s* ze%DLjtlBJ3J^mjdhpYS&KHhLtEER0qe!}jSz)j!ZZ-IM^PyuCw`^UhF>-b#k+8Iy5 z6AaZ4A&Xq%&+_?S(5v7XD!jr33q6w?0lYr{>v*}X!%U70v)~Mr%G(X|z3!lFIaa1L zfQ0w~!qde;e`Qysu9~#Go#kF1%fOWArM{Mn8ka`_e~v062!glKJ&T@vI296JzF(fj zEh6OxU-?l%mk!~ZsgCtRzoqSX{Xx(8dR!Wi0o^6jrn~^N^e)m4lzibA1pI1DM3-+A zPHkf75Hf`?v%70k2dtsg#IAA4or2{%0knBhD~AZN|E1?4F)_My3~}1qZEWH=acK8K zy`E#bZAUU{oEIG16*q~@2w(KmbaLL3ZJU`>jcSkD;Rb8xs*@=Tpo`7URxTEvh8}aB z59a7b^2;VACnsMmn>5Y(-uFgDXXA-qe-i_?X{amzXdd(7t_um)O3-T!-O(MfjH3)T zB`WKv#Bfa|Ikw?;xi@$3h9l5i{w{f>jvKj6O2Ce`EE7Rj#82CX(oBQvTp*AuSJjP= z1c8KP#;G8?iZvdI!DkdGkiww5Fu;!dp`Du9slR$ptC#@U$)FyWdEtRvNm96{vmi3a)P=nWJ4SMEwd+Qq_MO-TN=W)#UsKXi0cnf zW8zT@}hid9|>sYp(4nK}+U1OMfTj@HpunjM4iDqTkW_oqn| z9>%dcI5TwQ?HYw{24L4hTb(!rbEFm11t|1!g1IQ$3h@@D?Zd)5kI_S(=LQzQwuPk{ zMu#u%A2w$ZD0bBPT}N3St@s1<uH{DNgtpTIu8Pl5ld>rr8Y-DpL?idur9l*Wm2? ze;w4v*_ zs;bptf7w1FXXU*I3|F{sa$YL$ehDDyrH>1-4G6vyM}?3S?l`VNpWOJzLa^l5NWiRX z(6gl685g8WiZa0=EfYz&_Z@N~tOr6ARl@t3B;Kl6oXBE&j2L-Btar*;>R6+RtCbdr z&E$t5Mt#+q{M1#Lw7ePDe2_KiXr9WqVc2C$?dI_~pJp#Xjt}FZ(CBJ%t==T7jrujC z`I5n!3Fi4x>VZz(KpkGyT|E~Wn2Q7AO+ho`58>Wi!i>5BE-|^Mcso}H^-t| zVnv=DV-ZxUzPBp{c0{x+A`Mj+m1 zj;F}Mr+4y4Wi!c7UTh(Xu<%3y#e85Wf5y*`?tJ$w<*u8yi}V5}pl87|t5MM);O(cF zs$!~2-GUhp{CySmuz1>Xwv=Od%?JzQ7kK(o#WMi*q)BF^troc41Q#?&o_0Bd@&U%k zzrDGf$k6yh^W%;$y&}Mb(>DhMY@cTOO6XMHc_Xn&apvQaXZ&*&nj31 z4XF11`(nb3w3-R?n^9CLN@Qf+_hHVvV^nxS z=Wj6_89nkp-|9TtWvVxuXzJGh#ZwEdlpK|WXw>rf`k{k?TObmE5v*(e5oy1n`6!QF z-0)CqBW$-qCt_%M2I_V`KixbHEQM>e&$TZ4I-R6UdpeG>*IrUK8}9pgtCP&key z?`}%$`T1cgRS77IbP=Sk%;f|5vkkMdRL0ghd-eD?TrK#0g`Mu3j!~-Nrv+i%tBjS&!jptL)XLC!t3 z$kk3Nx6Q=pMn&Md zX|+;uUdl6dE~eJ9mFA1?)VkV5sAh`4RXqrau4-SfDi}8pBlUs-U$i$)CFu@kkV3F~ zj7njkLBlgcK*o{ESpu$*s-+*rjBGJ`Xf=r9eXd>sj43GQ-)bf4_b%ltZNGv6O^a)K z?WmC$%~)p%X?*2Cs{IQF7Pv=P112kV4X)8qgahX9mx~7EDDtPs;(Cjm<^$GU*cpaP zZP0>10;j%RY|j8FqxK2sS++ryZY9FuZfIwd2Gj6>JK3>)F%+oBe;N~U8I*wW;LK^j zxb<<;!n{R1bMT1<!ofEHmy&4qu=;JYZ{0&d6XK>43hM!ZNO=vTZ8*37>$oCqusP z=F&S|M6jc?vTN7y?62q*bdx15s0E77n}}nfm-Txzuw|a?B#pII0?O8i8C;%39%>R3 zua0k|r$`t?c_sMAuUFhjX#4MeEdItn1>c2W?AgW*u(?qiU*ZH>BVM7^S!0X)ezHp# z5I?PPy)W3O(T;3fXW%JSHU*f+@&|%b7h%ec*Vzmyagf1?D8;)VZC5yC^{EHz5nmQe zY>IFJ-N>F{xJ{Kj#q7n_@K-wj>J+~%=w5<#3@05Pc;NPRu+F!gWdoLaxYk3v zh)foSyIbJfEs4^MGfjK@PF+3HNJr))8%NO`ZRla<)GdNkk{(=9DrsSKeSjtK6-Wc( zxO@^OWScw<8M;iaJz#A9pUgX`qtb)q=4=e`831mK{d-owS2S|{1eAd6ZIvXu;KaEs>>0-lFrDbl|a&!o?I&HZxaw^N?}=jo(&vr#{5yJ=C2ryPXIQN!jTr3Nx~G7i@%QX}nn-H2lhqU*~vr6Mw-`;nK1) zVgnWGwZysjbg!hB3z4W+$5?M(=)=@aIJJaDL7DabLYv_a?uT+nvd~tlxO&~zO>p^z zX<|i5Sl{&1{99E|Y?|DX5v^vzQZ(Ls)|pp%7nNYTezSi9_UIK4u%s~TClBhKY+Kqr z2_*B{UW_B@NB~X0pGO;e7Xc%+0Q%yduH5gw+{G=^OBox$(bJ-n0;OE`-8@=O%#~KV z>NZA9v^g!iHli9|{XS0SZ#ocv@~`Gol8>c$AaDRGc^W!kj`BjwVU9K7Asxow3_TGX z%7VzMSb?NLT4>5XP!2jxoDYo&4d^qP9Ynm!A8JwOj3l_9eO#BW8XITotN_)5~ zHz$$EF%PE~TO%QF|8}NcnfJ)RLm;7F@Sz16M+{5j^d0X2+aYB;RIKlzJ{Dp(2ma-BU0I@aIwH1rU4I@#NJ3TlC%Ny9lhtVG2+wq z1Fa~c+@U=he@ruu;9++wj0qxvC6!2d&Jo#KYWXBh~oAK2XebT`#}s+w7@ z$1HRY3ViSc2f(Tiox|{^`LFM>j$eONV=7$BkI`N@JQhc{azyNfQJgD7l6Cz5PMEPtD>DB0e~ zywem!2;k^J&r~BZg_zJk$(qvhluB(U3R!OMu_q`pIxH{08K8z8K^QrqrvH4}F)vi4kPU8ZxBel8%H^?FF+Eph%WkC};A&3(!O?DBtEmmX@>zU zfrA}|L$5w73ok)%gDfjITqWxkImIcA2O<@6*JW0zQ&vO@{b86$l=9ng_Uz$``e)e%@`fkHKqUwr|Da>4CE`> zFdD&y1wM)B#2e=Y?7I$$HURBSB8P75P;-oaD*wS6^=x(Quuu^kZdNppGTxPD1ic>? znSwz4XG^4^VKs-==Wxe3R@1}z?kI4n4wH!pEjvkana6AndJ%c|^zFw3Efvl4F+S8k z2`bsrsQyk~EYvZjwUvu)=t+q>%HjauFK2|tk!01^fx$4~S34d3!kO)|HI%y|_-p zkLGOIiI^ae6yUeE6Pjy-tfzRQrUSl=$+YJDPPz2G0v#-1?u0s433@DmVOXF31{ElF z;rq6w?btj`zHfkiQtp!o{mBxZlb@bwv&!(0mL28*fLzKTw|@!<>E3un8i{FXz?0!t zrf&Ra!G~SW3Lg5#l0sw$38O_Eu`Xv%#$nMC1hv%VVV~5mm8RDPrT6~vClde97T|#U z4I6I5r?*JdkhnB{OttRlZou5q-2B8?^5icq$WSmndGby0H>kEB^mciC%c&u&k1&-kTOjX3pINjQFVu<$%J08!QGuY1n%lLgxnU$?BWBKLGu)z0e^VCcd7D5n8$ zQTP#eqmeLLhas%H=eYks%8~gq_T!b{aPwB@NA_`su+hZ=`BD6(sccdcoVLQ_GWdI=?i2#>P*6ZJOc~GYhkH4H?9Zj?1e{x5NN^Al33Z*#X0Zj^CXK!{o=nwN?3VOIy=|<& z7Zc;t&!aU{Dh|Qq=)zeLC7(Bm&odC^=bNAA=;+;MG93cn9C4Hfo%eK+GVKom@`vTn z%B*`>S@&Z*Ze`RoR50%KPG6KR)p4|ii6)C}kbm1e5bp-nIpF%~Pze0d zFkk@iB}}zL0#0RXaK{QxD8ci-?(sVMO#_1}EHATj2Cj7VK6Qh_ZH?-x#mW8FW7K4p zD}nQO8Y2`Wt;;U3`a8Xzvzbj$hOlJ#v}`ZA@uO@ZP*F(L#K6ks)b76{oO~;cQi(z?!ZZue& z&%x)T*8--nHoz|Hyhr_u&*Js{j8vRKXn|WiXC*e;YfAFFt6TUjC`=D_XWx1W55^IQ z$R)bWXUu4>x_ndTwA3OM4z<0Gi074|l7E4sQ7D&oulWs%0hc|P3wULv2OmHP7FTnC z^^=VJKLU%lCtFAg>EAvQe(X@PS!>2Zn!9KjOO9N?`FaDDas5CXl}^08Gb#&Z5s&Rq zI+Nz0jKwV}BS~(B#ZRY7)(89+&>s}R3Kr~~)mZ?xP?c7mgd51`sE3j<-%)m2yQ4V- z(elP_?Ju%PbzI`esVXG~!%=oUvG_=KUO^IRRA(0ZRsv}wjoNDjqw9wWTl@caap|aS za|uA#+5*9?XQzF0HcVk^lR{lQTOl4VhLNHGA_s&d$}T1i$ZjM4dP%}t3i0r-&=W3; zUGafc$}IDPKXg`;^ErE!(F#goSDInTt3N!+-FJXRT$zl)9KqjG)}Wj@>>Ts^Hl}^p zKk&N4b2M><29NgWhpll9yH}r0nilSq>b347m-N-^!hq&)9A?YPP}M&Y0(N}NVjq7j z@R*!`Mqrr!iW6{6y^OF~+}VX4rXcAcg~B^;Fvw|sU4hESn1Db7fm(JXhaTDk?`sht z*F?SDf{ySI)VJ{dnNT=<>^^D2)QH;!OP0e=SMpGCyHMa#Al294_WAi(3kWoV6%-z} z!2*$EeN+j(kM$#ban}t|t%VWHC@YsbbM^%$sZvWf9>EC{D(DX) zfCmai!PFKlF5{njiAAOkb1(`^0Dq*~bYPy=i{;u_KmY!Uw?UudK`HwwuATu069Sr# zUwLF&Z>%V;B6PxF?6VUmr8-J|4~hDx(T5$2X$G06&T%%Gw?1Izw7uUa){!#Arprw- ziM}9JSA^O4pHqhAayV2QL^_~IxbFuixVyf2Q~FK6Q&vTt6PDjunF`!wdVh(AmXwx) zbMZfp2qQ~~$e=TDbtce{^*@|6k8>dt%#GWcY-)UkzG1ahuh|=%VPD?RhR?)4)~o+F z3ep)!PQ?pm##ySoOtr|!d>Q9CFfT>h)|;kby4~Wd6fx>M64`J z?>}+k-`EU!Yb^#4LOrEKKWwYWj&MhSh$K8a)!$gli>LfMs@Q6XDY_Fc)@F+@xBNu1 zx3v%Nj{l;m!^* z%ZoY5v%gbh&vN^q*$YpaW+{>-#%J$kQDNnhX|$3t*#?K#G9!taS&g?AQVJAA40aKc z0*xRqRHH*@KBKz=$+3Co-Ihm;T&GLSZV=l_mwHfR0ksPd2ckvx=nqRva=ek3oJrI$ zK6T*Z-EW?K($A-$sO5yVe`<{Sbl7rnmOm0u3IG|5HzTTnn(&L@6On@)$u`$Lfj z9^!YrZ$#9D--owtc2ce22HJe2hm;t<$?hlAc?j?RjDTE(#MB**14|`alvblgM4{Ya zn+W|>cCW;nnDhff0VcKk1af5?+lD5;8u`%^-Oq+89WGiCR2UKK1L|2Umj$`;IKqd? z7Z3XTO3LQ*T<2h*N!rO>Vz@mcL$EmK@k%*l`aiv%_sqLn)5F~LK5fV~Ird6K#%S~F z<`kZftBSPj_O95zr*?XKK!$^p7<^-TXiE*gJf=^&5$4H4bvza|*&w4l^yDe(F!u4> z`%f2b%;feI10fIejQc755;u$@mSu|U99k} ztvL#Q{|ht4!Wb8r$<*NE#zZ4U+(AdD45+TvcCHc&p&Qn&N!9C*>}oH#N%8?%&uR7E z!{17dg%D^zV2#{aN{6JTt$yKJG>XRpwq~a!4l8sg6g1PKKAJ?@epNZ=0e^LJ1<)f+ zi;0W7w^3DZBGr6|s(bY(YaA=7;wqo4X;IuX&P(q-Wx);itP!x^vY z&P0$uqik@{mAMJnnM;sCDr2bVMIf$6gk|U0nqS7X65R~2!Z+ZMNSRh_%kk3m`wX3kxufy_L&M0!bID;5r&Hy4eRVJzltLD_Ek%3+!j@R8-FL->1v>MWEbzIKX3Xp)c_8+*4a{>cu>}7 zcSa9q^>h0z!-oHbGU}X^De2y8D1u8*1H*o!jxxPRcgmhm>#D99iGuu!ccJ6nELy$E~lvr4F73-6Af#4j^?s|-{ccC7SAN5B+iY80_Q z4*ne!bU+-Ac=}G+lp!kBC#2?E%;+fu$&8i2K=w=oR z2pU9k3|~Aw04+e$zaXMH_N2j766g;{hvQY6QryEOmF9v_o z;NLJO$OadmiQFl&=k_`3T86Iyh0A zp$nRU6OQs&n(Y2Qm`s zscDtNkSJLLsM;z$2woXD>u^)0|GfA54#=arVFnPGQs9R!*X4;%4(|&l2;6`x>G>ZH zLahF?6e~G#sb~9aFX=Uw)fCxGjY@PoEd%2K>CfKRC|8t^;`yg#q3jnSriecEJeptj zp^f6FsT=hkb%;rNvqT3-igNiMW1ECuGqpav>5fWfMreS0fOn;Y@^w;)<64~jfu6Oo zI(Qyxv~W;NYKg(rJZZqjftEb}`uOLpJmx%q^rdgbWkrfuwW0ce)tBoqORkv9T)bRx zp_#f_jD-?)xvYoLW@cr82IRfovKR~4zim7DMgKcb6}3dJueTnS_q54u_O)qYfEY_e zr090+8EN2-RGkH3FRMk48r>IbKKL=w^Lm1X=#{a8d&K%G1;`C_&t)=<(1(0$v3tlq*Wm6S%^srTvw0-kBSo4bU zx~?XZyX~hXmOOw+kPKR*VSM93?P+s7Z+BaI&(-0L5?#J#3*MKNo)yxe$XXiw_&JPz z6>W7!zPH>C@8zqjOkw`vXT~f|#}tdx?YoL-TS6FfY=f{yDXbu7eQZuDZJ+hNm%gcE z67~JegtEnJ`EY1jv2@{4BTJDByjHl%t|HiB-)0;aG4S;+jq{TtQ;@G%aX_qBcRKe= z!3Tbw{%QI~94!ywF@Ud*&NnJxPzlWO8Uz5Y;x0RN$&aNe`88C~h_E782dIoOd6uty zq?CE~0RZod;S$K$Qts;;z_AG&oxxYRLMg1-p!9OqB`o_xj$iJ7nC=Kl;n{px0%S>( z#i~48y+jt_N(`9aI@CmBfT=gsdZoj%H2fB7z#fzI+C%Saa46x?b;M&mr`iWA@*QA z?5w-<+(ERbfFKb?X*_%9Ql+#$4A>+2a4GoPUYriTsa?wh`qVl&BpHB@E zSvm%A?=X!=Oof`Yf{%DHF#HCe^>o%vvJ&EMlG2YMk0(zpSQ=v7Cj`&hxV3m{z6cH< z&Gcy|J*=CEDm=s?lYt}AwF-zk*(=)0vYdY;qNs~^r7%C4MGnmR>?qN{5byo_dCXsM zQVoD@dZCgFA+f^)%y2W@R5jdXeydrKwkrk4QbT$kwM&yszJ+5Z9q}1!V~~j`+-$82 zQv|vUFDOQ>zFq1)|KO%XL0d<@SRfBLnT};pfg$tOdH#K@GV*=nA^twkMQjeWb)~3) z`=jD(4ooPWh@iNsrD*a=WFFsIdP;6AHTkj{FNTxgxcnmDyUb)m`G7m4dM(|~B^}OP zRg%a=pZWf$6?5MB$k`-#DH3oQe{}SNyy&+T>)j}C6NYy*R^Df_uRa>&Q{X3);~#;% zUx{64kJ+wdLBDqB`-K^s<=fbgp9M0|2k;ZHp7i%~BQ25rIvek*)gkaPiZ z_s~`kxD8Ze#)1G17tk}V+7`DlKZ@Vzisp|M7arGutP?7w=jGZc+t3b=tl z*ITO$nH8mdT)BgGDY7qk>fqp}-@FG2-e_L{g76UBD=V9}RIejnYV{2mgYUhM} zsCmkivBL64qopfd@xbn~tWOnS&2#dQP)}k27}?GQsolJ@;`7AEYnbA8;Po9j4}xm9 zFeTd%BhHYxqJpY{$itbC^O=rJW?)346%Z z_cs5TEt$vQlaAC?;^Nh@YiJA>k(^|S!7KIkzcrAb4!})7!eQ%_id4bYTz5InnZpa6 zvKGVY5Tz1pn(~xns}?qe%k};WFdouG4u^WW*g zH-)>#=^5_4`ai0RRu<}?H7)YgdlE4%NsEktY+&SUGL4BuB#7(GYVbOPA2Uv-pFT`| zmGY2*{jH5Bs|B$V^O@npweQE%JSwSx>+pHXwDwmV1R=G`z1?3R!tSY`2uLd3uIMK36zSo^yOWXtIP4EE2|*7_lex%%bZ%zoqP@hO=mI0g`AYJE zh?9`m!4Pzm#%$s`FmD#^py;C^tvLedkMv#&j?qs13gL7iR5CL2_=??7W`vK1aR$l0 zEynrA2@bA(4G@tPgiLVfoSSavAm3}d*@Y#x!e+Ex>^Co-cVZ9O`_P-~KxJJ*|q9)mows zyaVc$Lv4D+8e$76?`YdbuLN7i?kVB8GP#ymL$zMMGpB1`P9#+N_8=}2c=THzZm}1b8AQA~(*fy>AG| zw=l)E9k#Lf?;^aN@&)*esQ)P$nT5w`p9hn9`53hN+xd%=1EPxzLvE9cO<{JXrnuhr z#>oCCU(BMSzDv~AqTd(KGt2;KPhNC`eCAh95j)+toO-}bPbwb=7T248+v=pMoMgDr z&!JC5sp~9?iG&503kPjV5MH(=kp^PF#U=7P;?8At_37e;W+4HHgQ+HLrh17)u-_BM z>Afj$dd!e6|G7mA>N_-srsS>{a;o;Lj?Pxe8OW}pBgWJYA3c=zDAN|INHzBZCeXM+q zwnNl)aMm!+!EIK!xISJ%U5CZT%)1BnwmhPJlegq{SWxcm5uJ}b^b>(qXpHEGka)v;K zi#Ifi_wVqg9U?XmuP(PLSHuD)A^3acxL6!4_z8Rt%#up1e$U0%ld8~MQSs;>N+K~dg9vcj6tRW>$U83M75QY zCTiTlDD9@eRJfyC^+ciU!9Q@1#RvdY{weO3#%0CyM?8aK*Fo0p?wBix_uqX(+5vZYnv~;D^({swcD^bid%A9!OO9EP=_`ZPx+bnUv z*mp`TfuSk?BFs*t(Z0g~v#@;^$wu6A+_xU6zJ2yu8V(D0_Q8LYi6XejRaSKv9AgRf~9`cmfxvcpyB0Rg$>$(TVt%h#oxCGn2GCLpG$8{iE zsNVbWV|4{awPa4NiB@VPuS!$dj@BEsh}~^VL#Zhs|Nf7AtijkKqL5=uNm^suDy{-6 z68nxNTdVvJkyLu(3_n<4akL3i==k#|C0Fk%ay%=j9rX$4nv1?QpgEjg{U+q@Yx)pQ zk}+(M?|2JV@XOGFe#kPys|7p1XO#Ke#?$y+K>ZW7tIX9d$mh0GioK8iw3-48Il#lM zc-~}31GZkFYKjI z^9jce{ne@=h9lRfw@0LW8(FeqK@qVNlt7aqlbufPp!l9>@y&uxNkDvoR1P;zEzTAV zGoptj4*kMLc|N*~rDO8@)p7@x|6&Bk-ZR_mgX}`eQO?uMAsfG1aZLZ+&H|Jj0g^;3 zKiuUuM-m>F&N$!9L!W;9`4Pe)@_mxMJvsArEWQ{bN+Erp%rq?EEQDhf#WSmKkuMS} zoO6~aOZI_gGUNv($Cp0CT%}8sil1wNn%S~!Bm&l3u>%x4uy>oAej_YE%p2W18*rK= zpY66nGQ-45L%lib?ZiplVJ!W{pgG5eU2l2bIa(cbQ|OoMGVn~g!(SOIBh&kKJ^ddO zK+a;T*XnyYf6#Giq$g#M+t!2DupLgcfp!m8(u3vIZBF$t)L~08y2`LBVSP(u? zao9SwDeOF(1XZ%TwKYVDN^1mNRbG|@X3z>>&7^wr;JADvH-mL-s2f@X2h_4+9(o`< z4+n(9=?Gp@=h<9Bz|@Y|M_S8dtM8Lmmu*`%od%W`JBh%ww3Kx8 zybsqe`Qz{(mPC}WkIk5f3f8zgK-Fd1P#7=I>nTIusp@pefWNb;D=x7=HX%hxS2&KS z>-;XNeAS<-bo{ z_tXysGa&|Z(MSRiF{q)n<3~Vdr(Pd@an>&1pTzoAz$+)}6h1boK``>ah8{D)qM?Bv zA}A1G?hSSzu63bH3ZQTXrdvJet&%@B*S^8v8yw;8h;lf~-JVeW*~nsNU~j>7SG%FZ zu6;t+a?=rRj;+dzTMcRkVrQ!`!t0C8!Yl|gYEA?YQ71Sc;iv?;k0Zqk?^oouxzof{ zGXm9$+JPiDEFcSm=`^9&&PJxBNm#U$B-5?=hn-`}ZbGLy?5gdQZG+~!V@LP&{oE@;|*9a-u!U?hb^kQg$0ED7x*Vz0sr%K^_=k(!De^50=o{YD4)vY>TbQT!L;=7(p#QB`hw zB($+f1-YQfDBhUc06W=O^7|@0hHElHRts@#3kO$13+_-Ob`?vJ-QHkxR{*-N>HjBM zY3HOIzvBlPJ(+d~haifX#n~nP#?&E%m8rB(Lbvcf{aTuor?kJfdLw}B^j!O5i*(yhZy zqQ9YOi4|Zo;9uOux~8YeaI3$$jy7TrjO97qj{!j&xa?zMWKej1F6L$vVV!&=8%9JM z-Mg=Z5xtl1EZlq7k44asTEoLzgZ~pHRM3H-zcs8i>CtgDp|*7IayCf!nxS_C$rZYp zO{XS`F$0|cD&l@8&LH4M)Mse{#TiA|uZ#{N+!{t98uspMc)=M9BBNPcQC9}#M@xc7 zMOXaT?`Ra+9^WGayjUjfmKLx$2lW1=r_kl$wgbp2<&3OmXl)wdq)XKkxkBsXF(enA zqp1)*Spz_vA;^qxp8T=`bPJXS!~UJSbw)QjnzC|Ba{Lgg8ucx2GF*w-`4}g^!~*4J zyrLu))TXrl^`_j&a&vt!%aJwZ5o|&~@C#Mvn)>{Zitz%GHmk6v5BgktC`{t5%c{`d zgi6EJxeZp2QA4VikR59_;BulielpedI&1hhz}TfqZyl8H&8=gZFB7`2D-VD~M7hR$ zV;2}XeJ7k#h6K=b;sN!=^Ct*D!1>V^4|`cmi?Qt* zH6#zTxJpm#dV?Kqn$tJCV+rOdn)l*OKL#woRvc@CsnTpdsQ71xs2_?7Sum==L`qmc z0&j&ai!(fKu5jK$U}w7kTlg;UmH%#<_E<9-=Mnt07@$R9GyB<*RUc}B)64R&vRpbP z(kernwoJJ6T$~KIV;{l8Y(5-15_}tyuAjTlp8NXle#Kd#miuAB>!x2Wl6w(f!`PX0 zFE}*WX(Tq3Wa~*c+8}vJ%8KkaHbh*(L~pxKb!o`)o9|`5qc8iaJklEcK!TIExGc*c z(_~J6vb)G@Oj)V{o@?Y6O`SN#<-X&isi>6l>5pTKrU0+T5L~5$%B!4JpIe2zVkSig zbHaS-YD0BibRwDaF-hM}Dq*HaAx?<8|HVn?(^n3+MpLmY-vA^D?_6~#z@sX3&#C-E z)G7~-G9cY6Aah#%zX@YYYp;TXBxmLGUcjEc-engF)& zhi!Kv^m?7Wrl=~!x*UHqZ`_o ztq0SyUU%ngJX$cPNfF&xQudZRSnDTf@+YjHdCsos$7i(4|Mztrm_ywa2qnCoe$5LZMjFZhytZ$G=cSIKL43WyDFrvYl`BDHZWx9P|2 zX+Mg)LP=GodHtTLZU%Ic?pd+D|7CQ+t2A06bz9Sru~9ZLA{i@P^}%I1*@pRD&V*Hd z>&-vi-%%HBi>UU4*~U+mwxj5W)Z;kkTfXL(FkXTYBHD=-7J?6sd?H)VqksHw{&+3x z9v&bEm=dZL2~mySOAR$SmIaIn94RPKdB^t;u15FRyPirZ^@qS5?BXjsS1ei7wQTPE z>nLUPjPk%tty;(8kP9Ale`i)iieO%A-_9{pE#=2uy86@{s|JVg-jae%!=LK(co?Yy zqbKL@jBAgJE4ZLoLFp8NhmErfEda_kfL?|{{vw1fJ=W6PN6(*o-CO>C&Vqr!TpU{5 zA$@TxW9>kYUFziL%GXLINZv8Jd0Pc(={D9qwKm+bG9yUx(&CLmb5(fxD|>tM(JwIr zMeIJI`%XuVk(w`0Hav*ue3k&c>~)Pw)f69}oH0G<-cb{)$r8G2UNnS4H~9Y))@WR~ z-U-0VNa~i}aN&{_j`-ZEs7?3}Uiz7kuAdB2)X2Ch1@|*-N=L}#<1!CjrtHD zWV^u`)U50<94N+@hoEw1Evk^Rh}FL7$^no%*%&e_l;*>_yFn1pT~qB?>ES~udycwY zVK?L@T?%Hfg8IrLwB1YL$vK)VE{U~+)(&%D^}Jb)Lr79P(4|+^gIh|!N3B*Lx%?af zS0zlv>VbP}pU-x5_bO1}a!53p|F8L4-sdp~Q zkfe)nt(7KC9e-+S{V9j3eGmeQ%mDb}Q^pmCx5E z9!etHQ&=#U;U5{fn-LUsOtg{gfZ%6Es)?fa36a8Z@h~Eqs3TUVY@>zVunjblutxTL z%Nloj%!21Fu4ns{`8TFhfP$m1zzDwlSi8lSM=^$lH{zPR0=qox`oqRuF(4||sQ#8B z20*cFUtQL~51Pckg&_USb!VVpel5Q6ko=*m;*H?*+OzJ?xh8814yX^fxnaIVH~%?! zrcuT-e6M}pnUO0H+E1;HeWkk+lFgM0^tN!kL&63|K0Fg$=Rtj)dmniPyLirMWu-!w z`#bfnkfBe^HylNTZ*oQc9mtS0^HM?khg(tMz7wn_B%2xWp$cp{;4Hsb)K|m%1gZ}s zho8%VCKSldVp07lOlS{H4QbKw<3Z=)RtyYKg(tD6K8N4|Kg8O|a}W*#x|WY-&%KI- zkcZ4*Bp6E`<9ZVGF$UwBW;y1nMQxL_wxc&!?QgAKR~khS$Ip3>ng(MVq*fs``0Ije zA-)zbr=j&7WY9U5H~i1x+62!G1->q$x=;uOcnqsOb@xeTfcNH5bR7RHcZ9&Q=}XIi z!-ccu3G%b0krI73K41uL*x6>Y&i)Yt^yUY24eBJp_C|wrK`VuZWSOAJ^Y2p9zR?^-F=rx{UQf$x+IEEH?=sYI zAG5GJ=By@b5Zg|sL7GFY*q-%(H$NDlmhj$hd$ZPlt6^LwT}w!&sjRixIerbFTd~0% zlSy5x;Q}A!2$JpI<>#68o3~OlHF4s>(h7?6y)Z{Xk%H zn55UJd&5!fTg%G9+)!t4=DZ6(+Md*cu<=T{i>FmJyr*uJlc|vxI9?3Vqkrfv8w_t? zR~GBZBd^+nLDQ|Rg4L|bD1p`13r=ni7I)FN4;%w#^Sn}i6~lYwvM6wqTDJbeRRM zDi;d{oyNsqjs&@BDTWrx~B@ ztK1y)BV~Bk*un2qd863keGqA*w*2(Y>6<|j5)E-bI;wn=M{b{Bou%;wqDzOf-JW6yGPK|CA>up^}PcDB5sW=XJ=dij2=J&86h8kG=Ga3)@040LlYsGlhwP zx)c1~Dg|)>$H3WA>$JY%jm`XGCl}v%*RgKD*DM1D&=KFDnfyzA5T_~qS8DUTid_HA zTx6$3>E%bfHPj7OZfOK&8p_qi;Y#KD^#6cHDd|#K?KPhjnlIuB4Sa|>_iloCY7#1Qkfo>@qv=V_smdX+k<*K3l!aTGI*Am5M*R3i_@M^|7VTCBlSPiP_ zhdu}~$m-aHe~jJ6Fqa$TuXgDMED;yFkPbc(11eeni~ zBDGe$aiTH*7xJ}R4WC!D)EOU9kF?nF#FWOm)`yIaGl_O%)B1KVsEZs~@<2r=21HK8 zFMw!)Js^nAhnU4DK%qF|#wY+V499Gp+J{lqs|9p+hf_ECuoFI zxco(zaw~H$iCG;pP*c1uy_0D4C^K&PLQ&SB+}A3&V!XqYk;La|(7xpW`;H4!v!yhN zm;#IUtT!O5XjeeLu_;#!QAy5YmfG}9kKQj@!9nnbSgwZEC{XEwmtmRFef&V>;F+-x2m8q76#jAYHq zB+=#)Qq;q9S}>fT{u)k<8__iNA{9z_9+p+Um1VAR74;VvQHQl1o1Am0(R^TBj=7S? zr-Io)hHRP5yJEasG<|CS?XRyw^tP<5`$bdPW(eTTpx)DAHeu ziiIpaa(j;KRRBUF5fS1D%iWRqb~IS5Aript&A%_M;7;Oi_c6|xk?ay|K?9jq>9*iW z@bJ&7!FR~aN!aw?Q=1u=Qf;Rp{?6Vth8@1PuUI|3g7o{0b$G`p8QW0 zBT{8}-`87LnwnM5evJYiJHvOvfq*PmM*hsPvE#6zhp+<7|4-OINLmlKdE;DZ8omH+ zc1JaT?&2^`-cM*g3dER}VMl_r$HM(qnTr!NE(8>JMD_W`8TL1+_OHQak}!3XO(Cd9 zx)b6ZE_|jig0?91=H6F;-Lnk%B!h0)%Tky5<{h#7{1Z0mzuDLDy|@l6;*XJ|c@1R5 z^%`imu8$~;B?9^bt_Z1cot*|_FJS&rScap23Jf5*h7d759MVatwAv2!mN16Y=HOY5 z;MK>5MUSli<@T(3K2N@lR1md2BRiJ&;Ls^S!4&U$UNR>S1Jck<)`J_Q^Z+PPM7JKS zWx$Ya30Ztg>VHCuC`#pCA)mjxpsqSO^rpnlV%F2^Tmo^ z9hO))DPVvdga68QqBH(Zdn{J)1t0|Sxu(vbbuY%VFKM7xNCAR_{Z47)HJoq}a{Cj8 z{sr|%VK+pAJg!@_ss5XM8xA{VOy0sXk97B<_4fg>M%UkM04rW?f>3AdTEB&E_cn|8 zw2hY~K%e{%(U;?TU*6w3{ysD#6E_IT-2N((M)kc1-);N zg<@x2`RX{LmtDGk(7aGKm+UfQ^9-llU;f2tH)dhm-e71BC4gl|lN>roo*Z4M)uO96 zef~4U$KE9L{>A_YtrS-Bna>U+Go^VJ6*!A#l0NPp8_jcw6E*KaSSAYaHN;a@!ZJ5f zr)#>&unH?>hm}XKFV4~9t4l6>sqG0IKy2xC4d$UG5Tq^iW-wKa zbf5ld{0epl&wY07^zXUgJ?l6t{Mo0Ssd~5}#~n;?+C51#OR+oinniK0qQ{RT^mPC% zFes;Hev}&WqLGiI4j$Ird@5*|SaYKsiNo_Sj)Rz4#>p>@WQj%LPgghWdVf*{p{5P5 zT1G2yf7kb3Gjl8#GMw7l%tg7B?(giSTO@M+3zdIUXP7TWIgYqOzGK^L>2FrxDHE<+ zV43VAj=WK=@Bry_v`7lQV!FLM=C?QDC`RdQXY4VU@_-^RNTnQxsh4GaZ6kFcghE4K zTSY$n)zG*R63S$kY0G@qcmxhkE`Gwlhc<;Efp%y@@c7_i4kz#WBP7$^RO z$XS!fx+j0oa3gPx+{=1|LR2TzBt8t&hd)^UBUW4z=bIl|otC9Z7st>ESK(Ajc~?A1 zOxMRI!V|3_BgNI!kGGJ5L$6aed0Hok>a(88555lb{e{0U>^3`4J=0l`h*^Kk0d`eM zQ|;CV?i?0C>%I^8VCjae&0jyE$9mTo4VxFE@y*veRNCVE z-|D>+_eF;nBg@8JcO}KRHpz!iVl)aqcuOAe(+Tmmg>sCiAUhA-eHR-*fx1a ziAnce|Lx0cIza>grbD8+pRy7AHjpz?KUDS@1I<;ltj0^22bAa_Hjk$S`A@lDRcu6x z?kq@N%LbM61?{@P^rD7SIA<#g!94D9m88ljMOcHQDnP_^j}iL7Igz~4;a zj5^hZ8^Xu<5n~AKE_cH?3omPo2Horj@hQ;eRBbI3+#`y#`$9?PF2T?Gzod@=sFl z^3&Rv$VWv!;#s(zSepgowDKU zJfg(sG9!gmx*^QWl-aM0Cjg;EQ(MB^1U)!`#9CL6q8MsvzM0-l<2Ebbt7-OiAL<^L zPTX^V17bM#h%nRHG{3LW>$J`D6YO^RIaAyW=!nI)Kl|TB|<-7~8U?pCmbaln83Gar-;Xh_MRXWL}k&XmF|I7DFtn zEp7dwV;qu%Yg31Gf77Z31(aBls(n2Ih&1F45MEhsp-x2`knnLM+;Go8s{%8bfqo3R z?bmhEVEd{Wp*suS*wT=~5cXG?AeO1TWlRk&5Tc2rP!xS9ikw7K{3V`=X2Ra9x;Qf~ zv^ZOYB0gY0u!w7bfe`)~d~o8}&XwO~Z|J$#jiZ!jI$$`HbEdEq7ra>u5(y!)#N#4& z*~P5FR0)Y1ZYM_JB@OByP?aZQ1ob#ZI69d?*o9<{Zn)!jN^^tN&!-Fx{4>hxHT3l~ z*#?%2^mA^rWFRS6D(z@xh%~>E;t`60@d1KS|{pnH#;&IwdydJe@>PUGtn~yu~gtt*DH1-=`_K>n20QO zTP6l8@{~{g#6Nodd@UU}Z-oa;ghf=i43-P?8FHR=ZraviF@j+lIy|UPVUN!_`~P>t zu1>Oef^#IMnk^}NYV{^0-2h2$x)&R1AP6oW_|Ui#yqo*my=v5OFm*L%h8es}p#Spw z)K8!SRGB5YTvh?KRtsY$eOxKy!cYO0OFw^U5#H&2HR6m3Q=pgv=TP;%5N!{2-s!Bu zdl`aT)XfK@iP1zEb1)nFd#QR>%RIQ)3>fa9n4L6wSyQ>0NCQT$E~^N*mVnL^v3d*@ z9!iH0rDh;-uN9{v=2&*!v@PXZAmSD%LMThVK{p0#SH-pA@p5qF;V7}prqEfL9FjbD zUjzRX0?K($=aJq>z>4%F47-PEEKjKkhl2CgxLi}g=7Ntheu?c!ae;iF6&n$whifYB z__*K*y@;Upd<`WgS<01o2d&n$r$$GQ{R9Uu2hmBRyRhp)THEWAKg3=oVi^w}@*K)E zJn;&V5nXf6a4YxC@-#SV*t>$(zW7+pq&;O9eAt_Yk_g4u@ef^-8Mp#{bulZ(#pA&^ zCLzH^d{kkzsH~>#$Ep{B=@IET%T;T$i7RkPQq;QWV#CcrY=^0{iZi*$a$_U2y_jq_N z=)Br8lNKGNA>|Ajo56`p2VOH(;;-y5y0a>Ba@>XSC`P8Ky(A~=v`aNg;&pJ5S>qHR z7SDedu1_@~P+(xYnsz=s^uK(5hTyUEvQ!oeFgVCYXbE7^h8!)W{msY}8m!jrM_MJE zA~kcG^YJ74j-bFXs$Q(UvR4R+HVWsOyRHG%y3OSbuS4#IKZ(0qQbpGnJ|~`DsY1re zCr?D`oH*?L_~2#TC1U~CK~})c;Y5h_jJLC0TGNj7JUA~C&zN>5be+7oNhxVHz0u|o zChBh{7w2xW0&mF~_=53Q^=PFyvKj zK~%oI4U`}N9Jb}Q0WE?WX&~UD9YY;TT3V+DQRJj@*8t7Ir z&rN~J!o|aPo5aS061z|a>Meh8L@x(Zj`HHQ&+813-9?Q`%}Bx9Ndm$R6BGl&J$@_y z6pLb?RA6EKo+08Cbi)1DhgE2(8I5azL+b0x$yZysEqL z7`-&;<>>{btw+G7qKe`7IJCB-!YnM`2#1p^6hz0FDJ?@S7-BaEW&+~Vu^}LtfR^5I z3?(9Jo^FT9v1fWTH`4X|>tWNsh%P`&GfsUzF=e#xbcl556#_pqakdH(bEASGM}uv| zawo<Qx8Up|Uw83I!>&UnVG$@tzPr2T9QA zk0np#ys04mFPR#$=%Aq*iy}vEHb#djL{-B$$JrMX;Kc}foiP|uQaCF2_QM#nqNk@c z?j#hsPQIFInvxi)tk@iX0n)mss-rbyyv&U9ZW*-?r4Fn1wc0If>bzB$GCj0ei_s+} z5kexZV-6)-ec&8s&Ie1ydwfE+BNG!64t;$opeRuKTjhoxe?_@V*l8$>EQ%{ z{D(8nCE#g;B3@`%3Suef!*u>V_ni!IYGQ8B=OXXaK9jlIw~uNep|O~5scQI&cPr+$ zme`qnoe`g;`Ay~fdYmj#L8CQr+MJWO!4xX=?pOJo&b{ozoH_Cai2TOlqE6!pviOZ5^swI+}8i^?A^X3)9{M9AOz=Zs~9XxG2l<+Q0u1e0T^SuvI+Oa9L2XMP z!zEyv?*u6X7NG?mMpuEDE=tEd69~BPcr){i-H55K*XRNg^T>8K7BnDw{tB20w`{7b z2{Dy%kVCz+Bc1}t&Bjp)n}TTEsK~v@BE_r$g2V?Tm0Oxb!w=sO9+cw!Nha=(n~Ayg z*=6641M32M^Jf1rWagnUt&u-ntS$QI>XSoR_{CbrU?)t0!NjS3E{x5VaxAY9Q~K{j zGJHrXQp+n8;6-vyi?>*jR52OVSmLhD-r~}V9pa$dv5;dzA66SG;K7Vx0jq{fV$KhT zA&qB;;JRi28xYDNN&Qww({usTBJ74lEEMesYeHo`^P(Pt|3}%WI*(+n&PZia1*KJH z&K*C(OC?=joRux!G$D~s7)Izdq8{~;un?5a(#geL-48Ae0@uK*Ld7S217LVB7bvr<_k=5IB)jRV0AD?;20tAid3lYm33->wsiVK> zBhno!vg5X%S?X2z!XUU6ZH4#&RI9`rL9wyY^f(kSh9)h?#`?BDb2hjE)~92wpVpyWxK0lG%t`KS)uab z-hwH8AuaL$xNeq}SuRDt@hpD&^=T^e5l&2yC|?aFoEzZ6ZC58c6K7_tp-Hq+Lu8ki zlA9Lb8HvQ=1xd*l;!@l(&^!YMDm1So? zAWyzC^0X_N^2w@$`_IOMy+LLjBUQUjQA+{m-NZcHZI!{VI9r3MH1>;hkM=L}=2qji zD+hva>Pk}o=|CB6zyiA4>KVG@#TK9vdsl*BZ~xoUSOAvqSET7eb6T-C*vLjneR;|^ zo7T(t*Y_n$c|X7d3FlN1c?3hHmX8?@{a1jo3zSHNN8pPEVGKCsBCC0EulkV$M+6l3 zZ&3$)U2(8oVx!8rFi?_>RYL3Za8KOFF0UfGa5rQW{M?fug>v-kVgj}g%mb<+PFP(ZZ@BON&$$lS?ZLRS`oAm_ zx-FGOK8nibIA?-rM2j^qdMyK(a z=JI>Yqygadt#t>t?1Rx}`=E3*keK#5l%QK>+KPLUs`28GzfpgsI;v?zKDCO=Rf}Zx zk=M~^VYA*nw5@Y9)SN%*HIie~^JQ#VsJ(Qi?{HdIgXPxQd9zx*N3+t;Ef*HLB~Q0O z`0DKc)XI;Y&jSf$Z3`ESyeP0O{DbS>A**I?`V@Jfu-3!5#N7aO4h^vcf0T5sW;kf4 zIw1D~>xm6&vfrRX+sTMSvWY>HcX?<*? zua6N4Is}GdH`yU|o}*EbTj|6?+qdHWJ18RRE2O~{4_07BnGe4(+)r!LLA zTrP}<;wr14_!nngJzicC6>KeyB_behZRoIY@vs8_W$iY?aIy*~VP6C9F@5uYhb!7* zs6%mCouJq75>T8D*3*Kb9rjo(>`jMjwiP}f1;RRx2=vudu*y4OVz$`m_C=K=(Y-0n zDv2J9Mfe>RZfnW_pqi=sk~Lzf(hwXEd*lRNW)ZEVDc1RzSu-Myb3E9apD6#AHFz)tHM2o8Kyqq{!>U&xZKi*fQCL$AW1Hk3V536( zts=2t5V?rM14G1cfPz7Y7qSv$G6%RAR?P+$Ko>q~Uqiy!_x~N1RKU;d@d&kOs3e1m ztu_BE*7~;btI`F(`rsxY#ln=n&eS9jEE51v^Z=sJi)0#h{+GL(ANlFv8>JnDgY@

)6Z=XWvGPU1CSs^}EwDk|Nv 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

2xiFUFYsZD|H#ZIpLPXf7#_&@GkAWzt``e7dd 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

;#97!YRgN8Rz)R)i2(3i7kY=H_WuWQWA%r z3&TrZUcUXF<|C#UB(WK|V!hq$OyMIJienTxF};>D%Y-&Bh~Qr70cuF|%cuMa;+U3w!t^ys0p?LZUToU+)$|y%X}r;2`#)10HFlE z>y*vDv#qf-;OM&{cJ=6{t)^_G8_yw>hP_&PtWtxx~{REzCG#2MR@L0_DZ3|oFD(v6iPAj+9CS6%LVtz9lQ}yiezNk-Wp(+?~ zAV-YGJs=~sA=y?+pKjGyparw=f{%AIb}`2roZ^2T@wVotd_k?n*4>j%`7fW1x>qJC z-29|KgSqtZY`yS|i@CCZN<%K>T{1<@n(7w<4-FfrDxkjJbevzs)uO~~1(HrlaVJ17 zJkX^B2`GFN9nk84i!@3qY9NEU+9X0k}{|@ zOufp-B16!qBA=7|K>?CA@Gr_OIcEZP$8|egjm*zG&qOKENN3lTB~G>@+D>pq&jNmY z1IUWIisPrjsTTH53j1yI`;fwtgtwO9t;$oOSy?0qCkRhb4+4j{E#mj6i2n|6T*?m8Utv{}y=L4!rV}DE*`MG@U|v~SAa}-U8XTkWFI|JX);+ez*qy> z%yj5}=kCzD2Vr@Z>HVfZ45vIJPe{nWg)d-;VMeHCNlv(->#NGnkk=TJ29}W|&$iun zt9D)9|KLUzi>{;wMWww`aD`!BnsaWddStUfgP|Jx)e}kH(pJsQ3_t_.#$2r>|e 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≥7`pR)^iowpBW3^yVkKxlyM*M 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 z z^m6BIG2WyMVuYREO^L7FV7ZD_&T6$NP1L{1xom`BCTkl@M;vIFu7en@v^{(}_v~%g z*@UEui|~$n3vS|zdDB^kcVk}A%ha!P5UL{|vUoksGHZzoP$@k%@s#S1r5Bx)6D;$( z0j5P$iX8|=BOthgSOVOS#(i_FsuM!wV9GsCsG00r;!-qZU9>RIDMkryz7y5@MltdokB$g4Ye0DI*$Q9a=^9hwzVHrHo$v%^rPZ@Tc?HQ*W*qmAx&9_kXYfU z?UX?fy*7ND0Ug>npcU@49%s;Whh! zlLpI#a#y=5&1i^Itci21LN(Ef?vfhYE_v`>9^vOG=4q5F3;eEm5-PI4@%9!X0@&{p~Yh^?~!b^L7ClY-Y%3_OR)n2XktffpJ=06d4qGe{GqvRtMjUM|=D!9`4MPGd~3U+ha_ZD7!F z+})tqM=di0hdZrdKX@tE_L~sE0{zv7`91g?bZ-KFD)f}uxOJ6ly3AQIpG!CcyHw6th_j0Nnp7;2pmIT#PD zg()>7re@86TY5pa&`WWIxl#^F1-JaES6aL(z95bJG{SnD=n-{9{TYWEjOaSJd$%3Tu5o4gGxlAig6;_t@!glbd(Mb`&<)&`phPe0V_xV;z}@!tqkUv zjZ1M5X_Rt+|7fax0HdpClY-(IwiD1W`Hh;d=Y|Oe1)|;7?fv-GwW05dNsOf$jMiNg zQ0uf0l+G6bz$czkM{%yucb|NtP-Y_JnY1?3VexZYkhdI46^;0=MvfbiuZt?Ysv7GE27sV<) z3bm3dKCa5vhSDjx+Q`t)ID`4!!6^uBE#l#m%`OUBj6zh8O95H~EJpy!=xp^;%!!RJ z1JL^;f1IhW`yRO~x{X)iBu70T#ZwOJpzYL=yJkP+I7A_ly;v^fb^e zC(9Q^co>39M_+9l)yrN>i;9S{CIRDQ2ys;kxF@8FTh%hbGJnH(CS-f+L_^Ts{(~^2 zD__)$$?$D;&*YoJ&H1;jK=cHABz88G^!|+4MP9RrG2h4 z2|BWkeG;8<_@Wo1|GW{58A?7oG9H-Rv9Q|v;4}=(a z5kb67c%4LrM5wbIEM<0bEhwR=*6G67&4%d>i9b$rVbWv_cMp2ZQ=~9UvP?fttz1x3 zIc{_HyasLSpCIPgs7+V80Bn)tG0+ys5HQ+3T7{9RbOlXA$l%5!e>RSIp0z;cOfjWz7R|5!*)ezRkwDDFH1p+@Hk6X%*#8gcC7buj&>l$8f z1T->NgBZ`RSnq>oC^AkBMFPrS2ui0#k;zwHH9~xr(LN7Ib5xwyhh)6) zOzi$f9A zj2??kY4KE@T%vHP`a?;uP-N1l$vK1BM^s_;H?6WR1+;t4Y&hqH}UlntS!yF zGey}kz-rjO@?vE&xGXf5#)rH6pWG@{Pc1mK!1V6LY|OLmR2vOpKADA%323TVvn6$^ z*Mpn@CY79BW`q!rM0on~}haY?fwYsEwnH;^ZFjKq{e; z{zp~a{G;i9evTsCKUV&`zWEgfqCTmv;*Gr8TfZ$^*LxG_+io*yp|S9~e=cuVPj(Q| zL5dFGEM-d71>zStIuJwLS(&O$m}vLMlRbwst!m-~j8eh~X_R2)B*Q5eNMwWkKg3F0>+dqZ z*bRQTUIij_sB2;3k(-@`n*A$}f?a@1^fS^gwPGQ7YA(}rLb3w(slZJ%tMhgC<>Dqy zysusMgSPqDj_$h8Q4xrbdjV2@AJg5LD<4IDA5=kC3rRLr)djy7bAgou>>=S}?bJ)q z)yAU}QEQk8RBWOZRdZ?;e3(qqYrYm7TOSqDyfy8j@`5~=+nOI>Q&oUP;1W!Y(dHR1 za53yU@=@UOX^tj|pMDfQ*c1uJvKXoNd@|hc$egnheV}g8W&Ir$OvBD&{ulcHCxoL( zaTp|y>p-tM5qX^N0GQJV@F(KrSxpaJiT>+m{C?AhoZQVmWJ-1x8R-RzZwM>0C$g+v zf3Evnq36TTNaMz-ww_1SM`&pB%X-8O*rb6d95+^8NaDrBPCdVl$zV=lD(_k$7`lDe zU$nB>0S^((4cA@jei$;>JYsQ$4_GxaPjE6WxcFFnhk#CP#)9)4U$^DHwQ$e1@ zNm={C5K3LjC^lj{`LWY^Dfy_c#Z14(v*{pA`)AQ-2;9ggHH#_S!0pte71aNx=Y2=B z?tKW;fh)>MpE+Qs0i4w=C+ItcvIe7;%A_DVtnS7n;PH0*riCV*n4n;R2_fe!$&5d0 zN$%L#Fz+0QWA&l+yyjnZc!9Q(i$fTrWa^6Fov6d3ow^5&mUbdp`YauvB;k+%XBT%f zuS2F|U*&bXuoH+R;$K%EeSyu<0f=Nf!9JAnrD#HmK7du@j3Oc~?P#s}zr@pEv%8J$ z_~I_%fTF-QXe8OR-(W?*f05@S=G*Do5U6ncXS0&DWLCzgR̎?5#E@}9^28hr%H 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%0Dl9F3`#|C_(wlvQ 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 z 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 BfDQlv literal 0 HcmV?d00001 diff --git a/gorgone/packages/perl-CryptX.spec b/gorgone/packages/perl-CryptX.spec new file mode 100644 index 00000000000..076399ae6f8 --- /dev/null +++ b/gorgone/packages/perl-CryptX.spec @@ -0,0 +1,46 @@ +%define cpan_name CryptX + +Name: perl-CryptX +Version: 0.064 +Release: 1%{?dist} +Summary: Cryptographic toolkit (self-contained, no external libraries needed) +Group: Development/Libraries +License: GPL or Artistic +URL: https://metacpan.org/pod/CryptX +Source0: https://cpan.metacpan.org/authors/id/M/MI/MIK/%{cpan_name}-%{version}.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + +BuildRequires: make +BuildRequires: gcc + +%description +Cryptography in CryptX is based on https://github.com/libtom/libtomcrypt + +%prep +%setup -q -n %{cpan_name}-%{version} + +%build +%{__perl} Makefile.PL INSTALLDIRS=vendor OPTIMIZE="$RPM_OPT_FLAGS" +make %{?_smp_mflags} + +%install +rm -rf %{buildroot} +make pure_install PERL_INSTALL_ROOT=$RPM_BUILD_ROOT +find $RPM_BUILD_ROOT -type f -name .packlist -exec rm -f {} ';' +find $RPM_BUILD_ROOT -type f -name '*.bs' -a -size 0 -exec rm -f {} ';' +find $RPM_BUILD_ROOT -type d -depth -exec rmdir {} 2>/dev/null ';' +%{_fixperms} $RPM_BUILD_ROOT/* + +%check +#make test + +%clean +rm -rf %{buildroot} + +%files +%defattr(-,root,root,-) +%{perl_vendorarch} +%{_mandir}/man3/*.3* + +%changelog + diff --git a/gorgone/schema/centreond_database.sql b/gorgone/schema/centreond_database.sql new file mode 100644 index 00000000000..d610b11f6f7 --- /dev/null +++ b/gorgone/schema/centreond_database.sql @@ -0,0 +1,31 @@ +CREATE TABLE IF NOT EXISTS `centreond_identity` ( + `id` INTEGER PRIMARY KEY, + `ctime` int(11) DEFAULT NULL, + `identity` varchar(2048) DEFAULT NULL, + `key` varchar(4096) DEFAULT NULL +); + +CREATE INDEX IF NOT EXISTS idx_centreond_identity_identity ON centreond_identity (identity); + +CREATE TABLE IF NOT EXISTS `centreond_history` ( + `id` INTEGER PRIMARY KEY, + `token` varchar(255) DEFAULT NULL, + `code` int(11) DEFAULT NULL, + `etime` int(11) DEFAULT NULL, + `ctime` int(11) DEFAULT NULL, + `data` TEXT DEFAULT NULL +); + +CREATE INDEX IF NOT EXISTS idx_centreond_history_id ON centreond_history (id); +CREATE INDEX IF NOT EXISTS idx_centreond_history_token ON centreond_history (token); +CREATE INDEX IF NOT EXISTS idx_centreond_history_etime ON centreond_history (etime); +CREATE INDEX IF NOT EXISTS idx_centreond_history_code ON centreond_history (code); +CREATE INDEX IF NOT EXISTS idx_centreond_history_ctime ON centreond_history (ctime); + +CREATE TABLE IF NOT EXISTS `centreond_synchistory` ( + `id` int(11) DEFAULT NULL, + `ctime` int(11) DEFAULT NULL, + `last_id` int(11) DEFAULT NULL +); + +CREATE INDEX IF NOT EXISTS idx_centreond_synchistory_id ON centreond_synchistory (id); \ No newline at end of file diff --git a/gorgone/test-client.pl b/gorgone/test-client.pl new file mode 100644 index 00000000000..5247af8ebea --- /dev/null +++ b/gorgone/test-client.pl @@ -0,0 +1,183 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; + +use ZMQ::LibZMQ4; +use ZMQ::Constants qw(:all); +use UUID; +use Data::Dumper; +use Sys::Hostname; +use centreon::centreond::clientzmq; +use centreon::centreond::common; + +my ($client, $client2); +my $identities_token = {}; +my $stopped = {}; +my $results = {}; + +sub get_command_result { + my ($current_retries, $retries) = (0, 4); + $stopped->{$client2->{identity}} = '^([0-9]+0|32)$'; + $client2->send_message( + action => 'COMMAND', data => { command => 'ls /' }, target => 120, + json_encode => 1 + ); + while (1) { + my $poll = []; + + $client2->ping(poll => $poll); + my $rev = zmq_poll($poll, 15000); + + if (defined($results->{$client2->{identity}})) { + print "The result: " . Data::Dumper::Dumper($results->{$client2->{identity}}); + last; + } + + if (!defined($rev) || $rev == 0) { + $current_retries++; + last if ($current_retries >= $retries); + + if (defined($identities_token->{$client2->{identity}})) { + # We ask a sync + print "==== send logs ===\n"; + $client2->send_message(action => 'GETLOG', target => 120, token => $identities_token->{$client2->{identity}}, + json_encode => 1); + $client2->send_message(action => 'GETLOG', token => $identities_token->{$client2->{identity}}, data => { token => $identities_token->{$client2->{identity}} }, + json_encode => 1); + } + } + + } +} + +sub read_response_result { + my (%options) = @_; + + $options{data} =~ /^\[ACK\]\s+\[(.*?)\]\s+(.*)$/m; + $identities_token->{$options{identity}} = $1; + + my $data; + eval { + $data = JSON->new->utf8->decode($2); + }; + if ($@) { + return undef; + } + + if (defined($data->{data}->{action}) && $data->{data}->{action} eq 'getlog') { + if (defined($data->{data}->{result})) { + foreach my $key (keys %{$data->{data}->{result}}) { + if ($data->{data}->{result}->{$key}->{code} =~ /$stopped->{$options{identity}}/) { + $results->{$options{identity}} = $data->{data}->{result}; + last; + } + } + } + } +} + +sub read_response { + my (%options) = @_; + + print "==== PLOP = " . $options{data} . "===\n"; +} + +my ($symkey, $status, $hostname, $ciphertext); + +my $uuid; +#$uuid = 'toto'; +UUID::generate($uuid); + +$client = centreon::centreond::clientzmq->new( + identity => 'toto', + cipher => 'Cipher::AES', + vector => '0123456789012345', + pubkey => 'keys/central/pubkey.crt', + target_type => 'tcp', + target_path => '127.0.0.1:5555', + ping => 60, +); +$client->init(callback => \&read_response); +$client2 = centreon::centreond::clientzmq->new( + identity => 'tata', + cipher => 'Cipher::AES', + vector => '0123456789012345', + pubkey => 'keys/central/pubkey.crt', + target_type => 'tcp', + target_path => '127.0.0.1:5555' +); +$client2->init(callback => \&read_response_result); + +#$client->send_message(action => 'ACLADDHOST', data => { organization_id => 1 }, +# json_encode => 1); +$client->send_message( + action => 'SCOMRESYNC', + data => { container_id => 'toto' }, + json_encode => 1 +); +#$client->send_message(action => 'PUTLOG', data => { code => 120, etime => time(), token => 'plopplop', data => { 'nawak' => 'nawak2' } }, +# json_encode => 1); +#$client->send_message(action => 'ACLADDHOST', data => { organization_id => 10 }, target => 10, +# json_encode => 1); +$client2->send_message(action => 'ACLADDHOST', data => { organization_id => 14 }, + json_encode => 1); +#$client2->send_message(action => 'RELOADCRON', data => { }, +# json_encode => 1); + +# We send a request to a poller +#$client2->send_message(action => 'ENGINECOMMAND', data => { command => '[1417705150] ENABLE_HOST_CHECK;host1', engine_pipe => '/var/lib/centreon-engine/rw/centengine.cmd' }, target => 120, +# json_encode => 1); + +#$client2->send_message(action => 'COMMAND', data => { cmd => 'ls' }, target => 140, +# json_encode => 1); +#$client2->send_message(action => 'CONSTATUS'); + +# It will transform +#$client2->send_message(action => 'GETLOG', data => { cmd => 'ls' }, target => 120, +# json_encode => 1); +#$client2->send_message(action => 'GETLOG', data => { cmd => 'ls' }, target => 140, +# json_encode => 1); + +#get_command_result(); + +#while (1) { +# my $poll = []; + +# $client->ping(poll => $poll); +# $client2->ping(poll => $poll); +# zmq_poll($poll, 5000); +#} + +while (1) { + my $poll = [$client->get_poll(), $client2->get_poll()]; + +# $client->ping(poll => $poll); +# $client2->ping(poll => $poll); + zmq_poll($poll, 5000); +} + +$client->close(); +$client2->close(); +exit(0); + +#zmq_close($requester); + From e47769747baeca20b539e4e5ede3eabc3bd67a58 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 3 Jul 2019 18:22:58 +0200 Subject: [PATCH 005/948] rename daemon --- .../{centreond => gorgone}/clientzmq.pm | 28 +- .../centreon/{centreond => gorgone}/common.pm | 26 +- .../{centreondcore.pm => gorgonecore.pm} | 244 +++++++++--------- ...ntreond-poller.ini => gorgoned-poller.ini} | 24 +- ...reond-poller2.ini => gorgoned-poller2.ini} | 20 +- .../config/{centreond.ini => gorgoned.ini} | 40 +-- gorgone/docs/guide.rst | 78 +++--- gorgone/{centreond => gorgoned} | 10 +- .../{centreondacl => gorgoneacl}/class.pm | 30 +-- .../{centreondacl => gorgoneacl}/hooks.pm | 34 +-- .../class.pm | 50 ++-- .../hooks.pm | 32 +-- .../{centreondcron => gorgonecron}/class.pm | 20 +- .../{centreondcron => gorgonecron}/hooks.pm | 34 +-- .../{centreondproxy => gorgoneproxy}/class.pm | 71 ++--- .../{centreondproxy => gorgoneproxy}/hooks.pm | 197 ++++++++------ .../{centreondpull => gorgonepull}/hooks.pm | 54 ++-- .../modules/{scom => gorgonescom}/class.pm | 32 +-- .../modules/{scom => gorgonescom}/hooks.pm | 42 +-- gorgone/schema/centreond_database.sql | 31 --- gorgone/schema/gorgone_database.sql | 31 +++ gorgone/test-client.pl | 8 +- 22 files changed, 589 insertions(+), 547 deletions(-) rename gorgone/centreon/{centreond => gorgone}/clientzmq.pm (83%) rename gorgone/centreon/{centreond => gorgone}/common.pm (91%) rename gorgone/centreon/script/{centreondcore.pm => gorgonecore.pm} (57%) rename gorgone/config/{centreond-poller.ini => gorgoned-poller.ini} (72%) rename gorgone/config/{centreond-poller2.ini => gorgoned-poller2.ini} (63%) rename gorgone/config/{centreond.ini => gorgoned.ini} (79%) rename gorgone/{centreond => gorgoned} (86%) rename gorgone/modules/{centreondacl => gorgoneacl}/class.pm (94%) rename gorgone/modules/{centreondacl => gorgoneacl}/hooks.pm (86%) rename gorgone/modules/{centreondaction => gorgoneaction}/class.pm (81%) rename gorgone/modules/{centreondaction => gorgoneaction}/hooks.pm (78%) rename gorgone/modules/{centreondcron => gorgonecron}/class.pm (82%) rename gorgone/modules/{centreondcron => gorgonecron}/hooks.pm (75%) rename gorgone/modules/{centreondproxy => gorgoneproxy}/class.pm (65%) rename gorgone/modules/{centreondproxy => gorgoneproxy}/hooks.pm (59%) rename gorgone/modules/{centreondpull => gorgonepull}/hooks.pm (60%) rename gorgone/modules/{scom => gorgonescom}/class.pm (85%) rename gorgone/modules/{scom => gorgonescom}/hooks.pm (81%) delete mode 100644 gorgone/schema/centreond_database.sql create mode 100644 gorgone/schema/gorgone_database.sql diff --git a/gorgone/centreon/centreond/clientzmq.pm b/gorgone/centreon/gorgone/clientzmq.pm similarity index 83% rename from gorgone/centreon/centreond/clientzmq.pm rename to gorgone/centreon/gorgone/clientzmq.pm index 1a4188bcd11..5744645e5b1 100644 --- a/gorgone/centreon/centreond/clientzmq.pm +++ b/gorgone/centreon/gorgone/clientzmq.pm @@ -18,11 +18,11 @@ # limitations under the License. # -package centreon::centreond::clientzmq; +package centreon::gorgone::clientzmq; use strict; use warnings; -use centreon::centreond::common; +use centreon::gorgone::common; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); @@ -39,7 +39,7 @@ sub new { $connector->{cipher} = $options{cipher}; $connector->{vector} = $options{vector}; $connector->{symkey} = undef; - $connector->{pubkey} = centreon::centreond::common::loadpubkey(pubkey => $options{pubkey}); + $connector->{pubkey} = centreon::gorgone::common::loadpubkey(pubkey => $options{pubkey}); $connector->{target_type} = $options{target_type}; $connector->{target_path} = $options{target_path}; $connector->{ping} = defined($options{ping}) ? $options{ping} : -1; @@ -57,10 +57,12 @@ sub init { my ($self, %options) = @_; $self->{handshake} = 0; - $sockets->{$self->{identity}} = centreon::centreond::common::connect_com(zmq_type => 'ZMQ_DEALER', name => $self->{identity}, - logger => $self->{logger}, - type => $self->{target_type}, - path => $self->{target_path}); + $sockets->{$self->{identity}} = centreon::gorgone::common::connect_com( + zmq_type => 'ZMQ_DEALER', name => $self->{identity}, + logger => $self->{logger}, + type => $self->{target_type}, + path => $self->{target_path} + ); $callbacks->{$self->{identity}} = $options{callback} if (defined($options{callback})); } @@ -128,11 +130,11 @@ sub event { } $connectors->{$options{identity}}->{ping_time} = time(); while (1) { - my $message = centreon::centreond::common::zmq_dealer_read_message(socket => $sockets->{$options{identity}}); + my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $sockets->{$options{identity}}); # in progress if ($connectors->{$options{identity}}->{handshake} == 0 || $connectors->{$options{identity}}->{handshake} == 1) { - my ($status, $symkey, $hostname) = centreon::centreond::common::client_get_secret(pubkey => $connectors->{$options{identity}}->{pubkey}, + my ($status, $symkey, $hostname) = centreon::gorgone::common::client_get_secret(pubkey => $connectors->{$options{identity}}->{pubkey}, message => $message); if ($status == -1) { $connectors->{$options{identity}}->{handshake} = 0; @@ -144,7 +146,7 @@ sub event { $connectors->{$options{identity}}->{logger}->writeLogInfo("Client connected successfuly to '" . $connectors->{$options{identity}}->{target_type} . '//' . $connectors->{$options{identity}}->{target_path}); } } else { - my ($status, $data) = centreon::centreond::common::uncrypt_message( + my ($status, $data) = centreon::gorgone::common::uncrypt_message( message => $message, cipher => $connectors->{$options{identity}}->{cipher}, vector => $connectors->{$options{identity}}->{vector}, @@ -161,7 +163,7 @@ sub event { } } - last unless (centreon::centreond::common::zmq_still_read(socket => $sockets->{$options{identity}})); + last unless (centreon::gorgone::common::zmq_still_read(socket => $sockets->{$options{identity}})); } } @@ -170,7 +172,7 @@ sub send_message { if ($self->{handshake} == 0) { my $message = '[HELO] [' . $self->{identity} . ']'; - my ($status, $ciphertext) = centreon::centreond::common::client_helo_encrypt( + my ($status, $ciphertext) = centreon::gorgone::common::client_helo_encrypt( pubkey => $self->{pubkey}, message => $message ); @@ -191,7 +193,7 @@ sub send_message { return (-1, 'Handshake issue'); } - centreon::centreond::common::zmq_send_message( + centreon::gorgone::common::zmq_send_message( socket => $sockets->{$self->{identity}}, cipher => $self->{cipher}, symkey => $self->{symkey}, diff --git a/gorgone/centreon/centreond/common.pm b/gorgone/centreon/gorgone/common.pm similarity index 91% rename from gorgone/centreon/centreond/common.pm rename to gorgone/centreon/gorgone/common.pm index ef048689e74..85e53714a68 100644 --- a/gorgone/centreon/centreond/common.pm +++ b/gorgone/centreon/gorgone/common.pm @@ -18,7 +18,7 @@ # limitations under the License. # -package centreon::centreond::common; +package centreon::gorgone::common; use strict; use warnings; @@ -129,7 +129,7 @@ sub zmq_core_response { } my $data = json_encode(data => { code => $options{code}, data => $options{data} }); - # We add 'target' for 'PONG', 'CONSTATUS'. Like that 'centreond-proxy can get it + # We add 'target' for 'PONG', 'CONSTATUS'. Like that 'gorgone-proxy can get it $msg = '[' . $response_type . '] [' . (defined($options{token}) ? $options{token} : '') . '] ' . ($response_type eq 'PONG' ? '[] ' : '') . $data; if (defined($options{cipher})) { @@ -242,7 +242,7 @@ sub is_client_can_connect { sub is_handshake_done { my (%options) = @_; - my ($status, $sth) = $options{dbh}->query("SELECT `key` FROM centreond_identity WHERE identity = " . $options{dbh}->quote($options{identity}) . " ORDER BY id DESC"); + my ($status, $sth) = $options{dbh}->query("SELECT `key` FROM gorgone_identity WHERE identity = " . $options{dbh}->quote($options{identity}) . " ORDER BY id DESC"); return if ($status == -1); if (my $row = $sth->fetchrow_hashref()) { return (1, pack('H*', $row->{key})); @@ -257,8 +257,8 @@ sub is_handshake_done { sub constatus { my (%options) = @_; - if (defined($options{centreond}->{modules_register}->{ $options{centreond}->{modules_id}->{$options{centreond_config}->{centreondcore}{proxy_name}} })) { - my $name = $options{centreond_config}->{$options{centreond_config}->{centreondcore}{proxy_name}}{module}; + if (defined($options{gorgone}->{modules_register}->{ $options{gorgone}->{modules_id}->{$options{gorgone_config}->{gorgonecore}{proxy_name}} })) { + my $name = $options{gorgone_config}->{$options{gorgone_config}->{gorgonecore}{proxy_name}}{module}; my $method; if (defined($name) && ($method = $name->can('get_constatus_result'))) { return (0, { action => 'constatus', mesage => 'ok', data => $method->() }, 'CONSTATUS'); @@ -271,7 +271,7 @@ sub constatus { sub ping { my (%options) = @_; - #my $status = add_history(dbh => $options{centreond}->{db_centreond}, + #my $status = add_history(dbh => $options{gorgone}->{db_gorgone}, # token => $options{token}, logger => $options{logger}, code => 0); return (0, { action => 'ping', mesage => 'ping ok', id => $options{id} }, 'PONG'); } @@ -287,7 +287,7 @@ sub putlog { return (1, { mesage => 'request not well formatted' }); } - my $status = add_history(dbh => $options{centreond}->{db_centreond}, + my $status = add_history(dbh => $options{gorgone}->{db_gorgone}, etime => $data->{etime}, token => $data->{token}, data => json_encode(data => $data->{data}, logger => $options{logger}), code => $data->{code}); if ($status == -1) { return (1, { mesage => 'database issue' }); @@ -311,7 +311,7 @@ sub getlog { foreach ((['id', '>'], ['token', '='], ['ctime', '>='], ['etime', '>'], ['code', '='])) { if (defined($data->{${$_}[0]}) && $data->{${$_}[0]} ne '') { - $filter .= $filter_append . ${$_}[0] . ' ' . ${$_}[1] . ' ' . $options{centreond}->{db_centreond}->quote($data->{${$_}[0]}); + $filter .= $filter_append . ${$_}[0] . ' ' . ${$_}[1] . ' ' . $options{gorgone}->{db_gorgone}->quote($data->{${$_}[0]}); $filter_append = ' AND '; } } @@ -320,12 +320,12 @@ sub getlog { return (1, { mesage => 'need at least one filter' }); } - my ($status, $sth) = $options{centreond}->{db_centreond}->query("SELECT * FROM centreond_history WHERE " . $filter); + my ($status, $sth) = $options{gorgone}->{db_gorgone}->query("SELECT * FROM gorgone_history WHERE " . $filter); if ($status == -1) { return (1, { mesage => 'database issue' }); } - return (0, { action => 'getlog', result => $sth->fetchall_hashref('id'), id => $options{centreond}->{id} }); + return (0, { action => 'getlog', result => $sth->fetchall_hashref('id'), id => $options{gorgone}->{id} }); } sub kill { @@ -340,14 +340,14 @@ sub kill { sub update_identity { my (%options) = @_; - my ($status, $sth) = $options{dbh}->query("UPDATE centreond_identity SET `ctime` = " . $options{dbh}->quote(time()) . " WHERE `identity` = " . $options{dbh}->quote($options{identity})); + my ($status, $sth) = $options{dbh}->query("UPDATE gorgone_identity SET `ctime` = " . $options{dbh}->quote(time()) . " WHERE `identity` = " . $options{dbh}->quote($options{identity})); return $status; } sub add_identity { my (%options) = @_; - my ($status, $sth) = $options{dbh}->query("INSERT INTO centreond_identity (`ctime`, `identity`, `key`) VALUES (" . + my ($status, $sth) = $options{dbh}->query("INSERT INTO gorgone_identity (`ctime`, `identity`, `key`) VALUES (" . $options{dbh}->quote(time()) . ", " . $options{dbh}->quote($options{identity}) . ", " . $options{dbh}->quote(unpack('H*', $options{symkey})) . ")"); return $status; } @@ -373,7 +373,7 @@ sub add_history { push @values, $options{dbh}->quote($options{$_}); } } - my ($status, $sth) = $options{dbh}->query("INSERT INTO centreond_history (" . join(',', @names) . ") VALUES (" . + my ($status, $sth) = $options{dbh}->query("INSERT INTO gorgone_history (" . join(',', @names) . ") VALUES (" . join(',', @values) . ")"); return $status; } diff --git a/gorgone/centreon/script/centreondcore.pm b/gorgone/centreon/script/gorgonecore.pm similarity index 57% rename from gorgone/centreon/script/centreondcore.pm rename to gorgone/centreon/script/gorgonecore.pm index 51cd5a8b562..277df145368 100644 --- a/gorgone/centreon/script/centreondcore.pm +++ b/gorgone/centreon/script/gorgonecore.pm @@ -18,7 +18,7 @@ # limitations under the License. # -package centreon::script::centreondcore; +package centreon::script::gorgonecore; use strict; use warnings; @@ -26,11 +26,11 @@ use POSIX ":sys_wait_h"; use Sys::Hostname; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); -use centreon::centreond::common; +use centreon::gorgone::common; use centreon::misc::db; use centreon::script; -my ($centreond, $centreond_config); +my ($gorgone, $gorgone_config); use base qw(centreon::script); @@ -39,7 +39,7 @@ my %handlers = (TERM => {}, HUP => {}, CHLD => {}); sub new { my $class = shift; - my $self = $class->SUPER::new('centreond', + my $self = $class->SUPER::new('gorgoned', centreon_db_conn => 0, centstorage_db_conn => 0, noconfig => 1 @@ -75,36 +75,38 @@ sub init { $self->{logger}->writeLogError("Can't find extra config file '$self->{opt_extra}'"); exit(1); } - $centreond_config = centreon::centreond::common::read_config(config_file => $self->{opt_extra}, - logger => $self->{logger}); - if (defined($centreond_config->{centreondcore}{external_com_type}) && $centreond_config->{centreondcore}{external_com_type} ne '') { - centreon::centreond::common::loadprivkey(logger => $self->{logger}, privkey => $centreond_config->{centreondcore}{privkey}); + $gorgone_config = centreon::gorgone::common::read_config( + config_file => $self->{opt_extra}, + logger => $self->{logger} + ); + if (defined($gorgone_config->{gorgonecore}{external_com_type}) && $gorgone_config->{gorgonecore}{external_com_type} ne '') { + centreon::gorgone::common::loadprivkey(logger => $self->{logger}, privkey => $gorgone_config->{gorgonecore}{privkey}); } # Database connections: - # We add in centreond database - $centreond->{db_centreond} = centreon::misc::db->new( - type => $centreond_config->{centreondcore}{centreond_db_type}, - db => $centreond_config->{centreondcore}{centreond_db_name}, - host => $centreond_config->{centreondcore}{centreond_db_host}, - port => $centreond_config->{centreondcore}{centreond_db_port}, - user => $centreond_config->{centreondcore}{centreond_db_user}, - password => $centreond_config->{centreondcore}{centreond_db_password}, + # We add in gorgone database + $gorgone->{db_gorgone} = centreon::misc::db->new( + type => $gorgone_config->{gorgonecore}{gorgone_db_type}, + db => $gorgone_config->{gorgonecore}{gorgone_db_name}, + host => $gorgone_config->{gorgonecore}{gorgone_db_host}, + port => $gorgone_config->{gorgonecore}{gorgone_db_port}, + user => $gorgone_config->{gorgonecore}{gorgone_db_user}, + password => $gorgone_config->{gorgonecore}{gorgone_db_password}, force => 2, - logger => $centreond->{logger} + logger => $gorgone->{logger} ); - $centreond->{db_centreond}->set_inactive_destroy(); - if ($centreond->{db_centreond}->connect() == -1) { - $centreond->{logger}->writeLogInfo("Cannot connect. We quit!!"); + $gorgone->{db_gorgone}->set_inactive_destroy(); + if ($gorgone->{db_gorgone}->connect() == -1) { + $gorgone->{logger}->writeLogInfo("Cannot connect. We quit!!"); exit(1); } - $self->{hostname} = $centreond_config->{centreondcore}{hostname}; + $self->{hostname} = $gorgone_config->{gorgonecore}{hostname}; if (!defined($self->{hostname}) || $self->{hostname} eq '') { $self->{hostname} = hostname(); } - $self->{id} = $centreond_config->{centreondcore}{id}; + $self->{id} = $gorgone_config->{gorgonecore}{id}; if (!defined($self->{hostname}) || $self->{hostname} eq '') { #$self->{id} = get_poller_id(dbh => $dbh, name => $self->{hostname}); } @@ -174,10 +176,10 @@ sub handle_CHLD { sub load_modules { my $self = shift; - foreach my $section (keys %{$centreond_config}) { - next if (!defined($centreond_config->{$section}{module})); + foreach my $section (keys %{$gorgone_config}) { + next if (!defined($gorgone_config->{$section}{module})); - my $name = $centreond_config->{$section}{module}; + my $name = $gorgone_config->{$section}{module}; (my $file = "$name.pm") =~ s{::}{/}g; require $file; $self->{logger}->writeLogInfo("Module '$section' is loading"); @@ -191,10 +193,10 @@ sub load_modules { } my ($events, $id) = $self->{modules_register}->{$name}->{register}->( - config => $centreond_config->{$section}, - config_core => $centreond_config->{centreondcore}, - config_db_centreon => $centreond_config->{db_centreon}, - config_db_centstorage => $centreond_config->{db_centstorage} + config => $gorgone_config->{$section}, + config_core => $gorgone_config->{gorgonecore}, + config_db_centreon => $gorgone_config->{db_centreon}, + config_db_centstorage => $gorgone_config->{db_centstorage} ); $self->{modules_id}->{$id} = $name; foreach my $event (@{$events}) { @@ -207,7 +209,7 @@ sub load_modules { # Load internal functions foreach my $method_name (('putlog', 'getlog', 'kill', 'ping', 'constatus')) { - unless ($self->{internal_register}->{$method_name} = centreon::centreond::common->can($method_name)) { + unless ($self->{internal_register}->{$method_name} = centreon::gorgone::common->can($method_name)) { $self->{logger}->writeLogError("No function '$method_name'"); exit(1); } @@ -222,8 +224,8 @@ sub message_run { } my ($action, $token, $target, $data) = ($1, $2, $3, $4); if ($action !~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS)$/ && !defined($self->{modules_events}->{$action})) { - centreon::centreond::common::add_history( - dbh => $self->{db_centreond}, + centreon::gorgone::common::add_history( + dbh => $self->{db_gorgone}, code => 1, token => $token, data => { msg => "action '$action' is not known" }, json_encode => 1 @@ -231,25 +233,25 @@ sub message_run { return (undef, 1, { message => "action '$action' is not known" }); } if (!defined($token) || $token eq '') { - $token = centreon::centreond::common::generate_token(); + $token = centreon::gorgone::common::generate_token(); } if ($self->{stop} == 1) { - centreon::centreond::common::add_history( - dbh => $self->{db_centreond}, + centreon::gorgone::common::add_history( + dbh => $self->{db_gorgone}, code => 1, token => $token, - data => { msg => 'centreond is stopping/restarting. Not proceed request.' }, + data => { msg => 'gorgone is stopping/restarting. Not proceed request.' }, json_encode => 1 ); - return ($token, 1, { message => "centreond is stopping/restarting. Not proceed request." }); + return ($token, 1, { message => "gorgone is stopping/restarting. Not proceed request." }); } # Check Routing if (defined($target) && $target ne '') { # Check if not myself ;) if ($target ne $self->{id}) { - $self->{modules_register}->{ $self->{modules_id}->{$centreond_config->{centreondcore}{proxy_name}} }->{routing}->( - socket => $self->{internal_socket}, dbh => $self->{db_centreond}, logger => $self->{logger}, + $self->{modules_register}->{ $self->{modules_id}->{$gorgone_config->{gorgonecore}{proxy_name}} }->{routing}->( + socket => $self->{internal_socket}, dbh => $self->{db_gorgone}, logger => $self->{logger}, action => $1, token => $token, target => $target, data => $data, hostname => $self->{hostname} ); @@ -259,8 +261,8 @@ sub message_run { if ($action =~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS)$/) { my ($code, $response, $response_type) = $self->{internal_register}->{lc($action)}->( - centreond => $self, - centreond_config => $centreond_config, + gorgone => $self, + gorgone_config => $gorgone_config, id => $self->{id}, data => $data, token => $token, @@ -271,7 +273,7 @@ sub message_run { foreach (@{$self->{modules_events}->{$action}}) { $self->{modules_register}->{$_}->{routing}->( socket => $self->{internal_socket}, - dbh => $self->{db_centreond}, logger => $self->{logger}, + dbh => $self->{db_gorgone}, logger => $self->{logger}, action => $1, token => $token, target => $target, data => $data, hostname => $self->{hostname} ); @@ -282,33 +284,33 @@ sub message_run { sub router_internal_event { while (1) { - my ($identity, $message) = centreon::centreond::common::zmq_read_message(socket => $centreond->{internal_socket}); - my ($token, $code, $response, $response_type) = $centreond->message_run(message => $message); - centreon::centreond::common::zmq_core_response( - socket => $centreond->{internal_socket}, + my ($identity, $message) = centreon::gorgone::common::zmq_read_message(socket => $gorgone->{internal_socket}); + my ($token, $code, $response, $response_type) = $gorgone->message_run(message => $message); + centreon::gorgone::common::zmq_core_response( + socket => $gorgone->{internal_socket}, identity => $identity, response_type => $response_type, data => $response, code => $code, token => $token ); - last unless (centreon::centreond::common::zmq_still_read(socket => $centreond->{internal_socket})); + last unless (centreon::gorgone::common::zmq_still_read(socket => $gorgone->{internal_socket})); } } sub handshake { my ($self, %options) = @_; - my ($identity, $message) = centreon::centreond::common::zmq_read_message(socket => $self->{external_socket}); - my ($status, $key) = centreon::centreond::common::is_handshake_done(dbh => $self->{db_centreond}, identity => $identity); + my ($identity, $message) = centreon::gorgone::common::zmq_read_message(socket => $self->{external_socket}); + my ($status, $key) = centreon::gorgone::common::is_handshake_done(dbh => $self->{db_gorgone}, identity => $identity); if ($status == 1) { - ($status, my $response) = centreon::centreond::common::uncrypt_message( - cipher => $centreond_config->{centreondcore}{cipher}, + ($status, my $response) = centreon::gorgone::common::uncrypt_message( + cipher => $gorgone_config->{gorgonecore}{cipher}, message => $message, symkey => $key, - vector => $centreond_config->{centreondcore}{vector} + vector => $gorgone_config->{gorgonecore}{vector} ); if ($status == 0 && $response =~ /^\[.*\]/) { - centreon::centreond::common::update_identity(dbh => $self->{db_centreond}, identity => $identity); + centreon::gorgone::common::update_identity(dbh => $self->{db_gorgone}, identity => $identity); return ($identity, $key, $response); } @@ -317,7 +319,7 @@ sub handshake { } if ($status == -1) { - centreon::centreond::common::zmq_core_response( + centreon::gorgone::common::zmq_core_response( socket => $self->{external_socket}, identity => $identity, code => 1, @@ -326,34 +328,34 @@ sub handshake { return undef; } elsif ($status == 0) { # We try to uncrypt - if (centreon::centreond::common::is_client_can_connect(message => $message, + if (centreon::gorgone::common::is_client_can_connect(message => $message, logger => $self->{logger}) == -1) { - centreon::centreond::common::zmq_core_response( + centreon::gorgone::common::zmq_core_response( socket => $self->{external_socket}, identity => $identity, code => 1, data => { message => 'handshake issue' } ); } - my ($status, $symkey) = centreon::centreond::common::generate_symkey( + my ($status, $symkey) = centreon::gorgone::common::generate_symkey( logger => $self->{logger}, - cipher => $centreond_config->{centreondcore}{cipher}, - keysize => $centreond_config->{centreondcore}{keysize} + cipher => $gorgone_config->{gorgonecore}{cipher}, + keysize => $gorgone_config->{gorgonecore}{keysize} ); if ($status == -1) { - centreon::centreond::common::zmq_core_response( + centreon::gorgone::common::zmq_core_response( socket => $self->{external_socket}, identity => $identity, code => 1, data => { message => 'handshake issue' } ); } - if (centreon::centreond::common::add_identity(dbh => $self->{db_centreond}, identity => $identity, symkey => $symkey) == -1) { - centreon::centreond::common::zmq_core_response( + if (centreon::gorgone::common::add_identity(dbh => $self->{db_gorgone}, identity => $identity, symkey => $symkey) == -1) { + centreon::gorgone::common::zmq_core_response( socket => $self->{external_socket}, identity => $identity, code => 1, data => { message => 'handshake issue' } ); } - if (centreon::centreond::common::zmq_core_key_response(logger => $self->{logger}, socket => $self->{external_socket}, identity => $identity, + if (centreon::gorgone::common::zmq_core_key_response(logger => $self->{logger}, socket => $self->{external_socket}, identity => $identity, hostname => $self->{hostname}, symkey => $symkey) == -1) { - centreon::centreond::common::zmq_core_response( + centreon::gorgone::common::zmq_core_response( socket => $self->{external_socket}, identity => $identity, code => 1, data => { message => 'handshake issue' } ); @@ -364,20 +366,20 @@ sub handshake { sub router_external_event { while (1) { - my ($identity, $key, $message) = $centreond->handshake(); + my ($identity, $key, $message) = $gorgone->handshake(); if (defined($message)) { - my ($token, $code, $response, $response_type) = $centreond->message_run(message => $message); - centreon::centreond::common::zmq_core_response( - socket => $centreond->{external_socket}, + my ($token, $code, $response, $response_type) = $gorgone->message_run(message => $message); + centreon::gorgone::common::zmq_core_response( + socket => $gorgone->{external_socket}, identity => $identity, response_type => $response_type, - cipher => $centreond_config->{centreondcore}{cipher}, - vector => $centreond_config->{centreondcore}{vector}, + cipher => $gorgone_config->{gorgonecore}{cipher}, + vector => $gorgone_config->{gorgonecore}{vector}, symkey => $key, token => $token, code => $code, data => $response ); } - last unless (centreon::centreond::common::zmq_still_read(socket => $centreond->{external_socket})); + last unless (centreon::gorgone::common::zmq_still_read(socket => $gorgone->{external_socket})); } } @@ -390,7 +392,7 @@ sub waiting_ready_pool { foreach my $pool_id (keys %{$options{pool}}) { return 1 if ($options{pool}->{$pool_id}->{ready} == 1); } - zmq_poll($centreond->{poll}, 5000); + zmq_poll($gorgone->{poll}, 5000); } foreach my $pool_id (keys %{$options{pool}}) { return 1 if ($options{pool}->{$pool_id}->{ready} == 1); @@ -408,7 +410,7 @@ sub waiting_ready { # We wait 10 seconds while (${$options{ready}} == 0 && time() - $time < 10) { - zmq_poll($centreond->{poll}, 5000); + zmq_poll($gorgone->{poll}, 5000); } if (${$options{ready}} == 0) { @@ -421,9 +423,9 @@ sub waiting_ready { sub clean_sessions { my ($self, %options) = @_; - if ($self->{sessions_timer} - time() > $centreond_config->{centreondcore}{purge_sessions_time}) { + if ($self->{sessions_timer} - time() > $gorgone_config->{gorgonecore}{purge_sessions_time}) { $self->{logger}->writeLogInfo("purge sessions in progress..."); - $self->{db_centreond}->query("DELETE FROM centreond_identity WHERE `ctime` < " . $self->{db_centreond}->quote(time() - $centreond_config->{centreondcore}{sessions_time})); + $self->{db_gorgone}->query("DELETE FROM gorgone_identity WHERE `ctime` < " . $self->{db_gorgone}->quote(time() - $gorgone_config->{gorgonecore}{sessions_time})); $self->{sessions_timer} = time(); } } @@ -433,107 +435,107 @@ sub quit { $self->{logger}->writeLogInfo("Quit main process"); zmq_close($self->{internal_socket}); - if (defined($centreond_config->{centreondcore}{external_com_type}) && $centreond_config->{centreondcore}{external_com_type} ne '') { + if (defined($gorgone_config->{gorgonecore}{external_com_type}) && $gorgone_config->{gorgonecore}{external_com_type} ne '') { zmq_close($self->{external_socket}); } exit(0); } sub run { - $centreond = shift; + $gorgone = shift; - $centreond->SUPER::run(); - $centreond->{logger}->redirect_output(); + $gorgone->SUPER::run(); + $gorgone->{logger}->redirect_output(); - $centreond->{logger}->writeLogDebug("centreond launched...."); - $centreond->{logger}->writeLogDebug("PID: $$"); + $gorgone->{logger}->writeLogDebug("gorgoned launched...."); + $gorgone->{logger}->writeLogDebug("PID: $$"); - if (centreon::centreond::common::add_history(dbh => $centreond->{db_centreond}, + if (centreon::gorgone::common::add_history(dbh => $gorgone->{db_gorgone}, code => 0, - data => { msg => 'centreond is starting...' }, + data => { msg => 'gorgoned is starting...' }, json_encode => 1) == -1) { - $centreond->{logger}->writeLogInfo("Cannot write in history. We quit!!"); + $gorgone->{logger}->writeLogInfo("Cannot write in history. We quit!!"); exit(1); } - $centreond->{internal_socket} = centreon::centreond::common::create_com( - type => $centreond_config->{centreondcore}{internal_com_type}, - path => $centreond_config->{centreondcore}{internal_com_path}, + $gorgone->{internal_socket} = centreon::gorgone::common::create_com( + type => $gorgone_config->{gorgonecore}{internal_com_type}, + path => $gorgone_config->{gorgonecore}{internal_com_path}, zmq_type => 'ZMQ_ROUTER', name => 'router-internal', - logger => $centreond->{logger} + logger => $gorgone->{logger} ); - if (defined($centreond_config->{centreondcore}{external_com_type}) && $centreond_config->{centreondcore}{external_com_type} ne '') { - $centreond->{external_socket} = centreon::centreond::common::create_com( - type => $centreond_config->{centreondcore}{external_com_type}, - path => $centreond_config->{centreondcore}{external_com_path}, + if (defined($gorgone_config->{gorgonecore}{external_com_type}) && $gorgone_config->{gorgonecore}{external_com_type} ne '') { + $gorgone->{external_socket} = centreon::gorgone::common::create_com( + type => $gorgone_config->{gorgonecore}{external_com_type}, + path => $gorgone_config->{gorgonecore}{external_com_path}, zmq_type => 'ZMQ_ROUTER', name => 'router-external', - logger => $centreond->{logger} + logger => $gorgone->{logger} ); } # Initialize poll set - $centreond->{poll} = [ + $gorgone->{poll} = [ { - socket => $centreond->{internal_socket}, + socket => $gorgone->{internal_socket}, events => ZMQ_POLLIN, callback => \&router_internal_event, } ]; - if (defined($centreond->{external_socket})) { - push @{$centreond->{poll}}, { - socket => $centreond->{external_socket}, + if (defined($gorgone->{external_socket})) { + push @{$gorgone->{poll}}, { + socket => $gorgone->{external_socket}, events => ZMQ_POLLIN, callback => \&router_external_event, }; } # init all modules - foreach my $name (keys %{$centreond->{modules_register}}) { - $centreond->{logger}->writeLogInfo("Call init function from module '$name'"); - $centreond->{modules_register}->{$name}->{init}->( - logger => $centreond->{logger}, id => $centreond->{id}, - poll => $centreond->{poll}, - external_socket => $centreond->{external_socket}, - internal_socket => $centreond->{internal_socket}, - dbh => $centreond->{db_centreond} + foreach my $name (keys %{$gorgone->{modules_register}}) { + $gorgone->{logger}->writeLogInfo("Call init function from module '$name'"); + $gorgone->{modules_register}->{$name}->{init}->( + logger => $gorgone->{logger}, id => $gorgone->{id}, + poll => $gorgone->{poll}, + external_socket => $gorgone->{external_socket}, + internal_socket => $gorgone->{internal_socket}, + dbh => $gorgone->{db_gorgone} ); } - $centreond->{logger}->writeLogInfo("[Server accepting clients]"); + $gorgone->{logger}->writeLogInfo("[Server accepting clients]"); while (1) { my $count = 0; - my $poll = [@{$centreond->{poll}}]; + my $poll = [@{$gorgone->{poll}}]; - foreach my $name (keys %{$centreond->{modules_register}}) { - $count += $centreond->{modules_register}->{$name}->{check}->( - logger => $centreond->{logger}, - dead_childs => $centreond->{return_child}, - internal_socket => $centreond->{internal_socket}, - dbh => $centreond->{db_centreond}, + foreach my $name (keys %{$gorgone->{modules_register}}) { + $count += $gorgone->{modules_register}->{$name}->{check}->( + logger => $gorgone->{logger}, + dead_childs => $gorgone->{return_child}, + internal_socket => $gorgone->{internal_socket}, + dbh => $gorgone->{db_gorgone}, poll => $poll ); } - if ($centreond->{stop} == 1) { + if ($gorgone->{stop} == 1) { # No childs if ($count == 0) { - $centreond->quit(); + $gorgone->quit(); } # Send KILL - if (time() - $centreond->{kill_timer} > $centreond_config->{centreondcore}{timeout}) { - foreach my $name (keys %{$centreond->{modules_register}}) { - $centreond->{modules_register}->{$name}->{kill_internal}->(logger => $centreond->{logger}); + if (time() - $gorgone->{kill_timer} > $gorgone_config->{gorgonecore}{timeout}) { + foreach my $name (keys %{$gorgone->{modules_register}}) { + $gorgone->{modules_register}->{$name}->{kill_internal}->(logger => $gorgone->{logger}); } - $centreond->quit(); + $gorgone->quit(); } } zmq_poll($poll, 5000); - $centreond->clean_sessions(); + $gorgone->clean_sessions(); } } diff --git a/gorgone/config/centreond-poller.ini b/gorgone/config/gorgoned-poller.ini similarity index 72% rename from gorgone/config/centreond-poller.ini rename to gorgone/config/gorgoned-poller.ini index cea899e09cc..5625eafddfa 100644 --- a/gorgone/config/centreond-poller.ini +++ b/gorgone/config/gorgoned-poller.ini @@ -1,14 +1,14 @@ -[centreondcore] +[gorgonecore] internal_com_type=ipc -internal_com_path=/tmp/centreond/routing-poller.ipc +internal_com_path=/tmp/gorgone/routing-poller.ipc ; in seconds before sending kill signals (not gently) timeout=50 -centreond_db_type=SQLite -centreond_db_name=dbname=/tmp/centreond_poller.sdb -centreond_db_host= -centreond_db_port= -centreond_db_user= -centreond_db_password= +gorgone_db_type=SQLite +gorgone_db_name=dbname=/tmp/gorgone_poller.sdb +gorgone_db_host= +gorgone_db_port= +gorgone_db_user= +gorgone_db_password= ; If not set. Use 'hostname' function. hostname= ; If not set. Try from 'hostname' in database @@ -25,8 +25,8 @@ id=120 sessions_time=86400 purge_sessions_time=3600 -[centreondpull] -module=modules::centreondpull::hooks +[gorgonepull] +module=modules::gorgonepull::hooks ; ID used (should be the poller ID) target_type=tcp target_path=127.0.0.1:5555 @@ -42,8 +42,8 @@ vector=0123456789012345 ping=60 ping_timeout=30 -[centreondaction] -module=modules::centreondaction::hooks +[gorgoneaction] +module=modules::gorgoneaction::hooks disable_command_event=0 disable_enginecomand_event=0 enginecommand_timeout=30 diff --git a/gorgone/config/centreond-poller2.ini b/gorgone/config/gorgoned-poller2.ini similarity index 63% rename from gorgone/config/centreond-poller2.ini rename to gorgone/config/gorgoned-poller2.ini index fd602ba7088..1faf5681e9f 100644 --- a/gorgone/config/centreond-poller2.ini +++ b/gorgone/config/gorgoned-poller2.ini @@ -1,16 +1,16 @@ -[centreondcore] +[gorgonecore] internal_com_type=ipc -internal_com_path=/tmp/centreond/routing-poller.ipc +internal_com_path=/tmp/gorgone/routing-poller.ipc external_com_type=tcp external_com_path=*:5556 ; in seconds before sending kill signals (not gently) timeout=50 -centreond_db_type=SQLite -centreond_db_name=dbname=/tmp/centreond_poller2.sdb -centreond_db_host= -centreond_db_port= -centreond_db_user= -centreond_db_password= +gorgone_db_type=SQLite +gorgone_db_name=dbname=/tmp/gorgone_poller2.sdb +gorgone_db_host= +gorgone_db_port= +gorgone_db_user= +gorgone_db_password= ; If not set. Use 'hostname' function. hostname= ; If not set. Try from 'hostname' in database @@ -27,5 +27,5 @@ vector=0123456789012345 sessions_time=86400 purge_sessions_time=3600 -[centreondaction] -module=modules::centreondaction::hooks +[gorgoneaction] +module=modules::gorgoneaction::hooks diff --git a/gorgone/config/centreond.ini b/gorgone/config/gorgoned.ini similarity index 79% rename from gorgone/config/centreond.ini rename to gorgone/config/gorgoned.ini index ab086046489..a1abc00cc9f 100644 --- a/gorgone/config/centreond.ini +++ b/gorgone/config/gorgoned.ini @@ -10,20 +10,20 @@ dsn="mysql:host=localhost;dbname=centreon_storage" username=centreon password=centreon -; centreond -[centreondcore] +; gorgoned +[gorgonecore] internal_com_type=ipc -internal_com_path=/tmp/centreond/routing.ipc +internal_com_path=/tmp/gorgone/routing.ipc external_com_type=tcp external_com_path=*:5555 ; in seconds before sending kill signals (not gently) timeout=50 -centreond_db_type=SQLite -centreond_db_name=dbname=/tmp/centreond.sdb -centreond_db_host= -centreond_db_port= -centreond_db_user= -centreond_db_password= +gorgone_db_type=SQLite +gorgone_db_name=dbname=/tmp/gorgone.sdb +gorgone_db_host= +gorgone_db_port= +gorgone_db_user= +gorgone_db_password= ; If not set. Use 'hostname' function. hostname= ; If not set. Try from 'hostname' in database @@ -40,10 +40,10 @@ vector=0123456789012345 sessions_time=86400 purge_sessions_time=3600 ; shouldn't be changed -proxy_name=centreondproxy +proxy_name=gorgoneproxy -;[centreondacl] -;module=modules::centreondacl::hooks +;[gorgoneacl] +;module=modules::gorgoneacl::hooks ;on_demand=1 ;; How much to keep open in seconds without event received ;on_demand_time=60 @@ -58,11 +58,11 @@ proxy_name=centreondproxy ;sql_fetch=10000 ;sql_bulk=2000 -[centreondcron] -module=modules::centreondcron::hooks +[gorgonecron] +module=modules::gorgonecron::hooks -;[centreondproxy] -;module=modules::centreondproxy::hooks +;[gorgoneproxy] +;module=modules::gorgoneproxy::hooks ;pool=5 ;; sync history each 5 minutes ;synchistory_time=300 @@ -71,11 +71,11 @@ module=modules::centreondcron::hooks ;; ping each X seconds ;ping=60 -;[centreondaction] -;module=modules::centreondaction::hooks +;[gorgoneaction] +;module=modules::gorgoneaction::hooks -[scom] -module=modules::scom::hooks +[gorgonescom] +module=modules::gorgonescom::hooks ; in seconds - do purge for container also check_containers_time=3600 diff --git a/gorgone/docs/guide.rst b/gorgone/docs/guide.rst index e26a322e18a..092058ec1e3 100644 --- a/gorgone/docs/guide.rst +++ b/gorgone/docs/guide.rst @@ -2,12 +2,12 @@ Description *********** -"centreond" is a daemon which handles some tasks. You can plug-in some modules: +"gorgoned" is a daemon which handles some tasks. You can plug-in some modules: -* centreond-action: execute commands, send files/directories -* centreond-cron: schedule tasks -* centreond-acl: manage centreon ACL -* centreond-proxy: push tasks (to another "centreond" instance) or execute (through SSH) +* gorgone-action: execute commands, send files/directories +* gorgone-cron: schedule tasks +* gorgone-acl: manage centreon ACL +* gorgone-proxy: push tasks (to another "gorgoned" instance) or execute (through SSH) The daemon is installed on centreon central server and also poller server. It uses zeromq library. @@ -40,19 +40,19 @@ Create sqlite database: :: - # sqlite3 -init schema/centreond_database.sql /tmp/centreond.sdb + # sqlite3 -init schema/gorgone_database.sql /tmp/gorgone.sdb To execute the daemon: :: - # perl centreond --config-extra=centreond.ini --severity=debug + # perl gorgoned --config-extra=config/gorgoned.ini --severity=debug -****************** -centreond protocol -****************** +**************** +gorgone protocol +**************** -"centreond-core" (main mandatory module) can have 2 interfaces: +"gorgone-core" (main mandatory module) can have 2 interfaces: * internal: uncrypted dialog (used by internal modules. Commonly in ipc) * external: crypted dialog (used by third-party clients. Commonly in tcp) @@ -106,7 +106,7 @@ After a successful handshake, client requests uses the following syntax: * ACTION: the request. For example: COMMAND, ENGINECOMMAND,... It depends of the target server capabilites * TOKEN: Can be used to create some "sessions". If empty, the server creates an uniq token for each requests -* TARGET: which "centreond" must execute the request. With the following option, you can execute a command to a specific server through another. The poller id is needed. If empty, the server (which is connected with the client) is the target. +* TARGET: which "gorgoned" must execute the request. With the following option, you can execute a command to a specific server through another. The poller id is needed. If empty, the server (which is connected with the client) is the target. * DATA: json stream. It depends of the request For each client requests, the server get an immediate response: @@ -130,7 +130,7 @@ Core requests CONSTATUS --------- -The following request gives you a table with the last ping response of "centreond" nodes connected to the server. +The following request gives you a table with the last ping response of "gorgoned" nodes connected to the server. The command is useful to know if some pollers are disconnected. The client request: @@ -169,7 +169,7 @@ An example of the json stream: GETLOG ------ -The following request gives you the capability to follow your requests. "centreond" protocol is asynchronous. +The following request gives you the capability to follow your requests. "gorgone" protocol is asynchronous. An example: when you request a command execution, the server gives you a direct response and a token. These token can be used to know what happened to your command. The client request: @@ -222,7 +222,7 @@ An example of the json stream: } } -Each 'centreond' nodes store its logs. But every 5 minutes (by default), the central server gets the new logs of its connected nodes and stores it. +Each 'gorgoned' nodes store its logs. But every 5 minutes (by default), the central server gets the new logs of its connected nodes and stores it. A client can force a synchronization with the following request: :: @@ -244,9 +244,9 @@ The client request: module requests =============== -------------- -centreond-acl -------------- +----------- +gorgone-acl +----------- Common code responses: @@ -394,9 +394,9 @@ Example: The following action should be used when you want to rebuild an entire organization. You can rebuild a specific 'acl_resource' group if you set it. ----------------- -centreond-action ----------------- +-------------- +gorgone-action +-------------- COMMAND ^^^^^^^ @@ -449,17 +449,17 @@ FAQ Which modules should i enable ? =============================== -A poller with centreond should have the following modules: +A poller with gorgoned should have the following modules: -* centreond-action -* centreond-pull: if the connection to the central should be opened by the poller +* gorgone-action +* gorgone-pull: if the connection to the central should be opened by the poller -A central with centreond should have the following modules: +A central with gorgoned should have the following modules: -* centreond-acl -* centreond-action -* centreond-proxy -* centreond-cron +* gorgone-acl +* gorgone-action +* gorgone-proxy +* gorgone-cron ================================================= I want to create a client. How should i proceed ? @@ -488,16 +488,16 @@ Database scheme :: - CREATE TABLE IF NOT EXISTS `centreond_identity` ( + CREATE TABLE IF NOT EXISTS `gorgone_identity` ( `id` INTEGER PRIMARY KEY, `ctime` int(11) DEFAULT NULL, `identity` varchar(2048) DEFAULT NULL, `key` varchar(4096) DEFAULT NULL ); - CREATE INDEX IF NOT EXISTS idx_centreond_identity_identity ON centreond_identity (identity); + CREATE INDEX IF NOT EXISTS idx_gorgone_identity_identity ON gorgone_identity (identity); - CREATE TABLE IF NOT EXISTS `centreond_history` ( + CREATE TABLE IF NOT EXISTS `gorgone_history` ( `id` INTEGER PRIMARY KEY, `token` varchar(255) DEFAULT NULL, `code` int(11) DEFAULT NULL, @@ -506,16 +506,16 @@ Database scheme `data` TEXT DEFAULT NULL ); - CREATE INDEX IF NOT EXISTS idx_centreond_history_id ON centreond_history (id); - CREATE INDEX IF NOT EXISTS idx_centreond_history_token ON centreond_history (token); - CREATE INDEX IF NOT EXISTS idx_centreond_history_etime ON centreond_history (etime); - CREATE INDEX IF NOT EXISTS idx_centreond_history_code ON centreond_history (code); - CREATE INDEX IF NOT EXISTS idx_centreond_history_ctime ON centreond_history (ctime); + CREATE INDEX IF NOT EXISTS idx_gorgone_history_id ON gorgone_history (id); + CREATE INDEX IF NOT EXISTS idx_gorgone_history_token ON gorgone_history (token); + CREATE INDEX IF NOT EXISTS idx_gorgone_history_etime ON gorgone_history (etime); + CREATE INDEX IF NOT EXISTS idx_gorgone_history_code ON gorgone_history (code); + CREATE INDEX IF NOT EXISTS idx_gorgone_history_ctime ON gorgone_history (ctime); - CREATE TABLE IF NOT EXISTS `centreond_synchistory` ( + CREATE TABLE IF NOT EXISTS `gorgone_synchistory` ( `id` int(11) DEFAULT NULL, `ctime` int(11) DEFAULT NULL, `last_id` int(11) DEFAULT NULL ); - CREATE INDEX IF NOT EXISTS idx_centreond_synchistory_id ON centreond_synchistory (id); + CREATE INDEX IF NOT EXISTS idx_gorgone_synchistory_id ON gorgone_synchistory (id); diff --git a/gorgone/centreond b/gorgone/gorgoned similarity index 86% rename from gorgone/centreond rename to gorgone/gorgoned index 48801b10668..6287d2e8284 100644 --- a/gorgone/centreond +++ b/gorgone/gorgoned @@ -20,21 +20,21 @@ # use warnings; -use centreon::script::centreondcore; +use centreon::script::gorgonecore; use FindBin; use lib "$FindBin::Bin"; -centreon::script::centreondcore->new()->run(); +centreon::script::gorgonecore->new()->run(); __END__ =head1 NAME -centreond - a daemon to handle so many things. +gorgoned - a daemon to handle so many things. =head1 SYNOPSIS -centreond [options] +gorgoned [options] =head1 OPTIONS @@ -52,6 +52,6 @@ Print a brief help message and exits. =head1 DESCRIPTION -B will +B will =cut diff --git a/gorgone/modules/centreondacl/class.pm b/gorgone/modules/gorgoneacl/class.pm similarity index 94% rename from gorgone/modules/centreondacl/class.pm rename to gorgone/modules/gorgoneacl/class.pm index 1655967c0e5..9671093d325 100644 --- a/gorgone/modules/centreondacl/class.pm +++ b/gorgone/modules/gorgoneacl/class.pm @@ -18,11 +18,11 @@ # limitations under the License. # -package modules::centreondacl::class; +package modules::gorgoneacl::class; use strict; use warnings; -use centreon::centreond::common; +use centreon::gorgone::common; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); use JSON; @@ -65,7 +65,7 @@ sub handle_HUP { sub handle_TERM { my $self = shift; - $self->{logger}->writeLogInfo("centreond-acl $$ Receiving order to stop..."); + $self->{logger}->writeLogInfo("gorgone-acl $$ Receiving order to stop..."); $self->{stop} = 1; } @@ -151,7 +151,7 @@ sub acl_resource_list_hs { $filters->{hosts} . ')'; } - $self->{logger}->writeLogDebug("centreondacl: request: " . join(' UNION ALL ', @{$requests})); + $self->{logger}->writeLogDebug("gorgoneacl: request: " . join(' UNION ALL ', @{$requests})); return 2 if (scalar(@{$requests}) == 0); return $self->{class_object}->custom_execute(request => join(' UNION ALL ', @{$requests}), mode => 0); } @@ -324,21 +324,21 @@ sub action_aclresync { my ($self, %options) = @_; my ($status, $sth, $resource_configs); - $self->{logger}->writeLogDebug("centreondacl: organization $self->{organization_id} : begin resync"); + $self->{logger}->writeLogDebug("gorgoneacl: organization $self->{organization_id} : begin resync"); ($status, $resource_configs) = $self->acl_get_resources_config(); if ($status) { return 1; } foreach my $acl_resource_id (sort keys %{$resource_configs}) { - $self->{logger}->writeLogDebug("centreondacl: organization $self->{organization_id} acl resource $acl_resource_id : begin resync"); + $self->{logger}->writeLogDebug("gorgoneacl: organization $self->{organization_id} acl resource $acl_resource_id : begin resync"); ($status, $sth) = $self->acl_resource_list_hs(resource_config => $resource_configs->{$acl_resource_id}); if ($status == -1) { return 1; } $status = $self->insert_result(acl_resource_id => $acl_resource_id, hs_sth => $sth, first_request => "DELETE FROM cfg_acl_resources_cache WHERE organization_id = '" . $self->{organization_id} . "' AND acl_resource_id = " . $acl_resource_id); - $self->{logger}->writeLogDebug("centreondacl: organization $self->{organization_id} acl resource $acl_resource_id : finished resync (status: $status)"); + $self->{logger}->writeLogDebug("gorgoneacl: organization $self->{organization_id} acl resource $acl_resource_id : finished resync (status: $status)"); if ($status == 1) { return 1; } @@ -349,9 +349,9 @@ sub action_aclresync { sub event { while (1) { - my $message = centreon::centreond::common::zmq_dealer_read_message(socket => $socket); + my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $socket); - $connector->{logger}->writeLogDebug("centreondacl: class: $message"); + $connector->{logger}->writeLogDebug("gorgoneacl: class: $message"); if ($message =~ /^\[(.*?)\]/) { if ((my $method = $connector->can('action_' . lc($1)))) { $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; @@ -376,7 +376,7 @@ sub event { # print Data::Dumper::Dumper($connector->{class_host}->get_hosts_by_organization(organization_id => $connector->{organization_id}, with_services => 1, # mode => 1, keys => ['host_id', 'service_id'], fields => ['host_id', 'host_name', 'service_id'])); - last unless (centreon::centreond::common::zmq_still_read(socket => $socket)); + last unless (centreon::gorgone::common::zmq_still_read(socket => $socket)); } } @@ -399,13 +399,13 @@ sub run { $self->{class_object} = centreon::misc::objects::object->new(logger => $self->{logger}, db_centreon => $self->{db_centreon}); # Connect internal - $socket = centreon::centreond::common::connect_com( - zmq_type => 'ZMQ_DEALER', name => 'centreondacl-' . $self->{organization_id}, + $socket = centreon::gorgone::common::connect_com( + zmq_type => 'ZMQ_DEALER', name => 'gorgoneacl-' . $self->{organization_id}, logger => $self->{logger}, type => $self->{config_core}{internal_com_type}, path => $self->{config_core}{internal_com_path} ); - centreon::centreond::common::zmq_send_message(socket => $socket, + centreon::gorgone::common::zmq_send_message(socket => $socket, action => 'ACLREADY', data => { organization_id => $self->{organization_id} }, json_encode => 1); $self->{poll} = [ @@ -419,7 +419,7 @@ sub run { # we try to do all we can my $rev = zmq_poll($self->{poll}, 5000); if (defined($rev) && $rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("centreond-acl $$ has quit"); + $self->{logger}->writeLogInfo("gorgone-acl $$ has quit"); zmq_close($socket); exit(0); } @@ -428,7 +428,7 @@ sub run { if ($on_demand == 1) { if (defined($rev) && $rev == 0) { if (time() - $on_demand_time > $self->{config}{on_demand_time}) { - $self->{logger}->writeLogInfo("centreond-acl $$ has quit"); + $self->{logger}->writeLogInfo("gorgone-acl $$ has quit"); zmq_close($socket); exit(0); } diff --git a/gorgone/modules/centreondacl/hooks.pm b/gorgone/modules/gorgoneacl/hooks.pm similarity index 86% rename from gorgone/modules/centreondacl/hooks.pm rename to gorgone/modules/gorgoneacl/hooks.pm index 199516a4a8d..5c086ae8696 100644 --- a/gorgone/modules/centreondacl/hooks.pm +++ b/gorgone/modules/gorgoneacl/hooks.pm @@ -18,17 +18,17 @@ # limitations under the License. # -package modules::centreondacl::hooks; +package modules::gorgoneacl::hooks; use warnings; use strict; -use centreon::script::centreondcore; -use modules::centreondacl::class; +use centreon::script::gorgonecore; +use modules::gorgoneacl::class; use centreon::misc::db; my ($config_core, $config); my $config_db_centreon; -my $module_id = 'centreondacl'; +my $module_id = 'gorgoneacl'; my $events = [ 'ACLREADY', 'ACLPURGEORGANIZATION', 'ACLRESYNC', @@ -82,9 +82,9 @@ sub routing { }; if ($@) { $options{logger}->writeLogError("Cannot decode json data: $@"); - centreon::centreond::common::add_history(dbh => $options{dbh}, + centreon::gorgone::common::add_history(dbh => $options{dbh}, code => 100, token => $options{token}, - data => { message => 'centreondacl: cannot decode json' }, + data => { message => 'gorgoneacl: cannot decode json' }, json_encode => 1); return undef; } @@ -95,9 +95,9 @@ sub routing { } if (!defined($data->{organization_id}) || !defined($last_organizations->{$data->{organization_id}})) { - centreon::centreond::common::add_history(dbh => $options{dbh}, + centreon::gorgone::common::add_history(dbh => $options{dbh}, code => 100, token => $options{token}, - data => { message => 'centreondacl: need a valid organization id' }, + data => { message => 'gorgoneacl: need a valid organization id' }, json_encode => 1); return undef; } @@ -108,15 +108,15 @@ sub routing { } } - if (centreon::script::centreondcore::waiting_ready(ready => \$organizations->{$data->{organization_id}}->{ready}) == 0) { - centreon::centreond::common::add_history(dbh => $options{dbh}, + if (centreon::script::gorgonecore::waiting_ready(ready => \$organizations->{$data->{organization_id}}->{ready}) == 0) { + centreon::gorgone::common::add_history(dbh => $options{dbh}, code => 100, token => $options{token}, - data => { message => 'centreondacl: still no ready' }, + data => { message => 'gorgoneacl: still no ready' }, json_encode => 1); return undef; } - centreon::centreond::common::zmq_send_message(socket => $options{socket}, identity => 'centreondacl-' . $data->{organization_id}, + centreon::gorgone::common::zmq_send_message(socket => $options{socket}, identity => 'gorgoneacl-' . $data->{organization_id}, action => $options{action}, data => $options{data}, token => $options{token}, ); } @@ -128,7 +128,7 @@ sub gently { # They stop from themself in 'on_demand' mode return if ($on_demand == 1); foreach my $organization_id (keys %{$organizations}) { - $options{logger}->writeLogInfo("centreond-acl: Send TERM signal for organization '" . $organization_id . "'"); + $options{logger}->writeLogInfo("gorgone-acl: Send TERM signal for organization '" . $organization_id . "'"); if ($organizations->{$organization_id}->{running} == 1) { CORE::kill('TERM', $organizations->{$organization_id}->{pid}); } @@ -140,7 +140,7 @@ sub kill_internal { foreach (keys %{$organizations}) { if ($organizations->{$_}->{running} == 1) { - $options{logger}->writeLogInfo("centreond-acl: Send KILL signal for organization '" . $_ . "'"); + $options{logger}->writeLogInfo("gorgone-acl: Send KILL signal for organization '" . $_ . "'"); CORE::kill('KILL', $organizations->{$_}->{pid}); } } @@ -226,10 +226,10 @@ sub sync_organization_childs { sub create_child { my (%options) = @_; - $options{logger}->writeLogInfo("Create centreondacl for organization id '" . $options{organization_id} . "'"); + $options{logger}->writeLogInfo("Create gorgoneacl for organization id '" . $options{organization_id} . "'"); my $child_pid = fork(); if ($child_pid == 0) { - my $module = modules::centreondacl::class->new(logger => $options{logger}, + my $module = modules::gorgoneacl::class->new(logger => $options{logger}, config_core => $config_core, config => $config, config_db_centreon => $config_db_centreon, @@ -238,7 +238,7 @@ sub create_child { $module->run(on_demand => $options{on_demand}); exit(0); } - $options{logger}->writeLogInfo("PID $child_pid for centreondacl for organization id '" . $options{organization_id} . "'"); + $options{logger}->writeLogInfo("PID $child_pid for gorgoneacl for organization id '" . $options{organization_id} . "'"); $organizations->{$options{organization_id}} = { pid => $child_pid, ready => 0, running => 1 }; $organizations_pid->{$child_pid} = $options{organization_id}; } diff --git a/gorgone/modules/centreondaction/class.pm b/gorgone/modules/gorgoneaction/class.pm similarity index 81% rename from gorgone/modules/centreondaction/class.pm rename to gorgone/modules/gorgoneaction/class.pm index 7d7e4dbb2c5..6326fd7da16 100644 --- a/gorgone/modules/centreondaction/class.pm +++ b/gorgone/modules/gorgoneaction/class.pm @@ -18,11 +18,11 @@ # limitations under the License. # -package modules::centreondaction::class; +package modules::gorgoneaction::class; use strict; use warnings; -use centreon::centreond::common; +use centreon::gorgone::common; use centreon::misc::misc; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); @@ -62,7 +62,7 @@ sub handle_HUP { sub handle_TERM { my $self = shift; - $self->{logger}->writeLogInfo("centreond-action $$ Receiving order to stop..."); + $self->{logger}->writeLogInfo("gorgone-action $$ Receiving order to stop..."); $self->{stop} = 1; } @@ -82,7 +82,7 @@ sub action_command { my ($self, %options) = @_; if (!defined($options{data}->{command}) || $options{data}->{command} eq '') { - centreon::centreond::common::zmq_send_message(socket => $options{socket_log}, + centreon::gorgone::common::zmq_send_message(socket => $options{socket_log}, action => 'PUTLOG', data => { code => 35, etime => time(), token => $options{token}, data => { message => "need command argument" } }, json_encode => 1); return -1; @@ -95,13 +95,13 @@ sub action_command { redirect_stderr => 1, logger => $self->{logger}); if ($error <= -1000) { - centreon::centreond::common::zmq_send_message(socket => $options{socket_log}, + centreon::gorgone::common::zmq_send_message(socket => $options{socket_log}, action => 'PUTLOG', data => { code => 35, etime => time(), token => $options{token}, data => { message => "command '$options{data}->{command}' execution issue: $stdout" } }, json_encode => 1); return -1; } - centreon::centreond::common::zmq_send_message(socket => $options{socket_log}, + centreon::gorgone::common::zmq_send_message(socket => $options{socket_log}, action => 'PUTLOG', data => { code => 36, etime => time(), token => $options{token}, data => { message => "command '$options{data}->{command}' had finished", stdout => $stdout, exit_code => $return_code } }, json_encode => 1); return 0; @@ -111,31 +111,31 @@ sub action_enginecommand { my ($self, %options) = @_; if (!defined($options{data}->{engine_pipe}) || $options{data}->{engine_pipe} eq '') { - centreon::centreond::common::zmq_send_message(socket => $options{socket_log}, + centreon::gorgone::common::zmq_send_message(socket => $options{socket_log}, action => 'PUTLOG', data => { code => 35, etime => time(), token => $options{token}, data => { message => "need engine_pipe argument" } }, json_encode => 1); return -1; } if (! -e $options{data}->{engine_pipe}) { - centreon::centreond::common::zmq_send_message(socket => $options{socket_log}, + centreon::gorgone::common::zmq_send_message(socket => $options{socket_log}, action => 'PUTLOG', data => { code => 35, etime => time(), token => $options{token}, data => { message => "command '$options{data}->{command}' - engine_pipe '$options{data}->{engine_pipe}' must exist" } }, json_encode => 1); return -1; } if (! -p $options{data}->{engine_pipe}) { - centreon::centreond::common::zmq_send_message(socket => $options{socket_log}, + centreon::gorgone::common::zmq_send_message(socket => $options{socket_log}, action => 'PUTLOG', data => { code => 35, etime => time(), token => $options{token}, data => { message => "command '$options{data}->{command}' - engine_pipe '$options{data}->{engine_pipe}' must be a pipe file" } }, json_encode => 1); return -1; } if (! -w $options{data}->{engine_pipe}) { - centreon::centreond::common::zmq_send_message(socket => $options{socket_log}, + centreon::gorgone::common::zmq_send_message(socket => $options{socket_log}, action => 'PUTLOG', data => { code => 35, etime => time(), token => $options{token}, data => { message => "command '$options{data}->{command}' - engine_pipe '$options{data}->{engine_pipe}' must be writeable" } }, json_encode => 1); return -1; } - $self->{logger}->writeLogDebug("centreond-action: class: submit engine command '$options{data}->{command}'"); + $self->{logger}->writeLogDebug("gorgone-action: class: submit engine command '$options{data}->{command}'"); my $fh; eval { local $SIG{ALRM} = sub { die "Timeout command\n" }; @@ -147,14 +147,14 @@ sub action_enginecommand { }; if ($@) { close $fh if (defined($fh)); - $self->{logger}->writeLogError("centreond-action: class: submit engine command '$options{data}->{command}' issue: $@"); - centreon::centreond::common::zmq_send_message(socket => $options{socket_log}, + $self->{logger}->writeLogError("gorgone-action: class: submit engine command '$options{data}->{command}' issue: $@"); + centreon::gorgone::common::zmq_send_message(socket => $options{socket_log}, action => 'PUTLOG', data => { code => 35, etime => time(), token => $options{token}, data => { message => "submit command issue '$options{data}->{command}': $@" } }, json_encode => 1); return undef; } - centreon::centreond::common::zmq_send_message(socket => $options{socket_log}, + centreon::gorgone::common::zmq_send_message(socket => $options{socket_log}, action => 'PUTLOG', data => { code => 36, etime => time(), token => $options{token}, data => { message => "command '$options{data}->{command}' had been submitted" } }, json_encode => 1); return 0; @@ -163,7 +163,7 @@ sub action_enginecommand { sub action_run { my ($self, %options) = @_; - my $socket_log = centreon::centreond::common::connect_com(zmq_type => 'ZMQ_DEALER', name => 'centreondaction-'. $$, + my $socket_log = centreon::gorgone::common::connect_com(zmq_type => 'ZMQ_DEALER', name => 'gorgoneaction-'. $$, logger => $self->{logger}, linger => 5000, type => $self->{config_core}{internal_com_type}, path => $self->{config_core}{internal_com_path}); @@ -173,7 +173,7 @@ sub action_run { $self->action_enginecommand(%options, socket_log => $socket_log); } - centreon::centreond::common::zmq_send_message(socket => $socket_log, + centreon::gorgone::common::zmq_send_message(socket => $socket_log, action => 'PUTLOG', data => { code => 32, etime => time(), token => $options{token}, data => { message => "proceed action end" } }, json_encode => 1); zmq_close($socket_log); @@ -182,7 +182,7 @@ sub action_run { sub create_child { my ($self, %options) = @_; - $self->{logger}->writeLogInfo("Create centreondaction sub-process"); + $self->{logger}->writeLogInfo("Create gorgoneaction sub-process"); $options{message} =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; my ($action, $token) = ($1, $2); @@ -190,7 +190,7 @@ sub create_child { my $child_pid = fork(); if (!defined($child_pid)) { - centreon::centreond::common::zmq_send_message(socket => $socket, + centreon::gorgone::common::zmq_send_message(socket => $socket, action => 'PUTLOG', data => { code => 30, etime => time(), token => $token, data => { message => "cannot fork: $!" } }, json_encode => 1); return undef; @@ -200,7 +200,7 @@ sub create_child { $self->action_run(action => $action, token => $token, data => $data); exit(0); } else { - centreon::centreond::common::zmq_send_message(socket => $socket, + centreon::gorgone::common::zmq_send_message(socket => $socket, action => 'PUTLOG', data => { code => 31, etime => time(), token => $token, data => { message => "proceed action" } }, json_encode => 1); } @@ -208,15 +208,15 @@ sub create_child { sub event { while (1) { - my $message = centreon::centreond::common::zmq_dealer_read_message(socket => $socket); + my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $socket); - $connector->{logger}->writeLogDebug("centreondaction: class: $message"); + $connector->{logger}->writeLogDebug("gorgoneaction: class: $message"); if ($message !~ /^\[ACK\]/) { $connector->create_child(message => $message); } - last unless (centreon::centreond::common::zmq_still_read(socket => $socket)); + last unless (centreon::gorgone::common::zmq_still_read(socket => $socket)); } } @@ -224,11 +224,11 @@ sub run { my ($self, %options) = @_; # Connect internal - $socket = centreon::centreond::common::connect_com(zmq_type => 'ZMQ_DEALER', name => 'centreondaction', + $socket = centreon::gorgone::common::connect_com(zmq_type => 'ZMQ_DEALER', name => 'gorgoneaction', logger => $self->{logger}, type => $self->{config_core}{internal_com_type}, path => $self->{config_core}{internal_com_path}); - centreon::centreond::common::zmq_send_message(socket => $socket, + centreon::gorgone::common::zmq_send_message(socket => $socket, action => 'ACTIONREADY', data => { }, json_encode => 1); $self->{poll} = [ @@ -242,7 +242,7 @@ sub run { # we try to do all we can my $rev = zmq_poll($self->{poll}, 5000); if ($rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("centreond-action $$ has quit"); + $self->{logger}->writeLogInfo("gorgone-action $$ has quit"); zmq_close($socket); exit(0); } diff --git a/gorgone/modules/centreondaction/hooks.pm b/gorgone/modules/gorgoneaction/hooks.pm similarity index 78% rename from gorgone/modules/centreondaction/hooks.pm rename to gorgone/modules/gorgoneaction/hooks.pm index c15c0e77142..8a2f16c3a0c 100644 --- a/gorgone/modules/centreondaction/hooks.pm +++ b/gorgone/modules/gorgoneaction/hooks.pm @@ -18,16 +18,16 @@ # limitations under the License. # -package modules::centreondaction::hooks; +package modules::gorgoneaction::hooks; use warnings; use strict; -use centreon::script::centreondcore; -use modules::centreondaction::class; +use centreon::script::gorgonecore; +use modules::gorgoneaction::class; my $config_core; my $config; -my $module_id = 'centreondaction'; +my $module_id = 'gorgoneaction'; my $events = [ 'ACTIONREADY', ]; @@ -63,9 +63,9 @@ sub routing { }; if ($@) { $options{logger}->writeLogError("Cannot decode json data: $@"); - centreon::centreond::common::add_history(dbh => $options{dbh}, + centreon::gorgone::common::add_history(dbh => $options{dbh}, code => 30, token => $options{token}, - data => { msg => 'centreondaction: cannot decode json' }, + data => { msg => 'gorgoneaction: cannot decode json' }, json_encode => 1); return undef; } @@ -75,15 +75,15 @@ sub routing { return undef; } - if (centreon::script::centreondcore::waiting_ready(ready => \$action->{ready}) == 0) { - centreon::centreond::common::add_history(dbh => $options{dbh}, + if (centreon::script::gorgonecore::waiting_ready(ready => \$action->{ready}) == 0) { + centreon::gorgone::common::add_history(dbh => $options{dbh}, code => 30, token => $options{token}, - data => { msg => 'centreondaction: still no ready' }, + data => { msg => 'gorgoneaction: still no ready' }, json_encode => 1); return undef; } - centreon::centreond::common::zmq_send_message(socket => $options{socket}, identity => 'centreondaction', + centreon::gorgone::common::zmq_send_message(socket => $options{socket}, identity => 'gorgoneaction', action => $options{action}, data => $options{data}, token => $options{token}, ); } @@ -92,7 +92,7 @@ sub gently { my (%options) = @_; $stop = 1; - $options{logger}->writeLogInfo("centreond-action: Send TERM signal"); + $options{logger}->writeLogInfo("gorgone-action: Send TERM signal"); if ($action->{running} == 1) { CORE::kill('TERM', $action->{pid}); } @@ -102,7 +102,7 @@ sub kill { my (%options) = @_; if ($action->{running} == 1) { - $options{logger}->writeLogInfo("centreond-action: Send KILL signal for pool"); + $options{logger}->writeLogInfo("gorgone-action: Send KILL signal for pool"); CORE::kill('KILL', $action->{pid}); } } @@ -136,18 +136,18 @@ sub check { sub create_child { my (%options) = @_; - $options{logger}->writeLogInfo("Create centreondaction process"); + $options{logger}->writeLogInfo("Create gorgoneaction process"); my $child_pid = fork(); if ($child_pid == 0) { - $0 = 'centreond-action'; - my $module = modules::centreondaction::class->new(logger => $options{logger}, + $0 = 'gorgone-action'; + my $module = modules::gorgoneaction::class->new(logger => $options{logger}, config_core => $config_core, config => $config, ); $module->run(); exit(0); } - $options{logger}->writeLogInfo("PID $child_pid centreondaction"); + $options{logger}->writeLogInfo("PID $child_pid gorgoneaction"); $action = { pid => $child_pid, ready => 0, running => 1 }; } diff --git a/gorgone/modules/centreondcron/class.pm b/gorgone/modules/gorgonecron/class.pm similarity index 82% rename from gorgone/modules/centreondcron/class.pm rename to gorgone/modules/gorgonecron/class.pm index 2c2c768a153..06ac60b490e 100644 --- a/gorgone/modules/centreondcron/class.pm +++ b/gorgone/modules/gorgonecron/class.pm @@ -18,11 +18,11 @@ # limitations under the License. # -package modules::centreondcron::class; +package modules::gorgonecron::class; use strict; use warnings; -use centreon::centreond::common; +use centreon::gorgone::common; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); use Schedule::Cron; @@ -59,7 +59,7 @@ sub handle_HUP { sub handle_TERM { my $self = shift; - $self->{logger}->writeLogInfo("centreond-action $$ Receiving order to stop..."); + $self->{logger}->writeLogInfo("gorgone-action $$ Receiving order to stop..."); $self->{stop} = 1; } @@ -77,18 +77,18 @@ sub class_handle_HUP { sub event { while (1) { - my $message = centreon::centreond::common::zmq_dealer_read_message(socket => $socket); + my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $socket); - $connector->{logger}->writeLogDebug("centreondcron: class: $message"); + $connector->{logger}->writeLogDebug("gorgonecron: class: $message"); - last unless (centreon::centreond::common::zmq_still_read(socket => $socket)); + last unless (centreon::gorgone::common::zmq_still_read(socket => $socket)); } } sub cron_sleep { my $rev = zmq_poll($connector->{poll}, 1000); if ($rev == 0 && $connector->{stop} == 1) { - $connector->{logger}->writeLogInfo("centreond-cron $$ has quit"); + $connector->{logger}->writeLogInfo("gorgone-cron $$ has quit"); zmq_close($socket); exit(0); } @@ -104,13 +104,13 @@ sub run { my ($self, %options) = @_; # Connect internal - $socket = centreon::centreond::common::connect_com( - zmq_type => 'ZMQ_DEALER', name => 'centreondcron', + $socket = centreon::gorgone::common::connect_com( + zmq_type => 'ZMQ_DEALER', name => 'gorgonecron', logger => $self->{logger}, type => $self->{config_core}{internal_com_type}, path => $self->{config_core}{internal_com_path} ); - centreon::centreond::common::zmq_send_message( + centreon::gorgone::common::zmq_send_message( socket => $socket, action => 'CRONREADY', data => { }, json_encode => 1 diff --git a/gorgone/modules/centreondcron/hooks.pm b/gorgone/modules/gorgonecron/hooks.pm similarity index 75% rename from gorgone/modules/centreondcron/hooks.pm rename to gorgone/modules/gorgonecron/hooks.pm index d715273e2cb..c12ce6fe94e 100644 --- a/gorgone/modules/centreondcron/hooks.pm +++ b/gorgone/modules/gorgonecron/hooks.pm @@ -18,16 +18,16 @@ # limitations under the License. # -package modules::centreondcron::hooks; +package modules::gorgonecron::hooks; use warnings; use strict; -use centreon::script::centreondcore; -use modules::centreondcron::class; +use centreon::script::gorgonecore; +use modules::gorgonecron::class; my $config_core; my $config; -my $module_id = 'centreondcron'; +my $module_id = 'gorgonecron'; my $events = [ 'CRONREADY', 'RELOADCRON', ]; @@ -57,10 +57,10 @@ sub routing { }; if ($@) { $options{logger}->writeLogError("Cannot decode json data: $@"); - centreon::centreond::common::add_history( + centreon::gorgone::common::add_history( dbh => $options{dbh}, code => 10, token => $options{token}, - data => { message => 'centreondcron: cannot decode json' }, + data => { message => 'gorgonecron: cannot decode json' }, json_encode => 1 ); return undef; @@ -71,18 +71,18 @@ sub routing { return undef; } - if (centreon::script::centreondcore::waiting_ready(ready => \$cron->{ready}) == 0) { - centreon::centreond::common::add_history( + if (centreon::script::gorgonecore::waiting_ready(ready => \$cron->{ready}) == 0) { + centreon::gorgone::common::add_history( dbh => $options{dbh}, code => 10, token => $options{token}, - data => { message => 'centreondcron: still no ready' }, + data => { message => 'gorgonecron: still no ready' }, json_encode => 1 ); return undef; } - centreon::centreond::common::zmq_send_message( - socket => $options{socket}, identity => 'centreondcron', + centreon::gorgone::common::zmq_send_message( + socket => $options{socket}, identity => 'gorgonecron', action => $options{action}, data => $options{data}, token => $options{token}, ); } @@ -91,7 +91,7 @@ sub gently { my (%options) = @_; $stop = 1; - $options{logger}->writeLogInfo("centreond-cron: Send TERM signal"); + $options{logger}->writeLogInfo("gorgone-cron: Send TERM signal"); if ($cron->{running} == 1) { CORE::kill('TERM', $cron->{pid}); } @@ -101,7 +101,7 @@ sub kill { my (%options) = @_; if ($cron->{running} == 1) { - $options{logger}->writeLogInfo("centreond-cron: Send KILL signal for pool"); + $options{logger}->writeLogInfo("gorgone-cron: Send KILL signal for pool"); CORE::kill('KILL', $cron->{pid}); } } @@ -135,11 +135,11 @@ sub check { sub create_child { my (%options) = @_; - $options{logger}->writeLogInfo("Create centreondcron process"); + $options{logger}->writeLogInfo("Create gorgonecron process"); my $child_pid = fork(); if ($child_pid == 0) { - $0 = 'centreond-cron'; - my $module = modules::centreondcron::class->new( + $0 = 'gorgone-cron'; + my $module = modules::gorgonecron::class->new( logger => $options{logger}, config_core => $config_core, config => $config, @@ -147,7 +147,7 @@ sub create_child { $module->run(); exit(0); } - $options{logger}->writeLogInfo("PID $child_pid centreondcron"); + $options{logger}->writeLogInfo("PID $child_pid gorgonecron"); $cron = { pid => $child_pid, ready => 0, running => 1 }; } diff --git a/gorgone/modules/centreondproxy/class.pm b/gorgone/modules/gorgoneproxy/class.pm similarity index 65% rename from gorgone/modules/centreondproxy/class.pm rename to gorgone/modules/gorgoneproxy/class.pm index 75cf3d466e0..2c396dfc55b 100644 --- a/gorgone/modules/centreondproxy/class.pm +++ b/gorgone/modules/gorgoneproxy/class.pm @@ -18,12 +18,12 @@ # limitations under the License. # -package modules::centreondproxy::class; +package modules::gorgoneproxy::class; use strict; use warnings; -use centreon::centreond::common; -use centreon::centreond::clientzmq; +use centreon::gorgone::common; +use centreon::gorgone::clientzmq; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); @@ -62,7 +62,7 @@ sub handle_HUP { sub handle_TERM { my $self = shift; - $self->{logger}->writeLogInfo("centreond-proxy $$ Receiving order to stop..."); + $self->{logger}->writeLogInfo("gorgone-proxy $$ Receiving order to stop..."); $self->{stop} = 1; } @@ -100,10 +100,12 @@ sub read_message { } my ($action, $token) = ($1, $2); - centreon::centreond::common::zmq_send_message(socket => $socket, - action => 'PONG', token => $token, target => '', - data => { code => 0, data => { message => 'ping ok', action => 'ping', id => $client_identity } }, - json_encode => 1); + centreon::gorgone::common::zmq_send_message( + socket => $socket, + action => 'PONG', token => $token, target => '', + data => { code => 0, data => { message => 'ping ok', action => 'ping', id => $client_identity } }, + json_encode => 1 + ); } elsif ($options{data} =~ /^\[ACK\]\s+\[(.*?)\]\s+(.*)/m) { my $data; @@ -115,9 +117,11 @@ sub read_message { } if (defined($data->{data}->{action}) && $data->{data}->{action} eq 'getlog') { - centreon::centreond::common::zmq_send_message(socket => $socket, - action => 'SETLOGS', token => $1, target => '', - data => $2); + centreon::gorgone::common::zmq_send_message( + socket => $socket, + action => 'SETLOGS', token => $1, target => '', + data => $2 + ); } return undef; } @@ -127,14 +131,15 @@ sub connect { my ($self, %options) = @_; if ($options{entry}->{type} == 1) { - $options{entry}->{class} = centreon::centreond::clientzmq->new(identity => 'proxy-' . $self->{core_id} . '-' . $options{id}, - cipher => $options{entry}->{cipher}, - vector => $options{entry}->{vector}, - pubkey => $options{entry}->{pubkey}, - target_type => $options{entry}->{target_type}, - target_path => $options{entry}->{target_path}, - logger => $self->{logger} - ); + $options{entry}->{class} = centreon::gorgone::clientzmq->new( + identity => 'proxy-' . $self->{core_id} . '-' . $options{id}, + cipher => $options{entry}->{cipher}, + vector => $options{entry}->{vector}, + pubkey => $options{entry}->{pubkey}, + target_type => $options{entry}->{target_type}, + target_path => $options{entry}->{target_path}, + logger => $self->{logger} + ); $options{entry}->{class}->init(callback => \&read_message); } } @@ -163,20 +168,20 @@ sub proxy { target => '', data => $data); if ($status != 0) { # error we put log and we close (TODO the log) - $connector->{logger}->writeLogError("centreondproxy: class: send message problem for '$target': $msg"); + $connector->{logger}->writeLogError("gorgoneproxy: class: send message problem for '$target': $msg"); $connector->{clients}->{$target}->{delete} = 1; } } - $connector->{logger}->writeLogDebug("centreondproxy: class: [action = $action] [token = $token] [target = $target] [data = $data]"); + $connector->{logger}->writeLogDebug("gorgoneproxy: class: [action = $action] [token = $token] [target = $target] [data = $data]"); } sub event_internal { while (1) { - my $message = centreon::centreond::common::zmq_dealer_read_message(socket => $socket); + my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $socket); proxy(message => $message); - last unless (centreon::centreond::common::zmq_still_read(socket => $socket)); + last unless (centreon::gorgone::common::zmq_still_read(socket => $socket)); } } @@ -184,13 +189,17 @@ sub run { my ($self, %options) = @_; # Connect internal - $socket = centreon::centreond::common::connect_com(zmq_type => 'ZMQ_DEALER', name => 'centreondproxy-' . $self->{pool_id}, - logger => $self->{logger}, - type => $self->{config_core}{internal_com_type}, - path => $self->{config_core}{internal_com_path}); - centreon::centreond::common::zmq_send_message(socket => $socket, - action => 'PROXYREADY', data => { pool_id => $self->{pool_id} }, - json_encode => 1); + $socket = centreon::gorgone::common::connect_com( + zmq_type => 'ZMQ_DEALER', name => 'gorgoneproxy-' . $self->{pool_id}, + logger => $self->{logger}, + type => $self->{config_core}{internal_com_type}, + path => $self->{config_core}{internal_com_path} + ); + centreon::gorgone::common::zmq_send_message( + socket => $socket, + action => 'PROXYREADY', data => { pool_id => $self->{pool_id} }, + json_encode => 1 + ); my $poll = { socket => $socket, events => ZMQ_POLLIN, @@ -216,7 +225,7 @@ sub run { next if (!defined($rev)); if ($rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("centreond-proxy $$ has quit"); + $self->{logger}->writeLogInfo("gorgone-proxy $$ has quit"); zmq_close($socket); exit(0); } diff --git a/gorgone/modules/centreondproxy/hooks.pm b/gorgone/modules/gorgoneproxy/hooks.pm similarity index 59% rename from gorgone/modules/centreondproxy/hooks.pm rename to gorgone/modules/gorgoneproxy/hooks.pm index fdc3542ded4..26354c798dd 100644 --- a/gorgone/modules/centreondproxy/hooks.pm +++ b/gorgone/modules/gorgoneproxy/hooks.pm @@ -18,17 +18,17 @@ # limitations under the License. # -package modules::centreondproxy::hooks; +package modules::gorgoneproxy::hooks; use warnings; use strict; -use centreon::script::centreondcore; -use centreon::centreond::common; -use modules::centreondproxy::class; +use centreon::script::gorgonecore; +use centreon::gorgone::common; +use modules::gorgoneproxy::class; my $config_core; my $config; -my $module_id = 'centreondproxy'; +my $module_id = 'gorgoneproxy'; my $events = [ 'PROXYREADY', 'SETLOGS', 'PONG', 'REGISTERNODE', 'UNREGISTERNODE', # internal. Shouldn't be used by third party clients 'ADDPOLLER', @@ -86,22 +86,24 @@ sub routing { }; if ($@) { $options{logger}->writeLogError("Cannot decode json data: $@"); - centreon::centreond::common::add_history(dbh => $options{dbh}, - code => 20, token => $options{token}, - data => { message => 'centreondproxy: cannot decode json' }, - json_encode => 1); + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => 20, token => $options{token}, + data => { message => 'gorgoneproxy: cannot decode json' }, + json_encode => 1 + ); return undef; } if ($options{action} eq 'PONG') { return undef if (!defined($data->{data}->{id}) || $data->{data}->{id} eq ''); $last_pong->{$data->{data}->{id}} = time(); - $options{logger}->writeLogInfo("centreond-proxy: pong received from '" . $data->{data}->{id} . "'"); + $options{logger}->writeLogInfo("gorgone-proxy: pong received from '" . $data->{data}->{id} . "'"); return undef; } if ($options{action} eq 'UNREGISTERNODE') { - $options{logger}->writeLogInfo("centreond-proxy: poller '" . $data->{id} . "' is unregistered"); + $options{logger}->writeLogInfo("gorgone-proxy: poller '" . $data->{id} . "' is unregistered"); if (defined($register_pollers->{$data->{id}})) { delete $register_pollers->{$data->{id}}; delete $synctime_pollers->{$data->{id}}; @@ -110,7 +112,7 @@ sub routing { } if ($options{action} eq 'REGISTERNODE') { - $options{logger}->writeLogInfo("centreond-proxy: poller '" . $data->{id} . "' is registered"); + $options{logger}->writeLogInfo("gorgone-proxy: poller '" . $data->{id} . "' is registered"); $register_pollers->{$data->{id}} = 1; $last_pong->{$data->{id}} = 0 if (!defined($last_pong->{$data->{id}})); if ($synctime_error == 0 && !defined($synctime_pollers->{$options{target}}) && @@ -132,41 +134,49 @@ sub routing { if (!defined($options{target}) || (!defined($last_pollers->{$options{target}}) && !defined($register_pollers->{$options{target}}))) { - centreon::centreond::common::add_history(dbh => $options{dbh}, - code => 20, token => $options{token}, - data => { message => 'centreondproxy: need a valid poller id' }, - json_encode => 1); + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => 20, token => $options{token}, + data => { message => 'gorgoneproxy: need a valid poller id' }, + json_encode => 1 + ); return undef; } if ($options{action} eq 'GETLOG') { if ($synctime_error == -1 || get_sync_time(dbh => $options{dbh}) == -1) { - centreon::centreond::common::add_history(dbh => $options{dbh}, - code => 20, token => $options{token}, - data => { message => 'centreondproxy: problem to getlog' }, - json_encode => 1); + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => 20, token => $options{token}, + data => { message => 'gorgoneproxy: problem to getlog' }, + json_encode => 1 + ); return undef; } if ($synctime_pollers->{$options{target}}->{in_progress} == 1) { - centreon::centreond::common::add_history(dbh => $options{dbh}, - code => 20, token => $options{token}, - data => { message => 'centreondproxy: getlog already in progress' }, - json_encode => 1); + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => 20, token => $options{token}, + data => { message => 'gorgoneproxy: getlog already in progress' }, + json_encode => 1 + ); return undef; } if (defined($last_pollers->{$options{target}}) && $last_pollers->{$options{target}}->{type} == 2) { - centreon::centreond::common::add_history(dbh => $options{dbh}, - code => 20, token => $options{token}, - data => { message => "centreondproxy: can't get log a ssh target" }, - json_encode => 1); + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => 20, token => $options{token}, + data => { message => "gorgoneproxy: can't get log a ssh target" }, + json_encode => 1 + ); return undef; } # We put the good time to get my $ctime = $synctime_pollers->{$options{target}}->{ctime}; my $last_id = $synctime_pollers->{$options{target}}->{last_id}; - $options{data} = centreon::centreond::common::json_encode(data => { ctime => $ctime, id => $last_id }); + $options{data} = centreon::gorgone::common::json_encode(data => { ctime => $ctime, id => $last_id }); $synctime_pollers->{$options{target}}->{in_progress} = 1; $synctime_pollers->{$options{target}}->{in_progress_time} = time(); } @@ -177,11 +187,13 @@ sub routing { return undef; } - if (centreon::script::centreondcore::waiting_ready_pool(pool => $pools) == 0) { - centreon::centreond::common::add_history(dbh => $options{dbh}, - code => 20, token => $options{token}, - data => { message => 'centreondproxy: still none ready' }, - json_encode => 1); + if (centreon::script::gorgonecore::waiting_ready_pool(pool => $pools) == 0) { + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => 20, token => $options{token}, + data => { message => 'gorgoneproxy: still none ready' }, + json_encode => 1 + ); return undef; } @@ -193,10 +205,11 @@ sub routing { $poller_pool->{$options{target}} = $identity; } - centreon::centreond::common::zmq_send_message(socket => $options{socket}, identity => 'centreondproxy-' . $identity, - action => $options{action}, data => $options{data}, token => $options{token}, - target => $options{target} - ); + centreon::gorgone::common::zmq_send_message( + socket => $options{socket}, identity => 'gorgoneproxy-' . $identity, + action => $options{action}, data => $options{data}, token => $options{token}, + target => $options{target} + ); } sub gently { @@ -204,7 +217,7 @@ sub gently { $stop = 1; foreach my $pool_id (keys %{$pools}) { - $options{logger}->writeLogInfo("centreond-proxy: Send TERM signal for pool '" . $pool_id . "'"); + $options{logger}->writeLogInfo("gorgone-proxy: Send TERM signal for pool '" . $pool_id . "'"); if ($pools->{$pool_id}->{running} == 1) { CORE::kill('TERM', $pools->{$pool_id}->{pid}); } @@ -216,7 +229,7 @@ sub kill { foreach (keys %{$pools}) { if ($pools->{$_}->{running} == 1) { - $options{logger}->writeLogInfo("centreond-proxy: Send KILL signal for pool '" . $_ . "'"); + $options{logger}->writeLogInfo("gorgone-proxy: Send KILL signal for pool '" . $_ . "'"); CORE::kill('KILL', $pools->{$_}->{pid}); } } @@ -253,10 +266,12 @@ sub check { foreach (keys %{$synctime_pollers}) { if ($synctime_pollers->{$_}->{in_progress} == 1 && time() - $synctime_pollers->{$_}->{in_progress_time} > $synctimeout_option) { - centreon::centreond::common::add_history(dbh => $options{dbh}, - code => 20, - data => { message => "centreondproxy: getlog in timeout for '$_'" }, - json_encode => 1); + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => 20, + data => { message => "gorgoneproxy: getlog in timeout for '$_'" }, + json_encode => 1 + ); $synctime_pollers->{$_}->{in_progress} = 0; } } @@ -271,7 +286,7 @@ sub check { if ($stop == 0 && time() - $ping_time > $ping_option) { - $options{logger}->writeLogInfo("centreond-proxy: send pings"); + $options{logger}->writeLogInfo("gorgone-proxy: send pings"); $ping_time = time(); ping_send(dbh => $options{dbh}); } @@ -284,21 +299,25 @@ sub setlogs { my (%options) = @_; if (!defined($options{data}->{data}->{id}) || $options{data}->{data}->{id} eq '') { - centreon::centreond::common::add_history(dbh => $options{dbh}, - code => 20, token => $options{token}, - data => { message => 'centreondproxy: need a id to setlogs' }, - json_encode => 1); + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => 20, token => $options{token}, + data => { message => 'gorgoneproxy: need a id to setlogs' }, + json_encode => 1 + ); return undef; } if ($synctime_pollers->{$options{data}->{data}->{id}}->{in_progress} == 0) { - centreon::centreond::common::add_history(dbh => $options{dbh}, - code => 20, token => $options{token}, - data => { message => 'centreondproxy: skip setlogs response. Maybe too much time to get response. Retry' }, - json_encode => 1); + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => 20, token => $options{token}, + data => { message => 'gorgoneproxy: skip setlogs response. Maybe too much time to get response. Retry' }, + json_encode => 1 + ); return undef; } - $options{logger}->writeLogInfo("centreondproxy: hooks: received setlogs for '$options{data}->{data}->{id}'"); + $options{logger}->writeLogInfo("gorgoneproxy: hooks: received setlogs for '$options{data}->{data}->{id}'"); $synctime_pollers->{$options{data}->{data}->{id}}->{in_progress} = 0; @@ -308,11 +327,13 @@ sub setlogs { $options{dbh}->transaction_mode(1); my $status = 0; foreach (keys %{$options{data}->{data}->{result}}) { - $status = centreon::centreond::common::add_history(dbh => $options{dbh}, - etime => $options{data}->{data}->{result}->{$_}->{etime}, - code => $options{data}->{data}->{result}->{$_}->{code}, - token => $options{data}->{data}->{result}->{$_}->{token}, - data => $options{data}->{data}->{result}->{$_}->{data}); + $status = centreon::gorgone::common::add_history( + dbh => $options{dbh}, + etime => $options{data}->{data}->{result}->{$_}->{etime}, + code => $options{data}->{data}->{result}->{$_}->{code}, + token => $options{data}->{data}->{result}->{$_}->{token}, + data => $options{data}->{data}->{result}->{$_}->{data} + ); last if ($status == -1); $ctime_recent = $options{data}->{data}->{result}->{$_}->{ctime} if ($ctime_recent < $options{data}->{data}->{result}->{$_}->{ctime}); $last_id = $options{data}->{data}->{result}->{$_}->{id} if ($last_id < $options{data}->{data}->{result}->{$_}->{id}); @@ -363,9 +384,9 @@ sub update_sync_time { my $status; if ($synctime_pollers->{$options{id}}->{last_id} == 0) { - ($status) = $options{dbh}->query("INSERT INTO centreond_synchistory (`id`, `ctime`, `last_id`) VALUES (" . $options{dbh}->quote($options{id}) . ", " . $options{dbh}->quote($options{ctime}) . ", " . $options{dbh}->quote($options{last_id}) . ")"); + ($status) = $options{dbh}->query("INSERT INTO gorgone_synchistory (`id`, `ctime`, `last_id`) VALUES (" . $options{dbh}->quote($options{id}) . ", " . $options{dbh}->quote($options{ctime}) . ", " . $options{dbh}->quote($options{last_id}) . ")"); } else { - ($status) = $options{dbh}->query("UPDATE centreond_synchistory SET `ctime` = " . $options{dbh}->quote($options{ctime}) . ", `last_id` = " . $options{dbh}->quote($options{last_id}) . " WHERE `id` = " . $options{dbh}->quote($options{id})); + ($status) = $options{dbh}->query("UPDATE gorgone_synchistory SET `ctime` = " . $options{dbh}->quote($options{ctime}) . ", `last_id` = " . $options{dbh}->quote($options{last_id}) . " WHERE `id` = " . $options{dbh}->quote($options{id})); } return $status; } @@ -373,7 +394,7 @@ sub update_sync_time { sub get_sync_time { my (%options) = @_; - my ($status, $sth) = $options{dbh}->query("SELECT * FROM centreond_synchistory"); + my ($status, $sth) = $options{dbh}->query("SELECT * FROM gorgone_synchistory"); if ($status == -1) { $synctime_error = -1; return -1; @@ -419,20 +440,21 @@ sub rr_pool { sub create_child { my (%options) = @_; - $options{logger}->writeLogInfo("Create centreondproxy for pool id '" . $options{pool_id} . "'"); + $options{logger}->writeLogInfo("Create gorgoneproxy for pool id '" . $options{pool_id} . "'"); my $child_pid = fork(); if ($child_pid == 0) { - $0 = 'centreond-proxy'; - my $module = modules::centreondproxy::class->new(logger => $options{logger}, - config_core => $config_core, - config => $config, - pool_id => $options{pool_id}, - core_id => $core_id - ); + $0 = 'gorgone-proxy'; + my $module = modules::gorgone::class->new( + logger => $options{logger}, + config_core => $config_core, + config => $config, + pool_id => $options{pool_id}, + core_id => $core_id + ); $module->run(); exit(0); } - $options{logger}->writeLogInfo("PID $child_pid centreondproxy for pool id '" . $options{pool_id} . "'"); + $options{logger}->writeLogInfo("PID $child_pid gorgoneproxy for pool id '" . $options{pool_id} . "'"); $pools->{$options{pool_id}} = { pid => $child_pid, ready => 0, running => 1 }; $pools_pid->{$child_pid} = $options{pool_id}; } @@ -441,15 +463,18 @@ sub pull_request { my (%options) = @_; # No target anymore. We remove it. - my $message = centreon::centreond::common::build_protocol(action => $options{action}, data => $options{data}, token => $options{token}, - target => '' - ); - my ($status, $key) = centreon::centreond::common::is_handshake_done(dbh => $options{dbh}, identity => unpack('H*', $options{target})); + my $message = centreon::gorgone::common::build_protocol( + action => $options{action}, data => $options{data}, token => $options{token}, + target => '' + ); + my ($status, $key) = centreon::gorgone::common::is_handshake_done(dbh => $options{dbh}, identity => unpack('H*', $options{target})); if ($status == 0) { - centreon::centreond::common::add_history(dbh => $options{dbh}, - code => 20, token => $options{token}, - data => { message => "centreondproxy: node '" . $options{target} . "' had never been connected" }, - json_encode => 1); + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => 20, token => $options{token}, + data => { message => "gorgoneproxy: node '" . $options{target} . "' had never been connected" }, + json_encode => 1 + ); return undef; } @@ -457,12 +482,14 @@ sub pull_request { # Catch some actions call and do some transformation (on file copy) # TODO - centreon::centreond::common::zmq_send_message(socket => $external_socket, - cipher => $config_core->{cipher}, - vector => $config_core->{vector}, - symkey => $key, - identity => $options{target}, - message => $message); + centreon::gorgone::common::zmq_send_message( + socket => $external_socket, + cipher => $config_core->{cipher}, + vector => $config_core->{vector}, + symkey => $key, + identity => $options{target}, + message => $message + ); } sub get_constatus_result { diff --git a/gorgone/modules/centreondpull/hooks.pm b/gorgone/modules/gorgonepull/hooks.pm similarity index 60% rename from gorgone/modules/centreondpull/hooks.pm rename to gorgone/modules/gorgonepull/hooks.pm index 47a25c54a6b..04de88ce97b 100644 --- a/gorgone/modules/centreondpull/hooks.pm +++ b/gorgone/modules/gorgonepull/hooks.pm @@ -18,15 +18,15 @@ # limitations under the License. # -package modules::centreondpull::hooks; +package modules::gorgonepull::hooks; use warnings; use strict; -use centreon::centreond::clientzmq; +use centreon::gorgone::clientzmq; my $config_core; my $config; -my $module_id = 'centreondpull'; +my $module_id = 'gorgonepull'; my $events = [ ]; my $stop = 0; @@ -47,27 +47,29 @@ sub init { $logger = $options{logger}; # Connect internal - $socket_to_internal = centreon::centreond::common::connect_com(zmq_type => 'ZMQ_DEALER', name => 'centreondpull', - logger => $options{logger}, - type => $config_core->{internal_com_type}, - path => $config_core->{internal_com_path}, - linger => $config->{linger} - ); - $client = centreon::centreond::clientzmq->new(identity => $config_core->{id}, - cipher => $config->{cipher}, - vector => $config->{vector}, - pubkey => $config->{pubkey}, - target_type => $config->{target_type}, - target_path => $config->{target_path}, - logger => $options{logger}, - ping => $config->{ping}, - ping_timeout => $config->{ping_timeout} - ); + $socket_to_internal = centreon::gorgone::common::connect_com( + zmq_type => 'ZMQ_DEALER', name => 'gorgonepull', + logger => $options{logger}, + type => $config_core->{internal_com_type}, + path => $config_core->{internal_com_path}, + linger => $config->{linger} + ); + $client = centreon::gorgone::clientzmq->new( + identity => $config_core->{id}, + cipher => $config->{cipher}, + vector => $config->{vector}, + pubkey => $config->{pubkey}, + target_type => $config->{target_type}, + target_path => $config->{target_path}, + logger => $options{logger}, + ping => $config->{ping}, + ping_timeout => $config->{ping_timeout} + ); $client->init(callback => \&read_message); $client->send_message(action => 'REGISTERNODE', data => { id => $config_core->{id} }, json_encode => 1); - centreon::centreond::common::add_zmq_pollin(socket => $socket_to_internal, + centreon::gorgone::common::add_zmq_pollin(socket => $socket_to_internal, callback => \&from_router, poll => $options{poll}); } @@ -136,26 +138,26 @@ sub transmit_back { sub from_router { while (1) { - my $message = transmit_back(message => centreon::centreond::common::zmq_dealer_read_message(socket => $socket_to_internal)); + my $message = transmit_back(message => centreon::gorgone::common::zmq_dealer_read_message(socket => $socket_to_internal)); # Only send back SETLOGS and PONG if (defined($message)) { - $logger->writeLogDebug("centreond-pull: hook: read message from internal: $message"); + $logger->writeLogDebug("gorgone-pull: hook: read message from internal: $message"); $client->send_message(message => $message); } - last unless (centreon::centreond::common::zmq_still_read(socket => $socket_to_internal)); + last unless (centreon::gorgone::common::zmq_still_read(socket => $socket_to_internal)); } } sub read_message { my (%options) = @_; - # We skip. Dont need to send it in centreond-core + # We skip. Dont need to send it in gorgone-core if ($options{data} =~ /^\[ACK\]/) { return undef; } - $logger->writeLogDebug("centreond-pull: hook: read message from external: $options{data}"); - centreon::centreond::common::zmq_send_message(socket => $socket_to_internal, + $logger->writeLogDebug("gorgone-pull: hook: read message from external: $options{data}"); + centreon::gorgone::common::zmq_send_message(socket => $socket_to_internal, message => $options{data}); } diff --git a/gorgone/modules/scom/class.pm b/gorgone/modules/gorgonescom/class.pm similarity index 85% rename from gorgone/modules/scom/class.pm rename to gorgone/modules/gorgonescom/class.pm index 65a42111bb9..8b0569a1d93 100644 --- a/gorgone/modules/scom/class.pm +++ b/gorgone/modules/gorgonescom/class.pm @@ -22,7 +22,7 @@ package modules::scom::class; use strict; use warnings; -use centreon::centreond::common; +use centreon::gorgone::common; use centreon::misc::objects::object; use centreon::misc::http::http; use ZMQ::LibZMQ4; @@ -81,7 +81,7 @@ sub handle_HUP { sub handle_TERM { my $self = shift; - $self->{logger}->writeLogInfo("scom $$ Receiving order to stop..."); + $self->{logger}->writeLogInfo("gorgonescom $$ Receiving order to stop..."); $self->{stop} = 1; } @@ -105,7 +105,7 @@ sub json_encode { $encoded_arguments = JSON::XS->new->utf8->encode($options{argument}); }; if ($@) { - $self->{logger}->writeLogError("scom: container $self->{container_id}: scom $options{method} - cannot encode json: $@"); + $self->{logger}->writeLogError("gorgonescom: container $self->{container_id}: scom $options{method} - cannot encode json: $@"); return 1; } @@ -116,13 +116,13 @@ sub http_check_error { my ($self, %options) = @_; if ($options{status} == 1) { - $self->{logger}->writeLogError("scom: container $self->{container_id}: scom $options{method} issue"); + $self->{logger}->writeLogError("gorgonescom: container $self->{container_id}: scom $options{method} issue"); return 1; } my $code = $self->{http}->get_code(); if ($code !~ /^2/) { - $self->{logger}->writeLogError("scom: container $self->{container_id}: scom $options{method} issue - " . $self->{http}->get_message()); + $self->{logger}->writeLogError("gorgonescom: container $self->{container_id}: scom $options{method} issue - " . $self->{http}->get_message()); return 1; } @@ -149,7 +149,7 @@ sub scom_authenticate { if (defined($header) && $header =~ /SCOMSessionId=([^;]+);/i) { $connector->{scom_session_id} = $1; } else { - $self->{logger}->writeLogError("scom: container $self->{container_id}: scom authenticate issue - error retrieving cookie"); + $self->{logger}->writeLogError("gorgonescom: container $self->{container_id}: scom authenticate issue - error retrieving cookie"); return 1; } @@ -222,16 +222,16 @@ sub action_scomresync { my ($self, %options) = @_; my ($status, $sth, $resource_configs); - $self->{logger}->writeLogDebug("scom: container $self->{container_id}: begin resync"); + $self->{logger}->writeLogDebug("gorgonescom: container $self->{container_id}: begin resync"); $self->get_realtime_slots(); if (scalar(@{$self->{realtime_slots}}) <= 0) { - $self->{logger}->writeLogError("scom: container $self->{container_id}: cannot find realtime slots"); + $self->{logger}->writeLogError("gorgonescom: container $self->{container_id}: cannot find realtime slots"); return 1; } if ($self->get_realtime_scom_alerts() == 0) { - $self->{logger}->writeLogError("scom: container $self->{container_id}: cannot get scom realtime alerts"); + $self->{logger}->writeLogError("gorgonescom: container $self->{container_id}: cannot get scom realtime alerts"); return 1; } @@ -240,9 +240,9 @@ sub action_scomresync { sub event { while (1) { - my $message = centreon::centreond::common::zmq_dealer_read_message(socket => $socket); + my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $socket); - $connector->{logger}->writeLogDebug("scom: class: $message"); + $connector->{logger}->writeLogDebug("gorgonescom: class: $message"); if ($message =~ /^\[(.*?)\]/) { if ((my $method = $connector->can('action_' . lc($1)))) { $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; @@ -255,7 +255,7 @@ sub event { } } - last unless (centreon::centreond::common::zmq_still_read(socket => $socket)); + last unless (centreon::gorgone::common::zmq_still_read(socket => $socket)); } } @@ -275,13 +275,13 @@ sub run { $self->{http} = centreon::misc::http::http->new(logger => $self->{logger}); # Connect internal - $socket = centreon::centreond::common::connect_com( - zmq_type => 'ZMQ_DEALER', name => 'scom-' . $self->{container_id}, + $socket = centreon::gorgone::common::connect_com( + zmq_type => 'ZMQ_DEALER', name => 'gorgonescom-' . $self->{container_id}, logger => $self->{logger}, type => $self->{config_core}{internal_com_type}, path => $self->{config_core}{internal_com_path} ); - centreon::centreond::common::zmq_send_message( + centreon::gorgone::common::zmq_send_message( socket => $socket, action => 'SCOMREADY', data => { container_id => $self->{container_id} }, json_encode => 1 @@ -297,7 +297,7 @@ sub run { # we try to do all we can my $rev = zmq_poll($self->{poll}, 5000); if (defined($rev) && $rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("scom $$ has quit"); + $self->{logger}->writeLogInfo("gorgonescom $$ has quit"); zmq_close($socket); exit(0); } diff --git a/gorgone/modules/scom/hooks.pm b/gorgone/modules/gorgonescom/hooks.pm similarity index 81% rename from gorgone/modules/scom/hooks.pm rename to gorgone/modules/gorgonescom/hooks.pm index b474bff0a87..d6506991dc4 100644 --- a/gorgone/modules/scom/hooks.pm +++ b/gorgone/modules/gorgonescom/hooks.pm @@ -18,16 +18,16 @@ # limitations under the License. # -package modules::scom::hooks; +package modules::gorgonescom::hooks; use warnings; use strict; -use centreon::script::centreondcore; -use modules::scom::class; +use centreon::script::gorgonecore; +use modules::gorgonescom::class; my ($config_core, $config); my $config_db_centstorage; -my $module_id = 'scom'; +my $module_id = 'gorgonescom'; my $events = [ 'SCOMREADY', 'SCOMRESYNC', @@ -68,10 +68,10 @@ sub routing { }; if ($@) { $options{logger}->writeLogError("Cannot decode json data: $@"); - centreon::centreond::common::add_history( + centreon::gorgone::common::add_history( dbh => $options{dbh}, code => 200, token => $options{token}, - data => { message => 'scom: cannot decode json' }, + data => { message => 'gorgone-scom: cannot decode json' }, json_encode => 1 ); return undef; @@ -83,27 +83,27 @@ sub routing { } if (!defined($data->{container_id}) || !defined($last_containers->{$data->{container_id}})) { - centreon::centreond::common::add_history( + centreon::gorgone::common::add_history( dbh => $options{dbh}, code => 200, token => $options{token}, - data => { message => 'scom: need a valid container id' }, + data => { message => 'gorgone-scom: need a valid container id' }, json_encode => 1 ); return undef; } - if (centreon::script::centreondcore::waiting_ready(ready => \$containers->{$data->{container_id}}->{ready}) == 0) { - centreon::centreond::common::add_history( + if (centreon::script::gorgonecore::waiting_ready(ready => \$containers->{$data->{container_id}}->{ready}) == 0) { + centreon::gorgone::common::add_history( dbh => $options{dbh}, code => 200, token => $options{token}, - data => { message => 'scom: still no ready' }, + data => { message => 'gorgone-scom: still no ready' }, json_encode => 1 ); return undef; } - centreon::centreond::common::zmq_send_message( - socket => $options{socket}, identity => 'scom-' . $data->{container_id}, + centreon::gorgone::common::zmq_send_message( + socket => $options{socket}, identity => 'gorgonescom-' . $data->{container_id}, action => $options{action}, data => $options{data}, token => $options{token}, ); } @@ -113,7 +113,7 @@ sub gently { $stop = 1; foreach my $container_id (keys %$containers) { - $options{logger}->writeLogInfo("scom: Send TERM signal for container '" . $container_id . "'"); + $options{logger}->writeLogInfo("gorgonescom: Send TERM signal for container '" . $container_id . "'"); if ($containers->{$container_id}->{running} == 1) { CORE::kill('TERM', $containers->{$container_id}->{pid}); } @@ -125,7 +125,7 @@ sub kill_internal { foreach (keys %$containers) { if ($containers->{$_}->{running} == 1) { - $options{logger}->writeLogInfo("scom: Send KILL signal for container '" . $_ . "'"); + $options{logger}->writeLogInfo("gorgonescom: Send KILL signal for container '" . $_ . "'"); CORE::kill('KILL', $containers->{$_}->{pid}); } } @@ -173,11 +173,11 @@ sub get_containers { next if ($container_id eq '' || !defined($config->{$container_id . '_url'})); if (!defined($config->{$container_id . '_dsmhost'}) || $config->{$container_id . '_dsmhost'} eq '') { - $options{logger}->writeLogError("scom: cannot load container '" . $container_id . "' - please set dsmhost option"); + $options{logger}->writeLogError("gorgonescom: cannot load container '" . $container_id . "' - please set dsmhost option"); next; } if (!defined($config->{$container_id . '_dsmslot'}) || $config->{$container_id . '_dsmslot'} eq '') { - $options{logger}->writeLogError("scom: cannot load container '" . $container_id . "' - please set dsmslot option"); + $options{logger}->writeLogError("gorgonescom: cannot load container '" . $container_id . "' - please set dsmslot option"); next; } @@ -215,7 +215,7 @@ sub sync_container_childs { next if (defined($last_containers->{$container_id})); if ($containers->{$container_id}->{running} == 1) { - $options{logger}->writeLogInfo("scom: Send KILL signal for container '" . $container_id . "'"); + $options{logger}->writeLogInfo("gorgonescom: Send KILL signal for container '" . $container_id . "'"); CORE::kill('KILL', $containers->{$container_id}->{pid}); } @@ -227,10 +227,10 @@ sub sync_container_childs { sub create_child { my (%options) = @_; - $options{logger}->writeLogInfo("Create scom for container '" . $options{container_id} . "'"); + $options{logger}->writeLogInfo("Create gorgonescom for container '" . $options{container_id} . "'"); my $child_pid = fork(); if ($child_pid == 0) { - $0 = 'centreond-scom'; + $0 = 'gorgone-scom'; my $module = modules::scom::class->new( logger => $options{logger}, config_core => $config_core, @@ -242,7 +242,7 @@ sub create_child { $module->run(); exit(0); } - $options{logger}->writeLogInfo("PID $child_pid scom for container '" . $options{container_id} . "'"); + $options{logger}->writeLogInfo("PID $child_pid gorgonescom for container '" . $options{container_id} . "'"); $containers->{$options{container_id}} = { pid => $child_pid, ready => 0, running => 1 }; $containers_pid->{$child_pid} = $options{container_id}; } diff --git a/gorgone/schema/centreond_database.sql b/gorgone/schema/centreond_database.sql deleted file mode 100644 index d610b11f6f7..00000000000 --- a/gorgone/schema/centreond_database.sql +++ /dev/null @@ -1,31 +0,0 @@ -CREATE TABLE IF NOT EXISTS `centreond_identity` ( - `id` INTEGER PRIMARY KEY, - `ctime` int(11) DEFAULT NULL, - `identity` varchar(2048) DEFAULT NULL, - `key` varchar(4096) DEFAULT NULL -); - -CREATE INDEX IF NOT EXISTS idx_centreond_identity_identity ON centreond_identity (identity); - -CREATE TABLE IF NOT EXISTS `centreond_history` ( - `id` INTEGER PRIMARY KEY, - `token` varchar(255) DEFAULT NULL, - `code` int(11) DEFAULT NULL, - `etime` int(11) DEFAULT NULL, - `ctime` int(11) DEFAULT NULL, - `data` TEXT DEFAULT NULL -); - -CREATE INDEX IF NOT EXISTS idx_centreond_history_id ON centreond_history (id); -CREATE INDEX IF NOT EXISTS idx_centreond_history_token ON centreond_history (token); -CREATE INDEX IF NOT EXISTS idx_centreond_history_etime ON centreond_history (etime); -CREATE INDEX IF NOT EXISTS idx_centreond_history_code ON centreond_history (code); -CREATE INDEX IF NOT EXISTS idx_centreond_history_ctime ON centreond_history (ctime); - -CREATE TABLE IF NOT EXISTS `centreond_synchistory` ( - `id` int(11) DEFAULT NULL, - `ctime` int(11) DEFAULT NULL, - `last_id` int(11) DEFAULT NULL -); - -CREATE INDEX IF NOT EXISTS idx_centreond_synchistory_id ON centreond_synchistory (id); \ No newline at end of file diff --git a/gorgone/schema/gorgone_database.sql b/gorgone/schema/gorgone_database.sql new file mode 100644 index 00000000000..368268cff9e --- /dev/null +++ b/gorgone/schema/gorgone_database.sql @@ -0,0 +1,31 @@ +CREATE TABLE IF NOT EXISTS `gorgone_identity` ( + `id` INTEGER PRIMARY KEY, + `ctime` int(11) DEFAULT NULL, + `identity` varchar(2048) DEFAULT NULL, + `key` varchar(4096) DEFAULT NULL +); + +CREATE INDEX IF NOT EXISTS idx_gorgone_identity_identity ON gorgone_identity (identity); + +CREATE TABLE IF NOT EXISTS `gorgone_history` ( + `id` INTEGER PRIMARY KEY, + `token` varchar(255) DEFAULT NULL, + `code` int(11) DEFAULT NULL, + `etime` int(11) DEFAULT NULL, + `ctime` int(11) DEFAULT NULL, + `data` TEXT DEFAULT NULL +); + +CREATE INDEX IF NOT EXISTS idx_gorgone_history_id ON gorgone_history (id); +CREATE INDEX IF NOT EXISTS idx_gorgone_history_token ON gorgone_history (token); +CREATE INDEX IF NOT EXISTS idx_gorgone_history_etime ON gorgone_history (etime); +CREATE INDEX IF NOT EXISTS idx_gorgone_history_code ON gorgone_history (code); +CREATE INDEX IF NOT EXISTS idx_gorgone_history_ctime ON gorgone_history (ctime); + +CREATE TABLE IF NOT EXISTS `gorgone_synchistory` ( + `id` int(11) DEFAULT NULL, + `ctime` int(11) DEFAULT NULL, + `last_id` int(11) DEFAULT NULL +); + +CREATE INDEX IF NOT EXISTS idx_gorgone_synchistory_id ON gorgone_synchistory (id); diff --git a/gorgone/test-client.pl b/gorgone/test-client.pl index 5247af8ebea..51a72012062 100644 --- a/gorgone/test-client.pl +++ b/gorgone/test-client.pl @@ -26,8 +26,8 @@ use UUID; use Data::Dumper; use Sys::Hostname; -use centreon::centreond::clientzmq; -use centreon::centreond::common; +use centreon::gorgone::clientzmq; +use centreon::gorgone::common; my ($client, $client2); my $identities_token = {}; @@ -107,7 +107,7 @@ sub read_response { #$uuid = 'toto'; UUID::generate($uuid); -$client = centreon::centreond::clientzmq->new( +$client = centreon::gorgone::clientzmq->new( identity => 'toto', cipher => 'Cipher::AES', vector => '0123456789012345', @@ -117,7 +117,7 @@ sub read_response { ping => 60, ); $client->init(callback => \&read_response); -$client2 = centreon::centreond::clientzmq->new( +$client2 = centreon::gorgone::clientzmq->new( identity => 'tata', cipher => 'Cipher::AES', vector => '0123456789012345', From ba956e3dbdcd4b1dcfeda8fcc9ebb771ad205f35 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Thu, 4 Jul 2019 13:51:49 +0200 Subject: [PATCH 006/948] add module newtest --- gorgone/centreon/misc/objects/object.pm | 6 + gorgone/config/gorgoned.ini | 34 + gorgone/docs/modules/newtest/exploitation.txt | 83 + gorgone/docs/modules/newtest/installation.rst | 47 + gorgone/modules/gorgonenewtest/class.pm | 643 +++++ gorgone/modules/gorgonenewtest/hooks.pm | 272 +++ .../newtest/stubs/ManagementConsoleService.pm | 392 +++ .../gorgonenewtest/newtest/stubs/errors.pm | 31 + .../gorgonenewtest/newtest/wsdl/newtest.wsdl | 2097 +++++++++++++++++ gorgone/modules/gorgonescom/class.pm | 22 +- gorgone/modules/gorgonescom/hooks.pm | 16 +- 11 files changed, 3624 insertions(+), 19 deletions(-) create mode 100644 gorgone/docs/modules/newtest/exploitation.txt create mode 100644 gorgone/docs/modules/newtest/installation.rst create mode 100644 gorgone/modules/gorgonenewtest/class.pm create mode 100644 gorgone/modules/gorgonenewtest/hooks.pm create mode 100644 gorgone/modules/gorgonenewtest/newtest/stubs/ManagementConsoleService.pm create mode 100644 gorgone/modules/gorgonenewtest/newtest/stubs/errors.pm create mode 100644 gorgone/modules/gorgonenewtest/newtest/wsdl/newtest.wsdl diff --git a/gorgone/centreon/misc/objects/object.pm b/gorgone/centreon/misc/objects/object.pm index 73dd822c8d4..4f1fd6f4bcd 100644 --- a/gorgone/centreon/misc/objects/object.pm +++ b/gorgone/centreon/misc/objects/object.pm @@ -79,4 +79,10 @@ sub execute { return $self->do(request => $request, %options); } +sub quote { + my ($self, %options) = @_; + + return $self->{db_centreon}->quote($options{value}); +} + 1; diff --git a/gorgone/config/gorgoned.ini b/gorgone/config/gorgoned.ini index a1abc00cc9f..06ba2745f0b 100644 --- a/gorgone/config/gorgoned.ini +++ b/gorgone/config/gorgoned.ini @@ -97,3 +97,37 @@ tutu_url=http://scomserver2/ tutu_username=toto2 tutu_password=toto2 tutu_resync_time=600 + +[gorgonenewtest] +module=modules::gorgonenewtest::hooks + +; in seconds - do purge for container also +check_containers_time=3600 +clapi_command=/usr/bin/centreon +clapi_username=admin +clapi_password=centreon +clapi_action_applycfg=RELOAD +centcore_cmd=/var/lib/centreon/centcore.cmd + +containers=toto,tutu + +toto_resync_time=300 +toto_nmc_endpoint=http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx +toto_password=pass +toto_host_template=generic-active-host-custom +toto_host_prefix=Robot-%s +toto_service_template=generic-passive-service-custom +toto_service_prefix=Scenario-%s +toto_poller_name=Central +toto_list_scenario_status={ "search": "All", "instances": [] } + +tutu_resync_time=600 +tutu_nmc_endpoint=http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx +tutu_nmc_timeout=10 +tutu_password=pass +tutu_host_template=generic-active-host-custom +tutu_host_prefix=Robot-%s +tutu_service_template=generic-passive-service-custom +tutu_service_prefix=Scenario-%s +tutu_poller_name=Central +tutu_list_scenario_status={ "search": "Robot", "instances": ["XXXX"] } diff --git a/gorgone/docs/modules/newtest/exploitation.txt b/gorgone/docs/modules/newtest/exploitation.txt new file mode 100644 index 00000000000..562a0224efb --- /dev/null +++ b/gorgone/docs/modules/newtest/exploitation.txt @@ -0,0 +1,83 @@ +============ +Exploitation +============ + +Generals Principles +------------------- + +gorgone-newtest is a module program in charged to get back Newtest services. This program uses the newtest webservice in order to connect and get back the informations of one (or more) Newtest Management Console (NMC). + +By default "gorgone-newtest" starts X processes (it depends of the configuration) : + +Steps of operation for one process: + +- get Centreon datas: robots and scenarios already configured. +- gets from the NMC the list of robots and scenarios: it creates it in centreon with centreon-clapi (It don't disable it or delete it in Centreon). +- gets from the NMC the last status of scenarios: it submits through "centcore" the result. + +gorgone-newtest necessitates the utilization of one (or more) NMC. + +Configuration +-------------------- + +The « gorgone-newtest » module is configured with gorgone configuration file :: + + [gorgonenewtest] + module=modules::gorgonenewtest::hooks + + ; in seconds - do purge for container also + check_containers_time=3600 + clapi_command=/usr/bin/centreon + clapi_username=admin + clapi_password=centreon + clapi_action_applycfg=RELOAD + + containers=toto,tutu + + toto_resync_time=300 + toto_nmc_endpoint=http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx + toto_nmc_username=test + toto_nmc_password=test + toto_nmc_timeout=10 + toto_password=pass + toto_host_template=generic-active-host-custom + toto_host_prefix=Robot-%s + toto_service_template=generic-passive-service-custom + toto_service_prefix=Scenario-%s + toto_poller_name=Central + toto_list_scenario_status={ "search": "All", "instances": [] } + + tutu_resync_time=600 + tutu_nmc_endpoint=http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx + tutu_nmc_timeout=10 + tutu_password=pass + tutu_host_template=generic-active-host-custom + tutu_host_prefix=Robot-%s + tutu_service_template=generic-passive-service-custom + tutu_service_prefix=Scenario-%s + tutu_poller_name=Central + tutu_list_scenario_status={ "search": "Robot", "instances": ["XXXX"] } + +You have to add at least one entry in the configuration. The meaning of attributes : + +- 'nmc_endpoint': address of the NMC +- 'nmc_timeout': timeout NMC console response +- 'host_template': template used when the daemon creates a host in Centreon +- 'host_prefix': name used when the daemon creates and looks for a host in Centreon +- 'service_templte': template used when the daemon creates a service in Centreon +- 'service_prefix': name used when the daemon creates and looks for a service in Centreon +- 'poller_name': poller used when the daemon creates a host in Centreon +- 'clapi_username' and 'clapi_password': centreon-clapi connections +- 'clapi_action_applycfg': could be 'POLLERRELOAD' or 'POLLERRESTART' +- 'ListScenarioStatus': informations to gets from NMC : + - To get all robots: { "search": "All", "instances": [] } + - To filter on some robots: { "search": "Robot", "instances": ["XXXX", "YYYY"] } + +Troubleshooting +--------------- + +It is possible to get this kind of errors in the « logs » of « gorgone-newtest » :: + + die: syntax error at line 1, column 0, byte 0 at /usr/lib/perl5/vendor_perl/5.8.8/i386-linux-thread-multi/XML/Parser.pm line 189 + +It often means that there is a timeout error. diff --git a/gorgone/docs/modules/newtest/installation.rst b/gorgone/docs/modules/newtest/installation.rst new file mode 100644 index 00000000000..aebbb1195a9 --- /dev/null +++ b/gorgone/docs/modules/newtest/installation.rst @@ -0,0 +1,47 @@ +============ +Installation +============ + +Prerequisites +============= + +Software Recommandations +```````````````````````` + +The module "gorgonenewtest" has been tested on red-hat 7 with rpms. +Installation on other system is possible but is outside the scope of this document (Debian,...). + +==================== ===================== +Software Version +==================== ===================== +Perl SOAP::Lite 0.71 +Perl Date::Parse 1.16 +centreon 19.04 +centreon-gorgone 1.0 +==================== ===================== + +Module location +``````````````` + +The module "gorgonenewtest" daemon must be installed on Centreon Central server. Minimal used ressources are : + +* RAM : 128 MB. +* CPU : it depends the number of newtest scenarios. + +Gorgone-newtest Installation - centos/rhel 7 systems +==================================================== + +Requirements +```````````` + +======================= ===================== ====================== +Dependency Version Repository +======================= ===================== ====================== +perl-SOAP-Lite 1.10 centreon base +perl-TimeDate 2.30 redhat/centos base +======================= ===================== ====================== + +gorgone-newtest Installation +````````````````````````````````````````` + +"gorgonenewtest" is an official gorgone module. No installation needed. diff --git a/gorgone/modules/gorgonenewtest/class.pm b/gorgone/modules/gorgonenewtest/class.pm new file mode 100644 index 00000000000..beb9b95429d --- /dev/null +++ b/gorgone/modules/gorgonenewtest/class.pm @@ -0,0 +1,643 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package modules::gorgonenewtest::class; + +use strict; +use warnings; +use centreon::gorgone::common; +use centreon::misc::objects::object; +use ZMQ::LibZMQ4; +use ZMQ::Constants qw(:all); +use MIME::Base64; +use JSON::XS; +use Data::Dumper; +use modules::gorgonenewtest::newtest::stubs::ManagementConsoleService; +use modules::gorgonenewtest::newtest::stubs::errors; +use Date::Parse; + +my %handlers = (TERM => {}, HUP => {}); +my ($connector, $socket); + +sub new { + my ($class, %options) = @_; + $connector = {}; + $connector->{logger} = $options{logger}; + $connector->{container_id} = $options{container_id}; + $connector->{config} = $options{config}; + $connector->{config_core} = $options{config_core}; + $connector->{config_newtest} = $options{config_newtest}; + $connector->{config_db_centstorage} = $options{config_db_centstorage}; + $connector->{config_db_centreon} = $options{config_db_centreon}; + $connector->{stop} = 0; + + $connector->{resync_time} = $options{config_newtest}->{resync_time}; + $connector->{last_resync_time} = time() - $connector->{resync_time}; + + $connector->{endpoint} = $options{config_newtest}->{nmc_endpoint}; + $connector->{nmc_username} = $options{config_newtest}->{nmc_username}; + $connector->{nmc_password} = $options{config_newtest}->{nmc_password}; + $connector->{nmc_timeout} = $options{config_newtest}->{nmc_timeout}; + $connector->{poller_name} = $options{config_newtest}->{poller_name}; + $connector->{list_scenario_status} = $options{config_newtest}->{list_scenario_status}; + $connector->{host_template} = $options{config_newtest}->{host_template}; + $connector->{host_prefix} = $options{config_newtest}->{host_prefix}; + $connector->{service_template} = $options{config_newtest}->{service_template}; + $connector->{service_prefix} = $options{config_newtest}->{service_prefix}; + + $connector->{clapi_generate_config_timeout} = defined($options{config}->{clapi_generate_config_timeout}) ? $options{config}->{clapi_generate_config_timeout} : 180; + $connector->{clapi_timeout} = defined($options{config}->{clapi_timeout}) ? $options{config}->{clapi_timeout} : 10; + $connector->{clapi_command} = defined($options{config}->{clapi_command}) && $options{config}->{clapi_command} ne '' ? $options{config}->{clapi_command} : '/usr/bin/centreon'; + $connector->{clapi_username} = $options{config}->{clapi_username}; + $connector->{clapi_password} = $options{config}->{clapi_password}; + $connector->{clapi_action_applycfg} = $options{config}->{clapi_action_applycfg}; + $connector->{cmdFile} = defined($options{config}->{centcore_cmd}) && $options{config}->{centcore_cmd} ne '' ? $options{config}->{centcore_cmd} : '/var/lib/centreon/centcore.cmd'; + + bless $connector, $class; + $connector->set_signal_handlers(); + return $connector; +} + +sub set_signal_handlers { + my $self = shift; + + $SIG{TERM} = \&class_handle_TERM; + $handlers{TERM}->{$self} = sub { $self->handle_TERM() }; + $SIG{HUP} = \&class_handle_HUP; + $handlers{HUP}->{$self} = sub { $self->handle_HUP() }; +} + +sub handle_HUP { + my $self = shift; + $self->{reload} = 0; +} + +sub handle_TERM { + my $self = shift; + $self->{logger}->writeLogInfo("gorgone-newtest $$ Receiving order to stop..."); + $self->{stop} = 1; +} + +sub class_handle_TERM { + foreach (keys %{$handlers{TERM}}) { + &{$handlers{TERM}->{$_}}(); + } +} + +sub class_handle_HUP { + foreach (keys %{$handlers{HUP}}) { + &{$handlers{HUP}->{$_}}(); + } +} + +my %map_scenario_status = ( + Available => 0, Warning => 1, Failed => 2, Suspended => 2, + Canceled => 2, Unknown => 3, OutOfRange => 3, +); + +my %map_newtest_units = ( + Second => 's', Millisecond => 'ms', BytePerSecond => 'Bps', UnitLess => '', Unknown => '', +); + +my %map_service_status = ( + 0 => 'OK', 1 => 'WARNING', 2 => 'CRITICAL', 3 => 'UNKNOWN', 4 => 'PENDING', +); + +sub newtestresync_init { + my ($self, %options) = @_; + + # list from robot/scenario from db + # Format = { robot_name1 => { scenario1 => { last_execution_time => xxxx }, scenario2 => { } }, ... } + $self->{db_newtest} = {}; + $self->{api_newtest} = {}; + $self->{poller_id} = undef; + $self->{must_push_config} = 0; + $self->{external_commands} = []; + $self->{perfdatas} = []; + $self->{cache_robot_list_results} = undef; +} + + +sub perfdata_add { + my ($self, %options) = @_; + + my $perfdata = {label => '', value => '', unit => '', warning => '', critical => '', min => '', max => ''}; + foreach (keys %options) { + next if (!defined($options{$_})); + $perfdata->{$_} = $options{$_}; + } + $perfdata->{label} =~ s/'/''/g; + push @{$self->{perfdatas}}, $perfdata; +} + +sub add_output { + my ($self, %options) = @_; + + my $str = $map_service_status{$self->{current_status}} . ': ' . $self->{current_text} . '|'; + foreach my $perf (@{$self->{perfdatas}}) { + $str .= " '" . $perf->{label} . "'=" . $perf->{value} . $perf->{unit} . ";" . $perf->{warning} . ";" . $perf->{critical} . ";" . $perf->{min} . ";" . $perf->{max}; + } + $self->{perfdatas} = []; + + $self->push_external_cmd(cmd => 'PROCESS_SERVICE_CHECK_RESULT;' . $options{host_name} . ';' . + $options{service_name} . ';' . $self->{current_status} . ';' . $str, + time => $options{time}); +} + +sub convert_measure { + my ($self, %options) = @_; + + if (defined($map_newtest_units{$options{unit}}) && + $map_newtest_units{$options{unit}} eq 'ms') { + $options{value} /= 1000; + $options{unit} = 's'; + } + return ($options{value}, $options{unit}); +} + +sub get_poller_id { + my ($self, %options) = @_; + + my ($status, $datas) = $self->{class_object_centreon}->custom_execute( + request => 'SELECT id FROM nagios_server WHERE name = ' . $self->{class_object_centreon}->quote(value => $self->{poller_name}), + mode => 2 + ); + if ($status == -1) { + $self->{logger}->writeLogError("gorgone-newtest cannot get poller id for poller '" . $self->{poller_name} . "'."); + return 1; + } + + if (!defined($datas->[0])) { + $self->{logger}->writeLogError("gorgone-newtest cannot find poller id for poller '" . $self->{poller_name} . "'."); + return 1; + } + + $self->{poller_id} = $datas->[0]->[0]; + return 0; +} + +sub get_centreondb_cache { + my ($self, %options) = @_; + + my $request = ' + SELECT host.host_name, service.service_description + FROM host + LEFT JOIN (host_service_relation, service) ON + (host_service_relation.host_host_id = host.host_id AND + service.service_id = host_service_relation.service_service_id AND + service.service_description LIKE ' . $self->{class_object_centreon}->quote(value => $self->{service_prefix}) . ') + WHERE host_name LIKE ' . $self->{class_object_centreon}->quote(value => $self->{host_prefix}) . " AND host_register = '1'"; + $request =~ s/%s/%/g; + my ($status, $datas) = $self->{class_object_centreon}->custom_execute( + request => $request, + mode => 2 + ); + if ($status == -1) { + $self->{logger}->writeLogError("gorgone-newtest cannot get robot/scenarios list from centreon db."); + return 1; + } + + foreach (@$datas) { + $self->{db_newtest}->{$_->[0]} = {} if (!defined($self->{db_newtest}->{$_->[0]})); + if (defined($_->[1])) { + $self->{db_newtest}->{$_->[0]}->{$_->[1]} = {}; + } + } + + return 0; +} + +sub get_centstoragedb_cache { + my ($self, %options) = @_; + + my $request = 'SELECT hosts.name, services.description, services.last_check + FROM hosts LEFT JOIN services ON (services.host_id = hosts.host_id AND services.description LIKE ' . $self->{class_object_centstorage}->quote(value => $self->{service_prefix}) . ') + WHERE name like ' . $self->{class_object_centstorage}->quote(value => $self->{host_prefix}); + $request =~ s/%s/%/g; + my ($status, $datas) = $self->{class_object_centstorage}->custom_execute( + request => $request, + mode => 2 + ); + if ($status == -1) { + $self->{logger}->writeLogError("gorgone-newtest cannot get robot/scenarios list from centstorage db."); + return 1; + } + + foreach (@$datas) { + if (!defined($self->{db_newtest}->{$_->[0]})) { + $self->{logger}->writeLogError("gorgone-newtest host '" . $_->[0] . "'is in censtorage DB but not in centreon config..."); + next; + } + if (defined($_->[1]) && !defined($self->{db_newtest}->{$_->[0]}->{$_->[1]})) { + $self->{logger}->writeLogError("gorgone-newtest host scenario '" . $_->[0] . "/" . $_->[1] . "' is in censtorage DB but not in centreon config..."); + next; + } + + if (defined($_->[1])) { + $self->{db_newtest}->{$_->[0]}->{$_->[1]}->{last_execution_time} = $_->[2]; + } + } + + return 0; +} + +sub clapi_execute { + my ($self, %options) = @_; + + my $cmd = $self->{clapi_command} . " -u '" . $self->{clapi_username} . "' -p '" . $self->{clapi_password} . "' " . $options{cmd}; + my ($lerror, $stdout, $exit_code) = centreon::misc::misc::backtick( + command => $cmd, + logger => $self->{logger}, + timeout => $options{timeout}, + wait_exit => 1, + ); + if ($lerror == -1 || ($exit_code >> 8) != 0) { + $self->{logger}->writeLogError("gorgone-newtest clapi execution problem for command $cmd : " . $stdout); + return -1; + } + + return 0; +} + +sub push_external_cmd { + my ($self, %options) = @_; + my $time = defined($options{time}) ? $options{time} : time(); + + push @{$self->{external_commands}}, + 'EXTERNALCMD:' . $self->{poller_id} . ':[' . $time . '] ' . $options{cmd}; +} + +sub submit_external_cmd { + my ($self, %options) = @_; + + foreach my $cmd (@{$self->{external_commands}}) { + my ($lerror, $stdout, $exit_code) = centreon::misc::misc::backtick(command => '/bin/echo "' . $cmd . '" >> ' . $self->{cmdFile}, + logger => $self->{logger}, + timeout => 5, + wait_exit => 1 + ); + if ($lerror == -1 || ($exit_code >> 8) != 0) { + $self->{logger}->writeLogError("gorgone-newtest clapi execution problem for command $cmd : " . $stdout); + return -1; + } + } +} + +sub push_config { + my ($self, %options) = @_; + + if ($self->{must_push_config} == 1) { + $self->{logger}->writeLogInfo("gorgone-newtest generation config for '$self->{poller_name}':"); + if ($self->clapi_execute(cmd => '-a POLLERGENERATE -v ' . $self->{poller_id}, + timeout => $self->{clapi_generate_config_timeout}) != 0) { + $self->{logger}->writeLogError("gorgone-newtest generation config for '$self->{poller_name}': failed"); + return ; + } + $self->{logger}->writeLogError("gorgone-newtest generation config for '$self->{poller_name}': succeeded."); + + $self->{logger}->writeLogInfo("gorgone-newtest move config for '$self->{poller_name}':"); + if ($self->clapi_execute(cmd => '-a CFGMOVE -v ' . $self->{poller_id}, + timeout => $self->{clapi_timeout}) != 0) { + $self->{logger}->writeLogError("gorgone-newtest move config for '$self->{poller_name}': failed"); + return ; + } + $self->{logger}->writeLogError("gorgone-newtest move config for '$self->{poller_name}': succeeded."); + + $self->{logger}->writeLogInfo("gorgone-newtest restart/reload config for '$self->{poller_name}':"); + if ($self->clapi_execute(cmd => '-a ' . $self->{clapi_action_applycfg} . ' -v ' . $self->{poller_id}, + timeout => $self->{clapi_timeout}) != 0) { + $self->{logger}->writeLogError("gorgone-newtest restart/reload config for '$self->{poller_name}': failed"); + return ; + } + $self->{logger}->writeLogError("gorgone-newtest restart/reload config for '$self->{poller_name}': succeeded."); + } +} + +sub get_newtest_diagnostic { + my ($self, %options) = @_; + + my $result = $self->{instance}->ListMessages('Instance', 30, 'Diagnostics', [$options{scenario}, $options{robot}]); + if (defined(my $com_error = centreon::newtest::stubs::errors::get_error())) { + $self->{logger}->writeLogError("gorgone-newtest newtest API error 'ListMessages' method: " . $com_error); + return -1; + } + + if (!(ref($result) && defined($result->{MessageItem}))) { + $self->{logger}->writeLogError("gorgone-newtest no diagnostic found for scenario: " . $options{scenario} . '/' . $options{robot}); + return 1; + } + if (ref($result->{MessageItem}) eq 'HASH') { + $result->{MessageItem} = [$result->{MessageItem}]; + } + + my $macro_value = ''; + my $macro_append = ''; + foreach my $item (@{$result->{MessageItem}}) { + if (defined($item->{SubCategory})) { + $macro_value .= $macro_append . $item->{SubCategory} . ':' . $item->{Id}; + $macro_append = '|'; + } + } + + if ($macro_value ne '') { + $self->push_external_cmd(cmd => + 'CHANGE_CUSTOM_SVC_VAR;' . $options{host_name} . ';' . + $options{service_name} . ';NEWTEST_MESSAGEID;' . $macro_value + ); + } + return 0; +} + +sub get_scenario_results { + my ($self, %options) = @_; + + # Already test the robot but no response + if (defined($self->{cache_robot_list_results}->{$options{robot}}) && + !defined($self->{cache_robot_list_results}->{$options{robot}}->{ResultItem})) { + $self->{current_text} = sprintf("gorgone-newtest no result avaiblable for scenario '%s'", $options{scenario}); + $self->{current_status} = 3; + return 1; + } + if (!defined($self->{cache_robot_list_results}->{$options{robot}})) { + my $result = $self->{instance}->ListResults('Robot', 30, [$options{robot}]); + if (defined(my $com_error = centreon::newtest::stubs::errors::get_error())) { + $self->{logger}->writeLogError("gorgone-newtest newtest API error 'ListResults' method: " . $com_error); + return -1; + } + + if (!(ref($result) && defined($result->{ResultItem}))) { + $self->{cache_robot_list_results}->{$options{robot}} = {}; + $self->{logger}->writeLogError("gorgone-newtest no results found for robot: " . $options{robot}); + return 1; + } + + if (ref($result->{ResultItem}) eq 'HASH') { + $result->{ResultItem} = [$result->{ResultItem}]; + } + $self->{cache_robot_list_results}->{$options{robot}} = $result; + } + + # stop at first + foreach my $result (@{$self->{cache_robot_list_results}->{$options{robot}}->{ResultItem}}) { + if ($result->{MeasureName} eq $options{scenario}) { + my ($value, $unit) = $self->convert_measure( + value => $result->{ExecutionValue}, + unit => $result->{MeasureUnit} + ); + $self->{current_text} = sprintf( + "Execution status '%s'. Scenario '%s' total duration is %d%s.", + $result->{ExecutionStatus}, $options{scenario}, + $value, $unit + ); + $self->perfdata_add( + label => $result->{MeasureName}, unit => $unit, + value => sprintf("%d", $value), + min => 0 + ); + + $self->get_newtest_extra_metrics( + scenario => $options{scenario}, + robot => $options{robot}, + id => $result->{Id} + ); + return 0; + } + } + + $self->{logger}->writeLogError("gorgone-newtest no result found for scenario: " . $options{scenario} . '/' . $options{robot}); + return 1; +} + +sub get_newtest_extra_metrics { + my ($self, %options) = @_; + + my $result = $self->{instance}->ListResultChildren($options{id}); + if (defined(my $com_error = centreon::newtest::stubs::errors::get_error())) { + $self->{logger}->writeLogError("gorgone-newtest newtest API error 'ListResultChildren' method: " . $com_error); + return -1; + } + + if (!(ref($result) && defined($result->{ResultItem}))) { + $self->{logger}->writeLogError("gorgone-newtest no extra metrics found for scenario: " . $options{scenario} . '/' . $options{robot}); + return 1; + } + + if (ref($result->{ResultItem}) eq 'HASH') { + $result->{ResultItem} = [$result->{ResultItem}]; + } + foreach my $item (@{$result->{ResultItem}}) { + $self->perfdata_add( + label => $item->{MeasureName}, unit => $map_newtest_units{$item->{MeasureUnit}}, + value => $item->{ExecutionValue} + ); + } + return 0; +} + +sub get_newtest_scenarios { + my ($self, %options) = @_; + + eval { + $self->{instance}->proxy($self->{endpoint}, timeout => $self->{nmc_timeout}); + }; + if ($@) { + $self->{logger}->writeLogError('gorgone-newtest newtest proxy error: ' . $@); + return -1; + } + + if (defined($self->{nmc_username}) && $self->{nmc_username} ne '' && + defined($self->{nmc_password}) && $self->{nmc_password} ne '') { + $self->{instance}->transport->http_request->header( + 'Authorization' => 'Basic ' . MIME::Base64::encode($self->{nmc_username} . ':' . $self->{nmc_password}, '') + ); + } + my $result = $self->{instance}->ListScenarioStatus( + $self->{list_scenario_status}->{search}, + 0, + $self->{list_scenario_status}->{instances} + ); + if (defined(my $com_error = modules::gorgonenewtest::newtest::stubs::errors::get_error())) { + $self->{logger}->writeLogError("gorgone-newtest newtest API error 'ListScenarioStatus' method: " . $com_error); + return -1; + } + + if (defined($result->{InstanceScenarioItem})) { + if (ref($result->{InstanceScenarioItem}) eq 'HASH') { + $result->{InstanceScenarioItem} = [$result->{InstanceScenarioItem}]; + } + + foreach my $scenario (@{$result->{InstanceScenarioItem}}) { + my $scenario_name = $scenario->{MeasureName}; + my $robot_name = $scenario->{RobotName}; + my $last_check = sprintf("%d", Date::Parse::str2time($scenario->{LastMessageUtc}, 'UTC')); + my $host_name = sprintf($self->{host_prefix}, $robot_name); + my $service_name = sprintf($self->{service_prefix}, $scenario_name); + $self->{current_status} = $map_scenario_status{$scenario->{Status}}; + $self->{current_text} = ''; + + # Add host config + if (!defined($self->{db_newtest}->{$host_name})) { + $self->{logger}->writeLogInfo("gorgone-newtest create host '$host_name'"); + if ($self->clapi_execute(cmd => '-o HOST -a ADD -v "' . $host_name . ';' . $host_name . ';127.0.0.1;' . $self->{host_template} . ';' . $self->{poller_name} . ';"', + timeout => $self->{clapi_timeout}) == 0) { + $self->{db_newtest}->{$host_name} = {}; + $self->{must_push_config} = 1; + $self->{logger}->writeLogInfo("gorgone-newtest create host '$host_name' succeeded."); + } + } + + # Add service config + if (defined($self->{db_newtest}->{$host_name}) && !defined($self->{db_newtest}->{$host_name}->{$service_name})) { + $self->{logger}->writeLogInfo("gorgone-newtest create service '$service_name' for host '$host_name':"); + if ($self->clapi_execute(cmd => '-o SERVICE -a ADD -v "' . $host_name . ';' . $service_name . ';' . $self->{service_template} . '"', + timeout => $self->{clapi_timeout}) == 0) { + $self->{db_newtest}->{$host_name}->{$service_name} = {}; + $self->{must_push_config} = 1; + $self->{logger}->writeLogInfo("gorgone-newtest create service '$service_name' for host '$host_name' succeeded."); + $self->clapi_execute(cmd => '-o SERVICE -a setmacro -v "' . $host_name . ';' . $service_name . ';NEWTEST_MESSAGEID;"', + timeout => $self->{clapi_timeout}); + } + } + + # Check if new message + if (defined($self->{db_newtest}->{$host_name}->{$service_name}->{last_execution_time}) && + $last_check <= $self->{db_newtest}->{$host_name}->{$service_name}->{last_execution_time}) { + $self->{logger}->writeLogInfo("gorgone-newtest skip: service '$service_name' for host '$host_name' already submitted."); + next; + } + + if ($self->{current_status} == 2) { + $self->get_newtest_diagnostic( + scenario => $scenario_name, robot => $robot_name, + host_name => $host_name, service_name => $service_name + ); + } + + if ($self->get_scenario_results(scenario => $scenario_name, robot => $robot_name, + host_name => $host_name, service_name => $service_name) == 1) { + $self->{current_text} = sprintf("No result avaiblable for scenario '%s'", $scenario_name); + $self->{current_status} = 3; + } + $self->add_output(time => $last_check, host_name => $host_name, service_name => $service_name); + } + } + + return 0; +} + +sub action_newtestresync { + my ($self, %options) = @_; + + $self->{logger}->writeLogDebug("gorgone-newtest: container $self->{container_id}: begin resync"); + $self->newtestresync_init(); + + return -1 if ($self->get_poller_id()); + return -1 if ($self->get_centreondb_cache()); + return -1 if ($self->get_centstoragedb_cache()); + + return -1 if ($self->get_newtest_scenarios(%options)); + + $self->push_config(); + $self->submit_external_cmd(); + + return 0; +} + +sub event { + while (1) { + my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $socket); + + $connector->{logger}->writeLogDebug("gorgone-newtest: class: $message"); + if ($message =~ /^\[(.*?)\]/) { + if ((my $method = $connector->can('action_' . lc($1)))) { + $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; + my ($action, $token) = ($1, $2); + my $data = JSON::XS->new->utf8->decode($3); + while ($method->($connector, token => $token, data => $data)) { + # We block until it's fixed!! + sleep(5); + } + } + } + + last unless (centreon::gorgone::common::zmq_still_read(socket => $socket)); + } +} + +sub run { + my ($self, %options) = @_; + + # Database creation. We stay in the loop still there is an error + $self->{db_centstorage} = centreon::misc::db->new( + dsn => $self->{config_db_centstorage}{dsn}, + user => $self->{config_db_centstorage}{username}, + password => $self->{config_db_centstorage}{password}, + force => 2, + logger => $self->{logger} + ); + $self->{db_centreon} = centreon::misc::db->new( + dsn => $self->{config_db_centreon}{dsn}, + user => $self->{config_db_centreon}{username}, + password => $self->{config_db_centreon}{password}, + force => 2, + logger => $self->{logger} + ); + ##### Load objects ##### + $self->{class_object_centstorage} = centreon::misc::objects::object->new(logger => $self->{logger}, db_centreon => $self->{db_centstorage}); + $self->{class_object_centreon} = centreon::misc::objects::object->new(logger => $self->{logger}, db_centreon => $self->{db_centreon}); + $SOAP::Constants::PREFIX_ENV = 'SOAP-ENV'; + $self->{instance} = modules::gorgonenewtest::newtest::stubs::ManagementConsoleService->new(); + + # Connect internal + $socket = centreon::gorgone::common::connect_com( + zmq_type => 'ZMQ_DEALER', name => 'gorgonenewtest-' . $self->{container_id}, + logger => $self->{logger}, + type => $self->{config_core}{internal_com_type}, + path => $self->{config_core}{internal_com_path} + ); + centreon::gorgone::common::zmq_send_message( + socket => $socket, + action => 'NEWTESTREADY', data => { container_id => $self->{container_id} }, + json_encode => 1 + ); + $self->{poll} = [ + { + socket => $socket, + events => ZMQ_POLLIN, + callback => \&event, + } + ]; + while (1) { + # we try to do all we can + my $rev = zmq_poll($self->{poll}, 5000); + if (defined($rev) && $rev == 0 && $self->{stop} == 1) { + $self->{logger}->writeLogInfo("gorgone-newtest $$ has quit"); + zmq_close($socket); + exit(0); + } + + if (time() - $self->{resync_time} > $self->{last_resync_time}) { + $self->{last_resync_time} = time(); + $self->action_newtestresync(); + } + } +} + +1; diff --git a/gorgone/modules/gorgonenewtest/hooks.pm b/gorgone/modules/gorgonenewtest/hooks.pm new file mode 100644 index 00000000000..1563c3a411c --- /dev/null +++ b/gorgone/modules/gorgonenewtest/hooks.pm @@ -0,0 +1,272 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package modules::gorgonenewtest::hooks; + +use warnings; +use strict; +use JSON::XS; +use centreon::script::gorgonecore; +use modules::gorgonenewtest::class; + +my ($config_core, $config); +my ($config_db_centreon, $config_db_centstorage); +my $module_id = 'gorgonenewtest'; +my $events = [ + 'NEWTESTREADY', + 'NEWTESTRESYNC', +]; + +my $last_containers = {}; # Last values from config ini +my $containers = {}; +my $containers_pid = {}; +my $stop = 0; +my $timer_check = time(); +my $config_check_containers_time; + +sub register { + my (%options) = @_; + + $config = $options{config}; + $config_core = $options{config_core}; + $config_db_centstorage = $options{config_db_centstorage}; + $config_db_centreon = $options{config_db_centreon}; + $config_check_containers_time = defined($config->{check_containers_time}) ? $config->{check_containers_time} : 3600; + return ($events, $module_id); +} + +sub init { + my (%options) = @_; + + $last_containers = get_containers(logger => $options{logger}); + foreach my $container_id (keys %$last_containers) { + create_child(container_id => $container_id, logger => $options{logger}); + } +} + +sub routing { + my (%options) = @_; + + my $data; + eval { + $data = JSON::XS->new->utf8->decode($options{data}); + }; + if ($@) { + $options{logger}->writeLogError("Cannot decode json data: $@"); + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => 300, token => $options{token}, + data => { message => 'gorgone-newtest: cannot decode json' }, + json_encode => 1 + ); + return undef; + } + + if ($options{action} eq 'NEWTESTREADY') { + $containers->{$data->{container_id}}->{ready} = 1; + return undef; + } + + if (!defined($data->{container_id}) || !defined($last_containers->{$data->{container_id}})) { + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => 300, token => $options{token}, + data => { message => 'gorgone-newtest: need a valid container id' }, + json_encode => 1 + ); + return undef; + } + + if (centreon::script::gorgonecore::waiting_ready(ready => \$containers->{$data->{container_id}}->{ready}) == 0) { + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => 300, token => $options{token}, + data => { message => 'gorgone-newtest: still no ready' }, + json_encode => 1 + ); + return undef; + } + + centreon::gorgone::common::zmq_send_message( + socket => $options{socket}, identity => 'gorgonenewtest-' . $data->{container_id}, + action => $options{action}, data => $options{data}, token => $options{token}, + ); +} + +sub gently { + my (%options) = @_; + + $stop = 1; + foreach my $container_id (keys %$containers) { + $options{logger}->writeLogInfo("gorgone-newtest: Send TERM signal for container '" . $container_id . "'"); + if ($containers->{$container_id}->{running} == 1) { + CORE::kill('TERM', $containers->{$container_id}->{pid}); + } + } +} + +sub kill_internal { + my (%options) = @_; + + foreach (keys %$containers) { + if ($containers->{$_}->{running} == 1) { + $options{logger}->writeLogInfo("gorgone-newtest: Send KILL signal for container '" . $_ . "'"); + CORE::kill('KILL', $containers->{$_}->{pid}); + } + } +} + +sub kill { + my (%options) = @_; + + +} + +sub check { + my (%options) = @_; + + if ($timer_check - time() > $config_check_containers_time) { + sync_container_childs(logger => $options{logger}); + $timer_check = time(); + } + + my $count = 0; + foreach my $pid (keys %{$options{dead_childs}}) { + # Not me + next if (!defined($containers_pid->{$pid})); + + # If someone dead, we recreate + delete $containers->{$containers_pid->{$pid}}; + delete $containers_pid->{$pid}; + delete $options{dead_childs}->{$pid}; + if ($stop == 0) { + # Need to check if we need to recreate (can be a container destruction)!!! + sync_container_childs(logger => $options{logger}); + } + } + + return $count; +} + +# Specific functions +sub get_containers { + my (%options) = @_; + + my $containers = {}; + return $containers if (!defined($config->{containers})); + foreach my $container_id (split /,/, $config->{containers}) { + next if ($container_id eq ''); + + if (!defined($config->{$container_id . '_nmc_endpoint'}) || $config->{$container_id . '_nmc_endpoint'} eq '') { + $options{logger}->writeLogError("gorgone-newtest: cannot load container '" . $container_id . "' - please set nmc_endpoint option"); + next; + } + if (!defined($config->{$container_id . '_poller_name'}) || $config->{$container_id . '_poller_name'} eq '') { + $options{logger}->writeLogError("gorgone-newtest: cannot load container '" . $container_id . "' - please set poller_name option"); + next; + } + if (!defined($config->{$container_id . '_list_scenario_status'}) || $config->{$container_id . '_list_scenario_status'} eq '') { + $options{logger}->writeLogError("gorgone-newtest: cannot load container '" . $container_id . "' - please set list_scenario_status option"); + next; + } + + my $list_scenario; + eval { + $list_scenario = JSON::XS->new->utf8->decode($config->{$container_id . '_list_scenario_status'}); + }; + if ($@) { + $options{logger}->writeLogError("gorgone-newtest: cannot load container '" . $container_id . "' - cannot decode list scenario option"); + next; + } + + $containers->{$container_id} = { + nmc_endpoint => $config->{$container_id . '_nmc_endpoint'}, + nmc_timeout => (defined($config->{$container_id . '_nmc_timeout'}) && $config->{$container_id . '_nmc_timeout'} =~ /(\d+)/) ? + $1 : 10, + nmc_username => $config->{$container_id . '_nmc_username'}, + nmc_password => $config->{$container_id . '_nmc_password'}, + poller_name => $config->{$container_id . '_poller_name'}, + list_scenario_status => $list_scenario, + resync_time => + (defined($config->{$container_id . '_resync_time'}) && $config->{$container_id . '_resync_time'} =~ /(\d+)/) ? + $1 : 300, + host_template => + defined($config->{$container_id . '_host_template'}) && $config->{$container_id . '_host_template'} ne '' ? $config->{$container_id . '_host_template'} : 'generic-active-host-custom', + host_prefix => + defined($config->{$container_id . '_host_prefix'}) && $config->{$container_id . '_host_prefix'} ne '' ? $config->{$container_id . '_host_prefix'} : 'Robot-%s', + service_template => + defined($config->{$container_id . '_service_template'}) && $config->{$container_id . '_service_template'} ne '' ? $config->{$container_id . '_service_template'} : 'generic-passive-service-custom', + service_prefix => + defined($config->{$container_id . '_service_prefix'}) && $config->{$container_id . '_service_prefix'} ne '' ? $config->{$container_id . '_service_prefix'} : 'Scenario-%s', + }; + } + + return $containers; +} + +sub sync_container_childs { + my (%options) = @_; + + $last_containers = get_containers(logger => $options{logger}); + foreach my $container_id (keys %$last_containers) { + if (!defined($containers->{$container_id})) { + create_child(container_id => $container_id, logger => $options{logger}); + } + } + + # Check if need to delete on containers + foreach my $container_id (keys %$containers) { + next if (defined($last_containers->{$container_id})); + + if ($containers->{$container_id}->{running} == 1) { + $options{logger}->writeLogInfo("gorgone-newtest: Send KILL signal for container '" . $container_id . "'"); + CORE::kill('KILL', $containers->{$container_id}->{pid}); + } + + delete $containers_pid->{ $containers->{$container_id}->{pid} }; + delete $containers->{$container_id}; + } +} + +sub create_child { + my (%options) = @_; + + $options{logger}->writeLogInfo("Create gorgone-newtest for container '" . $options{container_id} . "'"); + my $child_pid = fork(); + if ($child_pid == 0) { + $0 = 'gorgone-newtest'; + my $module = modules::gorgonenewtest::class->new( + logger => $options{logger}, + config_core => $config_core, + config => $config, + config_db_centreon => $config_db_centreon, + config_db_centstorage => $config_db_centstorage, + config_newtest => $last_containers->{$options{container_id}}, + container_id => $options{container_id}, + ); + $module->run(); + exit(0); + } + $options{logger}->writeLogInfo("PID $child_pid gorgone-newtest for container '" . $options{container_id} . "'"); + $containers->{$options{container_id}} = { pid => $child_pid, ready => 0, running => 1 }; + $containers_pid->{$child_pid} = $options{container_id}; +} + +1; diff --git a/gorgone/modules/gorgonenewtest/newtest/stubs/ManagementConsoleService.pm b/gorgone/modules/gorgonenewtest/newtest/stubs/ManagementConsoleService.pm new file mode 100644 index 00000000000..a8ce6ef6b9d --- /dev/null +++ b/gorgone/modules/gorgonenewtest/newtest/stubs/ManagementConsoleService.pm @@ -0,0 +1,392 @@ +package modules::gorgonenewtest::newtest::stubs::ManagementConsoleService; + +sub SOAP::Serializer::as_SearchMode { + my $self = shift; + my($value, $name, $type, $attr) = @_; + return [$name, {'xsi:type' => 'tns:SearchMode', %$attr}, $value]; +} + +sub SOAP::Serializer::as_MessageCategory { + my $self = shift; + my($value, $name, $type, $attr) = @_; + return [$name, {'xsi:type' => 'tns:MessageCategory', %$attr}, $value]; +} + +sub SOAP::Serializer::as_ArrayOfString { + my $self = shift; + my($value, $name, $type, $attr) = @_; + + my $args = []; + foreach (@$value) { + push @$args, SOAP::Data->new(name => 'string', type => 's:string', attr => {}, prefix => 'tns', value => $_); + } + return [$name, {'xsi:type' => 'tns:ArrayOfString', %$attr}, $args]; +} + +# Generated by SOAP::Lite (v0.712) for Perl -- soaplite.com +# Copyright (C) 2000-2006 Paul Kulchenko, Byrne Reese +# -- generated at [Tue Oct 7 11:04:21 2014] +# -- generated from http://192.168.6.84/nws/managementconsoleservice.asmx?wsdl +my %methods = ( +ListInformationRangesFromDWH => { + endpoint => '', + soapaction => 'http://www.auditec-newtest.com/ListInformationRangesFromDWH', + namespace => 'http://www.auditec-newtest.com', + parameters => [ + SOAP::Data->new(name => 'mode', type => 'tns:SearchMode', attr => {}), + SOAP::Data->new(name => 'args', type => 'tns:ArrayOfString', attr => {}), + ], # end parameters + }, # end ListInformationRangesFromDWH +ListComponentStatus => { + endpoint => '', + soapaction => 'http://www.auditec-newtest.com/ListComponentStatus', + namespace => 'http://www.auditec-newtest.com', + parameters => [ + SOAP::Data->new(name => 'mode', type => 'tns:SearchMode', attr => {}), + SOAP::Data->new(name => 'args', type => 'tns:ArrayOfString', attr => {}), + ], # end parameters + }, # end ListComponentStatus +IsOptionAllowed => { + endpoint => '', + soapaction => 'http://www.auditec-newtest.com/IsOptionAllowed', + namespace => 'http://www.auditec-newtest.com', + parameters => [ + SOAP::Data->new(name => 'optionId', type => 's:int', attr => {}), + ], # end parameters + }, # end IsOptionAllowed +SendCommand => { + endpoint => '', + soapaction => 'http://www.auditec-newtest.com/SendCommand', + namespace => 'http://www.auditec-newtest.com', + parameters => [ + SOAP::Data->new(name => 'commandType', type => 'tns:CommandType', attr => {}), + SOAP::Data->new(name => 'agentName', type => 's:string', attr => {}), + SOAP::Data->new(name => 'args', type => 'tns:ArrayOfString', attr => {}), + ], # end parameters + }, # end SendCommand +ListInformationRanges => { + endpoint => '', + soapaction => 'http://www.auditec-newtest.com/ListInformationRanges', + namespace => 'http://www.auditec-newtest.com', + parameters => [ + SOAP::Data->new(name => 'mode', type => 'tns:SearchMode', attr => {}), + SOAP::Data->new(name => 'args', type => 'tns:ArrayOfString', attr => {}), + ], # end parameters + }, # end ListInformationRanges +ListResources => { + endpoint => '', + soapaction => 'http://www.auditec-newtest.com/ListResources', + namespace => 'http://www.auditec-newtest.com', + parameters => [ + SOAP::Data->new(name => 'mode', type => 'tns:SearchMode', attr => {}), + SOAP::Data->new(name => 'args', type => 'tns:ArrayOfString', attr => {}), + ], # end parameters + }, # end ListResources +GetLocationProperties => { + endpoint => '', + soapaction => 'http://www.auditec-newtest.com/GetLocationProperties', + namespace => 'http://www.auditec-newtest.com', + parameters => [ + SOAP::Data->new(name => 'locationPath', type => 's:string', attr => {}), + ], # end parameters + }, # end GetLocationProperties +ListLocationChildren => { + endpoint => '', + soapaction => 'http://www.auditec-newtest.com/ListLocationChildren', + namespace => 'http://www.auditec-newtest.com', + parameters => [ + SOAP::Data->new(name => 'locationPath', type => 's:string', attr => {}), + SOAP::Data->new(name => 'recursive', type => 's:boolean', attr => {}), + ], # end parameters + }, # end ListLocationChildren +ListBusinessChildren => { + endpoint => '', + soapaction => 'http://www.auditec-newtest.com/ListBusinessChildren', + namespace => 'http://www.auditec-newtest.com', + parameters => [ + SOAP::Data->new(name => 'businessPath', type => 's:string', attr => {}), + SOAP::Data->new(name => 'recursive', type => 's:boolean', attr => {}), + ], # end parameters + }, # end ListBusinessChildren +GetMeasureProperties => { + endpoint => '', + soapaction => 'http://www.auditec-newtest.com/GetMeasureProperties', + namespace => 'http://www.auditec-newtest.com', + parameters => [ + SOAP::Data->new(name => 'measurePath', type => 's:string', attr => {}), + ], # end parameters + }, # end GetMeasureProperties +GetBusinessProperties => { + endpoint => '', + soapaction => 'http://www.auditec-newtest.com/GetBusinessProperties', + namespace => 'http://www.auditec-newtest.com', + parameters => [ + SOAP::Data->new(name => 'businessPath', type => 's:string', attr => {}), + ], # end parameters + }, # end GetBusinessProperties +ListResults => { + endpoint => '', + soapaction => 'http://www.auditec-newtest.com/ListResults', + namespace => 'http://www.auditec-newtest.com', + parameters => [ + SOAP::Data->new(name => 'mode', type => 'tns:SearchMode', attr => {}, prefix => 'tns'), + SOAP::Data->new(name => 'range', type => 's:int', attr => {}, prefix => 'tns'), + SOAP::Data->new(name => 'args', type => 'tns:ArrayOfString', attr => {}, prefix => 'tns'), + ], # end parameters + }, # end ListResults +ListRobotStatus => { + endpoint => '', + soapaction => 'http://www.auditec-newtest.com/ListRobotStatus', + namespace => 'http://www.auditec-newtest.com', + parameters => [ + SOAP::Data->new(name => 'mode', type => 'tns:SearchMode', attr => {}), + SOAP::Data->new(name => 'args', type => 'tns:ArrayOfString', attr => {}), + ], # end parameters + }, # end ListRobotStatus +ListAllResults => { + endpoint => '', + soapaction => 'http://www.auditec-newtest.com/ListAllResults', + namespace => 'http://www.auditec-newtest.com', + parameters => [ + SOAP::Data->new(name => 'mode', type => 'tns:SearchMode', attr => {}), + SOAP::Data->new(name => 'start', type => 's:dateTime', attr => {}), + SOAP::Data->new(name => 'end', type => 's:dateTime', attr => {}), + SOAP::Data->new(name => 'types', type => 'tns:MeasureType', attr => {}), + SOAP::Data->new(name => 'args', type => 'tns:ArrayOfString', attr => {}), + ], # end parameters + }, # end ListAllResults +ListScenariosStatus => { + endpoint => '', + soapaction => 'http://www.auditec-newtest.com/ListScenariosStatus', + namespace => 'http://www.auditec-newtest.com', + parameters => [ + SOAP::Data->new(name => 'businessPath', type => 's:string', attr => {}), + SOAP::Data->new(name => 'locationPath', type => 's:string', attr => {}), + ], # end parameters + }, # end ListScenariosStatus +ListAlarms => { + endpoint => '', + soapaction => 'http://www.auditec-newtest.com/ListAlarms', + namespace => 'http://www.auditec-newtest.com', + parameters => [ + SOAP::Data->new(name => 'mode', type => 'tns:SearchMode', attr => {}), + SOAP::Data->new(name => 'range', type => 's:int', attr => {}), + SOAP::Data->new(name => 'types', type => 'tns:AlarmType', attr => {}), + SOAP::Data->new(name => 'levels', type => 'tns:AlarmLevel', attr => {}), + SOAP::Data->new(name => 'args', type => 'tns:ArrayOfString', attr => {}), + ], # end parameters + }, # end ListAlarms +ListScenarios => { + endpoint => '', + soapaction => 'http://www.auditec-newtest.com/ListScenarios', + namespace => 'http://www.auditec-newtest.com', + parameters => [ + SOAP::Data->new(name => 'businessPath', type => 's:string', attr => {}), + ], # end parameters + }, # end ListScenarios +ListResultChildren => { + endpoint => '', + soapaction => 'http://www.auditec-newtest.com/ListResultChildren', + namespace => 'http://www.auditec-newtest.com', + parameters => [ + SOAP::Data->new(name => 'resultId', type => 's:long', attr => {}, prefix => 'tns'), + ], # end parameters + }, # end ListResultChildren +GetUserItem => { + endpoint => '', + soapaction => 'http://www.auditec-newtest.com/GetUserItem', + namespace => 'http://www.auditec-newtest.com', + parameters => [ + SOAP::Data->new(name => 'login', type => 's:string', attr => {}), + ], # end parameters + }, # end GetUserItem +ListCollectorStatus => { + endpoint => '', + soapaction => 'http://www.auditec-newtest.com/ListCollectorStatus', + namespace => 'http://www.auditec-newtest.com', + parameters => [ + SOAP::Data->new(name => 'mode', type => 'tns:SearchMode', attr => {}), + SOAP::Data->new(name => 'args', type => 'tns:ArrayOfString', attr => {}), + ], # end parameters + }, # end ListCollectorStatus +GetDiagnostic => { + endpoint => '', + soapaction => 'http://www.auditec-newtest.com/GetDiagnostic', + namespace => 'http://www.auditec-newtest.com', + parameters => [ + SOAP::Data->new(name => 'messageId', type => 's:long', attr => {}), + ], # end parameters + }, # end GetDiagnostic +LogIn => { + endpoint => '', + soapaction => 'http://www.auditec-newtest.com/LogIn', + namespace => 'http://www.auditec-newtest.com', + parameters => [ + SOAP::Data->new(name => 'login', type => 's:string', attr => {}), + SOAP::Data->new(name => 'password', type => 's:string', attr => {}), + ], # end parameters + }, # end LogIn +ListCustomGroupChildren => { + endpoint => '', + soapaction => 'http://www.auditec-newtest.com/ListCustomGroupChildren', + namespace => 'http://www.auditec-newtest.com', + parameters => [ + SOAP::Data->new(name => 'customGroupPath', type => 's:string', attr => {}), + SOAP::Data->new(name => 'recursive', type => 's:boolean', attr => {}), + ], # end parameters + }, # end ListCustomGroupChildren +GetCustomGroupProperties => { + endpoint => '', + soapaction => 'http://www.auditec-newtest.com/GetCustomGroupProperties', + namespace => 'http://www.auditec-newtest.com', + parameters => [ + SOAP::Data->new(name => 'customGroupPath', type => 's:string', attr => {}), + ], # end parameters + }, # end GetCustomGroupProperties +ListMessages => { + endpoint => '', + soapaction => 'http://www.auditec-newtest.com/ListMessages', + namespace => 'http://www.auditec-newtest.com', + parameters => [ + SOAP::Data->new(name => 'mode', type => 'tns:SearchMode', attr => {}, prefix => 'tns'), + SOAP::Data->new(name => 'range', type => 's:int', attr => {}, prefix => 'tns'), + SOAP::Data->new(name => 'categories', type => 'tns:MessageCategory', attr => {}, prefix => 'tns'), + SOAP::Data->new(name => 'args', type => 'tns:ArrayOfString', attr => {}, prefix => 'tns'), + ], # end parameters + }, # end ListMessages +ListScenarioStatus => { + endpoint => '', + soapaction => 'http://www.auditec-newtest.com/ListScenarioStatus', + namespace => 'http://www.auditec-newtest.com', + parameters => [ + SOAP::Data->new(name => 'mode', type => 'tns:SearchMode', attr => {}, prefix => 'tns'), + SOAP::Data->new(name => 'range', type => 's:int', attr => {}, prefix => 'tns'), + SOAP::Data->new(name => 'args', type => 'tns:ArrayOfString', attr => {}, prefix => 'tns'), + ], # end parameters + }, # end ListScenarioStatus +ListMeasureResults => { + endpoint => '', + soapaction => 'http://www.auditec-newtest.com/ListMeasureResults', + namespace => 'http://www.auditec-newtest.com', + parameters => [ + SOAP::Data->new(name => 'measureId', type => 's:string', attr => {}), + SOAP::Data->new(name => 'locationPath', type => 's:string', attr => {}), + SOAP::Data->new(name => 'range', type => 's:int', attr => {}), + SOAP::Data->new(name => 'recursive', type => 's:boolean', attr => {}), + SOAP::Data->new(name => 'types', type => 'tns:MeasureType', attr => {}), + ], # end parameters + }, # end ListMeasureResults +GetUserProperties => { + endpoint => '', + soapaction => 'http://www.auditec-newtest.com/GetUserProperties', + namespace => 'http://www.auditec-newtest.com', + parameters => [ + SOAP::Data->new(name => 'login', type => 's:string', attr => {}), + ], # end parameters + }, # end GetUserProperties +ListMeasureChildren => { + endpoint => '', + soapaction => 'http://www.auditec-newtest.com/ListMeasureChildren', + namespace => 'http://www.auditec-newtest.com', + parameters => [ + SOAP::Data->new(name => 'measureId', type => 's:string', attr => {}), + SOAP::Data->new(name => 'types', type => 'tns:MeasureType', attr => {}), + SOAP::Data->new(name => 'recursive', type => 's:boolean', attr => {}), + SOAP::Data->new(name => 'measurePath', type => 's:string', attr => {}), + SOAP::Data->new(name => 'recursive', type => 's:boolean', attr => {}), + ], # end parameters + }, # end ListMeasureChildren +GetLicenceOptionValue => { + endpoint => '', + soapaction => 'http://www.auditec-newtest.com/GetLicenceOptionValue', + namespace => 'http://www.auditec-newtest.com', + parameters => [ + SOAP::Data->new(name => 'optionId', type => 's:int', attr => {}), + ], # end parameters + }, # end GetLicenceOptionValue +); # end my %methods + +use SOAP::Lite; +use modules::gorgonenewtest::newtest::stubs::errors; +use Exporter; +use Carp (); + +use vars qw(@ISA $AUTOLOAD @EXPORT_OK %EXPORT_TAGS); +@ISA = qw(Exporter SOAP::Lite); +@EXPORT_OK = (keys %methods); +%EXPORT_TAGS = ('all' => [@EXPORT_OK]); + +sub _call { + my ($self, $method) = (shift, shift); + my $name = UNIVERSAL::isa($method => 'SOAP::Data') ? $method->name : $method; + my %method = %{$methods{$name}}; + $self->on_fault(\&modules::gorgonenewtest::newtest::stubs::errors::soapGetBad); + $self->proxy($method{endpoint} || Carp::croak "No server address (proxy) specified") + unless $self->proxy; + my @templates = @{$method{parameters}}; + my @parameters = (); + foreach my $param (@_) { + if (@templates) { + my $template = shift @templates; + my ($prefix,$typename) = SOAP::Utils::splitqname($template->type); + my $method = 'as_'.$typename; + # TODO - if can('as_'.$typename) {...} + my $result = $self->serializer->$method($param, $template->name, $template->type, $template->attr); + #print Data::Dumper::Dumper($result); + push(@parameters, $template->value($result->[2])); + } + else { + push(@parameters, $param); + } + } + $self->endpoint($method{endpoint}) + ->ns($method{namespace}) + ->on_action(sub{qq!"$method{soapaction}"!}); + $self->serializer->register_ns("http://microsoft.com/wsdl/mime/textMatching/","tm"); + $self->serializer->register_ns("http://schemas.xmlsoap.org/wsdl/soap12/","soap12"); + $self->serializer->register_ns("http://schemas.xmlsoap.org/wsdl/mime/","mime"); + $self->serializer->register_ns("http://www.w3.org/2001/XMLSchema","s"); + $self->serializer->register_ns("http://schemas.xmlsoap.org/wsdl/soap/","soap"); + $self->serializer->register_ns("http://schemas.xmlsoap.org/wsdl/","wsdl"); + $self->serializer->register_ns("http://schemas.xmlsoap.org/soap/encoding/","soapenc"); + $self->serializer->register_ns("http://schemas.xmlsoap.org/wsdl/http/","http"); + $self->serializer->register_ns("http://www.auditec-newtest.com","tns"); + my $som = $self->SUPER::call($method => @parameters); + if ($self->want_som) { + return $som; + } + UNIVERSAL::isa($som => 'SOAP::SOM') ? wantarray ? $som->paramsall : $som->result : $som; +} + +sub BEGIN { + no strict 'refs'; + for my $method (qw(want_som)) { + my $field = '_' . $method; + *$method = sub { + my $self = shift->new; + @_ ? ($self->{$field} = shift, return $self) : return $self->{$field}; + } + } +} +no strict 'refs'; +for my $method (@EXPORT_OK) { + my %method = %{$methods{$method}}; + *$method = sub { + my $self = UNIVERSAL::isa($_[0] => __PACKAGE__) + ? ref $_[0] + ? shift # OBJECT + # CLASS, either get self or create new and assign to self + : (shift->self || __PACKAGE__->self(__PACKAGE__->new)) + # function call, either get self or create new and assign to self + : (__PACKAGE__->self || __PACKAGE__->self(__PACKAGE__->new)); + $self->_call($method, @_); + } +} + +sub AUTOLOAD { + my $method = substr($AUTOLOAD, rindex($AUTOLOAD, '::') + 2); + return if $method eq 'DESTROY' || $method eq 'want_som'; + die "Unrecognized method '$method'. List of available method(s): @EXPORT_OK\n"; +} + +1; diff --git a/gorgone/modules/gorgonenewtest/newtest/stubs/errors.pm b/gorgone/modules/gorgonenewtest/newtest/stubs/errors.pm new file mode 100644 index 00000000000..d3522252674 --- /dev/null +++ b/gorgone/modules/gorgonenewtest/newtest/stubs/errors.pm @@ -0,0 +1,31 @@ + +package modules::gorgonenewtest::newtest::stubs::errors; + +use strict; +use warnings; + +our $SOAP_ERRORS; + +sub soapGetBad { + my $soap = shift; + my $res = shift; + + if(ref($res)) { + chomp( my $err = $res->faultstring ); + $SOAP_ERRORS = "SOAP FAULT: $err"; + } else { + chomp( my $err = $soap->transport->status ); + $SOAP_ERRORS = "TRANSPORT ERROR: $err"; + } + return new SOAP::SOM; +} + +sub get_error { + my $error = $SOAP_ERRORS; + + $SOAP_ERRORS = undef; + return $error; +} + +1; + diff --git a/gorgone/modules/gorgonenewtest/newtest/wsdl/newtest.wsdl b/gorgone/modules/gorgonenewtest/newtest/wsdl/newtest.wsdl new file mode 100644 index 00000000000..f5cb180daec --- /dev/null +++ b/gorgone/modules/gorgonenewtest/newtest/wsdl/newtest.wsdl @@ -0,0 +1,2097 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Return user item (Obsolete). + + + + + Return a list of scenarios instances statuses (Obsolete). + + + + + Return a list of results for a specified measure/agent item. Ordering of results is as follows: results, subresults, measure rank within subresults (Obsolete). + + + + + Return a list of children for a specific Measure node. Result set is ordered first into a hierarchical structure (measure, sub-measure) with a post-ordering on the measures' rank and then display name (Obsolete). + + + + + Check Licence Options + + + + + Get Option Value + + + + + Method used to log user in + + + + + Return user item + + + + + Gets a list of children of a specified Business node. + + + + + Gets specified Business node properties. + + + + + Return a list of children of a specified Location node. + + + + + Return specified Location node properties. + + + + + Return a list of children of a specified CustomGroup node. + + + + + Return CustomGroup node properties. + + + + + Return a list of robots statuses. + + + + + Return a list of Collectors statuses. + + + + + Return a list of Components statuses. + + + + + Gets a list of children of a specified Business node. + + + + + Return a list of children for a specified Measure node. Result set is ordered first into a hierarchical structure (measure, sub-measure) with a post-ordering on the measures' rank and then display name. + + + + + Return measure properties. + + + + + Return a list of scenarios instances statuses. + + + + + Return a list of results for a specific measure item. Ordering of results is as follows: results, subresults, measure rank within subresults. + + + + + Return a list of results for a specific measure item. Ordering of results is as follows: results, subresults, measure rank within subresults. + + + + + Return a list of sub results for a specific result item + + + + + Returns a list of alarms for specified parameter + + + + + Returns a list of messages for specified item + + + + + Returns a list of messages for specified item + + + + + Returns a list of information ranges from Newtest DWH for specified item + + + + + Returns a list of resources for specified item + + + + + Return diagnostic content for given message Id + + + + + Sends a command to a couple measure/agent + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gorgone/modules/gorgonescom/class.pm b/gorgone/modules/gorgonescom/class.pm index 8b0569a1d93..f02b12b8d8b 100644 --- a/gorgone/modules/gorgonescom/class.pm +++ b/gorgone/modules/gorgonescom/class.pm @@ -18,7 +18,7 @@ # limitations under the License. # -package modules::scom::class; +package modules::gorgonescom::class; use strict; use warnings; @@ -81,7 +81,7 @@ sub handle_HUP { sub handle_TERM { my $self = shift; - $self->{logger}->writeLogInfo("gorgonescom $$ Receiving order to stop..."); + $self->{logger}->writeLogInfo("gorgone-scom $$ Receiving order to stop..."); $self->{stop} = 1; } @@ -105,7 +105,7 @@ sub json_encode { $encoded_arguments = JSON::XS->new->utf8->encode($options{argument}); }; if ($@) { - $self->{logger}->writeLogError("gorgonescom: container $self->{container_id}: scom $options{method} - cannot encode json: $@"); + $self->{logger}->writeLogError("gorgone-scom: container $self->{container_id}: scom $options{method} - cannot encode json: $@"); return 1; } @@ -116,13 +116,13 @@ sub http_check_error { my ($self, %options) = @_; if ($options{status} == 1) { - $self->{logger}->writeLogError("gorgonescom: container $self->{container_id}: scom $options{method} issue"); + $self->{logger}->writeLogError("gorgone-scom: container $self->{container_id}: scom $options{method} issue"); return 1; } my $code = $self->{http}->get_code(); if ($code !~ /^2/) { - $self->{logger}->writeLogError("gorgonescom: container $self->{container_id}: scom $options{method} issue - " . $self->{http}->get_message()); + $self->{logger}->writeLogError("gorgone-scom: container $self->{container_id}: scom $options{method} issue - " . $self->{http}->get_message()); return 1; } @@ -149,7 +149,7 @@ sub scom_authenticate { if (defined($header) && $header =~ /SCOMSessionId=([^;]+);/i) { $connector->{scom_session_id} = $1; } else { - $self->{logger}->writeLogError("gorgonescom: container $self->{container_id}: scom authenticate issue - error retrieving cookie"); + $self->{logger}->writeLogError("gorgone-scom: container $self->{container_id}: scom authenticate issue - error retrieving cookie"); return 1; } @@ -222,16 +222,16 @@ sub action_scomresync { my ($self, %options) = @_; my ($status, $sth, $resource_configs); - $self->{logger}->writeLogDebug("gorgonescom: container $self->{container_id}: begin resync"); + $self->{logger}->writeLogDebug("gorgone-scom: container $self->{container_id}: begin resync"); $self->get_realtime_slots(); if (scalar(@{$self->{realtime_slots}}) <= 0) { - $self->{logger}->writeLogError("gorgonescom: container $self->{container_id}: cannot find realtime slots"); + $self->{logger}->writeLogError("gorgone-scom: container $self->{container_id}: cannot find realtime slots"); return 1; } if ($self->get_realtime_scom_alerts() == 0) { - $self->{logger}->writeLogError("gorgonescom: container $self->{container_id}: cannot get scom realtime alerts"); + $self->{logger}->writeLogError("gorgone-scom: container $self->{container_id}: cannot get scom realtime alerts"); return 1; } @@ -242,7 +242,7 @@ sub event { while (1) { my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $socket); - $connector->{logger}->writeLogDebug("gorgonescom: class: $message"); + $connector->{logger}->writeLogDebug("gorgone-scom: class: $message"); if ($message =~ /^\[(.*?)\]/) { if ((my $method = $connector->can('action_' . lc($1)))) { $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; @@ -297,7 +297,7 @@ sub run { # we try to do all we can my $rev = zmq_poll($self->{poll}, 5000); if (defined($rev) && $rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("gorgonescom $$ has quit"); + $self->{logger}->writeLogInfo("gorgone-scom $$ has quit"); zmq_close($socket); exit(0); } diff --git a/gorgone/modules/gorgonescom/hooks.pm b/gorgone/modules/gorgonescom/hooks.pm index d6506991dc4..b5264975651 100644 --- a/gorgone/modules/gorgonescom/hooks.pm +++ b/gorgone/modules/gorgonescom/hooks.pm @@ -113,7 +113,7 @@ sub gently { $stop = 1; foreach my $container_id (keys %$containers) { - $options{logger}->writeLogInfo("gorgonescom: Send TERM signal for container '" . $container_id . "'"); + $options{logger}->writeLogInfo("gorgone-scom: Send TERM signal for container '" . $container_id . "'"); if ($containers->{$container_id}->{running} == 1) { CORE::kill('TERM', $containers->{$container_id}->{pid}); } @@ -125,7 +125,7 @@ sub kill_internal { foreach (keys %$containers) { if ($containers->{$_}->{running} == 1) { - $options{logger}->writeLogInfo("gorgonescom: Send KILL signal for container '" . $_ . "'"); + $options{logger}->writeLogInfo("gorgone-scom: Send KILL signal for container '" . $_ . "'"); CORE::kill('KILL', $containers->{$_}->{pid}); } } @@ -173,11 +173,11 @@ sub get_containers { next if ($container_id eq '' || !defined($config->{$container_id . '_url'})); if (!defined($config->{$container_id . '_dsmhost'}) || $config->{$container_id . '_dsmhost'} eq '') { - $options{logger}->writeLogError("gorgonescom: cannot load container '" . $container_id . "' - please set dsmhost option"); + $options{logger}->writeLogError("gorgone-scom: cannot load container '" . $container_id . "' - please set dsmhost option"); next; } if (!defined($config->{$container_id . '_dsmslot'}) || $config->{$container_id . '_dsmslot'} eq '') { - $options{logger}->writeLogError("gorgonescom: cannot load container '" . $container_id . "' - please set dsmslot option"); + $options{logger}->writeLogError("gorgone-scom: cannot load container '" . $container_id . "' - please set dsmslot option"); next; } @@ -215,7 +215,7 @@ sub sync_container_childs { next if (defined($last_containers->{$container_id})); if ($containers->{$container_id}->{running} == 1) { - $options{logger}->writeLogInfo("gorgonescom: Send KILL signal for container '" . $container_id . "'"); + $options{logger}->writeLogInfo("gorgone-scom: Send KILL signal for container '" . $container_id . "'"); CORE::kill('KILL', $containers->{$container_id}->{pid}); } @@ -227,11 +227,11 @@ sub sync_container_childs { sub create_child { my (%options) = @_; - $options{logger}->writeLogInfo("Create gorgonescom for container '" . $options{container_id} . "'"); + $options{logger}->writeLogInfo("Create gorgone-scom for container '" . $options{container_id} . "'"); my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-scom'; - my $module = modules::scom::class->new( + my $module = modules::gorgonescom::class->new( logger => $options{logger}, config_core => $config_core, config => $config, @@ -242,7 +242,7 @@ sub create_child { $module->run(); exit(0); } - $options{logger}->writeLogInfo("PID $child_pid gorgonescom for container '" . $options{container_id} . "'"); + $options{logger}->writeLogInfo("PID $child_pid gorgone-scom for container '" . $options{container_id} . "'"); $containers->{$options{container_id}} = { pid => $child_pid, ready => 0, running => 1 }; $containers_pid->{$child_pid} = $options{container_id}; } From 43d368717ae82d37f92445732272d87165058109 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Thu, 4 Jul 2019 14:13:30 +0200 Subject: [PATCH 007/948] add log in newtest module --- gorgone/modules/gorgonecron/class.pm | 6 ++-- gorgone/modules/gorgonenewtest/class.pm | 46 ++++++++++++++++++++++--- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/gorgone/modules/gorgonecron/class.pm b/gorgone/modules/gorgonecron/class.pm index 06ac60b490e..3d2110335ec 100644 --- a/gorgone/modules/gorgonecron/class.pm +++ b/gorgone/modules/gorgonecron/class.pm @@ -97,7 +97,7 @@ sub cron_sleep { sub dispatcher { my ($options) = @_; - $options->{logger}->writeLogInfo("Job is starting"); + $options->{logger}->writeLogInfo('gorgone-cron Job is starting'); } sub run { @@ -116,11 +116,11 @@ sub run { json_encode => 1 ); $self->{poll} = [ - { + { socket => $socket, events => ZMQ_POLLIN, callback => \&event, - } + } ]; my $cron = new Schedule::Cron(\&dispatcher, nostatus => 1, nofork => 1); $cron->add_entry('* * * * *', \&dispatcher, { logger => $self->{logger}, plop => 1 }); diff --git a/gorgone/modules/gorgonenewtest/class.pm b/gorgone/modules/gorgonenewtest/class.pm index beb9b95429d..de1845b0bce 100644 --- a/gorgone/modules/gorgonenewtest/class.pm +++ b/gorgone/modules/gorgonenewtest/class.pm @@ -107,6 +107,28 @@ sub class_handle_HUP { } } +sub send_log { + my ($self, %options) = @_; + + return if (!defined($options{token})); + + if (!defined($self->{socket_log})) { + $self->{socket_log} = centreon::gorgone::common::connect_com( + zmq_type => 'ZMQ_DEALER', name => 'gorgonenewtest-'. $self->{container_id}, + logger => $self->{logger}, linger => 5000, + type => $self->{config_core}{internal_com_type}, + path => $self->{config_core}{internal_com_path} + ); + } + + centreon::gorgone::common::zmq_send_message( + socket => $self->{socket_log}, + action => 'PUTLOG', + data => { code => $options{code}, etime => time(), token => $options{token}, data => $options{data} }, + json_encode => 1 + ); +} + my %map_scenario_status = ( Available => 0, Warning => 1, Failed => 2, Suspended => 2, Canceled => 2, Unknown => 3, OutOfRange => 3, @@ -134,7 +156,6 @@ sub newtestresync_init { $self->{cache_robot_list_results} = undef; } - sub perfdata_add { my ($self, %options) = @_; @@ -546,17 +567,31 @@ sub action_newtestresync { my ($self, %options) = @_; $self->{logger}->writeLogDebug("gorgone-newtest: container $self->{container_id}: begin resync"); + $self->send_log(code => 301, token => $options{token}, data => { message => 'action newtestresync proceed' }); $self->newtestresync_init(); - return -1 if ($self->get_poller_id()); - return -1 if ($self->get_centreondb_cache()); - return -1 if ($self->get_centstoragedb_cache()); + if ($self->get_poller_id()) { + $self->send_log(code => 305, token => $options{token}, data => { message => 'cannot get poller id' }); + return -1; + } + if ($self->get_centreondb_cache()) { + $self->send_log(code => 305, token => $options{token}, data => { message => 'cannot get centreon config cache' }); + return -1; + } + if ($self->get_centstoragedb_cache()) { + $self->send_log(code => 305, token => $options{token}, data => { message => 'cannot get centreon storage cache' }); + return -1; + } - return -1 if ($self->get_newtest_scenarios(%options)); + if ($self->get_newtest_scenarios(%options)) { + $self->send_log(code => 305, token => $options{token}, data => { message => 'cannot get newtest scenarios' }); + return -1; + } $self->push_config(); $self->submit_external_cmd(); + $self->send_log(code => 306, token => $options{token}, data => { message => 'action newtestresync finished' }); return 0; } @@ -630,6 +665,7 @@ sub run { if (defined($rev) && $rev == 0 && $self->{stop} == 1) { $self->{logger}->writeLogInfo("gorgone-newtest $$ has quit"); zmq_close($socket); + zmq_close($self->{socket_log}) if (defined($self->{socket_log})); exit(0); } From bbb11513043f641eb1bfabd7576902fdfb81f2ec Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Thu, 4 Jul 2019 14:20:40 +0200 Subject: [PATCH 008/948] Fix doc --- gorgone/docs/modules/newtest/exploitation.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/docs/modules/newtest/exploitation.txt b/gorgone/docs/modules/newtest/exploitation.txt index 562a0224efb..46876874e2f 100644 --- a/gorgone/docs/modules/newtest/exploitation.txt +++ b/gorgone/docs/modules/newtest/exploitation.txt @@ -69,7 +69,7 @@ You have to add at least one entry in the configuration. The meaning of attribut - 'poller_name': poller used when the daemon creates a host in Centreon - 'clapi_username' and 'clapi_password': centreon-clapi connections - 'clapi_action_applycfg': could be 'POLLERRELOAD' or 'POLLERRESTART' -- 'ListScenarioStatus': informations to gets from NMC : +- 'list_scenario_status': informations to gets from NMC : - To get all robots: { "search": "All", "instances": [] } - To filter on some robots: { "search": "Robot", "instances": ["XXXX", "YYYY"] } From 2d202d5b0b37e53463fa9644f0c8bae82bb25721 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 17 Jul 2019 09:40:59 +0200 Subject: [PATCH 009/948] add packages epel --- .../perl-Schedule-Cron-1.01-1.el7.noarch.rpm | Bin 0 -> 44950 bytes ...erl-Time-ParseDate-2015.103-1.el7.noarch.rpm | Bin 0 -> 40012 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 gorgone/packages/perl-Schedule-Cron-1.01-1.el7.noarch.rpm create mode 100644 gorgone/packages/perl-Time-ParseDate-2015.103-1.el7.noarch.rpm diff --git a/gorgone/packages/perl-Schedule-Cron-1.01-1.el7.noarch.rpm b/gorgone/packages/perl-Schedule-Cron-1.01-1.el7.noarch.rpm new file mode 100644 index 0000000000000000000000000000000000000000..97735403aa7ca41c1dd846365a12b943f1ad337d GIT binary patch literal 44950 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 Q=(g$K90C9U000VETF~I;WB>pF literal 0 HcmV?d00001 diff --git a/gorgone/packages/perl-Time-ParseDate-2015.103-1.el7.noarch.rpm b/gorgone/packages/perl-Time-ParseDate-2015.103-1.el7.noarch.rpm new file mode 100644 index 0000000000000000000000000000000000000000..d4010fb5dd85d7362558e6ecdb2fbeb49b2ad81c GIT binary patch literal 40012 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

0o(`B9*=pRi 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 literal 0 HcmV?d00001 From 6b05046110500a06c1209621700ede8caf0dd435 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 17 Jul 2019 10:19:49 +0200 Subject: [PATCH 010/948] fix newtest and scom --- gorgone/centreon/misc/http/backend/curl.pm | 8 ++++---- gorgone/centreon/misc/misc.pm | 12 +++++++++++- gorgone/modules/gorgonenewtest/class.pm | 7 ++++--- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/gorgone/centreon/misc/http/backend/curl.pm b/gorgone/centreon/misc/http/backend/curl.pm index eb7348a82bd..9791f72244d 100644 --- a/gorgone/centreon/misc/http/backend/curl.pm +++ b/gorgone/centreon/misc/http/backend/curl.pm @@ -23,7 +23,7 @@ package centreon::misc::http::backend::curl; use strict; use warnings; use URI; -use centreon::plugins::misc; +use centreon::misc::misc; sub new { my ($class, %options) = @_; @@ -38,13 +38,13 @@ sub new { sub check_options { my ($self, %options) = @_; - if (centreon::plugins::misc::mymodule_load( + if (centreon::misc::misc::mymodule_load( logger => $self->{logger}, module => 'Net::Curl::Easy', error_msg => "Cannot load module 'Net::Curl::Easy'." ) == 1) { return 1; } - if (centreon::plugins::misc::mymodule_load( + if (centreon::misc::misc::mymodule_load( logger => $self->{logger}, module => 'centreon::misc::http::backend::curlconstants', error_msg => "Cannot load module 'centreon::misc::http::backend::curlconstants'." ) == 1) { @@ -221,7 +221,7 @@ sub set_extra_curl_opt { foreach (@{$options{request}->{curl_opt}}) { ($fields->{key}, $fields->{value}) = split /=>/; foreach my $label ('key', 'value') { - $fields->{$label} = centreon::plugins::misc::trim($fields->{$label}); + $fields->{$label} = centreon::misc::misc::trim($fields->{$label}); if ($fields->{$label} =~ /^CURLOPT|CURL/) { $fields->{$label} = $self->{constant_cb}->(name => $fields->{$label}); } diff --git a/gorgone/centreon/misc/misc.pm b/gorgone/centreon/misc/misc.pm index e5a17921b66..f50507ebc87 100644 --- a/gorgone/centreon/misc/misc.pm +++ b/gorgone/centreon/misc/misc.pm @@ -226,5 +226,15 @@ sub mymodule_load { } return wantarray ? (0, $file) : 0; } - + +sub trim { + my ($value) = $_[0]; + + # Sometimes there is a null character + $value =~ s/\x00$//; + $value =~ s/^[ \t\n]+//; + $value =~ s/[ \t\n]+$//; + return $value; +} + 1; diff --git a/gorgone/modules/gorgonenewtest/class.pm b/gorgone/modules/gorgonenewtest/class.pm index de1845b0bce..2038ca8dfa9 100644 --- a/gorgone/modules/gorgonenewtest/class.pm +++ b/gorgone/modules/gorgonenewtest/class.pm @@ -22,6 +22,7 @@ package modules::gorgonenewtest::class; use strict; use warnings; +use centreon::misc::misc; use centreon::gorgone::common; use centreon::misc::objects::object; use ZMQ::LibZMQ4; @@ -355,7 +356,7 @@ sub get_newtest_diagnostic { my ($self, %options) = @_; my $result = $self->{instance}->ListMessages('Instance', 30, 'Diagnostics', [$options{scenario}, $options{robot}]); - if (defined(my $com_error = centreon::newtest::stubs::errors::get_error())) { + if (defined(my $com_error = modules::gorgonenewtest::newtest::stubs::errors::get_error())) { $self->{logger}->writeLogError("gorgone-newtest newtest API error 'ListMessages' method: " . $com_error); return -1; } @@ -398,7 +399,7 @@ sub get_scenario_results { } if (!defined($self->{cache_robot_list_results}->{$options{robot}})) { my $result = $self->{instance}->ListResults('Robot', 30, [$options{robot}]); - if (defined(my $com_error = centreon::newtest::stubs::errors::get_error())) { + if (defined(my $com_error = modules::gorgonenewtest::newtest::stubs::errors::get_error())) { $self->{logger}->writeLogError("gorgone-newtest newtest API error 'ListResults' method: " . $com_error); return -1; } @@ -450,7 +451,7 @@ sub get_newtest_extra_metrics { my ($self, %options) = @_; my $result = $self->{instance}->ListResultChildren($options{id}); - if (defined(my $com_error = centreon::newtest::stubs::errors::get_error())) { + if (defined(my $com_error = modules::gorgonenewtest::newtest::stubs::errors::get_error())) { $self->{logger}->writeLogError("gorgone-newtest newtest API error 'ListResultChildren' method: " . $com_error); return -1; } From f688d0bde3d63afe99f3ee70b58288c65f4e7552 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 17 Jul 2019 11:44:21 +0200 Subject: [PATCH 011/948] fix illegal characters newtest --- gorgone/modules/gorgonenewtest/class.pm | 26 +++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/gorgone/modules/gorgonenewtest/class.pm b/gorgone/modules/gorgonenewtest/class.pm index 2038ca8dfa9..6c7aec2f9f5 100644 --- a/gorgone/modules/gorgonenewtest/class.pm +++ b/gorgone/modules/gorgonenewtest/class.pm @@ -70,6 +70,7 @@ sub new { $connector->{clapi_password} = $options{config}->{clapi_password}; $connector->{clapi_action_applycfg} = $options{config}->{clapi_action_applycfg}; $connector->{cmdFile} = defined($options{config}->{centcore_cmd}) && $options{config}->{centcore_cmd} ne '' ? $options{config}->{centcore_cmd} : '/var/lib/centreon/centcore.cmd'; + $connector->{illegal_characters} = defined($options{config}->{illegal_characters}) && $options{config}->{illegal_characters} ne '' ? $options{config}->{illegal_characters} : '~!$%^&*"|\'<>?,()='; bless $connector, $class; $connector->set_signal_handlers(); @@ -159,7 +160,7 @@ sub newtestresync_init { sub perfdata_add { my ($self, %options) = @_; - + my $perfdata = {label => '', value => '', unit => '', warning => '', critical => '', min => '', max => ''}; foreach (keys %options) { next if (!defined($options{$_})); @@ -171,7 +172,7 @@ sub perfdata_add { sub add_output { my ($self, %options) = @_; - + my $str = $map_service_status{$self->{current_status}} . ': ' . $self->{current_text} . '|'; foreach my $perf (@{$self->{perfdatas}}) { $str .= " '" . $perf->{label} . "'=" . $perf->{value} . $perf->{unit} . ";" . $perf->{warning} . ";" . $perf->{critical} . ";" . $perf->{min} . ";" . $perf->{max}; @@ -332,7 +333,7 @@ sub push_config { $self->{logger}->writeLogError("gorgone-newtest generation config for '$self->{poller_name}': failed"); return ; } - $self->{logger}->writeLogError("gorgone-newtest generation config for '$self->{poller_name}': succeeded."); + $self->{logger}->writeLogInfo("gorgone-newtest generation config for '$self->{poller_name}': succeeded."); $self->{logger}->writeLogInfo("gorgone-newtest move config for '$self->{poller_name}':"); if ($self->clapi_execute(cmd => '-a CFGMOVE -v ' . $self->{poller_id}, @@ -340,7 +341,7 @@ sub push_config { $self->{logger}->writeLogError("gorgone-newtest move config for '$self->{poller_name}': failed"); return ; } - $self->{logger}->writeLogError("gorgone-newtest move config for '$self->{poller_name}': succeeded."); + $self->{logger}->writeLogInfo("gorgone-newtest move config for '$self->{poller_name}': succeeded."); $self->{logger}->writeLogInfo("gorgone-newtest restart/reload config for '$self->{poller_name}':"); if ($self->clapi_execute(cmd => '-a ' . $self->{clapi_action_applycfg} . ' -v ' . $self->{poller_id}, @@ -348,7 +349,7 @@ sub push_config { $self->{logger}->writeLogError("gorgone-newtest restart/reload config for '$self->{poller_name}': failed"); return ; } - $self->{logger}->writeLogError("gorgone-newtest restart/reload config for '$self->{poller_name}': succeeded."); + $self->{logger}->writeLogInfo("gorgone-newtest restart/reload config for '$self->{poller_name}': succeeded."); } } @@ -433,17 +434,19 @@ sub get_scenario_results { value => sprintf("%d", $value), min => 0 ); - + $self->get_newtest_extra_metrics( scenario => $options{scenario}, robot => $options{robot}, id => $result->{Id} ); + + $self->{logger}->writeLogError("gorgone-newtest result found for scenario: " . $options{scenario} . '/' . $options{robot}); return 0; } } - $self->{logger}->writeLogError("gorgone-newtest no result found for scenario: " . $options{scenario} . '/' . $options{robot}); + $self->{logger}->writeLogError("gorgone-newtest no result found for scenario: " . $options{scenario} . '/' . $options{robot}); return 1; } @@ -513,7 +516,10 @@ sub get_newtest_scenarios { my $service_name = sprintf($self->{service_prefix}, $scenario_name); $self->{current_status} = $map_scenario_status{$scenario->{Status}}; $self->{current_text} = ''; - + + $host_name =~ s/\Q$self->{illegal_characters}\E//g; + $service_name =~ s/\Q$self->{illegal_characters}\E//g; + # Add host config if (!defined($self->{db_newtest}->{$host_name})) { $self->{logger}->writeLogInfo("gorgone-newtest create host '$host_name'"); @@ -524,7 +530,7 @@ sub get_newtest_scenarios { $self->{logger}->writeLogInfo("gorgone-newtest create host '$host_name' succeeded."); } } - + # Add service config if (defined($self->{db_newtest}->{$host_name}) && !defined($self->{db_newtest}->{$host_name}->{$service_name})) { $self->{logger}->writeLogInfo("gorgone-newtest create service '$service_name' for host '$host_name':"); @@ -537,7 +543,7 @@ sub get_newtest_scenarios { timeout => $self->{clapi_timeout}); } } - + # Check if new message if (defined($self->{db_newtest}->{$host_name}->{$service_name}->{last_execution_time}) && $last_check <= $self->{db_newtest}->{$host_name}->{$service_name}->{last_execution_time}) { From e6c856cdd7eb6ade0879ab63fed024328667c75b Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 17 Jul 2019 12:54:14 +0200 Subject: [PATCH 012/948] new test character --- gorgone/modules/gorgonenewtest/class.pm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gorgone/modules/gorgonenewtest/class.pm b/gorgone/modules/gorgonenewtest/class.pm index 6c7aec2f9f5..a0717c390a3 100644 --- a/gorgone/modules/gorgonenewtest/class.pm +++ b/gorgone/modules/gorgonenewtest/class.pm @@ -441,7 +441,7 @@ sub get_scenario_results { id => $result->{Id} ); - $self->{logger}->writeLogError("gorgone-newtest result found for scenario: " . $options{scenario} . '/' . $options{robot}); + $self->{logger}->writeLogInfo("gorgone-newtest result found for scenario: " . $options{scenario} . '/' . $options{robot}); return 0; } } @@ -517,8 +517,8 @@ sub get_newtest_scenarios { $self->{current_status} = $map_scenario_status{$scenario->{Status}}; $self->{current_text} = ''; - $host_name =~ s/\Q$self->{illegal_characters}\E//g; - $service_name =~ s/\Q$self->{illegal_characters}\E//g; + $host_name =~ s/[\Q$self->{illegal_characters}\E]//g; + $service_name =~ s/[\Q$self->{illegal_characters}\E]//g; # Add host config if (!defined($self->{db_newtest}->{$host_name})) { From baf85f0dcbca11f9d4e7fea1c0434dc2a8ba15e0 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 17 Jul 2019 14:07:49 +0200 Subject: [PATCH 013/948] change newtest OutOfRange code --- gorgone/modules/gorgonenewtest/class.pm | 31 ++++++++++++++----------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/gorgone/modules/gorgonenewtest/class.pm b/gorgone/modules/gorgonenewtest/class.pm index a0717c390a3..7293a3f5ded 100644 --- a/gorgone/modules/gorgonenewtest/class.pm +++ b/gorgone/modules/gorgonenewtest/class.pm @@ -133,7 +133,8 @@ sub send_log { my %map_scenario_status = ( Available => 0, Warning => 1, Failed => 2, Suspended => 2, - Canceled => 2, Unknown => 3, OutOfRange => 3, + Canceled => 2, Unknown => 3, + OutOfRange => 0, # Not Scheduled scenario ); my %map_newtest_units = ( @@ -550,18 +551,22 @@ sub get_newtest_scenarios { $self->{logger}->writeLogInfo("gorgone-newtest skip: service '$service_name' for host '$host_name' already submitted."); next; } - - if ($self->{current_status} == 2) { - $self->get_newtest_diagnostic( - scenario => $scenario_name, robot => $robot_name, - host_name => $host_name, service_name => $service_name - ); - } - - if ($self->get_scenario_results(scenario => $scenario_name, robot => $robot_name, - host_name => $host_name, service_name => $service_name) == 1) { - $self->{current_text} = sprintf("No result avaiblable for scenario '%s'", $scenario_name); - $self->{current_status} = 3; + + if ($scenario->{Status} eq 'OutOfRange') { + $self->{current_text} = sprintf("scenario '%s' not scheduled", $scenario_name); + } else { + if ($self->{current_status} == 2) { + $self->get_newtest_diagnostic( + scenario => $scenario_name, robot => $robot_name, + host_name => $host_name, service_name => $service_name + ); + } + + if ($self->get_scenario_results(scenario => $scenario_name, robot => $robot_name, + host_name => $host_name, service_name => $service_name) == 1) { + $self->{current_text} = sprintf("No result avaiblable for scenario '%s'", $scenario_name); + $self->{current_status} = 3; + } } $self->add_output(time => $last_check, host_name => $host_name, service_name => $service_name); } From 23ead8f84c03a91a8113d13a00cb1eaca3ee44a4 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Thu, 18 Jul 2019 12:58:03 +0200 Subject: [PATCH 014/948] add some help and indent --- gorgone/modules/gorgoneaction/class.pm | 155 ++++++++++++++++--------- gorgone/modules/gorgoneaction/hooks.pm | 39 ++++--- gorgone/modules/gorgoneproxy/class.pm | 5 +- gorgone/modules/gorgoneproxy/hooks.pm | 5 +- gorgone/modules/gorgonepull/hooks.pm | 21 ++-- 5 files changed, 145 insertions(+), 80 deletions(-) diff --git a/gorgone/modules/gorgoneaction/class.pm b/gorgone/modules/gorgoneaction/class.pm index 6326fd7da16..1d15163073e 100644 --- a/gorgone/modules/gorgoneaction/class.pm +++ b/gorgone/modules/gorgoneaction/class.pm @@ -82,28 +82,39 @@ sub action_command { my ($self, %options) = @_; if (!defined($options{data}->{command}) || $options{data}->{command} eq '') { - centreon::gorgone::common::zmq_send_message(socket => $options{socket_log}, - action => 'PUTLOG', data => { code => 35, etime => time(), token => $options{token}, data => { message => "need command argument" } }, - json_encode => 1); + centreon::gorgone::common::zmq_send_message( + socket => $options{socket_log}, + action => 'PUTLOG', + data => { code => 35, etime => time(), token => $options{token}, data => { message => "need command argument" } }, + json_encode => 1 + ); return -1; } - my ($error, $stdout, $return_code) = centreon::misc::misc::backtick(command => $options{data}->{command}, - #arguments => [@$args, $sub_cmd], - timeout => $self->{command_timeout}, - wait_exit => 1, - redirect_stderr => 1, - logger => $self->{logger}); + my ($error, $stdout, $return_code) = centreon::misc::misc::backtick( + command => $options{data}->{command}, + #arguments => [@$args, $sub_cmd], + timeout => $self->{command_timeout}, + wait_exit => 1, + redirect_stderr => 1, + logger => $self->{logger} + ); if ($error <= -1000) { - centreon::gorgone::common::zmq_send_message(socket => $options{socket_log}, - action => 'PUTLOG', data => { code => 35, etime => time(), token => $options{token}, data => { message => "command '$options{data}->{command}' execution issue: $stdout" } }, - json_encode => 1); + centreon::gorgone::common::zmq_send_message( + socket => $options{socket_log}, + action => 'PUTLOG', + data => { code => 35, etime => time(), token => $options{token}, data => { message => "command '$options{data}->{command}' execution issue: $stdout" } }, + json_encode => 1 + ); return -1; } - centreon::gorgone::common::zmq_send_message(socket => $options{socket_log}, - action => 'PUTLOG', data => { code => 36, etime => time(), token => $options{token}, data => { message => "command '$options{data}->{command}' had finished", stdout => $stdout, exit_code => $return_code } }, - json_encode => 1); + centreon::gorgone::common::zmq_send_message( + socket => $options{socket_log}, + action => 'PUTLOG', + data => { code => 36, etime => time(), token => $options{token}, data => { message => "command '$options{data}->{command}' had finished", stdout => $stdout, exit_code => $return_code } }, + json_encode => 1 + ); return 0; } @@ -111,27 +122,37 @@ sub action_enginecommand { my ($self, %options) = @_; if (!defined($options{data}->{engine_pipe}) || $options{data}->{engine_pipe} eq '') { - centreon::gorgone::common::zmq_send_message(socket => $options{socket_log}, - action => 'PUTLOG', data => { code => 35, etime => time(), token => $options{token}, data => { message => "need engine_pipe argument" } }, - json_encode => 1); + centreon::gorgone::common::zmq_send_message( + socket => $options{socket_log}, + action => 'PUTLOG', + data => { code => 35, etime => time(), token => $options{token}, data => { message => "need engine_pipe argument" } }, + json_encode => 1 + ); return -1; } if (! -e $options{data}->{engine_pipe}) { - centreon::gorgone::common::zmq_send_message(socket => $options{socket_log}, - action => 'PUTLOG', data => { code => 35, etime => time(), token => $options{token}, data => { message => "command '$options{data}->{command}' - engine_pipe '$options{data}->{engine_pipe}' must exist" } }, - json_encode => 1); + centreon::gorgone::common::zmq_send_message( + socket => $options{socket_log}, + action => 'PUTLOG', + data => { code => 35, etime => time(), token => $options{token}, data => { message => "command '$options{data}->{command}' - engine_pipe '$options{data}->{engine_pipe}' must exist" } }, + json_encode => 1 + ); return -1; } if (! -p $options{data}->{engine_pipe}) { - centreon::gorgone::common::zmq_send_message(socket => $options{socket_log}, - action => 'PUTLOG', data => { code => 35, etime => time(), token => $options{token}, data => { message => "command '$options{data}->{command}' - engine_pipe '$options{data}->{engine_pipe}' must be a pipe file" } }, - json_encode => 1); + centreon::gorgone::common::zmq_send_message( + socket => $options{socket_log}, + action => 'PUTLOG', data => { code => 35, etime => time(), token => $options{token}, data => { message => "command '$options{data}->{command}' - engine_pipe '$options{data}->{engine_pipe}' must be a pipe file" } }, + json_encode => 1 + ); return -1; } if (! -w $options{data}->{engine_pipe}) { - centreon::gorgone::common::zmq_send_message(socket => $options{socket_log}, - action => 'PUTLOG', data => { code => 35, etime => time(), token => $options{token}, data => { message => "command '$options{data}->{command}' - engine_pipe '$options{data}->{engine_pipe}' must be writeable" } }, - json_encode => 1); + centreon::gorgone::common::zmq_send_message( + socket => $options{socket_log}, + action => 'PUTLOG', data => { code => 35, etime => time(), token => $options{token}, data => { message => "command '$options{data}->{command}' - engine_pipe '$options{data}->{engine_pipe}' must be writeable" } }, + json_encode => 1 + ); return -1; } @@ -148,34 +169,47 @@ sub action_enginecommand { if ($@) { close $fh if (defined($fh)); $self->{logger}->writeLogError("gorgone-action: class: submit engine command '$options{data}->{command}' issue: $@"); - centreon::gorgone::common::zmq_send_message(socket => $options{socket_log}, - action => 'PUTLOG', data => { code => 35, etime => time(), token => $options{token}, data => { message => "submit command issue '$options{data}->{command}': $@" } }, - json_encode => 1); + centreon::gorgone::common::zmq_send_message( + socket => $options{socket_log}, + action => 'PUTLOG', + data => { code => 35, etime => time(), token => $options{token}, data => { message => "submit command issue '$options{data}->{command}': $@" } }, + json_encode => 1 + ); return undef; } - centreon::gorgone::common::zmq_send_message(socket => $options{socket_log}, - action => 'PUTLOG', data => { code => 36, etime => time(), token => $options{token}, data => { message => "command '$options{data}->{command}' had been submitted" } }, - json_encode => 1); + centreon::gorgone::common::zmq_send_message( + socket => $options{socket_log}, + action => 'PUTLOG', + data => { code => 36, etime => time(), token => $options{token}, data => { message => "command '$options{data}->{command}' had been submitted" } }, + json_encode => 1 + ); return 0; } sub action_run { my ($self, %options) = @_; - my $socket_log = centreon::gorgone::common::connect_com(zmq_type => 'ZMQ_DEALER', name => 'gorgoneaction-'. $$, - logger => $self->{logger}, linger => 5000, - type => $self->{config_core}{internal_com_type}, - path => $self->{config_core}{internal_com_path}); + my $socket_log = centreon::gorgone::common::connect_com( + zmq_type => 'ZMQ_DEALER', + name => 'gorgoneaction-'. $$, + logger => $self->{logger}, + linger => 5000, + type => $self->{config_core}{internal_com_type}, + path => $self->{config_core}{internal_com_path} + ); if ($options{action} eq 'COMMAND') { $self->action_command(%options, socket_log => $socket_log); } elsif ($options{action} eq 'ENGINECOMMAND') { $self->action_enginecommand(%options, socket_log => $socket_log); } - centreon::gorgone::common::zmq_send_message(socket => $socket_log, - action => 'PUTLOG', data => { code => 32, etime => time(), token => $options{token}, data => { message => "proceed action end" } }, - json_encode => 1); + centreon::gorgone::common::zmq_send_message( + socket => $socket_log, + action => 'PUTLOG', + data => { code => 32, etime => time(), token => $options{token}, data => { message => "proceed action end" } }, + json_encode => 1 + ); zmq_close($socket_log); } @@ -190,9 +224,12 @@ sub create_child { my $child_pid = fork(); if (!defined($child_pid)) { - centreon::gorgone::common::zmq_send_message(socket => $socket, - action => 'PUTLOG', data => { code => 30, etime => time(), token => $token, data => { message => "cannot fork: $!" } }, - json_encode => 1); + centreon::gorgone::common::zmq_send_message( + socket => $socket, + action => 'PUTLOG', + data => { code => 30, etime => time(), token => $token, data => { message => "cannot fork: $!" } }, + json_encode => 1 + ); return undef; } @@ -200,9 +237,12 @@ sub create_child { $self->action_run(action => $action, token => $token, data => $data); exit(0); } else { - centreon::gorgone::common::zmq_send_message(socket => $socket, - action => 'PUTLOG', data => { code => 31, etime => time(), token => $token, data => { message => "proceed action" } }, - json_encode => 1); + centreon::gorgone::common::zmq_send_message( + socket => $socket, + action => 'PUTLOG', + data => { code => 31, etime => time(), token => $token, data => { message => "proceed action" } }, + json_encode => 1 + ); } } @@ -224,19 +264,24 @@ sub run { my ($self, %options) = @_; # Connect internal - $socket = centreon::gorgone::common::connect_com(zmq_type => 'ZMQ_DEALER', name => 'gorgoneaction', - logger => $self->{logger}, - type => $self->{config_core}{internal_com_type}, - path => $self->{config_core}{internal_com_path}); - centreon::gorgone::common::zmq_send_message(socket => $socket, - action => 'ACTIONREADY', data => { }, - json_encode => 1); + $socket = centreon::gorgone::common::connect_com( + zmq_type => 'ZMQ_DEALER', + name => 'gorgoneaction', + logger => $self->{logger}, + type => $self->{config_core}{internal_com_type}, + path => $self->{config_core}{internal_com_path} + ); + centreon::gorgone::common::zmq_send_message( + socket => $socket, + action => 'ACTIONREADY', data => { }, + json_encode => 1 + ); $self->{poll} = [ - { + { socket => $socket, events => ZMQ_POLLIN, callback => \&event, - } + } ]; while (1) { # we try to do all we can diff --git a/gorgone/modules/gorgoneaction/hooks.pm b/gorgone/modules/gorgoneaction/hooks.pm index 8a2f16c3a0c..1ec9ac9966b 100644 --- a/gorgone/modules/gorgoneaction/hooks.pm +++ b/gorgone/modules/gorgoneaction/hooks.pm @@ -63,10 +63,12 @@ sub routing { }; if ($@) { $options{logger}->writeLogError("Cannot decode json data: $@"); - centreon::gorgone::common::add_history(dbh => $options{dbh}, - code => 30, token => $options{token}, - data => { msg => 'gorgoneaction: cannot decode json' }, - json_encode => 1); + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => 30, token => $options{token}, + data => { msg => 'gorgoneaction: cannot decode json' }, + json_encode => 1 + ); return undef; } @@ -76,16 +78,22 @@ sub routing { } if (centreon::script::gorgonecore::waiting_ready(ready => \$action->{ready}) == 0) { - centreon::gorgone::common::add_history(dbh => $options{dbh}, - code => 30, token => $options{token}, - data => { msg => 'gorgoneaction: still no ready' }, - json_encode => 1); + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => 30, token => $options{token}, + data => { msg => 'gorgoneaction: still no ready' }, + json_encode => 1 + ); return undef; } - centreon::gorgone::common::zmq_send_message(socket => $options{socket}, identity => 'gorgoneaction', - action => $options{action}, data => $options{data}, token => $options{token}, - ); + centreon::gorgone::common::zmq_send_message( + socket => $options{socket}, + identity => 'gorgoneaction', + action => $options{action}, + data => $options{data}, + token => $options{token}, + ); } sub gently { @@ -140,10 +148,11 @@ sub create_child { my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-action'; - my $module = modules::gorgoneaction::class->new(logger => $options{logger}, - config_core => $config_core, - config => $config, - ); + my $module = modules::gorgoneaction::class->new( + logger => $options{logger}, + config_core => $config_core, + config => $config, + ); $module->run(); exit(0); } diff --git a/gorgone/modules/gorgoneproxy/class.pm b/gorgone/modules/gorgoneproxy/class.pm index 2c396dfc55b..f74b1db7b63 100644 --- a/gorgone/modules/gorgoneproxy/class.pm +++ b/gorgone/modules/gorgoneproxy/class.pm @@ -81,7 +81,7 @@ sub class_handle_HUP { sub get_client_information { my ($self, %options) = @_; - # TODO DATABASE. Si database marche pas. On fait un PUTLOG. + # TODO DATABASE or file maybe. hardcoded right now my $result = { type => 1, target_type => 'tcp', target_path => 'localhost:5556', pubkey => 'keys/poller/pubkey.crt', cipher => 'Cipher::AES', keysize => '32', vector => '0123456789012345', class => undef, delete => 0 }; @@ -163,6 +163,9 @@ sub proxy { $entry = $connector->{clients}->{$target}; } + # TODO we need to manage type SSH with libssh + # type 1 = ZMQ. + # type 2 = SSH if ($entry->{type} == 1) { my ($status, $msg) = $entry->{class}->send_message(action => $action, token => $token, target => '', data => $data); diff --git a/gorgone/modules/gorgoneproxy/hooks.pm b/gorgone/modules/gorgoneproxy/hooks.pm index 26354c798dd..1d8b9da93b2 100644 --- a/gorgone/modules/gorgoneproxy/hooks.pm +++ b/gorgone/modules/gorgoneproxy/hooks.pm @@ -410,7 +410,8 @@ sub get_sync_time { sub get_pollers { my (%options) = @_; - # TODO: 1 for 'zmq', 2 for 'ssh' + # TODO method + # type 1 = 'zmq', type 2 = 'ssh' my $pollers = {}; foreach (([1, 1], [2, 1], [10, 1], [166, 2], [140, 1])) { @@ -418,7 +419,7 @@ sub get_pollers { $synctime_pollers->{${$_}[0]} = { ctime => 0, in_progress => 0, in_progress_time => -1, last_id => 0 }; $last_pong->{${$_}[0]} = 0 if (!defined($last_pong->{${$_}[0]})); } - + get_sync_time(dbh => $options{dbh}); return $pollers; diff --git a/gorgone/modules/gorgonepull/hooks.pm b/gorgone/modules/gorgonepull/hooks.pm index 04de88ce97b..59889c5d670 100644 --- a/gorgone/modules/gorgonepull/hooks.pm +++ b/gorgone/modules/gorgonepull/hooks.pm @@ -69,9 +69,11 @@ sub init { $client->send_message(action => 'REGISTERNODE', data => { id => $config_core->{id} }, json_encode => 1); - centreon::gorgone::common::add_zmq_pollin(socket => $socket_to_internal, - callback => \&from_router, - poll => $options{poll}); + centreon::gorgone::common::add_zmq_pollin( + socket => $socket_to_internal, + callback => \&from_router, + poll => $options{poll} + ); } sub routing { @@ -83,8 +85,11 @@ sub gently { my (%options) = @_; $stop = 1; - $client->send_message(action => 'UNREGISTERNODE', data => { id => $config_core->{id} }, - json_encode => 1); + $client->send_message( + action => 'UNREGISTERNODE', + data => { id => $config_core->{id} }, + json_encode => 1 + ); $client->close(); return 0; } @@ -157,8 +162,10 @@ sub read_message { } $logger->writeLogDebug("gorgone-pull: hook: read message from external: $options{data}"); - centreon::gorgone::common::zmq_send_message(socket => $socket_to_internal, - message => $options{data}); + centreon::gorgone::common::zmq_send_message( + socket => $socket_to_internal, + message => $options{data} + ); } From fba6259cda23d88403cdcfaf832a1fb8f6a551e9 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Thu, 18 Jul 2019 13:59:32 +0200 Subject: [PATCH 015/948] add schema architecture --- gorgone/docs/zmq_architecture.svg | 713 ++++++++++++++++++++++++++++++ 1 file changed, 713 insertions(+) create mode 100644 gorgone/docs/zmq_architecture.svg diff --git a/gorgone/docs/zmq_architecture.svg b/gorgone/docs/zmq_architecture.svg new file mode 100644 index 00000000000..e1027101a51 --- /dev/null +++ b/gorgone/docs/zmq_architecture.svg @@ -0,0 +1,713 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + DEALER + + + + DEALER + + + + DEALER + + + Interface Web + + + ROUTER + + + + + ROUTER + + + + + + DEALER + + + + ROUTER + + + + + DEALER + + + + DEALER + + gorgone-crond + + + DEALER + + + + + + DEALER + + gorgone-core + gorgone-proxy + gorgone-action + Gorgoned + Agent + + + + + + + + + + + + Flux chiffrés + gorgone-pull + Agent + + From 57357b510969bf1db72e9e04e54f3470f46bc778 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Thu, 18 Jul 2019 15:40:14 +0200 Subject: [PATCH 016/948] add todo --- gorgone/TODO | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 gorgone/TODO diff --git a/gorgone/TODO b/gorgone/TODO new file mode 100644 index 00000000000..0f8f0e4d69a --- /dev/null +++ b/gorgone/TODO @@ -0,0 +1,2 @@ +- Add a HTTP frontend module for clients (https and basic auth). Can try: https://metacpan.org/pod/HTTP::Server::Simple +- Can chain agents and forward protocol From 235bc2459e3d87854c608489572fd4ae5d6c5b36 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Thu, 18 Jul 2019 17:38:38 +0200 Subject: [PATCH 017/948] update todo --- gorgone/TODO | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gorgone/TODO b/gorgone/TODO index 0f8f0e4d69a..866112d0c82 100644 --- a/gorgone/TODO +++ b/gorgone/TODO @@ -1,2 +1,5 @@ +- Use YAML configuration file instead of ini - Add a HTTP frontend module for clients (https and basic auth). Can try: https://metacpan.org/pod/HTTP::Server::Simple +- Add legacy module: read centcore.cmd - Can chain agents and forward protocol +- gorgone-newtest: don't use centcore.cmd. use ssh system. From d68aa0b22cd3884d575d88df7520ac74c8d0252e Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 7 Aug 2019 19:19:35 +0200 Subject: [PATCH 018/948] use YAML instead of INI Use YAML instead of INI for config files. Containers parsing needs to be rewrited. --- gorgone/centreon/gorgone/common.pm | 19 ++-- gorgone/centreon/script/gorgonecore.pm | 80 +++++++------- gorgone/config/gorgoned-poller.ini | 53 ---------- gorgone/config/gorgoned-poller.yml | 54 ++++++++++ gorgone/config/gorgoned-poller2.ini | 31 ------ gorgone/config/gorgoned-poller2.yml | 33 ++++++ gorgone/config/gorgoned.ini | 133 ------------------------ gorgone/config/gorgoned.yml | 127 ++++++++++++++++++++++ gorgone/modules/gorgoneacl/class.pm | 20 ++-- gorgone/modules/gorgoneaction/class.pm | 12 +-- gorgone/modules/gorgonecron/class.pm | 4 +- gorgone/modules/gorgonenewtest/class.pm | 20 ++-- gorgone/modules/gorgonescom/class.pm | 10 +- 13 files changed, 298 insertions(+), 298 deletions(-) delete mode 100644 gorgone/config/gorgoned-poller.ini create mode 100644 gorgone/config/gorgoned-poller.yml delete mode 100644 gorgone/config/gorgoned-poller2.ini create mode 100644 gorgone/config/gorgoned-poller2.yml delete mode 100644 gorgone/config/gorgoned.ini create mode 100644 gorgone/config/gorgoned.yml diff --git a/gorgone/centreon/gorgone/common.pm b/gorgone/centreon/gorgone/common.pm index 85e53714a68..6c753315fbf 100644 --- a/gorgone/centreon/gorgone/common.pm +++ b/gorgone/centreon/gorgone/common.pm @@ -26,26 +26,29 @@ use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); use JSON; use File::Basename; -use Config::IniFiles; use Crypt::OpenSSL::RSA; use Crypt::OpenSSL::Random; use Crypt::CBC; use Data::Dumper; +use YAML 'LoadFile';; my %zmq_type = ('ZMQ_ROUTER' => ZMQ_ROUTER, 'ZMQ_DEALER' => ZMQ_DEALER); my $privkey; sub read_config { my (%options) = @_; - my %config; - tie %config, 'Config::IniFiles', (-file => $options{config_file}); - if (@Config::IniFiles::errors) { + my $config; + eval { + $config = LoadFile($options{config_file}); + }; + if ($@) { $options{logger}->writeLogError("Parsinig extra config file error:"); - $options{logger}->writeLogError(join("\n", @Config::IniFiles::errors)); + $options{logger}->writeLogError($@); exit(1); } - return \%config; + + return $config; } ####################### @@ -257,8 +260,8 @@ sub is_handshake_done { sub constatus { my (%options) = @_; - if (defined($options{gorgone}->{modules_register}->{ $options{gorgone}->{modules_id}->{$options{gorgone_config}->{gorgonecore}{proxy_name}} })) { - my $name = $options{gorgone_config}->{$options{gorgone_config}->{gorgonecore}{proxy_name}}{module}; + if (defined($options{gorgone}->{modules_register}->{ $options{gorgone}->{modules_id}->{$options{gorgone_config}->{gorgonecore}->{proxy_name}} })) { + my $name = $options{gorgone_config}->{modules}->{$options{gorgone_config}->{gorgonecore}->{proxy_name}}->{module}; my $method; if (defined($name) && ($method = $name->can('get_constatus_result'))) { return (0, { action => 'constatus', mesage => 'ok', data => $method->() }, 'CONSTATUS'); diff --git a/gorgone/centreon/script/gorgonecore.pm b/gorgone/centreon/script/gorgonecore.pm index 277df145368..4dd9e3b97b9 100644 --- a/gorgone/centreon/script/gorgonecore.pm +++ b/gorgone/centreon/script/gorgonecore.pm @@ -30,7 +30,7 @@ use centreon::gorgone::common; use centreon::misc::db; use centreon::script; -my ($gorgone, $gorgone_config); +my ($gorgone, $config); use base qw(centreon::script); @@ -75,23 +75,23 @@ sub init { $self->{logger}->writeLogError("Can't find extra config file '$self->{opt_extra}'"); exit(1); } - $gorgone_config = centreon::gorgone::common::read_config( + $config = centreon::gorgone::common::read_config( config_file => $self->{opt_extra}, logger => $self->{logger} ); - if (defined($gorgone_config->{gorgonecore}{external_com_type}) && $gorgone_config->{gorgonecore}{external_com_type} ne '') { - centreon::gorgone::common::loadprivkey(logger => $self->{logger}, privkey => $gorgone_config->{gorgonecore}{privkey}); + if (defined($config->{gorgonecore}->{external_com_type}) && $config->{gorgonecore}->{external_com_type} ne '') { + centreon::gorgone::common::loadprivkey(logger => $self->{logger}, privkey => $config->{gorgonecore}->{privkey}); } # Database connections: # We add in gorgone database $gorgone->{db_gorgone} = centreon::misc::db->new( - type => $gorgone_config->{gorgonecore}{gorgone_db_type}, - db => $gorgone_config->{gorgonecore}{gorgone_db_name}, - host => $gorgone_config->{gorgonecore}{gorgone_db_host}, - port => $gorgone_config->{gorgonecore}{gorgone_db_port}, - user => $gorgone_config->{gorgonecore}{gorgone_db_user}, - password => $gorgone_config->{gorgonecore}{gorgone_db_password}, + type => $config->{gorgonecore}->{gorgone_db_type}, + db => $config->{gorgonecore}->{gorgone_db_name}, + host => $config->{gorgonecore}->{gorgone_db_host}, + port => $config->{gorgonecore}->{gorgone_db_port}, + user => $config->{gorgonecore}->{gorgone_db_user}, + password => $config->{gorgonecore}->{gorgone_db_password}, force => 2, logger => $gorgone->{logger} ); @@ -101,12 +101,12 @@ sub init { exit(1); } - $self->{hostname} = $gorgone_config->{gorgonecore}{hostname}; + $self->{hostname} = $config->{gorgonecore}->{hostname}; if (!defined($self->{hostname}) || $self->{hostname} eq '') { $self->{hostname} = hostname(); } - $self->{id} = $gorgone_config->{gorgonecore}{id}; + $self->{id} = $config->{gorgonecore}->{id}; if (!defined($self->{hostname}) || $self->{hostname} eq '') { #$self->{id} = get_poller_id(dbh => $dbh, name => $self->{hostname}); } @@ -175,28 +175,28 @@ sub handle_CHLD { sub load_modules { my $self = shift; + next if (!defined($config->{modules})); - foreach my $section (keys %{$gorgone_config}) { - next if (!defined($gorgone_config->{$section}{module})); - - my $name = $gorgone_config->{$section}{module}; + foreach my $module (@{$config->{modules}}) { + next if (!defined($module->{enable}) || $module->{enable} eq '0'); + my $name = $module->{module}; (my $file = "$name.pm") =~ s{::}{/}g; require $file; - $self->{logger}->writeLogInfo("Module '$section' is loading"); + $self->{logger}->writeLogInfo("Module '" . $module->{name} . "' is loading"); $self->{modules_register}->{$name} = {}; foreach my $method_name (('register', 'routing', 'kill', 'kill_internal', 'gently', 'check', 'init')) { unless ($self->{modules_register}->{$name}->{$method_name} = $name->can($method_name)) { - $self->{logger}->writeLogError("No function '$method_name' for module '$section'"); + $self->{logger}->writeLogError("No function '$method_name' for module '" . $module->{name} . "'"); exit(1); } } my ($events, $id) = $self->{modules_register}->{$name}->{register}->( - config => $gorgone_config->{$section}, - config_core => $gorgone_config->{gorgonecore}, - config_db_centreon => $gorgone_config->{db_centreon}, - config_db_centstorage => $gorgone_config->{db_centstorage} + config => $module, + config_core => $config->{gorgonecore}, + config_db_centreon => $config->{database}->{db_centreon}, + config_db_centstorage => $config->{database}->{db_centstorage} ); $self->{modules_id}->{$id} = $name; foreach my $event (@{$events}) { @@ -204,7 +204,7 @@ sub load_modules { push @{$self->{modules_events}->{$event}}, $name; } - $self->{logger}->writeLogInfo("Module '$section' is loaded"); + $self->{logger}->writeLogInfo("Module '" . $module->{name} . "' is loaded"); } # Load internal functions @@ -250,7 +250,7 @@ sub message_run { if (defined($target) && $target ne '') { # Check if not myself ;) if ($target ne $self->{id}) { - $self->{modules_register}->{ $self->{modules_id}->{$gorgone_config->{gorgonecore}{proxy_name}} }->{routing}->( + $self->{modules_register}->{ $self->{modules_id}->{$config->{gorgonecore}->{proxy_name}} }->{routing}->( socket => $self->{internal_socket}, dbh => $self->{db_gorgone}, logger => $self->{logger}, action => $1, token => $token, target => $target, data => $data, hostname => $self->{hostname} @@ -262,7 +262,7 @@ sub message_run { if ($action =~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS)$/) { my ($code, $response, $response_type) = $self->{internal_register}->{lc($action)}->( gorgone => $self, - gorgone_config => $gorgone_config, + gorgone_config => $config, id => $self->{id}, data => $data, token => $token, @@ -304,10 +304,10 @@ sub handshake { if ($status == 1) { ($status, my $response) = centreon::gorgone::common::uncrypt_message( - cipher => $gorgone_config->{gorgonecore}{cipher}, + cipher => $config->{gorgonecore}->{cipher}, message => $message, symkey => $key, - vector => $gorgone_config->{gorgonecore}{vector} + vector => $config->{gorgonecore}->{vector} ); if ($status == 0 && $response =~ /^\[.*\]/) { centreon::gorgone::common::update_identity(dbh => $self->{db_gorgone}, identity => $identity); @@ -337,8 +337,8 @@ sub handshake { } my ($status, $symkey) = centreon::gorgone::common::generate_symkey( logger => $self->{logger}, - cipher => $gorgone_config->{gorgonecore}{cipher}, - keysize => $gorgone_config->{gorgonecore}{keysize} + cipher => $config->{gorgonecore}->{cipher}, + keysize => $config->{gorgonecore}->{keysize} ); if ($status == -1) { centreon::gorgone::common::zmq_core_response( @@ -372,8 +372,8 @@ sub router_external_event { centreon::gorgone::common::zmq_core_response( socket => $gorgone->{external_socket}, identity => $identity, response_type => $response_type, - cipher => $gorgone_config->{gorgonecore}{cipher}, - vector => $gorgone_config->{gorgonecore}{vector}, + cipher => $config->{gorgonecore}->{cipher}, + vector => $config->{gorgonecore}->{vector}, symkey => $key, token => $token, code => $code, data => $response @@ -423,9 +423,9 @@ sub waiting_ready { sub clean_sessions { my ($self, %options) = @_; - if ($self->{sessions_timer} - time() > $gorgone_config->{gorgonecore}{purge_sessions_time}) { + if ($self->{sessions_timer} - time() > $config->{gorgonecore}->{purge_sessions_time}) { $self->{logger}->writeLogInfo("purge sessions in progress..."); - $self->{db_gorgone}->query("DELETE FROM gorgone_identity WHERE `ctime` < " . $self->{db_gorgone}->quote(time() - $gorgone_config->{gorgonecore}{sessions_time})); + $self->{db_gorgone}->query("DELETE FROM gorgone_identity WHERE `ctime` < " . $self->{db_gorgone}->quote(time() - $config->{gorgonecore}->{sessions_time})); $self->{sessions_timer} = time(); } } @@ -435,7 +435,7 @@ sub quit { $self->{logger}->writeLogInfo("Quit main process"); zmq_close($self->{internal_socket}); - if (defined($gorgone_config->{gorgonecore}{external_com_type}) && $gorgone_config->{gorgonecore}{external_com_type} ne '') { + if (defined($config->{gorgonecore}->{external_com_type}) && $config->{gorgonecore}->{external_com_type} ne '') { zmq_close($self->{external_socket}); } exit(0); @@ -459,15 +459,15 @@ sub run { } $gorgone->{internal_socket} = centreon::gorgone::common::create_com( - type => $gorgone_config->{gorgonecore}{internal_com_type}, - path => $gorgone_config->{gorgonecore}{internal_com_path}, + type => $config->{gorgonecore}->{internal_com_type}, + path => $config->{gorgonecore}->{internal_com_path}, zmq_type => 'ZMQ_ROUTER', name => 'router-internal', logger => $gorgone->{logger} ); - if (defined($gorgone_config->{gorgonecore}{external_com_type}) && $gorgone_config->{gorgonecore}{external_com_type} ne '') { + if (defined($config->{gorgonecore}->{external_com_type}) && $config->{gorgonecore}->{external_com_type} ne '') { $gorgone->{external_socket} = centreon::gorgone::common::create_com( - type => $gorgone_config->{gorgonecore}{external_com_type}, - path => $gorgone_config->{gorgonecore}{external_com_path}, + type => $config->{gorgonecore}->{external_com_type}, + path => $config->{gorgonecore}->{external_com_path}, zmq_type => 'ZMQ_ROUTER', name => 'router-external', logger => $gorgone->{logger} ); @@ -525,7 +525,7 @@ sub run { } # Send KILL - if (time() - $gorgone->{kill_timer} > $gorgone_config->{gorgonecore}{timeout}) { + if (time() - $gorgone->{kill_timer} > $config->{gorgonecore}->{timeout}) { foreach my $name (keys %{$gorgone->{modules_register}}) { $gorgone->{modules_register}->{$name}->{kill_internal}->(logger => $gorgone->{logger}); } diff --git a/gorgone/config/gorgoned-poller.ini b/gorgone/config/gorgoned-poller.ini deleted file mode 100644 index 5625eafddfa..00000000000 --- a/gorgone/config/gorgoned-poller.ini +++ /dev/null @@ -1,53 +0,0 @@ -[gorgonecore] -internal_com_type=ipc -internal_com_path=/tmp/gorgone/routing-poller.ipc -; in seconds before sending kill signals (not gently) -timeout=50 -gorgone_db_type=SQLite -gorgone_db_name=dbname=/tmp/gorgone_poller.sdb -gorgone_db_host= -gorgone_db_port= -gorgone_db_user= -gorgone_db_password= -; If not set. Use 'hostname' function. -hostname= -; If not set. Try from 'hostname' in database -; Set 'none', if you don't need it (for poller in push mode) -id=120 -; crypt options -;privkey=keys/privkey.pem -;cipher=Crypt::OpenSSL::AES -; in bytes -;keysize=32 -; 16 bytes for AES -;vector=0123456789012345 -; in seconds -sessions_time=86400 -purge_sessions_time=3600 - -[gorgonepull] -module=modules::gorgonepull::hooks -; ID used (should be the poller ID) -target_type=tcp -target_path=127.0.0.1:5555 -linger=5000 -; crypt options -pubkey=keys/central/pubkey.crt -cipher=Cipher::AES -; in bytes -keysize=32 -; 16 bytes for AES -vector=0123456789012345 -; ping -ping=60 -ping_timeout=30 - -[gorgoneaction] -module=modules::gorgoneaction::hooks -disable_command_event=0 -disable_enginecomand_event=0 -enginecommand_timeout=30 -command_timeout=60 -; characters to delete in commands -paranoid_mode=1 -paranoid_characters= diff --git a/gorgone/config/gorgoned-poller.yml b/gorgone/config/gorgoned-poller.yml new file mode 100644 index 00000000000..e6077812e7b --- /dev/null +++ b/gorgone/config/gorgoned-poller.yml @@ -0,0 +1,54 @@ +name: gorgoned-poller +gorgonecore: + internal_com_type: ipc + internal_com_path: /tmp/gorgone/routing-poller.ipc + # in seconds before sending kill signals (not gently) + timeout: 50 + gorgone_db_type: SQLite + gorgone_db_name: dbname=/tmp/gorgone_poller.sdb + gorgone_db_host: + gorgone_db_port: + gorgone_db_user: + gorgone_db_password: + # If not set. Use 'hostname' function. + hostname: + # If not set. Try from 'hostname' in database + # Set 'none', if you don't need it (for poller in push mode) + id: 120 + privkey: keys/privkey.pem + cipher: "Crypt::OpenSSL::AES" + # in bytes + keysize: 32 + # 16 bytes for AES + vector: 0123456789012345 + # in seconds + sessions_time: 86400 + # in seconds + purge_sessions_time: 3600 +modules: + - name: gorgonepull + module: modules::gorgonepull::hooks + enable: 1 + target_type: tcp + target_path: 127.0.0.1:5555 + linger: 5000 + # crypt options + pubkey: keys/central/pubkey.crt + cipher: "Cipher::AES" + # in bytes + keysize: 32 + # 16 bytes for AES + vector: 0123456789012345 + # ping + ping: 60 + ping_timeout: 30 + - name: gorgoneaction + module: modules::gorgoneaction::hooks + enable: 1 + disable_command_event: 0 + disable_enginecomand_event: 0 + enginecommand_timeout: 30 + command_timeout: 60 + # characters to delete in commands + paranoid_mode: 1 + paranoid_characters: diff --git a/gorgone/config/gorgoned-poller2.ini b/gorgone/config/gorgoned-poller2.ini deleted file mode 100644 index 1faf5681e9f..00000000000 --- a/gorgone/config/gorgoned-poller2.ini +++ /dev/null @@ -1,31 +0,0 @@ -[gorgonecore] -internal_com_type=ipc -internal_com_path=/tmp/gorgone/routing-poller.ipc -external_com_type=tcp -external_com_path=*:5556 -; in seconds before sending kill signals (not gently) -timeout=50 -gorgone_db_type=SQLite -gorgone_db_name=dbname=/tmp/gorgone_poller2.sdb -gorgone_db_host= -gorgone_db_port= -gorgone_db_user= -gorgone_db_password= -; If not set. Use 'hostname' function. -hostname= -; If not set. Try from 'hostname' in database -; Set 'none', if you don't need it (for poller in push mode) -id=140 -; crypt options -privkey=keys/poller/privkey.pem -cipher=Cipher::AES -; in bytes -keysize=32 -; 16 bytes for AES -vector=0123456789012345 -; in seconds -sessions_time=86400 -purge_sessions_time=3600 - -[gorgoneaction] -module=modules::gorgoneaction::hooks diff --git a/gorgone/config/gorgoned-poller2.yml b/gorgone/config/gorgoned-poller2.yml new file mode 100644 index 00000000000..c3ebf1669e5 --- /dev/null +++ b/gorgone/config/gorgoned-poller2.yml @@ -0,0 +1,33 @@ +name: gorgoned-poller2 +gorgonecore: + internal_com_type: ipc + internal_com_path: /tmp/gorgone/routing-poller.ipc + external_com_type: tcp + external_com_path: "*:5556" + # in seconds before sending kill signals (not gently) + timeout: 50 + gorgone_db_type: SQLite + gorgone_db_name: dbname=/tmp/gorgone_poller.sdb + gorgone_db_host: + gorgone_db_port: + gorgone_db_user: + gorgone_db_password: + # If not set. Use 'hostname' function. + hostname: + # If not set. Try from 'hostname' in database + # Set 'none', if you don't need it (for poller in push mode) + id: 140 + privkey: keys/poller/privkey.pem + cipher: "Cipher::AES" + # in bytes + keysize: 32 + # 16 bytes for AES + vector: 0123456789012345 + # in seconds + sessions_time: 86400 + # in seconds + purge_sessions_time: 3600 +modules: + - name: gorgoneaction + module: modules::gorgoneaction::hooks + enable: 1 diff --git a/gorgone/config/gorgoned.ini b/gorgone/config/gorgoned.ini deleted file mode 100644 index 06ba2745f0b..00000000000 --- a/gorgone/config/gorgoned.ini +++ /dev/null @@ -1,133 +0,0 @@ -; Database Centreon configuration -[db_centreon] -dsn="mysql:host=localhost;dbname=centreon" -username=centreon -password=centreon - -; Database centreon_storage configuration -[db_centstorage] -dsn="mysql:host=localhost;dbname=centreon_storage" -username=centreon -password=centreon - -; gorgoned -[gorgonecore] -internal_com_type=ipc -internal_com_path=/tmp/gorgone/routing.ipc -external_com_type=tcp -external_com_path=*:5555 -; in seconds before sending kill signals (not gently) -timeout=50 -gorgone_db_type=SQLite -gorgone_db_name=dbname=/tmp/gorgone.sdb -gorgone_db_host= -gorgone_db_port= -gorgone_db_user= -gorgone_db_password= -; If not set. Use 'hostname' function. -hostname= -; If not set. Try from 'hostname' in database -; Set 'none', if you don't need it (for poller in push mode) -id=none -; crypt options -privkey=keys/central/privkey.pem -cipher=Cipher::AES -; in bytes -keysize=32 -; 16 bytes for AES -vector=0123456789012345 -; in seconds -sessions_time=86400 -purge_sessions_time=3600 -; shouldn't be changed -proxy_name=gorgoneproxy - -;[gorgoneacl] -;module=modules::gorgoneacl::hooks -;on_demand=1 -;; How much to keep open in seconds without event received -;on_demand_time=60 -;; in seconds - do purge for organizations also -;check_organizations_time=3600 -;; in seconds - do a resync of the organizations -;resync_time=28800 -;; in seconds - random windows (to avoid resync at the same time) -;resync_random_windows=7200 -;; set to 1 to disable - if you want to do it by a cron -;resync_auto_disable=0 -;sql_fetch=10000 -;sql_bulk=2000 - -[gorgonecron] -module=modules::gorgonecron::hooks - -;[gorgoneproxy] -;module=modules::gorgoneproxy::hooks -;pool=5 -;; sync history each 5 minutes -;synchistory_time=300 -;; how much time before the response is in timeout -;synchistory_timeout=120 -;; ping each X seconds -;ping=60 - -;[gorgoneaction] -;module=modules::gorgoneaction::hooks - -[gorgonescom] -module=modules::gorgonescom::hooks - -; in seconds - do purge for container also -check_containers_time=3600 -dsmclient_bin=/usr/share/centreon/bin/dsmclient.pl -centcore_cmd=/var/lib/centreon/centcore.cmd -containers=toto,tutu - -toto_url=http://scomserver -toto_username=toto -toto_password=pass -toto_resync_time=300 -toto_dsmhost=centreon -toto_dsmslot=slot-% -toto_dsmmacro=ALARM_ID -toto_dsmalertmessage=%{monitoringobjectdisplayname} %{name} -toto_dsmrecoverymessage=slot ok - -tutu_url=http://scomserver2/ -tutu_username=toto2 -tutu_password=toto2 -tutu_resync_time=600 - -[gorgonenewtest] -module=modules::gorgonenewtest::hooks - -; in seconds - do purge for container also -check_containers_time=3600 -clapi_command=/usr/bin/centreon -clapi_username=admin -clapi_password=centreon -clapi_action_applycfg=RELOAD -centcore_cmd=/var/lib/centreon/centcore.cmd - -containers=toto,tutu - -toto_resync_time=300 -toto_nmc_endpoint=http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx -toto_password=pass -toto_host_template=generic-active-host-custom -toto_host_prefix=Robot-%s -toto_service_template=generic-passive-service-custom -toto_service_prefix=Scenario-%s -toto_poller_name=Central -toto_list_scenario_status={ "search": "All", "instances": [] } - -tutu_resync_time=600 -tutu_nmc_endpoint=http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx -tutu_nmc_timeout=10 -tutu_password=pass -tutu_host_template=generic-active-host-custom -tutu_host_prefix=Robot-%s -tutu_service_template=generic-passive-service-custom -tutu_service_prefix=Scenario-%s -tutu_poller_name=Central -tutu_list_scenario_status={ "search": "Robot", "instances": ["XXXX"] } diff --git a/gorgone/config/gorgoned.yml b/gorgone/config/gorgoned.yml new file mode 100644 index 00000000000..d7cb4098393 --- /dev/null +++ b/gorgone/config/gorgoned.yml @@ -0,0 +1,127 @@ +name: gorgoned +database: + db_centreon: + dsn: "mysql:host=10.30.2.245;dbname=centreon" + username: centreon + password: centreon + db_centstorage: + dsn: "mysql:host=10.30.2.245;dbname=centreon_storage" + username: centreon + password: centreon +gorgonecore: + internal_com_type: ipc + internal_com_path: /tmp/gorgone/routing.ipc + external_com_type: tcp + external_com_path: "*:5555" + # in seconds before sending kill signals (not gently) + timeout: 50 + gorgone_db_type: SQLite + gorgone_db_name: dbname=/tmp/gorgone.sdb + gorgone_db_host: + gorgone_db_port: + gorgone_db_user: + gorgone_db_password: + # If not set. Use 'hostname' function. + hostname: + # If not set. Try from 'hostname' in database + # Set 'none', if you don't need it (for poller in push mode) + id: none + privkey: keys/central/privkey.pem + cipher: "Cipher::AES" + # in bytes + keysize: 32 + # 16 bytes for AES + vector: 0123456789012345 + # in seconds + sessions_time: 86400 + # in seconds + purge_sessions_time: 3600 + # shouldn't be changed + proxy_name: gorgoneproxy +modules: + - name: gorgonecron + module: modules::gorgonecron::hooks + enable: 1 + - name: gorgoneaction + module: modules::gorgoneaction::hooks + enable: 1 + - name: gorgoneacl + module: modules::gorgoneacl::hooks + enable: 0 + on_demand: 1 + # How much to keep open in seconds without event received + on_demand_time: 60 + # in seconds - do purge for organizations also + check_organizations_time: 3600 + # in seconds - do a resync of the organizations + resync_time: 28800 + # in seconds - random windows (to avoid resync at the same time) + resync_random_windows: 7200 + # set to 1 to disable - if you want to do it by a cron + resync_auto_disable: 0 + sql_fetch: 10000 + sql_bulk: 2000 + - name: gorgoneproxy + module: modules::gorgoneproxy::hooks + enable: 0 + pool: 5 + # sync history each 5 minutes + synchistory_time: 300 + # how much time before the response is in timeout + synchistory_timeout: 120 + # ping each X seconds + ping: 60 + - name: gorgonescom + module: modules::gorgonescom::hooks + enable: 0 + # in seconds - do purge for container also + check_containers_time: 3600 + dsmclient_bin: /usr/share/centreon/bin/dsmclient.pl + centcore_cmd: /var/lib/centreon/centcore.cmd + containers: + # - name: toto + # url: "http://scomserver" + # username: toto + # password: pass + # resync_time: 300 + # dsmhost: centreon + # dsmslot: slot-% + # dsmmacro: ALARM_ID + # dsmalertmessage: "%{monitoringobjectdisplayname} %{name}" + # dsmrecoverymessage: slot ok + # - name: tutu + # url: http://scomserver2/ + # username: toto2 + # password: toto2 + # resync_time: 600 + - name: gorgonenewtest + module: modules::gorgonenewtest::hooks + enable: 0 + # in seconds - do purge for container also + check_containers_time: 3600 + clapi_command: /usr/bin/centreon + clapi_username: admin + clapi_password: centreon + clapi_action_applycfg: RELOAD + centcore_cmd: /var/lib/centreon/centcore.cmd + containers: + # - name: toto + # resync_time: 300 + # nmc_endpoint: "http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx" + # password: pass + # host_template: generic-active-host-custom + # host_prefix: Robot-%s + # service_template: generic-passive-service-custom + # service_prefix: Scenario-%s + # poller_name=: Central + # list_scenario_status: '{ "search": "All", "instances": [] }' + # - name: tutu + # resync_time: 600 + # nmc_endpoint: "http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx" + # password: pass + # host_template: generic-active-host-custom + # host_prefix: Robot-%s + # service_template: generic-passive-service-custom + # service_prefix: Scenario-%s + # poller_name=: Central + # list_scenario_status: '{ "search": "Robot", "instances": ["XXXX"] }' diff --git a/gorgone/modules/gorgoneacl/class.pm b/gorgone/modules/gorgoneacl/class.pm index 9671093d325..eea01a14c39 100644 --- a/gorgone/modules/gorgoneacl/class.pm +++ b/gorgone/modules/gorgoneacl/class.pm @@ -275,7 +275,7 @@ sub insert_result { # MS SQL: IGNORE_DUP_KEY # Postgres: ??? my $prepare_str = '(' . $self->{organization_id} . ', ' . $options{acl_resource_id} . ', ?, ?)'; - for (my $i = 1; $i < $self->{config}{sql_bulk}; $i++) { + for (my $i = 1; $i < $self->{config}->{sql_bulk}; $i++) { $prepare_str .= ', (' . $self->{organization_id} . ', ' . $options{acl_resource_id} . ', ?, ?)'; } ($status, $sth) = $self->{db_centreon}->query('INSERT IGNORE INTO cfg_acl_resources_cache VALUES ' . $prepare_str, prepare_only => 1); @@ -284,13 +284,13 @@ sub insert_result { my $i = 0; my @array_binds = (); while (my $row = ( shift(@$rows) || # get row from cache, or reload cache: - shift(@{$rows = $options{hs_sth}->fetchall_arrayref(undef, $self->{config}{sql_fetch})||[]})) ) { + shift(@{$rows = $options{hs_sth}->fetchall_arrayref(undef, $self->{config}->{sql_fetch})||[]})) ) { if (!defined($host_insert{$$row[0]})) { $host_insert{$$row[0]} = 1; push @array_binds, 1, $$row[0]; $i++; - if ($i == $self->{config}{sql_bulk}) { + if ($i == $self->{config}->{sql_bulk}) { return 1 if ($self->insert_execute(sth => $sth, bind => \@array_binds)); $i = 0; @array_binds = (); @@ -299,7 +299,7 @@ sub insert_result { push @array_binds, 2, $$row[1]; $i++; - if ($i == $self->{config}{sql_bulk}) { + if ($i == $self->{config}->{sql_bulk}) { return 1 if ($self->insert_execute(sth => $sth, bind => \@array_binds)); $i = 0; @array_binds = (); @@ -387,9 +387,9 @@ sub run { # Database creation. We stay in the loop still there is an error $self->{db_centreon} = centreon::misc::db->new( - dsn => $self->{config_db_centreon}{dsn}, - user => $self->{config_db_centreon}{username}, - password => $self->{config_db_centreon}{password}, + dsn => $self->{config_db_centreon}->{dsn}, + user => $self->{config_db_centreon}->{username}, + password => $self->{config_db_centreon}->{password}, force => 2, logger => $self->{logger} ); @@ -402,8 +402,8 @@ sub run { $socket = centreon::gorgone::common::connect_com( zmq_type => 'ZMQ_DEALER', name => 'gorgoneacl-' . $self->{organization_id}, logger => $self->{logger}, - type => $self->{config_core}{internal_com_type}, - path => $self->{config_core}{internal_com_path} + type => $self->{config_core}->{internal_com_type}, + path => $self->{config_core}->{internal_com_path} ); centreon::gorgone::common::zmq_send_message(socket => $socket, action => 'ACLREADY', data => { organization_id => $self->{organization_id} }, @@ -427,7 +427,7 @@ sub run { # Check if we need to quit if ($on_demand == 1) { if (defined($rev) && $rev == 0) { - if (time() - $on_demand_time > $self->{config}{on_demand_time}) { + if (time() - $on_demand_time > $self->{config}->{on_demand_time}) { $self->{logger}->writeLogInfo("gorgone-acl $$ has quit"); zmq_close($socket); exit(0); diff --git a/gorgone/modules/gorgoneaction/class.pm b/gorgone/modules/gorgoneaction/class.pm index 1d15163073e..9e135cf527b 100644 --- a/gorgone/modules/gorgoneaction/class.pm +++ b/gorgone/modules/gorgoneaction/class.pm @@ -38,8 +38,8 @@ sub new { $connector->{config_core} = $options{config_core}; $connector->{stop} = 0; - $connector->{enginecommand_timeout} = defined($connector->{config}{enginecommand_timeout}) ? $connector->{config}{enginecommand_timeout} : 30; - $connector->{command_timeout} = defined($connector->{config}{command_timeout}) ? $connector->{config}{command_timeout} : 30; + $connector->{enginecommand_timeout} = defined($connector->{config}->{enginecommand_timeout}) ? $connector->{config}->{enginecommand_timeout} : 30; + $connector->{command_timeout} = defined($connector->{config}->{command_timeout}) ? $connector->{config}->{command_timeout} : 30; bless $connector, $class; $connector->set_signal_handlers; @@ -195,8 +195,8 @@ sub action_run { name => 'gorgoneaction-'. $$, logger => $self->{logger}, linger => 5000, - type => $self->{config_core}{internal_com_type}, - path => $self->{config_core}{internal_com_path} + type => $self->{config_core}->{internal_com_type}, + path => $self->{config_core}->{internal_com_path} ); if ($options{action} eq 'COMMAND') { $self->action_command(%options, socket_log => $socket_log); @@ -268,8 +268,8 @@ sub run { zmq_type => 'ZMQ_DEALER', name => 'gorgoneaction', logger => $self->{logger}, - type => $self->{config_core}{internal_com_type}, - path => $self->{config_core}{internal_com_path} + type => $self->{config_core}->{internal_com_type}, + path => $self->{config_core}->{internal_com_path} ); centreon::gorgone::common::zmq_send_message( socket => $socket, diff --git a/gorgone/modules/gorgonecron/class.pm b/gorgone/modules/gorgonecron/class.pm index 3d2110335ec..7c6219dbeb7 100644 --- a/gorgone/modules/gorgonecron/class.pm +++ b/gorgone/modules/gorgonecron/class.pm @@ -107,8 +107,8 @@ sub run { $socket = centreon::gorgone::common::connect_com( zmq_type => 'ZMQ_DEALER', name => 'gorgonecron', logger => $self->{logger}, - type => $self->{config_core}{internal_com_type}, - path => $self->{config_core}{internal_com_path} + type => $self->{config_core}->{internal_com_type}, + path => $self->{config_core}->{internal_com_path} ); centreon::gorgone::common::zmq_send_message( socket => $socket, diff --git a/gorgone/modules/gorgonenewtest/class.pm b/gorgone/modules/gorgonenewtest/class.pm index 7293a3f5ded..2b83f4b147a 100644 --- a/gorgone/modules/gorgonenewtest/class.pm +++ b/gorgone/modules/gorgonenewtest/class.pm @@ -118,8 +118,8 @@ sub send_log { $self->{socket_log} = centreon::gorgone::common::connect_com( zmq_type => 'ZMQ_DEALER', name => 'gorgonenewtest-'. $self->{container_id}, logger => $self->{logger}, linger => 5000, - type => $self->{config_core}{internal_com_type}, - path => $self->{config_core}{internal_com_path} + type => $self->{config_core}->{internal_com_type}, + path => $self->{config_core}->{internal_com_path} ); } @@ -633,16 +633,16 @@ sub run { # Database creation. We stay in the loop still there is an error $self->{db_centstorage} = centreon::misc::db->new( - dsn => $self->{config_db_centstorage}{dsn}, - user => $self->{config_db_centstorage}{username}, - password => $self->{config_db_centstorage}{password}, + dsn => $self->{config_db_centstorage}->{dsn}, + user => $self->{config_db_centstorage}->{username}, + password => $self->{config_db_centstorage}->{password}, force => 2, logger => $self->{logger} ); $self->{db_centreon} = centreon::misc::db->new( - dsn => $self->{config_db_centreon}{dsn}, - user => $self->{config_db_centreon}{username}, - password => $self->{config_db_centreon}{password}, + dsn => $self->{config_db_centreon}->{dsn}, + user => $self->{config_db_centreon}->{username}, + password => $self->{config_db_centreon}->{password}, force => 2, logger => $self->{logger} ); @@ -656,8 +656,8 @@ sub run { $socket = centreon::gorgone::common::connect_com( zmq_type => 'ZMQ_DEALER', name => 'gorgonenewtest-' . $self->{container_id}, logger => $self->{logger}, - type => $self->{config_core}{internal_com_type}, - path => $self->{config_core}{internal_com_path} + type => $self->{config_core}->{internal_com_type}, + path => $self->{config_core}->{internal_com_path} ); centreon::gorgone::common::zmq_send_message( socket => $socket, diff --git a/gorgone/modules/gorgonescom/class.pm b/gorgone/modules/gorgonescom/class.pm index f02b12b8d8b..4315fd02603 100644 --- a/gorgone/modules/gorgonescom/class.pm +++ b/gorgone/modules/gorgonescom/class.pm @@ -264,9 +264,9 @@ sub run { # Database creation. We stay in the loop still there is an error $self->{db_centstorage} = centreon::misc::db->new( - dsn => $self->{config_db_centstorage}{dsn}, - user => $self->{config_db_centstorage}{username}, - password => $self->{config_db_centstorage}{password}, + dsn => $self->{config_db_centstorage}->{dsn}, + user => $self->{config_db_centstorage}->{username}, + password => $self->{config_db_centstorage}->{password}, force => 2, logger => $self->{logger} ); @@ -278,8 +278,8 @@ sub run { $socket = centreon::gorgone::common::connect_com( zmq_type => 'ZMQ_DEALER', name => 'gorgonescom-' . $self->{container_id}, logger => $self->{logger}, - type => $self->{config_core}{internal_com_type}, - path => $self->{config_core}{internal_com_path} + type => $self->{config_core}->{internal_com_type}, + path => $self->{config_core}->{internal_com_path} ); centreon::gorgone::common::zmq_send_message( socket => $socket, From 6826288af68d5b3e5bcec45795a15fcea464e76a Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Fri, 9 Aug 2019 17:11:48 +0200 Subject: [PATCH 019/948] put some todo --- gorgone/modules/gorgoneproxy/class.pm | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/gorgone/modules/gorgoneproxy/class.pm b/gorgone/modules/gorgoneproxy/class.pm index f74b1db7b63..b163df2d0b8 100644 --- a/gorgone/modules/gorgoneproxy/class.pm +++ b/gorgone/modules/gorgoneproxy/class.pm @@ -167,8 +167,12 @@ sub proxy { # type 1 = ZMQ. # type 2 = SSH if ($entry->{type} == 1) { - my ($status, $msg) = $entry->{class}->send_message(action => $action, token => $token, - target => '', data => $data); + my ($status, $msg) = $entry->{class}->send_message( + action => $action, + token => $token, + target => '', # TODO: don't set to null if we need to chain it!!! + data => $data + ); if ($status != 0) { # error we put log and we close (TODO the log) $connector->{logger}->writeLogError("gorgoneproxy: class: send message problem for '$target': $msg"); From 563564c960e9e5b9318d2bb0397dac4148059d8f Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Fri, 9 Aug 2019 17:14:08 +0200 Subject: [PATCH 020/948] update todo --- gorgone/TODO | 1 + 1 file changed, 1 insertion(+) diff --git a/gorgone/TODO b/gorgone/TODO index 866112d0c82..4b1bde51e71 100644 --- a/gorgone/TODO +++ b/gorgone/TODO @@ -2,4 +2,5 @@ - Add a HTTP frontend module for clients (https and basic auth). Can try: https://metacpan.org/pod/HTTP::Server::Simple - Add legacy module: read centcore.cmd - Can chain agents and forward protocol +- Add a core event: GETINFORMATION (events loaded, target loaded and module loaded) - gorgone-newtest: don't use centcore.cmd. use ssh system. From 34c6a16256b536f95c4122f0946ce83bef249e99 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 12 Aug 2019 10:20:11 +0200 Subject: [PATCH 021/948] change events tab format --- gorgone/centreon/gorgone/common.pm | 18 +++---- gorgone/centreon/script/gorgonecore.pm | 68 ++++++++++++++++---------- gorgone/config/gorgoned-poller.yml | 4 +- gorgone/config/gorgoned-poller2.yml | 2 +- gorgone/config/gorgoned.yml | 23 ++++++--- gorgone/modules/gorgoneaction/hooks.pm | 9 ++-- gorgone/modules/gorgonecron/hooks.pm | 7 ++- 7 files changed, 81 insertions(+), 50 deletions(-) diff --git a/gorgone/centreon/gorgone/common.pm b/gorgone/centreon/gorgone/common.pm index 6c753315fbf..0dedac64ee6 100644 --- a/gorgone/centreon/gorgone/common.pm +++ b/gorgone/centreon/gorgone/common.pm @@ -264,11 +264,11 @@ sub constatus { my $name = $options{gorgone_config}->{modules}->{$options{gorgone_config}->{gorgonecore}->{proxy_name}}->{module}; my $method; if (defined($name) && ($method = $name->can('get_constatus_result'))) { - return (0, { action => 'constatus', mesage => 'ok', data => $method->() }, 'CONSTATUS'); + return (0, { action => 'constatus', message => 'ok', data => $method->() }, 'CONSTATUS'); } } - return (1, { action => 'constatus', mesage => 'cannot get value' }, 'CONSTATUS'); + return (1, { action => 'constatus', message => 'cannot get value' }, 'CONSTATUS'); } sub ping { @@ -276,7 +276,7 @@ sub ping { #my $status = add_history(dbh => $options{gorgone}->{db_gorgone}, # token => $options{token}, logger => $options{logger}, code => 0); - return (0, { action => 'ping', mesage => 'ping ok', id => $options{id} }, 'PONG'); + return (0, { action => 'ping', message => 'ping ok', id => $options{id} }, 'PONG'); } sub putlog { @@ -287,15 +287,15 @@ sub putlog { $data = JSON->new->utf8->decode($options{data}); }; if ($@) { - return (1, { mesage => 'request not well formatted' }); + return (1, { message => 'request not well formatted' }); } my $status = add_history(dbh => $options{gorgone}->{db_gorgone}, etime => $data->{etime}, token => $data->{token}, data => json_encode(data => $data->{data}, logger => $options{logger}), code => $data->{code}); if ($status == -1) { - return (1, { mesage => 'database issue' }); + return (1, { message => 'database issue' }); } - return (0, { mesage => 'message inserted' }); + return (0, { message => 'message inserted' }); } sub getlog { @@ -306,7 +306,7 @@ sub getlog { $data = JSON->new->utf8->decode($options{data}); }; if ($@) { - return (1, { mesage => 'request not well formatted' }); + return (1, { message => 'request not well formatted' }); } my %filters = (); @@ -320,12 +320,12 @@ sub getlog { } if ($filter eq '') { - return (1, { mesage => 'need at least one filter' }); + return (1, { message => 'need at least one filter' }); } my ($status, $sth) = $options{gorgone}->{db_gorgone}->query("SELECT * FROM gorgone_history WHERE " . $filter); if ($status == -1) { - return (1, { mesage => 'database issue' }); + return (1, { message => 'database issue' }); } return (0, { action => 'getlog', result => $sth->fetchall_hashref('id'), id => $options{gorgone}->{id} }); diff --git a/gorgone/centreon/script/gorgonecore.pm b/gorgone/centreon/script/gorgonecore.pm index 4dd9e3b97b9..3018e5b553f 100644 --- a/gorgone/centreon/script/gorgonecore.pm +++ b/gorgone/centreon/script/gorgonecore.pm @@ -177,8 +177,8 @@ sub load_modules { my $self = shift; next if (!defined($config->{modules})); - foreach my $module (@{$config->{modules}}) { - next if (!defined($module->{enable}) || $module->{enable} eq '0'); + foreach my $module (@{$config->{modules}}) { + next if (!defined($module->{enable}) || $module->{enable} eq 'false'); my $name = $module->{module}; (my $file = "$name.pm") =~ s{::}{/}g; require $file; @@ -192,20 +192,22 @@ sub load_modules { } } - my ($events, $id) = $self->{modules_register}->{$name}->{register}->( + my ($events, $shortname, $id) = $self->{modules_register}->{$name}->{register}->( config => $module, config_core => $config->{gorgonecore}, config_db_centreon => $config->{database}->{db_centreon}, config_db_centstorage => $config->{database}->{db_centstorage} ); $self->{modules_id}->{$id} = $name; + foreach my $event (@{$events}) { - $self->{modules_events}->{$event} = [] if (!defined($self->{modules_events}->{$event})); - push @{$self->{modules_events}->{$event}}, $name; + $self->{modules_events}->{$event->{event}} = { + module => { name => $name, shortname => $shortname }, + api => { uri => $event->{uri}, method => $event->{method} } + }; } - $self->{logger}->writeLogInfo("Module '" . $module->{name} . "' is loaded"); - } + } # Load internal functions foreach my $method_name (('putlog', 'getlog', 'kill', 'ping', 'constatus')) { @@ -220,13 +222,14 @@ sub message_run { my ($self, %options) = @_; if ($options{message} !~ /^\[(.+?)\]\s+\[(.*?)\]\s+\[(.*?)\]\s+(.*)$/) { - return (undef, 1, { mesage => 'request not well formatted' }); + return (undef, 1, { message => 'request not well formatted' }); } my ($action, $token, $target, $data) = ($1, $2, $3, $4); if ($action !~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS)$/ && !defined($self->{modules_events}->{$action})) { centreon::gorgone::common::add_history( dbh => $self->{db_gorgone}, - code => 1, token => $token, + code => 1, + token => $token, data => { msg => "action '$action' is not known" }, json_encode => 1 ); @@ -239,7 +242,8 @@ sub message_run { if ($self->{stop} == 1) { centreon::gorgone::common::add_history( dbh => $self->{db_gorgone}, - code => 1, token => $token, + code => 1, + token => $token, data => { msg => 'gorgone is stopping/restarting. Not proceed request.' }, json_encode => 1 ); @@ -250,9 +254,14 @@ sub message_run { if (defined($target) && $target ne '') { # Check if not myself ;) if ($target ne $self->{id}) { - $self->{modules_register}->{ $self->{modules_id}->{$config->{gorgonecore}->{proxy_name}} }->{routing}->( - socket => $self->{internal_socket}, dbh => $self->{db_gorgone}, logger => $self->{logger}, - action => $1, token => $token, target => $target, data => $data, + $self->{modules_register}->{ $self->{modules_id}->{$config->{gorgonecore}->{proxy_name}} }->{routing}->( + socket => $self->{internal_socket}, + dbh => $self->{db_gorgone}, + logger => $self->{logger}, + action => $1, + token => $token, + target => $target, + data => $data, hostname => $self->{hostname} ); return ($token, 0); @@ -270,14 +279,16 @@ sub message_run { ); return ($token, $code, $response, $response_type); } else { - foreach (@{$self->{modules_events}->{$action}}) { - $self->{modules_register}->{$_}->{routing}->( - socket => $self->{internal_socket}, - dbh => $self->{db_gorgone}, logger => $self->{logger}, - action => $1, token => $token, target => $target, data => $data, - hostname => $self->{hostname} - ); - } + $self->{modules_register}->{$self->{modules_events}->{$action}->{module}->{name}}->{routing}->( + socket => $self->{internal_socket}, + dbh => $self->{db_gorgone}, + logger => $self->{logger}, + action => $1, + token => $token, + target => $target, + data => $data, + hostname => $self->{hostname} + ); } return ($token, 0); } @@ -288,8 +299,10 @@ sub router_internal_event { my ($token, $code, $response, $response_type) = $gorgone->message_run(message => $message); centreon::gorgone::common::zmq_core_response( socket => $gorgone->{internal_socket}, - identity => $identity, response_type => $response_type, - data => $response, code => $code, + identity => $identity, + response_type => $response_type, + data => $response, + code => $code, token => $token ); last unless (centreon::gorgone::common::zmq_still_read(socket => $gorgone->{internal_socket})); @@ -494,11 +507,13 @@ sub run { foreach my $name (keys %{$gorgone->{modules_register}}) { $gorgone->{logger}->writeLogInfo("Call init function from module '$name'"); $gorgone->{modules_register}->{$name}->{init}->( - logger => $gorgone->{logger}, id => $gorgone->{id}, + id => $gorgone->{id}, + logger => $gorgone->{logger}, poll => $gorgone->{poll}, external_socket => $gorgone->{external_socket}, internal_socket => $gorgone->{internal_socket}, - dbh => $gorgone->{db_gorgone} + dbh => $gorgone->{db_gorgone}, + modules_events => $gorgone->{modules_events}, ); } @@ -514,7 +529,8 @@ sub run { dead_childs => $gorgone->{return_child}, internal_socket => $gorgone->{internal_socket}, dbh => $gorgone->{db_gorgone}, - poll => $poll + poll => $poll, + modules_events => $gorgone->{modules_events}, ); } diff --git a/gorgone/config/gorgoned-poller.yml b/gorgone/config/gorgoned-poller.yml index e6077812e7b..6724ef7aa67 100644 --- a/gorgone/config/gorgoned-poller.yml +++ b/gorgone/config/gorgoned-poller.yml @@ -28,7 +28,7 @@ gorgonecore: modules: - name: gorgonepull module: modules::gorgonepull::hooks - enable: 1 + enable: true target_type: tcp target_path: 127.0.0.1:5555 linger: 5000 @@ -44,7 +44,7 @@ modules: ping_timeout: 30 - name: gorgoneaction module: modules::gorgoneaction::hooks - enable: 1 + enable: true disable_command_event: 0 disable_enginecomand_event: 0 enginecommand_timeout: 30 diff --git a/gorgone/config/gorgoned-poller2.yml b/gorgone/config/gorgoned-poller2.yml index c3ebf1669e5..fe144e09257 100644 --- a/gorgone/config/gorgoned-poller2.yml +++ b/gorgone/config/gorgoned-poller2.yml @@ -30,4 +30,4 @@ gorgonecore: modules: - name: gorgoneaction module: modules::gorgoneaction::hooks - enable: 1 + enable: true diff --git a/gorgone/config/gorgoned.yml b/gorgone/config/gorgoned.yml index d7cb4098393..0a501feb41e 100644 --- a/gorgone/config/gorgoned.yml +++ b/gorgone/config/gorgoned.yml @@ -39,15 +39,26 @@ gorgonecore: # shouldn't be changed proxy_name: gorgoneproxy modules: + - name: gorgonehttpserver + module: modules::gorgonehttpserver::hooks + enable: true + address: 0.0.0.0 + port: 8080 + ssl: true + ssl_cert_file: /etc/pki/tls/certs/server-cert.pem + ssl_key_file: /etc/pki/tls/server-key.pem + auth: + user: admin + password: password - name: gorgonecron module: modules::gorgonecron::hooks - enable: 1 + enable: true - name: gorgoneaction module: modules::gorgoneaction::hooks - enable: 1 + enable: false - name: gorgoneacl module: modules::gorgoneacl::hooks - enable: 0 + enable: false on_demand: 1 # How much to keep open in seconds without event received on_demand_time: 60 @@ -63,7 +74,7 @@ modules: sql_bulk: 2000 - name: gorgoneproxy module: modules::gorgoneproxy::hooks - enable: 0 + enable: false pool: 5 # sync history each 5 minutes synchistory_time: 300 @@ -73,7 +84,7 @@ modules: ping: 60 - name: gorgonescom module: modules::gorgonescom::hooks - enable: 0 + enable: false # in seconds - do purge for container also check_containers_time: 3600 dsmclient_bin: /usr/share/centreon/bin/dsmclient.pl @@ -96,7 +107,7 @@ modules: # resync_time: 600 - name: gorgonenewtest module: modules::gorgonenewtest::hooks - enable: 0 + enable: false # in seconds - do purge for container also check_containers_time: 3600 clapi_command: /usr/bin/centreon diff --git a/gorgone/modules/gorgoneaction/hooks.pm b/gorgone/modules/gorgoneaction/hooks.pm index 1ec9ac9966b..fdf0aa332c0 100644 --- a/gorgone/modules/gorgoneaction/hooks.pm +++ b/gorgone/modules/gorgoneaction/hooks.pm @@ -27,9 +27,10 @@ use modules::gorgoneaction::class; my $config_core; my $config; +my $module_shortname = 'action'; my $module_id = 'gorgoneaction'; my $events = [ - 'ACTIONREADY', + { event => 'ACTIONREADY' }, ]; my $action = {}; my $stop = 0; @@ -40,12 +41,12 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; if (!defined($config->{disable_command_event}) || $config->{disable_command_event} != 1) { - push @{$events}, 'COMMAND'; + push @{$events}, { event => 'COMMAND', uri => '/command', method => 'POST' }; } if (!defined($config->{disable_enginecommand_event}) || $config->{disable_enginecommand_event} != 1) { - push @{$events}, 'ENGINECOMMAND'; + push @{$events}, { event => 'ENGINECOMMAND', uri => '/enginecommand', method => 'POST' }; } - return ($events, $module_id); + return ($events, $module_shortname, $module_id); } sub init { diff --git a/gorgone/modules/gorgonecron/hooks.pm b/gorgone/modules/gorgonecron/hooks.pm index c12ce6fe94e..595a957ca3b 100644 --- a/gorgone/modules/gorgonecron/hooks.pm +++ b/gorgone/modules/gorgonecron/hooks.pm @@ -27,9 +27,12 @@ use modules::gorgonecron::class; my $config_core; my $config; +my $module_shortname = 'cron'; my $module_id = 'gorgonecron'; my $events = [ - 'CRONREADY', 'RELOADCRON', + { event => 'CRONREADY' }, + { event => 'RELOADCRON', uri => '/reload', method => 'POST' }, + { event => 'LISTCRON', uri => '/list', method => 'GET' }, ]; my $cron = {}; my $stop = 0; @@ -39,7 +42,7 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; - return ($events, $module_id); + return ($events, $module_shortname , $module_id); } sub init { From cc9deee0391175b6927be056f9b0164f27e8c449 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Mon, 12 Aug 2019 11:15:35 +0200 Subject: [PATCH 022/948] manage yaml config --- gorgone/TODO | 1 + gorgone/centreon/gorgone/module.pm | 59 ++++++++++++++++ gorgone/config/gorgoned.yml | 49 ++++++++------ gorgone/docs/guide.rst | 42 +++++------- gorgone/docs/modules/newtest/exploitation.txt | 67 +++++++++---------- gorgone/gorgoned | 2 +- gorgone/modules/gorgoneaction/hooks.pm | 5 +- gorgone/modules/gorgonenewtest/class.pm | 52 +++++--------- gorgone/modules/gorgonenewtest/hooks.pm | 54 +++++++-------- gorgone/modules/gorgoneproxy/hooks.pm | 11 ++- gorgone/modules/gorgonepull/hooks.pm | 6 +- 11 files changed, 194 insertions(+), 154 deletions(-) create mode 100644 gorgone/centreon/gorgone/module.pm diff --git a/gorgone/TODO b/gorgone/TODO index 4b1bde51e71..c3b1387c93f 100644 --- a/gorgone/TODO +++ b/gorgone/TODO @@ -4,3 +4,4 @@ - Can chain agents and forward protocol - Add a core event: GETINFORMATION (events loaded, target loaded and module loaded) - gorgone-newtest: don't use centcore.cmd. use ssh system. +- Use 4 code for all modules: 0 -> transaction progress, 1 -> transaction finished ok, 2 -> transaction finished ko diff --git a/gorgone/centreon/gorgone/module.pm b/gorgone/centreon/gorgone/module.pm new file mode 100644 index 00000000000..b18f75623d1 --- /dev/null +++ b/gorgone/centreon/gorgone/module.pm @@ -0,0 +1,59 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package centreon::gorgone::module; + +use centreon::gorgone::common; + +use constant ACTION_BEGIN => 0; +use constant ACTION_FINISH_KO => 1; +use constant ACTION_FINISH_OK => 2; + +sub generate_token { + my ($self, %options) = @_; + + return centreon::gorgone::common::generate_token(); +} + +sub send_log { + my ($self, %options) = @_; + + return if (!defined($options{token})); + + if (!defined($self->{socket_log})) { + $self->{socket_log} = centreon::gorgone::common::connect_com( + zmq_type => 'ZMQ_DEALER', name => $self->{module_id} . '-'. $self->{container_id}, + logger => $self->{logger}, linger => 5000, + type => $self->{config_core}->{internal_com_type}, + path => $self->{config_core}->{internal_com_path} + ); + } + + centreon::gorgone::common::zmq_send_message( + socket => $self->{socket_log}, + action => 'PUTLOG', + data => { code => $options{code}, etime => time(), token => $options{token}, data => $options{data} }, + json_encode => 1 + ); +} + + + +1; diff --git a/gorgone/config/gorgoned.yml b/gorgone/config/gorgoned.yml index d7cb4098393..c518248efe5 100644 --- a/gorgone/config/gorgoned.yml +++ b/gorgone/config/gorgoned.yml @@ -42,9 +42,11 @@ modules: - name: gorgonecron module: modules::gorgonecron::hooks enable: 1 + - name: gorgoneaction module: modules::gorgoneaction::hooks - enable: 1 + enable: 0 + - name: gorgoneacl module: modules::gorgoneacl::hooks enable: 0 @@ -61,6 +63,7 @@ modules: resync_auto_disable: 0 sql_fetch: 10000 sql_bulk: 2000 + - name: gorgoneproxy module: modules::gorgoneproxy::hooks enable: 0 @@ -71,6 +74,7 @@ modules: synchistory_timeout: 120 # ping each X seconds ping: 60 + - name: gorgonescom module: modules::gorgonescom::hooks enable: 0 @@ -94,9 +98,10 @@ modules: # username: toto2 # password: toto2 # resync_time: 600 + - name: gorgonenewtest module: modules::gorgonenewtest::hooks - enable: 0 + enable: 1 # in seconds - do purge for container also check_containers_time: 3600 clapi_command: /usr/bin/centreon @@ -105,23 +110,23 @@ modules: clapi_action_applycfg: RELOAD centcore_cmd: /var/lib/centreon/centcore.cmd containers: - # - name: toto - # resync_time: 300 - # nmc_endpoint: "http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx" - # password: pass - # host_template: generic-active-host-custom - # host_prefix: Robot-%s - # service_template: generic-passive-service-custom - # service_prefix: Scenario-%s - # poller_name=: Central - # list_scenario_status: '{ "search": "All", "instances": [] }' - # - name: tutu - # resync_time: 600 - # nmc_endpoint: "http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx" - # password: pass - # host_template: generic-active-host-custom - # host_prefix: Robot-%s - # service_template: generic-passive-service-custom - # service_prefix: Scenario-%s - # poller_name=: Central - # list_scenario_status: '{ "search": "Robot", "instances": ["XXXX"] }' + - name: toto + resync_time: 300 + nmc_endpoint: "http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx" + password: pass + host_template: generic-active-host-custom + host_prefix: Robot-%s + service_template: generic-passive-service-custom + service_prefix: Scenario-%s + poller_name: Central + list_scenario_status: '{ "search": "All", "instances": [] }' + - name: tutu + resync_time: 600 + nmc_endpoint: "http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx" + password: pass + host_template: generic-active-host-custom + host_prefix: Robot-%s + service_template: generic-passive-service-custom + service_prefix: Scenario-%s + poller_name: Central + list_scenario_status: '{ "search": "Robot", "instances": ["XXXX"] }' diff --git a/gorgone/docs/guide.rst b/gorgone/docs/guide.rst index 092058ec1e3..4dc5d2f1192 100644 --- a/gorgone/docs/guide.rst +++ b/gorgone/docs/guide.rst @@ -20,7 +20,7 @@ Daemon uses following Perl modules: * ZMQ::LibZMQ4: repository 'centreon-stable' * JSON: repository 'centos base' -* Config::IniFiles: repository 'centos base' +* YAML: repository 'centos base' * DBD::SQLite: repository 'centos base' * DBD::mysql: repository 'centos base' * UUID: repository 'centreon-stable' @@ -33,7 +33,7 @@ Execute following commands: :: - # yum install 'perl(Schedule::Cron)' 'perl(Crypt::CBC)' 'perl(ZMQ::LibZMQ4)' 'perl(JSON)' 'perl(Config::IniFiles)' 'perl(DBD::SQLite)' 'perl(DBD::mysql)' 'perl(UUID)' 'perl(Crypt::OpenSSL::RSA)' + # yum install 'perl(Schedule::Cron)' 'perl(Crypt::CBC)' 'perl(ZMQ::LibZMQ4)' 'perl(JSON)' 'perl(YAML)' 'perl(DBD::SQLite)' 'perl(DBD::mysql)' 'perl(UUID)' 'perl(Crypt::OpenSSL::RSA)' # yum install perl-CryptX-0.064-1.el7.x86_64 Create sqlite database: @@ -240,6 +240,17 @@ The client request: [PUTLOG] [TOKEN] [TARGET] { code => xxx, etime => xxx, token => xxxx, data => { some_datas } } +============ +Common codes +============ + +Common code responses for all module requests: +* 0: action proceed +* 1: action finished OK +* 2: action finished KO + +Modules can have extra codes. + =============== module requests =============== @@ -248,12 +259,7 @@ module requests gorgone-acl ----------- -Common code responses: - -* 100: problem. It stopped (read the message) -* 101: action proceed -* 105: problem (read the message) -* 106: action had been finished +No extra codes. ACLADDHOST ^^^^^^^^^^ @@ -407,18 +413,10 @@ A client example: [COMMAND] [] [target_id] { command => 'ls /' } -The code responses: - -* x0: problem. It stopped (read the message) -* 31: command proceed -* 32: command proceed end -* 35: problem. It stopped (read the message) -* 36: command had been finished - -With the code 36, you can get following attributes: +With the code 1, you can get following attributes: :: - { code => 36, stdout => 'xxxxx', exit_code => xxx } + { code => 1, stdout => 'xxxxx', exit_code => xxx } ENGINECOMMAND ^^^^^^^^^^^^^ @@ -429,14 +427,6 @@ A client example: [ENGINECOMMAND] [] [target_id] { command => '[1417705150] ENABLE_HOST_CHECK;host1', engine_pipe => '/var/lib/centreon-engine/rw/centengine.cmd' -The code responses: - -* x0: problem. It stopped (read the message) -* 31: command proceed -* 32: command proceed end -* 35: problem. It stopped (read the message) -* 36: command had been submitted - You only have the message to get informations (it tells you if there are some permission problems or file missing). *** diff --git a/gorgone/docs/modules/newtest/exploitation.txt b/gorgone/docs/modules/newtest/exploitation.txt index 46876874e2f..0decbf69b72 100644 --- a/gorgone/docs/modules/newtest/exploitation.txt +++ b/gorgone/docs/modules/newtest/exploitation.txt @@ -22,41 +22,38 @@ Configuration The « gorgone-newtest » module is configured with gorgone configuration file :: - [gorgonenewtest] - module=modules::gorgonenewtest::hooks - - ; in seconds - do purge for container also - check_containers_time=3600 - clapi_command=/usr/bin/centreon - clapi_username=admin - clapi_password=centreon - clapi_action_applycfg=RELOAD - - containers=toto,tutu - - toto_resync_time=300 - toto_nmc_endpoint=http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx - toto_nmc_username=test - toto_nmc_password=test - toto_nmc_timeout=10 - toto_password=pass - toto_host_template=generic-active-host-custom - toto_host_prefix=Robot-%s - toto_service_template=generic-passive-service-custom - toto_service_prefix=Scenario-%s - toto_poller_name=Central - toto_list_scenario_status={ "search": "All", "instances": [] } - - tutu_resync_time=600 - tutu_nmc_endpoint=http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx - tutu_nmc_timeout=10 - tutu_password=pass - tutu_host_template=generic-active-host-custom - tutu_host_prefix=Robot-%s - tutu_service_template=generic-passive-service-custom - tutu_service_prefix=Scenario-%s - tutu_poller_name=Central - tutu_list_scenario_status={ "search": "Robot", "instances": ["XXXX"] } + modules: + - name: gorgonenewtest + module: modules::gorgonenewtest::hooks + enable: 1 + # in seconds - do purge for container also + check_containers_time: 3600 + clapi_command: /usr/bin/centreon + clapi_username: admin + clapi_password: centreon + clapi_action_applycfg: RELOAD + centcore_cmd: /var/lib/centreon/centcore.cmd + containers: + - name: toto + resync_time: 300 + nmc_endpoint: "http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx" + password: pass + host_template: generic-active-host-custom + host_prefix: Robot-%s + service_template: generic-passive-service-custom + service_prefix: Scenario-%s + poller_name: Central + list_scenario_status: '{ "search": "All", "instances": [] }' + - name: tutu + resync_time: 600 + nmc_endpoint: "http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx" + password: pass + host_template: generic-active-host-custom + host_prefix: Robot-%s + service_template: generic-passive-service-custom + service_prefix: Scenario-%s + poller_name: Central + list_scenario_status: '{ "search": "Robot", "instances": ["XXXX"] }' You have to add at least one entry in the configuration. The meaning of attributes : diff --git a/gorgone/gorgoned b/gorgone/gorgoned index 6287d2e8284..a4131c05419 100644 --- a/gorgone/gorgoned +++ b/gorgone/gorgoned @@ -42,7 +42,7 @@ gorgoned [options] =item B<--config-extra> -Specify the path to the centreonesxd configuration file (default: /etc/centreon/centreon_esxd.pm). +Specify the path to the centreonesxd configuration file (default: ''). =item B<--help> diff --git a/gorgone/modules/gorgoneaction/hooks.pm b/gorgone/modules/gorgoneaction/hooks.pm index 1ec9ac9966b..60bf5ae4250 100644 --- a/gorgone/modules/gorgoneaction/hooks.pm +++ b/gorgone/modules/gorgoneaction/hooks.pm @@ -27,9 +27,10 @@ use modules::gorgoneaction::class; my $config_core; my $config; +my $module_shortname = 'action'; my $module_id = 'gorgoneaction'; my $events = [ - 'ACTIONREADY', + { event => 'ACTIONREADY' }, ]; my $action = {}; my $stop = 0; @@ -45,7 +46,7 @@ sub register { if (!defined($config->{disable_enginecommand_event}) || $config->{disable_enginecommand_event} != 1) { push @{$events}, 'ENGINECOMMAND'; } - return ($events, $module_id); + return ($events, $module_shortname , $module_id); } sub init { diff --git a/gorgone/modules/gorgonenewtest/class.pm b/gorgone/modules/gorgonenewtest/class.pm index 2b83f4b147a..a3917f4327b 100644 --- a/gorgone/modules/gorgonenewtest/class.pm +++ b/gorgone/modules/gorgonenewtest/class.pm @@ -20,6 +20,8 @@ package modules::gorgonenewtest::class; +use base qw(centreon::gorgone::module); + use strict; use warnings; use centreon::misc::misc; @@ -39,7 +41,9 @@ my ($connector, $socket); sub new { my ($class, %options) = @_; + $connector = {}; + $connector->{module_id} = $options{module_id}; $connector->{logger} = $options{logger}; $connector->{container_id} = $options{container_id}; $connector->{config} = $options{config}; @@ -109,28 +113,6 @@ sub class_handle_HUP { } } -sub send_log { - my ($self, %options) = @_; - - return if (!defined($options{token})); - - if (!defined($self->{socket_log})) { - $self->{socket_log} = centreon::gorgone::common::connect_com( - zmq_type => 'ZMQ_DEALER', name => 'gorgonenewtest-'. $self->{container_id}, - logger => $self->{logger}, linger => 5000, - type => $self->{config_core}->{internal_com_type}, - path => $self->{config_core}->{internal_com_path} - ); - } - - centreon::gorgone::common::zmq_send_message( - socket => $self->{socket_log}, - action => 'PUTLOG', - data => { code => $options{code}, etime => time(), token => $options{token}, data => $options{data} }, - json_encode => 1 - ); -} - my %map_scenario_status = ( Available => 0, Warning => 1, Failed => 2, Suspended => 2, Canceled => 2, Unknown => 3, @@ -180,9 +162,11 @@ sub add_output { } $self->{perfdatas} = []; - $self->push_external_cmd(cmd => 'PROCESS_SERVICE_CHECK_RESULT;' . $options{host_name} . ';' . - $options{service_name} . ';' . $self->{current_status} . ';' . $str, - time => $options{time}); + $self->push_external_cmd( + cmd => 'PROCESS_SERVICE_CHECK_RESULT;' . $options{host_name} . ';' . + $options{service_name} . ';' . $self->{current_status} . ';' . $str, + time => $options{time} + ); } sub convert_measure { @@ -578,32 +562,33 @@ sub get_newtest_scenarios { sub action_newtestresync { my ($self, %options) = @_; + $options{token} = $self->generate_token() if (!defined($options{token})); $self->{logger}->writeLogDebug("gorgone-newtest: container $self->{container_id}: begin resync"); - $self->send_log(code => 301, token => $options{token}, data => { message => 'action newtestresync proceed' }); + $self->send_log(code => centreon::gorgone::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action newtestresync proceed' }); $self->newtestresync_init(); if ($self->get_poller_id()) { - $self->send_log(code => 305, token => $options{token}, data => { message => 'cannot get poller id' }); + $self->send_log(code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot get poller id' }); return -1; } if ($self->get_centreondb_cache()) { - $self->send_log(code => 305, token => $options{token}, data => { message => 'cannot get centreon config cache' }); + $self->send_log(code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot get centreon config cache' }); return -1; } if ($self->get_centstoragedb_cache()) { - $self->send_log(code => 305, token => $options{token}, data => { message => 'cannot get centreon storage cache' }); + $self->send_log(code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot get centreon storage cache' }); return -1; } if ($self->get_newtest_scenarios(%options)) { - $self->send_log(code => 305, token => $options{token}, data => { message => 'cannot get newtest scenarios' }); + $self->send_log(code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot get newtest scenarios' }); return -1; } $self->push_config(); $self->submit_external_cmd(); - $self->send_log(code => 306, token => $options{token}, data => { message => 'action newtestresync finished' }); + $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => { message => 'action newtestresync finished' }); return 0; } @@ -617,10 +602,7 @@ sub event { $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; my ($action, $token) = ($1, $2); my $data = JSON::XS->new->utf8->decode($3); - while ($method->($connector, token => $token, data => $data)) { - # We block until it's fixed!! - sleep(5); - } + $method->($connector, token => $token, data => $data); } } diff --git a/gorgone/modules/gorgonenewtest/hooks.pm b/gorgone/modules/gorgonenewtest/hooks.pm index 1563c3a411c..9c22e497e6a 100644 --- a/gorgone/modules/gorgonenewtest/hooks.pm +++ b/gorgone/modules/gorgonenewtest/hooks.pm @@ -28,10 +28,11 @@ use modules::gorgonenewtest::class; my ($config_core, $config); my ($config_db_centreon, $config_db_centstorage); +my $module_shortname = 'newtest'; my $module_id = 'gorgonenewtest'; my $events = [ - 'NEWTESTREADY', - 'NEWTESTRESYNC', + { event => 'NEWTESTREADY' }, + { event => 'NEWTESTRESYNC', uri => '/resync', method => 'GET' }, ]; my $last_containers = {}; # Last values from config ini @@ -49,7 +50,7 @@ sub register { $config_db_centstorage = $options{config_db_centstorage}; $config_db_centreon = $options{config_db_centreon}; $config_check_containers_time = defined($config->{check_containers_time}) ? $config->{check_containers_time} : 3600; - return ($events, $module_id); + return ($events, $module_shortname , $module_id); } sub init { @@ -169,52 +170,50 @@ sub check { sub get_containers { my (%options) = @_; - my $containers = {}; return $containers if (!defined($config->{containers})); - foreach my $container_id (split /,/, $config->{containers}) { - next if ($container_id eq ''); + foreach (@{$config->{containers}}) { + next if (!defined($_->{name}) || $_->{name} eq ''); - if (!defined($config->{$container_id . '_nmc_endpoint'}) || $config->{$container_id . '_nmc_endpoint'} eq '') { - $options{logger}->writeLogError("gorgone-newtest: cannot load container '" . $container_id . "' - please set nmc_endpoint option"); + if (!defined($_->{nmc_endpoint}) || $_->{nmc_endpoint} eq '') { + $options{logger}->writeLogError("gorgone-newtest: cannot load container '" . $_->{name} . "' - please set nmc_endpoint option"); next; } - if (!defined($config->{$container_id . '_poller_name'}) || $config->{$container_id . '_poller_name'} eq '') { - $options{logger}->writeLogError("gorgone-newtest: cannot load container '" . $container_id . "' - please set poller_name option"); + if (!defined($_->{poller_name}) || $_->{poller_name} eq '') { + $options{logger}->writeLogError("gorgone-newtest: cannot load container '" . $_->{name} . "' - please set poller_name option"); next; } - if (!defined($config->{$container_id . '_list_scenario_status'}) || $config->{$container_id . '_list_scenario_status'} eq '') { - $options{logger}->writeLogError("gorgone-newtest: cannot load container '" . $container_id . "' - please set list_scenario_status option"); + if (!defined($_->{list_scenario_status}) || $_->{list_scenario_status} eq '') { + $options{logger}->writeLogError("gorgone-newtest: cannot load container '" . $_->{name} . "' - please set list_scenario_status option"); next; } - + my $list_scenario; eval { - $list_scenario = JSON::XS->new->utf8->decode($config->{$container_id . '_list_scenario_status'}); + $list_scenario = JSON::XS->new->utf8->decode($_->{list_scenario_status}); }; if ($@) { - $options{logger}->writeLogError("gorgone-newtest: cannot load container '" . $container_id . "' - cannot decode list scenario option"); + $options{logger}->writeLogError("gorgone-newtest: cannot load container '" . $_->{name} . "' - cannot decode list scenario option"); next; } - $containers->{$container_id} = { - nmc_endpoint => $config->{$container_id . '_nmc_endpoint'}, - nmc_timeout => (defined($config->{$container_id . '_nmc_timeout'}) && $config->{$container_id . '_nmc_timeout'} =~ /(\d+)/) ? + $containers->{$_->{name}} = { + nmc_endpoint => $_->{nmc_endpoint}, + nmc_timeout => (defined($_->{nmc_timeout}) && $_->{nmc_timeout} =~ /(\d+)/) ? $1 : 10, - nmc_username => $config->{$container_id . '_nmc_username'}, - nmc_password => $config->{$container_id . '_nmc_password'}, - poller_name => $config->{$container_id . '_poller_name'}, + nmc_username => $_->{nmc_username}, + nmc_password => $_->{nmc_password}, + poller_name => $_->{poller_name}, list_scenario_status => $list_scenario, resync_time => - (defined($config->{$container_id . '_resync_time'}) && $config->{$container_id . '_resync_time'} =~ /(\d+)/) ? - $1 : 300, + (defined($_->{resync_time}) && $_->{resync_time} =~ /(\d+)/) ? $1 : 300, host_template => - defined($config->{$container_id . '_host_template'}) && $config->{$container_id . '_host_template'} ne '' ? $config->{$container_id . '_host_template'} : 'generic-active-host-custom', + defined($_->{host_template}) && $_->{host_template} ne '' ? $_->{host_template} : 'generic-active-host-custom', host_prefix => - defined($config->{$container_id . '_host_prefix'}) && $config->{$container_id . '_host_prefix'} ne '' ? $config->{$container_id . '_host_prefix'} : 'Robot-%s', + defined($_->{host_prefix}) && $_->{host_prefix} ne '' ? $_->{host_prefix} : 'Robot-%s', service_template => - defined($config->{$container_id . '_service_template'}) && $config->{$container_id . '_service_template'} ne '' ? $config->{$container_id . '_service_template'} : 'generic-passive-service-custom', + defined($_->{service_template}) && $_->{service_template} ne '' ? $_->{service_template} : 'generic-passive-service-custom', service_prefix => - defined($config->{$container_id . '_service_prefix'}) && $config->{$container_id . '_service_prefix'} ne '' ? $config->{$container_id . '_service_prefix'} : 'Scenario-%s', + defined($_->{service_prefix}) && $_->{service_prefix} ne '' ? $_->{service_prefix} : 'Scenario-%s', }; } @@ -254,6 +253,7 @@ sub create_child { $0 = 'gorgone-newtest'; my $module = modules::gorgonenewtest::class->new( logger => $options{logger}, + module_id => $module_id, config_core => $config_core, config => $config, config_db_centreon => $config_db_centreon, diff --git a/gorgone/modules/gorgoneproxy/hooks.pm b/gorgone/modules/gorgoneproxy/hooks.pm index 1d8b9da93b2..71ab7bed53d 100644 --- a/gorgone/modules/gorgoneproxy/hooks.pm +++ b/gorgone/modules/gorgoneproxy/hooks.pm @@ -28,10 +28,15 @@ use modules::gorgoneproxy::class; my $config_core; my $config; +my $module_shortname = 'proxy'; my $module_id = 'gorgoneproxy'; my $events = [ - 'PROXYREADY', 'SETLOGS', 'PONG', 'REGISTERNODE', 'UNREGISTERNODE', # internal. Shouldn't be used by third party clients - 'ADDPOLLER', + { event => 'PROXYREADY' }, + { event => 'SETLOGS' }, # internal. Shouldn't be used by third party clients + { event => 'PONG' }, # internal. Shouldn't be used by third party clients + { event => 'REGISTERNODE' }, # internal. Shouldn't be used by third party clients + { event => 'UNREGISTERNODE' }, # internal. Shouldn't be used by third party clients + { event => 'ADDPOLLER', uri => '/poller', method => 'POST' }, ]; my $synctime_error = 0; @@ -57,7 +62,7 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; - return ($events, $module_id); + return ($events, $module_shortname , $module_id); } sub init { diff --git a/gorgone/modules/gorgonepull/hooks.pm b/gorgone/modules/gorgonepull/hooks.pm index 59889c5d670..6c1bdde5031 100644 --- a/gorgone/modules/gorgonepull/hooks.pm +++ b/gorgone/modules/gorgonepull/hooks.pm @@ -26,9 +26,9 @@ use centreon::gorgone::clientzmq; my $config_core; my $config; +my $module_shortname = 'pull'; my $module_id = 'gorgonepull'; -my $events = [ -]; +my $events = []; my $stop = 0; my $client; my $socket_to_internal; @@ -39,7 +39,7 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; - return ($events, $module_id); + return ($events, $module_shortname , $module_id); } sub init { From 7abd8d57e1178efef34e82146cbaef933da691bb Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Mon, 12 Aug 2019 11:41:58 +0200 Subject: [PATCH 023/948] better management of putlog --- gorgone/centreon/gorgone/common.pm | 6 ++++-- gorgone/centreon/gorgone/module.pm | 11 +---------- gorgone/centreon/misc/db.pm | 4 +++- gorgone/centreon/misc/objects/object.pm | 3 +++ gorgone/centreon/script/gorgonecore.pm | 10 ++++++---- gorgone/config/gorgoned.yml | 3 ++- gorgone/modules/gorgonenewtest/class.pm | 16 +++++++++------- gorgone/schema/gorgone_database.sql | 2 +- 8 files changed, 29 insertions(+), 26 deletions(-) diff --git a/gorgone/centreon/gorgone/common.pm b/gorgone/centreon/gorgone/common.pm index 0dedac64ee6..3227f02ad40 100644 --- a/gorgone/centreon/gorgone/common.pm +++ b/gorgone/centreon/gorgone/common.pm @@ -290,8 +290,10 @@ sub putlog { return (1, { message => 'request not well formatted' }); } - my $status = add_history(dbh => $options{gorgone}->{db_gorgone}, - etime => $data->{etime}, token => $data->{token}, data => json_encode(data => $data->{data}, logger => $options{logger}), code => $data->{code}); + my $status = add_history( + dbh => $options{gorgone}->{db_gorgone}, + etime => $data->{etime}, token => $data->{token}, data => json_encode(data => $data->{data}, logger => $options{logger}), code => $data->{code} + ); if ($status == -1) { return (1, { message => 'database issue' }); } diff --git a/gorgone/centreon/gorgone/module.pm b/gorgone/centreon/gorgone/module.pm index b18f75623d1..6be9d5cbf9e 100644 --- a/gorgone/centreon/gorgone/module.pm +++ b/gorgone/centreon/gorgone/module.pm @@ -37,17 +37,8 @@ sub send_log { return if (!defined($options{token})); - if (!defined($self->{socket_log})) { - $self->{socket_log} = centreon::gorgone::common::connect_com( - zmq_type => 'ZMQ_DEALER', name => $self->{module_id} . '-'. $self->{container_id}, - logger => $self->{logger}, linger => 5000, - type => $self->{config_core}->{internal_com_type}, - path => $self->{config_core}->{internal_com_path} - ); - } - centreon::gorgone::common::zmq_send_message( - socket => $self->{socket_log}, + socket => $self->{internal_socket}, action => 'PUTLOG', data => { code => $options{code}, etime => time(), token => $options{token}, data => $options{data} }, json_encode => 1 diff --git a/gorgone/centreon/misc/db.pm b/gorgone/centreon/misc/db.pm index 1bc10d44db9..a1847f080a3 100644 --- a/gorgone/centreon/misc/db.pm +++ b/gorgone/centreon/misc/db.pm @@ -203,7 +203,9 @@ sub connect() { } my ($package, $filename, $line) = caller; - $self->{logger}->writeLogError("MySQL error : cannot connect to database " . $self->{db} . ": " . $DBI::errstr . " (caller: $package:$filename:$line) (try: $count)"); + $self->{logger}->writeLogError("MySQL error : cannot connect to database '" . + (defined($self->{db}) ? $self->{db} : $self->{dsn}) . "': " . $DBI::errstr . " (caller: $package:$filename:$line) (try: $count)" + ); if ($self->{force} == 0 || ($self->{force} == 2 && $count == 1)) { $status = -1; last; diff --git a/gorgone/centreon/misc/objects/object.pm b/gorgone/centreon/misc/objects/object.pm index 4f1fd6f4bcd..3e6c62dec03 100644 --- a/gorgone/centreon/misc/objects/object.pm +++ b/gorgone/centreon/misc/objects/object.pm @@ -48,6 +48,9 @@ sub do { my $mode = defined($options{mode}) ? $options{mode} : 0; my ($status, $sth) = $self->{db_centreon}->query($options{request}); + if ($status == -1) { + return (-1, undef); + } if ($mode == 0) { return ($status, $sth); } elsif ($mode == 1) { diff --git a/gorgone/centreon/script/gorgonecore.pm b/gorgone/centreon/script/gorgonecore.pm index 3018e5b553f..65c368ac031 100644 --- a/gorgone/centreon/script/gorgonecore.pm +++ b/gorgone/centreon/script/gorgonecore.pm @@ -463,10 +463,12 @@ sub run { $gorgone->{logger}->writeLogDebug("gorgoned launched...."); $gorgone->{logger}->writeLogDebug("PID: $$"); - if (centreon::gorgone::common::add_history(dbh => $gorgone->{db_gorgone}, - code => 0, - data => { msg => 'gorgoned is starting...' }, - json_encode => 1) == -1) { + if (centreon::gorgone::common::add_history( + dbh => $gorgone->{db_gorgone}, + code => 0, + data => { msg => 'gorgoned is starting...' }, + json_encode => 1) == -1 + ) { $gorgone->{logger}->writeLogInfo("Cannot write in history. We quit!!"); exit(1); } diff --git a/gorgone/config/gorgoned.yml b/gorgone/config/gorgoned.yml index 179c93f4271..eabceb12e97 100644 --- a/gorgone/config/gorgoned.yml +++ b/gorgone/config/gorgoned.yml @@ -41,7 +41,7 @@ gorgonecore: modules: - name: gorgonehttpserver module: modules::gorgonehttpserver::hooks - enable: true + enable: false address: 0.0.0.0 port: 8080 ssl: true @@ -50,6 +50,7 @@ modules: auth: user: admin password: password + - name: gorgonecron module: modules::gorgonecron::hooks enable: true diff --git a/gorgone/modules/gorgonenewtest/class.pm b/gorgone/modules/gorgonenewtest/class.pm index a3917f4327b..a2bb807c7e8 100644 --- a/gorgone/modules/gorgonenewtest/class.pm +++ b/gorgone/modules/gorgonenewtest/class.pm @@ -37,7 +37,7 @@ use modules::gorgonenewtest::newtest::stubs::errors; use Date::Parse; my %handlers = (TERM => {}, HUP => {}); -my ($connector, $socket); +my ($connector); sub new { my ($class, %options) = @_; @@ -52,6 +52,7 @@ sub new { $connector->{config_db_centstorage} = $options{config_db_centstorage}; $connector->{config_db_centreon} = $options{config_db_centreon}; $connector->{stop} = 0; + $connector->{internal_socket} = undef; $connector->{resync_time} = $options{config_newtest}->{resync_time}; $connector->{last_resync_time} = time() - $connector->{resync_time}; @@ -563,6 +564,7 @@ sub action_newtestresync { my ($self, %options) = @_; $options{token} = $self->generate_token() if (!defined($options{token})); + $self->{logger}->writeLogDebug("gorgone-newtest: container $self->{container_id}: begin resync"); $self->send_log(code => centreon::gorgone::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action newtestresync proceed' }); $self->newtestresync_init(); @@ -594,7 +596,7 @@ sub action_newtestresync { sub event { while (1) { - my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $socket); + my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $connector->{internal_socket}); $connector->{logger}->writeLogDebug("gorgone-newtest: class: $message"); if ($message =~ /^\[(.*?)\]/) { @@ -606,7 +608,7 @@ sub event { } } - last unless (centreon::gorgone::common::zmq_still_read(socket => $socket)); + last unless (centreon::gorgone::common::zmq_still_read(socket => $connector->{internal_socket})); } } @@ -635,20 +637,20 @@ sub run { $self->{instance} = modules::gorgonenewtest::newtest::stubs::ManagementConsoleService->new(); # Connect internal - $socket = centreon::gorgone::common::connect_com( + $connector->{internal_socket} = centreon::gorgone::common::connect_com( zmq_type => 'ZMQ_DEALER', name => 'gorgonenewtest-' . $self->{container_id}, logger => $self->{logger}, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); centreon::gorgone::common::zmq_send_message( - socket => $socket, + socket => $connector->{internal_socket}, action => 'NEWTESTREADY', data => { container_id => $self->{container_id} }, json_encode => 1 ); $self->{poll} = [ { - socket => $socket, + socket => $connector->{internal_socket}, events => ZMQ_POLLIN, callback => \&event, } @@ -658,7 +660,7 @@ sub run { my $rev = zmq_poll($self->{poll}, 5000); if (defined($rev) && $rev == 0 && $self->{stop} == 1) { $self->{logger}->writeLogInfo("gorgone-newtest $$ has quit"); - zmq_close($socket); + zmq_close($connector->{internal_socket}); zmq_close($self->{socket_log}) if (defined($self->{socket_log})); exit(0); } diff --git a/gorgone/schema/gorgone_database.sql b/gorgone/schema/gorgone_database.sql index 368268cff9e..06511cc7b34 100644 --- a/gorgone/schema/gorgone_database.sql +++ b/gorgone/schema/gorgone_database.sql @@ -9,7 +9,7 @@ CREATE INDEX IF NOT EXISTS idx_gorgone_identity_identity ON gorgone_identity (id CREATE TABLE IF NOT EXISTS `gorgone_history` ( `id` INTEGER PRIMARY KEY, - `token` varchar(255) DEFAULT NULL, + `token` varchar(2048) DEFAULT NULL, `code` int(11) DEFAULT NULL, `etime` int(11) DEFAULT NULL, `ctime` int(11) DEFAULT NULL, From 19a22a27feab86252fc1321dd254c783dcbc729e Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Mon, 12 Aug 2019 11:52:00 +0200 Subject: [PATCH 024/948] WIP: scom connector --- gorgone/modules/gorgonenewtest/class.pm | 2 +- gorgone/modules/gorgonescom/class.pm | 28 ++++++++------- gorgone/modules/gorgonescom/hooks.pm | 47 ++++++++++++++----------- 3 files changed, 43 insertions(+), 34 deletions(-) diff --git a/gorgone/modules/gorgonenewtest/class.pm b/gorgone/modules/gorgonenewtest/class.pm index a2bb807c7e8..c4f1e1f16a4 100644 --- a/gorgone/modules/gorgonenewtest/class.pm +++ b/gorgone/modules/gorgonenewtest/class.pm @@ -43,6 +43,7 @@ sub new { my ($class, %options) = @_; $connector = {}; + $connector->{internal_socket} = undef; $connector->{module_id} = $options{module_id}; $connector->{logger} = $options{logger}; $connector->{container_id} = $options{container_id}; @@ -52,7 +53,6 @@ sub new { $connector->{config_db_centstorage} = $options{config_db_centstorage}; $connector->{config_db_centreon} = $options{config_db_centreon}; $connector->{stop} = 0; - $connector->{internal_socket} = undef; $connector->{resync_time} = $options{config_newtest}->{resync_time}; $connector->{last_resync_time} = time() - $connector->{resync_time}; diff --git a/gorgone/modules/gorgonescom/class.pm b/gorgone/modules/gorgonescom/class.pm index 4315fd02603..b9ac615f041 100644 --- a/gorgone/modules/gorgonescom/class.pm +++ b/gorgone/modules/gorgonescom/class.pm @@ -20,6 +20,8 @@ package modules::gorgonescom::class; +use base qw(centreon::gorgone::module); + use strict; use warnings; use centreon::gorgone::common; @@ -32,11 +34,14 @@ use JSON::XS; use Data::Dumper; my %handlers = (TERM => {}, HUP => {}); -my ($connector, $socket); +my ($connector); sub new { my ($class, %options) = @_; + $connector = {}; + $connector->{internal_socket} = undef; + $connector->{module_id} = $options{module_id}; $connector->{logger} = $options{logger}; $connector->{container_id} = $options{container_id}; $connector->{config} = $options{config}; @@ -45,6 +50,7 @@ sub new { $connector->{config_db_centstorage} = $options{config_db_centstorage}; $connector->{stop} = 0; + $connector->{api_version} = $options{config_scom}->{api_version}; $connector->{dsmhost} = $options{config_scom}->{dsmhost}; $connector->{dsmslot} = $options{config_scom}->{dsmslot}; $connector->{dsmmacro} = $options{config_scom}->{dsmmacro}; @@ -220,7 +226,8 @@ sub get_realtime_slots { sub action_scomresync { my ($self, %options) = @_; - my ($status, $sth, $resource_configs); + + $options{token} = $self->generate_token() if (!defined($options{token})); $self->{logger}->writeLogDebug("gorgone-scom: container $self->{container_id}: begin resync"); @@ -240,7 +247,7 @@ sub action_scomresync { sub event { while (1) { - my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $socket); + my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $connector->{internal_socket}); $connector->{logger}->writeLogDebug("gorgone-scom: class: $message"); if ($message =~ /^\[(.*?)\]/) { @@ -248,14 +255,11 @@ sub event { $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; my ($action, $token) = ($1, $2); my $data = JSON::XS->new->utf8->decode($3); - while ($method->($connector, token => $token, data => $data)) { - # We block until it's fixed!! - sleep(5); - } + $method->($connector, token => $token, data => $data); } } - last unless (centreon::gorgone::common::zmq_still_read(socket => $socket)); + last unless (centreon::gorgone::common::zmq_still_read(socket => $connector->{internal_socket})); } } @@ -275,20 +279,20 @@ sub run { $self->{http} = centreon::misc::http::http->new(logger => $self->{logger}); # Connect internal - $socket = centreon::gorgone::common::connect_com( + $connector->{internal_socket} = centreon::gorgone::common::connect_com( zmq_type => 'ZMQ_DEALER', name => 'gorgonescom-' . $self->{container_id}, logger => $self->{logger}, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); centreon::gorgone::common::zmq_send_message( - socket => $socket, + socket => $connector->{internal_socket}, action => 'SCOMREADY', data => { container_id => $self->{container_id} }, json_encode => 1 ); $self->{poll} = [ { - socket => $socket, + socket => $connector->{internal_socket}, events => ZMQ_POLLIN, callback => \&event, } @@ -298,7 +302,7 @@ sub run { my $rev = zmq_poll($self->{poll}, 5000); if (defined($rev) && $rev == 0 && $self->{stop} == 1) { $self->{logger}->writeLogInfo("gorgone-scom $$ has quit"); - zmq_close($socket); + zmq_close($connector->{internal_socket}); exit(0); } diff --git a/gorgone/modules/gorgonescom/hooks.pm b/gorgone/modules/gorgonescom/hooks.pm index b5264975651..befdd2b05bb 100644 --- a/gorgone/modules/gorgonescom/hooks.pm +++ b/gorgone/modules/gorgonescom/hooks.pm @@ -27,10 +27,11 @@ use modules::gorgonescom::class; my ($config_core, $config); my $config_db_centstorage; +my $module_shortname = 'scom'; my $module_id = 'gorgonescom'; my $events = [ - 'SCOMREADY', - 'SCOMRESYNC', + { event => 'SCOMREADY' }, + { event => 'SCOMRESYNC', uri => '/resync', method => 'GET' }, ]; my $last_containers = {}; # Last values from config ini @@ -47,7 +48,7 @@ sub register { $config_core = $options{config_core}; $config_db_centstorage = $options{config_db_centstorage}; $config_check_containers_time = defined($config->{check_containers_time}) ? $config->{check_containers_time} : 3600; - return ($events, $module_id); + return ($events, $module_shortname , $module_id); } sub init { @@ -169,31 +170,34 @@ sub get_containers { my $containers = {}; return $containers if (!defined($config->{containers})); - foreach my $container_id (split /,/, $config->{containers}) { - next if ($container_id eq '' || !defined($config->{$container_id . '_url'})); + foreach (@{$config->{containers}}) { + next if (!defined($_->{name}) || $_->{name} eq ''); - if (!defined($config->{$container_id . '_dsmhost'}) || $config->{$container_id . '_dsmhost'} eq '') { - $options{logger}->writeLogError("gorgone-scom: cannot load container '" . $container_id . "' - please set dsmhost option"); + if (!defined($_->{url}) || $_->{url} eq '') { + $options{logger}->writeLogError("gorgone-scom: cannot load container '" . $_->{name} . "' - please set url option"); next; } - if (!defined($config->{$container_id . '_dsmslot'}) || $config->{$container_id . '_dsmslot'} eq '') { - $options{logger}->writeLogError("gorgone-scom: cannot load container '" . $container_id . "' - please set dsmslot option"); + if (!defined($_->{dsmhost}) || $_->{dsmhost} eq '') { + $options{logger}->writeLogError("gorgone-scom: cannot load container '" . $_->{name} . "' - please set dsmhost option"); + next; + } + if (!defined($_->{dsmslot}) || $_->{dsmslot} eq '') { + $options{logger}->writeLogError("gorgone-scom: cannot load container '" . $_->{name} . "' - please set dsmslot option"); next; } - $containers->{$container_id} = { - url => $config->{$container_id . '_url'}, - username => $config->{$container_id . '_username'}, - password => $config->{$container_id . '_password'}, - username => $config->{$container_id . '_username'}, + $containers->{$_->{name}} = { + url => $_->{url}, + username => $_->{username}, + password => $_->{password}, resync_time => - (defined($config->{$container_id . '_resync_time'}) && $config->{$container_id . '_resync_time'} =~ /(\d+)/) ? - $1 : 300, - dsmhost => $config->{$container_id . '_dsmhost'}, - dsmslot => $config->{$container_id . '_dsmslot'}, - dsmmacro => defined($config->{$container_id . '_dsmmacro'}) ? $config->{$container_id . '_dsmmacro'} : 'ALARM_ID', - dsmalertmessage => defined($config->{$container_id . '_dsmalertmessage'}) ? $config->{$container_id . '_dsmalertmessage'} : '%{monitoringobjectdisplayname} %{name}', - dsmrecoverymessage => defined($config->{$container_id . '_dsmrecoverymessage'}) ? $config->{$container_id . '_dsmrecoverymessage'} : 'slot ok', + (defined($_->{resync_time}) && $_->{resync_time} =~ /(\d+)/) ? $1 : 300, + api_version => (defined($_->{api_version}) && $_->{api_version} =~ /(2012|2016|1801)/) ? $1 : '2016', + dsmhost => $_->{dsmhost}, + dsmslot => $_->{dsmslot}, + dsmmacro => defined($_->{dsmmacro}) ? $_->{dsmmacro} : 'ALARM_ID', + dsmalertmessage => defined($_->{dsmalertmessage}) ? $_->{dsmalertmessage} : '%{monitoringobjectdisplayname} %{name}', + dsmrecoverymessage => defined($_->{dsmrecoverymessage}) ? $_->{dsmrecoverymessage} : 'slot ok', }; } @@ -233,6 +237,7 @@ sub create_child { $0 = 'gorgone-scom'; my $module = modules::gorgonescom::class->new( logger => $options{logger}, + module_id => $module_id, config_core => $config_core, config => $config, config_db_centstorage => $config_db_centstorage, From bbffbdae38b9ff5d0cd074b5cd02e19f5291a03e Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Mon, 12 Aug 2019 15:37:00 +0200 Subject: [PATCH 025/948] WIP scom --- gorgone/TODO | 1 - gorgone/centreon/gorgone/module.pm | 61 ++++++++++ gorgone/config/gorgoned.yml | 32 ++--- gorgone/modules/gorgonescom/class.pm | 171 +++++++++++++++++++++------ gorgone/modules/gorgonescom/hooks.pm | 1 + 5 files changed, 218 insertions(+), 48 deletions(-) diff --git a/gorgone/TODO b/gorgone/TODO index c3b1387c93f..fe9b0e1b037 100644 --- a/gorgone/TODO +++ b/gorgone/TODO @@ -1,4 +1,3 @@ -- Use YAML configuration file instead of ini - Add a HTTP frontend module for clients (https and basic auth). Can try: https://metacpan.org/pod/HTTP::Server::Simple - Add legacy module: read centcore.cmd - Can chain agents and forward protocol diff --git a/gorgone/centreon/gorgone/module.pm b/gorgone/centreon/gorgone/module.pm index 6be9d5cbf9e..0d46d9f7f37 100644 --- a/gorgone/centreon/gorgone/module.pm +++ b/gorgone/centreon/gorgone/module.pm @@ -20,7 +20,12 @@ package centreon::gorgone::module; +use strict; +use warnings; + use centreon::gorgone::common; +use centreon::misc::misc; +use JSON::XS; use constant ACTION_BEGIN => 0; use constant ACTION_FINISH_KO => 1; @@ -45,6 +50,62 @@ sub send_log { ); } +sub json_encode { + my ($self, %options) = @_; + + my $encoded_arguments; + eval { + $encoded_arguments = JSON::XS->new->utf8->encode($options{argument}); + }; + if ($@) { + $self->{logger}->writeLogError("gorgone-$self->{module_id}: container $self->{container_id}: $options{method} - cannot encode json: $@"); + return 1; + } + + return (0, $encoded_arguments); +} + +sub json_decode { + my ($self, %options) = @_; + + my $decoded_arguments; + eval { + $decoded_arguments = JSON::XS->new->utf8->decode($options{argument}); + }; + if ($@) { + $self->{logger}->writeLogError("gorgone-$self->{module_id}: container $self->{container_id}: $options{method} - cannot decode json: $@"); + return 1; + } + + return (0, $decoded_arguments); +} +sub execute_shell_cmd { + my ($self, %options) = @_; + + my $timeout = defined($options{timeout}) && $options{timeout} =~ /(\d+)/ ? $1 : 30; + my ($lerror, $stdout, $exit_code) = centreon::misc::misc::backtick( + command => $options{cmd}, + logger => $self->{logger}, + timeout => $timeout, + wait_exit => 1, + ); + if ($lerror == -1 || ($exit_code >> 8) != 0) { + $self->{logger}->writeLogError("gorgone-$self->{module_id} command execution issue $options{cmd} : " . $stdout); + return -1; + } + + return 0; +} + +sub change_macros { + my ($self, %options) = @_; + + $options{template} =~ s/%\{(.*?)\}/$options{macros}->{$1}/g; + if (defined($options{escape})) { + $options{template} =~ s/([\Q$options{escape}\E])/\\$1/g; + } + return $options{template}; +} 1; diff --git a/gorgone/config/gorgoned.yml b/gorgone/config/gorgoned.yml index eabceb12e97..e324e5d2d56 100644 --- a/gorgone/config/gorgoned.yml +++ b/gorgone/config/gorgoned.yml @@ -1,11 +1,11 @@ name: gorgoned database: db_centreon: - dsn: "mysql:host=10.30.2.245;dbname=centreon" + dsn: "mysql:host=localhost;dbname=centreon" username: centreon password: centreon db_centstorage: - dsn: "mysql:host=10.30.2.245;dbname=centreon_storage" + dsn: "mysql:host=localhost;dbname=centreon_storage" username: centreon password: centreon gorgonecore: @@ -89,22 +89,26 @@ modules: - name: gorgonescom module: modules::gorgonescom::hooks - enable: false + enable: true # in seconds - do purge for container also check_containers_time: 3600 dsmclient_bin: /usr/share/centreon/bin/dsmclient.pl centcore_cmd: /var/lib/centreon/centcore.cmd containers: - # - name: toto - # url: "http://scomserver" - # username: toto - # password: pass - # resync_time: 300 - # dsmhost: centreon - # dsmslot: slot-% - # dsmmacro: ALARM_ID - # dsmalertmessage: "%{monitoringobjectdisplayname} %{name}" - # dsmrecoverymessage: slot ok + - name: toto + api_version: 2016 + url: "http://scomserver/api/" + username: toto + password: pass + resync_time: 300 + dsmhost: ADH3 + dsmslot: Scom-% + dsmmacro: ALARM_ID + dsmalertmessage: "%{monitoringobjectdisplayname} %{name}" + dsmrecoverymessage: slot ok + curlopts: + CURLOPT_SSL_VERIFYPEER: 0 + CURLOPT_PROXYAUTH: CURLAUTH_NTLM # - name: tutu # url: http://scomserver2/ # username: toto2 @@ -113,7 +117,7 @@ modules: - name: gorgonenewtest module: modules::gorgonenewtest::hooks - enable: true + enable: false # in seconds - do purge for container also check_containers_time: 3600 clapi_command: /usr/bin/centreon diff --git a/gorgone/modules/gorgonescom/class.pm b/gorgone/modules/gorgonescom/class.pm index b9ac615f041..939a0226e5c 100644 --- a/gorgone/modules/gorgonescom/class.pm +++ b/gorgone/modules/gorgonescom/class.pm @@ -103,21 +103,6 @@ sub class_handle_HUP { } } -sub json_encode { - my ($self, %options) = @_; - - my $encoded_arguments; - eval { - $encoded_arguments = JSON::XS->new->utf8->encode($options{argument}); - }; - if ($@) { - $self->{logger}->writeLogError("gorgone-scom: container $self->{container_id}: scom $options{method} - cannot encode json: $@"); - return 1; - } - - return (0, $encoded_arguments); -} - sub http_check_error { my ($self, %options) = @_; @@ -135,7 +120,7 @@ sub http_check_error { return 0; } -sub scom_authenticate { +sub scom_authenticate_1801 { my ($self, %options) = @_; my ($status) = $self->{http}->request( @@ -162,12 +147,13 @@ sub scom_authenticate { return 0; } -sub get_realtime_scom_alerts { + +sub get_realtime_scom_alerts_1801 { my ($self, %options) = @_; $self->{scom_realtime_alerts} = {}; if (!defined($connector->{scom_session_id})) { - return 1 if ($self->scom_authenticate() == 1); + return 1 if ($self->scom_authenticate_1801() == 1); } my $arguments = { @@ -179,7 +165,13 @@ sub get_realtime_scom_alerts { }; my ($status, $encoded_argument) = $self->json_encode(argument => $arguments); return 1 if ($status == 1); - + + my $curl_opts = []; + if (defined($self->{config_scom}->{curlopts})) { + foreach (keys %{$self->{config_scom}->{curlopts}}) { + push @{$curl_opts}, $_ . ' => ' . $self->{config_scom}->{curlopts}->{$_}; + } + } ($status, my $response) = $self->{http}->request( method => 'POST', hostname => '', full_url => $self->{config_scom}->{url} . '/OperationsManager/data/alert', @@ -189,23 +181,80 @@ sub get_realtime_scom_alerts { 'Content-Type: application/json; charset=utf-8', 'Cookie: SCOMSessionId=' . $self->{scom_session_id} . ';', ], - curl_opt => ['CURLOPT_SSL_VERIFYPEER => 0'], + curl_opt => $curl_opts, ); return 1 if ($self->http_check_error(status => $status, method => 'data/alert') == 1); - $self->{scom_realtime_alerts} = {}; print Data::Dumper::Dumper($response); return 0; } +sub get_realtime_scom_alerts_2016 { + my ($self, %options) = @_; + + my $curl_opts = []; + if (defined($self->{config_scom}->{curlopts})) { + foreach (keys %{$self->{config_scom}->{curlopts}}) { + push @{$curl_opts}, $_ . ' => ' . $self->{config_scom}->{curlopts}->{$_}; + } + } + $self->{scom_realtime_alerts} = {}; + my ($status, $response) = $self->{http}->request( + method => 'GET', hostname => '', + full_url => $self->{config_scom}->{url} . 'alerts', + ntlmv2 => 1, + username => $self->{config_scom}->{username}, + password => $self->{config_scom}->{password}, + header => [ + 'Accept-Type: application/json; charset=utf-8', + 'Content-Type: application/json; charset=utf-8', + ], + curl_opt => $curl_opts, + ); + + return 1 if ($self->http_check_error(status => $status, method => 'alerts') == 1); + + ($status, my $entries) = $self->json_decode(argument => $response); + return 1 if ($status == 1); + + # Resolution State: + # 0 => New + # 255 => Closed + # 254 => Resolved + # 250 => Scheduled + # 247 => Awaiting Evidence + # 248 => Assigned to Engineering + # 249 => Acknowledge + # Severity: + # 0 => Information + # 1 => Warning + # 2 => Critical + foreach (@$entries) { + next if (!defined($_->{alertGenerated}->{resolutionState})); + next if ($_->{alertGenerated}->{resolutionState} == 255); + next if ($_->{alertGenerated}->{severity} == 0); + + $self->{scom_realtime_alerts}->{$_->{alertGenerated}->{id}} = { + monitoringobjectdisplayname => $_->{alertGenerated}->{monitoringObjectDisplayName}, + resolutionstate => $_->{alertGenerated}->{resolutionState}, + name => $_->{alertGenerated}->{name}, + severity => $_->{alertGenerated}->{severity}, + timeraised => $_->{alertGenerated}->{timeRaised}, + description => $_->{alertGenerated}->{description}, + }; + } + + return 0; +} + sub get_realtime_slots { my ($self, %options) = @_; - $self->{realtime_slots} = []; + $self->{realtime_slots} = {}; my $request = " - SELECT hosts.instance_id, hosts.name, services.description, services.state, cv.name, cv.value + SELECT hosts.instance_id, hosts.host_id, hosts.name, services.description, services.state, cv.name, cv.value FROM hosts, services LEFT JOIN customvariables cv ON services.host_id = cv.host_id AND services.service_id = cv.service_id AND cv.name = '$self->{dsmmacro}' WHERE hosts.name = '$self->{dsmhost}' AND hosts.host_id = services.host_id AND services.enabled = '1' AND services.description LIKE '$self->{dsmslot}'; @@ -213,35 +262,91 @@ sub get_realtime_slots { my ($status, $datas) = $self->{class_object}->custom_execute(request => $request, mode => 2); return 1 if ($status == -1); foreach (@$datas) { - my ($name, $id) = split('##', $$_[5]); - push @{$self->{realtime_slots}}, { - host_name => $$_[1], - description => $$_[2], - state => $$_[3], - id => $id, + my ($name, $id) = split('##', $$_[6]); + next if (!defined($id)); + $self->{realtime_slots}->{$id} = { + host_name => $$_[2], + host_id => $$_[1], + description => $$_[3], + state => $$_[4], instance_id => $$_[0], }; } + + return 0; +} + +sub sync_alerts { + my ($self, %options) = @_; + + # Look if scom alers is in centreon-dsm services + my $pool_prefix = $self->{dsmslot}; + $pool_prefix =~ s/%//g; + foreach my $alert_id (keys %{$self->{scom_realtime_alerts}}) { + if (!defined($self->{realtime_slots}->{$alert_id}) || + $self->{realtime_slots}->{$alert_id}->{state} == 0) { + my $output = $self->change_macros( + template => $self->{dsmalertmessage}, + macros => $self->{scom_realtime_alerts}->{$alert_id}, + escape => '"', + ); + $self->execute_shell_cmd( + cmd => $self->{config}->{dsmclient_bin} . + ' --Host "' . $connector->{dsmhost} . '"' . + ' --pool-prefix "' . $pool_prefix . '"' . + ' --status ' . $self->{scom_realtime_alerts}->{$alert_id}->{severity} . + ' --id "' . $alert_id . '"' . + ' --output "' . $output . '"' + ); + } + } + + # Close centreon alerts not present in scom + foreach my $alert_id (keys %{$self->{realtime_slots}}) { + next if (defined($self->{scom_realtime_alerts}->{$alert_id}) && $self->{scom_realtime_alerts}->{$alert_id} != 255); + my $output = $self->change_macros( + template => $self->{dsmrecoverymessage}, + macros => {}, + escape => '"', + ); + $self->execute_shell_cmd( + cmd => $self->{config}->{dsmclient_bin} . + ' --Host "' . $connector->{dsmhost} . '"' . + ' --pool-prefix "' . $pool_prefix . '"' . + ' --status 0 ' . + ' --id "' . $alert_id . '"' . + ' --output "' . $output . '"' + ); + } } sub action_scomresync { my ($self, %options) = @_; $options{token} = $self->generate_token() if (!defined($options{token})); - + + $self->send_log(code => centreon::gorgone::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action scomresync proceed' }); $self->{logger}->writeLogDebug("gorgone-scom: container $self->{container_id}: begin resync"); - $self->get_realtime_slots(); - if (scalar(@{$self->{realtime_slots}}) <= 0) { + if ($self->get_realtime_slots()) { + $self->send_log(code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot find realtime slots' }); $self->{logger}->writeLogError("gorgone-scom: container $self->{container_id}: cannot find realtime slots"); return 1; } - if ($self->get_realtime_scom_alerts() == 0) { + my $api = 2016; + $api = 1801 if ($self->{api_version} == 1801); + my $func = $self->can('get_realtime_scom_alerts_' . $api); + if ($func->($self)) { + $self->send_log(code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot get scom realtime alerts' }); $self->{logger}->writeLogError("gorgone-scom: container $self->{container_id}: cannot get scom realtime alerts"); return 1; } + $self->sync_alerts(); + + $self->{logger}->writeLogDebug("gorgone-scom: container $self->{container_id}: finish resync"); + $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => { message => 'action scomresync finished' }); return 0; } diff --git a/gorgone/modules/gorgonescom/hooks.pm b/gorgone/modules/gorgonescom/hooks.pm index befdd2b05bb..403b5cb9847 100644 --- a/gorgone/modules/gorgonescom/hooks.pm +++ b/gorgone/modules/gorgonescom/hooks.pm @@ -198,6 +198,7 @@ sub get_containers { dsmmacro => defined($_->{dsmmacro}) ? $_->{dsmmacro} : 'ALARM_ID', dsmalertmessage => defined($_->{dsmalertmessage}) ? $_->{dsmalertmessage} : '%{monitoringobjectdisplayname} %{name}', dsmrecoverymessage => defined($_->{dsmrecoverymessage}) ? $_->{dsmrecoverymessage} : 'slot ok', + curlopts => $_->{curlopts}, }; } From 352ae3345a11249f2615dfa56fbbb04e1fc56d7f Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Mon, 12 Aug 2019 16:40:28 +0200 Subject: [PATCH 026/948] various fix --- gorgone/config/gorgoned.yml | 4 +++- gorgone/docs/guide.rst | 4 ++-- gorgone/modules/gorgonenewtest/hooks.pm | 2 +- gorgone/modules/gorgonescom/class.pm | 27 +++++++++++++++++++++++-- gorgone/modules/gorgonescom/hooks.pm | 3 ++- 5 files changed, 33 insertions(+), 7 deletions(-) diff --git a/gorgone/config/gorgoned.yml b/gorgone/config/gorgoned.yml index e324e5d2d56..e93e0dc74d1 100644 --- a/gorgone/config/gorgoned.yml +++ b/gorgone/config/gorgoned.yml @@ -100,6 +100,7 @@ modules: url: "http://scomserver/api/" username: toto password: pass + httpauth: basic resync_time: 300 dsmhost: ADH3 dsmslot: Scom-% @@ -108,7 +109,6 @@ modules: dsmrecoverymessage: slot ok curlopts: CURLOPT_SSL_VERIFYPEER: 0 - CURLOPT_PROXYAUTH: CURLAUTH_NTLM # - name: tutu # url: http://scomserver2/ # username: toto2 @@ -129,6 +129,7 @@ modules: - name: toto resync_time: 300 nmc_endpoint: "http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx" + username: user password: pass host_template: generic-active-host-custom host_prefix: Robot-%s @@ -139,6 +140,7 @@ modules: - name: tutu resync_time: 600 nmc_endpoint: "http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx" + username: user password: pass host_template: generic-active-host-custom host_prefix: Robot-%s diff --git a/gorgone/docs/guide.rst b/gorgone/docs/guide.rst index 4dc5d2f1192..1dc135bab1a 100644 --- a/gorgone/docs/guide.rst +++ b/gorgone/docs/guide.rst @@ -46,7 +46,7 @@ To execute the daemon: :: - # perl gorgoned --config-extra=config/gorgoned.ini --severity=debug + # perl gorgoned --config-extra=config/gorgoned.yml --severity=debug **************** gorgone protocol @@ -489,7 +489,7 @@ Database scheme CREATE TABLE IF NOT EXISTS `gorgone_history` ( `id` INTEGER PRIMARY KEY, - `token` varchar(255) DEFAULT NULL, + `token` varchar(2048) DEFAULT NULL, `code` int(11) DEFAULT NULL, `etime` int(11) DEFAULT NULL, `ctime` int(11) DEFAULT NULL, diff --git a/gorgone/modules/gorgonenewtest/hooks.pm b/gorgone/modules/gorgonenewtest/hooks.pm index 9c22e497e6a..d5ae440ed62 100644 --- a/gorgone/modules/gorgonenewtest/hooks.pm +++ b/gorgone/modules/gorgonenewtest/hooks.pm @@ -250,7 +250,7 @@ sub create_child { $options{logger}->writeLogInfo("Create gorgone-newtest for container '" . $options{container_id} . "'"); my $child_pid = fork(); if ($child_pid == 0) { - $0 = 'gorgone-newtest'; + $0 = 'gorgone-newtest ' . $options{container_id}; my $module = modules::gorgonenewtest::class->new( logger => $options{logger}, module_id => $module_id, diff --git a/gorgone/modules/gorgonescom/class.pm b/gorgone/modules/gorgonescom/class.pm index 939a0226e5c..ad949501c4e 100644 --- a/gorgone/modules/gorgonescom/class.pm +++ b/gorgone/modules/gorgonescom/class.pm @@ -120,6 +120,18 @@ sub http_check_error { return 0; } +sub get_httpauth { + my ($self, %options) = @_; + + my $httpauth = {}; + if ($self->{config_scom}->{httpauth} eq 'basic') { + $httpauth->{basic} = 1; + } elsif ($self->{config_scom}->{httpauth} eq 'ntlmv2') { + $httpauth->{ntlmv2} = 1; + } + return $httpauth; +} + sub scom_authenticate_1801 { my ($self, %options) = @_; @@ -147,7 +159,6 @@ sub scom_authenticate_1801 { return 0; } - sub get_realtime_scom_alerts_1801 { my ($self, %options) = @_; @@ -200,11 +211,15 @@ sub get_realtime_scom_alerts_2016 { push @{$curl_opts}, $_ . ' => ' . $self->{config_scom}->{curlopts}->{$_}; } } + my $httpauth = $self->get_httpauth(); + + $self->{scom_realtime_alerts} = {}; my ($status, $response) = $self->{http}->request( method => 'GET', hostname => '', full_url => $self->{config_scom}->{url} . 'alerts', - ntlmv2 => 1, + credentials => 1, + %$httpauth, username => $self->{config_scom}->{username}, password => $self->{config_scom}->{password}, header => [ @@ -303,6 +318,7 @@ sub sync_alerts { # Close centreon alerts not present in scom foreach my $alert_id (keys %{$self->{realtime_slots}}) { + next if ($self->{realtime_slots}->{$alert_id}->{state} == 0); next if (defined($self->{scom_realtime_alerts}->{$alert_id}) && $self->{scom_realtime_alerts}->{$alert_id} != 255); my $output = $self->change_macros( template => $self->{dsmrecoverymessage}, @@ -320,6 +336,12 @@ sub sync_alerts { } } +sub sync_acks { + my ($self, %options) = @_; + + +} + sub action_scomresync { my ($self, %options) = @_; @@ -344,6 +366,7 @@ sub action_scomresync { } $self->sync_alerts(); + $self->sync_acks(); $self->{logger}->writeLogDebug("gorgone-scom: container $self->{container_id}: finish resync"); $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => { message => 'action scomresync finished' }); diff --git a/gorgone/modules/gorgonescom/hooks.pm b/gorgone/modules/gorgonescom/hooks.pm index 403b5cb9847..74e839c6073 100644 --- a/gorgone/modules/gorgonescom/hooks.pm +++ b/gorgone/modules/gorgonescom/hooks.pm @@ -190,6 +190,7 @@ sub get_containers { url => $_->{url}, username => $_->{username}, password => $_->{password}, + httpauth => defined($_->{httpauth}) && $_->{httpauth} =~ /(basic|ntlmv2)/ ? $_->{httpauth} : 'basic', resync_time => (defined($_->{resync_time}) && $_->{resync_time} =~ /(\d+)/) ? $1 : 300, api_version => (defined($_->{api_version}) && $_->{api_version} =~ /(2012|2016|1801)/) ? $1 : '2016', @@ -235,7 +236,7 @@ sub create_child { $options{logger}->writeLogInfo("Create gorgone-scom for container '" . $options{container_id} . "'"); my $child_pid = fork(); if ($child_pid == 0) { - $0 = 'gorgone-scom'; + $0 = 'gorgone-scom ' . $options{container_id}; my $module = modules::gorgonescom::class->new( logger => $options{logger}, module_id => $module_id, From d8f41c8a4557cfa52ae500dc7928886a2232821f Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Mon, 12 Aug 2019 16:50:17 +0200 Subject: [PATCH 027/948] fix token for modules send_log --- gorgone/centreon/gorgone/module.pm | 3 ++- gorgone/centreon/script/gorgonecore.pm | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/gorgone/centreon/gorgone/module.pm b/gorgone/centreon/gorgone/module.pm index 0d46d9f7f37..6147da84983 100644 --- a/gorgone/centreon/gorgone/module.pm +++ b/gorgone/centreon/gorgone/module.pm @@ -44,7 +44,8 @@ sub send_log { centreon::gorgone::common::zmq_send_message( socket => $self->{internal_socket}, - action => 'PUTLOG', + action => 'PUTLOG', + token => $options{token}, data => { code => $options{code}, etime => time(), token => $options{token}, data => $options{data} }, json_encode => 1 ); diff --git a/gorgone/centreon/script/gorgonecore.pm b/gorgone/centreon/script/gorgonecore.pm index 65c368ac031..9c6be779b34 100644 --- a/gorgone/centreon/script/gorgonecore.pm +++ b/gorgone/centreon/script/gorgonecore.pm @@ -235,6 +235,7 @@ sub message_run { ); return (undef, 1, { message => "action '$action' is not known" }); } + if (!defined($token) || $token eq '') { $token = centreon::gorgone::common::generate_token(); } From e27453370bf42945ad62305fd87dde0b9df3ab81 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Mon, 12 Aug 2019 16:56:09 +0200 Subject: [PATCH 028/948] change random generator library --- gorgone/centreon/gorgone/common.pm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gorgone/centreon/gorgone/common.pm b/gorgone/centreon/gorgone/common.pm index 3227f02ad40..9d9959f683b 100644 --- a/gorgone/centreon/gorgone/common.pm +++ b/gorgone/centreon/gorgone/common.pm @@ -27,7 +27,7 @@ use ZMQ::Constants qw(:all); use JSON; use File::Basename; use Crypt::OpenSSL::RSA; -use Crypt::OpenSSL::Random; +use Crypt::PRNG; use Crypt::CBC; use Data::Dumper; use YAML 'LoadFile';; @@ -174,8 +174,8 @@ sub uncrypt_message { sub generate_token { my (%options) = @_; - my $token = Crypt::OpenSSL::Random::random_bytes(256); - return unpack('H*', $token); + my $token = Crypt::PRNG::random_bytes_hex(256); + return $token; } sub generate_symkey { From bbf9766260661d7ad93a0561d475d3d96f7a8e8d Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 13 Aug 2019 08:48:05 +0200 Subject: [PATCH 029/948] change openssl random --- gorgone/centreon/gorgone/common.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/centreon/gorgone/common.pm b/gorgone/centreon/gorgone/common.pm index 9d9959f683b..32ad18a79a9 100644 --- a/gorgone/centreon/gorgone/common.pm +++ b/gorgone/centreon/gorgone/common.pm @@ -181,7 +181,7 @@ sub generate_token { sub generate_symkey { my (%options) = @_; - my $random_key = Crypt::OpenSSL::Random::random_bytes($options{keysize}); + my $random_key = Crypt::PRNG::random_bytes($options{keysize}); return (0, $random_key); } From 27c76f1bd1980457d7d2fbef5258ed3da52a74c5 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 13 Aug 2019 11:27:16 +0200 Subject: [PATCH 030/948] update doc --- gorgone/centreon/gorgone/clientzmq.pm | 22 +++-- gorgone/centreon/gorgone/common.pm | 116 +++++++++++++++++-------- gorgone/centreon/script/gorgonecore.pm | 23 +++-- gorgone/config/gorgoned-poller.yml | 7 +- gorgone/config/gorgoned.yml | 5 ++ gorgone/docs/guide.rst | 17 ++-- gorgone/modules/gorgoneproxy/class.pm | 14 ++- gorgone/modules/gorgonepull/hooks.pm | 4 +- gorgone/test-client.pl | 8 +- 9 files changed, 150 insertions(+), 66 deletions(-) diff --git a/gorgone/centreon/gorgone/clientzmq.pm b/gorgone/centreon/gorgone/clientzmq.pm index 5744645e5b1..cafe5456d64 100644 --- a/gorgone/centreon/gorgone/clientzmq.pm +++ b/gorgone/centreon/gorgone/clientzmq.pm @@ -39,7 +39,9 @@ sub new { $connector->{cipher} = $options{cipher}; $connector->{vector} = $options{vector}; $connector->{symkey} = undef; - $connector->{pubkey} = centreon::gorgone::common::loadpubkey(pubkey => $options{pubkey}); + $connector->{server_pubkey} = centreon::gorgone::common::loadpubkey(pubkey => $options{server_pubkey}); + $connector->{client_pubkey} = centreon::gorgone::common::loadpubkey(pubkey => $options{client_pubkey}); + $connector->{client_privkey} = centreon::gorgone::common::loadprivkey(privkey => $options{client_privkey}); $connector->{target_type} = $options{target_type}; $connector->{target_path} = $options{target_path}; $connector->{ping} = defined($options{ping}) ? $options{ping} : -1; @@ -47,7 +49,11 @@ sub new { $connector->{ping_progress} = 0; $connector->{ping_time} = time(); $connector->{ping_timeout_time} = time(); - + + if (defined($connector->{logger}) && $connector->{logger}->is_debug()) { + $connector->{logger}->writeLogDebug($connector->{client_pubkey}->export_key_jwk_thumbprint('SHA256')); + } + $connectors->{$options{identity}} = $connector; bless $connector, $class; return $connector; @@ -134,8 +140,10 @@ sub event { # in progress if ($connectors->{$options{identity}}->{handshake} == 0 || $connectors->{$options{identity}}->{handshake} == 1) { - my ($status, $symkey, $hostname) = centreon::gorgone::common::client_get_secret(pubkey => $connectors->{$options{identity}}->{pubkey}, - message => $message); + my ($status, $symkey, $hostname) = centreon::gorgone::common::client_get_secret( + privkey => $connectors->{$options{identity}}->{client_privkey}, + message => $message + ); if ($status == -1) { $connectors->{$options{identity}}->{handshake} = 0; return ; @@ -171,10 +179,10 @@ sub send_message { my ($self, %options) = @_; if ($self->{handshake} == 0) { - my $message = '[HELO] [' . $self->{identity} . ']'; my ($status, $ciphertext) = centreon::gorgone::common::client_helo_encrypt( - pubkey => $self->{pubkey}, - message => $message + identity => $self->{identity}, + server_pubkey => $self->{server_pubkey}, + client_pubkey => $self->{client_pubkey}, ); if ($status == -1) { return (-1, 'crypt handshake issue'); diff --git a/gorgone/centreon/gorgone/common.pm b/gorgone/centreon/gorgone/common.pm index 32ad18a79a9..baed4366ddd 100644 --- a/gorgone/centreon/gorgone/common.pm +++ b/gorgone/centreon/gorgone/common.pm @@ -26,14 +26,13 @@ use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); use JSON; use File::Basename; -use Crypt::OpenSSL::RSA; +use Crypt::PK::RSA; use Crypt::PRNG; use Crypt::CBC; use Data::Dumper; -use YAML 'LoadFile';; +use YAML 'LoadFile'; my %zmq_type = ('ZMQ_ROUTER' => ZMQ_ROUTER, 'ZMQ_DEALER' => ZMQ_DEALER); -my $privkey; sub read_config { my (%options) = @_; @@ -58,7 +57,7 @@ sub read_config { sub loadpubkey { my (%options) = @_; my $string_key = ''; - + if (!open FILE, "<" . $options{pubkey}) { $options{logger}->writeLogError("Cannot read file '$options{pubkey}': $!"); exit(1); @@ -70,11 +69,14 @@ sub loadpubkey { my $pubkey; eval { - $pubkey = Crypt::OpenSSL::RSA->new_public_key($string_key); - $pubkey->use_pkcs1_padding(); + $pubkey = Crypt::PK::RSA->new(\$string_key); }; if ($@) { - $options{logger}->writeLogError("Cannot load privkey '$options{pubkey}': $@"); + $options{logger}->writeLogError("Cannot load pubkey '$options{pubkey}': $@"); + exit(1); + } + if ($pubkey->is_private()) { + $options{logger}->writeLogError("'$options{pubkey}' is not a publickey"); exit(1); } @@ -94,14 +96,20 @@ sub loadprivkey { } close FILE; + my $privkey; eval { - $privkey = Crypt::OpenSSL::RSA->new_private_key($string_key); - $privkey->use_pkcs1_padding(); + $privkey = Crypt::PK::RSA->new(\$string_key); }; if ($@) { $options{logger}->writeLogError("Cannot load privkey '$options{privkey}': $@"); exit(1); } + if (!$privkey->is_private()) { + $options{logger}->writeLogError("'$options{privkey}' is not a privkey"); + exit(1); + } + + return $privkey; } sub zmq_core_key_response { @@ -112,7 +120,7 @@ sub zmq_core_key_response { } my $crypttext; eval { - $crypttext = $privkey->private_encrypt("[KEY] [$options{hostname}] [" . $options{symkey} . "]"); + $crypttext = $options{client_pubkey}->encrypt("[KEY] [$options{hostname}] [" . $options{symkey} . "]", 'v1.5'); }; if ($@) { $options{logger}->writeLogError("Encoding issue: " . $@); @@ -136,13 +144,14 @@ sub zmq_core_response { $msg = '[' . $response_type . '] [' . (defined($options{token}) ? $options{token} : '') . '] ' . ($response_type eq 'PONG' ? '[] ' : '') . $data; if (defined($options{cipher})) { - my $cipher = Crypt::CBC->new(-key => $options{symkey}, - -keysize => length($options{symkey}), - -cipher => $options{cipher}, - -iv => $options{vector}, - -header => 'none', - -literal_key => 1 - ); + my $cipher = Crypt::CBC->new( + -key => $options{symkey}, + -keysize => length($options{symkey}), + -cipher => $options{cipher}, + -iv => $options{vector}, + -header => 'none', + -literal_key => 1 + ); $msg = $cipher->encrypt($msg); } zmq_sendmsg($options{socket}, $msg, ZMQ_NOBLOCK); @@ -152,13 +161,14 @@ sub uncrypt_message { my (%options) = @_; my $plaintext; - my $cipher = Crypt::CBC->new(-key => $options{symkey}, - -keysize => length($options{symkey}), - -cipher => $options{cipher}, - -iv => $options{vector}, - -header => 'none', - -literal_key => 1 - ); + my $cipher = Crypt::CBC->new( + -key => $options{symkey}, + -keysize => length($options{symkey}), + -cipher => $options{cipher}, + -iv => $options{vector}, + -header => 'none', + -literal_key => 1 + ); eval { $plaintext = $cipher->decrypt($options{message}); }; @@ -188,11 +198,12 @@ sub generate_symkey { sub client_get_secret { my (%options) = @_; my $plaintext; - + eval { - $plaintext = $options{pubkey}->public_decrypt($options{message}); + $plaintext = $options{privkey}->decrypt($options{message}, 'v1.5'); }; if ($@) { + print "====$@====\n"; return (-1, "Decoding issue: $@"); } @@ -210,36 +221,69 @@ sub client_get_secret { sub client_helo_encrypt { my (%options) = @_; my $ciphertext; - + + my $client_pubkey = $options{client_pubkey}->export_key_pem('public'); + $client_pubkey =~ s/\n/\\n/g; eval { - $ciphertext = $options{pubkey}->encrypt($options{message}); + $ciphertext = $options{server_pubkey}->encrypt('HELO', 'v1.5'); }; if ($@) { - return (-1, "Decoding issue: $@"); + return (-1, "Encoding issue: $@"); } - return (0, $ciphertext); + return (0, '[' . $options{identity} . '] [' . $client_pubkey . '] [' . $ciphertext . ']'); } sub is_client_can_connect { my (%options) = @_; my $plaintext; - + + if ($options{message} !~ /\[(.+)\]\s+\[(.+)\]\s+\[(.+)\]$/ms) { + $options{logger}->writeLogError("Decoding issue. Protocol not good"); + return -1; + } + + my ($client, $client_pubkey_str, $cipher_text) = ($1, $2, $3); eval { - $plaintext = $privkey->decrypt($options{message}); + $plaintext = $options{privkey}->decrypt($cipher_text, 'v1.5'); }; if ($@) { $options{logger}->writeLogError("Decoding issue: " . $@); return -1; } + if ($plaintext ne 'HELO') { + $options{logger}->writeLogError("Encrypted issue for HELO"); + return -1; + } - if ($plaintext !~ /\[HELO\]\s+\[(.+)\]/) { - $options{logger}->writeLogError("Decoding issue. Protocol not good"); + my ($client_pubkey); + $client_pubkey_str =~ s/\\n/\n/g; + eval { + $client_pubkey = Crypt::PK::RSA->new(\$client_pubkey_str); + }; + if ($@) { + $options{logger}->writeLogError("Cannot load client pubkey '$client_pubkey': $@"); return -1; } - $options{logger}->writeLogInfo("Connection from $1"); - return 0; + my $is_authorized = 0; + my $thumbprint = $client_pubkey->export_key_jwk_thumbprint('SHA256'); + if (defined($options{authorized_clients})) { + foreach (@{$options{authorized_clients}}) { + if ($_->{key} eq $thumbprint) { + $is_authorized = 1; + last; + } + } + } + + if ($is_authorized == 0) { + $options{logger}->writeLogError("client pubkey is not authorized. thumprint is '$thumbprint"); + return -1; + } + + $options{logger}->writeLogInfo("Connection from $client"); + return (0, $client_pubkey); } sub is_handshake_done { diff --git a/gorgone/centreon/script/gorgonecore.pm b/gorgone/centreon/script/gorgonecore.pm index 9c6be779b34..3a20cc6bbd0 100644 --- a/gorgone/centreon/script/gorgonecore.pm +++ b/gorgone/centreon/script/gorgonecore.pm @@ -59,6 +59,7 @@ sub new { $self->{modules_id} = {}; $self->{sessions_timer} = time(); $self->{kill_timer} = undef; + $self->{server_privkey} = undef; return $self; } @@ -80,7 +81,7 @@ sub init { logger => $self->{logger} ); if (defined($config->{gorgonecore}->{external_com_type}) && $config->{gorgonecore}->{external_com_type} ne '') { - centreon::gorgone::common::loadprivkey(logger => $self->{logger}, privkey => $config->{gorgonecore}->{privkey}); + $self->{server_privkey} = centreon::gorgone::common::loadprivkey(logger => $self->{logger}, privkey => $config->{gorgonecore}->{privkey}); } # Database connections: @@ -342,12 +343,20 @@ sub handshake { return undef; } elsif ($status == 0) { # We try to uncrypt - if (centreon::gorgone::common::is_client_can_connect(message => $message, - logger => $self->{logger}) == -1) { + ($status, my $client_pubkey) = centreon::gorgone::common::is_client_can_connect( + privkey => $self->{server_privkey}, + message => $message, + logger => $self->{logger}, + authorized_clients => $config->{gorgonecore}->{authorized_clients} + ); + if ($status == -1) { centreon::gorgone::common::zmq_core_response( - socket => $self->{external_socket}, identity => $identity, - code => 1, data => { message => 'handshake issue' } + socket => $self->{external_socket}, + identity => $identity, + code => 1, + data => { message => 'handshake issue' } ); + return undef; } my ($status, $symkey) = centreon::gorgone::common::generate_symkey( logger => $self->{logger}, @@ -366,9 +375,9 @@ sub handshake { code => 1, data => { message => 'handshake issue' } ); } - + if (centreon::gorgone::common::zmq_core_key_response(logger => $self->{logger}, socket => $self->{external_socket}, identity => $identity, - hostname => $self->{hostname}, symkey => $symkey) == -1) { + client_pubkey => $client_pubkey, hostname => $self->{hostname}, symkey => $symkey) == -1) { centreon::gorgone::common::zmq_core_response( socket => $self->{external_socket}, identity => $identity, code => 1, data => { message => 'handshake issue' } diff --git a/gorgone/config/gorgoned-poller.yml b/gorgone/config/gorgoned-poller.yml index 6724ef7aa67..bd983a386be 100644 --- a/gorgone/config/gorgoned-poller.yml +++ b/gorgone/config/gorgoned-poller.yml @@ -32,13 +32,18 @@ modules: target_type: tcp target_path: 127.0.0.1:5555 linger: 5000 + # crypt options - pubkey: keys/central/pubkey.crt + server_pubkey: keys/central/pubkey.crt + client_pubkey => 'keys/poller/pubkey.crt', + client_privkey => 'keys/poller/privkey.pem', + cipher: "Cipher::AES" # in bytes keysize: 32 # 16 bytes for AES vector: 0123456789012345 + # ping ping: 60 ping_timeout: 30 diff --git a/gorgone/config/gorgoned.yml b/gorgone/config/gorgoned.yml index e93e0dc74d1..fffaf924676 100644 --- a/gorgone/config/gorgoned.yml +++ b/gorgone/config/gorgoned.yml @@ -26,12 +26,17 @@ gorgonecore: # If not set. Try from 'hostname' in database # Set 'none', if you don't need it (for poller in push mode) id: none + privkey: keys/central/privkey.pem cipher: "Cipher::AES" # in bytes keysize: 32 # 16 bytes for AES vector: 0123456789012345 + # JWK format export thumbprint SHA256 + authorized_clients: + - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 + # in seconds sessions_time: 86400 # in seconds diff --git a/gorgone/docs/guide.rst b/gorgone/docs/guide.rst index 1dc135bab1a..3b79394e2a6 100644 --- a/gorgone/docs/guide.rst +++ b/gorgone/docs/guide.rst @@ -24,16 +24,17 @@ Daemon uses following Perl modules: * DBD::SQLite: repository 'centos base' * DBD::mysql: repository 'centos base' * UUID: repository 'centreon-stable' -* Crypt::OpenSSL::RSA: repository 'centos base' * Crypt::CBC: repository 'centos base' * Schedule::Cron: in EPEL -* Crypt::Cipher::AES: in attachment +* Crypt::Cipher::AES: in attachment (module CryptX) +* Crypt::PK::RSA: in attachment (module CryptX) +* Crypt::PRNG: in attachment (module CryptX) Execute following commands: :: - # yum install 'perl(Schedule::Cron)' 'perl(Crypt::CBC)' 'perl(ZMQ::LibZMQ4)' 'perl(JSON)' 'perl(YAML)' 'perl(DBD::SQLite)' 'perl(DBD::mysql)' 'perl(UUID)' 'perl(Crypt::OpenSSL::RSA)' + # yum install 'perl(Schedule::Cron)' 'perl(Crypt::CBC)' 'perl(ZMQ::LibZMQ4)' 'perl(JSON)' 'perl(YAML)' 'perl(DBD::SQLite)' 'perl(DBD::mysql)' 'perl(UUID)' # yum install perl-CryptX-0.064-1.el7.x86_64 Create sqlite database: @@ -66,27 +67,27 @@ Handshake scenario Third-party clients connected had to use the zeromq library and the following process: * client : need to create an uniq identity (will be used in "zmq_setsockopt" and "ZMQ_IDENTITY") -* client -> server : send the following message crypted with the public key of the server: +* client -> server : send the following message with HELO crypted with the public key of the server and provides client pubkey: :: - [HELO] [HOSTNAME] + [HOSTNAME] [CLIENTPUBKEY] [HELO] * server: uncrypt the client message: * If uncrypted message result is not "HELO", server refused the connection and send it back: :: - + [ACK] [] { "code" => 1, "data" => { "message" => "handshake issue" } } - * If uncrypted message result is "HELO", server accepts the connection. It creates symmetric key and send the following message crypted with its private key: + * If uncrypted message result is "HELO", server accepts the connection if the clientpubkey is authorized. It creates symmetric key and send the following message crypted with client pubkey: :: [KEY] [HOSTNAME] [symmetric key] -* client: uncrypt the server message with the public key of the server. +* client: uncrypt the server message with its private key. * client and server uses the symmetric key to dialog The server keeps sessions for 24 hours since the last message of the client. Otherwise, it purges the identity/symmetric-key of the client. diff --git a/gorgone/modules/gorgoneproxy/class.pm b/gorgone/modules/gorgoneproxy/class.pm index b163df2d0b8..71ee6c63684 100644 --- a/gorgone/modules/gorgoneproxy/class.pm +++ b/gorgone/modules/gorgoneproxy/class.pm @@ -82,9 +82,13 @@ sub get_client_information { my ($self, %options) = @_; # TODO DATABASE or file maybe. hardcoded right now - my $result = { type => 1, target_type => 'tcp', target_path => 'localhost:5556', - pubkey => 'keys/poller/pubkey.crt', cipher => 'Cipher::AES', - keysize => '32', vector => '0123456789012345', class => undef, delete => 0 }; + my $result = { + type => 1, target_type => 'tcp', target_path => 'localhost:5556', + server_pubkey => 'keys/poller/pubkey.crt', + client_pubkey => 'keys/central/pubkey.crt', + client_privkey => 'keys/central/privkey.pem', + cipher => 'Cipher::AES', keysize => '32', vector => '0123456789012345', class => undef, delete => 0 + }; return $result; } @@ -135,7 +139,9 @@ sub connect { identity => 'proxy-' . $self->{core_id} . '-' . $options{id}, cipher => $options{entry}->{cipher}, vector => $options{entry}->{vector}, - pubkey => $options{entry}->{pubkey}, + server_pubkey => $options{entry}->{server_pubkey}, + client_pubkey => $options{entry}->{client_pubkey}, + client_privkey => $options{entry}->{client_privkey}, target_type => $options{entry}->{target_type}, target_path => $options{entry}->{target_path}, logger => $self->{logger} diff --git a/gorgone/modules/gorgonepull/hooks.pm b/gorgone/modules/gorgonepull/hooks.pm index 6c1bdde5031..9ae54a2b5d5 100644 --- a/gorgone/modules/gorgonepull/hooks.pm +++ b/gorgone/modules/gorgonepull/hooks.pm @@ -58,7 +58,9 @@ sub init { identity => $config_core->{id}, cipher => $config->{cipher}, vector => $config->{vector}, - pubkey => $config->{pubkey}, + server_pubkey => $config->{server_pubkey}, + client_pubkey => $config->{client_pubkey}, + client_privkey => $config->{server_privkey}, target_type => $config->{target_type}, target_path => $config->{target_path}, logger => $options{logger}, diff --git a/gorgone/test-client.pl b/gorgone/test-client.pl index 51a72012062..137e21381be 100644 --- a/gorgone/test-client.pl +++ b/gorgone/test-client.pl @@ -111,7 +111,9 @@ sub read_response { identity => 'toto', cipher => 'Cipher::AES', vector => '0123456789012345', - pubkey => 'keys/central/pubkey.crt', + server_pubkey => 'keys/central/pubkey.crt', + client_pubkey => 'keys/poller/pubkey.crt', + client_privkey => 'keys/poller/privkey.pem', target_type => 'tcp', target_path => '127.0.0.1:5555', ping => 60, @@ -121,7 +123,9 @@ sub read_response { identity => 'tata', cipher => 'Cipher::AES', vector => '0123456789012345', - pubkey => 'keys/central/pubkey.crt', + server_pubkey => 'keys/central/pubkey.crt', + client_pubkey => 'keys/poller/pubkey.crt', + client_privkey => 'keys/poller/privkey.pem', target_type => 'tcp', target_path => '127.0.0.1:5555' ); From 10260b380d636a8fa81c4abca96ffd4ab66b3354 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 13 Aug 2019 14:19:36 +0200 Subject: [PATCH 031/948] add ack sync for scom --- gorgone/modules/gorgonescom/class.pm | 94 +++++++++++++++++++++++++--- 1 file changed, 87 insertions(+), 7 deletions(-) diff --git a/gorgone/modules/gorgonescom/class.pm b/gorgone/modules/gorgonescom/class.pm index ad949501c4e..72dfcc677d2 100644 --- a/gorgone/modules/gorgonescom/class.pm +++ b/gorgone/modules/gorgonescom/class.pm @@ -132,6 +132,31 @@ sub get_httpauth { return $httpauth; } +sub get_method { + my ($self, %options) = @_; + + my $api = 2016; + $api = 1801 if ($self->{api_version} == 1801); + return $self->can($options{method} . '_' . $api); +} + +sub submit_external_cmd { + my ($self, %options) = @_; + + my ($lerror, $stdout, $exit_code) = centreon::misc::misc::backtick( + command => '/bin/echo "' . $options{cmd} . '" >> ' . $self->{centcore_cmd}, + logger => $self->{logger}, + timeout => 5, + wait_exit => 1 + ); + if ($lerror == -1 || ($exit_code >> 8) != 0) { + $self->{logger}->writeLogError("gorgone-scom cmd execution problem for command $options{cmd} : " . $stdout); + return -1; + } + + return 0; +} + sub scom_authenticate_1801 { my ($self, %options) = @_; @@ -159,6 +184,36 @@ sub scom_authenticate_1801 { return 0; } +sub acknowledge_alert_2016 { + my ($self, %options) = @_; + + my $curl_opts = []; + if (defined($self->{config_scom}->{curlopts})) { + foreach (keys %{$self->{config_scom}->{curlopts}}) { + push @{$curl_opts}, $_ . ' => ' . $self->{config_scom}->{curlopts}->{$_}; + } + } + my ($status, $response) = $self->{http}->request( + method => 'PUT', hostname => '', + full_url => $self->{config_scom}->{url} . 'alerts', + get_param => ['id=' . $options{alert_id}, 'ResolutionState=249'], + header => [ + 'Accept-Type: application/json; charset=utf-8', + 'Content-Type: application/json; charset=utf-8', + ], + curl_opt => $curl_opts, + ); + + return 1 if ($self->http_check_error(status => $status, method => 'data/alert') == 1); + + return 0; +} + +sub acknowledge_alert_1801 { + my ($self, %options) = @_; + +} + sub get_realtime_scom_alerts_1801 { my ($self, %options) = @_; @@ -269,7 +324,7 @@ sub get_realtime_slots { $self->{realtime_slots} = {}; my $request = " - SELECT hosts.instance_id, hosts.host_id, hosts.name, services.description, services.state, cv.name, cv.value + SELECT hosts.instance_id, hosts.host_id, hosts.name, services.description, services.state, cv.name, cv.value, services.acknowledged, hosts.instance_id FROM hosts, services LEFT JOIN customvariables cv ON services.host_id = cv.host_id AND services.service_id = cv.service_id AND cv.name = '$self->{dsmmacro}' WHERE hosts.name = '$self->{dsmhost}' AND hosts.host_id = services.host_id AND services.enabled = '1' AND services.description LIKE '$self->{dsmslot}'; @@ -285,6 +340,8 @@ sub get_realtime_slots { description => $$_[3], state => $$_[4], instance_id => $$_[0], + acknowledged => $$_[7], + instance_id => $$_[8], }; } @@ -303,7 +360,7 @@ sub sync_alerts { my $output = $self->change_macros( template => $self->{dsmalertmessage}, macros => $self->{scom_realtime_alerts}->{$alert_id}, - escape => '"', + escape => '[" . time() . "]"', ); $self->execute_shell_cmd( cmd => $self->{config}->{dsmclient_bin} . @@ -319,7 +376,7 @@ sub sync_alerts { # Close centreon alerts not present in scom foreach my $alert_id (keys %{$self->{realtime_slots}}) { next if ($self->{realtime_slots}->{$alert_id}->{state} == 0); - next if (defined($self->{scom_realtime_alerts}->{$alert_id}) && $self->{scom_realtime_alerts}->{$alert_id} != 255); + next if (defined($self->{scom_realtime_alerts}->{$alert_id}) && $self->{scom_realtime_alerts}->{$alert_id}->{resolutionstate} != 255); my $output = $self->change_macros( template => $self->{dsmrecoverymessage}, macros => {}, @@ -339,7 +396,32 @@ sub sync_alerts { sub sync_acks { my ($self, %options) = @_; - + my $func = $self->get_method(method => 'acknowledge_alert'); + foreach my $alert_id (keys %{$self->{realtime_slots}}) { + next if ($self->{realtime_slots}->{$alert_id}->{state} == 0); + next if ($self->{realtime_slots}->{$alert_id}->{acknowledged} == 0); + next if (!defined($self->{scom_realtime_alerts}->{$alert_id}) || + $self->{scom_realtime_alerts}->{$alert_id}->{resolutionstate} == 249); + $func->( + $self, + alert_id => $alert_id, + ); + } + + foreach my $alert_id (keys %{$self->{scom_realtime_alerts}}) { + next if (!defined($self->{realtime_slots}->{$alert_id}) || + $self->{realtime_slots}->{$alert_id}->{state} == 0); + $self->submit_external_cmd( + cmd => sprintf( + 'EXTERNALCMD:%s:[%s] ACKNOWLEDGE_SVC_PROBLEM;%s;%s;%s;%s;%s;%s;%s', + $self->{realtime_slots}->{$alert_id}->{instance_id}, + time(), + $self->{realtime_slots}->{$alert_id}->{host_name}, + $self->{realtime_slots}->{$alert_id}->{description}, + 2, 0, 1, 'scom connector', 'ack from scom' + ) + ); + } } sub action_scomresync { @@ -356,9 +438,7 @@ sub action_scomresync { return 1; } - my $api = 2016; - $api = 1801 if ($self->{api_version} == 1801); - my $func = $self->can('get_realtime_scom_alerts_' . $api); + my $func = $self->get_method(method => 'get_realtime_scom_alerts'); if ($func->($self)) { $self->send_log(code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot get scom realtime alerts' }); $self->{logger}->writeLogError("gorgone-scom: container $self->{container_id}: cannot get scom realtime alerts"); From 5c2648c066c15a5da3f760ac0a8f8357b6f85f8a Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 13 Aug 2019 14:20:38 +0200 Subject: [PATCH 032/948] add scom auth --- gorgone/modules/gorgonescom/class.pm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gorgone/modules/gorgonescom/class.pm b/gorgone/modules/gorgonescom/class.pm index 72dfcc677d2..34685ad00e4 100644 --- a/gorgone/modules/gorgonescom/class.pm +++ b/gorgone/modules/gorgonescom/class.pm @@ -193,10 +193,16 @@ sub acknowledge_alert_2016 { push @{$curl_opts}, $_ . ' => ' . $self->{config_scom}->{curlopts}->{$_}; } } + my $httpauth = $self->get_httpauth(); + my ($status, $response) = $self->{http}->request( method => 'PUT', hostname => '', full_url => $self->{config_scom}->{url} . 'alerts', get_param => ['id=' . $options{alert_id}, 'ResolutionState=249'], + credentials => 1, + %$httpauth, + username => $self->{config_scom}->{username}, + password => $self->{config_scom}->{password}, header => [ 'Accept-Type: application/json; charset=utf-8', 'Content-Type: application/json; charset=utf-8', From 592c4997f460fb61eced5d736e882dccd99b1003 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 13 Aug 2019 14:40:17 +0200 Subject: [PATCH 033/948] fix yaml format --- gorgone/config/gorgoned-poller.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/config/gorgoned-poller.yml b/gorgone/config/gorgoned-poller.yml index bd983a386be..28598089963 100644 --- a/gorgone/config/gorgoned-poller.yml +++ b/gorgone/config/gorgoned-poller.yml @@ -35,8 +35,8 @@ modules: # crypt options server_pubkey: keys/central/pubkey.crt - client_pubkey => 'keys/poller/pubkey.crt', - client_privkey => 'keys/poller/privkey.pem', + client_pubkey: keys/poller/pubkey.crt, + client_privkey: keys/poller/privkey.pem, cipher: "Cipher::AES" # in bytes From 384092049d061a1544a42f3b49e1007ba599bbba Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 13 Aug 2019 14:40:38 +0200 Subject: [PATCH 034/948] add httpserver module and api --- gorgone/centreon/script/gorgoneapi.pm | 79 +++++++ gorgone/modules/gorgonehttpserver/class.pm | 253 +++++++++++++++++++++ gorgone/modules/gorgonehttpserver/hooks.pm | 161 +++++++++++++ 3 files changed, 493 insertions(+) create mode 100644 gorgone/centreon/script/gorgoneapi.pm create mode 100644 gorgone/modules/gorgonehttpserver/class.pm create mode 100644 gorgone/modules/gorgonehttpserver/hooks.pm diff --git a/gorgone/centreon/script/gorgoneapi.pm b/gorgone/centreon/script/gorgoneapi.pm new file mode 100644 index 00000000000..1fdadf22492 --- /dev/null +++ b/gorgone/centreon/script/gorgoneapi.pm @@ -0,0 +1,79 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package centreon::script::gorgoneapi; + +use strict; +use warnings; +use centreon::gorgone::common; + +sub root { + my (%options) = @_; + + $options{logger}->writeLogInfo("gorgoneapi - requesting '" . $options{uri} . "' [" . $options{method} . "]"); + + my %dispatch; + foreach my $action (keys $options{modules_events}) { + next if (!defined($options{modules_events}->{$action}->{api}->{uri})); + $dispatch{$options{modules_events}->{$action}->{api}->{method} . '_/' . + $options{modules_events}->{$action}->{module}->{shortname} . + $options{modules_events}->{$action}->{api}->{uri}} = $action; + } + + my $response; + if ($options{method} eq 'GET' && $options{uri} =~ /^\/api\/get\/(.*)$/) { + $response = get_log(socket => $options{socket}, token => $1); + } elsif ($options{method} eq 'GET' && $options{uri} =~ /^\/api\/module\/(.*)$/) { + $response = call_action(socket => $options{socket}, action => $dispatch{'GET_' . $1}); + } elsif ($options{method} eq 'POST' && $options{uri} =~ /^\/api\/module\/(.*)$/) { + $response = call_action(socket => $options{socket}, action => $dispatch{'POST_' . $1}); + # } elsif { + # } + } else { + $response = '{"error":"method_unknown","message":"Method not implemented"}'; + } + + return $response; +} + +sub call_action { + my (%options) = @_; + + centreon::gorgone::common::zmq_send_message( + socket => $options{socket}, + action => $options{action}, + # target => $options{target}, + # data => $options{data}, + json_encode => 1 + ); +} + +sub get_log { + my (%options) = @_; + + centreon::gorgone::common::zmq_send_message( + socket => $options{socket}, + action => 'GETLOG', + token => $options{token}, + json_encode => 1 + ); +} + +1; diff --git a/gorgone/modules/gorgonehttpserver/class.pm b/gorgone/modules/gorgonehttpserver/class.pm new file mode 100644 index 00000000000..7ce241629cd --- /dev/null +++ b/gorgone/modules/gorgonehttpserver/class.pm @@ -0,0 +1,253 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package modules::gorgonehttpserver::class; + +use strict; +use warnings; +use centreon::gorgone::common; +use ZMQ::LibZMQ4; +use ZMQ::Constants qw(:all); +use HTTP::Daemon; +use HTTP::Daemon::SSL; +use HTTP::Status; +use MIME::Base64; + +my $time = time(); + +my %handlers = (TERM => {}, HUP => {}); +my ($connector, $socket); + +my %dispatch; + +sub new { + my ($class, %options) = @_; + $connector = {}; + $connector->{logger} = $options{logger}; + $connector->{config} = $options{config}; + $connector->{config_core} = $options{config_core}; + $connector->{stop} = 0; + $connector->{modules_events} = $options{modules_events}; + + bless $connector, $class; + $connector->set_signal_handlers; + return $connector; +} + +sub set_signal_handlers { + my $self = shift; + + $SIG{TERM} = \&class_handle_TERM; + $handlers{TERM}->{$self} = sub { $self->handle_TERM() }; + $SIG{HUP} = \&class_handle_HUP; + $handlers{HUP}->{$self} = sub { $self->handle_HUP() }; +} + +sub handle_HUP { + my $self = shift; + $self->{reload} = 0; +} + +sub handle_TERM { + my $self = shift; + $self->{logger}->writeLogInfo("gorgone-action $$ Receiving order to stop..."); + $self->{stop} = 1; +} + +sub class_handle_TERM { + foreach (keys %{$handlers{TERM}}) { + &{$handlers{TERM}->{$_}}(); + } +} + +sub class_handle_HUP { + foreach (keys %{$handlers{HUP}}) { + &{$handlers{HUP}->{$_}}(); + } +} + +sub event { + while (1) { + my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $socket); + + $connector->{logger}->writeLogDebug("gorgonehttpserver: class: $message"); + + last unless (centreon::gorgone::common::zmq_still_read(socket => $socket)); + } +} + +sub init_dispatch { + my ($self, $config_dispatch) = @_; + + $self->{dispatch} = { %{$self->{config}->{dispatch}} } + if (defined($self->{config}->{dispatch}) && $self->{config}->{dispatch} ne ''); +} + +sub run { + my ($self, %options) = @_; + + # Connect internal + $socket = centreon::gorgone::common::connect_com( + zmq_type => 'ZMQ_DEALER', + name => 'gorgonehttpserver', + logger => $self->{logger}, + type => $self->{config_core}->{internal_com_type}, + path => $self->{config_core}->{internal_com_path} + ); + centreon::gorgone::common::zmq_send_message( + socket => $socket, + action => 'HTTPSERVERREADY', + data => { }, + json_encode => 1 + ); + $self->{poll} = [ + { + socket => $socket, + events => ZMQ_POLLIN, + callback => \&event, + } + ]; + + $self->init_dispatch; + + # HTTP daemon + my $daemon; + if ($self->{config}->{ssl} eq 'false') { + $daemon = HTTP::Daemon->new( + LocalAddr => $self->{config}->{address} . ':' . $self->{config}->{port} + ); + } elsif ($self->{config}->{ssl} eq 'true') { + $daemon = HTTP::Daemon::SSL->new( + LocalAddr => $self->{config}->{address} . ':' . $self->{config}->{port}, + SSL_cert_file => $self->{config}->{ssl_cert_file}, + SSL_key_file => $self->{config}->{ssl_key_file}, + SSL_error_trap => \&ssl_error + ); + } + + while (my ($connection, $peer_addr) = $daemon->accept) { + while (my $request = $connection->get_request) { + $connector->{logger}->writeLogInfo("gorgone-httpserver - " . $request->method . " '" . $request->uri->path . "'"); + + if ($self->authentication($request->header('Authorization'))) { # Check Basic authentication + my ($root) = ($request->uri->path =~ /^(\/\w+)/); + + if ($request->method eq 'GET' && $root eq "/status") { # Server status + $self->send_response(connection => $connection, response => $self->server_status); + } elsif ($root eq "/api") { # API + $self->send_response(connection => $connection, response => $self->api_call($request)); + } elsif (defined($self->{dispatch}->{$root})) { # Other dispatch definition + $self->send_response(connection => $connection, response => $self->dispatch_call(root => $root, request => $request)); + } else { # Forbidden + $connection->send_error(RC_FORBIDDEN) + } + } else { # Authen error + $connection->send_error(RC_UNAUTHORIZED); + } + } + $connection->close; + undef($connection); + } +} + +sub ssl_error { + my ($self, $error) = @_; + + ${*$self}{'httpd_client_proto'} = 1000; + ${*$self}{'httpd_daemon'} = new HTTP::Daemon::SSL::DummyDaemon; + $self->send_error(RC_BAD_REQUEST); + $self->close; +} + +sub authentication { + my ($self, $header) = @_; + return 0 if (!defined($header) || $header eq ''); + + ($header =~ /Basic\s(.*)$/); + my ($user, $password) = split(/:/, MIME::Base64::decode($1), 2); + return 1 if ($user eq $self->{config}->{auth}->{user} && $password eq $self->{config}->{auth}->{password}); + + return 0; +} + +sub send_response { + my ($self, %options) = @_; + + my $response = HTTP::Response->new(200); + $response->content($options{response} . "\n"); + $options{connection}->send_response($response); +} + +sub api_call { + my ($self, $request) = @_; + + require 'centreon/script/gorgoneapi.pm'; + my %params = $request->uri->query_form; + my $response = centreon::script::gorgoneapi::root( + uri => $request->uri->path, + params => \%params, + method => $request->method, + socket => $socket, + logger => $self->{logger}, + modules_events => $self->{modules_events} + ); + + return $response; +} + +sub dispatch_call { + my ($self, %options) = @_; + + my $class = $self->{dispatch}->{$options{root}}->{class}; + my $method = $self->{dispatch}->{$options{root}}->{method}; + my $response; + eval { + (my $file = "$class.pm") =~ s|::|/|g; + require $file; + $response = $class->$method(request => $options{request}); + }; + if ($@) { + $response = $@; + }; + + return $response; +} + +sub server_status { + my ($self, %options) = @_; + + my %data = ( + starttime => $time, + dispatch => $self->{dispatch}, + modules_events => $self->{modules_events} + ); + + my $encoded_data; + eval { + $encoded_data = JSON::XS->new->utf8->encode(\%data); + }; + if ($@) { + $encoded_data = '{"code":"encode_error","message":"Cannot encode response into JSON format"}'; + } + + return $encoded_data; +} + +1; diff --git a/gorgone/modules/gorgonehttpserver/hooks.pm b/gorgone/modules/gorgonehttpserver/hooks.pm new file mode 100644 index 00000000000..e3ea7bebfe7 --- /dev/null +++ b/gorgone/modules/gorgonehttpserver/hooks.pm @@ -0,0 +1,161 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package modules::gorgonehttpserver::hooks; + +use warnings; +use strict; +use centreon::script::gorgonecore; +use modules::gorgonehttpserver::class; + +my $config_core; +my $config; +my $module_shortname = 'httpserver'; +my $module_id = 'gorgonehttpserver'; +my $events = [ + { event => 'HTTPSERVERREADY' }, +]; +my $httpserver = {}; +my $stop = 0; + +sub register { + my (%options) = @_; + + $config = $options{config}; + $config_core = $options{config_core}; + return ($events, $module_shortname, $module_id); +} + +sub init { + my (%options) = @_; + + create_child(logger => $options{logger}, modules_events => $options{modules_events}); +} + +sub routing { + my (%options) = @_; + + my $data; + eval { + $data = JSON->new->utf8->decode($options{data}); + }; + if ($@) { + $options{logger}->writeLogError("Cannot decode json data: $@"); + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => 10, + token => $options{token}, + data => { message => 'gorgonehttpserver: cannot decode json' }, + json_encode => 1 + ); + return undef; + } + + if ($options{action} eq 'HTTPSERVERREADY') { + $httpserver->{ready} = 1; + return undef; + } + + if (centreon::script::gorgonecore::waiting_ready(ready => \$httpserver->{ready}) == 0) { + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => 10, + token => $options{token}, + data => { message => 'gorgonehttpserver: still no ready' }, + json_encode => 1 + ); + return undef; + } + + centreon::gorgone::common::zmq_send_message( + socket => $options{socket}, + identity => 'gorgonehttpserver', + action => $options{action}, + data => $options{data}, + token => $options{token}, + ); +} + +sub gently { + my (%options) = @_; + + $stop = 1; + $options{logger}->writeLogInfo("gorgone-httpserver: Send TERM signal"); + if ($httpserver->{running} == 1) { + CORE::kill('TERM', $httpserver->{pid}); + } +} + +sub kill { + my (%options) = @_; + + if ($httpserver->{running} == 1) { + $options{logger}->writeLogInfo("gorgone-httpserver: Send KILL signal for pool"); + CORE::kill('KILL', $httpserver->{pid}); + } +} + +sub kill_internal { + my (%options) = @_; + +} + +sub check { + my (%options) = @_; + + my $count = 0; + foreach my $pid (keys %{$options{dead_childs}}) { + # Not me + next if ($httpserver->{pid} != $pid); + + $httpserver = {}; + delete $options{dead_childs}->{$pid}; + if ($stop == 0) { + create_child(logger => $options{logger}, modules_events => $options{modules_events}); + } + } + + $count++ if (defined($httpserver->{running}) && $httpserver->{running} == 1); + + return $count; +} + +# Specific functions +sub create_child { + my (%options) = @_; + + $options{logger}->writeLogInfo("Create gorgonehttpserver process"); + my $child_pid = fork(); + if ($child_pid == 0) { + $0 = 'gorgone-httpserver'; + my $module = modules::gorgonehttpserver::class->new( + logger => $options{logger}, + config_core => $config_core, + config => $config, + modules_events => $options{modules_events} + ); + $module->run(); + exit(0); + } + $options{logger}->writeLogInfo("PID $child_pid gorgonehttpserver"); + $httpserver = { pid => $child_pid, ready => 0, running => 1 }; +} + +1; From 593273cda7c20ef57efc2a7496a8e8611b9cac12 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 13 Aug 2019 14:41:28 +0200 Subject: [PATCH 035/948] fix yaml format --- gorgone/config/gorgoned-poller.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/config/gorgoned-poller.yml b/gorgone/config/gorgoned-poller.yml index 28598089963..ce2381f2440 100644 --- a/gorgone/config/gorgoned-poller.yml +++ b/gorgone/config/gorgoned-poller.yml @@ -35,8 +35,8 @@ modules: # crypt options server_pubkey: keys/central/pubkey.crt - client_pubkey: keys/poller/pubkey.crt, - client_privkey: keys/poller/privkey.pem, + client_pubkey: keys/poller/pubkey.crt + client_privkey: keys/poller/privkey.pem cipher: "Cipher::AES" # in bytes From 1a5f402803af809729ab0026b411446858c5c440 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 13 Aug 2019 15:41:45 +0200 Subject: [PATCH 036/948] add scom sync --- gorgone/modules/gorgonescom/class.pm | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/gorgone/modules/gorgonescom/class.pm b/gorgone/modules/gorgonescom/class.pm index 34685ad00e4..ef928562def 100644 --- a/gorgone/modules/gorgonescom/class.pm +++ b/gorgone/modules/gorgonescom/class.pm @@ -187,6 +187,12 @@ sub scom_authenticate_1801 { sub acknowledge_alert_2016 { my ($self, %options) = @_; + my $arguments = { + 'resolutionState' => $options{resolutionstate}, + }; + my ($status, $encoded_argument) = $self->json_encode(argument => $arguments); + return 1 if ($status == 1); + my $curl_opts = []; if (defined($self->{config_scom}->{curlopts})) { foreach (keys %{$self->{config_scom}->{curlopts}}) { @@ -195,10 +201,10 @@ sub acknowledge_alert_2016 { } my $httpauth = $self->get_httpauth(); - my ($status, $response) = $self->{http}->request( + ($status, my $response) = $self->{http}->request( method => 'PUT', hostname => '', - full_url => $self->{config_scom}->{url} . 'alerts', - get_param => ['id=' . $options{alert_id}, 'ResolutionState=249'], + full_url => $self->{config_scom}->{url} . 'alerts/' . $options{alert_id}, + query_form_post => $encoded_argument, credentials => 1, %$httpauth, username => $self->{config_scom}->{username}, @@ -357,6 +363,21 @@ sub get_realtime_slots { sub sync_alerts { my ($self, %options) = @_; + my $func = $self->get_method(method => 'acknowledge_alert'); + # First we look closed alerts in centreon + foreach my $alert_id (keys %{$self->{realtime_slots}}) { + next if ($self->{realtime_slots}->{$alert_id}->{state} != 0); + next if (!defined($self->{scom_realtime_alerts}->{$alert_id}) || + $self->{scom_realtime_alerts}->{$alert_id}->{resolutionstate} == 254 || + $self->{scom_realtime_alerts}->{$alert_id}->{resolutionstate} == 255 + ); + $func->( + $self, + alert_id => $alert_id, + resolutionstate => 254, + ); + } + # Look if scom alers is in centreon-dsm services my $pool_prefix = $self->{dsmslot}; $pool_prefix =~ s/%//g; @@ -411,6 +432,7 @@ sub sync_acks { $func->( $self, alert_id => $alert_id, + resolutionstate => 249, ); } From bb04780379b5bcd30157d91cc3a87a39263f9786 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 13 Aug 2019 17:30:51 +0200 Subject: [PATCH 037/948] fix debug --- gorgone/centreon/gorgone/common.pm | 1 - 1 file changed, 1 deletion(-) diff --git a/gorgone/centreon/gorgone/common.pm b/gorgone/centreon/gorgone/common.pm index baed4366ddd..881f76cd0de 100644 --- a/gorgone/centreon/gorgone/common.pm +++ b/gorgone/centreon/gorgone/common.pm @@ -203,7 +203,6 @@ sub client_get_secret { $plaintext = $options{privkey}->decrypt($options{message}, 'v1.5'); }; if ($@) { - print "====$@====\n"; return (-1, "Decoding issue: $@"); } From c069507d9dc1323de51877e9298ad4a9c81274c2 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 14 Aug 2019 09:18:33 +0200 Subject: [PATCH 038/948] limit class loaded --- gorgone/centreon/misc/http/http.pm | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/gorgone/centreon/misc/http/http.pm b/gorgone/centreon/misc/http/http.pm index 4fab790ea37..8dc7da6f8e1 100644 --- a/gorgone/centreon/misc/http/http.pm +++ b/gorgone/centreon/misc/http/http.pm @@ -29,20 +29,6 @@ sub new { my $self = {}; bless $self, $class; - if (centreon::misc::misc::mymodule_load( - logger => $options{logger}, module => 'centreon::misc::http::backend::lwp', - error_msg => "Cannot load module 'centreon::misc::http::backend::lwp'." - ) == 0) { - $self->{backend_lwp} = centreon::misc::http::backend::lwp->new(%options); - } - - if (centreon::misc::misc::mymodule_load( - logger => $options{logger}, module => 'centreon::misc::http::backend::curl', - error_msg => "Cannot load module 'centreon::misc::http::backend::curl'." - ) == 0) { - $self->{backend_curl} = centreon::misc::http::backend::curl->new(%options); - } - $self->{logger} = $options{logger}; $self->{options} = { proto => 'http', @@ -81,6 +67,20 @@ sub check_options { return 1; } + if ($options{request}->{http_backend} eq 'lwp' && centreon::misc::misc::mymodule_load( + logger => $options{logger}, module => 'centreon::misc::http::backend::lwp', + error_msg => "Cannot load module 'centreon::misc::http::backend::lwp'." + ) == 0) { + $self->{backend_lwp} = centreon::misc::http::backend::lwp->new(%options, logger => $self->{logger}); + } + + if ($options{request}->{http_backend} eq 'curl' && centreon::misc::misc::mymodule_load( + logger => $options{logger}, module => 'centreon::misc::http::backend::curl', + error_msg => "Cannot load module 'centreon::misc::http::backend::curl'." + ) == 0) { + $self->{backend_curl} = centreon::misc::http::backend::curl->new(%options, logger => $self->{logger}); + } + if (($options{request}->{proto} ne 'http') && ($options{request}->{proto} ne 'https')) { $self->{logger}->writeLogError("Unsupported protocol specified '" . $self->{option_results}->{proto} . "'."); return 1; From 14f3d675873a53447bb9b375e7132d59ac26ad2e Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 14 Aug 2019 13:56:25 +0200 Subject: [PATCH 039/948] change tree --- .../{script/gorgoneapi.pm => gorgone/api.pm} | 69 ++- gorgone/centreon/script/gorgonecore.pm | 46 +- gorgone/config/gorgoned.yml | 125 +++-- gorgone/docs/guide.rst | 167 +------ gorgone/docs/modules/newtest/exploitation.txt | 14 +- gorgone/docs/modules/newtest/installation.rst | 8 +- .../{gorgoneaction => core/action}/class.pm | 37 +- .../{gorgoneaction => core/action}/hooks.pm | 32 +- gorgone/modules/core/cron/class.pm | 195 ++++++++ .../{gorgonecron => core/cron}/hooks.pm | 35 +- .../httpserver}/class.pm | 29 +- .../httpserver}/hooks.pm | 28 +- .../{gorgoneproxy => core/proxy}/class.pm | 34 +- .../{gorgoneproxy => core/proxy}/hooks.pm | 58 +-- .../{gorgonepull => core/pull}/hooks.pm | 24 +- gorgone/modules/gorgoneacl/class.pm | 442 ------------------ gorgone/modules/gorgoneacl/hooks.pm | 246 ---------- gorgone/modules/gorgonecron/class.pm | 136 ------ .../{gorgonenewtest => newtest}/class.pm | 86 ++-- .../{gorgonenewtest => newtest}/hooks.pm | 37 +- .../libs}/stubs/ManagementConsoleService.pm | 6 +- .../newtest => newtest/libs}/stubs/errors.pm | 2 +- .../libs}/wsdl/newtest.wsdl | 0 .../modules/{gorgonescom => scom}/class.pm | 27 +- .../modules/{gorgonescom => scom}/hooks.pm | 37 +- 25 files changed, 607 insertions(+), 1313 deletions(-) rename gorgone/centreon/{script/gorgoneapi.pm => gorgone/api.pm} (57%) rename gorgone/modules/{gorgoneaction => core/action}/class.pm (88%) rename gorgone/modules/{gorgoneaction => core/action}/hooks.pm (82%) create mode 100644 gorgone/modules/core/cron/class.pm rename gorgone/modules/{gorgonecron => core/cron}/hooks.pm (81%) rename gorgone/modules/{gorgonehttpserver => core/httpserver}/class.pm (88%) rename gorgone/modules/{gorgonehttpserver => core/httpserver}/hooks.pm (84%) rename gorgone/modules/{gorgoneproxy => core/proxy}/class.pm (87%) rename gorgone/modules/{gorgoneproxy => core/proxy}/hooks.pm (88%) rename gorgone/modules/{gorgonepull => core/pull}/hooks.pm (88%) delete mode 100644 gorgone/modules/gorgoneacl/class.pm delete mode 100644 gorgone/modules/gorgoneacl/hooks.pm delete mode 100644 gorgone/modules/gorgonecron/class.pm rename gorgone/modules/{gorgonenewtest => newtest}/class.pm (83%) rename gorgone/modules/{gorgonenewtest => newtest}/hooks.pm (84%) rename gorgone/modules/{gorgonenewtest/newtest => newtest/libs}/stubs/ManagementConsoleService.pm (98%) rename gorgone/modules/{gorgonenewtest/newtest => newtest/libs}/stubs/errors.pm (89%) rename gorgone/modules/{gorgonenewtest/newtest => newtest/libs}/wsdl/newtest.wsdl (100%) rename gorgone/modules/{gorgonescom => scom}/class.pm (93%) rename gorgone/modules/{gorgonescom => scom}/hooks.pm (85%) diff --git a/gorgone/centreon/script/gorgoneapi.pm b/gorgone/centreon/gorgone/api.pm similarity index 57% rename from gorgone/centreon/script/gorgoneapi.pm rename to gorgone/centreon/gorgone/api.pm index 1fdadf22492..e09f95b86e1 100644 --- a/gorgone/centreon/script/gorgoneapi.pm +++ b/gorgone/centreon/gorgone/api.pm @@ -18,31 +18,36 @@ # limitations under the License. # -package centreon::script::gorgoneapi; +package centreon::gorgone::api; use strict; use warnings; use centreon::gorgone::common; +use ZMQ::LibZMQ4; +use ZMQ::Constants qw(:all); + +my $socket; +my $result; sub root { my (%options) = @_; - $options{logger}->writeLogInfo("gorgoneapi - requesting '" . $options{uri} . "' [" . $options{method} . "]"); + $options{logger}->writeLogInfo("[api] Requesting '" . $options{uri} . "' [" . $options{method} . "]"); my %dispatch; foreach my $action (keys $options{modules_events}) { next if (!defined($options{modules_events}->{$action}->{api}->{uri})); $dispatch{$options{modules_events}->{$action}->{api}->{method} . '_/' . - $options{modules_events}->{$action}->{module}->{shortname} . + $options{modules_events}->{$action}->{module}->{name} . $options{modules_events}->{$action}->{api}->{uri}} = $action; } my $response; if ($options{method} eq 'GET' && $options{uri} =~ /^\/api\/get\/(.*)$/) { $response = get_log(socket => $options{socket}, token => $1); - } elsif ($options{method} eq 'GET' && $options{uri} =~ /^\/api\/module\/(.*)$/) { + } elsif ($options{method} eq 'GET' && $options{uri} =~ /^\/api\/module(.*)$/) { $response = call_action(socket => $options{socket}, action => $dispatch{'GET_' . $1}); - } elsif ($options{method} eq 'POST' && $options{uri} =~ /^\/api\/module\/(.*)$/) { + } elsif ($options{method} eq 'POST' && $options{uri} =~ /^\/api\/module(.*)$/) { $response = call_action(socket => $options{socket}, action => $dispatch{'POST_' . $1}); # } elsif { # } @@ -59,10 +64,23 @@ sub call_action { centreon::gorgone::common::zmq_send_message( socket => $options{socket}, action => $options{action}, - # target => $options{target}, - # data => $options{data}, + target => $options{target}, + data => $options{data}, json_encode => 1 ); + + $socket = $options{socket}; + my $poll = [ + { + socket => $options{socket}, + events => ZMQ_POLLIN, + callback => \&event, + } + ]; + + my $rev = zmq_poll($poll, 5000); + + return '{"token":"' . $result->{token} . '"}'; } sub get_log { @@ -71,9 +89,42 @@ sub get_log { centreon::gorgone::common::zmq_send_message( socket => $options{socket}, action => 'GETLOG', - token => $options{token}, + data => { + token => $options{token} + }, json_encode => 1 - ); + ); + + $socket = $options{socket}; + my $poll = [ + { + socket => $options{socket}, + events => ZMQ_POLLIN, + callback => \&event, + } + ]; + + my $rev = zmq_poll($poll, 5000); + + return $result->{data}; +} + +sub event { + while (1) { + my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $socket); + + $result = {}; + if ($message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m || + $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+(.*)$/m) { + $result = { + action => $1, + token => $2, + data => $3, + }; + } + + last unless (centreon::gorgone::common::zmq_still_read(socket => $socket)); + } } 1; diff --git a/gorgone/centreon/script/gorgonecore.pm b/gorgone/centreon/script/gorgonecore.pm index 3a20cc6bbd0..47760322348 100644 --- a/gorgone/centreon/script/gorgonecore.pm +++ b/gorgone/centreon/script/gorgonecore.pm @@ -73,7 +73,7 @@ sub init { ## load config ini if (! -f $self->{opt_extra}) { - $self->{logger}->writeLogError("Can't find extra config file '$self->{opt_extra}'"); + $self->{logger}->writeLogError("[core] Can't find extra config file '$self->{opt_extra}'"); exit(1); } $config = centreon::gorgone::common::read_config( @@ -98,7 +98,7 @@ sub init { ); $gorgone->{db_gorgone}->set_inactive_destroy(); if ($gorgone->{db_gorgone}->connect() == -1) { - $gorgone->{logger}->writeLogInfo("Cannot connect. We quit!!"); + $gorgone->{logger}->writeLogInfo("[core] Cannot connect. We quit!!"); exit(1); } @@ -148,7 +148,7 @@ sub class_handle_CHLD { sub handle_TERM { my $self = shift; - $self->{logger}->writeLogInfo("$$ Receiving order to stop..."); + $self->{logger}->writeLogInfo("[core] $$ Receiving order to stop..."); $self->{stop} = 1; foreach my $name (keys %{$self->{modules_register}}) { @@ -159,7 +159,7 @@ sub handle_TERM { sub handle_HUP { my $self = shift; - $self->{logger}->writeLogInfo("$$ Receiving order to reload..."); + $self->{logger}->writeLogInfo("[core] $$ Receiving order to reload..."); # TODO } @@ -180,40 +180,40 @@ sub load_modules { foreach my $module (@{$config->{modules}}) { next if (!defined($module->{enable}) || $module->{enable} eq 'false'); - my $name = $module->{module}; - (my $file = "$name.pm") =~ s{::}{/}g; + my $package = $module->{package}; + (my $file = "$package.pm") =~ s{::}{/}g; require $file; - $self->{logger}->writeLogInfo("Module '" . $module->{name} . "' is loading"); - $self->{modules_register}->{$name} = {}; + $self->{logger}->writeLogInfo("[core] Module '" . $module->{name} . "' is loading"); + $self->{modules_register}->{$package} = {}; foreach my $method_name (('register', 'routing', 'kill', 'kill_internal', 'gently', 'check', 'init')) { - unless ($self->{modules_register}->{$name}->{$method_name} = $name->can($method_name)) { - $self->{logger}->writeLogError("No function '$method_name' for module '" . $module->{name} . "'"); + unless ($self->{modules_register}->{$package}->{$method_name} = $package->can($method_name)) { + $self->{logger}->writeLogError("[core] No function '$method_name' for module '" . $module->{name} . "'"); exit(1); } } - my ($events, $shortname, $id) = $self->{modules_register}->{$name}->{register}->( + my ($name, $events) = $self->{modules_register}->{$package}->{register}->( config => $module, config_core => $config->{gorgonecore}, config_db_centreon => $config->{database}->{db_centreon}, config_db_centstorage => $config->{database}->{db_centstorage} ); - $self->{modules_id}->{$id} = $name; + $self->{modules_id}->{$name} = $package; foreach my $event (@{$events}) { $self->{modules_events}->{$event->{event}} = { - module => { name => $name, shortname => $shortname }, + module => { name => $name, package => $package }, api => { uri => $event->{uri}, method => $event->{method} } }; } - $self->{logger}->writeLogInfo("Module '" . $module->{name} . "' is loaded"); + $self->{logger}->writeLogInfo("[core] Module '" . $module->{name} . "' is loaded"); } # Load internal functions foreach my $method_name (('putlog', 'getlog', 'kill', 'ping', 'constatus')) { unless ($self->{internal_register}->{$method_name} = centreon::gorgone::common->can($method_name)) { - $self->{logger}->writeLogError("No function '$method_name'"); + $self->{logger}->writeLogError("[core] No function '$method_name'"); exit(1); } } @@ -281,7 +281,7 @@ sub message_run { ); return ($token, $code, $response, $response_type); } else { - $self->{modules_register}->{$self->{modules_events}->{$action}->{module}->{name}}->{routing}->( + $self->{modules_register}->{$self->{modules_events}->{$action}->{module}->{package}}->{routing}->( socket => $self->{internal_socket}, dbh => $self->{db_gorgone}, logger => $self->{logger}, @@ -447,7 +447,7 @@ sub clean_sessions { my ($self, %options) = @_; if ($self->{sessions_timer} - time() > $config->{gorgonecore}->{purge_sessions_time}) { - $self->{logger}->writeLogInfo("purge sessions in progress..."); + $self->{logger}->writeLogInfo("[core] Purge sessions in progress..."); $self->{db_gorgone}->query("DELETE FROM gorgone_identity WHERE `ctime` < " . $self->{db_gorgone}->quote(time() - $config->{gorgonecore}->{sessions_time})); $self->{sessions_timer} = time(); } @@ -456,7 +456,7 @@ sub clean_sessions { sub quit { my ($self, %options) = @_; - $self->{logger}->writeLogInfo("Quit main process"); + $self->{logger}->writeLogInfo("[core] Quit main process"); zmq_close($self->{internal_socket}); if (defined($config->{gorgonecore}->{external_com_type}) && $config->{gorgonecore}->{external_com_type} ne '') { zmq_close($self->{external_socket}); @@ -470,8 +470,8 @@ sub run { $gorgone->SUPER::run(); $gorgone->{logger}->redirect_output(); - $gorgone->{logger}->writeLogDebug("gorgoned launched...."); - $gorgone->{logger}->writeLogDebug("PID: $$"); + $gorgone->{logger}->writeLogDebug("[core] gorgoned launched...."); + $gorgone->{logger}->writeLogDebug("[core] PID $$"); if (centreon::gorgone::common::add_history( dbh => $gorgone->{db_gorgone}, @@ -479,7 +479,7 @@ sub run { data => { msg => 'gorgoned is starting...' }, json_encode => 1) == -1 ) { - $gorgone->{logger}->writeLogInfo("Cannot write in history. We quit!!"); + $gorgone->{logger}->writeLogInfo("[core] Cannot write in history. We quit!!"); exit(1); } @@ -517,7 +517,7 @@ sub run { # init all modules foreach my $name (keys %{$gorgone->{modules_register}}) { - $gorgone->{logger}->writeLogInfo("Call init function from module '$name'"); + $gorgone->{logger}->writeLogInfo("[core] Call init function from module '$name'"); $gorgone->{modules_register}->{$name}->{init}->( id => $gorgone->{id}, logger => $gorgone->{logger}, @@ -529,7 +529,7 @@ sub run { ); } - $gorgone->{logger}->writeLogInfo("[Server accepting clients]"); + $gorgone->{logger}->writeLogInfo("[core] Server accepting clients"); while (1) { my $count = 0; diff --git a/gorgone/config/gorgoned.yml b/gorgone/config/gorgoned.yml index fffaf924676..bd647c01c24 100644 --- a/gorgone/config/gorgoned.yml +++ b/gorgone/config/gorgoned.yml @@ -42,11 +42,11 @@ gorgonecore: # in seconds purge_sessions_time: 3600 # shouldn't be changed - proxy_name: gorgoneproxy + proxy_name: proxy modules: - - name: gorgonehttpserver - module: modules::gorgonehttpserver::hooks - enable: false + - name: httpserver + package: modules::core::httpserver::hooks + enable: true address: 0.0.0.0 port: 8080 ssl: true @@ -56,33 +56,20 @@ modules: user: admin password: password - - name: gorgonecron - module: modules::gorgonecron::hooks + - name: cron + package: modules::core::cron::hooks enable: true + cron: + - name: "Echo date in /tmp/date.log" + timespec: "* * * * *" + command_line: "date >> /tmp/date.log" - - name: gorgoneaction - module: modules::gorgoneaction::hooks - enable: false - - - name: gorgoneacl - module: modules::gorgoneacl::hooks - enable: false - on_demand: 1 - # How much to keep open in seconds without event received - on_demand_time: 60 - # in seconds - do purge for organizations also - check_organizations_time: 3600 - # in seconds - do a resync of the organizations - resync_time: 28800 - # in seconds - random windows (to avoid resync at the same time) - resync_random_windows: 7200 - # set to 1 to disable - if you want to do it by a cron - resync_auto_disable: 0 - sql_fetch: 10000 - sql_bulk: 2000 + - name: action + package: modules::core::action::hooks + enable: true - - name: gorgoneproxy - module: modules::gorgoneproxy::hooks + - name: proxy + package: modules::core::proxy::hooks enable: false pool: 5 # sync history each 5 minutes @@ -92,36 +79,36 @@ modules: # ping each X seconds ping: 60 - - name: gorgonescom - module: modules::gorgonescom::hooks - enable: true + - name: scom + package: modules::scom::hooks + enable: false # in seconds - do purge for container also check_containers_time: 3600 dsmclient_bin: /usr/share/centreon/bin/dsmclient.pl centcore_cmd: /var/lib/centreon/centcore.cmd containers: - - name: toto - api_version: 2016 - url: "http://scomserver/api/" - username: toto - password: pass - httpauth: basic - resync_time: 300 - dsmhost: ADH3 - dsmslot: Scom-% - dsmmacro: ALARM_ID - dsmalertmessage: "%{monitoringobjectdisplayname} %{name}" - dsmrecoverymessage: slot ok - curlopts: - CURLOPT_SSL_VERIFYPEER: 0 + - name: toto + api_version: 2016 + url: "http://scomserver/api/" + username: toto + password: pass + httpauth: basic + resync_time: 300 + dsmhost: ADH3 + dsmslot: Scom-% + dsmmacro: ALARM_ID + dsmalertmessage: "%{monitoringobjectdisplayname} %{name}" + dsmrecoverymessage: slot ok + curlopts: + CURLOPT_SSL_VERIFYPEER: 0 # - name: tutu # url: http://scomserver2/ # username: toto2 # password: toto2 # resync_time: 600 - - name: gorgonenewtest - module: modules::gorgonenewtest::hooks + - name: newtest + package: modules::newtest::hooks enable: false # in seconds - do purge for container also check_containers_time: 3600 @@ -131,25 +118,25 @@ modules: clapi_action_applycfg: RELOAD centcore_cmd: /var/lib/centreon/centcore.cmd containers: - - name: toto - resync_time: 300 - nmc_endpoint: "http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx" - username: user - password: pass - host_template: generic-active-host-custom - host_prefix: Robot-%s - service_template: generic-passive-service-custom - service_prefix: Scenario-%s - poller_name: Central - list_scenario_status: '{ "search": "All", "instances": [] }' - - name: tutu - resync_time: 600 - nmc_endpoint: "http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx" - username: user - password: pass - host_template: generic-active-host-custom - host_prefix: Robot-%s - service_template: generic-passive-service-custom - service_prefix: Scenario-%s - poller_name: Central - list_scenario_status: '{ "search": "Robot", "instances": ["XXXX"] }' + - name: toto + resync_time: 300 + nmc_endpoint: "http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx" + username: user + password: pass + host_template: generic-active-host-custom + host_prefix: Robot-%s + service_template: generic-passive-service-custom + service_prefix: Scenario-%s + poller_name: Central + list_scenario_status: '{ "search": "All", "instances": [] }' + - name: tutu + resync_time: 600 + nmc_endpoint: "http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx" + username: user + password: pass + host_template: generic-active-host-custom + host_prefix: Robot-%s + service_template: generic-passive-service-custom + service_prefix: Scenario-%s + poller_name: Central + list_scenario_status: '{ "search": "Robot", "instances": ["XXXX"] }' diff --git a/gorgone/docs/guide.rst b/gorgone/docs/guide.rst index 3b79394e2a6..d084ac2f00e 100644 --- a/gorgone/docs/guide.rst +++ b/gorgone/docs/guide.rst @@ -29,12 +29,18 @@ Daemon uses following Perl modules: * Crypt::Cipher::AES: in attachment (module CryptX) * Crypt::PK::RSA: in attachment (module CryptX) * Crypt::PRNG: in attachment (module CryptX) +* HTTP::Daemon: repository 'centos base' +* HTTP::Daemon::SSL: in EPEL +* HTTP::Status: repository 'centos base' +* MIME::Base64: repository 'centos base' Execute following commands: :: - # yum install 'perl(Schedule::Cron)' 'perl(Crypt::CBC)' 'perl(ZMQ::LibZMQ4)' 'perl(JSON)' 'perl(YAML)' 'perl(DBD::SQLite)' 'perl(DBD::mysql)' 'perl(UUID)' + # yum install 'perl(Schedule::Cron)' 'perl(Crypt::CBC)' 'perl(ZMQ::LibZMQ4)' 'perl(JSON)' 'perl(YAML)' \ + 'perl(DBD::SQLite)' 'perl(DBD::mysql)' 'perl(UUID)' 'perl(HTTP::Daemon)' 'perl(HTTP::Daemon::SSL)' \ + 'perl(HTTP::Status)' 'perl(MIME::Base64)' # yum install perl-CryptX-0.064-1.el7.x86_64 Create sqlite database: @@ -256,153 +262,8 @@ Modules can have extra codes. module requests =============== ------------ -gorgone-acl ------------ - -No extra codes. - -ACLADDHOST -^^^^^^^^^^ - -Example: -:: - - [ACLADDHOST] [] [] { organization_id => XX, host_id => XX } - -ACLDELHOST -^^^^^^^^^^ - -Example: -:: - - [ACLDELHOST] [] [] { organization_id => XX, host_id => XX } - -ACLADDSERVICE -^^^^^^^^^^^^^ - -Example: -:: - - [ACLADDSERVICE] [] [] { organization_id => XX, service_id => XX } - -ACLDELSERVICE -^^^^^^^^^^^^^ - -Example: -:: - - [ACLDELSERVICE] [] [] { organization_id => XX, service_id => XX } - -ACLUPDATETAG -^^^^^^^^^^^^ - -Example: -:: - - [ACLUPDATETAG] [] [] { organization_id => XX, tag_id => XX, tag_type => X, resource_id => X, action => X } - -The following action should be used when you assign/unassign a tag: - -* tag_type: '1' (host tag), '2' (service tag), '3' (ba tag) -* resource_id: host_id, service_id or ba_id (depends of the tag_type) -* action: '1' (assign), '2' (unassign) - -ACLDELTAG -^^^^^^^^^ - -Example: -:: - - [ACLDELTAG] [] [] { organization_id => XX, tag_id => XX, tag_type => X } - -The following action should be used when you delete a tag: - -* tag_type: '1' (host tag), '2' (service tag), '3' (ba tag) - -ACLUPDATEDOMAIN -^^^^^^^^^^^^^^^ - -Example: -:: - - [ACLUPDATEDOMAIN] [] [] { organization_id => XX, domain_id => XX, service_id => XX, action => X } - -The following action should be used when you assign/unassign a domain to a service. - -ACLDELDOMAIN -^^^^^^^^^^^^ - -Example: -:: - - [ACLDELDOMAIN] [] [] { organization_id => XX, domain_id => XX } - -ACLUPDATEENVIRONMENT -^^^^^^^^^^^^^^^^^^^^ - -Example: -:: - - [ACLUPDATEENVIRONMENT] [] [] { organization_id => XX,, environment_id => XX, environment_type => X, resource_id => X, action => X } - -The following action should be used when you assign/unassign a environment: - -* environment_type: '1' (host), '2' (service), '3' (ba) -* resource_id: host_id, service_id (depends of the environment_type) -* action: '1' (assign), '2' (unassign) - -ACLDELENVIRONMENT -^^^^^^^^^^^^^^^^^ - -Example: -:: - - [ACLDELENVIRONMENT] [] [] { organization_id => XX, environment_id => XX } - -ACLUPDATEPOLLER -^^^^^^^^^^^^^^^ - -Example: -:: - - [ACLUPDATEPOLLER] [] [] { organization_id => XX, poller_id => XX, host_id => XX, action => X } - -The following action should be used when you assign/unassign a poller to a host: - -* action: '1' (assign), '2' (unassign) - -ACLDELPOLLER -^^^^^^^^^^^^ - -Example: -:: - - [ACLDELPOLLER] [] [] { organization_id => XX, poller_id => XX } - -ACLPURGEORGANIZATION -^^^^^^^^^^^^^^^^^^^^ - -Example: -:: - - [ACLPURGEORGANIZATION] [] [] { organization_id => XX } - -The following action should be used when you delete a organization. - -ACLRESYNC -^^^^^^^^^ - -Example: -:: - - [ACLRESYNC] [] [] { organization_id => XX, acl_resource_id => XX } - -The following action should be used when you want to rebuild an entire organization. -You can rebuild a specific 'acl_resource' group if you set it. - -------------- -gorgone-action +action -------------- COMMAND @@ -442,15 +303,15 @@ Which modules should i enable ? A poller with gorgoned should have the following modules: -* gorgone-action -* gorgone-pull: if the connection to the central should be opened by the poller +* action +* pull: if the connection to the central should be opened by the poller A central with gorgoned should have the following modules: -* gorgone-acl -* gorgone-action -* gorgone-proxy -* gorgone-cron +* action +* proxy +* cron +* httpserver ================================================= I want to create a client. How should i proceed ? diff --git a/gorgone/docs/modules/newtest/exploitation.txt b/gorgone/docs/modules/newtest/exploitation.txt index 0decbf69b72..07297a0ff2d 100644 --- a/gorgone/docs/modules/newtest/exploitation.txt +++ b/gorgone/docs/modules/newtest/exploitation.txt @@ -5,9 +5,9 @@ Exploitation Generals Principles ------------------- -gorgone-newtest is a module program in charged to get back Newtest services. This program uses the newtest webservice in order to connect and get back the informations of one (or more) Newtest Management Console (NMC). +newtest is a module program in charged to get back Newtest services. This program uses the newtest webservice in order to connect and get back the informations of one (or more) Newtest Management Console (NMC). -By default "gorgone-newtest" starts X processes (it depends of the configuration) : +By default "newtest" starts X processes (it depends of the configuration) : Steps of operation for one process: @@ -15,16 +15,16 @@ Steps of operation for one process: - gets from the NMC the list of robots and scenarios: it creates it in centreon with centreon-clapi (It don't disable it or delete it in Centreon). - gets from the NMC the last status of scenarios: it submits through "centcore" the result. -gorgone-newtest necessitates the utilization of one (or more) NMC. +newtest necessitates the utilization of one (or more) NMC. Configuration -------------------- -The « gorgone-newtest » module is configured with gorgone configuration file :: +The « newtest » module is configured with gorgone configuration file :: modules: - - name: gorgonenewtest - module: modules::gorgonenewtest::hooks + - name: newtest + module: modules::newtest::hooks enable: 1 # in seconds - do purge for container also check_containers_time: 3600 @@ -73,7 +73,7 @@ You have to add at least one entry in the configuration. The meaning of attribut Troubleshooting --------------- -It is possible to get this kind of errors in the « logs » of « gorgone-newtest » :: +It is possible to get this kind of errors in the « logs » of « newtest » :: die: syntax error at line 1, column 0, byte 0 at /usr/lib/perl5/vendor_perl/5.8.8/i386-linux-thread-multi/XML/Parser.pm line 189 diff --git a/gorgone/docs/modules/newtest/installation.rst b/gorgone/docs/modules/newtest/installation.rst index aebbb1195a9..4c0a1a7130a 100644 --- a/gorgone/docs/modules/newtest/installation.rst +++ b/gorgone/docs/modules/newtest/installation.rst @@ -23,12 +23,12 @@ centreon-gorgone 1.0 Module location ``````````````` -The module "gorgonenewtest" daemon must be installed on Centreon Central server. Minimal used ressources are : +The module "newtest" daemon must be installed on Centreon Central server. Minimal used ressources are : * RAM : 128 MB. * CPU : it depends the number of newtest scenarios. -Gorgone-newtest Installation - centos/rhel 7 systems +Module installation - centos/rhel 7 systems ==================================================== Requirements @@ -41,7 +41,7 @@ perl-SOAP-Lite 1.10 centreon base perl-TimeDate 2.30 redhat/centos base ======================= ===================== ====================== -gorgone-newtest Installation +newtest Installation ````````````````````````````````````````` -"gorgonenewtest" is an official gorgone module. No installation needed. +"newtest" is an official gorgone module. No installation needed. diff --git a/gorgone/modules/gorgoneaction/class.pm b/gorgone/modules/core/action/class.pm similarity index 88% rename from gorgone/modules/gorgoneaction/class.pm rename to gorgone/modules/core/action/class.pm index 9e135cf527b..9e4638970d4 100644 --- a/gorgone/modules/gorgoneaction/class.pm +++ b/gorgone/modules/core/action/class.pm @@ -18,7 +18,9 @@ # limitations under the License. # -package modules::gorgoneaction::class; +package modules::core::action::class; + +use base qw(centreon::gorgone::module); use strict; use warnings; @@ -28,11 +30,12 @@ use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); my %handlers = (TERM => {}, HUP => {}); -my ($connector, $socket); +my ($connector); sub new { my ($class, %options) = @_; $connector = {}; + $connector->{internal_socket} = undef; $connector->{logger} = $options{logger}; $connector->{config} = $options{config}; $connector->{config_core} = $options{config_core}; @@ -62,7 +65,7 @@ sub handle_HUP { sub handle_TERM { my $self = shift; - $self->{logger}->writeLogInfo("gorgone-action $$ Receiving order to stop..."); + $self->{logger}->writeLogInfo("[action] -class- $$ Receiving order to stop..."); $self->{stop} = 1; } @@ -112,7 +115,7 @@ sub action_command { centreon::gorgone::common::zmq_send_message( socket => $options{socket_log}, action => 'PUTLOG', - data => { code => 36, etime => time(), token => $options{token}, data => { message => "command '$options{data}->{command}' had finished", stdout => $stdout, exit_code => $return_code } }, + data => { code => 36, etime => time(), token => $options{token}, data => { message => "command '$options{data}->{command}' has finished", stdout => $stdout, exit_code => $return_code } }, json_encode => 1 ); return 0; @@ -156,7 +159,7 @@ sub action_enginecommand { return -1; } - $self->{logger}->writeLogDebug("gorgone-action: class: submit engine command '$options{data}->{command}'"); + $self->{logger}->writeLogDebug("[action] -class- Submit engine command '$options{data}->{command}'"); my $fh; eval { local $SIG{ALRM} = sub { die "Timeout command\n" }; @@ -168,7 +171,7 @@ sub action_enginecommand { }; if ($@) { close $fh if (defined($fh)); - $self->{logger}->writeLogError("gorgone-action: class: submit engine command '$options{data}->{command}' issue: $@"); + $self->{logger}->writeLogError("[action] -class- Submit engine command '$options{data}->{command}' issue: $@"); centreon::gorgone::common::zmq_send_message( socket => $options{socket_log}, action => 'PUTLOG', @@ -216,7 +219,7 @@ sub action_run { sub create_child { my ($self, %options) = @_; - $self->{logger}->writeLogInfo("Create gorgoneaction sub-process"); + $self->{logger}->writeLogInfo("[action] -class- Create module 'action' sub-process"); $options{message} =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; my ($action, $token) = ($1, $2); @@ -225,7 +228,7 @@ sub create_child { my $child_pid = fork(); if (!defined($child_pid)) { centreon::gorgone::common::zmq_send_message( - socket => $socket, + socket => $connector->{internal_socket}, action => 'PUTLOG', data => { code => 30, etime => time(), token => $token, data => { message => "cannot fork: $!" } }, json_encode => 1 @@ -238,7 +241,7 @@ sub create_child { exit(0); } else { centreon::gorgone::common::zmq_send_message( - socket => $socket, + socket => $connector->{internal_socket}, action => 'PUTLOG', data => { code => 31, etime => time(), token => $token, data => { message => "proceed action" } }, json_encode => 1 @@ -248,15 +251,15 @@ sub create_child { sub event { while (1) { - my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $socket); + my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $connector->{internal_socket}); - $connector->{logger}->writeLogDebug("gorgoneaction: class: $message"); + $connector->{logger}->writeLogDebug("[action] -class- Event: $message"); if ($message !~ /^\[ACK\]/) { $connector->create_child(message => $message); } - last unless (centreon::gorgone::common::zmq_still_read(socket => $socket)); + last unless (centreon::gorgone::common::zmq_still_read(socket => $connector->{internal_socket})); } } @@ -264,7 +267,7 @@ sub run { my ($self, %options) = @_; # Connect internal - $socket = centreon::gorgone::common::connect_com( + $connector->{internal_socket} = centreon::gorgone::common::connect_com( zmq_type => 'ZMQ_DEALER', name => 'gorgoneaction', logger => $self->{logger}, @@ -272,13 +275,13 @@ sub run { path => $self->{config_core}->{internal_com_path} ); centreon::gorgone::common::zmq_send_message( - socket => $socket, + socket => $connector->{internal_socket}, action => 'ACTIONREADY', data => { }, json_encode => 1 ); $self->{poll} = [ { - socket => $socket, + socket => $connector->{internal_socket}, events => ZMQ_POLLIN, callback => \&event, } @@ -287,8 +290,8 @@ sub run { # we try to do all we can my $rev = zmq_poll($self->{poll}, 5000); if ($rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("gorgone-action $$ has quit"); - zmq_close($socket); + $self->{logger}->writeLogInfo("[action] -class- $$ has quit"); + zmq_close($connector->{internal_socket}); exit(0); } } diff --git a/gorgone/modules/gorgoneaction/hooks.pm b/gorgone/modules/core/action/hooks.pm similarity index 82% rename from gorgone/modules/gorgoneaction/hooks.pm rename to gorgone/modules/core/action/hooks.pm index edf2001fca5..aa0ea35d8ba 100644 --- a/gorgone/modules/gorgoneaction/hooks.pm +++ b/gorgone/modules/core/action/hooks.pm @@ -18,20 +18,20 @@ # limitations under the License. # -package modules::gorgoneaction::hooks; +package modules::core::action::hooks; use warnings; use strict; use centreon::script::gorgonecore; -use modules::gorgoneaction::class; +use modules::core::action::class; -my $config_core; -my $config; -my $module_shortname = 'action'; -my $module_id = 'gorgoneaction'; -my $events = [ +my $NAME = 'action'; +my $EVENTS = [ { event => 'ACTIONREADY' }, ]; + +my $config_core; +my $config; my $action = {}; my $stop = 0; @@ -41,13 +41,13 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; if (!defined($config->{disable_command_event}) || $config->{disable_command_event} != 1) { - push @{$events}, { event => 'COMMAND', uri => '/command', method => 'POST' }; + push @{$EVENTS}, { event => 'COMMAND', uri => '/command', method => 'POST' }; } if (!defined($config->{disable_enginecommand_event}) || $config->{disable_enginecommand_event} != 1) { - push @{$events}, { event => 'ENGINECOMMAND', uri => '/enginecommand', method => 'POST' }; + push @{$EVENTS}, { event => 'ENGINECOMMAND', uri => '/enginecommand', method => 'POST' }; } - return ($events, $module_shortname, $module_id); + return ($NAME, $EVENTS); } sub init { @@ -64,7 +64,7 @@ sub routing { $data = JSON->new->utf8->decode($options{data}); }; if ($@) { - $options{logger}->writeLogError("Cannot decode json data: $@"); + $options{logger}->writeLogError("[action] -hooks- Cannot decode json data: $@"); centreon::gorgone::common::add_history( dbh => $options{dbh}, code => 30, token => $options{token}, @@ -102,7 +102,7 @@ sub gently { my (%options) = @_; $stop = 1; - $options{logger}->writeLogInfo("gorgone-action: Send TERM signal"); + $options{logger}->writeLogInfo("[action] -hooks- Send TERM signal"); if ($action->{running} == 1) { CORE::kill('TERM', $action->{pid}); } @@ -112,7 +112,7 @@ sub kill { my (%options) = @_; if ($action->{running} == 1) { - $options{logger}->writeLogInfo("gorgone-action: Send KILL signal for pool"); + $options{logger}->writeLogInfo("[action] -hooks- Send KILL signal for pool"); CORE::kill('KILL', $action->{pid}); } } @@ -146,11 +146,11 @@ sub check { sub create_child { my (%options) = @_; - $options{logger}->writeLogInfo("Create gorgoneaction process"); + $options{logger}->writeLogInfo("[action] -hooks- Create module 'action' process"); my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-action'; - my $module = modules::gorgoneaction::class->new( + my $module = modules::core::action::class->new( logger => $options{logger}, config_core => $config_core, config => $config, @@ -158,7 +158,7 @@ sub create_child { $module->run(); exit(0); } - $options{logger}->writeLogInfo("PID $child_pid gorgoneaction"); + $options{logger}->writeLogInfo("[action] -hooks- PID $child_pid (gorgone-action)"); $action = { pid => $child_pid, ready => 0, running => 1 }; } diff --git a/gorgone/modules/core/cron/class.pm b/gorgone/modules/core/cron/class.pm new file mode 100644 index 00000000000..f8483dc1f8f --- /dev/null +++ b/gorgone/modules/core/cron/class.pm @@ -0,0 +1,195 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package modules::core::cron::class; + +use base qw(centreon::gorgone::module); + +use strict; +use warnings; +use centreon::gorgone::common; +use centreon::misc::misc; +use ZMQ::LibZMQ4; +use ZMQ::Constants qw(:all); +use Schedule::Cron; + +my %handlers = (TERM => {}, HUP => {}); +my ($connector); + +sub new { + my ($class, %options) = @_; + $connector = {}; + $connector->{internal_socket} = undef; + $connector->{logger} = $options{logger}; + $connector->{config} = $options{config}; + $connector->{config_core} = $options{config_core}; + $connector->{stop} = 0; + + bless $connector, $class; + $connector->set_signal_handlers; + return $connector; +} + +sub set_signal_handlers { + my $self = shift; + + $SIG{TERM} = \&class_handle_TERM; + $handlers{TERM}->{$self} = sub { $self->handle_TERM() }; + $SIG{HUP} = \&class_handle_HUP; + $handlers{HUP}->{$self} = sub { $self->handle_HUP() }; +} + +sub handle_HUP { + my $self = shift; + $self->{reload} = 0; +} + +sub handle_TERM { + my $self = shift; + $self->{logger}->writeLogInfo("[cron] -class- $$ Receiving order to stop..."); + $self->{stop} = 1; +} + +sub class_handle_TERM { + foreach (keys %{$handlers{TERM}}) { + &{$handlers{TERM}->{$_}}(); + } +} + +sub class_handle_HUP { + foreach (keys %{$handlers{HUP}}) { + &{$handlers{HUP}->{$_}}(); + } +} + +sub action_listcron { + my ($self, %options) = @_; + + $options{token} = $self->generate_token() if (!defined($options{token})); + + $self->send_log(code => centreon::gorgone::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action listcron proceed' }); + $self->{logger}->writeLogDebug("[cron] -class- Cron list start"); + + my @cron_list; + eval { + my @results = $self->{cron}->list_entries(); + foreach my $cron (@results) { + push @cron_list, { %{$cron->{args}[0]->{definition}} }; + } + }; + if ($@) { + $self->{logger}->writeLogDebug("[cron] -class- Cron list failed"); + $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'action listcron failed' }); + return 1; + } + + $self->{logger}->writeLogDebug("[cron] -class- Cron list finish"); + $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => \@cron_list); + return 0; +} + +sub event { + while (1) { + my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $connector->{internal_socket}); + + $connector->{logger}->writeLogDebug("[cron] -class- Event: $message"); + if ($message =~ /^\[(.*?)\]/) { + if ((my $method = $connector->can('action_' . lc($1)))) { + $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; + my ($action, $token) = ($1, $2); + my $data = JSON::XS->new->utf8->decode($3); + $method->($connector, token => $token, data => $data); + } + } + + last unless (centreon::gorgone::common::zmq_still_read(socket => $connector->{internal_socket})); + } +} + +sub cron_sleep { + my $rev = zmq_poll($connector->{poll}, 1000); + if ($rev == 0 && $connector->{stop} == 1) { + $connector->{logger}->writeLogInfo("[cron] -class- $$ has quit"); + zmq_close($connector->{internal_socket}); + exit(0); + } +} + +sub dispatcher { + my ($options) = @_; + + $options->{logger}->writeLogInfo("[cron] -class- Launching job '" . $options->{definition}->{name} . "'"); + + centreon::gorgone::common::zmq_send_message( + socket => $options->{socket}, + action => 'COMMAND', + target => $options->{definition}->{target}, + data => { command => $options->{definition}->{command_line} }, + json_encode => 1 + ); + + my $poll = [ + { + socket => $options->{socket}, + events => ZMQ_POLLIN, + callback => \&event, + } + ]; + + my $rev = zmq_poll($poll, 5000); +} + +sub run { + my ($self, %options) = @_; + + # Connect internal + $connector->{internal_socket} = centreon::gorgone::common::connect_com( + zmq_type => 'ZMQ_DEALER', + name => 'gorgonecron', + logger => $self->{logger}, + type => $self->{config_core}->{internal_com_type}, + path => $self->{config_core}->{internal_com_path} + ); + centreon::gorgone::common::zmq_send_message( + socket => $connector->{internal_socket}, + action => 'CRONREADY', data => { }, + json_encode => 1 + ); + $connector->{poll} = [ + { + socket => $connector->{internal_socket}, + events => ZMQ_POLLIN, + callback => \&event, + } + ]; + + $self->{cron} = new Schedule::Cron(\&dispatcher, nostatus => 1, nofork => 1); + + foreach my $def (@{$self->{config}->{cron}}) { + $self->{cron}->add_entry($def->{timespec}, \&dispatcher, { socket => $connector->{internal_socket}, logger => $self->{logger}, definition => $def }); + } + + $self->{cron}->run(sleep => \&cron_sleep); + + zmq_close($connector->{internal_socket}); + exit(0); +} + +1; diff --git a/gorgone/modules/gorgonecron/hooks.pm b/gorgone/modules/core/cron/hooks.pm similarity index 81% rename from gorgone/modules/gorgonecron/hooks.pm rename to gorgone/modules/core/cron/hooks.pm index 595a957ca3b..98cb14e8eea 100644 --- a/gorgone/modules/gorgonecron/hooks.pm +++ b/gorgone/modules/core/cron/hooks.pm @@ -18,22 +18,22 @@ # limitations under the License. # -package modules::gorgonecron::hooks; +package modules::core::cron::hooks; use warnings; use strict; use centreon::script::gorgonecore; -use modules::gorgonecron::class; +use modules::core::cron::class; -my $config_core; -my $config; -my $module_shortname = 'cron'; -my $module_id = 'gorgonecron'; -my $events = [ +my $NAME = 'cron'; +my $EVENTS = [ { event => 'CRONREADY' }, { event => 'RELOADCRON', uri => '/reload', method => 'POST' }, { event => 'LISTCRON', uri => '/list', method => 'GET' }, ]; + +my $config_core; +my $config; my $cron = {}; my $stop = 0; @@ -42,7 +42,7 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; - return ($events, $module_shortname , $module_id); + return ($NAME, $EVENTS); } sub init { @@ -59,7 +59,7 @@ sub routing { $data = JSON->new->utf8->decode($options{data}); }; if ($@) { - $options{logger}->writeLogError("Cannot decode json data: $@"); + $options{logger}->writeLogError("[cron] -hooks- Cannot decode json data: $@"); centreon::gorgone::common::add_history( dbh => $options{dbh}, code => 10, token => $options{token}, @@ -85,8 +85,11 @@ sub routing { } centreon::gorgone::common::zmq_send_message( - socket => $options{socket}, identity => 'gorgonecron', - action => $options{action}, data => $options{data}, token => $options{token}, + socket => $options{socket}, + identity => 'gorgonecron', + action => $options{action}, + data => $options{data}, + token => $options{token}, ); } @@ -94,7 +97,7 @@ sub gently { my (%options) = @_; $stop = 1; - $options{logger}->writeLogInfo("gorgone-cron: Send TERM signal"); + $options{logger}->writeLogInfo("[cron] -hooks- Send TERM signal"); if ($cron->{running} == 1) { CORE::kill('TERM', $cron->{pid}); } @@ -104,7 +107,7 @@ sub kill { my (%options) = @_; if ($cron->{running} == 1) { - $options{logger}->writeLogInfo("gorgone-cron: Send KILL signal for pool"); + $options{logger}->writeLogInfo("[cron] -hooks- Send KILL signal for pool"); CORE::kill('KILL', $cron->{pid}); } } @@ -138,11 +141,11 @@ sub check { sub create_child { my (%options) = @_; - $options{logger}->writeLogInfo("Create gorgonecron process"); + $options{logger}->writeLogInfo("[cron] -hooks- Create module 'cron' process"); my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-cron'; - my $module = modules::gorgonecron::class->new( + my $module = modules::core::cron::class->new( logger => $options{logger}, config_core => $config_core, config => $config, @@ -150,7 +153,7 @@ sub create_child { $module->run(); exit(0); } - $options{logger}->writeLogInfo("PID $child_pid gorgonecron"); + $options{logger}->writeLogInfo("[cron] -hooks- PID $child_pid (gorgone-cron)"); $cron = { pid => $child_pid, ready => 0, running => 1 }; } diff --git a/gorgone/modules/gorgonehttpserver/class.pm b/gorgone/modules/core/httpserver/class.pm similarity index 88% rename from gorgone/modules/gorgonehttpserver/class.pm rename to gorgone/modules/core/httpserver/class.pm index 7ce241629cd..59cb1cd2880 100644 --- a/gorgone/modules/gorgonehttpserver/class.pm +++ b/gorgone/modules/core/httpserver/class.pm @@ -18,7 +18,7 @@ # limitations under the License. # -package modules::gorgonehttpserver::class; +package modules::core::httpserver::class; use strict; use warnings; @@ -33,13 +33,14 @@ use MIME::Base64; my $time = time(); my %handlers = (TERM => {}, HUP => {}); -my ($connector, $socket); +my ($connector); my %dispatch; sub new { my ($class, %options) = @_; $connector = {}; + $connector->{internal_socket} = undef; $connector->{logger} = $options{logger}; $connector->{config} = $options{config}; $connector->{config_core} = $options{config_core}; @@ -67,7 +68,7 @@ sub handle_HUP { sub handle_TERM { my $self = shift; - $self->{logger}->writeLogInfo("gorgone-action $$ Receiving order to stop..."); + $self->{logger}->writeLogInfo("[httpserver] -class- $$ Receiving order to stop..."); $self->{stop} = 1; } @@ -85,11 +86,11 @@ sub class_handle_HUP { sub event { while (1) { - my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $socket); + my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $connector->{internal_socket}); - $connector->{logger}->writeLogDebug("gorgonehttpserver: class: $message"); + $connector->{logger}->writeLogDebug("[httpserver] -class- Event: $message"); - last unless (centreon::gorgone::common::zmq_still_read(socket => $socket)); + last unless (centreon::gorgone::common::zmq_still_read(socket => $connector->{internal_socket})); } } @@ -104,7 +105,7 @@ sub run { my ($self, %options) = @_; # Connect internal - $socket = centreon::gorgone::common::connect_com( + $connector->{internal_socket} = centreon::gorgone::common::connect_com( zmq_type => 'ZMQ_DEALER', name => 'gorgonehttpserver', logger => $self->{logger}, @@ -112,19 +113,21 @@ sub run { path => $self->{config_core}->{internal_com_path} ); centreon::gorgone::common::zmq_send_message( - socket => $socket, + socket => $connector->{internal_socket}, action => 'HTTPSERVERREADY', data => { }, json_encode => 1 ); $self->{poll} = [ { - socket => $socket, + socket => $connector->{internal_socket}, events => ZMQ_POLLIN, callback => \&event, } ]; + my $rev = zmq_poll($self->{poll}, 4000); + $self->init_dispatch; # HTTP daemon @@ -144,7 +147,7 @@ sub run { while (my ($connection, $peer_addr) = $daemon->accept) { while (my $request = $connection->get_request) { - $connector->{logger}->writeLogInfo("gorgone-httpserver - " . $request->method . " '" . $request->uri->path . "'"); + $connector->{logger}->writeLogInfo("[httpserver] -class- " . $request->method . " '" . $request->uri->path . "'"); if ($self->authentication($request->header('Authorization'))) { # Check Basic authentication my ($root) = ($request->uri->path =~ /^(\/\w+)/); @@ -198,13 +201,13 @@ sub send_response { sub api_call { my ($self, $request) = @_; - require 'centreon/script/gorgoneapi.pm'; + require 'centreon/gorgone/api.pm'; my %params = $request->uri->query_form; - my $response = centreon::script::gorgoneapi::root( + my $response = centreon::gorgone::api::root( uri => $request->uri->path, params => \%params, method => $request->method, - socket => $socket, + socket => $connector->{internal_socket}, logger => $self->{logger}, modules_events => $self->{modules_events} ); diff --git a/gorgone/modules/gorgonehttpserver/hooks.pm b/gorgone/modules/core/httpserver/hooks.pm similarity index 84% rename from gorgone/modules/gorgonehttpserver/hooks.pm rename to gorgone/modules/core/httpserver/hooks.pm index e3ea7bebfe7..9a0df9267e4 100644 --- a/gorgone/modules/gorgonehttpserver/hooks.pm +++ b/gorgone/modules/core/httpserver/hooks.pm @@ -18,20 +18,20 @@ # limitations under the License. # -package modules::gorgonehttpserver::hooks; +package modules::core::httpserver::hooks; use warnings; use strict; use centreon::script::gorgonecore; -use modules::gorgonehttpserver::class; +use modules::core::httpserver::class; -my $config_core; -my $config; -my $module_shortname = 'httpserver'; -my $module_id = 'gorgonehttpserver'; -my $events = [ +my $NAME = 'httpserver'; +my $EVENTS = [ { event => 'HTTPSERVERREADY' }, ]; + +my $config_core; +my $config; my $httpserver = {}; my $stop = 0; @@ -40,7 +40,7 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; - return ($events, $module_shortname, $module_id); + return ($NAME, $EVENTS); } sub init { @@ -57,7 +57,7 @@ sub routing { $data = JSON->new->utf8->decode($options{data}); }; if ($@) { - $options{logger}->writeLogError("Cannot decode json data: $@"); + $options{logger}->writeLogError("[httpserver] -hooks- Cannot decode json data: $@"); centreon::gorgone::common::add_history( dbh => $options{dbh}, code => 10, @@ -97,7 +97,7 @@ sub gently { my (%options) = @_; $stop = 1; - $options{logger}->writeLogInfo("gorgone-httpserver: Send TERM signal"); + $options{logger}->writeLogInfo("[httpserver] -hooks- Send TERM signal"); if ($httpserver->{running} == 1) { CORE::kill('TERM', $httpserver->{pid}); } @@ -107,7 +107,7 @@ sub kill { my (%options) = @_; if ($httpserver->{running} == 1) { - $options{logger}->writeLogInfo("gorgone-httpserver: Send KILL signal for pool"); + $options{logger}->writeLogInfo("[httpserver] -hooks- Send KILL signal for pool"); CORE::kill('KILL', $httpserver->{pid}); } } @@ -141,11 +141,11 @@ sub check { sub create_child { my (%options) = @_; - $options{logger}->writeLogInfo("Create gorgonehttpserver process"); + $options{logger}->writeLogInfo("[httpserver] -hooks- Create module 'httpserver' process"); my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-httpserver'; - my $module = modules::gorgonehttpserver::class->new( + my $module = modules::core::httpserver::class->new( logger => $options{logger}, config_core => $config_core, config => $config, @@ -154,7 +154,7 @@ sub create_child { $module->run(); exit(0); } - $options{logger}->writeLogInfo("PID $child_pid gorgonehttpserver"); + $options{logger}->writeLogInfo("[httpserver] -hooks- PID $child_pid (gorgone-httpserver)"); $httpserver = { pid => $child_pid, ready => 0, running => 1 }; } diff --git a/gorgone/modules/gorgoneproxy/class.pm b/gorgone/modules/core/proxy/class.pm similarity index 87% rename from gorgone/modules/gorgoneproxy/class.pm rename to gorgone/modules/core/proxy/class.pm index 71ee6c63684..86f53ff26fb 100644 --- a/gorgone/modules/gorgoneproxy/class.pm +++ b/gorgone/modules/core/proxy/class.pm @@ -18,7 +18,7 @@ # limitations under the License. # -package modules::gorgoneproxy::class; +package modules::core::proxy::class; use strict; use warnings; @@ -62,7 +62,7 @@ sub handle_HUP { sub handle_TERM { my $self = shift; - $self->{logger}->writeLogInfo("gorgone-proxy $$ Receiving order to stop..."); + $self->{logger}->writeLogInfo("[proxy] -class- $$ Receiving order to stop..."); $self->{stop} = 1; } @@ -83,11 +83,17 @@ sub get_client_information { # TODO DATABASE or file maybe. hardcoded right now my $result = { - type => 1, target_type => 'tcp', target_path => 'localhost:5556', + type => 1, + target_type => 'tcp', + target_path => 'localhost:5556', server_pubkey => 'keys/poller/pubkey.crt', client_pubkey => 'keys/central/pubkey.crt', client_privkey => 'keys/central/privkey.pem', - cipher => 'Cipher::AES', keysize => '32', vector => '0123456789012345', class => undef, delete => 0 + cipher => 'Cipher::AES', + keysize => '32', + vector => '0123456789012345', + class => undef, + delete => 0 }; return $result; } @@ -106,7 +112,9 @@ sub read_message { centreon::gorgone::common::zmq_send_message( socket => $socket, - action => 'PONG', token => $token, target => '', + action => 'PONG', + token => $token, + target => '', data => { code => 0, data => { message => 'ping ok', action => 'ping', id => $client_identity } }, json_encode => 1 ); @@ -123,7 +131,9 @@ sub read_message { if (defined($data->{data}->{action}) && $data->{data}->{action} eq 'getlog') { centreon::gorgone::common::zmq_send_message( socket => $socket, - action => 'SETLOGS', token => $1, target => '', + action => 'SETLOGS', + token => $1, + target => '', data => $2 ); } @@ -181,12 +191,12 @@ sub proxy { ); if ($status != 0) { # error we put log and we close (TODO the log) - $connector->{logger}->writeLogError("gorgoneproxy: class: send message problem for '$target': $msg"); + $connector->{logger}->writeLogError("[proxy] -class- Send message problem for '$target': $msg"); $connector->{clients}->{$target}->{delete} = 1; } } - $connector->{logger}->writeLogDebug("gorgoneproxy: class: [action = $action] [token = $token] [target = $target] [data = $data]"); + $connector->{logger}->writeLogDebug("[proxy] -class- Send message: [action = $action] [token = $token] [target = $target] [data = $data]"); } sub event_internal { @@ -203,14 +213,16 @@ sub run { # Connect internal $socket = centreon::gorgone::common::connect_com( - zmq_type => 'ZMQ_DEALER', name => 'gorgoneproxy-' . $self->{pool_id}, + zmq_type => 'ZMQ_DEALER', + name => 'gorgoneproxy-' . $self->{pool_id}, logger => $self->{logger}, type => $self->{config_core}{internal_com_type}, path => $self->{config_core}{internal_com_path} ); centreon::gorgone::common::zmq_send_message( socket => $socket, - action => 'PROXYREADY', data => { pool_id => $self->{pool_id} }, + action => 'PROXYREADY', + data => { pool_id => $self->{pool_id} }, json_encode => 1 ); my $poll = { @@ -238,7 +250,7 @@ sub run { next if (!defined($rev)); if ($rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("gorgone-proxy $$ has quit"); + $self->{logger}->writeLogInfo("[proxy] -class- $$ has quit"); zmq_close($socket); exit(0); } diff --git a/gorgone/modules/gorgoneproxy/hooks.pm b/gorgone/modules/core/proxy/hooks.pm similarity index 88% rename from gorgone/modules/gorgoneproxy/hooks.pm rename to gorgone/modules/core/proxy/hooks.pm index 71ab7bed53d..6fed7b9c2ca 100644 --- a/gorgone/modules/gorgoneproxy/hooks.pm +++ b/gorgone/modules/core/proxy/hooks.pm @@ -18,19 +18,16 @@ # limitations under the License. # -package modules::gorgoneproxy::hooks; +package modules::core::proxy::hooks; use warnings; use strict; use centreon::script::gorgonecore; use centreon::gorgone::common; -use modules::gorgoneproxy::class; +use modules::core::proxy::class; -my $config_core; -my $config; -my $module_shortname = 'proxy'; -my $module_id = 'gorgoneproxy'; -my $events = [ +my $NAME = 'proxy'; +my $EVENTS = [ { event => 'PROXYREADY' }, { event => 'SETLOGS' }, # internal. Shouldn't be used by third party clients { event => 'PONG' }, # internal. Shouldn't be used by third party clients @@ -39,6 +36,9 @@ my $events = [ { event => 'ADDPOLLER', uri => '/poller', method => 'POST' }, ]; +my $config_core; +my $config; + my $synctime_error = 0; my $synctime_pollers = {}; # get last time retrieved my $synctime_lasttime; @@ -62,7 +62,7 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; - return ($events, $module_shortname , $module_id); + return ($NAME, $EVENTS); } sub init { @@ -90,11 +90,11 @@ sub routing { $data = JSON->new->utf8->decode($options{data}); }; if ($@) { - $options{logger}->writeLogError("Cannot decode json data: $@"); + $options{logger}->writeLogError("[proxy] -hooks- Cannot decode json data: $@"); centreon::gorgone::common::add_history( dbh => $options{dbh}, code => 20, token => $options{token}, - data => { message => 'gorgoneproxy: cannot decode json' }, + data => { message => 'proxy - cannot decode json' }, json_encode => 1 ); return undef; @@ -103,12 +103,12 @@ sub routing { if ($options{action} eq 'PONG') { return undef if (!defined($data->{data}->{id}) || $data->{data}->{id} eq ''); $last_pong->{$data->{data}->{id}} = time(); - $options{logger}->writeLogInfo("gorgone-proxy: pong received from '" . $data->{data}->{id} . "'"); + $options{logger}->writeLogInfo("[proxy] -hooks- Pong received from '" . $data->{data}->{id} . "'"); return undef; } if ($options{action} eq 'UNREGISTERNODE') { - $options{logger}->writeLogInfo("gorgone-proxy: poller '" . $data->{id} . "' is unregistered"); + $options{logger}->writeLogInfo("[proxy] -hooks- Poller '" . $data->{id} . "' is unregistered"); if (defined($register_pollers->{$data->{id}})) { delete $register_pollers->{$data->{id}}; delete $synctime_pollers->{$data->{id}}; @@ -117,7 +117,7 @@ sub routing { } if ($options{action} eq 'REGISTERNODE') { - $options{logger}->writeLogInfo("gorgone-proxy: poller '" . $data->{id} . "' is registered"); + $options{logger}->writeLogInfo("[proxy] -hooks- Poller '" . $data->{id} . "' is registered"); $register_pollers->{$data->{id}} = 1; $last_pong->{$data->{id}} = 0 if (!defined($last_pong->{$data->{id}})); if ($synctime_error == 0 && !defined($synctime_pollers->{$options{target}}) && @@ -142,7 +142,7 @@ sub routing { centreon::gorgone::common::add_history( dbh => $options{dbh}, code => 20, token => $options{token}, - data => { message => 'gorgoneproxy: need a valid poller id' }, + data => { message => 'proxy - need a valid poller id' }, json_encode => 1 ); return undef; @@ -153,7 +153,7 @@ sub routing { centreon::gorgone::common::add_history( dbh => $options{dbh}, code => 20, token => $options{token}, - data => { message => 'gorgoneproxy: problem to getlog' }, + data => { message => 'proxy - problem to getlog' }, json_encode => 1 ); return undef; @@ -163,7 +163,7 @@ sub routing { centreon::gorgone::common::add_history( dbh => $options{dbh}, code => 20, token => $options{token}, - data => { message => 'gorgoneproxy: getlog already in progress' }, + data => { message => 'proxy - getlog already in progress' }, json_encode => 1 ); return undef; @@ -172,7 +172,7 @@ sub routing { centreon::gorgone::common::add_history( dbh => $options{dbh}, code => 20, token => $options{token}, - data => { message => "gorgoneproxy: can't get log a ssh target" }, + data => { message => "proxy - can't get log a ssh target" }, json_encode => 1 ); return undef; @@ -196,7 +196,7 @@ sub routing { centreon::gorgone::common::add_history( dbh => $options{dbh}, code => 20, token => $options{token}, - data => { message => 'gorgoneproxy: still none ready' }, + data => { message => 'proxy - still none ready' }, json_encode => 1 ); return undef; @@ -222,7 +222,7 @@ sub gently { $stop = 1; foreach my $pool_id (keys %{$pools}) { - $options{logger}->writeLogInfo("gorgone-proxy: Send TERM signal for pool '" . $pool_id . "'"); + $options{logger}->writeLogInfo("[proxy] -hooks- Send TERM signal for pool '" . $pool_id . "'"); if ($pools->{$pool_id}->{running} == 1) { CORE::kill('TERM', $pools->{$pool_id}->{pid}); } @@ -234,7 +234,7 @@ sub kill { foreach (keys %{$pools}) { if ($pools->{$_}->{running} == 1) { - $options{logger}->writeLogInfo("gorgone-proxy: Send KILL signal for pool '" . $_ . "'"); + $options{logger}->writeLogInfo("[proxy] -hooks- Send KILL signal for pool '" . $_ . "'"); CORE::kill('KILL', $pools->{$_}->{pid}); } } @@ -274,7 +274,7 @@ sub check { centreon::gorgone::common::add_history( dbh => $options{dbh}, code => 20, - data => { message => "gorgoneproxy: getlog in timeout for '$_'" }, + data => { message => "proxy - getlog in timeout for '$_'" }, json_encode => 1 ); $synctime_pollers->{$_}->{in_progress} = 0; @@ -291,7 +291,7 @@ sub check { if ($stop == 0 && time() - $ping_time > $ping_option) { - $options{logger}->writeLogInfo("gorgone-proxy: send pings"); + $options{logger}->writeLogInfo("[proxy] -hooks- Send pings"); $ping_time = time(); ping_send(dbh => $options{dbh}); } @@ -307,7 +307,7 @@ sub setlogs { centreon::gorgone::common::add_history( dbh => $options{dbh}, code => 20, token => $options{token}, - data => { message => 'gorgoneproxy: need a id to setlogs' }, + data => { message => 'proxy - need a id to setlogs' }, json_encode => 1 ); return undef; @@ -316,13 +316,13 @@ sub setlogs { centreon::gorgone::common::add_history( dbh => $options{dbh}, code => 20, token => $options{token}, - data => { message => 'gorgoneproxy: skip setlogs response. Maybe too much time to get response. Retry' }, + data => { message => 'proxy - skip setlogs response. Maybe too much time to get response. Retry' }, json_encode => 1 ); return undef; } - $options{logger}->writeLogInfo("gorgoneproxy: hooks: received setlogs for '$options{data}->{data}->{id}'"); + $options{logger}->writeLogInfo("[proxy] -hooks- Received setlogs for '$options{data}->{data}->{id}'"); $synctime_pollers->{$options{data}->{data}->{id}}->{in_progress} = 0; @@ -446,11 +446,11 @@ sub rr_pool { sub create_child { my (%options) = @_; - $options{logger}->writeLogInfo("Create gorgoneproxy for pool id '" . $options{pool_id} . "'"); + $options{logger}->writeLogInfo("[proxy] -hooks- Create module 'proxy' child process for pool id '" . $options{pool_id} . "'"); my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-proxy'; - my $module = modules::gorgone::class->new( + my $module = modules::core::proxy::class->new( logger => $options{logger}, config_core => $config_core, config => $config, @@ -460,7 +460,7 @@ sub create_child { $module->run(); exit(0); } - $options{logger}->writeLogInfo("PID $child_pid gorgoneproxy for pool id '" . $options{pool_id} . "'"); + $options{logger}->writeLogInfo("[proxy] -hooks- PID $child_pid (gorgone-proxy) for pool id '" . $options{pool_id} . "'"); $pools->{$options{pool_id}} = { pid => $child_pid, ready => 0, running => 1 }; $pools_pid->{$child_pid} = $options{pool_id}; } @@ -478,7 +478,7 @@ sub pull_request { centreon::gorgone::common::add_history( dbh => $options{dbh}, code => 20, token => $options{token}, - data => { message => "gorgoneproxy: node '" . $options{target} . "' had never been connected" }, + data => { message => "proxy - node '" . $options{target} . "' had never been connected" }, json_encode => 1 ); return undef; diff --git a/gorgone/modules/gorgonepull/hooks.pm b/gorgone/modules/core/pull/hooks.pm similarity index 88% rename from gorgone/modules/gorgonepull/hooks.pm rename to gorgone/modules/core/pull/hooks.pm index 9ae54a2b5d5..000493f4df3 100644 --- a/gorgone/modules/gorgonepull/hooks.pm +++ b/gorgone/modules/core/pull/hooks.pm @@ -18,17 +18,17 @@ # limitations under the License. # -package modules::gorgonepull::hooks; +package modules::core::pull::hooks; use warnings; use strict; use centreon::gorgone::clientzmq; +my $NAME = 'pull'; +my $EVENTS = []; + my $config_core; my $config; -my $module_shortname = 'pull'; -my $module_id = 'gorgonepull'; -my $events = []; my $stop = 0; my $client; my $socket_to_internal; @@ -39,7 +39,7 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; - return ($events, $module_shortname , $module_id); + return ($NAME, $EVENTS); } sub init { @@ -48,7 +48,8 @@ sub init { $logger = $options{logger}; # Connect internal $socket_to_internal = centreon::gorgone::common::connect_com( - zmq_type => 'ZMQ_DEALER', name => 'gorgonepull', + zmq_type => 'ZMQ_DEALER', + name => 'gorgonepull', logger => $options{logger}, type => $config_core->{internal_com_type}, path => $config_core->{internal_com_path}, @@ -69,8 +70,11 @@ sub init { ); $client->init(callback => \&read_message); - $client->send_message(action => 'REGISTERNODE', data => { id => $config_core->{id} }, - json_encode => 1); + $client->send_message( + action => 'REGISTERNODE', + data => { id => $config_core->{id} }, + json_encode => 1 + ); centreon::gorgone::common::add_zmq_pollin( socket => $socket_to_internal, callback => \&from_router, @@ -148,7 +152,7 @@ sub from_router { my $message = transmit_back(message => centreon::gorgone::common::zmq_dealer_read_message(socket => $socket_to_internal)); # Only send back SETLOGS and PONG if (defined($message)) { - $logger->writeLogDebug("gorgone-pull: hook: read message from internal: $message"); + $logger->writeLogDebug("[pull] -hooks- Read message from internal: $message"); $client->send_message(message => $message); } last unless (centreon::gorgone::common::zmq_still_read(socket => $socket_to_internal)); @@ -163,7 +167,7 @@ sub read_message { return undef; } - $logger->writeLogDebug("gorgone-pull: hook: read message from external: $options{data}"); + $logger->writeLogDebug("[pull] -hooks- Read message from external: $options{data}"); centreon::gorgone::common::zmq_send_message( socket => $socket_to_internal, message => $options{data} diff --git a/gorgone/modules/gorgoneacl/class.pm b/gorgone/modules/gorgoneacl/class.pm deleted file mode 100644 index eea01a14c39..00000000000 --- a/gorgone/modules/gorgoneacl/class.pm +++ /dev/null @@ -1,442 +0,0 @@ -# -# Copyright 2019 Centreon (http://www.centreon.com/) -# -# Centreon is a full-fledged industry-strength solution that meets -# the needs in IT infrastructure and application monitoring for -# service performance. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -package modules::gorgoneacl::class; - -use strict; -use warnings; -use centreon::gorgone::common; -use ZMQ::LibZMQ4; -use ZMQ::Constants qw(:all); -use JSON; -use centreon::misc::objects::organization; -use centreon::misc::objects::host; -use centreon::misc::objects::object; -use Data::Dumper; - -my %handlers = (TERM => {}, HUP => {}); -my ($connector, $socket); - -sub new { - my ($class, %options) = @_; - $connector = {}; - $connector->{logger} = $options{logger}; - $connector->{organization_id} = $options{organization_id}; - $connector->{config} = $options{config}; - $connector->{config_core} = $options{config_core}; - $connector->{config_db_centreon} = $options{config_db_centreon}; - $connector->{stop} = 0; - - bless $connector, $class; - $connector->set_signal_handlers; - return $connector; -} - -sub set_signal_handlers { - my $self = shift; - - $SIG{TERM} = \&class_handle_TERM; - $handlers{TERM}->{$self} = sub { $self->handle_TERM() }; - $SIG{HUP} = \&class_handle_HUP; - $handlers{HUP}->{$self} = sub { $self->handle_HUP() }; -} - -sub handle_HUP { - my $self = shift; - $self->{reload} = 0; -} - -sub handle_TERM { - my $self = shift; - $self->{logger}->writeLogInfo("gorgone-acl $$ Receiving order to stop..."); - $self->{stop} = 1; -} - -sub class_handle_TERM { - foreach (keys %{$handlers{TERM}}) { - &{$handlers{TERM}->{$_}}(); - } -} - -sub class_handle_HUP { - foreach (keys %{$handlers{HUP}}) { - &{$handlers{HUP}->{$_}}(); - } -} - -sub acl_resource_add_filters { - my ($self, %options) = @_; - - my %filters = ( hosts => [], services => [], extra_labels => [] ); - if (defined($options{resource_config}->{filter_host_tags}) and scalar(@{$options{resource_config}->{filter_host_tags}}) > 0) { - push @{$filters{hosts}}, 'cfg_hosts.host_id = cfg_thf.resource_id AND cfg_thf.tag_id IN (' . join(', ', @{$options{resource_config}->{filter_host_tags}}) . ')'; - push @{$filters{extra_tables}}, 'cfg_tags_hosts as cfg_thf'; - } - if (defined($options{resource_config}->{filter_environnement}) and scalar(@{$options{resource_config}->{filter_environnement}}) > 0) { - push @{$filters{hosts}}, "cfg_hosts.environment_id IN (" . join(', ', @{$options{resource_config}->{filter_environnement}}) . ")"; - # Not yet. - #push @{$filters{services}}, "cfg_services.environment_id IN (" . join(', ', @{$options{resource_config}->{filter_environnement}}) . ")"; - } - if (defined($options{resource_config}->{filter_pollers}) and scalar(@{$options{resource_config}->{filter_pollers}}) > 0) { - push @{$filters{hosts}}, "cfg_hosts.poller_id IN (" . join(', ', @{$options{resource_config}->{filter_pollers}}) . ")"; - } - if (defined($options{resource_config}->{filter_domains}) and scalar(@{$options{resource_config}->{filter_domains}}) > 0) { - push @{$filters{services}}, "cfg_services.domain_id IN (" . join(', ', @{$options{resource_config}->{filter_domains}}) . ")"; - } - if (defined($options{resource_config}->{filter_service_tags}) and scalar(@{$options{resource_config}->{filter_service_tags}}) > 0) { - push @{$filters{services}}, 'cfg_services.service_id = cfg_tsfilter.resource_id AND cfg_tsfilter.tag_id IN (' . join(', ', @{$options{resource_config}->{filter_service_tags}}) . ')'; - push @{$filters{extra_tables}}, 'cfg_tags_services as cfg_tsfilter'; - } - - foreach (keys %filters) { - $options{filters}->{$_} = ' AND ' . join(' AND ', @{$filters{$_}}) if (scalar(@{$filters{$_}}) > 0); - } -} - -sub acl_resource_list_hs { - my ($self, %options) = @_; - - my $filters = { hosts => '', services => '', extra_tables => '' }; - my $requests = []; - $self->acl_resource_add_filters(filters => $filters, %options); - # Manage hosts - if (defined($options{resource_config}->{host_alls}) && $options{resource_config}->{host_alls} == 1) { - push @{$requests}, "(SELECT host_id, service_id FROM cfg_hosts, cfg_hosts_services_relations, cfg_services" . $filters->{extra_tables} . ' WHERE ' . - 'cfg_hosts.organization_id = ' . $self->{organization_id} . $filters->{hosts} . - ' AND cfg_hosts.host_id = cfg_hosts_services_relations.host_host_id AND cfg_hosts_services_relations.service_service_id = cfg_services.service_id' . - $filters->{services} . ')'; - } else { - if (defined($options{resource_config}->{host_ids}) and scalar(@{$options{resource_config}->{host_ids}}) > 0) { - push @{$requests}, "(SELECT host_id, service_id FROM cfg_hosts, cfg_hosts_services_relations, cfg_services" . $filters->{extra_tables} . ' WHERE ' . - 'cfg_hosts.organization_id = ' . $self->{organization_id} . ' AND cfg_hosts.host_id IN (' . join(', ', @{$options{resource_config}->{host_ids}}) . ') ' . $filters->{hosts} . - ' AND cfg_hosts.host_id = cfg_hosts_services_relations.host_host_id AND cfg_hosts_services_relations.service_service_id = cfg_services.service_id' . - $filters->{services} . ')'; - } - if (defined($options{resource_config}->{host_tags}) and scalar(@{$options{resource_config}->{host_tags}}) > 0) { - push @{$requests}, "(SELECT host_id, service_id FROM cfg_hosts, cfg_hosts_services_relations, cfg_services, cfg_tags_hosts" . $filters->{extra_tables} . ' WHERE ' . - 'cfg_hosts.organization_id = ' . $self->{organization_id} . ' AND cfg_hosts.host_id = cfg_tags_hosts.resource_id AND cfg_tags_hosts.tag_id IN (' . join(', ', @{$options{resource_config}->{host_tags}}) . ')' . $filters->{hosts} . - ' AND cfg_hosts.host_id = cfg_hosts_services_relations.host_host_id AND cfg_hosts_services_relations.service_service_id = cfg_services.service_id' . - $filters->{services} . ')'; - } - } - - # Manage services - if (defined($options{resource_config}->{service_ids}) and scalar(@{$options{resource_config}->{service_ids}}) > 0) { - push @{$requests}, "(SELECT host_id, service_id FROM cfg_hosts, cfg_hosts_services_relations, cfg_services" . $filters->{extra_tables} . ' WHERE ' . - 'cfg_services.organization_id = ' . $self->{organization_id} . ' AND cfg_services.service_id IN (' . join(', ', @{$options{resource_config}->{service_ids}}) . ') ' . $filters->{services} . - ' AND cfg_services.service_id = cfg_hosts_services_relations.service_service_id AND cfg_hosts_services_relations.host_id = cfg_hosts.host_id' . - $filters->{hosts} . ')'; - } - if (defined($options{resource_config}->{service_tags}) and scalar(@{$options{resource_config}->{service_tags}}) > 0) { - push @{$requests}, "(SELECT host_id, service_id FROM cfg_hosts, cfg_hosts_services_relations, cfg_services, cfg_tags_services" . $filters->{extra_tables} . ' WHERE ' . - 'cfg_services.organization_id = ' . $self->{organization_id} . ' AND cfg_services.service_id = cfg_tags_services.resource_id AND cfg_tags_services.tag_id IN (' . join(', ', @{$options{resource_config}->{service_tags}}) . ')' . $filters->{services} . - ' AND cfg_services.service_id = cfg_hosts_services_relations.service_service_id AND cfg_hosts_services_relations.host_id = cfg_hosts.host_id' . - $filters->{hosts} . ')'; - } - - $self->{logger}->writeLogDebug("gorgoneacl: request: " . join(' UNION ALL ', @{$requests})); - return 2 if (scalar(@{$requests}) == 0); - return $self->{class_object}->custom_execute(request => join(' UNION ALL ', @{$requests}), mode => 0); -} - -sub set_default_resource_config { - my ($self, %options) = @_; - - $options{config}->{$options{acl_resource_id}} = { - host_ids => [], host_alls => 0, host_tags => [], - service_ids => [], service_tags => [], - filter_domains => [], filter_pollers => [], - filter_environnement => [], - filter_host_tags => [], filter_service_tags => [] - }; -} - -sub acl_get_resources_config { - my ($self, %options) = @_; - - # filter - my $filter = 'cfg_acl_resources.organization_id = ' . $self->{organization_id}; - if (defined($options{acl_resource_id})) { - $filter .= ' AND cfg_acl_resources.acl_resource_id = ' . $options{acl_resource_id}; - } - - my $resource_configs = {}; - - # Get all_hosts - my $request = 'SELECT cfg_acl_resources.acl_resource_id, all_hosts FROM cfg_acl_resources, cfg_acl_resources_hosts_params WHERE ' . $filter . ' AND cfg_acl_resources.acl_resource_id = cfg_acl_resources_hosts_params.acl_resource_id'; - my ($status, $datas) = $self->{class_object}->custom_execute(request => $request, mode => 2); - return 1 if ($status == -1); - foreach (@{$datas}) { - $self->set_default_resource_config(config => $resource_configs, acl_resource_id => $$_[0]) if (!defined($resource_configs->{$$_[0]})); - $resource_configs->{$$_[0]}->{host_alls} = $$_[1]; - } - - # Get hosts - $request = 'SELECT cfg_acl_resources.acl_resource_id, host_id, type FROM cfg_acl_resources, cfg_acl_resources_hosts_relations WHERE ' . $filter . ' AND cfg_acl_resources.acl_resource_id = cfg_acl_resources_hosts_relations.acl_resource_id'; - ($status, $datas) = $self->{class_object}->custom_execute(request => $request, mode => 2); - return 1 if ($status == -1); - foreach (@{$datas}) { - $self->set_default_resource_config(config => $resource_configs, acl_resource_id => $$_[0]) if (!defined($resource_configs->{$$_[0]})); - # 0 = inclus, 2 => exclude (pas de filtre pour les hôtes) - push @{$resource_configs->{$$_[0]}->{host_ids}}, $$_[1] if ($$_[2] == 0); - } - - # Get host tags - $request = 'SELECT cfg_acl_resources.acl_resource_id, tag_id, type FROM cfg_acl_resources, cfg_acl_resources_tags_hosts_relations WHERE ' . $filter . ' AND cfg_acl_resources.acl_resource_id = cfg_acl_resources_tags_hosts_relations.acl_resource_id'; - ($status, $datas) = $self->{class_object}->custom_execute(request => $request, mode => 2); - return 1 if ($status == -1); - foreach (@{$datas}) { - $self->set_default_resource_config(config => $resource_configs, acl_resource_id => $$_[0]) if (!defined($resource_configs->{$$_[0]})); - # 0 = inclus, 1 => filter, 2 => exclude - push @{$resource_configs->{$$_[0]}->{host_tags}}, $$_[1] if ($$_[2] == 0); - push @{$resource_configs->{$$_[0]}->{filter_host_tags}}, $$_[1] if ($$_[2] == 1); - } - - # Get services - $request = 'SELECT cfg_acl_resources.acl_resource_id, service_id, type FROM cfg_acl_resources, cfg_acl_resources_services_relations WHERE ' . $filter . ' AND cfg_acl_resources.acl_resource_id = cfg_acl_resources_services_relations.acl_resource_id'; - ($status, $datas) = $self->{class_object}->custom_execute(request => $request, mode => 2); - return 1 if ($status == -1); - foreach (@{$datas}) { - $self->set_default_resource_config(config => $resource_configs, acl_resource_id => $$_[0]) if (!defined($resource_configs->{$$_[0]})); - # 0 = inclus, 2 => exclude (pas de filtre pour les services) - push @{$resource_configs->{$$_[0]}->{service_ids}}, $$_[1] if ($$_[2] == 0); - } - - # Get Environment - $request = 'SELECT cfg_acl_resources.acl_resource_id, environment_id, type FROM cfg_acl_resources, cfg_acl_resources_environments_relations WHERE ' . $filter . ' AND cfg_acl_resources.acl_resource_id = cfg_acl_resources_environments_relations.acl_resource_id'; - ($status, $datas) = $self->{class_object}->custom_execute(request => $request, mode => 2); - return 1 if ($status == -1); - foreach (@{$datas}) { - $self->set_default_resource_config(config => $resource_configs, acl_resource_id => $$_[0]) if (!defined($resource_configs->{$$_[0]})); - # 0 = inclus, 2 => exclude (pas de filtre pour les services) - push @{$resource_configs->{$$_[0]}->{filter_environnement}}, $$_[1]; #if ($$_[2] == 0); - } - - # Get Domains - $request = 'SELECT cfg_acl_resources.acl_resource_id, domain_id, type FROM cfg_acl_resources, cfg_acl_resources_domains_relations WHERE ' . $filter . ' AND cfg_acl_resources.acl_resource_id = cfg_acl_resources_domains_relations.acl_resource_id'; - ($status, $datas) = $self->{class_object}->custom_execute(request => $request, mode => 2); - return 1 if ($status == -1); - foreach (@{$datas}) { - $self->set_default_resource_config(config => $resource_configs, acl_resource_id => $$_[0]) if (!defined($resource_configs->{$$_[0]})); - # 0 = inclus, 2 => exclude (pas de filtre pour les services) - push @{$resource_configs->{$$_[0]}->{filter_domains}}, $$_[1]; #if ($$_[2] == 0); - } - - return (0, $resource_configs); -} - -sub insert_execute { - my ($self, %options) = @_; - - if (!(my $rv = $options{sth}->execute(@{$options{bind}}))) { - $self->{logger}->writeLogError('SQL error: ' . $options{sth}->errstr); - $self->{db_centreon}->rollback(); - return 1; - } - - return 0; -} - -sub insert_result { - my ($self, %options) = @_; - my ($status, $sth); - - $self->{db_centreon}->transaction_mode(1); - if (defined($options{first_request})) { - ($status) = $self->{db_centreon}->query($options{first_request}); - if ($status == -1) { - $self->{db_centreon}->rollback(); - return 1; - } - } - if (!defined($options{hs_sth})) { - $self->{db_centreon}->commit(); - return 0; - } - - # In Oracle 11: IGNORE_ROW_ON_DUPKEY_INDEX - # MS SQL: IGNORE_DUP_KEY - # Postgres: ??? - my $prepare_str = '(' . $self->{organization_id} . ', ' . $options{acl_resource_id} . ', ?, ?)'; - for (my $i = 1; $i < $self->{config}->{sql_bulk}; $i++) { - $prepare_str .= ', (' . $self->{organization_id} . ', ' . $options{acl_resource_id} . ', ?, ?)'; - } - ($status, $sth) = $self->{db_centreon}->query('INSERT IGNORE INTO cfg_acl_resources_cache VALUES ' . $prepare_str, prepare_only => 1); - my $rows = []; - my %host_insert = (); - my $i = 0; - my @array_binds = (); - while (my $row = ( shift(@$rows) || # get row from cache, or reload cache: - shift(@{$rows = $options{hs_sth}->fetchall_arrayref(undef, $self->{config}->{sql_fetch})||[]})) ) { - - if (!defined($host_insert{$$row[0]})) { - $host_insert{$$row[0]} = 1; - push @array_binds, 1, $$row[0]; - $i++; - if ($i == $self->{config}->{sql_bulk}) { - return 1 if ($self->insert_execute(sth => $sth, bind => \@array_binds)); - $i = 0; - @array_binds = (); - } - } - - push @array_binds, 2, $$row[1]; - $i++; - if ($i == $self->{config}->{sql_bulk}) { - return 1 if ($self->insert_execute(sth => $sth, bind => \@array_binds)); - $i = 0; - @array_binds = (); - } - } - - if (scalar(@array_binds) > 0) { - my $query = 'INSERT IGNORE INTO cfg_acl_resources_cache VALUES (' . $self->{organization_id} . ', ' . $options{acl_resource_id} . ', ?, ?)'; - my $num = scalar(@array_binds) / 2; - for (my $i = 1; $i < $num; $i++) { - $query .= ', (' . $self->{organization_id} . ', ' . $options{acl_resource_id} . ', ?, ?)'; - } - ($status, $sth) = $self->{db_centreon}->query($query, prepare_only => 1); - return 1 if ($self->insert_execute(sth => $sth, bind => \@array_binds)); - } - - $self->{db_centreon}->commit(); - return 0; -} - -sub action_aclresync { - my ($self, %options) = @_; - my ($status, $sth, $resource_configs); - - $self->{logger}->writeLogDebug("gorgoneacl: organization $self->{organization_id} : begin resync"); - ($status, $resource_configs) = $self->acl_get_resources_config(); - if ($status) { - return 1; - } - - foreach my $acl_resource_id (sort keys %{$resource_configs}) { - $self->{logger}->writeLogDebug("gorgoneacl: organization $self->{organization_id} acl resource $acl_resource_id : begin resync"); - ($status, $sth) = $self->acl_resource_list_hs(resource_config => $resource_configs->{$acl_resource_id}); - if ($status == -1) { - return 1; - } - - $status = $self->insert_result(acl_resource_id => $acl_resource_id, hs_sth => $sth, first_request => "DELETE FROM cfg_acl_resources_cache WHERE organization_id = '" . $self->{organization_id} . "' AND acl_resource_id = " . $acl_resource_id); - $self->{logger}->writeLogDebug("gorgoneacl: organization $self->{organization_id} acl resource $acl_resource_id : finished resync (status: $status)"); - if ($status == 1) { - return 1; - } - } - - return 0; -} - -sub event { - while (1) { - my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $socket); - - $connector->{logger}->writeLogDebug("gorgoneacl: class: $message"); - if ($message =~ /^\[(.*?)\]/) { - if ((my $method = $connector->can('action_' . lc($1)))) { - $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; - my ($action, $token) = ($1, $2); - my $data = JSON->new->utf8->decode($3); - while ($method->($connector, token => $token, data => $data)) { - # We block until it's fixed!! - sleep(5); - } - } - } - - # Function examples - #print Data::Dumper::Dumper($connector->{class_organization}->get_organizations(mode => 1, keys => 'organization_id')); - # Get all hosts - #print Data::Dumper::Dumper($connector->{class_host}->get_hosts_by_organization(organization_id => $connector->{organization_id}, mode => 2, fields => ['host_id', 'host_name'])); - # Get all hosts with services - #print Data::Dumper::Dumper($connector->{class_host}->get_hosts_by_organization(organization_id => $connector->{organization_id}, with_services => 1, - # mode => 1, keys => ['host_id', 'service_id'], fields => ['host_id', 'host_name', 'service_id'])); - - # we try an open one: - # print Data::Dumper::Dumper($connector->{class_host}->get_hosts_by_organization(organization_id => $connector->{organization_id}, with_services => 1, - # mode => 1, keys => ['host_id', 'service_id'], fields => ['host_id', 'host_name', 'service_id'])); - - last unless (centreon::gorgone::common::zmq_still_read(socket => $socket)); - } -} - -sub run { - my ($self, %options) = @_; - my $on_demand = (defined($options{on_demand}) && $options{on_demand} == 1) ? 1 : 0; - my $on_demand_time = time(); - - # Database creation. We stay in the loop still there is an error - $self->{db_centreon} = centreon::misc::db->new( - dsn => $self->{config_db_centreon}->{dsn}, - user => $self->{config_db_centreon}->{username}, - password => $self->{config_db_centreon}->{password}, - force => 2, - logger => $self->{logger} - ); - ##### Load objects ##### - #$self->{class_organization} = centreon::misc::objects::organization->new(logger => $self->{logger}, db_centreon => $self->{db_centreon}); - #$self->{class_host} = centreon::misc::objects::host->new(logger => $self->{logger}, db_centreon => $self->{db_centreon}); - $self->{class_object} = centreon::misc::objects::object->new(logger => $self->{logger}, db_centreon => $self->{db_centreon}); - - # Connect internal - $socket = centreon::gorgone::common::connect_com( - zmq_type => 'ZMQ_DEALER', name => 'gorgoneacl-' . $self->{organization_id}, - logger => $self->{logger}, - type => $self->{config_core}->{internal_com_type}, - path => $self->{config_core}->{internal_com_path} - ); - centreon::gorgone::common::zmq_send_message(socket => $socket, - action => 'ACLREADY', data => { organization_id => $self->{organization_id} }, - json_encode => 1); - $self->{poll} = [ - { - socket => $socket, - events => ZMQ_POLLIN, - callback => \&event, - } - ]; - while (1) { - # we try to do all we can - my $rev = zmq_poll($self->{poll}, 5000); - if (defined($rev) && $rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("gorgone-acl $$ has quit"); - zmq_close($socket); - exit(0); - } - - # Check if we need to quit - if ($on_demand == 1) { - if (defined($rev) && $rev == 0) { - if (time() - $on_demand_time > $self->{config}->{on_demand_time}) { - $self->{logger}->writeLogInfo("gorgone-acl $$ has quit"); - zmq_close($socket); - exit(0); - } - } else { - $on_demand_time = time(); - } - } - } -} - -1; diff --git a/gorgone/modules/gorgoneacl/hooks.pm b/gorgone/modules/gorgoneacl/hooks.pm deleted file mode 100644 index 5c086ae8696..00000000000 --- a/gorgone/modules/gorgoneacl/hooks.pm +++ /dev/null @@ -1,246 +0,0 @@ -# -# Copyright 2019 Centreon (http://www.centreon.com/) -# -# Centreon is a full-fledged industry-strength solution that meets -# the needs in IT infrastructure and application monitoring for -# service performance. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -package modules::gorgoneacl::hooks; - -use warnings; -use strict; -use centreon::script::gorgonecore; -use modules::gorgoneacl::class; -use centreon::misc::db; - -my ($config_core, $config); -my $config_db_centreon; -my $module_id = 'gorgoneacl'; -my $events = [ - 'ACLREADY', - 'ACLPURGEORGANIZATION', 'ACLRESYNC', - 'ACLADDHOST', 'ACLDELHOST', 'ACLADDSERVICE', 'ACLDELSERVICE', - 'ACLUPDATETAG', 'ACLDELTAG', - 'ACLUPDATEDOMAIN', 'ACLDELDOMAIN', - 'ACLUPDATEENVIRONMENT', 'ACLDELENVIRONMENT', - 'ACLUPDATEPOLLER', 'ACLDELPOLLER', -]; - -my $last_organizations = {}; # Last values from centreon database -my $organizations = {}; -my $organizations_pid = {}; -my $stop = 0; -my $timer_check = time(); -my $config_check_organizations_time; -my $on_demand = 0; -my ($resync_auto_disable, $resync_time, $resync_random_windows) = (0, 28800, 7200); - -sub register { - my (%options) = @_; - - $config = $options{config}; - $config_core = $options{config_core}; - $config_db_centreon = $options{config_db_centreon}; - $config_check_organizations_time = defined($config->{check_organizations_time}) ? $config->{check_organizations_time} : 3600; - $on_demand = defined($config->{on_demand}) && $config->{on_demand} == 1 ? 1 : 0; - $resync_auto_disable = defined($config->{resync_auto_disable}) && $config->{resync_auto_disable} == 1 ? 1 : 0; - $resync_time = defined($config->{resync_time}) && $config->{resync_time} > 0 ? $config->{resync_time} : 28800; - $resync_random_windows = defined($config->{resync_random_windows}) && $config->{resync_random_windows} > 0 ? $config->{resync_random_windows} : 7200; - return ($events, $module_id); -} - -sub init { - my (%options) = @_; - - $last_organizations = get_organizations(logger => $options{logger}); - # We quit - return if ($on_demand == 1); - foreach my $organization_id (keys %{$last_organizations}) { - create_child(organization_id => $organization_id, logger => $options{logger}); - } -} - -sub routing { - my (%options) = @_; - - my $data; - eval { - $data = JSON->new->utf8->decode($options{data}); - }; - if ($@) { - $options{logger}->writeLogError("Cannot decode json data: $@"); - centreon::gorgone::common::add_history(dbh => $options{dbh}, - code => 100, token => $options{token}, - data => { message => 'gorgoneacl: cannot decode json' }, - json_encode => 1); - return undef; - } - - if ($options{action} eq 'ACLREADY') { - $organizations->{$data->{organization_id}}->{ready} = 1; - return undef; - } - - if (!defined($data->{organization_id}) || !defined($last_organizations->{$data->{organization_id}})) { - centreon::gorgone::common::add_history(dbh => $options{dbh}, - code => 100, token => $options{token}, - data => { message => 'gorgoneacl: need a valid organization id' }, - json_encode => 1); - return undef; - } - - if ($on_demand == 1) { - if (!defined($organizations->{$data->{organization_id}})) { - create_child(organization_id => $data->{organization_id}, logger => $options{logger}, on_demand => 1); - } - } - - if (centreon::script::gorgonecore::waiting_ready(ready => \$organizations->{$data->{organization_id}}->{ready}) == 0) { - centreon::gorgone::common::add_history(dbh => $options{dbh}, - code => 100, token => $options{token}, - data => { message => 'gorgoneacl: still no ready' }, - json_encode => 1); - return undef; - } - - centreon::gorgone::common::zmq_send_message(socket => $options{socket}, identity => 'gorgoneacl-' . $data->{organization_id}, - action => $options{action}, data => $options{data}, token => $options{token}, - ); -} - -sub gently { - my (%options) = @_; - - $stop = 1; - # They stop from themself in 'on_demand' mode - return if ($on_demand == 1); - foreach my $organization_id (keys %{$organizations}) { - $options{logger}->writeLogInfo("gorgone-acl: Send TERM signal for organization '" . $organization_id . "'"); - if ($organizations->{$organization_id}->{running} == 1) { - CORE::kill('TERM', $organizations->{$organization_id}->{pid}); - } - } -} - -sub kill_internal { - my (%options) = @_; - - foreach (keys %{$organizations}) { - if ($organizations->{$_}->{running} == 1) { - $options{logger}->writeLogInfo("gorgone-acl: Send KILL signal for organization '" . $_ . "'"); - CORE::kill('KILL', $organizations->{$_}->{pid}); - } - } -} - -sub kill { - my (%options) = @_; - - -} - -sub check { - my (%options) = @_; - - if ($timer_check - time() > $config_check_organizations_time) { - sync_organization_childs(logger => $options{logger}); - $timer_check = time(); - } - - my $count = 0; - foreach my $pid (keys %{$options{dead_childs}}) { - # Not me - next if (!defined($organizations_pid->{$pid})); - - # If someone dead, we recreate - delete $organizations->{$organizations_pid->{$pid}}; - delete $organizations_pid->{$pid}; - delete $options{dead_childs}->{$pid}; - if ($stop == 0 && $on_demand == 0) { - # Need to check if we need to recreate (can be a organization destruction)!!! - sync_organization_childs(logger => $options{logger}); - } - } - - foreach (keys %{$organizations}) { - if ($organizations->{$_}->{running} == 1) { - $count++; - # Test resync - if ($resync_auto_disable == 0 && defined($last_organizations->{$_}) && time() > $last_organizations->{$_}) { - routing(dbh => $options{dbh}, socket => $options{internal_socket}, logger => $options{logger}, - action => 'ACLRESYNC', data => '{ "organization_id": ' . $_ . ' } ', - token => 'internal_action_aclresync_' . $_ . ''); - $last_organizations->{$_} = time() + $resync_time + int(rand($resync_random_windows)); - } - } - } - - return $count; -} - -# Specific functions -sub get_organizations { - my (%options) = @_; - - my $db = centreon::misc::db->new( - dsn => $config_db_centreon->{dsn}, - user => $config_db_centreon->{username}, - password => $config_db_centreon->{password}, - force => 1, - logger => $options{logger} - ); - my ($status, $sth) = $db->query("SELECT organization_id FROM cfg_organizations WHERE active = '1'"); - my $org = {}; - while ((my $row = $sth->fetchrow_arrayref())) { - $org->{$$row[0]} = time() + $resync_time + int(rand($resync_random_windows)); - } - return $org; -} - -sub sync_organization_childs { - my (%options) = @_; - - $last_organizations = get_organizations(logger => $options{logger}); - foreach my $organization_id (keys %{$last_organizations}) { - if (!defined($organizations->{$organization_id}) && $on_demand == 0) { - create_child(organization_id => $organization_id, logger => $options{logger}); - } - } - - # TODO. Check Orga ID in my tables with a distinct to get what i need to destroy -} - -sub create_child { - my (%options) = @_; - - $options{logger}->writeLogInfo("Create gorgoneacl for organization id '" . $options{organization_id} . "'"); - my $child_pid = fork(); - if ($child_pid == 0) { - my $module = modules::gorgoneacl::class->new(logger => $options{logger}, - config_core => $config_core, - config => $config, - config_db_centreon => $config_db_centreon, - organization_id => $options{organization_id} - ); - $module->run(on_demand => $options{on_demand}); - exit(0); - } - $options{logger}->writeLogInfo("PID $child_pid for gorgoneacl for organization id '" . $options{organization_id} . "'"); - $organizations->{$options{organization_id}} = { pid => $child_pid, ready => 0, running => 1 }; - $organizations_pid->{$child_pid} = $options{organization_id}; -} - -1; diff --git a/gorgone/modules/gorgonecron/class.pm b/gorgone/modules/gorgonecron/class.pm deleted file mode 100644 index 7c6219dbeb7..00000000000 --- a/gorgone/modules/gorgonecron/class.pm +++ /dev/null @@ -1,136 +0,0 @@ -# -# Copyright 2019 Centreon (http://www.centreon.com/) -# -# Centreon is a full-fledged industry-strength solution that meets -# the needs in IT infrastructure and application monitoring for -# service performance. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -package modules::gorgonecron::class; - -use strict; -use warnings; -use centreon::gorgone::common; -use ZMQ::LibZMQ4; -use ZMQ::Constants qw(:all); -use Schedule::Cron; - -my %handlers = (TERM => {}, HUP => {}); -my ($connector, $socket); - -sub new { - my ($class, %options) = @_; - $connector = {}; - $connector->{logger} = $options{logger}; - $connector->{config} = $options{config}; - $connector->{config_core} = $options{config_core}; - $connector->{stop} = 0; - - bless $connector, $class; - $connector->set_signal_handlers; - return $connector; -} - -sub set_signal_handlers { - my $self = shift; - - $SIG{TERM} = \&class_handle_TERM; - $handlers{TERM}->{$self} = sub { $self->handle_TERM() }; - $SIG{HUP} = \&class_handle_HUP; - $handlers{HUP}->{$self} = sub { $self->handle_HUP() }; -} - -sub handle_HUP { - my $self = shift; - $self->{reload} = 0; -} - -sub handle_TERM { - my $self = shift; - $self->{logger}->writeLogInfo("gorgone-action $$ Receiving order to stop..."); - $self->{stop} = 1; -} - -sub class_handle_TERM { - foreach (keys %{$handlers{TERM}}) { - &{$handlers{TERM}->{$_}}(); - } -} - -sub class_handle_HUP { - foreach (keys %{$handlers{HUP}}) { - &{$handlers{HUP}->{$_}}(); - } -} - -sub event { - while (1) { - my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $socket); - - $connector->{logger}->writeLogDebug("gorgonecron: class: $message"); - - last unless (centreon::gorgone::common::zmq_still_read(socket => $socket)); - } -} - -sub cron_sleep { - my $rev = zmq_poll($connector->{poll}, 1000); - if ($rev == 0 && $connector->{stop} == 1) { - $connector->{logger}->writeLogInfo("gorgone-cron $$ has quit"); - zmq_close($socket); - exit(0); - } -} - -sub dispatcher { - my ($options) = @_; - - $options->{logger}->writeLogInfo('gorgone-cron Job is starting'); -} - -sub run { - my ($self, %options) = @_; - - # Connect internal - $socket = centreon::gorgone::common::connect_com( - zmq_type => 'ZMQ_DEALER', name => 'gorgonecron', - logger => $self->{logger}, - type => $self->{config_core}->{internal_com_type}, - path => $self->{config_core}->{internal_com_path} - ); - centreon::gorgone::common::zmq_send_message( - socket => $socket, - action => 'CRONREADY', data => { }, - json_encode => 1 - ); - $self->{poll} = [ - { - socket => $socket, - events => ZMQ_POLLIN, - callback => \&event, - } - ]; - my $cron = new Schedule::Cron(\&dispatcher, nostatus => 1, nofork => 1); - $cron->add_entry('* * * * *', \&dispatcher, { logger => $self->{logger}, plop => 1 }); - - # Each cron should have an ID in centreon DB. And like that, you can delete some not here. - #print Data::Dumper::Dumper($cron->list_entries()); - - $cron->run(sleep => \&cron_sleep); - zmq_close($socket); - exit(0); -} - -1; diff --git a/gorgone/modules/gorgonenewtest/class.pm b/gorgone/modules/newtest/class.pm similarity index 83% rename from gorgone/modules/gorgonenewtest/class.pm rename to gorgone/modules/newtest/class.pm index c4f1e1f16a4..1c425cfa6ac 100644 --- a/gorgone/modules/gorgonenewtest/class.pm +++ b/gorgone/modules/newtest/class.pm @@ -18,7 +18,7 @@ # limitations under the License. # -package modules::gorgonenewtest::class; +package modules::newtest::class; use base qw(centreon::gorgone::module); @@ -32,8 +32,8 @@ use ZMQ::Constants qw(:all); use MIME::Base64; use JSON::XS; use Data::Dumper; -use modules::gorgonenewtest::newtest::stubs::ManagementConsoleService; -use modules::gorgonenewtest::newtest::stubs::errors; +use modules::newtest::libs::stubs::ManagementConsoleService; +use modules::newtest::libs::stubs::errors; use Date::Parse; my %handlers = (TERM => {}, HUP => {}); @@ -98,7 +98,7 @@ sub handle_HUP { sub handle_TERM { my $self = shift; - $self->{logger}->writeLogInfo("gorgone-newtest $$ Receiving order to stop..."); + $self->{logger}->writeLogInfo("[newtest] -class- $$ Receiving order to stop..."); $self->{stop} = 1; } @@ -189,12 +189,12 @@ sub get_poller_id { mode => 2 ); if ($status == -1) { - $self->{logger}->writeLogError("gorgone-newtest cannot get poller id for poller '" . $self->{poller_name} . "'."); + $self->{logger}->writeLogError("[newtest] -class- cannot get poller id for poller '" . $self->{poller_name} . "'."); return 1; } if (!defined($datas->[0])) { - $self->{logger}->writeLogError("gorgone-newtest cannot find poller id for poller '" . $self->{poller_name} . "'."); + $self->{logger}->writeLogError("[newtest] -class- cannot find poller id for poller '" . $self->{poller_name} . "'."); return 1; } @@ -219,7 +219,7 @@ sub get_centreondb_cache { mode => 2 ); if ($status == -1) { - $self->{logger}->writeLogError("gorgone-newtest cannot get robot/scenarios list from centreon db."); + $self->{logger}->writeLogError("[newtest] -class- cannot get robot/scenarios list from centreon db."); return 1; } @@ -245,17 +245,17 @@ sub get_centstoragedb_cache { mode => 2 ); if ($status == -1) { - $self->{logger}->writeLogError("gorgone-newtest cannot get robot/scenarios list from centstorage db."); + $self->{logger}->writeLogError("[newtest] -class- cannot get robot/scenarios list from centstorage db."); return 1; } foreach (@$datas) { if (!defined($self->{db_newtest}->{$_->[0]})) { - $self->{logger}->writeLogError("gorgone-newtest host '" . $_->[0] . "'is in censtorage DB but not in centreon config..."); + $self->{logger}->writeLogError("[newtest] -class- host '" . $_->[0] . "'is in censtorage DB but not in centreon config..."); next; } if (defined($_->[1]) && !defined($self->{db_newtest}->{$_->[0]}->{$_->[1]})) { - $self->{logger}->writeLogError("gorgone-newtest host scenario '" . $_->[0] . "/" . $_->[1] . "' is in censtorage DB but not in centreon config..."); + $self->{logger}->writeLogError("[newtest] -class- host scenario '" . $_->[0] . "/" . $_->[1] . "' is in censtorage DB but not in centreon config..."); next; } @@ -278,7 +278,7 @@ sub clapi_execute { wait_exit => 1, ); if ($lerror == -1 || ($exit_code >> 8) != 0) { - $self->{logger}->writeLogError("gorgone-newtest clapi execution problem for command $cmd : " . $stdout); + $self->{logger}->writeLogError("[newtest] -class- clapi execution problem for command $cmd : " . $stdout); return -1; } @@ -303,7 +303,7 @@ sub submit_external_cmd { wait_exit => 1 ); if ($lerror == -1 || ($exit_code >> 8) != 0) { - $self->{logger}->writeLogError("gorgone-newtest clapi execution problem for command $cmd : " . $stdout); + $self->{logger}->writeLogError("[newtest] -class- clapi execution problem for command $cmd : " . $stdout); return -1; } } @@ -313,29 +313,29 @@ sub push_config { my ($self, %options) = @_; if ($self->{must_push_config} == 1) { - $self->{logger}->writeLogInfo("gorgone-newtest generation config for '$self->{poller_name}':"); + $self->{logger}->writeLogInfo("[newtest] -class- generation config for '$self->{poller_name}':"); if ($self->clapi_execute(cmd => '-a POLLERGENERATE -v ' . $self->{poller_id}, timeout => $self->{clapi_generate_config_timeout}) != 0) { - $self->{logger}->writeLogError("gorgone-newtest generation config for '$self->{poller_name}': failed"); + $self->{logger}->writeLogError("[newtest] -class- generation config for '$self->{poller_name}': failed"); return ; } - $self->{logger}->writeLogInfo("gorgone-newtest generation config for '$self->{poller_name}': succeeded."); + $self->{logger}->writeLogInfo("[newtest] -class- generation config for '$self->{poller_name}': succeeded."); - $self->{logger}->writeLogInfo("gorgone-newtest move config for '$self->{poller_name}':"); + $self->{logger}->writeLogInfo("[newtest] -class- move config for '$self->{poller_name}':"); if ($self->clapi_execute(cmd => '-a CFGMOVE -v ' . $self->{poller_id}, timeout => $self->{clapi_timeout}) != 0) { - $self->{logger}->writeLogError("gorgone-newtest move config for '$self->{poller_name}': failed"); + $self->{logger}->writeLogError("[newtest] -class- move config for '$self->{poller_name}': failed"); return ; } - $self->{logger}->writeLogInfo("gorgone-newtest move config for '$self->{poller_name}': succeeded."); + $self->{logger}->writeLogInfo("[newtest] -class- move config for '$self->{poller_name}': succeeded."); - $self->{logger}->writeLogInfo("gorgone-newtest restart/reload config for '$self->{poller_name}':"); + $self->{logger}->writeLogInfo("[newtest] -class- restart/reload config for '$self->{poller_name}':"); if ($self->clapi_execute(cmd => '-a ' . $self->{clapi_action_applycfg} . ' -v ' . $self->{poller_id}, timeout => $self->{clapi_timeout}) != 0) { - $self->{logger}->writeLogError("gorgone-newtest restart/reload config for '$self->{poller_name}': failed"); + $self->{logger}->writeLogError("[newtest] -class- restart/reload config for '$self->{poller_name}': failed"); return ; } - $self->{logger}->writeLogInfo("gorgone-newtest restart/reload config for '$self->{poller_name}': succeeded."); + $self->{logger}->writeLogInfo("[newtest] -class- restart/reload config for '$self->{poller_name}': succeeded."); } } @@ -343,13 +343,13 @@ sub get_newtest_diagnostic { my ($self, %options) = @_; my $result = $self->{instance}->ListMessages('Instance', 30, 'Diagnostics', [$options{scenario}, $options{robot}]); - if (defined(my $com_error = modules::gorgonenewtest::newtest::stubs::errors::get_error())) { - $self->{logger}->writeLogError("gorgone-newtest newtest API error 'ListMessages' method: " . $com_error); + if (defined(my $com_error = modules::newtest::libs::stubs::errors::get_error())) { + $self->{logger}->writeLogError("[newtest] -class- newtest API error 'ListMessages' method: " . $com_error); return -1; } if (!(ref($result) && defined($result->{MessageItem}))) { - $self->{logger}->writeLogError("gorgone-newtest no diagnostic found for scenario: " . $options{scenario} . '/' . $options{robot}); + $self->{logger}->writeLogError("[newtest] -class- no diagnostic found for scenario: " . $options{scenario} . '/' . $options{robot}); return 1; } if (ref($result->{MessageItem}) eq 'HASH') { @@ -380,20 +380,20 @@ sub get_scenario_results { # Already test the robot but no response if (defined($self->{cache_robot_list_results}->{$options{robot}}) && !defined($self->{cache_robot_list_results}->{$options{robot}}->{ResultItem})) { - $self->{current_text} = sprintf("gorgone-newtest no result avaiblable for scenario '%s'", $options{scenario}); + $self->{current_text} = sprintf("[newtest] -class- no result avaiblable for scenario '%s'", $options{scenario}); $self->{current_status} = 3; return 1; } if (!defined($self->{cache_robot_list_results}->{$options{robot}})) { my $result = $self->{instance}->ListResults('Robot', 30, [$options{robot}]); - if (defined(my $com_error = modules::gorgonenewtest::newtest::stubs::errors::get_error())) { - $self->{logger}->writeLogError("gorgone-newtest newtest API error 'ListResults' method: " . $com_error); + if (defined(my $com_error = modules::newtest::libs::stubs::errors::get_error())) { + $self->{logger}->writeLogError("[newtest] -class- newtest API error 'ListResults' method: " . $com_error); return -1; } if (!(ref($result) && defined($result->{ResultItem}))) { $self->{cache_robot_list_results}->{$options{robot}} = {}; - $self->{logger}->writeLogError("gorgone-newtest no results found for robot: " . $options{robot}); + $self->{logger}->writeLogError("[newtest] -class- no results found for robot: " . $options{robot}); return 1; } @@ -427,12 +427,12 @@ sub get_scenario_results { id => $result->{Id} ); - $self->{logger}->writeLogInfo("gorgone-newtest result found for scenario: " . $options{scenario} . '/' . $options{robot}); + $self->{logger}->writeLogInfo("[newtest] -class- result found for scenario: " . $options{scenario} . '/' . $options{robot}); return 0; } } - $self->{logger}->writeLogError("gorgone-newtest no result found for scenario: " . $options{scenario} . '/' . $options{robot}); + $self->{logger}->writeLogError("[newtest] -class- no result found for scenario: " . $options{scenario} . '/' . $options{robot}); return 1; } @@ -440,13 +440,13 @@ sub get_newtest_extra_metrics { my ($self, %options) = @_; my $result = $self->{instance}->ListResultChildren($options{id}); - if (defined(my $com_error = modules::gorgonenewtest::newtest::stubs::errors::get_error())) { - $self->{logger}->writeLogError("gorgone-newtest newtest API error 'ListResultChildren' method: " . $com_error); + if (defined(my $com_error = modules::newtest::libs::stubs::errors::get_error())) { + $self->{logger}->writeLogError("[newtest] -class- newtest API error 'ListResultChildren' method: " . $com_error); return -1; } if (!(ref($result) && defined($result->{ResultItem}))) { - $self->{logger}->writeLogError("gorgone-newtest no extra metrics found for scenario: " . $options{scenario} . '/' . $options{robot}); + $self->{logger}->writeLogError("[newtest] -class- no extra metrics found for scenario: " . $options{scenario} . '/' . $options{robot}); return 1; } @@ -469,7 +469,7 @@ sub get_newtest_scenarios { $self->{instance}->proxy($self->{endpoint}, timeout => $self->{nmc_timeout}); }; if ($@) { - $self->{logger}->writeLogError('gorgone-newtest newtest proxy error: ' . $@); + $self->{logger}->writeLogError('[newtest] -class- newtest proxy error: ' . $@); return -1; } @@ -484,8 +484,8 @@ sub get_newtest_scenarios { 0, $self->{list_scenario_status}->{instances} ); - if (defined(my $com_error = modules::gorgonenewtest::newtest::stubs::errors::get_error())) { - $self->{logger}->writeLogError("gorgone-newtest newtest API error 'ListScenarioStatus' method: " . $com_error); + if (defined(my $com_error = modules::newtest::libs::stubs::errors::get_error())) { + $self->{logger}->writeLogError("[newtest] -class- newtest API error 'ListScenarioStatus' method: " . $com_error); return -1; } @@ -508,23 +508,23 @@ sub get_newtest_scenarios { # Add host config if (!defined($self->{db_newtest}->{$host_name})) { - $self->{logger}->writeLogInfo("gorgone-newtest create host '$host_name'"); + $self->{logger}->writeLogInfo("[newtest] -class- create host '$host_name'"); if ($self->clapi_execute(cmd => '-o HOST -a ADD -v "' . $host_name . ';' . $host_name . ';127.0.0.1;' . $self->{host_template} . ';' . $self->{poller_name} . ';"', timeout => $self->{clapi_timeout}) == 0) { $self->{db_newtest}->{$host_name} = {}; $self->{must_push_config} = 1; - $self->{logger}->writeLogInfo("gorgone-newtest create host '$host_name' succeeded."); + $self->{logger}->writeLogInfo("[newtest] -class- create host '$host_name' succeeded."); } } # Add service config if (defined($self->{db_newtest}->{$host_name}) && !defined($self->{db_newtest}->{$host_name}->{$service_name})) { - $self->{logger}->writeLogInfo("gorgone-newtest create service '$service_name' for host '$host_name':"); + $self->{logger}->writeLogInfo("[newtest] -class- create service '$service_name' for host '$host_name':"); if ($self->clapi_execute(cmd => '-o SERVICE -a ADD -v "' . $host_name . ';' . $service_name . ';' . $self->{service_template} . '"', timeout => $self->{clapi_timeout}) == 0) { $self->{db_newtest}->{$host_name}->{$service_name} = {}; $self->{must_push_config} = 1; - $self->{logger}->writeLogInfo("gorgone-newtest create service '$service_name' for host '$host_name' succeeded."); + $self->{logger}->writeLogInfo("[newtest] -class- create service '$service_name' for host '$host_name' succeeded."); $self->clapi_execute(cmd => '-o SERVICE -a setmacro -v "' . $host_name . ';' . $service_name . ';NEWTEST_MESSAGEID;"', timeout => $self->{clapi_timeout}); } @@ -533,7 +533,7 @@ sub get_newtest_scenarios { # Check if new message if (defined($self->{db_newtest}->{$host_name}->{$service_name}->{last_execution_time}) && $last_check <= $self->{db_newtest}->{$host_name}->{$service_name}->{last_execution_time}) { - $self->{logger}->writeLogInfo("gorgone-newtest skip: service '$service_name' for host '$host_name' already submitted."); + $self->{logger}->writeLogInfo("[newtest] -class- skip: service '$service_name' for host '$host_name' already submitted."); next; } @@ -634,7 +634,7 @@ sub run { $self->{class_object_centstorage} = centreon::misc::objects::object->new(logger => $self->{logger}, db_centreon => $self->{db_centstorage}); $self->{class_object_centreon} = centreon::misc::objects::object->new(logger => $self->{logger}, db_centreon => $self->{db_centreon}); $SOAP::Constants::PREFIX_ENV = 'SOAP-ENV'; - $self->{instance} = modules::gorgonenewtest::newtest::stubs::ManagementConsoleService->new(); + $self->{instance} = modules::newtest::libs::stubs::ManagementConsoleService->new(); # Connect internal $connector->{internal_socket} = centreon::gorgone::common::connect_com( @@ -659,7 +659,7 @@ sub run { # we try to do all we can my $rev = zmq_poll($self->{poll}, 5000); if (defined($rev) && $rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("gorgone-newtest $$ has quit"); + $self->{logger}->writeLogInfo("[newtest] -class- $$ has quit"); zmq_close($connector->{internal_socket}); zmq_close($self->{socket_log}) if (defined($self->{socket_log})); exit(0); diff --git a/gorgone/modules/gorgonenewtest/hooks.pm b/gorgone/modules/newtest/hooks.pm similarity index 84% rename from gorgone/modules/gorgonenewtest/hooks.pm rename to gorgone/modules/newtest/hooks.pm index d5ae440ed62..01f4e65cc5d 100644 --- a/gorgone/modules/gorgonenewtest/hooks.pm +++ b/gorgone/modules/newtest/hooks.pm @@ -18,23 +18,22 @@ # limitations under the License. # -package modules::gorgonenewtest::hooks; +package modules::newtest::hooks; use warnings; use strict; use JSON::XS; use centreon::script::gorgonecore; -use modules::gorgonenewtest::class; +use modules::newtest::class; -my ($config_core, $config); -my ($config_db_centreon, $config_db_centstorage); -my $module_shortname = 'newtest'; -my $module_id = 'gorgonenewtest'; -my $events = [ +my $NAME = 'newtest'; +my $EVENTS = [ { event => 'NEWTESTREADY' }, { event => 'NEWTESTRESYNC', uri => '/resync', method => 'GET' }, ]; +my ($config_core, $config); +my ($config_db_centreon, $config_db_centstorage); my $last_containers = {}; # Last values from config ini my $containers = {}; my $containers_pid = {}; @@ -50,7 +49,7 @@ sub register { $config_db_centstorage = $options{config_db_centstorage}; $config_db_centreon = $options{config_db_centreon}; $config_check_containers_time = defined($config->{check_containers_time}) ? $config->{check_containers_time} : 3600; - return ($events, $module_shortname , $module_id); + return ($NAME, $EVENTS); } sub init { @@ -116,7 +115,7 @@ sub gently { $stop = 1; foreach my $container_id (keys %$containers) { - $options{logger}->writeLogInfo("gorgone-newtest: Send TERM signal for container '" . $container_id . "'"); + $options{logger}->writeLogInfo("[newtest] -hooks- Send TERM signal for container '" . $container_id . "'"); if ($containers->{$container_id}->{running} == 1) { CORE::kill('TERM', $containers->{$container_id}->{pid}); } @@ -128,7 +127,7 @@ sub kill_internal { foreach (keys %$containers) { if ($containers->{$_}->{running} == 1) { - $options{logger}->writeLogInfo("gorgone-newtest: Send KILL signal for container '" . $_ . "'"); + $options{logger}->writeLogInfo("[newtest] -hooks- Send KILL signal for container '" . $_ . "'"); CORE::kill('KILL', $containers->{$_}->{pid}); } } @@ -175,15 +174,15 @@ sub get_containers { next if (!defined($_->{name}) || $_->{name} eq ''); if (!defined($_->{nmc_endpoint}) || $_->{nmc_endpoint} eq '') { - $options{logger}->writeLogError("gorgone-newtest: cannot load container '" . $_->{name} . "' - please set nmc_endpoint option"); + $options{logger}->writeLogError("[newtest] -hooks- cannot load container '" . $_->{name} . "' - please set nmc_endpoint option"); next; } if (!defined($_->{poller_name}) || $_->{poller_name} eq '') { - $options{logger}->writeLogError("gorgone-newtest: cannot load container '" . $_->{name} . "' - please set poller_name option"); + $options{logger}->writeLogError("[newtest] -hooks- cannot load container '" . $_->{name} . "' - please set poller_name option"); next; } if (!defined($_->{list_scenario_status}) || $_->{list_scenario_status} eq '') { - $options{logger}->writeLogError("gorgone-newtest: cannot load container '" . $_->{name} . "' - please set list_scenario_status option"); + $options{logger}->writeLogError("[newtest] -hooks- cannot load container '" . $_->{name} . "' - please set list_scenario_status option"); next; } @@ -192,7 +191,7 @@ sub get_containers { $list_scenario = JSON::XS->new->utf8->decode($_->{list_scenario_status}); }; if ($@) { - $options{logger}->writeLogError("gorgone-newtest: cannot load container '" . $_->{name} . "' - cannot decode list scenario option"); + $options{logger}->writeLogError("[newtest] -hooks- cannot load container '" . $_->{name} . "' - cannot decode list scenario option"); next; } @@ -235,7 +234,7 @@ sub sync_container_childs { next if (defined($last_containers->{$container_id})); if ($containers->{$container_id}->{running} == 1) { - $options{logger}->writeLogInfo("gorgone-newtest: Send KILL signal for container '" . $container_id . "'"); + $options{logger}->writeLogInfo("[newtest] -hooks- Send KILL signal for container '" . $container_id . "'"); CORE::kill('KILL', $containers->{$container_id}->{pid}); } @@ -247,13 +246,13 @@ sub sync_container_childs { sub create_child { my (%options) = @_; - $options{logger}->writeLogInfo("Create gorgone-newtest for container '" . $options{container_id} . "'"); + $options{logger}->writeLogInfo("[newtest] -hooks- Create 'gorgone-newtest' process for container '" . $options{container_id} . "'"); my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-newtest ' . $options{container_id}; - my $module = modules::gorgonenewtest::class->new( + my $module = modules::newtest::class->new( logger => $options{logger}, - module_id => $module_id, + module_id => $NAME, config_core => $config_core, config => $config, config_db_centreon => $config_db_centreon, @@ -264,7 +263,7 @@ sub create_child { $module->run(); exit(0); } - $options{logger}->writeLogInfo("PID $child_pid gorgone-newtest for container '" . $options{container_id} . "'"); + $options{logger}->writeLogInfo("[newtest] -hooks- PID $child_pid (gorgone-newtest) for container '" . $options{container_id} . "'"); $containers->{$options{container_id}} = { pid => $child_pid, ready => 0, running => 1 }; $containers_pid->{$child_pid} = $options{container_id}; } diff --git a/gorgone/modules/gorgonenewtest/newtest/stubs/ManagementConsoleService.pm b/gorgone/modules/newtest/libs/stubs/ManagementConsoleService.pm similarity index 98% rename from gorgone/modules/gorgonenewtest/newtest/stubs/ManagementConsoleService.pm rename to gorgone/modules/newtest/libs/stubs/ManagementConsoleService.pm index a8ce6ef6b9d..ce15680dd06 100644 --- a/gorgone/modules/gorgonenewtest/newtest/stubs/ManagementConsoleService.pm +++ b/gorgone/modules/newtest/libs/stubs/ManagementConsoleService.pm @@ -1,4 +1,4 @@ -package modules::gorgonenewtest::newtest::stubs::ManagementConsoleService; +package modules::newtest::libs::stubs::ManagementConsoleService; sub SOAP::Serializer::as_SearchMode { my $self = shift; @@ -307,7 +307,7 @@ GetLicenceOptionValue => { ); # end my %methods use SOAP::Lite; -use modules::gorgonenewtest::newtest::stubs::errors; +use modules::newtest::libs::stubs::errors; use Exporter; use Carp (); @@ -320,7 +320,7 @@ sub _call { my ($self, $method) = (shift, shift); my $name = UNIVERSAL::isa($method => 'SOAP::Data') ? $method->name : $method; my %method = %{$methods{$name}}; - $self->on_fault(\&modules::gorgonenewtest::newtest::stubs::errors::soapGetBad); + $self->on_fault(\&modules::newtest::libs::stubs::errors::soapGetBad); $self->proxy($method{endpoint} || Carp::croak "No server address (proxy) specified") unless $self->proxy; my @templates = @{$method{parameters}}; diff --git a/gorgone/modules/gorgonenewtest/newtest/stubs/errors.pm b/gorgone/modules/newtest/libs/stubs/errors.pm similarity index 89% rename from gorgone/modules/gorgonenewtest/newtest/stubs/errors.pm rename to gorgone/modules/newtest/libs/stubs/errors.pm index d3522252674..61082cddb8a 100644 --- a/gorgone/modules/gorgonenewtest/newtest/stubs/errors.pm +++ b/gorgone/modules/newtest/libs/stubs/errors.pm @@ -1,5 +1,5 @@ -package modules::gorgonenewtest::newtest::stubs::errors; +package modules::newtest::libs::stubs::errors; use strict; use warnings; diff --git a/gorgone/modules/gorgonenewtest/newtest/wsdl/newtest.wsdl b/gorgone/modules/newtest/libs/wsdl/newtest.wsdl similarity index 100% rename from gorgone/modules/gorgonenewtest/newtest/wsdl/newtest.wsdl rename to gorgone/modules/newtest/libs/wsdl/newtest.wsdl diff --git a/gorgone/modules/gorgonescom/class.pm b/gorgone/modules/scom/class.pm similarity index 93% rename from gorgone/modules/gorgonescom/class.pm rename to gorgone/modules/scom/class.pm index ef928562def..7c63e643076 100644 --- a/gorgone/modules/gorgonescom/class.pm +++ b/gorgone/modules/scom/class.pm @@ -18,7 +18,7 @@ # limitations under the License. # -package modules::gorgonescom::class; +package modules::scom::class; use base qw(centreon::gorgone::module); @@ -87,7 +87,7 @@ sub handle_HUP { sub handle_TERM { my $self = shift; - $self->{logger}->writeLogInfo("gorgone-scom $$ Receiving order to stop..."); + $self->{logger}->writeLogInfo("[scom] -class- $$ Receiving order to stop..."); $self->{stop} = 1; } @@ -107,13 +107,13 @@ sub http_check_error { my ($self, %options) = @_; if ($options{status} == 1) { - $self->{logger}->writeLogError("gorgone-scom: container $self->{container_id}: scom $options{method} issue"); + $self->{logger}->writeLogError("[scom] -class- Container $self->{container_id}: scom $options{method} issue"); return 1; } my $code = $self->{http}->get_code(); if ($code !~ /^2/) { - $self->{logger}->writeLogError("gorgone-scom: container $self->{container_id}: scom $options{method} issue - " . $self->{http}->get_message()); + $self->{logger}->writeLogError("[scom] -class- Container $self->{container_id}: scom $options{method} issue - " . $self->{http}->get_message()); return 1; } @@ -150,7 +150,7 @@ sub submit_external_cmd { wait_exit => 1 ); if ($lerror == -1 || ($exit_code >> 8) != 0) { - $self->{logger}->writeLogError("gorgone-scom cmd execution problem for command $options{cmd} : " . $stdout); + $self->{logger}->writeLogError("[scom] -class- Command execution problem for command $options{cmd} : " . $stdout); return -1; } @@ -177,7 +177,7 @@ sub scom_authenticate_1801 { if (defined($header) && $header =~ /SCOMSessionId=([^;]+);/i) { $connector->{scom_session_id} = $1; } else { - $self->{logger}->writeLogError("gorgone-scom: container $self->{container_id}: scom authenticate issue - error retrieving cookie"); + $self->{logger}->writeLogError("[scom] -class- Container $self->{container_id}: scom authenticate issue - error retrieving cookie"); return 1; } @@ -458,25 +458,25 @@ sub action_scomresync { $options{token} = $self->generate_token() if (!defined($options{token})); $self->send_log(code => centreon::gorgone::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action scomresync proceed' }); - $self->{logger}->writeLogDebug("gorgone-scom: container $self->{container_id}: begin resync"); + $self->{logger}->writeLogDebug("[scom] -class- Container $self->{container_id}: begin resync"); if ($self->get_realtime_slots()) { $self->send_log(code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot find realtime slots' }); - $self->{logger}->writeLogError("gorgone-scom: container $self->{container_id}: cannot find realtime slots"); + $self->{logger}->writeLogError("[scom] -class- Container $self->{container_id}: cannot find realtime slots"); return 1; } my $func = $self->get_method(method => 'get_realtime_scom_alerts'); if ($func->($self)) { $self->send_log(code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot get scom realtime alerts' }); - $self->{logger}->writeLogError("gorgone-scom: container $self->{container_id}: cannot get scom realtime alerts"); + $self->{logger}->writeLogError("[scom] -class- Container $self->{container_id}: cannot get scom realtime alerts"); return 1; } $self->sync_alerts(); $self->sync_acks(); - $self->{logger}->writeLogDebug("gorgone-scom: container $self->{container_id}: finish resync"); + $self->{logger}->writeLogDebug("[scom] -class- Container $self->{container_id}: finish resync"); $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => { message => 'action scomresync finished' }); return 0; } @@ -485,7 +485,7 @@ sub event { while (1) { my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $connector->{internal_socket}); - $connector->{logger}->writeLogDebug("gorgone-scom: class: $message"); + $connector->{logger}->writeLogDebug("[scom] -class- Event: $message"); if ($message =~ /^\[(.*?)\]/) { if ((my $method = $connector->can('action_' . lc($1)))) { $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; @@ -516,7 +516,8 @@ sub run { # Connect internal $connector->{internal_socket} = centreon::gorgone::common::connect_com( - zmq_type => 'ZMQ_DEALER', name => 'gorgonescom-' . $self->{container_id}, + zmq_type => 'ZMQ_DEALER', + name => 'gorgonescom-' . $self->{container_id}, logger => $self->{logger}, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} @@ -537,7 +538,7 @@ sub run { # we try to do all we can my $rev = zmq_poll($self->{poll}, 5000); if (defined($rev) && $rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("gorgone-scom $$ has quit"); + $self->{logger}->writeLogInfo("[scom] -class- $$ has quit"); zmq_close($connector->{internal_socket}); exit(0); } diff --git a/gorgone/modules/gorgonescom/hooks.pm b/gorgone/modules/scom/hooks.pm similarity index 85% rename from gorgone/modules/gorgonescom/hooks.pm rename to gorgone/modules/scom/hooks.pm index 74e839c6073..9d2445570d1 100644 --- a/gorgone/modules/gorgonescom/hooks.pm +++ b/gorgone/modules/scom/hooks.pm @@ -18,22 +18,21 @@ # limitations under the License. # -package modules::gorgonescom::hooks; +package modules::scom::hooks; use warnings; use strict; use centreon::script::gorgonecore; -use modules::gorgonescom::class; +use modules::scom::class; -my ($config_core, $config); -my $config_db_centstorage; -my $module_shortname = 'scom'; -my $module_id = 'gorgonescom'; -my $events = [ +my $NAME = 'scom'; +my $EVENTS = [ { event => 'SCOMREADY' }, { event => 'SCOMRESYNC', uri => '/resync', method => 'GET' }, ]; +my ($config_core, $config); +my $config_db_centstorage; my $last_containers = {}; # Last values from config ini my $containers = {}; my $containers_pid = {}; @@ -48,7 +47,7 @@ sub register { $config_core = $options{config_core}; $config_db_centstorage = $options{config_db_centstorage}; $config_check_containers_time = defined($config->{check_containers_time}) ? $config->{check_containers_time} : 3600; - return ($events, $module_shortname , $module_id); + return ($NAME, $EVENTS); } sub init { @@ -68,7 +67,7 @@ sub routing { $data = JSON->new->utf8->decode($options{data}); }; if ($@) { - $options{logger}->writeLogError("Cannot decode json data: $@"); + $options{logger}->writeLogError("[scom] -hooks- Cannot decode json data: $@"); centreon::gorgone::common::add_history( dbh => $options{dbh}, code => 200, token => $options{token}, @@ -114,7 +113,7 @@ sub gently { $stop = 1; foreach my $container_id (keys %$containers) { - $options{logger}->writeLogInfo("gorgone-scom: Send TERM signal for container '" . $container_id . "'"); + $options{logger}->writeLogInfo("[scom] -hooks- Send TERM signal for container '" . $container_id . "'"); if ($containers->{$container_id}->{running} == 1) { CORE::kill('TERM', $containers->{$container_id}->{pid}); } @@ -126,7 +125,7 @@ sub kill_internal { foreach (keys %$containers) { if ($containers->{$_}->{running} == 1) { - $options{logger}->writeLogInfo("gorgone-scom: Send KILL signal for container '" . $_ . "'"); + $options{logger}->writeLogInfo("[scom] -hooks- Send KILL signal for container '" . $_ . "'"); CORE::kill('KILL', $containers->{$_}->{pid}); } } @@ -174,15 +173,15 @@ sub get_containers { next if (!defined($_->{name}) || $_->{name} eq ''); if (!defined($_->{url}) || $_->{url} eq '') { - $options{logger}->writeLogError("gorgone-scom: cannot load container '" . $_->{name} . "' - please set url option"); + $options{logger}->writeLogError("[scom] -hooks- Cannot load container '" . $_->{name} . "' - please set url option"); next; } if (!defined($_->{dsmhost}) || $_->{dsmhost} eq '') { - $options{logger}->writeLogError("gorgone-scom: cannot load container '" . $_->{name} . "' - please set dsmhost option"); + $options{logger}->writeLogError("[scom] -hooks- Cannot load container '" . $_->{name} . "' - please set dsmhost option"); next; } if (!defined($_->{dsmslot}) || $_->{dsmslot} eq '') { - $options{logger}->writeLogError("gorgone-scom: cannot load container '" . $_->{name} . "' - please set dsmslot option"); + $options{logger}->writeLogError("[scom] -hooks- Cannot load container '" . $_->{name} . "' - please set dsmslot option"); next; } @@ -221,7 +220,7 @@ sub sync_container_childs { next if (defined($last_containers->{$container_id})); if ($containers->{$container_id}->{running} == 1) { - $options{logger}->writeLogInfo("gorgone-scom: Send KILL signal for container '" . $container_id . "'"); + $options{logger}->writeLogInfo("[scom] -hooks- Send KILL signal for container '" . $container_id . "'"); CORE::kill('KILL', $containers->{$container_id}->{pid}); } @@ -233,13 +232,13 @@ sub sync_container_childs { sub create_child { my (%options) = @_; - $options{logger}->writeLogInfo("Create gorgone-scom for container '" . $options{container_id} . "'"); + $options{logger}->writeLogInfo("[scom] -hooks- Create 'gorgone-scom' process for container '" . $options{container_id} . "'"); my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-scom ' . $options{container_id}; - my $module = modules::gorgonescom::class->new( + my $module = modules::scom::class->new( logger => $options{logger}, - module_id => $module_id, + module_id => $NAME, config_core => $config_core, config => $config, config_db_centstorage => $config_db_centstorage, @@ -249,7 +248,7 @@ sub create_child { $module->run(); exit(0); } - $options{logger}->writeLogInfo("PID $child_pid gorgone-scom for container '" . $options{container_id} . "'"); + $options{logger}->writeLogInfo("[scom] -hooks- PID $child_pid (gorgone-scom) for container '" . $options{container_id} . "'"); $containers->{$options{container_id}} = { pid => $child_pid, ready => 0, running => 1 }; $containers_pid->{$child_pid} = $options{container_id}; } From 9e5679eb9b4ae63b22ae61057d767b1b826d13f4 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 14 Aug 2019 17:18:47 +0200 Subject: [PATCH 040/948] restapi mocking --- gorgone/centreon/gorgone/api.pm | 47 ++++++++++++++++---- gorgone/modules/core/cron/class.pm | 55 ++++++++++++++++++++++-- gorgone/modules/core/cron/hooks.pm | 6 ++- gorgone/modules/core/httpserver/class.pm | 14 +++++- 4 files changed, 106 insertions(+), 16 deletions(-) diff --git a/gorgone/centreon/gorgone/api.pm b/gorgone/centreon/gorgone/api.pm index e09f95b86e1..df0eae58e88 100644 --- a/gorgone/centreon/gorgone/api.pm +++ b/gorgone/centreon/gorgone/api.pm @@ -37,20 +37,42 @@ sub root { my %dispatch; foreach my $action (keys $options{modules_events}) { next if (!defined($options{modules_events}->{$action}->{api}->{uri})); - $dispatch{$options{modules_events}->{$action}->{api}->{method} . '_/' . - $options{modules_events}->{$action}->{module}->{name} . + $dispatch{$options{modules_events}->{$action}->{api}->{method} . '_' . $options{modules_events}->{$action}->{api}->{uri}} = $action; } + use Data::Dumper; + print Dumper \%dispatch; my $response; if ($options{method} eq 'GET' && $options{uri} =~ /^\/api\/get\/(.*)$/) { $response = get_log(socket => $options{socket}, token => $1); - } elsif ($options{method} eq 'GET' && $options{uri} =~ /^\/api\/module(.*)$/) { - $response = call_action(socket => $options{socket}, action => $dispatch{'GET_' . $1}); - } elsif ($options{method} eq 'POST' && $options{uri} =~ /^\/api\/module(.*)$/) { - $response = call_action(socket => $options{socket}, action => $dispatch{'POST_' . $1}); - # } elsif { - # } + } elsif ($options{method} eq 'GET' + && $options{uri} =~ /^\/api\/(target\/(\w*)\/)?(\w+)\/?(\w*?)$/ + && defined($dispatch{'GET_/' . $3})) { + $response = call_action( + socket => $options{socket}, + action => $dispatch{'GET_/' . $3}, + target => $2, + data => { params => $options{params} } + ); + } elsif ($options{method} eq 'POST' + && $options{uri} =~ /^\/api\/(target\/(\w*)\/)?(\w+)\/?(\w*?)$/ + && defined($dispatch{'POST_/' . $3})) { + $response = call_action( + socket => $options{socket}, + action => $dispatch{'POST_/' . $3}, + target => $2, + data => { content => $options{content}, params => $options{params} } + ); + } elsif ($options{method} eq 'DELETE' + && $options{uri} =~ /^\/api\/(target\/(\w*)\/)?(\w+)\/?(\w*?)$/ + && defined($dispatch{'DELETE_/' . $3})) { + $response = call_action( + socket => $options{socket}, + action => $dispatch{'DELETE_/' . $3}, + target => $2, + data => { params => $options{params} } + ); } else { $response = '{"error":"method_unknown","message":"Method not implemented"}'; } @@ -80,7 +102,14 @@ sub call_action { my $rev = zmq_poll($poll, 5000); - return '{"token":"' . $result->{token} . '"}'; + my $response = ""; + if (defined($result->{token}) && $result->{token} ne '') { + $response = '{"token":"' . $result->{token} . '"}'; + } else { + $response = '{"error":"no_token","message":"Cannot retrieve token from ack"}'; + } + + return $response; } sub get_log { diff --git a/gorgone/modules/core/cron/class.pm b/gorgone/modules/core/cron/class.pm index f8483dc1f8f..9d35ba5d5db 100644 --- a/gorgone/modules/core/cron/class.pm +++ b/gorgone/modules/core/cron/class.pm @@ -79,7 +79,56 @@ sub class_handle_HUP { } } -sub action_listcron { +sub action_deletecron { + my ($self, %options) = @_; + + $options{token} = $self->generate_token() if (!defined($options{token})); + + $self->send_log(code => centreon::gorgone::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action deletecron proceed' }); + $self->{logger}->writeLogDebug("[cron] -class- Cron delete start"); + + # Delete... + + $self->{logger}->writeLogDebug("[cron] -class- Cron delete finish"); + $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => { message => 'action deletecron succeed' }); + return 0; +} + +sub action_addcron { + my ($self, %options) = @_; + + $options{token} = $self->generate_token() if (!defined($options{token})); + + $self->send_log(code => centreon::gorgone::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action addcron proceed' }); + $self->{logger}->writeLogDebug("[cron] -class- Cron add start"); + + foreach my $definition (@{$options{data}->{content}}) { + if (!defined($definition->{timespec}) || $definition->{timespec} eq '' || + !defined($definition->{command_line}) || $definition->{command_line} eq '' || + !defined($definition->{name}) || $definition->{name} eq '') { + $self->{logger}->writeLogDebug("[cron] -class- Cron add missing arguments"); + $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'action addcron missing arguments' }); + return 1; + } + } + + eval { + foreach my $definition (@{$options{data}->{content}}) { + $self->{cron}->add_entry($definition->{timespec}, \&dispatcher, { socket => $connector->{internal_socket}, logger => $self->{logger}, definition => $definition }); + } + }; + if ($@) { + $self->{logger}->writeLogDebug("[cron] -class- Cron add failed"); + $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'action addcron failed:' . $@ }); + return 1; + } + + $self->{logger}->writeLogDebug("[cron] -class- Cron add finish"); + $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => { message => 'action addcron succeed' }); + return 0; +} + +sub action_getcron { my ($self, %options) = @_; $options{token} = $self->generate_token() if (!defined($options{token})); @@ -94,9 +143,9 @@ sub action_listcron { push @cron_list, { %{$cron->{args}[0]->{definition}} }; } }; - if ($@) { + if ($@) { $self->{logger}->writeLogDebug("[cron] -class- Cron list failed"); - $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'action listcron failed' }); + $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'action listcron failed:' . $@ }); return 1; } diff --git a/gorgone/modules/core/cron/hooks.pm b/gorgone/modules/core/cron/hooks.pm index 98cb14e8eea..2fa0f6eb0b0 100644 --- a/gorgone/modules/core/cron/hooks.pm +++ b/gorgone/modules/core/cron/hooks.pm @@ -28,8 +28,10 @@ use modules::core::cron::class; my $NAME = 'cron'; my $EVENTS = [ { event => 'CRONREADY' }, - { event => 'RELOADCRON', uri => '/reload', method => 'POST' }, - { event => 'LISTCRON', uri => '/list', method => 'GET' }, + { event => 'GETCRON', uri => '/cron', method => 'GET' }, + { event => 'ADDCRON', uri => '/cron', method => 'POST' }, + { event => 'DELETECRON', uri => '/cron', method => 'DELETE' }, + { event => 'UPDATECRON', uri => '/cron', method => 'PATCH' }, ]; my $config_core; diff --git a/gorgone/modules/core/httpserver/class.pm b/gorgone/modules/core/httpserver/class.pm index 59cb1cd2880..9ecc39c2982 100644 --- a/gorgone/modules/core/httpserver/class.pm +++ b/gorgone/modules/core/httpserver/class.pm @@ -29,6 +29,7 @@ use HTTP::Daemon; use HTTP::Daemon::SSL; use HTTP::Status; use MIME::Base64; +use JSON::XS; my $time = time(); @@ -201,12 +202,21 @@ sub send_response { sub api_call { my ($self, $request) = @_; + my $content; + eval { + $content = JSON::XS->new->utf8->decode($request->content) if ($request->method eq 'POST' && defined($request->content)); + }; + if ($@) { + return '{"error":"decode_error","message":"POST content must be JSON-formated"}';; + } + require 'centreon/gorgone/api.pm'; my %params = $request->uri->query_form; my $response = centreon::gorgone::api::root( + method => $request->method, uri => $request->uri->path, params => \%params, - method => $request->method, + content => $content, socket => $connector->{internal_socket}, logger => $self->{logger}, modules_events => $self->{modules_events} @@ -247,7 +257,7 @@ sub server_status { $encoded_data = JSON::XS->new->utf8->encode(\%data); }; if ($@) { - $encoded_data = '{"code":"encode_error","message":"Cannot encode response into JSON format"}'; + $encoded_data = '{"error":"encode_error","message":"Cannot encode response into JSON format"}'; } return $encoded_data; From 3f044c48889d5966905c3f996dccaa8fb3f0af59 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 14 Aug 2019 17:19:33 +0200 Subject: [PATCH 041/948] remove dumper --- gorgone/centreon/gorgone/api.pm | 2 -- 1 file changed, 2 deletions(-) diff --git a/gorgone/centreon/gorgone/api.pm b/gorgone/centreon/gorgone/api.pm index df0eae58e88..3f1b612779c 100644 --- a/gorgone/centreon/gorgone/api.pm +++ b/gorgone/centreon/gorgone/api.pm @@ -40,8 +40,6 @@ sub root { $dispatch{$options{modules_events}->{$action}->{api}->{method} . '_' . $options{modules_events}->{$action}->{api}->{uri}} = $action; } - use Data::Dumper; - print Dumper \%dispatch; my $response; if ($options{method} eq 'GET' && $options{uri} =~ /^\/api\/get\/(.*)$/) { From 103d382f06008040452f9126aa86c80ae3be2f45 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 19 Aug 2019 18:06:11 +0200 Subject: [PATCH 042/948] enhance api and cron --- gorgone/centreon/gorgone/api.pm | 34 ++-- gorgone/config/gorgoned.yml | 2 +- gorgone/modules/core/cron/class.pm | 192 +++++++++++++++++++---- gorgone/modules/core/httpserver/class.pm | 50 +++--- 4 files changed, 201 insertions(+), 77 deletions(-) diff --git a/gorgone/centreon/gorgone/api.pm b/gorgone/centreon/gorgone/api.pm index 3f1b612779c..4b9659f499b 100644 --- a/gorgone/centreon/gorgone/api.pm +++ b/gorgone/centreon/gorgone/api.pm @@ -42,34 +42,20 @@ sub root { } my $response; - if ($options{method} eq 'GET' && $options{uri} =~ /^\/api\/get\/(.*)$/) { + if ($options{method} eq 'GET' && $options{uri} =~ /^\/api\/log\/(.*)$/) { $response = get_log(socket => $options{socket}, token => $1); - } elsif ($options{method} eq 'GET' - && $options{uri} =~ /^\/api\/(target\/(\w*)\/)?(\w+)\/?(\w*?)$/ - && defined($dispatch{'GET_/' . $3})) { + } elsif ($options{uri} =~ /^\/api\/(target\/(\w*)\/)?(\w+)\/?([\w\/]*?)$/ + && defined($dispatch{$options{method} . '_/' . $3})) { + my @variables = split(/\//, $4); $response = call_action( socket => $options{socket}, - action => $dispatch{'GET_/' . $3}, + action => $dispatch{$options{method} . '_/' . $3}, target => $2, - data => { params => $options{params} } - ); - } elsif ($options{method} eq 'POST' - && $options{uri} =~ /^\/api\/(target\/(\w*)\/)?(\w+)\/?(\w*?)$/ - && defined($dispatch{'POST_/' . $3})) { - $response = call_action( - socket => $options{socket}, - action => $dispatch{'POST_/' . $3}, - target => $2, - data => { content => $options{content}, params => $options{params} } - ); - } elsif ($options{method} eq 'DELETE' - && $options{uri} =~ /^\/api\/(target\/(\w*)\/)?(\w+)\/?(\w*?)$/ - && defined($dispatch{'DELETE_/' . $3})) { - $response = call_action( - socket => $options{socket}, - action => $dispatch{'DELETE_/' . $3}, - target => $2, - data => { params => $options{params} } + data => { + content => $options{content}, + parameters => $options{parameters}, + variables => \@variables, + } ); } else { $response = '{"error":"method_unknown","message":"Method not implemented"}'; diff --git a/gorgone/config/gorgoned.yml b/gorgone/config/gorgoned.yml index bd647c01c24..d9a000fe2eb 100644 --- a/gorgone/config/gorgoned.yml +++ b/gorgone/config/gorgoned.yml @@ -60,7 +60,7 @@ modules: package: modules::core::cron::hooks enable: true cron: - - name: "Echo date in /tmp/date.log" + - id: echo_date timespec: "* * * * *" command_line: "date >> /tmp/date.log" diff --git a/gorgone/modules/core/cron/class.pm b/gorgone/modules/core/cron/class.pm index 9d35ba5d5db..45a6c611b52 100644 --- a/gorgone/modules/core/cron/class.pm +++ b/gorgone/modules/core/cron/class.pm @@ -79,18 +79,57 @@ sub class_handle_HUP { } } -sub action_deletecron { +sub action_getcron { my ($self, %options) = @_; $options{token} = $self->generate_token() if (!defined($options{token})); - $self->send_log(code => centreon::gorgone::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action deletecron proceed' }); - $self->{logger}->writeLogDebug("[cron] -class- Cron delete start"); - - # Delete... + $self->send_log(code => centreon::gorgone::module::ACTION_BEGIN, token => $options{token}, data => { message => 'get start' }); + $self->{logger}->writeLogDebug("[cron] -class- Cron get start"); + + my @cron_list = (); + my $id = $options{data}->{variables}[0]; + if (defined($id) && $id ne '') { + my $idx; + eval { + $idx = $self->{cron}->check_entry($id); + }; + if ($@) { + $self->{logger}->writeLogDebug("[cron] -class- Cron get failed to retrieve entry index"); + $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'failed to retrieve entry index' }); + return 1; + } + if (!defined($idx)) { + $self->{logger}->writeLogDebug("[cron] -class- Cron get failed no entry found for id"); + $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'no entry found for id' }); + return 1; + } - $self->{logger}->writeLogDebug("[cron] -class- Cron delete finish"); - $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => { message => 'action deletecron succeed' }); + eval { + my $result = $self->{cron}->get_entry($idx); + push @cron_list, %{$result->{args}[1]->{definition}} if (defined($result->{args}[1]->{definition})); + }; + if ($@) { + $self->{logger}->writeLogDebug("[cron] -class- Cron get failed"); + $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'get failed:' . $@ }); + return 1; + } + } else { + eval { + my @results = $self->{cron}->list_entries(); + foreach my $cron (@results) { + push @cron_list, { %{$cron->{args}[1]->{definition}} }; + } + }; + if ($@) { + $self->{logger}->writeLogDebug("[cron] -class- Cron get failed"); + $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'get failed:' . $@ }); + return 1; + } + } + + $self->{logger}->writeLogDebug("[cron] -class- Cron get finish"); + $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => \@cron_list); return 0; } @@ -99,58 +138,145 @@ sub action_addcron { $options{token} = $self->generate_token() if (!defined($options{token})); - $self->send_log(code => centreon::gorgone::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action addcron proceed' }); + $self->send_log(code => centreon::gorgone::module::ACTION_BEGIN, token => $options{token}, data => { message => 'add start' }); $self->{logger}->writeLogDebug("[cron] -class- Cron add start"); foreach my $definition (@{$options{data}->{content}}) { if (!defined($definition->{timespec}) || $definition->{timespec} eq '' || !defined($definition->{command_line}) || $definition->{command_line} eq '' || - !defined($definition->{name}) || $definition->{name} eq '') { + !defined($definition->{id}) || $definition->{id} eq '') { $self->{logger}->writeLogDebug("[cron] -class- Cron add missing arguments"); - $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'action addcron missing arguments' }); + $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'missing arguments' }); return 1; } } eval { foreach my $definition (@{$options{data}->{content}}) { - $self->{cron}->add_entry($definition->{timespec}, \&dispatcher, { socket => $connector->{internal_socket}, logger => $self->{logger}, definition => $definition }); + my $idx = $self->{cron}->check_entry($definition->{id}); + if (defined($idx)) { + $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => "id '" . $definition->{id} . "' already exists" }); + next; + } + $self->{cron}->add_entry( + $definition->{timespec}, + $definition->{id}, + { + socket => $connector->{internal_socket}, + logger => $self->{logger}, + definition => $definition + } + ); } }; if ($@) { $self->{logger}->writeLogDebug("[cron] -class- Cron add failed"); - $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'action addcron failed:' . $@ }); + $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'add failed:' . $@ }); return 1; } $self->{logger}->writeLogDebug("[cron] -class- Cron add finish"); - $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => { message => 'action addcron succeed' }); + $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => { message => 'add succeed' }); return 0; } -sub action_getcron { +sub action_updatecron { my ($self, %options) = @_; $options{token} = $self->generate_token() if (!defined($options{token})); - $self->send_log(code => centreon::gorgone::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action listcron proceed' }); - $self->{logger}->writeLogDebug("[cron] -class- Cron list start"); + $self->send_log(code => centreon::gorgone::module::ACTION_BEGIN, token => $options{token}, data => { message => 'update start' }); + $self->{logger}->writeLogDebug("[cron] -class- Cron update start"); + + my $id = $options{data}->{variables}[0]; + if (!defined($id)) { + $self->{logger}->writeLogDebug("[cron] -class- Cron update missing id"); + $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'missing id' }); + return 1; + } - my @cron_list; + my $idx; eval { - my @results = $self->{cron}->list_entries(); - foreach my $cron (@results) { - push @cron_list, { %{$cron->{args}[0]->{definition}} }; - } + $idx = $self->{cron}->check_entry($id); + }; + if ($@) { + $self->{logger}->writeLogDebug("[cron] -class- Cron update failed to retrieve entry index"); + $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'failed to retrieve entry index' }); + return 1; + } + if (!defined($idx)) { + $self->{logger}->writeLogDebug("[cron] -class- Cron update failed no entry found for id"); + $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'no entry found for id' }); + return 1; + } + + my $definition = $options{data}->{content}; + if ((!defined($definition->{timespec}) || $definition->{timespec} eq '') && + (!defined($definition->{command_line}) || $definition->{command_line} eq '')) { + $self->{logger}->writeLogDebug("[cron] -class- Cron update missing arguments"); + $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'missing arguments' }); + return 1; + } + + eval { + my $entry = $self->{cron}->get_entry($idx); + $entry->{time} = $definition->{timespec}; + $entry->{args}[1]->{definition}->{timespec} = $definition->{timespec} if (defined($definition->{timespec})); + $entry->{args}[1]->{definition}->{command_line} = $definition->{command_line} if (defined($definition->{command_line})); + $self->{cron}->update_entry($idx, $entry); }; if ($@) { - $self->{logger}->writeLogDebug("[cron] -class- Cron list failed"); - $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'action listcron failed:' . $@ }); + $self->{logger}->writeLogDebug("[cron] -class- Cron update failed"); + $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'update failed:' . $@ }); return 1; } - $self->{logger}->writeLogDebug("[cron] -class- Cron list finish"); - $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => \@cron_list); + $self->{logger}->writeLogDebug("[cron] -class- Cron update succeed"); + $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => { message => 'update succeed' }); + return 0; +} + +sub action_deletecron { + my ($self, %options) = @_; + + $options{token} = $self->generate_token() if (!defined($options{token})); + + $self->send_log(code => centreon::gorgone::module::ACTION_BEGIN, token => $options{token}, data => { message => 'delete start' }); + $self->{logger}->writeLogDebug("[cron] -class- Cron delete start"); + + my $id = $options{data}->{variables}[0]; + if (!defined($id) || $id eq '') { + $self->{logger}->writeLogDebug("[cron] -class- Cron delete missing id"); + $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'missing id' }); + return 1; + } + + my $idx; + eval { + $idx = $self->{cron}->check_entry($id); + }; + if ($@) { + $self->{logger}->writeLogDebug("[cron] -class- Cron delete failed to retrieve entry index"); + $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'failed to retrieve entry index' }); + return 1; + } + if (!defined($idx)) { + $self->{logger}->writeLogDebug("[cron] -class- Cron delete failed no entry found for id"); + $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'no entry found for id' }); + return 1; + } + + eval { + $self->{cron}->delete_entry($idx); + }; + if ($@) { + $self->{logger}->writeLogDebug("[cron] -class- Cron delete failed"); + $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'delete failed:' . $@ }); + return 1; + } + + $self->{logger}->writeLogDebug("[cron] -class- Cron delete finish"); + $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => { message => 'delete succeed' }); return 0; } @@ -182,9 +308,9 @@ sub cron_sleep { } sub dispatcher { - my ($options) = @_; + my ($id, $options) = @_; - $options->{logger}->writeLogInfo("[cron] -class- Launching job '" . $options->{definition}->{name} . "'"); + $options->{logger}->writeLogInfo("[cron] -class- Launching job '" . $id . "'"); centreon::gorgone::common::zmq_send_message( socket => $options->{socket}, @@ -231,8 +357,16 @@ sub run { $self->{cron} = new Schedule::Cron(\&dispatcher, nostatus => 1, nofork => 1); - foreach my $def (@{$self->{config}->{cron}}) { - $self->{cron}->add_entry($def->{timespec}, \&dispatcher, { socket => $connector->{internal_socket}, logger => $self->{logger}, definition => $def }); + foreach my $definition (@{$self->{config}->{cron}}) { + $self->{cron}->add_entry( + $definition->{timespec}, + $definition->{id}, + { + socket => $connector->{internal_socket}, + logger => $self->{logger}, + definition => $definition + } + ); } $self->{cron}->run(sleep => \&cron_sleep); diff --git a/gorgone/modules/core/httpserver/class.pm b/gorgone/modules/core/httpserver/class.pm index 9ecc39c2982..c78c450b325 100644 --- a/gorgone/modules/core/httpserver/class.pm +++ b/gorgone/modules/core/httpserver/class.pm @@ -145,29 +145,31 @@ sub run { SSL_error_trap => \&ssl_error ); } - - while (my ($connection, $peer_addr) = $daemon->accept) { - while (my $request = $connection->get_request) { - $connector->{logger}->writeLogInfo("[httpserver] -class- " . $request->method . " '" . $request->uri->path . "'"); - - if ($self->authentication($request->header('Authorization'))) { # Check Basic authentication - my ($root) = ($request->uri->path =~ /^(\/\w+)/); - - if ($request->method eq 'GET' && $root eq "/status") { # Server status - $self->send_response(connection => $connection, response => $self->server_status); - } elsif ($root eq "/api") { # API - $self->send_response(connection => $connection, response => $self->api_call($request)); - } elsif (defined($self->{dispatch}->{$root})) { # Other dispatch definition - $self->send_response(connection => $connection, response => $self->dispatch_call(root => $root, request => $request)); - } else { # Forbidden - $connection->send_error(RC_FORBIDDEN) + + if (defined($daemon)) { + while (my ($connection, $peer_addr) = $daemon->accept) { + while (my $request = $connection->get_request) { + $connector->{logger}->writeLogInfo("[httpserver] -class- " . $request->method . " '" . $request->uri->path . "'"); + + if ($self->authentication($request->header('Authorization'))) { # Check Basic authentication + my ($root) = ($request->uri->path =~ /^(\/\w+)/); + + if ($request->method eq 'GET' && $root eq "/status") { # Server status + $self->send_response(connection => $connection, response => $self->server_status); + } elsif ($root eq "/api") { # API + $self->send_response(connection => $connection, response => $self->api_call($request)); + } elsif (defined($self->{dispatch}->{$root})) { # Other dispatch definition + $self->send_response(connection => $connection, response => $self->dispatch_call(root => $root, request => $request)); + } else { # Forbidden + $connection->send_error(RC_FORBIDDEN) + } + } else { # Authen error + $connection->send_error(RC_UNAUTHORIZED); } - } else { # Authen error - $connection->send_error(RC_UNAUTHORIZED); } + $connection->close; + undef($connection); } - $connection->close; - undef($connection); } } @@ -195,6 +197,7 @@ sub send_response { my ($self, %options) = @_; my $response = HTTP::Response->new(200); + $response->header('Content-Type' => 'application/json'); $response->content($options{response} . "\n"); $options{connection}->send_response($response); } @@ -204,18 +207,19 @@ sub api_call { my $content; eval { - $content = JSON::XS->new->utf8->decode($request->content) if ($request->method eq 'POST' && defined($request->content)); + $content = JSON::XS->new->utf8->decode($request->content) + if ($request->method =~ /POST|PATCH/ && defined($request->content)); }; if ($@) { return '{"error":"decode_error","message":"POST content must be JSON-formated"}';; } require 'centreon/gorgone/api.pm'; - my %params = $request->uri->query_form; + my %parameters = $request->uri->query_form; my $response = centreon::gorgone::api::root( method => $request->method, uri => $request->uri->path, - params => \%params, + parameters => \%parameters, content => $content, socket => $connector->{internal_socket}, logger => $self->{logger}, From 6038d842f7fdf88eef76abbc5513c7c1f3d0d600 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 20 Aug 2019 11:49:25 +0200 Subject: [PATCH 043/948] enhance cron, api and action --- gorgone/centreon/gorgone/api.pm | 48 ++++++-- gorgone/centreon/gorgone/common.pm | 5 +- gorgone/centreon/gorgone/module.pm | 2 +- gorgone/modules/core/action/class.pm | 145 +++++++----------------- gorgone/modules/core/action/hooks.pm | 8 +- gorgone/modules/core/cron/class.pm | 160 ++++++++++++++++++++++----- 6 files changed, 212 insertions(+), 156 deletions(-) diff --git a/gorgone/centreon/gorgone/api.pm b/gorgone/centreon/gorgone/api.pm index 4b9659f499b..d1ab0ce63ef 100644 --- a/gorgone/centreon/gorgone/api.pm +++ b/gorgone/centreon/gorgone/api.pm @@ -25,6 +25,8 @@ use warnings; use centreon::gorgone::common; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); +use Time::HiRes; +use JSON::XS; my $socket; my $result; @@ -44,7 +46,7 @@ sub root { my $response; if ($options{method} eq 'GET' && $options{uri} =~ /^\/api\/log\/(.*)$/) { $response = get_log(socket => $options{socket}, token => $1); - } elsif ($options{uri} =~ /^\/api\/(target\/(\w*)\/)?(\w+)\/?([\w\/]*?)$/ + } elsif ($options{uri} =~ /^\/api\/(targets\/(\w*)\/)?(\w+)\/?([\w\/]*?)$/ && defined($dispatch{$options{method} . '_/' . $3})) { my @variables = split(/\//, $4); $response = call_action( @@ -55,7 +57,8 @@ sub root { content => $options{content}, parameters => $options{parameters}, variables => \@variables, - } + }, + wait => (defined($options{parameters}->{wait})) ? $options{parameters}->{wait} : undef ); } else { $response = '{"error":"method_unknown","message":"Method not implemented"}'; @@ -78,19 +81,22 @@ sub call_action { $socket = $options{socket}; my $poll = [ { - socket => $options{socket}, - events => ZMQ_POLLIN, + socket => $options{socket}, + events => ZMQ_POLLIN, callback => \&event, } ]; my $rev = zmq_poll($poll, 5000); - my $response = ""; + my $response = '{"error":"no_token","message":"Cannot retrieve token from ack"}'; if (defined($result->{token}) && $result->{token} ne '') { - $response = '{"token":"' . $result->{token} . '"}'; - } else { - $response = '{"error":"no_token","message":"Cannot retrieve token from ack"}'; + if (defined($options{wait}) && $options{wait} ne '') { + Time::HiRes::usleep($options{wait}); + $response = get_log(socket => $options{socket}, token => $result->{token}); + } else { + $response = '{"token":"' . $result->{token} . '"}'; + } } return $response; @@ -98,7 +104,7 @@ sub call_action { sub get_log { my (%options) = @_; - + centreon::gorgone::common::zmq_send_message( socket => $options{socket}, action => 'GETLOG', @@ -106,8 +112,8 @@ sub get_log { token => $options{token} }, json_encode => 1 - ); - + ); + $socket = $options{socket}; my $poll = [ { @@ -119,7 +125,25 @@ sub get_log { my $rev = zmq_poll($poll, 5000); - return $result->{data}; + my $response = '{"error":"no_log","message":"No log found for token","token":"' . $options{token} . '"}'; + if (defined($result->{data})) { + my $content; + eval { + $content = JSON::XS->new->utf8->decode($result->{data}); + }; + if ($@) { + $response = '{"error":"decode_error","message":"Cannot decode response"}'; + } elsif (defined($content->{data}->{result}) && scalar(keys %{$content->{data}->{result}}) > 0) { + eval { + $response = JSON::XS->new->utf8->encode($content->{data}->{result}); + }; + if ($@) { + $response = '{"error":"encode_error","message":"Cannot encode response"}'; + } + } + } + + return $response; } sub event { diff --git a/gorgone/centreon/gorgone/common.pm b/gorgone/centreon/gorgone/common.pm index 881f76cd0de..b352c6c3f6b 100644 --- a/gorgone/centreon/gorgone/common.pm +++ b/gorgone/centreon/gorgone/common.pm @@ -335,7 +335,10 @@ sub putlog { my $status = add_history( dbh => $options{gorgone}->{db_gorgone}, - etime => $data->{etime}, token => $data->{token}, data => json_encode(data => $data->{data}, logger => $options{logger}), code => $data->{code} + etime => $data->{etime}, + token => $data->{token}, + data => json_encode(data => $data->{data}, logger => $options{logger}), + code => $data->{code} ); if ($status == -1) { return (1, { message => 'database issue' }); diff --git a/gorgone/centreon/gorgone/module.pm b/gorgone/centreon/gorgone/module.pm index 6147da84983..c92a5f1050a 100644 --- a/gorgone/centreon/gorgone/module.pm +++ b/gorgone/centreon/gorgone/module.pm @@ -43,7 +43,7 @@ sub send_log { return if (!defined($options{token})); centreon::gorgone::common::zmq_send_message( - socket => $self->{internal_socket}, + socket => (defined($options{socket})) ? $options{socket} : $self->{internal_socket}, action => 'PUTLOG', token => $options{token}, data => { code => $options{code}, etime => time(), token => $options{token}, data => $options{data} }, diff --git a/gorgone/modules/core/action/class.pm b/gorgone/modules/core/action/class.pm index 9e4638970d4..517f83917eb 100644 --- a/gorgone/modules/core/action/class.pm +++ b/gorgone/modules/core/action/class.pm @@ -41,8 +41,8 @@ sub new { $connector->{config_core} = $options{config_core}; $connector->{stop} = 0; - $connector->{enginecommand_timeout} = defined($connector->{config}->{enginecommand_timeout}) ? $connector->{config}->{enginecommand_timeout} : 30; - $connector->{command_timeout} = defined($connector->{config}->{command_timeout}) ? $connector->{config}->{command_timeout} : 30; + $connector->{command_timeout} = defined($connector->{config}->{command_timeout}) ? + $connector->{config}->{command_timeout} : 30; bless $connector, $class; $connector->set_signal_handlers; @@ -84,109 +84,45 @@ sub class_handle_HUP { sub action_command { my ($self, %options) = @_; - if (!defined($options{data}->{command}) || $options{data}->{command} eq '') { - centreon::gorgone::common::zmq_send_message( + if (!defined($options{data}->{content}->{command}) || $options{data}->{content}->{command} eq '') { + $self->send_log( socket => $options{socket_log}, - action => 'PUTLOG', - data => { code => 35, etime => time(), token => $options{token}, data => { message => "need command argument" } }, - json_encode => 1 + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'need command argument' } ); return -1; } my ($error, $stdout, $return_code) = centreon::misc::misc::backtick( - command => $options{data}->{command}, + command => $options{data}->{content}->{command}, #arguments => [@$args, $sub_cmd], - timeout => $self->{command_timeout}, + timeout => (defined($options{data}->{content}->{timeout})) ? $options{data}->{content}->{timeout} : $self->{command_timeout}, wait_exit => 1, redirect_stderr => 1, logger => $self->{logger} ); if ($error <= -1000) { - centreon::gorgone::common::zmq_send_message( + $self->send_log( socket => $options{socket_log}, - action => 'PUTLOG', - data => { code => 35, etime => time(), token => $options{token}, data => { message => "command '$options{data}->{command}' execution issue: $stdout" } }, - json_encode => 1 + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => "command '$options{data}->{content}->{command}' execution issue: $stdout" } ); return -1; } - centreon::gorgone::common::zmq_send_message( + $self->send_log( socket => $options{socket_log}, - action => 'PUTLOG', - data => { code => 36, etime => time(), token => $options{token}, data => { message => "command '$options{data}->{command}' has finished", stdout => $stdout, exit_code => $return_code } }, - json_encode => 1 + code => $self->ACTION_FINISH_OK, + token => $options{token}, + data => { + message => "command '$options{data}->{content}->{command}' has finished", + stdout => $stdout, + exit_code => $return_code + } ); - return 0; -} -sub action_enginecommand { - my ($self, %options) = @_; - - if (!defined($options{data}->{engine_pipe}) || $options{data}->{engine_pipe} eq '') { - centreon::gorgone::common::zmq_send_message( - socket => $options{socket_log}, - action => 'PUTLOG', - data => { code => 35, etime => time(), token => $options{token}, data => { message => "need engine_pipe argument" } }, - json_encode => 1 - ); - return -1; - } - if (! -e $options{data}->{engine_pipe}) { - centreon::gorgone::common::zmq_send_message( - socket => $options{socket_log}, - action => 'PUTLOG', - data => { code => 35, etime => time(), token => $options{token}, data => { message => "command '$options{data}->{command}' - engine_pipe '$options{data}->{engine_pipe}' must exist" } }, - json_encode => 1 - ); - return -1; - } - if (! -p $options{data}->{engine_pipe}) { - centreon::gorgone::common::zmq_send_message( - socket => $options{socket_log}, - action => 'PUTLOG', data => { code => 35, etime => time(), token => $options{token}, data => { message => "command '$options{data}->{command}' - engine_pipe '$options{data}->{engine_pipe}' must be a pipe file" } }, - json_encode => 1 - ); - return -1; - } - if (! -w $options{data}->{engine_pipe}) { - centreon::gorgone::common::zmq_send_message( - socket => $options{socket_log}, - action => 'PUTLOG', data => { code => 35, etime => time(), token => $options{token}, data => { message => "command '$options{data}->{command}' - engine_pipe '$options{data}->{engine_pipe}' must be writeable" } }, - json_encode => 1 - ); - return -1; - } - - $self->{logger}->writeLogDebug("[action] -class- Submit engine command '$options{data}->{command}'"); - my $fh; - eval { - local $SIG{ALRM} = sub { die "Timeout command\n" }; - alarm $self->{enginecommand_timeout}; - open($fh, ">", $options{data}->{engine_pipe}) or die "cannot open '$options{data}->{engine_pipe}': $!"; - print $fh $options{data}->{command} . "\n"; - close $fh; - alarm 0; - }; - if ($@) { - close $fh if (defined($fh)); - $self->{logger}->writeLogError("[action] -class- Submit engine command '$options{data}->{command}' issue: $@"); - centreon::gorgone::common::zmq_send_message( - socket => $options{socket_log}, - action => 'PUTLOG', - data => { code => 35, etime => time(), token => $options{token}, data => { message => "submit command issue '$options{data}->{command}': $@" } }, - json_encode => 1 - ); - return undef; - } - - centreon::gorgone::common::zmq_send_message( - socket => $options{socket_log}, - action => 'PUTLOG', - data => { code => 36, etime => time(), token => $options{token}, data => { message => "command '$options{data}->{command}' had been submitted" } }, - json_encode => 1 - ); return 0; } @@ -201,25 +137,26 @@ sub action_run { type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); + if ($options{action} eq 'COMMAND') { $self->action_command(%options, socket_log => $socket_log); - } elsif ($options{action} eq 'ENGINECOMMAND') { - $self->action_enginecommand(%options, socket_log => $socket_log); + } else { + $self->send_log( + socket => $socket_log, + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => "action unknown" } + ); + return -1; } - - centreon::gorgone::common::zmq_send_message( - socket => $socket_log, - action => 'PUTLOG', - data => { code => 32, etime => time(), token => $options{token}, data => { message => "proceed action end" } }, - json_encode => 1 - ); + zmq_close($socket_log); } sub create_child { my ($self, %options) = @_; - $self->{logger}->writeLogInfo("[action] -class- Create module 'action' sub-process"); + $self->{logger}->writeLogInfo("[action] -class- create sub-process"); $options{message} =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; my ($action, $token) = ($1, $2); @@ -227,11 +164,10 @@ sub create_child { my $child_pid = fork(); if (!defined($child_pid)) { - centreon::gorgone::common::zmq_send_message( - socket => $connector->{internal_socket}, - action => 'PUTLOG', - data => { code => 30, etime => time(), token => $token, data => { message => "cannot fork: $!" } }, - json_encode => 1 + $self->send_log( + code => $self->ACTION_FINISH_KO, + token => $token, + data => { message => "cannot fork: $!" } ); return undef; } @@ -240,11 +176,10 @@ sub create_child { $self->action_run(action => $action, token => $token, data => $data); exit(0); } else { - centreon::gorgone::common::zmq_send_message( - socket => $connector->{internal_socket}, - action => 'PUTLOG', - data => { code => 31, etime => time(), token => $token, data => { message => "proceed action" } }, - json_encode => 1 + $self->send_log( + code => $self->ACTION_BEGIN, + token => $token, + data => { message => "proceed action" } ); } } diff --git a/gorgone/modules/core/action/hooks.pm b/gorgone/modules/core/action/hooks.pm index aa0ea35d8ba..39613120a90 100644 --- a/gorgone/modules/core/action/hooks.pm +++ b/gorgone/modules/core/action/hooks.pm @@ -28,6 +28,7 @@ use modules::core::action::class; my $NAME = 'action'; my $EVENTS = [ { event => 'ACTIONREADY' }, + { event => 'COMMAND', uri => '/command', method => 'POST' }, ]; my $config_core; @@ -40,13 +41,6 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; - if (!defined($config->{disable_command_event}) || $config->{disable_command_event} != 1) { - push @{$EVENTS}, { event => 'COMMAND', uri => '/command', method => 'POST' }; - } - if (!defined($config->{disable_enginecommand_event}) || $config->{disable_enginecommand_event} != 1) { - push @{$EVENTS}, { event => 'ENGINECOMMAND', uri => '/enginecommand', method => 'POST' }; - } - return ($NAME, $EVENTS); } diff --git a/gorgone/modules/core/cron/class.pm b/gorgone/modules/core/cron/class.pm index 45a6c611b52..a51bc6cddc3 100644 --- a/gorgone/modules/core/cron/class.pm +++ b/gorgone/modules/core/cron/class.pm @@ -84,7 +84,11 @@ sub action_getcron { $options{token} = $self->generate_token() if (!defined($options{token})); - $self->send_log(code => centreon::gorgone::module::ACTION_BEGIN, token => $options{token}, data => { message => 'get start' }); + $self->send_log( + code => centreon::gorgone::module::ACTION_BEGIN, + token => $options{token}, + data => { message => 'get start' } + ); $self->{logger}->writeLogDebug("[cron] -class- Cron get start"); my @cron_list = (); @@ -96,22 +100,34 @@ sub action_getcron { }; if ($@) { $self->{logger}->writeLogDebug("[cron] -class- Cron get failed to retrieve entry index"); - $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'failed to retrieve entry index' }); + $self->send_log( + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'failed to retrieve entry index' } + ); return 1; } if (!defined($idx)) { $self->{logger}->writeLogDebug("[cron] -class- Cron get failed no entry found for id"); - $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'no entry found for id' }); + $self->send_log( + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'no entry found for id' } + ); return 1; } eval { my $result = $self->{cron}->get_entry($idx); - push @cron_list, %{$result->{args}[1]->{definition}} if (defined($result->{args}[1]->{definition})); + push @cron_list, { %{$result->{args}[1]->{definition}} } if (defined($result->{args}[1]->{definition})); }; if ($@) { $self->{logger}->writeLogDebug("[cron] -class- Cron get failed"); - $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'get failed:' . $@ }); + $self->send_log( + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'get failed:' . $@ } + ); return 1; } } else { @@ -123,13 +139,21 @@ sub action_getcron { }; if ($@) { $self->{logger}->writeLogDebug("[cron] -class- Cron get failed"); - $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'get failed:' . $@ }); + $self->send_log( + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'get failed:' . $@ } + ); return 1; } } $self->{logger}->writeLogDebug("[cron] -class- Cron get finish"); - $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => \@cron_list); + $self->send_log( + code => $self->ACTION_FINISH_OK, + token => $options{token}, + data => \@cron_list + ); return 0; } @@ -138,15 +162,23 @@ sub action_addcron { $options{token} = $self->generate_token() if (!defined($options{token})); - $self->send_log(code => centreon::gorgone::module::ACTION_BEGIN, token => $options{token}, data => { message => 'add start' }); $self->{logger}->writeLogDebug("[cron] -class- Cron add start"); - + $self->send_log( + code => centreon::gorgone::module::ACTION_BEGIN, + token => $options{token}, + data => { message => 'add start' } + ); + foreach my $definition (@{$options{data}->{content}}) { - if (!defined($definition->{timespec}) || $definition->{timespec} eq '' || + if (!defined($definition->{timespec}) || $definition->{timespec} eq '' || !defined($definition->{command_line}) || $definition->{command_line} eq '' || !defined($definition->{id}) || $definition->{id} eq '') { $self->{logger}->writeLogDebug("[cron] -class- Cron add missing arguments"); - $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'missing arguments' }); + $self->send_log( + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'missing arguments' } + ); return 1; } } @@ -155,7 +187,11 @@ sub action_addcron { foreach my $definition (@{$options{data}->{content}}) { my $idx = $self->{cron}->check_entry($definition->{id}); if (defined($idx)) { - $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => "id '" . $definition->{id} . "' already exists" }); + $self->send_log( + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => "id '" . $definition->{id} . "' already exists" } + ); next; } $self->{cron}->add_entry( @@ -171,12 +207,20 @@ sub action_addcron { }; if ($@) { $self->{logger}->writeLogDebug("[cron] -class- Cron add failed"); - $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'add failed:' . $@ }); + $self->send_log( + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'add failed:' . $@ } + ); return 1; } $self->{logger}->writeLogDebug("[cron] -class- Cron add finish"); - $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => { message => 'add succeed' }); + $self->send_log( + code => $self->ACTION_FINISH_OK, + token => $options{token}, + data => { message => 'add succeed' } + ); return 0; } @@ -185,13 +229,21 @@ sub action_updatecron { $options{token} = $self->generate_token() if (!defined($options{token})); - $self->send_log(code => centreon::gorgone::module::ACTION_BEGIN, token => $options{token}, data => { message => 'update start' }); $self->{logger}->writeLogDebug("[cron] -class- Cron update start"); + $self->send_log( + code => centreon::gorgone::module::ACTION_BEGIN, + token => $options{token}, + data => { message => 'update start' } + ); my $id = $options{data}->{variables}[0]; if (!defined($id)) { $self->{logger}->writeLogDebug("[cron] -class- Cron update missing id"); - $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'missing id' }); + $self->send_log( + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'missing id' } + ); return 1; } @@ -201,12 +253,20 @@ sub action_updatecron { }; if ($@) { $self->{logger}->writeLogDebug("[cron] -class- Cron update failed to retrieve entry index"); - $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'failed to retrieve entry index' }); + $self->send_log( + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'failed to retrieve entry index' } + ); return 1; } if (!defined($idx)) { $self->{logger}->writeLogDebug("[cron] -class- Cron update failed no entry found for id"); - $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'no entry found for id' }); + $self->send_log( + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'no entry found for id' } + ); return 1; } @@ -214,25 +274,39 @@ sub action_updatecron { if ((!defined($definition->{timespec}) || $definition->{timespec} eq '') && (!defined($definition->{command_line}) || $definition->{command_line} eq '')) { $self->{logger}->writeLogDebug("[cron] -class- Cron update missing arguments"); - $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'missing arguments' }); + $self->send_log( + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'missing arguments' } + ); return 1; } eval { my $entry = $self->{cron}->get_entry($idx); $entry->{time} = $definition->{timespec}; - $entry->{args}[1]->{definition}->{timespec} = $definition->{timespec} if (defined($definition->{timespec})); - $entry->{args}[1]->{definition}->{command_line} = $definition->{command_line} if (defined($definition->{command_line})); + $entry->{args}[1]->{definition}->{timespec} = $definition->{timespec} + if (defined($definition->{timespec})); + $entry->{args}[1]->{definition}->{command_line} = $definition->{command_line} + if (defined($definition->{command_line})); $self->{cron}->update_entry($idx, $entry); }; if ($@) { $self->{logger}->writeLogDebug("[cron] -class- Cron update failed"); - $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'update failed:' . $@ }); + $self->send_log( + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'update failed:' . $@ } + ); return 1; } $self->{logger}->writeLogDebug("[cron] -class- Cron update succeed"); - $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => { message => 'update succeed' }); + $self->send_log( + code => $self->ACTION_FINISH_OK, + token => $options{token}, + data => { message => 'update succeed' } + ); return 0; } @@ -241,13 +315,21 @@ sub action_deletecron { $options{token} = $self->generate_token() if (!defined($options{token})); - $self->send_log(code => centreon::gorgone::module::ACTION_BEGIN, token => $options{token}, data => { message => 'delete start' }); $self->{logger}->writeLogDebug("[cron] -class- Cron delete start"); + $self->send_log( + code => centreon::gorgone::module::ACTION_BEGIN, + token => $options{token}, + data => { message => 'delete start' } + ); my $id = $options{data}->{variables}[0]; if (!defined($id) || $id eq '') { $self->{logger}->writeLogDebug("[cron] -class- Cron delete missing id"); - $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'missing id' }); + $self->send_log( + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'missing id' } + ); return 1; } @@ -257,12 +339,20 @@ sub action_deletecron { }; if ($@) { $self->{logger}->writeLogDebug("[cron] -class- Cron delete failed to retrieve entry index"); - $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'failed to retrieve entry index' }); + $self->send_log( + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'failed to retrieve entry index' } + ); return 1; } if (!defined($idx)) { $self->{logger}->writeLogDebug("[cron] -class- Cron delete failed no entry found for id"); - $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'no entry found for id' }); + $self->send_log( + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'no entry found for id' } + ); return 1; } @@ -271,12 +361,20 @@ sub action_deletecron { }; if ($@) { $self->{logger}->writeLogDebug("[cron] -class- Cron delete failed"); - $self->send_log(code => $self->ACTION_FINISH_KO, token => $options{token}, data => { message => 'delete failed:' . $@ }); + $self->send_log( + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'delete failed:' . $@ } + ); return 1; } $self->{logger}->writeLogDebug("[cron] -class- Cron delete finish"); - $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => { message => 'delete succeed' }); + $self->send_log( + code => $self->ACTION_FINISH_OK, + token => $options{token}, + data => { message => 'delete succeed' } + ); return 0; } @@ -316,7 +414,9 @@ sub dispatcher { socket => $options->{socket}, action => 'COMMAND', target => $options->{definition}->{target}, - data => { command => $options->{definition}->{command_line} }, + data => { + content => { command => $options->{definition}->{command_line}, timeout => $options->{definition}->{timeout} } + }, json_encode => 1 ); From 028cf4ce73823987c99484f0186c95d152bb374b Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 20 Aug 2019 15:17:30 +0200 Subject: [PATCH 044/948] add module dimension in api uri --- gorgone/centreon/gorgone/api.pm | 11 ++++++----- gorgone/modules/core/cron/class.pm | 2 +- gorgone/modules/core/cron/hooks.pm | 8 ++++---- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/gorgone/centreon/gorgone/api.pm b/gorgone/centreon/gorgone/api.pm index d1ab0ce63ef..384c96333db 100644 --- a/gorgone/centreon/gorgone/api.pm +++ b/gorgone/centreon/gorgone/api.pm @@ -39,19 +39,20 @@ sub root { my %dispatch; foreach my $action (keys $options{modules_events}) { next if (!defined($options{modules_events}->{$action}->{api}->{uri})); - $dispatch{$options{modules_events}->{$action}->{api}->{method} . '_' . + $dispatch{$options{modules_events}->{$action}->{module}->{name} . '_' . + $options{modules_events}->{$action}->{api}->{method} . '_' . $options{modules_events}->{$action}->{api}->{uri}} = $action; } my $response; if ($options{method} eq 'GET' && $options{uri} =~ /^\/api\/log\/(.*)$/) { $response = get_log(socket => $options{socket}, token => $1); - } elsif ($options{uri} =~ /^\/api\/(targets\/(\w*)\/)?(\w+)\/?([\w\/]*?)$/ - && defined($dispatch{$options{method} . '_/' . $3})) { - my @variables = split(/\//, $4); + } elsif ($options{uri} =~ /^\/api\/(targets\/(\w*)\/)?(\w+)\/(\w+)\/?([\w\/]*?)$/ + && defined($dispatch{$3 . '_' . $options{method} . '_/' . $4})) { + my @variables = split(/\//, $5); $response = call_action( socket => $options{socket}, - action => $dispatch{$options{method} . '_/' . $3}, + action => $dispatch{$3 . '_' . $options{method} . '_/' . $4}, target => $2, data => { content => $options{content}, diff --git a/gorgone/modules/core/cron/class.pm b/gorgone/modules/core/cron/class.pm index a51bc6cddc3..a6b000501fe 100644 --- a/gorgone/modules/core/cron/class.pm +++ b/gorgone/modules/core/cron/class.pm @@ -170,7 +170,7 @@ sub action_addcron { ); foreach my $definition (@{$options{data}->{content}}) { - if (!defined($definition->{timespec}) || $definition->{timespec} eq '' || + if (!defined($definition->{timespec}) || $definition->{timespec} eq '' || !defined($definition->{command_line}) || $definition->{command_line} eq '' || !defined($definition->{id}) || $definition->{id} eq '') { $self->{logger}->writeLogDebug("[cron] -class- Cron add missing arguments"); diff --git a/gorgone/modules/core/cron/hooks.pm b/gorgone/modules/core/cron/hooks.pm index 2fa0f6eb0b0..de60a606317 100644 --- a/gorgone/modules/core/cron/hooks.pm +++ b/gorgone/modules/core/cron/hooks.pm @@ -28,10 +28,10 @@ use modules::core::cron::class; my $NAME = 'cron'; my $EVENTS = [ { event => 'CRONREADY' }, - { event => 'GETCRON', uri => '/cron', method => 'GET' }, - { event => 'ADDCRON', uri => '/cron', method => 'POST' }, - { event => 'DELETECRON', uri => '/cron', method => 'DELETE' }, - { event => 'UPDATECRON', uri => '/cron', method => 'PATCH' }, + { event => 'GETCRON', uri => '/definitions', method => 'GET' }, + { event => 'ADDCRON', uri => '/definitions', method => 'POST' }, + { event => 'DELETECRON', uri => '/definitions', method => 'DELETE' }, + { event => 'UPDATECRON', uri => '/definitions', method => 'PATCH' }, ]; my $config_core; From dd0e83e18bcbb43414cc09a786209dd13405c840 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 20 Aug 2019 15:18:03 +0200 Subject: [PATCH 045/948] move newtest and scom to plugins folder --- gorgone/modules/{ => plugins}/newtest/class.pm | 16 ++++++++-------- gorgone/modules/{ => plugins}/newtest/hooks.pm | 6 +++--- .../libs/stubs/ManagementConsoleService.pm | 6 +++--- .../{ => plugins}/newtest/libs/stubs/errors.pm | 2 +- .../{ => plugins}/newtest/libs/wsdl/newtest.wsdl | 0 gorgone/modules/{ => plugins}/scom/class.pm | 2 +- gorgone/modules/{ => plugins}/scom/hooks.pm | 6 +++--- 7 files changed, 19 insertions(+), 19 deletions(-) rename gorgone/modules/{ => plugins}/newtest/class.pm (97%) rename gorgone/modules/{ => plugins}/newtest/hooks.pm (98%) rename gorgone/modules/{ => plugins}/newtest/libs/stubs/ManagementConsoleService.pm (98%) rename gorgone/modules/{ => plugins}/newtest/libs/stubs/errors.pm (89%) rename gorgone/modules/{ => plugins}/newtest/libs/wsdl/newtest.wsdl (100%) rename gorgone/modules/{ => plugins}/scom/class.pm (99%) rename gorgone/modules/{ => plugins}/scom/hooks.pm (98%) diff --git a/gorgone/modules/newtest/class.pm b/gorgone/modules/plugins/newtest/class.pm similarity index 97% rename from gorgone/modules/newtest/class.pm rename to gorgone/modules/plugins/newtest/class.pm index 1c425cfa6ac..8c85ec2a63b 100644 --- a/gorgone/modules/newtest/class.pm +++ b/gorgone/modules/plugins/newtest/class.pm @@ -18,7 +18,7 @@ # limitations under the License. # -package modules::newtest::class; +package modules::plugins::newtest::class; use base qw(centreon::gorgone::module); @@ -32,8 +32,8 @@ use ZMQ::Constants qw(:all); use MIME::Base64; use JSON::XS; use Data::Dumper; -use modules::newtest::libs::stubs::ManagementConsoleService; -use modules::newtest::libs::stubs::errors; +use modules::plugins::newtest::libs::stubs::ManagementConsoleService; +use modules::plugins::newtest::libs::stubs::errors; use Date::Parse; my %handlers = (TERM => {}, HUP => {}); @@ -343,7 +343,7 @@ sub get_newtest_diagnostic { my ($self, %options) = @_; my $result = $self->{instance}->ListMessages('Instance', 30, 'Diagnostics', [$options{scenario}, $options{robot}]); - if (defined(my $com_error = modules::newtest::libs::stubs::errors::get_error())) { + if (defined(my $com_error = modules::plugins::newtest::libs::stubs::errors::get_error())) { $self->{logger}->writeLogError("[newtest] -class- newtest API error 'ListMessages' method: " . $com_error); return -1; } @@ -386,7 +386,7 @@ sub get_scenario_results { } if (!defined($self->{cache_robot_list_results}->{$options{robot}})) { my $result = $self->{instance}->ListResults('Robot', 30, [$options{robot}]); - if (defined(my $com_error = modules::newtest::libs::stubs::errors::get_error())) { + if (defined(my $com_error = modules::plugins::newtest::libs::stubs::errors::get_error())) { $self->{logger}->writeLogError("[newtest] -class- newtest API error 'ListResults' method: " . $com_error); return -1; } @@ -440,7 +440,7 @@ sub get_newtest_extra_metrics { my ($self, %options) = @_; my $result = $self->{instance}->ListResultChildren($options{id}); - if (defined(my $com_error = modules::newtest::libs::stubs::errors::get_error())) { + if (defined(my $com_error = modules::plugins::newtest::libs::stubs::errors::get_error())) { $self->{logger}->writeLogError("[newtest] -class- newtest API error 'ListResultChildren' method: " . $com_error); return -1; } @@ -484,7 +484,7 @@ sub get_newtest_scenarios { 0, $self->{list_scenario_status}->{instances} ); - if (defined(my $com_error = modules::newtest::libs::stubs::errors::get_error())) { + if (defined(my $com_error = modules::plugins::newtest::libs::stubs::errors::get_error())) { $self->{logger}->writeLogError("[newtest] -class- newtest API error 'ListScenarioStatus' method: " . $com_error); return -1; } @@ -634,7 +634,7 @@ sub run { $self->{class_object_centstorage} = centreon::misc::objects::object->new(logger => $self->{logger}, db_centreon => $self->{db_centstorage}); $self->{class_object_centreon} = centreon::misc::objects::object->new(logger => $self->{logger}, db_centreon => $self->{db_centreon}); $SOAP::Constants::PREFIX_ENV = 'SOAP-ENV'; - $self->{instance} = modules::newtest::libs::stubs::ManagementConsoleService->new(); + $self->{instance} = modules::plugins::newtest::libs::stubs::ManagementConsoleService->new(); # Connect internal $connector->{internal_socket} = centreon::gorgone::common::connect_com( diff --git a/gorgone/modules/newtest/hooks.pm b/gorgone/modules/plugins/newtest/hooks.pm similarity index 98% rename from gorgone/modules/newtest/hooks.pm rename to gorgone/modules/plugins/newtest/hooks.pm index 01f4e65cc5d..c5f8a5dc5d8 100644 --- a/gorgone/modules/newtest/hooks.pm +++ b/gorgone/modules/plugins/newtest/hooks.pm @@ -18,13 +18,13 @@ # limitations under the License. # -package modules::newtest::hooks; +package modules::plugins::newtest::hooks; use warnings; use strict; use JSON::XS; use centreon::script::gorgonecore; -use modules::newtest::class; +use modules::plugins::newtest::class; my $NAME = 'newtest'; my $EVENTS = [ @@ -250,7 +250,7 @@ sub create_child { my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-newtest ' . $options{container_id}; - my $module = modules::newtest::class->new( + my $module = modules::plugins::newtest::class->new( logger => $options{logger}, module_id => $NAME, config_core => $config_core, diff --git a/gorgone/modules/newtest/libs/stubs/ManagementConsoleService.pm b/gorgone/modules/plugins/newtest/libs/stubs/ManagementConsoleService.pm similarity index 98% rename from gorgone/modules/newtest/libs/stubs/ManagementConsoleService.pm rename to gorgone/modules/plugins/newtest/libs/stubs/ManagementConsoleService.pm index ce15680dd06..7bfc4ac38c1 100644 --- a/gorgone/modules/newtest/libs/stubs/ManagementConsoleService.pm +++ b/gorgone/modules/plugins/newtest/libs/stubs/ManagementConsoleService.pm @@ -1,4 +1,4 @@ -package modules::newtest::libs::stubs::ManagementConsoleService; +package modules::plugins::newtest::libs::stubs::ManagementConsoleService; sub SOAP::Serializer::as_SearchMode { my $self = shift; @@ -307,7 +307,7 @@ GetLicenceOptionValue => { ); # end my %methods use SOAP::Lite; -use modules::newtest::libs::stubs::errors; +use modules::plugins::newtest::libs::stubs::errors; use Exporter; use Carp (); @@ -320,7 +320,7 @@ sub _call { my ($self, $method) = (shift, shift); my $name = UNIVERSAL::isa($method => 'SOAP::Data') ? $method->name : $method; my %method = %{$methods{$name}}; - $self->on_fault(\&modules::newtest::libs::stubs::errors::soapGetBad); + $self->on_fault(\&modules::plugins::newtest::libs::stubs::errors::soapGetBad); $self->proxy($method{endpoint} || Carp::croak "No server address (proxy) specified") unless $self->proxy; my @templates = @{$method{parameters}}; diff --git a/gorgone/modules/newtest/libs/stubs/errors.pm b/gorgone/modules/plugins/newtest/libs/stubs/errors.pm similarity index 89% rename from gorgone/modules/newtest/libs/stubs/errors.pm rename to gorgone/modules/plugins/newtest/libs/stubs/errors.pm index 61082cddb8a..f45bfa7f51d 100644 --- a/gorgone/modules/newtest/libs/stubs/errors.pm +++ b/gorgone/modules/plugins/newtest/libs/stubs/errors.pm @@ -1,5 +1,5 @@ -package modules::newtest::libs::stubs::errors; +package modules::plugins::newtest::libs::stubs::errors; use strict; use warnings; diff --git a/gorgone/modules/newtest/libs/wsdl/newtest.wsdl b/gorgone/modules/plugins/newtest/libs/wsdl/newtest.wsdl similarity index 100% rename from gorgone/modules/newtest/libs/wsdl/newtest.wsdl rename to gorgone/modules/plugins/newtest/libs/wsdl/newtest.wsdl diff --git a/gorgone/modules/scom/class.pm b/gorgone/modules/plugins/scom/class.pm similarity index 99% rename from gorgone/modules/scom/class.pm rename to gorgone/modules/plugins/scom/class.pm index 7c63e643076..de84314c87e 100644 --- a/gorgone/modules/scom/class.pm +++ b/gorgone/modules/plugins/scom/class.pm @@ -18,7 +18,7 @@ # limitations under the License. # -package modules::scom::class; +package modules::plugins::scom::class; use base qw(centreon::gorgone::module); diff --git a/gorgone/modules/scom/hooks.pm b/gorgone/modules/plugins/scom/hooks.pm similarity index 98% rename from gorgone/modules/scom/hooks.pm rename to gorgone/modules/plugins/scom/hooks.pm index 9d2445570d1..55ec7591605 100644 --- a/gorgone/modules/scom/hooks.pm +++ b/gorgone/modules/plugins/scom/hooks.pm @@ -18,12 +18,12 @@ # limitations under the License. # -package modules::scom::hooks; +package modules::plugins::scom::hooks; use warnings; use strict; use centreon::script::gorgonecore; -use modules::scom::class; +use modules::plugins::scom::class; my $NAME = 'scom'; my $EVENTS = [ @@ -236,7 +236,7 @@ sub create_child { my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-scom ' . $options{container_id}; - my $module = modules::scom::class->new( + my $module = modules::plugins::scom::class->new( logger => $options{logger}, module_id => $NAME, config_core => $config_core, From c944a89df1350a59820123cc7fe1700a0740246c Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 20 Aug 2019 15:18:23 +0200 Subject: [PATCH 046/948] update config --- gorgone/config/gorgoned-poller.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/gorgone/config/gorgoned-poller.yml b/gorgone/config/gorgoned-poller.yml index ce2381f2440..b4847d8d433 100644 --- a/gorgone/config/gorgoned-poller.yml +++ b/gorgone/config/gorgoned-poller.yml @@ -47,12 +47,10 @@ modules: # ping ping: 60 ping_timeout: 30 + - name: gorgoneaction module: modules::gorgoneaction::hooks enable: true - disable_command_event: 0 - disable_enginecomand_event: 0 - enginecommand_timeout: 30 command_timeout: 60 # characters to delete in commands paranoid_mode: 1 From 252c2b2f800a2e6c0a6086cc3c997bb83a1b58e0 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 20 Aug 2019 15:29:48 +0200 Subject: [PATCH 047/948] update module config plugins --- gorgone/config/gorgoned.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/config/gorgoned.yml b/gorgone/config/gorgoned.yml index d9a000fe2eb..d13ee961c14 100644 --- a/gorgone/config/gorgoned.yml +++ b/gorgone/config/gorgoned.yml @@ -80,7 +80,7 @@ modules: ping: 60 - name: scom - package: modules::scom::hooks + package: modules::plugins::scom::hooks enable: false # in seconds - do purge for container also check_containers_time: 3600 @@ -108,7 +108,7 @@ modules: # resync_time: 600 - name: newtest - package: modules::newtest::hooks + package: modules::plugins::newtest::hooks enable: false # in seconds - do purge for container also check_containers_time: 3600 From 018edf10d60116b0f2d8bdb4bdaa8d9dae5595b8 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 20 Aug 2019 15:49:48 +0200 Subject: [PATCH 048/948] add engine module --- gorgone/config/gorgoned.yml | 9 +- gorgone/modules/centreon/engine/class.pm | 260 +++++++++++++++++++++++ gorgone/modules/centreon/engine/hooks.pm | 159 ++++++++++++++ 3 files changed, 426 insertions(+), 2 deletions(-) create mode 100644 gorgone/modules/centreon/engine/class.pm create mode 100644 gorgone/modules/centreon/engine/hooks.pm diff --git a/gorgone/config/gorgoned.yml b/gorgone/config/gorgoned.yml index d9a000fe2eb..f82fa7ba4d4 100644 --- a/gorgone/config/gorgoned.yml +++ b/gorgone/config/gorgoned.yml @@ -79,8 +79,13 @@ modules: # ping each X seconds ping: 60 + - name: engine + package: modules::centreon::engine::hooks + enable: true + command_file: "/var/lib/centreon-engine/rw/centengine.cmd" + - name: scom - package: modules::scom::hooks + package: modules::plugins::scom::hooks enable: false # in seconds - do purge for container also check_containers_time: 3600 @@ -108,7 +113,7 @@ modules: # resync_time: 600 - name: newtest - package: modules::newtest::hooks + package: modules::plugins::newtest::hooks enable: false # in seconds - do purge for container also check_containers_time: 3600 diff --git a/gorgone/modules/centreon/engine/class.pm b/gorgone/modules/centreon/engine/class.pm new file mode 100644 index 00000000000..7fe168af8db --- /dev/null +++ b/gorgone/modules/centreon/engine/class.pm @@ -0,0 +1,260 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package modules::centreon::engine::class; + +use base qw(centreon::gorgone::module); + +use strict; +use warnings; +use centreon::gorgone::common; +use centreon::misc::misc; +use ZMQ::LibZMQ4; +use ZMQ::Constants qw(:all); + +my %handlers = (TERM => {}, HUP => {}); +my ($connector); + +sub new { + my ($class, %options) = @_; + $connector = {}; + $connector->{internal_socket} = undef; + $connector->{logger} = $options{logger}; + $connector->{config} = $options{config}; + $connector->{config_core} = $options{config_core}; + $connector->{stop} = 0; + + $connector->{timeout} = defined($connector->{config}->{timeout}) ? $connector->{config}->{timeout} : 5; + + bless $connector, $class; + $connector->set_signal_handlers; + return $connector; +} + +sub set_signal_handlers { + my $self = shift; + + $SIG{TERM} = \&class_handle_TERM; + $handlers{TERM}->{$self} = sub { $self->handle_TERM() }; + $SIG{HUP} = \&class_handle_HUP; + $handlers{HUP}->{$self} = sub { $self->handle_HUP() }; +} + +sub handle_HUP { + my $self = shift; + $self->{reload} = 0; +} + +sub handle_TERM { + my $self = shift; + $self->{logger}->writeLogInfo("[engine] -class- $$ Receiving order to stop..."); + $self->{stop} = 1; +} + +sub class_handle_TERM { + foreach (keys %{$handlers{TERM}}) { + &{$handlers{TERM}->{$_}}(); + } +} + +sub class_handle_HUP { + foreach (keys %{$handlers{HUP}}) { + &{$handlers{HUP}->{$_}}(); + } +} + +sub action_enginecommand { + my ($self, %options) = @_; + + if (!defined($self->{config}->{command_file}) || $self->{config}->{command_file} eq '') { + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => "need command_file argument" } + ); + return -1; + } + if (! -e $self->{config}->{command_file}) { + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => "command '$options{data}->{content}->{command}' - engine_pipe '$self->{config}->{command_file}' must exist" } + ); + return -1; + } + if (! -p $self->{config}->{command_file}) { + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => "command '$options{data}->{content}->{command}' - engine_pipe '$self->{config}->{command_file}' must be a pipe file" } + ); + return -1; + } + if (! -w $self->{config}->{command_file}) { + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => "command '$options{data}->{content}->{command}' - engine_pipe '$self->{config}->{command_file}' must be writeable" } + ); + return -1; + } + + my $fh; + eval { + local $SIG{ALRM} = sub { die "Timeout command\n" }; + alarm $self->{timeout}; + open($fh, ">", $self->{config}->{command_file}) or die "cannot open '$self->{config}->{command_file}': $!"; + print $fh $options{data}->{content}->{command} . "\n"; + close $fh; + alarm 0; + }; + if ($@) { + close $fh if (defined($fh)); + $self->{logger}->writeLogError("[action] -class- Submit engine command '$options{data}->{content}->{command}' issue: $@"); + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => "submit command issue '$options{data}->{content}->{command}': $@" } + ); + return undef; + } + + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_OK, + token => $options{token}, + data => { message => "command '$options{data}->{content}->{command}' had been submitted': $@" } + ); + + return 0; +} + +sub action_run { + my ($self, %options) = @_; + + my $socket_log = centreon::gorgone::common::connect_com( + zmq_type => 'ZMQ_DEALER', + name => 'gorgoneengine-'. $$, + logger => $self->{logger}, + linger => 5000, + type => $self->{config_core}->{internal_com_type}, + path => $self->{config_core}->{internal_com_path} + ); + + if ($options{action} eq 'ENGINECOMMAND') { + $self->action_enginecommand(%options, socket_log => $socket_log); + } else { + $self->send_log( + socket => $socket_log, + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => "action unknown" } + ); + return -1; + } + + zmq_close($socket_log); +} + +sub create_child { + my ($self, %options) = @_; + + $self->{logger}->writeLogInfo("[engine] -class- create sub-process"); + $options{message} =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; + + my ($action, $token) = ($1, $2); + my $data = JSON->new->utf8->decode($3); + + my $child_pid = fork(); + if (!defined($child_pid)) { + $self->send_log( + code => $self->ACTION_FINISH_KO, + token => $token, + data => { message => "cannot fork: $!" } + ); + return undef; + } + + if ($child_pid == 0) { + $self->action_run(action => $action, token => $token, data => $data); + exit(0); + } else { + $self->send_log( + code => $self->ACTION_BEGIN, + token => $token, + data => { message => "proceed action" } + ); + } +} + +sub event { + while (1) { + my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $connector->{internal_socket}); + + $connector->{logger}->writeLogDebug("[engine] -class- Event: $message"); + + if ($message !~ /^\[ACK\]/) { + $connector->create_child(message => $message); + } + + last unless (centreon::gorgone::common::zmq_still_read(socket => $connector->{internal_socket})); + } +} + +sub run { + my ($self, %options) = @_; + + # Connect internal + $connector->{internal_socket} = centreon::gorgone::common::connect_com( + zmq_type => 'ZMQ_DEALER', + name => 'gorgoneengine', + logger => $self->{logger}, + type => $self->{config_core}->{internal_com_type}, + path => $self->{config_core}->{internal_com_path} + ); + centreon::gorgone::common::zmq_send_message( + socket => $connector->{internal_socket}, + action => 'ENGINEREADY', data => { }, + json_encode => 1 + ); + $self->{poll} = [ + { + socket => $connector->{internal_socket}, + events => ZMQ_POLLIN, + callback => \&event, + } + ]; + while (1) { + # we try to do all we can + my $rev = zmq_poll($self->{poll}, 5000); + if ($rev == 0 && $self->{stop} == 1) { + $self->{logger}->writeLogInfo("[engine] -class- $$ has quit"); + zmq_close($connector->{internal_socket}); + exit(0); + } + } +} + +1; diff --git a/gorgone/modules/centreon/engine/hooks.pm b/gorgone/modules/centreon/engine/hooks.pm new file mode 100644 index 00000000000..1941a681054 --- /dev/null +++ b/gorgone/modules/centreon/engine/hooks.pm @@ -0,0 +1,159 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package modules::centreon::engine::hooks; + +use warnings; +use strict; +use centreon::script::gorgonecore; +use modules::centreon::engine::class; + +my $NAME = 'engine'; +my $EVENTS = [ + { event => 'ENGINEREADY' }, + { event => 'ENGINECOMMAND', uri => '/command', method => 'POST' }, +]; + +my $config_core; +my $config; +my $engine = {}; +my $stop = 0; + +sub register { + my (%options) = @_; + + $config = $options{config}; + $config_core = $options{config_core}; + return ($NAME, $EVENTS); +} + +sub init { + my (%options) = @_; + + create_child(logger => $options{logger}); +} + +sub routing { + my (%options) = @_; + + my $data; + eval { + $data = JSON->new->utf8->decode($options{data}); + }; + if ($@) { + $options{logger}->writeLogError("[engine] -hooks- Cannot decode json data: $@"); + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => 30, token => $options{token}, + data => { msg => 'gorgoneengine: cannot decode json' }, + json_encode => 1 + ); + return undef; + } + + if ($options{action} eq 'ENGINEREADY') { + $engine->{ready} = 1; + return undef; + } + + if (centreon::script::gorgonecore::waiting_ready(ready => \$engine->{ready}) == 0) { + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => 30, token => $options{token}, + data => { msg => 'gorgoneengine: still no ready' }, + json_encode => 1 + ); + return undef; + } + + centreon::gorgone::common::zmq_send_message( + socket => $options{socket}, + identity => 'gorgoneengine', + action => $options{action}, + data => $options{data}, + token => $options{token}, + ); +} + +sub gently { + my (%options) = @_; + + $stop = 1; + $options{logger}->writeLogInfo("[engine] -hooks- Send TERM signal"); + if ($engine->{running} == 1) { + CORE::kill('TERM', $engine->{pid}); + } +} + +sub kill { + my (%options) = @_; + + if ($engine->{running} == 1) { + $options{logger}->writeLogInfo("[engine] -hooks- Send KILL signal for pool"); + CORE::kill('KILL', $engine->{pid}); + } +} + +sub kill_internal { + my (%options) = @_; + +} + +sub check { + my (%options) = @_; + + my $count = 0; + foreach my $pid (keys %{$options{dead_childs}}) { + # Not me + next if ($engine->{pid} != $pid); + + $engine = {}; + delete $options{dead_childs}->{$pid}; + if ($stop == 0) { + create_child(logger => $options{logger}); + } + } + + $count++ if (defined($engine->{running}) && $engine->{running} == 1); + + return $count; +} + +# Specific functions +sub create_child { + my (%options) = @_; + + $options{logger}->writeLogInfo("[engine] -hooks- Create module 'engine' process"); + my $child_pid = fork(); + if ($child_pid == 0) { + $0 = 'gorgone-engine'; + my $module = modules::centreon::engine::class->new( + logger => $options{logger}, + config_core => $config_core, + config => $config, + ); + $module->run(); + exit(0); + } + $options{logger}->writeLogInfo("[engine] -hooks- PID $child_pid (gorgone-engine)"); + $engine = { pid => $child_pid, ready => 0, running => 1 }; +} + +1; From 7d92b0b57f5abb992a5d327be634559fc9ab5e66 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 20 Aug 2019 16:09:24 +0200 Subject: [PATCH 049/948] update changelog --- gorgone/TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/TODO b/gorgone/TODO index fe9b0e1b037..002c87bfbed 100644 --- a/gorgone/TODO +++ b/gorgone/TODO @@ -1,6 +1,6 @@ -- Add a HTTP frontend module for clients (https and basic auth). Can try: https://metacpan.org/pod/HTTP::Server::Simple - Add legacy module: read centcore.cmd - Can chain agents and forward protocol - Add a core event: GETINFORMATION (events loaded, target loaded and module loaded) - gorgone-newtest: don't use centcore.cmd. use ssh system. - Use 4 code for all modules: 0 -> transaction progress, 1 -> transaction finished ok, 2 -> transaction finished ko +- Need to manage some legacy commands in proxy module with libssh (ENGINECOMMAND, COMMAND, copy files). Will be hardcoded From 024224369f7adad45c0a3e030ede5ed7fa3f11c0 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 23 Aug 2019 11:36:24 +0200 Subject: [PATCH 050/948] add ReusePort option in httpserver --- gorgone/modules/core/httpserver/class.pm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gorgone/modules/core/httpserver/class.pm b/gorgone/modules/core/httpserver/class.pm index c78c450b325..460d9e23f42 100644 --- a/gorgone/modules/core/httpserver/class.pm +++ b/gorgone/modules/core/httpserver/class.pm @@ -135,14 +135,16 @@ sub run { my $daemon; if ($self->{config}->{ssl} eq 'false') { $daemon = HTTP::Daemon->new( - LocalAddr => $self->{config}->{address} . ':' . $self->{config}->{port} + LocalAddr => $self->{config}->{address} . ':' . $self->{config}->{port}, + ReusePort => 1 ); } elsif ($self->{config}->{ssl} eq 'true') { $daemon = HTTP::Daemon::SSL->new( LocalAddr => $self->{config}->{address} . ':' . $self->{config}->{port}, SSL_cert_file => $self->{config}->{ssl_cert_file}, SSL_key_file => $self->{config}->{ssl_key_file}, - SSL_error_trap => \&ssl_error + SSL_error_trap => \&ssl_error, + ReusePort => 1 ); } From 6fbb52e1ecb391afb8d38dc7ed87034c8d0c2f01 Mon Sep 17 00:00:00 2001 From: Kevin Duret Date: Fri, 23 Aug 2019 17:00:39 +0200 Subject: [PATCH 051/948] enh(sec): deny root user execution by default --- gorgone/centreon/script.pm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gorgone/centreon/script.pm b/gorgone/centreon/script.pm index d6ce8564d5d..e03912d3619 100644 --- a/gorgone/centreon/script.pm +++ b/gorgone/centreon/script.pm @@ -39,7 +39,7 @@ $SIG{__DIE__} = sub { sub new { my ($class, $name, %options) = @_; - my %defaults = + my %defaults = ( config_file => "/etc/centreon/centreon-config.pm", log_file => undef, @@ -47,7 +47,7 @@ sub new { centstorage_db_conn => 0, severity => "info", noconfig => 0, - noroot => 0 + noroot => 1 ); my $self = {%defaults, %options}; @@ -78,7 +78,7 @@ sub init { die("Quit"); } } - + if ($self->{centreon_db_conn}) { $self->{cdb} = centreon::misc::db->new (db => $self->{centreon_config}->{centreon_db}, From b3388c199ff75f5405701284fd2c6ff70d8cbd6d Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 28 Aug 2019 11:07:35 +0200 Subject: [PATCH 052/948] update config --- gorgone/centreon/script/gorgonecore.pm | 5 +---- gorgone/config/gorgoned-poller.yml | 11 ++++++----- gorgone/config/gorgoned-poller2.yml | 8 ++++++-- gorgone/config/gorgoned.yml | 10 +++++++--- 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/gorgone/centreon/script/gorgonecore.pm b/gorgone/centreon/script/gorgonecore.pm index 47760322348..547c6c10e99 100644 --- a/gorgone/centreon/script/gorgonecore.pm +++ b/gorgone/centreon/script/gorgonecore.pm @@ -108,10 +108,7 @@ sub init { } $self->{id} = $config->{gorgonecore}->{id}; - if (!defined($self->{hostname}) || $self->{hostname} eq '') { - #$self->{id} = get_poller_id(dbh => $dbh, name => $self->{hostname}); - } - + $self->load_modules(); $self->set_signal_handlers(); diff --git a/gorgone/config/gorgoned-poller.yml b/gorgone/config/gorgoned-poller.yml index b4847d8d433..705440a3e99 100644 --- a/gorgone/config/gorgoned-poller.yml +++ b/gorgone/config/gorgoned-poller.yml @@ -26,8 +26,8 @@ gorgonecore: # in seconds purge_sessions_time: 3600 modules: - - name: gorgonepull - module: modules::gorgonepull::hooks + - name: pull + module: modules::core::pull::hooks enable: true target_type: tcp target_path: 127.0.0.1:5555 @@ -47,11 +47,12 @@ modules: # ping ping: 60 ping_timeout: 30 - - - name: gorgoneaction - module: modules::gorgoneaction::hooks + + - name: action + package: modules::core::action::hooks enable: true command_timeout: 60 # characters to delete in commands paranoid_mode: 1 paranoid_characters: + diff --git a/gorgone/config/gorgoned-poller2.yml b/gorgone/config/gorgoned-poller2.yml index fe144e09257..901514bcceb 100644 --- a/gorgone/config/gorgoned-poller2.yml +++ b/gorgone/config/gorgoned-poller2.yml @@ -23,11 +23,15 @@ gorgonecore: keysize: 32 # 16 bytes for AES vector: 0123456789012345 + # JWK format export thumbprint SHA256 + authorized_clients: + - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 + # in seconds sessions_time: 86400 # in seconds purge_sessions_time: 3600 modules: - - name: gorgoneaction - module: modules::gorgoneaction::hooks + - name: action + package: modules::core::action::hooks enable: true diff --git a/gorgone/config/gorgoned.yml b/gorgone/config/gorgoned.yml index f82fa7ba4d4..b113947cd7e 100644 --- a/gorgone/config/gorgoned.yml +++ b/gorgone/config/gorgoned.yml @@ -23,9 +23,9 @@ gorgonecore: gorgone_db_password: # If not set. Use 'hostname' function. hostname: - # If not set. Try from 'hostname' in database - # Set 'none', if you don't need it (for poller in push mode) - id: none + # If not set. + # Can be override by action: SETCOREID + id: privkey: keys/central/privkey.pem cipher: "Cipher::AES" @@ -79,6 +79,10 @@ modules: # ping each X seconds ping: 60 + - name: + package: modules::centreon::popo::hooks + resync_time: 600 + - name: engine package: modules::centreon::engine::hooks enable: true From 1e72f68801471f9790424c80879acbb688a90fcd Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 28 Aug 2019 11:19:59 +0200 Subject: [PATCH 053/948] update config --- gorgone/config/gorgoned.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/config/gorgoned.yml b/gorgone/config/gorgoned.yml index b113947cd7e..8878937898c 100644 --- a/gorgone/config/gorgoned.yml +++ b/gorgone/config/gorgoned.yml @@ -79,8 +79,8 @@ modules: # ping each X seconds ping: 60 - - name: - package: modules::centreon::popo::hooks + - name: pollers + package: modules::centreon::pollers::hooks resync_time: 600 - name: engine From 208e6539b77b71542f83a2873c7499eeb725cf08 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 28 Aug 2019 16:07:36 +0200 Subject: [PATCH 054/948] feat(core): wip on chaining system --- gorgone/centreon/gorgone/common.pm | 28 ++- gorgone/centreon/gorgone/module.pm | 12 ++ gorgone/centreon/script/gorgonecore.pm | 6 +- gorgone/config/gorgoned.yml | 1 + gorgone/docs/guide.rst | 23 ++- gorgone/modules/centreon/engine/class.pm | 3 +- gorgone/modules/centreon/engine/hooks.pm | 3 +- gorgone/modules/centreon/pollers/class.pm | 209 ++++++++++++++++++++ gorgone/modules/centreon/pollers/hooks.pm | 162 ++++++++++++++++ gorgone/modules/core/action/class.pm | 3 +- gorgone/modules/core/action/hooks.pm | 3 +- gorgone/modules/core/cron/hooks.pm | 3 +- gorgone/modules/core/httpserver/hooks.pm | 3 +- gorgone/modules/core/proxy/class.pm | 25 ++- gorgone/modules/core/proxy/hooks.pm | 225 ++++++++++++---------- gorgone/modules/core/pull/hooks.pm | 11 +- gorgone/modules/plugins/scom/hooks.pm | 3 +- gorgone/test-client.pl | 3 +- 18 files changed, 597 insertions(+), 129 deletions(-) create mode 100644 gorgone/modules/centreon/pollers/class.pm create mode 100644 gorgone/modules/centreon/pollers/hooks.pm diff --git a/gorgone/centreon/gorgone/common.pm b/gorgone/centreon/gorgone/common.pm index b352c6c3f6b..35d5b5eeba7 100644 --- a/gorgone/centreon/gorgone/common.pm +++ b/gorgone/centreon/gorgone/common.pm @@ -24,7 +24,7 @@ use strict; use warnings; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); -use JSON; +use JSON::XS; use File::Basename; use Crypt::PK::RSA; use Crypt::PRNG; @@ -314,6 +314,26 @@ sub constatus { return (1, { action => 'constatus', message => 'cannot get value' }, 'CONSTATUS'); } +sub setcoreid { + my (%options) = @_; + + my $data; + eval { + $data = JSON::XS->new->utf8->decode($options{data}); + }; + if ($@) { + return (1, { message => 'request not well formatted' }); + } + + if (!defined($data->{id})) { + return (1, { action => 'setcoreid', message => 'please set id for setcoreid' }); + } + + $options{logger}->writeLogInfo('[core] setcoreid changed ' . $data->{id}); + $options{gorgone}->{id} = $data->{id}; + return (0, { action => 'setcoreid', message => 'setcoreid changed' }); +} + sub ping { my (%options) = @_; @@ -327,7 +347,7 @@ sub putlog { my $data; eval { - $data = JSON->new->utf8->decode($options{data}); + $data = JSON::XS->new->utf8->decode($options{data}); }; if ($@) { return (1, { message => 'request not well formatted' }); @@ -351,7 +371,7 @@ sub getlog { my $data; eval { - $data = JSON->new->utf8->decode($options{data}); + $data = JSON::XS->new->utf8->decode($options{data}); }; if ($@) { return (1, { message => 'request not well formatted' }); @@ -438,7 +458,7 @@ sub json_encode { my $data; eval { - $data = JSON->new->utf8->encode($options{data}); + $data = JSON::XS->new->utf8->encode($options{data}); }; if ($@) { if (defined($options{logger})) { diff --git a/gorgone/centreon/gorgone/module.pm b/gorgone/centreon/gorgone/module.pm index c92a5f1050a..d5fe7e0f77d 100644 --- a/gorgone/centreon/gorgone/module.pm +++ b/gorgone/centreon/gorgone/module.pm @@ -37,6 +37,18 @@ sub generate_token { return centreon::gorgone::common::generate_token(); } +sub send_internal_action { + my ($self, %options) = @_; + + centreon::gorgone::common::zmq_send_message( + socket => $self->{internal_socket}, + action => $options{action}, + target => $options{target}, + data => $options{data}, + json_encode => 1 + ); +} + sub send_log { my ($self, %options) = @_; diff --git a/gorgone/centreon/script/gorgonecore.pm b/gorgone/centreon/script/gorgonecore.pm index 547c6c10e99..f32379762ca 100644 --- a/gorgone/centreon/script/gorgonecore.pm +++ b/gorgone/centreon/script/gorgonecore.pm @@ -208,7 +208,7 @@ sub load_modules { } # Load internal functions - foreach my $method_name (('putlog', 'getlog', 'kill', 'ping', 'constatus')) { + foreach my $method_name (('putlog', 'getlog', 'kill', 'ping', 'constatus', 'setcoreid')) { unless ($self->{internal_register}->{$method_name} = centreon::gorgone::common->can($method_name)) { $self->{logger}->writeLogError("[core] No function '$method_name'"); exit(1); @@ -223,7 +223,7 @@ sub message_run { return (undef, 1, { message => 'request not well formatted' }); } my ($action, $token, $target, $data) = ($1, $2, $3, $4); - if ($action !~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS)$/ && !defined($self->{modules_events}->{$action})) { + if ($action !~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID)$/ && !defined($self->{modules_events}->{$action})) { centreon::gorgone::common::add_history( dbh => $self->{db_gorgone}, code => 1, @@ -267,7 +267,7 @@ sub message_run { } } - if ($action =~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS)$/) { + if ($action =~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID)$/) { my ($code, $response, $response_type) = $self->{internal_register}->{lc($action)}->( gorgone => $self, gorgone_config => $config, diff --git a/gorgone/config/gorgoned.yml b/gorgone/config/gorgoned.yml index 8878937898c..da91561dd3c 100644 --- a/gorgone/config/gorgoned.yml +++ b/gorgone/config/gorgoned.yml @@ -81,6 +81,7 @@ modules: - name: pollers package: modules::centreon::pollers::hooks + enable: false resync_time: 600 - name: engine diff --git a/gorgone/docs/guide.rst b/gorgone/docs/guide.rst index d084ac2f00e..0ba2dd66afd 100644 --- a/gorgone/docs/guide.rst +++ b/gorgone/docs/guide.rst @@ -19,7 +19,7 @@ Installation Daemon uses following Perl modules: * ZMQ::LibZMQ4: repository 'centreon-stable' -* JSON: repository 'centos base' +* JSON::XS: repository 'centos base' * YAML: repository 'centos base' * DBD::SQLite: repository 'centos base' * DBD::mysql: repository 'centos base' @@ -38,7 +38,7 @@ Execute following commands: :: - # yum install 'perl(Schedule::Cron)' 'perl(Crypt::CBC)' 'perl(ZMQ::LibZMQ4)' 'perl(JSON)' 'perl(YAML)' \ + # yum install 'perl(Schedule::Cron)' 'perl(Crypt::CBC)' 'perl(ZMQ::LibZMQ4)' 'perl(JSON::XS)' 'perl(YAML)' \ 'perl(DBD::SQLite)' 'perl(DBD::mysql)' 'perl(UUID)' 'perl(HTTP::Daemon)' 'perl(HTTP::Daemon::SSL)' \ 'perl(HTTP::Status)' 'perl(MIME::Base64)' # yum install perl-CryptX-0.064-1.el7.x86_64 @@ -247,6 +247,25 @@ The client request: [PUTLOG] [TOKEN] [TARGET] { code => xxx, etime => xxx, token => xxxx, data => { some_datas } } +------------- +REGISTERNODES +------------- + +The request shouldn't be used by third-party program. It's commonly used by the internal modules. +The client request (no carriage returns. only for reading): +:: + + [REGISTERNODES] [TOKEN] [TARGET] { nodes => [ + { id => 20, type => 'pull' }, + { id => 100, type => 'push_ssh', address => 10.0.0.1, ssh_port => 22 }, + { + id => 150, type => 'push_zmq', address => 10.3.2.1, + server_pubkey => 'test.pem', client_pubkey => 'client_pubkey.pem', server_pubkey => 'server_pubkey.pem', cipher => 'Cipher::AES', keysize => 32, vector => '0123456789012345' + nodes => [400, 455] + } + ] + } + ============ Common codes ============ diff --git a/gorgone/modules/centreon/engine/class.pm b/gorgone/modules/centreon/engine/class.pm index 7fe168af8db..4af678a4a51 100644 --- a/gorgone/modules/centreon/engine/class.pm +++ b/gorgone/modules/centreon/engine/class.pm @@ -24,6 +24,7 @@ use base qw(centreon::gorgone::module); use strict; use warnings; +use JSON::XS; use centreon::gorgone::common; use centreon::misc::misc; use ZMQ::LibZMQ4; @@ -185,7 +186,7 @@ sub create_child { $options{message} =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; my ($action, $token) = ($1, $2); - my $data = JSON->new->utf8->decode($3); + my $data = JSON::XS->new->utf8->decode($3); my $child_pid = fork(); if (!defined($child_pid)) { diff --git a/gorgone/modules/centreon/engine/hooks.pm b/gorgone/modules/centreon/engine/hooks.pm index 1941a681054..93a3c1f7a6f 100644 --- a/gorgone/modules/centreon/engine/hooks.pm +++ b/gorgone/modules/centreon/engine/hooks.pm @@ -22,6 +22,7 @@ package modules::centreon::engine::hooks; use warnings; use strict; +use JSON::XS; use centreon::script::gorgonecore; use modules::centreon::engine::class; @@ -55,7 +56,7 @@ sub routing { my $data; eval { - $data = JSON->new->utf8->decode($options{data}); + $data = JSON::XS->new->utf8->decode($options{data}); }; if ($@) { $options{logger}->writeLogError("[engine] -hooks- Cannot decode json data: $@"); diff --git a/gorgone/modules/centreon/pollers/class.pm b/gorgone/modules/centreon/pollers/class.pm new file mode 100644 index 00000000000..fcae5955ddc --- /dev/null +++ b/gorgone/modules/centreon/pollers/class.pm @@ -0,0 +1,209 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package modules::centreon::pollers::class; + +use base qw(centreon::gorgone::module); + +use strict; +use warnings; +use centreon::gorgone::common; +use centreon::misc::objects::object; +use centreon::misc::http::http; +use ZMQ::LibZMQ4; +use ZMQ::Constants qw(:all); +use MIME::Base64; +use JSON::XS; + +my %handlers = (TERM => {}, HUP => {}); +my ($connector); + +sub new { + my ($class, %options) = @_; + + $connector = {}; + $connector->{internal_socket} = undef; + $connector->{module_id} = $options{module_id}; + $connector->{logger} = $options{logger}; + $connector->{config} = $options{config}; + $connector->{config_core} = $options{config_core}; + $connector->{config_db_centreon} = $options{config_db_centreon}; + $connector->{stop} = 0; + $connector->{register_pollers} = {}; + + $connector->{resync_time} = (defined($options{config}->{resync_time}) && $options{config}->{resync_time} =~ /(\d+)/) ? $1 : 600; + $connector->{last_resync_time} = -1; + + bless $connector, $class; + $connector->set_signal_handlers(); + return $connector; +} + +sub set_signal_handlers { + my $self = shift; + + $SIG{TERM} = \&class_handle_TERM; + $handlers{TERM}->{$self} = sub { $self->handle_TERM() }; + $SIG{HUP} = \&class_handle_HUP; + $handlers{HUP}->{$self} = sub { $self->handle_HUP() }; +} + +sub handle_HUP { + my $self = shift; + $self->{reload} = 0; +} + +sub handle_TERM { + my $self = shift; + $self->{logger}->writeLogInfo("[pollers] -class- $$ Receiving order to stop..."); + $self->{stop} = 1; +} + +sub class_handle_TERM { + foreach (keys %{$handlers{TERM}}) { + &{$handlers{TERM}->{$_}}(); + } +} + +sub class_handle_HUP { + foreach (keys %{$handlers{HUP}}) { + &{$handlers{HUP}->{$_}}(); + } +} + +sub action_pollersresync { + my ($self, %options) = @_; + + $options{token} = $self->generate_token() if (!defined($options{token})); + + $self->send_log(code => centreon::gorgone::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action pollersresync proceed' }); + + my $request = " + SELECT id, name, localhost, ns_ip_address, ssh_port + FROM nagios_server + WHERE ns_activate = '1' + "; + my ($status, $datas) = $self->{class_object}->custom_execute(request => $request, mode => 2); + if ($status == -1) { + $self->send_log(code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot find pollers configuration' }); + $self->{logger}->writeLogError("[pollers] -class- cannot find pollers configuration"); + return 1; + } + + my $core_id; + my $register_temp = {}; + my $register_nodes = []; + foreach (@$datas) { + if ($_->[2] == 1) { + $core_id = $_->[0]; + next; + } + + $self->{register_pollers}->{$_->[0]} = 1; + $register_temp->{$_->[0]} = 1; + push @{$register_nodes}, { id => $_->[0], type => 'push_ssh', address => $_->[3], ssh_port => $_[4] }; + } + my $unregister_nodes = []; + + foreach (keys %{$self->{register_pollers}}) { + if (!defined($register_temp->{$_})) { + push @{$unregister_nodes}, { id => $_ }; + delete $self->{register_pollers}->{$_}; + } + } + + $self->send_internal_action(action => 'REGISTERNODES', data => { nodes => $register_nodes } ); + $self->send_internal_action(action => 'UNREGISTERNODES', data => { nodes => $unregister_nodes } ); + $self->send_internal_action(action => 'SETCOREID', data => { id => $core_id } ) if (defined($core_id)); + + $self->{logger}->writeLogDebug("[pollers] -class- finish resync"); + $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => { message => 'action pollersresync finished' }); + return 0; +} + +sub event { + while (1) { + my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $connector->{internal_socket}); + + $connector->{logger}->writeLogDebug("[pollers] -class- Event: $message"); + if ($message =~ /^\[(.*?)\]/) { + if ((my $method = $connector->can('action_' . lc($1)))) { + $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; + my ($action, $token) = ($1, $2); + my $data = JSON::XS->new->utf8->decode($3); + $method->($connector, token => $token, data => $data); + } + } + + last unless (centreon::gorgone::common::zmq_still_read(socket => $connector->{internal_socket})); + } +} + +sub run { + my ($self, %options) = @_; + + # Database creation. We stay in the loop still there is an error + $self->{db_centreon} = centreon::misc::db->new( + dsn => $self->{config_db_centreon}->{dsn}, + user => $self->{config_db_centreon}->{username}, + password => $self->{config_db_centreon}->{password}, + force => 2, + logger => $self->{logger} + ); + ##### Load objects ##### + $self->{class_object} = centreon::misc::objects::object->new(logger => $self->{logger}, db_centreon => $self->{db_centreon}); + + # Connect internal + $connector->{internal_socket} = centreon::gorgone::common::connect_com( + zmq_type => 'ZMQ_DEALER', + name => 'gorgonepollers', + logger => $self->{logger}, + type => $self->{config_core}->{internal_com_type}, + path => $self->{config_core}->{internal_com_path} + ); + centreon::gorgone::common::zmq_send_message( + socket => $connector->{internal_socket}, + action => 'POLLERSREADY', data => { }, + json_encode => 1 + ); + $self->{poll} = [ + { + socket => $connector->{internal_socket}, + events => ZMQ_POLLIN, + callback => \&event, + } + ]; + while (1) { + # we try to do all we can + my $rev = zmq_poll($self->{poll}, 5000); + if (defined($rev) && $rev == 0 && $self->{stop} == 1) { + $self->{logger}->writeLogInfo("[pollers] -class- $$ has quit"); + zmq_close($connector->{internal_socket}); + exit(0); + } + + if (time() - $self->{resync_time} > $self->{last_resync_time}) { + $self->{last_resync_time} = time(); + $self->action_pollersresync(); + } + } +} + +1; diff --git a/gorgone/modules/centreon/pollers/hooks.pm b/gorgone/modules/centreon/pollers/hooks.pm new file mode 100644 index 00000000000..1719357dcc1 --- /dev/null +++ b/gorgone/modules/centreon/pollers/hooks.pm @@ -0,0 +1,162 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package modules::centreon::pollers::hooks; + +use warnings; +use strict; +use JSON::XS; +use centreon::script::gorgonecore; +use modules::centreon::pollers::class; + +my $NAME = 'pollers'; +my $EVENTS = [ + { event => 'POLLERSREADY' }, +]; + +my $config_core; +my $config; +my ($config_db_centreon); +my $pollers = {}; +my $stop = 0; + +sub register { + my (%options) = @_; + + $config = $options{config}; + $config_core = $options{config_core}; + $config_db_centreon = $options{config_db_centreon}; + return ($NAME, $EVENTS); +} + +sub init { + my (%options) = @_; + + create_child(logger => $options{logger}); +} + +sub routing { + my (%options) = @_; + + my $data; + eval { + $data = JSON::XS->new->utf8->decode($options{data}); + }; + if ($@) { + $options{logger}->writeLogError("[pollers] -hooks- Cannot decode json data: $@"); + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => 10, token => $options{token}, + data => { message => 'gorgonepollers: cannot decode json' }, + json_encode => 1 + ); + return undef; + } + + if ($options{action} eq 'POLLERSREADY') { + $pollers->{ready} = 1; + return undef; + } + + if (centreon::script::gorgonecore::waiting_ready(ready => \$pollers->{ready}) == 0) { + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => 10, token => $options{token}, + data => { message => 'gorgonepollers: still no ready' }, + json_encode => 1 + ); + return undef; + } + + centreon::gorgone::common::zmq_send_message( + socket => $options{socket}, + identity => 'gorgonepollers', + action => $options{action}, + data => $options{data}, + token => $options{token}, + ); +} + +sub gently { + my (%options) = @_; + + $stop = 1; + $options{logger}->writeLogInfo("[pollers] -hooks- Send TERM signal"); + if ($pollers->{running} == 1) { + CORE::kill('TERM', $pollers->{pid}); + } +} + +sub kill { + my (%options) = @_; + + if ($pollers->{running} == 1) { + $options{logger}->writeLogInfo("[pollers] -hooks- Send KILL signal for pool"); + CORE::kill('KILL', $pollers->{pid}); + } +} + +sub kill_internal { + my (%options) = @_; + +} + +sub check { + my (%options) = @_; + + my $count = 0; + foreach my $pid (keys %{$options{dead_childs}}) { + # Not me + next if ($pollers->{pid} != $pid); + + $pollers = {}; + delete $options{dead_childs}->{$pid}; + if ($stop == 0) { + create_child(logger => $options{logger}); + } + } + + $count++ if (defined($pollers->{running}) && $pollers->{running} == 1); + + return $count; +} + +# Specific functions +sub create_child { + my (%options) = @_; + + $options{logger}->writeLogInfo("[pollers] -hooks- Create module 'pollers' process"); + my $child_pid = fork(); + if ($child_pid == 0) { + $0 = 'gorgone-pollers'; + my $module = modules::centreon::pollers::class->new( + logger => $options{logger}, + config_core => $config_core, + config => $config, + config_db_centreon => $config_db_centreon, + ); + $module->run(); + exit(0); + } + $options{logger}->writeLogInfo("[pollers] -hooks- PID $child_pid (gorgone-pollers)"); + $pollers = { pid => $child_pid, ready => 0, running => 1 }; +} + +1; diff --git a/gorgone/modules/core/action/class.pm b/gorgone/modules/core/action/class.pm index 517f83917eb..873687cbbba 100644 --- a/gorgone/modules/core/action/class.pm +++ b/gorgone/modules/core/action/class.pm @@ -28,6 +28,7 @@ use centreon::gorgone::common; use centreon::misc::misc; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); +use JSON::XS; my %handlers = (TERM => {}, HUP => {}); my ($connector); @@ -160,7 +161,7 @@ sub create_child { $options{message} =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; my ($action, $token) = ($1, $2); - my $data = JSON->new->utf8->decode($3); + my $data = JSON::XS->new->utf8->decode($3); my $child_pid = fork(); if (!defined($child_pid)) { diff --git a/gorgone/modules/core/action/hooks.pm b/gorgone/modules/core/action/hooks.pm index 39613120a90..69b6d19aada 100644 --- a/gorgone/modules/core/action/hooks.pm +++ b/gorgone/modules/core/action/hooks.pm @@ -24,6 +24,7 @@ use warnings; use strict; use centreon::script::gorgonecore; use modules::core::action::class; +use JSON::XS; my $NAME = 'action'; my $EVENTS = [ @@ -55,7 +56,7 @@ sub routing { my $data; eval { - $data = JSON->new->utf8->decode($options{data}); + $data = JSON::XS->new->utf8->decode($options{data}); }; if ($@) { $options{logger}->writeLogError("[action] -hooks- Cannot decode json data: $@"); diff --git a/gorgone/modules/core/cron/hooks.pm b/gorgone/modules/core/cron/hooks.pm index de60a606317..e3d4b136ced 100644 --- a/gorgone/modules/core/cron/hooks.pm +++ b/gorgone/modules/core/cron/hooks.pm @@ -24,6 +24,7 @@ use warnings; use strict; use centreon::script::gorgonecore; use modules::core::cron::class; +use JSON::XS; my $NAME = 'cron'; my $EVENTS = [ @@ -58,7 +59,7 @@ sub routing { my $data; eval { - $data = JSON->new->utf8->decode($options{data}); + $data = JSON::XS->new->utf8->decode($options{data}); }; if ($@) { $options{logger}->writeLogError("[cron] -hooks- Cannot decode json data: $@"); diff --git a/gorgone/modules/core/httpserver/hooks.pm b/gorgone/modules/core/httpserver/hooks.pm index 9a0df9267e4..161b1f0f91b 100644 --- a/gorgone/modules/core/httpserver/hooks.pm +++ b/gorgone/modules/core/httpserver/hooks.pm @@ -24,6 +24,7 @@ use warnings; use strict; use centreon::script::gorgonecore; use modules::core::httpserver::class; +use JSON::XS; my $NAME = 'httpserver'; my $EVENTS = [ @@ -54,7 +55,7 @@ sub routing { my $data; eval { - $data = JSON->new->utf8->decode($options{data}); + $data = JSON::XS->new->utf8->decode($options{data}); }; if ($@) { $options{logger}->writeLogError("[httpserver] -hooks- Cannot decode json data: $@"); diff --git a/gorgone/modules/core/proxy/class.pm b/gorgone/modules/core/proxy/class.pm index 86f53ff26fb..e557eadfc0f 100644 --- a/gorgone/modules/core/proxy/class.pm +++ b/gorgone/modules/core/proxy/class.pm @@ -20,19 +20,24 @@ package modules::core::proxy::class; +use base qw(centreon::gorgone::module); + use strict; use warnings; use centreon::gorgone::common; use centreon::gorgone::clientzmq; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); +use JSON::XS; my %handlers = (TERM => {}, HUP => {}); -my ($connector, $socket); +my ($connector); sub new { my ($class, %options) = @_; + $connector = {}; + $connector->{internal_socket} = undef; $connector->{logger} = $options{logger}; $connector->{core_id} = $options{core_id}; $connector->{pool_id} = $options{pool_id}; @@ -111,7 +116,7 @@ sub read_message { my ($action, $token) = ($1, $2); centreon::gorgone::common::zmq_send_message( - socket => $socket, + socket => $connector->{internal_socket}, action => 'PONG', token => $token, target => '', @@ -122,7 +127,7 @@ sub read_message { elsif ($options{data} =~ /^\[ACK\]\s+\[(.*?)\]\s+(.*)/m) { my $data; eval { - $data = JSON->new->utf8->decode($2); + $data = JSON::XS->new->utf8->decode($2); }; if ($@) { return undef; @@ -130,7 +135,7 @@ sub read_message { if (defined($data->{data}->{action}) && $data->{data}->{action} eq 'getlog') { centreon::gorgone::common::zmq_send_message( - socket => $socket, + socket => $connector->{internal_socket}, action => 'SETLOGS', token => $1, target => '', @@ -201,10 +206,10 @@ sub proxy { sub event_internal { while (1) { - my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $socket); + my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $connector->{internal_socket}); proxy(message => $message); - last unless (centreon::gorgone::common::zmq_still_read(socket => $socket)); + last unless (centreon::gorgone::common::zmq_still_read(socket => $connector->{internal_socket})); } } @@ -212,7 +217,7 @@ sub run { my ($self, %options) = @_; # Connect internal - $socket = centreon::gorgone::common::connect_com( + $connector->{internal_socket} = centreon::gorgone::common::connect_com( zmq_type => 'ZMQ_DEALER', name => 'gorgoneproxy-' . $self->{pool_id}, logger => $self->{logger}, @@ -220,13 +225,13 @@ sub run { path => $self->{config_core}{internal_com_path} ); centreon::gorgone::common::zmq_send_message( - socket => $socket, + socket => $connector->{internal_socket}, action => 'PROXYREADY', data => { pool_id => $self->{pool_id} }, json_encode => 1 ); my $poll = { - socket => $socket, + socket => $connector->{internal_socket}, events => ZMQ_POLLIN, callback => \&event_internal, }; @@ -251,7 +256,7 @@ sub run { if ($rev == 0 && $self->{stop} == 1) { $self->{logger}->writeLogInfo("[proxy] -class- $$ has quit"); - zmq_close($socket); + zmq_close($connector->{internal_socket}); exit(0); } } diff --git a/gorgone/modules/core/proxy/hooks.pm b/gorgone/modules/core/proxy/hooks.pm index 6fed7b9c2ca..1889936a2c7 100644 --- a/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/modules/core/proxy/hooks.pm @@ -22,6 +22,7 @@ package modules::core::proxy::hooks; use warnings; use strict; +use JSON::XS; use centreon::script::gorgonecore; use centreon::gorgone::common; use modules::core::proxy::class; @@ -31,8 +32,8 @@ my $EVENTS = [ { event => 'PROXYREADY' }, { event => 'SETLOGS' }, # internal. Shouldn't be used by third party clients { event => 'PONG' }, # internal. Shouldn't be used by third party clients - { event => 'REGISTERNODE' }, # internal. Shouldn't be used by third party clients - { event => 'UNREGISTERNODE' }, # internal. Shouldn't be used by third party clients + { event => 'REGISTERNODES' }, # internal. Shouldn't be used by third party clients + { event => 'UNREGISTERNODES' }, # internal. Shouldn't be used by third party clients { event => 'ADDPOLLER', uri => '/poller', method => 'POST' }, ]; @@ -40,7 +41,7 @@ my $config_core; my $config; my $synctime_error = 0; -my $synctime_pollers = {}; # get last time retrieved +my $synctime_nodes = {}; # get last time retrieved my $synctime_lasttime; my $synctime_option; my $synctimeout_option; @@ -48,8 +49,8 @@ my $ping_option; my $ping_time = 0; my $last_pong = {}; -my $register_pollers = {}; -my $last_pollers = {}; # Last values from centreon database and the type +my $register_nodes = {}; +my $register_subnodes = {}; my $pools = {}; my $pools_pid = {}; my $poller_pool = {}; @@ -76,7 +77,6 @@ sub init { $core_id = $options{id}; $external_socket = $options{external_socket}; $internal_socket = $options{internal_socket}; - $last_pollers = get_pollers(dbh => $options{dbh}); for my $pool_id (1..$config->{pool}) { create_child(pool_id => $pool_id, logger => $options{logger}); } @@ -87,7 +87,7 @@ sub routing { my $data; eval { - $data = JSON->new->utf8->decode($options{data}); + $data = JSON::XS->new->utf8->decode($options{data}); }; if ($@) { $options{logger}->writeLogError("[proxy] -hooks- Cannot decode json data: $@"); @@ -107,23 +107,13 @@ sub routing { return undef; } - if ($options{action} eq 'UNREGISTERNODE') { - $options{logger}->writeLogInfo("[proxy] -hooks- Poller '" . $data->{id} . "' is unregistered"); - if (defined($register_pollers->{$data->{id}})) { - delete $register_pollers->{$data->{id}}; - delete $synctime_pollers->{$data->{id}}; - } + if ($options{action} eq 'UNREGISTERNODES') { + unregister_nodes(%options, data => $data); return undef; } - if ($options{action} eq 'REGISTERNODE') { - $options{logger}->writeLogInfo("[proxy] -hooks- Poller '" . $data->{id} . "' is registered"); - $register_pollers->{$data->{id}} = 1; - $last_pong->{$data->{id}} = 0 if (!defined($last_pong->{$data->{id}})); - if ($synctime_error == 0 && !defined($synctime_pollers->{$options{target}}) && - !defined($synctime_pollers->{$data->{id}})) { - $synctime_pollers->{$data->{id}} = { ctime => 0, in_progress => 0, in_progress_time => -1, last_id => 0 }; - } + if ($options{action} eq 'REGISTERNODES') { + register_nodes(%options, data => $data); return undef; } @@ -138,37 +128,18 @@ sub routing { } if (!defined($options{target}) || - (!defined($last_pollers->{$options{target}}) && !defined($register_pollers->{$options{target}}))) { + (!defined($register_subnodes->{$options{target}}) && !defined($register_nodes->{$options{target}}))) { centreon::gorgone::common::add_history( dbh => $options{dbh}, code => 20, token => $options{token}, - data => { message => 'proxy - need a valid poller id' }, + data => { message => 'proxy - need a valid node id' }, json_encode => 1 ); return undef; } if ($options{action} eq 'GETLOG') { - if ($synctime_error == -1 || get_sync_time(dbh => $options{dbh}) == -1) { - centreon::gorgone::common::add_history( - dbh => $options{dbh}, - code => 20, token => $options{token}, - data => { message => 'proxy - problem to getlog' }, - json_encode => 1 - ); - return undef; - } - - if ($synctime_pollers->{$options{target}}->{in_progress} == 1) { - centreon::gorgone::common::add_history( - dbh => $options{dbh}, - code => 20, token => $options{token}, - data => { message => 'proxy - getlog already in progress' }, - json_encode => 1 - ); - return undef; - } - if (defined($last_pollers->{$options{target}}) && $last_pollers->{$options{target}}->{type} == 2) { + if (defined($register_nodes->{$options{target}}) && $register_nodes->{$options{target}}->{type} eq 'push_ssh') { centreon::gorgone::common::add_history( dbh => $options{dbh}, code => 20, token => $options{token}, @@ -177,17 +148,40 @@ sub routing { ); return undef; } - - # We put the good time to get - my $ctime = $synctime_pollers->{$options{target}}->{ctime}; - my $last_id = $synctime_pollers->{$options{target}}->{last_id}; - $options{data} = centreon::gorgone::common::json_encode(data => { ctime => $ctime, id => $last_id }); - $synctime_pollers->{$options{target}}->{in_progress} = 1; - $synctime_pollers->{$options{target}}->{in_progress_time} = time(); + + if (defined($register_nodes->{$options{target}})) { + if ($synctime_nodes->{$options{target}}->{synctime_error} == -1 || get_sync_time(dbh => $options{dbh}, node_id => $options{target}) == -1) { + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => 20, token => $options{token}, + data => { message => 'proxy - problem to getlog' }, + json_encode => 1 + ); + return undef; + } + + if ($synctime_nodes->{$options{target}}->{in_progress} == 1) { + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => 20, token => $options{token}, + data => { message => 'proxy - getlog already in progress' }, + json_encode => 1 + ); + return undef; + } + + + # We put the good time to get + my $ctime = $synctime_nodes->{$options{target}}->{ctime}; + my $last_id = $synctime_nodes->{$options{target}}->{last_id}; + $options{data} = centreon::gorgone::common::json_encode(data => { ctime => $ctime, id => $last_id }); + $synctime_nodes->{$options{target}}->{in_progress} = 1; + $synctime_nodes->{$options{target}}->{in_progress_time} = time(); + } } # Mode zmq pull - if (defined($register_pollers->{$options{target}})) { + if ($register_nodes->{$options{target}}->{type} eq 'pull') { pull_request(%options, data_decoded => $data); return undef; } @@ -268,22 +262,21 @@ sub check { } # We put synclog request in timeout - foreach (keys %{$synctime_pollers}) { - if ($synctime_pollers->{$_}->{in_progress} == 1 && - time() - $synctime_pollers->{$_}->{in_progress_time} > $synctimeout_option) { + foreach (keys %{$synctime_nodes}) { + if ($synctime_nodes->{$_}->{in_progress} == 1 && + time() - $synctime_nodes->{$_}->{in_progress_time} > $synctimeout_option) { centreon::gorgone::common::add_history( dbh => $options{dbh}, code => 20, data => { message => "proxy - getlog in timeout for '$_'" }, json_encode => 1 ); - $synctime_pollers->{$_}->{in_progress} = 0; + $synctime_nodes->{$_}->{in_progress} = 0; } } # We check if we need synclogs - if ($stop == 0 && - ($synctime_error == 0 || get_sync_time(dbh => $options{dbh}) == 0) && + if ($stop == 0 && time() - $synctime_lasttime > $synctime_option) { $synctime_lasttime = time(); full_sync_history(dbh => $options{dbh}); @@ -312,7 +305,7 @@ sub setlogs { ); return undef; } - if ($synctime_pollers->{$options{data}->{data}->{id}}->{in_progress} == 0) { + if ($synctime_nodes->{$options{data}->{data}->{id}}->{in_progress} == 0) { centreon::gorgone::common::add_history( dbh => $options{dbh}, code => 20, token => $options{token}, @@ -324,7 +317,7 @@ sub setlogs { $options{logger}->writeLogInfo("[proxy] -hooks- Received setlogs for '$options{data}->{data}->{id}'"); - $synctime_pollers->{$options{data}->{data}->{id}}->{in_progress} = 0; + $synctime_nodes->{$options{data}->{data}->{id}}->{in_progress} = 0; my $ctime_recent = 0; my $last_id = 0; @@ -345,8 +338,8 @@ sub setlogs { } if ($status == 0 && update_sync_time(dbh => $options{dbh}, id => $options{data}->{data}->{id}, last_id => $last_id, ctime => $ctime_recent) == 0) { $options{dbh}->commit(); - $synctime_pollers->{$options{data}->{data}->{id}}->{last_id} = $last_id if ($last_id != 0); - $synctime_pollers->{$options{data}->{data}->{id}}->{ctime} = $ctime_recent if ($ctime_recent != 0); + $synctime_nodes->{$options{data}->{data}->{id}}->{last_id} = $last_id if ($last_id != 0); + $synctime_nodes->{$options{data}->{data}->{id}}->{ctime} = $ctime_recent if ($ctime_recent != 0); } else { $options{dbh}->rollback(); } @@ -356,29 +349,25 @@ sub setlogs { sub ping_send { my (%options) = @_; - foreach my $id (keys %{$last_pollers}) { - if ($last_pollers->{$id}->{type} == 1) { + foreach my $id (keys %{$register_nodes}) { + if ($register_nodes->{$id}->{type} eq 'push_zmq') { routing(socket => $internal_socket, action => 'PING', target => $id, data => '{}', dbh => $options{dbh}); + } elsif ($register_nodes->{$id}->{type} eq 'pull') { + routing(action => 'PING', target => $id, data => '{}', dbh => $options{dbh}); } } - - foreach my $id (keys %{$register_pollers}) { - routing(action => 'PING', target => $id, data => '{}', dbh => $options{dbh}); - } } sub full_sync_history { my (%options) = @_; - foreach my $id (keys %{$last_pollers}) { - if ($last_pollers->{$id}->{type} == 1) { + foreach my $id (keys %{$register_nodes}) { + if ($register_nodes->{$id}->{type} eq 'push_zmq') { routing(socket => $internal_socket, action => 'GETLOG', target => $id, data => '{}', dbh => $options{dbh}); + } elsif ($register_nodes->{$id}->{type} eq 'pull') { + routing(action => 'GETLOG', target => $id, data => '{}', dbh => $options{dbh}); } } - - foreach my $id (keys %{$register_pollers}) { - routing(action => 'GETLOG', target => $id, data => '{}', dbh => $options{dbh}); - } } sub update_sync_time { @@ -386,9 +375,9 @@ sub update_sync_time { # Nothing to update (no insert before) return 0 if ($options{ctime} == 0); - + my $status; - if ($synctime_pollers->{$options{id}}->{last_id} == 0) { + if ($synctime_nodes->{$options{id}}->{last_id} == 0) { ($status) = $options{dbh}->query("INSERT INTO gorgone_synchistory (`id`, `ctime`, `last_id`) VALUES (" . $options{dbh}->quote($options{id}) . ", " . $options{dbh}->quote($options{ctime}) . ", " . $options{dbh}->quote($options{last_id}) . ")"); } else { ($status) = $options{dbh}->query("UPDATE gorgone_synchistory SET `ctime` = " . $options{dbh}->quote($options{ctime}) . ", `last_id` = " . $options{dbh}->quote($options{last_id}) . " WHERE `id` = " . $options{dbh}->quote($options{id})); @@ -399,37 +388,23 @@ sub update_sync_time { sub get_sync_time { my (%options) = @_; - my ($status, $sth) = $options{dbh}->query("SELECT * FROM gorgone_synchistory"); + my ($status, $sth) = $options{dbh}->query("SELECT * FROM gorgone_synchistory WHERE id = '" . $options{node_id} . "'"); if ($status == -1) { - $synctime_error = -1; + $synctime_nodes->{$options{node_id}}->{synctime_error} = -1; return -1; } - $synctime_error = 0; - while (my $row = $sth->fetchrow_hashref()) { - $synctime_pollers->{$row->{id}} = { ctime => $row->{ctime}, in_progress => 0, in_progress_time => -1, last_id => $row->{last_id} }; + $synctime_nodes->{$options{node_id}}->{synctime_error} = 0; + if (my $row = $sth->fetchrow_hashref()) { + $synctime_nodes->{$row->{id}}->{ctime} = $row->{ctime}; + $synctime_nodes->{$row->{id}}->{in_progress} = 0; + $synctime_nodes->{$row->{id}}->{in_progress_time} = -1; + $synctime_nodes->{$row->{id}}->{last_id} = $row->{last_id}; } return 0; } -sub get_pollers { - my (%options) = @_; - # TODO method - # type 1 = 'zmq', type 2 = 'ssh' - - my $pollers = {}; - foreach (([1, 1], [2, 1], [10, 1], [166, 2], [140, 1])) { - $pollers->{${$_}[0]} = { type => ${$_}[1] }; - $synctime_pollers->{${$_}[0]} = { ctime => 0, in_progress => 0, in_progress_time => -1, last_id => 0 }; - $last_pong->{${$_}[0]} = 0 if (!defined($last_pong->{${$_}[0]})); - } - - get_sync_time(dbh => $options{dbh}); - - return $pollers; -} - sub rr_pool { my (%options) = @_; @@ -505,4 +480,60 @@ sub get_constatus_result { return $result; } +sub unregister_nodes { + my (%options) = @_; + + return if (!defined($options{data}->{nodes})); + + foreach my $node (@{$options{data}->{nodes}}) { + $options{logger}->writeLogInfo("[proxy] -hooks- Poller '" . $node->{id} . "' is unregistered"); + if (defined($register_nodes->{$node->{id}}) && $register_nodes->{$node->{id}}->{nodes}) { + foreach my $subnode_id (@{$register_nodes->{$node->{id}}->{nodes}}) { + delete $register_subnodes->{$subnode_id} + if ($register_subnodes->{$subnode_id} eq $node->{id}); + } + } + + if (defined($register_nodes->{$node->{id}})) { + delete $register_nodes->{$node->{id}}; + delete $synctime_nodes->{$node->{id}}; + } + } +} + +sub register_nodes { + my (%options) = @_; + + return if (!defined($options{data}->{nodes})); + + foreach my $node (@{$options{data}->{nodes}}) { + if (defined($register_nodes->{$node->{id}})) { + # we remove subnodes before + if ($register_nodes->{$node->{id}}->{type} ne 'push_ssh') { + foreach my $subnode_id (keys %$register_subnodes) { + delete $register_subnodes->{$subnode_id} + if ($register_subnodes->{$subnode_id} eq $node->{id}); + } + } + } + + $register_nodes->{$node->{id}} = $node; + if (defined($node->{nodes})) { + foreach my $subnode_id (@{$node->{nodes}}) { + $register_subnodes->{$subnode_id} = $node->{id}; + } + } + + $options{logger}->writeLogInfo("[proxy] -hooks- Poller '" . $node->{id} . "' is registered"); + + if ($node->{type} eq 'push_zmq' || $node->{type} eq 'pull') { + $last_pong->{$node->{id}} = 0 if (!defined($last_pong->{$node->{id}})); + if (!defined($synctime_nodes->{$node->{id}})) { + $synctime_nodes->{$node->{id}} = { ctime => 0, in_progress => 0, in_progress_time => -1, last_id => 0, synctime_error => 0 }; + get_sync_time(node_id => $node->{id}); + } + } + } +} + 1; diff --git a/gorgone/modules/core/pull/hooks.pm b/gorgone/modules/core/pull/hooks.pm index 000493f4df3..bf218cbf26d 100644 --- a/gorgone/modules/core/pull/hooks.pm +++ b/gorgone/modules/core/pull/hooks.pm @@ -23,6 +23,7 @@ package modules::core::pull::hooks; use warnings; use strict; use centreon::gorgone::clientzmq; +use JSON::XS; my $NAME = 'pull'; my $EVENTS = []; @@ -71,8 +72,8 @@ sub init { $client->init(callback => \&read_message); $client->send_message( - action => 'REGISTERNODE', - data => { id => $config_core->{id} }, + action => 'REGISTERNODES', + data => { nodes => [ { id => $config_core->{id}, type => 'pull' } ] }, json_encode => 1 ); centreon::gorgone::common::add_zmq_pollin( @@ -92,8 +93,8 @@ sub gently { $stop = 1; $client->send_message( - action => 'UNREGISTERNODE', - data => { id => $config_core->{id} }, + action => 'UNREGISTERNODES', + data => { nodes => [ { id => $config_core->{id} } ] }, json_encode => 1 ); $client->close(); @@ -131,7 +132,7 @@ sub transmit_back { if ($options{message} =~ /^\[ACK\]\s+\[(.*?)\]\s+(.*)/m) { my $data; eval { - $data = JSON->new->utf8->decode($2); + $data = JSON::XS->new->utf8->decode($2); }; if ($@) { return $options{message}; diff --git a/gorgone/modules/plugins/scom/hooks.pm b/gorgone/modules/plugins/scom/hooks.pm index 55ec7591605..2415302e281 100644 --- a/gorgone/modules/plugins/scom/hooks.pm +++ b/gorgone/modules/plugins/scom/hooks.pm @@ -22,6 +22,7 @@ package modules::plugins::scom::hooks; use warnings; use strict; +use JSON::XS; use centreon::script::gorgonecore; use modules::plugins::scom::class; @@ -64,7 +65,7 @@ sub routing { my $data; eval { - $data = JSON->new->utf8->decode($options{data}); + $data = JSON::XS->new->utf8->decode($options{data}); }; if ($@) { $options{logger}->writeLogError("[scom] -hooks- Cannot decode json data: $@"); diff --git a/gorgone/test-client.pl b/gorgone/test-client.pl index 137e21381be..a928daa2ec4 100644 --- a/gorgone/test-client.pl +++ b/gorgone/test-client.pl @@ -23,6 +23,7 @@ use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); +use JSON::XS; use UUID; use Data::Dumper; use Sys::Hostname; @@ -77,7 +78,7 @@ sub read_response_result { my $data; eval { - $data = JSON->new->utf8->decode($2); + $data = JSON::XS->new->utf8->decode($2); }; if ($@) { return undef; From f338af4c191bda6d9e7e9b7e3fe6d45cfe3910a7 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 28 Aug 2019 17:29:43 +0200 Subject: [PATCH 055/948] fix(doc): json format fix --- gorgone/docs/guide.rst | 102 ++++++++++++++++++++--------------------- 1 file changed, 50 insertions(+), 52 deletions(-) diff --git a/gorgone/docs/guide.rst b/gorgone/docs/guide.rst index 0ba2dd66afd..c1bc88bcbce 100644 --- a/gorgone/docs/guide.rst +++ b/gorgone/docs/guide.rst @@ -85,7 +85,7 @@ Third-party clients connected had to use the zeromq library and the following pr :: - [ACK] [] { "code" => 1, "data" => { "message" => "handshake issue" } } + [ACK] [] { "code": 1, "data": { "message": "handshake issue" } } * If uncrypted message result is "HELO", server accepts the connection if the clientpubkey is authorized. It creates symmetric key and send the following message crypted with client pubkey: @@ -119,7 +119,7 @@ After a successful handshake, client requests uses the following syntax: For each client requests, the server get an immediate response: :: - [ACK] [TOKEN] { "code" => x, "data" => { "message" => "xxxxx" } } + [ACK] [TOKEN] { "code": "x", "data": { "message": "xxxxx" } } * TOKEN: a uniq ID to follow the request * DATA: a json stream @@ -154,19 +154,19 @@ An example of the json stream: :: { - code => 1, - data => { - action => 'constatus', - mesage => 'ok', - data => { - last_ping => xxxx, - entries => { - 1 => xxx, - 2 => xxx, - ... - } - } - } + "code": 1, + "data": { + "action": "constatus", + "mesage": "ok", + "data": { + "last_ping": "xxxx", + "entries": { + "1": "xxx", + "2": "xxx", + ... + } + } + } } 'last_ping' and 'entries' values are unix timestamp in seconds. The 'last_ping' is the date when the daemon had launched a ping broadcast to the poller connected. @@ -182,7 +182,7 @@ An example: when you request a command execution, the server gives you a direct The client request: :: - [GETLOG] [TOKEN] [TARGET] { code => 'xx', ctime => 'xx', etime => 'xx', token => 'xx', id => 'xx' } + [GETLOG] [TOKEN] [TARGET] { "code": "xx", "ctime": "xx", "etime": "xx", "token": "xx", "id": "xx" } At least one of the 5 values must be defined: @@ -203,30 +203,30 @@ An example of the json stream: :: { - code => 1, - data => { - action => 'getlog', - mesage => 'ok', - result => { - 10 => { - id => 10, - token => 'xxxx', - code => 1, - etime => 1419252684, - ctime => 1419252686, - data => xxxxx, - }, - 100 => { - id => 100, - token => 'xxxx', - code => 1, - etime => 1419252688, - ctime => 1419252690, - data => xxxxx, - }, - ... - } - } + "code": 1, + "data": { + "action": "getlog", + "message": "ok", + "result": { + "10": { + "id": 10, + "token": "xxxx", + "code": 1, + "etime": 1419252684, + "ctime": 1419252686, + "data": xxxxx, + }, + "100" => { + "id": 100, + "token": "xxxx", + "code": 1, + "etime": 1419252688, + "ctime": 1419252690, + "data": xxxxx, + }, + ... + } + } } Each 'gorgoned' nodes store its logs. But every 5 minutes (by default), the central server gets the new logs of its connected nodes and stores it. @@ -245,7 +245,7 @@ The request shouldn't be used by third-party program. It's commonly used by the The client request: :: - [PUTLOG] [TOKEN] [TARGET] { code => xxx, etime => xxx, token => xxxx, data => { some_datas } } + [PUTLOG] [TOKEN] [TARGET] { "code": xxx, "etime": "xxx", "token": "xxxx", "data": { some_datas } } ------------- REGISTERNODES @@ -255,13 +255,13 @@ The request shouldn't be used by third-party program. It's commonly used by the The client request (no carriage returns. only for reading): :: - [REGISTERNODES] [TOKEN] [TARGET] { nodes => [ - { id => 20, type => 'pull' }, - { id => 100, type => 'push_ssh', address => 10.0.0.1, ssh_port => 22 }, + [REGISTERNODES] [TOKEN] [TARGET] { "nodes" => [ + { "id": 20, "type": "pull" }, + { "id": 100, "type": "push_ssh", "address": "10.0.0.1", "ssh_port": 22 }, { - id => 150, type => 'push_zmq', address => 10.3.2.1, - server_pubkey => 'test.pem', client_pubkey => 'client_pubkey.pem', server_pubkey => 'server_pubkey.pem', cipher => 'Cipher::AES', keysize => 32, vector => '0123456789012345' - nodes => [400, 455] + "id": 150, "type": "push_zmq", address => 10.3.2.1, + "server_pubkey": "server_pubkey.pem", "client_pubkey": "client_pubkey.pem", "client_privkey": "client_privkey.pem", "cipher": "Cipher::AES", "keysize": 32, "vector": "0123456789012345", + "nodes": [400, 455] } ] } @@ -292,12 +292,12 @@ With the following request, you can execute shell commands. A client example: :: - [COMMAND] [] [target_id] { command => 'ls /' } + [COMMAND] [] [target_id] { "command": "ls /" } With the code 1, you can get following attributes: :: - { code => 1, stdout => 'xxxxx', exit_code => xxx } + { "code": 1, "stdout": "xxxxx", "exit_code": "xxx" } ENGINECOMMAND ^^^^^^^^^^^^^ @@ -306,7 +306,7 @@ With the following request, you can submit external commands to the scheduler li A client example: :: - [ENGINECOMMAND] [] [target_id] { command => '[1417705150] ENABLE_HOST_CHECK;host1', engine_pipe => '/var/lib/centreon-engine/rw/centengine.cmd' + [ENGINECOMMAND] [] [target_id] { "command": "[1417705150] ENABLE_HOST_CHECK;host1", "engine_pipe": "/var/lib/centreon-engine/rw/centengine.cmd" You only have the message to get informations (it tells you if there are some permission problems or file missing). @@ -314,8 +314,6 @@ You only have the message to get informations (it tells you if there are some pe FAQ *** - - =============================== Which modules should i enable ? =============================== From 449c3c326d27470ace52c125f610e7315023cf35 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 28 Aug 2019 17:54:08 +0200 Subject: [PATCH 056/948] fix(doc): move from rst to markdown --- gorgone/docs/{guide.rst => guide.md} | 300 ++++++++++++--------------- 1 file changed, 137 insertions(+), 163 deletions(-) rename gorgone/docs/{guide.rst => guide.md} (63%) diff --git a/gorgone/docs/guide.rst b/gorgone/docs/guide.md similarity index 63% rename from gorgone/docs/guide.rst rename to gorgone/docs/guide.md index c1bc88bcbce..d362ba4a553 100644 --- a/gorgone/docs/guide.rst +++ b/gorgone/docs/guide.md @@ -1,6 +1,4 @@ -*********** -Description -*********** +# Description "gorgoned" is a daemon which handles some tasks. You can plug-in some modules: @@ -12,9 +10,7 @@ Description The daemon is installed on centreon central server and also poller server. It uses zeromq library. -************ -Installation -************ +# Installation Daemon uses following Perl modules: @@ -36,62 +32,54 @@ Daemon uses following Perl modules: Execute following commands: - :: - - # yum install 'perl(Schedule::Cron)' 'perl(Crypt::CBC)' 'perl(ZMQ::LibZMQ4)' 'perl(JSON::XS)' 'perl(YAML)' \ - 'perl(DBD::SQLite)' 'perl(DBD::mysql)' 'perl(UUID)' 'perl(HTTP::Daemon)' 'perl(HTTP::Daemon::SSL)' \ - 'perl(HTTP::Status)' 'perl(MIME::Base64)' - # yum install perl-CryptX-0.064-1.el7.x86_64 +``` +# yum install 'perl(Schedule::Cron)' 'perl(Crypt::CBC)' 'perl(ZMQ::LibZMQ4)' 'perl(JSON::XS)' 'perl(YAML)' 'perl(DBD::SQLite)' 'perl(DBD::mysql)' 'perl(UUID)' 'perl(HTTP::Daemon)' 'perl(HTTP::Daemon::SSL)' 'perl(HTTP::Status)' 'perl(MIME::Base64)' +# yum install perl-CryptX-0.064-1.el7.x86_64 +``` Create sqlite database: - :: - - # sqlite3 -init schema/gorgone_database.sql /tmp/gorgone.sdb +``` +# sqlite3 -init schema/gorgone_database.sql /tmp/gorgone.sdb +``` To execute the daemon: - :: - - # perl gorgoned --config-extra=config/gorgoned.yml --severity=debug +``` +# perl gorgoned --config-extra=config/gorgoned.yml --severity=debug +``` -**************** -gorgone protocol -**************** +# gorgone protocol "gorgone-core" (main mandatory module) can have 2 interfaces: * internal: uncrypted dialog (used by internal modules. Commonly in ipc) * external: crypted dialog (used by third-party clients. Commonly in tcp) -.. _handshake-scenario: - -================== -Handshake scenario -================== +## Handshake scenario Third-party clients connected had to use the zeromq library and the following process: * client : need to create an uniq identity (will be used in "zmq_setsockopt" and "ZMQ_IDENTITY") * client -> server : send the following message with HELO crypted with the public key of the server and provides client pubkey: -:: - - [HOSTNAME] [CLIENTPUBKEY] [HELO] +``` +[HOSTNAME] [CLIENTPUBKEY] [HELO] +``` * server: uncrypt the client message: * If uncrypted message result is not "HELO", server refused the connection and send it back: - :: - - [ACK] [] { "code": 1, "data": { "message": "handshake issue" } } +``` +[ACK] [] { "code": 1, "data": { "message": "handshake issue" } } +``` * If uncrypted message result is "HELO", server accepts the connection if the clientpubkey is authorized. It creates symmetric key and send the following message crypted with client pubkey: - :: - - [KEY] [HOSTNAME] [symmetric key] +``` +[KEY] [HOSTNAME] [symmetric key] +``` * client: uncrypt the server message with its private key. * client and server uses the symmetric key to dialog @@ -99,17 +87,17 @@ Third-party clients connected had to use the zeromq library and the following pr The server keeps sessions for 24 hours since the last message of the client. Otherwise, it purges the identity/symmetric-key of the client. If a third-party client with the same identity try to open a new session, the server deletes the old identity/symmetric-key. -.. Warning:: - Be sure to have the same parameters to crypt/uncrypt with the symmetric key. Commonly: 'AES' cipher, keysize of 32 bytes, vector '0123456789012345', +

-============== -Client request -============== +## Client request After a successful handshake, client requests uses the following syntax: -:: - [ACTION] [TOKEN] [TARGET] DATA +``` +[ACTION] [TOKEN] [TARGET] DATA +``` * ACTION: the request. For example: COMMAND, ENGINECOMMAND,... It depends of the target server capabilites * TOKEN: Can be used to create some "sessions". If empty, the server creates an uniq token for each requests @@ -117,9 +105,9 @@ After a successful handshake, client requests uses the following syntax: * DATA: json stream. It depends of the request For each client requests, the server get an immediate response: -:: - - [ACK] [TOKEN] { "code": "x", "data": { "message": "xxxxx" } } +``` +[ACK] [TOKEN] { "code": "x", "data": { "message": "xxxxx" } } +``` * TOKEN: a uniq ID to follow the request * DATA: a json stream @@ -129,31 +117,29 @@ For each client requests, the server get an immediate response: There are some exceptions for 'CONSTATUS' and 'GETLOG' requests. -============= -Core requests -============= +## Core requests ---------- -CONSTATUS ---------- +### CONSTATUS The following request gives you a table with the last ping response of "gorgoned" nodes connected to the server. The command is useful to know if some pollers are disconnected. The client request: -:: - [CONSTATUS] [] [] +``` +[CONSTATUS] [] [] +``` The server response: -:: - [ACK] [token_id] DATA +``` +[ACK] [token_id] DATA +``` An example of the json stream: -:: - { +``` +{ "code": 1, "data": { "action": "constatus", @@ -167,22 +153,22 @@ An example of the json stream: } } } - } +} +``` 'last_ping' and 'entries' values are unix timestamp in seconds. The 'last_ping' is the date when the daemon had launched a ping broadcast to the poller connected. 'entries' values are the last time the poller had responded to the ping broadcast. ------- -GETLOG ------- +### GETLOG The following request gives you the capability to follow your requests. "gorgone" protocol is asynchronous. An example: when you request a command execution, the server gives you a direct response and a token. These token can be used to know what happened to your command. The client request: -:: - [GETLOG] [TOKEN] [TARGET] { "code": "xx", "ctime": "xx", "etime": "xx", "token": "xx", "id": "xx" } +``` +[GETLOG] [TOKEN] [TARGET] { "code": "xx", "ctime": "xx", "etime": "xx", "token": "xx", "id": "xx" } +``` At least one of the 5 values must be defined: @@ -195,80 +181,79 @@ At least one of the 5 values must be defined: The 'etime' is when the event had occured. The 'ctime' is when the server had stored the log in its database. The server response: -:: - [ACK] [token_id] DATA +``` +[ACK] [token_id] DATA +``` An example of the json stream: -:: - { - "code": 1, - "data": { - "action": "getlog", - "message": "ok", - "result": { - "10": { - "id": 10, - "token": "xxxx", - "code": 1, - "etime": 1419252684, - "ctime": 1419252686, - "data": xxxxx, - }, - "100" => { - "id": 100, - "token": "xxxx", - "code": 1, - "etime": 1419252688, - "ctime": 1419252690, - "data": xxxxx, - }, - ... - } - } - } +``` +{ + "code": 1, + "data": { + "action": "getlog", + "message": "ok", + "result": { + "10": { + "id": 10, + "token": "xxxx", + "code": 1, + "etime": 1419252684, + "ctime": 1419252686, + "data": xxxxx, + }, + "100" => { + "id": 100, + "token": "xxxx", + "code": 1, + "etime": 1419252688, + "ctime": 1419252690, + "data": xxxxx, + }, + ... + } + } +} +``` Each 'gorgoned' nodes store its logs. But every 5 minutes (by default), the central server gets the new logs of its connected nodes and stores it. A client can force a synchronization with the following request: -:: - [GETLOG] [] [target_id] +``` +[GETLOG] [] [target_id] +``` The client have to set the poller id. ------- -PUTLOG ------- +### PUTLOG The request shouldn't be used by third-party program. It's commonly used by the internal modules. The client request: -:: +``` [PUTLOG] [TOKEN] [TARGET] { "code": xxx, "etime": "xxx", "token": "xxxx", "data": { some_datas } } +``` -------------- -REGISTERNODES -------------- +### REGISTERNODES The request shouldn't be used by third-party program. It's commonly used by the internal modules. The client request (no carriage returns. only for reading): -:: - - [REGISTERNODES] [TOKEN] [TARGET] { "nodes" => [ - { "id": 20, "type": "pull" }, - { "id": 100, "type": "push_ssh", "address": "10.0.0.1", "ssh_port": 22 }, - { - "id": 150, "type": "push_zmq", address => 10.3.2.1, - "server_pubkey": "server_pubkey.pem", "client_pubkey": "client_pubkey.pem", "client_privkey": "client_privkey.pem", "cipher": "Cipher::AES", "keysize": 32, "vector": "0123456789012345", - "nodes": [400, 455] - } - ] - } -============ -Common codes -============ +``` +[REGISTERNODES] [TOKEN] [TARGET] { "nodes" => [ + { "id": 20, "type": "pull" }, + { "id": 100, "type": "push_ssh", "address": "10.0.0.1", "ssh_port": 22 }, + { + "id": 150, "type": "push_zmq", address => 10.3.2.1, + "server_pubkey": "server_pubkey.pem", "client_pubkey": "client_pubkey.pem", "client_privkey": "client_privkey.pem", "cipher": "Cipher::AES", "keysize": 32, "vector": "0123456789012345", + "nodes": [400, 455] + } + ] +} +``` + +## Common codes Common code responses for all module requests: * 0: action proceed @@ -277,46 +262,39 @@ Common code responses for all module requests: Modules can have extra codes. -=============== -module requests -=============== +## module requests --------------- -action --------------- +### action -COMMAND -^^^^^^^ +#### COMMAND With the following request, you can execute shell commands. A client example: -:: - [COMMAND] [] [target_id] { "command": "ls /" } +``` +[COMMAND] [] [target_id] { "command": "ls /" } +``` With the code 1, you can get following attributes: -:: - { "code": 1, "stdout": "xxxxx", "exit_code": "xxx" } +``` +{ "code": 1, "stdout": "xxxxx", "exit_code": "xxx" } +``` -ENGINECOMMAND -^^^^^^^^^^^^^ +#### ENGINECOMMAND With the following request, you can submit external commands to the scheduler like "centreon-engine". A client example: -:: - [ENGINECOMMAND] [] [target_id] { "command": "[1417705150] ENABLE_HOST_CHECK;host1", "engine_pipe": "/var/lib/centreon-engine/rw/centengine.cmd" +``` +[ENGINECOMMAND] [] [target_id] { "command": "[1417705150] ENABLE_HOST_CHECK;host1", "engine_pipe": "/var/lib/centreon-engine/rw/centengine.cmd" +``` You only have the message to get informations (it tells you if there are some permission problems or file missing). -*** -FAQ -*** +# FAQ -=============================== -Which modules should i enable ? -=============================== +## Which modules should i enable ? A poller with gorgoned should have the following modules: @@ -330,9 +308,7 @@ A central with gorgoned should have the following modules: * cron * httpserver -================================================= -I want to create a client. How should i proceed ? -================================================= +## I want to create a client. How should i proceed First, you must choose a language which can used zeromq library and have some knowledge about zeromq. I recommend following scenarios: @@ -351,40 +327,38 @@ I recommend following scenarios: You can see the code from 'test-client.pl'. -*************** -Database scheme -*************** - -:: +# Database scheme - CREATE TABLE IF NOT EXISTS `gorgone_identity` ( +``` +CREATE TABLE IF NOT EXISTS `gorgone_identity` ( `id` INTEGER PRIMARY KEY, `ctime` int(11) DEFAULT NULL, `identity` varchar(2048) DEFAULT NULL, `key` varchar(4096) DEFAULT NULL - ); - - CREATE INDEX IF NOT EXISTS idx_gorgone_identity_identity ON gorgone_identity (identity); - - CREATE TABLE IF NOT EXISTS `gorgone_history` ( +); + +CREATE INDEX IF NOT EXISTS idx_gorgone_identity_identity ON gorgone_identity (identity); + +CREATE TABLE IF NOT EXISTS `gorgone_history` ( `id` INTEGER PRIMARY KEY, `token` varchar(2048) DEFAULT NULL, `code` int(11) DEFAULT NULL, `etime` int(11) DEFAULT NULL, `ctime` int(11) DEFAULT NULL, `data` TEXT DEFAULT NULL - ); - - CREATE INDEX IF NOT EXISTS idx_gorgone_history_id ON gorgone_history (id); - CREATE INDEX IF NOT EXISTS idx_gorgone_history_token ON gorgone_history (token); - CREATE INDEX IF NOT EXISTS idx_gorgone_history_etime ON gorgone_history (etime); - CREATE INDEX IF NOT EXISTS idx_gorgone_history_code ON gorgone_history (code); - CREATE INDEX IF NOT EXISTS idx_gorgone_history_ctime ON gorgone_history (ctime); - - CREATE TABLE IF NOT EXISTS `gorgone_synchistory` ( +); + +CREATE INDEX IF NOT EXISTS idx_gorgone_history_id ON gorgone_history (id); +CREATE INDEX IF NOT EXISTS idx_gorgone_history_token ON gorgone_history (token); +CREATE INDEX IF NOT EXISTS idx_gorgone_history_etime ON gorgone_history (etime); +CREATE INDEX IF NOT EXISTS idx_gorgone_history_code ON gorgone_history (code); +CREATE INDEX IF NOT EXISTS idx_gorgone_history_ctime ON gorgone_history (ctime); + +CREATE TABLE IF NOT EXISTS `gorgone_synchistory` ( `id` int(11) DEFAULT NULL, `ctime` int(11) DEFAULT NULL, `last_id` int(11) DEFAULT NULL - ); +); - CREATE INDEX IF NOT EXISTS idx_gorgone_synchistory_id ON gorgone_synchistory (id); +CREATE INDEX IF NOT EXISTS idx_gorgone_synchistory_id ON gorgone_synchistory (id); +``` From 86e50638a79d6106aec38e27148eb0d1dc9b0a50 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 28 Aug 2019 17:56:19 +0200 Subject: [PATCH 057/948] fix(doc): typo --- gorgone/docs/guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/docs/guide.md b/gorgone/docs/guide.md index d362ba4a553..686b7046d96 100644 --- a/gorgone/docs/guide.md +++ b/gorgone/docs/guide.md @@ -287,7 +287,7 @@ With the following request, you can submit external commands to the scheduler li A client example: ``` -[ENGINECOMMAND] [] [target_id] { "command": "[1417705150] ENABLE_HOST_CHECK;host1", "engine_pipe": "/var/lib/centreon-engine/rw/centengine.cmd" +[ENGINECOMMAND] [] [target_id] { "command": "[1417705150] ENABLE_HOST_CHECK;host1", "engine_pipe": "/var/lib/centreon-engine/rw/centengine.cmd" } ``` You only have the message to get informations (it tells you if there are some permission problems or file missing). From 1a46774e4c4d3aa91ec9063fa3aa307feb9589bf Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 28 Aug 2019 17:57:55 +0200 Subject: [PATCH 058/948] fix(doc): remove acl --- gorgone/docs/guide.md | 1 - 1 file changed, 1 deletion(-) diff --git a/gorgone/docs/guide.md b/gorgone/docs/guide.md index 686b7046d96..dd0dfdcfc74 100644 --- a/gorgone/docs/guide.md +++ b/gorgone/docs/guide.md @@ -4,7 +4,6 @@ * gorgone-action: execute commands, send files/directories * gorgone-cron: schedule tasks -* gorgone-acl: manage centreon ACL * gorgone-proxy: push tasks (to another "gorgoned" instance) or execute (through SSH) The daemon is installed on centreon central server and also poller server. From b8627e20d33591fccf019dc2eaed933b67270a0d Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Thu, 29 Aug 2019 08:38:22 +0200 Subject: [PATCH 059/948] fix(doc): move in markdown --- .../{exploitation.txt => exploitation.md} | 31 ++++++------ gorgone/docs/modules/newtest/installation.md | 35 ++++++++++++++ gorgone/docs/modules/newtest/installation.rst | 47 ------------------- 3 files changed, 50 insertions(+), 63 deletions(-) rename gorgone/docs/modules/newtest/{exploitation.txt => exploitation.md} (79%) create mode 100644 gorgone/docs/modules/newtest/installation.md delete mode 100644 gorgone/docs/modules/newtest/installation.rst diff --git a/gorgone/docs/modules/newtest/exploitation.txt b/gorgone/docs/modules/newtest/exploitation.md similarity index 79% rename from gorgone/docs/modules/newtest/exploitation.txt rename to gorgone/docs/modules/newtest/exploitation.md index 07297a0ff2d..d24f17a8848 100644 --- a/gorgone/docs/modules/newtest/exploitation.txt +++ b/gorgone/docs/modules/newtest/exploitation.md @@ -1,13 +1,10 @@ -============ -Exploitation -============ +# Exploitation -Generals Principles -------------------- +## Generals Principles -newtest is a module program in charged to get back Newtest services. This program uses the newtest webservice in order to connect and get back the informations of one (or more) Newtest Management Console (NMC). +`newtest` is a module program in charged to get back Newtest services. This program uses the newtest webservice in order to connect and get back the informations of one (or more) Newtest Management Console (NMC). -By default "newtest" starts X processes (it depends of the configuration) : +By default `newtest` starts X processes (it depends of the configuration) : Steps of operation for one process: @@ -17,11 +14,11 @@ Steps of operation for one process: newtest necessitates the utilization of one (or more) NMC. -Configuration --------------------- +## Configuration -The « newtest » module is configured with gorgone configuration file :: - +The `newtest` module is configured with gorgone configuration file: + +``` modules: - name: newtest module: modules::newtest::hooks @@ -54,8 +51,9 @@ The « newtest » module is configured with gorgone configuration file :: service_prefix: Scenario-%s poller_name: Central list_scenario_status: '{ "search": "Robot", "instances": ["XXXX"] }' +``` -You have to add at least one entry in the configuration. The meaning of attributes : +You have to add at least one entry in the configuration. The meaning of attributes: - 'nmc_endpoint': address of the NMC - 'nmc_timeout': timeout NMC console response @@ -70,11 +68,12 @@ You have to add at least one entry in the configuration. The meaning of attribut - To get all robots: { "search": "All", "instances": [] } - To filter on some robots: { "search": "Robot", "instances": ["XXXX", "YYYY"] } -Troubleshooting ---------------- +## Troubleshooting -It is possible to get this kind of errors in the « logs » of « newtest » :: +It is possible to get this kind of errors in logs of `newtest`: - die: syntax error at line 1, column 0, byte 0 at /usr/lib/perl5/vendor_perl/5.8.8/i386-linux-thread-multi/XML/Parser.pm line 189 +``` +die: syntax error at line 1, column 0, byte 0 at /usr/lib/perl5/vendor_perl/5.8.8/i386-linux-thread-multi/XML/Parser.pm line 189 +``` It often means that there is a timeout error. diff --git a/gorgone/docs/modules/newtest/installation.md b/gorgone/docs/modules/newtest/installation.md new file mode 100644 index 00000000000..d7bf09f2cf4 --- /dev/null +++ b/gorgone/docs/modules/newtest/installation.md @@ -0,0 +1,35 @@ +# Installation + +## Prerequisites + +### Software Recommandations + +The module "gorgonenewtest" has been tested on red-hat 7 with rpms. +Installation on other system is possible but is outside the scope of this document (Debian,...). + +| Software | Version | +| :----------------- | :----------: | +| Perl SOAP::Lite | 0.71 | +| Perl Date::Parse | 1.16 | +| centreon | 19.04 | +| gorgone | 1.0 | + +### Module location + +The module "newtest" daemon must be installed on Centreon Central server. Minimal used ressources are : + +* RAM : 128 MB. +* CPU : it depends the number of newtest scenarios. + +## Module installation - centos/rhel 7 systems + +### Requirements + +| Dependency | Version | Repository | +| :----------------- | :----------: | :----------------- | +| perl-SOAP-Lite | 1.10 | centreon base | +| perl-TimeDate | 2.30 | redhat/centos base | + +### Newtest installation + +"newtest" is an official gorgone module. No installation needed. diff --git a/gorgone/docs/modules/newtest/installation.rst b/gorgone/docs/modules/newtest/installation.rst deleted file mode 100644 index 4c0a1a7130a..00000000000 --- a/gorgone/docs/modules/newtest/installation.rst +++ /dev/null @@ -1,47 +0,0 @@ -============ -Installation -============ - -Prerequisites -============= - -Software Recommandations -```````````````````````` - -The module "gorgonenewtest" has been tested on red-hat 7 with rpms. -Installation on other system is possible but is outside the scope of this document (Debian,...). - -==================== ===================== -Software Version -==================== ===================== -Perl SOAP::Lite 0.71 -Perl Date::Parse 1.16 -centreon 19.04 -centreon-gorgone 1.0 -==================== ===================== - -Module location -``````````````` - -The module "newtest" daemon must be installed on Centreon Central server. Minimal used ressources are : - -* RAM : 128 MB. -* CPU : it depends the number of newtest scenarios. - -Module installation - centos/rhel 7 systems -==================================================== - -Requirements -```````````` - -======================= ===================== ====================== -Dependency Version Repository -======================= ===================== ====================== -perl-SOAP-Lite 1.10 centreon base -perl-TimeDate 2.30 redhat/centos base -======================= ===================== ====================== - -newtest Installation -````````````````````````````````````````` - -"newtest" is an official gorgone module. No installation needed. From bd6559752d54011e6e5e9068055cb8061bc99127 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Thu, 29 Aug 2019 09:32:38 +0200 Subject: [PATCH 060/948] feat(core): add register module --- gorgone/centreon/gorgone/common.pm | 2 +- gorgone/config/gorgoned.yml | 5 + gorgone/config/registernodes.json | 16 +++ gorgone/docs/guide.md | 6 +- gorgone/modules/core/proxy/hooks.pm | 2 +- gorgone/modules/core/register/class.pm | 191 +++++++++++++++++++++++++ gorgone/modules/core/register/hooks.pm | 160 +++++++++++++++++++++ 7 files changed, 377 insertions(+), 5 deletions(-) create mode 100644 gorgone/config/registernodes.json create mode 100644 gorgone/modules/core/register/class.pm create mode 100644 gorgone/modules/core/register/hooks.pm diff --git a/gorgone/centreon/gorgone/common.pm b/gorgone/centreon/gorgone/common.pm index 35d5b5eeba7..76f515731be 100644 --- a/gorgone/centreon/gorgone/common.pm +++ b/gorgone/centreon/gorgone/common.pm @@ -184,7 +184,7 @@ sub uncrypt_message { sub generate_token { my (%options) = @_; - my $token = Crypt::PRNG::random_bytes_hex(256); + my $token = Crypt::PRNG::random_bytes_hex(64); return $token; } diff --git a/gorgone/config/gorgoned.yml b/gorgone/config/gorgoned.yml index da91561dd3c..26c59334f2a 100644 --- a/gorgone/config/gorgoned.yml +++ b/gorgone/config/gorgoned.yml @@ -84,6 +84,11 @@ modules: enable: false resync_time: 600 + - name: register + package: modules::core::register::hooks + enable: true + config_file: config/registernodes.json + - name: engine package: modules::centreon::engine::hooks enable: true diff --git a/gorgone/config/registernodes.json b/gorgone/config/registernodes.json new file mode 100644 index 00000000000..051d3e92ae6 --- /dev/null +++ b/gorgone/config/registernodes.json @@ -0,0 +1,16 @@ +{ + "nodes": [ + { "id": 100, "type": "push_ssh", "address": "10.0.0.1", "ssh_port": 22 }, + { + "id": 140, + "type": "push_zmq", + "address": "10.3.2.15", + "server_pubkey": "keys/poller/pubkey.crt", + "client_pubkey": "keys/central/pubkey.crt", + "client_privkey": "keys/central/privkey.pem", + "cipher": "Cipher::AES", + "keysize": 32, + "vector": "0123456789012345" + } + ] +} diff --git a/gorgone/docs/guide.md b/gorgone/docs/guide.md index dd0dfdcfc74..922715f10c4 100644 --- a/gorgone/docs/guide.md +++ b/gorgone/docs/guide.md @@ -202,7 +202,7 @@ An example of the json stream: "ctime": 1419252686, "data": xxxxx, }, - "100" => { + "100": { "id": 100, "token": "xxxx", "code": 1, @@ -240,11 +240,11 @@ The request shouldn't be used by third-party program. It's commonly used by the The client request (no carriage returns. only for reading): ``` -[REGISTERNODES] [TOKEN] [TARGET] { "nodes" => [ +[REGISTERNODES] [TOKEN] [TARGET] { "nodes": [ { "id": 20, "type": "pull" }, { "id": 100, "type": "push_ssh", "address": "10.0.0.1", "ssh_port": 22 }, { - "id": 150, "type": "push_zmq", address => 10.3.2.1, + "id": 150, "type": "push_zmq", "address": "10.3.2.1", "server_pubkey": "server_pubkey.pem", "client_pubkey": "client_pubkey.pem", "client_privkey": "client_privkey.pem", "cipher": "Cipher::AES", "keysize": 32, "vector": "0123456789012345", "nodes": [400, 455] } diff --git a/gorgone/modules/core/proxy/hooks.pm b/gorgone/modules/core/proxy/hooks.pm index 1889936a2c7..3fadef031b5 100644 --- a/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/modules/core/proxy/hooks.pm @@ -530,7 +530,7 @@ sub register_nodes { $last_pong->{$node->{id}} = 0 if (!defined($last_pong->{$node->{id}})); if (!defined($synctime_nodes->{$node->{id}})) { $synctime_nodes->{$node->{id}} = { ctime => 0, in_progress => 0, in_progress_time => -1, last_id => 0, synctime_error => 0 }; - get_sync_time(node_id => $node->{id}); + get_sync_time(node_id => $node->{id}, dbh => $options{dbh}); } } } diff --git a/gorgone/modules/core/register/class.pm b/gorgone/modules/core/register/class.pm new file mode 100644 index 00000000000..ebf26566f2b --- /dev/null +++ b/gorgone/modules/core/register/class.pm @@ -0,0 +1,191 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package modules::core::register::class; + +use base qw(centreon::gorgone::module); + +use strict; +use warnings; +use centreon::gorgone::common; +use ZMQ::LibZMQ4; +use ZMQ::Constants qw(:all); +use JSON::XS; + +my %handlers = (TERM => {}, HUP => {}); +my ($connector); + +sub new { + my ($class, %options) = @_; + + $connector = {}; + $connector->{internal_socket} = undef; + $connector->{module_id} = $options{module_id}; + $connector->{logger} = $options{logger}; + $connector->{config} = $options{config}; + $connector->{config_core} = $options{config_core}; + $connector->{stop} = 0; + $connector->{register_nodes} = {}; + + bless $connector, $class; + $connector->set_signal_handlers(); + return $connector; +} + +sub set_signal_handlers { + my $self = shift; + + $SIG{TERM} = \&class_handle_TERM; + $handlers{TERM}->{$self} = sub { $self->handle_TERM() }; + $SIG{HUP} = \&class_handle_HUP; + $handlers{HUP}->{$self} = sub { $self->handle_HUP() }; +} + +sub handle_HUP { + my $self = shift; + $self->{reload} = 0; +} + +sub handle_TERM { + my $self = shift; + $self->{logger}->writeLogInfo("[register] -class- $$ Receiving order to stop..."); + $self->{stop} = 1; +} + +sub class_handle_TERM { + foreach (keys %{$handlers{TERM}}) { + &{$handlers{TERM}->{$_}}(); + } +} + +sub class_handle_HUP { + foreach (keys %{$handlers{HUP}}) { + &{$handlers{HUP}->{$_}}(); + } +} + +sub action_registerresync { + my ($self, %options) = @_; + + $options{token} = $self->generate_token() if (!defined($options{token})); + + $self->send_log(code => centreon::gorgone::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action registerresync proceed' }); + + my $content = do { + local $/ = undef; + if (!open my $fh, "<", $self->{config}->{config_file}) { + $self->send_log(code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, data => { message => "Could not open file $self->{config}->{config_file}: $!" }); + $self->{logger}->writeLogError("[register] -class- Could not open file $self->{config}->{config_file}: $!"); + return 1; + } + <$fh>; + }; + + my $data; + eval { + $data = JSON::XS->new->utf8->decode($content); + }; + if ($@) { + $self->send_log(code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, data => { message => "Cannot decode json file: $@" }); + $self->{logger}->writeLogError("[register] -class- Cannot decode json file: $@"); + return 1; + } + + my $register_temp = {}; + my $register_nodes = []; + if (defined($data->{nodes})) { + foreach (@{$data->{nodes}}) { + $self->{register_nodes}->{$_->{id}} = 1; + $register_temp->{$_->{id}} = 1; + push @{$register_nodes}, { %$_ }; + } + } + + my $unregister_nodes = []; + foreach (keys %{$self->{register_nodes}}) { + if (!defined($register_temp->{$_})) { + push @{$unregister_nodes}, { id => $_ }; + delete $self->{register_nodes}->{$_}; + } + } + + $self->send_internal_action(action => 'REGISTERNODES', data => { nodes => $register_nodes } ); + $self->send_internal_action(action => 'UNREGISTERNODES', data => { nodes => $unregister_nodes } ); + + $self->{logger}->writeLogDebug("[register] -class- finish resync"); + $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => { message => 'action registerresync finished' }); + return 0; +} + +sub event { + while (1) { + my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $connector->{internal_socket}); + + $connector->{logger}->writeLogDebug("[register] -class- Event: $message"); + if ($message =~ /^\[(.*?)\]/) { + if ((my $method = $connector->can('action_' . lc($1)))) { + $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; + my ($action, $token) = ($1, $2); + my $data = JSON::XS->new->utf8->decode($3); + $method->($connector, token => $token, data => $data); + } + } + + last unless (centreon::gorgone::common::zmq_still_read(socket => $connector->{internal_socket})); + } +} + +sub run { + my ($self, %options) = @_; + + # Connect internal + $connector->{internal_socket} = centreon::gorgone::common::connect_com( + zmq_type => 'ZMQ_DEALER', + name => 'gorgoneregister', + logger => $self->{logger}, + type => $self->{config_core}->{internal_com_type}, + path => $self->{config_core}->{internal_com_path} + ); + centreon::gorgone::common::zmq_send_message( + socket => $connector->{internal_socket}, + action => 'REGISTERREADY', data => { }, + json_encode => 1 + ); + $self->{poll} = [ + { + socket => $connector->{internal_socket}, + events => ZMQ_POLLIN, + callback => \&event, + } + ]; + + $self->action_registerresync(); + while (1) { + # we try to do all we can + my $rev = zmq_poll($self->{poll}, 5000); + if (defined($rev) && $rev == 0 && $self->{stop} == 1) { + $self->{logger}->writeLogInfo("[register] -class- $$ has quit"); + zmq_close($connector->{internal_socket}); + exit(0); + } + } +} + +1; diff --git a/gorgone/modules/core/register/hooks.pm b/gorgone/modules/core/register/hooks.pm new file mode 100644 index 00000000000..9c6fea943c6 --- /dev/null +++ b/gorgone/modules/core/register/hooks.pm @@ -0,0 +1,160 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package modules::core::register::hooks; + +use warnings; +use strict; +use JSON::XS; +use centreon::script::gorgonecore; +use modules::core::register::class; + +my $NAME = 'register'; +my $EVENTS = [ + { event => 'REGISTERREADY' }, +]; + +my $config_core; +my $config; +my ($config_db_centreon); +my $register = {}; +my $stop = 0; + +sub register { + my (%options) = @_; + + $config = $options{config}; + $config_core = $options{config_core}; + return ($NAME, $EVENTS); +} + +sub init { + my (%options) = @_; + + create_child(logger => $options{logger}); +} + +sub routing { + my (%options) = @_; + + my $data; + eval { + $data = JSON::XS->new->utf8->decode($options{data}); + }; + if ($@) { + $options{logger}->writeLogError("[register] -hooks- Cannot decode json data: $@"); + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => 10, token => $options{token}, + data => { message => 'gorgoneregister: cannot decode json' }, + json_encode => 1 + ); + return undef; + } + + if ($options{action} eq 'REGISTERREADY') { + $register->{ready} = 1; + return undef; + } + + if (centreon::script::gorgonecore::waiting_ready(ready => \$register->{ready}) == 0) { + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => 10, token => $options{token}, + data => { message => 'gorgoneregister: still no ready' }, + json_encode => 1 + ); + return undef; + } + + centreon::gorgone::common::zmq_send_message( + socket => $options{socket}, + identity => 'gorgoneregister', + action => $options{action}, + data => $options{data}, + token => $options{token}, + ); +} + +sub gently { + my (%options) = @_; + + $stop = 1; + $options{logger}->writeLogInfo("[register] -hooks- Send TERM signal"); + if ($register->{running} == 1) { + CORE::kill('TERM', $register->{pid}); + } +} + +sub kill { + my (%options) = @_; + + if ($register->{running} == 1) { + $options{logger}->writeLogInfo("[register] -hooks- Send KILL signal for pool"); + CORE::kill('KILL', $register->{pid}); + } +} + +sub kill_internal { + my (%options) = @_; + +} + +sub check { + my (%options) = @_; + + my $count = 0; + foreach my $pid (keys %{$options{dead_childs}}) { + # Not me + next if ($register->{pid} != $pid); + + $register = {}; + delete $options{dead_childs}->{$pid}; + if ($stop == 0) { + create_child(logger => $options{logger}); + } + } + + $count++ if (defined($register->{running}) && $register->{running} == 1); + + return $count; +} + +# Specific functions +sub create_child { + my (%options) = @_; + + $options{logger}->writeLogInfo("[register] -hooks- Create module 'register' process"); + my $child_pid = fork(); + if ($child_pid == 0) { + $0 = 'gorgone-register'; + my $module = modules::core::register::class->new( + logger => $options{logger}, + config_core => $config_core, + config => $config, + ); + $module->run(); + exit(0); + } + $options{logger}->writeLogInfo("[register] -hooks- PID $child_pid (gorgone-register)"); + $register = { pid => $child_pid, ready => 0, running => 1 }; +} + +1; From 71b2343bacb33f0873309ed828952b388b79e7f1 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Thu, 29 Aug 2019 14:44:33 +0200 Subject: [PATCH 061/948] feat(proxy): enhance zmq proxy. in progress --- gorgone/centreon/gorgone/clientzmq.pm | 22 ++-- gorgone/centreon/gorgone/common.pm | 25 ++-- gorgone/centreon/gorgone/module.pm | 12 +- gorgone/config/registernodes.json | 6 +- gorgone/modules/centreon/pollers/class.pm | 2 +- gorgone/modules/core/proxy/class.pm | 152 +++++++++++++--------- gorgone/modules/core/proxy/hooks.pm | 45 +++++-- 7 files changed, 163 insertions(+), 101 deletions(-) diff --git a/gorgone/centreon/gorgone/clientzmq.pm b/gorgone/centreon/gorgone/clientzmq.pm index cafe5456d64..3fd2ddb19b5 100644 --- a/gorgone/centreon/gorgone/clientzmq.pm +++ b/gorgone/centreon/gorgone/clientzmq.pm @@ -29,7 +29,6 @@ use ZMQ::Constants qw(:all); my $connectors = {}; my $callbacks = {}; my $sockets = {}; -my $polls = {}; sub new { my ($class, %options) = @_; @@ -51,7 +50,7 @@ sub new { $connector->{ping_timeout_time} = time(); if (defined($connector->{logger}) && $connector->{logger}->is_debug()) { - $connector->{logger}->writeLogDebug($connector->{client_pubkey}->export_key_jwk_thumbprint('SHA256')); + $connector->{logger}->writeLogDebug('jwk thumbprint = ' . $connector->{client_pubkey}->export_key_jwk_thumbprint('SHA256')); } $connectors->{$options{identity}} = $connector; @@ -116,20 +115,19 @@ sub ping { sub get_poll { my ($self, %options) = @_; - - $polls->{$sockets->{$self->{identity}}} = { - socket => $sockets->{$self->{identity}}, - events => ZMQ_POLLIN, - callback => sub { - event(identity => $self->{identity}); - } + + return { + socket => $sockets->{$self->{identity}}, + events => ZMQ_POLLIN, + callback => sub { + event(identity => $self->{identity}); + } }; - return $polls->{$sockets->{$self->{identity}}}; } sub event { my (%options) = @_; - + # We have a response. So it's ok :) if ($connectors->{$options{identity}}->{ping_progress} == 1) { $connectors->{$options{identity}}->{ping_progress} = 0; @@ -189,7 +187,7 @@ sub send_message { } $self->{handshake} = 1; - zmq_sendmsg($sockets->{$self->{identity}}, $ciphertext); + zmq_sendmsg($sockets->{$self->{identity}}, $ciphertext, ZMQ_DONTWAIT); zmq_poll([$self->get_poll()], 10000); } diff --git a/gorgone/centreon/gorgone/common.pm b/gorgone/centreon/gorgone/common.pm index 76f515731be..29cfb540600 100644 --- a/gorgone/centreon/gorgone/common.pm +++ b/gorgone/centreon/gorgone/common.pm @@ -486,6 +486,10 @@ sub connect_com { zmq_setsockopt($socket, ZMQ_IDENTITY, $options{name}); zmq_setsockopt($socket, ZMQ_LINGER, defined($options{linger}) ? $options{linger} : 0); # 0 we discard + zmq_setsockopt($socket, ZMQ_SNDHWM, defined($options{sndhwm}) ? $options{sndhwm} : 0); + zmq_setsockopt($socket, ZMQ_RCVHWM, defined($options{rcvhwm}) ? $options{sndhwm} : 0); + #zmq_setsockopt($socket, ZMQ_CONNECT_TIMEOUT, 60000); # for tcp: 60 seconds + zmq_setsockopt($socket, ZMQ_RECONNECT_IVL, 1000); zmq_connect($socket, $options{type} . '://' . $options{path}); return $socket; } @@ -557,13 +561,14 @@ sub zmq_send_message { zmq_sendmsg($options{socket}, $options{identity}, ZMQ_NOBLOCK | ZMQ_SNDMORE); } if (defined($options{cipher})) { - my $cipher = Crypt::CBC->new(-key => $options{symkey}, - -keysize => length($options{symkey}), - -cipher => $options{cipher}, - -iv => $options{vector}, - -header => 'none', - -literal_key => 1 - ); + my $cipher = Crypt::CBC->new( + -key => $options{symkey}, + -keysize => length($options{symkey}), + -cipher => $options{cipher}, + -iv => $options{vector}, + -header => 'none', + -literal_key => 1 + ); $message = $cipher->encrypt($message); } zmq_sendmsg($options{socket}, $message, ZMQ_NOBLOCK); @@ -601,9 +606,9 @@ sub add_zmq_pollin { my (%options) = @_; push @{$options{poll}}, { - socket => $options{socket}, - events => ZMQ_POLLIN, - callback => $options{callback}, + socket => $options{socket}, + events => ZMQ_POLLIN, + callback => $options{callback}, }; } diff --git a/gorgone/centreon/gorgone/module.pm b/gorgone/centreon/gorgone/module.pm index d5fe7e0f77d..f7aad6d9e16 100644 --- a/gorgone/centreon/gorgone/module.pm +++ b/gorgone/centreon/gorgone/module.pm @@ -71,7 +71,9 @@ sub json_encode { $encoded_arguments = JSON::XS->new->utf8->encode($options{argument}); }; if ($@) { - $self->{logger}->writeLogError("gorgone-$self->{module_id}: container $self->{container_id}: $options{method} - cannot encode json: $@"); + my $container = ''; + $container = 'container ' . $self->{container_id} . ': ' if (defined($self->{container_id})); + $self->{logger}->writeLogError("[$self->{module_id}] -class- ${container}$options{method} - cannot encode json: $@"); return 1; } @@ -86,7 +88,9 @@ sub json_decode { $decoded_arguments = JSON::XS->new->utf8->decode($options{argument}); }; if ($@) { - $self->{logger}->writeLogError("gorgone-$self->{module_id}: container $self->{container_id}: $options{method} - cannot decode json: $@"); + my $container = ''; + $container = 'container ' . $self->{container_id} . ': ' if (defined($self->{container_id})); + $self->{logger}->writeLogError("[$self->{module_id}] -class- ${container}$options{method} - cannot decode json: $@"); return 1; } @@ -104,7 +108,9 @@ sub execute_shell_cmd { wait_exit => 1, ); if ($lerror == -1 || ($exit_code >> 8) != 0) { - $self->{logger}->writeLogError("gorgone-$self->{module_id} command execution issue $options{cmd} : " . $stdout); + my $container = ''; + $container = 'container ' . $self->{container_id} . ': ' if (defined($self->{container_id})); + $self->{logger}->writeLogError("[$self->{module_id}] -class- ${container}command execution issue $options{cmd} : " . $stdout); return -1; } diff --git a/gorgone/config/registernodes.json b/gorgone/config/registernodes.json index 051d3e92ae6..6752674b5f9 100644 --- a/gorgone/config/registernodes.json +++ b/gorgone/config/registernodes.json @@ -4,13 +4,15 @@ { "id": 140, "type": "push_zmq", - "address": "10.3.2.15", + "address": "10.30.2.15", + "port": 5556, "server_pubkey": "keys/poller/pubkey.crt", "client_pubkey": "keys/central/pubkey.crt", "client_privkey": "keys/central/privkey.pem", "cipher": "Cipher::AES", "keysize": 32, - "vector": "0123456789012345" + "vector": "0123456789012345", + "nodes": [20,50] } ] } diff --git a/gorgone/modules/centreon/pollers/class.pm b/gorgone/modules/centreon/pollers/class.pm index fcae5955ddc..d67c7d09668 100644 --- a/gorgone/modules/centreon/pollers/class.pm +++ b/gorgone/modules/centreon/pollers/class.pm @@ -118,7 +118,7 @@ sub action_pollersresync { $self->{register_pollers}->{$_->[0]} = 1; $register_temp->{$_->[0]} = 1; - push @{$register_nodes}, { id => $_->[0], type => 'push_ssh', address => $_->[3], ssh_port => $_[4] }; + push @{$register_nodes}, { id => $_->[0], type => 'push_ssh', address => $_->[3], ssh_port => $_->[4] }; } my $unregister_nodes = []; diff --git a/gorgone/modules/core/proxy/class.pm b/gorgone/modules/core/proxy/class.pm index e557eadfc0f..2dd10518a75 100644 --- a/gorgone/modules/core/proxy/class.pm +++ b/gorgone/modules/core/proxy/class.pm @@ -37,6 +37,7 @@ sub new { my ($class, %options) = @_; $connector = {}; + $connector->{module_id} = $options{module_id}; $connector->{internal_socket} = undef; $connector->{logger} = $options{logger}; $connector->{core_id} = $options{core_id}; @@ -45,9 +46,10 @@ sub new { $connector->{config_core} = $options{config_core}; $connector->{stop} = 0; $connector->{clients} = {}; + $connector->{subnodes} = {}; bless $connector, $class; - $connector->set_signal_handlers; + $connector->set_signal_handlers(); return $connector; } @@ -83,29 +85,9 @@ sub class_handle_HUP { } } -sub get_client_information { - my ($self, %options) = @_; - - # TODO DATABASE or file maybe. hardcoded right now - my $result = { - type => 1, - target_type => 'tcp', - target_path => 'localhost:5556', - server_pubkey => 'keys/poller/pubkey.crt', - client_pubkey => 'keys/central/pubkey.crt', - client_privkey => 'keys/central/privkey.pem', - cipher => 'Cipher::AES', - keysize => '32', - vector => '0123456789012345', - class => undef, - delete => 0 - }; - return $result; -} - sub read_message { my (%options) = @_; - + return undef if (!defined($options{identity}) || $options{identity} !~ /^proxy-(.*?)-(.*?)$/); my ($client_identity) = ($2); @@ -123,8 +105,20 @@ sub read_message { data => { code => 0, data => { message => 'ping ok', action => 'ping', id => $client_identity } }, json_encode => 1 ); - } - elsif ($options{data} =~ /^\[ACK\]\s+\[(.*?)\]\s+(.*)/m) { + } elsif ($options{data} =~ /^\[REGISTERNODES|UNREGISTERNODES\]/) { + if ($options{data} !~ /^\[(.+?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)/m) { + return undef; + } + my ($action, $token, $data) = ($1, $2, $3); + + centreon::gorgone::common::zmq_send_message( + socket => $connector->{internal_socket}, + action => $action, + token => $token, + target => '', + data => $data + ); + } elsif ($options{data} =~ /^\[ACK\]\s+\[(.*?)\]\s+(.*)/m) { my $data; eval { $data = JSON::XS->new->utf8->decode($2); @@ -142,27 +136,58 @@ sub read_message { data => $2 ); } - return undef; } } sub connect { my ($self, %options) = @_; - if ($options{entry}->{type} == 1) { - $options{entry}->{class} = centreon::gorgone::clientzmq->new( - identity => 'proxy-' . $self->{core_id} . '-' . $options{id}, - cipher => $options{entry}->{cipher}, - vector => $options{entry}->{vector}, - server_pubkey => $options{entry}->{server_pubkey}, - client_pubkey => $options{entry}->{client_pubkey}, - client_privkey => $options{entry}->{client_privkey}, - target_type => $options{entry}->{target_type}, - target_path => $options{entry}->{target_path}, - logger => $self->{logger} + if ($self->{clients}->{$options{id}}->{type} eq 'push_zmq') { + $self->{clients}->{$options{id}}->{class} = centreon::gorgone::clientzmq->new( + identity => 'proxy-' . $self->{pool_id} . '-' . $options{id}, + cipher => $self->{clients}->{$options{id}}->{cipher}, + vector => $self->{clients}->{$options{id}}->{vector}, + server_pubkey => $self->{clients}->{$options{id}}->{server_pubkey}, + client_pubkey => $self->{clients}->{$options{id}}->{client_pubkey}, + client_privkey => $self->{clients}->{$options{id}}->{client_privkey}, + target_type => + defined($self->{clients}->{$options{id}}->{target_type}) ? $self->{clients}->{$options{id}}->{target_type} : 'tcp', + target_path => + defined($self->{clients}->{$options{id}}->{target_path}) ? $self->{clients}->{$options{id}}->{target_path} : $self->{clients}->{$options{id}}->{address} . ':' . $self->{clients}->{$options{id}}->{port}, + logger => $self->{logger}, ); - $options{entry}->{class}->init(callback => \&read_message); + $self->{clients}->{$options{id}}->{class}->init(callback => \&read_message); + } +} + +sub action_proxyaddnode { + my ($self, %options) = @_; + + my ($code, $data) = $self->json_decode(argument => $options{data}); + return if ($code == 1); + + $self->{clients}->{$data->{id}} = $data; + $self->{clients}->{$data->{id}}->{delete} = 0; + $self->{clients}->{$data->{id}}->{class} = undef; + + my $temp = {}; + foreach (@{$data->{nodes}}) { + $temp->{$_} = 1; + $self->{subnodes}->{$_} = $data->{id}; } + foreach (keys %{$self->{subnodes}}) { + delete $self->{subnodes}->{$_} + if ($self->{subnodes}->{$_} eq $data->{id} && !defined($temp->{$_})); + } +} + +sub action_proxydelnode { + my ($self, %options) = @_; + + my ($code, $data) = $self->json_decode(argument => $options{data}); + return if ($code == 1); + + } sub proxy { @@ -172,35 +197,36 @@ sub proxy { return undef; } my ($action, $token, $target, $data) = ($1, $2, $3, $4); - - my $entry; + if ($action eq 'PROXYADDNODE') { + action_proxyaddnode($connector, data => $data); + return ; + } elsif ($action eq 'PROXYDELNODE') { + action_proxydelnode($connector, data => $data); + return ; + } + + my $target_client = $target; if (!defined($connector->{clients}->{$target})) { - $entry = $connector->get_client_information(id => $target); - return if (!defined($entry)); - - $connector->connect(id => $target, entry => $entry); - $connector->{clients}->{$target} = $entry; - } else { - $entry = $connector->{clients}->{$target}; + $target_client = $connector->{subnodes}->{$target}; + } + if (!defined($connector->{clients}->{$target_client}->{class})) { + $connector->connect(id => $target_client); } - # TODO we need to manage type SSH with libssh - # type 1 = ZMQ. - # type 2 = SSH - if ($entry->{type} == 1) { - my ($status, $msg) = $entry->{class}->send_message( + if ($connector->{clients}->{$target_client}->{type} eq 'push_zmq') { + my ($status, $msg) = $connector->{clients}->{$target_client}->{class}->send_message( action => $action, token => $token, - target => '', # TODO: don't set to null if we need to chain it!!! + target => $target, data => $data ); if ($status != 0) { - # error we put log and we close (TODO the log) + $connector->send_log(code => centreon::gorgone::module::ACTION_FINISH_KO, token => $token, data => { message => "Send message problem for '$target': $msg" }); $connector->{logger}->writeLogError("[proxy] -class- Send message problem for '$target': $msg"); $connector->{clients}->{$target}->{delete} = 1; } } - + $connector->{logger}->writeLogDebug("[proxy] -class- Send message: [action = $action] [token = $token] [target = $target] [data = $data]"); } @@ -217,7 +243,7 @@ sub run { my ($self, %options) = @_; # Connect internal - $connector->{internal_socket} = centreon::gorgone::common::connect_com( + $self->{internal_socket} = centreon::gorgone::common::connect_com( zmq_type => 'ZMQ_DEALER', name => 'gorgoneproxy-' . $self->{pool_id}, logger => $self->{logger}, @@ -231,20 +257,22 @@ sub run { json_encode => 1 ); my $poll = { - socket => $connector->{internal_socket}, - events => ZMQ_POLLIN, - callback => \&event_internal, + socket => $connector->{internal_socket}, + events => ZMQ_POLLIN, + callback => \&event_internal, }; while (1) { my $polls = [$poll]; foreach (keys %{$self->{clients}}) { - if ($self->{clients}->{$_}->{delete} == 1) { + if (defined($self->{clients}->{$_}->{delete}) && $self->{clients}->{$_}->{delete} == 1) { $self->{clients}->{$_}->{class}->close(); - delete $self->{clients}->{$_}; + $self->{clients}->{$_}->{class} = undef; + $self->{clients}->{$_}->{delete} = 0; next; } - if ($self->{clients}->{$_}->{type} == 1) { - push @{$polls}, $self->{clients}->{$_}->{class}->get_poll(); + + if (defined($self->{clients}->{$_}->{class}) && $self->{clients}->{$_}->{type} eq 'push_zmq') { + push @$polls, $self->{clients}->{$_}->{class}->get_poll(); } } diff --git a/gorgone/modules/core/proxy/hooks.pm b/gorgone/modules/core/proxy/hooks.pm index 3fadef031b5..249f50cdfde 100644 --- a/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/modules/core/proxy/hooks.pm @@ -34,7 +34,8 @@ my $EVENTS = [ { event => 'PONG' }, # internal. Shouldn't be used by third party clients { event => 'REGISTERNODES' }, # internal. Shouldn't be used by third party clients { event => 'UNREGISTERNODES' }, # internal. Shouldn't be used by third party clients - { event => 'ADDPOLLER', uri => '/poller', method => 'POST' }, + { event => 'PROXYADDNODE' }, # internal. Shouldn't be used by third party clients + { event => 'PROXYDELNODE' }, # internal. Shouldn't be used by third party clients ]; my $config_core; @@ -53,7 +54,7 @@ my $register_nodes = {}; my $register_subnodes = {}; my $pools = {}; my $pools_pid = {}; -my $poller_pool = {}; +my $nodes_pool = {}; my $rr_current = 0; my $stop = 0; my ($external_socket, $internal_socket, $core_id); @@ -195,15 +196,21 @@ sub routing { ); return undef; } - + my $identity; - if (defined($poller_pool->{$options{target}})) { - $identity = $poller_pool->{$options{target}}; + my $target = $options{target}; + # we prefer to use direct target + if (!defined($register_nodes->{$options{target}})) { + $target = $register_subnodes->{$options{target}}; + } + + if (defined($nodes_pool->{$target})) { + $identity = $nodes_pool->{$target}; } else { $identity = rr_pool(); - $poller_pool->{$options{target}} = $identity; + $nodes_pool->{$target} = $identity; } - + centreon::gorgone::common::zmq_send_message( socket => $options{socket}, identity => 'gorgoneproxy-' . $identity, action => $options{action}, data => $options{data}, token => $options{token}, @@ -348,8 +355,10 @@ sub setlogs { sub ping_send { my (%options) = @_; - - foreach my $id (keys %{$register_nodes}) { + + my $nodes_id = [keys %$register_nodes]; + $nodes_id = [$options{node_id}] if (defined($options{node_id})); + foreach my $id (@$nodes_id) { if ($register_nodes->{$id}->{type} eq 'push_zmq') { routing(socket => $internal_socket, action => 'PING', target => $id, data => '{}', dbh => $options{dbh}); } elsif ($register_nodes->{$id}->{type} eq 'pull') { @@ -427,6 +436,7 @@ sub create_child { $0 = 'gorgone-proxy'; my $module = modules::core::proxy::class->new( logger => $options{logger}, + module_id => $NAME, config_core => $config_core, config => $config, pool_id => $options{pool_id}, @@ -486,6 +496,10 @@ sub unregister_nodes { return if (!defined($options{data}->{nodes})); foreach my $node (@{$options{data}->{nodes}}) { + if ($node->{type} ne 'pull') { + routing(socket => $internal_socket, action => 'PROXYDELNODE', target => $node->{id}, data => JSON::XS->new->utf8->encode($node), dbh => $options{dbh}); + } + $options{logger}->writeLogInfo("[proxy] -hooks- Poller '" . $node->{id} . "' is unregistered"); if (defined($register_nodes->{$node->{id}}) && $register_nodes->{$node->{id}}->{nodes}) { foreach my $subnode_id (@{$register_nodes->{$node->{id}}->{nodes}}) { @@ -507,7 +521,9 @@ sub register_nodes { return if (!defined($options{data}->{nodes})); foreach my $node (@{$options{data}->{nodes}}) { + my $new_node = 1; if (defined($register_nodes->{$node->{id}})) { + $new_node = 0; # we remove subnodes before if ($register_nodes->{$node->{id}}->{type} ne 'push_ssh') { foreach my $subnode_id (keys %$register_subnodes) { @@ -524,8 +540,6 @@ sub register_nodes { } } - $options{logger}->writeLogInfo("[proxy] -hooks- Poller '" . $node->{id} . "' is registered"); - if ($node->{type} eq 'push_zmq' || $node->{type} eq 'pull') { $last_pong->{$node->{id}} = 0 if (!defined($last_pong->{$node->{id}})); if (!defined($synctime_nodes->{$node->{id}})) { @@ -533,6 +547,15 @@ sub register_nodes { get_sync_time(node_id => $node->{id}, dbh => $options{dbh}); } } + + if ($node->{type} ne 'pull') { + routing(socket => $internal_socket, action => 'PROXYADDNODE', target => $node->{id}, data => JSON::XS->new->utf8->encode($node), dbh => $options{dbh}); + } + if ($new_node == 1) { + # we provide information to the proxy class + ping_send(node_id => $node->{id}, dbh => $options{dbh}); + $options{logger}->writeLogInfo("[proxy] -hooks- Poller '" . $node->{id} . "' is registered"); + } } } From 99fb0a126e87b4c6cbbab9975956288a7b4938a0 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 3 Sep 2019 11:04:23 +0200 Subject: [PATCH 062/948] feat(core): avoid ping stack. wip parent node --- gorgone/centreon/gorgone/common.pm | 18 +++++++++++++++++- gorgone/centreon/script/gorgonecore.pm | 9 +++++---- gorgone/docs/guide.md | 6 ++++-- gorgone/modules/core/proxy/class.pm | 13 +++++++++++-- gorgone/modules/core/proxy/hooks.pm | 18 ++++++++++++++++-- gorgone/modules/core/pull/hooks.pm | 9 +++++++-- gorgone/modules/core/register/class.pm | 4 ++-- gorgone/schema/gorgone_database.sql | 6 ++++-- 8 files changed, 66 insertions(+), 17 deletions(-) diff --git a/gorgone/centreon/gorgone/common.pm b/gorgone/centreon/gorgone/common.pm index 29cfb540600..f48f4d01a2f 100644 --- a/gorgone/centreon/gorgone/common.pm +++ b/gorgone/centreon/gorgone/common.pm @@ -300,6 +300,22 @@ sub is_handshake_done { # internal functions ####################### +sub registerparent { + my (%options) = @_; + + # in pull mode, we do the registerparent directly. But we don't have an history. Not a issue. + #$options{gorgone}->{register_parent_nodes}->{} + if (defined($options{gorgone}->{modules_register}->{ $options{gorgone}->{modules_id}->{$options{gorgone_config}->{gorgonecore}->{proxy_name}} })) { + my $name = $options{gorgone_config}->{modules}->{$options{gorgone_config}->{gorgonecore}->{proxy_name}}->{module}; + my $method; + if (defined($name) && ($method = $name->can('get_constatus_result'))) { + return (0, { action => 'constatus', message => 'ok', data => $method->() }, 'CONSTATUS'); + } + } + + return (1, { action => 'constatus', message => 'cannot get value' }, 'CONSTATUS'); +} + sub constatus { my (%options) = @_; @@ -341,7 +357,7 @@ sub ping { # token => $options{token}, logger => $options{logger}, code => 0); return (0, { action => 'ping', message => 'ping ok', id => $options{id} }, 'PONG'); } - + sub putlog { my (%options) = @_; diff --git a/gorgone/centreon/script/gorgonecore.pm b/gorgone/centreon/script/gorgonecore.pm index f32379762ca..2ae47700649 100644 --- a/gorgone/centreon/script/gorgonecore.pm +++ b/gorgone/centreon/script/gorgonecore.pm @@ -60,7 +60,8 @@ sub new { $self->{sessions_timer} = time(); $self->{kill_timer} = undef; $self->{server_privkey} = undef; - + $self->{register_parent_nodes} = {}; + return $self; } @@ -208,7 +209,7 @@ sub load_modules { } # Load internal functions - foreach my $method_name (('putlog', 'getlog', 'kill', 'ping', 'constatus', 'setcoreid')) { + foreach my $method_name (('putlog', 'getlog', 'kill', 'ping', 'constatus', 'setcoreid', 'registerparent')) { unless ($self->{internal_register}->{$method_name} = centreon::gorgone::common->can($method_name)) { $self->{logger}->writeLogError("[core] No function '$method_name'"); exit(1); @@ -223,7 +224,7 @@ sub message_run { return (undef, 1, { message => 'request not well formatted' }); } my ($action, $token, $target, $data) = ($1, $2, $3, $4); - if ($action !~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID)$/ && !defined($self->{modules_events}->{$action})) { + if ($action !~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID|REGISTERPARENT)$/ && !defined($self->{modules_events}->{$action})) { centreon::gorgone::common::add_history( dbh => $self->{db_gorgone}, code => 1, @@ -267,7 +268,7 @@ sub message_run { } } - if ($action =~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID)$/) { + if ($action =~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID|REGISTERPARENT)$/) { my ($code, $response, $response_type) = $self->{internal_register}->{lc($action)}->( gorgone => $self, gorgone_config => $config, diff --git a/gorgone/docs/guide.md b/gorgone/docs/guide.md index 922715f10c4..bb20b0bdf8c 100644 --- a/gorgone/docs/guide.md +++ b/gorgone/docs/guide.md @@ -333,10 +333,12 @@ CREATE TABLE IF NOT EXISTS `gorgone_identity` ( `id` INTEGER PRIMARY KEY, `ctime` int(11) DEFAULT NULL, `identity` varchar(2048) DEFAULT NULL, - `key` varchar(4096) DEFAULT NULL + `key` varchar(4096) DEFAULT NULL, + `parent` INTEGER DEFAULT '0' ); -CREATE INDEX IF NOT EXISTS idx_gorgone_identity_identity ON gorgone_identity (identity); +CREATE INDEX IF NOT EXISTS idx_gorgone_identity ON gorgone_identity (identity); +CREATE INDEX IF NOT EXISTS idx_gorgone_parent ON gorgone_identity (parent); CREATE TABLE IF NOT EXISTS `gorgone_history` ( `id` INTEGER PRIMARY KEY, diff --git a/gorgone/modules/core/proxy/class.pm b/gorgone/modules/core/proxy/class.pm index 2dd10518a75..742bfc702d6 100644 --- a/gorgone/modules/core/proxy/class.pm +++ b/gorgone/modules/core/proxy/class.pm @@ -251,13 +251,13 @@ sub run { path => $self->{config_core}{internal_com_path} ); centreon::gorgone::common::zmq_send_message( - socket => $connector->{internal_socket}, + socket => $self->{internal_socket}, action => 'PROXYREADY', data => { pool_id => $self->{pool_id} }, json_encode => 1 ); my $poll = { - socket => $connector->{internal_socket}, + socket => $self->{internal_socket}, events => ZMQ_POLLIN, callback => \&event_internal, }; @@ -265,6 +265,15 @@ sub run { my $polls = [$poll]; foreach (keys %{$self->{clients}}) { if (defined($self->{clients}->{$_}->{delete}) && $self->{clients}->{$_}->{delete} == 1) { + if ($self->{clients}->{$_}->{type} eq 'push_zmq') { + centreon::gorgone::common::zmq_send_message( + socket => $self->{internal_socket}, + action => 'PONGRESET', + token => $self->generate_token(), + target => '', + data => '{ "id": ' . $_ . '}' + ); + } $self->{clients}->{$_}->{class}->close(); $self->{clients}->{$_}->{class} = undef; $self->{clients}->{$_}->{delete} = 0; diff --git a/gorgone/modules/core/proxy/hooks.pm b/gorgone/modules/core/proxy/hooks.pm index 249f50cdfde..ca281b1e2e8 100644 --- a/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/modules/core/proxy/hooks.pm @@ -36,6 +36,7 @@ my $EVENTS = [ { event => 'UNREGISTERNODES' }, # internal. Shouldn't be used by third party clients { event => 'PROXYADDNODE' }, # internal. Shouldn't be used by third party clients { event => 'PROXYDELNODE' }, # internal. Shouldn't be used by third party clients + { event => 'PONGRESET' }, # internal. Shouldn't be used by third party clients ]; my $config_core; @@ -100,13 +101,22 @@ sub routing { ); return undef; } - + if ($options{action} eq 'PONG') { return undef if (!defined($data->{data}->{id}) || $data->{data}->{id} eq ''); + $synctime_nodes->{$data->{data}->{id}}->{in_progress_ping} = 0; $last_pong->{$data->{data}->{id}} = time(); $options{logger}->writeLogInfo("[proxy] -hooks- Pong received from '" . $data->{data}->{id} . "'"); return undef; } + + if ($options{action} eq 'PONGRESET') { + use Data::Dumper; print Data::Dumper::Dumper($data); + return undef if (!defined($data->{id}) || $data->{id} eq ''); + $synctime_nodes->{$data->{id}}->{in_progress_ping} = 0; + $options{logger}->writeLogInfo("[proxy] -hooks- PongReset received from '" . $data->{id} . "'"); + return undef; + } if ($options{action} eq 'UNREGISTERNODES') { unregister_nodes(%options, data => $data); @@ -359,9 +369,13 @@ sub ping_send { my $nodes_id = [keys %$register_nodes]; $nodes_id = [$options{node_id}] if (defined($options{node_id})); foreach my $id (@$nodes_id) { + next if (defined($synctime_nodes->{$id}) && $synctime_nodes->{$id}->{in_progress_ping} == 1); + if ($register_nodes->{$id}->{type} eq 'push_zmq') { + $synctime_nodes->{$id}->{in_progress_ping} = 1; routing(socket => $internal_socket, action => 'PING', target => $id, data => '{}', dbh => $options{dbh}); } elsif ($register_nodes->{$id}->{type} eq 'pull') { + $synctime_nodes->{$id}->{in_progress_ping} = 1; routing(action => 'PING', target => $id, data => '{}', dbh => $options{dbh}); } } @@ -543,7 +557,7 @@ sub register_nodes { if ($node->{type} eq 'push_zmq' || $node->{type} eq 'pull') { $last_pong->{$node->{id}} = 0 if (!defined($last_pong->{$node->{id}})); if (!defined($synctime_nodes->{$node->{id}})) { - $synctime_nodes->{$node->{id}} = { ctime => 0, in_progress => 0, in_progress_time => -1, last_id => 0, synctime_error => 0 }; + $synctime_nodes->{$node->{id}} = { ctime => 0, in_progress_ping => 0, in_progress => 0, in_progress_time => -1, last_id => 0, synctime_error => 0 }; get_sync_time(node_id => $node->{id}, dbh => $options{dbh}); } } diff --git a/gorgone/modules/core/pull/hooks.pm b/gorgone/modules/core/pull/hooks.pm index bf218cbf26d..19a3e90d0d7 100644 --- a/gorgone/modules/core/pull/hooks.pm +++ b/gorgone/modules/core/pull/hooks.pm @@ -119,7 +119,12 @@ sub check { if ($stop == 0) { # If distant server restart, it's a not problem. It save the key. # But i don't have the registernode anymore. The ping is the 'registernode' for pull mode. - $client->ping(poll => $options{poll}, action => 'REGISTERNODE', data => { id => $config_core->{id} }, json_encode => 1); + $client->ping( + poll => $options{poll}, + action => 'REGISTERNODES', + data => { nodes => [ { id => $config_core->{id}, type => 'pull' } ] } + json_encode => 1 + ); } return 0; } @@ -167,7 +172,7 @@ sub read_message { if ($options{data} =~ /^\[ACK\]/) { return undef; } - + $logger->writeLogDebug("[pull] -hooks- Read message from external: $options{data}"); centreon::gorgone::common::zmq_send_message( socket => $socket_to_internal, diff --git a/gorgone/modules/core/register/class.pm b/gorgone/modules/core/register/class.pm index ebf26566f2b..7191eb3959f 100644 --- a/gorgone/modules/core/register/class.pm +++ b/gorgone/modules/core/register/class.pm @@ -126,8 +126,8 @@ sub action_registerresync { } } - $self->send_internal_action(action => 'REGISTERNODES', data => { nodes => $register_nodes } ); - $self->send_internal_action(action => 'UNREGISTERNODES', data => { nodes => $unregister_nodes } ); + $self->send_internal_action(action => 'REGISTERNODES', data => { nodes => $register_nodes } ) if (scalar(@$register_nodes) > 0); + $self->send_internal_action(action => 'UNREGISTERNODES', data => { nodes => $unregister_nodes } ) if (scalar(@$unregister_nodes) > 0); $self->{logger}->writeLogDebug("[register] -class- finish resync"); $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => { message => 'action registerresync finished' }); diff --git a/gorgone/schema/gorgone_database.sql b/gorgone/schema/gorgone_database.sql index 06511cc7b34..99c9ffed0f3 100644 --- a/gorgone/schema/gorgone_database.sql +++ b/gorgone/schema/gorgone_database.sql @@ -2,10 +2,12 @@ CREATE TABLE IF NOT EXISTS `gorgone_identity` ( `id` INTEGER PRIMARY KEY, `ctime` int(11) DEFAULT NULL, `identity` varchar(2048) DEFAULT NULL, - `key` varchar(4096) DEFAULT NULL + `key` varchar(4096) DEFAULT NULL, + `parent` INTEGER DEFAULT '0' ); -CREATE INDEX IF NOT EXISTS idx_gorgone_identity_identity ON gorgone_identity (identity); +CREATE INDEX IF NOT EXISTS idx_gorgone_identity ON gorgone_identity (identity); +CREATE INDEX IF NOT EXISTS idx_gorgone_parent ON gorgone_identity (parent); CREATE TABLE IF NOT EXISTS `gorgone_history` ( `id` INTEGER PRIMARY KEY, From 550d7679fc6677fd09f57f125cc870e7275c0057 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 3 Sep 2019 15:11:47 +0200 Subject: [PATCH 063/948] feat(core): wip on chaining system --- gorgone/centreon/gorgone/common.pm | 37 +++++------- gorgone/centreon/script/gorgonecore.pm | 62 +++++++++++-------- gorgone/config/gorgoned-poller2.yml | 19 ++++++ gorgone/config/gorgoned-poller3.yml | 38 ++++++++++++ gorgone/config/registernodes.json | 3 +- gorgone/config/registernodes2.json | 16 +++++ gorgone/docs/guide.md | 5 +- gorgone/modules/core/proxy/class.pm | 29 ++++++--- gorgone/modules/core/proxy/hooks.pm | 83 +++++++++++++++++++------- gorgone/test-client.pl | 53 ++++++++-------- 10 files changed, 238 insertions(+), 107 deletions(-) create mode 100644 gorgone/config/gorgoned-poller3.yml create mode 100644 gorgone/config/registernodes2.json diff --git a/gorgone/centreon/gorgone/common.pm b/gorgone/centreon/gorgone/common.pm index f48f4d01a2f..828eb9e3aac 100644 --- a/gorgone/centreon/gorgone/common.pm +++ b/gorgone/centreon/gorgone/common.pm @@ -288,7 +288,7 @@ sub is_client_can_connect { sub is_handshake_done { my (%options) = @_; - my ($status, $sth) = $options{dbh}->query("SELECT `key` FROM gorgone_identity WHERE identity = " . $options{dbh}->quote($options{identity}) . " ORDER BY id DESC"); + my ($status, $sth) = $options{dbh}->query("SELECT `key` FROM gorgone_identity WHERE identity = " . $options{dbh}->quote($options{identity}) . " ORDER BY id DESC LIMIT 1"); return if ($status == -1); if (my $row = $sth->fetchrow_hashref()) { return (1, pack('H*', $row->{key})); @@ -300,27 +300,11 @@ sub is_handshake_done { # internal functions ####################### -sub registerparent { - my (%options) = @_; - - # in pull mode, we do the registerparent directly. But we don't have an history. Not a issue. - #$options{gorgone}->{register_parent_nodes}->{} - if (defined($options{gorgone}->{modules_register}->{ $options{gorgone}->{modules_id}->{$options{gorgone_config}->{gorgonecore}->{proxy_name}} })) { - my $name = $options{gorgone_config}->{modules}->{$options{gorgone_config}->{gorgonecore}->{proxy_name}}->{module}; - my $method; - if (defined($name) && ($method = $name->can('get_constatus_result'))) { - return (0, { action => 'constatus', message => 'ok', data => $method->() }, 'CONSTATUS'); - } - } - - return (1, { action => 'constatus', message => 'cannot get value' }, 'CONSTATUS'); -} - sub constatus { my (%options) = @_; - - if (defined($options{gorgone}->{modules_register}->{ $options{gorgone}->{modules_id}->{$options{gorgone_config}->{gorgonecore}->{proxy_name}} })) { - my $name = $options{gorgone_config}->{modules}->{$options{gorgone_config}->{gorgonecore}->{proxy_name}}->{module}; + + if (defined($options{gorgone_config}->{gorgonecore}->{proxy_name}) && defined($options{gorgone}->{modules_id}->{$options{gorgone_config}->{gorgonecore}->{proxy_name}})) { + my $name = $options{gorgone}->{modules_id}->{$options{gorgone_config}->{gorgonecore}->{proxy_name}}; my $method; if (defined($name) && ($method = $name->can('get_constatus_result'))) { return (0, { action => 'constatus', message => 'ok', data => $method->() }, 'CONSTATUS'); @@ -353,9 +337,16 @@ sub setcoreid { sub ping { my (%options) = @_; - #my $status = add_history(dbh => $options{gorgone}->{db_gorgone}, - # token => $options{token}, logger => $options{logger}, code => 0); - return (0, { action => 'ping', message => 'ping ok', id => $options{id} }, 'PONG'); + my $constatus = {}; + if (defined($options{gorgone_config}->{gorgonecore}->{proxy_name}) && defined($options{gorgone}->{modules_id}->{$options{gorgone_config}->{gorgonecore}->{proxy_name}})) { + my $name = $options{gorgone}->{modules_id}->{$options{gorgone_config}->{gorgonecore}->{proxy_name}}; + my $method; + if (defined($name) && ($method = $name->can('get_constatus_result'))) { + $constatus = $method->(); + } + } + + return (0, { action => 'ping', message => 'ping ok', id => $options{id}, data => $constatus }, 'PONG'); } sub putlog { diff --git a/gorgone/centreon/script/gorgonecore.pm b/gorgone/centreon/script/gorgonecore.pm index 2ae47700649..c53848662d0 100644 --- a/gorgone/centreon/script/gorgonecore.pm +++ b/gorgone/centreon/script/gorgonecore.pm @@ -209,7 +209,7 @@ sub load_modules { } # Load internal functions - foreach my $method_name (('putlog', 'getlog', 'kill', 'ping', 'constatus', 'setcoreid', 'registerparent')) { + foreach my $method_name (('putlog', 'getlog', 'kill', 'ping', 'constatus', 'setcoreid')) { unless ($self->{internal_register}->{$method_name} = centreon::gorgone::common->can($method_name)) { $self->{logger}->writeLogError("[core] No function '$method_name'"); exit(1); @@ -224,7 +224,18 @@ sub message_run { return (undef, 1, { message => 'request not well formatted' }); } my ($action, $token, $target, $data) = ($1, $2, $3, $4); - if ($action !~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID|REGISTERPARENT)$/ && !defined($self->{modules_events}->{$action})) { + + # Check if not myself ;) + if (defined($target) && ($target eq '' || $target eq $self->{id})) { + $target = undef; + } + + if (!defined($token) || $token eq '') { + $token = centreon::gorgone::common::generate_token(); + } + + if ($action !~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID)$/ && + !defined($target) && !defined($self->{modules_events}->{$action})) { centreon::gorgone::common::add_history( dbh => $self->{db_gorgone}, code => 1, @@ -235,10 +246,6 @@ sub message_run { return (undef, 1, { message => "action '$action' is not known" }); } - if (!defined($token) || $token eq '') { - $token = centreon::gorgone::common::generate_token(); - } - if ($self->{stop} == 1) { centreon::gorgone::common::add_history( dbh => $self->{db_gorgone}, @@ -251,27 +258,26 @@ sub message_run { } # Check Routing - if (defined($target) && $target ne '') { - # Check if not myself ;) - if ($target ne $self->{id}) { - $self->{modules_register}->{ $self->{modules_id}->{$config->{gorgonecore}->{proxy_name}} }->{routing}->( - socket => $self->{internal_socket}, - dbh => $self->{db_gorgone}, - logger => $self->{logger}, - action => $1, - token => $token, - target => $target, - data => $data, - hostname => $self->{hostname} - ); - return ($token, 0); - } + if (defined($target)) { + $self->{modules_register}->{ $self->{modules_id}->{$config->{gorgonecore}->{proxy_name}} }->{routing}->( + socket => $self->{internal_socket}, + dbh => $self->{db_gorgone}, + logger => $self->{logger}, + action => $1, + token => $token, + target => $target, + data => $data, + hostname => $self->{hostname} + ); + return ($token, 0); } - if ($action =~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID|REGISTERPARENT)$/) { + if ($action =~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID)$/) { my ($code, $response, $response_type) = $self->{internal_register}->{lc($action)}->( gorgone => $self, gorgone_config => $config, + identity => $options{identity}, + router_type => $options{router_type}, id => $self->{id}, data => $data, token => $token, @@ -296,7 +302,11 @@ sub message_run { sub router_internal_event { while (1) { my ($identity, $message) = centreon::gorgone::common::zmq_read_message(socket => $gorgone->{internal_socket}); - my ($token, $code, $response, $response_type) = $gorgone->message_run(message => $message); + my ($token, $code, $response, $response_type) = $gorgone->message_run( + message => $message, + identity => $identity, + router_type => 'internal', + ); centreon::gorgone::common::zmq_core_response( socket => $gorgone->{internal_socket}, identity => $identity, @@ -389,7 +399,11 @@ sub router_external_event { while (1) { my ($identity, $key, $message) = $gorgone->handshake(); if (defined($message)) { - my ($token, $code, $response, $response_type) = $gorgone->message_run(message => $message); + my ($token, $code, $response, $response_type) = $gorgone->message_run( + message => $message, + identity => $identity, + router_type => 'external', + ); centreon::gorgone::common::zmq_core_response( socket => $gorgone->{external_socket}, identity => $identity, response_type => $response_type, diff --git a/gorgone/config/gorgoned-poller2.yml b/gorgone/config/gorgoned-poller2.yml index 901514bcceb..ea63c035e6f 100644 --- a/gorgone/config/gorgoned-poller2.yml +++ b/gorgone/config/gorgoned-poller2.yml @@ -31,7 +31,26 @@ gorgonecore: sessions_time: 86400 # in seconds purge_sessions_time: 3600 + + # shouldn't be changed + proxy_name: proxy modules: + - name: proxy + package: modules::core::proxy::hooks + enable: true + pool: 2 + # sync history each 5 minutes + synchistory_time: 300 + # how much time before the response is in timeout + synchistory_timeout: 120 + # ping each X seconds + ping: 60 + - name: action package: modules::core::action::hooks enable: true + + - name: register + package: modules::core::register::hooks + enable: true + config_file: config/registernodes2.json diff --git a/gorgone/config/gorgoned-poller3.yml b/gorgone/config/gorgoned-poller3.yml new file mode 100644 index 00000000000..8f0045a0885 --- /dev/null +++ b/gorgone/config/gorgoned-poller3.yml @@ -0,0 +1,38 @@ +name: gorgoned-poller2 +gorgonecore: + internal_com_type: ipc + internal_com_path: /tmp/gorgone/routing-poller2.ipc + external_com_type: tcp + external_com_path: "*:5557" + # in seconds before sending kill signals (not gently) + timeout: 50 + gorgone_db_type: SQLite + gorgone_db_name: dbname=/tmp/gorgone_poller2.sdb + gorgone_db_host: + gorgone_db_port: + gorgone_db_user: + gorgone_db_password: + # If not set. Use 'hostname' function. + hostname: + # If not set. Try from 'hostname' in database + # Set 'none', if you don't need it (for poller in push mode) + id: 150 + privkey: keys/poller/privkey.pem + cipher: "Cipher::AES" + # in bytes + keysize: 32 + # 16 bytes for AES + vector: 0123456789012345 + # JWK format export thumbprint SHA256 + authorized_clients: + - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 + + # in seconds + sessions_time: 86400 + # in seconds + purge_sessions_time: 3600 +modules: + - name: action + package: modules::core::action::hooks + enable: true + diff --git a/gorgone/config/registernodes.json b/gorgone/config/registernodes.json index 6752674b5f9..688572a0914 100644 --- a/gorgone/config/registernodes.json +++ b/gorgone/config/registernodes.json @@ -11,8 +11,7 @@ "client_privkey": "keys/central/privkey.pem", "cipher": "Cipher::AES", "keysize": 32, - "vector": "0123456789012345", - "nodes": [20,50] + "vector": "0123456789012345" } ] } diff --git a/gorgone/config/registernodes2.json b/gorgone/config/registernodes2.json new file mode 100644 index 00000000000..9c2f4083be0 --- /dev/null +++ b/gorgone/config/registernodes2.json @@ -0,0 +1,16 @@ +{ + "nodes": [ + { + "id": 150, + "type": "push_zmq", + "address": "10.30.2.15", + "port": 5557, + "server_pubkey": "keys/poller/pubkey.crt", + "client_pubkey": "keys/central/pubkey.crt", + "client_privkey": "keys/central/privkey.pem", + "cipher": "Cipher::AES", + "keysize": 32, + "vector": "0123456789012345" + } + ] +} diff --git a/gorgone/docs/guide.md b/gorgone/docs/guide.md index bb20b0bdf8c..7bf4ff53ede 100644 --- a/gorgone/docs/guide.md +++ b/gorgone/docs/guide.md @@ -144,8 +144,9 @@ An example of the json stream: "action": "constatus", "mesage": "ok", "data": { - "last_ping": "xxxx", - "entries": { + "last_ping_sent": "xxxx", + "last_ping_recv": "xxxx", + "nodes": { "1": "xxx", "2": "xxx", ... diff --git a/gorgone/modules/core/proxy/class.pm b/gorgone/modules/core/proxy/class.pm index 742bfc702d6..3f0b87a517e 100644 --- a/gorgone/modules/core/proxy/class.pm +++ b/gorgone/modules/core/proxy/class.pm @@ -92,18 +92,17 @@ sub read_message { my ($client_identity) = ($2); if ($options{data} =~ /^\[PONG\]/) { - if ($options{data} !~ /^\[(.+?)\]\s+\[(.*?)\]\s+\[(.*?)\]/m) { + if ($options{data} !~ /^\[(.+?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)/m) { return undef; } - my ($action, $token) = ($1, $2); - + my ($action, $token, $data) = ($1, $2, $3); + centreon::gorgone::common::zmq_send_message( socket => $connector->{internal_socket}, action => 'PONG', token => $token, target => '', - data => { code => 0, data => { message => 'ping ok', action => 'ping', id => $client_identity } }, - json_encode => 1 + data => $data, ); } elsif ($options{data} =~ /^\[REGISTERNODES|UNREGISTERNODES\]/) { if ($options{data} !~ /^\[(.+?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)/m) { @@ -178,7 +177,7 @@ sub action_proxyaddnode { foreach (keys %{$self->{subnodes}}) { delete $self->{subnodes}->{$_} if ($self->{subnodes}->{$_} eq $data->{id} && !defined($temp->{$_})); - } + } } sub action_proxydelnode { @@ -187,7 +186,20 @@ sub action_proxydelnode { my ($code, $data) = $self->json_decode(argument => $options{data}); return if ($code == 1); - +} + +sub action_proxyaddsubnode { + my ($self, %options) = @_; + + my ($code, $data) = $self->json_decode(argument => $options{data}); + return if ($code == 1); + + foreach (keys %{$self->{subnodes}}) { + delete $self->{subnodes}->{$_} if ($self->{subnodes}->{$_} eq $data->{id}); + } + foreach (keys %{$data->{nodes}}) { + $self->{subnodes}->{$_} = $data->{id}; + } } sub proxy { @@ -200,6 +212,9 @@ sub proxy { if ($action eq 'PROXYADDNODE') { action_proxyaddnode($connector, data => $data); return ; + } elsif ($action eq 'PROXYADDSUBNODE') { + action_proxyaddsubnode($connector, data => $data); + return ; } elsif ($action eq 'PROXYDELNODE') { action_proxydelnode($connector, data => $data); return ; diff --git a/gorgone/modules/core/proxy/hooks.pm b/gorgone/modules/core/proxy/hooks.pm index ca281b1e2e8..f2a02004ada 100644 --- a/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/modules/core/proxy/hooks.pm @@ -36,6 +36,7 @@ my $EVENTS = [ { event => 'UNREGISTERNODES' }, # internal. Shouldn't be used by third party clients { event => 'PROXYADDNODE' }, # internal. Shouldn't be used by third party clients { event => 'PROXYDELNODE' }, # internal. Shouldn't be used by third party clients + { event => 'PROXYADDSUBNODE' }, # internal. Shouldn't be used by third party clients { event => 'PONGRESET' }, # internal. Shouldn't be used by third party clients ]; @@ -53,6 +54,7 @@ my $ping_time = 0; my $last_pong = {}; my $register_nodes = {}; my $register_subnodes = {}; +my $constatus_ping = {}; my $pools = {}; my $pools_pid = {}; my $nodes_pool = {}; @@ -106,12 +108,14 @@ sub routing { return undef if (!defined($data->{data}->{id}) || $data->{data}->{id} eq ''); $synctime_nodes->{$data->{data}->{id}}->{in_progress_ping} = 0; $last_pong->{$data->{data}->{id}} = time(); + $constatus_ping->{$data->{data}->{id}}->{last_ping_recv} = time(); + $constatus_ping->{$data->{data}->{id}}->{nodes} = $data->{data}->{data}; + register_subnodes(%options, id => $data->{data}->{id}, subnodes => $data->{data}->{data}); $options{logger}->writeLogInfo("[proxy] -hooks- Pong received from '" . $data->{data}->{id} . "'"); return undef; } if ($options{action} eq 'PONGRESET') { - use Data::Dumper; print Data::Dumper::Dumper($data); return undef if (!defined($data->{id}) || $data->{id} eq ''); $synctime_nodes->{$data->{id}}->{in_progress_ping} = 0; $options{logger}->writeLogInfo("[proxy] -hooks- PongReset received from '" . $data->{id} . "'"); @@ -137,12 +141,12 @@ sub routing { setlogs(dbh => $options{dbh}, data => $data, token => $options{token}, logger => $options{logger}); return undef; } - + if (!defined($options{target}) || (!defined($register_subnodes->{$options{target}}) && !defined($register_nodes->{$options{target}}))) { centreon::gorgone::common::add_history( dbh => $options{dbh}, - code => 20, token => $options{token}, + code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'proxy - need a valid node id' }, json_encode => 1 ); @@ -153,7 +157,7 @@ sub routing { if (defined($register_nodes->{$options{target}}) && $register_nodes->{$options{target}}->{type} eq 'push_ssh') { centreon::gorgone::common::add_history( dbh => $options{dbh}, - code => 20, token => $options{token}, + code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, data => { message => "proxy - can't get log a ssh target" }, json_encode => 1 ); @@ -164,7 +168,7 @@ sub routing { if ($synctime_nodes->{$options{target}}->{synctime_error} == -1 || get_sync_time(dbh => $options{dbh}, node_id => $options{target}) == -1) { centreon::gorgone::common::add_history( dbh => $options{dbh}, - code => 20, token => $options{token}, + code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'proxy - problem to getlog' }, json_encode => 1 ); @@ -174,14 +178,13 @@ sub routing { if ($synctime_nodes->{$options{target}}->{in_progress} == 1) { centreon::gorgone::common::add_history( dbh => $options{dbh}, - code => 20, token => $options{token}, + code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'proxy - getlog already in progress' }, json_encode => 1 ); return undef; } - - + # We put the good time to get my $ctime = $synctime_nodes->{$options{target}}->{ctime}; my $last_id = $synctime_nodes->{$options{target}}->{last_id}; @@ -190,9 +193,15 @@ sub routing { $synctime_nodes->{$options{target}}->{in_progress_time} = time(); } } - + + my $target = $options{target}; + # we prefer to use direct target + if (!defined($register_nodes->{$options{target}})) { + $target = $register_subnodes->{$options{target}}; + } + # Mode zmq pull - if ($register_nodes->{$options{target}}->{type} eq 'pull') { + if ($register_nodes->{$target}->{type} eq 'pull') { pull_request(%options, data_decoded => $data); return undef; } @@ -200,7 +209,7 @@ sub routing { if (centreon::script::gorgonecore::waiting_ready_pool(pool => $pools) == 0) { centreon::gorgone::common::add_history( dbh => $options{dbh}, - code => 20, token => $options{token}, + code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'proxy - still none ready' }, json_encode => 1 ); @@ -208,12 +217,6 @@ sub routing { } my $identity; - my $target = $options{target}; - # we prefer to use direct target - if (!defined($register_nodes->{$options{target}})) { - $target = $register_subnodes->{$options{target}}; - } - if (defined($nodes_pool->{$target})) { $identity = $nodes_pool->{$target}; } else { @@ -284,7 +287,7 @@ sub check { time() - $synctime_nodes->{$_}->{in_progress_time} > $synctimeout_option) { centreon::gorgone::common::add_history( dbh => $options{dbh}, - code => 20, + code => centreon::gorgone::module::ACTION_FINISH_KO, data => { message => "proxy - getlog in timeout for '$_'" }, json_encode => 1 ); @@ -316,7 +319,7 @@ sub setlogs { if (!defined($options{data}->{data}->{id}) || $options{data}->{data}->{id} eq '') { centreon::gorgone::common::add_history( dbh => $options{dbh}, - code => 20, token => $options{token}, + code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'proxy - need a id to setlogs' }, json_encode => 1 ); @@ -325,7 +328,7 @@ sub setlogs { if ($synctime_nodes->{$options{data}->{data}->{id}}->{in_progress} == 0) { centreon::gorgone::common::add_history( dbh => $options{dbh}, - code => 20, token => $options{token}, + code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'proxy - skip setlogs response. Maybe too much time to get response. Retry' }, json_encode => 1 ); @@ -371,6 +374,7 @@ sub ping_send { foreach my $id (@$nodes_id) { next if (defined($synctime_nodes->{$id}) && $synctime_nodes->{$id}->{in_progress_ping} == 1); + $constatus_ping->{$id}->{last_ping_sent} = time(); if ($register_nodes->{$id}->{type} eq 'push_zmq') { $synctime_nodes->{$id}->{in_progress_ping} = 1; routing(socket => $internal_socket, action => 'PING', target => $id, data => '{}', dbh => $options{dbh}); @@ -500,8 +504,7 @@ sub pull_request { sub get_constatus_result { my (%options) = @_; - my $result = { last_ping => $ping_time, entries => $last_pong }; - return $result; + return $constatus_ping; } sub unregister_nodes { @@ -525,7 +528,40 @@ sub unregister_nodes { if (defined($register_nodes->{$node->{id}})) { delete $register_nodes->{$node->{id}}; delete $synctime_nodes->{$node->{id}}; + delete $constatus_ping->{$node->{id}}; + } + } +} + +sub register_subnodes { + my (%options) = @_; + + foreach my $subnode_id (keys %$register_subnodes) { + delete $register_subnodes->{$subnode_id} + if ($register_subnodes->{$subnode_id} eq $options{id}); + } + + my $subnodes_register = { id => $options{id}, nodes => {} }; + my $subnodes = [$options{subnodes}]; + while (1) { + last if (scalar(@$subnodes) <= 0); + + my $entry = shift(@$subnodes); + foreach (keys %$entry) { + $subnodes_register->{nodes}->{$_} = $options{id}; + $register_subnodes->{$_} = $options{id}; } + push @$subnodes, $entry->{nodes} if (defined($entry->{nodes})); + } + + if ($register_nodes->{$options{id}}->{type} ne 'pull') { + routing( + socket => $internal_socket, + action => 'PROXYADDSUBNODE', + target => $options{id}, + data => JSON::XS->new->utf8->encode($subnodes_register), + dbh => $options{dbh} + ); } } @@ -546,7 +582,7 @@ sub register_nodes { } } } - + $register_nodes->{$node->{id}} = $node; if (defined($node->{nodes})) { foreach my $subnode_id (@{$node->{nodes}}) { @@ -566,6 +602,7 @@ sub register_nodes { routing(socket => $internal_socket, action => 'PROXYADDNODE', target => $node->{id}, data => JSON::XS->new->utf8->encode($node), dbh => $options{dbh}); } if ($new_node == 1) { + $constatus_ping->{$node->{id}} = { type => $node->{type}, last_ping_sent => 0, last_ping_recv => 0, nodes => {} }; # we provide information to the proxy class ping_send(node_id => $node->{id}, dbh => $options{dbh}); $options{logger}->writeLogInfo("[proxy] -hooks- Poller '" . $node->{id} . "' is registered"); diff --git a/gorgone/test-client.pl b/gorgone/test-client.pl index a928daa2ec4..07456549079 100644 --- a/gorgone/test-client.pl +++ b/gorgone/test-client.pl @@ -39,7 +39,7 @@ sub get_command_result { my ($current_retries, $retries) = (0, 4); $stopped->{$client2->{identity}} = '^([0-9]+0|32)$'; $client2->send_message( - action => 'COMMAND', data => { command => 'ls /' }, target => 120, + action => 'COMMAND', data => { command => 'ls /' }, target => 150, json_encode => 1 ); while (1) { @@ -60,7 +60,7 @@ sub get_command_result { if (defined($identities_token->{$client2->{identity}})) { # We ask a sync print "==== send logs ===\n"; - $client2->send_message(action => 'GETLOG', target => 120, token => $identities_token->{$client2->{identity}}, + $client2->send_message(action => 'GETLOG', target => 150, token => $identities_token->{$client2->{identity}}, json_encode => 1); $client2->send_message(action => 'GETLOG', token => $identities_token->{$client2->{identity}}, data => { token => $identities_token->{$client2->{identity}} }, json_encode => 1); @@ -108,21 +108,21 @@ sub read_response { #$uuid = 'toto'; UUID::generate($uuid); -$client = centreon::gorgone::clientzmq->new( - identity => 'toto', - cipher => 'Cipher::AES', - vector => '0123456789012345', - server_pubkey => 'keys/central/pubkey.crt', - client_pubkey => 'keys/poller/pubkey.crt', - client_privkey => 'keys/poller/privkey.pem', - target_type => 'tcp', - target_path => '127.0.0.1:5555', - ping => 60, -); -$client->init(callback => \&read_response); +#$client = centreon::gorgone::clientzmq->new( +# identity => 'toto', +# cipher => 'Cipher::AES', +# vector => '0123456789012345', +# server_pubkey => 'keys/central/pubkey.crt', +# client_pubkey => 'keys/poller/pubkey.crt', +# client_privkey => 'keys/poller/privkey.pem', +# target_type => 'tcp', +# target_path => '127.0.0.1:5555', +# ping => 60, +#); +#$client->init(callback => \&read_response); $client2 = centreon::gorgone::clientzmq->new( identity => 'tata', - cipher => 'Cipher::AES', + cipher => 'Cipher::AES', vector => '0123456789012345', server_pubkey => 'keys/central/pubkey.crt', client_pubkey => 'keys/poller/pubkey.crt', @@ -134,17 +134,17 @@ sub read_response { #$client->send_message(action => 'ACLADDHOST', data => { organization_id => 1 }, # json_encode => 1); -$client->send_message( - action => 'SCOMRESYNC', - data => { container_id => 'toto' }, - json_encode => 1 -); +#$client->send_message( +# action => 'SCOMRESYNC', +# data => { container_id => 'toto' }, +# json_encode => 1 +#); #$client->send_message(action => 'PUTLOG', data => { code => 120, etime => time(), token => 'plopplop', data => { 'nawak' => 'nawak2' } }, # json_encode => 1); #$client->send_message(action => 'ACLADDHOST', data => { organization_id => 10 }, target => 10, # json_encode => 1); -$client2->send_message(action => 'ACLADDHOST', data => { organization_id => 14 }, - json_encode => 1); +#$client2->send_message(action => 'ACLADDHOST', data => { organization_id => 14 }, +# json_encode => 1); #$client2->send_message(action => 'RELOADCRON', data => { }, # json_encode => 1); @@ -152,17 +152,17 @@ sub read_response { #$client2->send_message(action => 'ENGINECOMMAND', data => { command => '[1417705150] ENABLE_HOST_CHECK;host1', engine_pipe => '/var/lib/centreon-engine/rw/centengine.cmd' }, target => 120, # json_encode => 1); -#$client2->send_message(action => 'COMMAND', data => { cmd => 'ls' }, target => 140, +#$client2->send_message(action => 'COMMAND', data => { cmd => 'ls' }, target => 150, # json_encode => 1); #$client2->send_message(action => 'CONSTATUS'); # It will transform #$client2->send_message(action => 'GETLOG', data => { cmd => 'ls' }, target => 120, # json_encode => 1); -#$client2->send_message(action => 'GETLOG', data => { cmd => 'ls' }, target => 140, +#$client2->send_message(action => 'GETLOG', data => {}, target => 140, # json_encode => 1); -#get_command_result(); +get_command_result(); #while (1) { # my $poll = []; @@ -173,7 +173,8 @@ sub read_response { #} while (1) { - my $poll = [$client->get_poll(), $client2->get_poll()]; + #my $poll = [$client->get_poll(), $client2->get_poll()]; + my $poll = [$client2->get_poll()]; # $client->ping(poll => $poll); # $client2->ping(poll => $poll); From a2194b70feea434979a53915a40c843d2b6c07a8 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 3 Sep 2019 16:23:18 +0200 Subject: [PATCH 064/948] feat(core): working on synclog - many debugs --- gorgone/centreon/gorgone/common.pm | 31 +++++++++++++++++++ gorgone/centreon/script/gorgonecore.pm | 43 +++++++++++++++++++++++--- gorgone/modules/core/proxy/class.pm | 3 +- gorgone/modules/core/proxy/hooks.pm | 37 ++++++++++++++++++++-- 4 files changed, 107 insertions(+), 7 deletions(-) diff --git a/gorgone/centreon/gorgone/common.pm b/gorgone/centreon/gorgone/common.pm index 828eb9e3aac..5df78417769 100644 --- a/gorgone/centreon/gorgone/common.pm +++ b/gorgone/centreon/gorgone/common.pm @@ -300,6 +300,34 @@ sub is_handshake_done { # internal functions ####################### +sub synclogs { + my (%options) = @_; + + my $data; + eval { + $data = JSON::XS->new->utf8->decode($options{data}); + }; + if ($@) { + return (1, { message => 'request not well formatted' }); + } + + if (!defined($data->{id})) { + return (1, { action => 'synclog', message => 'please set id for synclog' }); + } + + print "===in synclog call===\n"; + if (defined($options{gorgone_config}->{gorgonecore}->{proxy_name}) && defined($options{gorgone}->{modules_id}->{$options{gorgone_config}->{gorgonecore}->{proxy_name}})) { + my $name = $options{gorgone}->{modules_id}->{$options{gorgone_config}->{gorgonecore}->{proxy_name}}; + my $method; + if (defined($name) && ($method = $name->can('synclog'))) { + $method->(dbh => $options{gorgone}->{db_gorgone}); + return (0, { action => 'synclog', message => 'synclog launched' }); + } + } + + return (1, { action => 'synclog', message => 'no proxy module' }); +} + sub constatus { my (%options) = @_; @@ -344,6 +372,9 @@ sub ping { if (defined($name) && ($method = $name->can('get_constatus_result'))) { $constatus = $method->(); } + if (defined($name) && ($method = $name->can('add_parent_ping'))) { + $method->(router_type => $options{router_type}, identity => $options{identity}, logger => $options{logger}); + } } return (0, { action => 'ping', message => 'ping ok', id => $options{id}, data => $constatus }, 'PONG'); diff --git a/gorgone/centreon/script/gorgonecore.pm b/gorgone/centreon/script/gorgonecore.pm index c53848662d0..24d80ce7d61 100644 --- a/gorgone/centreon/script/gorgonecore.pm +++ b/gorgone/centreon/script/gorgonecore.pm @@ -209,7 +209,7 @@ sub load_modules { } # Load internal functions - foreach my $method_name (('putlog', 'getlog', 'kill', 'ping', 'constatus', 'setcoreid')) { + foreach my $method_name (('putlog', 'getlog', 'kill', 'ping', 'constatus', 'setcoreid', 'synclogs')) { unless ($self->{internal_register}->{$method_name} = centreon::gorgone::common->can($method_name)) { $self->{logger}->writeLogError("[core] No function '$method_name'"); exit(1); @@ -224,7 +224,9 @@ sub message_run { return (undef, 1, { message => 'request not well formatted' }); } my ($action, $token, $target, $data) = ($1, $2, $3, $4); - + + print "===$action====$target=== la ===\n"; + # Check if not myself ;) if (defined($target) && ($target eq '' || $target eq $self->{id})) { $target = undef; @@ -234,7 +236,7 @@ sub message_run { $token = centreon::gorgone::common::generate_token(); } - if ($action !~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID)$/ && + if ($action !~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID|SYNCLOGS)$/ && !defined($target) && !defined($self->{modules_events}->{$action})) { centreon::gorgone::common::add_history( dbh => $self->{db_gorgone}, @@ -272,7 +274,7 @@ sub message_run { return ($token, 0); } - if ($action =~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID)$/) { + if ($action =~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID|SYNCLOGS)$/) { my ($code, $response, $response_type) = $self->{internal_register}->{lc($action)}->( gorgone => $self, gorgone_config => $config, @@ -395,6 +397,39 @@ sub handshake { } } +sub send_message_parent { + my (%options) = @_; + + if ($options{router_type} eq 'internal') { + centreon::gorgone::common::zmq_core_response( + socket => $gorgone->{internal_socket}, + identity => $options{identity}, + response_type => $options{response_type}, + data => $options{data}, + code => $options{code}, + token => $options{token} + ); + } + if ($options{router_type} eq 'external') { + my ($status, $key) = centreon::gorgone::common::is_handshake_done(dbh => $gorgone->{db_gorgone}, identity => $options{identity}); + return if ($status == 0); + print "=== response type: $options{response_type} === status: $status === $options{identity}====\n"; + print "==== token $options{token} === $options{data} ===\n"; + print "==== $config->{gorgonecore}->{cipher} = $config->{gorgonecore}->{vector} ==\n"; + centreon::gorgone::common::zmq_core_response( + socket => $gorgone->{external_socket}, + identity => $options{identity}, + response_type => $options{response_type}, + cipher => $config->{gorgonecore}->{cipher}, + vector => $config->{gorgonecore}->{vector}, + symkey => $key, + token => $options{token}, + code => $options{code}, + data => $options{data} + ); + } +} + sub router_external_event { while (1) { my ($identity, $key, $message) = $gorgone->handshake(); diff --git a/gorgone/modules/core/proxy/class.pm b/gorgone/modules/core/proxy/class.pm index 3f0b87a517e..19f8e2fd590 100644 --- a/gorgone/modules/core/proxy/class.pm +++ b/gorgone/modules/core/proxy/class.pm @@ -186,6 +186,7 @@ sub action_proxydelnode { my ($code, $data) = $self->json_decode(argument => $options{data}); return if ($code == 1); + # TODO } sub action_proxyaddsubnode { @@ -299,7 +300,7 @@ sub run { push @$polls, $self->{clients}->{$_}->{class}->get_poll(); } } - + # we try to do all we can my $rev = zmq_poll($polls, 5000); diff --git a/gorgone/modules/core/proxy/hooks.pm b/gorgone/modules/core/proxy/hooks.pm index f2a02004ada..e34f25bb5ac 100644 --- a/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/modules/core/proxy/hooks.pm @@ -55,6 +55,7 @@ my $last_pong = {}; my $register_nodes = {}; my $register_subnodes = {}; my $constatus_ping = {}; +my $parent_ping = {}; my $pools = {}; my $pools_pid = {}; my $nodes_pool = {}; @@ -334,7 +335,7 @@ sub setlogs { ); return undef; } - + $options{logger}->writeLogInfo("[proxy] -hooks- Received setlogs for '$options{data}->{data}->{id}'"); $synctime_nodes->{$options{data}->{data}->{id}}->{in_progress} = 0; @@ -363,7 +364,21 @@ sub setlogs { } else { $options{dbh}->rollback(); } - $options{dbh}->transaction_mode(0); + $options{dbh}->transaction_mode(0); + + # We try to send it to parents + foreach (keys %{$parent_ping}) { + use Data::Dumper; print Data::Dumper::Dumper($parent_ping); + print Data::Dumper::Dumper($options{data}); + centreon::script::gorgonecore::send_message_parent( + router_type => $parent_ping->{$_}, + identity => $_, + response_type => 'SYNCLOGS', + data => '{ "id": "' . $core_id . '"}', + code => 0, + token => $options{token} + ); + } } sub ping_send { @@ -385,6 +400,17 @@ sub ping_send { } } +sub synclog { + my (%options) = @_; + + # We check if we need synclogs + if ($stop == 0 && + time() - $synctime_lasttime > $synctime_option) { + $synctime_lasttime = time(); + full_sync_history(dbh => $options{dbh}); + } +} + sub full_sync_history { my (%options) = @_; @@ -610,4 +636,11 @@ sub register_nodes { } } +sub add_parent_ping { + my (%options) = @_; + + $options{logger}->writeLogDebug("[proxy] -hooks- parent ping '" . $options{identity} . "' is registered"); + $parent_ping->{$options{identity}} = $options{router_type}; +} + 1; From 376d1a6840c44ec28f6a9c5f5b82c5073eb4e061 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 3 Sep 2019 21:49:26 +0200 Subject: [PATCH 065/948] feat(proxy): manage synclogs --- gorgone/centreon/gorgone/common.pm | 7 +++---- gorgone/centreon/script/gorgonecore.pm | 5 ----- gorgone/gorgoned | 4 +++- gorgone/modules/core/proxy/class.pm | 2 +- gorgone/modules/core/proxy/hooks.pm | 9 +++------ gorgone/test-client.pl | 12 +++--------- 6 files changed, 13 insertions(+), 26 deletions(-) diff --git a/gorgone/centreon/gorgone/common.pm b/gorgone/centreon/gorgone/common.pm index 5df78417769..ad32b593dbb 100644 --- a/gorgone/centreon/gorgone/common.pm +++ b/gorgone/centreon/gorgone/common.pm @@ -140,8 +140,8 @@ sub zmq_core_response { } my $data = json_encode(data => { code => $options{code}, data => $options{data} }); - # We add 'target' for 'PONG', 'CONSTATUS'. Like that 'gorgone-proxy can get it - $msg = '[' . $response_type . '] [' . (defined($options{token}) ? $options{token} : '') . '] ' . ($response_type eq 'PONG' ? '[] ' : '') . $data; + # We add 'target' for 'PONG', 'SYNCLOGS'. Like that 'gorgone-proxy can get it + $msg = '[' . $response_type . '] [' . (defined($options{token}) ? $options{token} : '') . '] ' . ($response_type =~ /^PONG|SYNCLOGS$/ ? '[] ' : '') . $data; if (defined($options{cipher})) { my $cipher = Crypt::CBC->new( @@ -311,11 +311,10 @@ sub synclogs { return (1, { message => 'request not well formatted' }); } - if (!defined($data->{id})) { + if (!defined($data->{data}->{id})) { return (1, { action => 'synclog', message => 'please set id for synclog' }); } - print "===in synclog call===\n"; if (defined($options{gorgone_config}->{gorgonecore}->{proxy_name}) && defined($options{gorgone}->{modules_id}->{$options{gorgone_config}->{gorgonecore}->{proxy_name}})) { my $name = $options{gorgone}->{modules_id}->{$options{gorgone_config}->{gorgonecore}->{proxy_name}}; my $method; diff --git a/gorgone/centreon/script/gorgonecore.pm b/gorgone/centreon/script/gorgonecore.pm index 24d80ce7d61..8b15b91d648 100644 --- a/gorgone/centreon/script/gorgonecore.pm +++ b/gorgone/centreon/script/gorgonecore.pm @@ -225,8 +225,6 @@ sub message_run { } my ($action, $token, $target, $data) = ($1, $2, $3, $4); - print "===$action====$target=== la ===\n"; - # Check if not myself ;) if (defined($target) && ($target eq '' || $target eq $self->{id})) { $target = undef; @@ -413,9 +411,6 @@ sub send_message_parent { if ($options{router_type} eq 'external') { my ($status, $key) = centreon::gorgone::common::is_handshake_done(dbh => $gorgone->{db_gorgone}, identity => $options{identity}); return if ($status == 0); - print "=== response type: $options{response_type} === status: $status === $options{identity}====\n"; - print "==== token $options{token} === $options{data} ===\n"; - print "==== $config->{gorgonecore}->{cipher} = $config->{gorgonecore}->{vector} ==\n"; centreon::gorgone::common::zmq_core_response( socket => $gorgone->{external_socket}, identity => $options{identity}, diff --git a/gorgone/gorgoned b/gorgone/gorgoned index a4131c05419..17f7b863a7f 100644 --- a/gorgone/gorgoned +++ b/gorgone/gorgoned @@ -19,10 +19,12 @@ # limitations under the License. # +use strict; use warnings; -use centreon::script::gorgonecore; + use FindBin; use lib "$FindBin::Bin"; +use centreon::script::gorgonecore; centreon::script::gorgonecore->new()->run(); diff --git a/gorgone/modules/core/proxy/class.pm b/gorgone/modules/core/proxy/class.pm index 19f8e2fd590..a3362d7eac9 100644 --- a/gorgone/modules/core/proxy/class.pm +++ b/gorgone/modules/core/proxy/class.pm @@ -104,7 +104,7 @@ sub read_message { target => '', data => $data, ); - } elsif ($options{data} =~ /^\[REGISTERNODES|UNREGISTERNODES\]/) { + } elsif ($options{data} =~ /^\[REGISTERNODES|UNREGISTERNODES|SYNCLOGS\]/) { if ($options{data} !~ /^\[(.+?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)/m) { return undef; } diff --git a/gorgone/modules/core/proxy/hooks.pm b/gorgone/modules/core/proxy/hooks.pm index e34f25bb5ac..3b913a00e16 100644 --- a/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/modules/core/proxy/hooks.pm @@ -368,15 +368,13 @@ sub setlogs { # We try to send it to parents foreach (keys %{$parent_ping}) { - use Data::Dumper; print Data::Dumper::Dumper($parent_ping); - print Data::Dumper::Dumper($options{data}); centreon::script::gorgonecore::send_message_parent( router_type => $parent_ping->{$_}, identity => $_, response_type => 'SYNCLOGS', - data => '{ "id": "' . $core_id . '"}', + data => { id => $core_id }, code => 0, - token => $options{token} + token => undef, ); } } @@ -404,8 +402,7 @@ sub synclog { my (%options) = @_; # We check if we need synclogs - if ($stop == 0 && - time() - $synctime_lasttime > $synctime_option) { + if ($stop == 0) { $synctime_lasttime = time(); full_sync_history(dbh => $options{dbh}); } diff --git a/gorgone/test-client.pl b/gorgone/test-client.pl index 07456549079..280d775d366 100644 --- a/gorgone/test-client.pl +++ b/gorgone/test-client.pl @@ -37,9 +37,9 @@ sub get_command_result { my ($current_retries, $retries) = (0, 4); - $stopped->{$client2->{identity}} = '^([0-9]+0|32)$'; + $stopped->{$client2->{identity}} = '^(1|2)$'; $client2->send_message( - action => 'COMMAND', data => { command => 'ls /' }, target => 150, + action => 'COMMAND', data => { content => { command => 'ls /' } }, target => 150, json_encode => 1 ); while (1) { @@ -132,8 +132,6 @@ sub read_response { ); $client2->init(callback => \&read_response_result); -#$client->send_message(action => 'ACLADDHOST', data => { organization_id => 1 }, -# json_encode => 1); #$client->send_message( # action => 'SCOMRESYNC', # data => { container_id => 'toto' }, @@ -141,10 +139,6 @@ sub read_response { #); #$client->send_message(action => 'PUTLOG', data => { code => 120, etime => time(), token => 'plopplop', data => { 'nawak' => 'nawak2' } }, # json_encode => 1); -#$client->send_message(action => 'ACLADDHOST', data => { organization_id => 10 }, target => 10, -# json_encode => 1); -#$client2->send_message(action => 'ACLADDHOST', data => { organization_id => 14 }, -# json_encode => 1); #$client2->send_message(action => 'RELOADCRON', data => { }, # json_encode => 1); @@ -152,7 +146,7 @@ sub read_response { #$client2->send_message(action => 'ENGINECOMMAND', data => { command => '[1417705150] ENABLE_HOST_CHECK;host1', engine_pipe => '/var/lib/centreon-engine/rw/centengine.cmd' }, target => 120, # json_encode => 1); -#$client2->send_message(action => 'COMMAND', data => { cmd => 'ls' }, target => 150, +#$client2->send_message(action => 'COMMAND', data => { content => { command => 'ls' } }, target => 150, # json_encode => 1); #$client2->send_message(action => 'CONSTATUS'); From 2baf417e4bd9ab3de86c3c7fb157d396ee2ce768 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Thu, 5 Sep 2019 16:27:42 +0200 Subject: [PATCH 066/948] feat(core): enhance setcoreid system. and enhance logger --- gorgone/centreon/gorgone/common.pm | 8 ++++ gorgone/centreon/misc/logger.pm | 74 +++++++++++++++++++---------- gorgone/modules/core/proxy/class.pm | 2 +- gorgone/modules/core/proxy/hooks.pm | 33 +++++++++++-- 4 files changed, 88 insertions(+), 29 deletions(-) diff --git a/gorgone/centreon/gorgone/common.pm b/gorgone/centreon/gorgone/common.pm index ad32b593dbb..6f0fd98458e 100644 --- a/gorgone/centreon/gorgone/common.pm +++ b/gorgone/centreon/gorgone/common.pm @@ -356,6 +356,14 @@ sub setcoreid { return (1, { action => 'setcoreid', message => 'please set id for setcoreid' }); } + if (defined($options{gorgone_config}->{gorgonecore}->{proxy_name}) && defined($options{gorgone}->{modules_id}->{$options{gorgone_config}->{gorgonecore}->{proxy_name}})) { + my $name = $options{gorgone}->{modules_id}->{$options{gorgone_config}->{gorgonecore}->{proxy_name}}; + my $method; + if (defined($name) && ($method = $name->can('setcoreid'))) { + $method->(core_id => $data->{id}); + } + } + $options{logger}->writeLogInfo('[core] setcoreid changed ' . $data->{id}); $options{gorgone}->{id} = $data->{id}; return (0, { action => 'setcoreid', message => 'setcoreid changed' }); diff --git a/gorgone/centreon/misc/logger.pm b/gorgone/centreon/misc/logger.pm index 89b7fc54e36..fa99cc08fd6 100644 --- a/gorgone/centreon/misc/logger.pm +++ b/gorgone/centreon/misc/logger.pm @@ -51,6 +51,7 @@ use strict; use warnings; use Sys::Syslog qw(:standard :macros); use IO::Handle; +use Encode; my %severities = (1 => LOG_INFO, 2 => LOG_ERR, @@ -68,6 +69,8 @@ sub new { old_severity => 3, # 0 = stdout, 1 = file, 2 = syslog log_mode => 0, + # Output pid of current process + withpid => 0, # syslog log_facility => undef, log_option => LOG_PID, @@ -132,49 +135,66 @@ sub redirect_output { sub set_default_severity { my $self = shift; - $self->{"severity"} = $self->{"old_severity"}; + $self->{severity} = $self->{old_severity}; } # Getter/Setter Log severity sub severity { my $self = shift; if (@_) { - my $save_severity = $self->{"severity"}; + my $save_severity = $self->{severity}; if ($_[0] =~ /^[012347]$/) { - $self->{"severity"} = $_[0]; - } elsif ($_[0] eq "none") { - $self->{"severity"} = 0; - } elsif ($_[0] eq "error") { - $self->{"severity"} = 1; - } elsif ($_[0] eq "info") { - $self->{"severity"} = 3; - } elsif ($_[0] eq "debug") { - $self->{"severity"} = 7; + $self->{severity} = $_[0]; + } elsif ($_[0] eq 'none') { + $self->{severity} = 0; + } elsif ($_[0] eq 'error') { + $self->{severity} = 1; + } elsif ($_[0] eq 'info') { + $self->{severity} = 3; + } elsif ($_[0] eq 'debug') { + $self->{severity} = 7; } else { - $self->writeLogError("Wrong severity value set."); + $self->writeLogError('Wrong severity value set.'); return -1; } - $self->{"old_severity"} = $save_severity; + $self->{old_severity} = $save_severity; } - return $self->{"severity"}; + return $self->{severity}; +} + +sub withpid { + my $self = shift; + if (@_) { + $self->{withpid} = $_[0]; + } + return $self->{withpid}; } 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", + return sprintf('%04d-%02d-%02d %02d:%02d:%02d', $year+1900, $mon+1, $mday, $hour, $min, $sec); } -sub writeLog($$$%) { - my ($self, $severity, $msg, %options) = @_; +sub writeLog { + my ($self, %options) = @_; + my $withdate = (defined $options{withdate}) ? $options{withdate} : 1; - my $newmsg = ($withdate) - ? $self->get_date . " - $msg" : $msg; + my $withseverity = (defined $options{withseverity}) ? $options{withseverity} : 1; - if (($self->{severity} & $severity) == 0) { + my $msg = $options{message}; + $msg = (($self->{withpid} == 1) ? "$$ - $msg " : $msg); + my $newmsg = ($withseverity) + ? $options{severity_str} . " - $msg" : $msg; + $newmsg = ($withdate) + ? $self->get_date . " - $newmsg" : $newmsg; + + if (($self->{severity} & $options{severity}) == 0) { return; } + + $newmsg = encode('UTF-8', $newmsg); if ($self->{log_mode} == 0) { print "$newmsg\n"; } elsif ($self->{log_mode} == 1) { @@ -182,20 +202,26 @@ sub writeLog($$$%) { print { $self->{filehandler} } "$newmsg\n"; } } elsif ($self->{log_mode} == 2) { - syslog($severities{$severity}, $msg); + syslog($severities{$options{severity}}, $msg); } } sub writeLogDebug { - shift->writeLog(4, @_); + my ($self, $msg) = @_; + + $self->writeLog(severity => 4, severity_str => 'DEBUG', message => $msg); } sub writeLogInfo { - shift->writeLog(2, @_); + my ($self, $msg) = @_; + + $self->writeLog(severity => 2, severity_str => 'INFO', message => $msg); } sub writeLogError { - shift->writeLog(1, @_); + my ($self, $msg) = @_; + + $self->writeLog(severity => 1, severity_str => 'ERROR', message => $msg); } sub DESTROY { diff --git a/gorgone/modules/core/proxy/class.pm b/gorgone/modules/core/proxy/class.pm index a3362d7eac9..b5d28ea9022 100644 --- a/gorgone/modules/core/proxy/class.pm +++ b/gorgone/modules/core/proxy/class.pm @@ -143,7 +143,7 @@ sub connect { if ($self->{clients}->{$options{id}}->{type} eq 'push_zmq') { $self->{clients}->{$options{id}}->{class} = centreon::gorgone::clientzmq->new( - identity => 'proxy-' . $self->{pool_id} . '-' . $options{id}, + identity => 'proxy-' . $self->{core_id} . '-' . $options{id}, cipher => $self->{clients}->{$options{id}}->{cipher}, vector => $self->{clients}->{$options{id}}->{vector}, server_pubkey => $self->{clients}->{$options{id}}->{server_pubkey}, diff --git a/gorgone/modules/core/proxy/hooks.pm b/gorgone/modules/core/proxy/hooks.pm index 3b913a00e16..7d2c00e1934 100644 --- a/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/modules/core/proxy/hooks.pm @@ -277,7 +277,14 @@ sub check { create_child(pool_id => $pool_id, logger => $options{logger}); } } - + + # Check if we need to create a child + for my $pool_id (1..$config->{pool}) { + if (!defined($pools->{$pool_id})) { + create_child(pool_id => $pool_id, logger => $options{logger}); + } + } + foreach (keys %{$pools}) { $count++ if ($pools->{$_}->{running} == 1); } @@ -309,6 +316,13 @@ sub check { $ping_time = time(); ping_send(dbh => $options{dbh}); } + + # We clean all parents + foreach (keys %$parent_ping) { + if (time() - $parent_ping->{$_}->{last_time} > 1800) { # 30 minutes + delete $parent_ping->{$_}; + } + } return $count; } @@ -369,7 +383,7 @@ sub setlogs { # We try to send it to parents foreach (keys %{$parent_ping}) { centreon::script::gorgonecore::send_message_parent( - router_type => $parent_ping->{$_}, + router_type => $parent_ping->{$_}->{router_type}, identity => $_, response_type => 'SYNCLOGS', data => { id => $core_id }, @@ -470,7 +484,12 @@ sub rr_pool { sub create_child { my (%options) = @_; - + + if (!defined($core_id) || $core_id =~ /^\s*$/) { + $options{logger}->writeLogError("[proxy] -hooks- Cannot create child. need a core id"); + return ; + } + $options{logger}->writeLogInfo("[proxy] -hooks- Create module 'proxy' child process for pool id '" . $options{pool_id} . "'"); my $child_pid = fork(); if ($child_pid == 0) { @@ -633,11 +652,17 @@ sub register_nodes { } } +sub setcoreid { + my (%options) = @_; + + $core_id = $options{core_id}; +} + sub add_parent_ping { my (%options) = @_; $options{logger}->writeLogDebug("[proxy] -hooks- parent ping '" . $options{identity} . "' is registered"); - $parent_ping->{$options{identity}} = $options{router_type}; + $parent_ping->{$options{identity}} = { last_time => time(), router_type => $options{router_type} }; } 1; From 6462b9c545e161189f1b76f60e48ef3e647f8ed8 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Fri, 6 Sep 2019 15:03:26 +0200 Subject: [PATCH 067/948] feat(core): add new module legacycmd --- gorgone/centreon/gorgone/common.pm | 4 +- gorgone/centreon/misc/logger.pm | 6 +- gorgone/centreon/script.pm | 51 ++--- gorgone/config/gorgoned.yml | 5 + gorgone/modules/centreon/engine/class.pm | 8 +- gorgone/modules/centreon/legacycmd/class.pm | 221 ++++++++++++++++++++ gorgone/modules/centreon/legacycmd/hooks.pm | 159 ++++++++++++++ gorgone/modules/core/proxy/class.pm | 2 +- 8 files changed, 422 insertions(+), 34 deletions(-) create mode 100644 gorgone/modules/centreon/legacycmd/class.pm create mode 100644 gorgone/modules/centreon/legacycmd/hooks.pm diff --git a/gorgone/centreon/gorgone/common.pm b/gorgone/centreon/gorgone/common.pm index 6f0fd98458e..191e59d1da2 100644 --- a/gorgone/centreon/gorgone/common.pm +++ b/gorgone/centreon/gorgone/common.pm @@ -230,7 +230,7 @@ sub client_helo_encrypt { return (-1, "Encoding issue: $@"); } - return (0, '[' . $options{identity} . '] [' . $client_pubkey . '] [' . $ciphertext . ']'); + return (0, '[' . $options{identity} . '] [' . $client_pubkey . '] [' . unpack('H*', $ciphertext) . ']'); } sub is_client_can_connect { @@ -244,7 +244,7 @@ sub is_client_can_connect { my ($client, $client_pubkey_str, $cipher_text) = ($1, $2, $3); eval { - $plaintext = $options{privkey}->decrypt($cipher_text, 'v1.5'); + $plaintext = $options{privkey}->decrypt(pack('H*', $cipher_text), 'v1.5'); }; if ($@) { $options{logger}->writeLogError("Decoding issue: " . $@); diff --git a/gorgone/centreon/misc/logger.pm b/gorgone/centreon/misc/logger.pm index fa99cc08fd6..1a3a6082d73 100644 --- a/gorgone/centreon/misc/logger.pm +++ b/gorgone/centreon/misc/logger.pm @@ -173,8 +173,10 @@ sub withpid { 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); + return sprintf( + '%04d-%02d-%02d %02d:%02d:%02d', + $year+1900, $mon+1, $mday, $hour, $min, $sec + ); } sub writeLog { diff --git a/gorgone/centreon/script.pm b/gorgone/centreon/script.pm index e03912d3619..6d4da4ccfa7 100644 --- a/gorgone/centreon/script.pm +++ b/gorgone/centreon/script.pm @@ -39,26 +39,25 @@ $SIG{__DIE__} = sub { sub new { my ($class, $name, %options) = @_; - my %defaults = - ( - config_file => "/etc/centreon/centreon-config.pm", + my %defaults = ( + config_file => '/etc/centreon/centreon-config.pm', log_file => undef, centreon_db_conn => 0, centstorage_db_conn => 0, - severity => "info", + severity => 'info', noconfig => 0, - noroot => 1 - ); + noroot => 0 + ); my $self = {%defaults, %options}; bless $self, $class; $self->{name} = $name; $self->{logger} = centreon::misc::logger->new(); $self->{options} = { - "config=s" => \$self->{config_file}, - "logfile=s" => \$self->{log_file}, - "severity=s" => \$self->{severity}, - "help|?" => \$self->{help} + 'config=s' => \$self->{config_file}, + 'logfile=s' => \$self->{log_file}, + 'severity=s' => \$self->{severity}, + 'help|?' => \$self->{help} }; return $self; } @@ -75,27 +74,29 @@ sub init { # Stop exec if root if ($< == 0) { $self->{logger}->writeLogError("Can't execute script as root."); - die("Quit"); + die('Quit'); } } if ($self->{centreon_db_conn}) { - $self->{cdb} = centreon::misc::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->{cdb} = centreon::misc::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::misc::lock::sql->new($self->{name}, dbc => $self->{cdb}); $self->{lock}->set(); } if ($self->{centstorage_db_conn}) { - $self->{csdb} = centreon::misc::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}); + $self->{csdb} = centreon::misc::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} + ); } } @@ -120,8 +121,8 @@ sub parse_options { my $self = shift; Getopt::Long::Configure('bundling'); - die "Command line error" if !GetOptions(%{$self->{options}}); - pod2usage(-exitval => 1, -input => $FindBin::Bin . "/" . $FindBin::Script) if $self->{help}; + die "Command line error" if (!GetOptions(%{$self->{options}})); + pod2usage(-exitval => 1, -input => $FindBin::Bin . "/" . $FindBin::Script) if ($self->{help}); if ($self->{noconfig} == 0) { require $self->{config_file}; $self->{centreon_config} = $centreon_config; diff --git a/gorgone/config/gorgoned.yml b/gorgone/config/gorgoned.yml index 26c59334f2a..566b8dfabbb 100644 --- a/gorgone/config/gorgoned.yml +++ b/gorgone/config/gorgoned.yml @@ -94,6 +94,11 @@ modules: enable: true command_file: "/var/lib/centreon-engine/rw/centengine.cmd" + - name: legacycmd + package: modules::centreon::legacycmd::hooks + enable: true + cmd_file: "/var/lib/centreon/centcore.cmd" + - name: scom package: modules::plugins::scom::hooks enable: false diff --git a/gorgone/modules/centreon/engine/class.pm b/gorgone/modules/centreon/engine/class.pm index 4af678a4a51..37aecdee9f9 100644 --- a/gorgone/modules/centreon/engine/class.pm +++ b/gorgone/modules/centreon/engine/class.pm @@ -123,7 +123,7 @@ sub action_enginecommand { my $fh; eval { - local $SIG{ALRM} = sub { die "Timeout command\n" }; + local $SIG{ALRM} = sub { die 'Timeout command' }; alarm $self->{timeout}; open($fh, ">", $self->{config}->{command_file}) or die "cannot open '$self->{config}->{command_file}': $!"; print $fh $options{data}->{content}->{command} . "\n"; @@ -171,7 +171,7 @@ sub action_run { socket => $socket_log, code => $self->ACTION_FINISH_KO, token => $options{token}, - data => { message => "action unknown" } + data => { message => 'action unknown' } ); return -1; } @@ -182,7 +182,7 @@ sub action_run { sub create_child { my ($self, %options) = @_; - $self->{logger}->writeLogInfo("[engine] -class- create sub-process"); + $self->{logger}->writeLogInfo('[engine] -class- create sub-process'); $options{message} =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; my ($action, $token) = ($1, $2); @@ -205,7 +205,7 @@ sub create_child { $self->send_log( code => $self->ACTION_BEGIN, token => $token, - data => { message => "proceed action" } + data => { message => 'proceed action' } ); } } diff --git a/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/modules/centreon/legacycmd/class.pm new file mode 100644 index 00000000000..3a2731daf22 --- /dev/null +++ b/gorgone/modules/centreon/legacycmd/class.pm @@ -0,0 +1,221 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package modules::centreon::legacycmd::class; + +use base qw(centreon::gorgone::module); + +use strict; +use warnings; +use centreon::gorgone::common; +use centreon::misc::misc; +use ZMQ::LibZMQ4; +use ZMQ::Constants qw(:all); +use File::Copy; + +my %handlers = (TERM => {}, HUP => {}); +my ($connector); + +sub new { + my ($class, %options) = @_; + $connector = {}; + $connector->{internal_socket} = undef; + $connector->{logger} = $options{logger}; + $connector->{config} = $options{config}; + if (!defined($connector->{config}->{cmd_file}) || $connector->{config}->{cmd_file} eq '') { + $connector->{config}->{cmd_file} = '/var/lib/centreon/centcore.cmd'; + } + $connector->{config_core} = $options{config_core}; + $connector->{stop} = 0; + + bless $connector, $class; + $connector->set_signal_handlers; + return $connector; +} + +sub set_signal_handlers { + my $self = shift; + + $SIG{TERM} = \&class_handle_TERM; + $handlers{TERM}->{$self} = sub { $self->handle_TERM() }; + $SIG{HUP} = \&class_handle_HUP; + $handlers{HUP}->{$self} = sub { $self->handle_HUP() }; +} + +sub handle_HUP { + my $self = shift; + $self->{reload} = 0; +} + +sub handle_TERM { + my $self = shift; + $self->{logger}->writeLogInfo("[legacycmd] -class- $$ Receiving order to stop..."); + $self->{stop} = 1; +} + +sub class_handle_TERM { + foreach (keys %{$handlers{TERM}}) { + &{$handlers{TERM}->{$_}}(); + } +} + +sub class_handle_HUP { + foreach (keys %{$handlers{HUP}}) { + &{$handlers{HUP}->{$_}}(); + } +} + +sub move_cmd_file { + my ($self, %options) = @_; + + my $handle; + if (-e $self->{config}->{cmd_file} . '_read') { + if (!open($handle, '+<', $self->{config}->{cmd_file} . '_read')) { + $self->{logger}->writeLogError("[legacycmd] -class- cannot open file '" . $self->{config}->{cmd_file} . "_read': $!"); + return -1; + } + + return (0, $handle); + } + + return -1 if (! -e $connector->{config}->{cmd_file}); + + if (!File::Copy::move($self->{config}->{cmd_file}, $self->{config}->{cmd_file} . '_read')) { + $self->{logger}->writeLogError("[legacycmd] -class- cannot move file '" . $connector->{config}->{cmd_file} . "': $!"); + return -1; + } + + if (!open($handle, '+<', $self->{config}->{cmd_file} . '_read')) { + $self->{logger}->writeLogError("[legacycmd] -class- cannot open file '" . $self->{config}->{cmd_file} . "_read': $!"); + return -1; + } + + return (0, $handle); +} + +#sub getNagiosConfigurationField($$){ +# my $self = shift; + +# my ($status, $sth) = $self->{centreon_dbc}->query("SELECT " . $_[1] . " FROM `cfg_nagios` WHERE `nagios_server_id` = '" . $_[0] . "' AND nagios_activate = '1'"); +# if ($status == -1) { +# $self->{logger}->writeLogError("Error when getting server properties"); +# return undef; +# } +# my $data = $sth->fetchrow_hashref(); +# return $data->{$_[1]}; +#} + +sub execute_cmd { + my ($self, %options) = @_; + + if ($options{cmd} eq 'EXTERNALCMD') { + $self->send_internal_action( + action => 'ENGINECOMMAND', + target => $options{target}, + token => $self->generate_token(), + data => { content => { command => $options{param}, engine_pipe => '/var/lib/centreon-engine/rw/centengine.cmd' } }, + ); + } +} + +sub handle_cmd_file { + my ($self, %options) = @_; + require bytes; + + my ($code, $handle) = $self->move_cmd_file(); + return if ($code == -1); + + while (my $line = <$handle>) { + if ($self->{stop} == 1) { + close($handle); + return ; + } + + if ($line =~ /^(.*?):(.*?):(.*)/) { + $self->execute_cmd(cmd => $1, target => $2, param => $3); + my $current_pos = tell($handle); + seek($handle, $current_pos - bytes::length($line), 0); + syswrite($handle, '-'); + # line is useless + $line = <$handle>; + } + } + + $self->{logger}->writeLogDebug("[legacycmd] -class- process file '" . $connector->{config}->{cmd_file} . "_read'"); + close($handle); + unlink($self->{config}->{cmd_file} . '_read'); +} + +sub event { + while (1) { + my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $connector->{internal_socket}); + + $connector->{logger}->writeLogDebug("[legacycmd] -class- Event: $message"); + if ($message =~ /^\[(.*?)\]/) { + if ((my $method = $connector->can('action_' . lc($1)))) { + $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; + my ($action, $token) = ($1, $2); + my $data = JSON::XS->new->utf8->decode($3); + $method->($connector, token => $token, data => $data); + } + } + + last unless (centreon::gorgone::common::zmq_still_read(socket => $connector->{internal_socket})); + } +} + +sub run { + my ($self, %options) = @_; + + # Connect internal + $connector->{internal_socket} = centreon::gorgone::common::connect_com( + zmq_type => 'ZMQ_DEALER', + name => 'gorgonelegacycmd', + logger => $self->{logger}, + type => $self->{config_core}->{internal_com_type}, + path => $self->{config_core}->{internal_com_path} + ); + centreon::gorgone::common::zmq_send_message( + socket => $connector->{internal_socket}, + action => 'LEGACYCMDREADY', data => {}, + json_encode => 1 + ); + + $self->{poll} = [ + { + socket => $connector->{internal_socket}, + events => ZMQ_POLLIN, + callback => \&event, + } + ]; + while (1) { + # we try to do all we can + my $rev = zmq_poll($self->{poll}, 1000); + if ($rev == 0 && $self->{stop} == 1) { + $self->{logger}->writeLogInfo("[legacycmd] -class- $$ has quit"); + zmq_close($connector->{internal_socket}); + exit(0); + } + + $self->handle_cmd_file(); + } +} + +1; diff --git a/gorgone/modules/centreon/legacycmd/hooks.pm b/gorgone/modules/centreon/legacycmd/hooks.pm new file mode 100644 index 00000000000..433d19a01dd --- /dev/null +++ b/gorgone/modules/centreon/legacycmd/hooks.pm @@ -0,0 +1,159 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package modules::centreon::legacycmd::hooks; + +use warnings; +use strict; +use centreon::script::gorgonecore; +use modules::centreon::legacycmd::class; +use JSON::XS; + +my $NAME = 'legacycmd'; +my $EVENTS = [ + { event => 'LEGACYCMDREADY' }, +]; + +my $config_core; +my $config; +my $legacycmd = {}; +my $stop = 0; + +sub register { + my (%options) = @_; + + $config = $options{config}; + $config_core = $options{config_core}; + return ($NAME, $EVENTS); +} + +sub init { + my (%options) = @_; + + create_child(logger => $options{logger}); +} + +sub routing { + my (%options) = @_; + + my $data; + eval { + $data = JSON::XS->new->utf8->decode($options{data}); + }; + if ($@) { + $options{logger}->writeLogError("[legacycmd] -hooks- Cannot decode json data: $@"); + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => 10, token => $options{token}, + data => { message => 'gorgone-legacycmd: cannot decode json' }, + json_encode => 1 + ); + return undef; + } + + if ($options{action} eq 'LEGACYCMDREADY') { + $legacycmd->{ready} = 1; + return undef; + } + + if (centreon::script::gorgonecore::waiting_ready(ready => \$legacycmd->{ready}) == 0) { + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => 10, token => $options{token}, + data => { message => 'gorgone-legacycmd: still no ready' }, + json_encode => 1 + ); + return undef; + } + + centreon::gorgone::common::zmq_send_message( + socket => $options{socket}, + identity => 'gorgonelegacycmd', + action => $options{action}, + data => $options{data}, + token => $options{token}, + ); +} + +sub gently { + my (%options) = @_; + + $stop = 1; + $options{logger}->writeLogInfo("[legacycmd] -hooks- Send TERM signal"); + if ($legacycmd->{running} == 1) { + CORE::kill('TERM', $legacycmd->{pid}); + } +} + +sub kill { + my (%options) = @_; + + if ($legacycmd->{running} == 1) { + $options{logger}->writeLogInfo("[legacycmd] -hooks- Send KILL signal for pool"); + CORE::kill('KILL', $legacycmd->{pid}); + } +} + +sub kill_internal { + my (%options) = @_; + +} + +sub check { + my (%options) = @_; + + my $count = 0; + foreach my $pid (keys %{$options{dead_childs}}) { + # Not me + next if ($legacycmd->{pid} != $pid); + + $legacycmd = {}; + delete $options{dead_childs}->{$pid}; + if ($stop == 0) { + create_child(logger => $options{logger}); + } + } + + $count++ if (defined($legacycmd->{running}) && $legacycmd->{running} == 1); + + return $count; +} + +# Specific functions +sub create_child { + my (%options) = @_; + + $options{logger}->writeLogInfo("[legacycmd] -hooks- Create module process"); + my $child_pid = fork(); + if ($child_pid == 0) { + $0 = 'gorgone-legacycmd'; + my $module = modules::centreon::legacycmd::class->new( + logger => $options{logger}, + config_core => $config_core, + config => $config, + ); + $module->run(); + exit(0); + } + $options{logger}->writeLogInfo("[legacycmd] -hooks- PID $child_pid (gorgone-legacycmd)"); + $legacycmd = { pid => $child_pid, ready => 0, running => 1 }; +} + +1; diff --git a/gorgone/modules/core/proxy/class.pm b/gorgone/modules/core/proxy/class.pm index b5d28ea9022..d666f1795df 100644 --- a/gorgone/modules/core/proxy/class.pm +++ b/gorgone/modules/core/proxy/class.pm @@ -249,7 +249,7 @@ sub proxy { sub event_internal { while (1) { my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $connector->{internal_socket}); - + proxy(message => $message); last unless (centreon::gorgone::common::zmq_still_read(socket => $connector->{internal_socket})); } From 7538865f415e84f516b685b21215b7943745c61f Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Fri, 6 Sep 2019 17:02:38 +0200 Subject: [PATCH 068/948] feat(legacymd): wip --- gorgone/modules/centreon/legacycmd/class.pm | 49 ++++++++++++++++----- gorgone/modules/centreon/legacycmd/hooks.pm | 4 ++ 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/modules/centreon/legacycmd/class.pm index 3a2731daf22..92ff2b2e18a 100644 --- a/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/modules/centreon/legacycmd/class.pm @@ -26,6 +26,7 @@ use strict; use warnings; use centreon::gorgone::common; use centreon::misc::misc; +use centreon::misc::objects::object; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); use File::Copy; @@ -43,6 +44,7 @@ sub new { $connector->{config}->{cmd_file} = '/var/lib/centreon/centcore.cmd'; } $connector->{config_core} = $options{config_core}; + $connector->{config_db_centreon} = $options{config_db_centreon}; $connector->{stop} = 0; bless $connector, $class; @@ -110,17 +112,25 @@ sub move_cmd_file { return (0, $handle); } -#sub getNagiosConfigurationField($$){ -# my $self = shift; +sub get_pipe_engines { + my ($self, %options) = @_; + + $self->{engine_pipe} = {}; + my ($status, $datas) = $self->{class_object_centreon}->custom_execute( + request => 'SELECT nagios_server_id, command_file FROM nagios_server', + mode => 2 + ); + if ($status == -1 || !defined($datas->[0])) { + $self->{logger}->writeLogError('[legacycmd] -class- cannot get engine pipe for pollers'); + return -1; + } -# my ($status, $sth) = $self->{centreon_dbc}->query("SELECT " . $_[1] . " FROM `cfg_nagios` WHERE `nagios_server_id` = '" . $_[0] . "' AND nagios_activate = '1'"); -# if ($status == -1) { -# $self->{logger}->writeLogError("Error when getting server properties"); -# return undef; -# } -# my $data = $sth->fetchrow_hashref(); -# return $data->{$_[1]}; -#} + foreach (@$datas) { + $self->{engine_pipe}->{$_->[0]} = $_->[1]; + } + + return 0; +} sub execute_cmd { my ($self, %options) = @_; @@ -130,7 +140,12 @@ sub execute_cmd { action => 'ENGINECOMMAND', target => $options{target}, token => $self->generate_token(), - data => { content => { command => $options{param}, engine_pipe => '/var/lib/centreon-engine/rw/centengine.cmd' } }, + data => { + content => { + command => $options{param}, + engine_pipe => defined($self->{engine_pipe}->{$options{target}}) ? $self->{engine_pipe}->{$options{target}} : '/var/lib/centreon-engine/rw/centengine.cmd' + } + }, ); } } @@ -139,6 +154,7 @@ sub handle_cmd_file { my ($self, %options) = @_; require bytes; + return if ($self->get_pipe_engines() == -1); my ($code, $handle) = $self->move_cmd_file(); return if ($code == -1); @@ -198,6 +214,15 @@ sub run { json_encode => 1 ); + $self->{db_centreon} = centreon::misc::db->new( + dsn => $self->{config_db_centreon}->{dsn}, + user => $self->{config_db_centreon}->{username}, + password => $self->{config_db_centreon}->{password}, + force => 2, + logger => $self->{logger} + ); + $self->{class_object_centreon} = centreon::misc::objects::object->new(logger => $self->{logger}, db_centreon => $self->{db_centreon}); + $self->{poll} = [ { socket => $connector->{internal_socket}, @@ -207,7 +232,7 @@ sub run { ]; while (1) { # we try to do all we can - my $rev = zmq_poll($self->{poll}, 1000); + my $rev = zmq_poll($self->{poll}, 2000); if ($rev == 0 && $self->{stop} == 1) { $self->{logger}->writeLogInfo("[legacycmd] -class- $$ has quit"); zmq_close($connector->{internal_socket}); diff --git a/gorgone/modules/centreon/legacycmd/hooks.pm b/gorgone/modules/centreon/legacycmd/hooks.pm index 433d19a01dd..96332d34650 100644 --- a/gorgone/modules/centreon/legacycmd/hooks.pm +++ b/gorgone/modules/centreon/legacycmd/hooks.pm @@ -35,12 +35,14 @@ my $config_core; my $config; my $legacycmd = {}; my $stop = 0; +my $config_db_centreon; sub register { my (%options) = @_; $config = $options{config}; $config_core = $options{config_core}; + $config_db_centreon = $options{config_db_centreon}; return ($NAME, $EVENTS); } @@ -141,6 +143,7 @@ sub create_child { my (%options) = @_; $options{logger}->writeLogInfo("[legacycmd] -hooks- Create module process"); + my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-legacycmd'; @@ -148,6 +151,7 @@ sub create_child { logger => $options{logger}, config_core => $config_core, config => $config, + config_db_centreon => $config_db_centreon, ); $module->run(); exit(0); From ddfa5ed69a4e549c7dd11591141cbd84d1eb66d3 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Mon, 9 Sep 2019 15:01:17 +0200 Subject: [PATCH 069/948] feat(pull): synclogs trasnmit --- gorgone/modules/core/pull/hooks.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/modules/core/pull/hooks.pm b/gorgone/modules/core/pull/hooks.pm index 19a3e90d0d7..abbe186bb50 100644 --- a/gorgone/modules/core/pull/hooks.pm +++ b/gorgone/modules/core/pull/hooks.pm @@ -147,9 +147,9 @@ sub transmit_back { return '[SETLOGS] [' . $1 . '] [] ' . $2; } return undef; - } elsif ($options{message} =~ /^\[PONG\]/) { + } elsif ($options{message} =~ /^\[PONG|SYNCLOGS\]/) { return $options{message}; - } + } return undef; } From 4cb2bcc3d1563c2e6640e5419cec8af6ed197fb5 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Mon, 9 Sep 2019 15:01:32 +0200 Subject: [PATCH 070/948] feat(pull): synclogs trasnmit --- gorgone/modules/core/pull/hooks.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/modules/core/pull/hooks.pm b/gorgone/modules/core/pull/hooks.pm index abbe186bb50..6e26c211995 100644 --- a/gorgone/modules/core/pull/hooks.pm +++ b/gorgone/modules/core/pull/hooks.pm @@ -147,7 +147,7 @@ sub transmit_back { return '[SETLOGS] [' . $1 . '] [] ' . $2; } return undef; - } elsif ($options{message} =~ /^\[PONG|SYNCLOGS\]/) { + } elsif ($options{message} =~ /^\[(PONG|SYNCLOGS)\]/) { return $options{message}; } return undef; From 7064b9d642fbfb0941f9a0d4c79b17b8e9faffb2 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 10 Sep 2019 10:25:38 +0200 Subject: [PATCH 071/948] feat(proxy): wip sshclient --- gorgone/config/registernodes.json | 2 +- gorgone/modules/core/proxy/class.pm | 25 ++++++- gorgone/modules/core/proxy/sshclient.pm | 87 +++++++++++++++++++++++++ gorgone/test-client.pl | 2 +- 4 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 gorgone/modules/core/proxy/sshclient.pm diff --git a/gorgone/config/registernodes.json b/gorgone/config/registernodes.json index 688572a0914..015c346450b 100644 --- a/gorgone/config/registernodes.json +++ b/gorgone/config/registernodes.json @@ -1,6 +1,6 @@ { "nodes": [ - { "id": 100, "type": "push_ssh", "address": "10.0.0.1", "ssh_port": 22 }, + { "id": 100, "type": "push_ssh", "address": "10.30.2.49", "ssh_port": 22 }, { "id": 140, "type": "push_zmq", diff --git a/gorgone/modules/core/proxy/class.pm b/gorgone/modules/core/proxy/class.pm index d666f1795df..9bd9c3db654 100644 --- a/gorgone/modules/core/proxy/class.pm +++ b/gorgone/modules/core/proxy/class.pm @@ -26,6 +26,7 @@ use strict; use warnings; use centreon::gorgone::common; use centreon::gorgone::clientzmq; +use modules::core::proxy::sshclient; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); use JSON::XS; @@ -156,7 +157,21 @@ sub connect { logger => $self->{logger}, ); $self->{clients}->{$options{id}}->{class}->init(callback => \&read_message); + } elsif ($self->{clients}->{$options{id}}->{type} eq 'push_ssh') { + $self->{clients}->{$options{id}}->{class} = modules::core::proxy::sshclient->new(logger => $self->{logger}); + my $code = $self->{clients}->{$options{id}}->{class}->open_session( + ssh_host => $self->{clients}->{$options{id}}->{address}, + ssh_port => $self->{clients}->{$options{id}}->{ssh_port}, + ssh_username => $self->{clients}->{$options{id}}->{ssh_username}, + ssh_password => $self->{clients}->{$options{id}}->{ssh_password}, + ); + if ($code != 0) { + $self->{clients}->{$options{id}}->{delete} = 1; + return -1; + } } + + return 0; } sub action_proxyaddnode { @@ -226,7 +241,10 @@ sub proxy { $target_client = $connector->{subnodes}->{$target}; } if (!defined($connector->{clients}->{$target_client}->{class})) { - $connector->connect(id => $target_client); + if ($connector->connect(id => $target_client) != 0) { + $connector->send_log(code => centreon::gorgone::module::ACTION_FINISH_KO, token => $token, data => { message => "cannot connect on target node '$target_client'" }); + return ; + } } if ($connector->{clients}->{$target_client}->{type} eq 'push_zmq') { @@ -241,6 +259,11 @@ sub proxy { $connector->{logger}->writeLogError("[proxy] -class- Send message problem for '$target': $msg"); $connector->{clients}->{$target}->{delete} = 1; } + } elsif ($connector->{clients}->{$target_client}->{type} eq 'push_ssh') { + my ($status) = $connector->{clients}->{$target_client}->{class}->action( + action => $action, + data => $data + ); } $connector->{logger}->writeLogDebug("[proxy] -class- Send message: [action = $action] [token = $token] [target = $target] [data = $data]"); diff --git a/gorgone/modules/core/proxy/sshclient.pm b/gorgone/modules/core/proxy/sshclient.pm new file mode 100644 index 00000000000..453819af59d --- /dev/null +++ b/gorgone/modules/core/proxy/sshclient.pm @@ -0,0 +1,87 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package modules::core::proxy::sshclient; + +use base qw(Libssh::Session); + +use strict; +use warnings; +use Libssh::Sftp qw(:all); + +sub new { + my ($class, %options) = @_; + my $self = $class->SUPER::new(%options); + bless $self, $class; + + $self->{logger} = $options{logger}; + $self->{sftp} = undef; + return $self; +} + +sub open_session { + my ($self, %options) = @_; + + if ($self->options(host => $options{ssh_host}, port => $options{ssh_port}, user => $options{ssh_username}) != Libssh::Session::SSH_OK) { + $self->{logger}->writeLogError('[proxy] -sshclient- options method: ' . $self->error()); + return -1; + } + + if ($self->connect() != Libssh::Session::SSH_OK) { + $self->{logger}->writeLogError('[proxy] -sshclient- connect method: ' . $self->error()); + return -1; + } + + if ($self->auth_publickey_auto() != Libssh::Session::SSH_AUTH_SUCCESS) { + $self->{logger}->writeLogInfo('[proxy] -sshclient- auth publickey auto failure: ' . $self->error(GetErrorSession => 1)); + if (!defined($options{ssh_password}) || $options{ssh_password} eq '') { + $self->{logger}->writeLogError('[proxy] -sshclient- auth issue: no password'); + return -1; + } + if ($self->auth_password(password => $options{ssh_password}) != Libssh::Session::SSH_AUTH_SUCCESS) { + $self->{logger}->writeLogError('[proxy] -sshclient- auth issue: ' . $self->error(GetErrorSession => 1)); + return -1; + } + } + + $self->{logger}->writeLogInfo('[proxy] -sshclient- authentification succeed'); + + $self->{sftp} = Libssh::Sftp->new(session => $self); + if (!defined($self->{sftp})) { + $self->{logger}->writeLogError('[proxy] -sshclient- cannot init sftp: ' . Libssh::Sftp::error()); + return -1; + } + + return 0; +} + +sub action { + my ($self, %options) = @_; + + print "===lall $options{action}====\n"; +} + +sub close { + my ($self, %options) = @_; + + # to be compatible with zmq close class +} + +1; diff --git a/gorgone/test-client.pl b/gorgone/test-client.pl index 280d775d366..9e1c9400fa7 100644 --- a/gorgone/test-client.pl +++ b/gorgone/test-client.pl @@ -39,7 +39,7 @@ sub get_command_result { my ($current_retries, $retries) = (0, 4); $stopped->{$client2->{identity}} = '^(1|2)$'; $client2->send_message( - action => 'COMMAND', data => { content => { command => 'ls /' } }, target => 150, + action => 'COMMAND', data => { content => { command => 'ls /' } }, target => 100, json_encode => 1 ); while (1) { From b16b6c4a4c11a9a6187fed8d3df017cedeedee0f Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 10 Sep 2019 11:11:14 +0200 Subject: [PATCH 072/948] feat(proxy): add sshclient action COMMAND --- gorgone/modules/core/proxy/class.pm | 13 ++++++-- gorgone/modules/core/proxy/sshclient.pm | 41 +++++++++++++++++++++++-- gorgone/test-client.pl | 3 +- 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/gorgone/modules/core/proxy/class.pm b/gorgone/modules/core/proxy/class.pm index 9bd9c3db654..7be92374595 100644 --- a/gorgone/modules/core/proxy/class.pm +++ b/gorgone/modules/core/proxy/class.pm @@ -164,6 +164,7 @@ sub connect { ssh_port => $self->{clients}->{$options{id}}->{ssh_port}, ssh_username => $self->{clients}->{$options{id}}->{ssh_username}, ssh_password => $self->{clients}->{$options{id}}->{ssh_password}, + strict_serverkey_check => $self->{clients}->{$options{id}}->{strict_serverkey_check}, ); if ($code != 0) { $self->{clients}->{$options{id}}->{delete} = 1; @@ -260,10 +261,18 @@ sub proxy { $connector->{clients}->{$target}->{delete} = 1; } } elsif ($connector->{clients}->{$target_client}->{type} eq 'push_ssh') { - my ($status) = $connector->{clients}->{$target_client}->{class}->action( + my ($code, $decoded_data) = $connector->json_decode(argument => $data); + return if ($code == 1); + + my ($status, $data_ret) = $connector->{clients}->{$target_client}->{class}->action( action => $action, - data => $data + data => $decoded_data ); + if ($status == 0) { + $connector->send_log(code => centreon::gorgone::module::ACTION_FINISH_OK, token => $token, data => $data_ret); + } else { + $connector->send_log(code => centreon::gorgone::module::ACTION_FINISH_KO, token => $token, data => $data_ret); + } } $connector->{logger}->writeLogDebug("[proxy] -class- Send message: [action = $action] [token = $token] [target = $target] [data = $data]"); diff --git a/gorgone/modules/core/proxy/sshclient.pm b/gorgone/modules/core/proxy/sshclient.pm index 453819af59d..3eb51353bef 100644 --- a/gorgone/modules/core/proxy/sshclient.pm +++ b/gorgone/modules/core/proxy/sshclient.pm @@ -44,7 +44,7 @@ sub open_session { return -1; } - if ($self->connect() != Libssh::Session::SSH_OK) { + if ($self->connect(SkipKeyProblem => $options{strict_serverkey_check}) != Libssh::Session::SSH_OK) { $self->{logger}->writeLogError('[proxy] -sshclient- connect method: ' . $self->error()); return -1; } @@ -72,10 +72,47 @@ sub open_session { return 0; } +sub action_command { + my ($self, %options) = @_; + + if (!defined($options{data}->{content}->{command}) || $options{data}->{content}->{command} eq '') { + $self->{logger}->writeLogError('[proxy] -sshclient- action_command: need command'); + return (-1, { message => 'please set command' }); + } + + my $timeout = defined($options{data}->{content}->{timeout}) && $options{data}->{content}->{timeout} =~ /(\d+)/ ? $1 : 60; + my $timeout_nodata = defined($options{data}->{content}->{timeout_nodata}) && $options{data}->{content}->{timeout_nodata} =~ /(\d+)/ ? $1 : 30; + + my $ret = $self->execute_simple(cmd => $options{data}->{content}->{command}, timeout => $timeout, timeout_nodata => $timeout_nodata); + my ($code, $data) = (0, {}); + if ($ret->{exit} == Libssh::Session::SSH_OK) { + $data->{message} = "command '$options{data}->{content}->{command}' had finished successfuly"; + $data->{exit_code} = $ret->{exit_code}; + $data->{stdout} = $ret->{stdout}; + $data->{stderr} = $ret->{stderr}; + } elsif ($ret->{exit} == Libssh::Session::SSH_AGAIN) { # AGAIN means timeout + $code = -1; + $data->{message} = "command '$options{data}->{content}->{command}' had timeout"; + $data->{exit_code} = $ret->{exit_code}; + $data->{stdout} = $ret->{stdout}; + $data->{stderr} = $ret->{stderr}; + } else { + return (-1, { message => $self->error(GetErrorSession => 1) }); + } + + return ($code, $data); +} + sub action { my ($self, %options) = @_; - print "===lall $options{action}====\n"; + my $func = $self->can('action_' . lc($options{action})); + if (defined($func)) { + return $func->($self, data => $options{data}); + } + + $self->{logger}->writeLogError('[proxy] -sshclient- unsupported action ' . $options{action}); + return (-1, { message => 'unsupported action' }); } sub close { diff --git a/gorgone/test-client.pl b/gorgone/test-client.pl index 9e1c9400fa7..7858ba61d60 100644 --- a/gorgone/test-client.pl +++ b/gorgone/test-client.pl @@ -60,8 +60,7 @@ sub get_command_result { if (defined($identities_token->{$client2->{identity}})) { # We ask a sync print "==== send logs ===\n"; - $client2->send_message(action => 'GETLOG', target => 150, token => $identities_token->{$client2->{identity}}, - json_encode => 1); + $client2->send_message(action => 'GETLOG', target => 150, json_encode => 1); $client2->send_message(action => 'GETLOG', token => $identities_token->{$client2->{identity}}, data => { token => $identities_token->{$client2->{identity}} }, json_encode => 1); } From 9571b99f7b534c0ea734a9b1b70c739626cd7d27 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 10 Sep 2019 11:57:13 +0200 Subject: [PATCH 073/948] feat(proxy): add enginecommand in sshclient --- gorgone/modules/centreon/legacycmd/class.pm | 2 +- gorgone/modules/core/proxy/sshclient.pm | 35 +++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/modules/centreon/legacycmd/class.pm index 92ff2b2e18a..f4002db1a7d 100644 --- a/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/modules/centreon/legacycmd/class.pm @@ -117,7 +117,7 @@ sub get_pipe_engines { $self->{engine_pipe} = {}; my ($status, $datas) = $self->{class_object_centreon}->custom_execute( - request => 'SELECT nagios_server_id, command_file FROM nagios_server', + request => 'SELECT nagios_server_id, command_file FROM cfg_nagios', mode => 2 ); if ($status == -1 || !defined($datas->[0])) { diff --git a/gorgone/modules/core/proxy/sshclient.pm b/gorgone/modules/core/proxy/sshclient.pm index 3eb51353bef..0c294d28a99 100644 --- a/gorgone/modules/core/proxy/sshclient.pm +++ b/gorgone/modules/core/proxy/sshclient.pm @@ -25,6 +25,7 @@ use base qw(Libssh::Session); use strict; use warnings; use Libssh::Sftp qw(:all); +use POSIX; sub new { my ($class, %options) = @_; @@ -103,6 +104,40 @@ sub action_command { return ($code, $data); } +sub action_enginecommand { + my ($self, %options) = @_; + + if (!defined($options{data}->{content}->{command}) || $options{data}->{content}->{command} eq '') { + $self->{logger}->writeLogError('[proxy] -sshclient- action_enginecommand: need command'); + return (-1, { message => 'please set command' }); + } + if (!defined($options{data}->{content}->{engine_pipe}) || $options{data}->{content}->{engine_pipe} eq '') { + $self->{logger}->writeLogError('[proxy] -sshclient- action_enginecommand: need engine_pipe'); + return (-1, { message => 'please set engine_pipe' }); + } + + chomp $options{data}->{content}->{command}; + my $ret = $self->{sftp}->stat_file(file => $options{data}->{content}->{engine_pipe}); + if (!defined($ret)) { + return (-1, { message => "cannot stat file '$options{data}->{content}->{engine_pipe}': " . $self->{sftp}->get_msg_error() }); + } + + if ($ret->{type} != SSH_FILEXFER_TYPE_SPECIAL) { + return (-1, { message => "stat file '$options{data}->{content}->{engine_pipe}' is not a pipe file" }); + } + + my $file = $self->{sftp}->open(file => $options{data}->{content}->{engine_pipe}, accesstype => O_WRONLY|O_APPEND); + if (!defined($file)) { + return (-1, { message => "cannot open stat file '$options{data}->{content}->{engine_pipe}': " . $self->{sftp}->error() }); + } + if ($self->{sftp}->write(handle_file => $file, data => $options{data}->{content}->{command} . "\n") != Libssh::Session::SSH_OK) { + return (-1, { message => "cannot write stat file '$options{data}->{content}->{engine_pipe}': " . $self->{sftp}->error() }); + } + + $self->{logger}->writeLogDebug("[proxy] -sshclient- action_enginecommand '" . $options{data}->{content}->{command} . "' succeeded"); + return (0, { message => 'send enginecommand succeeded' }); +} + sub action { my ($self, %options) = @_; From 0c4ba10cdd2f6c151a622c17b9b98deb11b1dd46 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 10 Sep 2019 14:27:13 +0200 Subject: [PATCH 074/948] feat(proxy): wip on sendcfgfile --- gorgone/modules/core/proxy/sshclient.pm | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/gorgone/modules/core/proxy/sshclient.pm b/gorgone/modules/core/proxy/sshclient.pm index 0c294d28a99..54cb8263d85 100644 --- a/gorgone/modules/core/proxy/sshclient.pm +++ b/gorgone/modules/core/proxy/sshclient.pm @@ -32,6 +32,7 @@ sub new { my $self = $class->SUPER::new(%options); bless $self, $class; + $self->{save_options} = {}; $self->{logger} = $options{logger}; $self->{sftp} = undef; return $self; @@ -40,6 +41,7 @@ sub new { sub open_session { my ($self, %options) = @_; + $self->{save_options} = { %options }; if ($self->options(host => $options{ssh_host}, port => $options{ssh_port}, user => $options{ssh_username}) != Libssh::Session::SSH_OK) { $self->{logger}->writeLogError('[proxy] -sshclient- options method: ' . $self->error()); return -1; @@ -138,9 +140,21 @@ sub action_enginecommand { return (0, { message => 'send enginecommand succeeded' }); } +sub action_sendcfgfile { + my ($self, %options) = @_; + + # cacheDir = /var/cache/centreon + # src = $self->{cacheDir} . "/config/engine/" . $id | dst = 'cfg_dir' of cfg_nagios table + # src2 = $self->{cacheDir} . "/config/broker/" . $id | dst = 'centreonbroker_cfg_path' of 'nagios_server' table + + # tar file: tar czf centreon-engine-config-ID.tar.gz -C "$self->{cacheDir}/config/engine/$id" . + # untar file: tar zxf centreon-engine-config-ID.tar.gz -C "cfg_dir" +} + sub action { my ($self, %options) = @_; + $self->test_connection(); my $func = $self->can('action_' . lc($options{action})); if (defined($func)) { return $func->($self, data => $options{data}); @@ -150,6 +164,15 @@ sub action { return (-1, { message => 'unsupported action' }); } +sub test_connection { + my ($self, %options) = @_; + + if ($self->is_connected() == 0) { + $self->disconnect(); + $self->open_session(%{$self->{save_options}}); + } +} + sub close { my ($self, %options) = @_; From 25ebe5fd69b03df13f45c973e859dbf1a69a7ea5 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 10 Sep 2019 14:50:10 +0200 Subject: [PATCH 075/948] doc(legacycmd): add todo --- gorgone/modules/centreon/legacycmd/class.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/modules/centreon/legacycmd/class.pm index f4002db1a7d..1ac160b84af 100644 --- a/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/modules/centreon/legacycmd/class.pm @@ -136,6 +136,7 @@ sub execute_cmd { my ($self, %options) = @_; if ($options{cmd} eq 'EXTERNALCMD') { + # TODO: need to remove illegal characters!! $self->send_internal_action( action => 'ENGINECOMMAND', target => $options{target}, From 565a5a58ee7e3b5dc74b0510eb1597f9480789dc Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 16 Sep 2019 13:31:33 +0200 Subject: [PATCH 076/948] enh(centreon/engine): change command_file option, use engine_pipe when defined --- gorgone/config/gorgoned.yml | 2 +- gorgone/modules/centreon/engine/class.pm | 49 +++++++++++++++++------- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/gorgone/config/gorgoned.yml b/gorgone/config/gorgoned.yml index 566b8dfabbb..4a5eac33692 100644 --- a/gorgone/config/gorgoned.yml +++ b/gorgone/config/gorgoned.yml @@ -92,7 +92,7 @@ modules: - name: engine package: modules::centreon::engine::hooks enable: true - command_file: "/var/lib/centreon-engine/rw/centengine.cmd" + cmd_file: "/var/lib/centreon-engine/rw/centengine.cmd" - name: legacycmd package: modules::centreon::legacycmd::hooks diff --git a/gorgone/modules/centreon/engine/class.pm b/gorgone/modules/centreon/engine/class.pm index 37aecdee9f9..1fd1e3991dc 100644 --- a/gorgone/modules/centreon/engine/class.pm +++ b/gorgone/modules/centreon/engine/class.pm @@ -84,39 +84,62 @@ sub class_handle_HUP { sub action_enginecommand { my ($self, %options) = @_; - if (!defined($self->{config}->{command_file}) || $self->{config}->{command_file} eq '') { + my $command = $options{data}->{content}->{command}; + if (!defined($command) || $command eq '') { + $self->{logger}->writeLogError('[engine] -class- action_enginecommand: need command argument'); $self->send_log( socket => $options{socket_log}, code => $self->ACTION_FINISH_KO, token => $options{token}, - data => { message => "need command_file argument" } + data => { message => "need command argument" } + ); + return -1; + } + + my $cmd_file = '' + if (defined($options{data}->{content}->{engine_pipe}) && $options{data}->{content}->{engine_pipe} ne '') { + $cmd_file = $options{data}->{content}->{engine_pipe}; + } elsif (defined($self->{config}->{cmd_file}) && $self->{config}->{cmd_file} ne '') { + $cmd_file = $self->{config}->{cmd_file}; + } + + if (!defined($cmd_file) || $cmd_file eq '') { + $self->{logger}->writeLogError("[engine] -class- need cmd_file (config) or engine_pipe (call) argument"); + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => "need cmd_file (config) or engine_pipe (call) argument" } ); return -1; } - if (! -e $self->{config}->{command_file}) { + if (! -e $cmd_file) { + $self->{logger}->writeLogError("[engine] -class- command '$command' - engine_pipe '$cmd_file' must exist"); $self->send_log( socket => $options{socket_log}, code => $self->ACTION_FINISH_KO, token => $options{token}, - data => { message => "command '$options{data}->{content}->{command}' - engine_pipe '$self->{config}->{command_file}' must exist" } + data => { message => "command '$command' - engine_pipe '$cmd_file' must exist" } ); return -1; } - if (! -p $self->{config}->{command_file}) { + if (! -p $cmd_file) { + $self->{logger}->writeLogError("[engine] -class- command '$command' - engine_pipe '$cmd_file' must be a pipe file"); $self->send_log( socket => $options{socket_log}, code => $self->ACTION_FINISH_KO, token => $options{token}, - data => { message => "command '$options{data}->{content}->{command}' - engine_pipe '$self->{config}->{command_file}' must be a pipe file" } + data => { message => "command '$command' - engine_pipe '$cmd_file' must be a pipe file" } ); return -1; } - if (! -w $self->{config}->{command_file}) { + if (! -w $cmd_file) { + $self->{logger}->writeLogError("[engine] -class- command '$command' - engine_pipe '$cmd_file' must be writeable"); $self->send_log( socket => $options{socket_log}, code => $self->ACTION_FINISH_KO, token => $options{token}, - data => { message => "command '$options{data}->{content}->{command}' - engine_pipe '$self->{config}->{command_file}' must be writeable" } + data => { message => "command '$command' - engine_pipe '$cmd_file' must be writeable" } ); return -1; } @@ -125,19 +148,19 @@ sub action_enginecommand { eval { local $SIG{ALRM} = sub { die 'Timeout command' }; alarm $self->{timeout}; - open($fh, ">", $self->{config}->{command_file}) or die "cannot open '$self->{config}->{command_file}': $!"; - print $fh $options{data}->{content}->{command} . "\n"; + open($fh, ">", $cmd_file) or die "cannot open '$cmd_file': $!"; + print $fh $command . "\n"; close $fh; alarm 0; }; if ($@) { close $fh if (defined($fh)); - $self->{logger}->writeLogError("[action] -class- Submit engine command '$options{data}->{content}->{command}' issue: $@"); + $self->{logger}->writeLogError("[engine] -class- submit engine command '$command' issue: $@"); $self->send_log( socket => $options{socket_log}, code => $self->ACTION_FINISH_KO, token => $options{token}, - data => { message => "submit command issue '$options{data}->{content}->{command}': $@" } + data => { message => "submit engine command '$command' issue: $@" } ); return undef; } @@ -146,7 +169,7 @@ sub action_enginecommand { socket => $options{socket_log}, code => $self->ACTION_FINISH_OK, token => $options{token}, - data => { message => "command '$options{data}->{content}->{command}' had been submitted': $@" } + data => { message => "command '$command' had been submitted': $@" } ); return 0; From 8e5b91458885c8d42549f37e8985fbc1ca74da4a Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 16 Sep 2019 14:50:28 +0200 Subject: [PATCH 077/948] enh(centreon/legacycmd): remove default engine_pipe value --- gorgone/modules/centreon/legacycmd/class.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/modules/centreon/legacycmd/class.pm index 1ac160b84af..71cd7b285a1 100644 --- a/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/modules/centreon/legacycmd/class.pm @@ -144,7 +144,7 @@ sub execute_cmd { data => { content => { command => $options{param}, - engine_pipe => defined($self->{engine_pipe}->{$options{target}}) ? $self->{engine_pipe}->{$options{target}} : '/var/lib/centreon-engine/rw/centengine.cmd' + engine_pipe => $self->{engine_pipe}->{$options{target}}, } }, ); From d9adc83d8402ea9498104bde36f39cbfed701b18 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Mon, 16 Sep 2019 15:29:17 +0200 Subject: [PATCH 078/948] feat(core): add debug + subnodes register --- gorgone/centreon/script/gorgonecore.pm | 3 ++- gorgone/modules/centreon/pollers/class.pm | 19 ++++++++++++++++--- gorgone/modules/core/proxy/class.pm | 6 ++++-- gorgone/modules/core/proxy/sshclient.pm | 3 +++ 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/gorgone/centreon/script/gorgonecore.pm b/gorgone/centreon/script/gorgonecore.pm index 8b15b91d648..91980b2ad48 100644 --- a/gorgone/centreon/script/gorgonecore.pm +++ b/gorgone/centreon/script/gorgonecore.pm @@ -207,7 +207,7 @@ sub load_modules { } $self->{logger}->writeLogInfo("[core] Module '" . $module->{name} . "' is loaded"); } - + # Load internal functions foreach my $method_name (('putlog', 'getlog', 'kill', 'ping', 'constatus', 'setcoreid', 'synclogs')) { unless ($self->{internal_register}->{$method_name} = centreon::gorgone::common->can($method_name)) { @@ -220,6 +220,7 @@ sub load_modules { sub message_run { my ($self, %options) = @_; + $self->{logger}->writeLogDebug('[core] message received - ' . $options{message}); if ($options{message} !~ /^\[(.+?)\]\s+\[(.*?)\]\s+\[(.*?)\]\s+(.*)$/) { return (undef, 1, { message => 'request not well formatted' }); } diff --git a/gorgone/modules/centreon/pollers/class.pm b/gorgone/modules/centreon/pollers/class.pm index d67c7d09668..d637ec34940 100644 --- a/gorgone/modules/centreon/pollers/class.pm +++ b/gorgone/modules/centreon/pollers/class.pm @@ -96,7 +96,7 @@ sub action_pollersresync { $self->send_log(code => centreon::gorgone::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action pollersresync proceed' }); my $request = " - SELECT id, name, localhost, ns_ip_address, ssh_port + SELECT id, name, localhost, ns_ip_address, ssh_port, remote_id FROM nagios_server WHERE ns_activate = '1' "; @@ -110,18 +110,24 @@ sub action_pollersresync { my $core_id; my $register_temp = {}; my $register_nodes = []; + my $register_subnodes = {}; foreach (@$datas) { if ($_->[2] == 1) { $core_id = $_->[0]; next; } + if (defined($_->[5]) && $_->[5] =~ /\d+/) { + $register_subnodes->{$_->[5]} = [] if (!defined($register_subnodes->{$_->[5]})); + push @{$register_subnodes->{$_->[5]}}, $_->[0]; + next; + } $self->{register_pollers}->{$_->[0]} = 1; $register_temp->{$_->[0]} = 1; push @{$register_nodes}, { id => $_->[0], type => 'push_ssh', address => $_->[3], ssh_port => $_->[4] }; } - my $unregister_nodes = []; - + + my $unregister_nodes = []; foreach (keys %{$self->{register_pollers}}) { if (!defined($register_temp->{$_})) { push @{$unregister_nodes}, { id => $_ }; @@ -129,6 +135,13 @@ sub action_pollersresync { } } + # We add subnodes + foreach (@$register_nodes) { + if (defined($register_subnodes->{ $_->{id} })) { + $_->{nodes} = $register_subnodes->{ $_->{id} }; + } + } + $self->send_internal_action(action => 'REGISTERNODES', data => { nodes => $register_nodes } ); $self->send_internal_action(action => 'UNREGISTERNODES', data => { nodes => $unregister_nodes } ); $self->send_internal_action(action => 'SETCOREID', data => { id => $core_id } ) if (defined($core_id)); diff --git a/gorgone/modules/core/proxy/class.pm b/gorgone/modules/core/proxy/class.pm index 7be92374595..5021943692d 100644 --- a/gorgone/modules/core/proxy/class.pm +++ b/gorgone/modules/core/proxy/class.pm @@ -237,9 +237,10 @@ sub proxy { return ; } - my $target_client = $target; + my ($target_client, $target_direct) = ($target, 1); if (!defined($connector->{clients}->{$target})) { $target_client = $connector->{subnodes}->{$target}; + $target_direct = 0; } if (!defined($connector->{clients}->{$target_client}->{class})) { if ($connector->connect(id => $target_client) != 0) { @@ -266,7 +267,8 @@ sub proxy { my ($status, $data_ret) = $connector->{clients}->{$target_client}->{class}->action( action => $action, - data => $decoded_data + data => $decoded_data, + target_direct => $target_direct ); if ($status == 0) { $connector->send_log(code => centreon::gorgone::module::ACTION_FINISH_OK, token => $token, data => $data_ret); diff --git a/gorgone/modules/core/proxy/sshclient.pm b/gorgone/modules/core/proxy/sshclient.pm index 54cb8263d85..e76f23cbc38 100644 --- a/gorgone/modules/core/proxy/sshclient.pm +++ b/gorgone/modules/core/proxy/sshclient.pm @@ -146,6 +146,9 @@ sub action_sendcfgfile { # cacheDir = /var/cache/centreon # src = $self->{cacheDir} . "/config/engine/" . $id | dst = 'cfg_dir' of cfg_nagios table # src2 = $self->{cacheDir} . "/config/broker/" . $id | dst = 'centreonbroker_cfg_path' of 'nagios_server' table + + # /var/cache/centreon/config/engine/1/ + # /var/cache/centreon/config/broker/1/ # tar file: tar czf centreon-engine-config-ID.tar.gz -C "$self->{cacheDir}/config/engine/$id" . # untar file: tar zxf centreon-engine-config-ID.tar.gz -C "cfg_dir" From ff069e230e6050df94431b542707573d90320180 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 16 Sep 2019 16:00:27 +0200 Subject: [PATCH 079/948] feat(centreon/legacycmd): wip on files copy --- gorgone/modules/centreon/legacycmd/class.pm | 61 ++++++++++++++++++--- 1 file changed, 52 insertions(+), 9 deletions(-) diff --git a/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/modules/centreon/legacycmd/class.pm index 71cd7b285a1..cbdf42e63c9 100644 --- a/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/modules/centreon/legacycmd/class.pm @@ -112,21 +112,23 @@ sub move_cmd_file { return (0, $handle); } -sub get_pipe_engines { +sub get_pollers_config { my ($self, %options) = @_; - $self->{engine_pipe} = {}; + $self->{pollers} = {}; my ($status, $datas) = $self->{class_object_centreon}->custom_execute( - request => 'SELECT nagios_server_id, command_file FROM cfg_nagios', + request => 'SELECT nagios_server_id, command_file, cfg_dir, centreonbroker_cfg_path FROM cfg_nagios JOIN nagios_server WHERE id = nagios_server_id', mode => 2 ); if ($status == -1 || !defined($datas->[0])) { - $self->{logger}->writeLogError('[legacycmd] -class- cannot get engine pipe for pollers'); + $self->{logger}->writeLogError('[legacycmd] -class- cannot get engine pipe for pollers (command_file)'); return -1; } foreach (@$datas) { - $self->{engine_pipe}->{$_->[0]} = $_->[1]; + $self->{pollers}->{$_->[0]}->{command_file} = $_->[1]; + $self->{pollers}->{$_->[0]}->{cfg_dir} = $_->[2]; + $self->{pollers}->{$_->[0]}->{centreonbroker_cfg_path} = $_->[3]; } return 0; @@ -136,7 +138,7 @@ sub execute_cmd { my ($self, %options) = @_; if ($options{cmd} eq 'EXTERNALCMD') { - # TODO: need to remove illegal characters!! + # TODO: need to remove illegal characters!! $self->send_internal_action( action => 'ENGINECOMMAND', target => $options{target}, @@ -144,7 +146,48 @@ sub execute_cmd { data => { content => { command => $options{param}, - engine_pipe => $self->{engine_pipe}->{$options{target}}, + command_file => $self->{pollers}->{$options{target}}->{command_file}, + } + }, + ); + } elsif ($options{cmd} eq 'SENDCFGFILE') { + # engine + $self->send_internal_action( + action => 'REMOTECOPY', + target => $options{target}, + token => $self->generate_token(), + data => { + content => { + source => $connector->{config}->{cache_dir} . '/config/engine/' . $options{target}, + destination => $self->{pollers}->{$options{target}}->{cfg_dir} . '/', + type => 'engine', + } + }, + ); + # broker + $self->send_internal_action( + action => 'REMOTECOPY', + target => $options{target}, + token => $self->generate_token(), + data => { + content => { + source => $connector->{config}->{cache_dir} . '/config/broker/' . $options{target}, + destination => $self->{pollers}->{$options{target}}->{centreonbroker_cfg_path} . '/', + type => 'broker', + } + }, + ); + } elsif ($options{cmd} eq 'SENDEXPORTFILE') { + # remote server + $self->send_internal_action( + action => 'REMOTECOPY', + target => $options{target}, + token => $self->generate_token(), + data => { + content => { + source => $connector->{config}->{cache_dir} . '/config/export/' . $options{target}, + destination => '/var/lib/centreon/remote-data/', + type => 'remote', } }, ); @@ -155,7 +198,7 @@ sub handle_cmd_file { my ($self, %options) = @_; require bytes; - return if ($self->get_pipe_engines() == -1); + return if ($self->get_pollers_config() == -1); my ($code, $handle) = $self->move_cmd_file(); return if ($code == -1); @@ -165,7 +208,7 @@ sub handle_cmd_file { return ; } - if ($line =~ /^(.*?):(.*?):(.*)/) { + if ($line =~ /^(.*?):([^:]*)(?::(.*)){0,1}/) { $self->execute_cmd(cmd => $1, target => $2, param => $3); my $current_pos = tell($handle); seek($handle, $current_pos - bytes::length($line), 0); From 8c272b20724134b3afbaef26e50d31a42f2b7863 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 16 Sep 2019 16:00:58 +0200 Subject: [PATCH 080/948] enh(centreon/engine): use command_file argument --- gorgone/config/gorgoned.yml | 4 ++- gorgone/modules/centreon/engine/class.pm | 38 ++++++++++++------------ 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/gorgone/config/gorgoned.yml b/gorgone/config/gorgoned.yml index 4a5eac33692..c91ef1cadf5 100644 --- a/gorgone/config/gorgoned.yml +++ b/gorgone/config/gorgoned.yml @@ -92,12 +92,14 @@ modules: - name: engine package: modules::centreon::engine::hooks enable: true - cmd_file: "/var/lib/centreon-engine/rw/centengine.cmd" + command_file: "/var/lib/centreon-engine/rw/centengine.cmd" - name: legacycmd package: modules::centreon::legacycmd::hooks enable: true cmd_file: "/var/lib/centreon/centcore.cmd" + cache_dir: "/var/cache/centreon/" + remote_dir: "/var/lib/centreon/remote-data/" - name: scom package: modules::plugins::scom::hooks diff --git a/gorgone/modules/centreon/engine/class.pm b/gorgone/modules/centreon/engine/class.pm index 1fd1e3991dc..cc30abd7a2a 100644 --- a/gorgone/modules/centreon/engine/class.pm +++ b/gorgone/modules/centreon/engine/class.pm @@ -96,50 +96,50 @@ sub action_enginecommand { return -1; } - my $cmd_file = '' - if (defined($options{data}->{content}->{engine_pipe}) && $options{data}->{content}->{engine_pipe} ne '') { - $cmd_file = $options{data}->{content}->{engine_pipe}; - } elsif (defined($self->{config}->{cmd_file}) && $self->{config}->{cmd_file} ne '') { - $cmd_file = $self->{config}->{cmd_file}; + my $command_file = '' + if (defined($options{data}->{content}->{command_file}) && $options{data}->{content}->{command_file} ne '') { + $command_file = $options{data}->{content}->{command_file}; + } elsif (defined($self->{config}->{command_file}) && $self->{config}->{command_file} ne '') { + $command_file = $self->{config}->{command_file}; } - - if (!defined($cmd_file) || $cmd_file eq '') { - $self->{logger}->writeLogError("[engine] -class- need cmd_file (config) or engine_pipe (call) argument"); + + if (!defined($command_file) || $command_file eq '') { + $self->{logger}->writeLogError("[engine] -class- need command_file (config or call) argument"); $self->send_log( socket => $options{socket_log}, code => $self->ACTION_FINISH_KO, token => $options{token}, - data => { message => "need cmd_file (config) or engine_pipe (call) argument" } + data => { message => "need command_file (config or call) argument" } ); return -1; } - if (! -e $cmd_file) { - $self->{logger}->writeLogError("[engine] -class- command '$command' - engine_pipe '$cmd_file' must exist"); + if (! -e $command_file) { + $self->{logger}->writeLogError("[engine] -class- command '$command' - command_file '$command_file' must exist"); $self->send_log( socket => $options{socket_log}, code => $self->ACTION_FINISH_KO, token => $options{token}, - data => { message => "command '$command' - engine_pipe '$cmd_file' must exist" } + data => { message => "command '$command' - command_file '$command_file' must exist" } ); return -1; } - if (! -p $cmd_file) { - $self->{logger}->writeLogError("[engine] -class- command '$command' - engine_pipe '$cmd_file' must be a pipe file"); + if (! -p $command_file) { + $self->{logger}->writeLogError("[engine] -class- command '$command' - command_file '$command_file' must be a pipe file"); $self->send_log( socket => $options{socket_log}, code => $self->ACTION_FINISH_KO, token => $options{token}, - data => { message => "command '$command' - engine_pipe '$cmd_file' must be a pipe file" } + data => { message => "command '$command' - command_file '$command_file' must be a pipe file" } ); return -1; } - if (! -w $cmd_file) { - $self->{logger}->writeLogError("[engine] -class- command '$command' - engine_pipe '$cmd_file' must be writeable"); + if (! -w $command_file) { + $self->{logger}->writeLogError("[engine] -class- command '$command' - command_file '$command_file' must be writeable"); $self->send_log( socket => $options{socket_log}, code => $self->ACTION_FINISH_KO, token => $options{token}, - data => { message => "command '$command' - engine_pipe '$cmd_file' must be writeable" } + data => { message => "command '$command' - command_file '$command_file' must be writeable" } ); return -1; } @@ -148,7 +148,7 @@ sub action_enginecommand { eval { local $SIG{ALRM} = sub { die 'Timeout command' }; alarm $self->{timeout}; - open($fh, ">", $cmd_file) or die "cannot open '$cmd_file': $!"; + open($fh, ">", $command_file) or die "cannot open '$command_file': $!"; print $fh $command . "\n"; close $fh; alarm 0; From a3dec2ed8244ce8c6c058528fb35c5978d040780 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 16 Sep 2019 16:05:55 +0200 Subject: [PATCH 081/948] feat(centreon/legacycmd): wip on files copy --- gorgone/modules/centreon/legacycmd/class.pm | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/modules/centreon/legacycmd/class.pm index cbdf42e63c9..a7e2ab4b472 100644 --- a/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/modules/centreon/legacycmd/class.pm @@ -151,6 +151,7 @@ sub execute_cmd { }, ); } elsif ($options{cmd} eq 'SENDCFGFILE') { + my $cache_dir = (defined($connector->{config}->{cache_dir})) ? $connector->{config}->{cache_dir} : '/var/cache/centreon'; # engine $self->send_internal_action( action => 'REMOTECOPY', @@ -158,7 +159,7 @@ sub execute_cmd { token => $self->generate_token(), data => { content => { - source => $connector->{config}->{cache_dir} . '/config/engine/' . $options{target}, + source => $cache_dir . '/config/engine/' . $options{target}, destination => $self->{pollers}->{$options{target}}->{cfg_dir} . '/', type => 'engine', } @@ -171,13 +172,15 @@ sub execute_cmd { token => $self->generate_token(), data => { content => { - source => $connector->{config}->{cache_dir} . '/config/broker/' . $options{target}, + source => $cache_dir . '/config/broker/' . $options{target}, destination => $self->{pollers}->{$options{target}}->{centreonbroker_cfg_path} . '/', type => 'broker', } }, ); } elsif ($options{cmd} eq 'SENDEXPORTFILE') { + my $cache_dir = (defined($connector->{config}->{cache_dir})) ? $connector->{config}->{cache_dir} : '/var/cache/centreon'; + my $remote_dir = (defined($connector->{config}->{remote_dir})) ? $connector->{config}->{remote_dir} : '/var/lib/centreon/remote-data/' # remote server $self->send_internal_action( action => 'REMOTECOPY', @@ -185,8 +188,8 @@ sub execute_cmd { token => $self->generate_token(), data => { content => { - source => $connector->{config}->{cache_dir} . '/config/export/' . $options{target}, - destination => '/var/lib/centreon/remote-data/', + source => $cache_dir . '/config/export/' . $options{target}, + destination => $remote_dir, type => 'remote', } }, From b47649d8f6513c935f37301a28999d0fa731d663 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Mon, 16 Sep 2019 16:17:12 +0200 Subject: [PATCH 082/948] feat(proxy): manage proxydelnode --- gorgone/modules/core/proxy/class.pm | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/gorgone/modules/core/proxy/class.pm b/gorgone/modules/core/proxy/class.pm index 5021943692d..4cb08237be1 100644 --- a/gorgone/modules/core/proxy/class.pm +++ b/gorgone/modules/core/proxy/class.pm @@ -202,7 +202,14 @@ sub action_proxydelnode { my ($code, $data) = $self->json_decode(argument => $options{data}); return if ($code == 1); - # TODO + if (defined($self->{clients}->{$data->{id}})) { + $self->{clients}->{$data->{id}}->{delete} = 1; + } + + foreach (keys %{$self->{subnodes}}) { + delete $self->{subnodes}->{$_} + if ($self->{subnodes}->{$_} eq $data->{id}); + } } sub action_proxyaddsubnode { From 1436576fef2623b11412b19fc79d595d2c5c4c0c Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 16 Sep 2019 16:19:52 +0200 Subject: [PATCH 083/948] fix(class): missing semicolons --- gorgone/modules/centreon/engine/class.pm | 2 +- gorgone/modules/centreon/legacycmd/class.pm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/modules/centreon/engine/class.pm b/gorgone/modules/centreon/engine/class.pm index cc30abd7a2a..be85f2d853c 100644 --- a/gorgone/modules/centreon/engine/class.pm +++ b/gorgone/modules/centreon/engine/class.pm @@ -96,7 +96,7 @@ sub action_enginecommand { return -1; } - my $command_file = '' + my $command_file = ''; if (defined($options{data}->{content}->{command_file}) && $options{data}->{content}->{command_file} ne '') { $command_file = $options{data}->{content}->{command_file}; } elsif (defined($self->{config}->{command_file}) && $self->{config}->{command_file} ne '') { diff --git a/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/modules/centreon/legacycmd/class.pm index a7e2ab4b472..b4b4f4b6459 100644 --- a/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/modules/centreon/legacycmd/class.pm @@ -180,7 +180,7 @@ sub execute_cmd { ); } elsif ($options{cmd} eq 'SENDEXPORTFILE') { my $cache_dir = (defined($connector->{config}->{cache_dir})) ? $connector->{config}->{cache_dir} : '/var/cache/centreon'; - my $remote_dir = (defined($connector->{config}->{remote_dir})) ? $connector->{config}->{remote_dir} : '/var/lib/centreon/remote-data/' + my $remote_dir = (defined($connector->{config}->{remote_dir})) ? $connector->{config}->{remote_dir} : '/var/lib/centreon/remote-data/'; # remote server $self->send_internal_action( action => 'REMOTECOPY', From 9527a9e1f09d9e0f99f47c70769e79b3d1c0f562 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Mon, 16 Sep 2019 16:26:07 +0200 Subject: [PATCH 084/948] feat(proxy): add target for sshclient --- gorgone/modules/core/proxy/class.pm | 3 ++- gorgone/modules/core/proxy/sshclient.pm | 12 ++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/gorgone/modules/core/proxy/class.pm b/gorgone/modules/core/proxy/class.pm index 4cb08237be1..0877818f41c 100644 --- a/gorgone/modules/core/proxy/class.pm +++ b/gorgone/modules/core/proxy/class.pm @@ -275,7 +275,8 @@ sub proxy { my ($status, $data_ret) = $connector->{clients}->{$target_client}->{class}->action( action => $action, data => $decoded_data, - target_direct => $target_direct + target_direct => $target_direct, + target => $target ); if ($status == 0) { $connector->send_log(code => centreon::gorgone::module::ACTION_FINISH_OK, token => $token, data => $data_ret); diff --git a/gorgone/modules/core/proxy/sshclient.pm b/gorgone/modules/core/proxy/sshclient.pm index e76f23cbc38..2cb552eef58 100644 --- a/gorgone/modules/core/proxy/sshclient.pm +++ b/gorgone/modules/core/proxy/sshclient.pm @@ -140,9 +140,10 @@ sub action_enginecommand { return (0, { message => 'send enginecommand succeeded' }); } -sub action_sendcfgfile { +sub action_remotecopy { my ($self, %options) = @_; + # cacheDir = /var/cache/centreon # src = $self->{cacheDir} . "/config/engine/" . $id | dst = 'cfg_dir' of cfg_nagios table # src2 = $self->{cacheDir} . "/config/broker/" . $id | dst = 'centreonbroker_cfg_path' of 'nagios_server' table @@ -152,6 +153,8 @@ sub action_sendcfgfile { # tar file: tar czf centreon-engine-config-ID.tar.gz -C "$self->{cacheDir}/config/engine/$id" . # untar file: tar zxf centreon-engine-config-ID.tar.gz -C "cfg_dir" + + # meme si pas de type, on fait la copie sans actions derriere } sub action { @@ -160,7 +163,12 @@ sub action { $self->test_connection(); my $func = $self->can('action_' . lc($options{action})); if (defined($func)) { - return $func->($self, data => $options{data}); + return $func->( + $self, + data => $options{data}, + target_direct => $options{target_direct}, + target => $options{target} + ); } $self->{logger}->writeLogError('[proxy] -sshclient- unsupported action ' . $options{action}); From 3ace0ea44ba692690bfe380cd52acdf5e79e4e05 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 16 Sep 2019 16:27:19 +0200 Subject: [PATCH 085/948] enh(centreon/legacycmd): add cache_dir when calling REMOTECOPY --- gorgone/modules/centreon/legacycmd/class.pm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/modules/centreon/legacycmd/class.pm index b4b4f4b6459..9e840b77da3 100644 --- a/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/modules/centreon/legacycmd/class.pm @@ -161,6 +161,7 @@ sub execute_cmd { content => { source => $cache_dir . '/config/engine/' . $options{target}, destination => $self->{pollers}->{$options{target}}->{cfg_dir} . '/', + cache_dir => $cache_dir, type => 'engine', } }, @@ -174,6 +175,7 @@ sub execute_cmd { content => { source => $cache_dir . '/config/broker/' . $options{target}, destination => $self->{pollers}->{$options{target}}->{centreonbroker_cfg_path} . '/', + cache_dir => $cache_dir, type => 'broker', } }, @@ -190,6 +192,7 @@ sub execute_cmd { content => { source => $cache_dir . '/config/export/' . $options{target}, destination => $remote_dir, + cache_dir => $cache_dir, type => 'remote', } }, From db65419c940981d95e79e1f7aff1ec941fda09d2 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Mon, 16 Sep 2019 17:11:35 +0200 Subject: [PATCH 086/948] fix(legacycmd): add chomp --- gorgone/modules/centreon/legacycmd/class.pm | 1 + gorgone/modules/core/proxy/sshclient.pm | 83 ++++++++++++++++++--- 2 files changed, 73 insertions(+), 11 deletions(-) diff --git a/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/modules/centreon/legacycmd/class.pm index 9e840b77da3..83e06867b26 100644 --- a/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/modules/centreon/legacycmd/class.pm @@ -214,6 +214,7 @@ sub handle_cmd_file { return ; } + chomp $line; if ($line =~ /^(.*?):([^:]*)(?::(.*)){0,1}/) { $self->execute_cmd(cmd => $1, target => $2, param => $3); my $current_pos = tell($handle); diff --git a/gorgone/modules/core/proxy/sshclient.pm b/gorgone/modules/core/proxy/sshclient.pm index 2cb552eef58..4224fa44e78 100644 --- a/gorgone/modules/core/proxy/sshclient.pm +++ b/gorgone/modules/core/proxy/sshclient.pm @@ -26,6 +26,8 @@ use strict; use warnings; use Libssh::Sftp qw(:all); use POSIX; +use centreon::misc::misc; +use File::Basename; sub new { my ($class, %options) = @_; @@ -75,6 +77,25 @@ sub open_session { return 0; } +sub local_command { + my ($self, %options) = @_; + + my ($error, $stdout, $exit_code) = centreon::misc::misc::backtick( + command => $options{command}, + timeout => (defined($options{timeout})) ? $options{timeout} : 120, + wait_exit => 1, + redirect_stderr => 1, + logger => $self->{logger} + ); + if ($error <= -1000) { + return (-1, { message => "command '$options{command}' execution issue: $stdout" }); + } + if ($exit_code != 0) { + return (-1, { message => "command '$options{command}' execution issue ($exit_code): $stdout" }); + } + return 0; +} + sub action_command { my ($self, %options) = @_; @@ -112,7 +133,7 @@ sub action_enginecommand { if (!defined($options{data}->{content}->{command}) || $options{data}->{content}->{command} eq '') { $self->{logger}->writeLogError('[proxy] -sshclient- action_enginecommand: need command'); return (-1, { message => 'please set command' }); - } + } if (!defined($options{data}->{content}->{engine_pipe}) || $options{data}->{content}->{engine_pipe} eq '') { $self->{logger}->writeLogError('[proxy] -sshclient- action_enginecommand: need engine_pipe'); return (-1, { message => 'please set engine_pipe' }); @@ -142,19 +163,59 @@ sub action_enginecommand { sub action_remotecopy { my ($self, %options) = @_; - - # cacheDir = /var/cache/centreon - # src = $self->{cacheDir} . "/config/engine/" . $id | dst = 'cfg_dir' of cfg_nagios table - # src2 = $self->{cacheDir} . "/config/broker/" . $id | dst = 'centreonbroker_cfg_path' of 'nagios_server' table - - # /var/cache/centreon/config/engine/1/ - # /var/cache/centreon/config/broker/1/ + if (!defined($options{data}->{content}->{source}) || $options{data}->{content}->{source} eq '') { + $self->{logger}->writeLogError('[proxy] -sshclient- action_remotecopy: need source'); + return (-1, { message => 'please set source' }); + } + if (!defined($options{data}->{content}->{destination}) || $options{data}->{content}->{destination} eq '') { + $self->{logger}->writeLogError('[proxy] -sshclient- action_remotecopy: need destination'); + return (-1, { message => 'please set destination' }); + } - # tar file: tar czf centreon-engine-config-ID.tar.gz -C "$self->{cacheDir}/config/engine/$id" . - # untar file: tar zxf centreon-engine-config-ID.tar.gz -C "cfg_dir" + my ($code, $message, $data); + + my $srcname; + my $localsrc = $options{data}->{content}->{source}; + my $src = $options{data}->{content}->{source}; + my ($dst, $dst_sftp) = ($options{data}->{content}->{destination}, $options{data}->{content}->{destination}); + if ($options{target_direct} == 0) { + $dst = $src; + $dst_sftp = $src; + } + + if (-f $options{data}->{content}->{source}) { + $srcname = File::Basename::basename($src); + $dst_sftp .= $srcname if ($dst =~ /\/$/); + } elsif (-d $options{data}->{content}->{source}) { + $srcname = (defined($options{data}->{content}->{type}) ? $options{data}->{content}->{type} : 'tmp') . '-' . $options{target} . '.tar.gz'; + $localsrc = $options{data}->{content}->{cache_dir} . '/' . $srcname; + $dst_sftp = $options{data}->{content}->{cache_dir} . '/' . $srcname; + + ($code, $message) = $self->local_command(command => "tar czf $localsrc -C '" . $src . "'"); + return ($code, $message) if ($code == -1); + } else { + return (-1, { message => 'unknown source' }); + } + + if (($code = $self->{sftp}->copy_file(src => $src, dst => $dst_sftp)) == -1) { + return (-1, { message => "cannot sftp copy file : " . $self->{sftp}->error() }); + } + + if (-d $options{data}->{content}->{source}) { + ($code, $data) = $self->action_command( + data => { + content => { command => "tar zxf $dst_sftp -C '" . $dst . "'" } + }, + ); + return ($code, $data) if ($code == -1); + } + + if (defined($options{data}->{content}->{type}) && $options{target_direct} == 0) { + # for remote server: ask in centcore + } - # meme si pas de type, on fait la copie sans actions derriere + return (0, { message => 'send remotecopy succeeded' }); } sub action { From c24117ae8964ea829efedefdc0bf4b81ba4a794c Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Mon, 16 Sep 2019 17:24:33 +0200 Subject: [PATCH 087/948] fix(pollers): good order --- gorgone/modules/centreon/legacycmd/class.pm | 3 ++- gorgone/modules/centreon/pollers/class.pm | 2 +- gorgone/modules/core/proxy/class.pm | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/modules/centreon/legacycmd/class.pm index 83e06867b26..f2a819b6ffa 100644 --- a/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/modules/centreon/legacycmd/class.pm @@ -137,6 +137,8 @@ sub get_pollers_config { sub execute_cmd { my ($self, %options) = @_; + chomp $options{target}; + chomp $options{param} if (defined($options{param})); if ($options{cmd} eq 'EXTERNALCMD') { # TODO: need to remove illegal characters!! $self->send_internal_action( @@ -214,7 +216,6 @@ sub handle_cmd_file { return ; } - chomp $line; if ($line =~ /^(.*?):([^:]*)(?::(.*)){0,1}/) { $self->execute_cmd(cmd => $1, target => $2, param => $3); my $current_pos = tell($handle); diff --git a/gorgone/modules/centreon/pollers/class.pm b/gorgone/modules/centreon/pollers/class.pm index d637ec34940..9ec15803b2c 100644 --- a/gorgone/modules/centreon/pollers/class.pm +++ b/gorgone/modules/centreon/pollers/class.pm @@ -142,9 +142,9 @@ sub action_pollersresync { } } + $self->send_internal_action(action => 'SETCOREID', data => { id => $core_id } ) if (defined($core_id)); $self->send_internal_action(action => 'REGISTERNODES', data => { nodes => $register_nodes } ); $self->send_internal_action(action => 'UNREGISTERNODES', data => { nodes => $unregister_nodes } ); - $self->send_internal_action(action => 'SETCOREID', data => { id => $core_id } ) if (defined($core_id)); $self->{logger}->writeLogDebug("[pollers] -class- finish resync"); $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => { message => 'action pollersresync finished' }); diff --git a/gorgone/modules/core/proxy/class.pm b/gorgone/modules/core/proxy/class.pm index 0877818f41c..b7f61b201a1 100644 --- a/gorgone/modules/core/proxy/class.pm +++ b/gorgone/modules/core/proxy/class.pm @@ -278,6 +278,8 @@ sub proxy { target_direct => $target_direct, target => $target ); + + $connector->{logger}->writeLogDebug("[proxy] -class- sshclient return: [message = $data_ret->{message}]"); if ($status == 0) { $connector->send_log(code => centreon::gorgone::module::ACTION_FINISH_OK, token => $token, data => $data_ret); } else { From b403a84b2b249afa886f1e4029875cb8f912d580 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Mon, 16 Sep 2019 17:34:07 +0200 Subject: [PATCH 088/948] feat(proxy): add remotecopy in sshclient --- gorgone/modules/core/proxy/sshclient.pm | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gorgone/modules/core/proxy/sshclient.pm b/gorgone/modules/core/proxy/sshclient.pm index 4224fa44e78..f01d67699f3 100644 --- a/gorgone/modules/core/proxy/sshclient.pm +++ b/gorgone/modules/core/proxy/sshclient.pm @@ -180,6 +180,7 @@ sub action_remotecopy { my $src = $options{data}->{content}->{source}; my ($dst, $dst_sftp) = ($options{data}->{content}->{destination}, $options{data}->{content}->{destination}); if ($options{target_direct} == 0) { + $localsrc = $src; $dst = $src; $dst_sftp = $src; } @@ -192,20 +193,20 @@ sub action_remotecopy { $localsrc = $options{data}->{content}->{cache_dir} . '/' . $srcname; $dst_sftp = $options{data}->{content}->{cache_dir} . '/' . $srcname; - ($code, $message) = $self->local_command(command => "tar czf $localsrc -C '" . $src . "'"); + ($code, $message) = $self->local_command(command => "tar czf $localsrc -C '" . $src . "' ."); return ($code, $message) if ($code == -1); } else { return (-1, { message => 'unknown source' }); } - if (($code = $self->{sftp}->copy_file(src => $src, dst => $dst_sftp)) == -1) { + if (($code = $self->{sftp}->copy_file(src => $localsrc, dst => $dst_sftp)) == -1) { return (-1, { message => "cannot sftp copy file : " . $self->{sftp}->error() }); } if (-d $options{data}->{content}->{source}) { ($code, $data) = $self->action_command( data => { - content => { command => "tar zxf $dst_sftp -C '" . $dst . "'" } + content => { command => "tar zxf $dst_sftp -C '" . $dst . "' ." } }, ); return ($code, $data) if ($code == -1); From ccd15cf209186463538c75224b5bf75cd552bf59 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Mon, 16 Sep 2019 17:38:57 +0200 Subject: [PATCH 089/948] feat(proxy): add remotecopy in sshclient --- gorgone/modules/core/proxy/sshclient.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/modules/core/proxy/sshclient.pm b/gorgone/modules/core/proxy/sshclient.pm index f01d67699f3..b88e3b471de 100644 --- a/gorgone/modules/core/proxy/sshclient.pm +++ b/gorgone/modules/core/proxy/sshclient.pm @@ -180,12 +180,12 @@ sub action_remotecopy { my $src = $options{data}->{content}->{source}; my ($dst, $dst_sftp) = ($options{data}->{content}->{destination}, $options{data}->{content}->{destination}); if ($options{target_direct} == 0) { - $localsrc = $src; $dst = $src; $dst_sftp = $src; } if (-f $options{data}->{content}->{source}) { + $localsrc = $src; $srcname = File::Basename::basename($src); $dst_sftp .= $srcname if ($dst =~ /\/$/); } elsif (-d $options{data}->{content}->{source}) { From 8a589f8bc7460e69d77a27396f6e53cf873c5269 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 17 Sep 2019 09:58:03 +0200 Subject: [PATCH 090/948] fix(proxy): sync proxyaddnode --- gorgone/centreon/gorgone/common.pm | 2 +- gorgone/modules/core/proxy/hooks.pm | 37 ++++++++++++++++++++++------- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/gorgone/centreon/gorgone/common.pm b/gorgone/centreon/gorgone/common.pm index 191e59d1da2..7cc13eea5b1 100644 --- a/gorgone/centreon/gorgone/common.pm +++ b/gorgone/centreon/gorgone/common.pm @@ -360,7 +360,7 @@ sub setcoreid { my $name = $options{gorgone}->{modules_id}->{$options{gorgone_config}->{gorgonecore}->{proxy_name}}; my $method; if (defined($name) && ($method = $name->can('setcoreid'))) { - $method->(core_id => $data->{id}); + $method->(dbh => $options{dbh}, core_id => $data->{id}, logger => $options{logger}); } } diff --git a/gorgone/modules/core/proxy/hooks.pm b/gorgone/modules/core/proxy/hooks.pm index 7d2c00e1934..1550f21e91c 100644 --- a/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/modules/core/proxy/hooks.pm @@ -83,7 +83,7 @@ sub init { $external_socket = $options{external_socket}; $internal_socket = $options{internal_socket}; for my $pool_id (1..$config->{pool}) { - create_child(pool_id => $pool_id, logger => $options{logger}); + create_child(dbh => $options{dbh}, pool_id => $pool_id, logger => $options{logger}); } } @@ -260,6 +260,17 @@ sub kill_internal { } +sub check_create_child { + my (%options) = @_; + + # Check if we need to create a child + for my $pool_id (1..$config->{pool}) { + if (!defined($pools->{$pool_id})) { + create_child(dbh => $options{dbh}, pool_id => $pool_id, logger => $options{logger}); + } + } +} + sub check { my (%options) = @_; @@ -274,16 +285,11 @@ sub check { delete $pools_pid->{$pid}; delete $options{dead_childs}->{$pid}; if ($stop == 0) { - create_child(pool_id => $pool_id, logger => $options{logger}); + create_child(dbh => $options{dbh}, pool_id => $pool_id, logger => $options{logger}); } } - # Check if we need to create a child - for my $pool_id (1..$config->{pool}) { - if (!defined($pools->{$pool_id})) { - create_child(pool_id => $pool_id, logger => $options{logger}); - } - } + check_create_child(dbh => $options{dbh}, logger => $options{logger}); foreach (keys %{$pools}) { $count++ if ($pools->{$_}->{running} == 1); @@ -508,6 +514,20 @@ sub create_child { $options{logger}->writeLogInfo("[proxy] -hooks- PID $child_pid (gorgone-proxy) for pool id '" . $options{pool_id} . "'"); $pools->{$options{pool_id}} = { pid => $child_pid, ready => 0, running => 1 }; $pools_pid->{$child_pid} = $options{pool_id}; + + #$nodes_pool->{$target} + + # we sent proxyaddnode to sync + foreach my $node_id (keys %$nodes_pool) { + next if ($nodes_pool->{$node_id} != $options{pool_id}); + routing( + socket => $internal_socket, + action => 'PROXYADDNODE', + target => $node_id, + data => JSON::XS->new->utf8->encode($register_nodes->{$node_id}), + dbh => $options{dbh} + ); + } } sub pull_request { @@ -656,6 +676,7 @@ sub setcoreid { my (%options) = @_; $core_id = $options{core_id}; + check_create_child(%options); } sub add_parent_ping { From 4fb1f41829a507bbd97a44c2b471012a1d1d6123 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 17 Sep 2019 10:05:58 +0200 Subject: [PATCH 091/948] update todo --- gorgone/TODO | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/gorgone/TODO b/gorgone/TODO index 002c87bfbed..e73d263ec02 100644 --- a/gorgone/TODO +++ b/gorgone/TODO @@ -1,6 +1,3 @@ -- Add legacy module: read centcore.cmd -- Can chain agents and forward protocol - Add a core event: GETINFORMATION (events loaded, target loaded and module loaded) - gorgone-newtest: don't use centcore.cmd. use ssh system. -- Use 4 code for all modules: 0 -> transaction progress, 1 -> transaction finished ok, 2 -> transaction finished ko -- Need to manage some legacy commands in proxy module with libssh (ENGINECOMMAND, COMMAND, copy files). Will be hardcoded +- Add gorgone sqlite cleaning From fc363e31e9e8e02587c07b3cdffa344b4515150d Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 17 Sep 2019 10:35:10 +0200 Subject: [PATCH 092/948] feat(proxy): add ping for ssh --- gorgone/modules/core/proxy/class.pm | 19 ++++++++++++++++--- gorgone/modules/core/proxy/hooks.pm | 20 ++++++++------------ gorgone/modules/core/proxy/sshclient.pm | 10 ++++++++++ 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/gorgone/modules/core/proxy/class.pm b/gorgone/modules/core/proxy/class.pm index b7f61b201a1..f7be9385867 100644 --- a/gorgone/modules/core/proxy/class.pm +++ b/gorgone/modules/core/proxy/class.pm @@ -233,6 +233,8 @@ sub proxy { return undef; } my ($action, $token, $target, $data) = ($1, $2, $3, $4); + $connector->{logger}->writeLogDebug("[proxy] -class- Send message: [action = $action] [token = $token] [target = $target] [data = $data]"); + if ($action eq 'PROXYADDNODE') { action_proxyaddnode($connector, data => $data); return ; @@ -271,7 +273,20 @@ sub proxy { } elsif ($connector->{clients}->{$target_client}->{type} eq 'push_ssh') { my ($code, $decoded_data) = $connector->json_decode(argument => $data); return if ($code == 1); - + + if ($action eq 'PING') { + if ($connector->{clients}->{$target_client}->{class}->ping() != -1) { + centreon::gorgone::common::zmq_send_message( + socket => $connector->{internal_socket}, + action => 'PONG', + token => $token, + target => '', + data => { id => $target_client }, + json_encode => 1 + ); + } + return ; + } my ($status, $data_ret) = $connector->{clients}->{$target_client}->{class}->action( action => $action, data => $decoded_data, @@ -286,8 +301,6 @@ sub proxy { $connector->send_log(code => centreon::gorgone::module::ACTION_FINISH_KO, token => $token, data => $data_ret); } } - - $connector->{logger}->writeLogDebug("[proxy] -class- Send message: [action = $action] [token = $token] [target = $target] [data = $data]"); } sub event_internal { diff --git a/gorgone/modules/core/proxy/hooks.pm b/gorgone/modules/core/proxy/hooks.pm index 1550f21e91c..e001cc1f99b 100644 --- a/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/modules/core/proxy/hooks.pm @@ -408,7 +408,7 @@ sub ping_send { next if (defined($synctime_nodes->{$id}) && $synctime_nodes->{$id}->{in_progress_ping} == 1); $constatus_ping->{$id}->{last_ping_sent} = time(); - if ($register_nodes->{$id}->{type} eq 'push_zmq') { + if ($register_nodes->{$id}->{type} eq 'push_zmq' || $register_nodes->{$id}->{type} eq 'push_ssh') { $synctime_nodes->{$id}->{in_progress_ping} = 1; routing(socket => $internal_socket, action => 'PING', target => $id, data => '{}', dbh => $options{dbh}); } elsif ($register_nodes->{$id}->{type} eq 'pull') { @@ -637,11 +637,9 @@ sub register_nodes { if (defined($register_nodes->{$node->{id}})) { $new_node = 0; # we remove subnodes before - if ($register_nodes->{$node->{id}}->{type} ne 'push_ssh') { - foreach my $subnode_id (keys %$register_subnodes) { - delete $register_subnodes->{$subnode_id} - if ($register_subnodes->{$subnode_id} eq $node->{id}); - } + foreach my $subnode_id (keys %$register_subnodes) { + delete $register_subnodes->{$subnode_id} + if ($register_subnodes->{$subnode_id} eq $node->{id}); } } @@ -652,12 +650,10 @@ sub register_nodes { } } - if ($node->{type} eq 'push_zmq' || $node->{type} eq 'pull') { - $last_pong->{$node->{id}} = 0 if (!defined($last_pong->{$node->{id}})); - if (!defined($synctime_nodes->{$node->{id}})) { - $synctime_nodes->{$node->{id}} = { ctime => 0, in_progress_ping => 0, in_progress => 0, in_progress_time => -1, last_id => 0, synctime_error => 0 }; - get_sync_time(node_id => $node->{id}, dbh => $options{dbh}); - } + $last_pong->{$node->{id}} = 0 if (!defined($last_pong->{$node->{id}})); + if (!defined($synctime_nodes->{$node->{id}})) { + $synctime_nodes->{$node->{id}} = { ctime => 0, in_progress_ping => 0, in_progress => 0, in_progress_time => -1, last_id => 0, synctime_error => 0 }; + get_sync_time(node_id => $node->{id}, dbh => $options{dbh}); } if ($node->{type} ne 'pull') { diff --git a/gorgone/modules/core/proxy/sshclient.pm b/gorgone/modules/core/proxy/sshclient.pm index b88e3b471de..09338aa5e92 100644 --- a/gorgone/modules/core/proxy/sshclient.pm +++ b/gorgone/modules/core/proxy/sshclient.pm @@ -96,6 +96,16 @@ sub local_command { return 0; } +sub ping { + my ($self, %options) = @_; + + if ($self->is_connected()) { + return 0; + } + + return -1; +} + sub action_command { my ($self, %options) = @_; From fc7b8bd2af242577f7fa90e725c3f5b96176f388 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 17 Sep 2019 10:46:56 +0200 Subject: [PATCH 093/948] fix(proxy): pong sshclient --- gorgone/modules/core/proxy/class.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/modules/core/proxy/class.pm b/gorgone/modules/core/proxy/class.pm index f7be9385867..21d6e20f375 100644 --- a/gorgone/modules/core/proxy/class.pm +++ b/gorgone/modules/core/proxy/class.pm @@ -281,7 +281,7 @@ sub proxy { action => 'PONG', token => $token, target => '', - data => { id => $target_client }, + data => { data => { id => $target_client } }, json_encode => 1 ); } From 8fcd6e141915f6e17ac057cb4a99dd52351b418f Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 17 Sep 2019 11:02:15 +0200 Subject: [PATCH 094/948] enh(api): add refresh option to get logs from childs --- gorgone/centreon/gorgone/api.pm | 51 ++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/gorgone/centreon/gorgone/api.pm b/gorgone/centreon/gorgone/api.pm index 384c96333db..7b7bb8f71ae 100644 --- a/gorgone/centreon/gorgone/api.pm +++ b/gorgone/centreon/gorgone/api.pm @@ -45,8 +45,13 @@ sub root { } my $response; - if ($options{method} eq 'GET' && $options{uri} =~ /^\/api\/log\/(.*)$/) { - $response = get_log(socket => $options{socket}, token => $1); + if ($options{method} eq 'GET' && $options{uri} =~ /^\/api\/(targets\/(\w*)\/)?log\/(.*)$/) { + $response = get_log( + socket => $options{socket}, + target => $2, + token => $3, + refresh => (defined($options{parameters}->{refresh})) ? $options{parameters}->{refresh} : undef + ); } elsif ($options{uri} =~ /^\/api\/(targets\/(\w*)\/)?(\w+)\/(\w+)\/?([\w\/]*?)$/ && defined($dispatch{$3 . '_' . $options{method} . '_/' . $4})) { my @variables = split(/\//, $5); @@ -59,7 +64,8 @@ sub root { parameters => $options{parameters}, variables => \@variables, }, - wait => (defined($options{parameters}->{wait})) ? $options{parameters}->{wait} : undef + wait => (defined($options{parameters}->{wait})) ? $options{parameters}->{wait} : undef, + refresh => (defined($options{parameters}->{refresh})) ? $options{parameters}->{refresh} : undef, ); } else { $response = '{"error":"method_unknown","message":"Method not implemented"}'; @@ -94,7 +100,12 @@ sub call_action { if (defined($result->{token}) && $result->{token} ne '') { if (defined($options{wait}) && $options{wait} ne '') { Time::HiRes::usleep($options{wait}); - $response = get_log(socket => $options{socket}, token => $result->{token}); + $response = get_log( + socket => $options{socket}, + target => $options{target}, + token => $result->{token}, + refresh => $options{refresh}, + ); } else { $response = '{"token":"' . $result->{token} . '"}'; } @@ -105,15 +116,6 @@ sub call_action { sub get_log { my (%options) = @_; - - centreon::gorgone::common::zmq_send_message( - socket => $options{socket}, - action => 'GETLOG', - data => { - token => $options{token} - }, - json_encode => 1 - ); $socket = $options{socket}; my $poll = [ @@ -124,6 +126,29 @@ sub get_log { } ]; + if (defined($options{target}) && $options{target} ne '') { + centreon::gorgone::common::zmq_send_message( + socket => $options{socket}, + target => $options{target}, + action => 'GETLOG', + json_encode => 1 + ); + + my $refresh_wait = (defined($options{refresh}) && $options{refresh} ne '') ? $options{refresh} : '10000'; + Time::HiRes::usleep($refresh_wait); + + my $rev = zmq_poll($poll, 5000); + } + + centreon::gorgone::common::zmq_send_message( + socket => $options{socket}, + action => 'GETLOG', + data => { + token => $options{token} + }, + json_encode => 1 + ); + my $rev = zmq_poll($poll, 5000); my $response = '{"error":"no_log","message":"No log found for token","token":"' . $options{token} . '"}'; From 2ec8fae74fa6ca593124693cc74ea50d87babad1 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 17 Sep 2019 14:26:42 +0200 Subject: [PATCH 095/948] enh(proxy): add legacy commands for sshclient --- gorgone/modules/core/proxy/sshclient.pm | 67 ++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/gorgone/modules/core/proxy/sshclient.pm b/gorgone/modules/core/proxy/sshclient.pm index 09338aa5e92..5438df1c148 100644 --- a/gorgone/modules/core/proxy/sshclient.pm +++ b/gorgone/modules/core/proxy/sshclient.pm @@ -28,6 +28,7 @@ use Libssh::Sftp qw(:all); use POSIX; use centreon::misc::misc; use File::Basename; +use Time::HiRes; sub new { my ($class, %options) = @_; @@ -106,6 +107,39 @@ sub ping { return -1; } +sub action_centcore { + my ($self, %options) = @_; + + if (!defined($options{data}->{content}->{command}) || $options{data}->{content}->{command} eq '') { + $self->{logger}->writeLogError('[proxy] -sshclient- action_centcore: need command'); + return (-1, { message => 'please set command' }); + } + if (!defined($options{data}->{content}->{target}) || $options{data}->{content}->{target} eq '') { + $self->{logger}->writeLogError('[proxy] -sshclient- action_centcore: need target'); + return (-1, { message => 'please set target' }); + } + + my $centcore_cmd = defined($options{data}->{content}->{centcore_cmd}) ? $options{data}->{content}->{centcore_dir} : '/var/lib/centreon/centcore/'; + my $time = Time::HiRes::time(); + $time =~ s/\.//g; + $centcore_cmd .= $time . '.cmd'; + + my $data = $options{data}->{content}->{command} . ':' . $options{data}->{content}->{target}; + $data .= ':' . $options{data}->{content}->{param} if (defined($options{data}->{content}->{param}) && $options{data}->{content}->{param} ne ''); + chomp $data; + + my $file = $self->{sftp}->open(file => $centcore_cmd, accesstype => O_WRONLY|O_CREAT|O_TRUNC); + if (!defined($file)) { + return (-1, { message => "cannot open stat file '$centcore_cmd': " . $self->{sftp}->error() }); + } + if ($self->{sftp}->write(handle_file => $file, data => $data . "\n") != Libssh::Session::SSH_OK) { + return (-1, { message => "cannot write stat file '$centcore_cmd': " . $self->{sftp}->error() }); + } + + $self->{logger}->writeLogDebug("[proxy] -sshclient- action_centcore '" . $centcore_cmd . "' succeeded"); + return (0, { message => 'send action_centcore succeeded' }); +} + sub action_command { my ($self, %options) = @_; @@ -150,6 +184,18 @@ sub action_enginecommand { } chomp $options{data}->{content}->{command}; + if ($options{target_direct} == 0) { + return $self->action_centcore( + data => { + content => { + command => 'EXTERNALCMD', + target => $options{target}, + param => $options{data}->{content}->{command}, + } + } + ); + } + my $ret = $self->{sftp}->stat_file(file => $options{data}->{content}->{engine_pipe}); if (!defined($ret)) { return (-1, { message => "cannot stat file '$options{data}->{content}->{engine_pipe}': " . $self->{sftp}->get_msg_error() }); @@ -223,7 +269,26 @@ sub action_remotecopy { } if (defined($options{data}->{content}->{type}) && $options{target_direct} == 0) { - # for remote server: ask in centcore + # only one time (after broker) + if ($options{data}->{content}->{type} eq 'broker') { + $self->action_centcore( + data => { + content => { + command => 'SENDCFGFILE', + target => $options{target}, + } + } + ); + } elsif ($options{data}->{content}->{type} eq 'remote') { + $self->action_centcore( + data => { + content => { + command => 'SENDEXPORTFILE', + target => $options{target}, + } + } + ); + } } return (0, { message => 'send remotecopy succeeded' }); From 99fca12d18eeed2ef303b70b348414ac9f8c5847 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 17 Sep 2019 15:33:53 +0200 Subject: [PATCH 096/948] enh(legacycmd): read centcore directory --- gorgone/modules/centreon/legacycmd/class.pm | 86 +++++++++++++++++---- 1 file changed, 69 insertions(+), 17 deletions(-) diff --git a/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/modules/centreon/legacycmd/class.pm index f2a819b6ffa..05c51a424d4 100644 --- a/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/modules/centreon/legacycmd/class.pm @@ -43,6 +43,9 @@ sub new { if (!defined($connector->{config}->{cmd_file}) || $connector->{config}->{cmd_file} eq '') { $connector->{config}->{cmd_file} = '/var/lib/centreon/centcore.cmd'; } + if (!defined($connector->{config}->{cmd_dir}) || $connector->{config}->{cmd_dir} eq '') { + $connector->{config}->{cmd_dir} = '/var/lib/centreon/centcore/'; + } $connector->{config_core} = $options{config_core}; $connector->{config_db_centreon} = $options{config_db_centreon}; $connector->{stop} = 0; @@ -88,24 +91,25 @@ sub move_cmd_file { my ($self, %options) = @_; my $handle; - if (-e $self->{config}->{cmd_file} . '_read') { - if (!open($handle, '+<', $self->{config}->{cmd_file} . '_read')) { - $self->{logger}->writeLogError("[legacycmd] -class- cannot open file '" . $self->{config}->{cmd_file} . "_read': $!"); + if (-e $options{dst}) { + if (!open($handle, '+<', $options{dst})) { + $self->{logger}->writeLogError("[legacycmd] -class- cannot open file '" . $options{dst} . "': $!"); return -1; } return (0, $handle); } - return -1 if (! -e $connector->{config}->{cmd_file}); + return -1 if (!defined($options{src})); + return -1 if (! -e $options{src}); - if (!File::Copy::move($self->{config}->{cmd_file}, $self->{config}->{cmd_file} . '_read')) { - $self->{logger}->writeLogError("[legacycmd] -class- cannot move file '" . $connector->{config}->{cmd_file} . "': $!"); + if (!File::Copy::move($options{src}, $options{dst})) { + $self->{logger}->writeLogError("[legacycmd] -class- cannot move file '" . $options{src} . "': $!"); return -1; } - if (!open($handle, '+<', $self->{config}->{cmd_file} . '_read')) { - $self->{logger}->writeLogError("[legacycmd] -class- cannot open file '" . $self->{config}->{cmd_file} . "_read': $!"); + if (!open($handle, '+<', $options{dst})) { + $self->{logger}->writeLogError("[legacycmd] -class- cannot open file '" . $options{dst} . "': $!"); return -1; } @@ -202,18 +206,15 @@ sub execute_cmd { } } -sub handle_cmd_file { +sub handle_file { my ($self, %options) = @_; require bytes; - return if ($self->get_pollers_config() == -1); - my ($code, $handle) = $self->move_cmd_file(); - return if ($code == -1); - + my $handle = $options{handle}; while (my $line = <$handle>) { if ($self->{stop} == 1) { close($handle); - return ; + return -1; } if ($line =~ /^(.*?):([^:]*)(?::(.*)){0,1}/) { @@ -226,9 +227,60 @@ sub handle_cmd_file { } } - $self->{logger}->writeLogDebug("[legacycmd] -class- process file '" . $connector->{config}->{cmd_file} . "_read'"); + $self->{logger}->writeLogDebug("[legacycmd] -class- process file '" . $options{file} . "'"); close($handle); - unlink($self->{config}->{cmd_file} . '_read'); + unlink($options{file}); + return 0; +} + +sub handle_centcore_cmd { + my ($self, %options) = @_; + + my ($code, $handle) = $self->move_cmd_file( + src => $self->{config}->{cmd_file}, + dst => $self->{config}->{cmd_file} . '_read', + ); + return if ($code == -1); + $self->handle_file(handle => $handle, file => $self->{config}->{cmd_file} . '_read'); +} + +sub handle_centcore_dir { + my ($self, %options) = @_; + + my ($dh, @files); + if (!opendir($dh, $self->{config}->{cmd_dir})) { + $self->{logger}->writeLogError("[legacycmd] -class- cant opendir '" . $self->{config}->{cmd_dir} . "': $!"); + return ; + } + @files = sort { (stat($self->{config}->{cmd_dir} . '/' . $a))[10] <=> (stat($self->{config}->{cmd_dir} . '/' . $b))[10] } (readdir($dh)); + closedir($dh); + + my ($code, $handle); + foreach (@files) { + next if ($_ =~ /^\./); + if ($_ =~ /_read$/) { + ($code, $handle) = $self->move_cmd_file( + dst => $self->{config}->{cmd_dir} . '/' . $_, + ); + } else { + ($code, $handle) = $self->move_cmd_file( + src => $self->{config}->{cmd_dir} . '/' . $_, + dst => $self->{config}->{cmd_dir} . '/' . $_ . '_read', + ); + } + return if ($code == -1); + if ($self->handle_file(handle => $handle, file => $self->{config}->{cmd_dir} . '/' . $_) == -1) { + return ; + } + } +} + +sub handle_cmd_files { + my ($self, %options) = @_; + + return if ($self->get_pollers_config() == -1); + $self->handle_centcore_cmd(); + $self->handle_centcore_dir(); } sub event { @@ -291,7 +343,7 @@ sub run { exit(0); } - $self->handle_cmd_file(); + $self->handle_cmd_files(); } } From 20d09dd26f8c54c3df1c9775b98d265116d1850f Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 17 Sep 2019 16:28:16 +0200 Subject: [PATCH 097/948] feat(proxy/action): copy files through zmq --- gorgone/modules/core/action/class.pm | 112 +++++++++++++++- gorgone/modules/core/action/hooks.pm | 1 + gorgone/modules/core/proxy/hooks.pm | 194 +++++++++++++++++++++++---- 3 files changed, 281 insertions(+), 26 deletions(-) diff --git a/gorgone/modules/core/action/class.pm b/gorgone/modules/core/action/class.pm index 873687cbbba..23d708cb782 100644 --- a/gorgone/modules/core/action/class.pm +++ b/gorgone/modules/core/action/class.pm @@ -29,6 +29,11 @@ use centreon::misc::misc; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); use JSON::XS; +use File::Basename; +use File::Copy; +use MIME::Base64; +use Digest::MD5::File qw(file_md5_hex); +use Fcntl; my %handlers = (TERM => {}, HUP => {}); my ($connector); @@ -98,7 +103,8 @@ sub action_command { my ($error, $stdout, $return_code) = centreon::misc::misc::backtick( command => $options{data}->{content}->{command}, #arguments => [@$args, $sub_cmd], - timeout => (defined($options{data}->{content}->{timeout})) ? $options{data}->{content}->{timeout} : $self->{command_timeout}, + timeout => (defined($options{data}->{content}->{timeout})) ? + $options{data}->{content}->{timeout} : $self->{command_timeout}, wait_exit => 1, redirect_stderr => 1, logger => $self->{logger} @@ -127,6 +133,96 @@ sub action_command { return 0; } +sub action_processcopy { + my ($self, %options) = @_; + + if (!defined($options{data}->{content}) || $options{data}->{content} eq '') { + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'no content' } + ); + return -1; + } + + my $cache_file = $options{data}->{content}->{cache_dir} . '/copy_' . $options{token}; + if ($options{data}->{content}->{status} eq "inprogress" && defined($options{data}->{content}->{chunk}->{data})) { + sysopen(FH, $cache_file, O_WRONLY|O_APPEND|O_CREAT); + binmode(FH); + syswrite( + FH, + MIME::Base64::decode_base64($options{data}->{content}->{chunk}->{data}), + $options{data}->{content}->{chunk}->{size} + ); + close FH; + + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_OK, + token => $options{token}, + data => { + message => "process copy inprogress", + } + ); + return 0; + } elsif ($options{data}->{content}->{status} eq "end" && defined($options{data}->{content}->{md5})) { + if ($options{data}->{content}->{md5} eq file_md5_hex($cache_file)) { + if ($options{data}->{content}->{type} eq "archive") { + my ($error, $stdout, $exit_code) = centreon::misc::misc::backtick( + command => "tar --no-overwrite-dir -zxf $cache_file -C '" . $options{data}->{content}->{destination} . "' .", + timeout => (defined($options{timeout})) ? $options{timeout} : 10, + wait_exit => 1, + redirect_stderr => 1, + ); + if ($error <= -1000) { + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => "untar failed: $stdout" } + ); + return -1; + } + if ($exit_code != 0) { + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => "untar failed ($exit_code): $stdout" } + ); + return -1; + } + } elsif ($options{data}->{content}->{type} eq "regular") { + copy( + $cache_file, + $options{data}->{content}->{destination} . '/' . $options{data}->{content}->{filename} + ); + } + } else { + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'md5 does not match' } + ); + return -1; + } + } + + # unlink($cache_file); + + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_OK, + token => $options{token}, + data => { + message => "process copy finished successfully", + } + ); + return 0; +} + sub action_run { my ($self, %options) = @_; @@ -192,9 +288,19 @@ sub event { $connector->{logger}->writeLogDebug("[action] -class- Event: $message"); if ($message !~ /^\[ACK\]/) { - $connector->create_child(message => $message); + $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; + + my ($action, $token) = ($1, $2); + my $data = JSON::XS->new->utf8->decode($3); + if (defined($data->{parameters}->{no_fork})) { + if ((my $method = $connector->can('action_' . lc($action)))) { + $method->($connector, token => $token, data => $data); + } + } else{ + $connector->create_child(message => $message); + } } - + last unless (centreon::gorgone::common::zmq_still_read(socket => $connector->{internal_socket})); } } diff --git a/gorgone/modules/core/action/hooks.pm b/gorgone/modules/core/action/hooks.pm index 69b6d19aada..a4447418454 100644 --- a/gorgone/modules/core/action/hooks.pm +++ b/gorgone/modules/core/action/hooks.pm @@ -29,6 +29,7 @@ use JSON::XS; my $NAME = 'action'; my $EVENTS = [ { event => 'ACTIONREADY' }, + { event => 'PROCESSCOPY' }, { event => 'COMMAND', uri => '/command', method => 'POST' }, ]; diff --git a/gorgone/modules/core/proxy/hooks.pm b/gorgone/modules/core/proxy/hooks.pm index e001cc1f99b..502f9ee265f 100644 --- a/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/modules/core/proxy/hooks.pm @@ -23,9 +23,14 @@ package modules::core::proxy::hooks; use warnings; use strict; use JSON::XS; +use centreon::misc::misc; use centreon::script::gorgonecore; use centreon::gorgone::common; use modules::core::proxy::class; +use File::Basename; +use MIME::Base64; +use Digest::MD5::File qw(file_md5_hex); +use Fcntl; my $NAME = 'proxy'; my $EVENTS = [ @@ -201,35 +206,57 @@ sub routing { $target = $register_subnodes->{$options{target}}; } - # Mode zmq pull - if ($register_nodes->{$target}->{type} eq 'pull') { - pull_request(%options, data_decoded => $data); - return undef; - } + my $action = $options{action}; + my $bulk_actions; + push @{$bulk_actions}, $data; - if (centreon::script::gorgonecore::waiting_ready_pool(pool => $pools) == 0) { - centreon::gorgone::common::add_history( + if ($options{action} eq 'REMOTECOPY') { + $action = 'PROCESSCOPY'; + $bulk_actions = prepare_remote_copy( dbh => $options{dbh}, - code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, - data => { message => 'proxy - still none ready' }, - json_encode => 1 + data => $data, + target => $options{target}, + token => $options{token}, + logger => $options{logger} ); - return undef; } - my $identity; - if (defined($nodes_pool->{$target})) { - $identity = $nodes_pool->{$target}; - } else { - $identity = rr_pool(); - $nodes_pool->{$target} = $identity; - } + foreach my $data (@{$bulk_actions}) { + # Mode zmq pull + if ($register_nodes->{$target}->{type} eq 'pull') { + pull_request(%options, data_decoded => $data); + next; + } + + if (centreon::script::gorgonecore::waiting_ready_pool(pool => $pools) == 0) { + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => centreon::gorgone::module::ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'proxy - still none ready' }, + json_encode => 1 + ); + next; + } - centreon::gorgone::common::zmq_send_message( - socket => $options{socket}, identity => 'gorgoneproxy-' . $identity, - action => $options{action}, data => $options{data}, token => $options{token}, - target => $options{target} - ); + my $identity; + if (defined($nodes_pool->{$target})) { + $identity = $nodes_pool->{$target}; + } else { + $identity = rr_pool(); + $nodes_pool->{$target} = $identity; + } + + centreon::gorgone::common::zmq_send_message( + socket => $options{socket}, + identity => 'gorgoneproxy-' . $identity, + action => $action, + data => $data, + token => $options{token}, + target => $options{target}, + json_encode => 1 + ); + } } sub gently { @@ -668,6 +695,127 @@ sub register_nodes { } } +sub prepare_remote_copy { + my (%options) = @_; + + my @actions; + + if (!defined($options{data}->{content}->{source}) || $options{data}->{content}->{source} eq '') { + $options{logger}->writeLogError('[proxy] -hooks- prepare_remote_copy: need source'); + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => centreon::gorgone::module::ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'remote copy failed' }, + json_encode => 1 + ); + return -1; + } + if (!defined($options{data}->{content}->{destination}) || $options{data}->{content}->{destination} eq '') { + $options{logger}->writeLogError('[proxy] -hooks- prepare_remote_copy: need destination'); + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => centreon::gorgone::module::ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'remote copy failed' }, + json_encode => 1 + ); + return -1; + } + + my $type; + my $filename; + my $localsrc = $options{data}->{content}->{source}; + my $src = $options{data}->{content}->{source}; + my $dst = $options{data}->{content}->{destination}; + + if (-f $options{data}->{content}->{source}) { + $type = 'regular'; + $localsrc = $src; + $filename = File::Basename::basename($src); + $filename = File::Basename::basename($dst) if ($dst !~ /\/$/); + } elsif (-d $options{data}->{content}->{source}) { + $type = 'archive'; + $filename = (defined($options{data}->{content}->{type}) ? $options{data}->{content}->{type} : 'tmp') . '-' . $options{target} . '.tar.gz'; + $localsrc = $options{data}->{content}->{cache_dir} . '/' . $filename; + + my ($error, $stdout, $exit_code) = centreon::misc::misc::backtick( + command => "tar -czf $localsrc -C '" . $src . "' .", + timeout => (defined($options{timeout})) ? $options{timeout} : 10, + wait_exit => 1, + redirect_stderr => 1, + ); + if ($error <= -1000) { + $options{logger}->writeLogError("[proxy] -hooks- prepare_remote_copy: tar failed: $stdout"); + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => centreon::gorgone::module::ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'tar failed' }, + json_encode => 1 + ); + return -1; + } + if ($exit_code != 0) { + $options{logger}->writeLogError("[proxy] -hooks- prepare_remote_copy: tar failed ($exit_code): $stdout"); + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => centreon::gorgone::module::ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'tar failed' }, + json_encode => 1 + ); + return -1; + }; + } else { + $options{logger}->writeLogError('[proxy] -hooks- prepare_remote_copy: unknown source'); + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => centreon::gorgone::module::ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'unknown source' }, + json_encode => 1 + ); + return -1; + } + + sysopen(FH, $localsrc, O_RDONLY); + binmode(FH); + my $buffer_size = (defined($config->{buffer_size})) ? $config->{buffer_size} : 500_000; + my $buffer; + while (my $bytes = sysread(FH, $buffer, $buffer_size)) { + push @actions, { content => { + status => 'inprogress', + type => $type, + chunk => { + data => MIME::Base64::encode_base64($buffer), + size => $bytes, + }, + md5 => undef, + destination => $dst, + filename => $filename, + cache_dir => $options{data}->{content}->{cache_dir}, + }, + parameters => { no_fork => 1 } + }; + } + close FH; + + push @actions, { content => { + status => 'end', + type => $type, + chunk => undef, + md5 => file_md5_hex($localsrc), + destination => $dst, + filename => $filename, + cache_dir => $options{data}->{content}->{cache_dir}, + }, + parameters => { no_fork => 1 } + }; + + return \@actions; +} + sub setcoreid { my (%options) = @_; From a4870ece4d687ee448722920671024f76905896c Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 17 Sep 2019 16:33:43 +0200 Subject: [PATCH 098/948] fix(core/action): remove comment --- gorgone/modules/core/action/class.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/modules/core/action/class.pm b/gorgone/modules/core/action/class.pm index 23d708cb782..e70e8943aab 100644 --- a/gorgone/modules/core/action/class.pm +++ b/gorgone/modules/core/action/class.pm @@ -210,7 +210,7 @@ sub action_processcopy { } } - # unlink($cache_file); + unlink($cache_file); $self->send_log( socket => $options{socket_log}, From ba79dd7028df7a46df7b7f32c90b6f9263434882 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 17 Sep 2019 16:45:18 +0200 Subject: [PATCH 099/948] enh(proxy/action): change destination for single file --- gorgone/modules/core/action/class.pm | 5 +---- gorgone/modules/core/proxy/hooks.pm | 4 +--- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/gorgone/modules/core/action/class.pm b/gorgone/modules/core/action/class.pm index e70e8943aab..31dd2af81c6 100644 --- a/gorgone/modules/core/action/class.pm +++ b/gorgone/modules/core/action/class.pm @@ -194,10 +194,7 @@ sub action_processcopy { return -1; } } elsif ($options{data}->{content}->{type} eq "regular") { - copy( - $cache_file, - $options{data}->{content}->{destination} . '/' . $options{data}->{content}->{filename} - ); + copy($cache_file, $options{data}->{content}->{destination}); } } else { $self->send_log( diff --git a/gorgone/modules/core/proxy/hooks.pm b/gorgone/modules/core/proxy/hooks.pm index 502f9ee265f..a9d3c6925f2 100644 --- a/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/modules/core/proxy/hooks.pm @@ -733,7 +733,7 @@ sub prepare_remote_copy { $type = 'regular'; $localsrc = $src; $filename = File::Basename::basename($src); - $filename = File::Basename::basename($dst) if ($dst !~ /\/$/); + $dst .= $filename if ($dst =~ /\/$/); } elsif (-d $options{data}->{content}->{source}) { $type = 'archive'; $filename = (defined($options{data}->{content}->{type}) ? $options{data}->{content}->{type} : 'tmp') . '-' . $options{target} . '.tar.gz'; @@ -793,7 +793,6 @@ sub prepare_remote_copy { }, md5 => undef, destination => $dst, - filename => $filename, cache_dir => $options{data}->{content}->{cache_dir}, }, parameters => { no_fork => 1 } @@ -807,7 +806,6 @@ sub prepare_remote_copy { chunk => undef, md5 => file_md5_hex($localsrc), destination => $dst, - filename => $filename, cache_dir => $options{data}->{content}->{cache_dir}, }, parameters => { no_fork => 1 } From 7bd6b80397134596695babe2f9a69331f3826fc9 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 18 Sep 2019 09:45:46 +0200 Subject: [PATCH 100/948] enh(register): use YAML for config files --- gorgone/centreon/gorgone/common.pm | 2 +- gorgone/config/gorgoned.yml | 4 +- gorgone/config/registernodes.json | 17 -------- gorgone/config/registernodes.yml | 15 +++++++ gorgone/config/registernodes2.json | 16 ------- gorgone/config/registernodes2.yml | 11 +++++ gorgone/modules/core/register/class.pm | 60 +++++++++++++++----------- 7 files changed, 63 insertions(+), 62 deletions(-) delete mode 100644 gorgone/config/registernodes.json create mode 100644 gorgone/config/registernodes.yml delete mode 100644 gorgone/config/registernodes2.json create mode 100644 gorgone/config/registernodes2.yml diff --git a/gorgone/centreon/gorgone/common.pm b/gorgone/centreon/gorgone/common.pm index 7cc13eea5b1..a7346e3e854 100644 --- a/gorgone/centreon/gorgone/common.pm +++ b/gorgone/centreon/gorgone/common.pm @@ -42,7 +42,7 @@ sub read_config { $config = LoadFile($options{config_file}); }; if ($@) { - $options{logger}->writeLogError("Parsinig extra config file error:"); + $options{logger}->writeLogError("Parsinig config file error:"); $options{logger}->writeLogError($@); exit(1); } diff --git a/gorgone/config/gorgoned.yml b/gorgone/config/gorgoned.yml index c91ef1cadf5..51c99ff07bb 100644 --- a/gorgone/config/gorgoned.yml +++ b/gorgone/config/gorgoned.yml @@ -48,7 +48,7 @@ modules: package: modules::core::httpserver::hooks enable: true address: 0.0.0.0 - port: 8080 + port: 8443 ssl: true ssl_cert_file: /etc/pki/tls/certs/server-cert.pem ssl_key_file: /etc/pki/tls/server-key.pem @@ -87,7 +87,7 @@ modules: - name: register package: modules::core::register::hooks enable: true - config_file: config/registernodes.json + config_file: config/registernodes.yml - name: engine package: modules::centreon::engine::hooks diff --git a/gorgone/config/registernodes.json b/gorgone/config/registernodes.json deleted file mode 100644 index 015c346450b..00000000000 --- a/gorgone/config/registernodes.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "nodes": [ - { "id": 100, "type": "push_ssh", "address": "10.30.2.49", "ssh_port": 22 }, - { - "id": 140, - "type": "push_zmq", - "address": "10.30.2.15", - "port": 5556, - "server_pubkey": "keys/poller/pubkey.crt", - "client_pubkey": "keys/central/pubkey.crt", - "client_privkey": "keys/central/privkey.pem", - "cipher": "Cipher::AES", - "keysize": 32, - "vector": "0123456789012345" - } - ] -} diff --git a/gorgone/config/registernodes.yml b/gorgone/config/registernodes.yml new file mode 100644 index 00000000000..04bd540bf5b --- /dev/null +++ b/gorgone/config/registernodes.yml @@ -0,0 +1,15 @@ +nodes: + - id: 100 + type: push_ssh + address: 10.30.2.49 + ssh_port: 22 + - id: 140 + type: push_zmq + address: 10.30.2.15 + port: 5556 + server_pubkey: keys/poller/pubkey.crt + client_pubkey: keys/central/pubkey.crt + client_privkey: keys/central/privkey.pem + cipher: "Cipher::AES" + keysize: 32 + vector: 0123456789012345 \ No newline at end of file diff --git a/gorgone/config/registernodes2.json b/gorgone/config/registernodes2.json deleted file mode 100644 index 9c2f4083be0..00000000000 --- a/gorgone/config/registernodes2.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "nodes": [ - { - "id": 150, - "type": "push_zmq", - "address": "10.30.2.15", - "port": 5557, - "server_pubkey": "keys/poller/pubkey.crt", - "client_pubkey": "keys/central/pubkey.crt", - "client_privkey": "keys/central/privkey.pem", - "cipher": "Cipher::AES", - "keysize": 32, - "vector": "0123456789012345" - } - ] -} diff --git a/gorgone/config/registernodes2.yml b/gorgone/config/registernodes2.yml new file mode 100644 index 00000000000..a3ee536d23c --- /dev/null +++ b/gorgone/config/registernodes2.yml @@ -0,0 +1,11 @@ +nodes: + - id: 150 + type: push_zmq + address: 10.30.2.15 + port: 5557 + server_pubkey: keys/poller/pubkey.crt + client_pubkey: keys/central/pubkey.crt + client_privkey: keys/central/privkey.pem + cipher: "Cipher::AES" + keysize: 32 + vector: 0123456789012345 \ No newline at end of file diff --git a/gorgone/modules/core/register/class.pm b/gorgone/modules/core/register/class.pm index 7191eb3959f..4598e9eb88a 100644 --- a/gorgone/modules/core/register/class.pm +++ b/gorgone/modules/core/register/class.pm @@ -86,32 +86,23 @@ sub action_registerresync { $options{token} = $self->generate_token() if (!defined($options{token})); - $self->send_log(code => centreon::gorgone::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action registerresync proceed' }); - - my $content = do { - local $/ = undef; - if (!open my $fh, "<", $self->{config}->{config_file}) { - $self->send_log(code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, data => { message => "Could not open file $self->{config}->{config_file}: $!" }); - $self->{logger}->writeLogError("[register] -class- Could not open file $self->{config}->{config_file}: $!"); - return 1; + $self->send_log( + code => centreon::gorgone::module::ACTION_BEGIN, + token => $options{token}, + data => { + message => 'action registerresync proceed' } - <$fh>; - }; - - my $data; - eval { - $data = JSON::XS->new->utf8->decode($content); - }; - if ($@) { - $self->send_log(code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, data => { message => "Cannot decode json file: $@" }); - $self->{logger}->writeLogError("[register] -class- Cannot decode json file: $@"); - return 1; - } + ); + + my $config = centreon::gorgone::common::read_config( + config_file => $self->{config}->{config_file}, + logger => $self->{logger} + ); my $register_temp = {}; my $register_nodes = []; - if (defined($data->{nodes})) { - foreach (@{$data->{nodes}}) { + if (defined($config->{nodes})) { + foreach (@{$config->{nodes}}) { $self->{register_nodes}->{$_->{id}} = 1; $register_temp->{$_->{id}} = 1; push @{$register_nodes}, { %$_ }; @@ -126,11 +117,27 @@ sub action_registerresync { } } - $self->send_internal_action(action => 'REGISTERNODES', data => { nodes => $register_nodes } ) if (scalar(@$register_nodes) > 0); - $self->send_internal_action(action => 'UNREGISTERNODES', data => { nodes => $unregister_nodes } ) if (scalar(@$unregister_nodes) > 0); + $self->send_internal_action( + action => 'REGISTERNODES', + data => { + nodes => $register_nodes + } + ) if (scalar(@$register_nodes) > 0); + $self->send_internal_action( + action => 'UNREGISTERNODES', + data => { + nodes => $unregister_nodes + } + ) if (scalar(@$unregister_nodes) > 0); $self->{logger}->writeLogDebug("[register] -class- finish resync"); - $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => { message => 'action registerresync finished' }); + $self->send_log( + code => $self->ACTION_FINISH_OK, + token => $options{token}, + data => { + message => 'action registerresync finished' + } + ); return 0; } @@ -165,7 +172,8 @@ sub run { ); centreon::gorgone::common::zmq_send_message( socket => $connector->{internal_socket}, - action => 'REGISTERREADY', data => { }, + action => 'REGISTERREADY', + data => {}, json_encode => 1 ); $self->{poll} = [ From 45b4c6822c667ec4f1d858a144301282f50e9b8b Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 18 Sep 2019 09:47:53 +0200 Subject: [PATCH 101/948] fix(common): fix typo --- gorgone/centreon/gorgone/common.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/centreon/gorgone/common.pm b/gorgone/centreon/gorgone/common.pm index a7346e3e854..d240a033429 100644 --- a/gorgone/centreon/gorgone/common.pm +++ b/gorgone/centreon/gorgone/common.pm @@ -42,7 +42,7 @@ sub read_config { $config = LoadFile($options{config_file}); }; if ($@) { - $options{logger}->writeLogError("Parsinig config file error:"); + $options{logger}->writeLogError("Parsing config file error:"); $options{logger}->writeLogError($@); exit(1); } From 08155beec6473ec8e6a886377dbc22eb5c8e4d03 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 18 Sep 2019 11:06:56 +0200 Subject: [PATCH 102/948] enh(legacycmd): add SYNCTRAP handling --- gorgone/config/gorgoned.yml | 1 + gorgone/modules/centreon/legacycmd/class.pm | 43 ++++++++++++++--- gorgone/modules/core/proxy/class.pm | 52 +++++++++++++++------ gorgone/modules/core/proxy/sshclient.pm | 9 ++++ 4 files changed, 85 insertions(+), 20 deletions(-) diff --git a/gorgone/config/gorgoned.yml b/gorgone/config/gorgoned.yml index 51c99ff07bb..fe3ce7b9d06 100644 --- a/gorgone/config/gorgoned.yml +++ b/gorgone/config/gorgoned.yml @@ -99,6 +99,7 @@ modules: enable: true cmd_file: "/var/lib/centreon/centcore.cmd" cache_dir: "/var/cache/centreon/" + cache_dir_trap: "/etc/snmp/centreon_traps/" remote_dir: "/var/lib/centreon/remote-data/" - name: scom diff --git a/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/modules/centreon/legacycmd/class.pm index 05c51a424d4..c1797846f3d 100644 --- a/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/modules/centreon/legacycmd/class.pm @@ -121,7 +121,10 @@ sub get_pollers_config { $self->{pollers} = {}; my ($status, $datas) = $self->{class_object_centreon}->custom_execute( - request => 'SELECT nagios_server_id, command_file, cfg_dir, centreonbroker_cfg_path FROM cfg_nagios JOIN nagios_server WHERE id = nagios_server_id', + request => 'SELECT nagios_server_id, command_file, cfg_dir, centreonbroker_cfg_path, snmp_trapd_path_conf ' . + 'FROM cfg_nagios ' . + 'JOIN nagios_server ' . + 'WHERE id = nagios_server_id', mode => 2 ); if ($status == -1 || !defined($datas->[0])) { @@ -133,6 +136,7 @@ sub get_pollers_config { $self->{pollers}->{$_->[0]}->{command_file} = $_->[1]; $self->{pollers}->{$_->[0]}->{cfg_dir} = $_->[2]; $self->{pollers}->{$_->[0]}->{centreonbroker_cfg_path} = $_->[3]; + $self->{pollers}->{$_->[0]}->{snmp_trapd_path_conf} = $_->[4]; } return 0; @@ -157,7 +161,8 @@ sub execute_cmd { }, ); } elsif ($options{cmd} eq 'SENDCFGFILE') { - my $cache_dir = (defined($connector->{config}->{cache_dir})) ? $connector->{config}->{cache_dir} : '/var/cache/centreon'; + my $cache_dir = (defined($connector->{config}->{cache_dir})) ? + $connector->{config}->{cache_dir} : '/var/cache/centreon'; # engine $self->send_internal_action( action => 'REMOTECOPY', @@ -187,8 +192,10 @@ sub execute_cmd { }, ); } elsif ($options{cmd} eq 'SENDEXPORTFILE') { - my $cache_dir = (defined($connector->{config}->{cache_dir})) ? $connector->{config}->{cache_dir} : '/var/cache/centreon'; - my $remote_dir = (defined($connector->{config}->{remote_dir})) ? $connector->{config}->{remote_dir} : '/var/lib/centreon/remote-data/'; + my $cache_dir = (defined($connector->{config}->{cache_dir})) ? + $connector->{config}->{cache_dir} : '/var/cache/centreon'; + my $remote_dir = (defined($connector->{config}->{remote_dir})) ? + $connector->{config}->{remote_dir} : '/var/lib/centreon/remote-data/'; # remote server $self->send_internal_action( action => 'REMOTECOPY', @@ -203,6 +210,25 @@ sub execute_cmd { } }, ); + } elsif ($options{cmd} eq 'SYNCTRAP') { + my $cache_dir = (defined($connector->{config}->{cache_dir})) ? + $connector->{config}->{cache_dir} : '/var/cache/centreon'; + my $cache_dir_trap = (defined($connector->{config}->{cache_dir_trap})) ? + $connector->{config}->{cache_dir_trap} : '/etc/snmp/centreon_traps/'; + # centreontrapd + $self->send_internal_action( + action => 'REMOTECOPY', + target => $options{target}, + token => $self->generate_token(), + data => { + content => { + source => $cache_dir_trap . '/' . $options{target} . '/centreontrapd.sdb', + destination => $self->{pollers}->{$options{target}}->{snmp_trapd_path_conf} . '/', + cache_dir => $cache_dir, + type => 'trap', + } + }, + ); } } @@ -252,7 +278,9 @@ sub handle_centcore_dir { $self->{logger}->writeLogError("[legacycmd] -class- cant opendir '" . $self->{config}->{cmd_dir} . "': $!"); return ; } - @files = sort { (stat($self->{config}->{cmd_dir} . '/' . $a))[10] <=> (stat($self->{config}->{cmd_dir} . '/' . $b))[10] } (readdir($dh)); + @files = sort { + (stat($self->{config}->{cmd_dir} . '/' . $a))[10] <=> (stat($self->{config}->{cmd_dir} . '/' . $b))[10] + } (readdir($dh)); closedir($dh); my ($code, $handle); @@ -325,7 +353,10 @@ sub run { force => 2, logger => $self->{logger} ); - $self->{class_object_centreon} = centreon::misc::objects::object->new(logger => $self->{logger}, db_centreon => $self->{db_centreon}); + $self->{class_object_centreon} = centreon::misc::objects::object->new( + logger => $self->{logger}, + db_centreon => $self->{db_centreon} + ); $self->{poll} = [ { diff --git a/gorgone/modules/core/proxy/class.pm b/gorgone/modules/core/proxy/class.pm index 21d6e20f375..cd84c8e707b 100644 --- a/gorgone/modules/core/proxy/class.pm +++ b/gorgone/modules/core/proxy/class.pm @@ -150,10 +150,12 @@ sub connect { server_pubkey => $self->{clients}->{$options{id}}->{server_pubkey}, client_pubkey => $self->{clients}->{$options{id}}->{client_pubkey}, client_privkey => $self->{clients}->{$options{id}}->{client_privkey}, - target_type => - defined($self->{clients}->{$options{id}}->{target_type}) ? $self->{clients}->{$options{id}}->{target_type} : 'tcp', - target_path => - defined($self->{clients}->{$options{id}}->{target_path}) ? $self->{clients}->{$options{id}}->{target_path} : $self->{clients}->{$options{id}}->{address} . ':' . $self->{clients}->{$options{id}}->{port}, + target_type => defined($self->{clients}->{$options{id}}->{target_type}) ? + $self->{clients}->{$options{id}}->{target_type} : + 'tcp', + target_path => defined($self->{clients}->{$options{id}}->{target_path}) ? + $self->{clients}->{$options{id}}->{target_path} : + $self->{clients}->{$options{id}}->{address} . ':' . $self->{clients}->{$options{id}}->{port}, logger => $self->{logger}, ); $self->{clients}->{$options{id}}->{class}->init(callback => \&read_message); @@ -191,8 +193,7 @@ sub action_proxyaddnode { $self->{subnodes}->{$_} = $data->{id}; } foreach (keys %{$self->{subnodes}}) { - delete $self->{subnodes}->{$_} - if ($self->{subnodes}->{$_} eq $data->{id} && !defined($temp->{$_})); + delete $self->{subnodes}->{$_} if ($self->{subnodes}->{$_} eq $data->{id} && !defined($temp->{$_})); } } @@ -207,8 +208,7 @@ sub action_proxydelnode { } foreach (keys %{$self->{subnodes}}) { - delete $self->{subnodes}->{$_} - if ($self->{subnodes}->{$_} eq $data->{id}); + delete $self->{subnodes}->{$_} if ($self->{subnodes}->{$_} eq $data->{id}); } } @@ -233,7 +233,9 @@ sub proxy { return undef; } my ($action, $token, $target, $data) = ($1, $2, $3, $4); - $connector->{logger}->writeLogDebug("[proxy] -class- Send message: [action = $action] [token = $token] [target = $target] [data = $data]"); + $connector->{logger}->writeLogDebug( + "[proxy] -class- Send message: [action = $action] [token = $token] [target = $target] [data = $data]" + ); if ($action eq 'PROXYADDNODE') { action_proxyaddnode($connector, data => $data); @@ -253,7 +255,13 @@ sub proxy { } if (!defined($connector->{clients}->{$target_client}->{class})) { if ($connector->connect(id => $target_client) != 0) { - $connector->send_log(code => centreon::gorgone::module::ACTION_FINISH_KO, token => $token, data => { message => "cannot connect on target node '$target_client'" }); + $connector->send_log( + code => centreon::gorgone::module::ACTION_FINISH_KO, + token => $token, + data => { + message => "cannot connect on target node '$target_client'" + } + ); return ; } } @@ -266,7 +274,13 @@ sub proxy { data => $data ); if ($status != 0) { - $connector->send_log(code => centreon::gorgone::module::ACTION_FINISH_KO, token => $token, data => { message => "Send message problem for '$target': $msg" }); + $connector->send_log( + code => centreon::gorgone::module::ACTION_FINISH_KO, + token => $token, + data => { + message => "Send message problem for '$target': $msg" + } + ); $connector->{logger}->writeLogError("[proxy] -class- Send message problem for '$target': $msg"); $connector->{clients}->{$target}->{delete} = 1; } @@ -296,9 +310,17 @@ sub proxy { $connector->{logger}->writeLogDebug("[proxy] -class- sshclient return: [message = $data_ret->{message}]"); if ($status == 0) { - $connector->send_log(code => centreon::gorgone::module::ACTION_FINISH_OK, token => $token, data => $data_ret); + $connector->send_log( + code => centreon::gorgone::module::ACTION_FINISH_OK, + token => $token, + data => $data_ret + ); } else { - $connector->send_log(code => centreon::gorgone::module::ACTION_FINISH_KO, token => $token, data => $data_ret); + $connector->send_log( + code => centreon::gorgone::module::ACTION_FINISH_KO, + token => $token, + data => $data_ret + ); } } } @@ -326,7 +348,9 @@ sub run { centreon::gorgone::common::zmq_send_message( socket => $self->{internal_socket}, action => 'PROXYREADY', - data => { pool_id => $self->{pool_id} }, + data => { + pool_id => $self->{pool_id} + }, json_encode => 1 ); my $poll = { diff --git a/gorgone/modules/core/proxy/sshclient.pm b/gorgone/modules/core/proxy/sshclient.pm index 5438df1c148..9f264018683 100644 --- a/gorgone/modules/core/proxy/sshclient.pm +++ b/gorgone/modules/core/proxy/sshclient.pm @@ -288,6 +288,15 @@ sub action_remotecopy { } } ); + } elsif ($options{data}->{content}->{type} eq 'trap') { + $self->action_centcore( + data => { + content => { + command => 'SYNCTRAP', + target => $options{target}, + } + } + ); } } From cc32872125207d20a22a4fcb7ee0beb7155618ee Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 18 Sep 2019 14:12:06 +0200 Subject: [PATCH 103/948] fix(proxy): fix GETLOG handling --- gorgone/modules/core/proxy/hooks.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/modules/core/proxy/hooks.pm b/gorgone/modules/core/proxy/hooks.pm index a9d3c6925f2..fabe112f1a6 100644 --- a/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/modules/core/proxy/hooks.pm @@ -194,7 +194,7 @@ sub routing { # We put the good time to get my $ctime = $synctime_nodes->{$options{target}}->{ctime}; my $last_id = $synctime_nodes->{$options{target}}->{last_id}; - $options{data} = centreon::gorgone::common::json_encode(data => { ctime => $ctime, id => $last_id }); + $data = { ctime => $ctime, id => $last_id }; $synctime_nodes->{$options{target}}->{in_progress} = 1; $synctime_nodes->{$options{target}}->{in_progress_time} = time(); } From a25b3c922136fe64fcb51ad8da42b011aa015d14 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 18 Sep 2019 17:43:59 +0200 Subject: [PATCH 104/948] fix(module): add token to send_internal_action --- gorgone/centreon/gorgone/module.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/gorgone/centreon/gorgone/module.pm b/gorgone/centreon/gorgone/module.pm index f7aad6d9e16..aa82bb5a733 100644 --- a/gorgone/centreon/gorgone/module.pm +++ b/gorgone/centreon/gorgone/module.pm @@ -42,6 +42,7 @@ sub send_internal_action { centreon::gorgone::common::zmq_send_message( socket => $self->{internal_socket}, + token => $options{token}, action => $options{action}, target => $options{target}, data => $options{data}, From 821fec7e1a28d5d5f3ee7900d4c569ae75524ebb Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 18 Sep 2019 17:46:17 +0200 Subject: [PATCH 105/948] enh(core/cron): open cron definition to any action --- gorgone/config/gorgoned.yml | 5 ++++- gorgone/modules/core/cron/class.pm | 7 ++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/gorgone/config/gorgoned.yml b/gorgone/config/gorgoned.yml index fe3ce7b9d06..e77a93fe680 100644 --- a/gorgone/config/gorgoned.yml +++ b/gorgone/config/gorgoned.yml @@ -62,7 +62,10 @@ modules: cron: - id: echo_date timespec: "* * * * *" - command_line: "date >> /tmp/date.log" + action: COMMAND + parameters: + command: "date >> /tmp/date.log" + timeout: 10 - name: action package: modules::core::action::hooks diff --git a/gorgone/modules/core/cron/class.pm b/gorgone/modules/core/cron/class.pm index a6b000501fe..c5e83a3944f 100644 --- a/gorgone/modules/core/cron/class.pm +++ b/gorgone/modules/core/cron/class.pm @@ -171,7 +171,7 @@ sub action_addcron { foreach my $definition (@{$options{data}->{content}}) { if (!defined($definition->{timespec}) || $definition->{timespec} eq '' || - !defined($definition->{command_line}) || $definition->{command_line} eq '' || + !defined($definition->{action}) || $definition->{action} eq '' || !defined($definition->{id}) || $definition->{id} eq '') { $self->{logger}->writeLogDebug("[cron] -class- Cron add missing arguments"); $self->send_log( @@ -194,6 +194,7 @@ sub action_addcron { ); next; } + $self->{logger}->writeLogInfo("[cron] -class- Adding cron definition '" . $definition->{id} . "'"); $self->{cron}->add_entry( $definition->{timespec}, $definition->{id}, @@ -412,10 +413,10 @@ sub dispatcher { centreon::gorgone::common::zmq_send_message( socket => $options->{socket}, - action => 'COMMAND', + action => $options->{definition}->{action}, target => $options->{definition}->{target}, data => { - content => { command => $options->{definition}->{command_line}, timeout => $options->{definition}->{timeout} } + content => { %{$options->{definition}->{parameters}} } }, json_encode => 1 ); From 68f71002951f20f6d96d213414e3d122cc2eb2b5 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 18 Sep 2019 17:47:43 +0200 Subject: [PATCH 106/948] enh(core/action): add metadata to return message --- gorgone/modules/core/action/class.pm | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/gorgone/modules/core/action/class.pm b/gorgone/modules/core/action/class.pm index 31dd2af81c6..25a43548dcd 100644 --- a/gorgone/modules/core/action/class.pm +++ b/gorgone/modules/core/action/class.pm @@ -95,7 +95,10 @@ sub action_command { socket => $options{socket_log}, code => $self->ACTION_FINISH_KO, token => $options{token}, - data => { message => 'need command argument' } + data => { + message => 'need command argument', + metadata => $options{data}->{content}->{metadata} + } ); return -1; } @@ -114,7 +117,10 @@ sub action_command { socket => $options{socket_log}, code => $self->ACTION_FINISH_KO, token => $options{token}, - data => { message => "command '$options{data}->{content}->{command}' execution issue: $stdout" } + data => { + message => "command '$options{data}->{content}->{command}' execution issue: $stdout", + metadata => $options{data}->{content}->{metadata} + } ); return -1; } @@ -126,7 +132,8 @@ sub action_command { data => { message => "command '$options{data}->{content}->{command}' has finished", stdout => $stdout, - exit_code => $return_code + exit_code => $return_code, + metadata => $options{data}->{content}->{metadata} } ); @@ -250,7 +257,7 @@ sub action_run { sub create_child { my ($self, %options) = @_; - $self->{logger}->writeLogInfo("[action] -class- create sub-process"); + $self->{logger}->writeLogDebug("[action] -class- Create sub-process"); $options{message} =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; my ($action, $token) = ($1, $2); From 49d9554666460cdf77003ef1a50ab875bf33f0af Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 18 Sep 2019 17:49:09 +0200 Subject: [PATCH 107/948] feat(centreon/broker): add broker stats collections --- gorgone/config/gorgoned.yml | 11 + gorgone/modules/centreon/broker/class.pm | 279 +++++++++++++++++++++++ gorgone/modules/centreon/broker/hooks.pm | 163 +++++++++++++ 3 files changed, 453 insertions(+) create mode 100644 gorgone/modules/centreon/broker/class.pm create mode 100644 gorgone/modules/centreon/broker/hooks.pm diff --git a/gorgone/config/gorgoned.yml b/gorgone/config/gorgoned.yml index e77a93fe680..735d32a406d 100644 --- a/gorgone/config/gorgoned.yml +++ b/gorgone/config/gorgoned.yml @@ -97,6 +97,17 @@ modules: enable: true command_file: "/var/lib/centreon-engine/rw/centengine.cmd" + - name: broker + package: modules::centreon::broker::hooks + enable: false + cache_dir: "/var/lib/centreon/broker-stats/" + cron: + - id: broker_stats + timespec: "*/2 * * * *" + action: BROKERSTATS + parameters: + timeout: 10 + - name: legacycmd package: modules::centreon::legacycmd::hooks enable: true diff --git a/gorgone/modules/centreon/broker/class.pm b/gorgone/modules/centreon/broker/class.pm new file mode 100644 index 00000000000..101af2ce01e --- /dev/null +++ b/gorgone/modules/centreon/broker/class.pm @@ -0,0 +1,279 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package modules::centreon::broker::class; + +use base qw(centreon::gorgone::module); + +use strict; +use warnings; +use centreon::gorgone::common; +use centreon::misc::objects::object; +use ZMQ::LibZMQ4; +use ZMQ::Constants qw(:all); +use JSON::XS; +use Time::HiRes; + +my $result; +my %handlers = (TERM => {}, HUP => {}); +my ($connector); + +sub new { + my ($class, %options) = @_; + + $connector = {}; + $connector->{internal_socket} = undef; + $connector->{module_id} = $options{module_id}; + $connector->{logger} = $options{logger}; + $connector->{config} = $options{config}; + $connector->{config_core} = $options{config_core}; + $connector->{config_db_centreon} = $options{config_db_centreon}; + $connector->{stop} = 0; + + bless $connector, $class; + $connector->set_signal_handlers(); + return $connector; +} + +sub set_signal_handlers { + my $self = shift; + + $SIG{TERM} = \&class_handle_TERM; + $handlers{TERM}->{$self} = sub { $self->handle_TERM() }; + $SIG{HUP} = \&class_handle_HUP; + $handlers{HUP}->{$self} = sub { $self->handle_HUP() }; +} + +sub handle_HUP { + my $self = shift; + $self->{reload} = 0; +} + +sub handle_TERM { + my $self = shift; + $self->{logger}->writeLogInfo("[broker] -class- $$ Receiving order to stop..."); + $self->{stop} = 1; +} + +sub class_handle_TERM { + foreach (keys %{$handlers{TERM}}) { + &{$handlers{TERM}->{$_}}(); + } +} + +sub class_handle_HUP { + foreach (keys %{$handlers{HUP}}) { + &{$handlers{HUP}->{$_}}(); + } +} + +sub action_brokerstats { + my ($self, %options) = @_; + + $options{token} = $self->generate_token() if (!defined($options{token})); + + $self->send_log( + code => centreon::gorgone::module::ACTION_BEGIN, + token => $options{token}, + data => { + message => 'action brokerstats starting' + } + ); + + my $request = "SELECT id, cache_directory, config_name FROM cfg_centreonbroker " . + "JOIN nagios_server " . + "WHERE ns_activate = '1' AND stats_activate = '1' AND ns_nagios_server = id"; + + if (defined($options{data}->{variables}[0]) && $options{data}->{variables}[0] =~ /\d+/) { + $request .= " AND id = '" . $options{data}->{variables}[0] . "'"; + } + + my ($status, $datas) = $self->{class_object}->custom_execute(request => $request, mode => 2); + if ($status == -1) { + $self->send_log( + code => centreon::gorgone::module::ACTION_FINISH_KO, + token => $options{token}, + data => { + message => 'cannot find configuration' + } + ); + $self->{logger}->writeLogError("[broker] -class- Cannot find configuration"); + return 1; + } + + my %targets; + foreach (@{$datas}) { + my $target = $_->[0]; + my $statistics_file = $_->[1] . "/" . $_->[2] . "-stats.json"; + $self->{logger}->writeLogInfo( + "[broker] -class- Collecting file '" . $statistics_file . "' on target '" . $target . "'" + ); + $self->send_internal_action( + target => $target, + action => 'COMMAND', + token => $options{token}, + data => { + content => { + command => 'cat ' . $statistics_file, + timeout => 10, + metadata => { + poller_id => $target, + config_name => $_->[2], + } + } + } + ); + $targets{$target} = 1; + } + + my $wait = (defined($self->{config}->{command_wait})) ? $self->{config}->{command_wait} : 1_000_000; + Time::HiRes::usleep($wait); + + foreach my $target (keys %targets) { + $self->send_internal_action( + target => $target, + action => 'GETLOG', + ); + } + + $wait = (defined($self->{config}->{sync_wait})) ? $self->{config}->{sync_wait} : 1_000_000; + Time::HiRes::usleep($wait); + + $self->send_log( + code => $self->ACTION_FINISH_OK, + token => $options{token}, + data => { + message => 'action brokerstats finished' + } + ); + + $self->send_internal_action( + action => 'GETLOG', + data => { + token => $options{token} + } + ); + return 0; +} + +sub write_stats { + my ($self, %options) = @_; + + return if (!defined($options{data}->{data}->{action}) || $options{data}->{data}->{action} ne "getlog" && + defined($options{data}->{data}->{result})); + + foreach my $id (sort keys %{$options{data}->{data}->{result}}) { + my $data = JSON::XS->new->utf8->decode($options{data}->{data}->{result}->{$id}->{data}); + next if (!defined($data->{exit_code}) || $data->{exit_code} != 0); + my $stats = JSON::XS->new->utf8->decode($data->{stdout}); + + my $cache_file = $self->{config}->{cache_dir} . '/' . $data->{metadata}->{poller_id} . '-' . + $data->{metadata}->{config_name} . '.dat'; + $self->{logger}->writeLogDebug("[broker] -class- Writing file '" . $cache_file . "'"); + open(FH, '>', $cache_file); + print FH $data->{stdout}; + close(FH); + } +} + +sub event { + while (1) { + my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $connector->{internal_socket}); + + $connector->{logger}->writeLogDebug("[broker] -class- Event: $message"); + if ($message =~ /^\[ACK\]\s+\[(.*?)\]\s+(.*)$/m) { + my $token = $1; + my $data = JSON::XS->new->utf8->decode($2); + my $method = $connector->can('write_stats'); + $method->($connector, data => $data); + } else { + $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; + if ((my $method = $connector->can('action_' . lc($1)))) { + $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; + my ($action, $token) = ($1, $2); + my $data = JSON::XS->new->utf8->decode($3); + $method->($connector, token => $token, data => $data); + } + } + + last unless (centreon::gorgone::common::zmq_still_read(socket => $connector->{internal_socket})); + } +} + +sub run { + my ($self, %options) = @_; + + # Database creation. We stay in the loop still there is an error + $self->{db_centreon} = centreon::misc::db->new( + dsn => $self->{config_db_centreon}->{dsn}, + user => $self->{config_db_centreon}->{username}, + password => $self->{config_db_centreon}->{password}, + force => 2, + logger => $self->{logger} + ); + ##### Load objects ##### + $self->{class_object} = centreon::misc::objects::object->new( + logger => $self->{logger}, + db_centreon => $self->{db_centreon} + ); + + # Connect internal + $connector->{internal_socket} = centreon::gorgone::common::connect_com( + zmq_type => 'ZMQ_DEALER', + name => 'gorgonebroker', + logger => $self->{logger}, + type => $self->{config_core}->{internal_com_type}, + path => $self->{config_core}->{internal_com_path} + ); + centreon::gorgone::common::zmq_send_message( + socket => $connector->{internal_socket}, + action => 'BROKERREADY', + data => {}, + json_encode => 1 + ); + $self->{poll} = [ + { + socket => $connector->{internal_socket}, + events => ZMQ_POLLIN, + callback => \&event, + } + ]; + + if (defined($self->{config}->{cron})) { + $self->send_internal_action( + action => 'ADDCRON', + data => { + content => $self->{config}->{cron}, + } + ); + } + + while (1) { + # we try to do all we can + my $rev = zmq_poll($self->{poll}, 5000); + if (defined($rev) && $rev == 0 && $self->{stop} == 1) { + $self->{logger}->writeLogInfo("[broker] -class- $$ has quit"); + zmq_close($connector->{internal_socket}); + exit(0); + } + } +} + +1; diff --git a/gorgone/modules/centreon/broker/hooks.pm b/gorgone/modules/centreon/broker/hooks.pm new file mode 100644 index 00000000000..42a36ff1e1d --- /dev/null +++ b/gorgone/modules/centreon/broker/hooks.pm @@ -0,0 +1,163 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package modules::centreon::broker::hooks; + +use warnings; +use strict; +use JSON::XS; +use centreon::script::gorgonecore; +use modules::centreon::broker::class; + +my $NAME = 'broker'; +my $EVENTS = [ + { event => 'BROKERREADY' }, + { event => 'BROKERSTATS', uri => '/statistics', method => 'GET' }, +]; + +my $config_core; +my $config; +my $config_db_centreon; +my $broker = {}; +my $stop = 0; + +sub register { + my (%options) = @_; + + $config = $options{config}; + $config_core = $options{config_core}; + $config_db_centreon = $options{config_db_centreon}; + return ($NAME, $EVENTS); +} + +sub init { + my (%options) = @_; + + create_child(logger => $options{logger}); +} + +sub routing { + my (%options) = @_; + + my $data; + eval { + $data = JSON::XS->new->utf8->decode($options{data}); + }; + if ($@) { + $options{logger}->writeLogError("[broker] -hooks- Cannot decode json data: $@"); + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => 30, token => $options{token}, + data => { msg => 'gorgonebroker: cannot decode json' }, + json_encode => 1 + ); + return undef; + } + + if ($options{action} eq 'BROKERREADY') { + $broker->{ready} = 1; + return undef; + } + + if (centreon::script::gorgonecore::waiting_ready(ready => \$broker->{ready}) == 0) { + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => 30, token => $options{token}, + data => { msg => 'gorgonebroker: still no ready' }, + json_encode => 1 + ); + return undef; + } + + centreon::gorgone::common::zmq_send_message( + socket => $options{socket}, + identity => 'gorgonebroker', + action => $options{action}, + data => $options{data}, + token => $options{token}, + ); +} + +sub gently { + my (%options) = @_; + + $stop = 1; + $options{logger}->writeLogInfo("[broker] -hooks- Send TERM signal"); + if ($broker->{running} == 1) { + CORE::kill('TERM', $broker->{pid}); + } +} + +sub kill { + my (%options) = @_; + + if ($broker->{running} == 1) { + $options{logger}->writeLogInfo("[broker] -hooks- Send KILL signal for pool"); + CORE::kill('KILL', $broker->{pid}); + } +} + +sub kill_internal { + my (%options) = @_; + +} + +sub check { + my (%options) = @_; + + my $count = 0; + foreach my $pid (keys %{$options{dead_childs}}) { + # Not me + next if ($broker->{pid} != $pid); + + $broker = {}; + delete $options{dead_childs}->{$pid}; + if ($stop == 0) { + create_child(logger => $options{logger}); + } + } + + $count++ if (defined($broker->{running}) && $broker->{running} == 1); + + return $count; +} + +# Specific functions +sub create_child { + my (%options) = @_; + + $options{logger}->writeLogInfo("[broker] -hooks- Create module 'broker' process"); + my $child_pid = fork(); + if ($child_pid == 0) { + $0 = 'gorgone-broker'; + my $module = modules::centreon::broker::class->new( + logger => $options{logger}, + config_core => $config_core, + config => $config, + config_db_centreon => $config_db_centreon, + ); + $module->run(); + exit(0); + } + $options{logger}->writeLogInfo("[broker] -hooks- PID $child_pid (gorgone-broker)"); + $broker = { pid => $child_pid, ready => 0, running => 1 }; +} + +1; From 130d7d29566eb86fa4f48eab19410d54f1c83ebd Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 18 Sep 2019 17:59:08 +0200 Subject: [PATCH 108/948] enh(centreon/broker): harden write to file --- gorgone/modules/centreon/broker/class.pm | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/gorgone/modules/centreon/broker/class.pm b/gorgone/modules/centreon/broker/class.pm index 101af2ce01e..fe018fd29df 100644 --- a/gorgone/modules/centreon/broker/class.pm +++ b/gorgone/modules/centreon/broker/class.pm @@ -179,12 +179,14 @@ sub write_stats { return if (!defined($options{data}->{data}->{action}) || $options{data}->{data}->{action} ne "getlog" && defined($options{data}->{data}->{result})); + my $cache_dir = (defined($self->{config}->{cache_dir})) ? + $self->{config}->{cache_dir} : '/var/lib/centreon/broker-stats/'; foreach my $id (sort keys %{$options{data}->{data}->{result}}) { my $data = JSON::XS->new->utf8->decode($options{data}->{data}->{result}->{$id}->{data}); - next if (!defined($data->{exit_code}) || $data->{exit_code} != 0); - my $stats = JSON::XS->new->utf8->decode($data->{stdout}); + next if (!defined($data->{exit_code}) || $data->{exit_code} != 0 || + !defined($data->{metadata}->{poller_id}) || !defined($data->{metadata}->{config_name})); - my $cache_file = $self->{config}->{cache_dir} . '/' . $data->{metadata}->{poller_id} . '-' . + my $cache_file = $cache_dir . '/' . $data->{metadata}->{poller_id} . '-' . $data->{metadata}->{config_name} . '.dat'; $self->{logger}->writeLogDebug("[broker] -class- Writing file '" . $cache_file . "'"); open(FH, '>', $cache_file); From 2dcd2324ac698e19b0eb91a355a41f5b270f68cb Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Thu, 19 Sep 2019 12:10:42 +0200 Subject: [PATCH 109/948] enh(modules): add namespace, use constant --- gorgone/centreon/gorgone/api.pm | 9 +++++---- gorgone/centreon/script/gorgonecore.pm | 13 ++++++++++--- gorgone/modules/centreon/broker/hooks.pm | 7 ++++--- gorgone/modules/centreon/engine/hooks.pm | 7 ++++--- gorgone/modules/centreon/legacycmd/hooks.pm | 7 ++++--- gorgone/modules/centreon/pollers/hooks.pm | 7 ++++--- gorgone/modules/core/action/hooks.pm | 7 ++++--- gorgone/modules/core/cron/hooks.pm | 7 ++++--- gorgone/modules/core/httpserver/hooks.pm | 7 ++++--- gorgone/modules/core/proxy/hooks.pm | 9 +++++---- gorgone/modules/core/pull/hooks.pm | 3 ++- gorgone/modules/core/register/hooks.pm | 7 ++++--- gorgone/modules/plugins/newtest/hooks.pm | 7 ++++--- gorgone/modules/plugins/scom/hooks.pm | 7 ++++--- 14 files changed, 62 insertions(+), 42 deletions(-) diff --git a/gorgone/centreon/gorgone/api.pm b/gorgone/centreon/gorgone/api.pm index 7b7bb8f71ae..416bee7b852 100644 --- a/gorgone/centreon/gorgone/api.pm +++ b/gorgone/centreon/gorgone/api.pm @@ -39,7 +39,8 @@ sub root { my %dispatch; foreach my $action (keys $options{modules_events}) { next if (!defined($options{modules_events}->{$action}->{api}->{uri})); - $dispatch{$options{modules_events}->{$action}->{module}->{name} . '_' . + $dispatch{$options{modules_events}->{$action}->{module}->{namespace} . '_' . + $options{modules_events}->{$action}->{module}->{name} . '_' . $options{modules_events}->{$action}->{api}->{method} . '_' . $options{modules_events}->{$action}->{api}->{uri}} = $action; } @@ -52,12 +53,12 @@ sub root { token => $3, refresh => (defined($options{parameters}->{refresh})) ? $options{parameters}->{refresh} : undef ); - } elsif ($options{uri} =~ /^\/api\/(targets\/(\w*)\/)?(\w+)\/(\w+)\/?([\w\/]*?)$/ - && defined($dispatch{$3 . '_' . $options{method} . '_/' . $4})) { + } elsif ($options{uri} =~ /^\/api\/(targets\/(\w*)\/)?(\w+)\/(\w+)\/(\w+)\/?([\w\/]*?)$/ + && defined($dispatch{$3 . '_' . $4 . '_' . $options{method} . '_/' . $5})) { my @variables = split(/\//, $5); $response = call_action( socket => $options{socket}, - action => $dispatch{$3 . '_' . $options{method} . '_/' . $4}, + action => $dispatch{$3 . '_' . $4 . '_' . $options{method} . '_/' . $5}, target => $2, data => { content => $options{content}, diff --git a/gorgone/centreon/script/gorgonecore.pm b/gorgone/centreon/script/gorgonecore.pm index 91980b2ad48..8147e7fc5c2 100644 --- a/gorgone/centreon/script/gorgonecore.pm +++ b/gorgone/centreon/script/gorgonecore.pm @@ -191,7 +191,7 @@ sub load_modules { } } - my ($name, $events) = $self->{modules_register}->{$package}->{register}->( + my ($namespace, $name, $events) = $self->{modules_register}->{$package}->{register}->( config => $module, config_core => $config->{gorgonecore}, config_db_centreon => $config->{database}->{db_centreon}, @@ -201,8 +201,15 @@ sub load_modules { foreach my $event (@{$events}) { $self->{modules_events}->{$event->{event}} = { - module => { name => $name, package => $package }, - api => { uri => $event->{uri}, method => $event->{method} } + module => { + namespace => $namespace, + name => $name, + package => $package + }, + api => { + uri => $event->{uri}, + method => $event->{method} + } }; } $self->{logger}->writeLogInfo("[core] Module '" . $module->{name} . "' is loaded"); diff --git a/gorgone/modules/centreon/broker/hooks.pm b/gorgone/modules/centreon/broker/hooks.pm index 42a36ff1e1d..13d8b95ad37 100644 --- a/gorgone/modules/centreon/broker/hooks.pm +++ b/gorgone/modules/centreon/broker/hooks.pm @@ -26,8 +26,9 @@ use JSON::XS; use centreon::script::gorgonecore; use modules::centreon::broker::class; -my $NAME = 'broker'; -my $EVENTS = [ +use constant NAMESPACE => 'centreon'; +use constant NAME => 'broker'; +use constant EVENTS => [ { event => 'BROKERREADY' }, { event => 'BROKERSTATS', uri => '/statistics', method => 'GET' }, ]; @@ -44,7 +45,7 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; $config_db_centreon = $options{config_db_centreon}; - return ($NAME, $EVENTS); + return (NAMESPACE, NAME, EVENTS); } sub init { diff --git a/gorgone/modules/centreon/engine/hooks.pm b/gorgone/modules/centreon/engine/hooks.pm index 93a3c1f7a6f..b8ecead2061 100644 --- a/gorgone/modules/centreon/engine/hooks.pm +++ b/gorgone/modules/centreon/engine/hooks.pm @@ -26,8 +26,9 @@ use JSON::XS; use centreon::script::gorgonecore; use modules::centreon::engine::class; -my $NAME = 'engine'; -my $EVENTS = [ +use constant NAMESPACE => 'centreon'; +use constant NAME => 'engine'; +use constant EVENTS => [ { event => 'ENGINEREADY' }, { event => 'ENGINECOMMAND', uri => '/command', method => 'POST' }, ]; @@ -42,7 +43,7 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; - return ($NAME, $EVENTS); + return (NAMESPACE, NAME, EVENTS); } sub init { diff --git a/gorgone/modules/centreon/legacycmd/hooks.pm b/gorgone/modules/centreon/legacycmd/hooks.pm index 96332d34650..342fe7e93c1 100644 --- a/gorgone/modules/centreon/legacycmd/hooks.pm +++ b/gorgone/modules/centreon/legacycmd/hooks.pm @@ -26,8 +26,9 @@ use centreon::script::gorgonecore; use modules::centreon::legacycmd::class; use JSON::XS; -my $NAME = 'legacycmd'; -my $EVENTS = [ +use constant NAMESPACE => 'centreon'; +use constant NAME => 'legacycmd'; +use constant EVENTS => [ { event => 'LEGACYCMDREADY' }, ]; @@ -43,7 +44,7 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; $config_db_centreon = $options{config_db_centreon}; - return ($NAME, $EVENTS); + return (NAMESPACE, NAME, EVENTS); } sub init { diff --git a/gorgone/modules/centreon/pollers/hooks.pm b/gorgone/modules/centreon/pollers/hooks.pm index 1719357dcc1..a7830b87587 100644 --- a/gorgone/modules/centreon/pollers/hooks.pm +++ b/gorgone/modules/centreon/pollers/hooks.pm @@ -26,8 +26,9 @@ use JSON::XS; use centreon::script::gorgonecore; use modules::centreon::pollers::class; -my $NAME = 'pollers'; -my $EVENTS = [ +use constant NAMESPACE => 'centreon'; +use constant NAME => 'pollers'; +use constant EVENTS => [ { event => 'POLLERSREADY' }, ]; @@ -43,7 +44,7 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; $config_db_centreon = $options{config_db_centreon}; - return ($NAME, $EVENTS); + return (NAMESPACE, NAME, EVENTS); } sub init { diff --git a/gorgone/modules/core/action/hooks.pm b/gorgone/modules/core/action/hooks.pm index a4447418454..d5f805b1224 100644 --- a/gorgone/modules/core/action/hooks.pm +++ b/gorgone/modules/core/action/hooks.pm @@ -26,8 +26,9 @@ use centreon::script::gorgonecore; use modules::core::action::class; use JSON::XS; -my $NAME = 'action'; -my $EVENTS = [ +use constant NAMESPACE => 'core'; +use constant NAME => 'action'; +use constant EVENTS => [ { event => 'ACTIONREADY' }, { event => 'PROCESSCOPY' }, { event => 'COMMAND', uri => '/command', method => 'POST' }, @@ -43,7 +44,7 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; - return ($NAME, $EVENTS); + return (NAMESPACE, NAME, EVENTS); } sub init { diff --git a/gorgone/modules/core/cron/hooks.pm b/gorgone/modules/core/cron/hooks.pm index e3d4b136ced..e7e80e28d8f 100644 --- a/gorgone/modules/core/cron/hooks.pm +++ b/gorgone/modules/core/cron/hooks.pm @@ -26,8 +26,9 @@ use centreon::script::gorgonecore; use modules::core::cron::class; use JSON::XS; -my $NAME = 'cron'; -my $EVENTS = [ +use constant NAMESPACE => 'core'; +use constant NAME => 'cron'; +use constant EVENTS => [ { event => 'CRONREADY' }, { event => 'GETCRON', uri => '/definitions', method => 'GET' }, { event => 'ADDCRON', uri => '/definitions', method => 'POST' }, @@ -45,7 +46,7 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; - return ($NAME, $EVENTS); + return (NAMESPACE, NAME, EVENTS); } sub init { diff --git a/gorgone/modules/core/httpserver/hooks.pm b/gorgone/modules/core/httpserver/hooks.pm index 161b1f0f91b..f63e04f22a1 100644 --- a/gorgone/modules/core/httpserver/hooks.pm +++ b/gorgone/modules/core/httpserver/hooks.pm @@ -26,8 +26,9 @@ use centreon::script::gorgonecore; use modules::core::httpserver::class; use JSON::XS; -my $NAME = 'httpserver'; -my $EVENTS = [ +use constant NAMESPACE => 'core'; +use constant NAME => 'httpserver'; +use constant EVENTS => [ { event => 'HTTPSERVERREADY' }, ]; @@ -41,7 +42,7 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; - return ($NAME, $EVENTS); + return (NAMESPACE, NAME, EVENTS); } sub init { diff --git a/gorgone/modules/core/proxy/hooks.pm b/gorgone/modules/core/proxy/hooks.pm index fabe112f1a6..4d581f5fa7f 100644 --- a/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/modules/core/proxy/hooks.pm @@ -32,8 +32,9 @@ use MIME::Base64; use Digest::MD5::File qw(file_md5_hex); use Fcntl; -my $NAME = 'proxy'; -my $EVENTS = [ +use constant NAMESPACE => 'core'; +use constant NAME => 'proxy'; +use constant EVENTS => [ { event => 'PROXYREADY' }, { event => 'SETLOGS' }, # internal. Shouldn't be used by third party clients { event => 'PONG' }, # internal. Shouldn't be used by third party clients @@ -73,7 +74,7 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; - return ($NAME, $EVENTS); + return (NAMESPACE, NAME, EVENTS); } sub init { @@ -529,7 +530,7 @@ sub create_child { $0 = 'gorgone-proxy'; my $module = modules::core::proxy::class->new( logger => $options{logger}, - module_id => $NAME, + module_id => NAME, config_core => $config_core, config => $config, pool_id => $options{pool_id}, diff --git a/gorgone/modules/core/pull/hooks.pm b/gorgone/modules/core/pull/hooks.pm index 6e26c211995..3f7a288e8e9 100644 --- a/gorgone/modules/core/pull/hooks.pm +++ b/gorgone/modules/core/pull/hooks.pm @@ -25,6 +25,7 @@ use strict; use centreon::gorgone::clientzmq; use JSON::XS; +my $NAMESPACE = 'core'; my $NAME = 'pull'; my $EVENTS = []; @@ -40,7 +41,7 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; - return ($NAME, $EVENTS); + return ($NAMESPACE, $NAME, $EVENTS); } sub init { diff --git a/gorgone/modules/core/register/hooks.pm b/gorgone/modules/core/register/hooks.pm index 9c6fea943c6..ed3754e8e78 100644 --- a/gorgone/modules/core/register/hooks.pm +++ b/gorgone/modules/core/register/hooks.pm @@ -26,8 +26,9 @@ use JSON::XS; use centreon::script::gorgonecore; use modules::core::register::class; -my $NAME = 'register'; -my $EVENTS = [ +use constant NAMESPACE => 'core'; +use constant NAME => 'register'; +use constant EVENTS => [ { event => 'REGISTERREADY' }, ]; @@ -42,7 +43,7 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; - return ($NAME, $EVENTS); + return (NAMESPACE, NAME, EVENTS); } sub init { diff --git a/gorgone/modules/plugins/newtest/hooks.pm b/gorgone/modules/plugins/newtest/hooks.pm index c5f8a5dc5d8..9bcd0f12b66 100644 --- a/gorgone/modules/plugins/newtest/hooks.pm +++ b/gorgone/modules/plugins/newtest/hooks.pm @@ -26,8 +26,9 @@ use JSON::XS; use centreon::script::gorgonecore; use modules::plugins::newtest::class; -my $NAME = 'newtest'; -my $EVENTS = [ +use constant NAMESPACE => 'plugins'; +use constant NAME => 'newtest'; +use constant EVENTS => [ { event => 'NEWTESTREADY' }, { event => 'NEWTESTRESYNC', uri => '/resync', method => 'GET' }, ]; @@ -49,7 +50,7 @@ sub register { $config_db_centstorage = $options{config_db_centstorage}; $config_db_centreon = $options{config_db_centreon}; $config_check_containers_time = defined($config->{check_containers_time}) ? $config->{check_containers_time} : 3600; - return ($NAME, $EVENTS); + return (NAMESPACE, NAME, EVENTS); } sub init { diff --git a/gorgone/modules/plugins/scom/hooks.pm b/gorgone/modules/plugins/scom/hooks.pm index 2415302e281..7c1bedda7cc 100644 --- a/gorgone/modules/plugins/scom/hooks.pm +++ b/gorgone/modules/plugins/scom/hooks.pm @@ -26,8 +26,9 @@ use JSON::XS; use centreon::script::gorgonecore; use modules::plugins::scom::class; -my $NAME = 'scom'; -my $EVENTS = [ +use constant NAMESPACE => 'plugins'; +use constant NAME => 'scom'; +use constant EVENTS => [ { event => 'SCOMREADY' }, { event => 'SCOMRESYNC', uri => '/resync', method => 'GET' }, ]; @@ -48,7 +49,7 @@ sub register { $config_core = $options{config_core}; $config_db_centstorage = $options{config_db_centstorage}; $config_check_containers_time = defined($config->{check_containers_time}) ? $config->{check_containers_time} : 3600; - return ($NAME, $EVENTS); + return (NAMESPACE, NAME, EVENTS); } sub init { From 75370137420825f0ffb2e5d2f2ca349cfd194690 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Thu, 19 Sep 2019 12:14:03 +0200 Subject: [PATCH 110/948] enh(core/pull): add namespace and use constant --- gorgone/modules/core/pull/hooks.pm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gorgone/modules/core/pull/hooks.pm b/gorgone/modules/core/pull/hooks.pm index 3f7a288e8e9..81296617ba2 100644 --- a/gorgone/modules/core/pull/hooks.pm +++ b/gorgone/modules/core/pull/hooks.pm @@ -25,9 +25,9 @@ use strict; use centreon::gorgone::clientzmq; use JSON::XS; -my $NAMESPACE = 'core'; -my $NAME = 'pull'; -my $EVENTS = []; +use constant NAMESPACE => 'core'; +use constant NAME => 'pull'; +use constant EVENTS => []; my $config_core; my $config; @@ -41,7 +41,7 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; - return ($NAMESPACE, $NAME, $EVENTS); + return (NAMESPACE, NAME, EVENTS); } sub init { From bdd03ff86f973b7fd8be2387ddb7a9091ac01c2e Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Thu, 19 Sep 2019 13:41:03 +0200 Subject: [PATCH 111/948] fix(api): fix variables setting --- gorgone/centreon/gorgone/api.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/centreon/gorgone/api.pm b/gorgone/centreon/gorgone/api.pm index 416bee7b852..4cb8588013f 100644 --- a/gorgone/centreon/gorgone/api.pm +++ b/gorgone/centreon/gorgone/api.pm @@ -55,7 +55,7 @@ sub root { ); } elsif ($options{uri} =~ /^\/api\/(targets\/(\w*)\/)?(\w+)\/(\w+)\/(\w+)\/?([\w\/]*?)$/ && defined($dispatch{$3 . '_' . $4 . '_' . $options{method} . '_/' . $5})) { - my @variables = split(/\//, $5); + my @variables = split(/\//, $6); $response = call_action( socket => $options{socket}, action => $dispatch{$3 . '_' . $4 . '_' . $options{method} . '_/' . $5}, From 3a0618f430586839a024abdb808feec9d5bb4ef9 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Thu, 19 Sep 2019 13:45:38 +0200 Subject: [PATCH 112/948] update todo --- gorgone/TODO | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gorgone/TODO b/gorgone/TODO index e73d263ec02..17fcd235dc7 100644 --- a/gorgone/TODO +++ b/gorgone/TODO @@ -1,3 +1,5 @@ - Add a core event: GETINFORMATION (events loaded, target loaded and module loaded) - gorgone-newtest: don't use centcore.cmd. use ssh system. - Add gorgone sqlite cleaning +- Add an action to load module dynamically +- Add a broadcast action to update module (like logger in debug for example) From b7cd350357e50bef63a8d85a7783ec1af12a1376 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Thu, 19 Sep 2019 16:51:24 +0200 Subject: [PATCH 113/948] feat(core): add db cleaning --- gorgone/centreon/script/gorgonecore.pm | 30 +++++++++++++++++++------- gorgone/config/gorgoned.yml | 3 ++- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/gorgone/centreon/script/gorgonecore.pm b/gorgone/centreon/script/gorgonecore.pm index 8147e7fc5c2..0e805515f5a 100644 --- a/gorgone/centreon/script/gorgonecore.pm +++ b/gorgone/centreon/script/gorgonecore.pm @@ -57,7 +57,8 @@ sub new { $self->{modules_register} = {}; $self->{modules_events} = {}; $self->{modules_id} = {}; - $self->{sessions_timer} = time(); + $self->{purge_timer} = time(); + $self->{history_timer} = time(); $self->{kill_timer} = undef; $self->{server_privkey} = undef; $self->{register_parent_nodes} = {}; @@ -81,6 +82,18 @@ sub init { config_file => $self->{opt_extra}, logger => $self->{logger} ); + + $config->{gorgonecore}->{purge_sessions_time} = + defined($config->{gorgonecore}->{purge_sessions_time}) && $config->{gorgonecore}->{purge_sessions_time} =~ /\d+/ ? + $config->{gorgonecore}->{purge_sessions_time} : + 3600 + ; + $config->{gorgonecore}->{purge_history_time} = + defined($config->{gorgonecore}->{purge_history_time}) && $config->{gorgonecore}->{purge_history_time} =~ /\d+/ ? + $config->{gorgonecore}->{purge_history_time} : + 604800 + ; + if (defined($config->{gorgonecore}->{external_com_type}) && $config->{gorgonecore}->{external_com_type} ne '') { $self->{server_privkey} = centreon::gorgone::common::loadprivkey(logger => $self->{logger}, privkey => $config->{gorgonecore}->{privkey}); } @@ -493,13 +506,14 @@ sub waiting_ready { return 1; } -sub clean_sessions { +sub clean_db { my ($self, %options) = @_; - - if ($self->{sessions_timer} - time() > $config->{gorgonecore}->{purge_sessions_time}) { - $self->{logger}->writeLogInfo("[core] Purge sessions in progress..."); - $self->{db_gorgone}->query("DELETE FROM gorgone_identity WHERE `ctime` < " . $self->{db_gorgone}->quote(time() - $config->{gorgonecore}->{sessions_time})); - $self->{sessions_timer} = time(); + + if ($self->{purge_timer} - time() > 3600) { + $self->{logger}->writeLogInfo("[core] Purge db in progress..."); + $self->{db_gorgone}->query("DELETE FROM gorgone_identity WHERE `ctime` < " . $self->{db_gorgone}->quote(time() - $config->{gorgonecore}->{purge_sessions_time})); + $self->{db_gorgone}->query("DELETE FROM gorgone_history WHERE `ctime` < " . $self->{db_gorgone}->quote(time() - $config->{gorgonecore}->{purge_history_time})); + $self->{purge_timer} = time(); } } @@ -613,7 +627,7 @@ sub run { zmq_poll($poll, 5000); - $gorgone->clean_sessions(); + $gorgone->clean_db(); } } diff --git a/gorgone/config/gorgoned.yml b/gorgone/config/gorgoned.yml index 735d32a406d..6c8b0e7f4eb 100644 --- a/gorgone/config/gorgoned.yml +++ b/gorgone/config/gorgoned.yml @@ -40,7 +40,8 @@ gorgonecore: # in seconds sessions_time: 86400 # in seconds - purge_sessions_time: 3600 + #purge_sessions_time: 3600 + #purge_history_time: 604800 # shouldn't be changed proxy_name: proxy modules: From dd3f6a11398aef4cd1bd855c9ec51026fe98fb00 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 20 Sep 2019 10:46:36 +0200 Subject: [PATCH 114/948] enh(common): add possibility to choose token size --- gorgone/centreon/gorgone/common.pm | 5 +++-- gorgone/centreon/gorgone/module.pm | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/gorgone/centreon/gorgone/common.pm b/gorgone/centreon/gorgone/common.pm index d240a033429..8b063c7023a 100644 --- a/gorgone/centreon/gorgone/common.pm +++ b/gorgone/centreon/gorgone/common.pm @@ -183,8 +183,9 @@ sub uncrypt_message { sub generate_token { my (%options) = @_; - - my $token = Crypt::PRNG::random_bytes_hex(64); + + my $length = (defined($options{length})) ? $options{length} : 64; + my $token = Crypt::PRNG::random_bytes_hex($length); return $token; } diff --git a/gorgone/centreon/gorgone/module.pm b/gorgone/centreon/gorgone/module.pm index aa82bb5a733..5ebc287fe2b 100644 --- a/gorgone/centreon/gorgone/module.pm +++ b/gorgone/centreon/gorgone/module.pm @@ -34,7 +34,7 @@ use constant ACTION_FINISH_OK => 2; sub generate_token { my ($self, %options) = @_; - return centreon::gorgone::common::generate_token(); + return centreon::gorgone::common::generate_token(length => $options{length}); } sub send_internal_action { From 1aae5228580c02a4b4534f10f689915d363f530f Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 20 Sep 2019 10:47:18 +0200 Subject: [PATCH 115/948] enh(core/action): add start and end time for COMMAND --- gorgone/modules/core/action/class.pm | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/gorgone/modules/core/action/class.pm b/gorgone/modules/core/action/class.pm index 25a43548dcd..7c8bb0e9181 100644 --- a/gorgone/modules/core/action/class.pm +++ b/gorgone/modules/core/action/class.pm @@ -103,6 +103,7 @@ sub action_command { return -1; } + my $start = time(); my ($error, $stdout, $return_code) = centreon::misc::misc::backtick( command => $options{data}->{content}->{command}, #arguments => [@$args, $sub_cmd], @@ -112,6 +113,7 @@ sub action_command { redirect_stderr => 1, logger => $self->{logger} ); + my $end = time(); if ($error <= -1000) { $self->send_log( socket => $options{socket_log}, @@ -119,7 +121,10 @@ sub action_command { token => $options{token}, data => { message => "command '$options{data}->{content}->{command}' execution issue: $stdout", - metadata => $options{data}->{content}->{metadata} + exit_code => $return_code, + metadata => $options{data}->{content}->{metadata}, + start => $start, + end => $end } ); return -1; @@ -133,7 +138,9 @@ sub action_command { message => "command '$options{data}->{content}->{command}' has finished", stdout => $stdout, exit_code => $return_code, - metadata => $options{data}->{content}->{metadata} + metadata => $options{data}->{content}->{metadata}, + start => $start, + end => $end } ); From c75c27673f03c9a747a2f0d4a09dab6e32abaa78 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 20 Sep 2019 10:48:11 +0200 Subject: [PATCH 116/948] enh(core/cron): allow id to be used as token --- gorgone/modules/core/cron/class.pm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gorgone/modules/core/cron/class.pm b/gorgone/modules/core/cron/class.pm index c5e83a3944f..50021158e75 100644 --- a/gorgone/modules/core/cron/class.pm +++ b/gorgone/modules/core/cron/class.pm @@ -411,8 +411,11 @@ sub dispatcher { $options->{logger}->writeLogInfo("[cron] -class- Launching job '" . $id . "'"); + my $token = (defined($options->{definition}->{keep_token})) ? $options->{definition}->{id} : undef; + centreon::gorgone::common::zmq_send_message( socket => $options->{socket}, + token => $token, action => $options->{definition}->{action}, target => $options->{definition}->{target}, data => { From 584fac2d78e7db93b4cae82f91fe538316c9b896 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 20 Sep 2019 10:49:05 +0200 Subject: [PATCH 117/948] feat(centreon/autodiscovery): add module centreon autodiscovery --- gorgone/config/gorgoned.yml | 4 + .../modules/centreon/autodiscovery/class.pm | 322 ++++++++++++++++++ .../modules/centreon/autodiscovery/hooks.pm | 168 +++++++++ 3 files changed, 494 insertions(+) create mode 100644 gorgone/modules/centreon/autodiscovery/class.pm create mode 100644 gorgone/modules/centreon/autodiscovery/hooks.pm diff --git a/gorgone/config/gorgoned.yml b/gorgone/config/gorgoned.yml index 6c8b0e7f4eb..26ca428ef1a 100644 --- a/gorgone/config/gorgoned.yml +++ b/gorgone/config/gorgoned.yml @@ -98,6 +98,10 @@ modules: enable: true command_file: "/var/lib/centreon-engine/rw/centengine.cmd" + - name: autodiscovery + package: modules::centreon::autodiscovery::hooks + enable: true + - name: broker package: modules::centreon::broker::hooks enable: false diff --git a/gorgone/modules/centreon/autodiscovery/class.pm b/gorgone/modules/centreon/autodiscovery/class.pm new file mode 100644 index 00000000000..9ad28e06187 --- /dev/null +++ b/gorgone/modules/centreon/autodiscovery/class.pm @@ -0,0 +1,322 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package modules::centreon::autodiscovery::class; + +use base qw(centreon::gorgone::module); + +use strict; +use warnings; +use centreon::gorgone::common; +use centreon::misc::objects::object; +use ZMQ::LibZMQ4; +use ZMQ::Constants qw(:all); +use JSON::XS; +use Time::HiRes; + +my %jobs; +my %handlers = (TERM => {}, HUP => {}); +my ($connector); + +sub new { + my ($class, %options) = @_; + + $connector = {}; + $connector->{internal_socket} = undef; + $connector->{module_id} = $options{module_id}; + $connector->{logger} = $options{logger}; + $connector->{config} = $options{config}; + $connector->{config_core} = $options{config_core}; + $connector->{stop} = 0; + $connector->{sync_time} = + (defined($options{config}->{sync_time}) && $options{config}->{sync_time} =~ /(\d+)/) ? $1 : 50; + $connector->{last_sync_time} = -1; + + bless $connector, $class; + $connector->set_signal_handlers(); + return $connector; +} + +sub set_signal_handlers { + my $self = shift; + + $SIG{TERM} = \&class_handle_TERM; + $handlers{TERM}->{$self} = sub { $self->handle_TERM() }; + $SIG{HUP} = \&class_handle_HUP; + $handlers{HUP}->{$self} = sub { $self->handle_HUP() }; +} + +sub handle_HUP { + my $self = shift; + $self->{reload} = 0; +} + +sub handle_TERM { + my $self = shift; + $self->{logger}->writeLogInfo("[autodiscovery] -class- $$ Receiving order to stop..."); + $self->{stop} = 1; +} + +sub class_handle_TERM { + foreach (keys %{$handlers{TERM}}) { + &{$handlers{TERM}->{$_}}(); + } +} + +sub class_handle_HUP { + foreach (keys %{$handlers{HUP}}) { + &{$handlers{HUP}->{$_}}(); + } +} + +sub action_adddiscoveryjob { + my ($self, %options) = @_; + + $options{token} = $self->generate_token() if (!defined($options{token})); + my $id = 'autodiscovery_job_' . $self->generate_token(length => 12) if (!defined($options{data}->{content}->{id})); + + my $definition = { + id => $id, + target => $options{data}->{content}->{target}, + timespec => $options{data}->{content}->{timespec}, + action => 'COMMAND', + parameters => { + command => $options{data}->{content}->{command}, + timeout => $options{data}->{content}->{timeout}, + metadata => { + id => $id, + source => 'autodiscovery', + } + }, + keep_token => 1, + }; + + $self->send_internal_action( + action => 'ADDCRON', + token => $options{token}, + data => { + content => [ $definition ], + } + ); + + $self->send_log( + code => $self->ACTION_FINISH_OK, + token => $options{token}, + data => { + id => $id + } + ); + + $jobs{$id} = { target => $options{data}->{content}->{target} }; + + return 0; +} + +sub action_getdiscoveryjob { + my ($self, %options) = @_; + + $options{token} = $self->generate_token() if (!defined($options{token})); + + if (!defined($options{data}->{variables}[0])) { + $self->{logger}->writeLogError("[autodiscovery] -class- Need to specify job id"); + $self->send_log( + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { + message => 'need to specify job id' + } + ); + return 1; + } + my $id = $options{data}->{variables}[0]; + + $self->send_log( + code => $self->ACTION_FINISH_OK, + token => $options{token}, + data => { + results => $jobs{$id}->{results} + } + ); + + return 0; +} + +sub action_syncdiscoverylogs { + my ($self, %options) = @_; + + $options{token} = $self->generate_token() if (!defined($options{token})); + + $self->{logger}->writeLogDebug("[autodiscovery] -class- Discovery logs sync start"); + my %synced; + foreach my $id (keys %jobs) { + next if (!defined($jobs{$id}->{target}) || defined($synced{$jobs{$id}->{target}})); + $self->send_internal_action( + action => 'GETLOG', + token => $options{token}, + target => $jobs{$id}->{target}, + data => {} + ); + $synced{$jobs{$id}->{target}} = 1; + } + + return 0; +} + +sub action_getdiscoveryresults { + my ($self, %options) = @_; + + foreach my $id (keys %jobs) { + $self->{logger}->writeLogDebug("[autodiscovery] -class- Get logs results for job '" . $id . "'"); + $self->send_internal_action( + action => 'GETLOG', + data => { + token => $id + } + ); + } + + return 0; +} + +sub action_updatediscoveryresults { + my ($self, %options) = @_; + + return if (!defined($options{data}->{data}->{action}) || $options{data}->{data}->{action} ne "getlog" && + defined($options{data}->{data}->{result})); + + foreach my $message_id (sort keys %{$options{data}->{data}->{result}}) { + my $data = JSON::XS->new->utf8->decode($options{data}->{data}->{result}->{$message_id}->{data}); + next if (!defined($data->{exit_code}) || $data->{exit_code} != 0 || + !defined($data->{metadata}->{id}) || !defined($data->{metadata}->{source}) || + $data->{metadata}->{source} ne 'autodiscovery'); + + $jobs{$data->{metadata}->{id}}->{results} = $data; + } + + return 0; +} + +sub event { + while (1) { + my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $connector->{internal_socket}); + + $connector->{logger}->writeLogDebug("[autodiscovery] -class- Event: $message"); + if ($message =~ /^\[ACK\]\s+\[(.*?)\]\s+(.*)$/m) { + my $token = $1; + my $data = JSON::XS->new->utf8->decode($2); + my $method = $connector->can('action_updatediscoveryresults'); + $method->($connector, data => $data); + } else { + $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; + if ((my $method = $connector->can('action_' . lc($1)))) { + $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; + my ($action, $token) = ($1, $2); + my $data = JSON::XS->new->utf8->decode($3); + $method->($connector, token => $token, data => $data); + } + } + + last unless (centreon::gorgone::common::zmq_still_read(socket => $connector->{internal_socket})); + } +} + +sub run { + my ($self, %options) = @_; + + # Database creation. We stay in the loop still there is an error + $self->{db_centreon} = centreon::misc::db->new( + dsn => $self->{config_db_centreon}->{dsn}, + user => $self->{config_db_centreon}->{username}, + password => $self->{config_db_centreon}->{password}, + force => 2, + logger => $self->{logger} + ); + ##### Load objects ##### + $self->{class_object} = centreon::misc::objects::object->new( + logger => $self->{logger}, + db_centreon => $self->{db_centreon} + ); + + # Connect internal + $connector->{internal_socket} = centreon::gorgone::common::connect_com( + zmq_type => 'ZMQ_DEALER', + name => 'gorgoneautodiscovery', + logger => $self->{logger}, + type => $self->{config_core}->{internal_com_type}, + path => $self->{config_core}->{internal_com_path} + ); + centreon::gorgone::common::zmq_send_message( + socket => $connector->{internal_socket}, + action => 'AUTODISCOVERYREADY', + data => {}, + json_encode => 1 + ); + $self->{poll} = [ + { + socket => $connector->{internal_socket}, + events => ZMQ_POLLIN, + callback => \&event, + } + ]; + + $self->send_internal_action( + action => 'ADDCRON', + data => { + content => [ + { + id => 'autodiscovery_syncdiscoverylogs', + target => undef, + timespec => '*/2 * * * *', + action => 'SYNCDISCOVERYLOGS', + parameters => {}, + keep_token => 1, + } + ] + } + ); + + $self->send_internal_action( + action => 'ADDCRON', + data => { + content => [ + { + id => 'autodiscovery_getdiscoveryresults', + target => undef, + timespec => '* * * * *', + action => 'GETDISCOVERYRESULTS', + parameters => {}, + keep_token => 1, + } + ] + } + ); + + while (1) { + # we try to do all we can + my $rev = zmq_poll($self->{poll}, 5000); + if (defined($rev) && $rev == 0 && $self->{stop} == 1) { + $self->{logger}->writeLogInfo("[autodiscovery] -class- $$ has quit"); + zmq_close($connector->{internal_socket}); + exit(0); + } + } +} + +1; diff --git a/gorgone/modules/centreon/autodiscovery/hooks.pm b/gorgone/modules/centreon/autodiscovery/hooks.pm new file mode 100644 index 00000000000..9ec03ed41c1 --- /dev/null +++ b/gorgone/modules/centreon/autodiscovery/hooks.pm @@ -0,0 +1,168 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package modules::centreon::autodiscovery::hooks; + +use warnings; +use strict; +use JSON::XS; +use centreon::script::gorgonecore; +use modules::centreon::autodiscovery::class; + +use constant NAMESPACE => 'centreon'; +use constant NAME => 'autodiscovery'; +use constant EVENTS => [ + { event => 'AUTODISCOVERYREADY' }, + { event => 'SYNCDISCOVERYLOGS' }, + { event => 'GETDISCOVERYRESULTS' }, + { event => 'UPDATEDISCOVERYRESULTS' }, + { event => 'GETDISCOVERYJOB', uri => '/job', method => 'GET' }, + { event => 'ADDDISCOVERYJOB', uri => '/job', method => 'POST' }, +]; + +my $config_core; +my $config; +my $config_db_centreon; +my $autodiscovery = {}; +my $stop = 0; + +sub register { + my (%options) = @_; + + $config = $options{config}; + $config_core = $options{config_core}; + $config_db_centreon = $options{config_db_centreon}; + return (NAMESPACE, NAME, EVENTS); +} + +sub init { + my (%options) = @_; + + create_child(logger => $options{logger}); +} + +sub routing { + my (%options) = @_; + + my $data; + eval { + $data = JSON::XS->new->utf8->decode($options{data}); + }; + if ($@) { + $options{logger}->writeLogError("[autodiscovery] -hooks- Cannot decode json data: $@"); + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => 30, token => $options{token}, + data => { msg => 'gorgoneautodiscovery: cannot decode json' }, + json_encode => 1 + ); + return undef; + } + + if ($options{action} eq 'AUTODISCOVERYREADY') { + $autodiscovery->{ready} = 1; + return undef; + } + + if (centreon::script::gorgonecore::waiting_ready(ready => \$autodiscovery->{ready}) == 0) { + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => 30, token => $options{token}, + data => { msg => 'gorgoneautodiscovery: still no ready' }, + json_encode => 1 + ); + return undef; + } + + centreon::gorgone::common::zmq_send_message( + socket => $options{socket}, + identity => 'gorgoneautodiscovery', + action => $options{action}, + data => $options{data}, + token => $options{token}, + ); +} + +sub gently { + my (%options) = @_; + + $stop = 1; + $options{logger}->writeLogInfo("[autodiscovery] -hooks- Send TERM signal"); + if ($autodiscovery->{running} == 1) { + CORE::kill('TERM', $autodiscovery->{pid}); + } +} + +sub kill { + my (%options) = @_; + + if ($autodiscovery->{running} == 1) { + $options{logger}->writeLogInfo("[autodiscovery] -hooks- Send KILL signal for pool"); + CORE::kill('KILL', $autodiscovery->{pid}); + } +} + +sub kill_internal { + my (%options) = @_; + +} + +sub check { + my (%options) = @_; + + my $count = 0; + foreach my $pid (keys %{$options{dead_childs}}) { + # Not me + next if ($autodiscovery->{pid} != $pid); + + $autodiscovery = {}; + delete $options{dead_childs}->{$pid}; + if ($stop == 0) { + create_child(logger => $options{logger}); + } + } + + $count++ if (defined($autodiscovery->{running}) && $autodiscovery->{running} == 1); + + return $count; +} + +# Specific functions +sub create_child { + my (%options) = @_; + + $options{logger}->writeLogInfo("[autodiscovery] -hooks- Create module 'autodiscovery' process"); + my $child_pid = fork(); + if ($child_pid == 0) { + $0 = 'gorgone-autodiscovery'; + my $module = modules::centreon::autodiscovery::class->new( + logger => $options{logger}, + config_core => $config_core, + config => $config, + config_db_centreon => $config_db_centreon, + ); + $module->run(); + exit(0); + } + $options{logger}->writeLogInfo("[autodiscovery] -hooks- PID $child_pid (gorgone-autodiscovery)"); + $autodiscovery = { pid => $child_pid, ready => 0, running => 1 }; +} + +1; From 5e23326c9d92aeda2713154157ee97a2d98592cf Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Fri, 20 Sep 2019 11:38:40 +0200 Subject: [PATCH 118/948] enh(proxy): add a system to discard message --- gorgone/TODO | 1 - gorgone/modules/core/proxy/hooks.pm | 12 ++++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/gorgone/TODO b/gorgone/TODO index 17fcd235dc7..dcd0b83149b 100644 --- a/gorgone/TODO +++ b/gorgone/TODO @@ -1,5 +1,4 @@ - Add a core event: GETINFORMATION (events loaded, target loaded and module loaded) - gorgone-newtest: don't use centcore.cmd. use ssh system. -- Add gorgone sqlite cleaning - Add an action to load module dynamically - Add a broadcast action to update module (like logger in debug for example) diff --git a/gorgone/modules/core/proxy/hooks.pm b/gorgone/modules/core/proxy/hooks.pm index 4d581f5fa7f..f20cc395603 100644 --- a/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/modules/core/proxy/hooks.pm @@ -84,6 +84,7 @@ sub init { $synctime_option = defined($config->{synchistory_time}) ? $config->{synchistory_time} : 300; $synctimeout_option = defined($config->{synchistory_timeout}) ? $config->{synchistory_timeout} : 120; $ping_option = defined($config->{ping}) ? $config->{ping} : 60; + $config->{pong_discard_timeout} = defined($config->{pong_discard_timeout}) ? $config->{pong_discard_timeout} : 300; $core_id = $options{id}; $external_socket = $options{external_socket}; @@ -207,6 +208,16 @@ sub routing { $target = $register_subnodes->{$options{target}}; } + if (defined($last_pong->{$target}) && $last_pong->{$target} != 0 && (time() - $config->{pong_discard_timeout} > $last_pong->{$target})) { + centreon::gorgone::common::add_history( + dbh => $options{dbh}, + code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, + data => { message => 'proxy - discard message. target peer seems disconnected' }, + json_encode => 1 + ); + return undef; + } + my $action = $options{action}; my $bulk_actions; push @{$bulk_actions}, $data; @@ -619,6 +630,7 @@ sub unregister_nodes { delete $register_nodes->{$node->{id}}; delete $synctime_nodes->{$node->{id}}; delete $constatus_ping->{$node->{id}}; + delete $last_pong->{$node->{id}}; } } } From 80586516ceed6b650adef1d0ea911803f2faa006 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Fri, 20 Sep 2019 12:02:36 +0200 Subject: [PATCH 119/948] update todo --- gorgone/TODO | 1 + 1 file changed, 1 insertion(+) diff --git a/gorgone/TODO b/gorgone/TODO index dcd0b83149b..4babf61c047 100644 --- a/gorgone/TODO +++ b/gorgone/TODO @@ -2,3 +2,4 @@ - gorgone-newtest: don't use centcore.cmd. use ssh system. - Add an action to load module dynamically - Add a broadcast action to update module (like logger in debug for example) +- Move cleaning db in a module From fea99ffe2e052521f9ae9a48c07c290a38543e40 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 20 Sep 2019 14:47:02 +0200 Subject: [PATCH 120/948] enh(centreon/autodiscovery): add one-shot task launch --- .../modules/centreon/autodiscovery/class.pm | 107 +++++++++++++++++- .../modules/centreon/autodiscovery/hooks.pm | 2 + 2 files changed, 103 insertions(+), 6 deletions(-) diff --git a/gorgone/modules/centreon/autodiscovery/class.pm b/gorgone/modules/centreon/autodiscovery/class.pm index 9ad28e06187..870fb7127b0 100644 --- a/gorgone/modules/centreon/autodiscovery/class.pm +++ b/gorgone/modules/centreon/autodiscovery/class.pm @@ -31,6 +31,7 @@ use ZMQ::Constants qw(:all); use JSON::XS; use Time::HiRes; +my %tasks; my %jobs; my %handlers = (TERM => {}, HUP => {}); my ($connector); @@ -86,12 +87,78 @@ sub class_handle_HUP { } } +sub action_adddiscoverytask { + my ($self, %options) = @_; + + $options{token} = $self->generate_token() if (!defined($options{token})); + my $id = 'autodiscovery_task_' . $self->generate_token(length => 12) if (!defined($options{data}->{content}->{id})); + + $self->{logger}->writeLogInfo("[autodiscovery] -class- Add task '" . $id . "'"); + $self->send_internal_action( + action => 'COMMAND', + target => $options{data}->{content}->{target}, + token => $id, + data => { + content => { + %{$options{data}->{content}}, + metadata => { + id => $id, + source => 'autodiscovery', + type => 'task', + }, + } + } + ); + + $self->send_log( + code => $self->ACTION_FINISH_OK, + token => $options{token}, + data => { + id => $id + } + ); + + $tasks{$id} = { target => $options{data}->{content}->{target} }; + + return 0; +} + +sub action_getdiscoverytask { + my ($self, %options) = @_; + + $options{token} = $self->generate_token() if (!defined($options{token})); + + if (!defined($options{data}->{variables}[0])) { + $self->{logger}->writeLogError("[autodiscovery] -class- Need to specify job id"); + $self->send_log( + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { + message => 'need to specify job id' + } + ); + return 1; + } + my $id = $options{data}->{variables}[0]; + + $self->send_log( + code => $self->ACTION_FINISH_OK, + token => $options{token}, + data => { + results => $tasks{$id}->{results} + } + ); + + return 0; +} + sub action_adddiscoveryjob { my ($self, %options) = @_; $options{token} = $self->generate_token() if (!defined($options{token})); my $id = 'autodiscovery_job_' . $self->generate_token(length => 12) if (!defined($options{data}->{content}->{id})); + $self->{logger}->writeLogInfo("[autodiscovery] -class- Add job '" . $id . "'"); my $definition = { id => $id, target => $options{data}->{content}->{target}, @@ -103,6 +170,7 @@ sub action_adddiscoveryjob { metadata => { id => $id, source => 'autodiscovery', + type => 'job', } }, keep_token => 1, @@ -165,6 +233,17 @@ sub action_syncdiscoverylogs { $self->{logger}->writeLogDebug("[autodiscovery] -class- Discovery logs sync start"); my %synced; + foreach my $id (keys %tasks) { + next if (!defined($tasks{$id}->{target}) || defined($synced{$tasks{$id}->{target}}) || + defined($tasks{$id}->{results})); + $self->send_internal_action( + action => 'GETLOG', + token => $options{token}, + target => $tasks{$id}->{target}, + data => {} + ); + $synced{$tasks{$id}->{target}} = 1; + } foreach my $id (keys %jobs) { next if (!defined($jobs{$id}->{target}) || defined($synced{$jobs{$id}->{target}})); $self->send_internal_action( @@ -182,6 +261,16 @@ sub action_syncdiscoverylogs { sub action_getdiscoveryresults { my ($self, %options) = @_; + foreach my $id (keys %tasks) { + next if (defined($tasks{$id}->{results})); + $self->{logger}->writeLogDebug("[autodiscovery] -class- Get logs results for task '" . $id . "'"); + $self->send_internal_action( + action => 'GETLOG', + data => { + token => $id + } + ); + } foreach my $id (keys %jobs) { $self->{logger}->writeLogDebug("[autodiscovery] -class- Get logs results for job '" . $id . "'"); $self->send_internal_action( @@ -199,15 +288,21 @@ sub action_updatediscoveryresults { my ($self, %options) = @_; return if (!defined($options{data}->{data}->{action}) || $options{data}->{data}->{action} ne "getlog" && - defined($options{data}->{data}->{result})); + !defined($options{data}->{data}->{result})); foreach my $message_id (sort keys %{$options{data}->{data}->{result}}) { my $data = JSON::XS->new->utf8->decode($options{data}->{data}->{result}->{$message_id}->{data}); - next if (!defined($data->{exit_code}) || $data->{exit_code} != 0 || - !defined($data->{metadata}->{id}) || !defined($data->{metadata}->{source}) || - $data->{metadata}->{source} ne 'autodiscovery'); - - $jobs{$data->{metadata}->{id}}->{results} = $data; + next if (!defined($data->{exit_code}) || !defined($data->{metadata}->{id}) || + !defined($data->{metadata}->{source}) || $data->{metadata}->{source} ne 'autodiscovery'); + + if ($data->{metadata}->{type} eq 'task') { + $self->{logger}->writeLogInfo( + "[autodiscovery] -class- Found result for task '" . $data->{metadata}->{id} . "'" + ); + $tasks{$data->{metadata}->{id}}->{results} = $data; + } elsif ($data->{metadata}->{type} eq 'job') { + $jobs{$data->{metadata}->{id}}->{results} = $data ; + } } return 0; diff --git a/gorgone/modules/centreon/autodiscovery/hooks.pm b/gorgone/modules/centreon/autodiscovery/hooks.pm index 9ec03ed41c1..bb85c1749f2 100644 --- a/gorgone/modules/centreon/autodiscovery/hooks.pm +++ b/gorgone/modules/centreon/autodiscovery/hooks.pm @@ -35,6 +35,8 @@ use constant EVENTS => [ { event => 'UPDATEDISCOVERYRESULTS' }, { event => 'GETDISCOVERYJOB', uri => '/job', method => 'GET' }, { event => 'ADDDISCOVERYJOB', uri => '/job', method => 'POST' }, + { event => 'GETDISCOVERYTASK', uri => '/task', method => 'GET' }, + { event => 'ADDDISCOVERYTASK', uri => '/task', method => 'POST' }, ]; my $config_core; From adf5e858e11e4ba42620374370c848fe3611bbd9 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 23 Sep 2019 15:45:38 +0200 Subject: [PATCH 121/948] feat(centreon/legacycmd): add reload/restart of centengine and centreontrapd --- gorgone/modules/centreon/legacycmd/class.pm | 96 ++++++++++++++++++--- 1 file changed, 86 insertions(+), 10 deletions(-) diff --git a/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/modules/centreon/legacycmd/class.pm index c1797846f3d..1cf2d4737d5 100644 --- a/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/modules/centreon/legacycmd/class.pm @@ -121,23 +121,21 @@ sub get_pollers_config { $self->{pollers} = {}; my ($status, $datas) = $self->{class_object_centreon}->custom_execute( - request => 'SELECT nagios_server_id, command_file, cfg_dir, centreonbroker_cfg_path, snmp_trapd_path_conf ' . + request => 'SELECT nagios_server_id, command_file, cfg_dir, centreonbroker_cfg_path, snmp_trapd_path_conf, ' . + 'engine_start_command, engine_stop_command, engine_restart_command, engine_reload_command, ' . + 'broker_reload_command, init_script_centreontrapd ' . 'FROM cfg_nagios ' . 'JOIN nagios_server ' . 'WHERE id = nagios_server_id', - mode => 2 + mode => 1, + keys => 'nagios_server_id' ); - if ($status == -1 || !defined($datas->[0])) { - $self->{logger}->writeLogError('[legacycmd] -class- cannot get engine pipe for pollers (command_file)'); + if ($status == -1 || !defined($datas)) { + $self->{logger}->writeLogError('[legacycmd] -class- cannot get configuration for pollers'); return -1; } - foreach (@$datas) { - $self->{pollers}->{$_->[0]}->{command_file} = $_->[1]; - $self->{pollers}->{$_->[0]}->{cfg_dir} = $_->[2]; - $self->{pollers}->{$_->[0]}->{centreonbroker_cfg_path} = $_->[3]; - $self->{pollers}->{$_->[0]}->{snmp_trapd_path_conf} = $_->[4]; - } + $self->{pollers} = $datas; return 0; } @@ -229,6 +227,84 @@ sub execute_cmd { } }, ); + } elsif ($options{cmd} eq 'RESTART') { + my $cmd = $self->{pollers}->{$options{target}}->{engine_restart_command}; + $self->send_internal_action( + action => 'COMMAND', + target => $options{target}, + token => $self->generate_token(), + data => { + content => { + command => 'sudo ' . $cmd, + type => 'restart engine', + } + }, + ); + } elsif ($options{cmd} eq 'RELOAD') { + my $cmd = $self->{pollers}->{$options{target}}->{engine_reload_command}; + $self->send_internal_action( + action => 'COMMAND', + target => $options{target}, + token => $self->generate_token(), + data => { + content => { + command => 'sudo ' . $cmd, + type => 'reload engine', + } + }, + ); + } elsif ($options{cmd} eq 'START') { + my $cmd = $self->{pollers}->{$options{target}}->{engine_start_command}; + $self->send_internal_action( + action => 'COMMAND', + target => $options{target}, + token => $self->generate_token(), + data => { + content => { + command => 'sudo ' . $cmd, + type => 'start engine', + } + }, + ); + } elsif ($options{cmd} eq 'STOP') { + my $cmd = $self->{pollers}->{$options{target}}->{engine_stop_command}; + $self->send_internal_action( + action => 'COMMAND', + target => $options{target}, + token => $self->generate_token(), + data => { + content => { + command => 'sudo ' . $cmd, + type => 'stop engine', + } + }, + ); + } elsif ($options{cmd} eq 'RESTARTCENTREONTRAPD') { + my $cmd = $self->{pollers}->{$options{target}}->{init_script_centreontrapd}; + $self->send_internal_action( + action => 'COMMAND', + target => $options{target}, + token => $self->generate_token(), + data => { + content => { + command => 'sudo service ' . $cmd . ' restart', + type => 'restart trap', + } + }, + ); + } elsif ($options{cmd} eq 'RELOADCENTREONTRAPD') { + my $cmd = $self->{pollers}->{$options{target}}->{init_script_centreontrapd}; + $self->send_internal_action( + action => 'COMMAND', + target => $options{target}, + token => $self->generate_token(), + data => { + content => { + command => 'sudo service ' . $cmd . ' reload', + type => 'reload trap', + } + }, + ); } } From eacbcaff48a069f583c84554d58137594f324e5a Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 23 Sep 2019 15:58:46 +0200 Subject: [PATCH 122/948] enh(centreon/autodiscovery): remove synclogs cron --- gorgone/modules/centreon/autodiscovery/class.pm | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/gorgone/modules/centreon/autodiscovery/class.pm b/gorgone/modules/centreon/autodiscovery/class.pm index 870fb7127b0..24804a2a582 100644 --- a/gorgone/modules/centreon/autodiscovery/class.pm +++ b/gorgone/modules/centreon/autodiscovery/class.pm @@ -370,22 +370,6 @@ sub run { callback => \&event, } ]; - - $self->send_internal_action( - action => 'ADDCRON', - data => { - content => [ - { - id => 'autodiscovery_syncdiscoverylogs', - target => undef, - timespec => '*/2 * * * *', - action => 'SYNCDISCOVERYLOGS', - parameters => {}, - keep_token => 1, - } - ] - } - ); $self->send_internal_action( action => 'ADDCRON', From fc8c87c07f3b0c79cc559495272346f9cde8056d Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 23 Sep 2019 15:59:22 +0200 Subject: [PATCH 123/948] enh(core/proxy): change synclogs time and timeout --- gorgone/modules/core/proxy/hooks.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/modules/core/proxy/hooks.pm b/gorgone/modules/core/proxy/hooks.pm index f20cc395603..a1ac0e2c987 100644 --- a/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/modules/core/proxy/hooks.pm @@ -81,8 +81,8 @@ sub init { my (%options) = @_; $synctime_lasttime = time(); - $synctime_option = defined($config->{synchistory_time}) ? $config->{synchistory_time} : 300; - $synctimeout_option = defined($config->{synchistory_timeout}) ? $config->{synchistory_timeout} : 120; + $synctime_option = defined($config->{synchistory_time}) ? $config->{synchistory_time} : 60; + $synctimeout_option = defined($config->{synchistory_timeout}) ? $config->{synchistory_timeout} : 30; $ping_option = defined($config->{ping}) ? $config->{ping} : 60; $config->{pong_discard_timeout} = defined($config->{pong_discard_timeout}) ? $config->{pong_discard_timeout} : 300; From 278d25b691cc992d7e4b88eeb3c3aeb65432bc53 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 23 Sep 2019 16:49:15 +0200 Subject: [PATCH 124/948] feat(centreon/legacycmd): add remote task and worker extcommands --- gorgone/modules/centreon/legacycmd/class.pm | 78 ++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/modules/centreon/legacycmd/class.pm index 1cf2d4737d5..a433eef969a 100644 --- a/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/modules/centreon/legacycmd/class.pm @@ -140,6 +140,32 @@ sub get_pollers_config { return 0; } +sub get_clapi_user { + my ($self, %options) = @_; + + my $clapi_user = (defined($connector->{config}->{clapi_user})) ? + $connector->{config}->{clapi_user} : 'admin'; + my ($status, $datas) = $self->{class_object_centreon}->custom_execute( + request => "SELECT contact_passwd " . + "FROM `contact` " . + "WHERE `contact_activate` = '1' AND `contact_alias` = '" . $clapi_user . "'", + mode => 2 + ); + if ($status == -1 || !defined($datas->[0])) { + $self->{logger}->writeLogError('[legacycmd] -class- cannot get configuration for clapi user'); + return -1; + } + my $clapi_password = $datas->[0]; + if ($clapi_password =~ m/^md5__(.*)/) { + $clapi_password = $1; + } + + $self->{clapi_user} = $clapi_user; + $self->{clapi_password} = $clapi_password; + + return 0; +} + sub execute_cmd { my ($self, %options) = @_; @@ -208,6 +234,23 @@ sub execute_cmd { } }, ); + + my $centreon_dir = (defined($connector->{config}->{centreon_dir})) ? + $connector->{config}->{centreon_dir} : '/usr/share/centreon'; + my $task_id = $options{param}; + my $cmd = $centreon_dir . '/bin/centreon -u ' . $self->{clapi_user} . ' -p ' . + $self->{clapi_password} . ' -w -o CentreonWorker -a createRemoteTask -v ' . $task_id; + $self->send_internal_action( + action => 'COMMAND', + target => undef, + token => $self->generate_token(), + data => { + content => { + command => $cmd, + type => 'CREATEREMOTETASK', + } + }, + ); } elsif ($options{cmd} eq 'SYNCTRAP') { my $cache_dir = (defined($connector->{config}->{cache_dir})) ? $connector->{config}->{cache_dir} : '/var/cache/centreon'; @@ -305,6 +348,39 @@ sub execute_cmd { } }, ); + } elsif ($options{cmd} eq 'STARTWORKER') { + my $centreon_dir = (defined($connector->{config}->{centreon_dir})) ? + $connector->{config}->{centreon_dir} : '/usr/share/centreon'; + my $cmd = $centreon_dir . '/bin/centreon -u ' . $self->{clapi_user} . ' -p ' . + $self->{clapi_password} . ' -w -o CentreonWorker -a processQueue'; + $self->send_internal_action( + action => 'COMMAND', + target => undef, + token => $self->generate_token(), + data => { + content => { + command => $cmd, + type => 'STARTWORKER', + } + }, + ); + } elsif ($options{cmd} eq 'CREATEREMOTETASK') { + my $centreon_dir = (defined($connector->{config}->{centreon_dir})) ? + $connector->{config}->{centreon_dir} : '/usr/share/centreon'; + my $task_id = $options{target}; + my $cmd = $centreon_dir . '/bin/centreon -u ' . $self->{clapi_user} . ' -p ' . + $self->{clapi_password} . ' -w -o CentreonWorker -a createRemoteTask -v ' . $task_id; + $self->send_internal_action( + action => 'COMMAND', + target => undef, + token => $self->generate_token(), + data => { + content => { + command => $cmd, + type => 'CREATEREMOTETASK', + } + }, + ); } } @@ -382,7 +458,7 @@ sub handle_centcore_dir { sub handle_cmd_files { my ($self, %options) = @_; - return if ($self->get_pollers_config() == -1); + return if ($self->get_pollers_config() == -1 || $self->get_clapi_user() == -1); $self->handle_centcore_cmd(); $self->handle_centcore_dir(); } From f569ea24eda9300c9f6764bb5db1b3ccabc495c8 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 23 Sep 2019 17:19:02 +0200 Subject: [PATCH 125/948] enh(core/proxy): no redirect needed for SENDEXPORTFILE command --- gorgone/modules/core/proxy/sshclient.pm | 9 --------- 1 file changed, 9 deletions(-) diff --git a/gorgone/modules/core/proxy/sshclient.pm b/gorgone/modules/core/proxy/sshclient.pm index 9f264018683..508441f53ce 100644 --- a/gorgone/modules/core/proxy/sshclient.pm +++ b/gorgone/modules/core/proxy/sshclient.pm @@ -279,15 +279,6 @@ sub action_remotecopy { } } ); - } elsif ($options{data}->{content}->{type} eq 'remote') { - $self->action_centcore( - data => { - content => { - command => 'SENDEXPORTFILE', - target => $options{target}, - } - } - ); } elsif ($options{data}->{content}->{type} eq 'trap') { $self->action_centcore( data => { From 2d4f2b0cf6c00e4f714b90eb51bae1f9a511ab68 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 23 Sep 2019 17:51:41 +0200 Subject: [PATCH 126/948] enh(proxy): standardize extcommands proxying --- gorgone/modules/centreon/legacycmd/class.pm | 60 ++++++++++++++++----- gorgone/modules/core/proxy/sshclient.pm | 28 +++------- 2 files changed, 55 insertions(+), 33 deletions(-) diff --git a/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/modules/centreon/legacycmd/class.pm index a433eef969a..5a549dc3986 100644 --- a/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/modules/centreon/legacycmd/class.pm @@ -197,7 +197,9 @@ sub execute_cmd { source => $cache_dir . '/config/engine/' . $options{target}, destination => $self->{pollers}->{$options{target}}->{cfg_dir} . '/', cache_dir => $cache_dir, - type => 'engine', + metadata => { + centcore_cmd => 'SENDCFGFILE', + } } }, ); @@ -211,7 +213,10 @@ sub execute_cmd { source => $cache_dir . '/config/broker/' . $options{target}, destination => $self->{pollers}->{$options{target}}->{centreonbroker_cfg_path} . '/', cache_dir => $cache_dir, - type => 'broker', + metadata => { + centcore_proxy => 1, + centcore_cmd => 'SENDCFGFILE', + } } }, ); @@ -230,7 +235,9 @@ sub execute_cmd { source => $cache_dir . '/config/export/' . $options{target}, destination => $remote_dir, cache_dir => $cache_dir, - type => 'remote', + metadata => { + centcore_cmd => 'SENDEXPORTFILE', + } } }, ); @@ -247,7 +254,9 @@ sub execute_cmd { data => { content => { command => $cmd, - type => 'CREATEREMOTETASK', + metadata => { + centcore_cmd => 'SENDEXPORTFILE', + } } }, ); @@ -266,7 +275,10 @@ sub execute_cmd { source => $cache_dir_trap . '/' . $options{target} . '/centreontrapd.sdb', destination => $self->{pollers}->{$options{target}}->{snmp_trapd_path_conf} . '/', cache_dir => $cache_dir, - type => 'trap', + metadata => { + centcore_proxy => 1, + centcore_cmd => 'SYNCTRAP', + } } }, ); @@ -279,7 +291,10 @@ sub execute_cmd { data => { content => { command => 'sudo ' . $cmd, - type => 'restart engine', + metadata => { + centcore_proxy => 1, + centcore_cmd => 'SYNCTRAP', + } } }, ); @@ -292,7 +307,10 @@ sub execute_cmd { data => { content => { command => 'sudo ' . $cmd, - type => 'reload engine', + metadata => { + centcore_proxy => 1, + centcore_cmd => 'RELOAD', + } } }, ); @@ -305,7 +323,10 @@ sub execute_cmd { data => { content => { command => 'sudo ' . $cmd, - type => 'start engine', + metadata => { + centcore_proxy => 1, + centcore_cmd => 'START', + } } }, ); @@ -318,7 +339,10 @@ sub execute_cmd { data => { content => { command => 'sudo ' . $cmd, - type => 'stop engine', + metadata => { + centcore_proxy => 1, + centcore_cmd => 'STOP', + } } }, ); @@ -331,7 +355,10 @@ sub execute_cmd { data => { content => { command => 'sudo service ' . $cmd . ' restart', - type => 'restart trap', + metadata => { + centcore_proxy => 1, + centcore_cmd => 'RESTARTCENTREONTRAPD', + } } }, ); @@ -344,7 +371,10 @@ sub execute_cmd { data => { content => { command => 'sudo service ' . $cmd . ' reload', - type => 'reload trap', + metadata => { + centcore_proxy => 1, + centcore_cmd => 'RELOADCENTREONTRAPD', + } } }, ); @@ -360,7 +390,9 @@ sub execute_cmd { data => { content => { command => $cmd, - type => 'STARTWORKER', + metadata => { + centcore_cmd => 'STARTWORKER', + } } }, ); @@ -377,7 +409,9 @@ sub execute_cmd { data => { content => { command => $cmd, - type => 'CREATEREMOTETASK', + metadata => { + centcore_cmd => 'CREATEREMOTETASK', + } } }, ); diff --git a/gorgone/modules/core/proxy/sshclient.pm b/gorgone/modules/core/proxy/sshclient.pm index 508441f53ce..1e4d1492e96 100644 --- a/gorgone/modules/core/proxy/sshclient.pm +++ b/gorgone/modules/core/proxy/sshclient.pm @@ -268,27 +268,15 @@ sub action_remotecopy { return ($code, $data) if ($code == -1); } - if (defined($options{data}->{content}->{type}) && $options{target_direct} == 0) { - # only one time (after broker) - if ($options{data}->{content}->{type} eq 'broker') { - $self->action_centcore( - data => { - content => { - command => 'SENDCFGFILE', - target => $options{target}, - } - } - ); - } elsif ($options{data}->{content}->{type} eq 'trap') { - $self->action_centcore( - data => { - content => { - command => 'SYNCTRAP', - target => $options{target}, - } + if (defined($options{data}->{content}->{metadata}->{centcore_proxy}) && $options{target_direct} == 0) { + $self->action_centcore( + data => { + content => { + command => $options{data}->{content}->{metadata}->{centcore_cmd}, + target => $options{target}, } - ); - } + } + ); } return (0, { message => 'send remotecopy succeeded' }); From a8bc1ae38d51049ccdb3f19ca25cc3fa6166e5e5 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 24 Sep 2019 10:08:19 +0200 Subject: [PATCH 127/948] enh(proxy): proxy_name by default + error message --- gorgone/centreon/script/gorgonecore.pm | 16 ++++++++++++++-- gorgone/config/gorgoned.yml | 2 -- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/gorgone/centreon/script/gorgonecore.pm b/gorgone/centreon/script/gorgonecore.pm index 0e805515f5a..5946040ffb4 100644 --- a/gorgone/centreon/script/gorgonecore.pm +++ b/gorgone/centreon/script/gorgonecore.pm @@ -120,7 +120,9 @@ sub init { if (!defined($self->{hostname}) || $self->{hostname} eq '') { $self->{hostname} = hostname(); } - + + $config->{gorgonecore}->{proxy_name} = + (defined($config->{gorgonecore}->{proxy_name}) && $config->{gorgonecore}->{proxy_name} ne '') ? $config->{gorgonecore}->{proxy_name} : 'proxy'; $self->{id} = $config->{gorgonecore}->{id}; $self->load_modules(); @@ -275,11 +277,21 @@ sub message_run { data => { msg => 'gorgone is stopping/restarting. Not proceed request.' }, json_encode => 1 ); - return ($token, 1, { message => "gorgone is stopping/restarting. Not proceed request." }); + return ($token, 1, { message => 'gorgone is stopping/restarting. Not proceed request.' }); } # Check Routing if (defined($target)) { + if (!defined($self->{modules_register}->{ $self->{modules_id}->{$config->{gorgonecore}->{proxy_name}} })) { + centreon::gorgone::common::add_history( + dbh => $self->{db_gorgone}, + code => 1, + token => $token, + data => { msg => 'no proxy configured. cannot manage target.' }, + json_encode => 1 + ); + return ($token, 1, { message => 'no proxy configured. cannot manage target.' }); + } $self->{modules_register}->{ $self->{modules_id}->{$config->{gorgonecore}->{proxy_name}} }->{routing}->( socket => $self->{internal_socket}, dbh => $self->{db_gorgone}, diff --git a/gorgone/config/gorgoned.yml b/gorgone/config/gorgoned.yml index 26ca428ef1a..3196021d0d2 100644 --- a/gorgone/config/gorgoned.yml +++ b/gorgone/config/gorgoned.yml @@ -42,8 +42,6 @@ gorgonecore: # in seconds #purge_sessions_time: 3600 #purge_history_time: 604800 - # shouldn't be changed - proxy_name: proxy modules: - name: httpserver package: modules::core::httpserver::hooks From 08d979ceeb528ef97f3c4f7f1a2916928b8396d4 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 24 Sep 2019 10:53:09 +0200 Subject: [PATCH 128/948] enh(core): add default configuration + module hook load check --- gorgone/centreon/script/gorgonecore.pm | 89 +++++++++++-------- gorgone/config/gorgoned.yml | 9 +- .../modules/centreon/autodiscovery/hooks.pm | 2 +- gorgone/modules/centreon/broker/hooks.pm | 3 +- gorgone/modules/centreon/engine/hooks.pm | 3 +- gorgone/modules/centreon/legacycmd/hooks.pm | 6 +- gorgone/modules/centreon/pollers/hooks.pm | 3 +- gorgone/modules/core/action/hooks.pm | 2 +- gorgone/modules/core/cron/hooks.pm | 2 +- gorgone/modules/core/httpserver/hooks.pm | 16 +++- gorgone/modules/core/proxy/hooks.pm | 13 +-- gorgone/modules/core/pull/hooks.pm | 2 +- gorgone/modules/core/register/hooks.pm | 9 +- gorgone/modules/plugins/newtest/hooks.pm | 2 +- gorgone/modules/plugins/scom/hooks.pm | 2 +- 15 files changed, 100 insertions(+), 63 deletions(-) diff --git a/gorgone/centreon/script/gorgonecore.pm b/gorgone/centreon/script/gorgonecore.pm index 5946040ffb4..ee9a094ee8c 100644 --- a/gorgone/centreon/script/gorgonecore.pm +++ b/gorgone/centreon/script/gorgonecore.pm @@ -187,47 +187,60 @@ sub handle_CHLD { $SIG{CHLD} = \&class_handle_CHLD; } +sub load_module { + my ($self, %options) = @_; + + return if (!defined($options{config_module}->{enable}) || $options{config_module}->{enable} eq 'false'); + + my $package = $options{config_module}->{package}; + (my $file = "$package.pm") =~ s{::}{/}g; + require $file; + $self->{logger}->writeLogInfo("[core] Module '" . $options{config_module}->{name} . "' is loading"); + $self->{modules_register}->{$package} = {}; + + foreach my $method_name (('register', 'routing', 'kill', 'kill_internal', 'gently', 'check', 'init')) { + unless ($self->{modules_register}->{$package}->{$method_name} = $package->can($method_name)) { + $self->{logger}->writeLogError("[core] No function '$method_name' for module '" . $options{config_module}->{name} . "'"); + exit(1); + } + } + + my ($loaded, $namespace, $name, $events) = $self->{modules_register}->{$package}->{register}->( + config => $options{config_module}, + config_core => $config->{gorgonecore}, + config_db_centreon => $config->{database}->{db_centreon}, + config_db_centstorage => $config->{database}->{db_centstorage}, + logger => $self->{logger}, + ); + if ($loaded == 0) { + $self->{logger}->writeLogError("[core] Module '" . $options{config_module}->{name} . "' cannot be loaded"); + return ; + } + + $self->{modules_id}->{$name} = $package; + + foreach my $event (@{$events}) { + $self->{modules_events}->{$event->{event}} = { + module => { + namespace => $namespace, + name => $name, + package => $package + }, + api => { + uri => $event->{uri}, + method => $event->{method} + } + }; + } + $self->{logger}->writeLogInfo("[core] Module '" . $options{config_module}->{name} . "' is loaded"); +} + sub load_modules { - my $self = shift; + my ($self) = @_; next if (!defined($config->{modules})); foreach my $module (@{$config->{modules}}) { - next if (!defined($module->{enable}) || $module->{enable} eq 'false'); - my $package = $module->{package}; - (my $file = "$package.pm") =~ s{::}{/}g; - require $file; - $self->{logger}->writeLogInfo("[core] Module '" . $module->{name} . "' is loading"); - $self->{modules_register}->{$package} = {}; - - foreach my $method_name (('register', 'routing', 'kill', 'kill_internal', 'gently', 'check', 'init')) { - unless ($self->{modules_register}->{$package}->{$method_name} = $package->can($method_name)) { - $self->{logger}->writeLogError("[core] No function '$method_name' for module '" . $module->{name} . "'"); - exit(1); - } - } - - my ($namespace, $name, $events) = $self->{modules_register}->{$package}->{register}->( - config => $module, - config_core => $config->{gorgonecore}, - config_db_centreon => $config->{database}->{db_centreon}, - config_db_centstorage => $config->{database}->{db_centstorage} - ); - $self->{modules_id}->{$name} = $package; - - foreach my $event (@{$events}) { - $self->{modules_events}->{$event->{event}} = { - module => { - namespace => $namespace, - name => $name, - package => $package - }, - api => { - uri => $event->{uri}, - method => $event->{method} - } - }; - } - $self->{logger}->writeLogInfo("[core] Module '" . $module->{name} . "' is loaded"); + $self->load_module(config_module => $module); } # Load internal functions @@ -282,7 +295,7 @@ sub message_run { # Check Routing if (defined($target)) { - if (!defined($self->{modules_register}->{ $self->{modules_id}->{$config->{gorgonecore}->{proxy_name}} })) { + if (!defined($self->{modules}->{ $self->{modules_id}->{$config->{gorgonecore}->{proxy_name}} })) { centreon::gorgone::common::add_history( dbh => $self->{db_gorgone}, code => 1, diff --git a/gorgone/config/gorgoned.yml b/gorgone/config/gorgoned.yml index 3196021d0d2..8fc3c49629f 100644 --- a/gorgone/config/gorgoned.yml +++ b/gorgone/config/gorgoned.yml @@ -73,18 +73,17 @@ modules: - name: proxy package: modules::core::proxy::hooks enable: false - pool: 5 + #pool: 5 # sync history each 5 minutes - synchistory_time: 300 + #synchistory_time: 300 # how much time before the response is in timeout - synchistory_timeout: 120 + #synchistory_timeout: 120 # ping each X seconds - ping: 60 + #ping: 60 - name: pollers package: modules::centreon::pollers::hooks enable: false - resync_time: 600 - name: register package: modules::core::register::hooks diff --git a/gorgone/modules/centreon/autodiscovery/hooks.pm b/gorgone/modules/centreon/autodiscovery/hooks.pm index bb85c1749f2..9f9341e2648 100644 --- a/gorgone/modules/centreon/autodiscovery/hooks.pm +++ b/gorgone/modules/centreon/autodiscovery/hooks.pm @@ -51,7 +51,7 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; $config_db_centreon = $options{config_db_centreon}; - return (NAMESPACE, NAME, EVENTS); + return (1, NAMESPACE, NAME, EVENTS); } sub init { diff --git a/gorgone/modules/centreon/broker/hooks.pm b/gorgone/modules/centreon/broker/hooks.pm index 13d8b95ad37..105c6e5bc48 100644 --- a/gorgone/modules/centreon/broker/hooks.pm +++ b/gorgone/modules/centreon/broker/hooks.pm @@ -45,7 +45,8 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; $config_db_centreon = $options{config_db_centreon}; - return (NAMESPACE, NAME, EVENTS); + $config->{cache_dir} = defined($config->{cache_dir}) ? $config->{cache_dir} : '/var/lib/centreon/broker-stats/'; + return (1, NAMESPACE, NAME, EVENTS); } sub init { diff --git a/gorgone/modules/centreon/engine/hooks.pm b/gorgone/modules/centreon/engine/hooks.pm index b8ecead2061..29ff68b8729 100644 --- a/gorgone/modules/centreon/engine/hooks.pm +++ b/gorgone/modules/centreon/engine/hooks.pm @@ -43,7 +43,8 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; - return (NAMESPACE, NAME, EVENTS); + $config->{command_file} = defined($config->{command_file}) ? $config->{command_file} : '/var/lib/centreon-engine/rw/centengine.cmd'; + return (1, NAMESPACE, NAME, EVENTS); } sub init { diff --git a/gorgone/modules/centreon/legacycmd/hooks.pm b/gorgone/modules/centreon/legacycmd/hooks.pm index 342fe7e93c1..4df5565d1d1 100644 --- a/gorgone/modules/centreon/legacycmd/hooks.pm +++ b/gorgone/modules/centreon/legacycmd/hooks.pm @@ -44,7 +44,11 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; $config_db_centreon = $options{config_db_centreon}; - return (NAMESPACE, NAME, EVENTS); + $config->{cmd_file} = defined($config->{cmd_file}) ? $config->{cmd_file} : '/var/lib/centreon/centcore.cmd'; + $config->{cache_dir} = defined($config->{cache_dir}) ? $config->{cache_dir} : '/var/cache/centreon/'; + $config->{cache_dir_trap} = defined($config->{cache_dir_trap}) ? $config->{cache_dir_trap} : '/etc/snmp/centreon_traps/'; + $config->{remote_dir} = defined($config->{remote_dir}) ? $config->{remote_dir} : '/var/lib/centreon/remote-data/'; + return (1, NAMESPACE, NAME, EVENTS); } sub init { diff --git a/gorgone/modules/centreon/pollers/hooks.pm b/gorgone/modules/centreon/pollers/hooks.pm index a7830b87587..7543c2165d5 100644 --- a/gorgone/modules/centreon/pollers/hooks.pm +++ b/gorgone/modules/centreon/pollers/hooks.pm @@ -44,7 +44,8 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; $config_db_centreon = $options{config_db_centreon}; - return (NAMESPACE, NAME, EVENTS); + $config->{resync_time} = defined($config->{resync_time}) && $config->{resync_time} =~ /(\d+)/ ? $1 : 600; + return (1, NAMESPACE, NAME, EVENTS); } sub init { diff --git a/gorgone/modules/core/action/hooks.pm b/gorgone/modules/core/action/hooks.pm index d5f805b1224..aadf850fc26 100644 --- a/gorgone/modules/core/action/hooks.pm +++ b/gorgone/modules/core/action/hooks.pm @@ -44,7 +44,7 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; - return (NAMESPACE, NAME, EVENTS); + return (1, NAMESPACE, NAME, EVENTS); } sub init { diff --git a/gorgone/modules/core/cron/hooks.pm b/gorgone/modules/core/cron/hooks.pm index e7e80e28d8f..2b9276959a7 100644 --- a/gorgone/modules/core/cron/hooks.pm +++ b/gorgone/modules/core/cron/hooks.pm @@ -46,7 +46,7 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; - return (NAMESPACE, NAME, EVENTS); + return (1, NAMESPACE, NAME, EVENTS); } sub init { diff --git a/gorgone/modules/core/httpserver/hooks.pm b/gorgone/modules/core/httpserver/hooks.pm index f63e04f22a1..0627d9127f3 100644 --- a/gorgone/modules/core/httpserver/hooks.pm +++ b/gorgone/modules/core/httpserver/hooks.pm @@ -39,10 +39,22 @@ my $stop = 0; sub register { my (%options) = @_; - + + my $loaded = 1; $config = $options{config}; $config_core = $options{config_core}; - return (NAMESPACE, NAME, EVENTS); + $config->{address} = defined($config->{address}) && $config->{address} ne '' ? $config->{address} : '0.0.0.0'; + $config->{port} = defined($config->{port}) && $config->{port} =~ /(\d+)/ ? $1 : 8080; + if (!defined($config->{auth}->{user}) || $config->{auth}->{user} =~ /^\s*$/) { + $self->{logger}->writeLogError('[httpserver] -hooks- auth user option mandatory'); + $loaded = 0; + } + if (!defined($config->{auth}->{password}) || $config->{auth}->{password} =~ /^\s*$/) { + $self->{logger}->writeLogError('[httpserver] -hooks- auth password option mandatory'); + $loaded = 0; + } + + return ($loaded, NAMESPACE, NAME, EVENTS); } sub init { diff --git a/gorgone/modules/core/proxy/hooks.pm b/gorgone/modules/core/proxy/hooks.pm index a1ac0e2c987..ed2fa507bfd 100644 --- a/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/modules/core/proxy/hooks.pm @@ -74,18 +74,19 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; - return (NAMESPACE, NAME, EVENTS); + + $synctime_option = defined($config->{synchistory_time}) ? $config->{synchistory_time} : 60; + $synctimeout_option = defined($config->{synchistory_timeout}) ? $config->{synchistory_timeout} : 30; + $ping_option = defined($config->{ping}) ? $config->{ping} : 60; + $config->{pong_discard_timeout} = defined($config->{pong_discard_timeout}) ? $config->{pong_discard_timeout} : 300; + $config->{pool} = defined($config->{pool}) && $config->{pool} =~ /(\d+)/ ? $1 : 5; + return (1, NAMESPACE, NAME, EVENTS); } sub init { my (%options) = @_; $synctime_lasttime = time(); - $synctime_option = defined($config->{synchistory_time}) ? $config->{synchistory_time} : 60; - $synctimeout_option = defined($config->{synchistory_timeout}) ? $config->{synchistory_timeout} : 30; - $ping_option = defined($config->{ping}) ? $config->{ping} : 60; - $config->{pong_discard_timeout} = defined($config->{pong_discard_timeout}) ? $config->{pong_discard_timeout} : 300; - $core_id = $options{id}; $external_socket = $options{external_socket}; $internal_socket = $options{internal_socket}; diff --git a/gorgone/modules/core/pull/hooks.pm b/gorgone/modules/core/pull/hooks.pm index 81296617ba2..a1f8b4609d4 100644 --- a/gorgone/modules/core/pull/hooks.pm +++ b/gorgone/modules/core/pull/hooks.pm @@ -41,7 +41,7 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; - return (NAMESPACE, NAME, EVENTS); + return (1, NAMESPACE, NAME, EVENTS); } sub init { diff --git a/gorgone/modules/core/register/hooks.pm b/gorgone/modules/core/register/hooks.pm index ed3754e8e78..71ac6452b83 100644 --- a/gorgone/modules/core/register/hooks.pm +++ b/gorgone/modules/core/register/hooks.pm @@ -40,10 +40,15 @@ my $stop = 0; sub register { my (%options) = @_; - + + my $loaded = 1; $config = $options{config}; $config_core = $options{config_core}; - return (NAMESPACE, NAME, EVENTS); + if (!defined($config->{config_file}) || $config->{config_file} =~ /^\s*$/) { + $self->{logger}->writeLogError('[register] -hooks- config_file option mandatory'); + $loaded = 0; + } + return ($loaded, NAMESPACE, NAME, EVENTS); } sub init { diff --git a/gorgone/modules/plugins/newtest/hooks.pm b/gorgone/modules/plugins/newtest/hooks.pm index 9bcd0f12b66..f6f5bcd98ad 100644 --- a/gorgone/modules/plugins/newtest/hooks.pm +++ b/gorgone/modules/plugins/newtest/hooks.pm @@ -50,7 +50,7 @@ sub register { $config_db_centstorage = $options{config_db_centstorage}; $config_db_centreon = $options{config_db_centreon}; $config_check_containers_time = defined($config->{check_containers_time}) ? $config->{check_containers_time} : 3600; - return (NAMESPACE, NAME, EVENTS); + return (1, NAMESPACE, NAME, EVENTS); } sub init { diff --git a/gorgone/modules/plugins/scom/hooks.pm b/gorgone/modules/plugins/scom/hooks.pm index 7c1bedda7cc..d4bc8f0852d 100644 --- a/gorgone/modules/plugins/scom/hooks.pm +++ b/gorgone/modules/plugins/scom/hooks.pm @@ -49,7 +49,7 @@ sub register { $config_core = $options{config_core}; $config_db_centstorage = $options{config_db_centstorage}; $config_check_containers_time = defined($config->{check_containers_time}) ? $config->{check_containers_time} : 3600; - return (NAMESPACE, NAME, EVENTS); + return (1, NAMESPACE, NAME, EVENTS); } sub init { From 0224a7b8d29e5c61b8019a2d53c69110e1307c79 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 24 Sep 2019 10:59:15 +0200 Subject: [PATCH 129/948] enh(core): avoid crash when we provide a wrong module --- gorgone/centreon/script/gorgonecore.pm | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/gorgone/centreon/script/gorgonecore.pm b/gorgone/centreon/script/gorgonecore.pm index ee9a094ee8c..9a06a5b5383 100644 --- a/gorgone/centreon/script/gorgonecore.pm +++ b/gorgone/centreon/script/gorgonecore.pm @@ -191,17 +191,24 @@ sub load_module { my ($self, %options) = @_; return if (!defined($options{config_module}->{enable}) || $options{config_module}->{enable} eq 'false'); + $self->{logger}->writeLogInfo("[core] Module '" . $options{config_module}->{name} . "' is loading"); my $package = $options{config_module}->{package}; (my $file = "$package.pm") =~ s{::}{/}g; - require $file; - $self->{logger}->writeLogInfo("[core] Module '" . $options{config_module}->{name} . "' is loading"); + eval { + local $SIG{__DIE__} = 'IGNORE'; + require $file; + }; + if ($@) { + $self->{logger}->writeLogInfo("[core] Module '" . $options{config_module}->{name} . "' cannot be loaded: " . $@); + return 0; + } $self->{modules_register}->{$package} = {}; foreach my $method_name (('register', 'routing', 'kill', 'kill_internal', 'gently', 'check', 'init')) { unless ($self->{modules_register}->{$package}->{$method_name} = $package->can($method_name)) { $self->{logger}->writeLogError("[core] No function '$method_name' for module '" . $options{config_module}->{name} . "'"); - exit(1); + return 0; } } @@ -214,7 +221,7 @@ sub load_module { ); if ($loaded == 0) { $self->{logger}->writeLogError("[core] Module '" . $options{config_module}->{name} . "' cannot be loaded"); - return ; + return 0; } $self->{modules_id}->{$name} = $package; @@ -232,7 +239,9 @@ sub load_module { } }; } + $self->{logger}->writeLogInfo("[core] Module '" . $options{config_module}->{name} . "' is loaded"); + return 1; } sub load_modules { From cdb59e862bb605f7f7ac8aaf4550f784c323495c Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 24 Sep 2019 11:31:00 +0200 Subject: [PATCH 130/948] enh(pollers): manage remote_server_centcore_ssh_proxy option --- gorgone/modules/centreon/pollers/class.pm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gorgone/modules/centreon/pollers/class.pm b/gorgone/modules/centreon/pollers/class.pm index 9ec15803b2c..8492f006f70 100644 --- a/gorgone/modules/centreon/pollers/class.pm +++ b/gorgone/modules/centreon/pollers/class.pm @@ -96,7 +96,7 @@ sub action_pollersresync { $self->send_log(code => centreon::gorgone::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action pollersresync proceed' }); my $request = " - SELECT id, name, localhost, ns_ip_address, ssh_port, remote_id + SELECT id, name, localhost, ns_ip_address, ssh_port, remote_id, remote_server_centcore_ssh_proxy FROM nagios_server WHERE ns_activate = '1' "; @@ -117,7 +117,8 @@ sub action_pollersresync { next; } - if (defined($_->[5]) && $_->[5] =~ /\d+/) { + # remote_server_centcore_ssh_proxy = 1 means: pass through the remote. otherwise directly. + if (defined($_->[5]) && $_->[5] =~ /\d+/ && $_->[6] == 1) { $register_subnodes->{$_->[5]} = [] if (!defined($register_subnodes->{$_->[5]})); push @{$register_subnodes->{$_->[5]}}, $_->[0]; next; From 3da4a2f7c116076e9eb25c39026e3a201c5871b1 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 24 Sep 2019 13:40:23 +0200 Subject: [PATCH 131/948] enh(core): add LOADMODULE command --- gorgone/centreon/gorgone/common.pm | 28 ++++++++++++++++++++++++++ gorgone/centreon/script/gorgonecore.pm | 23 ++++++++++++++++----- gorgone/test-client.pl | 5 +++++ 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/gorgone/centreon/gorgone/common.pm b/gorgone/centreon/gorgone/common.pm index 8b063c7023a..8cccd4bc623 100644 --- a/gorgone/centreon/gorgone/common.pm +++ b/gorgone/centreon/gorgone/common.pm @@ -301,6 +301,34 @@ sub is_handshake_done { # internal functions ####################### +sub loadmodule { + my (%options) = @_; + + my $data; + eval { + $data = JSON::XS->new->utf8->decode($options{data}); + }; + if ($@) { + return (1, { message => 'request not well formatted' }); + } + + if ($options{gorgone}->load_module(config_module => $data->{content})) { + $options{gorgone}->{modules_register}->{ $data->{content}->{package} }->{init}->( + id => $options{gorgone}->{id}, + logger => $options{gorgone}->{logger}, + poll => $options{gorgone}->{poll}, + external_socket => $options{gorgone}->{external_socket}, + internal_socket => $options{gorgone}->{internal_socket}, + dbh => $options{gorgone}->{db_gorgone}, + modules_events => $options{gorgone}->{modules_events}, + ); + return (0, { action => 'loadmodule', message => "module '$data->{content}->{name}' is loaded" }); + } + + # test if the module is already loaded + return (1, { action => 'loadmodule', message => "cannot load module '$data->{content}->{name}'" }, 'LOADMODULE'); +} + sub synclogs { my (%options) = @_; diff --git a/gorgone/centreon/script/gorgonecore.pm b/gorgone/centreon/script/gorgonecore.pm index 9a06a5b5383..57ab72b2b56 100644 --- a/gorgone/centreon/script/gorgonecore.pm +++ b/gorgone/centreon/script/gorgonecore.pm @@ -190,7 +190,20 @@ sub handle_CHLD { sub load_module { my ($self, %options) = @_; - return if (!defined($options{config_module}->{enable}) || $options{config_module}->{enable} eq 'false'); + if (!defined($options{config_module}->{name}) || $options{config_module}->{name} eq '') { + $self->{logger}->writeLogError('[core] No module name'); + return 0; + } + if (!defined($options{config_module}->{package}) || $options{config_module}->{package} eq '') { + $self->{logger}->writeLogError('[core] No package name'); + return 0; + } + if (defined($self->{modules_register}->{ $options{config_module}->{package} })) { + $self->{logger}->writeLogError("[core] package '$options{config_module}->{package}' already loaded"); + return 0; + } + + return 0 if (!defined($options{config_module}->{enable}) || $options{config_module}->{enable} eq 'false'); $self->{logger}->writeLogInfo("[core] Module '" . $options{config_module}->{name} . "' is loading"); my $package = $options{config_module}->{package}; @@ -253,7 +266,7 @@ sub load_modules { } # Load internal functions - foreach my $method_name (('putlog', 'getlog', 'kill', 'ping', 'constatus', 'setcoreid', 'synclogs')) { + foreach my $method_name (('putlog', 'getlog', 'kill', 'ping', 'constatus', 'setcoreid', 'synclogs', 'loadmodule')) { unless ($self->{internal_register}->{$method_name} = centreon::gorgone::common->can($method_name)) { $self->{logger}->writeLogError("[core] No function '$method_name'"); exit(1); @@ -271,7 +284,7 @@ sub message_run { my ($action, $token, $target, $data) = ($1, $2, $3, $4); # Check if not myself ;) - if (defined($target) && ($target eq '' || $target eq $self->{id})) { + if (defined($target) && ($target eq '' || (defined($self->{id}) && $target eq $self->{id}))) { $target = undef; } @@ -279,7 +292,7 @@ sub message_run { $token = centreon::gorgone::common::generate_token(); } - if ($action !~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID|SYNCLOGS)$/ && + if ($action !~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID|SYNCLOGS|LOADMODULE)$/ && !defined($target) && !defined($self->{modules_events}->{$action})) { centreon::gorgone::common::add_history( dbh => $self->{db_gorgone}, @@ -327,7 +340,7 @@ sub message_run { return ($token, 0); } - if ($action =~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID|SYNCLOGS)$/) { + if ($action =~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID|SYNCLOGS|LOADMODULE)$/) { my ($code, $response, $response_type) = $self->{internal_register}->{lc($action)}->( gorgone => $self, gorgone_config => $config, diff --git a/gorgone/test-client.pl b/gorgone/test-client.pl index 7858ba61d60..0fb7791b31d 100644 --- a/gorgone/test-client.pl +++ b/gorgone/test-client.pl @@ -148,6 +148,11 @@ sub read_response { #$client2->send_message(action => 'COMMAND', data => { content => { command => 'ls' } }, target => 150, # json_encode => 1); #$client2->send_message(action => 'CONSTATUS'); +$client2->send_message( + action => 'LOADMODULE', + data => { content => { name => 'engine', package => 'modules::centreon::engine::hooks', enable => 'true', command_file => 'plop' } }, + json_encode => 1 +); # It will transform #$client2->send_message(action => 'GETLOG', data => { cmd => 'ls' }, target => 120, From a47d99ae528b3d7f4aaa5f0f4351920418305a4f Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 24 Sep 2019 14:05:43 +0200 Subject: [PATCH 132/948] fix(core): logger self issue --- gorgone/modules/core/httpserver/hooks.pm | 4 ++-- gorgone/modules/core/register/hooks.pm | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gorgone/modules/core/httpserver/hooks.pm b/gorgone/modules/core/httpserver/hooks.pm index 0627d9127f3..2aeeb86744c 100644 --- a/gorgone/modules/core/httpserver/hooks.pm +++ b/gorgone/modules/core/httpserver/hooks.pm @@ -46,11 +46,11 @@ sub register { $config->{address} = defined($config->{address}) && $config->{address} ne '' ? $config->{address} : '0.0.0.0'; $config->{port} = defined($config->{port}) && $config->{port} =~ /(\d+)/ ? $1 : 8080; if (!defined($config->{auth}->{user}) || $config->{auth}->{user} =~ /^\s*$/) { - $self->{logger}->writeLogError('[httpserver] -hooks- auth user option mandatory'); + $options{logger}->writeLogError('[httpserver] -hooks- auth user option mandatory'); $loaded = 0; } if (!defined($config->{auth}->{password}) || $config->{auth}->{password} =~ /^\s*$/) { - $self->{logger}->writeLogError('[httpserver] -hooks- auth password option mandatory'); + $options{logger}->writeLogError('[httpserver] -hooks- auth password option mandatory'); $loaded = 0; } diff --git a/gorgone/modules/core/register/hooks.pm b/gorgone/modules/core/register/hooks.pm index 71ac6452b83..49296ac9e7e 100644 --- a/gorgone/modules/core/register/hooks.pm +++ b/gorgone/modules/core/register/hooks.pm @@ -45,7 +45,7 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; if (!defined($config->{config_file}) || $config->{config_file} =~ /^\s*$/) { - $self->{logger}->writeLogError('[register] -hooks- config_file option mandatory'); + $options{logger}->writeLogError('[register] -hooks- config_file option mandatory'); $loaded = 0; } return ($loaded, NAMESPACE, NAME, EVENTS); From 277e64b94c2378e32e59a26177fee25bcc0be6bd Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 24 Sep 2019 14:34:19 +0200 Subject: [PATCH 133/948] fix(core): fix modules hash --- gorgone/centreon/script/gorgonecore.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/centreon/script/gorgonecore.pm b/gorgone/centreon/script/gorgonecore.pm index 57ab72b2b56..aec0c47abaa 100644 --- a/gorgone/centreon/script/gorgonecore.pm +++ b/gorgone/centreon/script/gorgonecore.pm @@ -317,7 +317,7 @@ sub message_run { # Check Routing if (defined($target)) { - if (!defined($self->{modules}->{ $self->{modules_id}->{$config->{gorgonecore}->{proxy_name}} })) { + if (!defined($self->{modules_register}->{ $self->{modules_id}->{$config->{gorgonecore}->{proxy_name}} })) { centreon::gorgone::common::add_history( dbh => $self->{db_gorgone}, code => 1, From a4a20c2fe013bc69fcbcfa03042ee1ca1604dfeb Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 24 Sep 2019 15:52:51 +0200 Subject: [PATCH 134/948] enh(core): remove unused library --- gorgone/centreon/misc/objects/host.pm | 60 ------------------- gorgone/centreon/misc/objects/organization.pm | 44 -------------- 2 files changed, 104 deletions(-) delete mode 100644 gorgone/centreon/misc/objects/host.pm delete mode 100644 gorgone/centreon/misc/objects/organization.pm diff --git a/gorgone/centreon/misc/objects/host.pm b/gorgone/centreon/misc/objects/host.pm deleted file mode 100644 index 6cfee7ebb99..00000000000 --- a/gorgone/centreon/misc/objects/host.pm +++ /dev/null @@ -1,60 +0,0 @@ -# -# Copyright 2019 Centreon (http://www.centreon.com/) -# -# Centreon is a full-fledged industry-strength solution that meets -# the needs in IT infrastructure and application monitoring for -# service performance. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -package centreon::misc::objects::host; - -use strict; -use warnings; - -use base qw(centreon::misc::objects::object); - -sub new { - my ($class, %options) = @_; - my $self = $class->SUPER::new(%options); - - bless $self, $class; - return $self; -} - -# special options: -# 'organization_name' or 'organization_id' -# 'with_services' -sub get_hosts_by_organization { - my ($self, %options) = @_; - - my %defaults = (request => 'SELECT', tables => ['cfg_hosts'], fields => ['*']); - if (defined($options{organization_name})) { - $defaults{tables} = ['cfg_hosts', 'cfg_organizations']; - $defaults{where} = 'cfg_organizations.name = ' . $self->{db_centreon}->quote($options{organization_name}); - } elsif (defined($options{organization_id})) { - $defaults{where} = 'cfg_hosts.organization_id = ' . $self->{db_centreon}->quote($options{organization_id}); - } else { - $self->{logger}->writeLogError("Please specify 'organization_name' or 'organization_id' parameter."); - return (-1, undef); - } - if (defined($options{with_services})) { - push @{$defaults{tables}}, 'cfg_hosts_services_relations', 'cfg_services'; - $defaults{where} .= ' AND cfg_hosts.host_id = cfg_hosts_services_relations.host_host_id AND cfg_hosts_services_relations.service_service_id = cfg_services.service_id'; - } - - my $options_builder = {%defaults, %options}; - return $self->execute(%$options_builder); -} - -1; diff --git a/gorgone/centreon/misc/objects/organization.pm b/gorgone/centreon/misc/objects/organization.pm deleted file mode 100644 index 6b9887372ce..00000000000 --- a/gorgone/centreon/misc/objects/organization.pm +++ /dev/null @@ -1,44 +0,0 @@ -# -# Copyright 2019 Centreon (http://www.centreon.com/) -# -# Centreon is a full-fledged industry-strength solution that meets -# the needs in IT infrastructure and application monitoring for -# service performance. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -package centreon::misc::objects::organization; - -use strict; -use warnings; - -use base qw(centreon::misc::objects::object); - -sub new { - my ($class, %options) = @_; - my $self = $class->SUPER::new(%options); - - bless $self, $class; - return $self; -} - -sub get_organizations { - my ($self, %options) = @_; - - my %defaults = (request => 'SELECT', tables => ['cfg_organizations'], fields => ['*'], where => "active = '1'"); - my $options_builder = {%defaults, %options}; - return $self->execute(%$options_builder); -} - -1; From ef03ee1369ade2fc43f2f9b2d159ad0dcd73aee9 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 24 Sep 2019 16:21:58 +0200 Subject: [PATCH 135/948] fix(centreon/legacycmd): fix centcore dir file unlink,fix password --- gorgone/modules/centreon/legacycmd/class.pm | 24 ++++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/modules/centreon/legacycmd/class.pm index 5a549dc3986..6e58dc7c30d 100644 --- a/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/modules/centreon/legacycmd/class.pm @@ -151,11 +151,11 @@ sub get_clapi_user { "WHERE `contact_activate` = '1' AND `contact_alias` = '" . $clapi_user . "'", mode => 2 ); - if ($status == -1 || !defined($datas->[0])) { + if ($status == -1 || !defined($datas->[0][0])) { $self->{logger}->writeLogError('[legacycmd] -class- cannot get configuration for clapi user'); return -1; } - my $clapi_password = $datas->[0]; + my $clapi_password = $datas->[0][0]; if ($clapi_password =~ m/^md5__(.*)/) { $clapi_password = $1; } @@ -171,6 +171,12 @@ sub execute_cmd { chomp $options{target}; chomp $options{param} if (defined($options{param})); + + my $msg = "[legacycmd] -class- Handling command '" . $options{cmd} . "'"; + $msg .= ", Target: '" . $options{target} . "'" if (defined($options{target})); + $msg .= ", Parameters: '" . $options{param} . "'" if (defined($options{param})); + $self->{logger}->writeLogInfo($msg); + if ($options{cmd} eq 'EXTERNALCMD') { # TODO: need to remove illegal characters!! $self->send_internal_action( @@ -422,6 +428,7 @@ sub handle_file { my ($self, %options) = @_; require bytes; + $self->{logger}->writeLogDebug("[legacycmd] -class- Processing file '" . $options{file} . "'"); my $handle = $options{handle}; while (my $line = <$handle>) { if ($self->{stop} == 1) { @@ -439,7 +446,6 @@ sub handle_file { } } - $self->{logger}->writeLogDebug("[legacycmd] -class- process file '" . $options{file} . "'"); close($handle); unlink($options{file}); return 0; @@ -472,18 +478,20 @@ sub handle_centcore_dir { my ($code, $handle); foreach (@files) { next if ($_ =~ /^\./); - if ($_ =~ /_read$/) { + my $file = $self->{config}->{cmd_dir} . '/' . $_; + if ($file =~ /_read$/) { ($code, $handle) = $self->move_cmd_file( - dst => $self->{config}->{cmd_dir} . '/' . $_, + dst => $file, ); } else { ($code, $handle) = $self->move_cmd_file( - src => $self->{config}->{cmd_dir} . '/' . $_, - dst => $self->{config}->{cmd_dir} . '/' . $_ . '_read', + src => $file, + dst => $file . '_read', ); + $file .= '_read'; } return if ($code == -1); - if ($self->handle_file(handle => $handle, file => $self->{config}->{cmd_dir} . '/' . $_) == -1) { + if ($self->handle_file(handle => $handle, file => $file) == -1) { return ; } } From aa2ed6db4d214c288add02af625fd02d437dbd63 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 24 Sep 2019 16:22:42 +0200 Subject: [PATCH 136/948] enh(core/action): create destination directory --- gorgone/modules/core/action/class.pm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gorgone/modules/core/action/class.pm b/gorgone/modules/core/action/class.pm index 7c8bb0e9181..e106e49ab5f 100644 --- a/gorgone/modules/core/action/class.pm +++ b/gorgone/modules/core/action/class.pm @@ -31,6 +31,7 @@ use ZMQ::Constants qw(:all); use JSON::XS; use File::Basename; use File::Copy; +use File::Path qw(make_path); use MIME::Base64; use Digest::MD5::File qw(file_md5_hex); use Fcntl; @@ -183,6 +184,9 @@ sub action_processcopy { } elsif ($options{data}->{content}->{status} eq "end" && defined($options{data}->{content}->{md5})) { if ($options{data}->{content}->{md5} eq file_md5_hex($cache_file)) { if ($options{data}->{content}->{type} eq "archive") { + if (! -d $options{data}->{content}->{destination}) { + make_path($options{data}->{content}->{destination}); + } my ($error, $stdout, $exit_code) = centreon::misc::misc::backtick( command => "tar --no-overwrite-dir -zxf $cache_file -C '" . $options{data}->{content}->{destination} . "' .", timeout => (defined($options{timeout})) ? $options{timeout} : 10, From cb1027f68a19b9d02c374b401e3925670477aaad Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 24 Sep 2019 16:37:34 +0200 Subject: [PATCH 137/948] fix(proxy): delete the good target --- gorgone/modules/core/proxy/class.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/modules/core/proxy/class.pm b/gorgone/modules/core/proxy/class.pm index cd84c8e707b..70763728766 100644 --- a/gorgone/modules/core/proxy/class.pm +++ b/gorgone/modules/core/proxy/class.pm @@ -282,7 +282,7 @@ sub proxy { } ); $connector->{logger}->writeLogError("[proxy] -class- Send message problem for '$target': $msg"); - $connector->{clients}->{$target}->{delete} = 1; + $connector->{clients}->{$target_client}->{delete} = 1; } } elsif ($connector->{clients}->{$target_client}->{type} eq 'push_ssh') { my ($code, $decoded_data) = $connector->json_decode(argument => $data); From ba5668db3d81f3e09b863903222d2d95584e31e6 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 24 Sep 2019 17:15:44 +0200 Subject: [PATCH 138/948] fix(core/proxy): change engine_pipe to command_file --- gorgone/modules/core/proxy/sshclient.pm | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/gorgone/modules/core/proxy/sshclient.pm b/gorgone/modules/core/proxy/sshclient.pm index 1e4d1492e96..815d8c7fee5 100644 --- a/gorgone/modules/core/proxy/sshclient.pm +++ b/gorgone/modules/core/proxy/sshclient.pm @@ -178,9 +178,9 @@ sub action_enginecommand { $self->{logger}->writeLogError('[proxy] -sshclient- action_enginecommand: need command'); return (-1, { message => 'please set command' }); } - if (!defined($options{data}->{content}->{engine_pipe}) || $options{data}->{content}->{engine_pipe} eq '') { - $self->{logger}->writeLogError('[proxy] -sshclient- action_enginecommand: need engine_pipe'); - return (-1, { message => 'please set engine_pipe' }); + if (!defined($options{data}->{content}->{command_file}) || $options{data}->{content}->{command_file} eq '') { + $self->{logger}->writeLogError('[proxy] -sshclient- action_enginecommand: need command_file'); + return (-1, { message => 'please set command_file' }); } chomp $options{data}->{content}->{command}; @@ -196,21 +196,21 @@ sub action_enginecommand { ); } - my $ret = $self->{sftp}->stat_file(file => $options{data}->{content}->{engine_pipe}); + my $ret = $self->{sftp}->stat_file(file => $options{data}->{content}->{command_file}); if (!defined($ret)) { - return (-1, { message => "cannot stat file '$options{data}->{content}->{engine_pipe}': " . $self->{sftp}->get_msg_error() }); + return (-1, { message => "cannot stat file '$options{data}->{content}->{command_file}': " . $self->{sftp}->get_msg_error() }); } if ($ret->{type} != SSH_FILEXFER_TYPE_SPECIAL) { - return (-1, { message => "stat file '$options{data}->{content}->{engine_pipe}' is not a pipe file" }); + return (-1, { message => "stat file '$options{data}->{content}->{command_file}' is not a pipe file" }); } - my $file = $self->{sftp}->open(file => $options{data}->{content}->{engine_pipe}, accesstype => O_WRONLY|O_APPEND); + my $file = $self->{sftp}->open(file => $options{data}->{content}->{command_file}, accesstype => O_WRONLY|O_APPEND); if (!defined($file)) { - return (-1, { message => "cannot open stat file '$options{data}->{content}->{engine_pipe}': " . $self->{sftp}->error() }); + return (-1, { message => "cannot open stat file '$options{data}->{content}->{command_file}': " . $self->{sftp}->error() }); } if ($self->{sftp}->write(handle_file => $file, data => $options{data}->{content}->{command} . "\n") != Libssh::Session::SSH_OK) { - return (-1, { message => "cannot write stat file '$options{data}->{content}->{engine_pipe}': " . $self->{sftp}->error() }); + return (-1, { message => "cannot write stat file '$options{data}->{content}->{command_file}': " . $self->{sftp}->error() }); } $self->{logger}->writeLogDebug("[proxy] -sshclient- action_enginecommand '" . $options{data}->{content}->{command} . "' succeeded"); From 93da1acd9757b030c35a123881428f55af9e9b70 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 24 Sep 2019 17:16:19 +0200 Subject: [PATCH 139/948] fix(core/proxy): add target type check for remotecopy --- gorgone/modules/core/proxy/hooks.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gorgone/modules/core/proxy/hooks.pm b/gorgone/modules/core/proxy/hooks.pm index ed2fa507bfd..c91fc32099b 100644 --- a/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/modules/core/proxy/hooks.pm @@ -223,7 +223,8 @@ sub routing { my $bulk_actions; push @{$bulk_actions}, $data; - if ($options{action} eq 'REMOTECOPY') { + if ($options{action} eq 'REMOTECOPY' && defined($register_nodes->{$options{target}}) && + $register_nodes->{$options{target}}->{type} ne 'push_ssh') { $action = 'PROCESSCOPY'; $bulk_actions = prepare_remote_copy( dbh => $options{dbh}, From 3679ab6d8bdff5c9292a15cd51eff0a6b39ada50 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 24 Sep 2019 18:07:54 +0200 Subject: [PATCH 140/948] enh(core): add debug for sigchld --- gorgone/centreon/script/gorgonecore.pm | 1 + gorgone/modules/core/proxy/hooks.pm | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/gorgone/centreon/script/gorgonecore.pm b/gorgone/centreon/script/gorgonecore.pm index aec0c47abaa..2934ac6b4b1 100644 --- a/gorgone/centreon/script/gorgonecore.pm +++ b/gorgone/centreon/script/gorgonecore.pm @@ -181,6 +181,7 @@ sub handle_CHLD { my $child_pid; while (($child_pid = waitpid(-1, &WNOHANG)) > 0) { + $self->{logger}->writeLogInfo("[core] received SIGCLD signal (pid: $child_pid)"); $self->{return_child}->{$child_pid} = time(); } diff --git a/gorgone/modules/core/proxy/hooks.pm b/gorgone/modules/core/proxy/hooks.pm index c91fc32099b..26c25f92226 100644 --- a/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/modules/core/proxy/hooks.pm @@ -556,8 +556,6 @@ sub create_child { $pools->{$options{pool_id}} = { pid => $child_pid, ready => 0, running => 1 }; $pools_pid->{$child_pid} = $options{pool_id}; - #$nodes_pool->{$target} - # we sent proxyaddnode to sync foreach my $node_id (keys %$nodes_pool) { next if ($nodes_pool->{$node_id} != $options{pool_id}); From aa668a7a16ef4eff371cc8537a62b380d5209980 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 25 Sep 2019 09:41:21 +0200 Subject: [PATCH 141/948] enh(proxy): move proxaddnode --- gorgone/modules/core/proxy/hooks.pm | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/gorgone/modules/core/proxy/hooks.pm b/gorgone/modules/core/proxy/hooks.pm index 26c25f92226..13bc420dd84 100644 --- a/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/modules/core/proxy/hooks.pm @@ -143,6 +143,17 @@ sub routing { if ($options{action} eq 'PROXYREADY') { $pools->{$data->{pool_id}}->{ready} = 1; + # we sent proxyaddnode to sync + foreach my $node_id (keys %$nodes_pool) { + next if ($nodes_pool->{$node_id} != $data->{pool_id}); + routing( + socket => $internal_socket, + action => 'PROXYADDNODE', + target => $node_id, + data => JSON::XS->new->utf8->encode($register_nodes->{$node_id}), + dbh => $options{dbh} + ); + } return undef; } @@ -555,18 +566,6 @@ sub create_child { $options{logger}->writeLogInfo("[proxy] -hooks- PID $child_pid (gorgone-proxy) for pool id '" . $options{pool_id} . "'"); $pools->{$options{pool_id}} = { pid => $child_pid, ready => 0, running => 1 }; $pools_pid->{$child_pid} = $options{pool_id}; - - # we sent proxyaddnode to sync - foreach my $node_id (keys %$nodes_pool) { - next if ($nodes_pool->{$node_id} != $options{pool_id}); - routing( - socket => $internal_socket, - action => 'PROXYADDNODE', - target => $node_id, - data => JSON::XS->new->utf8->encode($register_nodes->{$node_id}), - dbh => $options{dbh} - ); - } } sub pull_request { From e74904b457dafd514320b1556ca13fae12ec7aa0 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 25 Sep 2019 09:42:36 +0200 Subject: [PATCH 142/948] update todo --- gorgone/TODO | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gorgone/TODO b/gorgone/TODO index 4babf61c047..e0e57e60ffe 100644 --- a/gorgone/TODO +++ b/gorgone/TODO @@ -3,3 +3,5 @@ - Add an action to load module dynamically - Add a broadcast action to update module (like logger in debug for example) - Move cleaning db in a module +- Add redis backend to store logs (we could disable synclog in redis mode) +- Add a websocket module to call gorgone From 91a80519dd1905edd4d248ebbbcaab30973a79f4 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 25 Sep 2019 10:00:59 +0200 Subject: [PATCH 143/948] enh(core): move directories for packaging --- gorgone/config/registernodes.yml | 2 +- gorgone/config/registernodes2.yml | 2 +- .../gorgone => gorgone/class}/clientzmq.pm | 24 ++--- .../gorgonecore.pm => gorgone/class/core.pm} | 70 +++++++-------- .../{centreon/misc => gorgone/class}/db.pm | 2 +- .../class}/http/backend/curl.pm | 16 ++-- .../class}/http/backend/curlconstants.pm | 2 +- .../class}/http/backend/lwp.pm | 12 +-- .../class}/http/backend/useragent.pm | 4 +- .../misc => gorgone/class}/http/http.pm | 20 ++--- .../{centreon/misc => gorgone/class}/lock.pm | 10 +-- .../misc => gorgone/class}/logger.pm | 6 +- .../gorgone => gorgone/class}/module.pm | 14 +-- gorgone/{centreon => gorgone/class}/script.pm | 16 ++-- .../object.pm => gorgone/class/sqlquery.pm} | 2 +- .../modules/centreon/autodiscovery/class.pm | 20 ++--- .../modules/centreon/autodiscovery/hooks.pm | 16 ++-- .../modules/centreon/broker/class.pm | 24 ++--- .../modules/centreon/broker/hooks.pm | 16 ++-- .../modules/centreon/engine/class.pm | 18 ++-- .../modules/centreon/engine/hooks.pm | 16 ++-- .../modules/centreon/legacycmd/class.pm | 22 ++--- .../modules/centreon/legacycmd/hooks.pm | 16 ++-- .../modules/centreon/pollers/class.pm | 26 +++--- .../modules/centreon/pollers/hooks.pm | 16 ++-- .../modules/core/action/class.pm | 22 ++--- .../modules/core/action/hooks.pm | 16 ++-- .../{ => gorgone}/modules/core/cron/class.pm | 26 +++--- .../{ => gorgone}/modules/core/cron/hooks.pm | 16 ++-- .../modules/core/httpserver/class.pm | 14 +-- .../modules/core/httpserver/hooks.pm | 16 ++-- .../{ => gorgone}/modules/core/proxy/class.pm | 40 ++++----- .../{ => gorgone}/modules/core/proxy/hooks.pm | 88 +++++++++---------- .../modules/core/proxy/sshclient.pm | 6 +- .../{ => gorgone}/modules/core/pull/hooks.pm | 16 ++-- .../modules/core/register/class.pm | 18 ++-- .../modules/core/register/hooks.pm | 16 ++-- .../modules/plugins/newtest/class.pm | 54 ++++++------ .../modules/plugins/newtest/hooks.pm | 18 ++-- .../libs/stubs/ManagementConsoleService.pm | 6 +- .../plugins/newtest/libs/stubs/errors.pm | 2 +- .../plugins/newtest/libs/wsdl/newtest.wsdl | 0 .../modules/plugins/scom/class.pm | 32 +++---- .../modules/plugins/scom/hooks.pm | 18 ++-- .../gorgone => gorgone/standard}/api.pm | 14 +-- .../common.pm => gorgone/standard/library.pm} | 2 +- .../misc => gorgone/standard}/misc.pm | 2 +- gorgone/gorgoned | 4 +- 48 files changed, 419 insertions(+), 419 deletions(-) rename gorgone/{centreon/gorgone => gorgone/class}/clientzmq.pm (86%) rename gorgone/{centreon/script/gorgonecore.pm => gorgone/class/core.pm} (88%) rename gorgone/{centreon/misc => gorgone/class}/db.pm (99%) rename gorgone/{centreon/misc => gorgone/class}/http/backend/curl.pm (97%) rename gorgone/{centreon/misc => gorgone/class}/http/backend/curlconstants.pm (94%) rename gorgone/{centreon/misc => gorgone/class}/http/backend/lwp.pm (97%) rename gorgone/{centreon/misc => gorgone/class}/http/backend/useragent.pm (92%) rename gorgone/{centreon/misc => gorgone/class}/http/http.pm (90%) rename gorgone/{centreon/misc => gorgone/class}/lock.pm (95%) rename gorgone/{centreon/misc => gorgone/class}/logger.pm (97%) rename gorgone/{centreon/gorgone => gorgone/class}/module.pm (91%) rename gorgone/{centreon => gorgone/class}/script.pm (91%) rename gorgone/{centreon/misc/objects/object.pm => gorgone/class/sqlquery.pm} (98%) rename gorgone/{ => gorgone}/modules/centreon/autodiscovery/class.pm (94%) rename gorgone/{ => gorgone}/modules/centreon/autodiscovery/hooks.pm (90%) rename gorgone/{ => gorgone}/modules/centreon/broker/class.pm (91%) rename gorgone/{ => gorgone}/modules/centreon/broker/hooks.pm (90%) rename gorgone/{ => gorgone}/modules/centreon/engine/class.pm (93%) rename gorgone/{ => gorgone}/modules/centreon/engine/hooks.pm (90%) rename gorgone/{ => gorgone}/modules/centreon/legacycmd/class.pm (96%) rename gorgone/{ => gorgone}/modules/centreon/legacycmd/hooks.pm (90%) rename gorgone/{ => gorgone}/modules/centreon/pollers/class.pm (86%) rename gorgone/{ => gorgone}/modules/centreon/pollers/hooks.pm (90%) rename gorgone/{ => gorgone}/modules/core/action/class.pm (93%) rename gorgone/{ => gorgone}/modules/core/action/hooks.pm (90%) rename gorgone/{ => gorgone}/modules/core/cron/class.pm (94%) rename gorgone/{ => gorgone}/modules/core/cron/hooks.pm (90%) rename gorgone/{ => gorgone}/modules/core/httpserver/class.pm (94%) rename gorgone/{ => gorgone}/modules/core/httpserver/hooks.pm (91%) rename gorgone/{ => gorgone}/modules/core/proxy/class.pm (90%) rename gorgone/{ => gorgone}/modules/core/proxy/hooks.pm (91%) rename gorgone/{ => gorgone}/modules/core/proxy/sshclient.pm (98%) rename gorgone/{ => gorgone}/modules/core/pull/hooks.pm (89%) rename gorgone/{ => gorgone}/modules/core/register/class.pm (89%) rename gorgone/{ => gorgone}/modules/core/register/hooks.pm (90%) rename gorgone/{ => gorgone}/modules/plugins/newtest/class.pm (90%) rename gorgone/{ => gorgone}/modules/plugins/newtest/hooks.pm (94%) rename gorgone/{ => gorgone}/modules/plugins/newtest/libs/stubs/ManagementConsoleService.pm (98%) rename gorgone/{ => gorgone}/modules/plugins/newtest/libs/stubs/errors.pm (87%) rename gorgone/{ => gorgone}/modules/plugins/newtest/libs/wsdl/newtest.wsdl (100%) rename gorgone/{ => gorgone}/modules/plugins/scom/class.pm (93%) rename gorgone/{ => gorgone}/modules/plugins/scom/hooks.pm (94%) rename gorgone/{centreon/gorgone => gorgone/standard}/api.pm (90%) rename gorgone/{centreon/gorgone/common.pm => gorgone/standard/library.pm} (99%) rename gorgone/{centreon/misc => gorgone/standard}/misc.pm (99%) diff --git a/gorgone/config/registernodes.yml b/gorgone/config/registernodes.yml index 04bd540bf5b..c95a7f496de 100644 --- a/gorgone/config/registernodes.yml +++ b/gorgone/config/registernodes.yml @@ -12,4 +12,4 @@ nodes: client_privkey: keys/central/privkey.pem cipher: "Cipher::AES" keysize: 32 - vector: 0123456789012345 \ No newline at end of file + vector: 0123456789012345 diff --git a/gorgone/config/registernodes2.yml b/gorgone/config/registernodes2.yml index a3ee536d23c..ab0010d0fd3 100644 --- a/gorgone/config/registernodes2.yml +++ b/gorgone/config/registernodes2.yml @@ -8,4 +8,4 @@ nodes: client_privkey: keys/central/privkey.pem cipher: "Cipher::AES" keysize: 32 - vector: 0123456789012345 \ No newline at end of file + vector: 0123456789012345 diff --git a/gorgone/centreon/gorgone/clientzmq.pm b/gorgone/gorgone/class/clientzmq.pm similarity index 86% rename from gorgone/centreon/gorgone/clientzmq.pm rename to gorgone/gorgone/class/clientzmq.pm index 3fd2ddb19b5..3a68a9df10c 100644 --- a/gorgone/centreon/gorgone/clientzmq.pm +++ b/gorgone/gorgone/class/clientzmq.pm @@ -18,11 +18,11 @@ # limitations under the License. # -package centreon::gorgone::clientzmq; +package gorgone::class::clientzmq; use strict; use warnings; -use centreon::gorgone::common; +use gorgone::standard::library; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); @@ -38,9 +38,9 @@ sub new { $connector->{cipher} = $options{cipher}; $connector->{vector} = $options{vector}; $connector->{symkey} = undef; - $connector->{server_pubkey} = centreon::gorgone::common::loadpubkey(pubkey => $options{server_pubkey}); - $connector->{client_pubkey} = centreon::gorgone::common::loadpubkey(pubkey => $options{client_pubkey}); - $connector->{client_privkey} = centreon::gorgone::common::loadprivkey(privkey => $options{client_privkey}); + $connector->{server_pubkey} = gorgone::standard::library::loadpubkey(pubkey => $options{server_pubkey}); + $connector->{client_pubkey} = gorgone::standard::library::loadpubkey(pubkey => $options{client_pubkey}); + $connector->{client_privkey} = gorgone::standard::library::loadprivkey(privkey => $options{client_privkey}); $connector->{target_type} = $options{target_type}; $connector->{target_path} = $options{target_path}; $connector->{ping} = defined($options{ping}) ? $options{ping} : -1; @@ -62,7 +62,7 @@ sub init { my ($self, %options) = @_; $self->{handshake} = 0; - $sockets->{$self->{identity}} = centreon::gorgone::common::connect_com( + $sockets->{$self->{identity}} = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', name => $self->{identity}, logger => $self->{logger}, type => $self->{target_type}, @@ -134,11 +134,11 @@ sub event { } $connectors->{$options{identity}}->{ping_time} = time(); while (1) { - my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $sockets->{$options{identity}}); + my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $sockets->{$options{identity}}); # in progress if ($connectors->{$options{identity}}->{handshake} == 0 || $connectors->{$options{identity}}->{handshake} == 1) { - my ($status, $symkey, $hostname) = centreon::gorgone::common::client_get_secret( + my ($status, $symkey, $hostname) = gorgone::standard::library::client_get_secret( privkey => $connectors->{$options{identity}}->{client_privkey}, message => $message ); @@ -152,7 +152,7 @@ sub event { $connectors->{$options{identity}}->{logger}->writeLogInfo("Client connected successfuly to '" . $connectors->{$options{identity}}->{target_type} . '//' . $connectors->{$options{identity}}->{target_path}); } } else { - my ($status, $data) = centreon::gorgone::common::uncrypt_message( + my ($status, $data) = gorgone::standard::library::uncrypt_message( message => $message, cipher => $connectors->{$options{identity}}->{cipher}, vector => $connectors->{$options{identity}}->{vector}, @@ -169,7 +169,7 @@ sub event { } } - last unless (centreon::gorgone::common::zmq_still_read(socket => $sockets->{$options{identity}})); + last unless (gorgone::standard::library::zmq_still_read(socket => $sockets->{$options{identity}})); } } @@ -177,7 +177,7 @@ sub send_message { my ($self, %options) = @_; if ($self->{handshake} == 0) { - my ($status, $ciphertext) = centreon::gorgone::common::client_helo_encrypt( + my ($status, $ciphertext) = gorgone::standard::library::client_helo_encrypt( identity => $self->{identity}, server_pubkey => $self->{server_pubkey}, client_pubkey => $self->{client_pubkey}, @@ -199,7 +199,7 @@ sub send_message { return (-1, 'Handshake issue'); } - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $sockets->{$self->{identity}}, cipher => $self->{cipher}, symkey => $self->{symkey}, diff --git a/gorgone/centreon/script/gorgonecore.pm b/gorgone/gorgone/class/core.pm similarity index 88% rename from gorgone/centreon/script/gorgonecore.pm rename to gorgone/gorgone/class/core.pm index 2934ac6b4b1..0ab5d61c46f 100644 --- a/gorgone/centreon/script/gorgonecore.pm +++ b/gorgone/gorgone/class/core.pm @@ -18,7 +18,7 @@ # limitations under the License. # -package centreon::script::gorgonecore; +package gorgone::class::core; use strict; use warnings; @@ -26,8 +26,8 @@ use POSIX ":sys_wait_h"; use Sys::Hostname; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); -use centreon::gorgone::common; -use centreon::misc::db; +use gorgone::standard::library; +use gorgone::class::db; use centreon::script; my ($gorgone, $config); @@ -78,7 +78,7 @@ sub init { $self->{logger}->writeLogError("[core] Can't find extra config file '$self->{opt_extra}'"); exit(1); } - $config = centreon::gorgone::common::read_config( + $config = gorgone::standard::library::read_config( config_file => $self->{opt_extra}, logger => $self->{logger} ); @@ -95,12 +95,12 @@ sub init { ; if (defined($config->{gorgonecore}->{external_com_type}) && $config->{gorgonecore}->{external_com_type} ne '') { - $self->{server_privkey} = centreon::gorgone::common::loadprivkey(logger => $self->{logger}, privkey => $config->{gorgonecore}->{privkey}); + $self->{server_privkey} = gorgone::standard::library::loadprivkey(logger => $self->{logger}, privkey => $config->{gorgonecore}->{privkey}); } # Database connections: # We add in gorgone database - $gorgone->{db_gorgone} = centreon::misc::db->new( + $gorgone->{db_gorgone} = gorgone::class::db->new( type => $config->{gorgonecore}->{gorgone_db_type}, db => $config->{gorgonecore}->{gorgone_db_name}, host => $config->{gorgonecore}->{gorgone_db_host}, @@ -268,7 +268,7 @@ sub load_modules { # Load internal functions foreach my $method_name (('putlog', 'getlog', 'kill', 'ping', 'constatus', 'setcoreid', 'synclogs', 'loadmodule')) { - unless ($self->{internal_register}->{$method_name} = centreon::gorgone::common->can($method_name)) { + unless ($self->{internal_register}->{$method_name} = gorgone::standard::library->can($method_name)) { $self->{logger}->writeLogError("[core] No function '$method_name'"); exit(1); } @@ -290,12 +290,12 @@ sub message_run { } if (!defined($token) || $token eq '') { - $token = centreon::gorgone::common::generate_token(); + $token = gorgone::standard::library::generate_token(); } if ($action !~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID|SYNCLOGS|LOADMODULE)$/ && !defined($target) && !defined($self->{modules_events}->{$action})) { - centreon::gorgone::common::add_history( + gorgone::standard::library::add_history( dbh => $self->{db_gorgone}, code => 1, token => $token, @@ -306,7 +306,7 @@ sub message_run { } if ($self->{stop} == 1) { - centreon::gorgone::common::add_history( + gorgone::standard::library::add_history( dbh => $self->{db_gorgone}, code => 1, token => $token, @@ -319,7 +319,7 @@ sub message_run { # Check Routing if (defined($target)) { if (!defined($self->{modules_register}->{ $self->{modules_id}->{$config->{gorgonecore}->{proxy_name}} })) { - centreon::gorgone::common::add_history( + gorgone::standard::library::add_history( dbh => $self->{db_gorgone}, code => 1, token => $token, @@ -370,13 +370,13 @@ sub message_run { sub router_internal_event { while (1) { - my ($identity, $message) = centreon::gorgone::common::zmq_read_message(socket => $gorgone->{internal_socket}); + my ($identity, $message) = gorgone::standard::library::zmq_read_message(socket => $gorgone->{internal_socket}); my ($token, $code, $response, $response_type) = $gorgone->message_run( message => $message, identity => $identity, router_type => 'internal', ); - centreon::gorgone::common::zmq_core_response( + gorgone::standard::library::zmq_core_response( socket => $gorgone->{internal_socket}, identity => $identity, response_type => $response_type, @@ -384,25 +384,25 @@ sub router_internal_event { code => $code, token => $token ); - last unless (centreon::gorgone::common::zmq_still_read(socket => $gorgone->{internal_socket})); + last unless (gorgone::standard::library::zmq_still_read(socket => $gorgone->{internal_socket})); } } sub handshake { my ($self, %options) = @_; - my ($identity, $message) = centreon::gorgone::common::zmq_read_message(socket => $self->{external_socket}); - my ($status, $key) = centreon::gorgone::common::is_handshake_done(dbh => $self->{db_gorgone}, identity => $identity); + my ($identity, $message) = gorgone::standard::library::zmq_read_message(socket => $self->{external_socket}); + my ($status, $key) = gorgone::standard::library::is_handshake_done(dbh => $self->{db_gorgone}, identity => $identity); if ($status == 1) { - ($status, my $response) = centreon::gorgone::common::uncrypt_message( + ($status, my $response) = gorgone::standard::library::uncrypt_message( cipher => $config->{gorgonecore}->{cipher}, message => $message, symkey => $key, vector => $config->{gorgonecore}->{vector} ); if ($status == 0 && $response =~ /^\[.*\]/) { - centreon::gorgone::common::update_identity(dbh => $self->{db_gorgone}, identity => $identity); + gorgone::standard::library::update_identity(dbh => $self->{db_gorgone}, identity => $identity); return ($identity, $key, $response); } @@ -411,7 +411,7 @@ sub handshake { } if ($status == -1) { - centreon::gorgone::common::zmq_core_response( + gorgone::standard::library::zmq_core_response( socket => $self->{external_socket}, identity => $identity, code => 1, @@ -420,14 +420,14 @@ sub handshake { return undef; } elsif ($status == 0) { # We try to uncrypt - ($status, my $client_pubkey) = centreon::gorgone::common::is_client_can_connect( + ($status, my $client_pubkey) = gorgone::standard::library::is_client_can_connect( privkey => $self->{server_privkey}, message => $message, logger => $self->{logger}, authorized_clients => $config->{gorgonecore}->{authorized_clients} ); if ($status == -1) { - centreon::gorgone::common::zmq_core_response( + gorgone::standard::library::zmq_core_response( socket => $self->{external_socket}, identity => $identity, code => 1, @@ -435,27 +435,27 @@ sub handshake { ); return undef; } - my ($status, $symkey) = centreon::gorgone::common::generate_symkey( + my ($status, $symkey) = gorgone::standard::library::generate_symkey( logger => $self->{logger}, cipher => $config->{gorgonecore}->{cipher}, keysize => $config->{gorgonecore}->{keysize} ); if ($status == -1) { - centreon::gorgone::common::zmq_core_response( + gorgone::standard::library::zmq_core_response( socket => $self->{external_socket}, identity => $identity, code => 1, data => { message => 'handshake issue' } ); } - if (centreon::gorgone::common::add_identity(dbh => $self->{db_gorgone}, identity => $identity, symkey => $symkey) == -1) { - centreon::gorgone::common::zmq_core_response( + if (gorgone::standard::library::add_identity(dbh => $self->{db_gorgone}, identity => $identity, symkey => $symkey) == -1) { + gorgone::standard::library::zmq_core_response( socket => $self->{external_socket}, identity => $identity, code => 1, data => { message => 'handshake issue' } ); } - if (centreon::gorgone::common::zmq_core_key_response(logger => $self->{logger}, socket => $self->{external_socket}, identity => $identity, + if (gorgone::standard::library::zmq_core_key_response(logger => $self->{logger}, socket => $self->{external_socket}, identity => $identity, client_pubkey => $client_pubkey, hostname => $self->{hostname}, symkey => $symkey) == -1) { - centreon::gorgone::common::zmq_core_response( + gorgone::standard::library::zmq_core_response( socket => $self->{external_socket}, identity => $identity, code => 1, data => { message => 'handshake issue' } ); @@ -468,7 +468,7 @@ sub send_message_parent { my (%options) = @_; if ($options{router_type} eq 'internal') { - centreon::gorgone::common::zmq_core_response( + gorgone::standard::library::zmq_core_response( socket => $gorgone->{internal_socket}, identity => $options{identity}, response_type => $options{response_type}, @@ -478,9 +478,9 @@ sub send_message_parent { ); } if ($options{router_type} eq 'external') { - my ($status, $key) = centreon::gorgone::common::is_handshake_done(dbh => $gorgone->{db_gorgone}, identity => $options{identity}); + my ($status, $key) = gorgone::standard::library::is_handshake_done(dbh => $gorgone->{db_gorgone}, identity => $options{identity}); return if ($status == 0); - centreon::gorgone::common::zmq_core_response( + gorgone::standard::library::zmq_core_response( socket => $gorgone->{external_socket}, identity => $options{identity}, response_type => $options{response_type}, @@ -503,7 +503,7 @@ sub router_external_event { identity => $identity, router_type => 'external', ); - centreon::gorgone::common::zmq_core_response( + gorgone::standard::library::zmq_core_response( socket => $gorgone->{external_socket}, identity => $identity, response_type => $response_type, cipher => $config->{gorgonecore}->{cipher}, @@ -513,7 +513,7 @@ sub router_external_event { data => $response ); } - last unless (centreon::gorgone::common::zmq_still_read(socket => $gorgone->{external_socket})); + last unless (gorgone::standard::library::zmq_still_read(socket => $gorgone->{external_socket})); } } @@ -585,7 +585,7 @@ sub run { $gorgone->{logger}->writeLogDebug("[core] gorgoned launched...."); $gorgone->{logger}->writeLogDebug("[core] PID $$"); - if (centreon::gorgone::common::add_history( + if (gorgone::standard::library::add_history( dbh => $gorgone->{db_gorgone}, code => 0, data => { msg => 'gorgoned is starting...' }, @@ -595,14 +595,14 @@ sub run { exit(1); } - $gorgone->{internal_socket} = centreon::gorgone::common::create_com( + $gorgone->{internal_socket} = gorgone::standard::library::create_com( type => $config->{gorgonecore}->{internal_com_type}, path => $config->{gorgonecore}->{internal_com_path}, zmq_type => 'ZMQ_ROUTER', name => 'router-internal', logger => $gorgone->{logger} ); if (defined($config->{gorgonecore}->{external_com_type}) && $config->{gorgonecore}->{external_com_type} ne '') { - $gorgone->{external_socket} = centreon::gorgone::common::create_com( + $gorgone->{external_socket} = gorgone::standard::library::create_com( type => $config->{gorgonecore}->{external_com_type}, path => $config->{gorgonecore}->{external_com_path}, zmq_type => 'ZMQ_ROUTER', name => 'router-external', diff --git a/gorgone/centreon/misc/db.pm b/gorgone/gorgone/class/db.pm similarity index 99% rename from gorgone/centreon/misc/db.pm rename to gorgone/gorgone/class/db.pm index a1847f080a3..fc9e2569efb 100644 --- a/gorgone/centreon/misc/db.pm +++ b/gorgone/gorgone/class/db.pm @@ -18,7 +18,7 @@ # limitations under the License. # -package centreon::misc::db; +package gorgone::class::db; use strict; use warnings; diff --git a/gorgone/centreon/misc/http/backend/curl.pm b/gorgone/gorgone/class/http/backend/curl.pm similarity index 97% rename from gorgone/centreon/misc/http/backend/curl.pm rename to gorgone/gorgone/class/http/backend/curl.pm index 9791f72244d..725495e406c 100644 --- a/gorgone/centreon/misc/http/backend/curl.pm +++ b/gorgone/gorgone/class/http/backend/curl.pm @@ -18,12 +18,12 @@ # limitations under the License. # -package centreon::misc::http::backend::curl; +package gorgone::class::http::backend::curl; use strict; use warnings; use URI; -use centreon::misc::misc; +use gorgone::standard::misc; sub new { my ($class, %options) = @_; @@ -38,19 +38,19 @@ sub new { sub check_options { my ($self, %options) = @_; - if (centreon::misc::misc::mymodule_load( + if (gorgone::standard::misc::mymodule_load( logger => $self->{logger}, module => 'Net::Curl::Easy', error_msg => "Cannot load module 'Net::Curl::Easy'." ) == 1) { return 1; } - if (centreon::misc::misc::mymodule_load( - logger => $self->{logger}, module => 'centreon::misc::http::backend::curlconstants', - error_msg => "Cannot load module 'centreon::misc::http::backend::curlconstants'." + if (gorgone::standard::misc::mymodule_load( + logger => $self->{logger}, module => 'gorgone::class::http::backend::curlconstants', + error_msg => "Cannot load module 'gorgone::class::http::backend::curlconstants'." ) == 1) { return 1; } - $self->{constant_cb} = \¢reon::misc::http::backend::curlconstants::get_constant_value; + $self->{constant_cb} = \&gorgone::class::http::backend::curlconstants::get_constant_value; if (!defined($options{request}->{curl_opt})) { $options{request}->{curl_opt} = []; @@ -221,7 +221,7 @@ sub set_extra_curl_opt { foreach (@{$options{request}->{curl_opt}}) { ($fields->{key}, $fields->{value}) = split /=>/; foreach my $label ('key', 'value') { - $fields->{$label} = centreon::misc::misc::trim($fields->{$label}); + $fields->{$label} = gorgone::standard::misc::trim($fields->{$label}); if ($fields->{$label} =~ /^CURLOPT|CURL/) { $fields->{$label} = $self->{constant_cb}->(name => $fields->{$label}); } diff --git a/gorgone/centreon/misc/http/backend/curlconstants.pm b/gorgone/gorgone/class/http/backend/curlconstants.pm similarity index 94% rename from gorgone/centreon/misc/http/backend/curlconstants.pm rename to gorgone/gorgone/class/http/backend/curlconstants.pm index 9cbe53abddd..41045c38bf0 100644 --- a/gorgone/centreon/misc/http/backend/curlconstants.pm +++ b/gorgone/gorgone/class/http/backend/curlconstants.pm @@ -18,7 +18,7 @@ # limitations under the License. # -package centreon::misc::http::backend::curlconstants; +package gorgone::class::http::backend::curlconstants; use strict; use warnings; diff --git a/gorgone/centreon/misc/http/backend/lwp.pm b/gorgone/gorgone/class/http/backend/lwp.pm similarity index 97% rename from gorgone/centreon/misc/http/backend/lwp.pm rename to gorgone/gorgone/class/http/backend/lwp.pm index 54e58540501..f396a35093d 100644 --- a/gorgone/centreon/misc/http/backend/lwp.pm +++ b/gorgone/gorgone/class/http/backend/lwp.pm @@ -18,14 +18,14 @@ # limitations under the License. # -package centreon::misc::http::backend::lwp; +package gorgone::class::http::backend::lwp; use strict; use warnings; -use centreon::misc::http::backend::useragent; +use gorgone::class::http::backend::useragent; use URI; use IO::Socket::SSL; -use centreon::misc::misc; +use gorgone::standard::misc; sub new { my ($class, %options) = @_; @@ -70,7 +70,7 @@ sub set_proxy { my ($self, %options) = @_; if (defined($options{request}->{proxypac}) && $options{request}->{proxypac} ne '') { - if (centreon::misc::misc::mymodule_load( + if (gorgone::standard::misc::mymodule_load( logger => $self->{logger}, module => 'HTTP::ProxyPAC', error_msg => "Cannot load module 'HTTP::ProxyPAC'." ) == 1) { @@ -110,7 +110,7 @@ sub request { keep_alive => 1, protocols_allowed => ['http', 'https'], timeout => $request_options->{timeout}, credentials => $request_options->{credentials}, username => $request_options->{username}, password => $request_options->{password}); if (defined($request_options->{cookies_file})) { - if (centreon::misc::misc::mymodule_load( + if (gorgone::standard::misc::mymodule_load( logger => $self->{logger}, module => 'HTTP::Cookies', error_msg => "Cannot load module 'HTTP::Cookies'." ) == 1) { @@ -187,7 +187,7 @@ sub request { } if (defined($request_options->{credentials}) && defined($request_options->{ntlmv2})) { - if (centreon::misc::misc::mymodule_load( + if (gorgone::standard::misc::mymodule_load( logger => $self->{logger}, module => 'Authen::NTLM', error_msg => "Cannot load module 'Authen::NTLM'." ) == 1) { diff --git a/gorgone/centreon/misc/http/backend/useragent.pm b/gorgone/gorgone/class/http/backend/useragent.pm similarity index 92% rename from gorgone/centreon/misc/http/backend/useragent.pm rename to gorgone/gorgone/class/http/backend/useragent.pm index 45f6386f5c2..e3c2d56b3ee 100644 --- a/gorgone/centreon/misc/http/backend/useragent.pm +++ b/gorgone/gorgone/class/http/backend/useragent.pm @@ -18,7 +18,7 @@ # limitations under the License. # -package centreon::misc::http::backend::useragent; +package gorgone::class::http::backend::useragent; use strict; use warnings; @@ -30,7 +30,7 @@ sub new { bless $self, $class; $self = LWP::UserAgent::new(@_); - $self->agent('centreon::misc::http::backend::useragent'); + $self->agent('gorgone::class::http::backend::useragent'); $self->{credentials} = $options{credentials} if defined($options{credentials}); $self->{username} = $options{username} if defined($options{username}); diff --git a/gorgone/centreon/misc/http/http.pm b/gorgone/gorgone/class/http/http.pm similarity index 90% rename from gorgone/centreon/misc/http/http.pm rename to gorgone/gorgone/class/http/http.pm index 8dc7da6f8e1..acc134a8246 100644 --- a/gorgone/centreon/misc/http/http.pm +++ b/gorgone/gorgone/class/http/http.pm @@ -18,11 +18,11 @@ # limitations under the License. # -package centreon::misc::http::http; +package gorgone::class::http::http; use strict; use warnings; -use centreon::misc::misc; +use gorgone::standard::misc; sub new { my ($class, %options) = @_; @@ -67,18 +67,18 @@ sub check_options { return 1; } - if ($options{request}->{http_backend} eq 'lwp' && centreon::misc::misc::mymodule_load( - logger => $options{logger}, module => 'centreon::misc::http::backend::lwp', - error_msg => "Cannot load module 'centreon::misc::http::backend::lwp'." + if ($options{request}->{http_backend} eq 'lwp' && gorgone::standard::misc::mymodule_load( + logger => $options{logger}, module => 'gorgone::class::http::backend::lwp', + error_msg => "Cannot load module 'gorgone::class::http::backend::lwp'." ) == 0) { - $self->{backend_lwp} = centreon::misc::http::backend::lwp->new(%options, logger => $self->{logger}); + $self->{backend_lwp} = gorgone::class::http::backend::lwp->new(%options, logger => $self->{logger}); } - if ($options{request}->{http_backend} eq 'curl' && centreon::misc::misc::mymodule_load( - logger => $options{logger}, module => 'centreon::misc::http::backend::curl', - error_msg => "Cannot load module 'centreon::misc::http::backend::curl'." + if ($options{request}->{http_backend} eq 'curl' && gorgone::standard::misc::mymodule_load( + logger => $options{logger}, module => 'gorgone::class::http::backend::curl', + error_msg => "Cannot load module 'gorgone::class::http::backend::curl'." ) == 0) { - $self->{backend_curl} = centreon::misc::http::backend::curl->new(%options, logger => $self->{logger}); + $self->{backend_curl} = gorgone::class::http::backend::curl->new(%options, logger => $self->{logger}); } if (($options{request}->{proto} ne 'http') && ($options{request}->{proto} ne 'https')) { diff --git a/gorgone/centreon/misc/lock.pm b/gorgone/gorgone/class/lock.pm similarity index 95% rename from gorgone/centreon/misc/lock.pm rename to gorgone/gorgone/class/lock.pm index a2dfc40db2b..064aafc1677 100644 --- a/gorgone/centreon/misc/lock.pm +++ b/gorgone/gorgone/class/lock.pm @@ -18,7 +18,7 @@ # limitations under the License. # -package centreon::misc::lock; +package gorgone::class::lock; use strict; use warnings; @@ -45,9 +45,9 @@ sub set { die "Failed to set lock for $self->{name}" if $self->is_set(); } -package centreon::misc::lock::file; +package gorgone::class::lock::file; -use base qw(centreon::misc::lock); +use base qw(gorgone::class::lock); sub new { my $class = shift; @@ -82,9 +82,9 @@ sub DESTROY { } } -package centreon::misc::lock::sql; +package gorgone::class::lock::sql; -use base qw(centreon::misc::lock); +use base qw(gorgone::class::lock); sub new { my $class = shift; diff --git a/gorgone/centreon/misc/logger.pm b/gorgone/gorgone/class/logger.pm similarity index 97% rename from gorgone/centreon/misc/logger.pm rename to gorgone/gorgone/class/logger.pm index 1a3a6082d73..82d68a5a42d 100644 --- a/gorgone/centreon/misc/logger.pm +++ b/gorgone/gorgone/class/logger.pm @@ -18,11 +18,11 @@ # limitations under the License. # -package centreon::misc::logger; +package gorgone::class::logger; =head1 NOM -centreon::misc::logger - Simple logging module +gorgone::class::logger - Simple logging module =head1 SYNOPSIS @@ -33,7 +33,7 @@ centreon::misc::logger - Simple logging module use centreon::polling; - my $logger = new centreon::misc::logger(); + my $logger = new gorgone::class::logger(); $logger->writeLogInfo("information"); diff --git a/gorgone/centreon/gorgone/module.pm b/gorgone/gorgone/class/module.pm similarity index 91% rename from gorgone/centreon/gorgone/module.pm rename to gorgone/gorgone/class/module.pm index 5ebc287fe2b..0fc45fecb47 100644 --- a/gorgone/centreon/gorgone/module.pm +++ b/gorgone/gorgone/class/module.pm @@ -18,13 +18,13 @@ # limitations under the License. # -package centreon::gorgone::module; +package gorgone::class::module; use strict; use warnings; -use centreon::gorgone::common; -use centreon::misc::misc; +use gorgone::standard::library; +use gorgone::standard::misc; use JSON::XS; use constant ACTION_BEGIN => 0; @@ -34,13 +34,13 @@ use constant ACTION_FINISH_OK => 2; sub generate_token { my ($self, %options) = @_; - return centreon::gorgone::common::generate_token(length => $options{length}); + return gorgone::standard::library::generate_token(length => $options{length}); } sub send_internal_action { my ($self, %options) = @_; - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $self->{internal_socket}, token => $options{token}, action => $options{action}, @@ -55,7 +55,7 @@ sub send_log { return if (!defined($options{token})); - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => (defined($options{socket})) ? $options{socket} : $self->{internal_socket}, action => 'PUTLOG', token => $options{token}, @@ -102,7 +102,7 @@ sub execute_shell_cmd { my ($self, %options) = @_; my $timeout = defined($options{timeout}) && $options{timeout} =~ /(\d+)/ ? $1 : 30; - my ($lerror, $stdout, $exit_code) = centreon::misc::misc::backtick( + my ($lerror, $stdout, $exit_code) = gorgone::standard::misc::backtick( command => $options{cmd}, logger => $self->{logger}, timeout => $timeout, diff --git a/gorgone/centreon/script.pm b/gorgone/gorgone/class/script.pm similarity index 91% rename from gorgone/centreon/script.pm rename to gorgone/gorgone/class/script.pm index 6d4da4ccfa7..fb8d1c9e45d 100644 --- a/gorgone/centreon/script.pm +++ b/gorgone/gorgone/class/script.pm @@ -18,16 +18,16 @@ # limitations under the License. # -package centreon::script; +package gorgone::class::script; use strict; use warnings; use FindBin; use Getopt::Long; use Pod::Usage; -use centreon::misc::logger; -use centreon::misc::db; -use centreon::misc::lock; +use gorgone::class::logger; +use gorgone::class::db; +use gorgone::class::lock; use vars qw($centreon_config); @@ -52,7 +52,7 @@ sub new { bless $self, $class; $self->{name} = $name; - $self->{logger} = centreon::misc::logger->new(); + $self->{logger} = gorgone::class::logger->new(); $self->{options} = { 'config=s' => \$self->{config_file}, 'logfile=s' => \$self->{log_file}, @@ -79,18 +79,18 @@ sub init { } if ($self->{centreon_db_conn}) { - $self->{cdb} = centreon::misc::db->new( + $self->{cdb} = gorgone::class::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::misc::lock::sql->new($self->{name}, dbc => $self->{cdb}); + $self->{lock} = gorgone::class::lock::sql->new($self->{name}, dbc => $self->{cdb}); $self->{lock}->set(); } if ($self->{centstorage_db_conn}) { - $self->{csdb} = centreon::misc::db->new( + $self->{csdb} = gorgone::class::db->new( db => $self->{centreon_config}->{centstorage_db}, host => $self->{centreon_config}->{db_host}, user => $self->{centreon_config}->{db_user}, diff --git a/gorgone/centreon/misc/objects/object.pm b/gorgone/gorgone/class/sqlquery.pm similarity index 98% rename from gorgone/centreon/misc/objects/object.pm rename to gorgone/gorgone/class/sqlquery.pm index 3e6c62dec03..1af61d36964 100644 --- a/gorgone/centreon/misc/objects/object.pm +++ b/gorgone/gorgone/class/sqlquery.pm @@ -18,7 +18,7 @@ # limitations under the License. # -package centreon::misc::objects::object; +package gorgone::class::sqlquery; use strict; use warnings; diff --git a/gorgone/modules/centreon/autodiscovery/class.pm b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm similarity index 94% rename from gorgone/modules/centreon/autodiscovery/class.pm rename to gorgone/gorgone/modules/centreon/autodiscovery/class.pm index 24804a2a582..a90ce5ba8bf 100644 --- a/gorgone/modules/centreon/autodiscovery/class.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm @@ -18,14 +18,14 @@ # limitations under the License. # -package modules::centreon::autodiscovery::class; +package gorgone::modules::centreon::autodiscovery::class; -use base qw(centreon::gorgone::module); +use base qw(gorgone::class::module); use strict; use warnings; -use centreon::gorgone::common; -use centreon::misc::objects::object; +use gorgone::standard::library; +use gorgone::class::sqlquery; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); use JSON::XS; @@ -310,7 +310,7 @@ sub action_updatediscoveryresults { sub event { while (1) { - my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $connector->{internal_socket}); + my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); $connector->{logger}->writeLogDebug("[autodiscovery] -class- Event: $message"); if ($message =~ /^\[ACK\]\s+\[(.*?)\]\s+(.*)$/m) { @@ -328,7 +328,7 @@ sub event { } } - last unless (centreon::gorgone::common::zmq_still_read(socket => $connector->{internal_socket})); + last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); } } @@ -336,7 +336,7 @@ sub run { my ($self, %options) = @_; # Database creation. We stay in the loop still there is an error - $self->{db_centreon} = centreon::misc::db->new( + $self->{db_centreon} = gorgone::class::db->new( dsn => $self->{config_db_centreon}->{dsn}, user => $self->{config_db_centreon}->{username}, password => $self->{config_db_centreon}->{password}, @@ -344,20 +344,20 @@ sub run { logger => $self->{logger} ); ##### Load objects ##### - $self->{class_object} = centreon::misc::objects::object->new( + $self->{class_object} = gorgone::class::sqlquery->new( logger => $self->{logger}, db_centreon => $self->{db_centreon} ); # Connect internal - $connector->{internal_socket} = centreon::gorgone::common::connect_com( + $connector->{internal_socket} = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', name => 'gorgoneautodiscovery', logger => $self->{logger}, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $connector->{internal_socket}, action => 'AUTODISCOVERYREADY', data => {}, diff --git a/gorgone/modules/centreon/autodiscovery/hooks.pm b/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm similarity index 90% rename from gorgone/modules/centreon/autodiscovery/hooks.pm rename to gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm index 9f9341e2648..ca293bffca9 100644 --- a/gorgone/modules/centreon/autodiscovery/hooks.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm @@ -18,13 +18,13 @@ # limitations under the License. # -package modules::centreon::autodiscovery::hooks; +package gorgone::modules::centreon::autodiscovery::hooks; use warnings; use strict; use JSON::XS; -use centreon::script::gorgonecore; -use modules::centreon::autodiscovery::class; +use gorgone::class::core; +use gorgone::modules::centreon::autodiscovery::class; use constant NAMESPACE => 'centreon'; use constant NAME => 'autodiscovery'; @@ -69,7 +69,7 @@ sub routing { }; if ($@) { $options{logger}->writeLogError("[autodiscovery] -hooks- Cannot decode json data: $@"); - centreon::gorgone::common::add_history( + gorgone::standard::library::add_history( dbh => $options{dbh}, code => 30, token => $options{token}, data => { msg => 'gorgoneautodiscovery: cannot decode json' }, @@ -83,8 +83,8 @@ sub routing { return undef; } - if (centreon::script::gorgonecore::waiting_ready(ready => \$autodiscovery->{ready}) == 0) { - centreon::gorgone::common::add_history( + if (gorgone::class::core::waiting_ready(ready => \$autodiscovery->{ready}) == 0) { + gorgone::standard::library::add_history( dbh => $options{dbh}, code => 30, token => $options{token}, data => { msg => 'gorgoneautodiscovery: still no ready' }, @@ -93,7 +93,7 @@ sub routing { return undef; } - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $options{socket}, identity => 'gorgoneautodiscovery', action => $options{action}, @@ -154,7 +154,7 @@ sub create_child { my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-autodiscovery'; - my $module = modules::centreon::autodiscovery::class->new( + my $module = gorgone::modules::centreon::autodiscovery::class->new( logger => $options{logger}, config_core => $config_core, config => $config, diff --git a/gorgone/modules/centreon/broker/class.pm b/gorgone/gorgone/modules/centreon/broker/class.pm similarity index 91% rename from gorgone/modules/centreon/broker/class.pm rename to gorgone/gorgone/modules/centreon/broker/class.pm index fe018fd29df..a789e6f1c97 100644 --- a/gorgone/modules/centreon/broker/class.pm +++ b/gorgone/gorgone/modules/centreon/broker/class.pm @@ -18,14 +18,14 @@ # limitations under the License. # -package modules::centreon::broker::class; +package gorgone::modules::centreon::broker::class; -use base qw(centreon::gorgone::module); +use base qw(gorgone::class::module); use strict; use warnings; -use centreon::gorgone::common; -use centreon::misc::objects::object; +use gorgone::standard::library; +use gorgone::class::sqlquery; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); use JSON::XS; @@ -90,7 +90,7 @@ sub action_brokerstats { $options{token} = $self->generate_token() if (!defined($options{token})); $self->send_log( - code => centreon::gorgone::module::ACTION_BEGIN, + code => gorgone::class::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action brokerstats starting' @@ -108,7 +108,7 @@ sub action_brokerstats { my ($status, $datas) = $self->{class_object}->custom_execute(request => $request, mode => 2); if ($status == -1) { $self->send_log( - code => centreon::gorgone::module::ACTION_FINISH_KO, + code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot find configuration' @@ -197,7 +197,7 @@ sub write_stats { sub event { while (1) { - my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $connector->{internal_socket}); + my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); $connector->{logger}->writeLogDebug("[broker] -class- Event: $message"); if ($message =~ /^\[ACK\]\s+\[(.*?)\]\s+(.*)$/m) { @@ -215,7 +215,7 @@ sub event { } } - last unless (centreon::gorgone::common::zmq_still_read(socket => $connector->{internal_socket})); + last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); } } @@ -223,7 +223,7 @@ sub run { my ($self, %options) = @_; # Database creation. We stay in the loop still there is an error - $self->{db_centreon} = centreon::misc::db->new( + $self->{db_centreon} = gorgone::class::db->new( dsn => $self->{config_db_centreon}->{dsn}, user => $self->{config_db_centreon}->{username}, password => $self->{config_db_centreon}->{password}, @@ -231,20 +231,20 @@ sub run { logger => $self->{logger} ); ##### Load objects ##### - $self->{class_object} = centreon::misc::objects::object->new( + $self->{class_object} = gorgone::class::sqlquery->new( logger => $self->{logger}, db_centreon => $self->{db_centreon} ); # Connect internal - $connector->{internal_socket} = centreon::gorgone::common::connect_com( + $connector->{internal_socket} = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', name => 'gorgonebroker', logger => $self->{logger}, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $connector->{internal_socket}, action => 'BROKERREADY', data => {}, diff --git a/gorgone/modules/centreon/broker/hooks.pm b/gorgone/gorgone/modules/centreon/broker/hooks.pm similarity index 90% rename from gorgone/modules/centreon/broker/hooks.pm rename to gorgone/gorgone/modules/centreon/broker/hooks.pm index 105c6e5bc48..ac47d0d8fc1 100644 --- a/gorgone/modules/centreon/broker/hooks.pm +++ b/gorgone/gorgone/modules/centreon/broker/hooks.pm @@ -18,13 +18,13 @@ # limitations under the License. # -package modules::centreon::broker::hooks; +package gorgone::modules::centreon::broker::hooks; use warnings; use strict; use JSON::XS; -use centreon::script::gorgonecore; -use modules::centreon::broker::class; +use gorgone::class::core; +use gorgone::modules::centreon::broker::class; use constant NAMESPACE => 'centreon'; use constant NAME => 'broker'; @@ -64,7 +64,7 @@ sub routing { }; if ($@) { $options{logger}->writeLogError("[broker] -hooks- Cannot decode json data: $@"); - centreon::gorgone::common::add_history( + gorgone::standard::library::add_history( dbh => $options{dbh}, code => 30, token => $options{token}, data => { msg => 'gorgonebroker: cannot decode json' }, @@ -78,8 +78,8 @@ sub routing { return undef; } - if (centreon::script::gorgonecore::waiting_ready(ready => \$broker->{ready}) == 0) { - centreon::gorgone::common::add_history( + if (gorgone::class::core::waiting_ready(ready => \$broker->{ready}) == 0) { + gorgone::standard::library::add_history( dbh => $options{dbh}, code => 30, token => $options{token}, data => { msg => 'gorgonebroker: still no ready' }, @@ -88,7 +88,7 @@ sub routing { return undef; } - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $options{socket}, identity => 'gorgonebroker', action => $options{action}, @@ -149,7 +149,7 @@ sub create_child { my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-broker'; - my $module = modules::centreon::broker::class->new( + my $module = gorgone::modules::centreon::broker::class->new( logger => $options{logger}, config_core => $config_core, config => $config, diff --git a/gorgone/modules/centreon/engine/class.pm b/gorgone/gorgone/modules/centreon/engine/class.pm similarity index 93% rename from gorgone/modules/centreon/engine/class.pm rename to gorgone/gorgone/modules/centreon/engine/class.pm index be85f2d853c..adb39a98e43 100644 --- a/gorgone/modules/centreon/engine/class.pm +++ b/gorgone/gorgone/modules/centreon/engine/class.pm @@ -18,15 +18,15 @@ # limitations under the License. # -package modules::centreon::engine::class; +package gorgone::modules::centreon::engine::class; -use base qw(centreon::gorgone::module); +use base qw(gorgone::class::module); use strict; use warnings; use JSON::XS; -use centreon::gorgone::common; -use centreon::misc::misc; +use gorgone::standard::library; +use gorgone::standard::misc; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); @@ -178,7 +178,7 @@ sub action_enginecommand { sub action_run { my ($self, %options) = @_; - my $socket_log = centreon::gorgone::common::connect_com( + my $socket_log = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', name => 'gorgoneengine-'. $$, logger => $self->{logger}, @@ -235,7 +235,7 @@ sub create_child { sub event { while (1) { - my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $connector->{internal_socket}); + my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); $connector->{logger}->writeLogDebug("[engine] -class- Event: $message"); @@ -243,7 +243,7 @@ sub event { $connector->create_child(message => $message); } - last unless (centreon::gorgone::common::zmq_still_read(socket => $connector->{internal_socket})); + last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); } } @@ -251,14 +251,14 @@ sub run { my ($self, %options) = @_; # Connect internal - $connector->{internal_socket} = centreon::gorgone::common::connect_com( + $connector->{internal_socket} = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', name => 'gorgoneengine', logger => $self->{logger}, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $connector->{internal_socket}, action => 'ENGINEREADY', data => { }, json_encode => 1 diff --git a/gorgone/modules/centreon/engine/hooks.pm b/gorgone/gorgone/modules/centreon/engine/hooks.pm similarity index 90% rename from gorgone/modules/centreon/engine/hooks.pm rename to gorgone/gorgone/modules/centreon/engine/hooks.pm index 29ff68b8729..958b081532e 100644 --- a/gorgone/modules/centreon/engine/hooks.pm +++ b/gorgone/gorgone/modules/centreon/engine/hooks.pm @@ -18,13 +18,13 @@ # limitations under the License. # -package modules::centreon::engine::hooks; +package gorgone::modules::centreon::engine::hooks; use warnings; use strict; use JSON::XS; -use centreon::script::gorgonecore; -use modules::centreon::engine::class; +use gorgone::class::core; +use gorgone::modules::centreon::engine::class; use constant NAMESPACE => 'centreon'; use constant NAME => 'engine'; @@ -62,7 +62,7 @@ sub routing { }; if ($@) { $options{logger}->writeLogError("[engine] -hooks- Cannot decode json data: $@"); - centreon::gorgone::common::add_history( + gorgone::standard::library::add_history( dbh => $options{dbh}, code => 30, token => $options{token}, data => { msg => 'gorgoneengine: cannot decode json' }, @@ -76,8 +76,8 @@ sub routing { return undef; } - if (centreon::script::gorgonecore::waiting_ready(ready => \$engine->{ready}) == 0) { - centreon::gorgone::common::add_history( + if (gorgone::class::core::waiting_ready(ready => \$engine->{ready}) == 0) { + gorgone::standard::library::add_history( dbh => $options{dbh}, code => 30, token => $options{token}, data => { msg => 'gorgoneengine: still no ready' }, @@ -86,7 +86,7 @@ sub routing { return undef; } - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $options{socket}, identity => 'gorgoneengine', action => $options{action}, @@ -147,7 +147,7 @@ sub create_child { my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-engine'; - my $module = modules::centreon::engine::class->new( + my $module = gorgone::modules::centreon::engine::class->new( logger => $options{logger}, config_core => $config_core, config => $config, diff --git a/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm similarity index 96% rename from gorgone/modules/centreon/legacycmd/class.pm rename to gorgone/gorgone/modules/centreon/legacycmd/class.pm index 6e58dc7c30d..c1e922c3379 100644 --- a/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -18,15 +18,15 @@ # limitations under the License. # -package modules::centreon::legacycmd::class; +package gorgone::modules::centreon::legacycmd::class; -use base qw(centreon::gorgone::module); +use base qw(gorgone::class::module); use strict; use warnings; -use centreon::gorgone::common; -use centreon::misc::misc; -use centreon::misc::objects::object; +use gorgone::standard::library; +use gorgone::standard::misc; +use gorgone::class::sqlquery; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); use File::Copy; @@ -507,7 +507,7 @@ sub handle_cmd_files { sub event { while (1) { - my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $connector->{internal_socket}); + my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); $connector->{logger}->writeLogDebug("[legacycmd] -class- Event: $message"); if ($message =~ /^\[(.*?)\]/) { @@ -519,7 +519,7 @@ sub event { } } - last unless (centreon::gorgone::common::zmq_still_read(socket => $connector->{internal_socket})); + last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); } } @@ -527,27 +527,27 @@ sub run { my ($self, %options) = @_; # Connect internal - $connector->{internal_socket} = centreon::gorgone::common::connect_com( + $connector->{internal_socket} = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', name => 'gorgonelegacycmd', logger => $self->{logger}, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $connector->{internal_socket}, action => 'LEGACYCMDREADY', data => {}, json_encode => 1 ); - $self->{db_centreon} = centreon::misc::db->new( + $self->{db_centreon} = gorgone::class::db->new( dsn => $self->{config_db_centreon}->{dsn}, user => $self->{config_db_centreon}->{username}, password => $self->{config_db_centreon}->{password}, force => 2, logger => $self->{logger} ); - $self->{class_object_centreon} = centreon::misc::objects::object->new( + $self->{class_object_centreon} = gorgone::class::sqlquery->new( logger => $self->{logger}, db_centreon => $self->{db_centreon} ); diff --git a/gorgone/modules/centreon/legacycmd/hooks.pm b/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm similarity index 90% rename from gorgone/modules/centreon/legacycmd/hooks.pm rename to gorgone/gorgone/modules/centreon/legacycmd/hooks.pm index 4df5565d1d1..416d872afa6 100644 --- a/gorgone/modules/centreon/legacycmd/hooks.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm @@ -18,12 +18,12 @@ # limitations under the License. # -package modules::centreon::legacycmd::hooks; +package gorgone::modules::centreon::legacycmd::hooks; use warnings; use strict; -use centreon::script::gorgonecore; -use modules::centreon::legacycmd::class; +use gorgone::class::core; +use gorgone::modules::centreon::legacycmd::class; use JSON::XS; use constant NAMESPACE => 'centreon'; @@ -66,7 +66,7 @@ sub routing { }; if ($@) { $options{logger}->writeLogError("[legacycmd] -hooks- Cannot decode json data: $@"); - centreon::gorgone::common::add_history( + gorgone::standard::library::add_history( dbh => $options{dbh}, code => 10, token => $options{token}, data => { message => 'gorgone-legacycmd: cannot decode json' }, @@ -80,8 +80,8 @@ sub routing { return undef; } - if (centreon::script::gorgonecore::waiting_ready(ready => \$legacycmd->{ready}) == 0) { - centreon::gorgone::common::add_history( + if (gorgone::class::core::waiting_ready(ready => \$legacycmd->{ready}) == 0) { + gorgone::standard::library::add_history( dbh => $options{dbh}, code => 10, token => $options{token}, data => { message => 'gorgone-legacycmd: still no ready' }, @@ -90,7 +90,7 @@ sub routing { return undef; } - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $options{socket}, identity => 'gorgonelegacycmd', action => $options{action}, @@ -152,7 +152,7 @@ sub create_child { my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-legacycmd'; - my $module = modules::centreon::legacycmd::class->new( + my $module = gorgone::modules::centreon::legacycmd::class->new( logger => $options{logger}, config_core => $config_core, config => $config, diff --git a/gorgone/modules/centreon/pollers/class.pm b/gorgone/gorgone/modules/centreon/pollers/class.pm similarity index 86% rename from gorgone/modules/centreon/pollers/class.pm rename to gorgone/gorgone/modules/centreon/pollers/class.pm index 8492f006f70..d79b8841596 100644 --- a/gorgone/modules/centreon/pollers/class.pm +++ b/gorgone/gorgone/modules/centreon/pollers/class.pm @@ -18,15 +18,15 @@ # limitations under the License. # -package modules::centreon::pollers::class; +package gorgone::modules::centreon::pollers::class; -use base qw(centreon::gorgone::module); +use base qw(gorgone::class::module); use strict; use warnings; -use centreon::gorgone::common; -use centreon::misc::objects::object; -use centreon::misc::http::http; +use gorgone::standard::library; +use gorgone::class::sqlquery; +use gorgone::class::http::http; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); use MIME::Base64; @@ -93,7 +93,7 @@ sub action_pollersresync { $options{token} = $self->generate_token() if (!defined($options{token})); - $self->send_log(code => centreon::gorgone::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action pollersresync proceed' }); + $self->send_log(code => gorgone::class::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action pollersresync proceed' }); my $request = " SELECT id, name, localhost, ns_ip_address, ssh_port, remote_id, remote_server_centcore_ssh_proxy @@ -102,7 +102,7 @@ sub action_pollersresync { "; my ($status, $datas) = $self->{class_object}->custom_execute(request => $request, mode => 2); if ($status == -1) { - $self->send_log(code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot find pollers configuration' }); + $self->send_log(code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot find pollers configuration' }); $self->{logger}->writeLogError("[pollers] -class- cannot find pollers configuration"); return 1; } @@ -154,7 +154,7 @@ sub action_pollersresync { sub event { while (1) { - my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $connector->{internal_socket}); + my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); $connector->{logger}->writeLogDebug("[pollers] -class- Event: $message"); if ($message =~ /^\[(.*?)\]/) { @@ -166,7 +166,7 @@ sub event { } } - last unless (centreon::gorgone::common::zmq_still_read(socket => $connector->{internal_socket})); + last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); } } @@ -174,7 +174,7 @@ sub run { my ($self, %options) = @_; # Database creation. We stay in the loop still there is an error - $self->{db_centreon} = centreon::misc::db->new( + $self->{db_centreon} = gorgone::class::db->new( dsn => $self->{config_db_centreon}->{dsn}, user => $self->{config_db_centreon}->{username}, password => $self->{config_db_centreon}->{password}, @@ -182,17 +182,17 @@ sub run { logger => $self->{logger} ); ##### Load objects ##### - $self->{class_object} = centreon::misc::objects::object->new(logger => $self->{logger}, db_centreon => $self->{db_centreon}); + $self->{class_object} = gorgone::class::sqlquery->new(logger => $self->{logger}, db_centreon => $self->{db_centreon}); # Connect internal - $connector->{internal_socket} = centreon::gorgone::common::connect_com( + $connector->{internal_socket} = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', name => 'gorgonepollers', logger => $self->{logger}, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $connector->{internal_socket}, action => 'POLLERSREADY', data => { }, json_encode => 1 diff --git a/gorgone/modules/centreon/pollers/hooks.pm b/gorgone/gorgone/modules/centreon/pollers/hooks.pm similarity index 90% rename from gorgone/modules/centreon/pollers/hooks.pm rename to gorgone/gorgone/modules/centreon/pollers/hooks.pm index 7543c2165d5..d18e5895457 100644 --- a/gorgone/modules/centreon/pollers/hooks.pm +++ b/gorgone/gorgone/modules/centreon/pollers/hooks.pm @@ -18,13 +18,13 @@ # limitations under the License. # -package modules::centreon::pollers::hooks; +package gorgone::modules::centreon::pollers::hooks; use warnings; use strict; use JSON::XS; -use centreon::script::gorgonecore; -use modules::centreon::pollers::class; +use gorgone::class::core; +use gorgone::modules::centreon::pollers::class; use constant NAMESPACE => 'centreon'; use constant NAME => 'pollers'; @@ -63,7 +63,7 @@ sub routing { }; if ($@) { $options{logger}->writeLogError("[pollers] -hooks- Cannot decode json data: $@"); - centreon::gorgone::common::add_history( + gorgone::standard::library::add_history( dbh => $options{dbh}, code => 10, token => $options{token}, data => { message => 'gorgonepollers: cannot decode json' }, @@ -77,8 +77,8 @@ sub routing { return undef; } - if (centreon::script::gorgonecore::waiting_ready(ready => \$pollers->{ready}) == 0) { - centreon::gorgone::common::add_history( + if (gorgone::class::core::waiting_ready(ready => \$pollers->{ready}) == 0) { + gorgone::standard::library::add_history( dbh => $options{dbh}, code => 10, token => $options{token}, data => { message => 'gorgonepollers: still no ready' }, @@ -87,7 +87,7 @@ sub routing { return undef; } - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $options{socket}, identity => 'gorgonepollers', action => $options{action}, @@ -148,7 +148,7 @@ sub create_child { my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-pollers'; - my $module = modules::centreon::pollers::class->new( + my $module = gorgone::modules::centreon::pollers::class->new( logger => $options{logger}, config_core => $config_core, config => $config, diff --git a/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm similarity index 93% rename from gorgone/modules/core/action/class.pm rename to gorgone/gorgone/modules/core/action/class.pm index e106e49ab5f..89fd303d382 100644 --- a/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -18,14 +18,14 @@ # limitations under the License. # -package modules::core::action::class; +package gorgone::modules::core::action::class; -use base qw(centreon::gorgone::module); +use base qw(gorgone::class::module); use strict; use warnings; -use centreon::gorgone::common; -use centreon::misc::misc; +use gorgone::standard::library; +use gorgone::standard::misc; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); use JSON::XS; @@ -105,7 +105,7 @@ sub action_command { } my $start = time(); - my ($error, $stdout, $return_code) = centreon::misc::misc::backtick( + my ($error, $stdout, $return_code) = gorgone::standard::misc::backtick( command => $options{data}->{content}->{command}, #arguments => [@$args, $sub_cmd], timeout => (defined($options{data}->{content}->{timeout})) ? @@ -187,7 +187,7 @@ sub action_processcopy { if (! -d $options{data}->{content}->{destination}) { make_path($options{data}->{content}->{destination}); } - my ($error, $stdout, $exit_code) = centreon::misc::misc::backtick( + my ($error, $stdout, $exit_code) = gorgone::standard::misc::backtick( command => "tar --no-overwrite-dir -zxf $cache_file -C '" . $options{data}->{content}->{destination} . "' .", timeout => (defined($options{timeout})) ? $options{timeout} : 10, wait_exit => 1, @@ -241,7 +241,7 @@ sub action_processcopy { sub action_run { my ($self, %options) = @_; - my $socket_log = centreon::gorgone::common::connect_com( + my $socket_log = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', name => 'gorgoneaction-'. $$, logger => $self->{logger}, @@ -298,7 +298,7 @@ sub create_child { sub event { while (1) { - my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $connector->{internal_socket}); + my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); $connector->{logger}->writeLogDebug("[action] -class- Event: $message"); @@ -316,7 +316,7 @@ sub event { } } - last unless (centreon::gorgone::common::zmq_still_read(socket => $connector->{internal_socket})); + last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); } } @@ -324,14 +324,14 @@ sub run { my ($self, %options) = @_; # Connect internal - $connector->{internal_socket} = centreon::gorgone::common::connect_com( + $connector->{internal_socket} = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', name => 'gorgoneaction', logger => $self->{logger}, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $connector->{internal_socket}, action => 'ACTIONREADY', data => { }, json_encode => 1 diff --git a/gorgone/modules/core/action/hooks.pm b/gorgone/gorgone/modules/core/action/hooks.pm similarity index 90% rename from gorgone/modules/core/action/hooks.pm rename to gorgone/gorgone/modules/core/action/hooks.pm index aadf850fc26..216379ab866 100644 --- a/gorgone/modules/core/action/hooks.pm +++ b/gorgone/gorgone/modules/core/action/hooks.pm @@ -18,12 +18,12 @@ # limitations under the License. # -package modules::core::action::hooks; +package gorgone::modules::core::action::hooks; use warnings; use strict; -use centreon::script::gorgonecore; -use modules::core::action::class; +use gorgone::class::core; +use gorgone::modules::core::action::class; use JSON::XS; use constant NAMESPACE => 'core'; @@ -62,7 +62,7 @@ sub routing { }; if ($@) { $options{logger}->writeLogError("[action] -hooks- Cannot decode json data: $@"); - centreon::gorgone::common::add_history( + gorgone::standard::library::add_history( dbh => $options{dbh}, code => 30, token => $options{token}, data => { msg => 'gorgoneaction: cannot decode json' }, @@ -76,8 +76,8 @@ sub routing { return undef; } - if (centreon::script::gorgonecore::waiting_ready(ready => \$action->{ready}) == 0) { - centreon::gorgone::common::add_history( + if (gorgone::class::core::waiting_ready(ready => \$action->{ready}) == 0) { + gorgone::standard::library::add_history( dbh => $options{dbh}, code => 30, token => $options{token}, data => { msg => 'gorgoneaction: still no ready' }, @@ -86,7 +86,7 @@ sub routing { return undef; } - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $options{socket}, identity => 'gorgoneaction', action => $options{action}, @@ -147,7 +147,7 @@ sub create_child { my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-action'; - my $module = modules::core::action::class->new( + my $module = gorgone::modules::core::action::class->new( logger => $options{logger}, config_core => $config_core, config => $config, diff --git a/gorgone/modules/core/cron/class.pm b/gorgone/gorgone/modules/core/cron/class.pm similarity index 94% rename from gorgone/modules/core/cron/class.pm rename to gorgone/gorgone/modules/core/cron/class.pm index 50021158e75..5abaecf9b4b 100644 --- a/gorgone/modules/core/cron/class.pm +++ b/gorgone/gorgone/modules/core/cron/class.pm @@ -18,14 +18,14 @@ # limitations under the License. # -package modules::core::cron::class; +package gorgone::modules::core::cron::class; -use base qw(centreon::gorgone::module); +use base qw(gorgone::class::module); use strict; use warnings; -use centreon::gorgone::common; -use centreon::misc::misc; +use gorgone::standard::library; +use gorgone::standard::misc; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); use Schedule::Cron; @@ -85,7 +85,7 @@ sub action_getcron { $options{token} = $self->generate_token() if (!defined($options{token})); $self->send_log( - code => centreon::gorgone::module::ACTION_BEGIN, + code => gorgone::class::module::ACTION_BEGIN, token => $options{token}, data => { message => 'get start' } ); @@ -164,7 +164,7 @@ sub action_addcron { $self->{logger}->writeLogDebug("[cron] -class- Cron add start"); $self->send_log( - code => centreon::gorgone::module::ACTION_BEGIN, + code => gorgone::class::module::ACTION_BEGIN, token => $options{token}, data => { message => 'add start' } ); @@ -232,7 +232,7 @@ sub action_updatecron { $self->{logger}->writeLogDebug("[cron] -class- Cron update start"); $self->send_log( - code => centreon::gorgone::module::ACTION_BEGIN, + code => gorgone::class::module::ACTION_BEGIN, token => $options{token}, data => { message => 'update start' } ); @@ -318,7 +318,7 @@ sub action_deletecron { $self->{logger}->writeLogDebug("[cron] -class- Cron delete start"); $self->send_log( - code => centreon::gorgone::module::ACTION_BEGIN, + code => gorgone::class::module::ACTION_BEGIN, token => $options{token}, data => { message => 'delete start' } ); @@ -381,7 +381,7 @@ sub action_deletecron { sub event { while (1) { - my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $connector->{internal_socket}); + my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); $connector->{logger}->writeLogDebug("[cron] -class- Event: $message"); if ($message =~ /^\[(.*?)\]/) { @@ -393,7 +393,7 @@ sub event { } } - last unless (centreon::gorgone::common::zmq_still_read(socket => $connector->{internal_socket})); + last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); } } @@ -413,7 +413,7 @@ sub dispatcher { my $token = (defined($options->{definition}->{keep_token})) ? $options->{definition}->{id} : undef; - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $options->{socket}, token => $token, action => $options->{definition}->{action}, @@ -439,14 +439,14 @@ sub run { my ($self, %options) = @_; # Connect internal - $connector->{internal_socket} = centreon::gorgone::common::connect_com( + $connector->{internal_socket} = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', name => 'gorgonecron', logger => $self->{logger}, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $connector->{internal_socket}, action => 'CRONREADY', data => { }, json_encode => 1 diff --git a/gorgone/modules/core/cron/hooks.pm b/gorgone/gorgone/modules/core/cron/hooks.pm similarity index 90% rename from gorgone/modules/core/cron/hooks.pm rename to gorgone/gorgone/modules/core/cron/hooks.pm index 2b9276959a7..b6911e312fb 100644 --- a/gorgone/modules/core/cron/hooks.pm +++ b/gorgone/gorgone/modules/core/cron/hooks.pm @@ -18,12 +18,12 @@ # limitations under the License. # -package modules::core::cron::hooks; +package gorgone::modules::core::cron::hooks; use warnings; use strict; -use centreon::script::gorgonecore; -use modules::core::cron::class; +use gorgone::class::core; +use gorgone::modules::core::cron::class; use JSON::XS; use constant NAMESPACE => 'core'; @@ -64,7 +64,7 @@ sub routing { }; if ($@) { $options{logger}->writeLogError("[cron] -hooks- Cannot decode json data: $@"); - centreon::gorgone::common::add_history( + gorgone::standard::library::add_history( dbh => $options{dbh}, code => 10, token => $options{token}, data => { message => 'gorgonecron: cannot decode json' }, @@ -78,8 +78,8 @@ sub routing { return undef; } - if (centreon::script::gorgonecore::waiting_ready(ready => \$cron->{ready}) == 0) { - centreon::gorgone::common::add_history( + if (gorgone::class::core::waiting_ready(ready => \$cron->{ready}) == 0) { + gorgone::standard::library::add_history( dbh => $options{dbh}, code => 10, token => $options{token}, data => { message => 'gorgonecron: still no ready' }, @@ -88,7 +88,7 @@ sub routing { return undef; } - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $options{socket}, identity => 'gorgonecron', action => $options{action}, @@ -149,7 +149,7 @@ sub create_child { my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-cron'; - my $module = modules::core::cron::class->new( + my $module = gorgone::modules::core::cron::class->new( logger => $options{logger}, config_core => $config_core, config => $config, diff --git a/gorgone/modules/core/httpserver/class.pm b/gorgone/gorgone/modules/core/httpserver/class.pm similarity index 94% rename from gorgone/modules/core/httpserver/class.pm rename to gorgone/gorgone/modules/core/httpserver/class.pm index 460d9e23f42..55dfb2febae 100644 --- a/gorgone/modules/core/httpserver/class.pm +++ b/gorgone/gorgone/modules/core/httpserver/class.pm @@ -18,11 +18,11 @@ # limitations under the License. # -package modules::core::httpserver::class; +package gorgone::modules::core::httpserver::class; use strict; use warnings; -use centreon::gorgone::common; +use gorgone::standard::library; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); use HTTP::Daemon; @@ -87,11 +87,11 @@ sub class_handle_HUP { sub event { while (1) { - my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $connector->{internal_socket}); + my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); $connector->{logger}->writeLogDebug("[httpserver] -class- Event: $message"); - last unless (centreon::gorgone::common::zmq_still_read(socket => $connector->{internal_socket})); + last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); } } @@ -106,14 +106,14 @@ sub run { my ($self, %options) = @_; # Connect internal - $connector->{internal_socket} = centreon::gorgone::common::connect_com( + $connector->{internal_socket} = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', name => 'gorgonehttpserver', logger => $self->{logger}, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $connector->{internal_socket}, action => 'HTTPSERVERREADY', data => { }, @@ -218,7 +218,7 @@ sub api_call { require 'centreon/gorgone/api.pm'; my %parameters = $request->uri->query_form; - my $response = centreon::gorgone::api::root( + my $response = gorgone::standard::api::root( method => $request->method, uri => $request->uri->path, parameters => \%parameters, diff --git a/gorgone/modules/core/httpserver/hooks.pm b/gorgone/gorgone/modules/core/httpserver/hooks.pm similarity index 91% rename from gorgone/modules/core/httpserver/hooks.pm rename to gorgone/gorgone/modules/core/httpserver/hooks.pm index 2aeeb86744c..9f4bdb7e481 100644 --- a/gorgone/modules/core/httpserver/hooks.pm +++ b/gorgone/gorgone/modules/core/httpserver/hooks.pm @@ -18,12 +18,12 @@ # limitations under the License. # -package modules::core::httpserver::hooks; +package gorgone::modules::core::httpserver::hooks; use warnings; use strict; -use centreon::script::gorgonecore; -use modules::core::httpserver::class; +use gorgone::class::core; +use gorgone::modules::core::httpserver::class; use JSON::XS; use constant NAMESPACE => 'core'; @@ -72,7 +72,7 @@ sub routing { }; if ($@) { $options{logger}->writeLogError("[httpserver] -hooks- Cannot decode json data: $@"); - centreon::gorgone::common::add_history( + gorgone::standard::library::add_history( dbh => $options{dbh}, code => 10, token => $options{token}, @@ -87,8 +87,8 @@ sub routing { return undef; } - if (centreon::script::gorgonecore::waiting_ready(ready => \$httpserver->{ready}) == 0) { - centreon::gorgone::common::add_history( + if (gorgone::class::core::waiting_ready(ready => \$httpserver->{ready}) == 0) { + gorgone::standard::library::add_history( dbh => $options{dbh}, code => 10, token => $options{token}, @@ -98,7 +98,7 @@ sub routing { return undef; } - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $options{socket}, identity => 'gorgonehttpserver', action => $options{action}, @@ -159,7 +159,7 @@ sub create_child { my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-httpserver'; - my $module = modules::core::httpserver::class->new( + my $module = gorgone::modules::core::httpserver::class->new( logger => $options{logger}, config_core => $config_core, config => $config, diff --git a/gorgone/modules/core/proxy/class.pm b/gorgone/gorgone/modules/core/proxy/class.pm similarity index 90% rename from gorgone/modules/core/proxy/class.pm rename to gorgone/gorgone/modules/core/proxy/class.pm index 70763728766..fe58fe09f36 100644 --- a/gorgone/modules/core/proxy/class.pm +++ b/gorgone/gorgone/modules/core/proxy/class.pm @@ -18,15 +18,15 @@ # limitations under the License. # -package modules::core::proxy::class; +package gorgone::modules::core::proxy::class; -use base qw(centreon::gorgone::module); +use base qw(gorgone::class::module); use strict; use warnings; -use centreon::gorgone::common; -use centreon::gorgone::clientzmq; -use modules::core::proxy::sshclient; +use gorgone::standard::library; +use gorgone::class::clientzmq; +use gorgone::modules::core::proxy::sshclient; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); use JSON::XS; @@ -98,7 +98,7 @@ sub read_message { } my ($action, $token, $data) = ($1, $2, $3); - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $connector->{internal_socket}, action => 'PONG', token => $token, @@ -111,7 +111,7 @@ sub read_message { } my ($action, $token, $data) = ($1, $2, $3); - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $connector->{internal_socket}, action => $action, token => $token, @@ -128,7 +128,7 @@ sub read_message { } if (defined($data->{data}->{action}) && $data->{data}->{action} eq 'getlog') { - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $connector->{internal_socket}, action => 'SETLOGS', token => $1, @@ -143,7 +143,7 @@ sub connect { my ($self, %options) = @_; if ($self->{clients}->{$options{id}}->{type} eq 'push_zmq') { - $self->{clients}->{$options{id}}->{class} = centreon::gorgone::clientzmq->new( + $self->{clients}->{$options{id}}->{class} = gorgone::class::clientzmq->new( identity => 'proxy-' . $self->{core_id} . '-' . $options{id}, cipher => $self->{clients}->{$options{id}}->{cipher}, vector => $self->{clients}->{$options{id}}->{vector}, @@ -160,7 +160,7 @@ sub connect { ); $self->{clients}->{$options{id}}->{class}->init(callback => \&read_message); } elsif ($self->{clients}->{$options{id}}->{type} eq 'push_ssh') { - $self->{clients}->{$options{id}}->{class} = modules::core::proxy::sshclient->new(logger => $self->{logger}); + $self->{clients}->{$options{id}}->{class} = gorgone::modules::core::proxy::sshclient->new(logger => $self->{logger}); my $code = $self->{clients}->{$options{id}}->{class}->open_session( ssh_host => $self->{clients}->{$options{id}}->{address}, ssh_port => $self->{clients}->{$options{id}}->{ssh_port}, @@ -256,7 +256,7 @@ sub proxy { if (!defined($connector->{clients}->{$target_client}->{class})) { if ($connector->connect(id => $target_client) != 0) { $connector->send_log( - code => centreon::gorgone::module::ACTION_FINISH_KO, + code => gorgone::class::module::ACTION_FINISH_KO, token => $token, data => { message => "cannot connect on target node '$target_client'" @@ -275,7 +275,7 @@ sub proxy { ); if ($status != 0) { $connector->send_log( - code => centreon::gorgone::module::ACTION_FINISH_KO, + code => gorgone::class::module::ACTION_FINISH_KO, token => $token, data => { message => "Send message problem for '$target': $msg" @@ -290,7 +290,7 @@ sub proxy { if ($action eq 'PING') { if ($connector->{clients}->{$target_client}->{class}->ping() != -1) { - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $connector->{internal_socket}, action => 'PONG', token => $token, @@ -311,13 +311,13 @@ sub proxy { $connector->{logger}->writeLogDebug("[proxy] -class- sshclient return: [message = $data_ret->{message}]"); if ($status == 0) { $connector->send_log( - code => centreon::gorgone::module::ACTION_FINISH_OK, + code => gorgone::class::module::ACTION_FINISH_OK, token => $token, data => $data_ret ); } else { $connector->send_log( - code => centreon::gorgone::module::ACTION_FINISH_KO, + code => gorgone::class::module::ACTION_FINISH_KO, token => $token, data => $data_ret ); @@ -327,10 +327,10 @@ sub proxy { sub event_internal { while (1) { - my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $connector->{internal_socket}); + my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); proxy(message => $message); - last unless (centreon::gorgone::common::zmq_still_read(socket => $connector->{internal_socket})); + last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); } } @@ -338,14 +338,14 @@ sub run { my ($self, %options) = @_; # Connect internal - $self->{internal_socket} = centreon::gorgone::common::connect_com( + $self->{internal_socket} = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', name => 'gorgoneproxy-' . $self->{pool_id}, logger => $self->{logger}, type => $self->{config_core}{internal_com_type}, path => $self->{config_core}{internal_com_path} ); - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $self->{internal_socket}, action => 'PROXYREADY', data => { @@ -363,7 +363,7 @@ sub run { foreach (keys %{$self->{clients}}) { if (defined($self->{clients}->{$_}->{delete}) && $self->{clients}->{$_}->{delete} == 1) { if ($self->{clients}->{$_}->{type} eq 'push_zmq') { - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $self->{internal_socket}, action => 'PONGRESET', token => $self->generate_token(), diff --git a/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm similarity index 91% rename from gorgone/modules/core/proxy/hooks.pm rename to gorgone/gorgone/modules/core/proxy/hooks.pm index 13bc420dd84..8cec622e123 100644 --- a/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -18,15 +18,15 @@ # limitations under the License. # -package modules::core::proxy::hooks; +package gorgone::modules::core::proxy::hooks; use warnings; use strict; use JSON::XS; -use centreon::misc::misc; -use centreon::script::gorgonecore; -use centreon::gorgone::common; -use modules::core::proxy::class; +use gorgone::standard::misc; +use gorgone::class::core; +use gorgone::standard::library; +use gorgone::modules::core::proxy::class; use File::Basename; use MIME::Base64; use Digest::MD5::File qw(file_md5_hex); @@ -104,7 +104,7 @@ sub routing { }; if ($@) { $options{logger}->writeLogError("[proxy] -hooks- Cannot decode json data: $@"); - centreon::gorgone::common::add_history( + gorgone::standard::library::add_history( dbh => $options{dbh}, code => 20, token => $options{token}, data => { message => 'proxy - cannot decode json' }, @@ -164,9 +164,9 @@ sub routing { if (!defined($options{target}) || (!defined($register_subnodes->{$options{target}}) && !defined($register_nodes->{$options{target}}))) { - centreon::gorgone::common::add_history( + gorgone::standard::library::add_history( dbh => $options{dbh}, - code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, + code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'proxy - need a valid node id' }, json_encode => 1 ); @@ -175,9 +175,9 @@ sub routing { if ($options{action} eq 'GETLOG') { if (defined($register_nodes->{$options{target}}) && $register_nodes->{$options{target}}->{type} eq 'push_ssh') { - centreon::gorgone::common::add_history( + gorgone::standard::library::add_history( dbh => $options{dbh}, - code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, + code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => "proxy - can't get log a ssh target" }, json_encode => 1 ); @@ -186,9 +186,9 @@ sub routing { if (defined($register_nodes->{$options{target}})) { if ($synctime_nodes->{$options{target}}->{synctime_error} == -1 || get_sync_time(dbh => $options{dbh}, node_id => $options{target}) == -1) { - centreon::gorgone::common::add_history( + gorgone::standard::library::add_history( dbh => $options{dbh}, - code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, + code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'proxy - problem to getlog' }, json_encode => 1 ); @@ -196,9 +196,9 @@ sub routing { } if ($synctime_nodes->{$options{target}}->{in_progress} == 1) { - centreon::gorgone::common::add_history( + gorgone::standard::library::add_history( dbh => $options{dbh}, - code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, + code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'proxy - getlog already in progress' }, json_encode => 1 ); @@ -221,9 +221,9 @@ sub routing { } if (defined($last_pong->{$target}) && $last_pong->{$target} != 0 && (time() - $config->{pong_discard_timeout} > $last_pong->{$target})) { - centreon::gorgone::common::add_history( + gorgone::standard::library::add_history( dbh => $options{dbh}, - code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, + code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'proxy - discard message. target peer seems disconnected' }, json_encode => 1 ); @@ -253,10 +253,10 @@ sub routing { next; } - if (centreon::script::gorgonecore::waiting_ready_pool(pool => $pools) == 0) { - centreon::gorgone::common::add_history( + if (gorgone::class::core::waiting_ready_pool(pool => $pools) == 0) { + gorgone::standard::library::add_history( dbh => $options{dbh}, - code => centreon::gorgone::module::ACTION_FINISH_KO, + code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'proxy - still none ready' }, json_encode => 1 @@ -272,7 +272,7 @@ sub routing { $nodes_pool->{$target} = $identity; } - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $options{socket}, identity => 'gorgoneproxy-' . $identity, action => $action, @@ -351,9 +351,9 @@ sub check { foreach (keys %{$synctime_nodes}) { if ($synctime_nodes->{$_}->{in_progress} == 1 && time() - $synctime_nodes->{$_}->{in_progress_time} > $synctimeout_option) { - centreon::gorgone::common::add_history( + gorgone::standard::library::add_history( dbh => $options{dbh}, - code => centreon::gorgone::module::ACTION_FINISH_KO, + code => gorgone::class::module::ACTION_FINISH_KO, data => { message => "proxy - getlog in timeout for '$_'" }, json_encode => 1 ); @@ -390,18 +390,18 @@ sub setlogs { my (%options) = @_; if (!defined($options{data}->{data}->{id}) || $options{data}->{data}->{id} eq '') { - centreon::gorgone::common::add_history( + gorgone::standard::library::add_history( dbh => $options{dbh}, - code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, + code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'proxy - need a id to setlogs' }, json_encode => 1 ); return undef; } if ($synctime_nodes->{$options{data}->{data}->{id}}->{in_progress} == 0) { - centreon::gorgone::common::add_history( + gorgone::standard::library::add_history( dbh => $options{dbh}, - code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, + code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'proxy - skip setlogs response. Maybe too much time to get response. Retry' }, json_encode => 1 ); @@ -418,7 +418,7 @@ sub setlogs { $options{dbh}->transaction_mode(1); my $status = 0; foreach (keys %{$options{data}->{data}->{result}}) { - $status = centreon::gorgone::common::add_history( + $status = gorgone::standard::library::add_history( dbh => $options{dbh}, etime => $options{data}->{data}->{result}->{$_}->{etime}, code => $options{data}->{data}->{result}->{$_}->{code}, @@ -440,7 +440,7 @@ sub setlogs { # We try to send it to parents foreach (keys %{$parent_ping}) { - centreon::script::gorgonecore::send_message_parent( + gorgone::class::core::send_message_parent( router_type => $parent_ping->{$_}->{router_type}, identity => $_, response_type => 'SYNCLOGS', @@ -552,7 +552,7 @@ sub create_child { my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-proxy'; - my $module = modules::core::proxy::class->new( + my $module = gorgone::modules::core::proxy::class->new( logger => $options{logger}, module_id => NAME, config_core => $config_core, @@ -572,13 +572,13 @@ sub pull_request { my (%options) = @_; # No target anymore. We remove it. - my $message = centreon::gorgone::common::build_protocol( + my $message = gorgone::standard::library::build_protocol( action => $options{action}, data => $options{data}, token => $options{token}, target => '' ); - my ($status, $key) = centreon::gorgone::common::is_handshake_done(dbh => $options{dbh}, identity => unpack('H*', $options{target})); + my ($status, $key) = gorgone::standard::library::is_handshake_done(dbh => $options{dbh}, identity => unpack('H*', $options{target})); if ($status == 0) { - centreon::gorgone::common::add_history( + gorgone::standard::library::add_history( dbh => $options{dbh}, code => 20, token => $options{token}, data => { message => "proxy - node '" . $options{target} . "' had never been connected" }, @@ -591,7 +591,7 @@ sub pull_request { # Catch some actions call and do some transformation (on file copy) # TODO - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $external_socket, cipher => $config_core->{cipher}, vector => $config_core->{vector}, @@ -714,9 +714,9 @@ sub prepare_remote_copy { if (!defined($options{data}->{content}->{source}) || $options{data}->{content}->{source} eq '') { $options{logger}->writeLogError('[proxy] -hooks- prepare_remote_copy: need source'); - centreon::gorgone::common::add_history( + gorgone::standard::library::add_history( dbh => $options{dbh}, - code => centreon::gorgone::module::ACTION_FINISH_KO, + code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'remote copy failed' }, json_encode => 1 @@ -725,9 +725,9 @@ sub prepare_remote_copy { } if (!defined($options{data}->{content}->{destination}) || $options{data}->{content}->{destination} eq '') { $options{logger}->writeLogError('[proxy] -hooks- prepare_remote_copy: need destination'); - centreon::gorgone::common::add_history( + gorgone::standard::library::add_history( dbh => $options{dbh}, - code => centreon::gorgone::module::ACTION_FINISH_KO, + code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'remote copy failed' }, json_encode => 1 @@ -751,7 +751,7 @@ sub prepare_remote_copy { $filename = (defined($options{data}->{content}->{type}) ? $options{data}->{content}->{type} : 'tmp') . '-' . $options{target} . '.tar.gz'; $localsrc = $options{data}->{content}->{cache_dir} . '/' . $filename; - my ($error, $stdout, $exit_code) = centreon::misc::misc::backtick( + my ($error, $stdout, $exit_code) = gorgone::standard::misc::backtick( command => "tar -czf $localsrc -C '" . $src . "' .", timeout => (defined($options{timeout})) ? $options{timeout} : 10, wait_exit => 1, @@ -759,9 +759,9 @@ sub prepare_remote_copy { ); if ($error <= -1000) { $options{logger}->writeLogError("[proxy] -hooks- prepare_remote_copy: tar failed: $stdout"); - centreon::gorgone::common::add_history( + gorgone::standard::library::add_history( dbh => $options{dbh}, - code => centreon::gorgone::module::ACTION_FINISH_KO, + code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'tar failed' }, json_encode => 1 @@ -770,9 +770,9 @@ sub prepare_remote_copy { } if ($exit_code != 0) { $options{logger}->writeLogError("[proxy] -hooks- prepare_remote_copy: tar failed ($exit_code): $stdout"); - centreon::gorgone::common::add_history( + gorgone::standard::library::add_history( dbh => $options{dbh}, - code => centreon::gorgone::module::ACTION_FINISH_KO, + code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'tar failed' }, json_encode => 1 @@ -781,9 +781,9 @@ sub prepare_remote_copy { }; } else { $options{logger}->writeLogError('[proxy] -hooks- prepare_remote_copy: unknown source'); - centreon::gorgone::common::add_history( + gorgone::standard::library::add_history( dbh => $options{dbh}, - code => centreon::gorgone::module::ACTION_FINISH_KO, + code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'unknown source' }, json_encode => 1 diff --git a/gorgone/modules/core/proxy/sshclient.pm b/gorgone/gorgone/modules/core/proxy/sshclient.pm similarity index 98% rename from gorgone/modules/core/proxy/sshclient.pm rename to gorgone/gorgone/modules/core/proxy/sshclient.pm index 815d8c7fee5..f5b646706cc 100644 --- a/gorgone/modules/core/proxy/sshclient.pm +++ b/gorgone/gorgone/modules/core/proxy/sshclient.pm @@ -18,7 +18,7 @@ # limitations under the License. # -package modules::core::proxy::sshclient; +package gorgone::modules::core::proxy::sshclient; use base qw(Libssh::Session); @@ -26,7 +26,7 @@ use strict; use warnings; use Libssh::Sftp qw(:all); use POSIX; -use centreon::misc::misc; +use gorgone::standard::misc; use File::Basename; use Time::HiRes; @@ -81,7 +81,7 @@ sub open_session { sub local_command { my ($self, %options) = @_; - my ($error, $stdout, $exit_code) = centreon::misc::misc::backtick( + my ($error, $stdout, $exit_code) = gorgone::standard::misc::backtick( command => $options{command}, timeout => (defined($options{timeout})) ? $options{timeout} : 120, wait_exit => 1, diff --git a/gorgone/modules/core/pull/hooks.pm b/gorgone/gorgone/modules/core/pull/hooks.pm similarity index 89% rename from gorgone/modules/core/pull/hooks.pm rename to gorgone/gorgone/modules/core/pull/hooks.pm index a1f8b4609d4..524c9b45976 100644 --- a/gorgone/modules/core/pull/hooks.pm +++ b/gorgone/gorgone/modules/core/pull/hooks.pm @@ -18,11 +18,11 @@ # limitations under the License. # -package modules::core::pull::hooks; +package gorgone::modules::core::pull::hooks; use warnings; use strict; -use centreon::gorgone::clientzmq; +use gorgone::class::clientzmq; use JSON::XS; use constant NAMESPACE => 'core'; @@ -49,7 +49,7 @@ sub init { $logger = $options{logger}; # Connect internal - $socket_to_internal = centreon::gorgone::common::connect_com( + $socket_to_internal = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', name => 'gorgonepull', logger => $options{logger}, @@ -57,7 +57,7 @@ sub init { path => $config_core->{internal_com_path}, linger => $config->{linger} ); - $client = centreon::gorgone::clientzmq->new( + $client = gorgone::class::clientzmq->new( identity => $config_core->{id}, cipher => $config->{cipher}, vector => $config->{vector}, @@ -77,7 +77,7 @@ sub init { data => { nodes => [ { id => $config_core->{id}, type => 'pull' } ] }, json_encode => 1 ); - centreon::gorgone::common::add_zmq_pollin( + gorgone::standard::library::add_zmq_pollin( socket => $socket_to_internal, callback => \&from_router, poll => $options{poll} @@ -156,13 +156,13 @@ sub transmit_back { sub from_router { while (1) { - my $message = transmit_back(message => centreon::gorgone::common::zmq_dealer_read_message(socket => $socket_to_internal)); + my $message = transmit_back(message => gorgone::standard::library::zmq_dealer_read_message(socket => $socket_to_internal)); # Only send back SETLOGS and PONG if (defined($message)) { $logger->writeLogDebug("[pull] -hooks- Read message from internal: $message"); $client->send_message(message => $message); } - last unless (centreon::gorgone::common::zmq_still_read(socket => $socket_to_internal)); + last unless (gorgone::standard::library::zmq_still_read(socket => $socket_to_internal)); } } @@ -175,7 +175,7 @@ sub read_message { } $logger->writeLogDebug("[pull] -hooks- Read message from external: $options{data}"); - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $socket_to_internal, message => $options{data} ); diff --git a/gorgone/modules/core/register/class.pm b/gorgone/gorgone/modules/core/register/class.pm similarity index 89% rename from gorgone/modules/core/register/class.pm rename to gorgone/gorgone/modules/core/register/class.pm index 4598e9eb88a..d1fd34d4a91 100644 --- a/gorgone/modules/core/register/class.pm +++ b/gorgone/gorgone/modules/core/register/class.pm @@ -18,13 +18,13 @@ # limitations under the License. # -package modules::core::register::class; +package gorgone::modules::core::register::class; -use base qw(centreon::gorgone::module); +use base qw(gorgone::class::module); use strict; use warnings; -use centreon::gorgone::common; +use gorgone::standard::library; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); use JSON::XS; @@ -87,14 +87,14 @@ sub action_registerresync { $options{token} = $self->generate_token() if (!defined($options{token})); $self->send_log( - code => centreon::gorgone::module::ACTION_BEGIN, + code => gorgone::class::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action registerresync proceed' } ); - my $config = centreon::gorgone::common::read_config( + my $config = gorgone::standard::library::read_config( config_file => $self->{config}->{config_file}, logger => $self->{logger} ); @@ -143,7 +143,7 @@ sub action_registerresync { sub event { while (1) { - my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $connector->{internal_socket}); + my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); $connector->{logger}->writeLogDebug("[register] -class- Event: $message"); if ($message =~ /^\[(.*?)\]/) { @@ -155,7 +155,7 @@ sub event { } } - last unless (centreon::gorgone::common::zmq_still_read(socket => $connector->{internal_socket})); + last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); } } @@ -163,14 +163,14 @@ sub run { my ($self, %options) = @_; # Connect internal - $connector->{internal_socket} = centreon::gorgone::common::connect_com( + $connector->{internal_socket} = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', name => 'gorgoneregister', logger => $self->{logger}, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $connector->{internal_socket}, action => 'REGISTERREADY', data => {}, diff --git a/gorgone/modules/core/register/hooks.pm b/gorgone/gorgone/modules/core/register/hooks.pm similarity index 90% rename from gorgone/modules/core/register/hooks.pm rename to gorgone/gorgone/modules/core/register/hooks.pm index 49296ac9e7e..fb0f078266c 100644 --- a/gorgone/modules/core/register/hooks.pm +++ b/gorgone/gorgone/modules/core/register/hooks.pm @@ -18,13 +18,13 @@ # limitations under the License. # -package modules::core::register::hooks; +package gorgone::modules::core::register::hooks; use warnings; use strict; use JSON::XS; -use centreon::script::gorgonecore; -use modules::core::register::class; +use gorgone::class::core; +use gorgone::modules::core::register::class; use constant NAMESPACE => 'core'; use constant NAME => 'register'; @@ -66,7 +66,7 @@ sub routing { }; if ($@) { $options{logger}->writeLogError("[register] -hooks- Cannot decode json data: $@"); - centreon::gorgone::common::add_history( + gorgone::standard::library::add_history( dbh => $options{dbh}, code => 10, token => $options{token}, data => { message => 'gorgoneregister: cannot decode json' }, @@ -80,8 +80,8 @@ sub routing { return undef; } - if (centreon::script::gorgonecore::waiting_ready(ready => \$register->{ready}) == 0) { - centreon::gorgone::common::add_history( + if (gorgone::class::core::waiting_ready(ready => \$register->{ready}) == 0) { + gorgone::standard::library::add_history( dbh => $options{dbh}, code => 10, token => $options{token}, data => { message => 'gorgoneregister: still no ready' }, @@ -90,7 +90,7 @@ sub routing { return undef; } - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $options{socket}, identity => 'gorgoneregister', action => $options{action}, @@ -151,7 +151,7 @@ sub create_child { my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-register'; - my $module = modules::core::register::class->new( + my $module = gorgone::modules::core::register::class->new( logger => $options{logger}, config_core => $config_core, config => $config, diff --git a/gorgone/modules/plugins/newtest/class.pm b/gorgone/gorgone/modules/plugins/newtest/class.pm similarity index 90% rename from gorgone/modules/plugins/newtest/class.pm rename to gorgone/gorgone/modules/plugins/newtest/class.pm index 8c85ec2a63b..74478f8fe68 100644 --- a/gorgone/modules/plugins/newtest/class.pm +++ b/gorgone/gorgone/modules/plugins/newtest/class.pm @@ -18,22 +18,22 @@ # limitations under the License. # -package modules::plugins::newtest::class; +package gorgone::modules::plugins::newtest::class; -use base qw(centreon::gorgone::module); +use base qw(gorgone::class::module); use strict; use warnings; -use centreon::misc::misc; -use centreon::gorgone::common; -use centreon::misc::objects::object; +use gorgone::standard::misc; +use gorgone::standard::library; +use gorgone::class::sqlquery; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); use MIME::Base64; use JSON::XS; use Data::Dumper; -use modules::plugins::newtest::libs::stubs::ManagementConsoleService; -use modules::plugins::newtest::libs::stubs::errors; +use gorgone::modules::plugins::newtest::libs::stubs::ManagementConsoleService; +use gorgone::modules::plugins::newtest::libs::stubs::errors; use Date::Parse; my %handlers = (TERM => {}, HUP => {}); @@ -271,7 +271,7 @@ sub clapi_execute { my ($self, %options) = @_; my $cmd = $self->{clapi_command} . " -u '" . $self->{clapi_username} . "' -p '" . $self->{clapi_password} . "' " . $options{cmd}; - my ($lerror, $stdout, $exit_code) = centreon::misc::misc::backtick( + my ($lerror, $stdout, $exit_code) = gorgone::standard::misc::backtick( command => $cmd, logger => $self->{logger}, timeout => $options{timeout}, @@ -297,7 +297,7 @@ sub submit_external_cmd { my ($self, %options) = @_; foreach my $cmd (@{$self->{external_commands}}) { - my ($lerror, $stdout, $exit_code) = centreon::misc::misc::backtick(command => '/bin/echo "' . $cmd . '" >> ' . $self->{cmdFile}, + my ($lerror, $stdout, $exit_code) = gorgone::standard::misc::backtick(command => '/bin/echo "' . $cmd . '" >> ' . $self->{cmdFile}, logger => $self->{logger}, timeout => 5, wait_exit => 1 @@ -343,7 +343,7 @@ sub get_newtest_diagnostic { my ($self, %options) = @_; my $result = $self->{instance}->ListMessages('Instance', 30, 'Diagnostics', [$options{scenario}, $options{robot}]); - if (defined(my $com_error = modules::plugins::newtest::libs::stubs::errors::get_error())) { + if (defined(my $com_error = gorgone::modules::plugins::newtest::libs::stubs::errors::get_error())) { $self->{logger}->writeLogError("[newtest] -class- newtest API error 'ListMessages' method: " . $com_error); return -1; } @@ -386,7 +386,7 @@ sub get_scenario_results { } if (!defined($self->{cache_robot_list_results}->{$options{robot}})) { my $result = $self->{instance}->ListResults('Robot', 30, [$options{robot}]); - if (defined(my $com_error = modules::plugins::newtest::libs::stubs::errors::get_error())) { + if (defined(my $com_error = gorgone::modules::plugins::newtest::libs::stubs::errors::get_error())) { $self->{logger}->writeLogError("[newtest] -class- newtest API error 'ListResults' method: " . $com_error); return -1; } @@ -440,7 +440,7 @@ sub get_newtest_extra_metrics { my ($self, %options) = @_; my $result = $self->{instance}->ListResultChildren($options{id}); - if (defined(my $com_error = modules::plugins::newtest::libs::stubs::errors::get_error())) { + if (defined(my $com_error = gorgone::modules::plugins::newtest::libs::stubs::errors::get_error())) { $self->{logger}->writeLogError("[newtest] -class- newtest API error 'ListResultChildren' method: " . $com_error); return -1; } @@ -484,7 +484,7 @@ sub get_newtest_scenarios { 0, $self->{list_scenario_status}->{instances} ); - if (defined(my $com_error = modules::plugins::newtest::libs::stubs::errors::get_error())) { + if (defined(my $com_error = gorgone::modules::plugins::newtest::libs::stubs::errors::get_error())) { $self->{logger}->writeLogError("[newtest] -class- newtest API error 'ListScenarioStatus' method: " . $com_error); return -1; } @@ -566,24 +566,24 @@ sub action_newtestresync { $options{token} = $self->generate_token() if (!defined($options{token})); $self->{logger}->writeLogDebug("gorgone-newtest: container $self->{container_id}: begin resync"); - $self->send_log(code => centreon::gorgone::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action newtestresync proceed' }); + $self->send_log(code => gorgone::class::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action newtestresync proceed' }); $self->newtestresync_init(); if ($self->get_poller_id()) { - $self->send_log(code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot get poller id' }); + $self->send_log(code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot get poller id' }); return -1; } if ($self->get_centreondb_cache()) { - $self->send_log(code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot get centreon config cache' }); + $self->send_log(code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot get centreon config cache' }); return -1; } if ($self->get_centstoragedb_cache()) { - $self->send_log(code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot get centreon storage cache' }); + $self->send_log(code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot get centreon storage cache' }); return -1; } if ($self->get_newtest_scenarios(%options)) { - $self->send_log(code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot get newtest scenarios' }); + $self->send_log(code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot get newtest scenarios' }); return -1; } @@ -596,7 +596,7 @@ sub action_newtestresync { sub event { while (1) { - my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $connector->{internal_socket}); + my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); $connector->{logger}->writeLogDebug("gorgone-newtest: class: $message"); if ($message =~ /^\[(.*?)\]/) { @@ -608,7 +608,7 @@ sub event { } } - last unless (centreon::gorgone::common::zmq_still_read(socket => $connector->{internal_socket})); + last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); } } @@ -616,14 +616,14 @@ sub run { my ($self, %options) = @_; # Database creation. We stay in the loop still there is an error - $self->{db_centstorage} = centreon::misc::db->new( + $self->{db_centstorage} = gorgone::class::db->new( dsn => $self->{config_db_centstorage}->{dsn}, user => $self->{config_db_centstorage}->{username}, password => $self->{config_db_centstorage}->{password}, force => 2, logger => $self->{logger} ); - $self->{db_centreon} = centreon::misc::db->new( + $self->{db_centreon} = gorgone::class::db->new( dsn => $self->{config_db_centreon}->{dsn}, user => $self->{config_db_centreon}->{username}, password => $self->{config_db_centreon}->{password}, @@ -631,19 +631,19 @@ sub run { logger => $self->{logger} ); ##### Load objects ##### - $self->{class_object_centstorage} = centreon::misc::objects::object->new(logger => $self->{logger}, db_centreon => $self->{db_centstorage}); - $self->{class_object_centreon} = centreon::misc::objects::object->new(logger => $self->{logger}, db_centreon => $self->{db_centreon}); + $self->{class_object_centstorage} = gorgone::class::sqlquery->new(logger => $self->{logger}, db_centreon => $self->{db_centstorage}); + $self->{class_object_centreon} = gorgone::class::sqlquery->new(logger => $self->{logger}, db_centreon => $self->{db_centreon}); $SOAP::Constants::PREFIX_ENV = 'SOAP-ENV'; - $self->{instance} = modules::plugins::newtest::libs::stubs::ManagementConsoleService->new(); + $self->{instance} = gorgone::modules::plugins::newtest::libs::stubs::ManagementConsoleService->new(); # Connect internal - $connector->{internal_socket} = centreon::gorgone::common::connect_com( + $connector->{internal_socket} = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', name => 'gorgonenewtest-' . $self->{container_id}, logger => $self->{logger}, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $connector->{internal_socket}, action => 'NEWTESTREADY', data => { container_id => $self->{container_id} }, json_encode => 1 diff --git a/gorgone/modules/plugins/newtest/hooks.pm b/gorgone/gorgone/modules/plugins/newtest/hooks.pm similarity index 94% rename from gorgone/modules/plugins/newtest/hooks.pm rename to gorgone/gorgone/modules/plugins/newtest/hooks.pm index f6f5bcd98ad..49183ec193a 100644 --- a/gorgone/modules/plugins/newtest/hooks.pm +++ b/gorgone/gorgone/modules/plugins/newtest/hooks.pm @@ -18,13 +18,13 @@ # limitations under the License. # -package modules::plugins::newtest::hooks; +package gorgone::modules::plugins::newtest::hooks; use warnings; use strict; use JSON::XS; -use centreon::script::gorgonecore; -use modules::plugins::newtest::class; +use gorgone::class::core; +use gorgone::modules::plugins::newtest::class; use constant NAMESPACE => 'plugins'; use constant NAME => 'newtest'; @@ -71,7 +71,7 @@ sub routing { }; if ($@) { $options{logger}->writeLogError("Cannot decode json data: $@"); - centreon::gorgone::common::add_history( + gorgone::standard::library::add_history( dbh => $options{dbh}, code => 300, token => $options{token}, data => { message => 'gorgone-newtest: cannot decode json' }, @@ -86,7 +86,7 @@ sub routing { } if (!defined($data->{container_id}) || !defined($last_containers->{$data->{container_id}})) { - centreon::gorgone::common::add_history( + gorgone::standard::library::add_history( dbh => $options{dbh}, code => 300, token => $options{token}, data => { message => 'gorgone-newtest: need a valid container id' }, @@ -95,8 +95,8 @@ sub routing { return undef; } - if (centreon::script::gorgonecore::waiting_ready(ready => \$containers->{$data->{container_id}}->{ready}) == 0) { - centreon::gorgone::common::add_history( + if (gorgone::class::core::waiting_ready(ready => \$containers->{$data->{container_id}}->{ready}) == 0) { + gorgone::standard::library::add_history( dbh => $options{dbh}, code => 300, token => $options{token}, data => { message => 'gorgone-newtest: still no ready' }, @@ -105,7 +105,7 @@ sub routing { return undef; } - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $options{socket}, identity => 'gorgonenewtest-' . $data->{container_id}, action => $options{action}, data => $options{data}, token => $options{token}, ); @@ -251,7 +251,7 @@ sub create_child { my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-newtest ' . $options{container_id}; - my $module = modules::plugins::newtest::class->new( + my $module = gorgone::modules::plugins::newtest::class->new( logger => $options{logger}, module_id => $NAME, config_core => $config_core, diff --git a/gorgone/modules/plugins/newtest/libs/stubs/ManagementConsoleService.pm b/gorgone/gorgone/modules/plugins/newtest/libs/stubs/ManagementConsoleService.pm similarity index 98% rename from gorgone/modules/plugins/newtest/libs/stubs/ManagementConsoleService.pm rename to gorgone/gorgone/modules/plugins/newtest/libs/stubs/ManagementConsoleService.pm index 7bfc4ac38c1..10688740d5e 100644 --- a/gorgone/modules/plugins/newtest/libs/stubs/ManagementConsoleService.pm +++ b/gorgone/gorgone/modules/plugins/newtest/libs/stubs/ManagementConsoleService.pm @@ -1,4 +1,4 @@ -package modules::plugins::newtest::libs::stubs::ManagementConsoleService; +package gorgone::modules::plugins::newtest::libs::stubs::ManagementConsoleService; sub SOAP::Serializer::as_SearchMode { my $self = shift; @@ -307,7 +307,7 @@ GetLicenceOptionValue => { ); # end my %methods use SOAP::Lite; -use modules::plugins::newtest::libs::stubs::errors; +use gorgone::modules::plugins::newtest::libs::stubs::errors; use Exporter; use Carp (); @@ -320,7 +320,7 @@ sub _call { my ($self, $method) = (shift, shift); my $name = UNIVERSAL::isa($method => 'SOAP::Data') ? $method->name : $method; my %method = %{$methods{$name}}; - $self->on_fault(\&modules::plugins::newtest::libs::stubs::errors::soapGetBad); + $self->on_fault(\&gorgone::modules::plugins::newtest::libs::stubs::errors::soapGetBad); $self->proxy($method{endpoint} || Carp::croak "No server address (proxy) specified") unless $self->proxy; my @templates = @{$method{parameters}}; diff --git a/gorgone/modules/plugins/newtest/libs/stubs/errors.pm b/gorgone/gorgone/modules/plugins/newtest/libs/stubs/errors.pm similarity index 87% rename from gorgone/modules/plugins/newtest/libs/stubs/errors.pm rename to gorgone/gorgone/modules/plugins/newtest/libs/stubs/errors.pm index f45bfa7f51d..ba6b951f6d7 100644 --- a/gorgone/modules/plugins/newtest/libs/stubs/errors.pm +++ b/gorgone/gorgone/modules/plugins/newtest/libs/stubs/errors.pm @@ -1,5 +1,5 @@ -package modules::plugins::newtest::libs::stubs::errors; +package gorgone::modules::plugins::newtest::libs::stubs::errors; use strict; use warnings; diff --git a/gorgone/modules/plugins/newtest/libs/wsdl/newtest.wsdl b/gorgone/gorgone/modules/plugins/newtest/libs/wsdl/newtest.wsdl similarity index 100% rename from gorgone/modules/plugins/newtest/libs/wsdl/newtest.wsdl rename to gorgone/gorgone/modules/plugins/newtest/libs/wsdl/newtest.wsdl diff --git a/gorgone/modules/plugins/scom/class.pm b/gorgone/gorgone/modules/plugins/scom/class.pm similarity index 93% rename from gorgone/modules/plugins/scom/class.pm rename to gorgone/gorgone/modules/plugins/scom/class.pm index de84314c87e..48a2e7e98bf 100644 --- a/gorgone/modules/plugins/scom/class.pm +++ b/gorgone/gorgone/modules/plugins/scom/class.pm @@ -18,15 +18,15 @@ # limitations under the License. # -package modules::plugins::scom::class; +package gorgone::modules::plugins::scom::class; -use base qw(centreon::gorgone::module); +use base qw(gorgone::class::module); use strict; use warnings; -use centreon::gorgone::common; -use centreon::misc::objects::object; -use centreon::misc::http::http; +use gorgone::standard::library; +use gorgone::class::sqlquery; +use gorgone::class::http::http; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); use MIME::Base64; @@ -143,7 +143,7 @@ sub get_method { sub submit_external_cmd { my ($self, %options) = @_; - my ($lerror, $stdout, $exit_code) = centreon::misc::misc::backtick( + my ($lerror, $stdout, $exit_code) = gorgone::standard::misc::backtick( command => '/bin/echo "' . $options{cmd} . '" >> ' . $self->{centcore_cmd}, logger => $self->{logger}, timeout => 5, @@ -457,18 +457,18 @@ sub action_scomresync { $options{token} = $self->generate_token() if (!defined($options{token})); - $self->send_log(code => centreon::gorgone::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action scomresync proceed' }); + $self->send_log(code => gorgone::class::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action scomresync proceed' }); $self->{logger}->writeLogDebug("[scom] -class- Container $self->{container_id}: begin resync"); if ($self->get_realtime_slots()) { - $self->send_log(code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot find realtime slots' }); + $self->send_log(code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot find realtime slots' }); $self->{logger}->writeLogError("[scom] -class- Container $self->{container_id}: cannot find realtime slots"); return 1; } my $func = $self->get_method(method => 'get_realtime_scom_alerts'); if ($func->($self)) { - $self->send_log(code => centreon::gorgone::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot get scom realtime alerts' }); + $self->send_log(code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot get scom realtime alerts' }); $self->{logger}->writeLogError("[scom] -class- Container $self->{container_id}: cannot get scom realtime alerts"); return 1; } @@ -483,7 +483,7 @@ sub action_scomresync { sub event { while (1) { - my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $connector->{internal_socket}); + my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); $connector->{logger}->writeLogDebug("[scom] -class- Event: $message"); if ($message =~ /^\[(.*?)\]/) { @@ -495,7 +495,7 @@ sub event { } } - last unless (centreon::gorgone::common::zmq_still_read(socket => $connector->{internal_socket})); + last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); } } @@ -503,7 +503,7 @@ sub run { my ($self, %options) = @_; # Database creation. We stay in the loop still there is an error - $self->{db_centstorage} = centreon::misc::db->new( + $self->{db_centstorage} = gorgone::class::db->new( dsn => $self->{config_db_centstorage}->{dsn}, user => $self->{config_db_centstorage}->{username}, password => $self->{config_db_centstorage}->{password}, @@ -511,18 +511,18 @@ sub run { logger => $self->{logger} ); ##### Load objects ##### - $self->{class_object} = centreon::misc::objects::object->new(logger => $self->{logger}, db_centreon => $self->{db_centstorage}); - $self->{http} = centreon::misc::http::http->new(logger => $self->{logger}); + $self->{class_object} = gorgone::class::sqlquery->new(logger => $self->{logger}, db_centreon => $self->{db_centstorage}); + $self->{http} = gorgone::class::http::http->new(logger => $self->{logger}); # Connect internal - $connector->{internal_socket} = centreon::gorgone::common::connect_com( + $connector->{internal_socket} = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', name => 'gorgonescom-' . $self->{container_id}, logger => $self->{logger}, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $connector->{internal_socket}, action => 'SCOMREADY', data => { container_id => $self->{container_id} }, json_encode => 1 diff --git a/gorgone/modules/plugins/scom/hooks.pm b/gorgone/gorgone/modules/plugins/scom/hooks.pm similarity index 94% rename from gorgone/modules/plugins/scom/hooks.pm rename to gorgone/gorgone/modules/plugins/scom/hooks.pm index d4bc8f0852d..ec723cf8437 100644 --- a/gorgone/modules/plugins/scom/hooks.pm +++ b/gorgone/gorgone/modules/plugins/scom/hooks.pm @@ -18,13 +18,13 @@ # limitations under the License. # -package modules::plugins::scom::hooks; +package gorgone::modules::plugins::scom::hooks; use warnings; use strict; use JSON::XS; -use centreon::script::gorgonecore; -use modules::plugins::scom::class; +use gorgone::class::core; +use gorgone::modules::plugins::scom::class; use constant NAMESPACE => 'plugins'; use constant NAME => 'scom'; @@ -70,7 +70,7 @@ sub routing { }; if ($@) { $options{logger}->writeLogError("[scom] -hooks- Cannot decode json data: $@"); - centreon::gorgone::common::add_history( + gorgone::standard::library::add_history( dbh => $options{dbh}, code => 200, token => $options{token}, data => { message => 'gorgone-scom: cannot decode json' }, @@ -85,7 +85,7 @@ sub routing { } if (!defined($data->{container_id}) || !defined($last_containers->{$data->{container_id}})) { - centreon::gorgone::common::add_history( + gorgone::standard::library::add_history( dbh => $options{dbh}, code => 200, token => $options{token}, data => { message => 'gorgone-scom: need a valid container id' }, @@ -94,8 +94,8 @@ sub routing { return undef; } - if (centreon::script::gorgonecore::waiting_ready(ready => \$containers->{$data->{container_id}}->{ready}) == 0) { - centreon::gorgone::common::add_history( + if (gorgone::class::core::waiting_ready(ready => \$containers->{$data->{container_id}}->{ready}) == 0) { + gorgone::standard::library::add_history( dbh => $options{dbh}, code => 200, token => $options{token}, data => { message => 'gorgone-scom: still no ready' }, @@ -104,7 +104,7 @@ sub routing { return undef; } - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $options{socket}, identity => 'gorgonescom-' . $data->{container_id}, action => $options{action}, data => $options{data}, token => $options{token}, ); @@ -238,7 +238,7 @@ sub create_child { my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-scom ' . $options{container_id}; - my $module = modules::plugins::scom::class->new( + my $module = gorgone::modules::plugins::scom::class->new( logger => $options{logger}, module_id => $NAME, config_core => $config_core, diff --git a/gorgone/centreon/gorgone/api.pm b/gorgone/gorgone/standard/api.pm similarity index 90% rename from gorgone/centreon/gorgone/api.pm rename to gorgone/gorgone/standard/api.pm index 4cb8588013f..80aa384da78 100644 --- a/gorgone/centreon/gorgone/api.pm +++ b/gorgone/gorgone/standard/api.pm @@ -18,11 +18,11 @@ # limitations under the License. # -package centreon::gorgone::api; +package gorgone::standard::api; use strict; use warnings; -use centreon::gorgone::common; +use gorgone::standard::library; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); use Time::HiRes; @@ -78,7 +78,7 @@ sub root { sub call_action { my (%options) = @_; - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $options{socket}, action => $options{action}, target => $options{target}, @@ -128,7 +128,7 @@ sub get_log { ]; if (defined($options{target}) && $options{target} ne '') { - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $options{socket}, target => $options{target}, action => 'GETLOG', @@ -141,7 +141,7 @@ sub get_log { my $rev = zmq_poll($poll, 5000); } - centreon::gorgone::common::zmq_send_message( + gorgone::standard::library::zmq_send_message( socket => $options{socket}, action => 'GETLOG', data => { @@ -175,7 +175,7 @@ sub get_log { sub event { while (1) { - my $message = centreon::gorgone::common::zmq_dealer_read_message(socket => $socket); + my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $socket); $result = {}; if ($message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m || @@ -187,7 +187,7 @@ sub event { }; } - last unless (centreon::gorgone::common::zmq_still_read(socket => $socket)); + last unless (gorgone::standard::library::zmq_still_read(socket => $socket)); } } diff --git a/gorgone/centreon/gorgone/common.pm b/gorgone/gorgone/standard/library.pm similarity index 99% rename from gorgone/centreon/gorgone/common.pm rename to gorgone/gorgone/standard/library.pm index 8cccd4bc623..c5f05e67a91 100644 --- a/gorgone/centreon/gorgone/common.pm +++ b/gorgone/gorgone/standard/library.pm @@ -18,7 +18,7 @@ # limitations under the License. # -package centreon::gorgone::common; +package gorgone::standard::library; use strict; use warnings; diff --git a/gorgone/centreon/misc/misc.pm b/gorgone/gorgone/standard/misc.pm similarity index 99% rename from gorgone/centreon/misc/misc.pm rename to gorgone/gorgone/standard/misc.pm index f50507ebc87..253e1abf66b 100644 --- a/gorgone/centreon/misc/misc.pm +++ b/gorgone/gorgone/standard/misc.pm @@ -18,7 +18,7 @@ # limitations under the License. # -package centreon::misc::misc; +package gorgone::standard::misc; use strict; use warnings; diff --git a/gorgone/gorgoned b/gorgone/gorgoned index 17f7b863a7f..8963134d890 100644 --- a/gorgone/gorgoned +++ b/gorgone/gorgoned @@ -24,9 +24,9 @@ use warnings; use FindBin; use lib "$FindBin::Bin"; -use centreon::script::gorgonecore; +use gorgone::class::core; -centreon::script::gorgonecore->new()->run(); +gorgone::class::core->new()->run(); __END__ From 36d1e005f05cabe667d1eb0c6a7f2a9ae82969fd Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 25 Sep 2019 10:40:24 +0200 Subject: [PATCH 144/948] fix curl multiple call --- gorgone/gorgone/class/http/backend/curl.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/gorgone/gorgone/class/http/backend/curl.pm b/gorgone/gorgone/class/http/backend/curl.pm index 725495e406c..381f02af3a5 100644 --- a/gorgone/gorgone/class/http/backend/curl.pm +++ b/gorgone/gorgone/class/http/backend/curl.pm @@ -321,6 +321,7 @@ sub request { $self->set_proxy(%options); $self->set_extra_curl_opt(%options); + $self->{response_body} = ''; $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_FILE'), parameter => \$self->{response_body}); $self->{nheaders} = 0; $self->{response_headers} = [{}]; From aff6387dbc7bb767fca3ae0769afaf1d02504042 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 25 Sep 2019 10:47:18 +0200 Subject: [PATCH 145/948] enh(config): add/remove configurations files --- gorgone/config/gorgoned-central-ssh.yml | 79 ++++++++++++++++++ gorgone/config/gorgoned-central-zmq.yml | 81 +++++++++++++++++++ gorgone/config/gorgoned-poller.yml | 54 ++++--------- gorgone/config/gorgoned-poller2.yml | 56 ------------- gorgone/config/gorgoned-poller3.yml | 38 --------- gorgone/config/gorgoned-remote-ssh.yml | 67 +++++++++++++++ gorgone/config/gorgoned-remote-zmq.yml | 69 ++++++++++++++++ gorgone/config/gorgoned.yml | 1 + ...ternodes.yml => registernodes-central.yml} | 10 +-- ...ternodes2.yml => registernodes-remote.yml} | 6 +- 10 files changed, 319 insertions(+), 142 deletions(-) create mode 100644 gorgone/config/gorgoned-central-ssh.yml create mode 100644 gorgone/config/gorgoned-central-zmq.yml delete mode 100644 gorgone/config/gorgoned-poller2.yml delete mode 100644 gorgone/config/gorgoned-poller3.yml create mode 100644 gorgone/config/gorgoned-remote-ssh.yml create mode 100644 gorgone/config/gorgoned-remote-zmq.yml rename gorgone/config/{registernodes.yml => registernodes-central.yml} (69%) rename gorgone/config/{registernodes2.yml => registernodes-remote.yml} (81%) diff --git a/gorgone/config/gorgoned-central-ssh.yml b/gorgone/config/gorgoned-central-ssh.yml new file mode 100644 index 00000000000..656e45e5d2b --- /dev/null +++ b/gorgone/config/gorgoned-central-ssh.yml @@ -0,0 +1,79 @@ +name: gorgoned-central-ssh +description: Configuration example in a SSH environment for Central server +database: + db_centreon: + dsn: "mysql:host=localhost;dbname=centreon" + username: centreon + password: centreon + db_centstorage: + dsn: "mysql:host=localhost;dbname=centreon_storage" + username: centreon + password: centreon +gorgonecore: + internal_com_type: ipc + internal_com_path: /tmp/gorgone/routing.ipc + external_com_type: tcp + external_com_path: "*:5555" + # in seconds before sending kill signals (not gently) + timeout: 50 + gorgone_db_type: SQLite + gorgone_db_name: dbname=/tmp/gorgone.sdb + privkey: keys/central/privkey.pem + cipher: "Cipher::AES" + # in bytes + keysize: 32 + # 16 bytes for AES + vector: 0123456789012345 + # JWK format export thumbprint SHA256 + authorized_clients: + - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 + # in seconds + sessions_time: 86400 +modules: + - name: httpserver + package: gorgone::modules::core::httpserver::hooks + enable: true + address: 0.0.0.0 + port: 8443 + ssl: true + ssl_cert_file: /etc/pki/tls/certs/server-cert.pem + ssl_key_file: /etc/pki/tls/server-key.pem + auth: + user: admin + password: password + + - name: cron + package: gorgone::modules::core::cron::hooks + enable: true + cron: + - id: echo_date + timespec: "*/5 * * * *" + action: COMMAND + parameters: + command: "date >> /tmp/date.log" + timeout: 10 + + - name: action + package: gorgone::modules::core::action::hooks + enable: true + + - name: proxy + package: gorgone::modules::core::proxy::hooks + enable: true + + - name: pollers + package: gorgone::modules::centreon::pollers::hooks + enable: true + + - name: legacycmd + package: gorgone::modules::centreon::legacycmd::hooks + enable: true + cmd_file: "/var/lib/centreon/centcore.cmd" + cache_dir: "/var/cache/centreon/" + cache_dir_trap: "/etc/snmp/centreon_traps/" + remote_dir: "/var/lib/centreon/remote-data/" + + - name: engine + package: gorgone::modules::centreon::engine::hooks + enable: true + command_file: "/var/lib/centreon-engine/rw/centengine.cmd" diff --git a/gorgone/config/gorgoned-central-zmq.yml b/gorgone/config/gorgoned-central-zmq.yml new file mode 100644 index 00000000000..bf13104c069 --- /dev/null +++ b/gorgone/config/gorgoned-central-zmq.yml @@ -0,0 +1,81 @@ +name: gorgoned-central-zmq +description: Configuration example in a full ZMQ environment for Central server +database: + db_centreon: + dsn: "mysql:host=localhost;dbname=centreon" + username: centreon + password: centreon + db_centstorage: + dsn: "mysql:host=localhost;dbname=centreon_storage" + username: centreon + password: centreon +gorgonecore: + internal_com_type: ipc + internal_com_path: /tmp/gorgone/routing.ipc + external_com_type: tcp + external_com_path: "*:5555" + # in seconds before sending kill signals (not gently) + timeout: 50 + gorgone_db_type: SQLite + gorgone_db_name: dbname=/tmp/gorgone.sdb + id: 1 + privkey: keys/central/privkey.pem + cipher: "Cipher::AES" + # in bytes + keysize: 32 + # 16 bytes for AES + vector: 0123456789012345 + # JWK format export thumbprint SHA256 + authorized_clients: + - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 + # in seconds + sessions_time: 86400 +modules: + - name: httpserver + package: gorgone::modules::core::httpserver::hooks + enable: true + address: 0.0.0.0 + port: 8443 + ssl: true + ssl_cert_file: /etc/pki/tls/certs/server-cert.pem + ssl_key_file: /etc/pki/tls/server-key.pem + auth: + user: admin + password: password + + - name: cron + package: gorgone::modules::core::cron::hooks + enable: true + cron: + - id: echo_date + timespec: "* * * * *" + action: COMMAND + parameters: + command: "date >> /tmp/date.log" + timeout: 10 + + - name: action + package: gorgone::modules::core::action::hooks + enable: true + + - name: proxy + package: gorgone::modules::core::proxy::hooks + enable: true + + - name: register + package: gorgone::modules::core::register::hooks + enable: true + config_file: config/registernodes-central.yml + + - name: legacycmd + package: gorgone::modules::centreon::legacycmd::hooks + enable: true + cmd_file: "/var/lib/centreon/centcore.cmd" + cache_dir: "/var/cache/centreon/" + cache_dir_trap: "/etc/snmp/centreon_traps/" + remote_dir: "/var/lib/centreon/remote-data/" + + - name: engine + package: gorgone::modules::centreon::engine::hooks + enable: true + command_file: "/var/lib/centreon-engine/rw/centengine.cmd" diff --git a/gorgone/config/gorgoned-poller.yml b/gorgone/config/gorgoned-poller.yml index 705440a3e99..37883a1e3c0 100644 --- a/gorgone/config/gorgoned-poller.yml +++ b/gorgone/config/gorgoned-poller.yml @@ -1,58 +1,34 @@ name: gorgoned-poller +description: Configuration example in a full ZMQ environment for Poller server gorgonecore: internal_com_type: ipc internal_com_path: /tmp/gorgone/routing-poller.ipc + external_com_type: tcp + external_com_path: "*:5556" # in seconds before sending kill signals (not gently) timeout: 50 gorgone_db_type: SQLite - gorgone_db_name: dbname=/tmp/gorgone_poller.sdb - gorgone_db_host: - gorgone_db_port: - gorgone_db_user: - gorgone_db_password: - # If not set. Use 'hostname' function. - hostname: - # If not set. Try from 'hostname' in database - # Set 'none', if you don't need it (for poller in push mode) - id: 120 - privkey: keys/privkey.pem - cipher: "Crypt::OpenSSL::AES" + gorgone_db_name: dbname=/tmp/gorgone.sdb + id: 2 + privkey: keys/poller/privkey.pem + cipher: "Cipher::AES" # in bytes keysize: 32 # 16 bytes for AES vector: 0123456789012345 + # JWK format export thumbprint SHA256 + authorized_clients: + - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 # in seconds sessions_time: 86400 # in seconds purge_sessions_time: 3600 modules: - - name: pull - module: modules::core::pull::hooks - enable: true - target_type: tcp - target_path: 127.0.0.1:5555 - linger: 5000 - - # crypt options - server_pubkey: keys/central/pubkey.crt - client_pubkey: keys/poller/pubkey.crt - client_privkey: keys/poller/privkey.pem - - cipher: "Cipher::AES" - # in bytes - keysize: 32 - # 16 bytes for AES - vector: 0123456789012345 - - # ping - ping: 60 - ping_timeout: 30 - - name: action package: modules::core::action::hooks enable: true - command_timeout: 60 - # characters to delete in commands - paranoid_mode: 1 - paranoid_characters: - + + - name: engine + package: modules::centreon::engine::hooks + enable: true + command_file: "/var/lib/centreon-engine/rw/centengine.cmd" diff --git a/gorgone/config/gorgoned-poller2.yml b/gorgone/config/gorgoned-poller2.yml deleted file mode 100644 index ea63c035e6f..00000000000 --- a/gorgone/config/gorgoned-poller2.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: gorgoned-poller2 -gorgonecore: - internal_com_type: ipc - internal_com_path: /tmp/gorgone/routing-poller.ipc - external_com_type: tcp - external_com_path: "*:5556" - # in seconds before sending kill signals (not gently) - timeout: 50 - gorgone_db_type: SQLite - gorgone_db_name: dbname=/tmp/gorgone_poller.sdb - gorgone_db_host: - gorgone_db_port: - gorgone_db_user: - gorgone_db_password: - # If not set. Use 'hostname' function. - hostname: - # If not set. Try from 'hostname' in database - # Set 'none', if you don't need it (for poller in push mode) - id: 140 - privkey: keys/poller/privkey.pem - cipher: "Cipher::AES" - # in bytes - keysize: 32 - # 16 bytes for AES - vector: 0123456789012345 - # JWK format export thumbprint SHA256 - authorized_clients: - - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 - - # in seconds - sessions_time: 86400 - # in seconds - purge_sessions_time: 3600 - - # shouldn't be changed - proxy_name: proxy -modules: - - name: proxy - package: modules::core::proxy::hooks - enable: true - pool: 2 - # sync history each 5 minutes - synchistory_time: 300 - # how much time before the response is in timeout - synchistory_timeout: 120 - # ping each X seconds - ping: 60 - - - name: action - package: modules::core::action::hooks - enable: true - - - name: register - package: modules::core::register::hooks - enable: true - config_file: config/registernodes2.json diff --git a/gorgone/config/gorgoned-poller3.yml b/gorgone/config/gorgoned-poller3.yml deleted file mode 100644 index 8f0045a0885..00000000000 --- a/gorgone/config/gorgoned-poller3.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: gorgoned-poller2 -gorgonecore: - internal_com_type: ipc - internal_com_path: /tmp/gorgone/routing-poller2.ipc - external_com_type: tcp - external_com_path: "*:5557" - # in seconds before sending kill signals (not gently) - timeout: 50 - gorgone_db_type: SQLite - gorgone_db_name: dbname=/tmp/gorgone_poller2.sdb - gorgone_db_host: - gorgone_db_port: - gorgone_db_user: - gorgone_db_password: - # If not set. Use 'hostname' function. - hostname: - # If not set. Try from 'hostname' in database - # Set 'none', if you don't need it (for poller in push mode) - id: 150 - privkey: keys/poller/privkey.pem - cipher: "Cipher::AES" - # in bytes - keysize: 32 - # 16 bytes for AES - vector: 0123456789012345 - # JWK format export thumbprint SHA256 - authorized_clients: - - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 - - # in seconds - sessions_time: 86400 - # in seconds - purge_sessions_time: 3600 -modules: - - name: action - package: modules::core::action::hooks - enable: true - diff --git a/gorgone/config/gorgoned-remote-ssh.yml b/gorgone/config/gorgoned-remote-ssh.yml new file mode 100644 index 00000000000..870c6c5adc4 --- /dev/null +++ b/gorgone/config/gorgoned-remote-ssh.yml @@ -0,0 +1,67 @@ +name: gorgoned-remote-ssh +description: Configuration example in a SSH environment for Remote server +database: + db_centreon: + dsn: "mysql:host=localhost;dbname=centreon" + username: centreon + password: centreon + db_centstorage: + dsn: "mysql:host=localhost;dbname=centreon_storage" + username: centreon + password: centreon +gorgonecore: + internal_com_type: ipc + internal_com_path: /tmp/gorgone/routing.ipc + external_com_type: tcp + external_com_path: "*:5556" + # in seconds before sending kill signals (not gently) + timeout: 50 + gorgone_db_type: SQLite + gorgone_db_name: dbname=/tmp/gorgone.sdb + privkey: keys/central/privkey.pem + cipher: "Cipher::AES" + # in bytes + keysize: 32 + # 16 bytes for AES + vector: 0123456789012345 + # JWK format export thumbprint SHA256 + authorized_clients: + - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 + # in seconds + sessions_time: 86400 +modules: + - name: cron + package: modules::core::cron::hooks + enable: true + cron: + - id: echo_date + timespec: "* * * * *" + action: COMMAND + parameters: + command: "date >> /tmp/date.log" + timeout: 10 + + - name: action + package: modules::core::action::hooks + enable: true + + - name: proxy + package: modules::core::proxy::hooks + enable: true + + - name: pollers + package: modules::centreon::pollers::hooks + enable: true + + - name: legacycmd + package: modules::centreon::legacycmd::hooks + enable: true + cmd_file: "/var/lib/centreon/centcore.cmd" + cache_dir: "/var/cache/centreon/" + cache_dir_trap: "/etc/snmp/centreon_traps/" + remote_dir: "/var/lib/centreon/remote-data/" + + - name: engine + package: modules::centreon::engine::hooks + enable: true + command_file: "/var/lib/centreon-engine/rw/centengine.cmd" diff --git a/gorgone/config/gorgoned-remote-zmq.yml b/gorgone/config/gorgoned-remote-zmq.yml new file mode 100644 index 00000000000..0354a6b93f3 --- /dev/null +++ b/gorgone/config/gorgoned-remote-zmq.yml @@ -0,0 +1,69 @@ +name: gorgoned-remote-zmq +description: Configuration example in a full ZMQ environment for Remote server +database: + db_centreon: + dsn: "mysql:host=localhost;dbname=centreon" + username: centreon + password: centreon + db_centstorage: + dsn: "mysql:host=localhost;dbname=centreon_storage" + username: centreon + password: centreon +gorgonecore: + internal_com_type: ipc + internal_com_path: /tmp/gorgone/routing.ipc + external_com_type: tcp + external_com_path: "*:5556" + # in seconds before sending kill signals (not gently) + timeout: 50 + gorgone_db_type: SQLite + gorgone_db_name: dbname=/tmp/gorgone.sdb + id: 4 + privkey: keys/central/privkey.pem + cipher: "Cipher::AES" + # in bytes + keysize: 32 + # 16 bytes for AES + vector: 0123456789012345 + # JWK format export thumbprint SHA256 + authorized_clients: + - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 + # in seconds + sessions_time: 86400 +modules: + - name: cron + package: modules::core::cron::hooks + enable: true + cron: + - id: echo_date + timespec: "* * * * *" + action: COMMAND + parameters: + command: "date >> /tmp/date.log" + timeout: 10 + + - name: action + package: modules::core::action::hooks + enable: true + + - name: proxy + package: modules::core::proxy::hooks + enable: true + + - name: register + package: modules::core::register::hooks + enable: true + config_file: config/registernodes-remote.yml + + - name: legacycmd + package: modules::centreon::legacycmd::hooks + enable: true + cmd_file: "/var/lib/centreon/centcore.cmd" + cache_dir: "/var/cache/centreon/" + cache_dir_trap: "/etc/snmp/centreon_traps/" + remote_dir: "/var/lib/centreon/remote-data/" + + - name: engine + package: modules::centreon::engine::hooks + enable: true + command_file: "/var/lib/centreon-engine/rw/centengine.cmd" diff --git a/gorgone/config/gorgoned.yml b/gorgone/config/gorgoned.yml index 8fc3c49629f..42615f4d4b0 100644 --- a/gorgone/config/gorgoned.yml +++ b/gorgone/config/gorgoned.yml @@ -1,4 +1,5 @@ name: gorgoned +description: Configuration example with more directives database: db_centreon: dsn: "mysql:host=localhost;dbname=centreon" diff --git a/gorgone/config/registernodes.yml b/gorgone/config/registernodes-central.yml similarity index 69% rename from gorgone/config/registernodes.yml rename to gorgone/config/registernodes-central.yml index c95a7f496de..e0119674df3 100644 --- a/gorgone/config/registernodes.yml +++ b/gorgone/config/registernodes-central.yml @@ -1,11 +1,7 @@ nodes: - - id: 100 - type: push_ssh - address: 10.30.2.49 - ssh_port: 22 - - id: 140 + - id: 4 type: push_zmq - address: 10.30.2.15 + address: 10.30.2.135 port: 5556 server_pubkey: keys/poller/pubkey.crt client_pubkey: keys/central/pubkey.crt @@ -13,3 +9,5 @@ nodes: cipher: "Cipher::AES" keysize: 32 vector: 0123456789012345 + nodes: + - 2 diff --git a/gorgone/config/registernodes2.yml b/gorgone/config/registernodes-remote.yml similarity index 81% rename from gorgone/config/registernodes2.yml rename to gorgone/config/registernodes-remote.yml index ab0010d0fd3..b267be541b7 100644 --- a/gorgone/config/registernodes2.yml +++ b/gorgone/config/registernodes-remote.yml @@ -1,8 +1,8 @@ nodes: - - id: 150 + - id: 2 type: push_zmq - address: 10.30.2.15 - port: 5557 + address: 10.30.2.90 + port: 5556 server_pubkey: keys/poller/pubkey.crt client_pubkey: keys/central/pubkey.crt client_privkey: keys/central/privkey.pem From 3cf5769749def8fe14fd48255952566ffefcc556 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 25 Sep 2019 12:01:27 +0200 Subject: [PATCH 146/948] feat(service): add systemd files --- .../scripts/systemd/centreon-gorgone-service | 27 +++++++++++++++++++ .../systemd/centreon-gorgone-sysconfig | 2 ++ 2 files changed, 29 insertions(+) create mode 100644 gorgone/scripts/systemd/centreon-gorgone-service create mode 100644 gorgone/scripts/systemd/centreon-gorgone-sysconfig diff --git a/gorgone/scripts/systemd/centreon-gorgone-service b/gorgone/scripts/systemd/centreon-gorgone-service new file mode 100644 index 00000000000..d4e1ed95cbd --- /dev/null +++ b/gorgone/scripts/systemd/centreon-gorgone-service @@ -0,0 +1,27 @@ +## Copyright 2019 Centreon +## +## Licensed under the Apache License, Version 2.0 (the "License"); +## you may not use this file except in compliance with the License. +## You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. +## +## For more information : contact@centreon.com +## + +[Unit] +Description=Centreon Gorgone + +[Service] +ExecStart=/usr/bin/perl /usr/bin/gorgoned.pl --logfile=/var/log/centreon/centreon-gorgone.log --severity=error +Type=simple +User=centreon + +[Install] +WantedBy=multi-user.target diff --git a/gorgone/scripts/systemd/centreon-gorgone-sysconfig b/gorgone/scripts/systemd/centreon-gorgone-sysconfig new file mode 100644 index 00000000000..67c3bcfde83 --- /dev/null +++ b/gorgone/scripts/systemd/centreon-gorgone-sysconfig @@ -0,0 +1,2 @@ +# centreon-gorgone command line options +OPTIONS="--logfile=/var/log/centreon/centreon-gorgone.log --severity=error" \ No newline at end of file From 123b2eb32546dd3355ce1c878c62eb915945a3a6 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 25 Sep 2019 12:49:31 +0200 Subject: [PATCH 147/948] enh(spec): add spec file for gorgone --- gorgone/packages/centreon-gorgone.spec | 63 ++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 gorgone/packages/centreon-gorgone.spec diff --git a/gorgone/packages/centreon-gorgone.spec b/gorgone/packages/centreon-gorgone.spec new file mode 100644 index 00000000000..874bc6d7677 --- /dev/null +++ b/gorgone/packages/centreon-gorgone.spec @@ -0,0 +1,63 @@ +Name: centreon-gorgone +Version: 19.10 +Release: 1%{?dist} +Summary: Perl daemon task handlers +Group: Applications/System +License: Apache2 +URL: http://www.centreon.com +Source0: %{name}-%{version}.tar.gz +BuildArch: noarch +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + +Requires: perl-Cryptx +Requires: perl(Schedule::Cron) +Requires: perl(perl(ZMQ::LibZMQ4)) +Requires: perl(ZMQ::Constants) +Requires: perl(Crypt::CBC) +Requires: perl(JSON::XS) +Requires: perl(YAML) +Requires: perl(DBD::SQLite) +Requires: perl(DBD::mysql) +Requires: perl(DBI) +Requires: perl(UUID) +Requires: perl(HTTP::Daemon) +Requires: perl(HTTP::Status) +Requires: perl(MIME::Base64) +Requires: perl(Digest::MD5::File) +Requires: perl(Libssh::Session) +AutoReqProv: no + +%description + +%prep +%setup -q -n %{name}-%{version} + +%build + +%install +rm -rf %{buildroot} +mkdir -p %{buildroot}/%{perl_vendorlib}/gorgone +%{__install} -d -m 0755 %{buildroot}%{_bindir} +%{__install} -d -m 0755 %{buildroot}%{_sysconfdir}/systemd/system/ +%{__cp} scripts/systemd/centreon-gorgone-service %{buildroot}%{_sysconfdir}/systemd/system/centreon-gorgone.service +%{__install} -d -m 0755 %{buildroot}%{_sysconfdir}/sysconfig +%{__cp} scripts/systemd/centreon-gorgone-sysconfig %{buildroot}%{_sysconfdir}/sysconfig/centreon-gorgone + +%{__cp} -R gorgone/* %{buildroot}/%{perl_vendorlib}/gorgone/ +%{__cp} gorgoned %{buildroot}%{_bindir}/ + +%{_fixperms} $RPM_BUILD_ROOT/* + +%check + +%clean +rm -rf %{buildroot} + +%files +%defattr(-,root,root,-) +%{perl_vendorlib}/gorgone/ +%attr(755,root,root) %{_bindir}/gorgoned +%attr(755,root,root) %{_sysconfdir}/systemd/system/centreon-gorgone.service +%config(noreplace) %{_sysconfdir}/sysconfig/centreon-gorgone + +%changelog From b91b2b873bb006fa9ab7b220fa3620672de72ac6 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 25 Sep 2019 12:50:58 +0200 Subject: [PATCH 148/948] enh(spec): add curl dependency --- gorgone/packages/centreon-gorgone.spec | 1 + 1 file changed, 1 insertion(+) diff --git a/gorgone/packages/centreon-gorgone.spec b/gorgone/packages/centreon-gorgone.spec index 874bc6d7677..04e2e5a7ac9 100644 --- a/gorgone/packages/centreon-gorgone.spec +++ b/gorgone/packages/centreon-gorgone.spec @@ -25,6 +25,7 @@ Requires: perl(HTTP::Status) Requires: perl(MIME::Base64) Requires: perl(Digest::MD5::File) Requires: perl(Libssh::Session) +Requires: perl(Net::Curl::Easy) AutoReqProv: no %description From c4537a7366951531d2ee14125e3895800dd32644 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 25 Sep 2019 12:55:01 +0200 Subject: [PATCH 149/948] enh(packaging): move files, fix daemon call --- .../centreon-gorgone.spectemplate} | 0 .../{ => packaging}/packages/CryptX-0.064.tar.gz | Bin .../packages/perl-CryptX-0.064-1.el7.x86_64.rpm | Bin gorgone/{ => packaging}/packages/perl-CryptX.spec | 0 .../perl-Schedule-Cron-1.01-1.el7.noarch.rpm | Bin .../perl-Time-ParseDate-2015.103-1.el7.noarch.rpm | Bin gorgone/scripts/systemd/centreon-gorgone-service | 2 +- 7 files changed, 1 insertion(+), 1 deletion(-) rename gorgone/{packages/centreon-gorgone.spec => packaging/centreon-gorgone.spectemplate} (100%) rename gorgone/{ => packaging}/packages/CryptX-0.064.tar.gz (100%) rename gorgone/{ => packaging}/packages/perl-CryptX-0.064-1.el7.x86_64.rpm (100%) rename gorgone/{ => packaging}/packages/perl-CryptX.spec (100%) rename gorgone/{ => packaging}/packages/perl-Schedule-Cron-1.01-1.el7.noarch.rpm (100%) rename gorgone/{ => packaging}/packages/perl-Time-ParseDate-2015.103-1.el7.noarch.rpm (100%) diff --git a/gorgone/packages/centreon-gorgone.spec b/gorgone/packaging/centreon-gorgone.spectemplate similarity index 100% rename from gorgone/packages/centreon-gorgone.spec rename to gorgone/packaging/centreon-gorgone.spectemplate diff --git a/gorgone/packages/CryptX-0.064.tar.gz b/gorgone/packaging/packages/CryptX-0.064.tar.gz similarity index 100% rename from gorgone/packages/CryptX-0.064.tar.gz rename to gorgone/packaging/packages/CryptX-0.064.tar.gz diff --git a/gorgone/packages/perl-CryptX-0.064-1.el7.x86_64.rpm b/gorgone/packaging/packages/perl-CryptX-0.064-1.el7.x86_64.rpm similarity index 100% rename from gorgone/packages/perl-CryptX-0.064-1.el7.x86_64.rpm rename to gorgone/packaging/packages/perl-CryptX-0.064-1.el7.x86_64.rpm diff --git a/gorgone/packages/perl-CryptX.spec b/gorgone/packaging/packages/perl-CryptX.spec similarity index 100% rename from gorgone/packages/perl-CryptX.spec rename to gorgone/packaging/packages/perl-CryptX.spec diff --git a/gorgone/packages/perl-Schedule-Cron-1.01-1.el7.noarch.rpm b/gorgone/packaging/packages/perl-Schedule-Cron-1.01-1.el7.noarch.rpm similarity index 100% rename from gorgone/packages/perl-Schedule-Cron-1.01-1.el7.noarch.rpm rename to gorgone/packaging/packages/perl-Schedule-Cron-1.01-1.el7.noarch.rpm diff --git a/gorgone/packages/perl-Time-ParseDate-2015.103-1.el7.noarch.rpm b/gorgone/packaging/packages/perl-Time-ParseDate-2015.103-1.el7.noarch.rpm similarity index 100% rename from gorgone/packages/perl-Time-ParseDate-2015.103-1.el7.noarch.rpm rename to gorgone/packaging/packages/perl-Time-ParseDate-2015.103-1.el7.noarch.rpm diff --git a/gorgone/scripts/systemd/centreon-gorgone-service b/gorgone/scripts/systemd/centreon-gorgone-service index d4e1ed95cbd..2408af0941e 100644 --- a/gorgone/scripts/systemd/centreon-gorgone-service +++ b/gorgone/scripts/systemd/centreon-gorgone-service @@ -19,7 +19,7 @@ Description=Centreon Gorgone [Service] -ExecStart=/usr/bin/perl /usr/bin/gorgoned.pl --logfile=/var/log/centreon/centreon-gorgone.log --severity=error +ExecStart=/usr/bin/perl /usr/bin/gorgoned --logfile=/var/log/centreon/centreon-gorgone.log --severity=error Type=simple User=centreon From ccbdf5f72c8cd794f44281cd56fa79306a463c91 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 25 Sep 2019 13:01:48 +0200 Subject: [PATCH 150/948] enh(core): update config option --- gorgone/gorgone/class/core.pm | 10 +++++----- gorgone/gorgone/class/script.pm | 8 -------- gorgone/gorgoned | 6 +++--- 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 0ab5d61c46f..7901b867794 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -47,10 +47,10 @@ sub new { bless $self, $class; $self->add_options( - 'config-extra:s' => \$self->{opt_extra}, + 'config:s' => \$self->{opt_config}, ); - $self->{opt_extra} = ''; + $self->{opt_config} = ''; $self->{return_child} = {}; $self->{stop} = 0; $self->{internal_register} = {}; @@ -74,12 +74,12 @@ sub init { $SIG{__DIE__} = undef; ## load config ini - if (! -f $self->{opt_extra}) { - $self->{logger}->writeLogError("[core] Can't find extra config file '$self->{opt_extra}'"); + if (! -f $self->{opt_config}) { + $self->{logger}->writeLogError("[core] Can't find config file '$self->{opt_config}'"); exit(1); } $config = gorgone::standard::library::read_config( - config_file => $self->{opt_extra}, + config_file => $self->{opt_config}, logger => $self->{logger} ); diff --git a/gorgone/gorgone/class/script.pm b/gorgone/gorgone/class/script.pm index fb8d1c9e45d..cac9c142b1e 100644 --- a/gorgone/gorgone/class/script.pm +++ b/gorgone/gorgone/class/script.pm @@ -29,8 +29,6 @@ use gorgone::class::logger; use gorgone::class::db; use gorgone::class::lock; -use vars qw($centreon_config); - $SIG{__DIE__} = sub { my $error = shift; print "Error: $error"; @@ -40,12 +38,10 @@ $SIG{__DIE__} = sub { 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, severity => 'info', - noconfig => 0, noroot => 0 ); my $self = {%defaults, %options}; @@ -123,10 +119,6 @@ sub parse_options { Getopt::Long::Configure('bundling'); die "Command line error" if (!GetOptions(%{$self->{options}})); pod2usage(-exitval => 1, -input => $FindBin::Bin . "/" . $FindBin::Script) if ($self->{help}); - if ($self->{noconfig} == 0) { - require $self->{config_file}; - $self->{centreon_config} = $centreon_config; - } } sub run { diff --git a/gorgone/gorgoned b/gorgone/gorgoned index 8963134d890..85d2929291e 100644 --- a/gorgone/gorgoned +++ b/gorgone/gorgoned @@ -42,9 +42,9 @@ gorgoned [options] =over 8 -=item B<--config-extra> +=item B<--config> -Specify the path to the centreonesxd configuration file (default: ''). +Specify the path to the yaml configuration file (default: ''). =item B<--help> @@ -54,6 +54,6 @@ Print a brief help message and exits. =head1 DESCRIPTION -B will +B will survive =cut From 417c3af536e94ffce09238c7eb594d7cf9795f9b Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 25 Sep 2019 13:05:54 +0200 Subject: [PATCH 151/948] fix(doc): update command line --- gorgone/docs/guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/docs/guide.md b/gorgone/docs/guide.md index 7bf4ff53ede..d10a9fc89d6 100644 --- a/gorgone/docs/guide.md +++ b/gorgone/docs/guide.md @@ -45,7 +45,7 @@ Create sqlite database: To execute the daemon: ``` -# perl gorgoned --config-extra=config/gorgoned.yml --severity=debug +# perl gorgoned --config=config/gorgoned.yml --severity=debug ``` # gorgone protocol From 2127bc868d8dd5978d75e98929dbabc901e3806e Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 25 Sep 2019 13:19:01 +0200 Subject: [PATCH 152/948] add license --- gorgone/LICENSE.txt | 201 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 gorgone/LICENSE.txt diff --git a/gorgone/LICENSE.txt b/gorgone/LICENSE.txt new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/gorgone/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. From 6bff429353151edd08fe9989b98d5bed531bbd32 Mon Sep 17 00:00:00 2001 From: Matthieu Kermagoret Date: Wed, 25 Sep 2019 11:43:33 +0200 Subject: [PATCH 153/948] enh(build): add Jenkinsfile. --- gorgone/Jenkinsfile | 61 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 gorgone/Jenkinsfile diff --git a/gorgone/Jenkinsfile b/gorgone/Jenkinsfile new file mode 100644 index 00000000000..a17aba06b82 --- /dev/null +++ b/gorgone/Jenkinsfile @@ -0,0 +1,61 @@ +/* +** Variables. +*/ +def serie = '19.10' +def maintenanceBranch = "${serie}.x" +if (env.BRANCH_NAME.startsWith('release-')) { + env.BUILD = 'RELEASE' +} else if ((env.BRANCH_NAME == 'master') || (env.BRANCH_NAME == maintenanceBranch)) { + env.BUILD = 'REFERENCE' +} else { + env.BUILD = 'CI' +} + +/* +** Pipeline code. +*/ +stage('Source') { + node { + sh 'setup_centreon_build.sh' + dir('centreon-gorgone') { + checkout scm + } + sh "./centreon-build/jobs/gorgone/${serie}/gorgone-source.sh" + source = readProperties file: 'source.properties' + env.VERSION = "${source.VERSION}" + env.RELEASE = "${source.RELEASE}" + if ((env.BUILD == 'RELEASE') || (env.BUILD == 'REFERENCE')) { + withSonarQubeEnv('SonarQube') { + sh "./centreon-build/jobs/gorgone/${serie}/gorgone-analysis.sh" + } + } + } +} + +try { + stage('Package') { + parallel 'centos7': { + node { + sh 'setup_centreon_build.sh' + sh "./centreon-build/jobs/gorgone/${serie}/gorgone-package.sh centos7" + } + } + if ((currentBuild.result ?: 'SUCCESS') != 'SUCCESS') { + error('Package stage failure.'); + } + } + + if ((env.BUILD == 'RELEASE') || (env.BUILD == 'REFERENCE')) { + stage('Delivery') { + node { + sh 'setup_centreon_build.sh' + sh "./centreon-build/jobs/gorgone/${serie}/gorgone-delivery.sh" + } + if ((currentBuild.result ?: 'SUCCESS') != 'SUCCESS') { + error('Delivery stage failure.'); + } + } + } +} catch(e) { + currentBuild.result = 'FAILURE' +} From 8a4821183f41d22b832a32dc04975ae2b1082fd8 Mon Sep 17 00:00:00 2001 From: Matthieu Kermagoret Date: Wed, 25 Sep 2019 13:11:25 +0200 Subject: [PATCH 154/948] fix: set version as 19.10.0 instead of 19.10. --- gorgone/packaging/centreon-gorgone.spectemplate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 04e2e5a7ac9..09612b02258 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -1,5 +1,5 @@ Name: centreon-gorgone -Version: 19.10 +Version: 19.10.0 Release: 1%{?dist} Summary: Perl daemon task handlers Group: Applications/System From e8e309c92b466e535df4bae629cfef74ca07c6f3 Mon Sep 17 00:00:00 2001 From: Matthieu Kermagoret Date: Wed, 25 Sep 2019 13:12:58 +0200 Subject: [PATCH 155/948] fix: use @RELEASE@ macro in spectemplate. --- gorgone/packaging/centreon-gorgone.spectemplate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 09612b02258..03358ad8340 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -1,6 +1,6 @@ Name: centreon-gorgone Version: 19.10.0 -Release: 1%{?dist} +Release: @RELEASE@%{?dist} Summary: Perl daemon task handlers Group: Applications/System License: Apache2 From bbd8af44a326e2c43aaaf86e2c946a1dcdcd34ba Mon Sep 17 00:00:00 2001 From: Matthieu Kermagoret Date: Wed, 25 Sep 2019 13:40:06 +0200 Subject: [PATCH 156/948] enh: add sonar-project.properties to run SonarQube analysis. --- gorgone/sonar-project.properties | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 gorgone/sonar-project.properties diff --git a/gorgone/sonar-project.properties b/gorgone/sonar-project.properties new file mode 100644 index 00000000000..703363e7eec --- /dev/null +++ b/gorgone/sonar-project.properties @@ -0,0 +1,3 @@ +sonar.projectKey=centreon-gorgone-19.10 +sonar.projectName=Centreon Gorgone 19.10 +sonar.sources=. From bc65241d141865383269a2c5fb14b0aba1f2e0a4 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 25 Sep 2019 13:57:12 +0200 Subject: [PATCH 157/948] fix(packaging): fix spec template requires --- gorgone/packaging/centreon-gorgone.spectemplate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 03358ad8340..5a13f17c842 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -11,7 +11,7 @@ BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) Requires: perl-Cryptx Requires: perl(Schedule::Cron) -Requires: perl(perl(ZMQ::LibZMQ4)) +Requires: perl(ZMQ::LibZMQ4) Requires: perl(ZMQ::Constants) Requires: perl(Crypt::CBC) Requires: perl(JSON::XS) From 2713d86431ed72ca989771fe34bce4a22d26a754 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 25 Sep 2019 13:58:02 +0200 Subject: [PATCH 158/948] enh(service): add config option in service start --- gorgone/scripts/systemd/centreon-gorgone-service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/scripts/systemd/centreon-gorgone-service b/gorgone/scripts/systemd/centreon-gorgone-service index 2408af0941e..b5ab8cf4107 100644 --- a/gorgone/scripts/systemd/centreon-gorgone-service +++ b/gorgone/scripts/systemd/centreon-gorgone-service @@ -19,7 +19,7 @@ Description=Centreon Gorgone [Service] -ExecStart=/usr/bin/perl /usr/bin/gorgoned --logfile=/var/log/centreon/centreon-gorgone.log --severity=error +ExecStart=/usr/bin/perl /usr/bin/gorgoned --config=/etc/centreon/gorgoned.yml --logfile=/var/log/centreon/centreon-gorgone.log --severity=error Type=simple User=centreon From 048d69ce4e647b029bafb49bc4e715925108c4df Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 25 Sep 2019 13:59:23 +0200 Subject: [PATCH 159/948] enh(service): change log name --- gorgone/scripts/systemd/centreon-gorgone-service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/scripts/systemd/centreon-gorgone-service b/gorgone/scripts/systemd/centreon-gorgone-service index b5ab8cf4107..cc327ec4bf1 100644 --- a/gorgone/scripts/systemd/centreon-gorgone-service +++ b/gorgone/scripts/systemd/centreon-gorgone-service @@ -19,7 +19,7 @@ Description=Centreon Gorgone [Service] -ExecStart=/usr/bin/perl /usr/bin/gorgoned --config=/etc/centreon/gorgoned.yml --logfile=/var/log/centreon/centreon-gorgone.log --severity=error +ExecStart=/usr/bin/perl /usr/bin/gorgoned --config=/etc/centreon/gorgoned.yml --logfile=/var/log/centreon/gorgoned.log --severity=error Type=simple User=centreon From a10e65ef50e8c762f3cc6a3870837ed379b78d71 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 25 Sep 2019 14:04:29 +0200 Subject: [PATCH 160/948] fix(service); really use sysconfig file --- gorgone/scripts/systemd/centreon-gorgone-service | 3 ++- gorgone/scripts/systemd/centreon-gorgone-sysconfig | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/gorgone/scripts/systemd/centreon-gorgone-service b/gorgone/scripts/systemd/centreon-gorgone-service index cc327ec4bf1..8f443666a7f 100644 --- a/gorgone/scripts/systemd/centreon-gorgone-service +++ b/gorgone/scripts/systemd/centreon-gorgone-service @@ -19,7 +19,8 @@ Description=Centreon Gorgone [Service] -ExecStart=/usr/bin/perl /usr/bin/gorgoned --config=/etc/centreon/gorgoned.yml --logfile=/var/log/centreon/gorgoned.log --severity=error +EnvironmentFile=/etc/sysconfig/centreon-gorgone +ExecStart=/usr/bin/perl /usr/bin/gorgoned $OPTIONS Type=simple User=centreon diff --git a/gorgone/scripts/systemd/centreon-gorgone-sysconfig b/gorgone/scripts/systemd/centreon-gorgone-sysconfig index 67c3bcfde83..ae0c673c291 100644 --- a/gorgone/scripts/systemd/centreon-gorgone-sysconfig +++ b/gorgone/scripts/systemd/centreon-gorgone-sysconfig @@ -1,2 +1,4 @@ -# centreon-gorgone command line options -OPTIONS="--logfile=/var/log/centreon/centreon-gorgone.log --severity=error" \ No newline at end of file +# Configuration file for Centreon Gorgone. + +# OPTIONS fro the daemon launch +OPTIONS="--config=/etc/centreon/gorgoned.yml --logfile=/var/log/centreon/gorgoned.log --severity=error" \ No newline at end of file From 4171a4b69e3698c8bb8d4c4f258d4e4d350cdad4 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 25 Sep 2019 14:20:49 +0200 Subject: [PATCH 161/948] fix(packaging): fix requires --- gorgone/packaging/centreon-gorgone.spectemplate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 5a13f17c842..544b9e7f504 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -9,7 +9,7 @@ Source0: %{name}-%{version}.tar.gz BuildArch: noarch BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) -Requires: perl-Cryptx +Requires: perl-CryptX Requires: perl(Schedule::Cron) Requires: perl(ZMQ::LibZMQ4) Requires: perl(ZMQ::Constants) From fcb30318c4a36fdc3291acb5b34314e75050d792 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 25 Sep 2019 14:24:54 +0200 Subject: [PATCH 162/948] fix(config): update module package --- gorgone/config/gorgoned-central-ssh.yml | 14 ++++++------- gorgone/config/gorgoned-central-zmq.yml | 14 ++++++------- gorgone/config/gorgoned-poller.yml | 4 ++-- gorgone/config/gorgoned-remote-ssh.yml | 12 +++++------ gorgone/config/gorgoned-remote-zmq.yml | 12 +++++------ gorgone/config/gorgoned.yml | 27 +++++++++++-------------- 6 files changed, 40 insertions(+), 43 deletions(-) diff --git a/gorgone/config/gorgoned-central-ssh.yml b/gorgone/config/gorgoned-central-ssh.yml index 656e45e5d2b..346176aa80c 100644 --- a/gorgone/config/gorgoned-central-ssh.yml +++ b/gorgone/config/gorgoned-central-ssh.yml @@ -31,7 +31,7 @@ gorgonecore: sessions_time: 86400 modules: - name: httpserver - package: gorgone::modules::core::httpserver::hooks + package: gorgone::gorgone::modules::core::httpserver::hooks enable: true address: 0.0.0.0 port: 8443 @@ -43,7 +43,7 @@ modules: password: password - name: cron - package: gorgone::modules::core::cron::hooks + package: gorgone::gorgone::modules::core::cron::hooks enable: true cron: - id: echo_date @@ -54,19 +54,19 @@ modules: timeout: 10 - name: action - package: gorgone::modules::core::action::hooks + package: gorgone::gorgone::modules::core::action::hooks enable: true - name: proxy - package: gorgone::modules::core::proxy::hooks + package: gorgone::gorgone::modules::core::proxy::hooks enable: true - name: pollers - package: gorgone::modules::centreon::pollers::hooks + package: gorgone::gorgone::modules::centreon::pollers::hooks enable: true - name: legacycmd - package: gorgone::modules::centreon::legacycmd::hooks + package: gorgone::gorgone::modules::centreon::legacycmd::hooks enable: true cmd_file: "/var/lib/centreon/centcore.cmd" cache_dir: "/var/cache/centreon/" @@ -74,6 +74,6 @@ modules: remote_dir: "/var/lib/centreon/remote-data/" - name: engine - package: gorgone::modules::centreon::engine::hooks + package: gorgone::gorgone::modules::centreon::engine::hooks enable: true command_file: "/var/lib/centreon-engine/rw/centengine.cmd" diff --git a/gorgone/config/gorgoned-central-zmq.yml b/gorgone/config/gorgoned-central-zmq.yml index bf13104c069..d23135d95ad 100644 --- a/gorgone/config/gorgoned-central-zmq.yml +++ b/gorgone/config/gorgoned-central-zmq.yml @@ -32,7 +32,7 @@ gorgonecore: sessions_time: 86400 modules: - name: httpserver - package: gorgone::modules::core::httpserver::hooks + package: gorgone::gorgone::modules::core::httpserver::hooks enable: true address: 0.0.0.0 port: 8443 @@ -44,7 +44,7 @@ modules: password: password - name: cron - package: gorgone::modules::core::cron::hooks + package: gorgone::gorgone::modules::core::cron::hooks enable: true cron: - id: echo_date @@ -55,20 +55,20 @@ modules: timeout: 10 - name: action - package: gorgone::modules::core::action::hooks + package: gorgone::gorgone::modules::core::action::hooks enable: true - name: proxy - package: gorgone::modules::core::proxy::hooks + package: gorgone::gorgone::modules::core::proxy::hooks enable: true - name: register - package: gorgone::modules::core::register::hooks + package: gorgone::gorgone::modules::core::register::hooks enable: true config_file: config/registernodes-central.yml - name: legacycmd - package: gorgone::modules::centreon::legacycmd::hooks + package: gorgone::gorgone::modules::centreon::legacycmd::hooks enable: true cmd_file: "/var/lib/centreon/centcore.cmd" cache_dir: "/var/cache/centreon/" @@ -76,6 +76,6 @@ modules: remote_dir: "/var/lib/centreon/remote-data/" - name: engine - package: gorgone::modules::centreon::engine::hooks + package: gorgone::gorgone::modules::centreon::engine::hooks enable: true command_file: "/var/lib/centreon-engine/rw/centengine.cmd" diff --git a/gorgone/config/gorgoned-poller.yml b/gorgone/config/gorgoned-poller.yml index 37883a1e3c0..79592079a1e 100644 --- a/gorgone/config/gorgoned-poller.yml +++ b/gorgone/config/gorgoned-poller.yml @@ -25,10 +25,10 @@ gorgonecore: purge_sessions_time: 3600 modules: - name: action - package: modules::core::action::hooks + package: gorgone::modules::core::action::hooks enable: true - name: engine - package: modules::centreon::engine::hooks + package: gorgone::modules::centreon::engine::hooks enable: true command_file: "/var/lib/centreon-engine/rw/centengine.cmd" diff --git a/gorgone/config/gorgoned-remote-ssh.yml b/gorgone/config/gorgoned-remote-ssh.yml index 870c6c5adc4..de21881cff5 100644 --- a/gorgone/config/gorgoned-remote-ssh.yml +++ b/gorgone/config/gorgoned-remote-ssh.yml @@ -31,7 +31,7 @@ gorgonecore: sessions_time: 86400 modules: - name: cron - package: modules::core::cron::hooks + package: gorgone::modules::core::cron::hooks enable: true cron: - id: echo_date @@ -42,19 +42,19 @@ modules: timeout: 10 - name: action - package: modules::core::action::hooks + package: gorgone::modules::core::action::hooks enable: true - name: proxy - package: modules::core::proxy::hooks + package: gorgone::modules::core::proxy::hooks enable: true - name: pollers - package: modules::centreon::pollers::hooks + package: gorgone::modules::centreon::pollers::hooks enable: true - name: legacycmd - package: modules::centreon::legacycmd::hooks + package: gorgone::modules::centreon::legacycmd::hooks enable: true cmd_file: "/var/lib/centreon/centcore.cmd" cache_dir: "/var/cache/centreon/" @@ -62,6 +62,6 @@ modules: remote_dir: "/var/lib/centreon/remote-data/" - name: engine - package: modules::centreon::engine::hooks + package: gorgone::modules::centreon::engine::hooks enable: true command_file: "/var/lib/centreon-engine/rw/centengine.cmd" diff --git a/gorgone/config/gorgoned-remote-zmq.yml b/gorgone/config/gorgoned-remote-zmq.yml index 0354a6b93f3..a1f5b26cb5e 100644 --- a/gorgone/config/gorgoned-remote-zmq.yml +++ b/gorgone/config/gorgoned-remote-zmq.yml @@ -32,7 +32,7 @@ gorgonecore: sessions_time: 86400 modules: - name: cron - package: modules::core::cron::hooks + package: gorgone::modules::core::cron::hooks enable: true cron: - id: echo_date @@ -43,20 +43,20 @@ modules: timeout: 10 - name: action - package: modules::core::action::hooks + package: gorgone::modules::core::action::hooks enable: true - name: proxy - package: modules::core::proxy::hooks + package: gorgone::modules::core::proxy::hooks enable: true - name: register - package: modules::core::register::hooks + package: gorgone::modules::core::register::hooks enable: true config_file: config/registernodes-remote.yml - name: legacycmd - package: modules::centreon::legacycmd::hooks + package: gorgone::modules::centreon::legacycmd::hooks enable: true cmd_file: "/var/lib/centreon/centcore.cmd" cache_dir: "/var/cache/centreon/" @@ -64,6 +64,6 @@ modules: remote_dir: "/var/lib/centreon/remote-data/" - name: engine - package: modules::centreon::engine::hooks + package: gorgone::modules::centreon::engine::hooks enable: true command_file: "/var/lib/centreon-engine/rw/centengine.cmd" diff --git a/gorgone/config/gorgoned.yml b/gorgone/config/gorgoned.yml index 42615f4d4b0..2914439354e 100644 --- a/gorgone/config/gorgoned.yml +++ b/gorgone/config/gorgoned.yml @@ -40,12 +40,9 @@ gorgonecore: # in seconds sessions_time: 86400 - # in seconds - #purge_sessions_time: 3600 - #purge_history_time: 604800 modules: - name: httpserver - package: modules::core::httpserver::hooks + package: gorgone::modules::core::httpserver::hooks enable: true address: 0.0.0.0 port: 8443 @@ -57,7 +54,7 @@ modules: password: password - name: cron - package: modules::core::cron::hooks + package: gorgone::modules::core::cron::hooks enable: true cron: - id: echo_date @@ -68,11 +65,11 @@ modules: timeout: 10 - name: action - package: modules::core::action::hooks + package: gorgone::modules::core::action::hooks enable: true - name: proxy - package: modules::core::proxy::hooks + package: gorgone::modules::core::proxy::hooks enable: false #pool: 5 # sync history each 5 minutes @@ -83,25 +80,25 @@ modules: #ping: 60 - name: pollers - package: modules::centreon::pollers::hooks + package: gorgone::modules::centreon::pollers::hooks enable: false - name: register - package: modules::core::register::hooks + package: gorgone::modules::core::register::hooks enable: true config_file: config/registernodes.yml - name: engine - package: modules::centreon::engine::hooks + package: gorgone::modules::centreon::engine::hooks enable: true command_file: "/var/lib/centreon-engine/rw/centengine.cmd" - name: autodiscovery - package: modules::centreon::autodiscovery::hooks + package: gorgone::modules::centreon::autodiscovery::hooks enable: true - name: broker - package: modules::centreon::broker::hooks + package: gorgone::modules::centreon::broker::hooks enable: false cache_dir: "/var/lib/centreon/broker-stats/" cron: @@ -112,7 +109,7 @@ modules: timeout: 10 - name: legacycmd - package: modules::centreon::legacycmd::hooks + package: gorgone::modules::centreon::legacycmd::hooks enable: true cmd_file: "/var/lib/centreon/centcore.cmd" cache_dir: "/var/cache/centreon/" @@ -120,7 +117,7 @@ modules: remote_dir: "/var/lib/centreon/remote-data/" - name: scom - package: modules::plugins::scom::hooks + package: gorgone::modules::plugins::scom::hooks enable: false # in seconds - do purge for container also check_containers_time: 3600 @@ -148,7 +145,7 @@ modules: # resync_time: 600 - name: newtest - package: modules::plugins::newtest::hooks + package: gorgone::modules::plugins::newtest::hooks enable: false # in seconds - do purge for container also check_containers_time: 3600 From 1cf5521fc356775aa3d6b6f86f3cdfeb1b45d982 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 25 Sep 2019 14:26:16 +0200 Subject: [PATCH 163/948] fix(service): fix typo --- gorgone/scripts/systemd/centreon-gorgone-sysconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/scripts/systemd/centreon-gorgone-sysconfig b/gorgone/scripts/systemd/centreon-gorgone-sysconfig index ae0c673c291..e42a3f37104 100644 --- a/gorgone/scripts/systemd/centreon-gorgone-sysconfig +++ b/gorgone/scripts/systemd/centreon-gorgone-sysconfig @@ -1,4 +1,4 @@ # Configuration file for Centreon Gorgone. -# OPTIONS fro the daemon launch -OPTIONS="--config=/etc/centreon/gorgoned.yml --logfile=/var/log/centreon/gorgoned.log --severity=error" \ No newline at end of file +# OPTIONS for the daemon launch +OPTIONS="--config=/etc/centreon/gorgoned.yml --logfile=/var/log/centreon/gorgoned.log --severity=error" From e9badf58bd0f01d9fbc6e72aa92d7ce44886a446 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 25 Sep 2019 14:28:52 +0200 Subject: [PATCH 164/948] fix(config): fix central config examples --- gorgone/config/gorgoned-central-ssh.yml | 14 +++++++------- gorgone/config/gorgoned-central-zmq.yml | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/gorgone/config/gorgoned-central-ssh.yml b/gorgone/config/gorgoned-central-ssh.yml index 346176aa80c..656e45e5d2b 100644 --- a/gorgone/config/gorgoned-central-ssh.yml +++ b/gorgone/config/gorgoned-central-ssh.yml @@ -31,7 +31,7 @@ gorgonecore: sessions_time: 86400 modules: - name: httpserver - package: gorgone::gorgone::modules::core::httpserver::hooks + package: gorgone::modules::core::httpserver::hooks enable: true address: 0.0.0.0 port: 8443 @@ -43,7 +43,7 @@ modules: password: password - name: cron - package: gorgone::gorgone::modules::core::cron::hooks + package: gorgone::modules::core::cron::hooks enable: true cron: - id: echo_date @@ -54,19 +54,19 @@ modules: timeout: 10 - name: action - package: gorgone::gorgone::modules::core::action::hooks + package: gorgone::modules::core::action::hooks enable: true - name: proxy - package: gorgone::gorgone::modules::core::proxy::hooks + package: gorgone::modules::core::proxy::hooks enable: true - name: pollers - package: gorgone::gorgone::modules::centreon::pollers::hooks + package: gorgone::modules::centreon::pollers::hooks enable: true - name: legacycmd - package: gorgone::gorgone::modules::centreon::legacycmd::hooks + package: gorgone::modules::centreon::legacycmd::hooks enable: true cmd_file: "/var/lib/centreon/centcore.cmd" cache_dir: "/var/cache/centreon/" @@ -74,6 +74,6 @@ modules: remote_dir: "/var/lib/centreon/remote-data/" - name: engine - package: gorgone::gorgone::modules::centreon::engine::hooks + package: gorgone::modules::centreon::engine::hooks enable: true command_file: "/var/lib/centreon-engine/rw/centengine.cmd" diff --git a/gorgone/config/gorgoned-central-zmq.yml b/gorgone/config/gorgoned-central-zmq.yml index d23135d95ad..bf13104c069 100644 --- a/gorgone/config/gorgoned-central-zmq.yml +++ b/gorgone/config/gorgoned-central-zmq.yml @@ -32,7 +32,7 @@ gorgonecore: sessions_time: 86400 modules: - name: httpserver - package: gorgone::gorgone::modules::core::httpserver::hooks + package: gorgone::modules::core::httpserver::hooks enable: true address: 0.0.0.0 port: 8443 @@ -44,7 +44,7 @@ modules: password: password - name: cron - package: gorgone::gorgone::modules::core::cron::hooks + package: gorgone::modules::core::cron::hooks enable: true cron: - id: echo_date @@ -55,20 +55,20 @@ modules: timeout: 10 - name: action - package: gorgone::gorgone::modules::core::action::hooks + package: gorgone::modules::core::action::hooks enable: true - name: proxy - package: gorgone::gorgone::modules::core::proxy::hooks + package: gorgone::modules::core::proxy::hooks enable: true - name: register - package: gorgone::gorgone::modules::core::register::hooks + package: gorgone::modules::core::register::hooks enable: true config_file: config/registernodes-central.yml - name: legacycmd - package: gorgone::gorgone::modules::centreon::legacycmd::hooks + package: gorgone::modules::centreon::legacycmd::hooks enable: true cmd_file: "/var/lib/centreon/centcore.cmd" cache_dir: "/var/cache/centreon/" @@ -76,6 +76,6 @@ modules: remote_dir: "/var/lib/centreon/remote-data/" - name: engine - package: gorgone::gorgone::modules::centreon::engine::hooks + package: gorgone::modules::centreon::engine::hooks enable: true command_file: "/var/lib/centreon-engine/rw/centengine.cmd" From deffad72b8a1cf0fc425187559a53d9bfa069cb5 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 25 Sep 2019 14:42:31 +0200 Subject: [PATCH 165/948] enh(core): move db cleaning in a moduel --- gorgone/TODO | 1 - gorgone/gorgone/class/core.pm | 35 +--- gorgone/gorgone/class/db.pm | 5 +- .../gorgone/modules/core/dbcleaner/class.pm | 197 ++++++++++++++++++ .../gorgone/modules/core/dbcleaner/hooks.pm | 171 +++++++++++++++ 5 files changed, 377 insertions(+), 32 deletions(-) create mode 100644 gorgone/gorgone/modules/core/dbcleaner/class.pm create mode 100644 gorgone/gorgone/modules/core/dbcleaner/hooks.pm diff --git a/gorgone/TODO b/gorgone/TODO index e0e57e60ffe..1b6d3ca7fbb 100644 --- a/gorgone/TODO +++ b/gorgone/TODO @@ -2,6 +2,5 @@ - gorgone-newtest: don't use centcore.cmd. use ssh system. - Add an action to load module dynamically - Add a broadcast action to update module (like logger in debug for example) -- Move cleaning db in a module - Add redis backend to store logs (we could disable synclog in redis mode) - Add a websocket module to call gorgone diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 7901b867794..7c8ded4e456 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -83,17 +83,6 @@ sub init { logger => $self->{logger} ); - $config->{gorgonecore}->{purge_sessions_time} = - defined($config->{gorgonecore}->{purge_sessions_time}) && $config->{gorgonecore}->{purge_sessions_time} =~ /\d+/ ? - $config->{gorgonecore}->{purge_sessions_time} : - 3600 - ; - $config->{gorgonecore}->{purge_history_time} = - defined($config->{gorgonecore}->{purge_history_time}) && $config->{gorgonecore}->{purge_history_time} =~ /\d+/ ? - $config->{gorgonecore}->{purge_history_time} : - 604800 - ; - if (defined($config->{gorgonecore}->{external_com_type}) && $config->{gorgonecore}->{external_com_type} ne '') { $self->{server_privkey} = gorgone::standard::library::loadprivkey(logger => $self->{logger}, privkey => $config->{gorgonecore}->{privkey}); } @@ -266,6 +255,9 @@ sub load_modules { $self->load_module(config_module => $module); } + # force to load module dbclean + $self->load_module(config_module => { name => 'dbcleaner', package => 'gorgone::modules::core::dbcleaner::hooks', enable => 'true' }); + # Load internal functions foreach my $method_name (('putlog', 'getlog', 'kill', 'ping', 'constatus', 'setcoreid', 'synclogs', 'loadmodule')) { unless ($self->{internal_register}->{$method_name} = gorgone::standard::library->can($method_name)) { @@ -519,7 +511,7 @@ sub router_external_event { sub waiting_ready_pool { my (%options) = @_; - + my $time = time(); # We wait 10 seconds while (time() - $time < 10) { @@ -531,7 +523,7 @@ sub waiting_ready_pool { foreach my $pool_id (keys %{$options{pool}}) { return 1 if ($options{pool}->{$pool_id}->{ready} == 1); } - + return 0; } @@ -546,23 +538,12 @@ sub waiting_ready { time() - $time < 10) { zmq_poll($gorgone->{poll}, 5000); } - + if (${$options{ready}} == 0) { return 0; } - - return 1; -} - -sub clean_db { - my ($self, %options) = @_; - if ($self->{purge_timer} - time() > 3600) { - $self->{logger}->writeLogInfo("[core] Purge db in progress..."); - $self->{db_gorgone}->query("DELETE FROM gorgone_identity WHERE `ctime` < " . $self->{db_gorgone}->quote(time() - $config->{gorgonecore}->{purge_sessions_time})); - $self->{db_gorgone}->query("DELETE FROM gorgone_history WHERE `ctime` < " . $self->{db_gorgone}->quote(time() - $config->{gorgonecore}->{purge_history_time})); - $self->{purge_timer} = time(); - } + return 1; } sub quit { @@ -674,8 +655,6 @@ sub run { } zmq_poll($poll, 5000); - - $gorgone->clean_db(); } } diff --git a/gorgone/gorgone/class/db.pm b/gorgone/gorgone/class/db.pm index fc9e2569efb..af717f85837 100644 --- a/gorgone/gorgone/class/db.pm +++ b/gorgone/gorgone/class/db.pm @@ -26,8 +26,7 @@ use DBI; sub new { my ($class, %options) = @_; - my %defaults = - ( + my %defaults = ( logger => undef, db => undef, dsn => undef, @@ -37,7 +36,7 @@ sub new { port => 3306, force => 0, type => "mysql" - ); + ); my $self = {%defaults, %options}; $self->{type} = 'mysql' if (!defined($self->{type})); diff --git a/gorgone/gorgone/modules/core/dbcleaner/class.pm b/gorgone/gorgone/modules/core/dbcleaner/class.pm new file mode 100644 index 00000000000..29986971504 --- /dev/null +++ b/gorgone/gorgone/modules/core/dbcleaner/class.pm @@ -0,0 +1,197 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::core::dbcleaner::class; + +use base qw(gorgone::class::module); + +use strict; +use warnings; +use gorgone::class::db; +use gorgone::standard::library; +use ZMQ::LibZMQ4; +use ZMQ::Constants qw(:all); +use JSON::XS; + +my %handlers = (TERM => {}, HUP => {}); +my ($connector); + +sub new { + my ($class, %options) = @_; + + $connector = {}; + $connector->{internal_socket} = undef; + $connector->{module_id} = $options{module_id}; + $connector->{logger} = $options{logger}; + $connector->{config} = $options{config}; + $connector->{config_core} = $options{config_core}; + $connector->{stop} = 0; + $connector->{purge_timer} = time(); + + bless $connector, $class; + $connector->set_signal_handlers(); + return $connector; +} + +sub set_signal_handlers { + my $self = shift; + + $SIG{TERM} = \&class_handle_TERM; + $handlers{TERM}->{$self} = sub { $self->handle_TERM() }; + $SIG{HUP} = \&class_handle_HUP; + $handlers{HUP}->{$self} = sub { $self->handle_HUP() }; +} + +sub handle_HUP { + my $self = shift; + $self->{reload} = 0; +} + +sub handle_TERM { + my $self = shift; + $self->{logger}->writeLogInfo("[dbcleaner] -class- $$ Receiving order to stop..."); + $self->{stop} = 1; +} + +sub class_handle_TERM { + foreach (keys %{$handlers{TERM}}) { + &{$handlers{TERM}->{$_}}(); + } +} + +sub class_handle_HUP { + foreach (keys %{$handlers{HUP}}) { + &{$handlers{HUP}->{$_}}(); + } +} + +sub action_dbclean { + my ($self, %options) = @_; + + $options{token} = $self->generate_token() if (!defined($options{token})); + + if (defined($options{cycle})) { + return 0 if ($self->{purge_timer} - time() < 3600); + } + + $self->send_log( + code => gorgone::class::module::ACTION_BEGIN, + token => $options{token}, + data => { + message => 'action dbclean proceed' + } + ) if (!defined($options{cycle})); + + $self->{logger}->writeLogInfo("[dbcleaner] -class- purge db in progress..."); + my ($status) = $self->{db_gorgone}->query("DELETE FROM gorgone_identity WHERE `ctime` < " . $self->{db_gorgone}->quote(time() - $self->{config}->{purge_sessions_time})); + my ($status2) = $self->{db_gorgone}->query("DELETE FROM gorgone_history WHERE `ctime` < " . $self->{db_gorgone}->quote(time() - $self->{config}->{purge_history_time})); + $self->{purge_timer} = time(); + + $self->{logger}->writeLogDebug("[dbcleaner] -class- finish dbclean"); + + if ($status == -1 || $status2 == -1) { + $self->send_log( + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { + message => 'action dbclean finished' + } + ) if (!defined($options{cycle})); + return 0; + } + + $self->send_log( + code => $self->ACTION_FINISH_OK, + token => $options{token}, + data => { + message => 'action dbclean finished' + } + ) if (!defined($options{cycle})); + return 0; +} + +sub event { + while (1) { + my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); + + $connector->{logger}->writeLogDebug("[dbcleaner] -class- Event: $message"); + if ($message =~ /^\[(.*?)\]/) { + if ((my $method = $connector->can('action_' . lc($1)))) { + $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; + my ($action, $token) = ($1, $2); + my $data = JSON::XS->new->utf8->decode($3); + $method->($connector, token => $token, data => $data); + } + } + + last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); + } +} + +sub run { + my ($self, %options) = @_; + + # Connect internal + $connector->{internal_socket} = gorgone::standard::library::connect_com( + zmq_type => 'ZMQ_DEALER', + name => 'gorgonedbcleaner', + logger => $self->{logger}, + type => $self->{config_core}->{internal_com_type}, + path => $self->{config_core}->{internal_com_path} + ); + gorgone::standard::library::zmq_send_message( + socket => $connector->{internal_socket}, + action => 'DBCLEANERREADY', + data => {}, + json_encode => 1 + ); + $self->{poll} = [ + { + socket => $connector->{internal_socket}, + events => ZMQ_POLLIN, + callback => \&event, + } + ]; + + $self->{db_gorgone} = gorgone::class::db->new( + type => $self->{config_core}->{gorgone_db_type}, + db => $self->{config_core}->{gorgone_db_name}, + host => $self->{config_core}->{gorgone_db_host}, + port => $self->{config_core}->{gorgone_db_port}, + user => $self->{config_core}->{gorgone_db_user}, + password => $self->{config_core}->{gorgone_db_password}, + force => 2, + logger => $self->{logger} + ); + + while (1) { + # we try to do all we can + my $rev = zmq_poll($self->{poll}, 5000); + if (defined($rev) && $rev == 0 && $self->{stop} == 1) { + $self->{logger}->writeLogInfo("[dbcleaner] -class- $$ has quit"); + zmq_close($connector->{internal_socket}); + exit(0); + } + + $self->action_dbclean(cycle => 1); + } +} + +1; diff --git a/gorgone/gorgone/modules/core/dbcleaner/hooks.pm b/gorgone/gorgone/modules/core/dbcleaner/hooks.pm new file mode 100644 index 00000000000..5b73bfdf969 --- /dev/null +++ b/gorgone/gorgone/modules/core/dbcleaner/hooks.pm @@ -0,0 +1,171 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::core::dbcleaner::hooks; + +use warnings; +use strict; +use JSON::XS; +use gorgone::class::core; +use gorgone::modules::core::dbcleaner::class; + +use constant NAMESPACE => 'core'; +use constant NAME => 'dbcleaner'; +use constant EVENTS => [ + { event => 'DBCLEANERREADY' }, +]; + +my $config_core; +my $config; +my ($config_db_centreon); +my $dbcleaner = {}; +my $stop = 0; + +sub register { + my (%options) = @_; + + $config = $options{config}; + $config_core = $options{config_core}; + $config->{purge_sessions_time} = + defined($config->{purge_sessions_time}) && $config->{purge_sessions_time} =~ /(\d+)/ ? + $1 : + 3600 + ; + $config->{purge_history_time} = + defined($config->{purge_history_time}) && $config->{purge_history_time} =~ /(\d+)/ ? + $1 : + 604800 + ; + return (1, NAMESPACE, NAME, EVENTS); +} + +sub init { + my (%options) = @_; + + create_child(logger => $options{logger}); +} + +sub routing { + my (%options) = @_; + + my $data; + eval { + $data = JSON::XS->new->utf8->decode($options{data}); + }; + if ($@) { + $options{logger}->writeLogError("[dbcleaner] -hooks- Cannot decode json data: $@"); + gorgone::standard::library::add_history( + dbh => $options{dbh}, + code => 10, token => $options{token}, + data => { message => 'gorgonedbcleaner cannot decode json' }, + json_encode => 1 + ); + return undef; + } + + if ($options{action} eq 'DBCLEANERREADY') { + $dbcleaner->{ready} = 1; + return undef; + } + + if (gorgone::class::core::waiting_ready(ready => \$dbcleaner->{ready}) == 0) { + gorgone::standard::library::add_history( + dbh => $options{dbh}, + code => 10, token => $options{token}, + data => { message => 'gorgonedbcleaner: still no ready' }, + json_encode => 1 + ); + return undef; + } + + gorgone::standard::library::zmq_send_message( + socket => $options{socket}, + identity => 'gorgonedbcleaner', + action => $options{action}, + data => $options{data}, + token => $options{token}, + ); +} + +sub gently { + my (%options) = @_; + + $stop = 1; + $options{logger}->writeLogInfo("[dbcleaner] -hooks- Send TERM signal"); + if ($dbcleaner->{running} == 1) { + CORE::kill('TERM', $dbcleaner->{pid}); + } +} + +sub kill { + my (%options) = @_; + + if ($dbcleaner->{running} == 1) { + $options{logger}->writeLogInfo("[dbcleaner] -hooks- Send KILL signal for pool"); + CORE::kill('KILL', $dbcleaner->{pid}); + } +} + +sub kill_internal { + my (%options) = @_; + +} + +sub check { + my (%options) = @_; + + my $count = 0; + foreach my $pid (keys %{$options{dead_childs}}) { + # Not me + next if ($dbcleaner->{pid} != $pid); + + $dbcleaner = {}; + delete $options{dead_childs}->{$pid}; + if ($stop == 0) { + create_child(logger => $options{logger}); + } + } + + $count++ if (defined($dbcleaner->{running}) && $dbcleaner->{running} == 1); + + return $count; +} + +# Specific functions +sub create_child { + my (%options) = @_; + + $options{logger}->writeLogInfo("[dbcleaner] -hooks- Create module 'dbcleaner' process"); + my $child_pid = fork(); + if ($child_pid == 0) { + $0 = 'gorgone-dbcleaner'; + my $module = gorgone::modules::core::dbcleaner::class->new( + logger => $options{logger}, + config_core => $config_core, + config => $config, + ); + $module->run(); + exit(0); + } + $options{logger}->writeLogInfo("[dbcleaner] -hooks- PID $child_pid (gorgone-dbcleaner)"); + $dbcleaner = { pid => $child_pid, ready => 0, running => 1 }; +} + +1; From 7064bd4f83b5ced75a128074dc61c9acbef5a374 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 25 Sep 2019 14:49:59 +0200 Subject: [PATCH 166/948] update todo --- gorgone/TODO | 1 - 1 file changed, 1 deletion(-) diff --git a/gorgone/TODO b/gorgone/TODO index 1b6d3ca7fbb..887c138a903 100644 --- a/gorgone/TODO +++ b/gorgone/TODO @@ -1,6 +1,5 @@ - Add a core event: GETINFORMATION (events loaded, target loaded and module loaded) - gorgone-newtest: don't use centcore.cmd. use ssh system. -- Add an action to load module dynamically - Add a broadcast action to update module (like logger in debug for example) - Add redis backend to store logs (we could disable synclog in redis mode) - Add a websocket module to call gorgone From c700e9e609e3acb4c473409fe3aa400f315080ec Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 25 Sep 2019 15:01:17 +0200 Subject: [PATCH 167/948] fix(legacycmd): dirty_mode --- .../modules/centreon/legacycmd/class.pm | 75 ++++++++++--------- 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index c1e922c3379..672e2c03da7 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -46,6 +46,7 @@ sub new { if (!defined($connector->{config}->{cmd_dir}) || $connector->{config}->{cmd_dir} eq '') { $connector->{config}->{cmd_dir} = '/var/lib/centreon/centcore/'; } + $connector->{config}->{dirty_mode} = defined($connector->{config}->{dirty_mode}) ? $connector->{config}->{dirty_mode} : 1; $connector->{config_core} = $options{config_core}; $connector->{config_db_centreon} = $options{config_db_centreon}; $connector->{stop} = 0; @@ -87,35 +88,6 @@ sub class_handle_HUP { } } -sub move_cmd_file { - my ($self, %options) = @_; - - my $handle; - if (-e $options{dst}) { - if (!open($handle, '+<', $options{dst})) { - $self->{logger}->writeLogError("[legacycmd] -class- cannot open file '" . $options{dst} . "': $!"); - return -1; - } - - return (0, $handle); - } - - return -1 if (!defined($options{src})); - return -1 if (! -e $options{src}); - - if (!File::Copy::move($options{src}, $options{dst})) { - $self->{logger}->writeLogError("[legacycmd] -class- cannot move file '" . $options{src} . "': $!"); - return -1; - } - - if (!open($handle, '+<', $options{dst})) { - $self->{logger}->writeLogError("[legacycmd] -class- cannot open file '" . $options{dst} . "': $!"); - return -1; - } - - return (0, $handle); -} - sub get_pollers_config { my ($self, %options) = @_; @@ -424,6 +396,39 @@ sub execute_cmd { } } +sub move_cmd_file { + my ($self, %options) = @_; + + my $operator = '+<'; + if ($self->{config}->{dirty_mode} == 1) { + $operator = '<'; + } + my $handle; + if (-e $options{dst}) { + if (!open($handle, $operator, $options{dst})) { + $self->{logger}->writeLogError("[legacycmd] -class- cannot open file '" . $options{dst} . "': $!"); + return -1; + } + + return (0, $handle); + } + + return -1 if (!defined($options{src})); + return -1 if (! -e $options{src}); + + if (!File::Copy::move($options{src}, $options{dst})) { + $self->{logger}->writeLogError("[legacycmd] -class- cannot move file '" . $options{src} . "': $!"); + return -1; + } + + if (!open($handle, $operator, $options{dst})) { + $self->{logger}->writeLogError("[legacycmd] -class- cannot open file '" . $options{dst} . "': $!"); + return -1; + } + + return (0, $handle); +} + sub handle_file { my ($self, %options) = @_; require bytes; @@ -438,11 +443,13 @@ sub handle_file { if ($line =~ /^(.*?):([^:]*)(?::(.*)){0,1}/) { $self->execute_cmd(cmd => $1, target => $2, param => $3); - my $current_pos = tell($handle); - seek($handle, $current_pos - bytes::length($line), 0); - syswrite($handle, '-'); - # line is useless - $line = <$handle>; + if ($self->{config}->{dirty_mode} != 1) { + my $current_pos = tell($handle); + seek($handle, $current_pos - bytes::length($line), 0); + syswrite($handle, '-'); + # line is useless + $line = <$handle>; + } } } From 185029ceed371d6b8ed1a2aa13d43643723b249d Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 25 Sep 2019 15:02:30 +0200 Subject: [PATCH 168/948] fix(clientzmq): add logger to key loader calls --- gorgone/gorgone/class/clientzmq.pm | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/gorgone/gorgone/class/clientzmq.pm b/gorgone/gorgone/class/clientzmq.pm index 3a68a9df10c..b9ac4336096 100644 --- a/gorgone/gorgone/class/clientzmq.pm +++ b/gorgone/gorgone/class/clientzmq.pm @@ -38,9 +38,18 @@ sub new { $connector->{cipher} = $options{cipher}; $connector->{vector} = $options{vector}; $connector->{symkey} = undef; - $connector->{server_pubkey} = gorgone::standard::library::loadpubkey(pubkey => $options{server_pubkey}); - $connector->{client_pubkey} = gorgone::standard::library::loadpubkey(pubkey => $options{client_pubkey}); - $connector->{client_privkey} = gorgone::standard::library::loadprivkey(privkey => $options{client_privkey}); + $connector->{server_pubkey} = gorgone::standard::library::loadpubkey( + pubkey => $options{server_pubkey}, + logger => $options{logger} + ); + $connector->{client_pubkey} = gorgone::standard::library::loadpubkey( + pubkey => $options{client_pubkey}, + logger => $options{logger} + ); + $connector->{client_privkey} = gorgone::standard::library::loadprivkey( + privkey => $options{client_privkey}, + logger => $options{logger} + ); $connector->{target_type} = $options{target_type}; $connector->{target_path} = $options{target_path}; $connector->{ping} = defined($options{ping}) ? $options{ping} : -1; From 059fb8f73f916314f7fb212397c9a9ef5aade15b Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 25 Sep 2019 15:37:11 +0200 Subject: [PATCH 169/948] enh(core): add default config value --- gorgone/config/gorgoned-central-ssh.yml | 4 ---- gorgone/config/gorgoned-central-zmq.yml | 4 ---- gorgone/config/gorgoned-poller.yml | 6 ------ gorgone/config/gorgoned-remote-ssh.yml | 4 ---- gorgone/config/gorgoned-remote-zmq.yml | 4 ---- gorgone/config/gorgoned.yml | 9 +++------ gorgone/gorgone/class/core.pm | 9 ++++++++- 7 files changed, 11 insertions(+), 29 deletions(-) diff --git a/gorgone/config/gorgoned-central-ssh.yml b/gorgone/config/gorgoned-central-ssh.yml index 656e45e5d2b..1dcaf879fba 100644 --- a/gorgone/config/gorgoned-central-ssh.yml +++ b/gorgone/config/gorgoned-central-ssh.yml @@ -10,8 +10,6 @@ database: username: centreon password: centreon gorgonecore: - internal_com_type: ipc - internal_com_path: /tmp/gorgone/routing.ipc external_com_type: tcp external_com_path: "*:5555" # in seconds before sending kill signals (not gently) @@ -27,8 +25,6 @@ gorgonecore: # JWK format export thumbprint SHA256 authorized_clients: - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 - # in seconds - sessions_time: 86400 modules: - name: httpserver package: gorgone::modules::core::httpserver::hooks diff --git a/gorgone/config/gorgoned-central-zmq.yml b/gorgone/config/gorgoned-central-zmq.yml index bf13104c069..cfd560ffa7e 100644 --- a/gorgone/config/gorgoned-central-zmq.yml +++ b/gorgone/config/gorgoned-central-zmq.yml @@ -10,8 +10,6 @@ database: username: centreon password: centreon gorgonecore: - internal_com_type: ipc - internal_com_path: /tmp/gorgone/routing.ipc external_com_type: tcp external_com_path: "*:5555" # in seconds before sending kill signals (not gently) @@ -28,8 +26,6 @@ gorgonecore: # JWK format export thumbprint SHA256 authorized_clients: - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 - # in seconds - sessions_time: 86400 modules: - name: httpserver package: gorgone::modules::core::httpserver::hooks diff --git a/gorgone/config/gorgoned-poller.yml b/gorgone/config/gorgoned-poller.yml index 79592079a1e..4cf7ec0589d 100644 --- a/gorgone/config/gorgoned-poller.yml +++ b/gorgone/config/gorgoned-poller.yml @@ -1,8 +1,6 @@ name: gorgoned-poller description: Configuration example in a full ZMQ environment for Poller server gorgonecore: - internal_com_type: ipc - internal_com_path: /tmp/gorgone/routing-poller.ipc external_com_type: tcp external_com_path: "*:5556" # in seconds before sending kill signals (not gently) @@ -19,10 +17,6 @@ gorgonecore: # JWK format export thumbprint SHA256 authorized_clients: - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 - # in seconds - sessions_time: 86400 - # in seconds - purge_sessions_time: 3600 modules: - name: action package: gorgone::modules::core::action::hooks diff --git a/gorgone/config/gorgoned-remote-ssh.yml b/gorgone/config/gorgoned-remote-ssh.yml index de21881cff5..dc2b58b3ae7 100644 --- a/gorgone/config/gorgoned-remote-ssh.yml +++ b/gorgone/config/gorgoned-remote-ssh.yml @@ -10,8 +10,6 @@ database: username: centreon password: centreon gorgonecore: - internal_com_type: ipc - internal_com_path: /tmp/gorgone/routing.ipc external_com_type: tcp external_com_path: "*:5556" # in seconds before sending kill signals (not gently) @@ -27,8 +25,6 @@ gorgonecore: # JWK format export thumbprint SHA256 authorized_clients: - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 - # in seconds - sessions_time: 86400 modules: - name: cron package: gorgone::modules::core::cron::hooks diff --git a/gorgone/config/gorgoned-remote-zmq.yml b/gorgone/config/gorgoned-remote-zmq.yml index a1f5b26cb5e..c2df2db1d38 100644 --- a/gorgone/config/gorgoned-remote-zmq.yml +++ b/gorgone/config/gorgoned-remote-zmq.yml @@ -10,8 +10,6 @@ database: username: centreon password: centreon gorgonecore: - internal_com_type: ipc - internal_com_path: /tmp/gorgone/routing.ipc external_com_type: tcp external_com_path: "*:5556" # in seconds before sending kill signals (not gently) @@ -28,8 +26,6 @@ gorgonecore: # JWK format export thumbprint SHA256 authorized_clients: - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 - # in seconds - sessions_time: 86400 modules: - name: cron package: gorgone::modules::core::cron::hooks diff --git a/gorgone/config/gorgoned.yml b/gorgone/config/gorgoned.yml index 2914439354e..c7b0123f10f 100644 --- a/gorgone/config/gorgoned.yml +++ b/gorgone/config/gorgoned.yml @@ -10,12 +10,12 @@ database: username: centreon password: centreon gorgonecore: - internal_com_type: ipc - internal_com_path: /tmp/gorgone/routing.ipc + #internal_com_type: ipc + #internal_com_path: /tmp/gorgone/routing.ipc external_com_type: tcp external_com_path: "*:5555" # in seconds before sending kill signals (not gently) - timeout: 50 + #timeout: 50 gorgone_db_type: SQLite gorgone_db_name: dbname=/tmp/gorgone.sdb gorgone_db_host: @@ -37,9 +37,6 @@ gorgonecore: # JWK format export thumbprint SHA256 authorized_clients: - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 - - # in seconds - sessions_time: 86400 modules: - name: httpserver package: gorgone::modules::core::httpserver::hooks diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 7c8ded4e456..1a301ab2221 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -86,7 +86,14 @@ sub init { if (defined($config->{gorgonecore}->{external_com_type}) && $config->{gorgonecore}->{external_com_type} ne '') { $self->{server_privkey} = gorgone::standard::library::loadprivkey(logger => $self->{logger}, privkey => $config->{gorgonecore}->{privkey}); } - + + $config->{gorgonecore}->{internal_com_type} = + defined($config->{gorgonecore}->{internal_com_type}) && $config->{gorgonecore}->{internal_com_type} ne '' ? $config->{gorgonecore}->{internal_com_type} : 'ipc'; + $config->{gorgonecore}->{internal_com_path} = + defined($config->{gorgonecore}->{internal_com_path}) && $config->{gorgonecore}->{internal_com_path} ne '' ? $config->{gorgonecore}->{internal_com_path} : '/tmp/gorgone/routing.ipc'; + $config->{gorgonecore}->{timeout} = + defined($config->{gorgonecore}->{timeout}) && $config->{gorgonecore}->{timeout} =~ /(\d+)/ ? $1 : 50; + # Database connections: # We add in gorgone database $gorgone->{db_gorgone} = gorgone::class::db->new( From 2a2466d53a5a9d54d8277884ddf6322c94913d81 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 25 Sep 2019 16:13:36 +0200 Subject: [PATCH 170/948] fix(core/proxy): no register subnodes for ssh pong --- gorgone/gorgone/modules/core/proxy/hooks.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index 8cec622e123..6f63299fb88 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -115,6 +115,7 @@ sub routing { if ($options{action} eq 'PONG') { return undef if (!defined($data->{data}->{id}) || $data->{data}->{id} eq ''); + return undef if ($register_nodes->{$data->{data}->{id}}->{type} eq 'push_ssh'); $synctime_nodes->{$data->{data}->{id}}->{in_progress_ping} = 0; $last_pong->{$data->{data}->{id}} = time(); $constatus_ping->{$data->{data}->{id}}->{last_ping_recv} = time(); From afb2699f0c21fa02a07af15f072214ed9dfb43e1 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 25 Sep 2019 16:20:42 +0200 Subject: [PATCH 171/948] fix(centreon/legacycmd): fix restart centcore cmd --- gorgone/gorgone/modules/centreon/legacycmd/class.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index 672e2c03da7..ff5317d4d15 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -271,7 +271,7 @@ sub execute_cmd { command => 'sudo ' . $cmd, metadata => { centcore_proxy => 1, - centcore_cmd => 'SYNCTRAP', + centcore_cmd => 'RESTART', } } }, From a014c96eb7f745932bc429c0e22d2fdeff31071b Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 25 Sep 2019 16:21:25 +0200 Subject: [PATCH 172/948] enh(sshclient): add service cmd handling --- gorgone/gorgone/modules/core/proxy/sshclient.pm | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/gorgone/gorgone/modules/core/proxy/sshclient.pm b/gorgone/gorgone/modules/core/proxy/sshclient.pm index f5b646706cc..af0439d425c 100644 --- a/gorgone/gorgone/modules/core/proxy/sshclient.pm +++ b/gorgone/gorgone/modules/core/proxy/sshclient.pm @@ -148,6 +148,17 @@ sub action_command { return (-1, { message => 'please set command' }); } + if (defined($options{data}->{content}->{metadata}->{centcore_proxy}) && $options{target_direct} == 0) { + return $self->action_centcore( + data => { + content => { + command => $options{data}->{content}->{metadata}->{centcore_cmd}, + target => $options{target}, + } + } + ); + } + my $timeout = defined($options{data}->{content}->{timeout}) && $options{data}->{content}->{timeout} =~ /(\d+)/ ? $1 : 60; my $timeout_nodata = defined($options{data}->{content}->{timeout_nodata}) && $options{data}->{content}->{timeout_nodata} =~ /(\d+)/ ? $1 : 30; From 0f3fe4c795b50689356092273336e5089cc974e5 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 25 Sep 2019 16:36:29 +0200 Subject: [PATCH 173/948] move test-client.pl --- gorgone/{ => contrib}/test-client.pl | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename gorgone/{ => contrib}/test-client.pl (100%) diff --git a/gorgone/test-client.pl b/gorgone/contrib/test-client.pl similarity index 100% rename from gorgone/test-client.pl rename to gorgone/contrib/test-client.pl From 68d5eaf4076b9c7c7993f3d3cd9fc200cd3e352f Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 25 Sep 2019 17:05:51 +0200 Subject: [PATCH 174/948] enh(centreon/legacycmd): add centcore illegal characters handling --- .../modules/centreon/legacycmd/class.pm | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index ff5317d4d15..d7916f89f2a 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -138,6 +138,23 @@ sub get_clapi_user { return 0; } +sub get_illegal_characters { + my ($self, %options) = @_; + + my ($status, $datas) = $self->{class_object_centreon}->custom_execute( + request => "SELECT `value` FROM options WHERE `key` = 'centcore_illegal_characters'", + mode => 2 + ); + if ($status == -1 || !defined($datas->[0][0])) { + $self->{logger}->writeLogError('[legacycmd] -class- cannot get centcore illegal characters'); + return -1; + } + + $self->{centcore_illegal_characters} = $datas->[0][0]; + + return 0; +} + sub execute_cmd { my ($self, %options) = @_; @@ -150,7 +167,8 @@ sub execute_cmd { $self->{logger}->writeLogInfo($msg); if ($options{cmd} eq 'EXTERNALCMD') { - # TODO: need to remove illegal characters!! + $options{param} =~ s/[\Q$self->{centcore_illegal_characters}\E]//g + if (defined($self->{centcore_illegal_characters}) && $self->{centcore_illegal_characters} ne ''); $self->send_internal_action( action => 'ENGINECOMMAND', target => $options{target}, @@ -507,7 +525,8 @@ sub handle_centcore_dir { sub handle_cmd_files { my ($self, %options) = @_; - return if ($self->get_pollers_config() == -1 || $self->get_clapi_user() == -1); + return if ($self->get_pollers_config() == -1 || $self->get_clapi_user() == -1 || + $self->get_illegal_characters() == -1); $self->handle_centcore_cmd(); $self->handle_centcore_dir(); } From 9a16eb81d62b02c1701b1efa529439728f9be5bb Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Thu, 26 Sep 2019 10:08:41 +0200 Subject: [PATCH 175/948] feat(core): lean (david) deployment --- gorgone/config/gorgoned.yml | 12 ++-- gorgone/docs/guide.md | 4 +- gorgone/gorgone/class/core.pm | 17 +++--- gorgone/gorgone/standard/library.pm | 88 +++++++++++++++++++++++++++++ gorgone/schema/gorgone_database.sql | 4 +- 5 files changed, 109 insertions(+), 16 deletions(-) diff --git a/gorgone/config/gorgoned.yml b/gorgone/config/gorgoned.yml index c7b0123f10f..a8249ccd96f 100644 --- a/gorgone/config/gorgoned.yml +++ b/gorgone/config/gorgoned.yml @@ -16,12 +16,12 @@ gorgonecore: external_com_path: "*:5555" # in seconds before sending kill signals (not gently) #timeout: 50 - gorgone_db_type: SQLite - gorgone_db_name: dbname=/tmp/gorgone.sdb - gorgone_db_host: - gorgone_db_port: - gorgone_db_user: - gorgone_db_password: + #gorgone_db_type: SQLite + #gorgone_db_name: dbname=/var/lib/centreon/gorgone/gorgone.sdb + #gorgone_db_host: + #gorgone_db_port: + #gorgone_db_user: + #gorgone_db_password: # If not set. Use 'hostname' function. hostname: # If not set. diff --git a/gorgone/docs/guide.md b/gorgone/docs/guide.md index d10a9fc89d6..5e891d7c852 100644 --- a/gorgone/docs/guide.md +++ b/gorgone/docs/guide.md @@ -335,7 +335,7 @@ CREATE TABLE IF NOT EXISTS `gorgone_identity` ( `ctime` int(11) DEFAULT NULL, `identity` varchar(2048) DEFAULT NULL, `key` varchar(4096) DEFAULT NULL, - `parent` INTEGER DEFAULT '0' + `parent` int(11) DEFAULT '0' ); CREATE INDEX IF NOT EXISTS idx_gorgone_identity ON gorgone_identity (identity); @@ -347,6 +347,7 @@ CREATE TABLE IF NOT EXISTS `gorgone_history` ( `code` int(11) DEFAULT NULL, `etime` int(11) DEFAULT NULL, `ctime` int(11) DEFAULT NULL, + `instant` int(11) DEFAULT '0', `data` TEXT DEFAULT NULL ); @@ -355,6 +356,7 @@ CREATE INDEX IF NOT EXISTS idx_gorgone_history_token ON gorgone_history (token); CREATE INDEX IF NOT EXISTS idx_gorgone_history_etime ON gorgone_history (etime); CREATE INDEX IF NOT EXISTS idx_gorgone_history_code ON gorgone_history (code); CREATE INDEX IF NOT EXISTS idx_gorgone_history_ctime ON gorgone_history (ctime); +CREATE INDEX IF NOT EXISTS idx_gorgone_history_instant ON gorgone_history (instant); CREATE TABLE IF NOT EXISTS `gorgone_synchistory` ( `id` int(11) DEFAULT NULL, diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 1a301ab2221..056ca71e451 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -94,23 +94,24 @@ sub init { $config->{gorgonecore}->{timeout} = defined($config->{gorgonecore}->{timeout}) && $config->{gorgonecore}->{timeout} =~ /(\d+)/ ? $1 : 50; - # Database connections: - # We add in gorgone database - $gorgone->{db_gorgone} = gorgone::class::db->new( + $config->{gorgonecore}->{gorgone_db_type} = + defined($config->{gorgonecore}->{gorgone_db_type}) && $config->{gorgonecore}->{gorgone_db_type} ne '' ? $config->{gorgonecore}->{gorgone_db_type} : 'SQLite'; + $config->{gorgonecore}->{gorgone_db_name} = + defined($config->{gorgonecore}->{gorgone_db_name}) && $config->{gorgonecore}->{gorgone_db_name} ne '' ? $config->{gorgonecore}->{gorgone_db_name} : 'dbname=/var/lib/centreon/gorgone/gorgone.sdb'; + $config->{gorgonecore}->{gorgone_db_autocreate_schema} = + defined($config->{gorgonecore}->{gorgone_db_autocreate_schema}) && $config->{gorgonecore}->{gorgone_db_autocreate_schema} =~ /(\d+)/ ? $1 : 1; + gorgone::standard::library::init_database( + gorgone => $gorgone, type => $config->{gorgonecore}->{gorgone_db_type}, db => $config->{gorgonecore}->{gorgone_db_name}, host => $config->{gorgonecore}->{gorgone_db_host}, port => $config->{gorgonecore}->{gorgone_db_port}, user => $config->{gorgonecore}->{gorgone_db_user}, password => $config->{gorgonecore}->{gorgone_db_password}, + autocreate_schema => $config->{gorgonecore}->{gorgone_db_autocreate_schema}, force => 2, logger => $gorgone->{logger} ); - $gorgone->{db_gorgone}->set_inactive_destroy(); - if ($gorgone->{db_gorgone}->connect() == -1) { - $gorgone->{logger}->writeLogInfo("[core] Cannot connect. We quit!!"); - exit(1); - } $self->{hostname} = $config->{gorgonecore}->{hostname}; if (!defined($self->{hostname}) || $self->{hostname} eq '') { diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index c5f05e67a91..be0937e0ae9 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -31,6 +31,8 @@ use Crypt::PRNG; use Crypt::CBC; use Data::Dumper; use YAML 'LoadFile'; +use File::Path; +use File::Basename; my %zmq_type = ('ZMQ_ROUTER' => ZMQ_ROUTER, 'ZMQ_DEALER' => ZMQ_DEALER); @@ -685,5 +687,91 @@ sub add_zmq_pollin { callback => $options{callback}, }; } + +sub init_database { + my (%options) = @_; + + if ($options{type} =~ /sqlite/i && $options{db} =~ /dbname=(.*)/i) { + my $sdb_path = File::Basename::dirname($1); + File::Path::make_path($sdb_path); + } + $options{gorgone}->{db_gorgone} = gorgone::class::db->new( + type => $options{type}, + db => $options{db}, + host => $options{host}, + port => $options{port}, + user => $options{user}, + password => $options{password}, + force => 2, + logger => $options{logger} + ); + $options{gorgone}->{db_gorgone}->set_inactive_destroy(); + if ($options{gorgone}->{db_gorgone}->connect() == -1) { + $options{logger}->writeLogError("[core] Cannot connect. We quit!!"); + exit(1); + } + + if (defined($options{autocreate_schema}) && $options{autocreate_schema} == 1) { + my $requests = [ + q{ + CREATE TABLE IF NOT EXISTS `gorgone_identity` ( + `id` INTEGER PRIMARY KEY, + `ctime` int(11) DEFAULT NULL, + `identity` varchar(2048) DEFAULT NULL, + `key` varchar(4096) DEFAULT NULL, + `parent` int(11) DEFAULT '0' + ); + }, + q{ + CREATE INDEX IF NOT EXISTS idx_gorgone_identity ON gorgone_identity (identity); + }, + q{ + CREATE INDEX IF NOT EXISTS idx_gorgone_parent ON gorgone_identity (parent); + }, + q{ + CREATE TABLE IF NOT EXISTS `gorgone_history` ( + `id` INTEGER PRIMARY KEY, + `token` varchar(2048) DEFAULT NULL, + `code` int(11) DEFAULT NULL, + `etime` int(11) DEFAULT NULL, + `ctime` int(11) DEFAULT NULL, + `instant` int(11) DEFAULT '0', + `data` TEXT DEFAULT NULL + ); + }, + q{ + CREATE INDEX IF NOT EXISTS idx_gorgone_history_id ON gorgone_history (id); + }, + q{ + CREATE INDEX IF NOT EXISTS idx_gorgone_history_token ON gorgone_history (token); + }, + q{ + CREATE INDEX IF NOT EXISTS idx_gorgone_history_etime ON gorgone_history (etime); + }, + q{ + CREATE INDEX IF NOT EXISTS idx_gorgone_history_code ON gorgone_history (code); + }, + q{ + CREATE INDEX IF NOT EXISTS idx_gorgone_history_ctime ON gorgone_history (ctime); + }, + q{ + CREATE INDEX IF NOT EXISTS idx_gorgone_history_instant ON gorgone_history (instant); + }, + q{ + CREATE TABLE IF NOT EXISTS `gorgone_synchistory` ( + `id` int(11) DEFAULT NULL, + `ctime` int(11) DEFAULT NULL, + `last_id` int(11) DEFAULT NULL + ); + }, + q{ + CREATE INDEX IF NOT EXISTS idx_gorgone_synchistory_id ON gorgone_synchistory (id); + }, + ]; + foreach (@$requests) { + $options{gorgone}->{db_gorgone}->query($_); + } + } +} 1; diff --git a/gorgone/schema/gorgone_database.sql b/gorgone/schema/gorgone_database.sql index 99c9ffed0f3..1f586131438 100644 --- a/gorgone/schema/gorgone_database.sql +++ b/gorgone/schema/gorgone_database.sql @@ -3,7 +3,7 @@ CREATE TABLE IF NOT EXISTS `gorgone_identity` ( `ctime` int(11) DEFAULT NULL, `identity` varchar(2048) DEFAULT NULL, `key` varchar(4096) DEFAULT NULL, - `parent` INTEGER DEFAULT '0' + `parent` int(11) DEFAULT '0' ); CREATE INDEX IF NOT EXISTS idx_gorgone_identity ON gorgone_identity (identity); @@ -15,6 +15,7 @@ CREATE TABLE IF NOT EXISTS `gorgone_history` ( `code` int(11) DEFAULT NULL, `etime` int(11) DEFAULT NULL, `ctime` int(11) DEFAULT NULL, + `instant` int(11) DEFAULT '0', `data` TEXT DEFAULT NULL ); @@ -23,6 +24,7 @@ CREATE INDEX IF NOT EXISTS idx_gorgone_history_token ON gorgone_history (token); CREATE INDEX IF NOT EXISTS idx_gorgone_history_etime ON gorgone_history (etime); CREATE INDEX IF NOT EXISTS idx_gorgone_history_code ON gorgone_history (code); CREATE INDEX IF NOT EXISTS idx_gorgone_history_ctime ON gorgone_history (ctime); +CREATE INDEX IF NOT EXISTS idx_gorgone_history_instant ON gorgone_history (instant); CREATE TABLE IF NOT EXISTS `gorgone_synchistory` ( `id` int(11) DEFAULT NULL, From 4ce544a538a4c10ccfd8245dfedf3af716d79629 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Thu, 26 Sep 2019 10:30:23 +0200 Subject: [PATCH 176/948] enh(core): avoid memory leak --- gorgone/gorgone/class/core.pm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 056ca71e451..3379b70419e 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -646,6 +646,9 @@ sub run { modules_events => $gorgone->{modules_events}, ); } + + # We can clean return_child. + $gorgone->{return_child} = {}; if ($gorgone->{stop} == 1) { # No childs From 842a7a4a02493a9430b8bc06c66a7989fb2e3199 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Thu, 26 Sep 2019 11:02:08 +0200 Subject: [PATCH 177/948] enh(docs): real guys read the code --- gorgone/docs/guide.md | 391 ++++++++++++++++++++---------------------- 1 file changed, 190 insertions(+), 201 deletions(-) diff --git a/gorgone/docs/guide.md b/gorgone/docs/guide.md index 5e891d7c852..961dc5de5f7 100644 --- a/gorgone/docs/guide.md +++ b/gorgone/docs/guide.md @@ -1,148 +1,198 @@ -# Description +# Centreon Gorgone -"gorgoned" is a daemon which handles some tasks. You can plug-in some modules: +## Description -* gorgone-action: execute commands, send files/directories -* gorgone-cron: schedule tasks -* gorgone-proxy: push tasks (to another "gorgoned" instance) or execute (through SSH) +"gorgoned" is a daemon which handles tasks. You can plug-in modules: -The daemon is installed on centreon central server and also poller server. -It uses zeromq library. +* Action: execute commands, send files/directories, +* Cron: cron-like tasks scheduler, +* Proxy: push tasks (to another "gorgoned" instance) or execute (through SSH). +* ... -# Installation +The daemon can be installed on Centreon Central, Remote and Poller servers. -Daemon uses following Perl modules: +It uses ZeroMQ library. -* ZMQ::LibZMQ4: repository 'centreon-stable' -* JSON::XS: repository 'centos base' -* YAML: repository 'centos base' -* DBD::SQLite: repository 'centos base' -* DBD::mysql: repository 'centos base' -* UUID: repository 'centreon-stable' -* Crypt::CBC: repository 'centos base' -* Schedule::Cron: in EPEL -* Crypt::Cipher::AES: in attachment (module CryptX) -* Crypt::PK::RSA: in attachment (module CryptX) -* Crypt::PRNG: in attachment (module CryptX) -* HTTP::Daemon: repository 'centos base' -* HTTP::Daemon::SSL: in EPEL -* HTTP::Status: repository 'centos base' -* MIME::Base64: repository 'centos base' +## Installation -Execute following commands: +The daemon uses the following Perl modules: -``` -# yum install 'perl(Schedule::Cron)' 'perl(Crypt::CBC)' 'perl(ZMQ::LibZMQ4)' 'perl(JSON::XS)' 'perl(YAML)' 'perl(DBD::SQLite)' 'perl(DBD::mysql)' 'perl(UUID)' 'perl(HTTP::Daemon)' 'perl(HTTP::Daemon::SSL)' 'perl(HTTP::Status)' 'perl(MIME::Base64)' -# yum install perl-CryptX-0.064-1.el7.x86_64 -``` +* Repository 'centreon-stable': + * ZMQ::LibZMQ4 + * UUID +* Repository 'centos base': + * JSON::XS + * YAML + * DBD::SQLite + * DBD::mysql + * Crypt::CBC + * HTTP::Daemon + * HTTP::Status + * MIME::Base64 +* Repository 'epel': + * HTTP::Daemon::SSL + * Schedule::Cron +* From offline packages: + * Crypt::Cipher::AES (module CryptX) + * Crypt::PK::RSA (module CryptX) + * Crypt::PRNG (module CryptX) -Create sqlite database: +Execute the following commands to install them all: +```bash +yum install 'perl(Schedule::Cron)' 'perl(Crypt::CBC)' 'perl(ZMQ::LibZMQ4)' 'perl(JSON::XS)' 'perl(YAML)' 'perl(DBD::SQLite)' 'perl(DBD::mysql)' 'perl(UUID)' 'perl(HTTP::Daemon)' 'perl(HTTP::Daemon::SSL)' 'perl(HTTP::Status)' 'perl(MIME::Base64)' +yum install perl-CryptX-0.064-1.el7.x86_64 ``` -# sqlite3 -init schema/gorgone_database.sql /tmp/gorgone.sdb + +Create sqlite database with the database schema: + +```bash +sqlite3 -init schema/gorgone_database.sql /tmp/gorgone.sdb ``` -To execute the daemon: +Database schema: + +```sql +CREATE TABLE IF NOT EXISTS `gorgone_identity` ( + `id` INTEGER PRIMARY KEY, + `ctime` int(11) DEFAULT NULL, + `identity` varchar(2048) DEFAULT NULL, + `key` varchar(4096) DEFAULT NULL, + `parent` int(11) DEFAULT '0' +); + +CREATE INDEX IF NOT EXISTS idx_gorgone_identity ON gorgone_identity (identity); +CREATE INDEX IF NOT EXISTS idx_gorgone_parent ON gorgone_identity (parent); + +CREATE TABLE IF NOT EXISTS `gorgone_history` ( + `id` INTEGER PRIMARY KEY, + `token` varchar(2048) DEFAULT NULL, + `code` int(11) DEFAULT NULL, + `etime` int(11) DEFAULT NULL, + `ctime` int(11) DEFAULT NULL, + `instant` int(11) DEFAULT '0', + `data` TEXT DEFAULT NULL +); + +CREATE INDEX IF NOT EXISTS idx_gorgone_history_id ON gorgone_history (id); +CREATE INDEX IF NOT EXISTS idx_gorgone_history_token ON gorgone_history (token); +CREATE INDEX IF NOT EXISTS idx_gorgone_history_etime ON gorgone_history (etime); +CREATE INDEX IF NOT EXISTS idx_gorgone_history_code ON gorgone_history (code); +CREATE INDEX IF NOT EXISTS idx_gorgone_history_ctime ON gorgone_history (ctime); +CREATE INDEX IF NOT EXISTS idx_gorgone_history_instant ON gorgone_history (instant); + +CREATE TABLE IF NOT EXISTS `gorgone_synchistory` ( + `id` int(11) DEFAULT NULL, + `ctime` int(11) DEFAULT NULL, + `last_id` int(11) DEFAULT NULL +); +CREATE INDEX IF NOT EXISTS idx_gorgone_synchistory_id ON gorgone_synchistory (id); ``` -# perl gorgoned --config=config/gorgoned.yml --severity=debug + +Launch the daemon: + +```bash +perl gorgoned --config=config/gorgoned.yml --severity=debug ``` -# gorgone protocol +## Gorgone protocol "gorgone-core" (main mandatory module) can have 2 interfaces: -* internal: uncrypted dialog (used by internal modules. Commonly in ipc) -* external: crypted dialog (used by third-party clients. Commonly in tcp) +* Internal: uncrypted dialog (used by internal modules. Commonly in ipc) +* External: crypted dialog (used by third-party clients. Commonly in tcp) -## Handshake scenario +### Handshake scenario -Third-party clients connected had to use the zeromq library and the following process: +Third-party clients have to use the ZeroMQ library and the following process: -* client : need to create an uniq identity (will be used in "zmq_setsockopt" and "ZMQ_IDENTITY") -* client -> server : send the following message with HELO crypted with the public key of the server and provides client pubkey: +1. Client : need to create an uniq identity (will be used in "zmq_setsockopt" and "ZMQ_IDENTITY") +2. Client -> Server : send the following message with HELO crypted with the public key of the server and provides client pubkey: -``` -[HOSTNAME] [CLIENTPUBKEY] [HELO] -``` + ```text + [HOSTNAME] [CLIENTPUBKEY] [HELO] + ``` -* server: uncrypt the client message: +3. Server: uncrypt the client message: - * If uncrypted message result is not "HELO", server refused the connection and send it back: + * If uncrypted message result is not "HELO", server refuses the connection and send it back: -``` -[ACK] [] { "code": 1, "data": { "message": "handshake issue" } } -``` + ```json + [ACK] [] { "code": 1, "data": { "message": "handshake issue" } } + ``` - * If uncrypted message result is "HELO", server accepts the connection if the clientpubkey is authorized. It creates symmetric key and send the following message crypted with client pubkey: + * If uncrypted message result is "HELO", server accepts the connection if the clientpubkey is authorized. It creates a symmetric key and send the following message crypted with client pubkey: -``` -[KEY] [HOSTNAME] [symmetric key] -``` + ```text + [KEY] [HOSTNAME] [symmetric key] + ``` + +4. Client: uncrypts the Server message with its private key. +5. Client and Server uses the symmetric key to dialog. -* client: uncrypt the server message with its private key. -* client and server uses the symmetric key to dialog +The server keeps sessions for 24 hours since the last message of the client. + +Otherwise, it purges the identity/symmetric-key of the client. -The server keeps sessions for 24 hours since the last message of the client. Otherwise, it purges the identity/symmetric-key of the client. If a third-party client with the same identity try to open a new session, the server deletes the old identity/symmetric-key. - +Be sure to have the same parameters to crypt/uncrypt with the symmetric key. Commonly: 'AES' cipher, keysize of 32 bytes, vector '0123456789012345'. -## Client request +### Client request -After a successful handshake, client requests uses the following syntax: +After a successful handshake, client requests use the following syntax: -``` +```text [ACTION] [TOKEN] [TARGET] DATA ``` -* ACTION: the request. For example: COMMAND, ENGINECOMMAND,... It depends of the target server capabilites -* TOKEN: Can be used to create some "sessions". If empty, the server creates an uniq token for each requests -* TARGET: which "gorgoned" must execute the request. With the following option, you can execute a command to a specific server through another. The poller id is needed. If empty, the server (which is connected with the client) is the target. -* DATA: json stream. It depends of the request +* ACTION: the request, for example 'COMMAND' or 'ENGINECOMMAND'. It depends of the target server capabilites, +* TOKEN: can be used to create some "sessions". If empty, the server creates an uniq token for each requests, +* TARGET: which "gorgoned" must execute the request. With the following option, you can execute a command on a specific server through another. The poller ID is needed. If empty, the server (which is connected with the client) is the target. +* DATA: JSON stream. It depends on the request. For each client requests, the server get an immediate response: -``` + +```json [ACK] [TOKEN] { "code": "x", "data": { "message": "xxxxx" } } ``` -* TOKEN: a uniq ID to follow the request -* DATA: a json stream +* TOKEN: a uniq ID to follow the request, +* DATA: a JSON stream * 0 : OK * 1 : NOK There are some exceptions for 'CONSTATUS' and 'GETLOG' requests. -## Core requests +### Core requests -### CONSTATUS +#### CONSTATUS The following request gives you a table with the last ping response of "gorgoned" nodes connected to the server. The command is useful to know if some pollers are disconnected. The client request: -``` +```text [CONSTATUS] [] [] ``` The server response: -``` +```text [ACK] [token_id] DATA ``` -An example of the json stream: +An example of the JSON stream: -``` -{ - "code": 1, - "data": { - "action": "constatus", - "mesage": "ok", +```json +{ + "code": 1, + "data": { + "action": "constatus", + "mesage": "ok", "data": { "last_ping_sent": "xxxx", "last_ping_recv": "xxxx", @@ -152,21 +202,25 @@ An example of the json stream: ... } } - } + } } ``` -'last_ping' and 'entries' values are unix timestamp in seconds. The 'last_ping' is the date when the daemon had launched a ping broadcast to the poller connected. -'entries' values are the last time the poller had responded to the ping broadcast. +'last_ping' and 'entries' values are unix timestamp in seconds. + +The 'last_ping' value is the date when the daemon have launched a PING broadcast to the poller connected. + +The 'entries' values are the last time the poller have responded to the PING broadcast. + +#### GETLOG -### GETLOG +The following request gives you the capability to follow your requests. "gorgone" protocol is asynchronous. -The following request gives you the capability to follow your requests. "gorgone" protocol is asynchronous. -An example: when you request a command execution, the server gives you a direct response and a token. These token can be used to know what happened to your command. +An example: when you request a command execution, the server gives you a direct response and a token. This token can be used to know what happened to your command. The client request: -``` +```json [GETLOG] [TOKEN] [TARGET] { "code": "xx", "ctime": "xx", "etime": "xx", "token": "xx", "id": "xx" } ``` @@ -178,21 +232,23 @@ At least one of the 5 values must be defined: * etime: get logs if event time in seconds >= value * id: get logs if id > value -The 'etime' is when the event had occured. The 'ctime' is when the server had stored the log in its database. +The 'etime' value gives the time when the event has occured. + +The 'ctime' value gives the time when the server has stored the log in its database. The server response: -``` +```text [ACK] [token_id] DATA ``` An example of the json stream: -``` -{ - "code": 1, - "data": { - "action": "getlog", +```json +{ + "code": 1, + "data": { + "action": "getlog", "message": "ok", "result": { "10": { @@ -213,39 +269,42 @@ An example of the json stream: }, ... } - } + } } ``` -Each 'gorgoned' nodes store its logs. But every 5 minutes (by default), the central server gets the new logs of its connected nodes and stores it. +Each 'gorgoned' nodes store its logs. But every minute (by default), the Central server gets the new logs of its connected nodes and stores it. + A client can force a synchronization with the following request: -``` +```text [GETLOG] [] [target_id] ``` -The client have to set the poller id. +The client have to set the target ID (it can be the Poller ID). -### PUTLOG +#### PUTLOG The request shouldn't be used by third-party program. It's commonly used by the internal modules. + The client request: -``` - [PUTLOG] [TOKEN] [TARGET] { "code": xxx, "etime": "xxx", "token": "xxxx", "data": { some_datas } } +```json +[PUTLOG] [TOKEN] [TARGET] { "code": xxx, "etime": "xxx", "token": "xxxx", "data": { some_datas } } ``` -### REGISTERNODES +#### REGISTERNODES The request shouldn't be used by third-party program. It's commonly used by the internal modules. + The client request (no carriage returns. only for reading): -``` -[REGISTERNODES] [TOKEN] [TARGET] { "nodes": [ - { "id": 20, "type": "pull" }, +```json +[REGISTERNODES] [TOKEN] [TARGET] { "nodes": [ + { "id": 20, "type": "pull" }, { "id": 100, "type": "push_ssh", "address": "10.0.0.1", "ssh_port": 22 }, - { - "id": 150, "type": "push_zmq", "address": "10.3.2.1", + { + "id": 150, "type": "push_zmq", "address": "10.3.2.1", "server_pubkey": "server_pubkey.pem", "client_pubkey": "client_pubkey.pem", "client_privkey": "client_privkey.pem", "cipher": "Cipher::AES", "keysize": 32, "vector": "0123456789012345", "nodes": [400, 455] } @@ -253,116 +312,46 @@ The client request (no carriage returns. only for reading): } ``` -## Common codes +### Common codes Common code responses for all module requests: + * 0: action proceed * 1: action finished OK * 2: action finished KO Modules can have extra codes. -## module requests - -### action - -#### COMMAND - -With the following request, you can execute shell commands. -A client example: - -``` -[COMMAND] [] [target_id] { "command": "ls /" } -``` - -With the code 1, you can get following attributes: - -``` -{ "code": 1, "stdout": "xxxxx", "exit_code": "xxx" } -``` - -#### ENGINECOMMAND - -With the following request, you can submit external commands to the scheduler like "centreon-engine". -A client example: - -``` -[ENGINECOMMAND] [] [target_id] { "command": "[1417705150] ENABLE_HOST_CHECK;host1", "engine_pipe": "/var/lib/centreon-engine/rw/centengine.cmd" } -``` +## FAQ -You only have the message to get informations (it tells you if there are some permission problems or file missing). +### Which modules should I enable ? -# FAQ +A Central with gorgoned should have the following modules: -## Which modules should i enable ? +* action, +* proxy, +* cron, +* httpserver. -A poller with gorgoned should have the following modules: +A Poller with gorgoned should have the following modules: -* action -* pull: if the connection to the central should be opened by the poller +* action, +* pull (if the connection to the Central should be opened by the Poller). -A central with gorgoned should have the following modules: +## I want to create a client. How should I proceed ? -* action -* proxy -* cron -* httpserver +First, you must choose a language which can use ZeroMQ library and have some knowledge about ZeroMQ. -## I want to create a client. How should i proceed +I recommend the following scenario: -First, you must choose a language which can used zeromq library and have some knowledge about zeromq. -I recommend following scenarios: - -* Create a ZMQ_DEALER -* Manage the handshake with the server. See :ref:`handshake-scenario` +* Create a ZMQ_DEALER, +* Manage the handshake with the server (see :ref:`handshake-scenario`), * Do a request: - - * if you don't need to get the result: close the connection - * if you need to get the result: - - 1. get the token - 2. if you have used a target, force a synchronization with 'GETLOG' - 3. do a 'GETLOG' request with the token to get the result - 4. repeat actions 2 and 3 if you don't have a result (you should stop after X retries) - -You can see the code from 'test-client.pl'. - -# Database scheme - -``` -CREATE TABLE IF NOT EXISTS `gorgone_identity` ( - `id` INTEGER PRIMARY KEY, - `ctime` int(11) DEFAULT NULL, - `identity` varchar(2048) DEFAULT NULL, - `key` varchar(4096) DEFAULT NULL, - `parent` int(11) DEFAULT '0' -); - -CREATE INDEX IF NOT EXISTS idx_gorgone_identity ON gorgone_identity (identity); -CREATE INDEX IF NOT EXISTS idx_gorgone_parent ON gorgone_identity (parent); - -CREATE TABLE IF NOT EXISTS `gorgone_history` ( - `id` INTEGER PRIMARY KEY, - `token` varchar(2048) DEFAULT NULL, - `code` int(11) DEFAULT NULL, - `etime` int(11) DEFAULT NULL, - `ctime` int(11) DEFAULT NULL, - `instant` int(11) DEFAULT '0', - `data` TEXT DEFAULT NULL -); - -CREATE INDEX IF NOT EXISTS idx_gorgone_history_id ON gorgone_history (id); -CREATE INDEX IF NOT EXISTS idx_gorgone_history_token ON gorgone_history (token); -CREATE INDEX IF NOT EXISTS idx_gorgone_history_etime ON gorgone_history (etime); -CREATE INDEX IF NOT EXISTS idx_gorgone_history_code ON gorgone_history (code); -CREATE INDEX IF NOT EXISTS idx_gorgone_history_ctime ON gorgone_history (ctime); -CREATE INDEX IF NOT EXISTS idx_gorgone_history_instant ON gorgone_history (instant); - -CREATE TABLE IF NOT EXISTS `gorgone_synchistory` ( - `id` int(11) DEFAULT NULL, - `ctime` int(11) DEFAULT NULL, - `last_id` int(11) DEFAULT NULL -); - -CREATE INDEX IF NOT EXISTS idx_gorgone_synchistory_id ON gorgone_synchistory (id); -``` + * If you don't need to get the result: close the connection, + * If you need to get the result: + 1. Get the token, + 2. If you have used a target, force a synchronization with 'GETLOG' (without token), + 3. Do a 'GETLOG' request with the token to get the result, + 4. Repeat actions 2 and 3 if you don't have a result yet (you should stop after X retries). + +You can inspire from the code of 'test-client.pl'. From 70cc8208197912e4665ec7ac1e1ffadd20daf77c Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Thu, 26 Sep 2019 11:40:42 +0200 Subject: [PATCH 178/948] enh(docs): refacto newtest doc --- gorgone/docs/modules/newtest/exploitation.md | 79 ----------- gorgone/docs/modules/newtest/installation.md | 35 ----- gorgone/docs/modules/plugins/newtest.md | 138 +++++++++++++++++++ 3 files changed, 138 insertions(+), 114 deletions(-) delete mode 100644 gorgone/docs/modules/newtest/exploitation.md delete mode 100644 gorgone/docs/modules/newtest/installation.md create mode 100644 gorgone/docs/modules/plugins/newtest.md diff --git a/gorgone/docs/modules/newtest/exploitation.md b/gorgone/docs/modules/newtest/exploitation.md deleted file mode 100644 index d24f17a8848..00000000000 --- a/gorgone/docs/modules/newtest/exploitation.md +++ /dev/null @@ -1,79 +0,0 @@ -# Exploitation - -## Generals Principles - -`newtest` is a module program in charged to get back Newtest services. This program uses the newtest webservice in order to connect and get back the informations of one (or more) Newtest Management Console (NMC). - -By default `newtest` starts X processes (it depends of the configuration) : - -Steps of operation for one process: - -- get Centreon datas: robots and scenarios already configured. -- gets from the NMC the list of robots and scenarios: it creates it in centreon with centreon-clapi (It don't disable it or delete it in Centreon). -- gets from the NMC the last status of scenarios: it submits through "centcore" the result. - -newtest necessitates the utilization of one (or more) NMC. - -## Configuration - -The `newtest` module is configured with gorgone configuration file: - -``` - modules: - - name: newtest - module: modules::newtest::hooks - enable: 1 - # in seconds - do purge for container also - check_containers_time: 3600 - clapi_command: /usr/bin/centreon - clapi_username: admin - clapi_password: centreon - clapi_action_applycfg: RELOAD - centcore_cmd: /var/lib/centreon/centcore.cmd - containers: - - name: toto - resync_time: 300 - nmc_endpoint: "http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx" - password: pass - host_template: generic-active-host-custom - host_prefix: Robot-%s - service_template: generic-passive-service-custom - service_prefix: Scenario-%s - poller_name: Central - list_scenario_status: '{ "search": "All", "instances": [] }' - - name: tutu - resync_time: 600 - nmc_endpoint: "http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx" - password: pass - host_template: generic-active-host-custom - host_prefix: Robot-%s - service_template: generic-passive-service-custom - service_prefix: Scenario-%s - poller_name: Central - list_scenario_status: '{ "search": "Robot", "instances": ["XXXX"] }' -``` - -You have to add at least one entry in the configuration. The meaning of attributes: - -- 'nmc_endpoint': address of the NMC -- 'nmc_timeout': timeout NMC console response -- 'host_template': template used when the daemon creates a host in Centreon -- 'host_prefix': name used when the daemon creates and looks for a host in Centreon -- 'service_templte': template used when the daemon creates a service in Centreon -- 'service_prefix': name used when the daemon creates and looks for a service in Centreon -- 'poller_name': poller used when the daemon creates a host in Centreon -- 'clapi_username' and 'clapi_password': centreon-clapi connections -- 'clapi_action_applycfg': could be 'POLLERRELOAD' or 'POLLERRESTART' -- 'list_scenario_status': informations to gets from NMC : - - To get all robots: { "search": "All", "instances": [] } - - To filter on some robots: { "search": "Robot", "instances": ["XXXX", "YYYY"] } - -## Troubleshooting - -It is possible to get this kind of errors in logs of `newtest`: - -``` -die: syntax error at line 1, column 0, byte 0 at /usr/lib/perl5/vendor_perl/5.8.8/i386-linux-thread-multi/XML/Parser.pm line 189 -``` - -It often means that there is a timeout error. diff --git a/gorgone/docs/modules/newtest/installation.md b/gorgone/docs/modules/newtest/installation.md deleted file mode 100644 index d7bf09f2cf4..00000000000 --- a/gorgone/docs/modules/newtest/installation.md +++ /dev/null @@ -1,35 +0,0 @@ -# Installation - -## Prerequisites - -### Software Recommandations - -The module "gorgonenewtest" has been tested on red-hat 7 with rpms. -Installation on other system is possible but is outside the scope of this document (Debian,...). - -| Software | Version | -| :----------------- | :----------: | -| Perl SOAP::Lite | 0.71 | -| Perl Date::Parse | 1.16 | -| centreon | 19.04 | -| gorgone | 1.0 | - -### Module location - -The module "newtest" daemon must be installed on Centreon Central server. Minimal used ressources are : - -* RAM : 128 MB. -* CPU : it depends the number of newtest scenarios. - -## Module installation - centos/rhel 7 systems - -### Requirements - -| Dependency | Version | Repository | -| :----------------- | :----------: | :----------------- | -| perl-SOAP-Lite | 1.10 | centreon base | -| perl-TimeDate | 2.30 | redhat/centos base | - -### Newtest installation - -"newtest" is an official gorgone module. No installation needed. diff --git a/gorgone/docs/modules/plugins/newtest.md b/gorgone/docs/modules/plugins/newtest.md new file mode 100644 index 00000000000..70a3b535ef1 --- /dev/null +++ b/gorgone/docs/modules/plugins/newtest.md @@ -0,0 +1,138 @@ +# IP-Label Newtest + +## Installation + +### Prerequisites + +#### Software recommandations + +The module "newtest" has been tested on RedHat 7 with RPMs. + +Installation on other system is possible but is outside the scope of this document. + +#### Module location + +The module "newtest" must be installed on Centreon Central server. + +Minimal used ressources are: + +* RAM: 128 MB, +* CPU: it depends the number of Newtest scenarios. + +### Module installation + +#### Requirements + +| Dependency | Version | Repository | +| :----------------- | :----------: | :----------------- | +| perl-SOAP-Lite | 1.10 | centreon base | +| perl-TimeDate | 2.30 | redhat/centos base | + +#### Newtest installation + +"newtest" is an official Gorgone module. No installation needed. + +## Exploitation + +### Generals Principles + +`newtest` is a module in charge to retrieve Newtest services. + +This module uses the Newtest webservice in order to connect and retrieve the informations of one (or more) Newtest Management Console (NMC). + +By default `newtest` starts X processes (it depends of the configuration). + +Here are the steps done by one process: + +1. Centreon configuration: get the robots and scenarios already configured, + +2. Get the list of robots and scenarios from the NMC, + +3. Create the needed configuration in Centreon with CLAPI (no disable or delete actions), + +4. Get the last status of scenarios from the NMC, + +5. Submit the result to Centreon through "centcore". + +### Configuration + +The `newtest` module is configured in the Gorgone configuration file and the `modules` table. + +Common attributes: + +| Label | Description | +| :------------ | :---------- | +| name | Name of the module | +| package | Perl code package used by the module | +| enable | Activation boolean | +| check_containers_time | | +| clapi_command | Path to the CLAPI binary | +| clapi_username | CLAPI username | +| clapi_password | CLAPI username's password | +| clapi_action_applycfg | CLAPI action used to apply Poller configuration | +| centcore_cmd | Path to centcore command file | + +An entry in the `containers` table with the following attributes per NWC definition: + +| Label | Description | +| :------------ | :---------- | +| name | Name of the NWC configuration entrie | +| resync_time | | +| nmc_endpoint | Address of the NMC endpoint | +| username | Username to connect to NWC endpoint | +| password | Username's password | +| host_template | Host template used when the daemon creates a host in Centreon | +| host_prefix | Name used when the daemon creates and looks for a host in Centreon | +| service_template | Service template used when the daemon creates a host in Centreon | +| service_prefix | Name used when the daemon creates and looks for a service in Centreon | +| poller_name | Poller used when the daemon creates a host in Centreon | +| list_scenario_status | Informations to look for from the NWC endpoint | + +#### Example + +```yaml +modules: + - name: newtest + package: gorgone::modules::plugins::newtest::hooks + enable: false + # in seconds - do purge for container also + check_containers_time: 3600 + clapi_command: /usr/bin/centreon + clapi_username: admin + clapi_password: centreon + clapi_action_applycfg: POLLERRELOAD + centcore_cmd: /var/lib/centreon/centcore.cmd + containers: + - name: nwc_1 + resync_time: 300 + nmc_endpoint: "http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx" + username: user + password: pass + host_template: generic-active-host-custom + host_prefix: Robot-%s + service_template: generic-passive-service-custom + service_prefix: Scenario-%s + poller_name: Central + list_scenario_status: '{ "search": "All", "instances": [] }' + - name: nwc_2 + resync_time: 600 + nmc_endpoint: "http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx" + username: user + password: pass + host_template: generic-active-host-custom + host_prefix: Robot-%s + service_template: generic-passive-service-custom + service_prefix: Scenario-%s + poller_name: Central + list_scenario_status: '{ "search": "Robot", "instances": ["XXXX"] }' +``` + +### Troubleshooting + +It is possible to get this kind of error in logs of `newtest`: + +```bash +die: syntax error at line 1, column 0, byte 0 at /usr/lib/perl5/vendor_perl/5.8.8/i386-linux-thread-multi/XML/Parser.pm line 189 +``` + +It often means that a timeout occur. From b02174a5668b5df78b9fc405422b17d44ddd5a86 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Thu, 26 Sep 2019 11:59:21 +0200 Subject: [PATCH 179/948] feat(github): add README --- gorgone/README.md | 22 ++++++++++++++++++++++ gorgone/docs/guide.md | 13 ------------- 2 files changed, 22 insertions(+), 13 deletions(-) create mode 100644 gorgone/README.md diff --git a/gorgone/README.md b/gorgone/README.md new file mode 100644 index 00000000000..bf8821b3b53 --- /dev/null +++ b/gorgone/README.md @@ -0,0 +1,22 @@ +# Centreon Gorgone + +Centreon Gorgone and his "gorgoned" daemon is a lightweight, distributed, modular tasks handler. + +It provides a set of actions like: + +* Execute commands +* Send files/directories, +* Schedule cron-like tasks, +* Push or execute tasks through SSH. + +The daemon can be installed on Centreon environments like Centreon Central, Remote and Poller servers. + +It uses ZeroMQ library. + +## Modules + +The Centreon Gorgone project encloses several built-in modules. + +See the list above: + +* [Newtest](docs/modules/plugins/newtest.md) diff --git a/gorgone/docs/guide.md b/gorgone/docs/guide.md index 961dc5de5f7..810a5c78b23 100644 --- a/gorgone/docs/guide.md +++ b/gorgone/docs/guide.md @@ -1,18 +1,5 @@ # Centreon Gorgone -## Description - -"gorgoned" is a daemon which handles tasks. You can plug-in modules: - -* Action: execute commands, send files/directories, -* Cron: cron-like tasks scheduler, -* Proxy: push tasks (to another "gorgoned" instance) or execute (through SSH). -* ... - -The daemon can be installed on Centreon Central, Remote and Poller servers. - -It uses ZeroMQ library. - ## Installation The daemon uses the following Perl modules: From 9e419ac0f1c830bc4eecaffbd3f1e4c4e9a8ce35 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Thu, 26 Sep 2019 17:42:29 +0200 Subject: [PATCH 180/948] feat(docs): prepare doc for modules --- gorgone/README.md | 19 ++++++++++++++++++- .../docs/modules/centreon/autodiscovery.md | 0 gorgone/docs/modules/centreon/broker.md | 0 gorgone/docs/modules/centreon/engine.md | 0 gorgone/docs/modules/centreon/legacycmd.md | 0 gorgone/docs/modules/centreon/pollers.md | 0 gorgone/docs/modules/core/action.md | 0 gorgone/docs/modules/core/cron.md | 0 gorgone/docs/modules/core/dbcleaner.md | 0 gorgone/docs/modules/core/httpserver.md | 0 gorgone/docs/modules/core/proxy.md | 0 gorgone/docs/modules/core/pull.md | 0 gorgone/docs/modules/core/register.md | 0 13 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 gorgone/docs/modules/centreon/autodiscovery.md create mode 100644 gorgone/docs/modules/centreon/broker.md create mode 100644 gorgone/docs/modules/centreon/engine.md create mode 100644 gorgone/docs/modules/centreon/legacycmd.md create mode 100644 gorgone/docs/modules/centreon/pollers.md create mode 100644 gorgone/docs/modules/core/action.md create mode 100644 gorgone/docs/modules/core/cron.md create mode 100644 gorgone/docs/modules/core/dbcleaner.md create mode 100644 gorgone/docs/modules/core/httpserver.md create mode 100644 gorgone/docs/modules/core/proxy.md create mode 100644 gorgone/docs/modules/core/pull.md create mode 100644 gorgone/docs/modules/core/register.md diff --git a/gorgone/README.md b/gorgone/README.md index bf8821b3b53..a43ece3d83b 100644 --- a/gorgone/README.md +++ b/gorgone/README.md @@ -13,10 +13,27 @@ The daemon can be installed on Centreon environments like Centreon Central, Remo It uses ZeroMQ library. +To install it and understand the main principles, follow the [guide](docs/guide.md). + ## Modules The Centreon Gorgone project encloses several built-in modules. See the list above: -* [Newtest](docs/modules/plugins/newtest.md) +* Core + * [Action](docs/modules/core/action.md) + * [Cron](docs/modules/core/cron.md) + * [DB Cleaner](docs/modules/core/dbcleaner.md) + * [HTTP Server](docs/modules/core/httpserver.md) + * [Proxy](docs/modules/core/proxy.md) + * [Pull](docs/modules/core/pull.md) + * [Register](docs/modules/core/register.md) +* Centreon + * [Autodiscovery](docs/modules/centreon/autodiscovery.md) + * [Broker](docs/modules/centreon/broker.md) + * [Engine](docs/modules/centreon/engine.md) + * [Legacy Cmd](docs/modules/centreon/legacycmd.md) + * [Pollers](docs/modules/centreon/pollers.md) +* Plugins + * [Newtest](docs/modules/plugins/newtest.md) diff --git a/gorgone/docs/modules/centreon/autodiscovery.md b/gorgone/docs/modules/centreon/autodiscovery.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/gorgone/docs/modules/centreon/broker.md b/gorgone/docs/modules/centreon/broker.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/gorgone/docs/modules/centreon/engine.md b/gorgone/docs/modules/centreon/engine.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/gorgone/docs/modules/centreon/legacycmd.md b/gorgone/docs/modules/centreon/legacycmd.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/gorgone/docs/modules/centreon/pollers.md b/gorgone/docs/modules/centreon/pollers.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/gorgone/docs/modules/core/action.md b/gorgone/docs/modules/core/action.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/gorgone/docs/modules/core/cron.md b/gorgone/docs/modules/core/cron.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/gorgone/docs/modules/core/dbcleaner.md b/gorgone/docs/modules/core/dbcleaner.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/gorgone/docs/modules/core/httpserver.md b/gorgone/docs/modules/core/httpserver.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/gorgone/docs/modules/core/proxy.md b/gorgone/docs/modules/core/proxy.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/gorgone/docs/modules/core/pull.md b/gorgone/docs/modules/core/pull.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/gorgone/docs/modules/core/register.md b/gorgone/docs/modules/core/register.md new file mode 100644 index 00000000000..e69de29bb2d From a81923b8f8d62cd5bf98e47ec760684b0c95e796 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Thu, 26 Sep 2019 18:19:20 +0200 Subject: [PATCH 181/948] feat(docs): start docs on modules --- gorgone/docs/modules/core/action.md | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/gorgone/docs/modules/core/action.md b/gorgone/docs/modules/core/action.md index e69de29bb2d..8526e898920 100644 --- a/gorgone/docs/modules/core/action.md +++ b/gorgone/docs/modules/core/action.md @@ -0,0 +1,32 @@ +# Action + +## Description + +This module aims to execute actions on the server running the Gorgone daemon or remotly using SSH. + +## Events + +| Event | Description | +| :- | :- | +| ACTIONREADY | Internal event to notify the core | +| PROCESSCOPY | Process file or archive received from another daemon | +| COMMAND | Execute a shell command on the server running the daemon or on another server using SSH | + +## API + +### Execute a Command Line + +Endpoint | Method | Body +| :- | :- | :- | +/api/core/action/command | `POST` | Body + +#### Example + +```bash +curl --request POST "https://hostname:8443/api/core/action/command" \ + --header "Accept: application/json" \ + --header "Content-Type: application/json" \ + --data "{ + \"command\": \"echo 'Test command' >> /tmp/here.log\" +}" +``` From a074d3e46afea9207a82780b89d55b0bafd52eaa Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Thu, 26 Sep 2019 18:26:32 +0200 Subject: [PATCH 182/948] enh(docs): module action --- gorgone/docs/modules/core/action.md | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/gorgone/docs/modules/core/action.md b/gorgone/docs/modules/core/action.md index 8526e898920..29f2b4285fc 100644 --- a/gorgone/docs/modules/core/action.md +++ b/gorgone/docs/modules/core/action.md @@ -16,9 +16,28 @@ This module aims to execute actions on the server running the Gorgone daemon or ### Execute a Command Line -Endpoint | Method | Body -| :- | :- | :- | -/api/core/action/command | `POST` | Body +| Endpoint | Method | +| :- | :- | +| /api/core/action/command | `POST` | + +Headers + +--- + +| Header | Value | +| :- | :- | +| Accept | application/json | +| Content-Type | application/json| + +Body + +--- + +```json +{ + "command": "" +} +``` #### Example From b960a9b441736e069ab9e2873fc5e9758924ff19 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Thu, 26 Sep 2019 18:28:16 +0200 Subject: [PATCH 183/948] enh(docs): module action --- gorgone/docs/modules/core/action.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/gorgone/docs/modules/core/action.md b/gorgone/docs/modules/core/action.md index 29f2b4285fc..8134917f3e3 100644 --- a/gorgone/docs/modules/core/action.md +++ b/gorgone/docs/modules/core/action.md @@ -20,18 +20,14 @@ This module aims to execute actions on the server running the Gorgone daemon or | :- | :- | | /api/core/action/command | `POST` | -Headers - ---- +#### Headers | Header | Value | | :- | :- | | Accept | application/json | | Content-Type | application/json| -Body - ---- +#### Body ```json { From 7ad99cfba8dc23e6df3e868b4d66e24fcc51e4ee Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 27 Sep 2019 09:12:53 +0200 Subject: [PATCH 184/948] fix(docs): fix guide --- gorgone/docs/guide.md | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/gorgone/docs/guide.md b/gorgone/docs/guide.md index 810a5c78b23..8e534c1c78e 100644 --- a/gorgone/docs/guide.md +++ b/gorgone/docs/guide.md @@ -105,7 +105,7 @@ Third-party clients have to use the ZeroMQ library and the following process: * If uncrypted message result is not "HELO", server refuses the connection and send it back: - ```json + ```text [ACK] [] { "code": 1, "data": { "message": "handshake issue" } } ``` @@ -141,7 +141,7 @@ After a successful handshake, client requests use the following syntax: For each client requests, the server get an immediate response: -```json +```text [ACK] [TOKEN] { "code": "x", "data": { "message": "xxxxx" } } ``` @@ -185,8 +185,7 @@ An example of the JSON stream: "last_ping_recv": "xxxx", "nodes": { "1": "xxx", - "2": "xxx", - ... + "2": "xxx" } } } @@ -207,7 +206,7 @@ An example: when you request a command execution, the server gives you a direct The client request: -```json +```text [GETLOG] [TOKEN] [TARGET] { "code": "xx", "ctime": "xx", "etime": "xx", "token": "xx", "id": "xx" } ``` @@ -244,7 +243,7 @@ An example of the json stream: "code": 1, "etime": 1419252684, "ctime": 1419252686, - "data": xxxxx, + "data": "xxxx", }, "100": { "id": 100, @@ -252,9 +251,8 @@ An example of the json stream: "code": 1, "etime": 1419252688, "ctime": 1419252690, - "data": xxxxx, - }, - ... + "data": "xxxx", + } } } } @@ -276,7 +274,7 @@ The request shouldn't be used by third-party program. It's commonly used by the The client request: -```json +```text [PUTLOG] [TOKEN] [TARGET] { "code": xxx, "etime": "xxx", "token": "xxxx", "data": { some_datas } } ``` @@ -286,7 +284,7 @@ The request shouldn't be used by third-party program. It's commonly used by the The client request (no carriage returns. only for reading): -```json +```text [REGISTERNODES] [TOKEN] [TARGET] { "nodes": [ { "id": 20, "type": "pull" }, { "id": 100, "type": "push_ssh", "address": "10.0.0.1", "ssh_port": 22 }, @@ -325,7 +323,7 @@ A Poller with gorgoned should have the following modules: * action, * pull (if the connection to the Central should be opened by the Poller). -## I want to create a client. How should I proceed ? +### I want to create a client. How should I proceed ? First, you must choose a language which can use ZeroMQ library and have some knowledge about ZeroMQ. From d3ba416f1dccfcfede814280a1fe9eba644e6e05 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 27 Sep 2019 09:22:52 +0200 Subject: [PATCH 185/948] fix(core/httpserver): fix api call --- gorgone/gorgone/modules/core/httpserver/class.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/gorgone/modules/core/httpserver/class.pm b/gorgone/gorgone/modules/core/httpserver/class.pm index 55dfb2febae..6fa6dc4f9b7 100644 --- a/gorgone/gorgone/modules/core/httpserver/class.pm +++ b/gorgone/gorgone/modules/core/httpserver/class.pm @@ -216,7 +216,7 @@ sub api_call { return '{"error":"decode_error","message":"POST content must be JSON-formated"}';; } - require 'centreon/gorgone/api.pm'; + require 'gorgone/standard/api.pm'; my %parameters = $request->uri->query_form; my $response = gorgone::standard::api::root( method => $request->method, From ee73148e5dd9c970a4234fa16ae7226dece29aff Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 27 Sep 2019 09:49:49 +0200 Subject: [PATCH 186/948] enh(docs): module action --- gorgone/docs/modules/core/action.md | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/gorgone/docs/modules/core/action.md b/gorgone/docs/modules/core/action.md index 8134917f3e3..e22ce063342 100644 --- a/gorgone/docs/modules/core/action.md +++ b/gorgone/docs/modules/core/action.md @@ -4,6 +4,21 @@ This module aims to execute actions on the server running the Gorgone daemon or remotly using SSH. +## Configuration + +| Directive | Description | Default value | +| :- | :- | :- | +| command_timeout | Time in seconds before a command is considered timed out | 30 | + +#### Example + +```yaml +name: action +package: "gorgone::modules::core::action::hooks" +enable: true +command_timeout: 30 +``` + ## Events | Event | Description | @@ -25,13 +40,19 @@ This module aims to execute actions on the server running the Gorgone daemon or | Header | Value | | :- | :- | | Accept | application/json | -| Content-Type | application/json| +| Content-Type | application/json | #### Body +| Key | Value | +| :- | :- | +| command | Command to execute | +| timeout | Time in seconds before a command is considered timed out | + ```json { - "command": "" + "command": "", + "timeout": "" } ``` From 5d6b08d938b7857166e666cd6b6aef9d66cdf06c Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 27 Sep 2019 09:50:07 +0200 Subject: [PATCH 187/948] feat(docs): add legacycmd module --- gorgone/docs/modules/centreon/legacycmd.md | 44 ++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/gorgone/docs/modules/centreon/legacycmd.md b/gorgone/docs/modules/centreon/legacycmd.md index e69de29bb2d..582a27a553a 100644 --- a/gorgone/docs/modules/centreon/legacycmd.md +++ b/gorgone/docs/modules/centreon/legacycmd.md @@ -0,0 +1,44 @@ +# Legacy Cmd + +## Description + +This module aims to mimick the behaviour of the antique `centcore` daemon. + +As for `centcore`, it reads a file (called command file) and process every commands that it knows of. + +The module relies on the following modules to process commands: + +* [Action](docs/modules/core/action.md) +* [Proxy](docs/modules/core/proxy.md) +* [Engine](docs/modules/centreon/engine.md) + +## Configuration + +| Directive | Description | Default value | +| :- | :- | :- | +| cmd_file | *Command file* to read commands from | /var/lib/centreon/centcore.cmd | +| cache_dir | Directory where to process Centreon configuration files | /var/cache/centreon/ | +| cache_dir_trap | Directory where to process Centreontrapd databases | /etc/snmp/centreon_traps/ | +| remote_dir | Directory where to export Remote Servers configuration | /var/lib/centreon/remote-data/ | + +#### Example + +```yaml +name: legacycmd +package: "gorgone::modules::centreon::legacycmd::hooks" +enable: true +cmd_file: "/var/lib/centreon/centcore.cmd" +cache_dir: "/var/cache/centreon/" +cache_dir_trap: "/etc/snmp/centreon_traps/" +remote_dir: "/var/lib/centreon/remote-data/" +``` + +## Events + +| Event | Description | +| :- | :- | +| LEGACYCMDREADY | Internal event to notify the core | + +## API + +No API endpoints. From c9866b962546929b93997ce4d314ebcca22b69af Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 27 Sep 2019 10:21:07 +0200 Subject: [PATCH 188/948] enh(docs): enhance modules docs --- gorgone/docs/modules/centreon/legacycmd.md | 6 +- gorgone/docs/modules/core/action.md | 2 +- gorgone/docs/modules/core/cron.md | 126 +++++++++++++++++++++ 3 files changed, 130 insertions(+), 4 deletions(-) diff --git a/gorgone/docs/modules/centreon/legacycmd.md b/gorgone/docs/modules/centreon/legacycmd.md index 582a27a553a..617a6226db9 100644 --- a/gorgone/docs/modules/centreon/legacycmd.md +++ b/gorgone/docs/modules/centreon/legacycmd.md @@ -8,9 +8,9 @@ As for `centcore`, it reads a file (called command file) and process every comma The module relies on the following modules to process commands: -* [Action](docs/modules/core/action.md) -* [Proxy](docs/modules/core/proxy.md) -* [Engine](docs/modules/centreon/engine.md) +* [Action](../core/action.md) +* [Proxy](../core/proxy.md) +* [Engine](engine.md) ## Configuration diff --git a/gorgone/docs/modules/core/action.md b/gorgone/docs/modules/core/action.md index e22ce063342..55b292077c4 100644 --- a/gorgone/docs/modules/core/action.md +++ b/gorgone/docs/modules/core/action.md @@ -29,7 +29,7 @@ command_timeout: 30 ## API -### Execute a Command Line +### Execute a command line | Endpoint | Method | | :- | :- | diff --git a/gorgone/docs/modules/core/cron.md b/gorgone/docs/modules/core/cron.md index e69de29bb2d..6c1b09937df 100644 --- a/gorgone/docs/modules/core/cron.md +++ b/gorgone/docs/modules/core/cron.md @@ -0,0 +1,126 @@ +# Cron + +## Description + +This module aims to reproduce a cron-like scheduler that can send events to other Gorgone modules. + +## Configuration + +No specific configuration is needed. + +Below the configuration to add cron definitions: + +| Directive | Description | +| :- | :- | +| id | Unique identifier of the cron definition | +| timespec | Cron-like time specification | +| action | Action/event to call at job execution | +| parameters | Parameters needed by the called action/event | + +#### Example + +```yaml +name: cron +package: "gorgone::modules::core::cron::hooks" +enable: true +cron: + - id: echo_date + timespec: "* * * * *" + action: COMMAND + parameters: + command: "date >> /tmp/date.log" + timeout: 10 +``` + +## Events + +| Event | Description | +| :- | :- | +| CRONREADY | Internal event to notify the core | +| GETCRON | Get one or all cron definitions | +| ADDCRON | Add one or several cron definitions | +| DELETECRON | Delete a cron definition | +| UPDATECRON | Update a cron definition | + +## API + +### Get one or all definitions + +| Endpoint | Method | +| :- | :- | +| /api/core/cron/definitions | `GET` | +| /api/core/cron/definitions/:id | `GET` | + +#### Headers + +| Header | Value | +| :- | :- | +| Accept | application/json | + +#### Body + +Not needed. + +#### Example + +```bash +curl --request GET "https://hostname:8443/api/core/cron/definitions" \ + --header "Accept: application/json" +``` + +```bash +curl --request GET "https://hostname:8443/api/core/cron/definitions/echo_date" \ + --header "Accept: application/json" +``` + +### Add one or several cron definitions + +| Endpoint | Method | +| :- | :- | +| /api/core/cron/definitions | `POST` | + +#### Headers + +| Header | Value | +| :- | :- | +| Accept | application/json | +| Content-Type | application/json | + +#### Body + +| Key | Value | +| :- | :- | +| id | ID of the definition | +| timespec | Cron-like time specification | +| command | Action/event to call at job execution | +| parameters | Parameters needed by the called action/event | + +```json +[ + { + "id": "", + "timespec": "", + "command": "", + "parameters": "" + } +] +``` + +#### Example + +```bash +curl --request POST "https://hostname:8443/api/core/cron/definitions" \ + --header "Accept: application/json" \ + --header "Content-Type: application/json" \ + --data "[ + { + \"timespec\": \"*/15 * * * *\", + \"id\": \"job_123\", + \"action\": \"COMMAND\" + \"parameters\": { + \"command\": \"date >> /tmp/the_date_again.log\", + \"timeout\": 5 + } + } +]" +``` From fb0384163691f0222f5c28ee8941e23258b2cfa5 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Fri, 27 Sep 2019 11:12:54 +0200 Subject: [PATCH 189/948] fix(core): use the gorgone class --- gorgone/gorgone/class/core.pm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 3379b70419e..cfbe7a15810 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -28,11 +28,10 @@ use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); use gorgone::standard::library; use gorgone::class::db; -use centreon::script; my ($gorgone, $config); -use base qw(centreon::script); +use base qw(gorgone::class::script); my $VERSION = '1.0'; my %handlers = (TERM => {}, HUP => {}, CHLD => {}); From 917150fc8407f47220a558fc522e6867f1c85efc Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Fri, 27 Sep 2019 11:49:02 +0200 Subject: [PATCH 190/948] enh(contrib): add centreon init gorgone script --- gorgone/contrib/gorgone_config_init.pl | 200 +++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 gorgone/contrib/gorgone_config_init.pl diff --git a/gorgone/contrib/gorgone_config_init.pl b/gorgone/contrib/gorgone_config_init.pl new file mode 100644 index 00000000000..730969b7437 --- /dev/null +++ b/gorgone/contrib/gorgone_config_init.pl @@ -0,0 +1,200 @@ +#!/usr/bin/perl + +use warnings; +use strict; +use FindBin; +use lib "$FindBin::Bin"; +# to be launched from contrib directory +use lib "$FindBin::Bin/../"; + +gorgone::script::gorgone_config_init->new()->run(); + +package gorgone::script::gorgone_config_init; + +use strict; +use warnings; +use gorgone::standard::misc; + +use base qw(gorgone::class::script); + +use vars qw($centreon_config); + +sub new { + my $class = shift; + my $self = $class->SUPER::new("gorgone_config_init", + centreon_db_conn => 0, + centstorage_db_conn => 0, + noconfig => 0 + ); + + bless $self, $class; + $self->add_options( + 'centcore-config:s' => \$self->{centcore_config}, + 'gorgone-config:s' => \$self->{gorgone_config}, + ); + return $self; +} + +sub init { + my $self = shift; + $self->SUPER::init(); + + $self->{centcore_config} = '/etc/centreon/conf.pm' if (!defined($self->{centcore_config}) || $self->{centcore_config} eq ''); + $self->{gorgone_config} = '/etc/centreon/gorgoned.yml' if (!defined($self->{gorgone_config}) || $self->{gorgone_config} eq ''); +} + +sub read_centcore_config { + my ($self) = @_; + + unless (my $return = do $self->{centcore_config}) { + $self->{logger}->writeLogError("couldn't parse $self->{centcore_config}: $@") if $@; + $self->{logger}->writeLogError("couldn't do $self->{centcore_config}: $!") unless defined $return; + $self->{logger}->writeLogError("couldn't run $self->{centcore_config}") unless $return; + exit(1); + } + + if (!defined($centreon_config->{VarLib})) { + $self->{logger}->writeLogError("config file doesn't look like a centcore config file"); + exit(1); + } + + $centreon_config->{VarLib} =~ s/\/$//; + if ($centreon_config->{db_host} =~ /^(.*?):(\d+)$/) { + $centreon_config->{db_host} = $1; + $centreon_config->{db_port} = $2; + } +} + +sub write_gorgone_config { + my ($self) = @_; + + my $fh; + if (!open($fh, '>', $self->{gorgone_config})) { + $self->{logger}->writeLogError("couldn't open file '$self->{gorgone_config}': $!"); + exit(1); + } + + my $db_port = ''; + if (defined($centreon_config->{db_port})) { + $db_port = ';port=' . $centreon_config->{db_port}; + } + + my $content = <<"END_FILE"; +name: gorgoned +description: Configuration init by gorgone_config_init +database: + db_centreon: + dsn: "mysql:host=$centreon_config->{db_host}${db_port};dbname=$centreon_config->{centreon_db}" + username: "$centreon_config->{db_user}" + password: "$centreon_config->{db_passwd}" + db_centstorage: + dsn: "mysql:host=$centreon_config->{db_host}${db_port};dbname=$centreon_config->{centstorage_db}" + username: "$centreon_config->{db_user}" + password: "$centreon_config->{db_passwd}" +gorgonecore: + external_com_type: tcp + external_com_path: "*:5555" + hostname: + id: +modules: + - name: httpserver + package: gorgone::modules::core::httpserver::hooks + enable: false + address: 0.0.0.0 + port: 8443 + ssl: true + ssl_cert_file: /etc/pki/tls/certs/server-cert.pem + ssl_key_file: /etc/pki/tls/server-key.pem + auth: + user: admin + password: password + + - name: cron + package: gorgone::modules::core::cron::hooks + enable: false + + - name: action + package: gorgone::modules::core::action::hooks + enable: true + + - name: proxy + package: gorgone::modules::core::proxy::hooks + enable: true + + - name: pollers + package: gorgone::modules::centreon::pollers::hooks + enable: true + + - name: broker + package: gorgone::modules::centreon::broker::hooks + enable: false + cache_dir: "/var/lib/centreon/broker-stats/" + cron: + - id: broker_stats + timespec: "*/2 * * * *" + action: BROKERSTATS + parameters: + timeout: 10 + + - name: legacycmd + package: gorgone::modules::centreon::legacycmd::hooks + enable: true + cmd_file: "$centreon_config->{VarLib}/centcore.cmd" + cache_dir: "$centreon_config->{CacheDir}" + cache_dir_trap: "/etc/snmp/centreon_traps/" + remote_dir: "$centreon_config->{VarLib}/remote-data/" +END_FILE + + chomp $content; + print $fh $content; + close($fh); +} + +sub run { + my $self = shift; + + $self->SUPER::run(); + $self->read_centcore_config(); + $self->write_gorgone_config(); + + $self->{logger}->writeLogInfo("file '$self->{gorgone_config}' created success"); +} + +__END__ + +=head1 NAME + +gorgone_config_init.pl - script to create gorgone config to replace centcore + +=head1 SYNOPSIS + +gorgone_config_init.pl [options] + +=head1 OPTIONS + +=over 8 + +=item B<--centcore-config> + +Specify the path to the centcore configuration file (default: '/etc/centreon/conf.pm'). + +=item B<--gorgone-config> + +Specify the gorgone config file created (default: '/etc/centreon/gorgoned.yml'). + +=item B<--severity> + +Set the script log severity (default: 'error'). + +=item B<--help> + +Print a brief help message and exits. + +=back + +=head1 DESCRIPTION + +B + +=cut + From eb51cb6668252c154091fa989a15e83c63eae008 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Fri, 27 Sep 2019 11:53:05 +0200 Subject: [PATCH 191/948] fix(contrib): endline yml config --- gorgone/contrib/gorgone_config_init.pl | 1 - 1 file changed, 1 deletion(-) diff --git a/gorgone/contrib/gorgone_config_init.pl b/gorgone/contrib/gorgone_config_init.pl index 730969b7437..80936f87261 100644 --- a/gorgone/contrib/gorgone_config_init.pl +++ b/gorgone/contrib/gorgone_config_init.pl @@ -145,7 +145,6 @@ sub write_gorgone_config { remote_dir: "$centreon_config->{VarLib}/remote-data/" END_FILE - chomp $content; print $fh $content; close($fh); } From cb12f69a2f1345fe5be27865c0dcfa6cf48cdd2b Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Fri, 27 Sep 2019 11:54:30 +0200 Subject: [PATCH 192/948] fix(contrib): no external config by default --- gorgone/contrib/gorgone_config_init.pl | 2 -- 1 file changed, 2 deletions(-) diff --git a/gorgone/contrib/gorgone_config_init.pl b/gorgone/contrib/gorgone_config_init.pl index 80936f87261..399d46ad432 100644 --- a/gorgone/contrib/gorgone_config_init.pl +++ b/gorgone/contrib/gorgone_config_init.pl @@ -92,8 +92,6 @@ sub write_gorgone_config { username: "$centreon_config->{db_user}" password: "$centreon_config->{db_passwd}" gorgonecore: - external_com_type: tcp - external_com_path: "*:5555" hostname: id: modules: From ad10f517f89709532814b6ef8aedaf29afba946e Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 27 Sep 2019 11:59:21 +0200 Subject: [PATCH 193/948] enh(core/proxy): add REMOTECOPY event --- gorgone/gorgone/modules/core/proxy/hooks.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index 6f63299fb88..89e56a81abd 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -36,6 +36,7 @@ use constant NAMESPACE => 'core'; use constant NAME => 'proxy'; use constant EVENTS => [ { event => 'PROXYREADY' }, + { event => 'REMOTECOPY', uri => '/remotecopy', method => 'POST' }, { event => 'SETLOGS' }, # internal. Shouldn't be used by third party clients { event => 'PONG' }, # internal. Shouldn't be used by third party clients { event => 'REGISTERNODES' }, # internal. Shouldn't be used by third party clients From 91630ce25adc4690da60726f9174eec78342ae58 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 27 Sep 2019 11:59:39 +0200 Subject: [PATCH 194/948] enh(docs): enhance modules docs --- gorgone/docs/modules/core/cron.md | 73 ++++++++++++++++++- gorgone/docs/modules/core/dbcleaner.md | 34 +++++++++ gorgone/docs/modules/core/httpserver.md | 70 +++++++++++++++++++ gorgone/docs/modules/core/proxy.md | 93 +++++++++++++++++++++++++ 4 files changed, 267 insertions(+), 3 deletions(-) diff --git a/gorgone/docs/modules/core/cron.md b/gorgone/docs/modules/core/cron.md index 6c1b09937df..226b64736a2 100644 --- a/gorgone/docs/modules/core/cron.md +++ b/gorgone/docs/modules/core/cron.md @@ -57,9 +57,11 @@ cron: | :- | :- | | Accept | application/json | -#### Body +#### Path variables -Not needed. +| Variable | Description | +| :- | :- | +| id | Identifier of the cron definition | #### Example @@ -116,7 +118,7 @@ curl --request POST "https://hostname:8443/api/core/cron/definitions" \ { \"timespec\": \"*/15 * * * *\", \"id\": \"job_123\", - \"action\": \"COMMAND\" + \"action\": \"COMMAND\", \"parameters\": { \"command\": \"date >> /tmp/the_date_again.log\", \"timeout\": 5 @@ -124,3 +126,68 @@ curl --request POST "https://hostname:8443/api/core/cron/definitions" \ } ]" ``` + +### Update a definition + +| Endpoint | Method | +| :- | :- | +| /api/core/cron/definitions/:id | `PATCH` | + +#### Headers + +| Header | Value | +| :- | :- | +| Accept | application/json | +| Content-Type | application/json | + +#### Path variables + +| Variable | Description | +| :- | :- | +| id | Identifier of the cron definition | + +#### Body + +One or several keys allowed by the add endpoint. + +```json +{ + "parameters": "" +} +``` + +#### Example + +```bash +curl --request PATCH "https://hostname:8443/api/core/cron/definitions/job_123" \ + --header "Accept: application/json" \ + --header "Content-Type: application/json" \ + --data "{ + \"timespec\": \"*/2 * * * *\" +}" +``` + +### Delete a definition + +| Endpoint | Method | +| :- | :- | +| /api/core/cron/definitions/:id | `DELETE` | + +#### Headers + +| Header | Value | +| :- | :- | +| Accept | application/json | + +#### Path variables + +| Variable | Description | +| :- | :- | +| id | Identifier of the cron definition | + +#### Example + +```bash +curl --request DELETE "https://hostname:8443/api/core/cron/definitions/job_123" \ + --header "Accept: application/json" +``` diff --git a/gorgone/docs/modules/core/dbcleaner.md b/gorgone/docs/modules/core/dbcleaner.md index e69de29bb2d..c9a8c6675dd 100644 --- a/gorgone/docs/modules/core/dbcleaner.md +++ b/gorgone/docs/modules/core/dbcleaner.md @@ -0,0 +1,34 @@ +# DB Cleaner + +## Description + +This module aims to maintain the Gorgone daemon database by purging entries cyclically. + +The module is loaded by default. Adding it to the configuration will overload daemon default configuration. + +## Configuration + +| Directive | Description | Default value | +| :- | :- | :- | +| purge_sessions_time | Time in seconds before deleting sessions in the `gorgone_identity` table | 3600 | +| purge_history_time | Time in seconds before deleting history in the `gorgone_history` table | 604800 | + +#### Example + +```yaml +name: dbcleaner +package: "gorgone::modules::core::dbcleaner::hooks" +enable: true +purge_sessions_time: 3600 +purge_history_time: 604800 +``` + +## Events + +| Event | Description | +| :- | :- | +| DBCLEANERREADY | Internal event to notify the core | + +## API + +No API endpoints. diff --git a/gorgone/docs/modules/core/httpserver.md b/gorgone/docs/modules/core/httpserver.md index e69de29bb2d..4cb40c9e242 100644 --- a/gorgone/docs/modules/core/httpserver.md +++ b/gorgone/docs/modules/core/httpserver.md @@ -0,0 +1,70 @@ +# HTTP Server + +## Description + +This module aims to provide a HTTP/S server to expose handy endpoints to talk to Gorgone. + +It relies on a core API module to server Gorgone events and can dispatch any other piece of code. + +## Configuration + +| Directive | Description | Default value | +| :- | :- | :- | +| address | IP address for the server to bind to | 0.0.0.0 | +| port | Port on which the server will listen to requests | 8080 | +| ssl | Boolean to enable SSL terminaison | false | +| ssl_cert_file | Path to the SSL certificate (if SSL enabled) | | +| ssl_key_file | Path to the SSL key (if SSL enabled) | | +| auth | Basic credentials to access the server | | + +#### Example + +```yaml +name: httpserver +package: "gorgone::modules::core::httpserver::hooks" +enable: true +address: 0.0.0.0 +port: 8443 +ssl: true +ssl_cert_file: /etc/pki/tls/certs/server-cert.pem +ssl_key_file: /etc/pki/tls/server-key.pem +auth: + user: admin + password: password +``` + +Below the configuration to add other endpoints: + +```yaml +dispatch: + - endpoint: "/mycode" + method: GET + class: "path::to::my::code" +``` + +## Events + +| Event | Description | +| :- | :- | +| HTTPSERVERREADY | Internal event to notify the core | + +## API + +### Get HTTP server status + +| Endpoint | Method | +| :- | :- | +| /status | `GET` | + +#### Headers + +| Header | Value | +| :- | :- | +| Accept | application/json | + +#### Example + +```bash +curl --request GET "https://hostname:8443/status" \ + --header "Accept: application/json" +``` diff --git a/gorgone/docs/modules/core/proxy.md b/gorgone/docs/modules/core/proxy.md index e69de29bb2d..b21e5a54766 100644 --- a/gorgone/docs/modules/core/proxy.md +++ b/gorgone/docs/modules/core/proxy.md @@ -0,0 +1,93 @@ +# Proxy + +## Description + +This module aims to give the possibility to Gorgone to become distributed. + +It is not needed in a Centreon standalone configuration, but must be enabled if there is Poller or Remote servers. + +The module includes mechanisms like ping to make sure targets are alive, synchronisation to store logs in the Central Gorgone database, etc. + +A SSH client library make routing to non-gorgoned targets to be handlded. + +## Configuration + +| Directive | Description | Default value | +| :- | :- | :- | +| pool | Number of childs to instantiate to process events | 5 | +| synchistory_time | Time in seconds between two logs synchronisation | 60 | +| synchistory_timeout | Time in seconds before logs synchronisation is considered timed out | 30 | +| ping | Time in seconds between two target pings | 60 | +| pong_discard_timeout | Time in seconds before a target is considered dead | 300 | + +#### Example + +```yaml +name: proxy +package: "gorgone::modules::core::proxy::hooks" +enable: false +pool: 5 +synchistory_time: 60 +synchistory_timeout: 30 +ping: 60 +pong_discard_timeout: 300 +``` + +## Events + +| Event | Description | +| :- | :- | +| PROXYREADY | Internal event to notify the core | +| REMOTECOPY | Copy files or directories from the server running the daemon to another server | +| SETLOGS | Internal event to insert logs into the database | +| PONG | Internal event to handle target ping response | +| REGISTERNODES | Internal event to register targets | +| UNREGISTERNODES | Internal event to unregister targets | +| PROXYADDNODE | Internal event to add nodes for proxying | +| PROXYDELNODE | Internal event to delete nodes from proxying | +| PROXYADDSUBNODE | Internal event to add nodes of nodes for proxying | +| PONGRESET | Internal event to deal with no pong targets | + +## API + +### Copy files or directory to remote server + +| Endpoint | Method | +| :- | :- | +| /api/core/proxy/remotecopy | `POST` | + +#### Headers + +| Header | Value | +| :- | :- | +| Accept | application/json | +| Content-Type | application/json | + +#### Body + +| Key | Value | +| :- | :- | +| source | Path of the source file or directory | +| destination | Path of the destination file or directory | +| cache_dir | Path to the cache directory for archiving purpose | + +```json +{ + "source": "", + "destination": "", + "cache_dir": "" +} +``` + +#### Example + +```bash +curl --request GET "https://hostname:8443/api/core/proxy/remotecopy" \ + --header "Accept: application/json" \ + --header "Content-Type: application/json" \ + --data " { + \"source\": \"/var/cache/centreon/config/engine/2/\", + \"destination\": \"/etc/centreon-engine\", + \"cache_dir\": \"/var/cache/centreon\" +}" +``` From e42fb0e651cc0149f34bc6038aa367270e5532f7 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Fri, 27 Sep 2019 12:03:50 +0200 Subject: [PATCH 195/948] enh(spec): add gorgone_config_init.pl script --- gorgone/packaging/centreon-gorgone.spectemplate | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 544b9e7f504..09715ee904b 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -39,6 +39,7 @@ AutoReqProv: no rm -rf %{buildroot} mkdir -p %{buildroot}/%{perl_vendorlib}/gorgone %{__install} -d -m 0755 %{buildroot}%{_bindir} +%{__install} -d -m 0755 %{buildroot}%{_usr}/local/bin/ %{__install} -d -m 0755 %{buildroot}%{_sysconfdir}/systemd/system/ %{__cp} scripts/systemd/centreon-gorgone-service %{buildroot}%{_sysconfdir}/systemd/system/centreon-gorgone.service %{__install} -d -m 0755 %{buildroot}%{_sysconfdir}/sysconfig @@ -46,6 +47,7 @@ mkdir -p %{buildroot}/%{perl_vendorlib}/gorgone %{__cp} -R gorgone/* %{buildroot}/%{perl_vendorlib}/gorgone/ %{__cp} gorgoned %{buildroot}%{_bindir}/ +%{__cp} contrib/gorgone_config_init.pl %{buildroot}%{_usr}/local/bin/ %{_fixperms} $RPM_BUILD_ROOT/* @@ -58,6 +60,7 @@ rm -rf %{buildroot} %defattr(-,root,root,-) %{perl_vendorlib}/gorgone/ %attr(755,root,root) %{_bindir}/gorgoned +%attr(755,root,root) %{_usr}/local/bin/gorgone_config_init.pl %attr(755,root,root) %{_sysconfdir}/systemd/system/centreon-gorgone.service %config(noreplace) %{_sysconfdir}/sysconfig/centreon-gorgone From 7cc5dc2831ced562bd27d17c5f5718c3395dd2a0 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 27 Sep 2019 16:54:12 +0200 Subject: [PATCH 196/948] feat(docs): add modules doc --- gorgone/README.md | 1 + .../docs/modules/centreon/autodiscovery.md | 172 +++++++++++++++++ gorgone/docs/modules/centreon/broker.md | 68 +++++++ gorgone/docs/modules/centreon/engine.md | 67 +++++++ gorgone/docs/modules/centreon/pollers.md | 29 +++ gorgone/docs/modules/core/proxy.md | 2 +- gorgone/docs/modules/core/pull.md | 25 +++ gorgone/docs/modules/core/register.md | 93 +++++++++ gorgone/docs/modules/plugins/newtest.md | 177 +++++++++--------- gorgone/docs/modules/plugins/scom.md | 90 +++++++++ 10 files changed, 634 insertions(+), 90 deletions(-) create mode 100644 gorgone/docs/modules/plugins/scom.md diff --git a/gorgone/README.md b/gorgone/README.md index a43ece3d83b..3a3bc69740c 100644 --- a/gorgone/README.md +++ b/gorgone/README.md @@ -37,3 +37,4 @@ See the list above: * [Pollers](docs/modules/centreon/pollers.md) * Plugins * [Newtest](docs/modules/plugins/newtest.md) + * [Scom](docs/modules/plugins/scom.md) diff --git a/gorgone/docs/modules/centreon/autodiscovery.md b/gorgone/docs/modules/centreon/autodiscovery.md index e69de29bb2d..06c05c03715 100644 --- a/gorgone/docs/modules/centreon/autodiscovery.md +++ b/gorgone/docs/modules/centreon/autodiscovery.md @@ -0,0 +1,172 @@ +# Autodiscovery + +## Description + +This module aims to extend Centreon Autodiscovery server functionalities. + +## Configuration + +No specific configuration. + +#### Example + +```yaml +name: autodiscovery +package: "gorgone::modules::centreon::autodiscovery::hooks" +enable: true +``` + +## Events + +| Event | Description | +| :- | :- | +| AUTODISCOVERYREADY | Internal event to notify the core | +| GETDISCOVERYRESULTS | Internal event to retrieve discovery results from logs | +| UPDATEDISCOVERYRESULTS | Internal event to update tasks/jobs result table | +| GETDISCOVERYJOB | Get discovery job result | +| ADDDISCOVERYJOB | Add a discovery job | +| GETDISCOVERYTASK | Get discovery task result | +| ADDDISCOVERYTASK | Get a discovery task | + +## API + +### Add a discovery task + +| Endpoint | Method | +| :- | :- | +| /api/centreon/autodiscovery/task | `POST` | + +#### Headers + +| Header | Value | +| :- | :- | +| Accept | application/json | +| Content-Type | application/json | + +#### Body + +| Key | Value | +| :- | :- | +| id | Identifier of the task (random if empty) | +| command | Command line to execute to perform the discovery | +| timeout | Time in seconds before the command is considered timed out | +| target | Identifier of the target on which to execute the command | + +```json +{ + "id": "", + "command": "", + "timeout": "", + "target": "" +} +``` + +#### Example + +```bash +curl --request POST "https://hostname:8443/api/centreon/autodiscovery/task" \ + --header "Accept: application/json" \ + --header "Content-Type: application/json" \ + --data "{ + \"command\": \"perl /usr/lib/centreon/plugins/centreon_generic_snmp.pl --plugin=os::linux::local::plugin --mode=discovery-snmp --subnet='10.1.2.3/24' --snmp-port='161' --snmp-version='2c' --snmp-community='public'\", + \"timeout\": 300, + \"target\": 2 +}" +``` + +### Get a discovery task results + +| Endpoint | Method | +| :- | :- | +| /api/centreon/autodiscovery/task/:id | `GET` | + +#### Headers + +| Header | Value | +| :- | :- | +| Accept | application/json | + +#### Path variables + +| Variable | Description | +| :- | :- | +| id | Identifier of the task | + +#### Example + +```bash +curl --request GET "https://hostname:8443/api/centreon/autodiscovery/task/autodiscovery_task_3209230948" \ + --header "Accept: application/json" +``` + +### Add a discovery job + +| Endpoint | Method | +| :- | :- | +| /api/centreon/autodiscovery/job | `POST` | + +#### Headers + +| Header | Value | +| :- | :- | +| Accept | application/json | +| Content-Type | application/json | + +#### Body + +| Key | Value | +| :- | :- | +| id | Identifier of the task (random if empty) | +| timespec | Cron-like time specification | +| command | Command line to execute to perform the discovery | +| timeout | Time in seconds before the command is considered timed out | +| target | Identifier of the target on which to execute the command | + +```json +{ + "id": "", + "timespec": "", + "command": "", + "timeout": "", + "target": "" +} +``` + +#### Example + +```bash +curl --request POST "https://hostname:8443/api/centreon/autodiscovery/job" \ + --header "Accept: application/json" \ + --header "Content-Type: application/json" \ + --data "{ + \"timespec\": \"0 10 * * *\", + \"command\": \"perl /usr/lib/centreon/plugins/centreon_generic_snmp.pl --plugin=os::linux::local::plugin --mode=discovery-snmp --subnet='10.1.2.3/24' --snmp-port='161' --snmp-version='2c' --snmp-community='public'\", + \"timeout\": 300, + \"target\": 2 +}" +``` + +### Get a discovery job results + +| Endpoint | Method | +| :- | :- | +| /api/centreon/autodiscovery/job/:id | `GET` | + +#### Headers + +| Header | Value | +| :- | :- | +| Accept | application/json | + +#### Path variables + +| Variable | Description | +| :- | :- | +| id | Identifier of the job | + +#### Example + +```bash +curl --request GET "https://hostname:8443/api/centreon/autodiscovery/job/autodiscovery_job_40894092083" \ + --header "Accept: application/json" +``` diff --git a/gorgone/docs/modules/centreon/broker.md b/gorgone/docs/modules/centreon/broker.md index e69de29bb2d..48a4528393a 100644 --- a/gorgone/docs/modules/centreon/broker.md +++ b/gorgone/docs/modules/centreon/broker.md @@ -0,0 +1,68 @@ +# Broker + +## Description + +This module aims to deal with Centreon Broker daemon. + +## Configuration + +| Directive | Description | Default value | +| :- | :- | :- | +| cache_dir | Path to the Centreon Broker statistics directory (local) use to store target's broker statistics | /var/lib/centreon/broker-stats/ | + +The configuration needs a cron definition to unsure that statistics collection will be done cyclically. + +#### Example + +```yaml +name: broker +package: "gorgone::modules::centreon::broker::hooks" +enable: false +cache_dir: "/var/lib/centreon/broker-stats/" +cron: + - id: broker_stats + timespec: "*/2 * * * *" + action: BROKERSTATS + parameters: + timeout: 10 +``` + +## Events + +| Event | Description | +| :- | :- | +| BROKERREADY | Internal event to notify the core | +| BROKERSTATS | Collect Centreon Broker statistics files on target | + +## API + +### Collect Centreon Broker statistics on one or several targets + +| Endpoint | Method | +| :- | :- | +| /api/centreon/broker/statistics | `GET` | +| /api/centreon/broker/statistics/:id | `GET` | + +#### Headers + +| Header | Value | +| :- | :- | +| Accept | application/json | + +#### Path variables + +| Variable | Description | +| :- | :- | +| id | Identifier of the target | + +#### Example + +```bash +curl --request POST "https://hostname:8443/api/centreon/broker/statistics" \ + --header "Accept: application/json" +``` + +```bash +curl --request POST "https://hostname:8443/api/centreon/broker/statistics/2" \ + --header "Accept: application/json" +``` diff --git a/gorgone/docs/modules/centreon/engine.md b/gorgone/docs/modules/centreon/engine.md index e69de29bb2d..90e538d1fe5 100644 --- a/gorgone/docs/modules/centreon/engine.md +++ b/gorgone/docs/modules/centreon/engine.md @@ -0,0 +1,67 @@ +# Engine + +## Description + +This module aims to provide a bridge to communicate with Centreon Engine daemon. + +## Configuration + +| Directive | Description | Default value | +| :- | :- | :- | +| command_file | Path to the Centreon Engine command file pipe | /var/lib/centreon-engine/rw/centengine.cmd | + +#### Example + +```yaml +name: engine +package: "gorgone::modules::centreon::engine::hooks" +enable: true +command_file: "/var/lib/centreon-engine/rw/centengine.cmd" +``` + +## Events + +| Event | Description | +| :- | :- | +| ENGINEREADY | Internal event to notify the core | +| ENGINECOMMAND | Send a Centreon external command to Centreon Engine daemon command file pipe | + +## API + +### Execute a command line + +| Endpoint | Method | +| :- | :- | +| /api/centreon/engine/command | `POST` | + +#### Headers + +| Header | Value | +| :- | :- | +| Accept | application/json | +| Content-Type | application/json | + +#### Body + +| Key | Value | +| :- | :- | +| command | External command (old-style format) | +| command_file | Path to the Centreon Engine command file pipe | + +```json +{ + "command": "", + "command_file": "" +} +``` + +#### Example + +```bash +curl --request POST "https://hostname:8443/api/centreon/engine/command" \ + --header "Accept: application/json" \ + --header "Content-Type: application/json" \ + --data "{ + \"command\": \"[653284380] SCHEDULE_SVC_CHECK;host1;service1;653284380\" +}" +``` diff --git a/gorgone/docs/modules/centreon/pollers.md b/gorgone/docs/modules/centreon/pollers.md index e69de29bb2d..75f2fd20dd5 100644 --- a/gorgone/docs/modules/centreon/pollers.md +++ b/gorgone/docs/modules/centreon/pollers.md @@ -0,0 +1,29 @@ +# Pollers + +## Description + +This module aims to automatically register Poller servers as Gorgone targets, in opposition to the [register](../core/register.md) module. + +For now, targets will only be registered as SSH targets. + +## Configuration + +No specific configuration. + +#### Example + +```yaml +name: pollers +package: "gorgone::modules::centreon::pollers::hooks" +enable: true +``` + +## Events + +| Event | Description | +| :- | :- | +| POLLERSREADY | Internal event to notify the core | + +## API + +No API endpoints. diff --git a/gorgone/docs/modules/core/proxy.md b/gorgone/docs/modules/core/proxy.md index b21e5a54766..26594c37d23 100644 --- a/gorgone/docs/modules/core/proxy.md +++ b/gorgone/docs/modules/core/proxy.md @@ -8,7 +8,7 @@ It is not needed in a Centreon standalone configuration, but must be enabled if The module includes mechanisms like ping to make sure targets are alive, synchronisation to store logs in the Central Gorgone database, etc. -A SSH client library make routing to non-gorgoned targets to be handlded. +A SSH client library make routing to non-gorgoned targets possible. ## Configuration diff --git a/gorgone/docs/modules/core/pull.md b/gorgone/docs/modules/core/pull.md index e69de29bb2d..5687859bf85 100644 --- a/gorgone/docs/modules/core/pull.md +++ b/gorgone/docs/modules/core/pull.md @@ -0,0 +1,25 @@ +# Pull + +## Description + +This module should be used on remote targets where the connection has to be opened from the target to the Central Gorgone. + +## Configuration + +No specific configuration. + +#### Example + +```yaml +name: pull +package: "gorgone::modules::core::pull::hooks" +enable: true +``` + +## Events + +No events. + +## API + +No API endpoints. diff --git a/gorgone/docs/modules/core/register.md b/gorgone/docs/modules/core/register.md index e69de29bb2d..d53bc283a34 100644 --- a/gorgone/docs/modules/core/register.md +++ b/gorgone/docs/modules/core/register.md @@ -0,0 +1,93 @@ +# Register + +## Description + +This module aims to provide a way to register targets manually, in opposition to the [pollers](../centreon/pollers.md) module. + +Targets are either servers running Gorgone daemon or simple equipment with SSH server. + +## Configuration + +There is no specific configuration in the Gorgone daemon configuration file, only a directive to set a path to a dedicated configuration file. + +| Directive | Description | Default value | +| :- | :- | :- | +| config_file | Path to the configuration file listing targets | | + +#### Example + +```yaml +name: register +package: "gorgone::modules::core::register::hooks" +enable: true +config_file: config/registernodes.yml +``` + +Targets are listed in a separate configuration file in a `nodes` table as below: + +##### Using ZMQ (Gorgone running on target) + +| Directive | Description | +| :- | :- | +| id | Unique identifier of the target (can be Poller's ID if [pollers](../centreon/pollers.md) module is not used) | +| type | Way for the daemon to connect to the target (push_zmq) | +| address | IP address of the target | +| port | Port to connect to on the target | +| server_pubkey | Server public key | +| client_pubkey | Client public key | +| client_privkey | Client private key | +| cipher | Cipher used for encryption | +| keysize | Size of the encryption key | +| vector | Encryption vector | +| nodes | Table to register subnodes managed by target | + +#### Example + +```yaml +nodes: + - id: 4 + type: push_zmq + address: 10.1.2.3 + port: 5556 + server_pubkey: keys/poller/pubkey.crt + client_pubkey: keys/central/pubkey.crt + client_privkey: keys/central/privkey.pem + cipher: "Cipher::AES" + keysize: 32 + vector: 0123456789012345 + nodes: + - 2 +``` + +##### Using SSH + +| Directive | Description | +| :- | :- | +| id | Unique identifier of the target (can be Poller's ID if [pollers](../centreon/pollers.md) module is not used) | +| type | Way for the daemon to connect to the target (push_ssh) | +| address | IP address of the target | +| ssh_port | Port to connect to on the target | +| ssh_username | SSH username (if no SSH key) | +| ssh_password | SSH password (if no SSH key) | +| strict_serverkey_check | Boolean to strictly check the target fingerprint | + +#### Example + +```yaml +nodes: + - id: 8 + type: push_ssh + address: 10.4.5.6 + ssh_port: 22 + ssh_username: user + ssh_password: pass + strict_serverkey_check: false +``` + +## Events + +No events. + +## API + +No API endpoints. diff --git a/gorgone/docs/modules/plugins/newtest.md b/gorgone/docs/modules/plugins/newtest.md index 70a3b535ef1..1dd16c8559f 100644 --- a/gorgone/docs/modules/plugins/newtest.md +++ b/gorgone/docs/modules/plugins/newtest.md @@ -1,25 +1,24 @@ # IP-Label Newtest -## Installation +## Description -### Prerequisites +This module aims to retrieve Newtest services. -#### Software recommandations +It uses the Newtest webservice in order to connect and retrieve the informations of one (or more) Newtest Management Console (NMC). -The module "newtest" has been tested on RedHat 7 with RPMs. +By default `newtest` starts X processes (it depends of the configuration). -Installation on other system is possible but is outside the scope of this document. +Here are the steps done by one process: -#### Module location +1. Centreon configuration: get the robots and scenarios already configured, -The module "newtest" must be installed on Centreon Central server. +2. Get the list of robots and scenarios from the NMC, -Minimal used ressources are: +3. Create the needed configuration in Centreon with CLAPI (no disable or delete actions), -* RAM: 128 MB, -* CPU: it depends the number of Newtest scenarios. +4. Get the last status of scenarios from the NMC, -### Module installation +5. Submit the result to Centreon through "centcore". #### Requirements @@ -28,56 +27,39 @@ Minimal used ressources are: | perl-SOAP-Lite | 1.10 | centreon base | | perl-TimeDate | 2.30 | redhat/centos base | -#### Newtest installation - -"newtest" is an official Gorgone module. No installation needed. - -## Exploitation - -### Generals Principles - -`newtest` is a module in charge to retrieve Newtest services. +## Configuration -This module uses the Newtest webservice in order to connect and retrieve the informations of one (or more) Newtest Management Console (NMC). - -By default `newtest` starts X processes (it depends of the configuration). - -Here are the steps done by one process: - -1. Centreon configuration: get the robots and scenarios already configured, - -2. Get the list of robots and scenarios from the NMC, - -3. Create the needed configuration in Centreon with CLAPI (no disable or delete actions), +| Directive | Description | Default value | +| :- | :- | :- | +| clapi_command | Path to the CLAPI binary | /usr/bin/centreon | +| clapi_timeout | Time in seconds before CLAPI command execution is considered timed out | 10 | +| clapi_username | CLAPI username | | +| clapi_password | CLAPI username's password | | +| centcore_cmd | Path to centcore command file | /var/lib/centreon/centcore.cmd | +| clapi_action_applycfg | CLAPI action used to apply Poller configuration | | +| clapi_generate_config_timeout | Time in seconds before the configuration generation is considered timed out | 180 | +| check_containers_time | Time in seconds between two containers synchronisation | 3600 | -4. Get the last status of scenarios from the NMC, - -5. Submit the result to Centreon through "centcore". - -### Configuration +#### Example -The `newtest` module is configured in the Gorgone configuration file and the `modules` table. +```yaml +name: newtest +package: "gorgone::modules::plugins::newtest::hooks" +enable: false +check_containers_time: 3600 +clapi_command: /usr/bin/centreon +clapi_username: admin +clapi_password: centreon +clapi_action_applycfg: POLLERRELOAD +centcore_cmd: /var/lib/centreon/centcore.cmd +``` -Common attributes: +Add an entry in the `containers` table with the following attributes per NWC definition: -| Label | Description | -| :------------ | :---------- | -| name | Name of the module | -| package | Perl code package used by the module | -| enable | Activation boolean | -| check_containers_time | | -| clapi_command | Path to the CLAPI binary | -| clapi_username | CLAPI username | -| clapi_password | CLAPI username's password | -| clapi_action_applycfg | CLAPI action used to apply Poller configuration | -| centcore_cmd | Path to centcore command file | - -An entry in the `containers` table with the following attributes per NWC definition: - -| Label | Description | +| Directive | Description | | :------------ | :---------- | | name | Name of the NWC configuration entrie | -| resync_time | | +| resync_time | Time in seconds between two NWC/Centreon synchronisations | | nmc_endpoint | Address of the NMC endpoint | | username | Username to connect to NWC endpoint | | password | Username's password | @@ -91,43 +73,60 @@ An entry in the `containers` table with the following attributes per NWC definit #### Example ```yaml -modules: - - name: newtest - package: gorgone::modules::plugins::newtest::hooks - enable: false - # in seconds - do purge for container also - check_containers_time: 3600 - clapi_command: /usr/bin/centreon - clapi_username: admin - clapi_password: centreon - clapi_action_applycfg: POLLERRELOAD - centcore_cmd: /var/lib/centreon/centcore.cmd - containers: - - name: nwc_1 - resync_time: 300 - nmc_endpoint: "http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx" - username: user - password: pass - host_template: generic-active-host-custom - host_prefix: Robot-%s - service_template: generic-passive-service-custom - service_prefix: Scenario-%s - poller_name: Central - list_scenario_status: '{ "search": "All", "instances": [] }' - - name: nwc_2 - resync_time: 600 - nmc_endpoint: "http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx" - username: user - password: pass - host_template: generic-active-host-custom - host_prefix: Robot-%s - service_template: generic-passive-service-custom - service_prefix: Scenario-%s - poller_name: Central - list_scenario_status: '{ "search": "Robot", "instances": ["XXXX"] }' +containers: + - name: nwc_1 + resync_time: 300 + nmc_endpoint: "http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx" + username: user + password: pass + host_template: generic-active-host-custom + host_prefix: Robot-%s + service_template: generic-passive-service-custom + service_prefix: Scenario-%s + poller_name: Central + list_scenario_status: '{ "search": "All", "instances": [] }' + - name: nwc_2 + resync_time: 600 + nmc_endpoint: "http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx" + username: user + password: pass + host_template: generic-active-host-custom + host_prefix: Robot-%s + service_template: generic-passive-service-custom + service_prefix: Scenario-%s + poller_name: Central + list_scenario_status: '{ "search": "Robot", "instances": ["XXXX"] }' +``` + +## Events + +| Event | Description | +| :- | :- | +| NEWTESTREADY | Internal event to notify the core | +| NEWTESTRESYNC | Synchronise NWC and Centreon configuration | + +## API + +### Force synchronisation between NWC endpoints and Centreon configuration + +| Endpoint | Method | +| :- | :- | +| /api/plugins/newtest/resync | `GET` | + +#### Headers + +| Header | Value | +| :- | :- | +| Accept | application/json | + +#### Example + +```bash +curl --request POST "https://hostname:8443/api/plugins/newtest/resync" \ + --header "Accept: application/json" ``` -### Troubleshooting +## Troubleshooting It is possible to get this kind of error in logs of `newtest`: diff --git a/gorgone/docs/modules/plugins/scom.md b/gorgone/docs/modules/plugins/scom.md new file mode 100644 index 00000000000..c7e7dc98c6c --- /dev/null +++ b/gorgone/docs/modules/plugins/scom.md @@ -0,0 +1,90 @@ +# Microsoft SCOM + +## Description + +This module aims to retreive alerts from Microsoft SCOM and store them in Centreon DSM slots. + +## Configuration + +| Directive | Description | Default value | +| :- | :- | :- | +| dsmclient_bin | Path to the Centreon DSM client | /usr/share/centreon/bin/dsmclient.pl| +| centcore_cmd | Path to centcore command file | /var/lib/centreon/centcore.cmd | +| check_containers_time | Time in seconds between two containers synchronisation | 3600 | + +#### Example + +```yaml +name: scom +package: "gorgone::modules::plugins::scom::hooks" +enable: false +check_containers_time: 3600 +dsmclient_bin: /usr/share/centreon/bin/dsmclient.pl +centcore_cmd: /var/lib/centreon/centcore.cmd +``` + +Add an entry in the `containers` table with the following attributes per SCOM server: + +| Directive | Description | +| :------------ | :---------- | +| name | Name of the SCOM configuration entrie | +| api_version | SCOM API version | +| url | URL of the SCOM API | +| username | Username to connect to SCOM API | +| password | Username's password | +| httpauth | API authentification type | +| resync_time | Time in seconds between two SCOM/Centreon synchronisations | +| dsmhost | Name of the Centreon host to link alerts to | +| dsmslot | Name of the Centreon DSM slots to link alerts to | +| dsmmacro | Name of the Centreon DSM macro to fill | +| dsmalertmessage | Output template for Centreon DSM service when there is an alert | +| dsmrecoverymessage | Output template for Centreon DSM service when alert is recovered | +| curlopts | Options table for Curl library | + +#### Example + +```yaml +containers: + - name: SCOM_prod + api_version: 2016 + url: "http://scomserver/api/" + username: user + password: pass + httpauth: basic + resync_time: 300 + dsmhost: ADH3 + dsmslot: Scom-% + dsmmacro: ALARM_ID + dsmalertmessage: "%{monitoringobjectdisplayname} %{name}" + dsmrecoverymessage: slot ok + curlopts: + CURLOPT_SSL_VERIFYPEER: 0 +``` + +## Events + +| Event | Description | +| :- | :- | +| SCOMREADY | Internal event to notify the core | +| SCOMRESYNC | Synchronise SCOM and Centreon realtime | + +## API + +### Force synchronisation between SCOM endpoints and Centreon realtime + +| Endpoint | Method | +| :- | :- | +| /api/plugins/scom/resync | `GET` | + +#### Headers + +| Header | Value | +| :- | :- | +| Accept | application/json | + +#### Example + +```bash +curl --request POST "https://hostname:8443/api/plugins/scom/resync" \ + --header "Accept: application/json" +``` From a2398831ac48ec254bd5e519a035451657468e46 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 27 Sep 2019 16:56:26 +0200 Subject: [PATCH 197/948] enh(docs): enh docs --- gorgone/docs/modules/plugins/newtest.md | 40 ++++++++++++------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/gorgone/docs/modules/plugins/newtest.md b/gorgone/docs/modules/plugins/newtest.md index 1dd16c8559f..23675fd3974 100644 --- a/gorgone/docs/modules/plugins/newtest.md +++ b/gorgone/docs/modules/plugins/newtest.md @@ -75,27 +75,27 @@ Add an entry in the `containers` table with the following attributes per NWC def ```yaml containers: - name: nwc_1 - resync_time: 300 - nmc_endpoint: "http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx" - username: user - password: pass - host_template: generic-active-host-custom - host_prefix: Robot-%s - service_template: generic-passive-service-custom - service_prefix: Scenario-%s - poller_name: Central - list_scenario_status: '{ "search": "All", "instances": [] }' + resync_time: 300 + nmc_endpoint: "http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx" + username: user + password: pass + host_template: generic-active-host-custom + host_prefix: Robot-%s + service_template: generic-passive-service-custom + service_prefix: Scenario-%s + poller_name: Central + list_scenario_status: '{ "search": "All", "instances": [] }' - name: nwc_2 - resync_time: 600 - nmc_endpoint: "http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx" - username: user - password: pass - host_template: generic-active-host-custom - host_prefix: Robot-%s - service_template: generic-passive-service-custom - service_prefix: Scenario-%s - poller_name: Central - list_scenario_status: '{ "search": "Robot", "instances": ["XXXX"] }' + resync_time: 600 + nmc_endpoint: "http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx" + username: user + password: pass + host_template: generic-active-host-custom + host_prefix: Robot-%s + service_template: generic-passive-service-custom + service_prefix: Scenario-%s + poller_name: Central + list_scenario_status: '{ "search": "Robot", "instances": ["XXXX"] }' ``` ## Events From 8b8b6047ddf5e7a482d1068e7c34c16c54df2e5b Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 27 Sep 2019 17:01:25 +0200 Subject: [PATCH 198/948] enh(docs): typo --- gorgone/docs/modules/plugins/scom.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/docs/modules/plugins/scom.md b/gorgone/docs/modules/plugins/scom.md index c7e7dc98c6c..6cf3dbb74c9 100644 --- a/gorgone/docs/modules/plugins/scom.md +++ b/gorgone/docs/modules/plugins/scom.md @@ -32,7 +32,7 @@ Add an entry in the `containers` table with the following attributes per SCOM se | url | URL of the SCOM API | | username | Username to connect to SCOM API | | password | Username's password | -| httpauth | API authentification type | +| httpauth | API authentication type | | resync_time | Time in seconds between two SCOM/Centreon synchronisations | | dsmhost | Name of the Centreon host to link alerts to | | dsmslot | Name of the Centreon DSM slots to link alerts to | From 04216e0b93dff3626afa314fb9070a2a789d58ef Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 30 Sep 2019 14:03:36 +0200 Subject: [PATCH 199/948] feat(docs): update docs --- gorgone/README.md | 24 +--- gorgone/docs/configuration.md | 84 ++++++++++++ gorgone/docs/getting_started.md | 143 +++++++++++++++++++++ gorgone/docs/guide.md | 111 ++-------------- gorgone/docs/migration.md | 83 ++++++++++++ gorgone/docs/modules.md | 21 +++ gorgone/docs/modules/centreon/broker.md | 2 +- gorgone/docs/modules/centreon/engine.md | 2 +- gorgone/docs/modules/centreon/legacycmd.md | 8 +- gorgone/docs/modules/core/action.md | 2 +- gorgone/docs/modules/core/dbcleaner.md | 4 +- gorgone/docs/modules/core/httpserver.md | 6 +- gorgone/docs/modules/core/proxy.md | 10 +- gorgone/docs/modules/plugins/newtest.md | 10 +- gorgone/docs/modules/plugins/scom.md | 6 +- 15 files changed, 373 insertions(+), 143 deletions(-) create mode 100644 gorgone/docs/configuration.md create mode 100644 gorgone/docs/getting_started.md create mode 100644 gorgone/docs/migration.md create mode 100644 gorgone/docs/modules.md diff --git a/gorgone/README.md b/gorgone/README.md index 3a3bc69740c..50ace2a0280 100644 --- a/gorgone/README.md +++ b/gorgone/README.md @@ -13,28 +13,12 @@ The daemon can be installed on Centreon environments like Centreon Central, Remo It uses ZeroMQ library. -To install it and understand the main principles, follow the [guide](docs/guide.md). +To install it follow the [Getting started](docs/getting_started.md) documentation. + +To understand the main principles of Gorgone protocol, follow the [guide](docs/guide.md). ## Modules The Centreon Gorgone project encloses several built-in modules. -See the list above: - -* Core - * [Action](docs/modules/core/action.md) - * [Cron](docs/modules/core/cron.md) - * [DB Cleaner](docs/modules/core/dbcleaner.md) - * [HTTP Server](docs/modules/core/httpserver.md) - * [Proxy](docs/modules/core/proxy.md) - * [Pull](docs/modules/core/pull.md) - * [Register](docs/modules/core/register.md) -* Centreon - * [Autodiscovery](docs/modules/centreon/autodiscovery.md) - * [Broker](docs/modules/centreon/broker.md) - * [Engine](docs/modules/centreon/engine.md) - * [Legacy Cmd](docs/modules/centreon/legacycmd.md) - * [Pollers](docs/modules/centreon/pollers.md) -* Plugins - * [Newtest](docs/modules/plugins/newtest.md) - * [Scom](docs/modules/plugins/scom.md) +See the full list [here](docs/modules.md). diff --git a/gorgone/docs/configuration.md b/gorgone/docs/configuration.md new file mode 100644 index 00000000000..9b8fd12e046 --- /dev/null +++ b/gorgone/docs/configuration.md @@ -0,0 +1,84 @@ +# Configuration + +| Directive | Description | +| :- | :- | +| name | Name of the configuration | +| description | Short string to decribe the configuration | +| database | Table to set Centreon databases data source names and credentials | +| gorgonecore | Table to set Gorgone main configuration | +| modules | Table to load and configuration Gorgone modules | + +## `database` + +Usefull in a Centreon Central installation to access Centreon databases. + +| Directive | Description | +| :- | :- | +| dsn | Data source name of the database | +| username | Username to access the database | +| password | Username's password | + +#### Example + +```yaml +database: + db_centreon: + dsn: "mysql:host=localhost;dbname=centreon" + username: centreon + password: centreon + db_centstorage: + dsn: "mysql:host=localhost;dbname=centreon_storage" + username: centreon + password: centreon +``` + +## `gorgonecore` + +| Directive | Description | Default value +| :- | :- | :- | +| internal_com_type | Type of the internal ZMQ socket | `ipc` | +| internal_com_path | Path to the internal ZMQ socket | `/tmp/gorgone/routing.ipc` | +| external_com_type | Type of the external ZMQ socket | `tcp` | +| external_com_path | Path to the external ZMQ socket | `*:5555` | +| timeout | Time in seconds before killing child processes when stopping Gorgone | `50` | +| gorgone_db_type | Type of the Gorgone database | `SQLite` | +| gorgone_db_name | Path and name of the database | `dbname=/var/lib/centreon/gorgone/gorgone.sdb` | +| gorgone_db_host | Hostname/IP address of the server hosting the database | | +| gorgone_db_port | Port of the database listener | | +| gorgone_db_user | Username to access the database | | +| gorgone_db_password | Username's password | | +| hostname | Hostname of the server running Gorgone | Result of *hostname* system function. | +| id | Identifier of server running Gorgone | None. Must be unique over all Gorgone daemons. | +| privkey | Path to the Gorgone core private key | `keys/central/privkey.pem` | +| cipher | Cipher used for encryption | `Cipher::AES` | +| keysize | Size in bytes of the encryption key | `32` | +| vector | Encryption vector | `0123456789012345` | +| proxy_name | Name of the proxy module definition | `proxy` (loaded internally) | + +#### Example with hardcoded default values + +```yaml +gorgonecore: + internal_com_type: ipc + internal_com_path: /tmp/gorgone/routing.ipc + external_com_type: tcp + external_com_path: "*:5555" + timeout: 50 + gorgone_db_type: SQLite + gorgone_db_name: dbname=/var/lib/centreon/gorgone/gorgone.sdb + gorgone_db_host: + gorgone_db_port: + gorgone_db_user: + gorgone_db_password: + hostname: + id: + privkey: keys/central/privkey.pem + cipher: "Cipher::AES" + keysize: 32 + vector: 0123456789012345 + proxy_name: gproxy +``` + +## `modules` + +See the `configuration` titles of the modules configurations listed [here](../docs/modules.md). diff --git a/gorgone/docs/getting_started.md b/gorgone/docs/getting_started.md new file mode 100644 index 00000000000..ec57f0da431 --- /dev/null +++ b/gorgone/docs/getting_started.md @@ -0,0 +1,143 @@ +# Getting started + +## Installation + +### From package + +Using Centreon standard yum repositories, execute the following command to install Gorgone: + +```bash +yum install centreon-gorgone +``` + +### From sources + +Using Github project, execute the following command to retrieve Gorgone source code: + +```bash +git clone https://github.com/centreon/centreon-gorgone +``` + +The daemon uses the following Perl modules: + +* Repository 'centreon-stable': + * ZMQ::LibZMQ4 + * UUID +* Repository 'centos base': + * JSON::XS + * YAML + * DBD::SQLite + * DBD::mysql + * Crypt::CBC + * HTTP::Daemon + * HTTP::Status + * MIME::Base64 +* Repository 'epel': + * HTTP::Daemon::SSL + * Schedule::Cron +* From offline packages: + * Crypt::Cipher::AES (module CryptX) + * Crypt::PK::RSA (module CryptX) + * Crypt::PRNG (module CryptX) + +Execute the following commands to install them all: + +```bash +yum install 'perl(Schedule::Cron)' 'perl(Crypt::CBC)' 'perl(ZMQ::LibZMQ4)' 'perl(JSON::XS)' 'perl(YAML)' 'perl(DBD::SQLite)' 'perl(DBD::mysql)' 'perl(UUID)' 'perl(HTTP::Daemon)' 'perl(HTTP::Daemon::SSL)' 'perl(HTTP::Status)' 'perl(MIME::Base64)' +yum install packaging/packages/perl-CryptX-0.064-1.el7.x86_64 +``` + +## Configuration + +You can retrieve `centcore` configuration, i.e. database hostname and credentials in */etc/centreon/conf.pm*, and build a minimal configuration by applying the [migration procedure](../docs/migration.md). + +All directives are available [here](../docs/configuration.md). + +## Create the database + +Gorgone uses a SQLite database to store all events messages. + +If it does not exist, the daemon will automatically create it in the path set by the `gorgone_db_name` configuration directive. + +However, you can manualy create it with the database schema: + +```bash +sqlite3 -init schema/gorgone_database.sql /var/lib/centreon/gorgone/gorgone.sdb +``` + +Database schema: + +```sql +CREATE TABLE IF NOT EXISTS `gorgone_identity` ( + `id` INTEGER PRIMARY KEY, + `ctime` int(11) DEFAULT NULL, + `identity` varchar(2048) DEFAULT NULL, + `key` varchar(4096) DEFAULT NULL, + `parent` int(11) DEFAULT '0' +); + +CREATE INDEX IF NOT EXISTS idx_gorgone_identity ON gorgone_identity (identity); +CREATE INDEX IF NOT EXISTS idx_gorgone_parent ON gorgone_identity (parent); + +CREATE TABLE IF NOT EXISTS `gorgone_history` ( + `id` INTEGER PRIMARY KEY, + `token` varchar(2048) DEFAULT NULL, + `code` int(11) DEFAULT NULL, + `etime` int(11) DEFAULT NULL, + `ctime` int(11) DEFAULT NULL, + `instant` int(11) DEFAULT '0', + `data` TEXT DEFAULT NULL +); + +CREATE INDEX IF NOT EXISTS idx_gorgone_history_id ON gorgone_history (id); +CREATE INDEX IF NOT EXISTS idx_gorgone_history_token ON gorgone_history (token); +CREATE INDEX IF NOT EXISTS idx_gorgone_history_etime ON gorgone_history (etime); +CREATE INDEX IF NOT EXISTS idx_gorgone_history_code ON gorgone_history (code); +CREATE INDEX IF NOT EXISTS idx_gorgone_history_ctime ON gorgone_history (ctime); +CREATE INDEX IF NOT EXISTS idx_gorgone_history_instant ON gorgone_history (instant); + +CREATE TABLE IF NOT EXISTS `gorgone_synchistory` ( + `id` int(11) DEFAULT NULL, + `ctime` int(11) DEFAULT NULL, + `last_id` int(11) DEFAULT NULL +); + +CREATE INDEX IF NOT EXISTS idx_gorgone_synchistory_id ON gorgone_synchistory (id); +``` + +## Launch the daemon + +If you are using the package, just launch the service as below: + +```bash +systemctl start centreon-gorgone +``` + +Make sure the daemon is running: + +```bash +$ systemctl status centreon-gorgone +● centreon-gorgone.service - Centreon Gorgone + Loaded: loaded (/etc/systemd/system/centreon-gorgone.service; disabled; vendor preset: disabled) + Active: active (running) since Mon 2019-09-30 09:36:19 CEST; 2min 29s ago + Main PID: 5168 (perl) + CGroup: /system.slice/centreon-gorgone.service + ├─5168 /usr/bin/perl /usr/bin/gorgoned --config=/etc/centreon/gorgoned.yml --logfile=/var/log/centreon/gorgoned.log --severity=error + ├─5175 gorgone-dbcleaner + ├─5182 gorgone-action + ├─5187 gorgone-pollers + ├─5190 gorgone-legacycmd + ├─5203 gorgone-proxy + ├─5204 gorgone-proxy + ├─5205 gorgone-proxy + ├─5206 gorgone-proxy + └─5207 gorgone-proxy + +Sep 30 09:36:19 cga-centreon-19-10.int.centreon.com systemd[1]: Started Centreon Gorgone. +``` + +If you are using the sources, execute the following command: + +```bash +perl gorgoned --config=config/gorgoned.yml --severity=error +``` diff --git a/gorgone/docs/guide.md b/gorgone/docs/guide.md index 8e534c1c78e..1cd25e6b721 100644 --- a/gorgone/docs/guide.md +++ b/gorgone/docs/guide.md @@ -1,96 +1,11 @@ -# Centreon Gorgone - -## Installation - -The daemon uses the following Perl modules: - -* Repository 'centreon-stable': - * ZMQ::LibZMQ4 - * UUID -* Repository 'centos base': - * JSON::XS - * YAML - * DBD::SQLite - * DBD::mysql - * Crypt::CBC - * HTTP::Daemon - * HTTP::Status - * MIME::Base64 -* Repository 'epel': - * HTTP::Daemon::SSL - * Schedule::Cron -* From offline packages: - * Crypt::Cipher::AES (module CryptX) - * Crypt::PK::RSA (module CryptX) - * Crypt::PRNG (module CryptX) - -Execute the following commands to install them all: - -```bash -yum install 'perl(Schedule::Cron)' 'perl(Crypt::CBC)' 'perl(ZMQ::LibZMQ4)' 'perl(JSON::XS)' 'perl(YAML)' 'perl(DBD::SQLite)' 'perl(DBD::mysql)' 'perl(UUID)' 'perl(HTTP::Daemon)' 'perl(HTTP::Daemon::SSL)' 'perl(HTTP::Status)' 'perl(MIME::Base64)' -yum install perl-CryptX-0.064-1.el7.x86_64 -``` - -Create sqlite database with the database schema: - -```bash -sqlite3 -init schema/gorgone_database.sql /tmp/gorgone.sdb -``` - -Database schema: - -```sql -CREATE TABLE IF NOT EXISTS `gorgone_identity` ( - `id` INTEGER PRIMARY KEY, - `ctime` int(11) DEFAULT NULL, - `identity` varchar(2048) DEFAULT NULL, - `key` varchar(4096) DEFAULT NULL, - `parent` int(11) DEFAULT '0' -); - -CREATE INDEX IF NOT EXISTS idx_gorgone_identity ON gorgone_identity (identity); -CREATE INDEX IF NOT EXISTS idx_gorgone_parent ON gorgone_identity (parent); - -CREATE TABLE IF NOT EXISTS `gorgone_history` ( - `id` INTEGER PRIMARY KEY, - `token` varchar(2048) DEFAULT NULL, - `code` int(11) DEFAULT NULL, - `etime` int(11) DEFAULT NULL, - `ctime` int(11) DEFAULT NULL, - `instant` int(11) DEFAULT '0', - `data` TEXT DEFAULT NULL -); - -CREATE INDEX IF NOT EXISTS idx_gorgone_history_id ON gorgone_history (id); -CREATE INDEX IF NOT EXISTS idx_gorgone_history_token ON gorgone_history (token); -CREATE INDEX IF NOT EXISTS idx_gorgone_history_etime ON gorgone_history (etime); -CREATE INDEX IF NOT EXISTS idx_gorgone_history_code ON gorgone_history (code); -CREATE INDEX IF NOT EXISTS idx_gorgone_history_ctime ON gorgone_history (ctime); -CREATE INDEX IF NOT EXISTS idx_gorgone_history_instant ON gorgone_history (instant); - -CREATE TABLE IF NOT EXISTS `gorgone_synchistory` ( - `id` int(11) DEFAULT NULL, - `ctime` int(11) DEFAULT NULL, - `last_id` int(11) DEFAULT NULL -); - -CREATE INDEX IF NOT EXISTS idx_gorgone_synchistory_id ON gorgone_synchistory (id); -``` - -Launch the daemon: - -```bash -perl gorgoned --config=config/gorgoned.yml --severity=debug -``` - -## Gorgone protocol +# Gorgone protocol "gorgone-core" (main mandatory module) can have 2 interfaces: * Internal: uncrypted dialog (used by internal modules. Commonly in ipc) * External: crypted dialog (used by third-party clients. Commonly in tcp) -### Handshake scenario +## Handshake scenario Third-party clients have to use the ZeroMQ library and the following process: @@ -126,7 +41,7 @@ If a third-party client with the same identity try to open a new session, the se Be sure to have the same parameters to crypt/uncrypt with the symmetric key. Commonly: 'AES' cipher, keysize of 32 bytes, vector '0123456789012345'. -### Client request +## Client request After a successful handshake, client requests use the following syntax: @@ -153,9 +68,9 @@ For each client requests, the server get an immediate response: There are some exceptions for 'CONSTATUS' and 'GETLOG' requests. -### Core requests +## Core requests -#### CONSTATUS +### CONSTATUS The following request gives you a table with the last ping response of "gorgoned" nodes connected to the server. The command is useful to know if some pollers are disconnected. @@ -198,7 +113,7 @@ The 'last_ping' value is the date when the daemon have launched a PING broadcast The 'entries' values are the last time the poller have responded to the PING broadcast. -#### GETLOG +### GETLOG The following request gives you the capability to follow your requests. "gorgone" protocol is asynchronous. @@ -268,7 +183,7 @@ A client can force a synchronization with the following request: The client have to set the target ID (it can be the Poller ID). -#### PUTLOG +### PUTLOG The request shouldn't be used by third-party program. It's commonly used by the internal modules. @@ -278,7 +193,7 @@ The client request: [PUTLOG] [TOKEN] [TARGET] { "code": xxx, "etime": "xxx", "token": "xxxx", "data": { some_datas } } ``` -#### REGISTERNODES +### REGISTERNODES The request shouldn't be used by third-party program. It's commonly used by the internal modules. @@ -297,7 +212,7 @@ The client request (no carriage returns. only for reading): } ``` -### Common codes +## Common codes Common code responses for all module requests: @@ -307,9 +222,9 @@ Common code responses for all module requests: Modules can have extra codes. -## FAQ +# FAQ -### Which modules should I enable ? +## Which modules should I enable ? A Central with gorgoned should have the following modules: @@ -323,7 +238,7 @@ A Poller with gorgoned should have the following modules: * action, * pull (if the connection to the Central should be opened by the Poller). -### I want to create a client. How should I proceed ? +## I want to create a client. How should I proceed ? First, you must choose a language which can use ZeroMQ library and have some knowledge about ZeroMQ. @@ -339,4 +254,4 @@ I recommend the following scenario: 3. Do a 'GETLOG' request with the token to get the result, 4. Repeat actions 2 and 3 if you don't have a result yet (you should stop after X retries). -You can inspire from the code of 'test-client.pl'. +You can inspire from the code of '[test-client.pl](../contrib/test-client.pl)'. diff --git a/gorgone/docs/migration.md b/gorgone/docs/migration.md new file mode 100644 index 00000000000..cc026b77a23 --- /dev/null +++ b/gorgone/docs/migration.md @@ -0,0 +1,83 @@ +# Migrate from Centreon `centcore` + +To build a configuration file based on */etc/centreon/conf.pm*, execute the following command line. + +If using package: + +```bash +$ perl /usr/local/bin/gorgone_config_init.pl +2019-09-30 11:00:00 - INFO - file '/etc/centreon/gorgoned.yml' created success +``` + +If using sources: + +```bash +$ perl ./contrib/gorgone_config_init.pl +2019-09-30 11:00:00 - INFO - file '/etc/centreon/gorgoned.yml' created success +``` + +As a result the following configuration will be created in */etc/centreon/gorgoned.yml*: + +```yaml +name: gorgoned +description: Configuration init by gorgone_config_init +database: + db_centreon: + dsn: "mysql:host=localhost;port=3306;dbname=centreon" + username: "centreon" + password: "centreon" + db_centstorage: + dsn: "mysql:host=localhost;port=3306;dbname=centreon_storage" + username: "centreon" + password: "centreon" +gorgonecore: + hostname: + id: +modules: + - name: httpserver + package: gorgone::modules::core::httpserver::hooks + enable: false + address: 0.0.0.0 + port: 8443 + ssl: true + ssl_cert_file: /etc/pki/tls/certs/server-cert.pem + ssl_key_file: /etc/pki/tls/server-key.pem + auth: + user: admin + password: password + + - name: cron + package: gorgone::modules::core::cron::hooks + enable: false + + - name: action + package: gorgone::modules::core::action::hooks + enable: true + + - name: proxy + package: gorgone::modules::core::proxy::hooks + enable: true + + - name: pollers + package: gorgone::modules::centreon::pollers::hooks + enable: true + + - name: broker + package: gorgone::modules::centreon::broker::hooks + enable: false + cache_dir: "/var/lib/centreon/broker-stats/" + cron: + - id: broker_stats + timespec: "*/2 * * * *" + action: BROKERSTATS + parameters: + timeout: 10 + + - name: legacycmd + package: gorgone::modules::centreon::legacycmd::hooks + enable: true + cmd_file: "/var/lib/centreon/centcore.cmd" + cache_dir: "/var/cache/centreon/" + cache_dir_trap: "/etc/snmp/centreon_traps/" + remote_dir: "/var/lib/centreon/remote-data/" +``` diff --git a/gorgone/docs/modules.md b/gorgone/docs/modules.md new file mode 100644 index 00000000000..4e1d34f9fc5 --- /dev/null +++ b/gorgone/docs/modules.md @@ -0,0 +1,21 @@ +# Modules + +List of the available `modules`: + +* Core + * [Action](../docs/modules/core/action.md) + * [Cron](../docs/modules/core/cron.md) + * [DB Cleaner](../docs/modules/core/dbcleaner.md) + * [HTTP Server](../docs/modules/core/httpserver.md) + * [Proxy](../docs/modules/core/proxy.md) + * [Pull](../docs/modules/core/pull.md) + * [Register](../docs/modules/core/register.md) +* Centreon + * [Autodiscovery](../docs/modules/centreon/autodiscovery.md) + * [Broker](../docs/modules/centreon/broker.md) + * [Engine](../docs/modules/centreon/engine.md) + * [Legacy Cmd](../docs/modules/centreon/legacycmd.md) + * [Pollers](../docs/modules/centreon/pollers.md) +* Plugins + * [Newtest](../docs/modules/plugins/newtest.md) + * [Scom](../docs/modules/plugins/scom.md) diff --git a/gorgone/docs/modules/centreon/broker.md b/gorgone/docs/modules/centreon/broker.md index 48a4528393a..eb9c0e97e46 100644 --- a/gorgone/docs/modules/centreon/broker.md +++ b/gorgone/docs/modules/centreon/broker.md @@ -8,7 +8,7 @@ This module aims to deal with Centreon Broker daemon. | Directive | Description | Default value | | :- | :- | :- | -| cache_dir | Path to the Centreon Broker statistics directory (local) use to store target's broker statistics | /var/lib/centreon/broker-stats/ | +| cache_dir | Path to the Centreon Broker statistics directory (local) use to store target's broker statistics | `/var/lib/centreon/broker-stats/` | The configuration needs a cron definition to unsure that statistics collection will be done cyclically. diff --git a/gorgone/docs/modules/centreon/engine.md b/gorgone/docs/modules/centreon/engine.md index 90e538d1fe5..a5997bf2bc1 100644 --- a/gorgone/docs/modules/centreon/engine.md +++ b/gorgone/docs/modules/centreon/engine.md @@ -8,7 +8,7 @@ This module aims to provide a bridge to communicate with Centreon Engine daemon. | Directive | Description | Default value | | :- | :- | :- | -| command_file | Path to the Centreon Engine command file pipe | /var/lib/centreon-engine/rw/centengine.cmd | +| command_file | Path to the Centreon Engine command file pipe | `/var/lib/centreon-engine/rw/centengine.cmd` | #### Example diff --git a/gorgone/docs/modules/centreon/legacycmd.md b/gorgone/docs/modules/centreon/legacycmd.md index 617a6226db9..fc97a91a766 100644 --- a/gorgone/docs/modules/centreon/legacycmd.md +++ b/gorgone/docs/modules/centreon/legacycmd.md @@ -16,10 +16,10 @@ The module relies on the following modules to process commands: | Directive | Description | Default value | | :- | :- | :- | -| cmd_file | *Command file* to read commands from | /var/lib/centreon/centcore.cmd | -| cache_dir | Directory where to process Centreon configuration files | /var/cache/centreon/ | -| cache_dir_trap | Directory where to process Centreontrapd databases | /etc/snmp/centreon_traps/ | -| remote_dir | Directory where to export Remote Servers configuration | /var/lib/centreon/remote-data/ | +| cmd_file | *Command file* to read commands from | `/var/lib/centreon/centcore.cmd` | +| cache_dir | Directory where to process Centreon configuration files | `/var/cache/centreon/` | +| cache_dir_trap | Directory where to process Centreontrapd databases | `/etc/snmp/centreon_traps/` | +| remote_dir | Directory where to export Remote Servers configuration | `/var/lib/centreon/remote-data/` | #### Example diff --git a/gorgone/docs/modules/core/action.md b/gorgone/docs/modules/core/action.md index 55b292077c4..c7102485a26 100644 --- a/gorgone/docs/modules/core/action.md +++ b/gorgone/docs/modules/core/action.md @@ -8,7 +8,7 @@ This module aims to execute actions on the server running the Gorgone daemon or | Directive | Description | Default value | | :- | :- | :- | -| command_timeout | Time in seconds before a command is considered timed out | 30 | +| command_timeout | Time in seconds before a command is considered timed out | `30` | #### Example diff --git a/gorgone/docs/modules/core/dbcleaner.md b/gorgone/docs/modules/core/dbcleaner.md index c9a8c6675dd..d42ac06a170 100644 --- a/gorgone/docs/modules/core/dbcleaner.md +++ b/gorgone/docs/modules/core/dbcleaner.md @@ -10,8 +10,8 @@ The module is loaded by default. Adding it to the configuration will overload da | Directive | Description | Default value | | :- | :- | :- | -| purge_sessions_time | Time in seconds before deleting sessions in the `gorgone_identity` table | 3600 | -| purge_history_time | Time in seconds before deleting history in the `gorgone_history` table | 604800 | +| purge_sessions_time | Time in seconds before deleting sessions in the `gorgone_identity` table | `3600` | +| purge_history_time | Time in seconds before deleting history in the `gorgone_history` table | `604800` | #### Example diff --git a/gorgone/docs/modules/core/httpserver.md b/gorgone/docs/modules/core/httpserver.md index 4cb40c9e242..bd1920f54d1 100644 --- a/gorgone/docs/modules/core/httpserver.md +++ b/gorgone/docs/modules/core/httpserver.md @@ -10,9 +10,9 @@ It relies on a core API module to server Gorgone events and can dispatch any oth | Directive | Description | Default value | | :- | :- | :- | -| address | IP address for the server to bind to | 0.0.0.0 | -| port | Port on which the server will listen to requests | 8080 | -| ssl | Boolean to enable SSL terminaison | false | +| address | IP address for the server to bind to | `0.0.0.0` | +| port | Port on which the server will listen to requests | `8080` | +| ssl | Boolean to enable SSL terminaison | `false` | | ssl_cert_file | Path to the SSL certificate (if SSL enabled) | | | ssl_key_file | Path to the SSL key (if SSL enabled) | | | auth | Basic credentials to access the server | | diff --git a/gorgone/docs/modules/core/proxy.md b/gorgone/docs/modules/core/proxy.md index 26594c37d23..8fb24cb6f6f 100644 --- a/gorgone/docs/modules/core/proxy.md +++ b/gorgone/docs/modules/core/proxy.md @@ -14,11 +14,11 @@ A SSH client library make routing to non-gorgoned targets possible. | Directive | Description | Default value | | :- | :- | :- | -| pool | Number of childs to instantiate to process events | 5 | -| synchistory_time | Time in seconds between two logs synchronisation | 60 | -| synchistory_timeout | Time in seconds before logs synchronisation is considered timed out | 30 | -| ping | Time in seconds between two target pings | 60 | -| pong_discard_timeout | Time in seconds before a target is considered dead | 300 | +| pool | Number of childs to instantiate to process events | `5` | +| synchistory_time | Time in seconds between two logs synchronisation | `60` | +| synchistory_timeout | Time in seconds before logs synchronisation is considered timed out | `30` | +| ping | Time in seconds between two target pings | `60` | +| pong_discard_timeout | Time in seconds before a target is considered dead | `300` | #### Example diff --git a/gorgone/docs/modules/plugins/newtest.md b/gorgone/docs/modules/plugins/newtest.md index 23675fd3974..4f9fd90fb8c 100644 --- a/gorgone/docs/modules/plugins/newtest.md +++ b/gorgone/docs/modules/plugins/newtest.md @@ -31,14 +31,14 @@ Here are the steps done by one process: | Directive | Description | Default value | | :- | :- | :- | -| clapi_command | Path to the CLAPI binary | /usr/bin/centreon | -| clapi_timeout | Time in seconds before CLAPI command execution is considered timed out | 10 | +| clapi_command | Path to the CLAPI binary | `/usr/bin/centreon` | +| clapi_timeout | Time in seconds before CLAPI command execution is considered timed out | `10` | | clapi_username | CLAPI username | | | clapi_password | CLAPI username's password | | -| centcore_cmd | Path to centcore command file | /var/lib/centreon/centcore.cmd | +| centcore_cmd | Path to centcore command file | `/var/lib/centreon/centcore.cmd` | | clapi_action_applycfg | CLAPI action used to apply Poller configuration | | -| clapi_generate_config_timeout | Time in seconds before the configuration generation is considered timed out | 180 | -| check_containers_time | Time in seconds between two containers synchronisation | 3600 | +| clapi_generate_config_timeout | Time in seconds before the configuration generation is considered timed out | `180` | +| check_containers_time | Time in seconds between two containers synchronisation | `3600` | #### Example diff --git a/gorgone/docs/modules/plugins/scom.md b/gorgone/docs/modules/plugins/scom.md index 6cf3dbb74c9..9b7e0004f2e 100644 --- a/gorgone/docs/modules/plugins/scom.md +++ b/gorgone/docs/modules/plugins/scom.md @@ -8,9 +8,9 @@ This module aims to retreive alerts from Microsoft SCOM and store them in Centre | Directive | Description | Default value | | :- | :- | :- | -| dsmclient_bin | Path to the Centreon DSM client | /usr/share/centreon/bin/dsmclient.pl| -| centcore_cmd | Path to centcore command file | /var/lib/centreon/centcore.cmd | -| check_containers_time | Time in seconds between two containers synchronisation | 3600 | +| dsmclient_bin | Path to the Centreon DSM client | `/usr/share/centreon/bin/`dsmclient.pl| +| centcore_cmd | Path to centcore command file | `/var/lib/centreon/centcore.cmd` | +| check_containers_time | Time in seconds between two containers synchronisation | `3600` | #### Example From ad65b8fbc2c6c5f2e53b1295ed62f01b686163fd Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 30 Sep 2019 14:16:43 +0200 Subject: [PATCH 200/948] feat(docs): update docs --- gorgone/docs/configuration.md | 8 ++++---- gorgone/docs/migration.md | 2 +- gorgone/docs/modules.md | 2 +- gorgone/docs/modules/centreon/legacycmd.md | 6 ++++-- gorgone/docs/modules/plugins/newtest.md | 8 ++++---- gorgone/docs/modules/plugins/scom.md | 2 +- 6 files changed, 15 insertions(+), 13 deletions(-) diff --git a/gorgone/docs/configuration.md b/gorgone/docs/configuration.md index 9b8fd12e046..d9c4c41c6a1 100644 --- a/gorgone/docs/configuration.md +++ b/gorgone/docs/configuration.md @@ -8,7 +8,7 @@ | gorgonecore | Table to set Gorgone main configuration | | modules | Table to load and configuration Gorgone modules | -## `database` +## *database* Usefull in a Centreon Central installation to access Centreon databases. @@ -32,7 +32,7 @@ database: password: centreon ``` -## `gorgonecore` +## *gorgonecore* | Directive | Description | Default value | :- | :- | :- | @@ -79,6 +79,6 @@ gorgonecore: proxy_name: gproxy ``` -## `modules` +## *modules* -See the `configuration` titles of the modules configurations listed [here](../docs/modules.md). +See the *configuration* titles of the modules documentations listed [here](../docs/modules.md). diff --git a/gorgone/docs/migration.md b/gorgone/docs/migration.md index cc026b77a23..2f701494997 100644 --- a/gorgone/docs/migration.md +++ b/gorgone/docs/migration.md @@ -1,4 +1,4 @@ -# Migrate from Centreon `centcore` +# Migrate from Centreon *centcore* To build a configuration file based on */etc/centreon/conf.pm*, execute the following command line. diff --git a/gorgone/docs/modules.md b/gorgone/docs/modules.md index 4e1d34f9fc5..e62cb385ec4 100644 --- a/gorgone/docs/modules.md +++ b/gorgone/docs/modules.md @@ -1,6 +1,6 @@ # Modules -List of the available `modules`: +List of the available modules: * Core * [Action](../docs/modules/core/action.md) diff --git a/gorgone/docs/modules/centreon/legacycmd.md b/gorgone/docs/modules/centreon/legacycmd.md index fc97a91a766..89adb43e782 100644 --- a/gorgone/docs/modules/centreon/legacycmd.md +++ b/gorgone/docs/modules/centreon/legacycmd.md @@ -2,9 +2,9 @@ ## Description -This module aims to mimick the behaviour of the antique `centcore` daemon. +This module aims to mimick the behaviour of the antique *centcore* daemon. -As for `centcore`, it reads a file (called command file) and process every commands that it knows of. +As for *centcore*, it reads a file (called command file) and process every commands that it knows of. The module relies on the following modules to process commands: @@ -17,6 +17,7 @@ The module relies on the following modules to process commands: | Directive | Description | Default value | | :- | :- | :- | | cmd_file | *Command file* to read commands from | `/var/lib/centreon/centcore.cmd` | +| cmd_dir | Directory where to watch for *command files* | `/var/lib/centreon/` | | cache_dir | Directory where to process Centreon configuration files | `/var/cache/centreon/` | | cache_dir_trap | Directory where to process Centreontrapd databases | `/etc/snmp/centreon_traps/` | | remote_dir | Directory where to export Remote Servers configuration | `/var/lib/centreon/remote-data/` | @@ -28,6 +29,7 @@ name: legacycmd package: "gorgone::modules::centreon::legacycmd::hooks" enable: true cmd_file: "/var/lib/centreon/centcore.cmd" +cmd_dir: "/var/lib/centreon/" cache_dir: "/var/cache/centreon/" cache_dir_trap: "/etc/snmp/centreon_traps/" remote_dir: "/var/lib/centreon/remote-data/" diff --git a/gorgone/docs/modules/plugins/newtest.md b/gorgone/docs/modules/plugins/newtest.md index 4f9fd90fb8c..4349f3d830c 100644 --- a/gorgone/docs/modules/plugins/newtest.md +++ b/gorgone/docs/modules/plugins/newtest.md @@ -6,7 +6,7 @@ This module aims to retrieve Newtest services. It uses the Newtest webservice in order to connect and retrieve the informations of one (or more) Newtest Management Console (NMC). -By default `newtest` starts X processes (it depends of the configuration). +By default *newtest* starts X processes (it depends of the configuration). Here are the steps done by one process: @@ -18,7 +18,7 @@ Here are the steps done by one process: 4. Get the last status of scenarios from the NMC, -5. Submit the result to Centreon through "centcore". +5. Submit the result to Centreon through *centcore*. #### Requirements @@ -54,7 +54,7 @@ clapi_action_applycfg: POLLERRELOAD centcore_cmd: /var/lib/centreon/centcore.cmd ``` -Add an entry in the `containers` table with the following attributes per NWC definition: +Add an entry in the *containers* table with the following attributes per NWC definition: | Directive | Description | | :------------ | :---------- | @@ -128,7 +128,7 @@ curl --request POST "https://hostname:8443/api/plugins/newtest/resync" \ ## Troubleshooting -It is possible to get this kind of error in logs of `newtest`: +It is possible to get this kind of error in logs of *newtest*: ```bash die: syntax error at line 1, column 0, byte 0 at /usr/lib/perl5/vendor_perl/5.8.8/i386-linux-thread-multi/XML/Parser.pm line 189 diff --git a/gorgone/docs/modules/plugins/scom.md b/gorgone/docs/modules/plugins/scom.md index 9b7e0004f2e..3586a9876a1 100644 --- a/gorgone/docs/modules/plugins/scom.md +++ b/gorgone/docs/modules/plugins/scom.md @@ -23,7 +23,7 @@ dsmclient_bin: /usr/share/centreon/bin/dsmclient.pl centcore_cmd: /var/lib/centreon/centcore.cmd ``` -Add an entry in the `containers` table with the following attributes per SCOM server: +Add an entry in the *containers* table with the following attributes per SCOM server: | Directive | Description | | :------------ | :---------- | From e0b28fc859f7e0525f8c4582974782b118aee985 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 30 Sep 2019 18:28:21 +0200 Subject: [PATCH 201/948] enh(config): review config examples --- gorgone/config/gorgoned-central-ssh.yml | 25 ---- gorgone/config/gorgoned-central-zmq.yml | 11 -- gorgone/config/gorgoned-poller.yml | 9 +- gorgone/config/gorgoned-remote-ssh.yml | 25 ---- gorgone/config/gorgoned-remote-zmq.yml | 20 +-- gorgone/config/gorgoned.yml | 176 ------------------------ 6 files changed, 2 insertions(+), 264 deletions(-) delete mode 100644 gorgone/config/gorgoned.yml diff --git a/gorgone/config/gorgoned-central-ssh.yml b/gorgone/config/gorgoned-central-ssh.yml index 1dcaf879fba..4b413ac9e14 100644 --- a/gorgone/config/gorgoned-central-ssh.yml +++ b/gorgone/config/gorgoned-central-ssh.yml @@ -10,21 +10,7 @@ database: username: centreon password: centreon gorgonecore: - external_com_type: tcp - external_com_path: "*:5555" - # in seconds before sending kill signals (not gently) timeout: 50 - gorgone_db_type: SQLite - gorgone_db_name: dbname=/tmp/gorgone.sdb - privkey: keys/central/privkey.pem - cipher: "Cipher::AES" - # in bytes - keysize: 32 - # 16 bytes for AES - vector: 0123456789012345 - # JWK format export thumbprint SHA256 - authorized_clients: - - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 modules: - name: httpserver package: gorgone::modules::core::httpserver::hooks @@ -38,17 +24,6 @@ modules: user: admin password: password - - name: cron - package: gorgone::modules::core::cron::hooks - enable: true - cron: - - id: echo_date - timespec: "*/5 * * * *" - action: COMMAND - parameters: - command: "date >> /tmp/date.log" - timeout: 10 - - name: action package: gorgone::modules::core::action::hooks enable: true diff --git a/gorgone/config/gorgoned-central-zmq.yml b/gorgone/config/gorgoned-central-zmq.yml index cfd560ffa7e..89b136b309d 100644 --- a/gorgone/config/gorgoned-central-zmq.yml +++ b/gorgone/config/gorgoned-central-zmq.yml @@ -10,22 +10,11 @@ database: username: centreon password: centreon gorgonecore: - external_com_type: tcp - external_com_path: "*:5555" - # in seconds before sending kill signals (not gently) - timeout: 50 - gorgone_db_type: SQLite - gorgone_db_name: dbname=/tmp/gorgone.sdb id: 1 privkey: keys/central/privkey.pem cipher: "Cipher::AES" - # in bytes keysize: 32 - # 16 bytes for AES vector: 0123456789012345 - # JWK format export thumbprint SHA256 - authorized_clients: - - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 modules: - name: httpserver package: gorgone::modules::core::httpserver::hooks diff --git a/gorgone/config/gorgoned-poller.yml b/gorgone/config/gorgoned-poller.yml index 4cf7ec0589d..020ea0c9693 100644 --- a/gorgone/config/gorgoned-poller.yml +++ b/gorgone/config/gorgoned-poller.yml @@ -1,20 +1,13 @@ name: gorgoned-poller description: Configuration example in a full ZMQ environment for Poller server gorgonecore: + id: 2 external_com_type: tcp external_com_path: "*:5556" - # in seconds before sending kill signals (not gently) - timeout: 50 - gorgone_db_type: SQLite - gorgone_db_name: dbname=/tmp/gorgone.sdb - id: 2 privkey: keys/poller/privkey.pem cipher: "Cipher::AES" - # in bytes keysize: 32 - # 16 bytes for AES vector: 0123456789012345 - # JWK format export thumbprint SHA256 authorized_clients: - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 modules: diff --git a/gorgone/config/gorgoned-remote-ssh.yml b/gorgone/config/gorgoned-remote-ssh.yml index dc2b58b3ae7..731ab267e7f 100644 --- a/gorgone/config/gorgoned-remote-ssh.yml +++ b/gorgone/config/gorgoned-remote-ssh.yml @@ -10,33 +10,8 @@ database: username: centreon password: centreon gorgonecore: - external_com_type: tcp - external_com_path: "*:5556" - # in seconds before sending kill signals (not gently) timeout: 50 - gorgone_db_type: SQLite - gorgone_db_name: dbname=/tmp/gorgone.sdb - privkey: keys/central/privkey.pem - cipher: "Cipher::AES" - # in bytes - keysize: 32 - # 16 bytes for AES - vector: 0123456789012345 - # JWK format export thumbprint SHA256 - authorized_clients: - - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 modules: - - name: cron - package: gorgone::modules::core::cron::hooks - enable: true - cron: - - id: echo_date - timespec: "* * * * *" - action: COMMAND - parameters: - command: "date >> /tmp/date.log" - timeout: 10 - - name: action package: gorgone::modules::core::action::hooks enable: true diff --git a/gorgone/config/gorgoned-remote-zmq.yml b/gorgone/config/gorgoned-remote-zmq.yml index c2df2db1d38..2c5852139af 100644 --- a/gorgone/config/gorgoned-remote-zmq.yml +++ b/gorgone/config/gorgoned-remote-zmq.yml @@ -10,34 +10,16 @@ database: username: centreon password: centreon gorgonecore: + id: 4 external_com_type: tcp external_com_path: "*:5556" - # in seconds before sending kill signals (not gently) - timeout: 50 - gorgone_db_type: SQLite - gorgone_db_name: dbname=/tmp/gorgone.sdb - id: 4 privkey: keys/central/privkey.pem cipher: "Cipher::AES" - # in bytes keysize: 32 - # 16 bytes for AES vector: 0123456789012345 - # JWK format export thumbprint SHA256 authorized_clients: - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 modules: - - name: cron - package: gorgone::modules::core::cron::hooks - enable: true - cron: - - id: echo_date - timespec: "* * * * *" - action: COMMAND - parameters: - command: "date >> /tmp/date.log" - timeout: 10 - - name: action package: gorgone::modules::core::action::hooks enable: true diff --git a/gorgone/config/gorgoned.yml b/gorgone/config/gorgoned.yml deleted file mode 100644 index a8249ccd96f..00000000000 --- a/gorgone/config/gorgoned.yml +++ /dev/null @@ -1,176 +0,0 @@ -name: gorgoned -description: Configuration example with more directives -database: - db_centreon: - dsn: "mysql:host=localhost;dbname=centreon" - username: centreon - password: centreon - db_centstorage: - dsn: "mysql:host=localhost;dbname=centreon_storage" - username: centreon - password: centreon -gorgonecore: - #internal_com_type: ipc - #internal_com_path: /tmp/gorgone/routing.ipc - external_com_type: tcp - external_com_path: "*:5555" - # in seconds before sending kill signals (not gently) - #timeout: 50 - #gorgone_db_type: SQLite - #gorgone_db_name: dbname=/var/lib/centreon/gorgone/gorgone.sdb - #gorgone_db_host: - #gorgone_db_port: - #gorgone_db_user: - #gorgone_db_password: - # If not set. Use 'hostname' function. - hostname: - # If not set. - # Can be override by action: SETCOREID - id: - - privkey: keys/central/privkey.pem - cipher: "Cipher::AES" - # in bytes - keysize: 32 - # 16 bytes for AES - vector: 0123456789012345 - # JWK format export thumbprint SHA256 - authorized_clients: - - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 -modules: - - name: httpserver - package: gorgone::modules::core::httpserver::hooks - enable: true - address: 0.0.0.0 - port: 8443 - ssl: true - ssl_cert_file: /etc/pki/tls/certs/server-cert.pem - ssl_key_file: /etc/pki/tls/server-key.pem - auth: - user: admin - password: password - - - name: cron - package: gorgone::modules::core::cron::hooks - enable: true - cron: - - id: echo_date - timespec: "* * * * *" - action: COMMAND - parameters: - command: "date >> /tmp/date.log" - timeout: 10 - - - name: action - package: gorgone::modules::core::action::hooks - enable: true - - - name: proxy - package: gorgone::modules::core::proxy::hooks - enable: false - #pool: 5 - # sync history each 5 minutes - #synchistory_time: 300 - # how much time before the response is in timeout - #synchistory_timeout: 120 - # ping each X seconds - #ping: 60 - - - name: pollers - package: gorgone::modules::centreon::pollers::hooks - enable: false - - - name: register - package: gorgone::modules::core::register::hooks - enable: true - config_file: config/registernodes.yml - - - name: engine - package: gorgone::modules::centreon::engine::hooks - enable: true - command_file: "/var/lib/centreon-engine/rw/centengine.cmd" - - - name: autodiscovery - package: gorgone::modules::centreon::autodiscovery::hooks - enable: true - - - name: broker - package: gorgone::modules::centreon::broker::hooks - enable: false - cache_dir: "/var/lib/centreon/broker-stats/" - cron: - - id: broker_stats - timespec: "*/2 * * * *" - action: BROKERSTATS - parameters: - timeout: 10 - - - name: legacycmd - package: gorgone::modules::centreon::legacycmd::hooks - enable: true - cmd_file: "/var/lib/centreon/centcore.cmd" - cache_dir: "/var/cache/centreon/" - cache_dir_trap: "/etc/snmp/centreon_traps/" - remote_dir: "/var/lib/centreon/remote-data/" - - - name: scom - package: gorgone::modules::plugins::scom::hooks - enable: false - # in seconds - do purge for container also - check_containers_time: 3600 - dsmclient_bin: /usr/share/centreon/bin/dsmclient.pl - centcore_cmd: /var/lib/centreon/centcore.cmd - containers: - - name: toto - api_version: 2016 - url: "http://scomserver/api/" - username: toto - password: pass - httpauth: basic - resync_time: 300 - dsmhost: ADH3 - dsmslot: Scom-% - dsmmacro: ALARM_ID - dsmalertmessage: "%{monitoringobjectdisplayname} %{name}" - dsmrecoverymessage: slot ok - curlopts: - CURLOPT_SSL_VERIFYPEER: 0 - # - name: tutu - # url: http://scomserver2/ - # username: toto2 - # password: toto2 - # resync_time: 600 - - - name: newtest - package: gorgone::modules::plugins::newtest::hooks - enable: false - # in seconds - do purge for container also - check_containers_time: 3600 - clapi_command: /usr/bin/centreon - clapi_username: admin - clapi_password: centreon - clapi_action_applycfg: RELOAD - centcore_cmd: /var/lib/centreon/centcore.cmd - containers: - - name: toto - resync_time: 300 - nmc_endpoint: "http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx" - username: user - password: pass - host_template: generic-active-host-custom - host_prefix: Robot-%s - service_template: generic-passive-service-custom - service_prefix: Scenario-%s - poller_name: Central - list_scenario_status: '{ "search": "All", "instances": [] }' - - name: tutu - resync_time: 600 - nmc_endpoint: "http://__NMC_ADDRESS__/nws/managementconsoleservice.asmx" - username: user - password: pass - host_template: generic-active-host-custom - host_prefix: Robot-%s - service_template: generic-passive-service-custom - service_prefix: Scenario-%s - poller_name: Central - list_scenario_status: '{ "search": "Robot", "instances": ["XXXX"] }' From 293451aceac3aff8ac75718ad8baadf587bfb0b4 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 30 Sep 2019 18:29:15 +0200 Subject: [PATCH 202/948] feat(docs): add zmq client/server doc --- gorgone/docs/client_server_zmq.md | 92 +++++++++++++++++++++++++++ gorgone/docs/configuration.md | 9 ++- gorgone/docs/modules/core/register.md | 2 +- 3 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 gorgone/docs/client_server_zmq.md diff --git a/gorgone/docs/client_server_zmq.md b/gorgone/docs/client_server_zmq.md new file mode 100644 index 00000000000..730413c6e30 --- /dev/null +++ b/gorgone/docs/client_server_zmq.md @@ -0,0 +1,92 @@ +# Client/Server ZMQ communication + +When using ZMQ protocol, all communications are encrypted using symmetric-key encryption based on public/private keys from both client and server. + +In a Centreon context, the **client** is the Gorgone daemon running on the **Centreon Central**, the **servers** are the daemon running on **Pollers**. + +## Generate private and public keys + +On both client and server, generate RSA private and public keys. + +```bash +$ mkdir -p /etc/pki/gorgone/ +$ openssl genrsa -out /etc/pki/gorgone/privkey.pem 4092 +Generating RSA private key, 4092 bit long modulus +...................................++ +...........................................................................................................................................................................++ +e is 65537 (0x10001) +$ openssl rsa -in /etc/pki/gorgone/privkey.pem -out /etc/pki/gorgone/pubkey.pem -pubout -outform PEM +writing RSA key +$ chmod 644 /etc/pki/gorgone/* +``` + +Copy the server public key onto the client in a specific directory (for example */etc/pki/gorgone/*) + +## Get the string-formatted JWK thumbprint + +On the client, execute the following command: + +```bash +$ perl /usr/local/bin/gorgone_key_thumbprint.pl --key-path='/etc/pki/gorgone/pubkey.pem' +2019-09-30 11:00:00 - INFO - File '/etc/pki/gorgone/pubkey.pem' JWK thumbprint: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 +``` + +## Set the configurations + +*Make the IDs match Centreon Pollers ID to benefit from [legacy cmd](../docs/modules/core/legacycmd.md) module's actions.* + +#### Client + +In the *gorgoned.yml* configuration file, add the following directives under the *gorgonecore* section: + +```yaml +gorgonecore: + id: 1 + privkey: /etc/pki/gorgone/privkey.pem + cipher: "Cipher::AES" + keysize: 32 + vector: 0123456789012345 +``` + +Add the [register](../docs/modules/core/register.md) module and define the path to the dedicated configuration file. + +```yaml +modules: + - name: register + package: "gorgone::modules::core::register::hooks" + enable: true + config_file: /etc/centreon/gorgone-targets.yml +``` + +Create the file */etc/centreon/gorgone-targets.yml* and fill it with the following configuration: + +```yaml +nodes: + - id: 2 + type: push_zmq + address: 10.1.2.3 + port: 5556 + server_pubkey: /etc/pki/gorgone/2/pubkey.pem + client_pubkey: /etc/pki/gorgone/pubkey.pem + client_privkey: /etc/pki/gorgone/privkey.pem + cipher: "Cipher::AES" + keysize: 32 + vector: 0123456789012345 +``` + +#### Server + +In the *gorgoned.yml* configuration file, add the following directives under the *gorgonecore* section: + +```yaml +gorgonecore: + id: 2 + external_com_type: tcp + external_com_path: "*:5556" + privkey: /etc/pki/gorgone/privkey.pem + cipher: "Cipher::AES" + keysize: 32 + vector: 0123456789012345 + authorized_clients: + - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 +``` diff --git a/gorgone/docs/configuration.md b/gorgone/docs/configuration.md index d9c4c41c6a1..0c51f2bb8a0 100644 --- a/gorgone/docs/configuration.md +++ b/gorgone/docs/configuration.md @@ -51,11 +51,12 @@ database: | id | Identifier of server running Gorgone | None. Must be unique over all Gorgone daemons. | | privkey | Path to the Gorgone core private key | `keys/central/privkey.pem` | | cipher | Cipher used for encryption | `Cipher::AES` | -| keysize | Size in bytes of the encryption key | `32` | +| keysize | Size in bytes of the symmetric encryption key | `32` | | vector | Encryption vector | `0123456789012345` | +| authorized_clients | Table of string-formated JWK thumbprints of clients public key | | | proxy_name | Name of the proxy module definition | `proxy` (loaded internally) | -#### Example with hardcoded default values +#### Example ```yaml gorgonecore: @@ -76,7 +77,9 @@ gorgonecore: cipher: "Cipher::AES" keysize: 32 vector: 0123456789012345 - proxy_name: gproxy + authorized_clients: + - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 + proxy_name: proxy ``` ## *modules* diff --git a/gorgone/docs/modules/core/register.md b/gorgone/docs/modules/core/register.md index d53bc283a34..7f0601eeb3a 100644 --- a/gorgone/docs/modules/core/register.md +++ b/gorgone/docs/modules/core/register.md @@ -37,7 +37,7 @@ Targets are listed in a separate configuration file in a `nodes` table as below: | client_pubkey | Client public key | | client_privkey | Client private key | | cipher | Cipher used for encryption | -| keysize | Size of the encryption key | +| keysize | Size in bytes of the symmetric encryption key | | vector | Encryption vector | | nodes | Table to register subnodes managed by target | From 50abb7a0176bfb1b05d050a398cd865f88a5b338 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 30 Sep 2019 18:29:44 +0200 Subject: [PATCH 203/948] feat(contrib): add script to get key jwk thumbprint --- gorgone/contrib/gorgone_key_thumbprint.pl | 116 ++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 gorgone/contrib/gorgone_key_thumbprint.pl diff --git a/gorgone/contrib/gorgone_key_thumbprint.pl b/gorgone/contrib/gorgone_key_thumbprint.pl new file mode 100644 index 00000000000..bf7b9fdd5d0 --- /dev/null +++ b/gorgone/contrib/gorgone_key_thumbprint.pl @@ -0,0 +1,116 @@ +#!/usr/bin/perl + +use warnings; +use strict; +use FindBin; +use lib "$FindBin::Bin"; +# to be launched from contrib directory +use lib "$FindBin::Bin/../"; + +gorgone::script::gorgone_key_thumbprint->new()->run(); + +package gorgone::script::gorgone_key_thumbprint; + +use strict; +use warnings; +use gorgone::standard::misc; +use Crypt::PK::RSA; + +use base qw(gorgone::class::script); + +sub new { + my $class = shift; + my $self = $class->SUPER::new("gorgone_key_thumbprint", + centreon_db_conn => 0, + centstorage_db_conn => 0, + noconfig => 0 + ); + + bless $self, $class; + $self->add_options( + 'key-path:s' => \$self->{key_path}, + ); + return $self; +} + +sub init { + my $self = shift; + $self->SUPER::init(); + + $self->{key_path} = '/etc/pki/gorgone/pubkey.pem' if (!defined($self->{key_path}) || $self->{key_path} eq ''); +} + +sub read_key { + my ($self, $key_path) = @_; + + my $fh; + if (!open($fh, '<', $key_path)) { + $self->{logger}->writeLogError("Couldn't open file '$key_path': $!"); + exit(1); + } + my $content = do { local $/; <$fh> }; + close($fh); + + return $content; +} + +sub get_key_thumbprint { + my ($self, $key_string) = @_; + + my $kh; + $key_string =~ s/\\n/\n/g; + eval { + $kh = Crypt::PK::RSA->new(\$key_string); + }; + if ($@) { + $self->{logger}->writeLogError("Cannot load key: $@"); + return -1; + } + + return $kh->export_key_jwk_thumbprint('SHA256'); +} + +sub run { + my $self = shift; + + $self->SUPER::run(); + my $key = $self->read_key($self->{key_path}); + my $thumbprint = $self->get_key_thumbprint($key); + + $self->{logger}->writeLogInfo("File '$self->{key_path}' JWK thumbprint: " . $thumbprint); +} + +__END__ + +=head1 NAME + +gorgone_key_thumbprint.pl - script to get the JWK thumbprint of a RSA key. + +=head1 SYNOPSIS + +gorgone_key_thumbprint.pl [options] + +=head1 OPTIONS + +=over 8 + +=item B<--key-path> + +Specify the path to the RSA key (default: '/etc/pki/gorgone/pubkey.pem'). + +=item B<--severity> + +Set the script log severity (default: 'error'). + +=item B<--help> + +Print a brief help message and exits. + +=back + +=head1 DESCRIPTION + +B + +=cut + From e6dc47e91ed50998767897e97ced8b0eae70e59d Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 30 Sep 2019 18:39:25 +0200 Subject: [PATCH 204/948] fix(library): fix returned value when key is empty --- gorgone/gorgone/standard/library.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index be0937e0ae9..db17051bef6 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -294,6 +294,7 @@ sub is_handshake_done { my ($status, $sth) = $options{dbh}->query("SELECT `key` FROM gorgone_identity WHERE identity = " . $options{dbh}->quote($options{identity}) . " ORDER BY id DESC LIMIT 1"); return if ($status == -1); if (my $row = $sth->fetchrow_hashref()) { + return 0 if (!defined($row->{key}) || $row->{key} eq '') return (1, pack('H*', $row->{key})); } return 0; From e7790c5e11d321fd21cf6b55d72d6fc7acc15a3d Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 1 Oct 2019 09:49:04 +0200 Subject: [PATCH 205/948] enh(docs): enh client/server zmq --- gorgone/docs/client_server_zmq.md | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/gorgone/docs/client_server_zmq.md b/gorgone/docs/client_server_zmq.md index 730413c6e30..d54cb07b599 100644 --- a/gorgone/docs/client_server_zmq.md +++ b/gorgone/docs/client_server_zmq.md @@ -6,29 +6,31 @@ In a Centreon context, the **client** is the Gorgone daemon running on the **Cen ## Generate private and public keys -On both client and server, generate RSA private and public keys. +On both client and server, generate RSA private and public keys using *centreon* user. ```bash -$ mkdir -p /etc/pki/gorgone/ -$ openssl genrsa -out /etc/pki/gorgone/privkey.pem 4092 +$ mkdir -p /var/spool/centreon/.gorgone/ +$ chmod 700 /var/spool/centreon/.gorgone +$ openssl genrsa -out /var/spool/centreon/.gorgone/privkey.pem 4092 Generating RSA private key, 4092 bit long modulus ...................................++ ...........................................................................................................................................................................++ e is 65537 (0x10001) -$ openssl rsa -in /etc/pki/gorgone/privkey.pem -out /etc/pki/gorgone/pubkey.pem -pubout -outform PEM +$ openssl rsa -in /var/spool/centreon/.gorgone/privkey.pem -out /var/spool/centreon/.gorgone/pubkey.pem -pubout -outform PEM writing RSA key -$ chmod 644 /etc/pki/gorgone/* +$ chmod 644 /var/spool/centreon/.gorgone/pubkey.pem +$ chmod 600 /var/spool/centreon/.gorgone/privkey.pem ``` -Copy the server public key onto the client in a specific directory (for example */etc/pki/gorgone/*) +Copy the server public key onto the client in a specific directory (for example */var/spool/centreon/.gorgone/*) ## Get the string-formatted JWK thumbprint On the client, execute the following command: ```bash -$ perl /usr/local/bin/gorgone_key_thumbprint.pl --key-path='/etc/pki/gorgone/pubkey.pem' -2019-09-30 11:00:00 - INFO - File '/etc/pki/gorgone/pubkey.pem' JWK thumbprint: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 +$ perl /usr/local/bin/gorgone_key_thumbprint.pl --key-path='/var/spool/centreon/.gorgone/pubkey.pem' +2019-09-30 11:00:00 - INFO - File '/var/spool/centreon/.gorgone/pubkey.pem' JWK thumbprint: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 ``` ## Set the configurations @@ -42,7 +44,7 @@ In the *gorgoned.yml* configuration file, add the following directives under the ```yaml gorgonecore: id: 1 - privkey: /etc/pki/gorgone/privkey.pem + privkey: /var/spool/centreon/.gorgone/privkey.pem cipher: "Cipher::AES" keysize: 32 vector: 0123456789012345 @@ -66,9 +68,9 @@ nodes: type: push_zmq address: 10.1.2.3 port: 5556 - server_pubkey: /etc/pki/gorgone/2/pubkey.pem - client_pubkey: /etc/pki/gorgone/pubkey.pem - client_privkey: /etc/pki/gorgone/privkey.pem + server_pubkey: /var/spool/centreon/.gorgone/2/pubkey.pem + client_pubkey: /var/spool/centreon/.gorgone/pubkey.pem + client_privkey: /var/spool/centreon/.gorgone/privkey.pem cipher: "Cipher::AES" keysize: 32 vector: 0123456789012345 @@ -83,10 +85,12 @@ gorgonecore: id: 2 external_com_type: tcp external_com_path: "*:5556" - privkey: /etc/pki/gorgone/privkey.pem + privkey: /var/spool/centreon/.gorgone/privkey.pem cipher: "Cipher::AES" keysize: 32 vector: 0123456789012345 authorized_clients: - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 ``` + +The *authorized_clients* entry allows to define the client public key thumbprint retrieved earlier. From c7bc37c8d156beb6af4614f3e545b6740c63191d Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Thu, 3 Oct 2019 09:32:58 +0200 Subject: [PATCH 206/948] fix(core:): compilation --- gorgone/gorgone/class/core.pm | 6 ++--- gorgone/gorgone/standard/library.pm | 35 ++++++++++++++++++++++++++--- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index cfbe7a15810..47f7a9528ab 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -266,7 +266,7 @@ sub load_modules { $self->load_module(config_module => { name => 'dbcleaner', package => 'gorgone::modules::core::dbcleaner::hooks', enable => 'true' }); # Load internal functions - foreach my $method_name (('putlog', 'getlog', 'kill', 'ping', 'constatus', 'setcoreid', 'synclogs', 'loadmodule')) { + foreach my $method_name (('putlog', 'getlog', 'kill', 'ping', 'constatus', 'setcoreid', 'synclogs', 'loadmodule', 'unloadmodule', 'information')) { unless ($self->{internal_register}->{$method_name} = gorgone::standard::library->can($method_name)) { $self->{logger}->writeLogError("[core] No function '$method_name'"); exit(1); @@ -292,7 +292,7 @@ sub message_run { $token = gorgone::standard::library::generate_token(); } - if ($action !~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID|SYNCLOGS|LOADMODULE)$/ && + if ($action !~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID|SYNCLOGS|LOADMODULE|UNLOADMODULE|INFORMATION)$/ && !defined($target) && !defined($self->{modules_events}->{$action})) { gorgone::standard::library::add_history( dbh => $self->{db_gorgone}, @@ -340,7 +340,7 @@ sub message_run { return ($token, 0); } - if ($action =~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID|SYNCLOGS|LOADMODULE)$/) { + if ($action =~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID|SYNCLOGS|LOADMODULE|UNLOADMODULE|INFORMATION)$/) { my ($code, $response, $response_type) = $self->{internal_register}->{lc($action)}->( gorgone => $self, gorgone_config => $config, diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index db17051bef6..55023198465 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -294,7 +294,7 @@ sub is_handshake_done { my ($status, $sth) = $options{dbh}->query("SELECT `key` FROM gorgone_identity WHERE identity = " . $options{dbh}->quote($options{identity}) . " ORDER BY id DESC LIMIT 1"); return if ($status == -1); if (my $row = $sth->fetchrow_hashref()) { - return 0 if (!defined($row->{key}) || $row->{key} eq '') + return 0 if (!defined($row->{key}) || $row->{key} eq ''); return (1, pack('H*', $row->{key})); } return 0; @@ -304,6 +304,36 @@ sub is_handshake_done { # internal functions ####################### +sub information { + my (%options) = @_; + +} + +sub unloadmodule { + my (%options) = @_; + + my $data; + eval { + $data = JSON::XS->new->utf8->decode($options{data}); + }; + if ($@) { + return (1, { message => 'request not well formatted' }); + } + + if (defined($data->{content}->{package}) && defined($options{gorgone}->{modules_register}->{ $data->{content}->{package} })) { + $options{gorgone}->{modules_register}->{ $data->{content}->{package} }->{gently}->(logger => $options{gorgone}->{logger}); + return (0, { action => 'unloadmodule', message => "module '$data->{content}->{package}' unload in progress" }, 'UNLOADMODULE'); + } + if (defined($data->{content}->{name}) && + defined($options{gorgone}->{modules_id}->{$data->{content}->{name}}) && + defined($options{gorgone}->{modules_register}->{ $options{gorgone}->{modules_id}->{$data->{content}->{name}} })) { + $options{gorgone}->{modules_register}->{ $options{gorgone}->{modules_id}->{$data->{content}->{name}} }->{gently}->(logger => $options{gorgone}->{logger}); + return (0, { action => 'unloadmodule', message => "module '$data->{content}->{name}' unload in progress" }, 'UNLOADMODULE'); + } + + return (1, { action => 'unloadmodule', message => 'cannot find unload module' }, 'UNLOADMODULE'); +} + sub loadmodule { my (%options) = @_; @@ -325,10 +355,9 @@ sub loadmodule { dbh => $options{gorgone}->{db_gorgone}, modules_events => $options{gorgone}->{modules_events}, ); - return (0, { action => 'loadmodule', message => "module '$data->{content}->{name}' is loaded" }); + return (0, { action => 'loadmodule', message => "module '$data->{content}->{name}' is loaded" }, 'LOADMODULE'); } - # test if the module is already loaded return (1, { action => 'loadmodule', message => "cannot load module '$data->{content}->{name}'" }, 'LOADMODULE'); } From c297a90fbf4ad3f0cbd5477660f5c20b5ff564c1 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Thu, 3 Oct 2019 09:40:00 +0200 Subject: [PATCH 207/948] fix(core): gently stop --- gorgone/gorgone/modules/core/dbcleaner/hooks.pm | 2 +- gorgone/gorgone/modules/core/proxy/hooks.pm | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/gorgone/gorgone/modules/core/dbcleaner/hooks.pm b/gorgone/gorgone/modules/core/dbcleaner/hooks.pm index 5b73bfdf969..c27a7a5176e 100644 --- a/gorgone/gorgone/modules/core/dbcleaner/hooks.pm +++ b/gorgone/gorgone/modules/core/dbcleaner/hooks.pm @@ -134,7 +134,7 @@ sub check { my $count = 0; foreach my $pid (keys %{$options{dead_childs}}) { # Not me - next if ($dbcleaner->{pid} != $pid); + next if (!defined($dbcleaner->{pid}) || $dbcleaner->{pid} != $pid); $dbcleaner = {}; delete $options{dead_childs}->{$pid}; diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index 89e56a81abd..b4beeeee0fa 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -317,6 +317,8 @@ sub kill_internal { sub check_create_child { my (%options) = @_; + return if ($stop == 1); + # Check if we need to create a child for my $pool_id (1..$config->{pool}) { if (!defined($pools->{$pool_id})) { From b1da179e284d2caf51cf4ec6b0491aa202964906 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Thu, 3 Oct 2019 10:46:22 +0200 Subject: [PATCH 208/948] enh(core): add unloadmodule --- gorgone/contrib/test-client.pl | 10 ++++---- gorgone/gorgone/class/core.pm | 29 +++++++++++++++++++--- gorgone/gorgone/modules/core/cron/hooks.pm | 2 +- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/gorgone/contrib/test-client.pl b/gorgone/contrib/test-client.pl index 0fb7791b31d..c8de55eaf20 100644 --- a/gorgone/contrib/test-client.pl +++ b/gorgone/contrib/test-client.pl @@ -27,8 +27,8 @@ use UUID; use Data::Dumper; use Sys::Hostname; -use centreon::gorgone::clientzmq; -use centreon::gorgone::common; +use gorgone::class::clientzmq; +use gorgone::standard::library; my ($client, $client2); my $identities_token = {}; @@ -107,7 +107,7 @@ sub read_response { #$uuid = 'toto'; UUID::generate($uuid); -#$client = centreon::gorgone::clientzmq->new( +#$client = gorgone::class::clientzmq->new( # identity => 'toto', # cipher => 'Cipher::AES', # vector => '0123456789012345', @@ -119,7 +119,7 @@ sub read_response { # ping => 60, #); #$client->init(callback => \&read_response); -$client2 = centreon::gorgone::clientzmq->new( +$client2 = gorgone::class::clientzmq->new( identity => 'tata', cipher => 'Cipher::AES', vector => '0123456789012345', @@ -150,7 +150,7 @@ sub read_response { #$client2->send_message(action => 'CONSTATUS'); $client2->send_message( action => 'LOADMODULE', - data => { content => { name => 'engine', package => 'modules::centreon::engine::hooks', enable => 'true', command_file => 'plop' } }, + data => { content => { name => 'engine', package => 'gorgone::modules::centreon::engine::hooks', enable => 'true', command_file => 'plop' } }, json_encode => 1 ); diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 47f7a9528ab..9539ad5304b 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -184,6 +184,25 @@ sub handle_CHLD { $SIG{CHLD} = \&class_handle_CHLD; } +sub unload_module { + my ($self, %options) = @_; + + foreach my $event (keys %{$self->{modules_events}}) { + if ($self->{modules_events}->{$event}->{module}->{package} eq $options{package}) { + delete $self->{modules_events}->{$event}; + } + } + + delete $self->{modules_register}->{ $options{package} }; + foreach (keys %{$self->{modules_id}}) { + if ($self->{modules_id}->{$_} eq $options{package}) { + delete $self->{modules_id}->{$_}; + last; + } + } + $self->{logger}->writeLogInfo("[core] Module '" . $options{package} . "' is unloaded"); +} + sub load_module { my ($self, %options) = @_; @@ -236,7 +255,7 @@ sub load_module { $self->{modules_id}->{$name} = $package; - foreach my $event (@{$events}) { + foreach my $event (@$events) { $self->{modules_events}->{$event->{event}} = { module => { namespace => $namespace, @@ -634,9 +653,9 @@ sub run { while (1) { my $count = 0; my $poll = [@{$gorgone->{poll}}]; - + foreach my $name (keys %{$gorgone->{modules_register}}) { - $count += $gorgone->{modules_register}->{$name}->{check}->( + my $count_module = $gorgone->{modules_register}->{$name}->{check}->( logger => $gorgone->{logger}, dead_childs => $gorgone->{return_child}, internal_socket => $gorgone->{internal_socket}, @@ -644,6 +663,10 @@ sub run { poll => $poll, modules_events => $gorgone->{modules_events}, ); + $count += $count_module; + if ($count_module == 0) { + $gorgone->unload_module(package => $name); + } } # We can clean return_child. diff --git a/gorgone/gorgone/modules/core/cron/hooks.pm b/gorgone/gorgone/modules/core/cron/hooks.pm index b6911e312fb..a15b01e3811 100644 --- a/gorgone/gorgone/modules/core/cron/hooks.pm +++ b/gorgone/gorgone/modules/core/cron/hooks.pm @@ -136,7 +136,7 @@ sub check { } } - $count++ if (defined($cron->{running}) && $cron->{running} == 1); + $count++ if (defined($cron->{running}) && $cron->{running} == 1); return $count; } From 3b5cc9d914023e867a700711dd216c726abae46a Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Thu, 3 Oct 2019 11:03:34 +0200 Subject: [PATCH 209/948] enh(core): add information --- gorgone/gorgone/class/core.pm | 8 ++++++++ gorgone/gorgone/standard/library.pm | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 9539ad5304b..41c673940cd 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -61,6 +61,7 @@ sub new { $self->{kill_timer} = undef; $self->{server_privkey} = undef; $self->{register_parent_nodes} = {}; + $self->{counters} = { internal => {}, external => {}, proxy => {} }; return $self; } @@ -323,6 +324,9 @@ sub message_run { return (undef, 1, { message => "action '$action' is not known" }); } + $self->{counters}->{$options{router_type}}->{lc($action)} = 0 if (!defined($self->{counters}->{$options{router_type}}->{lc($action)})); + $self->{counters}->{$options{router_type}}->{lc($action)}++; + if ($self->{stop} == 1) { gorgone::standard::library::add_history( dbh => $self->{db_gorgone}, @@ -346,6 +350,10 @@ sub message_run { ); return ($token, 1, { message => 'no proxy configured. cannot manage target.' }); } + + $self->{counters}->{proxy}->{lc($action)} = 0 if (!defined($self->{counters}->{proxy}->{lc($action)})); + $self->{counters}->{proxy}->{lc($action)}++; + $self->{modules_register}->{ $self->{modules_id}->{$config->{gorgonecore}->{proxy_name}} }->{routing}->( socket => $self->{internal_socket}, dbh => $self->{db_gorgone}, diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index 55023198465..ad42d54e6a5 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -307,6 +307,11 @@ sub is_handshake_done { sub information { my (%options) = @_; + my $data = { + counters => $options{gorgone}->{counters}, + modules => $options{gorgone}->{modules_id}, + }; + return (0, { action => 'information', message => 'ok', data => $data }, 'INFORMATION'); } sub unloadmodule { From 0a25057987dd7cbea95b44f11180968a70c0a407 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Thu, 3 Oct 2019 11:11:02 +0200 Subject: [PATCH 210/948] enh(core): add total for counters --- gorgone/gorgone/class/core.pm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 41c673940cd..9b30b8baac7 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -61,7 +61,7 @@ sub new { $self->{kill_timer} = undef; $self->{server_privkey} = undef; $self->{register_parent_nodes} = {}; - $self->{counters} = { internal => {}, external => {}, proxy => {} }; + $self->{counters} = { total => 0, internal => { total => 0 }, external => { total => 0 }, proxy => { total => 0 } }; return $self; } @@ -326,6 +326,8 @@ sub message_run { $self->{counters}->{$options{router_type}}->{lc($action)} = 0 if (!defined($self->{counters}->{$options{router_type}}->{lc($action)})); $self->{counters}->{$options{router_type}}->{lc($action)}++; + $self->{counters}->{total}++; + $self->{counters}->{$options{router_type}}->{total}++; if ($self->{stop} == 1) { gorgone::standard::library::add_history( @@ -353,6 +355,7 @@ sub message_run { $self->{counters}->{proxy}->{lc($action)} = 0 if (!defined($self->{counters}->{proxy}->{lc($action)})); $self->{counters}->{proxy}->{lc($action)}++; + $self->{counters}->{proxy}->{total}++; $self->{modules_register}->{ $self->{modules_id}->{$config->{gorgonecore}->{proxy_name}} }->{routing}->( socket => $self->{internal_socket}, From f7418b7c220ffe652d6243fe3ac74a6b636c2263 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Thu, 3 Oct 2019 11:11:28 +0200 Subject: [PATCH 211/948] update todo --- gorgone/TODO | 1 - 1 file changed, 1 deletion(-) diff --git a/gorgone/TODO b/gorgone/TODO index 887c138a903..c34df1f9d4a 100644 --- a/gorgone/TODO +++ b/gorgone/TODO @@ -1,4 +1,3 @@ -- Add a core event: GETINFORMATION (events loaded, target loaded and module loaded) - gorgone-newtest: don't use centcore.cmd. use ssh system. - Add a broadcast action to update module (like logger in debug for example) - Add redis backend to store logs (we could disable synclog in redis mode) From af578d9e4cca74addd853a51d822f1bf0b3fb37d Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 7 Oct 2019 18:11:09 +0200 Subject: [PATCH 212/948] enh(library): add LIMIT clause when requesting db --- gorgone/gorgone/standard/library.pm | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index ad42d54e6a5..5f4c7287a5b 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -501,13 +501,22 @@ sub getlog { if ($filter eq '') { return (1, { message => 'need at least one filter' }); } + + my $query = "SELECT * FROM gorgone_history WHERE " . $filter; + $query .= " ORDER BY id DESC LIMIT " . $data->{limit} if (defined($data->{limit}) && $data->{limit} ne ''); - my ($status, $sth) = $options{gorgone}->{db_gorgone}->query("SELECT * FROM gorgone_history WHERE " . $filter); + my ($status, $sth) = $options{gorgone}->{db_gorgone}->query($query); if ($status == -1) { return (1, { message => 'database issue' }); } + + my @result; + my $results = $sth->fetchall_hashref('id'); + foreach (sort keys %{$results}) { + push @result, $results->{$_}; + } - return (0, { action => 'getlog', result => $sth->fetchall_hashref('id'), id => $options{gorgone}->{id} }); + return (0, { action => 'getlog', result => \@result, id => $options{gorgone}->{id} }); } sub kill { From c639145ff5486429c48700c8956ccd96adf5310a Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 7 Oct 2019 18:12:13 +0200 Subject: [PATCH 213/948] enh(api): handle new db results type --- gorgone/gorgone/standard/api.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/gorgone/standard/api.pm b/gorgone/gorgone/standard/api.pm index 80aa384da78..be9f1acca74 100644 --- a/gorgone/gorgone/standard/api.pm +++ b/gorgone/gorgone/standard/api.pm @@ -160,7 +160,7 @@ sub get_log { }; if ($@) { $response = '{"error":"decode_error","message":"Cannot decode response"}'; - } elsif (defined($content->{data}->{result}) && scalar(keys %{$content->{data}->{result}}) > 0) { + } elsif (defined($content->{data}->{result}) && scalar(@{$content->{data}->{result}}) > 0) { eval { $response = JSON::XS->new->utf8->encode($content->{data}->{result}); }; From f5f5e9d7d12012687a19256a0d9cc94caeee5959 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 7 Oct 2019 18:12:41 +0200 Subject: [PATCH 214/948] enh(core/action): remove useless log --- gorgone/gorgone/modules/core/action/class.pm | 6 ------ 1 file changed, 6 deletions(-) diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index 89fd303d382..b2feb70b448 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -287,12 +287,6 @@ sub create_child { if ($child_pid == 0) { $self->action_run(action => $action, token => $token, data => $data); exit(0); - } else { - $self->send_log( - code => $self->ACTION_BEGIN, - token => $token, - data => { message => "proceed action" } - ); } } From 4a38f63c1b38ab874575cddb6270b554e49b5dd9 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 7 Oct 2019 18:13:01 +0200 Subject: [PATCH 215/948] enh(core/cron): add get status of a cron --- gorgone/gorgone/modules/core/cron/class.pm | 135 +++++++++++---------- 1 file changed, 74 insertions(+), 61 deletions(-) diff --git a/gorgone/gorgone/modules/core/cron/class.pm b/gorgone/gorgone/modules/core/cron/class.pm index 5abaecf9b4b..024887f67d2 100644 --- a/gorgone/gorgone/modules/core/cron/class.pm +++ b/gorgone/gorgone/modules/core/cron/class.pm @@ -84,57 +84,68 @@ sub action_getcron { $options{token} = $self->generate_token() if (!defined($options{token})); - $self->send_log( - code => gorgone::class::module::ACTION_BEGIN, - token => $options{token}, - data => { message => 'get start' } - ); - $self->{logger}->writeLogDebug("[cron] -class- Cron get start"); - - my @cron_list = (); + my $data; my $id = $options{data}->{variables}[0]; + my $parameter = $options{data}->{variables}[1]; if (defined($id) && $id ne '') { - my $idx; - eval { - $idx = $self->{cron}->check_entry($id); - }; - if ($@) { - $self->{logger}->writeLogDebug("[cron] -class- Cron get failed to retrieve entry index"); - $self->send_log( - code => $self->ACTION_FINISH_KO, - token => $options{token}, - data => { message => 'failed to retrieve entry index' } - ); - return 1; - } - if (!defined($idx)) { - $self->{logger}->writeLogDebug("[cron] -class- Cron get failed no entry found for id"); - $self->send_log( - code => $self->ACTION_FINISH_KO, + if (defined($parameter) && $parameter =~ /^status$/) { + $self->{logger}->writeLogDebug("[cron] -class- Get logs results for definition '" . $id . "'"); + $self->send_internal_action( + action => 'GETLOG', token => $options{token}, - data => { message => 'no entry found for id' } + data => { + token => $id, + ctime => $options{data}->{parameters}->{ctime}, + etime => $options{data}->{parameters}->{etime}, + limit => $options{data}->{parameters}->{limit}, + code => $options{data}->{parameters}->{code} + } ); - return 1; - } + my $rev = zmq_poll($connector->{poll}, 5000); + $data = $connector->{ack}->{data}->{data}->{result}; + } else { + my $idx; + eval { + $idx = $self->{cron}->check_entry($id); + }; + if ($@) { + $self->{logger}->writeLogDebug("[cron] -class- Cron get failed to retrieve entry index"); + $self->send_log( + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'failed to retrieve entry index' } + ); + return 1; + } + if (!defined($idx)) { + $self->{logger}->writeLogDebug("[cron] -class- Cron get failed no entry found for id"); + $self->send_log( + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'no entry found for id' } + ); + return 1; + } - eval { - my $result = $self->{cron}->get_entry($idx); - push @cron_list, { %{$result->{args}[1]->{definition}} } if (defined($result->{args}[1]->{definition})); - }; - if ($@) { - $self->{logger}->writeLogDebug("[cron] -class- Cron get failed"); - $self->send_log( - code => $self->ACTION_FINISH_KO, - token => $options{token}, - data => { message => 'get failed:' . $@ } - ); - return 1; + eval { + my $result = $self->{cron}->get_entry($idx); + push @{$data}, { %{$result->{args}[1]->{definition}} } if (defined($result->{args}[1]->{definition})); + }; + if ($@) { + $self->{logger}->writeLogDebug("[cron] -class- Cron get failed"); + $self->send_log( + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'get failed:' . $@ } + ); + return 1; + } } } else { eval { my @results = $self->{cron}->list_entries(); foreach my $cron (@results) { - push @cron_list, { %{$cron->{args}[1]->{definition}} }; + push @{$data}, { %{$cron->{args}[1]->{definition}} }; } }; if ($@) { @@ -148,11 +159,10 @@ sub action_getcron { } } - $self->{logger}->writeLogDebug("[cron] -class- Cron get finish"); $self->send_log( code => $self->ACTION_FINISH_OK, token => $options{token}, - data => \@cron_list + data => $data ); return 0; } @@ -163,11 +173,6 @@ sub action_addcron { $options{token} = $self->generate_token() if (!defined($options{token})); $self->{logger}->writeLogDebug("[cron] -class- Cron add start"); - $self->send_log( - code => gorgone::class::module::ACTION_BEGIN, - token => $options{token}, - data => { message => 'add start' } - ); foreach my $definition (@{$options{data}->{content}}) { if (!defined($definition->{timespec}) || $definition->{timespec} eq '' || @@ -231,11 +236,6 @@ sub action_updatecron { $options{token} = $self->generate_token() if (!defined($options{token})); $self->{logger}->writeLogDebug("[cron] -class- Cron update start"); - $self->send_log( - code => gorgone::class::module::ACTION_BEGIN, - token => $options{token}, - data => { message => 'update start' } - ); my $id = $options{data}->{variables}[0]; if (!defined($id)) { @@ -317,11 +317,6 @@ sub action_deletecron { $options{token} = $self->generate_token() if (!defined($options{token})); $self->{logger}->writeLogDebug("[cron] -class- Cron delete start"); - $self->send_log( - code => gorgone::class::module::ACTION_BEGIN, - token => $options{token}, - data => { message => 'delete start' } - ); my $id = $options{data}->{variables}[0]; if (!defined($id) || $id eq '') { @@ -382,9 +377,17 @@ sub action_deletecron { sub event { while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); - + $connector->{logger}->writeLogDebug("[cron] -class- Event: $message"); - if ($message =~ /^\[(.*?)\]/) { + if ($message =~ /^\[ACK\]\s+\[(.*?)\]\s+(.*)$/m) { + my $token = $1; + my $data = JSON::XS->new->utf8->decode($2); + $connector->{ack} = { + token => $token, + data => $data, + }; + } else { + $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; if ((my $method = $connector->can('action_' . lc($1)))) { $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; my ($action, $token) = ($1, $2); @@ -459,7 +462,17 @@ sub run { } ]; - $self->{cron} = new Schedule::Cron(\&dispatcher, nostatus => 1, nofork => 1); + push @{$self->{config}->{cron}}, { + id => "default", + timespec => "0 0 * * *", + action => "COMMAND", + parameters => { + command => "date >> /tmp/date.log", + timeout => 2, + } + }; + + $self->{cron} = new Schedule::Cron(\&dispatcher, nostatus => 1, nofork => 1, catch => 1); foreach my $definition (@{$self->{config}->{cron}}) { $self->{cron}->add_entry( From 9275b58ff6be34670517435bb98932509b4db42f Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 8 Oct 2019 09:45:19 +0200 Subject: [PATCH 216/948] enh(docs): add cron status doc --- gorgone/docs/modules/core/cron.md | 35 ++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/gorgone/docs/modules/core/cron.md b/gorgone/docs/modules/core/cron.md index 226b64736a2..79b6c9764ae 100644 --- a/gorgone/docs/modules/core/cron.md +++ b/gorgone/docs/modules/core/cron.md @@ -30,6 +30,7 @@ cron: parameters: command: "date >> /tmp/date.log" timeout: 10 + keep_token: true ``` ## Events @@ -44,7 +45,7 @@ cron: ## API -### Get one or all definitions +### Get one or all definitions configuration | Endpoint | Method | | :- | :- | @@ -75,6 +76,31 @@ curl --request GET "https://hostname:8443/api/core/cron/definitions/echo_date" \ --header "Accept: application/json" ``` +### Get one definition status + +| Endpoint | Method | +| :- | :- | +| /api/core/cron/definitions/:id/status | `GET` | + +#### Headers + +| Header | Value | +| :- | :- | +| Accept | application/json | + +#### Path variables + +| Variable | Description | +| :- | :- | +| id | Identifier of the cron definition | + +#### Example + +```bash +curl --request GET "https://hostname:8443/api/core/cron/definitions/echo_date/status" \ + --header "Accept: application/json" +``` + ### Add one or several cron definitions | Endpoint | Method | @@ -96,6 +122,7 @@ curl --request GET "https://hostname:8443/api/core/cron/definitions/echo_date" \ | timespec | Cron-like time specification | | command | Action/event to call at job execution | | parameters | Parameters needed by the called action/event | +| keep_token | Boolean to define whether or not the ID of the definition will be used as token for the command | ```json [ @@ -103,7 +130,8 @@ curl --request GET "https://hostname:8443/api/core/cron/definitions/echo_date" \ "id": "", "timespec": "", "command": "", - "parameters": "" + "parameters": "", + "keep_token": "" } ] ``` @@ -122,7 +150,8 @@ curl --request POST "https://hostname:8443/api/core/cron/definitions" \ \"parameters\": { \"command\": \"date >> /tmp/the_date_again.log\", \"timeout\": 5 - } + }, + \"keep_token\": true } ]" ``` From c3b86a94f1da59c8678393a6ea4c2784aa2e43f1 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 8 Oct 2019 09:50:57 +0200 Subject: [PATCH 217/948] enh(docs): add link to zmq setup --- gorgone/docs/getting_started.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gorgone/docs/getting_started.md b/gorgone/docs/getting_started.md index ec57f0da431..531f2e5b1b6 100644 --- a/gorgone/docs/getting_started.md +++ b/gorgone/docs/getting_started.md @@ -141,3 +141,7 @@ If you are using the sources, execute the following command: ```bash perl gorgoned --config=config/gorgoned.yml --severity=error ``` + +## Full-ZMQ setup + +To use Gorgone distributed on multiple servers using ZMQ, follow the example given [here](../docs/client_server_zmq.md). From f2fecefc2f62f7ec2528c54fd82845d9d8638be9 Mon Sep 17 00:00:00 2001 From: Zadkiel Date: Wed, 16 Oct 2019 17:11:27 +0200 Subject: [PATCH 218/948] docs: Fix SCOM resync method --- gorgone/docs/modules/plugins/scom.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/docs/modules/plugins/scom.md b/gorgone/docs/modules/plugins/scom.md index 3586a9876a1..8db958a9a25 100644 --- a/gorgone/docs/modules/plugins/scom.md +++ b/gorgone/docs/modules/plugins/scom.md @@ -85,6 +85,6 @@ containers: #### Example ```bash -curl --request POST "https://hostname:8443/api/plugins/scom/resync" \ +curl --request GET "https://hostname:8443/api/plugins/scom/resync" \ --header "Accept: application/json" ``` From aa6ae5b7714fb66e623fc0dccb7cf84dfcf9a7e0 Mon Sep 17 00:00:00 2001 From: Zadkiel Date: Wed, 16 Oct 2019 17:15:33 +0200 Subject: [PATCH 219/948] docs: Fix Newtest resync method --- gorgone/docs/modules/plugins/newtest.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/docs/modules/plugins/newtest.md b/gorgone/docs/modules/plugins/newtest.md index 4349f3d830c..72f287c4b96 100644 --- a/gorgone/docs/modules/plugins/newtest.md +++ b/gorgone/docs/modules/plugins/newtest.md @@ -122,7 +122,7 @@ containers: #### Example ```bash -curl --request POST "https://hostname:8443/api/plugins/newtest/resync" \ +curl --request GET "https://hostname:8443/api/plugins/newtest/resync" \ --header "Accept: application/json" ``` From 46a1d74bee8b0ef262afeacef25c8ee13ddc90e2 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Fri, 8 Nov 2019 11:07:05 +0100 Subject: [PATCH 220/948] fix(modules): constant error --- gorgone/gorgone/modules/plugins/newtest/hooks.pm | 2 +- gorgone/gorgone/modules/plugins/scom/hooks.pm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/modules/plugins/newtest/hooks.pm b/gorgone/gorgone/modules/plugins/newtest/hooks.pm index 49183ec193a..054449e37a3 100644 --- a/gorgone/gorgone/modules/plugins/newtest/hooks.pm +++ b/gorgone/gorgone/modules/plugins/newtest/hooks.pm @@ -253,7 +253,7 @@ sub create_child { $0 = 'gorgone-newtest ' . $options{container_id}; my $module = gorgone::modules::plugins::newtest::class->new( logger => $options{logger}, - module_id => $NAME, + module_id => NAME, config_core => $config_core, config => $config, config_db_centreon => $config_db_centreon, diff --git a/gorgone/gorgone/modules/plugins/scom/hooks.pm b/gorgone/gorgone/modules/plugins/scom/hooks.pm index ec723cf8437..50206e139a1 100644 --- a/gorgone/gorgone/modules/plugins/scom/hooks.pm +++ b/gorgone/gorgone/modules/plugins/scom/hooks.pm @@ -240,7 +240,7 @@ sub create_child { $0 = 'gorgone-scom ' . $options{container_id}; my $module = gorgone::modules::plugins::scom::class->new( logger => $options{logger}, - module_id => $NAME, + module_id => NAME, config_core => $config_core, config => $config, config_db_centstorage => $config_db_centstorage, From 42879e7188876f3e2225e769a54d7a9cf8d72dbb Mon Sep 17 00:00:00 2001 From: Matthieu Kermagoret Date: Thu, 28 Nov 2019 15:55:40 +0100 Subject: [PATCH 221/948] chore: update version to 20.04.0. --- gorgone/Jenkinsfile | 2 +- gorgone/packaging/centreon-gorgone.spectemplate | 2 +- gorgone/sonar-project.properties | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gorgone/Jenkinsfile b/gorgone/Jenkinsfile index a17aba06b82..b34dd1a8207 100644 --- a/gorgone/Jenkinsfile +++ b/gorgone/Jenkinsfile @@ -1,7 +1,7 @@ /* ** Variables. */ -def serie = '19.10' +def serie = '20.04' def maintenanceBranch = "${serie}.x" if (env.BRANCH_NAME.startsWith('release-')) { env.BUILD = 'RELEASE' diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 09715ee904b..9b29a575adc 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -1,5 +1,5 @@ Name: centreon-gorgone -Version: 19.10.0 +Version: 20.04.0 Release: @RELEASE@%{?dist} Summary: Perl daemon task handlers Group: Applications/System diff --git a/gorgone/sonar-project.properties b/gorgone/sonar-project.properties index 703363e7eec..0b335ba601b 100644 --- a/gorgone/sonar-project.properties +++ b/gorgone/sonar-project.properties @@ -1,3 +1,3 @@ -sonar.projectKey=centreon-gorgone-19.10 -sonar.projectName=Centreon Gorgone 19.10 +sonar.projectKey=centreon-gorgone-20.04 +sonar.projectName=Centreon Gorgone 20.04 sonar.sources=. From e51c30fca65e87349a8c9c1d0c71727321ef798f Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Fri, 29 Nov 2019 11:29:53 +0100 Subject: [PATCH 222/948] enh(core): generate rsa keys --- gorgone/gorgone/class/clientzmq.pm | 6 +- gorgone/gorgone/class/core.pm | 88 +++++++++++++++++++++++------ gorgone/gorgone/standard/library.pm | 44 ++++++++++++--- gorgone/gorgone/standard/misc.pm | 13 +++++ 4 files changed, 123 insertions(+), 28 deletions(-) diff --git a/gorgone/gorgone/class/clientzmq.pm b/gorgone/gorgone/class/clientzmq.pm index b9ac4336096..29555ecf9ab 100644 --- a/gorgone/gorgone/class/clientzmq.pm +++ b/gorgone/gorgone/class/clientzmq.pm @@ -38,15 +38,15 @@ sub new { $connector->{cipher} = $options{cipher}; $connector->{vector} = $options{vector}; $connector->{symkey} = undef; - $connector->{server_pubkey} = gorgone::standard::library::loadpubkey( + (undef, $connector->{server_pubkey}) = gorgone::standard::library::loadpubkey( pubkey => $options{server_pubkey}, logger => $options{logger} ); - $connector->{client_pubkey} = gorgone::standard::library::loadpubkey( + (undef, $connector->{client_pubkey}) = gorgone::standard::library::loadpubkey( pubkey => $options{client_pubkey}, logger => $options{logger} ); - $connector->{client_privkey} = gorgone::standard::library::loadprivkey( + (undef, $connector->{client_privkey}) = gorgone::standard::library::loadprivkey( privkey => $options{client_privkey}, logger => $options{logger} ); diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 9b30b8baac7..e13c8e2adae 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -27,6 +27,7 @@ use Sys::Hostname; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); use gorgone::standard::library; +use gorgone::standard::misc; use gorgone::class::db; my ($gorgone, $config); @@ -66,8 +67,59 @@ sub new { return $self; } +sub init_server_keys { + my ($self, %options) = @_; + + my ($code, $content_privkey, $content_pubkey); + $self->{logger}->writeLogInfo("[core] init server keys"); + + $self->{keys_loaded} = 0; + $self->{server_privkey_file} = defined($options{config}->{gorgonecore}->{privkey}) && $options{config}->{gorgonecore}->{privkey} ne '' ? + $options{config}->{gorgonecore}->{privkey} : 'keys/rsakey.priv.pem'; + $self->{server_pubkey_file} = defined($options{config}->{gorgonecore}->{pubkey}) && $options{config}->{gorgonecore}->{pubkey} ne '' ? + $options{config}->{gorgonecore}->{privkey} : 'keys/rsakey.pub.pem'; + + if (! -f $self->{server_privkey_file} && ! -f $self->{server_pubkey_file}) { + ($code, $content_privkey, $content_pubkey) = gorgone::standard::library::generate_keys(logger => $self->{logger}); + return if ($code == 0); + $code = gorgone::standard::misc::write_file( + logger => $self->{logger}, + filename => $self->{server_privkey_file}, + content => $content_privkey, + ); + return if ($code == 0); + $self->{logger}->writeLogInfo("[core] private key file '$self->{server_privkey_file}' written"); + + $code = gorgone::standard::misc::write_file( + logger => $self->{logger}, + filename => $self->{server_pubkey_file}, + content => $content_pubkey, + ); + return if ($code == 0); + $self->{logger}->writeLogInfo("[core] public key file '$self->{server_pubkey_file}' written"); + } + + ($code, $self->{server_privkey}) = gorgone::standard::library::loadprivkey( + logger => $self->{logger}, + privkey => $self->{server_privkey_file}, + noquit => 1 + ); + return if ($code == 0); + $self->{logger}->writeLogInfo("[core] private key file '$self->{server_privkey_file}' loaded"); + + ($code, $self->{server_pubkey}) = gorgone::standard::library::loadpubkey( + logger => $self->{logger}, + pubkey => $self->{server_pubkey_file}, + noquit => 1 + ); + return if ($code == 0); + $self->{logger}->writeLogInfo("[core] public key file '$self->{server_pubkey_file}' loaded"); + + $self->{keys_loaded} = 1; +} + sub init { - my $self = shift; + my ($self) = @_; $self->SUPER::init(); # redefine to avoid out when we try modules @@ -82,10 +134,8 @@ sub init { config_file => $self->{opt_config}, logger => $self->{logger} ); - - if (defined($config->{gorgonecore}->{external_com_type}) && $config->{gorgonecore}->{external_com_type} ne '') { - $self->{server_privkey} = gorgone::standard::library::loadprivkey(logger => $self->{logger}, privkey => $config->{gorgonecore}->{privkey}); - } + + $self->init_server_keys(config => $config); $config->{gorgonecore}->{internal_com_type} = defined($config->{gorgonecore}->{internal_com_type}) && $config->{gorgonecore}->{internal_com_type} ne '' ? $config->{gorgonecore}->{internal_com_type} : 'ipc'; @@ -128,7 +178,7 @@ sub init { } sub set_signal_handlers { - my $self = shift; + my ($self) = @_; $SIG{TERM} = \&class_handle_TERM; $handlers{TERM}->{$self} = sub { $self->handle_TERM() }; @@ -157,7 +207,7 @@ sub class_handle_CHLD { } sub handle_TERM { - my $self = shift; + my ($self) = @_; $self->{logger}->writeLogInfo("[core] $$ Receiving order to stop..."); $self->{stop} = 1; @@ -482,8 +532,10 @@ sub handshake { ); } - if (gorgone::standard::library::zmq_core_key_response(logger => $self->{logger}, socket => $self->{external_socket}, identity => $identity, - client_pubkey => $client_pubkey, hostname => $self->{hostname}, symkey => $symkey) == -1) { + if (gorgone::standard::library::zmq_core_key_response( + logger => $self->{logger}, socket => $self->{external_socket}, identity => $identity, + client_pubkey => $client_pubkey, hostname => $self->{hostname}, symkey => $symkey) == -1 + ) { gorgone::standard::library::zmq_core_response( socket => $self->{external_socket}, identity => $identity, code => 1, data => { message => 'handshake issue' } @@ -588,7 +640,7 @@ sub quit { $self->{logger}->writeLogInfo("[core] Quit main process"); zmq_close($self->{internal_socket}); - if (defined($config->{gorgonecore}->{external_com_type}) && $config->{gorgonecore}->{external_com_type} ne '') { + if (defined($self->{external_socket})) { zmq_close($self->{external_socket}); } exit(0); @@ -620,12 +672,16 @@ sub run { logger => $gorgone->{logger} ); if (defined($config->{gorgonecore}->{external_com_type}) && $config->{gorgonecore}->{external_com_type} ne '') { - $gorgone->{external_socket} = gorgone::standard::library::create_com( - type => $config->{gorgonecore}->{external_com_type}, - path => $config->{gorgonecore}->{external_com_path}, - zmq_type => 'ZMQ_ROUTER', name => 'router-external', - logger => $gorgone->{logger} - ); + if ($gorgone->{keys_loaded}) { + $gorgone->{external_socket} = gorgone::standard::library::create_com( + type => $config->{gorgonecore}->{external_com_type}, + path => $config->{gorgonecore}->{external_com_path}, + zmq_type => 'ZMQ_ROUTER', name => 'router-external', + logger => $gorgone->{logger} + ); + } else { + $gorgone->{logger}->writeLogError("[core] Cannot create external com: no keys loaded"); + } } # Initialize poll set diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index 5f4c7287a5b..11988a53465 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -56,13 +56,33 @@ sub read_config { # Handshake functions ####################### +sub generate_keys { + my (%options) = @_; + + my ($privkey, $pubkey); + eval { + my $pkrsa = Crypt::PK::RSA->new(); + $pkrsa->generate_key(256, 65537); + $pubkey = $pkrsa->export_key_pem('public_x509'); + $privkey = $pkrsa->export_key_pem('private'); + }; + if ($@) { + $options{logger}->writeLogError("Cannot generate server keys: $@"); + return 0; + } + + return (1, $privkey, $pubkey); +} + sub loadpubkey { my (%options) = @_; + my $quit = defined($options{noquit}) ? 0 : 1; my $string_key = ''; if (!open FILE, "<" . $options{pubkey}) { $options{logger}->writeLogError("Cannot read file '$options{pubkey}': $!"); - exit(1); + exit(1) if ($quit); + return 0; } while () { $string_key .= $_; @@ -75,23 +95,27 @@ sub loadpubkey { }; if ($@) { $options{logger}->writeLogError("Cannot load pubkey '$options{pubkey}': $@"); - exit(1); + exit(1) if ($quit); + return 0; } if ($pubkey->is_private()) { $options{logger}->writeLogError("'$options{pubkey}' is not a publickey"); - exit(1); + exit(1) if ($quit); + return 0; } - return $pubkey; + return (1, $pubkey); } sub loadprivkey { my (%options) = @_; my $string_key = ''; - + my $quit = defined($options{noquit}) ? 0 : 1; + if (!open FILE, "<" . $options{privkey}) { $options{logger}->writeLogError("Cannot read file '$options{privkey}': $!"); - exit(1); + exit(1) if ($quit); + return 0; } while () { $string_key .= $_; @@ -104,14 +128,16 @@ sub loadprivkey { }; if ($@) { $options{logger}->writeLogError("Cannot load privkey '$options{privkey}': $@"); - exit(1); + exit(1) if ($quit); + return 0; } if (!$privkey->is_private()) { $options{logger}->writeLogError("'$options{privkey}' is not a privkey"); - exit(1); + exit(1) if ($quit); + return 0; } - return $privkey; + return (1, $privkey); } sub zmq_core_key_response { diff --git a/gorgone/gorgone/standard/misc.pm b/gorgone/gorgone/standard/misc.pm index 253e1abf66b..7d2f75fa8f7 100644 --- a/gorgone/gorgone/standard/misc.pm +++ b/gorgone/gorgone/standard/misc.pm @@ -227,6 +227,19 @@ sub mymodule_load { return wantarray ? (0, $file) : 0; } +sub write_file { + my (%options) = @_; + + my $fh; + if (!open($fh, '>', $options{filename})) { + $options{logger}->writeLogError("Could not open file '$options{filename}': $!"); + return 0; + } + print $fh $options{content}; + close $fh; + return 1; +} + sub trim { my ($value) = $_[0]; From 660e2366baaf1eb471fcb2f082d6ca7bd3fb5759 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Fri, 29 Nov 2019 12:33:10 +0100 Subject: [PATCH 223/948] enh(core): create subdirs for keys --- gorgone/contrib/gorgone_config_init.pl | 2 ++ gorgone/gorgone/standard/misc.pm | 3 +++ 2 files changed, 5 insertions(+) diff --git a/gorgone/contrib/gorgone_config_init.pl b/gorgone/contrib/gorgone_config_init.pl index 399d46ad432..5984e4135bd 100644 --- a/gorgone/contrib/gorgone_config_init.pl +++ b/gorgone/contrib/gorgone_config_init.pl @@ -94,6 +94,8 @@ sub write_gorgone_config { gorgonecore: hostname: id: + privkey: /var/spool/centreon/.gorgone/rsakey.priv.pem + pubkey: /var/spool/centreon/.gorgone/rsakey.pub.pem modules: - name: httpserver package: gorgone::modules::core::httpserver::hooks diff --git a/gorgone/gorgone/standard/misc.pm b/gorgone/gorgone/standard/misc.pm index 7d2f75fa8f7..add880c2fde 100644 --- a/gorgone/gorgone/standard/misc.pm +++ b/gorgone/gorgone/standard/misc.pm @@ -24,6 +24,8 @@ use strict; use warnings; use vars qw($centreon_config); use POSIX ":sys_wait_h"; +use File::Path; +use File::Basename; sub reload_db_config { my ($logger, $config_file, $cdb, $csdb) = @_; @@ -230,6 +232,7 @@ sub mymodule_load { sub write_file { my (%options) = @_; + File::Path::make_path(File::Basename::dirname($options{filename})); my $fh; if (!open($fh, '>', $options{filename})) { $options{logger}->writeLogError("Could not open file '$options{filename}': $!"); From b81fa07d2a36391c8f220b1fa14deb7822049194 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Fri, 29 Nov 2019 13:54:26 +0100 Subject: [PATCH 224/948] enh(core): use keys from server in modules --- gorgone/gorgone/class/core.pm | 24 ++++++++++----------- gorgone/gorgone/modules/core/proxy/class.pm | 12 +++++++---- gorgone/gorgone/modules/core/pull/hooks.pm | 8 +++++-- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index e13c8e2adae..724b8237570 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -74,46 +74,46 @@ sub init_server_keys { $self->{logger}->writeLogInfo("[core] init server keys"); $self->{keys_loaded} = 0; - $self->{server_privkey_file} = defined($options{config}->{gorgonecore}->{privkey}) && $options{config}->{gorgonecore}->{privkey} ne '' ? + $options{config}->{gorgonecore}->{privkey} = defined($options{config}->{gorgonecore}->{privkey}) && $options{config}->{gorgonecore}->{privkey} ne '' ? $options{config}->{gorgonecore}->{privkey} : 'keys/rsakey.priv.pem'; - $self->{server_pubkey_file} = defined($options{config}->{gorgonecore}->{pubkey}) && $options{config}->{gorgonecore}->{pubkey} ne '' ? - $options{config}->{gorgonecore}->{privkey} : 'keys/rsakey.pub.pem'; + $options{config}->{gorgonecore}->{pubkey} = defined($options{config}->{gorgonecore}->{pubkey}) && $options{config}->{gorgonecore}->{pubkey} ne '' ? + $options{config}->{gorgonecore}->{pubkey} : 'keys/rsakey.pub.pem'; - if (! -f $self->{server_privkey_file} && ! -f $self->{server_pubkey_file}) { + if (! -f $options{config}->{gorgonecore}->{privkey} && ! -f $options{config}->{gorgonecore}->{pubkey}) { ($code, $content_privkey, $content_pubkey) = gorgone::standard::library::generate_keys(logger => $self->{logger}); return if ($code == 0); $code = gorgone::standard::misc::write_file( logger => $self->{logger}, - filename => $self->{server_privkey_file}, + filename => $options{config}->{gorgonecore}->{privkey}, content => $content_privkey, ); return if ($code == 0); - $self->{logger}->writeLogInfo("[core] private key file '$self->{server_privkey_file}' written"); + $self->{logger}->writeLogInfo("[core] private key file '$options{config}->{gorgonecore}->{privkey}' written"); $code = gorgone::standard::misc::write_file( logger => $self->{logger}, - filename => $self->{server_pubkey_file}, + filename => $options{config}->{gorgonecore}->{pubkey}, content => $content_pubkey, ); return if ($code == 0); - $self->{logger}->writeLogInfo("[core] public key file '$self->{server_pubkey_file}' written"); + $self->{logger}->writeLogInfo("[core] public key file '$options{config}->{gorgonecore}->{pubkey}' written"); } ($code, $self->{server_privkey}) = gorgone::standard::library::loadprivkey( logger => $self->{logger}, - privkey => $self->{server_privkey_file}, + privkey => $options{config}->{gorgonecore}->{privkey}, noquit => 1 ); return if ($code == 0); - $self->{logger}->writeLogInfo("[core] private key file '$self->{server_privkey_file}' loaded"); + $self->{logger}->writeLogInfo("[core] private key file '$options{config}->{gorgonecore}->{privkey}' loaded"); ($code, $self->{server_pubkey}) = gorgone::standard::library::loadpubkey( logger => $self->{logger}, - pubkey => $self->{server_pubkey_file}, + pubkey => $options{config}->{gorgonecore}->{pubkey}, noquit => 1 ); return if ($code == 0); - $self->{logger}->writeLogInfo("[core] public key file '$self->{server_pubkey_file}' loaded"); + $self->{logger}->writeLogInfo("[core] public key file '$options{config}->{gorgonecore}->{pubkey}' loaded"); $self->{keys_loaded} = 1; } diff --git a/gorgone/gorgone/modules/core/proxy/class.pm b/gorgone/gorgone/modules/core/proxy/class.pm index fe58fe09f36..f8cd9dab745 100644 --- a/gorgone/gorgone/modules/core/proxy/class.pm +++ b/gorgone/gorgone/modules/core/proxy/class.pm @@ -148,8 +148,12 @@ sub connect { cipher => $self->{clients}->{$options{id}}->{cipher}, vector => $self->{clients}->{$options{id}}->{vector}, server_pubkey => $self->{clients}->{$options{id}}->{server_pubkey}, - client_pubkey => $self->{clients}->{$options{id}}->{client_pubkey}, - client_privkey => $self->{clients}->{$options{id}}->{client_privkey}, + client_pubkey => + defined($self->{clients}->{$options{id}}->{client_pubkey}) && $self->{clients}->{$options{id}}->{client_pubkey} ne '' + ? $self->{clients}->{$options{id}}->{client_pubkey} : $self->{config_core}->{pubkey}, + client_privkey => + defined($self->{clients}->{$options{id}}->{client_privkey}) && $self->{clients}->{$options{id}}->{client_privkey} ne '' + ? $self->{clients}->{$options{id}}->{client_privkey} : $self->{config_core}->{privkey}, target_type => defined($self->{clients}->{$options{id}}->{target_type}) ? $self->{clients}->{$options{id}}->{target_type} : 'tcp', @@ -342,8 +346,8 @@ sub run { zmq_type => 'ZMQ_DEALER', name => 'gorgoneproxy-' . $self->{pool_id}, logger => $self->{logger}, - type => $self->{config_core}{internal_com_type}, - path => $self->{config_core}{internal_com_path} + type => $self->{config_core}->{internal_com_type}, + path => $self->{config_core}->{internal_com_path} ); gorgone::standard::library::zmq_send_message( socket => $self->{internal_socket}, diff --git a/gorgone/gorgone/modules/core/pull/hooks.pm b/gorgone/gorgone/modules/core/pull/hooks.pm index 524c9b45976..47f127d3ede 100644 --- a/gorgone/gorgone/modules/core/pull/hooks.pm +++ b/gorgone/gorgone/modules/core/pull/hooks.pm @@ -62,8 +62,12 @@ sub init { cipher => $config->{cipher}, vector => $config->{vector}, server_pubkey => $config->{server_pubkey}, - client_pubkey => $config->{client_pubkey}, - client_privkey => $config->{server_privkey}, + client_pubkey => + defined($config->{client_pubkey}) && $config->{client_pubkey} ne '' ? + $config->{client_pubkey} : $config_core->{pubkey}, + client_privkey => + defined($config->{client_privkey}) && $config->{client_privkey} ne '' ? + $config->{client_privkey} : $config_core->{privkey}, target_type => $config->{target_type}, target_path => $config->{target_path}, logger => $options{logger}, From 404ee0af0724ba9ae0ad508b15945a9e6530fd5f Mon Sep 17 00:00:00 2001 From: Loic Laurent Date: Fri, 29 Nov 2019 16:40:31 +0100 Subject: [PATCH 225/948] enh(script): link gorgone service to centreon service --- .../scripts/systemd/centreon-gorgone-service | 60 ++++++++++--------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/gorgone/scripts/systemd/centreon-gorgone-service b/gorgone/scripts/systemd/centreon-gorgone-service index 8f443666a7f..c59d31db66e 100644 --- a/gorgone/scripts/systemd/centreon-gorgone-service +++ b/gorgone/scripts/systemd/centreon-gorgone-service @@ -1,28 +1,32 @@ -## Copyright 2019 Centreon -## -## Licensed under the Apache License, Version 2.0 (the "License"); -## you may not use this file except in compliance with the License. -## You may obtain a copy of the License at -## -## http://www.apache.org/licenses/LICENSE-2.0 -## -## Unless required by applicable law or agreed to in writing, software -## distributed under the License is distributed on an "AS IS" BASIS, -## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -## See the License for the specific language governing permissions and -## limitations under the License. -## -## For more information : contact@centreon.com -## - -[Unit] -Description=Centreon Gorgone - -[Service] -EnvironmentFile=/etc/sysconfig/centreon-gorgone -ExecStart=/usr/bin/perl /usr/bin/gorgoned $OPTIONS -Type=simple -User=centreon - -[Install] -WantedBy=multi-user.target +## Copyright 2019 Centreon +## +## Licensed under the Apache License, Version 2.0 (the "License"); +## you may not use this file except in compliance with the License. +## You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. +## +## For more information : contact@centreon.com +## + +[Unit] +Description=Centreon Gorgone +PartOf=centreon.service +After=centreon.service +ReloadPropagatedFrom=centreon.service + +[Service] +EnvironmentFile=/etc/sysconfig/centreon-gorgone +ExecStart=/usr/bin/perl /usr/bin/gorgoned $OPTIONS +Type=simple +User=centreon + +[Install] +WantedBy=multi-user.target +WantedBy=centreon.service From a03eebcae3e2ee6e2dda1ea2169cf39f70ccc12f Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Mon, 2 Dec 2019 14:11:52 +0100 Subject: [PATCH 226/948] enh(core): add server pubkey in protocol --- gorgone/config/registernodes-central.yml | 3 - gorgone/config/registernodes-remote.yml | 3 - gorgone/docs/guide.md | 18 ++++- gorgone/gorgone/class/clientzmq.pm | 81 +++++++++++++++++---- gorgone/gorgone/class/core.pm | 11 +++ gorgone/gorgone/modules/core/proxy/class.pm | 1 - gorgone/gorgone/modules/core/pull/hooks.pm | 1 - gorgone/gorgone/standard/library.pm | 43 +++++++---- 8 files changed, 121 insertions(+), 40 deletions(-) diff --git a/gorgone/config/registernodes-central.yml b/gorgone/config/registernodes-central.yml index e0119674df3..9aa87eefd1c 100644 --- a/gorgone/config/registernodes-central.yml +++ b/gorgone/config/registernodes-central.yml @@ -3,9 +3,6 @@ nodes: type: push_zmq address: 10.30.2.135 port: 5556 - server_pubkey: keys/poller/pubkey.crt - client_pubkey: keys/central/pubkey.crt - client_privkey: keys/central/privkey.pem cipher: "Cipher::AES" keysize: 32 vector: 0123456789012345 diff --git a/gorgone/config/registernodes-remote.yml b/gorgone/config/registernodes-remote.yml index b267be541b7..f23c4a69208 100644 --- a/gorgone/config/registernodes-remote.yml +++ b/gorgone/config/registernodes-remote.yml @@ -3,9 +3,6 @@ nodes: type: push_zmq address: 10.30.2.90 port: 5556 - server_pubkey: keys/poller/pubkey.crt - client_pubkey: keys/central/pubkey.crt - client_privkey: keys/central/privkey.pem cipher: "Cipher::AES" keysize: 32 vector: 0123456789012345 diff --git a/gorgone/docs/guide.md b/gorgone/docs/guide.md index 1cd25e6b721..6786d786ff7 100644 --- a/gorgone/docs/guide.md +++ b/gorgone/docs/guide.md @@ -9,14 +9,26 @@ Third-party clients have to use the ZeroMQ library and the following process: -1. Client : need to create an uniq identity (will be used in "zmq_setsockopt" and "ZMQ_IDENTITY") -2. Client -> Server : send the following message with HELO crypted with the public key of the server and provides client pubkey: +1. Client: need to create an uniq identity (will be used in "zmq_setsockopt" and "ZMQ_IDENTITY") +2. Client -> Server: ask the server pubkey + + ```text + [GETPUBKEY] + ``` + +3. Server -> Client: send back the pubkey + + ```text + [PUBKEY] [xxxxx] + ``` + +4. Client -> Server: send the following message with HELO crypted with the public key of the server and provides client pubkey: ```text [HOSTNAME] [CLIENTPUBKEY] [HELO] ``` -3. Server: uncrypt the client message: +5. Server -> Client: uncrypt the client message: * If uncrypted message result is not "HELO", server refuses the connection and send it back: diff --git a/gorgone/gorgone/class/clientzmq.pm b/gorgone/gorgone/class/clientzmq.pm index 29555ecf9ab..1a9ff285c48 100644 --- a/gorgone/gorgone/class/clientzmq.pm +++ b/gorgone/gorgone/class/clientzmq.pm @@ -38,10 +38,14 @@ sub new { $connector->{cipher} = $options{cipher}; $connector->{vector} = $options{vector}; $connector->{symkey} = undef; - (undef, $connector->{server_pubkey}) = gorgone::standard::library::loadpubkey( - pubkey => $options{server_pubkey}, - logger => $options{logger} - ); + $connector->{verbose_last_message} = ''; + + if (defined($options{server_pubkey}) && $options{server_pubkey} ne '') { + (undef, $connector->{server_pubkey}) = gorgone::standard::library::loadpubkey( + pubkey => $options{server_pubkey}, + logger => $options{logger} + ); + } (undef, $connector->{client_pubkey}) = gorgone::standard::library::loadpubkey( pubkey => $options{client_pubkey}, logger => $options{logger} @@ -86,6 +90,38 @@ sub close { zmq_close($sockets->{$self->{identity}}); } +sub get_server_pubkey { + my ($self, %options) = @_; + + zmq_sendmsg($sockets->{$self->{identity}}, '[GETPUBKEY]', ZMQ_DONTWAIT); + zmq_poll([$self->get_poll()], 10000); +} + +sub check_server_pubkey { + my ($self, %options) = @_; + + if ($options{message} !~ /^\s*\[PUBKEY\]\s+\[(.*?)\]/) { + $self->{logger}->writeLogError('cannot read pubbkey response from server: ' . $options{message}) if (defined($self->{logger})); + $self->{verbose_last_message} = 'cannot read pubkey response from server'; + return 0; + } + + my $server_pubkey_str = pack('H*', $1); + (my $code, $self->{server_pubkey}) = gorgone::standard::library::loadpubkey( + pubkey_str => $server_pubkey_str, + logger => $self->{logger}, + noquit => 1 + ); + + if ($code == 0) { + $self->{logger}->writeLogError('cannot load pubbkey') if (defined($self->{logger})); + $self->{verbose_last_message} = 'cannot load pubkey'; + return 0; + } + + return 1; +} + sub is_connected { my ($self, %options) = @_; @@ -146,13 +182,19 @@ sub event { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $sockets->{$options{identity}}); # in progress - if ($connectors->{$options{identity}}->{handshake} == 0 || $connectors->{$options{identity}}->{handshake} == 1) { - my ($status, $symkey, $hostname) = gorgone::standard::library::client_get_secret( + if ($connectors->{$options{identity}}->{handshake} == 0) { + $connectors->{$options{identity}}->{handshake} = 1; + if ($connectors->{$options{identity}}->check_server_pubkey(message => $message) == 0) { + $connectors->{$options{identity}}->{handshake} = -1; + } + } elsif ($connectors->{$options{identity}}->{handshake} == 1) { + my ($status, $verbose, $symkey, $hostname) = gorgone::standard::library::client_get_secret( privkey => $connectors->{$options{identity}}->{client_privkey}, message => $message ); if ($status == -1) { - $connectors->{$options{identity}}->{handshake} = 0; + $connectors->{$options{identity}}->{handshake} = -1; + $connectors->{$options{identity}}->{verbose_last_message} = $verbose; return ; } $connectors->{$options{identity}}->{symkey} = $symkey; @@ -169,7 +211,8 @@ sub event { ); if ($status == -1 || $data !~ /^\[(.+?)\]\s+\[(.*?)\]\s+(?:\[(.*?)\]\s*(.*)|(.*))$/m) { - $connectors->{$options{identity}}->{handshake} = 0; + $connectors->{$options{identity}}->{handshake} = -1; + $connectors->{$options{identity}}->{verbose_last_message} = 'uncrypt issue: ' . $data; return ; } @@ -184,28 +227,34 @@ sub event { sub send_message { my ($self, %options) = @_; - + if ($self->{handshake} == 0) { + if (!defined($self->{server_pubkey})) { + $self->get_server_pubkey(); + } else { + $self->{handshake} = 1; + } + } + + if ($self->{handshake} == 1) { my ($status, $ciphertext) = gorgone::standard::library::client_helo_encrypt( identity => $self->{identity}, server_pubkey => $self->{server_pubkey}, client_pubkey => $self->{client_pubkey}, ); if ($status == -1) { - return (-1, 'crypt handshake issue'); + $self->{verbose_last_message} = 'crypt handshake issue'; + return (-1, $self->{verbose_last_message}); } - $self->{handshake} = 1; + $self->{verbose_last_message} = 'Handshake timeout'; zmq_sendmsg($sockets->{$self->{identity}}, $ciphertext, ZMQ_DONTWAIT); zmq_poll([$self->get_poll()], 10000); } - if ($self->{handshake} == 1) { + if ($self->{handshake} < 2) { $self->{handshake} = 0; - return (-1, 'Handshake timeout'); - } - if ($self->{handshake} == 0) { - return (-1, 'Handshake issue'); + return (-1, $self->{verbose_last_message}); } gorgone::standard::library::zmq_send_message( diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 724b8237570..2ebcf8d7b53 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -471,6 +471,17 @@ sub handshake { my ($self, %options) = @_; my ($identity, $message) = gorgone::standard::library::zmq_read_message(socket => $self->{external_socket}); + + # Test if it asks for the pubkey + if ($message =~ /^\s*\[GETPUBKEY\]/) { + gorgone::standard::library::zmq_core_pubkey_response( + socket => $self->{external_socket}, + identity => $identity, + pubkey => $self->{server_pubkey} + ); + return undef; + } + my ($status, $key) = gorgone::standard::library::is_handshake_done(dbh => $self->{db_gorgone}, identity => $identity); if ($status == 1) { diff --git a/gorgone/gorgone/modules/core/proxy/class.pm b/gorgone/gorgone/modules/core/proxy/class.pm index f8cd9dab745..3507dc2bdf9 100644 --- a/gorgone/gorgone/modules/core/proxy/class.pm +++ b/gorgone/gorgone/modules/core/proxy/class.pm @@ -147,7 +147,6 @@ sub connect { identity => 'proxy-' . $self->{core_id} . '-' . $options{id}, cipher => $self->{clients}->{$options{id}}->{cipher}, vector => $self->{clients}->{$options{id}}->{vector}, - server_pubkey => $self->{clients}->{$options{id}}->{server_pubkey}, client_pubkey => defined($self->{clients}->{$options{id}}->{client_pubkey}) && $self->{clients}->{$options{id}}->{client_pubkey} ne '' ? $self->{clients}->{$options{id}}->{client_pubkey} : $self->{config_core}->{pubkey}, diff --git a/gorgone/gorgone/modules/core/pull/hooks.pm b/gorgone/gorgone/modules/core/pull/hooks.pm index 47f127d3ede..4a5b0efae4d 100644 --- a/gorgone/gorgone/modules/core/pull/hooks.pm +++ b/gorgone/gorgone/modules/core/pull/hooks.pm @@ -61,7 +61,6 @@ sub init { identity => $config_core->{id}, cipher => $config->{cipher}, vector => $config->{vector}, - server_pubkey => $config->{server_pubkey}, client_pubkey => defined($config->{client_pubkey}) && $config->{client_pubkey} ne '' ? $config->{client_pubkey} : $config_core->{pubkey}, diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index 11988a53465..e228bfa9d2d 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -79,31 +79,35 @@ sub loadpubkey { my $quit = defined($options{noquit}) ? 0 : 1; my $string_key = ''; - if (!open FILE, "<" . $options{pubkey}) { - $options{logger}->writeLogError("Cannot read file '$options{pubkey}': $!"); - exit(1) if ($quit); - return 0; - } - while () { - $string_key .= $_; + if (defined($options{pubkey})) { + if (!open FILE, "<" . $options{pubkey}) { + $options{logger}->writeLogError("Cannot read file '$options{pubkey}': $!") if (defined($options{logger})); + exit(1) if ($quit); + return 0; + } + while () { + $string_key .= $_; + } + close FILE; + } else { + $string_key = $options{pubkey_str}; } - close FILE; - + my $pubkey; eval { $pubkey = Crypt::PK::RSA->new(\$string_key); }; if ($@) { - $options{logger}->writeLogError("Cannot load pubkey '$options{pubkey}': $@"); + $options{logger}->writeLogError("Cannot load pubkey '$options{pubkey}': $@") if (defined($options{logger})); exit(1) if ($quit); return 0; } if ($pubkey->is_private()) { - $options{logger}->writeLogError("'$options{pubkey}' is not a publickey"); + $options{logger}->writeLogError("'$options{pubkey}' is not a publickey") if (defined($options{logger})); exit(1) if ($quit); return 0; } - + return (1, $pubkey); } @@ -140,6 +144,19 @@ sub loadprivkey { return (1, $privkey); } +sub zmq_core_pubkey_response { + my (%options) = @_; + + if (defined($options{identity})) { + zmq_sendmsg($options{socket}, pack('H*', $options{identity}), ZMQ_NOBLOCK | ZMQ_SNDMORE); + } + my $client_pubkey = $options{pubkey}->export_key_pem('public'); + my $msg = '[PUBKEY] [' . unpack('H*', $client_pubkey) . ']'; + + zmq_sendmsg($options{socket}, $msg, ZMQ_NOBLOCK); + return 0; +} + sub zmq_core_key_response { my (%options) = @_; @@ -243,7 +260,7 @@ sub client_get_secret { my $hostname = pack('H*', $3); my $symkey = pack('H*', $5); - return (0, $symkey, $hostname); + return (0, 'ok', $symkey, $hostname); } sub client_helo_encrypt { From 00b780a699e6594f4dafff0eb827228b01aae6cc Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Mon, 2 Dec 2019 14:49:32 +0100 Subject: [PATCH 227/948] fix(proxy): setlogs bug --- gorgone/config/gorgoned-central-zmq.yml | 7 +++++++ gorgone/docs/guide.md | 8 ++++---- gorgone/gorgone/modules/core/proxy/hooks.pm | 14 +++++++------- gorgone/schema/gorgone_database.sql | 8 ++++++++ 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/gorgone/config/gorgoned-central-zmq.yml b/gorgone/config/gorgoned-central-zmq.yml index 89b136b309d..9175bf715b8 100644 --- a/gorgone/config/gorgoned-central-zmq.yml +++ b/gorgone/config/gorgoned-central-zmq.yml @@ -15,6 +15,13 @@ gorgonecore: cipher: "Cipher::AES" keysize: 32 vector: 0123456789012345 + # can be: always, first, strict + fingerprint_mode: first + fingerprint_mgr: + package: sql + # if unset, it uses global configuration + #gorgone_db_type: + #gorgone_db_name: modules: - name: httpserver package: gorgone::modules::core::httpserver::hooks diff --git a/gorgone/docs/guide.md b/gorgone/docs/guide.md index 6786d786ff7..7be9da6060f 100644 --- a/gorgone/docs/guide.md +++ b/gorgone/docs/guide.md @@ -163,8 +163,8 @@ An example of the json stream: "data": { "action": "getlog", "message": "ok", - "result": { - "10": { + "result": [ + { "id": 10, "token": "xxxx", "code": 1, @@ -172,7 +172,7 @@ An example of the json stream: "ctime": 1419252686, "data": "xxxx", }, - "100": { + { "id": 100, "token": "xxxx", "code": 1, @@ -180,7 +180,7 @@ An example of the json stream: "ctime": 1419252690, "data": "xxxx", } - } + ] } } ``` diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index b4beeeee0fa..99366e554ea 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -421,17 +421,17 @@ sub setlogs { # Transaction $options{dbh}->transaction_mode(1); my $status = 0; - foreach (keys %{$options{data}->{data}->{result}}) { + foreach (@{$options{data}->{data}->{result}}) { $status = gorgone::standard::library::add_history( dbh => $options{dbh}, - etime => $options{data}->{data}->{result}->{$_}->{etime}, - code => $options{data}->{data}->{result}->{$_}->{code}, - token => $options{data}->{data}->{result}->{$_}->{token}, - data => $options{data}->{data}->{result}->{$_}->{data} + etime => $_->{etime}, + code => $_->{code}, + token => $_->{token}, + data => $_->{data} ); last if ($status == -1); - $ctime_recent = $options{data}->{data}->{result}->{$_}->{ctime} if ($ctime_recent < $options{data}->{data}->{result}->{$_}->{ctime}); - $last_id = $options{data}->{data}->{result}->{$_}->{id} if ($last_id < $options{data}->{data}->{result}->{$_}->{id}); + $ctime_recent = $_->{ctime} if ($ctime_recent < $_->{ctime}); + $last_id = $_->{id} if ($last_id < $_->{id}); } if ($status == 0 && update_sync_time(dbh => $options{dbh}, id => $options{data}->{data}->{id}, last_id => $last_id, ctime => $ctime_recent) == 0) { $options{dbh}->commit(); diff --git a/gorgone/schema/gorgone_database.sql b/gorgone/schema/gorgone_database.sql index 1f586131438..5a87c32f5c4 100644 --- a/gorgone/schema/gorgone_database.sql +++ b/gorgone/schema/gorgone_database.sql @@ -33,3 +33,11 @@ CREATE TABLE IF NOT EXISTS `gorgone_synchistory` ( ); CREATE INDEX IF NOT EXISTS idx_gorgone_synchistory_id ON gorgone_synchistory (id); + +CREATE TABLE IF NOT EXISTS `gorgone_target_fingerprint` ( + `id` INTEGER PRIMARY KEY, + `target` varchar(2048) DEFAULT NULL, + `fingerprint` varchar(4096) DEFAULT NULL +); + +CREATE INDEX IF NOT EXISTS idx_gorgone_target_fingerprint_target ON gorgone_target_fingerprint (target); From c887dab65208708c273489ccd8345944cff3c8b2 Mon Sep 17 00:00:00 2001 From: loiclau Date: Mon, 2 Dec 2019 16:40:17 +0100 Subject: [PATCH 228/948] enh(core): add ssl dependance (#7) --- gorgone/packaging/centreon-gorgone.spectemplate | 1 + 1 file changed, 1 insertion(+) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 9b29a575adc..c9e6bb1eaac 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -26,6 +26,7 @@ Requires: perl(MIME::Base64) Requires: perl(Digest::MD5::File) Requires: perl(Libssh::Session) Requires: perl(Net::Curl::Easy) +Requires: perl(HTTP::Daemon::SSL) AutoReqProv: no %description From 21fc62b8d21979fe94747b8cdca7b7267c3cdacd Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Mon, 2 Dec 2019 16:44:08 +0100 Subject: [PATCH 229/948] enh(core): add fingerprint system --- gorgone/config/gorgoned-central-zmq.yml | 4 +- gorgone/gorgone/class/clientzmq.pm | 34 +++++++- gorgone/gorgone/class/core.pm | 9 +++ .../gorgone/class/fingerprint/backend/sql.pm | 80 +++++++++++++++++++ gorgone/gorgone/modules/core/proxy/class.pm | 3 +- gorgone/gorgone/modules/core/pull/hooks.pm | 1 + gorgone/gorgone/standard/library.pm | 10 +++ 7 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 gorgone/gorgone/class/fingerprint/backend/sql.pm diff --git a/gorgone/config/gorgoned-central-zmq.yml b/gorgone/config/gorgoned-central-zmq.yml index 9175bf715b8..87190131023 100644 --- a/gorgone/config/gorgoned-central-zmq.yml +++ b/gorgone/config/gorgoned-central-zmq.yml @@ -15,10 +15,10 @@ gorgonecore: cipher: "Cipher::AES" keysize: 32 vector: 0123456789012345 - # can be: always, first, strict + # can be: always, first (default), strict fingerprint_mode: first fingerprint_mgr: - package: sql + package: gorgone::class::fingerprint::backend::sql # if unset, it uses global configuration #gorgone_db_type: #gorgone_db_name: diff --git a/gorgone/gorgone/class/clientzmq.pm b/gorgone/gorgone/class/clientzmq.pm index 1a9ff285c48..6f2ebbb2288 100644 --- a/gorgone/gorgone/class/clientzmq.pm +++ b/gorgone/gorgone/class/clientzmq.pm @@ -23,6 +23,7 @@ package gorgone::class::clientzmq; use strict; use warnings; use gorgone::standard::library; +use gorgone::standard::misc; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); @@ -39,6 +40,22 @@ sub new { $connector->{vector} = $options{vector}; $connector->{symkey} = undef; $connector->{verbose_last_message} = ''; + $connector->{config_core} = $options{config_core}; + + if (defined($connector->{config_core}) && defined($connector->{config_core}->{fingerprint_mgr}->{package})) { + my ($code, $class_mgr) = gorgone::standard::misc::mymodule_load( + logger => $connector->{logger}, + module => $connector->{config_core}->{fingerprint_mgr}->{package}, + error_msg => "Cannot load module $connector->{config_core}->{fingerprint_mgr}->{package}" + ); + if ($code == 0) { + $connector->{fingerprint_mgr} = $class_mgr->new( + logger => $connector->{logger}, + config => $connector->{config_core}->{fingerprint_mgr}, + config_core => $connector->{config_core} + ); + } + } if (defined($options{server_pubkey}) && $options{server_pubkey} ne '') { (undef, $connector->{server_pubkey}) = gorgone::standard::library::loadpubkey( @@ -106,8 +123,9 @@ sub check_server_pubkey { return 0; } + my ($code, $verbose_message); my $server_pubkey_str = pack('H*', $1); - (my $code, $self->{server_pubkey}) = gorgone::standard::library::loadpubkey( + ($code, $self->{server_pubkey}) = gorgone::standard::library::loadpubkey( pubkey_str => $server_pubkey_str, logger => $self->{logger}, noquit => 1 @@ -119,6 +137,20 @@ sub check_server_pubkey { return 0; } + # if not set, we are in 'always' mode + if (defined($self->{fingerprint_mgr})) { + my $thumbprint = $self->{server_pubkey}->export_key_jwk_thumbprint('SHA256'); + ($code, $verbose_message) = $self->{fingerprint_mgr}->check_fingerprint( + target => $self->{target_type} . '://' . $self->{target_path}, + fingerprint => $thumbprint + ); + if ($code == 0) { + $self->{logger}->writeLogError($verbose_message) if (defined($self->{logger})); + $self->{verbose_last_message} = $verbose_message; + return 0; + } + } + return 1; } diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 2ebcf8d7b53..de925e02f3c 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -144,6 +144,15 @@ sub init { $config->{gorgonecore}->{timeout} = defined($config->{gorgonecore}->{timeout}) && $config->{gorgonecore}->{timeout} =~ /(\d+)/ ? $1 : 50; + $config->{gorgonecore}->{fingerprint_mode} = + defined($config->{gorgonecore}->{fingerprint_mode}) && $config->{gorgonecore}->{fingerprint_mode} =~ /^\s*(always|firt|strict)\s*/i ? lc($1) : 'first'; + $config->{gorgonecore}->{fingerprint_mgr} = {} if (!defined($config->{gorgonecore}->{fingerprint_mgr})); + $config->{gorgonecore}->{fingerprint_mgr}->{package} = 'gorgone::class::fingerprint::backend::sql' + if (!defined($config->{gorgonecore}->{fingerprint_mgr}->{package}) || $config->{gorgonecore}->{fingerprint_mgr}->{package} eq ''); + + $config->{gorgonecore}->{fingerprint_mode} = + defined($config->{gorgonecore}->{fingerprint_mode}) && $config->{gorgonecore}->{fingerprint_mode} =~ /^\s*(always|firt|strict)\s*/i ? lc($1) : 'first'; + $config->{gorgonecore}->{gorgone_db_type} = defined($config->{gorgonecore}->{gorgone_db_type}) && $config->{gorgonecore}->{gorgone_db_type} ne '' ? $config->{gorgonecore}->{gorgone_db_type} : 'SQLite'; $config->{gorgonecore}->{gorgone_db_name} = diff --git a/gorgone/gorgone/class/fingerprint/backend/sql.pm b/gorgone/gorgone/class/fingerprint/backend/sql.pm new file mode 100644 index 00000000000..e58e5c86252 --- /dev/null +++ b/gorgone/gorgone/class/fingerprint/backend/sql.pm @@ -0,0 +1,80 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::class::fingerprint::backend::sql; + +use base qw(gorgone::class::db); + +use strict; +use warnings; + +sub new { + my ($class, %options) = @_; + my $self = $class->SUPER::new( + logger => $options{logger}, + type => defined($options{config}->{gorgone_db_type}) && $options{config}->{gorgone_db_type} ne '' ? + $options{config}->{gorgone_db_type} : $options{config_core}->{gorgone_db_type}, + db => defined($options{config}->{gorgone_db_name}) && $options{config}->{gorgone_db_name} ne '' ? + $options{config}->{gorgone_db_name} : $options{config_core}->{gorgone_db_name}, + host => defined($options{config}->{gorgone_db_host}) && $options{config}->{gorgone_db_host} ne '' ? + $options{config}->{gorgone_db_host} : $options{config_core}->{gorgone_db_host}, + port => defined($options{config}->{gorgone_db_port}) && $options{config}->{gorgone_db_port} ne '' ? + $options{config}->{gorgone_db_port} : $options{config_core}->{gorgone_db_port}, + user => defined($options{config}->{gorgone_db_user}) && $options{config}->{gorgone_db_user} ne '' ? + $options{config}->{gorgone_db_user} : $options{config_core}->{gorgone_db_user}, + password => defined($options{config}->{gorgone_db_password}) && $options{config}->{gorgone_db_password} ne '' ? + $options{config}->{gorgone_db_password} : $options{config_core}->{gorgone_db_password}, + force => 2 + ); + bless $self, $class; + + $self->{fingerprint_mode} = $options{config_core}->{fingerprint_mode}; + + return $self; +} + +sub check_fingerprint { + my ($self, %options) = @_; + + return 1 if ($self->{fingerprint_mode} eq 'always'); + + my ($status, $sth) = $self->query("SELECT `id`, `fingerprint` FROM gorgone_target_fingerprint WHERE target = " . $self->quote($options{target}) . " ORDER BY id ASC LIMIT 1"); + return (0, "cannot get fingerprint for target '$options{target}'") if ($status == -1); + my $row = $sth->fetchrow_hashref(); + + if (!defined($row)) { + if ($self->{fingerprint_mode} eq 'strict') { + return (0, "no fingerprint found for target '" . $options{target} . "' [strict mode] [fingerprint: $options{fingerprint}]"); + } + ($status) = $self->query("INSERT INTO gorgone_target_fingerprint (`target`, `fingerprint`) VALUES (" + . $self->quote($options{target}) . ', ' . $self->quote($options{fingerprint}) . ')'); + return (0, "cannot insert target '$options{target}' fingerprint") if ($status == -1); + return 1; + } + + if ($row->{fingerprint} ne $options{fingerprint}) { + return (0, "fingerprint changed for target '" . $options{target} . "' [id: $row->{id}] [old fingerprint: $row->{id}] [new fingerprint: $options{fingerprint}]"); + } + return 1; +} + +1; + +__END__ diff --git a/gorgone/gorgone/modules/core/proxy/class.pm b/gorgone/gorgone/modules/core/proxy/class.pm index 3507dc2bdf9..c2d047fd5f7 100644 --- a/gorgone/gorgone/modules/core/proxy/class.pm +++ b/gorgone/gorgone/modules/core/proxy/class.pm @@ -159,7 +159,8 @@ sub connect { target_path => defined($self->{clients}->{$options{id}}->{target_path}) ? $self->{clients}->{$options{id}}->{target_path} : $self->{clients}->{$options{id}}->{address} . ':' . $self->{clients}->{$options{id}}->{port}, - logger => $self->{logger}, + config_core => $self->{config_core}, + logger => $self->{logger} ); $self->{clients}->{$options{id}}->{class}->init(callback => \&read_message); } elsif ($self->{clients}->{$options{id}}->{type} eq 'push_ssh') { diff --git a/gorgone/gorgone/modules/core/pull/hooks.pm b/gorgone/gorgone/modules/core/pull/hooks.pm index 4a5b0efae4d..c83477b9aad 100644 --- a/gorgone/gorgone/modules/core/pull/hooks.pm +++ b/gorgone/gorgone/modules/core/pull/hooks.pm @@ -69,6 +69,7 @@ sub init { $config->{client_privkey} : $config_core->{privkey}, target_type => $config->{target_type}, target_path => $config->{target_path}, + config_core => $config_core, logger => $options{logger}, ping => $config->{ping}, ping_timeout => $config->{ping_timeout} diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index e228bfa9d2d..e6f07ed26bb 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -854,6 +854,16 @@ sub init_database { q{ CREATE INDEX IF NOT EXISTS idx_gorgone_synchistory_id ON gorgone_synchistory (id); }, + q{ + CREATE TABLE IF NOT EXISTS `gorgone_target_fingerprint` ( + `id` INTEGER PRIMARY KEY, + `target` varchar(2048) DEFAULT NULL, + `fingerprint` varchar(4096) DEFAULT NULL + ); + }, + q{ + CREATE INDEX IF NOT EXISTS idx_gorgone_target_fingerprint_target ON gorgone_target_fingerprint (target); + }, ]; foreach (@$requests) { $options{gorgone}->{db_gorgone}->query($_); From e290e2a14910244aafc0913c755a648f5ad9c1db Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 3 Dec 2019 08:35:50 +0100 Subject: [PATCH 230/948] enh(core): fix module load flooding --- gorgone/gorgone/class/core.pm | 36 ++++++++++--------- .../gorgone/modules/core/httpserver/class.pm | 16 ++++++--- .../gorgone/modules/core/httpserver/hooks.pm | 6 ++-- 3 files changed, 35 insertions(+), 23 deletions(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index de925e02f3c..f4af8a19d6b 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -736,29 +736,33 @@ sub run { } $gorgone->{logger}->writeLogInfo("[core] Server accepting clients"); - + my $cb_timer_check = time(); while (1) { my $count = 0; my $poll = [@{$gorgone->{poll}}]; - foreach my $name (keys %{$gorgone->{modules_register}}) { - my $count_module = $gorgone->{modules_register}->{$name}->{check}->( - logger => $gorgone->{logger}, - dead_childs => $gorgone->{return_child}, - internal_socket => $gorgone->{internal_socket}, - dbh => $gorgone->{db_gorgone}, - poll => $poll, - modules_events => $gorgone->{modules_events}, - ); - $count += $count_module; - if ($count_module == 0) { - $gorgone->unload_module(package => $name); + my $current_time = time(); + if (time() - $cb_timer_check > 15) { + foreach my $name (keys %{$gorgone->{modules_register}}) { + my $count_module = $gorgone->{modules_register}->{$name}->{check}->( + logger => $gorgone->{logger}, + dead_childs => $gorgone->{return_child}, + internal_socket => $gorgone->{internal_socket}, + dbh => $gorgone->{db_gorgone}, + poll => $poll, + modules_events => $gorgone->{modules_events}, + ); + $cb_timer_check = time(); + $count += $count_module; + if ($count_module == 0) { + $gorgone->unload_module(package => $name); + } } + + # We can clean return_child. + $gorgone->{return_child} = {}; } - # We can clean return_child. - $gorgone->{return_child} = {}; - if ($gorgone->{stop} == 1) { # No childs if ($count == 0) { diff --git a/gorgone/gorgone/modules/core/httpserver/class.pm b/gorgone/gorgone/modules/core/httpserver/class.pm index 6fa6dc4f9b7..21dbf8736d6 100644 --- a/gorgone/gorgone/modules/core/httpserver/class.pm +++ b/gorgone/gorgone/modules/core/httpserver/class.pm @@ -23,10 +23,10 @@ package gorgone::modules::core::httpserver::class; use strict; use warnings; use gorgone::standard::library; +use gorgone::standard::misc; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); use HTTP::Daemon; -use HTTP::Daemon::SSL; use HTTP::Status; use MIME::Base64; use JSON::XS; @@ -47,7 +47,15 @@ sub new { $connector->{config_core} = $options{config_core}; $connector->{stop} = 0; $connector->{modules_events} = $options{modules_events}; - + + if ($connector->{config}->{ssl} eq 'true') { + exit(1) if (gorgone::standard::misc::mymodule_load( + logger => $connector->{logger}, + module => 'HTTP::Daemon::SSL', + error_msg => "[httpserver] -class- cannot load module 'HTTP::Daemon::SSL'") + ); + } + bless $connector, $class; $connector->set_signal_handlers; return $connector; @@ -178,8 +186,8 @@ sub run { sub ssl_error { my ($self, $error) = @_; - ${*$self}{'httpd_client_proto'} = 1000; - ${*$self}{'httpd_daemon'} = new HTTP::Daemon::SSL::DummyDaemon; + ${*$self}{httpd_client_proto} = 1000; + ${*$self}{httpd_daemon} = HTTP::Daemon::SSL::DummyDaemon->new(); $self->send_error(RC_BAD_REQUEST); $self->close; } diff --git a/gorgone/gorgone/modules/core/httpserver/hooks.pm b/gorgone/gorgone/modules/core/httpserver/hooks.pm index 9f4bdb7e481..a5cb53244d0 100644 --- a/gorgone/gorgone/modules/core/httpserver/hooks.pm +++ b/gorgone/gorgone/modules/core/httpserver/hooks.pm @@ -138,16 +138,16 @@ sub check { foreach my $pid (keys %{$options{dead_childs}}) { # Not me next if ($httpserver->{pid} != $pid); - + $httpserver = {}; delete $options{dead_childs}->{$pid}; if ($stop == 0) { create_child(logger => $options{logger}, modules_events => $options{modules_events}); } } - + $count++ if (defined($httpserver->{running}) && $httpserver->{running} == 1); - + return $count; } From c55205683c10b5f0e787da10deb788d7a07b7139 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 3 Dec 2019 10:34:31 +0100 Subject: [PATCH 231/948] enh(httpserver): add filter peer addr options --- gorgone/config/gorgoned-central-ssh.yml | 1 + gorgone/config/gorgoned-central-zmq.yml | 6 ++ gorgone/contrib/gorgone_config_init.pl | 5 ++ gorgone/docs/getting_started.md | 3 +- gorgone/docs/modules/core/httpserver.md | 7 +++ .../gorgone/modules/core/httpserver/class.pm | 62 ++++++++++++++++++- 6 files changed, 80 insertions(+), 4 deletions(-) diff --git a/gorgone/config/gorgoned-central-ssh.yml b/gorgone/config/gorgoned-central-ssh.yml index 4b413ac9e14..2929771516a 100644 --- a/gorgone/config/gorgoned-central-ssh.yml +++ b/gorgone/config/gorgoned-central-ssh.yml @@ -21,6 +21,7 @@ modules: ssl_cert_file: /etc/pki/tls/certs/server-cert.pem ssl_key_file: /etc/pki/tls/server-key.pem auth: + enabled: true user: admin password: password diff --git a/gorgone/config/gorgoned-central-zmq.yml b/gorgone/config/gorgoned-central-zmq.yml index 87190131023..750318b9d67 100644 --- a/gorgone/config/gorgoned-central-zmq.yml +++ b/gorgone/config/gorgoned-central-zmq.yml @@ -32,8 +32,14 @@ modules: ssl_cert_file: /etc/pki/tls/certs/server-cert.pem ssl_key_file: /etc/pki/tls/server-key.pem auth: + enabled: true user: admin password: password + allowed_hosts: + enabled: true + subnets: + - 127.0.0.1/32 + - 10.30.2.0/16 - name: cron package: gorgone::modules::core::cron::hooks diff --git a/gorgone/contrib/gorgone_config_init.pl b/gorgone/contrib/gorgone_config_init.pl index 5984e4135bd..97c30851d62 100644 --- a/gorgone/contrib/gorgone_config_init.pl +++ b/gorgone/contrib/gorgone_config_init.pl @@ -106,8 +106,13 @@ sub write_gorgone_config { ssl_cert_file: /etc/pki/tls/certs/server-cert.pem ssl_key_file: /etc/pki/tls/server-key.pem auth: + enabled: false user: admin password: password + allowed_hosts: + enabled: true + subnets: + - 127.0.0.1/32 - name: cron package: gorgone::modules::core::cron::hooks diff --git a/gorgone/docs/getting_started.md b/gorgone/docs/getting_started.md index 531f2e5b1b6..66113ebc52f 100644 --- a/gorgone/docs/getting_started.md +++ b/gorgone/docs/getting_started.md @@ -32,6 +32,7 @@ The daemon uses the following Perl modules: * HTTP::Daemon * HTTP::Status * MIME::Base64 + * NetAddr::IP * Repository 'epel': * HTTP::Daemon::SSL * Schedule::Cron @@ -43,7 +44,7 @@ The daemon uses the following Perl modules: Execute the following commands to install them all: ```bash -yum install 'perl(Schedule::Cron)' 'perl(Crypt::CBC)' 'perl(ZMQ::LibZMQ4)' 'perl(JSON::XS)' 'perl(YAML)' 'perl(DBD::SQLite)' 'perl(DBD::mysql)' 'perl(UUID)' 'perl(HTTP::Daemon)' 'perl(HTTP::Daemon::SSL)' 'perl(HTTP::Status)' 'perl(MIME::Base64)' +yum install 'perl(NetAddr::IP)' 'perl(Schedule::Cron)' 'perl(Crypt::CBC)' 'perl(ZMQ::LibZMQ4)' 'perl(JSON::XS)' 'perl(YAML)' 'perl(DBD::SQLite)' 'perl(DBD::mysql)' 'perl(UUID)' 'perl(HTTP::Daemon)' 'perl(HTTP::Daemon::SSL)' 'perl(HTTP::Status)' 'perl(MIME::Base64)' yum install packaging/packages/perl-CryptX-0.064-1.el7.x86_64 ``` diff --git a/gorgone/docs/modules/core/httpserver.md b/gorgone/docs/modules/core/httpserver.md index bd1920f54d1..bcbcb8ee9c1 100644 --- a/gorgone/docs/modules/core/httpserver.md +++ b/gorgone/docs/modules/core/httpserver.md @@ -16,6 +16,7 @@ It relies on a core API module to server Gorgone events and can dispatch any oth | ssl_cert_file | Path to the SSL certificate (if SSL enabled) | | | ssl_key_file | Path to the SSL key (if SSL enabled) | | | auth | Basic credentials to access the server | | +| allowed_hosts | Peer address to access the server | | #### Example @@ -29,8 +30,14 @@ ssl: true ssl_cert_file: /etc/pki/tls/certs/server-cert.pem ssl_key_file: /etc/pki/tls/server-key.pem auth: + enabled: true user: admin password: password +allowed_hosts: + enabled: true + subnets: + - 127.0.0.1/32 + - 10.30.2.0/16 ``` Below the configuration to add other endpoints: diff --git a/gorgone/gorgone/modules/core/httpserver/class.pm b/gorgone/gorgone/modules/core/httpserver/class.pm index 21dbf8736d6..0d67d08fc9e 100644 --- a/gorgone/gorgone/modules/core/httpserver/class.pm +++ b/gorgone/gorgone/modules/core/httpserver/class.pm @@ -30,6 +30,7 @@ use HTTP::Daemon; use HTTP::Status; use MIME::Base64; use JSON::XS; +use Socket; my $time = time(); @@ -56,6 +57,17 @@ sub new { ); } + $connector->{auth_enabled} = (defined($connector->{config}->{auth}->{enabled}) && $connector->{config}->{auth}->{enabled} eq 'true') ? 1 : 0; + + $connector->{allowed_hosts_enabled} = (defined($connector->{config}->{allowed_hosts}->{enabled}) && $connector->{config}->{allowed_hosts}->{enabled} eq 'true') ? 1 : 0; + if (gorgone::standard::misc::mymodule_load( + logger => $connector->{logger}, + module => 'NetAddr::IP', + error_msg => "[httpserver] -class- cannot load module 'NetAddr::IP'. Cannot use allowed_hosts configuration.") + ) { + $connector->{allowed_hosts_enabled} = 0; + } + bless $connector, $class; $connector->set_signal_handlers; return $connector; @@ -110,9 +122,41 @@ sub init_dispatch { if (defined($self->{config}->{dispatch}) && $self->{config}->{dispatch} ne ''); } +sub check_allowed_host { + my ($self, %options) = @_; + + my $subnet = NetAddr::IP->new($options{peer_addr} . '/32'); + foreach (@{$self->{peer_subnets}}) { + return 1 if ($_->contains($subnet)); + } + + return 0; +} + +sub load_peer_subnets { + my ($self, %options) = @_; + + return if ($connector->{allowed_hosts_enabled} == 0); + + $connector->{peer_subnets} = []; + return if (!defined($connector->{config}->{allowed_hosts}->{subnets})); + + foreach (@{$connector->{config}->{allowed_hosts}->{subnets}}) { + my $subnet = NetAddr::IP->new($_); + if (!defined($subnet)) { + $self->{logger}->writeLogError("[httpserver] -class- cannot load subnet: $_"); + next; + } + + push @{$connector->{peer_subnets}}, $subnet; + } +} + sub run { my ($self, %options) = @_; + $self->load_peer_subnets(); + # Connect internal $connector->{internal_socket} = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', @@ -127,6 +171,7 @@ sub run { data => { }, json_encode => 1 ); + $self->{poll} = [ { socket => $connector->{internal_socket}, @@ -137,7 +182,7 @@ sub run { my $rev = zmq_poll($self->{poll}, 4000); - $self->init_dispatch; + $self->init_dispatch(); # HTTP daemon my $daemon; @@ -157,10 +202,17 @@ sub run { } if (defined($daemon)) { - while (my ($connection, $peer_addr) = $daemon->accept) { + while (my ($connection, $peer_addr) = $daemon->accept()) { while (my $request = $connection->get_request) { $connector->{logger}->writeLogInfo("[httpserver] -class- " . $request->method . " '" . $request->uri->path . "'"); + if ($connector->{allowed_hosts_enabled} == 1) { + if ($connector->check_allowed_host(peer_addr => inet_ntoa($connection->peeraddr())) == 0) { + $connection->send_error(RC_UNAUTHORIZED); + next; + } + } + if ($self->authentication($request->header('Authorization'))) { # Check Basic authentication my ($root) = ($request->uri->path =~ /^(\/\w+)/); @@ -194,11 +246,15 @@ sub ssl_error { sub authentication { my ($self, $header) = @_; + + return 1 if ($self->{auth_enabled} == 0); + return 0 if (!defined($header) || $header eq ''); ($header =~ /Basic\s(.*)$/); my ($user, $password) = split(/:/, MIME::Base64::decode($1), 2); - return 1 if ($user eq $self->{config}->{auth}->{user} && $password eq $self->{config}->{auth}->{password}); + return 1 if (defined($self->{config}->{auth}->{user}) && $user eq $self->{config}->{auth}->{user} && + defined($self->{config}->{auth}->{password}) && $password eq $self->{config}->{auth}->{password}); return 0; } From 1bc3d5a20707b758c10ec5583851fb4bd9b1939b Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 3 Dec 2019 10:36:21 +0100 Subject: [PATCH 232/948] enh(doc): update sql schema --- gorgone/docs/getting_started.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/gorgone/docs/getting_started.md b/gorgone/docs/getting_started.md index 66113ebc52f..b8b129185da 100644 --- a/gorgone/docs/getting_started.md +++ b/gorgone/docs/getting_started.md @@ -104,6 +104,14 @@ CREATE TABLE IF NOT EXISTS `gorgone_synchistory` ( ); CREATE INDEX IF NOT EXISTS idx_gorgone_synchistory_id ON gorgone_synchistory (id); + +CREATE TABLE IF NOT EXISTS `gorgone_target_fingerprint` ( + `id` INTEGER PRIMARY KEY, + `target` varchar(2048) DEFAULT NULL, + `fingerprint` varchar(4096) DEFAULT NULL +); + +CREATE INDEX IF NOT EXISTS idx_gorgone_target_fingerprint_target ON gorgone_target_fingerprint (target); ``` ## Launch the daemon From 6d46edab046db1bb99d082ad8102d7bd0aa31023 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 3 Dec 2019 11:22:38 +0100 Subject: [PATCH 233/948] enh(core): use module internal send message method --- gorgone/gorgone/class/module.pm | 2 +- .../modules/centreon/autodiscovery/class.pm | 6 +-- .../gorgone/modules/centreon/broker/class.pm | 6 +-- .../gorgone/modules/centreon/engine/class.pm | 7 ++- .../modules/centreon/legacycmd/class.pm | 7 ++- .../gorgone/modules/centreon/pollers/class.pm | 7 ++- gorgone/gorgone/modules/core/action/class.pm | 7 ++- gorgone/gorgone/modules/core/cron/class.pm | 15 +++--- .../gorgone/modules/core/dbcleaner/class.pm | 6 +-- .../gorgone/modules/core/httpserver/class.pm | 8 ++-- gorgone/gorgone/modules/core/proxy/class.pm | 46 +++++++++---------- .../gorgone/modules/core/register/class.pm | 6 +-- .../gorgone/modules/plugins/newtest/class.pm | 7 ++- gorgone/gorgone/modules/plugins/scom/class.pm | 7 ++- 14 files changed, 58 insertions(+), 79 deletions(-) diff --git a/gorgone/gorgone/class/module.pm b/gorgone/gorgone/class/module.pm index 0fc45fecb47..1d7a958a110 100644 --- a/gorgone/gorgone/class/module.pm +++ b/gorgone/gorgone/class/module.pm @@ -46,7 +46,7 @@ sub send_internal_action { action => $options{action}, target => $options{target}, data => $options{data}, - json_encode => 1 + json_encode => defined($options{data_noencode}) ? undef : 1 ); } diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm index a90ce5ba8bf..389dac7c23a 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm @@ -357,11 +357,9 @@ sub run { type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); - gorgone::standard::library::zmq_send_message( - socket => $connector->{internal_socket}, + $connector->send_internal_action( action => 'AUTODISCOVERYREADY', - data => {}, - json_encode => 1 + data => {} ); $self->{poll} = [ { diff --git a/gorgone/gorgone/modules/centreon/broker/class.pm b/gorgone/gorgone/modules/centreon/broker/class.pm index a789e6f1c97..89ac113096a 100644 --- a/gorgone/gorgone/modules/centreon/broker/class.pm +++ b/gorgone/gorgone/modules/centreon/broker/class.pm @@ -244,11 +244,9 @@ sub run { type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); - gorgone::standard::library::zmq_send_message( - socket => $connector->{internal_socket}, + $connector->send_internal_action( action => 'BROKERREADY', - data => {}, - json_encode => 1 + data => {} ); $self->{poll} = [ { diff --git a/gorgone/gorgone/modules/centreon/engine/class.pm b/gorgone/gorgone/modules/centreon/engine/class.pm index adb39a98e43..4b8c6f7839f 100644 --- a/gorgone/gorgone/modules/centreon/engine/class.pm +++ b/gorgone/gorgone/modules/centreon/engine/class.pm @@ -258,10 +258,9 @@ sub run { type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); - gorgone::standard::library::zmq_send_message( - socket => $connector->{internal_socket}, - action => 'ENGINEREADY', data => { }, - json_encode => 1 + $connector->send_internal_action( + action => 'ENGINEREADY', + data => {} ); $self->{poll} = [ { diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index d7916f89f2a..3882f311c27 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -560,10 +560,9 @@ sub run { type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); - gorgone::standard::library::zmq_send_message( - socket => $connector->{internal_socket}, - action => 'LEGACYCMDREADY', data => {}, - json_encode => 1 + $connector->send_internal_action( + action => 'LEGACYCMDREADY', + data => {} ); $self->{db_centreon} = gorgone::class::db->new( diff --git a/gorgone/gorgone/modules/centreon/pollers/class.pm b/gorgone/gorgone/modules/centreon/pollers/class.pm index d79b8841596..2432070425c 100644 --- a/gorgone/gorgone/modules/centreon/pollers/class.pm +++ b/gorgone/gorgone/modules/centreon/pollers/class.pm @@ -192,10 +192,9 @@ sub run { type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); - gorgone::standard::library::zmq_send_message( - socket => $connector->{internal_socket}, - action => 'POLLERSREADY', data => { }, - json_encode => 1 + $connector->send_internal_action( + action => 'POLLERSREADY', + data => {} ); $self->{poll} = [ { diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index b2feb70b448..8db4c258e4b 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -325,10 +325,9 @@ sub run { type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); - gorgone::standard::library::zmq_send_message( - socket => $connector->{internal_socket}, - action => 'ACTIONREADY', data => { }, - json_encode => 1 + $connector->send_internal_action( + action => 'ACTIONREADY', + data => {} ); $self->{poll} = [ { diff --git a/gorgone/gorgone/modules/core/cron/class.pm b/gorgone/gorgone/modules/core/cron/class.pm index 024887f67d2..050b5f509d8 100644 --- a/gorgone/gorgone/modules/core/cron/class.pm +++ b/gorgone/gorgone/modules/core/cron/class.pm @@ -449,10 +449,9 @@ sub run { type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); - gorgone::standard::library::zmq_send_message( - socket => $connector->{internal_socket}, - action => 'CRONREADY', data => { }, - json_encode => 1 + $connector->send_internal_action( + action => 'CRONREADY', + data => {} ); $connector->{poll} = [ { @@ -462,14 +461,12 @@ sub run { } ]; + # need at least one cron to get sleep working push @{$self->{config}->{cron}}, { id => "default", timespec => "0 0 * * *", - action => "COMMAND", - parameters => { - command => "date >> /tmp/date.log", - timeout => 2, - } + action => "INFORMATION", + parameters => {} }; $self->{cron} = new Schedule::Cron(\&dispatcher, nostatus => 1, nofork => 1, catch => 1); diff --git a/gorgone/gorgone/modules/core/dbcleaner/class.pm b/gorgone/gorgone/modules/core/dbcleaner/class.pm index 29986971504..e617b2ba6c3 100644 --- a/gorgone/gorgone/modules/core/dbcleaner/class.pm +++ b/gorgone/gorgone/modules/core/dbcleaner/class.pm @@ -156,11 +156,9 @@ sub run { type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); - gorgone::standard::library::zmq_send_message( - socket => $connector->{internal_socket}, + $connector->send_internal_action( action => 'DBCLEANERREADY', - data => {}, - json_encode => 1 + data => {} ); $self->{poll} = [ { diff --git a/gorgone/gorgone/modules/core/httpserver/class.pm b/gorgone/gorgone/modules/core/httpserver/class.pm index 0d67d08fc9e..88532426856 100644 --- a/gorgone/gorgone/modules/core/httpserver/class.pm +++ b/gorgone/gorgone/modules/core/httpserver/class.pm @@ -20,6 +20,8 @@ package gorgone::modules::core::httpserver::class; +use base qw(gorgone::class::module); + use strict; use warnings; use gorgone::standard::library; @@ -165,11 +167,9 @@ sub run { type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); - gorgone::standard::library::zmq_send_message( - socket => $connector->{internal_socket}, + $connector->send_internal_action( action => 'HTTPSERVERREADY', - data => { }, - json_encode => 1 + data => {} ); $self->{poll} = [ diff --git a/gorgone/gorgone/modules/core/proxy/class.pm b/gorgone/gorgone/modules/core/proxy/class.pm index c2d047fd5f7..4a24d8f1671 100644 --- a/gorgone/gorgone/modules/core/proxy/class.pm +++ b/gorgone/gorgone/modules/core/proxy/class.pm @@ -98,25 +98,25 @@ sub read_message { } my ($action, $token, $data) = ($1, $2, $3); - gorgone::standard::library::zmq_send_message( - socket => $connector->{internal_socket}, + $connector->send_internal_action( action => 'PONG', - token => $token, - target => '', data => $data, + data_noencode => 1, + token => $token, + target => '' ); } elsif ($options{data} =~ /^\[REGISTERNODES|UNREGISTERNODES|SYNCLOGS\]/) { if ($options{data} !~ /^\[(.+?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)/m) { return undef; } my ($action, $token, $data) = ($1, $2, $3); - - gorgone::standard::library::zmq_send_message( - socket => $connector->{internal_socket}, + + $connector->send_internal_action( action => $action, + data => $data, + data_noencode => 1, token => $token, - target => '', - data => $data + target => '' ); } elsif ($options{data} =~ /^\[ACK\]\s+\[(.*?)\]\s+(.*)/m) { my $data; @@ -128,12 +128,12 @@ sub read_message { } if (defined($data->{data}->{action}) && $data->{data}->{action} eq 'getlog') { - gorgone::standard::library::zmq_send_message( - socket => $connector->{internal_socket}, + $connector->send_internal_action( action => 'SETLOGS', + data => $2, + data_noencode => 1, token => $1, - target => '', - data => $2 + target => '' ); } } @@ -294,13 +294,11 @@ sub proxy { if ($action eq 'PING') { if ($connector->{clients}->{$target_client}->{class}->ping() != -1) { - gorgone::standard::library::zmq_send_message( - socket => $connector->{internal_socket}, + $connector->send_internal_action( action => 'PONG', - token => $token, - target => '', data => { data => { id => $target_client } }, - json_encode => 1 + token => $token, + target => '' ); } return ; @@ -349,13 +347,11 @@ sub run { type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); - gorgone::standard::library::zmq_send_message( - socket => $self->{internal_socket}, + $connector->send_internal_action( action => 'PROXYREADY', data => { pool_id => $self->{pool_id} }, - json_encode => 1 ); my $poll = { socket => $self->{internal_socket}, @@ -367,12 +363,12 @@ sub run { foreach (keys %{$self->{clients}}) { if (defined($self->{clients}->{$_}->{delete}) && $self->{clients}->{$_}->{delete} == 1) { if ($self->{clients}->{$_}->{type} eq 'push_zmq') { - gorgone::standard::library::zmq_send_message( - socket => $self->{internal_socket}, + $connector->send_internal_action( action => 'PONGRESET', + data => '{ "id": ' . $_ . '}', + data_noencode => 1, token => $self->generate_token(), - target => '', - data => '{ "id": ' . $_ . '}' + target => '' ); } $self->{clients}->{$_}->{class}->close(); diff --git a/gorgone/gorgone/modules/core/register/class.pm b/gorgone/gorgone/modules/core/register/class.pm index d1fd34d4a91..db577dcb8c3 100644 --- a/gorgone/gorgone/modules/core/register/class.pm +++ b/gorgone/gorgone/modules/core/register/class.pm @@ -170,11 +170,9 @@ sub run { type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); - gorgone::standard::library::zmq_send_message( - socket => $connector->{internal_socket}, + $connector->send_internal_action( action => 'REGISTERREADY', - data => {}, - json_encode => 1 + data => {} ); $self->{poll} = [ { diff --git a/gorgone/gorgone/modules/plugins/newtest/class.pm b/gorgone/gorgone/modules/plugins/newtest/class.pm index 74478f8fe68..b53e944fdf9 100644 --- a/gorgone/gorgone/modules/plugins/newtest/class.pm +++ b/gorgone/gorgone/modules/plugins/newtest/class.pm @@ -643,10 +643,9 @@ sub run { type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); - gorgone::standard::library::zmq_send_message( - socket => $connector->{internal_socket}, - action => 'NEWTESTREADY', data => { container_id => $self->{container_id} }, - json_encode => 1 + $connector->send_internal_action( + action => 'NEWTESTREADY', + data => { container_id => $self->{container_id} } ); $self->{poll} = [ { diff --git a/gorgone/gorgone/modules/plugins/scom/class.pm b/gorgone/gorgone/modules/plugins/scom/class.pm index 48a2e7e98bf..793158e337d 100644 --- a/gorgone/gorgone/modules/plugins/scom/class.pm +++ b/gorgone/gorgone/modules/plugins/scom/class.pm @@ -522,10 +522,9 @@ sub run { type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); - gorgone::standard::library::zmq_send_message( - socket => $connector->{internal_socket}, - action => 'SCOMREADY', data => { container_id => $self->{container_id} }, - json_encode => 1 + $connector->send_internal_action( + action => 'SCOMREADY', + data => { container_id => $self->{container_id} } ); $self->{poll} = [ { From 20c28e15f34eb62ba12066a7e72756617d10e1a7 Mon Sep 17 00:00:00 2001 From: Loic Laurent Date: Tue, 3 Dec 2019 11:30:20 +0100 Subject: [PATCH 234/948] enh(core): remove service with uninstall --- gorgone/packaging/centreon-gorgone.spectemplate | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index c9e6bb1eaac..c35b88a622e 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -65,4 +65,13 @@ rm -rf %{buildroot} %attr(755,root,root) %{_sysconfdir}/systemd/system/centreon-gorgone.service %config(noreplace) %{_sysconfdir}/sysconfig/centreon-gorgone +%post +%systemd_post centreon-gorgone.service || : + +%preun +%systemd_preun centreon-gorgone.service || : + +%postun +%systemd_postun_with_restart centreon-gorgone.service || : + %changelog From d17280577d2ec2a4798fffea6de27f18b7b52523 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 3 Dec 2019 11:46:41 +0100 Subject: [PATCH 235/948] enh(spec): add NetAddr::IP --- gorgone/packaging/centreon-gorgone.spectemplate | 1 + 1 file changed, 1 insertion(+) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index c35b88a622e..2c93b3a28e0 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -27,6 +27,7 @@ Requires: perl(Digest::MD5::File) Requires: perl(Libssh::Session) Requires: perl(Net::Curl::Easy) Requires: perl(HTTP::Daemon::SSL) +Requires: perl(NetAddr::IP) AutoReqProv: no %description From 27815fc104de1332d91ad74420ede807b132dd72 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 3 Dec 2019 14:46:08 +0100 Subject: [PATCH 236/948] enh(core): add internal in api --- gorgone/docs/guide.md | 2 +- gorgone/gorgone/class/core.pm | 7 +- .../gorgone/modules/core/httpserver/class.pm | 2 +- gorgone/gorgone/standard/api.pm | 67 ++++++++++++++++++- gorgone/gorgone/standard/library.pm | 10 +++ 5 files changed, 82 insertions(+), 6 deletions(-) diff --git a/gorgone/docs/guide.md b/gorgone/docs/guide.md index 7be9da6060f..8b6f9b7436f 100644 --- a/gorgone/docs/guide.md +++ b/gorgone/docs/guide.md @@ -96,7 +96,7 @@ The client request: The server response: ```text -[ACK] [token_id] DATA +[CONSTATUS] [token_id] DATA ``` An example of the JSON stream: diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index f4af8a19d6b..8e6802aaa91 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -345,7 +345,8 @@ sub load_modules { $self->load_module(config_module => { name => 'dbcleaner', package => 'gorgone::modules::core::dbcleaner::hooks', enable => 'true' }); # Load internal functions - foreach my $method_name (('putlog', 'getlog', 'kill', 'ping', 'constatus', 'setcoreid', 'synclogs', 'loadmodule', 'unloadmodule', 'information')) { + foreach my $method_name (('putlog', 'getlog', 'kill', 'ping', + 'getthumbprint', 'constatus', 'setcoreid', 'synclogs', 'loadmodule', 'unloadmodule', 'information')) { unless ($self->{internal_register}->{$method_name} = gorgone::standard::library->can($method_name)) { $self->{logger}->writeLogError("[core] No function '$method_name'"); exit(1); @@ -371,7 +372,7 @@ sub message_run { $token = gorgone::standard::library::generate_token(); } - if ($action !~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID|SYNCLOGS|LOADMODULE|UNLOADMODULE|INFORMATION)$/ && + if ($action !~ /^(?:PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID|SYNCLOGS|LOADMODULE|UNLOADMODULE|INFORMATION|GETTHUMBPRINT)$/ && !defined($target) && !defined($self->{modules_events}->{$action})) { gorgone::standard::library::add_history( dbh => $self->{db_gorgone}, @@ -429,7 +430,7 @@ sub message_run { return ($token, 0); } - if ($action =~ /^(PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID|SYNCLOGS|LOADMODULE|UNLOADMODULE|INFORMATION)$/) { + if ($action =~ /^(?:PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID|SYNCLOGS|LOADMODULE|UNLOADMODULE|INFORMATION|GETTHUMBPRINT)$/) { my ($code, $response, $response_type) = $self->{internal_register}->{lc($action)}->( gorgone => $self, gorgone_config => $config, diff --git a/gorgone/gorgone/modules/core/httpserver/class.pm b/gorgone/gorgone/modules/core/httpserver/class.pm index 88532426856..c2b8f953bd9 100644 --- a/gorgone/gorgone/modules/core/httpserver/class.pm +++ b/gorgone/gorgone/modules/core/httpserver/class.pm @@ -217,7 +217,7 @@ sub run { my ($root) = ($request->uri->path =~ /^(\/\w+)/); if ($request->method eq 'GET' && $root eq "/status") { # Server status - $self->send_response(connection => $connection, response => $self->server_status); + $self->send_response(connection => $connection, response => $self->server_status()); } elsif ($root eq "/api") { # API $self->send_response(connection => $connection, response => $self->api_call($request)); } elsif (defined($self->{dispatch}->{$root})) { # Other dispatch definition diff --git a/gorgone/gorgone/standard/api.pm b/gorgone/gorgone/standard/api.pm index be9f1acca74..bb67854fcaf 100644 --- a/gorgone/gorgone/standard/api.pm +++ b/gorgone/gorgone/standard/api.pm @@ -53,6 +53,13 @@ sub root { token => $3, refresh => (defined($options{parameters}->{refresh})) ? $options{parameters}->{refresh} : undef ); + } elsif ($options{method} eq 'GET' && $options{uri} =~ /^\/api\/(targets\/(\w*)\/)?internal\/(.*)$/) { + $response = get_internal( + socket => $options{socket}, + target => $2, + action => $3, + refresh => (defined($options{parameters}->{refresh})) ? $options{parameters}->{refresh} : undef + ); } elsif ($options{uri} =~ /^\/api\/(targets\/(\w*)\/)?(\w+)\/(\w+)\/(\w+)\/?([\w\/]*?)$/ && defined($dispatch{$3 . '_' . $4 . '_' . $options{method} . '_/' . $5})) { my @variables = split(/\//, $6); @@ -115,6 +122,64 @@ sub call_action { return $response; } +sub get_internal { + my (%options) = @_; + + $socket = $options{socket}; + my $poll = [ + { + socket => $options{socket}, + events => ZMQ_POLLIN, + callback => \&event, + } + ]; + + if (!defined($options{action}) || $options{action} !~ /[a-z]/i) { + my $response = '{"error":"method_unknown","message":"Method not implemented"}'; + return $response; + } + + if (defined($options{target}) && $options{target} ne '') { + return call_action( + socket => $options{socket}, + target => $options{target}, + action => uc($options{action}), + data => {}, + json_encode => 1, + refresh => $options{refresh} + ); + } + + gorgone::standard::library::zmq_send_message( + socket => $options{socket}, + action => uc($options{action}), + data => {}, + json_encode => 1 + ); + + my $rev = zmq_poll($poll, 5000); + + my $response = '{"error":"no_result", "message":"No result found for action "' . uc($options{action}) . '"}'; + if (defined($result->{data})) { + my $content; + eval { + $content = JSON::XS->new->utf8->decode($result->{data}); + }; + if ($@) { + $response = '{"error":"decode_error","message":"Cannot decode response"}'; + } else { + eval { + $response = JSON::XS->new->utf8->encode($content->{data}); + }; + if ($@) { + $response = '{"error":"encode_error","message":"Cannot encode response"}'; + } + } + } + + return $response; +} + sub get_log { my (%options) = @_; @@ -167,7 +232,7 @@ sub get_log { if ($@) { $response = '{"error":"encode_error","message":"Cannot encode response"}'; } - } + } } return $response; diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index e6f07ed26bb..8ca2a2294ec 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -347,6 +347,16 @@ sub is_handshake_done { # internal functions ####################### +sub getthumbprint { + my (%options) = @_; + + if ($options{gorgone}->{keys_loaded} == 0) { + return (1, { action => 'getthumbprint', message => 'no public key loaded' }, 'GETTHUMBPRINT'); + } + my $thumbprint = $options{gorgone}->{server_pubkey}->export_key_jwk_thumbprint('SHA256'); + return (0, { action => 'getthumbprint', message => 'ok', data => { thumbprint => $thumbprint } }, 'GETTHUMBPRINT'); +} + sub information { my (%options) = @_; From 841a10799ec780611888ef5842e9338a60c40112 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 3 Dec 2019 14:49:55 +0100 Subject: [PATCH 237/948] enh(doc): add example --- gorgone/docs/modules/core/httpserver.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/gorgone/docs/modules/core/httpserver.md b/gorgone/docs/modules/core/httpserver.md index bcbcb8ee9c1..3bb732676eb 100644 --- a/gorgone/docs/modules/core/httpserver.md +++ b/gorgone/docs/modules/core/httpserver.md @@ -75,3 +75,14 @@ dispatch: curl --request GET "https://hostname:8443/status" \ --header "Accept: application/json" ``` + +```bash +curl --request GET "https://hostname:8443/api/internal/constatus" \ + --header "Accept: application/json" +``` + +```bash +curl --request GET "https://hostname:8443/api/internal/getthumbprint" \ + --header "Accept: application/json" +``` + From eba1fd27dfdc3916ea36160a4fc35584737506d6 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 3 Dec 2019 14:57:29 +0100 Subject: [PATCH 238/948] enh(api): better endpoint for internal --- gorgone/docs/modules/core/httpserver.md | 2 +- gorgone/gorgone/standard/api.pm | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/gorgone/docs/modules/core/httpserver.md b/gorgone/docs/modules/core/httpserver.md index 3bb732676eb..56ac4a9c35e 100644 --- a/gorgone/docs/modules/core/httpserver.md +++ b/gorgone/docs/modules/core/httpserver.md @@ -82,7 +82,7 @@ curl --request GET "https://hostname:8443/api/internal/constatus" \ ``` ```bash -curl --request GET "https://hostname:8443/api/internal/getthumbprint" \ +curl --request GET "https://hostname:8443/api/internal/thumbprint" \ --header "Accept: application/json" ``` diff --git a/gorgone/gorgone/standard/api.pm b/gorgone/gorgone/standard/api.pm index bb67854fcaf..d03ebdfff4c 100644 --- a/gorgone/gorgone/standard/api.pm +++ b/gorgone/gorgone/standard/api.pm @@ -31,6 +31,11 @@ use JSON::XS; my $socket; my $result; +my $mapping_internal_endpoint = { + thumbprint => 'GETTHUMBPRINT', + constatus => 'CONSTATUS' +}; + sub root { my (%options) = @_; @@ -57,7 +62,7 @@ sub root { $response = get_internal( socket => $options{socket}, target => $2, - action => $3, + endpoint => $3, refresh => (defined($options{parameters}->{refresh})) ? $options{parameters}->{refresh} : undef ); } elsif ($options{uri} =~ /^\/api\/(targets\/(\w*)\/)?(\w+)\/(\w+)\/(\w+)\/?([\w\/]*?)$/ @@ -134,16 +139,18 @@ sub get_internal { } ]; - if (!defined($options{action}) || $options{action} !~ /[a-z]/i) { - my $response = '{"error":"method_unknown","message":"Method not implemented"}'; + if (!defined($options{endpoint}) || !defined($mapping_internal_endpoint->{lc($options{endpoint})})) { + my $response = '{"error":"endpoint_unknown","message":"endpoint not implemented"}'; return $response; } + my $action = $mapping_internal_endpoint->{lc($options{endpoint})}; + if (defined($options{target}) && $options{target} ne '') { return call_action( socket => $options{socket}, target => $options{target}, - action => uc($options{action}), + action => $action, data => {}, json_encode => 1, refresh => $options{refresh} @@ -152,14 +159,14 @@ sub get_internal { gorgone::standard::library::zmq_send_message( socket => $options{socket}, - action => uc($options{action}), + action => $action, data => {}, json_encode => 1 ); my $rev = zmq_poll($poll, 5000); - my $response = '{"error":"no_result", "message":"No result found for action "' . uc($options{action}) . '"}'; + my $response = '{"error":"no_result", "message":"No result found for action "' . $action . '"}'; if (defined($result->{data})) { my $content; eval { From f8f2fde2115a626be311e1a4f8d73e8c86856da8 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Fri, 6 Dec 2019 15:38:39 +0100 Subject: [PATCH 239/948] enh(proxy): add pathscore system --- gorgone/config/gorgoned-central-zmq.yml | 3 - gorgone/config/gorgoned-poller.yml | 3 - gorgone/config/gorgoned-remote-zmq.yml | 3 - gorgone/config/registernodes-central.yml | 6 +- gorgone/config/registernodes-remote.yml | 3 - gorgone/docs/getting_started.md | 2 +- gorgone/docs/modules.md | 2 +- .../modules/centreon/{pollers.md => nodes.md} | 10 +- gorgone/docs/modules/core/register.md | 26 +-- gorgone/gorgone/class/clientzmq.pm | 6 +- gorgone/gorgone/class/core.pm | 10 +- .../centreon/{pollers => nodes}/class.pm | 34 +-- .../centreon/{pollers => nodes}/hooks.pm | 52 ++--- gorgone/gorgone/modules/core/proxy/class.pm | 53 ++--- gorgone/gorgone/modules/core/proxy/hooks.pm | 193 +++++++++++------- .../gorgone/modules/core/register/class.pm | 1 + 16 files changed, 208 insertions(+), 199 deletions(-) rename gorgone/docs/modules/centreon/{pollers.md => nodes.md} (58%) rename gorgone/gorgone/modules/centreon/{pollers => nodes}/class.pm (86%) rename gorgone/gorgone/modules/centreon/{pollers => nodes}/hooks.pm (67%) diff --git a/gorgone/config/gorgoned-central-zmq.yml b/gorgone/config/gorgoned-central-zmq.yml index 750318b9d67..52a09be2b82 100644 --- a/gorgone/config/gorgoned-central-zmq.yml +++ b/gorgone/config/gorgoned-central-zmq.yml @@ -12,9 +12,6 @@ database: gorgonecore: id: 1 privkey: keys/central/privkey.pem - cipher: "Cipher::AES" - keysize: 32 - vector: 0123456789012345 # can be: always, first (default), strict fingerprint_mode: first fingerprint_mgr: diff --git a/gorgone/config/gorgoned-poller.yml b/gorgone/config/gorgoned-poller.yml index 020ea0c9693..3a12c5958ca 100644 --- a/gorgone/config/gorgoned-poller.yml +++ b/gorgone/config/gorgoned-poller.yml @@ -5,9 +5,6 @@ gorgonecore: external_com_type: tcp external_com_path: "*:5556" privkey: keys/poller/privkey.pem - cipher: "Cipher::AES" - keysize: 32 - vector: 0123456789012345 authorized_clients: - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 modules: diff --git a/gorgone/config/gorgoned-remote-zmq.yml b/gorgone/config/gorgoned-remote-zmq.yml index 2c5852139af..eb28cdf20dc 100644 --- a/gorgone/config/gorgoned-remote-zmq.yml +++ b/gorgone/config/gorgoned-remote-zmq.yml @@ -14,9 +14,6 @@ gorgonecore: external_com_type: tcp external_com_path: "*:5556" privkey: keys/central/privkey.pem - cipher: "Cipher::AES" - keysize: 32 - vector: 0123456789012345 authorized_clients: - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 modules: diff --git a/gorgone/config/registernodes-central.yml b/gorgone/config/registernodes-central.yml index 9aa87eefd1c..73acc437064 100644 --- a/gorgone/config/registernodes-central.yml +++ b/gorgone/config/registernodes-central.yml @@ -3,8 +3,6 @@ nodes: type: push_zmq address: 10.30.2.135 port: 5556 - cipher: "Cipher::AES" - keysize: 32 - vector: 0123456789012345 nodes: - - 2 + - id: 2 + pathscore: 1 diff --git a/gorgone/config/registernodes-remote.yml b/gorgone/config/registernodes-remote.yml index f23c4a69208..41a0e672033 100644 --- a/gorgone/config/registernodes-remote.yml +++ b/gorgone/config/registernodes-remote.yml @@ -3,6 +3,3 @@ nodes: type: push_zmq address: 10.30.2.90 port: 5556 - cipher: "Cipher::AES" - keysize: 32 - vector: 0123456789012345 diff --git a/gorgone/docs/getting_started.md b/gorgone/docs/getting_started.md index b8b129185da..c90e585de4c 100644 --- a/gorgone/docs/getting_started.md +++ b/gorgone/docs/getting_started.md @@ -134,7 +134,7 @@ $ systemctl status centreon-gorgone ├─5168 /usr/bin/perl /usr/bin/gorgoned --config=/etc/centreon/gorgoned.yml --logfile=/var/log/centreon/gorgoned.log --severity=error ├─5175 gorgone-dbcleaner ├─5182 gorgone-action - ├─5187 gorgone-pollers + ├─5187 gorgone-nodes ├─5190 gorgone-legacycmd ├─5203 gorgone-proxy ├─5204 gorgone-proxy diff --git a/gorgone/docs/modules.md b/gorgone/docs/modules.md index e62cb385ec4..56ee20e2ca3 100644 --- a/gorgone/docs/modules.md +++ b/gorgone/docs/modules.md @@ -15,7 +15,7 @@ List of the available modules: * [Broker](../docs/modules/centreon/broker.md) * [Engine](../docs/modules/centreon/engine.md) * [Legacy Cmd](../docs/modules/centreon/legacycmd.md) - * [Pollers](../docs/modules/centreon/pollers.md) + * [Nodes](../docs/modules/centreon/nodes.md) * Plugins * [Newtest](../docs/modules/plugins/newtest.md) * [Scom](../docs/modules/plugins/scom.md) diff --git a/gorgone/docs/modules/centreon/pollers.md b/gorgone/docs/modules/centreon/nodes.md similarity index 58% rename from gorgone/docs/modules/centreon/pollers.md rename to gorgone/docs/modules/centreon/nodes.md index 75f2fd20dd5..8afa7396d25 100644 --- a/gorgone/docs/modules/centreon/pollers.md +++ b/gorgone/docs/modules/centreon/nodes.md @@ -1,10 +1,10 @@ -# Pollers +# Nodes ## Description This module aims to automatically register Poller servers as Gorgone targets, in opposition to the [register](../core/register.md) module. -For now, targets will only be registered as SSH targets. +For now, targets can be registered as SSH targets or ZMQ targets. ## Configuration @@ -13,8 +13,8 @@ No specific configuration. #### Example ```yaml -name: pollers -package: "gorgone::modules::centreon::pollers::hooks" +name: nodes +package: "gorgone::modules::centreon::nodes::hooks" enable: true ``` @@ -22,7 +22,7 @@ enable: true | Event | Description | | :- | :- | -| POLLERSREADY | Internal event to notify the core | +| CENTREONNODESREADY | Internal event to notify the core | ## API diff --git a/gorgone/docs/modules/core/register.md b/gorgone/docs/modules/core/register.md index 7f0601eeb3a..900db087806 100644 --- a/gorgone/docs/modules/core/register.md +++ b/gorgone/docs/modules/core/register.md @@ -29,17 +29,16 @@ Targets are listed in a separate configuration file in a `nodes` table as below: | Directive | Description | | :- | :- | -| id | Unique identifier of the target (can be Poller's ID if [pollers](../centreon/pollers.md) module is not used) | +| id | Unique identifier of the target (can be Poller's ID if [nodes](../centreon/nodes.md) module is not used) | | type | Way for the daemon to connect to the target (push_zmq) | | address | IP address of the target | | port | Port to connect to on the target | -| server_pubkey | Server public key | -| client_pubkey | Client public key | -| client_privkey | Client private key | -| cipher | Cipher used for encryption | -| keysize | Size in bytes of the symmetric encryption key | -| vector | Encryption vector | -| nodes | Table to register subnodes managed by target | +| server_pubkey | Server public key (Default: ask the server pubkey when it connects) | +| client_pubkey | Client public key (Default: use global public key) | +| client_privkey | Client private key (Default: use global private key) | +| cipher | Cipher used for encryption (Default: "Cipher::AES") | +| vector | Encryption vector (Default: 0123456789012345) | +| nodes | Table to register subnodes managed by target (pathscore is not mandatory) | #### Example @@ -49,14 +48,11 @@ nodes: type: push_zmq address: 10.1.2.3 port: 5556 - server_pubkey: keys/poller/pubkey.crt - client_pubkey: keys/central/pubkey.crt - client_privkey: keys/central/privkey.pem - cipher: "Cipher::AES" - keysize: 32 - vector: 0123456789012345 nodes: - - 2 + - id: 2 + pathscore: 1 + - id: 20 + pathscore: 10 ``` ##### Using SSH diff --git a/gorgone/gorgone/class/clientzmq.pm b/gorgone/gorgone/class/clientzmq.pm index 6f2ebbb2288..85387f3c827 100644 --- a/gorgone/gorgone/class/clientzmq.pm +++ b/gorgone/gorgone/class/clientzmq.pm @@ -36,8 +36,10 @@ sub new { my $connector = {}; $connector->{logger} = $options{logger}; $connector->{identity} = $options{identity}; - $connector->{cipher} = $options{cipher}; - $connector->{vector} = $options{vector}; + + $connector->{cipher} = defined($options{cipher}) && $options{cipher} ne '' ? $options{cipher} : 'Cipher::AES'; + $connector->{vector} = defined($options{vector}) && $options{vector} ne '' ? $options{vector} : '0123456789012345'; + $connector->{symkey} = undef; $connector->{verbose_last_message} = ''; $connector->{config_core} = $options{config_core}; diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 8e6802aaa91..b631ece93af 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -144,6 +144,13 @@ sub init { $config->{gorgonecore}->{timeout} = defined($config->{gorgonecore}->{timeout}) && $config->{gorgonecore}->{timeout} =~ /(\d+)/ ? $1 : 50; + $config->{gorgonecore}->{cipher} = + defined($config->{gorgonecore}->{cipher}) && $config->{gorgonecore}->{cipher} ne '' ? $config->{gorgonecore}->{cipher} : 'Cipher::AES'; + $config->{gorgonecore}->{keysize} = + defined($config->{gorgonecore}->{keysize}) && $config->{gorgonecore}->{keysize} ne '' ? $config->{gorgonecore}->{keysize} : 32; + $config->{gorgonecore}->{vector} = + defined($config->{gorgonecore}->{vector}) && $config->{gorgonecore}->{vector} ne '' ? $config->{gorgonecore}->{vector} : '0123456789012345'; + $config->{gorgonecore}->{fingerprint_mode} = defined($config->{gorgonecore}->{fingerprint_mode}) && $config->{gorgonecore}->{fingerprint_mode} =~ /^\s*(always|firt|strict)\s*/i ? lc($1) : 'first'; $config->{gorgonecore}->{fingerprint_mgr} = {} if (!defined($config->{gorgonecore}->{fingerprint_mgr})); @@ -402,7 +409,8 @@ sub message_run { # Check Routing if (defined($target)) { - if (!defined($self->{modules_register}->{ $self->{modules_id}->{$config->{gorgonecore}->{proxy_name}} })) { + if (!defined($self->{modules_id}->{$config->{gorgonecore}->{proxy_name}}) || + !defined($self->{modules_register}->{ $self->{modules_id}->{$config->{gorgonecore}->{proxy_name}} })) { gorgone::standard::library::add_history( dbh => $self->{db_gorgone}, code => 1, diff --git a/gorgone/gorgone/modules/centreon/pollers/class.pm b/gorgone/gorgone/modules/centreon/nodes/class.pm similarity index 86% rename from gorgone/gorgone/modules/centreon/pollers/class.pm rename to gorgone/gorgone/modules/centreon/nodes/class.pm index 2432070425c..5dfe1a1a4dc 100644 --- a/gorgone/gorgone/modules/centreon/pollers/class.pm +++ b/gorgone/gorgone/modules/centreon/nodes/class.pm @@ -18,7 +18,7 @@ # limitations under the License. # -package gorgone::modules::centreon::pollers::class; +package gorgone::modules::centreon::nodes::class; use base qw(gorgone::class::module); @@ -46,7 +46,7 @@ sub new { $connector->{config_core} = $options{config_core}; $connector->{config_db_centreon} = $options{config_db_centreon}; $connector->{stop} = 0; - $connector->{register_pollers} = {}; + $connector->{register_nodes} = {}; $connector->{resync_time} = (defined($options{config}->{resync_time}) && $options{config}->{resync_time} =~ /(\d+)/) ? $1 : 600; $connector->{last_resync_time} = -1; @@ -72,7 +72,7 @@ sub handle_HUP { sub handle_TERM { my $self = shift; - $self->{logger}->writeLogInfo("[pollers] -class- $$ Receiving order to stop..."); + $self->{logger}->writeLogInfo("[nodes] -class- $$ Receiving order to stop..."); $self->{stop} = 1; } @@ -88,12 +88,12 @@ sub class_handle_HUP { } } -sub action_pollersresync { +sub action_nodesresync { my ($self, %options) = @_; $options{token} = $self->generate_token() if (!defined($options{token})); - $self->send_log(code => gorgone::class::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action pollersresync proceed' }); + $self->send_log(code => gorgone::class::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action nodesresync proceed' }); my $request = " SELECT id, name, localhost, ns_ip_address, ssh_port, remote_id, remote_server_centcore_ssh_proxy @@ -102,8 +102,8 @@ sub action_pollersresync { "; my ($status, $datas) = $self->{class_object}->custom_execute(request => $request, mode => 2); if ($status == -1) { - $self->send_log(code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot find pollers configuration' }); - $self->{logger}->writeLogError("[pollers] -class- cannot find pollers configuration"); + $self->send_log(code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot find nodes configuration' }); + $self->{logger}->writeLogError("[nodes] -class- cannot find nodes configuration"); return 1; } @@ -123,16 +123,16 @@ sub action_pollersresync { push @{$register_subnodes->{$_->[5]}}, $_->[0]; next; } - $self->{register_pollers}->{$_->[0]} = 1; + $self->{register_nodes}->{$_->[0]} = 1; $register_temp->{$_->[0]} = 1; push @{$register_nodes}, { id => $_->[0], type => 'push_ssh', address => $_->[3], ssh_port => $_->[4] }; } my $unregister_nodes = []; - foreach (keys %{$self->{register_pollers}}) { + foreach (keys %{$self->{register_nodes}}) { if (!defined($register_temp->{$_})) { push @{$unregister_nodes}, { id => $_ }; - delete $self->{register_pollers}->{$_}; + delete $self->{register_nodes}->{$_}; } } @@ -147,8 +147,8 @@ sub action_pollersresync { $self->send_internal_action(action => 'REGISTERNODES', data => { nodes => $register_nodes } ); $self->send_internal_action(action => 'UNREGISTERNODES', data => { nodes => $unregister_nodes } ); - $self->{logger}->writeLogDebug("[pollers] -class- finish resync"); - $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => { message => 'action pollersresync finished' }); + $self->{logger}->writeLogDebug("[nodes] -class- finish resync"); + $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => { message => 'action nodesresync finished' }); return 0; } @@ -156,7 +156,7 @@ sub event { while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); - $connector->{logger}->writeLogDebug("[pollers] -class- Event: $message"); + $connector->{logger}->writeLogDebug("[nodes] -class- Event: $message"); if ($message =~ /^\[(.*?)\]/) { if ((my $method = $connector->can('action_' . lc($1)))) { $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; @@ -187,13 +187,13 @@ sub run { # Connect internal $connector->{internal_socket} = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', - name => 'gorgonepollers', + name => 'gorgonenodes', logger => $self->{logger}, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); $connector->send_internal_action( - action => 'POLLERSREADY', + action => 'CENTREONNODESREADY', data => {} ); $self->{poll} = [ @@ -207,14 +207,14 @@ sub run { # we try to do all we can my $rev = zmq_poll($self->{poll}, 5000); if (defined($rev) && $rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("[pollers] -class- $$ has quit"); + $self->{logger}->writeLogInfo("[nodes] -class- $$ has quit"); zmq_close($connector->{internal_socket}); exit(0); } if (time() - $self->{resync_time} > $self->{last_resync_time}) { $self->{last_resync_time} = time(); - $self->action_pollersresync(); + $self->action_nodesresync(); } } } diff --git a/gorgone/gorgone/modules/centreon/pollers/hooks.pm b/gorgone/gorgone/modules/centreon/nodes/hooks.pm similarity index 67% rename from gorgone/gorgone/modules/centreon/pollers/hooks.pm rename to gorgone/gorgone/modules/centreon/nodes/hooks.pm index d18e5895457..7eeb22d5e11 100644 --- a/gorgone/gorgone/modules/centreon/pollers/hooks.pm +++ b/gorgone/gorgone/modules/centreon/nodes/hooks.pm @@ -18,24 +18,24 @@ # limitations under the License. # -package gorgone::modules::centreon::pollers::hooks; +package gorgone::modules::centreon::nodes::hooks; use warnings; use strict; use JSON::XS; use gorgone::class::core; -use gorgone::modules::centreon::pollers::class; +use gorgone::modules::centreon::nodes::class; use constant NAMESPACE => 'centreon'; -use constant NAME => 'pollers'; +use constant NAME => 'nodes'; use constant EVENTS => [ - { event => 'POLLERSREADY' }, + { event => 'CENTREONNODESREADY' }, ]; my $config_core; my $config; my ($config_db_centreon); -my $pollers = {}; +my $nodes = {}; my $stop = 0; sub register { @@ -62,26 +62,26 @@ sub routing { $data = JSON::XS->new->utf8->decode($options{data}); }; if ($@) { - $options{logger}->writeLogError("[pollers] -hooks- Cannot decode json data: $@"); + $options{logger}->writeLogError("[nodes] -hooks- Cannot decode json data: $@"); gorgone::standard::library::add_history( dbh => $options{dbh}, code => 10, token => $options{token}, - data => { message => 'gorgonepollers: cannot decode json' }, + data => { message => 'gorgonenodes: cannot decode json' }, json_encode => 1 ); return undef; } - if ($options{action} eq 'POLLERSREADY') { - $pollers->{ready} = 1; + if ($options{action} eq 'CENTREONNODESREADY') { + $nodes->{ready} = 1; return undef; } - if (gorgone::class::core::waiting_ready(ready => \$pollers->{ready}) == 0) { + if (gorgone::class::core::waiting_ready(ready => \$nodes->{ready}) == 0) { gorgone::standard::library::add_history( dbh => $options{dbh}, code => 10, token => $options{token}, - data => { message => 'gorgonepollers: still no ready' }, + data => { message => 'gorgonenodes: still no ready' }, json_encode => 1 ); return undef; @@ -89,7 +89,7 @@ sub routing { gorgone::standard::library::zmq_send_message( socket => $options{socket}, - identity => 'gorgonepollers', + identity => 'gorgonenodes', action => $options{action}, data => $options{data}, token => $options{token}, @@ -100,18 +100,18 @@ sub gently { my (%options) = @_; $stop = 1; - $options{logger}->writeLogInfo("[pollers] -hooks- Send TERM signal"); - if ($pollers->{running} == 1) { - CORE::kill('TERM', $pollers->{pid}); + $options{logger}->writeLogInfo("[nodes] -hooks- Send TERM signal"); + if ($nodes->{running} == 1) { + CORE::kill('TERM', $nodes->{pid}); } } sub kill { my (%options) = @_; - if ($pollers->{running} == 1) { - $options{logger}->writeLogInfo("[pollers] -hooks- Send KILL signal for pool"); - CORE::kill('KILL', $pollers->{pid}); + if ($nodes->{running} == 1) { + $options{logger}->writeLogInfo("[nodes] -hooks- Send KILL signal for pool"); + CORE::kill('KILL', $nodes->{pid}); } } @@ -126,16 +126,16 @@ sub check { my $count = 0; foreach my $pid (keys %{$options{dead_childs}}) { # Not me - next if ($pollers->{pid} != $pid); + next if ($nodes->{pid} != $pid); - $pollers = {}; + $nodes = {}; delete $options{dead_childs}->{$pid}; if ($stop == 0) { create_child(logger => $options{logger}); } } - $count++ if (defined($pollers->{running}) && $pollers->{running} == 1); + $count++ if (defined($nodes->{running}) && $nodes->{running} == 1); return $count; } @@ -144,11 +144,11 @@ sub check { sub create_child { my (%options) = @_; - $options{logger}->writeLogInfo("[pollers] -hooks- Create module 'pollers' process"); + $options{logger}->writeLogInfo("[nodes] -hooks- Create module 'nodes' process"); my $child_pid = fork(); if ($child_pid == 0) { - $0 = 'gorgone-pollers'; - my $module = gorgone::modules::centreon::pollers::class->new( + $0 = 'gorgone-nodes'; + my $module = gorgone::modules::centreon::nodes::class->new( logger => $options{logger}, config_core => $config_core, config => $config, @@ -157,8 +157,8 @@ sub create_child { $module->run(); exit(0); } - $options{logger}->writeLogInfo("[pollers] -hooks- PID $child_pid (gorgone-pollers)"); - $pollers = { pid => $child_pid, ready => 0, running => 1 }; + $options{logger}->writeLogInfo("[nodes] -hooks- PID $child_pid (gorgone-nodes)"); + $nodes = { pid => $child_pid, ready => 0, running => 1 }; } 1; diff --git a/gorgone/gorgone/modules/core/proxy/class.pm b/gorgone/gorgone/modules/core/proxy/class.pm index 4a24d8f1671..7142dc05f9a 100644 --- a/gorgone/gorgone/modules/core/proxy/class.pm +++ b/gorgone/gorgone/modules/core/proxy/class.pm @@ -47,7 +47,6 @@ sub new { $connector->{config_core} = $options{config_core}; $connector->{stop} = 0; $connector->{clients} = {}; - $connector->{subnodes} = {}; bless $connector, $class; $connector->set_signal_handlers(); @@ -90,7 +89,7 @@ sub read_message { my (%options) = @_; return undef if (!defined($options{identity}) || $options{identity} !~ /^proxy-(.*?)-(.*?)$/); - + my ($client_identity) = ($2); if ($options{data} =~ /^\[PONG\]/) { if ($options{data} !~ /^\[(.+?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)/m) { @@ -190,15 +189,6 @@ sub action_proxyaddnode { $self->{clients}->{$data->{id}} = $data; $self->{clients}->{$data->{id}}->{delete} = 0; $self->{clients}->{$data->{id}}->{class} = undef; - - my $temp = {}; - foreach (@{$data->{nodes}}) { - $temp->{$_} = 1; - $self->{subnodes}->{$_} = $data->{id}; - } - foreach (keys %{$self->{subnodes}}) { - delete $self->{subnodes}->{$_} if ($self->{subnodes}->{$_} eq $data->{id} && !defined($temp->{$_})); - } } sub action_proxydelnode { @@ -210,24 +200,6 @@ sub action_proxydelnode { if (defined($self->{clients}->{$data->{id}})) { $self->{clients}->{$data->{id}}->{delete} = 1; } - - foreach (keys %{$self->{subnodes}}) { - delete $self->{subnodes}->{$_} if ($self->{subnodes}->{$_} eq $data->{id}); - } -} - -sub action_proxyaddsubnode { - my ($self, %options) = @_; - - my ($code, $data) = $self->json_decode(argument => $options{data}); - return if ($code == 1); - - foreach (keys %{$self->{subnodes}}) { - delete $self->{subnodes}->{$_} if ($self->{subnodes}->{$_} eq $data->{id}); - } - foreach (keys %{$data->{nodes}}) { - $self->{subnodes}->{$_} = $data->{id}; - } } sub proxy { @@ -236,25 +208,32 @@ sub proxy { if ($options{message} !~ /^\[(.+?)\]\s+\[(.*?)\]\s+\[(.*?)\]\s+(.*)$/m) { return undef; } - my ($action, $token, $target, $data) = ($1, $2, $3, $4); + my ($action, $token, $target_complete, $data) = ($1, $2, $3, $4); $connector->{logger}->writeLogDebug( - "[proxy] -class- Send message: [action = $action] [token = $token] [target = $target] [data = $data]" + "[proxy] -class- Send message: [action = $action] [token = $token] [target = $target_complete] [data = $data]" ); if ($action eq 'PROXYADDNODE') { action_proxyaddnode($connector, data => $data); return ; - } elsif ($action eq 'PROXYADDSUBNODE') { - action_proxyaddsubnode($connector, data => $data); - return ; } elsif ($action eq 'PROXYDELNODE') { action_proxydelnode($connector, data => $data); return ; } - my ($target_client, $target_direct) = ($target, 1); - if (!defined($connector->{clients}->{$target})) { - $target_client = $connector->{subnodes}->{$target}; + if ($target_complete !~ /^(.+)~~(.+)$/) { + $connector->send_log( + code => gorgone::class::module::ACTION_FINISH_KO, + token => $token, + data => { + message => "unknown target format '$target_complete'" + } + ); + return ; + } + + my ($target_client, $target, $target_direct) = ($1, $2, 1); + if ($target_client ne $target) { $target_direct = 0; } if (!defined($connector->{clients}->{$target_client}->{class})) { diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index 99366e554ea..bfe33215497 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -60,6 +60,21 @@ my $ping_time = 0; my $last_pong = {}; my $register_nodes = {}; +# With static routes we have a pathscore. Dynamic no pathscore. +# algo is: we use static routes first. after we use dynamic routes +# { +# subnode_id => { +# static => { +# parent_id1 => 1, +# parent_id2 => 2, +# }, +# dynamic => { +# parent_id3 => 1, +# parent_id5 => 1, +# }, +# } +# } +# my $register_subnodes = {}; my $constatus_ping = {}; my $parent_ping = {}; @@ -153,7 +168,8 @@ sub routing { action => 'PROXYADDNODE', target => $node_id, data => JSON::XS->new->utf8->encode($register_nodes->{$node_id}), - dbh => $options{dbh} + dbh => $options{dbh}, + logger => $options{logger} ); } return undef; @@ -164,30 +180,27 @@ sub routing { return undef; } - if (!defined($options{target}) || - (!defined($register_subnodes->{$options{target}}) && !defined($register_nodes->{$options{target}}))) { - gorgone::standard::library::add_history( - dbh => $options{dbh}, - code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, - data => { message => 'proxy - need a valid node id' }, - json_encode => 1 - ); - return undef; - } + my ($code, $target_complete, $target_parent, $target) = pathway( + target => $options{target}, + dbh => $options{dbh}, + token => $options{token}, + logger => $options{logger} + ); + return if ($code == -1); if ($options{action} eq 'GETLOG') { - if (defined($register_nodes->{$options{target}}) && $register_nodes->{$options{target}}->{type} eq 'push_ssh') { + if (defined($register_nodes->{$target_parent}) && $register_nodes->{$target_parent}->{type} eq 'push_ssh') { gorgone::standard::library::add_history( dbh => $options{dbh}, code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, - data => { message => "proxy - can't get log a ssh target" }, + data => { message => "proxy - can't get log a ssh target or through a ssh node" }, json_encode => 1 ); return undef; } - if (defined($register_nodes->{$options{target}})) { - if ($synctime_nodes->{$options{target}}->{synctime_error} == -1 || get_sync_time(dbh => $options{dbh}, node_id => $options{target}) == -1) { + if (defined($register_nodes->{$target})) { + if ($synctime_nodes->{$target}->{synctime_error} == -1 || get_sync_time(dbh => $options{dbh}, node_id => $target) == -1) { gorgone::standard::library::add_history( dbh => $options{dbh}, code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, @@ -197,7 +210,7 @@ sub routing { return undef; } - if ($synctime_nodes->{$options{target}}->{in_progress} == 1) { + if ($synctime_nodes->{$target}->{in_progress} == 1) { gorgone::standard::library::add_history( dbh => $options{dbh}, code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, @@ -208,41 +221,25 @@ sub routing { } # We put the good time to get - my $ctime = $synctime_nodes->{$options{target}}->{ctime}; - my $last_id = $synctime_nodes->{$options{target}}->{last_id}; + my $ctime = $synctime_nodes->{$target}->{ctime}; + my $last_id = $synctime_nodes->{$target}->{last_id}; $data = { ctime => $ctime, id => $last_id }; - $synctime_nodes->{$options{target}}->{in_progress} = 1; - $synctime_nodes->{$options{target}}->{in_progress_time} = time(); + $synctime_nodes->{$target}->{in_progress} = 1; + $synctime_nodes->{$target}->{in_progress_time} = time(); } } - my $target = $options{target}; - # we prefer to use direct target - if (!defined($register_nodes->{$options{target}})) { - $target = $register_subnodes->{$options{target}}; - } - - if (defined($last_pong->{$target}) && $last_pong->{$target} != 0 && (time() - $config->{pong_discard_timeout} > $last_pong->{$target})) { - gorgone::standard::library::add_history( - dbh => $options{dbh}, - code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, - data => { message => 'proxy - discard message. target peer seems disconnected' }, - json_encode => 1 - ); - return undef; - } - my $action = $options{action}; my $bulk_actions; push @{$bulk_actions}, $data; - if ($options{action} eq 'REMOTECOPY' && defined($register_nodes->{$options{target}}) && - $register_nodes->{$options{target}}->{type} ne 'push_ssh') { + if ($options{action} eq 'REMOTECOPY' && defined($register_nodes->{$target_parent}) && + $register_nodes->{$target_parent}->{type} ne 'push_ssh') { $action = 'PROCESSCOPY'; $bulk_actions = prepare_remote_copy( dbh => $options{dbh}, data => $data, - target => $options{target}, + target => $target_parent, token => $options{token}, logger => $options{logger} ); @@ -250,7 +247,7 @@ sub routing { foreach my $data (@{$bulk_actions}) { # Mode zmq pull - if ($register_nodes->{$target}->{type} eq 'pull') { + if ($register_nodes->{$target_parent}->{type} eq 'pull') { pull_request(%options, data_decoded => $data); next; } @@ -267,11 +264,11 @@ sub routing { } my $identity; - if (defined($nodes_pool->{$target})) { - $identity = $nodes_pool->{$target}; + if (defined($nodes_pool->{$target_parent})) { + $identity = $nodes_pool->{$target_parent}; } else { $identity = rr_pool(); - $nodes_pool->{$target} = $identity; + $nodes_pool->{$target_parent} = $identity; } gorgone::standard::library::zmq_send_message( @@ -280,7 +277,7 @@ sub routing { action => $action, data => $data, token => $options{token}, - target => $options{target}, + target => $target_complete, json_encode => 1 ); } @@ -369,14 +366,14 @@ sub check { if ($stop == 0 && time() - $synctime_lasttime > $synctime_option) { $synctime_lasttime = time(); - full_sync_history(dbh => $options{dbh}); + full_sync_history(dbh => $options{dbh}, logger => $options{logger}); } if ($stop == 0 && time() - $ping_time > $ping_option) { $options{logger}->writeLogInfo("[proxy] -hooks- Send pings"); $ping_time = time(); - ping_send(dbh => $options{dbh}); + ping_send(dbh => $options{dbh}, logger => $options{logger}); } # We clean all parents @@ -390,6 +387,49 @@ sub check { } # Specific functions +sub pathway { + my (%options) = @_; + + my $target = $options{target}; + if (!defined($target)) { + gorgone::standard::library::add_history( + dbh => $options{dbh}, + code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, + data => { message => 'proxy - need a valid node id' }, + json_encode => 1 + ); + return -1; + } + + my @targets = (); + if (defined($register_nodes->{$target})) { + push @targets, $target; + } + if (defined($register_subnodes->{$target}->{static})) { + push @targets, sort { $register_subnodes->{$target}->{static}->{$a} <=> $register_subnodes->{$target}->{static}->{$b} } keys %{$register_subnodes->{$target}->{static}}; + } + if (defined($register_subnodes->{$target}->{dynamic})) { + push @targets, keys %{$register_subnodes->{$target}->{dynamic}}; + } + + foreach (@targets) { + if (!defined($last_pong->{$_}) || $last_pong->{$_} == 0 || (time() - $config->{pong_discard_timeout} < $last_pong->{$_})) { + $options{logger}->writeLogDebug("[proxy] -hooks- choose node target '$_' for node '$target'"); + return (1, $_ . '~~' . $target, $_, $target); + } + + $options{logger}->writeLogDebug("[proxy] -hooks- skip node target '$_' for node '$target'"); + } + + gorgone::standard::library::add_history( + dbh => $options{dbh}, + code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, + data => { message => 'proxy - discard message. target peer(s) seems disconnected' }, + json_encode => 1 + ); + return -1; +} + sub setlogs { my (%options) = @_; @@ -466,10 +506,10 @@ sub ping_send { $constatus_ping->{$id}->{last_ping_sent} = time(); if ($register_nodes->{$id}->{type} eq 'push_zmq' || $register_nodes->{$id}->{type} eq 'push_ssh') { $synctime_nodes->{$id}->{in_progress_ping} = 1; - routing(socket => $internal_socket, action => 'PING', target => $id, data => '{}', dbh => $options{dbh}); + routing(socket => $internal_socket, action => 'PING', target => $id, data => '{}', dbh => $options{dbh}, logger => $options{logger}); } elsif ($register_nodes->{$id}->{type} eq 'pull') { $synctime_nodes->{$id}->{in_progress_ping} = 1; - routing(action => 'PING', target => $id, data => '{}', dbh => $options{dbh}); + routing(action => 'PING', target => $id, data => '{}', dbh => $options{dbh}, logger => $options{logger}); } } } @@ -480,7 +520,7 @@ sub synclog { # We check if we need synclogs if ($stop == 0) { $synctime_lasttime = time(); - full_sync_history(dbh => $options{dbh}); + full_sync_history(dbh => $options{dbh}, logger => $options{logger}); } } @@ -489,9 +529,9 @@ sub full_sync_history { foreach my $id (keys %{$register_nodes}) { if ($register_nodes->{$id}->{type} eq 'push_zmq') { - routing(socket => $internal_socket, action => 'GETLOG', target => $id, data => '{}', dbh => $options{dbh}); + routing(socket => $internal_socket, action => 'GETLOG', target => $id, data => '{}', dbh => $options{dbh}, logger => $options{logger}); } elsif ($register_nodes->{$id}->{type} eq 'pull') { - routing(action => 'GETLOG', target => $id, data => '{}', dbh => $options{dbh}); + routing(action => 'GETLOG', target => $id, data => '{}', dbh => $options{dbh}, logger => $options{logger}); } } } @@ -572,6 +612,7 @@ sub create_child { $pools_pid->{$child_pid} = $options{pool_id}; } +# we don't manage (yet) the target from pull connection sub pull_request { my (%options) = @_; @@ -618,14 +659,16 @@ sub unregister_nodes { foreach my $node (@{$options{data}->{nodes}}) { if ($node->{type} ne 'pull') { - routing(socket => $internal_socket, action => 'PROXYDELNODE', target => $node->{id}, data => JSON::XS->new->utf8->encode($node), dbh => $options{dbh}); + routing(socket => $internal_socket, action => 'PROXYDELNODE', target => $node->{id}, data => JSON::XS->new->utf8->encode($node), dbh => $options{dbh}, logger => $options{logger}); } - $options{logger}->writeLogInfo("[proxy] -hooks- Poller '" . $node->{id} . "' is unregistered"); + $options{logger}->writeLogInfo("[proxy] -hooks- node '" . $node->{id} . "' is unregistered"); if (defined($register_nodes->{$node->{id}}) && $register_nodes->{$node->{id}}->{nodes}) { - foreach my $subnode_id (@{$register_nodes->{$node->{id}}->{nodes}}) { - delete $register_subnodes->{$subnode_id} - if ($register_subnodes->{$subnode_id} eq $node->{id}); + foreach my $subnode (@{$register_nodes->{$node->{id}}->{nodes}}) { + delete $register_subnodes->{ $subnode->{id} }->{static}->{ $node->{id} } + if (defined($register_subnodes->{ $subnode->{id} }->{static}->{ $node->{id} })); + delete $register_subnodes->{ $subnode->{id} }->{dynamic}->{ $node->{id} } + if (defined($register_subnodes->{ $subnode->{id} }->{dynamic}->{ $node->{id} })); } } @@ -638,36 +681,27 @@ sub unregister_nodes { } } +# It comes from PONG result. sub register_subnodes { my (%options) = @_; + # we remove dynamic values foreach my $subnode_id (keys %$register_subnodes) { - delete $register_subnodes->{$subnode_id} - if ($register_subnodes->{$subnode_id} eq $options{id}); + delete $register_subnodes->{$subnode_id}->{dynamic}->{ $options{id} } + if (defined($register_subnodes->{$subnode_id}->{dynamic}->{ $options{id} })); } - my $subnodes_register = { id => $options{id}, nodes => {} }; + # we can add in dynamic even if it's in static (not an issue) my $subnodes = [$options{subnodes}]; while (1) { last if (scalar(@$subnodes) <= 0); my $entry = shift(@$subnodes); foreach (keys %$entry) { - $subnodes_register->{nodes}->{$_} = $options{id}; - $register_subnodes->{$_} = $options{id}; + $register_subnodes->{$_}->{dynamic}->{ $options{id} } = 1; } push @$subnodes, $entry->{nodes} if (defined($entry->{nodes})); } - - if ($register_nodes->{$options{id}}->{type} ne 'pull') { - routing( - socket => $internal_socket, - action => 'PROXYADDSUBNODE', - target => $options{id}, - data => JSON::XS->new->utf8->encode($subnodes_register), - dbh => $options{dbh} - ); - } } sub register_nodes { @@ -681,15 +715,18 @@ sub register_nodes { $new_node = 0; # we remove subnodes before foreach my $subnode_id (keys %$register_subnodes) { - delete $register_subnodes->{$subnode_id} - if ($register_subnodes->{$subnode_id} eq $node->{id}); + delete $register_subnodes->{$subnode_id}->{static}->{ $node->{id} } + if (defined($register_subnodes->{$subnode_id}->{static}->{ $node->{id} })); + delete $register_subnodes->{$subnode_id}->{dynamic}->{ $node->{id} } + if (defined($register_subnodes->{$subnode_id}->{dynamic}->{ $node->{id} })); } } $register_nodes->{$node->{id}} = $node; if (defined($node->{nodes})) { - foreach my $subnode_id (@{$node->{nodes}}) { - $register_subnodes->{$subnode_id} = $node->{id}; + foreach my $subnode (@{$node->{nodes}}) { + $register_subnodes->{$subnode->{id}} = { static => {}, dynamic => {} } if (!defined($register_subnodes->{ $subnode->{id} })); + $register_subnodes->{$subnode->{id}}->{static}->{ $node->{id} } = defined($subnode->{pathscore}) && $subnode->{pathscore} =~ /[0-9]+/ ? $subnode->{pathscore} : 1; } } @@ -700,13 +737,13 @@ sub register_nodes { } if ($node->{type} ne 'pull') { - routing(socket => $internal_socket, action => 'PROXYADDNODE', target => $node->{id}, data => JSON::XS->new->utf8->encode($node), dbh => $options{dbh}); + routing(socket => $internal_socket, action => 'PROXYADDNODE', target => $node->{id}, data => JSON::XS->new->utf8->encode($node), dbh => $options{dbh}, logger => $options{logger}); } if ($new_node == 1) { $constatus_ping->{$node->{id}} = { type => $node->{type}, last_ping_sent => 0, last_ping_recv => 0, nodes => {} }; # we provide information to the proxy class - ping_send(node_id => $node->{id}, dbh => $options{dbh}); - $options{logger}->writeLogInfo("[proxy] -hooks- Poller '" . $node->{id} . "' is registered"); + ping_send(node_id => $node->{id}, dbh => $options{dbh}, logger => $options{logger}); + $options{logger}->writeLogInfo("[proxy] -hooks- node '" . $node->{id} . "' is registered"); } } } diff --git a/gorgone/gorgone/modules/core/register/class.pm b/gorgone/gorgone/modules/core/register/class.pm index db577dcb8c3..abd68c48ff6 100644 --- a/gorgone/gorgone/modules/core/register/class.pm +++ b/gorgone/gorgone/modules/core/register/class.pm @@ -123,6 +123,7 @@ sub action_registerresync { nodes => $register_nodes } ) if (scalar(@$register_nodes) > 0); + $self->send_internal_action( action => 'UNREGISTERNODES', data => { From 736407bebcfe661111feb3402eb964c88e97fa66 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Fri, 6 Dec 2019 16:13:51 +0100 Subject: [PATCH 240/948] fix(proxy): error if cache_dir not exist --- gorgone/docs/client_server_zmq.md | 18 ++++-------------- gorgone/docs/configuration.md | 3 ++- gorgone/gorgone/modules/core/proxy/hooks.pm | 5 +++-- 3 files changed, 9 insertions(+), 17 deletions(-) diff --git a/gorgone/docs/client_server_zmq.md b/gorgone/docs/client_server_zmq.md index d54cb07b599..7ea22410a18 100644 --- a/gorgone/docs/client_server_zmq.md +++ b/gorgone/docs/client_server_zmq.md @@ -37,7 +37,7 @@ $ perl /usr/local/bin/gorgone_key_thumbprint.pl --key-path='/var/spool/centreon/ *Make the IDs match Centreon Pollers ID to benefit from [legacy cmd](../docs/modules/core/legacycmd.md) module's actions.* -#### Client +#### Server In the *gorgoned.yml* configuration file, add the following directives under the *gorgonecore* section: @@ -45,9 +45,7 @@ In the *gorgoned.yml* configuration file, add the following directives under the gorgonecore: id: 1 privkey: /var/spool/centreon/.gorgone/privkey.pem - cipher: "Cipher::AES" - keysize: 32 - vector: 0123456789012345 + pubkey: /var/spool/centreon/.gorgone/pubkey.pem ``` Add the [register](../docs/modules/core/register.md) module and define the path to the dedicated configuration file. @@ -68,15 +66,9 @@ nodes: type: push_zmq address: 10.1.2.3 port: 5556 - server_pubkey: /var/spool/centreon/.gorgone/2/pubkey.pem - client_pubkey: /var/spool/centreon/.gorgone/pubkey.pem - client_privkey: /var/spool/centreon/.gorgone/privkey.pem - cipher: "Cipher::AES" - keysize: 32 - vector: 0123456789012345 ``` -#### Server +#### Client In the *gorgoned.yml* configuration file, add the following directives under the *gorgonecore* section: @@ -86,9 +78,7 @@ gorgonecore: external_com_type: tcp external_com_path: "*:5556" privkey: /var/spool/centreon/.gorgone/privkey.pem - cipher: "Cipher::AES" - keysize: 32 - vector: 0123456789012345 + pubkey: /var/spool/centreon/.gorgone/pubkey.pem authorized_clients: - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 ``` diff --git a/gorgone/docs/configuration.md b/gorgone/docs/configuration.md index 0c51f2bb8a0..43de47fab27 100644 --- a/gorgone/docs/configuration.md +++ b/gorgone/docs/configuration.md @@ -49,7 +49,8 @@ database: | gorgone_db_password | Username's password | | | hostname | Hostname of the server running Gorgone | Result of *hostname* system function. | | id | Identifier of server running Gorgone | None. Must be unique over all Gorgone daemons. | -| privkey | Path to the Gorgone core private key | `keys/central/privkey.pem` | +| privkey | Path to the Gorgone core private key | `keys/rsakey.priv.pem` | +| privkey | Path to the Gorgone core public key | `keys/rsakey.pub.pem` | | cipher | Cipher used for encryption | `Cipher::AES` | | keysize | Size in bytes of the symmetric encryption key | `32` | | vector | Encryption vector | `0123456789012345` | diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index bfe33215497..db8b7e5c079 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -236,13 +236,14 @@ sub routing { if ($options{action} eq 'REMOTECOPY' && defined($register_nodes->{$target_parent}) && $register_nodes->{$target_parent}->{type} ne 'push_ssh') { $action = 'PROCESSCOPY'; - $bulk_actions = prepare_remote_copy( + ($code, $bulk_actions) = prepare_remote_copy( dbh => $options{dbh}, data => $data, target => $target_parent, token => $options{token}, logger => $options{logger} ); + return if ($code == -1); } foreach my $data (@{$bulk_actions}) { @@ -864,7 +865,7 @@ sub prepare_remote_copy { parameters => { no_fork => 1 } }; - return \@actions; + return (0, \@actions); } sub setcoreid { From 0156a8273f48967c2939410bbbe6a76f5f6e3e0d Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Fri, 6 Dec 2019 16:20:00 +0100 Subject: [PATCH 241/948] enh(doc): add some config --- gorgone/docs/configuration.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gorgone/docs/configuration.md b/gorgone/docs/configuration.md index 43de47fab27..3e9899c2ab9 100644 --- a/gorgone/docs/configuration.md +++ b/gorgone/docs/configuration.md @@ -54,6 +54,8 @@ database: | cipher | Cipher used for encryption | `Cipher::AES` | | keysize | Size in bytes of the symmetric encryption key | `32` | | vector | Encryption vector | `0123456789012345` | +| fingerprint_mode | Validation mode of zmq nodes to connect (can be: always, first, strict) | `first` | +| fingerprint_mgr | Hash of the definition class to store fingerprints | | | authorized_clients | Table of string-formated JWK thumbprints of clients public key | | | proxy_name | Name of the proxy module definition | `proxy` (loaded internally) | @@ -78,6 +80,9 @@ gorgonecore: cipher: "Cipher::AES" keysize: 32 vector: 0123456789012345 + fingerprint_mode: first + fingerprint_mgr: + package: gorgone::class::fingerprint::backend::sql authorized_clients: - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 proxy_name: proxy From e26dfd779ef764cb206bcd9b9f705c6b9f19e869 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Fri, 6 Dec 2019 16:22:04 +0100 Subject: [PATCH 242/948] enh(doc): update method registernodes --- gorgone/docs/guide.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gorgone/docs/guide.md b/gorgone/docs/guide.md index 8b6f9b7436f..2c5267f7a01 100644 --- a/gorgone/docs/guide.md +++ b/gorgone/docs/guide.md @@ -217,8 +217,7 @@ The client request (no carriage returns. only for reading): { "id": 100, "type": "push_ssh", "address": "10.0.0.1", "ssh_port": 22 }, { "id": 150, "type": "push_zmq", "address": "10.3.2.1", - "server_pubkey": "server_pubkey.pem", "client_pubkey": "client_pubkey.pem", "client_privkey": "client_privkey.pem", "cipher": "Cipher::AES", "keysize": 32, "vector": "0123456789012345", - "nodes": [400, 455] + "nodes": [ { "id": 400, { "id": 455 } ] } ] } From 46d18b3f0d9b1196ca783570b22759f3adc8ff4b Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 9 Dec 2019 17:44:24 +0100 Subject: [PATCH 243/948] enh(httpserver): handle auth not enabled --- gorgone/gorgone/modules/core/httpserver/hooks.pm | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/gorgone/gorgone/modules/core/httpserver/hooks.pm b/gorgone/gorgone/modules/core/httpserver/hooks.pm index a5cb53244d0..d4bdf745d2f 100644 --- a/gorgone/gorgone/modules/core/httpserver/hooks.pm +++ b/gorgone/gorgone/modules/core/httpserver/hooks.pm @@ -45,13 +45,15 @@ sub register { $config_core = $options{config_core}; $config->{address} = defined($config->{address}) && $config->{address} ne '' ? $config->{address} : '0.0.0.0'; $config->{port} = defined($config->{port}) && $config->{port} =~ /(\d+)/ ? $1 : 8080; - if (!defined($config->{auth}->{user}) || $config->{auth}->{user} =~ /^\s*$/) { - $options{logger}->writeLogError('[httpserver] -hooks- auth user option mandatory'); - $loaded = 0; - } - if (!defined($config->{auth}->{password}) || $config->{auth}->{password} =~ /^\s*$/) { - $options{logger}->writeLogError('[httpserver] -hooks- auth password option mandatory'); - $loaded = 0; + if (defined($config->{auth}->{enabled}) && $config->{auth}->{enabled} eq 'true') { + if (!defined($config->{auth}->{user}) || $config->{auth}->{user} =~ /^\s*$/) { + $options{logger}->writeLogError('[httpserver] -hooks- user option mandatory if auth enabled'); + $loaded = 0; + } + if (!defined($config->{auth}->{password}) || $config->{auth}->{password} =~ /^\s*$/) { + $options{logger}->writeLogError('[httpserver] -hooks- password option mandatory if auth enabled'); + $loaded = 0; + } } return ($loaded, NAMESPACE, NAME, EVENTS); From fd4df701089d63066f95f240505c2fe47beac92b Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 9 Dec 2019 17:47:20 +0100 Subject: [PATCH 244/948] enh(broker): result is array, change dir tree, create dir --- gorgone/gorgone/modules/centreon/broker/class.pm | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/broker/class.pm b/gorgone/gorgone/modules/centreon/broker/class.pm index 89ac113096a..794244df099 100644 --- a/gorgone/gorgone/modules/centreon/broker/class.pm +++ b/gorgone/gorgone/modules/centreon/broker/class.pm @@ -28,6 +28,7 @@ use gorgone::standard::library; use gorgone::class::sqlquery; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); +use File::Path qw(make_path); use JSON::XS; use Time::HiRes; @@ -181,15 +182,16 @@ sub write_stats { my $cache_dir = (defined($self->{config}->{cache_dir})) ? $self->{config}->{cache_dir} : '/var/lib/centreon/broker-stats/'; - foreach my $id (sort keys %{$options{data}->{data}->{result}}) { - my $data = JSON::XS->new->utf8->decode($options{data}->{data}->{result}->{$id}->{data}); + foreach my $entry (@{$options{data}->{data}->{result}}) { + my $data = JSON::XS->new->utf8->decode($entry->{data}); next if (!defined($data->{exit_code}) || $data->{exit_code} != 0 || !defined($data->{metadata}->{poller_id}) || !defined($data->{metadata}->{config_name})); - my $cache_file = $cache_dir . '/' . $data->{metadata}->{poller_id} . '-' . - $data->{metadata}->{config_name} . '.dat'; - $self->{logger}->writeLogDebug("[broker] -class- Writing file '" . $cache_file . "'"); - open(FH, '>', $cache_file); + my $dest_dir = $cache_dir . '/' . $data->{metadata}->{poller_id}; + make_path($dest_dir) if (! -d $dest_dir); + my $dest_file = $dest_dir . '/' . $data->{metadata}->{config_name} . '.json'; + $self->{logger}->writeLogDebug("[broker] -class- Writing file '" . $dest_file . "'"); + open(FH, '>', $dest_file); print FH $data->{stdout}; close(FH); } From 36454da0ecb8b9af87bc6635681d87c8c126ccba Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 10 Dec 2019 10:38:59 +0100 Subject: [PATCH 245/948] enh(broker): change default cache dir --- gorgone/gorgone/modules/centreon/broker/class.pm | 4 +--- gorgone/gorgone/modules/centreon/broker/hooks.pm | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/broker/class.pm b/gorgone/gorgone/modules/centreon/broker/class.pm index 794244df099..bb09a4bcc19 100644 --- a/gorgone/gorgone/modules/centreon/broker/class.pm +++ b/gorgone/gorgone/modules/centreon/broker/class.pm @@ -180,14 +180,12 @@ sub write_stats { return if (!defined($options{data}->{data}->{action}) || $options{data}->{data}->{action} ne "getlog" && defined($options{data}->{data}->{result})); - my $cache_dir = (defined($self->{config}->{cache_dir})) ? - $self->{config}->{cache_dir} : '/var/lib/centreon/broker-stats/'; foreach my $entry (@{$options{data}->{data}->{result}}) { my $data = JSON::XS->new->utf8->decode($entry->{data}); next if (!defined($data->{exit_code}) || $data->{exit_code} != 0 || !defined($data->{metadata}->{poller_id}) || !defined($data->{metadata}->{config_name})); - my $dest_dir = $cache_dir . '/' . $data->{metadata}->{poller_id}; + my $dest_dir = $self->{config}->{cache_dir} . '/' . $data->{metadata}->{poller_id}; make_path($dest_dir) if (! -d $dest_dir); my $dest_file = $dest_dir . '/' . $data->{metadata}->{config_name} . '.json'; $self->{logger}->writeLogDebug("[broker] -class- Writing file '" . $dest_file . "'"); diff --git a/gorgone/gorgone/modules/centreon/broker/hooks.pm b/gorgone/gorgone/modules/centreon/broker/hooks.pm index ac47d0d8fc1..3ea6dd7dd27 100644 --- a/gorgone/gorgone/modules/centreon/broker/hooks.pm +++ b/gorgone/gorgone/modules/centreon/broker/hooks.pm @@ -45,7 +45,7 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; $config_db_centreon = $options{config_db_centreon}; - $config->{cache_dir} = defined($config->{cache_dir}) ? $config->{cache_dir} : '/var/lib/centreon/broker-stats/'; + $config->{cache_dir} = defined($config->{cache_dir}) ? $config->{cache_dir} : '/cache/lib/centreon/broker-stats/'; return (1, NAMESPACE, NAME, EVENTS); } From 51e52e9f40797b35da2f8e3c3d2677ffea1c7b5b Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 10 Dec 2019 10:40:09 +0100 Subject: [PATCH 246/948] fix(broker): fix typo --- gorgone/gorgone/modules/centreon/broker/hooks.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/gorgone/modules/centreon/broker/hooks.pm b/gorgone/gorgone/modules/centreon/broker/hooks.pm index 3ea6dd7dd27..815bd10f09d 100644 --- a/gorgone/gorgone/modules/centreon/broker/hooks.pm +++ b/gorgone/gorgone/modules/centreon/broker/hooks.pm @@ -45,7 +45,7 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; $config_db_centreon = $options{config_db_centreon}; - $config->{cache_dir} = defined($config->{cache_dir}) ? $config->{cache_dir} : '/cache/lib/centreon/broker-stats/'; + $config->{cache_dir} = defined($config->{cache_dir}) ? $config->{cache_dir} : '/var/cache/centreon/broker-stats/'; return (1, NAMESPACE, NAME, EVENTS); } From 5aff8101be49c138641f46ed3533b6700c668042 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 10 Dec 2019 15:44:17 +0100 Subject: [PATCH 247/948] enh(api): allow use of parameters for log --- gorgone/gorgone/standard/api.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gorgone/gorgone/standard/api.pm b/gorgone/gorgone/standard/api.pm index d03ebdfff4c..bf76f4a6b21 100644 --- a/gorgone/gorgone/standard/api.pm +++ b/gorgone/gorgone/standard/api.pm @@ -217,7 +217,8 @@ sub get_log { socket => $options{socket}, action => 'GETLOG', data => { - token => $options{token} + token => $options{token}, + %{$options{parameters}} }, json_encode => 1 ); From 2934ed805f339bc8973b656530e09c931a13f847 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 11 Dec 2019 15:32:51 +0100 Subject: [PATCH 248/948] enh(httpserver): close connections each time --- gorgone/gorgone/modules/core/httpserver/class.pm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/modules/core/httpserver/class.pm b/gorgone/gorgone/modules/core/httpserver/class.pm index c2b8f953bd9..8498589797d 100644 --- a/gorgone/gorgone/modules/core/httpserver/class.pm +++ b/gorgone/gorgone/modules/core/httpserver/class.pm @@ -202,9 +202,9 @@ sub run { } if (defined($daemon)) { - while (my ($connection, $peer_addr) = $daemon->accept()) { + while (my ($connection) = $daemon->accept()) { while (my $request = $connection->get_request) { - $connector->{logger}->writeLogInfo("[httpserver] -class- " . $request->method . " '" . $request->uri->path . "'"); + $connector->{logger}->writeLogInfo("[httpserver] -class- " . $connection->peerhost() . " " . $request->method . " '" . $request->uri->path . "' '" . $request->header("User-Agent") . "'"); if ($connector->{allowed_hosts_enabled} == 1) { if ($connector->check_allowed_host(peer_addr => inet_ntoa($connection->peeraddr())) == 0) { @@ -228,6 +228,7 @@ sub run { } else { # Authen error $connection->send_error(RC_UNAUTHORIZED); } + $connection->force_last_request; } $connection->close; undef($connection); From deaa6161550eb94c524408d47865b948edd5c0b0 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 11 Dec 2019 17:21:08 +0100 Subject: [PATCH 249/948] enh(api): change log response format --- gorgone/gorgone/standard/api.pm | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/gorgone/gorgone/standard/api.pm b/gorgone/gorgone/standard/api.pm index bf76f4a6b21..7a0938156cb 100644 --- a/gorgone/gorgone/standard/api.pm +++ b/gorgone/gorgone/standard/api.pm @@ -33,7 +33,8 @@ my $result; my $mapping_internal_endpoint = { thumbprint => 'GETTHUMBPRINT', - constatus => 'CONSTATUS' + constatus => 'CONSTATUS', + information => 'INFORMATION' }; sub root { @@ -56,7 +57,8 @@ sub root { socket => $options{socket}, target => $2, token => $3, - refresh => (defined($options{parameters}->{refresh})) ? $options{parameters}->{refresh} : undef + refresh => (defined($options{parameters}->{refresh})) ? $options{parameters}->{refresh} : undef, + parameters => $options{parameters} ); } elsif ($options{method} eq 'GET' && $options{uri} =~ /^\/api\/(targets\/(\w*)\/)?internal\/(.*)$/) { $response = get_internal( @@ -235,7 +237,13 @@ sub get_log { $response = '{"error":"decode_error","message":"Cannot decode response"}'; } elsif (defined($content->{data}->{result}) && scalar(@{$content->{data}->{result}}) > 0) { eval { - $response = JSON::XS->new->utf8->encode($content->{data}->{result}); + $response = JSON::XS->new->utf8->encode( + { + message => "Logs found", + token => $options{token}, + data => $content->{data}->{result} + } + ); }; if ($@) { $response = '{"error":"encode_error","message":"Cannot encode response"}'; From 6c2587628f1ef8455786ecbd96179114c054059c Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 11 Dec 2019 17:23:05 +0100 Subject: [PATCH 250/948] enh(api): change log response format --- gorgone/gorgone/standard/api.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/gorgone/standard/api.pm b/gorgone/gorgone/standard/api.pm index 7a0938156cb..1e35be68251 100644 --- a/gorgone/gorgone/standard/api.pm +++ b/gorgone/gorgone/standard/api.pm @@ -227,7 +227,7 @@ sub get_log { my $rev = zmq_poll($poll, 5000); - my $response = '{"error":"no_log","message":"No log found for token","token":"' . $options{token} . '"}'; + my $response = '{"error":"no_log","message":"No log found for token","data":[],"token":"' . $options{token} . '"}'; if (defined($result->{data})) { my $content; eval { From 474097013b9c63752daa44cc869edb86df64f155 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 11 Dec 2019 18:31:17 +0100 Subject: [PATCH 251/948] enh(doc): update api doc --- gorgone/README.md | 6 + gorgone/docs/api.md | 267 ++++++++++++++++++++++++ gorgone/docs/modules/core/httpserver.md | 11 - 3 files changed, 273 insertions(+), 11 deletions(-) create mode 100644 gorgone/docs/api.md diff --git a/gorgone/README.md b/gorgone/README.md index 50ace2a0280..28fbfbfea19 100644 --- a/gorgone/README.md +++ b/gorgone/README.md @@ -22,3 +22,9 @@ To understand the main principles of Gorgone protocol, follow the [guide](docs/g The Centreon Gorgone project encloses several built-in modules. See the full list [here](docs/modules.md). + +## API + +The HTTP server module exposes a RestAPI. + +See how to use it [here](docs/api.md). diff --git a/gorgone/docs/api.md b/gorgone/docs/api.md new file mode 100644 index 00000000000..71e7ee2bede --- /dev/null +++ b/gorgone/docs/api.md @@ -0,0 +1,267 @@ +# API + +Centreon Gorgone provides a RestAPI through its HTTP server module. + +## Internal endpoints + +### Get Nodes Connection Status + +| Endpoint | Method | +| :- | :- | +| /api/internal/constatus | `GET` | + +#### Headers + +| Header | Value | +| :- | :- | +| Accept | application/json | + +#### Example + +```bash +curl --request GET "https://hostname:8443/api/internal/constatus" \ + --header "Accept: application/json" +``` + +### Get Public Key Thumbprint + +| Endpoint | Method | +| :- | :- | +| /api/internal/thumbprint | `GET` | + +#### Headers + +| Header | Value | +| :- | :- | +| Accept | application/json | + +#### Example + +```bash +curl --request GET "https://hostname:8443/api/internal/thumbprint" \ + --header "Accept: application/json" +``` + +### Get Runtime Informations And Statistics + +| Endpoint | Method | +| :- | :- | +| /api/internal/information | `GET` | + +#### Headers + +| Header | Value | +| :- | :- | +| Accept | application/json | + +#### Example + +```bash +curl --request GET "https://hostname:8443/api/internal/information" \ + --header "Accept: application/json" +``` + +#### Response example + +```json +{ + "action": "information", + "data": { + "modules": { + "httpserver": "gorgone::modules::core::httpserver::hooks", + "dbcleaner": "gorgone::modules::core::dbcleaner::hooks", + "cron": "gorgone::modules::core::cron::hooks", + "engine": "gorgone::modules::centreon::engine::hooks", + "action": "gorgone::modules::core::action::hooks", + "broker": "gorgone::modules::centreon::broker::hooks", + "nodes": "gorgone::modules::centreon::nodes::hooks", + "legacycmd": "gorgone::modules::centreon::legacycmd::hooks" + }, + "counters": { + "external": { + "total": 0 + }, + "total": 183, + "internal": { + "legacycmdready": 1, + "brokerready": 1, + "addcron": 1, + "cronready": 1, + "centreonnodesready": 1, + "httpserverready": 1, + "command": 51, + "putlog": 75, + "dbcleanerready": 1, + "information": 1, + "brokerstats": 8, + "total": 183, + "setcoreid": 2, + "getlog": 37, + "engineready": 1, + "actionready": 1 + }, + "proxy": { + "total": 0 + } + } + }, + "message": "ok" +} +``` + +## Modules endpoints + +The available endpoints depend on which modules are loaded. + +Endpoints are basically built from: + +* API root, +* Module's namespace, +* Module's name, +* Action + +#### Example + +```bash +curl --request POST "https://hostname:8443/api/core/action/command" \ + --header "Accept: application/json" \ + --header "Content-Type: application/json" \ + --data "{ + \"command\": \"echo 'Test command'\" +}" +``` + +Find more informations directly from modules documentations [here](../docs/modules.md). + +As Centreon Gorgone is asynchronous, those endpoints will return a token corresponding to the action. + +#### Example + +```json +{ + "token": "3f25bc3a797fe989d1fb052b1886a806e73fe2d8ccfc6377ee3d4490f8ad03c02cb2533edcc1b3d8e1770e28d6f2de83bd98923b66c0c33395e5f835759de4b1" +} +``` + +## Log endpoint + +To retrieve the logs, a specific endpoint can be called as follow. + +| Endpoint | Method | +| :- | :- | +| /api/log/:token | `GET` | + +#### Headers + +| Header | Value | +| :- | :- | +| Accept | application/json | + +#### Path variables + +| Variable | Description | +| :- | :- | +| token | Token of the action | + +#### Example + +```bash +curl --request GET "https://hostname:8443/api/log/3f25bc3a797fe989d1fb052b1886a806e73fe2d8ccfc6377ee3d4490f8ad03c02cb2533edcc1b3d8e1770e28d6f2de83bd98923b66c0c33395e5f835759de4b1" \ + --header "Accept: application/json" +``` + +#### Response example + +```json +{ + "data": [ + { + "ctime": 1576083003, + "etime": 1576083003, + "id": "15639", + "instant": 0, + "data": "{\"metadata\":null,\"message\":\"command 'echo 'Test command'' has started\"}", + "token": "3f25bc3a797fe989d1fb052b1886a806e73fe2d8ccfc6377ee3d4490f8ad03c02cb2533edcc1b3d8e1770e28d6f2de83bd98923b66c0c33395e5f835759de4b1", + "code": 0 + }, + { + "ctime": 1576083003, + "etime": 1576083003, + "id": "15640", + "instant": 0, + "data": "{\"exit_code\":0,\"metadata\":null,\"stdout\":\"Test command\",\"message\":\"command 'echo 'Test command'' has finished\",\"start\":1576083003,\"end\":1576083003}", + "token": "3f25bc3a797fe989d1fb052b1886a806e73fe2d8ccfc6377ee3d4490f8ad03c02cb2533edcc1b3d8e1770e28d6f2de83bd98923b66c0c33395e5f835759de4b1", + "code": 2 + } + ], + "token": "3f25bc3a797fe989d1fb052b1886a806e73fe2d8ccfc6377ee3d4490f8ad03c02cb2533edcc1b3d8e1770e28d6f2de83bd98923b66c0c33395e5f835759de4b1", + "message": "Logs found" +} +``` + +## Errors + +### Unknown endpoint + +```json +{ + "error":"endpoint_unknown", + "message":"endpoint not implemented" +} +``` + +### Unknown method + +```json +{ + "error": "method_unknown", + "message": "Method not implemented" +} +``` + +### No logs for provided token + +```json +{ + "error": "no_log", + "message": "No log found for token", + "data": [], + "token": "" +} +``` + +### JSON decoding error for request + +```json +{ + "error":"decode_error", + "message":"Cannot decode response" +} +``` + +### JSON encoding error for response + +```json +{ + "error":"encode_error", + "message":"Cannot encode response" +} +``` + +### No results for internal actions + +```json +{ + "error":"no_result", + "message":"No result found for action " + } +``` + +### No token found when using wait parameter + +```json +{ + "error":"no_token", + "message":"Cannot retrieve token from ack" +} +``` diff --git a/gorgone/docs/modules/core/httpserver.md b/gorgone/docs/modules/core/httpserver.md index 56ac4a9c35e..bcbcb8ee9c1 100644 --- a/gorgone/docs/modules/core/httpserver.md +++ b/gorgone/docs/modules/core/httpserver.md @@ -75,14 +75,3 @@ dispatch: curl --request GET "https://hostname:8443/status" \ --header "Accept: application/json" ``` - -```bash -curl --request GET "https://hostname:8443/api/internal/constatus" \ - --header "Accept: application/json" -``` - -```bash -curl --request GET "https://hostname:8443/api/internal/thumbprint" \ - --header "Accept: application/json" -``` - From 3082a81943f6ffb2586ef68662f8544d6b0ddc59 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 11 Dec 2019 18:32:32 +0100 Subject: [PATCH 252/948] enh(action): add start log TODO: handle array of commands --- gorgone/gorgone/modules/core/action/class.pm | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index 8db4c258e4b..b65feb6630d 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -104,6 +104,16 @@ sub action_command { return -1; } + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_BEGIN, + token => $options{token}, + data => { + message => "command '$options{data}->{content}->{command}' has started", + metadata => $options{data}->{content}->{metadata} + } + ); + my $start = time(); my ($error, $stdout, $return_code) = gorgone::standard::misc::backtick( command => $options{data}->{content}->{command}, From 4b1f3eba01c82b4becd23af7330dc0749d710f5d Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 11 Dec 2019 18:34:52 +0100 Subject: [PATCH 253/948] enh(doc): typo --- gorgone/docs/api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/docs/api.md b/gorgone/docs/api.md index 71e7ee2bede..15fc8382e60 100644 --- a/gorgone/docs/api.md +++ b/gorgone/docs/api.md @@ -254,7 +254,7 @@ curl --request GET "https://hostname:8443/api/log/3f25bc3a797fe989d1fb052b1886a8 { "error":"no_result", "message":"No result found for action " - } +} ``` ### No token found when using wait parameter From 6880c9fba9669c9c398370a08701511813983f11 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Mon, 16 Dec 2019 10:17:51 +0100 Subject: [PATCH 254/948] enh(nodes): prepare for 20.04.x release --- .../gorgone/modules/centreon/nodes/class.pm | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/nodes/class.pm b/gorgone/gorgone/modules/centreon/nodes/class.pm index 5dfe1a1a4dc..5b87399952b 100644 --- a/gorgone/gorgone/modules/centreon/nodes/class.pm +++ b/gorgone/gorgone/modules/centreon/nodes/class.pm @@ -95,43 +95,61 @@ sub action_nodesresync { $self->send_log(code => gorgone::class::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action nodesresync proceed' }); - my $request = " - SELECT id, name, localhost, ns_ip_address, ssh_port, remote_id, remote_server_centcore_ssh_proxy + my $request = 'SELECT remote_server_id, poller_server_id FROM rs_poller_relation'; + my ($status, $datas) = $self->{class_object}->custom_execute(request => $request, mode => 2); + if ($status == -1) { + $self->send_log(code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot find nodes remote configuration' }); + $self->{logger}->writeLogError('[nodes] -class- cannot find nodes remote configuration'); + return 1; + } + + # we set a pathscore of 100 because it's "slave" + my $register_subnodes = {}; + foreach (@$datas) { + $register_subnodes->{$_->[0]} = [] if (!defined($register_subnodes->{$_->[0]})); + unshift $register_subnodes->{$_->[0]}, { id => $_->[1], pathscore => 100 }; + } + + $request = " + SELECT id, name, localhost, ns_ip_address, gorgone_port, remote_id, remote_server_use_as_proxy, gorgone_communication_type FROM nagios_server WHERE ns_activate = '1' "; - my ($status, $datas) = $self->{class_object}->custom_execute(request => $request, mode => 2); + ($status, $datas) = $self->{class_object}->custom_execute(request => $request, mode => 2); if ($status == -1) { $self->send_log(code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot find nodes configuration' }); - $self->{logger}->writeLogError("[nodes] -class- cannot find nodes configuration"); + $self->{logger}->writeLogError('[nodes] -class- cannot find nodes configuration'); return 1; } my $core_id; my $register_temp = {}; my $register_nodes = []; - my $register_subnodes = {}; foreach (@$datas) { if ($_->[2] == 1) { $core_id = $_->[0]; next; } - # remote_server_centcore_ssh_proxy = 1 means: pass through the remote. otherwise directly. + # remote_server_use_as_proxy = 1 means: pass through the remote. otherwise directly. if (defined($_->[5]) && $_->[5] =~ /\d+/ && $_->[6] == 1) { $register_subnodes->{$_->[5]} = [] if (!defined($register_subnodes->{$_->[5]})); - push @{$register_subnodes->{$_->[5]}}, $_->[0]; + unshift @{$register_subnodes->{$_->[5]}}, { id => $_->[0], pathscore => 1 }; next; } $self->{register_nodes}->{$_->[0]} = 1; $register_temp->{$_->[0]} = 1; - push @{$register_nodes}, { id => $_->[0], type => 'push_ssh', address => $_->[3], ssh_port => $_->[4] }; + if ($_->[7] == 2) { + push @$register_nodes, { id => $_->[0], type => 'push_ssh', address => $_->[3], ssh_port => $_->[4] }; + } else { + push @$register_nodes, { id => $_->[0], type => 'push_zmq', address => $_->[3], port => $_->[4] }; + } } my $unregister_nodes = []; foreach (keys %{$self->{register_nodes}}) { if (!defined($register_temp->{$_})) { - push @{$unregister_nodes}, { id => $_ }; + push @$unregister_nodes, { id => $_ }; delete $self->{register_nodes}->{$_}; } } @@ -178,7 +196,7 @@ sub run { dsn => $self->{config_db_centreon}->{dsn}, user => $self->{config_db_centreon}->{username}, password => $self->{config_db_centreon}->{password}, - force => 2, + force => 0, logger => $self->{logger} ); ##### Load objects ##### From bed2c4d648f32d55fb251bc37a799f9c627d9b64 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 16 Dec 2019 12:02:52 +0100 Subject: [PATCH 255/948] enh(httpserver): use json response for http error --- gorgone/docs/api.md | 18 +++++++++++++ .../gorgone/modules/core/httpserver/class.pm | 27 ++++++++++++++++--- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/gorgone/docs/api.md b/gorgone/docs/api.md index 15fc8382e60..af3c7359aaa 100644 --- a/gorgone/docs/api.md +++ b/gorgone/docs/api.md @@ -201,6 +201,24 @@ curl --request GET "https://hostname:8443/api/log/3f25bc3a797fe989d1fb052b1886a8 ## Errors +### Unauthorized + +```json +{ + "error":"http_error_401", + "message":"unauthorized" +} +``` + +### Forbidden + +```json +{ + "error":"http_error_403", + "message":"forbidden" +} +``` + ### Unknown endpoint ```json diff --git a/gorgone/gorgone/modules/core/httpserver/class.pm b/gorgone/gorgone/modules/core/httpserver/class.pm index 8498589797d..97b5e7ad541 100644 --- a/gorgone/gorgone/modules/core/httpserver/class.pm +++ b/gorgone/gorgone/modules/core/httpserver/class.pm @@ -208,7 +208,11 @@ sub run { if ($connector->{allowed_hosts_enabled} == 1) { if ($connector->check_allowed_host(peer_addr => inet_ntoa($connection->peeraddr())) == 0) { - $connection->send_error(RC_UNAUTHORIZED); + $self->send_error( + connection => $connection, + code => "401", + response => '{"error":"http_error_401","message":"unauthorized"}' + ); next; } } @@ -223,10 +227,18 @@ sub run { } elsif (defined($self->{dispatch}->{$root})) { # Other dispatch definition $self->send_response(connection => $connection, response => $self->dispatch_call(root => $root, request => $request)); } else { # Forbidden - $connection->send_error(RC_FORBIDDEN) + $self->send_error( + connection => $connection, + code => "403", + response => '{"error":"http_error_403","message":"forbidden"}' + ); } } else { # Authen error - $connection->send_error(RC_UNAUTHORIZED); + $self->send_error( + connection => $connection, + code => "401", + response => '{"error":"http_error_401","message":"unauthorized"}' + ); } $connection->force_last_request; } @@ -269,6 +281,15 @@ sub send_response { $options{connection}->send_response($response); } +sub send_error { + my ($self, %options) = @_; + + my $response = HTTP::Response->new($options{code}); + $response->header('Content-Type' => 'application/json'); + $response->content($options{response} . "\n"); + $options{connection}->send_response($response); +} + sub api_call { my ($self, $request) = @_; From ce08a08a8b4de9a4e27acd20bf21ee1af67d2c8d Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 16 Dec 2019 12:52:19 +0100 Subject: [PATCH 256/948] enh(httpserver): add log when http error --- gorgone/gorgone/modules/core/httpserver/class.pm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gorgone/gorgone/modules/core/httpserver/class.pm b/gorgone/gorgone/modules/core/httpserver/class.pm index 97b5e7ad541..8f5849c342f 100644 --- a/gorgone/gorgone/modules/core/httpserver/class.pm +++ b/gorgone/gorgone/modules/core/httpserver/class.pm @@ -208,6 +208,7 @@ sub run { if ($connector->{allowed_hosts_enabled} == 1) { if ($connector->check_allowed_host(peer_addr => inet_ntoa($connection->peeraddr())) == 0) { + $connector->{logger}->writeLogError("[httpserver] -class- " . $connection->peerhost() . " Unauthorized"); $self->send_error( connection => $connection, code => "401", @@ -227,6 +228,7 @@ sub run { } elsif (defined($self->{dispatch}->{$root})) { # Other dispatch definition $self->send_response(connection => $connection, response => $self->dispatch_call(root => $root, request => $request)); } else { # Forbidden + $connector->{logger}->writeLogError("[httpserver] -class- " . $connection->peerhost() . " '" . $request->uri->path . "' Forbidden"); $self->send_error( connection => $connection, code => "403", @@ -234,6 +236,7 @@ sub run { ); } } else { # Authen error + $connector->{logger}->writeLogError("[httpserver] -class- " . $connection->peerhost() . " Unauthorized"); $self->send_error( connection => $connection, code => "401", From a6499c7a47911ddd9a8b1c1aae1a095b2e1994fe Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 17 Dec 2019 16:10:39 +0100 Subject: [PATCH 257/948] enh(build): add logrotate and move systemd files --- gorgone/config/logrotate/centreon-gorgone-logrotate | 10 ++++++++++ .../systemd/centreon-gorgone-service | 0 .../systemd/centreon-gorgone-sysconfig | 0 gorgone/packaging/centreon-gorgone.spectemplate | 7 +++++-- 4 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 gorgone/config/logrotate/centreon-gorgone-logrotate rename gorgone/{scripts => config}/systemd/centreon-gorgone-service (100%) rename gorgone/{scripts => config}/systemd/centreon-gorgone-sysconfig (100%) diff --git a/gorgone/config/logrotate/centreon-gorgone-logrotate b/gorgone/config/logrotate/centreon-gorgone-logrotate new file mode 100644 index 00000000000..db54ecadfa2 --- /dev/null +++ b/gorgone/config/logrotate/centreon-gorgone-logrotate @@ -0,0 +1,10 @@ +@CENTREON_LOG@/gorgoned.log { + copytruncate + weekly + rotate 52 + compress + delaycompress + notifempty + missingok + su root root +} \ No newline at end of file diff --git a/gorgone/scripts/systemd/centreon-gorgone-service b/gorgone/config/systemd/centreon-gorgone-service similarity index 100% rename from gorgone/scripts/systemd/centreon-gorgone-service rename to gorgone/config/systemd/centreon-gorgone-service diff --git a/gorgone/scripts/systemd/centreon-gorgone-sysconfig b/gorgone/config/systemd/centreon-gorgone-sysconfig similarity index 100% rename from gorgone/scripts/systemd/centreon-gorgone-sysconfig rename to gorgone/config/systemd/centreon-gorgone-sysconfig diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 2c93b3a28e0..968b5bc73c5 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -43,9 +43,11 @@ mkdir -p %{buildroot}/%{perl_vendorlib}/gorgone %{__install} -d -m 0755 %{buildroot}%{_bindir} %{__install} -d -m 0755 %{buildroot}%{_usr}/local/bin/ %{__install} -d -m 0755 %{buildroot}%{_sysconfdir}/systemd/system/ -%{__cp} scripts/systemd/centreon-gorgone-service %{buildroot}%{_sysconfdir}/systemd/system/centreon-gorgone.service +%{__cp} config/systemd/centreon-gorgone-service %{buildroot}%{_sysconfdir}/systemd/system/centreon-gorgone.service %{__install} -d -m 0755 %{buildroot}%{_sysconfdir}/sysconfig -%{__cp} scripts/systemd/centreon-gorgone-sysconfig %{buildroot}%{_sysconfdir}/sysconfig/centreon-gorgone +%{__cp} config/systemd/centreon-gorgone-sysconfig %{buildroot}%{_sysconfdir}/sysconfig/centreon-gorgone +%{__install} -d -m 0755 %{buildroot}%{_sysconfdir}/logrotate.d +%{__cp} config/logrotate/gorgoned %{buildroot}%{_sysconfdir}/logrotate.d/gorgoned %{__cp} -R gorgone/* %{buildroot}/%{perl_vendorlib}/gorgone/ %{__cp} gorgoned %{buildroot}%{_bindir}/ @@ -65,6 +67,7 @@ rm -rf %{buildroot} %attr(755,root,root) %{_usr}/local/bin/gorgone_config_init.pl %attr(755,root,root) %{_sysconfdir}/systemd/system/centreon-gorgone.service %config(noreplace) %{_sysconfdir}/sysconfig/centreon-gorgone +%config(noreplace) %{_sysconfdir}/logrotate.d/gorgoned %post %systemd_post centreon-gorgone.service || : From 5b35bfffea2b1423038ec8ed441103497fad789a Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 17 Dec 2019 16:12:45 +0100 Subject: [PATCH 258/948] fin(build): rename logrotate file --- gorgone/config/logrotate/{centreon-gorgone-logrotate => gorgoned} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename gorgone/config/logrotate/{centreon-gorgone-logrotate => gorgoned} (100%) diff --git a/gorgone/config/logrotate/centreon-gorgone-logrotate b/gorgone/config/logrotate/gorgoned similarity index 100% rename from gorgone/config/logrotate/centreon-gorgone-logrotate rename to gorgone/config/logrotate/gorgoned From 5014f245cc531914ab1271e26ce94f5e26e3faee Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 17 Dec 2019 16:23:57 +0100 Subject: [PATCH 259/948] fix(build): log path --- gorgone/config/logrotate/gorgoned | 2 +- gorgone/packaging/centreon-gorgone.spectemplate | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/gorgone/config/logrotate/gorgoned b/gorgone/config/logrotate/gorgoned index db54ecadfa2..59d4d5fbcaa 100644 --- a/gorgone/config/logrotate/gorgoned +++ b/gorgone/config/logrotate/gorgoned @@ -1,4 +1,4 @@ -@CENTREON_LOG@/gorgoned.log { +/var/log/centreon/gorgoned.log { copytruncate weekly rotate 52 diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 968b5bc73c5..95106db3324 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -43,6 +43,7 @@ mkdir -p %{buildroot}/%{perl_vendorlib}/gorgone %{__install} -d -m 0755 %{buildroot}%{_bindir} %{__install} -d -m 0755 %{buildroot}%{_usr}/local/bin/ %{__install} -d -m 0755 %{buildroot}%{_sysconfdir}/systemd/system/ +%{__install} -d -m 0775 %buildroot%{_localstatedir}/log/centreon %{__cp} config/systemd/centreon-gorgone-service %{buildroot}%{_sysconfdir}/systemd/system/centreon-gorgone.service %{__install} -d -m 0755 %{buildroot}%{_sysconfdir}/sysconfig %{__cp} config/systemd/centreon-gorgone-sysconfig %{buildroot}%{_sysconfdir}/sysconfig/centreon-gorgone From 9441e5415ec059e8fc622ab56a2dff177e536e5a Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 17 Dec 2019 17:24:19 +0100 Subject: [PATCH 260/948] enh(broker): add flag check --- .../gorgone/modules/centreon/broker/class.pm | 58 ++++++++++++++----- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/broker/class.pm b/gorgone/gorgone/modules/centreon/broker/class.pm index bb09a4bcc19..9fcc1b10819 100644 --- a/gorgone/gorgone/modules/centreon/broker/class.pm +++ b/gorgone/gorgone/modules/centreon/broker/class.pm @@ -85,6 +85,21 @@ sub class_handle_HUP { } } +sub get_broker_stats_collection_flag { + my ($self, %options) = @_; + + my ($status, $datas) = $self->{class_object_centreon}->custom_execute( + request => "SELECT `value` FROM options WHERE `key` = 'enable_broker_stats'", + mode => 2 + ); + if ($status == -1 || !defined($datas->[0][0])) { + $self->{logger}->writeLogError('[broker] -class- Cannot get Broker statistics collection flag'); + return -1; + } + + return $datas->[0][0]; +} + sub action_brokerstats { my ($self, %options) = @_; @@ -98,6 +113,18 @@ sub action_brokerstats { } ); + if ($self->get_broker_stats_collection_flag() < 1) { + $self->send_log( + code => gorgone::class::module::ACTION_FINISH_OK, + token => $options{token}, + data => { + message => 'no collection configured' + } + ); + $self->{logger}->writeLogInfo("[broker] -class- No Broker statistics collection configured"); + return 0; + } + my $request = "SELECT id, cache_directory, config_name FROM cfg_centreonbroker " . "JOIN nagios_server " . "WHERE ns_activate = '1' AND stats_activate = '1' AND ns_nagios_server = id"; @@ -106,7 +133,7 @@ sub action_brokerstats { $request .= " AND id = '" . $options{data}->{variables}[0] . "'"; } - my ($status, $datas) = $self->{class_object}->custom_execute(request => $request, mode => 2); + my ($status, $datas) = $self->{class_object_centreon}->custom_execute(request => $request, mode => 2); if ($status == -1) { $self->send_log( code => gorgone::class::module::ACTION_FINISH_KO, @@ -221,20 +248,6 @@ sub event { sub run { my ($self, %options) = @_; - - # Database creation. We stay in the loop still there is an error - $self->{db_centreon} = gorgone::class::db->new( - dsn => $self->{config_db_centreon}->{dsn}, - user => $self->{config_db_centreon}->{username}, - password => $self->{config_db_centreon}->{password}, - force => 2, - logger => $self->{logger} - ); - ##### Load objects ##### - $self->{class_object} = gorgone::class::sqlquery->new( - logger => $self->{logger}, - db_centreon => $self->{db_centreon} - ); # Connect internal $connector->{internal_socket} = gorgone::standard::library::connect_com( @@ -248,6 +261,19 @@ sub run { action => 'BROKERREADY', data => {} ); + + $self->{db_centreon} = gorgone::class::db->new( + dsn => $self->{config_db_centreon}->{dsn}, + user => $self->{config_db_centreon}->{username}, + password => $self->{config_db_centreon}->{password}, + force => 2, + logger => $self->{logger} + ); + $self->{class_object_centreon} = gorgone::class::sqlquery->new( + logger => $self->{logger}, + db_centreon => $self->{db_centreon} + ); + $self->{poll} = [ { socket => $connector->{internal_socket}, @@ -255,7 +281,7 @@ sub run { callback => \&event, } ]; - + if (defined($self->{config}->{cron})) { $self->send_internal_action( action => 'ADDCRON', From 3b2b66324c916c2009bc01ab4c6b908a168b05b0 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 17 Dec 2019 17:58:43 +0100 Subject: [PATCH 261/948] enh(legacycmd): change illegal characters key --- gorgone/gorgone/modules/centreon/legacycmd/class.pm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index 3882f311c27..bab9436415e 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -142,7 +142,7 @@ sub get_illegal_characters { my ($self, %options) = @_; my ($status, $datas) = $self->{class_object_centreon}->custom_execute( - request => "SELECT `value` FROM options WHERE `key` = 'centcore_illegal_characters'", + request => "SELECT `value` FROM options WHERE `key` = 'gorgone_illegal_characters'", mode => 2 ); if ($status == -1 || !defined($datas->[0][0])) { @@ -150,7 +150,7 @@ sub get_illegal_characters { return -1; } - $self->{centcore_illegal_characters} = $datas->[0][0]; + $self->{gorgone_illegal_characters} = $datas->[0][0]; return 0; } @@ -167,8 +167,8 @@ sub execute_cmd { $self->{logger}->writeLogInfo($msg); if ($options{cmd} eq 'EXTERNALCMD') { - $options{param} =~ s/[\Q$self->{centcore_illegal_characters}\E]//g - if (defined($self->{centcore_illegal_characters}) && $self->{centcore_illegal_characters} ne ''); + $options{param} =~ s/[\Q$self->{gorgone_illegal_characters}\E]//g + if (defined($self->{gorgone_illegal_characters}) && $self->{gorgone_illegal_characters} ne ''); $self->send_internal_action( action => 'ENGINECOMMAND', target => $options{target}, From 38cef139c9c323b246fd3cf20965e388ee28814c Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Thu, 19 Dec 2019 14:09:05 +0100 Subject: [PATCH 262/948] fix(core): issue with action --- gorgone/gorgone/class/core.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index b631ece93af..29810a0fa5f 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -429,7 +429,7 @@ sub message_run { socket => $self->{internal_socket}, dbh => $self->{db_gorgone}, logger => $self->{logger}, - action => $1, + action => $action, token => $token, target => $target, data => $data, @@ -455,7 +455,7 @@ sub message_run { socket => $self->{internal_socket}, dbh => $self->{db_gorgone}, logger => $self->{logger}, - action => $1, + action => $action, token => $token, target => $target, data => $data, From 9737798f6ae45d1e2a8e4afeb2d21d7628dc3919 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Fri, 20 Dec 2019 10:34:02 +0100 Subject: [PATCH 263/948] enh(core): avoid binary in message --- gorgone/gorgone/standard/library.pm | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index 8ca2a2294ec..60705b58784 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -171,7 +171,9 @@ sub zmq_core_key_response { $options{logger}->writeLogError("Encoding issue: " . $@); return -1; } - zmq_sendmsg($options{socket}, $crypttext, ZMQ_NOBLOCK); + + + zmq_sendmsg($options{socket}, unpack('H*', $crypttext), ZMQ_NOBLOCK); return 0; } @@ -246,14 +248,14 @@ sub client_get_secret { my $plaintext; eval { - $plaintext = $options{privkey}->decrypt($options{message}, 'v1.5'); + my $cryptedtext = pack('H*', $options{message}); + $plaintext = $options{privkey}->decrypt($cryptedtext, 'v1.5'); }; if ($@) { return (-1, "Decoding issue: $@"); } $plaintext = unpack('H*', $plaintext); - if ($plaintext !~ /^5b(.*?)5d(.*?)5b(.*?)5d(.*?)5b(.*)5d$/i) { return (-1, 'Wrong protocol'); } From 8556d91acfcee6b0e782ea0b6fe9d052258b4955 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Fri, 20 Dec 2019 14:56:38 +0100 Subject: [PATCH 264/948] enh(core): add base64 encoding --- gorgone/gorgone/standard/library.pm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index 60705b58784..d85aa872fb5 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -200,6 +200,7 @@ sub zmq_core_response { -literal_key => 1 ); $msg = $cipher->encrypt($msg); + $msg = unpack('H*', $msg); } zmq_sendmsg($options{socket}, $msg, ZMQ_NOBLOCK); } @@ -217,7 +218,8 @@ sub uncrypt_message { -literal_key => 1 ); eval { - $plaintext = $cipher->decrypt($options{message}); + my $cryptedmessage = pack('H*', $options{message}); + $plaintext = $cipher->decrypt($cryptedmessage); }; if ($@) { if (defined($options{logger})) { @@ -745,6 +747,7 @@ sub zmq_send_message { -literal_key => 1 ); $message = $cipher->encrypt($message); + $message = unpack('H*', $message); } zmq_sendmsg($options{socket}, $message, ZMQ_NOBLOCK); } From b71f1fc5e56a3a0add58b94e592964332a1b31ba Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Fri, 20 Dec 2019 15:17:35 +0100 Subject: [PATCH 265/948] enh(doc): update protocol details --- gorgone/docs/guide.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gorgone/docs/guide.md b/gorgone/docs/guide.md index 2c5267f7a01..42407b98a0f 100644 --- a/gorgone/docs/guide.md +++ b/gorgone/docs/guide.md @@ -19,10 +19,10 @@ Third-party clients have to use the ZeroMQ library and the following process: 3. Server -> Client: send back the pubkey ```text - [PUBKEY] [xxxxx] + [PUBKEY] [hex encoding pubkey] ``` -4. Client -> Server: send the following message with HELO crypted with the public key of the server and provides client pubkey: +4. Client -> Server: send the following message with HELO crypted with the public key of the server (and hex encoding) and provides client pubkey: ```text [HOSTNAME] [CLIENTPUBKEY] [HELO] @@ -42,8 +42,8 @@ Third-party clients have to use the ZeroMQ library and the following process: [KEY] [HOSTNAME] [symmetric key] ``` -4. Client: uncrypts the Server message with its private key. -5. Client and Server uses the symmetric key to dialog. +4. Client: uncrypts the server message with its private key. +5. Client and Server uses the symmetric key+hex encoding to dialog. The server keeps sessions for 24 hours since the last message of the client. From 8ee6377649dc6ed25aff6cd53f6bc1882bc94e7b Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 24 Dec 2019 11:02:25 +0100 Subject: [PATCH 266/948] fix(proxy): update session --- gorgone/gorgone/modules/core/proxy/class.pm | 28 +++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/gorgone/gorgone/modules/core/proxy/class.pm b/gorgone/gorgone/modules/core/proxy/class.pm index 7142dc05f9a..b6a396f1d33 100644 --- a/gorgone/gorgone/modules/core/proxy/class.pm +++ b/gorgone/gorgone/modules/core/proxy/class.pm @@ -186,6 +186,34 @@ sub action_proxyaddnode { my ($code, $data) = $self->json_decode(argument => $options{data}); return if ($code == 1); + if (defined($self->{clients}->{$data->{id}}->{class})) { + # test if a connection parameter changed + my $changed = 0; + foreach (keys %$data) { + if (ref($data->{$_}) eq 'SCALAR' && $data->{$_} ne $self->{clients}->{$_}) { + $changed = 1; + last; + } + } + + if ($changed == 0) { + $self->{logger}->writeLogInfo("[proxy] -class- session not changed $data->{id}"); + return ; + } + + $self->{logger}->writeLogInfo("[proxy] -class- recreate session for $data->{id}"); + # we send a pong reset. because the ping can be lost + $connector->send_internal_action( + action => 'PONGRESET', + data => '{ "id": ' . $data->{id} . '}', + data_noencode => 1, + token => $self->generate_token(), + target => '' + ); + + $self->{clients}->{$data->{id}}->{class}->close(); + } + $self->{clients}->{$data->{id}} = $data; $self->{clients}->{$data->{id}}->{delete} = 0; $self->{clients}->{$data->{id}}->{class} = undef; From fc9e5d40bb481b9227ea68dd574e22307c045cc5 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 24 Dec 2019 11:03:37 +0100 Subject: [PATCH 267/948] fix(proxy): update session --- gorgone/gorgone/modules/core/proxy/class.pm | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/gorgone/gorgone/modules/core/proxy/class.pm b/gorgone/gorgone/modules/core/proxy/class.pm index b6a396f1d33..83099d851dd 100644 --- a/gorgone/gorgone/modules/core/proxy/class.pm +++ b/gorgone/gorgone/modules/core/proxy/class.pm @@ -369,15 +369,13 @@ sub run { my $polls = [$poll]; foreach (keys %{$self->{clients}}) { if (defined($self->{clients}->{$_}->{delete}) && $self->{clients}->{$_}->{delete} == 1) { - if ($self->{clients}->{$_}->{type} eq 'push_zmq') { - $connector->send_internal_action( - action => 'PONGRESET', - data => '{ "id": ' . $_ . '}', - data_noencode => 1, - token => $self->generate_token(), - target => '' - ); - } + $connector->send_internal_action( + action => 'PONGRESET', + data => '{ "id": ' . $_ . '}', + data_noencode => 1, + token => $self->generate_token(), + target => '' + ); $self->{clients}->{$_}->{class}->close(); $self->{clients}->{$_}->{class} = undef; $self->{clients}->{$_}->{delete} = 0; From a2112909dd7e338f2b16789acf884184c13f2c9b Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 24 Dec 2019 11:22:31 +0100 Subject: [PATCH 268/948] enh(proxy): close connection --- gorgone/gorgone/modules/core/proxy/class.pm | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/gorgone/gorgone/modules/core/proxy/class.pm b/gorgone/gorgone/modules/core/proxy/class.pm index 83099d851dd..6cfe88f5cf6 100644 --- a/gorgone/gorgone/modules/core/proxy/class.pm +++ b/gorgone/gorgone/modules/core/proxy/class.pm @@ -203,7 +203,7 @@ sub action_proxyaddnode { $self->{logger}->writeLogInfo("[proxy] -class- recreate session for $data->{id}"); # we send a pong reset. because the ping can be lost - $connector->send_internal_action( + $self->send_internal_action( action => 'PONGRESET', data => '{ "id": ' . $data->{id} . '}', data_noencode => 1, @@ -230,6 +230,17 @@ sub action_proxydelnode { } } +sub close_connections { + my ($self, %options) = @_; + + foreach (keys %{$self->{clients}}) { + if (defined($self->{clients}->{$_}->{class})) { + $self->{logger}->writeLogInfo("[proxy] -class- close connection for $_"); + $self->{clients}->{$_}->{class}->close(); + } + } +} + sub proxy { my (%options) = @_; @@ -354,7 +365,7 @@ sub run { type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); - $connector->send_internal_action( + $self->send_internal_action( action => 'PROXYREADY', data => { pool_id => $self->{pool_id} @@ -369,7 +380,7 @@ sub run { my $polls = [$poll]; foreach (keys %{$self->{clients}}) { if (defined($self->{clients}->{$_}->{delete}) && $self->{clients}->{$_}->{delete} == 1) { - $connector->send_internal_action( + $self->send_internal_action( action => 'PONGRESET', data => '{ "id": ' . $_ . '}', data_noencode => 1, @@ -395,6 +406,7 @@ sub run { if ($rev == 0 && $self->{stop} == 1) { $self->{logger}->writeLogInfo("[proxy] -class- $$ has quit"); + $self->close_connections(); zmq_close($connector->{internal_socket}); exit(0); } From 3afe82c4f1567e64d891aeb4afe518bf9ab587a9 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 24 Dec 2019 14:23:11 +0100 Subject: [PATCH 269/948] enh(broker): add possibility to not collect localhost stats --- gorgone/docs/modules/centreon/broker.md | 1 + gorgone/gorgone/modules/centreon/broker/class.pm | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/gorgone/docs/modules/centreon/broker.md b/gorgone/docs/modules/centreon/broker.md index eb9c0e97e46..d4d56a61368 100644 --- a/gorgone/docs/modules/centreon/broker.md +++ b/gorgone/docs/modules/centreon/broker.md @@ -25,6 +25,7 @@ cron: action: BROKERSTATS parameters: timeout: 10 + collect_localhost: false ``` ## Events diff --git a/gorgone/gorgone/modules/centreon/broker/class.pm b/gorgone/gorgone/modules/centreon/broker/class.pm index 9fcc1b10819..38efde6f625 100644 --- a/gorgone/gorgone/modules/centreon/broker/class.pm +++ b/gorgone/gorgone/modules/centreon/broker/class.pm @@ -132,6 +132,11 @@ sub action_brokerstats { if (defined($options{data}->{variables}[0]) && $options{data}->{variables}[0] =~ /\d+/) { $request .= " AND id = '" . $options{data}->{variables}[0] . "'"; } + + if (!defined($options{data}->{content}->{collect_localhost}) || + $options{data}->{content}->{collect_localhost} eq 'false') { + $request .= " AND localhost = '0'"; + } my ($status, $datas) = $self->{class_object_centreon}->custom_execute(request => $request, mode => 2); if ($status == -1) { @@ -160,7 +165,7 @@ sub action_brokerstats { data => { content => { command => 'cat ' . $statistics_file, - timeout => 10, + timeout => $options{data}->{content}->{timeout}, metadata => { poller_id => $target, config_name => $_->[2], From 9cbda888faf67f15e76719c71093c777a7b41c80 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Thu, 26 Dec 2019 12:14:13 +0100 Subject: [PATCH 270/948] enh(action): launch multiple actions in one call --- gorgone/docs/modules/core/action.md | 12 +- gorgone/docs/modules/core/cron.md | 4 +- .../modules/centreon/autodiscovery/class.pm | 34 ++--- .../gorgone/modules/centreon/broker/class.pm | 20 +-- .../modules/centreon/legacycmd/class.pm | 120 +++++++++-------- gorgone/gorgone/modules/core/action/class.pm | 122 +++++++++++------- gorgone/gorgone/modules/core/cron/class.pm | 2 +- 7 files changed, 186 insertions(+), 128 deletions(-) diff --git a/gorgone/docs/modules/core/action.md b/gorgone/docs/modules/core/action.md index c7102485a26..d0ca9bd43df 100644 --- a/gorgone/docs/modules/core/action.md +++ b/gorgone/docs/modules/core/action.md @@ -48,12 +48,16 @@ command_timeout: 30 | :- | :- | | command | Command to execute | | timeout | Time in seconds before a command is considered timed out | +| continue_on_error | Behaviour in case of execution issue | ```json -{ - "command": "", - "timeout": "" -} +[ + { + "command": "", + "timeout": "", + "continue_on_error": "" + } +] ``` #### Example diff --git a/gorgone/docs/modules/core/cron.md b/gorgone/docs/modules/core/cron.md index 79b6c9764ae..c3f6fe28459 100644 --- a/gorgone/docs/modules/core/cron.md +++ b/gorgone/docs/modules/core/cron.md @@ -28,8 +28,8 @@ cron: timespec: "* * * * *" action: COMMAND parameters: - command: "date >> /tmp/date.log" - timeout: 10 + - command: "date >> /tmp/date.log" + timeout: 10 keep_token: true ``` diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm index 389dac7c23a..f90e9d0ae37 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm @@ -99,14 +99,16 @@ sub action_adddiscoverytask { target => $options{data}->{content}->{target}, token => $id, data => { - content => { - %{$options{data}->{content}}, - metadata => { - id => $id, - source => 'autodiscovery', - type => 'task', - }, - } + content => [ + { + %{$options{data}->{content}}, + metadata => { + id => $id, + source => 'autodiscovery', + type => 'task', + }, + } + ] } ); @@ -165,13 +167,15 @@ sub action_adddiscoveryjob { timespec => $options{data}->{content}->{timespec}, action => 'COMMAND', parameters => { - command => $options{data}->{content}->{command}, - timeout => $options{data}->{content}->{timeout}, - metadata => { - id => $id, - source => 'autodiscovery', - type => 'job', - } + [ + command => $options{data}->{content}->{command}, + timeout => $options{data}->{content}->{timeout}, + metadata => { + id => $id, + source => 'autodiscovery', + type => 'job', + } + ] }, keep_token => 1, }; diff --git a/gorgone/gorgone/modules/centreon/broker/class.pm b/gorgone/gorgone/modules/centreon/broker/class.pm index 38efde6f625..f73616a2550 100644 --- a/gorgone/gorgone/modules/centreon/broker/class.pm +++ b/gorgone/gorgone/modules/centreon/broker/class.pm @@ -163,14 +163,16 @@ sub action_brokerstats { action => 'COMMAND', token => $options{token}, data => { - content => { - command => 'cat ' . $statistics_file, - timeout => $options{data}->{content}->{timeout}, - metadata => { - poller_id => $target, - config_name => $_->[2], + content => [ + { + command => 'cat ' . $statistics_file, + timeout => $options{data}->{content}->{timeout}, + metadata => { + poller_id => $target, + config_name => $_->[2], + } } - } + ] } ); $targets{$target} = 1; @@ -214,7 +216,7 @@ sub write_stats { foreach my $entry (@{$options{data}->{data}->{result}}) { my $data = JSON::XS->new->utf8->decode($entry->{data}); - next if (!defined($data->{exit_code}) || $data->{exit_code} != 0 || + next if (!defined($data->{result}->{exit_code}) || $data->{result}->{exit_code} != 0 || !defined($data->{metadata}->{poller_id}) || !defined($data->{metadata}->{config_name})); my $dest_dir = $self->{config}->{cache_dir} . '/' . $data->{metadata}->{poller_id}; @@ -222,7 +224,7 @@ sub write_stats { my $dest_file = $dest_dir . '/' . $data->{metadata}->{config_name} . '.json'; $self->{logger}->writeLogDebug("[broker] -class- Writing file '" . $dest_file . "'"); open(FH, '>', $dest_file); - print FH $data->{stdout}; + print FH $data->{result}->{stdout}; close(FH); } } diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index bab9436415e..7ed2a29b485 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -248,12 +248,14 @@ sub execute_cmd { target => undef, token => $self->generate_token(), data => { - content => { - command => $cmd, - metadata => { - centcore_cmd => 'SENDEXPORTFILE', + content => [ + { + command => $cmd, + metadata => { + centcore_cmd => 'SENDEXPORTFILE', + } } - } + ] }, ); } elsif ($options{cmd} eq 'SYNCTRAP') { @@ -285,13 +287,15 @@ sub execute_cmd { target => $options{target}, token => $self->generate_token(), data => { - content => { - command => 'sudo ' . $cmd, - metadata => { - centcore_proxy => 1, - centcore_cmd => 'RESTART', + content => [ + { + command => 'sudo ' . $cmd, + metadata => { + centcore_proxy => 1, + centcore_cmd => 'RESTART', + } } - } + ] }, ); } elsif ($options{cmd} eq 'RELOAD') { @@ -301,13 +305,15 @@ sub execute_cmd { target => $options{target}, token => $self->generate_token(), data => { - content => { - command => 'sudo ' . $cmd, - metadata => { - centcore_proxy => 1, - centcore_cmd => 'RELOAD', + content => [ + { + command => 'sudo ' . $cmd, + metadata => { + centcore_proxy => 1, + centcore_cmd => 'RELOAD', + } } - } + ] }, ); } elsif ($options{cmd} eq 'START') { @@ -317,13 +323,15 @@ sub execute_cmd { target => $options{target}, token => $self->generate_token(), data => { - content => { - command => 'sudo ' . $cmd, - metadata => { - centcore_proxy => 1, - centcore_cmd => 'START', + content => [ + { + command => 'sudo ' . $cmd, + metadata => { + centcore_proxy => 1, + centcore_cmd => 'START', + } } - } + ] }, ); } elsif ($options{cmd} eq 'STOP') { @@ -333,13 +341,15 @@ sub execute_cmd { target => $options{target}, token => $self->generate_token(), data => { - content => { - command => 'sudo ' . $cmd, - metadata => { - centcore_proxy => 1, - centcore_cmd => 'STOP', + content => [ + { + command => 'sudo ' . $cmd, + metadata => { + centcore_proxy => 1, + centcore_cmd => 'STOP', + } } - } + ] }, ); } elsif ($options{cmd} eq 'RESTARTCENTREONTRAPD') { @@ -349,13 +359,15 @@ sub execute_cmd { target => $options{target}, token => $self->generate_token(), data => { - content => { - command => 'sudo service ' . $cmd . ' restart', - metadata => { - centcore_proxy => 1, - centcore_cmd => 'RESTARTCENTREONTRAPD', + content => [ + { + command => 'sudo service ' . $cmd . ' restart', + metadata => { + centcore_proxy => 1, + centcore_cmd => 'RESTARTCENTREONTRAPD', + } } - } + ] }, ); } elsif ($options{cmd} eq 'RELOADCENTREONTRAPD') { @@ -365,13 +377,15 @@ sub execute_cmd { target => $options{target}, token => $self->generate_token(), data => { - content => { - command => 'sudo service ' . $cmd . ' reload', - metadata => { - centcore_proxy => 1, - centcore_cmd => 'RELOADCENTREONTRAPD', + content => [ + { + command => 'sudo service ' . $cmd . ' reload', + metadata => { + centcore_proxy => 1, + centcore_cmd => 'RELOADCENTREONTRAPD', + } } - } + ] }, ); } elsif ($options{cmd} eq 'STARTWORKER') { @@ -384,12 +398,14 @@ sub execute_cmd { target => undef, token => $self->generate_token(), data => { - content => { - command => $cmd, - metadata => { - centcore_cmd => 'STARTWORKER', + content => [ + { + command => $cmd, + metadata => { + centcore_cmd => 'STARTWORKER', + } } - } + ] }, ); } elsif ($options{cmd} eq 'CREATEREMOTETASK') { @@ -403,12 +419,14 @@ sub execute_cmd { target => undef, token => $self->generate_token(), data => { - content => { - command => $cmd, - metadata => { - centcore_cmd => 'CREATEREMOTETASK', + content => [ + { + command => $cmd, + metadata => { + centcore_cmd => 'CREATEREMOTETASK', + } } - } + ] }, ); } diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index b65feb6630d..da23eaccf60 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -91,69 +91,99 @@ sub class_handle_HUP { sub action_command { my ($self, %options) = @_; - if (!defined($options{data}->{content}->{command}) || $options{data}->{content}->{command} eq '') { + if (!defined($options{data}->{content}) || ref($options{data}->{content}) ne 'ARRAY') { $self->send_log( socket => $options{socket_log}, code => $self->ACTION_FINISH_KO, token => $options{token}, data => { - message => 'need command argument', - metadata => $options{data}->{content}->{metadata} + message => "expected array, found '" . ref($options{data}->{content}) . "'", } ); return -1; } - - $self->send_log( - socket => $options{socket_log}, - code => $self->ACTION_BEGIN, - token => $options{token}, - data => { - message => "command '$options{data}->{content}->{command}' has started", - metadata => $options{data}->{content}->{metadata} + + my $index = 0; + foreach my $command (@{$options{data}->{content}}) { + if (!defined($command->{command}) || $command->{command} eq '') { + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { + message => "need command argument at array index '" . $index . "'", + } + ); + return -1; } - ); - - my $start = time(); - my ($error, $stdout, $return_code) = gorgone::standard::misc::backtick( - command => $options{data}->{content}->{command}, - #arguments => [@$args, $sub_cmd], - timeout => (defined($options{data}->{content}->{timeout})) ? - $options{data}->{content}->{timeout} : $self->{command_timeout}, - wait_exit => 1, - redirect_stderr => 1, - logger => $self->{logger} - ); - my $end = time(); - if ($error <= -1000) { + $index++; + } + + foreach my $command (@{$options{data}->{content}}) { $self->send_log( socket => $options{socket_log}, - code => $self->ACTION_FINISH_KO, + code => $self->ACTION_BEGIN, token => $options{token}, data => { - message => "command '$options{data}->{content}->{command}' execution issue: $stdout", - exit_code => $return_code, - metadata => $options{data}->{content}->{metadata}, - start => $start, - end => $end + message => "command has started", + command => $command->{command}, + metadata => $command->{metadata} } ); - return -1; - } - - $self->send_log( - socket => $options{socket_log}, - code => $self->ACTION_FINISH_OK, - token => $options{token}, - data => { - message => "command '$options{data}->{content}->{command}' has finished", - stdout => $stdout, - exit_code => $return_code, - metadata => $options{data}->{content}->{metadata}, - start => $start, - end => $end + + my $start = time(); + my ($error, $stdout, $return_code) = gorgone::standard::misc::backtick( + command => $command->{command}, + timeout => (defined($command->{timeout})) ? $command->{timeout} : $self->{command_timeout}, + wait_exit => 1, + redirect_stderr => 1, + logger => $self->{logger} + ); + my $end = time(); + if ($error <= -1000) { + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { + message => "command execution issue", + command => $command->{command}, + metadata => $command->{metadata}, + result => { + exit_code => $return_code, + stdout => $stdout + }, + metrics => { + start => $start, + end => $end, + duration => $end - $start + } + } + ); + } else { + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_OK, + token => $options{token}, + data => { + message => "command has finished", + command => $command->{command}, + metadata => $command->{metadata}, + result => { + exit_code => $return_code, + stdout => $stdout + }, + metrics => { + start => $start, + end => $end, + duration => $end - $start + } + } + ); } - ); + + return -1 if (defined($command->{continue_on_error}) && $command->{continue_on_error} eq 'false'); + } return 0; } diff --git a/gorgone/gorgone/modules/core/cron/class.pm b/gorgone/gorgone/modules/core/cron/class.pm index 050b5f509d8..5dd04e0a98e 100644 --- a/gorgone/gorgone/modules/core/cron/class.pm +++ b/gorgone/gorgone/modules/core/cron/class.pm @@ -422,7 +422,7 @@ sub dispatcher { action => $options->{definition}->{action}, target => $options->{definition}->{target}, data => { - content => { %{$options->{definition}->{parameters}} } + content => $options->{definition}->{parameters} }, json_encode => 1 ); From 4c685443b88df16c4431827fc6c9ba7ec82a1ab6 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Thu, 26 Dec 2019 12:16:19 +0100 Subject: [PATCH 271/948] fix(autodiscovery): fix job cron add --- .../modules/centreon/autodiscovery/class.pm | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm index f90e9d0ae37..2342230c73f 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm @@ -166,17 +166,15 @@ sub action_adddiscoveryjob { target => $options{data}->{content}->{target}, timespec => $options{data}->{content}->{timespec}, action => 'COMMAND', - parameters => { - [ - command => $options{data}->{content}->{command}, - timeout => $options{data}->{content}->{timeout}, - metadata => { - id => $id, - source => 'autodiscovery', - type => 'job', - } - ] - }, + parameters => [ + command => $options{data}->{content}->{command}, + timeout => $options{data}->{content}->{timeout}, + metadata => { + id => $id, + source => 'autodiscovery', + type => 'job', + } + ] keep_token => 1, }; From 756d541beb9f0d97ee45d7f89cebe0d70ab782ee Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Thu, 26 Dec 2019 14:26:58 +0100 Subject: [PATCH 272/948] enh(engine): launch multiple commands in one call --- gorgone/docs/modules/centreon/engine.md | 18 +- .../gorgone/modules/centreon/engine/class.pm | 179 +++++++++++------- 2 files changed, 119 insertions(+), 78 deletions(-) diff --git a/gorgone/docs/modules/centreon/engine.md b/gorgone/docs/modules/centreon/engine.md index a5997bf2bc1..944f83775aa 100644 --- a/gorgone/docs/modules/centreon/engine.md +++ b/gorgone/docs/modules/centreon/engine.md @@ -49,10 +49,12 @@ command_file: "/var/lib/centreon-engine/rw/centengine.cmd" | command_file | Path to the Centreon Engine command file pipe | ```json -{ - "command": "", - "command_file": "" -} +[ + { + "command": "", + "command_file": "" + } +] ``` #### Example @@ -61,7 +63,9 @@ command_file: "/var/lib/centreon-engine/rw/centengine.cmd" curl --request POST "https://hostname:8443/api/centreon/engine/command" \ --header "Accept: application/json" \ --header "Content-Type: application/json" \ - --data "{ - \"command\": \"[653284380] SCHEDULE_SVC_CHECK;host1;service1;653284380\" -}" + --data "[ + { + \"command\": \"[653284380] SCHEDULE_SVC_CHECK;host1;service1;653284380\" + } +]" ``` diff --git a/gorgone/gorgone/modules/centreon/engine/class.pm b/gorgone/gorgone/modules/centreon/engine/class.pm index 4b8c6f7839f..4e7518d1239 100644 --- a/gorgone/gorgone/modules/centreon/engine/class.pm +++ b/gorgone/gorgone/modules/centreon/engine/class.pm @@ -84,93 +84,130 @@ sub class_handle_HUP { sub action_enginecommand { my ($self, %options) = @_; - my $command = $options{data}->{content}->{command}; - if (!defined($command) || $command eq '') { - $self->{logger}->writeLogError('[engine] -class- action_enginecommand: need command argument'); + if (!defined($options{data}->{content}) || ref($options{data}->{content}) ne 'ARRAY') { $self->send_log( socket => $options{socket_log}, code => $self->ACTION_FINISH_KO, token => $options{token}, - data => { message => "need command argument" } + data => { + message => "expected array, found '" . ref($options{data}->{content}) . "'", + } ); return -1; } - my $command_file = ''; - if (defined($options{data}->{content}->{command_file}) && $options{data}->{content}->{command_file} ne '') { - $command_file = $options{data}->{content}->{command_file}; - } elsif (defined($self->{config}->{command_file}) && $self->{config}->{command_file} ne '') { - $command_file = $self->{config}->{command_file}; + my $index = 0; + foreach my $command (@{$options{data}->{content}}) { + if (!defined($command->{command}) || $command->{command} eq '') { + $self->{logger}->writeLogError("[engine] -class- action_enginecommand: need command argument at array index '" . $index . "'"); + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { + message => "need command argument at array index '" . $index . "'", + } + ); + return -1; + } + $index++; } - if (!defined($command_file) || $command_file eq '') { - $self->{logger}->writeLogError("[engine] -class- need command_file (config or call) argument"); - $self->send_log( - socket => $options{socket_log}, - code => $self->ACTION_FINISH_KO, - token => $options{token}, - data => { message => "need command_file (config or call) argument" } - ); - return -1; - } - if (! -e $command_file) { - $self->{logger}->writeLogError("[engine] -class- command '$command' - command_file '$command_file' must exist"); - $self->send_log( - socket => $options{socket_log}, - code => $self->ACTION_FINISH_KO, - token => $options{token}, - data => { message => "command '$command' - command_file '$command_file' must exist" } - ); - return -1; - } - if (! -p $command_file) { - $self->{logger}->writeLogError("[engine] -class- command '$command' - command_file '$command_file' must be a pipe file"); - $self->send_log( - socket => $options{socket_log}, - code => $self->ACTION_FINISH_KO, - token => $options{token}, - data => { message => "command '$command' - command_file '$command_file' must be a pipe file" } - ); - return -1; - } - if (! -w $command_file) { - $self->{logger}->writeLogError("[engine] -class- command '$command' - command_file '$command_file' must be writeable"); - $self->send_log( - socket => $options{socket_log}, - code => $self->ACTION_FINISH_KO, - token => $options{token}, - data => { message => "command '$command' - command_file '$command_file' must be writeable" } - ); - return -1; - } + foreach my $command (@{$options{data}->{content}}) { + my $command_file = ''; + if (defined($command->{command_file}) && $command->{command_file} ne '') { + $command_file = $command->{command_file}; + } elsif (defined($self->{config}->{command_file}) && $self->{config}->{command_file} ne '') { + $command_file = $self->{config}->{command_file}; + } - my $fh; - eval { - local $SIG{ALRM} = sub { die 'Timeout command' }; - alarm $self->{timeout}; - open($fh, ">", $command_file) or die "cannot open '$command_file': $!"; - print $fh $command . "\n"; - close $fh; - alarm 0; - }; - if ($@) { - close $fh if (defined($fh)); - $self->{logger}->writeLogError("[engine] -class- submit engine command '$command' issue: $@"); + if (!defined($command_file) || $command_file eq '') { + $self->{logger}->writeLogError("[engine] -class- need command_file (config or call) argument"); + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { + message => "need command_file (config or call) argument", + command => $command->{command} + } + ); + return -1; + } + if (! -e $command_file) { + $self->{logger}->writeLogError("[engine] -class- command '$command->{command}' - command_file '$command_file' must exist"); + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { + message => "command_file '$command_file' must exist", + command => $command->{command} + } + ); + return -1; + } + if (! -p $command_file) { + $self->{logger}->writeLogError("[engine] -class- command '$command->{command}' - command_file '$command_file' must be a pipe file"); + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { + message => "command_file '$command_file' must be a pipe file", + command => $command->{command} + } + ); + return -1; + } + if (! -w $command_file) { + $self->{logger}->writeLogError("[engine] -class- command '$command->{command}' - command_file '$command_file' must be writeable"); + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { + message => "command_file '$command_file' must be writeable", + command => $command->{command} + } + ); + return -1; + } + + my $fh; + eval { + local $SIG{ALRM} = sub { die 'Timeout command' }; + alarm $self->{timeout}; + open($fh, ">", $command_file) or die "cannot open '$command_file': $!"; + print $fh $command->{command} . "\n"; + close $fh; + alarm 0; + }; + if ($@) { + close $fh if (defined($fh)); + $self->{logger}->writeLogError("[engine] -class- submit engine command '$command->{command}' issue: $@"); + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { + message => "submit engine command issue: $@", + command => $command->{command} + } + ); + return undef; + } + $self->send_log( socket => $options{socket_log}, - code => $self->ACTION_FINISH_KO, + code => $self->ACTION_FINISH_OK, token => $options{token}, - data => { message => "submit engine command '$command' issue: $@" } + data => { + message => "command had been submitted", + command => $command->{command} + } ); - return undef; } - - $self->send_log( - socket => $options{socket_log}, - code => $self->ACTION_FINISH_OK, - token => $options{token}, - data => { message => "command '$command' had been submitted': $@" } - ); return 0; } From 25a25bfb92aefcb7fbfdb55be0e274661829d68c Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Thu, 26 Dec 2019 14:27:26 +0100 Subject: [PATCH 273/948] fix(doc): fix action command example --- gorgone/docs/modules/core/action.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/gorgone/docs/modules/core/action.md b/gorgone/docs/modules/core/action.md index d0ca9bd43df..54127d2ae70 100644 --- a/gorgone/docs/modules/core/action.md +++ b/gorgone/docs/modules/core/action.md @@ -66,7 +66,9 @@ command_timeout: 30 curl --request POST "https://hostname:8443/api/core/action/command" \ --header "Accept: application/json" \ --header "Content-Type: application/json" \ - --data "{ - \"command\": \"echo 'Test command' >> /tmp/here.log\" -}" + --data "[ + { + \"command\": \"echo 'Test command' >> /tmp/here.log\" + } +]" ``` From 0df62320e843a7dfdb8c57e1a4fbcae60a83a1b1 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Thu, 26 Dec 2019 14:55:58 +0100 Subject: [PATCH 274/948] enh(action/engine): add more info in logs --- .../gorgone/modules/centreon/engine/class.pm | 39 +++++++++++++------ gorgone/gorgone/modules/core/action/class.pm | 21 +++++++++- 2 files changed, 47 insertions(+), 13 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/engine/class.pm b/gorgone/gorgone/modules/centreon/engine/class.pm index 4e7518d1239..aca37d0eaf9 100644 --- a/gorgone/gorgone/modules/centreon/engine/class.pm +++ b/gorgone/gorgone/modules/centreon/engine/class.pm @@ -91,6 +91,7 @@ sub action_enginecommand { token => $options{token}, data => { message => "expected array, found '" . ref($options{data}->{content}) . "'", + request_content => $options{data}->{content} } ); return -1; @@ -106,12 +107,23 @@ sub action_enginecommand { token => $options{token}, data => { message => "need command argument at array index '" . $index . "'", + request_content => $options{data}->{content} } ); return -1; } $index++; } + + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_BEGIN, + token => $options{token}, + data => { + message => "commands processing has started", + request_content => $options{data}->{content} + } + ); foreach my $command (@{$options{data}->{content}}) { my $command_file = ''; @@ -132,7 +144,7 @@ sub action_enginecommand { command => $command->{command} } ); - return -1; + (defined($command->{continue_on_error}) && $command->{continue_on_error} eq 'false') ? return -1 : next; } if (! -e $command_file) { $self->{logger}->writeLogError("[engine] -class- command '$command->{command}' - command_file '$command_file' must exist"); @@ -145,7 +157,7 @@ sub action_enginecommand { command => $command->{command} } ); - return -1; + (defined($command->{continue_on_error}) && $command->{continue_on_error} eq 'false') ? return -1 : next; } if (! -p $command_file) { $self->{logger}->writeLogError("[engine] -class- command '$command->{command}' - command_file '$command_file' must be a pipe file"); @@ -158,7 +170,7 @@ sub action_enginecommand { command => $command->{command} } ); - return -1; + (defined($command->{continue_on_error}) && $command->{continue_on_error} eq 'false') ? return -1 : next; } if (! -w $command_file) { $self->{logger}->writeLogError("[engine] -class- command '$command->{command}' - command_file '$command_file' must be writeable"); @@ -171,7 +183,7 @@ sub action_enginecommand { command => $command->{command} } ); - return -1; + (defined($command->{continue_on_error}) && $command->{continue_on_error} eq 'false') ? return -1 : next; } my $fh; @@ -195,7 +207,7 @@ sub action_enginecommand { command => $command->{command} } ); - return undef; + (defined($command->{continue_on_error}) && $command->{continue_on_error} eq 'false') ? return -1 : next; } $self->send_log( @@ -203,11 +215,20 @@ sub action_enginecommand { code => $self->ACTION_FINISH_OK, token => $options{token}, data => { - message => "command had been submitted", + message => "command has been submitted", command => $command->{command} } ); } + + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_OK, + token => $options{token}, + data => { + message => "commands processing has finished" + } + ); return 0; } @@ -261,12 +282,6 @@ sub create_child { if ($child_pid == 0) { $self->action_run(action => $action, token => $token, data => $data); exit(0); - } else { - $self->send_log( - code => $self->ACTION_BEGIN, - token => $token, - data => { message => 'proceed action' } - ); } } diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index da23eaccf60..07a8354f18e 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -118,6 +118,16 @@ sub action_command { } $index++; } + + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_BEGIN, + token => $options{token}, + data => { + message => "commands processing has started", + request_content => $options{data}->{content} + } + ); foreach my $command (@{$options{data}->{content}}) { $self->send_log( @@ -182,8 +192,17 @@ sub action_command { ); } - return -1 if (defined($command->{continue_on_error}) && $command->{continue_on_error} eq 'false'); + return -1 if (defined($command->{continue_on_error}) && $command->{continue_on_error} eq 'false'); } + + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_OK, + token => $options{token}, + data => { + message => "commands processing has finished" + } + ); return 0; } From 88be3f1c660d63418289b35d29dcf0e6b4c6bde9 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Thu, 26 Dec 2019 15:00:01 +0100 Subject: [PATCH 275/948] enh(legacycmd): manage engine command array --- gorgone/gorgone/modules/centreon/legacycmd/class.pm | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index 7ed2a29b485..7db9946ab9e 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -174,10 +174,12 @@ sub execute_cmd { target => $options{target}, token => $self->generate_token(), data => { - content => { - command => $options{param}, - command_file => $self->{pollers}->{$options{target}}->{command_file}, - } + content => [ + { + command => $options{param}, + command_file => $self->{pollers}->{$options{target}}->{command_file}, + } + ] }, ); } elsif ($options{cmd} eq 'SENDCFGFILE') { From 141bbc4377040341ded7797aa94c2dbf0847572e Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Thu, 26 Dec 2019 15:41:23 +0100 Subject: [PATCH 276/948] fix(api): fix parameters transit --- gorgone/gorgone/standard/api.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/gorgone/gorgone/standard/api.pm b/gorgone/gorgone/standard/api.pm index 1e35be68251..fa286088553 100644 --- a/gorgone/gorgone/standard/api.pm +++ b/gorgone/gorgone/standard/api.pm @@ -120,6 +120,7 @@ sub call_action { target => $options{target}, token => $result->{token}, refresh => $options{refresh}, + parameters => $options{data}->{parameters} ); } else { $response = '{"token":"' . $result->{token} . '"}'; From 52d4f0f2cdae57f17a0415a4c8a079b984544ab5 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Thu, 26 Dec 2019 17:07:20 +0100 Subject: [PATCH 277/948] enh(api): better handle endpoints --- gorgone/gorgone/class/core.pm | 10 +++---- .../gorgone/modules/core/httpserver/class.pm | 30 +++---------------- .../gorgone/modules/core/httpserver/hooks.pm | 6 ++-- gorgone/gorgone/standard/api.pm | 13 ++------ gorgone/gorgone/standard/library.pm | 5 ++-- 5 files changed, 16 insertions(+), 48 deletions(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 29810a0fa5f..1b32d82092b 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -57,6 +57,7 @@ sub new { $self->{modules_register} = {}; $self->{modules_events} = {}; $self->{modules_id} = {}; + $self->{api_endpoints} = {}; $self->{purge_timer} = time(); $self->{history_timer} = time(); $self->{kill_timer} = undef; @@ -328,12 +329,9 @@ sub load_module { namespace => $namespace, name => $name, package => $package - }, - api => { - uri => $event->{uri}, - method => $event->{method} } }; + $self->{api_endpoints}->{$event->{method} . '_/' . $namespace . '/' . $name . $event->{uri}} = $event->{event} if defined($event->{uri}); } $self->{logger}->writeLogInfo("[core] Module '" . $options{config_module}->{name} . "' is loaded"); @@ -740,7 +738,7 @@ sub run { external_socket => $gorgone->{external_socket}, internal_socket => $gorgone->{internal_socket}, dbh => $gorgone->{db_gorgone}, - modules_events => $gorgone->{modules_events}, + api_endpoints => $gorgone->{api_endpoints} ); } @@ -759,7 +757,7 @@ sub run { internal_socket => $gorgone->{internal_socket}, dbh => $gorgone->{db_gorgone}, poll => $poll, - modules_events => $gorgone->{modules_events}, + api_endpoints => $gorgone->{api_endpoints}, ); $cb_timer_check = time(); $count += $count_module; diff --git a/gorgone/gorgone/modules/core/httpserver/class.pm b/gorgone/gorgone/modules/core/httpserver/class.pm index 8f5849c342f..87afee417bc 100644 --- a/gorgone/gorgone/modules/core/httpserver/class.pm +++ b/gorgone/gorgone/modules/core/httpserver/class.pm @@ -26,6 +26,7 @@ use strict; use warnings; use gorgone::standard::library; use gorgone::standard::misc; +use gorgone::standard::api; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); use HTTP::Daemon; @@ -49,7 +50,7 @@ sub new { $connector->{config} = $options{config}; $connector->{config_core} = $options{config_core}; $connector->{stop} = 0; - $connector->{modules_events} = $options{modules_events}; + $connector->{api_endpoints} = $options{api_endpoints}; if ($connector->{config}->{ssl} eq 'true') { exit(1) if (gorgone::standard::misc::mymodule_load( @@ -221,9 +222,7 @@ sub run { if ($self->authentication($request->header('Authorization'))) { # Check Basic authentication my ($root) = ($request->uri->path =~ /^(\/\w+)/); - if ($request->method eq 'GET' && $root eq "/status") { # Server status - $self->send_response(connection => $connection, response => $self->server_status()); - } elsif ($root eq "/api") { # API + if ($root eq "/api") { # API $self->send_response(connection => $connection, response => $self->api_call($request)); } elsif (defined($self->{dispatch}->{$root})) { # Other dispatch definition $self->send_response(connection => $connection, response => $self->dispatch_call(root => $root, request => $request)); @@ -305,7 +304,6 @@ sub api_call { return '{"error":"decode_error","message":"POST content must be JSON-formated"}';; } - require 'gorgone/standard/api.pm'; my %parameters = $request->uri->query_form; my $response = gorgone::standard::api::root( method => $request->method, @@ -314,7 +312,7 @@ sub api_call { content => $content, socket => $connector->{internal_socket}, logger => $self->{logger}, - modules_events => $self->{modules_events} + api_endpoints => $self->{api_endpoints} ); return $response; @@ -338,24 +336,4 @@ sub dispatch_call { return $response; } -sub server_status { - my ($self, %options) = @_; - - my %data = ( - starttime => $time, - dispatch => $self->{dispatch}, - modules_events => $self->{modules_events} - ); - - my $encoded_data; - eval { - $encoded_data = JSON::XS->new->utf8->encode(\%data); - }; - if ($@) { - $encoded_data = '{"error":"encode_error","message":"Cannot encode response into JSON format"}'; - } - - return $encoded_data; -} - 1; diff --git a/gorgone/gorgone/modules/core/httpserver/hooks.pm b/gorgone/gorgone/modules/core/httpserver/hooks.pm index d4bdf745d2f..4f7befa7260 100644 --- a/gorgone/gorgone/modules/core/httpserver/hooks.pm +++ b/gorgone/gorgone/modules/core/httpserver/hooks.pm @@ -62,7 +62,7 @@ sub register { sub init { my (%options) = @_; - create_child(logger => $options{logger}, modules_events => $options{modules_events}); + create_child(logger => $options{logger}, api_endpoints => $options{api_endpoints}); } sub routing { @@ -144,7 +144,7 @@ sub check { $httpserver = {}; delete $options{dead_childs}->{$pid}; if ($stop == 0) { - create_child(logger => $options{logger}, modules_events => $options{modules_events}); + create_child(logger => $options{logger}, api_endpoints => $options{api_endpoints}); } } @@ -165,7 +165,7 @@ sub create_child { logger => $options{logger}, config_core => $config_core, config => $config, - modules_events => $options{modules_events} + api_endpoints => $options{api_endpoints} ); $module->run(); exit(0); diff --git a/gorgone/gorgone/standard/api.pm b/gorgone/gorgone/standard/api.pm index fa286088553..65dff398bc9 100644 --- a/gorgone/gorgone/standard/api.pm +++ b/gorgone/gorgone/standard/api.pm @@ -42,15 +42,6 @@ sub root { $options{logger}->writeLogInfo("[api] Requesting '" . $options{uri} . "' [" . $options{method} . "]"); - my %dispatch; - foreach my $action (keys $options{modules_events}) { - next if (!defined($options{modules_events}->{$action}->{api}->{uri})); - $dispatch{$options{modules_events}->{$action}->{module}->{namespace} . '_' . - $options{modules_events}->{$action}->{module}->{name} . '_' . - $options{modules_events}->{$action}->{api}->{method} . '_' . - $options{modules_events}->{$action}->{api}->{uri}} = $action; - } - my $response; if ($options{method} eq 'GET' && $options{uri} =~ /^\/api\/(targets\/(\w*)\/)?log\/(.*)$/) { $response = get_log( @@ -68,11 +59,11 @@ sub root { refresh => (defined($options{parameters}->{refresh})) ? $options{parameters}->{refresh} : undef ); } elsif ($options{uri} =~ /^\/api\/(targets\/(\w*)\/)?(\w+)\/(\w+)\/(\w+)\/?([\w\/]*?)$/ - && defined($dispatch{$3 . '_' . $4 . '_' . $options{method} . '_/' . $5})) { + && defined($options{api_endpoints}->{$options{method} . '_/' . $3 . '/' . $4 . '/' . $5})) { my @variables = split(/\//, $6); $response = call_action( socket => $options{socket}, - action => $dispatch{$3 . '_' . $4 . '_' . $options{method} . '_/' . $5}, + action => $options{api_endpoints}->{$options{method} . '_/' . $3 . '/' . $4 . '/' . $5}, target => $2, data => { content => $options{content}, diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index d85aa872fb5..8998f729c96 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -363,10 +363,11 @@ sub getthumbprint { sub information { my (%options) = @_; - + my $data = { counters => $options{gorgone}->{counters}, modules => $options{gorgone}->{modules_id}, + api_endpoints => $options{gorgone}->{api_endpoints} }; return (0, { action => 'information', message => 'ok', data => $data }, 'INFORMATION'); } @@ -415,7 +416,7 @@ sub loadmodule { external_socket => $options{gorgone}->{external_socket}, internal_socket => $options{gorgone}->{internal_socket}, dbh => $options{gorgone}->{db_gorgone}, - modules_events => $options{gorgone}->{modules_events}, + api_endpoints => $options{gorgone}->{api_endpoints}, ); return (0, { action => 'loadmodule', message => "module '$data->{content}->{name}' is loaded" }, 'LOADMODULE'); } From 3b2e06fba9f6a9f5a260df609ebc59ff1b6dca73 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Thu, 26 Dec 2019 17:11:07 +0100 Subject: [PATCH 278/948] enh(core): use base64 instead of hex --- gorgone/TODO | 4 ++++ gorgone/gorgone/standard/library.pm | 18 +++++++++--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/gorgone/TODO b/gorgone/TODO index c34df1f9d4a..71c822ce8bc 100644 --- a/gorgone/TODO +++ b/gorgone/TODO @@ -2,3 +2,7 @@ - Add a broadcast action to update module (like logger in debug for example) - Add redis backend to store logs (we could disable synclog in redis mode) - Add a websocket module to call gorgone + +- gorgone_port: 22 ou 5665 +- gorgone_communication_type: 1 = 'zmq', 2 = 'ssh' +- remote_server_use_proxy diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index d85aa872fb5..c10ee107d31 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -33,6 +33,7 @@ use Data::Dumper; use YAML 'LoadFile'; use File::Path; use File::Basename; +use MIME::Base64; my %zmq_type = ('ZMQ_ROUTER' => ZMQ_ROUTER, 'ZMQ_DEALER' => ZMQ_DEALER); @@ -151,7 +152,7 @@ sub zmq_core_pubkey_response { zmq_sendmsg($options{socket}, pack('H*', $options{identity}), ZMQ_NOBLOCK | ZMQ_SNDMORE); } my $client_pubkey = $options{pubkey}->export_key_pem('public'); - my $msg = '[PUBKEY] [' . unpack('H*', $client_pubkey) . ']'; + my $msg = '[PUBKEY] [' . MIME::Base64::encode_base64($client_pubkey) . ']'; zmq_sendmsg($options{socket}, $msg, ZMQ_NOBLOCK); return 0; @@ -172,8 +173,7 @@ sub zmq_core_key_response { return -1; } - - zmq_sendmsg($options{socket}, unpack('H*', $crypttext), ZMQ_NOBLOCK); + zmq_sendmsg($options{socket}, MIME::Base64::encode_base64($crypttext), ZMQ_NOBLOCK); return 0; } @@ -200,7 +200,7 @@ sub zmq_core_response { -literal_key => 1 ); $msg = $cipher->encrypt($msg); - $msg = unpack('H*', $msg); + $msg = MIME::Base64::encode_base64($msg); } zmq_sendmsg($options{socket}, $msg, ZMQ_NOBLOCK); } @@ -218,7 +218,7 @@ sub uncrypt_message { -literal_key => 1 ); eval { - my $cryptedmessage = pack('H*', $options{message}); + my $cryptedmessage = MIME::Base64::decode_base64($options{message}); $plaintext = $cipher->decrypt($cryptedmessage); }; if ($@) { @@ -250,7 +250,7 @@ sub client_get_secret { my $plaintext; eval { - my $cryptedtext = pack('H*', $options{message}); + my $cryptedtext = MIME::Base64::decode_base64($options{message}); $plaintext = $options{privkey}->decrypt($cryptedtext, 'v1.5'); }; if ($@) { @@ -280,7 +280,7 @@ sub client_helo_encrypt { return (-1, "Encoding issue: $@"); } - return (0, '[' . $options{identity} . '] [' . $client_pubkey . '] [' . unpack('H*', $ciphertext) . ']'); + return (0, '[' . $options{identity} . '] [' . $client_pubkey . '] [' . MIME::Base64::encode_base64($ciphertext) . ']'); } sub is_client_can_connect { @@ -294,7 +294,7 @@ sub is_client_can_connect { my ($client, $client_pubkey_str, $cipher_text) = ($1, $2, $3); eval { - $plaintext = $options{privkey}->decrypt(pack('H*', $cipher_text), 'v1.5'); + $plaintext = $options{privkey}->decrypt(MIME::Base64::decode_base64($cipher_text), 'v1.5'); }; if ($@) { $options{logger}->writeLogError("Decoding issue: " . $@); @@ -747,7 +747,7 @@ sub zmq_send_message { -literal_key => 1 ); $message = $cipher->encrypt($message); - $message = unpack('H*', $message); + $message = MIME::Base64::encode_base64($message); } zmq_sendmsg($options{socket}, $message, ZMQ_NOBLOCK); } From 66acb34a6c378e3f2d518a5ac4115d89299a1cca Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Thu, 26 Dec 2019 17:22:07 +0100 Subject: [PATCH 279/948] enh(core): use base64 encoding --- gorgone/docs/guide.md | 6 +++--- gorgone/gorgone/class/clientzmq.pm | 3 ++- gorgone/gorgone/standard/library.pm | 10 +++++----- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/gorgone/docs/guide.md b/gorgone/docs/guide.md index 42407b98a0f..e559eb37e03 100644 --- a/gorgone/docs/guide.md +++ b/gorgone/docs/guide.md @@ -19,10 +19,10 @@ Third-party clients have to use the ZeroMQ library and the following process: 3. Server -> Client: send back the pubkey ```text - [PUBKEY] [hex encoding pubkey] + [PUBKEY] [base64 encoding pubkey] ``` -4. Client -> Server: send the following message with HELO crypted with the public key of the server (and hex encoding) and provides client pubkey: +4. Client -> Server: send the following message with HELO crypted with the public key of the server (and base64 encoding) and provides client pubkey: ```text [HOSTNAME] [CLIENTPUBKEY] [HELO] @@ -43,7 +43,7 @@ Third-party clients have to use the ZeroMQ library and the following process: ``` 4. Client: uncrypts the server message with its private key. -5. Client and Server uses the symmetric key+hex encoding to dialog. +5. Client and Server uses the symmetric key+base64 encoding to dialog. The server keeps sessions for 24 hours since the last message of the client. diff --git a/gorgone/gorgone/class/clientzmq.pm b/gorgone/gorgone/class/clientzmq.pm index 85387f3c827..f5e50a635a7 100644 --- a/gorgone/gorgone/class/clientzmq.pm +++ b/gorgone/gorgone/class/clientzmq.pm @@ -26,6 +26,7 @@ use gorgone::standard::library; use gorgone::standard::misc; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); +use MIME::Base64; my $connectors = {}; my $callbacks = {}; @@ -126,7 +127,7 @@ sub check_server_pubkey { } my ($code, $verbose_message); - my $server_pubkey_str = pack('H*', $1); + my $server_pubkey_str = MIME::Base64::decode_base64($1); ($code, $self->{server_pubkey}) = gorgone::standard::library::loadpubkey( pubkey_str => $server_pubkey_str, logger => $self->{logger}, diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index 6ada7180d27..93e3c33b54a 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -152,7 +152,7 @@ sub zmq_core_pubkey_response { zmq_sendmsg($options{socket}, pack('H*', $options{identity}), ZMQ_NOBLOCK | ZMQ_SNDMORE); } my $client_pubkey = $options{pubkey}->export_key_pem('public'); - my $msg = '[PUBKEY] [' . MIME::Base64::encode_base64($client_pubkey) . ']'; + my $msg = '[PUBKEY] [' . MIME::Base64::encode_base64($client_pubkey, '') . ']'; zmq_sendmsg($options{socket}, $msg, ZMQ_NOBLOCK); return 0; @@ -173,7 +173,7 @@ sub zmq_core_key_response { return -1; } - zmq_sendmsg($options{socket}, MIME::Base64::encode_base64($crypttext), ZMQ_NOBLOCK); + zmq_sendmsg($options{socket}, MIME::Base64::encode_base64($crypttext, ''), ZMQ_NOBLOCK); return 0; } @@ -200,7 +200,7 @@ sub zmq_core_response { -literal_key => 1 ); $msg = $cipher->encrypt($msg); - $msg = MIME::Base64::encode_base64($msg); + $msg = MIME::Base64::encode_base64($msg, ''); } zmq_sendmsg($options{socket}, $msg, ZMQ_NOBLOCK); } @@ -280,7 +280,7 @@ sub client_helo_encrypt { return (-1, "Encoding issue: $@"); } - return (0, '[' . $options{identity} . '] [' . $client_pubkey . '] [' . MIME::Base64::encode_base64($ciphertext) . ']'); + return (0, '[' . $options{identity} . '] [' . $client_pubkey . '] [' . MIME::Base64::encode_base64($ciphertext, '') . ']'); } sub is_client_can_connect { @@ -748,7 +748,7 @@ sub zmq_send_message { -literal_key => 1 ); $message = $cipher->encrypt($message); - $message = MIME::Base64::encode_base64($message); + $message = MIME::Base64::encode_base64($message, ''); } zmq_sendmsg($options{socket}, $message, ZMQ_NOBLOCK); } From 344160566b26c7874edb2e63054140c14cbe211f Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Thu, 26 Dec 2019 19:19:18 +0100 Subject: [PATCH 280/948] enh(doc): update httpserver doc --- gorgone/docs/modules/core/httpserver.md | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/gorgone/docs/modules/core/httpserver.md b/gorgone/docs/modules/core/httpserver.md index bcbcb8ee9c1..bb36041362b 100644 --- a/gorgone/docs/modules/core/httpserver.md +++ b/gorgone/docs/modules/core/httpserver.md @@ -54,24 +54,3 @@ dispatch: | Event | Description | | :- | :- | | HTTPSERVERREADY | Internal event to notify the core | - -## API - -### Get HTTP server status - -| Endpoint | Method | -| :- | :- | -| /status | `GET` | - -#### Headers - -| Header | Value | -| :- | :- | -| Accept | application/json | - -#### Example - -```bash -curl --request GET "https://hostname:8443/status" \ - --header "Accept: application/json" -``` From 30019b3f8bec48eac90b4dd9ade4c2930e1a55d9 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Thu, 26 Dec 2019 19:19:39 +0100 Subject: [PATCH 281/948] enh(api): enh internal endpoints --- gorgone/docs/api.md | 13 +++++++++++++ gorgone/gorgone/class/core.pm | 6 +++++- gorgone/gorgone/standard/api.pm | 34 ++++++++++++++------------------- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/gorgone/docs/api.md b/gorgone/docs/api.md index af3c7359aaa..1f1e42d6962 100644 --- a/gorgone/docs/api.md +++ b/gorgone/docs/api.md @@ -77,6 +77,19 @@ curl --request GET "https://hostname:8443/api/internal/information" \ "nodes": "gorgone::modules::centreon::nodes::hooks", "legacycmd": "gorgone::modules::centreon::legacycmd::hooks" }, + "api_endpoints": { + "GET_/centreon/broker/statistics": "BROKERSTATS", + "GET_/internal/thumbprint": "GETTHUMBPRINT", + "GET_/core/cron/definitions": "GETCRON", + "GET_/internal/information": "INFORMATION", + "POST_/core/cron/definitions": "ADDCRON", + "POST_/core/action/command": "COMMAND", + "POST_/centreon/engine/command": "ENGINECOMMAND", + "POST_/core/proxy/remotecopy": "REMOTECOPY", + "PATCH_/core/cron/definitions": "UPDATECRON", + "DELETE_/core/cron/definitions": "DELETECRON", + "GET_/internal/constatus": "CONSTATUS" + }, "counters": { "external": { "total": 0 diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 1b32d82092b..c61c3a69f0a 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -57,13 +57,17 @@ sub new { $self->{modules_register} = {}; $self->{modules_events} = {}; $self->{modules_id} = {}; - $self->{api_endpoints} = {}; $self->{purge_timer} = time(); $self->{history_timer} = time(); $self->{kill_timer} = undef; $self->{server_privkey} = undef; $self->{register_parent_nodes} = {}; $self->{counters} = { total => 0, internal => { total => 0 }, external => { total => 0 }, proxy => { total => 0 } }; + $self->{api_endpoints} = { + 'GET_/internal/thumbprint' => 'GETTHUMBPRINT', + 'GET_/internal/constatus' => 'CONSTATUS', + 'GET_/internal/information' => 'INFORMATION' + }; return $self; } diff --git a/gorgone/gorgone/standard/api.pm b/gorgone/gorgone/standard/api.pm index 65dff398bc9..5851f5c537e 100644 --- a/gorgone/gorgone/standard/api.pm +++ b/gorgone/gorgone/standard/api.pm @@ -31,12 +31,6 @@ use JSON::XS; my $socket; my $result; -my $mapping_internal_endpoint = { - thumbprint => 'GETTHUMBPRINT', - constatus => 'CONSTATUS', - information => 'INFORMATION' -}; - sub root { my (%options) = @_; @@ -51,11 +45,18 @@ sub root { refresh => (defined($options{parameters}->{refresh})) ? $options{parameters}->{refresh} : undef, parameters => $options{parameters} ); - } elsif ($options{method} eq 'GET' && $options{uri} =~ /^\/api\/(targets\/(\w*)\/)?internal\/(.*)$/) { - $response = get_internal( + } elsif ($options{uri} =~ /^\/api\/(targets\/(\w*)\/)?internal\/(.*)$/ + && defined($options{api_endpoints}->{$options{method} . '_/internal/' . $3})) { + my @variables = split(/\//, $4); + $response = call_internal( socket => $options{socket}, + action => $options{api_endpoints}->{$options{method} . '_/internal/' . $3}, target => $2, - endpoint => $3, + data => { + content => $options{content}, + parameters => $options{parameters}, + variables => \@variables, + }, refresh => (defined($options{parameters}->{refresh})) ? $options{parameters}->{refresh} : undef ); } elsif ($options{uri} =~ /^\/api\/(targets\/(\w*)\/)?(\w+)\/(\w+)\/(\w+)\/?([\w\/]*?)$/ @@ -121,7 +122,7 @@ sub call_action { return $response; } -sub get_internal { +sub call_internal { my (%options) = @_; $socket = $options{socket}; @@ -133,18 +134,11 @@ sub get_internal { } ]; - if (!defined($options{endpoint}) || !defined($mapping_internal_endpoint->{lc($options{endpoint})})) { - my $response = '{"error":"endpoint_unknown","message":"endpoint not implemented"}'; - return $response; - } - - my $action = $mapping_internal_endpoint->{lc($options{endpoint})}; - if (defined($options{target}) && $options{target} ne '') { return call_action( socket => $options{socket}, target => $options{target}, - action => $action, + action => $options{action}, data => {}, json_encode => 1, refresh => $options{refresh} @@ -153,14 +147,14 @@ sub get_internal { gorgone::standard::library::zmq_send_message( socket => $options{socket}, - action => $action, + action => $options{action}, data => {}, json_encode => 1 ); my $rev = zmq_poll($poll, 5000); - my $response = '{"error":"no_result", "message":"No result found for action "' . $action . '"}'; + my $response = '{"error":"no_result", "message":"No result found for action "' . $options{action} . '"}'; if (defined($result->{data})) { my $content; eval { From e9eeaf377884fa91e0b0f1c50df974d218e3d70f Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 27 Dec 2019 10:12:35 +0100 Subject: [PATCH 282/948] fix(doc): fix examples --- gorgone/docs/api.md | 30 +++++++++++++++++++++++++----- gorgone/docs/modules/core/cron.md | 17 ++++++++++++----- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/gorgone/docs/api.md b/gorgone/docs/api.md index 1f1e42d6962..bf92ad97c7e 100644 --- a/gorgone/docs/api.md +++ b/gorgone/docs/api.md @@ -139,9 +139,11 @@ Endpoints are basically built from: curl --request POST "https://hostname:8443/api/core/action/command" \ --header "Accept: application/json" \ --header "Content-Type: application/json" \ - --data "{ - \"command\": \"echo 'Test command'\" -}" + --data "[ + { + \"command\": \"echo 'Test command'\" + } +]" ``` Find more informations directly from modules documentations [here](../docs/modules.md). @@ -188,12 +190,21 @@ curl --request GET "https://hostname:8443/api/log/3f25bc3a797fe989d1fb052b1886a8 ```json { "data": [ + { + "ctime": 1576083003, + "etime": 1576083003, + "id": "15638", + "instant": 0, + "data": "{\"message\":\"commands processing has started\",\"request_content\":[{\"timeout\":10,\"command\":\"echo 'Test command'\"}]}", + "token": "3f25bc3a797fe989d1fb052b1886a806e73fe2d8ccfc6377ee3d4490f8ad03c02cb2533edcc1b3d8e1770e28d6f2de83bd98923b66c0c33395e5f835759de4b1", + "code": 0 + }, { "ctime": 1576083003, "etime": 1576083003, "id": "15639", "instant": 0, - "data": "{\"metadata\":null,\"message\":\"command 'echo 'Test command'' has started\"}", + "data": "{\"metadata\":null,\"message\":\"command has started\",\"command\":\"echo 'Test command'\"}", "token": "3f25bc3a797fe989d1fb052b1886a806e73fe2d8ccfc6377ee3d4490f8ad03c02cb2533edcc1b3d8e1770e28d6f2de83bd98923b66c0c33395e5f835759de4b1", "code": 0 }, @@ -202,7 +213,16 @@ curl --request GET "https://hostname:8443/api/log/3f25bc3a797fe989d1fb052b1886a8 "etime": 1576083003, "id": "15640", "instant": 0, - "data": "{\"exit_code\":0,\"metadata\":null,\"stdout\":\"Test command\",\"message\":\"command 'echo 'Test command'' has finished\",\"start\":1576083003,\"end\":1576083003}", + "data": "{\"metadata\":null,\"metrics\":{\"duration\":0,\"start\":1576083003,\"end\":1576083003},\"message\":\"command has finished\",\"command\":\"echo 'Test command'\",\"result\":{\"exit_code\":0,\"stdout\":\"Test command\"}}", + "token": "3f25bc3a797fe989d1fb052b1886a806e73fe2d8ccfc6377ee3d4490f8ad03c02cb2533edcc1b3d8e1770e28d6f2de83bd98923b66c0c33395e5f835759de4b1", + "code": 2 + }, + { + "ctime": 1576083003, + "etime": 1576083003, + "id": "15641", + "instant": 0, + "data": "{\"message\":\"commands processing has finished\"}", "token": "3f25bc3a797fe989d1fb052b1886a806e73fe2d8ccfc6377ee3d4490f8ad03c02cb2533edcc1b3d8e1770e28d6f2de83bd98923b66c0c33395e5f835759de4b1", "code": 2 } diff --git a/gorgone/docs/modules/core/cron.md b/gorgone/docs/modules/core/cron.md index c3f6fe28459..82834446e56 100644 --- a/gorgone/docs/modules/core/cron.md +++ b/gorgone/docs/modules/core/cron.md @@ -16,6 +16,7 @@ Below the configuration to add cron definitions: | timespec | Cron-like time specification | | action | Action/event to call at job execution | | parameters | Parameters needed by the called action/event | +| keep_token | Boolean to define whether or not the ID of the definition will be used as token for the command | #### Example @@ -147,10 +148,12 @@ curl --request POST "https://hostname:8443/api/core/cron/definitions" \ \"timespec\": \"*/15 * * * *\", \"id\": \"job_123\", \"action\": \"COMMAND\", - \"parameters\": { - \"command\": \"date >> /tmp/the_date_again.log\", - \"timeout\": 5 - }, + \"parameters\": [ + { + \"command\": \"date >> /tmp/the_date_again.log\", + \"timeout\": 5 + } + ], \"keep_token\": true } ]" @@ -181,7 +184,11 @@ One or several keys allowed by the add endpoint. ```json { - "parameters": "" + "id": "", + "timespec": "", + "command": "", + "parameters": "", + "keep_token": "" } ``` From cc79e18281596c2c38a0116f7168ea188fff6164 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 27 Dec 2019 10:40:18 +0100 Subject: [PATCH 283/948] fix(api): fix internal calls --- gorgone/gorgone/standard/api.pm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gorgone/gorgone/standard/api.pm b/gorgone/gorgone/standard/api.pm index 5851f5c537e..c3414a6596a 100644 --- a/gorgone/gorgone/standard/api.pm +++ b/gorgone/gorgone/standard/api.pm @@ -45,7 +45,7 @@ sub root { refresh => (defined($options{parameters}->{refresh})) ? $options{parameters}->{refresh} : undef, parameters => $options{parameters} ); - } elsif ($options{uri} =~ /^\/api\/(targets\/(\w*)\/)?internal\/(.*)$/ + } elsif ($options{uri} =~ /^\/api\/(targets\/(\w*)\/)?internal\/(\w+)\/?([\w\/]*?)$/ && defined($options{api_endpoints}->{$options{method} . '_/internal/' . $3})) { my @variables = split(/\//, $4); $response = call_internal( @@ -139,7 +139,7 @@ sub call_internal { socket => $options{socket}, target => $options{target}, action => $options{action}, - data => {}, + data => $options{data}, json_encode => 1, refresh => $options{refresh} ); @@ -148,7 +148,7 @@ sub call_internal { gorgone::standard::library::zmq_send_message( socket => $options{socket}, action => $options{action}, - data => {}, + data => $options{data}, json_encode => 1 ); @@ -164,7 +164,7 @@ sub call_internal { $response = '{"error":"decode_error","message":"Cannot decode response"}'; } else { eval { - $response = JSON::XS->new->utf8->encode($content->{data}); + $response = JSON::XS->new->utf8->encode($content); }; if ($@) { $response = '{"error":"encode_error","message":"Cannot encode response"}'; From f0956f84a0e4bd776c2816ac13ab52b922e62ef1 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Fri, 27 Dec 2019 10:48:08 +0100 Subject: [PATCH 284/948] enh(core): add bcastlogger system --- gorgone/gorgone/class/core.pm | 41 +++++++++++++++++-- gorgone/gorgone/class/module.pm | 12 ++++++ .../modules/centreon/autodiscovery/hooks.pm | 6 +++ .../gorgone/modules/centreon/broker/hooks.pm | 6 +++ .../gorgone/modules/centreon/engine/class.pm | 11 ++++- .../gorgone/modules/centreon/engine/hooks.pm | 6 +++ .../modules/centreon/legacycmd/hooks.pm | 6 +++ .../gorgone/modules/centreon/nodes/hooks.pm | 6 +++ gorgone/gorgone/modules/core/action/class.pm | 13 ++++-- gorgone/gorgone/modules/core/action/hooks.pm | 6 +++ gorgone/gorgone/modules/core/cron/hooks.pm | 6 +++ .../gorgone/modules/core/dbcleaner/hooks.pm | 6 +++ .../gorgone/modules/core/httpserver/hooks.pm | 6 +++ gorgone/gorgone/modules/core/proxy/class.pm | 7 +++- gorgone/gorgone/modules/core/proxy/hooks.pm | 16 ++++++++ gorgone/gorgone/modules/core/pull/hooks.pm | 2 + .../gorgone/modules/core/register/hooks.pm | 6 +++ .../gorgone/modules/plugins/newtest/hooks.pm | 14 ++++++- gorgone/gorgone/modules/plugins/scom/hooks.pm | 14 ++++++- gorgone/gorgone/standard/library.pm | 17 ++++++++ 20 files changed, 195 insertions(+), 12 deletions(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index c61c3a69f0a..efa799a16e6 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -66,7 +66,8 @@ sub new { $self->{api_endpoints} = { 'GET_/internal/thumbprint' => 'GETTHUMBPRINT', 'GET_/internal/constatus' => 'CONSTATUS', - 'GET_/internal/information' => 'INFORMATION' + 'GET_/internal/information' => 'INFORMATION', + 'POST_/internal/logger' => 'BCASTLOGGER', }; return $self; @@ -306,7 +307,7 @@ sub load_module { } $self->{modules_register}->{$package} = {}; - foreach my $method_name (('register', 'routing', 'kill', 'kill_internal', 'gently', 'check', 'init')) { + foreach my $method_name (('register', 'routing', 'kill', 'kill_internal', 'gently', 'check', 'init', 'broadcast')) { unless ($self->{modules_register}->{$package}->{$method_name} = $package->can($method_name)) { $self->{logger}->writeLogError("[core] No function '$method_name' for module '" . $options{config_module}->{name} . "'"); return 0; @@ -363,6 +364,33 @@ sub load_modules { } } +sub broadcast_run { + my ($self, %options) = @_; + + if ($options{action} eq 'BCASTLOGGER') { + my $data = gorgone::standard::library::json_decode(data => $options{data}, logger => $self->{logger}); + return if (!defined($data)); + + if (defined($data->{content}->{severity}) && $data->{content}->{severity} ne '') { + if ($data->{content}->{severity} eq 'default') { + $self->{logger}->set_default_severity(); + } else { + $self->{logger}->severity($data->{content}->{severity}); + } + } + } + + foreach (keys %{$self->{modules_register}}) { + $self->{modules_register}->{$_}->{broadcast}->( + socket => $self->{internal_socket}, + action => $options{action}, + logger => $self->{logger}, + data => $options{data}, + token => $options{token} + ); + } +} + sub message_run { my ($self, %options) = @_; @@ -381,7 +409,7 @@ sub message_run { $token = gorgone::standard::library::generate_token(); } - if ($action !~ /^(?:PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID|SYNCLOGS|LOADMODULE|UNLOADMODULE|INFORMATION|GETTHUMBPRINT)$/ && + if ($action !~ /^(?:PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID|SYNCLOGS|LOADMODULE|UNLOADMODULE|INFORMATION|GETTHUMBPRINT|BCAST.*)$/ && !defined($target) && !defined($self->{modules_events}->{$action})) { gorgone::standard::library::add_history( dbh => $self->{db_gorgone}, @@ -452,6 +480,13 @@ sub message_run { logger => $self->{logger} ); return ($token, $code, $response, $response_type); + } elsif ($action =~ /^BCAST(.*)$/) { + return (undef, 1, { message => "action '$action' is not known" }) if ($1 !~ /^LOGGER$/); + $self->broadcast_run( + action => $action, + data => $data, + token => $token + ); } else { $self->{modules_register}->{$self->{modules_events}->{$action}->{module}->{package}}->{routing}->( socket => $self->{internal_socket}, diff --git a/gorgone/gorgone/class/module.pm b/gorgone/gorgone/class/module.pm index 1d7a958a110..efdca94a76e 100644 --- a/gorgone/gorgone/class/module.pm +++ b/gorgone/gorgone/class/module.pm @@ -128,4 +128,16 @@ sub change_macros { return $options{template}; } +sub action_bcastlogger { + my ($self, %options) = @_; + + if (defined($options{data}->{content}->{severity}) && $options{data}->{content}->{severity} ne '') { + if ($options{data}->{content}->{severity} eq 'default') { + $self->{logger}->set_default_severity(); + } else { + $self->{logger}->severity($options{data}->{content}->{severity}); + } + } +} + 1; diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm b/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm index ca293bffca9..3dee41991bc 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm @@ -146,6 +146,12 @@ sub check { return $count; } +sub broadcast { + my (%options) = @_; + + routing(%options); +} + # Specific functions sub create_child { my (%options) = @_; diff --git a/gorgone/gorgone/modules/centreon/broker/hooks.pm b/gorgone/gorgone/modules/centreon/broker/hooks.pm index 815bd10f09d..ec6d14ca01c 100644 --- a/gorgone/gorgone/modules/centreon/broker/hooks.pm +++ b/gorgone/gorgone/modules/centreon/broker/hooks.pm @@ -141,6 +141,12 @@ sub check { return $count; } +sub broadcast { + my (%options) = @_; + + routing(%options); +} + # Specific functions sub create_child { my (%options) = @_; diff --git a/gorgone/gorgone/modules/centreon/engine/class.pm b/gorgone/gorgone/modules/centreon/engine/class.pm index aca37d0eaf9..343cab7d083 100644 --- a/gorgone/gorgone/modules/centreon/engine/class.pm +++ b/gorgone/gorgone/modules/centreon/engine/class.pm @@ -263,12 +263,19 @@ sub action_run { sub create_child { my ($self, %options) = @_; - $self->{logger}->writeLogInfo('[engine] -class- create sub-process'); $options{message} =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; my ($action, $token) = ($1, $2); my $data = JSON::XS->new->utf8->decode($3); + if ($action =~ /^BCAST.*/) { + if ((my $method = $self->can('action_' . lc($action)))) { + $method->($self, token => $token, data => $data); + } + return undef; + } + + $self->{logger}->writeLogInfo('[engine] -class- create sub-process'); my $child_pid = fork(); if (!defined($child_pid)) { $self->send_log( @@ -290,7 +297,7 @@ sub event { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); $connector->{logger}->writeLogDebug("[engine] -class- Event: $message"); - + if ($message !~ /^\[ACK\]/) { $connector->create_child(message => $message); } diff --git a/gorgone/gorgone/modules/centreon/engine/hooks.pm b/gorgone/gorgone/modules/centreon/engine/hooks.pm index 958b081532e..58631d6af8f 100644 --- a/gorgone/gorgone/modules/centreon/engine/hooks.pm +++ b/gorgone/gorgone/modules/centreon/engine/hooks.pm @@ -139,6 +139,12 @@ sub check { return $count; } +sub broadcast { + my (%options) = @_; + + routing(%options); +} + # Specific functions sub create_child { my (%options) = @_; diff --git a/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm b/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm index 416d872afa6..b63516ef7d2 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm @@ -143,6 +143,12 @@ sub check { return $count; } +sub broadcast { + my (%options) = @_; + + routing(%options); +} + # Specific functions sub create_child { my (%options) = @_; diff --git a/gorgone/gorgone/modules/centreon/nodes/hooks.pm b/gorgone/gorgone/modules/centreon/nodes/hooks.pm index 7eeb22d5e11..f6f8343a9b2 100644 --- a/gorgone/gorgone/modules/centreon/nodes/hooks.pm +++ b/gorgone/gorgone/modules/centreon/nodes/hooks.pm @@ -140,6 +140,12 @@ sub check { return $count; } +sub broadcast { + my (%options) = @_; + + routing(%options); +} + # Specific functions sub create_child { my (%options) = @_; diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index 07a8354f18e..d59607e35fa 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -326,13 +326,20 @@ sub action_run { sub create_child { my ($self, %options) = @_; - - $self->{logger}->writeLogDebug("[action] -class- Create sub-process"); + $options{message} =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; my ($action, $token) = ($1, $2); my $data = JSON::XS->new->utf8->decode($3); - + + if ($action =~ /^BCAST.*/) { + if ((my $method = $self->can('action_' . lc($action)))) { + $method->($self, token => $token, data => $data); + } + return undef; + } + + $self->{logger}->writeLogDebug("[action] -class- Create sub-process"); my $child_pid = fork(); if (!defined($child_pid)) { $self->send_log( diff --git a/gorgone/gorgone/modules/core/action/hooks.pm b/gorgone/gorgone/modules/core/action/hooks.pm index 216379ab866..70608e58c37 100644 --- a/gorgone/gorgone/modules/core/action/hooks.pm +++ b/gorgone/gorgone/modules/core/action/hooks.pm @@ -139,6 +139,12 @@ sub check { return $count; } +sub broadcast { + my (%options) = @_; + + routing(%options); +} + # Specific functions sub create_child { my (%options) = @_; diff --git a/gorgone/gorgone/modules/core/cron/hooks.pm b/gorgone/gorgone/modules/core/cron/hooks.pm index a15b01e3811..08a28e381f1 100644 --- a/gorgone/gorgone/modules/core/cron/hooks.pm +++ b/gorgone/gorgone/modules/core/cron/hooks.pm @@ -141,6 +141,12 @@ sub check { return $count; } +sub broadcast { + my (%options) = @_; + + routing(%options); +} + # Specific functions sub create_child { my (%options) = @_; diff --git a/gorgone/gorgone/modules/core/dbcleaner/hooks.pm b/gorgone/gorgone/modules/core/dbcleaner/hooks.pm index c27a7a5176e..d8d1b576058 100644 --- a/gorgone/gorgone/modules/core/dbcleaner/hooks.pm +++ b/gorgone/gorgone/modules/core/dbcleaner/hooks.pm @@ -148,6 +148,12 @@ sub check { return $count; } +sub broadcast { + my (%options) = @_; + + routing(%options); +} + # Specific functions sub create_child { my (%options) = @_; diff --git a/gorgone/gorgone/modules/core/httpserver/hooks.pm b/gorgone/gorgone/modules/core/httpserver/hooks.pm index 4f7befa7260..bfce13ac5f0 100644 --- a/gorgone/gorgone/modules/core/httpserver/hooks.pm +++ b/gorgone/gorgone/modules/core/httpserver/hooks.pm @@ -153,6 +153,12 @@ sub check { return $count; } +sub broadcast { + my (%options) = @_; + + routing(%options); +} + # Specific functions sub create_child { my (%options) = @_; diff --git a/gorgone/gorgone/modules/core/proxy/class.pm b/gorgone/gorgone/modules/core/proxy/class.pm index 6cfe88f5cf6..128d895afbe 100644 --- a/gorgone/gorgone/modules/core/proxy/class.pm +++ b/gorgone/gorgone/modules/core/proxy/class.pm @@ -253,10 +253,13 @@ sub proxy { ); if ($action eq 'PROXYADDNODE') { - action_proxyaddnode($connector, data => $data); + $connector->action_proxyaddnode(data => $data); return ; } elsif ($action eq 'PROXYDELNODE') { - action_proxydelnode($connector, data => $data); + $connector->action_proxydelnode(data => $data); + return ; + } elsif ($action eq 'BCASTLOGGER' && $target_complete eq '') { + $connector->action_bcastlogger(data => $data); return ; } diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index db8b7e5c079..9eb79fc5ed0 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -387,6 +387,22 @@ sub check { return $count; } +sub broadcast { + my (%options) = @_; + + foreach my $pool_id (keys %$pools) { + next if ($pools->{$pool_id} != 1); + + gorgone::standard::library::zmq_send_message( + socket => $options{socket}, + identity => 'gorgoneproxy-' . $pool_id, + action => $options{action}, + data => $options{data}, + token => $options{token}, + ); + } +} + # Specific functions sub pathway { my (%options) = @_; diff --git a/gorgone/gorgone/modules/core/pull/hooks.pm b/gorgone/gorgone/modules/core/pull/hooks.pm index c83477b9aad..c8ac53d34a0 100644 --- a/gorgone/gorgone/modules/core/pull/hooks.pm +++ b/gorgone/gorgone/modules/core/pull/hooks.pm @@ -134,6 +134,8 @@ sub check { return 0; } +sub broadcast {} + ####### specific sub transmit_back { diff --git a/gorgone/gorgone/modules/core/register/hooks.pm b/gorgone/gorgone/modules/core/register/hooks.pm index fb0f078266c..35cf2465fd8 100644 --- a/gorgone/gorgone/modules/core/register/hooks.pm +++ b/gorgone/gorgone/modules/core/register/hooks.pm @@ -143,6 +143,12 @@ sub check { return $count; } +sub broadcast { + my (%options) = @_; + + routing(%options); +} + # Specific functions sub create_child { my (%options) = @_; diff --git a/gorgone/gorgone/modules/plugins/newtest/hooks.pm b/gorgone/gorgone/modules/plugins/newtest/hooks.pm index 054449e37a3..42402db5188 100644 --- a/gorgone/gorgone/modules/plugins/newtest/hooks.pm +++ b/gorgone/gorgone/modules/plugins/newtest/hooks.pm @@ -137,7 +137,6 @@ sub kill_internal { sub kill { my (%options) = @_; - } sub check { @@ -166,6 +165,19 @@ sub check { return $count; } +sub broadcast { + my (%options) = @_; + + foreach my $container_id (keys %$containers) { + if ($containers->{$container_id}->{running} == 1) { + gorgone::standard::library::zmq_send_message( + socket => $options{socket}, identity => 'gorgonenewtest-' . $container_id, + action => $options{action}, data => $options{data}, token => $options{token} + ); + } + } +} + # Specific functions sub get_containers { my (%options) = @_; diff --git a/gorgone/gorgone/modules/plugins/scom/hooks.pm b/gorgone/gorgone/modules/plugins/scom/hooks.pm index 50206e139a1..791a0d00f0d 100644 --- a/gorgone/gorgone/modules/plugins/scom/hooks.pm +++ b/gorgone/gorgone/modules/plugins/scom/hooks.pm @@ -136,7 +136,6 @@ sub kill_internal { sub kill { my (%options) = @_; - } sub check { @@ -165,6 +164,19 @@ sub check { return $count; } +sub broadcast { + my (%options) = @_; + + foreach my $container_id (keys %$containers) { + if ($containers->{$container_id}->{running} == 1) { + gorgone::standard::library::zmq_send_message( + socket => $options{socket}, identity => 'gorgonescom-' . $container_id, + action => $options{action}, data => $options{data}, token => $options{token} + ); + } + } +} + # Specific functions sub get_containers { my (%options) = @_; diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index 93e3c33b54a..01725a95f0b 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -648,6 +648,23 @@ sub json_encode { return $data; } +sub json_decode { + my (%options) = @_; + + my $data; + eval { + $data = JSON::XS->new->utf8->decode($options{data}); + }; + if ($@) { + if (defined($options{logger})) { + $options{logger}->writeLogError("Cannot decode json data: $@"); + } + return undef; + } + + return $data; +} + ####################### # Global ZMQ functions ####################### From c52d2782773891d90ab0f84482241882364d1f1c Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Fri, 27 Dec 2019 11:29:29 +0100 Subject: [PATCH 285/948] enh(httpserver): close connections --- .../gorgone/modules/core/httpserver/class.pm | 28 +++++++++++++++++-- .../gorgone/modules/core/httpserver/hooks.pm | 6 +--- gorgone/gorgone/modules/core/proxy/class.pm | 1 + gorgone/gorgone/modules/core/proxy/hooks.pm | 2 +- 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/gorgone/gorgone/modules/core/httpserver/class.pm b/gorgone/gorgone/modules/core/httpserver/class.pm index 87afee417bc..7eac66e4336 100644 --- a/gorgone/gorgone/modules/core/httpserver/class.pm +++ b/gorgone/gorgone/modules/core/httpserver/class.pm @@ -113,6 +113,15 @@ sub event { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); $connector->{logger}->writeLogDebug("[httpserver] -class- Event: $message"); + + if ($message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/) { + if ((my $method = $connector->can('action_' . lc($1)))) { + $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; + my ($action, $token) = ($1, $2); + my $data = JSON::XS->new->utf8->decode($3); + $method->($connector, token => $token, data => $data); + } + } last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); } @@ -190,7 +199,8 @@ sub run { if ($self->{config}->{ssl} eq 'false') { $daemon = HTTP::Daemon->new( LocalAddr => $self->{config}->{address} . ':' . $self->{config}->{port}, - ReusePort => 1 + ReusePort => 1, + Timeout => 5 ); } elsif ($self->{config}->{ssl} eq 'true') { $daemon = HTTP::Daemon::SSL->new( @@ -198,12 +208,24 @@ sub run { SSL_cert_file => $self->{config}->{ssl_cert_file}, SSL_key_file => $self->{config}->{ssl_key_file}, SSL_error_trap => \&ssl_error, - ReusePort => 1 + ReusePort => 1, + Timeout => 5 ); } if (defined($daemon)) { - while (my ($connection) = $daemon->accept()) { + while (1) { + my ($connection) = $daemon->accept(); + + if ($self->{stop} == 1) { + $self->{logger}->writeLogInfo("[httpserver] -class- $$ has quit"); + $connection->close() if (defined($connection)); + zmq_close($connector->{internal_socket}); + exit(0); + } + + next if (!defined($connection)); + while (my $request = $connection->get_request) { $connector->{logger}->writeLogInfo("[httpserver] -class- " . $connection->peerhost() . " " . $request->method . " '" . $request->uri->path . "' '" . $request->header("User-Agent") . "'"); diff --git a/gorgone/gorgone/modules/core/httpserver/hooks.pm b/gorgone/gorgone/modules/core/httpserver/hooks.pm index bfce13ac5f0..01ef4461aa2 100644 --- a/gorgone/gorgone/modules/core/httpserver/hooks.pm +++ b/gorgone/gorgone/modules/core/httpserver/hooks.pm @@ -153,11 +153,7 @@ sub check { return $count; } -sub broadcast { - my (%options) = @_; - - routing(%options); -} +sub broadcast {} # Specific functions sub create_child { diff --git a/gorgone/gorgone/modules/core/proxy/class.pm b/gorgone/gorgone/modules/core/proxy/class.pm index 128d895afbe..78ec8bef610 100644 --- a/gorgone/gorgone/modules/core/proxy/class.pm +++ b/gorgone/gorgone/modules/core/proxy/class.pm @@ -259,6 +259,7 @@ sub proxy { $connector->action_proxydelnode(data => $data); return ; } elsif ($action eq 'BCASTLOGGER' && $target_complete eq '') { + (undef, $data) = $connector->json_decode(argument => $data); $connector->action_bcastlogger(data => $data); return ; } diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index 9eb79fc5ed0..86b541bc495 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -391,7 +391,7 @@ sub broadcast { my (%options) = @_; foreach my $pool_id (keys %$pools) { - next if ($pools->{$pool_id} != 1); + next if ($pools->{$pool_id}->{ready} != 1); gorgone::standard::library::zmq_send_message( socket => $options{socket}, From 91f76ba12da35733bab1ea7754e9ae34b47ea0fb Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 27 Dec 2019 11:55:26 +0100 Subject: [PATCH 286/948] enh(httpserver): handle empty response --- gorgone/gorgone/modules/core/httpserver/class.pm | 13 +++++++++---- gorgone/gorgone/standard/api.pm | 14 +++++++++----- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/gorgone/gorgone/modules/core/httpserver/class.pm b/gorgone/gorgone/modules/core/httpserver/class.pm index 7eac66e4336..860aa6bf39b 100644 --- a/gorgone/gorgone/modules/core/httpserver/class.pm +++ b/gorgone/gorgone/modules/core/httpserver/class.pm @@ -299,10 +299,15 @@ sub authentication { sub send_response { my ($self, %options) = @_; - my $response = HTTP::Response->new(200); - $response->header('Content-Type' => 'application/json'); - $response->content($options{response} . "\n"); - $options{connection}->send_response($response); + if (defined($options{response}) && $options{response} ne '') { + my $response = HTTP::Response->new(200); + $response->header('Content-Type' => 'application/json'); + $response->content($options{response} . "\n"); + $options{connection}->send_response($response); + } else { + my $response = HTTP::Response->new(203); + $options{connection}->send_response($response); + } } sub send_error { diff --git a/gorgone/gorgone/standard/api.pm b/gorgone/gorgone/standard/api.pm index c3414a6596a..76f3f8e7934 100644 --- a/gorgone/gorgone/standard/api.pm +++ b/gorgone/gorgone/standard/api.pm @@ -163,11 +163,15 @@ sub call_internal { if ($@) { $response = '{"error":"decode_error","message":"Cannot decode response"}'; } else { - eval { - $response = JSON::XS->new->utf8->encode($content); - }; - if ($@) { - $response = '{"error":"encode_error","message":"Cannot encode response"}'; + if (defined($content->{data})) { + eval { + $response = JSON::XS->new->utf8->encode($content->{data}); + }; + if ($@) { + $response = '{"error":"encode_error","message":"Cannot encode response"}'; + } + } else { + $response = ''; } } } From b531072af79e8f037af52140c34f07e4cb28701d Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 27 Dec 2019 11:56:41 +0100 Subject: [PATCH 287/948] fix(httpserver): fix return code --- gorgone/gorgone/modules/core/httpserver/class.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/gorgone/modules/core/httpserver/class.pm b/gorgone/gorgone/modules/core/httpserver/class.pm index 860aa6bf39b..577f2d2d79e 100644 --- a/gorgone/gorgone/modules/core/httpserver/class.pm +++ b/gorgone/gorgone/modules/core/httpserver/class.pm @@ -305,7 +305,7 @@ sub send_response { $response->content($options{response} . "\n"); $options{connection}->send_response($response); } else { - my $response = HTTP::Response->new(203); + my $response = HTTP::Response->new(204); $options{connection}->send_response($response); } } From 0a017c6e75b26c4e2f5e505e8f29324839f25082 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 27 Dec 2019 14:54:24 +0100 Subject: [PATCH 288/948] enh(core): enhance logging --- gorgone/gorgone/standard/library.pm | 47 +++++++++++++++-------------- gorgone/gorgone/standard/misc.pm | 20 ++++++------ 2 files changed, 34 insertions(+), 33 deletions(-) diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index 01725a95f0b..d8145767769 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -45,7 +45,7 @@ sub read_config { $config = LoadFile($options{config_file}); }; if ($@) { - $options{logger}->writeLogError("Parsing config file error:"); + $options{logger}->writeLogError("[core] Parsing config file error:"); $options{logger}->writeLogError($@); exit(1); } @@ -68,7 +68,7 @@ sub generate_keys { $privkey = $pkrsa->export_key_pem('private'); }; if ($@) { - $options{logger}->writeLogError("Cannot generate server keys: $@"); + $options{logger}->writeLogError("[core] Cannot generate server keys: $@"); return 0; } @@ -82,7 +82,7 @@ sub loadpubkey { if (defined($options{pubkey})) { if (!open FILE, "<" . $options{pubkey}) { - $options{logger}->writeLogError("Cannot read file '$options{pubkey}': $!") if (defined($options{logger})); + $options{logger}->writeLogError("[core] Cannot read file '$options{pubkey}': $!") if (defined($options{logger})); exit(1) if ($quit); return 0; } @@ -99,12 +99,12 @@ sub loadpubkey { $pubkey = Crypt::PK::RSA->new(\$string_key); }; if ($@) { - $options{logger}->writeLogError("Cannot load pubkey '$options{pubkey}': $@") if (defined($options{logger})); + $options{logger}->writeLogError("[core] Cannot load pubkey '$options{pubkey}': $@") if (defined($options{logger})); exit(1) if ($quit); return 0; } if ($pubkey->is_private()) { - $options{logger}->writeLogError("'$options{pubkey}' is not a publickey") if (defined($options{logger})); + $options{logger}->writeLogError("[core] '$options{pubkey}' is not a public key") if (defined($options{logger})); exit(1) if ($quit); return 0; } @@ -118,7 +118,7 @@ sub loadprivkey { my $quit = defined($options{noquit}) ? 0 : 1; if (!open FILE, "<" . $options{privkey}) { - $options{logger}->writeLogError("Cannot read file '$options{privkey}': $!"); + $options{logger}->writeLogError("[core] Cannot read file '$options{privkey}': $!"); exit(1) if ($quit); return 0; } @@ -132,12 +132,12 @@ sub loadprivkey { $privkey = Crypt::PK::RSA->new(\$string_key); }; if ($@) { - $options{logger}->writeLogError("Cannot load privkey '$options{privkey}': $@"); + $options{logger}->writeLogError("[core] Cannot load privkey '$options{privkey}': $@"); exit(1) if ($quit); return 0; } if (!$privkey->is_private()) { - $options{logger}->writeLogError("'$options{privkey}' is not a privkey"); + $options{logger}->writeLogError("[core] '$options{privkey}' is not a private key"); exit(1) if ($quit); return 0; } @@ -169,7 +169,7 @@ sub zmq_core_key_response { $crypttext = $options{client_pubkey}->encrypt("[KEY] [$options{hostname}] [" . $options{symkey} . "]", 'v1.5'); }; if ($@) { - $options{logger}->writeLogError("Encoding issue: " . $@); + $options{logger}->writeLogError("[core] Encoding issue: " . $@); return -1; } @@ -223,7 +223,7 @@ sub uncrypt_message { }; if ($@) { if (defined($options{logger})) { - $options{logger}->writeLogError("Sym encrypt issue: " . $@); + $options{logger}->writeLogError("[core] Sym encrypt issue: " . $@); } return (-1, $@); } @@ -288,7 +288,7 @@ sub is_client_can_connect { my $plaintext; if ($options{message} !~ /\[(.+)\]\s+\[(.+)\]\s+\[(.+)\]$/ms) { - $options{logger}->writeLogError("Decoding issue. Protocol not good"); + $options{logger}->writeLogError("[core] Decoding issue. Protocol not good"); return -1; } @@ -297,11 +297,11 @@ sub is_client_can_connect { $plaintext = $options{privkey}->decrypt(MIME::Base64::decode_base64($cipher_text), 'v1.5'); }; if ($@) { - $options{logger}->writeLogError("Decoding issue: " . $@); + $options{logger}->writeLogError("[core] Decoding issue: " . $@); return -1; } if ($plaintext ne 'HELO') { - $options{logger}->writeLogError("Encrypted issue for HELO"); + $options{logger}->writeLogError("[core] Encrypted issue for HELO"); return -1; } @@ -311,7 +311,7 @@ sub is_client_can_connect { $client_pubkey = Crypt::PK::RSA->new(\$client_pubkey_str); }; if ($@) { - $options{logger}->writeLogError("Cannot load client pubkey '$client_pubkey': $@"); + $options{logger}->writeLogError("[core] Cannot load client pubkey '$client_pubkey': $@"); return -1; } @@ -327,11 +327,11 @@ sub is_client_can_connect { } if ($is_authorized == 0) { - $options{logger}->writeLogError("client pubkey is not authorized. thumprint is '$thumbprint"); + $options{logger}->writeLogError("[core] client pubkey is not authorized. thumprint is '$thumbprint"); return -1; } - $options{logger}->writeLogInfo("Connection from $client"); + $options{logger}->writeLogInfo("[core] Connection from $client"); return (0, $client_pubkey); } @@ -488,7 +488,7 @@ sub setcoreid { } } - $options{logger}->writeLogInfo('[core] setcoreid changed ' . $data->{id}); + $options{logger}->writeLogInfo('[core] Setcoreid changed ' . $data->{id}); $options{gorgone}->{id} = $data->{id}; return (0, { action => 'setcoreid', message => 'setcoreid changed' }); } @@ -640,7 +640,7 @@ sub json_encode { }; if ($@) { if (defined($options{logger})) { - $options{logger}->writeLogError("Cannot encode json data: $@"); + $options{logger}->writeLogError("[core] Cannot encode json data: $@"); } return undef; } @@ -657,7 +657,7 @@ sub json_decode { }; if ($@) { if (defined($options{logger})) { - $options{logger}->writeLogError("Cannot decode json data: $@"); + $options{logger}->writeLogError("[core] Cannot decode json data: $@"); } return undef; } @@ -705,21 +705,22 @@ sub create_com { zmq_bind($socket, 'tcp://' . $options{path}); } elsif ($options{type} eq 'ipc') { if (zmq_bind($socket, 'ipc://' . $options{path}) == -1) { - $options{logger}->writeLogError("Cannot bind ipc '$options{path}': $!"); + $options{logger}->writeLogDebug("[core] Cannot bind IPC '$options{path}': $!"); # try create dir - $options{logger}->writeLogError("Maybe directory not exist. We try to create it!!!"); + $options{logger}->writeLogDebug("[core] Maybe directory not exist. We try to create it!!!"); if (!mkdir(dirname($options{path}))) { + $options{logger}->writeLogError("[core] Cannot create IPC file directory '$options{path}'"); zmq_close($socket); exit(1); } if (zmq_bind($socket, 'ipc://' . $options{path}) == -1) { - $options{logger}->writeLogError("Cannot bind ipc '$options{path}': $!"); + $options{logger}->writeLogError("[core] Cannot bind IPC '$options{path}': $!"); zmq_close($socket); exit(1); } } } else { - $options{logger}->writeLogError("zmq type '$options{type}' not managed"); + $options{logger}->writeLogError("[core] ZMQ type '$options{type}' not managed"); zmq_close($socket); exit(1); } diff --git a/gorgone/gorgone/standard/misc.pm b/gorgone/gorgone/standard/misc.pm index add880c2fde..6137eb7754a 100644 --- a/gorgone/gorgone/standard/misc.pm +++ b/gorgone/gorgone/standard/misc.pm @@ -32,9 +32,9 @@ sub reload_db_config { my ($cdb_mod, $csdb_mod) = (0, 0); unless (my $return = do $config_file) { - $logger->writeLogError("couldn't parse $config_file: $@") if $@; - $logger->writeLogError("couldn't do $config_file: $!") unless defined $return; - $logger->writeLogError("couldn't run $config_file") unless $return; + $logger->writeLogError("[core] Couldn't parse $config_file: $@") if $@; + $logger->writeLogError("[core] Couldn't do $config_file: $!") unless defined $return; + $logger->writeLogError("[core] Couldn't run $config_file") unless $return; return -1; } @@ -44,7 +44,7 @@ sub reload_db_config { $centreon_config->{db_user} ne $cdb->user() || $centreon_config->{db_passwd} ne $cdb->password() || $centreon_config->{db_port} ne $cdb->port()) { - $logger->writeLogInfo("Database centreon config had been modified"); + $logger->writeLogInfo("[core] Database centreon config has been modified"); $cdb->db($centreon_config->{centreon_db}); $cdb->host($centreon_config->{db_host}); $cdb->user($centreon_config->{db_user}); @@ -60,7 +60,7 @@ sub reload_db_config { $centreon_config->{db_user} ne $csdb->user() || $centreon_config->{db_passwd} ne $csdb->password() || $centreon_config->{db_port} ne $csdb->port()) { - $logger->writeLogInfo("Database centstorage config had been modified"); + $logger->writeLogInfo("[core] Database centstorage config has been modified"); $csdb->db($centreon_config->{centstorage_db}); $csdb->host($centreon_config->{db_host}); $csdb->user($centreon_config->{db_user}); @@ -123,12 +123,12 @@ sub check_debug { if (defined($data->{'value'}) && $data->{'value'} == 1) { if (!$logger->is_debug()) { $logger->severity("debug"); - $logger->writeLogInfo("Enable Debug in $name"); + $logger->writeLogInfo("[core] Enable Debug in $name"); } } else { if ($logger->is_debug()) { $logger->set_default_severity(); - $logger->writeLogInfo("Disable Debug in $name"); + $logger->writeLogInfo("[core] Disable Debug in $name"); } } return 0; @@ -159,7 +159,7 @@ sub backtick { $| = 1; if (!defined($pid = open( KID, "-|" ))) { - $arg{logger}->writeLogError("Cant fork: $!"); + $arg{logger}->writeLogError("[core] Cant fork: $!"); return (-1000, "cant fork: $!"); } @@ -223,7 +223,7 @@ sub mymodule_load { $file =~ s/\.pm$//; }; if ($@) { - $options{logger}->writeLogError($options{error_msg} . ' - ' . $@); + $options{logger}->writeLogError('[core] ' . $options{error_msg} . ' - ' . $@); return 1; } return wantarray ? (0, $file) : 0; @@ -235,7 +235,7 @@ sub write_file { File::Path::make_path(File::Basename::dirname($options{filename})); my $fh; if (!open($fh, '>', $options{filename})) { - $options{logger}->writeLogError("Could not open file '$options{filename}': $!"); + $options{logger}->writeLogError("[core] Cannot open file '$options{filename}': $!"); return 0; } print $fh $options{content}; From 34c27ecbe161885bc8db199fe90037f35d0201b0 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Fri, 27 Dec 2019 15:08:53 +0100 Subject: [PATCH 289/948] enh(nodes): manage debug_gorgone --- gorgone/gorgone/class/logger.pm | 14 ++++++++--- gorgone/gorgone/class/script.pm | 1 + .../gorgone/modules/centreon/nodes/class.pm | 24 +++++++++++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/gorgone/gorgone/class/logger.pm b/gorgone/gorgone/class/logger.pm index 82d68a5a42d..c0b553b83dd 100644 --- a/gorgone/gorgone/class/logger.pm +++ b/gorgone/gorgone/class/logger.pm @@ -53,9 +53,11 @@ use Sys::Syslog qw(:standard :macros); use IO::Handle; use Encode; -my %severities = (1 => LOG_INFO, - 2 => LOG_ERR, - 4 => LOG_DEBUG); +my %severities = ( + 1 => LOG_INFO, + 2 => LOG_ERR, + 4 => LOG_DEBUG +); sub new { my $class = shift; @@ -132,6 +134,12 @@ sub redirect_output { } } +sub force_default_severity { + my ($self, %options) = @_; + + $self->{old_severity} = defined($options{severity}) ? $options{severity} : $self->{severity}; +} + sub set_default_severity { my $self = shift; diff --git a/gorgone/gorgone/class/script.pm b/gorgone/gorgone/class/script.pm index cac9c142b1e..6a223a4e2ea 100644 --- a/gorgone/gorgone/class/script.pm +++ b/gorgone/gorgone/class/script.pm @@ -65,6 +65,7 @@ sub init { $self->{logger}->file_mode($self->{log_file}); } $self->{logger}->severity($self->{severity}); + $self->{logger}->force_default_severity(); if ($self->{noroot} == 1) { # Stop exec if root diff --git a/gorgone/gorgone/modules/centreon/nodes/class.pm b/gorgone/gorgone/modules/centreon/nodes/class.pm index 5b87399952b..3656248b441 100644 --- a/gorgone/gorgone/modules/centreon/nodes/class.pm +++ b/gorgone/gorgone/modules/centreon/nodes/class.pm @@ -88,6 +88,28 @@ sub class_handle_HUP { } } +sub check_debug { + my ($self, %options) = @_; + + my $request = "SELECT `value` FROM options WHERE `key` = 'debug_gorgone'"; + my ($status, $datas) = $self->{class_object}->custom_execute(request => $request, mode => 2); + if ($status == -1) { + $self->send_log(code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot find debug configuration' }); + $self->{logger}->writeLogError('[nodes] -class- cannot find debug configuration'); + return 1; + } + + my $map_values = { 0 => 'default', 1 => 'debug' }; + my $debug_gorgone = 0; + $debug_gorgone = $datas->[0]->[0] if (defined($datas->[0]->[0])); + if (!defined($self->{debug_gorgone}) || $self->{debug_gorgone} != $debug_gorgone) { + $self->send_internal_action(action => 'BCASTLOGGER', data => { content => { severity => $map_values->{$debug_gorgone} } } ); + } + + $self->{debug_gorgone} = $debug_gorgone; + return 0; +} + sub action_nodesresync { my ($self, %options) = @_; @@ -95,6 +117,8 @@ sub action_nodesresync { $self->send_log(code => gorgone::class::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action nodesresync proceed' }); + return 1 if ($self->check_debug()); + my $request = 'SELECT remote_server_id, poller_server_id FROM rs_poller_relation'; my ($status, $datas) = $self->{class_object}->custom_execute(request => $request, mode => 2); if ($status == -1) { From 66575df01304018007233b29e94d40f14cd05fbe Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Fri, 27 Dec 2019 15:35:01 +0100 Subject: [PATCH 290/948] enh(contrib): update config --- gorgone/contrib/gorgone_config_init.pl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/contrib/gorgone_config_init.pl b/gorgone/contrib/gorgone_config_init.pl index 97c30851d62..7864e202a06 100644 --- a/gorgone/contrib/gorgone_config_init.pl +++ b/gorgone/contrib/gorgone_config_init.pl @@ -127,7 +127,7 @@ sub write_gorgone_config { enable: true - name: pollers - package: gorgone::modules::centreon::pollers::hooks + package: gorgone::modules::centreon::nodes::hooks enable: true - name: broker From 13ed52bd568c90305377858cd22c7bde8b61f4b6 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 27 Dec 2019 15:48:47 +0100 Subject: [PATCH 291/948] enh(core): enhance logging --- gorgone/gorgone/class/clientzmq.pm | 8 +-- gorgone/gorgone/class/core.pm | 22 +++--- gorgone/gorgone/class/db.pm | 2 +- gorgone/gorgone/class/module.pm | 6 +- gorgone/gorgone/class/sqlquery.pm | 4 +- .../modules/centreon/autodiscovery/class.pm | 22 +++--- .../modules/centreon/autodiscovery/hooks.pm | 10 +-- .../gorgone/modules/centreon/broker/class.pm | 16 ++--- .../gorgone/modules/centreon/broker/hooks.pm | 10 +-- .../gorgone/modules/centreon/engine/class.pm | 21 +++--- .../modules/centreon/legacycmd/class.pm | 22 +++--- .../modules/centreon/legacycmd/hooks.pm | 10 +-- .../gorgone/modules/centreon/nodes/class.pm | 10 +-- .../gorgone/modules/centreon/nodes/hooks.pm | 10 +-- gorgone/gorgone/modules/core/action/class.pm | 9 +-- gorgone/gorgone/modules/core/action/hooks.pm | 10 +-- gorgone/gorgone/modules/core/cron/class.pm | 54 +++++++------- gorgone/gorgone/modules/core/cron/hooks.pm | 10 +-- .../gorgone/modules/core/dbcleaner/class.pm | 10 +-- .../gorgone/modules/core/dbcleaner/hooks.pm | 10 +-- .../gorgone/modules/core/httpserver/class.pm | 16 ++--- .../gorgone/modules/core/httpserver/hooks.pm | 14 ++-- gorgone/gorgone/modules/core/proxy/class.pm | 16 ++--- gorgone/gorgone/modules/core/proxy/hooks.pm | 40 +++++------ .../gorgone/modules/core/proxy/sshclient.pm | 34 ++++----- gorgone/gorgone/modules/core/pull/hooks.pm | 4 +- .../gorgone/modules/core/register/class.pm | 8 +-- .../gorgone/modules/core/register/hooks.pm | 12 ++-- .../gorgone/modules/plugins/newtest/class.pm | 70 +++++++++---------- .../gorgone/modules/plugins/newtest/hooks.pm | 20 +++--- gorgone/gorgone/modules/plugins/scom/class.pm | 22 +++--- gorgone/gorgone/modules/plugins/scom/hooks.pm | 18 ++--- 32 files changed, 275 insertions(+), 275 deletions(-) diff --git a/gorgone/gorgone/class/clientzmq.pm b/gorgone/gorgone/class/clientzmq.pm index f5e50a635a7..5dcb58f596b 100644 --- a/gorgone/gorgone/class/clientzmq.pm +++ b/gorgone/gorgone/class/clientzmq.pm @@ -83,7 +83,7 @@ sub new { $connector->{ping_timeout_time} = time(); if (defined($connector->{logger}) && $connector->{logger}->is_debug()) { - $connector->{logger}->writeLogDebug('jwk thumbprint = ' . $connector->{client_pubkey}->export_key_jwk_thumbprint('SHA256')); + $connector->{logger}->writeLogDebug('[core] JWK thumbprint = ' . $connector->{client_pubkey}->export_key_jwk_thumbprint('SHA256')); } $connectors->{$options{identity}} = $connector; @@ -121,7 +121,7 @@ sub check_server_pubkey { my ($self, %options) = @_; if ($options{message} !~ /^\s*\[PUBKEY\]\s+\[(.*?)\]/) { - $self->{logger}->writeLogError('cannot read pubbkey response from server: ' . $options{message}) if (defined($self->{logger})); + $self->{logger}->writeLogError('[core] Cannot read pubbkey response from server: ' . $options{message}) if (defined($self->{logger})); $self->{verbose_last_message} = 'cannot read pubkey response from server'; return 0; } @@ -135,7 +135,7 @@ sub check_server_pubkey { ); if ($code == 0) { - $self->{logger}->writeLogError('cannot load pubbkey') if (defined($self->{logger})); + $self->{logger}->writeLogError('[core] Cannot load pubbkey') if (defined($self->{logger})); $self->{verbose_last_message} = 'cannot load pubkey'; return 0; } @@ -181,7 +181,7 @@ sub ping { } if ($self->{ping_progress} == 1 && time() - $self->{ping_timeout_time} > $self->{ping_timeout}) { - $self->{logger}->writeLogError("no ping response") if (defined($self->{logger})); + $self->{logger}->writeLogError("[core] No ping response") if (defined($self->{logger})); $self->{ping_progress} = 0; zmq_close($sockets->{$self->{identity}}); $self->init(); diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index efa799a16e6..0f4cae0031c 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -77,7 +77,7 @@ sub init_server_keys { my ($self, %options) = @_; my ($code, $content_privkey, $content_pubkey); - $self->{logger}->writeLogInfo("[core] init server keys"); + $self->{logger}->writeLogInfo("[core] Initialize server keys"); $self->{keys_loaded} = 0; $options{config}->{gorgonecore}->{privkey} = defined($options{config}->{gorgonecore}->{privkey}) && $options{config}->{gorgonecore}->{privkey} ne '' ? @@ -94,7 +94,7 @@ sub init_server_keys { content => $content_privkey, ); return if ($code == 0); - $self->{logger}->writeLogInfo("[core] private key file '$options{config}->{gorgonecore}->{privkey}' written"); + $self->{logger}->writeLogInfo("[core] Private key file '$options{config}->{gorgonecore}->{privkey}' written"); $code = gorgone::standard::misc::write_file( logger => $self->{logger}, @@ -102,7 +102,7 @@ sub init_server_keys { content => $content_pubkey, ); return if ($code == 0); - $self->{logger}->writeLogInfo("[core] public key file '$options{config}->{gorgonecore}->{pubkey}' written"); + $self->{logger}->writeLogInfo("[core] Public key file '$options{config}->{gorgonecore}->{pubkey}' written"); } ($code, $self->{server_privkey}) = gorgone::standard::library::loadprivkey( @@ -111,7 +111,7 @@ sub init_server_keys { noquit => 1 ); return if ($code == 0); - $self->{logger}->writeLogInfo("[core] private key file '$options{config}->{gorgonecore}->{privkey}' loaded"); + $self->{logger}->writeLogInfo("[core] Private key file '$options{config}->{gorgonecore}->{privkey}' loaded"); ($code, $self->{server_pubkey}) = gorgone::standard::library::loadpubkey( logger => $self->{logger}, @@ -119,7 +119,7 @@ sub init_server_keys { noquit => 1 ); return if ($code == 0); - $self->{logger}->writeLogInfo("[core] public key file '$options{config}->{gorgonecore}->{pubkey}' loaded"); + $self->{logger}->writeLogInfo("[core] Public key file '$options{config}->{gorgonecore}->{pubkey}' loaded"); $self->{keys_loaded} = 1; } @@ -250,7 +250,7 @@ sub handle_CHLD { my $child_pid; while (($child_pid = waitpid(-1, &WNOHANG)) > 0) { - $self->{logger}->writeLogInfo("[core] received SIGCLD signal (pid: $child_pid)"); + $self->{logger}->writeLogDebug("[core] Received SIGCLD signal (pid: $child_pid)"); $self->{return_child}->{$child_pid} = time(); } @@ -288,7 +288,7 @@ sub load_module { return 0; } if (defined($self->{modules_register}->{ $options{config_module}->{package} })) { - $self->{logger}->writeLogError("[core] package '$options{config_module}->{package}' already loaded"); + $self->{logger}->writeLogError("[core] Package '$options{config_module}->{package}' already loaded"); return 0; } @@ -394,7 +394,7 @@ sub broadcast_run { sub message_run { my ($self, %options) = @_; - $self->{logger}->writeLogDebug('[core] message received - ' . $options{message}); + $self->{logger}->writeLogDebug('[core] Message received - ' . $options{message}); if ($options{message} !~ /^\[(.+?)\]\s+\[(.*?)\]\s+\[(.*?)\]\s+(.*)$/) { return (undef, 1, { message => 'request not well formatted' }); } @@ -718,8 +718,8 @@ sub run { $gorgone->SUPER::run(); $gorgone->{logger}->redirect_output(); - $gorgone->{logger}->writeLogDebug("[core] gorgoned launched...."); - $gorgone->{logger}->writeLogDebug("[core] PID $$"); + $gorgone->{logger}->writeLogInfo("[core] Gorgoned started"); + $gorgone->{logger}->writeLogInfo("[core] PID $$"); if (gorgone::standard::library::add_history( dbh => $gorgone->{db_gorgone}, @@ -769,7 +769,7 @@ sub run { # init all modules foreach my $name (keys %{$gorgone->{modules_register}}) { - $gorgone->{logger}->writeLogInfo("[core] Call init function from module '$name'"); + $gorgone->{logger}->writeLogDebug("[core] Call init function from module '$name'"); $gorgone->{modules_register}->{$name}->{init}->( id => $gorgone->{id}, logger => $gorgone->{logger}, diff --git a/gorgone/gorgone/class/db.pm b/gorgone/gorgone/class/db.pm index af717f85837..b407cef955e 100644 --- a/gorgone/gorgone/class/db.pm +++ b/gorgone/gorgone/class/db.pm @@ -230,7 +230,7 @@ sub do { if (!defined $self->{instance}) { if ($self->connect() == -1) { - $self->{logger}->writeLogError("Can't connect to the database"); + $self->{logger}->writeLogError("Cannot connect to database"); return -1; } } diff --git a/gorgone/gorgone/class/module.pm b/gorgone/gorgone/class/module.pm index efdca94a76e..5bd379e7a1d 100644 --- a/gorgone/gorgone/class/module.pm +++ b/gorgone/gorgone/class/module.pm @@ -74,7 +74,7 @@ sub json_encode { if ($@) { my $container = ''; $container = 'container ' . $self->{container_id} . ': ' if (defined($self->{container_id})); - $self->{logger}->writeLogError("[$self->{module_id}] -class- ${container}$options{method} - cannot encode json: $@"); + $self->{logger}->writeLogError("[$self->{module_id}] ${container}$options{method} - cannot encode json: $@"); return 1; } @@ -91,7 +91,7 @@ sub json_decode { if ($@) { my $container = ''; $container = 'container ' . $self->{container_id} . ': ' if (defined($self->{container_id})); - $self->{logger}->writeLogError("[$self->{module_id}] -class- ${container}$options{method} - cannot decode json: $@"); + $self->{logger}->writeLogError("[$self->{module_id}] ${container}$options{method} - cannot decode json: $@"); return 1; } @@ -111,7 +111,7 @@ sub execute_shell_cmd { if ($lerror == -1 || ($exit_code >> 8) != 0) { my $container = ''; $container = 'container ' . $self->{container_id} . ': ' if (defined($self->{container_id})); - $self->{logger}->writeLogError("[$self->{module_id}] -class- ${container}command execution issue $options{cmd} : " . $stdout); + $self->{logger}->writeLogError("[$self->{module_id}] ${container}command execution issue $options{cmd} : " . $stdout); return -1; } diff --git a/gorgone/gorgone/class/sqlquery.pm b/gorgone/gorgone/class/sqlquery.pm index 1af61d36964..9ca15404abf 100644 --- a/gorgone/gorgone/class/sqlquery.pm +++ b/gorgone/gorgone/class/sqlquery.pm @@ -56,14 +56,14 @@ sub do { } elsif ($mode == 1) { my $result = $sth->fetchall_hashref($options{keys}); if (!defined($result)) { - $self->{logger}->writeLogError("Cannot fetch database data: " . $sth->errstr . " [request = $options{request}]"); + $self->{logger}->writeLogError("[core] Cannot fetch database data: " . $sth->errstr . " [request = $options{request}]"); return (-1, undef); } return ($status, $result); } my $result = $sth->fetchall_arrayref(); if (!defined($result)) { - $self->{logger}->writeLogError("Cannot fetch database data: " . $sth->errstr . " [request = $options{request}]"); + $self->{logger}->writeLogError("[core] Cannot fetch database data: " . $sth->errstr . " [request = $options{request}]"); return (-1, undef); } return ($status, $result); diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm index 2342230c73f..703f289e45a 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm @@ -71,7 +71,7 @@ sub handle_HUP { sub handle_TERM { my $self = shift; - $self->{logger}->writeLogInfo("[autodiscovery] -class- $$ Receiving order to stop..."); + $self->{logger}->writeLogInfo("[autodiscovery] $$ Receiving order to stop..."); $self->{stop} = 1; } @@ -93,7 +93,7 @@ sub action_adddiscoverytask { $options{token} = $self->generate_token() if (!defined($options{token})); my $id = 'autodiscovery_task_' . $self->generate_token(length => 12) if (!defined($options{data}->{content}->{id})); - $self->{logger}->writeLogInfo("[autodiscovery] -class- Add task '" . $id . "'"); + $self->{logger}->writeLogInfo("[autodiscovery] Add task '" . $id . "'"); $self->send_internal_action( action => 'COMMAND', target => $options{data}->{content}->{target}, @@ -131,7 +131,7 @@ sub action_getdiscoverytask { $options{token} = $self->generate_token() if (!defined($options{token})); if (!defined($options{data}->{variables}[0])) { - $self->{logger}->writeLogError("[autodiscovery] -class- Need to specify job id"); + $self->{logger}->writeLogError("[autodiscovery] Need to specify job id"); $self->send_log( code => $self->ACTION_FINISH_KO, token => $options{token}, @@ -160,7 +160,7 @@ sub action_adddiscoveryjob { $options{token} = $self->generate_token() if (!defined($options{token})); my $id = 'autodiscovery_job_' . $self->generate_token(length => 12) if (!defined($options{data}->{content}->{id})); - $self->{logger}->writeLogInfo("[autodiscovery] -class- Add job '" . $id . "'"); + $self->{logger}->writeLogInfo("[autodiscovery] Add job '" . $id . "'"); my $definition = { id => $id, target => $options{data}->{content}->{target}, @@ -205,7 +205,7 @@ sub action_getdiscoveryjob { $options{token} = $self->generate_token() if (!defined($options{token})); if (!defined($options{data}->{variables}[0])) { - $self->{logger}->writeLogError("[autodiscovery] -class- Need to specify job id"); + $self->{logger}->writeLogError("[autodiscovery] Need to specify job id"); $self->send_log( code => $self->ACTION_FINISH_KO, token => $options{token}, @@ -265,7 +265,7 @@ sub action_getdiscoveryresults { foreach my $id (keys %tasks) { next if (defined($tasks{$id}->{results})); - $self->{logger}->writeLogDebug("[autodiscovery] -class- Get logs results for task '" . $id . "'"); + $self->{logger}->writeLogDebug("[autodiscovery] Get logs results for task '" . $id . "'"); $self->send_internal_action( action => 'GETLOG', data => { @@ -274,7 +274,7 @@ sub action_getdiscoveryresults { ); } foreach my $id (keys %jobs) { - $self->{logger}->writeLogDebug("[autodiscovery] -class- Get logs results for job '" . $id . "'"); + $self->{logger}->writeLogDebug("[autodiscovery] Get logs results for job '" . $id . "'"); $self->send_internal_action( action => 'GETLOG', data => { @@ -298,9 +298,7 @@ sub action_updatediscoveryresults { !defined($data->{metadata}->{source}) || $data->{metadata}->{source} ne 'autodiscovery'); if ($data->{metadata}->{type} eq 'task') { - $self->{logger}->writeLogInfo( - "[autodiscovery] -class- Found result for task '" . $data->{metadata}->{id} . "'" - ); + $self->{logger}->writeLogInfo("[autodiscovery] Found result for task '" . $data->{metadata}->{id} . "'"); $tasks{$data->{metadata}->{id}}->{results} = $data; } elsif ($data->{metadata}->{type} eq 'job') { $jobs{$data->{metadata}->{id}}->{results} = $data ; @@ -314,7 +312,7 @@ sub event { while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); - $connector->{logger}->writeLogDebug("[autodiscovery] -class- Event: $message"); + $connector->{logger}->writeLogDebug("[autodiscovery] Event: $message"); if ($message =~ /^\[ACK\]\s+\[(.*?)\]\s+(.*)$/m) { my $token = $1; my $data = JSON::XS->new->utf8->decode($2); @@ -391,7 +389,7 @@ sub run { # we try to do all we can my $rev = zmq_poll($self->{poll}, 5000); if (defined($rev) && $rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("[autodiscovery] -class- $$ has quit"); + $self->{logger}->writeLogInfo("[autodiscovery] $$ has quit"); zmq_close($connector->{internal_socket}); exit(0); } diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm b/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm index 3dee41991bc..2dd9a5131b0 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm @@ -68,7 +68,7 @@ sub routing { $data = JSON::XS->new->utf8->decode($options{data}); }; if ($@) { - $options{logger}->writeLogError("[autodiscovery] -hooks- Cannot decode json data: $@"); + $options{logger}->writeLogError("[autodiscovery] Cannot decode json data: $@"); gorgone::standard::library::add_history( dbh => $options{dbh}, code => 30, token => $options{token}, @@ -106,7 +106,7 @@ sub gently { my (%options) = @_; $stop = 1; - $options{logger}->writeLogInfo("[autodiscovery] -hooks- Send TERM signal"); + $options{logger}->writeLogDebug("[autodiscovery] Send TERM signal"); if ($autodiscovery->{running} == 1) { CORE::kill('TERM', $autodiscovery->{pid}); } @@ -116,7 +116,7 @@ sub kill { my (%options) = @_; if ($autodiscovery->{running} == 1) { - $options{logger}->writeLogInfo("[autodiscovery] -hooks- Send KILL signal for pool"); + $options{logger}->writeLogDebug("[autodiscovery] Send KILL signal for pool"); CORE::kill('KILL', $autodiscovery->{pid}); } } @@ -156,7 +156,7 @@ sub broadcast { sub create_child { my (%options) = @_; - $options{logger}->writeLogInfo("[autodiscovery] -hooks- Create module 'autodiscovery' process"); + $options{logger}->writeLogInfo("[autodiscovery] Create module 'autodiscovery' process"); my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-autodiscovery'; @@ -169,7 +169,7 @@ sub create_child { $module->run(); exit(0); } - $options{logger}->writeLogInfo("[autodiscovery] -hooks- PID $child_pid (gorgone-autodiscovery)"); + $options{logger}->writeLogDebug("[autodiscovery] PID $child_pid (gorgone-autodiscovery)"); $autodiscovery = { pid => $child_pid, ready => 0, running => 1 }; } diff --git a/gorgone/gorgone/modules/centreon/broker/class.pm b/gorgone/gorgone/modules/centreon/broker/class.pm index f73616a2550..bdc47f10004 100644 --- a/gorgone/gorgone/modules/centreon/broker/class.pm +++ b/gorgone/gorgone/modules/centreon/broker/class.pm @@ -69,7 +69,7 @@ sub handle_HUP { sub handle_TERM { my $self = shift; - $self->{logger}->writeLogInfo("[broker] -class- $$ Receiving order to stop..."); + $self->{logger}->writeLogInfo("[broker] $$ Receiving order to stop..."); $self->{stop} = 1; } @@ -93,7 +93,7 @@ sub get_broker_stats_collection_flag { mode => 2 ); if ($status == -1 || !defined($datas->[0][0])) { - $self->{logger}->writeLogError('[broker] -class- Cannot get Broker statistics collection flag'); + $self->{logger}->writeLogError('[broker] Cannot get Broker statistics collection flag'); return -1; } @@ -121,7 +121,7 @@ sub action_brokerstats { message => 'no collection configured' } ); - $self->{logger}->writeLogInfo("[broker] -class- No Broker statistics collection configured"); + $self->{logger}->writeLogInfo("[broker] No Broker statistics collection configured"); return 0; } @@ -147,7 +147,7 @@ sub action_brokerstats { message => 'cannot find configuration' } ); - $self->{logger}->writeLogError("[broker] -class- Cannot find configuration"); + $self->{logger}->writeLogError("[broker] Cannot find configuration"); return 1; } @@ -156,7 +156,7 @@ sub action_brokerstats { my $target = $_->[0]; my $statistics_file = $_->[1] . "/" . $_->[2] . "-stats.json"; $self->{logger}->writeLogInfo( - "[broker] -class- Collecting file '" . $statistics_file . "' on target '" . $target . "'" + "[broker] Collecting file '" . $statistics_file . "' on target '" . $target . "'" ); $self->send_internal_action( target => $target, @@ -222,7 +222,7 @@ sub write_stats { my $dest_dir = $self->{config}->{cache_dir} . '/' . $data->{metadata}->{poller_id}; make_path($dest_dir) if (! -d $dest_dir); my $dest_file = $dest_dir . '/' . $data->{metadata}->{config_name} . '.json'; - $self->{logger}->writeLogDebug("[broker] -class- Writing file '" . $dest_file . "'"); + $self->{logger}->writeLogDebug("[broker] Writing file '" . $dest_file . "'"); open(FH, '>', $dest_file); print FH $data->{result}->{stdout}; close(FH); @@ -233,7 +233,7 @@ sub event { while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); - $connector->{logger}->writeLogDebug("[broker] -class- Event: $message"); + $connector->{logger}->writeLogDebug("[broker] Event: $message"); if ($message =~ /^\[ACK\]\s+\[(.*?)\]\s+(.*)$/m) { my $token = $1; my $data = JSON::XS->new->utf8->decode($2); @@ -302,7 +302,7 @@ sub run { # we try to do all we can my $rev = zmq_poll($self->{poll}, 5000); if (defined($rev) && $rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("[broker] -class- $$ has quit"); + $self->{logger}->writeLogInfo("[broker] $$ has quit"); zmq_close($connector->{internal_socket}); exit(0); } diff --git a/gorgone/gorgone/modules/centreon/broker/hooks.pm b/gorgone/gorgone/modules/centreon/broker/hooks.pm index ec6d14ca01c..0f3e5180c44 100644 --- a/gorgone/gorgone/modules/centreon/broker/hooks.pm +++ b/gorgone/gorgone/modules/centreon/broker/hooks.pm @@ -63,7 +63,7 @@ sub routing { $data = JSON::XS->new->utf8->decode($options{data}); }; if ($@) { - $options{logger}->writeLogError("[broker] -hooks- Cannot decode json data: $@"); + $options{logger}->writeLogError("[broker] Cannot decode json data: $@"); gorgone::standard::library::add_history( dbh => $options{dbh}, code => 30, token => $options{token}, @@ -101,7 +101,7 @@ sub gently { my (%options) = @_; $stop = 1; - $options{logger}->writeLogInfo("[broker] -hooks- Send TERM signal"); + $options{logger}->writeLogDebug("[broker] Send TERM signal"); if ($broker->{running} == 1) { CORE::kill('TERM', $broker->{pid}); } @@ -111,7 +111,7 @@ sub kill { my (%options) = @_; if ($broker->{running} == 1) { - $options{logger}->writeLogInfo("[broker] -hooks- Send KILL signal for pool"); + $options{logger}->writeLogDebug("[broker] Send KILL signal for pool"); CORE::kill('KILL', $broker->{pid}); } } @@ -151,7 +151,7 @@ sub broadcast { sub create_child { my (%options) = @_; - $options{logger}->writeLogInfo("[broker] -hooks- Create module 'broker' process"); + $options{logger}->writeLogInfo("[broker] Create module 'broker' process"); my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-broker'; @@ -164,7 +164,7 @@ sub create_child { $module->run(); exit(0); } - $options{logger}->writeLogInfo("[broker] -hooks- PID $child_pid (gorgone-broker)"); + $options{logger}->writeLogDebug("[broker] PID $child_pid (gorgone-broker)"); $broker = { pid => $child_pid, ready => 0, running => 1 }; } diff --git a/gorgone/gorgone/modules/centreon/engine/class.pm b/gorgone/gorgone/modules/centreon/engine/class.pm index 343cab7d083..63287eb52f9 100644 --- a/gorgone/gorgone/modules/centreon/engine/class.pm +++ b/gorgone/gorgone/modules/centreon/engine/class.pm @@ -65,7 +65,7 @@ sub handle_HUP { sub handle_TERM { my $self = shift; - $self->{logger}->writeLogInfo("[engine] -class- $$ Receiving order to stop..."); + $self->{logger}->writeLogInfo("[engine] $$ Receiving order to stop..."); $self->{stop} = 1; } @@ -100,7 +100,7 @@ sub action_enginecommand { my $index = 0; foreach my $command (@{$options{data}->{content}}) { if (!defined($command->{command}) || $command->{command} eq '') { - $self->{logger}->writeLogError("[engine] -class- action_enginecommand: need command argument at array index '" . $index . "'"); + $self->{logger}->writeLogError("[engine] Need command argument at array index '" . $index . "'"); $self->send_log( socket => $options{socket_log}, code => $self->ACTION_FINISH_KO, @@ -134,7 +134,7 @@ sub action_enginecommand { } if (!defined($command_file) || $command_file eq '') { - $self->{logger}->writeLogError("[engine] -class- need command_file (config or call) argument"); + $self->{logger}->writeLogError("[engine] Need command_file (config or call) argument"); $self->send_log( socket => $options{socket_log}, code => $self->ACTION_FINISH_KO, @@ -147,7 +147,7 @@ sub action_enginecommand { (defined($command->{continue_on_error}) && $command->{continue_on_error} eq 'false') ? return -1 : next; } if (! -e $command_file) { - $self->{logger}->writeLogError("[engine] -class- command '$command->{command}' - command_file '$command_file' must exist"); + $self->{logger}->writeLogError("[engine] Command '$command->{command}' - command_file '$command_file' must exist"); $self->send_log( socket => $options{socket_log}, code => $self->ACTION_FINISH_KO, @@ -160,7 +160,7 @@ sub action_enginecommand { (defined($command->{continue_on_error}) && $command->{continue_on_error} eq 'false') ? return -1 : next; } if (! -p $command_file) { - $self->{logger}->writeLogError("[engine] -class- command '$command->{command}' - command_file '$command_file' must be a pipe file"); + $self->{logger}->writeLogError("[engine] Command '$command->{command}' - command_file '$command_file' must be a pipe file"); $self->send_log( socket => $options{socket_log}, code => $self->ACTION_FINISH_KO, @@ -173,7 +173,7 @@ sub action_enginecommand { (defined($command->{continue_on_error}) && $command->{continue_on_error} eq 'false') ? return -1 : next; } if (! -w $command_file) { - $self->{logger}->writeLogError("[engine] -class- command '$command->{command}' - command_file '$command_file' must be writeable"); + $self->{logger}->writeLogError("[engine] Command '$command->{command}' - command_file '$command_file' must be writeable"); $self->send_log( socket => $options{socket_log}, code => $self->ACTION_FINISH_KO, @@ -197,7 +197,7 @@ sub action_enginecommand { }; if ($@) { close $fh if (defined($fh)); - $self->{logger}->writeLogError("[engine] -class- submit engine command '$command->{command}' issue: $@"); + $self->{logger}->writeLogError("[engine] Submit engine command '$command->{command}' issue: $@"); $self->send_log( socket => $options{socket_log}, code => $self->ACTION_FINISH_KO, @@ -275,9 +275,10 @@ sub create_child { return undef; } - $self->{logger}->writeLogInfo('[engine] -class- create sub-process'); + $self->{logger}->writeLogDebug('[engine] Create sub-process'); my $child_pid = fork(); if (!defined($child_pid)) { + $self->{logger}->writeLogError("[engine] Cannot fork process: $!"); $self->send_log( code => $self->ACTION_FINISH_KO, token => $token, @@ -296,7 +297,7 @@ sub event { while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); - $connector->{logger}->writeLogDebug("[engine] -class- Event: $message"); + $connector->{logger}->writeLogDebug("[engine] Event: $message"); if ($message !~ /^\[ACK\]/) { $connector->create_child(message => $message); @@ -332,7 +333,7 @@ sub run { # we try to do all we can my $rev = zmq_poll($self->{poll}, 5000); if ($rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("[engine] -class- $$ has quit"); + $self->{logger}->writeLogInfo("[engine] $$ has quit"); zmq_close($connector->{internal_socket}); exit(0); } diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index 7db9946ab9e..974d39e1645 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -72,7 +72,7 @@ sub handle_HUP { sub handle_TERM { my $self = shift; - $self->{logger}->writeLogInfo("[legacycmd] -class- $$ Receiving order to stop..."); + $self->{logger}->writeLogDebug("[legacycmd] $$ Receiving order to stop..."); $self->{stop} = 1; } @@ -103,7 +103,7 @@ sub get_pollers_config { keys => 'nagios_server_id' ); if ($status == -1 || !defined($datas)) { - $self->{logger}->writeLogError('[legacycmd] -class- cannot get configuration for pollers'); + $self->{logger}->writeLogError('[legacycmd] Cannot get configuration for pollers'); return -1; } @@ -124,7 +124,7 @@ sub get_clapi_user { mode => 2 ); if ($status == -1 || !defined($datas->[0][0])) { - $self->{logger}->writeLogError('[legacycmd] -class- cannot get configuration for clapi user'); + $self->{logger}->writeLogError('[legacycmd] Cannot get configuration for CLAPI user'); return -1; } my $clapi_password = $datas->[0][0]; @@ -146,7 +146,7 @@ sub get_illegal_characters { mode => 2 ); if ($status == -1 || !defined($datas->[0][0])) { - $self->{logger}->writeLogError('[legacycmd] -class- cannot get centcore illegal characters'); + $self->{logger}->writeLogError('[legacycmd] Cannot get illegal characters'); return -1; } @@ -444,7 +444,7 @@ sub move_cmd_file { my $handle; if (-e $options{dst}) { if (!open($handle, $operator, $options{dst})) { - $self->{logger}->writeLogError("[legacycmd] -class- cannot open file '" . $options{dst} . "': $!"); + $self->{logger}->writeLogError("[legacycmd] Cannot open file '" . $options{dst} . "': $!"); return -1; } @@ -455,12 +455,12 @@ sub move_cmd_file { return -1 if (! -e $options{src}); if (!File::Copy::move($options{src}, $options{dst})) { - $self->{logger}->writeLogError("[legacycmd] -class- cannot move file '" . $options{src} . "': $!"); + $self->{logger}->writeLogError("[legacycmd] Cannot move file '" . $options{src} . "': $!"); return -1; } if (!open($handle, $operator, $options{dst})) { - $self->{logger}->writeLogError("[legacycmd] -class- cannot open file '" . $options{dst} . "': $!"); + $self->{logger}->writeLogError("[legacycmd] Cannot open file '" . $options{dst} . "': $!"); return -1; } @@ -471,7 +471,7 @@ sub handle_file { my ($self, %options) = @_; require bytes; - $self->{logger}->writeLogDebug("[legacycmd] -class- Processing file '" . $options{file} . "'"); + $self->{logger}->writeLogDebug("[legacycmd] Processing file '" . $options{file} . "'"); my $handle = $options{handle}; while (my $line = <$handle>) { if ($self->{stop} == 1) { @@ -512,7 +512,7 @@ sub handle_centcore_dir { my ($dh, @files); if (!opendir($dh, $self->{config}->{cmd_dir})) { - $self->{logger}->writeLogError("[legacycmd] -class- cant opendir '" . $self->{config}->{cmd_dir} . "': $!"); + $self->{logger}->writeLogError("[legacycmd] Cannot open directory '" . $self->{config}->{cmd_dir} . "': $!"); return ; } @files = sort { @@ -555,7 +555,7 @@ sub event { while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); - $connector->{logger}->writeLogDebug("[legacycmd] -class- Event: $message"); + $connector->{logger}->writeLogDebug("[legacycmd] Event: $message"); if ($message =~ /^\[(.*?)\]/) { if ((my $method = $connector->can('action_' . lc($1)))) { $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; @@ -608,7 +608,7 @@ sub run { # we try to do all we can my $rev = zmq_poll($self->{poll}, 2000); if ($rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("[legacycmd] -class- $$ has quit"); + $self->{logger}->writeLogInfo("[legacycmd] $$ has quit"); zmq_close($connector->{internal_socket}); exit(0); } diff --git a/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm b/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm index b63516ef7d2..c0963bce419 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm @@ -65,7 +65,7 @@ sub routing { $data = JSON::XS->new->utf8->decode($options{data}); }; if ($@) { - $options{logger}->writeLogError("[legacycmd] -hooks- Cannot decode json data: $@"); + $options{logger}->writeLogError("[legacycmd] Cannot decode json data: $@"); gorgone::standard::library::add_history( dbh => $options{dbh}, code => 10, token => $options{token}, @@ -103,7 +103,7 @@ sub gently { my (%options) = @_; $stop = 1; - $options{logger}->writeLogInfo("[legacycmd] -hooks- Send TERM signal"); + $options{logger}->writeLogDebug("[legacycmd] Send TERM signal"); if ($legacycmd->{running} == 1) { CORE::kill('TERM', $legacycmd->{pid}); } @@ -113,7 +113,7 @@ sub kill { my (%options) = @_; if ($legacycmd->{running} == 1) { - $options{logger}->writeLogInfo("[legacycmd] -hooks- Send KILL signal for pool"); + $options{logger}->writeLogDebug("[legacycmd] Send KILL signal for pool"); CORE::kill('KILL', $legacycmd->{pid}); } } @@ -153,7 +153,7 @@ sub broadcast { sub create_child { my (%options) = @_; - $options{logger}->writeLogInfo("[legacycmd] -hooks- Create module process"); + $options{logger}->writeLogInfo("[legacycmd] Create module 'legacycmd' process"); my $child_pid = fork(); if ($child_pid == 0) { @@ -167,7 +167,7 @@ sub create_child { $module->run(); exit(0); } - $options{logger}->writeLogInfo("[legacycmd] -hooks- PID $child_pid (gorgone-legacycmd)"); + $options{logger}->writeLogDebug("[legacycmd] PID $child_pid (gorgone-legacycmd)"); $legacycmd = { pid => $child_pid, ready => 0, running => 1 }; } diff --git a/gorgone/gorgone/modules/centreon/nodes/class.pm b/gorgone/gorgone/modules/centreon/nodes/class.pm index 5b87399952b..f34831c4c0d 100644 --- a/gorgone/gorgone/modules/centreon/nodes/class.pm +++ b/gorgone/gorgone/modules/centreon/nodes/class.pm @@ -72,7 +72,7 @@ sub handle_HUP { sub handle_TERM { my $self = shift; - $self->{logger}->writeLogInfo("[nodes] -class- $$ Receiving order to stop..."); + $self->{logger}->writeLogDebug("[nodes] $$ Receiving order to stop..."); $self->{stop} = 1; } @@ -99,7 +99,7 @@ sub action_nodesresync { my ($status, $datas) = $self->{class_object}->custom_execute(request => $request, mode => 2); if ($status == -1) { $self->send_log(code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot find nodes remote configuration' }); - $self->{logger}->writeLogError('[nodes] -class- cannot find nodes remote configuration'); + $self->{logger}->writeLogError('[nodes] Cannot find nodes remote configuration'); return 1; } @@ -118,7 +118,7 @@ sub action_nodesresync { ($status, $datas) = $self->{class_object}->custom_execute(request => $request, mode => 2); if ($status == -1) { $self->send_log(code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot find nodes configuration' }); - $self->{logger}->writeLogError('[nodes] -class- cannot find nodes configuration'); + $self->{logger}->writeLogError('[nodes] Cannot find nodes configuration'); return 1; } @@ -165,7 +165,7 @@ sub action_nodesresync { $self->send_internal_action(action => 'REGISTERNODES', data => { nodes => $register_nodes } ); $self->send_internal_action(action => 'UNREGISTERNODES', data => { nodes => $unregister_nodes } ); - $self->{logger}->writeLogDebug("[nodes] -class- finish resync"); + $self->{logger}->writeLogDebug("[nodes] Finish resync"); $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => { message => 'action nodesresync finished' }); return 0; } @@ -174,7 +174,7 @@ sub event { while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); - $connector->{logger}->writeLogDebug("[nodes] -class- Event: $message"); + $connector->{logger}->writeLogDebug("[nodes] Event: $message"); if ($message =~ /^\[(.*?)\]/) { if ((my $method = $connector->can('action_' . lc($1)))) { $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; diff --git a/gorgone/gorgone/modules/centreon/nodes/hooks.pm b/gorgone/gorgone/modules/centreon/nodes/hooks.pm index f6f8343a9b2..8d8490f9090 100644 --- a/gorgone/gorgone/modules/centreon/nodes/hooks.pm +++ b/gorgone/gorgone/modules/centreon/nodes/hooks.pm @@ -62,7 +62,7 @@ sub routing { $data = JSON::XS->new->utf8->decode($options{data}); }; if ($@) { - $options{logger}->writeLogError("[nodes] -hooks- Cannot decode json data: $@"); + $options{logger}->writeLogError("[nodes] Cannot decode json data: $@"); gorgone::standard::library::add_history( dbh => $options{dbh}, code => 10, token => $options{token}, @@ -100,7 +100,7 @@ sub gently { my (%options) = @_; $stop = 1; - $options{logger}->writeLogInfo("[nodes] -hooks- Send TERM signal"); + $options{logger}->writeLogDebug("[nodes] Send TERM signal"); if ($nodes->{running} == 1) { CORE::kill('TERM', $nodes->{pid}); } @@ -110,7 +110,7 @@ sub kill { my (%options) = @_; if ($nodes->{running} == 1) { - $options{logger}->writeLogInfo("[nodes] -hooks- Send KILL signal for pool"); + $options{logger}->writeLogDebug("[nodes] Send KILL signal for pool"); CORE::kill('KILL', $nodes->{pid}); } } @@ -150,7 +150,7 @@ sub broadcast { sub create_child { my (%options) = @_; - $options{logger}->writeLogInfo("[nodes] -hooks- Create module 'nodes' process"); + $options{logger}->writeLogInfo("[nodes] Create module 'nodes' process"); my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-nodes'; @@ -163,7 +163,7 @@ sub create_child { $module->run(); exit(0); } - $options{logger}->writeLogInfo("[nodes] -hooks- PID $child_pid (gorgone-nodes)"); + $options{logger}->writeLogDebug("[nodes] PID $child_pid (gorgone-nodes)"); $nodes = { pid => $child_pid, ready => 0, running => 1 }; } diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index d59607e35fa..957dca05c30 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -72,7 +72,7 @@ sub handle_HUP { sub handle_TERM { my $self = shift; - $self->{logger}->writeLogInfo("[action] -class- $$ Receiving order to stop..."); + $self->{logger}->writeLogInfo("[action] $$ Receiving order to stop..."); $self->{stop} = 1; } @@ -339,9 +339,10 @@ sub create_child { return undef; } - $self->{logger}->writeLogDebug("[action] -class- Create sub-process"); + $self->{logger}->writeLogDebug("[action] Create sub-process"); my $child_pid = fork(); if (!defined($child_pid)) { + $self->{logger}->writeLogError("[action] Cannot fork process: $!"); $self->send_log( code => $self->ACTION_FINISH_KO, token => $token, @@ -360,7 +361,7 @@ sub event { while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); - $connector->{logger}->writeLogDebug("[action] -class- Event: $message"); + $connector->{logger}->writeLogDebug("[action] Event: $message"); if ($message !~ /^\[ACK\]/) { $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; @@ -406,7 +407,7 @@ sub run { # we try to do all we can my $rev = zmq_poll($self->{poll}, 5000); if ($rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("[action] -class- $$ has quit"); + $self->{logger}->writeLogInfo("[action] $$ has quit"); zmq_close($connector->{internal_socket}); exit(0); } diff --git a/gorgone/gorgone/modules/core/action/hooks.pm b/gorgone/gorgone/modules/core/action/hooks.pm index 70608e58c37..17e1731fdf5 100644 --- a/gorgone/gorgone/modules/core/action/hooks.pm +++ b/gorgone/gorgone/modules/core/action/hooks.pm @@ -61,7 +61,7 @@ sub routing { $data = JSON::XS->new->utf8->decode($options{data}); }; if ($@) { - $options{logger}->writeLogError("[action] -hooks- Cannot decode json data: $@"); + $options{logger}->writeLogError("[action] Cannot decode json data: $@"); gorgone::standard::library::add_history( dbh => $options{dbh}, code => 30, token => $options{token}, @@ -99,7 +99,7 @@ sub gently { my (%options) = @_; $stop = 1; - $options{logger}->writeLogInfo("[action] -hooks- Send TERM signal"); + $options{logger}->writeLogDebug("[action] Send TERM signal"); if ($action->{running} == 1) { CORE::kill('TERM', $action->{pid}); } @@ -109,7 +109,7 @@ sub kill { my (%options) = @_; if ($action->{running} == 1) { - $options{logger}->writeLogInfo("[action] -hooks- Send KILL signal for pool"); + $options{logger}->writeLogDebug("[action] Send KILL signal for pool"); CORE::kill('KILL', $action->{pid}); } } @@ -149,7 +149,7 @@ sub broadcast { sub create_child { my (%options) = @_; - $options{logger}->writeLogInfo("[action] -hooks- Create module 'action' process"); + $options{logger}->writeLogInfo("[action] Create module 'action' process"); my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-action'; @@ -161,7 +161,7 @@ sub create_child { $module->run(); exit(0); } - $options{logger}->writeLogInfo("[action] -hooks- PID $child_pid (gorgone-action)"); + $options{logger}->writeLogDebug("[action] PID $child_pid (gorgone-action)"); $action = { pid => $child_pid, ready => 0, running => 1 }; } diff --git a/gorgone/gorgone/modules/core/cron/class.pm b/gorgone/gorgone/modules/core/cron/class.pm index 5dd04e0a98e..1a23520e878 100644 --- a/gorgone/gorgone/modules/core/cron/class.pm +++ b/gorgone/gorgone/modules/core/cron/class.pm @@ -63,7 +63,7 @@ sub handle_HUP { sub handle_TERM { my $self = shift; - $self->{logger}->writeLogInfo("[cron] -class- $$ Receiving order to stop..."); + $self->{logger}->writeLogDebug("[cron] $$ Receiving order to stop..."); $self->{stop} = 1; } @@ -89,7 +89,7 @@ sub action_getcron { my $parameter = $options{data}->{variables}[1]; if (defined($id) && $id ne '') { if (defined($parameter) && $parameter =~ /^status$/) { - $self->{logger}->writeLogDebug("[cron] -class- Get logs results for definition '" . $id . "'"); + $self->{logger}->writeLogInfo("[cron] Get logs results for definition '" . $id . "'"); $self->send_internal_action( action => 'GETLOG', token => $options{token}, @@ -109,7 +109,7 @@ sub action_getcron { $idx = $self->{cron}->check_entry($id); }; if ($@) { - $self->{logger}->writeLogDebug("[cron] -class- Cron get failed to retrieve entry index"); + $self->{logger}->writeLogError("[cron] Cron get failed to retrieve entry index"); $self->send_log( code => $self->ACTION_FINISH_KO, token => $options{token}, @@ -118,7 +118,7 @@ sub action_getcron { return 1; } if (!defined($idx)) { - $self->{logger}->writeLogDebug("[cron] -class- Cron get failed no entry found for id"); + $self->{logger}->writeLogError("[cron] Cron get failed no entry found for id"); $self->send_log( code => $self->ACTION_FINISH_KO, token => $options{token}, @@ -132,7 +132,7 @@ sub action_getcron { push @{$data}, { %{$result->{args}[1]->{definition}} } if (defined($result->{args}[1]->{definition})); }; if ($@) { - $self->{logger}->writeLogDebug("[cron] -class- Cron get failed"); + $self->{logger}->writeLogError("[cron] Cron get failed"); $self->send_log( code => $self->ACTION_FINISH_KO, token => $options{token}, @@ -149,7 +149,7 @@ sub action_getcron { } }; if ($@) { - $self->{logger}->writeLogDebug("[cron] -class- Cron get failed"); + $self->{logger}->writeLogError("[cron] Cron get failed"); $self->send_log( code => $self->ACTION_FINISH_KO, token => $options{token}, @@ -172,13 +172,13 @@ sub action_addcron { $options{token} = $self->generate_token() if (!defined($options{token})); - $self->{logger}->writeLogDebug("[cron] -class- Cron add start"); + $self->{logger}->writeLogDebug("[cron] Cron add start"); foreach my $definition (@{$options{data}->{content}}) { if (!defined($definition->{timespec}) || $definition->{timespec} eq '' || !defined($definition->{action}) || $definition->{action} eq '' || !defined($definition->{id}) || $definition->{id} eq '') { - $self->{logger}->writeLogDebug("[cron] -class- Cron add missing arguments"); + $self->{logger}->writeLogError("[cron] Cron add missing arguments"); $self->send_log( code => $self->ACTION_FINISH_KO, token => $options{token}, @@ -199,7 +199,7 @@ sub action_addcron { ); next; } - $self->{logger}->writeLogInfo("[cron] -class- Adding cron definition '" . $definition->{id} . "'"); + $self->{logger}->writeLogInfo("[cron] Adding cron definition '" . $definition->{id} . "'"); $self->{cron}->add_entry( $definition->{timespec}, $definition->{id}, @@ -212,7 +212,7 @@ sub action_addcron { } }; if ($@) { - $self->{logger}->writeLogDebug("[cron] -class- Cron add failed"); + $self->{logger}->writeLogError("[cron] Cron add failed"); $self->send_log( code => $self->ACTION_FINISH_KO, token => $options{token}, @@ -221,7 +221,7 @@ sub action_addcron { return 1; } - $self->{logger}->writeLogDebug("[cron] -class- Cron add finish"); + $self->{logger}->writeLogDebug("[cron] Cron add finish"); $self->send_log( code => $self->ACTION_FINISH_OK, token => $options{token}, @@ -235,11 +235,11 @@ sub action_updatecron { $options{token} = $self->generate_token() if (!defined($options{token})); - $self->{logger}->writeLogDebug("[cron] -class- Cron update start"); + $self->{logger}->writeLogDebug("[cron] Cron update start"); my $id = $options{data}->{variables}[0]; if (!defined($id)) { - $self->{logger}->writeLogDebug("[cron] -class- Cron update missing id"); + $self->{logger}->writeLogError("[cron] Cron update missing id"); $self->send_log( code => $self->ACTION_FINISH_KO, token => $options{token}, @@ -253,7 +253,7 @@ sub action_updatecron { $idx = $self->{cron}->check_entry($id); }; if ($@) { - $self->{logger}->writeLogDebug("[cron] -class- Cron update failed to retrieve entry index"); + $self->{logger}->writeLogError("[cron] Cron update failed to retrieve entry index"); $self->send_log( code => $self->ACTION_FINISH_KO, token => $options{token}, @@ -262,7 +262,7 @@ sub action_updatecron { return 1; } if (!defined($idx)) { - $self->{logger}->writeLogDebug("[cron] -class- Cron update failed no entry found for id"); + $self->{logger}->writeLogError("[cron] Cron update failed no entry found for id"); $self->send_log( code => $self->ACTION_FINISH_KO, token => $options{token}, @@ -274,7 +274,7 @@ sub action_updatecron { my $definition = $options{data}->{content}; if ((!defined($definition->{timespec}) || $definition->{timespec} eq '') && (!defined($definition->{command_line}) || $definition->{command_line} eq '')) { - $self->{logger}->writeLogDebug("[cron] -class- Cron update missing arguments"); + $self->{logger}->writeLogError("[cron] Cron update missing arguments"); $self->send_log( code => $self->ACTION_FINISH_KO, token => $options{token}, @@ -293,7 +293,7 @@ sub action_updatecron { $self->{cron}->update_entry($idx, $entry); }; if ($@) { - $self->{logger}->writeLogDebug("[cron] -class- Cron update failed"); + $self->{logger}->writeLogError("[cron] Cron update failed"); $self->send_log( code => $self->ACTION_FINISH_KO, token => $options{token}, @@ -302,7 +302,7 @@ sub action_updatecron { return 1; } - $self->{logger}->writeLogDebug("[cron] -class- Cron update succeed"); + $self->{logger}->writeLogDebug("[cron] Cron update succeed"); $self->send_log( code => $self->ACTION_FINISH_OK, token => $options{token}, @@ -316,11 +316,11 @@ sub action_deletecron { $options{token} = $self->generate_token() if (!defined($options{token})); - $self->{logger}->writeLogDebug("[cron] -class- Cron delete start"); + $self->{logger}->writeLogDebug("[cron] Cron delete start"); my $id = $options{data}->{variables}[0]; if (!defined($id) || $id eq '') { - $self->{logger}->writeLogDebug("[cron] -class- Cron delete missing id"); + $self->{logger}->writeLogError("[cron] Cron delete missing id"); $self->send_log( code => $self->ACTION_FINISH_KO, token => $options{token}, @@ -334,7 +334,7 @@ sub action_deletecron { $idx = $self->{cron}->check_entry($id); }; if ($@) { - $self->{logger}->writeLogDebug("[cron] -class- Cron delete failed to retrieve entry index"); + $self->{logger}->writeLogError("[cron] Cron delete failed to retrieve entry index"); $self->send_log( code => $self->ACTION_FINISH_KO, token => $options{token}, @@ -343,7 +343,7 @@ sub action_deletecron { return 1; } if (!defined($idx)) { - $self->{logger}->writeLogDebug("[cron] -class- Cron delete failed no entry found for id"); + $self->{logger}->writeLogError("[cron] Cron delete failed no entry found for id"); $self->send_log( code => $self->ACTION_FINISH_KO, token => $options{token}, @@ -356,7 +356,7 @@ sub action_deletecron { $self->{cron}->delete_entry($idx); }; if ($@) { - $self->{logger}->writeLogDebug("[cron] -class- Cron delete failed"); + $self->{logger}->writeLogError("[cron] Cron delete failed"); $self->send_log( code => $self->ACTION_FINISH_KO, token => $options{token}, @@ -365,7 +365,7 @@ sub action_deletecron { return 1; } - $self->{logger}->writeLogDebug("[cron] -class- Cron delete finish"); + $self->{logger}->writeLogDebug("[cron] Cron delete finish"); $self->send_log( code => $self->ACTION_FINISH_OK, token => $options{token}, @@ -378,7 +378,7 @@ sub event { while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); - $connector->{logger}->writeLogDebug("[cron] -class- Event: $message"); + $connector->{logger}->writeLogDebug("[cron] Event: $message"); if ($message =~ /^\[ACK\]\s+\[(.*?)\]\s+(.*)$/m) { my $token = $1; my $data = JSON::XS->new->utf8->decode($2); @@ -403,7 +403,7 @@ sub event { sub cron_sleep { my $rev = zmq_poll($connector->{poll}, 1000); if ($rev == 0 && $connector->{stop} == 1) { - $connector->{logger}->writeLogInfo("[cron] -class- $$ has quit"); + $connector->{logger}->writeLogInfo("[cron] $$ has quit"); zmq_close($connector->{internal_socket}); exit(0); } @@ -412,7 +412,7 @@ sub cron_sleep { sub dispatcher { my ($id, $options) = @_; - $options->{logger}->writeLogInfo("[cron] -class- Launching job '" . $id . "'"); + $options->{logger}->writeLogInfo("[cron] Launching job '" . $id . "'"); my $token = (defined($options->{definition}->{keep_token})) ? $options->{definition}->{id} : undef; diff --git a/gorgone/gorgone/modules/core/cron/hooks.pm b/gorgone/gorgone/modules/core/cron/hooks.pm index 08a28e381f1..0bf38ac4f81 100644 --- a/gorgone/gorgone/modules/core/cron/hooks.pm +++ b/gorgone/gorgone/modules/core/cron/hooks.pm @@ -63,7 +63,7 @@ sub routing { $data = JSON::XS->new->utf8->decode($options{data}); }; if ($@) { - $options{logger}->writeLogError("[cron] -hooks- Cannot decode json data: $@"); + $options{logger}->writeLogError("[cron] Cannot decode json data: $@"); gorgone::standard::library::add_history( dbh => $options{dbh}, code => 10, token => $options{token}, @@ -101,7 +101,7 @@ sub gently { my (%options) = @_; $stop = 1; - $options{logger}->writeLogInfo("[cron] -hooks- Send TERM signal"); + $options{logger}->writeLogDebug("[cron] Send TERM signal"); if ($cron->{running} == 1) { CORE::kill('TERM', $cron->{pid}); } @@ -111,7 +111,7 @@ sub kill { my (%options) = @_; if ($cron->{running} == 1) { - $options{logger}->writeLogInfo("[cron] -hooks- Send KILL signal for pool"); + $options{logger}->writeLogDebug("[cron] Send KILL signal for pool"); CORE::kill('KILL', $cron->{pid}); } } @@ -151,7 +151,7 @@ sub broadcast { sub create_child { my (%options) = @_; - $options{logger}->writeLogInfo("[cron] -hooks- Create module 'cron' process"); + $options{logger}->writeLogInfo("[cron] Create module 'cron' process"); my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-cron'; @@ -163,7 +163,7 @@ sub create_child { $module->run(); exit(0); } - $options{logger}->writeLogInfo("[cron] -hooks- PID $child_pid (gorgone-cron)"); + $options{logger}->writeLogDebug("[cron] PID $child_pid (gorgone-cron)"); $cron = { pid => $child_pid, ready => 0, running => 1 }; } diff --git a/gorgone/gorgone/modules/core/dbcleaner/class.pm b/gorgone/gorgone/modules/core/dbcleaner/class.pm index e617b2ba6c3..c421794950a 100644 --- a/gorgone/gorgone/modules/core/dbcleaner/class.pm +++ b/gorgone/gorgone/modules/core/dbcleaner/class.pm @@ -66,7 +66,7 @@ sub handle_HUP { sub handle_TERM { my $self = shift; - $self->{logger}->writeLogInfo("[dbcleaner] -class- $$ Receiving order to stop..."); + $self->{logger}->writeLogDebug("[dbcleaner] $$ Receiving order to stop..."); $self->{stop} = 1; } @@ -99,12 +99,12 @@ sub action_dbclean { } ) if (!defined($options{cycle})); - $self->{logger}->writeLogInfo("[dbcleaner] -class- purge db in progress..."); + $self->{logger}->writeLogDebug("[dbcleaner] Purge database in progress..."); my ($status) = $self->{db_gorgone}->query("DELETE FROM gorgone_identity WHERE `ctime` < " . $self->{db_gorgone}->quote(time() - $self->{config}->{purge_sessions_time})); my ($status2) = $self->{db_gorgone}->query("DELETE FROM gorgone_history WHERE `ctime` < " . $self->{db_gorgone}->quote(time() - $self->{config}->{purge_history_time})); $self->{purge_timer} = time(); - $self->{logger}->writeLogDebug("[dbcleaner] -class- finish dbclean"); + $self->{logger}->writeLogDebug("[dbcleaner] Purge finished"); if ($status == -1 || $status2 == -1) { $self->send_log( @@ -131,7 +131,7 @@ sub event { while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); - $connector->{logger}->writeLogDebug("[dbcleaner] -class- Event: $message"); + $connector->{logger}->writeLogDebug("[dbcleaner] Event: $message"); if ($message =~ /^\[(.*?)\]/) { if ((my $method = $connector->can('action_' . lc($1)))) { $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; @@ -183,7 +183,7 @@ sub run { # we try to do all we can my $rev = zmq_poll($self->{poll}, 5000); if (defined($rev) && $rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("[dbcleaner] -class- $$ has quit"); + $self->{logger}->writeLogInfo("[dbcleaner] $$ has quit"); zmq_close($connector->{internal_socket}); exit(0); } diff --git a/gorgone/gorgone/modules/core/dbcleaner/hooks.pm b/gorgone/gorgone/modules/core/dbcleaner/hooks.pm index d8d1b576058..70766aeca30 100644 --- a/gorgone/gorgone/modules/core/dbcleaner/hooks.pm +++ b/gorgone/gorgone/modules/core/dbcleaner/hooks.pm @@ -70,7 +70,7 @@ sub routing { $data = JSON::XS->new->utf8->decode($options{data}); }; if ($@) { - $options{logger}->writeLogError("[dbcleaner] -hooks- Cannot decode json data: $@"); + $options{logger}->writeLogError("[dbcleaner] Cannot decode json data: $@"); gorgone::standard::library::add_history( dbh => $options{dbh}, code => 10, token => $options{token}, @@ -108,7 +108,7 @@ sub gently { my (%options) = @_; $stop = 1; - $options{logger}->writeLogInfo("[dbcleaner] -hooks- Send TERM signal"); + $options{logger}->writeLogDebug("[dbcleaner] Send TERM signal"); if ($dbcleaner->{running} == 1) { CORE::kill('TERM', $dbcleaner->{pid}); } @@ -118,7 +118,7 @@ sub kill { my (%options) = @_; if ($dbcleaner->{running} == 1) { - $options{logger}->writeLogInfo("[dbcleaner] -hooks- Send KILL signal for pool"); + $options{logger}->writeLogDebug("[dbcleaner] Send KILL signal for pool"); CORE::kill('KILL', $dbcleaner->{pid}); } } @@ -158,7 +158,7 @@ sub broadcast { sub create_child { my (%options) = @_; - $options{logger}->writeLogInfo("[dbcleaner] -hooks- Create module 'dbcleaner' process"); + $options{logger}->writeLogInfo("[dbcleaner] Create module 'dbcleaner' process"); my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-dbcleaner'; @@ -170,7 +170,7 @@ sub create_child { $module->run(); exit(0); } - $options{logger}->writeLogInfo("[dbcleaner] -hooks- PID $child_pid (gorgone-dbcleaner)"); + $options{logger}->writeLogDebug("[dbcleaner] PID $child_pid (gorgone-dbcleaner)"); $dbcleaner = { pid => $child_pid, ready => 0, running => 1 }; } diff --git a/gorgone/gorgone/modules/core/httpserver/class.pm b/gorgone/gorgone/modules/core/httpserver/class.pm index 577f2d2d79e..e0e1116897a 100644 --- a/gorgone/gorgone/modules/core/httpserver/class.pm +++ b/gorgone/gorgone/modules/core/httpserver/class.pm @@ -92,7 +92,7 @@ sub handle_HUP { sub handle_TERM { my $self = shift; - $self->{logger}->writeLogInfo("[httpserver] -class- $$ Receiving order to stop..."); + $self->{logger}->writeLogDebug("[httpserver] $$ Receiving order to stop..."); $self->{stop} = 1; } @@ -112,7 +112,7 @@ sub event { while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); - $connector->{logger}->writeLogDebug("[httpserver] -class- Event: $message"); + $connector->{logger}->writeLogDebug("[httpserver] Event: $message"); if ($message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/) { if ((my $method = $connector->can('action_' . lc($1)))) { @@ -156,7 +156,7 @@ sub load_peer_subnets { foreach (@{$connector->{config}->{allowed_hosts}->{subnets}}) { my $subnet = NetAddr::IP->new($_); if (!defined($subnet)) { - $self->{logger}->writeLogError("[httpserver] -class- cannot load subnet: $_"); + $self->{logger}->writeLogError("[httpserver] Cannot load subnet: $_"); next; } @@ -218,7 +218,7 @@ sub run { my ($connection) = $daemon->accept(); if ($self->{stop} == 1) { - $self->{logger}->writeLogInfo("[httpserver] -class- $$ has quit"); + $self->{logger}->writeLogInfo("[httpserver] $$ has quit"); $connection->close() if (defined($connection)); zmq_close($connector->{internal_socket}); exit(0); @@ -227,11 +227,11 @@ sub run { next if (!defined($connection)); while (my $request = $connection->get_request) { - $connector->{logger}->writeLogInfo("[httpserver] -class- " . $connection->peerhost() . " " . $request->method . " '" . $request->uri->path . "' '" . $request->header("User-Agent") . "'"); + $connector->{logger}->writeLogInfo("[httpserver] " . $connection->peerhost() . " " . $request->method . " '" . $request->uri->path . "' '" . $request->header("User-Agent") . "'"); if ($connector->{allowed_hosts_enabled} == 1) { if ($connector->check_allowed_host(peer_addr => inet_ntoa($connection->peeraddr())) == 0) { - $connector->{logger}->writeLogError("[httpserver] -class- " . $connection->peerhost() . " Unauthorized"); + $connector->{logger}->writeLogError("[httpserver] " . $connection->peerhost() . " Unauthorized"); $self->send_error( connection => $connection, code => "401", @@ -249,7 +249,7 @@ sub run { } elsif (defined($self->{dispatch}->{$root})) { # Other dispatch definition $self->send_response(connection => $connection, response => $self->dispatch_call(root => $root, request => $request)); } else { # Forbidden - $connector->{logger}->writeLogError("[httpserver] -class- " . $connection->peerhost() . " '" . $request->uri->path . "' Forbidden"); + $connector->{logger}->writeLogError("[httpserver] " . $connection->peerhost() . " '" . $request->uri->path . "' Forbidden"); $self->send_error( connection => $connection, code => "403", @@ -257,7 +257,7 @@ sub run { ); } } else { # Authen error - $connector->{logger}->writeLogError("[httpserver] -class- " . $connection->peerhost() . " Unauthorized"); + $connector->{logger}->writeLogError("[httpserver] " . $connection->peerhost() . " Unauthorized"); $self->send_error( connection => $connection, code => "401", diff --git a/gorgone/gorgone/modules/core/httpserver/hooks.pm b/gorgone/gorgone/modules/core/httpserver/hooks.pm index 01ef4461aa2..bbc18f4ef1c 100644 --- a/gorgone/gorgone/modules/core/httpserver/hooks.pm +++ b/gorgone/gorgone/modules/core/httpserver/hooks.pm @@ -47,11 +47,11 @@ sub register { $config->{port} = defined($config->{port}) && $config->{port} =~ /(\d+)/ ? $1 : 8080; if (defined($config->{auth}->{enabled}) && $config->{auth}->{enabled} eq 'true') { if (!defined($config->{auth}->{user}) || $config->{auth}->{user} =~ /^\s*$/) { - $options{logger}->writeLogError('[httpserver] -hooks- user option mandatory if auth enabled'); + $options{logger}->writeLogError('[httpserver] User option mandatory if authentication is enabled'); $loaded = 0; } if (!defined($config->{auth}->{password}) || $config->{auth}->{password} =~ /^\s*$/) { - $options{logger}->writeLogError('[httpserver] -hooks- password option mandatory if auth enabled'); + $options{logger}->writeLogError('[httpserver] Password option mandatory if authentication is enabled'); $loaded = 0; } } @@ -73,7 +73,7 @@ sub routing { $data = JSON::XS->new->utf8->decode($options{data}); }; if ($@) { - $options{logger}->writeLogError("[httpserver] -hooks- Cannot decode json data: $@"); + $options{logger}->writeLogError("[httpserver] Cannot decode json data: $@"); gorgone::standard::library::add_history( dbh => $options{dbh}, code => 10, @@ -113,7 +113,7 @@ sub gently { my (%options) = @_; $stop = 1; - $options{logger}->writeLogInfo("[httpserver] -hooks- Send TERM signal"); + $options{logger}->writeLogDebug("[httpserver] Send TERM signal"); if ($httpserver->{running} == 1) { CORE::kill('TERM', $httpserver->{pid}); } @@ -123,7 +123,7 @@ sub kill { my (%options) = @_; if ($httpserver->{running} == 1) { - $options{logger}->writeLogInfo("[httpserver] -hooks- Send KILL signal for pool"); + $options{logger}->writeLogDebug("[httpserver] Send KILL signal for pool"); CORE::kill('KILL', $httpserver->{pid}); } } @@ -159,7 +159,7 @@ sub broadcast {} sub create_child { my (%options) = @_; - $options{logger}->writeLogInfo("[httpserver] -hooks- Create module 'httpserver' process"); + $options{logger}->writeLogInfo("[httpserver] Create module 'httpserver' process"); my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-httpserver'; @@ -172,7 +172,7 @@ sub create_child { $module->run(); exit(0); } - $options{logger}->writeLogInfo("[httpserver] -hooks- PID $child_pid (gorgone-httpserver)"); + $options{logger}->writeLogDebug("[httpserver] PID $child_pid (gorgone-httpserver)"); $httpserver = { pid => $child_pid, ready => 0, running => 1 }; } diff --git a/gorgone/gorgone/modules/core/proxy/class.pm b/gorgone/gorgone/modules/core/proxy/class.pm index 78ec8bef610..6e46b61db28 100644 --- a/gorgone/gorgone/modules/core/proxy/class.pm +++ b/gorgone/gorgone/modules/core/proxy/class.pm @@ -69,7 +69,7 @@ sub handle_HUP { sub handle_TERM { my $self = shift; - $self->{logger}->writeLogInfo("[proxy] -class- $$ Receiving order to stop..."); + $self->{logger}->writeLogInfo("[proxy] $$ Receiving order to stop..."); $self->{stop} = 1; } @@ -197,11 +197,11 @@ sub action_proxyaddnode { } if ($changed == 0) { - $self->{logger}->writeLogInfo("[proxy] -class- session not changed $data->{id}"); + $self->{logger}->writeLogInfo("[proxy] Session not changed $data->{id}"); return ; } - $self->{logger}->writeLogInfo("[proxy] -class- recreate session for $data->{id}"); + $self->{logger}->writeLogInfo("[proxy] Recreate session for $data->{id}"); # we send a pong reset. because the ping can be lost $self->send_internal_action( action => 'PONGRESET', @@ -235,7 +235,7 @@ sub close_connections { foreach (keys %{$self->{clients}}) { if (defined($self->{clients}->{$_}->{class})) { - $self->{logger}->writeLogInfo("[proxy] -class- close connection for $_"); + $self->{logger}->writeLogInfo("[proxy] Close connection for $_"); $self->{clients}->{$_}->{class}->close(); } } @@ -249,7 +249,7 @@ sub proxy { } my ($action, $token, $target_complete, $data) = ($1, $2, $3, $4); $connector->{logger}->writeLogDebug( - "[proxy] -class- Send message: [action = $action] [token = $token] [target = $target_complete] [data = $data]" + "[proxy] Send message: [action = $action] [token = $token] [target = $target_complete] [data = $data]" ); if ($action eq 'PROXYADDNODE') { @@ -307,7 +307,7 @@ sub proxy { message => "Send message problem for '$target': $msg" } ); - $connector->{logger}->writeLogError("[proxy] -class- Send message problem for '$target': $msg"); + $connector->{logger}->writeLogError("[proxy] Send message problem for '$target': $msg"); $connector->{clients}->{$target_client}->{delete} = 1; } } elsif ($connector->{clients}->{$target_client}->{type} eq 'push_ssh') { @@ -332,7 +332,7 @@ sub proxy { target => $target ); - $connector->{logger}->writeLogDebug("[proxy] -class- sshclient return: [message = $data_ret->{message}]"); + $connector->{logger}->writeLogDebug("[proxy] Sshclient return: [message = $data_ret->{message}]"); if ($status == 0) { $connector->send_log( code => gorgone::class::module::ACTION_FINISH_OK, @@ -409,7 +409,7 @@ sub run { next if (!defined($rev)); if ($rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("[proxy] -class- $$ has quit"); + $self->{logger}->writeLogInfo("[proxy] $$ has quit"); $self->close_connections(); zmq_close($connector->{internal_socket}); exit(0); diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index 86b541bc495..b192554b3cc 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -119,7 +119,7 @@ sub routing { $data = JSON::XS->new->utf8->decode($options{data}); }; if ($@) { - $options{logger}->writeLogError("[proxy] -hooks- Cannot decode json data: $@"); + $options{logger}->writeLogError("[proxy] Cannot decode json data: $@"); gorgone::standard::library::add_history( dbh => $options{dbh}, code => 20, token => $options{token}, @@ -137,14 +137,14 @@ sub routing { $constatus_ping->{$data->{data}->{id}}->{last_ping_recv} = time(); $constatus_ping->{$data->{data}->{id}}->{nodes} = $data->{data}->{data}; register_subnodes(%options, id => $data->{data}->{id}, subnodes => $data->{data}->{data}); - $options{logger}->writeLogInfo("[proxy] -hooks- Pong received from '" . $data->{data}->{id} . "'"); + $options{logger}->writeLogInfo("[proxy] Pong received from '" . $data->{data}->{id} . "'"); return undef; } if ($options{action} eq 'PONGRESET') { return undef if (!defined($data->{id}) || $data->{id} eq ''); $synctime_nodes->{$data->{id}}->{in_progress_ping} = 0; - $options{logger}->writeLogInfo("[proxy] -hooks- PongReset received from '" . $data->{id} . "'"); + $options{logger}->writeLogInfo("[proxy] PongReset received from '" . $data->{id} . "'"); return undef; } @@ -289,7 +289,7 @@ sub gently { $stop = 1; foreach my $pool_id (keys %{$pools}) { - $options{logger}->writeLogInfo("[proxy] -hooks- Send TERM signal for pool '" . $pool_id . "'"); + $options{logger}->writeLogDebug("[proxy] Send TERM signal for pool '" . $pool_id . "'"); if ($pools->{$pool_id}->{running} == 1) { CORE::kill('TERM', $pools->{$pool_id}->{pid}); } @@ -301,7 +301,7 @@ sub kill { foreach (keys %{$pools}) { if ($pools->{$_}->{running} == 1) { - $options{logger}->writeLogInfo("[proxy] -hooks- Send KILL signal for pool '" . $_ . "'"); + $options{logger}->writeLogDebug("[proxy] Send KILL signal for pool '" . $_ . "'"); CORE::kill('KILL', $pools->{$_}->{pid}); } } @@ -372,7 +372,7 @@ sub check { if ($stop == 0 && time() - $ping_time > $ping_option) { - $options{logger}->writeLogInfo("[proxy] -hooks- Send pings"); + $options{logger}->writeLogInfo("[proxy] Send pings"); $ping_time = time(); ping_send(dbh => $options{dbh}, logger => $options{logger}); } @@ -431,11 +431,11 @@ sub pathway { foreach (@targets) { if (!defined($last_pong->{$_}) || $last_pong->{$_} == 0 || (time() - $config->{pong_discard_timeout} < $last_pong->{$_})) { - $options{logger}->writeLogDebug("[proxy] -hooks- choose node target '$_' for node '$target'"); + $options{logger}->writeLogDebug("[proxy] Choose node target '$_' for node '$target'"); return (1, $_ . '~~' . $target, $_, $target); } - $options{logger}->writeLogDebug("[proxy] -hooks- skip node target '$_' for node '$target'"); + $options{logger}->writeLogDebug("[proxy] Skip node target '$_' for node '$target'"); } gorgone::standard::library::add_history( @@ -469,7 +469,7 @@ sub setlogs { return undef; } - $options{logger}->writeLogInfo("[proxy] -hooks- Received setlogs for '$options{data}->{data}->{id}'"); + $options{logger}->writeLogInfo("[proxy] Received setlogs for '$options{data}->{data}->{id}'"); $synctime_nodes->{$options{data}->{data}->{id}}->{in_progress} = 0; @@ -605,11 +605,11 @@ sub create_child { my (%options) = @_; if (!defined($core_id) || $core_id =~ /^\s*$/) { - $options{logger}->writeLogError("[proxy] -hooks- Cannot create child. need a core id"); + $options{logger}->writeLogError("[proxy] Cannot create child, need a core id"); return ; } - $options{logger}->writeLogInfo("[proxy] -hooks- Create module 'proxy' child process for pool id '" . $options{pool_id} . "'"); + $options{logger}->writeLogInfo("[proxy] Create module 'proxy' child process for pool id '" . $options{pool_id} . "'"); my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-proxy'; @@ -624,7 +624,7 @@ sub create_child { $module->run(); exit(0); } - $options{logger}->writeLogInfo("[proxy] -hooks- PID $child_pid (gorgone-proxy) for pool id '" . $options{pool_id} . "'"); + $options{logger}->writeLogDebug("[proxy] PID $child_pid (gorgone-proxy) for pool id '" . $options{pool_id} . "'"); $pools->{$options{pool_id}} = { pid => $child_pid, ready => 0, running => 1 }; $pools_pid->{$child_pid} = $options{pool_id}; } @@ -679,7 +679,7 @@ sub unregister_nodes { routing(socket => $internal_socket, action => 'PROXYDELNODE', target => $node->{id}, data => JSON::XS->new->utf8->encode($node), dbh => $options{dbh}, logger => $options{logger}); } - $options{logger}->writeLogInfo("[proxy] -hooks- node '" . $node->{id} . "' is unregistered"); + $options{logger}->writeLogInfo("[proxy] Node '" . $node->{id} . "' is unregistered"); if (defined($register_nodes->{$node->{id}}) && $register_nodes->{$node->{id}}->{nodes}) { foreach my $subnode (@{$register_nodes->{$node->{id}}->{nodes}}) { delete $register_subnodes->{ $subnode->{id} }->{static}->{ $node->{id} } @@ -760,7 +760,7 @@ sub register_nodes { $constatus_ping->{$node->{id}} = { type => $node->{type}, last_ping_sent => 0, last_ping_recv => 0, nodes => {} }; # we provide information to the proxy class ping_send(node_id => $node->{id}, dbh => $options{dbh}, logger => $options{logger}); - $options{logger}->writeLogInfo("[proxy] -hooks- node '" . $node->{id} . "' is registered"); + $options{logger}->writeLogInfo("[proxy] Node '" . $node->{id} . "' is registered"); } } } @@ -771,7 +771,7 @@ sub prepare_remote_copy { my @actions; if (!defined($options{data}->{content}->{source}) || $options{data}->{content}->{source} eq '') { - $options{logger}->writeLogError('[proxy] -hooks- prepare_remote_copy: need source'); + $options{logger}->writeLogError('[proxy] Need source for remote copy'); gorgone::standard::library::add_history( dbh => $options{dbh}, code => gorgone::class::module::ACTION_FINISH_KO, @@ -782,7 +782,7 @@ sub prepare_remote_copy { return -1; } if (!defined($options{data}->{content}->{destination}) || $options{data}->{content}->{destination} eq '') { - $options{logger}->writeLogError('[proxy] -hooks- prepare_remote_copy: need destination'); + $options{logger}->writeLogError('[proxy] Need destination for remote copy'); gorgone::standard::library::add_history( dbh => $options{dbh}, code => gorgone::class::module::ACTION_FINISH_KO, @@ -816,7 +816,7 @@ sub prepare_remote_copy { redirect_stderr => 1, ); if ($error <= -1000) { - $options{logger}->writeLogError("[proxy] -hooks- prepare_remote_copy: tar failed: $stdout"); + $options{logger}->writeLogError("[proxy] Tar failed: $stdout"); gorgone::standard::library::add_history( dbh => $options{dbh}, code => gorgone::class::module::ACTION_FINISH_KO, @@ -827,7 +827,7 @@ sub prepare_remote_copy { return -1; } if ($exit_code != 0) { - $options{logger}->writeLogError("[proxy] -hooks- prepare_remote_copy: tar failed ($exit_code): $stdout"); + $options{logger}->writeLogError("[proxy] Tar failed ($exit_code): $stdout"); gorgone::standard::library::add_history( dbh => $options{dbh}, code => gorgone::class::module::ACTION_FINISH_KO, @@ -838,7 +838,7 @@ sub prepare_remote_copy { return -1; }; } else { - $options{logger}->writeLogError('[proxy] -hooks- prepare_remote_copy: unknown source'); + $options{logger}->writeLogError('[proxy] Unknown source for remote copy'); gorgone::standard::library::add_history( dbh => $options{dbh}, code => gorgone::class::module::ACTION_FINISH_KO, @@ -894,7 +894,7 @@ sub setcoreid { sub add_parent_ping { my (%options) = @_; - $options{logger}->writeLogDebug("[proxy] -hooks- parent ping '" . $options{identity} . "' is registered"); + $options{logger}->writeLogDebug("[proxy] Parent ping '" . $options{identity} . "' is registered"); $parent_ping->{$options{identity}} = { last_time => time(), router_type => $options{router_type} }; } diff --git a/gorgone/gorgone/modules/core/proxy/sshclient.pm b/gorgone/gorgone/modules/core/proxy/sshclient.pm index af0439d425c..03ed3d7c91c 100644 --- a/gorgone/gorgone/modules/core/proxy/sshclient.pm +++ b/gorgone/gorgone/modules/core/proxy/sshclient.pm @@ -46,32 +46,32 @@ sub open_session { $self->{save_options} = { %options }; if ($self->options(host => $options{ssh_host}, port => $options{ssh_port}, user => $options{ssh_username}) != Libssh::Session::SSH_OK) { - $self->{logger}->writeLogError('[proxy] -sshclient- options method: ' . $self->error()); + $self->{logger}->writeLogError('[sshclient] Options method: ' . $self->error()); return -1; } if ($self->connect(SkipKeyProblem => $options{strict_serverkey_check}) != Libssh::Session::SSH_OK) { - $self->{logger}->writeLogError('[proxy] -sshclient- connect method: ' . $self->error()); + $self->{logger}->writeLogError('[sshclient] Connect method: ' . $self->error()); return -1; } if ($self->auth_publickey_auto() != Libssh::Session::SSH_AUTH_SUCCESS) { - $self->{logger}->writeLogInfo('[proxy] -sshclient- auth publickey auto failure: ' . $self->error(GetErrorSession => 1)); + $self->{logger}->writeLogInfo('[sshclient] Auth publickey auto failure: ' . $self->error(GetErrorSession => 1)); if (!defined($options{ssh_password}) || $options{ssh_password} eq '') { - $self->{logger}->writeLogError('[proxy] -sshclient- auth issue: no password'); + $self->{logger}->writeLogError('[sshclient] Auth issue: no password'); return -1; } if ($self->auth_password(password => $options{ssh_password}) != Libssh::Session::SSH_AUTH_SUCCESS) { - $self->{logger}->writeLogError('[proxy] -sshclient- auth issue: ' . $self->error(GetErrorSession => 1)); + $self->{logger}->writeLogError('[sshclient] Auth issue: ' . $self->error(GetErrorSession => 1)); return -1; } } - $self->{logger}->writeLogInfo('[proxy] -sshclient- authentification succeed'); + $self->{logger}->writeLogInfo('[sshclient] Authentification succeed'); $self->{sftp} = Libssh::Sftp->new(session => $self); if (!defined($self->{sftp})) { - $self->{logger}->writeLogError('[proxy] -sshclient- cannot init sftp: ' . Libssh::Sftp::error()); + $self->{logger}->writeLogError('[sshclient] Cannot init sftp: ' . Libssh::Sftp::error()); return -1; } @@ -111,11 +111,11 @@ sub action_centcore { my ($self, %options) = @_; if (!defined($options{data}->{content}->{command}) || $options{data}->{content}->{command} eq '') { - $self->{logger}->writeLogError('[proxy] -sshclient- action_centcore: need command'); + $self->{logger}->writeLogError('[sshclient] Action centcore - Need command'); return (-1, { message => 'please set command' }); } if (!defined($options{data}->{content}->{target}) || $options{data}->{content}->{target} eq '') { - $self->{logger}->writeLogError('[proxy] -sshclient- action_centcore: need target'); + $self->{logger}->writeLogError('[sshclient] Action centcore - Need target'); return (-1, { message => 'please set target' }); } @@ -136,7 +136,7 @@ sub action_centcore { return (-1, { message => "cannot write stat file '$centcore_cmd': " . $self->{sftp}->error() }); } - $self->{logger}->writeLogDebug("[proxy] -sshclient- action_centcore '" . $centcore_cmd . "' succeeded"); + $self->{logger}->writeLogDebug("[sshclient] Action centcore - '" . $centcore_cmd . "' succeeded"); return (0, { message => 'send action_centcore succeeded' }); } @@ -144,7 +144,7 @@ sub action_command { my ($self, %options) = @_; if (!defined($options{data}->{content}->{command}) || $options{data}->{content}->{command} eq '') { - $self->{logger}->writeLogError('[proxy] -sshclient- action_command: need command'); + $self->{logger}->writeLogError('[sshclient] Action command - Need command'); return (-1, { message => 'please set command' }); } @@ -186,11 +186,11 @@ sub action_enginecommand { my ($self, %options) = @_; if (!defined($options{data}->{content}->{command}) || $options{data}->{content}->{command} eq '') { - $self->{logger}->writeLogError('[proxy] -sshclient- action_enginecommand: need command'); + $self->{logger}->writeLogError('[sshclient] Action engine command - Need command'); return (-1, { message => 'please set command' }); } if (!defined($options{data}->{content}->{command_file}) || $options{data}->{content}->{command_file} eq '') { - $self->{logger}->writeLogError('[proxy] -sshclient- action_enginecommand: need command_file'); + $self->{logger}->writeLogError('[sshclient] Action engine command - Need command_file'); return (-1, { message => 'please set command_file' }); } @@ -224,7 +224,7 @@ sub action_enginecommand { return (-1, { message => "cannot write stat file '$options{data}->{content}->{command_file}': " . $self->{sftp}->error() }); } - $self->{logger}->writeLogDebug("[proxy] -sshclient- action_enginecommand '" . $options{data}->{content}->{command} . "' succeeded"); + $self->{logger}->writeLogDebug("[sshclient] Action engine command - '" . $options{data}->{content}->{command} . "' succeeded"); return (0, { message => 'send enginecommand succeeded' }); } @@ -232,11 +232,11 @@ sub action_remotecopy { my ($self, %options) = @_; if (!defined($options{data}->{content}->{source}) || $options{data}->{content}->{source} eq '') { - $self->{logger}->writeLogError('[proxy] -sshclient- action_remotecopy: need source'); + $self->{logger}->writeLogError('[sshclient] Action remote copy - Need source'); return (-1, { message => 'please set source' }); } if (!defined($options{data}->{content}->{destination}) || $options{data}->{content}->{destination} eq '') { - $self->{logger}->writeLogError('[proxy] -sshclient- action_remotecopy: need destination'); + $self->{logger}->writeLogError('[sshclient] Action remote copy - Need destination'); return (-1, { message => 'please set destination' }); } @@ -307,7 +307,7 @@ sub action { ); } - $self->{logger}->writeLogError('[proxy] -sshclient- unsupported action ' . $options{action}); + $self->{logger}->writeLogError("[sshclient] Unsupported action '" . $options{action} . "'"); return (-1, { message => 'unsupported action' }); } diff --git a/gorgone/gorgone/modules/core/pull/hooks.pm b/gorgone/gorgone/modules/core/pull/hooks.pm index c8ac53d34a0..423a74b4972 100644 --- a/gorgone/gorgone/modules/core/pull/hooks.pm +++ b/gorgone/gorgone/modules/core/pull/hooks.pm @@ -165,7 +165,7 @@ sub from_router { my $message = transmit_back(message => gorgone::standard::library::zmq_dealer_read_message(socket => $socket_to_internal)); # Only send back SETLOGS and PONG if (defined($message)) { - $logger->writeLogDebug("[pull] -hooks- Read message from internal: $message"); + $logger->writeLogDebug("[pull] Read message from internal: $message"); $client->send_message(message => $message); } last unless (gorgone::standard::library::zmq_still_read(socket => $socket_to_internal)); @@ -180,7 +180,7 @@ sub read_message { return undef; } - $logger->writeLogDebug("[pull] -hooks- Read message from external: $options{data}"); + $logger->writeLogDebug("[pull] Read message from external: $options{data}"); gorgone::standard::library::zmq_send_message( socket => $socket_to_internal, message => $options{data} diff --git a/gorgone/gorgone/modules/core/register/class.pm b/gorgone/gorgone/modules/core/register/class.pm index abd68c48ff6..79f276ae790 100644 --- a/gorgone/gorgone/modules/core/register/class.pm +++ b/gorgone/gorgone/modules/core/register/class.pm @@ -65,7 +65,7 @@ sub handle_HUP { sub handle_TERM { my $self = shift; - $self->{logger}->writeLogInfo("[register] -class- $$ Receiving order to stop..."); + $self->{logger}->writeLogInfo("[register] $$ Receiving order to stop..."); $self->{stop} = 1; } @@ -131,7 +131,7 @@ sub action_registerresync { } ) if (scalar(@$unregister_nodes) > 0); - $self->{logger}->writeLogDebug("[register] -class- finish resync"); + $self->{logger}->writeLogDebug("[register] Finish resync"); $self->send_log( code => $self->ACTION_FINISH_OK, token => $options{token}, @@ -146,7 +146,7 @@ sub event { while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); - $connector->{logger}->writeLogDebug("[register] -class- Event: $message"); + $connector->{logger}->writeLogDebug("[register] Event: $message"); if ($message =~ /^\[(.*?)\]/) { if ((my $method = $connector->can('action_' . lc($1)))) { $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; @@ -188,7 +188,7 @@ sub run { # we try to do all we can my $rev = zmq_poll($self->{poll}, 5000); if (defined($rev) && $rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("[register] -class- $$ has quit"); + $self->{logger}->writeLogInfo("[register] $$ has quit"); zmq_close($connector->{internal_socket}); exit(0); } diff --git a/gorgone/gorgone/modules/core/register/hooks.pm b/gorgone/gorgone/modules/core/register/hooks.pm index 35cf2465fd8..bbdaad3c8f5 100644 --- a/gorgone/gorgone/modules/core/register/hooks.pm +++ b/gorgone/gorgone/modules/core/register/hooks.pm @@ -45,7 +45,7 @@ sub register { $config = $options{config}; $config_core = $options{config_core}; if (!defined($config->{config_file}) || $config->{config_file} =~ /^\s*$/) { - $options{logger}->writeLogError('[register] -hooks- config_file option mandatory'); + $options{logger}->writeLogError("[register] Option 'config_file' mandatory"); $loaded = 0; } return ($loaded, NAMESPACE, NAME, EVENTS); @@ -65,7 +65,7 @@ sub routing { $data = JSON::XS->new->utf8->decode($options{data}); }; if ($@) { - $options{logger}->writeLogError("[register] -hooks- Cannot decode json data: $@"); + $options{logger}->writeLogError("[register] Cannot decode json data: $@"); gorgone::standard::library::add_history( dbh => $options{dbh}, code => 10, token => $options{token}, @@ -103,7 +103,7 @@ sub gently { my (%options) = @_; $stop = 1; - $options{logger}->writeLogInfo("[register] -hooks- Send TERM signal"); + $options{logger}->writeLogDebug("[register] Send TERM signal"); if ($register->{running} == 1) { CORE::kill('TERM', $register->{pid}); } @@ -113,7 +113,7 @@ sub kill { my (%options) = @_; if ($register->{running} == 1) { - $options{logger}->writeLogInfo("[register] -hooks- Send KILL signal for pool"); + $options{logger}->writeLogDebug("[register] Send KILL signal for pool"); CORE::kill('KILL', $register->{pid}); } } @@ -153,7 +153,7 @@ sub broadcast { sub create_child { my (%options) = @_; - $options{logger}->writeLogInfo("[register] -hooks- Create module 'register' process"); + $options{logger}->writeLogInfo("[register] Create module 'register' process"); my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-register'; @@ -165,7 +165,7 @@ sub create_child { $module->run(); exit(0); } - $options{logger}->writeLogInfo("[register] -hooks- PID $child_pid (gorgone-register)"); + $options{logger}->writeLogDebug("[register] PID $child_pid (gorgone-register)"); $register = { pid => $child_pid, ready => 0, running => 1 }; } diff --git a/gorgone/gorgone/modules/plugins/newtest/class.pm b/gorgone/gorgone/modules/plugins/newtest/class.pm index b53e944fdf9..ee3c0905169 100644 --- a/gorgone/gorgone/modules/plugins/newtest/class.pm +++ b/gorgone/gorgone/modules/plugins/newtest/class.pm @@ -98,7 +98,7 @@ sub handle_HUP { sub handle_TERM { my $self = shift; - $self->{logger}->writeLogInfo("[newtest] -class- $$ Receiving order to stop..."); + $self->{logger}->writeLogInfo("[newtest] $$ Receiving order to stop..."); $self->{stop} = 1; } @@ -189,12 +189,12 @@ sub get_poller_id { mode => 2 ); if ($status == -1) { - $self->{logger}->writeLogError("[newtest] -class- cannot get poller id for poller '" . $self->{poller_name} . "'."); + $self->{logger}->writeLogError("[newtest] cannot get poller id for poller '" . $self->{poller_name} . "'."); return 1; } if (!defined($datas->[0])) { - $self->{logger}->writeLogError("[newtest] -class- cannot find poller id for poller '" . $self->{poller_name} . "'."); + $self->{logger}->writeLogError("[newtest] cannot find poller id for poller '" . $self->{poller_name} . "'."); return 1; } @@ -219,7 +219,7 @@ sub get_centreondb_cache { mode => 2 ); if ($status == -1) { - $self->{logger}->writeLogError("[newtest] -class- cannot get robot/scenarios list from centreon db."); + $self->{logger}->writeLogError("[newtest] cannot get robot/scenarios list from centreon db."); return 1; } @@ -245,17 +245,17 @@ sub get_centstoragedb_cache { mode => 2 ); if ($status == -1) { - $self->{logger}->writeLogError("[newtest] -class- cannot get robot/scenarios list from centstorage db."); + $self->{logger}->writeLogError("[newtest] cannot get robot/scenarios list from centstorage db."); return 1; } foreach (@$datas) { if (!defined($self->{db_newtest}->{$_->[0]})) { - $self->{logger}->writeLogError("[newtest] -class- host '" . $_->[0] . "'is in censtorage DB but not in centreon config..."); + $self->{logger}->writeLogError("[newtest] host '" . $_->[0] . "'is in censtorage DB but not in centreon config..."); next; } if (defined($_->[1]) && !defined($self->{db_newtest}->{$_->[0]}->{$_->[1]})) { - $self->{logger}->writeLogError("[newtest] -class- host scenario '" . $_->[0] . "/" . $_->[1] . "' is in censtorage DB but not in centreon config..."); + $self->{logger}->writeLogError("[newtest] host scenario '" . $_->[0] . "/" . $_->[1] . "' is in censtorage DB but not in centreon config..."); next; } @@ -278,7 +278,7 @@ sub clapi_execute { wait_exit => 1, ); if ($lerror == -1 || ($exit_code >> 8) != 0) { - $self->{logger}->writeLogError("[newtest] -class- clapi execution problem for command $cmd : " . $stdout); + $self->{logger}->writeLogError("[newtest] clapi execution problem for command $cmd : " . $stdout); return -1; } @@ -303,7 +303,7 @@ sub submit_external_cmd { wait_exit => 1 ); if ($lerror == -1 || ($exit_code >> 8) != 0) { - $self->{logger}->writeLogError("[newtest] -class- clapi execution problem for command $cmd : " . $stdout); + $self->{logger}->writeLogError("[newtest] clapi execution problem for command $cmd : " . $stdout); return -1; } } @@ -313,29 +313,29 @@ sub push_config { my ($self, %options) = @_; if ($self->{must_push_config} == 1) { - $self->{logger}->writeLogInfo("[newtest] -class- generation config for '$self->{poller_name}':"); + $self->{logger}->writeLogInfo("[newtest] generation config for '$self->{poller_name}':"); if ($self->clapi_execute(cmd => '-a POLLERGENERATE -v ' . $self->{poller_id}, timeout => $self->{clapi_generate_config_timeout}) != 0) { - $self->{logger}->writeLogError("[newtest] -class- generation config for '$self->{poller_name}': failed"); + $self->{logger}->writeLogError("[newtest] generation config for '$self->{poller_name}': failed"); return ; } - $self->{logger}->writeLogInfo("[newtest] -class- generation config for '$self->{poller_name}': succeeded."); + $self->{logger}->writeLogInfo("[newtest] generation config for '$self->{poller_name}': succeeded."); - $self->{logger}->writeLogInfo("[newtest] -class- move config for '$self->{poller_name}':"); + $self->{logger}->writeLogInfo("[newtest] move config for '$self->{poller_name}':"); if ($self->clapi_execute(cmd => '-a CFGMOVE -v ' . $self->{poller_id}, timeout => $self->{clapi_timeout}) != 0) { - $self->{logger}->writeLogError("[newtest] -class- move config for '$self->{poller_name}': failed"); + $self->{logger}->writeLogError("[newtest] move config for '$self->{poller_name}': failed"); return ; } - $self->{logger}->writeLogInfo("[newtest] -class- move config for '$self->{poller_name}': succeeded."); + $self->{logger}->writeLogInfo("[newtest] move config for '$self->{poller_name}': succeeded."); - $self->{logger}->writeLogInfo("[newtest] -class- restart/reload config for '$self->{poller_name}':"); + $self->{logger}->writeLogInfo("[newtest] restart/reload config for '$self->{poller_name}':"); if ($self->clapi_execute(cmd => '-a ' . $self->{clapi_action_applycfg} . ' -v ' . $self->{poller_id}, timeout => $self->{clapi_timeout}) != 0) { - $self->{logger}->writeLogError("[newtest] -class- restart/reload config for '$self->{poller_name}': failed"); + $self->{logger}->writeLogError("[newtest] restart/reload config for '$self->{poller_name}': failed"); return ; } - $self->{logger}->writeLogInfo("[newtest] -class- restart/reload config for '$self->{poller_name}': succeeded."); + $self->{logger}->writeLogInfo("[newtest] restart/reload config for '$self->{poller_name}': succeeded."); } } @@ -344,12 +344,12 @@ sub get_newtest_diagnostic { my $result = $self->{instance}->ListMessages('Instance', 30, 'Diagnostics', [$options{scenario}, $options{robot}]); if (defined(my $com_error = gorgone::modules::plugins::newtest::libs::stubs::errors::get_error())) { - $self->{logger}->writeLogError("[newtest] -class- newtest API error 'ListMessages' method: " . $com_error); + $self->{logger}->writeLogError("[newtest] newtest API error 'ListMessages' method: " . $com_error); return -1; } if (!(ref($result) && defined($result->{MessageItem}))) { - $self->{logger}->writeLogError("[newtest] -class- no diagnostic found for scenario: " . $options{scenario} . '/' . $options{robot}); + $self->{logger}->writeLogError("[newtest] no diagnostic found for scenario: " . $options{scenario} . '/' . $options{robot}); return 1; } if (ref($result->{MessageItem}) eq 'HASH') { @@ -380,20 +380,20 @@ sub get_scenario_results { # Already test the robot but no response if (defined($self->{cache_robot_list_results}->{$options{robot}}) && !defined($self->{cache_robot_list_results}->{$options{robot}}->{ResultItem})) { - $self->{current_text} = sprintf("[newtest] -class- no result avaiblable for scenario '%s'", $options{scenario}); + $self->{current_text} = sprintf("[newtest] no result avaiblable for scenario '%s'", $options{scenario}); $self->{current_status} = 3; return 1; } if (!defined($self->{cache_robot_list_results}->{$options{robot}})) { my $result = $self->{instance}->ListResults('Robot', 30, [$options{robot}]); if (defined(my $com_error = gorgone::modules::plugins::newtest::libs::stubs::errors::get_error())) { - $self->{logger}->writeLogError("[newtest] -class- newtest API error 'ListResults' method: " . $com_error); + $self->{logger}->writeLogError("[newtest] newtest API error 'ListResults' method: " . $com_error); return -1; } if (!(ref($result) && defined($result->{ResultItem}))) { $self->{cache_robot_list_results}->{$options{robot}} = {}; - $self->{logger}->writeLogError("[newtest] -class- no results found for robot: " . $options{robot}); + $self->{logger}->writeLogError("[newtest] no results found for robot: " . $options{robot}); return 1; } @@ -427,12 +427,12 @@ sub get_scenario_results { id => $result->{Id} ); - $self->{logger}->writeLogInfo("[newtest] -class- result found for scenario: " . $options{scenario} . '/' . $options{robot}); + $self->{logger}->writeLogInfo("[newtest] result found for scenario: " . $options{scenario} . '/' . $options{robot}); return 0; } } - $self->{logger}->writeLogError("[newtest] -class- no result found for scenario: " . $options{scenario} . '/' . $options{robot}); + $self->{logger}->writeLogError("[newtest] no result found for scenario: " . $options{scenario} . '/' . $options{robot}); return 1; } @@ -441,12 +441,12 @@ sub get_newtest_extra_metrics { my $result = $self->{instance}->ListResultChildren($options{id}); if (defined(my $com_error = gorgone::modules::plugins::newtest::libs::stubs::errors::get_error())) { - $self->{logger}->writeLogError("[newtest] -class- newtest API error 'ListResultChildren' method: " . $com_error); + $self->{logger}->writeLogError("[newtest] newtest API error 'ListResultChildren' method: " . $com_error); return -1; } if (!(ref($result) && defined($result->{ResultItem}))) { - $self->{logger}->writeLogError("[newtest] -class- no extra metrics found for scenario: " . $options{scenario} . '/' . $options{robot}); + $self->{logger}->writeLogError("[newtest] no extra metrics found for scenario: " . $options{scenario} . '/' . $options{robot}); return 1; } @@ -469,7 +469,7 @@ sub get_newtest_scenarios { $self->{instance}->proxy($self->{endpoint}, timeout => $self->{nmc_timeout}); }; if ($@) { - $self->{logger}->writeLogError('[newtest] -class- newtest proxy error: ' . $@); + $self->{logger}->writeLogError('[newtest] newtest proxy error: ' . $@); return -1; } @@ -485,7 +485,7 @@ sub get_newtest_scenarios { $self->{list_scenario_status}->{instances} ); if (defined(my $com_error = gorgone::modules::plugins::newtest::libs::stubs::errors::get_error())) { - $self->{logger}->writeLogError("[newtest] -class- newtest API error 'ListScenarioStatus' method: " . $com_error); + $self->{logger}->writeLogError("[newtest] newtest API error 'ListScenarioStatus' method: " . $com_error); return -1; } @@ -508,23 +508,23 @@ sub get_newtest_scenarios { # Add host config if (!defined($self->{db_newtest}->{$host_name})) { - $self->{logger}->writeLogInfo("[newtest] -class- create host '$host_name'"); + $self->{logger}->writeLogInfo("[newtest] create host '$host_name'"); if ($self->clapi_execute(cmd => '-o HOST -a ADD -v "' . $host_name . ';' . $host_name . ';127.0.0.1;' . $self->{host_template} . ';' . $self->{poller_name} . ';"', timeout => $self->{clapi_timeout}) == 0) { $self->{db_newtest}->{$host_name} = {}; $self->{must_push_config} = 1; - $self->{logger}->writeLogInfo("[newtest] -class- create host '$host_name' succeeded."); + $self->{logger}->writeLogInfo("[newtest] create host '$host_name' succeeded."); } } # Add service config if (defined($self->{db_newtest}->{$host_name}) && !defined($self->{db_newtest}->{$host_name}->{$service_name})) { - $self->{logger}->writeLogInfo("[newtest] -class- create service '$service_name' for host '$host_name':"); + $self->{logger}->writeLogInfo("[newtest] create service '$service_name' for host '$host_name':"); if ($self->clapi_execute(cmd => '-o SERVICE -a ADD -v "' . $host_name . ';' . $service_name . ';' . $self->{service_template} . '"', timeout => $self->{clapi_timeout}) == 0) { $self->{db_newtest}->{$host_name}->{$service_name} = {}; $self->{must_push_config} = 1; - $self->{logger}->writeLogInfo("[newtest] -class- create service '$service_name' for host '$host_name' succeeded."); + $self->{logger}->writeLogInfo("[newtest] create service '$service_name' for host '$host_name' succeeded."); $self->clapi_execute(cmd => '-o SERVICE -a setmacro -v "' . $host_name . ';' . $service_name . ';NEWTEST_MESSAGEID;"', timeout => $self->{clapi_timeout}); } @@ -533,7 +533,7 @@ sub get_newtest_scenarios { # Check if new message if (defined($self->{db_newtest}->{$host_name}->{$service_name}->{last_execution_time}) && $last_check <= $self->{db_newtest}->{$host_name}->{$service_name}->{last_execution_time}) { - $self->{logger}->writeLogInfo("[newtest] -class- skip: service '$service_name' for host '$host_name' already submitted."); + $self->{logger}->writeLogInfo("[newtest] skip: service '$service_name' for host '$host_name' already submitted."); next; } @@ -658,7 +658,7 @@ sub run { # we try to do all we can my $rev = zmq_poll($self->{poll}, 5000); if (defined($rev) && $rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("[newtest] -class- $$ has quit"); + $self->{logger}->writeLogInfo("[newtest] $$ has quit"); zmq_close($connector->{internal_socket}); zmq_close($self->{socket_log}) if (defined($self->{socket_log})); exit(0); diff --git a/gorgone/gorgone/modules/plugins/newtest/hooks.pm b/gorgone/gorgone/modules/plugins/newtest/hooks.pm index 42402db5188..200bacf38ea 100644 --- a/gorgone/gorgone/modules/plugins/newtest/hooks.pm +++ b/gorgone/gorgone/modules/plugins/newtest/hooks.pm @@ -70,7 +70,7 @@ sub routing { $data = JSON::XS->new->utf8->decode($options{data}); }; if ($@) { - $options{logger}->writeLogError("Cannot decode json data: $@"); + $options{logger}->writeLogError("[newtest] Cannot decode json data: $@"); gorgone::standard::library::add_history( dbh => $options{dbh}, code => 300, token => $options{token}, @@ -116,7 +116,7 @@ sub gently { $stop = 1; foreach my $container_id (keys %$containers) { - $options{logger}->writeLogInfo("[newtest] -hooks- Send TERM signal for container '" . $container_id . "'"); + $options{logger}->writeLogDebug("[newtest] Send TERM signal for container '" . $container_id . "'"); if ($containers->{$container_id}->{running} == 1) { CORE::kill('TERM', $containers->{$container_id}->{pid}); } @@ -128,7 +128,7 @@ sub kill_internal { foreach (keys %$containers) { if ($containers->{$_}->{running} == 1) { - $options{logger}->writeLogInfo("[newtest] -hooks- Send KILL signal for container '" . $_ . "'"); + $options{logger}->writeLogDebug("[newtest] Send KILL signal for container '" . $_ . "'"); CORE::kill('KILL', $containers->{$_}->{pid}); } } @@ -187,15 +187,15 @@ sub get_containers { next if (!defined($_->{name}) || $_->{name} eq ''); if (!defined($_->{nmc_endpoint}) || $_->{nmc_endpoint} eq '') { - $options{logger}->writeLogError("[newtest] -hooks- cannot load container '" . $_->{name} . "' - please set nmc_endpoint option"); + $options{logger}->writeLogError("[newtest] cannot load container '" . $_->{name} . "' - please set nmc_endpoint option"); next; } if (!defined($_->{poller_name}) || $_->{poller_name} eq '') { - $options{logger}->writeLogError("[newtest] -hooks- cannot load container '" . $_->{name} . "' - please set poller_name option"); + $options{logger}->writeLogError("[newtest] cannot load container '" . $_->{name} . "' - please set poller_name option"); next; } if (!defined($_->{list_scenario_status}) || $_->{list_scenario_status} eq '') { - $options{logger}->writeLogError("[newtest] -hooks- cannot load container '" . $_->{name} . "' - please set list_scenario_status option"); + $options{logger}->writeLogError("[newtest] cannot load container '" . $_->{name} . "' - please set list_scenario_status option"); next; } @@ -204,7 +204,7 @@ sub get_containers { $list_scenario = JSON::XS->new->utf8->decode($_->{list_scenario_status}); }; if ($@) { - $options{logger}->writeLogError("[newtest] -hooks- cannot load container '" . $_->{name} . "' - cannot decode list scenario option"); + $options{logger}->writeLogError("[newtest] cannot load container '" . $_->{name} . "' - cannot decode list scenario option"); next; } @@ -247,7 +247,7 @@ sub sync_container_childs { next if (defined($last_containers->{$container_id})); if ($containers->{$container_id}->{running} == 1) { - $options{logger}->writeLogInfo("[newtest] -hooks- Send KILL signal for container '" . $container_id . "'"); + $options{logger}->writeLogDebug("[newtest] Send KILL signal for container '" . $container_id . "'"); CORE::kill('KILL', $containers->{$container_id}->{pid}); } @@ -259,7 +259,7 @@ sub sync_container_childs { sub create_child { my (%options) = @_; - $options{logger}->writeLogInfo("[newtest] -hooks- Create 'gorgone-newtest' process for container '" . $options{container_id} . "'"); + $options{logger}->writeLogInfo("[newtest] Create 'gorgone-newtest' process for container '" . $options{container_id} . "'"); my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-newtest ' . $options{container_id}; @@ -276,7 +276,7 @@ sub create_child { $module->run(); exit(0); } - $options{logger}->writeLogInfo("[newtest] -hooks- PID $child_pid (gorgone-newtest) for container '" . $options{container_id} . "'"); + $options{logger}->writeLogDebug("[newtest] PID $child_pid (gorgone-newtest) for container '" . $options{container_id} . "'"); $containers->{$options{container_id}} = { pid => $child_pid, ready => 0, running => 1 }; $containers_pid->{$child_pid} = $options{container_id}; } diff --git a/gorgone/gorgone/modules/plugins/scom/class.pm b/gorgone/gorgone/modules/plugins/scom/class.pm index 793158e337d..6a61bf23707 100644 --- a/gorgone/gorgone/modules/plugins/scom/class.pm +++ b/gorgone/gorgone/modules/plugins/scom/class.pm @@ -87,7 +87,7 @@ sub handle_HUP { sub handle_TERM { my $self = shift; - $self->{logger}->writeLogInfo("[scom] -class- $$ Receiving order to stop..."); + $self->{logger}->writeLogInfo("[scom] $$ Receiving order to stop..."); $self->{stop} = 1; } @@ -107,13 +107,13 @@ sub http_check_error { my ($self, %options) = @_; if ($options{status} == 1) { - $self->{logger}->writeLogError("[scom] -class- Container $self->{container_id}: scom $options{method} issue"); + $self->{logger}->writeLogError("[scom] Container $self->{container_id}: scom $options{method} issue"); return 1; } my $code = $self->{http}->get_code(); if ($code !~ /^2/) { - $self->{logger}->writeLogError("[scom] -class- Container $self->{container_id}: scom $options{method} issue - " . $self->{http}->get_message()); + $self->{logger}->writeLogError("[scom] Container $self->{container_id}: scom $options{method} issue - " . $self->{http}->get_message()); return 1; } @@ -150,7 +150,7 @@ sub submit_external_cmd { wait_exit => 1 ); if ($lerror == -1 || ($exit_code >> 8) != 0) { - $self->{logger}->writeLogError("[scom] -class- Command execution problem for command $options{cmd} : " . $stdout); + $self->{logger}->writeLogError("[scom] Command execution problem for command $options{cmd} : " . $stdout); return -1; } @@ -177,7 +177,7 @@ sub scom_authenticate_1801 { if (defined($header) && $header =~ /SCOMSessionId=([^;]+);/i) { $connector->{scom_session_id} = $1; } else { - $self->{logger}->writeLogError("[scom] -class- Container $self->{container_id}: scom authenticate issue - error retrieving cookie"); + $self->{logger}->writeLogError("[scom] Container $self->{container_id}: scom authenticate issue - error retrieving cookie"); return 1; } @@ -458,25 +458,25 @@ sub action_scomresync { $options{token} = $self->generate_token() if (!defined($options{token})); $self->send_log(code => gorgone::class::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action scomresync proceed' }); - $self->{logger}->writeLogDebug("[scom] -class- Container $self->{container_id}: begin resync"); + $self->{logger}->writeLogDebug("[scom] Container $self->{container_id}: begin resync"); if ($self->get_realtime_slots()) { $self->send_log(code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot find realtime slots' }); - $self->{logger}->writeLogError("[scom] -class- Container $self->{container_id}: cannot find realtime slots"); + $self->{logger}->writeLogError("[scom] Container $self->{container_id}: cannot find realtime slots"); return 1; } my $func = $self->get_method(method => 'get_realtime_scom_alerts'); if ($func->($self)) { $self->send_log(code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot get scom realtime alerts' }); - $self->{logger}->writeLogError("[scom] -class- Container $self->{container_id}: cannot get scom realtime alerts"); + $self->{logger}->writeLogError("[scom] Container $self->{container_id}: cannot get scom realtime alerts"); return 1; } $self->sync_alerts(); $self->sync_acks(); - $self->{logger}->writeLogDebug("[scom] -class- Container $self->{container_id}: finish resync"); + $self->{logger}->writeLogDebug("[scom] Container $self->{container_id}: finish resync"); $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => { message => 'action scomresync finished' }); return 0; } @@ -485,7 +485,7 @@ sub event { while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); - $connector->{logger}->writeLogDebug("[scom] -class- Event: $message"); + $connector->{logger}->writeLogDebug("[scom] Event: $message"); if ($message =~ /^\[(.*?)\]/) { if ((my $method = $connector->can('action_' . lc($1)))) { $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; @@ -537,7 +537,7 @@ sub run { # we try to do all we can my $rev = zmq_poll($self->{poll}, 5000); if (defined($rev) && $rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("[scom] -class- $$ has quit"); + $self->{logger}->writeLogInfo("[scom] $$ has quit"); zmq_close($connector->{internal_socket}); exit(0); } diff --git a/gorgone/gorgone/modules/plugins/scom/hooks.pm b/gorgone/gorgone/modules/plugins/scom/hooks.pm index 791a0d00f0d..cf4c09a2c2a 100644 --- a/gorgone/gorgone/modules/plugins/scom/hooks.pm +++ b/gorgone/gorgone/modules/plugins/scom/hooks.pm @@ -69,7 +69,7 @@ sub routing { $data = JSON::XS->new->utf8->decode($options{data}); }; if ($@) { - $options{logger}->writeLogError("[scom] -hooks- Cannot decode json data: $@"); + $options{logger}->writeLogError("[scom] Cannot decode json data: $@"); gorgone::standard::library::add_history( dbh => $options{dbh}, code => 200, token => $options{token}, @@ -115,7 +115,7 @@ sub gently { $stop = 1; foreach my $container_id (keys %$containers) { - $options{logger}->writeLogInfo("[scom] -hooks- Send TERM signal for container '" . $container_id . "'"); + $options{logger}->writeLogInfo("[scom] Send TERM signal for container '" . $container_id . "'"); if ($containers->{$container_id}->{running} == 1) { CORE::kill('TERM', $containers->{$container_id}->{pid}); } @@ -127,7 +127,7 @@ sub kill_internal { foreach (keys %$containers) { if ($containers->{$_}->{running} == 1) { - $options{logger}->writeLogInfo("[scom] -hooks- Send KILL signal for container '" . $_ . "'"); + $options{logger}->writeLogInfo("[scom] Send KILL signal for container '" . $_ . "'"); CORE::kill('KILL', $containers->{$_}->{pid}); } } @@ -187,15 +187,15 @@ sub get_containers { next if (!defined($_->{name}) || $_->{name} eq ''); if (!defined($_->{url}) || $_->{url} eq '') { - $options{logger}->writeLogError("[scom] -hooks- Cannot load container '" . $_->{name} . "' - please set url option"); + $options{logger}->writeLogError("[scom] Cannot load container '" . $_->{name} . "' - please set url option"); next; } if (!defined($_->{dsmhost}) || $_->{dsmhost} eq '') { - $options{logger}->writeLogError("[scom] -hooks- Cannot load container '" . $_->{name} . "' - please set dsmhost option"); + $options{logger}->writeLogError("[scom] Cannot load container '" . $_->{name} . "' - please set dsmhost option"); next; } if (!defined($_->{dsmslot}) || $_->{dsmslot} eq '') { - $options{logger}->writeLogError("[scom] -hooks- Cannot load container '" . $_->{name} . "' - please set dsmslot option"); + $options{logger}->writeLogError("[scom] Cannot load container '" . $_->{name} . "' - please set dsmslot option"); next; } @@ -234,7 +234,7 @@ sub sync_container_childs { next if (defined($last_containers->{$container_id})); if ($containers->{$container_id}->{running} == 1) { - $options{logger}->writeLogInfo("[scom] -hooks- Send KILL signal for container '" . $container_id . "'"); + $options{logger}->writeLogDebug("[scom] Send KILL signal for container '" . $container_id . "'"); CORE::kill('KILL', $containers->{$container_id}->{pid}); } @@ -246,7 +246,7 @@ sub sync_container_childs { sub create_child { my (%options) = @_; - $options{logger}->writeLogInfo("[scom] -hooks- Create 'gorgone-scom' process for container '" . $options{container_id} . "'"); + $options{logger}->writeLogInfo("[scom] Create 'gorgone-scom' process for container '" . $options{container_id} . "'"); my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-scom ' . $options{container_id}; @@ -262,7 +262,7 @@ sub create_child { $module->run(); exit(0); } - $options{logger}->writeLogInfo("[scom] -hooks- PID $child_pid (gorgone-scom) for container '" . $options{container_id} . "'"); + $options{logger}->writeLogDebug("[scom] PID $child_pid (gorgone-scom) for container '" . $options{container_id} . "'"); $containers->{$options{container_id}} = { pid => $child_pid, ready => 0, running => 1 }; $containers_pid->{$child_pid} = $options{container_id}; } From a8af088b127ba6225d94e5a835d4a6513f0ea7b2 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 27 Dec 2019 15:59:43 +0100 Subject: [PATCH 292/948] enh(core): enhance logging --- gorgone/gorgone/modules/centreon/engine/hooks.pm | 10 +++++----- gorgone/gorgone/modules/centreon/legacycmd/class.pm | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/engine/hooks.pm b/gorgone/gorgone/modules/centreon/engine/hooks.pm index 58631d6af8f..94846398115 100644 --- a/gorgone/gorgone/modules/centreon/engine/hooks.pm +++ b/gorgone/gorgone/modules/centreon/engine/hooks.pm @@ -61,7 +61,7 @@ sub routing { $data = JSON::XS->new->utf8->decode($options{data}); }; if ($@) { - $options{logger}->writeLogError("[engine] -hooks- Cannot decode json data: $@"); + $options{logger}->writeLogError("[engine] Cannot decode json data: $@"); gorgone::standard::library::add_history( dbh => $options{dbh}, code => 30, token => $options{token}, @@ -99,7 +99,7 @@ sub gently { my (%options) = @_; $stop = 1; - $options{logger}->writeLogInfo("[engine] -hooks- Send TERM signal"); + $options{logger}->writeLogDebug("[engine] Send TERM signal"); if ($engine->{running} == 1) { CORE::kill('TERM', $engine->{pid}); } @@ -109,7 +109,7 @@ sub kill { my (%options) = @_; if ($engine->{running} == 1) { - $options{logger}->writeLogInfo("[engine] -hooks- Send KILL signal for pool"); + $options{logger}->writeLogDebug("[engine] Send KILL signal for pool"); CORE::kill('KILL', $engine->{pid}); } } @@ -149,7 +149,7 @@ sub broadcast { sub create_child { my (%options) = @_; - $options{logger}->writeLogInfo("[engine] -hooks- Create module 'engine' process"); + $options{logger}->writeLogInfo("[engine] Create module 'engine' process"); my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-engine'; @@ -161,7 +161,7 @@ sub create_child { $module->run(); exit(0); } - $options{logger}->writeLogInfo("[engine] -hooks- PID $child_pid (gorgone-engine)"); + $options{logger}->writeLogDebug("[engine] PID $child_pid (gorgone-engine)"); $engine = { pid => $child_pid, ready => 0, running => 1 }; } diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index 974d39e1645..d47f3772568 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -161,7 +161,7 @@ sub execute_cmd { chomp $options{target}; chomp $options{param} if (defined($options{param})); - my $msg = "[legacycmd] -class- Handling command '" . $options{cmd} . "'"; + my $msg = "[legacycmd] Handling command '" . $options{cmd} . "'"; $msg .= ", Target: '" . $options{target} . "'" if (defined($options{target})); $msg .= ", Parameters: '" . $options{param} . "'" if (defined($options{param})); $self->{logger}->writeLogInfo($msg); From 720140493d70dea3396b7b0dd4ac9a79077e6adf Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Fri, 27 Dec 2019 16:05:49 +0100 Subject: [PATCH 293/948] enh(legacycmd): set illegal character default --- .../modules/centreon/legacycmd/class.pm | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index d47f3772568..1f4e9f75152 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -50,7 +50,8 @@ sub new { $connector->{config_core} = $options{config_core}; $connector->{config_db_centreon} = $options{config_db_centreon}; $connector->{stop} = 0; - + $connector->{gorgone_illegal_characters} = '`'; + bless $connector, $class; $connector->set_signal_handlers; return $connector; @@ -134,7 +135,7 @@ sub get_clapi_user { $self->{clapi_user} = $clapi_user; $self->{clapi_password} = $clapi_password; - + return 0; } @@ -145,13 +146,15 @@ sub get_illegal_characters { request => "SELECT `value` FROM options WHERE `key` = 'gorgone_illegal_characters'", mode => 2 ); - if ($status == -1 || !defined($datas->[0][0])) { + if ($status == -1) { $self->{logger}->writeLogError('[legacycmd] Cannot get illegal characters'); return -1; } - $self->{gorgone_illegal_characters} = $datas->[0][0]; - + if (defined($datas->[0]->[0])) { + $self->{gorgone_illegal_characters} = $datas->[0]->[0]; + } + return 0; } @@ -239,7 +242,7 @@ sub execute_cmd { } }, ); - + my $centreon_dir = (defined($connector->{config}->{centreon_dir})) ? $connector->{config}->{centreon_dir} : '/usr/share/centreon'; my $task_id = $options{param}; @@ -519,7 +522,7 @@ sub handle_centcore_dir { (stat($self->{config}->{cmd_dir} . '/' . $a))[10] <=> (stat($self->{config}->{cmd_dir} . '/' . $b))[10] } (readdir($dh)); closedir($dh); - + my ($code, $handle); foreach (@files) { next if ($_ =~ /^\./); @@ -554,7 +557,7 @@ sub handle_cmd_files { sub event { while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); - + $connector->{logger}->writeLogDebug("[legacycmd] Event: $message"); if ($message =~ /^\[(.*?)\]/) { if ((my $method = $connector->can('action_' . lc($1)))) { From 20b9fe18533f8222bc6fe2d45fe2d3b2f7966c4a Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Fri, 27 Dec 2019 16:28:07 +0100 Subject: [PATCH 294/948] update TODO --- gorgone/TODO | 5 ----- 1 file changed, 5 deletions(-) diff --git a/gorgone/TODO b/gorgone/TODO index 71c822ce8bc..b4826becdef 100644 --- a/gorgone/TODO +++ b/gorgone/TODO @@ -1,8 +1,3 @@ - gorgone-newtest: don't use centcore.cmd. use ssh system. -- Add a broadcast action to update module (like logger in debug for example) - Add redis backend to store logs (we could disable synclog in redis mode) - Add a websocket module to call gorgone - -- gorgone_port: 22 ou 5665 -- gorgone_communication_type: 1 = 'zmq', 2 = 'ssh' -- remote_server_use_proxy From 4a37fde22ef3d08b1de6a4460eb14d4a007fcea4 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Fri, 27 Dec 2019 16:42:44 +0100 Subject: [PATCH 295/948] enh(httpserver): remove useless code --- .../gorgone/modules/core/httpserver/class.pm | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/gorgone/gorgone/modules/core/httpserver/class.pm b/gorgone/gorgone/modules/core/httpserver/class.pm index e0e1116897a..74aeda07215 100644 --- a/gorgone/gorgone/modules/core/httpserver/class.pm +++ b/gorgone/gorgone/modules/core/httpserver/class.pm @@ -111,18 +111,9 @@ sub class_handle_HUP { sub event { while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); - + $connector->{logger}->writeLogDebug("[httpserver] Event: $message"); - if ($message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/) { - if ((my $method = $connector->can('action_' . lc($1)))) { - $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; - my ($action, $token) = ($1, $2); - my $data = JSON::XS->new->utf8->decode($3); - $method->($connector, token => $token, data => $data); - } - } - last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); } } @@ -212,7 +203,7 @@ sub run { Timeout => 5 ); } - + if (defined($daemon)) { while (1) { my ($connection) = $daemon->accept(); @@ -223,9 +214,9 @@ sub run { zmq_close($connector->{internal_socket}); exit(0); } - + next if (!defined($connection)); - + while (my $request = $connection->get_request) { $connector->{logger}->writeLogInfo("[httpserver] " . $connection->peerhost() . " " . $request->method . " '" . $request->uri->path . "' '" . $request->header("User-Agent") . "'"); @@ -274,7 +265,7 @@ sub run { sub ssl_error { my ($self, $error) = @_; - + ${*$self}{httpd_client_proto} = 1000; ${*$self}{httpd_daemon} = HTTP::Daemon::SSL::DummyDaemon->new(); $self->send_error(RC_BAD_REQUEST); @@ -287,7 +278,7 @@ sub authentication { return 1 if ($self->{auth_enabled} == 0); return 0 if (!defined($header) || $header eq ''); - + ($header =~ /Basic\s(.*)$/); my ($user, $password) = split(/:/, MIME::Base64::decode($1), 2); return 1 if (defined($self->{config}->{auth}->{user}) && $user eq $self->{config}->{auth}->{user} && From 1e5c828c21414a4b177d1ffa4da040dd761ab3be Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 31 Dec 2019 11:51:06 +0100 Subject: [PATCH 296/948] feat(docs): add an openapi spec for the api --- gorgone/docs/gorgone-openapi.yaml | 1083 +++++++++++++++++++++++++++++ 1 file changed, 1083 insertions(+) create mode 100644 gorgone/docs/gorgone-openapi.yaml diff --git a/gorgone/docs/gorgone-openapi.yaml b/gorgone/docs/gorgone-openapi.yaml new file mode 100644 index 00000000000..848194dc121 --- /dev/null +++ b/gorgone/docs/gorgone-openapi.yaml @@ -0,0 +1,1083 @@ +openapi: 3.0.1 +info: + title: Centreon Gorgone RestAPI + description: | + # Information + Centreon Gorgone and his "gorgoned" daemon is a lightweight, distributed, modular tasks handler. + + It provides a set of actions like: + + - Execute commands + - Send files/directories, + - Schedule cron-like tasks, + - Push or execute tasks through SSH. + + The daemon can be installed on Centreon environments like Centreon Central, Remote and Poller servers. + + It uses ZeroMQ library. + x-logo: + url: ./centreon-logo.png + contact: + url: 'https://www.centreon.com' + license: + name: Apache 2.0 + url: 'http://www.apache.org/licenses/LICENSE-2.0.html' + version: "1.0" +externalDocs: + description: You can contact us on our community Slack + url: 'https://centreon.slack.com/messages/CCRGLQSE5' +servers: + - url: '{protocol}://{server}:{port}/api' + description: "Local Gorgone instance" + variables: + protocol: + enum: + - http + - https + default: http + description: "HTTP schema" + server: + default: localhost + description: "IP address or hostname of Gorgone instance" + port: + default: '8085' + description: "Port used by HTTP server" + - url: '{protocol}://{server}:{port}/api/nodes/{id}' + description: "Remote Gorgone instance" + variables: + protocol: + enum: + - http + - https + default: http + description: "HTTP schema" + server: + default: localhost + description: "IP address or hostname of Gorgone instance" + port: + default: '8085' + description: "Port used by HTTP server" + id: + default: '1' + description: "ID of the remote Gorgone node" +tags: + - name: Internal + description: "Internal events." + - name: Logs + description: "Logs management." + - name: Cron + description: "Module aiming to reproduce a cron-like scheduler that can send events to other Gorgone modules." + - name: Action + description: "Module aiming to execute actions on the server running the Gorgone daemon or remotly using SSH." + - name: Engine + description: "Module aiming to provide a bridge to communicate with Centreon Engine daemon." + - name: Broker + description: "Module aiming to deal with Centreon Broker daemon." + - name: Autodiscovery + description: "Module aiming to extend Centreon Autodiscovery server functionalities." +security: + - Basic Authentication: [] +paths: + /internal/constatus: + get: + tags: + - Internal + summary: "Get nodes connection status" + description: "Get the connection status of all nodes managed by the Gorgone daemon" + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/NodesStatus' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + /internal/information: + get: + tags: + - Internal + summary: "Get runtime informations and statistics" + description: "Get informations and statistics about loaded modules, available endpoints and number of events computed at runtime." + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Information' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + /internal/thumbprint: + get: + tags: + - Internal + summary: "Get public key thumbprint" + description: "Get the thumbprint of the public key of the Gorgone daemon." + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Thumbprint' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + /internal/logger: + post: + tags: + - Internal + summary: "Set logger severity level" + description: "Set the logger severity level for all modules." + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SeverityLevel' + responses: + '204': + description: OK + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + /log/{token}: + get: + tags: + - Logs + summary: "Retrieve event's logs" + description: "Retrieve the event's logs based on event's token." + parameters: + - name: token + in: path + description: "Token of the event" + schema: + type: string + required: true + example: + "1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165" + responses: + '200': + description: OK + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/Logs' + - $ref: '#/components/schemas/NoLogs' + - $ref: '#/components/schemas/Token' + - $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + /core/cron/definitions: + get: + tags: + - Cron + summary: "List definitions" + description: "List all cron definitions." + responses: + '200': + description: OK + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/Logs' + - $ref: '#/components/schemas/NoLogs' + - $ref: '#/components/schemas/Token' + - $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + post: + tags: + - Cron + summary: "Add definitions" + description: "Add one or multiple cron definitions to runtime." + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CronDefinitions' + responses: + '200': + description: OK + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/Logs' + - $ref: '#/components/schemas/NoLogs' + - $ref: '#/components/schemas/Token' + - $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + /core/cron/definitions/{id}: + get: + tags: + - Cron + summary: "Get a definition" + description: "List cron definition identified by id." + parameters: + - name: id + in: path + description: "ID of the definition" + schema: + type: string + required: true + example: + "broker_stats" + responses: + '200': + description: OK + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/Logs' + - $ref: '#/components/schemas/NoLogs' + - $ref: '#/components/schemas/Token' + - $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + patch: + tags: + - Cron + summary: "Update a definition" + description: "Update a cron definition." + parameters: + - name: id + in: path + description: "ID of the definition" + schema: + type: string + required: true + example: + "broker_stats" + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/CronDefinition' + responses: + '200': + description: OK + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/Logs' + - $ref: '#/components/schemas/NoLogs' + - $ref: '#/components/schemas/Token' + - $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + delete: + tags: + - Cron + summary: "Delete a definition" + description: "Delete a cron definition." + parameters: + - name: id + in: path + description: "ID of the definition" + schema: + type: string + required: true + example: + "broker_stats" + responses: + '200': + description: OK + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/Logs' + - $ref: '#/components/schemas/NoLogs' + - $ref: '#/components/schemas/Token' + - $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + /core/cron/definitions/{id}/status: + get: + tags: + - Cron + summary: "Get a definition status" + description: "Get a definition execution status." + parameters: + - name: id + in: path + description: "ID of the definition" + schema: + type: string + required: true + example: + "broker_stats" + responses: + '200': + description: OK + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/Logs' + - $ref: '#/components/schemas/NoLogs' + - $ref: '#/components/schemas/Token' + - $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + /core/action/command: + post: + tags: + - Action + summary: "Execute one or several command lines" + description: "Execute a command or a set of commands on server running Gorgone." + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ActionCommands' + responses: + '200': + description: OK + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/Logs' + - $ref: '#/components/schemas/NoLogs' + - $ref: '#/components/schemas/Token' + - $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + /centreon/engine/command: + post: + tags: + - Engine + summary: "Send one or several external commands" + description: | + Send an external command or a set of external commands to a running Centreon Engine instance using command file pipe. + This method needs the commands to be preformatted as Nagios external commands format. + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/EngineCommands' + responses: + '200': + description: OK + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/Logs' + - $ref: '#/components/schemas/NoLogs' + - $ref: '#/components/schemas/Token' + - $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + /centreon/broker/statistics: + get: + tags: + - Broker + summary: "Launch Broker statistics collection" + description: "Launch Broker statistics collection and store the result on disk." + responses: + '200': + description: OK + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/Logs' + - $ref: '#/components/schemas/NoLogs' + - $ref: '#/components/schemas/Token' + - $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + /centreon/broker/statistics/{id}: + get: + tags: + - Broker + summary: "Launch Broker statistics collection of a specific monitoring server" + description: "Launch Broker statistics collection and store the result on disk." + parameters: + - name: id + in: path + description: "ID of the monitoring server" + schema: + type: integer + required: true + example: + 2 + responses: + '200': + description: OK + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/Logs' + - $ref: '#/components/schemas/NoLogs' + - $ref: '#/components/schemas/Token' + - $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + /centreon/autodiscovery/task: + post: + tags: + - Autodiscovery + summary: "Add a discovery task" + description: "Add one Centreon Autodiscovery task." + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AutodiscoveryTask' + responses: + '200': + description: OK + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/Logs' + - $ref: '#/components/schemas/NoLogs' + - $ref: '#/components/schemas/Token' + - $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + /centreon/autodiscovery/task/{id}: + get: + tags: + - Autodiscovery + summary: "Get a discovery task results" + description: "Get Centreon Autodiscovery task results." + parameters: + - name: id + in: path + description: "ID of the task" + schema: + type: string + required: true + example: + "Task-SNMP-10.1.2.3" + responses: + '200': + description: OK + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/Logs' + - $ref: '#/components/schemas/NoLogs' + - $ref: '#/components/schemas/Token' + - $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + /centreon/autodiscovery/job: + post: + tags: + - Autodiscovery + summary: "Add a discovery job" + description: "Add one Centreon Autodiscovery job." + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/AutodiscoveryJob' + responses: + '200': + description: OK + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/Logs' + - $ref: '#/components/schemas/NoLogs' + - $ref: '#/components/schemas/Token' + - $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' + /centreon/autodiscovery/job/{id}: + get: + tags: + - Autodiscovery + summary: "Get a discovery job results" + description: "Get Centreon Autodiscovery job results." + parameters: + - name: id + in: path + description: "ID of the job" + schema: + type: string + required: true + example: + "Job-SNMP-10.1.2.3" + responses: + '200': + description: OK + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/Logs' + - $ref: '#/components/schemas/NoLogs' + - $ref: '#/components/schemas/Token' + - $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/Unauthorized' + '403': + $ref: '#/components/responses/Forbidden' +components: + securitySchemes: + Basic Authentication: + type: http + scheme: basic + responses: + NotFound: + description: "The specified resource was not found" + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + Unauthorized: + description: "Unauthorized" + headers: + WWW-Authenticate: + schema: + type: string + content: + application/json: + schema: + type: object + properties: + error: + type: string + description: "Short error description" + example: + "http_error_401" + message: + type: string + description: "Message explaining the error" + example: + "unauthorized" + required: + - error + - message + Forbidden: + description: "Forbidden" + content: + application/json: + schema: + type: object + properties: + error: + type: string + description: "Short error description" + example: + "http_error_403" + message: + type: string + description: "Message explaining the error" + example: + "forbidden" + required: + - error + - message + UnknownEndpoint: + description: "Unknown endpoint" + content: + application/json: + schema: + type: object + properties: + error: + type: string + description: "Short error description" + example: + "method_unknown" + message: + type: string + description: "Message explaining the error" + example: + "Method not implemented" + required: + - error + - message + UnknownMethod: + description: "Unknown method" + content: + application/json: + schema: + type: object + properties: + error: + type: string + description: "Short error description" + example: + "endpoint_unknown" + message: + type: string + description: "Message explaining the error" + example: + "endpoint not implemented" + required: + - error + - message + schemas: + Error: + type: object + properties: + error: + type: string + description: "Short error description" + message: + type: string + description: "Message explaining the error" + required: + - error + - message + Token: + type: object + properties: + token: + type: string + format: byte + description: "Token related to the event's result" + example: + "1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165" + Logs: + type: object + properties: + message: + type: string + description: "Additionnal message" + example: + "Logs found" + token: + type: string + format: byte + description: "Token related to the event's result" + example: + "03008486ba50b52e529ff5828d1432e5578dd18bb530c145b133dc902c8cfa6b8aac4d58fffb0c5ed44b943d2acbfb7cd1b18c55fcebce62e51999db460112c7" + data: + type: array + description: "Results array containing all logs related to token" + items: + $ref: '#/components/schemas/Log' + Log: + type: object + properties: + ctime: + type: string + format: timestamp + description: "Time when the server has stored the log in its database" + example: + 1577727699 + etime: + type: string + format: timestamp + description: "Time when the event has occured" + example: + 1577727699 + id: + type: integer + description: "ID of the event" + example: + 101483 + instant: + type: integer + example: + 0 + data: + type: object + description: "Data stored for this event" + token: + type: string + format: byte + description: "Token related to the event" + example: + "03008486ba50b52e529ff5828d1432e5578dd18bb530c145b133dc902c8cfa6b8aac4d58fffb0c5ed44b943d2acbfb7cd1b18c55fcebce62e51999db460112c7" + code: + type: integer + description: "Returned code of the event" + example: + 2 + NoLogs: + type: object + properties: + error: + type: string + description: "Short error description" + example: + "no_log" + message: + type: string + description: "Message explaining the error" + example: + "No log found for token" + token: + type: string + description: "Token related to the event's result" + example: + "03008486ba50b52e529ff5828d1432e5578dd18bb530c145b133dc902c8cfa6b8aac4d58fffb0c5ed44b943d2acbfb7cd1b18c55fcebce62e51999db460112c7" + data: + type: array + description: "Empty array" + items: + type: object + NodesStatus: + type: object + properties: + action: + type: string + description: "Event sent to retrieve data" + example: + "constatus" + message: + type: string + description: "Response message" + example: + "ok" + data: + type: object + properties: + id: + $ref: '#/components/schemas/NodeStatus' + NodeStatus: + type: object + properties: + last_ping_sent: + type: string + format: timestamp + description: "Last ping sent timestamp" + example: + 1577726040 + type: + type: string + enum: [push_zmq, pull_zmq, ssh] + description: "Communication type" + example: + "push_zmq" + nodes: + type: object + description: "Nodes managed by this Gorgone daemon" + last_ping_recv: + type: string + format: timestamp + description: "Last ping received timestamp" + example: + 1577726040 + Information: + type: object + properties: + action: + type: string + description: "Event sent to retrieve data" + example: + "information" + message: + type: string + description: "Response message" + example: + "ok" + data: + type: object + properties: + modules: + $ref: '#/components/schemas/Modules' + api_endpoints: + $ref: '#/components/schemas/ApiEndpoints' + counters: + $ref: '#/components/schemas/Counters' + Modules: + type: object + description: "List of loaded modules" + additionalProperties: + type: string + example: + httpserver: "gorgone::modules::core::httpserver::hooks" + dbcleaner: "gorgone::modules::core::dbcleaner::hooks" + cron: "gorgone::modules::core::cron::hooks" + engine: "gorgone::modules::centreon::engine::hooks" + action: "gorgone::modules::core::action::hooks" + broker: "gorgone::modules::centreon::broker::hooks" + nodes: "gorgone::modules::centreon::nodes::hooks" + legacycmd: "gorgone::modules::centreon::legacycmd::hooks" + proxy: "gorgone::modules::core::proxy::hooks" + ApiEndpoints: + type: object + description: "List of available endpoints" + additionalProperties: + type: string + example: + POST_/internal/logger: "BCASTLOGGER" + GET_/centreon/broker/statistics: "BROKERSTATS" + GET_/internal/thumbprint: "GETTHUMBPRINT" + GET_/core/cron/definitions: "GETCRON" + GET_/internal/information: "INFORMATION" + POST_/core/cron/definitions: "ADDCRON" + POST_/core/action/command: "COMMAND" + POST_/core/proxy/remotecopy: "REMOTECOPY" + POST_/centreon/engine/command: "ENGINECOMMAND" + PATCH_/core/cron/definitions: "UPDATECRON" + DELETE_/core/cron/definitions: "DELETECRON" + GET_/internal/constatus: "CONSTATUS" + Counters: + type: object + description: "List of metric counters" + properties: + total: + type: integer + description: "Total number of events processed since startup" + example: + 40210 + external: + type: object + description: "Number of external events since startup" + additionalProperties: + type: string + example: + total: 0 + internal: + type: object + description: "Number of internal events since startup" + additionalProperties: + type: string + example: + legacycmdready: 1 + setlogs: 7841 + enginecommand: 20 + registernodes: 443 + pong: 3397 + proxyready: 5 + brokerready: 1 + addcron: 1 + cronready: 1 + getthumbprint: 2 + centreonnodesready: 1 + httpserverready: 1 + command: 4446 + putlog: 9809 + dbcleanerready: 1 + information: 6 + brokerstats: 4446 + constatus: 1 + total: 40210 + setcoreid: 443 + getlog: 8893 + engineready: 1 + unregisternodes: 443 + actionready: 1 + proxy: + type: object + description: "Number of events passed through proxy since startup" + additionalProperties: + type: string + example: + enginecommand: 10 + getlog: 4446 + total: 8902 + command: 4446 + Thumbprint: + type: object + properties: + action: + type: string + description: "Event sent to retrieve data" + example: + "getthumbprint" + message: + type: string + description: "Response message" + example: + "ok" + data: + type: object + properties: + thumbprint: + type: string + description: "Thumbprint of the public key" + example: + "cS4B3lZq96qcP4FTMhVMuwAhztqRBQERKyhnEitnTFM" + SeverityLevel: + type: object + properties: + severity: + type: string + description: "Severity level to be defined for all loaded modules" + enum: + - info + - error + - debug + CronDefinitions: + type: array + items: + $ref: '#/components/schemas/CronDefinition' + CronDefinition: + type: object + properties: + timespec: + type: string + description: "Cron-like time specification" + id: + type: string + description: "Unique identifier of the cron definition" + action: + type: string + description: "Action/event to call at job execution" + parameters: + type: object + description: "Parameters needed by the called action/event" + keep_token: + type: boolean + description: "Boolean to define whether or not the ID of the definition will be used as token for the command" + required: + - timespec + - id + - action + - parameters + ActionCommands: + type: array + items: + $ref: '#/components/schemas/ActionCommand' + ActionCommand: + type: object + properties: + command: + type: string + description: "Command to execute" + example: + "echo data > /tmp/date.log" + timeout: + type: integer + description: "Time in seconds before a command is considered timed out" + example: + 5 + continue_on_error: + type: boolean + description: "Behaviour in case of execution issue" + example: + true + required: + - command + EngineCommands: + type: array + items: + $ref: '#/components/schemas/EngineCommand' + EngineCommand: + type: object + properties: + command: + type: string + description: "External command" + example: + "[653284380] SCHEDULE_SVC_CHECK;host1;service1;653284380" + command_file: + type: string + description: "Path to the Centreon Engine command file pipe" + example: + "/var/lib/centreon-engine/rw/centengine.cmd" + required: + - command + AutodiscoveryTask: + type: object + properties: + id: + type: string + description: "Identifier of the task (random if empty)" + example: + "Task-SNMP-10.1.2.3" + command: + type: string + description: "Command line to execute to perform the discovery" + example: | + perl /usr/lib/centreon/plugins/centreon_generic_snmp.pl --plugin=os::linux::local::plugin --mode=discovery-snmp --subnet='10.1.2.3/24' --snmp-port='161' --snmp-version='2c' --snmp-community='public' + timeout: + type: integer + description: "Time in seconds before the command is considered timed out" + example: + 300 + target: + type: integer + description: "Identifier of the target on which to execute the command" + example: + 2 + required: + - command + - target + AutodiscoveryJob: + type: object + properties: + id: + type: string + description: "Identifier of the job (random if empty)" + example: + "Job-SNMP-10.1.2.3" + timespec: + type: string + description: "Cron-like time specification" + example: + "30 2 * * *" + command: + type: string + description: "Command line to execute to perform the discovery" + example: | + perl /usr/lib/centreon/plugins/centreon_generic_snmp.pl --plugin=os::linux::local::plugin --mode=discovery-snmp --subnet='10.1.2.3/24' --snmp-port='161' --snmp-version='2c' --snmp-community='public' + timeout: + type: integer + description: "Time in seconds before the command is considered timed out" + example: + 300 + target: + type: integer + description: "Identifier of the target on which to execute the command" + example: + 2 + required: + - timespec + - command + - target + \ No newline at end of file From b462aa70b79f96133a624a1525351059e24de67c Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 31 Dec 2019 11:51:50 +0100 Subject: [PATCH 297/948] enh(docs): enhance endpoints declaration and other things --- gorgone/docs/api.md | 8 ++--- .../docs/modules/centreon/autodiscovery.md | 8 ++--- gorgone/docs/modules/centreon/broker.md | 12 ++++---- gorgone/docs/modules/centreon/engine.md | 2 +- gorgone/docs/modules/centreon/nodes.md | 4 +-- gorgone/docs/modules/core/action.md | 2 +- gorgone/docs/modules/core/cron.md | 12 ++++---- gorgone/docs/modules/core/proxy.md | 16 +++++----- gorgone/docs/modules/core/pull.md | 2 +- gorgone/docs/modules/core/register.md | 30 +++++++++---------- gorgone/docs/modules/plugins/newtest.md | 2 +- gorgone/docs/modules/plugins/scom.md | 2 +- 12 files changed, 50 insertions(+), 50 deletions(-) diff --git a/gorgone/docs/api.md b/gorgone/docs/api.md index bf92ad97c7e..2bb2b80514c 100644 --- a/gorgone/docs/api.md +++ b/gorgone/docs/api.md @@ -8,7 +8,7 @@ Centreon Gorgone provides a RestAPI through its HTTP server module. | Endpoint | Method | | :- | :- | -| /api/internal/constatus | `GET` | +| /internal/constatus | `GET` | #### Headers @@ -27,7 +27,7 @@ curl --request GET "https://hostname:8443/api/internal/constatus" \ | Endpoint | Method | | :- | :- | -| /api/internal/thumbprint | `GET` | +| /internal/thumbprint | `GET` | #### Headers @@ -46,7 +46,7 @@ curl --request GET "https://hostname:8443/api/internal/thumbprint" \ | Endpoint | Method | | :- | :- | -| /api/internal/information | `GET` | +| /internal/information | `GET` | #### Headers @@ -164,7 +164,7 @@ To retrieve the logs, a specific endpoint can be called as follow. | Endpoint | Method | | :- | :- | -| /api/log/:token | `GET` | +| /log/:token | `GET` | #### Headers diff --git a/gorgone/docs/modules/centreon/autodiscovery.md b/gorgone/docs/modules/centreon/autodiscovery.md index 06c05c03715..cd0831e1748 100644 --- a/gorgone/docs/modules/centreon/autodiscovery.md +++ b/gorgone/docs/modules/centreon/autodiscovery.md @@ -34,7 +34,7 @@ enable: true | Endpoint | Method | | :- | :- | -| /api/centreon/autodiscovery/task | `POST` | +| /centreon/autodiscovery/task | `POST` | #### Headers @@ -78,7 +78,7 @@ curl --request POST "https://hostname:8443/api/centreon/autodiscovery/task" \ | Endpoint | Method | | :- | :- | -| /api/centreon/autodiscovery/task/:id | `GET` | +| /centreon/autodiscovery/task/:id | `GET` | #### Headers @@ -103,7 +103,7 @@ curl --request GET "https://hostname:8443/api/centreon/autodiscovery/task/autodi | Endpoint | Method | | :- | :- | -| /api/centreon/autodiscovery/job | `POST` | +| /centreon/autodiscovery/job | `POST` | #### Headers @@ -150,7 +150,7 @@ curl --request POST "https://hostname:8443/api/centreon/autodiscovery/job" \ | Endpoint | Method | | :- | :- | -| /api/centreon/autodiscovery/job/:id | `GET` | +| /centreon/autodiscovery/job/:id | `GET` | #### Headers diff --git a/gorgone/docs/modules/centreon/broker.md b/gorgone/docs/modules/centreon/broker.md index d4d56a61368..64cfa550fba 100644 --- a/gorgone/docs/modules/centreon/broker.md +++ b/gorgone/docs/modules/centreon/broker.md @@ -8,7 +8,7 @@ This module aims to deal with Centreon Broker daemon. | Directive | Description | Default value | | :- | :- | :- | -| cache_dir | Path to the Centreon Broker statistics directory (local) use to store target's broker statistics | `/var/lib/centreon/broker-stats/` | +| cache_dir | Path to the Centreon Broker statistics directory (local) use to store node's broker statistics | `/var/lib/centreon/broker-stats/` | The configuration needs a cron definition to unsure that statistics collection will be done cyclically. @@ -33,16 +33,16 @@ cron: | Event | Description | | :- | :- | | BROKERREADY | Internal event to notify the core | -| BROKERSTATS | Collect Centreon Broker statistics files on target | +| BROKERSTATS | Collect Centreon Broker statistics files on node | ## API -### Collect Centreon Broker statistics on one or several targets +### Collect Centreon Broker statistics on one or several nodes | Endpoint | Method | | :- | :- | -| /api/centreon/broker/statistics | `GET` | -| /api/centreon/broker/statistics/:id | `GET` | +| /centreon/broker/statistics | `GET` | +| /centreon/broker/statistics/:id | `GET` | #### Headers @@ -54,7 +54,7 @@ cron: | Variable | Description | | :- | :- | -| id | Identifier of the target | +| id | Identifier of the node | #### Example diff --git a/gorgone/docs/modules/centreon/engine.md b/gorgone/docs/modules/centreon/engine.md index 944f83775aa..79e8ecf8847 100644 --- a/gorgone/docs/modules/centreon/engine.md +++ b/gorgone/docs/modules/centreon/engine.md @@ -32,7 +32,7 @@ command_file: "/var/lib/centreon-engine/rw/centengine.cmd" | Endpoint | Method | | :- | :- | -| /api/centreon/engine/command | `POST` | +| /centreon/engine/command | `POST` | #### Headers diff --git a/gorgone/docs/modules/centreon/nodes.md b/gorgone/docs/modules/centreon/nodes.md index 8afa7396d25..342bb480cf1 100644 --- a/gorgone/docs/modules/centreon/nodes.md +++ b/gorgone/docs/modules/centreon/nodes.md @@ -2,9 +2,9 @@ ## Description -This module aims to automatically register Poller servers as Gorgone targets, in opposition to the [register](../core/register.md) module. +This module aims to automatically register Poller servers as Gorgone nodes, in opposition to the [register](../core/register.md) module. -For now, targets can be registered as SSH targets or ZMQ targets. +For now, nodes can be registered as SSH nodes or ZMQ nodes. ## Configuration diff --git a/gorgone/docs/modules/core/action.md b/gorgone/docs/modules/core/action.md index 54127d2ae70..d53d7a2ef5e 100644 --- a/gorgone/docs/modules/core/action.md +++ b/gorgone/docs/modules/core/action.md @@ -33,7 +33,7 @@ command_timeout: 30 | Endpoint | Method | | :- | :- | -| /api/core/action/command | `POST` | +| /core/action/command | `POST` | #### Headers diff --git a/gorgone/docs/modules/core/cron.md b/gorgone/docs/modules/core/cron.md index 82834446e56..d1b90090769 100644 --- a/gorgone/docs/modules/core/cron.md +++ b/gorgone/docs/modules/core/cron.md @@ -50,8 +50,8 @@ cron: | Endpoint | Method | | :- | :- | -| /api/core/cron/definitions | `GET` | -| /api/core/cron/definitions/:id | `GET` | +| /core/cron/definitions | `GET` | +| /core/cron/definitions/:id | `GET` | #### Headers @@ -81,7 +81,7 @@ curl --request GET "https://hostname:8443/api/core/cron/definitions/echo_date" \ | Endpoint | Method | | :- | :- | -| /api/core/cron/definitions/:id/status | `GET` | +| /core/cron/definitions/:id/status | `GET` | #### Headers @@ -106,7 +106,7 @@ curl --request GET "https://hostname:8443/api/core/cron/definitions/echo_date/st | Endpoint | Method | | :- | :- | -| /api/core/cron/definitions | `POST` | +| /core/cron/definitions | `POST` | #### Headers @@ -163,7 +163,7 @@ curl --request POST "https://hostname:8443/api/core/cron/definitions" \ | Endpoint | Method | | :- | :- | -| /api/core/cron/definitions/:id | `PATCH` | +| /core/cron/definitions/:id | `PATCH` | #### Headers @@ -207,7 +207,7 @@ curl --request PATCH "https://hostname:8443/api/core/cron/definitions/job_123" \ | Endpoint | Method | | :- | :- | -| /api/core/cron/definitions/:id | `DELETE` | +| /core/cron/definitions/:id | `DELETE` | #### Headers diff --git a/gorgone/docs/modules/core/proxy.md b/gorgone/docs/modules/core/proxy.md index 8fb24cb6f6f..c023e6aff40 100644 --- a/gorgone/docs/modules/core/proxy.md +++ b/gorgone/docs/modules/core/proxy.md @@ -6,9 +6,9 @@ This module aims to give the possibility to Gorgone to become distributed. It is not needed in a Centreon standalone configuration, but must be enabled if there is Poller or Remote servers. -The module includes mechanisms like ping to make sure targets are alive, synchronisation to store logs in the Central Gorgone database, etc. +The module includes mechanisms like ping to make sure nodes are alive, synchronisation to store logs in the Central Gorgone database, etc. -A SSH client library make routing to non-gorgoned targets possible. +A SSH client library make routing to non-gorgoned nodes possible. ## Configuration @@ -17,8 +17,8 @@ A SSH client library make routing to non-gorgoned targets possible. | pool | Number of childs to instantiate to process events | `5` | | synchistory_time | Time in seconds between two logs synchronisation | `60` | | synchistory_timeout | Time in seconds before logs synchronisation is considered timed out | `30` | -| ping | Time in seconds between two target pings | `60` | -| pong_discard_timeout | Time in seconds before a target is considered dead | `300` | +| ping | Time in seconds between two node pings | `60` | +| pong_discard_timeout | Time in seconds before a node is considered dead | `300` | #### Example @@ -40,13 +40,13 @@ pong_discard_timeout: 300 | PROXYREADY | Internal event to notify the core | | REMOTECOPY | Copy files or directories from the server running the daemon to another server | | SETLOGS | Internal event to insert logs into the database | -| PONG | Internal event to handle target ping response | -| REGISTERNODES | Internal event to register targets | -| UNREGISTERNODES | Internal event to unregister targets | +| PONG | Internal event to handle node ping response | +| REGISTERNODES | Internal event to register nodes | +| UNREGISTERNODES | Internal event to unregister nodes | | PROXYADDNODE | Internal event to add nodes for proxying | | PROXYDELNODE | Internal event to delete nodes from proxying | | PROXYADDSUBNODE | Internal event to add nodes of nodes for proxying | -| PONGRESET | Internal event to deal with no pong targets | +| PONGRESET | Internal event to deal with no pong nodes | ## API diff --git a/gorgone/docs/modules/core/pull.md b/gorgone/docs/modules/core/pull.md index 5687859bf85..d62357089d1 100644 --- a/gorgone/docs/modules/core/pull.md +++ b/gorgone/docs/modules/core/pull.md @@ -2,7 +2,7 @@ ## Description -This module should be used on remote targets where the connection has to be opened from the target to the Central Gorgone. +This module should be used on remote nodes where the connection has to be opened from the node to the Central Gorgone. ## Configuration diff --git a/gorgone/docs/modules/core/register.md b/gorgone/docs/modules/core/register.md index 900db087806..5bdb37a78f5 100644 --- a/gorgone/docs/modules/core/register.md +++ b/gorgone/docs/modules/core/register.md @@ -2,9 +2,9 @@ ## Description -This module aims to provide a way to register targets manually, in opposition to the [pollers](../centreon/pollers.md) module. +This module aims to provide a way to register nodes manually, in opposition to the [pollers](../centreon/pollers.md) module. -Targets are either servers running Gorgone daemon or simple equipment with SSH server. +Nodes are either servers running Gorgone daemon or simple equipment with SSH server. ## Configuration @@ -12,7 +12,7 @@ There is no specific configuration in the Gorgone daemon configuration file, onl | Directive | Description | Default value | | :- | :- | :- | -| config_file | Path to the configuration file listing targets | | +| config_file | Path to the configuration file listing nodes | | #### Example @@ -23,22 +23,22 @@ enable: true config_file: config/registernodes.yml ``` -Targets are listed in a separate configuration file in a `nodes` table as below: +Nodes are listed in a separate configuration file in a `nodes` table as below: -##### Using ZMQ (Gorgone running on target) +##### Using ZMQ (Gorgone running on node) | Directive | Description | | :- | :- | -| id | Unique identifier of the target (can be Poller's ID if [nodes](../centreon/nodes.md) module is not used) | -| type | Way for the daemon to connect to the target (push_zmq) | -| address | IP address of the target | -| port | Port to connect to on the target | +| id | Unique identifier of the node (can be Poller's ID if [nodes](../centreon/nodes.md) module is not used) | +| type | Way for the daemon to connect to the node (push_zmq) | +| address | IP address of the node | +| port | Port to connect to on the node | | server_pubkey | Server public key (Default: ask the server pubkey when it connects) | | client_pubkey | Client public key (Default: use global public key) | | client_privkey | Client private key (Default: use global private key) | | cipher | Cipher used for encryption (Default: "Cipher::AES") | | vector | Encryption vector (Default: 0123456789012345) | -| nodes | Table to register subnodes managed by target (pathscore is not mandatory) | +| nodes | Table to register subnodes managed by node (pathscore is not mandatory) | #### Example @@ -59,13 +59,13 @@ nodes: | Directive | Description | | :- | :- | -| id | Unique identifier of the target (can be Poller's ID if [pollers](../centreon/pollers.md) module is not used) | -| type | Way for the daemon to connect to the target (push_ssh) | -| address | IP address of the target | -| ssh_port | Port to connect to on the target | +| id | Unique identifier of the node (can be Poller's ID if [pollers](../centreon/pollers.md) module is not used) | +| type | Way for the daemon to connect to the node (push_ssh) | +| address | IP address of the node | +| ssh_port | Port to connect to on the node | | ssh_username | SSH username (if no SSH key) | | ssh_password | SSH password (if no SSH key) | -| strict_serverkey_check | Boolean to strictly check the target fingerprint | +| strict_serverkey_check | Boolean to strictly check the node fingerprint | #### Example diff --git a/gorgone/docs/modules/plugins/newtest.md b/gorgone/docs/modules/plugins/newtest.md index 72f287c4b96..6705c91a37c 100644 --- a/gorgone/docs/modules/plugins/newtest.md +++ b/gorgone/docs/modules/plugins/newtest.md @@ -111,7 +111,7 @@ containers: | Endpoint | Method | | :- | :- | -| /api/plugins/newtest/resync | `GET` | +| /plugins/newtest/resync | `GET` | #### Headers diff --git a/gorgone/docs/modules/plugins/scom.md b/gorgone/docs/modules/plugins/scom.md index 8db958a9a25..a18477da000 100644 --- a/gorgone/docs/modules/plugins/scom.md +++ b/gorgone/docs/modules/plugins/scom.md @@ -74,7 +74,7 @@ containers: | Endpoint | Method | | :- | :- | -| /api/plugins/scom/resync | `GET` | +| /plugins/scom/resync | `GET` | #### Headers From 64446f79a0fffe1e57b84f2d9ba8b1fd8c0ec993 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 31 Dec 2019 11:53:39 +0100 Subject: [PATCH 298/948] enh(api): change targets noun to nodes for targeted api calls --- gorgone/gorgone/standard/api.pm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gorgone/gorgone/standard/api.pm b/gorgone/gorgone/standard/api.pm index 76f3f8e7934..a90417199ca 100644 --- a/gorgone/gorgone/standard/api.pm +++ b/gorgone/gorgone/standard/api.pm @@ -37,7 +37,7 @@ sub root { $options{logger}->writeLogInfo("[api] Requesting '" . $options{uri} . "' [" . $options{method} . "]"); my $response; - if ($options{method} eq 'GET' && $options{uri} =~ /^\/api\/(targets\/(\w*)\/)?log\/(.*)$/) { + if ($options{method} eq 'GET' && $options{uri} =~ /^\/api\/(nodes\/(\w*)\/)?log\/(.*)$/) { $response = get_log( socket => $options{socket}, target => $2, @@ -45,7 +45,7 @@ sub root { refresh => (defined($options{parameters}->{refresh})) ? $options{parameters}->{refresh} : undef, parameters => $options{parameters} ); - } elsif ($options{uri} =~ /^\/api\/(targets\/(\w*)\/)?internal\/(\w+)\/?([\w\/]*?)$/ + } elsif ($options{uri} =~ /^\/api\/(nodes\/(\w*)\/)?internal\/(\w+)\/?([\w\/]*?)$/ && defined($options{api_endpoints}->{$options{method} . '_/internal/' . $3})) { my @variables = split(/\//, $4); $response = call_internal( @@ -59,7 +59,7 @@ sub root { }, refresh => (defined($options{parameters}->{refresh})) ? $options{parameters}->{refresh} : undef ); - } elsif ($options{uri} =~ /^\/api\/(targets\/(\w*)\/)?(\w+)\/(\w+)\/(\w+)\/?([\w\/]*?)$/ + } elsif ($options{uri} =~ /^\/api\/(nodes\/(\w*)\/)?(\w+)\/(\w+)\/(\w+)\/?([\w\/]*?)$/ && defined($options{api_endpoints}->{$options{method} . '_/' . $3 . '/' . $4 . '/' . $5})) { my @variables = split(/\//, $6); $response = call_action( From f14324f244ed076ff1b77e1f5384b3bcffb2ea79 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Thu, 2 Jan 2020 15:38:03 +0100 Subject: [PATCH 299/948] enh(doc): add static redoc bundle --- gorgone/docs/api/centreon-logo.png | Bin 0 -> 27407 bytes gorgone/docs/{ => api}/gorgone-openapi.yaml | 320 ++++++------ gorgone/docs/api/index.html | 512 ++++++++++++++++++++ 3 files changed, 663 insertions(+), 169 deletions(-) create mode 100755 gorgone/docs/api/centreon-logo.png rename gorgone/docs/{ => api}/gorgone-openapi.yaml (85%) create mode 100644 gorgone/docs/api/index.html diff --git a/gorgone/docs/api/centreon-logo.png b/gorgone/docs/api/centreon-logo.png new file mode 100755 index 0000000000000000000000000000000000000000..5458fb678d4f0f12f82c7c0530aa942d7b850d8d GIT binary patch literal 27407 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 literal 0 HcmV?d00001 diff --git a/gorgone/docs/gorgone-openapi.yaml b/gorgone/docs/api/gorgone-openapi.yaml similarity index 85% rename from gorgone/docs/gorgone-openapi.yaml rename to gorgone/docs/api/gorgone-openapi.yaml index 848194dc121..79a31e9c4ad 100644 --- a/gorgone/docs/gorgone-openapi.yaml +++ b/gorgone/docs/api/gorgone-openapi.yaml @@ -83,7 +83,7 @@ paths: tags: - Internal summary: "Get nodes connection status" - description: "Get the connection status of all nodes managed by the Gorgone daemon" + description: "Get the connection status of all nodes managed by the Gorgone daemon." responses: '200': description: OK @@ -155,14 +155,11 @@ paths: summary: "Retrieve event's logs" description: "Retrieve the event's logs based on event's token." parameters: - - name: token - in: path - description: "Token of the event" - schema: - type: string - required: true - example: - "1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165" + - $ref: '#/components/parameters/Token' + - $ref: '#/components/parameters/Code' + - $ref: '#/components/parameters/Limit' + - $ref: '#/components/parameters/Ctime' + - $ref: '#/components/parameters/Etime' responses: '200': description: OK @@ -172,7 +169,6 @@ paths: oneOf: - $ref: '#/components/schemas/Logs' - $ref: '#/components/schemas/NoLogs' - - $ref: '#/components/schemas/Token' - $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' @@ -191,9 +187,9 @@ paths: application/json: schema: oneOf: + - $ref: '#/components/schemas/Token' - $ref: '#/components/schemas/Logs' - $ref: '#/components/schemas/NoLogs' - - $ref: '#/components/schemas/Token' - $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' @@ -217,29 +213,22 @@ paths: application/json: schema: oneOf: + - $ref: '#/components/schemas/Token' - $ref: '#/components/schemas/Logs' - $ref: '#/components/schemas/NoLogs' - - $ref: '#/components/schemas/Token' - $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' - /core/cron/definitions/{id}: + /core/cron/definitions/{definition_id}: get: tags: - Cron summary: "Get a definition" description: "List cron definition identified by id." parameters: - - name: id - in: path - description: "ID of the definition" - schema: - type: string - required: true - example: - "broker_stats" + - $ref: '#/components/parameters/DefinitionId' responses: '200': description: OK @@ -247,9 +236,9 @@ paths: application/json: schema: oneOf: + - $ref: '#/components/schemas/Token' - $ref: '#/components/schemas/Logs' - $ref: '#/components/schemas/NoLogs' - - $ref: '#/components/schemas/Token' - $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' @@ -261,14 +250,7 @@ paths: summary: "Update a definition" description: "Update a cron definition." parameters: - - name: id - in: path - description: "ID of the definition" - schema: - type: string - required: true - example: - "broker_stats" + - $ref: '#/components/parameters/DefinitionId' requestBody: required: true content: @@ -282,9 +264,9 @@ paths: application/json: schema: oneOf: + - $ref: '#/components/schemas/Token' - $ref: '#/components/schemas/Logs' - $ref: '#/components/schemas/NoLogs' - - $ref: '#/components/schemas/Token' - $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' @@ -296,14 +278,7 @@ paths: summary: "Delete a definition" description: "Delete a cron definition." parameters: - - name: id - in: path - description: "ID of the definition" - schema: - type: string - required: true - example: - "broker_stats" + - $ref: '#/components/parameters/DefinitionId' responses: '200': description: OK @@ -311,29 +286,22 @@ paths: application/json: schema: oneOf: + - $ref: '#/components/schemas/Token' - $ref: '#/components/schemas/Logs' - $ref: '#/components/schemas/NoLogs' - - $ref: '#/components/schemas/Token' - $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' - /core/cron/definitions/{id}/status: + /core/cron/definitions/{definition_id}/status: get: tags: - Cron summary: "Get a definition status" description: "Get a definition execution status." parameters: - - name: id - in: path - description: "ID of the definition" - schema: - type: string - required: true - example: - "broker_stats" + - $ref: '#/components/parameters/DefinitionId' responses: '200': description: OK @@ -341,9 +309,9 @@ paths: application/json: schema: oneOf: + - $ref: '#/components/schemas/Token' - $ref: '#/components/schemas/Logs' - $ref: '#/components/schemas/NoLogs' - - $ref: '#/components/schemas/Token' - $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' @@ -368,9 +336,9 @@ paths: application/json: schema: oneOf: + - $ref: '#/components/schemas/Token' - $ref: '#/components/schemas/Logs' - $ref: '#/components/schemas/NoLogs' - - $ref: '#/components/schemas/Token' - $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' @@ -397,9 +365,9 @@ paths: application/json: schema: oneOf: + - $ref: '#/components/schemas/Token' - $ref: '#/components/schemas/Logs' - $ref: '#/components/schemas/NoLogs' - - $ref: '#/components/schemas/Token' - $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' @@ -418,29 +386,22 @@ paths: application/json: schema: oneOf: + - $ref: '#/components/schemas/Token' - $ref: '#/components/schemas/Logs' - $ref: '#/components/schemas/NoLogs' - - $ref: '#/components/schemas/Token' - $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' - /centreon/broker/statistics/{id}: + /centreon/broker/statistics/{monitoring_server_id}: get: tags: - Broker summary: "Launch Broker statistics collection of a specific monitoring server" description: "Launch Broker statistics collection and store the result on disk." parameters: - - name: id - in: path - description: "ID of the monitoring server" - schema: - type: integer - required: true - example: - 2 + - $ref: '#/components/parameters/MonitoringServerId' responses: '200': description: OK @@ -448,9 +409,9 @@ paths: application/json: schema: oneOf: + - $ref: '#/components/schemas/Token' - $ref: '#/components/schemas/Logs' - $ref: '#/components/schemas/NoLogs' - - $ref: '#/components/schemas/Token' - $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' @@ -475,29 +436,22 @@ paths: application/json: schema: oneOf: + - $ref: '#/components/schemas/Token' - $ref: '#/components/schemas/Logs' - $ref: '#/components/schemas/NoLogs' - - $ref: '#/components/schemas/Token' - $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' - /centreon/autodiscovery/task/{id}: + /centreon/autodiscovery/task/{task_id}: get: tags: - Autodiscovery summary: "Get a discovery task results" description: "Get Centreon Autodiscovery task results." parameters: - - name: id - in: path - description: "ID of the task" - schema: - type: string - required: true - example: - "Task-SNMP-10.1.2.3" + - $ref: '#/components/parameters/TaskId' responses: '200': description: OK @@ -505,9 +459,9 @@ paths: application/json: schema: oneOf: + - $ref: '#/components/schemas/Token' - $ref: '#/components/schemas/Logs' - $ref: '#/components/schemas/NoLogs' - - $ref: '#/components/schemas/Token' - $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' @@ -532,29 +486,22 @@ paths: application/json: schema: oneOf: + - $ref: '#/components/schemas/Token' - $ref: '#/components/schemas/Logs' - $ref: '#/components/schemas/NoLogs' - - $ref: '#/components/schemas/Token' - $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' - /centreon/autodiscovery/job/{id}: + /centreon/autodiscovery/job/{job_id}: get: tags: - Autodiscovery summary: "Get a discovery job results" description: "Get Centreon Autodiscovery job results." parameters: - - name: id - in: path - description: "ID of the job" - schema: - type: string - required: true - example: - "Job-SNMP-10.1.2.3" + - $ref: '#/components/parameters/JobId' responses: '200': description: OK @@ -562,9 +509,9 @@ paths: application/json: schema: oneOf: + - $ref: '#/components/schemas/Token' - $ref: '#/components/schemas/Logs' - $ref: '#/components/schemas/NoLogs' - - $ref: '#/components/schemas/Token' - $ref: '#/components/schemas/Error' '401': $ref: '#/components/responses/Unauthorized' @@ -575,6 +522,83 @@ components: Basic Authentication: type: http scheme: basic + parameters: + Token: + in: path + name: token + required: true + description: "Token of the event" + schema: + type: string + example: "1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165" + Code: + in: query + name: code + required: false + description: "Only retrieve logs with defined code" + schema: + type: integer + enum: [0, 1, 2] + example: 2 + Limit: + in: query + name: limit + required: false + description: "Only retrieve the last x logs" + schema: + type: integer + minimum: 1 + example: 1 + Ctime: + in: query + name: ctime + required: false + description: "Only retrieve logs with a creation time equal or superior to a timestamp" + schema: + type: integer + format: int64 + example: 1577726040 + Etime: + in: query + name: etime + required: false + description: "Only retrieve logs of an event time superior to a timestamp" + schema: + type: integer + format: int64 + example: 1577726040 + DefinitionId: + in: path + name: definition_id + required: true + description: "ID of the definition" + schema: + type: string + example: "broker_stats" + MonitoringServerId: + in: path + name: monitoring_server_id + required: true + description: "ID of the monitoring server" + schema: + type: integer + example: 2 + TaskId: + in: path + name: task_id + required: true + description: "ID of the task" + schema: + type: string + example: "Task-SNMP-10.1.2.3" + JobId: + in: path + name: job_id + required: true + description: "ID of the job" + schema: + type: string + example: "Job-SNMP-10.1.2.3" responses: NotFound: description: "The specified resource was not found" @@ -596,13 +620,11 @@ components: error: type: string description: "Short error description" - example: - "http_error_401" + example: "http_error_401" message: type: string description: "Message explaining the error" - example: - "unauthorized" + example: "unauthorized" required: - error - message @@ -616,13 +638,11 @@ components: error: type: string description: "Short error description" - example: - "http_error_403" + example: "http_error_403" message: type: string description: "Message explaining the error" - example: - "forbidden" + example: "forbidden" required: - error - message @@ -636,13 +656,11 @@ components: error: type: string description: "Short error description" - example: - "method_unknown" + example: "method_unknown" message: type: string description: "Message explaining the error" - example: - "Method not implemented" + example: "Method not implemented" required: - error - message @@ -656,13 +674,11 @@ components: error: type: string description: "Short error description" - example: - "endpoint_unknown" + example: "endpoint_unknown" message: type: string description: "Message explaining the error" - example: - "endpoint not implemented" + example: "endpoint not implemented" required: - error - message @@ -686,22 +702,19 @@ components: type: string format: byte description: "Token related to the event's result" - example: - "1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165" + example: "1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165" Logs: type: object properties: message: type: string description: "Additionnal message" - example: - "Logs found" + example: "Logs found" token: type: string format: byte description: "Token related to the event's result" - example: - "03008486ba50b52e529ff5828d1432e5578dd18bb530c145b133dc902c8cfa6b8aac4d58fffb0c5ed44b943d2acbfb7cd1b18c55fcebce62e51999db460112c7" + example: "03008486ba50b52e529ff5828d1432e5578dd18bb530c145b133dc902c8cfa6b8aac4d58fffb0c5ed44b943d2acbfb7cd1b18c55fcebce62e51999db460112c7" data: type: array description: "Results array containing all logs related to token" @@ -714,23 +727,19 @@ components: type: string format: timestamp description: "Time when the server has stored the log in its database" - example: - 1577727699 + example: 1577727699 etime: type: string format: timestamp description: "Time when the event has occured" - example: - 1577727699 + example: 1577727699 id: type: integer description: "ID of the event" - example: - 101483 + example: 101483 instant: type: integer - example: - 0 + example: 0 data: type: object description: "Data stored for this event" @@ -738,31 +747,26 @@ components: type: string format: byte description: "Token related to the event" - example: - "03008486ba50b52e529ff5828d1432e5578dd18bb530c145b133dc902c8cfa6b8aac4d58fffb0c5ed44b943d2acbfb7cd1b18c55fcebce62e51999db460112c7" + example: "03008486ba50b52e529ff5828d1432e5578dd18bb530c145b133dc902c8cfa6b8aac4d58fffb0c5ed44b943d2acbfb7cd1b18c55fcebce62e51999db460112c7" code: type: integer description: "Returned code of the event" - example: - 2 + example: 2 NoLogs: type: object properties: error: type: string description: "Short error description" - example: - "no_log" + example: "no_log" message: type: string description: "Message explaining the error" - example: - "No log found for token" + example: "No log found for token" token: type: string description: "Token related to the event's result" - example: - "03008486ba50b52e529ff5828d1432e5578dd18bb530c145b133dc902c8cfa6b8aac4d58fffb0c5ed44b943d2acbfb7cd1b18c55fcebce62e51999db460112c7" + example: "03008486ba50b52e529ff5828d1432e5578dd18bb530c145b133dc902c8cfa6b8aac4d58fffb0c5ed44b943d2acbfb7cd1b18c55fcebce62e51999db460112c7" data: type: array description: "Empty array" @@ -774,13 +778,11 @@ components: action: type: string description: "Event sent to retrieve data" - example: - "constatus" + example: "constatus" message: type: string description: "Response message" - example: - "ok" + example: "ok" data: type: object properties: @@ -793,14 +795,12 @@ components: type: string format: timestamp description: "Last ping sent timestamp" - example: - 1577726040 + example: 1577726040 type: type: string enum: [push_zmq, pull_zmq, ssh] description: "Communication type" - example: - "push_zmq" + example: "push_zmq" nodes: type: object description: "Nodes managed by this Gorgone daemon" @@ -808,21 +808,18 @@ components: type: string format: timestamp description: "Last ping received timestamp" - example: - 1577726040 + example: 1577726040 Information: type: object properties: action: type: string description: "Event sent to retrieve data" - example: - "information" + example: "information" message: type: string description: "Response message" - example: - "ok" + example: "ok" data: type: object properties: @@ -872,8 +869,7 @@ components: total: type: integer description: "Total number of events processed since startup" - example: - 40210 + example: 40210 external: type: object description: "Number of external events since startup" @@ -927,13 +923,11 @@ components: action: type: string description: "Event sent to retrieve data" - example: - "getthumbprint" + example: "getthumbprint" message: type: string description: "Response message" - example: - "ok" + example: "ok" data: type: object properties: @@ -989,18 +983,15 @@ components: command: type: string description: "Command to execute" - example: - "echo data > /tmp/date.log" + example: "echo data > /tmp/date.log" timeout: type: integer description: "Time in seconds before a command is considered timed out" - example: - 5 + example: 5 continue_on_error: type: boolean description: "Behaviour in case of execution issue" - example: - true + example: true required: - command EngineCommands: @@ -1013,13 +1004,11 @@ components: command: type: string description: "External command" - example: - "[653284380] SCHEDULE_SVC_CHECK;host1;service1;653284380" + example: "[653284380] SCHEDULE_SVC_CHECK;host1;service1;653284380" command_file: type: string description: "Path to the Centreon Engine command file pipe" - example: - "/var/lib/centreon-engine/rw/centengine.cmd" + example: "/var/lib/centreon-engine/rw/centengine.cmd" required: - command AutodiscoveryTask: @@ -1028,8 +1017,7 @@ components: id: type: string description: "Identifier of the task (random if empty)" - example: - "Task-SNMP-10.1.2.3" + example: "Task-SNMP-10.1.2.3" command: type: string description: "Command line to execute to perform the discovery" @@ -1038,13 +1026,11 @@ components: timeout: type: integer description: "Time in seconds before the command is considered timed out" - example: - 300 + example: 300 target: type: integer description: "Identifier of the target on which to execute the command" - example: - 2 + example: 2 required: - command - target @@ -1054,13 +1040,11 @@ components: id: type: string description: "Identifier of the job (random if empty)" - example: - "Job-SNMP-10.1.2.3" + example: "Job-SNMP-10.1.2.3" timespec: type: string description: "Cron-like time specification" - example: - "30 2 * * *" + example: "30 2 * * *" command: type: string description: "Command line to execute to perform the discovery" @@ -1069,13 +1053,11 @@ components: timeout: type: integer description: "Time in seconds before the command is considered timed out" - example: - 300 + example: 300 target: type: integer description: "Identifier of the target on which to execute the command" - example: - 2 + example: 2 required: - timespec - command diff --git a/gorgone/docs/api/index.html b/gorgone/docs/api/index.html new file mode 100644 index 00000000000..7344000e305 --- /dev/null +++ b/gorgone/docs/api/index.html @@ -0,0 +1,512 @@ + + + + + + Centreon Gorgone RestAPI + + + + + + + + + + + + + + \ No newline at end of file From 46f682a39cc7e281ac3cb6d5300e9f975521c6c2 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 7 Jan 2020 13:59:00 +0100 Subject: [PATCH 300/948] enh(core): add history for some internal events --- gorgone/gorgone/class/core.pm | 11 +++++++++++ gorgone/gorgone/standard/api.pm | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 0f4cae0031c..540c0cc4f47 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -479,6 +479,17 @@ sub message_run { token => $token, logger => $self->{logger} ); + + if ($action =~ /^(?:CONSTATUS|INFORMATION|GETTHUMBPRINT)$/) { + gorgone::standard::library::add_history( + dbh => $self->{db_gorgone}, + code => 0, + token => $token, + data => $response, + json_encode => 1 + ); + } + return ($token, $code, $response, $response_type); } elsif ($action =~ /^BCAST(.*)$/) { return (undef, 1, { message => "action '$action' is not known" }) if ($1 !~ /^LOGGER$/); diff --git a/gorgone/gorgone/standard/api.pm b/gorgone/gorgone/standard/api.pm index a90417199ca..e640966fd13 100644 --- a/gorgone/gorgone/standard/api.pm +++ b/gorgone/gorgone/standard/api.pm @@ -154,7 +154,7 @@ sub call_internal { my $rev = zmq_poll($poll, 5000); - my $response = '{"error":"no_result", "message":"No result found for action "' . $options{action} . '"}'; + my $response = '{"error":"no_result", "message":"No result found for action \'' . $options{action} . '\'"}'; if (defined($result->{data})) { my $content; eval { From d7517f4df18f84c93dd0e5ddf0603aaea6f0e351 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 20 Jan 2020 14:43:48 +0100 Subject: [PATCH 301/948] enh(legacycmd): add RELOADBROKER command Refs: https://github.com/centreon/centreon/pull/8062 --- .../modules/centreon/legacycmd/class.pm | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index 1f4e9f75152..eaf16645b58 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -357,6 +357,24 @@ sub execute_cmd { ] }, ); + } elsif ($options{cmd} eq 'RELOADBROKER') { + my $cmd = $self->{pollers}->{$options{target}}->{broker_reload_command}; + $self->send_internal_action( + action => 'COMMAND', + target => $options{target}, + token => $self->generate_token(), + data => { + content => [ + { + command => 'sudo ' . $cmd, + metadata => { + centcore_proxy => 1, + centcore_cmd => 'RELOADBROKER', + } + } + ] + }, + ); } elsif ($options{cmd} eq 'RESTARTCENTREONTRAPD') { my $cmd = $self->{pollers}->{$options{target}}->{init_script_centreontrapd}; $self->send_internal_action( From f153aa61b5b3ac0b267c04483e99a4a63464f999 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 20 Jan 2020 15:13:52 +0100 Subject: [PATCH 302/948] enh(core): enhance logging --- gorgone/gorgone/class/core.pm | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 540c0cc4f47..1be399a0799 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -415,10 +415,10 @@ sub message_run { dbh => $self->{db_gorgone}, code => 1, token => $token, - data => { msg => "action '$action' is not known" }, + data => { error => "unknown_action", message => "action '$action' is unknown" }, json_encode => 1 ); - return (undef, 1, { message => "action '$action' is not known" }); + return (undef, 1, { error => "unknown_action", message => "action '$action' is unknown" }); } $self->{counters}->{$options{router_type}}->{lc($action)} = 0 if (!defined($self->{counters}->{$options{router_type}}->{lc($action)})); @@ -431,10 +431,10 @@ sub message_run { dbh => $self->{db_gorgone}, code => 1, token => $token, - data => { msg => 'gorgone is stopping/restarting. Not proceed request.' }, + data => { message => 'gorgone is stopping/restarting. Cannot proceed request.' }, json_encode => 1 ); - return ($token, 1, { message => 'gorgone is stopping/restarting. Not proceed request.' }); + return ($token, 1, { message => 'gorgone is stopping/restarting. Cannot proceed request.' }); } # Check Routing @@ -445,10 +445,10 @@ sub message_run { dbh => $self->{db_gorgone}, code => 1, token => $token, - data => { msg => 'no proxy configured. cannot manage target.' }, + data => { error => "no_proxy", message => 'no proxy configured. cannot manage target.' }, json_encode => 1 ); - return ($token, 1, { message => 'no proxy configured. cannot manage target.' }); + return ($token, 1, { error => "no_proxy", message => 'no proxy configured. cannot manage target.' }); } $self->{counters}->{proxy}->{lc($action)} = 0 if (!defined($self->{counters}->{proxy}->{lc($action)})); @@ -735,7 +735,7 @@ sub run { if (gorgone::standard::library::add_history( dbh => $gorgone->{db_gorgone}, code => 0, - data => { msg => 'gorgoned is starting...' }, + data => { message => 'gorgoned is starting...' }, json_encode => 1) == -1 ) { $gorgone->{logger}->writeLogInfo("[core] Cannot write in history. We quit!!"); From e50b6e55264d0727b7f8df6ad1af7baa600b8075 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 21 Jan 2020 18:28:49 +0100 Subject: [PATCH 303/948] enh(api): allow wait for internal endpoints --- gorgone/gorgone/standard/api.pm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gorgone/gorgone/standard/api.pm b/gorgone/gorgone/standard/api.pm index e640966fd13..57acbdd4faa 100644 --- a/gorgone/gorgone/standard/api.pm +++ b/gorgone/gorgone/standard/api.pm @@ -57,6 +57,7 @@ sub root { parameters => $options{parameters}, variables => \@variables, }, + wait => (defined($options{parameters}->{wait})) ? $options{parameters}->{wait} : undef, refresh => (defined($options{parameters}->{refresh})) ? $options{parameters}->{refresh} : undef ); } elsif ($options{uri} =~ /^\/api\/(nodes\/(\w*)\/)?(\w+)\/(\w+)\/(\w+)\/?([\w\/]*?)$/ @@ -141,6 +142,7 @@ sub call_internal { action => $options{action}, data => $options{data}, json_encode => 1, + wait => $options{wait}, refresh => $options{refresh} ); } From 75a94c37f2c2ff0dd274d9d73981dee7641a6c4d Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 22 Jan 2020 09:51:58 +0100 Subject: [PATCH 304/948] enh(api): change wait parameters --- gorgone/gorgone/standard/api.pm | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/gorgone/gorgone/standard/api.pm b/gorgone/gorgone/standard/api.pm index 57acbdd4faa..8d3a0dc2880 100644 --- a/gorgone/gorgone/standard/api.pm +++ b/gorgone/gorgone/standard/api.pm @@ -42,7 +42,7 @@ sub root { socket => $options{socket}, target => $2, token => $3, - refresh => (defined($options{parameters}->{refresh})) ? $options{parameters}->{refresh} : undef, + sync_wait => (defined($options{parameters}->{sync_wait})) ? $options{parameters}->{sync_wait} : undef, parameters => $options{parameters} ); } elsif ($options{uri} =~ /^\/api\/(nodes\/(\w*)\/)?internal\/(\w+)\/?([\w\/]*?)$/ @@ -57,8 +57,8 @@ sub root { parameters => $options{parameters}, variables => \@variables, }, - wait => (defined($options{parameters}->{wait})) ? $options{parameters}->{wait} : undef, - refresh => (defined($options{parameters}->{refresh})) ? $options{parameters}->{refresh} : undef + log_wait => (defined($options{parameters}->{log_wait})) ? $options{parameters}->{log_wait} : undef, + sync_wait => (defined($options{parameters}->{sync_wait})) ? $options{parameters}->{sync_wait} : undef ); } elsif ($options{uri} =~ /^\/api\/(nodes\/(\w*)\/)?(\w+)\/(\w+)\/(\w+)\/?([\w\/]*?)$/ && defined($options{api_endpoints}->{$options{method} . '_/' . $3 . '/' . $4 . '/' . $5})) { @@ -72,8 +72,8 @@ sub root { parameters => $options{parameters}, variables => \@variables, }, - wait => (defined($options{parameters}->{wait})) ? $options{parameters}->{wait} : undef, - refresh => (defined($options{parameters}->{refresh})) ? $options{parameters}->{refresh} : undef, + log_wait => (defined($options{parameters}->{log_wait})) ? $options{parameters}->{log_wait} : undef, + sync_wait => (defined($options{parameters}->{sync_wait})) ? $options{parameters}->{sync_wait} : undef, ); } else { $response = '{"error":"method_unknown","message":"Method not implemented"}'; @@ -106,13 +106,13 @@ sub call_action { my $response = '{"error":"no_token","message":"Cannot retrieve token from ack"}'; if (defined($result->{token}) && $result->{token} ne '') { - if (defined($options{wait}) && $options{wait} ne '') { - Time::HiRes::usleep($options{wait}); + if (defined($options{log_wait}) && $options{log_wait} ne '') { + Time::HiRes::usleep($options{log_wait}); $response = get_log( socket => $options{socket}, target => $options{target}, token => $result->{token}, - refresh => $options{refresh}, + sync_wait => $options{sync_wait}, parameters => $options{data}->{parameters} ); } else { @@ -142,8 +142,8 @@ sub call_internal { action => $options{action}, data => $options{data}, json_encode => 1, - wait => $options{wait}, - refresh => $options{refresh} + log_wait => $options{log_wait}, + sync_wait => $options{sync_wait} ); } @@ -201,8 +201,8 @@ sub get_log { json_encode => 1 ); - my $refresh_wait = (defined($options{refresh}) && $options{refresh} ne '') ? $options{refresh} : '10000'; - Time::HiRes::usleep($refresh_wait); + my $sync_wait = (defined($options{sync_wait}) && $options{sync_wait} ne '') ? $options{sync_wait} : '10000'; + Time::HiRes::usleep($sync_wait); my $rev = zmq_poll($poll, 5000); } From 66542e47d1ac2fe67f29996c26b9e54a9b7e0985 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 22 Jan 2020 13:24:37 +0100 Subject: [PATCH 305/948] enh(doc): enhance API doc --- gorgone/docs/api.md | 90 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/gorgone/docs/api.md b/gorgone/docs/api.md index 2bb2b80514c..db4e5686ede 100644 --- a/gorgone/docs/api.md +++ b/gorgone/docs/api.md @@ -23,6 +23,23 @@ curl --request GET "https://hostname:8443/api/internal/constatus" \ --header "Accept: application/json" ``` +#### Response example + +```json +{ + "action": "constatus", + "data": { + "2": { + "last_ping_sent": 1579684258, + "type": "push_zmq", + "nodes": {}, + "last_ping_recv": 1579684258 + } + }, + "message": "ok" +} +``` + ### Get Public Key Thumbprint | Endpoint | Method | @@ -42,6 +59,18 @@ curl --request GET "https://hostname:8443/api/internal/thumbprint" \ --header "Accept: application/json" ``` +#### Response example + +```json +{ + "action": "getthumbprint", + "data": { + "thumbprint": "cS4B3lZq96qcP4FTMhVMuwAhztqRBQERKyhnEitnTFM" + }, + "message": "ok" +} +``` + ### Get Runtime Informations And Statistics | Endpoint | Method | @@ -158,6 +187,58 @@ As Centreon Gorgone is asynchronous, those endpoints will return a token corresp } ``` +That being said, its possible to make Gorgone work synchronously by providing two parameters. + +First one is `log_wait` with a numeric value in microseconds: this value defines the amount of time the API will wait before trying to retrieve log results. + +Second one is `sync_wait` with a numeric value in microseconds: this value defines the amount of time the API will wait after asking for logs synchronisation if a remote node is involved. + +Note: the `sync_wait` parameter is induced if you ask for a log directly specifying a node, by using the log endpoint, and the default value is 10000 microseconds (10 milliseconds). + +#### Examples + +##### Launch a command locally and wait for the result + +Using the `/core/action/command` endpoint with `log_wait` parameter set to 100000: + +```bash +curl --request POST "https://hostname:8443/api/core/action/command&log_wait=100000" \ + --header "Accept: application/json" \ + --header "Content-Type: application/json" \ + --data "[ + { + \"command\": \"echo 'Test command'\" + } +]" +``` + +This call will ask for the API to execute an action and will give a result after 100ms that can be: + +* Logs, like the log endpoint could provide, +* A no_log error with a token to retrieve the logs later. + +Note: there is no need for logs synchronisation when dealing with local actions. + +##### Launch a command remotly and wait for the result + +Using the `/nodes/:id/core/action/command` endpoint with `log_wait` parameter set to 100000: + +```bash +curl --request POST "https://hostname:8443/api/nodes/2/core/action/command&log_wait=100000&sync_wait=200000" \ + --header "Accept: application/json" \ + --header "Content-Type: application/json" \ + --data "[ + { + \"command\": \"echo 'Test command'\" + } +]" +``` + +This call will ask for the API to execute an action on the node with ID 2, will then wait for 100ms before getting a result, but will wait for an extra 200ms for logs synchronisation before giving a result, that can be: + +* Logs, like the log endpoint could provide, +* A no_log error with a token to retrieve the logs later. + ## Log endpoint To retrieve the logs, a specific endpoint can be called as follow. @@ -178,13 +259,20 @@ To retrieve the logs, a specific endpoint can be called as follow. | :- | :- | | token | Token of the action | -#### Example +#### Examples ```bash curl --request GET "https://hostname:8443/api/log/3f25bc3a797fe989d1fb052b1886a806e73fe2d8ccfc6377ee3d4490f8ad03c02cb2533edcc1b3d8e1770e28d6f2de83bd98923b66c0c33395e5f835759de4b1" \ --header "Accept: application/json" ``` +```bash +curl --request GET "https://hostname:8443/api/nodes/2/log/3f25bc3a797fe989d1fb052b1886a806e73fe2d8ccfc6377ee3d4490f8ad03c02cb2533edcc1b3d8e1770e28d6f2de83bd98923b66c0c33395e5f835759de4b1" \ + --header "Accept: application/json" +``` + +This second example will force logs synchonisation before looking for results to retrieve. Default temporisation is 10ms and can be changed by providing `sync_wait` parameter. + #### Response example ```json From 2018c4f8e8302864489011cc9626a496dba01e3d Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 22 Jan 2020 14:47:50 +0100 Subject: [PATCH 306/948] enh(packaging): new perl modules --- .../perl-Clone-Choose-0.010-1.el7.noarch.rpm | Bin 0 -> 9708 bytes .../packaging/packages/perl-Clone-Choose.spec | 51 +++++++++++++++++ .../perl-Hash-Merge-0.300-1.el7.noarch.rpm | Bin 0 -> 14768 bytes .../packaging/packages/perl-Hash-Merge.spec | 53 ++++++++++++++++++ .../perl-YAML-LibYAML-0.80-1.el7.x86_64.rpm | Bin 0 -> 77612 bytes .../packaging/packages/perl-YAML-LibYAML.spec | 47 ++++++++++++++++ 6 files changed, 151 insertions(+) create mode 100644 gorgone/packaging/packages/perl-Clone-Choose-0.010-1.el7.noarch.rpm create mode 100644 gorgone/packaging/packages/perl-Clone-Choose.spec create mode 100644 gorgone/packaging/packages/perl-Hash-Merge-0.300-1.el7.noarch.rpm create mode 100644 gorgone/packaging/packages/perl-Hash-Merge.spec create mode 100644 gorgone/packaging/packages/perl-YAML-LibYAML-0.80-1.el7.x86_64.rpm create mode 100644 gorgone/packaging/packages/perl-YAML-LibYAML.spec diff --git a/gorgone/packaging/packages/perl-Clone-Choose-0.010-1.el7.noarch.rpm b/gorgone/packaging/packages/perl-Clone-Choose-0.010-1.el7.noarch.rpm new file mode 100644 index 0000000000000000000000000000000000000000..fb1a22d349c73a50b16eb7b67c01cf4c38959b05 GIT binary patch literal 9708 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 QeVgUL|Dg-IXkq#P08TgF(*OVf literal 0 HcmV?d00001 diff --git a/gorgone/packaging/packages/perl-Clone-Choose.spec b/gorgone/packaging/packages/perl-Clone-Choose.spec new file mode 100644 index 00000000000..5390763404e --- /dev/null +++ b/gorgone/packaging/packages/perl-Clone-Choose.spec @@ -0,0 +1,51 @@ +%define cpan_name Clone-Choose + +Name: perl-Clone-Choose +Version: 0.010 +Release: 1%{?dist} +Summary: Choose appropriate clone utility +Group: Development/Libraries +License: GPL or Artistic +URL: https://metacpan.org/pod/Clone::Choose +Source0: https://cpan.metacpan.org/authors/id/H/HE/HERMES/%{cpan_name}-%{version}.tar.gz +BuildArch: noarch +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + +BuildRequires: perl(ExtUtils::MakeMaker) +BuildRequires: make + +Provides: perl(Clone::Choose) +AutoReqProv: no + +%description +Clone::Choose checks several different modules which provides a clone() function and selects an appropriate one. + +%prep +%setup -q -n %{cpan_name}-%{version} + +%build +%{__perl} Makefile.PL INSTALLDIRS=vendor OPTIMIZE="$RPM_OPT_FLAGS" +make %{?_smp_mflags} + +%install +rm -rf %{buildroot} +make pure_install PERL_INSTALL_ROOT=$RPM_BUILD_ROOT +find $RPM_BUILD_ROOT -type f -name .packlist -exec rm -f {} ';' +find $RPM_BUILD_ROOT -type f -name '*.bs' -a -size 0 -exec rm -f {} ';' +find $RPM_BUILD_ROOT -type d -depth -exec rmdir {} 2>/dev/null ';' +%{_fixperms} $RPM_BUILD_ROOT/* + +%check +#make test + +%clean +rm -rf %{buildroot} + +%files +%defattr(-,root,root,-) +%doc Changes +%{perl_vendorlib} +%{_mandir}/man3/*.3* + +%changelog + diff --git a/gorgone/packaging/packages/perl-Hash-Merge-0.300-1.el7.noarch.rpm b/gorgone/packaging/packages/perl-Hash-Merge-0.300-1.el7.noarch.rpm new file mode 100644 index 0000000000000000000000000000000000000000..5856fc131d0c77893b44478abefef00529000b9e GIT binary patch literal 14768 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 HUA_MW?T(jw literal 0 HcmV?d00001 diff --git a/gorgone/packaging/packages/perl-Hash-Merge.spec b/gorgone/packaging/packages/perl-Hash-Merge.spec new file mode 100644 index 00000000000..c2088f31b99 --- /dev/null +++ b/gorgone/packaging/packages/perl-Hash-Merge.spec @@ -0,0 +1,53 @@ +%define cpan_name Hash-Merge + +Name: perl-Hash-Merge +Version: 0.300 +Release: 1%{?dist} +Summary: Merges arbitrarily deep hashes into a single hash +Group: Development/Libraries +License: GPL or Artistic +URL: https://metacpan.org/pod/Hash::Merge +Source0: https://cpan.metacpan.org/authors/id/R/RE/REHSACK/%{cpan_name}-%{version}.tar.gz +BuildArch: noarch +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + +BuildRequires: perl(ExtUtils::MakeMaker) +BuildRequires: make + +Provides: perl(Hash::Merge) +Requires: perl(Scalar::Util) +Requires: perl(Clone::Choose) +AutoReqProv: no + +%description +Hash::Merge merges two arbitrarily deep hashes into a single hash. That is, at any level, it will add non-conflicting key-value pairs from one hash to the other, and follows a set of specific rules when there are key value conflicts (as outlined below). The hash is followed recursively, so that deeply nested hashes that are at the same level will be merged when the parent hashes are merged. Please note that self-referencing hashes, or recursive references, are not handled well by this method. + +%prep +%setup -q -n %{cpan_name}-%{version} + +%build +%{__perl} Makefile.PL INSTALLDIRS=vendor OPTIMIZE="$RPM_OPT_FLAGS" +make %{?_smp_mflags} + +%install +rm -rf %{buildroot} +make pure_install PERL_INSTALL_ROOT=$RPM_BUILD_ROOT +find $RPM_BUILD_ROOT -type f -name .packlist -exec rm -f {} ';' +find $RPM_BUILD_ROOT -type f -name '*.bs' -a -size 0 -exec rm -f {} ';' +find $RPM_BUILD_ROOT -type d -depth -exec rmdir {} 2>/dev/null ';' +%{_fixperms} $RPM_BUILD_ROOT/* + +%check +#make test + +%clean +rm -rf %{buildroot} + +%files +%defattr(-,root,root,-) +%doc Changes +%{perl_vendorlib} +%{_mandir}/man3/*.3* + +%changelog + diff --git a/gorgone/packaging/packages/perl-YAML-LibYAML-0.80-1.el7.x86_64.rpm b/gorgone/packaging/packages/perl-YAML-LibYAML-0.80-1.el7.x86_64.rpm new file mode 100644 index 0000000000000000000000000000000000000000..063a3ac87a28cf8c50760ca634e56d95d6de207f GIT binary patch literal 77612 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 FSz6^gi4*_; literal 0 HcmV?d00001 diff --git a/gorgone/packaging/packages/perl-YAML-LibYAML.spec b/gorgone/packaging/packages/perl-YAML-LibYAML.spec new file mode 100644 index 00000000000..42e01e77934 --- /dev/null +++ b/gorgone/packaging/packages/perl-YAML-LibYAML.spec @@ -0,0 +1,47 @@ +%define cpan_name YAML-LibYAML + +Name: perl-YAML-LibYAML +Version: 0.80 +Release: 1%{?dist} +Summary: Perl YAML Serialization using XS and libyaml +Group: Development/Libraries +License: GPL or Artistic +URL: https://metacpan.org/release/YAML-LibYAML +Source0: https://cpan.metacpan.org/authors/id/T/TI/TINITA/%{cpan_name}-%{version}.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + +BuildRequires: make +BuildRequires: gcc + +%description +Kirill Simonov's libyaml is arguably the best YAML implementation. The C library is written precisely to the YAML 1.1 specification. It was originally bound to Python and was later bound to Ruby. +This module is a Perl XS binding to libyaml which offers Perl the best YAML support to date. + +%prep +%setup -q -n %{cpan_name}-%{version} + +%build +%{__perl} Makefile.PL INSTALLDIRS=vendor OPTIMIZE="$RPM_OPT_FLAGS" +make %{?_smp_mflags} + +%install +rm -rf %{buildroot} +make pure_install PERL_INSTALL_ROOT=$RPM_BUILD_ROOT +find $RPM_BUILD_ROOT -type f -name .packlist -exec rm -f {} ';' +find $RPM_BUILD_ROOT -type f -name '*.bs' -a -size 0 -exec rm -f {} ';' +find $RPM_BUILD_ROOT -type d -depth -exec rmdir {} 2>/dev/null ';' +%{_fixperms} $RPM_BUILD_ROOT/* + +%check +#make test + +%clean +rm -rf %{buildroot} + +%files +%defattr(-,root,root,-) +%{perl_vendorarch} +%{_mandir}/man3/*.3* + +%changelog + From 3a69ee30050ffb9f4486387971634fef4557d37c Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 22 Jan 2020 16:40:45 +0100 Subject: [PATCH 307/948] enh(core): new yaml configuration --- gorgone/config/gorgoned-central-ssh.yml | 90 ++++++------ gorgone/config/gorgoned-central-zmq.yml | 136 +++++++++--------- gorgone/config/gorgoned-poller.yml | 31 ++-- gorgone/config/gorgoned-remote-ssh.yml | 66 ++++----- gorgone/config/gorgoned-remote-zmq.yml | 78 +++++----- gorgone/docs/getting_started.md | 7 +- gorgone/gorgone/class/core.pm | 183 ++++++++++++------------ gorgone/gorgone/class/script.pm | 83 +++++++++++ 8 files changed, 384 insertions(+), 290 deletions(-) diff --git a/gorgone/config/gorgoned-central-ssh.yml b/gorgone/config/gorgoned-central-ssh.yml index 2929771516a..7af8868f7f7 100644 --- a/gorgone/config/gorgoned-central-ssh.yml +++ b/gorgone/config/gorgoned-central-ssh.yml @@ -1,51 +1,53 @@ name: gorgoned-central-ssh description: Configuration example in a SSH environment for Central server -database: - db_centreon: - dsn: "mysql:host=localhost;dbname=centreon" - username: centreon - password: centreon - db_centstorage: - dsn: "mysql:host=localhost;dbname=centreon_storage" - username: centreon - password: centreon -gorgonecore: - timeout: 50 -modules: - - name: httpserver - package: gorgone::modules::core::httpserver::hooks - enable: true - address: 0.0.0.0 - port: 8443 - ssl: true - ssl_cert_file: /etc/pki/tls/certs/server-cert.pem - ssl_key_file: /etc/pki/tls/server-key.pem - auth: - enabled: true - user: admin - password: password +common: + database: + db_configuration: + dsn: "mysql:host=localhost;dbname=centreon" + username: centreon + password: centreon + db_realtime: + dsn: "mysql:host=localhost;dbname=centreon_storage" + username: centreon + password: centreon +gorgone: + gorgonecore: + timeout: 50 + modules: + - name: httpserver + package: gorgone::modules::core::httpserver::hooks + enable: true + address: 0.0.0.0 + port: 8443 + ssl: true + ssl_cert_file: /etc/pki/tls/certs/server-cert.pem + ssl_key_file: /etc/pki/tls/server-key.pem + auth: + enabled: true + user: admin + password: password - - name: action - package: gorgone::modules::core::action::hooks - enable: true + - name: action + package: gorgone::modules::core::action::hooks + enable: true - - name: proxy - package: gorgone::modules::core::proxy::hooks - enable: true + - name: proxy + package: gorgone::modules::core::proxy::hooks + enable: true - - name: pollers - package: gorgone::modules::centreon::pollers::hooks - enable: true + - name: nodes + package: gorgone::modules::centreon::nodes::hooks + enable: true - - name: legacycmd - package: gorgone::modules::centreon::legacycmd::hooks - enable: true - cmd_file: "/var/lib/centreon/centcore.cmd" - cache_dir: "/var/cache/centreon/" - cache_dir_trap: "/etc/snmp/centreon_traps/" - remote_dir: "/var/lib/centreon/remote-data/" + - name: legacycmd + package: gorgone::modules::centreon::legacycmd::hooks + enable: true + cmd_file: "/var/lib/centreon/centcore.cmd" + cache_dir: "/var/cache/centreon/" + cache_dir_trap: "/etc/snmp/centreon_traps/" + remote_dir: "/var/lib/centreon/remote-data/" - - name: engine - package: gorgone::modules::centreon::engine::hooks - enable: true - command_file: "/var/lib/centreon-engine/rw/centengine.cmd" + - name: engine + package: gorgone::modules::centreon::engine::hooks + enable: true + command_file: "/var/lib/centreon-engine/rw/centengine.cmd" diff --git a/gorgone/config/gorgoned-central-zmq.yml b/gorgone/config/gorgoned-central-zmq.yml index 52a09be2b82..a05a2199dd8 100644 --- a/gorgone/config/gorgoned-central-zmq.yml +++ b/gorgone/config/gorgoned-central-zmq.yml @@ -1,76 +1,78 @@ name: gorgoned-central-zmq description: Configuration example in a full ZMQ environment for Central server -database: - db_centreon: - dsn: "mysql:host=localhost;dbname=centreon" - username: centreon - password: centreon - db_centstorage: - dsn: "mysql:host=localhost;dbname=centreon_storage" - username: centreon - password: centreon -gorgonecore: - id: 1 - privkey: keys/central/privkey.pem - # can be: always, first (default), strict - fingerprint_mode: first - fingerprint_mgr: - package: gorgone::class::fingerprint::backend::sql - # if unset, it uses global configuration - #gorgone_db_type: - #gorgone_db_name: -modules: - - name: httpserver - package: gorgone::modules::core::httpserver::hooks - enable: true - address: 0.0.0.0 - port: 8443 - ssl: true - ssl_cert_file: /etc/pki/tls/certs/server-cert.pem - ssl_key_file: /etc/pki/tls/server-key.pem - auth: - enabled: true - user: admin - password: password - allowed_hosts: +common: + database: + db_configuration: + dsn: "mysql:host=localhost;dbname=centreon" + username: centreon + password: centreon + db_realtime: + dsn: "mysql:host=localhost;dbname=centreon_storage" + username: centreon + password: centreon +gorgone: + gorgonecore: + id: 1 + privkey: keys/central/privkey.pem + # can be: always, first (default), strict + fingerprint_mode: first + fingerprint_mgr: + package: gorgone::class::fingerprint::backend::sql + # if unset, it uses global configuration + #gorgone_db_type: + #gorgone_db_name: + modules: + - name: httpserver + package: gorgone::modules::core::httpserver::hooks + enable: true + address: 0.0.0.0 + port: 8443 + ssl: true + ssl_cert_file: /etc/pki/tls/certs/server-cert.pem + ssl_key_file: /etc/pki/tls/server-key.pem + auth: enabled: true - subnets: - - 127.0.0.1/32 - - 10.30.2.0/16 + user: admin + password: password + allowed_hosts: + enabled: true + subnets: + - 127.0.0.1/32 + - 10.30.2.0/16 - - name: cron - package: gorgone::modules::core::cron::hooks - enable: true - cron: - - id: echo_date - timespec: "* * * * *" - action: COMMAND - parameters: - command: "date >> /tmp/date.log" - timeout: 10 + - name: cron + package: gorgone::modules::core::cron::hooks + enable: true + cron: + - id: echo_date + timespec: "* * * * *" + action: COMMAND + parameters: + command: "date >> /tmp/date.log" + timeout: 10 - - name: action - package: gorgone::modules::core::action::hooks - enable: true + - name: action + package: gorgone::modules::core::action::hooks + enable: true - - name: proxy - package: gorgone::modules::core::proxy::hooks - enable: true + - name: proxy + package: gorgone::modules::core::proxy::hooks + enable: true - - name: register - package: gorgone::modules::core::register::hooks - enable: true - config_file: config/registernodes-central.yml + - name: register + package: gorgone::modules::core::register::hooks + enable: true + config_file: config/registernodes-central.yml - - name: legacycmd - package: gorgone::modules::centreon::legacycmd::hooks - enable: true - cmd_file: "/var/lib/centreon/centcore.cmd" - cache_dir: "/var/cache/centreon/" - cache_dir_trap: "/etc/snmp/centreon_traps/" - remote_dir: "/var/lib/centreon/remote-data/" + - name: legacycmd + package: gorgone::modules::centreon::legacycmd::hooks + enable: true + cmd_file: "/var/lib/centreon/centcore.cmd" + cache_dir: "/var/cache/centreon/" + cache_dir_trap: "/etc/snmp/centreon_traps/" + remote_dir: "/var/lib/centreon/remote-data/" - - name: engine - package: gorgone::modules::centreon::engine::hooks - enable: true - command_file: "/var/lib/centreon-engine/rw/centengine.cmd" + - name: engine + package: gorgone::modules::centreon::engine::hooks + enable: true + command_file: "/var/lib/centreon-engine/rw/centengine.cmd" diff --git a/gorgone/config/gorgoned-poller.yml b/gorgone/config/gorgoned-poller.yml index 3a12c5958ca..511caf08eee 100644 --- a/gorgone/config/gorgoned-poller.yml +++ b/gorgone/config/gorgoned-poller.yml @@ -1,18 +1,19 @@ name: gorgoned-poller description: Configuration example in a full ZMQ environment for Poller server -gorgonecore: - id: 2 - external_com_type: tcp - external_com_path: "*:5556" - privkey: keys/poller/privkey.pem - authorized_clients: - - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 -modules: - - name: action - package: gorgone::modules::core::action::hooks - enable: true +gorgone: + gorgonecore: + id: 2 + external_com_type: tcp + external_com_path: "*:5556" + privkey: keys/poller/privkey.pem + authorized_clients: + - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 + modules: + - name: action + package: gorgone::modules::core::action::hooks + enable: true - - name: engine - package: gorgone::modules::centreon::engine::hooks - enable: true - command_file: "/var/lib/centreon-engine/rw/centengine.cmd" + - name: engine + package: gorgone::modules::centreon::engine::hooks + enable: true + command_file: "/var/lib/centreon-engine/rw/centengine.cmd" diff --git a/gorgone/config/gorgoned-remote-ssh.yml b/gorgone/config/gorgoned-remote-ssh.yml index 731ab267e7f..01345e67a64 100644 --- a/gorgone/config/gorgoned-remote-ssh.yml +++ b/gorgone/config/gorgoned-remote-ssh.yml @@ -1,38 +1,40 @@ name: gorgoned-remote-ssh description: Configuration example in a SSH environment for Remote server -database: - db_centreon: - dsn: "mysql:host=localhost;dbname=centreon" - username: centreon - password: centreon - db_centstorage: - dsn: "mysql:host=localhost;dbname=centreon_storage" - username: centreon - password: centreon -gorgonecore: - timeout: 50 -modules: - - name: action - package: gorgone::modules::core::action::hooks - enable: true +common: + database: + db_configuration: + dsn: "mysql:host=localhost;dbname=centreon" + username: centreon + password: centreon + db_realtime: + dsn: "mysql:host=localhost;dbname=centreon_storage" + username: centreon + password: centreon +gorgone: + gorgonecore: + timeout: 50 + modules: + - name: action + package: gorgone::modules::core::action::hooks + enable: true - - name: proxy - package: gorgone::modules::core::proxy::hooks - enable: true + - name: proxy + package: gorgone::modules::core::proxy::hooks + enable: true - - name: pollers - package: gorgone::modules::centreon::pollers::hooks - enable: true + - name: nodes + package: gorgone::modules::centreon::nodes::hooks + enable: true - - name: legacycmd - package: gorgone::modules::centreon::legacycmd::hooks - enable: true - cmd_file: "/var/lib/centreon/centcore.cmd" - cache_dir: "/var/cache/centreon/" - cache_dir_trap: "/etc/snmp/centreon_traps/" - remote_dir: "/var/lib/centreon/remote-data/" + - name: legacycmd + package: gorgone::modules::centreon::legacycmd::hooks + enable: true + cmd_file: "/var/lib/centreon/centcore.cmd" + cache_dir: "/var/cache/centreon/" + cache_dir_trap: "/etc/snmp/centreon_traps/" + remote_dir: "/var/lib/centreon/remote-data/" - - name: engine - package: gorgone::modules::centreon::engine::hooks - enable: true - command_file: "/var/lib/centreon-engine/rw/centengine.cmd" + - name: engine + package: gorgone::modules::centreon::engine::hooks + enable: true + command_file: "/var/lib/centreon-engine/rw/centengine.cmd" diff --git a/gorgone/config/gorgoned-remote-zmq.yml b/gorgone/config/gorgoned-remote-zmq.yml index eb28cdf20dc..f61c08a399a 100644 --- a/gorgone/config/gorgoned-remote-zmq.yml +++ b/gorgone/config/gorgoned-remote-zmq.yml @@ -1,44 +1,46 @@ name: gorgoned-remote-zmq description: Configuration example in a full ZMQ environment for Remote server -database: - db_centreon: - dsn: "mysql:host=localhost;dbname=centreon" - username: centreon - password: centreon - db_centstorage: - dsn: "mysql:host=localhost;dbname=centreon_storage" - username: centreon - password: centreon -gorgonecore: - id: 4 - external_com_type: tcp - external_com_path: "*:5556" - privkey: keys/central/privkey.pem - authorized_clients: - - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 -modules: - - name: action - package: gorgone::modules::core::action::hooks - enable: true +common: + database: + db_configuration: + dsn: "mysql:host=localhost;dbname=centreon" + username: centreon + password: centreon + db_realtime: + dsn: "mysql:host=localhost;dbname=centreon_storage" + username: centreon + password: centreon +gorgone: + gorgonecore: + id: 4 + external_com_type: tcp + external_com_path: "*:5556" + privkey: keys/central/privkey.pem + authorized_clients: + - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 + modules: + - name: action + package: gorgone::modules::core::action::hooks + enable: true - - name: proxy - package: gorgone::modules::core::proxy::hooks - enable: true + - name: proxy + package: gorgone::modules::core::proxy::hooks + enable: true - - name: register - package: gorgone::modules::core::register::hooks - enable: true - config_file: config/registernodes-remote.yml + - name: register + package: gorgone::modules::core::register::hooks + enable: true + config_file: config/registernodes-remote.yml - - name: legacycmd - package: gorgone::modules::centreon::legacycmd::hooks - enable: true - cmd_file: "/var/lib/centreon/centcore.cmd" - cache_dir: "/var/cache/centreon/" - cache_dir_trap: "/etc/snmp/centreon_traps/" - remote_dir: "/var/lib/centreon/remote-data/" + - name: legacycmd + package: gorgone::modules::centreon::legacycmd::hooks + enable: true + cmd_file: "/var/lib/centreon/centcore.cmd" + cache_dir: "/var/cache/centreon/" + cache_dir_trap: "/etc/snmp/centreon_traps/" + remote_dir: "/var/lib/centreon/remote-data/" - - name: engine - package: gorgone::modules::centreon::engine::hooks - enable: true - command_file: "/var/lib/centreon-engine/rw/centengine.cmd" + - name: engine + package: gorgone::modules::centreon::engine::hooks + enable: true + command_file: "/var/lib/centreon-engine/rw/centengine.cmd" diff --git a/gorgone/docs/getting_started.md b/gorgone/docs/getting_started.md index c90e585de4c..e90d71f21ff 100644 --- a/gorgone/docs/getting_started.md +++ b/gorgone/docs/getting_started.md @@ -24,6 +24,7 @@ The daemon uses the following Perl modules: * ZMQ::LibZMQ4 * UUID * Repository 'centos base': + * JSON::PP * JSON::XS * YAML * DBD::SQLite @@ -37,6 +38,8 @@ The daemon uses the following Perl modules: * HTTP::Daemon::SSL * Schedule::Cron * From offline packages: + * Hash::Merge + * YAML::XS * Crypt::Cipher::AES (module CryptX) * Crypt::PK::RSA (module CryptX) * Crypt::PRNG (module CryptX) @@ -44,8 +47,8 @@ The daemon uses the following Perl modules: Execute the following commands to install them all: ```bash -yum install 'perl(NetAddr::IP)' 'perl(Schedule::Cron)' 'perl(Crypt::CBC)' 'perl(ZMQ::LibZMQ4)' 'perl(JSON::XS)' 'perl(YAML)' 'perl(DBD::SQLite)' 'perl(DBD::mysql)' 'perl(UUID)' 'perl(HTTP::Daemon)' 'perl(HTTP::Daemon::SSL)' 'perl(HTTP::Status)' 'perl(MIME::Base64)' -yum install packaging/packages/perl-CryptX-0.064-1.el7.x86_64 +yum install 'perl(JSON::PP)' 'perl(NetAddr::IP)' 'perl(Schedule::Cron)' 'perl(Crypt::CBC)' 'perl(ZMQ::LibZMQ4)' 'perl(JSON::XS)' 'perl(YAML)' 'perl(DBD::SQLite)' 'perl(DBD::mysql)' 'perl(UUID)' 'perl(HTTP::Daemon)' 'perl(HTTP::Daemon::SSL)' 'perl(HTTP::Status)' 'perl(MIME::Base64)' +yum install packaging/packages/perl-CryptX-0.064-1.el7.x86_64 packaging/packages/perl-YAML-LibYAML-0.80-1.el7.x86_64.rpm packaging/packages/perl-Hash-Merge-0.300-1.el7.noarch.rpm packaging/packages/perl-Clone-Choose-0.010-1.el7.noarch.rpm ``` ## Configuration diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 1be399a0799..26aca7cf5a3 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -30,7 +30,7 @@ use gorgone::standard::library; use gorgone::standard::misc; use gorgone::class::db; -my ($gorgone, $config); +my ($gorgone); use base qw(gorgone::class::script); @@ -46,11 +46,7 @@ sub new { ); bless $self, $class; - $self->add_options( - 'config:s' => \$self->{opt_config}, - ); - $self->{opt_config} = ''; $self->{return_child} = {}; $self->{stop} = 0; $self->{internal_register} = {}; @@ -69,6 +65,7 @@ sub new { 'GET_/internal/information' => 'INFORMATION', 'POST_/internal/logger' => 'BCASTLOGGER', }; + $self->{config} = return $self; } @@ -80,46 +77,48 @@ sub init_server_keys { $self->{logger}->writeLogInfo("[core] Initialize server keys"); $self->{keys_loaded} = 0; - $options{config}->{gorgonecore}->{privkey} = defined($options{config}->{gorgonecore}->{privkey}) && $options{config}->{gorgonecore}->{privkey} ne '' ? - $options{config}->{gorgonecore}->{privkey} : 'keys/rsakey.priv.pem'; - $options{config}->{gorgonecore}->{pubkey} = defined($options{config}->{gorgonecore}->{pubkey}) && $options{config}->{gorgonecore}->{pubkey} ne '' ? - $options{config}->{gorgonecore}->{pubkey} : 'keys/rsakey.pub.pem'; + $self->{config} = { gorgone => { gorgonecore => { } } } if (!defined($self->{config}->{gorgone})); + + $self->{config}->{gorgone}->{gorgonecore}->{privkey} = 'keys/rsakey.priv.pem' + if (!defined($self->{config}->{gorgone}->{gorgonecore}->{privkey}) || $self->{config}->{gorgone}->{gorgonecore}->{privkey} eq ''); + $self->{config}->{gorgone}->{gorgonecore}->{pubkey} = 'keys/rsakey.pub.pem' + if (!defined($self->{config}->{gorgone}->{gorgonecore}->{pubkey}) || $self->{config}->{gorgone}->{gorgonecore}->{pubkey} eq ''); - if (! -f $options{config}->{gorgonecore}->{privkey} && ! -f $options{config}->{gorgonecore}->{pubkey}) { + if (! -f $self->{config}->{gorgone}->{gorgonecore}->{privkey} && ! -f $self->{config}->{gorgone}->{gorgonecore}->{pubkey}) { ($code, $content_privkey, $content_pubkey) = gorgone::standard::library::generate_keys(logger => $self->{logger}); return if ($code == 0); $code = gorgone::standard::misc::write_file( logger => $self->{logger}, - filename => $options{config}->{gorgonecore}->{privkey}, + filename => $self->{config}->{gorgone}->{gorgonecore}->{privkey}, content => $content_privkey, ); return if ($code == 0); - $self->{logger}->writeLogInfo("[core] Private key file '$options{config}->{gorgonecore}->{privkey}' written"); + $self->{logger}->writeLogInfo("[core] Private key file '$self->{config}->{gorgone}->{gorgonecore}->{privkey}' written"); $code = gorgone::standard::misc::write_file( logger => $self->{logger}, - filename => $options{config}->{gorgonecore}->{pubkey}, + filename => $self->{config}->{gorgone}->{gorgonecore}->{pubkey}, content => $content_pubkey, ); return if ($code == 0); - $self->{logger}->writeLogInfo("[core] Public key file '$options{config}->{gorgonecore}->{pubkey}' written"); + $self->{logger}->writeLogInfo("[core] Public key file '$self->{config}->{gorgone}->{gorgonecore}->{pubkey}' written"); } ($code, $self->{server_privkey}) = gorgone::standard::library::loadprivkey( logger => $self->{logger}, - privkey => $options{config}->{gorgonecore}->{privkey}, + privkey => $self->{config}->{gorgone}->{gorgonecore}->{privkey}, noquit => 1 ); return if ($code == 0); - $self->{logger}->writeLogInfo("[core] Private key file '$options{config}->{gorgonecore}->{privkey}' loaded"); + $self->{logger}->writeLogInfo("[core] Private key file '$self->{config}->{gorgone}->{gorgonecore}->{privkey}' loaded"); ($code, $self->{server_pubkey}) = gorgone::standard::library::loadpubkey( logger => $self->{logger}, - pubkey => $options{config}->{gorgonecore}->{pubkey}, + pubkey => $self->{config}->{gorgone}->{gorgonecore}->{pubkey}, noquit => 1 ); return if ($code == 0); - $self->{logger}->writeLogInfo("[core] Public key file '$options{config}->{gorgonecore}->{pubkey}' loaded"); + $self->{logger}->writeLogInfo("[core] Public key file '$self->{config}->{gorgone}->{gorgonecore}->{pubkey}' loaded"); $self->{keys_loaded} = 1; } @@ -131,68 +130,68 @@ sub init { # redefine to avoid out when we try modules $SIG{__DIE__} = undef; - ## load config ini - if (! -f $self->{opt_config}) { - $self->{logger}->writeLogError("[core] Can't find config file '$self->{opt_config}'"); + ## load config + if (!defined($self->{config_file})) { + $self->{logger}->writeLogError('[core] please define config file option'); exit(1); } - $config = gorgone::standard::library::read_config( - config_file => $self->{opt_config}, - logger => $self->{logger} - ); - - $self->init_server_keys(config => $config); - - $config->{gorgonecore}->{internal_com_type} = - defined($config->{gorgonecore}->{internal_com_type}) && $config->{gorgonecore}->{internal_com_type} ne '' ? $config->{gorgonecore}->{internal_com_type} : 'ipc'; - $config->{gorgonecore}->{internal_com_path} = - defined($config->{gorgonecore}->{internal_com_path}) && $config->{gorgonecore}->{internal_com_path} ne '' ? $config->{gorgonecore}->{internal_com_path} : '/tmp/gorgone/routing.ipc'; - $config->{gorgonecore}->{timeout} = - defined($config->{gorgonecore}->{timeout}) && $config->{gorgonecore}->{timeout} =~ /(\d+)/ ? $1 : 50; - - $config->{gorgonecore}->{cipher} = - defined($config->{gorgonecore}->{cipher}) && $config->{gorgonecore}->{cipher} ne '' ? $config->{gorgonecore}->{cipher} : 'Cipher::AES'; - $config->{gorgonecore}->{keysize} = - defined($config->{gorgonecore}->{keysize}) && $config->{gorgonecore}->{keysize} ne '' ? $config->{gorgonecore}->{keysize} : 32; - $config->{gorgonecore}->{vector} = - defined($config->{gorgonecore}->{vector}) && $config->{gorgonecore}->{vector} ne '' ? $config->{gorgonecore}->{vector} : '0123456789012345'; - - $config->{gorgonecore}->{fingerprint_mode} = - defined($config->{gorgonecore}->{fingerprint_mode}) && $config->{gorgonecore}->{fingerprint_mode} =~ /^\s*(always|firt|strict)\s*/i ? lc($1) : 'first'; - $config->{gorgonecore}->{fingerprint_mgr} = {} if (!defined($config->{gorgonecore}->{fingerprint_mgr})); - $config->{gorgonecore}->{fingerprint_mgr}->{package} = 'gorgone::class::fingerprint::backend::sql' - if (!defined($config->{gorgonecore}->{fingerprint_mgr}->{package}) || $config->{gorgonecore}->{fingerprint_mgr}->{package} eq ''); - - $config->{gorgonecore}->{fingerprint_mode} = - defined($config->{gorgonecore}->{fingerprint_mode}) && $config->{gorgonecore}->{fingerprint_mode} =~ /^\s*(always|firt|strict)\s*/i ? lc($1) : 'first'; - - $config->{gorgonecore}->{gorgone_db_type} = - defined($config->{gorgonecore}->{gorgone_db_type}) && $config->{gorgonecore}->{gorgone_db_type} ne '' ? $config->{gorgonecore}->{gorgone_db_type} : 'SQLite'; - $config->{gorgonecore}->{gorgone_db_name} = - defined($config->{gorgonecore}->{gorgone_db_name}) && $config->{gorgonecore}->{gorgone_db_name} ne '' ? $config->{gorgonecore}->{gorgone_db_name} : 'dbname=/var/lib/centreon/gorgone/gorgone.sdb'; - $config->{gorgonecore}->{gorgone_db_autocreate_schema} = - defined($config->{gorgonecore}->{gorgone_db_autocreate_schema}) && $config->{gorgonecore}->{gorgone_db_autocreate_schema} =~ /(\d+)/ ? $1 : 1; + if (! -f $self->{config_file}) { + $self->{logger}->writeLogError("[core] can't find config file '$self->{config_file}'"); + exit(1); + } + $self->{config} = $self->yaml_load_config(file => $self->{config_file}); + $self->init_server_keys(); + + $self->{config}->{gorgone}->{gorgonecore}->{internal_com_type} = 'ipc' + if (!defined($self->{config}->{gorgone}->{gorgonecore}->{internal_com_type}) || $self->{config}->{gorgone}->{gorgonecore}->{internal_com_type} eq ''); + $self->{config}->{gorgone}->{gorgonecore}->{internal_com_path} = '/tmp/gorgone/routing.ipc' + if (!defined($self->{config}->{gorgone}->{gorgonecore}->{internal_com_path}) || $self->{config}->{gorgone}->{gorgonecore}->{internal_com_path} eq ''); + $self->{config}->{gorgone}->{gorgonecore}->{timeout} = + defined($self->{config}->{gorgone}->{gorgonecore}->{timeout}) && $self->{config}->{gorgone}->{gorgonecore}->{timeout} =~ /(\d+)/ ? $1 : 50; + + $self->{config}->{gorgone}->{gorgonecore}->{cipher} = 'Cipher::AES' + if (!defined($self->{config}->{gorgone}->{gorgonecore}->{cipher}) || $self->{config}->{gorgone}->{gorgonecore}->{cipher} eq ''); + $self->{config}->{gorgone}->{gorgonecore}->{keysize} = 32 + if (!defined($self->{config}->{gorgone}->{gorgonecore}->{keysize}) || $self->{config}->{gorgone}->{gorgonecore}->{keysize} eq ''); + $self->{config}->{gorgone}->{gorgonecore}->{vector} = '0123456789012345' + if (!defined($self->{config}->{gorgone}->{gorgonecore}->{vector}) || $self->{config}->{gorgone}->{gorgonecore}->{vector} eq ''); + + $self->{config}->{gorgone}->{gorgonecore}->{fingerprint_mode} = + defined($self->{config}->{gorgone}->{gorgonecore}->{fingerprint_mode}) && $self->{config}->{gorgone}->{gorgonecore}->{fingerprint_mode} =~ /^\s*(always|firt|strict)\s*/i ? lc($1) : 'first'; + $self->{config}->{gorgone}->{gorgonecore}->{fingerprint_mgr} = {} if (!defined($self->{config}->{gorgone}->{gorgonecore}->{fingerprint_mgr})); + $self->{config}->{gorgone}->{gorgonecore}->{fingerprint_mgr}->{package} = 'gorgone::class::fingerprint::backend::sql' + if (!defined($self->{config}->{gorgone}->{gorgonecore}->{fingerprint_mgr}->{package}) || $self->{config}->{gorgone}->{gorgonecore}->{fingerprint_mgr}->{package} eq ''); + + $self->{config}->{gorgone}->{gorgonecore}->{fingerprint_mode} = + defined($self->{config}->{gorgone}->{gorgonecore}->{fingerprint_mode}) && $self->{config}->{gorgone}->{gorgonecore}->{fingerprint_mode} =~ /^\s*(always|firt|strict)\s*/i ? lc($1) : 'first'; + + $self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_type} = + defined($self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_type}) && $self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_type} ne '' ? $self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_type} : 'SQLite'; + $self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_name} = + defined($self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_name}) && $self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_name} ne '' ? $self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_name} : 'dbname=/var/lib/centreon/gorgone/gorgone.sdb'; + $self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_autocreate_schema} = + defined($self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_autocreate_schema}) && $self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_autocreate_schema} =~ /(\d+)/ ? $1 : 1; gorgone::standard::library::init_database( gorgone => $gorgone, - type => $config->{gorgonecore}->{gorgone_db_type}, - db => $config->{gorgonecore}->{gorgone_db_name}, - host => $config->{gorgonecore}->{gorgone_db_host}, - port => $config->{gorgonecore}->{gorgone_db_port}, - user => $config->{gorgonecore}->{gorgone_db_user}, - password => $config->{gorgonecore}->{gorgone_db_password}, - autocreate_schema => $config->{gorgonecore}->{gorgone_db_autocreate_schema}, + type => $self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_type}, + db => $self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_name}, + host => $self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_host}, + port => $self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_port}, + user => $self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_user}, + password => $self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_password}, + autocreate_schema => $self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_autocreate_schema}, force => 2, logger => $gorgone->{logger} ); - $self->{hostname} = $config->{gorgonecore}->{hostname}; + $self->{hostname} = $self->{config}->{gorgone}->{gorgonecore}->{hostname}; if (!defined($self->{hostname}) || $self->{hostname} eq '') { $self->{hostname} = hostname(); } - $config->{gorgonecore}->{proxy_name} = - (defined($config->{gorgonecore}->{proxy_name}) && $config->{gorgonecore}->{proxy_name} ne '') ? $config->{gorgonecore}->{proxy_name} : 'proxy'; - $self->{id} = $config->{gorgonecore}->{id}; + $self->{config}->{gorgone}->{gorgonecore}->{proxy_name} = + (defined($self->{config}->{gorgone}->{gorgonecore}->{proxy_name}) && $self->{config}->{gorgone}->{gorgonecore}->{proxy_name} ne '') ? $self->{config}->{gorgone}->{gorgonecore}->{proxy_name} : 'proxy'; + $self->{id} = $self->{config}->{gorgone}->{gorgonecore}->{id}; $self->load_modules(); @@ -316,9 +315,9 @@ sub load_module { my ($loaded, $namespace, $name, $events) = $self->{modules_register}->{$package}->{register}->( config => $options{config_module}, - config_core => $config->{gorgonecore}, - config_db_centreon => $config->{database}->{db_centreon}, - config_db_centstorage => $config->{database}->{db_centstorage}, + config_core => $self->{config}->{gorgone}->{gorgonecore}, + config_db_centreon => $self->{config}->{common}->{database}->{db_configuration}, + config_db_centstorage => $self->{config}->{common}->{database}->{db_realtime}, logger => $self->{logger}, ); if ($loaded == 0) { @@ -345,9 +344,9 @@ sub load_module { sub load_modules { my ($self) = @_; - next if (!defined($config->{modules})); + return if (!defined($self->{config}->{gorgone}->{modules})); - foreach my $module (@{$config->{modules}}) { + foreach my $module (@{$self->{config}->{gorgone}->{modules}}) { $self->load_module(config_module => $module); } @@ -439,8 +438,8 @@ sub message_run { # Check Routing if (defined($target)) { - if (!defined($self->{modules_id}->{$config->{gorgonecore}->{proxy_name}}) || - !defined($self->{modules_register}->{ $self->{modules_id}->{$config->{gorgonecore}->{proxy_name}} })) { + if (!defined($self->{modules_id}->{ $self->{config}->{gorgone}->{gorgonecore}->{proxy_name} }) || + !defined($self->{modules_register}->{ $self->{modules_id}->{ $self->{config}->{gorgone}->{gorgonecore}->{proxy_name} } })) { gorgone::standard::library::add_history( dbh => $self->{db_gorgone}, code => 1, @@ -455,7 +454,7 @@ sub message_run { $self->{counters}->{proxy}->{lc($action)}++; $self->{counters}->{proxy}->{total}++; - $self->{modules_register}->{ $self->{modules_id}->{$config->{gorgonecore}->{proxy_name}} }->{routing}->( + $self->{modules_register}->{ $self->{modules_id}->{ $self->{config}->{gorgone}->{gorgonecore}->{proxy_name} } }->{routing}->( socket => $self->{internal_socket}, dbh => $self->{db_gorgone}, logger => $self->{logger}, @@ -471,7 +470,7 @@ sub message_run { if ($action =~ /^(?:PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID|SYNCLOGS|LOADMODULE|UNLOADMODULE|INFORMATION|GETTHUMBPRINT)$/) { my ($code, $response, $response_type) = $self->{internal_register}->{lc($action)}->( gorgone => $self, - gorgone_config => $config, + gorgone_config => $self->{config}->{gorgone}, identity => $options{identity}, router_type => $options{router_type}, id => $self->{id}, @@ -552,10 +551,10 @@ sub handshake { if ($status == 1) { ($status, my $response) = gorgone::standard::library::uncrypt_message( - cipher => $config->{gorgonecore}->{cipher}, + cipher => $self->{config}->{gorgone}->{gorgonecore}->{cipher}, message => $message, symkey => $key, - vector => $config->{gorgonecore}->{vector} + vector => $self->{config}->{gorgone}->{gorgonecore}->{vector} ); if ($status == 0 && $response =~ /^\[.*\]/) { gorgone::standard::library::update_identity(dbh => $self->{db_gorgone}, identity => $identity); @@ -580,7 +579,7 @@ sub handshake { privkey => $self->{server_privkey}, message => $message, logger => $self->{logger}, - authorized_clients => $config->{gorgonecore}->{authorized_clients} + authorized_clients => $self->{config}->{gorgone}->{gorgonecore}->{authorized_clients} ); if ($status == -1) { gorgone::standard::library::zmq_core_response( @@ -593,8 +592,8 @@ sub handshake { } my ($status, $symkey) = gorgone::standard::library::generate_symkey( logger => $self->{logger}, - cipher => $config->{gorgonecore}->{cipher}, - keysize => $config->{gorgonecore}->{keysize} + cipher => $self->{config}->{gorgone}->{gorgonecore}->{cipher}, + keysize => $self->{config}->{gorgone}->{gorgonecore}->{keysize} ); if ($status == -1) { gorgone::standard::library::zmq_core_response( @@ -642,8 +641,8 @@ sub send_message_parent { socket => $gorgone->{external_socket}, identity => $options{identity}, response_type => $options{response_type}, - cipher => $config->{gorgonecore}->{cipher}, - vector => $config->{gorgonecore}->{vector}, + cipher => $gorgone->{config}->{gorgone}->{gorgonecore}->{cipher}, + vector => $gorgone->{config}->{gorgone}->{gorgonecore}->{vector}, symkey => $key, token => $options{token}, code => $options{code}, @@ -664,8 +663,8 @@ sub router_external_event { gorgone::standard::library::zmq_core_response( socket => $gorgone->{external_socket}, identity => $identity, response_type => $response_type, - cipher => $config->{gorgonecore}->{cipher}, - vector => $config->{gorgonecore}->{vector}, + cipher => $gorgone->{config}->{gorgone}->{gorgonecore}->{cipher}, + vector => $gorgone->{config}->{gorgone}->{gorgonecore}->{vector}, symkey => $key, token => $token, code => $code, data => $response @@ -743,16 +742,16 @@ sub run { } $gorgone->{internal_socket} = gorgone::standard::library::create_com( - type => $config->{gorgonecore}->{internal_com_type}, - path => $config->{gorgonecore}->{internal_com_path}, + type => $gorgone->{config}->{gorgone}->{gorgonecore}->{internal_com_type}, + path => $gorgone->{config}->{gorgone}->{gorgonecore}->{internal_com_path}, zmq_type => 'ZMQ_ROUTER', name => 'router-internal', logger => $gorgone->{logger} ); - if (defined($config->{gorgonecore}->{external_com_type}) && $config->{gorgonecore}->{external_com_type} ne '') { + if (defined($gorgone->{config}->{gorgone}->{gorgonecore}->{external_com_type}) && $gorgone->{config}->{gorgone}->{gorgonecore}->{external_com_type} ne '') { if ($gorgone->{keys_loaded}) { $gorgone->{external_socket} = gorgone::standard::library::create_com( - type => $config->{gorgonecore}->{external_com_type}, - path => $config->{gorgonecore}->{external_com_path}, + type => $gorgone->{config}->{gorgone}->{gorgonecore}->{external_com_type}, + path => $gorgone->{config}->{gorgone}->{gorgonecore}->{external_com_path}, zmq_type => 'ZMQ_ROUTER', name => 'router-external', logger => $gorgone->{logger} ); @@ -827,7 +826,7 @@ sub run { } # Send KILL - if (time() - $gorgone->{kill_timer} > $config->{gorgonecore}->{timeout}) { + if (time() - $gorgone->{kill_timer} > $gorgone->{config}->{gorgone}->{gorgonecore}->{timeout}) { foreach my $name (keys %{$gorgone->{modules_register}}) { $gorgone->{modules_register}->{$name}->{kill_internal}->(logger => $gorgone->{logger}); } diff --git a/gorgone/gorgone/class/script.pm b/gorgone/gorgone/class/script.pm index 6a223a4e2ea..87eebc82b64 100644 --- a/gorgone/gorgone/class/script.pm +++ b/gorgone/gorgone/class/script.pm @@ -28,6 +28,10 @@ use Pod::Usage; use gorgone::class::logger; use gorgone::class::db; use gorgone::class::lock; +use YAML::XS; +use Hash::Merge; +Hash::Merge::set_behavior('RIGHT_PRECEDENT'); +$YAML::XS::Boolean = 'JSON::PP'; $SIG{__DIE__} = sub { my $error = shift; @@ -129,4 +133,83 @@ sub run { $self->init(); } +sub yaml_get_include { + my ($self, %options) = @_; + + my $dir = File::Basename::dirname($options{include}); + $dir = $options{current_dir} . '/' . $dir if ($dir !~ /^\//); + my $match_files = File::Basename::basename($options{include}); + $match_files =~ s/\*/\\E.*\\Q/g; + $match_files = '\Q' . $match_files . '\E'; + + my $DIR; + if (!opendir($DIR, $dir)) { + $self->{logger}->writeLogError("config - cannot opendir '$dir' error: $!"); + return (); + } + my @sorted_files = (); + while (readdir($DIR)) { + if (-f "$dir/$_" && eval "/$match_files/") { + push @sorted_files, "$dir/$_"; + } + } + closedir($DIR); + @sorted_files = sort { $a cmp $b } @sorted_files; + return @sorted_files; +} + +sub yaml_parse_config { + my ($self, %options) = @_; + + if (ref(${$options{config}}) eq 'HASH') { + foreach (keys %{${$options{config}}}) { + $self->yaml_parse_config(config => \${$options{config}}->{$_}, current_dir => $options{current_dir}); + } + } elsif (ref(${$options{config}}) eq 'ARRAY') { + for (my $i = 0; $i < scalar(@{${$options{config}}}); $i++) { + $self->yaml_parse_config(config => \${$options{config}}->[$i], current_dir => $options{current_dir}); + } + } elsif (ref(${$options{config}}) eq 'include') { + my @files = $self->yaml_get_include(include => ${${$options{config}}}, current_dir => $options{current_dir}); + ${$options{config}} = undef; + foreach (@files) { + next if (! -r $_); + my $config = yaml_load_config(file => $_); + next if (!defined($config)); + if (ref($config) eq 'ARRAY') { + ${$options{config}} = [] if (ref(${$options{config}}) ne 'ARRAY'); + push @{${$options{config}}}, @$config; + } elsif (ref($config) eq 'HASH') { + ${$options{config}} = {} if (ref(${$options{config}}) ne 'HASH'); + ${$options{config}} = Hash::Merge::merge(${$options{config}}, $config); + } else { + ${$options{config}} = $config; + } + } + } elsif (ref(${$options{config}}) eq 'JSON::PP::Boolean') { + if (${${$options{config}}}) { + ${$options{config}} = 'true'; + } else { + ${$options{config}} = 'false'; + } + } +} + +sub yaml_load_config { + my ($self, %options) = @_; + + my $config; + eval { + $config = YAML::XS::LoadFile($options{file}); + }; + if ($@) { + $self->{logger}->writeLogError("config - yaml load file '$options{file}' error: $@"); + return undef; + } + + my $current_dir = File::Basename::dirname($options{file}); + $self->yaml_parse_config(config => \$config, current_dir => $current_dir); + return $config; +} + 1; From 9a5876c927aca90a11921886f38db56df55149bb Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 22 Jan 2020 16:46:42 +0100 Subject: [PATCH 308/948] enh(packaging): update spec --- gorgone/packaging/centreon-gorgone.spectemplate | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 95106db3324..1e7374d1590 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -15,7 +15,8 @@ Requires: perl(ZMQ::LibZMQ4) Requires: perl(ZMQ::Constants) Requires: perl(Crypt::CBC) Requires: perl(JSON::XS) -Requires: perl(YAML) +Requires: perl(JSON::PP) +Requires: perl(YAML::XS) Requires: perl(DBD::SQLite) Requires: perl(DBD::mysql) Requires: perl(DBI) @@ -28,6 +29,7 @@ Requires: perl(Libssh::Session) Requires: perl(Net::Curl::Easy) Requires: perl(HTTP::Daemon::SSL) Requires: perl(NetAddr::IP) +Requires: perl(Hash::Merge) AutoReqProv: no %description From f083adf11ff217cf8db98c1d4cb0afceffd79534 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Thu, 23 Jan 2020 10:11:08 +0100 Subject: [PATCH 309/948] enh(config): slightly change configuration structure --- gorgone/config/gorgoned-central-ssh.yml | 74 +++++------ gorgone/config/gorgoned-central-zmq.yml | 120 +++++++++--------- gorgone/config/gorgoned-poller.yml | 33 ++--- gorgone/config/gorgoned-remote-ssh.yml | 50 ++++---- gorgone/config/gorgoned-remote-zmq.yml | 62 +++++----- gorgone/docs/configuration.md | 74 +++++------ gorgone/gorgone/class/core.pm | 156 ++++++++++++------------ 7 files changed, 287 insertions(+), 282 deletions(-) diff --git a/gorgone/config/gorgoned-central-ssh.yml b/gorgone/config/gorgoned-central-ssh.yml index 7af8868f7f7..0e414a9dc7e 100644 --- a/gorgone/config/gorgoned-central-ssh.yml +++ b/gorgone/config/gorgoned-central-ssh.yml @@ -1,6 +1,6 @@ name: gorgoned-central-ssh description: Configuration example in a SSH environment for Central server -common: +configuration: database: db_configuration: dsn: "mysql:host=localhost;dbname=centreon" @@ -10,44 +10,44 @@ common: dsn: "mysql:host=localhost;dbname=centreon_storage" username: centreon password: centreon -gorgone: - gorgonecore: - timeout: 50 - modules: - - name: httpserver - package: gorgone::modules::core::httpserver::hooks - enable: true - address: 0.0.0.0 - port: 8443 - ssl: true - ssl_cert_file: /etc/pki/tls/certs/server-cert.pem - ssl_key_file: /etc/pki/tls/server-key.pem - auth: - enabled: true - user: admin - password: password + gorgone: + gorgonecore: + timeout: 50 + modules: + - name: httpserver + package: gorgone::modules::core::httpserver::hooks + enable: true + address: 0.0.0.0 + port: 8443 + ssl: true + ssl_cert_file: /etc/pki/tls/certs/server-cert.pem + ssl_key_file: /etc/pki/tls/server-key.pem + auth: + enabled: true + user: admin + password: password - - name: action - package: gorgone::modules::core::action::hooks - enable: true + - name: action + package: gorgone::modules::core::action::hooks + enable: true - - name: proxy - package: gorgone::modules::core::proxy::hooks - enable: true + - name: proxy + package: gorgone::modules::core::proxy::hooks + enable: true - - name: nodes - package: gorgone::modules::centreon::nodes::hooks - enable: true + - name: nodes + package: gorgone::modules::centreon::nodes::hooks + enable: true - - name: legacycmd - package: gorgone::modules::centreon::legacycmd::hooks - enable: true - cmd_file: "/var/lib/centreon/centcore.cmd" - cache_dir: "/var/cache/centreon/" - cache_dir_trap: "/etc/snmp/centreon_traps/" - remote_dir: "/var/lib/centreon/remote-data/" + - name: legacycmd + package: gorgone::modules::centreon::legacycmd::hooks + enable: true + cmd_file: "/var/lib/centreon/centcore.cmd" + cache_dir: "/var/cache/centreon/" + cache_dir_trap: "/etc/snmp/centreon_traps/" + remote_dir: "/var/lib/centreon/remote-data/" - - name: engine - package: gorgone::modules::centreon::engine::hooks - enable: true - command_file: "/var/lib/centreon-engine/rw/centengine.cmd" + - name: engine + package: gorgone::modules::centreon::engine::hooks + enable: true + command_file: "/var/lib/centreon-engine/rw/centengine.cmd" diff --git a/gorgone/config/gorgoned-central-zmq.yml b/gorgone/config/gorgoned-central-zmq.yml index a05a2199dd8..217072398f8 100644 --- a/gorgone/config/gorgoned-central-zmq.yml +++ b/gorgone/config/gorgoned-central-zmq.yml @@ -1,6 +1,6 @@ name: gorgoned-central-zmq description: Configuration example in a full ZMQ environment for Central server -common: +configuration: database: db_configuration: dsn: "mysql:host=localhost;dbname=centreon" @@ -10,69 +10,69 @@ common: dsn: "mysql:host=localhost;dbname=centreon_storage" username: centreon password: centreon -gorgone: - gorgonecore: - id: 1 - privkey: keys/central/privkey.pem - # can be: always, first (default), strict - fingerprint_mode: first - fingerprint_mgr: - package: gorgone::class::fingerprint::backend::sql - # if unset, it uses global configuration - #gorgone_db_type: - #gorgone_db_name: - modules: - - name: httpserver - package: gorgone::modules::core::httpserver::hooks - enable: true - address: 0.0.0.0 - port: 8443 - ssl: true - ssl_cert_file: /etc/pki/tls/certs/server-cert.pem - ssl_key_file: /etc/pki/tls/server-key.pem - auth: - enabled: true - user: admin - password: password - allowed_hosts: + gorgone: + gorgonecore: + id: 1 + privkey: keys/central/privkey.pem + # can be: always, first (default), strict + fingerprint_mode: first + fingerprint_mgr: + package: gorgone::class::fingerprint::backend::sql + # if unset, it uses global configuration + #gorgone_db_type: + #gorgone_db_name: + modules: + - name: httpserver + package: gorgone::modules::core::httpserver::hooks + enable: true + address: 0.0.0.0 + port: 8443 + ssl: true + ssl_cert_file: /etc/pki/tls/certs/server-cert.pem + ssl_key_file: /etc/pki/tls/server-key.pem + auth: enabled: true - subnets: - - 127.0.0.1/32 - - 10.30.2.0/16 + user: admin + password: password + allowed_hosts: + enabled: true + subnets: + - 127.0.0.1/32 + - 10.30.2.0/16 - - name: cron - package: gorgone::modules::core::cron::hooks - enable: true - cron: - - id: echo_date - timespec: "* * * * *" - action: COMMAND - parameters: - command: "date >> /tmp/date.log" - timeout: 10 + - name: cron + package: gorgone::modules::core::cron::hooks + enable: true + cron: + - id: echo_date + timespec: "* * * * *" + action: COMMAND + parameters: + command: "date >> /tmp/date.log" + timeout: 10 - - name: action - package: gorgone::modules::core::action::hooks - enable: true + - name: action + package: gorgone::modules::core::action::hooks + enable: true - - name: proxy - package: gorgone::modules::core::proxy::hooks - enable: true + - name: proxy + package: gorgone::modules::core::proxy::hooks + enable: true - - name: register - package: gorgone::modules::core::register::hooks - enable: true - config_file: config/registernodes-central.yml + - name: register + package: gorgone::modules::core::register::hooks + enable: true + config_file: config/registernodes-central.yml - - name: legacycmd - package: gorgone::modules::centreon::legacycmd::hooks - enable: true - cmd_file: "/var/lib/centreon/centcore.cmd" - cache_dir: "/var/cache/centreon/" - cache_dir_trap: "/etc/snmp/centreon_traps/" - remote_dir: "/var/lib/centreon/remote-data/" + - name: legacycmd + package: gorgone::modules::centreon::legacycmd::hooks + enable: true + cmd_file: "/var/lib/centreon/centcore.cmd" + cache_dir: "/var/cache/centreon/" + cache_dir_trap: "/etc/snmp/centreon_traps/" + remote_dir: "/var/lib/centreon/remote-data/" - - name: engine - package: gorgone::modules::centreon::engine::hooks - enable: true - command_file: "/var/lib/centreon-engine/rw/centengine.cmd" + - name: engine + package: gorgone::modules::centreon::engine::hooks + enable: true + command_file: "/var/lib/centreon-engine/rw/centengine.cmd" diff --git a/gorgone/config/gorgoned-poller.yml b/gorgone/config/gorgoned-poller.yml index 511caf08eee..39dae92f89d 100644 --- a/gorgone/config/gorgoned-poller.yml +++ b/gorgone/config/gorgoned-poller.yml @@ -1,19 +1,20 @@ name: gorgoned-poller description: Configuration example in a full ZMQ environment for Poller server -gorgone: - gorgonecore: - id: 2 - external_com_type: tcp - external_com_path: "*:5556" - privkey: keys/poller/privkey.pem - authorized_clients: - - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 - modules: - - name: action - package: gorgone::modules::core::action::hooks - enable: true +configuration: + gorgone: + gorgonecore: + id: 2 + external_com_type: tcp + external_com_path: "*:5556" + privkey: keys/poller/privkey.pem + authorized_clients: + - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 + modules: + - name: action + package: gorgone::modules::core::action::hooks + enable: true - - name: engine - package: gorgone::modules::centreon::engine::hooks - enable: true - command_file: "/var/lib/centreon-engine/rw/centengine.cmd" + - name: engine + package: gorgone::modules::centreon::engine::hooks + enable: true + command_file: "/var/lib/centreon-engine/rw/centengine.cmd" diff --git a/gorgone/config/gorgoned-remote-ssh.yml b/gorgone/config/gorgoned-remote-ssh.yml index 01345e67a64..592132d79c3 100644 --- a/gorgone/config/gorgoned-remote-ssh.yml +++ b/gorgone/config/gorgoned-remote-ssh.yml @@ -1,6 +1,6 @@ name: gorgoned-remote-ssh description: Configuration example in a SSH environment for Remote server -common: +configuration: database: db_configuration: dsn: "mysql:host=localhost;dbname=centreon" @@ -10,31 +10,31 @@ common: dsn: "mysql:host=localhost;dbname=centreon_storage" username: centreon password: centreon -gorgone: - gorgonecore: - timeout: 50 - modules: - - name: action - package: gorgone::modules::core::action::hooks - enable: true + gorgone: + gorgonecore: + timeout: 50 + modules: + - name: action + package: gorgone::modules::core::action::hooks + enable: true - - name: proxy - package: gorgone::modules::core::proxy::hooks - enable: true + - name: proxy + package: gorgone::modules::core::proxy::hooks + enable: true - - name: nodes - package: gorgone::modules::centreon::nodes::hooks - enable: true + - name: nodes + package: gorgone::modules::centreon::nodes::hooks + enable: true - - name: legacycmd - package: gorgone::modules::centreon::legacycmd::hooks - enable: true - cmd_file: "/var/lib/centreon/centcore.cmd" - cache_dir: "/var/cache/centreon/" - cache_dir_trap: "/etc/snmp/centreon_traps/" - remote_dir: "/var/lib/centreon/remote-data/" + - name: legacycmd + package: gorgone::modules::centreon::legacycmd::hooks + enable: true + cmd_file: "/var/lib/centreon/centcore.cmd" + cache_dir: "/var/cache/centreon/" + cache_dir_trap: "/etc/snmp/centreon_traps/" + remote_dir: "/var/lib/centreon/remote-data/" - - name: engine - package: gorgone::modules::centreon::engine::hooks - enable: true - command_file: "/var/lib/centreon-engine/rw/centengine.cmd" + - name: engine + package: gorgone::modules::centreon::engine::hooks + enable: true + command_file: "/var/lib/centreon-engine/rw/centengine.cmd" diff --git a/gorgone/config/gorgoned-remote-zmq.yml b/gorgone/config/gorgoned-remote-zmq.yml index f61c08a399a..0c831737571 100644 --- a/gorgone/config/gorgoned-remote-zmq.yml +++ b/gorgone/config/gorgoned-remote-zmq.yml @@ -1,6 +1,6 @@ name: gorgoned-remote-zmq description: Configuration example in a full ZMQ environment for Remote server -common: +configuration: database: db_configuration: dsn: "mysql:host=localhost;dbname=centreon" @@ -10,37 +10,37 @@ common: dsn: "mysql:host=localhost;dbname=centreon_storage" username: centreon password: centreon -gorgone: - gorgonecore: - id: 4 - external_com_type: tcp - external_com_path: "*:5556" - privkey: keys/central/privkey.pem - authorized_clients: - - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 - modules: - - name: action - package: gorgone::modules::core::action::hooks - enable: true + gorgone: + gorgonecore: + id: 4 + external_com_type: tcp + external_com_path: "*:5556" + privkey: keys/central/privkey.pem + authorized_clients: + - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 + modules: + - name: action + package: gorgone::modules::core::action::hooks + enable: true - - name: proxy - package: gorgone::modules::core::proxy::hooks - enable: true + - name: proxy + package: gorgone::modules::core::proxy::hooks + enable: true - - name: register - package: gorgone::modules::core::register::hooks - enable: true - config_file: config/registernodes-remote.yml + - name: register + package: gorgone::modules::core::register::hooks + enable: true + config_file: config/registernodes-remote.yml - - name: legacycmd - package: gorgone::modules::centreon::legacycmd::hooks - enable: true - cmd_file: "/var/lib/centreon/centcore.cmd" - cache_dir: "/var/cache/centreon/" - cache_dir_trap: "/etc/snmp/centreon_traps/" - remote_dir: "/var/lib/centreon/remote-data/" + - name: legacycmd + package: gorgone::modules::centreon::legacycmd::hooks + enable: true + cmd_file: "/var/lib/centreon/centcore.cmd" + cache_dir: "/var/cache/centreon/" + cache_dir_trap: "/etc/snmp/centreon_traps/" + remote_dir: "/var/lib/centreon/remote-data/" - - name: engine - package: gorgone::modules::centreon::engine::hooks - enable: true - command_file: "/var/lib/centreon-engine/rw/centengine.cmd" + - name: engine + package: gorgone::modules::centreon::engine::hooks + enable: true + command_file: "/var/lib/centreon-engine/rw/centengine.cmd" diff --git a/gorgone/docs/configuration.md b/gorgone/docs/configuration.md index 3e9899c2ab9..d38bde7cf7a 100644 --- a/gorgone/docs/configuration.md +++ b/gorgone/docs/configuration.md @@ -4,8 +4,9 @@ | :- | :- | | name | Name of the configuration | | description | Short string to decribe the configuration | -| database | Table to set Centreon databases data source names and credentials | -| gorgonecore | Table to set Gorgone main configuration | +| configuration | First configuration entry point | +| database | Entry point to set Centreon databases data source names and credentials | +| gorgonecore | Entry point to set Gorgone main configuration | | modules | Table to load and configuration Gorgone modules | ## *database* @@ -21,15 +22,16 @@ Usefull in a Centreon Central installation to access Centreon databases. #### Example ```yaml -database: - db_centreon: - dsn: "mysql:host=localhost;dbname=centreon" - username: centreon - password: centreon - db_centstorage: - dsn: "mysql:host=localhost;dbname=centreon_storage" - username: centreon - password: centreon +configuration: + database: + db_configuration: + dsn: "mysql:host=localhost;dbname=centreon" + username: centreon + password: centreon + db_realtime: + dsn: "mysql:host=localhost;dbname=centreon_storage" + username: centreon + password: centreon ``` ## *gorgonecore* @@ -62,30 +64,32 @@ database: #### Example ```yaml -gorgonecore: - internal_com_type: ipc - internal_com_path: /tmp/gorgone/routing.ipc - external_com_type: tcp - external_com_path: "*:5555" - timeout: 50 - gorgone_db_type: SQLite - gorgone_db_name: dbname=/var/lib/centreon/gorgone/gorgone.sdb - gorgone_db_host: - gorgone_db_port: - gorgone_db_user: - gorgone_db_password: - hostname: - id: - privkey: keys/central/privkey.pem - cipher: "Cipher::AES" - keysize: 32 - vector: 0123456789012345 - fingerprint_mode: first - fingerprint_mgr: - package: gorgone::class::fingerprint::backend::sql - authorized_clients: - - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 - proxy_name: proxy +configuration: + gorgone: + gorgonecore: + internal_com_type: ipc + internal_com_path: /tmp/gorgone/routing.ipc + external_com_type: tcp + external_com_path: "*:5555" + timeout: 50 + gorgone_db_type: SQLite + gorgone_db_name: dbname=/var/lib/centreon/gorgone/gorgone.sdb + gorgone_db_host: + gorgone_db_port: + gorgone_db_user: + gorgone_db_password: + hostname: + id: + privkey: keys/central/privkey.pem + cipher: "Cipher::AES" + keysize: 32 + vector: 0123456789012345 + fingerprint_mode: first + fingerprint_mgr: + package: gorgone::class::fingerprint::backend::sql + authorized_clients: + - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 + proxy_name: proxy ``` ## *modules* diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 26aca7cf5a3..9c1b025a984 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -77,48 +77,48 @@ sub init_server_keys { $self->{logger}->writeLogInfo("[core] Initialize server keys"); $self->{keys_loaded} = 0; - $self->{config} = { gorgone => { gorgonecore => { } } } if (!defined($self->{config}->{gorgone})); + $self->{config} = { gorgone => { gorgonecore => { } } } if (!defined($self->{config}->{configuration}->{gorgone})); - $self->{config}->{gorgone}->{gorgonecore}->{privkey} = 'keys/rsakey.priv.pem' - if (!defined($self->{config}->{gorgone}->{gorgonecore}->{privkey}) || $self->{config}->{gorgone}->{gorgonecore}->{privkey} eq ''); - $self->{config}->{gorgone}->{gorgonecore}->{pubkey} = 'keys/rsakey.pub.pem' - if (!defined($self->{config}->{gorgone}->{gorgonecore}->{pubkey}) || $self->{config}->{gorgone}->{gorgonecore}->{pubkey} eq ''); + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{privkey} = 'keys/rsakey.priv.pem' + if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{privkey}) || $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{privkey} eq ''); + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{pubkey} = 'keys/rsakey.pub.pem' + if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{pubkey}) || $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{pubkey} eq ''); - if (! -f $self->{config}->{gorgone}->{gorgonecore}->{privkey} && ! -f $self->{config}->{gorgone}->{gorgonecore}->{pubkey}) { + if (! -f $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{privkey} && ! -f $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{pubkey}) { ($code, $content_privkey, $content_pubkey) = gorgone::standard::library::generate_keys(logger => $self->{logger}); return if ($code == 0); $code = gorgone::standard::misc::write_file( logger => $self->{logger}, - filename => $self->{config}->{gorgone}->{gorgonecore}->{privkey}, + filename => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{privkey}, content => $content_privkey, ); return if ($code == 0); - $self->{logger}->writeLogInfo("[core] Private key file '$self->{config}->{gorgone}->{gorgonecore}->{privkey}' written"); + $self->{logger}->writeLogInfo("[core] Private key file '$self->{config}->{configuration}->{gorgone}->{gorgonecore}->{privkey}' written"); $code = gorgone::standard::misc::write_file( logger => $self->{logger}, - filename => $self->{config}->{gorgone}->{gorgonecore}->{pubkey}, + filename => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{pubkey}, content => $content_pubkey, ); return if ($code == 0); - $self->{logger}->writeLogInfo("[core] Public key file '$self->{config}->{gorgone}->{gorgonecore}->{pubkey}' written"); + $self->{logger}->writeLogInfo("[core] Public key file '$self->{config}->{configuration}->{gorgone}->{gorgonecore}->{pubkey}' written"); } ($code, $self->{server_privkey}) = gorgone::standard::library::loadprivkey( logger => $self->{logger}, - privkey => $self->{config}->{gorgone}->{gorgonecore}->{privkey}, + privkey => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{privkey}, noquit => 1 ); return if ($code == 0); - $self->{logger}->writeLogInfo("[core] Private key file '$self->{config}->{gorgone}->{gorgonecore}->{privkey}' loaded"); + $self->{logger}->writeLogInfo("[core] Private key file '$self->{config}->{configuration}->{gorgone}->{gorgonecore}->{privkey}' loaded"); ($code, $self->{server_pubkey}) = gorgone::standard::library::loadpubkey( logger => $self->{logger}, - pubkey => $self->{config}->{gorgone}->{gorgonecore}->{pubkey}, + pubkey => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{pubkey}, noquit => 1 ); return if ($code == 0); - $self->{logger}->writeLogInfo("[core] Public key file '$self->{config}->{gorgone}->{gorgonecore}->{pubkey}' loaded"); + $self->{logger}->writeLogInfo("[core] Public key file '$self->{config}->{configuration}->{gorgone}->{gorgonecore}->{pubkey}' loaded"); $self->{keys_loaded} = 1; } @@ -142,56 +142,56 @@ sub init { $self->{config} = $self->yaml_load_config(file => $self->{config_file}); $self->init_server_keys(); - $self->{config}->{gorgone}->{gorgonecore}->{internal_com_type} = 'ipc' - if (!defined($self->{config}->{gorgone}->{gorgonecore}->{internal_com_type}) || $self->{config}->{gorgone}->{gorgonecore}->{internal_com_type} eq ''); - $self->{config}->{gorgone}->{gorgonecore}->{internal_com_path} = '/tmp/gorgone/routing.ipc' - if (!defined($self->{config}->{gorgone}->{gorgonecore}->{internal_com_path}) || $self->{config}->{gorgone}->{gorgonecore}->{internal_com_path} eq ''); - $self->{config}->{gorgone}->{gorgonecore}->{timeout} = - defined($self->{config}->{gorgone}->{gorgonecore}->{timeout}) && $self->{config}->{gorgone}->{gorgonecore}->{timeout} =~ /(\d+)/ ? $1 : 50; - - $self->{config}->{gorgone}->{gorgonecore}->{cipher} = 'Cipher::AES' - if (!defined($self->{config}->{gorgone}->{gorgonecore}->{cipher}) || $self->{config}->{gorgone}->{gorgonecore}->{cipher} eq ''); - $self->{config}->{gorgone}->{gorgonecore}->{keysize} = 32 - if (!defined($self->{config}->{gorgone}->{gorgonecore}->{keysize}) || $self->{config}->{gorgone}->{gorgonecore}->{keysize} eq ''); - $self->{config}->{gorgone}->{gorgonecore}->{vector} = '0123456789012345' - if (!defined($self->{config}->{gorgone}->{gorgonecore}->{vector}) || $self->{config}->{gorgone}->{gorgonecore}->{vector} eq ''); - - $self->{config}->{gorgone}->{gorgonecore}->{fingerprint_mode} = - defined($self->{config}->{gorgone}->{gorgonecore}->{fingerprint_mode}) && $self->{config}->{gorgone}->{gorgonecore}->{fingerprint_mode} =~ /^\s*(always|firt|strict)\s*/i ? lc($1) : 'first'; - $self->{config}->{gorgone}->{gorgonecore}->{fingerprint_mgr} = {} if (!defined($self->{config}->{gorgone}->{gorgonecore}->{fingerprint_mgr})); - $self->{config}->{gorgone}->{gorgonecore}->{fingerprint_mgr}->{package} = 'gorgone::class::fingerprint::backend::sql' - if (!defined($self->{config}->{gorgone}->{gorgonecore}->{fingerprint_mgr}->{package}) || $self->{config}->{gorgone}->{gorgonecore}->{fingerprint_mgr}->{package} eq ''); - - $self->{config}->{gorgone}->{gorgonecore}->{fingerprint_mode} = - defined($self->{config}->{gorgone}->{gorgonecore}->{fingerprint_mode}) && $self->{config}->{gorgone}->{gorgonecore}->{fingerprint_mode} =~ /^\s*(always|firt|strict)\s*/i ? lc($1) : 'first'; - - $self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_type} = - defined($self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_type}) && $self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_type} ne '' ? $self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_type} : 'SQLite'; - $self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_name} = - defined($self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_name}) && $self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_name} ne '' ? $self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_name} : 'dbname=/var/lib/centreon/gorgone/gorgone.sdb'; - $self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_autocreate_schema} = - defined($self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_autocreate_schema}) && $self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_autocreate_schema} =~ /(\d+)/ ? $1 : 1; + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_type} = 'ipc' + if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_type}) || $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_type} eq ''); + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_path} = '/tmp/gorgone/routing.ipc' + if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_path}) || $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_path} eq ''); + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{timeout} = + defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{timeout}) && $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{timeout} =~ /(\d+)/ ? $1 : 50; + + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{cipher} = 'Cipher::AES' + if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{cipher}) || $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{cipher} eq ''); + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{keysize} = 32 + if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{keysize}) || $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{keysize} eq ''); + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{vector} = '0123456789012345' + if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{vector}) || $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{vector} eq ''); + + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{fingerprint_mode} = + defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{fingerprint_mode}) && $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{fingerprint_mode} =~ /^\s*(always|firt|strict)\s*/i ? lc($1) : 'first'; + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{fingerprint_mgr} = {} if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{fingerprint_mgr})); + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{fingerprint_mgr}->{package} = 'gorgone::class::fingerprint::backend::sql' + if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{fingerprint_mgr}->{package}) || $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{fingerprint_mgr}->{package} eq ''); + + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{fingerprint_mode} = + defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{fingerprint_mode}) && $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{fingerprint_mode} =~ /^\s*(always|firt|strict)\s*/i ? lc($1) : 'first'; + + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_type} = + defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_type}) && $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_type} ne '' ? $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_type} : 'SQLite'; + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_name} = + defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_name}) && $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_name} ne '' ? $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_name} : 'dbname=/var/lib/centreon/gorgone/gorgone.sdb'; + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_autocreate_schema} = + defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_autocreate_schema}) && $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_autocreate_schema} =~ /(\d+)/ ? $1 : 1; gorgone::standard::library::init_database( gorgone => $gorgone, - type => $self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_type}, - db => $self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_name}, - host => $self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_host}, - port => $self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_port}, - user => $self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_user}, - password => $self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_password}, - autocreate_schema => $self->{config}->{gorgone}->{gorgonecore}->{gorgone_db_autocreate_schema}, + type => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_type}, + db => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_name}, + host => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_host}, + port => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_port}, + user => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_user}, + password => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_password}, + autocreate_schema => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_autocreate_schema}, force => 2, logger => $gorgone->{logger} ); - $self->{hostname} = $self->{config}->{gorgone}->{gorgonecore}->{hostname}; + $self->{hostname} = $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{hostname}; if (!defined($self->{hostname}) || $self->{hostname} eq '') { $self->{hostname} = hostname(); } - $self->{config}->{gorgone}->{gorgonecore}->{proxy_name} = - (defined($self->{config}->{gorgone}->{gorgonecore}->{proxy_name}) && $self->{config}->{gorgone}->{gorgonecore}->{proxy_name} ne '') ? $self->{config}->{gorgone}->{gorgonecore}->{proxy_name} : 'proxy'; - $self->{id} = $self->{config}->{gorgone}->{gorgonecore}->{id}; + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{proxy_name} = + (defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{proxy_name}) && $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{proxy_name} ne '') ? $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{proxy_name} : 'proxy'; + $self->{id} = $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{id}; $self->load_modules(); @@ -315,9 +315,9 @@ sub load_module { my ($loaded, $namespace, $name, $events) = $self->{modules_register}->{$package}->{register}->( config => $options{config_module}, - config_core => $self->{config}->{gorgone}->{gorgonecore}, - config_db_centreon => $self->{config}->{common}->{database}->{db_configuration}, - config_db_centstorage => $self->{config}->{common}->{database}->{db_realtime}, + config_core => $self->{config}->{configuration}->{gorgone}->{gorgonecore}, + config_db_centreon => $self->{config}->{configuration}->{database}->{db_configuration}, + config_db_centstorage => $self->{config}->{configuration}->{database}->{db_realtime}, logger => $self->{logger}, ); if ($loaded == 0) { @@ -344,9 +344,9 @@ sub load_module { sub load_modules { my ($self) = @_; - return if (!defined($self->{config}->{gorgone}->{modules})); + return if (!defined($self->{config}->{configuration}->{gorgone}->{modules})); - foreach my $module (@{$self->{config}->{gorgone}->{modules}}) { + foreach my $module (@{$self->{config}->{configuration}->{gorgone}->{modules}}) { $self->load_module(config_module => $module); } @@ -438,8 +438,8 @@ sub message_run { # Check Routing if (defined($target)) { - if (!defined($self->{modules_id}->{ $self->{config}->{gorgone}->{gorgonecore}->{proxy_name} }) || - !defined($self->{modules_register}->{ $self->{modules_id}->{ $self->{config}->{gorgone}->{gorgonecore}->{proxy_name} } })) { + if (!defined($self->{modules_id}->{ $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{proxy_name} }) || + !defined($self->{modules_register}->{ $self->{modules_id}->{ $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{proxy_name} } })) { gorgone::standard::library::add_history( dbh => $self->{db_gorgone}, code => 1, @@ -454,7 +454,7 @@ sub message_run { $self->{counters}->{proxy}->{lc($action)}++; $self->{counters}->{proxy}->{total}++; - $self->{modules_register}->{ $self->{modules_id}->{ $self->{config}->{gorgone}->{gorgonecore}->{proxy_name} } }->{routing}->( + $self->{modules_register}->{ $self->{modules_id}->{ $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{proxy_name} } }->{routing}->( socket => $self->{internal_socket}, dbh => $self->{db_gorgone}, logger => $self->{logger}, @@ -470,7 +470,7 @@ sub message_run { if ($action =~ /^(?:PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID|SYNCLOGS|LOADMODULE|UNLOADMODULE|INFORMATION|GETTHUMBPRINT)$/) { my ($code, $response, $response_type) = $self->{internal_register}->{lc($action)}->( gorgone => $self, - gorgone_config => $self->{config}->{gorgone}, + gorgone_config => $self->{config}->{configuration}->{gorgone}, identity => $options{identity}, router_type => $options{router_type}, id => $self->{id}, @@ -551,10 +551,10 @@ sub handshake { if ($status == 1) { ($status, my $response) = gorgone::standard::library::uncrypt_message( - cipher => $self->{config}->{gorgone}->{gorgonecore}->{cipher}, + cipher => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{cipher}, message => $message, symkey => $key, - vector => $self->{config}->{gorgone}->{gorgonecore}->{vector} + vector => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{vector} ); if ($status == 0 && $response =~ /^\[.*\]/) { gorgone::standard::library::update_identity(dbh => $self->{db_gorgone}, identity => $identity); @@ -579,7 +579,7 @@ sub handshake { privkey => $self->{server_privkey}, message => $message, logger => $self->{logger}, - authorized_clients => $self->{config}->{gorgone}->{gorgonecore}->{authorized_clients} + authorized_clients => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{authorized_clients} ); if ($status == -1) { gorgone::standard::library::zmq_core_response( @@ -592,8 +592,8 @@ sub handshake { } my ($status, $symkey) = gorgone::standard::library::generate_symkey( logger => $self->{logger}, - cipher => $self->{config}->{gorgone}->{gorgonecore}->{cipher}, - keysize => $self->{config}->{gorgone}->{gorgonecore}->{keysize} + cipher => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{cipher}, + keysize => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{keysize} ); if ($status == -1) { gorgone::standard::library::zmq_core_response( @@ -641,8 +641,8 @@ sub send_message_parent { socket => $gorgone->{external_socket}, identity => $options{identity}, response_type => $options{response_type}, - cipher => $gorgone->{config}->{gorgone}->{gorgonecore}->{cipher}, - vector => $gorgone->{config}->{gorgone}->{gorgonecore}->{vector}, + cipher => $gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{cipher}, + vector => $gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{vector}, symkey => $key, token => $options{token}, code => $options{code}, @@ -663,8 +663,8 @@ sub router_external_event { gorgone::standard::library::zmq_core_response( socket => $gorgone->{external_socket}, identity => $identity, response_type => $response_type, - cipher => $gorgone->{config}->{gorgone}->{gorgonecore}->{cipher}, - vector => $gorgone->{config}->{gorgone}->{gorgonecore}->{vector}, + cipher => $gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{cipher}, + vector => $gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{vector}, symkey => $key, token => $token, code => $code, data => $response @@ -742,16 +742,16 @@ sub run { } $gorgone->{internal_socket} = gorgone::standard::library::create_com( - type => $gorgone->{config}->{gorgone}->{gorgonecore}->{internal_com_type}, - path => $gorgone->{config}->{gorgone}->{gorgonecore}->{internal_com_path}, + type => $gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_type}, + path => $gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_path}, zmq_type => 'ZMQ_ROUTER', name => 'router-internal', logger => $gorgone->{logger} ); - if (defined($gorgone->{config}->{gorgone}->{gorgonecore}->{external_com_type}) && $gorgone->{config}->{gorgone}->{gorgonecore}->{external_com_type} ne '') { + if (defined($gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_type}) && $gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_type} ne '') { if ($gorgone->{keys_loaded}) { $gorgone->{external_socket} = gorgone::standard::library::create_com( - type => $gorgone->{config}->{gorgone}->{gorgonecore}->{external_com_type}, - path => $gorgone->{config}->{gorgone}->{gorgonecore}->{external_com_path}, + type => $gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_type}, + path => $gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_path}, zmq_type => 'ZMQ_ROUTER', name => 'router-external', logger => $gorgone->{logger} ); @@ -826,7 +826,7 @@ sub run { } # Send KILL - if (time() - $gorgone->{kill_timer} > $gorgone->{config}->{gorgone}->{gorgonecore}->{timeout}) { + if (time() - $gorgone->{kill_timer} > $gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{timeout}) { foreach my $name (keys %{$gorgone->{modules_register}}) { $gorgone->{modules_register}->{$name}->{kill_internal}->(logger => $gorgone->{logger}); } From d9812c3250e4b6aa035dfd81cb2ebc9e5d7aebcf Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Thu, 23 Jan 2020 17:15:41 +0100 Subject: [PATCH 310/948] enh(config): add filter system --- gorgone/gorgone/class/core.pm | 11 ++++++--- gorgone/gorgone/class/script.pm | 42 ++++++++++++++++++++++++++++----- 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 9c1b025a984..bba5b4be823 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -77,8 +77,10 @@ sub init_server_keys { $self->{logger}->writeLogInfo("[core] Initialize server keys"); $self->{keys_loaded} = 0; - $self->{config} = { gorgone => { gorgonecore => { } } } if (!defined($self->{config}->{configuration}->{gorgone})); - + $self->{config} = { configuration => {} } if (!defined($self->{config}->{configuration})); + $self->{config}->{configuration} = { gorgone => {} } if (!defined($self->{config}->{configuration}->{gorgone})); + $self->{config}->{configuration}->{gorgone} = { gorgonecore => {} } if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore})); + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{privkey} = 'keys/rsakey.priv.pem' if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{privkey}) || $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{privkey} eq ''); $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{pubkey} = 'keys/rsakey.pub.pem' @@ -139,7 +141,10 @@ sub init { $self->{logger}->writeLogError("[core] can't find config file '$self->{config_file}'"); exit(1); } - $self->{config} = $self->yaml_load_config(file => $self->{config_file}); + $self->{config} = $self->yaml_load_config( + file => $self->{config_file}, + filter => '!($ariane eq "configuration##" || $ariane =~ /^configuration##(?:gorgone|database)##/)' + ); $self->init_server_keys(); $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_type} = 'ipc' diff --git a/gorgone/gorgone/class/script.pm b/gorgone/gorgone/class/script.pm index 87eebc82b64..fa2ea12a317 100644 --- a/gorgone/gorgone/class/script.pm +++ b/gorgone/gorgone/class/script.pm @@ -163,18 +163,43 @@ sub yaml_parse_config { if (ref(${$options{config}}) eq 'HASH') { foreach (keys %{${$options{config}}}) { - $self->yaml_parse_config(config => \${$options{config}}->{$_}, current_dir => $options{current_dir}); + my $ariane = $options{ariane} . $_ . '##'; + if (defined($options{filter}) && eval "$options{filter}") { + delete ${$options{config}}->{$_}; + next; + } + $self->yaml_parse_config( + config => \${$options{config}}->{$_}, + current_dir => $options{current_dir}, + filter => $options{filter}, + ariane => $ariane + ); } } elsif (ref(${$options{config}}) eq 'ARRAY') { - for (my $i = 0; $i < scalar(@{${$options{config}}}); $i++) { - $self->yaml_parse_config(config => \${$options{config}}->[$i], current_dir => $options{current_dir}); + my $size = @{${$options{config}}}; + my $ariane = $options{ariane} . 'ARRAY##'; + for (my $i = 0; $i < $size; $i++) { + if (defined($options{filter}) && eval "$options{filter}") { + ${$options{config}} = undef; + last; + } + $self->yaml_parse_config( + config => \${$options{config}}->[$i], + current_dir => $options{current_dir}, + filter => $options{filter}, + ariane => $ariane + ); } } elsif (ref(${$options{config}}) eq 'include') { - my @files = $self->yaml_get_include(include => ${${$options{config}}}, current_dir => $options{current_dir}); + my @files = $self->yaml_get_include( + include => ${${$options{config}}}, + current_dir => $options{current_dir}, + filter => $options{filter} + ); ${$options{config}} = undef; foreach (@files) { next if (! -r $_); - my $config = yaml_load_config(file => $_); + my $config = yaml_load_config(file => $_, filter => $options{filter}, $options{ariane}); next if (!defined($config)); if (ref($config) eq 'ARRAY') { ${$options{config}} = [] if (ref(${$options{config}}) ne 'ARRAY'); @@ -208,7 +233,12 @@ sub yaml_load_config { } my $current_dir = File::Basename::dirname($options{file}); - $self->yaml_parse_config(config => \$config, current_dir => $current_dir); + $self->yaml_parse_config( + config => \$config, + current_dir => $current_dir, + filter => $options{filter}, + ariane => defined($options{ariane}) ? $options{ariane} : '' + ); return $config; } From 65a67276369696c2739a7372c66245a62a562763 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 24 Jan 2020 13:48:24 +0100 Subject: [PATCH 311/948] enh(api/internal): send correct action return code --- gorgone/gorgone/class/core.pm | 2 +- gorgone/gorgone/standard/library.pm | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index bba5b4be823..2090bc8d40e 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -433,7 +433,7 @@ sub message_run { if ($self->{stop} == 1) { gorgone::standard::library::add_history( dbh => $self->{db_gorgone}, - code => 1, + code => $code, token => $token, data => { message => 'gorgone is stopping/restarting. Cannot proceed request.' }, json_encode => 1 diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index d8145767769..97ebdd0d697 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -358,7 +358,7 @@ sub getthumbprint { return (1, { action => 'getthumbprint', message => 'no public key loaded' }, 'GETTHUMBPRINT'); } my $thumbprint = $options{gorgone}->{server_pubkey}->export_key_jwk_thumbprint('SHA256'); - return (0, { action => 'getthumbprint', message => 'ok', data => { thumbprint => $thumbprint } }, 'GETTHUMBPRINT'); + return (2, { action => 'getthumbprint', message => 'ok', data => { thumbprint => $thumbprint } }, 'GETTHUMBPRINT'); } sub information { @@ -369,7 +369,7 @@ sub information { modules => $options{gorgone}->{modules_id}, api_endpoints => $options{gorgone}->{api_endpoints} }; - return (0, { action => 'information', message => 'ok', data => $data }, 'INFORMATION'); + return (2, { action => 'information', message => 'ok', data => $data }, 'INFORMATION'); } sub unloadmodule { @@ -458,7 +458,7 @@ sub constatus { my $name = $options{gorgone}->{modules_id}->{$options{gorgone_config}->{gorgonecore}->{proxy_name}}; my $method; if (defined($name) && ($method = $name->can('get_constatus_result'))) { - return (0, { action => 'constatus', message => 'ok', data => $method->() }, 'CONSTATUS'); + return (2, { action => 'constatus', message => 'ok', data => $method->() }, 'CONSTATUS'); } } From aae9265f2dad6c852245ca6669a14c13c0dea739 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 24 Jan 2020 14:01:32 +0100 Subject: [PATCH 312/948] enh(core): fix last commit --- gorgone/gorgone/class/core.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 2090bc8d40e..86efcaaf689 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -433,7 +433,7 @@ sub message_run { if ($self->{stop} == 1) { gorgone::standard::library::add_history( dbh => $self->{db_gorgone}, - code => $code, + code => 0, token => $token, data => { message => 'gorgone is stopping/restarting. Cannot proceed request.' }, json_encode => 1 @@ -487,7 +487,7 @@ sub message_run { if ($action =~ /^(?:CONSTATUS|INFORMATION|GETTHUMBPRINT)$/) { gorgone::standard::library::add_history( dbh => $self->{db_gorgone}, - code => 0, + code => $code, token => $token, data => $response, json_encode => 1 From fe0c62c47205fe07234a3eeed51306d2f70d9971 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 24 Jan 2020 18:13:39 +0100 Subject: [PATCH 313/948] fix(packaging): add missing YAML dep --- gorgone/packaging/centreon-gorgone.spectemplate | 1 + 1 file changed, 1 insertion(+) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 1e7374d1590..f85393ddd89 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -16,6 +16,7 @@ Requires: perl(ZMQ::Constants) Requires: perl(Crypt::CBC) Requires: perl(JSON::XS) Requires: perl(JSON::PP) +Requires: perl(YAML) Requires: perl(YAML::XS) Requires: perl(DBD::SQLite) Requires: perl(DBD::mysql) From 2ccab492fbbf72336cfee9fc779f6b087bedccb8 Mon Sep 17 00:00:00 2001 From: sc979 <34628915+sc979@users.noreply.github.com> Date: Wed, 29 Jan 2020 09:56:46 +0100 Subject: [PATCH 314/948] fix(core): adapt gorgone's CI integration (#9) * doc: update the doc * fix(core): modify required conf file * fix(core): modify service config file path * fix(core): add new mandatory config file * fix(core): deploy the new config file * style: remove carriage return * fix(packaging): rework spectemplate. * fix(packaging): add missing %description for centreon-config. * feat(packaging): updates. * fix(packaging): fix gorgoned.service. * enh(packaging): adapt some names/pathes * fix(core): add centreon dimension for db conf * fix(doc): adapt migration doc * fix(doc): adapt configuration doc Co-authored-by: Matthieu Kermagoret Co-authored-by: Colin Gagnaire --- gorgone/config/logrotate/gorgoned | 2 +- .../config/systemd/centreon-gorgone-sysconfig | 4 - ...treon-gorgone-service => gorgoned-service} | 7 +- gorgone/config/systemd/gorgoned-sysconfig | 4 + gorgone/contrib/gorgone_config_init.pl | 140 +++++++++--------- gorgone/docs/client_server_zmq.md | 32 ++-- gorgone/docs/configuration.md | 20 +-- gorgone/docs/getting_started.md | 4 +- gorgone/docs/migration.md | 124 +++++++++------- gorgone/gorgone/class/core.pm | 4 +- .../packaging/centreon-gorgone.spectemplate | 48 ++++-- gorgone/packaging/centreon.yaml | 3 + gorgone/packaging/config.yaml | 3 + 13 files changed, 225 insertions(+), 170 deletions(-) delete mode 100644 gorgone/config/systemd/centreon-gorgone-sysconfig rename gorgone/config/systemd/{centreon-gorgone-service => gorgoned-service} (89%) create mode 100644 gorgone/config/systemd/gorgoned-sysconfig create mode 100644 gorgone/packaging/centreon.yaml create mode 100644 gorgone/packaging/config.yaml diff --git a/gorgone/config/logrotate/gorgoned b/gorgone/config/logrotate/gorgoned index 59d4d5fbcaa..3232fa4fd86 100644 --- a/gorgone/config/logrotate/gorgoned +++ b/gorgone/config/logrotate/gorgoned @@ -1,4 +1,4 @@ -/var/log/centreon/gorgoned.log { +/var/log/centreon-gorgone/gorgoned.log { copytruncate weekly rotate 52 diff --git a/gorgone/config/systemd/centreon-gorgone-sysconfig b/gorgone/config/systemd/centreon-gorgone-sysconfig deleted file mode 100644 index e42a3f37104..00000000000 --- a/gorgone/config/systemd/centreon-gorgone-sysconfig +++ /dev/null @@ -1,4 +0,0 @@ -# Configuration file for Centreon Gorgone. - -# OPTIONS for the daemon launch -OPTIONS="--config=/etc/centreon/gorgoned.yml --logfile=/var/log/centreon/gorgoned.log --severity=error" diff --git a/gorgone/config/systemd/centreon-gorgone-service b/gorgone/config/systemd/gorgoned-service similarity index 89% rename from gorgone/config/systemd/centreon-gorgone-service rename to gorgone/config/systemd/gorgoned-service index c59d31db66e..aec4c1efede 100644 --- a/gorgone/config/systemd/centreon-gorgone-service +++ b/gorgone/config/systemd/gorgoned-service @@ -1,4 +1,5 @@ -## Copyright 2019 Centreon +## +## Copyright 2019-2020 Centreon ## ## Licensed under the Apache License, Version 2.0 (the "License"); ## you may not use this file except in compliance with the License. @@ -22,10 +23,10 @@ After=centreon.service ReloadPropagatedFrom=centreon.service [Service] -EnvironmentFile=/etc/sysconfig/centreon-gorgone +EnvironmentFile=/etc/sysconfig/gorgoned ExecStart=/usr/bin/perl /usr/bin/gorgoned $OPTIONS Type=simple -User=centreon +User=centreon-gorgone [Install] WantedBy=multi-user.target diff --git a/gorgone/config/systemd/gorgoned-sysconfig b/gorgone/config/systemd/gorgoned-sysconfig new file mode 100644 index 00000000000..4e14ad8b36b --- /dev/null +++ b/gorgone/config/systemd/gorgoned-sysconfig @@ -0,0 +1,4 @@ +# Configuration file for Centreon Gorgone. + +# OPTIONS for the daemon launch +OPTIONS="--config=/etc/centreon-gorgone/config.yaml --logfile=/var/log/centreon-gorgone/gorgoned.log --severity=info" diff --git a/gorgone/contrib/gorgone_config_init.pl b/gorgone/contrib/gorgone_config_init.pl index 7864e202a06..576ea8044c3 100644 --- a/gorgone/contrib/gorgone_config_init.pl +++ b/gorgone/contrib/gorgone_config_init.pl @@ -40,7 +40,8 @@ sub init { $self->SUPER::init(); $self->{centcore_config} = '/etc/centreon/conf.pm' if (!defined($self->{centcore_config}) || $self->{centcore_config} eq ''); - $self->{gorgone_config} = '/etc/centreon/gorgoned.yml' if (!defined($self->{gorgone_config}) || $self->{gorgone_config} eq ''); + $self->{gorgone_config} = '/etc/centreon-gorgone/config.yaml' if (!defined($self->{gorgone_config}) || + $self->{gorgone_config} eq ''); } sub read_centcore_config { @@ -82,72 +83,75 @@ sub write_gorgone_config { my $content = <<"END_FILE"; name: gorgoned description: Configuration init by gorgone_config_init -database: - db_centreon: - dsn: "mysql:host=$centreon_config->{db_host}${db_port};dbname=$centreon_config->{centreon_db}" - username: "$centreon_config->{db_user}" - password: "$centreon_config->{db_passwd}" - db_centstorage: - dsn: "mysql:host=$centreon_config->{db_host}${db_port};dbname=$centreon_config->{centstorage_db}" - username: "$centreon_config->{db_user}" - password: "$centreon_config->{db_passwd}" -gorgonecore: - hostname: - id: - privkey: /var/spool/centreon/.gorgone/rsakey.priv.pem - pubkey: /var/spool/centreon/.gorgone/rsakey.pub.pem -modules: - - name: httpserver - package: gorgone::modules::core::httpserver::hooks - enable: false - address: 0.0.0.0 - port: 8443 - ssl: true - ssl_cert_file: /etc/pki/tls/certs/server-cert.pem - ssl_key_file: /etc/pki/tls/server-key.pem - auth: - enabled: false - user: admin - password: password - allowed_hosts: - enabled: true - subnets: - - 127.0.0.1/32 - - - name: cron - package: gorgone::modules::core::cron::hooks - enable: false - - - name: action - package: gorgone::modules::core::action::hooks - enable: true - - - name: proxy - package: gorgone::modules::core::proxy::hooks - enable: true - - - name: pollers - package: gorgone::modules::centreon::nodes::hooks - enable: true - - - name: broker - package: gorgone::modules::centreon::broker::hooks - enable: false - cache_dir: "/var/lib/centreon/broker-stats/" - cron: - - id: broker_stats - timespec: "*/2 * * * *" - action: BROKERSTATS - parameters: - timeout: 10 - - - name: legacycmd - package: gorgone::modules::centreon::legacycmd::hooks - enable: true - cmd_file: "$centreon_config->{VarLib}/centcore.cmd" - cache_dir: "$centreon_config->{CacheDir}" - cache_dir_trap: "/etc/snmp/centreon_traps/" - remote_dir: "$centreon_config->{VarLib}/remote-data/" +configuration: + centreon: + database: + db_configuration: + dsn: "mysql:host=$centreon_config->{db_host}${db_port};dbname=$centreon_config->{centreon_db}" + username: "$centreon_config->{db_user}" + password: "$centreon_config->{db_passwd}" + db_realtime: + dsn: "mysql:host=$centreon_config->{db_host}${db_port};dbname=$centreon_config->{centstorage_db}" + username: "$centreon_config->{db_user}" + password: "$centreon_config->{db_passwd}" + gorgone: + gorgonecore: + hostname: + id: + privkey: /var/spool/centreon/.gorgone/rsakey.priv.pem + pubkey: /var/spool/centreon/.gorgone/rsakey.pub.pem + modules: + - name: httpserver + package: gorgone::modules::core::httpserver::hooks + enable: false + address: 0.0.0.0 + port: 8443 + ssl: true + ssl_cert_file: /etc/pki/tls/certs/server-cert.pem + ssl_key_file: /etc/pki/tls/server-key.pem + auth: + enabled: false + user: admin + password: password + allowed_hosts: + enabled: true + subnets: + - 127.0.0.1/32 + + - name: cron + package: gorgone::modules::core::cron::hooks + enable: false + + - name: action + package: gorgone::modules::core::action::hooks + enable: true + + - name: proxy + package: gorgone::modules::core::proxy::hooks + enable: true + + - name: pollers + package: gorgone::modules::centreon::nodes::hooks + enable: true + + - name: broker + package: gorgone::modules::centreon::broker::hooks + enable: false + cache_dir: "/var/lib/centreon/broker-stats/" + cron: + - id: broker_stats + timespec: "*/2 * * * *" + action: BROKERSTATS + parameters: + timeout: 10 + + - name: legacycmd + package: gorgone::modules::centreon::legacycmd::hooks + enable: true + cmd_file: "$centreon_config->{VarLib}/centcore.cmd" + cache_dir: "$centreon_config->{CacheDir}" + cache_dir_trap: "/etc/snmp/centreon_traps/" + remote_dir: "$centreon_config->{VarLib}/remote-data/" END_FILE print $fh $content; @@ -184,7 +188,7 @@ =head1 OPTIONS =item B<--gorgone-config> -Specify the gorgone config file created (default: '/etc/centreon/gorgoned.yml'). +Specify the gorgone config file created (default: '/etc/centreon-gorgone/config.yaml'). =item B<--severity> diff --git a/gorgone/docs/client_server_zmq.md b/gorgone/docs/client_server_zmq.md index 7ea22410a18..0ef1d30e1d5 100644 --- a/gorgone/docs/client_server_zmq.md +++ b/gorgone/docs/client_server_zmq.md @@ -39,13 +39,16 @@ $ perl /usr/local/bin/gorgone_key_thumbprint.pl --key-path='/var/spool/centreon/ #### Server -In the *gorgoned.yml* configuration file, add the following directives under the *gorgonecore* section: +In the */etc/centreon/confid.d/20-gorgoned.yaml* configuration file, add the following directives under the +*gorgonecore* +section: ```yaml -gorgonecore: - id: 1 - privkey: /var/spool/centreon/.gorgone/privkey.pem - pubkey: /var/spool/centreon/.gorgone/pubkey.pem +gorgone: + gorgonecore: + id: 1 + privkey: /var/spool/centreon/.gorgone/privkey.pem + pubkey: /var/spool/centreon/.gorgone/pubkey.pem ``` Add the [register](../docs/modules/core/register.md) module and define the path to the dedicated configuration file. @@ -70,17 +73,18 @@ nodes: #### Client -In the *gorgoned.yml* configuration file, add the following directives under the *gorgonecore* section: +In the */etc/centreon/config.d/20-gorgoned.yaml* configuration file, add the following directives: ```yaml -gorgonecore: - id: 2 - external_com_type: tcp - external_com_path: "*:5556" - privkey: /var/spool/centreon/.gorgone/privkey.pem - pubkey: /var/spool/centreon/.gorgone/pubkey.pem - authorized_clients: - - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 +gorgone: + gorgonecore: + id: 2 + external_com_type: tcp + external_com_path: "*:5556" + privkey: /var/spool/centreon/.gorgone/privkey.pem + pubkey: /var/spool/centreon/.gorgone/pubkey.pem + authorized_clients: + - key: pnI6EWkiTbazjikJXRkLmjml5wvVECYtQduJUjS4QK4 ``` The *authorized_clients* entry allows to define the client public key thumbprint retrieved earlier. diff --git a/gorgone/docs/configuration.md b/gorgone/docs/configuration.md index d38bde7cf7a..86813a5b17b 100644 --- a/gorgone/docs/configuration.md +++ b/gorgone/docs/configuration.md @@ -5,6 +5,7 @@ | name | Name of the configuration | | description | Short string to decribe the configuration | | configuration | First configuration entry point | +| centreon | Entry point to set Centreon configuration | | database | Entry point to set Centreon databases data source names and credentials | | gorgonecore | Entry point to set Gorgone main configuration | | modules | Table to load and configuration Gorgone modules | @@ -23,15 +24,16 @@ Usefull in a Centreon Central installation to access Centreon databases. ```yaml configuration: - database: - db_configuration: - dsn: "mysql:host=localhost;dbname=centreon" - username: centreon - password: centreon - db_realtime: - dsn: "mysql:host=localhost;dbname=centreon_storage" - username: centreon - password: centreon + centreon: + database: + db_configuration: + dsn: "mysql:host=localhost;dbname=centreon" + username: centreon + password: centreon + db_realtime: + dsn: "mysql:host=localhost;dbname=centreon_storage" + username: centreon + password: centreon ``` ## *gorgonecore* diff --git a/gorgone/docs/getting_started.md b/gorgone/docs/getting_started.md index e90d71f21ff..bec6b949f9e 100644 --- a/gorgone/docs/getting_started.md +++ b/gorgone/docs/getting_started.md @@ -134,7 +134,7 @@ $ systemctl status centreon-gorgone Active: active (running) since Mon 2019-09-30 09:36:19 CEST; 2min 29s ago Main PID: 5168 (perl) CGroup: /system.slice/centreon-gorgone.service - ├─5168 /usr/bin/perl /usr/bin/gorgoned --config=/etc/centreon/gorgoned.yml --logfile=/var/log/centreon/gorgoned.log --severity=error + ├─5168 /usr/bin/perl /usr/bin/gorgoned --config=/etc/centreon-gorgone/config.yaml --logfile=/var/log/centreon/gorgoned.log --severity=error ├─5175 gorgone-dbcleaner ├─5182 gorgone-action ├─5187 gorgone-nodes @@ -151,7 +151,7 @@ Sep 30 09:36:19 cga-centreon-19-10.int.centreon.com systemd[1]: Started Centreon If you are using the sources, execute the following command: ```bash -perl gorgoned --config=config/gorgoned.yml --severity=error +perl gorgoned --config=config/config.yaml --severity=error ``` ## Full-ZMQ setup diff --git a/gorgone/docs/migration.md b/gorgone/docs/migration.md index 2f701494997..f1e8c480fac 100644 --- a/gorgone/docs/migration.md +++ b/gorgone/docs/migration.md @@ -6,78 +6,88 @@ If using package: ```bash $ perl /usr/local/bin/gorgone_config_init.pl -2019-09-30 11:00:00 - INFO - file '/etc/centreon/gorgoned.yml' created success +2019-09-30 11:00:00 - INFO - file '/etc/centreon-gorgone/config.yaml' created success ``` If using sources: ```bash $ perl ./contrib/gorgone_config_init.pl -2019-09-30 11:00:00 - INFO - file '/etc/centreon/gorgoned.yml' created success +2019-09-30 11:00:00 - INFO - file '/etc/centreon-gorgone/config.yaml' created success ``` -As a result the following configuration will be created in */etc/centreon/gorgoned.yml*: +As a result the following configuration file will be created at */etc/centreon-gorgone/config.yaml*: ```yaml -name: gorgoned +name: config.yaml description: Configuration init by gorgone_config_init -database: - db_centreon: - dsn: "mysql:host=localhost;port=3306;dbname=centreon" - username: "centreon" - password: "centreon" - db_centstorage: - dsn: "mysql:host=localhost;port=3306;dbname=centreon_storage" - username: "centreon" - password: "centreon" -gorgonecore: - hostname: - id: -modules: - - name: httpserver - package: gorgone::modules::core::httpserver::hooks - enable: false - address: 0.0.0.0 - port: 8443 - ssl: true - ssl_cert_file: /etc/pki/tls/certs/server-cert.pem - ssl_key_file: /etc/pki/tls/server-key.pem - auth: - user: admin - password: password +configuration: + centreon: + database: + db_configuration: + dsn: "mysql:host=localhost;port=3306;dbname=centreon" + username: "centreon" + password: "centreon" + db_realtime: + dsn: "mysql:host=localhost;port=3306;dbname=centreon_storage" + username: "centreon" + password: "centreon" - - name: cron - package: gorgone::modules::core::cron::hooks - enable: false + gorgone: + gorgonecore: + privkey: "/var/spool/centreon/.gorgone/rsakey.priv.pem" + pubkey: "/var/spool/centreon/.gorgone/rsakey.pub.pem" + modules: + - name: httpserver + package: gorgone::modules::core::httpserver::hooks + enable: true + address: 0.0.0.0 + port: 8085 + ssl: true + ssl_cert_file: /etc/pki/tls/certs/server-cert.pem + ssl_key_file: /etc/pki/tls/server-key.pem + auth: + user: admin + password: password - - name: action - package: gorgone::modules::core::action::hooks - enable: true + - name: action + package: gorgone::modules::core::action::hooks + enable: true - - name: proxy - package: gorgone::modules::core::proxy::hooks - enable: true + - name: cron + package: gorgone::modules::core::cron::hooks + enable: false + cron: !include cron.d/*.yaml - - name: pollers - package: gorgone::modules::centreon::pollers::hooks - enable: true + - name: proxy + package: gorgone::modules::core::proxy::hooks + enable: true + + - name: legacycmd + package: gorgone::modules::centreon::legacycmd::hooks + enable: true + cmd_file: "/var/lib/centreon/centcore.cmd" + cache_dir: "/var/cache/centreon/" + cache_dir_trap: "/etc/snmp/centreon_traps/" + remote_dir: "/var/lib/centreon/remote-data/" - - name: broker - package: gorgone::modules::centreon::broker::hooks - enable: false - cache_dir: "/var/lib/centreon/broker-stats/" - cron: - - id: broker_stats - timespec: "*/2 * * * *" - action: BROKERSTATS - parameters: - timeout: 10 + - name: engine + package: "gorgone::modules::centreon::engine::hooks" + enable: true + command_file: "/var/lib/centreon-engine/rw/centengine.cmd" - - name: legacycmd - package: gorgone::modules::centreon::legacycmd::hooks - enable: true - cmd_file: "/var/lib/centreon/centcore.cmd" - cache_dir: "/var/cache/centreon/" - cache_dir_trap: "/etc/snmp/centreon_traps/" - remote_dir: "/var/lib/centreon/remote-data/" + - name: pollers + package: gorgone::modules::centreon::pollers::hooks + enable: true + + - name: broker + package: "gorgone::modules::centreon::broker::hooks" + enable: true + cache_dir: "/var/cache/centreon//broker-stats/" + cron: + - id: broker_stats + timespec: "*/2 * * * *" + action: BROKERSTATS + parameters: + timeout: 10 ``` diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 86efcaaf689..b38b58e70f7 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -321,8 +321,8 @@ sub load_module { my ($loaded, $namespace, $name, $events) = $self->{modules_register}->{$package}->{register}->( config => $options{config_module}, config_core => $self->{config}->{configuration}->{gorgone}->{gorgonecore}, - config_db_centreon => $self->{config}->{configuration}->{database}->{db_configuration}, - config_db_centstorage => $self->{config}->{configuration}->{database}->{db_realtime}, + config_db_centreon => $self->{config}->{configuration}->{centreon}->{database}->{db_configuration}, + config_db_centstorage => $self->{config}->{configuration}->{centreon}->{database}->{db_realtime}, logger => $self->{logger}, ); if ($loaded == 0) { diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index f85393ddd89..ebdee00c867 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -35,6 +35,13 @@ AutoReqProv: no %description +%package centreon-config +Summary: Configure Centreon Gorgone for use with Centreon Web +Group: Networking/Other +Requires: centreon-common + +%description centreon-config + %prep %setup -q -n %{name}-%{version} @@ -46,10 +53,10 @@ mkdir -p %{buildroot}/%{perl_vendorlib}/gorgone %{__install} -d -m 0755 %{buildroot}%{_bindir} %{__install} -d -m 0755 %{buildroot}%{_usr}/local/bin/ %{__install} -d -m 0755 %{buildroot}%{_sysconfdir}/systemd/system/ -%{__install} -d -m 0775 %buildroot%{_localstatedir}/log/centreon -%{__cp} config/systemd/centreon-gorgone-service %{buildroot}%{_sysconfdir}/systemd/system/centreon-gorgone.service +%{__install} -d -m 0775 %buildroot%{_localstatedir}/log/centreon-gorgone +%{__cp} config/systemd/gorgoned-service %{buildroot}%{_sysconfdir}/systemd/system/gorgoned.service %{__install} -d -m 0755 %{buildroot}%{_sysconfdir}/sysconfig -%{__cp} config/systemd/centreon-gorgone-sysconfig %{buildroot}%{_sysconfdir}/sysconfig/centreon-gorgone +%{__cp} config/systemd/gorgoned-sysconfig %{buildroot}%{_sysconfdir}/sysconfig/gorgoned %{__install} -d -m 0755 %{buildroot}%{_sysconfdir}/logrotate.d %{__cp} config/logrotate/gorgoned %{buildroot}%{_sysconfdir}/logrotate.d/gorgoned @@ -57,6 +64,10 @@ mkdir -p %{buildroot}/%{perl_vendorlib}/gorgone %{__cp} gorgoned %{buildroot}%{_bindir}/ %{__cp} contrib/gorgone_config_init.pl %{buildroot}%{_usr}/local/bin/ +%{__install} -d -m 0755 %{buildroot}%{_sysconfdir}/centreon-gorgone/config.d/ +%{__cp} packaging/config.yaml %{buildroot}%{_sysconfdir}/centreon-gorgone/ +%{__cp} packaging/centreon.yaml %{buildroot}%{_sysconfdir}/centreon-gorgone/config.d/30-centreon.yaml + %{_fixperms} $RPM_BUILD_ROOT/* %check @@ -65,19 +76,36 @@ mkdir -p %{buildroot}/%{perl_vendorlib}/gorgone rm -rf %{buildroot} %files -%defattr(-,root,root,-) +%defattr(-, root, root, -) %{perl_vendorlib}/gorgone/ -%attr(755,root,root) %{_bindir}/gorgoned -%attr(755,root,root) %{_usr}/local/bin/gorgone_config_init.pl -%attr(755,root,root) %{_sysconfdir}/systemd/system/centreon-gorgone.service -%config(noreplace) %{_sysconfdir}/sysconfig/centreon-gorgone +%attr(755, root, root) %{_bindir}/gorgoned +%attr(755, root, root) %{_usr}/local/bin/gorgone_config_init.pl +%attr(755, root, root) %{_sysconfdir}/systemd/system/gorgoned.service +%config(noreplace) %{_sysconfdir}/sysconfig/gorgoned %config(noreplace) %{_sysconfdir}/logrotate.d/gorgoned +%dir %{_sysconfdir}/centreon-gorgone +%{_sysconfdir}/centreon-gorgone/config.yaml + +%defattr(-, centreon-gorgone, centreon-gorgone, -) +%dir %{_sysconfdir}/centreon-gorgone/config.d +%{_localstatedir}/log/centreon-gorgone + +%pre +%{_bindir}/getent group centreon-gorgone &>/dev/null || %{_sbindir}/groupadd -r centreon-gorgone +%{_bindir}/getent passwd centreon-gorgone &>/dev/null || %{_sbindir}/useradd -g centreon-gorgone -m -d %{_localstatedir}/log/centreon-gorgone -r centreon-gorgone 2> /dev/null + +%files centreon-config +%{_sysconfdir}/centreon-gorgone/config.d/30-centreon.yaml + +%post centreon-config +%{_bindir}/getent passwd centreon &>/dev/null && %{_sbindir}/usermod -a G centreon-gorgone centreon 2> /dev/null +%{_bindir}/getent passwd centreon-gorgone &>/dev/null && %{_sbindir}/usermod -a G centreon centreon-gorgone 2> /dev/null %post -%systemd_post centreon-gorgone.service || : +%systemd_post gorgoned.service || : %preun -%systemd_preun centreon-gorgone.service || : +%systemd_preun gorgoned.service || : %postun %systemd_postun_with_restart centreon-gorgone.service || : diff --git a/gorgone/packaging/centreon.yaml b/gorgone/packaging/centreon.yaml new file mode 100644 index 00000000000..a66311890a3 --- /dev/null +++ b/gorgone/packaging/centreon.yaml @@ -0,0 +1,3 @@ +name: centreon.yaml +description: Configure Centreon Gorgone to work with Centreon Web. +centreon: !include /etc/centreon/config.d/*.yaml diff --git a/gorgone/packaging/config.yaml b/gorgone/packaging/config.yaml new file mode 100644 index 00000000000..18486132a1d --- /dev/null +++ b/gorgone/packaging/config.yaml @@ -0,0 +1,3 @@ +name: config.yaml +description: Configuration brought by Centreon Gorgone +configuration: !include /etc/centreon-gorgone/config.d/*.yaml From 62462f3600b644d6379e7ac860c2cf687538e61e Mon Sep 17 00:00:00 2001 From: Matthieu Kermagoret Date: Wed, 29 Jan 2020 14:08:09 +0100 Subject: [PATCH 315/948] fix(packaging): use renamed gorgoned service in %postun step. --- gorgone/packaging/centreon-gorgone.spectemplate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index ebdee00c867..833fe5409ea 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -108,6 +108,6 @@ rm -rf %{buildroot} %systemd_preun gorgoned.service || : %postun -%systemd_postun_with_restart centreon-gorgone.service || : +%systemd_postun_with_restart gorgoned.service || : %changelog From 69176b4a5a6af0c9efb47426c2f8ba2313711a9a Mon Sep 17 00:00:00 2001 From: Matthieu Kermagoret Date: Wed, 29 Jan 2020 17:08:11 +0100 Subject: [PATCH 316/948] fix(packaging): set home directory to /var/lib/centreon-gorgone. --- gorgone/packaging/centreon-gorgone.spectemplate | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 833fe5409ea..d0ba925161b 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -53,6 +53,7 @@ mkdir -p %{buildroot}/%{perl_vendorlib}/gorgone %{__install} -d -m 0755 %{buildroot}%{_bindir} %{__install} -d -m 0755 %{buildroot}%{_usr}/local/bin/ %{__install} -d -m 0755 %{buildroot}%{_sysconfdir}/systemd/system/ +%{__install} -d -m 0775 %buildroot%{_localstatedir}/lib/centreon-gorgone %{__install} -d -m 0775 %buildroot%{_localstatedir}/log/centreon-gorgone %{__cp} config/systemd/gorgoned-service %{buildroot}%{_sysconfdir}/systemd/system/gorgoned.service %{__install} -d -m 0755 %{buildroot}%{_sysconfdir}/sysconfig @@ -88,11 +89,12 @@ rm -rf %{buildroot} %defattr(-, centreon-gorgone, centreon-gorgone, -) %dir %{_sysconfdir}/centreon-gorgone/config.d +%{_localstatedir}/lib/centreon-gorgone %{_localstatedir}/log/centreon-gorgone %pre %{_bindir}/getent group centreon-gorgone &>/dev/null || %{_sbindir}/groupadd -r centreon-gorgone -%{_bindir}/getent passwd centreon-gorgone &>/dev/null || %{_sbindir}/useradd -g centreon-gorgone -m -d %{_localstatedir}/log/centreon-gorgone -r centreon-gorgone 2> /dev/null +%{_bindir}/getent passwd centreon-gorgone &>/dev/null || %{_sbindir}/useradd -g centreon-gorgone -m -d %{_localstatedir}/lib/centreon-gorgone -r centreon-gorgone 2> /dev/null %files centreon-config %{_sysconfdir}/centreon-gorgone/config.d/30-centreon.yaml From 024919dc0be12187f47e966e7fb35c2aa5fcabd1 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 29 Jan 2020 18:41:46 +0100 Subject: [PATCH 317/948] fix(script): fix config include loop --- gorgone/gorgone/class/script.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/gorgone/class/script.pm b/gorgone/gorgone/class/script.pm index fa2ea12a317..8443b74dd52 100644 --- a/gorgone/gorgone/class/script.pm +++ b/gorgone/gorgone/class/script.pm @@ -199,7 +199,7 @@ sub yaml_parse_config { ${$options{config}} = undef; foreach (@files) { next if (! -r $_); - my $config = yaml_load_config(file => $_, filter => $options{filter}, $options{ariane}); + my $config = $self->yaml_load_config(file => $_, filter => $options{filter}, ariane => $options{ariane}); next if (!defined($config)); if (ref($config) eq 'ARRAY') { ${$options{config}} = [] if (ref(${$options{config}}) ne 'ARRAY'); From d0d66bfa36c5d8a7730fbc99a6549ede32f21089 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 29 Jan 2020 18:42:24 +0100 Subject: [PATCH 318/948] fix(core): fix default path --- gorgone/gorgone/class/core.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index b38b58e70f7..bfb777aed24 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -143,7 +143,7 @@ sub init { } $self->{config} = $self->yaml_load_config( file => $self->{config_file}, - filter => '!($ariane eq "configuration##" || $ariane =~ /^configuration##(?:gorgone|database)##/)' + filter => '!($ariane eq "configuration##" || $ariane =~ /^configuration##(?:gorgone|centreon)##/)' ); $self->init_server_keys(); @@ -173,7 +173,7 @@ sub init { $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_type} = defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_type}) && $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_type} ne '' ? $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_type} : 'SQLite'; $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_name} = - defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_name}) && $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_name} ne '' ? $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_name} : 'dbname=/var/lib/centreon/gorgone/gorgone.sdb'; + defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_name}) && $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_name} ne '' ? $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_name} : 'dbname=/var/lib/centreon-gorgone/history.sdb'; $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_autocreate_schema} = defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_autocreate_schema}) && $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_autocreate_schema} =~ /(\d+)/ ? $1 : 1; gorgone::standard::library::init_database( From 27c421dc06d11d1f92b2e6d005e122e73107a15a Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Wed, 29 Jan 2020 18:42:47 +0100 Subject: [PATCH 319/948] fix(doc/config): fix default pathes --- gorgone/config/gorgoned-central-ssh.yml | 19 ++++++++++--------- gorgone/config/gorgoned-central-zmq.yml | 19 ++++++++++--------- gorgone/config/gorgoned-remote-ssh.yml | 19 ++++++++++--------- gorgone/config/gorgoned-remote-zmq.yml | 19 ++++++++++--------- gorgone/contrib/gorgone_config_init.pl | 12 ++++-------- gorgone/docs/configuration.md | 4 ++-- gorgone/docs/getting_started.md | 18 +++++++++--------- gorgone/docs/migration.md | 18 +++++++++--------- 8 files changed, 64 insertions(+), 64 deletions(-) diff --git a/gorgone/config/gorgoned-central-ssh.yml b/gorgone/config/gorgoned-central-ssh.yml index 0e414a9dc7e..9e60dce86d9 100644 --- a/gorgone/config/gorgoned-central-ssh.yml +++ b/gorgone/config/gorgoned-central-ssh.yml @@ -1,15 +1,16 @@ name: gorgoned-central-ssh description: Configuration example in a SSH environment for Central server configuration: - database: - db_configuration: - dsn: "mysql:host=localhost;dbname=centreon" - username: centreon - password: centreon - db_realtime: - dsn: "mysql:host=localhost;dbname=centreon_storage" - username: centreon - password: centreon + centreon: + database: + db_configuration: + dsn: "mysql:host=localhost;dbname=centreon" + username: centreon + password: centreon + db_realtime: + dsn: "mysql:host=localhost;dbname=centreon_storage" + username: centreon + password: centreon gorgone: gorgonecore: timeout: 50 diff --git a/gorgone/config/gorgoned-central-zmq.yml b/gorgone/config/gorgoned-central-zmq.yml index 217072398f8..732e2d0b97b 100644 --- a/gorgone/config/gorgoned-central-zmq.yml +++ b/gorgone/config/gorgoned-central-zmq.yml @@ -1,15 +1,16 @@ name: gorgoned-central-zmq description: Configuration example in a full ZMQ environment for Central server configuration: - database: - db_configuration: - dsn: "mysql:host=localhost;dbname=centreon" - username: centreon - password: centreon - db_realtime: - dsn: "mysql:host=localhost;dbname=centreon_storage" - username: centreon - password: centreon + centreon: + database: + db_configuration: + dsn: "mysql:host=localhost;dbname=centreon" + username: centreon + password: centreon + db_realtime: + dsn: "mysql:host=localhost;dbname=centreon_storage" + username: centreon + password: centreon gorgone: gorgonecore: id: 1 diff --git a/gorgone/config/gorgoned-remote-ssh.yml b/gorgone/config/gorgoned-remote-ssh.yml index 592132d79c3..7b35c7777b6 100644 --- a/gorgone/config/gorgoned-remote-ssh.yml +++ b/gorgone/config/gorgoned-remote-ssh.yml @@ -1,15 +1,16 @@ name: gorgoned-remote-ssh description: Configuration example in a SSH environment for Remote server configuration: - database: - db_configuration: - dsn: "mysql:host=localhost;dbname=centreon" - username: centreon - password: centreon - db_realtime: - dsn: "mysql:host=localhost;dbname=centreon_storage" - username: centreon - password: centreon + centreon: + database: + db_configuration: + dsn: "mysql:host=localhost;dbname=centreon" + username: centreon + password: centreon + db_realtime: + dsn: "mysql:host=localhost;dbname=centreon_storage" + username: centreon + password: centreon gorgone: gorgonecore: timeout: 50 diff --git a/gorgone/config/gorgoned-remote-zmq.yml b/gorgone/config/gorgoned-remote-zmq.yml index 0c831737571..c6b4e214a6d 100644 --- a/gorgone/config/gorgoned-remote-zmq.yml +++ b/gorgone/config/gorgoned-remote-zmq.yml @@ -1,15 +1,16 @@ name: gorgoned-remote-zmq description: Configuration example in a full ZMQ environment for Remote server configuration: - database: - db_configuration: - dsn: "mysql:host=localhost;dbname=centreon" - username: centreon - password: centreon - db_realtime: - dsn: "mysql:host=localhost;dbname=centreon_storage" - username: centreon - password: centreon + centreon: + database: + db_configuration: + dsn: "mysql:host=localhost;dbname=centreon" + username: centreon + password: centreon + db_realtime: + dsn: "mysql:host=localhost;dbname=centreon_storage" + username: centreon + password: centreon gorgone: gorgonecore: id: 4 diff --git a/gorgone/contrib/gorgone_config_init.pl b/gorgone/contrib/gorgone_config_init.pl index 576ea8044c3..0b57e35db02 100644 --- a/gorgone/contrib/gorgone_config_init.pl +++ b/gorgone/contrib/gorgone_config_init.pl @@ -98,21 +98,17 @@ sub write_gorgone_config { gorgonecore: hostname: id: - privkey: /var/spool/centreon/.gorgone/rsakey.priv.pem - pubkey: /var/spool/centreon/.gorgone/rsakey.pub.pem + privkey: /var/lib/centreon-gorgone/.keys/rsakey.priv.pem + pubkey: /var/lib/centreon-gorgone/.keys/rsakey.pub.pem modules: - name: httpserver package: gorgone::modules::core::httpserver::hooks enable: false address: 0.0.0.0 - port: 8443 - ssl: true - ssl_cert_file: /etc/pki/tls/certs/server-cert.pem - ssl_key_file: /etc/pki/tls/server-key.pem + port: 8085 + ssl: false auth: enabled: false - user: admin - password: password allowed_hosts: enabled: true subnets: diff --git a/gorgone/docs/configuration.md b/gorgone/docs/configuration.md index 86813a5b17b..81f10656ef8 100644 --- a/gorgone/docs/configuration.md +++ b/gorgone/docs/configuration.md @@ -46,7 +46,7 @@ configuration: | external_com_path | Path to the external ZMQ socket | `*:5555` | | timeout | Time in seconds before killing child processes when stopping Gorgone | `50` | | gorgone_db_type | Type of the Gorgone database | `SQLite` | -| gorgone_db_name | Path and name of the database | `dbname=/var/lib/centreon/gorgone/gorgone.sdb` | +| gorgone_db_name | Path and name of the database | `dbname=/var/lib/centreon-gorgone/history.sdb` | | gorgone_db_host | Hostname/IP address of the server hosting the database | | | gorgone_db_port | Port of the database listener | | | gorgone_db_user | Username to access the database | | @@ -75,7 +75,7 @@ configuration: external_com_path: "*:5555" timeout: 50 gorgone_db_type: SQLite - gorgone_db_name: dbname=/var/lib/centreon/gorgone/gorgone.sdb + gorgone_db_name: dbname=/var/lib/centreon-gorgone/history.sdb gorgone_db_host: gorgone_db_port: gorgone_db_user: diff --git a/gorgone/docs/getting_started.md b/gorgone/docs/getting_started.md index bec6b949f9e..510af5eb2c6 100644 --- a/gorgone/docs/getting_started.md +++ b/gorgone/docs/getting_started.md @@ -66,7 +66,7 @@ If it does not exist, the daemon will automatically create it in the path set by However, you can manualy create it with the database schema: ```bash -sqlite3 -init schema/gorgone_database.sql /var/lib/centreon/gorgone/gorgone.sdb +sqlite3 -init schema/gorgone_database.sql /var/lib/centreon-gorgone/history.sdb ``` Database schema: @@ -122,19 +122,19 @@ CREATE INDEX IF NOT EXISTS idx_gorgone_target_fingerprint_target ON gorgone_targ If you are using the package, just launch the service as below: ```bash -systemctl start centreon-gorgone +systemctl start gorgoned ``` Make sure the daemon is running: ```bash -$ systemctl status centreon-gorgone -● centreon-gorgone.service - Centreon Gorgone - Loaded: loaded (/etc/systemd/system/centreon-gorgone.service; disabled; vendor preset: disabled) +$ systemctl status gorgoned +● gorgoned.service - Centreon Gorgone + Loaded: loaded (/etc/systemd/system/gorgoned.service; disabled; vendor preset: disabled) Active: active (running) since Mon 2019-09-30 09:36:19 CEST; 2min 29s ago Main PID: 5168 (perl) - CGroup: /system.slice/centreon-gorgone.service - ├─5168 /usr/bin/perl /usr/bin/gorgoned --config=/etc/centreon-gorgone/config.yaml --logfile=/var/log/centreon/gorgoned.log --severity=error + CGroup: /system.slice/gorgoned.service + ├─5168 /usr/bin/perl /usr/bin/gorgoned --config=/etc/centreon-gorgone/config.yaml --logfile=/var/log/centreon/gorgoned.log --severity=info ├─5175 gorgone-dbcleaner ├─5182 gorgone-action ├─5187 gorgone-nodes @@ -145,13 +145,13 @@ $ systemctl status centreon-gorgone ├─5206 gorgone-proxy └─5207 gorgone-proxy -Sep 30 09:36:19 cga-centreon-19-10.int.centreon.com systemd[1]: Started Centreon Gorgone. +Sep 30 09:36:19 localhost systemd[1]: Started Centreon Gorgone. ``` If you are using the sources, execute the following command: ```bash -perl gorgoned --config=config/config.yaml --severity=error +perl gorgoned --config=config/config.yaml --severity=info ``` ## Full-ZMQ setup diff --git a/gorgone/docs/migration.md b/gorgone/docs/migration.md index f1e8c480fac..81d3d47592a 100644 --- a/gorgone/docs/migration.md +++ b/gorgone/docs/migration.md @@ -32,23 +32,23 @@ configuration: dsn: "mysql:host=localhost;port=3306;dbname=centreon_storage" username: "centreon" password: "centreon" - gorgone: gorgonecore: - privkey: "/var/spool/centreon/.gorgone/rsakey.priv.pem" - pubkey: "/var/spool/centreon/.gorgone/rsakey.pub.pem" + privkey: "/var/lib/centreon-gorgone/.keys/rsakey.priv.pem" + pubkey: "/var/lib/centreon-gorgone/.keys/rsakey.pub.pem" modules: - name: httpserver package: gorgone::modules::core::httpserver::hooks - enable: true + enable: false address: 0.0.0.0 port: 8085 - ssl: true - ssl_cert_file: /etc/pki/tls/certs/server-cert.pem - ssl_key_file: /etc/pki/tls/server-key.pem + ssl: false auth: - user: admin - password: password + enabled: false + allowed_hosts: + enabled: true + subnets: + - 127.0.0.1/32 - name: action package: gorgone::modules::core::action::hooks From 8d0785664014c0cbff0e927f2e6868a3c745b299 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 3 Feb 2020 15:15:34 +0100 Subject: [PATCH 320/948] enh(copy): add owner/group attributes --- gorgone/gorgone/modules/centreon/legacycmd/class.pm | 8 ++++++++ gorgone/gorgone/modules/core/action/class.pm | 6 ++++++ gorgone/gorgone/modules/core/proxy/hooks.pm | 7 ++++++- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index eaf16645b58..edc88f170bf 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -198,6 +198,8 @@ sub execute_cmd { source => $cache_dir . '/config/engine/' . $options{target}, destination => $self->{pollers}->{$options{target}}->{cfg_dir} . '/', cache_dir => $cache_dir, + owner => 'centreon-engine', + group => 'centreon-engine', metadata => { centcore_cmd => 'SENDCFGFILE', } @@ -214,6 +216,8 @@ sub execute_cmd { source => $cache_dir . '/config/broker/' . $options{target}, destination => $self->{pollers}->{$options{target}}->{centreonbroker_cfg_path} . '/', cache_dir => $cache_dir, + owner => 'centreon-broker', + group => 'centreon-broker', metadata => { centcore_proxy => 1, centcore_cmd => 'SENDCFGFILE', @@ -236,6 +240,8 @@ sub execute_cmd { source => $cache_dir . '/config/export/' . $options{target}, destination => $remote_dir, cache_dir => $cache_dir, + owner => 'centreon', + group => 'centreon', metadata => { centcore_cmd => 'SENDEXPORTFILE', } @@ -278,6 +284,8 @@ sub execute_cmd { source => $cache_dir_trap . '/' . $options{target} . '/centreontrapd.sdb', destination => $self->{pollers}->{$options{target}}->{snmp_trapd_path_conf} . '/', cache_dir => $cache_dir, + owner => 'centreon', + group => 'centreon', metadata => { centcore_proxy => 1, centcore_cmd => 'SYNCTRAP', diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index 957dca05c30..cb380ccdb97 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -272,6 +272,12 @@ sub action_processcopy { } } elsif ($options{data}->{content}->{type} eq "regular") { copy($cache_file, $options{data}->{content}->{destination}); + my $uid = getpwnam($options{data}->{content}->{owner}); + my $gid = getgrnam($options{data}->{content}->{group}); + use Data::Dumper; + print Dumper $uid; + print Dumper $gid; + chown($uid, $gid, $options{data}->{content}->{destination}); } } else { $self->send_log( diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index b192554b3cc..a58a656bf63 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -809,8 +809,11 @@ sub prepare_remote_copy { $filename = (defined($options{data}->{content}->{type}) ? $options{data}->{content}->{type} : 'tmp') . '-' . $options{target} . '.tar.gz'; $localsrc = $options{data}->{content}->{cache_dir} . '/' . $filename; + my $tar_cmd = "tar -czf $localsrc -C '" . $src . "' ."; + $tar_cmd = " --owner=" . $options{data}->{content}->{owner} if (defined($options{data}->{content}->{owner}) && $options{data}->{content}->{owner} ne ''); + $tar_cmd = " --group=" . $options{data}->{content}->{group} if (defined($options{data}->{content}->{group}) && $options{data}->{content}->{group} ne ''); my ($error, $stdout, $exit_code) = gorgone::standard::misc::backtick( - command => "tar -czf $localsrc -C '" . $src . "' .", + command => $tar_cmd, timeout => (defined($options{timeout})) ? $options{timeout} : 10, wait_exit => 1, redirect_stderr => 1, @@ -877,6 +880,8 @@ sub prepare_remote_copy { md5 => file_md5_hex($localsrc), destination => $dst, cache_dir => $options{data}->{content}->{cache_dir}, + owner => $options{data}->{content}->{owner}, + group => $options{data}->{content}->{group}, }, parameters => { no_fork => 1 } }; From cfaf2cfb0ee4982a8b00d8eb0d32ccb2cb0e7019 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 3 Feb 2020 15:16:25 +0100 Subject: [PATCH 321/948] fix(action): remove dumper --- gorgone/gorgone/modules/core/action/class.pm | 3 --- 1 file changed, 3 deletions(-) diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index cb380ccdb97..491f7e4be76 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -274,9 +274,6 @@ sub action_processcopy { copy($cache_file, $options{data}->{content}->{destination}); my $uid = getpwnam($options{data}->{content}->{owner}); my $gid = getgrnam($options{data}->{content}->{group}); - use Data::Dumper; - print Dumper $uid; - print Dumper $gid; chown($uid, $gid, $options{data}->{content}->{destination}); } } else { From 0fbe9c2f2f419fd04f8b9e6a771cec367435b6cb Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 3 Feb 2020 15:24:15 +0100 Subject: [PATCH 322/948] enh(packaging): add groups membership and fix file/dir ownership --- gorgone/packaging/centreon-gorgone.spectemplate | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index d0ba925161b..2405f8ba712 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -84,11 +84,11 @@ rm -rf %{buildroot} %attr(755, root, root) %{_sysconfdir}/systemd/system/gorgoned.service %config(noreplace) %{_sysconfdir}/sysconfig/gorgoned %config(noreplace) %{_sysconfdir}/logrotate.d/gorgoned -%dir %{_sysconfdir}/centreon-gorgone -%{_sysconfdir}/centreon-gorgone/config.yaml %defattr(-, centreon-gorgone, centreon-gorgone, -) +%dir %{_sysconfdir}/centreon-gorgone %dir %{_sysconfdir}/centreon-gorgone/config.d +%{_sysconfdir}/centreon-gorgone/config.yaml %{_localstatedir}/lib/centreon-gorgone %{_localstatedir}/log/centreon-gorgone @@ -100,8 +100,11 @@ rm -rf %{buildroot} %{_sysconfdir}/centreon-gorgone/config.d/30-centreon.yaml %post centreon-config -%{_bindir}/getent passwd centreon &>/dev/null && %{_sbindir}/usermod -a G centreon-gorgone centreon 2> /dev/null -%{_bindir}/getent passwd centreon-gorgone &>/dev/null && %{_sbindir}/usermod -a G centreon centreon-gorgone 2> /dev/null +%{_bindir}/getent passwd centreon &>/dev/null && %{_sbindir}/usermod -a -G centreon-gorgone centreon 2> /dev/null +%{_bindir}/getent passwd centreon-gorgone &>/dev/null && %{_sbindir}/usermod -a -G centreon centreon-gorgone 2> /dev/null +%{_bindir}/getent passwd centreon-gorgone &>/dev/null && %{_sbindir}/usermod -a -G centreon-engine centreon-gorgone 2> /dev/null +%{_bindir}/getent passwd centreon-gorgone &>/dev/null && %{_sbindir}/usermod -a -G centreon-broker centreon-gorgone 2> /dev/null +%{_bindir}/getent passwd centreon-gorgone &>/dev/null && %{_sbindir}/usermod -a -G apache centreon-gorgone 2> /dev/null %post %systemd_post gorgoned.service || : From b0cc5db14dda17e3d98aca817961acb9cac4aa1e Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 3 Feb 2020 15:46:48 +0100 Subject: [PATCH 323/948] fix(action): fix tar concat --- gorgone/gorgone/modules/core/proxy/hooks.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index a58a656bf63..664abd3e016 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -810,8 +810,8 @@ sub prepare_remote_copy { $localsrc = $options{data}->{content}->{cache_dir} . '/' . $filename; my $tar_cmd = "tar -czf $localsrc -C '" . $src . "' ."; - $tar_cmd = " --owner=" . $options{data}->{content}->{owner} if (defined($options{data}->{content}->{owner}) && $options{data}->{content}->{owner} ne ''); - $tar_cmd = " --group=" . $options{data}->{content}->{group} if (defined($options{data}->{content}->{group}) && $options{data}->{content}->{group} ne ''); + $tar_cmd .= " --owner=" . $options{data}->{content}->{owner} if (defined($options{data}->{content}->{owner}) && $options{data}->{content}->{owner} ne ''); + $tar_cmd .= " --group=" . $options{data}->{content}->{group} if (defined($options{data}->{content}->{group}) && $options{data}->{content}->{group} ne ''); my ($error, $stdout, $exit_code) = gorgone::standard::misc::backtick( command => $tar_cmd, timeout => (defined($options{timeout})) ? $options{timeout} : 10, From 2f28cb5f93d703d4b959e5654c96cfdf71c07f99 Mon Sep 17 00:00:00 2001 From: Matthieu Kermagoret Date: Tue, 4 Feb 2020 14:46:05 +0100 Subject: [PATCH 324/948] fix(packaging): centreon-gorgone-centreon-config requires centreon-gorgone. --- gorgone/packaging/centreon-gorgone.spectemplate | 1 + 1 file changed, 1 insertion(+) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 2405f8ba712..463ff5a9d7a 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -39,6 +39,7 @@ AutoReqProv: no Summary: Configure Centreon Gorgone for use with Centreon Web Group: Networking/Other Requires: centreon-common +Requires: centreon-gorgone %description centreon-config From 8500cb562a8f6aab51269daa554e2925e85c373a Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 4 Feb 2020 17:23:51 +0100 Subject: [PATCH 325/948] enh(legacycmd): change default path for remote server export --- gorgone/config/gorgoned-central-ssh.yml | 2 +- gorgone/config/gorgoned-central-zmq.yml | 2 +- gorgone/config/gorgoned-remote-ssh.yml | 2 +- gorgone/config/gorgoned-remote-zmq.yml | 2 +- gorgone/contrib/gorgone_config_init.pl | 2 +- gorgone/docs/modules/centreon/legacycmd.md | 4 ++-- gorgone/gorgone/modules/centreon/legacycmd/class.pm | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/gorgone/config/gorgoned-central-ssh.yml b/gorgone/config/gorgoned-central-ssh.yml index 9e60dce86d9..b9b87a3e97c 100644 --- a/gorgone/config/gorgoned-central-ssh.yml +++ b/gorgone/config/gorgoned-central-ssh.yml @@ -46,7 +46,7 @@ configuration: cmd_file: "/var/lib/centreon/centcore.cmd" cache_dir: "/var/cache/centreon/" cache_dir_trap: "/etc/snmp/centreon_traps/" - remote_dir: "/var/lib/centreon/remote-data/" + remote_dir: "/var/cache/centreon/config/remote-data/" - name: engine package: gorgone::modules::centreon::engine::hooks diff --git a/gorgone/config/gorgoned-central-zmq.yml b/gorgone/config/gorgoned-central-zmq.yml index 732e2d0b97b..9bd9dc4cecb 100644 --- a/gorgone/config/gorgoned-central-zmq.yml +++ b/gorgone/config/gorgoned-central-zmq.yml @@ -71,7 +71,7 @@ configuration: cmd_file: "/var/lib/centreon/centcore.cmd" cache_dir: "/var/cache/centreon/" cache_dir_trap: "/etc/snmp/centreon_traps/" - remote_dir: "/var/lib/centreon/remote-data/" + remote_dir: "/var/cache/centreon/config/remote-data/" - name: engine package: gorgone::modules::centreon::engine::hooks diff --git a/gorgone/config/gorgoned-remote-ssh.yml b/gorgone/config/gorgoned-remote-ssh.yml index 7b35c7777b6..bbdf5e7f2ad 100644 --- a/gorgone/config/gorgoned-remote-ssh.yml +++ b/gorgone/config/gorgoned-remote-ssh.yml @@ -33,7 +33,7 @@ configuration: cmd_file: "/var/lib/centreon/centcore.cmd" cache_dir: "/var/cache/centreon/" cache_dir_trap: "/etc/snmp/centreon_traps/" - remote_dir: "/var/lib/centreon/remote-data/" + remote_dir: "/var/cache/centreon/config/remote-data/" - name: engine package: gorgone::modules::centreon::engine::hooks diff --git a/gorgone/config/gorgoned-remote-zmq.yml b/gorgone/config/gorgoned-remote-zmq.yml index c6b4e214a6d..6b5a96e9f18 100644 --- a/gorgone/config/gorgoned-remote-zmq.yml +++ b/gorgone/config/gorgoned-remote-zmq.yml @@ -39,7 +39,7 @@ configuration: cmd_file: "/var/lib/centreon/centcore.cmd" cache_dir: "/var/cache/centreon/" cache_dir_trap: "/etc/snmp/centreon_traps/" - remote_dir: "/var/lib/centreon/remote-data/" + remote_dir: "/var/cache/centreon/config/remote-data/" - name: engine package: gorgone::modules::centreon::engine::hooks diff --git a/gorgone/contrib/gorgone_config_init.pl b/gorgone/contrib/gorgone_config_init.pl index 0b57e35db02..30a957d76f9 100644 --- a/gorgone/contrib/gorgone_config_init.pl +++ b/gorgone/contrib/gorgone_config_init.pl @@ -147,7 +147,7 @@ sub write_gorgone_config { cmd_file: "$centreon_config->{VarLib}/centcore.cmd" cache_dir: "$centreon_config->{CacheDir}" cache_dir_trap: "/etc/snmp/centreon_traps/" - remote_dir: "$centreon_config->{VarLib}/remote-data/" + remote_dir: "$centreon_config->{CacheDir}/config/remote-data/" END_FILE print $fh $content; diff --git a/gorgone/docs/modules/centreon/legacycmd.md b/gorgone/docs/modules/centreon/legacycmd.md index 89adb43e782..783c0f57045 100644 --- a/gorgone/docs/modules/centreon/legacycmd.md +++ b/gorgone/docs/modules/centreon/legacycmd.md @@ -20,7 +20,7 @@ The module relies on the following modules to process commands: | cmd_dir | Directory where to watch for *command files* | `/var/lib/centreon/` | | cache_dir | Directory where to process Centreon configuration files | `/var/cache/centreon/` | | cache_dir_trap | Directory where to process Centreontrapd databases | `/etc/snmp/centreon_traps/` | -| remote_dir | Directory where to export Remote Servers configuration | `/var/lib/centreon/remote-data/` | +| remote_dir | Directory where to export Remote Servers configuration | `/var/cache/centreon/config/remote-data/` | #### Example @@ -32,7 +32,7 @@ cmd_file: "/var/lib/centreon/centcore.cmd" cmd_dir: "/var/lib/centreon/" cache_dir: "/var/cache/centreon/" cache_dir_trap: "/etc/snmp/centreon_traps/" -remote_dir: "/var/lib/centreon/remote-data/" +remote_dir: "/var/cache/centreon/config/remote-data/" ``` ## Events diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index edc88f170bf..f1cddc8ec02 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -229,7 +229,7 @@ sub execute_cmd { my $cache_dir = (defined($connector->{config}->{cache_dir})) ? $connector->{config}->{cache_dir} : '/var/cache/centreon'; my $remote_dir = (defined($connector->{config}->{remote_dir})) ? - $connector->{config}->{remote_dir} : '/var/lib/centreon/remote-data/'; + $connector->{config}->{remote_dir} : '/var/cache/centreon/config/remote-data/'; # remote server $self->send_internal_action( action => 'REMOTECOPY', From 70252d700cb78a2dd65a83723e38db0799c72869 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Thu, 6 Feb 2020 15:35:49 +0100 Subject: [PATCH 326/948] fix(library): logger missing when synclog called --- gorgone/gorgone/standard/library.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index 97ebdd0d697..108408ecded 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -443,7 +443,7 @@ sub synclogs { my $name = $options{gorgone}->{modules_id}->{$options{gorgone_config}->{gorgonecore}->{proxy_name}}; my $method; if (defined($name) && ($method = $name->can('synclog'))) { - $method->(dbh => $options{gorgone}->{db_gorgone}); + $method->(dbh => $options{gorgone}->{db_gorgone}, logger => $options{gorgone}->{logger}); return (0, { action => 'synclog', message => 'synclog launched' }); } } From 107fde35c6abe17ef77613addd2826a9b485c131 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 7 Feb 2020 13:06:22 +0100 Subject: [PATCH 327/948] doc(api): style --- gorgone/docs/api.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/gorgone/docs/api.md b/gorgone/docs/api.md index db4e5686ede..be7bf94a4a7 100644 --- a/gorgone/docs/api.md +++ b/gorgone/docs/api.md @@ -326,8 +326,8 @@ This second example will force logs synchonisation before looking for results to ```json { - "error":"http_error_401", - "message":"unauthorized" + "error": "http_error_401", + "message": "unauthorized" } ``` @@ -335,8 +335,8 @@ This second example will force logs synchonisation before looking for results to ```json { - "error":"http_error_403", - "message":"forbidden" + "error": "http_error_403", + "message": "forbidden" } ``` @@ -344,8 +344,8 @@ This second example will force logs synchonisation before looking for results to ```json { - "error":"endpoint_unknown", - "message":"endpoint not implemented" + "error": "endpoint_unknown", + "message": "endpoint not implemented" } ``` @@ -373,8 +373,8 @@ This second example will force logs synchonisation before looking for results to ```json { - "error":"decode_error", - "message":"Cannot decode response" + "error": "decode_error", + "message": "Cannot decode response" } ``` @@ -382,8 +382,8 @@ This second example will force logs synchonisation before looking for results to ```json { - "error":"encode_error", - "message":"Cannot encode response" + "error": "encode_error", + "message": "Cannot encode response" } ``` @@ -391,8 +391,8 @@ This second example will force logs synchonisation before looking for results to ```json { - "error":"no_result", - "message":"No result found for action " + "error": "no_result", + "message": "No result found for action " } ``` @@ -400,7 +400,7 @@ This second example will force logs synchonisation before looking for results to ```json { - "error":"no_token", - "message":"Cannot retrieve token from ack" + "error": "no_token", + "message": "Cannot retrieve token from ack" } ``` From b227232d1a1f9ce1420526cbdea685d5bf4065bf Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 7 Feb 2020 13:07:19 +0100 Subject: [PATCH 328/948] enh(httpserver): handle not wanted client --- gorgone/gorgone/modules/core/httpserver/class.pm | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/gorgone/gorgone/modules/core/httpserver/class.pm b/gorgone/gorgone/modules/core/httpserver/class.pm index 74aeda07215..1719ad592f5 100644 --- a/gorgone/gorgone/modules/core/httpserver/class.pm +++ b/gorgone/gorgone/modules/core/httpserver/class.pm @@ -218,8 +218,15 @@ sub run { next if (!defined($connection)); while (my $request = $connection->get_request) { - $connector->{logger}->writeLogInfo("[httpserver] " . $connection->peerhost() . " " . $request->method . " '" . $request->uri->path . "' '" . $request->header("User-Agent") . "'"); + if ($connection->antique_client eq '1') { + $connection->force_last_request; + next; + } + my $msg = "[httpserver] " . $connection->peerhost() . " " . $request->method . " '" . $request->uri->path . "'"; + $msg .= " '" . $request->header("User-Agent") . "'" if (defined($request->header("User-Agent")) && $request->header("User-Agent") ne ''); + $connector->{logger}->writeLogInfo($msg); + if ($connector->{allowed_hosts_enabled} == 1) { if ($connector->check_allowed_host(peer_addr => inet_ntoa($connection->peeraddr())) == 0) { $connector->{logger}->writeLogError("[httpserver] " . $connection->peerhost() . " Unauthorized"); From e519be51020461cbedbd4a523193c36e67cc5384 Mon Sep 17 00:00:00 2001 From: schapron Date: Thu, 13 Feb 2020 09:28:25 +0100 Subject: [PATCH 329/948] sec(doc): add SECURITY.md --- gorgone/SECURITY.md | 96 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 gorgone/SECURITY.md diff --git a/gorgone/SECURITY.md b/gorgone/SECURITY.md new file mode 100644 index 00000000000..1aed97a9295 --- /dev/null +++ b/gorgone/SECURITY.md @@ -0,0 +1,96 @@ +# Security Policy + +Centreon takes the security of our software products seriously. + +If you believe you have found a security vulnerability, please report it to us as described below. + +## Reporting a Vulnerability + +**Please do not report security vulnerabilities through public GitHub issues.** + +Send an email to security@centreon.com. If possible, encrypt your message with our PGP key below. + +You should receive a response within 48 hours. If for some reason you do not, please follow up via email to ensure we received your original message. + +To help us better understand the nature and scope of the possible issue, please describe as much as you can: + +* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) +* Full paths of source file(s) related to the manifestation of the issue +* The location of the affected source code (tag/branch/commit or direct URL) +* Any special configuration required to reproduce the issue +* Step-by-step instructions to reproduce the issue +* Proof-of-concept or exploit code (if possible) +* Impact of the issue, including how an attacker might exploit the issue + +## PGP information + +### Public key + +``` +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +oWAyTvK/VwbB77CTz1yTUSE= +=3kAj +-----END PGP PUBLIC KEY BLOCK----- +``` + +| Tag | Value | +| -- | -- | +| ID | BEAF6EBF631106F9 | +| Type | RSA | +| Size | 4096 | +| Created | 2020-02-11 | +| Expires | 2021-02-10 | +| Cipher |AES-256| +| Fingerprint | C377 E9D5 2D5C 137D 3DD5 73B5 BEA F6EBF 6311 06F9 | + +## Bug bounty + +We don't have a bug bounty program but this is something we are thinking about. From 8b228b26eda4bf0d2a8167b71598b71cb99e925d Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Thu, 13 Feb 2020 15:01:02 +0100 Subject: [PATCH 330/948] enh(modules): skel of anomalydetection module --- .../centreon/anomalydetection/class.pm | 183 ++++++++++++++++++ .../centreon/anomalydetection/hooks.pm | 172 ++++++++++++++++ 2 files changed, 355 insertions(+) create mode 100644 gorgone/gorgone/modules/centreon/anomalydetection/class.pm create mode 100644 gorgone/gorgone/modules/centreon/anomalydetection/hooks.pm diff --git a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm new file mode 100644 index 00000000000..dcf426623b0 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm @@ -0,0 +1,183 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::centreon::anomalydetection::class; + +use base qw(gorgone::class::module); + +use strict; +use warnings; +use gorgone::standard::library; +use gorgone::class::sqlquery; +use gorgone::class::http::http; +use ZMQ::LibZMQ4; +use ZMQ::Constants qw(:all); +use JSON::XS; + +my %handlers = (TERM => {}, HUP => {}); +my ($connector); + +sub new { + my ($class, %options) = @_; + + $connector = {}; + $connector->{internal_socket} = undef; + $connector->{module_id} = $options{module_id}; + $connector->{logger} = $options{logger}; + $connector->{config} = $options{config}; + $connector->{config_core} = $options{config_core}; + $connector->{config_db_centreon} = $options{config_db_centreon}; + $connector->{config_db_centstorage} = $options{config_db_centstorage}; + $connector->{stop} = 0; + + $connector->{resync_time} = (defined($options{config}->{resync_time}) && $options{config}->{resync_time} =~ /(\d+)/) ? $1 : 600; + $connector->{last_resync_time} = -1; + $connector->{authtoken} = undef; + $connector->{proxyurl} = undef; # format http://[username:password@]server:port + + bless $connector, $class; + $connector->set_signal_handlers(); + return $connector; +} + +sub set_signal_handlers { + my $self = shift; + + $SIG{TERM} = \&class_handle_TERM; + $handlers{TERM}->{$self} = sub { $self->handle_TERM() }; + $SIG{HUP} = \&class_handle_HUP; + $handlers{HUP}->{$self} = sub { $self->handle_HUP() }; +} + +sub handle_HUP { + my $self = shift; + $self->{reload} = 0; +} + +sub handle_TERM { + my $self = shift; + $self->{logger}->writeLogDebug("[anomalydetection] $$ Receiving order to stop..."); + $self->{stop} = 1; +} + +sub class_handle_TERM { + foreach (keys %{$handlers{TERM}}) { + &{$handlers{TERM}->{$_}}(); + } +} + +sub class_handle_HUP { + foreach (keys %{$handlers{HUP}}) { + &{$handlers{HUP}->{$_}}(); + } +} + +sub action_adsync { + my ($self, %options) = @_; + + $options{token} = $self->generate_token() if (!defined($options{token})); + $self->send_log(code => gorgone::class::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action adsync proceed' }); + + # we need to get proxyurl, token options + # need to check if the module is installed + # first part: declare in SaaS + + $self->{logger}->writeLogDebug("[anomalydetection] Finish adsync"); + $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => { message => 'action adsync finished' }); + return 0; +} + +sub event { + while (1) { + my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); + + $connector->{logger}->writeLogDebug("[anomalydetection] Event: $message"); + if ($message =~ /^\[(.*?)\]/) { + if ((my $method = $connector->can('action_' . lc($1)))) { + $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; + my ($action, $token) = ($1, $2); + my $data = JSON::XS->new->utf8->decode($3); + $method->($connector, token => $token, data => $data); + } + } + + last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); + } +} + +sub run { + my ($self, %options) = @_; + + $self->{db_centreon} = gorgone::class::db->new( + dsn => $self->{config_db_centreon}->{dsn}, + user => $self->{config_db_centreon}->{username}, + password => $self->{config_db_centreon}->{password}, + force => 2, + logger => $self->{logger} + ); + $self->{db_centstorage} = gorgone::class::db->new( + dsn => $self->{config_db_centstorage}->{dsn}, + user => $self->{config_db_centstorage}->{username}, + password => $self->{config_db_centstorage}->{password}, + force => 2, + logger => $self->{logger} + ); + + ##### Load objects ##### + $self->{class_object_centreon} = gorgone::class::sqlquery->new(logger => $self->{logger}, db_centreon => $self->{db_centreon}); + $self->{class_object_centstorage} = gorgone::class::sqlquery->new(logger => $self->{logger}, db_centreon => $self->{db_centstorage}); + $self->{http} = gorgone::class::http::http->new(logger => $self->{logger}); + + # Connect internal + $connector->{internal_socket} = gorgone::standard::library::connect_com( + zmq_type => 'ZMQ_DEALER', + name => 'gorgone-anomalydetection', + logger => $self->{logger}, + type => $self->{config_core}->{internal_com_type}, + path => $self->{config_core}->{internal_com_path} + ); + $connector->send_internal_action( + action => 'CENTREONADREADY', + data => {} + ); + $self->{poll} = [ + { + socket => $connector->{internal_socket}, + events => ZMQ_POLLIN, + callback => \&event, + } + ]; + while (1) { + # we try to do all we can + my $rev = zmq_poll($self->{poll}, 5000); + if (defined($rev) && $rev == 0 && $self->{stop} == 1) { + $self->{logger}->writeLogInfo("[anomalydetection] -class- $$ has quit"); + zmq_close($connector->{internal_socket}); + exit(0); + } + + if (time() - $self->{resync_time} > $self->{last_resync_time}) { + $self->{last_resync_time} = time(); + $self->action_adsync(); + } + } +} + +1; diff --git a/gorgone/gorgone/modules/centreon/anomalydetection/hooks.pm b/gorgone/gorgone/modules/centreon/anomalydetection/hooks.pm new file mode 100644 index 00000000000..bceb1a7e331 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/anomalydetection/hooks.pm @@ -0,0 +1,172 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::centreon::anomalydetection::hooks; + +use warnings; +use strict; +use JSON::XS; +use gorgone::class::core; +use gorgone::modules::centreon::anomalydetection::class; + +use constant NAMESPACE => 'centreon'; +use constant NAME => 'anomalydetection'; +use constant EVENTS => [ + { event => 'CENTREONADREADY' }, +]; + +my $config_core; +my $config; +my ($config_db_centreon, $config_db_centstorage); +my $process = {}; +my $stop = 0; + +sub register { + my (%options) = @_; + + $config = $options{config}; + $config_core = $options{config_core}; + $config_db_centreon = $options{config_db_centreon}; + $config_db_centstorage = $options{config_db_centstorage}; + $config->{resync_time} = defined($config->{resync_time}) && $config->{resync_time} =~ /(\d+)/ ? $1 : 600; + return (1, NAMESPACE, NAME, EVENTS); +} + +sub init { + my (%options) = @_; + + create_child(logger => $options{logger}); +} + +sub routing { + my (%options) = @_; + + my $data; + eval { + $data = JSON::XS->new->utf8->decode($options{data}); + }; + if ($@) { + $options{logger}->writeLogError("[anomalydetection] Cannot decode json data: $@"); + gorgone::standard::library::add_history( + dbh => $options{dbh}, + code => 10, token => $options{token}, + data => { message => 'gorgone-anomalydetection: cannot decode json' }, + json_encode => 1 + ); + return undef; + } + + if ($options{action} eq 'CENTREONADREADY') { + $process->{ready} = 1; + return undef; + } + + if (gorgone::class::core::waiting_ready(ready => \$process->{ready}) == 0) { + gorgone::standard::library::add_history( + dbh => $options{dbh}, + code => 10, token => $options{token}, + data => { message => 'gorgone-anomalydetection: still no ready' }, + json_encode => 1 + ); + return undef; + } + + gorgone::standard::library::zmq_send_message( + socket => $options{socket}, + identity => 'gorgone-anomalydetection', + action => $options{action}, + data => $options{data}, + token => $options{token}, + ); +} + +sub gently { + my (%options) = @_; + + $stop = 1; + $options{logger}->writeLogDebug("[anomalydetection] Send TERM signal"); + if ($process->{running} == 1) { + CORE::kill('TERM', $process->{pid}); + } +} + +sub kill { + my (%options) = @_; + + if ($process->{running} == 1) { + $options{logger}->writeLogDebug("[anomalydetection] Send KILL signal for pool"); + CORE::kill('KILL', $process->{pid}); + } +} + +sub kill_internal { + my (%options) = @_; + +} + +sub check { + my (%options) = @_; + + my $count = 0; + foreach my $pid (keys %{$options{dead_childs}}) { + # Not me + next if ($process->{pid} != $pid); + + $process = {}; + delete $options{dead_childs}->{$pid}; + if ($stop == 0) { + create_child(logger => $options{logger}); + } + } + + $count++ if (defined($process->{running}) && $process->{running} == 1); + + return $count; +} + +sub broadcast { + my (%options) = @_; + + routing(%options); +} + +# Specific functions +sub create_child { + my (%options) = @_; + + $options{logger}->writeLogInfo("[anomalydetection] Create module 'anomalydetection' process"); + my $child_pid = fork(); + if ($child_pid == 0) { + $0 = 'gorgone-anomalydetection'; + my $module = gorgone::modules::centreon::anomalydetection::class->new( + logger => $options{logger}, + config_core => $config_core, + config => $config, + config_db_centreon => $config_db_centreon, + config_db_centstorage => $config_db_centstorage + ); + $module->run(); + exit(0); + } + $options{logger}->writeLogDebug("[anomalydetection] PID $child_pid (gorgone-anomalydetection)"); + $process = { pid => $child_pid, ready => 0, running => 1 }; +} + +1; From ca206905a842370fdfae80b8c7fe99b741634abb Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Mon, 24 Feb 2020 09:07:04 +0100 Subject: [PATCH 331/948] fix(doc): package install typo --- gorgone/docs/getting_started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/docs/getting_started.md b/gorgone/docs/getting_started.md index 510af5eb2c6..5177b6bacd2 100644 --- a/gorgone/docs/getting_started.md +++ b/gorgone/docs/getting_started.md @@ -48,7 +48,7 @@ Execute the following commands to install them all: ```bash yum install 'perl(JSON::PP)' 'perl(NetAddr::IP)' 'perl(Schedule::Cron)' 'perl(Crypt::CBC)' 'perl(ZMQ::LibZMQ4)' 'perl(JSON::XS)' 'perl(YAML)' 'perl(DBD::SQLite)' 'perl(DBD::mysql)' 'perl(UUID)' 'perl(HTTP::Daemon)' 'perl(HTTP::Daemon::SSL)' 'perl(HTTP::Status)' 'perl(MIME::Base64)' -yum install packaging/packages/perl-CryptX-0.064-1.el7.x86_64 packaging/packages/perl-YAML-LibYAML-0.80-1.el7.x86_64.rpm packaging/packages/perl-Hash-Merge-0.300-1.el7.noarch.rpm packaging/packages/perl-Clone-Choose-0.010-1.el7.noarch.rpm +yum install packaging/packages/perl-CryptX-0.064-1.el7.x86_64.rpm packaging/packages/perl-YAML-LibYAML-0.80-1.el7.x86_64.rpm packaging/packages/perl-Hash-Merge-0.300-1.el7.noarch.rpm packaging/packages/perl-Clone-Choose-0.010-1.el7.noarch.rpm ``` ## Configuration From 14fbe6065683e5e629ca8ecbe5b618cf4e3eadee Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Mon, 24 Feb 2020 09:09:24 +0100 Subject: [PATCH 332/948] enh(doc): add package digest md5 --- gorgone/docs/getting_started.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gorgone/docs/getting_started.md b/gorgone/docs/getting_started.md index 5177b6bacd2..95b15160753 100644 --- a/gorgone/docs/getting_started.md +++ b/gorgone/docs/getting_started.md @@ -23,6 +23,7 @@ The daemon uses the following Perl modules: * Repository 'centreon-stable': * ZMQ::LibZMQ4 * UUID + * Digest::MD5::File * Repository 'centos base': * JSON::PP * JSON::XS @@ -47,7 +48,7 @@ The daemon uses the following Perl modules: Execute the following commands to install them all: ```bash -yum install 'perl(JSON::PP)' 'perl(NetAddr::IP)' 'perl(Schedule::Cron)' 'perl(Crypt::CBC)' 'perl(ZMQ::LibZMQ4)' 'perl(JSON::XS)' 'perl(YAML)' 'perl(DBD::SQLite)' 'perl(DBD::mysql)' 'perl(UUID)' 'perl(HTTP::Daemon)' 'perl(HTTP::Daemon::SSL)' 'perl(HTTP::Status)' 'perl(MIME::Base64)' +yum install 'perl(JSON::PP)' 'perl(Digest::MD5::File)' 'perl(NetAddr::IP)' 'perl(Schedule::Cron)' 'perl(Crypt::CBC)' 'perl(ZMQ::LibZMQ4)' 'perl(JSON::XS)' 'perl(YAML)' 'perl(DBD::SQLite)' 'perl(DBD::mysql)' 'perl(UUID)' 'perl(HTTP::Daemon)' 'perl(HTTP::Daemon::SSL)' 'perl(HTTP::Status)' 'perl(MIME::Base64)' yum install packaging/packages/perl-CryptX-0.064-1.el7.x86_64.rpm packaging/packages/perl-YAML-LibYAML-0.80-1.el7.x86_64.rpm packaging/packages/perl-Hash-Merge-0.300-1.el7.noarch.rpm packaging/packages/perl-Clone-Choose-0.010-1.el7.noarch.rpm ``` From 5005e30e16740da5ef1fb77ec65e4b92b49a9a25 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Mon, 24 Feb 2020 09:47:41 +0100 Subject: [PATCH 333/948] enh(core): constatus get hostname for type zmq --- gorgone/gorgone/standard/library.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index 108408ecded..ba92fef13ca 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -508,7 +508,7 @@ sub ping { } } - return (0, { action => 'ping', message => 'ping ok', id => $options{id}, data => $constatus }, 'PONG'); + return (0, { action => 'ping', message => 'ping ok', id => $options{id}, hostname => $options{gorgone}->{hostname}, data => $constatus }, 'PONG'); } sub putlog { From 91a04a1bf85b813d9ad00d0db5d8c194d8832da4 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Mon, 24 Feb 2020 16:46:15 +0100 Subject: [PATCH 334/948] enh(module): wip anomaly detection --- gorgone/gorgone/class/http/backend/curl.pm | 1 + gorgone/gorgone/class/sqlquery.pm | 24 +- .../centreon/anomalydetection/class.pm | 323 +++++++++++++++++- 3 files changed, 341 insertions(+), 7 deletions(-) diff --git a/gorgone/gorgone/class/http/backend/curl.pm b/gorgone/gorgone/class/http/backend/curl.pm index 381f02af3a5..4b8fffbbcd7 100644 --- a/gorgone/gorgone/class/http/backend/curl.pm +++ b/gorgone/gorgone/class/http/backend/curl.pm @@ -104,6 +104,7 @@ my $http_code_explained = { sub cb_debug { my ($easy, $type, $data, $uservar) = @_; + chomp $data; my $msg = ''; if ($type == $uservar->{constant_cb}->(name => 'CURLINFO_TEXT')) { $msg = sprintf("== Info: %s", $data); diff --git a/gorgone/gorgone/class/sqlquery.pm b/gorgone/gorgone/class/sqlquery.pm index 9ca15404abf..390d5912b0f 100644 --- a/gorgone/gorgone/class/sqlquery.pm +++ b/gorgone/gorgone/class/sqlquery.pm @@ -28,7 +28,7 @@ sub new { my $self = {}; $self->{logger} = $options{logger}; $self->{db_centreon} = $options{db_centreon}; - + bless $self, $class; return $self; } @@ -39,7 +39,7 @@ sub builder { my $where = defined($options{where}) ? ' WHERE ' . $options{where} : ''; my $extra_suffix = defined($options{extra_suffix}) ? $options{extra_suffix} : ''; my $request = $options{request} . " " . join(', ', @{$options{fields}}) . - " FROM " . join(', ', @{$options{tables}}) . $where . $extra_suffix; + ' FROM ' . join(', ', @{$options{tables}}) . $where . $extra_suffix; return $request; } @@ -82,10 +82,30 @@ sub execute { return $self->do(request => $request, %options); } +sub transaction_query { + my ($self, %options) = @_; + + $self->transaction_mode(1); + my ($status) = $self->do(request => $options{request}); + if ($status == -1) { + $self->rollback(); + return -1; + } + + $self->commit(); + return 0; +} + sub quote { my ($self, %options) = @_; return $self->{db_centreon}->quote($options{value}); } +sub transaction_mode { shift->{db_centreon}->transaction_mode($_[0]); }; + +sub commit { shift->{db_centreon}->commit(); } + +sub rollback { shift->{db_centreon}->rollback(); } + 1; diff --git a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm index dcf426623b0..231d2ca0218 100644 --- a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm +++ b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm @@ -49,8 +49,11 @@ sub new { $connector->{resync_time} = (defined($options{config}->{resync_time}) && $options{config}->{resync_time} =~ /(\d+)/) ? $1 : 600; $connector->{last_resync_time} = -1; - $connector->{authtoken} = undef; - $connector->{proxyurl} = undef; # format http://[username:password@]server:port + $connector->{saas_token} = undef; + $connector->{saas_url} = undef; + $connector->{proxy_url} = undef; # format http://[username:password@]server:port + $connector->{centreon_metrics} = {}; + $connector->{unregister_metrics_centreon} = {}; bless $connector, $class; $connector->set_signal_handlers(); @@ -89,15 +92,325 @@ sub class_handle_HUP { } } +sub http_check_error { + my ($self, %options) = @_; + + if ($options{status} == 1) { + $self->{logger}->writeLogError("[anomalydetection] -class- $options{endpoint} issue"); + return 1; + } + + my $code = $self->{http}->get_code(); + if ($code !~ /$options{http_code_continue}/) { + $self->{logger}->writeLogError("[anomalydetection] -class- $options{endpoint} issue - " . $self->{http}->get_message()); + return 1; + } + + return 0; +} + +sub saas_api_request { + my ($self, %options) = @_; + + my ($status, $payload); + if (defined($options{payload})) { + ($status, $payload) = $self->json_encode(argument => $options{payload}); + return 1 if ($status == 1); + } + + ($status, my $response) = $self->{http}->request( + method => $options{method}, hostname => '', + full_url => $self->{saas_url} . $options{endpoint}, + query_form_post => $payload, + header => [ + 'Accept-Type: application/json; charset=utf-8', + 'Content-Type: application/json; charset=utf-8', + 'x-api-key: ' . $self->{saas_token} + ], + proxyurl => $self->{proxy_url}, + curl_opt => ['CURLOPT_SSL_VERIFYPEER => 0'] + ); + return 1 if ($self->http_check_error(status => $status, endpoint => $options{endpoint}, http_code_continue => $options{http_code_continue}) == 1); + + ($status, my $result) = $self->json_decode(argument => $response); + return 1 if ($status == 1); + + return (0, $result); +} + +sub connection_informations { + my ($self, %options) = @_; + + my ($status, $datas) = $self->{class_object_centreon}->custom_execute( + request => "select `key`, `value` from options WHERE `key` IN ('saas_url', 'saas_token', 'proxy_url', 'proxy_port', 'proxy_user', 'proxy_password')", + mode => 2 + ); + if ($status == -1) { + $self->{logger}->writeLogError('[anomalydetection] -class- cannot get connection informations'); + return 1; + } + + $self->{$_->[0]} = $_->[1] foreach (@$datas); + + if (!defined($self->{saas_url}) || $self->{saas_url} eq '') { + $self->{logger}->writeLogError('[anomalydetection] -class- database: saas_url is not defined'); + return 1; + } + if (!defined($self->{saas_token}) || $self->{saas_token} eq '') { + $self->{logger}->writeLogError('[anomalydetection] -class- database: saas_token is not defined'); + return 1; + } + + if (defined($self->{proxy_url})) { + if ($self->{proxy_url} eq '') { + $self->{proxy_url} = undef; + return 0; + } + + $self->{proxy_url} = $self->{proxy_user} . ':' . $self->{proxy_password} . '@' . $self->{proxy_url} + if (defined($self->{proxy_user}) && $self->{proxy_user} ne '' && + defined($self->{proxy_password}) && $self->{proxy_password} ne ''); + $self->{proxy_url} = $self->{proxy_url} . ':' . $self->{proxy_port} + if (defined($self->{proxy_port}) && $self->{proxy_port} =~ /(\d+)/); + $self->{proxy_url} = 'http://' . $self->{proxy_url}; + } + + return 0; +} + +sub get_centreon_anomaly_metrics { + my ($self, %options) = @_; + + my ($status, $datas) = $self->{class_object_centreon}->custom_execute( + request => ' + SELECT mas.*, hsr.host_host_id as host_id + FROM mod_anomaly_service mas, host_service_relation hsr + WHERE mas.service_id = hsr.service_service_id + ', + keys => 'id', + mode => 1 + ); + if ($status == -1) { + $self->{logger}->writeLogError('[anomalydetection] -class- database: cannot get metrics from centreon'); + return 1; + } + + $self->{centreon_metrics} = $datas; + + my $metric_ids = {}; + foreach (keys %{$self->{centreon_metrics}}) { + if (!defined($self->{centreon_metrics}->{$_}->{saas_creation_date})) { + $metric_ids->{ $self->{centreon_metrics}->{$_}->{metric_id} } = $_; + } + } + + if (scalar(keys %$metric_ids) > 0) { + ($status, $datas) = $self->{class_object_centstorage}->custom_execute( + request => 'SELECT `metric_id`, `metric_name` FROM metrics WHERE metric_id IN (' . join(', ', keys %$metric_ids) . ')', + mode => 2 + ); + if ($status == -1) { + $self->{logger}->writeLogError('[anomalydetection] -class- database: cannot get metric name informations'); + return 2; + } + + foreach (@$datas) { + $self->{centreon_metrics}->{ $metric_ids->{ $_->[0] } }->{metric_name} = $_->[1]; + } + } + + return 0; +} + +sub save_centreon_previous_register { + my ($self, %options) = @_; + + my ($query, $query_append) = ('', ''); + foreach (keys %{$self->{unregister_metrics_centreon}}) { + $query .= $query_append . + 'UPDATE mod_anomaly_service SET' . + ' saas_model_id = ' . $self->{class_object_centreon}->quote(value => $self->{unregister_metrics_centreon}->{$_}->{saas_model_id}) . ',' . + ' saas_metric_id = ' . $self->{class_object_centreon}->quote(value => $self->{unregister_metrics_centreon}->{$_}->{saas_metric_id}) . ',' . + ' saas_creation_date = ' . $self->{unregister_metrics_centreon}->{$_}->{creation_date} . + ' WHERE `id` = ' . $_; + $query_append = ';'; + } + if ($query ne '') { + my $status = $self->{class_object_centreon}->transaction_query(request => $query); + if ($status == -1) { + $self->{logger}->writeLogError('[anomalydetection] -class- database: cannot save centreon previous register'); + return 1; + } + + foreach (keys %{$self->{unregister_metrics_centreon}}) { + $self->{centreon_metrics}->{$_}->{saas_creation_date} = $self->{unregister_metrics_centreon}->{$_}->{creation_date}; + $self->{centreon_metrics}->{$_}->{saas_model_id} = $self->{unregister_metrics_centreon}->{$_}->{saas_model_id}; + $self->{centreon_metrics}->{$_}->{saas_metric_id} = $self->{unregister_metrics_centreon}->{$_}->{saas_metric_id}; + } + } + + $self->{unregister_metrics_centreon} = {}; + return 0; +} + +sub saas_register_metrics { + my ($self, %options) = @_; + + my $register_centreon_metrics = {}; + my ($query, $query_append) = ('', ''); + + $self->{generate_metrics_lua} = 0; + foreach (keys %{$self->{centreon_metrics}}) { + # metric_name is set when we need to register it + next if (!defined($self->{centreon_metrics}->{$_}->{metric_name})); + next if ($self->{centreon_metrics}->{$_}->{saas_to_delete} == 1); + + my $payload = { + metrics => [ + { + name => $self->{centreon_metrics}->{$_}->{metric_name}, + labels => { + host_id => $self->{centreon_metrics}->{$_}->{host_id}, + service_id => $self->{centreon_metrics}->{$_}->{service_id} + }, + preprocessingOptions => { + bucketize => { + bucketizeFunction => 'mean', + period => 300 + } + } + } + ], + algorithm => { + type => $self->{centreon_metrics}->{$_}->{ml_model_name}, + options => { + period => '30d' + } + } + }; + + my ($status, $result) = $self->saas_api_request( + endpoint => '/machinelearning', + method => 'POST', + payload => $payload, + http_code_continue => '^2' + ); + return 1 if ($status); + + $self->{logger}->writeLogDebug( + "[anomalydetection] -class- saas: metric '$self->{centreon_metrics}->{$_}->{host_id}/$self->{centreon_metrics}->{$_}->{service_id}/$self->{centreon_metrics}->{$_}->{metric_name}' registered" + ); + + # {"metrics": [{"name":"system_load1","labels":{"hostname":"srvi-monitoring"},"preprocessingOptions":{"bucketize":{"bucketizeFunction":"mean","period":300}},"id":"e255db55-008b-48cd-8dfe-34cf60babd01"}],"algorithm":{"type":"h2o","options":{"period":"180d"}}, + # "id":"257fc68d-3248-4c92-92a1-43c0c63d5e5e"} + + $self->{generate_metrics_lua} = 1; + $register_centreon_metrics->{$_} = { + saas_creation_date => time(), + saas_model_id => $result->{id}, + saas_metric_id => $result->{metrics}->[0]->{id} + }; + + $query .= $query_append . + 'UPDATE mod_anomaly_service SET' . + ' saas_model_id = ' . $self->{class_object_centreon}->quote(value => $register_centreon_metrics->{$_}->{saas_model_id}) . ',' . + ' saas_metric_id = ' . $self->{class_object_centreon}->quote(value => $register_centreon_metrics->{$_}->{saas_metric_id}) . ',' . + ' saas_creation_date = ' . $register_centreon_metrics->{$_}->{saas_creation_date} . + ' WHERE `id` = ' . $_; + $query_append = ';'; + } + + return 0 if ($query eq ''); + + my $status = $self->{class_object_centreon}->transaction_query(request => $query); + if ($status == -1) { + $self->{unregister_metrics_centreon} = $register_centreon_metrics; + $self->{logger}->writeLogError('[anomalydetection] -class- dabase: cannot update centreon register'); + return 1; + } + + foreach (keys %$register_centreon_metrics) { + $self->{centreon_metrics}->{$_}->{saas_creation_date} = $register_centreon_metrics->{$_}->{saas_creation_date}; + $self->{centreon_metrics}->{$_}->{saas_metric_id} = $register_centreon_metrics->{$_}->{saas_metric_id}; + $self->{centreon_metrics}->{$_}->{saas_model_id} = $register_centreon_metrics->{$_}->{saas_model_id}; + } + + return 0; +} + +sub saas_delete_metrics { + my ($self, %options) = @_; + + my $delete_ids = []; + foreach (keys %{$self->{centreon_metrics}}) { + next if ($self->{centreon_metrics}->{$_}->{saas_to_delete} == 0); + + if (defined($self->{centreon_metrics}->{$_}->{saas_model_id})) { + my ($status, $result) = $self->saas_api_request( + endpoint => '/machinelearning/' . $self->{centreon_metrics}->{$_}->{saas_model_id}, + method => 'DELETE', + http_code_continue => '^(?:2|404)' + ); + next if ($status); + + $self->{logger}->writeLogDebug( + "[anomalydetection] -class- saas:: metric '$self->{centreon_metrics}->{$_}->{host_id}/$self->{centreon_metrics}->{$_}->{service_id}/$self->{centreon_metrics}->{$_}->{metric_name}' deleted" + ); + + next if (!defined($result->{message}) || + $result->{message} !~ /machine learning request id is not found/i); + } + + push @$delete_ids, $_; + } + + return 0 if (scalar(@$delete_ids) <= 0); + + my $status = $self->{class_object_centreon}->transaction_query( + request => 'DELETE FROM mod_anomaly_service WHERE id IN (' . join(', ', @$delete_ids) . ')' + ); + if ($status == -1) { + $self->{logger}->writeLogError('[anomalydetection] -class- database: cannot delete centreon saas'); + return 1; + } + + return 0; +} + sub action_adsync { my ($self, %options) = @_; $options{token} = $self->generate_token() if (!defined($options{token})); $self->send_log(code => gorgone::class::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action adsync proceed' }); - # we need to get proxyurl, token options - # need to check if the module is installed - # first part: declare in SaaS + if ($self->connection_informations()) { + $self->send_log(code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot get connection informations' }); + return 1; + } + + if ($self->save_centreon_previous_register()) { + $self->send_log(code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot save previsous register' }); + return 1; + } + + if ($self->get_centreon_anomaly_metrics()) { + $self->send_log(code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot get metrics from centreon' }); + return 1; + } + + if ($self->saas_register_metrics()) { + $self->send_log(code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot get declare metrics in saas' }); + return 1; + } + + if ($self->{generate_metrics_lua} == 1) { + # need to generate a new file (TODO) + } + + if ($self->saas_delete_metrics()) { + $self->send_log(code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot delete metrics in saas' }); + return 1; + } $self->{logger}->writeLogDebug("[anomalydetection] Finish adsync"); $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => { message => 'action adsync finished' }); From 915113c9108d765438446b287880ca1602ce7649 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 25 Feb 2020 14:05:20 +0100 Subject: [PATCH 335/948] enh(anomaly): wip --- .../centreon/anomalydetection/class.pm | 191 ++++++++++++++++-- 1 file changed, 175 insertions(+), 16 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm index 231d2ca0218..3f136a6f003 100644 --- a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm +++ b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm @@ -1,5 +1,5 @@ # -# Copyright 2019 Centreon (http://www.centreon.com/) +# Copyright 2020 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for @@ -109,6 +109,39 @@ sub http_check_error { return 0; } +sub get_localhost_poller { + my ($self, %options) = @_; + + my $instance; + foreach (keys %{$self->{pollers}}) { + if ($self->{pollers}->{$_}->{localhost} == 1) { + $instance = $_; + last; + } + } + + return $instance; +} + +sub get_poller { + my ($self, %options) = @_; + + return $self->{pollers}->{$options{instance}}; +} + +sub write_file { + my ($self, %options) = @_; + + my $fh; + if (!open($fh, '>', $options{file})) { + $self->{logger}->writeLogError("[anomalydetection] -class- cannot open file '" . $options{file} . "': $!"); + return 1; + } + print $fh $options{content}; + close($fh); + return 0; +} + sub saas_api_request { my ($self, %options) = @_; @@ -182,10 +215,29 @@ sub get_centreon_anomaly_metrics { my ($self, %options) = @_; my ($status, $datas) = $self->{class_object_centreon}->custom_execute( + request => + 'SELECT nagios_server_id, cfg_dir, centreonbroker_cfg_path, localhost, ' . + 'engine_start_command, engine_stop_command, engine_restart_command, engine_reload_command, ' . + 'broker_reload_command ' . + 'FROM cfg_nagios ' . + 'JOIN nagios_server ' . + 'WHERE id = nagios_server_id', + mode => 1, + keys => 'nagios_server_id' + ); + if ($status == -1) { + $self->{logger}->writeLogError('[anomalydetection] cannot get configuration for pollers'); + return 1; + } + $self->{pollers} = $datas; + + ($status, $datas) = $self->{class_object_centreon}->custom_execute( request => ' - SELECT mas.*, hsr.host_host_id as host_id - FROM mod_anomaly_service mas, host_service_relation hsr - WHERE mas.service_id = hsr.service_service_id + SELECT mas.*, hsr.host_host_id as host_id, nhr.nagios_server_id as instance_id + FROM mod_anomaly_service mas, host_service_relation hsr, ns_host_relation nhr + WHERE + mas.service_id = hsr.service_service_id AND + hsr.host_host_id = nhr.host_host_id ', keys => 'id', mode => 1 @@ -231,7 +283,8 @@ sub save_centreon_previous_register { 'UPDATE mod_anomaly_service SET' . ' saas_model_id = ' . $self->{class_object_centreon}->quote(value => $self->{unregister_metrics_centreon}->{$_}->{saas_model_id}) . ',' . ' saas_metric_id = ' . $self->{class_object_centreon}->quote(value => $self->{unregister_metrics_centreon}->{$_}->{saas_metric_id}) . ',' . - ' saas_creation_date = ' . $self->{unregister_metrics_centreon}->{$_}->{creation_date} . + ' saas_creation_date = ' . $self->{unregister_metrics_centreon}->{$_}->{creation_date} . ',' . + ' saas_update_date = ' . $self->{unregister_metrics_centreon}->{$_}->{creation_date} . ' WHERE `id` = ' . $_; $query_append = ';'; } @@ -301,8 +354,25 @@ sub saas_register_metrics { "[anomalydetection] -class- saas: metric '$self->{centreon_metrics}->{$_}->{host_id}/$self->{centreon_metrics}->{$_}->{service_id}/$self->{centreon_metrics}->{$_}->{metric_name}' registered" ); - # {"metrics": [{"name":"system_load1","labels":{"hostname":"srvi-monitoring"},"preprocessingOptions":{"bucketize":{"bucketizeFunction":"mean","period":300}},"id":"e255db55-008b-48cd-8dfe-34cf60babd01"}],"algorithm":{"type":"h2o","options":{"period":"180d"}}, - # "id":"257fc68d-3248-4c92-92a1-43c0c63d5e5e"} + # { + # "metrics": [ + # { + # "name": "system_load1", + # "labels": { "hostname":"srvi-monitoring" }, + # "preprocessingOptions": { + # "bucketize": { + # "bucketizeFunction": "mean", "period": 300 + # } + # }, + # "id": "e255db55-008b-48cd-8dfe-34cf60babd01" + # } + # ], + # "algorithm": { + # "type": "h2o", + # "options": { "period":"180d" } + # }, + # "id":"257fc68d-3248-4c92-92a1-43c0c63d5e5e" + # } $self->{generate_metrics_lua} = 1; $register_centreon_metrics->{$_} = { @@ -315,7 +385,8 @@ sub saas_register_metrics { 'UPDATE mod_anomaly_service SET' . ' saas_model_id = ' . $self->{class_object_centreon}->quote(value => $register_centreon_metrics->{$_}->{saas_model_id}) . ',' . ' saas_metric_id = ' . $self->{class_object_centreon}->quote(value => $register_centreon_metrics->{$_}->{saas_metric_id}) . ',' . - ' saas_creation_date = ' . $register_centreon_metrics->{$_}->{saas_creation_date} . + ' saas_creation_date = ' . $register_centreon_metrics->{$_}->{saas_creation_date} . ',' . + ' saas_update_date = ' . $register_centreon_metrics->{$_}->{saas_creation_date} . ' WHERE `id` = ' . $_; $query_append = ';'; } @@ -325,12 +396,13 @@ sub saas_register_metrics { my $status = $self->{class_object_centreon}->transaction_query(request => $query); if ($status == -1) { $self->{unregister_metrics_centreon} = $register_centreon_metrics; - $self->{logger}->writeLogError('[anomalydetection] -class- dabase: cannot update centreon register'); + $self->{logger}->writeLogError('[anomalydetection] -class- database: cannot update centreon register'); return 1; } foreach (keys %$register_centreon_metrics) { $self->{centreon_metrics}->{$_}->{saas_creation_date} = $register_centreon_metrics->{$_}->{saas_creation_date}; + $self->{centreon_metrics}->{$_}->{saas_update_date} = $register_centreon_metrics->{$_}->{saas_creation_date}; $self->{centreon_metrics}->{$_}->{saas_metric_id} = $register_centreon_metrics->{$_}->{saas_metric_id}; $self->{centreon_metrics}->{$_}->{saas_model_id} = $register_centreon_metrics->{$_}->{saas_model_id}; } @@ -354,7 +426,7 @@ sub saas_delete_metrics { next if ($status); $self->{logger}->writeLogDebug( - "[anomalydetection] -class- saas:: metric '$self->{centreon_metrics}->{$_}->{host_id}/$self->{centreon_metrics}->{$_}->{service_id}/$self->{centreon_metrics}->{$_}->{metric_name}' deleted" + "[anomalydetection] -class- saas: metric '$self->{centreon_metrics}->{$_}->{host_id}/$self->{centreon_metrics}->{$_}->{service_id}/$self->{centreon_metrics}->{$_}->{metric_name}' deleted" ); next if (!defined($result->{message}) || @@ -377,11 +449,97 @@ sub saas_delete_metrics { return 0; } -sub action_adsync { +sub generate_lua_filter_file { + my ($self, %options) = @_; + + my $data = { filters => { } }; + foreach (values %{$self->{centreon_metrics}}) { + next if ($_->{saas_to_delete} == 1); + next if (!defined($_->{saas_creation_date})); + + $data->{filters}->{ $_->{host_id} } = {} + if (!defined($data->{filters}->{ $_->{host_id} })); + $data->{filters}->{ $_->{host_id} }->{ $_->{service_id} } = {} + if (!defined($data->{filters}->{ $_->{host_id} }->{ $_->{service_id} })); + $data->{filters}->{ $_->{host_id} }->{ $_->{service_id} }->{ $_->{metric_name} } = 1; + } + + my ($status, $content) = $self->json_encode(argument => $data); + if ($status == 1) { + $self->{logger}->writeLogError('[anomalydetection] -class- cannot encode lua filter file'); + return 1; + } + + my $instance = $self->get_localhost_poller(); + if ($status == 1) { + $self->{logger}->writeLogError('[anomalydetection] -class- cannot find localhost poller'); + return 1; + } + + my $poller = $self->get_poller(instance => $instance); + my $file = $poller->{centreonbroker_cfg_path} . '/anomaly-detection-filters.json'; + if (! -w $poller->{centreonbroker_cfg_path}) { + $self->{logger}->writeLogError("[anomalydetection] -class- cannot write file '" . $file . "'"); + return 1; + } + + return 1 if ($self->write_file(file => $file, content => $content)); + + $self->{logger}->writeLogDebug('[anomalydetection] -class- reload centreon-broker'); + + $self->send_internal_action( + action => 'COMMAND', + token => $options{token}, + data => { + content => [ { command => 'sudo ' . $poller->{broker_reload_command} } ] + }, + ); + + return 0; +} + +sub saas_get_predicts { + my ($self, %options) = @_; + + foreach (keys %{$self->{centreon_metrics}}) { + next if ($self->{centreon_metrics}->{$_}->{saas_to_delete} == 1); + next if (!defined($self->{centreon_metrics}->{$_}->{thresholds_file}) || + $self->{centreon_metrics}->{$_}->{thresholds_file} eq ''); + next if ($self->{centreon_metrics}->{$_}->{saas_update_date} > time() - 86400); + + my ($status, $result) = $self->saas_api_request( + endpoint => '/machinelearning/' . $self->{centreon_metrics}->{$_}->{saas_model_id} . '/predicts', + method => 'GET', + http_code_continue => '^2' + ); + next if ($status); + + $self->{logger}->writeLogDebug( + "[anomalydetection] -class- saas: get predict metric '$self->{centreon_metrics}->{$_}->{host_id}/$self->{centreon_metrics}->{$_}->{service_id}/$self->{centreon_metrics}->{$_}->{metric_name}'" + ); + } + + return 0; +} + +sub action_saaspredict { + my ($self, %options) = @_; + + $options{token} = $self->generate_token() if (!defined($options{token})); + $self->send_log(code => gorgone::class::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action saaspredict proceed' }); + + $self->saas_get_predicts(); + + $self->{logger}->writeLogDebug('[anomalydetection] Finish saaspredict'); + $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => { message => 'action saaspredict finished' }); + return 0; +} + +sub action_saasregister { my ($self, %options) = @_; $options{token} = $self->generate_token() if (!defined($options{token})); - $self->send_log(code => gorgone::class::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action adsync proceed' }); + $self->send_log(code => gorgone::class::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action saasregister proceed' }); if ($self->connection_informations()) { $self->send_log(code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot get connection informations' }); @@ -404,7 +562,7 @@ sub action_adsync { } if ($self->{generate_metrics_lua} == 1) { - # need to generate a new file (TODO) + $self->generate_lua_filter_file(token => $options{token}); } if ($self->saas_delete_metrics()) { @@ -412,8 +570,8 @@ sub action_adsync { return 1; } - $self->{logger}->writeLogDebug("[anomalydetection] Finish adsync"); - $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => { message => 'action adsync finished' }); + $self->{logger}->writeLogDebug('[anomalydetection] Finish saasregister'); + $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => { message => 'action saasregister finished' }); return 0; } @@ -488,7 +646,8 @@ sub run { if (time() - $self->{resync_time} > $self->{last_resync_time}) { $self->{last_resync_time} = time(); - $self->action_adsync(); + $self->action_saasregister(); + $self->action_saaspredict(); } } } From 58eb56776d3b6940f989c27cb22310f84834c2c7 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 25 Feb 2020 15:31:02 +0100 Subject: [PATCH 336/948] enh(anomaly): add prediction --- .../centreon/anomalydetection/class.pm | 99 ++++++++++++++----- 1 file changed, 72 insertions(+), 27 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm index 3f136a6f003..b99a428fd57 100644 --- a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm +++ b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm @@ -30,6 +30,8 @@ use gorgone::class::http::http; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); use JSON::XS; +use IO::Compress::Bzip2; +use MIME::Base64; my %handlers = (TERM => {}, HUP => {}); my ($connector); @@ -249,28 +251,6 @@ sub get_centreon_anomaly_metrics { $self->{centreon_metrics} = $datas; - my $metric_ids = {}; - foreach (keys %{$self->{centreon_metrics}}) { - if (!defined($self->{centreon_metrics}->{$_}->{saas_creation_date})) { - $metric_ids->{ $self->{centreon_metrics}->{$_}->{metric_id} } = $_; - } - } - - if (scalar(keys %$metric_ids) > 0) { - ($status, $datas) = $self->{class_object_centstorage}->custom_execute( - request => 'SELECT `metric_id`, `metric_name` FROM metrics WHERE metric_id IN (' . join(', ', keys %$metric_ids) . ')', - mode => 2 - ); - if ($status == -1) { - $self->{logger}->writeLogError('[anomalydetection] -class- database: cannot get metric name informations'); - return 2; - } - - foreach (@$datas) { - $self->{centreon_metrics}->{ $metric_ids->{ $_->[0] } }->{metric_name} = $_->[1]; - } - } - return 0; } @@ -297,6 +277,7 @@ sub save_centreon_previous_register { foreach (keys %{$self->{unregister_metrics_centreon}}) { $self->{centreon_metrics}->{$_}->{saas_creation_date} = $self->{unregister_metrics_centreon}->{$_}->{creation_date}; + $self->{centreon_metrics}->{$_}->{saas_update_date} = $self->{unregister_metrics_centreon}->{$_}->{creation_date}; $self->{centreon_metrics}->{$_}->{saas_model_id} = $self->{unregister_metrics_centreon}->{$_}->{saas_model_id}; $self->{centreon_metrics}->{$_}->{saas_metric_id} = $self->{unregister_metrics_centreon}->{$_}->{saas_metric_id}; } @@ -314,8 +295,8 @@ sub saas_register_metrics { $self->{generate_metrics_lua} = 0; foreach (keys %{$self->{centreon_metrics}}) { - # metric_name is set when we need to register it - next if (!defined($self->{centreon_metrics}->{$_}->{metric_name})); + # saas_creation_date is set when we need to register it + next if (defined($self->{centreon_metrics}->{$_}->{saas_creation_date})); next if ($self->{centreon_metrics}->{$_}->{saas_to_delete} == 1); my $payload = { @@ -501,6 +482,8 @@ sub generate_lua_filter_file { sub saas_get_predicts { my ($self, %options) = @_; + my ($query, $query_append) = ('', ''); + my $engine_reload = {}; foreach (keys %{$self->{centreon_metrics}}) { next if ($self->{centreon_metrics}->{$_}->{saas_to_delete} == 1); next if (!defined($self->{centreon_metrics}->{$_}->{thresholds_file}) || @@ -517,6 +500,66 @@ sub saas_get_predicts { $self->{logger}->writeLogDebug( "[anomalydetection] -class- saas: get predict metric '$self->{centreon_metrics}->{$_}->{host_id}/$self->{centreon_metrics}->{$_}->{service_id}/$self->{centreon_metrics}->{$_}->{metric_name}'" ); + + next if (!defined($result->[0]) || !defined($result->[0]->{predict})); + + my $data = [ + { + host_id => $self->{centreon_metrics}->{$_}->{host_id}, + service_id => $self->{centreon_metrics}->{$_}->{service_id}, + metric_name => $self->{centreon_metrics}->{$_}->{metric_name}, + predict => $result->[0]->{predict} + } + ]; + my ($status, $content) = $self->json_encode(argument => $data); + next if ($status == 1); + + my $encoded_content; + if (!IO::Compress::Bzip2::bzip2($content, \$encoded_content)) { + $self->{logger}->writeLogError('[anomalydetection] -class- cannot compress content: ' . $IO::Compress::Bzip2::Bzip2Error); + next; + } + + $encoded_content = MIME::Base64::encode_base64($encoded_content, ''); + + my $poller = $self->get_poller(instance => $self->{centreon_metrics}->{$_}->{instance_id}); + $self->send_internal_action( + action => 'COMMAND', + target => $self->{centreon_metrics}->{$_}->{instance_id}, + token => $options{token}, + data => { + content => [ { command => 'echo -n ' . $encoded_content . ' | bzcat -d | base64 -d > "' . $poller->{cfg_dir} . '/anomaly/' . $_ . '.json"' } ] + } + ); + + $engine_reload->{ $self->{centreon_metrics}->{$_}->{instance_id} } = 1; + + $query .= $query_append . + 'UPDATE mod_anomaly_service SET' . + ' saas_update_date = ' . time() . + ' WHERE `id` = ' . $_; + $query_append = ';'; + } + + return 0 if ($query eq ''); + + foreach (keys %$engine_reload) { + my $poller = $self->get_poller(instance => $_); + $self->{logger}->writeLogDebug('[anomalydetection] -class- reload process centengine ' . $_); + $self->send_internal_action( + action => 'COMMAND', + target => $_, + token => $options{token}, + data => { + content => [ { command => 'sudo ' . $poller->{engine_reload_command} } ] + } + ); + } + + my $status = $self->{class_object_centreon}->transaction_query(request => $query); + if ($status == -1) { + $self->{logger}->writeLogError('[anomalydetection] -class- database: cannot update predicts'); + return 1; } return 0; @@ -525,12 +568,13 @@ sub saas_get_predicts { sub action_saaspredict { my ($self, %options) = @_; + $self->{logger}->writeLogDebug('[anomalydetection] -class - start saaspredict'); $options{token} = $self->generate_token() if (!defined($options{token})); $self->send_log(code => gorgone::class::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action saaspredict proceed' }); - $self->saas_get_predicts(); + $self->saas_get_predicts(token => $options{token}); - $self->{logger}->writeLogDebug('[anomalydetection] Finish saaspredict'); + $self->{logger}->writeLogDebug('[anomalydetection] -class- finish saaspredict'); $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => { message => 'action saaspredict finished' }); return 0; } @@ -538,6 +582,7 @@ sub action_saaspredict { sub action_saasregister { my ($self, %options) = @_; + $self->{logger}->writeLogDebug('[anomalydetection] -class- start saasregister'); $options{token} = $self->generate_token() if (!defined($options{token})); $self->send_log(code => gorgone::class::module::ACTION_BEGIN, token => $options{token}, data => { message => 'action saasregister proceed' }); @@ -570,7 +615,7 @@ sub action_saasregister { return 1; } - $self->{logger}->writeLogDebug('[anomalydetection] Finish saasregister'); + $self->{logger}->writeLogDebug('[anomalydetection] -class- finish saasregister'); $self->send_log(code => $self->ACTION_FINISH_OK, token => $options{token}, data => { message => 'action saasregister finished' }); return 0; } From 26131953e36c11cfaeec7536b9a65d535c6ea7e2 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 25 Feb 2020 15:36:10 +0100 Subject: [PATCH 337/948] enh(anomaly): initial release --- gorgone/gorgone/modules/centreon/anomalydetection/class.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm index b99a428fd57..849408ccc34 100644 --- a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm +++ b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm @@ -515,7 +515,7 @@ sub saas_get_predicts { next if ($status == 1); my $encoded_content; - if (!IO::Compress::Bzip2::bzip2($content, \$encoded_content)) { + if (!IO::Compress::Bzip2::bzip2(\$content, \$encoded_content)) { $self->{logger}->writeLogError('[anomalydetection] -class- cannot compress content: ' . $IO::Compress::Bzip2::Bzip2Error); next; } @@ -528,7 +528,7 @@ sub saas_get_predicts { target => $self->{centreon_metrics}->{$_}->{instance_id}, token => $options{token}, data => { - content => [ { command => 'echo -n ' . $encoded_content . ' | bzcat -d | base64 -d > "' . $poller->{cfg_dir} . '/anomaly/' . $_ . '.json"' } ] + content => [ { command => 'mkdir -p ' . $poller->{cfg_dir} . '/anomaly/' . '; echo -n ' . $encoded_content . ' | base64 -d | bzcat -d > "' . $poller->{cfg_dir} . '/anomaly/' . $_ . '.json"' } ] } ); From 4f19ae00bcea8defb7eaa8c53724ba235a1c5d16 Mon Sep 17 00:00:00 2001 From: sc979 <34628915+sc979@users.noreply.github.com> Date: Thu, 27 Feb 2020 14:40:08 +0100 Subject: [PATCH 338/948] enh(core): update license --- gorgone/LICENSE.txt | 41 +++++++++++++++-------------------------- 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/gorgone/LICENSE.txt b/gorgone/LICENSE.txt index 261eeb9e9f8..1463fc641f6 100644 --- a/gorgone/LICENSE.txt +++ b/gorgone/LICENSE.txt @@ -1,4 +1,18 @@ - Apache License + Copyright 2020 - Centreon + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -174,28 +188,3 @@ of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. From 4af295baacf5d7aac1e073016d44cb74658bf10b Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 2 Mar 2020 17:49:11 +0100 Subject: [PATCH 339/948] enh(nodes): add ssh_username config --- gorgone/gorgone/modules/centreon/nodes/class.pm | 15 +++++++++++++-- gorgone/gorgone/modules/centreon/nodes/hooks.pm | 1 + 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/nodes/class.pm b/gorgone/gorgone/modules/centreon/nodes/class.pm index 9c6294cb517..e4471e25b47 100644 --- a/gorgone/gorgone/modules/centreon/nodes/class.pm +++ b/gorgone/gorgone/modules/centreon/nodes/class.pm @@ -164,9 +164,20 @@ sub action_nodesresync { $self->{register_nodes}->{$_->[0]} = 1; $register_temp->{$_->[0]} = 1; if ($_->[7] == 2) { - push @$register_nodes, { id => $_->[0], type => 'push_ssh', address => $_->[3], ssh_port => $_->[4] }; + push @$register_nodes, { + id => $_->[0], + type => 'push_ssh', + address => $_->[3], + ssh_port => $_->[4], + ssh_username => $self->{config}->{ssh_username} + }; } else { - push @$register_nodes, { id => $_->[0], type => 'push_zmq', address => $_->[3], port => $_->[4] }; + push @$register_nodes, { + id => $_->[0], + type => 'push_zmq', + address => $_->[3], + port => $_->[4] + }; } } diff --git a/gorgone/gorgone/modules/centreon/nodes/hooks.pm b/gorgone/gorgone/modules/centreon/nodes/hooks.pm index 8d8490f9090..d961293825e 100644 --- a/gorgone/gorgone/modules/centreon/nodes/hooks.pm +++ b/gorgone/gorgone/modules/centreon/nodes/hooks.pm @@ -45,6 +45,7 @@ sub register { $config_core = $options{config_core}; $config_db_centreon = $options{config_db_centreon}; $config->{resync_time} = defined($config->{resync_time}) && $config->{resync_time} =~ /(\d+)/ ? $1 : 600; + $config->{ssh_username} = defined($config->{ssh_username}) ? $config->{ssh_username} : 'centreon'; return (1, NAMESPACE, NAME, EVENTS); } From 30074f1f35451b016121d96098690bfd59c929d6 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 3 Mar 2020 10:51:51 +0100 Subject: [PATCH 340/948] enh(action): enh errors handling for command action --- gorgone/gorgone/modules/core/action/class.pm | 34 +++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index 491f7e4be76..1518492a56d 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -129,6 +129,8 @@ sub action_command { } ); + my $errors = 0; + foreach my $command (@{$options{data}->{content}}) { $self->send_log( socket => $options{socket_log}, @@ -170,13 +172,27 @@ sub action_command { } } ); + + if (defined($command->{continue_on_error}) && $command->{continue_on_error} eq 'false') { + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { + message => "commands processing has been interrupted because of error" + } + ); + return -1; + } + + $errors = 1; } else { $self->send_log( socket => $options{socket_log}, code => $self->ACTION_FINISH_OK, token => $options{token}, data => { - message => "command has finished", + message => "command has finished successfully", command => $command->{command}, metadata => $command->{metadata}, result => { @@ -191,16 +207,26 @@ sub action_command { } ); } - - return -1 if (defined($command->{continue_on_error}) && $command->{continue_on_error} eq 'false'); } + if ($errors) { + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { + message => "commands processing has finished with errors" + } + ); + return -1; + } + $self->send_log( socket => $options{socket_log}, code => $self->ACTION_FINISH_OK, token => $options{token}, data => { - message => "commands processing has finished" + message => "commands processing has finished successfully" } ); From 444eb05f325b3e3d663e2ff1af016a07aa38d3d0 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 3 Mar 2020 12:06:38 +0100 Subject: [PATCH 341/948] fix(action): fix boolean check --- gorgone/gorgone/modules/core/action/class.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index 1518492a56d..636c6541b83 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -173,7 +173,7 @@ sub action_command { } ); - if (defined($command->{continue_on_error}) && $command->{continue_on_error} eq 'false') { + if (defined($command->{continue_on_error}) && $command->{continue_on_error} == 0) { $self->send_log( socket => $options{socket_log}, code => $self->ACTION_FINISH_KO, From ea387c5d21352a13ef30b986d4513be8589f64d3 Mon Sep 17 00:00:00 2001 From: Matthieu Kermagoret Date: Tue, 3 Mar 2020 13:07:28 +0100 Subject: [PATCH 342/948] feat(packaging): migrate Centreon SSH keys when installing Gorgone. (#11) --- gorgone/packaging/centreon-gorgone.spectemplate | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 463ff5a9d7a..43866a645f2 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -106,6 +106,11 @@ rm -rf %{buildroot} %{_bindir}/getent passwd centreon-gorgone &>/dev/null && %{_sbindir}/usermod -a -G centreon-engine centreon-gorgone 2> /dev/null %{_bindir}/getent passwd centreon-gorgone &>/dev/null && %{_sbindir}/usermod -a -G centreon-broker centreon-gorgone 2> /dev/null %{_bindir}/getent passwd centreon-gorgone &>/dev/null && %{_sbindir}/usermod -a -G apache centreon-gorgone 2> /dev/null +if [ \( \! -d %{_localstatedir}/lib/centreon-gorgone/.ssh \) -a \( -d %{_localstatedir}/spool/centreon/.ssh \) ] ; then + %{__cp} -r %{_localstatedir}/spool/centreon/.ssh %{_localstatedir}/lib/centreon-gorgone/.ssh + %{__chown} -R centreon-gorgone:centreon-gorgone %{_localstatedir}/lib/centreon-gorgone/.ssh + %{__chmod} 600 %{_localstatedir}/lib/centreon-gorgone/.ssh/id_rsa +fi %post %systemd_post gorgoned.service || : From 6622f9e01568a0ee653734edc5003adfe84d16ce Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 3 Mar 2020 13:55:39 +0100 Subject: [PATCH 343/948] enh(engine): enh errors handling for command action --- .../gorgone/modules/centreon/engine/class.pm | 98 +++++++++++++++++-- 1 file changed, 91 insertions(+), 7 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/engine/class.pm b/gorgone/gorgone/modules/centreon/engine/class.pm index 63287eb52f9..f7e29ab6cf0 100644 --- a/gorgone/gorgone/modules/centreon/engine/class.pm +++ b/gorgone/gorgone/modules/centreon/engine/class.pm @@ -125,6 +125,8 @@ sub action_enginecommand { } ); + my $errors = 0; + foreach my $command (@{$options{data}->{content}}) { my $command_file = ''; if (defined($command->{command_file}) && $command->{command_file} ne '') { @@ -144,7 +146,21 @@ sub action_enginecommand { command => $command->{command} } ); - (defined($command->{continue_on_error}) && $command->{continue_on_error} eq 'false') ? return -1 : next; + + if (defined($command->{continue_on_error}) && $command->{continue_on_error} == 0) { + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { + message => "commands processing has been interrupted because of error" + } + ); + return -1; + } + + $errors = 1; + next; } if (! -e $command_file) { $self->{logger}->writeLogError("[engine] Command '$command->{command}' - command_file '$command_file' must exist"); @@ -157,7 +173,21 @@ sub action_enginecommand { command => $command->{command} } ); - (defined($command->{continue_on_error}) && $command->{continue_on_error} eq 'false') ? return -1 : next; + + if (defined($command->{continue_on_error}) && $command->{continue_on_error} == 0) { + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { + message => "commands processing has been interrupted because of error" + } + ); + return -1; + } + + $errors = 1; + next; } if (! -p $command_file) { $self->{logger}->writeLogError("[engine] Command '$command->{command}' - command_file '$command_file' must be a pipe file"); @@ -170,7 +200,21 @@ sub action_enginecommand { command => $command->{command} } ); - (defined($command->{continue_on_error}) && $command->{continue_on_error} eq 'false') ? return -1 : next; + + if (defined($command->{continue_on_error}) && $command->{continue_on_error} == 0) { + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { + message => "commands processing has been interrupted because of error" + } + ); + return -1; + } + + $errors = 1; + next; } if (! -w $command_file) { $self->{logger}->writeLogError("[engine] Command '$command->{command}' - command_file '$command_file' must be writeable"); @@ -183,7 +227,21 @@ sub action_enginecommand { command => $command->{command} } ); - (defined($command->{continue_on_error}) && $command->{continue_on_error} eq 'false') ? return -1 : next; + + if (defined($command->{continue_on_error}) && $command->{continue_on_error} == 0) { + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { + message => "commands processing has been interrupted because of error" + } + ); + return -1; + } + + $errors = 1; + next; } my $fh; @@ -207,7 +265,21 @@ sub action_enginecommand { command => $command->{command} } ); - (defined($command->{continue_on_error}) && $command->{continue_on_error} eq 'false') ? return -1 : next; + + if (defined($command->{continue_on_error}) && $command->{continue_on_error} == 0) { + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { + message => "commands processing has been interrupted because of error" + } + ); + return -1; + } + + $errors = 1; + next; } $self->send_log( @@ -220,13 +292,25 @@ sub action_enginecommand { } ); } - + + if ($errors) { + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { + message => "commands processing has finished with errors" + } + ); + return -1; + } + $self->send_log( socket => $options{socket_log}, code => $self->ACTION_FINISH_OK, token => $options{token}, data => { - message => "commands processing has finished" + message => "commands processing has finished successfully" } ); From 162b70011c9606c9afbac5cb781fece490cd8a65 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 3 Mar 2020 13:56:07 +0100 Subject: [PATCH 344/948] enh(proxy): update sshclient to match action and engine modules --- gorgone/gorgone/modules/core/proxy/class.pm | 36 +- .../gorgone/modules/core/proxy/sshclient.pm | 322 ++++++++++++++---- 2 files changed, 286 insertions(+), 72 deletions(-) diff --git a/gorgone/gorgone/modules/core/proxy/class.pm b/gorgone/gorgone/modules/core/proxy/class.pm index 6e46b61db28..432af84858f 100644 --- a/gorgone/gorgone/modules/core/proxy/class.pm +++ b/gorgone/gorgone/modules/core/proxy/class.pm @@ -332,20 +332,30 @@ sub proxy { target => $target ); - $connector->{logger}->writeLogDebug("[proxy] Sshclient return: [message = $data_ret->{message}]"); - if ($status == 0) { - $connector->send_log( - code => gorgone::class::module::ACTION_FINISH_OK, - token => $token, - data => $data_ret - ); + if (ref($data_ret) eq 'ARRAY') { + foreach (@{$data_ret}) { + $connector->send_log( + code => $_->{code}, + token => $token, + data => $_->{data} + ); + } } else { - $connector->send_log( - code => gorgone::class::module::ACTION_FINISH_KO, - token => $token, - data => $data_ret - ); - } + $connector->{logger}->writeLogDebug("[proxy] Sshclient return: [message = $data_ret->{message}]"); + if ($status == 0) { + $connector->send_log( + code => gorgone::class::module::ACTION_FINISH_OK, + token => $token, + data => $data_ret + ); + } else { + $connector->send_log( + code => gorgone::class::module::ACTION_FINISH_KO, + token => $token, + data => $data_ret + ); + } + } } } diff --git a/gorgone/gorgone/modules/core/proxy/sshclient.pm b/gorgone/gorgone/modules/core/proxy/sshclient.pm index 03ed3d7c91c..a0c1e2ecae2 100644 --- a/gorgone/gorgone/modules/core/proxy/sshclient.pm +++ b/gorgone/gorgone/modules/core/proxy/sshclient.pm @@ -143,89 +143,293 @@ sub action_centcore { sub action_command { my ($self, %options) = @_; - if (!defined($options{data}->{content}->{command}) || $options{data}->{content}->{command} eq '') { - $self->{logger}->writeLogError('[sshclient] Action command - Need command'); - return (-1, { message => 'please set command' }); + if (!defined($options{data}->{content}) || ref($options{data}->{content}) ne 'ARRAY') { + return (-1, { message => "expected array, found '" . ref($options{data}->{content}) . "'" }); } - if (defined($options{data}->{content}->{metadata}->{centcore_proxy}) && $options{target_direct} == 0) { - return $self->action_centcore( + my $index = 0; + foreach my $command (@{$options{data}->{content}}) { + if (!defined($command->{command}) || $command->{command} eq '') { + return (-1, { message => "need command argument at array index '" . $index . "'" }); + } + $index++; + } + + my $errors = 0; + my $results; + + push @{$results}, { + code => 0, + data => { + message => "commands processing has started", + request_content => $options{data}->{content} + } + }; + + foreach my $command (@{$options{data}->{content}}) { + my ($code, $data) = (0, {}); + + push @{$results}, { + code => 0, data => { - content => { - command => $options{data}->{content}->{metadata}->{centcore_cmd}, - target => $options{target}, + message => "command has started", + command => $command->{command}, + metadata => $command->{metadata} + } + }; + + if (defined($command->{metadata}->{centcore_proxy}) && $options{target_direct} == 0) { + ($code, $data->{data}) = $self->action_centcore( + data => { + content => { + command => $command->{metadata}->{centcore_cmd}, + target => $options{target}, + } } + ); + $data->{code} = ($code < 0 ) ? 1 : 2; + } else { + my $timeout = defined($command->{timeout}) && $command->{timeout} =~ /(\d+)/ ? $1 : 60; + my $timeout_nodata = defined($command->{timeout_nodata}) && $command->{timeout_nodata} =~ /(\d+)/ ? $1 : 30; + + my $start = time(); + my $ret = $self->execute_simple( + cmd => $command->{command}, + timeout => $timeout, + timeout_nodata => $timeout_nodata + ); + my $end = time(); + + $data = { + data => { + command => $command->{command}, + metadata => $command->{metadata}, + result => { + exit_code => $ret->{exit_code}, + stdout => $ret->{stdout}, + stderr => $ret->{stderr}, + }, + metrics => { + start => $start, + end => $end, + duration => $end - $start + } + } + }; + + if ($ret->{exit} == Libssh::Session::SSH_OK) { + $data->{data}->{message} = "command has finished successfully"; + $data->{code} = 2; + } elsif ($ret->{exit} == Libssh::Session::SSH_AGAIN) { # AGAIN means timeout + $code = -1; + $data->{data}->{message} = "command has timed out"; + $data->{code} = 1; + } else { + $code = -1; + $data->{data}->{message} = $self->error(GetErrorSession => 1); + $data->{code} = 1; } - ); + } + + push @{$results}, $data; + + if ($code < 0) { + if (defined($command->{continue_on_error}) && $command->{continue_on_error} == 0) { + push @{$results}, { + code => 1, + data => { + message => "commands processing has been interrupted because of error" + } + }; + return (-1, $results); + } + + $errors = 1; + } } - my $timeout = defined($options{data}->{content}->{timeout}) && $options{data}->{content}->{timeout} =~ /(\d+)/ ? $1 : 60; - my $timeout_nodata = defined($options{data}->{content}->{timeout_nodata}) && $options{data}->{content}->{timeout_nodata} =~ /(\d+)/ ? $1 : 30; - - my $ret = $self->execute_simple(cmd => $options{data}->{content}->{command}, timeout => $timeout, timeout_nodata => $timeout_nodata); - my ($code, $data) = (0, {}); - if ($ret->{exit} == Libssh::Session::SSH_OK) { - $data->{message} = "command '$options{data}->{content}->{command}' had finished successfuly"; - $data->{exit_code} = $ret->{exit_code}; - $data->{stdout} = $ret->{stdout}; - $data->{stderr} = $ret->{stderr}; - } elsif ($ret->{exit} == Libssh::Session::SSH_AGAIN) { # AGAIN means timeout - $code = -1; - $data->{message} = "command '$options{data}->{content}->{command}' had timeout"; - $data->{exit_code} = $ret->{exit_code}; - $data->{stdout} = $ret->{stdout}; - $data->{stderr} = $ret->{stderr}; - } else { - return (-1, { message => $self->error(GetErrorSession => 1) }); + if ($errors) { + push @{$results}, { + code => 1, + data => { + message => "commands processing has finished with errors" + } + }; + return (-1, $results); } - return ($code, $data); + push @{$results}, { + code => 2, + data => { + message => "commands processing has finished successfully" + } + }; + + return (0, $results); } sub action_enginecommand { my ($self, %options) = @_; - if (!defined($options{data}->{content}->{command}) || $options{data}->{content}->{command} eq '') { - $self->{logger}->writeLogError('[sshclient] Action engine command - Need command'); - return (-1, { message => 'please set command' }); + if (!defined($options{data}->{content}) || ref($options{data}->{content}) ne 'ARRAY') { + return (-1, { message => "expected array, found '" . ref($options{data}->{content}) . "'" }); } - if (!defined($options{data}->{content}->{command_file}) || $options{data}->{content}->{command_file} eq '') { - $self->{logger}->writeLogError('[sshclient] Action engine command - Need command_file'); - return (-1, { message => 'please set command_file' }); + + my $index = 0; + foreach my $command (@{$options{data}->{content}}) { + if (!defined($command->{command}) || $command->{command} eq '') { + return (-1, { message => "need command argument at array index '" . $index . "'" }); + } + if (!defined($command->{command_file}) || $command->{command_file} eq '') { + return (-1, { message => "need command_file argument at array index '" . $index . "'" }); + } + $index++; } - chomp $options{data}->{content}->{command}; - if ($options{target_direct} == 0) { - return $self->action_centcore( - data => { - content => { - command => 'EXTERNALCMD', - target => $options{target}, - param => $options{data}->{content}->{command}, + my $errors = 0; + my $results; + + push @{$results}, { + code => 0, + data => { + message => "commands processing has started", + request_content => $options{data}->{content} + } + }; + + foreach my $command (@{$options{data}->{content}}) { + my ($code, $data) = (0, {}); + + chomp $command->{command}; + if ($options{target_direct} == 0) { + ($code, $data->{data}) = $self->action_centcore( + data => { + content => { + command => 'EXTERNALCMD', + target => $options{target}, + param => $command->{command}, + } } + ); + $data->{code} = ($code < 0 ) ? 1 : 2; + } else { + my $ret = $self->{sftp}->stat_file(file => $command->{command_file}); + if (!defined($ret)) { + push @{$results}, { + code => 1, + data => { + message => "cannot stat file '$command->{command_file}': " . $self->{sftp}->get_msg_error() + } + }; + + if (defined($command->{continue_on_error}) && $command->{continue_on_error} == 0) { + push @{$results}, { + code => 1, + data => { + message => "commands processing has been interrupted because of error" + } + }; + return (-1, $results); + } + + $errors = 1; + next; } - ); - } - my $ret = $self->{sftp}->stat_file(file => $options{data}->{content}->{command_file}); - if (!defined($ret)) { - return (-1, { message => "cannot stat file '$options{data}->{content}->{command_file}': " . $self->{sftp}->get_msg_error() }); - } + if ($ret->{type} != SSH_FILEXFER_TYPE_SPECIAL) { + push @{$results}, { + code => 1, + data => { + message => "stat file '$command->{command_file}' is not a pipe file" + } + }; + + if (defined($command->{continue_on_error}) && $command->{continue_on_error} == 0) { + push @{$results}, { + code => 1, + data => { + message => "commands processing has been interrupted because of error" + } + }; + return (-1, $results); + } - if ($ret->{type} != SSH_FILEXFER_TYPE_SPECIAL) { - return (-1, { message => "stat file '$options{data}->{content}->{command_file}' is not a pipe file" }); - } + $errors = 1; + next; + } - my $file = $self->{sftp}->open(file => $options{data}->{content}->{command_file}, accesstype => O_WRONLY|O_APPEND); - if (!defined($file)) { - return (-1, { message => "cannot open stat file '$options{data}->{content}->{command_file}': " . $self->{sftp}->error() }); + my $file = $self->{sftp}->open(file => $command->{command_file}, accesstype => O_WRONLY|O_APPEND); + if (!defined($file)) { + push @{$results}, { + code => 1, + data => { + message => "cannot open stat file '$command->{command_file}': " . $self->{sftp}->error() + } + }; + + if (defined($command->{continue_on_error}) && $command->{continue_on_error} == 0) { + push @{$results}, { + code => 1, + data => { + message => "commands processing has been interrupted because of error" + } + }; + return (-1, $results); + } + + $errors = 1; + next; + } + if ($self->{sftp}->write(handle_file => $file, data => $command->{command} . "\n") != Libssh::Session::SSH_OK) { + push @{$results}, { + code => 1, + data => { + message => "cannot write stat file '$command->{command_file}': " . $self->{sftp}->error() + } + }; + + if (defined($command->{continue_on_error}) && $command->{continue_on_error} == 0) { + push @{$results}, { + code => 1, + data => { + message => "commands processing has been interrupted because of error" + } + }; + return (-1, $results); + } + + $errors = 1; + next; + } + } + + push @{$results}, { + code => 2, + data => { + message => "command has been submitted", + command => $command->{command} + } + }; } - if ($self->{sftp}->write(handle_file => $file, data => $options{data}->{content}->{command} . "\n") != Libssh::Session::SSH_OK) { - return (-1, { message => "cannot write stat file '$options{data}->{content}->{command_file}': " . $self->{sftp}->error() }); + + if ($errors) { + push @{$results}, { + code => 1, + data => { + message => "commands processing has finished with errors" + } + }; + return (-1, $results); } - $self->{logger}->writeLogDebug("[sshclient] Action engine command - '" . $options{data}->{content}->{command} . "' succeeded"); - return (0, { message => 'send enginecommand succeeded' }); + push @{$results}, { + code => 2, + data => { + message => "commands processing has finished successfully" + } + }; + + return (0, $results); } sub action_remotecopy { @@ -273,7 +477,7 @@ sub action_remotecopy { if (-d $options{data}->{content}->{source}) { ($code, $data) = $self->action_command( data => { - content => { command => "tar zxf $dst_sftp -C '" . $dst . "' ." } + content => [ { command => "tar zxf $dst_sftp -C '" . $dst . "' ." } ] }, ); return ($code, $data) if ($code == -1); From ee14075d7229aa0951fd7790498084d6f49c16bb Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 3 Mar 2020 15:23:39 +0100 Subject: [PATCH 345/948] enh(engine): change the way external commands are sent --- gorgone/docs/api/gorgone-openapi.yaml | 16 +- gorgone/docs/modules/centreon/engine.md | 25 +- .../gorgone/modules/centreon/engine/class.pm | 254 +++++------------- .../modules/centreon/legacycmd/class.pm | 12 +- .../gorgone/modules/core/proxy/sshclient.pm | 176 ++++-------- 5 files changed, 144 insertions(+), 339 deletions(-) diff --git a/gorgone/docs/api/gorgone-openapi.yaml b/gorgone/docs/api/gorgone-openapi.yaml index 79a31e9c4ad..db191f36660 100644 --- a/gorgone/docs/api/gorgone-openapi.yaml +++ b/gorgone/docs/api/gorgone-openapi.yaml @@ -995,22 +995,18 @@ components: required: - command EngineCommands: - type: array - items: - $ref: '#/components/schemas/EngineCommand' - EngineCommand: type: object properties: - command: - type: string - description: "External command" - example: "[653284380] SCHEDULE_SVC_CHECK;host1;service1;653284380" command_file: type: string description: "Path to the Centreon Engine command file pipe" example: "/var/lib/centreon-engine/rw/centengine.cmd" - required: - - command + command: + type: array + items: + type: string + description: "External command" + example: "[653284380] SCHEDULE_SVC_CHECK;host1;service1;653284380" AutodiscoveryTask: type: object properties: diff --git a/gorgone/docs/modules/centreon/engine.md b/gorgone/docs/modules/centreon/engine.md index 79e8ecf8847..c09baca074f 100644 --- a/gorgone/docs/modules/centreon/engine.md +++ b/gorgone/docs/modules/centreon/engine.md @@ -45,16 +45,16 @@ command_file: "/var/lib/centreon-engine/rw/centengine.cmd" | Key | Value | | :- | :- | -| command | External command (old-style format) | | command_file | Path to the Centreon Engine command file pipe | +| commands | Array of external commands (old-style format) | ```json -[ - { - "command": "", - "command_file": "" - } -] +{ + "command_file": "", + "commands": [ + "" + ] +} ``` #### Example @@ -63,9 +63,10 @@ command_file: "/var/lib/centreon-engine/rw/centengine.cmd" curl --request POST "https://hostname:8443/api/centreon/engine/command" \ --header "Accept: application/json" \ --header "Content-Type: application/json" \ - --data "[ - { - \"command\": \"[653284380] SCHEDULE_SVC_CHECK;host1;service1;653284380\" - } -]" + --data "{ + \"command_file\": \"/var/lib/centreon-engine/rw/centengine.cmd\", + \"commands\": [ + \"[653284380] SCHEDULE_SVC_CHECK;host1;service1;653284380\" + ] +}" ``` diff --git a/gorgone/gorgone/modules/centreon/engine/class.pm b/gorgone/gorgone/modules/centreon/engine/class.pm index f7e29ab6cf0..013c6e02d5b 100644 --- a/gorgone/gorgone/modules/centreon/engine/class.pm +++ b/gorgone/gorgone/modules/centreon/engine/class.pm @@ -83,36 +83,61 @@ sub class_handle_HUP { sub action_enginecommand { my ($self, %options) = @_; - - if (!defined($options{data}->{content}) || ref($options{data}->{content}) ne 'ARRAY') { + + my $command_file = ''; + if (defined($options{data}->{content}->{command_file}) && $options{data}->{content}->{command_file} ne '') { + $command_file = $options{data}->{content}->{command_file}; + } elsif (defined($self->{config}->{command_file}) && $self->{config}->{command_file} ne '') { + $command_file = $self->{config}->{command_file}; + } + + if (!defined($command_file) || $command_file eq '') { + $self->{logger}->writeLogError("[engine] Need command_file (config or call) argument"); + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { + message => "need command_file (config or call) argument" + } + ); + return -1; + } + if (! -e $command_file) { + $self->{logger}->writeLogError("[engine] Command file '$command_file' must exist"); $self->send_log( socket => $options{socket_log}, code => $self->ACTION_FINISH_KO, token => $options{token}, data => { - message => "expected array, found '" . ref($options{data}->{content}) . "'", - request_content => $options{data}->{content} + message => "command file '$command_file' must exist" } ); return -1; } - - my $index = 0; - foreach my $command (@{$options{data}->{content}}) { - if (!defined($command->{command}) || $command->{command} eq '') { - $self->{logger}->writeLogError("[engine] Need command argument at array index '" . $index . "'"); - $self->send_log( - socket => $options{socket_log}, - code => $self->ACTION_FINISH_KO, - token => $options{token}, - data => { - message => "need command argument at array index '" . $index . "'", - request_content => $options{data}->{content} - } - ); - return -1; - } - $index++; + if (! -p $command_file) { + $self->{logger}->writeLogError("[engine] Command file '$command_file' must be a pipe file"); + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { + message => "command file '$command_file' must be a pipe file" + } + ); + return -1; + } + if (! -w $command_file) { + $self->{logger}->writeLogError("[engine] Command file '$command_file' must be writeable"); + $self->send_log( + socket => $options{socket_log}, + code => $self->ACTION_FINISH_KO, + token => $options{token}, + data => { + message => "command file '$command_file' must be writeable" + } + ); + return -1; } $self->send_log( @@ -125,192 +150,49 @@ sub action_enginecommand { } ); - my $errors = 0; - - foreach my $command (@{$options{data}->{content}}) { - my $command_file = ''; - if (defined($command->{command_file}) && $command->{command_file} ne '') { - $command_file = $command->{command_file}; - } elsif (defined($self->{config}->{command_file}) && $self->{config}->{command_file} ne '') { - $command_file = $self->{config}->{command_file}; - } - - if (!defined($command_file) || $command_file eq '') { - $self->{logger}->writeLogError("[engine] Need command_file (config or call) argument"); - $self->send_log( - socket => $options{socket_log}, - code => $self->ACTION_FINISH_KO, - token => $options{token}, - data => { - message => "need command_file (config or call) argument", - command => $command->{command} - } - ); - - if (defined($command->{continue_on_error}) && $command->{continue_on_error} == 0) { - $self->send_log( - socket => $options{socket_log}, - code => $self->ACTION_FINISH_KO, - token => $options{token}, - data => { - message => "commands processing has been interrupted because of error" - } - ); - return -1; - } - - $errors = 1; - next; - } - if (! -e $command_file) { - $self->{logger}->writeLogError("[engine] Command '$command->{command}' - command_file '$command_file' must exist"); - $self->send_log( - socket => $options{socket_log}, - code => $self->ACTION_FINISH_KO, - token => $options{token}, - data => { - message => "command_file '$command_file' must exist", - command => $command->{command} - } - ); - - if (defined($command->{continue_on_error}) && $command->{continue_on_error} == 0) { - $self->send_log( - socket => $options{socket_log}, - code => $self->ACTION_FINISH_KO, - token => $options{token}, - data => { - message => "commands processing has been interrupted because of error" - } - ); - return -1; - } - - $errors = 1; - next; - } - if (! -p $command_file) { - $self->{logger}->writeLogError("[engine] Command '$command->{command}' - command_file '$command_file' must be a pipe file"); - $self->send_log( - socket => $options{socket_log}, - code => $self->ACTION_FINISH_KO, - token => $options{token}, - data => { - message => "command_file '$command_file' must be a pipe file", - command => $command->{command} - } - ); - - if (defined($command->{continue_on_error}) && $command->{continue_on_error} == 0) { - $self->send_log( - socket => $options{socket_log}, - code => $self->ACTION_FINISH_KO, - token => $options{token}, - data => { - message => "commands processing has been interrupted because of error" - } - ); - return -1; - } - - $errors = 1; - next; - } - if (! -w $command_file) { - $self->{logger}->writeLogError("[engine] Command '$command->{command}' - command_file '$command_file' must be writeable"); + my $fh; + eval { + local $SIG{ALRM} = sub { die 'Timeout command' }; + alarm $self->{timeout}; + open($fh, ">", $command_file) or die "cannot open '$command_file': $!"; + + foreach my $command (@{$options{data}->{content}->{commands}}) { + $self->{logger}->writeLogInfo("[engine] Processing external command '" . $command . "'"); + print $fh $command . "\n"; $self->send_log( socket => $options{socket_log}, - code => $self->ACTION_FINISH_KO, + code => $self->ACTION_FINISH_OK, token => $options{token}, data => { - message => "command_file '$command_file' must be writeable", - command => $command->{command} + message => "command has been submitted", + command => $command } ); - - if (defined($command->{continue_on_error}) && $command->{continue_on_error} == 0) { - $self->send_log( - socket => $options{socket_log}, - code => $self->ACTION_FINISH_KO, - token => $options{token}, - data => { - message => "commands processing has been interrupted because of error" - } - ); - return -1; - } - - $errors = 1; - next; } - my $fh; - eval { - local $SIG{ALRM} = sub { die 'Timeout command' }; - alarm $self->{timeout}; - open($fh, ">", $command_file) or die "cannot open '$command_file': $!"; - print $fh $command->{command} . "\n"; - close $fh; - alarm 0; - }; - if ($@) { - close $fh if (defined($fh)); - $self->{logger}->writeLogError("[engine] Submit engine command '$command->{command}' issue: $@"); - $self->send_log( - socket => $options{socket_log}, - code => $self->ACTION_FINISH_KO, - token => $options{token}, - data => { - message => "submit engine command issue: $@", - command => $command->{command} - } - ); - - if (defined($command->{continue_on_error}) && $command->{continue_on_error} == 0) { - $self->send_log( - socket => $options{socket_log}, - code => $self->ACTION_FINISH_KO, - token => $options{token}, - data => { - message => "commands processing has been interrupted because of error" - } - ); - return -1; - } - - $errors = 1; - next; - } - - $self->send_log( - socket => $options{socket_log}, - code => $self->ACTION_FINISH_OK, - token => $options{token}, - data => { - message => "command has been submitted", - command => $command->{command} - } - ); - } - - if ($errors) { + close $fh; + alarm 0; + }; + if ($@) { + close $fh if (defined($fh)); + $self->{logger}->writeLogError("[engine] Submit engine command issue: $@"); $self->send_log( socket => $options{socket_log}, code => $self->ACTION_FINISH_KO, token => $options{token}, data => { - message => "commands processing has finished with errors" + message => "submit engine command issue: $@" } ); - return -1; + return -1 } - + $self->send_log( socket => $options{socket_log}, code => $self->ACTION_FINISH_OK, token => $options{token}, data => { - message => "commands processing has finished successfully" + message => "commands processing has finished" } ); diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index f1cddc8ec02..17caff9a916 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -177,12 +177,12 @@ sub execute_cmd { target => $options{target}, token => $self->generate_token(), data => { - content => [ - { - command => $options{param}, - command_file => $self->{pollers}->{$options{target}}->{command_file}, - } - ] + content => { + command_file => $self->{pollers}->{$options{target}}->{command_file}, + commands => [ + $options{param} + ] + } }, ); } elsif ($options{cmd} eq 'SENDCFGFILE') { diff --git a/gorgone/gorgone/modules/core/proxy/sshclient.pm b/gorgone/gorgone/modules/core/proxy/sshclient.pm index a0c1e2ecae2..b91d2428ebd 100644 --- a/gorgone/gorgone/modules/core/proxy/sshclient.pm +++ b/gorgone/gorgone/modules/core/proxy/sshclient.pm @@ -271,161 +271,87 @@ sub action_command { sub action_enginecommand { my ($self, %options) = @_; - if (!defined($options{data}->{content}) || ref($options{data}->{content}) ne 'ARRAY') { - return (-1, { message => "expected array, found '" . ref($options{data}->{content}) . "'" }); - } - - my $index = 0; - foreach my $command (@{$options{data}->{content}}) { - if (!defined($command->{command}) || $command->{command} eq '') { - return (-1, { message => "need command argument at array index '" . $index . "'" }); - } - if (!defined($command->{command_file}) || $command->{command_file} eq '') { - return (-1, { message => "need command_file argument at array index '" . $index . "'" }); - } - $index++; - } - - my $errors = 0; my $results; - - push @{$results}, { - code => 0, - data => { - message => "commands processing has started", - request_content => $options{data}->{content} - } - }; - foreach my $command (@{$options{data}->{content}}) { - my ($code, $data) = (0, {}); - - chomp $command->{command}; - if ($options{target_direct} == 0) { - ($code, $data->{data}) = $self->action_centcore( + if ($options{target_direct} == 0) { + foreach my $command (@{$options{data}->{content}->{commands}}) { + chomp $command; + my $msg = "[sshclient] Handling command 'EXTERNALCMD'"; + $msg .= ", Target: '" . $options{target} . "'" if (defined($options{target})); + $msg .= ", Parameters: '" . $command . "'" if (defined($command)); + $self->{logger}->writeLogInfo($msg); + my ($code, $data) = $self->action_centcore( data => { content => { command => 'EXTERNALCMD', target => $options{target}, - param => $command->{command}, + param => $command, } } ); - $data->{code} = ($code < 0 ) ? 1 : 2; - } else { - my $ret = $self->{sftp}->stat_file(file => $command->{command_file}); - if (!defined($ret)) { - push @{$results}, { - code => 1, - data => { - message => "cannot stat file '$command->{command_file}': " . $self->{sftp}->get_msg_error() - } - }; - - if (defined($command->{continue_on_error}) && $command->{continue_on_error} == 0) { - push @{$results}, { - code => 1, - data => { - message => "commands processing has been interrupted because of error" - } - }; - return (-1, $results); - } + } + } else { + if (!defined($options{data}->{content}->{command_file}) || $options{data}->{content}->{command_file} eq '') { + $self->{logger}->writeLogError("[sshclient] Need command_file argument"); + return (-1, { message => "need command_file argument" }); + } - $errors = 1; - next; - } + my $command_file = $options{data}->{content}->{command_file}; - if ($ret->{type} != SSH_FILEXFER_TYPE_SPECIAL) { - push @{$results}, { - code => 1, - data => { - message => "stat file '$command->{command_file}' is not a pipe file" - } - }; + my $ret = $self->{sftp}->stat_file(file => $command_file); + if (!defined($ret)) { + $self->{logger}->writeLogError("[sshclient] Command file '$command_file' must exist"); + return (-1, { message => "command file '$command_file' must exist", error => $self->{sftp}->get_msg_error() }); + } - if (defined($command->{continue_on_error}) && $command->{continue_on_error} == 0) { - push @{$results}, { - code => 1, - data => { - message => "commands processing has been interrupted because of error" - } - }; - return (-1, $results); - } + if ($ret->{type} != SSH_FILEXFER_TYPE_SPECIAL) { + $self->{logger}->writeLogError("[sshclient] Command file '$command_file' must be a pipe file"); + return (-1, { message => "command file '$command_file' must be a pipe file" }); + } - $errors = 1; - next; + my $file = $self->{sftp}->open(file => $command_file, accesstype => O_WRONLY|O_APPEND); + if (!defined($file)) { + $self->{logger}->writeLogError("[sshclient] Cannot open command file '$command_file'"); + return (-1, { message => "cannot open command file '$command_file'", error => $self->{sftp}->error() }); + } + + push @{$results}, { + code => 0, + data => { + message => "commands processing has started", + request_content => $options{data}->{content} } + }; - my $file = $self->{sftp}->open(file => $command->{command_file}, accesstype => O_WRONLY|O_APPEND); - if (!defined($file)) { + foreach my $command (@{$options{data}->{content}->{commands}}) { + $self->{logger}->writeLogInfo("[sshclient] Processing external command '" . $command . "'"); + if ($self->{sftp}->write(handle_file => $file, data => $command . "\n") != Libssh::Session::SSH_OK) { + $self->{logger}->writeLogError("[sshclient] Command file '$command_file' must be writeable"); push @{$results}, { code => 1, data => { - message => "cannot open stat file '$command->{command_file}': " . $self->{sftp}->error() + message => "command file '$command_file' must be writeable", + error => $self->{sftp}->error() } }; - if (defined($command->{continue_on_error}) && $command->{continue_on_error} == 0) { - push @{$results}, { - code => 1, - data => { - message => "commands processing has been interrupted because of error" - } - }; - return (-1, $results); - } - - $errors = 1; - next; + return (-1, $results); } - if ($self->{sftp}->write(handle_file => $file, data => $command->{command} . "\n") != Libssh::Session::SSH_OK) { - push @{$results}, { - code => 1, - data => { - message => "cannot write stat file '$command->{command_file}': " . $self->{sftp}->error() - } - }; - if (defined($command->{continue_on_error}) && $command->{continue_on_error} == 0) { - push @{$results}, { - code => 1, - data => { - message => "commands processing has been interrupted because of error" - } - }; - return (-1, $results); + push @{$results}, { + code => 2, + data => { + message => "command has been submitted", + command => $command } - - $errors = 1; - next; - } + }; } - - push @{$results}, { - code => 2, - data => { - message => "command has been submitted", - command => $command->{command} - } - }; - } - - if ($errors) { - push @{$results}, { - code => 1, - data => { - message => "commands processing has finished with errors" - } - }; - return (-1, $results); } push @{$results}, { code => 2, data => { - message => "commands processing has finished successfully" + message => "commands processing has finished" } }; From 9cd42d727ae2644f45e02a385ec8786de7badb11 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Tue, 3 Mar 2020 23:24:02 +0100 Subject: [PATCH 346/948] enh(autodiscovery): refacto module --- .../modules/centreon/autodiscovery/class.pm | 341 ++++++++---------- .../modules/centreon/autodiscovery/hooks.pm | 7 +- 2 files changed, 158 insertions(+), 190 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm index 703f289e45a..52a7f2336a4 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm @@ -30,8 +30,8 @@ use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); use JSON::XS; use Time::HiRes; +use DateTime; -my %tasks; my %jobs; my %handlers = (TERM => {}, HUP => {}); my ($connector); @@ -45,10 +45,11 @@ sub new { $connector->{logger} = $options{logger}; $connector->{config} = $options{config}; $connector->{config_core} = $options{config_core}; + $connector->{config_db_centreon} = $options{config_db_centreon}; $connector->{stop} = 0; - $connector->{sync_time} = - (defined($options{config}->{sync_time}) && $options{config}->{sync_time} =~ /(\d+)/) ? $1 : 50; - $connector->{last_sync_time} = -1; + + $connector->{resync_time} = (defined($options{config}->{resync_time}) && $options{config}->{resync_time} =~ /(\d+)/) ? $1 : 15; + $connector->{last_resync_time} = -1; bless $connector, $class; $connector->set_signal_handlers(); @@ -87,198 +88,126 @@ sub class_handle_HUP { } } -sub action_adddiscoverytask { +sub action_adddiscoveryjob { my ($self, %options) = @_; $options{token} = $self->generate_token() if (!defined($options{token})); - my $id = 'autodiscovery_task_' . $self->generate_token(length => 12) if (!defined($options{data}->{content}->{id})); - - $self->{logger}->writeLogInfo("[autodiscovery] Add task '" . $id . "'"); - $self->send_internal_action( - action => 'COMMAND', - target => $options{data}->{content}->{target}, - token => $id, - data => { - content => [ - { - %{$options{data}->{content}}, - metadata => { - id => $id, - source => 'autodiscovery', - type => 'task', - }, - } - ] - } - ); - - $self->send_log( - code => $self->ACTION_FINISH_OK, - token => $options{token}, - data => { - id => $id - } - ); - - $tasks{$id} = { target => $options{data}->{content}->{target} }; + my $token = 'autodiscovery_' . $self->generate_token(length => 8); - return 0; -} - -sub action_getdiscoverytask { - my ($self, %options) = @_; - - $options{token} = $self->generate_token() if (!defined($options{token})); + # Scheduled + my $query = "UPDATE mod_host_disco_job " . + "SET generate_date = '" . $self->date . "', status = '0', duration = 0, discovered_items = 0, message = 'Scheduled' " . + "WHERE id = " . $options{data}->{content}->{job_id}; + my $status = $self->{class_object_centreon}->transaction_query(request => $query); + if ($status == -1) { + $self->{logger}->writeLogError('[autodiscovery] Failed to update job status'); + return 1; + } - if (!defined($options{data}->{variables}[0])) { - $self->{logger}->writeLogError("[autodiscovery] Need to specify job id"); - $self->send_log( - code => $self->ACTION_FINISH_KO, + if ($options{data}->{content}->{execution_mode} == 0) { + # Execute immediately + $self->action_launchdiscovery( + data => { + content => { + target => $options{data}->{content}->{target}, + command => $options{data}->{content}->{command}, + timeout => $options{data}->{content}->{timeout}, + job_id => $options{data}->{content}->{job_id}, + token => $token + } + } + ); + } else { + # Schedule with cron + $self->{logger}->writeLogInfo("[autodiscovery] Add cron '" . $token . "' for job '" . $options{data}->{content}->{job_id} . "'"); + my $definition = { + id => $token, + target => '1', + timespec => $options{data}->{content}->{timespec}, + action => 'LAUNCHDISCOVERY', + parameters => { + target => $options{data}->{content}->{target}, + command => $options{data}->{content}->{command}, + timeout => $options{data}->{content}->{timeout}, + job_id => $options{data}->{content}->{job_id}, + token => $token + }, + keep_token => 1, + }; + + $self->send_internal_action( + action => 'ADDCRON', token => $options{token}, data => { - message => 'need to specify job id' + content => [ $definition ], } ); - return 1; } - my $id = $options{data}->{variables}[0]; - - $self->send_log( - code => $self->ACTION_FINISH_OK, - token => $options{token}, - data => { - results => $tasks{$id}->{results} - } - ); - - return 0; -} - -sub action_adddiscoveryjob { - my ($self, %options) = @_; - - $options{token} = $self->generate_token() if (!defined($options{token})); - my $id = 'autodiscovery_job_' . $self->generate_token(length => 12) if (!defined($options{data}->{content}->{id})); - - $self->{logger}->writeLogInfo("[autodiscovery] Add job '" . $id . "'"); - my $definition = { - id => $id, - target => $options{data}->{content}->{target}, - timespec => $options{data}->{content}->{timespec}, - action => 'COMMAND', - parameters => [ - command => $options{data}->{content}->{command}, - timeout => $options{data}->{content}->{timeout}, - metadata => { - id => $id, - source => 'autodiscovery', - type => 'job', - } - ] - keep_token => 1, - }; - - $self->send_internal_action( - action => 'ADDCRON', - token => $options{token}, - data => { - content => [ $definition ], - } - ); $self->send_log( code => $self->ACTION_FINISH_OK, token => $options{token}, data => { - id => $id + id => $token } ); - $jobs{$id} = { target => $options{data}->{content}->{target} }; + $jobs{$token} = { target => $options{data}->{content}->{target} }; return 0; } -sub action_getdiscoveryjob { +sub action_launchdiscovery { my ($self, %options) = @_; - $options{token} = $self->generate_token() if (!defined($options{token})); + $self->{logger}->writeLogInfo("[autodiscovery] Launching discovery for job '" . $options{data}->{content}->{job_id} . "'"); - if (!defined($options{data}->{variables}[0])) { - $self->{logger}->writeLogError("[autodiscovery] Need to specify job id"); - $self->send_log( - code => $self->ACTION_FINISH_KO, - token => $options{token}, - data => { - message => 'need to specify job id' - } - ); - return 1; - } - my $id = $options{data}->{variables}[0]; - - $self->send_log( - code => $self->ACTION_FINISH_OK, - token => $options{token}, + $self->send_internal_action( + action => 'COMMAND', + target => $options{data}->{content}->{target}, + token => $options{data}->{content}->{token}, data => { - results => $jobs{$id}->{results} + content => [ + { + command => $options{data}->{content}->{command}, + timeout => $options{data}->{content}->{timeout}, + metadata => { + job_id => $options{data}->{content}->{job_id}, + source => 'autodiscovery' + }, + } + ] } ); - - return 0; -} - -sub action_syncdiscoverylogs { - my ($self, %options) = @_; - - $options{token} = $self->generate_token() if (!defined($options{token})); - $self->{logger}->writeLogDebug("[autodiscovery] -class- Discovery logs sync start"); - my %synced; - foreach my $id (keys %tasks) { - next if (!defined($tasks{$id}->{target}) || defined($synced{$tasks{$id}->{target}}) || - defined($tasks{$id}->{results})); - $self->send_internal_action( - action => 'GETLOG', - token => $options{token}, - target => $tasks{$id}->{target}, - data => {} - ); - $synced{$tasks{$id}->{target}} = 1; - } - foreach my $id (keys %jobs) { - next if (!defined($jobs{$id}->{target}) || defined($synced{$jobs{$id}->{target}})); - $self->send_internal_action( - action => 'GETLOG', - token => $options{token}, - target => $jobs{$id}->{target}, - data => {} - ); - $synced{$jobs{$id}->{target}} = 1; + # Running + my $query = "UPDATE mod_host_disco_job " . + "SET generate_date = '" . $self->date . "', status = '3', duration = 0, discovered_items = 0, message = '" . $options{data}->{content}->{token} . "' " . + "WHERE id = " . $options{data}->{content}->{job_id}; + my $status = $self->{class_object_centreon}->transaction_query(request => $query); + if ($status == -1) { + $self->{logger}->writeLogError('[autodiscovery] Failed to update job status'); + return 1; } - - return 0; } sub action_getdiscoveryresults { my ($self, %options) = @_; + + # List running jobs + my ($status, $data) = $self->{class_object_centreon}->custom_execute( + request => "SELECT id, message FROM mod_host_disco_job WHERE status = '3'", + mode => 1, + keys => 'id' + ); - foreach my $id (keys %tasks) { - next if (defined($tasks{$id}->{results})); - $self->{logger}->writeLogDebug("[autodiscovery] Get logs results for task '" . $id . "'"); + foreach my $job_id (keys %{$data}) { + $self->{logger}->writeLogDebug("[autodiscovery] Get logs results for job '" . $job_id . "'"); $self->send_internal_action( action => 'GETLOG', data => { - token => $id - } - ); - } - foreach my $id (keys %jobs) { - $self->{logger}->writeLogDebug("[autodiscovery] Get logs results for job '" . $id . "'"); - $self->send_internal_action( - action => 'GETLOG', - data => { - token => $id + token => $data->{$job_id}->{message}, + limit => 4 } ); } @@ -292,22 +221,76 @@ sub action_updatediscoveryresults { return if (!defined($options{data}->{data}->{action}) || $options{data}->{data}->{action} ne "getlog" && !defined($options{data}->{data}->{result})); - foreach my $message_id (sort keys %{$options{data}->{data}->{result}}) { - my $data = JSON::XS->new->utf8->decode($options{data}->{data}->{result}->{$message_id}->{data}); - next if (!defined($data->{exit_code}) || !defined($data->{metadata}->{id}) || + my ($exit_code, $stdout, $job_id); + + foreach my $message (@{$options{data}->{data}->{result}}) { + my $data = JSON::XS->new->utf8->decode($message->{data}); + next if (!defined($data->{result}->{exit_code}) || !defined($data->{metadata}->{job_id}) || !defined($data->{metadata}->{source}) || $data->{metadata}->{source} ne 'autodiscovery'); - if ($data->{metadata}->{type} eq 'task') { - $self->{logger}->writeLogInfo("[autodiscovery] Found result for task '" . $data->{metadata}->{id} . "'"); - $tasks{$data->{metadata}->{id}}->{results} = $data; - } elsif ($data->{metadata}->{type} eq 'job') { - $jobs{$data->{metadata}->{id}}->{results} = $data ; + $self->{logger}->writeLogInfo("[autodiscovery] Found result for job '" . $data->{metadata}->{job_id} . "'"); + + $exit_code = $data->{result}->{exit_code}; + $stdout = $data->{result}->{stdout}; + $job_id = $data->{metadata}->{job_id}; + } + + if ($exit_code == 0) { + my $result = JSON::XS->new->utf8->decode($stdout); + + # Finished + my $query = "UPDATE mod_host_disco_job SET generate_date = '" . $self->date . "', status = '1', duration = " . $result->{duration} .", " . + "discovered_items = " . $result->{discovered_items} .", message = 'Finished' " . + "WHERE id = " . $job_id; + my $status = $self->{class_object_centreon}->transaction_query(request => $query); + if ($status == -1) { + $self->{logger}->writeLogError('[autodiscovery] Failed to update job status'); + return 1; + } + + # Delete previous results + $query = "DELETE FROM mod_host_disco_host WHERE job_id = " . $job_id; + $status = $self->{class_object_centreon}->transaction_query(request => $query); + if ($status == -1) { + $self->{logger}->writeLogError('[autodiscovery] Failed to delete job hosts'); + return 1; + } + + # Add new results + my $values; + my $append = ''; + foreach my $host (@{$result->{results}}) { + $values .= $append . "(" . $job_id . ", 4, '" . JSON::XS->new->utf8->encode($host) ."')"; + $append = ', ' + } + + $query = "INSERT INTO mod_host_disco_host (job_id, mapping_id, data) VALUES " . $values; + $status = $self->{class_object_centreon}->transaction_query(request => $query); + if ($status == -1) { + $self->{logger}->writeLogError('[autodiscovery] Failed to insert job hosts'); + return 1; + } + } else { + # Failed + my $query = "UPDATE mod_host_disco_job " . + "SET generate_date = '" . $self->date . "', status = '2', duration = 0, discovered_items = 0, " . + "message = " . $self->{class_object_centreon}->quote(value => $stdout) . " " . + "WHERE id = " . $job_id; + my $status = $self->{class_object_centreon}->transaction_query(request => $query); + if ($status == -1) { + $self->{logger}->writeLogError('[autodiscovery] Failed to update job status'); + return 1; } } return 0; } +sub date { + my $dt = DateTime->now; + return $dt->ymd . ' ' . $dt->hms; +} + sub event { while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); @@ -335,7 +318,6 @@ sub event { sub run { my ($self, %options) = @_; - # Database creation. We stay in the loop still there is an error $self->{db_centreon} = gorgone::class::db->new( dsn => $self->{config_db_centreon}->{dsn}, user => $self->{config_db_centreon}->{username}, @@ -343,8 +325,8 @@ sub run { force => 2, logger => $self->{logger} ); - ##### Load objects ##### - $self->{class_object} = gorgone::class::sqlquery->new( + + $self->{class_object_centreon} = gorgone::class::sqlquery->new( logger => $self->{logger}, db_centreon => $self->{db_centreon} ); @@ -369,22 +351,6 @@ sub run { } ]; - $self->send_internal_action( - action => 'ADDCRON', - data => { - content => [ - { - id => 'autodiscovery_getdiscoveryresults', - target => undef, - timespec => '* * * * *', - action => 'GETDISCOVERYRESULTS', - parameters => {}, - keep_token => 1, - } - ] - } - ); - while (1) { # we try to do all we can my $rev = zmq_poll($self->{poll}, 5000); @@ -393,6 +359,11 @@ sub run { zmq_close($connector->{internal_socket}); exit(0); } + + if (time() - $self->{resync_time} > $self->{last_resync_time}) { + $self->{last_resync_time} = time(); + $self->action_getdiscoveryresults(); + } } } diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm b/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm index 2dd9a5131b0..2c0f193d65b 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm @@ -30,13 +30,10 @@ use constant NAMESPACE => 'centreon'; use constant NAME => 'autodiscovery'; use constant EVENTS => [ { event => 'AUTODISCOVERYREADY' }, - { event => 'SYNCDISCOVERYLOGS' }, + { event => 'ADDDISCOVERYJOB', uri => '/job', method => 'POST' }, + { event => 'LAUNCHDISCOVERY' }, { event => 'GETDISCOVERYRESULTS' }, { event => 'UPDATEDISCOVERYRESULTS' }, - { event => 'GETDISCOVERYJOB', uri => '/job', method => 'GET' }, - { event => 'ADDDISCOVERYJOB', uri => '/job', method => 'POST' }, - { event => 'GETDISCOVERYTASK', uri => '/task', method => 'GET' }, - { event => 'ADDDISCOVERYTASK', uri => '/task', method => 'POST' }, ]; my $config_core; From f4cf03740ba84d0840690fafa9a84f9744a50d07 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Thu, 5 Mar 2020 13:57:31 +0100 Subject: [PATCH 347/948] enh(autodiscovery): adapt code to new database schema --- .../modules/centreon/autodiscovery/class.pm | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm index 52a7f2336a4..b497fed8299 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm @@ -30,9 +30,7 @@ use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); use JSON::XS; use Time::HiRes; -use DateTime; -my %jobs; my %handlers = (TERM => {}, HUP => {}); my ($connector); @@ -96,8 +94,8 @@ sub action_adddiscoveryjob { # Scheduled my $query = "UPDATE mod_host_disco_job " . - "SET generate_date = '" . $self->date . "', status = '0', duration = 0, discovered_items = 0, message = 'Scheduled' " . - "WHERE id = " . $options{data}->{content}->{job_id}; + "SET status = '0', duration = '0', discovered_items = '0', message = 'Scheduled' " . + "WHERE id = '" . $options{data}->{content}->{job_id} . "'"; my $status = $self->{class_object_centreon}->transaction_query(request => $query); if ($status == -1) { $self->{logger}->writeLogError('[autodiscovery] Failed to update job status'); @@ -151,8 +149,6 @@ sub action_adddiscoveryjob { id => $token } ); - - $jobs{$token} = { target => $options{data}->{content}->{target} }; return 0; } @@ -182,8 +178,8 @@ sub action_launchdiscovery { # Running my $query = "UPDATE mod_host_disco_job " . - "SET generate_date = '" . $self->date . "', status = '3', duration = 0, discovered_items = 0, message = '" . $options{data}->{content}->{token} . "' " . - "WHERE id = " . $options{data}->{content}->{job_id}; + "SET status = '3', duration = '0', discovered_items = '0', token = '" . $options{data}->{content}->{token} . "' " . + "WHERE id = '" . $options{data}->{content}->{job_id} . "'"; my $status = $self->{class_object_centreon}->transaction_query(request => $query); if ($status == -1) { $self->{logger}->writeLogError('[autodiscovery] Failed to update job status'); @@ -196,17 +192,27 @@ sub action_getdiscoveryresults { # List running jobs my ($status, $data) = $self->{class_object_centreon}->custom_execute( - request => "SELECT id, message FROM mod_host_disco_job WHERE status = '3'", + request => "SELECT id, monitoring_server_id, token FROM mod_host_disco_job WHERE status = '3'", mode => 1, keys => 'id' ); + # Sync logs and try to retrieve results for each jobs foreach my $job_id (keys %{$data}) { $self->{logger}->writeLogDebug("[autodiscovery] Get logs results for job '" . $job_id . "'"); + + $self->send_internal_action( + target => $data->{$job_id}->{monitoring_server_id}, + action => 'GETLOG', + ); + + my $wait = (defined($self->{config}->{sync_wait})) ? $self->{config}->{sync_wait} : 1_000_000; + Time::HiRes::usleep($wait); + $self->send_internal_action( action => 'GETLOG', data => { - token => $data->{$job_id}->{message}, + token => $data->{$job_id}->{token}, limit => 4 } ); @@ -239,9 +245,9 @@ sub action_updatediscoveryresults { my $result = JSON::XS->new->utf8->decode($stdout); # Finished - my $query = "UPDATE mod_host_disco_job SET generate_date = '" . $self->date . "', status = '1', duration = " . $result->{duration} .", " . - "discovered_items = " . $result->{discovered_items} .", message = 'Finished' " . - "WHERE id = " . $job_id; + my $query = "UPDATE mod_host_disco_job SET status = '1', duration = '" . $result->{duration} ."', " . + "discovered_items = '" . $result->{discovered_items} ."', message = 'Finished' " . + "WHERE id = '" . $job_id ."'"; my $status = $self->{class_object_centreon}->transaction_query(request => $query); if ($status == -1) { $self->{logger}->writeLogError('[autodiscovery] Failed to update job status'); @@ -249,7 +255,7 @@ sub action_updatediscoveryresults { } # Delete previous results - $query = "DELETE FROM mod_host_disco_host WHERE job_id = " . $job_id; + $query = "DELETE FROM mod_host_disco_host WHERE job_id = '" . $job_id ."'"; $status = $self->{class_object_centreon}->transaction_query(request => $query); if ($status == -1) { $self->{logger}->writeLogError('[autodiscovery] Failed to delete job hosts'); @@ -260,11 +266,11 @@ sub action_updatediscoveryresults { my $values; my $append = ''; foreach my $host (@{$result->{results}}) { - $values .= $append . "(" . $job_id . ", 4, '" . JSON::XS->new->utf8->encode($host) ."')"; + $values .= $append . "('" . $job_id . "', '" . JSON::XS->new->utf8->encode($host) ."')"; $append = ', ' } - $query = "INSERT INTO mod_host_disco_host (job_id, mapping_id, data) VALUES " . $values; + $query = "INSERT INTO mod_host_disco_host (job_id, discovery_result) VALUES " . $values; $status = $self->{class_object_centreon}->transaction_query(request => $query); if ($status == -1) { $self->{logger}->writeLogError('[autodiscovery] Failed to insert job hosts'); @@ -273,9 +279,9 @@ sub action_updatediscoveryresults { } else { # Failed my $query = "UPDATE mod_host_disco_job " . - "SET generate_date = '" . $self->date . "', status = '2', duration = 0, discovered_items = 0, " . + "SET status = '2', duration = '0', discovered_items = '0', " . "message = " . $self->{class_object_centreon}->quote(value => $stdout) . " " . - "WHERE id = " . $job_id; + "WHERE id = '" . $job_id ."'"; my $status = $self->{class_object_centreon}->transaction_query(request => $query); if ($status == -1) { $self->{logger}->writeLogError('[autodiscovery] Failed to update job status'); @@ -286,11 +292,6 @@ sub action_updatediscoveryresults { return 0; } -sub date { - my $dt = DateTime->now; - return $dt->ymd . ' ' . $dt->hms; -} - sub event { while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); From 4a32dec3b3762d14498faa9deb67c89600733d39 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 6 Mar 2020 10:11:33 +0100 Subject: [PATCH 348/948] fix(packaging): change rights on config dir --- gorgone/packaging/centreon-gorgone.spectemplate | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 43866a645f2..fef9ed27e59 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -66,7 +66,8 @@ mkdir -p %{buildroot}/%{perl_vendorlib}/gorgone %{__cp} gorgoned %{buildroot}%{_bindir}/ %{__cp} contrib/gorgone_config_init.pl %{buildroot}%{_usr}/local/bin/ -%{__install} -d -m 0755 %{buildroot}%{_sysconfdir}/centreon-gorgone/config.d/ +%{__install} -d -m 0775 %{buildroot}%{_sysconfdir}/centreon-gorgone +%{__install} -d -m 0775 %{buildroot}%{_sysconfdir}/centreon-gorgone/config.d/ %{__cp} packaging/config.yaml %{buildroot}%{_sysconfdir}/centreon-gorgone/ %{__cp} packaging/centreon.yaml %{buildroot}%{_sysconfdir}/centreon-gorgone/config.d/30-centreon.yaml From f05566fb48c63cc4d0b7df5a953d8dcb0dff1875 Mon Sep 17 00:00:00 2001 From: Matthieu Kermagoret Date: Fri, 6 Mar 2020 12:13:52 +0100 Subject: [PATCH 349/948] feat(packaging): set 775 permissions on config.d directory and add apache to centreon-gorgone group. (#13) --- .../packaging/centreon-gorgone.spectemplate | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index fef9ed27e59..548a239080a 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -51,23 +51,23 @@ Requires: centreon-gorgone %install rm -rf %{buildroot} mkdir -p %{buildroot}/%{perl_vendorlib}/gorgone -%{__install} -d -m 0755 %{buildroot}%{_bindir} -%{__install} -d -m 0755 %{buildroot}%{_usr}/local/bin/ -%{__install} -d -m 0755 %{buildroot}%{_sysconfdir}/systemd/system/ -%{__install} -d -m 0775 %buildroot%{_localstatedir}/lib/centreon-gorgone -%{__install} -d -m 0775 %buildroot%{_localstatedir}/log/centreon-gorgone +%{__install} -d %{buildroot}%{_bindir} +%{__install} -d %{buildroot}%{_usr}/local/bin/ +%{__install} -d %{buildroot}%{_sysconfdir}/systemd/system/ +%{__install} -d %buildroot%{_localstatedir}/lib/centreon-gorgone +%{__install} -d %buildroot%{_localstatedir}/log/centreon-gorgone %{__cp} config/systemd/gorgoned-service %{buildroot}%{_sysconfdir}/systemd/system/gorgoned.service -%{__install} -d -m 0755 %{buildroot}%{_sysconfdir}/sysconfig +%{__install} -d %{buildroot}%{_sysconfdir}/sysconfig %{__cp} config/systemd/gorgoned-sysconfig %{buildroot}%{_sysconfdir}/sysconfig/gorgoned -%{__install} -d -m 0755 %{buildroot}%{_sysconfdir}/logrotate.d +%{__install} -d %{buildroot}%{_sysconfdir}/logrotate.d %{__cp} config/logrotate/gorgoned %{buildroot}%{_sysconfdir}/logrotate.d/gorgoned %{__cp} -R gorgone/* %{buildroot}/%{perl_vendorlib}/gorgone/ %{__cp} gorgoned %{buildroot}%{_bindir}/ %{__cp} contrib/gorgone_config_init.pl %{buildroot}%{_usr}/local/bin/ -%{__install} -d -m 0775 %{buildroot}%{_sysconfdir}/centreon-gorgone -%{__install} -d -m 0775 %{buildroot}%{_sysconfdir}/centreon-gorgone/config.d/ +%{__install} -d %{buildroot}%{_sysconfdir}/centreon-gorgone +%{__install} -d %{buildroot}%{_sysconfdir}/centreon-gorgone/config.d/ %{__cp} packaging/config.yaml %{buildroot}%{_sysconfdir}/centreon-gorgone/ %{__cp} packaging/centreon.yaml %{buildroot}%{_sysconfdir}/centreon-gorgone/config.d/30-centreon.yaml @@ -89,7 +89,7 @@ rm -rf %{buildroot} %defattr(-, centreon-gorgone, centreon-gorgone, -) %dir %{_sysconfdir}/centreon-gorgone -%dir %{_sysconfdir}/centreon-gorgone/config.d +%dir %attr(775, centreon-gorgone, centreon-gorgone) %{_sysconfdir}/centreon-gorgone/config.d %{_sysconfdir}/centreon-gorgone/config.yaml %{_localstatedir}/lib/centreon-gorgone %{_localstatedir}/log/centreon-gorgone @@ -107,6 +107,7 @@ rm -rf %{buildroot} %{_bindir}/getent passwd centreon-gorgone &>/dev/null && %{_sbindir}/usermod -a -G centreon-engine centreon-gorgone 2> /dev/null %{_bindir}/getent passwd centreon-gorgone &>/dev/null && %{_sbindir}/usermod -a -G centreon-broker centreon-gorgone 2> /dev/null %{_bindir}/getent passwd centreon-gorgone &>/dev/null && %{_sbindir}/usermod -a -G apache centreon-gorgone 2> /dev/null +%{_bindir}/getent passwd centreon-gorgone &>/dev/null && %{_sbindir}/usermod -a -G centreon-gorgone apache 2> /dev/null if [ \( \! -d %{_localstatedir}/lib/centreon-gorgone/.ssh \) -a \( -d %{_localstatedir}/spool/centreon/.ssh \) ] ; then %{__cp} -r %{_localstatedir}/spool/centreon/.ssh %{_localstatedir}/lib/centreon-gorgone/.ssh %{__chown} -R centreon-gorgone:centreon-gorgone %{_localstatedir}/lib/centreon-gorgone/.ssh From a5af7e026f731328023ad0a2840484727b2ea0e5 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 6 Mar 2020 18:09:19 +0100 Subject: [PATCH 350/948] fix(sshclient): english typo --- gorgone/gorgone/modules/core/proxy/sshclient.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/gorgone/modules/core/proxy/sshclient.pm b/gorgone/gorgone/modules/core/proxy/sshclient.pm index b91d2428ebd..f1852faa4a1 100644 --- a/gorgone/gorgone/modules/core/proxy/sshclient.pm +++ b/gorgone/gorgone/modules/core/proxy/sshclient.pm @@ -67,7 +67,7 @@ sub open_session { } } - $self->{logger}->writeLogInfo('[sshclient] Authentification succeed'); + $self->{logger}->writeLogInfo('[sshclient] Authentication succeed'); $self->{sftp} = Libssh::Sftp->new(session => $self); if (!defined($self->{sftp})) { From 97a49d20cca87398a40206235fe8bfe87b04bda0 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 9 Mar 2020 18:02:26 +0100 Subject: [PATCH 351/948] enh(core): add full default path to keys to avoid failure when using service --- gorgone/gorgone/class/core.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index bfb777aed24..4012fd86192 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -81,9 +81,9 @@ sub init_server_keys { $self->{config}->{configuration} = { gorgone => {} } if (!defined($self->{config}->{configuration}->{gorgone})); $self->{config}->{configuration}->{gorgone} = { gorgonecore => {} } if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore})); - $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{privkey} = 'keys/rsakey.priv.pem' + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{privkey} = '/var/lib/centreon-gorgone/.keys/rsakey.priv.pem' if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{privkey}) || $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{privkey} eq ''); - $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{pubkey} = 'keys/rsakey.pub.pem' + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{pubkey} = '/var/lib/centreon-gorgone/.keys/rsakey.pub.pem' if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{pubkey}) || $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{pubkey} eq ''); if (! -f $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{privkey} && ! -f $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{pubkey}) { From 59bfced39c0dc0cde9f3e3d204e3ebb86ceea3c6 Mon Sep 17 00:00:00 2001 From: sc979 <34628915+sc979@users.noreply.github.com> Date: Tue, 10 Mar 2020 13:23:21 +0100 Subject: [PATCH 352/948] enh(core): gorgone add a source install script (#12) * enh(core): gorgone install script * fix(core): remove daemon execution rigths * fix: modify default gorgone bin folder * fix: modify destination folder path --- gorgone/.gitignore | 5 + gorgone/LICENSE.txt | 2 +- gorgone/install.sh | 274 +++++++++ gorgone/sourceInstall/functions | 1027 +++++++++++++++++++++++++++++++ gorgone/sourceInstall/vars | 52 ++ 5 files changed, 1359 insertions(+), 1 deletion(-) create mode 100644 gorgone/.gitignore create mode 100755 gorgone/install.sh create mode 100755 gorgone/sourceInstall/functions create mode 100755 gorgone/sourceInstall/vars diff --git a/gorgone/.gitignore b/gorgone/.gitignore new file mode 100644 index 00000000000..33e72b73fd3 --- /dev/null +++ b/gorgone/.gitignore @@ -0,0 +1,5 @@ +## source script + +# temporary folder +log + diff --git a/gorgone/LICENSE.txt b/gorgone/LICENSE.txt index 1463fc641f6..dfbec9227fe 100644 --- a/gorgone/LICENSE.txt +++ b/gorgone/LICENSE.txt @@ -11,7 +11,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ diff --git a/gorgone/install.sh b/gorgone/install.sh new file mode 100755 index 00000000000..718999d9a9c --- /dev/null +++ b/gorgone/install.sh @@ -0,0 +1,274 @@ +#!/bin/bash +#---- +## @Synopsis Install Script for Centreon Gorgone module +## @Copyright Copyright 2008, Guillaume Watteeux +## @Copyright Copyright 2008-2020, Centreon +## @License GPL : http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt +#---- +## Centreon is developed with GPL Licence 2.0 +## Developed by : Julien Mathis - Romain Le Merlus +## +## 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, or (at your option) any later version. +## +## 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. +## +## For information : infos@centreon.com +# + +#---- +## define the available options and usage of the script +## @param the chosen option +#---- +display_help() { + echo + echo -e "Usage: $0 [option...]" + echo -e "\t-i\tRun the installation script\n" + exit 1 +} + +#---- +## Test if the file exists, is readable and not empty +## As the functions file has not been imported yet, we need to duplicate it here +## @param file +## @return 0 file exist +## @return 1 file does not exist +#---- +test_file() { + local file="$1" + if [ -r "$file" ] && [ -s "$file" ]; then + return 0 + else + echo -e "The file named '$file' is missing or empty\nPlease check your sources" + exit 1 + fi +} + +## define and check the current source location +BASE_DIR=$(dirname $0) +BASE_DIR=$( cd $BASE_DIR; pwd ) +export BASE_DIR + +if [ -z "${BASE_DIR#/}" ] ; then + echo -e "You shouldn't install Centreon from the root filesystem folder\nPlease move the sources to another folder" + exit 1 +fi + +## define, test and load functions and vars files +INSTALL_DIR="$BASE_DIR/sourceInstall" +export INSTALL_DIR +test_file "$INSTALL_DIR/functions" +test_file "$INSTALL_DIR/vars" +. $INSTALL_DIR/functions +. $INSTALL_DIR/vars + +## Valid if launched as root +if [ "${FORCE_NO_ROOT:-0}" -ne 0 ]; then + USERID=$(id -u) + if [ "$USERID" != "0" ]; then + echo -e "You must execute the script using root user" + exit 1 + fi +fi + +## get and check the chosen script's option +process_install="0" +silent_install="0" +if [ "$#" -eq 0 ] ; then + echo -e "Install : please select one option" + display_help + exit 1 +fi + +while getopts ":ih" options; do + case ${options} in + i ) silent_install="0" + process_install="1" + ;; + \?|h ) display_help; + exit 0 + ;; + * ) display_help ; + exit 1 + ;; + esac +done + +if [ "$process_install" -ne 1 ]; then + echo "Install : option not found" + exit 1 +fi; + +## init LOG_FILE +[ ! -d "$LOG_DIR" ] && mkdir -p "$LOG_DIR" +if [ -e "$LOG_FILE" ] ; then + mv "$LOG_FILE" "$LOG_FILE.`date +%Y%m%d-%H%M%S`" +fi +${CAT} << __EOL__ > "$LOG_FILE" +__EOL__ + +## Init GREP,CAT,SED,CHMOD,CHOWN variables +log "INFO" "Check mandatory binaries" +define_specific_binary_vars + +## display header banner +${CAT} << __EOT__ + +############################################################################### +# # +# # +# Centreon Gorgone daemon module # +# # +# # +############################################################################### + +__EOT__ + +## displaying the license +echo -e "\nPlease read the license.\\n\\tPress enter to continue." +read +tput clear +more "$BASE_DIR/LICENSE.txt" + +yes_no_default "Do you accept the license ?" +if [ "$?" -ne 0 ] ; then + echo_info "As you did not accept the license, we cannot continue." + log "INFO" "Installation aborted - License not accepted" + exit 1 +else + log "INFO" "Accepted the license" +fi + +## Test all binaries +BINARIES="rm cp mv ${CHMOD} ${CHOWN} echo more mkdir find ${GREP} ${CAT} ${SED}" + +line="------------------------------------------------------------------------" + +echo "$line" +echo -e "\tChecking all needed binaries" +echo "$line" + +binary_fail="0" +for binary in $BINARIES; do + if [ ! -e ${binary} ] ; then + pathfind "$binary" + if [ "$?" -eq 0 ] ; then + echo_success "${binary}" "$ok" + else + echo_failure "${binary}" "$fail" + log "ERROR" "\$binary not found in \$PATH" + binary_fail=1 + fi + else + echo_success "${binary}" "$ok" + fi +done + +## Script stop if one binary wasn't found +if [ "$binary_fail" -eq 1 ] ; then + echo_failure "Please check failing binary and retry" + echo -e "\tThe logs are available in this file :\n$LOG_FILE" + exit 1 +fi + +echo -e "\n$line" +echo -e "\tChecking the mandatory folders" +echo -e "$line" + +## check filesystem space +check_disk_space + +## define destination folders +allow_creation_of_missing_folders +locate_gorgone_logdir +locate_gorgone_varlib +locate_gorgone_etcdir +locate_gorgone_bindir +locate_gorgone_perldir +locate_cron_d +locate_logrotate_d +locate_system_d +locate_sysconfig + +echo "$line" +echo -e "\tChecking the required users" +echo "$line" + +## create gorgone user +check_gorgone_group +check_gorgone_user + +echo -e "\n$line" +echo -e "\tAdding Gorgone user to the mandatory folders" +echo -e "$line" + +change_rights "$GORGONE_USER" "$GORGONE_GROUP" "775" "$GORGONE_LOG" +change_rights "$GORGONE_USER" "$GORGONE_GROUP" "775" "$GORGONE_VARLIB" +change_rights "$GORGONE_USER" "$GORGONE_GROUP" "775" "$GORGONE_ETC" + +#---- +## installation of Gorgone files +#---- +echo "$line" +echo -e "\tInstalling Gorgone daemon" +echo "$line" + +## Copy the files in destination folders and modify rights +copy_and_modify_rights "$BASE_DIR/config/systemd" "gorgoned-service" "$SYSTEM_D" "gorgoned.service" "664" +copy_and_modify_rights "$BASE_DIR/config/systemd" "gorgoned-sysconfig" "$SYSCONFIG" "gorgoned" "664" +copy_and_modify_rights "$BASE_DIR/config/logrotate" "gorgoned" "$LOGROTATE_D" "gorgoned" "775" +copy_and_modify_rights "$BASE_DIR/packaging" "config.yaml" "$GORGONE_ETC" "config.yaml" "755" +copy_and_modify_rights "$BASE_DIR" "gorgoned" "$GORGONE_BINDIR" "gorgoned" "755" +copy_and_modify_rights "$BASE_DIR/contrib" "gorgone_config_init.pl" "$GORGONE_BINDIR" "gorgone_config_init.pl" "775" + +## Recursively copy perl files +cp -R "$BASE_DIR/gorgone" "$GORGONE_PERL" +${CHMOD} -R "775" "$GORGONE_PERL" +${CHOWN} -R "$GORGONE_USER.$GORGONE_GROUP" "$GORGONE_PERL" + +#---- +## starting the service +#---- +echo "$line" +echo -e "\tStarting gorgoned.service" +echo "$line" + +## check the OS and launch the service +foundOS="" +find_OS "foundOS" +install_init_service "gorgoned" + +## display footer banner +${CAT} << __EOT__ + +############################################################################### +# # +# Thanks for using Gorgone. # +# ----------------------- # +# # +# Please add the configuration in a file in the folder : # +# $GORGONE_ETC # +# # +# You can read the documentation available here : # +# https://github.com/centreon/centreon-gorgone/blob/master/README.md # +# # +# ------------------------------------------------------------------ # +# # +# Report bugs at https://github.com/centreon/centreon-gorgone/issues # +# # +# Contact : contact@centreon.com # +# http://www.centreon.com # +# # +# ----------------------- # +# For security issues, please read our security policy # +# https://github.com/centreon/centreon-gorgone/security/policy # +# # +############################################################################### + +__EOT__ +exit 0 diff --git a/gorgone/sourceInstall/functions b/gorgone/sourceInstall/functions new file mode 100755 index 00000000000..b78cfc1dc55 --- /dev/null +++ b/gorgone/sourceInstall/functions @@ -0,0 +1,1027 @@ +#!/bin/bash +#---- +## @Synopsis Functions needed in the installation script of Centreon Gorgone module +## @Copyright Copyright 2008, Guillaume Watteeux +## @Copyright Copyright 2008-2020, Centreon +## @License GPL : http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt +#---- +## Centreon is developed with GPL Licence 2.0 +## Developed by : Julien Mathis - Romain Le Merlus +## +## 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, or (at your option) any later version. +## +## 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. +## +## For information : infos@centreon.com +# + +## VARS +yes="y" +no="n" +ok="OK" +fail="FAIL" +passed="PASSED" +warning="WARNING" +critical="CRITICAL" +# Init binary to empty to use pathfind or manual define +GREP="" +CAT="" +SED="" +CHMOD="" +CHOWN="" + +## COLOR FUNCTIONS +RES_COL="60" +MOVE_TO_COL="\\033[${RES_COL}G" +SETCOLOR_INFO="\\033[1;38m" +SETCOLOR_SUCCESS="\\033[1;32m" +SETCOLOR_FAILURE="\\033[1;31m" +SETCOLOR_WARNING="\\033[1;33m" +SETCOLOR_NORMAL="\\033[0;39m" + +#---- +## make a question with yes/no possibility +## use "no" response by default +## @param message to print +## @param default response (default to no) +## @return 0 yes +## @return 1 no +#---- +yes_no_default() { + local message=$1 + local default=${2:-$no} + local res="not_define" + while [ "$res" != "$yes" ] && [ "$res" != "$no" ] && [ ! -z "$res" ] ; do + echo -e "\n$message\n[y/n], default to [$default]:" + echo -en "> " + read res + [ -z "$res" ] && res="$default" + done + if [ "$res" = "$yes" ] ; then + return 0 + else + return 1 + fi +} + +#---- +## print info message and add it to log file +## @param message info +## @param type info (ex: INFO, username...) +## @Stdout info message +## @Globals LOG_FILE +#---- +echo_info() { + echo -e "${1}${MOVE_TO_COL}${SETCOLOR_INFO}${2}${SETCOLOR_NORMAL}" + log "$1" "$2" +} + +#---- +## print success message +## add success message to log file +## @param message +## @param word to specify success (ex: OK) +## @Stdout success message +## @Globals LOG_FILE +#---- +echo_success() { + echo -e "${1}${MOVE_TO_COL}${SETCOLOR_SUCCESS}${2}${SETCOLOR_NORMAL}" + log "$1" "$2" +} + +#---- +## print failure message +## add failure message to log file +## @param message +## @param word to specify failure (ex: fail) +## @Stdout failure message +## @Globals LOG_FILE +#---- +echo_failure() { + echo -e "${1}${MOVE_TO_COL}${SETCOLOR_FAILURE}${2}${SETCOLOR_NORMAL}" + log "$1" "$2" +} + +#---- +## print passed message +## add passed message to log file +## @param message +## @param word to specify pass (ex: passed) +## @Stdout passed message +## @Globals LOG_FILE +#---- +echo_passed() { + echo -e "${1}${MOVE_TO_COL}${SETCOLOR_WARNING}${2}${SETCOLOR_NORMAL}" + log "$1" "$2" +} + +#---- +## print warning message +## add warning message to log file +## @param message +## @param word to specify warning (ex: warn) +## @Stdout warning message +## @Globals LOG_FILE +#---- +echo_warning() { + echo -e "${1}${MOVE_TO_COL}${SETCOLOR_WARNING}${2}${SETCOLOR_NORMAL}" + log "$1" "$2" +} + +#---- +## Trim whitespaces and tabulations +## @param string to trim +## @return string +#---- +trim() { + echo "$1" | sed 's/^[ \t]*\(.*\)[ \t]*$/\1/' +} + +#---- +## add message on log file +## @param type of message level (debug, info, ...) +## @param message +## @Globals LOG_FILE +#---- +log() { + local program="$0" + local type="$1" + shift + local message="$@" + echo -e "[$program] - $type: $message" >> $LOG_FILE +} + +#---- +## find in $PATH if binary exist +## @param file to test +## @return 0 found +## @return 1 not found +## @Globals PATH +#---- +pathfind() { + OLDIFS="$IFS" + IFS=: + for p in $PATH; do + if [ -x "$p/$*" ] ; then + IFS="$OLDIFS" + return 0 + fi + done + IFS="$OLDIFS" + return 1 +} + +#---- +## find in $PATH if binary exist and return dirname +## @param file to test +## @param global variable to set a result +## @return 0 found +## @return 1 not found +## @Globals PATH +#---- +pathfind_ret() { + local bin=$1 + local var_ref=$2 + local OLDIFS="$IFS" + IFS=: + for p in $PATH; do + if [ -x "$p/$bin" ] ; then + IFS="$OLDIFS" + eval $var_ref=$p + return 0 + fi + done + IFS="$OLDIFS" + return 1 +} + +#---- +## define a specific variables for grep,cat,sed,... binaries +## This functions was been use in first line on your script +## @return 0 All is't ok +## @return 1 problem with one variable +## @Globals GREP, CAT, SED, CHMOD, CHOWN +#---- +define_specific_binary_vars() { + local vars_bin="GREP CAT SED CHMOD CHOWN" + local var_bin_tolower="" + for var_bin in $vars_bin ; do + if [ -z $(eval echo \$$var_bin) ] ; then + var_bin_tolower="$(echo $var_bin | tr [:upper:] [:lower:])" + pathfind_ret "$var_bin_tolower" "$(echo -n $var_bin)" + if [ "$?" -eq 0 ] ; then + eval "$var_bin='$(eval echo \$$var_bin)/$var_bin_tolower'" + export $(echo $var_bin) + log "PATH" "$var_bin=$(eval echo \$$var_bin)" + else + return 1 + fi + fi + done + return 0 +} + +#---- +## Check space left before installing +## @return 0 Space ok +## @return 1 No Space left +## @Globals INSTALL_DIR +#---- +check_disk_space() { + local min_space="35584" + local free_space="" + local tmp_dir="" + dest_dir=$(dirname $INSTALL_DIR) + free_space=$(df -P $dest_dir | tail -1 | awk '{print $4}') + if [ "$free_space" -lt "$min_space" ] ; then + echo_failure "No space left on : $dest_dir (<$min_space Ko)" "$fail" + return 1 + else + return 0 + fi +} + +#---- +## print a message, test directory if answer is correct +## Loop if not a valid directory +## @param message +## @param default value (use "NO_DEFAULT" if not default value) +## @param global variable to set a result +## @return 0 end +#---- +answer_with_testdir() { + local message=$1 + local default=$2 + local var_ref=$3 + local res="not_defined" + local first=0 + while [ ! -d "$res" ] ; do + [ $first -eq 1 ] && echo_passed "$res is not a directory or does not exist" "$critical" + echo -e "\n$message" + [ "$default" != "NO_DEFAULT" ] && echo -e "default to [$default]" + echo -en "> " + read res + if [ -z "$res" ] ; then + [ $default != "NO_DEFAULT" ] && res=$default + fi + if [ -z ${res#/} ] ; then + echo_passed "You cannot select the filesystem root folder" + res="not_defined" + else + first=1 + fi + done + eval $var_ref=$res + return 0 +} + +#---- +## ask if the creation of missing folders should be automated or not +#---- +allow_creation_of_missing_folders() { + CREATION_ALLOWED=0 + yes_no_default "Do you want to be asked for confirmation before creating the missing folders ?" "$yes" + if [ "$?" -eq 1 ] ; then + CREATION_ALLOWED=1 + echo_warning "Ask confirmation before folder creation" "NO" + fi + export CREATION_ALLOWED +} + +#---- +## print a message, create directory with answer +## Loop if not a valid directory (slash...) +## @param message +## @param default value (use "NO_DEFAULT" if not default value) +## @param global variable to set a result +## @return 0 end +#---- +answer_with_createdir() { + local message=$1 + local default=$2 + local var_ref=$3 + local res="not_defined" + local first=0 + while [ ! -d "$res" ] ; do + [ $first -eq 1 ] && echo_passed "Directory $res does not exists." "$critical" + echo -e "\n$message" + [ "$default" != "NO_DEFAULT" ] && echo -e "default to [$default]" + echo -en "> " + read res + if [ -z "$res" ] ; then + [ "$default" != "NO_DEFAULT" ] && res=$default + fi + if [ -z "${res#/}" -o "$yes" = "$res" -o "$no" = "$res" ] ; then + echo_passed "You cannot select the filesystem root folder" + res="not_defined" + else + first=1 + if [ $CREATION_ALLOWED -eq 0 ] ; then + [ -d "$res" ] && break + yes_no_default "Do you want to create this directory ? [$res]" + fi + if [ $? -eq 0 ] || [ $CREATION_ALLOWED -eq 1 ] ; then + mkdir -p $res + log "INFO" "Creating $res" + elif [ $? -ne 0 ] ; then + echo_passed "Could not create directory." "$critical" + #continue + fi + fi + done + eval $var_ref=$res + return 0 +} + +#---- +## print a message, test if file exists +## Loop if not a valid file +## @param message +## @param default value (use "NO_DEFAULT" if not default value) +## @param global variable to set a result +## @return 0 end +#---- +answer_with_testfile() { + local message=$1 + local default=$2 + local var_ref=$3 + local res="not_define" + local first=0 + while [ ! -f "$res" ] ; do + [ $first -eq 1 ] && echo_passed "$res is not a valid file." "$critical" + echo -e "\n$message" + [ "$default" != "NO_DEFAULT" ] && echo -e "default to [$default]" + echo -en "> " + read res + if [ -z "$res" ] ; then + [ "$default" != "NO_DEFAULT" ] && res=$default + fi + first=1 + done + eval $var_ref=$res + return 0 +} + +#---- +## Test if file exist +## @param file +## @param variable to reset +## @return 0 file exist +## @return 1 file does not exist +#---- +testfile_clean() { + local file="$1" + local var="$2" + [ -z "$file" ] && return 1 + if [ ! -e "$file" ] ; then + eval $var="" + return 1 + else + return 0 + fi +} + +#---- +## Test if directory exist +## @param directory +## @param variable to reset +## @return 0 directory exist +## @return 1 directory does not exist +#---- +testdir_clean() { + local dir="$1" + local var="$2" + [ -z "$dir" ] && return 1 + if [ ! -d "$dir" ] ; then + eval $var="" + return 1 + else + return 0 + fi +} + +#---- +## Check equality of two arguments +## @param value1 +## @param value2 +#---- +test_answer() { + if [ ! -z $2 ] ; then + if [ $2 != "" ] ; then + eval $1=$2 + fi + fi +} + +#---- +## Try to create a new folder +## @param directory +## @return 0 on success +## @return 1 on failure +#---- +dir_test_create() { + local dirname="$1" + if [ ! -d "$1" ] ; then + mkdir -p "$dirname" + if [ $? -ne 0 ] ; then + echo_failure "Could not create folder $dirname" "$fail" + return 1 + fi + echo_success "Creating folder $dirname" "$ok" + fi + return 0 +} + +#---- +## Change rights on specific file/folders +#---- +change_rights() { + local user=$1 + local group=$2 + local rights=$3 + local target=$4 + + ${CHOWN} $user.$group $target + if [ $? -eq 0 ] ; then + echo_success "Modify owner of $target" "$ok" + else + echo_failure "Modify owner of $target" "$fail" + fi + + ${CHMOD} $rights $target + if [ $? -eq 0 ] ; then + echo_success "Modify rights of $target" "$ok" + else + echo_failure "Modify rights of $target" "$fail" + fi +} + +#---- +## Define where is Gorgone log folder +#---- +locate_gorgone_logdir() { + if [ -n "$GORGONE_LOG" ] ; then + dir_test_create "$GORGONE_LOG" + if [ $? -ne 0 ] ; then + GORGONE_LOG="" + fi + fi + if [ -z "$GORGONE_LOG" ] ; then + answer_with_createdir "Where is your Gorgone log folder" "$DEFAULT_GORGONE_LOG" "GORGONE_LOG" + echo_success "Path $GORGONE_LOG" "$ok" + fi + GORGONE_LOG=`trim ${GORGONE_LOG%/}` + export GORGONE_LOG + log "VALUE OF" "GORGONE_LOG folder = $GORGONE_LOG" +} + +#---- +## Define where is Gorgone database folder +#---- +locate_gorgone_varlib() { + if [ -n "$GORGONE_VARLIB" ] ; then + dir_test_create "$GORGONE_VARLIB" + if [ $? -ne 0 ] ; then + GORGONE_VARLIB="" + fi + fi + if [ -z "$GORGONE_VARLIB" ] ; then + answer_with_createdir "Where is your Gorgone database folder" "$DEFAULT_GORGONE_VARLIB" "GORGONE_VARLIB" + echo_success "Path $GORGONE_VARLIB" "$ok" + fi + GORGONE_VARLIB=`trim ${GORGONE_VARLIB%/}` + export GORGONE_VARLIB + log "VALUE OF" "GORGONE_VARLIB folder = $GORGONE_VARLIB" +} + + +#---- +## Define where is Gorgone config (etc) folder +#---- +locate_gorgone_etcdir() { + if [ -n "$GORGONE_ETC" ] ; then + dir_test_create "$GORGONE_ETC" + if [ $? -ne 0 ] ; then + GORGONE_ETC="" + fi + fi + if [ -z "$GORGONE_ETC" ] ; then + answer_with_createdir "Where is your Gorgone config (etc) folder" "$DEFAULT_GORGONE_ETC" "GORGONE_ETC" + echo_success "Path $GORGONE_ETC" "$ok" + fi + GORGONE_ETC=`trim ${GORGONE_ETC%/}` + export GORGONE_ETC + log "VALUE OF" "GORGONE_ETC folder = $GORGONE_ETC" + # create the sub-folder for specific config files + dir_test_create "$GORGONE_ETC/config.d" +} + +#---- +## Define where is Gorgone user's bin folder +#---- +locate_gorgone_bindir() { + if [ -n "$GORGONE_BINDIR" ] ; then + dir_test_create "$GORGONE_BINDIR" + if [ $? -ne 0 ] ; then + GORGONE_BINDIR="" + fi + fi + if [ -z "$GORGONE_BINDIR" ] ; then + answer_with_createdir "Where are your Gorgone user's folder" "$DEFAULT_GORGONE_BINDIR" "GORGONE_BINDIR" + echo_success "Path $GORGONE_BINDIR" "$ok" + fi + GORGONE_BINDIR=`trim ${GORGONE_BINDIR%/}` + export GORGONE_BINDIR + log "VALUE OF" "GORGONE_PERL folder = $GORGONE_BINDIR" +} + +#---- +## Define where is Gorgone perl folder +#---- +locate_gorgone_perldir() { + if [ -n "$GORGONE_PERL" ] ; then + dir_test_create "$GORGONE_PERL" + if [ $? -ne 0 ] ; then + GORGONE_PERL="" + fi + fi + if [ -z "$GORGONE_PERL" ] ; then + answer_with_createdir "Where are your Gorgone's perl files" "$DEFAULT_GORGONE_PERL" "GORGONE_PERL" + echo_success "Path $GORGONE_PERL" "$ok" + fi + GORGONE_PERL=`trim ${GORGONE_PERL%/}` + export GORGONE_PERL + log "VALUE OF" "GORGONE_PERL folder = $GORGONE_PERL" +} + +#---- +## Define where is perl binary +## find perl in PATH +#---- +locate_perl() { + testfile_clean "$BIN_PERL" "BIN_PERL" + if [ -z "$BIN_PERL" ] ; then + pathfind_ret "perl" "BIN_PERL" + if [ "$?" -ne 0 ] ; then + answer_with_testfile "Where is perl" "$DEFAULT_BIN_PERL" "BIN_PERL" + else + BIN_PERL="$BIN_PERL/perl" + fi + echo_success "$BIN_PERL" "$ok" + fi + BIN_PERL=${BIN_PERL%/} + export BIN_PERL + log "VALUE OF" "BIN_PERL = $BIN_PERL" +} + +#---- +## Define where is your cron.d directory +#---- +locate_cron_d() { + testdir_clean "$CRON_D" "CRON_D" + if [ -z "$CRON_D" ] ; then + if [ -d "/etc/cron.d" ] ; then + CRON_D="/etc/cron.d" + # Add most of init.d + else + answer_with_testdir "Where is your cron.d directory ?" "$DEFAULT_CRON_D" "CRON_D" + echo_success "Location $CRON_D" "$ok" + fi + fi + CRON_D=${CRON_D%/} + export CRON_D + log "VALUE OF" "CRON_D = $CRON_D" +} + +#---- +## Define where is your logrotate.d directory +## @Globals LOGROTATE_D, DEFAULT_LOGROTATE_D +#---- +locate_logrotate_d() { + testdir_clean "$LOGROTATE_D" "LOGROTATE_D" + if [ -z "$LOGROTATE_D" ] ; then + if [ -d "/etc/logrotate.d" ] ; then + LOGROTATE_D="/etc/logrotate.d" + # Add most of init.d + else + answer_with_testdir "Where is your logrotate.d folder ?" "$DEFAULT_LOGROTATE_D" "LOGROTATE_D" + echo_success "Path $LOGROTATE_D" "$ok" + fi + fi + LOGROTATE_D=`trim ${LOGROTATE_D%/}` + export LOGROTATE_D + log "VALUE OF" "LOGROTATE_D = $LOGROTATE_D" +} + +#---- +## Define where is your systemd folder +## @Globals SYSTEM_D, DEFAULT_SYSTEM_D +#---- +locate_system_d() { + testdir_clean "$SYSTEM_D" "SYSTEM_D" + if [ -z "$SYSTEM_D" ] ; then + if [ -d "/etc/systemd/system" ] ; then + SYSTEM_D="/etc/systemd/system" + # Add most of init.d + else + answer_with_testdir "Where is your systemd folder ?" "$DEFAULT_SYSTEM_D" "SYSTEM_D" + echo_success "Path $SYSTEM_D" "$ok" + fi + fi + SYSTEM_D=`trim ${SYSTEM_D%/}` + export SYSTEM_D + log "VALUE OF" "SYSTEM_D = $SYSTEM_D" +} + +#---- +## Define where is your sysconfig folder +## @Globals SYSCONFIG, DEFAULT_SYSCONFIG +#---- +locate_sysconfig() { + testdir_clean "$SYSCONFIG" "SYSCONFIG" + if [ -z "$SYSCONFIG" ] ; then + if [ -d "/etc/sysconfig" ] ; then + SYSCONFIG="/etc/sysconfig" + # Add most of init.d + else + answer_with_testdir "Where is your sysconfig folder ?" "$DEFAULT_SYSCONFIG" "SYSCONFIG" + echo_success "Path $SYSCONFIG" "$ok" + fi + fi + SYSCONFIG=`trim ${SYSCONFIG%/}` + export SYSCONFIG + log "VALUE OF" "SYSCONFIG = $SYSCONFIG" +} + +#---- +## Ask for centreon user, and create it if not exists +## @Globals GORGONE_GROUP, DEFAULT_GORGONE_GROUP +#---- +check_gorgone_group() +{ + if [ -n "$GORGONE_GROUP" ] ; then + group_test "$GORGONE_GROUP" + if [ $? -ne 0 ] ; then + group_create "$GORGONE_GROUP" + if [ $? -ne 0 ] ; then + GORGONE_GROUP="" + fi + fi + fi + if [ -z "$GORGONE_GROUP" ] ; then + answer_with_creategroup "What is the Gorgone group ?" "$DEFAULT_GORGONE_GROUP" "GORGONE_GROUP" + fi + log "VALUE OF" "GORGONE_GROUP = $GORGONE_GROUP" + return 0 +} + +group_test() { + grep "^$1:" /etc/group &>/dev/null + return $? +} + +group_create() { + local groupname="$1" + groupadd -r "$groupname" + if [ $? -ne 0 ] ; then + echo_failure "Could not create group $groupname" "$fail" + return 1 + fi + echo_success "Creating group $groupname" "$ok" + log "VALUE OF" "Creating group $groupname" + return 0 +} + +#---- +## print a message for get information with create a group system +## @param message +## @param default value (use "NO_DEFAULT" if not default value) +## @param global variable to set a result +## @return 0 end +#---- +answer_with_creategroup() { + local message=$1 + local default=$2 + local var_ref=$3 + local res="not_def" + local first=0 + local group_tested=1 + while [ "$group_tested" -ne 0 ] ; do + [ $first -eq 1 ] && echo_passed "The group $res does not exist" "$critical" + echo -e "\n$message" + [ "$default" != "NO_DEFAULT" ] && echo -e "default to [$default]" + echo -en "> " + read res + if [ -z "$res" ] ; then + [ "$default" != "NO_DEFAULT" ] && res=$default + fi + first=1 + if [ -n "$res" ] ; then + group_test "$res" + if [ $? -ne 0 ] ; then + if [ $CREATION_ALLOWED -eq 0 ] ; then + yes_no_default "Do you want to create this group ? [$res]" + fi + if [ $? -eq 0 ] || [ $CREATION_ALLOWED -eq 1 ] ; then + group_create "$res" + fi + fi + else + res="not_def" + fi + group_test "$res" + group_tested=$? + done + eval $var_ref=$res + return 0 +} + +#---- +## Ask for centreon user +## @Globals GORGONE_USER, DEFAULT_GORGONE_USER +#---- +check_gorgone_user() +{ + local groups="$GORGONE_GROUP" + local description="Gorgone user" + local home="$GORGONE_VARLIB" + if [ -n "$GORGONE_USER" ] ; then + user_test "$GORGONE_USER" + if [ $? -ne 0 ] ; then + user_create "$GORGONE_USER" "$groups" "$description" "$home" + if [ $? -ne 0 ] ; then + GORGONE_USER="" + fi + fi + fi + if [ -z "$GORGONE_USER" ] ; then + answer_with_createuser "What is the Gorgone user ?" "$DEFAULT_GORGONE_USER" "GORGONE_USER" "$groups" "$description" "$home" + fi + log "VALUE OF" "GORGONE_USER = $GORGONE_USER" + return 0 +} + +user_test() { + grep "^$1:" /etc/passwd &>/dev/null + return $? +} + +user_create() { + local username="$1" + local groupname="$2" + local description="$3" + local home="$4" + useradd -r -c "$description" -s "/bin/sh" -d "$home" -g "$groupname" "$username" + if [ $? -ne 0 ] ; then + echo_failure "Could not create user $username" "$fail" + return 1 + fi + echo_success "Creating user $username ($description)" "$ok" + log "INFO" "Creating user $username" + return 0 +} + +#---- +## print a message for get information with create a user system +## @param message +## @param default value (use "NO_DEFAULT" if not default value) +## @param global variable to set a result +## @return 0 end +#---- +answer_with_createuser() { + local message=$1 + local default=$2 + local var_ref=$3 + local groups="$4" + local description="$5" + local home="$6" + local res="not_def" + local first=0 + local user_tested=1 + while [ "$user_tested" -ne 0 ] ; do + [ $first -eq 1 ] && echo_passed "The user $res does not exist" "$critical" + echo -e "\n$message" + [ "$default" != "NO_DEFAULT" ] && echo -e "default to [$default]" + echo -en "> " + read res + if [ -z "$res" ] ; then + [ "$default" != "NO_DEFAULT" ] && res=$default + fi + first=1 + if [ -n "$res" ] ; then + user_test "$res" + if [ $? -ne 0 ] ; then + if [ $CREATION_ALLOWED -eq 0 ] ; then + yes_no_default "Do you want to create this user ? [$res]" + fi + if [ $? -eq 0 ] || [ $CREATION_ALLOWED -eq 1 ] ; then + user_create "$res" "$groups" "$description" "$home" + fi + fi + else + res="not_def" + fi + user_test "$res" + user_tested=$? + done + eval $var_ref=$res + return 0 +} + +#---- +## install init script on distrib +## use this fonction to install a init script on your system +## it call <@find_OS> to define install command +## @param name of service +## @return 0 install service ok +## @return 1 install service fail +#---- +install_init_service() { + # how to find methode to install in rc.d directory a correct link ? + # debian update-rc.d + # redhat chkconfig + # Suse chkconfig + # FreeBSD add ${service}_enable=YES in /etc/rc.conf + local service=$1 + + if [ -z $2 ]; then + OS="" + find_OS "OS" + else + OS=$2 + fi + + if [ "$OS" = "DEBIAN" ] ; then + systemctl start $service + systemctl enable $service + elif [ "$OS" = "SUSE" ] ; then + chkconfig --add $service + elif [ "$OS" = "REDHAT" ] ; then + systemctl start $service + systemctl enable $service + elif [ "$OS" = "FREEBSD" ] ; then + echo_info "You must configure your /etc/rc.conf with: ${service}_enable=YES" + else + echo_passed "Impossible to install your run level for $service" "$fail" + return 1 + fi + return 0 +} + +#---- +## Define OS +## check on etc to find a specific file

+## Debian, Suse, Redhat, FreeBSD +## @param variable to set a result +## @return 0 OS found +## @return 1 OS not found +#---- +find_OS() { + local distrib=$1 + local dist_found="" + local system="" + local lsb_release="" + system="$(uname -s)" + if [ "$system" = "Linux" ] ; then + if [ "$(pathfind lsb_release; echo $?)" -eq "0" ] ; then + lsb_release="$(lsb_release -i -s)" + else + lsb_release="NOT_FOUND" + fi + if [ "$lsb_release" = "Debian" ] || \ + [ "$lsb_release" = "Ubuntu" ] || \ + [ -e "/etc/debian_version" ] ; then + dist_found="DEBIAN" + log "INFO" "GNU/Linux Debian Distribution" + elif [ "$lsb_release" = "SUSE LINUX" ] || \ + [ -e "/etc/SuSE-release" ] ; then + dist_found="SUSE" + log "INFO" "GNU/Linux Suse Distribution" + elif [ "$lsb_release" = "RedHatEnterpriseES" ] || \ + [ "$lsb_release" = "Fedora" ] || \ + [ -e "/etc/redhat-release" ] ; then + dist_found="REDHAT" + log "INFO" "GNU/Linux Redhat Distribution" + else + dist_found="NOT_FOUND" + log "INFO" "GNU/Linux distribution not found" + return 1 + fi + elif [ "$system" = "FreeBSD" ] ; then + dist_found="FREEBSD" + log "INFO" "FreeBSD System" + elif [ "$system" = "AIX" ] ; then + dist_found="AIX" + log "INFO" "AIX System" + elif [ "$system" = "SunOS" ] ; then + dist_found="SUNOS" + log "INFO" "SunOS System" + else + dist_found="NOT_FOUND" + log "INFO" "System not found" + fi + + eval $distrib=$dist_found + return 0 +} + +#---- +## Check result and print a message +## @param return code to check +## @param message to print +#---- +check_result() { + local code=$1 + shift + local message=$@ + + if [ $code -eq 0 ] ; then + echo_success "$message" "$ok" + else + echo_failure "$message" "$fail" + fi + return 0 +} + +#---- +## Test if the file exists, is readable and not empty +## @param file +## @return 0 file exist +## @return 1 file does not exist +#---- +test_file() { + local file="$1" + if [ -r "$file" ] && [ -s "$file" ]; then + return 0 + else + echo -e "The file named '$file' is missing or empty\nPlease check your sources" + exit 1 + fi +} + +#---- +## Change rights and copy file +## @param origin path of the file +## @param source filename +## @param destination path +## @param destination filename +## @param chmod rights +## @Globals GORGONE_USER, GORGONE_GROUP +## return 0 is no errors +## return 1 on errors +#---- +copy_and_modify_rights() { + local origin=$1 + local src_name=$2 + local destination=$3 + local filename=$4 + local rights=$5 + + # check for empty source folder string + origin=`trim ${origin%/}` + if [ -z "$origin" ] ; then + echo_failure "Empty source folder defined for file : $src_name" "$fail" + return 1 + fi + + # check that the file exists in the source folder + file="$origin/$src_name" + test_file "$file" + [ $? -ne 0 ] && return 1 + + # check for empty destination folder string + destination=`trim ${destination%/}` + if [ -z "$origin" ] ; then + echo_failure "Empty destination folder defined for file : $src_name" "$fail" + return 1 + fi + dir_test_create "$destination" + [ $? -ne 0 ] && return 1 + + cp -f $file "$destination/$filename" + if [ $? -ne 0 ] ; then + echo_failure "Could not copy the file : $filename to : $destination" "$fail" + return 1 + fi + + ${CHMOD} "$rights" "$destination/$filename" + if [ $? -ne 0 ] ; then + echo_failure "Could not modify the rights of the file : $filename" "$fail" + return 1 + fi + + ${CHOWN} $GORGONE_USER.$GORGONE_GROUP "$destination/$filename" + if [ $? -ne 0 ] ; then + echo_failure "Could not modify the owner of the file : $filename" "$fail" + return 1 + fi + + echo_success "Modify rights on $filename" "$ok" + return 0 +} diff --git a/gorgone/sourceInstall/vars b/gorgone/sourceInstall/vars new file mode 100755 index 00000000000..a195f49ddbe --- /dev/null +++ b/gorgone/sourceInstall/vars @@ -0,0 +1,52 @@ +#!/bin/bash +#---- +## @Synopsis Installation variables needed for Centreon Gorgone module +## @Copyright Copyright 2008, Guillaume Watteeux +## @Copyright Copyright 2008-2020, Centreon +## @License GPL : http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt +#---- +## Centreon is developed with GPL Licence 2.0 +## Developed by : Julien Mathis - Romain Le Merlus +## +## 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, or (at your option) any later version. +## +## 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. +## +## For information : infos@centreon.com +# + +#---- +## install_vars +#---- + +# sources and log paths +LOG_DIR="$BASE_DIR/log" +LOG_FILE="$LOG_DIR/gorgone_install.log" + +# deployment paths +DEFAULT_GORGONE_LOG="/var/log/centreon-gorgone" +DEFAULT_GORGONE_VARLIB="/var/lib/centreon-gorgone" +DEFAULT_GORGONE_ETC="/etc/centreon-gorgone" +DEFAULT_GORGONE_CONF_FILE="/config.yaml" +DEFAULT_GORGONE_BINDIR="/usr/bin/" + +# perl related paths +DEFAULT_PERL_BIN="/usr/bin/perl" +DEFAULT_GORGONE_PERL=`eval "\`perl -V:installvendorlib\`"; echo $installvendorlib` + +# system tools paths +DEFAULT_INIT_D="/etc/init.d" +DEFAULT_CRON_D="/etc/cron.d" +DEFAULT_LOGROTATE_D="/etc/logrotate.d" +DEFAULT_SYSTEM_D="/etc/systemd/system" +DEFAULT_SYSCONFIG="/etc/sysconfig" + +# user and group +DEFAULT_GORGONE_USER="centreon-gorgone" +DEFAULT_GORGONE_GROUP="centreon-gorgone" From cf9ca2a35cc5b950f4bc8e8da5ff0a0f628b0b05 Mon Sep 17 00:00:00 2001 From: schapron Date: Tue, 10 Mar 2020 14:58:23 +0100 Subject: [PATCH 353/948] fix(core): correct typo in source script --- gorgone/install.sh | 2 +- gorgone/sourceInstall/functions | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/install.sh b/gorgone/install.sh index 718999d9a9c..ba37a7e018e 100755 --- a/gorgone/install.sh +++ b/gorgone/install.sh @@ -229,7 +229,7 @@ copy_and_modify_rights "$BASE_DIR/contrib" "gorgone_config_init.pl" "$GORGONE_BI ## Recursively copy perl files cp -R "$BASE_DIR/gorgone" "$GORGONE_PERL" ${CHMOD} -R "775" "$GORGONE_PERL" -${CHOWN} -R "$GORGONE_USER.$GORGONE_GROUP" "$GORGONE_PERL" +${CHOWN} -R "$GORGONE_USER:$GORGONE_GROUP" "$GORGONE_PERL" #---- ## starting the service diff --git a/gorgone/sourceInstall/functions b/gorgone/sourceInstall/functions index b78cfc1dc55..60b1da4624a 100755 --- a/gorgone/sourceInstall/functions +++ b/gorgone/sourceInstall/functions @@ -1016,7 +1016,7 @@ copy_and_modify_rights() { return 1 fi - ${CHOWN} $GORGONE_USER.$GORGONE_GROUP "$destination/$filename" + ${CHOWN} "$GORGONE_USER:$GORGONE_GROUP" "$destination/$filename" if [ $? -ne 0 ] ; then echo_failure "Could not modify the owner of the file : $filename" "$fail" return 1 From 10b0c6713cb15e6e3e856b014aa3a18f71d6a6a8 Mon Sep 17 00:00:00 2001 From: sc979 <34628915+sc979@users.noreply.github.com> Date: Wed, 11 Mar 2020 16:23:50 +0100 Subject: [PATCH 354/948] fix(core): correct source script (#15) * remove the recursive owner on perl folder * modiy owner of configuration sub folder * create user and group sooner * remove the service start as no conf * add the TODO tasks --- gorgone/install.sh | 28 +++++++++++++++++----------- gorgone/sourceInstall/functions | 12 +++++++++--- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/gorgone/install.sh b/gorgone/install.sh index ba37a7e018e..a5ede87f304 100755 --- a/gorgone/install.sh +++ b/gorgone/install.sh @@ -20,6 +20,10 @@ ## ## For information : infos@centreon.com # +# @TODO +# - add the silent mode install +# - add the upgrade mode +# - add the kill and clean system #---- ## define the available options and usage of the script @@ -176,6 +180,15 @@ if [ "$binary_fail" -eq 1 ] ; then exit 1 fi +echo "$line" +echo -e "\tChecking the required users" +echo "$line" + +## create gorgone user +check_gorgone_group +check_gorgone_user + + echo -e "\n$line" echo -e "\tChecking the mandatory folders" echo -e "$line" @@ -195,14 +208,6 @@ locate_logrotate_d locate_system_d locate_sysconfig -echo "$line" -echo -e "\tChecking the required users" -echo "$line" - -## create gorgone user -check_gorgone_group -check_gorgone_user - echo -e "\n$line" echo -e "\tAdding Gorgone user to the mandatory folders" echo -e "$line" @@ -228,8 +233,8 @@ copy_and_modify_rights "$BASE_DIR/contrib" "gorgone_config_init.pl" "$GORGONE_BI ## Recursively copy perl files cp -R "$BASE_DIR/gorgone" "$GORGONE_PERL" -${CHMOD} -R "775" "$GORGONE_PERL" -${CHOWN} -R "$GORGONE_USER:$GORGONE_GROUP" "$GORGONE_PERL" +${CHMOD} -R "775" "$GORGONE_PERL/gorgone" +${CHOWN} -R "$GORGONE_USER:$GORGONE_GROUP" "$GORGONE_PERL/gorgone" #---- ## starting the service @@ -252,7 +257,8 @@ ${CAT} << __EOT__ # ----------------------- # # # # Please add the configuration in a file in the folder : # -# $GORGONE_ETC # +# $GORGONE_ETC/config.d # +# Then start the gorgoned.service # # # # You can read the documentation available here : # # https://github.com/centreon/centreon-gorgone/blob/master/README.md # diff --git a/gorgone/sourceInstall/functions b/gorgone/sourceInstall/functions index 60b1da4624a..fec30d9f6ff 100755 --- a/gorgone/sourceInstall/functions +++ b/gorgone/sourceInstall/functions @@ -518,8 +518,16 @@ locate_gorgone_etcdir() { GORGONE_ETC=`trim ${GORGONE_ETC%/}` export GORGONE_ETC log "VALUE OF" "GORGONE_ETC folder = $GORGONE_ETC" + # create the sub-folder for specific config files dir_test_create "$GORGONE_ETC/config.d" + + # modify rights + ${CHOWN} -R "$GORGONE_USER:$GORGONE_GROUP" "$GORGONE_ETC" + if [ $? -ne 0 ] ; then + echo_failure "$(gettext "Cannot modify the owner of the files in $GORGONE_ETC folder")" "$fail" + return 1 + fi } #---- @@ -858,12 +866,10 @@ install_init_service() { fi if [ "$OS" = "DEBIAN" ] ; then - systemctl start $service systemctl enable $service elif [ "$OS" = "SUSE" ] ; then chkconfig --add $service elif [ "$OS" = "REDHAT" ] ; then - systemctl start $service systemctl enable $service elif [ "$OS" = "FREEBSD" ] ; then echo_info "You must configure your /etc/rc.conf with: ${service}_enable=YES" @@ -1022,6 +1028,6 @@ copy_and_modify_rights() { return 1 fi - echo_success "Modify rights on $filename" "$ok" + echo_success "Creating and adding rights on $filename" "$ok" return 0 } From 042b38e3c265a8ced7eb1ae16d3c99eaae845752 Mon Sep 17 00:00:00 2001 From: schapron Date: Thu, 12 Mar 2020 15:00:26 +0100 Subject: [PATCH 355/948] fix(source): correct unary operator error --- gorgone/install.sh | 2 +- gorgone/sourceInstall/functions | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gorgone/install.sh b/gorgone/install.sh index a5ede87f304..c51e8f916dc 100755 --- a/gorgone/install.sh +++ b/gorgone/install.sh @@ -185,6 +185,7 @@ echo -e "\tChecking the required users" echo "$line" ## create gorgone user +allow_creation_of_missing_resources check_gorgone_group check_gorgone_user @@ -197,7 +198,6 @@ echo -e "$line" check_disk_space ## define destination folders -allow_creation_of_missing_folders locate_gorgone_logdir locate_gorgone_varlib locate_gorgone_etcdir diff --git a/gorgone/sourceInstall/functions b/gorgone/sourceInstall/functions index fec30d9f6ff..5edfff32ab7 100755 --- a/gorgone/sourceInstall/functions +++ b/gorgone/sourceInstall/functions @@ -284,12 +284,12 @@ answer_with_testdir() { #---- ## ask if the creation of missing folders should be automated or not #---- -allow_creation_of_missing_folders() { +allow_creation_of_missing_resources() { CREATION_ALLOWED=0 - yes_no_default "Do you want to be asked for confirmation before creating the missing folders ?" "$yes" + yes_no_default "Do you want to be asked for confirmation before creating missing resources ?" "$yes" if [ "$?" -eq 1 ] ; then CREATION_ALLOWED=1 - echo_warning "Ask confirmation before folder creation" "NO" + echo_warning "Ask confirmation before creation" "NO" fi export CREATION_ALLOWED } From 14edcbfcfe0a6e4cc78b28214b1436878705724b Mon Sep 17 00:00:00 2001 From: schapron Date: Fri, 13 Mar 2020 15:17:49 +0100 Subject: [PATCH 356/948] fix(core): source install last issue --- gorgone/install.sh | 27 ++++++++++++++++----------- gorgone/sourceInstall/functions | 8 +------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/gorgone/install.sh b/gorgone/install.sh index c51e8f916dc..6156a2925cb 100755 --- a/gorgone/install.sh +++ b/gorgone/install.sh @@ -180,22 +180,13 @@ if [ "$binary_fail" -eq 1 ] ; then exit 1 fi -echo "$line" -echo -e "\tChecking the required users" -echo "$line" - -## create gorgone user -allow_creation_of_missing_resources -check_gorgone_group -check_gorgone_user - - echo -e "\n$line" echo -e "\tChecking the mandatory folders" echo -e "$line" ## check filesystem space check_disk_space +allow_creation_of_missing_resources ## define destination folders locate_gorgone_logdir @@ -208,13 +199,20 @@ locate_logrotate_d locate_system_d locate_sysconfig +echo "$line" +echo -e "\tChecking the required users" +echo "$line" + +## create gorgone user +check_gorgone_group +check_gorgone_user + echo -e "\n$line" echo -e "\tAdding Gorgone user to the mandatory folders" echo -e "$line" change_rights "$GORGONE_USER" "$GORGONE_GROUP" "775" "$GORGONE_LOG" change_rights "$GORGONE_USER" "$GORGONE_GROUP" "775" "$GORGONE_VARLIB" -change_rights "$GORGONE_USER" "$GORGONE_GROUP" "775" "$GORGONE_ETC" #---- ## installation of Gorgone files @@ -223,6 +221,13 @@ echo "$line" echo -e "\tInstalling Gorgone daemon" echo "$line" +# modify rights on etc folder +${CHMOD} -R "775" "$GORGONE_ETC" +${CHOWN} -R "$GORGONE_USER:$GORGONE_GROUP" "$GORGONE_ETC" +if [ $? -ne 0 ] ; then + echo_failure "$(gettext "Cannot modify the owner of the files in $GORGONE_ETC folder")" "$fail" +fi + ## Copy the files in destination folders and modify rights copy_and_modify_rights "$BASE_DIR/config/systemd" "gorgoned-service" "$SYSTEM_D" "gorgoned.service" "664" copy_and_modify_rights "$BASE_DIR/config/systemd" "gorgoned-sysconfig" "$SYSCONFIG" "gorgoned" "664" diff --git a/gorgone/sourceInstall/functions b/gorgone/sourceInstall/functions index 5edfff32ab7..89f87e30c03 100755 --- a/gorgone/sourceInstall/functions +++ b/gorgone/sourceInstall/functions @@ -521,13 +521,7 @@ locate_gorgone_etcdir() { # create the sub-folder for specific config files dir_test_create "$GORGONE_ETC/config.d" - - # modify rights - ${CHOWN} -R "$GORGONE_USER:$GORGONE_GROUP" "$GORGONE_ETC" - if [ $? -ne 0 ] ; then - echo_failure "$(gettext "Cannot modify the owner of the files in $GORGONE_ETC folder")" "$fail" - return 1 - fi + echo_success "Path $GORGONE_ETC/config.d" "$ok" } #---- From 033234b4a041a8500ebb2d72d5d7399f4b549a49 Mon Sep 17 00:00:00 2001 From: sc979 <34628915+sc979@users.noreply.github.com> Date: Wed, 18 Mar 2020 16:00:23 +0100 Subject: [PATCH 357/948] fix(core): fix sysconfig hardcoded path (#16) --- gorgone/install.sh | 3 +++ gorgone/sourceInstall/functions | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/gorgone/install.sh b/gorgone/install.sh index 6156a2925cb..a04a8103374 100755 --- a/gorgone/install.sh +++ b/gorgone/install.sh @@ -228,6 +228,9 @@ if [ $? -ne 0 ] ; then echo_failure "$(gettext "Cannot modify the owner of the files in $GORGONE_ETC folder")" "$fail" fi +# modify the gorgoned file to take in account the chosen user path +change_environment_file_path + ## Copy the files in destination folders and modify rights copy_and_modify_rights "$BASE_DIR/config/systemd" "gorgoned-service" "$SYSTEM_D" "gorgoned.service" "664" copy_and_modify_rights "$BASE_DIR/config/systemd" "gorgoned-sysconfig" "$SYSCONFIG" "gorgoned" "664" diff --git a/gorgone/sourceInstall/functions b/gorgone/sourceInstall/functions index 89f87e30c03..7a3e34d24fa 100755 --- a/gorgone/sourceInstall/functions +++ b/gorgone/sourceInstall/functions @@ -861,10 +861,12 @@ install_init_service() { if [ "$OS" = "DEBIAN" ] ; then systemctl enable $service + systemctl start $service elif [ "$OS" = "SUSE" ] ; then chkconfig --add $service elif [ "$OS" = "REDHAT" ] ; then systemctl enable $service + systemctl start $service elif [ "$OS" = "FREEBSD" ] ; then echo_info "You must configure your /etc/rc.conf with: ${service}_enable=YES" else @@ -874,6 +876,15 @@ install_init_service() { return 0 } +#---- +## Change gorgone service "environment file" value +## replace the default sysconfig path with the chosen custom path +## @Globals DEFAULT_SYSCONFIG, SYSCONFIG +#---- +change_environment_file_path() { + sed -i -e "s,$DEFAULT_SYSCONFIG,$SYSCONFIG,g" "$BASE_DIR/config/systemd/gorgoned-service" +} + #---- ## Define OS ## check on etc to find a specific file

From 2d67444129961d8a87466bbd46008c7e138f6601 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 20 Mar 2020 09:48:32 +0100 Subject: [PATCH 358/948] fix(contrib): fix init script --- gorgone/contrib/gorgone_config_init.pl | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/gorgone/contrib/gorgone_config_init.pl b/gorgone/contrib/gorgone_config_init.pl index 30a957d76f9..c0d374b9485 100644 --- a/gorgone/contrib/gorgone_config_init.pl +++ b/gorgone/contrib/gorgone_config_init.pl @@ -103,7 +103,7 @@ sub write_gorgone_config { modules: - name: httpserver package: gorgone::modules::core::httpserver::hooks - enable: false + enable: true address: 0.0.0.0 port: 8085 ssl: false @@ -116,7 +116,7 @@ sub write_gorgone_config { - name: cron package: gorgone::modules::core::cron::hooks - enable: false + enable: true - name: action package: gorgone::modules::core::action::hooks @@ -126,13 +126,13 @@ sub write_gorgone_config { package: gorgone::modules::core::proxy::hooks enable: true - - name: pollers + - name: nodes package: gorgone::modules::centreon::nodes::hooks enable: true - name: broker package: gorgone::modules::centreon::broker::hooks - enable: false + enable: true cache_dir: "/var/lib/centreon/broker-stats/" cron: - id: broker_stats @@ -148,6 +148,11 @@ sub write_gorgone_config { cache_dir: "$centreon_config->{CacheDir}" cache_dir_trap: "/etc/snmp/centreon_traps/" remote_dir: "$centreon_config->{CacheDir}/config/remote-data/" + + - name: engine + package: gorgone::modules::centreon::engine::hooks + enable: true + command_file: "/var/lib/centreon-engine/rw/centengine.cmd" END_FILE print $fh $content; From b0af15d01592d6779ea74058a1b8faa628e35c48 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 23 Mar 2020 14:09:53 +0100 Subject: [PATCH 359/948] fix(packaging): test apache user before add in group --- gorgone/packaging/centreon-gorgone.spectemplate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 548a239080a..1c26a49720a 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -107,7 +107,7 @@ rm -rf %{buildroot} %{_bindir}/getent passwd centreon-gorgone &>/dev/null && %{_sbindir}/usermod -a -G centreon-engine centreon-gorgone 2> /dev/null %{_bindir}/getent passwd centreon-gorgone &>/dev/null && %{_sbindir}/usermod -a -G centreon-broker centreon-gorgone 2> /dev/null %{_bindir}/getent passwd centreon-gorgone &>/dev/null && %{_sbindir}/usermod -a -G apache centreon-gorgone 2> /dev/null -%{_bindir}/getent passwd centreon-gorgone &>/dev/null && %{_sbindir}/usermod -a -G centreon-gorgone apache 2> /dev/null +%{_bindir}/getent passwd apache&>/dev/null && %{_sbindir}/usermod -a -G centreon-gorgone apache 2> /dev/null if [ \( \! -d %{_localstatedir}/lib/centreon-gorgone/.ssh \) -a \( -d %{_localstatedir}/spool/centreon/.ssh \) ] ; then %{__cp} -r %{_localstatedir}/spool/centreon/.ssh %{_localstatedir}/lib/centreon-gorgone/.ssh %{__chown} -R centreon-gorgone:centreon-gorgone %{_localstatedir}/lib/centreon-gorgone/.ssh From 8c9df2b5563d458d4986f91228aa17b545caddb9 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 23 Mar 2020 14:11:00 +0100 Subject: [PATCH 360/948] fix(packaging): missing space --- gorgone/packaging/centreon-gorgone.spectemplate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 1c26a49720a..e058cc96096 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -107,7 +107,7 @@ rm -rf %{buildroot} %{_bindir}/getent passwd centreon-gorgone &>/dev/null && %{_sbindir}/usermod -a -G centreon-engine centreon-gorgone 2> /dev/null %{_bindir}/getent passwd centreon-gorgone &>/dev/null && %{_sbindir}/usermod -a -G centreon-broker centreon-gorgone 2> /dev/null %{_bindir}/getent passwd centreon-gorgone &>/dev/null && %{_sbindir}/usermod -a -G apache centreon-gorgone 2> /dev/null -%{_bindir}/getent passwd apache&>/dev/null && %{_sbindir}/usermod -a -G centreon-gorgone apache 2> /dev/null +%{_bindir}/getent passwd apache &>/dev/null && %{_sbindir}/usermod -a -G centreon-gorgone apache 2> /dev/null if [ \( \! -d %{_localstatedir}/lib/centreon-gorgone/.ssh \) -a \( -d %{_localstatedir}/spool/centreon/.ssh \) ] ; then %{__cp} -r %{_localstatedir}/spool/centreon/.ssh %{_localstatedir}/lib/centreon-gorgone/.ssh %{__chown} -R centreon-gorgone:centreon-gorgone %{_localstatedir}/lib/centreon-gorgone/.ssh From 696d34f4962293fc588bb126f85a8f08968c532d Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 23 Mar 2020 17:52:29 +0100 Subject: [PATCH 361/948] fix(action): route logs correctly, add classic logs --- gorgone/gorgone/modules/core/action/class.pm | 23 ++++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index 636c6541b83..ea7c25b0d1c 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -238,8 +238,7 @@ sub action_processcopy { if (!defined($options{data}->{content}) || $options{data}->{content} eq '') { $self->send_log( - socket => $options{socket_log}, - code => $self->ACTION_FINISH_KO, + code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'no content' } ); @@ -258,13 +257,13 @@ sub action_processcopy { close FH; $self->send_log( - socket => $options{socket_log}, - code => $self->ACTION_FINISH_OK, + code => gorgone::class::module::ACTION_FINISH_OK, token => $options{token}, data => { message => "process copy inprogress", } ); + $self->{logger}->writeLogInfo("[action] Copy processing - Received chunk for '" . $options{data}->{content}->{destination} . "'"); return 0; } elsif ($options{data}->{content}->{status} eq "end" && defined($options{data}->{content}->{md5})) { if ($options{data}->{content}->{md5} eq file_md5_hex($cache_file)) { @@ -280,20 +279,20 @@ sub action_processcopy { ); if ($error <= -1000) { $self->send_log( - socket => $options{socket_log}, - code => $self->ACTION_FINISH_KO, + code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => "untar failed: $stdout" } ); + $self->{logger}->writeLogError('[action] Copy processing - Untar failed: ' . $stdout); return -1; } if ($exit_code != 0) { $self->send_log( - socket => $options{socket_log}, - code => $self->ACTION_FINISH_KO, + code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => "untar failed ($exit_code): $stdout" } ); + $self->{logger}->writeLogError('[action] Copy processing - Untar failed: ' . $stdout); return -1; } } elsif ($options{data}->{content}->{type} eq "regular") { @@ -304,11 +303,11 @@ sub action_processcopy { } } else { $self->send_log( - socket => $options{socket_log}, - code => $self->ACTION_FINISH_KO, + code => gorgone::class::module::ACTION_FINISH_KO, token => $options{token}, data => { message => 'md5 does not match' } ); + $self->{logger}->writeLogError('[action] Copy processing - MD5 does not match'); return -1; } } @@ -316,13 +315,13 @@ sub action_processcopy { unlink($cache_file); $self->send_log( - socket => $options{socket_log}, - code => $self->ACTION_FINISH_OK, + code => gorgone::class::module::ACTION_FINISH_OK, token => $options{token}, data => { message => "process copy finished successfully", } ); + $self->{logger}->writeLogInfo("[action] Copy processing - Copy to '" . $options{data}->{content}->{destination} . "' finished successfully"); return 0; } From f69f07b7512e408f362e4c610f3d796a91ea1e95 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 23 Mar 2020 22:25:06 +0100 Subject: [PATCH 362/948] fix(core): change setcoreid behaviour to keep value from config --- gorgone/gorgone/standard/library.pm | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index ba92fef13ca..b6cb778f78c 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -327,7 +327,7 @@ sub is_client_can_connect { } if ($is_authorized == 0) { - $options{logger}->writeLogError("[core] client pubkey is not authorized. thumprint is '$thumbprint"); + $options{logger}->writeLogError("[core] Client pubkey is not authorized. Thumprint is '$thumbprint"); return -1; } @@ -468,6 +468,11 @@ sub constatus { sub setcoreid { my (%options) = @_; + if (defined($options{gorgone}->{config}->{configuration}->{gorgone}->{gorgonecore}->{id}) && + $options{gorgone}->{config}->{configuration}->{gorgone}->{gorgonecore}->{id} =~ /\d+/) { + return (0, { action => 'setcoreid', message => 'setcoreid unchanged, use config value' }) + } + my $data; eval { $data = JSON::XS->new->utf8->decode($options{data}); From 00719910254a00d18674de67777b97e0d5a10323 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Mon, 23 Mar 2020 22:54:49 +0100 Subject: [PATCH 363/948] fix(core): typo --- gorgone/gorgone/standard/library.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index b6cb778f78c..05a0e9fa7a4 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -327,7 +327,7 @@ sub is_client_can_connect { } if ($is_authorized == 0) { - $options{logger}->writeLogError("[core] Client pubkey is not authorized. Thumprint is '$thumbprint"); + $options{logger}->writeLogError("[core] Client pubkey is not authorized. Thumbprint is '$thumbprint"); return -1; } From 7a7d50215408fdd9e6c9568ad9d7294eb10ab196 Mon Sep 17 00:00:00 2001 From: Matthieu Kermagoret Date: Tue, 24 Mar 2020 10:52:11 +0100 Subject: [PATCH 364/948] fix(packaging): remove centreon-gorgone user and group modification to other packages. --- gorgone/packaging/centreon-gorgone.spectemplate | 4 ---- 1 file changed, 4 deletions(-) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index e058cc96096..9ecdb065baa 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -104,10 +104,6 @@ rm -rf %{buildroot} %post centreon-config %{_bindir}/getent passwd centreon &>/dev/null && %{_sbindir}/usermod -a -G centreon-gorgone centreon 2> /dev/null %{_bindir}/getent passwd centreon-gorgone &>/dev/null && %{_sbindir}/usermod -a -G centreon centreon-gorgone 2> /dev/null -%{_bindir}/getent passwd centreon-gorgone &>/dev/null && %{_sbindir}/usermod -a -G centreon-engine centreon-gorgone 2> /dev/null -%{_bindir}/getent passwd centreon-gorgone &>/dev/null && %{_sbindir}/usermod -a -G centreon-broker centreon-gorgone 2> /dev/null -%{_bindir}/getent passwd centreon-gorgone &>/dev/null && %{_sbindir}/usermod -a -G apache centreon-gorgone 2> /dev/null -%{_bindir}/getent passwd apache &>/dev/null && %{_sbindir}/usermod -a -G centreon-gorgone apache 2> /dev/null if [ \( \! -d %{_localstatedir}/lib/centreon-gorgone/.ssh \) -a \( -d %{_localstatedir}/spool/centreon/.ssh \) ] ; then %{__cp} -r %{_localstatedir}/spool/centreon/.ssh %{_localstatedir}/lib/centreon-gorgone/.ssh %{__chown} -R centreon-gorgone:centreon-gorgone %{_localstatedir}/lib/centreon-gorgone/.ssh From 9860069a5e84bd20127502fdf5ff0ae1fc128b39 Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Tue, 24 Mar 2020 15:47:47 +0100 Subject: [PATCH 365/948] Fix #17 --- gorgone/gorgone/class/sqlquery.pm | 22 ++++++++++++ .../centreon/anomalydetection/class.pm | 36 +++++++------------ 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/gorgone/gorgone/class/sqlquery.pm b/gorgone/gorgone/class/sqlquery.pm index 390d5912b0f..9e6c571c1a9 100644 --- a/gorgone/gorgone/class/sqlquery.pm +++ b/gorgone/gorgone/class/sqlquery.pm @@ -82,6 +82,28 @@ sub execute { return $self->do(request => $request, %options); } +sub transaction_query_multi { + my ($self, %options) = @_; + + $self->transaction_mode(1); + my ($status, $sth) = $self->{db_centreon}->query($options{request}, prepare_only => 1); + if ($status == -1) { + $self->rollback(); + return -1; + } + $sth->execute(); + do { + if ($sth->err) { + $self->rollback(); + $self->{db_centreon}->error($sth->errstr, $options{request}); + return -1; + } + } while ($sth->more_results); + + $self->commit(); + return 0; +} + sub transaction_query { my ($self, %options) = @_; diff --git a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm index 849408ccc34..097cecc645d 100644 --- a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm +++ b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm @@ -46,7 +46,6 @@ sub new { $connector->{config} = $options{config}; $connector->{config_core} = $options{config_core}; $connector->{config_db_centreon} = $options{config_db_centreon}; - $connector->{config_db_centstorage} = $options{config_db_centstorage}; $connector->{stop} = 0; $connector->{resync_time} = (defined($options{config}->{resync_time}) && $options{config}->{resync_time} =~ /(\d+)/) ? $1 : 600; @@ -236,10 +235,9 @@ sub get_centreon_anomaly_metrics { ($status, $datas) = $self->{class_object_centreon}->custom_execute( request => ' SELECT mas.*, hsr.host_host_id as host_id, nhr.nagios_server_id as instance_id - FROM mod_anomaly_service mas, host_service_relation hsr, ns_host_relation nhr - WHERE - mas.service_id = hsr.service_service_id AND - hsr.host_host_id = nhr.host_host_id + FROM mod_anomaly_service mas + LEFT JOIN (host_service_relation hsr, ns_host_relation nhr) ON + (mas.service_id = hsr.service_service_id AND hsr.host_host_id = nhr.host_host_id) ', keys => 'id', mode => 1 @@ -269,7 +267,7 @@ sub save_centreon_previous_register { $query_append = ';'; } if ($query ne '') { - my $status = $self->{class_object_centreon}->transaction_query(request => $query); + my $status = $self->{class_object_centreon}->transaction_query_multi(request => $query); if ($status == -1) { $self->{logger}->writeLogError('[anomalydetection] -class- database: cannot save centreon previous register'); return 1; @@ -324,7 +322,7 @@ sub saas_register_metrics { }; my ($status, $result) = $self->saas_api_request( - endpoint => '/machinelearning', + endpoint => '/v1/machinelearning', # shitty version hardcoded method => 'POST', payload => $payload, http_code_continue => '^2' @@ -374,7 +372,7 @@ sub saas_register_metrics { return 0 if ($query eq ''); - my $status = $self->{class_object_centreon}->transaction_query(request => $query); + my $status = $self->{class_object_centreon}->transaction_query_multi(request => $query); if ($status == -1) { $self->{unregister_metrics_centreon} = $register_centreon_metrics; $self->{logger}->writeLogError('[anomalydetection] -class- database: cannot update centreon register'); @@ -400,7 +398,7 @@ sub saas_delete_metrics { if (defined($self->{centreon_metrics}->{$_}->{saas_model_id})) { my ($status, $result) = $self->saas_api_request( - endpoint => '/machinelearning/' . $self->{centreon_metrics}->{$_}->{saas_model_id}, + endpoint => '/v1/machinelearning/' . $self->{centreon_metrics}->{$_}->{saas_model_id}, # shitty version hardcoded method => 'DELETE', http_code_continue => '^(?:2|404)' ); @@ -482,7 +480,7 @@ sub generate_lua_filter_file { sub saas_get_predicts { my ($self, %options) = @_; - my ($query, $query_append) = ('', ''); + my ($query, $query_append, $status) = ('', ''); my $engine_reload = {}; foreach (keys %{$self->{centreon_metrics}}) { next if ($self->{centreon_metrics}->{$_}->{saas_to_delete} == 1); @@ -490,8 +488,8 @@ sub saas_get_predicts { $self->{centreon_metrics}->{$_}->{thresholds_file} eq ''); next if ($self->{centreon_metrics}->{$_}->{saas_update_date} > time() - 86400); - my ($status, $result) = $self->saas_api_request( - endpoint => '/machinelearning/' . $self->{centreon_metrics}->{$_}->{saas_model_id} . '/predicts', + ($status, my $result) = $self->saas_api_request( + endpoint => '/v1/machinelearning/' . $self->{centreon_metrics}->{$_}->{saas_model_id} . '/predicts', # shitty version hardcoded method => 'GET', http_code_continue => '^2' ); @@ -511,7 +509,7 @@ sub saas_get_predicts { predict => $result->[0]->{predict} } ]; - my ($status, $content) = $self->json_encode(argument => $data); + ($status, my $content) = $self->json_encode(argument => $data); next if ($status == 1); my $encoded_content; @@ -556,7 +554,7 @@ sub saas_get_predicts { ); } - my $status = $self->{class_object_centreon}->transaction_query(request => $query); + $status = $self->{class_object_centreon}->transaction_query_multi(request => $query); if ($status == -1) { $self->{logger}->writeLogError('[anomalydetection] -class- database: cannot update predicts'); return 1; @@ -642,23 +640,15 @@ sub run { my ($self, %options) = @_; $self->{db_centreon} = gorgone::class::db->new( - dsn => $self->{config_db_centreon}->{dsn}, + dsn => $self->{config_db_centreon}->{dsn} . ';mysql_multi_statements=1', user => $self->{config_db_centreon}->{username}, password => $self->{config_db_centreon}->{password}, force => 2, logger => $self->{logger} ); - $self->{db_centstorage} = gorgone::class::db->new( - dsn => $self->{config_db_centstorage}->{dsn}, - user => $self->{config_db_centstorage}->{username}, - password => $self->{config_db_centstorage}->{password}, - force => 2, - logger => $self->{logger} - ); ##### Load objects ##### $self->{class_object_centreon} = gorgone::class::sqlquery->new(logger => $self->{logger}, db_centreon => $self->{db_centreon}); - $self->{class_object_centstorage} = gorgone::class::sqlquery->new(logger => $self->{logger}, db_centreon => $self->{db_centstorage}); $self->{http} = gorgone::class::http::http->new(logger => $self->{logger}); # Connect internal From d1728fef635d05fd5b2b4ae66e46456323291fad Mon Sep 17 00:00:00 2001 From: garnier-quentin Date: Wed, 25 Mar 2020 10:32:37 +0100 Subject: [PATCH 366/948] fix(anomalydetection): remove log error --- gorgone/gorgone/modules/centreon/anomalydetection/class.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm index 097cecc645d..85f880623e1 100644 --- a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm +++ b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm @@ -405,7 +405,7 @@ sub saas_delete_metrics { next if ($status); $self->{logger}->writeLogDebug( - "[anomalydetection] -class- saas: metric '$self->{centreon_metrics}->{$_}->{host_id}/$self->{centreon_metrics}->{$_}->{service_id}/$self->{centreon_metrics}->{$_}->{metric_name}' deleted" + "[anomalydetection] -class- saas: metric '$self->{centreon_metrics}->{$_}->{service_id}/$self->{centreon_metrics}->{$_}->{metric_name}' deleted" ); next if (!defined($result->{message}) || From 860d638556c636aefdca283150e6a7749f601879 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Thu, 26 Mar 2020 16:16:38 +0100 Subject: [PATCH 367/948] fix(sshclient): add rights to sftp moves --- gorgone/gorgone/modules/core/proxy/sshclient.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/modules/core/proxy/sshclient.pm b/gorgone/gorgone/modules/core/proxy/sshclient.pm index f1852faa4a1..c9f98af2947 100644 --- a/gorgone/gorgone/modules/core/proxy/sshclient.pm +++ b/gorgone/gorgone/modules/core/proxy/sshclient.pm @@ -128,7 +128,7 @@ sub action_centcore { $data .= ':' . $options{data}->{content}->{param} if (defined($options{data}->{content}->{param}) && $options{data}->{content}->{param} ne ''); chomp $data; - my $file = $self->{sftp}->open(file => $centcore_cmd, accesstype => O_WRONLY|O_CREAT|O_TRUNC); + my $file = $self->{sftp}->open(file => $centcore_cmd, accesstype => O_WRONLY|O_CREAT|O_TRUNC, mode => 0660); if (!defined($file)) { return (-1, { message => "cannot open stat file '$centcore_cmd': " . $self->{sftp}->error() }); } @@ -309,7 +309,7 @@ sub action_enginecommand { return (-1, { message => "command file '$command_file' must be a pipe file" }); } - my $file = $self->{sftp}->open(file => $command_file, accesstype => O_WRONLY|O_APPEND); + my $file = $self->{sftp}->open(file => $command_file, accesstype => O_WRONLY|O_APPEND, mode => 0660); if (!defined($file)) { $self->{logger}->writeLogError("[sshclient] Cannot open command file '$command_file'"); return (-1, { message => "cannot open command file '$command_file'", error => $self->{sftp}->error() }); From cadf7c9c117847e3610f3a7c330e9e44d6b012ce Mon Sep 17 00:00:00 2001 From: Colin Gagnaire Date: Fri, 27 Mar 2020 10:21:44 +0100 Subject: [PATCH 368/948] enh(docs): update api doc --- gorgone/docs/api/gorgone-openapi.yaml | 122 ++----------------------- gorgone/docs/api/index.html | 123 ++++++++++---------------- 2 files changed, 55 insertions(+), 190 deletions(-) diff --git a/gorgone/docs/api/gorgone-openapi.yaml b/gorgone/docs/api/gorgone-openapi.yaml index db191f36660..233a6e1e30f 100644 --- a/gorgone/docs/api/gorgone-openapi.yaml +++ b/gorgone/docs/api/gorgone-openapi.yaml @@ -417,56 +417,6 @@ paths: $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' - /centreon/autodiscovery/task: - post: - tags: - - Autodiscovery - summary: "Add a discovery task" - description: "Add one Centreon Autodiscovery task." - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AutodiscoveryTask' - responses: - '200': - description: OK - content: - application/json: - schema: - oneOf: - - $ref: '#/components/schemas/Token' - - $ref: '#/components/schemas/Logs' - - $ref: '#/components/schemas/NoLogs' - - $ref: '#/components/schemas/Error' - '401': - $ref: '#/components/responses/Unauthorized' - '403': - $ref: '#/components/responses/Forbidden' - /centreon/autodiscovery/task/{task_id}: - get: - tags: - - Autodiscovery - summary: "Get a discovery task results" - description: "Get Centreon Autodiscovery task results." - parameters: - - $ref: '#/components/parameters/TaskId' - responses: - '200': - description: OK - content: - application/json: - schema: - oneOf: - - $ref: '#/components/schemas/Token' - - $ref: '#/components/schemas/Logs' - - $ref: '#/components/schemas/NoLogs' - - $ref: '#/components/schemas/Error' - '401': - $ref: '#/components/responses/Unauthorized' - '403': - $ref: '#/components/responses/Forbidden' /centreon/autodiscovery/job: post: tags: @@ -494,29 +444,6 @@ paths: $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' - /centreon/autodiscovery/job/{job_id}: - get: - tags: - - Autodiscovery - summary: "Get a discovery job results" - description: "Get Centreon Autodiscovery job results." - parameters: - - $ref: '#/components/parameters/JobId' - responses: - '200': - description: OK - content: - application/json: - schema: - oneOf: - - $ref: '#/components/schemas/Token' - - $ref: '#/components/schemas/Logs' - - $ref: '#/components/schemas/NoLogs' - - $ref: '#/components/schemas/Error' - '401': - $ref: '#/components/responses/Unauthorized' - '403': - $ref: '#/components/responses/Forbidden' components: securitySchemes: Basic Authentication: @@ -583,22 +510,6 @@ components: schema: type: integer example: 2 - TaskId: - in: path - name: task_id - required: true - description: "ID of the task" - schema: - type: string - example: "Task-SNMP-10.1.2.3" - JobId: - in: path - name: job_id - required: true - description: "ID of the job" - schema: - type: string - example: "Job-SNMP-10.1.2.3" responses: NotFound: description: "The specified resource was not found" @@ -988,10 +899,12 @@ components: type: integer description: "Time in seconds before a command is considered timed out" example: 5 + default: 30 continue_on_error: type: boolean description: "Behaviour in case of execution issue" example: true + default: false required: - command EngineCommands: @@ -1007,29 +920,6 @@ components: type: string description: "External command" example: "[653284380] SCHEDULE_SVC_CHECK;host1;service1;653284380" - AutodiscoveryTask: - type: object - properties: - id: - type: string - description: "Identifier of the task (random if empty)" - example: "Task-SNMP-10.1.2.3" - command: - type: string - description: "Command line to execute to perform the discovery" - example: | - perl /usr/lib/centreon/plugins/centreon_generic_snmp.pl --plugin=os::linux::local::plugin --mode=discovery-snmp --subnet='10.1.2.3/24' --snmp-port='161' --snmp-version='2c' --snmp-community='public' - timeout: - type: integer - description: "Time in seconds before the command is considered timed out" - example: 300 - target: - type: integer - description: "Identifier of the target on which to execute the command" - example: 2 - required: - - command - - target AutodiscoveryJob: type: object properties: @@ -1037,10 +927,10 @@ components: type: string description: "Identifier of the job (random if empty)" example: "Job-SNMP-10.1.2.3" - timespec: + execution_mode: type: string - description: "Cron-like time specification" - example: "30 2 * * *" + description: "Execution mode of this job ('0': execute immediately, '1': schedule with cron)" + example: 0 command: type: string description: "Command line to execute to perform the discovery" @@ -1055,7 +945,7 @@ components: description: "Identifier of the target on which to execute the command" example: 2 required: - - timespec + - execution_mode - command - target \ No newline at end of file diff --git a/gorgone/docs/api/index.html b/gorgone/docs/api/index.html index 7344000e305..50b5c9dd4fc 100644 --- a/gorgone/docs/api/index.html +++ b/gorgone/docs/api/index.html @@ -15,10 +15,10 @@ "}},ne=function(e,t){return function(){var n,r=((n={})[T]=X(t),n["data-styled-version"]="4.4.1",n),o=V();return o&&(r.nonce=o),l.a.createElement("style",v({},r,{dangerouslySetInnerHTML:{__html:e()}}))}},re=function(e){return function(){return Object.keys(e)}},oe=function(e,t){return e.createTextNode(J(t))},ie=function e(t,n){var r=void 0===t?Object.create(null):t,o=void 0===n?Object.create(null):n,i=function(e){var t=o[e];return void 0!==t?t:o[e]=[""]},a=function(){var e="";for(var t in o){var n=o[t][0];n&&(e+=J(t)+n)}return e};return{clone:function(){var t=function(e){var t=Object.create(null);for(var n in e)t[n]=v({},e[n]);return t}(r),n=Object.create(null);for(var i in o)n[i]=[o[i][0]];return e(t,n)},css:a,getIds:re(o),hasNameForId:G(r),insertMarker:i,insertRules:function(e,t,n){i(e)[0]+=t.join(" "),Y(r,e,n)},removeRules:function(e){var t=o[e];void 0!==t&&(t[0]="",Q(r,e))},sealed:!1,styleTag:null,toElement:ne(a,r),toHTML:te(a,r)}},ae=function(e,t,n,r,o){if(j&&!n){var i=function(e,t,n){var r=document;e?r=e.ownerDocument:t&&(r=t.ownerDocument);var o=r.createElement("style");o.setAttribute(T,""),o.setAttribute("data-styled-version","4.4.1");var i=V();if(i&&o.setAttribute("nonce",i),o.appendChild(r.createTextNode("")),e&&!t)e.appendChild(o);else{if(!t||!e||!t.parentNode)throw new I(6);t.parentNode.insertBefore(o,n?t:t.nextSibling)}return o}(e,t,r);return C?function(e,t){var n=Object.create(null),r=Object.create(null),o=void 0!==t,i=!1,a=function(t){var o=r[t];return void 0!==o?o:(r[t]=oe(e.ownerDocument,t),e.appendChild(r[t]),n[t]=Object.create(null),r[t])},s=function(){var e="";for(var t in r)e+=r[t].data;return e};return{clone:function(){throw new I(5)},css:s,getIds:re(r),hasNameForId:G(n),insertMarker:a,insertRules:function(e,r,s){for(var l=a(e),c=[],u=r.length,p=0;p0&&(i=!0,t().insertRules(e+"-import",c))},removeRules:function(a){var s=r[a];if(void 0!==s){var l=oe(e.ownerDocument,a);e.replaceChild(l,s),r[a]=l,Q(n,a),o&&i&&t().removeRules(a+"-import")}},sealed:!1,styleTag:e,toElement:ne(s,n),toHTML:te(s,n)}}(i,o):function(e,t){var n=Object.create(null),r=Object.create(null),o=[],i=void 0!==t,a=!1,s=function(e){var t=r[e];return void 0!==t?t:(r[e]=o.length,o.push(0),Q(n,e),r[e])},l=function(){var t=K(e).cssRules,n="";for(var i in r){n+=J(i);for(var a=r[i],s=ee(o,a),l=s-o[a];l0&&(a=!0,t().insertRules(r+"-import",h)),o[u]+=d,Y(n,r,c)},removeRules:function(s){var l=r[s];if(void 0!==l&&!1!==e.isConnected){var c=o[l];!function(e,t,n){for(var r=t-n,o=t;o>r;o-=1)e.deleteRule(o)}(K(e),ee(o,l)-1,c),o[l]=0,Q(n,s),i&&a&&t().removeRules(s+"-import")}},sealed:!1,styleTag:e,toElement:ne(l,n),toHTML:te(l,n)}}(i,o)}return ie()},se=/\s+/,le=void 0;le=j?C?40:1e3:-1;var ce=0,ue=void 0,pe=function(){function e(){var t=this,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:j?document.head:null,r=arguments.length>1&&void 0!==arguments[1]&&arguments[1];g(this,e),this.getImportRuleTag=function(){var e=t.importRuleTag;if(void 0!==e)return e;var n=t.tags[0];return t.importRuleTag=ae(t.target,n?n.styleTag:null,t.forceServer,!0)},ce+=1,this.id=ce,this.forceServer=r,this.target=r?null:n,this.tagMap={},this.deferred={},this.rehydratedNames={},this.ignoreRehydratedNames={},this.tags=[],this.capacity=1,this.clones=[]}return e.prototype.rehydrate=function(){if(!j||this.forceServer)return this;var e=[],t=[],n=!1,r=document.querySelectorAll("style["+T+'][data-styled-version="4.4.1"]'),o=r.length;if(!o)return this;for(var i=0;i0&&void 0!==arguments[0]&&arguments[0];ue=new e(void 0,t).rehydrate()},e.prototype.clone=function(){var t=new e(this.target,this.forceServer);return this.clones.push(t),t.tags=this.tags.map((function(e){for(var n=e.getIds(),r=e.clone(),o=0;o1?t-1:0),r=1;r=4;)t=1540483477*(65535&(t=255&e.charCodeAt(o)|(255&e.charCodeAt(++o))<<8|(255&e.charCodeAt(++o))<<16|(255&e.charCodeAt(++o))<<24))+((1540483477*(t>>>16)&65535)<<16),r=1540483477*(65535&r)+((1540483477*(r>>>16)&65535)<<16)^(t=1540483477*(65535&(t^=t>>>24))+((1540483477*(t>>>16)&65535)<<16)),n-=4,++o;switch(n){case 3:r^=(255&e.charCodeAt(o+2))<<16;case 2:r^=(255&e.charCodeAt(o+1))<<8;case 1:r=1540483477*(65535&(r^=255&e.charCodeAt(o)))+((1540483477*(r>>>16)&65535)<<16)}return((r=1540483477*(65535&(r^=r>>>13))+((1540483477*(r>>>16)&65535)<<16))^r>>>15)>>>0}var we=function(e){return String.fromCharCode(e+(e>25?39:97))};function ke(e){var t="",n=void 0;for(n=e;n>52;n=Math.floor(n/52))t=we(n%52)+t;return we(n%52)+t}function Oe(e,t){for(var n=0;n2&&void 0!==arguments[2]?arguments[2]:O,r=!!n&&e.theme===n.theme,o=e.theme&&!r?e.theme:t||n.theme;return o},je=/[[\].#*$><+~=|^:(),"'`-]+/g,Ce=/(^-|-$)/g;function Ae(e){return e.replace(je,"-").replace(Ce,"")}function Ie(e){return"string"==typeof e&&!0}var Pe={childContextTypes:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDerivedStateFromProps:!0,propTypes:!0,type:!0},Re={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},Ne=((_e={})[u.ForwardRef]={$$typeof:!0,render:!0},_e),Le=Object.defineProperty,Me=Object.getOwnPropertyNames,De=Object.getOwnPropertySymbols,Fe=void 0===De?function(){return[]}:De,ze=Object.getOwnPropertyDescriptor,Ue=Object.getPrototypeOf,Be=Object.prototype,$e=Array.prototype;function qe(e,t,n){if("string"!=typeof t){var r=Ue(t);r&&r!==Be&&qe(e,r,n);for(var o=$e.concat(Me(t),Fe(t)),i=Ne[e.$$typeof]||Pe,a=Ne[t.$$typeof]||Pe,s=o.length,l=void 0,c=void 0;s--;)if(c=o[s],!(Re[c]||n&&n[c]||a&&a[c]||i&&i[c])&&(l=ze(t,c)))try{Le(e,c,l)}catch(e){}return e}return e}var We=Object(s.createContext)(),He=We.Consumer,Ve=function(e){function t(n){g(this,t);var r=x(this,e.call(this,n));return r.getContext=Object(p.a)(r.getContext.bind(r)),r.renderInner=r.renderInner.bind(r),r}return b(t,e),t.prototype.render=function(){return this.props.children?l.a.createElement(We.Consumer,null,this.renderInner):null},t.prototype.renderInner=function(e){var t=this.getContext(this.props.theme,e);return l.a.createElement(We.Provider,{value:t},this.props.children)},t.prototype.getTheme=function(e,t){if(_(e))return e(t);if(null===e||Array.isArray(e)||"object"!==(void 0===e?"undefined":m(e)))throw new I(8);return v({},t,e)},t.prototype.getContext=function(e,t){return this.getTheme(e,t)},t}(s.Component),Ye=function(){function e(){g(this,e),this.masterSheet=pe.master,this.instance=this.masterSheet.clone(),this.sealed=!1}return e.prototype.seal=function(){if(!this.sealed){var e=this.masterSheet.clones.indexOf(this.instance);this.masterSheet.clones.splice(e,1),this.sealed=!0}},e.prototype.collectStyles=function(e){if(this.sealed)throw new I(2);return l.a.createElement(Xe,{sheet:this.instance},e)},e.prototype.getStyleTags=function(){return this.seal(),this.instance.toHTML()},e.prototype.getStyleElement=function(){return this.seal(),this.instance.toReactElements()},e.prototype.interleaveWithNodeStream=function(e){throw new I(3)},e}(),Qe=Object(s.createContext)(),Ge=Qe.Consumer,Xe=function(e){function t(n){g(this,t);var r=x(this,e.call(this,n));return r.getContext=Object(p.a)(r.getContext),r}return b(t,e),t.prototype.getContext=function(e,t){if(e)return e;if(t)return new pe(t);throw new I(4)},t.prototype.render=function(){var e=this.props,t=e.children,n=e.sheet,r=e.target;return l.a.createElement(Qe.Provider,{value:this.getContext(n,r)},t)},t}(s.Component),Ke={};var Ze=function(e){function t(){g(this,t);var n=x(this,e.call(this));return n.attrs={},n.renderOuter=n.renderOuter.bind(n),n.renderInner=n.renderInner.bind(n),n}return b(t,e),t.prototype.render=function(){return l.a.createElement(Ge,null,this.renderOuter)},t.prototype.renderOuter=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:pe.master;return this.styleSheet=e,this.props.forwardedComponent.componentStyle.isStatic?this.renderInner():l.a.createElement(He,null,this.renderInner)},t.prototype.renderInner=function(e){var t=this.props.forwardedComponent,n=t.componentStyle,r=t.defaultProps,o=(t.displayName,t.foldedComponentIds),i=t.styledComponentId,a=t.target,l=void 0;l=n.isStatic?this.generateAndInjectStyles(O,this.props):this.generateAndInjectStyles(Te(this.props,e,r)||O,this.props);var c=this.props.as||this.attrs.as||a,u=Ie(c),p={},d=v({},this.props,this.attrs),h=void 0;for(h in d)"forwardedComponent"!==h&&"as"!==h&&("forwardedRef"===h?p.ref=d[h]:"forwardedAs"===h?p.as=d[h]:u&&!Object(f.a)(h)||(p[h]=d[h]));return this.props.style&&this.attrs.style&&(p.style=v({},this.attrs.style,this.props.style)),p.className=Array.prototype.concat(o,i,l!==i?l:null,this.props.className,this.attrs.className).filter(Boolean).join(" "),Object(s.createElement)(c,p)},t.prototype.buildExecutionContext=function(e,t,n){var r=this,o=v({},t,{theme:e});return n.length?(this.attrs={},n.forEach((function(e){var t,n=e,i=!1,a=void 0,s=void 0;for(s in _(n)&&(n=n(o),i=!0),n)a=n[s],i||!_(a)||(t=a)&&t.prototype&&t.prototype.isReactComponent||S(a)||(a=a(o)),r.attrs[s]=a,o[s]=a})),o):o},t.prototype.generateAndInjectStyles=function(e,t){var n=t.forwardedComponent,r=n.attrs,o=n.componentStyle;n.warnTooManyClasses;return o.isStatic&&!r.length?o.generateAndInjectStyles(O,this.styleSheet):o.generateAndInjectStyles(this.buildExecutionContext(e,t,r),this.styleSheet)},t}(s.Component);function Je(e,t,n){var r=S(e),o=!Ie(e),i=t.displayName,a=void 0===i?function(e){return Ie(e)?"styled."+e:"Styled("+E(e)+")"}(e):i,s=t.componentId,c=void 0===s?function(e,t,n){var r="string"!=typeof t?"sc":Ae(t),o=(Ke[r]||0)+1;Ke[r]=o;var i=r+"-"+e.generateName(r+o);return n?n+"-"+i:i}(Se,t.displayName,t.parentComponentId):s,u=t.ParentComponent,p=void 0===u?Ze:u,f=t.attrs,h=void 0===f?k:f,m=t.displayName&&t.componentId?Ae(t.displayName)+"-"+t.componentId:t.componentId||c,g=r&&e.attrs?Array.prototype.concat(e.attrs,h).filter(Boolean):h,y=new Se(r?e.componentStyle.rules.concat(n):n,g,m),b=void 0,x=function(e,t){return l.a.createElement(p,v({},e,{forwardedComponent:b,forwardedRef:t}))};return x.displayName=a,(b=l.a.forwardRef(x)).displayName=a,b.attrs=g,b.componentStyle=y,b.foldedComponentIds=r?Array.prototype.concat(e.foldedComponentIds,e.styledComponentId):k,b.styledComponentId=m,b.target=r?e.target:e,b.withComponent=function(e){var r=t.componentId,o=function(e,t){var n={};for(var r in e)t.indexOf(r)>=0||Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r]);return n}(t,["componentId"]),i=r&&r+"-"+(Ie(e)?e:Ae(E(e)));return Je(e,v({},o,{attrs:g,componentId:i,ParentComponent:p}),n)},Object.defineProperty(b,"defaultProps",{get:function(){return this._foldedDefaultProps},set:function(t){this._foldedDefaultProps=r?Object(d.a)(e.defaultProps,t):t}}),b.toString=function(){return"."+b.styledComponentId},o&&qe(b,e,{attrs:!0,componentStyle:!0,displayName:!0,foldedComponentIds:!0,styledComponentId:!0,target:!0,withComponent:!0}),b}var et=function(e){return function e(t,n){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:O;if(!Object(u.isValidElementType)(n))throw new I(1,String(n));var o=function(){return t(n,r,be.apply(void 0,arguments))};return o.withConfig=function(o){return e(t,n,v({},r,o))},o.attrs=function(o){return e(t,n,v({},r,{attrs:Array.prototype.concat(r.attrs,o).filter(Boolean)}))},o}(Je,e)};["a","abbr","address","area","article","aside","audio","b","base","bdi","bdo","big","blockquote","body","br","button","canvas","caption","cite","code","col","colgroup","data","datalist","dd","del","details","dfn","dialog","div","dl","dt","em","embed","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","iframe","img","input","ins","kbd","keygen","label","legend","li","link","main","map","mark","marquee","menu","menuitem","meta","meter","nav","noscript","object","ol","optgroup","option","output","p","param","picture","pre","progress","q","rp","rt","ruby","s","samp","script","section","select","small","source","span","strong","style","sub","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","title","tr","track","u","ul","var","video","wbr","circle","clipPath","defs","ellipse","foreignObject","g","image","line","linearGradient","marker","mask","path","pattern","polygon","polyline","radialGradient","rect","stop","svg","text","tspan"].forEach((function(e){et[e]=et(e)}));var tt=function(){function e(t,n){g(this,e),this.rules=t,this.componentId=n,this.isStatic=Oe(t,k),pe.master.hasId(n)||pe.master.deferredInject(n,[])}return e.prototype.createStyles=function(e,t){var n=H(ve(this.rules,e,t),"");t.inject(this.componentId,n)},e.prototype.removeStyles=function(e){var t=this.componentId;e.hasId(t)&&e.remove(t)},e.prototype.renderStyles=function(e,t){this.removeStyles(t),this.createStyles(e,t)},e}();function nt(e){for(var t=arguments.length,n=Array(t>1?t-1:0),r=1;r1?t-1:0),r=1;r1&&O("Lost properties from oneOf",e,n),delete e.oneOf)}e.type&&Array.isArray(e.type)&&1===e.type.length&&(e.type=e.type[0])}else k("(Patchable) schema type must not be an array",n);e.type&&"null"===e.type&&(delete e.type,e.nullable=!0),"array"!==e.type||e.items||(e.items={}),"boolean"==typeof e.required&&(e.required&&e.name&&(void 0===t.required&&(t.required=[]),Array.isArray(t.required)&&t.required.push(e.name)),delete e.required),e.xml&&"string"==typeof e.xml.namespace&&(e.xml.namespace||delete e.xml.namespace)}(e,n,t)}))}function E(e,t,n){var o=n.payload.options;if(f(e,t)){if(e[t].startsWith("#/components/"));else if("#/consumes"===e[t])delete e[t],n.parent[n.pkey]=d(o.openapi.consumes);else if("#/produces"===e[t])delete e[t],n.parent[n.pkey]=d(o.openapi.produces);else if(e[t].startsWith("#/definitions/")){var i=e[t].replace("#/definitions/","").split("/"),a=u.jpunescape(i[0]);(w=r.schemas[decodeURIComponent(a)])?i[0]=w:O("Could not resolve reference "+e[t],e,o),e[t]="#/components/schemas/"+i.join("/")}else if(e[t].startsWith("#/parameters/"))e[t]="#/components/parameters/"+v.sanitise(e[t].replace("#/parameters/",""));else if(e[t].startsWith("#/responses/"))e[t]="#/components/responses/"+v.sanitise(e[t].replace("#/responses/",""));else if(e[t].startsWith("#")){var s=d(u.jptr(o.openapi,e[t]));if(!1===s)O("direct $ref not found "+e[t],e,o);else if(o.refmap[e[t]])e[t]=o.refmap[e[t]];else{var l=e[t],c="schemas",p=(l=(l=(l=(l=l.replace("/properties/headers/","")).replace("/properties/responses/","")).replace("/properties/parameters/","")).replace("/properties/schemas/","")).lastIndexOf("/schema");if("schemas"===(c=l.indexOf("/headers/")>p?"headers":l.indexOf("/responses/")>p?"responses":l.indexOf("/example")>p?"examples":l.indexOf("/x-")>p?"extensions":l.indexOf("/parameters/")>p?"parameters":"schemas")&&_(s,o),"responses"!==c&&"extensions"!==c){var h=c.substr(0,c.length-1);"parameter"===h&&s.name&&s.name===v.sanitise(s.name)&&(h=encodeURIComponent(s.name));var m=1;for(e["x-miro"]&&(h=function(e){return e=e.indexOf("#")>=0?e.split("#")[1].split("/").pop():e.split("/").pop().split(".")[0],encodeURIComponent(v.sanitise(e))}(e["x-miro"]),m="");u.jptr(o.openapi,"#/components/"+c+"/"+h+m);)m=""===m?2:++m;var g="#/components/"+c+"/"+h+m,y="";"examples"===c&&(s={value:s},y="/value"),u.jptr(o.openapi,g,s),o.refmap[e[t]]=g+y,e[t]=g+y}}}if(delete e["x-miro"],Object.keys(e).length>1){var b=e[t],x=n.path.indexOf("/schema")>=0;"preserve"===o.refSiblings||(x&&"allOf"===o.refSiblings?(delete e.$ref,n.parent[n.pkey]={allOf:[{$ref:b},e]}):n.parent[n.pkey]={$ref:b})}}if("x-ms-odata"===t&&"string"==typeof e[t]&&e[t].startsWith("#/")){var w;i=e[t].replace("#/definitions/","").replace("#/components/schemas/","").split("/");(w=r.schemas[decodeURIComponent(i[0])])?i[0]=w:O("Could not resolve reference "+e[t],e,o),e[t]="#/components/schemas/"+i.join("/")}}function S(e){for(var t in e)for(var n in e[t]){var r=v.sanitise(n);n!=r&&(e[t][r]=e[t][n],delete e[t][n])}}function T(e,t){if("basic"===e.type&&(e.type="http",e.scheme="basic"),"oauth2"===e.type){var n={},r=e.flow;"application"===e.flow&&(r="clientCredentials"),"accessCode"===e.flow&&(r="authorizationCode"),void 0!==e.authorizationUrl&&(n.authorizationUrl=e.authorizationUrl.split("?")[0].trim()||"/"),"string"==typeof e.tokenUrl&&(n.tokenUrl=e.tokenUrl.split("?")[0].trim()||"/"),n.scopes=e.scopes||{},e.flows={},e.flows[r]=n,delete e.flow,delete e.authorizationUrl,delete e.tokenUrl,delete e.scopes,void 0!==e.name&&(t.patch?delete e.name:k("(Patchable) oauth2 securitySchemes should not have name property",t))}}function j(e){return e&&!e["x-s2o-delete"]}function C(e,t){if(e.$ref)e.$ref=e.$ref.replace("#/responses/","#/components/responses/");else{e.type&&!e.schema&&(e.schema={}),e.type&&(e.schema.type=e.type),e.items&&"array"!==e.items.type&&(e.items.collectionFormat!==e.collectionFormat&&O("Nested collectionFormats are not supported",e,t),delete e.items.collectionFormat),"array"===e.type?("ssv"===e.collectionFormat?O("collectionFormat:ssv is no longer supported for headers",e,t):"pipes"===e.collectionFormat?O("collectionFormat:pipes is no longer supported for headers",e,t):"multi"===e.collectionFormat?e.explode=!0:"tsv"===e.collectionFormat?(O("collectionFormat:tsv is no longer supported",e,t),e["x-collectionFormat"]="tsv"):e.style="simple",delete e.collectionFormat):e.collectionFormat&&(t.patch?delete e.collectionFormat:k("(Patchable) collectionFormat is only applicable to header.type array",t)),delete e.type;for(var n=0,r=v.parameterTypeProperties;n=0){var n=e.$ref.split("#/parameters/");e.$ref=n[0]+"#/components/parameters/"+v.sanitise(n[1])}e.$ref.indexOf("#/definitions/")>=0&&O("Definition used as parameter",e,t)}function I(e,t,n,r,o,i,a){var s,l={},c=!0;if(t&&t.consumes&&"string"==typeof t.consumes){if(!a.patch)return k("(Patchable) operation.consumes must be an array",a);t.consumes=[t.consumes]}Array.isArray(i.consumes)||delete i.consumes;var u=((t?t.consumes:null)||i.consumes||[]).filter(v.uniqueOnly);if(e&&e.$ref&&"string"==typeof e.$ref){A(e,a);var f=decodeURIComponent(e.$ref.replace("#/components/parameters/","")),h=!1;if((j=i.components.parameters[f])&&!j["x-s2o-delete"]||!e.$ref.startsWith("#/")||(e["x-s2o-delete"]=!0,h=!0),h){var g=e.$ref,y=p(i,e.$ref);!y&&g.startsWith("#/")?O("Could not resolve reference "+g,e,a):y&&(e=y)}}if(e&&(e.name||e.in)){"boolean"==typeof e["x-deprecated"]&&(e.deprecated=e["x-deprecated"],delete e["x-deprecated"]),void 0!==e["x-example"]&&(e.example=e["x-example"],delete e["x-example"]),"body"==e.in||e.type||(a.patch?e.type="string":k("(Patchable) parameter.type is mandatory for non-body parameters",a)),e.type&&"object"==typeof e.type&&e.type.$ref&&(e.type=p(i,e.type.$ref)),"file"===e.type&&(e["x-s2o-originalType"]=e.type,s=e.type),e.description&&"object"==typeof e.description&&e.description.$ref&&(e.description=p(i,e.description.$ref)),null===e.description&&delete e.description;var b=e.collectionFormat;if("array"!==e.type||b||(b="csv"),b&&("array"!=e.type&&(a.patch?delete e.collectionFormat:k("(Patchable) collectionFormat is only applicable to param.type array",a)),"csv"!==b||"query"!==e.in&&"cookie"!==e.in||(e.style="form",e.explode=!1),"csv"!==b||"path"!==e.in&&"header"!==e.in||(e.style="simple"),"ssv"===b&&("query"===e.in?e.style="spaceDelimited":O("collectionFormat:ssv is no longer supported except for in:query parameters",e,a)),"pipes"===b&&("query"===e.in?e.style="pipeDelimited":O("collectionFormat:pipes is no longer supported except for in:query parameters",e,a)),"multi"===b&&(e.explode=!0),"tsv"===b&&(O("collectionFormat:tsv is no longer supported",e,a),e["x-collectionFormat"]="tsv"),delete e.collectionFormat),e.type&&"object"!=e.type&&"body"!=e.type&&"formData"!=e.in)if(e.items&&e.schema)O("parameter has array,items and schema",e,a);else{e.schema&&"object"==typeof e.schema||(e.schema={}),e.schema.type=e.type,e.items&&(e.schema.items=e.items,delete e.items,m(e.schema.items,null,(function(t,n,r){"collectionFormat"===n&&"string"==typeof t[n]&&(b&&t[n]!==b&&O("Nested collectionFormats are not supported",e,a),delete t[n])})));for(var x=0,w=v.parameterTypeProperties;x=0&&(S="multipart/form-data"),l.content[S]={},e.schema)l.content[S].schema=e.schema,e.schema.$ref&&(l["x-s2o-name"]=decodeURIComponent(e.schema.$ref.replace("#/components/schemas/","")));else{l.content[S].schema={},l.content[S].schema.type="object",l.content[S].schema.properties={},l.content[S].schema.properties[e.name]={};var T=l.content[S].schema,j=l.content[S].schema.properties[e.name];e.description&&(j.description=e.description),e.example&&(j.example=e.example),e.type&&(j.type=e.type);for(var C=0,I=v.parameterTypeProperties;C0&&(e["x-s2o-delete"]=!0,t&&(t.requestBody&&c?(t.requestBody["x-s2o-overloaded"]=!0,O("Operation "+(t.operationId||o)+" has multiple requestBodies",t,a)):(t.requestBody||(t=n[r]=function(e,t){for(var n={},r=0,o=Object.keys(e);r=0?O("definition used as response: "+e.$ref,e,o):e.$ref.startsWith("#/responses/")&&(e.$ref="#/components/responses/"+v.sanitise(decodeURIComponent(e.$ref.replace("#/responses/",""))));else{if((void 0===e.description||null===e.description||""===e.description&&o.patch)&&(o.patch?"object"!=typeof e||Array.isArray(e)||(e.description=b[e]||""):k("(Patchable) response.description is mandatory",o)),void 0!==e.schema){if(_(e.schema,o),e.schema.$ref&&"string"==typeof e.schema.$ref&&e.schema.$ref.startsWith("#/responses/")&&(e.schema.$ref="#/components/responses/"+v.sanitise(decodeURIComponent(e.schema.$ref.replace("#/responses/","")))),n&&n.produces&&"string"==typeof n.produces){if(!o.patch)return k("(Patchable) operation.produces must be an array",o);n.produces=[n.produces]}r.produces&&!Array.isArray(r.produces)&&delete r.produces;var i=((n?n.produces:null)||r.produces||[]).filter(v.uniqueOnly);i.length||i.push("*/*"),e.content={};for(var a=0,s=i;a=0||"x-amazon-apigateway-any-method"===l){var c=s[l];if(c&&c.parameters&&Array.isArray(c.parameters)){if(s.parameters)for(var f=function(e){"string"==typeof e.$ref&&(A(e,n),e=p(o,e.$ref)),c.parameters.find((function(t,n,r){return t.name===e.name&&t.in===e.in}))||"formData"!==e.in&&"body"!==e.in&&"file"!==e.type||(c=I(e,c,s,l,i,o,n),n.rbname&&""===c[n.rbname]&&delete c[n.rbname])},h=0,m=s.parameters;h1){i="";for(w.name||(w.name="requestBody",i=b++);y.indexOf(w.name+i)>=0;)i=i?++i:2;for(var s in w.name=w.name+i,y.push(w.name),e.components.requestBodies[w.name]=d(w.body),w.refs){var O={};O.$ref="#/components/requestBodies/"+w.name,u.jptr(e,w.refs[s],O)}}}}return e.components.responses&&0===Object.keys(e.components.responses).length&&delete e.components.responses,e.components.parameters&&0===Object.keys(e.components.parameters).length&&delete e.components.parameters,e.components.examples&&0===Object.keys(e.components.examples).length&&delete e.components.examples,e.components.requestBodies&&0===Object.keys(e.components.requestBodies).length&&delete e.components.requestBodies,e.components.securitySchemes&&0===Object.keys(e.components.securitySchemes).length&&delete e.components.securitySchemes,e.components.headers&&0===Object.keys(e.components.headers).length&&delete e.components.headers,e.components.schemas&&0===Object.keys(e.components.schemas).length&&delete e.components.schemas,e.components&&0===Object.keys(e.components).length&&delete e.components,e}function M(e){return e&&e.url&&"string"==typeof e.url?(e.url=e.url.split("{{").join("{"),e.url=e.url.split("}}").join("}"),e.url.replace(/\{(.+?)\}/g,(function(t,n){e.variables||(e.variables={}),e.variables[n]={default:"unknown"}})),e):e}function D(e,t,n){if(void 0===e.info||null===e.info){if(!t.patch)return n(new w("(Patchable) info object is mandatory"));e.info={version:"",title:""}}if("object"!=typeof e.info||Array.isArray(e.info))return n(new w("info must be an object"));if(void 0===e.info.title||null===e.info.title){if(!t.patch)return n(new w("(Patchable) info.title cannot be null"));e.info.title=""}if(void 0===e.info.version||null===e.info.version){if(!t.patch)return n(new w("(Patchable) info.version cannot be null"));e.info.version=""}if("string"!=typeof e.info.version){if(!t.patch)return n(new w("(Patchable) info.version must be a string"));e.info.version=e.info.version.toString()}if(void 0!==e.info.logo){if(!t.patch)return n(new w("(Patchable) info should not have logo property"));e.info["x-logo"]=e.info.logo,delete e.info.logo}if(void 0!==e.info.termsOfService){if(null===e.info.termsOfService){if(!t.patch)return n(new w("(Patchable) info.termsOfService cannot be null"));e.info.termsOfService=""}if(a.URL&&t.whatwg)try{a.URL.parse(e.info.termsOfService)}catch(r){if(!t.patch)return n(new w("(Patchable) info.termsOfService must be a URL"));delete e.info.termsOfService}}}function F(e,t,n){if(void 0===e.paths){if(!t.patch)return n(new w("(Patchable) paths object is mandatory"));e.paths={}}}function z(e,t,n){return s(n,new Promise((function(n,r){if(e||(e={}),t.original=e,t.text||(t.text=c.stringify(e)),t.externals=[],t.externalRefs={},t.rewriteRefs=!0,t.preserveMiro=!0,t.promise={},t.promise.resolve=n,t.promise.reject=r,t.cache||(t.cache={}),t.source&&(t.cache[t.source]=t.original),e.openapi&&"string"==typeof e.openapi&&e.openapi.startsWith("3."))return t.openapi=h(e),D(t.openapi,t,r),F(t.openapi,t,r),void g.optionalResolve(t).then((function(){return t.direct?n(t.openapi):n(t)})).catch((function(e){console.warn(e),r(e)}));if(!e.swagger||"2.0"!=e.swagger)return r(new w("Unsupported swagger/OpenAPI version: "+(e.openapi?e.openapi:e.swagger)));var o=t.openapi={};if(o.openapi="string"==typeof t.targetVersion&&t.targetVersion.startsWith("3.")?t.targetVersion:"3.0.0",t.origin){o["x-origin"]||(o["x-origin"]=[]);var i={};i.url=t.source||t.origin,i.format="swagger",i.version=e.swagger,i.converter={},i.converter.url="https://github.com/mermade/oas-kit",i.converter.version=x,o["x-origin"].push(i)}if(delete(o=Object.assign(o,h(e))).swagger,m(o,{},(function(e,t,n){null===e[t]&&!t.startsWith("x-")&&"default"!==t&&n.path.indexOf("/example")<0&&delete e[t]})),e.host)for(var a=0,s=Array.isArray(e.schemes)?e.schemes:[""];a1?n-1:0),o=1;o/gm),F=i(/^data-[\-\w.\u00B7-\uFFFF]/),z=i(/^aria-[\-\w]+$/),U=i(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),B=i(/^(?:\w+script|data):/i),$=i(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205f\u3000]/g),q="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};function W(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t0&&void 0!==arguments[0]?arguments[0]:H(),n=function(t){return e(t)};if(n.version="2.0.8",n.removed=[],!t||!t.document||9!==t.document.nodeType)return n.isSupported=!1,n;var i=t.document,a=!1,s=!1,l=t.document,O=t.DocumentFragment,_=t.HTMLTemplateElement,Y=t.Node,Q=t.NodeFilter,G=t.NamedNodeMap,X=void 0===G?t.NamedNodeMap||t.MozNamedAttrMap:G,K=t.Text,Z=t.Comment,J=t.DOMParser,ee=t.trustedTypes;if("function"==typeof _){var te=l.createElement("template");te.content&&te.content.ownerDocument&&(l=te.content.ownerDocument)}var ne=V(ee,i),re=ne?ne.createHTML(""):"",oe=l,ie=oe.implementation,ae=oe.createNodeIterator,se=oe.getElementsByTagName,le=oe.createDocumentFragment,ce=i.importNode,ue={};n.isSupported=ie&&void 0!==ie.createHTMLDocument&&9!==l.documentMode;var pe=M,fe=D,de=F,he=z,me=B,ge=$,ye=U,ve=null,be=E({},[].concat(W(T),W(j),W(C),W(A),W(I))),xe=null,we=E({},[].concat(W(P),W(R),W(N),W(L))),ke=null,Oe=null,_e=!0,Ee=!0,Se=!1,Te=!1,je=!1,Ce=!1,Ae=!1,Ie=!1,Pe=!1,Re=!1,Ne=!1,Le=!1,Me=!0,De=!0,Fe=!1,ze={},Ue=E({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","plaintext","script","style","svg","template","thead","title","video","xmp"]),Be=E({},["audio","video","img","source","image"]),$e=null,qe=E({},["alt","class","for","id","label","name","pattern","placeholder","summary","title","value","style","xmlns"]),We=null,He=l.createElement("form"),Ve=function(e){We&&We===e||(e&&"object"===(void 0===e?"undefined":q(e))||(e={}),ve="ALLOWED_TAGS"in e?E({},e.ALLOWED_TAGS):be,xe="ALLOWED_ATTR"in e?E({},e.ALLOWED_ATTR):we,$e="ADD_URI_SAFE_ATTR"in e?E(S(qe),e.ADD_URI_SAFE_ATTR):qe,ke="FORBID_TAGS"in e?E({},e.FORBID_TAGS):{},Oe="FORBID_ATTR"in e?E({},e.FORBID_ATTR):{},ze="USE_PROFILES"in e&&e.USE_PROFILES,_e=!1!==e.ALLOW_ARIA_ATTR,Ee=!1!==e.ALLOW_DATA_ATTR,Se=e.ALLOW_UNKNOWN_PROTOCOLS||!1,Te=e.SAFE_FOR_JQUERY||!1,je=e.SAFE_FOR_TEMPLATES||!1,Ce=e.WHOLE_DOCUMENT||!1,Pe=e.RETURN_DOM||!1,Re=e.RETURN_DOM_FRAGMENT||!1,Ne=e.RETURN_DOM_IMPORT||!1,Le=e.RETURN_TRUSTED_TYPE||!1,Ie=e.FORCE_BODY||!1,Me=!1!==e.SANITIZE_DOM,De=!1!==e.KEEP_CONTENT,Fe=e.IN_PLACE||!1,ye=e.ALLOWED_URI_REGEXP||ye,je&&(Ee=!1),Re&&(Pe=!0),ze&&(ve=E({},[].concat(W(I))),xe=[],!0===ze.html&&(E(ve,T),E(xe,P)),!0===ze.svg&&(E(ve,j),E(xe,R),E(xe,L)),!0===ze.svgFilters&&(E(ve,C),E(xe,R),E(xe,L)),!0===ze.mathMl&&(E(ve,A),E(xe,N),E(xe,L))),e.ADD_TAGS&&(ve===be&&(ve=S(ve)),E(ve,e.ADD_TAGS)),e.ADD_ATTR&&(xe===we&&(xe=S(xe)),E(xe,e.ADD_ATTR)),e.ADD_URI_SAFE_ATTR&&E($e,e.ADD_URI_SAFE_ATTR),De&&(ve["#text"]=!0),Ce&&E(ve,["html","head","body"]),ve.table&&(E(ve,["tbody"]),delete ke.tbody),o&&o(e),We=e)},Ye=function(e){d(n.removed,{element:e});try{e.parentNode.removeChild(e)}catch(t){e.outerHTML=re}},Qe=function(e,t){try{d(n.removed,{attribute:t.getAttributeNode(e),from:t})}catch(e){d(n.removed,{attribute:null,from:t})}t.removeAttribute(e)},Ge=function(e){var t=void 0,n=void 0;if(Ie)e=""+e;else{var r=g(e,/^[\s]+/);n=r&&r[0]}var o=ne?ne.createHTML(e):e;if(a)try{t=(new J).parseFromString(o,"text/html")}catch(e){}if(s&&E(ke,["title"]),!t||!t.documentElement){var i=(t=ie.createHTMLDocument("")).body;i.parentNode.removeChild(i.parentNode.firstElementChild),i.outerHTML=o}return e&&n&&t.body.insertBefore(l.createTextNode(n),t.body.childNodes[0]||null),se.call(t,Ce?"html":"body")[0]};n.isSupported&&(function(){try{Ge('

').querySelector("svg img")&&(a=!0)}catch(e){}}(),function(){try{var e=Ge("</title><img>");x(/<\/title/,e.querySelector("title").innerHTML)&&(s=!0)}catch(e){}}());var Xe=function(e){return ae.call(e.ownerDocument||e,e,Q.SHOW_ELEMENT|Q.SHOW_COMMENT|Q.SHOW_TEXT,(function(){return Q.FILTER_ACCEPT}),!1)},Ke=function(e){return!(e instanceof K||e instanceof Z||"string"==typeof e.nodeName&&"string"==typeof e.textContent&&"function"==typeof e.removeChild&&e.attributes instanceof X&&"function"==typeof e.removeAttribute&&"function"==typeof e.setAttribute&&"string"==typeof e.namespaceURI)},Ze=function(e){return"object"===(void 0===Y?"undefined":q(Y))?e instanceof Y:e&&"object"===(void 0===e?"undefined":q(e))&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName},Je=function(e,t,r){ue[e]&&c(ue[e],(function(e){e.call(n,t,r,We)}))},et=function(e){var t=void 0;if(Je("beforeSanitizeElements",e,null),Ke(e))return Ye(e),!0;var r=m(e.nodeName);if(Je("uponSanitizeElement",e,{tagName:r,allowedTags:ve}),("svg"===r||"math"===r)&&0!==e.querySelectorAll("p, br").length)return Ye(e),!0;if(!ve[r]||ke[r]){if(De&&!Ue[r]&&"function"==typeof e.insertAdjacentHTML)try{var o=e.innerHTML;e.insertAdjacentHTML("AfterEnd",ne?ne.createHTML(o):o)}catch(e){}return Ye(e),!0}return"noscript"===r&&x(/<\/noscript/i,e.innerHTML)||"noembed"===r&&x(/<\/noembed/i,e.innerHTML)?(Ye(e),!0):(!Te||e.firstElementChild||e.content&&e.content.firstElementChild||!x(/</g,e.textContent)||(d(n.removed,{element:e.cloneNode()}),e.innerHTML?e.innerHTML=y(e.innerHTML,/</g,"<"):e.innerHTML=y(e.textContent,/</g,"<")),je&&3===e.nodeType&&(t=e.textContent,t=y(t,pe," "),t=y(t,fe," "),e.textContent!==t&&(d(n.removed,{element:e.cloneNode()}),e.textContent=t)),Je("afterSanitizeElements",e,null),!1)},tt=function(e,t,n){if(Me&&("id"===t||"name"===t)&&(n in l||n in He))return!1;if(Ee&&x(de,t));else if(_e&&x(he,t));else{if(!xe[t]||Oe[t])return!1;if($e[t]);else if(x(ye,y(n,ge,"")));else if("src"!==t&&"xlink:href"!==t&&"href"!==t||"script"===e||0!==v(n,"data:")||!Be[e])if(Se&&!x(me,y(n,ge,"")));else if(n)return!1}return!0},nt=function(e){var t=void 0,o=void 0,i=void 0,a=void 0,s=void 0;Je("beforeSanitizeAttributes",e,null);var l=e.attributes;if(l){var c={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:xe};for(s=l.length;s--;){var d=t=l[s],g=d.name,v=d.namespaceURI;if(o=b(t.value),i=m(g),c.attrName=i,c.attrValue=o,c.keepAttr=!0,c.forceKeepAttr=void 0,Je("uponSanitizeAttribute",e,c),o=c.attrValue,!c.forceKeepAttr){if("name"===i&&"IMG"===e.nodeName&&l.id)a=l.id,l=h(l,[]),Qe("id",e),Qe(g,e),u(l,a)>s&&e.setAttribute("id",a.value);else{if("INPUT"===e.nodeName&&"type"===i&&"file"===o&&c.keepAttr&&(xe[i]||!Oe[i]))continue;"id"===g&&e.setAttribute(g,""),Qe(g,e)}if(c.keepAttr)if(Te&&x(/\/>/i,o))Qe(g,e);else if(x(/svg|math/i,e.namespaceURI)&&x(w("</("+p(r(Ue),"|")+")","i"),o))Qe(g,e);else{je&&(o=y(o,pe," "),o=y(o,fe," "));var k=e.nodeName.toLowerCase();if(tt(k,i,o))try{v?e.setAttributeNS(v,g,o):e.setAttribute(g,o),f(n.removed)}catch(e){}}}}Je("afterSanitizeAttributes",e,null)}},rt=function e(t){var n=void 0,r=Xe(t);for(Je("beforeSanitizeShadowDOM",t,null);n=r.nextNode();)Je("uponSanitizeShadowNode",n,null),et(n)||(n.content instanceof O&&e(n.content),nt(n));Je("afterSanitizeShadowDOM",t,null)};return n.sanitize=function(e,r){var o=void 0,a=void 0,s=void 0,l=void 0,c=void 0;if(e||(e="\x3c!--\x3e"),"string"!=typeof e&&!Ze(e)){if("function"!=typeof e.toString)throw k("toString is not a function");if("string"!=typeof(e=e.toString()))throw k("dirty is not a string, aborting")}if(!n.isSupported){if("object"===q(t.toStaticHTML)||"function"==typeof t.toStaticHTML){if("string"==typeof e)return t.toStaticHTML(e);if(Ze(e))return t.toStaticHTML(e.outerHTML)}return e}if(Ae||Ve(r),n.removed=[],"string"==typeof e&&(Fe=!1),Fe);else if(e instanceof Y)1===(a=(o=Ge("\x3c!--\x3e")).ownerDocument.importNode(e,!0)).nodeType&&"BODY"===a.nodeName||"HTML"===a.nodeName?o=a:o.appendChild(a);else{if(!Pe&&!je&&!Ce&&Le&&-1===e.indexOf("<"))return ne?ne.createHTML(e):e;if(!(o=Ge(e)))return Pe?null:re}o&&Ie&&Ye(o.firstChild);for(var u=Xe(Fe?e:o);s=u.nextNode();)3===s.nodeType&&s===l||et(s)||(s.content instanceof O&&rt(s.content),nt(s),l=s);if(l=null,Fe)return e;if(Pe){if(Re)for(c=le.call(o.ownerDocument);o.firstChild;)c.appendChild(o.firstChild);else c=o;return Ne&&(c=ce.call(i,c,!0)),c}var p=Ce?o.outerHTML:o.innerHTML;return je&&(p=y(p,pe," "),p=y(p,fe," ")),ne&&Le?ne.createHTML(p):p},n.setConfig=function(e){Ve(e),Ae=!0},n.clearConfig=function(){We=null,Ae=!1},n.isValidAttribute=function(e,t,n){We||Ve({});var r=m(e),o=m(t);return tt(r,o,n)},n.addHook=function(e,t){"function"==typeof t&&(ue[e]=ue[e]||[],d(ue[e],t))},n.removeHook=function(e){ue[e]&&f(ue[e])},n.removeHooks=function(e){ue[e]&&(ue[e]=[])},n.removeAllHooks=function(){ue={}},n}()}()},function(e,t,n){"use strict";function r(e){return Object.prototype.toString.call(e).slice(8,-1)}function o(e){return"Object"===r(e)&&(e.constructor===Object&&Object.getPrototypeOf(e)===Object.prototype)}function i(e){return"Array"===r(e)}function a(e){return"Symbol"===r(e)} +*/var r=Object.getOwnPropertySymbols,o=Object.prototype.hasOwnProperty,i=Object.prototype.propertyIsEnumerable;function a(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;n<10;n++)t["_"+String.fromCharCode(n)]=n;if("0123456789"!==Object.getOwnPropertyNames(t).map((function(e){return t[e]})).join(""))return!1;var r={};return"abcdefghijklmnopqrst".split("").forEach((function(e){r[e]=e})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},r)).join("")}catch(e){return!1}}()?Object.assign:function(e,t){for(var n,s,l=a(e),c=1;c<arguments.length;c++){for(var u in n=Object(arguments[c]))o.call(n,u)&&(l[u]=n[u]);if(r){s=r(n);for(var p=0;p<s.length;p++)i.call(n,s[p])&&(l[s[p]]=n[s[p]])}}return l}},function(e,t){var n={}.toString;e.exports=Array.isArray||function(e){return"[object Array]"==n.call(e)}},function(e,t,n){"use strict";var r=n(241),o=n(242),i=n(266),a=n(267),s=n(268),l=n(270);function c(e){u(this,c.defaults),u(this,e)}function u(e,t){if(p(t))for(var n=Object.keys(t),r=0;r<n.length;r++){var o=n[r],i=t[o],a=e[o];p(i)?e[o]=u(a||{},i):void 0!==i&&(e[o]=i)}return e}function p(e){return e&&"object"==typeof e&&!Array.isArray(e)&&!(e instanceof RegExp)&&!(e instanceof Date)}e.exports=c,c.defaults={parse:{json:r,yaml:o,text:i,binary:a},resolve:{file:s,http:l,external:!0},dereference:{circular:!0}}},function(e,t,n){"use strict";var r=n(243),o=n(31);e.exports={parse:function(e,t){try{return r.safeLoad(e)}catch(e){throw e instanceof Error?e:o(e,e.message)}},stringify:function(e,t,n){try{var i=("string"==typeof n?n.length:n)||2;return r.safeDump(e,{indent:i})}catch(e){throw e instanceof Error?e:o(e,e.message)}}}},function(e,t,n){"use strict";var r=n(39);e.exports=new r({include:[n(135)]})},function(e,t,n){"use strict";var r=n(39);e.exports=new r({include:[n(89)],implicit:[n(250),n(251),n(252),n(253)]})},function(e,t,n){(function(e){t.fetch=s(e.fetch)&&s(e.ReadableStream),t.writableStream=s(e.WritableStream),t.abortController=s(e.AbortController),t.blobConstructor=!1;try{new Blob([new ArrayBuffer(1)]),t.blobConstructor=!0}catch(e){}var n;function r(){if(void 0!==n)return n;if(e.XMLHttpRequest){n=new e.XMLHttpRequest;try{n.open("GET",e.XDomainRequest?"/":"https://example.com")}catch(e){n=null}}else n=null;return n}function o(e){var t=r();if(!t)return!1;try{return t.responseType=e,t.responseType===e}catch(e){}return!1}var i=void 0!==e.ArrayBuffer,a=i&&s(e.ArrayBuffer.prototype.slice);function s(e){return"function"==typeof e}t.arraybuffer=t.fetch||i&&o("arraybuffer"),t.msstream=!t.fetch&&a&&o("ms-stream"),t.mozchunkedarraybuffer=!t.fetch&&i&&o("moz-chunked-arraybuffer"),t.overrideMimeType=t.fetch||!!r()&&s(r().overrideMimeType),t.vbArray=s(e.VBArray),n=null}).call(this,n(7))},function(e,t,n){(function(e,r,o){var i=n(136),a=n(32),s=n(138),l=t.readyStates={UNSENT:0,OPENED:1,HEADERS_RECEIVED:2,LOADING:3,DONE:4},c=t.IncomingMessage=function(t,n,a,l){var c=this;if(s.Readable.call(c),c._mode=a,c.headers={},c.rawHeaders=[],c.trailers={},c.rawTrailers=[],c.on("end",(function(){e.nextTick((function(){c.emit("close")}))})),"fetch"===a){if(c._fetchResponse=n,c.url=n.url,c.statusCode=n.status,c.statusMessage=n.statusText,n.headers.forEach((function(e,t){c.headers[t.toLowerCase()]=e,c.rawHeaders.push(t,e)})),i.writableStream){var u=new WritableStream({write:function(e){return new Promise((function(t,n){c._destroyed?n():c.push(new r(e))?t():c._resumeFetch=t}))},close:function(){o.clearTimeout(l),c._destroyed||c.push(null)},abort:function(e){c._destroyed||c.emit("error",e)}});try{return void n.body.pipeTo(u).catch((function(e){o.clearTimeout(l),c._destroyed||c.emit("error",e)}))}catch(e){}}var p=n.body.getReader();!function e(){p.read().then((function(t){if(!c._destroyed){if(t.done)return o.clearTimeout(l),void c.push(null);c.push(new r(t.value)),e()}})).catch((function(e){o.clearTimeout(l),c._destroyed||c.emit("error",e)}))}()}else{if(c._xhr=t,c._pos=0,c.url=t.responseURL,c.statusCode=t.status,c.statusMessage=t.statusText,t.getAllResponseHeaders().split(/\r?\n/).forEach((function(e){var t=e.match(/^([^:]+):\s*(.*)/);if(t){var n=t[1].toLowerCase();"set-cookie"===n?(void 0===c.headers[n]&&(c.headers[n]=[]),c.headers[n].push(t[2])):void 0!==c.headers[n]?c.headers[n]+=", "+t[2]:c.headers[n]=t[2],c.rawHeaders.push(t[1],t[2])}})),c._charset="x-user-defined",!i.overrideMimeType){var f=c.rawHeaders["mime-type"];if(f){var d=f.match(/;\s*charset=([^;])(;|$)/);d&&(c._charset=d[1].toLowerCase())}c._charset||(c._charset="utf-8")}}};a(c,s.Readable),c.prototype._read=function(){var e=this._resumeFetch;e&&(this._resumeFetch=null,e())},c.prototype._onXHRProgress=function(){var e=this,t=e._xhr,n=null;switch(e._mode){case"text:vbarray":if(t.readyState!==l.DONE)break;try{n=new o.VBArray(t.responseBody).toArray()}catch(e){}if(null!==n){e.push(new r(n));break}case"text":try{n=t.responseText}catch(t){e._mode="text:vbarray";break}if(n.length>e._pos){var i=n.substr(e._pos);if("x-user-defined"===e._charset){for(var a=new r(i.length),s=0;s<i.length;s++)a[s]=255&i.charCodeAt(s);e.push(a)}else e.push(i,e._charset);e._pos=n.length}break;case"arraybuffer":if(t.readyState!==l.DONE||!t.response)break;n=t.response,e.push(new r(new Uint8Array(n)));break;case"moz-chunked-arraybuffer":if(n=t.response,t.readyState!==l.LOADING||!n)break;e.push(new r(new Uint8Array(n)));break;case"ms-stream":if(n=t.response,t.readyState!==l.LOADING)break;var c=new o.MSStreamReader;c.onprogress=function(){c.result.byteLength>e._pos&&(e.push(new r(new Uint8Array(c.result.slice(e._pos)))),e._pos=c.result.byteLength)},c.onload=function(){e.push(null)},c.readAsArrayBuffer(n)}e._xhr.readyState===l.DONE&&"ms-stream"!==e._mode&&e.push(null)}}).call(this,n(13),n(14).Buffer,n(7))},function(e,t,n){(t=e.exports=n(139)).Stream=t,t.Readable=t,t.Writable=n(143),t.Duplex=n(40),t.Transform=n(145),t.PassThrough=n(278)},function(e,t,n){"use strict";(function(t,r){var o=n(61);e.exports=b;var i,a=n(131);b.ReadableState=v;n(140).EventEmitter;var s=function(e,t){return e.listeners(t).length},l=n(141),c=n(62).Buffer,u=t.Uint8Array||function(){};var p=Object.create(n(50));p.inherits=n(32);var f=n(272),d=void 0;d=f&&f.debuglog?f.debuglog("stream"):function(){};var h,m=n(273),g=n(142);p.inherits(b,l);var y=["error","close","destroy","pause","resume"];function v(e,t){e=e||{};var r=t instanceof(i=i||n(40));this.objectMode=!!e.objectMode,r&&(this.objectMode=this.objectMode||!!e.readableObjectMode);var o=e.highWaterMark,a=e.readableHighWaterMark,s=this.objectMode?16:16384;this.highWaterMark=o||0===o?o:r&&(a||0===a)?a:s,this.highWaterMark=Math.floor(this.highWaterMark),this.buffer=new m,this.length=0,this.pipes=null,this.pipesCount=0,this.flowing=null,this.ended=!1,this.endEmitted=!1,this.reading=!1,this.sync=!0,this.needReadable=!1,this.emittedReadable=!1,this.readableListening=!1,this.resumeScheduled=!1,this.destroyed=!1,this.defaultEncoding=e.defaultEncoding||"utf8",this.awaitDrain=0,this.readingMore=!1,this.decoder=null,this.encoding=null,e.encoding&&(h||(h=n(144).StringDecoder),this.decoder=new h(e.encoding),this.encoding=e.encoding)}function b(e){if(i=i||n(40),!(this instanceof b))return new b(e);this._readableState=new v(e,this),this.readable=!0,e&&("function"==typeof e.read&&(this._read=e.read),"function"==typeof e.destroy&&(this._destroy=e.destroy)),l.call(this)}function x(e,t,n,r,o){var i,a=e._readableState;null===t?(a.reading=!1,function(e,t){if(t.ended)return;if(t.decoder){var n=t.decoder.end();n&&n.length&&(t.buffer.push(n),t.length+=t.objectMode?1:n.length)}t.ended=!0,O(e)}(e,a)):(o||(i=function(e,t){var n;r=t,c.isBuffer(r)||r instanceof u||"string"==typeof t||void 0===t||e.objectMode||(n=new TypeError("Invalid non-string/buffer chunk"));var r;return n}(a,t)),i?e.emit("error",i):a.objectMode||t&&t.length>0?("string"==typeof t||a.objectMode||Object.getPrototypeOf(t)===c.prototype||(t=function(e){return c.from(e)}(t)),r?a.endEmitted?e.emit("error",new Error("stream.unshift() after end event")):w(e,a,t,!0):a.ended?e.emit("error",new Error("stream.push() after EOF")):(a.reading=!1,a.decoder&&!n?(t=a.decoder.write(t),a.objectMode||0!==t.length?w(e,a,t,!1):E(e,a)):w(e,a,t,!1))):r||(a.reading=!1));return function(e){return!e.ended&&(e.needReadable||e.length<e.highWaterMark||0===e.length)}(a)}function w(e,t,n,r){t.flowing&&0===t.length&&!t.sync?(e.emit("data",n),e.read(0)):(t.length+=t.objectMode?1:n.length,r?t.buffer.unshift(n):t.buffer.push(n),t.needReadable&&O(e)),E(e,t)}Object.defineProperty(b.prototype,"destroyed",{get:function(){return void 0!==this._readableState&&this._readableState.destroyed},set:function(e){this._readableState&&(this._readableState.destroyed=e)}}),b.prototype.destroy=g.destroy,b.prototype._undestroy=g.undestroy,b.prototype._destroy=function(e,t){this.push(null),t(e)},b.prototype.push=function(e,t){var n,r=this._readableState;return r.objectMode?n=!0:"string"==typeof e&&((t=t||r.defaultEncoding)!==r.encoding&&(e=c.from(e,t),t=""),n=!0),x(this,e,t,!1,n)},b.prototype.unshift=function(e){return x(this,e,null,!0,!1)},b.prototype.isPaused=function(){return!1===this._readableState.flowing},b.prototype.setEncoding=function(e){return h||(h=n(144).StringDecoder),this._readableState.decoder=new h(e),this._readableState.encoding=e,this};function k(e,t){return e<=0||0===t.length&&t.ended?0:t.objectMode?1:e!=e?t.flowing&&t.length?t.buffer.head.data.length:t.length:(e>t.highWaterMark&&(t.highWaterMark=function(e){return e>=8388608?e=8388608:(e--,e|=e>>>1,e|=e>>>2,e|=e>>>4,e|=e>>>8,e|=e>>>16,e++),e}(e)),e<=t.length?e:t.ended?t.length:(t.needReadable=!0,0))}function O(e){var t=e._readableState;t.needReadable=!1,t.emittedReadable||(d("emitReadable",t.flowing),t.emittedReadable=!0,t.sync?o.nextTick(_,e):_(e))}function _(e){d("emit readable"),e.emit("readable"),C(e)}function E(e,t){t.readingMore||(t.readingMore=!0,o.nextTick(S,e,t))}function S(e,t){for(var n=t.length;!t.reading&&!t.flowing&&!t.ended&&t.length<t.highWaterMark&&(d("maybeReadMore read 0"),e.read(0),n!==t.length);)n=t.length;t.readingMore=!1}function T(e){d("readable nexttick read 0"),e.read(0)}function j(e,t){t.reading||(d("resume read 0"),e.read(0)),t.resumeScheduled=!1,t.awaitDrain=0,e.emit("resume"),C(e),t.flowing&&!t.reading&&e.read(0)}function C(e){var t=e._readableState;for(d("flow",t.flowing);t.flowing&&null!==e.read(););}function I(e,t){return 0===t.length?null:(t.objectMode?n=t.buffer.shift():!e||e>=t.length?(n=t.decoder?t.buffer.join(""):1===t.buffer.length?t.buffer.head.data:t.buffer.concat(t.length),t.buffer.clear()):n=function(e,t,n){var r;e<t.head.data.length?(r=t.head.data.slice(0,e),t.head.data=t.head.data.slice(e)):r=e===t.head.data.length?t.shift():n?function(e,t){var n=t.head,r=1,o=n.data;e-=o.length;for(;n=n.next;){var i=n.data,a=e>i.length?i.length:e;if(a===i.length?o+=i:o+=i.slice(0,e),0===(e-=a)){a===i.length?(++r,n.next?t.head=n.next:t.head=t.tail=null):(t.head=n,n.data=i.slice(a));break}++r}return t.length-=r,o}(e,t):function(e,t){var n=c.allocUnsafe(e),r=t.head,o=1;r.data.copy(n),e-=r.data.length;for(;r=r.next;){var i=r.data,a=e>i.length?i.length:e;if(i.copy(n,n.length-e,0,a),0===(e-=a)){a===i.length?(++o,r.next?t.head=r.next:t.head=t.tail=null):(t.head=r,r.data=i.slice(a));break}++o}return t.length-=o,n}(e,t);return r}(e,t.buffer,t.decoder),n);var n}function A(e){var t=e._readableState;if(t.length>0)throw new Error('"endReadable()" called on non-empty stream');t.endEmitted||(t.ended=!0,o.nextTick(P,t,e))}function P(e,t){e.endEmitted||0!==e.length||(e.endEmitted=!0,t.readable=!1,t.emit("end"))}function R(e,t){for(var n=0,r=e.length;n<r;n++)if(e[n]===t)return n;return-1}b.prototype.read=function(e){d("read",e),e=parseInt(e,10);var t=this._readableState,n=e;if(0!==e&&(t.emittedReadable=!1),0===e&&t.needReadable&&(t.length>=t.highWaterMark||t.ended))return d("read: emitReadable",t.length,t.ended),0===t.length&&t.ended?A(this):O(this),null;if(0===(e=k(e,t))&&t.ended)return 0===t.length&&A(this),null;var r,o=t.needReadable;return d("need readable",o),(0===t.length||t.length-e<t.highWaterMark)&&d("length less than watermark",o=!0),t.ended||t.reading?d("reading or ended",o=!1):o&&(d("do read"),t.reading=!0,t.sync=!0,0===t.length&&(t.needReadable=!0),this._read(t.highWaterMark),t.sync=!1,t.reading||(e=k(n,t))),null===(r=e>0?I(e,t):null)?(t.needReadable=!0,e=0):t.length-=e,0===t.length&&(t.ended||(t.needReadable=!0),n!==e&&t.ended&&A(this)),null!==r&&this.emit("data",r),r},b.prototype._read=function(e){this.emit("error",new Error("_read() is not implemented"))},b.prototype.pipe=function(e,t){var n=this,i=this._readableState;switch(i.pipesCount){case 0:i.pipes=e;break;case 1:i.pipes=[i.pipes,e];break;default:i.pipes.push(e)}i.pipesCount+=1,d("pipe count=%d opts=%j",i.pipesCount,t);var l=(!t||!1!==t.end)&&e!==r.stdout&&e!==r.stderr?u:b;function c(t,r){d("onunpipe"),t===n&&r&&!1===r.hasUnpiped&&(r.hasUnpiped=!0,d("cleanup"),e.removeListener("close",y),e.removeListener("finish",v),e.removeListener("drain",p),e.removeListener("error",g),e.removeListener("unpipe",c),n.removeListener("end",u),n.removeListener("end",b),n.removeListener("data",m),f=!0,!i.awaitDrain||e._writableState&&!e._writableState.needDrain||p())}function u(){d("onend"),e.end()}i.endEmitted?o.nextTick(l):n.once("end",l),e.on("unpipe",c);var p=function(e){return function(){var t=e._readableState;d("pipeOnDrain",t.awaitDrain),t.awaitDrain&&t.awaitDrain--,0===t.awaitDrain&&s(e,"data")&&(t.flowing=!0,C(e))}}(n);e.on("drain",p);var f=!1;var h=!1;function m(t){d("ondata"),h=!1,!1!==e.write(t)||h||((1===i.pipesCount&&i.pipes===e||i.pipesCount>1&&-1!==R(i.pipes,e))&&!f&&(d("false write response, pause",n._readableState.awaitDrain),n._readableState.awaitDrain++,h=!0),n.pause())}function g(t){d("onerror",t),b(),e.removeListener("error",g),0===s(e,"error")&&e.emit("error",t)}function y(){e.removeListener("finish",v),b()}function v(){d("onfinish"),e.removeListener("close",y),b()}function b(){d("unpipe"),n.unpipe(e)}return n.on("data",m),function(e,t,n){if("function"==typeof e.prependListener)return e.prependListener(t,n);e._events&&e._events[t]?a(e._events[t])?e._events[t].unshift(n):e._events[t]=[n,e._events[t]]:e.on(t,n)}(e,"error",g),e.once("close",y),e.once("finish",v),e.emit("pipe",n),i.flowing||(d("pipe resume"),n.resume()),e},b.prototype.unpipe=function(e){var t=this._readableState,n={hasUnpiped:!1};if(0===t.pipesCount)return this;if(1===t.pipesCount)return e&&e!==t.pipes||(e||(e=t.pipes),t.pipes=null,t.pipesCount=0,t.flowing=!1,e&&e.emit("unpipe",this,n)),this;if(!e){var r=t.pipes,o=t.pipesCount;t.pipes=null,t.pipesCount=0,t.flowing=!1;for(var i=0;i<o;i++)r[i].emit("unpipe",this,n);return this}var a=R(t.pipes,e);return-1===a||(t.pipes.splice(a,1),t.pipesCount-=1,1===t.pipesCount&&(t.pipes=t.pipes[0]),e.emit("unpipe",this,n)),this},b.prototype.on=function(e,t){var n=l.prototype.on.call(this,e,t);if("data"===e)!1!==this._readableState.flowing&&this.resume();else if("readable"===e){var r=this._readableState;r.endEmitted||r.readableListening||(r.readableListening=r.needReadable=!0,r.emittedReadable=!1,r.reading?r.length&&O(this):o.nextTick(T,this))}return n},b.prototype.addListener=b.prototype.on,b.prototype.resume=function(){var e=this._readableState;return e.flowing||(d("resume"),e.flowing=!0,function(e,t){t.resumeScheduled||(t.resumeScheduled=!0,o.nextTick(j,e,t))}(this,e)),this},b.prototype.pause=function(){return d("call pause flowing=%j",this._readableState.flowing),!1!==this._readableState.flowing&&(d("pause"),this._readableState.flowing=!1,this.emit("pause")),this},b.prototype.wrap=function(e){var t=this,n=this._readableState,r=!1;for(var o in e.on("end",(function(){if(d("wrapped end"),n.decoder&&!n.ended){var e=n.decoder.end();e&&e.length&&t.push(e)}t.push(null)})),e.on("data",(function(o){(d("wrapped data"),n.decoder&&(o=n.decoder.write(o)),n.objectMode&&null==o)||(n.objectMode||o&&o.length)&&(t.push(o)||(r=!0,e.pause()))})),e)void 0===this[o]&&"function"==typeof e[o]&&(this[o]=function(t){return function(){return e[t].apply(e,arguments)}}(o));for(var i=0;i<y.length;i++)e.on(y[i],this.emit.bind(this,y[i]));return this._read=function(t){d("wrapped _read",t),r&&(r=!1,e.resume())},this},Object.defineProperty(b.prototype,"readableHighWaterMark",{enumerable:!1,get:function(){return this._readableState.highWaterMark}}),b._fromList=I}).call(this,n(7),n(13))},function(e,t,n){"use strict";var r,o="object"==typeof Reflect?Reflect:null,i=o&&"function"==typeof o.apply?o.apply:function(e,t,n){return Function.prototype.apply.call(e,t,n)};r=o&&"function"==typeof o.ownKeys?o.ownKeys:Object.getOwnPropertySymbols?function(e){return Object.getOwnPropertyNames(e).concat(Object.getOwnPropertySymbols(e))}:function(e){return Object.getOwnPropertyNames(e)};var a=Number.isNaN||function(e){return e!=e};function s(){s.init.call(this)}e.exports=s,s.EventEmitter=s,s.prototype._events=void 0,s.prototype._eventsCount=0,s.prototype._maxListeners=void 0;var l=10;function c(e){if("function"!=typeof e)throw new TypeError('The "listener" argument must be of type Function. Received type '+typeof e)}function u(e){return void 0===e._maxListeners?s.defaultMaxListeners:e._maxListeners}function p(e,t,n,r){var o,i,a,s;if(c(n),void 0===(i=e._events)?(i=e._events=Object.create(null),e._eventsCount=0):(void 0!==i.newListener&&(e.emit("newListener",t,n.listener?n.listener:n),i=e._events),a=i[t]),void 0===a)a=i[t]=n,++e._eventsCount;else if("function"==typeof a?a=i[t]=r?[n,a]:[a,n]:r?a.unshift(n):a.push(n),(o=u(e))>0&&a.length>o&&!a.warned){a.warned=!0;var l=new Error("Possible EventEmitter memory leak detected. "+a.length+" "+String(t)+" listeners added. Use emitter.setMaxListeners() to increase limit");l.name="MaxListenersExceededWarning",l.emitter=e,l.type=t,l.count=a.length,s=l,console&&console.warn&&console.warn(s)}return e}function f(){if(!this.fired)return this.target.removeListener(this.type,this.wrapFn),this.fired=!0,0===arguments.length?this.listener.call(this.target):this.listener.apply(this.target,arguments)}function d(e,t,n){var r={fired:!1,wrapFn:void 0,target:e,type:t,listener:n},o=f.bind(r);return o.listener=n,r.wrapFn=o,o}function h(e,t,n){var r=e._events;if(void 0===r)return[];var o=r[t];return void 0===o?[]:"function"==typeof o?n?[o.listener||o]:[o]:n?function(e){for(var t=new Array(e.length),n=0;n<t.length;++n)t[n]=e[n].listener||e[n];return t}(o):g(o,o.length)}function m(e){var t=this._events;if(void 0!==t){var n=t[e];if("function"==typeof n)return 1;if(void 0!==n)return n.length}return 0}function g(e,t){for(var n=new Array(t),r=0;r<t;++r)n[r]=e[r];return n}Object.defineProperty(s,"defaultMaxListeners",{enumerable:!0,get:function(){return l},set:function(e){if("number"!=typeof e||e<0||a(e))throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received '+e+".");l=e}}),s.init=function(){void 0!==this._events&&this._events!==Object.getPrototypeOf(this)._events||(this._events=Object.create(null),this._eventsCount=0),this._maxListeners=this._maxListeners||void 0},s.prototype.setMaxListeners=function(e){if("number"!=typeof e||e<0||a(e))throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received '+e+".");return this._maxListeners=e,this},s.prototype.getMaxListeners=function(){return u(this)},s.prototype.emit=function(e){for(var t=[],n=1;n<arguments.length;n++)t.push(arguments[n]);var r="error"===e,o=this._events;if(void 0!==o)r=r&&void 0===o.error;else if(!r)return!1;if(r){var a;if(t.length>0&&(a=t[0]),a instanceof Error)throw a;var s=new Error("Unhandled error."+(a?" ("+a.message+")":""));throw s.context=a,s}var l=o[e];if(void 0===l)return!1;if("function"==typeof l)i(l,this,t);else{var c=l.length,u=g(l,c);for(n=0;n<c;++n)i(u[n],this,t)}return!0},s.prototype.addListener=function(e,t){return p(this,e,t,!1)},s.prototype.on=s.prototype.addListener,s.prototype.prependListener=function(e,t){return p(this,e,t,!0)},s.prototype.once=function(e,t){return c(t),this.on(e,d(this,e,t)),this},s.prototype.prependOnceListener=function(e,t){return c(t),this.prependListener(e,d(this,e,t)),this},s.prototype.removeListener=function(e,t){var n,r,o,i,a;if(c(t),void 0===(r=this._events))return this;if(void 0===(n=r[e]))return this;if(n===t||n.listener===t)0==--this._eventsCount?this._events=Object.create(null):(delete r[e],r.removeListener&&this.emit("removeListener",e,n.listener||t));else if("function"!=typeof n){for(o=-1,i=n.length-1;i>=0;i--)if(n[i]===t||n[i].listener===t){a=n[i].listener,o=i;break}if(o<0)return this;0===o?n.shift():function(e,t){for(;t+1<e.length;t++)e[t]=e[t+1];e.pop()}(n,o),1===n.length&&(r[e]=n[0]),void 0!==r.removeListener&&this.emit("removeListener",e,a||t)}return this},s.prototype.off=s.prototype.removeListener,s.prototype.removeAllListeners=function(e){var t,n,r;if(void 0===(n=this._events))return this;if(void 0===n.removeListener)return 0===arguments.length?(this._events=Object.create(null),this._eventsCount=0):void 0!==n[e]&&(0==--this._eventsCount?this._events=Object.create(null):delete n[e]),this;if(0===arguments.length){var o,i=Object.keys(n);for(r=0;r<i.length;++r)"removeListener"!==(o=i[r])&&this.removeAllListeners(o);return this.removeAllListeners("removeListener"),this._events=Object.create(null),this._eventsCount=0,this}if("function"==typeof(t=n[e]))this.removeListener(e,t);else if(void 0!==t)for(r=t.length-1;r>=0;r--)this.removeListener(e,t[r]);return this},s.prototype.listeners=function(e){return h(this,e,!0)},s.prototype.rawListeners=function(e){return h(this,e,!1)},s.listenerCount=function(e,t){return"function"==typeof e.listenerCount?e.listenerCount(t):m.call(e,t)},s.prototype.listenerCount=m,s.prototype.eventNames=function(){return this._eventsCount>0?r(this._events):[]}},function(e,t,n){e.exports=n(140).EventEmitter},function(e,t,n){"use strict";var r=n(61);function o(e,t){e.emit("error",t)}e.exports={destroy:function(e,t){var n=this,i=this._readableState&&this._readableState.destroyed,a=this._writableState&&this._writableState.destroyed;return i||a?(t?t(e):!e||this._writableState&&this._writableState.errorEmitted||r.nextTick(o,this,e),this):(this._readableState&&(this._readableState.destroyed=!0),this._writableState&&(this._writableState.destroyed=!0),this._destroy(e||null,(function(e){!t&&e?(r.nextTick(o,n,e),n._writableState&&(n._writableState.errorEmitted=!0)):t&&t(e)})),this)},undestroy:function(){this._readableState&&(this._readableState.destroyed=!1,this._readableState.reading=!1,this._readableState.ended=!1,this._readableState.endEmitted=!1),this._writableState&&(this._writableState.destroyed=!1,this._writableState.ended=!1,this._writableState.ending=!1,this._writableState.finished=!1,this._writableState.errorEmitted=!1)}}},function(e,t,n){"use strict";(function(t,r,o){var i=n(61);function a(e){var t=this;this.next=null,this.entry=null,this.finish=function(){!function(e,t,n){var r=e.entry;e.entry=null;for(;r;){var o=r.callback;t.pendingcb--,o(n),r=r.next}t.corkedRequestsFree?t.corkedRequestsFree.next=e:t.corkedRequestsFree=e}(t,e)}}e.exports=v;var s,l=!t.browser&&["v0.10","v0.9."].indexOf(t.version.slice(0,5))>-1?r:i.nextTick;v.WritableState=y;var c=Object.create(n(50));c.inherits=n(32);var u={deprecate:n(277)},p=n(141),f=n(62).Buffer,d=o.Uint8Array||function(){};var h,m=n(142);function g(){}function y(e,t){s=s||n(40),e=e||{};var r=t instanceof s;this.objectMode=!!e.objectMode,r&&(this.objectMode=this.objectMode||!!e.writableObjectMode);var o=e.highWaterMark,c=e.writableHighWaterMark,u=this.objectMode?16:16384;this.highWaterMark=o||0===o?o:r&&(c||0===c)?c:u,this.highWaterMark=Math.floor(this.highWaterMark),this.finalCalled=!1,this.needDrain=!1,this.ending=!1,this.ended=!1,this.finished=!1,this.destroyed=!1;var p=!1===e.decodeStrings;this.decodeStrings=!p,this.defaultEncoding=e.defaultEncoding||"utf8",this.length=0,this.writing=!1,this.corked=0,this.sync=!0,this.bufferProcessing=!1,this.onwrite=function(e){!function(e,t){var n=e._writableState,r=n.sync,o=n.writecb;if(function(e){e.writing=!1,e.writecb=null,e.length-=e.writelen,e.writelen=0}(n),t)!function(e,t,n,r,o){--t.pendingcb,n?(i.nextTick(o,r),i.nextTick(_,e,t),e._writableState.errorEmitted=!0,e.emit("error",r)):(o(r),e._writableState.errorEmitted=!0,e.emit("error",r),_(e,t))}(e,n,r,t,o);else{var a=k(n);a||n.corked||n.bufferProcessing||!n.bufferedRequest||w(e,n),r?l(x,e,n,a,o):x(e,n,a,o)}}(t,e)},this.writecb=null,this.writelen=0,this.bufferedRequest=null,this.lastBufferedRequest=null,this.pendingcb=0,this.prefinished=!1,this.errorEmitted=!1,this.bufferedRequestCount=0,this.corkedRequestsFree=new a(this)}function v(e){if(s=s||n(40),!(h.call(v,this)||this instanceof s))return new v(e);this._writableState=new y(e,this),this.writable=!0,e&&("function"==typeof e.write&&(this._write=e.write),"function"==typeof e.writev&&(this._writev=e.writev),"function"==typeof e.destroy&&(this._destroy=e.destroy),"function"==typeof e.final&&(this._final=e.final)),p.call(this)}function b(e,t,n,r,o,i,a){t.writelen=r,t.writecb=a,t.writing=!0,t.sync=!0,n?e._writev(o,t.onwrite):e._write(o,i,t.onwrite),t.sync=!1}function x(e,t,n,r){n||function(e,t){0===t.length&&t.needDrain&&(t.needDrain=!1,e.emit("drain"))}(e,t),t.pendingcb--,r(),_(e,t)}function w(e,t){t.bufferProcessing=!0;var n=t.bufferedRequest;if(e._writev&&n&&n.next){var r=t.bufferedRequestCount,o=new Array(r),i=t.corkedRequestsFree;i.entry=n;for(var s=0,l=!0;n;)o[s]=n,n.isBuf||(l=!1),n=n.next,s+=1;o.allBuffers=l,b(e,t,!0,t.length,o,"",i.finish),t.pendingcb++,t.lastBufferedRequest=null,i.next?(t.corkedRequestsFree=i.next,i.next=null):t.corkedRequestsFree=new a(t),t.bufferedRequestCount=0}else{for(;n;){var c=n.chunk,u=n.encoding,p=n.callback;if(b(e,t,!1,t.objectMode?1:c.length,c,u,p),n=n.next,t.bufferedRequestCount--,t.writing)break}null===n&&(t.lastBufferedRequest=null)}t.bufferedRequest=n,t.bufferProcessing=!1}function k(e){return e.ending&&0===e.length&&null===e.bufferedRequest&&!e.finished&&!e.writing}function O(e,t){e._final((function(n){t.pendingcb--,n&&e.emit("error",n),t.prefinished=!0,e.emit("prefinish"),_(e,t)}))}function _(e,t){var n=k(t);return n&&(!function(e,t){t.prefinished||t.finalCalled||("function"==typeof e._final?(t.pendingcb++,t.finalCalled=!0,i.nextTick(O,e,t)):(t.prefinished=!0,e.emit("prefinish")))}(e,t),0===t.pendingcb&&(t.finished=!0,e.emit("finish"))),n}c.inherits(v,p),y.prototype.getBuffer=function(){for(var e=this.bufferedRequest,t=[];e;)t.push(e),e=e.next;return t},function(){try{Object.defineProperty(y.prototype,"buffer",{get:u.deprecate((function(){return this.getBuffer()}),"_writableState.buffer is deprecated. Use _writableState.getBuffer instead.","DEP0003")})}catch(e){}}(),"function"==typeof Symbol&&Symbol.hasInstance&&"function"==typeof Function.prototype[Symbol.hasInstance]?(h=Function.prototype[Symbol.hasInstance],Object.defineProperty(v,Symbol.hasInstance,{value:function(e){return!!h.call(this,e)||this===v&&(e&&e._writableState instanceof y)}})):h=function(e){return e instanceof this},v.prototype.pipe=function(){this.emit("error",new Error("Cannot pipe, not readable"))},v.prototype.write=function(e,t,n){var r,o=this._writableState,a=!1,s=!o.objectMode&&(r=e,f.isBuffer(r)||r instanceof d);return s&&!f.isBuffer(e)&&(e=function(e){return f.from(e)}(e)),"function"==typeof t&&(n=t,t=null),s?t="buffer":t||(t=o.defaultEncoding),"function"!=typeof n&&(n=g),o.ended?function(e,t){var n=new Error("write after end");e.emit("error",n),i.nextTick(t,n)}(this,n):(s||function(e,t,n,r){var o=!0,a=!1;return null===n?a=new TypeError("May not write null values to stream"):"string"==typeof n||void 0===n||t.objectMode||(a=new TypeError("Invalid non-string/buffer chunk")),a&&(e.emit("error",a),i.nextTick(r,a),o=!1),o}(this,o,e,n))&&(o.pendingcb++,a=function(e,t,n,r,o,i){if(!n){var a=function(e,t,n){e.objectMode||!1===e.decodeStrings||"string"!=typeof t||(t=f.from(t,n));return t}(t,r,o);r!==a&&(n=!0,o="buffer",r=a)}var s=t.objectMode?1:r.length;t.length+=s;var l=t.length<t.highWaterMark;l||(t.needDrain=!0);if(t.writing||t.corked){var c=t.lastBufferedRequest;t.lastBufferedRequest={chunk:r,encoding:o,isBuf:n,callback:i,next:null},c?c.next=t.lastBufferedRequest:t.bufferedRequest=t.lastBufferedRequest,t.bufferedRequestCount+=1}else b(e,t,!1,s,r,o,i);return l}(this,o,s,e,t,n)),a},v.prototype.cork=function(){this._writableState.corked++},v.prototype.uncork=function(){var e=this._writableState;e.corked&&(e.corked--,e.writing||e.corked||e.finished||e.bufferProcessing||!e.bufferedRequest||w(this,e))},v.prototype.setDefaultEncoding=function(e){if("string"==typeof e&&(e=e.toLowerCase()),!(["hex","utf8","utf-8","ascii","binary","base64","ucs2","ucs-2","utf16le","utf-16le","raw"].indexOf((e+"").toLowerCase())>-1))throw new TypeError("Unknown encoding: "+e);return this._writableState.defaultEncoding=e,this},Object.defineProperty(v.prototype,"writableHighWaterMark",{enumerable:!1,get:function(){return this._writableState.highWaterMark}}),v.prototype._write=function(e,t,n){n(new Error("_write() is not implemented"))},v.prototype._writev=null,v.prototype.end=function(e,t,n){var r=this._writableState;"function"==typeof e?(n=e,e=null,t=null):"function"==typeof t&&(n=t,t=null),null!=e&&this.write(e,t),r.corked&&(r.corked=1,this.uncork()),r.ending||r.finished||function(e,t,n){t.ending=!0,_(e,t),n&&(t.finished?i.nextTick(n):e.once("finish",n));t.ended=!0,e.writable=!1}(this,r,n)},Object.defineProperty(v.prototype,"destroyed",{get:function(){return void 0!==this._writableState&&this._writableState.destroyed},set:function(e){this._writableState&&(this._writableState.destroyed=e)}}),v.prototype.destroy=m.destroy,v.prototype._undestroy=m.undestroy,v.prototype._destroy=function(e,t){this.end(),t(e)}}).call(this,n(13),n(275).setImmediate,n(7))},function(e,t,n){"use strict";var r=n(62).Buffer,o=r.isEncoding||function(e){switch((e=""+e)&&e.toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":case"raw":return!0;default:return!1}};function i(e){var t;switch(this.encoding=function(e){var t=function(e){if(!e)return"utf8";for(var t;;)switch(e){case"utf8":case"utf-8":return"utf8";case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return"utf16le";case"latin1":case"binary":return"latin1";case"base64":case"ascii":case"hex":return e;default:if(t)return;e=(""+e).toLowerCase(),t=!0}}(e);if("string"!=typeof t&&(r.isEncoding===o||!o(e)))throw new Error("Unknown encoding: "+e);return t||e}(e),this.encoding){case"utf16le":this.text=l,this.end=c,t=4;break;case"utf8":this.fillLast=s,t=4;break;case"base64":this.text=u,this.end=p,t=3;break;default:return this.write=f,void(this.end=d)}this.lastNeed=0,this.lastTotal=0,this.lastChar=r.allocUnsafe(t)}function a(e){return e<=127?0:e>>5==6?2:e>>4==14?3:e>>3==30?4:e>>6==2?-1:-2}function s(e){var t=this.lastTotal-this.lastNeed,n=function(e,t,n){if(128!=(192&t[0]))return e.lastNeed=0,"�";if(e.lastNeed>1&&t.length>1){if(128!=(192&t[1]))return e.lastNeed=1,"�";if(e.lastNeed>2&&t.length>2&&128!=(192&t[2]))return e.lastNeed=2,"�"}}(this,e);return void 0!==n?n:this.lastNeed<=e.length?(e.copy(this.lastChar,t,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal)):(e.copy(this.lastChar,t,0,e.length),void(this.lastNeed-=e.length))}function l(e,t){if((e.length-t)%2==0){var n=e.toString("utf16le",t);if(n){var r=n.charCodeAt(n.length-1);if(r>=55296&&r<=56319)return this.lastNeed=2,this.lastTotal=4,this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1],n.slice(0,-1)}return n}return this.lastNeed=1,this.lastTotal=2,this.lastChar[0]=e[e.length-1],e.toString("utf16le",t,e.length-1)}function c(e){var t=e&&e.length?this.write(e):"";if(this.lastNeed){var n=this.lastTotal-this.lastNeed;return t+this.lastChar.toString("utf16le",0,n)}return t}function u(e,t){var n=(e.length-t)%3;return 0===n?e.toString("base64",t):(this.lastNeed=3-n,this.lastTotal=3,1===n?this.lastChar[0]=e[e.length-1]:(this.lastChar[0]=e[e.length-2],this.lastChar[1]=e[e.length-1]),e.toString("base64",t,e.length-n))}function p(e){var t=e&&e.length?this.write(e):"";return this.lastNeed?t+this.lastChar.toString("base64",0,3-this.lastNeed):t}function f(e){return e.toString(this.encoding)}function d(e){return e&&e.length?this.write(e):""}t.StringDecoder=i,i.prototype.write=function(e){if(0===e.length)return"";var t,n;if(this.lastNeed){if(void 0===(t=this.fillLast(e)))return"";n=this.lastNeed,this.lastNeed=0}else n=0;return n<e.length?t?t+this.text(e,n):this.text(e,n):t||""},i.prototype.end=function(e){var t=e&&e.length?this.write(e):"";return this.lastNeed?t+"�":t},i.prototype.text=function(e,t){var n=function(e,t,n){var r=t.length-1;if(r<n)return 0;var o=a(t[r]);if(o>=0)return o>0&&(e.lastNeed=o-1),o;if(--r<n||-2===o)return 0;if((o=a(t[r]))>=0)return o>0&&(e.lastNeed=o-2),o;if(--r<n||-2===o)return 0;if((o=a(t[r]))>=0)return o>0&&(2===o?o=0:e.lastNeed=o-3),o;return 0}(this,e,t);if(!this.lastNeed)return e.toString("utf8",t);this.lastTotal=n;var r=e.length-(n-this.lastNeed);return e.copy(this.lastChar,0,r),e.toString("utf8",t,r)},i.prototype.fillLast=function(e){if(this.lastNeed<=e.length)return e.copy(this.lastChar,this.lastTotal-this.lastNeed,0,this.lastNeed),this.lastChar.toString(this.encoding,0,this.lastTotal);e.copy(this.lastChar,this.lastTotal-this.lastNeed,0,e.length),this.lastNeed-=e.length}},function(e,t,n){"use strict";e.exports=a;var r=n(40),o=Object.create(n(50));function i(e,t){var n=this._transformState;n.transforming=!1;var r=n.writecb;if(!r)return this.emit("error",new Error("write callback called multiple times"));n.writechunk=null,n.writecb=null,null!=t&&this.push(t),r(e);var o=this._readableState;o.reading=!1,(o.needReadable||o.length<o.highWaterMark)&&this._read(o.highWaterMark)}function a(e){if(!(this instanceof a))return new a(e);r.call(this,e),this._transformState={afterTransform:i.bind(this),needTransform:!1,transforming:!1,writecb:null,writechunk:null,writeencoding:null},this._readableState.needReadable=!0,this._readableState.sync=!1,e&&("function"==typeof e.transform&&(this._transform=e.transform),"function"==typeof e.flush&&(this._flush=e.flush)),this.on("prefinish",s)}function s(){var e=this;"function"==typeof this._flush?this._flush((function(t,n){l(e,t,n)})):l(this,null,null)}function l(e,t,n){if(t)return e.emit("error",t);if(null!=n&&e.push(n),e._writableState.length)throw new Error("Calling transform done when ws.length != 0");if(e._transformState.transforming)throw new Error("Calling transform done when still transforming");return e.push(null)}o.inherits=n(32),o.inherits(a,r),a.prototype.push=function(e,t){return this._transformState.needTransform=!1,r.prototype.push.call(this,e,t)},a.prototype._transform=function(e,t,n){throw new Error("_transform() is not implemented")},a.prototype._write=function(e,t,n){var r=this._transformState;if(r.writecb=n,r.writechunk=e,r.writeencoding=t,!r.transforming){var o=this._readableState;(r.needTransform||o.needReadable||o.length<o.highWaterMark)&&this._read(o.highWaterMark)}},a.prototype._read=function(e){var t=this._transformState;null!==t.writechunk&&t.writecb&&!t.transforming?(t.transforming=!0,this._transform(t.writechunk,t.writeencoding,t.afterTransform)):t.needTransform=!0},a.prototype._destroy=function(e,t){var n=this;r.prototype._destroy.call(this,e,(function(e){t(e),n.emit("close")}))}},function(e,t,n){"use strict";(function(t){var r=n(31),o=n(26),i=n(284);e.exports=function(e,n,a){try{e=o.stripHash(e);var s=n._add(e),l={url:e,extension:o.getExtension(e)};return function(e,t){return new Promise((function(n,o){var a=i.all(t.resolve);a=i.filter(a,"canRead",e),i.sort(a),i.run(a,"read",e).then(n,(function(t){!t||t instanceof SyntaxError?o(r.syntax('Unable to resolve $ref pointer "%s"',e.url)):o(t)}))}))}(l,a).then((function(e){return s.pathType=e.plugin.name,l.data=e.result,function(e,n){return new Promise((function(o,a){var s=i.all(n.parse),l=i.filter(s,"canParse",e),c=l.length>0?l:s;i.sort(c),i.run(c,"parse",e).then((function(n){!n.plugin.allowEmpty&&(i=n.result,void 0===i||"object"==typeof i&&0===Object.keys(i).length||"string"==typeof i&&0===i.trim().length||t.isBuffer(i)&&0===i.length)?a(r.syntax('Error parsing "%s" as %s. \nParsed value is empty',e.url,n.plugin.name)):o(n);var i}),(function(t){t?(t=t instanceof Error?t:new Error(t),a(r.syntax(t,"Error parsing %s",e.url))):a(r.syntax("Unable to parse %s",e.url))}))}))}(l,a)})).then((function(e){return s.value=e.result,e.result}))}catch(e){return Promise.reject(e)}}}).call(this,n(14).Buffer)},function(e,t,n){"use strict";(function(t,n){var r=t.process&&n.nextTick||t.setImmediate||function(e){setTimeout(e,0)};e.exports=function(e,t){return e?void t.then((function(t){r((function(){e(null,t)}))}),(function(t){r((function(){e(t)}))})):t}}).call(this,n(7),n(13))},function(e,t){},function(e,t,n){"use strict";(function(t){var r=n(51),o=t.env.NODE_DISABLE_COLORS?{red:"",yellow:"",green:"",normal:""}:{red:"",yellow:"",green:"",normal:""};function i(e){for(var t=[],n=function(e){t.find((function(t,n,o){return function(e,t){function n(e,t){return r.stringify(e)===r.stringify(Object.assign({},e,t))}return n(e,t)&&n(t,e)}(t,e)}))||t.push(e)},o=0,i=e;o<i.length;o++){n(i[o])}return t}String.prototype.toCamelCase=function(){return this.toLowerCase().replace(/[-_ \/\.](.)/g,(function(e,t){return t.toUpperCase()}))};function a(e){var t=(e=e.replace("[]","Array")).split("/");return t[0]=t[0].replace(/[^A-Za-z0-9_\-\.]+|\s+/gm,"_"),t.join("/")}e.exports={colour:o,uniqueOnly:function(e,t,n){return n.indexOf(e)===t},hasDuplicates:function(e){return new Set(e).size!==e.length},allSame:function(e){return new Set(e).size<=1},distinctArray:function(e){return e.length===i(e).length},firstDupe:function(e){return e.find((function(t,n,r){return e.indexOf(t)<n}))},hash:function(e){var t=0;if(0===e.length)return t;for(var n=0;n<e.length;n++)t=(t<<5)-t+e.charCodeAt(n),t|=0;return t},parameterTypeProperties:["format","minimum","maximum","exclusiveMinimum","exclusiveMaximum","minLength","maxLength","multipleOf","minItems","maxItems","uniqueItems","minProperties","maxProperties","additionalProperties","pattern","enum","default"],arrayProperties:["items","minItems","maxItems","uniqueItems"],httpMethods:["get","post","put","delete","patch","head","options","trace"],sanitise:a,sanitiseAll:function(e){return a(e.split("/").join("_"))}}}).call(this,n(13))},function(e,t){e.exports=function(){}},function(e,t,n){var r;r=function(){var e=JSON.parse('{"$":"dollar","%":"percent","&":"and","<":"less",">":"greater","|":"or","¢":"cent","£":"pound","¤":"currency","¥":"yen","©":"(c)","ª":"a","®":"(r)","º":"o","À":"A","Á":"A","Â":"A","Ã":"A","Ä":"A","Å":"A","Æ":"AE","Ç":"C","È":"E","É":"E","Ê":"E","Ë":"E","Ì":"I","Í":"I","Î":"I","Ï":"I","Ð":"D","Ñ":"N","Ò":"O","Ó":"O","Ô":"O","Õ":"O","Ö":"O","Ø":"O","Ù":"U","Ú":"U","Û":"U","Ü":"U","Ý":"Y","Þ":"TH","ß":"ss","à":"a","á":"a","â":"a","ã":"a","ä":"a","å":"a","æ":"ae","ç":"c","è":"e","é":"e","ê":"e","ë":"e","ì":"i","í":"i","î":"i","ï":"i","ð":"d","ñ":"n","ò":"o","ó":"o","ô":"o","õ":"o","ö":"o","ø":"o","ù":"u","ú":"u","û":"u","ü":"u","ý":"y","þ":"th","ÿ":"y","Ā":"A","ā":"a","Ă":"A","ă":"a","Ą":"A","ą":"a","Ć":"C","ć":"c","Č":"C","č":"c","Ď":"D","ď":"d","Đ":"DJ","đ":"dj","Ē":"E","ē":"e","Ė":"E","ė":"e","Ę":"e","ę":"e","Ě":"E","ě":"e","Ğ":"G","ğ":"g","Ģ":"G","ģ":"g","Ĩ":"I","ĩ":"i","Ī":"i","ī":"i","Į":"I","į":"i","İ":"I","ı":"i","Ķ":"k","ķ":"k","Ļ":"L","ļ":"l","Ľ":"L","ľ":"l","Ł":"L","ł":"l","Ń":"N","ń":"n","Ņ":"N","ņ":"n","Ň":"N","ň":"n","Ő":"O","ő":"o","Œ":"OE","œ":"oe","Ŕ":"R","ŕ":"r","Ř":"R","ř":"r","Ś":"S","ś":"s","Ş":"S","ş":"s","Š":"S","š":"s","Ţ":"T","ţ":"t","Ť":"T","ť":"t","Ũ":"U","ũ":"u","Ū":"u","ū":"u","Ů":"U","ů":"u","Ű":"U","ű":"u","Ų":"U","ų":"u","Ŵ":"W","ŵ":"w","Ŷ":"Y","ŷ":"y","Ÿ":"Y","Ź":"Z","ź":"z","Ż":"Z","ż":"z","Ž":"Z","ž":"z","ƒ":"f","Ơ":"O","ơ":"o","Ư":"U","ư":"u","Lj":"LJ","lj":"lj","Nj":"NJ","nj":"nj","Ș":"S","ș":"s","Ț":"T","ț":"t","˚":"o","Ά":"A","Έ":"E","Ή":"H","Ί":"I","Ό":"O","Ύ":"Y","Ώ":"W","ΐ":"i","Α":"A","Β":"B","Γ":"G","Δ":"D","Ε":"E","Ζ":"Z","Η":"H","Θ":"8","Ι":"I","Κ":"K","Λ":"L","Μ":"M","Ν":"N","Ξ":"3","Ο":"O","Π":"P","Ρ":"R","Σ":"S","Τ":"T","Υ":"Y","Φ":"F","Χ":"X","Ψ":"PS","Ω":"W","Ϊ":"I","Ϋ":"Y","ά":"a","έ":"e","ή":"h","ί":"i","ΰ":"y","α":"a","β":"b","γ":"g","δ":"d","ε":"e","ζ":"z","η":"h","θ":"8","ι":"i","κ":"k","λ":"l","μ":"m","ν":"n","ξ":"3","ο":"o","π":"p","ρ":"r","ς":"s","σ":"s","τ":"t","υ":"y","φ":"f","χ":"x","ψ":"ps","ω":"w","ϊ":"i","ϋ":"y","ό":"o","ύ":"y","ώ":"w","Ё":"Yo","Ђ":"DJ","Є":"Ye","І":"I","Ї":"Yi","Ј":"J","Љ":"LJ","Њ":"NJ","Ћ":"C","Џ":"DZ","А":"A","Б":"B","В":"V","Г":"G","Д":"D","Е":"E","Ж":"Zh","З":"Z","И":"I","Й":"J","К":"K","Л":"L","М":"M","Н":"N","О":"O","П":"P","Р":"R","С":"S","Т":"T","У":"U","Ф":"F","Х":"H","Ц":"C","Ч":"Ch","Ш":"Sh","Щ":"Sh","Ъ":"U","Ы":"Y","Ь":"","Э":"E","Ю":"Yu","Я":"Ya","а":"a","б":"b","в":"v","г":"g","д":"d","е":"e","ж":"zh","з":"z","и":"i","й":"j","к":"k","л":"l","м":"m","н":"n","о":"o","п":"p","р":"r","с":"s","т":"t","у":"u","ф":"f","х":"h","ц":"c","ч":"ch","ш":"sh","щ":"sh","ъ":"u","ы":"y","ь":"","э":"e","ю":"yu","я":"ya","ё":"yo","ђ":"dj","є":"ye","і":"i","ї":"yi","ј":"j","љ":"lj","њ":"nj","ћ":"c","ѝ":"u","џ":"dz","Ґ":"G","ґ":"g","Ғ":"GH","ғ":"gh","Қ":"KH","қ":"kh","Ң":"NG","ң":"ng","Ү":"UE","ү":"ue","Ұ":"U","ұ":"u","Һ":"H","һ":"h","Ә":"AE","ә":"ae","Ө":"OE","ө":"oe","฿":"baht","ა":"a","ბ":"b","გ":"g","დ":"d","ე":"e","ვ":"v","ზ":"z","თ":"t","ი":"i","კ":"k","ლ":"l","მ":"m","ნ":"n","ო":"o","პ":"p","ჟ":"zh","რ":"r","ს":"s","ტ":"t","უ":"u","ფ":"f","ქ":"k","ღ":"gh","ყ":"q","შ":"sh","ჩ":"ch","ც":"ts","ძ":"dz","წ":"ts","ჭ":"ch","ხ":"kh","ჯ":"j","ჰ":"h","Ẁ":"W","ẁ":"w","Ẃ":"W","ẃ":"w","Ẅ":"W","ẅ":"w","ẞ":"SS","Ạ":"A","ạ":"a","Ả":"A","ả":"a","Ấ":"A","ấ":"a","Ầ":"A","ầ":"a","Ẩ":"A","ẩ":"a","Ẫ":"A","ẫ":"a","Ậ":"A","ậ":"a","Ắ":"A","ắ":"a","Ằ":"A","ằ":"a","Ẳ":"A","ẳ":"a","Ẵ":"A","ẵ":"a","Ặ":"A","ặ":"a","Ẹ":"E","ẹ":"e","Ẻ":"E","ẻ":"e","Ẽ":"E","ẽ":"e","Ế":"E","ế":"e","Ề":"E","ề":"e","Ể":"E","ể":"e","Ễ":"E","ễ":"e","Ệ":"E","ệ":"e","Ỉ":"I","ỉ":"i","Ị":"I","ị":"i","Ọ":"O","ọ":"o","Ỏ":"O","ỏ":"o","Ố":"O","ố":"o","Ồ":"O","ồ":"o","Ổ":"O","ổ":"o","Ỗ":"O","ỗ":"o","Ộ":"O","ộ":"o","Ớ":"O","ớ":"o","Ờ":"O","ờ":"o","Ở":"O","ở":"o","Ỡ":"O","ỡ":"o","Ợ":"O","ợ":"o","Ụ":"U","ụ":"u","Ủ":"U","ủ":"u","Ứ":"U","ứ":"u","Ừ":"U","ừ":"u","Ử":"U","ử":"u","Ữ":"U","ữ":"u","Ự":"U","ự":"u","Ỳ":"Y","ỳ":"y","Ỵ":"Y","ỵ":"y","Ỷ":"Y","ỷ":"y","Ỹ":"Y","ỹ":"y","‘":"\'","’":"\'","“":"\\"","”":"\\"","†":"+","•":"*","…":"...","₠":"ecu","₢":"cruzeiro","₣":"french franc","₤":"lira","₥":"mill","₦":"naira","₧":"peseta","₨":"rupee","₩":"won","₪":"new shequel","₫":"dong","€":"euro","₭":"kip","₮":"tugrik","₯":"drachma","₰":"penny","₱":"peso","₲":"guarani","₳":"austral","₴":"hryvnia","₵":"cedi","₸":"kazakhstani tenge","₹":"indian rupee","₽":"russian ruble","₿":"bitcoin","℠":"sm","™":"tm","∂":"d","∆":"delta","∑":"sum","∞":"infinity","♥":"love","元":"yuan","円":"yen","﷼":"rial"}'),t=JSON.parse('{"vi":{"Đ":"D","đ":"d"}}');function n(n,r){if("string"!=typeof n)throw new Error("slugify: string argument expected");var o=t[(r="string"==typeof r?{replacement:r}:r||{}).locale]||{},i=r.replacement||"-",a=n.split("").reduce((function(t,n){return t+(o[n]||e[n]||n)}),"").replace(r.remove||/[^\w\s$*_+~.()'"!\-:@]+/g,"").trim().replace(new RegExp("[\\s"+i+"]+","g"),i);return r.lower&&(a=a.toLowerCase()),r.strict&&(a=a.replace(new RegExp("[^a-zA-Z0-9"+i+"]","g"),"")),a}return n.extend=function(t){for(var n in t)e[n]=t[n]},n},e.exports=r(),e.exports.default=r()},function(e,t,n){"use strict";n.r(t),function(e){n.d(t,"createGlobalStyle",(function(){return nt})),n.d(t,"css",(function(){return be})),n.d(t,"isStyledComponent",(function(){return S})),n.d(t,"keyframes",(function(){return ot})),n.d(t,"ServerStyleSheet",(function(){return Ye})),n.d(t,"StyleSheetConsumer",(function(){return Ge})),n.d(t,"StyleSheetContext",(function(){return Qe})),n.d(t,"StyleSheetManager",(function(){return Xe})),n.d(t,"ThemeConsumer",(function(){return He})),n.d(t,"ThemeContext",(function(){return We})),n.d(t,"ThemeProvider",(function(){return Ve})),n.d(t,"withTheme",(function(){return it})),n.d(t,"__DO_NOT_USE_OR_YOU_WILL_BE_HAUNTED_BY_SPOOKY_GHOSTS",(function(){return at}));var r=n(95),o=n.n(r),i=n(153),a=n.n(i),s=n(0),l=n.n(s),c=n(154),u=n(96),p=n(97),f=(n(19),n(161)),d=n(160),h=function(e,t){for(var n=[e[0]],r=0,o=t.length;r<o;r+=1)n.push(t[r],e[r+1]);return n},m="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},g=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},y=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),v=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},b=function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)},x=function(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t},w=function(e){return"object"===(void 0===e?"undefined":m(e))&&e.constructor===Object},k=Object.freeze([]),O=Object.freeze({});function _(e){return"function"==typeof e}function E(e){return e.displayName||e.name||"Component"}function S(e){return e&&"string"==typeof e.styledComponentId}var T=void 0!==e&&(e.env.REACT_APP_SC_ATTR||e.env.SC_ATTR)||"data-styled",j="undefined"!=typeof window&&"HTMLElement"in window,C="boolean"==typeof SC_DISABLE_SPEEDY&&SC_DISABLE_SPEEDY||void 0!==e&&(e.env.REACT_APP_SC_DISABLE_SPEEDY||e.env.SC_DISABLE_SPEEDY)||!1,I={};var A=function(e){function t(n){g(this,t);for(var r=arguments.length,o=Array(r>1?r-1:0),i=1;i<r;i++)o[i-1]=arguments[i];var a=x(this,e.call(this,"An error occurred. See https://github.com/styled-components/styled-components/blob/master/packages/styled-components/src/utils/errors.md#"+n+" for more information."+(o.length>0?" Additional arguments: "+o.join(", "):"")));return x(a)}return b(t,e),t}(Error),P=/^[^\S\n]*?\/\* sc-component-id:\s*(\S+)\s+\*\//gm,R=function(e){var t=""+(e||""),n=[];return t.replace(P,(function(e,t,r){return n.push({componentId:t,matchIndex:r}),e})),n.map((function(e,r){var o=e.componentId,i=e.matchIndex,a=n[r+1];return{componentId:o,cssFromDOM:a?t.slice(i,a.matchIndex):t.slice(i)}}))},N=/^\s*\/\/.*$/gm,L=new o.a({global:!1,cascade:!0,keyframe:!1,prefix:!1,compress:!1,semicolon:!0}),M=new o.a({global:!1,cascade:!0,keyframe:!1,prefix:!0,compress:!1,semicolon:!1}),D=[],F=function(e){if(-2===e){var t=D;return D=[],t}},z=a()((function(e){D.push(e)})),U=void 0,B=void 0,$=void 0,q=function(e,t,n){return t>0&&-1!==n.slice(0,t).indexOf(B)&&n.slice(t-B.length,t)!==B?"."+U:e};M.use([function(e,t,n){2===e&&n.length&&n[0].lastIndexOf(B)>0&&(n[0]=n[0].replace($,q))},z,F]),L.use([z,F]);var W=function(e){return L("",e)};function H(e,t,n){var r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:"&",o=e.join("").replace(N,""),i=t&&n?n+" "+t+" { "+o+" }":o;return U=r,B=t,$=new RegExp("\\"+B+"\\b","g"),M(n||!t?"":t,i)}var V=function(){return n.nc},Y=function(e,t,n){n&&((e[t]||(e[t]=Object.create(null)))[n]=!0)},Q=function(e,t){e[t]=Object.create(null)},G=function(e){return function(t,n){return void 0!==e[t]&&e[t][n]}},X=function(e){var t="";for(var n in e)t+=Object.keys(e[n]).join(" ")+" ";return t.trim()},K=function(e){if(e.sheet)return e.sheet;for(var t=e.ownerDocument.styleSheets.length,n=0;n<t;n+=1){var r=e.ownerDocument.styleSheets[n];if(r.ownerNode===e)return r}throw new A(10)},Z=function(e,t,n){if(!t)return!1;var r=e.cssRules.length;try{e.insertRule(t,n<=r?n:r)}catch(e){return!1}return!0},J=function(e){return"\n/* sc-component-id: "+e+" */\n"},ee=function(e,t){for(var n=0,r=0;r<=t;r+=1)n+=e[r];return n},te=function(e,t){return function(n){var r=V();return"<style "+[r&&'nonce="'+r+'"',T+'="'+X(t)+'"','data-styled-version="4.4.1"',n].filter(Boolean).join(" ")+">"+e()+"</style>"}},ne=function(e,t){return function(){var n,r=((n={})[T]=X(t),n["data-styled-version"]="4.4.1",n),o=V();return o&&(r.nonce=o),l.a.createElement("style",v({},r,{dangerouslySetInnerHTML:{__html:e()}}))}},re=function(e){return function(){return Object.keys(e)}},oe=function(e,t){return e.createTextNode(J(t))},ie=function e(t,n){var r=void 0===t?Object.create(null):t,o=void 0===n?Object.create(null):n,i=function(e){var t=o[e];return void 0!==t?t:o[e]=[""]},a=function(){var e="";for(var t in o){var n=o[t][0];n&&(e+=J(t)+n)}return e};return{clone:function(){var t=function(e){var t=Object.create(null);for(var n in e)t[n]=v({},e[n]);return t}(r),n=Object.create(null);for(var i in o)n[i]=[o[i][0]];return e(t,n)},css:a,getIds:re(o),hasNameForId:G(r),insertMarker:i,insertRules:function(e,t,n){i(e)[0]+=t.join(" "),Y(r,e,n)},removeRules:function(e){var t=o[e];void 0!==t&&(t[0]="",Q(r,e))},sealed:!1,styleTag:null,toElement:ne(a,r),toHTML:te(a,r)}},ae=function(e,t,n,r,o){if(j&&!n){var i=function(e,t,n){var r=document;e?r=e.ownerDocument:t&&(r=t.ownerDocument);var o=r.createElement("style");o.setAttribute(T,""),o.setAttribute("data-styled-version","4.4.1");var i=V();if(i&&o.setAttribute("nonce",i),o.appendChild(r.createTextNode("")),e&&!t)e.appendChild(o);else{if(!t||!e||!t.parentNode)throw new A(6);t.parentNode.insertBefore(o,n?t:t.nextSibling)}return o}(e,t,r);return C?function(e,t){var n=Object.create(null),r=Object.create(null),o=void 0!==t,i=!1,a=function(t){var o=r[t];return void 0!==o?o:(r[t]=oe(e.ownerDocument,t),e.appendChild(r[t]),n[t]=Object.create(null),r[t])},s=function(){var e="";for(var t in r)e+=r[t].data;return e};return{clone:function(){throw new A(5)},css:s,getIds:re(r),hasNameForId:G(n),insertMarker:a,insertRules:function(e,r,s){for(var l=a(e),c=[],u=r.length,p=0;p<u;p+=1){var f=r[p],d=o;if(d&&-1!==f.indexOf("@import"))c.push(f);else{d=!1;var h=p===u-1?"":" ";l.appendData(""+f+h)}}Y(n,e,s),o&&c.length>0&&(i=!0,t().insertRules(e+"-import",c))},removeRules:function(a){var s=r[a];if(void 0!==s){var l=oe(e.ownerDocument,a);e.replaceChild(l,s),r[a]=l,Q(n,a),o&&i&&t().removeRules(a+"-import")}},sealed:!1,styleTag:e,toElement:ne(s,n),toHTML:te(s,n)}}(i,o):function(e,t){var n=Object.create(null),r=Object.create(null),o=[],i=void 0!==t,a=!1,s=function(e){var t=r[e];return void 0!==t?t:(r[e]=o.length,o.push(0),Q(n,e),r[e])},l=function(){var t=K(e).cssRules,n="";for(var i in r){n+=J(i);for(var a=r[i],s=ee(o,a),l=s-o[a];l<s;l+=1){var c=t[l];void 0!==c&&(n+=c.cssText)}}return n};return{clone:function(){throw new A(5)},css:l,getIds:re(r),hasNameForId:G(n),insertMarker:s,insertRules:function(r,l,c){for(var u=s(r),p=K(e),f=ee(o,u),d=0,h=[],m=l.length,g=0;g<m;g+=1){var y=l[g],v=i;v&&-1!==y.indexOf("@import")?h.push(y):Z(p,y,f+d)&&(v=!1,d+=1)}i&&h.length>0&&(a=!0,t().insertRules(r+"-import",h)),o[u]+=d,Y(n,r,c)},removeRules:function(s){var l=r[s];if(void 0!==l&&!1!==e.isConnected){var c=o[l];!function(e,t,n){for(var r=t-n,o=t;o>r;o-=1)e.deleteRule(o)}(K(e),ee(o,l)-1,c),o[l]=0,Q(n,s),i&&a&&t().removeRules(s+"-import")}},sealed:!1,styleTag:e,toElement:ne(l,n),toHTML:te(l,n)}}(i,o)}return ie()},se=/\s+/,le=void 0;le=j?C?40:1e3:-1;var ce=0,ue=void 0,pe=function(){function e(){var t=this,n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:j?document.head:null,r=arguments.length>1&&void 0!==arguments[1]&&arguments[1];g(this,e),this.getImportRuleTag=function(){var e=t.importRuleTag;if(void 0!==e)return e;var n=t.tags[0];return t.importRuleTag=ae(t.target,n?n.styleTag:null,t.forceServer,!0)},ce+=1,this.id=ce,this.forceServer=r,this.target=r?null:n,this.tagMap={},this.deferred={},this.rehydratedNames={},this.ignoreRehydratedNames={},this.tags=[],this.capacity=1,this.clones=[]}return e.prototype.rehydrate=function(){if(!j||this.forceServer)return this;var e=[],t=[],n=!1,r=document.querySelectorAll("style["+T+'][data-styled-version="4.4.1"]'),o=r.length;if(!o)return this;for(var i=0;i<o;i+=1){var a=r[i];n||(n=!!a.getAttribute("data-styled-streamed"));for(var s,l=(a.getAttribute(T)||"").trim().split(se),c=l.length,u=0;u<c;u+=1)s=l[u],this.rehydratedNames[s]=!0;t.push.apply(t,R(a.textContent)),e.push(a)}var p=t.length;if(!p)return this;var f=this.makeTag(null);!function(e,t,n){for(var r=0,o=n.length;r<o;r+=1){var i=n[r],a=i.componentId,s=i.cssFromDOM,l=W(s);e.insertRules(a,l)}for(var c=0,u=t.length;c<u;c+=1){var p=t[c];p.parentNode&&p.parentNode.removeChild(p)}}(f,e,t),this.capacity=Math.max(1,le-p),this.tags.push(f);for(var d=0;d<p;d+=1)this.tagMap[t[d].componentId]=f;return this},e.reset=function(){var t=arguments.length>0&&void 0!==arguments[0]&&arguments[0];ue=new e(void 0,t).rehydrate()},e.prototype.clone=function(){var t=new e(this.target,this.forceServer);return this.clones.push(t),t.tags=this.tags.map((function(e){for(var n=e.getIds(),r=e.clone(),o=0;o<n.length;o+=1)t.tagMap[n[o]]=r;return r})),t.rehydratedNames=v({},this.rehydratedNames),t.deferred=v({},this.deferred),t},e.prototype.sealAllTags=function(){this.capacity=1,this.tags.forEach((function(e){e.sealed=!0}))},e.prototype.makeTag=function(e){var t=e?e.styleTag:null;return ae(this.target,t,this.forceServer,!1,this.getImportRuleTag)},e.prototype.getTagForId=function(e){var t=this.tagMap[e];if(void 0!==t&&!t.sealed)return t;var n=this.tags[this.tags.length-1];return this.capacity-=1,0===this.capacity&&(this.capacity=le,n=this.makeTag(n),this.tags.push(n)),this.tagMap[e]=n},e.prototype.hasId=function(e){return void 0!==this.tagMap[e]},e.prototype.hasNameForId=function(e,t){if(void 0===this.ignoreRehydratedNames[e]&&this.rehydratedNames[t])return!0;var n=this.tagMap[e];return void 0!==n&&n.hasNameForId(e,t)},e.prototype.deferredInject=function(e,t){if(void 0===this.tagMap[e]){for(var n=this.clones,r=0;r<n.length;r+=1)n[r].deferredInject(e,t);this.getTagForId(e).insertMarker(e),this.deferred[e]=t}},e.prototype.inject=function(e,t,n){for(var r=this.clones,o=0;o<r.length;o+=1)r[o].inject(e,t,n);var i=this.getTagForId(e);if(void 0!==this.deferred[e]){var a=this.deferred[e].concat(t);i.insertRules(e,a,n),this.deferred[e]=void 0}else i.insertRules(e,t,n)},e.prototype.remove=function(e){var t=this.tagMap[e];if(void 0!==t){for(var n=this.clones,r=0;r<n.length;r+=1)n[r].remove(e);t.removeRules(e),this.ignoreRehydratedNames[e]=!0,this.deferred[e]=void 0}},e.prototype.toHTML=function(){return this.tags.map((function(e){return e.toHTML()})).join("")},e.prototype.toReactElements=function(){var e=this.id;return this.tags.map((function(t,n){var r="sc-"+e+"-"+n;return Object(s.cloneElement)(t.toElement(),{key:r})}))},y(e,null,[{key:"master",get:function(){return ue||(ue=(new e).rehydrate())}},{key:"instance",get:function(){return e.master}}]),e}(),fe=function(){function e(t,n){var r=this;g(this,e),this.inject=function(e){e.hasNameForId(r.id,r.name)||e.inject(r.id,r.rules,r.name)},this.toString=function(){throw new A(12,String(r.name))},this.name=t,this.rules=n,this.id="sc-keyframes-"+t}return e.prototype.getName=function(){return this.name},e}(),de=/([A-Z])/g,he=/^ms-/;function me(e){return e.replace(de,"-$1").toLowerCase().replace(he,"-ms-")}var ge=function(e){return null==e||!1===e||""===e},ye=function e(t,n){var r=[];return Object.keys(t).forEach((function(n){if(!ge(t[n])){if(w(t[n]))return r.push.apply(r,e(t[n],n)),r;if(_(t[n]))return r.push(me(n)+":",t[n],";"),r;r.push(me(n)+": "+(o=n,null==(i=t[n])||"boolean"==typeof i||""===i?"":"number"!=typeof i||0===i||o in c.a?String(i).trim():i+"px")+";")}var o,i;return r})),n?[n+" {"].concat(r,["}"]):r};function ve(e,t,n){if(Array.isArray(e)){for(var r,o=[],i=0,a=e.length;i<a;i+=1)null!==(r=ve(e[i],t,n))&&(Array.isArray(r)?o.push.apply(o,r):o.push(r));return o}return ge(e)?null:S(e)?"."+e.styledComponentId:_(e)?"function"!=typeof(s=e)||s.prototype&&s.prototype.isReactComponent||!t?e:ve(e(t),t,n):e instanceof fe?n?(e.inject(n),e.getName()):e:w(e)?ye(e):e.toString();var s}function be(e){for(var t=arguments.length,n=Array(t>1?t-1:0),r=1;r<t;r++)n[r-1]=arguments[r];return _(e)||w(e)?ve(h(k,[e].concat(n))):ve(h(e,n))}function xe(e){for(var t,n=0|e.length,r=0|n,o=0;n>=4;)t=1540483477*(65535&(t=255&e.charCodeAt(o)|(255&e.charCodeAt(++o))<<8|(255&e.charCodeAt(++o))<<16|(255&e.charCodeAt(++o))<<24))+((1540483477*(t>>>16)&65535)<<16),r=1540483477*(65535&r)+((1540483477*(r>>>16)&65535)<<16)^(t=1540483477*(65535&(t^=t>>>24))+((1540483477*(t>>>16)&65535)<<16)),n-=4,++o;switch(n){case 3:r^=(255&e.charCodeAt(o+2))<<16;case 2:r^=(255&e.charCodeAt(o+1))<<8;case 1:r=1540483477*(65535&(r^=255&e.charCodeAt(o)))+((1540483477*(r>>>16)&65535)<<16)}return((r=1540483477*(65535&(r^=r>>>13))+((1540483477*(r>>>16)&65535)<<16))^r>>>15)>>>0}var we=function(e){return String.fromCharCode(e+(e>25?39:97))};function ke(e){var t="",n=void 0;for(n=e;n>52;n=Math.floor(n/52))t=we(n%52)+t;return we(n%52)+t}function Oe(e,t){for(var n=0;n<e.length;n+=1){var r=e[n];if(Array.isArray(r)&&!Oe(r,t))return!1;if(_(r)&&!S(r))return!1}return!t.some((function(e){return _(e)||function(e){for(var t in e)if(_(e[t]))return!0;return!1}(e)}))}var _e,Ee=function(e){return ke(xe(e))},Se=function(){function e(t,n,r){g(this,e),this.rules=t,this.isStatic=Oe(t,n),this.componentId=r,pe.master.hasId(r)||pe.master.deferredInject(r,[])}return e.prototype.generateAndInjectStyles=function(e,t){var n=this.isStatic,r=this.componentId,o=this.lastClassName;if(j&&n&&"string"==typeof o&&t.hasNameForId(r,o))return o;var i=ve(this.rules,e,t),a=Ee(this.componentId+i.join(""));return t.hasNameForId(r,a)||t.inject(this.componentId,H(i,"."+a,void 0,r),a),this.lastClassName=a,a},e.generateName=function(e){return Ee(e)},e}(),Te=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:O,r=!!n&&e.theme===n.theme,o=e.theme&&!r?e.theme:t||n.theme;return o},je=/[[\].#*$><+~=|^:(),"'`-]+/g,Ce=/(^-|-$)/g;function Ie(e){return e.replace(je,"-").replace(Ce,"")}function Ae(e){return"string"==typeof e&&!0}var Pe={childContextTypes:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDerivedStateFromProps:!0,propTypes:!0,type:!0},Re={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},Ne=((_e={})[u.ForwardRef]={$$typeof:!0,render:!0},_e),Le=Object.defineProperty,Me=Object.getOwnPropertyNames,De=Object.getOwnPropertySymbols,Fe=void 0===De?function(){return[]}:De,ze=Object.getOwnPropertyDescriptor,Ue=Object.getPrototypeOf,Be=Object.prototype,$e=Array.prototype;function qe(e,t,n){if("string"!=typeof t){var r=Ue(t);r&&r!==Be&&qe(e,r,n);for(var o=$e.concat(Me(t),Fe(t)),i=Ne[e.$$typeof]||Pe,a=Ne[t.$$typeof]||Pe,s=o.length,l=void 0,c=void 0;s--;)if(c=o[s],!(Re[c]||n&&n[c]||a&&a[c]||i&&i[c])&&(l=ze(t,c)))try{Le(e,c,l)}catch(e){}return e}return e}var We=Object(s.createContext)(),He=We.Consumer,Ve=function(e){function t(n){g(this,t);var r=x(this,e.call(this,n));return r.getContext=Object(p.a)(r.getContext.bind(r)),r.renderInner=r.renderInner.bind(r),r}return b(t,e),t.prototype.render=function(){return this.props.children?l.a.createElement(We.Consumer,null,this.renderInner):null},t.prototype.renderInner=function(e){var t=this.getContext(this.props.theme,e);return l.a.createElement(We.Provider,{value:t},this.props.children)},t.prototype.getTheme=function(e,t){if(_(e))return e(t);if(null===e||Array.isArray(e)||"object"!==(void 0===e?"undefined":m(e)))throw new A(8);return v({},t,e)},t.prototype.getContext=function(e,t){return this.getTheme(e,t)},t}(s.Component),Ye=function(){function e(){g(this,e),this.masterSheet=pe.master,this.instance=this.masterSheet.clone(),this.sealed=!1}return e.prototype.seal=function(){if(!this.sealed){var e=this.masterSheet.clones.indexOf(this.instance);this.masterSheet.clones.splice(e,1),this.sealed=!0}},e.prototype.collectStyles=function(e){if(this.sealed)throw new A(2);return l.a.createElement(Xe,{sheet:this.instance},e)},e.prototype.getStyleTags=function(){return this.seal(),this.instance.toHTML()},e.prototype.getStyleElement=function(){return this.seal(),this.instance.toReactElements()},e.prototype.interleaveWithNodeStream=function(e){throw new A(3)},e}(),Qe=Object(s.createContext)(),Ge=Qe.Consumer,Xe=function(e){function t(n){g(this,t);var r=x(this,e.call(this,n));return r.getContext=Object(p.a)(r.getContext),r}return b(t,e),t.prototype.getContext=function(e,t){if(e)return e;if(t)return new pe(t);throw new A(4)},t.prototype.render=function(){var e=this.props,t=e.children,n=e.sheet,r=e.target;return l.a.createElement(Qe.Provider,{value:this.getContext(n,r)},t)},t}(s.Component),Ke={};var Ze=function(e){function t(){g(this,t);var n=x(this,e.call(this));return n.attrs={},n.renderOuter=n.renderOuter.bind(n),n.renderInner=n.renderInner.bind(n),n}return b(t,e),t.prototype.render=function(){return l.a.createElement(Ge,null,this.renderOuter)},t.prototype.renderOuter=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:pe.master;return this.styleSheet=e,this.props.forwardedComponent.componentStyle.isStatic?this.renderInner():l.a.createElement(He,null,this.renderInner)},t.prototype.renderInner=function(e){var t=this.props.forwardedComponent,n=t.componentStyle,r=t.defaultProps,o=(t.displayName,t.foldedComponentIds),i=t.styledComponentId,a=t.target,l=void 0;l=n.isStatic?this.generateAndInjectStyles(O,this.props):this.generateAndInjectStyles(Te(this.props,e,r)||O,this.props);var c=this.props.as||this.attrs.as||a,u=Ae(c),p={},d=v({},this.props,this.attrs),h=void 0;for(h in d)"forwardedComponent"!==h&&"as"!==h&&("forwardedRef"===h?p.ref=d[h]:"forwardedAs"===h?p.as=d[h]:u&&!Object(f.a)(h)||(p[h]=d[h]));return this.props.style&&this.attrs.style&&(p.style=v({},this.attrs.style,this.props.style)),p.className=Array.prototype.concat(o,i,l!==i?l:null,this.props.className,this.attrs.className).filter(Boolean).join(" "),Object(s.createElement)(c,p)},t.prototype.buildExecutionContext=function(e,t,n){var r=this,o=v({},t,{theme:e});return n.length?(this.attrs={},n.forEach((function(e){var t,n=e,i=!1,a=void 0,s=void 0;for(s in _(n)&&(n=n(o),i=!0),n)a=n[s],i||!_(a)||(t=a)&&t.prototype&&t.prototype.isReactComponent||S(a)||(a=a(o)),r.attrs[s]=a,o[s]=a})),o):o},t.prototype.generateAndInjectStyles=function(e,t){var n=t.forwardedComponent,r=n.attrs,o=n.componentStyle;n.warnTooManyClasses;return o.isStatic&&!r.length?o.generateAndInjectStyles(O,this.styleSheet):o.generateAndInjectStyles(this.buildExecutionContext(e,t,r),this.styleSheet)},t}(s.Component);function Je(e,t,n){var r=S(e),o=!Ae(e),i=t.displayName,a=void 0===i?function(e){return Ae(e)?"styled."+e:"Styled("+E(e)+")"}(e):i,s=t.componentId,c=void 0===s?function(e,t,n){var r="string"!=typeof t?"sc":Ie(t),o=(Ke[r]||0)+1;Ke[r]=o;var i=r+"-"+e.generateName(r+o);return n?n+"-"+i:i}(Se,t.displayName,t.parentComponentId):s,u=t.ParentComponent,p=void 0===u?Ze:u,f=t.attrs,h=void 0===f?k:f,m=t.displayName&&t.componentId?Ie(t.displayName)+"-"+t.componentId:t.componentId||c,g=r&&e.attrs?Array.prototype.concat(e.attrs,h).filter(Boolean):h,y=new Se(r?e.componentStyle.rules.concat(n):n,g,m),b=void 0,x=function(e,t){return l.a.createElement(p,v({},e,{forwardedComponent:b,forwardedRef:t}))};return x.displayName=a,(b=l.a.forwardRef(x)).displayName=a,b.attrs=g,b.componentStyle=y,b.foldedComponentIds=r?Array.prototype.concat(e.foldedComponentIds,e.styledComponentId):k,b.styledComponentId=m,b.target=r?e.target:e,b.withComponent=function(e){var r=t.componentId,o=function(e,t){var n={};for(var r in e)t.indexOf(r)>=0||Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r]);return n}(t,["componentId"]),i=r&&r+"-"+(Ae(e)?e:Ie(E(e)));return Je(e,v({},o,{attrs:g,componentId:i,ParentComponent:p}),n)},Object.defineProperty(b,"defaultProps",{get:function(){return this._foldedDefaultProps},set:function(t){this._foldedDefaultProps=r?Object(d.a)(e.defaultProps,t):t}}),b.toString=function(){return"."+b.styledComponentId},o&&qe(b,e,{attrs:!0,componentStyle:!0,displayName:!0,foldedComponentIds:!0,styledComponentId:!0,target:!0,withComponent:!0}),b}var et=function(e){return function e(t,n){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:O;if(!Object(u.isValidElementType)(n))throw new A(1,String(n));var o=function(){return t(n,r,be.apply(void 0,arguments))};return o.withConfig=function(o){return e(t,n,v({},r,o))},o.attrs=function(o){return e(t,n,v({},r,{attrs:Array.prototype.concat(r.attrs,o).filter(Boolean)}))},o}(Je,e)};["a","abbr","address","area","article","aside","audio","b","base","bdi","bdo","big","blockquote","body","br","button","canvas","caption","cite","code","col","colgroup","data","datalist","dd","del","details","dfn","dialog","div","dl","dt","em","embed","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","iframe","img","input","ins","kbd","keygen","label","legend","li","link","main","map","mark","marquee","menu","menuitem","meta","meter","nav","noscript","object","ol","optgroup","option","output","p","param","picture","pre","progress","q","rp","rt","ruby","s","samp","script","section","select","small","source","span","strong","style","sub","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","title","tr","track","u","ul","var","video","wbr","circle","clipPath","defs","ellipse","foreignObject","g","image","line","linearGradient","marker","mask","path","pattern","polygon","polyline","radialGradient","rect","stop","svg","text","tspan"].forEach((function(e){et[e]=et(e)}));var tt=function(){function e(t,n){g(this,e),this.rules=t,this.componentId=n,this.isStatic=Oe(t,k),pe.master.hasId(n)||pe.master.deferredInject(n,[])}return e.prototype.createStyles=function(e,t){var n=H(ve(this.rules,e,t),"");t.inject(this.componentId,n)},e.prototype.removeStyles=function(e){var t=this.componentId;e.hasId(t)&&e.remove(t)},e.prototype.renderStyles=function(e,t){this.removeStyles(t),this.createStyles(e,t)},e}();function nt(e){for(var t=arguments.length,n=Array(t>1?t-1:0),r=1;r<t;r++)n[r-1]=arguments[r];var o=be.apply(void 0,[e].concat(n)),i="sc-global-"+xe(JSON.stringify(o)),a=new tt(o,i),s=function(e){function t(n){g(this,t);var r=x(this,e.call(this,n)),o=r.constructor,i=o.globalStyle,a=o.styledComponentId;return j&&(window.scCGSHMRCache[a]=(window.scCGSHMRCache[a]||0)+1),r.state={globalStyle:i,styledComponentId:a},r}return b(t,e),t.prototype.componentWillUnmount=function(){window.scCGSHMRCache[this.state.styledComponentId]&&(window.scCGSHMRCache[this.state.styledComponentId]-=1),0===window.scCGSHMRCache[this.state.styledComponentId]&&this.state.globalStyle.removeStyles(this.styleSheet)},t.prototype.render=function(){var e=this;return l.a.createElement(Ge,null,(function(t){e.styleSheet=t||pe.master;var n=e.state.globalStyle;return n.isStatic?(n.renderStyles(I,e.styleSheet),null):l.a.createElement(He,null,(function(t){var r=e.constructor.defaultProps,o=v({},e.props);return void 0!==t&&(o.theme=Te(e.props,t,r)),n.renderStyles(o,e.styleSheet),null}))}))},t}(l.a.Component);return s.globalStyle=a,s.styledComponentId=i,s}j&&(window.scCGSHMRCache={});var rt=function(e){return e.replace(/\s|\\n/g,"")};function ot(e){for(var t=arguments.length,n=Array(t>1?t-1:0),r=1;r<t;r++)n[r-1]=arguments[r];var o=be.apply(void 0,[e].concat(n)),i=ke(xe(rt(JSON.stringify(o))));return new fe(i,H(o,i,"@keyframes"))}var it=function(e){var t=l.a.forwardRef((function(t,n){return l.a.createElement(He,null,(function(r){var o=e.defaultProps,i=Te(t,r,o);return l.a.createElement(e,v({},t,{theme:i,ref:n}))}))}));return qe(t,e),t.displayName="WithTheme("+E(e)+")",t},at={StyleSheet:pe};t.default=et}.call(this,n(13))},function(e,t,n){e.exports=function(){"use strict";return function(e){function t(t){if(t)try{e(t+"}")}catch(e){}}return function(n,r,o,i,a,s,l,c,u,p){switch(n){case 1:if(0===u&&64===r.charCodeAt(0))return e(r+";"),"";break;case 2:if(0===c)return r+"/*|*/";break;case 3:switch(c){case 102:case 112:return e(o[0]+r),"";default:return r+(0===p?"/*|*/":"")}case-2:r.split("/*|*/}").forEach(t)}}}}()},function(e,t,n){"use strict";t.a={animationIterationCount:1,borderImageOutset:1,borderImageSlice:1,borderImageWidth:1,boxFlex:1,boxFlexGroup:1,boxOrdinalGroup:1,columnCount:1,columns:1,flex:1,flexGrow:1,flexPositive:1,flexShrink:1,flexNegative:1,flexOrder:1,gridRow:1,gridRowEnd:1,gridRowSpan:1,gridRowStart:1,gridColumn:1,gridColumnEnd:1,gridColumnSpan:1,gridColumnStart:1,msGridRow:1,msGridRowSpan:1,msGridColumn:1,msGridColumnSpan:1,fontWeight:1,lineHeight:1,opacity:1,order:1,orphans:1,tabSize:1,widows:1,zIndex:1,zoom:1,WebkitLineClamp:1,fillOpacity:1,floodOpacity:1,stopOpacity:1,strokeDasharray:1,strokeDashoffset:1,strokeMiterlimit:1,strokeOpacity:1,strokeWidth:1}},function(e,t,n){"use strict";(function(t){n(132);var r=n(283),o=n(146),i=n(285),a=n(286),s=n(287),l=n(288),c=n(26),u=n(147),p=n(31);function f(){this.schema=null,this.$refs=new r}e.exports=f,e.exports.YAML=n(133),f.parse=function(e,t,n,r){var o=this,i=new o;return i.parse.apply(i,arguments)},f.prototype.parse=function(e,n,a,s){var l,f=i(arguments);if(!f.path&&!f.schema){var d=p("Expected a file path, URL, or object. Got %s",f.path||f.schema);return u(f.callback,Promise.reject(d))}this.schema=null,this.$refs=new r;var h="http";if(c.isFileSystemPath(f.path)&&(f.path=c.fromFileSystemPath(f.path),h="file"),f.path=c.resolve(c.cwd(),f.path),f.schema&&"object"==typeof f.schema){var m=this.$refs._add(f.path);m.value=f.schema,m.pathType=h,l=Promise.resolve(f.schema)}else l=o(f.path,this.$refs,f.options);var g=this;return l.then((function(e){if(!e||"object"!=typeof e||t.isBuffer(e))throw p.syntax('"%s" is not a valid JSON Schema',g.$refs._root$Ref.path||e);return g.schema=e,u(f.callback,Promise.resolve(g.schema))})).catch((function(e){return u(f.callback,Promise.reject(e))}))},f.resolve=function(e,t,n,r){var o=this,i=new o;return i.resolve.apply(i,arguments)},f.prototype.resolve=function(e,t,n,r){var o=this,s=i(arguments);return this.parse(s.path,s.schema,s.options).then((function(){return a(o,s.options)})).then((function(){return u(s.callback,Promise.resolve(o.$refs))})).catch((function(e){return u(s.callback,Promise.reject(e))}))},f.bundle=function(e,t,n,r){var o=this,i=new o;return i.bundle.apply(i,arguments)},f.prototype.bundle=function(e,t,n,r){var o=this,a=i(arguments);return this.resolve(a.path,a.schema,a.options).then((function(){return s(o,a.options),u(a.callback,Promise.resolve(o.schema))})).catch((function(e){return u(a.callback,Promise.reject(e))}))},f.dereference=function(e,t,n,r){var o=this,i=new o;return i.dereference.apply(i,arguments)},f.prototype.dereference=function(e,t,n,r){var o=this,a=i(arguments);return this.resolve(a.path,a.schema,a.options).then((function(){return l(o,a.options),u(a.callback,Promise.resolve(o.schema))})).catch((function(e){return u(a.callback,Promise.reject(e))}))}}).call(this,n(14).Buffer)},function(e,t,n){"use strict";var r,o=n(1),i=n(148),a=n(10),s=(n(52),n(147)),l=n(51),c=n(51),u=n(64),p=u.jptr,f=n(93).isRef,d=n(65).clone,h=n(65).circularClone,m=n(94).recurse,g=n(289),y=n(291),v=n(149),b=n(292).statusCodes,x=n(293).version,w=function(e){function t(t){var n=e.call(this,t)||this;return n.name="S2OError",n}return o.__extends(t,e),t}(Error);function k(e,t){var n=new w(e);if(n.options=t,!t.promise)throw n;t.promise.reject(n)}function O(e,t,n){n.warnOnly?t[n.warnProperty||"x-s2o-warning"]=e:k(e,n)}function _(e,t){y.walkSchema(e,{},{},(function(e,n,r){!function(e,t){if(e["x-required"]&&Array.isArray(e["x-required"])&&(e.required||(e.required=[]),e.required=e.required.concat(e["x-required"]),delete e["x-required"]),e["x-anyOf"]&&(e.anyOf=e["x-anyOf"],delete e["x-anyOf"]),e["x-oneOf"]&&(e.oneOf=e["x-oneOf"],delete e["x-oneOf"]),e["x-not"]&&(e.not=e["x-not"],delete e["x-not"]),"boolean"==typeof e["x-nullable"]&&(e.nullable=e["x-nullable"],delete e["x-nullable"]),"object"==typeof e["x-discriminator"]&&"string"==typeof e["x-discriminator"].propertyName)for(var n in e.discriminator=e["x-discriminator"],delete e["x-discriminator"],e.discriminator.mapping){var r=e.discriminator.mapping[n];r.startsWith("#/definitions/")&&(e.discriminator.mapping[n]=r.replace("#/definitions/","#/components/schemas/"))}}(e),function(e,t,n){if(e.discriminator&&"string"==typeof e.discriminator&&(e.discriminator={propertyName:e.discriminator}),e.items&&Array.isArray(e.items)&&(0===e.items.length?e.items={}:1===e.items.length?e.items=e.items[0]:e.items={anyOf:e.items}),e.type&&Array.isArray(e.type))if(n.patch){if(0===e.type.length)delete e.type;else{e.oneOf||(e.oneOf=[]);for(var r=0,o=e.type;r<o.length;r++){var i=o[r],a={};if("null"===i)e.nullable=!0;else{a.type=i;for(var s=0,l=v.arrayProperties;s<l.length;s++){var c=l[s];void 0!==e.prop&&(a[c]=e[c],delete e[c])}}a.type&&e.oneOf.push(a)}delete e.type,0===e.oneOf.length?delete e.oneOf:e.oneOf.length<2&&(e.type=e.oneOf[0].type,Object.keys(e.oneOf[0]).length>1&&O("Lost properties from oneOf",e,n),delete e.oneOf)}e.type&&Array.isArray(e.type)&&1===e.type.length&&(e.type=e.type[0])}else k("(Patchable) schema type must not be an array",n);e.type&&"null"===e.type&&(delete e.type,e.nullable=!0),"array"!==e.type||e.items||(e.items={}),"boolean"==typeof e.required&&(e.required&&e.name&&(void 0===t.required&&(t.required=[]),Array.isArray(t.required)&&t.required.push(e.name)),delete e.required),e.xml&&"string"==typeof e.xml.namespace&&(e.xml.namespace||delete e.xml.namespace)}(e,n,t)}))}function E(e,t,n){var o=n.payload.options;if(f(e,t)){if(e[t].startsWith("#/components/"));else if("#/consumes"===e[t])delete e[t],n.parent[n.pkey]=d(o.openapi.consumes);else if("#/produces"===e[t])delete e[t],n.parent[n.pkey]=d(o.openapi.produces);else if(e[t].startsWith("#/definitions/")){var i=e[t].replace("#/definitions/","").split("/"),a=u.jpunescape(i[0]);(w=r.schemas[decodeURIComponent(a)])?i[0]=w:O("Could not resolve reference "+e[t],e,o),e[t]="#/components/schemas/"+i.join("/")}else if(e[t].startsWith("#/parameters/"))e[t]="#/components/parameters/"+v.sanitise(e[t].replace("#/parameters/",""));else if(e[t].startsWith("#/responses/"))e[t]="#/components/responses/"+v.sanitise(e[t].replace("#/responses/",""));else if(e[t].startsWith("#")){var s=d(u.jptr(o.openapi,e[t]));if(!1===s)O("direct $ref not found "+e[t],e,o);else if(o.refmap[e[t]])e[t]=o.refmap[e[t]];else{var l=e[t],c="schemas",p=(l=(l=(l=(l=l.replace("/properties/headers/","")).replace("/properties/responses/","")).replace("/properties/parameters/","")).replace("/properties/schemas/","")).lastIndexOf("/schema");if("schemas"===(c=l.indexOf("/headers/")>p?"headers":l.indexOf("/responses/")>p?"responses":l.indexOf("/example")>p?"examples":l.indexOf("/x-")>p?"extensions":l.indexOf("/parameters/")>p?"parameters":"schemas")&&_(s,o),"responses"!==c&&"extensions"!==c){var h=c.substr(0,c.length-1);"parameter"===h&&s.name&&s.name===v.sanitise(s.name)&&(h=encodeURIComponent(s.name));var m=1;for(e["x-miro"]&&(h=function(e){return e=e.indexOf("#")>=0?e.split("#")[1].split("/").pop():e.split("/").pop().split(".")[0],encodeURIComponent(v.sanitise(e))}(e["x-miro"]),m="");u.jptr(o.openapi,"#/components/"+c+"/"+h+m);)m=""===m?2:++m;var g="#/components/"+c+"/"+h+m,y="";"examples"===c&&(s={value:s},y="/value"),u.jptr(o.openapi,g,s),o.refmap[e[t]]=g+y,e[t]=g+y}}}if(delete e["x-miro"],Object.keys(e).length>1){var b=e[t],x=n.path.indexOf("/schema")>=0;"preserve"===o.refSiblings||(x&&"allOf"===o.refSiblings?(delete e.$ref,n.parent[n.pkey]={allOf:[{$ref:b},e]}):n.parent[n.pkey]={$ref:b})}}if("x-ms-odata"===t&&"string"==typeof e[t]&&e[t].startsWith("#/")){var w;i=e[t].replace("#/definitions/","").replace("#/components/schemas/","").split("/");(w=r.schemas[decodeURIComponent(i[0])])?i[0]=w:O("Could not resolve reference "+e[t],e,o),e[t]="#/components/schemas/"+i.join("/")}}function S(e){for(var t in e)for(var n in e[t]){var r=v.sanitise(n);n!=r&&(e[t][r]=e[t][n],delete e[t][n])}}function T(e,t){if("basic"===e.type&&(e.type="http",e.scheme="basic"),"oauth2"===e.type){var n={},r=e.flow;"application"===e.flow&&(r="clientCredentials"),"accessCode"===e.flow&&(r="authorizationCode"),void 0!==e.authorizationUrl&&(n.authorizationUrl=e.authorizationUrl.split("?")[0].trim()||"/"),"string"==typeof e.tokenUrl&&(n.tokenUrl=e.tokenUrl.split("?")[0].trim()||"/"),n.scopes=e.scopes||{},e.flows={},e.flows[r]=n,delete e.flow,delete e.authorizationUrl,delete e.tokenUrl,delete e.scopes,void 0!==e.name&&(t.patch?delete e.name:k("(Patchable) oauth2 securitySchemes should not have name property",t))}}function j(e){return e&&!e["x-s2o-delete"]}function C(e,t){if(e.$ref)e.$ref=e.$ref.replace("#/responses/","#/components/responses/");else{e.type&&!e.schema&&(e.schema={}),e.type&&(e.schema.type=e.type),e.items&&"array"!==e.items.type&&(e.items.collectionFormat!==e.collectionFormat&&O("Nested collectionFormats are not supported",e,t),delete e.items.collectionFormat),"array"===e.type?("ssv"===e.collectionFormat?O("collectionFormat:ssv is no longer supported for headers",e,t):"pipes"===e.collectionFormat?O("collectionFormat:pipes is no longer supported for headers",e,t):"multi"===e.collectionFormat?e.explode=!0:"tsv"===e.collectionFormat?(O("collectionFormat:tsv is no longer supported",e,t),e["x-collectionFormat"]="tsv"):e.style="simple",delete e.collectionFormat):e.collectionFormat&&(t.patch?delete e.collectionFormat:k("(Patchable) collectionFormat is only applicable to header.type array",t)),delete e.type;for(var n=0,r=v.parameterTypeProperties;n<r.length;n++){void 0!==e[a=r[n]]&&(e.schema[a]=e[a],delete e[a])}for(var o=0,i=v.arrayProperties;o<i.length;o++){var a;void 0!==e[a=i[o]]&&(e.schema[a]=e[a],delete e[a])}}}function I(e,t){if(e.$ref.indexOf("#/parameters/")>=0){var n=e.$ref.split("#/parameters/");e.$ref=n[0]+"#/components/parameters/"+v.sanitise(n[1])}e.$ref.indexOf("#/definitions/")>=0&&O("Definition used as parameter",e,t)}function A(e,t,n,r,o,i,a){var s,l={},c=!0;if(t&&t.consumes&&"string"==typeof t.consumes){if(!a.patch)return k("(Patchable) operation.consumes must be an array",a);t.consumes=[t.consumes]}Array.isArray(i.consumes)||delete i.consumes;var u=((t?t.consumes:null)||i.consumes||[]).filter(v.uniqueOnly);if(e&&e.$ref&&"string"==typeof e.$ref){I(e,a);var f=decodeURIComponent(e.$ref.replace("#/components/parameters/","")),h=!1;if((j=i.components.parameters[f])&&!j["x-s2o-delete"]||!e.$ref.startsWith("#/")||(e["x-s2o-delete"]=!0,h=!0),h){var g=e.$ref,y=p(i,e.$ref);!y&&g.startsWith("#/")?O("Could not resolve reference "+g,e,a):y&&(e=y)}}if(e&&(e.name||e.in)){"boolean"==typeof e["x-deprecated"]&&(e.deprecated=e["x-deprecated"],delete e["x-deprecated"]),void 0!==e["x-example"]&&(e.example=e["x-example"],delete e["x-example"]),"body"==e.in||e.type||(a.patch?e.type="string":k("(Patchable) parameter.type is mandatory for non-body parameters",a)),e.type&&"object"==typeof e.type&&e.type.$ref&&(e.type=p(i,e.type.$ref)),"file"===e.type&&(e["x-s2o-originalType"]=e.type,s=e.type),e.description&&"object"==typeof e.description&&e.description.$ref&&(e.description=p(i,e.description.$ref)),null===e.description&&delete e.description;var b=e.collectionFormat;if("array"!==e.type||b||(b="csv"),b&&("array"!=e.type&&(a.patch?delete e.collectionFormat:k("(Patchable) collectionFormat is only applicable to param.type array",a)),"csv"!==b||"query"!==e.in&&"cookie"!==e.in||(e.style="form",e.explode=!1),"csv"!==b||"path"!==e.in&&"header"!==e.in||(e.style="simple"),"ssv"===b&&("query"===e.in?e.style="spaceDelimited":O("collectionFormat:ssv is no longer supported except for in:query parameters",e,a)),"pipes"===b&&("query"===e.in?e.style="pipeDelimited":O("collectionFormat:pipes is no longer supported except for in:query parameters",e,a)),"multi"===b&&(e.explode=!0),"tsv"===b&&(O("collectionFormat:tsv is no longer supported",e,a),e["x-collectionFormat"]="tsv"),delete e.collectionFormat),e.type&&"object"!=e.type&&"body"!=e.type&&"formData"!=e.in)if(e.items&&e.schema)O("parameter has array,items and schema",e,a);else{e.schema&&"object"==typeof e.schema||(e.schema={}),e.schema.type=e.type,e.items&&(e.schema.items=e.items,delete e.items,m(e.schema.items,null,(function(t,n,r){"collectionFormat"===n&&"string"==typeof t[n]&&(b&&t[n]!==b&&O("Nested collectionFormats are not supported",e,a),delete t[n])})));for(var x=0,w=v.parameterTypeProperties;x<w.length;x++){var E=w[x];void 0!==e[E]&&(e.schema[E]=e[E]),delete e[E]}}e.schema&&_(e.schema,a),e["x-ms-skip-url-encoding"]&&"query"===e.in&&(e.allowReserved=!0,delete e["x-ms-skip-url-encoding"])}if(e&&"formData"===e.in){c=!1,l.content={};var S="application/x-www-form-urlencoded";if(u.length&&u.indexOf("multipart/form-data")>=0&&(S="multipart/form-data"),l.content[S]={},e.schema)l.content[S].schema=e.schema,e.schema.$ref&&(l["x-s2o-name"]=decodeURIComponent(e.schema.$ref.replace("#/components/schemas/","")));else{l.content[S].schema={},l.content[S].schema.type="object",l.content[S].schema.properties={},l.content[S].schema.properties[e.name]={};var T=l.content[S].schema,j=l.content[S].schema.properties[e.name];e.description&&(j.description=e.description),e.example&&(j.example=e.example),e.type&&(j.type=e.type);for(var C=0,A=v.parameterTypeProperties;C<A.length;C++){E=A[C];void 0!==e[E]&&(j[E]=e[E])}!0===e.required&&(T.required||(T.required=[]),T.required.push(e.name)),void 0!==e.default&&(j.default=e.default),j.properties&&(j.properties=e.properties),e.allOf&&(j.allOf=e.allOf),"array"===e.type&&e.items&&(j.items=e.items,j.items.collectionFormat&&delete j.items.collectionFormat),"file"!==s&&"file"!==e["x-s2o-originalType"]||(j.type="string",j.format="binary"),P(e,j)}}else e&&"file"===e.type&&(e.required&&(l.required=e.required),l.content={},l.content["application/octet-stream"]={},l.content["application/octet-stream"].schema={},l.content["application/octet-stream"].schema.type="string",l.content["application/octet-stream"].schema.format="binary",P(e,l));if(e&&"body"===e.in){l.content={},e.name&&(l["x-s2o-name"]=(t&&t.operationId?v.sanitiseAll(t.operationId):"")+("_"+e.name).toCamelCase()),e.description&&(l.description=e.description),e.required&&(l.required=e.required),t&&a.rbname&&e.name&&(t[a.rbname]=e.name),e.schema&&e.schema.$ref?l["x-s2o-name"]=decodeURIComponent(e.schema.$ref.replace("#/components/schemas/","")):e.schema&&"array"===e.schema.type&&e.schema.items&&e.schema.items.$ref&&(l["x-s2o-name"]=decodeURIComponent(e.schema.items.$ref.replace("#/components/schemas/",""))+"Array"),u.length||u.push("application/json");for(var R=0,N=u;R<N.length;R++){var L=N[R];l.content[L]={},l.content[L].schema=d(e.schema||{}),_(l.content[L].schema,a)}P(e,l)}Object.keys(l).length>0&&(e["x-s2o-delete"]=!0,t&&(t.requestBody&&c?(t.requestBody["x-s2o-overloaded"]=!0,O("Operation "+(t.operationId||o)+" has multiple requestBodies",t,a)):(t.requestBody||(t=n[r]=function(e,t){for(var n={},r=0,o=Object.keys(e);r<o.length;r++){var i=o[r];n[i]=e[i],"parameters"===i&&(n.requestBody={},t.rbname&&(n[t.rbname]=""))}return n.requestBody={},n}(t,a)),t.requestBody.content&&t.requestBody.content["multipart/form-data"]&&t.requestBody.content["multipart/form-data"].schema&&t.requestBody.content["multipart/form-data"].schema.properties&&l.content["multipart/form-data"]&&l.content["multipart/form-data"].schema&&l.content["multipart/form-data"].schema.properties?(t.requestBody.content["multipart/form-data"].schema.properties=Object.assign(t.requestBody.content["multipart/form-data"].schema.properties,l.content["multipart/form-data"].schema.properties),t.requestBody.content["multipart/form-data"].schema.required=(t.requestBody.content["multipart/form-data"].schema.required||[]).concat(l.content["multipart/form-data"].schema.required||[]),t.requestBody.content["multipart/form-data"].schema.required.length||delete t.requestBody.content["multipart/form-data"].schema.required):t.requestBody.content&&t.requestBody.content["application/x-www-form-urlencoded"]&&t.requestBody.content["application/x-www-form-urlencoded"].schema&&t.requestBody.content["application/x-www-form-urlencoded"].schema.properties&&l.content["application/x-www-form-urlencoded"]&&l.content["application/x-www-form-urlencoded"].schema&&l.content["application/x-www-form-urlencoded"].schema.properties?(t.requestBody.content["application/x-www-form-urlencoded"].schema.properties=Object.assign(t.requestBody.content["application/x-www-form-urlencoded"].schema.properties,l.content["application/x-www-form-urlencoded"].schema.properties),t.requestBody.content["application/x-www-form-urlencoded"].schema.required=(t.requestBody.content["application/x-www-form-urlencoded"].schema.required||[]).concat(l.content["application/x-www-form-urlencoded"].schema.required||[]),t.requestBody.content["application/x-www-form-urlencoded"].schema.required.length||delete t.requestBody.content["application/x-www-form-urlencoded"].schema.required):(t.requestBody=Object.assign(t.requestBody,l),t.requestBody["x-s2o-name"]||(t.requestBody.schema&&t.requestBody.schema.$ref?t.requestBody["x-s2o-name"]=decodeURIComponent(t.requestBody.schema.$ref.replace("#/components/schemas/","")).split("/").join(""):t.operationId&&(t.requestBody["x-s2o-name"]=v.sanitiseAll(t.operationId)))))));if(e&&!e["x-s2o-delete"]){delete e.type;for(var M=0,D=v.parameterTypeProperties;M<D.length;M++){E=D[M];delete e[E]}"path"!==e.in||void 0!==e.required&&!0===e.required||(a.patch?e.required=!0:k("(Patchable) path parameters must be required:true ["+e.name+" in "+o+"]",a))}return t}function P(e,t){for(var n in e)n.startsWith("x-")&&!n.startsWith("x-s2o")&&(t[n]=e[n])}function R(e,t,n,r,o){if(!e)return!1;if(e.$ref&&"string"==typeof e.$ref)e.$ref.indexOf("#/definitions/")>=0?O("definition used as response: "+e.$ref,e,o):e.$ref.startsWith("#/responses/")&&(e.$ref="#/components/responses/"+v.sanitise(decodeURIComponent(e.$ref.replace("#/responses/",""))));else{if((void 0===e.description||null===e.description||""===e.description&&o.patch)&&(o.patch?"object"!=typeof e||Array.isArray(e)||(e.description=b[e]||""):k("(Patchable) response.description is mandatory",o)),void 0!==e.schema){if(_(e.schema,o),e.schema.$ref&&"string"==typeof e.schema.$ref&&e.schema.$ref.startsWith("#/responses/")&&(e.schema.$ref="#/components/responses/"+v.sanitise(decodeURIComponent(e.schema.$ref.replace("#/responses/","")))),n&&n.produces&&"string"==typeof n.produces){if(!o.patch)return k("(Patchable) operation.produces must be an array",o);n.produces=[n.produces]}r.produces&&!Array.isArray(r.produces)&&delete r.produces;var i=((n?n.produces:null)||r.produces||[]).filter(v.uniqueOnly);i.length||i.push("*/*"),e.content={};for(var a=0,s=i;a<s.length;a++){var l=s[a];if(e.content[l]={},e.content[l].schema=d(e.schema),e.examples&&e.examples[l]){var c={};c.value=e.examples[l],e.content[l].examples={},e.content[l].examples.response=c,delete e.examples[l]}"file"===e.content[l].schema.type&&(e.content[l].schema={type:"string",format:"binary"})}delete e.schema}for(var l in e.examples)e.content||(e.content={}),e.content[l]||(e.content[l]={}),e.content[l].examples={},e.content[l].examples.response={},e.content[l].examples.response.value=e.examples[l];if(delete e.examples,e.headers)for(var u in e.headers)"status code"===u.toLowerCase()?o.patch?delete e.headers[u]:k('(Patchable) "Status Code" is not a valid header',o):C(e.headers[u],o)}}function N(e,t,n,r,o){for(var i in e){var s=e[i];for(var l in s&&s["x-trace"]&&"object"==typeof s["x-trace"]&&(s.trace=s["x-trace"],delete s["x-trace"]),s&&s["x-summary"]&&"string"==typeof s["x-summary"]&&(s.summary=s["x-summary"],delete s["x-summary"]),s&&s["x-description"]&&"string"==typeof s["x-description"]&&(s.description=s["x-description"],delete s["x-description"]),s&&s["x-servers"]&&Array.isArray(s["x-servers"])&&(s.servers=s["x-servers"],delete s["x-servers"]),s)if(v.httpMethods.indexOf(l)>=0||"x-amazon-apigateway-any-method"===l){var c=s[l];if(c&&c.parameters&&Array.isArray(c.parameters)){if(s.parameters)for(var f=function(e){"string"==typeof e.$ref&&(I(e,n),e=p(o,e.$ref)),c.parameters.find((function(t,n,r){return t.name===e.name&&t.in===e.in}))||"formData"!==e.in&&"body"!==e.in&&"file"!==e.type||(c=A(e,c,s,l,i,o,n),n.rbname&&""===c[n.rbname]&&delete c[n.rbname])},h=0,m=s.parameters;h<m.length;h++){f(b=m[h])}for(var g=0,y=c.parameters;g<y.length;g++){var b=y[g];c=A(b,c,s,l,l+":"+i,o,n)}n.rbname&&""===c[n.rbname]&&delete c[n.rbname],n.debug||(c.parameters=c.parameters.filter(j))}if(c&&c.security&&S(c.security),"object"==typeof c){if(!c.responses){var x={description:"Default response"};c.responses={default:x}}for(var w in c.responses){R(c.responses[w],0,c,o,n)}}if(c&&c["x-servers"]&&Array.isArray(c["x-servers"]))c.servers=c["x-servers"],delete c["x-servers"];else if(c&&c.schemes&&c.schemes.length)for(var k=0,O=c.schemes;k<O.length;k++){var _=O[k];if((!o.schemes||o.schemes.indexOf(_)<0)&&(c.servers||(c.servers=[]),Array.isArray(o.servers)))for(var E=0,T=o.servers;E<T.length;E++){var C=T[E],P=d(C),N=a.parse(P.url);N.protocol=_,P.url=N.format(),c.servers.push(P)}}if(n.debug&&(c["x-s2o-consumes"]=c.consumes||[],c["x-s2o-produces"]=c.produces||[]),c){if(delete c.consumes,delete c.produces,delete c.schemes,c["x-ms-examples"]){for(var L in c["x-ms-examples"]){var M=c["x-ms-examples"][L],D=v.sanitiseAll(L);if(M.parameters)for(var F in M.parameters)for(var z=M.parameters[F],U=0,B=(c.parameters||[]).concat(s.parameters||[]);U<B.length;U++){(b=B[U]).$ref&&(b=u.jptr(o,b.$ref)),b.name!==F||b.example||(b.examples||(b.examples={}),b.examples[L]={value:z})}if(M.responses)for(var w in M.responses){if(M.responses[w].headers)for(var $ in M.responses[w].headers){z=M.responses[w].headers[$];for(var q in c.responses[w].headers){if(q===$)c.responses[w].headers[q].example=z}}if(M.responses[w].body&&(o.components.examples[D]={value:d(M.responses[w].body)},c.responses[w]&&c.responses[w].content))for(var W in c.responses[w].content){var H=c.responses[w].content[W];H.examples||(H.examples={}),H.examples[L]={$ref:"#/components/examples/"+D}}}}delete c["x-ms-examples"]}if(c.parameters&&0===c.parameters.length&&delete c.parameters,c.requestBody){var V=c.operationId?v.sanitiseAll(c.operationId):v.sanitiseAll(l+i).toCamelCase(),Y=v.sanitise(c.requestBody["x-s2o-name"]||V||"");delete c.requestBody["x-s2o-name"];var Q=JSON.stringify(c.requestBody),G=v.hash(Q);if(!r[G]){var X={};X.name=Y,X.body=c.requestBody,X.refs=[],r[G]=X}var K="#/"+t+"/"+encodeURIComponent(u.jpescape(i))+"/"+l+"/requestBody";r[G].refs.push(K)}}}if(s&&s.parameters){for(var Z in s.parameters){A(b=s.parameters[Z],null,s,null,i,o,n)}!n.debug&&Array.isArray(s.parameters)&&(s.parameters=s.parameters.filter(j))}}}function L(e,t){var n={};for(var o in r={schemas:{}},e.security&&S(e.security),e.components.securitySchemes){o!=(l=v.sanitise(o))&&(e.components.securitySchemes[l]&&k("Duplicate sanitised securityScheme name "+l,t),e.components.securitySchemes[l]=e.components.securitySchemes[o],delete e.components.securitySchemes[o]),T(e.components.securitySchemes[l],t)}for(var o in e.components.schemas){var i="";if(o!=(l=v.sanitiseAll(o))){for(;e.components.schemas[l+i];)i=i?++i:2;e.components.schemas[l+i]=e.components.schemas[o],delete e.components.schemas[o]}r.schemas[o]=l+i,_(e.components.schemas[l+i],t)}for(var a in t.refmap={},m(e,{payload:{options:t}},E),function(e,t){for(var n in t.refmap)u.jptr(e,n,{$ref:t.refmap[n]})}(e,t),e.components.parameters){a!=(l=v.sanitise(a))&&(e.components.parameters[l]&&k("Duplicate sanitised parameter name "+l,t),e.components.parameters[l]=e.components.parameters[a],delete e.components.parameters[a]),A(e.components.parameters[l],null,null,null,l,e,t)}for(var s in e.components.responses){var l;s!=(l=v.sanitise(s))&&(e.components.responses[l]&&k("Duplicate sanitised response name "+l,t),e.components.responses[l]=e.components.responses[s],delete e.components.responses[s]);var c=e.components.responses[l];if(R(c,0,null,e,t),c.headers)for(var p in c.headers)"status code"===p.toLowerCase()?t.patch?delete c.headers[p]:k('(Patchable) "Status Code" is not a valid header',t):C(c.headers[p],t)}for(var s in e.components.requestBodies){var f=e.components.requestBodies[s],h=JSON.stringify(f),g=v.hash(h);(w={}).name=s,w.body=f,w.refs=[],n[g]=w}if(N(e.paths,"paths",t,n,e),e["x-ms-paths"]&&N(e["x-ms-paths"],"x-ms-paths",t,n,e),!t.debug)for(var a in e.components.parameters){e.components.parameters[a]["x-s2o-delete"]&&delete e.components.parameters[a]}t.debug&&(e["x-s2o-consumes"]=e.consumes||[],e["x-s2o-produces"]=e.produces||[]),delete e.consumes,delete e.produces,delete e.schemes;var y=[];if(e.components.requestBodies={},!t.resolveInternal){var b=1;for(var x in n){var w;if((w=n[x]).refs.length>1){i="";for(w.name||(w.name="requestBody",i=b++);y.indexOf(w.name+i)>=0;)i=i?++i:2;for(var s in w.name=w.name+i,y.push(w.name),e.components.requestBodies[w.name]=d(w.body),w.refs){var O={};O.$ref="#/components/requestBodies/"+w.name,u.jptr(e,w.refs[s],O)}}}}return e.components.responses&&0===Object.keys(e.components.responses).length&&delete e.components.responses,e.components.parameters&&0===Object.keys(e.components.parameters).length&&delete e.components.parameters,e.components.examples&&0===Object.keys(e.components.examples).length&&delete e.components.examples,e.components.requestBodies&&0===Object.keys(e.components.requestBodies).length&&delete e.components.requestBodies,e.components.securitySchemes&&0===Object.keys(e.components.securitySchemes).length&&delete e.components.securitySchemes,e.components.headers&&0===Object.keys(e.components.headers).length&&delete e.components.headers,e.components.schemas&&0===Object.keys(e.components.schemas).length&&delete e.components.schemas,e.components&&0===Object.keys(e.components).length&&delete e.components,e}function M(e){return e&&e.url&&"string"==typeof e.url?(e.url=e.url.split("{{").join("{"),e.url=e.url.split("}}").join("}"),e.url.replace(/\{(.+?)\}/g,(function(t,n){e.variables||(e.variables={}),e.variables[n]={default:"unknown"}})),e):e}function D(e,t,n){if(void 0===e.info||null===e.info){if(!t.patch)return n(new w("(Patchable) info object is mandatory"));e.info={version:"",title:""}}if("object"!=typeof e.info||Array.isArray(e.info))return n(new w("info must be an object"));if(void 0===e.info.title||null===e.info.title){if(!t.patch)return n(new w("(Patchable) info.title cannot be null"));e.info.title=""}if(void 0===e.info.version||null===e.info.version){if(!t.patch)return n(new w("(Patchable) info.version cannot be null"));e.info.version=""}if("string"!=typeof e.info.version){if(!t.patch)return n(new w("(Patchable) info.version must be a string"));e.info.version=e.info.version.toString()}if(void 0!==e.info.logo){if(!t.patch)return n(new w("(Patchable) info should not have logo property"));e.info["x-logo"]=e.info.logo,delete e.info.logo}if(void 0!==e.info.termsOfService){if(null===e.info.termsOfService){if(!t.patch)return n(new w("(Patchable) info.termsOfService cannot be null"));e.info.termsOfService=""}if(a.URL&&t.whatwg)try{a.URL.parse(e.info.termsOfService)}catch(r){if(!t.patch)return n(new w("(Patchable) info.termsOfService must be a URL"));delete e.info.termsOfService}}}function F(e,t,n){if(void 0===e.paths){if(!t.patch)return n(new w("(Patchable) paths object is mandatory"));e.paths={}}}function z(e,t,n){return s(n,new Promise((function(n,r){if(e||(e={}),t.original=e,t.text||(t.text=c.stringify(e)),t.externals=[],t.externalRefs={},t.rewriteRefs=!0,t.preserveMiro=!0,t.promise={},t.promise.resolve=n,t.promise.reject=r,t.cache||(t.cache={}),t.source&&(t.cache[t.source]=t.original),e.openapi&&"string"==typeof e.openapi&&e.openapi.startsWith("3."))return t.openapi=h(e),D(t.openapi,t,r),F(t.openapi,t,r),void g.optionalResolve(t).then((function(){return t.direct?n(t.openapi):n(t)})).catch((function(e){console.warn(e),r(e)}));if(!e.swagger||"2.0"!=e.swagger)return r(new w("Unsupported swagger/OpenAPI version: "+(e.openapi?e.openapi:e.swagger)));var o=t.openapi={};if(o.openapi="string"==typeof t.targetVersion&&t.targetVersion.startsWith("3.")?t.targetVersion:"3.0.0",t.origin){o["x-origin"]||(o["x-origin"]=[]);var i={};i.url=t.source||t.origin,i.format="swagger",i.version=e.swagger,i.converter={},i.converter.url="https://github.com/mermade/oas-kit",i.converter.version=x,o["x-origin"].push(i)}if(delete(o=Object.assign(o,h(e))).swagger,m(o,{},(function(e,t,n){null===e[t]&&!t.startsWith("x-")&&"default"!==t&&n.path.indexOf("/example")<0&&delete e[t]})),e.host)for(var a=0,s=Array.isArray(e.schemes)?e.schemes:[""];a<s.length;a++){var l=s[a];(u={}).url=(l?l+":":"")+"//"+e.host+(e.basePath?e.basePath:""),M(u),o.servers||(o.servers=[]),o.servers.push(u)}else if(e.basePath){var u;(u={}).url=e.basePath,M(u),o.servers||(o.servers=[]),o.servers.push(u)}if(delete o.host,delete o.basePath,o["x-servers"]&&Array.isArray(o["x-servers"])&&(o.servers=o["x-servers"],delete o["x-servers"]),e["x-ms-parameterized-host"]){var f=e["x-ms-parameterized-host"],y={};for(var v in y.url=f.hostTemplate+(e.basePath?e.basePath:""),y.variables={},f.parameters){var b=f.parameters[v];b.$ref&&(b=d(p(o,b.$ref))),v.startsWith("x-")||(delete b.required,delete b.type,delete b.in,void 0===b.default&&(b.enum?b.default=b.enum[0]:b.default=""),y.variables[b.name]=b,delete b.name)}o.servers||(o.servers=[]),!1===f.useSchemePrefix?o.servers.push(y):e.schemes.forEach((function(e){o.servers.push(Object.assign({},y,{url:e+"://"+y.url}))})),delete o["x-ms-parameterized-host"]}D(o,t,r),F(o,t,r),"string"==typeof o.consumes&&(o.consumes=[o.consumes]),"string"==typeof o.produces&&(o.produces=[o.produces]),o.components={},o["x-callbacks"]&&(o.components.callbacks=o["x-callbacks"],delete o["x-callbacks"]),o.components.examples={},o.components.headers={},o["x-links"]&&(o.components.links=o["x-links"],delete o["x-links"]),o.components.parameters=o.parameters||{},o.components.responses=o.responses||{},o.components.requestBodies={},o.components.securitySchemes=o.securityDefinitions||{},o.components.schemas=o.definitions||{},delete o.definitions,delete o.responses,delete o.parameters,delete o.securityDefinitions,g.optionalResolve(t).then((function(){L(t.openapi,t),t.direct?n(t.openapi):n(t)})).catch((function(e){console.warn(e),r(e)}))})))}function U(e,t,n){return s(n,new Promise((function(n,r){var o=null,i=null;try{o=JSON.parse(e),t.text=JSON.stringify(o,null,2)}catch(n){i=n;try{o=c.parse(e,{schema:"core",prettyErrors:!0}),t.sourceYaml=!0,t.text=e}catch(e){i=e}}o?z(o,t).then((function(e){return n(e)})).catch((function(e){return r(e)})):r(new w(i?i.message:"Could not parse string"))})))}e.exports={S2OError:w,targetVersion:"3.0.0",convert:z,convertObj:z,convertUrl:function(e,t,n){return s(n,new Promise((function(n,r){t.origin=!0,t.source||(t.source=e),t.verbose&&console.warn("GET "+e),l(e,{agent:t.agent}).then((function(e){if(200!==e.status)throw new w("Received status code "+e.status);return e.text()})).then((function(e){U(e,t).then((function(e){return n(e)})).catch((function(e){return r(e)}))})).catch((function(e){r(e)}))})))},convertStr:U,convertFile:function(e,t,n){return s(n,new Promise((function(n,r){i.readFile(e,t.encoding||"utf8",(function(o,i){o?r(o):(t.sourceFile=e,U(i,t).then((function(e){return n(e)})).catch((function(e){return r(e)})))}))})))},convertStream:function(e,t,n){return s(n,new Promise((function(n,r){var o="";e.on("data",(function(e){o+=e})).on("end",(function(){U(o,t).then((function(e){return n(e)})).catch((function(e){return r(e)}))}))})))}}},function(e,t,n){"use strict";function r(e,t){if(e.length!==t.length)return!1;for(var n=0;n<e.length;n++)if(e[n]!==t[n])return!1;return!0}e.exports=function(e,t){var n;void 0===t&&(t=r);var o,i=[],a=!1;return function(){for(var r=[],s=0;s<arguments.length;s++)r[s]=arguments[s];return a&&n===this&&t(r,i)||(o=e.apply(this,r),a=!0,n=this,i=r),o}}},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r=function(e){if(e&&e.__esModule)return e;if(null===e||"object"!==l(e)&&"function"!=typeof e)return{default:e};var t=s();if(t&&t.has(e))return t.get(e);var n={},r=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var o in e)if(Object.prototype.hasOwnProperty.call(e,o)){var i=r?Object.getOwnPropertyDescriptor(e,o):null;i&&(i.get||i.set)?Object.defineProperty(n,o,i):n[o]=e[o]}n.default=e,t&&t.set(e,n);return n}(n(0)),o=a(n(33)),i=a(n(20));function a(e){return e&&e.__esModule?e:{default:e}}function s(){if("function"!=typeof WeakMap)return null;var e=new WeakMap;return s=function(){return e},e}function l(e){return(l="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function c(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function u(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function p(e){return(p=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function f(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function d(e,t){return(d=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}var h=function(e){function t(e){var n;return function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,t),(n=function(e,t){return!t||"object"!==l(t)&&"function"!=typeof t?f(e):t}(this,p(t).call(this,e))).state={selected:n.parseValue(e.value,e.options)||{label:void 0===e.placeholder?"Select...":e.placeholder,value:""},isOpen:!1},n.mounted=!0,n.handleDocumentClick=n.handleDocumentClick.bind(f(n)),n.fireChangeEvent=n.fireChangeEvent.bind(f(n)),n}var n,a,s;return function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&d(e,t)}(t,e),n=t,(a=[{key:"componentWillReceiveProps",value:function(e){if(e.value){var t=this.parseValue(e.value,e.options);t!==this.state.selected&&this.setState({selected:t})}else this.setState({selected:{label:void 0===e.placeholder?"Select...":e.placeholder,value:""}})}},{key:"componentDidMount",value:function(){document.addEventListener("click",this.handleDocumentClick,!1),document.addEventListener("touchend",this.handleDocumentClick,!1)}},{key:"componentWillUnmount",value:function(){this.mounted=!1,document.removeEventListener("click",this.handleDocumentClick,!1),document.removeEventListener("touchend",this.handleDocumentClick,!1)}},{key:"handleMouseDown",value:function(e){this.props.onFocus&&"function"==typeof this.props.onFocus&&this.props.onFocus(this.state.isOpen),"mousedown"===e.type&&0!==e.button||(e.stopPropagation(),e.preventDefault(),this.props.disabled||this.setState({isOpen:!this.state.isOpen}))}},{key:"parseValue",value:function(e,t){var n;if("string"==typeof e)for(var r=0,o=t.length;r<o;r++)if("group"===t[r].type){var i=t[r].items.filter((function(t){return t.value===e}));i.length&&(n=i[0])}else void 0!==t[r].value&&t[r].value===e&&(n=t[r]);return n||e}},{key:"setValue",value:function(e,t){var n={selected:{value:e,label:t},isOpen:!1};this.fireChangeEvent(n),this.setState(n)}},{key:"fireChangeEvent",value:function(e){e.selected!==this.state.selected&&this.props.onChange&&this.props.onChange(e.selected)}},{key:"renderOption",value:function(e){var t,n=e.value;void 0===n&&(n=e.label||e);var o=e.label||e.value||e,a=n===this.state.selected.value||n===this.state.selected,s=(c(t={},"".concat(this.props.baseClassName,"-option"),!0),c(t,e.className,!!e.className),c(t,"is-selected",a),t),l=(0,i.default)(s);return r.default.createElement("div",{key:n,className:l,onMouseDown:this.setValue.bind(this,n,o),onClick:this.setValue.bind(this,n,o),role:"option","aria-selected":a?"true":"false"},o)}},{key:"buildMenu",value:function(){var e=this,t=this.props,n=t.options,o=t.baseClassName,i=n.map((function(t){if("group"===t.type){var n=r.default.createElement("div",{className:"".concat(o,"-title")},t.name),i=t.items.map((function(t){return e.renderOption(t)}));return r.default.createElement("div",{className:"".concat(o,"-group"),key:t.name,role:"listbox",tabIndex:"-1"},n,i)}return e.renderOption(t)}));return i.length?i:r.default.createElement("div",{className:"".concat(o,"-noresults")},"No options found")}},{key:"handleDocumentClick",value:function(e){this.mounted&&(o.default.findDOMNode(this).contains(e.target)||this.state.isOpen&&this.setState({isOpen:!1}))}},{key:"isValueSelected",value:function(){return"string"==typeof this.state.selected||""!==this.state.selected.value}},{key:"render",value:function(){var e,t,n,o,a,s=this.props,l=s.baseClassName,u=s.controlClassName,p=s.placeholderClassName,f=s.menuClassName,d=s.arrowClassName,h=s.arrowClosed,m=s.arrowOpen,g=s.className,y=this.props.disabled?"Dropdown-disabled":"",v="string"==typeof this.state.selected?this.state.selected:this.state.selected.label,b=(0,i.default)((c(e={},"".concat(l,"-root"),!0),c(e,g,!!g),c(e,"is-open",this.state.isOpen),e)),x=(0,i.default)((c(t={},"".concat(l,"-control"),!0),c(t,u,!!u),c(t,y,!!y),t)),w=(0,i.default)((c(n={},"".concat(l,"-placeholder"),!0),c(n,p,!!p),c(n,"is-selected",this.isValueSelected()),n)),k=(0,i.default)((c(o={},"".concat(l,"-menu"),!0),c(o,f,!!f),o)),O=(0,i.default)((c(a={},"".concat(l,"-arrow"),!0),c(a,d,!!d),a)),_=r.default.createElement("div",{className:w},v),E=this.state.isOpen?r.default.createElement("div",{className:k,"aria-expanded":"true"},this.buildMenu()):null;return r.default.createElement("div",{className:b},r.default.createElement("div",{className:x,onMouseDown:this.handleMouseDown.bind(this),onTouchEnd:this.handleMouseDown.bind(this),"aria-haspopup":"listbox"},_,r.default.createElement("div",{className:"".concat(l,"-arrow-wrapper")},m&&h?this.state.isOpen?m:h:r.default.createElement("span",{className:O}))),E)}}])&&u(n.prototype,a),s&&u(n,s),t}(r.Component);h.defaultProps={baseClassName:"Dropdown"};var m=h;t.default=m},function(e,t,n){e.exports=function(){"use strict";var e=Object.hasOwnProperty,t=Object.setPrototypeOf,n=Object.isFrozen,r=Object.keys,o=Object.freeze,i=Object.seal,a="undefined"!=typeof Reflect&&Reflect,s=a.apply,l=a.construct;s||(s=function(e,t,n){return e.apply(t,n)}),o||(o=function(e){return e}),i||(i=function(e){return e}),l||(l=function(e,t){return new(Function.prototype.bind.apply(e,[null].concat(function(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t<e.length;t++)n[t]=e[t];return n}return Array.from(e)}(t))))});var c=O(Array.prototype.forEach),u=O(Array.prototype.indexOf),p=O(Array.prototype.join),f=O(Array.prototype.pop),d=O(Array.prototype.push),h=O(Array.prototype.slice),m=O(String.prototype.toLowerCase),g=O(String.prototype.match),y=O(String.prototype.replace),v=O(String.prototype.indexOf),b=O(String.prototype.trim),x=O(RegExp.prototype.test),w=_(RegExp),k=_(TypeError);function O(e){return function(t){for(var n=arguments.length,r=Array(n>1?n-1:0),o=1;o<n;o++)r[o-1]=arguments[o];return s(e,t,r)}}function _(e){return function(){for(var t=arguments.length,n=Array(t),r=0;r<t;r++)n[r]=arguments[r];return l(e,n)}}function E(e,r){t&&t(e,null);for(var o=r.length;o--;){var i=r[o];if("string"==typeof i){var a=m(i);a!==i&&(n(r)||(r[o]=a),i=a)}e[i]=!0}return e}function S(t){var n={},r=void 0;for(r in t)s(e,t,[r])&&(n[r]=t[r]);return n}var T=o(["a","abbr","acronym","address","area","article","aside","audio","b","bdi","bdo","big","blink","blockquote","body","br","button","canvas","caption","center","cite","code","col","colgroup","content","data","datalist","dd","decorator","del","details","dfn","dir","div","dl","dt","element","em","fieldset","figcaption","figure","font","footer","form","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","img","input","ins","kbd","label","legend","li","main","map","mark","marquee","menu","menuitem","meter","nav","nobr","ol","optgroup","option","output","p","picture","pre","progress","q","rp","rt","ruby","s","samp","section","select","shadow","small","source","spacer","span","strike","strong","style","sub","summary","sup","table","tbody","td","template","textarea","tfoot","th","thead","time","tr","track","tt","u","ul","var","video","wbr"]),j=o(["svg","a","altglyph","altglyphdef","altglyphitem","animatecolor","animatemotion","animatetransform","audio","canvas","circle","clippath","defs","desc","ellipse","filter","font","g","glyph","glyphref","hkern","image","line","lineargradient","marker","mask","metadata","mpath","path","pattern","polygon","polyline","radialgradient","rect","stop","style","switch","symbol","text","textpath","title","tref","tspan","video","view","vkern"]),C=o(["feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence"]),I=o(["math","menclose","merror","mfenced","mfrac","mglyph","mi","mlabeledtr","mmultiscripts","mn","mo","mover","mpadded","mphantom","mroot","mrow","ms","mspace","msqrt","mstyle","msub","msup","msubsup","mtable","mtd","mtext","mtr","munder","munderover"]),A=o(["#text"]),P=o(["accept","action","align","alt","autocomplete","background","bgcolor","border","cellpadding","cellspacing","checked","cite","class","clear","color","cols","colspan","controls","coords","crossorigin","datetime","default","dir","disabled","download","enctype","face","for","headers","height","hidden","high","href","hreflang","id","integrity","ismap","label","lang","list","loop","low","max","maxlength","media","method","min","minlength","multiple","name","noshade","novalidate","nowrap","open","optimum","pattern","placeholder","poster","preload","pubdate","radiogroup","readonly","rel","required","rev","reversed","role","rows","rowspan","spellcheck","scope","selected","shape","size","sizes","span","srclang","start","src","srcset","step","style","summary","tabindex","title","type","usemap","valign","value","width","xmlns"]),R=o(["accent-height","accumulate","additive","alignment-baseline","ascent","attributename","attributetype","azimuth","basefrequency","baseline-shift","begin","bias","by","class","clip","clip-path","clip-rule","color","color-interpolation","color-interpolation-filters","color-profile","color-rendering","cx","cy","d","dx","dy","diffuseconstant","direction","display","divisor","dur","edgemode","elevation","end","fill","fill-opacity","fill-rule","filter","filterunits","flood-color","flood-opacity","font-family","font-size","font-size-adjust","font-stretch","font-style","font-variant","font-weight","fx","fy","g1","g2","glyph-name","glyphref","gradientunits","gradienttransform","height","href","id","image-rendering","in","in2","k","k1","k2","k3","k4","kerning","keypoints","keysplines","keytimes","lang","lengthadjust","letter-spacing","kernelmatrix","kernelunitlength","lighting-color","local","marker-end","marker-mid","marker-start","markerheight","markerunits","markerwidth","maskcontentunits","maskunits","max","mask","media","method","mode","min","name","numoctaves","offset","operator","opacity","order","orient","orientation","origin","overflow","paint-order","path","pathlength","patterncontentunits","patterntransform","patternunits","points","preservealpha","preserveaspectratio","primitiveunits","r","rx","ry","radius","refx","refy","repeatcount","repeatdur","restart","result","rotate","scale","seed","shape-rendering","specularconstant","specularexponent","spreadmethod","stddeviation","stitchtiles","stop-color","stop-opacity","stroke-dasharray","stroke-dashoffset","stroke-linecap","stroke-linejoin","stroke-miterlimit","stroke-opacity","stroke","stroke-width","style","surfacescale","tabindex","targetx","targety","transform","text-anchor","text-decoration","text-rendering","textlength","type","u1","u2","unicode","values","viewbox","visibility","version","vert-adv-y","vert-origin-x","vert-origin-y","width","word-spacing","wrap","writing-mode","xchannelselector","ychannelselector","x","x1","x2","xmlns","y","y1","y2","z","zoomandpan"]),N=o(["accent","accentunder","align","bevelled","close","columnsalign","columnlines","columnspan","denomalign","depth","dir","display","displaystyle","encoding","fence","frame","height","href","id","largeop","length","linethickness","lspace","lquote","mathbackground","mathcolor","mathsize","mathvariant","maxsize","minsize","movablelimits","notation","numalign","open","rowalign","rowlines","rowspacing","rowspan","rspace","rquote","scriptlevel","scriptminsize","scriptsizemultiplier","selection","separator","separators","stretchy","subscriptshift","supscriptshift","symmetric","voffset","width","xmlns"]),L=o(["xlink:href","xml:id","xlink:title","xml:space","xmlns:xlink"]),M=i(/\{\{[\s\S]*|[\s\S]*\}\}/gm),D=i(/<%[\s\S]*|[\s\S]*%>/gm),F=i(/^data-[\-\w.\u00B7-\uFFFF]/),z=i(/^aria-[\-\w]+$/),U=i(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),B=i(/^(?:\w+script|data):/i),$=i(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205f\u3000]/g),q="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};function W(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t<e.length;t++)n[t]=e[t];return n}return Array.from(e)}var H=function(){return"undefined"==typeof window?null:window},V=function(e,t){if("object"!==(void 0===e?"undefined":q(e))||"function"!=typeof e.createPolicy)return null;var n=null;t.currentScript&&t.currentScript.hasAttribute("data-tt-policy-suffix")&&(n=t.currentScript.getAttribute("data-tt-policy-suffix"));var r="dompurify"+(n?"#"+n:"");try{return e.createPolicy(r,{createHTML:function(e){return e}})}catch(e){return console.warn("TrustedTypes policy "+r+" could not be created."),null}};return function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:H(),n=function(t){return e(t)};if(n.version="2.0.8",n.removed=[],!t||!t.document||9!==t.document.nodeType)return n.isSupported=!1,n;var i=t.document,a=!1,s=!1,l=t.document,O=t.DocumentFragment,_=t.HTMLTemplateElement,Y=t.Node,Q=t.NodeFilter,G=t.NamedNodeMap,X=void 0===G?t.NamedNodeMap||t.MozNamedAttrMap:G,K=t.Text,Z=t.Comment,J=t.DOMParser,ee=t.trustedTypes;if("function"==typeof _){var te=l.createElement("template");te.content&&te.content.ownerDocument&&(l=te.content.ownerDocument)}var ne=V(ee,i),re=ne?ne.createHTML(""):"",oe=l,ie=oe.implementation,ae=oe.createNodeIterator,se=oe.getElementsByTagName,le=oe.createDocumentFragment,ce=i.importNode,ue={};n.isSupported=ie&&void 0!==ie.createHTMLDocument&&9!==l.documentMode;var pe=M,fe=D,de=F,he=z,me=B,ge=$,ye=U,ve=null,be=E({},[].concat(W(T),W(j),W(C),W(I),W(A))),xe=null,we=E({},[].concat(W(P),W(R),W(N),W(L))),ke=null,Oe=null,_e=!0,Ee=!0,Se=!1,Te=!1,je=!1,Ce=!1,Ie=!1,Ae=!1,Pe=!1,Re=!1,Ne=!1,Le=!1,Me=!0,De=!0,Fe=!1,ze={},Ue=E({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","plaintext","script","style","svg","template","thead","title","video","xmp"]),Be=E({},["audio","video","img","source","image"]),$e=null,qe=E({},["alt","class","for","id","label","name","pattern","placeholder","summary","title","value","style","xmlns"]),We=null,He=l.createElement("form"),Ve=function(e){We&&We===e||(e&&"object"===(void 0===e?"undefined":q(e))||(e={}),ve="ALLOWED_TAGS"in e?E({},e.ALLOWED_TAGS):be,xe="ALLOWED_ATTR"in e?E({},e.ALLOWED_ATTR):we,$e="ADD_URI_SAFE_ATTR"in e?E(S(qe),e.ADD_URI_SAFE_ATTR):qe,ke="FORBID_TAGS"in e?E({},e.FORBID_TAGS):{},Oe="FORBID_ATTR"in e?E({},e.FORBID_ATTR):{},ze="USE_PROFILES"in e&&e.USE_PROFILES,_e=!1!==e.ALLOW_ARIA_ATTR,Ee=!1!==e.ALLOW_DATA_ATTR,Se=e.ALLOW_UNKNOWN_PROTOCOLS||!1,Te=e.SAFE_FOR_JQUERY||!1,je=e.SAFE_FOR_TEMPLATES||!1,Ce=e.WHOLE_DOCUMENT||!1,Pe=e.RETURN_DOM||!1,Re=e.RETURN_DOM_FRAGMENT||!1,Ne=e.RETURN_DOM_IMPORT||!1,Le=e.RETURN_TRUSTED_TYPE||!1,Ae=e.FORCE_BODY||!1,Me=!1!==e.SANITIZE_DOM,De=!1!==e.KEEP_CONTENT,Fe=e.IN_PLACE||!1,ye=e.ALLOWED_URI_REGEXP||ye,je&&(Ee=!1),Re&&(Pe=!0),ze&&(ve=E({},[].concat(W(A))),xe=[],!0===ze.html&&(E(ve,T),E(xe,P)),!0===ze.svg&&(E(ve,j),E(xe,R),E(xe,L)),!0===ze.svgFilters&&(E(ve,C),E(xe,R),E(xe,L)),!0===ze.mathMl&&(E(ve,I),E(xe,N),E(xe,L))),e.ADD_TAGS&&(ve===be&&(ve=S(ve)),E(ve,e.ADD_TAGS)),e.ADD_ATTR&&(xe===we&&(xe=S(xe)),E(xe,e.ADD_ATTR)),e.ADD_URI_SAFE_ATTR&&E($e,e.ADD_URI_SAFE_ATTR),De&&(ve["#text"]=!0),Ce&&E(ve,["html","head","body"]),ve.table&&(E(ve,["tbody"]),delete ke.tbody),o&&o(e),We=e)},Ye=function(e){d(n.removed,{element:e});try{e.parentNode.removeChild(e)}catch(t){e.outerHTML=re}},Qe=function(e,t){try{d(n.removed,{attribute:t.getAttributeNode(e),from:t})}catch(e){d(n.removed,{attribute:null,from:t})}t.removeAttribute(e)},Ge=function(e){var t=void 0,n=void 0;if(Ae)e="<remove></remove>"+e;else{var r=g(e,/^[\s]+/);n=r&&r[0]}var o=ne?ne.createHTML(e):e;if(a)try{t=(new J).parseFromString(o,"text/html")}catch(e){}if(s&&E(ke,["title"]),!t||!t.documentElement){var i=(t=ie.createHTMLDocument("")).body;i.parentNode.removeChild(i.parentNode.firstElementChild),i.outerHTML=o}return e&&n&&t.body.insertBefore(l.createTextNode(n),t.body.childNodes[0]||null),se.call(t,Ce?"html":"body")[0]};n.isSupported&&(function(){try{Ge('<svg><p><textarea><img src="</textarea><img src=x abc=1//">').querySelector("svg img")&&(a=!0)}catch(e){}}(),function(){try{var e=Ge("<x/><title></title><img>");x(/<\/title/,e.querySelector("title").innerHTML)&&(s=!0)}catch(e){}}());var Xe=function(e){return ae.call(e.ownerDocument||e,e,Q.SHOW_ELEMENT|Q.SHOW_COMMENT|Q.SHOW_TEXT,(function(){return Q.FILTER_ACCEPT}),!1)},Ke=function(e){return!(e instanceof K||e instanceof Z||"string"==typeof e.nodeName&&"string"==typeof e.textContent&&"function"==typeof e.removeChild&&e.attributes instanceof X&&"function"==typeof e.removeAttribute&&"function"==typeof e.setAttribute&&"string"==typeof e.namespaceURI)},Ze=function(e){return"object"===(void 0===Y?"undefined":q(Y))?e instanceof Y:e&&"object"===(void 0===e?"undefined":q(e))&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName},Je=function(e,t,r){ue[e]&&c(ue[e],(function(e){e.call(n,t,r,We)}))},et=function(e){var t=void 0;if(Je("beforeSanitizeElements",e,null),Ke(e))return Ye(e),!0;var r=m(e.nodeName);if(Je("uponSanitizeElement",e,{tagName:r,allowedTags:ve}),("svg"===r||"math"===r)&&0!==e.querySelectorAll("p, br").length)return Ye(e),!0;if(!ve[r]||ke[r]){if(De&&!Ue[r]&&"function"==typeof e.insertAdjacentHTML)try{var o=e.innerHTML;e.insertAdjacentHTML("AfterEnd",ne?ne.createHTML(o):o)}catch(e){}return Ye(e),!0}return"noscript"===r&&x(/<\/noscript/i,e.innerHTML)||"noembed"===r&&x(/<\/noembed/i,e.innerHTML)?(Ye(e),!0):(!Te||e.firstElementChild||e.content&&e.content.firstElementChild||!x(/</g,e.textContent)||(d(n.removed,{element:e.cloneNode()}),e.innerHTML?e.innerHTML=y(e.innerHTML,/</g,"<"):e.innerHTML=y(e.textContent,/</g,"<")),je&&3===e.nodeType&&(t=e.textContent,t=y(t,pe," "),t=y(t,fe," "),e.textContent!==t&&(d(n.removed,{element:e.cloneNode()}),e.textContent=t)),Je("afterSanitizeElements",e,null),!1)},tt=function(e,t,n){if(Me&&("id"===t||"name"===t)&&(n in l||n in He))return!1;if(Ee&&x(de,t));else if(_e&&x(he,t));else{if(!xe[t]||Oe[t])return!1;if($e[t]);else if(x(ye,y(n,ge,"")));else if("src"!==t&&"xlink:href"!==t&&"href"!==t||"script"===e||0!==v(n,"data:")||!Be[e])if(Se&&!x(me,y(n,ge,"")));else if(n)return!1}return!0},nt=function(e){var t=void 0,o=void 0,i=void 0,a=void 0,s=void 0;Je("beforeSanitizeAttributes",e,null);var l=e.attributes;if(l){var c={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:xe};for(s=l.length;s--;){var d=t=l[s],g=d.name,v=d.namespaceURI;if(o=b(t.value),i=m(g),c.attrName=i,c.attrValue=o,c.keepAttr=!0,c.forceKeepAttr=void 0,Je("uponSanitizeAttribute",e,c),o=c.attrValue,!c.forceKeepAttr){if("name"===i&&"IMG"===e.nodeName&&l.id)a=l.id,l=h(l,[]),Qe("id",e),Qe(g,e),u(l,a)>s&&e.setAttribute("id",a.value);else{if("INPUT"===e.nodeName&&"type"===i&&"file"===o&&c.keepAttr&&(xe[i]||!Oe[i]))continue;"id"===g&&e.setAttribute(g,""),Qe(g,e)}if(c.keepAttr)if(Te&&x(/\/>/i,o))Qe(g,e);else if(x(/svg|math/i,e.namespaceURI)&&x(w("</("+p(r(Ue),"|")+")","i"),o))Qe(g,e);else{je&&(o=y(o,pe," "),o=y(o,fe," "));var k=e.nodeName.toLowerCase();if(tt(k,i,o))try{v?e.setAttributeNS(v,g,o):e.setAttribute(g,o),f(n.removed)}catch(e){}}}}Je("afterSanitizeAttributes",e,null)}},rt=function e(t){var n=void 0,r=Xe(t);for(Je("beforeSanitizeShadowDOM",t,null);n=r.nextNode();)Je("uponSanitizeShadowNode",n,null),et(n)||(n.content instanceof O&&e(n.content),nt(n));Je("afterSanitizeShadowDOM",t,null)};return n.sanitize=function(e,r){var o=void 0,a=void 0,s=void 0,l=void 0,c=void 0;if(e||(e="\x3c!--\x3e"),"string"!=typeof e&&!Ze(e)){if("function"!=typeof e.toString)throw k("toString is not a function");if("string"!=typeof(e=e.toString()))throw k("dirty is not a string, aborting")}if(!n.isSupported){if("object"===q(t.toStaticHTML)||"function"==typeof t.toStaticHTML){if("string"==typeof e)return t.toStaticHTML(e);if(Ze(e))return t.toStaticHTML(e.outerHTML)}return e}if(Ie||Ve(r),n.removed=[],"string"==typeof e&&(Fe=!1),Fe);else if(e instanceof Y)1===(a=(o=Ge("\x3c!--\x3e")).ownerDocument.importNode(e,!0)).nodeType&&"BODY"===a.nodeName||"HTML"===a.nodeName?o=a:o.appendChild(a);else{if(!Pe&&!je&&!Ce&&Le&&-1===e.indexOf("<"))return ne?ne.createHTML(e):e;if(!(o=Ge(e)))return Pe?null:re}o&&Ae&&Ye(o.firstChild);for(var u=Xe(Fe?e:o);s=u.nextNode();)3===s.nodeType&&s===l||et(s)||(s.content instanceof O&&rt(s.content),nt(s),l=s);if(l=null,Fe)return e;if(Pe){if(Re)for(c=le.call(o.ownerDocument);o.firstChild;)c.appendChild(o.firstChild);else c=o;return Ne&&(c=ce.call(i,c,!0)),c}var p=Ce?o.outerHTML:o.innerHTML;return je&&(p=y(p,pe," "),p=y(p,fe," ")),ne&&Le?ne.createHTML(p):p},n.setConfig=function(e){Ve(e),Ie=!0},n.clearConfig=function(){We=null,Ie=!1},n.isValidAttribute=function(e,t,n){We||Ve({});var r=m(e),o=m(t);return tt(r,o,n)},n.addHook=function(e,t){"function"==typeof t&&(ue[e]=ue[e]||[],d(ue[e],t))},n.removeHook=function(e){ue[e]&&f(ue[e])},n.removeHooks=function(e){ue[e]&&(ue[e]=[])},n.removeAllHooks=function(){ue={}},n}()}()},function(e,t,n){"use strict";function r(e){return Object.prototype.toString.call(e).slice(8,-1)}function o(e){return"Object"===r(e)&&(e.constructor===Object&&Object.getPrototypeOf(e)===Object.prototype)}function i(e){return"Array"===r(e)}function a(e){return"Symbol"===r(e)} /*! ***************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use @@ -101,7 +101,7 @@ See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. ***************************************************************************** */ -function s(){for(var e=0,t=0,n=arguments.length;t<n;t++)e+=arguments[t].length;var r=Array(e),o=0;for(t=0;t<n;t++)for(var i=arguments[t],a=0,s=i.length;a<s;a++,o++)r[o]=i[a];return r}function l(e,t,n,r){var o=r.propertyIsEnumerable(t)?"enumerable":"nonenumerable";"enumerable"===o&&(e[t]=n),"nonenumerable"===o&&Object.defineProperty(e,t,{value:n,enumerable:!1,writable:!0,configurable:!0})}function c(e,t,n){if(!o(t))return n&&i(n)&&n.forEach((function(n){t=n(e,t)})),t;var r={};o(e)&&(r=s(Object.getOwnPropertyNames(e),Object.getOwnPropertySymbols(e)).reduce((function(n,r){var o=e[r];return(!a(r)&&!Object.getOwnPropertyNames(t).includes(r)||a(r)&&!Object.getOwnPropertySymbols(t).includes(r))&&l(n,r,o,e),n}),{}));return s(Object.getOwnPropertyNames(t),Object.getOwnPropertySymbols(t)).reduce((function(r,a){var s=t[a],u=o(e)?e[a]:void 0;return n&&i(n)&&n.forEach((function(e){s=e(u,s)})),void 0!==u&&o(s)&&(s=c(u,s,n)),l(r,a,s,t),r}),r)}t.a=function(e){for(var t=[],n=1;n<arguments.length;n++)t[n-1]=arguments[n];var r=null,i=e;return o(e)&&e.extensions&&1===Object.keys(e).length&&(i={},r=e.extensions),t.reduce((function(e,t){return c(e,t,r)}),i)}},function(e,t,n){"use strict";var r=/^((children|dangerouslySetInnerHTML|key|ref|autoFocus|defaultValue|defaultChecked|innerHTML|suppressContentEditableWarning|suppressHydrationWarning|valueLink|accept|acceptCharset|accessKey|action|allow|allowUserMedia|allowPaymentRequest|allowFullScreen|allowTransparency|alt|async|autoComplete|autoPlay|capture|cellPadding|cellSpacing|challenge|charSet|checked|cite|classID|className|cols|colSpan|content|contentEditable|contextMenu|controls|controlsList|coords|crossOrigin|data|dateTime|decoding|default|defer|dir|disabled|disablePictureInPicture|download|draggable|encType|form|formAction|formEncType|formMethod|formNoValidate|formTarget|frameBorder|headers|height|hidden|high|href|hrefLang|htmlFor|httpEquiv|id|inputMode|integrity|is|keyParams|keyType|kind|label|lang|list|loading|loop|low|marginHeight|marginWidth|max|maxLength|media|mediaGroup|method|min|minLength|multiple|muted|name|nonce|noValidate|open|optimum|pattern|placeholder|playsInline|poster|preload|profile|radioGroup|readOnly|referrerPolicy|rel|required|reversed|role|rows|rowSpan|sandbox|scope|scoped|scrolling|seamless|selected|shape|size|sizes|slot|span|spellCheck|src|srcDoc|srcLang|srcSet|start|step|style|summary|tabIndex|target|title|type|useMap|value|width|wmode|wrap|about|datatype|inlist|prefix|property|resource|typeof|vocab|autoCapitalize|autoCorrect|autoSave|color|inert|itemProp|itemScope|itemType|itemID|itemRef|on|results|security|unselectable|accentHeight|accumulate|additive|alignmentBaseline|allowReorder|alphabetic|amplitude|arabicForm|ascent|attributeName|attributeType|autoReverse|azimuth|baseFrequency|baselineShift|baseProfile|bbox|begin|bias|by|calcMode|capHeight|clip|clipPathUnits|clipPath|clipRule|colorInterpolation|colorInterpolationFilters|colorProfile|colorRendering|contentScriptType|contentStyleType|cursor|cx|cy|d|decelerate|descent|diffuseConstant|direction|display|divisor|dominantBaseline|dur|dx|dy|edgeMode|elevation|enableBackground|end|exponent|externalResourcesRequired|fill|fillOpacity|fillRule|filter|filterRes|filterUnits|floodColor|floodOpacity|focusable|fontFamily|fontSize|fontSizeAdjust|fontStretch|fontStyle|fontVariant|fontWeight|format|from|fr|fx|fy|g1|g2|glyphName|glyphOrientationHorizontal|glyphOrientationVertical|glyphRef|gradientTransform|gradientUnits|hanging|horizAdvX|horizOriginX|ideographic|imageRendering|in|in2|intercept|k|k1|k2|k3|k4|kernelMatrix|kernelUnitLength|kerning|keyPoints|keySplines|keyTimes|lengthAdjust|letterSpacing|lightingColor|limitingConeAngle|local|markerEnd|markerMid|markerStart|markerHeight|markerUnits|markerWidth|mask|maskContentUnits|maskUnits|mathematical|mode|numOctaves|offset|opacity|operator|order|orient|orientation|origin|overflow|overlinePosition|overlineThickness|panose1|paintOrder|pathLength|patternContentUnits|patternTransform|patternUnits|pointerEvents|points|pointsAtX|pointsAtY|pointsAtZ|preserveAlpha|preserveAspectRatio|primitiveUnits|r|radius|refX|refY|renderingIntent|repeatCount|repeatDur|requiredExtensions|requiredFeatures|restart|result|rotate|rx|ry|scale|seed|shapeRendering|slope|spacing|specularConstant|specularExponent|speed|spreadMethod|startOffset|stdDeviation|stemh|stemv|stitchTiles|stopColor|stopOpacity|strikethroughPosition|strikethroughThickness|string|stroke|strokeDasharray|strokeDashoffset|strokeLinecap|strokeLinejoin|strokeMiterlimit|strokeOpacity|strokeWidth|surfaceScale|systemLanguage|tableValues|targetX|targetY|textAnchor|textDecoration|textRendering|textLength|to|transform|u1|u2|underlinePosition|underlineThickness|unicode|unicodeBidi|unicodeRange|unitsPerEm|vAlphabetic|vHanging|vIdeographic|vMathematical|values|vectorEffect|version|vertAdvY|vertOriginX|vertOriginY|viewBox|viewTarget|visibility|widths|wordSpacing|writingMode|x|xHeight|x1|x2|xChannelSelector|xlinkActuate|xlinkArcrole|xlinkHref|xlinkRole|xlinkShow|xlinkTitle|xlinkType|xmlBase|xmlns|xmlnsXlink|xmlLang|xmlSpace|y|y1|y2|yChannelSelector|z|zoomAndPan|for|class|autofocus)|(([Dd][Aa][Tt][Aa]|[Aa][Rr][Ii][Aa]|x)-.*))$/,o=function(e){var t={};return function(n){return void 0===t[n]&&(t[n]=e(n)),t[n]}}((function(e){return r.test(e)||111===e.charCodeAt(0)&&110===e.charCodeAt(1)&&e.charCodeAt(2)<91}));t.a=o},function(e,t,n){n(163),e.exports=n(319)},function(e,t,n){"use strict";n.r(t);n(164),n(185),n(188),n(191),n(194),n(196),n(202),n(224),n(225)},function(e,t,n){n(68),n(105),n(112),n(177),n(183),n(184);var r=n(37);e.exports=r.Promise},function(e,t,n){var r=n(4),o=n(74),i=r.WeakMap;e.exports="function"==typeof i&&/native code/.test(o(i))},function(e,t,n){"use strict";var r=n(69),o=n(104);e.exports=r?{}.toString:function(){return"[object "+o(this)+"]"}},function(e,t,n){var r=n(75),o=n(44),i=function(e){return function(t,n){var i,a,s=String(o(t)),l=r(n),c=s.length;return l<0||l>=c?e?"":void 0:(i=s.charCodeAt(l))<55296||i>56319||l+1===c||(a=s.charCodeAt(l+1))<56320||a>57343?e?s.charAt(l):i:e?s.slice(l,l+2):a-56320+(i-55296<<10)+65536}};e.exports={codeAt:i(!1),charAt:i(!0)}},function(e,t,n){var r=n(29),o=n(79),i=n(81),a=n(21);e.exports=r("Reflect","ownKeys")||function(e){var t=o.f(a(e)),n=i.f;return n?t.concat(n(e)):t}},function(e,t,n){var r=n(36),o=n(38),i=n(170),a=function(e){return function(t,n,a){var s,l=r(t),c=o(l.length),u=i(a,c);if(e&&n!=n){for(;c>u;)if((s=l[u++])!=s)return!0}else for(;c>u;u++)if((e||u in l)&&l[u]===n)return e||u||0;return!e&&-1}};e.exports={includes:a(!0),indexOf:a(!1)}},function(e,t,n){var r=n(75),o=Math.max,i=Math.min;e.exports=function(e,t){var n=r(e);return n<0?o(n+t,0):i(n,t)}},function(e,t,n){"use strict";var r=n(108).IteratorPrototype,o=n(57),i=n(42),a=n(30),s=n(46),l=function(){return this};e.exports=function(e,t,n){var c=t+" Iterator";return e.prototype=o(r,{next:i(1,n)}),a(e,c,!1,!0),s[c]=l,e}},function(e,t,n){var r=n(8);e.exports=!r((function(){function e(){}return e.prototype.constructor=null,Object.getPrototypeOf(new e)!==e.prototype}))},function(e,t,n){var r=n(18),o=n(16),i=n(21),a=n(83);e.exports=r?Object.defineProperties:function(e,t){i(e);for(var n,r=a(t),s=r.length,l=0;s>l;)o.f(e,n=r[l++],t[n]);return e}},function(e,t,n){var r=n(9);e.exports=function(e){if(!r(e)&&null!==e)throw TypeError("Can't set "+String(e)+" as a prototype");return e}},function(e,t){e.exports={CSSRuleList:0,CSSStyleDeclaration:0,CSSValueList:0,ClientRectList:0,DOMRectList:0,DOMStringList:0,DOMTokenList:1,DataTransferItemList:0,FileList:0,HTMLAllCollection:0,HTMLCollection:0,HTMLFormElement:0,HTMLSelectElement:0,MediaList:0,MimeTypeArray:0,NamedNodeMap:0,NodeList:1,PaintRequestList:0,Plugin:0,PluginArray:0,SVGLengthList:0,SVGNumberList:0,SVGPathSegList:0,SVGPointList:0,SVGStringList:0,SVGTransformList:0,SourceBufferList:0,StyleSheetList:0,TextTrackCueList:0,TextTrackList:0,TouchList:0}},function(e,t,n){"use strict";var r=n(36),o=n(113),i=n(46),a=n(34),s=n(76),l=a.set,c=a.getterFor("Array Iterator");e.exports=s(Array,"Array",(function(e,t){l(this,{type:"Array Iterator",target:r(e),index:0,kind:t})}),(function(){var e=c(this),t=e.target,n=e.kind,r=e.index++;return!t||r>=t.length?(e.target=void 0,{value:void 0,done:!0}):"keys"==n?{value:r,done:!1}:"values"==n?{value:t[r],done:!1}:{value:[r,t[r]],done:!1}}),"values"),i.Arguments=i.Array,o("keys"),o("values"),o("entries")},function(e,t,n){"use strict";var r,o,i,a,s=n(17),l=n(23),c=n(4),u=n(29),p=n(114),f=n(25),d=n(115),h=n(30),m=n(116),g=n(9),y=n(47),v=n(84),b=n(28),x=n(74),w=n(58),k=n(117),O=n(118),_=n(119).set,E=n(181),S=n(122),T=n(182),j=n(85),C=n(123),A=n(34),I=n(82),P=n(5),R=n(86),N=P("species"),L="Promise",M=A.get,D=A.set,F=A.getterFor(L),z=p,U=c.TypeError,B=c.document,$=c.process,q=u("fetch"),W=j.f,H=W,V="process"==b($),Y=!!(B&&B.createEvent&&c.dispatchEvent),Q=I(L,(function(){if(!(x(z)!==String(z))){if(66===R)return!0;if(!V&&"function"!=typeof PromiseRejectionEvent)return!0}if(l&&!z.prototype.finally)return!0;if(R>=51&&/native code/.test(z))return!1;var e=z.resolve(1),t=function(e){e((function(){}),(function(){}))};return(e.constructor={})[N]=t,!(e.then((function(){}))instanceof t)})),G=Q||!k((function(e){z.all(e).catch((function(){}))})),X=function(e){var t;return!(!g(e)||"function"!=typeof(t=e.then))&&t},K=function(e,t,n){if(!t.notified){t.notified=!0;var r=t.reactions;E((function(){for(var o=t.value,i=1==t.state,a=0;r.length>a;){var s,l,c,u=r[a++],p=i?u.ok:u.fail,f=u.resolve,d=u.reject,h=u.domain;try{p?(i||(2===t.rejection&&te(e,t),t.rejection=1),!0===p?s=o:(h&&h.enter(),s=p(o),h&&(h.exit(),c=!0)),s===u.promise?d(U("Promise-chain cycle")):(l=X(s))?l.call(s,f,d):f(s)):d(o)}catch(e){h&&!c&&h.exit(),d(e)}}t.reactions=[],t.notified=!1,n&&!t.rejection&&J(e,t)}))}},Z=function(e,t,n){var r,o;Y?((r=B.createEvent("Event")).promise=t,r.reason=n,r.initEvent(e,!1,!0),c.dispatchEvent(r)):r={promise:t,reason:n},(o=c["on"+e])?o(r):"unhandledrejection"===e&&T("Unhandled promise rejection",n)},J=function(e,t){_.call(c,(function(){var n,r=t.value;if(ee(t)&&(n=C((function(){V?$.emit("unhandledRejection",r,e):Z("unhandledrejection",e,r)})),t.rejection=V||ee(t)?2:1,n.error))throw n.value}))},ee=function(e){return 1!==e.rejection&&!e.parent},te=function(e,t){_.call(c,(function(){V?$.emit("rejectionHandled",e):Z("rejectionhandled",e,t.value)}))},ne=function(e,t,n,r){return function(o){e(t,n,o,r)}},re=function(e,t,n,r){t.done||(t.done=!0,r&&(t=r),t.value=n,t.state=2,K(e,t,!0))},oe=function(e,t,n,r){if(!t.done){t.done=!0,r&&(t=r);try{if(e===n)throw U("Promise can't be resolved itself");var o=X(n);o?E((function(){var r={done:!1};try{o.call(n,ne(oe,e,r,t),ne(re,e,r,t))}catch(n){re(e,r,n,t)}})):(t.value=n,t.state=1,K(e,t,!1))}catch(n){re(e,{done:!1},n,t)}}};Q&&(z=function(e){v(this,z,L),y(e),r.call(this);var t=M(this);try{e(ne(oe,this,t),ne(re,this,t))}catch(e){re(this,t,e)}},(r=function(e){D(this,{type:L,done:!1,notified:!1,parent:!1,reactions:[],rejection:!1,state:0,value:void 0})}).prototype=d(z.prototype,{then:function(e,t){var n=F(this),r=W(O(this,z));return r.ok="function"!=typeof e||e,r.fail="function"==typeof t&&t,r.domain=V?$.domain:void 0,n.parent=!0,n.reactions.push(r),0!=n.state&&K(this,n,!1),r.promise},catch:function(e){return this.then(void 0,e)}}),o=function(){var e=new r,t=M(e);this.promise=e,this.resolve=ne(oe,e,t),this.reject=ne(re,e,t)},j.f=W=function(e){return e===z||e===i?new o(e):H(e)},l||"function"!=typeof p||(a=p.prototype.then,f(p.prototype,"then",(function(e,t){var n=this;return new z((function(e,t){a.call(n,e,t)})).then(e,t)}),{unsafe:!0}),"function"==typeof q&&s({global:!0,enumerable:!0,forced:!0},{fetch:function(e){return S(z,q.apply(c,arguments))}}))),s({global:!0,wrap:!0,forced:Q},{Promise:z}),h(z,L,!1,!0),m(L),i=u(L),s({target:L,stat:!0,forced:Q},{reject:function(e){var t=W(this);return t.reject.call(void 0,e),t.promise}}),s({target:L,stat:!0,forced:l||Q},{resolve:function(e){return S(l&&this===i?z:this,e)}}),s({target:L,stat:!0,forced:G},{all:function(e){var t=this,n=W(t),r=n.resolve,o=n.reject,i=C((function(){var n=y(t.resolve),i=[],a=0,s=1;w(e,(function(e){var l=a++,c=!1;i.push(void 0),s++,n.call(t,e).then((function(e){c||(c=!0,i[l]=e,--s||r(i))}),o)})),--s||r(i)}));return i.error&&o(i.value),n.promise},race:function(e){var t=this,n=W(t),r=n.reject,o=C((function(){var o=y(t.resolve);w(e,(function(e){o.call(t,e).then(n.resolve,r)}))}));return o.error&&r(o.value),n.promise}})},function(e,t,n){var r=n(5),o=n(46),i=r("iterator"),a=Array.prototype;e.exports=function(e){return void 0!==e&&(o.Array===e||a[i]===e)}},function(e,t,n){var r=n(104),o=n(46),i=n(5)("iterator");e.exports=function(e){if(null!=e)return e[i]||e["@@iterator"]||o[r(e)]}},function(e,t,n){var r=n(21);e.exports=function(e,t,n,o){try{return o?t(r(n)[0],n[1]):t(n)}catch(t){var i=e.return;throw void 0!==i&&r(i.call(e)),t}}},function(e,t,n){var r,o,i,a,s,l,c,u,p=n(4),f=n(35).f,d=n(28),h=n(119).set,m=n(120),g=p.MutationObserver||p.WebKitMutationObserver,y=p.process,v=p.Promise,b="process"==d(y),x=f(p,"queueMicrotask"),w=x&&x.value;w||(r=function(){var e,t;for(b&&(e=y.domain)&&e.exit();o;){t=o.fn,o=o.next;try{t()}catch(e){throw o?a():i=void 0,e}}i=void 0,e&&e.enter()},b?a=function(){y.nextTick(r)}:g&&!m?(s=!0,l=document.createTextNode(""),new g(r).observe(l,{characterData:!0}),a=function(){l.data=s=!s}):v&&v.resolve?(c=v.resolve(void 0),u=c.then,a=function(){u.call(c,r)}):a=function(){h.call(p,r)}),e.exports=w||function(e){var t={fn:e,next:void 0};i&&(i.next=t),o||(o=t,a()),i=t}},function(e,t,n){var r=n(4);e.exports=function(e,t){var n=r.console;n&&n.error&&(1===arguments.length?n.error(e):n.error(e,t))}},function(e,t,n){"use strict";var r=n(17),o=n(47),i=n(85),a=n(123),s=n(58);r({target:"Promise",stat:!0},{allSettled:function(e){var t=this,n=i.f(t),r=n.resolve,l=n.reject,c=a((function(){var n=o(t.resolve),i=[],a=0,l=1;s(e,(function(e){var o=a++,s=!1;i.push(void 0),l++,n.call(t,e).then((function(e){s||(s=!0,i[o]={status:"fulfilled",value:e},--l||r(i))}),(function(e){s||(s=!0,i[o]={status:"rejected",reason:e},--l||r(i))}))})),--l||r(i)}));return c.error&&l(c.value),n.promise}})},function(e,t,n){"use strict";var r=n(17),o=n(23),i=n(114),a=n(8),s=n(29),l=n(118),c=n(122),u=n(25);r({target:"Promise",proto:!0,real:!0,forced:!!i&&a((function(){i.prototype.finally.call({then:function(){}},(function(){}))}))},{finally:function(e){var t=l(this,s("Promise")),n="function"==typeof e;return this.then(n?function(n){return c(t,e()).then((function(){return n}))}:e,n?function(n){return c(t,e()).then((function(){throw n}))}:e)}}),o||"function"!=typeof i||i.prototype.finally||u(i.prototype,"finally",s("Promise").prototype.finally)},function(e,t,n){n(186);var r=n(88);e.exports=r("Array","find")},function(e,t,n){"use strict";var r=n(17),o=n(124).find,i=n(113),a=n(187),s=!0,l=a("find");"find"in[]&&Array(1).find((function(){s=!1})),r({target:"Array",proto:!0,forced:s||!l},{find:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}}),i("find")},function(e,t,n){var r=n(18),o=n(8),i=n(11),a=Object.defineProperty,s={},l=function(e){throw e};e.exports=function(e,t){if(i(s,e))return s[e];t||(t={});var n=[][e],c=!!i(t,"ACCESSORS")&&t.ACCESSORS,u=i(t,0)?t[0]:l,p=i(t,1)?t[1]:void 0;return s[e]=!!n&&!o((function(){if(c&&!r)return!0;var e={length:-1};c?a(e,1,{enumerable:!0,get:l}):e[1]=1,n.call(e,u,p)}))}},function(e,t,n){n(189);var r=n(37);e.exports=r.Object.assign},function(e,t,n){var r=n(17),o=n(190);r({target:"Object",stat:!0,forced:Object.assign!==o},{assign:o})},function(e,t,n){"use strict";var r=n(18),o=n(8),i=n(83),a=n(81),s=n(77),l=n(45),c=n(78),u=Object.assign,p=Object.defineProperty;e.exports=!u||o((function(){if(r&&1!==u({b:1},u(p({},"a",{enumerable:!0,get:function(){p(this,"b",{value:3,enumerable:!1})}}),{b:2})).b)return!0;var e={},t={},n=Symbol();return e[n]=7,"abcdefghijklmnopqrst".split("").forEach((function(e){t[e]=e})),7!=u({},e)[n]||"abcdefghijklmnopqrst"!=i(u({},t)).join("")}))?function(e,t){for(var n=l(e),o=arguments.length,u=1,p=a.f,f=s.f;o>u;)for(var d,h=c(arguments[u++]),m=p?i(h).concat(p(h)):i(h),g=m.length,y=0;g>y;)d=m[y++],r&&!f.call(h,d)||(n[d]=h[d]);return n}:u},function(e,t,n){n(192);var r=n(88);e.exports=r("String","endsWith")},function(e,t,n){"use strict";var r,o=n(17),i=n(35).f,a=n(38),s=n(126),l=n(44),c=n(127),u=n(23),p="".endsWith,f=Math.min,d=c("endsWith");o({target:"String",proto:!0,forced:!!(u||d||(r=i(String.prototype,"endsWith"),!r||r.writable))&&!d},{endsWith:function(e){var t=String(l(this));s(e);var n=arguments.length>1?arguments[1]:void 0,r=a(t.length),o=void 0===n?r:f(a(n),r),i=String(e);return p?p.call(t,i,o):t.slice(o-i.length,o)===i}})},function(e,t,n){var r=n(9),o=n(28),i=n(5)("match");e.exports=function(e){var t;return r(e)&&(void 0!==(t=e[i])?!!t:"RegExp"==o(e))}},function(e,t,n){n(195);var r=n(88);e.exports=r("String","startsWith")},function(e,t,n){"use strict";var r,o=n(17),i=n(35).f,a=n(38),s=n(126),l=n(44),c=n(127),u=n(23),p="".startsWith,f=Math.min,d=c("startsWith");o({target:"String",proto:!0,forced:!!(u||d||(r=i(String.prototype,"startsWith"),!r||r.writable))&&!d},{startsWith:function(e){var t=String(l(this));s(e);var n=a(f(arguments.length>1?arguments[1]:void 0,t.length)),r=String(e);return p?p.call(t,r,n):t.slice(n,n+r.length)===r}})},function(e,t,n){n(197),n(68),n(105),n(112);var r=n(37);e.exports=r.Map},function(e,t,n){"use strict";var r=n(198),o=n(201);e.exports=r("Map",(function(e){return function(){return e(this,arguments.length?arguments[0]:void 0)}}),o)},function(e,t,n){"use strict";var r=n(17),o=n(4),i=n(82),a=n(25),s=n(128),l=n(58),c=n(84),u=n(9),p=n(8),f=n(117),d=n(30),h=n(200);e.exports=function(e,t,n){var m=-1!==e.indexOf("Map"),g=-1!==e.indexOf("Weak"),y=m?"set":"add",v=o[e],b=v&&v.prototype,x=v,w={},k=function(e){var t=b[e];a(b,e,"add"==e?function(e){return t.call(this,0===e?0:e),this}:"delete"==e?function(e){return!(g&&!u(e))&&t.call(this,0===e?0:e)}:"get"==e?function(e){return g&&!u(e)?void 0:t.call(this,0===e?0:e)}:"has"==e?function(e){return!(g&&!u(e))&&t.call(this,0===e?0:e)}:function(e,n){return t.call(this,0===e?0:e,n),this})};if(i(e,"function"!=typeof v||!(g||b.forEach&&!p((function(){(new v).entries().next()})))))x=n.getConstructor(t,e,m,y),s.REQUIRED=!0;else if(i(e,!0)){var O=new x,_=O[y](g?{}:-0,1)!=O,E=p((function(){O.has(1)})),S=f((function(e){new v(e)})),T=!g&&p((function(){for(var e=new v,t=5;t--;)e[y](t,t);return!e.has(-0)}));S||((x=t((function(t,n){c(t,x,e);var r=h(new v,t,x);return null!=n&&l(n,r[y],r,m),r}))).prototype=b,b.constructor=x),(E||T)&&(k("delete"),k("has"),m&&k("get")),(T||_)&&k(y),g&&b.clear&&delete b.clear}return w[e]=x,r({global:!0,forced:x!=v},w),d(x,e),g||n.setStrong(x,e,m),x}},function(e,t,n){var r=n(8);e.exports=!r((function(){return Object.isExtensible(Object.preventExtensions({}))}))},function(e,t,n){var r=n(9),o=n(111);e.exports=function(e,t,n){var i,a;return o&&"function"==typeof(i=t.constructor)&&i!==n&&r(a=i.prototype)&&a!==n.prototype&&o(e,a),e}},function(e,t,n){"use strict";var r=n(16).f,o=n(57),i=n(115),a=n(48),s=n(84),l=n(58),c=n(76),u=n(116),p=n(18),f=n(128).fastKey,d=n(34),h=d.set,m=d.getterFor;e.exports={getConstructor:function(e,t,n,c){var u=e((function(e,r){s(e,u,t),h(e,{type:t,index:o(null),first:void 0,last:void 0,size:0}),p||(e.size=0),null!=r&&l(r,e[c],e,n)})),d=m(t),g=function(e,t,n){var r,o,i=d(e),a=y(e,t);return a?a.value=n:(i.last=a={index:o=f(t,!0),key:t,value:n,previous:r=i.last,next:void 0,removed:!1},i.first||(i.first=a),r&&(r.next=a),p?i.size++:e.size++,"F"!==o&&(i.index[o]=a)),e},y=function(e,t){var n,r=d(e),o=f(t);if("F"!==o)return r.index[o];for(n=r.first;n;n=n.next)if(n.key==t)return n};return i(u.prototype,{clear:function(){for(var e=d(this),t=e.index,n=e.first;n;)n.removed=!0,n.previous&&(n.previous=n.previous.next=void 0),delete t[n.index],n=n.next;e.first=e.last=void 0,p?e.size=0:this.size=0},delete:function(e){var t=d(this),n=y(this,e);if(n){var r=n.next,o=n.previous;delete t.index[n.index],n.removed=!0,o&&(o.next=r),r&&(r.previous=o),t.first==n&&(t.first=r),t.last==n&&(t.last=o),p?t.size--:this.size--}return!!n},forEach:function(e){for(var t,n=d(this),r=a(e,arguments.length>1?arguments[1]:void 0,3);t=t?t.next:n.first;)for(r(t.value,t.key,this);t&&t.removed;)t=t.previous},has:function(e){return!!y(this,e)}}),i(u.prototype,n?{get:function(e){var t=y(this,e);return t&&t.value},set:function(e,t){return g(this,0===e?0:e,t)}}:{add:function(e){return g(this,e=0===e?0:e,e)}}),p&&r(u.prototype,"size",{get:function(){return d(this).size}}),u},setStrong:function(e,t,n){var r=t+" Iterator",o=m(t),i=m(r);c(e,t,(function(e,t){h(this,{type:r,target:e,state:o(e),kind:t,last:void 0})}),(function(){for(var e=i(this),t=e.kind,n=e.last;n&&n.removed;)n=n.previous;return e.target&&(e.last=n=n?n.next:e.state.first)?"keys"==t?{value:n.key,done:!1}:"values"==t?{value:n.value,done:!1}:{value:[n.key,n.value],done:!1}:(e.target=void 0,{value:void 0,done:!0})}),n?"entries":"values",!n,!0),u(t)}}},function(e,t,n){n(203),n(68),n(206),n(208),n(209),n(210),n(211),n(212),n(213),n(214),n(215),n(216),n(217),n(218),n(219),n(220),n(221),n(222),n(223);var r=n(37);e.exports=r.Symbol},function(e,t,n){"use strict";var r=n(17),o=n(8),i=n(87),a=n(9),s=n(45),l=n(38),c=n(204),u=n(125),p=n(205),f=n(5),d=n(86),h=f("isConcatSpreadable"),m=d>=51||!o((function(){var e=[];return e[h]=!1,e.concat()[0]!==e})),g=p("concat"),y=function(e){if(!a(e))return!1;var t=e[h];return void 0!==t?!!t:i(e)};r({target:"Array",proto:!0,forced:!m||!g},{concat:function(e){var t,n,r,o,i,a=s(this),p=u(a,0),f=0;for(t=-1,r=arguments.length;t<r;t++)if(i=-1===t?a:arguments[t],y(i)){if(f+(o=l(i.length))>9007199254740991)throw TypeError("Maximum allowed index exceeded");for(n=0;n<o;n++,f++)n in i&&c(p,f,i[n])}else{if(f>=9007199254740991)throw TypeError("Maximum allowed index exceeded");c(p,f++,i)}return p.length=f,p}})},function(e,t,n){"use strict";var r=n(54),o=n(16),i=n(42);e.exports=function(e,t,n){var a=r(t);a in e?o.f(e,a,i(0,n)):e[a]=n}},function(e,t,n){var r=n(8),o=n(5),i=n(86),a=o("species");e.exports=function(e){return i>=51||!r((function(){var t=[];return(t.constructor={})[a]=function(){return{foo:1}},1!==t[e](Boolean).foo}))}},function(e,t,n){"use strict";var r=n(17),o=n(4),i=n(29),a=n(23),s=n(18),l=n(73),c=n(103),u=n(8),p=n(11),f=n(87),d=n(9),h=n(21),m=n(45),g=n(36),y=n(54),v=n(42),b=n(57),x=n(83),w=n(79),k=n(207),O=n(81),_=n(35),E=n(16),S=n(77),T=n(24),j=n(25),C=n(70),A=n(56),I=n(43),P=n(55),R=n(5),N=n(129),L=n(12),M=n(30),D=n(34),F=n(124).forEach,z=A("hidden"),U=R("toPrimitive"),B=D.set,$=D.getterFor("Symbol"),q=Object.prototype,W=o.Symbol,H=i("JSON","stringify"),V=_.f,Y=E.f,Q=k.f,G=S.f,X=C("symbols"),K=C("op-symbols"),Z=C("string-to-symbol-registry"),J=C("symbol-to-string-registry"),ee=C("wks"),te=o.QObject,ne=!te||!te.prototype||!te.prototype.findChild,re=s&&u((function(){return 7!=b(Y({},"a",{get:function(){return Y(this,"a",{value:7}).a}})).a}))?function(e,t,n){var r=V(q,t);r&&delete q[t],Y(e,t,n),r&&e!==q&&Y(q,t,r)}:Y,oe=function(e,t){var n=X[e]=b(W.prototype);return B(n,{type:"Symbol",tag:e,description:t}),s||(n.description=t),n},ie=c?function(e){return"symbol"==typeof e}:function(e){return Object(e)instanceof W},ae=function(e,t,n){e===q&&ae(K,t,n),h(e);var r=y(t,!0);return h(n),p(X,r)?(n.enumerable?(p(e,z)&&e[z][r]&&(e[z][r]=!1),n=b(n,{enumerable:v(0,!1)})):(p(e,z)||Y(e,z,v(1,{})),e[z][r]=!0),re(e,r,n)):Y(e,r,n)},se=function(e,t){h(e);var n=g(t),r=x(n).concat(pe(n));return F(r,(function(t){s&&!le.call(n,t)||ae(e,t,n[t])})),e},le=function(e){var t=y(e,!0),n=G.call(this,t);return!(this===q&&p(X,t)&&!p(K,t))&&(!(n||!p(this,t)||!p(X,t)||p(this,z)&&this[z][t])||n)},ce=function(e,t){var n=g(e),r=y(t,!0);if(n!==q||!p(X,r)||p(K,r)){var o=V(n,r);return!o||!p(X,r)||p(n,z)&&n[z][r]||(o.enumerable=!0),o}},ue=function(e){var t=Q(g(e)),n=[];return F(t,(function(e){p(X,e)||p(I,e)||n.push(e)})),n},pe=function(e){var t=e===q,n=Q(t?K:g(e)),r=[];return F(n,(function(e){!p(X,e)||t&&!p(q,e)||r.push(X[e])})),r};(l||(j((W=function(){if(this instanceof W)throw TypeError("Symbol is not a constructor");var e=arguments.length&&void 0!==arguments[0]?String(arguments[0]):void 0,t=P(e),n=function(e){this===q&&n.call(K,e),p(this,z)&&p(this[z],t)&&(this[z][t]=!1),re(this,t,v(1,e))};return s&&ne&&re(q,t,{configurable:!0,set:n}),oe(t,e)}).prototype,"toString",(function(){return $(this).tag})),j(W,"withoutSetter",(function(e){return oe(P(e),e)})),S.f=le,E.f=ae,_.f=ce,w.f=k.f=ue,O.f=pe,N.f=function(e){return oe(R(e),e)},s&&(Y(W.prototype,"description",{configurable:!0,get:function(){return $(this).description}}),a||j(q,"propertyIsEnumerable",le,{unsafe:!0}))),r({global:!0,wrap:!0,forced:!l,sham:!l},{Symbol:W}),F(x(ee),(function(e){L(e)})),r({target:"Symbol",stat:!0,forced:!l},{for:function(e){var t=String(e);if(p(Z,t))return Z[t];var n=W(t);return Z[t]=n,J[n]=t,n},keyFor:function(e){if(!ie(e))throw TypeError(e+" is not a symbol");if(p(J,e))return J[e]},useSetter:function(){ne=!0},useSimple:function(){ne=!1}}),r({target:"Object",stat:!0,forced:!l,sham:!s},{create:function(e,t){return void 0===t?b(e):se(b(e),t)},defineProperty:ae,defineProperties:se,getOwnPropertyDescriptor:ce}),r({target:"Object",stat:!0,forced:!l},{getOwnPropertyNames:ue,getOwnPropertySymbols:pe}),r({target:"Object",stat:!0,forced:u((function(){O.f(1)}))},{getOwnPropertySymbols:function(e){return O.f(m(e))}}),H)&&r({target:"JSON",stat:!0,forced:!l||u((function(){var e=W();return"[null]"!=H([e])||"{}"!=H({a:e})||"{}"!=H(Object(e))}))},{stringify:function(e,t,n){for(var r,o=[e],i=1;arguments.length>i;)o.push(arguments[i++]);if(r=t,(d(t)||void 0!==e)&&!ie(e))return f(t)||(t=function(e,t){if("function"==typeof r&&(t=r.call(this,e,t)),!ie(t))return t}),o[1]=t,H.apply(null,o)}});W.prototype[U]||T(W.prototype,U,W.prototype.valueOf),M(W,"Symbol"),I[z]=!0},function(e,t,n){var r=n(36),o=n(79).f,i={}.toString,a="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[];e.exports.f=function(e){return a&&"[object Window]"==i.call(e)?function(e){try{return o(e)}catch(e){return a.slice()}}(e):o(r(e))}},function(e,t,n){n(12)("asyncIterator")},function(e,t,n){"use strict";var r=n(17),o=n(18),i=n(4),a=n(11),s=n(9),l=n(16).f,c=n(106),u=i.Symbol;if(o&&"function"==typeof u&&(!("description"in u.prototype)||void 0!==u().description)){var p={},f=function(){var e=arguments.length<1||void 0===arguments[0]?void 0:String(arguments[0]),t=this instanceof f?new u(e):void 0===e?u():u(e);return""===e&&(p[t]=!0),t};c(f,u);var d=f.prototype=u.prototype;d.constructor=f;var h=d.toString,m="Symbol(test)"==String(u("test")),g=/^Symbol\((.*)\)[^)]+$/;l(d,"description",{configurable:!0,get:function(){var e=s(this)?this.valueOf():this,t=h.call(e);if(a(p,e))return"";var n=m?t.slice(7,-1):t.replace(g,"$1");return""===n?void 0:n}}),r({global:!0,forced:!0},{Symbol:f})}},function(e,t,n){n(12)("hasInstance")},function(e,t,n){n(12)("isConcatSpreadable")},function(e,t,n){n(12)("iterator")},function(e,t,n){n(12)("match")},function(e,t,n){n(12)("matchAll")},function(e,t,n){n(12)("replace")},function(e,t,n){n(12)("search")},function(e,t,n){n(12)("species")},function(e,t,n){n(12)("split")},function(e,t,n){n(12)("toPrimitive")},function(e,t,n){n(12)("toStringTag")},function(e,t,n){n(12)("unscopables")},function(e,t,n){n(30)(Math,"Math",!0)},function(e,t,n){var r=n(4);n(30)(r.JSON,"JSON",!0)},function(e,t){self.fetch||(self.fetch=function(e,t){return t=t||{},new Promise((function(n,r){var o=new XMLHttpRequest,i=[],a=[],s={},l=function(){return{ok:2==(o.status/100|0),statusText:o.statusText,status:o.status,url:o.responseURL,text:function(){return Promise.resolve(o.responseText)},json:function(){return Promise.resolve(JSON.parse(o.responseText))},blob:function(){return Promise.resolve(new Blob([o.response]))},clone:l,headers:{keys:function(){return i},entries:function(){return a},get:function(e){return s[e.toLowerCase()]},has:function(e){return e.toLowerCase()in s}}}};for(var c in o.open(t.method||"get",e,!0),o.onload=function(){o.getAllResponseHeaders().replace(/^(.*?):[^\S\n]*([\s\S]*?)$/gm,(function(e,t,n){i.push(t=t.toLowerCase()),a.push([t,n]),s[t]=s[t]?s[t]+","+n:n})),n(l())},o.onerror=r,o.withCredentials="include"==t.credentials,t.headers)o.setRequestHeader(c,t.headers[c]);o.send(t.body||null)}))})},function(e,t,n){(function(e){!function(e){var t=function(){try{return!!Symbol.iterator}catch(e){return!1}}(),n=function(e){var n={next:function(){var t=e.shift();return{done:void 0===t,value:t}}};return t&&(n[Symbol.iterator]=function(){return n}),n},r=function(e){return encodeURIComponent(e).replace(/%20/g,"+")},o=function(e){return decodeURIComponent(String(e).replace(/\+/g," "))};(function(){try{var t=e.URLSearchParams;return"a=1"===new t("?a=1").toString()&&"function"==typeof t.prototype.set}catch(e){return!1}})()||function(){var o=function(e){Object.defineProperty(this,"_entries",{writable:!0,value:{}});var t=typeof e;if("undefined"===t);else if("string"===t)""!==e&&this._fromString(e);else if(e instanceof o){var n=this;e.forEach((function(e,t){n.append(t,e)}))}else{if(null===e||"object"!==t)throw new TypeError("Unsupported input's type for URLSearchParams");if("[object Array]"===Object.prototype.toString.call(e))for(var r=0;r<e.length;r++){var i=e[r];if("[object Array]"!==Object.prototype.toString.call(i)&&2===i.length)throw new TypeError("Expected [string, any] as entry at index "+r+" of URLSearchParams's input");this.append(i[0],i[1])}else for(var a in e)e.hasOwnProperty(a)&&this.append(a,e[a])}},i=o.prototype;i.append=function(e,t){e in this._entries?this._entries[e].push(String(t)):this._entries[e]=[String(t)]},i.delete=function(e){delete this._entries[e]},i.get=function(e){return e in this._entries?this._entries[e][0]:null},i.getAll=function(e){return e in this._entries?this._entries[e].slice(0):[]},i.has=function(e){return e in this._entries},i.set=function(e,t){this._entries[e]=[String(t)]},i.forEach=function(e,t){var n;for(var r in this._entries)if(this._entries.hasOwnProperty(r)){n=this._entries[r];for(var o=0;o<n.length;o++)e.call(t,n[o],r,this)}},i.keys=function(){var e=[];return this.forEach((function(t,n){e.push(n)})),n(e)},i.values=function(){var e=[];return this.forEach((function(t){e.push(t)})),n(e)},i.entries=function(){var e=[];return this.forEach((function(t,n){e.push([n,t])})),n(e)},t&&(i[Symbol.iterator]=i.entries),i.toString=function(){var e=[];return this.forEach((function(t,n){e.push(r(n)+"="+r(t))})),e.join("&")},e.URLSearchParams=o}();var i=e.URLSearchParams.prototype;"function"!=typeof i.sort&&(i.sort=function(){var e=this,t=[];this.forEach((function(n,r){t.push([r,n]),e._entries||e.delete(r)})),t.sort((function(e,t){return e[0]<t[0]?-1:e[0]>t[0]?1:0})),e._entries&&(e._entries={});for(var n=0;n<t.length;n++)this.append(t[n][0],t[n][1])}),"function"!=typeof i._fromString&&Object.defineProperty(i,"_fromString",{enumerable:!1,configurable:!1,writable:!1,value:function(e){if(this._entries)this._entries={};else{var t=[];this.forEach((function(e,n){t.push(n)}));for(var n=0;n<t.length;n++)this.delete(t[n])}var r,i=(e=e.replace(/^\?/,"")).split("&");for(n=0;n<i.length;n++)r=i[n].split("="),this.append(o(r[0]),r.length>1?o(r[1]):"")}})}(void 0!==e?e:"undefined"!=typeof window?window:"undefined"!=typeof self?self:this),function(e){if(function(){try{var t=new e.URL("b","http://a");return t.pathname="c d","http://a/c%20d"===t.href&&t.searchParams}catch(e){return!1}}()||function(){var t=e.URL,n=function(t,n){"string"!=typeof t&&(t=String(t));var r,o=document;if(n&&(void 0===e.location||n!==e.location.href)){(r=(o=document.implementation.createHTMLDocument("")).createElement("base")).href=n,o.head.appendChild(r);try{if(0!==r.href.indexOf(n))throw new Error(r.href)}catch(e){throw new Error("URL unable to set base "+n+" due to "+e)}}var i=o.createElement("a");if(i.href=t,r&&(o.body.appendChild(i),i.href=i.href),":"===i.protocol||!/:/.test(i.href))throw new TypeError("Invalid URL");Object.defineProperty(this,"_anchorElement",{value:i});var a=new e.URLSearchParams(this.search),s=!0,l=!0,c=this;["append","delete","set"].forEach((function(e){var t=a[e];a[e]=function(){t.apply(a,arguments),s&&(l=!1,c.search=a.toString(),l=!0)}})),Object.defineProperty(this,"searchParams",{value:a,enumerable:!0});var u=void 0;Object.defineProperty(this,"_updateSearchParams",{enumerable:!1,configurable:!1,writable:!1,value:function(){this.search!==u&&(u=this.search,l&&(s=!1,this.searchParams._fromString(this.search),s=!0))}})},r=n.prototype;["hash","host","hostname","port","protocol"].forEach((function(e){!function(e){Object.defineProperty(r,e,{get:function(){return this._anchorElement[e]},set:function(t){this._anchorElement[e]=t},enumerable:!0})}(e)})),Object.defineProperty(r,"search",{get:function(){return this._anchorElement.search},set:function(e){this._anchorElement.search=e,this._updateSearchParams()},enumerable:!0}),Object.defineProperties(r,{toString:{get:function(){var e=this;return function(){return e.href}}},href:{get:function(){return this._anchorElement.href.replace(/\?$/,"")},set:function(e){this._anchorElement.href=e,this._updateSearchParams()},enumerable:!0},pathname:{get:function(){return this._anchorElement.pathname.replace(/(^\/?)/,"/")},set:function(e){this._anchorElement.pathname=e},enumerable:!0},origin:{get:function(){var e={"http:":80,"https:":443,"ftp:":21}[this._anchorElement.protocol],t=this._anchorElement.port!=e&&""!==this._anchorElement.port;return this._anchorElement.protocol+"//"+this._anchorElement.hostname+(t?":"+this._anchorElement.port:"")},enumerable:!0},password:{get:function(){return""},set:function(e){},enumerable:!0},username:{get:function(){return""},set:function(e){},enumerable:!0}}),n.createObjectURL=function(e){return t.createObjectURL.apply(t,arguments)},n.revokeObjectURL=function(e){return t.revokeObjectURL.apply(t,arguments)},e.URL=n}(),void 0!==e.location&&!("origin"in e.location)){var t=function(){return e.location.protocol+"//"+e.location.hostname+(e.location.port?":"+e.location.port:"")};try{Object.defineProperty(e.location,"origin",{get:t,enumerable:!0})}catch(n){setInterval((function(){e.location.origin=t()}),100)}}}(void 0!==e?e:"undefined"!=typeof window?window:"undefined"!=typeof self?self:this)}).call(this,n(7))},function(e,t,n){"use strict"; +function s(){for(var e=0,t=0,n=arguments.length;t<n;t++)e+=arguments[t].length;var r=Array(e),o=0;for(t=0;t<n;t++)for(var i=arguments[t],a=0,s=i.length;a<s;a++,o++)r[o]=i[a];return r}function l(e,t,n,r){var o=r.propertyIsEnumerable(t)?"enumerable":"nonenumerable";"enumerable"===o&&(e[t]=n),"nonenumerable"===o&&Object.defineProperty(e,t,{value:n,enumerable:!1,writable:!0,configurable:!0})}function c(e,t,n){if(!o(t))return n&&i(n)&&n.forEach((function(n){t=n(e,t)})),t;var r={};o(e)&&(r=s(Object.getOwnPropertyNames(e),Object.getOwnPropertySymbols(e)).reduce((function(n,r){var o=e[r];return(!a(r)&&!Object.getOwnPropertyNames(t).includes(r)||a(r)&&!Object.getOwnPropertySymbols(t).includes(r))&&l(n,r,o,e),n}),{}));return s(Object.getOwnPropertyNames(t),Object.getOwnPropertySymbols(t)).reduce((function(r,a){var s=t[a],u=o(e)?e[a]:void 0;return n&&i(n)&&n.forEach((function(e){s=e(u,s)})),void 0!==u&&o(s)&&(s=c(u,s,n)),l(r,a,s,t),r}),r)}t.a=function(e){for(var t=[],n=1;n<arguments.length;n++)t[n-1]=arguments[n];var r=null,i=e;return o(e)&&e.extensions&&1===Object.keys(e).length&&(i={},r=e.extensions),t.reduce((function(e,t){return c(e,t,r)}),i)}},function(e,t,n){"use strict";var r=/^((children|dangerouslySetInnerHTML|key|ref|autoFocus|defaultValue|defaultChecked|innerHTML|suppressContentEditableWarning|suppressHydrationWarning|valueLink|accept|acceptCharset|accessKey|action|allow|allowUserMedia|allowPaymentRequest|allowFullScreen|allowTransparency|alt|async|autoComplete|autoPlay|capture|cellPadding|cellSpacing|challenge|charSet|checked|cite|classID|className|cols|colSpan|content|contentEditable|contextMenu|controls|controlsList|coords|crossOrigin|data|dateTime|decoding|default|defer|dir|disabled|disablePictureInPicture|download|draggable|encType|form|formAction|formEncType|formMethod|formNoValidate|formTarget|frameBorder|headers|height|hidden|high|href|hrefLang|htmlFor|httpEquiv|id|inputMode|integrity|is|keyParams|keyType|kind|label|lang|list|loading|loop|low|marginHeight|marginWidth|max|maxLength|media|mediaGroup|method|min|minLength|multiple|muted|name|nonce|noValidate|open|optimum|pattern|placeholder|playsInline|poster|preload|profile|radioGroup|readOnly|referrerPolicy|rel|required|reversed|role|rows|rowSpan|sandbox|scope|scoped|scrolling|seamless|selected|shape|size|sizes|slot|span|spellCheck|src|srcDoc|srcLang|srcSet|start|step|style|summary|tabIndex|target|title|type|useMap|value|width|wmode|wrap|about|datatype|inlist|prefix|property|resource|typeof|vocab|autoCapitalize|autoCorrect|autoSave|color|inert|itemProp|itemScope|itemType|itemID|itemRef|on|results|security|unselectable|accentHeight|accumulate|additive|alignmentBaseline|allowReorder|alphabetic|amplitude|arabicForm|ascent|attributeName|attributeType|autoReverse|azimuth|baseFrequency|baselineShift|baseProfile|bbox|begin|bias|by|calcMode|capHeight|clip|clipPathUnits|clipPath|clipRule|colorInterpolation|colorInterpolationFilters|colorProfile|colorRendering|contentScriptType|contentStyleType|cursor|cx|cy|d|decelerate|descent|diffuseConstant|direction|display|divisor|dominantBaseline|dur|dx|dy|edgeMode|elevation|enableBackground|end|exponent|externalResourcesRequired|fill|fillOpacity|fillRule|filter|filterRes|filterUnits|floodColor|floodOpacity|focusable|fontFamily|fontSize|fontSizeAdjust|fontStretch|fontStyle|fontVariant|fontWeight|format|from|fr|fx|fy|g1|g2|glyphName|glyphOrientationHorizontal|glyphOrientationVertical|glyphRef|gradientTransform|gradientUnits|hanging|horizAdvX|horizOriginX|ideographic|imageRendering|in|in2|intercept|k|k1|k2|k3|k4|kernelMatrix|kernelUnitLength|kerning|keyPoints|keySplines|keyTimes|lengthAdjust|letterSpacing|lightingColor|limitingConeAngle|local|markerEnd|markerMid|markerStart|markerHeight|markerUnits|markerWidth|mask|maskContentUnits|maskUnits|mathematical|mode|numOctaves|offset|opacity|operator|order|orient|orientation|origin|overflow|overlinePosition|overlineThickness|panose1|paintOrder|pathLength|patternContentUnits|patternTransform|patternUnits|pointerEvents|points|pointsAtX|pointsAtY|pointsAtZ|preserveAlpha|preserveAspectRatio|primitiveUnits|r|radius|refX|refY|renderingIntent|repeatCount|repeatDur|requiredExtensions|requiredFeatures|restart|result|rotate|rx|ry|scale|seed|shapeRendering|slope|spacing|specularConstant|specularExponent|speed|spreadMethod|startOffset|stdDeviation|stemh|stemv|stitchTiles|stopColor|stopOpacity|strikethroughPosition|strikethroughThickness|string|stroke|strokeDasharray|strokeDashoffset|strokeLinecap|strokeLinejoin|strokeMiterlimit|strokeOpacity|strokeWidth|surfaceScale|systemLanguage|tableValues|targetX|targetY|textAnchor|textDecoration|textRendering|textLength|to|transform|u1|u2|underlinePosition|underlineThickness|unicode|unicodeBidi|unicodeRange|unitsPerEm|vAlphabetic|vHanging|vIdeographic|vMathematical|values|vectorEffect|version|vertAdvY|vertOriginX|vertOriginY|viewBox|viewTarget|visibility|widths|wordSpacing|writingMode|x|xHeight|x1|x2|xChannelSelector|xlinkActuate|xlinkArcrole|xlinkHref|xlinkRole|xlinkShow|xlinkTitle|xlinkType|xmlBase|xmlns|xmlnsXlink|xmlLang|xmlSpace|y|y1|y2|yChannelSelector|z|zoomAndPan|for|class|autofocus)|(([Dd][Aa][Tt][Aa]|[Aa][Rr][Ii][Aa]|x)-.*))$/,o=function(e){var t={};return function(n){return void 0===t[n]&&(t[n]=e(n)),t[n]}}((function(e){return r.test(e)||111===e.charCodeAt(0)&&110===e.charCodeAt(1)&&e.charCodeAt(2)<91}));t.a=o},function(e,t,n){n(163),e.exports=n(319)},function(e,t,n){"use strict";n.r(t);n(164),n(185),n(188),n(191),n(194),n(196),n(202),n(224),n(225)},function(e,t,n){n(68),n(105),n(112),n(177),n(183),n(184);var r=n(37);e.exports=r.Promise},function(e,t,n){var r=n(4),o=n(74),i=r.WeakMap;e.exports="function"==typeof i&&/native code/.test(o(i))},function(e,t,n){"use strict";var r=n(69),o=n(104);e.exports=r?{}.toString:function(){return"[object "+o(this)+"]"}},function(e,t,n){var r=n(75),o=n(44),i=function(e){return function(t,n){var i,a,s=String(o(t)),l=r(n),c=s.length;return l<0||l>=c?e?"":void 0:(i=s.charCodeAt(l))<55296||i>56319||l+1===c||(a=s.charCodeAt(l+1))<56320||a>57343?e?s.charAt(l):i:e?s.slice(l,l+2):a-56320+(i-55296<<10)+65536}};e.exports={codeAt:i(!1),charAt:i(!0)}},function(e,t,n){var r=n(29),o=n(79),i=n(81),a=n(21);e.exports=r("Reflect","ownKeys")||function(e){var t=o.f(a(e)),n=i.f;return n?t.concat(n(e)):t}},function(e,t,n){var r=n(36),o=n(38),i=n(170),a=function(e){return function(t,n,a){var s,l=r(t),c=o(l.length),u=i(a,c);if(e&&n!=n){for(;c>u;)if((s=l[u++])!=s)return!0}else for(;c>u;u++)if((e||u in l)&&l[u]===n)return e||u||0;return!e&&-1}};e.exports={includes:a(!0),indexOf:a(!1)}},function(e,t,n){var r=n(75),o=Math.max,i=Math.min;e.exports=function(e,t){var n=r(e);return n<0?o(n+t,0):i(n,t)}},function(e,t,n){"use strict";var r=n(108).IteratorPrototype,o=n(57),i=n(42),a=n(30),s=n(46),l=function(){return this};e.exports=function(e,t,n){var c=t+" Iterator";return e.prototype=o(r,{next:i(1,n)}),a(e,c,!1,!0),s[c]=l,e}},function(e,t,n){var r=n(8);e.exports=!r((function(){function e(){}return e.prototype.constructor=null,Object.getPrototypeOf(new e)!==e.prototype}))},function(e,t,n){var r=n(18),o=n(16),i=n(21),a=n(83);e.exports=r?Object.defineProperties:function(e,t){i(e);for(var n,r=a(t),s=r.length,l=0;s>l;)o.f(e,n=r[l++],t[n]);return e}},function(e,t,n){var r=n(9);e.exports=function(e){if(!r(e)&&null!==e)throw TypeError("Can't set "+String(e)+" as a prototype");return e}},function(e,t){e.exports={CSSRuleList:0,CSSStyleDeclaration:0,CSSValueList:0,ClientRectList:0,DOMRectList:0,DOMStringList:0,DOMTokenList:1,DataTransferItemList:0,FileList:0,HTMLAllCollection:0,HTMLCollection:0,HTMLFormElement:0,HTMLSelectElement:0,MediaList:0,MimeTypeArray:0,NamedNodeMap:0,NodeList:1,PaintRequestList:0,Plugin:0,PluginArray:0,SVGLengthList:0,SVGNumberList:0,SVGPathSegList:0,SVGPointList:0,SVGStringList:0,SVGTransformList:0,SourceBufferList:0,StyleSheetList:0,TextTrackCueList:0,TextTrackList:0,TouchList:0}},function(e,t,n){"use strict";var r=n(36),o=n(113),i=n(46),a=n(34),s=n(76),l=a.set,c=a.getterFor("Array Iterator");e.exports=s(Array,"Array",(function(e,t){l(this,{type:"Array Iterator",target:r(e),index:0,kind:t})}),(function(){var e=c(this),t=e.target,n=e.kind,r=e.index++;return!t||r>=t.length?(e.target=void 0,{value:void 0,done:!0}):"keys"==n?{value:r,done:!1}:"values"==n?{value:t[r],done:!1}:{value:[r,t[r]],done:!1}}),"values"),i.Arguments=i.Array,o("keys"),o("values"),o("entries")},function(e,t,n){"use strict";var r,o,i,a,s=n(17),l=n(23),c=n(4),u=n(29),p=n(114),f=n(25),d=n(115),h=n(30),m=n(116),g=n(9),y=n(47),v=n(84),b=n(28),x=n(74),w=n(58),k=n(117),O=n(118),_=n(119).set,E=n(181),S=n(122),T=n(182),j=n(85),C=n(123),I=n(34),A=n(82),P=n(5),R=n(86),N=P("species"),L="Promise",M=I.get,D=I.set,F=I.getterFor(L),z=p,U=c.TypeError,B=c.document,$=c.process,q=u("fetch"),W=j.f,H=W,V="process"==b($),Y=!!(B&&B.createEvent&&c.dispatchEvent),Q=A(L,(function(){if(!(x(z)!==String(z))){if(66===R)return!0;if(!V&&"function"!=typeof PromiseRejectionEvent)return!0}if(l&&!z.prototype.finally)return!0;if(R>=51&&/native code/.test(z))return!1;var e=z.resolve(1),t=function(e){e((function(){}),(function(){}))};return(e.constructor={})[N]=t,!(e.then((function(){}))instanceof t)})),G=Q||!k((function(e){z.all(e).catch((function(){}))})),X=function(e){var t;return!(!g(e)||"function"!=typeof(t=e.then))&&t},K=function(e,t,n){if(!t.notified){t.notified=!0;var r=t.reactions;E((function(){for(var o=t.value,i=1==t.state,a=0;r.length>a;){var s,l,c,u=r[a++],p=i?u.ok:u.fail,f=u.resolve,d=u.reject,h=u.domain;try{p?(i||(2===t.rejection&&te(e,t),t.rejection=1),!0===p?s=o:(h&&h.enter(),s=p(o),h&&(h.exit(),c=!0)),s===u.promise?d(U("Promise-chain cycle")):(l=X(s))?l.call(s,f,d):f(s)):d(o)}catch(e){h&&!c&&h.exit(),d(e)}}t.reactions=[],t.notified=!1,n&&!t.rejection&&J(e,t)}))}},Z=function(e,t,n){var r,o;Y?((r=B.createEvent("Event")).promise=t,r.reason=n,r.initEvent(e,!1,!0),c.dispatchEvent(r)):r={promise:t,reason:n},(o=c["on"+e])?o(r):"unhandledrejection"===e&&T("Unhandled promise rejection",n)},J=function(e,t){_.call(c,(function(){var n,r=t.value;if(ee(t)&&(n=C((function(){V?$.emit("unhandledRejection",r,e):Z("unhandledrejection",e,r)})),t.rejection=V||ee(t)?2:1,n.error))throw n.value}))},ee=function(e){return 1!==e.rejection&&!e.parent},te=function(e,t){_.call(c,(function(){V?$.emit("rejectionHandled",e):Z("rejectionhandled",e,t.value)}))},ne=function(e,t,n,r){return function(o){e(t,n,o,r)}},re=function(e,t,n,r){t.done||(t.done=!0,r&&(t=r),t.value=n,t.state=2,K(e,t,!0))},oe=function(e,t,n,r){if(!t.done){t.done=!0,r&&(t=r);try{if(e===n)throw U("Promise can't be resolved itself");var o=X(n);o?E((function(){var r={done:!1};try{o.call(n,ne(oe,e,r,t),ne(re,e,r,t))}catch(n){re(e,r,n,t)}})):(t.value=n,t.state=1,K(e,t,!1))}catch(n){re(e,{done:!1},n,t)}}};Q&&(z=function(e){v(this,z,L),y(e),r.call(this);var t=M(this);try{e(ne(oe,this,t),ne(re,this,t))}catch(e){re(this,t,e)}},(r=function(e){D(this,{type:L,done:!1,notified:!1,parent:!1,reactions:[],rejection:!1,state:0,value:void 0})}).prototype=d(z.prototype,{then:function(e,t){var n=F(this),r=W(O(this,z));return r.ok="function"!=typeof e||e,r.fail="function"==typeof t&&t,r.domain=V?$.domain:void 0,n.parent=!0,n.reactions.push(r),0!=n.state&&K(this,n,!1),r.promise},catch:function(e){return this.then(void 0,e)}}),o=function(){var e=new r,t=M(e);this.promise=e,this.resolve=ne(oe,e,t),this.reject=ne(re,e,t)},j.f=W=function(e){return e===z||e===i?new o(e):H(e)},l||"function"!=typeof p||(a=p.prototype.then,f(p.prototype,"then",(function(e,t){var n=this;return new z((function(e,t){a.call(n,e,t)})).then(e,t)}),{unsafe:!0}),"function"==typeof q&&s({global:!0,enumerable:!0,forced:!0},{fetch:function(e){return S(z,q.apply(c,arguments))}}))),s({global:!0,wrap:!0,forced:Q},{Promise:z}),h(z,L,!1,!0),m(L),i=u(L),s({target:L,stat:!0,forced:Q},{reject:function(e){var t=W(this);return t.reject.call(void 0,e),t.promise}}),s({target:L,stat:!0,forced:l||Q},{resolve:function(e){return S(l&&this===i?z:this,e)}}),s({target:L,stat:!0,forced:G},{all:function(e){var t=this,n=W(t),r=n.resolve,o=n.reject,i=C((function(){var n=y(t.resolve),i=[],a=0,s=1;w(e,(function(e){var l=a++,c=!1;i.push(void 0),s++,n.call(t,e).then((function(e){c||(c=!0,i[l]=e,--s||r(i))}),o)})),--s||r(i)}));return i.error&&o(i.value),n.promise},race:function(e){var t=this,n=W(t),r=n.reject,o=C((function(){var o=y(t.resolve);w(e,(function(e){o.call(t,e).then(n.resolve,r)}))}));return o.error&&r(o.value),n.promise}})},function(e,t,n){var r=n(5),o=n(46),i=r("iterator"),a=Array.prototype;e.exports=function(e){return void 0!==e&&(o.Array===e||a[i]===e)}},function(e,t,n){var r=n(104),o=n(46),i=n(5)("iterator");e.exports=function(e){if(null!=e)return e[i]||e["@@iterator"]||o[r(e)]}},function(e,t,n){var r=n(21);e.exports=function(e,t,n,o){try{return o?t(r(n)[0],n[1]):t(n)}catch(t){var i=e.return;throw void 0!==i&&r(i.call(e)),t}}},function(e,t,n){var r,o,i,a,s,l,c,u,p=n(4),f=n(35).f,d=n(28),h=n(119).set,m=n(120),g=p.MutationObserver||p.WebKitMutationObserver,y=p.process,v=p.Promise,b="process"==d(y),x=f(p,"queueMicrotask"),w=x&&x.value;w||(r=function(){var e,t;for(b&&(e=y.domain)&&e.exit();o;){t=o.fn,o=o.next;try{t()}catch(e){throw o?a():i=void 0,e}}i=void 0,e&&e.enter()},b?a=function(){y.nextTick(r)}:g&&!m?(s=!0,l=document.createTextNode(""),new g(r).observe(l,{characterData:!0}),a=function(){l.data=s=!s}):v&&v.resolve?(c=v.resolve(void 0),u=c.then,a=function(){u.call(c,r)}):a=function(){h.call(p,r)}),e.exports=w||function(e){var t={fn:e,next:void 0};i&&(i.next=t),o||(o=t,a()),i=t}},function(e,t,n){var r=n(4);e.exports=function(e,t){var n=r.console;n&&n.error&&(1===arguments.length?n.error(e):n.error(e,t))}},function(e,t,n){"use strict";var r=n(17),o=n(47),i=n(85),a=n(123),s=n(58);r({target:"Promise",stat:!0},{allSettled:function(e){var t=this,n=i.f(t),r=n.resolve,l=n.reject,c=a((function(){var n=o(t.resolve),i=[],a=0,l=1;s(e,(function(e){var o=a++,s=!1;i.push(void 0),l++,n.call(t,e).then((function(e){s||(s=!0,i[o]={status:"fulfilled",value:e},--l||r(i))}),(function(e){s||(s=!0,i[o]={status:"rejected",reason:e},--l||r(i))}))})),--l||r(i)}));return c.error&&l(c.value),n.promise}})},function(e,t,n){"use strict";var r=n(17),o=n(23),i=n(114),a=n(8),s=n(29),l=n(118),c=n(122),u=n(25);r({target:"Promise",proto:!0,real:!0,forced:!!i&&a((function(){i.prototype.finally.call({then:function(){}},(function(){}))}))},{finally:function(e){var t=l(this,s("Promise")),n="function"==typeof e;return this.then(n?function(n){return c(t,e()).then((function(){return n}))}:e,n?function(n){return c(t,e()).then((function(){throw n}))}:e)}}),o||"function"!=typeof i||i.prototype.finally||u(i.prototype,"finally",s("Promise").prototype.finally)},function(e,t,n){n(186);var r=n(88);e.exports=r("Array","find")},function(e,t,n){"use strict";var r=n(17),o=n(124).find,i=n(113),a=n(187),s=!0,l=a("find");"find"in[]&&Array(1).find((function(){s=!1})),r({target:"Array",proto:!0,forced:s||!l},{find:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}}),i("find")},function(e,t,n){var r=n(18),o=n(8),i=n(11),a=Object.defineProperty,s={},l=function(e){throw e};e.exports=function(e,t){if(i(s,e))return s[e];t||(t={});var n=[][e],c=!!i(t,"ACCESSORS")&&t.ACCESSORS,u=i(t,0)?t[0]:l,p=i(t,1)?t[1]:void 0;return s[e]=!!n&&!o((function(){if(c&&!r)return!0;var e={length:-1};c?a(e,1,{enumerable:!0,get:l}):e[1]=1,n.call(e,u,p)}))}},function(e,t,n){n(189);var r=n(37);e.exports=r.Object.assign},function(e,t,n){var r=n(17),o=n(190);r({target:"Object",stat:!0,forced:Object.assign!==o},{assign:o})},function(e,t,n){"use strict";var r=n(18),o=n(8),i=n(83),a=n(81),s=n(77),l=n(45),c=n(78),u=Object.assign,p=Object.defineProperty;e.exports=!u||o((function(){if(r&&1!==u({b:1},u(p({},"a",{enumerable:!0,get:function(){p(this,"b",{value:3,enumerable:!1})}}),{b:2})).b)return!0;var e={},t={},n=Symbol();return e[n]=7,"abcdefghijklmnopqrst".split("").forEach((function(e){t[e]=e})),7!=u({},e)[n]||"abcdefghijklmnopqrst"!=i(u({},t)).join("")}))?function(e,t){for(var n=l(e),o=arguments.length,u=1,p=a.f,f=s.f;o>u;)for(var d,h=c(arguments[u++]),m=p?i(h).concat(p(h)):i(h),g=m.length,y=0;g>y;)d=m[y++],r&&!f.call(h,d)||(n[d]=h[d]);return n}:u},function(e,t,n){n(192);var r=n(88);e.exports=r("String","endsWith")},function(e,t,n){"use strict";var r,o=n(17),i=n(35).f,a=n(38),s=n(126),l=n(44),c=n(127),u=n(23),p="".endsWith,f=Math.min,d=c("endsWith");o({target:"String",proto:!0,forced:!!(u||d||(r=i(String.prototype,"endsWith"),!r||r.writable))&&!d},{endsWith:function(e){var t=String(l(this));s(e);var n=arguments.length>1?arguments[1]:void 0,r=a(t.length),o=void 0===n?r:f(a(n),r),i=String(e);return p?p.call(t,i,o):t.slice(o-i.length,o)===i}})},function(e,t,n){var r=n(9),o=n(28),i=n(5)("match");e.exports=function(e){var t;return r(e)&&(void 0!==(t=e[i])?!!t:"RegExp"==o(e))}},function(e,t,n){n(195);var r=n(88);e.exports=r("String","startsWith")},function(e,t,n){"use strict";var r,o=n(17),i=n(35).f,a=n(38),s=n(126),l=n(44),c=n(127),u=n(23),p="".startsWith,f=Math.min,d=c("startsWith");o({target:"String",proto:!0,forced:!!(u||d||(r=i(String.prototype,"startsWith"),!r||r.writable))&&!d},{startsWith:function(e){var t=String(l(this));s(e);var n=a(f(arguments.length>1?arguments[1]:void 0,t.length)),r=String(e);return p?p.call(t,r,n):t.slice(n,n+r.length)===r}})},function(e,t,n){n(197),n(68),n(105),n(112);var r=n(37);e.exports=r.Map},function(e,t,n){"use strict";var r=n(198),o=n(201);e.exports=r("Map",(function(e){return function(){return e(this,arguments.length?arguments[0]:void 0)}}),o)},function(e,t,n){"use strict";var r=n(17),o=n(4),i=n(82),a=n(25),s=n(128),l=n(58),c=n(84),u=n(9),p=n(8),f=n(117),d=n(30),h=n(200);e.exports=function(e,t,n){var m=-1!==e.indexOf("Map"),g=-1!==e.indexOf("Weak"),y=m?"set":"add",v=o[e],b=v&&v.prototype,x=v,w={},k=function(e){var t=b[e];a(b,e,"add"==e?function(e){return t.call(this,0===e?0:e),this}:"delete"==e?function(e){return!(g&&!u(e))&&t.call(this,0===e?0:e)}:"get"==e?function(e){return g&&!u(e)?void 0:t.call(this,0===e?0:e)}:"has"==e?function(e){return!(g&&!u(e))&&t.call(this,0===e?0:e)}:function(e,n){return t.call(this,0===e?0:e,n),this})};if(i(e,"function"!=typeof v||!(g||b.forEach&&!p((function(){(new v).entries().next()})))))x=n.getConstructor(t,e,m,y),s.REQUIRED=!0;else if(i(e,!0)){var O=new x,_=O[y](g?{}:-0,1)!=O,E=p((function(){O.has(1)})),S=f((function(e){new v(e)})),T=!g&&p((function(){for(var e=new v,t=5;t--;)e[y](t,t);return!e.has(-0)}));S||((x=t((function(t,n){c(t,x,e);var r=h(new v,t,x);return null!=n&&l(n,r[y],r,m),r}))).prototype=b,b.constructor=x),(E||T)&&(k("delete"),k("has"),m&&k("get")),(T||_)&&k(y),g&&b.clear&&delete b.clear}return w[e]=x,r({global:!0,forced:x!=v},w),d(x,e),g||n.setStrong(x,e,m),x}},function(e,t,n){var r=n(8);e.exports=!r((function(){return Object.isExtensible(Object.preventExtensions({}))}))},function(e,t,n){var r=n(9),o=n(111);e.exports=function(e,t,n){var i,a;return o&&"function"==typeof(i=t.constructor)&&i!==n&&r(a=i.prototype)&&a!==n.prototype&&o(e,a),e}},function(e,t,n){"use strict";var r=n(16).f,o=n(57),i=n(115),a=n(48),s=n(84),l=n(58),c=n(76),u=n(116),p=n(18),f=n(128).fastKey,d=n(34),h=d.set,m=d.getterFor;e.exports={getConstructor:function(e,t,n,c){var u=e((function(e,r){s(e,u,t),h(e,{type:t,index:o(null),first:void 0,last:void 0,size:0}),p||(e.size=0),null!=r&&l(r,e[c],e,n)})),d=m(t),g=function(e,t,n){var r,o,i=d(e),a=y(e,t);return a?a.value=n:(i.last=a={index:o=f(t,!0),key:t,value:n,previous:r=i.last,next:void 0,removed:!1},i.first||(i.first=a),r&&(r.next=a),p?i.size++:e.size++,"F"!==o&&(i.index[o]=a)),e},y=function(e,t){var n,r=d(e),o=f(t);if("F"!==o)return r.index[o];for(n=r.first;n;n=n.next)if(n.key==t)return n};return i(u.prototype,{clear:function(){for(var e=d(this),t=e.index,n=e.first;n;)n.removed=!0,n.previous&&(n.previous=n.previous.next=void 0),delete t[n.index],n=n.next;e.first=e.last=void 0,p?e.size=0:this.size=0},delete:function(e){var t=d(this),n=y(this,e);if(n){var r=n.next,o=n.previous;delete t.index[n.index],n.removed=!0,o&&(o.next=r),r&&(r.previous=o),t.first==n&&(t.first=r),t.last==n&&(t.last=o),p?t.size--:this.size--}return!!n},forEach:function(e){for(var t,n=d(this),r=a(e,arguments.length>1?arguments[1]:void 0,3);t=t?t.next:n.first;)for(r(t.value,t.key,this);t&&t.removed;)t=t.previous},has:function(e){return!!y(this,e)}}),i(u.prototype,n?{get:function(e){var t=y(this,e);return t&&t.value},set:function(e,t){return g(this,0===e?0:e,t)}}:{add:function(e){return g(this,e=0===e?0:e,e)}}),p&&r(u.prototype,"size",{get:function(){return d(this).size}}),u},setStrong:function(e,t,n){var r=t+" Iterator",o=m(t),i=m(r);c(e,t,(function(e,t){h(this,{type:r,target:e,state:o(e),kind:t,last:void 0})}),(function(){for(var e=i(this),t=e.kind,n=e.last;n&&n.removed;)n=n.previous;return e.target&&(e.last=n=n?n.next:e.state.first)?"keys"==t?{value:n.key,done:!1}:"values"==t?{value:n.value,done:!1}:{value:[n.key,n.value],done:!1}:(e.target=void 0,{value:void 0,done:!0})}),n?"entries":"values",!n,!0),u(t)}}},function(e,t,n){n(203),n(68),n(206),n(208),n(209),n(210),n(211),n(212),n(213),n(214),n(215),n(216),n(217),n(218),n(219),n(220),n(221),n(222),n(223);var r=n(37);e.exports=r.Symbol},function(e,t,n){"use strict";var r=n(17),o=n(8),i=n(87),a=n(9),s=n(45),l=n(38),c=n(204),u=n(125),p=n(205),f=n(5),d=n(86),h=f("isConcatSpreadable"),m=d>=51||!o((function(){var e=[];return e[h]=!1,e.concat()[0]!==e})),g=p("concat"),y=function(e){if(!a(e))return!1;var t=e[h];return void 0!==t?!!t:i(e)};r({target:"Array",proto:!0,forced:!m||!g},{concat:function(e){var t,n,r,o,i,a=s(this),p=u(a,0),f=0;for(t=-1,r=arguments.length;t<r;t++)if(i=-1===t?a:arguments[t],y(i)){if(f+(o=l(i.length))>9007199254740991)throw TypeError("Maximum allowed index exceeded");for(n=0;n<o;n++,f++)n in i&&c(p,f,i[n])}else{if(f>=9007199254740991)throw TypeError("Maximum allowed index exceeded");c(p,f++,i)}return p.length=f,p}})},function(e,t,n){"use strict";var r=n(54),o=n(16),i=n(42);e.exports=function(e,t,n){var a=r(t);a in e?o.f(e,a,i(0,n)):e[a]=n}},function(e,t,n){var r=n(8),o=n(5),i=n(86),a=o("species");e.exports=function(e){return i>=51||!r((function(){var t=[];return(t.constructor={})[a]=function(){return{foo:1}},1!==t[e](Boolean).foo}))}},function(e,t,n){"use strict";var r=n(17),o=n(4),i=n(29),a=n(23),s=n(18),l=n(73),c=n(103),u=n(8),p=n(11),f=n(87),d=n(9),h=n(21),m=n(45),g=n(36),y=n(54),v=n(42),b=n(57),x=n(83),w=n(79),k=n(207),O=n(81),_=n(35),E=n(16),S=n(77),T=n(24),j=n(25),C=n(70),I=n(56),A=n(43),P=n(55),R=n(5),N=n(129),L=n(12),M=n(30),D=n(34),F=n(124).forEach,z=I("hidden"),U=R("toPrimitive"),B=D.set,$=D.getterFor("Symbol"),q=Object.prototype,W=o.Symbol,H=i("JSON","stringify"),V=_.f,Y=E.f,Q=k.f,G=S.f,X=C("symbols"),K=C("op-symbols"),Z=C("string-to-symbol-registry"),J=C("symbol-to-string-registry"),ee=C("wks"),te=o.QObject,ne=!te||!te.prototype||!te.prototype.findChild,re=s&&u((function(){return 7!=b(Y({},"a",{get:function(){return Y(this,"a",{value:7}).a}})).a}))?function(e,t,n){var r=V(q,t);r&&delete q[t],Y(e,t,n),r&&e!==q&&Y(q,t,r)}:Y,oe=function(e,t){var n=X[e]=b(W.prototype);return B(n,{type:"Symbol",tag:e,description:t}),s||(n.description=t),n},ie=c?function(e){return"symbol"==typeof e}:function(e){return Object(e)instanceof W},ae=function(e,t,n){e===q&&ae(K,t,n),h(e);var r=y(t,!0);return h(n),p(X,r)?(n.enumerable?(p(e,z)&&e[z][r]&&(e[z][r]=!1),n=b(n,{enumerable:v(0,!1)})):(p(e,z)||Y(e,z,v(1,{})),e[z][r]=!0),re(e,r,n)):Y(e,r,n)},se=function(e,t){h(e);var n=g(t),r=x(n).concat(pe(n));return F(r,(function(t){s&&!le.call(n,t)||ae(e,t,n[t])})),e},le=function(e){var t=y(e,!0),n=G.call(this,t);return!(this===q&&p(X,t)&&!p(K,t))&&(!(n||!p(this,t)||!p(X,t)||p(this,z)&&this[z][t])||n)},ce=function(e,t){var n=g(e),r=y(t,!0);if(n!==q||!p(X,r)||p(K,r)){var o=V(n,r);return!o||!p(X,r)||p(n,z)&&n[z][r]||(o.enumerable=!0),o}},ue=function(e){var t=Q(g(e)),n=[];return F(t,(function(e){p(X,e)||p(A,e)||n.push(e)})),n},pe=function(e){var t=e===q,n=Q(t?K:g(e)),r=[];return F(n,(function(e){!p(X,e)||t&&!p(q,e)||r.push(X[e])})),r};(l||(j((W=function(){if(this instanceof W)throw TypeError("Symbol is not a constructor");var e=arguments.length&&void 0!==arguments[0]?String(arguments[0]):void 0,t=P(e),n=function(e){this===q&&n.call(K,e),p(this,z)&&p(this[z],t)&&(this[z][t]=!1),re(this,t,v(1,e))};return s&&ne&&re(q,t,{configurable:!0,set:n}),oe(t,e)}).prototype,"toString",(function(){return $(this).tag})),j(W,"withoutSetter",(function(e){return oe(P(e),e)})),S.f=le,E.f=ae,_.f=ce,w.f=k.f=ue,O.f=pe,N.f=function(e){return oe(R(e),e)},s&&(Y(W.prototype,"description",{configurable:!0,get:function(){return $(this).description}}),a||j(q,"propertyIsEnumerable",le,{unsafe:!0}))),r({global:!0,wrap:!0,forced:!l,sham:!l},{Symbol:W}),F(x(ee),(function(e){L(e)})),r({target:"Symbol",stat:!0,forced:!l},{for:function(e){var t=String(e);if(p(Z,t))return Z[t];var n=W(t);return Z[t]=n,J[n]=t,n},keyFor:function(e){if(!ie(e))throw TypeError(e+" is not a symbol");if(p(J,e))return J[e]},useSetter:function(){ne=!0},useSimple:function(){ne=!1}}),r({target:"Object",stat:!0,forced:!l,sham:!s},{create:function(e,t){return void 0===t?b(e):se(b(e),t)},defineProperty:ae,defineProperties:se,getOwnPropertyDescriptor:ce}),r({target:"Object",stat:!0,forced:!l},{getOwnPropertyNames:ue,getOwnPropertySymbols:pe}),r({target:"Object",stat:!0,forced:u((function(){O.f(1)}))},{getOwnPropertySymbols:function(e){return O.f(m(e))}}),H)&&r({target:"JSON",stat:!0,forced:!l||u((function(){var e=W();return"[null]"!=H([e])||"{}"!=H({a:e})||"{}"!=H(Object(e))}))},{stringify:function(e,t,n){for(var r,o=[e],i=1;arguments.length>i;)o.push(arguments[i++]);if(r=t,(d(t)||void 0!==e)&&!ie(e))return f(t)||(t=function(e,t){if("function"==typeof r&&(t=r.call(this,e,t)),!ie(t))return t}),o[1]=t,H.apply(null,o)}});W.prototype[U]||T(W.prototype,U,W.prototype.valueOf),M(W,"Symbol"),A[z]=!0},function(e,t,n){var r=n(36),o=n(79).f,i={}.toString,a="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[];e.exports.f=function(e){return a&&"[object Window]"==i.call(e)?function(e){try{return o(e)}catch(e){return a.slice()}}(e):o(r(e))}},function(e,t,n){n(12)("asyncIterator")},function(e,t,n){"use strict";var r=n(17),o=n(18),i=n(4),a=n(11),s=n(9),l=n(16).f,c=n(106),u=i.Symbol;if(o&&"function"==typeof u&&(!("description"in u.prototype)||void 0!==u().description)){var p={},f=function(){var e=arguments.length<1||void 0===arguments[0]?void 0:String(arguments[0]),t=this instanceof f?new u(e):void 0===e?u():u(e);return""===e&&(p[t]=!0),t};c(f,u);var d=f.prototype=u.prototype;d.constructor=f;var h=d.toString,m="Symbol(test)"==String(u("test")),g=/^Symbol\((.*)\)[^)]+$/;l(d,"description",{configurable:!0,get:function(){var e=s(this)?this.valueOf():this,t=h.call(e);if(a(p,e))return"";var n=m?t.slice(7,-1):t.replace(g,"$1");return""===n?void 0:n}}),r({global:!0,forced:!0},{Symbol:f})}},function(e,t,n){n(12)("hasInstance")},function(e,t,n){n(12)("isConcatSpreadable")},function(e,t,n){n(12)("iterator")},function(e,t,n){n(12)("match")},function(e,t,n){n(12)("matchAll")},function(e,t,n){n(12)("replace")},function(e,t,n){n(12)("search")},function(e,t,n){n(12)("species")},function(e,t,n){n(12)("split")},function(e,t,n){n(12)("toPrimitive")},function(e,t,n){n(12)("toStringTag")},function(e,t,n){n(12)("unscopables")},function(e,t,n){n(30)(Math,"Math",!0)},function(e,t,n){var r=n(4);n(30)(r.JSON,"JSON",!0)},function(e,t){self.fetch||(self.fetch=function(e,t){return t=t||{},new Promise((function(n,r){var o=new XMLHttpRequest,i=[],a=[],s={},l=function(){return{ok:2==(o.status/100|0),statusText:o.statusText,status:o.status,url:o.responseURL,text:function(){return Promise.resolve(o.responseText)},json:function(){return Promise.resolve(JSON.parse(o.responseText))},blob:function(){return Promise.resolve(new Blob([o.response]))},clone:l,headers:{keys:function(){return i},entries:function(){return a},get:function(e){return s[e.toLowerCase()]},has:function(e){return e.toLowerCase()in s}}}};for(var c in o.open(t.method||"get",e,!0),o.onload=function(){o.getAllResponseHeaders().replace(/^(.*?):[^\S\n]*([\s\S]*?)$/gm,(function(e,t,n){i.push(t=t.toLowerCase()),a.push([t,n]),s[t]=s[t]?s[t]+","+n:n})),n(l())},o.onerror=r,o.withCredentials="include"==t.credentials,t.headers)o.setRequestHeader(c,t.headers[c]);o.send(t.body||null)}))})},function(e,t,n){(function(e){!function(e){var t=function(){try{return!!Symbol.iterator}catch(e){return!1}}(),n=function(e){var n={next:function(){var t=e.shift();return{done:void 0===t,value:t}}};return t&&(n[Symbol.iterator]=function(){return n}),n},r=function(e){return encodeURIComponent(e).replace(/%20/g,"+")},o=function(e){return decodeURIComponent(String(e).replace(/\+/g," "))};(function(){try{var t=e.URLSearchParams;return"a=1"===new t("?a=1").toString()&&"function"==typeof t.prototype.set}catch(e){return!1}})()||function(){var o=function(e){Object.defineProperty(this,"_entries",{writable:!0,value:{}});var t=typeof e;if("undefined"===t);else if("string"===t)""!==e&&this._fromString(e);else if(e instanceof o){var n=this;e.forEach((function(e,t){n.append(t,e)}))}else{if(null===e||"object"!==t)throw new TypeError("Unsupported input's type for URLSearchParams");if("[object Array]"===Object.prototype.toString.call(e))for(var r=0;r<e.length;r++){var i=e[r];if("[object Array]"!==Object.prototype.toString.call(i)&&2===i.length)throw new TypeError("Expected [string, any] as entry at index "+r+" of URLSearchParams's input");this.append(i[0],i[1])}else for(var a in e)e.hasOwnProperty(a)&&this.append(a,e[a])}},i=o.prototype;i.append=function(e,t){e in this._entries?this._entries[e].push(String(t)):this._entries[e]=[String(t)]},i.delete=function(e){delete this._entries[e]},i.get=function(e){return e in this._entries?this._entries[e][0]:null},i.getAll=function(e){return e in this._entries?this._entries[e].slice(0):[]},i.has=function(e){return e in this._entries},i.set=function(e,t){this._entries[e]=[String(t)]},i.forEach=function(e,t){var n;for(var r in this._entries)if(this._entries.hasOwnProperty(r)){n=this._entries[r];for(var o=0;o<n.length;o++)e.call(t,n[o],r,this)}},i.keys=function(){var e=[];return this.forEach((function(t,n){e.push(n)})),n(e)},i.values=function(){var e=[];return this.forEach((function(t){e.push(t)})),n(e)},i.entries=function(){var e=[];return this.forEach((function(t,n){e.push([n,t])})),n(e)},t&&(i[Symbol.iterator]=i.entries),i.toString=function(){var e=[];return this.forEach((function(t,n){e.push(r(n)+"="+r(t))})),e.join("&")},e.URLSearchParams=o}();var i=e.URLSearchParams.prototype;"function"!=typeof i.sort&&(i.sort=function(){var e=this,t=[];this.forEach((function(n,r){t.push([r,n]),e._entries||e.delete(r)})),t.sort((function(e,t){return e[0]<t[0]?-1:e[0]>t[0]?1:0})),e._entries&&(e._entries={});for(var n=0;n<t.length;n++)this.append(t[n][0],t[n][1])}),"function"!=typeof i._fromString&&Object.defineProperty(i,"_fromString",{enumerable:!1,configurable:!1,writable:!1,value:function(e){if(this._entries)this._entries={};else{var t=[];this.forEach((function(e,n){t.push(n)}));for(var n=0;n<t.length;n++)this.delete(t[n])}var r,i=(e=e.replace(/^\?/,"")).split("&");for(n=0;n<i.length;n++)r=i[n].split("="),this.append(o(r[0]),r.length>1?o(r[1]):"")}})}(void 0!==e?e:"undefined"!=typeof window?window:"undefined"!=typeof self?self:this),function(e){if(function(){try{var t=new e.URL("b","http://a");return t.pathname="c d","http://a/c%20d"===t.href&&t.searchParams}catch(e){return!1}}()||function(){var t=e.URL,n=function(t,n){"string"!=typeof t&&(t=String(t));var r,o=document;if(n&&(void 0===e.location||n!==e.location.href)){(r=(o=document.implementation.createHTMLDocument("")).createElement("base")).href=n,o.head.appendChild(r);try{if(0!==r.href.indexOf(n))throw new Error(r.href)}catch(e){throw new Error("URL unable to set base "+n+" due to "+e)}}var i=o.createElement("a");if(i.href=t,r&&(o.body.appendChild(i),i.href=i.href),":"===i.protocol||!/:/.test(i.href))throw new TypeError("Invalid URL");Object.defineProperty(this,"_anchorElement",{value:i});var a=new e.URLSearchParams(this.search),s=!0,l=!0,c=this;["append","delete","set"].forEach((function(e){var t=a[e];a[e]=function(){t.apply(a,arguments),s&&(l=!1,c.search=a.toString(),l=!0)}})),Object.defineProperty(this,"searchParams",{value:a,enumerable:!0});var u=void 0;Object.defineProperty(this,"_updateSearchParams",{enumerable:!1,configurable:!1,writable:!1,value:function(){this.search!==u&&(u=this.search,l&&(s=!1,this.searchParams._fromString(this.search),s=!0))}})},r=n.prototype;["hash","host","hostname","port","protocol"].forEach((function(e){!function(e){Object.defineProperty(r,e,{get:function(){return this._anchorElement[e]},set:function(t){this._anchorElement[e]=t},enumerable:!0})}(e)})),Object.defineProperty(r,"search",{get:function(){return this._anchorElement.search},set:function(e){this._anchorElement.search=e,this._updateSearchParams()},enumerable:!0}),Object.defineProperties(r,{toString:{get:function(){var e=this;return function(){return e.href}}},href:{get:function(){return this._anchorElement.href.replace(/\?$/,"")},set:function(e){this._anchorElement.href=e,this._updateSearchParams()},enumerable:!0},pathname:{get:function(){return this._anchorElement.pathname.replace(/(^\/?)/,"/")},set:function(e){this._anchorElement.pathname=e},enumerable:!0},origin:{get:function(){var e={"http:":80,"https:":443,"ftp:":21}[this._anchorElement.protocol],t=this._anchorElement.port!=e&&""!==this._anchorElement.port;return this._anchorElement.protocol+"//"+this._anchorElement.hostname+(t?":"+this._anchorElement.port:"")},enumerable:!0},password:{get:function(){return""},set:function(e){},enumerable:!0},username:{get:function(){return""},set:function(e){},enumerable:!0}}),n.createObjectURL=function(e){return t.createObjectURL.apply(t,arguments)},n.revokeObjectURL=function(e){return t.revokeObjectURL.apply(t,arguments)},e.URL=n}(),void 0!==e.location&&!("origin"in e.location)){var t=function(){return e.location.protocol+"//"+e.location.hostname+(e.location.port?":"+e.location.port:"")};try{Object.defineProperty(e.location,"origin",{get:t,enumerable:!0})}catch(n){setInterval((function(){e.location.origin=t()}),100)}}}(void 0!==e?e:"undefined"!=typeof window?window:"undefined"!=typeof self?self:this)}).call(this,n(7))},function(e,t,n){"use strict"; /** @license React v16.13.0 * react.production.min.js * @@ -109,7 +109,7 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - */var r=n(130),o="function"==typeof Symbol&&Symbol.for,i=o?Symbol.for("react.element"):60103,a=o?Symbol.for("react.portal"):60106,s=o?Symbol.for("react.fragment"):60107,l=o?Symbol.for("react.strict_mode"):60108,c=o?Symbol.for("react.profiler"):60114,u=o?Symbol.for("react.provider"):60109,p=o?Symbol.for("react.context"):60110,f=o?Symbol.for("react.forward_ref"):60112,d=o?Symbol.for("react.suspense"):60113,h=o?Symbol.for("react.memo"):60115,m=o?Symbol.for("react.lazy"):60116,g="function"==typeof Symbol&&Symbol.iterator;function y(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n<arguments.length;n++)t+="&args[]="+encodeURIComponent(arguments[n]);return"Minified React error #"+e+"; visit "+t+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}var v={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},b={};function x(e,t,n){this.props=e,this.context=t,this.refs=b,this.updater=n||v}function w(){}function k(e,t,n){this.props=e,this.context=t,this.refs=b,this.updater=n||v}x.prototype.isReactComponent={},x.prototype.setState=function(e,t){if("object"!=typeof e&&"function"!=typeof e&&null!=e)throw Error(y(85));this.updater.enqueueSetState(this,e,t,"setState")},x.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")},w.prototype=x.prototype;var O=k.prototype=new w;O.constructor=k,r(O,x.prototype),O.isPureReactComponent=!0;var _={current:null},E=Object.prototype.hasOwnProperty,S={key:!0,ref:!0,__self:!0,__source:!0};function T(e,t,n){var r,o={},a=null,s=null;if(null!=t)for(r in void 0!==t.ref&&(s=t.ref),void 0!==t.key&&(a=""+t.key),t)E.call(t,r)&&!S.hasOwnProperty(r)&&(o[r]=t[r]);var l=arguments.length-2;if(1===l)o.children=n;else if(1<l){for(var c=Array(l),u=0;u<l;u++)c[u]=arguments[u+2];o.children=c}if(e&&e.defaultProps)for(r in l=e.defaultProps)void 0===o[r]&&(o[r]=l[r]);return{$$typeof:i,type:e,key:a,ref:s,props:o,_owner:_.current}}function j(e){return"object"==typeof e&&null!==e&&e.$$typeof===i}var C=/\/+/g,A=[];function I(e,t,n,r){if(A.length){var o=A.pop();return o.result=e,o.keyPrefix=t,o.func=n,o.context=r,o.count=0,o}return{result:e,keyPrefix:t,func:n,context:r,count:0}}function P(e){e.result=null,e.keyPrefix=null,e.func=null,e.context=null,e.count=0,10>A.length&&A.push(e)}function R(e,t,n){return null==e?0:function e(t,n,r,o){var s=typeof t;"undefined"!==s&&"boolean"!==s||(t=null);var l=!1;if(null===t)l=!0;else switch(s){case"string":case"number":l=!0;break;case"object":switch(t.$$typeof){case i:case a:l=!0}}if(l)return r(o,t,""===n?"."+N(t,0):n),1;if(l=0,n=""===n?".":n+":",Array.isArray(t))for(var c=0;c<t.length;c++){var u=n+N(s=t[c],c);l+=e(s,u,r,o)}else if(null===t||"object"!=typeof t?u=null:u="function"==typeof(u=g&&t[g]||t["@@iterator"])?u:null,"function"==typeof u)for(t=u.call(t),c=0;!(s=t.next()).done;)l+=e(s=s.value,u=n+N(s,c++),r,o);else if("object"===s)throw r=""+t,Error(y(31,"[object Object]"===r?"object with keys {"+Object.keys(t).join(", ")+"}":r,""));return l}(e,"",t,n)}function N(e,t){return"object"==typeof e&&null!==e&&null!=e.key?function(e){var t={"=":"=0",":":"=2"};return"$"+(""+e).replace(/[=:]/g,(function(e){return t[e]}))}(e.key):t.toString(36)}function L(e,t){e.func.call(e.context,t,e.count++)}function M(e,t,n){var r=e.result,o=e.keyPrefix;e=e.func.call(e.context,t,e.count++),Array.isArray(e)?D(e,r,n,(function(e){return e})):null!=e&&(j(e)&&(e=function(e,t){return{$$typeof:i,type:e.type,key:t,ref:e.ref,props:e.props,_owner:e._owner}}(e,o+(!e.key||t&&t.key===e.key?"":(""+e.key).replace(C,"$&/")+"/")+n)),r.push(e))}function D(e,t,n,r,o){var i="";null!=n&&(i=(""+n).replace(C,"$&/")+"/"),R(e,M,t=I(t,i,r,o)),P(t)}var F={current:null};function z(){var e=F.current;if(null===e)throw Error(y(321));return e}var U={ReactCurrentDispatcher:F,ReactCurrentBatchConfig:{suspense:null},ReactCurrentOwner:_,IsSomeRendererActing:{current:!1},assign:r};t.Children={map:function(e,t,n){if(null==e)return e;var r=[];return D(e,r,null,t,n),r},forEach:function(e,t,n){if(null==e)return e;R(e,L,t=I(null,null,t,n)),P(t)},count:function(e){return R(e,(function(){return null}),null)},toArray:function(e){var t=[];return D(e,t,null,(function(e){return e})),t},only:function(e){if(!j(e))throw Error(y(143));return e}},t.Component=x,t.Fragment=s,t.Profiler=c,t.PureComponent=k,t.StrictMode=l,t.Suspense=d,t.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=U,t.cloneElement=function(e,t,n){if(null==e)throw Error(y(267,e));var o=r({},e.props),a=e.key,s=e.ref,l=e._owner;if(null!=t){if(void 0!==t.ref&&(s=t.ref,l=_.current),void 0!==t.key&&(a=""+t.key),e.type&&e.type.defaultProps)var c=e.type.defaultProps;for(u in t)E.call(t,u)&&!S.hasOwnProperty(u)&&(o[u]=void 0===t[u]&&void 0!==c?c[u]:t[u])}var u=arguments.length-2;if(1===u)o.children=n;else if(1<u){c=Array(u);for(var p=0;p<u;p++)c[p]=arguments[p+2];o.children=c}return{$$typeof:i,type:e.type,key:a,ref:s,props:o,_owner:l}},t.createContext=function(e,t){return void 0===t&&(t=null),(e={$$typeof:p,_calculateChangedBits:t,_currentValue:e,_currentValue2:e,_threadCount:0,Provider:null,Consumer:null}).Provider={$$typeof:u,_context:e},e.Consumer=e},t.createElement=T,t.createFactory=function(e){var t=T.bind(null,e);return t.type=e,t},t.createRef=function(){return{current:null}},t.forwardRef=function(e){return{$$typeof:f,render:e}},t.isValidElement=j,t.lazy=function(e){return{$$typeof:m,_ctor:e,_status:-1,_result:null}},t.memo=function(e,t){return{$$typeof:h,type:e,compare:void 0===t?null:t}},t.useCallback=function(e,t){return z().useCallback(e,t)},t.useContext=function(e,t){return z().useContext(e,t)},t.useDebugValue=function(){},t.useEffect=function(e,t){return z().useEffect(e,t)},t.useImperativeHandle=function(e,t,n){return z().useImperativeHandle(e,t,n)},t.useLayoutEffect=function(e,t){return z().useLayoutEffect(e,t)},t.useMemo=function(e,t){return z().useMemo(e,t)},t.useReducer=function(e,t,n){return z().useReducer(e,t,n)},t.useRef=function(e){return z().useRef(e)},t.useState=function(e){return z().useState(e)},t.version="16.13.0"},function(e,t,n){"use strict"; + */var r=n(130),o="function"==typeof Symbol&&Symbol.for,i=o?Symbol.for("react.element"):60103,a=o?Symbol.for("react.portal"):60106,s=o?Symbol.for("react.fragment"):60107,l=o?Symbol.for("react.strict_mode"):60108,c=o?Symbol.for("react.profiler"):60114,u=o?Symbol.for("react.provider"):60109,p=o?Symbol.for("react.context"):60110,f=o?Symbol.for("react.forward_ref"):60112,d=o?Symbol.for("react.suspense"):60113,h=o?Symbol.for("react.memo"):60115,m=o?Symbol.for("react.lazy"):60116,g="function"==typeof Symbol&&Symbol.iterator;function y(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n<arguments.length;n++)t+="&args[]="+encodeURIComponent(arguments[n]);return"Minified React error #"+e+"; visit "+t+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}var v={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},b={};function x(e,t,n){this.props=e,this.context=t,this.refs=b,this.updater=n||v}function w(){}function k(e,t,n){this.props=e,this.context=t,this.refs=b,this.updater=n||v}x.prototype.isReactComponent={},x.prototype.setState=function(e,t){if("object"!=typeof e&&"function"!=typeof e&&null!=e)throw Error(y(85));this.updater.enqueueSetState(this,e,t,"setState")},x.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")},w.prototype=x.prototype;var O=k.prototype=new w;O.constructor=k,r(O,x.prototype),O.isPureReactComponent=!0;var _={current:null},E=Object.prototype.hasOwnProperty,S={key:!0,ref:!0,__self:!0,__source:!0};function T(e,t,n){var r,o={},a=null,s=null;if(null!=t)for(r in void 0!==t.ref&&(s=t.ref),void 0!==t.key&&(a=""+t.key),t)E.call(t,r)&&!S.hasOwnProperty(r)&&(o[r]=t[r]);var l=arguments.length-2;if(1===l)o.children=n;else if(1<l){for(var c=Array(l),u=0;u<l;u++)c[u]=arguments[u+2];o.children=c}if(e&&e.defaultProps)for(r in l=e.defaultProps)void 0===o[r]&&(o[r]=l[r]);return{$$typeof:i,type:e,key:a,ref:s,props:o,_owner:_.current}}function j(e){return"object"==typeof e&&null!==e&&e.$$typeof===i}var C=/\/+/g,I=[];function A(e,t,n,r){if(I.length){var o=I.pop();return o.result=e,o.keyPrefix=t,o.func=n,o.context=r,o.count=0,o}return{result:e,keyPrefix:t,func:n,context:r,count:0}}function P(e){e.result=null,e.keyPrefix=null,e.func=null,e.context=null,e.count=0,10>I.length&&I.push(e)}function R(e,t,n){return null==e?0:function e(t,n,r,o){var s=typeof t;"undefined"!==s&&"boolean"!==s||(t=null);var l=!1;if(null===t)l=!0;else switch(s){case"string":case"number":l=!0;break;case"object":switch(t.$$typeof){case i:case a:l=!0}}if(l)return r(o,t,""===n?"."+N(t,0):n),1;if(l=0,n=""===n?".":n+":",Array.isArray(t))for(var c=0;c<t.length;c++){var u=n+N(s=t[c],c);l+=e(s,u,r,o)}else if(null===t||"object"!=typeof t?u=null:u="function"==typeof(u=g&&t[g]||t["@@iterator"])?u:null,"function"==typeof u)for(t=u.call(t),c=0;!(s=t.next()).done;)l+=e(s=s.value,u=n+N(s,c++),r,o);else if("object"===s)throw r=""+t,Error(y(31,"[object Object]"===r?"object with keys {"+Object.keys(t).join(", ")+"}":r,""));return l}(e,"",t,n)}function N(e,t){return"object"==typeof e&&null!==e&&null!=e.key?function(e){var t={"=":"=0",":":"=2"};return"$"+(""+e).replace(/[=:]/g,(function(e){return t[e]}))}(e.key):t.toString(36)}function L(e,t){e.func.call(e.context,t,e.count++)}function M(e,t,n){var r=e.result,o=e.keyPrefix;e=e.func.call(e.context,t,e.count++),Array.isArray(e)?D(e,r,n,(function(e){return e})):null!=e&&(j(e)&&(e=function(e,t){return{$$typeof:i,type:e.type,key:t,ref:e.ref,props:e.props,_owner:e._owner}}(e,o+(!e.key||t&&t.key===e.key?"":(""+e.key).replace(C,"$&/")+"/")+n)),r.push(e))}function D(e,t,n,r,o){var i="";null!=n&&(i=(""+n).replace(C,"$&/")+"/"),R(e,M,t=A(t,i,r,o)),P(t)}var F={current:null};function z(){var e=F.current;if(null===e)throw Error(y(321));return e}var U={ReactCurrentDispatcher:F,ReactCurrentBatchConfig:{suspense:null},ReactCurrentOwner:_,IsSomeRendererActing:{current:!1},assign:r};t.Children={map:function(e,t,n){if(null==e)return e;var r=[];return D(e,r,null,t,n),r},forEach:function(e,t,n){if(null==e)return e;R(e,L,t=A(null,null,t,n)),P(t)},count:function(e){return R(e,(function(){return null}),null)},toArray:function(e){var t=[];return D(e,t,null,(function(e){return e})),t},only:function(e){if(!j(e))throw Error(y(143));return e}},t.Component=x,t.Fragment=s,t.Profiler=c,t.PureComponent=k,t.StrictMode=l,t.Suspense=d,t.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=U,t.cloneElement=function(e,t,n){if(null==e)throw Error(y(267,e));var o=r({},e.props),a=e.key,s=e.ref,l=e._owner;if(null!=t){if(void 0!==t.ref&&(s=t.ref,l=_.current),void 0!==t.key&&(a=""+t.key),e.type&&e.type.defaultProps)var c=e.type.defaultProps;for(u in t)E.call(t,u)&&!S.hasOwnProperty(u)&&(o[u]=void 0===t[u]&&void 0!==c?c[u]:t[u])}var u=arguments.length-2;if(1===u)o.children=n;else if(1<u){c=Array(u);for(var p=0;p<u;p++)c[p]=arguments[p+2];o.children=c}return{$$typeof:i,type:e.type,key:a,ref:s,props:o,_owner:l}},t.createContext=function(e,t){return void 0===t&&(t=null),(e={$$typeof:p,_calculateChangedBits:t,_currentValue:e,_currentValue2:e,_threadCount:0,Provider:null,Consumer:null}).Provider={$$typeof:u,_context:e},e.Consumer=e},t.createElement=T,t.createFactory=function(e){var t=T.bind(null,e);return t.type=e,t},t.createRef=function(){return{current:null}},t.forwardRef=function(e){return{$$typeof:f,render:e}},t.isValidElement=j,t.lazy=function(e){return{$$typeof:m,_ctor:e,_status:-1,_result:null}},t.memo=function(e,t){return{$$typeof:h,type:e,compare:void 0===t?null:t}},t.useCallback=function(e,t){return z().useCallback(e,t)},t.useContext=function(e,t){return z().useContext(e,t)},t.useDebugValue=function(){},t.useEffect=function(e,t){return z().useEffect(e,t)},t.useImperativeHandle=function(e,t,n){return z().useImperativeHandle(e,t,n)},t.useLayoutEffect=function(e,t){return z().useLayoutEffect(e,t)},t.useMemo=function(e,t){return z().useMemo(e,t)},t.useReducer=function(e,t,n){return z().useReducer(e,t,n)},t.useRef=function(e){return z().useRef(e)},t.useState=function(e){return z().useState(e)},t.version="16.13.0"},function(e,t,n){"use strict"; /** @license React v16.13.0 * react-dom.production.min.js * @@ -117,7 +117,7 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - */var r=n(0),o=n(130),i=n(228);function a(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n<arguments.length;n++)t+="&args[]="+encodeURIComponent(arguments[n]);return"Minified React error #"+e+"; visit "+t+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}if(!r)throw Error(a(227));function s(e,t,n,r,o,i,a,s,l){var c=Array.prototype.slice.call(arguments,3);try{t.apply(n,c)}catch(e){this.onError(e)}}var l=!1,c=null,u=!1,p=null,f={onError:function(e){l=!0,c=e}};function d(e,t,n,r,o,i,a,u,p){l=!1,c=null,s.apply(f,arguments)}var h=null,m=null,g=null;function y(e,t,n){var r=e.type||"unknown-event";e.currentTarget=g(n),function(e,t,n,r,o,i,s,f,h){if(d.apply(this,arguments),l){if(!l)throw Error(a(198));var m=c;l=!1,c=null,u||(u=!0,p=m)}}(r,t,void 0,e),e.currentTarget=null}var v=r.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;v.hasOwnProperty("ReactCurrentDispatcher")||(v.ReactCurrentDispatcher={current:null}),v.hasOwnProperty("ReactCurrentBatchConfig")||(v.ReactCurrentBatchConfig={suspense:null});var b=/^(.*)[\\\/]/,x="function"==typeof Symbol&&Symbol.for,w=x?Symbol.for("react.element"):60103,k=x?Symbol.for("react.portal"):60106,O=x?Symbol.for("react.fragment"):60107,_=x?Symbol.for("react.strict_mode"):60108,E=x?Symbol.for("react.profiler"):60114,S=x?Symbol.for("react.provider"):60109,T=x?Symbol.for("react.context"):60110,j=x?Symbol.for("react.concurrent_mode"):60111,C=x?Symbol.for("react.forward_ref"):60112,A=x?Symbol.for("react.suspense"):60113,I=x?Symbol.for("react.suspense_list"):60120,P=x?Symbol.for("react.memo"):60115,R=x?Symbol.for("react.lazy"):60116,N=x?Symbol.for("react.block"):60121,L="function"==typeof Symbol&&Symbol.iterator;function M(e){return null===e||"object"!=typeof e?null:"function"==typeof(e=L&&e[L]||e["@@iterator"])?e:null}function D(e){if(null==e)return null;if("function"==typeof e)return e.displayName||e.name||null;if("string"==typeof e)return e;switch(e){case O:return"Fragment";case k:return"Portal";case E:return"Profiler";case _:return"StrictMode";case A:return"Suspense";case I:return"SuspenseList"}if("object"==typeof e)switch(e.$$typeof){case T:return"Context.Consumer";case S:return"Context.Provider";case C:var t=e.render;return t=t.displayName||t.name||"",e.displayName||(""!==t?"ForwardRef("+t+")":"ForwardRef");case P:return D(e.type);case N:return D(e.render);case R:if(e=1===e._status?e._result:null)return D(e)}return null}function F(e){var t="";do{e:switch(e.tag){case 3:case 4:case 6:case 7:case 10:case 9:var n="";break e;default:var r=e._debugOwner,o=e._debugSource,i=D(e.type);n=null,r&&(n=D(r.type)),r=i,i="",o?i=" (at "+o.fileName.replace(b,"")+":"+o.lineNumber+")":n&&(i=" (created by "+n+")"),n="\n in "+(r||"Unknown")+i}t+=n,e=e.return}while(e);return t}var z=null,U={};function B(){if(z)for(var e in U){var t=U[e],n=z.indexOf(e);if(!(-1<n))throw Error(a(96,e));if(!q[n]){if(!t.extractEvents)throw Error(a(97,e));for(var r in q[n]=t,n=t.eventTypes){var o=void 0,i=n[r],s=t,l=r;if(W.hasOwnProperty(l))throw Error(a(99,l));W[l]=i;var c=i.phasedRegistrationNames;if(c){for(o in c)c.hasOwnProperty(o)&&$(c[o],s,l);o=!0}else i.registrationName?($(i.registrationName,s,l),o=!0):o=!1;if(!o)throw Error(a(98,r,e))}}}}function $(e,t,n){if(H[e])throw Error(a(100,e));H[e]=t,V[e]=t.eventTypes[n].dependencies}var q=[],W={},H={},V={};function Y(e){var t,n=!1;for(t in e)if(e.hasOwnProperty(t)){var r=e[t];if(!U.hasOwnProperty(t)||U[t]!==r){if(U[t])throw Error(a(102,t));U[t]=r,n=!0}}n&&B()}var Q=!("undefined"==typeof window||void 0===window.document||void 0===window.document.createElement),G=null,X=null,K=null;function Z(e){if(e=m(e)){if("function"!=typeof G)throw Error(a(280));var t=e.stateNode;t&&(t=h(t),G(e.stateNode,e.type,t))}}function J(e){X?K?K.push(e):K=[e]:X=e}function ee(){if(X){var e=X,t=K;if(K=X=null,Z(e),t)for(e=0;e<t.length;e++)Z(t[e])}}function te(e,t){return e(t)}function ne(e,t,n,r,o){return e(t,n,r,o)}function re(){}var oe=te,ie=!1,ae=!1;function se(){null===X&&null===K||(re(),ee())}function le(e,t,n){if(ae)return e(t,n);ae=!0;try{return oe(e,t,n)}finally{ae=!1,se()}}var ce=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,ue=Object.prototype.hasOwnProperty,pe={},fe={};function de(e,t,n,r,o,i){this.acceptsBooleans=2===t||3===t||4===t,this.attributeName=r,this.attributeNamespace=o,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=i}var he={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach((function(e){he[e]=new de(e,0,!1,e,null,!1)})),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach((function(e){var t=e[0];he[t]=new de(t,1,!1,e[1],null,!1)})),["contentEditable","draggable","spellCheck","value"].forEach((function(e){he[e]=new de(e,2,!1,e.toLowerCase(),null,!1)})),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach((function(e){he[e]=new de(e,2,!1,e,null,!1)})),"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach((function(e){he[e]=new de(e,3,!1,e.toLowerCase(),null,!1)})),["checked","multiple","muted","selected"].forEach((function(e){he[e]=new de(e,3,!0,e,null,!1)})),["capture","download"].forEach((function(e){he[e]=new de(e,4,!1,e,null,!1)})),["cols","rows","size","span"].forEach((function(e){he[e]=new de(e,6,!1,e,null,!1)})),["rowSpan","start"].forEach((function(e){he[e]=new de(e,5,!1,e.toLowerCase(),null,!1)}));var me=/[\-:]([a-z])/g;function ge(e){return e[1].toUpperCase()}function ye(e,t,n,r){var o=he.hasOwnProperty(t)?he[t]:null;(null!==o?0===o.type:!r&&(2<t.length&&("o"===t[0]||"O"===t[0])&&("n"===t[1]||"N"===t[1])))||(function(e,t,n,r){if(null==t||function(e,t,n,r){if(null!==n&&0===n.type)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return!r&&(null!==n?!n.acceptsBooleans:"data-"!==(e=e.toLowerCase().slice(0,5))&&"aria-"!==e);default:return!1}}(e,t,n,r))return!0;if(r)return!1;if(null!==n)switch(n.type){case 3:return!t;case 4:return!1===t;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}(t,n,o,r)&&(n=null),r||null===o?function(e){return!!ue.call(fe,e)||!ue.call(pe,e)&&(ce.test(e)?fe[e]=!0:(pe[e]=!0,!1))}(t)&&(null===n?e.removeAttribute(t):e.setAttribute(t,""+n)):o.mustUseProperty?e[o.propertyName]=null===n?3!==o.type&&"":n:(t=o.attributeName,r=o.attributeNamespace,null===n?e.removeAttribute(t):(n=3===(o=o.type)||4===o&&!0===n?"":""+n,r?e.setAttributeNS(r,t,n):e.setAttribute(t,n))))}function ve(e){switch(typeof e){case"boolean":case"number":case"object":case"string":case"undefined":return e;default:return""}}function be(e){var t=e.type;return(e=e.nodeName)&&"input"===e.toLowerCase()&&("checkbox"===t||"radio"===t)}function xe(e){e._valueTracker||(e._valueTracker=function(e){var t=be(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&void 0!==n&&"function"==typeof n.get&&"function"==typeof n.set){var o=n.get,i=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return o.call(this)},set:function(e){r=""+e,i.call(this,e)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(e){r=""+e},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}(e))}function we(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=be(e)?e.checked?"true":"false":e.value),(e=r)!==n&&(t.setValue(e),!0)}function ke(e,t){var n=t.checked;return o({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:null!=n?n:e._wrapperState.initialChecked})}function Oe(e,t){var n=null==t.defaultValue?"":t.defaultValue,r=null!=t.checked?t.checked:t.defaultChecked;n=ve(null!=t.value?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:"checkbox"===t.type||"radio"===t.type?null!=t.checked:null!=t.value}}function _e(e,t){null!=(t=t.checked)&&ye(e,"checked",t,!1)}function Ee(e,t){_e(e,t);var n=ve(t.value),r=t.type;if(null!=n)"number"===r?(0===n&&""===e.value||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if("submit"===r||"reset"===r)return void e.removeAttribute("value");t.hasOwnProperty("value")?Te(e,t.type,n):t.hasOwnProperty("defaultValue")&&Te(e,t.type,ve(t.defaultValue)),null==t.checked&&null!=t.defaultChecked&&(e.defaultChecked=!!t.defaultChecked)}function Se(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!("submit"!==r&&"reset"!==r||void 0!==t.value&&null!==t.value))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}""!==(n=e.name)&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,""!==n&&(e.name=n)}function Te(e,t,n){"number"===t&&e.ownerDocument.activeElement===e||(null==n?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}function je(e,t){return e=o({children:void 0},t),(t=function(e){var t="";return r.Children.forEach(e,(function(e){null!=e&&(t+=e)})),t}(t.children))&&(e.children=t),e}function Ce(e,t,n,r){if(e=e.options,t){t={};for(var o=0;o<n.length;o++)t["$"+n[o]]=!0;for(n=0;n<e.length;n++)o=t.hasOwnProperty("$"+e[n].value),e[n].selected!==o&&(e[n].selected=o),o&&r&&(e[n].defaultSelected=!0)}else{for(n=""+ve(n),t=null,o=0;o<e.length;o++){if(e[o].value===n)return e[o].selected=!0,void(r&&(e[o].defaultSelected=!0));null!==t||e[o].disabled||(t=e[o])}null!==t&&(t.selected=!0)}}function Ae(e,t){if(null!=t.dangerouslySetInnerHTML)throw Error(a(91));return o({},t,{value:void 0,defaultValue:void 0,children:""+e._wrapperState.initialValue})}function Ie(e,t){var n=t.value;if(null==n){if(n=t.children,t=t.defaultValue,null!=n){if(null!=t)throw Error(a(92));if(Array.isArray(n)){if(!(1>=n.length))throw Error(a(93));n=n[0]}t=n}null==t&&(t=""),n=t}e._wrapperState={initialValue:ve(n)}}function Pe(e,t){var n=ve(t.value),r=ve(t.defaultValue);null!=n&&((n=""+n)!==e.value&&(e.value=n),null==t.defaultValue&&e.defaultValue!==n&&(e.defaultValue=n)),null!=r&&(e.defaultValue=""+r)}function Re(e){var t=e.textContent;t===e._wrapperState.initialValue&&""!==t&&null!==t&&(e.value=t)}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach((function(e){var t=e.replace(me,ge);he[t]=new de(t,1,!1,e,null,!1)})),"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach((function(e){var t=e.replace(me,ge);he[t]=new de(t,1,!1,e,"http://www.w3.org/1999/xlink",!1)})),["xml:base","xml:lang","xml:space"].forEach((function(e){var t=e.replace(me,ge);he[t]=new de(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1)})),["tabIndex","crossOrigin"].forEach((function(e){he[e]=new de(e,1,!1,e.toLowerCase(),null,!1)})),he.xlinkHref=new de("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0),["src","href","action","formAction"].forEach((function(e){he[e]=new de(e,1,!1,e.toLowerCase(),null,!0)}));var Ne="http://www.w3.org/1999/xhtml",Le="http://www.w3.org/2000/svg";function Me(e){switch(e){case"svg":return"http://www.w3.org/2000/svg";case"math":return"http://www.w3.org/1998/Math/MathML";default:return"http://www.w3.org/1999/xhtml"}}function De(e,t){return null==e||"http://www.w3.org/1999/xhtml"===e?Me(t):"http://www.w3.org/2000/svg"===e&&"foreignObject"===t?"http://www.w3.org/1999/xhtml":e}var Fe,ze=function(e){return"undefined"!=typeof MSApp&&MSApp.execUnsafeLocalFunction?function(t,n,r,o){MSApp.execUnsafeLocalFunction((function(){return e(t,n)}))}:e}((function(e,t){if(e.namespaceURI!==Le||"innerHTML"in e)e.innerHTML=t;else{for((Fe=Fe||document.createElement("div")).innerHTML="<svg>"+t.valueOf().toString()+"</svg>",t=Fe.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}}));function Ue(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&3===n.nodeType)return void(n.nodeValue=t)}e.textContent=t}function Be(e,t){var n={};return n[e.toLowerCase()]=t.toLowerCase(),n["Webkit"+e]="webkit"+t,n["Moz"+e]="moz"+t,n}var $e={animationend:Be("Animation","AnimationEnd"),animationiteration:Be("Animation","AnimationIteration"),animationstart:Be("Animation","AnimationStart"),transitionend:Be("Transition","TransitionEnd")},qe={},We={};function He(e){if(qe[e])return qe[e];if(!$e[e])return e;var t,n=$e[e];for(t in n)if(n.hasOwnProperty(t)&&t in We)return qe[e]=n[t];return e}Q&&(We=document.createElement("div").style,"AnimationEvent"in window||(delete $e.animationend.animation,delete $e.animationiteration.animation,delete $e.animationstart.animation),"TransitionEvent"in window||delete $e.transitionend.transition);var Ve=He("animationend"),Ye=He("animationiteration"),Qe=He("animationstart"),Ge=He("transitionend"),Xe="abort canplay canplaythrough durationchange emptied encrypted ended error loadeddata loadedmetadata loadstart pause play playing progress ratechange seeked seeking stalled suspend timeupdate volumechange waiting".split(" "),Ke=new("function"==typeof WeakMap?WeakMap:Map);function Ze(e){var t=Ke.get(e);return void 0===t&&(t=new Map,Ke.set(e,t)),t}function Je(e){var t=e,n=e;if(e.alternate)for(;t.return;)t=t.return;else{e=t;do{0!=(1026&(t=e).effectTag)&&(n=t.return),e=t.return}while(e)}return 3===t.tag?n:null}function et(e){if(13===e.tag){var t=e.memoizedState;if(null===t&&(null!==(e=e.alternate)&&(t=e.memoizedState)),null!==t)return t.dehydrated}return null}function tt(e){if(Je(e)!==e)throw Error(a(188))}function nt(e){if(!(e=function(e){var t=e.alternate;if(!t){if(null===(t=Je(e)))throw Error(a(188));return t!==e?null:e}for(var n=e,r=t;;){var o=n.return;if(null===o)break;var i=o.alternate;if(null===i){if(null!==(r=o.return)){n=r;continue}break}if(o.child===i.child){for(i=o.child;i;){if(i===n)return tt(o),e;if(i===r)return tt(o),t;i=i.sibling}throw Error(a(188))}if(n.return!==r.return)n=o,r=i;else{for(var s=!1,l=o.child;l;){if(l===n){s=!0,n=o,r=i;break}if(l===r){s=!0,r=o,n=i;break}l=l.sibling}if(!s){for(l=i.child;l;){if(l===n){s=!0,n=i,r=o;break}if(l===r){s=!0,r=i,n=o;break}l=l.sibling}if(!s)throw Error(a(189))}}if(n.alternate!==r)throw Error(a(190))}if(3!==n.tag)throw Error(a(188));return n.stateNode.current===n?e:t}(e)))return null;for(var t=e;;){if(5===t.tag||6===t.tag)return t;if(t.child)t.child.return=t,t=t.child;else{if(t===e)break;for(;!t.sibling;){if(!t.return||t.return===e)return null;t=t.return}t.sibling.return=t.return,t=t.sibling}}return null}function rt(e,t){if(null==t)throw Error(a(30));return null==e?t:Array.isArray(e)?Array.isArray(t)?(e.push.apply(e,t),e):(e.push(t),e):Array.isArray(t)?[e].concat(t):[e,t]}function ot(e,t,n){Array.isArray(e)?e.forEach(t,n):e&&t.call(n,e)}var it=null;function at(e){if(e){var t=e._dispatchListeners,n=e._dispatchInstances;if(Array.isArray(t))for(var r=0;r<t.length&&!e.isPropagationStopped();r++)y(e,t[r],n[r]);else t&&y(e,t,n);e._dispatchListeners=null,e._dispatchInstances=null,e.isPersistent()||e.constructor.release(e)}}function st(e){if(null!==e&&(it=rt(it,e)),e=it,it=null,e){if(ot(e,at),it)throw Error(a(95));if(u)throw e=p,u=!1,p=null,e}}function lt(e){return(e=e.target||e.srcElement||window).correspondingUseElement&&(e=e.correspondingUseElement),3===e.nodeType?e.parentNode:e}function ct(e){if(!Q)return!1;var t=(e="on"+e)in document;return t||((t=document.createElement("div")).setAttribute(e,"return;"),t="function"==typeof t[e]),t}var ut=[];function pt(e){e.topLevelType=null,e.nativeEvent=null,e.targetInst=null,e.ancestors.length=0,10>ut.length&&ut.push(e)}function ft(e,t,n,r){if(ut.length){var o=ut.pop();return o.topLevelType=e,o.eventSystemFlags=r,o.nativeEvent=t,o.targetInst=n,o}return{topLevelType:e,eventSystemFlags:r,nativeEvent:t,targetInst:n,ancestors:[]}}function dt(e){var t=e.targetInst,n=t;do{if(!n){e.ancestors.push(n);break}var r=n;if(3===r.tag)r=r.stateNode.containerInfo;else{for(;r.return;)r=r.return;r=3!==r.tag?null:r.stateNode.containerInfo}if(!r)break;5!==(t=n.tag)&&6!==t||e.ancestors.push(n),n=Tn(r)}while(n);for(n=0;n<e.ancestors.length;n++){t=e.ancestors[n];var o=lt(e.nativeEvent);r=e.topLevelType;var i=e.nativeEvent,a=e.eventSystemFlags;0===n&&(a|=64);for(var s=null,l=0;l<q.length;l++){var c=q[l];c&&(c=c.extractEvents(r,t,i,o,a))&&(s=rt(s,c))}st(s)}}function ht(e,t,n){if(!n.has(e)){switch(e){case"scroll":Qt(t,"scroll",!0);break;case"focus":case"blur":Qt(t,"focus",!0),Qt(t,"blur",!0),n.set("blur",null),n.set("focus",null);break;case"cancel":case"close":ct(e)&&Qt(t,e,!0);break;case"invalid":case"submit":case"reset":break;default:-1===Xe.indexOf(e)&&Yt(e,t)}n.set(e,null)}}var mt,gt,yt,vt=!1,bt=[],xt=null,wt=null,kt=null,Ot=new Map,_t=new Map,Et=[],St="mousedown mouseup touchcancel touchend touchstart auxclick dblclick pointercancel pointerdown pointerup dragend dragstart drop compositionend compositionstart keydown keypress keyup input textInput close cancel copy cut paste click change contextmenu reset submit".split(" "),Tt="focus blur dragenter dragleave mouseover mouseout pointerover pointerout gotpointercapture lostpointercapture".split(" ");function jt(e,t,n,r,o){return{blockedOn:e,topLevelType:t,eventSystemFlags:32|n,nativeEvent:o,container:r}}function Ct(e,t){switch(e){case"focus":case"blur":xt=null;break;case"dragenter":case"dragleave":wt=null;break;case"mouseover":case"mouseout":kt=null;break;case"pointerover":case"pointerout":Ot.delete(t.pointerId);break;case"gotpointercapture":case"lostpointercapture":_t.delete(t.pointerId)}}function At(e,t,n,r,o,i){return null===e||e.nativeEvent!==i?(e=jt(t,n,r,o,i),null!==t&&(null!==(t=jn(t))&>(t)),e):(e.eventSystemFlags|=r,e)}function It(e){var t=Tn(e.target);if(null!==t){var n=Je(t);if(null!==n)if(13===(t=n.tag)){if(null!==(t=et(n)))return e.blockedOn=t,void i.unstable_runWithPriority(e.priority,(function(){yt(n)}))}else if(3===t&&n.stateNode.hydrate)return void(e.blockedOn=3===n.tag?n.stateNode.containerInfo:null)}e.blockedOn=null}function Pt(e){if(null!==e.blockedOn)return!1;var t=Zt(e.topLevelType,e.eventSystemFlags,e.container,e.nativeEvent);if(null!==t){var n=jn(t);return null!==n&>(n),e.blockedOn=t,!1}return!0}function Rt(e,t,n){Pt(e)&&n.delete(t)}function Nt(){for(vt=!1;0<bt.length;){var e=bt[0];if(null!==e.blockedOn){null!==(e=jn(e.blockedOn))&&mt(e);break}var t=Zt(e.topLevelType,e.eventSystemFlags,e.container,e.nativeEvent);null!==t?e.blockedOn=t:bt.shift()}null!==xt&&Pt(xt)&&(xt=null),null!==wt&&Pt(wt)&&(wt=null),null!==kt&&Pt(kt)&&(kt=null),Ot.forEach(Rt),_t.forEach(Rt)}function Lt(e,t){e.blockedOn===t&&(e.blockedOn=null,vt||(vt=!0,i.unstable_scheduleCallback(i.unstable_NormalPriority,Nt)))}function Mt(e){function t(t){return Lt(t,e)}if(0<bt.length){Lt(bt[0],e);for(var n=1;n<bt.length;n++){var r=bt[n];r.blockedOn===e&&(r.blockedOn=null)}}for(null!==xt&&Lt(xt,e),null!==wt&&Lt(wt,e),null!==kt&&Lt(kt,e),Ot.forEach(t),_t.forEach(t),n=0;n<Et.length;n++)(r=Et[n]).blockedOn===e&&(r.blockedOn=null);for(;0<Et.length&&null===(n=Et[0]).blockedOn;)It(n),null===n.blockedOn&&Et.shift()}var Dt={},Ft=new Map,zt=new Map,Ut=["abort","abort",Ve,"animationEnd",Ye,"animationIteration",Qe,"animationStart","canplay","canPlay","canplaythrough","canPlayThrough","durationchange","durationChange","emptied","emptied","encrypted","encrypted","ended","ended","error","error","gotpointercapture","gotPointerCapture","load","load","loadeddata","loadedData","loadedmetadata","loadedMetadata","loadstart","loadStart","lostpointercapture","lostPointerCapture","playing","playing","progress","progress","seeking","seeking","stalled","stalled","suspend","suspend","timeupdate","timeUpdate",Ge,"transitionEnd","waiting","waiting"];function Bt(e,t){for(var n=0;n<e.length;n+=2){var r=e[n],o=e[n+1],i="on"+(o[0].toUpperCase()+o.slice(1));i={phasedRegistrationNames:{bubbled:i,captured:i+"Capture"},dependencies:[r],eventPriority:t},zt.set(r,t),Ft.set(r,i),Dt[o]=i}}Bt("blur blur cancel cancel click click close close contextmenu contextMenu copy copy cut cut auxclick auxClick dblclick doubleClick dragend dragEnd dragstart dragStart drop drop focus focus input input invalid invalid keydown keyDown keypress keyPress keyup keyUp mousedown mouseDown mouseup mouseUp paste paste pause pause play play pointercancel pointerCancel pointerdown pointerDown pointerup pointerUp ratechange rateChange reset reset seeked seeked submit submit touchcancel touchCancel touchend touchEnd touchstart touchStart volumechange volumeChange".split(" "),0),Bt("drag drag dragenter dragEnter dragexit dragExit dragleave dragLeave dragover dragOver mousemove mouseMove mouseout mouseOut mouseover mouseOver pointermove pointerMove pointerout pointerOut pointerover pointerOver scroll scroll toggle toggle touchmove touchMove wheel wheel".split(" "),1),Bt(Ut,2);for(var $t="change selectionchange textInput compositionstart compositionend compositionupdate".split(" "),qt=0;qt<$t.length;qt++)zt.set($t[qt],0);var Wt=i.unstable_UserBlockingPriority,Ht=i.unstable_runWithPriority,Vt=!0;function Yt(e,t){Qt(t,e,!1)}function Qt(e,t,n){var r=zt.get(t);switch(void 0===r?2:r){case 0:r=Gt.bind(null,t,1,e);break;case 1:r=Xt.bind(null,t,1,e);break;default:r=Kt.bind(null,t,1,e)}n?e.addEventListener(t,r,!0):e.addEventListener(t,r,!1)}function Gt(e,t,n,r){ie||re();var o=Kt,i=ie;ie=!0;try{ne(o,e,t,n,r)}finally{(ie=i)||se()}}function Xt(e,t,n,r){Ht(Wt,Kt.bind(null,e,t,n,r))}function Kt(e,t,n,r){if(Vt)if(0<bt.length&&-1<St.indexOf(e))e=jt(null,e,t,n,r),bt.push(e);else{var o=Zt(e,t,n,r);if(null===o)Ct(e,r);else if(-1<St.indexOf(e))e=jt(o,e,t,n,r),bt.push(e);else if(!function(e,t,n,r,o){switch(t){case"focus":return xt=At(xt,e,t,n,r,o),!0;case"dragenter":return wt=At(wt,e,t,n,r,o),!0;case"mouseover":return kt=At(kt,e,t,n,r,o),!0;case"pointerover":var i=o.pointerId;return Ot.set(i,At(Ot.get(i)||null,e,t,n,r,o)),!0;case"gotpointercapture":return i=o.pointerId,_t.set(i,At(_t.get(i)||null,e,t,n,r,o)),!0}return!1}(o,e,t,n,r)){Ct(e,r),e=ft(e,r,null,t);try{le(dt,e)}finally{pt(e)}}}}function Zt(e,t,n,r){if(null!==(n=Tn(n=lt(r)))){var o=Je(n);if(null===o)n=null;else{var i=o.tag;if(13===i){if(null!==(n=et(o)))return n;n=null}else if(3===i){if(o.stateNode.hydrate)return 3===o.tag?o.stateNode.containerInfo:null;n=null}else o!==n&&(n=null)}}e=ft(e,r,n,t);try{le(dt,e)}finally{pt(e)}return null}var Jt={animationIterationCount:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},en=["Webkit","ms","Moz","O"];function tn(e,t,n){return null==t||"boolean"==typeof t||""===t?"":n||"number"!=typeof t||0===t||Jt.hasOwnProperty(e)&&Jt[e]?(""+t).trim():t+"px"}function nn(e,t){for(var n in e=e.style,t)if(t.hasOwnProperty(n)){var r=0===n.indexOf("--"),o=tn(n,t[n],r);"float"===n&&(n="cssFloat"),r?e.setProperty(n,o):e[n]=o}}Object.keys(Jt).forEach((function(e){en.forEach((function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),Jt[t]=Jt[e]}))}));var rn=o({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function on(e,t){if(t){if(rn[e]&&(null!=t.children||null!=t.dangerouslySetInnerHTML))throw Error(a(137,e,""));if(null!=t.dangerouslySetInnerHTML){if(null!=t.children)throw Error(a(60));if(!("object"==typeof t.dangerouslySetInnerHTML&&"__html"in t.dangerouslySetInnerHTML))throw Error(a(61))}if(null!=t.style&&"object"!=typeof t.style)throw Error(a(62,""))}}function an(e,t){if(-1===e.indexOf("-"))return"string"==typeof t.is;switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var sn=Ne;function ln(e,t){var n=Ze(e=9===e.nodeType||11===e.nodeType?e:e.ownerDocument);t=V[t];for(var r=0;r<t.length;r++)ht(t[r],e,n)}function cn(){}function un(e){if(void 0===(e=e||("undefined"!=typeof document?document:void 0)))return null;try{return e.activeElement||e.body}catch(t){return e.body}}function pn(e){for(;e&&e.firstChild;)e=e.firstChild;return e}function fn(e,t){var n,r=pn(e);for(e=0;r;){if(3===r.nodeType){if(n=e+r.textContent.length,e<=t&&n>=t)return{node:r,offset:t-e};e=n}e:{for(;r;){if(r.nextSibling){r=r.nextSibling;break e}r=r.parentNode}r=void 0}r=pn(r)}}function dn(){for(var e=window,t=un();t instanceof e.HTMLIFrameElement;){try{var n="string"==typeof t.contentWindow.location.href}catch(e){n=!1}if(!n)break;t=un((e=t.contentWindow).document)}return t}function hn(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&("input"===t&&("text"===e.type||"search"===e.type||"tel"===e.type||"url"===e.type||"password"===e.type)||"textarea"===t||"true"===e.contentEditable)}var mn=null,gn=null;function yn(e,t){switch(e){case"button":case"input":case"select":case"textarea":return!!t.autoFocus}return!1}function vn(e,t){return"textarea"===e||"option"===e||"noscript"===e||"string"==typeof t.children||"number"==typeof t.children||"object"==typeof t.dangerouslySetInnerHTML&&null!==t.dangerouslySetInnerHTML&&null!=t.dangerouslySetInnerHTML.__html}var bn="function"==typeof setTimeout?setTimeout:void 0,xn="function"==typeof clearTimeout?clearTimeout:void 0;function wn(e){for(;null!=e;e=e.nextSibling){var t=e.nodeType;if(1===t||3===t)break}return e}function kn(e){e=e.previousSibling;for(var t=0;e;){if(8===e.nodeType){var n=e.data;if("$"===n||"$!"===n||"$?"===n){if(0===t)return e;t--}else"/$"===n&&t++}e=e.previousSibling}return null}var On=Math.random().toString(36).slice(2),_n="__reactInternalInstance$"+On,En="__reactEventHandlers$"+On,Sn="__reactContainere$"+On;function Tn(e){var t=e[_n];if(t)return t;for(var n=e.parentNode;n;){if(t=n[Sn]||n[_n]){if(n=t.alternate,null!==t.child||null!==n&&null!==n.child)for(e=kn(e);null!==e;){if(n=e[_n])return n;e=kn(e)}return t}n=(e=n).parentNode}return null}function jn(e){return!(e=e[_n]||e[Sn])||5!==e.tag&&6!==e.tag&&13!==e.tag&&3!==e.tag?null:e}function Cn(e){if(5===e.tag||6===e.tag)return e.stateNode;throw Error(a(33))}function An(e){return e[En]||null}function In(e){do{e=e.return}while(e&&5!==e.tag);return e||null}function Pn(e,t){var n=e.stateNode;if(!n)return null;var r=h(n);if(!r)return null;n=r[t];e:switch(t){case"onClick":case"onClickCapture":case"onDoubleClick":case"onDoubleClickCapture":case"onMouseDown":case"onMouseDownCapture":case"onMouseMove":case"onMouseMoveCapture":case"onMouseUp":case"onMouseUpCapture":case"onMouseEnter":(r=!r.disabled)||(r=!("button"===(e=e.type)||"input"===e||"select"===e||"textarea"===e)),e=!r;break e;default:e=!1}if(e)return null;if(n&&"function"!=typeof n)throw Error(a(231,t,typeof n));return n}function Rn(e,t,n){(t=Pn(e,n.dispatchConfig.phasedRegistrationNames[t]))&&(n._dispatchListeners=rt(n._dispatchListeners,t),n._dispatchInstances=rt(n._dispatchInstances,e))}function Nn(e){if(e&&e.dispatchConfig.phasedRegistrationNames){for(var t=e._targetInst,n=[];t;)n.push(t),t=In(t);for(t=n.length;0<t--;)Rn(n[t],"captured",e);for(t=0;t<n.length;t++)Rn(n[t],"bubbled",e)}}function Ln(e,t,n){e&&n&&n.dispatchConfig.registrationName&&(t=Pn(e,n.dispatchConfig.registrationName))&&(n._dispatchListeners=rt(n._dispatchListeners,t),n._dispatchInstances=rt(n._dispatchInstances,e))}function Mn(e){e&&e.dispatchConfig.registrationName&&Ln(e._targetInst,null,e)}function Dn(e){ot(e,Nn)}var Fn=null,zn=null,Un=null;function Bn(){if(Un)return Un;var e,t,n=zn,r=n.length,o="value"in Fn?Fn.value:Fn.textContent,i=o.length;for(e=0;e<r&&n[e]===o[e];e++);var a=r-e;for(t=1;t<=a&&n[r-t]===o[i-t];t++);return Un=o.slice(e,1<t?1-t:void 0)}function $n(){return!0}function qn(){return!1}function Wn(e,t,n,r){for(var o in this.dispatchConfig=e,this._targetInst=t,this.nativeEvent=n,e=this.constructor.Interface)e.hasOwnProperty(o)&&((t=e[o])?this[o]=t(n):"target"===o?this.target=r:this[o]=n[o]);return this.isDefaultPrevented=(null!=n.defaultPrevented?n.defaultPrevented:!1===n.returnValue)?$n:qn,this.isPropagationStopped=qn,this}function Hn(e,t,n,r){if(this.eventPool.length){var o=this.eventPool.pop();return this.call(o,e,t,n,r),o}return new this(e,t,n,r)}function Vn(e){if(!(e instanceof this))throw Error(a(279));e.destructor(),10>this.eventPool.length&&this.eventPool.push(e)}function Yn(e){e.eventPool=[],e.getPooled=Hn,e.release=Vn}o(Wn.prototype,{preventDefault:function(){this.defaultPrevented=!0;var e=this.nativeEvent;e&&(e.preventDefault?e.preventDefault():"unknown"!=typeof e.returnValue&&(e.returnValue=!1),this.isDefaultPrevented=$n)},stopPropagation:function(){var e=this.nativeEvent;e&&(e.stopPropagation?e.stopPropagation():"unknown"!=typeof e.cancelBubble&&(e.cancelBubble=!0),this.isPropagationStopped=$n)},persist:function(){this.isPersistent=$n},isPersistent:qn,destructor:function(){var e,t=this.constructor.Interface;for(e in t)this[e]=null;this.nativeEvent=this._targetInst=this.dispatchConfig=null,this.isPropagationStopped=this.isDefaultPrevented=qn,this._dispatchInstances=this._dispatchListeners=null}}),Wn.Interface={type:null,target:null,currentTarget:function(){return null},eventPhase:null,bubbles:null,cancelable:null,timeStamp:function(e){return e.timeStamp||Date.now()},defaultPrevented:null,isTrusted:null},Wn.extend=function(e){function t(){}function n(){return r.apply(this,arguments)}var r=this;t.prototype=r.prototype;var i=new t;return o(i,n.prototype),n.prototype=i,n.prototype.constructor=n,n.Interface=o({},r.Interface,e),n.extend=r.extend,Yn(n),n},Yn(Wn);var Qn=Wn.extend({data:null}),Gn=Wn.extend({data:null}),Xn=[9,13,27,32],Kn=Q&&"CompositionEvent"in window,Zn=null;Q&&"documentMode"in document&&(Zn=document.documentMode);var Jn=Q&&"TextEvent"in window&&!Zn,er=Q&&(!Kn||Zn&&8<Zn&&11>=Zn),tr=String.fromCharCode(32),nr={beforeInput:{phasedRegistrationNames:{bubbled:"onBeforeInput",captured:"onBeforeInputCapture"},dependencies:["compositionend","keypress","textInput","paste"]},compositionEnd:{phasedRegistrationNames:{bubbled:"onCompositionEnd",captured:"onCompositionEndCapture"},dependencies:"blur compositionend keydown keypress keyup mousedown".split(" ")},compositionStart:{phasedRegistrationNames:{bubbled:"onCompositionStart",captured:"onCompositionStartCapture"},dependencies:"blur compositionstart keydown keypress keyup mousedown".split(" ")},compositionUpdate:{phasedRegistrationNames:{bubbled:"onCompositionUpdate",captured:"onCompositionUpdateCapture"},dependencies:"blur compositionupdate keydown keypress keyup mousedown".split(" ")}},rr=!1;function or(e,t){switch(e){case"keyup":return-1!==Xn.indexOf(t.keyCode);case"keydown":return 229!==t.keyCode;case"keypress":case"mousedown":case"blur":return!0;default:return!1}}function ir(e){return"object"==typeof(e=e.detail)&&"data"in e?e.data:null}var ar=!1;var sr={eventTypes:nr,extractEvents:function(e,t,n,r){var o;if(Kn)e:{switch(e){case"compositionstart":var i=nr.compositionStart;break e;case"compositionend":i=nr.compositionEnd;break e;case"compositionupdate":i=nr.compositionUpdate;break e}i=void 0}else ar?or(e,n)&&(i=nr.compositionEnd):"keydown"===e&&229===n.keyCode&&(i=nr.compositionStart);return i?(er&&"ko"!==n.locale&&(ar||i!==nr.compositionStart?i===nr.compositionEnd&&ar&&(o=Bn()):(zn="value"in(Fn=r)?Fn.value:Fn.textContent,ar=!0)),i=Qn.getPooled(i,t,n,r),o?i.data=o:null!==(o=ir(n))&&(i.data=o),Dn(i),o=i):o=null,(e=Jn?function(e,t){switch(e){case"compositionend":return ir(t);case"keypress":return 32!==t.which?null:(rr=!0,tr);case"textInput":return(e=t.data)===tr&&rr?null:e;default:return null}}(e,n):function(e,t){if(ar)return"compositionend"===e||!Kn&&or(e,t)?(e=Bn(),Un=zn=Fn=null,ar=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1<t.char.length)return t.char;if(t.which)return String.fromCharCode(t.which)}return null;case"compositionend":return er&&"ko"!==t.locale?null:t.data;default:return null}}(e,n))?((t=Gn.getPooled(nr.beforeInput,t,n,r)).data=e,Dn(t)):t=null,null===o?t:null===t?o:[o,t]}},lr={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0};function cr(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return"input"===t?!!lr[e.type]:"textarea"===t}var ur={change:{phasedRegistrationNames:{bubbled:"onChange",captured:"onChangeCapture"},dependencies:"blur change click focus input keydown keyup selectionchange".split(" ")}};function pr(e,t,n){return(e=Wn.getPooled(ur.change,e,t,n)).type="change",J(n),Dn(e),e}var fr=null,dr=null;function hr(e){st(e)}function mr(e){if(we(Cn(e)))return e}function gr(e,t){if("change"===e)return t}var yr=!1;function vr(){fr&&(fr.detachEvent("onpropertychange",br),dr=fr=null)}function br(e){if("value"===e.propertyName&&mr(dr))if(e=pr(dr,e,lt(e)),ie)st(e);else{ie=!0;try{te(hr,e)}finally{ie=!1,se()}}}function xr(e,t,n){"focus"===e?(vr(),dr=n,(fr=t).attachEvent("onpropertychange",br)):"blur"===e&&vr()}function wr(e){if("selectionchange"===e||"keyup"===e||"keydown"===e)return mr(dr)}function kr(e,t){if("click"===e)return mr(t)}function Or(e,t){if("input"===e||"change"===e)return mr(t)}Q&&(yr=ct("input")&&(!document.documentMode||9<document.documentMode));var _r={eventTypes:ur,_isInputEventSupported:yr,extractEvents:function(e,t,n,r){var o=t?Cn(t):window,i=o.nodeName&&o.nodeName.toLowerCase();if("select"===i||"input"===i&&"file"===o.type)var a=gr;else if(cr(o))if(yr)a=Or;else{a=wr;var s=xr}else(i=o.nodeName)&&"input"===i.toLowerCase()&&("checkbox"===o.type||"radio"===o.type)&&(a=kr);if(a&&(a=a(e,t)))return pr(a,n,r);s&&s(e,o,t),"blur"===e&&(e=o._wrapperState)&&e.controlled&&"number"===o.type&&Te(o,"number",o.value)}},Er=Wn.extend({view:null,detail:null}),Sr={Alt:"altKey",Control:"ctrlKey",Meta:"metaKey",Shift:"shiftKey"};function Tr(e){var t=this.nativeEvent;return t.getModifierState?t.getModifierState(e):!!(e=Sr[e])&&!!t[e]}function jr(){return Tr}var Cr=0,Ar=0,Ir=!1,Pr=!1,Rr=Er.extend({screenX:null,screenY:null,clientX:null,clientY:null,pageX:null,pageY:null,ctrlKey:null,shiftKey:null,altKey:null,metaKey:null,getModifierState:jr,button:null,buttons:null,relatedTarget:function(e){return e.relatedTarget||(e.fromElement===e.srcElement?e.toElement:e.fromElement)},movementX:function(e){if("movementX"in e)return e.movementX;var t=Cr;return Cr=e.screenX,Ir?"mousemove"===e.type?e.screenX-t:0:(Ir=!0,0)},movementY:function(e){if("movementY"in e)return e.movementY;var t=Ar;return Ar=e.screenY,Pr?"mousemove"===e.type?e.screenY-t:0:(Pr=!0,0)}}),Nr=Rr.extend({pointerId:null,width:null,height:null,pressure:null,tangentialPressure:null,tiltX:null,tiltY:null,twist:null,pointerType:null,isPrimary:null}),Lr={mouseEnter:{registrationName:"onMouseEnter",dependencies:["mouseout","mouseover"]},mouseLeave:{registrationName:"onMouseLeave",dependencies:["mouseout","mouseover"]},pointerEnter:{registrationName:"onPointerEnter",dependencies:["pointerout","pointerover"]},pointerLeave:{registrationName:"onPointerLeave",dependencies:["pointerout","pointerover"]}},Mr={eventTypes:Lr,extractEvents:function(e,t,n,r,o){var i="mouseover"===e||"pointerover"===e,a="mouseout"===e||"pointerout"===e;if(i&&0==(32&o)&&(n.relatedTarget||n.fromElement)||!a&&!i)return null;(i=r.window===r?r:(i=r.ownerDocument)?i.defaultView||i.parentWindow:window,a)?(a=t,null!==(t=(t=n.relatedTarget||n.toElement)?Tn(t):null)&&(t!==Je(t)||5!==t.tag&&6!==t.tag)&&(t=null)):a=null;if(a===t)return null;if("mouseout"===e||"mouseover"===e)var s=Rr,l=Lr.mouseLeave,c=Lr.mouseEnter,u="mouse";else"pointerout"!==e&&"pointerover"!==e||(s=Nr,l=Lr.pointerLeave,c=Lr.pointerEnter,u="pointer");if(e=null==a?i:Cn(a),i=null==t?i:Cn(t),(l=s.getPooled(l,a,n,r)).type=u+"leave",l.target=e,l.relatedTarget=i,(n=s.getPooled(c,t,n,r)).type=u+"enter",n.target=i,n.relatedTarget=e,u=t,(r=a)&&u)e:{for(c=u,a=0,e=s=r;e;e=In(e))a++;for(e=0,t=c;t;t=In(t))e++;for(;0<a-e;)s=In(s),a--;for(;0<e-a;)c=In(c),e--;for(;a--;){if(s===c||s===c.alternate)break e;s=In(s),c=In(c)}s=null}else s=null;for(c=s,s=[];r&&r!==c&&(null===(a=r.alternate)||a!==c);)s.push(r),r=In(r);for(r=[];u&&u!==c&&(null===(a=u.alternate)||a!==c);)r.push(u),u=In(u);for(u=0;u<s.length;u++)Ln(s[u],"bubbled",l);for(u=r.length;0<u--;)Ln(r[u],"captured",n);return 0==(64&o)?[l]:[l,n]}};var Dr="function"==typeof Object.is?Object.is:function(e,t){return e===t&&(0!==e||1/e==1/t)||e!=e&&t!=t},Fr=Object.prototype.hasOwnProperty;function zr(e,t){if(Dr(e,t))return!0;if("object"!=typeof e||null===e||"object"!=typeof t||null===t)return!1;var n=Object.keys(e),r=Object.keys(t);if(n.length!==r.length)return!1;for(r=0;r<n.length;r++)if(!Fr.call(t,n[r])||!Dr(e[n[r]],t[n[r]]))return!1;return!0}var Ur=Q&&"documentMode"in document&&11>=document.documentMode,Br={select:{phasedRegistrationNames:{bubbled:"onSelect",captured:"onSelectCapture"},dependencies:"blur contextmenu dragend focus keydown keyup mousedown mouseup selectionchange".split(" ")}},$r=null,qr=null,Wr=null,Hr=!1;function Vr(e,t){var n=t.window===t?t.document:9===t.nodeType?t:t.ownerDocument;return Hr||null==$r||$r!==un(n)?null:("selectionStart"in(n=$r)&&hn(n)?n={start:n.selectionStart,end:n.selectionEnd}:n={anchorNode:(n=(n.ownerDocument&&n.ownerDocument.defaultView||window).getSelection()).anchorNode,anchorOffset:n.anchorOffset,focusNode:n.focusNode,focusOffset:n.focusOffset},Wr&&zr(Wr,n)?null:(Wr=n,(e=Wn.getPooled(Br.select,qr,e,t)).type="select",e.target=$r,Dn(e),e))}var Yr={eventTypes:Br,extractEvents:function(e,t,n,r,o,i){if(!(i=!(o=i||(r.window===r?r.document:9===r.nodeType?r:r.ownerDocument)))){e:{o=Ze(o),i=V.onSelect;for(var a=0;a<i.length;a++)if(!o.has(i[a])){o=!1;break e}o=!0}i=!o}if(i)return null;switch(o=t?Cn(t):window,e){case"focus":(cr(o)||"true"===o.contentEditable)&&($r=o,qr=t,Wr=null);break;case"blur":Wr=qr=$r=null;break;case"mousedown":Hr=!0;break;case"contextmenu":case"mouseup":case"dragend":return Hr=!1,Vr(n,r);case"selectionchange":if(Ur)break;case"keydown":case"keyup":return Vr(n,r)}return null}},Qr=Wn.extend({animationName:null,elapsedTime:null,pseudoElement:null}),Gr=Wn.extend({clipboardData:function(e){return"clipboardData"in e?e.clipboardData:window.clipboardData}}),Xr=Er.extend({relatedTarget:null});function Kr(e){var t=e.keyCode;return"charCode"in e?0===(e=e.charCode)&&13===t&&(e=13):e=t,10===e&&(e=13),32<=e||13===e?e:0}var Zr={Esc:"Escape",Spacebar:" ",Left:"ArrowLeft",Up:"ArrowUp",Right:"ArrowRight",Down:"ArrowDown",Del:"Delete",Win:"OS",Menu:"ContextMenu",Apps:"ContextMenu",Scroll:"ScrollLock",MozPrintableKey:"Unidentified"},Jr={8:"Backspace",9:"Tab",12:"Clear",13:"Enter",16:"Shift",17:"Control",18:"Alt",19:"Pause",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",45:"Insert",46:"Delete",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"NumLock",145:"ScrollLock",224:"Meta"},eo=Er.extend({key:function(e){if(e.key){var t=Zr[e.key]||e.key;if("Unidentified"!==t)return t}return"keypress"===e.type?13===(e=Kr(e))?"Enter":String.fromCharCode(e):"keydown"===e.type||"keyup"===e.type?Jr[e.keyCode]||"Unidentified":""},location:null,ctrlKey:null,shiftKey:null,altKey:null,metaKey:null,repeat:null,locale:null,getModifierState:jr,charCode:function(e){return"keypress"===e.type?Kr(e):0},keyCode:function(e){return"keydown"===e.type||"keyup"===e.type?e.keyCode:0},which:function(e){return"keypress"===e.type?Kr(e):"keydown"===e.type||"keyup"===e.type?e.keyCode:0}}),to=Rr.extend({dataTransfer:null}),no=Er.extend({touches:null,targetTouches:null,changedTouches:null,altKey:null,metaKey:null,ctrlKey:null,shiftKey:null,getModifierState:jr}),ro=Wn.extend({propertyName:null,elapsedTime:null,pseudoElement:null}),oo=Rr.extend({deltaX:function(e){return"deltaX"in e?e.deltaX:"wheelDeltaX"in e?-e.wheelDeltaX:0},deltaY:function(e){return"deltaY"in e?e.deltaY:"wheelDeltaY"in e?-e.wheelDeltaY:"wheelDelta"in e?-e.wheelDelta:0},deltaZ:null,deltaMode:null}),io={eventTypes:Dt,extractEvents:function(e,t,n,r){var o=Ft.get(e);if(!o)return null;switch(e){case"keypress":if(0===Kr(n))return null;case"keydown":case"keyup":e=eo;break;case"blur":case"focus":e=Xr;break;case"click":if(2===n.button)return null;case"auxclick":case"dblclick":case"mousedown":case"mousemove":case"mouseup":case"mouseout":case"mouseover":case"contextmenu":e=Rr;break;case"drag":case"dragend":case"dragenter":case"dragexit":case"dragleave":case"dragover":case"dragstart":case"drop":e=to;break;case"touchcancel":case"touchend":case"touchmove":case"touchstart":e=no;break;case Ve:case Ye:case Qe:e=Qr;break;case Ge:e=ro;break;case"scroll":e=Er;break;case"wheel":e=oo;break;case"copy":case"cut":case"paste":e=Gr;break;case"gotpointercapture":case"lostpointercapture":case"pointercancel":case"pointerdown":case"pointermove":case"pointerout":case"pointerover":case"pointerup":e=Nr;break;default:e=Wn}return Dn(t=e.getPooled(o,t,n,r)),t}};if(z)throw Error(a(101));z=Array.prototype.slice.call("ResponderEventPlugin SimpleEventPlugin EnterLeaveEventPlugin ChangeEventPlugin SelectEventPlugin BeforeInputEventPlugin".split(" ")),B(),h=An,m=jn,g=Cn,Y({SimpleEventPlugin:io,EnterLeaveEventPlugin:Mr,ChangeEventPlugin:_r,SelectEventPlugin:Yr,BeforeInputEventPlugin:sr});var ao=[],so=-1;function lo(e){0>so||(e.current=ao[so],ao[so]=null,so--)}function co(e,t){so++,ao[so]=e.current,e.current=t}var uo={},po={current:uo},fo={current:!1},ho=uo;function mo(e,t){var n=e.type.contextTypes;if(!n)return uo;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var o,i={};for(o in n)i[o]=t[o];return r&&((e=e.stateNode).__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=i),i}function go(e){return null!=(e=e.childContextTypes)}function yo(){lo(fo),lo(po)}function vo(e,t,n){if(po.current!==uo)throw Error(a(168));co(po,t),co(fo,n)}function bo(e,t,n){var r=e.stateNode;if(e=t.childContextTypes,"function"!=typeof r.getChildContext)return n;for(var i in r=r.getChildContext())if(!(i in e))throw Error(a(108,D(t)||"Unknown",i));return o({},n,{},r)}function xo(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||uo,ho=po.current,co(po,e),co(fo,fo.current),!0}function wo(e,t,n){var r=e.stateNode;if(!r)throw Error(a(169));n?(e=bo(e,t,ho),r.__reactInternalMemoizedMergedChildContext=e,lo(fo),lo(po),co(po,e)):lo(fo),co(fo,n)}var ko=i.unstable_runWithPriority,Oo=i.unstable_scheduleCallback,_o=i.unstable_cancelCallback,Eo=i.unstable_requestPaint,So=i.unstable_now,To=i.unstable_getCurrentPriorityLevel,jo=i.unstable_ImmediatePriority,Co=i.unstable_UserBlockingPriority,Ao=i.unstable_NormalPriority,Io=i.unstable_LowPriority,Po=i.unstable_IdlePriority,Ro={},No=i.unstable_shouldYield,Lo=void 0!==Eo?Eo:function(){},Mo=null,Do=null,Fo=!1,zo=So(),Uo=1e4>zo?So:function(){return So()-zo};function Bo(){switch(To()){case jo:return 99;case Co:return 98;case Ao:return 97;case Io:return 96;case Po:return 95;default:throw Error(a(332))}}function $o(e){switch(e){case 99:return jo;case 98:return Co;case 97:return Ao;case 96:return Io;case 95:return Po;default:throw Error(a(332))}}function qo(e,t){return e=$o(e),ko(e,t)}function Wo(e,t,n){return e=$o(e),Oo(e,t,n)}function Ho(e){return null===Mo?(Mo=[e],Do=Oo(jo,Yo)):Mo.push(e),Ro}function Vo(){if(null!==Do){var e=Do;Do=null,_o(e)}Yo()}function Yo(){if(!Fo&&null!==Mo){Fo=!0;var e=0;try{var t=Mo;qo(99,(function(){for(;e<t.length;e++){var n=t[e];do{n=n(!0)}while(null!==n)}})),Mo=null}catch(t){throw null!==Mo&&(Mo=Mo.slice(e+1)),Oo(jo,Vo),t}finally{Fo=!1}}}function Qo(e,t,n){return 1073741821-(1+((1073741821-e+t/10)/(n/=10)|0))*n}function Go(e,t){if(e&&e.defaultProps)for(var n in t=o({},t),e=e.defaultProps)void 0===t[n]&&(t[n]=e[n]);return t}var Xo={current:null},Ko=null,Zo=null,Jo=null;function ei(){Jo=Zo=Ko=null}function ti(e){var t=Xo.current;lo(Xo),e.type._context._currentValue=t}function ni(e,t){for(;null!==e;){var n=e.alternate;if(e.childExpirationTime<t)e.childExpirationTime=t,null!==n&&n.childExpirationTime<t&&(n.childExpirationTime=t);else{if(!(null!==n&&n.childExpirationTime<t))break;n.childExpirationTime=t}e=e.return}}function ri(e,t){Ko=e,Jo=Zo=null,null!==(e=e.dependencies)&&null!==e.firstContext&&(e.expirationTime>=t&&(Aa=!0),e.firstContext=null)}function oi(e,t){if(Jo!==e&&!1!==t&&0!==t)if("number"==typeof t&&1073741823!==t||(Jo=e,t=1073741823),t={context:e,observedBits:t,next:null},null===Zo){if(null===Ko)throw Error(a(308));Zo=t,Ko.dependencies={expirationTime:0,firstContext:t,responders:null}}else Zo=Zo.next=t;return e._currentValue}var ii=!1;function ai(e){e.updateQueue={baseState:e.memoizedState,baseQueue:null,shared:{pending:null},effects:null}}function si(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,baseQueue:e.baseQueue,shared:e.shared,effects:e.effects})}function li(e,t){return(e={expirationTime:e,suspenseConfig:t,tag:0,payload:null,callback:null,next:null}).next=e}function ci(e,t){if(null!==(e=e.updateQueue)){var n=(e=e.shared).pending;null===n?t.next=t:(t.next=n.next,n.next=t),e.pending=t}}function ui(e,t){var n=e.alternate;null!==n&&si(n,e),null===(n=(e=e.updateQueue).baseQueue)?(e.baseQueue=t.next=t,t.next=t):(t.next=n.next,n.next=t)}function pi(e,t,n,r){var i=e.updateQueue;ii=!1;var a=i.baseQueue,s=i.shared.pending;if(null!==s){if(null!==a){var l=a.next;a.next=s.next,s.next=l}a=s,i.shared.pending=null,null!==(l=e.alternate)&&(null!==(l=l.updateQueue)&&(l.baseQueue=s))}if(null!==a){l=a.next;var c=i.baseState,u=0,p=null,f=null,d=null;if(null!==l)for(var h=l;;){if((s=h.expirationTime)<r){var m={expirationTime:h.expirationTime,suspenseConfig:h.suspenseConfig,tag:h.tag,payload:h.payload,callback:h.callback,next:null};null===d?(f=d=m,p=c):d=d.next=m,s>u&&(u=s)}else{null!==d&&(d=d.next={expirationTime:1073741823,suspenseConfig:h.suspenseConfig,tag:h.tag,payload:h.payload,callback:h.callback,next:null}),il(s,h.suspenseConfig);e:{var g=e,y=h;switch(s=t,m=n,y.tag){case 1:if("function"==typeof(g=y.payload)){c=g.call(m,c,s);break e}c=g;break e;case 3:g.effectTag=-4097&g.effectTag|64;case 0:if(null==(s="function"==typeof(g=y.payload)?g.call(m,c,s):g))break e;c=o({},c,s);break e;case 2:ii=!0}}null!==h.callback&&(e.effectTag|=32,null===(s=i.effects)?i.effects=[h]:s.push(h))}if(null===(h=h.next)||h===l){if(null===(s=i.shared.pending))break;h=a.next=s.next,s.next=l,i.baseQueue=a=s,i.shared.pending=null}}null===d?p=c:d.next=f,i.baseState=p,i.baseQueue=d,al(u),e.expirationTime=u,e.memoizedState=c}}function fi(e,t,n){if(e=t.effects,t.effects=null,null!==e)for(t=0;t<e.length;t++){var r=e[t],o=r.callback;if(null!==o){if(r.callback=null,r=o,o=n,"function"!=typeof r)throw Error(a(191,r));r.call(o)}}}var di=v.ReactCurrentBatchConfig,hi=(new r.Component).refs;function mi(e,t,n,r){n=null==(n=n(r,t=e.memoizedState))?t:o({},t,n),e.memoizedState=n,0===e.expirationTime&&(e.updateQueue.baseState=n)}var gi={isMounted:function(e){return!!(e=e._reactInternalFiber)&&Je(e)===e},enqueueSetState:function(e,t,n){e=e._reactInternalFiber;var r=Vs(),o=di.suspense;(o=li(r=Ys(r,e,o),o)).payload=t,null!=n&&(o.callback=n),ci(e,o),Qs(e,r)},enqueueReplaceState:function(e,t,n){e=e._reactInternalFiber;var r=Vs(),o=di.suspense;(o=li(r=Ys(r,e,o),o)).tag=1,o.payload=t,null!=n&&(o.callback=n),ci(e,o),Qs(e,r)},enqueueForceUpdate:function(e,t){e=e._reactInternalFiber;var n=Vs(),r=di.suspense;(r=li(n=Ys(n,e,r),r)).tag=2,null!=t&&(r.callback=t),ci(e,r),Qs(e,n)}};function yi(e,t,n,r,o,i,a){return"function"==typeof(e=e.stateNode).shouldComponentUpdate?e.shouldComponentUpdate(r,i,a):!t.prototype||!t.prototype.isPureReactComponent||(!zr(n,r)||!zr(o,i))}function vi(e,t,n){var r=!1,o=uo,i=t.contextType;return"object"==typeof i&&null!==i?i=oi(i):(o=go(t)?ho:po.current,i=(r=null!=(r=t.contextTypes))?mo(e,o):uo),t=new t(n,i),e.memoizedState=null!==t.state&&void 0!==t.state?t.state:null,t.updater=gi,e.stateNode=t,t._reactInternalFiber=e,r&&((e=e.stateNode).__reactInternalMemoizedUnmaskedChildContext=o,e.__reactInternalMemoizedMaskedChildContext=i),t}function bi(e,t,n,r){e=t.state,"function"==typeof t.componentWillReceiveProps&&t.componentWillReceiveProps(n,r),"function"==typeof t.UNSAFE_componentWillReceiveProps&&t.UNSAFE_componentWillReceiveProps(n,r),t.state!==e&&gi.enqueueReplaceState(t,t.state,null)}function xi(e,t,n,r){var o=e.stateNode;o.props=n,o.state=e.memoizedState,o.refs=hi,ai(e);var i=t.contextType;"object"==typeof i&&null!==i?o.context=oi(i):(i=go(t)?ho:po.current,o.context=mo(e,i)),pi(e,n,o,r),o.state=e.memoizedState,"function"==typeof(i=t.getDerivedStateFromProps)&&(mi(e,t,i,n),o.state=e.memoizedState),"function"==typeof t.getDerivedStateFromProps||"function"==typeof o.getSnapshotBeforeUpdate||"function"!=typeof o.UNSAFE_componentWillMount&&"function"!=typeof o.componentWillMount||(t=o.state,"function"==typeof o.componentWillMount&&o.componentWillMount(),"function"==typeof o.UNSAFE_componentWillMount&&o.UNSAFE_componentWillMount(),t!==o.state&&gi.enqueueReplaceState(o,o.state,null),pi(e,n,o,r),o.state=e.memoizedState),"function"==typeof o.componentDidMount&&(e.effectTag|=4)}var wi=Array.isArray;function ki(e,t,n){if(null!==(e=n.ref)&&"function"!=typeof e&&"object"!=typeof e){if(n._owner){if(n=n._owner){if(1!==n.tag)throw Error(a(309));var r=n.stateNode}if(!r)throw Error(a(147,e));var o=""+e;return null!==t&&null!==t.ref&&"function"==typeof t.ref&&t.ref._stringRef===o?t.ref:((t=function(e){var t=r.refs;t===hi&&(t=r.refs={}),null===e?delete t[o]:t[o]=e})._stringRef=o,t)}if("string"!=typeof e)throw Error(a(284));if(!n._owner)throw Error(a(290,e))}return e}function Oi(e,t){if("textarea"!==e.type)throw Error(a(31,"[object Object]"===Object.prototype.toString.call(t)?"object with keys {"+Object.keys(t).join(", ")+"}":t,""))}function _i(e){function t(t,n){if(e){var r=t.lastEffect;null!==r?(r.nextEffect=n,t.lastEffect=n):t.firstEffect=t.lastEffect=n,n.nextEffect=null,n.effectTag=8}}function n(n,r){if(!e)return null;for(;null!==r;)t(n,r),r=r.sibling;return null}function r(e,t){for(e=new Map;null!==t;)null!==t.key?e.set(t.key,t):e.set(t.index,t),t=t.sibling;return e}function o(e,t){return(e=Sl(e,t)).index=0,e.sibling=null,e}function i(t,n,r){return t.index=r,e?null!==(r=t.alternate)?(r=r.index)<n?(t.effectTag=2,n):r:(t.effectTag=2,n):n}function s(t){return e&&null===t.alternate&&(t.effectTag=2),t}function l(e,t,n,r){return null===t||6!==t.tag?((t=Cl(n,e.mode,r)).return=e,t):((t=o(t,n)).return=e,t)}function c(e,t,n,r){return null!==t&&t.elementType===n.type?((r=o(t,n.props)).ref=ki(e,t,n),r.return=e,r):((r=Tl(n.type,n.key,n.props,null,e.mode,r)).ref=ki(e,t,n),r.return=e,r)}function u(e,t,n,r){return null===t||4!==t.tag||t.stateNode.containerInfo!==n.containerInfo||t.stateNode.implementation!==n.implementation?((t=Al(n,e.mode,r)).return=e,t):((t=o(t,n.children||[])).return=e,t)}function p(e,t,n,r,i){return null===t||7!==t.tag?((t=jl(n,e.mode,r,i)).return=e,t):((t=o(t,n)).return=e,t)}function f(e,t,n){if("string"==typeof t||"number"==typeof t)return(t=Cl(""+t,e.mode,n)).return=e,t;if("object"==typeof t&&null!==t){switch(t.$$typeof){case w:return(n=Tl(t.type,t.key,t.props,null,e.mode,n)).ref=ki(e,null,t),n.return=e,n;case k:return(t=Al(t,e.mode,n)).return=e,t}if(wi(t)||M(t))return(t=jl(t,e.mode,n,null)).return=e,t;Oi(e,t)}return null}function d(e,t,n,r){var o=null!==t?t.key:null;if("string"==typeof n||"number"==typeof n)return null!==o?null:l(e,t,""+n,r);if("object"==typeof n&&null!==n){switch(n.$$typeof){case w:return n.key===o?n.type===O?p(e,t,n.props.children,r,o):c(e,t,n,r):null;case k:return n.key===o?u(e,t,n,r):null}if(wi(n)||M(n))return null!==o?null:p(e,t,n,r,null);Oi(e,n)}return null}function h(e,t,n,r,o){if("string"==typeof r||"number"==typeof r)return l(t,e=e.get(n)||null,""+r,o);if("object"==typeof r&&null!==r){switch(r.$$typeof){case w:return e=e.get(null===r.key?n:r.key)||null,r.type===O?p(t,e,r.props.children,o,r.key):c(t,e,r,o);case k:return u(t,e=e.get(null===r.key?n:r.key)||null,r,o)}if(wi(r)||M(r))return p(t,e=e.get(n)||null,r,o,null);Oi(t,r)}return null}function m(o,a,s,l){for(var c=null,u=null,p=a,m=a=0,g=null;null!==p&&m<s.length;m++){p.index>m?(g=p,p=null):g=p.sibling;var y=d(o,p,s[m],l);if(null===y){null===p&&(p=g);break}e&&p&&null===y.alternate&&t(o,p),a=i(y,a,m),null===u?c=y:u.sibling=y,u=y,p=g}if(m===s.length)return n(o,p),c;if(null===p){for(;m<s.length;m++)null!==(p=f(o,s[m],l))&&(a=i(p,a,m),null===u?c=p:u.sibling=p,u=p);return c}for(p=r(o,p);m<s.length;m++)null!==(g=h(p,o,m,s[m],l))&&(e&&null!==g.alternate&&p.delete(null===g.key?m:g.key),a=i(g,a,m),null===u?c=g:u.sibling=g,u=g);return e&&p.forEach((function(e){return t(o,e)})),c}function g(o,s,l,c){var u=M(l);if("function"!=typeof u)throw Error(a(150));if(null==(l=u.call(l)))throw Error(a(151));for(var p=u=null,m=s,g=s=0,y=null,v=l.next();null!==m&&!v.done;g++,v=l.next()){m.index>g?(y=m,m=null):y=m.sibling;var b=d(o,m,v.value,c);if(null===b){null===m&&(m=y);break}e&&m&&null===b.alternate&&t(o,m),s=i(b,s,g),null===p?u=b:p.sibling=b,p=b,m=y}if(v.done)return n(o,m),u;if(null===m){for(;!v.done;g++,v=l.next())null!==(v=f(o,v.value,c))&&(s=i(v,s,g),null===p?u=v:p.sibling=v,p=v);return u}for(m=r(o,m);!v.done;g++,v=l.next())null!==(v=h(m,o,g,v.value,c))&&(e&&null!==v.alternate&&m.delete(null===v.key?g:v.key),s=i(v,s,g),null===p?u=v:p.sibling=v,p=v);return e&&m.forEach((function(e){return t(o,e)})),u}return function(e,r,i,l){var c="object"==typeof i&&null!==i&&i.type===O&&null===i.key;c&&(i=i.props.children);var u="object"==typeof i&&null!==i;if(u)switch(i.$$typeof){case w:e:{for(u=i.key,c=r;null!==c;){if(c.key===u){switch(c.tag){case 7:if(i.type===O){n(e,c.sibling),(r=o(c,i.props.children)).return=e,e=r;break e}break;default:if(c.elementType===i.type){n(e,c.sibling),(r=o(c,i.props)).ref=ki(e,c,i),r.return=e,e=r;break e}}n(e,c);break}t(e,c),c=c.sibling}i.type===O?((r=jl(i.props.children,e.mode,l,i.key)).return=e,e=r):((l=Tl(i.type,i.key,i.props,null,e.mode,l)).ref=ki(e,r,i),l.return=e,e=l)}return s(e);case k:e:{for(c=i.key;null!==r;){if(r.key===c){if(4===r.tag&&r.stateNode.containerInfo===i.containerInfo&&r.stateNode.implementation===i.implementation){n(e,r.sibling),(r=o(r,i.children||[])).return=e,e=r;break e}n(e,r);break}t(e,r),r=r.sibling}(r=Al(i,e.mode,l)).return=e,e=r}return s(e)}if("string"==typeof i||"number"==typeof i)return i=""+i,null!==r&&6===r.tag?(n(e,r.sibling),(r=o(r,i)).return=e,e=r):(n(e,r),(r=Cl(i,e.mode,l)).return=e,e=r),s(e);if(wi(i))return m(e,r,i,l);if(M(i))return g(e,r,i,l);if(u&&Oi(e,i),void 0===i&&!c)switch(e.tag){case 1:case 0:throw e=e.type,Error(a(152,e.displayName||e.name||"Component"))}return n(e,r)}}var Ei=_i(!0),Si=_i(!1),Ti={},ji={current:Ti},Ci={current:Ti},Ai={current:Ti};function Ii(e){if(e===Ti)throw Error(a(174));return e}function Pi(e,t){switch(co(Ai,t),co(Ci,e),co(ji,Ti),e=t.nodeType){case 9:case 11:t=(t=t.documentElement)?t.namespaceURI:De(null,"");break;default:t=De(t=(e=8===e?t.parentNode:t).namespaceURI||null,e=e.tagName)}lo(ji),co(ji,t)}function Ri(){lo(ji),lo(Ci),lo(Ai)}function Ni(e){Ii(Ai.current);var t=Ii(ji.current),n=De(t,e.type);t!==n&&(co(Ci,e),co(ji,n))}function Li(e){Ci.current===e&&(lo(ji),lo(Ci))}var Mi={current:0};function Di(e){for(var t=e;null!==t;){if(13===t.tag){var n=t.memoizedState;if(null!==n&&(null===(n=n.dehydrated)||"$?"===n.data||"$!"===n.data))return t}else if(19===t.tag&&void 0!==t.memoizedProps.revealOrder){if(0!=(64&t.effectTag))return t}else if(null!==t.child){t.child.return=t,t=t.child;continue}if(t===e)break;for(;null===t.sibling;){if(null===t.return||t.return===e)return null;t=t.return}t.sibling.return=t.return,t=t.sibling}return null}function Fi(e,t){return{responder:e,props:t}}var zi=v.ReactCurrentDispatcher,Ui=v.ReactCurrentBatchConfig,Bi=0,$i=null,qi=null,Wi=null,Hi=!1;function Vi(){throw Error(a(321))}function Yi(e,t){if(null===t)return!1;for(var n=0;n<t.length&&n<e.length;n++)if(!Dr(e[n],t[n]))return!1;return!0}function Qi(e,t,n,r,o,i){if(Bi=i,$i=t,t.memoizedState=null,t.updateQueue=null,t.expirationTime=0,zi.current=null===e||null===e.memoizedState?ya:va,e=n(r,o),t.expirationTime===Bi){i=0;do{if(t.expirationTime=0,!(25>i))throw Error(a(301));i+=1,Wi=qi=null,t.updateQueue=null,zi.current=ba,e=n(r,o)}while(t.expirationTime===Bi)}if(zi.current=ga,t=null!==qi&&null!==qi.next,Bi=0,Wi=qi=$i=null,Hi=!1,t)throw Error(a(300));return e}function Gi(){var e={memoizedState:null,baseState:null,baseQueue:null,queue:null,next:null};return null===Wi?$i.memoizedState=Wi=e:Wi=Wi.next=e,Wi}function Xi(){if(null===qi){var e=$i.alternate;e=null!==e?e.memoizedState:null}else e=qi.next;var t=null===Wi?$i.memoizedState:Wi.next;if(null!==t)Wi=t,qi=e;else{if(null===e)throw Error(a(310));e={memoizedState:(qi=e).memoizedState,baseState:qi.baseState,baseQueue:qi.baseQueue,queue:qi.queue,next:null},null===Wi?$i.memoizedState=Wi=e:Wi=Wi.next=e}return Wi}function Ki(e,t){return"function"==typeof t?t(e):t}function Zi(e){var t=Xi(),n=t.queue;if(null===n)throw Error(a(311));n.lastRenderedReducer=e;var r=qi,o=r.baseQueue,i=n.pending;if(null!==i){if(null!==o){var s=o.next;o.next=i.next,i.next=s}r.baseQueue=o=i,n.pending=null}if(null!==o){o=o.next,r=r.baseState;var l=s=i=null,c=o;do{var u=c.expirationTime;if(u<Bi){var p={expirationTime:c.expirationTime,suspenseConfig:c.suspenseConfig,action:c.action,eagerReducer:c.eagerReducer,eagerState:c.eagerState,next:null};null===l?(s=l=p,i=r):l=l.next=p,u>$i.expirationTime&&($i.expirationTime=u,al(u))}else null!==l&&(l=l.next={expirationTime:1073741823,suspenseConfig:c.suspenseConfig,action:c.action,eagerReducer:c.eagerReducer,eagerState:c.eagerState,next:null}),il(u,c.suspenseConfig),r=c.eagerReducer===e?c.eagerState:e(r,c.action);c=c.next}while(null!==c&&c!==o);null===l?i=r:l.next=s,Dr(r,t.memoizedState)||(Aa=!0),t.memoizedState=r,t.baseState=i,t.baseQueue=l,n.lastRenderedState=r}return[t.memoizedState,n.dispatch]}function Ji(e){var t=Xi(),n=t.queue;if(null===n)throw Error(a(311));n.lastRenderedReducer=e;var r=n.dispatch,o=n.pending,i=t.memoizedState;if(null!==o){n.pending=null;var s=o=o.next;do{i=e(i,s.action),s=s.next}while(s!==o);Dr(i,t.memoizedState)||(Aa=!0),t.memoizedState=i,null===t.baseQueue&&(t.baseState=i),n.lastRenderedState=i}return[i,r]}function ea(e){var t=Gi();return"function"==typeof e&&(e=e()),t.memoizedState=t.baseState=e,e=(e=t.queue={pending:null,dispatch:null,lastRenderedReducer:Ki,lastRenderedState:e}).dispatch=ma.bind(null,$i,e),[t.memoizedState,e]}function ta(e,t,n,r){return e={tag:e,create:t,destroy:n,deps:r,next:null},null===(t=$i.updateQueue)?(t={lastEffect:null},$i.updateQueue=t,t.lastEffect=e.next=e):null===(n=t.lastEffect)?t.lastEffect=e.next=e:(r=n.next,n.next=e,e.next=r,t.lastEffect=e),e}function na(){return Xi().memoizedState}function ra(e,t,n,r){var o=Gi();$i.effectTag|=e,o.memoizedState=ta(1|t,n,void 0,void 0===r?null:r)}function oa(e,t,n,r){var o=Xi();r=void 0===r?null:r;var i=void 0;if(null!==qi){var a=qi.memoizedState;if(i=a.destroy,null!==r&&Yi(r,a.deps))return void ta(t,n,i,r)}$i.effectTag|=e,o.memoizedState=ta(1|t,n,i,r)}function ia(e,t){return ra(516,4,e,t)}function aa(e,t){return oa(516,4,e,t)}function sa(e,t){return oa(4,2,e,t)}function la(e,t){return"function"==typeof t?(e=e(),t(e),function(){t(null)}):null!=t?(e=e(),t.current=e,function(){t.current=null}):void 0}function ca(e,t,n){return n=null!=n?n.concat([e]):null,oa(4,2,la.bind(null,t,e),n)}function ua(){}function pa(e,t){return Gi().memoizedState=[e,void 0===t?null:t],e}function fa(e,t){var n=Xi();t=void 0===t?null:t;var r=n.memoizedState;return null!==r&&null!==t&&Yi(t,r[1])?r[0]:(n.memoizedState=[e,t],e)}function da(e,t){var n=Xi();t=void 0===t?null:t;var r=n.memoizedState;return null!==r&&null!==t&&Yi(t,r[1])?r[0]:(e=e(),n.memoizedState=[e,t],e)}function ha(e,t,n){var r=Bo();qo(98>r?98:r,(function(){e(!0)})),qo(97<r?97:r,(function(){var r=Ui.suspense;Ui.suspense=void 0===t?null:t;try{e(!1),n()}finally{Ui.suspense=r}}))}function ma(e,t,n){var r=Vs(),o=di.suspense;o={expirationTime:r=Ys(r,e,o),suspenseConfig:o,action:n,eagerReducer:null,eagerState:null,next:null};var i=t.pending;if(null===i?o.next=o:(o.next=i.next,i.next=o),t.pending=o,i=e.alternate,e===$i||null!==i&&i===$i)Hi=!0,o.expirationTime=Bi,$i.expirationTime=Bi;else{if(0===e.expirationTime&&(null===i||0===i.expirationTime)&&null!==(i=t.lastRenderedReducer))try{var a=t.lastRenderedState,s=i(a,n);if(o.eagerReducer=i,o.eagerState=s,Dr(s,a))return}catch(e){}Qs(e,r)}}var ga={readContext:oi,useCallback:Vi,useContext:Vi,useEffect:Vi,useImperativeHandle:Vi,useLayoutEffect:Vi,useMemo:Vi,useReducer:Vi,useRef:Vi,useState:Vi,useDebugValue:Vi,useResponder:Vi,useDeferredValue:Vi,useTransition:Vi},ya={readContext:oi,useCallback:pa,useContext:oi,useEffect:ia,useImperativeHandle:function(e,t,n){return n=null!=n?n.concat([e]):null,ra(4,2,la.bind(null,t,e),n)},useLayoutEffect:function(e,t){return ra(4,2,e,t)},useMemo:function(e,t){var n=Gi();return t=void 0===t?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=Gi();return t=void 0!==n?n(t):t,r.memoizedState=r.baseState=t,e=(e=r.queue={pending:null,dispatch:null,lastRenderedReducer:e,lastRenderedState:t}).dispatch=ma.bind(null,$i,e),[r.memoizedState,e]},useRef:function(e){return e={current:e},Gi().memoizedState=e},useState:ea,useDebugValue:ua,useResponder:Fi,useDeferredValue:function(e,t){var n=ea(e),r=n[0],o=n[1];return ia((function(){var n=Ui.suspense;Ui.suspense=void 0===t?null:t;try{o(e)}finally{Ui.suspense=n}}),[e,t]),r},useTransition:function(e){var t=ea(!1),n=t[0];return t=t[1],[pa(ha.bind(null,t,e),[t,e]),n]}},va={readContext:oi,useCallback:fa,useContext:oi,useEffect:aa,useImperativeHandle:ca,useLayoutEffect:sa,useMemo:da,useReducer:Zi,useRef:na,useState:function(){return Zi(Ki)},useDebugValue:ua,useResponder:Fi,useDeferredValue:function(e,t){var n=Zi(Ki),r=n[0],o=n[1];return aa((function(){var n=Ui.suspense;Ui.suspense=void 0===t?null:t;try{o(e)}finally{Ui.suspense=n}}),[e,t]),r},useTransition:function(e){var t=Zi(Ki),n=t[0];return t=t[1],[fa(ha.bind(null,t,e),[t,e]),n]}},ba={readContext:oi,useCallback:fa,useContext:oi,useEffect:aa,useImperativeHandle:ca,useLayoutEffect:sa,useMemo:da,useReducer:Ji,useRef:na,useState:function(){return Ji(Ki)},useDebugValue:ua,useResponder:Fi,useDeferredValue:function(e,t){var n=Ji(Ki),r=n[0],o=n[1];return aa((function(){var n=Ui.suspense;Ui.suspense=void 0===t?null:t;try{o(e)}finally{Ui.suspense=n}}),[e,t]),r},useTransition:function(e){var t=Ji(Ki),n=t[0];return t=t[1],[fa(ha.bind(null,t,e),[t,e]),n]}},xa=null,wa=null,ka=!1;function Oa(e,t){var n=_l(5,null,null,0);n.elementType="DELETED",n.type="DELETED",n.stateNode=t,n.return=e,n.effectTag=8,null!==e.lastEffect?(e.lastEffect.nextEffect=n,e.lastEffect=n):e.firstEffect=e.lastEffect=n}function _a(e,t){switch(e.tag){case 5:var n=e.type;return null!==(t=1!==t.nodeType||n.toLowerCase()!==t.nodeName.toLowerCase()?null:t)&&(e.stateNode=t,!0);case 6:return null!==(t=""===e.pendingProps||3!==t.nodeType?null:t)&&(e.stateNode=t,!0);case 13:default:return!1}}function Ea(e){if(ka){var t=wa;if(t){var n=t;if(!_a(e,t)){if(!(t=wn(n.nextSibling))||!_a(e,t))return e.effectTag=-1025&e.effectTag|2,ka=!1,void(xa=e);Oa(xa,n)}xa=e,wa=wn(t.firstChild)}else e.effectTag=-1025&e.effectTag|2,ka=!1,xa=e}}function Sa(e){for(e=e.return;null!==e&&5!==e.tag&&3!==e.tag&&13!==e.tag;)e=e.return;xa=e}function Ta(e){if(e!==xa)return!1;if(!ka)return Sa(e),ka=!0,!1;var t=e.type;if(5!==e.tag||"head"!==t&&"body"!==t&&!vn(t,e.memoizedProps))for(t=wa;t;)Oa(e,t),t=wn(t.nextSibling);if(Sa(e),13===e.tag){if(!(e=null!==(e=e.memoizedState)?e.dehydrated:null))throw Error(a(317));e:{for(e=e.nextSibling,t=0;e;){if(8===e.nodeType){var n=e.data;if("/$"===n){if(0===t){wa=wn(e.nextSibling);break e}t--}else"$"!==n&&"$!"!==n&&"$?"!==n||t++}e=e.nextSibling}wa=null}}else wa=xa?wn(e.stateNode.nextSibling):null;return!0}function ja(){wa=xa=null,ka=!1}var Ca=v.ReactCurrentOwner,Aa=!1;function Ia(e,t,n,r){t.child=null===e?Si(t,null,n,r):Ei(t,e.child,n,r)}function Pa(e,t,n,r,o){n=n.render;var i=t.ref;return ri(t,o),r=Qi(e,t,n,r,i,o),null===e||Aa?(t.effectTag|=1,Ia(e,t,r,o),t.child):(t.updateQueue=e.updateQueue,t.effectTag&=-517,e.expirationTime<=o&&(e.expirationTime=0),Qa(e,t,o))}function Ra(e,t,n,r,o,i){if(null===e){var a=n.type;return"function"!=typeof a||El(a)||void 0!==a.defaultProps||null!==n.compare||void 0!==n.defaultProps?((e=Tl(n.type,null,r,null,t.mode,i)).ref=t.ref,e.return=t,t.child=e):(t.tag=15,t.type=a,Na(e,t,a,r,o,i))}return a=e.child,o<i&&(o=a.memoizedProps,(n=null!==(n=n.compare)?n:zr)(o,r)&&e.ref===t.ref)?Qa(e,t,i):(t.effectTag|=1,(e=Sl(a,r)).ref=t.ref,e.return=t,t.child=e)}function Na(e,t,n,r,o,i){return null!==e&&zr(e.memoizedProps,r)&&e.ref===t.ref&&(Aa=!1,o<i)?(t.expirationTime=e.expirationTime,Qa(e,t,i)):Ma(e,t,n,r,i)}function La(e,t){var n=t.ref;(null===e&&null!==n||null!==e&&e.ref!==n)&&(t.effectTag|=128)}function Ma(e,t,n,r,o){var i=go(n)?ho:po.current;return i=mo(t,i),ri(t,o),n=Qi(e,t,n,r,i,o),null===e||Aa?(t.effectTag|=1,Ia(e,t,n,o),t.child):(t.updateQueue=e.updateQueue,t.effectTag&=-517,e.expirationTime<=o&&(e.expirationTime=0),Qa(e,t,o))}function Da(e,t,n,r,o){if(go(n)){var i=!0;xo(t)}else i=!1;if(ri(t,o),null===t.stateNode)null!==e&&(e.alternate=null,t.alternate=null,t.effectTag|=2),vi(t,n,r),xi(t,n,r,o),r=!0;else if(null===e){var a=t.stateNode,s=t.memoizedProps;a.props=s;var l=a.context,c=n.contextType;"object"==typeof c&&null!==c?c=oi(c):c=mo(t,c=go(n)?ho:po.current);var u=n.getDerivedStateFromProps,p="function"==typeof u||"function"==typeof a.getSnapshotBeforeUpdate;p||"function"!=typeof a.UNSAFE_componentWillReceiveProps&&"function"!=typeof a.componentWillReceiveProps||(s!==r||l!==c)&&bi(t,a,r,c),ii=!1;var f=t.memoizedState;a.state=f,pi(t,r,a,o),l=t.memoizedState,s!==r||f!==l||fo.current||ii?("function"==typeof u&&(mi(t,n,u,r),l=t.memoizedState),(s=ii||yi(t,n,s,r,f,l,c))?(p||"function"!=typeof a.UNSAFE_componentWillMount&&"function"!=typeof a.componentWillMount||("function"==typeof a.componentWillMount&&a.componentWillMount(),"function"==typeof a.UNSAFE_componentWillMount&&a.UNSAFE_componentWillMount()),"function"==typeof a.componentDidMount&&(t.effectTag|=4)):("function"==typeof a.componentDidMount&&(t.effectTag|=4),t.memoizedProps=r,t.memoizedState=l),a.props=r,a.state=l,a.context=c,r=s):("function"==typeof a.componentDidMount&&(t.effectTag|=4),r=!1)}else a=t.stateNode,si(e,t),s=t.memoizedProps,a.props=t.type===t.elementType?s:Go(t.type,s),l=a.context,"object"==typeof(c=n.contextType)&&null!==c?c=oi(c):c=mo(t,c=go(n)?ho:po.current),(p="function"==typeof(u=n.getDerivedStateFromProps)||"function"==typeof a.getSnapshotBeforeUpdate)||"function"!=typeof a.UNSAFE_componentWillReceiveProps&&"function"!=typeof a.componentWillReceiveProps||(s!==r||l!==c)&&bi(t,a,r,c),ii=!1,l=t.memoizedState,a.state=l,pi(t,r,a,o),f=t.memoizedState,s!==r||l!==f||fo.current||ii?("function"==typeof u&&(mi(t,n,u,r),f=t.memoizedState),(u=ii||yi(t,n,s,r,l,f,c))?(p||"function"!=typeof a.UNSAFE_componentWillUpdate&&"function"!=typeof a.componentWillUpdate||("function"==typeof a.componentWillUpdate&&a.componentWillUpdate(r,f,c),"function"==typeof a.UNSAFE_componentWillUpdate&&a.UNSAFE_componentWillUpdate(r,f,c)),"function"==typeof a.componentDidUpdate&&(t.effectTag|=4),"function"==typeof a.getSnapshotBeforeUpdate&&(t.effectTag|=256)):("function"!=typeof a.componentDidUpdate||s===e.memoizedProps&&l===e.memoizedState||(t.effectTag|=4),"function"!=typeof a.getSnapshotBeforeUpdate||s===e.memoizedProps&&l===e.memoizedState||(t.effectTag|=256),t.memoizedProps=r,t.memoizedState=f),a.props=r,a.state=f,a.context=c,r=u):("function"!=typeof a.componentDidUpdate||s===e.memoizedProps&&l===e.memoizedState||(t.effectTag|=4),"function"!=typeof a.getSnapshotBeforeUpdate||s===e.memoizedProps&&l===e.memoizedState||(t.effectTag|=256),r=!1);return Fa(e,t,n,r,i,o)}function Fa(e,t,n,r,o,i){La(e,t);var a=0!=(64&t.effectTag);if(!r&&!a)return o&&wo(t,n,!1),Qa(e,t,i);r=t.stateNode,Ca.current=t;var s=a&&"function"!=typeof n.getDerivedStateFromError?null:r.render();return t.effectTag|=1,null!==e&&a?(t.child=Ei(t,e.child,null,i),t.child=Ei(t,null,s,i)):Ia(e,t,s,i),t.memoizedState=r.state,o&&wo(t,n,!0),t.child}function za(e){var t=e.stateNode;t.pendingContext?vo(0,t.pendingContext,t.pendingContext!==t.context):t.context&&vo(0,t.context,!1),Pi(e,t.containerInfo)}var Ua,Ba,$a,qa={dehydrated:null,retryTime:0};function Wa(e,t,n){var r,o=t.mode,i=t.pendingProps,a=Mi.current,s=!1;if((r=0!=(64&t.effectTag))||(r=0!=(2&a)&&(null===e||null!==e.memoizedState)),r?(s=!0,t.effectTag&=-65):null!==e&&null===e.memoizedState||void 0===i.fallback||!0===i.unstable_avoidThisFallback||(a|=1),co(Mi,1&a),null===e){if(void 0!==i.fallback&&Ea(t),s){if(s=i.fallback,(i=jl(null,o,0,null)).return=t,0==(2&t.mode))for(e=null!==t.memoizedState?t.child.child:t.child,i.child=e;null!==e;)e.return=i,e=e.sibling;return(n=jl(s,o,n,null)).return=t,i.sibling=n,t.memoizedState=qa,t.child=i,n}return o=i.children,t.memoizedState=null,t.child=Si(t,null,o,n)}if(null!==e.memoizedState){if(o=(e=e.child).sibling,s){if(i=i.fallback,(n=Sl(e,e.pendingProps)).return=t,0==(2&t.mode)&&(s=null!==t.memoizedState?t.child.child:t.child)!==e.child)for(n.child=s;null!==s;)s.return=n,s=s.sibling;return(o=Sl(o,i)).return=t,n.sibling=o,n.childExpirationTime=0,t.memoizedState=qa,t.child=n,o}return n=Ei(t,e.child,i.children,n),t.memoizedState=null,t.child=n}if(e=e.child,s){if(s=i.fallback,(i=jl(null,o,0,null)).return=t,i.child=e,null!==e&&(e.return=i),0==(2&t.mode))for(e=null!==t.memoizedState?t.child.child:t.child,i.child=e;null!==e;)e.return=i,e=e.sibling;return(n=jl(s,o,n,null)).return=t,i.sibling=n,n.effectTag|=2,i.childExpirationTime=0,t.memoizedState=qa,t.child=i,n}return t.memoizedState=null,t.child=Ei(t,e,i.children,n)}function Ha(e,t){e.expirationTime<t&&(e.expirationTime=t);var n=e.alternate;null!==n&&n.expirationTime<t&&(n.expirationTime=t),ni(e.return,t)}function Va(e,t,n,r,o,i){var a=e.memoizedState;null===a?e.memoizedState={isBackwards:t,rendering:null,renderingStartTime:0,last:r,tail:n,tailExpiration:0,tailMode:o,lastEffect:i}:(a.isBackwards=t,a.rendering=null,a.renderingStartTime=0,a.last=r,a.tail=n,a.tailExpiration=0,a.tailMode=o,a.lastEffect=i)}function Ya(e,t,n){var r=t.pendingProps,o=r.revealOrder,i=r.tail;if(Ia(e,t,r.children,n),0!=(2&(r=Mi.current)))r=1&r|2,t.effectTag|=64;else{if(null!==e&&0!=(64&e.effectTag))e:for(e=t.child;null!==e;){if(13===e.tag)null!==e.memoizedState&&Ha(e,n);else if(19===e.tag)Ha(e,n);else if(null!==e.child){e.child.return=e,e=e.child;continue}if(e===t)break e;for(;null===e.sibling;){if(null===e.return||e.return===t)break e;e=e.return}e.sibling.return=e.return,e=e.sibling}r&=1}if(co(Mi,r),0==(2&t.mode))t.memoizedState=null;else switch(o){case"forwards":for(n=t.child,o=null;null!==n;)null!==(e=n.alternate)&&null===Di(e)&&(o=n),n=n.sibling;null===(n=o)?(o=t.child,t.child=null):(o=n.sibling,n.sibling=null),Va(t,!1,o,n,i,t.lastEffect);break;case"backwards":for(n=null,o=t.child,t.child=null;null!==o;){if(null!==(e=o.alternate)&&null===Di(e)){t.child=o;break}e=o.sibling,o.sibling=n,n=o,o=e}Va(t,!0,n,null,i,t.lastEffect);break;case"together":Va(t,!1,null,null,void 0,t.lastEffect);break;default:t.memoizedState=null}return t.child}function Qa(e,t,n){null!==e&&(t.dependencies=e.dependencies);var r=t.expirationTime;if(0!==r&&al(r),t.childExpirationTime<n)return null;if(null!==e&&t.child!==e.child)throw Error(a(153));if(null!==t.child){for(n=Sl(e=t.child,e.pendingProps),t.child=n,n.return=t;null!==e.sibling;)e=e.sibling,(n=n.sibling=Sl(e,e.pendingProps)).return=t;n.sibling=null}return t.child}function Ga(e,t){switch(e.tailMode){case"hidden":t=e.tail;for(var n=null;null!==t;)null!==t.alternate&&(n=t),t=t.sibling;null===n?e.tail=null:n.sibling=null;break;case"collapsed":n=e.tail;for(var r=null;null!==n;)null!==n.alternate&&(r=n),n=n.sibling;null===r?t||null===e.tail?e.tail=null:e.tail.sibling=null:r.sibling=null}}function Xa(e,t,n){var r=t.pendingProps;switch(t.tag){case 2:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return null;case 1:return go(t.type)&&yo(),null;case 3:return Ri(),lo(fo),lo(po),(n=t.stateNode).pendingContext&&(n.context=n.pendingContext,n.pendingContext=null),null!==e&&null!==e.child||!Ta(t)||(t.effectTag|=4),null;case 5:Li(t),n=Ii(Ai.current);var i=t.type;if(null!==e&&null!=t.stateNode)Ba(e,t,i,r,n),e.ref!==t.ref&&(t.effectTag|=128);else{if(!r){if(null===t.stateNode)throw Error(a(166));return null}if(e=Ii(ji.current),Ta(t)){r=t.stateNode,i=t.type;var s=t.memoizedProps;switch(r[_n]=t,r[En]=s,i){case"iframe":case"object":case"embed":Yt("load",r);break;case"video":case"audio":for(e=0;e<Xe.length;e++)Yt(Xe[e],r);break;case"source":Yt("error",r);break;case"img":case"image":case"link":Yt("error",r),Yt("load",r);break;case"form":Yt("reset",r),Yt("submit",r);break;case"details":Yt("toggle",r);break;case"input":Oe(r,s),Yt("invalid",r),ln(n,"onChange");break;case"select":r._wrapperState={wasMultiple:!!s.multiple},Yt("invalid",r),ln(n,"onChange");break;case"textarea":Ie(r,s),Yt("invalid",r),ln(n,"onChange")}for(var l in on(i,s),e=null,s)if(s.hasOwnProperty(l)){var c=s[l];"children"===l?"string"==typeof c?r.textContent!==c&&(e=["children",c]):"number"==typeof c&&r.textContent!==""+c&&(e=["children",""+c]):H.hasOwnProperty(l)&&null!=c&&ln(n,l)}switch(i){case"input":xe(r),Se(r,s,!0);break;case"textarea":xe(r),Re(r);break;case"select":case"option":break;default:"function"==typeof s.onClick&&(r.onclick=cn)}n=e,t.updateQueue=n,null!==n&&(t.effectTag|=4)}else{switch(l=9===n.nodeType?n:n.ownerDocument,e===sn&&(e=Me(i)),e===sn?"script"===i?((e=l.createElement("div")).innerHTML="<script><\/script>",e=e.removeChild(e.firstChild)):"string"==typeof r.is?e=l.createElement(i,{is:r.is}):(e=l.createElement(i),"select"===i&&(l=e,r.multiple?l.multiple=!0:r.size&&(l.size=r.size))):e=l.createElementNS(e,i),e[_n]=t,e[En]=r,Ua(e,t),t.stateNode=e,l=an(i,r),i){case"iframe":case"object":case"embed":Yt("load",e),c=r;break;case"video":case"audio":for(c=0;c<Xe.length;c++)Yt(Xe[c],e);c=r;break;case"source":Yt("error",e),c=r;break;case"img":case"image":case"link":Yt("error",e),Yt("load",e),c=r;break;case"form":Yt("reset",e),Yt("submit",e),c=r;break;case"details":Yt("toggle",e),c=r;break;case"input":Oe(e,r),c=ke(e,r),Yt("invalid",e),ln(n,"onChange");break;case"option":c=je(e,r);break;case"select":e._wrapperState={wasMultiple:!!r.multiple},c=o({},r,{value:void 0}),Yt("invalid",e),ln(n,"onChange");break;case"textarea":Ie(e,r),c=Ae(e,r),Yt("invalid",e),ln(n,"onChange");break;default:c=r}on(i,c);var u=c;for(s in u)if(u.hasOwnProperty(s)){var p=u[s];"style"===s?nn(e,p):"dangerouslySetInnerHTML"===s?null!=(p=p?p.__html:void 0)&&ze(e,p):"children"===s?"string"==typeof p?("textarea"!==i||""!==p)&&Ue(e,p):"number"==typeof p&&Ue(e,""+p):"suppressContentEditableWarning"!==s&&"suppressHydrationWarning"!==s&&"autoFocus"!==s&&(H.hasOwnProperty(s)?null!=p&&ln(n,s):null!=p&&ye(e,s,p,l))}switch(i){case"input":xe(e),Se(e,r,!1);break;case"textarea":xe(e),Re(e);break;case"option":null!=r.value&&e.setAttribute("value",""+ve(r.value));break;case"select":e.multiple=!!r.multiple,null!=(n=r.value)?Ce(e,!!r.multiple,n,!1):null!=r.defaultValue&&Ce(e,!!r.multiple,r.defaultValue,!0);break;default:"function"==typeof c.onClick&&(e.onclick=cn)}yn(i,r)&&(t.effectTag|=4)}null!==t.ref&&(t.effectTag|=128)}return null;case 6:if(e&&null!=t.stateNode)$a(0,t,e.memoizedProps,r);else{if("string"!=typeof r&&null===t.stateNode)throw Error(a(166));n=Ii(Ai.current),Ii(ji.current),Ta(t)?(n=t.stateNode,r=t.memoizedProps,n[_n]=t,n.nodeValue!==r&&(t.effectTag|=4)):((n=(9===n.nodeType?n:n.ownerDocument).createTextNode(r))[_n]=t,t.stateNode=n)}return null;case 13:return lo(Mi),r=t.memoizedState,0!=(64&t.effectTag)?(t.expirationTime=n,t):(n=null!==r,r=!1,null===e?void 0!==t.memoizedProps.fallback&&Ta(t):(r=null!==(i=e.memoizedState),n||null===i||null!==(i=e.child.sibling)&&(null!==(s=t.firstEffect)?(t.firstEffect=i,i.nextEffect=s):(t.firstEffect=t.lastEffect=i,i.nextEffect=null),i.effectTag=8)),n&&!r&&0!=(2&t.mode)&&(null===e&&!0!==t.memoizedProps.unstable_avoidThisFallback||0!=(1&Mi.current)?Ts===xs&&(Ts=ws):(Ts!==xs&&Ts!==ws||(Ts=ks),0!==Ps&&null!==_s&&(Rl(_s,Ss),Nl(_s,Ps)))),(n||r)&&(t.effectTag|=4),null);case 4:return Ri(),null;case 10:return ti(t),null;case 17:return go(t.type)&&yo(),null;case 19:if(lo(Mi),null===(r=t.memoizedState))return null;if(i=0!=(64&t.effectTag),null===(s=r.rendering)){if(i)Ga(r,!1);else if(Ts!==xs||null!==e&&0!=(64&e.effectTag))for(s=t.child;null!==s;){if(null!==(e=Di(s))){for(t.effectTag|=64,Ga(r,!1),null!==(i=e.updateQueue)&&(t.updateQueue=i,t.effectTag|=4),null===r.lastEffect&&(t.firstEffect=null),t.lastEffect=r.lastEffect,r=t.child;null!==r;)s=n,(i=r).effectTag&=2,i.nextEffect=null,i.firstEffect=null,i.lastEffect=null,null===(e=i.alternate)?(i.childExpirationTime=0,i.expirationTime=s,i.child=null,i.memoizedProps=null,i.memoizedState=null,i.updateQueue=null,i.dependencies=null):(i.childExpirationTime=e.childExpirationTime,i.expirationTime=e.expirationTime,i.child=e.child,i.memoizedProps=e.memoizedProps,i.memoizedState=e.memoizedState,i.updateQueue=e.updateQueue,s=e.dependencies,i.dependencies=null===s?null:{expirationTime:s.expirationTime,firstContext:s.firstContext,responders:s.responders}),r=r.sibling;return co(Mi,1&Mi.current|2),t.child}s=s.sibling}}else{if(!i)if(null!==(e=Di(s))){if(t.effectTag|=64,i=!0,null!==(n=e.updateQueue)&&(t.updateQueue=n,t.effectTag|=4),Ga(r,!0),null===r.tail&&"hidden"===r.tailMode&&!s.alternate)return null!==(t=t.lastEffect=r.lastEffect)&&(t.nextEffect=null),null}else 2*Uo()-r.renderingStartTime>r.tailExpiration&&1<n&&(t.effectTag|=64,i=!0,Ga(r,!1),t.expirationTime=t.childExpirationTime=n-1);r.isBackwards?(s.sibling=t.child,t.child=s):(null!==(n=r.last)?n.sibling=s:t.child=s,r.last=s)}return null!==r.tail?(0===r.tailExpiration&&(r.tailExpiration=Uo()+500),n=r.tail,r.rendering=n,r.tail=n.sibling,r.lastEffect=t.lastEffect,r.renderingStartTime=Uo(),n.sibling=null,t=Mi.current,co(Mi,i?1&t|2:1&t),n):null}throw Error(a(156,t.tag))}function Ka(e){switch(e.tag){case 1:go(e.type)&&yo();var t=e.effectTag;return 4096&t?(e.effectTag=-4097&t|64,e):null;case 3:if(Ri(),lo(fo),lo(po),0!=(64&(t=e.effectTag)))throw Error(a(285));return e.effectTag=-4097&t|64,e;case 5:return Li(e),null;case 13:return lo(Mi),4096&(t=e.effectTag)?(e.effectTag=-4097&t|64,e):null;case 19:return lo(Mi),null;case 4:return Ri(),null;case 10:return ti(e),null;default:return null}}function Za(e,t){return{value:e,source:t,stack:F(t)}}Ua=function(e,t){for(var n=t.child;null!==n;){if(5===n.tag||6===n.tag)e.appendChild(n.stateNode);else if(4!==n.tag&&null!==n.child){n.child.return=n,n=n.child;continue}if(n===t)break;for(;null===n.sibling;){if(null===n.return||n.return===t)return;n=n.return}n.sibling.return=n.return,n=n.sibling}},Ba=function(e,t,n,r,i){var a=e.memoizedProps;if(a!==r){var s,l,c=t.stateNode;switch(Ii(ji.current),e=null,n){case"input":a=ke(c,a),r=ke(c,r),e=[];break;case"option":a=je(c,a),r=je(c,r),e=[];break;case"select":a=o({},a,{value:void 0}),r=o({},r,{value:void 0}),e=[];break;case"textarea":a=Ae(c,a),r=Ae(c,r),e=[];break;default:"function"!=typeof a.onClick&&"function"==typeof r.onClick&&(c.onclick=cn)}for(s in on(n,r),n=null,a)if(!r.hasOwnProperty(s)&&a.hasOwnProperty(s)&&null!=a[s])if("style"===s)for(l in c=a[s])c.hasOwnProperty(l)&&(n||(n={}),n[l]="");else"dangerouslySetInnerHTML"!==s&&"children"!==s&&"suppressContentEditableWarning"!==s&&"suppressHydrationWarning"!==s&&"autoFocus"!==s&&(H.hasOwnProperty(s)?e||(e=[]):(e=e||[]).push(s,null));for(s in r){var u=r[s];if(c=null!=a?a[s]:void 0,r.hasOwnProperty(s)&&u!==c&&(null!=u||null!=c))if("style"===s)if(c){for(l in c)!c.hasOwnProperty(l)||u&&u.hasOwnProperty(l)||(n||(n={}),n[l]="");for(l in u)u.hasOwnProperty(l)&&c[l]!==u[l]&&(n||(n={}),n[l]=u[l])}else n||(e||(e=[]),e.push(s,n)),n=u;else"dangerouslySetInnerHTML"===s?(u=u?u.__html:void 0,c=c?c.__html:void 0,null!=u&&c!==u&&(e=e||[]).push(s,u)):"children"===s?c===u||"string"!=typeof u&&"number"!=typeof u||(e=e||[]).push(s,""+u):"suppressContentEditableWarning"!==s&&"suppressHydrationWarning"!==s&&(H.hasOwnProperty(s)?(null!=u&&ln(i,s),e||c===u||(e=[])):(e=e||[]).push(s,u))}n&&(e=e||[]).push("style",n),i=e,(t.updateQueue=i)&&(t.effectTag|=4)}},$a=function(e,t,n,r){n!==r&&(t.effectTag|=4)};var Ja="function"==typeof WeakSet?WeakSet:Set;function es(e,t){var n=t.source,r=t.stack;null===r&&null!==n&&(r=F(n)),null!==n&&D(n.type),t=t.value,null!==e&&1===e.tag&&D(e.type);try{console.error(t)}catch(e){setTimeout((function(){throw e}))}}function ts(e){var t=e.ref;if(null!==t)if("function"==typeof t)try{t(null)}catch(t){vl(e,t)}else t.current=null}function ns(e,t){switch(t.tag){case 0:case 11:case 15:case 22:return;case 1:if(256&t.effectTag&&null!==e){var n=e.memoizedProps,r=e.memoizedState;t=(e=t.stateNode).getSnapshotBeforeUpdate(t.elementType===t.type?n:Go(t.type,n),r),e.__reactInternalSnapshotBeforeUpdate=t}return;case 3:case 5:case 6:case 4:case 17:return}throw Error(a(163))}function rs(e,t){if(null!==(t=null!==(t=t.updateQueue)?t.lastEffect:null)){var n=t=t.next;do{if((n.tag&e)===e){var r=n.destroy;n.destroy=void 0,void 0!==r&&r()}n=n.next}while(n!==t)}}function os(e,t){if(null!==(t=null!==(t=t.updateQueue)?t.lastEffect:null)){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function is(e,t,n){switch(n.tag){case 0:case 11:case 15:case 22:return void os(3,n);case 1:if(e=n.stateNode,4&n.effectTag)if(null===t)e.componentDidMount();else{var r=n.elementType===n.type?t.memoizedProps:Go(n.type,t.memoizedProps);e.componentDidUpdate(r,t.memoizedState,e.__reactInternalSnapshotBeforeUpdate)}return void(null!==(t=n.updateQueue)&&fi(n,t,e));case 3:if(null!==(t=n.updateQueue)){if(e=null,null!==n.child)switch(n.child.tag){case 5:e=n.child.stateNode;break;case 1:e=n.child.stateNode}fi(n,t,e)}return;case 5:return e=n.stateNode,void(null===t&&4&n.effectTag&&yn(n.type,n.memoizedProps)&&e.focus());case 6:case 4:case 12:return;case 13:return void(null===n.memoizedState&&(n=n.alternate,null!==n&&(n=n.memoizedState,null!==n&&(n=n.dehydrated,null!==n&&Mt(n)))));case 19:case 17:case 20:case 21:return}throw Error(a(163))}function as(e,t,n){switch("function"==typeof kl&&kl(t),t.tag){case 0:case 11:case 14:case 15:case 22:if(null!==(e=t.updateQueue)&&null!==(e=e.lastEffect)){var r=e.next;qo(97<n?97:n,(function(){var e=r;do{var n=e.destroy;if(void 0!==n){var o=t;try{n()}catch(e){vl(o,e)}}e=e.next}while(e!==r)}))}break;case 1:ts(t),"function"==typeof(n=t.stateNode).componentWillUnmount&&function(e,t){try{t.props=e.memoizedProps,t.state=e.memoizedState,t.componentWillUnmount()}catch(t){vl(e,t)}}(t,n);break;case 5:ts(t);break;case 4:us(e,t,n)}}function ss(e){var t=e.alternate;e.return=null,e.child=null,e.memoizedState=null,e.updateQueue=null,e.dependencies=null,e.alternate=null,e.firstEffect=null,e.lastEffect=null,e.pendingProps=null,e.memoizedProps=null,e.stateNode=null,null!==t&&ss(t)}function ls(e){return 5===e.tag||3===e.tag||4===e.tag}function cs(e){e:{for(var t=e.return;null!==t;){if(ls(t)){var n=t;break e}t=t.return}throw Error(a(160))}switch(t=n.stateNode,n.tag){case 5:var r=!1;break;case 3:case 4:t=t.containerInfo,r=!0;break;default:throw Error(a(161))}16&n.effectTag&&(Ue(t,""),n.effectTag&=-17);e:t:for(n=e;;){for(;null===n.sibling;){if(null===n.return||ls(n.return)){n=null;break e}n=n.return}for(n.sibling.return=n.return,n=n.sibling;5!==n.tag&&6!==n.tag&&18!==n.tag;){if(2&n.effectTag)continue t;if(null===n.child||4===n.tag)continue t;n.child.return=n,n=n.child}if(!(2&n.effectTag)){n=n.stateNode;break e}}r?function e(t,n,r){var o=t.tag,i=5===o||6===o;if(i)t=i?t.stateNode:t.stateNode.instance,n?8===r.nodeType?r.parentNode.insertBefore(t,n):r.insertBefore(t,n):(8===r.nodeType?(n=r.parentNode).insertBefore(t,r):(n=r).appendChild(t),null!==(r=r._reactRootContainer)&&void 0!==r||null!==n.onclick||(n.onclick=cn));else if(4!==o&&null!==(t=t.child))for(e(t,n,r),t=t.sibling;null!==t;)e(t,n,r),t=t.sibling}(e,n,t):function e(t,n,r){var o=t.tag,i=5===o||6===o;if(i)t=i?t.stateNode:t.stateNode.instance,n?r.insertBefore(t,n):r.appendChild(t);else if(4!==o&&null!==(t=t.child))for(e(t,n,r),t=t.sibling;null!==t;)e(t,n,r),t=t.sibling}(e,n,t)}function us(e,t,n){for(var r,o,i=t,s=!1;;){if(!s){s=i.return;e:for(;;){if(null===s)throw Error(a(160));switch(r=s.stateNode,s.tag){case 5:o=!1;break e;case 3:case 4:r=r.containerInfo,o=!0;break e}s=s.return}s=!0}if(5===i.tag||6===i.tag){e:for(var l=e,c=i,u=n,p=c;;)if(as(l,p,u),null!==p.child&&4!==p.tag)p.child.return=p,p=p.child;else{if(p===c)break e;for(;null===p.sibling;){if(null===p.return||p.return===c)break e;p=p.return}p.sibling.return=p.return,p=p.sibling}o?(l=r,c=i.stateNode,8===l.nodeType?l.parentNode.removeChild(c):l.removeChild(c)):r.removeChild(i.stateNode)}else if(4===i.tag){if(null!==i.child){r=i.stateNode.containerInfo,o=!0,i.child.return=i,i=i.child;continue}}else if(as(e,i,n),null!==i.child){i.child.return=i,i=i.child;continue}if(i===t)break;for(;null===i.sibling;){if(null===i.return||i.return===t)return;4===(i=i.return).tag&&(s=!1)}i.sibling.return=i.return,i=i.sibling}}function ps(e,t){switch(t.tag){case 0:case 11:case 14:case 15:case 22:return void rs(3,t);case 1:return;case 5:var n=t.stateNode;if(null!=n){var r=t.memoizedProps,o=null!==e?e.memoizedProps:r;e=t.type;var i=t.updateQueue;if(t.updateQueue=null,null!==i){for(n[En]=r,"input"===e&&"radio"===r.type&&null!=r.name&&_e(n,r),an(e,o),t=an(e,r),o=0;o<i.length;o+=2){var s=i[o],l=i[o+1];"style"===s?nn(n,l):"dangerouslySetInnerHTML"===s?ze(n,l):"children"===s?Ue(n,l):ye(n,s,l,t)}switch(e){case"input":Ee(n,r);break;case"textarea":Pe(n,r);break;case"select":t=n._wrapperState.wasMultiple,n._wrapperState.wasMultiple=!!r.multiple,null!=(e=r.value)?Ce(n,!!r.multiple,e,!1):t!==!!r.multiple&&(null!=r.defaultValue?Ce(n,!!r.multiple,r.defaultValue,!0):Ce(n,!!r.multiple,r.multiple?[]:"",!1))}}}return;case 6:if(null===t.stateNode)throw Error(a(162));return void(t.stateNode.nodeValue=t.memoizedProps);case 3:return void((t=t.stateNode).hydrate&&(t.hydrate=!1,Mt(t.containerInfo)));case 12:return;case 13:if(n=t,null===t.memoizedState?r=!1:(r=!0,n=t.child,Ns=Uo()),null!==n)e:for(e=n;;){if(5===e.tag)i=e.stateNode,r?"function"==typeof(i=i.style).setProperty?i.setProperty("display","none","important"):i.display="none":(i=e.stateNode,o=null!=(o=e.memoizedProps.style)&&o.hasOwnProperty("display")?o.display:null,i.style.display=tn("display",o));else if(6===e.tag)e.stateNode.nodeValue=r?"":e.memoizedProps;else{if(13===e.tag&&null!==e.memoizedState&&null===e.memoizedState.dehydrated){(i=e.child.sibling).return=e,e=i;continue}if(null!==e.child){e.child.return=e,e=e.child;continue}}if(e===n)break;for(;null===e.sibling;){if(null===e.return||e.return===n)break e;e=e.return}e.sibling.return=e.return,e=e.sibling}return void fs(t);case 19:return void fs(t);case 17:return}throw Error(a(163))}function fs(e){var t=e.updateQueue;if(null!==t){e.updateQueue=null;var n=e.stateNode;null===n&&(n=e.stateNode=new Ja),t.forEach((function(t){var r=xl.bind(null,e,t);n.has(t)||(n.add(t),t.then(r,r))}))}}var ds="function"==typeof WeakMap?WeakMap:Map;function hs(e,t,n){(n=li(n,null)).tag=3,n.payload={element:null};var r=t.value;return n.callback=function(){Ms||(Ms=!0,Ds=r),es(e,t)},n}function ms(e,t,n){(n=li(n,null)).tag=3;var r=e.type.getDerivedStateFromError;if("function"==typeof r){var o=t.value;n.payload=function(){return es(e,t),r(o)}}var i=e.stateNode;return null!==i&&"function"==typeof i.componentDidCatch&&(n.callback=function(){"function"!=typeof r&&(null===Fs?Fs=new Set([this]):Fs.add(this),es(e,t));var n=t.stack;this.componentDidCatch(t.value,{componentStack:null!==n?n:""})}),n}var gs,ys=Math.ceil,vs=v.ReactCurrentDispatcher,bs=v.ReactCurrentOwner,xs=0,ws=3,ks=4,Os=0,_s=null,Es=null,Ss=0,Ts=xs,js=null,Cs=1073741823,As=1073741823,Is=null,Ps=0,Rs=!1,Ns=0,Ls=null,Ms=!1,Ds=null,Fs=null,zs=!1,Us=null,Bs=90,$s=null,qs=0,Ws=null,Hs=0;function Vs(){return 0!=(48&Os)?1073741821-(Uo()/10|0):0!==Hs?Hs:Hs=1073741821-(Uo()/10|0)}function Ys(e,t,n){if(0==(2&(t=t.mode)))return 1073741823;var r=Bo();if(0==(4&t))return 99===r?1073741823:1073741822;if(0!=(16&Os))return Ss;if(null!==n)e=Qo(e,0|n.timeoutMs||5e3,250);else switch(r){case 99:e=1073741823;break;case 98:e=Qo(e,150,100);break;case 97:case 96:e=Qo(e,5e3,250);break;case 95:e=2;break;default:throw Error(a(326))}return null!==_s&&e===Ss&&--e,e}function Qs(e,t){if(50<qs)throw qs=0,Ws=null,Error(a(185));if(null!==(e=Gs(e,t))){var n=Bo();1073741823===t?0!=(8&Os)&&0==(48&Os)?Js(e):(Ks(e),0===Os&&Vo()):Ks(e),0==(4&Os)||98!==n&&99!==n||(null===$s?$s=new Map([[e,t]]):(void 0===(n=$s.get(e))||n>t)&&$s.set(e,t))}}function Gs(e,t){e.expirationTime<t&&(e.expirationTime=t);var n=e.alternate;null!==n&&n.expirationTime<t&&(n.expirationTime=t);var r=e.return,o=null;if(null===r&&3===e.tag)o=e.stateNode;else for(;null!==r;){if(n=r.alternate,r.childExpirationTime<t&&(r.childExpirationTime=t),null!==n&&n.childExpirationTime<t&&(n.childExpirationTime=t),null===r.return&&3===r.tag){o=r.stateNode;break}r=r.return}return null!==o&&(_s===o&&(al(t),Ts===ks&&Rl(o,Ss)),Nl(o,t)),o}function Xs(e){var t=e.lastExpiredTime;if(0!==t)return t;if(!Pl(e,t=e.firstPendingTime))return t;var n=e.lastPingedTime;return 2>=(e=n>(e=e.nextKnownPendingLevel)?n:e)&&t!==e?0:e}function Ks(e){if(0!==e.lastExpiredTime)e.callbackExpirationTime=1073741823,e.callbackPriority=99,e.callbackNode=Ho(Js.bind(null,e));else{var t=Xs(e),n=e.callbackNode;if(0===t)null!==n&&(e.callbackNode=null,e.callbackExpirationTime=0,e.callbackPriority=90);else{var r=Vs();if(1073741823===t?r=99:1===t||2===t?r=95:r=0>=(r=10*(1073741821-t)-10*(1073741821-r))?99:250>=r?98:5250>=r?97:95,null!==n){var o=e.callbackPriority;if(e.callbackExpirationTime===t&&o>=r)return;n!==Ro&&_o(n)}e.callbackExpirationTime=t,e.callbackPriority=r,t=1073741823===t?Ho(Js.bind(null,e)):Wo(r,Zs.bind(null,e),{timeout:10*(1073741821-t)-Uo()}),e.callbackNode=t}}}function Zs(e,t){if(Hs=0,t)return Ll(e,t=Vs()),Ks(e),null;var n=Xs(e);if(0!==n){if(t=e.callbackNode,0!=(48&Os))throw Error(a(327));if(ml(),e===_s&&n===Ss||nl(e,n),null!==Es){var r=Os;Os|=16;for(var o=ol();;)try{ll();break}catch(t){rl(e,t)}if(ei(),Os=r,vs.current=o,1===Ts)throw t=js,nl(e,n),Rl(e,n),Ks(e),t;if(null===Es)switch(o=e.finishedWork=e.current.alternate,e.finishedExpirationTime=n,r=Ts,_s=null,r){case xs:case 1:throw Error(a(345));case 2:Ll(e,2<n?2:n);break;case ws:if(Rl(e,n),n===(r=e.lastSuspendedTime)&&(e.nextKnownPendingLevel=pl(o)),1073741823===Cs&&10<(o=Ns+500-Uo())){if(Rs){var i=e.lastPingedTime;if(0===i||i>=n){e.lastPingedTime=n,nl(e,n);break}}if(0!==(i=Xs(e))&&i!==n)break;if(0!==r&&r!==n){e.lastPingedTime=r;break}e.timeoutHandle=bn(fl.bind(null,e),o);break}fl(e);break;case ks:if(Rl(e,n),n===(r=e.lastSuspendedTime)&&(e.nextKnownPendingLevel=pl(o)),Rs&&(0===(o=e.lastPingedTime)||o>=n)){e.lastPingedTime=n,nl(e,n);break}if(0!==(o=Xs(e))&&o!==n)break;if(0!==r&&r!==n){e.lastPingedTime=r;break}if(1073741823!==As?r=10*(1073741821-As)-Uo():1073741823===Cs?r=0:(r=10*(1073741821-Cs)-5e3,0>(r=(o=Uo())-r)&&(r=0),(n=10*(1073741821-n)-o)<(r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*ys(r/1960))-r)&&(r=n)),10<r){e.timeoutHandle=bn(fl.bind(null,e),r);break}fl(e);break;case 5:if(1073741823!==Cs&&null!==Is){i=Cs;var s=Is;if(0>=(r=0|s.busyMinDurationMs)?r=0:(o=0|s.busyDelayMs,r=(i=Uo()-(10*(1073741821-i)-(0|s.timeoutMs||5e3)))<=o?0:o+r-i),10<r){Rl(e,n),e.timeoutHandle=bn(fl.bind(null,e),r);break}}fl(e);break;default:throw Error(a(329))}if(Ks(e),e.callbackNode===t)return Zs.bind(null,e)}}return null}function Js(e){var t=e.lastExpiredTime;if(t=0!==t?t:1073741823,0!=(48&Os))throw Error(a(327));if(ml(),e===_s&&t===Ss||nl(e,t),null!==Es){var n=Os;Os|=16;for(var r=ol();;)try{sl();break}catch(t){rl(e,t)}if(ei(),Os=n,vs.current=r,1===Ts)throw n=js,nl(e,t),Rl(e,t),Ks(e),n;if(null!==Es)throw Error(a(261));e.finishedWork=e.current.alternate,e.finishedExpirationTime=t,_s=null,fl(e),Ks(e)}return null}function el(e,t){var n=Os;Os|=1;try{return e(t)}finally{0===(Os=n)&&Vo()}}function tl(e,t){var n=Os;Os&=-2,Os|=8;try{return e(t)}finally{0===(Os=n)&&Vo()}}function nl(e,t){e.finishedWork=null,e.finishedExpirationTime=0;var n=e.timeoutHandle;if(-1!==n&&(e.timeoutHandle=-1,xn(n)),null!==Es)for(n=Es.return;null!==n;){var r=n;switch(r.tag){case 1:null!=(r=r.type.childContextTypes)&&yo();break;case 3:Ri(),lo(fo),lo(po);break;case 5:Li(r);break;case 4:Ri();break;case 13:case 19:lo(Mi);break;case 10:ti(r)}n=n.return}_s=e,Es=Sl(e.current,null),Ss=t,Ts=xs,js=null,As=Cs=1073741823,Is=null,Ps=0,Rs=!1}function rl(e,t){for(;;){try{if(ei(),zi.current=ga,Hi)for(var n=$i.memoizedState;null!==n;){var r=n.queue;null!==r&&(r.pending=null),n=n.next}if(Bi=0,Wi=qi=$i=null,Hi=!1,null===Es||null===Es.return)return Ts=1,js=t,Es=null;e:{var o=e,i=Es.return,a=Es,s=t;if(t=Ss,a.effectTag|=2048,a.firstEffect=a.lastEffect=null,null!==s&&"object"==typeof s&&"function"==typeof s.then){var l=s;if(0==(2&a.mode)){var c=a.alternate;c?(a.memoizedState=c.memoizedState,a.expirationTime=c.expirationTime):a.memoizedState=null}var u=0!=(1&Mi.current),p=i;do{var f;if(f=13===p.tag){var d=p.memoizedState;if(null!==d)f=null!==d.dehydrated;else{var h=p.memoizedProps;f=void 0!==h.fallback&&(!0!==h.unstable_avoidThisFallback||!u)}}if(f){var m=p.updateQueue;if(null===m){var g=new Set;g.add(l),p.updateQueue=g}else m.add(l);if(0==(2&p.mode)){if(p.effectTag|=64,a.effectTag&=-2981,1===a.tag)if(null===a.alternate)a.tag=17;else{var y=li(1073741823,null);y.tag=2,ci(a,y)}a.expirationTime=1073741823;break e}s=void 0,a=t;var v=o.pingCache;if(null===v?(v=o.pingCache=new ds,s=new Set,v.set(l,s)):void 0===(s=v.get(l))&&(s=new Set,v.set(l,s)),!s.has(a)){s.add(a);var b=bl.bind(null,o,l,a);l.then(b,b)}p.effectTag|=4096,p.expirationTime=t;break e}p=p.return}while(null!==p);s=Error((D(a.type)||"A React component")+" suspended while rendering, but no fallback UI was specified.\n\nAdd a <Suspense fallback=...> component higher in the tree to provide a loading indicator or placeholder to display."+F(a))}5!==Ts&&(Ts=2),s=Za(s,a),p=i;do{switch(p.tag){case 3:l=s,p.effectTag|=4096,p.expirationTime=t,ui(p,hs(p,l,t));break e;case 1:l=s;var x=p.type,w=p.stateNode;if(0==(64&p.effectTag)&&("function"==typeof x.getDerivedStateFromError||null!==w&&"function"==typeof w.componentDidCatch&&(null===Fs||!Fs.has(w)))){p.effectTag|=4096,p.expirationTime=t,ui(p,ms(p,l,t));break e}}p=p.return}while(null!==p)}Es=ul(Es)}catch(e){t=e;continue}break}}function ol(){var e=vs.current;return vs.current=ga,null===e?ga:e}function il(e,t){e<Cs&&2<e&&(Cs=e),null!==t&&e<As&&2<e&&(As=e,Is=t)}function al(e){e>Ps&&(Ps=e)}function sl(){for(;null!==Es;)Es=cl(Es)}function ll(){for(;null!==Es&&!No();)Es=cl(Es)}function cl(e){var t=gs(e.alternate,e,Ss);return e.memoizedProps=e.pendingProps,null===t&&(t=ul(e)),bs.current=null,t}function ul(e){Es=e;do{var t=Es.alternate;if(e=Es.return,0==(2048&Es.effectTag)){if(t=Xa(t,Es,Ss),1===Ss||1!==Es.childExpirationTime){for(var n=0,r=Es.child;null!==r;){var o=r.expirationTime,i=r.childExpirationTime;o>n&&(n=o),i>n&&(n=i),r=r.sibling}Es.childExpirationTime=n}if(null!==t)return t;null!==e&&0==(2048&e.effectTag)&&(null===e.firstEffect&&(e.firstEffect=Es.firstEffect),null!==Es.lastEffect&&(null!==e.lastEffect&&(e.lastEffect.nextEffect=Es.firstEffect),e.lastEffect=Es.lastEffect),1<Es.effectTag&&(null!==e.lastEffect?e.lastEffect.nextEffect=Es:e.firstEffect=Es,e.lastEffect=Es))}else{if(null!==(t=Ka(Es)))return t.effectTag&=2047,t;null!==e&&(e.firstEffect=e.lastEffect=null,e.effectTag|=2048)}if(null!==(t=Es.sibling))return t;Es=e}while(null!==Es);return Ts===xs&&(Ts=5),null}function pl(e){var t=e.expirationTime;return t>(e=e.childExpirationTime)?t:e}function fl(e){var t=Bo();return qo(99,dl.bind(null,e,t)),null}function dl(e,t){do{ml()}while(null!==Us);if(0!=(48&Os))throw Error(a(327));var n=e.finishedWork,r=e.finishedExpirationTime;if(null===n)return null;if(e.finishedWork=null,e.finishedExpirationTime=0,n===e.current)throw Error(a(177));e.callbackNode=null,e.callbackExpirationTime=0,e.callbackPriority=90,e.nextKnownPendingLevel=0;var o=pl(n);if(e.firstPendingTime=o,r<=e.lastSuspendedTime?e.firstSuspendedTime=e.lastSuspendedTime=e.nextKnownPendingLevel=0:r<=e.firstSuspendedTime&&(e.firstSuspendedTime=r-1),r<=e.lastPingedTime&&(e.lastPingedTime=0),r<=e.lastExpiredTime&&(e.lastExpiredTime=0),e===_s&&(Es=_s=null,Ss=0),1<n.effectTag?null!==n.lastEffect?(n.lastEffect.nextEffect=n,o=n.firstEffect):o=n:o=n.firstEffect,null!==o){var i=Os;Os|=32,bs.current=null,mn=Vt;var s=dn();if(hn(s)){if("selectionStart"in s)var l={start:s.selectionStart,end:s.selectionEnd};else e:{var c=(l=(l=s.ownerDocument)&&l.defaultView||window).getSelection&&l.getSelection();if(c&&0!==c.rangeCount){l=c.anchorNode;var u=c.anchorOffset,p=c.focusNode;c=c.focusOffset;try{l.nodeType,p.nodeType}catch(e){l=null;break e}var f=0,d=-1,h=-1,m=0,g=0,y=s,v=null;t:for(;;){for(var b;y!==l||0!==u&&3!==y.nodeType||(d=f+u),y!==p||0!==c&&3!==y.nodeType||(h=f+c),3===y.nodeType&&(f+=y.nodeValue.length),null!==(b=y.firstChild);)v=y,y=b;for(;;){if(y===s)break t;if(v===l&&++m===u&&(d=f),v===p&&++g===c&&(h=f),null!==(b=y.nextSibling))break;v=(y=v).parentNode}y=b}l=-1===d||-1===h?null:{start:d,end:h}}else l=null}l=l||{start:0,end:0}}else l=null;gn={activeElementDetached:null,focusedElem:s,selectionRange:l},Vt=!1,Ls=o;do{try{hl()}catch(e){if(null===Ls)throw Error(a(330));vl(Ls,e),Ls=Ls.nextEffect}}while(null!==Ls);Ls=o;do{try{for(s=e,l=t;null!==Ls;){var x=Ls.effectTag;if(16&x&&Ue(Ls.stateNode,""),128&x){var w=Ls.alternate;if(null!==w){var k=w.ref;null!==k&&("function"==typeof k?k(null):k.current=null)}}switch(1038&x){case 2:cs(Ls),Ls.effectTag&=-3;break;case 6:cs(Ls),Ls.effectTag&=-3,ps(Ls.alternate,Ls);break;case 1024:Ls.effectTag&=-1025;break;case 1028:Ls.effectTag&=-1025,ps(Ls.alternate,Ls);break;case 4:ps(Ls.alternate,Ls);break;case 8:us(s,u=Ls,l),ss(u)}Ls=Ls.nextEffect}}catch(e){if(null===Ls)throw Error(a(330));vl(Ls,e),Ls=Ls.nextEffect}}while(null!==Ls);if(k=gn,w=dn(),x=k.focusedElem,l=k.selectionRange,w!==x&&x&&x.ownerDocument&&function e(t,n){return!(!t||!n)&&(t===n||(!t||3!==t.nodeType)&&(n&&3===n.nodeType?e(t,n.parentNode):"contains"in t?t.contains(n):!!t.compareDocumentPosition&&!!(16&t.compareDocumentPosition(n))))}(x.ownerDocument.documentElement,x)){null!==l&&hn(x)&&(w=l.start,void 0===(k=l.end)&&(k=w),"selectionStart"in x?(x.selectionStart=w,x.selectionEnd=Math.min(k,x.value.length)):(k=(w=x.ownerDocument||document)&&w.defaultView||window).getSelection&&(k=k.getSelection(),u=x.textContent.length,s=Math.min(l.start,u),l=void 0===l.end?s:Math.min(l.end,u),!k.extend&&s>l&&(u=l,l=s,s=u),u=fn(x,s),p=fn(x,l),u&&p&&(1!==k.rangeCount||k.anchorNode!==u.node||k.anchorOffset!==u.offset||k.focusNode!==p.node||k.focusOffset!==p.offset)&&((w=w.createRange()).setStart(u.node,u.offset),k.removeAllRanges(),s>l?(k.addRange(w),k.extend(p.node,p.offset)):(w.setEnd(p.node,p.offset),k.addRange(w))))),w=[];for(k=x;k=k.parentNode;)1===k.nodeType&&w.push({element:k,left:k.scrollLeft,top:k.scrollTop});for("function"==typeof x.focus&&x.focus(),x=0;x<w.length;x++)(k=w[x]).element.scrollLeft=k.left,k.element.scrollTop=k.top}Vt=!!mn,gn=mn=null,e.current=n,Ls=o;do{try{for(x=e;null!==Ls;){var O=Ls.effectTag;if(36&O&&is(x,Ls.alternate,Ls),128&O){w=void 0;var _=Ls.ref;if(null!==_){var E=Ls.stateNode;switch(Ls.tag){case 5:w=E;break;default:w=E}"function"==typeof _?_(w):_.current=w}}Ls=Ls.nextEffect}}catch(e){if(null===Ls)throw Error(a(330));vl(Ls,e),Ls=Ls.nextEffect}}while(null!==Ls);Ls=null,Lo(),Os=i}else e.current=n;if(zs)zs=!1,Us=e,Bs=t;else for(Ls=o;null!==Ls;)t=Ls.nextEffect,Ls.nextEffect=null,Ls=t;if(0===(t=e.firstPendingTime)&&(Fs=null),1073741823===t?e===Ws?qs++:(qs=0,Ws=e):qs=0,"function"==typeof wl&&wl(n.stateNode,r),Ks(e),Ms)throw Ms=!1,e=Ds,Ds=null,e;return 0!=(8&Os)||Vo(),null}function hl(){for(;null!==Ls;){var e=Ls.effectTag;0!=(256&e)&&ns(Ls.alternate,Ls),0==(512&e)||zs||(zs=!0,Wo(97,(function(){return ml(),null}))),Ls=Ls.nextEffect}}function ml(){if(90!==Bs){var e=97<Bs?97:Bs;return Bs=90,qo(e,gl)}}function gl(){if(null===Us)return!1;var e=Us;if(Us=null,0!=(48&Os))throw Error(a(331));var t=Os;for(Os|=32,e=e.current.firstEffect;null!==e;){try{var n=e;if(0!=(512&n.effectTag))switch(n.tag){case 0:case 11:case 15:case 22:rs(5,n),os(5,n)}}catch(t){if(null===e)throw Error(a(330));vl(e,t)}n=e.nextEffect,e.nextEffect=null,e=n}return Os=t,Vo(),!0}function yl(e,t,n){ci(e,t=hs(e,t=Za(n,t),1073741823)),null!==(e=Gs(e,1073741823))&&Ks(e)}function vl(e,t){if(3===e.tag)yl(e,e,t);else for(var n=e.return;null!==n;){if(3===n.tag){yl(n,e,t);break}if(1===n.tag){var r=n.stateNode;if("function"==typeof n.type.getDerivedStateFromError||"function"==typeof r.componentDidCatch&&(null===Fs||!Fs.has(r))){ci(n,e=ms(n,e=Za(t,e),1073741823)),null!==(n=Gs(n,1073741823))&&Ks(n);break}}n=n.return}}function bl(e,t,n){var r=e.pingCache;null!==r&&r.delete(t),_s===e&&Ss===n?Ts===ks||Ts===ws&&1073741823===Cs&&Uo()-Ns<500?nl(e,Ss):Rs=!0:Pl(e,n)&&(0!==(t=e.lastPingedTime)&&t<n||(e.lastPingedTime=n,Ks(e)))}function xl(e,t){var n=e.stateNode;null!==n&&n.delete(t),0===(t=0)&&(t=Ys(t=Vs(),e,null)),null!==(e=Gs(e,t))&&Ks(e)}gs=function(e,t,n){var r=t.expirationTime;if(null!==e){var o=t.pendingProps;if(e.memoizedProps!==o||fo.current)Aa=!0;else{if(r<n){switch(Aa=!1,t.tag){case 3:za(t),ja();break;case 5:if(Ni(t),4&t.mode&&1!==n&&o.hidden)return t.expirationTime=t.childExpirationTime=1,null;break;case 1:go(t.type)&&xo(t);break;case 4:Pi(t,t.stateNode.containerInfo);break;case 10:r=t.memoizedProps.value,o=t.type._context,co(Xo,o._currentValue),o._currentValue=r;break;case 13:if(null!==t.memoizedState)return 0!==(r=t.child.childExpirationTime)&&r>=n?Wa(e,t,n):(co(Mi,1&Mi.current),null!==(t=Qa(e,t,n))?t.sibling:null);co(Mi,1&Mi.current);break;case 19:if(r=t.childExpirationTime>=n,0!=(64&e.effectTag)){if(r)return Ya(e,t,n);t.effectTag|=64}if(null!==(o=t.memoizedState)&&(o.rendering=null,o.tail=null),co(Mi,Mi.current),!r)return null}return Qa(e,t,n)}Aa=!1}}else Aa=!1;switch(t.expirationTime=0,t.tag){case 2:if(r=t.type,null!==e&&(e.alternate=null,t.alternate=null,t.effectTag|=2),e=t.pendingProps,o=mo(t,po.current),ri(t,n),o=Qi(null,t,r,e,o,n),t.effectTag|=1,"object"==typeof o&&null!==o&&"function"==typeof o.render&&void 0===o.$$typeof){if(t.tag=1,t.memoizedState=null,t.updateQueue=null,go(r)){var i=!0;xo(t)}else i=!1;t.memoizedState=null!==o.state&&void 0!==o.state?o.state:null,ai(t);var s=r.getDerivedStateFromProps;"function"==typeof s&&mi(t,r,s,e),o.updater=gi,t.stateNode=o,o._reactInternalFiber=t,xi(t,r,e,n),t=Fa(null,t,r,!0,i,n)}else t.tag=0,Ia(null,t,o,n),t=t.child;return t;case 16:e:{if(o=t.elementType,null!==e&&(e.alternate=null,t.alternate=null,t.effectTag|=2),e=t.pendingProps,function(e){if(-1===e._status){e._status=0;var t=e._ctor;t=t(),e._result=t,t.then((function(t){0===e._status&&(t=t.default,e._status=1,e._result=t)}),(function(t){0===e._status&&(e._status=2,e._result=t)}))}}(o),1!==o._status)throw o._result;switch(o=o._result,t.type=o,i=t.tag=function(e){if("function"==typeof e)return El(e)?1:0;if(null!=e){if((e=e.$$typeof)===C)return 11;if(e===P)return 14}return 2}(o),e=Go(o,e),i){case 0:t=Ma(null,t,o,e,n);break e;case 1:t=Da(null,t,o,e,n);break e;case 11:t=Pa(null,t,o,e,n);break e;case 14:t=Ra(null,t,o,Go(o.type,e),r,n);break e}throw Error(a(306,o,""))}return t;case 0:return r=t.type,o=t.pendingProps,Ma(e,t,r,o=t.elementType===r?o:Go(r,o),n);case 1:return r=t.type,o=t.pendingProps,Da(e,t,r,o=t.elementType===r?o:Go(r,o),n);case 3:if(za(t),r=t.updateQueue,null===e||null===r)throw Error(a(282));if(r=t.pendingProps,o=null!==(o=t.memoizedState)?o.element:null,si(e,t),pi(t,r,null,n),(r=t.memoizedState.element)===o)ja(),t=Qa(e,t,n);else{if((o=t.stateNode.hydrate)&&(wa=wn(t.stateNode.containerInfo.firstChild),xa=t,o=ka=!0),o)for(n=Si(t,null,r,n),t.child=n;n;)n.effectTag=-3&n.effectTag|1024,n=n.sibling;else Ia(e,t,r,n),ja();t=t.child}return t;case 5:return Ni(t),null===e&&Ea(t),r=t.type,o=t.pendingProps,i=null!==e?e.memoizedProps:null,s=o.children,vn(r,o)?s=null:null!==i&&vn(r,i)&&(t.effectTag|=16),La(e,t),4&t.mode&&1!==n&&o.hidden?(t.expirationTime=t.childExpirationTime=1,t=null):(Ia(e,t,s,n),t=t.child),t;case 6:return null===e&&Ea(t),null;case 13:return Wa(e,t,n);case 4:return Pi(t,t.stateNode.containerInfo),r=t.pendingProps,null===e?t.child=Ei(t,null,r,n):Ia(e,t,r,n),t.child;case 11:return r=t.type,o=t.pendingProps,Pa(e,t,r,o=t.elementType===r?o:Go(r,o),n);case 7:return Ia(e,t,t.pendingProps,n),t.child;case 8:case 12:return Ia(e,t,t.pendingProps.children,n),t.child;case 10:e:{r=t.type._context,o=t.pendingProps,s=t.memoizedProps,i=o.value;var l=t.type._context;if(co(Xo,l._currentValue),l._currentValue=i,null!==s)if(l=s.value,0===(i=Dr(l,i)?0:0|("function"==typeof r._calculateChangedBits?r._calculateChangedBits(l,i):1073741823))){if(s.children===o.children&&!fo.current){t=Qa(e,t,n);break e}}else for(null!==(l=t.child)&&(l.return=t);null!==l;){var c=l.dependencies;if(null!==c){s=l.child;for(var u=c.firstContext;null!==u;){if(u.context===r&&0!=(u.observedBits&i)){1===l.tag&&((u=li(n,null)).tag=2,ci(l,u)),l.expirationTime<n&&(l.expirationTime=n),null!==(u=l.alternate)&&u.expirationTime<n&&(u.expirationTime=n),ni(l.return,n),c.expirationTime<n&&(c.expirationTime=n);break}u=u.next}}else s=10===l.tag&&l.type===t.type?null:l.child;if(null!==s)s.return=l;else for(s=l;null!==s;){if(s===t){s=null;break}if(null!==(l=s.sibling)){l.return=s.return,s=l;break}s=s.return}l=s}Ia(e,t,o.children,n),t=t.child}return t;case 9:return o=t.type,r=(i=t.pendingProps).children,ri(t,n),r=r(o=oi(o,i.unstable_observedBits)),t.effectTag|=1,Ia(e,t,r,n),t.child;case 14:return i=Go(o=t.type,t.pendingProps),Ra(e,t,o,i=Go(o.type,i),r,n);case 15:return Na(e,t,t.type,t.pendingProps,r,n);case 17:return r=t.type,o=t.pendingProps,o=t.elementType===r?o:Go(r,o),null!==e&&(e.alternate=null,t.alternate=null,t.effectTag|=2),t.tag=1,go(r)?(e=!0,xo(t)):e=!1,ri(t,n),vi(t,r,o),xi(t,r,o,n),Fa(null,t,r,!0,e,n);case 19:return Ya(e,t,n)}throw Error(a(156,t.tag))};var wl=null,kl=null;function Ol(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.effectTag=0,this.lastEffect=this.firstEffect=this.nextEffect=null,this.childExpirationTime=this.expirationTime=0,this.alternate=null}function _l(e,t,n,r){return new Ol(e,t,n,r)}function El(e){return!(!(e=e.prototype)||!e.isReactComponent)}function Sl(e,t){var n=e.alternate;return null===n?((n=_l(e.tag,t,e.key,e.mode)).elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.effectTag=0,n.nextEffect=null,n.firstEffect=null,n.lastEffect=null),n.childExpirationTime=e.childExpirationTime,n.expirationTime=e.expirationTime,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=null===t?null:{expirationTime:t.expirationTime,firstContext:t.firstContext,responders:t.responders},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function Tl(e,t,n,r,o,i){var s=2;if(r=e,"function"==typeof e)El(e)&&(s=1);else if("string"==typeof e)s=5;else e:switch(e){case O:return jl(n.children,o,i,t);case j:s=8,o|=7;break;case _:s=8,o|=1;break;case E:return(e=_l(12,n,t,8|o)).elementType=E,e.type=E,e.expirationTime=i,e;case A:return(e=_l(13,n,t,o)).type=A,e.elementType=A,e.expirationTime=i,e;case I:return(e=_l(19,n,t,o)).elementType=I,e.expirationTime=i,e;default:if("object"==typeof e&&null!==e)switch(e.$$typeof){case S:s=10;break e;case T:s=9;break e;case C:s=11;break e;case P:s=14;break e;case R:s=16,r=null;break e;case N:s=22;break e}throw Error(a(130,null==e?e:typeof e,""))}return(t=_l(s,n,t,o)).elementType=e,t.type=r,t.expirationTime=i,t}function jl(e,t,n,r){return(e=_l(7,e,r,t)).expirationTime=n,e}function Cl(e,t,n){return(e=_l(6,e,null,t)).expirationTime=n,e}function Al(e,t,n){return(t=_l(4,null!==e.children?e.children:[],e.key,t)).expirationTime=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function Il(e,t,n){this.tag=t,this.current=null,this.containerInfo=e,this.pingCache=this.pendingChildren=null,this.finishedExpirationTime=0,this.finishedWork=null,this.timeoutHandle=-1,this.pendingContext=this.context=null,this.hydrate=n,this.callbackNode=null,this.callbackPriority=90,this.lastExpiredTime=this.lastPingedTime=this.nextKnownPendingLevel=this.lastSuspendedTime=this.firstSuspendedTime=this.firstPendingTime=0}function Pl(e,t){var n=e.firstSuspendedTime;return e=e.lastSuspendedTime,0!==n&&n>=t&&e<=t}function Rl(e,t){var n=e.firstSuspendedTime,r=e.lastSuspendedTime;n<t&&(e.firstSuspendedTime=t),(r>t||0===n)&&(e.lastSuspendedTime=t),t<=e.lastPingedTime&&(e.lastPingedTime=0),t<=e.lastExpiredTime&&(e.lastExpiredTime=0)}function Nl(e,t){t>e.firstPendingTime&&(e.firstPendingTime=t);var n=e.firstSuspendedTime;0!==n&&(t>=n?e.firstSuspendedTime=e.lastSuspendedTime=e.nextKnownPendingLevel=0:t>=e.lastSuspendedTime&&(e.lastSuspendedTime=t+1),t>e.nextKnownPendingLevel&&(e.nextKnownPendingLevel=t))}function Ll(e,t){var n=e.lastExpiredTime;(0===n||n>t)&&(e.lastExpiredTime=t)}function Ml(e,t,n,r){var o=t.current,i=Vs(),s=di.suspense;i=Ys(i,o,s);e:if(n){t:{if(Je(n=n._reactInternalFiber)!==n||1!==n.tag)throw Error(a(170));var l=n;do{switch(l.tag){case 3:l=l.stateNode.context;break t;case 1:if(go(l.type)){l=l.stateNode.__reactInternalMemoizedMergedChildContext;break t}}l=l.return}while(null!==l);throw Error(a(171))}if(1===n.tag){var c=n.type;if(go(c)){n=bo(n,c,l);break e}}n=l}else n=uo;return null===t.context?t.context=n:t.pendingContext=n,(t=li(i,s)).payload={element:e},null!==(r=void 0===r?null:r)&&(t.callback=r),ci(o,t),Qs(o,i),i}function Dl(e){if(!(e=e.current).child)return null;switch(e.child.tag){case 5:default:return e.child.stateNode}}function Fl(e,t){null!==(e=e.memoizedState)&&null!==e.dehydrated&&e.retryTime<t&&(e.retryTime=t)}function zl(e,t){Fl(e,t),(e=e.alternate)&&Fl(e,t)}function Ul(e,t,n){var r=new Il(e,t,n=null!=n&&!0===n.hydrate),o=_l(3,null,null,2===t?7:1===t?3:0);r.current=o,o.stateNode=r,ai(o),e[Sn]=r.current,n&&0!==t&&function(e,t){var n=Ze(t);St.forEach((function(e){ht(e,t,n)})),Tt.forEach((function(e){ht(e,t,n)}))}(0,9===e.nodeType?e:e.ownerDocument),this._internalRoot=r}function Bl(e){return!(!e||1!==e.nodeType&&9!==e.nodeType&&11!==e.nodeType&&(8!==e.nodeType||" react-mount-point-unstable "!==e.nodeValue))}function $l(e,t,n,r,o){var i=n._reactRootContainer;if(i){var a=i._internalRoot;if("function"==typeof o){var s=o;o=function(){var e=Dl(a);s.call(e)}}Ml(t,a,e,o)}else{if(i=n._reactRootContainer=function(e,t){if(t||(t=!(!(t=e?9===e.nodeType?e.documentElement:e.firstChild:null)||1!==t.nodeType||!t.hasAttribute("data-reactroot"))),!t)for(var n;n=e.lastChild;)e.removeChild(n);return new Ul(e,0,t?{hydrate:!0}:void 0)}(n,r),a=i._internalRoot,"function"==typeof o){var l=o;o=function(){var e=Dl(a);l.call(e)}}tl((function(){Ml(t,a,e,o)}))}return Dl(a)}function ql(e,t,n){var r=3<arguments.length&&void 0!==arguments[3]?arguments[3]:null;return{$$typeof:k,key:null==r?null:""+r,children:e,containerInfo:t,implementation:n}}function Wl(e,t){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:null;if(!Bl(t))throw Error(a(200));return ql(e,t,null,n)}Ul.prototype.render=function(e){Ml(e,this._internalRoot,null,null)},Ul.prototype.unmount=function(){var e=this._internalRoot,t=e.containerInfo;Ml(null,e,null,(function(){t[Sn]=null}))},mt=function(e){if(13===e.tag){var t=Qo(Vs(),150,100);Qs(e,t),zl(e,t)}},gt=function(e){13===e.tag&&(Qs(e,3),zl(e,3))},yt=function(e){if(13===e.tag){var t=Vs();Qs(e,t=Ys(t,e,null)),zl(e,t)}},G=function(e,t,n){switch(t){case"input":if(Ee(e,n),t=n.name,"radio"===n.type&&null!=t){for(n=e;n.parentNode;)n=n.parentNode;for(n=n.querySelectorAll("input[name="+JSON.stringify(""+t)+'][type="radio"]'),t=0;t<n.length;t++){var r=n[t];if(r!==e&&r.form===e.form){var o=An(r);if(!o)throw Error(a(90));we(r),Ee(r,o)}}}break;case"textarea":Pe(e,n);break;case"select":null!=(t=n.value)&&Ce(e,!!n.multiple,t,!1)}},te=el,ne=function(e,t,n,r,o){var i=Os;Os|=4;try{return qo(98,e.bind(null,t,n,r,o))}finally{0===(Os=i)&&Vo()}},re=function(){0==(49&Os)&&(function(){if(null!==$s){var e=$s;$s=null,e.forEach((function(e,t){Ll(t,e),Ks(t)})),Vo()}}(),ml())},oe=function(e,t){var n=Os;Os|=2;try{return e(t)}finally{0===(Os=n)&&Vo()}};var Hl,Vl,Yl={Events:[jn,Cn,An,Y,W,Dn,function(e){ot(e,Mn)},J,ee,Kt,st,ml,{current:!1}]};Vl=(Hl={findFiberByHostInstance:Tn,bundleType:0,version:"16.13.0",rendererPackageName:"react-dom"}).findFiberByHostInstance,function(e){if("undefined"==typeof __REACT_DEVTOOLS_GLOBAL_HOOK__)return!1;var t=__REACT_DEVTOOLS_GLOBAL_HOOK__;if(t.isDisabled||!t.supportsFiber)return!0;try{var n=t.inject(e);wl=function(e){try{t.onCommitFiberRoot(n,e,void 0,64==(64&e.current.effectTag))}catch(e){}},kl=function(e){try{t.onCommitFiberUnmount(n,e)}catch(e){}}}catch(e){}}(o({},Hl,{overrideHookState:null,overrideProps:null,setSuspenseHandler:null,scheduleUpdate:null,currentDispatcherRef:v.ReactCurrentDispatcher,findHostInstanceByFiber:function(e){return null===(e=nt(e))?null:e.stateNode},findFiberByHostInstance:function(e){return Vl?Vl(e):null},findHostInstancesForRefresh:null,scheduleRefresh:null,scheduleRoot:null,setRefreshHandler:null,getCurrentFiber:null})),t.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=Yl,t.createPortal=Wl,t.findDOMNode=function(e){if(null==e)return null;if(1===e.nodeType)return e;var t=e._reactInternalFiber;if(void 0===t){if("function"==typeof e.render)throw Error(a(188));throw Error(a(268,Object.keys(e)))}return e=null===(e=nt(t))?null:e.stateNode},t.flushSync=function(e,t){if(0!=(48&Os))throw Error(a(187));var n=Os;Os|=1;try{return qo(99,e.bind(null,t))}finally{Os=n,Vo()}},t.hydrate=function(e,t,n){if(!Bl(t))throw Error(a(200));return $l(null,e,t,!0,n)},t.render=function(e,t,n){if(!Bl(t))throw Error(a(200));return $l(null,e,t,!1,n)},t.unmountComponentAtNode=function(e){if(!Bl(e))throw Error(a(40));return!!e._reactRootContainer&&(tl((function(){$l(null,null,e,!1,(function(){e._reactRootContainer=null,e[Sn]=null}))})),!0)},t.unstable_batchedUpdates=el,t.unstable_createPortal=function(e,t){return Wl(e,t,2<arguments.length&&void 0!==arguments[2]?arguments[2]:null)},t.unstable_renderSubtreeIntoContainer=function(e,t,n,r){if(!Bl(n))throw Error(a(200));if(null==e||void 0===e._reactInternalFiber)throw Error(a(38));return $l(e,t,n,!1,r)},t.version="16.13.0"},function(e,t,n){"use strict";e.exports=n(229)},function(e,t,n){"use strict"; + */var r=n(0),o=n(130),i=n(228);function a(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n<arguments.length;n++)t+="&args[]="+encodeURIComponent(arguments[n]);return"Minified React error #"+e+"; visit "+t+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}if(!r)throw Error(a(227));function s(e,t,n,r,o,i,a,s,l){var c=Array.prototype.slice.call(arguments,3);try{t.apply(n,c)}catch(e){this.onError(e)}}var l=!1,c=null,u=!1,p=null,f={onError:function(e){l=!0,c=e}};function d(e,t,n,r,o,i,a,u,p){l=!1,c=null,s.apply(f,arguments)}var h=null,m=null,g=null;function y(e,t,n){var r=e.type||"unknown-event";e.currentTarget=g(n),function(e,t,n,r,o,i,s,f,h){if(d.apply(this,arguments),l){if(!l)throw Error(a(198));var m=c;l=!1,c=null,u||(u=!0,p=m)}}(r,t,void 0,e),e.currentTarget=null}var v=r.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;v.hasOwnProperty("ReactCurrentDispatcher")||(v.ReactCurrentDispatcher={current:null}),v.hasOwnProperty("ReactCurrentBatchConfig")||(v.ReactCurrentBatchConfig={suspense:null});var b=/^(.*)[\\\/]/,x="function"==typeof Symbol&&Symbol.for,w=x?Symbol.for("react.element"):60103,k=x?Symbol.for("react.portal"):60106,O=x?Symbol.for("react.fragment"):60107,_=x?Symbol.for("react.strict_mode"):60108,E=x?Symbol.for("react.profiler"):60114,S=x?Symbol.for("react.provider"):60109,T=x?Symbol.for("react.context"):60110,j=x?Symbol.for("react.concurrent_mode"):60111,C=x?Symbol.for("react.forward_ref"):60112,I=x?Symbol.for("react.suspense"):60113,A=x?Symbol.for("react.suspense_list"):60120,P=x?Symbol.for("react.memo"):60115,R=x?Symbol.for("react.lazy"):60116,N=x?Symbol.for("react.block"):60121,L="function"==typeof Symbol&&Symbol.iterator;function M(e){return null===e||"object"!=typeof e?null:"function"==typeof(e=L&&e[L]||e["@@iterator"])?e:null}function D(e){if(null==e)return null;if("function"==typeof e)return e.displayName||e.name||null;if("string"==typeof e)return e;switch(e){case O:return"Fragment";case k:return"Portal";case E:return"Profiler";case _:return"StrictMode";case I:return"Suspense";case A:return"SuspenseList"}if("object"==typeof e)switch(e.$$typeof){case T:return"Context.Consumer";case S:return"Context.Provider";case C:var t=e.render;return t=t.displayName||t.name||"",e.displayName||(""!==t?"ForwardRef("+t+")":"ForwardRef");case P:return D(e.type);case N:return D(e.render);case R:if(e=1===e._status?e._result:null)return D(e)}return null}function F(e){var t="";do{e:switch(e.tag){case 3:case 4:case 6:case 7:case 10:case 9:var n="";break e;default:var r=e._debugOwner,o=e._debugSource,i=D(e.type);n=null,r&&(n=D(r.type)),r=i,i="",o?i=" (at "+o.fileName.replace(b,"")+":"+o.lineNumber+")":n&&(i=" (created by "+n+")"),n="\n in "+(r||"Unknown")+i}t+=n,e=e.return}while(e);return t}var z=null,U={};function B(){if(z)for(var e in U){var t=U[e],n=z.indexOf(e);if(!(-1<n))throw Error(a(96,e));if(!q[n]){if(!t.extractEvents)throw Error(a(97,e));for(var r in q[n]=t,n=t.eventTypes){var o=void 0,i=n[r],s=t,l=r;if(W.hasOwnProperty(l))throw Error(a(99,l));W[l]=i;var c=i.phasedRegistrationNames;if(c){for(o in c)c.hasOwnProperty(o)&&$(c[o],s,l);o=!0}else i.registrationName?($(i.registrationName,s,l),o=!0):o=!1;if(!o)throw Error(a(98,r,e))}}}}function $(e,t,n){if(H[e])throw Error(a(100,e));H[e]=t,V[e]=t.eventTypes[n].dependencies}var q=[],W={},H={},V={};function Y(e){var t,n=!1;for(t in e)if(e.hasOwnProperty(t)){var r=e[t];if(!U.hasOwnProperty(t)||U[t]!==r){if(U[t])throw Error(a(102,t));U[t]=r,n=!0}}n&&B()}var Q=!("undefined"==typeof window||void 0===window.document||void 0===window.document.createElement),G=null,X=null,K=null;function Z(e){if(e=m(e)){if("function"!=typeof G)throw Error(a(280));var t=e.stateNode;t&&(t=h(t),G(e.stateNode,e.type,t))}}function J(e){X?K?K.push(e):K=[e]:X=e}function ee(){if(X){var e=X,t=K;if(K=X=null,Z(e),t)for(e=0;e<t.length;e++)Z(t[e])}}function te(e,t){return e(t)}function ne(e,t,n,r,o){return e(t,n,r,o)}function re(){}var oe=te,ie=!1,ae=!1;function se(){null===X&&null===K||(re(),ee())}function le(e,t,n){if(ae)return e(t,n);ae=!0;try{return oe(e,t,n)}finally{ae=!1,se()}}var ce=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,ue=Object.prototype.hasOwnProperty,pe={},fe={};function de(e,t,n,r,o,i){this.acceptsBooleans=2===t||3===t||4===t,this.attributeName=r,this.attributeNamespace=o,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=i}var he={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach((function(e){he[e]=new de(e,0,!1,e,null,!1)})),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach((function(e){var t=e[0];he[t]=new de(t,1,!1,e[1],null,!1)})),["contentEditable","draggable","spellCheck","value"].forEach((function(e){he[e]=new de(e,2,!1,e.toLowerCase(),null,!1)})),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach((function(e){he[e]=new de(e,2,!1,e,null,!1)})),"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach((function(e){he[e]=new de(e,3,!1,e.toLowerCase(),null,!1)})),["checked","multiple","muted","selected"].forEach((function(e){he[e]=new de(e,3,!0,e,null,!1)})),["capture","download"].forEach((function(e){he[e]=new de(e,4,!1,e,null,!1)})),["cols","rows","size","span"].forEach((function(e){he[e]=new de(e,6,!1,e,null,!1)})),["rowSpan","start"].forEach((function(e){he[e]=new de(e,5,!1,e.toLowerCase(),null,!1)}));var me=/[\-:]([a-z])/g;function ge(e){return e[1].toUpperCase()}function ye(e,t,n,r){var o=he.hasOwnProperty(t)?he[t]:null;(null!==o?0===o.type:!r&&(2<t.length&&("o"===t[0]||"O"===t[0])&&("n"===t[1]||"N"===t[1])))||(function(e,t,n,r){if(null==t||function(e,t,n,r){if(null!==n&&0===n.type)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return!r&&(null!==n?!n.acceptsBooleans:"data-"!==(e=e.toLowerCase().slice(0,5))&&"aria-"!==e);default:return!1}}(e,t,n,r))return!0;if(r)return!1;if(null!==n)switch(n.type){case 3:return!t;case 4:return!1===t;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}(t,n,o,r)&&(n=null),r||null===o?function(e){return!!ue.call(fe,e)||!ue.call(pe,e)&&(ce.test(e)?fe[e]=!0:(pe[e]=!0,!1))}(t)&&(null===n?e.removeAttribute(t):e.setAttribute(t,""+n)):o.mustUseProperty?e[o.propertyName]=null===n?3!==o.type&&"":n:(t=o.attributeName,r=o.attributeNamespace,null===n?e.removeAttribute(t):(n=3===(o=o.type)||4===o&&!0===n?"":""+n,r?e.setAttributeNS(r,t,n):e.setAttribute(t,n))))}function ve(e){switch(typeof e){case"boolean":case"number":case"object":case"string":case"undefined":return e;default:return""}}function be(e){var t=e.type;return(e=e.nodeName)&&"input"===e.toLowerCase()&&("checkbox"===t||"radio"===t)}function xe(e){e._valueTracker||(e._valueTracker=function(e){var t=be(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&void 0!==n&&"function"==typeof n.get&&"function"==typeof n.set){var o=n.get,i=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return o.call(this)},set:function(e){r=""+e,i.call(this,e)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(e){r=""+e},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}(e))}function we(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=be(e)?e.checked?"true":"false":e.value),(e=r)!==n&&(t.setValue(e),!0)}function ke(e,t){var n=t.checked;return o({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:null!=n?n:e._wrapperState.initialChecked})}function Oe(e,t){var n=null==t.defaultValue?"":t.defaultValue,r=null!=t.checked?t.checked:t.defaultChecked;n=ve(null!=t.value?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:"checkbox"===t.type||"radio"===t.type?null!=t.checked:null!=t.value}}function _e(e,t){null!=(t=t.checked)&&ye(e,"checked",t,!1)}function Ee(e,t){_e(e,t);var n=ve(t.value),r=t.type;if(null!=n)"number"===r?(0===n&&""===e.value||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if("submit"===r||"reset"===r)return void e.removeAttribute("value");t.hasOwnProperty("value")?Te(e,t.type,n):t.hasOwnProperty("defaultValue")&&Te(e,t.type,ve(t.defaultValue)),null==t.checked&&null!=t.defaultChecked&&(e.defaultChecked=!!t.defaultChecked)}function Se(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!("submit"!==r&&"reset"!==r||void 0!==t.value&&null!==t.value))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}""!==(n=e.name)&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,""!==n&&(e.name=n)}function Te(e,t,n){"number"===t&&e.ownerDocument.activeElement===e||(null==n?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}function je(e,t){return e=o({children:void 0},t),(t=function(e){var t="";return r.Children.forEach(e,(function(e){null!=e&&(t+=e)})),t}(t.children))&&(e.children=t),e}function Ce(e,t,n,r){if(e=e.options,t){t={};for(var o=0;o<n.length;o++)t["$"+n[o]]=!0;for(n=0;n<e.length;n++)o=t.hasOwnProperty("$"+e[n].value),e[n].selected!==o&&(e[n].selected=o),o&&r&&(e[n].defaultSelected=!0)}else{for(n=""+ve(n),t=null,o=0;o<e.length;o++){if(e[o].value===n)return e[o].selected=!0,void(r&&(e[o].defaultSelected=!0));null!==t||e[o].disabled||(t=e[o])}null!==t&&(t.selected=!0)}}function Ie(e,t){if(null!=t.dangerouslySetInnerHTML)throw Error(a(91));return o({},t,{value:void 0,defaultValue:void 0,children:""+e._wrapperState.initialValue})}function Ae(e,t){var n=t.value;if(null==n){if(n=t.children,t=t.defaultValue,null!=n){if(null!=t)throw Error(a(92));if(Array.isArray(n)){if(!(1>=n.length))throw Error(a(93));n=n[0]}t=n}null==t&&(t=""),n=t}e._wrapperState={initialValue:ve(n)}}function Pe(e,t){var n=ve(t.value),r=ve(t.defaultValue);null!=n&&((n=""+n)!==e.value&&(e.value=n),null==t.defaultValue&&e.defaultValue!==n&&(e.defaultValue=n)),null!=r&&(e.defaultValue=""+r)}function Re(e){var t=e.textContent;t===e._wrapperState.initialValue&&""!==t&&null!==t&&(e.value=t)}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach((function(e){var t=e.replace(me,ge);he[t]=new de(t,1,!1,e,null,!1)})),"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach((function(e){var t=e.replace(me,ge);he[t]=new de(t,1,!1,e,"http://www.w3.org/1999/xlink",!1)})),["xml:base","xml:lang","xml:space"].forEach((function(e){var t=e.replace(me,ge);he[t]=new de(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1)})),["tabIndex","crossOrigin"].forEach((function(e){he[e]=new de(e,1,!1,e.toLowerCase(),null,!1)})),he.xlinkHref=new de("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0),["src","href","action","formAction"].forEach((function(e){he[e]=new de(e,1,!1,e.toLowerCase(),null,!0)}));var Ne="http://www.w3.org/1999/xhtml",Le="http://www.w3.org/2000/svg";function Me(e){switch(e){case"svg":return"http://www.w3.org/2000/svg";case"math":return"http://www.w3.org/1998/Math/MathML";default:return"http://www.w3.org/1999/xhtml"}}function De(e,t){return null==e||"http://www.w3.org/1999/xhtml"===e?Me(t):"http://www.w3.org/2000/svg"===e&&"foreignObject"===t?"http://www.w3.org/1999/xhtml":e}var Fe,ze=function(e){return"undefined"!=typeof MSApp&&MSApp.execUnsafeLocalFunction?function(t,n,r,o){MSApp.execUnsafeLocalFunction((function(){return e(t,n)}))}:e}((function(e,t){if(e.namespaceURI!==Le||"innerHTML"in e)e.innerHTML=t;else{for((Fe=Fe||document.createElement("div")).innerHTML="<svg>"+t.valueOf().toString()+"</svg>",t=Fe.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}}));function Ue(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&3===n.nodeType)return void(n.nodeValue=t)}e.textContent=t}function Be(e,t){var n={};return n[e.toLowerCase()]=t.toLowerCase(),n["Webkit"+e]="webkit"+t,n["Moz"+e]="moz"+t,n}var $e={animationend:Be("Animation","AnimationEnd"),animationiteration:Be("Animation","AnimationIteration"),animationstart:Be("Animation","AnimationStart"),transitionend:Be("Transition","TransitionEnd")},qe={},We={};function He(e){if(qe[e])return qe[e];if(!$e[e])return e;var t,n=$e[e];for(t in n)if(n.hasOwnProperty(t)&&t in We)return qe[e]=n[t];return e}Q&&(We=document.createElement("div").style,"AnimationEvent"in window||(delete $e.animationend.animation,delete $e.animationiteration.animation,delete $e.animationstart.animation),"TransitionEvent"in window||delete $e.transitionend.transition);var Ve=He("animationend"),Ye=He("animationiteration"),Qe=He("animationstart"),Ge=He("transitionend"),Xe="abort canplay canplaythrough durationchange emptied encrypted ended error loadeddata loadedmetadata loadstart pause play playing progress ratechange seeked seeking stalled suspend timeupdate volumechange waiting".split(" "),Ke=new("function"==typeof WeakMap?WeakMap:Map);function Ze(e){var t=Ke.get(e);return void 0===t&&(t=new Map,Ke.set(e,t)),t}function Je(e){var t=e,n=e;if(e.alternate)for(;t.return;)t=t.return;else{e=t;do{0!=(1026&(t=e).effectTag)&&(n=t.return),e=t.return}while(e)}return 3===t.tag?n:null}function et(e){if(13===e.tag){var t=e.memoizedState;if(null===t&&(null!==(e=e.alternate)&&(t=e.memoizedState)),null!==t)return t.dehydrated}return null}function tt(e){if(Je(e)!==e)throw Error(a(188))}function nt(e){if(!(e=function(e){var t=e.alternate;if(!t){if(null===(t=Je(e)))throw Error(a(188));return t!==e?null:e}for(var n=e,r=t;;){var o=n.return;if(null===o)break;var i=o.alternate;if(null===i){if(null!==(r=o.return)){n=r;continue}break}if(o.child===i.child){for(i=o.child;i;){if(i===n)return tt(o),e;if(i===r)return tt(o),t;i=i.sibling}throw Error(a(188))}if(n.return!==r.return)n=o,r=i;else{for(var s=!1,l=o.child;l;){if(l===n){s=!0,n=o,r=i;break}if(l===r){s=!0,r=o,n=i;break}l=l.sibling}if(!s){for(l=i.child;l;){if(l===n){s=!0,n=i,r=o;break}if(l===r){s=!0,r=i,n=o;break}l=l.sibling}if(!s)throw Error(a(189))}}if(n.alternate!==r)throw Error(a(190))}if(3!==n.tag)throw Error(a(188));return n.stateNode.current===n?e:t}(e)))return null;for(var t=e;;){if(5===t.tag||6===t.tag)return t;if(t.child)t.child.return=t,t=t.child;else{if(t===e)break;for(;!t.sibling;){if(!t.return||t.return===e)return null;t=t.return}t.sibling.return=t.return,t=t.sibling}}return null}function rt(e,t){if(null==t)throw Error(a(30));return null==e?t:Array.isArray(e)?Array.isArray(t)?(e.push.apply(e,t),e):(e.push(t),e):Array.isArray(t)?[e].concat(t):[e,t]}function ot(e,t,n){Array.isArray(e)?e.forEach(t,n):e&&t.call(n,e)}var it=null;function at(e){if(e){var t=e._dispatchListeners,n=e._dispatchInstances;if(Array.isArray(t))for(var r=0;r<t.length&&!e.isPropagationStopped();r++)y(e,t[r],n[r]);else t&&y(e,t,n);e._dispatchListeners=null,e._dispatchInstances=null,e.isPersistent()||e.constructor.release(e)}}function st(e){if(null!==e&&(it=rt(it,e)),e=it,it=null,e){if(ot(e,at),it)throw Error(a(95));if(u)throw e=p,u=!1,p=null,e}}function lt(e){return(e=e.target||e.srcElement||window).correspondingUseElement&&(e=e.correspondingUseElement),3===e.nodeType?e.parentNode:e}function ct(e){if(!Q)return!1;var t=(e="on"+e)in document;return t||((t=document.createElement("div")).setAttribute(e,"return;"),t="function"==typeof t[e]),t}var ut=[];function pt(e){e.topLevelType=null,e.nativeEvent=null,e.targetInst=null,e.ancestors.length=0,10>ut.length&&ut.push(e)}function ft(e,t,n,r){if(ut.length){var o=ut.pop();return o.topLevelType=e,o.eventSystemFlags=r,o.nativeEvent=t,o.targetInst=n,o}return{topLevelType:e,eventSystemFlags:r,nativeEvent:t,targetInst:n,ancestors:[]}}function dt(e){var t=e.targetInst,n=t;do{if(!n){e.ancestors.push(n);break}var r=n;if(3===r.tag)r=r.stateNode.containerInfo;else{for(;r.return;)r=r.return;r=3!==r.tag?null:r.stateNode.containerInfo}if(!r)break;5!==(t=n.tag)&&6!==t||e.ancestors.push(n),n=Tn(r)}while(n);for(n=0;n<e.ancestors.length;n++){t=e.ancestors[n];var o=lt(e.nativeEvent);r=e.topLevelType;var i=e.nativeEvent,a=e.eventSystemFlags;0===n&&(a|=64);for(var s=null,l=0;l<q.length;l++){var c=q[l];c&&(c=c.extractEvents(r,t,i,o,a))&&(s=rt(s,c))}st(s)}}function ht(e,t,n){if(!n.has(e)){switch(e){case"scroll":Qt(t,"scroll",!0);break;case"focus":case"blur":Qt(t,"focus",!0),Qt(t,"blur",!0),n.set("blur",null),n.set("focus",null);break;case"cancel":case"close":ct(e)&&Qt(t,e,!0);break;case"invalid":case"submit":case"reset":break;default:-1===Xe.indexOf(e)&&Yt(e,t)}n.set(e,null)}}var mt,gt,yt,vt=!1,bt=[],xt=null,wt=null,kt=null,Ot=new Map,_t=new Map,Et=[],St="mousedown mouseup touchcancel touchend touchstart auxclick dblclick pointercancel pointerdown pointerup dragend dragstart drop compositionend compositionstart keydown keypress keyup input textInput close cancel copy cut paste click change contextmenu reset submit".split(" "),Tt="focus blur dragenter dragleave mouseover mouseout pointerover pointerout gotpointercapture lostpointercapture".split(" ");function jt(e,t,n,r,o){return{blockedOn:e,topLevelType:t,eventSystemFlags:32|n,nativeEvent:o,container:r}}function Ct(e,t){switch(e){case"focus":case"blur":xt=null;break;case"dragenter":case"dragleave":wt=null;break;case"mouseover":case"mouseout":kt=null;break;case"pointerover":case"pointerout":Ot.delete(t.pointerId);break;case"gotpointercapture":case"lostpointercapture":_t.delete(t.pointerId)}}function It(e,t,n,r,o,i){return null===e||e.nativeEvent!==i?(e=jt(t,n,r,o,i),null!==t&&(null!==(t=jn(t))&>(t)),e):(e.eventSystemFlags|=r,e)}function At(e){var t=Tn(e.target);if(null!==t){var n=Je(t);if(null!==n)if(13===(t=n.tag)){if(null!==(t=et(n)))return e.blockedOn=t,void i.unstable_runWithPriority(e.priority,(function(){yt(n)}))}else if(3===t&&n.stateNode.hydrate)return void(e.blockedOn=3===n.tag?n.stateNode.containerInfo:null)}e.blockedOn=null}function Pt(e){if(null!==e.blockedOn)return!1;var t=Zt(e.topLevelType,e.eventSystemFlags,e.container,e.nativeEvent);if(null!==t){var n=jn(t);return null!==n&>(n),e.blockedOn=t,!1}return!0}function Rt(e,t,n){Pt(e)&&n.delete(t)}function Nt(){for(vt=!1;0<bt.length;){var e=bt[0];if(null!==e.blockedOn){null!==(e=jn(e.blockedOn))&&mt(e);break}var t=Zt(e.topLevelType,e.eventSystemFlags,e.container,e.nativeEvent);null!==t?e.blockedOn=t:bt.shift()}null!==xt&&Pt(xt)&&(xt=null),null!==wt&&Pt(wt)&&(wt=null),null!==kt&&Pt(kt)&&(kt=null),Ot.forEach(Rt),_t.forEach(Rt)}function Lt(e,t){e.blockedOn===t&&(e.blockedOn=null,vt||(vt=!0,i.unstable_scheduleCallback(i.unstable_NormalPriority,Nt)))}function Mt(e){function t(t){return Lt(t,e)}if(0<bt.length){Lt(bt[0],e);for(var n=1;n<bt.length;n++){var r=bt[n];r.blockedOn===e&&(r.blockedOn=null)}}for(null!==xt&&Lt(xt,e),null!==wt&&Lt(wt,e),null!==kt&&Lt(kt,e),Ot.forEach(t),_t.forEach(t),n=0;n<Et.length;n++)(r=Et[n]).blockedOn===e&&(r.blockedOn=null);for(;0<Et.length&&null===(n=Et[0]).blockedOn;)At(n),null===n.blockedOn&&Et.shift()}var Dt={},Ft=new Map,zt=new Map,Ut=["abort","abort",Ve,"animationEnd",Ye,"animationIteration",Qe,"animationStart","canplay","canPlay","canplaythrough","canPlayThrough","durationchange","durationChange","emptied","emptied","encrypted","encrypted","ended","ended","error","error","gotpointercapture","gotPointerCapture","load","load","loadeddata","loadedData","loadedmetadata","loadedMetadata","loadstart","loadStart","lostpointercapture","lostPointerCapture","playing","playing","progress","progress","seeking","seeking","stalled","stalled","suspend","suspend","timeupdate","timeUpdate",Ge,"transitionEnd","waiting","waiting"];function Bt(e,t){for(var n=0;n<e.length;n+=2){var r=e[n],o=e[n+1],i="on"+(o[0].toUpperCase()+o.slice(1));i={phasedRegistrationNames:{bubbled:i,captured:i+"Capture"},dependencies:[r],eventPriority:t},zt.set(r,t),Ft.set(r,i),Dt[o]=i}}Bt("blur blur cancel cancel click click close close contextmenu contextMenu copy copy cut cut auxclick auxClick dblclick doubleClick dragend dragEnd dragstart dragStart drop drop focus focus input input invalid invalid keydown keyDown keypress keyPress keyup keyUp mousedown mouseDown mouseup mouseUp paste paste pause pause play play pointercancel pointerCancel pointerdown pointerDown pointerup pointerUp ratechange rateChange reset reset seeked seeked submit submit touchcancel touchCancel touchend touchEnd touchstart touchStart volumechange volumeChange".split(" "),0),Bt("drag drag dragenter dragEnter dragexit dragExit dragleave dragLeave dragover dragOver mousemove mouseMove mouseout mouseOut mouseover mouseOver pointermove pointerMove pointerout pointerOut pointerover pointerOver scroll scroll toggle toggle touchmove touchMove wheel wheel".split(" "),1),Bt(Ut,2);for(var $t="change selectionchange textInput compositionstart compositionend compositionupdate".split(" "),qt=0;qt<$t.length;qt++)zt.set($t[qt],0);var Wt=i.unstable_UserBlockingPriority,Ht=i.unstable_runWithPriority,Vt=!0;function Yt(e,t){Qt(t,e,!1)}function Qt(e,t,n){var r=zt.get(t);switch(void 0===r?2:r){case 0:r=Gt.bind(null,t,1,e);break;case 1:r=Xt.bind(null,t,1,e);break;default:r=Kt.bind(null,t,1,e)}n?e.addEventListener(t,r,!0):e.addEventListener(t,r,!1)}function Gt(e,t,n,r){ie||re();var o=Kt,i=ie;ie=!0;try{ne(o,e,t,n,r)}finally{(ie=i)||se()}}function Xt(e,t,n,r){Ht(Wt,Kt.bind(null,e,t,n,r))}function Kt(e,t,n,r){if(Vt)if(0<bt.length&&-1<St.indexOf(e))e=jt(null,e,t,n,r),bt.push(e);else{var o=Zt(e,t,n,r);if(null===o)Ct(e,r);else if(-1<St.indexOf(e))e=jt(o,e,t,n,r),bt.push(e);else if(!function(e,t,n,r,o){switch(t){case"focus":return xt=It(xt,e,t,n,r,o),!0;case"dragenter":return wt=It(wt,e,t,n,r,o),!0;case"mouseover":return kt=It(kt,e,t,n,r,o),!0;case"pointerover":var i=o.pointerId;return Ot.set(i,It(Ot.get(i)||null,e,t,n,r,o)),!0;case"gotpointercapture":return i=o.pointerId,_t.set(i,It(_t.get(i)||null,e,t,n,r,o)),!0}return!1}(o,e,t,n,r)){Ct(e,r),e=ft(e,r,null,t);try{le(dt,e)}finally{pt(e)}}}}function Zt(e,t,n,r){if(null!==(n=Tn(n=lt(r)))){var o=Je(n);if(null===o)n=null;else{var i=o.tag;if(13===i){if(null!==(n=et(o)))return n;n=null}else if(3===i){if(o.stateNode.hydrate)return 3===o.tag?o.stateNode.containerInfo:null;n=null}else o!==n&&(n=null)}}e=ft(e,r,n,t);try{le(dt,e)}finally{pt(e)}return null}var Jt={animationIterationCount:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},en=["Webkit","ms","Moz","O"];function tn(e,t,n){return null==t||"boolean"==typeof t||""===t?"":n||"number"!=typeof t||0===t||Jt.hasOwnProperty(e)&&Jt[e]?(""+t).trim():t+"px"}function nn(e,t){for(var n in e=e.style,t)if(t.hasOwnProperty(n)){var r=0===n.indexOf("--"),o=tn(n,t[n],r);"float"===n&&(n="cssFloat"),r?e.setProperty(n,o):e[n]=o}}Object.keys(Jt).forEach((function(e){en.forEach((function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),Jt[t]=Jt[e]}))}));var rn=o({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function on(e,t){if(t){if(rn[e]&&(null!=t.children||null!=t.dangerouslySetInnerHTML))throw Error(a(137,e,""));if(null!=t.dangerouslySetInnerHTML){if(null!=t.children)throw Error(a(60));if(!("object"==typeof t.dangerouslySetInnerHTML&&"__html"in t.dangerouslySetInnerHTML))throw Error(a(61))}if(null!=t.style&&"object"!=typeof t.style)throw Error(a(62,""))}}function an(e,t){if(-1===e.indexOf("-"))return"string"==typeof t.is;switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var sn=Ne;function ln(e,t){var n=Ze(e=9===e.nodeType||11===e.nodeType?e:e.ownerDocument);t=V[t];for(var r=0;r<t.length;r++)ht(t[r],e,n)}function cn(){}function un(e){if(void 0===(e=e||("undefined"!=typeof document?document:void 0)))return null;try{return e.activeElement||e.body}catch(t){return e.body}}function pn(e){for(;e&&e.firstChild;)e=e.firstChild;return e}function fn(e,t){var n,r=pn(e);for(e=0;r;){if(3===r.nodeType){if(n=e+r.textContent.length,e<=t&&n>=t)return{node:r,offset:t-e};e=n}e:{for(;r;){if(r.nextSibling){r=r.nextSibling;break e}r=r.parentNode}r=void 0}r=pn(r)}}function dn(){for(var e=window,t=un();t instanceof e.HTMLIFrameElement;){try{var n="string"==typeof t.contentWindow.location.href}catch(e){n=!1}if(!n)break;t=un((e=t.contentWindow).document)}return t}function hn(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&("input"===t&&("text"===e.type||"search"===e.type||"tel"===e.type||"url"===e.type||"password"===e.type)||"textarea"===t||"true"===e.contentEditable)}var mn=null,gn=null;function yn(e,t){switch(e){case"button":case"input":case"select":case"textarea":return!!t.autoFocus}return!1}function vn(e,t){return"textarea"===e||"option"===e||"noscript"===e||"string"==typeof t.children||"number"==typeof t.children||"object"==typeof t.dangerouslySetInnerHTML&&null!==t.dangerouslySetInnerHTML&&null!=t.dangerouslySetInnerHTML.__html}var bn="function"==typeof setTimeout?setTimeout:void 0,xn="function"==typeof clearTimeout?clearTimeout:void 0;function wn(e){for(;null!=e;e=e.nextSibling){var t=e.nodeType;if(1===t||3===t)break}return e}function kn(e){e=e.previousSibling;for(var t=0;e;){if(8===e.nodeType){var n=e.data;if("$"===n||"$!"===n||"$?"===n){if(0===t)return e;t--}else"/$"===n&&t++}e=e.previousSibling}return null}var On=Math.random().toString(36).slice(2),_n="__reactInternalInstance$"+On,En="__reactEventHandlers$"+On,Sn="__reactContainere$"+On;function Tn(e){var t=e[_n];if(t)return t;for(var n=e.parentNode;n;){if(t=n[Sn]||n[_n]){if(n=t.alternate,null!==t.child||null!==n&&null!==n.child)for(e=kn(e);null!==e;){if(n=e[_n])return n;e=kn(e)}return t}n=(e=n).parentNode}return null}function jn(e){return!(e=e[_n]||e[Sn])||5!==e.tag&&6!==e.tag&&13!==e.tag&&3!==e.tag?null:e}function Cn(e){if(5===e.tag||6===e.tag)return e.stateNode;throw Error(a(33))}function In(e){return e[En]||null}function An(e){do{e=e.return}while(e&&5!==e.tag);return e||null}function Pn(e,t){var n=e.stateNode;if(!n)return null;var r=h(n);if(!r)return null;n=r[t];e:switch(t){case"onClick":case"onClickCapture":case"onDoubleClick":case"onDoubleClickCapture":case"onMouseDown":case"onMouseDownCapture":case"onMouseMove":case"onMouseMoveCapture":case"onMouseUp":case"onMouseUpCapture":case"onMouseEnter":(r=!r.disabled)||(r=!("button"===(e=e.type)||"input"===e||"select"===e||"textarea"===e)),e=!r;break e;default:e=!1}if(e)return null;if(n&&"function"!=typeof n)throw Error(a(231,t,typeof n));return n}function Rn(e,t,n){(t=Pn(e,n.dispatchConfig.phasedRegistrationNames[t]))&&(n._dispatchListeners=rt(n._dispatchListeners,t),n._dispatchInstances=rt(n._dispatchInstances,e))}function Nn(e){if(e&&e.dispatchConfig.phasedRegistrationNames){for(var t=e._targetInst,n=[];t;)n.push(t),t=An(t);for(t=n.length;0<t--;)Rn(n[t],"captured",e);for(t=0;t<n.length;t++)Rn(n[t],"bubbled",e)}}function Ln(e,t,n){e&&n&&n.dispatchConfig.registrationName&&(t=Pn(e,n.dispatchConfig.registrationName))&&(n._dispatchListeners=rt(n._dispatchListeners,t),n._dispatchInstances=rt(n._dispatchInstances,e))}function Mn(e){e&&e.dispatchConfig.registrationName&&Ln(e._targetInst,null,e)}function Dn(e){ot(e,Nn)}var Fn=null,zn=null,Un=null;function Bn(){if(Un)return Un;var e,t,n=zn,r=n.length,o="value"in Fn?Fn.value:Fn.textContent,i=o.length;for(e=0;e<r&&n[e]===o[e];e++);var a=r-e;for(t=1;t<=a&&n[r-t]===o[i-t];t++);return Un=o.slice(e,1<t?1-t:void 0)}function $n(){return!0}function qn(){return!1}function Wn(e,t,n,r){for(var o in this.dispatchConfig=e,this._targetInst=t,this.nativeEvent=n,e=this.constructor.Interface)e.hasOwnProperty(o)&&((t=e[o])?this[o]=t(n):"target"===o?this.target=r:this[o]=n[o]);return this.isDefaultPrevented=(null!=n.defaultPrevented?n.defaultPrevented:!1===n.returnValue)?$n:qn,this.isPropagationStopped=qn,this}function Hn(e,t,n,r){if(this.eventPool.length){var o=this.eventPool.pop();return this.call(o,e,t,n,r),o}return new this(e,t,n,r)}function Vn(e){if(!(e instanceof this))throw Error(a(279));e.destructor(),10>this.eventPool.length&&this.eventPool.push(e)}function Yn(e){e.eventPool=[],e.getPooled=Hn,e.release=Vn}o(Wn.prototype,{preventDefault:function(){this.defaultPrevented=!0;var e=this.nativeEvent;e&&(e.preventDefault?e.preventDefault():"unknown"!=typeof e.returnValue&&(e.returnValue=!1),this.isDefaultPrevented=$n)},stopPropagation:function(){var e=this.nativeEvent;e&&(e.stopPropagation?e.stopPropagation():"unknown"!=typeof e.cancelBubble&&(e.cancelBubble=!0),this.isPropagationStopped=$n)},persist:function(){this.isPersistent=$n},isPersistent:qn,destructor:function(){var e,t=this.constructor.Interface;for(e in t)this[e]=null;this.nativeEvent=this._targetInst=this.dispatchConfig=null,this.isPropagationStopped=this.isDefaultPrevented=qn,this._dispatchInstances=this._dispatchListeners=null}}),Wn.Interface={type:null,target:null,currentTarget:function(){return null},eventPhase:null,bubbles:null,cancelable:null,timeStamp:function(e){return e.timeStamp||Date.now()},defaultPrevented:null,isTrusted:null},Wn.extend=function(e){function t(){}function n(){return r.apply(this,arguments)}var r=this;t.prototype=r.prototype;var i=new t;return o(i,n.prototype),n.prototype=i,n.prototype.constructor=n,n.Interface=o({},r.Interface,e),n.extend=r.extend,Yn(n),n},Yn(Wn);var Qn=Wn.extend({data:null}),Gn=Wn.extend({data:null}),Xn=[9,13,27,32],Kn=Q&&"CompositionEvent"in window,Zn=null;Q&&"documentMode"in document&&(Zn=document.documentMode);var Jn=Q&&"TextEvent"in window&&!Zn,er=Q&&(!Kn||Zn&&8<Zn&&11>=Zn),tr=String.fromCharCode(32),nr={beforeInput:{phasedRegistrationNames:{bubbled:"onBeforeInput",captured:"onBeforeInputCapture"},dependencies:["compositionend","keypress","textInput","paste"]},compositionEnd:{phasedRegistrationNames:{bubbled:"onCompositionEnd",captured:"onCompositionEndCapture"},dependencies:"blur compositionend keydown keypress keyup mousedown".split(" ")},compositionStart:{phasedRegistrationNames:{bubbled:"onCompositionStart",captured:"onCompositionStartCapture"},dependencies:"blur compositionstart keydown keypress keyup mousedown".split(" ")},compositionUpdate:{phasedRegistrationNames:{bubbled:"onCompositionUpdate",captured:"onCompositionUpdateCapture"},dependencies:"blur compositionupdate keydown keypress keyup mousedown".split(" ")}},rr=!1;function or(e,t){switch(e){case"keyup":return-1!==Xn.indexOf(t.keyCode);case"keydown":return 229!==t.keyCode;case"keypress":case"mousedown":case"blur":return!0;default:return!1}}function ir(e){return"object"==typeof(e=e.detail)&&"data"in e?e.data:null}var ar=!1;var sr={eventTypes:nr,extractEvents:function(e,t,n,r){var o;if(Kn)e:{switch(e){case"compositionstart":var i=nr.compositionStart;break e;case"compositionend":i=nr.compositionEnd;break e;case"compositionupdate":i=nr.compositionUpdate;break e}i=void 0}else ar?or(e,n)&&(i=nr.compositionEnd):"keydown"===e&&229===n.keyCode&&(i=nr.compositionStart);return i?(er&&"ko"!==n.locale&&(ar||i!==nr.compositionStart?i===nr.compositionEnd&&ar&&(o=Bn()):(zn="value"in(Fn=r)?Fn.value:Fn.textContent,ar=!0)),i=Qn.getPooled(i,t,n,r),o?i.data=o:null!==(o=ir(n))&&(i.data=o),Dn(i),o=i):o=null,(e=Jn?function(e,t){switch(e){case"compositionend":return ir(t);case"keypress":return 32!==t.which?null:(rr=!0,tr);case"textInput":return(e=t.data)===tr&&rr?null:e;default:return null}}(e,n):function(e,t){if(ar)return"compositionend"===e||!Kn&&or(e,t)?(e=Bn(),Un=zn=Fn=null,ar=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1<t.char.length)return t.char;if(t.which)return String.fromCharCode(t.which)}return null;case"compositionend":return er&&"ko"!==t.locale?null:t.data;default:return null}}(e,n))?((t=Gn.getPooled(nr.beforeInput,t,n,r)).data=e,Dn(t)):t=null,null===o?t:null===t?o:[o,t]}},lr={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0};function cr(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return"input"===t?!!lr[e.type]:"textarea"===t}var ur={change:{phasedRegistrationNames:{bubbled:"onChange",captured:"onChangeCapture"},dependencies:"blur change click focus input keydown keyup selectionchange".split(" ")}};function pr(e,t,n){return(e=Wn.getPooled(ur.change,e,t,n)).type="change",J(n),Dn(e),e}var fr=null,dr=null;function hr(e){st(e)}function mr(e){if(we(Cn(e)))return e}function gr(e,t){if("change"===e)return t}var yr=!1;function vr(){fr&&(fr.detachEvent("onpropertychange",br),dr=fr=null)}function br(e){if("value"===e.propertyName&&mr(dr))if(e=pr(dr,e,lt(e)),ie)st(e);else{ie=!0;try{te(hr,e)}finally{ie=!1,se()}}}function xr(e,t,n){"focus"===e?(vr(),dr=n,(fr=t).attachEvent("onpropertychange",br)):"blur"===e&&vr()}function wr(e){if("selectionchange"===e||"keyup"===e||"keydown"===e)return mr(dr)}function kr(e,t){if("click"===e)return mr(t)}function Or(e,t){if("input"===e||"change"===e)return mr(t)}Q&&(yr=ct("input")&&(!document.documentMode||9<document.documentMode));var _r={eventTypes:ur,_isInputEventSupported:yr,extractEvents:function(e,t,n,r){var o=t?Cn(t):window,i=o.nodeName&&o.nodeName.toLowerCase();if("select"===i||"input"===i&&"file"===o.type)var a=gr;else if(cr(o))if(yr)a=Or;else{a=wr;var s=xr}else(i=o.nodeName)&&"input"===i.toLowerCase()&&("checkbox"===o.type||"radio"===o.type)&&(a=kr);if(a&&(a=a(e,t)))return pr(a,n,r);s&&s(e,o,t),"blur"===e&&(e=o._wrapperState)&&e.controlled&&"number"===o.type&&Te(o,"number",o.value)}},Er=Wn.extend({view:null,detail:null}),Sr={Alt:"altKey",Control:"ctrlKey",Meta:"metaKey",Shift:"shiftKey"};function Tr(e){var t=this.nativeEvent;return t.getModifierState?t.getModifierState(e):!!(e=Sr[e])&&!!t[e]}function jr(){return Tr}var Cr=0,Ir=0,Ar=!1,Pr=!1,Rr=Er.extend({screenX:null,screenY:null,clientX:null,clientY:null,pageX:null,pageY:null,ctrlKey:null,shiftKey:null,altKey:null,metaKey:null,getModifierState:jr,button:null,buttons:null,relatedTarget:function(e){return e.relatedTarget||(e.fromElement===e.srcElement?e.toElement:e.fromElement)},movementX:function(e){if("movementX"in e)return e.movementX;var t=Cr;return Cr=e.screenX,Ar?"mousemove"===e.type?e.screenX-t:0:(Ar=!0,0)},movementY:function(e){if("movementY"in e)return e.movementY;var t=Ir;return Ir=e.screenY,Pr?"mousemove"===e.type?e.screenY-t:0:(Pr=!0,0)}}),Nr=Rr.extend({pointerId:null,width:null,height:null,pressure:null,tangentialPressure:null,tiltX:null,tiltY:null,twist:null,pointerType:null,isPrimary:null}),Lr={mouseEnter:{registrationName:"onMouseEnter",dependencies:["mouseout","mouseover"]},mouseLeave:{registrationName:"onMouseLeave",dependencies:["mouseout","mouseover"]},pointerEnter:{registrationName:"onPointerEnter",dependencies:["pointerout","pointerover"]},pointerLeave:{registrationName:"onPointerLeave",dependencies:["pointerout","pointerover"]}},Mr={eventTypes:Lr,extractEvents:function(e,t,n,r,o){var i="mouseover"===e||"pointerover"===e,a="mouseout"===e||"pointerout"===e;if(i&&0==(32&o)&&(n.relatedTarget||n.fromElement)||!a&&!i)return null;(i=r.window===r?r:(i=r.ownerDocument)?i.defaultView||i.parentWindow:window,a)?(a=t,null!==(t=(t=n.relatedTarget||n.toElement)?Tn(t):null)&&(t!==Je(t)||5!==t.tag&&6!==t.tag)&&(t=null)):a=null;if(a===t)return null;if("mouseout"===e||"mouseover"===e)var s=Rr,l=Lr.mouseLeave,c=Lr.mouseEnter,u="mouse";else"pointerout"!==e&&"pointerover"!==e||(s=Nr,l=Lr.pointerLeave,c=Lr.pointerEnter,u="pointer");if(e=null==a?i:Cn(a),i=null==t?i:Cn(t),(l=s.getPooled(l,a,n,r)).type=u+"leave",l.target=e,l.relatedTarget=i,(n=s.getPooled(c,t,n,r)).type=u+"enter",n.target=i,n.relatedTarget=e,u=t,(r=a)&&u)e:{for(c=u,a=0,e=s=r;e;e=An(e))a++;for(e=0,t=c;t;t=An(t))e++;for(;0<a-e;)s=An(s),a--;for(;0<e-a;)c=An(c),e--;for(;a--;){if(s===c||s===c.alternate)break e;s=An(s),c=An(c)}s=null}else s=null;for(c=s,s=[];r&&r!==c&&(null===(a=r.alternate)||a!==c);)s.push(r),r=An(r);for(r=[];u&&u!==c&&(null===(a=u.alternate)||a!==c);)r.push(u),u=An(u);for(u=0;u<s.length;u++)Ln(s[u],"bubbled",l);for(u=r.length;0<u--;)Ln(r[u],"captured",n);return 0==(64&o)?[l]:[l,n]}};var Dr="function"==typeof Object.is?Object.is:function(e,t){return e===t&&(0!==e||1/e==1/t)||e!=e&&t!=t},Fr=Object.prototype.hasOwnProperty;function zr(e,t){if(Dr(e,t))return!0;if("object"!=typeof e||null===e||"object"!=typeof t||null===t)return!1;var n=Object.keys(e),r=Object.keys(t);if(n.length!==r.length)return!1;for(r=0;r<n.length;r++)if(!Fr.call(t,n[r])||!Dr(e[n[r]],t[n[r]]))return!1;return!0}var Ur=Q&&"documentMode"in document&&11>=document.documentMode,Br={select:{phasedRegistrationNames:{bubbled:"onSelect",captured:"onSelectCapture"},dependencies:"blur contextmenu dragend focus keydown keyup mousedown mouseup selectionchange".split(" ")}},$r=null,qr=null,Wr=null,Hr=!1;function Vr(e,t){var n=t.window===t?t.document:9===t.nodeType?t:t.ownerDocument;return Hr||null==$r||$r!==un(n)?null:("selectionStart"in(n=$r)&&hn(n)?n={start:n.selectionStart,end:n.selectionEnd}:n={anchorNode:(n=(n.ownerDocument&&n.ownerDocument.defaultView||window).getSelection()).anchorNode,anchorOffset:n.anchorOffset,focusNode:n.focusNode,focusOffset:n.focusOffset},Wr&&zr(Wr,n)?null:(Wr=n,(e=Wn.getPooled(Br.select,qr,e,t)).type="select",e.target=$r,Dn(e),e))}var Yr={eventTypes:Br,extractEvents:function(e,t,n,r,o,i){if(!(i=!(o=i||(r.window===r?r.document:9===r.nodeType?r:r.ownerDocument)))){e:{o=Ze(o),i=V.onSelect;for(var a=0;a<i.length;a++)if(!o.has(i[a])){o=!1;break e}o=!0}i=!o}if(i)return null;switch(o=t?Cn(t):window,e){case"focus":(cr(o)||"true"===o.contentEditable)&&($r=o,qr=t,Wr=null);break;case"blur":Wr=qr=$r=null;break;case"mousedown":Hr=!0;break;case"contextmenu":case"mouseup":case"dragend":return Hr=!1,Vr(n,r);case"selectionchange":if(Ur)break;case"keydown":case"keyup":return Vr(n,r)}return null}},Qr=Wn.extend({animationName:null,elapsedTime:null,pseudoElement:null}),Gr=Wn.extend({clipboardData:function(e){return"clipboardData"in e?e.clipboardData:window.clipboardData}}),Xr=Er.extend({relatedTarget:null});function Kr(e){var t=e.keyCode;return"charCode"in e?0===(e=e.charCode)&&13===t&&(e=13):e=t,10===e&&(e=13),32<=e||13===e?e:0}var Zr={Esc:"Escape",Spacebar:" ",Left:"ArrowLeft",Up:"ArrowUp",Right:"ArrowRight",Down:"ArrowDown",Del:"Delete",Win:"OS",Menu:"ContextMenu",Apps:"ContextMenu",Scroll:"ScrollLock",MozPrintableKey:"Unidentified"},Jr={8:"Backspace",9:"Tab",12:"Clear",13:"Enter",16:"Shift",17:"Control",18:"Alt",19:"Pause",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",45:"Insert",46:"Delete",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"NumLock",145:"ScrollLock",224:"Meta"},eo=Er.extend({key:function(e){if(e.key){var t=Zr[e.key]||e.key;if("Unidentified"!==t)return t}return"keypress"===e.type?13===(e=Kr(e))?"Enter":String.fromCharCode(e):"keydown"===e.type||"keyup"===e.type?Jr[e.keyCode]||"Unidentified":""},location:null,ctrlKey:null,shiftKey:null,altKey:null,metaKey:null,repeat:null,locale:null,getModifierState:jr,charCode:function(e){return"keypress"===e.type?Kr(e):0},keyCode:function(e){return"keydown"===e.type||"keyup"===e.type?e.keyCode:0},which:function(e){return"keypress"===e.type?Kr(e):"keydown"===e.type||"keyup"===e.type?e.keyCode:0}}),to=Rr.extend({dataTransfer:null}),no=Er.extend({touches:null,targetTouches:null,changedTouches:null,altKey:null,metaKey:null,ctrlKey:null,shiftKey:null,getModifierState:jr}),ro=Wn.extend({propertyName:null,elapsedTime:null,pseudoElement:null}),oo=Rr.extend({deltaX:function(e){return"deltaX"in e?e.deltaX:"wheelDeltaX"in e?-e.wheelDeltaX:0},deltaY:function(e){return"deltaY"in e?e.deltaY:"wheelDeltaY"in e?-e.wheelDeltaY:"wheelDelta"in e?-e.wheelDelta:0},deltaZ:null,deltaMode:null}),io={eventTypes:Dt,extractEvents:function(e,t,n,r){var o=Ft.get(e);if(!o)return null;switch(e){case"keypress":if(0===Kr(n))return null;case"keydown":case"keyup":e=eo;break;case"blur":case"focus":e=Xr;break;case"click":if(2===n.button)return null;case"auxclick":case"dblclick":case"mousedown":case"mousemove":case"mouseup":case"mouseout":case"mouseover":case"contextmenu":e=Rr;break;case"drag":case"dragend":case"dragenter":case"dragexit":case"dragleave":case"dragover":case"dragstart":case"drop":e=to;break;case"touchcancel":case"touchend":case"touchmove":case"touchstart":e=no;break;case Ve:case Ye:case Qe:e=Qr;break;case Ge:e=ro;break;case"scroll":e=Er;break;case"wheel":e=oo;break;case"copy":case"cut":case"paste":e=Gr;break;case"gotpointercapture":case"lostpointercapture":case"pointercancel":case"pointerdown":case"pointermove":case"pointerout":case"pointerover":case"pointerup":e=Nr;break;default:e=Wn}return Dn(t=e.getPooled(o,t,n,r)),t}};if(z)throw Error(a(101));z=Array.prototype.slice.call("ResponderEventPlugin SimpleEventPlugin EnterLeaveEventPlugin ChangeEventPlugin SelectEventPlugin BeforeInputEventPlugin".split(" ")),B(),h=In,m=jn,g=Cn,Y({SimpleEventPlugin:io,EnterLeaveEventPlugin:Mr,ChangeEventPlugin:_r,SelectEventPlugin:Yr,BeforeInputEventPlugin:sr});var ao=[],so=-1;function lo(e){0>so||(e.current=ao[so],ao[so]=null,so--)}function co(e,t){so++,ao[so]=e.current,e.current=t}var uo={},po={current:uo},fo={current:!1},ho=uo;function mo(e,t){var n=e.type.contextTypes;if(!n)return uo;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var o,i={};for(o in n)i[o]=t[o];return r&&((e=e.stateNode).__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=i),i}function go(e){return null!=(e=e.childContextTypes)}function yo(){lo(fo),lo(po)}function vo(e,t,n){if(po.current!==uo)throw Error(a(168));co(po,t),co(fo,n)}function bo(e,t,n){var r=e.stateNode;if(e=t.childContextTypes,"function"!=typeof r.getChildContext)return n;for(var i in r=r.getChildContext())if(!(i in e))throw Error(a(108,D(t)||"Unknown",i));return o({},n,{},r)}function xo(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||uo,ho=po.current,co(po,e),co(fo,fo.current),!0}function wo(e,t,n){var r=e.stateNode;if(!r)throw Error(a(169));n?(e=bo(e,t,ho),r.__reactInternalMemoizedMergedChildContext=e,lo(fo),lo(po),co(po,e)):lo(fo),co(fo,n)}var ko=i.unstable_runWithPriority,Oo=i.unstable_scheduleCallback,_o=i.unstable_cancelCallback,Eo=i.unstable_requestPaint,So=i.unstable_now,To=i.unstable_getCurrentPriorityLevel,jo=i.unstable_ImmediatePriority,Co=i.unstable_UserBlockingPriority,Io=i.unstable_NormalPriority,Ao=i.unstable_LowPriority,Po=i.unstable_IdlePriority,Ro={},No=i.unstable_shouldYield,Lo=void 0!==Eo?Eo:function(){},Mo=null,Do=null,Fo=!1,zo=So(),Uo=1e4>zo?So:function(){return So()-zo};function Bo(){switch(To()){case jo:return 99;case Co:return 98;case Io:return 97;case Ao:return 96;case Po:return 95;default:throw Error(a(332))}}function $o(e){switch(e){case 99:return jo;case 98:return Co;case 97:return Io;case 96:return Ao;case 95:return Po;default:throw Error(a(332))}}function qo(e,t){return e=$o(e),ko(e,t)}function Wo(e,t,n){return e=$o(e),Oo(e,t,n)}function Ho(e){return null===Mo?(Mo=[e],Do=Oo(jo,Yo)):Mo.push(e),Ro}function Vo(){if(null!==Do){var e=Do;Do=null,_o(e)}Yo()}function Yo(){if(!Fo&&null!==Mo){Fo=!0;var e=0;try{var t=Mo;qo(99,(function(){for(;e<t.length;e++){var n=t[e];do{n=n(!0)}while(null!==n)}})),Mo=null}catch(t){throw null!==Mo&&(Mo=Mo.slice(e+1)),Oo(jo,Vo),t}finally{Fo=!1}}}function Qo(e,t,n){return 1073741821-(1+((1073741821-e+t/10)/(n/=10)|0))*n}function Go(e,t){if(e&&e.defaultProps)for(var n in t=o({},t),e=e.defaultProps)void 0===t[n]&&(t[n]=e[n]);return t}var Xo={current:null},Ko=null,Zo=null,Jo=null;function ei(){Jo=Zo=Ko=null}function ti(e){var t=Xo.current;lo(Xo),e.type._context._currentValue=t}function ni(e,t){for(;null!==e;){var n=e.alternate;if(e.childExpirationTime<t)e.childExpirationTime=t,null!==n&&n.childExpirationTime<t&&(n.childExpirationTime=t);else{if(!(null!==n&&n.childExpirationTime<t))break;n.childExpirationTime=t}e=e.return}}function ri(e,t){Ko=e,Jo=Zo=null,null!==(e=e.dependencies)&&null!==e.firstContext&&(e.expirationTime>=t&&(Ia=!0),e.firstContext=null)}function oi(e,t){if(Jo!==e&&!1!==t&&0!==t)if("number"==typeof t&&1073741823!==t||(Jo=e,t=1073741823),t={context:e,observedBits:t,next:null},null===Zo){if(null===Ko)throw Error(a(308));Zo=t,Ko.dependencies={expirationTime:0,firstContext:t,responders:null}}else Zo=Zo.next=t;return e._currentValue}var ii=!1;function ai(e){e.updateQueue={baseState:e.memoizedState,baseQueue:null,shared:{pending:null},effects:null}}function si(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,baseQueue:e.baseQueue,shared:e.shared,effects:e.effects})}function li(e,t){return(e={expirationTime:e,suspenseConfig:t,tag:0,payload:null,callback:null,next:null}).next=e}function ci(e,t){if(null!==(e=e.updateQueue)){var n=(e=e.shared).pending;null===n?t.next=t:(t.next=n.next,n.next=t),e.pending=t}}function ui(e,t){var n=e.alternate;null!==n&&si(n,e),null===(n=(e=e.updateQueue).baseQueue)?(e.baseQueue=t.next=t,t.next=t):(t.next=n.next,n.next=t)}function pi(e,t,n,r){var i=e.updateQueue;ii=!1;var a=i.baseQueue,s=i.shared.pending;if(null!==s){if(null!==a){var l=a.next;a.next=s.next,s.next=l}a=s,i.shared.pending=null,null!==(l=e.alternate)&&(null!==(l=l.updateQueue)&&(l.baseQueue=s))}if(null!==a){l=a.next;var c=i.baseState,u=0,p=null,f=null,d=null;if(null!==l)for(var h=l;;){if((s=h.expirationTime)<r){var m={expirationTime:h.expirationTime,suspenseConfig:h.suspenseConfig,tag:h.tag,payload:h.payload,callback:h.callback,next:null};null===d?(f=d=m,p=c):d=d.next=m,s>u&&(u=s)}else{null!==d&&(d=d.next={expirationTime:1073741823,suspenseConfig:h.suspenseConfig,tag:h.tag,payload:h.payload,callback:h.callback,next:null}),il(s,h.suspenseConfig);e:{var g=e,y=h;switch(s=t,m=n,y.tag){case 1:if("function"==typeof(g=y.payload)){c=g.call(m,c,s);break e}c=g;break e;case 3:g.effectTag=-4097&g.effectTag|64;case 0:if(null==(s="function"==typeof(g=y.payload)?g.call(m,c,s):g))break e;c=o({},c,s);break e;case 2:ii=!0}}null!==h.callback&&(e.effectTag|=32,null===(s=i.effects)?i.effects=[h]:s.push(h))}if(null===(h=h.next)||h===l){if(null===(s=i.shared.pending))break;h=a.next=s.next,s.next=l,i.baseQueue=a=s,i.shared.pending=null}}null===d?p=c:d.next=f,i.baseState=p,i.baseQueue=d,al(u),e.expirationTime=u,e.memoizedState=c}}function fi(e,t,n){if(e=t.effects,t.effects=null,null!==e)for(t=0;t<e.length;t++){var r=e[t],o=r.callback;if(null!==o){if(r.callback=null,r=o,o=n,"function"!=typeof r)throw Error(a(191,r));r.call(o)}}}var di=v.ReactCurrentBatchConfig,hi=(new r.Component).refs;function mi(e,t,n,r){n=null==(n=n(r,t=e.memoizedState))?t:o({},t,n),e.memoizedState=n,0===e.expirationTime&&(e.updateQueue.baseState=n)}var gi={isMounted:function(e){return!!(e=e._reactInternalFiber)&&Je(e)===e},enqueueSetState:function(e,t,n){e=e._reactInternalFiber;var r=Vs(),o=di.suspense;(o=li(r=Ys(r,e,o),o)).payload=t,null!=n&&(o.callback=n),ci(e,o),Qs(e,r)},enqueueReplaceState:function(e,t,n){e=e._reactInternalFiber;var r=Vs(),o=di.suspense;(o=li(r=Ys(r,e,o),o)).tag=1,o.payload=t,null!=n&&(o.callback=n),ci(e,o),Qs(e,r)},enqueueForceUpdate:function(e,t){e=e._reactInternalFiber;var n=Vs(),r=di.suspense;(r=li(n=Ys(n,e,r),r)).tag=2,null!=t&&(r.callback=t),ci(e,r),Qs(e,n)}};function yi(e,t,n,r,o,i,a){return"function"==typeof(e=e.stateNode).shouldComponentUpdate?e.shouldComponentUpdate(r,i,a):!t.prototype||!t.prototype.isPureReactComponent||(!zr(n,r)||!zr(o,i))}function vi(e,t,n){var r=!1,o=uo,i=t.contextType;return"object"==typeof i&&null!==i?i=oi(i):(o=go(t)?ho:po.current,i=(r=null!=(r=t.contextTypes))?mo(e,o):uo),t=new t(n,i),e.memoizedState=null!==t.state&&void 0!==t.state?t.state:null,t.updater=gi,e.stateNode=t,t._reactInternalFiber=e,r&&((e=e.stateNode).__reactInternalMemoizedUnmaskedChildContext=o,e.__reactInternalMemoizedMaskedChildContext=i),t}function bi(e,t,n,r){e=t.state,"function"==typeof t.componentWillReceiveProps&&t.componentWillReceiveProps(n,r),"function"==typeof t.UNSAFE_componentWillReceiveProps&&t.UNSAFE_componentWillReceiveProps(n,r),t.state!==e&&gi.enqueueReplaceState(t,t.state,null)}function xi(e,t,n,r){var o=e.stateNode;o.props=n,o.state=e.memoizedState,o.refs=hi,ai(e);var i=t.contextType;"object"==typeof i&&null!==i?o.context=oi(i):(i=go(t)?ho:po.current,o.context=mo(e,i)),pi(e,n,o,r),o.state=e.memoizedState,"function"==typeof(i=t.getDerivedStateFromProps)&&(mi(e,t,i,n),o.state=e.memoizedState),"function"==typeof t.getDerivedStateFromProps||"function"==typeof o.getSnapshotBeforeUpdate||"function"!=typeof o.UNSAFE_componentWillMount&&"function"!=typeof o.componentWillMount||(t=o.state,"function"==typeof o.componentWillMount&&o.componentWillMount(),"function"==typeof o.UNSAFE_componentWillMount&&o.UNSAFE_componentWillMount(),t!==o.state&&gi.enqueueReplaceState(o,o.state,null),pi(e,n,o,r),o.state=e.memoizedState),"function"==typeof o.componentDidMount&&(e.effectTag|=4)}var wi=Array.isArray;function ki(e,t,n){if(null!==(e=n.ref)&&"function"!=typeof e&&"object"!=typeof e){if(n._owner){if(n=n._owner){if(1!==n.tag)throw Error(a(309));var r=n.stateNode}if(!r)throw Error(a(147,e));var o=""+e;return null!==t&&null!==t.ref&&"function"==typeof t.ref&&t.ref._stringRef===o?t.ref:((t=function(e){var t=r.refs;t===hi&&(t=r.refs={}),null===e?delete t[o]:t[o]=e})._stringRef=o,t)}if("string"!=typeof e)throw Error(a(284));if(!n._owner)throw Error(a(290,e))}return e}function Oi(e,t){if("textarea"!==e.type)throw Error(a(31,"[object Object]"===Object.prototype.toString.call(t)?"object with keys {"+Object.keys(t).join(", ")+"}":t,""))}function _i(e){function t(t,n){if(e){var r=t.lastEffect;null!==r?(r.nextEffect=n,t.lastEffect=n):t.firstEffect=t.lastEffect=n,n.nextEffect=null,n.effectTag=8}}function n(n,r){if(!e)return null;for(;null!==r;)t(n,r),r=r.sibling;return null}function r(e,t){for(e=new Map;null!==t;)null!==t.key?e.set(t.key,t):e.set(t.index,t),t=t.sibling;return e}function o(e,t){return(e=Sl(e,t)).index=0,e.sibling=null,e}function i(t,n,r){return t.index=r,e?null!==(r=t.alternate)?(r=r.index)<n?(t.effectTag=2,n):r:(t.effectTag=2,n):n}function s(t){return e&&null===t.alternate&&(t.effectTag=2),t}function l(e,t,n,r){return null===t||6!==t.tag?((t=Cl(n,e.mode,r)).return=e,t):((t=o(t,n)).return=e,t)}function c(e,t,n,r){return null!==t&&t.elementType===n.type?((r=o(t,n.props)).ref=ki(e,t,n),r.return=e,r):((r=Tl(n.type,n.key,n.props,null,e.mode,r)).ref=ki(e,t,n),r.return=e,r)}function u(e,t,n,r){return null===t||4!==t.tag||t.stateNode.containerInfo!==n.containerInfo||t.stateNode.implementation!==n.implementation?((t=Il(n,e.mode,r)).return=e,t):((t=o(t,n.children||[])).return=e,t)}function p(e,t,n,r,i){return null===t||7!==t.tag?((t=jl(n,e.mode,r,i)).return=e,t):((t=o(t,n)).return=e,t)}function f(e,t,n){if("string"==typeof t||"number"==typeof t)return(t=Cl(""+t,e.mode,n)).return=e,t;if("object"==typeof t&&null!==t){switch(t.$$typeof){case w:return(n=Tl(t.type,t.key,t.props,null,e.mode,n)).ref=ki(e,null,t),n.return=e,n;case k:return(t=Il(t,e.mode,n)).return=e,t}if(wi(t)||M(t))return(t=jl(t,e.mode,n,null)).return=e,t;Oi(e,t)}return null}function d(e,t,n,r){var o=null!==t?t.key:null;if("string"==typeof n||"number"==typeof n)return null!==o?null:l(e,t,""+n,r);if("object"==typeof n&&null!==n){switch(n.$$typeof){case w:return n.key===o?n.type===O?p(e,t,n.props.children,r,o):c(e,t,n,r):null;case k:return n.key===o?u(e,t,n,r):null}if(wi(n)||M(n))return null!==o?null:p(e,t,n,r,null);Oi(e,n)}return null}function h(e,t,n,r,o){if("string"==typeof r||"number"==typeof r)return l(t,e=e.get(n)||null,""+r,o);if("object"==typeof r&&null!==r){switch(r.$$typeof){case w:return e=e.get(null===r.key?n:r.key)||null,r.type===O?p(t,e,r.props.children,o,r.key):c(t,e,r,o);case k:return u(t,e=e.get(null===r.key?n:r.key)||null,r,o)}if(wi(r)||M(r))return p(t,e=e.get(n)||null,r,o,null);Oi(t,r)}return null}function m(o,a,s,l){for(var c=null,u=null,p=a,m=a=0,g=null;null!==p&&m<s.length;m++){p.index>m?(g=p,p=null):g=p.sibling;var y=d(o,p,s[m],l);if(null===y){null===p&&(p=g);break}e&&p&&null===y.alternate&&t(o,p),a=i(y,a,m),null===u?c=y:u.sibling=y,u=y,p=g}if(m===s.length)return n(o,p),c;if(null===p){for(;m<s.length;m++)null!==(p=f(o,s[m],l))&&(a=i(p,a,m),null===u?c=p:u.sibling=p,u=p);return c}for(p=r(o,p);m<s.length;m++)null!==(g=h(p,o,m,s[m],l))&&(e&&null!==g.alternate&&p.delete(null===g.key?m:g.key),a=i(g,a,m),null===u?c=g:u.sibling=g,u=g);return e&&p.forEach((function(e){return t(o,e)})),c}function g(o,s,l,c){var u=M(l);if("function"!=typeof u)throw Error(a(150));if(null==(l=u.call(l)))throw Error(a(151));for(var p=u=null,m=s,g=s=0,y=null,v=l.next();null!==m&&!v.done;g++,v=l.next()){m.index>g?(y=m,m=null):y=m.sibling;var b=d(o,m,v.value,c);if(null===b){null===m&&(m=y);break}e&&m&&null===b.alternate&&t(o,m),s=i(b,s,g),null===p?u=b:p.sibling=b,p=b,m=y}if(v.done)return n(o,m),u;if(null===m){for(;!v.done;g++,v=l.next())null!==(v=f(o,v.value,c))&&(s=i(v,s,g),null===p?u=v:p.sibling=v,p=v);return u}for(m=r(o,m);!v.done;g++,v=l.next())null!==(v=h(m,o,g,v.value,c))&&(e&&null!==v.alternate&&m.delete(null===v.key?g:v.key),s=i(v,s,g),null===p?u=v:p.sibling=v,p=v);return e&&m.forEach((function(e){return t(o,e)})),u}return function(e,r,i,l){var c="object"==typeof i&&null!==i&&i.type===O&&null===i.key;c&&(i=i.props.children);var u="object"==typeof i&&null!==i;if(u)switch(i.$$typeof){case w:e:{for(u=i.key,c=r;null!==c;){if(c.key===u){switch(c.tag){case 7:if(i.type===O){n(e,c.sibling),(r=o(c,i.props.children)).return=e,e=r;break e}break;default:if(c.elementType===i.type){n(e,c.sibling),(r=o(c,i.props)).ref=ki(e,c,i),r.return=e,e=r;break e}}n(e,c);break}t(e,c),c=c.sibling}i.type===O?((r=jl(i.props.children,e.mode,l,i.key)).return=e,e=r):((l=Tl(i.type,i.key,i.props,null,e.mode,l)).ref=ki(e,r,i),l.return=e,e=l)}return s(e);case k:e:{for(c=i.key;null!==r;){if(r.key===c){if(4===r.tag&&r.stateNode.containerInfo===i.containerInfo&&r.stateNode.implementation===i.implementation){n(e,r.sibling),(r=o(r,i.children||[])).return=e,e=r;break e}n(e,r);break}t(e,r),r=r.sibling}(r=Il(i,e.mode,l)).return=e,e=r}return s(e)}if("string"==typeof i||"number"==typeof i)return i=""+i,null!==r&&6===r.tag?(n(e,r.sibling),(r=o(r,i)).return=e,e=r):(n(e,r),(r=Cl(i,e.mode,l)).return=e,e=r),s(e);if(wi(i))return m(e,r,i,l);if(M(i))return g(e,r,i,l);if(u&&Oi(e,i),void 0===i&&!c)switch(e.tag){case 1:case 0:throw e=e.type,Error(a(152,e.displayName||e.name||"Component"))}return n(e,r)}}var Ei=_i(!0),Si=_i(!1),Ti={},ji={current:Ti},Ci={current:Ti},Ii={current:Ti};function Ai(e){if(e===Ti)throw Error(a(174));return e}function Pi(e,t){switch(co(Ii,t),co(Ci,e),co(ji,Ti),e=t.nodeType){case 9:case 11:t=(t=t.documentElement)?t.namespaceURI:De(null,"");break;default:t=De(t=(e=8===e?t.parentNode:t).namespaceURI||null,e=e.tagName)}lo(ji),co(ji,t)}function Ri(){lo(ji),lo(Ci),lo(Ii)}function Ni(e){Ai(Ii.current);var t=Ai(ji.current),n=De(t,e.type);t!==n&&(co(Ci,e),co(ji,n))}function Li(e){Ci.current===e&&(lo(ji),lo(Ci))}var Mi={current:0};function Di(e){for(var t=e;null!==t;){if(13===t.tag){var n=t.memoizedState;if(null!==n&&(null===(n=n.dehydrated)||"$?"===n.data||"$!"===n.data))return t}else if(19===t.tag&&void 0!==t.memoizedProps.revealOrder){if(0!=(64&t.effectTag))return t}else if(null!==t.child){t.child.return=t,t=t.child;continue}if(t===e)break;for(;null===t.sibling;){if(null===t.return||t.return===e)return null;t=t.return}t.sibling.return=t.return,t=t.sibling}return null}function Fi(e,t){return{responder:e,props:t}}var zi=v.ReactCurrentDispatcher,Ui=v.ReactCurrentBatchConfig,Bi=0,$i=null,qi=null,Wi=null,Hi=!1;function Vi(){throw Error(a(321))}function Yi(e,t){if(null===t)return!1;for(var n=0;n<t.length&&n<e.length;n++)if(!Dr(e[n],t[n]))return!1;return!0}function Qi(e,t,n,r,o,i){if(Bi=i,$i=t,t.memoizedState=null,t.updateQueue=null,t.expirationTime=0,zi.current=null===e||null===e.memoizedState?ya:va,e=n(r,o),t.expirationTime===Bi){i=0;do{if(t.expirationTime=0,!(25>i))throw Error(a(301));i+=1,Wi=qi=null,t.updateQueue=null,zi.current=ba,e=n(r,o)}while(t.expirationTime===Bi)}if(zi.current=ga,t=null!==qi&&null!==qi.next,Bi=0,Wi=qi=$i=null,Hi=!1,t)throw Error(a(300));return e}function Gi(){var e={memoizedState:null,baseState:null,baseQueue:null,queue:null,next:null};return null===Wi?$i.memoizedState=Wi=e:Wi=Wi.next=e,Wi}function Xi(){if(null===qi){var e=$i.alternate;e=null!==e?e.memoizedState:null}else e=qi.next;var t=null===Wi?$i.memoizedState:Wi.next;if(null!==t)Wi=t,qi=e;else{if(null===e)throw Error(a(310));e={memoizedState:(qi=e).memoizedState,baseState:qi.baseState,baseQueue:qi.baseQueue,queue:qi.queue,next:null},null===Wi?$i.memoizedState=Wi=e:Wi=Wi.next=e}return Wi}function Ki(e,t){return"function"==typeof t?t(e):t}function Zi(e){var t=Xi(),n=t.queue;if(null===n)throw Error(a(311));n.lastRenderedReducer=e;var r=qi,o=r.baseQueue,i=n.pending;if(null!==i){if(null!==o){var s=o.next;o.next=i.next,i.next=s}r.baseQueue=o=i,n.pending=null}if(null!==o){o=o.next,r=r.baseState;var l=s=i=null,c=o;do{var u=c.expirationTime;if(u<Bi){var p={expirationTime:c.expirationTime,suspenseConfig:c.suspenseConfig,action:c.action,eagerReducer:c.eagerReducer,eagerState:c.eagerState,next:null};null===l?(s=l=p,i=r):l=l.next=p,u>$i.expirationTime&&($i.expirationTime=u,al(u))}else null!==l&&(l=l.next={expirationTime:1073741823,suspenseConfig:c.suspenseConfig,action:c.action,eagerReducer:c.eagerReducer,eagerState:c.eagerState,next:null}),il(u,c.suspenseConfig),r=c.eagerReducer===e?c.eagerState:e(r,c.action);c=c.next}while(null!==c&&c!==o);null===l?i=r:l.next=s,Dr(r,t.memoizedState)||(Ia=!0),t.memoizedState=r,t.baseState=i,t.baseQueue=l,n.lastRenderedState=r}return[t.memoizedState,n.dispatch]}function Ji(e){var t=Xi(),n=t.queue;if(null===n)throw Error(a(311));n.lastRenderedReducer=e;var r=n.dispatch,o=n.pending,i=t.memoizedState;if(null!==o){n.pending=null;var s=o=o.next;do{i=e(i,s.action),s=s.next}while(s!==o);Dr(i,t.memoizedState)||(Ia=!0),t.memoizedState=i,null===t.baseQueue&&(t.baseState=i),n.lastRenderedState=i}return[i,r]}function ea(e){var t=Gi();return"function"==typeof e&&(e=e()),t.memoizedState=t.baseState=e,e=(e=t.queue={pending:null,dispatch:null,lastRenderedReducer:Ki,lastRenderedState:e}).dispatch=ma.bind(null,$i,e),[t.memoizedState,e]}function ta(e,t,n,r){return e={tag:e,create:t,destroy:n,deps:r,next:null},null===(t=$i.updateQueue)?(t={lastEffect:null},$i.updateQueue=t,t.lastEffect=e.next=e):null===(n=t.lastEffect)?t.lastEffect=e.next=e:(r=n.next,n.next=e,e.next=r,t.lastEffect=e),e}function na(){return Xi().memoizedState}function ra(e,t,n,r){var o=Gi();$i.effectTag|=e,o.memoizedState=ta(1|t,n,void 0,void 0===r?null:r)}function oa(e,t,n,r){var o=Xi();r=void 0===r?null:r;var i=void 0;if(null!==qi){var a=qi.memoizedState;if(i=a.destroy,null!==r&&Yi(r,a.deps))return void ta(t,n,i,r)}$i.effectTag|=e,o.memoizedState=ta(1|t,n,i,r)}function ia(e,t){return ra(516,4,e,t)}function aa(e,t){return oa(516,4,e,t)}function sa(e,t){return oa(4,2,e,t)}function la(e,t){return"function"==typeof t?(e=e(),t(e),function(){t(null)}):null!=t?(e=e(),t.current=e,function(){t.current=null}):void 0}function ca(e,t,n){return n=null!=n?n.concat([e]):null,oa(4,2,la.bind(null,t,e),n)}function ua(){}function pa(e,t){return Gi().memoizedState=[e,void 0===t?null:t],e}function fa(e,t){var n=Xi();t=void 0===t?null:t;var r=n.memoizedState;return null!==r&&null!==t&&Yi(t,r[1])?r[0]:(n.memoizedState=[e,t],e)}function da(e,t){var n=Xi();t=void 0===t?null:t;var r=n.memoizedState;return null!==r&&null!==t&&Yi(t,r[1])?r[0]:(e=e(),n.memoizedState=[e,t],e)}function ha(e,t,n){var r=Bo();qo(98>r?98:r,(function(){e(!0)})),qo(97<r?97:r,(function(){var r=Ui.suspense;Ui.suspense=void 0===t?null:t;try{e(!1),n()}finally{Ui.suspense=r}}))}function ma(e,t,n){var r=Vs(),o=di.suspense;o={expirationTime:r=Ys(r,e,o),suspenseConfig:o,action:n,eagerReducer:null,eagerState:null,next:null};var i=t.pending;if(null===i?o.next=o:(o.next=i.next,i.next=o),t.pending=o,i=e.alternate,e===$i||null!==i&&i===$i)Hi=!0,o.expirationTime=Bi,$i.expirationTime=Bi;else{if(0===e.expirationTime&&(null===i||0===i.expirationTime)&&null!==(i=t.lastRenderedReducer))try{var a=t.lastRenderedState,s=i(a,n);if(o.eagerReducer=i,o.eagerState=s,Dr(s,a))return}catch(e){}Qs(e,r)}}var ga={readContext:oi,useCallback:Vi,useContext:Vi,useEffect:Vi,useImperativeHandle:Vi,useLayoutEffect:Vi,useMemo:Vi,useReducer:Vi,useRef:Vi,useState:Vi,useDebugValue:Vi,useResponder:Vi,useDeferredValue:Vi,useTransition:Vi},ya={readContext:oi,useCallback:pa,useContext:oi,useEffect:ia,useImperativeHandle:function(e,t,n){return n=null!=n?n.concat([e]):null,ra(4,2,la.bind(null,t,e),n)},useLayoutEffect:function(e,t){return ra(4,2,e,t)},useMemo:function(e,t){var n=Gi();return t=void 0===t?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=Gi();return t=void 0!==n?n(t):t,r.memoizedState=r.baseState=t,e=(e=r.queue={pending:null,dispatch:null,lastRenderedReducer:e,lastRenderedState:t}).dispatch=ma.bind(null,$i,e),[r.memoizedState,e]},useRef:function(e){return e={current:e},Gi().memoizedState=e},useState:ea,useDebugValue:ua,useResponder:Fi,useDeferredValue:function(e,t){var n=ea(e),r=n[0],o=n[1];return ia((function(){var n=Ui.suspense;Ui.suspense=void 0===t?null:t;try{o(e)}finally{Ui.suspense=n}}),[e,t]),r},useTransition:function(e){var t=ea(!1),n=t[0];return t=t[1],[pa(ha.bind(null,t,e),[t,e]),n]}},va={readContext:oi,useCallback:fa,useContext:oi,useEffect:aa,useImperativeHandle:ca,useLayoutEffect:sa,useMemo:da,useReducer:Zi,useRef:na,useState:function(){return Zi(Ki)},useDebugValue:ua,useResponder:Fi,useDeferredValue:function(e,t){var n=Zi(Ki),r=n[0],o=n[1];return aa((function(){var n=Ui.suspense;Ui.suspense=void 0===t?null:t;try{o(e)}finally{Ui.suspense=n}}),[e,t]),r},useTransition:function(e){var t=Zi(Ki),n=t[0];return t=t[1],[fa(ha.bind(null,t,e),[t,e]),n]}},ba={readContext:oi,useCallback:fa,useContext:oi,useEffect:aa,useImperativeHandle:ca,useLayoutEffect:sa,useMemo:da,useReducer:Ji,useRef:na,useState:function(){return Ji(Ki)},useDebugValue:ua,useResponder:Fi,useDeferredValue:function(e,t){var n=Ji(Ki),r=n[0],o=n[1];return aa((function(){var n=Ui.suspense;Ui.suspense=void 0===t?null:t;try{o(e)}finally{Ui.suspense=n}}),[e,t]),r},useTransition:function(e){var t=Ji(Ki),n=t[0];return t=t[1],[fa(ha.bind(null,t,e),[t,e]),n]}},xa=null,wa=null,ka=!1;function Oa(e,t){var n=_l(5,null,null,0);n.elementType="DELETED",n.type="DELETED",n.stateNode=t,n.return=e,n.effectTag=8,null!==e.lastEffect?(e.lastEffect.nextEffect=n,e.lastEffect=n):e.firstEffect=e.lastEffect=n}function _a(e,t){switch(e.tag){case 5:var n=e.type;return null!==(t=1!==t.nodeType||n.toLowerCase()!==t.nodeName.toLowerCase()?null:t)&&(e.stateNode=t,!0);case 6:return null!==(t=""===e.pendingProps||3!==t.nodeType?null:t)&&(e.stateNode=t,!0);case 13:default:return!1}}function Ea(e){if(ka){var t=wa;if(t){var n=t;if(!_a(e,t)){if(!(t=wn(n.nextSibling))||!_a(e,t))return e.effectTag=-1025&e.effectTag|2,ka=!1,void(xa=e);Oa(xa,n)}xa=e,wa=wn(t.firstChild)}else e.effectTag=-1025&e.effectTag|2,ka=!1,xa=e}}function Sa(e){for(e=e.return;null!==e&&5!==e.tag&&3!==e.tag&&13!==e.tag;)e=e.return;xa=e}function Ta(e){if(e!==xa)return!1;if(!ka)return Sa(e),ka=!0,!1;var t=e.type;if(5!==e.tag||"head"!==t&&"body"!==t&&!vn(t,e.memoizedProps))for(t=wa;t;)Oa(e,t),t=wn(t.nextSibling);if(Sa(e),13===e.tag){if(!(e=null!==(e=e.memoizedState)?e.dehydrated:null))throw Error(a(317));e:{for(e=e.nextSibling,t=0;e;){if(8===e.nodeType){var n=e.data;if("/$"===n){if(0===t){wa=wn(e.nextSibling);break e}t--}else"$"!==n&&"$!"!==n&&"$?"!==n||t++}e=e.nextSibling}wa=null}}else wa=xa?wn(e.stateNode.nextSibling):null;return!0}function ja(){wa=xa=null,ka=!1}var Ca=v.ReactCurrentOwner,Ia=!1;function Aa(e,t,n,r){t.child=null===e?Si(t,null,n,r):Ei(t,e.child,n,r)}function Pa(e,t,n,r,o){n=n.render;var i=t.ref;return ri(t,o),r=Qi(e,t,n,r,i,o),null===e||Ia?(t.effectTag|=1,Aa(e,t,r,o),t.child):(t.updateQueue=e.updateQueue,t.effectTag&=-517,e.expirationTime<=o&&(e.expirationTime=0),Qa(e,t,o))}function Ra(e,t,n,r,o,i){if(null===e){var a=n.type;return"function"!=typeof a||El(a)||void 0!==a.defaultProps||null!==n.compare||void 0!==n.defaultProps?((e=Tl(n.type,null,r,null,t.mode,i)).ref=t.ref,e.return=t,t.child=e):(t.tag=15,t.type=a,Na(e,t,a,r,o,i))}return a=e.child,o<i&&(o=a.memoizedProps,(n=null!==(n=n.compare)?n:zr)(o,r)&&e.ref===t.ref)?Qa(e,t,i):(t.effectTag|=1,(e=Sl(a,r)).ref=t.ref,e.return=t,t.child=e)}function Na(e,t,n,r,o,i){return null!==e&&zr(e.memoizedProps,r)&&e.ref===t.ref&&(Ia=!1,o<i)?(t.expirationTime=e.expirationTime,Qa(e,t,i)):Ma(e,t,n,r,i)}function La(e,t){var n=t.ref;(null===e&&null!==n||null!==e&&e.ref!==n)&&(t.effectTag|=128)}function Ma(e,t,n,r,o){var i=go(n)?ho:po.current;return i=mo(t,i),ri(t,o),n=Qi(e,t,n,r,i,o),null===e||Ia?(t.effectTag|=1,Aa(e,t,n,o),t.child):(t.updateQueue=e.updateQueue,t.effectTag&=-517,e.expirationTime<=o&&(e.expirationTime=0),Qa(e,t,o))}function Da(e,t,n,r,o){if(go(n)){var i=!0;xo(t)}else i=!1;if(ri(t,o),null===t.stateNode)null!==e&&(e.alternate=null,t.alternate=null,t.effectTag|=2),vi(t,n,r),xi(t,n,r,o),r=!0;else if(null===e){var a=t.stateNode,s=t.memoizedProps;a.props=s;var l=a.context,c=n.contextType;"object"==typeof c&&null!==c?c=oi(c):c=mo(t,c=go(n)?ho:po.current);var u=n.getDerivedStateFromProps,p="function"==typeof u||"function"==typeof a.getSnapshotBeforeUpdate;p||"function"!=typeof a.UNSAFE_componentWillReceiveProps&&"function"!=typeof a.componentWillReceiveProps||(s!==r||l!==c)&&bi(t,a,r,c),ii=!1;var f=t.memoizedState;a.state=f,pi(t,r,a,o),l=t.memoizedState,s!==r||f!==l||fo.current||ii?("function"==typeof u&&(mi(t,n,u,r),l=t.memoizedState),(s=ii||yi(t,n,s,r,f,l,c))?(p||"function"!=typeof a.UNSAFE_componentWillMount&&"function"!=typeof a.componentWillMount||("function"==typeof a.componentWillMount&&a.componentWillMount(),"function"==typeof a.UNSAFE_componentWillMount&&a.UNSAFE_componentWillMount()),"function"==typeof a.componentDidMount&&(t.effectTag|=4)):("function"==typeof a.componentDidMount&&(t.effectTag|=4),t.memoizedProps=r,t.memoizedState=l),a.props=r,a.state=l,a.context=c,r=s):("function"==typeof a.componentDidMount&&(t.effectTag|=4),r=!1)}else a=t.stateNode,si(e,t),s=t.memoizedProps,a.props=t.type===t.elementType?s:Go(t.type,s),l=a.context,"object"==typeof(c=n.contextType)&&null!==c?c=oi(c):c=mo(t,c=go(n)?ho:po.current),(p="function"==typeof(u=n.getDerivedStateFromProps)||"function"==typeof a.getSnapshotBeforeUpdate)||"function"!=typeof a.UNSAFE_componentWillReceiveProps&&"function"!=typeof a.componentWillReceiveProps||(s!==r||l!==c)&&bi(t,a,r,c),ii=!1,l=t.memoizedState,a.state=l,pi(t,r,a,o),f=t.memoizedState,s!==r||l!==f||fo.current||ii?("function"==typeof u&&(mi(t,n,u,r),f=t.memoizedState),(u=ii||yi(t,n,s,r,l,f,c))?(p||"function"!=typeof a.UNSAFE_componentWillUpdate&&"function"!=typeof a.componentWillUpdate||("function"==typeof a.componentWillUpdate&&a.componentWillUpdate(r,f,c),"function"==typeof a.UNSAFE_componentWillUpdate&&a.UNSAFE_componentWillUpdate(r,f,c)),"function"==typeof a.componentDidUpdate&&(t.effectTag|=4),"function"==typeof a.getSnapshotBeforeUpdate&&(t.effectTag|=256)):("function"!=typeof a.componentDidUpdate||s===e.memoizedProps&&l===e.memoizedState||(t.effectTag|=4),"function"!=typeof a.getSnapshotBeforeUpdate||s===e.memoizedProps&&l===e.memoizedState||(t.effectTag|=256),t.memoizedProps=r,t.memoizedState=f),a.props=r,a.state=f,a.context=c,r=u):("function"!=typeof a.componentDidUpdate||s===e.memoizedProps&&l===e.memoizedState||(t.effectTag|=4),"function"!=typeof a.getSnapshotBeforeUpdate||s===e.memoizedProps&&l===e.memoizedState||(t.effectTag|=256),r=!1);return Fa(e,t,n,r,i,o)}function Fa(e,t,n,r,o,i){La(e,t);var a=0!=(64&t.effectTag);if(!r&&!a)return o&&wo(t,n,!1),Qa(e,t,i);r=t.stateNode,Ca.current=t;var s=a&&"function"!=typeof n.getDerivedStateFromError?null:r.render();return t.effectTag|=1,null!==e&&a?(t.child=Ei(t,e.child,null,i),t.child=Ei(t,null,s,i)):Aa(e,t,s,i),t.memoizedState=r.state,o&&wo(t,n,!0),t.child}function za(e){var t=e.stateNode;t.pendingContext?vo(0,t.pendingContext,t.pendingContext!==t.context):t.context&&vo(0,t.context,!1),Pi(e,t.containerInfo)}var Ua,Ba,$a,qa={dehydrated:null,retryTime:0};function Wa(e,t,n){var r,o=t.mode,i=t.pendingProps,a=Mi.current,s=!1;if((r=0!=(64&t.effectTag))||(r=0!=(2&a)&&(null===e||null!==e.memoizedState)),r?(s=!0,t.effectTag&=-65):null!==e&&null===e.memoizedState||void 0===i.fallback||!0===i.unstable_avoidThisFallback||(a|=1),co(Mi,1&a),null===e){if(void 0!==i.fallback&&Ea(t),s){if(s=i.fallback,(i=jl(null,o,0,null)).return=t,0==(2&t.mode))for(e=null!==t.memoizedState?t.child.child:t.child,i.child=e;null!==e;)e.return=i,e=e.sibling;return(n=jl(s,o,n,null)).return=t,i.sibling=n,t.memoizedState=qa,t.child=i,n}return o=i.children,t.memoizedState=null,t.child=Si(t,null,o,n)}if(null!==e.memoizedState){if(o=(e=e.child).sibling,s){if(i=i.fallback,(n=Sl(e,e.pendingProps)).return=t,0==(2&t.mode)&&(s=null!==t.memoizedState?t.child.child:t.child)!==e.child)for(n.child=s;null!==s;)s.return=n,s=s.sibling;return(o=Sl(o,i)).return=t,n.sibling=o,n.childExpirationTime=0,t.memoizedState=qa,t.child=n,o}return n=Ei(t,e.child,i.children,n),t.memoizedState=null,t.child=n}if(e=e.child,s){if(s=i.fallback,(i=jl(null,o,0,null)).return=t,i.child=e,null!==e&&(e.return=i),0==(2&t.mode))for(e=null!==t.memoizedState?t.child.child:t.child,i.child=e;null!==e;)e.return=i,e=e.sibling;return(n=jl(s,o,n,null)).return=t,i.sibling=n,n.effectTag|=2,i.childExpirationTime=0,t.memoizedState=qa,t.child=i,n}return t.memoizedState=null,t.child=Ei(t,e,i.children,n)}function Ha(e,t){e.expirationTime<t&&(e.expirationTime=t);var n=e.alternate;null!==n&&n.expirationTime<t&&(n.expirationTime=t),ni(e.return,t)}function Va(e,t,n,r,o,i){var a=e.memoizedState;null===a?e.memoizedState={isBackwards:t,rendering:null,renderingStartTime:0,last:r,tail:n,tailExpiration:0,tailMode:o,lastEffect:i}:(a.isBackwards=t,a.rendering=null,a.renderingStartTime=0,a.last=r,a.tail=n,a.tailExpiration=0,a.tailMode=o,a.lastEffect=i)}function Ya(e,t,n){var r=t.pendingProps,o=r.revealOrder,i=r.tail;if(Aa(e,t,r.children,n),0!=(2&(r=Mi.current)))r=1&r|2,t.effectTag|=64;else{if(null!==e&&0!=(64&e.effectTag))e:for(e=t.child;null!==e;){if(13===e.tag)null!==e.memoizedState&&Ha(e,n);else if(19===e.tag)Ha(e,n);else if(null!==e.child){e.child.return=e,e=e.child;continue}if(e===t)break e;for(;null===e.sibling;){if(null===e.return||e.return===t)break e;e=e.return}e.sibling.return=e.return,e=e.sibling}r&=1}if(co(Mi,r),0==(2&t.mode))t.memoizedState=null;else switch(o){case"forwards":for(n=t.child,o=null;null!==n;)null!==(e=n.alternate)&&null===Di(e)&&(o=n),n=n.sibling;null===(n=o)?(o=t.child,t.child=null):(o=n.sibling,n.sibling=null),Va(t,!1,o,n,i,t.lastEffect);break;case"backwards":for(n=null,o=t.child,t.child=null;null!==o;){if(null!==(e=o.alternate)&&null===Di(e)){t.child=o;break}e=o.sibling,o.sibling=n,n=o,o=e}Va(t,!0,n,null,i,t.lastEffect);break;case"together":Va(t,!1,null,null,void 0,t.lastEffect);break;default:t.memoizedState=null}return t.child}function Qa(e,t,n){null!==e&&(t.dependencies=e.dependencies);var r=t.expirationTime;if(0!==r&&al(r),t.childExpirationTime<n)return null;if(null!==e&&t.child!==e.child)throw Error(a(153));if(null!==t.child){for(n=Sl(e=t.child,e.pendingProps),t.child=n,n.return=t;null!==e.sibling;)e=e.sibling,(n=n.sibling=Sl(e,e.pendingProps)).return=t;n.sibling=null}return t.child}function Ga(e,t){switch(e.tailMode){case"hidden":t=e.tail;for(var n=null;null!==t;)null!==t.alternate&&(n=t),t=t.sibling;null===n?e.tail=null:n.sibling=null;break;case"collapsed":n=e.tail;for(var r=null;null!==n;)null!==n.alternate&&(r=n),n=n.sibling;null===r?t||null===e.tail?e.tail=null:e.tail.sibling=null:r.sibling=null}}function Xa(e,t,n){var r=t.pendingProps;switch(t.tag){case 2:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return null;case 1:return go(t.type)&&yo(),null;case 3:return Ri(),lo(fo),lo(po),(n=t.stateNode).pendingContext&&(n.context=n.pendingContext,n.pendingContext=null),null!==e&&null!==e.child||!Ta(t)||(t.effectTag|=4),null;case 5:Li(t),n=Ai(Ii.current);var i=t.type;if(null!==e&&null!=t.stateNode)Ba(e,t,i,r,n),e.ref!==t.ref&&(t.effectTag|=128);else{if(!r){if(null===t.stateNode)throw Error(a(166));return null}if(e=Ai(ji.current),Ta(t)){r=t.stateNode,i=t.type;var s=t.memoizedProps;switch(r[_n]=t,r[En]=s,i){case"iframe":case"object":case"embed":Yt("load",r);break;case"video":case"audio":for(e=0;e<Xe.length;e++)Yt(Xe[e],r);break;case"source":Yt("error",r);break;case"img":case"image":case"link":Yt("error",r),Yt("load",r);break;case"form":Yt("reset",r),Yt("submit",r);break;case"details":Yt("toggle",r);break;case"input":Oe(r,s),Yt("invalid",r),ln(n,"onChange");break;case"select":r._wrapperState={wasMultiple:!!s.multiple},Yt("invalid",r),ln(n,"onChange");break;case"textarea":Ae(r,s),Yt("invalid",r),ln(n,"onChange")}for(var l in on(i,s),e=null,s)if(s.hasOwnProperty(l)){var c=s[l];"children"===l?"string"==typeof c?r.textContent!==c&&(e=["children",c]):"number"==typeof c&&r.textContent!==""+c&&(e=["children",""+c]):H.hasOwnProperty(l)&&null!=c&&ln(n,l)}switch(i){case"input":xe(r),Se(r,s,!0);break;case"textarea":xe(r),Re(r);break;case"select":case"option":break;default:"function"==typeof s.onClick&&(r.onclick=cn)}n=e,t.updateQueue=n,null!==n&&(t.effectTag|=4)}else{switch(l=9===n.nodeType?n:n.ownerDocument,e===sn&&(e=Me(i)),e===sn?"script"===i?((e=l.createElement("div")).innerHTML="<script><\/script>",e=e.removeChild(e.firstChild)):"string"==typeof r.is?e=l.createElement(i,{is:r.is}):(e=l.createElement(i),"select"===i&&(l=e,r.multiple?l.multiple=!0:r.size&&(l.size=r.size))):e=l.createElementNS(e,i),e[_n]=t,e[En]=r,Ua(e,t),t.stateNode=e,l=an(i,r),i){case"iframe":case"object":case"embed":Yt("load",e),c=r;break;case"video":case"audio":for(c=0;c<Xe.length;c++)Yt(Xe[c],e);c=r;break;case"source":Yt("error",e),c=r;break;case"img":case"image":case"link":Yt("error",e),Yt("load",e),c=r;break;case"form":Yt("reset",e),Yt("submit",e),c=r;break;case"details":Yt("toggle",e),c=r;break;case"input":Oe(e,r),c=ke(e,r),Yt("invalid",e),ln(n,"onChange");break;case"option":c=je(e,r);break;case"select":e._wrapperState={wasMultiple:!!r.multiple},c=o({},r,{value:void 0}),Yt("invalid",e),ln(n,"onChange");break;case"textarea":Ae(e,r),c=Ie(e,r),Yt("invalid",e),ln(n,"onChange");break;default:c=r}on(i,c);var u=c;for(s in u)if(u.hasOwnProperty(s)){var p=u[s];"style"===s?nn(e,p):"dangerouslySetInnerHTML"===s?null!=(p=p?p.__html:void 0)&&ze(e,p):"children"===s?"string"==typeof p?("textarea"!==i||""!==p)&&Ue(e,p):"number"==typeof p&&Ue(e,""+p):"suppressContentEditableWarning"!==s&&"suppressHydrationWarning"!==s&&"autoFocus"!==s&&(H.hasOwnProperty(s)?null!=p&&ln(n,s):null!=p&&ye(e,s,p,l))}switch(i){case"input":xe(e),Se(e,r,!1);break;case"textarea":xe(e),Re(e);break;case"option":null!=r.value&&e.setAttribute("value",""+ve(r.value));break;case"select":e.multiple=!!r.multiple,null!=(n=r.value)?Ce(e,!!r.multiple,n,!1):null!=r.defaultValue&&Ce(e,!!r.multiple,r.defaultValue,!0);break;default:"function"==typeof c.onClick&&(e.onclick=cn)}yn(i,r)&&(t.effectTag|=4)}null!==t.ref&&(t.effectTag|=128)}return null;case 6:if(e&&null!=t.stateNode)$a(0,t,e.memoizedProps,r);else{if("string"!=typeof r&&null===t.stateNode)throw Error(a(166));n=Ai(Ii.current),Ai(ji.current),Ta(t)?(n=t.stateNode,r=t.memoizedProps,n[_n]=t,n.nodeValue!==r&&(t.effectTag|=4)):((n=(9===n.nodeType?n:n.ownerDocument).createTextNode(r))[_n]=t,t.stateNode=n)}return null;case 13:return lo(Mi),r=t.memoizedState,0!=(64&t.effectTag)?(t.expirationTime=n,t):(n=null!==r,r=!1,null===e?void 0!==t.memoizedProps.fallback&&Ta(t):(r=null!==(i=e.memoizedState),n||null===i||null!==(i=e.child.sibling)&&(null!==(s=t.firstEffect)?(t.firstEffect=i,i.nextEffect=s):(t.firstEffect=t.lastEffect=i,i.nextEffect=null),i.effectTag=8)),n&&!r&&0!=(2&t.mode)&&(null===e&&!0!==t.memoizedProps.unstable_avoidThisFallback||0!=(1&Mi.current)?Ts===xs&&(Ts=ws):(Ts!==xs&&Ts!==ws||(Ts=ks),0!==Ps&&null!==_s&&(Rl(_s,Ss),Nl(_s,Ps)))),(n||r)&&(t.effectTag|=4),null);case 4:return Ri(),null;case 10:return ti(t),null;case 17:return go(t.type)&&yo(),null;case 19:if(lo(Mi),null===(r=t.memoizedState))return null;if(i=0!=(64&t.effectTag),null===(s=r.rendering)){if(i)Ga(r,!1);else if(Ts!==xs||null!==e&&0!=(64&e.effectTag))for(s=t.child;null!==s;){if(null!==(e=Di(s))){for(t.effectTag|=64,Ga(r,!1),null!==(i=e.updateQueue)&&(t.updateQueue=i,t.effectTag|=4),null===r.lastEffect&&(t.firstEffect=null),t.lastEffect=r.lastEffect,r=t.child;null!==r;)s=n,(i=r).effectTag&=2,i.nextEffect=null,i.firstEffect=null,i.lastEffect=null,null===(e=i.alternate)?(i.childExpirationTime=0,i.expirationTime=s,i.child=null,i.memoizedProps=null,i.memoizedState=null,i.updateQueue=null,i.dependencies=null):(i.childExpirationTime=e.childExpirationTime,i.expirationTime=e.expirationTime,i.child=e.child,i.memoizedProps=e.memoizedProps,i.memoizedState=e.memoizedState,i.updateQueue=e.updateQueue,s=e.dependencies,i.dependencies=null===s?null:{expirationTime:s.expirationTime,firstContext:s.firstContext,responders:s.responders}),r=r.sibling;return co(Mi,1&Mi.current|2),t.child}s=s.sibling}}else{if(!i)if(null!==(e=Di(s))){if(t.effectTag|=64,i=!0,null!==(n=e.updateQueue)&&(t.updateQueue=n,t.effectTag|=4),Ga(r,!0),null===r.tail&&"hidden"===r.tailMode&&!s.alternate)return null!==(t=t.lastEffect=r.lastEffect)&&(t.nextEffect=null),null}else 2*Uo()-r.renderingStartTime>r.tailExpiration&&1<n&&(t.effectTag|=64,i=!0,Ga(r,!1),t.expirationTime=t.childExpirationTime=n-1);r.isBackwards?(s.sibling=t.child,t.child=s):(null!==(n=r.last)?n.sibling=s:t.child=s,r.last=s)}return null!==r.tail?(0===r.tailExpiration&&(r.tailExpiration=Uo()+500),n=r.tail,r.rendering=n,r.tail=n.sibling,r.lastEffect=t.lastEffect,r.renderingStartTime=Uo(),n.sibling=null,t=Mi.current,co(Mi,i?1&t|2:1&t),n):null}throw Error(a(156,t.tag))}function Ka(e){switch(e.tag){case 1:go(e.type)&&yo();var t=e.effectTag;return 4096&t?(e.effectTag=-4097&t|64,e):null;case 3:if(Ri(),lo(fo),lo(po),0!=(64&(t=e.effectTag)))throw Error(a(285));return e.effectTag=-4097&t|64,e;case 5:return Li(e),null;case 13:return lo(Mi),4096&(t=e.effectTag)?(e.effectTag=-4097&t|64,e):null;case 19:return lo(Mi),null;case 4:return Ri(),null;case 10:return ti(e),null;default:return null}}function Za(e,t){return{value:e,source:t,stack:F(t)}}Ua=function(e,t){for(var n=t.child;null!==n;){if(5===n.tag||6===n.tag)e.appendChild(n.stateNode);else if(4!==n.tag&&null!==n.child){n.child.return=n,n=n.child;continue}if(n===t)break;for(;null===n.sibling;){if(null===n.return||n.return===t)return;n=n.return}n.sibling.return=n.return,n=n.sibling}},Ba=function(e,t,n,r,i){var a=e.memoizedProps;if(a!==r){var s,l,c=t.stateNode;switch(Ai(ji.current),e=null,n){case"input":a=ke(c,a),r=ke(c,r),e=[];break;case"option":a=je(c,a),r=je(c,r),e=[];break;case"select":a=o({},a,{value:void 0}),r=o({},r,{value:void 0}),e=[];break;case"textarea":a=Ie(c,a),r=Ie(c,r),e=[];break;default:"function"!=typeof a.onClick&&"function"==typeof r.onClick&&(c.onclick=cn)}for(s in on(n,r),n=null,a)if(!r.hasOwnProperty(s)&&a.hasOwnProperty(s)&&null!=a[s])if("style"===s)for(l in c=a[s])c.hasOwnProperty(l)&&(n||(n={}),n[l]="");else"dangerouslySetInnerHTML"!==s&&"children"!==s&&"suppressContentEditableWarning"!==s&&"suppressHydrationWarning"!==s&&"autoFocus"!==s&&(H.hasOwnProperty(s)?e||(e=[]):(e=e||[]).push(s,null));for(s in r){var u=r[s];if(c=null!=a?a[s]:void 0,r.hasOwnProperty(s)&&u!==c&&(null!=u||null!=c))if("style"===s)if(c){for(l in c)!c.hasOwnProperty(l)||u&&u.hasOwnProperty(l)||(n||(n={}),n[l]="");for(l in u)u.hasOwnProperty(l)&&c[l]!==u[l]&&(n||(n={}),n[l]=u[l])}else n||(e||(e=[]),e.push(s,n)),n=u;else"dangerouslySetInnerHTML"===s?(u=u?u.__html:void 0,c=c?c.__html:void 0,null!=u&&c!==u&&(e=e||[]).push(s,u)):"children"===s?c===u||"string"!=typeof u&&"number"!=typeof u||(e=e||[]).push(s,""+u):"suppressContentEditableWarning"!==s&&"suppressHydrationWarning"!==s&&(H.hasOwnProperty(s)?(null!=u&&ln(i,s),e||c===u||(e=[])):(e=e||[]).push(s,u))}n&&(e=e||[]).push("style",n),i=e,(t.updateQueue=i)&&(t.effectTag|=4)}},$a=function(e,t,n,r){n!==r&&(t.effectTag|=4)};var Ja="function"==typeof WeakSet?WeakSet:Set;function es(e,t){var n=t.source,r=t.stack;null===r&&null!==n&&(r=F(n)),null!==n&&D(n.type),t=t.value,null!==e&&1===e.tag&&D(e.type);try{console.error(t)}catch(e){setTimeout((function(){throw e}))}}function ts(e){var t=e.ref;if(null!==t)if("function"==typeof t)try{t(null)}catch(t){vl(e,t)}else t.current=null}function ns(e,t){switch(t.tag){case 0:case 11:case 15:case 22:return;case 1:if(256&t.effectTag&&null!==e){var n=e.memoizedProps,r=e.memoizedState;t=(e=t.stateNode).getSnapshotBeforeUpdate(t.elementType===t.type?n:Go(t.type,n),r),e.__reactInternalSnapshotBeforeUpdate=t}return;case 3:case 5:case 6:case 4:case 17:return}throw Error(a(163))}function rs(e,t){if(null!==(t=null!==(t=t.updateQueue)?t.lastEffect:null)){var n=t=t.next;do{if((n.tag&e)===e){var r=n.destroy;n.destroy=void 0,void 0!==r&&r()}n=n.next}while(n!==t)}}function os(e,t){if(null!==(t=null!==(t=t.updateQueue)?t.lastEffect:null)){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function is(e,t,n){switch(n.tag){case 0:case 11:case 15:case 22:return void os(3,n);case 1:if(e=n.stateNode,4&n.effectTag)if(null===t)e.componentDidMount();else{var r=n.elementType===n.type?t.memoizedProps:Go(n.type,t.memoizedProps);e.componentDidUpdate(r,t.memoizedState,e.__reactInternalSnapshotBeforeUpdate)}return void(null!==(t=n.updateQueue)&&fi(n,t,e));case 3:if(null!==(t=n.updateQueue)){if(e=null,null!==n.child)switch(n.child.tag){case 5:e=n.child.stateNode;break;case 1:e=n.child.stateNode}fi(n,t,e)}return;case 5:return e=n.stateNode,void(null===t&&4&n.effectTag&&yn(n.type,n.memoizedProps)&&e.focus());case 6:case 4:case 12:return;case 13:return void(null===n.memoizedState&&(n=n.alternate,null!==n&&(n=n.memoizedState,null!==n&&(n=n.dehydrated,null!==n&&Mt(n)))));case 19:case 17:case 20:case 21:return}throw Error(a(163))}function as(e,t,n){switch("function"==typeof kl&&kl(t),t.tag){case 0:case 11:case 14:case 15:case 22:if(null!==(e=t.updateQueue)&&null!==(e=e.lastEffect)){var r=e.next;qo(97<n?97:n,(function(){var e=r;do{var n=e.destroy;if(void 0!==n){var o=t;try{n()}catch(e){vl(o,e)}}e=e.next}while(e!==r)}))}break;case 1:ts(t),"function"==typeof(n=t.stateNode).componentWillUnmount&&function(e,t){try{t.props=e.memoizedProps,t.state=e.memoizedState,t.componentWillUnmount()}catch(t){vl(e,t)}}(t,n);break;case 5:ts(t);break;case 4:us(e,t,n)}}function ss(e){var t=e.alternate;e.return=null,e.child=null,e.memoizedState=null,e.updateQueue=null,e.dependencies=null,e.alternate=null,e.firstEffect=null,e.lastEffect=null,e.pendingProps=null,e.memoizedProps=null,e.stateNode=null,null!==t&&ss(t)}function ls(e){return 5===e.tag||3===e.tag||4===e.tag}function cs(e){e:{for(var t=e.return;null!==t;){if(ls(t)){var n=t;break e}t=t.return}throw Error(a(160))}switch(t=n.stateNode,n.tag){case 5:var r=!1;break;case 3:case 4:t=t.containerInfo,r=!0;break;default:throw Error(a(161))}16&n.effectTag&&(Ue(t,""),n.effectTag&=-17);e:t:for(n=e;;){for(;null===n.sibling;){if(null===n.return||ls(n.return)){n=null;break e}n=n.return}for(n.sibling.return=n.return,n=n.sibling;5!==n.tag&&6!==n.tag&&18!==n.tag;){if(2&n.effectTag)continue t;if(null===n.child||4===n.tag)continue t;n.child.return=n,n=n.child}if(!(2&n.effectTag)){n=n.stateNode;break e}}r?function e(t,n,r){var o=t.tag,i=5===o||6===o;if(i)t=i?t.stateNode:t.stateNode.instance,n?8===r.nodeType?r.parentNode.insertBefore(t,n):r.insertBefore(t,n):(8===r.nodeType?(n=r.parentNode).insertBefore(t,r):(n=r).appendChild(t),null!==(r=r._reactRootContainer)&&void 0!==r||null!==n.onclick||(n.onclick=cn));else if(4!==o&&null!==(t=t.child))for(e(t,n,r),t=t.sibling;null!==t;)e(t,n,r),t=t.sibling}(e,n,t):function e(t,n,r){var o=t.tag,i=5===o||6===o;if(i)t=i?t.stateNode:t.stateNode.instance,n?r.insertBefore(t,n):r.appendChild(t);else if(4!==o&&null!==(t=t.child))for(e(t,n,r),t=t.sibling;null!==t;)e(t,n,r),t=t.sibling}(e,n,t)}function us(e,t,n){for(var r,o,i=t,s=!1;;){if(!s){s=i.return;e:for(;;){if(null===s)throw Error(a(160));switch(r=s.stateNode,s.tag){case 5:o=!1;break e;case 3:case 4:r=r.containerInfo,o=!0;break e}s=s.return}s=!0}if(5===i.tag||6===i.tag){e:for(var l=e,c=i,u=n,p=c;;)if(as(l,p,u),null!==p.child&&4!==p.tag)p.child.return=p,p=p.child;else{if(p===c)break e;for(;null===p.sibling;){if(null===p.return||p.return===c)break e;p=p.return}p.sibling.return=p.return,p=p.sibling}o?(l=r,c=i.stateNode,8===l.nodeType?l.parentNode.removeChild(c):l.removeChild(c)):r.removeChild(i.stateNode)}else if(4===i.tag){if(null!==i.child){r=i.stateNode.containerInfo,o=!0,i.child.return=i,i=i.child;continue}}else if(as(e,i,n),null!==i.child){i.child.return=i,i=i.child;continue}if(i===t)break;for(;null===i.sibling;){if(null===i.return||i.return===t)return;4===(i=i.return).tag&&(s=!1)}i.sibling.return=i.return,i=i.sibling}}function ps(e,t){switch(t.tag){case 0:case 11:case 14:case 15:case 22:return void rs(3,t);case 1:return;case 5:var n=t.stateNode;if(null!=n){var r=t.memoizedProps,o=null!==e?e.memoizedProps:r;e=t.type;var i=t.updateQueue;if(t.updateQueue=null,null!==i){for(n[En]=r,"input"===e&&"radio"===r.type&&null!=r.name&&_e(n,r),an(e,o),t=an(e,r),o=0;o<i.length;o+=2){var s=i[o],l=i[o+1];"style"===s?nn(n,l):"dangerouslySetInnerHTML"===s?ze(n,l):"children"===s?Ue(n,l):ye(n,s,l,t)}switch(e){case"input":Ee(n,r);break;case"textarea":Pe(n,r);break;case"select":t=n._wrapperState.wasMultiple,n._wrapperState.wasMultiple=!!r.multiple,null!=(e=r.value)?Ce(n,!!r.multiple,e,!1):t!==!!r.multiple&&(null!=r.defaultValue?Ce(n,!!r.multiple,r.defaultValue,!0):Ce(n,!!r.multiple,r.multiple?[]:"",!1))}}}return;case 6:if(null===t.stateNode)throw Error(a(162));return void(t.stateNode.nodeValue=t.memoizedProps);case 3:return void((t=t.stateNode).hydrate&&(t.hydrate=!1,Mt(t.containerInfo)));case 12:return;case 13:if(n=t,null===t.memoizedState?r=!1:(r=!0,n=t.child,Ns=Uo()),null!==n)e:for(e=n;;){if(5===e.tag)i=e.stateNode,r?"function"==typeof(i=i.style).setProperty?i.setProperty("display","none","important"):i.display="none":(i=e.stateNode,o=null!=(o=e.memoizedProps.style)&&o.hasOwnProperty("display")?o.display:null,i.style.display=tn("display",o));else if(6===e.tag)e.stateNode.nodeValue=r?"":e.memoizedProps;else{if(13===e.tag&&null!==e.memoizedState&&null===e.memoizedState.dehydrated){(i=e.child.sibling).return=e,e=i;continue}if(null!==e.child){e.child.return=e,e=e.child;continue}}if(e===n)break;for(;null===e.sibling;){if(null===e.return||e.return===n)break e;e=e.return}e.sibling.return=e.return,e=e.sibling}return void fs(t);case 19:return void fs(t);case 17:return}throw Error(a(163))}function fs(e){var t=e.updateQueue;if(null!==t){e.updateQueue=null;var n=e.stateNode;null===n&&(n=e.stateNode=new Ja),t.forEach((function(t){var r=xl.bind(null,e,t);n.has(t)||(n.add(t),t.then(r,r))}))}}var ds="function"==typeof WeakMap?WeakMap:Map;function hs(e,t,n){(n=li(n,null)).tag=3,n.payload={element:null};var r=t.value;return n.callback=function(){Ms||(Ms=!0,Ds=r),es(e,t)},n}function ms(e,t,n){(n=li(n,null)).tag=3;var r=e.type.getDerivedStateFromError;if("function"==typeof r){var o=t.value;n.payload=function(){return es(e,t),r(o)}}var i=e.stateNode;return null!==i&&"function"==typeof i.componentDidCatch&&(n.callback=function(){"function"!=typeof r&&(null===Fs?Fs=new Set([this]):Fs.add(this),es(e,t));var n=t.stack;this.componentDidCatch(t.value,{componentStack:null!==n?n:""})}),n}var gs,ys=Math.ceil,vs=v.ReactCurrentDispatcher,bs=v.ReactCurrentOwner,xs=0,ws=3,ks=4,Os=0,_s=null,Es=null,Ss=0,Ts=xs,js=null,Cs=1073741823,Is=1073741823,As=null,Ps=0,Rs=!1,Ns=0,Ls=null,Ms=!1,Ds=null,Fs=null,zs=!1,Us=null,Bs=90,$s=null,qs=0,Ws=null,Hs=0;function Vs(){return 0!=(48&Os)?1073741821-(Uo()/10|0):0!==Hs?Hs:Hs=1073741821-(Uo()/10|0)}function Ys(e,t,n){if(0==(2&(t=t.mode)))return 1073741823;var r=Bo();if(0==(4&t))return 99===r?1073741823:1073741822;if(0!=(16&Os))return Ss;if(null!==n)e=Qo(e,0|n.timeoutMs||5e3,250);else switch(r){case 99:e=1073741823;break;case 98:e=Qo(e,150,100);break;case 97:case 96:e=Qo(e,5e3,250);break;case 95:e=2;break;default:throw Error(a(326))}return null!==_s&&e===Ss&&--e,e}function Qs(e,t){if(50<qs)throw qs=0,Ws=null,Error(a(185));if(null!==(e=Gs(e,t))){var n=Bo();1073741823===t?0!=(8&Os)&&0==(48&Os)?Js(e):(Ks(e),0===Os&&Vo()):Ks(e),0==(4&Os)||98!==n&&99!==n||(null===$s?$s=new Map([[e,t]]):(void 0===(n=$s.get(e))||n>t)&&$s.set(e,t))}}function Gs(e,t){e.expirationTime<t&&(e.expirationTime=t);var n=e.alternate;null!==n&&n.expirationTime<t&&(n.expirationTime=t);var r=e.return,o=null;if(null===r&&3===e.tag)o=e.stateNode;else for(;null!==r;){if(n=r.alternate,r.childExpirationTime<t&&(r.childExpirationTime=t),null!==n&&n.childExpirationTime<t&&(n.childExpirationTime=t),null===r.return&&3===r.tag){o=r.stateNode;break}r=r.return}return null!==o&&(_s===o&&(al(t),Ts===ks&&Rl(o,Ss)),Nl(o,t)),o}function Xs(e){var t=e.lastExpiredTime;if(0!==t)return t;if(!Pl(e,t=e.firstPendingTime))return t;var n=e.lastPingedTime;return 2>=(e=n>(e=e.nextKnownPendingLevel)?n:e)&&t!==e?0:e}function Ks(e){if(0!==e.lastExpiredTime)e.callbackExpirationTime=1073741823,e.callbackPriority=99,e.callbackNode=Ho(Js.bind(null,e));else{var t=Xs(e),n=e.callbackNode;if(0===t)null!==n&&(e.callbackNode=null,e.callbackExpirationTime=0,e.callbackPriority=90);else{var r=Vs();if(1073741823===t?r=99:1===t||2===t?r=95:r=0>=(r=10*(1073741821-t)-10*(1073741821-r))?99:250>=r?98:5250>=r?97:95,null!==n){var o=e.callbackPriority;if(e.callbackExpirationTime===t&&o>=r)return;n!==Ro&&_o(n)}e.callbackExpirationTime=t,e.callbackPriority=r,t=1073741823===t?Ho(Js.bind(null,e)):Wo(r,Zs.bind(null,e),{timeout:10*(1073741821-t)-Uo()}),e.callbackNode=t}}}function Zs(e,t){if(Hs=0,t)return Ll(e,t=Vs()),Ks(e),null;var n=Xs(e);if(0!==n){if(t=e.callbackNode,0!=(48&Os))throw Error(a(327));if(ml(),e===_s&&n===Ss||nl(e,n),null!==Es){var r=Os;Os|=16;for(var o=ol();;)try{ll();break}catch(t){rl(e,t)}if(ei(),Os=r,vs.current=o,1===Ts)throw t=js,nl(e,n),Rl(e,n),Ks(e),t;if(null===Es)switch(o=e.finishedWork=e.current.alternate,e.finishedExpirationTime=n,r=Ts,_s=null,r){case xs:case 1:throw Error(a(345));case 2:Ll(e,2<n?2:n);break;case ws:if(Rl(e,n),n===(r=e.lastSuspendedTime)&&(e.nextKnownPendingLevel=pl(o)),1073741823===Cs&&10<(o=Ns+500-Uo())){if(Rs){var i=e.lastPingedTime;if(0===i||i>=n){e.lastPingedTime=n,nl(e,n);break}}if(0!==(i=Xs(e))&&i!==n)break;if(0!==r&&r!==n){e.lastPingedTime=r;break}e.timeoutHandle=bn(fl.bind(null,e),o);break}fl(e);break;case ks:if(Rl(e,n),n===(r=e.lastSuspendedTime)&&(e.nextKnownPendingLevel=pl(o)),Rs&&(0===(o=e.lastPingedTime)||o>=n)){e.lastPingedTime=n,nl(e,n);break}if(0!==(o=Xs(e))&&o!==n)break;if(0!==r&&r!==n){e.lastPingedTime=r;break}if(1073741823!==Is?r=10*(1073741821-Is)-Uo():1073741823===Cs?r=0:(r=10*(1073741821-Cs)-5e3,0>(r=(o=Uo())-r)&&(r=0),(n=10*(1073741821-n)-o)<(r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*ys(r/1960))-r)&&(r=n)),10<r){e.timeoutHandle=bn(fl.bind(null,e),r);break}fl(e);break;case 5:if(1073741823!==Cs&&null!==As){i=Cs;var s=As;if(0>=(r=0|s.busyMinDurationMs)?r=0:(o=0|s.busyDelayMs,r=(i=Uo()-(10*(1073741821-i)-(0|s.timeoutMs||5e3)))<=o?0:o+r-i),10<r){Rl(e,n),e.timeoutHandle=bn(fl.bind(null,e),r);break}}fl(e);break;default:throw Error(a(329))}if(Ks(e),e.callbackNode===t)return Zs.bind(null,e)}}return null}function Js(e){var t=e.lastExpiredTime;if(t=0!==t?t:1073741823,0!=(48&Os))throw Error(a(327));if(ml(),e===_s&&t===Ss||nl(e,t),null!==Es){var n=Os;Os|=16;for(var r=ol();;)try{sl();break}catch(t){rl(e,t)}if(ei(),Os=n,vs.current=r,1===Ts)throw n=js,nl(e,t),Rl(e,t),Ks(e),n;if(null!==Es)throw Error(a(261));e.finishedWork=e.current.alternate,e.finishedExpirationTime=t,_s=null,fl(e),Ks(e)}return null}function el(e,t){var n=Os;Os|=1;try{return e(t)}finally{0===(Os=n)&&Vo()}}function tl(e,t){var n=Os;Os&=-2,Os|=8;try{return e(t)}finally{0===(Os=n)&&Vo()}}function nl(e,t){e.finishedWork=null,e.finishedExpirationTime=0;var n=e.timeoutHandle;if(-1!==n&&(e.timeoutHandle=-1,xn(n)),null!==Es)for(n=Es.return;null!==n;){var r=n;switch(r.tag){case 1:null!=(r=r.type.childContextTypes)&&yo();break;case 3:Ri(),lo(fo),lo(po);break;case 5:Li(r);break;case 4:Ri();break;case 13:case 19:lo(Mi);break;case 10:ti(r)}n=n.return}_s=e,Es=Sl(e.current,null),Ss=t,Ts=xs,js=null,Is=Cs=1073741823,As=null,Ps=0,Rs=!1}function rl(e,t){for(;;){try{if(ei(),zi.current=ga,Hi)for(var n=$i.memoizedState;null!==n;){var r=n.queue;null!==r&&(r.pending=null),n=n.next}if(Bi=0,Wi=qi=$i=null,Hi=!1,null===Es||null===Es.return)return Ts=1,js=t,Es=null;e:{var o=e,i=Es.return,a=Es,s=t;if(t=Ss,a.effectTag|=2048,a.firstEffect=a.lastEffect=null,null!==s&&"object"==typeof s&&"function"==typeof s.then){var l=s;if(0==(2&a.mode)){var c=a.alternate;c?(a.memoizedState=c.memoizedState,a.expirationTime=c.expirationTime):a.memoizedState=null}var u=0!=(1&Mi.current),p=i;do{var f;if(f=13===p.tag){var d=p.memoizedState;if(null!==d)f=null!==d.dehydrated;else{var h=p.memoizedProps;f=void 0!==h.fallback&&(!0!==h.unstable_avoidThisFallback||!u)}}if(f){var m=p.updateQueue;if(null===m){var g=new Set;g.add(l),p.updateQueue=g}else m.add(l);if(0==(2&p.mode)){if(p.effectTag|=64,a.effectTag&=-2981,1===a.tag)if(null===a.alternate)a.tag=17;else{var y=li(1073741823,null);y.tag=2,ci(a,y)}a.expirationTime=1073741823;break e}s=void 0,a=t;var v=o.pingCache;if(null===v?(v=o.pingCache=new ds,s=new Set,v.set(l,s)):void 0===(s=v.get(l))&&(s=new Set,v.set(l,s)),!s.has(a)){s.add(a);var b=bl.bind(null,o,l,a);l.then(b,b)}p.effectTag|=4096,p.expirationTime=t;break e}p=p.return}while(null!==p);s=Error((D(a.type)||"A React component")+" suspended while rendering, but no fallback UI was specified.\n\nAdd a <Suspense fallback=...> component higher in the tree to provide a loading indicator or placeholder to display."+F(a))}5!==Ts&&(Ts=2),s=Za(s,a),p=i;do{switch(p.tag){case 3:l=s,p.effectTag|=4096,p.expirationTime=t,ui(p,hs(p,l,t));break e;case 1:l=s;var x=p.type,w=p.stateNode;if(0==(64&p.effectTag)&&("function"==typeof x.getDerivedStateFromError||null!==w&&"function"==typeof w.componentDidCatch&&(null===Fs||!Fs.has(w)))){p.effectTag|=4096,p.expirationTime=t,ui(p,ms(p,l,t));break e}}p=p.return}while(null!==p)}Es=ul(Es)}catch(e){t=e;continue}break}}function ol(){var e=vs.current;return vs.current=ga,null===e?ga:e}function il(e,t){e<Cs&&2<e&&(Cs=e),null!==t&&e<Is&&2<e&&(Is=e,As=t)}function al(e){e>Ps&&(Ps=e)}function sl(){for(;null!==Es;)Es=cl(Es)}function ll(){for(;null!==Es&&!No();)Es=cl(Es)}function cl(e){var t=gs(e.alternate,e,Ss);return e.memoizedProps=e.pendingProps,null===t&&(t=ul(e)),bs.current=null,t}function ul(e){Es=e;do{var t=Es.alternate;if(e=Es.return,0==(2048&Es.effectTag)){if(t=Xa(t,Es,Ss),1===Ss||1!==Es.childExpirationTime){for(var n=0,r=Es.child;null!==r;){var o=r.expirationTime,i=r.childExpirationTime;o>n&&(n=o),i>n&&(n=i),r=r.sibling}Es.childExpirationTime=n}if(null!==t)return t;null!==e&&0==(2048&e.effectTag)&&(null===e.firstEffect&&(e.firstEffect=Es.firstEffect),null!==Es.lastEffect&&(null!==e.lastEffect&&(e.lastEffect.nextEffect=Es.firstEffect),e.lastEffect=Es.lastEffect),1<Es.effectTag&&(null!==e.lastEffect?e.lastEffect.nextEffect=Es:e.firstEffect=Es,e.lastEffect=Es))}else{if(null!==(t=Ka(Es)))return t.effectTag&=2047,t;null!==e&&(e.firstEffect=e.lastEffect=null,e.effectTag|=2048)}if(null!==(t=Es.sibling))return t;Es=e}while(null!==Es);return Ts===xs&&(Ts=5),null}function pl(e){var t=e.expirationTime;return t>(e=e.childExpirationTime)?t:e}function fl(e){var t=Bo();return qo(99,dl.bind(null,e,t)),null}function dl(e,t){do{ml()}while(null!==Us);if(0!=(48&Os))throw Error(a(327));var n=e.finishedWork,r=e.finishedExpirationTime;if(null===n)return null;if(e.finishedWork=null,e.finishedExpirationTime=0,n===e.current)throw Error(a(177));e.callbackNode=null,e.callbackExpirationTime=0,e.callbackPriority=90,e.nextKnownPendingLevel=0;var o=pl(n);if(e.firstPendingTime=o,r<=e.lastSuspendedTime?e.firstSuspendedTime=e.lastSuspendedTime=e.nextKnownPendingLevel=0:r<=e.firstSuspendedTime&&(e.firstSuspendedTime=r-1),r<=e.lastPingedTime&&(e.lastPingedTime=0),r<=e.lastExpiredTime&&(e.lastExpiredTime=0),e===_s&&(Es=_s=null,Ss=0),1<n.effectTag?null!==n.lastEffect?(n.lastEffect.nextEffect=n,o=n.firstEffect):o=n:o=n.firstEffect,null!==o){var i=Os;Os|=32,bs.current=null,mn=Vt;var s=dn();if(hn(s)){if("selectionStart"in s)var l={start:s.selectionStart,end:s.selectionEnd};else e:{var c=(l=(l=s.ownerDocument)&&l.defaultView||window).getSelection&&l.getSelection();if(c&&0!==c.rangeCount){l=c.anchorNode;var u=c.anchorOffset,p=c.focusNode;c=c.focusOffset;try{l.nodeType,p.nodeType}catch(e){l=null;break e}var f=0,d=-1,h=-1,m=0,g=0,y=s,v=null;t:for(;;){for(var b;y!==l||0!==u&&3!==y.nodeType||(d=f+u),y!==p||0!==c&&3!==y.nodeType||(h=f+c),3===y.nodeType&&(f+=y.nodeValue.length),null!==(b=y.firstChild);)v=y,y=b;for(;;){if(y===s)break t;if(v===l&&++m===u&&(d=f),v===p&&++g===c&&(h=f),null!==(b=y.nextSibling))break;v=(y=v).parentNode}y=b}l=-1===d||-1===h?null:{start:d,end:h}}else l=null}l=l||{start:0,end:0}}else l=null;gn={activeElementDetached:null,focusedElem:s,selectionRange:l},Vt=!1,Ls=o;do{try{hl()}catch(e){if(null===Ls)throw Error(a(330));vl(Ls,e),Ls=Ls.nextEffect}}while(null!==Ls);Ls=o;do{try{for(s=e,l=t;null!==Ls;){var x=Ls.effectTag;if(16&x&&Ue(Ls.stateNode,""),128&x){var w=Ls.alternate;if(null!==w){var k=w.ref;null!==k&&("function"==typeof k?k(null):k.current=null)}}switch(1038&x){case 2:cs(Ls),Ls.effectTag&=-3;break;case 6:cs(Ls),Ls.effectTag&=-3,ps(Ls.alternate,Ls);break;case 1024:Ls.effectTag&=-1025;break;case 1028:Ls.effectTag&=-1025,ps(Ls.alternate,Ls);break;case 4:ps(Ls.alternate,Ls);break;case 8:us(s,u=Ls,l),ss(u)}Ls=Ls.nextEffect}}catch(e){if(null===Ls)throw Error(a(330));vl(Ls,e),Ls=Ls.nextEffect}}while(null!==Ls);if(k=gn,w=dn(),x=k.focusedElem,l=k.selectionRange,w!==x&&x&&x.ownerDocument&&function e(t,n){return!(!t||!n)&&(t===n||(!t||3!==t.nodeType)&&(n&&3===n.nodeType?e(t,n.parentNode):"contains"in t?t.contains(n):!!t.compareDocumentPosition&&!!(16&t.compareDocumentPosition(n))))}(x.ownerDocument.documentElement,x)){null!==l&&hn(x)&&(w=l.start,void 0===(k=l.end)&&(k=w),"selectionStart"in x?(x.selectionStart=w,x.selectionEnd=Math.min(k,x.value.length)):(k=(w=x.ownerDocument||document)&&w.defaultView||window).getSelection&&(k=k.getSelection(),u=x.textContent.length,s=Math.min(l.start,u),l=void 0===l.end?s:Math.min(l.end,u),!k.extend&&s>l&&(u=l,l=s,s=u),u=fn(x,s),p=fn(x,l),u&&p&&(1!==k.rangeCount||k.anchorNode!==u.node||k.anchorOffset!==u.offset||k.focusNode!==p.node||k.focusOffset!==p.offset)&&((w=w.createRange()).setStart(u.node,u.offset),k.removeAllRanges(),s>l?(k.addRange(w),k.extend(p.node,p.offset)):(w.setEnd(p.node,p.offset),k.addRange(w))))),w=[];for(k=x;k=k.parentNode;)1===k.nodeType&&w.push({element:k,left:k.scrollLeft,top:k.scrollTop});for("function"==typeof x.focus&&x.focus(),x=0;x<w.length;x++)(k=w[x]).element.scrollLeft=k.left,k.element.scrollTop=k.top}Vt=!!mn,gn=mn=null,e.current=n,Ls=o;do{try{for(x=e;null!==Ls;){var O=Ls.effectTag;if(36&O&&is(x,Ls.alternate,Ls),128&O){w=void 0;var _=Ls.ref;if(null!==_){var E=Ls.stateNode;switch(Ls.tag){case 5:w=E;break;default:w=E}"function"==typeof _?_(w):_.current=w}}Ls=Ls.nextEffect}}catch(e){if(null===Ls)throw Error(a(330));vl(Ls,e),Ls=Ls.nextEffect}}while(null!==Ls);Ls=null,Lo(),Os=i}else e.current=n;if(zs)zs=!1,Us=e,Bs=t;else for(Ls=o;null!==Ls;)t=Ls.nextEffect,Ls.nextEffect=null,Ls=t;if(0===(t=e.firstPendingTime)&&(Fs=null),1073741823===t?e===Ws?qs++:(qs=0,Ws=e):qs=0,"function"==typeof wl&&wl(n.stateNode,r),Ks(e),Ms)throw Ms=!1,e=Ds,Ds=null,e;return 0!=(8&Os)||Vo(),null}function hl(){for(;null!==Ls;){var e=Ls.effectTag;0!=(256&e)&&ns(Ls.alternate,Ls),0==(512&e)||zs||(zs=!0,Wo(97,(function(){return ml(),null}))),Ls=Ls.nextEffect}}function ml(){if(90!==Bs){var e=97<Bs?97:Bs;return Bs=90,qo(e,gl)}}function gl(){if(null===Us)return!1;var e=Us;if(Us=null,0!=(48&Os))throw Error(a(331));var t=Os;for(Os|=32,e=e.current.firstEffect;null!==e;){try{var n=e;if(0!=(512&n.effectTag))switch(n.tag){case 0:case 11:case 15:case 22:rs(5,n),os(5,n)}}catch(t){if(null===e)throw Error(a(330));vl(e,t)}n=e.nextEffect,e.nextEffect=null,e=n}return Os=t,Vo(),!0}function yl(e,t,n){ci(e,t=hs(e,t=Za(n,t),1073741823)),null!==(e=Gs(e,1073741823))&&Ks(e)}function vl(e,t){if(3===e.tag)yl(e,e,t);else for(var n=e.return;null!==n;){if(3===n.tag){yl(n,e,t);break}if(1===n.tag){var r=n.stateNode;if("function"==typeof n.type.getDerivedStateFromError||"function"==typeof r.componentDidCatch&&(null===Fs||!Fs.has(r))){ci(n,e=ms(n,e=Za(t,e),1073741823)),null!==(n=Gs(n,1073741823))&&Ks(n);break}}n=n.return}}function bl(e,t,n){var r=e.pingCache;null!==r&&r.delete(t),_s===e&&Ss===n?Ts===ks||Ts===ws&&1073741823===Cs&&Uo()-Ns<500?nl(e,Ss):Rs=!0:Pl(e,n)&&(0!==(t=e.lastPingedTime)&&t<n||(e.lastPingedTime=n,Ks(e)))}function xl(e,t){var n=e.stateNode;null!==n&&n.delete(t),0===(t=0)&&(t=Ys(t=Vs(),e,null)),null!==(e=Gs(e,t))&&Ks(e)}gs=function(e,t,n){var r=t.expirationTime;if(null!==e){var o=t.pendingProps;if(e.memoizedProps!==o||fo.current)Ia=!0;else{if(r<n){switch(Ia=!1,t.tag){case 3:za(t),ja();break;case 5:if(Ni(t),4&t.mode&&1!==n&&o.hidden)return t.expirationTime=t.childExpirationTime=1,null;break;case 1:go(t.type)&&xo(t);break;case 4:Pi(t,t.stateNode.containerInfo);break;case 10:r=t.memoizedProps.value,o=t.type._context,co(Xo,o._currentValue),o._currentValue=r;break;case 13:if(null!==t.memoizedState)return 0!==(r=t.child.childExpirationTime)&&r>=n?Wa(e,t,n):(co(Mi,1&Mi.current),null!==(t=Qa(e,t,n))?t.sibling:null);co(Mi,1&Mi.current);break;case 19:if(r=t.childExpirationTime>=n,0!=(64&e.effectTag)){if(r)return Ya(e,t,n);t.effectTag|=64}if(null!==(o=t.memoizedState)&&(o.rendering=null,o.tail=null),co(Mi,Mi.current),!r)return null}return Qa(e,t,n)}Ia=!1}}else Ia=!1;switch(t.expirationTime=0,t.tag){case 2:if(r=t.type,null!==e&&(e.alternate=null,t.alternate=null,t.effectTag|=2),e=t.pendingProps,o=mo(t,po.current),ri(t,n),o=Qi(null,t,r,e,o,n),t.effectTag|=1,"object"==typeof o&&null!==o&&"function"==typeof o.render&&void 0===o.$$typeof){if(t.tag=1,t.memoizedState=null,t.updateQueue=null,go(r)){var i=!0;xo(t)}else i=!1;t.memoizedState=null!==o.state&&void 0!==o.state?o.state:null,ai(t);var s=r.getDerivedStateFromProps;"function"==typeof s&&mi(t,r,s,e),o.updater=gi,t.stateNode=o,o._reactInternalFiber=t,xi(t,r,e,n),t=Fa(null,t,r,!0,i,n)}else t.tag=0,Aa(null,t,o,n),t=t.child;return t;case 16:e:{if(o=t.elementType,null!==e&&(e.alternate=null,t.alternate=null,t.effectTag|=2),e=t.pendingProps,function(e){if(-1===e._status){e._status=0;var t=e._ctor;t=t(),e._result=t,t.then((function(t){0===e._status&&(t=t.default,e._status=1,e._result=t)}),(function(t){0===e._status&&(e._status=2,e._result=t)}))}}(o),1!==o._status)throw o._result;switch(o=o._result,t.type=o,i=t.tag=function(e){if("function"==typeof e)return El(e)?1:0;if(null!=e){if((e=e.$$typeof)===C)return 11;if(e===P)return 14}return 2}(o),e=Go(o,e),i){case 0:t=Ma(null,t,o,e,n);break e;case 1:t=Da(null,t,o,e,n);break e;case 11:t=Pa(null,t,o,e,n);break e;case 14:t=Ra(null,t,o,Go(o.type,e),r,n);break e}throw Error(a(306,o,""))}return t;case 0:return r=t.type,o=t.pendingProps,Ma(e,t,r,o=t.elementType===r?o:Go(r,o),n);case 1:return r=t.type,o=t.pendingProps,Da(e,t,r,o=t.elementType===r?o:Go(r,o),n);case 3:if(za(t),r=t.updateQueue,null===e||null===r)throw Error(a(282));if(r=t.pendingProps,o=null!==(o=t.memoizedState)?o.element:null,si(e,t),pi(t,r,null,n),(r=t.memoizedState.element)===o)ja(),t=Qa(e,t,n);else{if((o=t.stateNode.hydrate)&&(wa=wn(t.stateNode.containerInfo.firstChild),xa=t,o=ka=!0),o)for(n=Si(t,null,r,n),t.child=n;n;)n.effectTag=-3&n.effectTag|1024,n=n.sibling;else Aa(e,t,r,n),ja();t=t.child}return t;case 5:return Ni(t),null===e&&Ea(t),r=t.type,o=t.pendingProps,i=null!==e?e.memoizedProps:null,s=o.children,vn(r,o)?s=null:null!==i&&vn(r,i)&&(t.effectTag|=16),La(e,t),4&t.mode&&1!==n&&o.hidden?(t.expirationTime=t.childExpirationTime=1,t=null):(Aa(e,t,s,n),t=t.child),t;case 6:return null===e&&Ea(t),null;case 13:return Wa(e,t,n);case 4:return Pi(t,t.stateNode.containerInfo),r=t.pendingProps,null===e?t.child=Ei(t,null,r,n):Aa(e,t,r,n),t.child;case 11:return r=t.type,o=t.pendingProps,Pa(e,t,r,o=t.elementType===r?o:Go(r,o),n);case 7:return Aa(e,t,t.pendingProps,n),t.child;case 8:case 12:return Aa(e,t,t.pendingProps.children,n),t.child;case 10:e:{r=t.type._context,o=t.pendingProps,s=t.memoizedProps,i=o.value;var l=t.type._context;if(co(Xo,l._currentValue),l._currentValue=i,null!==s)if(l=s.value,0===(i=Dr(l,i)?0:0|("function"==typeof r._calculateChangedBits?r._calculateChangedBits(l,i):1073741823))){if(s.children===o.children&&!fo.current){t=Qa(e,t,n);break e}}else for(null!==(l=t.child)&&(l.return=t);null!==l;){var c=l.dependencies;if(null!==c){s=l.child;for(var u=c.firstContext;null!==u;){if(u.context===r&&0!=(u.observedBits&i)){1===l.tag&&((u=li(n,null)).tag=2,ci(l,u)),l.expirationTime<n&&(l.expirationTime=n),null!==(u=l.alternate)&&u.expirationTime<n&&(u.expirationTime=n),ni(l.return,n),c.expirationTime<n&&(c.expirationTime=n);break}u=u.next}}else s=10===l.tag&&l.type===t.type?null:l.child;if(null!==s)s.return=l;else for(s=l;null!==s;){if(s===t){s=null;break}if(null!==(l=s.sibling)){l.return=s.return,s=l;break}s=s.return}l=s}Aa(e,t,o.children,n),t=t.child}return t;case 9:return o=t.type,r=(i=t.pendingProps).children,ri(t,n),r=r(o=oi(o,i.unstable_observedBits)),t.effectTag|=1,Aa(e,t,r,n),t.child;case 14:return i=Go(o=t.type,t.pendingProps),Ra(e,t,o,i=Go(o.type,i),r,n);case 15:return Na(e,t,t.type,t.pendingProps,r,n);case 17:return r=t.type,o=t.pendingProps,o=t.elementType===r?o:Go(r,o),null!==e&&(e.alternate=null,t.alternate=null,t.effectTag|=2),t.tag=1,go(r)?(e=!0,xo(t)):e=!1,ri(t,n),vi(t,r,o),xi(t,r,o,n),Fa(null,t,r,!0,e,n);case 19:return Ya(e,t,n)}throw Error(a(156,t.tag))};var wl=null,kl=null;function Ol(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.effectTag=0,this.lastEffect=this.firstEffect=this.nextEffect=null,this.childExpirationTime=this.expirationTime=0,this.alternate=null}function _l(e,t,n,r){return new Ol(e,t,n,r)}function El(e){return!(!(e=e.prototype)||!e.isReactComponent)}function Sl(e,t){var n=e.alternate;return null===n?((n=_l(e.tag,t,e.key,e.mode)).elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.effectTag=0,n.nextEffect=null,n.firstEffect=null,n.lastEffect=null),n.childExpirationTime=e.childExpirationTime,n.expirationTime=e.expirationTime,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=null===t?null:{expirationTime:t.expirationTime,firstContext:t.firstContext,responders:t.responders},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function Tl(e,t,n,r,o,i){var s=2;if(r=e,"function"==typeof e)El(e)&&(s=1);else if("string"==typeof e)s=5;else e:switch(e){case O:return jl(n.children,o,i,t);case j:s=8,o|=7;break;case _:s=8,o|=1;break;case E:return(e=_l(12,n,t,8|o)).elementType=E,e.type=E,e.expirationTime=i,e;case I:return(e=_l(13,n,t,o)).type=I,e.elementType=I,e.expirationTime=i,e;case A:return(e=_l(19,n,t,o)).elementType=A,e.expirationTime=i,e;default:if("object"==typeof e&&null!==e)switch(e.$$typeof){case S:s=10;break e;case T:s=9;break e;case C:s=11;break e;case P:s=14;break e;case R:s=16,r=null;break e;case N:s=22;break e}throw Error(a(130,null==e?e:typeof e,""))}return(t=_l(s,n,t,o)).elementType=e,t.type=r,t.expirationTime=i,t}function jl(e,t,n,r){return(e=_l(7,e,r,t)).expirationTime=n,e}function Cl(e,t,n){return(e=_l(6,e,null,t)).expirationTime=n,e}function Il(e,t,n){return(t=_l(4,null!==e.children?e.children:[],e.key,t)).expirationTime=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function Al(e,t,n){this.tag=t,this.current=null,this.containerInfo=e,this.pingCache=this.pendingChildren=null,this.finishedExpirationTime=0,this.finishedWork=null,this.timeoutHandle=-1,this.pendingContext=this.context=null,this.hydrate=n,this.callbackNode=null,this.callbackPriority=90,this.lastExpiredTime=this.lastPingedTime=this.nextKnownPendingLevel=this.lastSuspendedTime=this.firstSuspendedTime=this.firstPendingTime=0}function Pl(e,t){var n=e.firstSuspendedTime;return e=e.lastSuspendedTime,0!==n&&n>=t&&e<=t}function Rl(e,t){var n=e.firstSuspendedTime,r=e.lastSuspendedTime;n<t&&(e.firstSuspendedTime=t),(r>t||0===n)&&(e.lastSuspendedTime=t),t<=e.lastPingedTime&&(e.lastPingedTime=0),t<=e.lastExpiredTime&&(e.lastExpiredTime=0)}function Nl(e,t){t>e.firstPendingTime&&(e.firstPendingTime=t);var n=e.firstSuspendedTime;0!==n&&(t>=n?e.firstSuspendedTime=e.lastSuspendedTime=e.nextKnownPendingLevel=0:t>=e.lastSuspendedTime&&(e.lastSuspendedTime=t+1),t>e.nextKnownPendingLevel&&(e.nextKnownPendingLevel=t))}function Ll(e,t){var n=e.lastExpiredTime;(0===n||n>t)&&(e.lastExpiredTime=t)}function Ml(e,t,n,r){var o=t.current,i=Vs(),s=di.suspense;i=Ys(i,o,s);e:if(n){t:{if(Je(n=n._reactInternalFiber)!==n||1!==n.tag)throw Error(a(170));var l=n;do{switch(l.tag){case 3:l=l.stateNode.context;break t;case 1:if(go(l.type)){l=l.stateNode.__reactInternalMemoizedMergedChildContext;break t}}l=l.return}while(null!==l);throw Error(a(171))}if(1===n.tag){var c=n.type;if(go(c)){n=bo(n,c,l);break e}}n=l}else n=uo;return null===t.context?t.context=n:t.pendingContext=n,(t=li(i,s)).payload={element:e},null!==(r=void 0===r?null:r)&&(t.callback=r),ci(o,t),Qs(o,i),i}function Dl(e){if(!(e=e.current).child)return null;switch(e.child.tag){case 5:default:return e.child.stateNode}}function Fl(e,t){null!==(e=e.memoizedState)&&null!==e.dehydrated&&e.retryTime<t&&(e.retryTime=t)}function zl(e,t){Fl(e,t),(e=e.alternate)&&Fl(e,t)}function Ul(e,t,n){var r=new Al(e,t,n=null!=n&&!0===n.hydrate),o=_l(3,null,null,2===t?7:1===t?3:0);r.current=o,o.stateNode=r,ai(o),e[Sn]=r.current,n&&0!==t&&function(e,t){var n=Ze(t);St.forEach((function(e){ht(e,t,n)})),Tt.forEach((function(e){ht(e,t,n)}))}(0,9===e.nodeType?e:e.ownerDocument),this._internalRoot=r}function Bl(e){return!(!e||1!==e.nodeType&&9!==e.nodeType&&11!==e.nodeType&&(8!==e.nodeType||" react-mount-point-unstable "!==e.nodeValue))}function $l(e,t,n,r,o){var i=n._reactRootContainer;if(i){var a=i._internalRoot;if("function"==typeof o){var s=o;o=function(){var e=Dl(a);s.call(e)}}Ml(t,a,e,o)}else{if(i=n._reactRootContainer=function(e,t){if(t||(t=!(!(t=e?9===e.nodeType?e.documentElement:e.firstChild:null)||1!==t.nodeType||!t.hasAttribute("data-reactroot"))),!t)for(var n;n=e.lastChild;)e.removeChild(n);return new Ul(e,0,t?{hydrate:!0}:void 0)}(n,r),a=i._internalRoot,"function"==typeof o){var l=o;o=function(){var e=Dl(a);l.call(e)}}tl((function(){Ml(t,a,e,o)}))}return Dl(a)}function ql(e,t,n){var r=3<arguments.length&&void 0!==arguments[3]?arguments[3]:null;return{$$typeof:k,key:null==r?null:""+r,children:e,containerInfo:t,implementation:n}}function Wl(e,t){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:null;if(!Bl(t))throw Error(a(200));return ql(e,t,null,n)}Ul.prototype.render=function(e){Ml(e,this._internalRoot,null,null)},Ul.prototype.unmount=function(){var e=this._internalRoot,t=e.containerInfo;Ml(null,e,null,(function(){t[Sn]=null}))},mt=function(e){if(13===e.tag){var t=Qo(Vs(),150,100);Qs(e,t),zl(e,t)}},gt=function(e){13===e.tag&&(Qs(e,3),zl(e,3))},yt=function(e){if(13===e.tag){var t=Vs();Qs(e,t=Ys(t,e,null)),zl(e,t)}},G=function(e,t,n){switch(t){case"input":if(Ee(e,n),t=n.name,"radio"===n.type&&null!=t){for(n=e;n.parentNode;)n=n.parentNode;for(n=n.querySelectorAll("input[name="+JSON.stringify(""+t)+'][type="radio"]'),t=0;t<n.length;t++){var r=n[t];if(r!==e&&r.form===e.form){var o=In(r);if(!o)throw Error(a(90));we(r),Ee(r,o)}}}break;case"textarea":Pe(e,n);break;case"select":null!=(t=n.value)&&Ce(e,!!n.multiple,t,!1)}},te=el,ne=function(e,t,n,r,o){var i=Os;Os|=4;try{return qo(98,e.bind(null,t,n,r,o))}finally{0===(Os=i)&&Vo()}},re=function(){0==(49&Os)&&(function(){if(null!==$s){var e=$s;$s=null,e.forEach((function(e,t){Ll(t,e),Ks(t)})),Vo()}}(),ml())},oe=function(e,t){var n=Os;Os|=2;try{return e(t)}finally{0===(Os=n)&&Vo()}};var Hl,Vl,Yl={Events:[jn,Cn,In,Y,W,Dn,function(e){ot(e,Mn)},J,ee,Kt,st,ml,{current:!1}]};Vl=(Hl={findFiberByHostInstance:Tn,bundleType:0,version:"16.13.0",rendererPackageName:"react-dom"}).findFiberByHostInstance,function(e){if("undefined"==typeof __REACT_DEVTOOLS_GLOBAL_HOOK__)return!1;var t=__REACT_DEVTOOLS_GLOBAL_HOOK__;if(t.isDisabled||!t.supportsFiber)return!0;try{var n=t.inject(e);wl=function(e){try{t.onCommitFiberRoot(n,e,void 0,64==(64&e.current.effectTag))}catch(e){}},kl=function(e){try{t.onCommitFiberUnmount(n,e)}catch(e){}}}catch(e){}}(o({},Hl,{overrideHookState:null,overrideProps:null,setSuspenseHandler:null,scheduleUpdate:null,currentDispatcherRef:v.ReactCurrentDispatcher,findHostInstanceByFiber:function(e){return null===(e=nt(e))?null:e.stateNode},findFiberByHostInstance:function(e){return Vl?Vl(e):null},findHostInstancesForRefresh:null,scheduleRefresh:null,scheduleRoot:null,setRefreshHandler:null,getCurrentFiber:null})),t.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=Yl,t.createPortal=Wl,t.findDOMNode=function(e){if(null==e)return null;if(1===e.nodeType)return e;var t=e._reactInternalFiber;if(void 0===t){if("function"==typeof e.render)throw Error(a(188));throw Error(a(268,Object.keys(e)))}return e=null===(e=nt(t))?null:e.stateNode},t.flushSync=function(e,t){if(0!=(48&Os))throw Error(a(187));var n=Os;Os|=1;try{return qo(99,e.bind(null,t))}finally{Os=n,Vo()}},t.hydrate=function(e,t,n){if(!Bl(t))throw Error(a(200));return $l(null,e,t,!0,n)},t.render=function(e,t,n){if(!Bl(t))throw Error(a(200));return $l(null,e,t,!1,n)},t.unmountComponentAtNode=function(e){if(!Bl(e))throw Error(a(40));return!!e._reactRootContainer&&(tl((function(){$l(null,null,e,!1,(function(){e._reactRootContainer=null,e[Sn]=null}))})),!0)},t.unstable_batchedUpdates=el,t.unstable_createPortal=function(e,t){return Wl(e,t,2<arguments.length&&void 0!==arguments[2]?arguments[2]:null)},t.unstable_renderSubtreeIntoContainer=function(e,t,n,r){if(!Bl(n))throw Error(a(200));if(null==e||void 0===e._reactInternalFiber)throw Error(a(38));return $l(e,t,n,!1,r)},t.version="16.13.0"},function(e,t,n){"use strict";e.exports=n(229)},function(e,t,n){"use strict"; /** @license React v0.19.0 * scheduler.production.min.js * @@ -125,7 +125,7 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - */var r,o,i,a,s;if("undefined"==typeof window||"function"!=typeof MessageChannel){var l=null,c=null,u=function(){if(null!==l)try{var e=t.unstable_now();l(!0,e),l=null}catch(e){throw setTimeout(u,0),e}},p=Date.now();t.unstable_now=function(){return Date.now()-p},r=function(e){null!==l?setTimeout(r,0,e):(l=e,setTimeout(u,0))},o=function(e,t){c=setTimeout(e,t)},i=function(){clearTimeout(c)},a=function(){return!1},s=t.unstable_forceFrameRate=function(){}}else{var f=window.performance,d=window.Date,h=window.setTimeout,m=window.clearTimeout;if("undefined"!=typeof console){var g=window.cancelAnimationFrame;"function"!=typeof window.requestAnimationFrame&&console.error("This browser doesn't support requestAnimationFrame. Make sure that you load a polyfill in older browsers. https://fb.me/react-polyfills"),"function"!=typeof g&&console.error("This browser doesn't support cancelAnimationFrame. Make sure that you load a polyfill in older browsers. https://fb.me/react-polyfills")}if("object"==typeof f&&"function"==typeof f.now)t.unstable_now=function(){return f.now()};else{var y=d.now();t.unstable_now=function(){return d.now()-y}}var v=!1,b=null,x=-1,w=5,k=0;a=function(){return t.unstable_now()>=k},s=function(){},t.unstable_forceFrameRate=function(e){0>e||125<e?console.error("forceFrameRate takes a positive int between 0 and 125, forcing framerates higher than 125 fps is not unsupported"):w=0<e?Math.floor(1e3/e):5};var O=new MessageChannel,_=O.port2;O.port1.onmessage=function(){if(null!==b){var e=t.unstable_now();k=e+w;try{b(!0,e)?_.postMessage(null):(v=!1,b=null)}catch(e){throw _.postMessage(null),e}}else v=!1},r=function(e){b=e,v||(v=!0,_.postMessage(null))},o=function(e,n){x=h((function(){e(t.unstable_now())}),n)},i=function(){m(x),x=-1}}function E(e,t){var n=e.length;e.push(t);e:for(;;){var r=n-1>>>1,o=e[r];if(!(void 0!==o&&0<j(o,t)))break e;e[r]=t,e[n]=o,n=r}}function S(e){return void 0===(e=e[0])?null:e}function T(e){var t=e[0];if(void 0!==t){var n=e.pop();if(n!==t){e[0]=n;e:for(var r=0,o=e.length;r<o;){var i=2*(r+1)-1,a=e[i],s=i+1,l=e[s];if(void 0!==a&&0>j(a,n))void 0!==l&&0>j(l,a)?(e[r]=l,e[s]=n,r=s):(e[r]=a,e[i]=n,r=i);else{if(!(void 0!==l&&0>j(l,n)))break e;e[r]=l,e[s]=n,r=s}}}return t}return null}function j(e,t){var n=e.sortIndex-t.sortIndex;return 0!==n?n:e.id-t.id}var C=[],A=[],I=1,P=null,R=3,N=!1,L=!1,M=!1;function D(e){for(var t=S(A);null!==t;){if(null===t.callback)T(A);else{if(!(t.startTime<=e))break;T(A),t.sortIndex=t.expirationTime,E(C,t)}t=S(A)}}function F(e){if(M=!1,D(e),!L)if(null!==S(C))L=!0,r(z);else{var t=S(A);null!==t&&o(F,t.startTime-e)}}function z(e,n){L=!1,M&&(M=!1,i()),N=!0;var r=R;try{for(D(n),P=S(C);null!==P&&(!(P.expirationTime>n)||e&&!a());){var s=P.callback;if(null!==s){P.callback=null,R=P.priorityLevel;var l=s(P.expirationTime<=n);n=t.unstable_now(),"function"==typeof l?P.callback=l:P===S(C)&&T(C),D(n)}else T(C);P=S(C)}if(null!==P)var c=!0;else{var u=S(A);null!==u&&o(F,u.startTime-n),c=!1}return c}finally{P=null,R=r,N=!1}}function U(e){switch(e){case 1:return-1;case 2:return 250;case 5:return 1073741823;case 4:return 1e4;default:return 5e3}}var B=s;t.unstable_IdlePriority=5,t.unstable_ImmediatePriority=1,t.unstable_LowPriority=4,t.unstable_NormalPriority=3,t.unstable_Profiling=null,t.unstable_UserBlockingPriority=2,t.unstable_cancelCallback=function(e){e.callback=null},t.unstable_continueExecution=function(){L||N||(L=!0,r(z))},t.unstable_getCurrentPriorityLevel=function(){return R},t.unstable_getFirstCallbackNode=function(){return S(C)},t.unstable_next=function(e){switch(R){case 1:case 2:case 3:var t=3;break;default:t=R}var n=R;R=t;try{return e()}finally{R=n}},t.unstable_pauseExecution=function(){},t.unstable_requestPaint=B,t.unstable_runWithPriority=function(e,t){switch(e){case 1:case 2:case 3:case 4:case 5:break;default:e=3}var n=R;R=e;try{return t()}finally{R=n}},t.unstable_scheduleCallback=function(e,n,a){var s=t.unstable_now();if("object"==typeof a&&null!==a){var l=a.delay;l="number"==typeof l&&0<l?s+l:s,a="number"==typeof a.timeout?a.timeout:U(e)}else a=U(e),l=s;return e={id:I++,callback:n,priorityLevel:e,startTime:l,expirationTime:a=l+a,sortIndex:-1},l>s?(e.sortIndex=l,E(A,e),null===S(C)&&e===S(A)&&(M?i():M=!0,o(F,l-s))):(e.sortIndex=a,E(C,e),L||N||(L=!0,r(z))),e},t.unstable_shouldYield=function(){var e=t.unstable_now();D(e);var n=S(C);return n!==P&&null!==P&&null!==n&&null!==n.callback&&n.startTime<=e&&n.expirationTime<P.expirationTime||a()},t.unstable_wrapCallback=function(e){var t=R;return function(){var n=R;R=t;try{return e.apply(this,arguments)}finally{R=n}}}},function(e,t,n){"use strict";var r=n(231);function o(){}function i(){}i.resetWarningCache=o,e.exports=function(){function e(e,t,n,o,i,a){if(a!==r){var s=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw s.name="Invariant Violation",s}}function t(){return e}e.isRequired=e;var n={array:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:i,resetWarningCache:o};return n.PropTypes=n,n}},function(e,t,n){"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},function(e,t,n){(function(e,r){var o;/*! https://mths.be/punycode v1.4.1 by @mathias */!function(i){t&&t.nodeType,e&&e.nodeType;var a="object"==typeof r&&r;a.global!==a&&a.window!==a&&a.self;var s,l=2147483647,c=/^xn--/,u=/[^\x20-\x7E]/,p=/[\x2E\u3002\uFF0E\uFF61]/g,f={overflow:"Overflow: input needs wider integers to process","not-basic":"Illegal input >= 0x80 (not a basic code point)","invalid-input":"Invalid input"},d=Math.floor,h=String.fromCharCode;function m(e){throw new RangeError(f[e])}function g(e,t){for(var n=e.length,r=[];n--;)r[n]=t(e[n]);return r}function y(e,t){var n=e.split("@"),r="";return n.length>1&&(r=n[0]+"@",e=n[1]),r+g((e=e.replace(p,".")).split("."),t).join(".")}function v(e){for(var t,n,r=[],o=0,i=e.length;o<i;)(t=e.charCodeAt(o++))>=55296&&t<=56319&&o<i?56320==(64512&(n=e.charCodeAt(o++)))?r.push(((1023&t)<<10)+(1023&n)+65536):(r.push(t),o--):r.push(t);return r}function b(e){return g(e,(function(e){var t="";return e>65535&&(t+=h((e-=65536)>>>10&1023|55296),e=56320|1023&e),t+=h(e)})).join("")}function x(e,t){return e+22+75*(e<26)-((0!=t)<<5)}function w(e,t,n){var r=0;for(e=n?d(e/700):e>>1,e+=d(e/t);e>455;r+=36)e=d(e/35);return d(r+36*e/(e+38))}function k(e){var t,n,r,o,i,a,s,c,u,p,f,h=[],g=e.length,y=0,v=128,x=72;for((n=e.lastIndexOf("-"))<0&&(n=0),r=0;r<n;++r)e.charCodeAt(r)>=128&&m("not-basic"),h.push(e.charCodeAt(r));for(o=n>0?n+1:0;o<g;){for(i=y,a=1,s=36;o>=g&&m("invalid-input"),((c=(f=e.charCodeAt(o++))-48<10?f-22:f-65<26?f-65:f-97<26?f-97:36)>=36||c>d((l-y)/a))&&m("overflow"),y+=c*a,!(c<(u=s<=x?1:s>=x+26?26:s-x));s+=36)a>d(l/(p=36-u))&&m("overflow"),a*=p;x=w(y-i,t=h.length+1,0==i),d(y/t)>l-v&&m("overflow"),v+=d(y/t),y%=t,h.splice(y++,0,v)}return b(h)}function O(e){var t,n,r,o,i,a,s,c,u,p,f,g,y,b,k,O=[];for(g=(e=v(e)).length,t=128,n=0,i=72,a=0;a<g;++a)(f=e[a])<128&&O.push(h(f));for(r=o=O.length,o&&O.push("-");r<g;){for(s=l,a=0;a<g;++a)(f=e[a])>=t&&f<s&&(s=f);for(s-t>d((l-n)/(y=r+1))&&m("overflow"),n+=(s-t)*y,t=s,a=0;a<g;++a)if((f=e[a])<t&&++n>l&&m("overflow"),f==t){for(c=n,u=36;!(c<(p=u<=i?1:u>=i+26?26:u-i));u+=36)k=c-p,b=36-p,O.push(h(x(p+k%b,0))),c=d(k/b);O.push(h(x(c,0))),i=w(n,y,r==o),n=0,++r}++n,++t}return O.join("")}s={version:"1.4.1",ucs2:{decode:v,encode:b},decode:k,encode:O,toASCII:function(e){return y(e,(function(e){return u.test(e)?"xn--"+O(e):e}))},toUnicode:function(e){return y(e,(function(e){return c.test(e)?k(e.slice(4).toLowerCase()):e}))}},void 0===(o=function(){return s}.call(t,n,t,e))||(e.exports=o)}()}).call(this,n(233)(e),n(7))},function(e,t){e.exports=function(e){return e.webpackPolyfill||(e.deprecate=function(){},e.paths=[],e.children||(e.children=[]),Object.defineProperty(e,"loaded",{enumerable:!0,get:function(){return e.l}}),Object.defineProperty(e,"id",{enumerable:!0,get:function(){return e.i}}),e.webpackPolyfill=1),e}},function(e,t,n){"use strict";e.exports={isString:function(e){return"string"==typeof e},isObject:function(e){return"object"==typeof e&&null!==e},isNull:function(e){return null===e},isNullOrUndefined:function(e){return null==e}}},function(e,t,n){"use strict";t.decode=t.parse=n(236),t.encode=t.stringify=n(237)},function(e,t,n){"use strict";function r(e,t){return Object.prototype.hasOwnProperty.call(e,t)}e.exports=function(e,t,n,i){t=t||"&",n=n||"=";var a={};if("string"!=typeof e||0===e.length)return a;var s=/\+/g;e=e.split(t);var l=1e3;i&&"number"==typeof i.maxKeys&&(l=i.maxKeys);var c=e.length;l>0&&c>l&&(c=l);for(var u=0;u<c;++u){var p,f,d,h,m=e[u].replace(s,"%20"),g=m.indexOf(n);g>=0?(p=m.substr(0,g),f=m.substr(g+1)):(p=m,f=""),d=decodeURIComponent(p),h=decodeURIComponent(f),r(a,d)?o(a[d])?a[d].push(h):a[d]=[a[d],h]:a[d]=h}return a};var o=Array.isArray||function(e){return"[object Array]"===Object.prototype.toString.call(e)}},function(e,t,n){"use strict";var r=function(e){switch(typeof e){case"string":return e;case"boolean":return e?"true":"false";case"number":return isFinite(e)?e:"";default:return""}};e.exports=function(e,t,n,s){return t=t||"&",n=n||"=",null===e&&(e=void 0),"object"==typeof e?i(a(e),(function(a){var s=encodeURIComponent(r(a))+n;return o(e[a])?i(e[a],(function(e){return s+encodeURIComponent(r(e))})).join(t):s+encodeURIComponent(r(e[a]))})).join(t):s?encodeURIComponent(r(s))+n+encodeURIComponent(r(e)):""};var o=Array.isArray||function(e){return"[object Array]"===Object.prototype.toString.call(e)};function i(e,t){if(e.map)return e.map(t);for(var n=[],r=0;r<e.length;r++)n.push(t(e[r],r));return n}var a=Object.keys||function(e){var t=[];for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&t.push(n);return t}},function(e,t,n){"use strict"; + */var r,o,i,a,s;if("undefined"==typeof window||"function"!=typeof MessageChannel){var l=null,c=null,u=function(){if(null!==l)try{var e=t.unstable_now();l(!0,e),l=null}catch(e){throw setTimeout(u,0),e}},p=Date.now();t.unstable_now=function(){return Date.now()-p},r=function(e){null!==l?setTimeout(r,0,e):(l=e,setTimeout(u,0))},o=function(e,t){c=setTimeout(e,t)},i=function(){clearTimeout(c)},a=function(){return!1},s=t.unstable_forceFrameRate=function(){}}else{var f=window.performance,d=window.Date,h=window.setTimeout,m=window.clearTimeout;if("undefined"!=typeof console){var g=window.cancelAnimationFrame;"function"!=typeof window.requestAnimationFrame&&console.error("This browser doesn't support requestAnimationFrame. Make sure that you load a polyfill in older browsers. https://fb.me/react-polyfills"),"function"!=typeof g&&console.error("This browser doesn't support cancelAnimationFrame. Make sure that you load a polyfill in older browsers. https://fb.me/react-polyfills")}if("object"==typeof f&&"function"==typeof f.now)t.unstable_now=function(){return f.now()};else{var y=d.now();t.unstable_now=function(){return d.now()-y}}var v=!1,b=null,x=-1,w=5,k=0;a=function(){return t.unstable_now()>=k},s=function(){},t.unstable_forceFrameRate=function(e){0>e||125<e?console.error("forceFrameRate takes a positive int between 0 and 125, forcing framerates higher than 125 fps is not unsupported"):w=0<e?Math.floor(1e3/e):5};var O=new MessageChannel,_=O.port2;O.port1.onmessage=function(){if(null!==b){var e=t.unstable_now();k=e+w;try{b(!0,e)?_.postMessage(null):(v=!1,b=null)}catch(e){throw _.postMessage(null),e}}else v=!1},r=function(e){b=e,v||(v=!0,_.postMessage(null))},o=function(e,n){x=h((function(){e(t.unstable_now())}),n)},i=function(){m(x),x=-1}}function E(e,t){var n=e.length;e.push(t);e:for(;;){var r=n-1>>>1,o=e[r];if(!(void 0!==o&&0<j(o,t)))break e;e[r]=t,e[n]=o,n=r}}function S(e){return void 0===(e=e[0])?null:e}function T(e){var t=e[0];if(void 0!==t){var n=e.pop();if(n!==t){e[0]=n;e:for(var r=0,o=e.length;r<o;){var i=2*(r+1)-1,a=e[i],s=i+1,l=e[s];if(void 0!==a&&0>j(a,n))void 0!==l&&0>j(l,a)?(e[r]=l,e[s]=n,r=s):(e[r]=a,e[i]=n,r=i);else{if(!(void 0!==l&&0>j(l,n)))break e;e[r]=l,e[s]=n,r=s}}}return t}return null}function j(e,t){var n=e.sortIndex-t.sortIndex;return 0!==n?n:e.id-t.id}var C=[],I=[],A=1,P=null,R=3,N=!1,L=!1,M=!1;function D(e){for(var t=S(I);null!==t;){if(null===t.callback)T(I);else{if(!(t.startTime<=e))break;T(I),t.sortIndex=t.expirationTime,E(C,t)}t=S(I)}}function F(e){if(M=!1,D(e),!L)if(null!==S(C))L=!0,r(z);else{var t=S(I);null!==t&&o(F,t.startTime-e)}}function z(e,n){L=!1,M&&(M=!1,i()),N=!0;var r=R;try{for(D(n),P=S(C);null!==P&&(!(P.expirationTime>n)||e&&!a());){var s=P.callback;if(null!==s){P.callback=null,R=P.priorityLevel;var l=s(P.expirationTime<=n);n=t.unstable_now(),"function"==typeof l?P.callback=l:P===S(C)&&T(C),D(n)}else T(C);P=S(C)}if(null!==P)var c=!0;else{var u=S(I);null!==u&&o(F,u.startTime-n),c=!1}return c}finally{P=null,R=r,N=!1}}function U(e){switch(e){case 1:return-1;case 2:return 250;case 5:return 1073741823;case 4:return 1e4;default:return 5e3}}var B=s;t.unstable_IdlePriority=5,t.unstable_ImmediatePriority=1,t.unstable_LowPriority=4,t.unstable_NormalPriority=3,t.unstable_Profiling=null,t.unstable_UserBlockingPriority=2,t.unstable_cancelCallback=function(e){e.callback=null},t.unstable_continueExecution=function(){L||N||(L=!0,r(z))},t.unstable_getCurrentPriorityLevel=function(){return R},t.unstable_getFirstCallbackNode=function(){return S(C)},t.unstable_next=function(e){switch(R){case 1:case 2:case 3:var t=3;break;default:t=R}var n=R;R=t;try{return e()}finally{R=n}},t.unstable_pauseExecution=function(){},t.unstable_requestPaint=B,t.unstable_runWithPriority=function(e,t){switch(e){case 1:case 2:case 3:case 4:case 5:break;default:e=3}var n=R;R=e;try{return t()}finally{R=n}},t.unstable_scheduleCallback=function(e,n,a){var s=t.unstable_now();if("object"==typeof a&&null!==a){var l=a.delay;l="number"==typeof l&&0<l?s+l:s,a="number"==typeof a.timeout?a.timeout:U(e)}else a=U(e),l=s;return e={id:A++,callback:n,priorityLevel:e,startTime:l,expirationTime:a=l+a,sortIndex:-1},l>s?(e.sortIndex=l,E(I,e),null===S(C)&&e===S(I)&&(M?i():M=!0,o(F,l-s))):(e.sortIndex=a,E(C,e),L||N||(L=!0,r(z))),e},t.unstable_shouldYield=function(){var e=t.unstable_now();D(e);var n=S(C);return n!==P&&null!==P&&null!==n&&null!==n.callback&&n.startTime<=e&&n.expirationTime<P.expirationTime||a()},t.unstable_wrapCallback=function(e){var t=R;return function(){var n=R;R=t;try{return e.apply(this,arguments)}finally{R=n}}}},function(e,t,n){"use strict";var r=n(231);function o(){}function i(){}i.resetWarningCache=o,e.exports=function(){function e(e,t,n,o,i,a){if(a!==r){var s=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw s.name="Invariant Violation",s}}function t(){return e}e.isRequired=e;var n={array:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:i,resetWarningCache:o};return n.PropTypes=n,n}},function(e,t,n){"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},function(e,t,n){(function(e,r){var o;/*! https://mths.be/punycode v1.4.1 by @mathias */!function(i){t&&t.nodeType,e&&e.nodeType;var a="object"==typeof r&&r;a.global!==a&&a.window!==a&&a.self;var s,l=2147483647,c=/^xn--/,u=/[^\x20-\x7E]/,p=/[\x2E\u3002\uFF0E\uFF61]/g,f={overflow:"Overflow: input needs wider integers to process","not-basic":"Illegal input >= 0x80 (not a basic code point)","invalid-input":"Invalid input"},d=Math.floor,h=String.fromCharCode;function m(e){throw new RangeError(f[e])}function g(e,t){for(var n=e.length,r=[];n--;)r[n]=t(e[n]);return r}function y(e,t){var n=e.split("@"),r="";return n.length>1&&(r=n[0]+"@",e=n[1]),r+g((e=e.replace(p,".")).split("."),t).join(".")}function v(e){for(var t,n,r=[],o=0,i=e.length;o<i;)(t=e.charCodeAt(o++))>=55296&&t<=56319&&o<i?56320==(64512&(n=e.charCodeAt(o++)))?r.push(((1023&t)<<10)+(1023&n)+65536):(r.push(t),o--):r.push(t);return r}function b(e){return g(e,(function(e){var t="";return e>65535&&(t+=h((e-=65536)>>>10&1023|55296),e=56320|1023&e),t+=h(e)})).join("")}function x(e,t){return e+22+75*(e<26)-((0!=t)<<5)}function w(e,t,n){var r=0;for(e=n?d(e/700):e>>1,e+=d(e/t);e>455;r+=36)e=d(e/35);return d(r+36*e/(e+38))}function k(e){var t,n,r,o,i,a,s,c,u,p,f,h=[],g=e.length,y=0,v=128,x=72;for((n=e.lastIndexOf("-"))<0&&(n=0),r=0;r<n;++r)e.charCodeAt(r)>=128&&m("not-basic"),h.push(e.charCodeAt(r));for(o=n>0?n+1:0;o<g;){for(i=y,a=1,s=36;o>=g&&m("invalid-input"),((c=(f=e.charCodeAt(o++))-48<10?f-22:f-65<26?f-65:f-97<26?f-97:36)>=36||c>d((l-y)/a))&&m("overflow"),y+=c*a,!(c<(u=s<=x?1:s>=x+26?26:s-x));s+=36)a>d(l/(p=36-u))&&m("overflow"),a*=p;x=w(y-i,t=h.length+1,0==i),d(y/t)>l-v&&m("overflow"),v+=d(y/t),y%=t,h.splice(y++,0,v)}return b(h)}function O(e){var t,n,r,o,i,a,s,c,u,p,f,g,y,b,k,O=[];for(g=(e=v(e)).length,t=128,n=0,i=72,a=0;a<g;++a)(f=e[a])<128&&O.push(h(f));for(r=o=O.length,o&&O.push("-");r<g;){for(s=l,a=0;a<g;++a)(f=e[a])>=t&&f<s&&(s=f);for(s-t>d((l-n)/(y=r+1))&&m("overflow"),n+=(s-t)*y,t=s,a=0;a<g;++a)if((f=e[a])<t&&++n>l&&m("overflow"),f==t){for(c=n,u=36;!(c<(p=u<=i?1:u>=i+26?26:u-i));u+=36)k=c-p,b=36-p,O.push(h(x(p+k%b,0))),c=d(k/b);O.push(h(x(c,0))),i=w(n,y,r==o),n=0,++r}++n,++t}return O.join("")}s={version:"1.4.1",ucs2:{decode:v,encode:b},decode:k,encode:O,toASCII:function(e){return y(e,(function(e){return u.test(e)?"xn--"+O(e):e}))},toUnicode:function(e){return y(e,(function(e){return c.test(e)?k(e.slice(4).toLowerCase()):e}))}},void 0===(o=function(){return s}.call(t,n,t,e))||(e.exports=o)}()}).call(this,n(233)(e),n(7))},function(e,t){e.exports=function(e){return e.webpackPolyfill||(e.deprecate=function(){},e.paths=[],e.children||(e.children=[]),Object.defineProperty(e,"loaded",{enumerable:!0,get:function(){return e.l}}),Object.defineProperty(e,"id",{enumerable:!0,get:function(){return e.i}}),e.webpackPolyfill=1),e}},function(e,t,n){"use strict";e.exports={isString:function(e){return"string"==typeof e},isObject:function(e){return"object"==typeof e&&null!==e},isNull:function(e){return null===e},isNullOrUndefined:function(e){return null==e}}},function(e,t,n){"use strict";t.decode=t.parse=n(236),t.encode=t.stringify=n(237)},function(e,t,n){"use strict";function r(e,t){return Object.prototype.hasOwnProperty.call(e,t)}e.exports=function(e,t,n,i){t=t||"&",n=n||"=";var a={};if("string"!=typeof e||0===e.length)return a;var s=/\+/g;e=e.split(t);var l=1e3;i&&"number"==typeof i.maxKeys&&(l=i.maxKeys);var c=e.length;l>0&&c>l&&(c=l);for(var u=0;u<c;++u){var p,f,d,h,m=e[u].replace(s,"%20"),g=m.indexOf(n);g>=0?(p=m.substr(0,g),f=m.substr(g+1)):(p=m,f=""),d=decodeURIComponent(p),h=decodeURIComponent(f),r(a,d)?o(a[d])?a[d].push(h):a[d]=[a[d],h]:a[d]=h}return a};var o=Array.isArray||function(e){return"[object Array]"===Object.prototype.toString.call(e)}},function(e,t,n){"use strict";var r=function(e){switch(typeof e){case"string":return e;case"boolean":return e?"true":"false";case"number":return isFinite(e)?e:"";default:return""}};e.exports=function(e,t,n,s){return t=t||"&",n=n||"=",null===e&&(e=void 0),"object"==typeof e?i(a(e),(function(a){var s=encodeURIComponent(r(a))+n;return o(e[a])?i(e[a],(function(e){return s+encodeURIComponent(r(e))})).join(t):s+encodeURIComponent(r(e[a]))})).join(t):s?encodeURIComponent(r(s))+n+encodeURIComponent(r(e)):""};var o=Array.isArray||function(e){return"[object Array]"===Object.prototype.toString.call(e)};function i(e,t){if(e.map)return e.map(t);for(var n=[],r=0;r<e.length;r++)n.push(t(e[r],r));return n}var a=Object.keys||function(e){var t=[];for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&t.push(n);return t}},function(e,t,n){"use strict"; /** @license React v16.13.0 * react-is.production.min.js * @@ -133,7 +133,7 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - */var r="function"==typeof Symbol&&Symbol.for,o=r?Symbol.for("react.element"):60103,i=r?Symbol.for("react.portal"):60106,a=r?Symbol.for("react.fragment"):60107,s=r?Symbol.for("react.strict_mode"):60108,l=r?Symbol.for("react.profiler"):60114,c=r?Symbol.for("react.provider"):60109,u=r?Symbol.for("react.context"):60110,p=r?Symbol.for("react.async_mode"):60111,f=r?Symbol.for("react.concurrent_mode"):60111,d=r?Symbol.for("react.forward_ref"):60112,h=r?Symbol.for("react.suspense"):60113,m=r?Symbol.for("react.suspense_list"):60120,g=r?Symbol.for("react.memo"):60115,y=r?Symbol.for("react.lazy"):60116,v=r?Symbol.for("react.block"):60121,b=r?Symbol.for("react.fundamental"):60117,x=r?Symbol.for("react.responder"):60118,w=r?Symbol.for("react.scope"):60119;function k(e){if("object"==typeof e&&null!==e){var t=e.$$typeof;switch(t){case o:switch(e=e.type){case p:case f:case a:case l:case s:case h:return e;default:switch(e=e&&e.$$typeof){case u:case d:case y:case g:case c:return e;default:return t}}case i:return t}}}function O(e){return k(e)===f}t.AsyncMode=p,t.ConcurrentMode=f,t.ContextConsumer=u,t.ContextProvider=c,t.Element=o,t.ForwardRef=d,t.Fragment=a,t.Lazy=y,t.Memo=g,t.Portal=i,t.Profiler=l,t.StrictMode=s,t.Suspense=h,t.isAsyncMode=function(e){return O(e)||k(e)===p},t.isConcurrentMode=O,t.isContextConsumer=function(e){return k(e)===u},t.isContextProvider=function(e){return k(e)===c},t.isElement=function(e){return"object"==typeof e&&null!==e&&e.$$typeof===o},t.isForwardRef=function(e){return k(e)===d},t.isFragment=function(e){return k(e)===a},t.isLazy=function(e){return k(e)===y},t.isMemo=function(e){return k(e)===g},t.isPortal=function(e){return k(e)===i},t.isProfiler=function(e){return k(e)===l},t.isStrictMode=function(e){return k(e)===s},t.isSuspense=function(e){return k(e)===h},t.isValidElementType=function(e){return"string"==typeof e||"function"==typeof e||e===a||e===f||e===l||e===s||e===h||e===m||"object"==typeof e&&null!==e&&(e.$$typeof===y||e.$$typeof===g||e.$$typeof===c||e.$$typeof===u||e.$$typeof===d||e.$$typeof===b||e.$$typeof===x||e.$$typeof===w||e.$$typeof===v)},t.typeOf=k},function(e,t,n){"use strict";t.byteLength=function(e){var t=c(e),n=t[0],r=t[1];return 3*(n+r)/4-r},t.toByteArray=function(e){var t,n,r=c(e),a=r[0],s=r[1],l=new i(function(e,t,n){return 3*(t+n)/4-n}(0,a,s)),u=0,p=s>0?a-4:a;for(n=0;n<p;n+=4)t=o[e.charCodeAt(n)]<<18|o[e.charCodeAt(n+1)]<<12|o[e.charCodeAt(n+2)]<<6|o[e.charCodeAt(n+3)],l[u++]=t>>16&255,l[u++]=t>>8&255,l[u++]=255&t;2===s&&(t=o[e.charCodeAt(n)]<<2|o[e.charCodeAt(n+1)]>>4,l[u++]=255&t);1===s&&(t=o[e.charCodeAt(n)]<<10|o[e.charCodeAt(n+1)]<<4|o[e.charCodeAt(n+2)]>>2,l[u++]=t>>8&255,l[u++]=255&t);return l},t.fromByteArray=function(e){for(var t,n=e.length,o=n%3,i=[],a=0,s=n-o;a<s;a+=16383)i.push(u(e,a,a+16383>s?s:a+16383));1===o?(t=e[n-1],i.push(r[t>>2]+r[t<<4&63]+"==")):2===o&&(t=(e[n-2]<<8)+e[n-1],i.push(r[t>>10]+r[t>>4&63]+r[t<<2&63]+"="));return i.join("")};for(var r=[],o=[],i="undefined"!=typeof Uint8Array?Uint8Array:Array,a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",s=0,l=a.length;s<l;++s)r[s]=a[s],o[a.charCodeAt(s)]=s;function c(e){var t=e.length;if(t%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var n=e.indexOf("=");return-1===n&&(n=t),[n,n===t?0:4-n%4]}function u(e,t,n){for(var o,i,a=[],s=t;s<n;s+=3)o=(e[s]<<16&16711680)+(e[s+1]<<8&65280)+(255&e[s+2]),a.push(r[(i=o)>>18&63]+r[i>>12&63]+r[i>>6&63]+r[63&i]);return a.join("")}o["-".charCodeAt(0)]=62,o["_".charCodeAt(0)]=63},function(e,t){t.read=function(e,t,n,r,o){var i,a,s=8*o-r-1,l=(1<<s)-1,c=l>>1,u=-7,p=n?o-1:0,f=n?-1:1,d=e[t+p];for(p+=f,i=d&(1<<-u)-1,d>>=-u,u+=s;u>0;i=256*i+e[t+p],p+=f,u-=8);for(a=i&(1<<-u)-1,i>>=-u,u+=r;u>0;a=256*a+e[t+p],p+=f,u-=8);if(0===i)i=1-c;else{if(i===l)return a?NaN:1/0*(d?-1:1);a+=Math.pow(2,r),i-=c}return(d?-1:1)*a*Math.pow(2,i-r)},t.write=function(e,t,n,r,o,i){var a,s,l,c=8*i-o-1,u=(1<<c)-1,p=u>>1,f=23===o?Math.pow(2,-24)-Math.pow(2,-77):0,d=r?0:i-1,h=r?1:-1,m=t<0||0===t&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(s=isNaN(t)?1:0,a=u):(a=Math.floor(Math.log(t)/Math.LN2),t*(l=Math.pow(2,-a))<1&&(a--,l*=2),(t+=a+p>=1?f/l:f*Math.pow(2,1-p))*l>=2&&(a++,l/=2),a+p>=u?(s=0,a=u):a+p>=1?(s=(t*l-1)*Math.pow(2,o),a+=p):(s=t*Math.pow(2,p-1)*Math.pow(2,o),a=0));o>=8;e[n+d]=255&s,d+=h,s/=256,o-=8);for(a=a<<o|s,c+=o;c>0;e[n+d]=255&a,d+=h,a/=256,c-=8);e[n+d-h]|=128*m}},function(e,t,n){"use strict";(function(t){e.exports={order:100,allowEmpty:!0,canParse:".json",parse:function(e){return new Promise((function(n,r){var o=e.data;t.isBuffer(o)&&(o=o.toString()),"string"==typeof o?0===o.trim().length?n(void 0):n(JSON.parse(o)):n(o)}))}}}).call(this,n(14).Buffer)},function(e,t,n){"use strict";(function(t){var r=n(133);e.exports={order:200,allowEmpty:!0,canParse:[".yaml",".yml",".json"],parse:function(e){return new Promise((function(n,o){var i=e.data;t.isBuffer(i)&&(i=i.toString()),n("string"==typeof i?r.parse(i):i)}))}}}).call(this,n(14).Buffer)},function(e,t,n){"use strict";var r=n(244);e.exports=r},function(e,t,n){"use strict";var r=n(245),o=n(264);function i(e){return function(){throw new Error("Function "+e+" is deprecated and cannot be used.")}}e.exports.Type=n(6),e.exports.Schema=n(39),e.exports.FAILSAFE_SCHEMA=n(89),e.exports.JSON_SCHEMA=n(135),e.exports.CORE_SCHEMA=n(134),e.exports.DEFAULT_SAFE_SCHEMA=n(60),e.exports.DEFAULT_FULL_SCHEMA=n(90),e.exports.load=r.load,e.exports.loadAll=r.loadAll,e.exports.safeLoad=r.safeLoad,e.exports.safeLoadAll=r.safeLoadAll,e.exports.dump=o.dump,e.exports.safeDump=o.safeDump,e.exports.YAMLException=n(59),e.exports.MINIMAL_SCHEMA=n(89),e.exports.SAFE_SCHEMA=n(60),e.exports.DEFAULT_SCHEMA=n(90),e.exports.scan=i("scan"),e.exports.parse=i("parse"),e.exports.compose=i("compose"),e.exports.addConstructor=i("addConstructor")},function(e,t,n){"use strict";var r=n(49),o=n(59),i=n(246),a=n(60),s=n(90),l=Object.prototype.hasOwnProperty,c=/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x84\x86-\x9F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/,u=/[\x85\u2028\u2029]/,p=/[,\[\]\{\}]/,f=/^(?:!|!!|![a-z\-]+!)$/i,d=/^(?:!|[^,\[\]\{\}])(?:%[0-9a-f]{2}|[0-9a-z\-#;\/\?:@&=\+\$,_\.!~\*'\(\)\[\]])*$/i;function h(e){return Object.prototype.toString.call(e)}function m(e){return 10===e||13===e}function g(e){return 9===e||32===e}function y(e){return 9===e||32===e||10===e||13===e}function v(e){return 44===e||91===e||93===e||123===e||125===e}function b(e){var t;return 48<=e&&e<=57?e-48:97<=(t=32|e)&&t<=102?t-97+10:-1}function x(e){return 48===e?"\0":97===e?"":98===e?"\b":116===e||9===e?"\t":110===e?"\n":118===e?"\v":102===e?"\f":114===e?"\r":101===e?"":32===e?" ":34===e?'"':47===e?"/":92===e?"\\":78===e?"…":95===e?" ":76===e?"\u2028":80===e?"\u2029":""}function w(e){return e<=65535?String.fromCharCode(e):String.fromCharCode(55296+(e-65536>>10),56320+(e-65536&1023))}for(var k=new Array(256),O=new Array(256),_=0;_<256;_++)k[_]=x(_)?1:0,O[_]=x(_);function E(e,t){this.input=e,this.filename=t.filename||null,this.schema=t.schema||s,this.onWarning=t.onWarning||null,this.legacy=t.legacy||!1,this.json=t.json||!1,this.listener=t.listener||null,this.implicitTypes=this.schema.compiledImplicit,this.typeMap=this.schema.compiledTypeMap,this.length=e.length,this.position=0,this.line=0,this.lineStart=0,this.lineIndent=0,this.documents=[]}function S(e,t){return new o(t,new i(e.filename,e.input,e.position,e.line,e.position-e.lineStart))}function T(e,t){throw S(e,t)}function j(e,t){e.onWarning&&e.onWarning.call(null,S(e,t))}var C={YAML:function(e,t,n){var r,o,i;null!==e.version&&T(e,"duplication of %YAML directive"),1!==n.length&&T(e,"YAML directive accepts exactly one argument"),null===(r=/^([0-9]+)\.([0-9]+)$/.exec(n[0]))&&T(e,"ill-formed argument of the YAML directive"),o=parseInt(r[1],10),i=parseInt(r[2],10),1!==o&&T(e,"unacceptable YAML version of the document"),e.version=n[0],e.checkLineBreaks=i<2,1!==i&&2!==i&&j(e,"unsupported YAML version of the document")},TAG:function(e,t,n){var r,o;2!==n.length&&T(e,"TAG directive accepts exactly two arguments"),r=n[0],o=n[1],f.test(r)||T(e,"ill-formed tag handle (first argument) of the TAG directive"),l.call(e.tagMap,r)&&T(e,'there is a previously declared suffix for "'+r+'" tag handle'),d.test(o)||T(e,"ill-formed tag prefix (second argument) of the TAG directive"),e.tagMap[r]=o}};function A(e,t,n,r){var o,i,a,s;if(t<n){if(s=e.input.slice(t,n),r)for(o=0,i=s.length;o<i;o+=1)9===(a=s.charCodeAt(o))||32<=a&&a<=1114111||T(e,"expected valid JSON character");else c.test(s)&&T(e,"the stream contains non-printable characters");e.result+=s}}function I(e,t,n,o){var i,a,s,c;for(r.isObject(n)||T(e,"cannot merge mappings; the provided source object is unacceptable"),s=0,c=(i=Object.keys(n)).length;s<c;s+=1)a=i[s],l.call(t,a)||(t[a]=n[a],o[a]=!0)}function P(e,t,n,r,o,i,a,s){var c,u;if(Array.isArray(o))for(c=0,u=(o=Array.prototype.slice.call(o)).length;c<u;c+=1)Array.isArray(o[c])&&T(e,"nested arrays are not supported inside keys"),"object"==typeof o&&"[object Object]"===h(o[c])&&(o[c]="[object Object]");if("object"==typeof o&&"[object Object]"===h(o)&&(o="[object Object]"),o=String(o),null===t&&(t={}),"tag:yaml.org,2002:merge"===r)if(Array.isArray(i))for(c=0,u=i.length;c<u;c+=1)I(e,t,i[c],n);else I(e,t,i,n);else e.json||l.call(n,o)||!l.call(t,o)||(e.line=a||e.line,e.position=s||e.position,T(e,"duplicated mapping key")),t[o]=i,delete n[o];return t}function R(e){var t;10===(t=e.input.charCodeAt(e.position))?e.position++:13===t?(e.position++,10===e.input.charCodeAt(e.position)&&e.position++):T(e,"a line break is expected"),e.line+=1,e.lineStart=e.position}function N(e,t,n){for(var r=0,o=e.input.charCodeAt(e.position);0!==o;){for(;g(o);)o=e.input.charCodeAt(++e.position);if(t&&35===o)do{o=e.input.charCodeAt(++e.position)}while(10!==o&&13!==o&&0!==o);if(!m(o))break;for(R(e),o=e.input.charCodeAt(e.position),r++,e.lineIndent=0;32===o;)e.lineIndent++,o=e.input.charCodeAt(++e.position)}return-1!==n&&0!==r&&e.lineIndent<n&&j(e,"deficient indentation"),r}function L(e){var t,n=e.position;return!(45!==(t=e.input.charCodeAt(n))&&46!==t||t!==e.input.charCodeAt(n+1)||t!==e.input.charCodeAt(n+2)||(n+=3,0!==(t=e.input.charCodeAt(n))&&!y(t)))}function M(e,t){1===t?e.result+=" ":t>1&&(e.result+=r.repeat("\n",t-1))}function D(e,t){var n,r,o=e.tag,i=e.anchor,a=[],s=!1;for(null!==e.anchor&&(e.anchorMap[e.anchor]=a),r=e.input.charCodeAt(e.position);0!==r&&45===r&&y(e.input.charCodeAt(e.position+1));)if(s=!0,e.position++,N(e,!0,-1)&&e.lineIndent<=t)a.push(null),r=e.input.charCodeAt(e.position);else if(n=e.line,U(e,t,3,!1,!0),a.push(e.result),N(e,!0,-1),r=e.input.charCodeAt(e.position),(e.line===n||e.lineIndent>t)&&0!==r)T(e,"bad indentation of a sequence entry");else if(e.lineIndent<t)break;return!!s&&(e.tag=o,e.anchor=i,e.kind="sequence",e.result=a,!0)}function F(e){var t,n,r,o,i=!1,a=!1;if(33!==(o=e.input.charCodeAt(e.position)))return!1;if(null!==e.tag&&T(e,"duplication of a tag property"),60===(o=e.input.charCodeAt(++e.position))?(i=!0,o=e.input.charCodeAt(++e.position)):33===o?(a=!0,n="!!",o=e.input.charCodeAt(++e.position)):n="!",t=e.position,i){do{o=e.input.charCodeAt(++e.position)}while(0!==o&&62!==o);e.position<e.length?(r=e.input.slice(t,e.position),o=e.input.charCodeAt(++e.position)):T(e,"unexpected end of the stream within a verbatim tag")}else{for(;0!==o&&!y(o);)33===o&&(a?T(e,"tag suffix cannot contain exclamation marks"):(n=e.input.slice(t-1,e.position+1),f.test(n)||T(e,"named tag handle cannot contain such characters"),a=!0,t=e.position+1)),o=e.input.charCodeAt(++e.position);r=e.input.slice(t,e.position),p.test(r)&&T(e,"tag suffix cannot contain flow indicator characters")}return r&&!d.test(r)&&T(e,"tag name cannot contain such characters: "+r),i?e.tag=r:l.call(e.tagMap,n)?e.tag=e.tagMap[n]+r:"!"===n?e.tag="!"+r:"!!"===n?e.tag="tag:yaml.org,2002:"+r:T(e,'undeclared tag handle "'+n+'"'),!0}function z(e){var t,n;if(38!==(n=e.input.charCodeAt(e.position)))return!1;for(null!==e.anchor&&T(e,"duplication of an anchor property"),n=e.input.charCodeAt(++e.position),t=e.position;0!==n&&!y(n)&&!v(n);)n=e.input.charCodeAt(++e.position);return e.position===t&&T(e,"name of an anchor node must contain at least one character"),e.anchor=e.input.slice(t,e.position),!0}function U(e,t,n,o,i){var a,s,c,u,p,f,d,h,x=1,_=!1,E=!1;if(null!==e.listener&&e.listener("open",e),e.tag=null,e.anchor=null,e.kind=null,e.result=null,a=s=c=4===n||3===n,o&&N(e,!0,-1)&&(_=!0,e.lineIndent>t?x=1:e.lineIndent===t?x=0:e.lineIndent<t&&(x=-1)),1===x)for(;F(e)||z(e);)N(e,!0,-1)?(_=!0,c=a,e.lineIndent>t?x=1:e.lineIndent===t?x=0:e.lineIndent<t&&(x=-1)):c=!1;if(c&&(c=_||i),1!==x&&4!==n||(d=1===n||2===n?t:t+1,h=e.position-e.lineStart,1===x?c&&(D(e,h)||function(e,t,n){var r,o,i,a,s,l=e.tag,c=e.anchor,u={},p={},f=null,d=null,h=null,m=!1,v=!1;for(null!==e.anchor&&(e.anchorMap[e.anchor]=u),s=e.input.charCodeAt(e.position);0!==s;){if(r=e.input.charCodeAt(e.position+1),i=e.line,a=e.position,63!==s&&58!==s||!y(r)){if(!U(e,n,2,!1,!0))break;if(e.line===i){for(s=e.input.charCodeAt(e.position);g(s);)s=e.input.charCodeAt(++e.position);if(58===s)y(s=e.input.charCodeAt(++e.position))||T(e,"a whitespace character is expected after the key-value separator within a block mapping"),m&&(P(e,u,p,f,d,null),f=d=h=null),v=!0,m=!1,o=!1,f=e.tag,d=e.result;else{if(!v)return e.tag=l,e.anchor=c,!0;T(e,"can not read an implicit mapping pair; a colon is missed")}}else{if(!v)return e.tag=l,e.anchor=c,!0;T(e,"can not read a block mapping entry; a multiline key may not be an implicit key")}}else 63===s?(m&&(P(e,u,p,f,d,null),f=d=h=null),v=!0,m=!0,o=!0):m?(m=!1,o=!0):T(e,"incomplete explicit mapping pair; a key node is missed; or followed by a non-tabulated empty line"),e.position+=1,s=r;if((e.line===i||e.lineIndent>t)&&(U(e,t,4,!0,o)&&(m?d=e.result:h=e.result),m||(P(e,u,p,f,d,h,i,a),f=d=h=null),N(e,!0,-1),s=e.input.charCodeAt(e.position)),e.lineIndent>t&&0!==s)T(e,"bad indentation of a mapping entry");else if(e.lineIndent<t)break}return m&&P(e,u,p,f,d,null),v&&(e.tag=l,e.anchor=c,e.kind="mapping",e.result=u),v}(e,h,d))||function(e,t){var n,r,o,i,a,s,l,c,u,p,f=!0,d=e.tag,h=e.anchor,m={};if(91===(p=e.input.charCodeAt(e.position)))o=93,s=!1,r=[];else{if(123!==p)return!1;o=125,s=!0,r={}}for(null!==e.anchor&&(e.anchorMap[e.anchor]=r),p=e.input.charCodeAt(++e.position);0!==p;){if(N(e,!0,t),(p=e.input.charCodeAt(e.position))===o)return e.position++,e.tag=d,e.anchor=h,e.kind=s?"mapping":"sequence",e.result=r,!0;f||T(e,"missed comma between flow collection entries"),u=null,i=a=!1,63===p&&y(e.input.charCodeAt(e.position+1))&&(i=a=!0,e.position++,N(e,!0,t)),n=e.line,U(e,t,1,!1,!0),c=e.tag,l=e.result,N(e,!0,t),p=e.input.charCodeAt(e.position),!a&&e.line!==n||58!==p||(i=!0,p=e.input.charCodeAt(++e.position),N(e,!0,t),U(e,t,1,!1,!0),u=e.result),s?P(e,r,m,c,l,u):i?r.push(P(e,null,m,c,l,u)):r.push(l),N(e,!0,t),44===(p=e.input.charCodeAt(e.position))?(f=!0,p=e.input.charCodeAt(++e.position)):f=!1}T(e,"unexpected end of the stream within a flow collection")}(e,d)?E=!0:(s&&function(e,t){var n,o,i,a,s,l=1,c=!1,u=!1,p=t,f=0,d=!1;if(124===(a=e.input.charCodeAt(e.position)))o=!1;else{if(62!==a)return!1;o=!0}for(e.kind="scalar",e.result="";0!==a;)if(43===(a=e.input.charCodeAt(++e.position))||45===a)1===l?l=43===a?3:2:T(e,"repeat of a chomping mode identifier");else{if(!((i=48<=(s=a)&&s<=57?s-48:-1)>=0))break;0===i?T(e,"bad explicit indentation width of a block scalar; it cannot be less than one"):u?T(e,"repeat of an indentation width identifier"):(p=t+i-1,u=!0)}if(g(a)){do{a=e.input.charCodeAt(++e.position)}while(g(a));if(35===a)do{a=e.input.charCodeAt(++e.position)}while(!m(a)&&0!==a)}for(;0!==a;){for(R(e),e.lineIndent=0,a=e.input.charCodeAt(e.position);(!u||e.lineIndent<p)&&32===a;)e.lineIndent++,a=e.input.charCodeAt(++e.position);if(!u&&e.lineIndent>p&&(p=e.lineIndent),m(a))f++;else{if(e.lineIndent<p){3===l?e.result+=r.repeat("\n",c?1+f:f):1===l&&c&&(e.result+="\n");break}for(o?g(a)?(d=!0,e.result+=r.repeat("\n",c?1+f:f)):d?(d=!1,e.result+=r.repeat("\n",f+1)):0===f?c&&(e.result+=" "):e.result+=r.repeat("\n",f):e.result+=r.repeat("\n",c?1+f:f),c=!0,u=!0,f=0,n=e.position;!m(a)&&0!==a;)a=e.input.charCodeAt(++e.position);A(e,n,e.position,!1)}}return!0}(e,d)||function(e,t){var n,r,o;if(39!==(n=e.input.charCodeAt(e.position)))return!1;for(e.kind="scalar",e.result="",e.position++,r=o=e.position;0!==(n=e.input.charCodeAt(e.position));)if(39===n){if(A(e,r,e.position,!0),39!==(n=e.input.charCodeAt(++e.position)))return!0;r=e.position,e.position++,o=e.position}else m(n)?(A(e,r,o,!0),M(e,N(e,!1,t)),r=o=e.position):e.position===e.lineStart&&L(e)?T(e,"unexpected end of the document within a single quoted scalar"):(e.position++,o=e.position);T(e,"unexpected end of the stream within a single quoted scalar")}(e,d)||function(e,t){var n,r,o,i,a,s,l;if(34!==(s=e.input.charCodeAt(e.position)))return!1;for(e.kind="scalar",e.result="",e.position++,n=r=e.position;0!==(s=e.input.charCodeAt(e.position));){if(34===s)return A(e,n,e.position,!0),e.position++,!0;if(92===s){if(A(e,n,e.position,!0),m(s=e.input.charCodeAt(++e.position)))N(e,!1,t);else if(s<256&&k[s])e.result+=O[s],e.position++;else if((a=120===(l=s)?2:117===l?4:85===l?8:0)>0){for(o=a,i=0;o>0;o--)(a=b(s=e.input.charCodeAt(++e.position)))>=0?i=(i<<4)+a:T(e,"expected hexadecimal character");e.result+=w(i),e.position++}else T(e,"unknown escape sequence");n=r=e.position}else m(s)?(A(e,n,r,!0),M(e,N(e,!1,t)),n=r=e.position):e.position===e.lineStart&&L(e)?T(e,"unexpected end of the document within a double quoted scalar"):(e.position++,r=e.position)}T(e,"unexpected end of the stream within a double quoted scalar")}(e,d)?E=!0:!function(e){var t,n,r;if(42!==(r=e.input.charCodeAt(e.position)))return!1;for(r=e.input.charCodeAt(++e.position),t=e.position;0!==r&&!y(r)&&!v(r);)r=e.input.charCodeAt(++e.position);return e.position===t&&T(e,"name of an alias node must contain at least one character"),n=e.input.slice(t,e.position),e.anchorMap.hasOwnProperty(n)||T(e,'unidentified alias "'+n+'"'),e.result=e.anchorMap[n],N(e,!0,-1),!0}(e)?function(e,t,n){var r,o,i,a,s,l,c,u,p=e.kind,f=e.result;if(y(u=e.input.charCodeAt(e.position))||v(u)||35===u||38===u||42===u||33===u||124===u||62===u||39===u||34===u||37===u||64===u||96===u)return!1;if((63===u||45===u)&&(y(r=e.input.charCodeAt(e.position+1))||n&&v(r)))return!1;for(e.kind="scalar",e.result="",o=i=e.position,a=!1;0!==u;){if(58===u){if(y(r=e.input.charCodeAt(e.position+1))||n&&v(r))break}else if(35===u){if(y(e.input.charCodeAt(e.position-1)))break}else{if(e.position===e.lineStart&&L(e)||n&&v(u))break;if(m(u)){if(s=e.line,l=e.lineStart,c=e.lineIndent,N(e,!1,-1),e.lineIndent>=t){a=!0,u=e.input.charCodeAt(e.position);continue}e.position=i,e.line=s,e.lineStart=l,e.lineIndent=c;break}}a&&(A(e,o,i,!1),M(e,e.line-s),o=i=e.position,a=!1),g(u)||(i=e.position+1),u=e.input.charCodeAt(++e.position)}return A(e,o,i,!1),!!e.result||(e.kind=p,e.result=f,!1)}(e,d,1===n)&&(E=!0,null===e.tag&&(e.tag="?")):(E=!0,null===e.tag&&null===e.anchor||T(e,"alias node should not have any properties")),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):0===x&&(E=c&&D(e,h))),null!==e.tag&&"!"!==e.tag)if("?"===e.tag){for(u=0,p=e.implicitTypes.length;u<p;u+=1)if((f=e.implicitTypes[u]).resolve(e.result)){e.result=f.construct(e.result),e.tag=f.tag,null!==e.anchor&&(e.anchorMap[e.anchor]=e.result);break}}else l.call(e.typeMap[e.kind||"fallback"],e.tag)?(f=e.typeMap[e.kind||"fallback"][e.tag],null!==e.result&&f.kind!==e.kind&&T(e,"unacceptable node kind for !<"+e.tag+'> tag; it should be "'+f.kind+'", not "'+e.kind+'"'),f.resolve(e.result)?(e.result=f.construct(e.result),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):T(e,"cannot resolve a node with !<"+e.tag+"> explicit tag")):T(e,"unknown tag !<"+e.tag+">");return null!==e.listener&&e.listener("close",e),null!==e.tag||null!==e.anchor||E}function B(e){var t,n,r,o,i=e.position,a=!1;for(e.version=null,e.checkLineBreaks=e.legacy,e.tagMap={},e.anchorMap={};0!==(o=e.input.charCodeAt(e.position))&&(N(e,!0,-1),o=e.input.charCodeAt(e.position),!(e.lineIndent>0||37!==o));){for(a=!0,o=e.input.charCodeAt(++e.position),t=e.position;0!==o&&!y(o);)o=e.input.charCodeAt(++e.position);for(r=[],(n=e.input.slice(t,e.position)).length<1&&T(e,"directive name must not be less than one character in length");0!==o;){for(;g(o);)o=e.input.charCodeAt(++e.position);if(35===o){do{o=e.input.charCodeAt(++e.position)}while(0!==o&&!m(o));break}if(m(o))break;for(t=e.position;0!==o&&!y(o);)o=e.input.charCodeAt(++e.position);r.push(e.input.slice(t,e.position))}0!==o&&R(e),l.call(C,n)?C[n](e,n,r):j(e,'unknown document directive "'+n+'"')}N(e,!0,-1),0===e.lineIndent&&45===e.input.charCodeAt(e.position)&&45===e.input.charCodeAt(e.position+1)&&45===e.input.charCodeAt(e.position+2)?(e.position+=3,N(e,!0,-1)):a&&T(e,"directives end mark is expected"),U(e,e.lineIndent-1,4,!1,!0),N(e,!0,-1),e.checkLineBreaks&&u.test(e.input.slice(i,e.position))&&j(e,"non-ASCII line breaks are interpreted as content"),e.documents.push(e.result),e.position===e.lineStart&&L(e)?46===e.input.charCodeAt(e.position)&&(e.position+=3,N(e,!0,-1)):e.position<e.length-1&&T(e,"end of the stream or a document separator is expected")}function $(e,t){t=t||{},0!==(e=String(e)).length&&(10!==e.charCodeAt(e.length-1)&&13!==e.charCodeAt(e.length-1)&&(e+="\n"),65279===e.charCodeAt(0)&&(e=e.slice(1)));var n=new E(e,t);for(n.input+="\0";32===n.input.charCodeAt(n.position);)n.lineIndent+=1,n.position+=1;for(;n.position<n.length-1;)B(n);return n.documents}function q(e,t,n){var r,o,i=$(e,n);if("function"!=typeof t)return i;for(r=0,o=i.length;r<o;r+=1)t(i[r])}function W(e,t){var n=$(e,t);if(0!==n.length){if(1===n.length)return n[0];throw new o("expected a single document in the stream, but found more")}}e.exports.loadAll=q,e.exports.load=W,e.exports.safeLoadAll=function(e,t,n){if("function"!=typeof t)return q(e,r.extend({schema:a},n));q(e,t,r.extend({schema:a},n))},e.exports.safeLoad=function(e,t){return W(e,r.extend({schema:a},t))}},function(e,t,n){"use strict";var r=n(49);function o(e,t,n,r,o){this.name=e,this.buffer=t,this.position=n,this.line=r,this.column=o}o.prototype.getSnippet=function(e,t){var n,o,i,a,s;if(!this.buffer)return null;for(e=e||4,t=t||75,n="",o=this.position;o>0&&-1==="\0\r\n…\u2028\u2029".indexOf(this.buffer.charAt(o-1));)if(o-=1,this.position-o>t/2-1){n=" ... ",o+=5;break}for(i="",a=this.position;a<this.buffer.length&&-1==="\0\r\n…\u2028\u2029".indexOf(this.buffer.charAt(a));)if((a+=1)-this.position>t/2-1){i=" ... ",a-=5;break}return s=this.buffer.slice(o,a),r.repeat(" ",e)+n+s+i+"\n"+r.repeat(" ",e+this.position-o+n.length)+"^"},o.prototype.toString=function(e){var t,n="";return this.name&&(n+='in "'+this.name+'" '),n+="at line "+(this.line+1)+", column "+(this.column+1),e||(t=this.getSnippet())&&(n+=":\n"+t),n},e.exports=o},function(e,t,n){"use strict";var r=n(6);e.exports=new r("tag:yaml.org,2002:str",{kind:"scalar",construct:function(e){return null!==e?e:""}})},function(e,t,n){"use strict";var r=n(6);e.exports=new r("tag:yaml.org,2002:seq",{kind:"sequence",construct:function(e){return null!==e?e:[]}})},function(e,t,n){"use strict";var r=n(6);e.exports=new r("tag:yaml.org,2002:map",{kind:"mapping",construct:function(e){return null!==e?e:{}}})},function(e,t,n){"use strict";var r=n(6);e.exports=new r("tag:yaml.org,2002:null",{kind:"scalar",resolve:function(e){if(null===e)return!0;var t=e.length;return 1===t&&"~"===e||4===t&&("null"===e||"Null"===e||"NULL"===e)},construct:function(){return null},predicate:function(e){return null===e},represent:{canonical:function(){return"~"},lowercase:function(){return"null"},uppercase:function(){return"NULL"},camelcase:function(){return"Null"}},defaultStyle:"lowercase"})},function(e,t,n){"use strict";var r=n(6);e.exports=new r("tag:yaml.org,2002:bool",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t=e.length;return 4===t&&("true"===e||"True"===e||"TRUE"===e)||5===t&&("false"===e||"False"===e||"FALSE"===e)},construct:function(e){return"true"===e||"True"===e||"TRUE"===e},predicate:function(e){return"[object Boolean]"===Object.prototype.toString.call(e)},represent:{lowercase:function(e){return e?"true":"false"},uppercase:function(e){return e?"TRUE":"FALSE"},camelcase:function(e){return e?"True":"False"}},defaultStyle:"lowercase"})},function(e,t,n){"use strict";var r=n(49),o=n(6);function i(e){return 48<=e&&e<=55}function a(e){return 48<=e&&e<=57}e.exports=new o("tag:yaml.org,2002:int",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t,n,r=e.length,o=0,s=!1;if(!r)return!1;if("-"!==(t=e[o])&&"+"!==t||(t=e[++o]),"0"===t){if(o+1===r)return!0;if("b"===(t=e[++o])){for(o++;o<r;o++)if("_"!==(t=e[o])){if("0"!==t&&"1"!==t)return!1;s=!0}return s&&"_"!==t}if("x"===t){for(o++;o<r;o++)if("_"!==(t=e[o])){if(!(48<=(n=e.charCodeAt(o))&&n<=57||65<=n&&n<=70||97<=n&&n<=102))return!1;s=!0}return s&&"_"!==t}for(;o<r;o++)if("_"!==(t=e[o])){if(!i(e.charCodeAt(o)))return!1;s=!0}return s&&"_"!==t}if("_"===t)return!1;for(;o<r;o++)if("_"!==(t=e[o])){if(":"===t)break;if(!a(e.charCodeAt(o)))return!1;s=!0}return!(!s||"_"===t)&&(":"!==t||/^(:[0-5]?[0-9])+$/.test(e.slice(o)))},construct:function(e){var t,n,r=e,o=1,i=[];return-1!==r.indexOf("_")&&(r=r.replace(/_/g,"")),"-"!==(t=r[0])&&"+"!==t||("-"===t&&(o=-1),t=(r=r.slice(1))[0]),"0"===r?0:"0"===t?"b"===r[1]?o*parseInt(r.slice(2),2):"x"===r[1]?o*parseInt(r,16):o*parseInt(r,8):-1!==r.indexOf(":")?(r.split(":").forEach((function(e){i.unshift(parseInt(e,10))})),r=0,n=1,i.forEach((function(e){r+=e*n,n*=60})),o*r):o*parseInt(r,10)},predicate:function(e){return"[object Number]"===Object.prototype.toString.call(e)&&e%1==0&&!r.isNegativeZero(e)},represent:{binary:function(e){return e>=0?"0b"+e.toString(2):"-0b"+e.toString(2).slice(1)},octal:function(e){return e>=0?"0"+e.toString(8):"-0"+e.toString(8).slice(1)},decimal:function(e){return e.toString(10)},hexadecimal:function(e){return e>=0?"0x"+e.toString(16).toUpperCase():"-0x"+e.toString(16).toUpperCase().slice(1)}},defaultStyle:"decimal",styleAliases:{binary:[2,"bin"],octal:[8,"oct"],decimal:[10,"dec"],hexadecimal:[16,"hex"]}})},function(e,t,n){"use strict";var r=n(49),o=n(6),i=new RegExp("^(?:[-+]?(?:0|[1-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]*|[-+]?\\.(?:inf|Inf|INF)|\\.(?:nan|NaN|NAN))$");var a=/^[-+]?[0-9]+e/;e.exports=new o("tag:yaml.org,2002:float",{kind:"scalar",resolve:function(e){return null!==e&&!(!i.test(e)||"_"===e[e.length-1])},construct:function(e){var t,n,r,o;return n="-"===(t=e.replace(/_/g,"").toLowerCase())[0]?-1:1,o=[],"+-".indexOf(t[0])>=0&&(t=t.slice(1)),".inf"===t?1===n?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY:".nan"===t?NaN:t.indexOf(":")>=0?(t.split(":").forEach((function(e){o.unshift(parseFloat(e,10))})),t=0,r=1,o.forEach((function(e){t+=e*r,r*=60})),n*t):n*parseFloat(t,10)},predicate:function(e){return"[object Number]"===Object.prototype.toString.call(e)&&(e%1!=0||r.isNegativeZero(e))},represent:function(e,t){var n;if(isNaN(e))switch(t){case"lowercase":return".nan";case"uppercase":return".NAN";case"camelcase":return".NaN"}else if(Number.POSITIVE_INFINITY===e)switch(t){case"lowercase":return".inf";case"uppercase":return".INF";case"camelcase":return".Inf"}else if(Number.NEGATIVE_INFINITY===e)switch(t){case"lowercase":return"-.inf";case"uppercase":return"-.INF";case"camelcase":return"-.Inf"}else if(r.isNegativeZero(e))return"-0.0";return n=e.toString(10),a.test(n)?n.replace("e",".e"):n},defaultStyle:"lowercase"})},function(e,t,n){"use strict";var r=n(6),o=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$"),i=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:[Tt]|[ \\t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\.([0-9]*))?(?:[ \\t]*(Z|([-+])([0-9][0-9]?)(?::([0-9][0-9]))?))?$");e.exports=new r("tag:yaml.org,2002:timestamp",{kind:"scalar",resolve:function(e){return null!==e&&(null!==o.exec(e)||null!==i.exec(e))},construct:function(e){var t,n,r,a,s,l,c,u,p=0,f=null;if(null===(t=o.exec(e))&&(t=i.exec(e)),null===t)throw new Error("Date resolve error");if(n=+t[1],r=+t[2]-1,a=+t[3],!t[4])return new Date(Date.UTC(n,r,a));if(s=+t[4],l=+t[5],c=+t[6],t[7]){for(p=t[7].slice(0,3);p.length<3;)p+="0";p=+p}return t[9]&&(f=6e4*(60*+t[10]+ +(t[11]||0)),"-"===t[9]&&(f=-f)),u=new Date(Date.UTC(n,r,a,s,l,c,p)),f&&u.setTime(u.getTime()-f),u},instanceOf:Date,represent:function(e){return e.toISOString()}})},function(e,t,n){"use strict";var r=n(6);e.exports=new r("tag:yaml.org,2002:merge",{kind:"scalar",resolve:function(e){return"<<"===e||null===e}})},function(e,t,n){"use strict";var r;try{r=n(14).Buffer}catch(e){}var o=n(6),i="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n\r";e.exports=new o("tag:yaml.org,2002:binary",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t,n,r=0,o=e.length,a=i;for(n=0;n<o;n++)if(!((t=a.indexOf(e.charAt(n)))>64)){if(t<0)return!1;r+=6}return r%8==0},construct:function(e){var t,n,o=e.replace(/[\r\n=]/g,""),a=o.length,s=i,l=0,c=[];for(t=0;t<a;t++)t%4==0&&t&&(c.push(l>>16&255),c.push(l>>8&255),c.push(255&l)),l=l<<6|s.indexOf(o.charAt(t));return 0===(n=a%4*6)?(c.push(l>>16&255),c.push(l>>8&255),c.push(255&l)):18===n?(c.push(l>>10&255),c.push(l>>2&255)):12===n&&c.push(l>>4&255),r?r.from?r.from(c):new r(c):c},predicate:function(e){return r&&r.isBuffer(e)},represent:function(e){var t,n,r="",o=0,a=e.length,s=i;for(t=0;t<a;t++)t%3==0&&t&&(r+=s[o>>18&63],r+=s[o>>12&63],r+=s[o>>6&63],r+=s[63&o]),o=(o<<8)+e[t];return 0===(n=a%3)?(r+=s[o>>18&63],r+=s[o>>12&63],r+=s[o>>6&63],r+=s[63&o]):2===n?(r+=s[o>>10&63],r+=s[o>>4&63],r+=s[o<<2&63],r+=s[64]):1===n&&(r+=s[o>>2&63],r+=s[o<<4&63],r+=s[64],r+=s[64]),r}})},function(e,t,n){"use strict";var r=n(6),o=Object.prototype.hasOwnProperty,i=Object.prototype.toString;e.exports=new r("tag:yaml.org,2002:omap",{kind:"sequence",resolve:function(e){if(null===e)return!0;var t,n,r,a,s,l=[],c=e;for(t=0,n=c.length;t<n;t+=1){if(r=c[t],s=!1,"[object Object]"!==i.call(r))return!1;for(a in r)if(o.call(r,a)){if(s)return!1;s=!0}if(!s)return!1;if(-1!==l.indexOf(a))return!1;l.push(a)}return!0},construct:function(e){return null!==e?e:[]}})},function(e,t,n){"use strict";var r=n(6),o=Object.prototype.toString;e.exports=new r("tag:yaml.org,2002:pairs",{kind:"sequence",resolve:function(e){if(null===e)return!0;var t,n,r,i,a,s=e;for(a=new Array(s.length),t=0,n=s.length;t<n;t+=1){if(r=s[t],"[object Object]"!==o.call(r))return!1;if(1!==(i=Object.keys(r)).length)return!1;a[t]=[i[0],r[i[0]]]}return!0},construct:function(e){if(null===e)return[];var t,n,r,o,i,a=e;for(i=new Array(a.length),t=0,n=a.length;t<n;t+=1)r=a[t],o=Object.keys(r),i[t]=[o[0],r[o[0]]];return i}})},function(e,t,n){"use strict";var r=n(6),o=Object.prototype.hasOwnProperty;e.exports=new r("tag:yaml.org,2002:set",{kind:"mapping",resolve:function(e){if(null===e)return!0;var t,n=e;for(t in n)if(o.call(n,t)&&null!==n[t])return!1;return!0},construct:function(e){return null!==e?e:{}}})},function(e,t,n){"use strict";var r=n(6);e.exports=new r("tag:yaml.org,2002:js/undefined",{kind:"scalar",resolve:function(){return!0},construct:function(){},predicate:function(e){return void 0===e},represent:function(){return""}})},function(e,t,n){"use strict";var r=n(6);e.exports=new r("tag:yaml.org,2002:js/regexp",{kind:"scalar",resolve:function(e){if(null===e)return!1;if(0===e.length)return!1;var t=e,n=/\/([gim]*)$/.exec(e),r="";if("/"===t[0]){if(n&&(r=n[1]),r.length>3)return!1;if("/"!==t[t.length-r.length-1])return!1}return!0},construct:function(e){var t=e,n=/\/([gim]*)$/.exec(e),r="";return"/"===t[0]&&(n&&(r=n[1]),t=t.slice(1,t.length-r.length-1)),new RegExp(t,r)},predicate:function(e){return"[object RegExp]"===Object.prototype.toString.call(e)},represent:function(e){var t="/"+e.source+"/";return e.global&&(t+="g"),e.multiline&&(t+="m"),e.ignoreCase&&(t+="i"),t}})},function(e,t,n){"use strict";var r;try{r=n(263)}catch(e){"undefined"!=typeof window&&(r=window.esprima)}var o=n(6);e.exports=new o("tag:yaml.org,2002:js/function",{kind:"scalar",resolve:function(e){if(null===e)return!1;try{var t="("+e+")",n=r.parse(t,{range:!0});return"Program"===n.type&&1===n.body.length&&"ExpressionStatement"===n.body[0].type&&("ArrowFunctionExpression"===n.body[0].expression.type||"FunctionExpression"===n.body[0].expression.type)}catch(e){return!1}},construct:function(e){var t,n="("+e+")",o=r.parse(n,{range:!0}),i=[];if("Program"!==o.type||1!==o.body.length||"ExpressionStatement"!==o.body[0].type||"ArrowFunctionExpression"!==o.body[0].expression.type&&"FunctionExpression"!==o.body[0].expression.type)throw new Error("Failed to resolve function");return o.body[0].expression.params.forEach((function(e){i.push(e.name)})),t=o.body[0].expression.body.range,"BlockStatement"===o.body[0].expression.body.type?new Function(i,n.slice(t[0]+1,t[1]-1)):new Function(i,"return "+n.slice(t[0],t[1]))},predicate:function(e){return"[object Function]"===Object.prototype.toString.call(e)},represent:function(e){return e.toString()}})},function(e,n){if(void 0===t){var r=new Error("Cannot find module 'esprima'");throw r.code="MODULE_NOT_FOUND",r}e.exports=t},function(e,t){e.exports=function(){}},function(e,t){e.exports=function(e){var t=/(%?)(%([jds]))/g,n=Array.prototype.slice.call(arguments,1);return n.length&&(e=e.replace(t,(function(e,t,r,o){var i=n.shift();switch(o){case"s":i=""+i;break;case"d":i=Number(i);break;case"j":i=JSON.stringify(i)}return t?(n.unshift(i),e):i}))),n.length&&(e+=" "+n.join(" ")),""+(e=e.replace(/%{2,2}/g,"%"))}},function(e,t,n){"use strict";(function(t){var n=/\.(txt|htm|html|md|xml|js|min|map|css|scss|less|svg)$/i;e.exports={order:300,allowEmpty:!0,encoding:"utf8",canParse:function(e){return("string"==typeof e.data||t.isBuffer(e.data))&&n.test(e.url)},parse:function(e){if("string"==typeof e.data)return e.data;if(t.isBuffer(e.data))return e.data.toString(this.encoding);throw new Error("data is not text")}}}).call(this,n(14).Buffer)},function(e,t,n){"use strict";(function(t){var n=/\.(jpeg|jpg|gif|png|bmp|ico)$/i;e.exports={order:400,allowEmpty:!0,canParse:function(e){return t.isBuffer(e.data)&&n.test(e.url)},parse:function(e){return t.isBuffer(e.data)?e.data:new t(e.data)}}}).call(this,n(14).Buffer)},function(e,t,n){"use strict";var r=n(269),o=n(31),i=n(26);e.exports={order:100,canRead:function(e){return i.isFileSystemPath(e.url)},read:function(e){return new Promise((function(t,n){var a;try{a=i.toFileSystemPath(e.url)}catch(t){n(o.uri(t,"Malformed URI: %s",e.url))}try{r.readFile(a,(function(e,r){e?n(o(e,'Error opening file "%s"',a)):t(r)}))}catch(e){n(o(e,'Error opening file "%s"',a))}}))}}},function(e,t){},function(e,t,n){"use strict";(function(t,r){var o=n(91),i=n(282),a=n(31),s=n(26);e.exports={order:200,headers:null,timeout:5e3,redirects:5,withCredentials:!1,canRead:function(e){return s.isHttp(e.url)},read:function(e){var n=s.parse(e.url);return t.browser&&!n.protocol&&(n.protocol=s.parse(location.href).protocol),function e(t,n,l){return new Promise((function(c,u){t=s.parse(t),(l=l||[]).push(t.href),function(e,t){return new Promise((function(n,a){var s=("https:"===e.protocol?i:o).get({hostname:e.hostname,port:e.port,path:e.path,auth:e.auth,protocol:e.protocol,headers:t.headers||{},withCredentials:t.withCredentials});"function"==typeof s.setTimeout&&s.setTimeout(t.timeout),s.on("timeout",(function(){s.abort()})),s.on("error",a),s.once("response",(function(e){e.body=new r(0),e.on("data",(function(t){e.body=r.concat([e.body,new r(t)])})),e.on("error",a),e.on("end",(function(){n(e)}))}))}))}(t,n).then((function(o){if(o.statusCode>=400)throw a({status:o.statusCode},"HTTP ERROR %d",o.statusCode);if(o.statusCode>=300)if(l.length>n.redirects)u(a({status:o.statusCode},"Error downloading %s. \nToo many redirects: \n %s",l[0],l.join(" \n ")));else{if(!o.headers.location)throw a({status:o.statusCode},"HTTP %d redirect with no location header",o.statusCode);var i=s.resolve(t,o.headers.location);e(i,n,l).then(c,u)}else c(o.body||new r(0))})).catch((function(e){u(a(e,"Error downloading",t.href))}))}))}(n,this)}}}).call(this,n(13),n(14).Buffer)},function(e,t,n){(function(t,r,o){var i=n(136),a=n(32),s=n(137),l=n(138),c=n(279),u=s.IncomingMessage,p=s.readyStates;var f=e.exports=function(e){var n,r=this;l.Writable.call(r),r._opts=e,r._body=[],r._headers={},e.auth&&r.setHeader("Authorization","Basic "+new t(e.auth).toString("base64")),Object.keys(e.headers).forEach((function(t){r.setHeader(t,e.headers[t])}));var o=!0;if("disable-fetch"===e.mode||"requestTimeout"in e&&!i.abortController)o=!1,n=!0;else if("prefer-streaming"===e.mode)n=!1;else if("allow-wrong-content-type"===e.mode)n=!i.overrideMimeType;else{if(e.mode&&"default"!==e.mode&&"prefer-fast"!==e.mode)throw new Error("Invalid value for opts.mode");n=!0}r._mode=function(e,t){return i.fetch&&t?"fetch":i.mozchunkedarraybuffer?"moz-chunked-arraybuffer":i.msstream?"ms-stream":i.arraybuffer&&e?"arraybuffer":i.vbArray&&e?"text:vbarray":"text"}(n,o),r._fetchTimer=null,r.on("finish",(function(){r._onFinish()}))};a(f,l.Writable),f.prototype.setHeader=function(e,t){var n=e.toLowerCase();-1===d.indexOf(n)&&(this._headers[n]={name:e,value:t})},f.prototype.getHeader=function(e){var t=this._headers[e.toLowerCase()];return t?t.value:null},f.prototype.removeHeader=function(e){delete this._headers[e.toLowerCase()]},f.prototype._onFinish=function(){var e=this;if(!e._destroyed){var n=e._opts,a=e._headers,s=null;"GET"!==n.method&&"HEAD"!==n.method&&(s=i.arraybuffer?c(t.concat(e._body)):i.blobConstructor?new r.Blob(e._body.map((function(e){return c(e)})),{type:(a["content-type"]||{}).value||""}):t.concat(e._body).toString());var l=[];if(Object.keys(a).forEach((function(e){var t=a[e].name,n=a[e].value;Array.isArray(n)?n.forEach((function(e){l.push([t,e])})):l.push([t,n])})),"fetch"===e._mode){var u=null;if(i.abortController){var f=new AbortController;u=f.signal,e._fetchAbortController=f,"requestTimeout"in n&&0!==n.requestTimeout&&(e._fetchTimer=r.setTimeout((function(){e.emit("requestTimeout"),e._fetchAbortController&&e._fetchAbortController.abort()}),n.requestTimeout))}r.fetch(e._opts.url,{method:e._opts.method,headers:l,body:s||void 0,mode:"cors",credentials:n.withCredentials?"include":"same-origin",signal:u}).then((function(t){e._fetchResponse=t,e._connect()}),(function(t){r.clearTimeout(e._fetchTimer),e._destroyed||e.emit("error",t)}))}else{var d=e._xhr=new r.XMLHttpRequest;try{d.open(e._opts.method,e._opts.url,!0)}catch(t){return void o.nextTick((function(){e.emit("error",t)}))}"responseType"in d&&(d.responseType=e._mode.split(":")[0]),"withCredentials"in d&&(d.withCredentials=!!n.withCredentials),"text"===e._mode&&"overrideMimeType"in d&&d.overrideMimeType("text/plain; charset=x-user-defined"),"requestTimeout"in n&&(d.timeout=n.requestTimeout,d.ontimeout=function(){e.emit("requestTimeout")}),l.forEach((function(e){d.setRequestHeader(e[0],e[1])})),e._response=null,d.onreadystatechange=function(){switch(d.readyState){case p.LOADING:case p.DONE:e._onXHRProgress()}},"moz-chunked-arraybuffer"===e._mode&&(d.onprogress=function(){e._onXHRProgress()}),d.onerror=function(){e._destroyed||e.emit("error",new Error("XHR error"))};try{d.send(s)}catch(t){return void o.nextTick((function(){e.emit("error",t)}))}}}},f.prototype._onXHRProgress=function(){(function(e){try{var t=e.status;return null!==t&&0!==t}catch(e){return!1}})(this._xhr)&&!this._destroyed&&(this._response||this._connect(),this._response._onXHRProgress())},f.prototype._connect=function(){var e=this;e._destroyed||(e._response=new u(e._xhr,e._fetchResponse,e._mode,e._fetchTimer),e._response.on("error",(function(t){e.emit("error",t)})),e.emit("response",e._response))},f.prototype._write=function(e,t,n){this._body.push(e),n()},f.prototype.abort=f.prototype.destroy=function(){this._destroyed=!0,r.clearTimeout(this._fetchTimer),this._response&&(this._response._destroyed=!0),this._xhr?this._xhr.abort():this._fetchAbortController&&this._fetchAbortController.abort()},f.prototype.end=function(e,t,n){"function"==typeof e&&(n=e,e=void 0),l.Writable.prototype.end.call(this,e,t,n)},f.prototype.flushHeaders=function(){},f.prototype.setTimeout=function(){},f.prototype.setNoDelay=function(){},f.prototype.setSocketKeepAlive=function(){};var d=["accept-charset","accept-encoding","access-control-request-headers","access-control-request-method","connection","content-length","cookie","cookie2","date","dnt","expect","host","keep-alive","origin","referer","te","trailer","transfer-encoding","upgrade","via"]}).call(this,n(14).Buffer,n(7),n(13))},function(e,t){},function(e,t,n){"use strict";var r=n(62).Buffer,o=n(274);e.exports=function(){function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.head=null,this.tail=null,this.length=0}return e.prototype.push=function(e){var t={data:e,next:null};this.length>0?this.tail.next=t:this.head=t,this.tail=t,++this.length},e.prototype.unshift=function(e){var t={data:e,next:this.head};0===this.length&&(this.tail=t),this.head=t,++this.length},e.prototype.shift=function(){if(0!==this.length){var e=this.head.data;return 1===this.length?this.head=this.tail=null:this.head=this.head.next,--this.length,e}},e.prototype.clear=function(){this.head=this.tail=null,this.length=0},e.prototype.join=function(e){if(0===this.length)return"";for(var t=this.head,n=""+t.data;t=t.next;)n+=e+t.data;return n},e.prototype.concat=function(e){if(0===this.length)return r.alloc(0);if(1===this.length)return this.head.data;for(var t,n,o,i=r.allocUnsafe(e>>>0),a=this.head,s=0;a;)t=a.data,n=i,o=s,t.copy(n,o),s+=a.data.length,a=a.next;return i},e}(),o&&o.inspect&&o.inspect.custom&&(e.exports.prototype[o.inspect.custom]=function(){var e=o.inspect({length:this.length});return this.constructor.name+" "+e})},function(e,t){},function(e,t,n){(function(e){var r=void 0!==e&&e||"undefined"!=typeof self&&self||window,o=Function.prototype.apply;function i(e,t){this._id=e,this._clearFn=t}t.setTimeout=function(){return new i(o.call(setTimeout,r,arguments),clearTimeout)},t.setInterval=function(){return new i(o.call(setInterval,r,arguments),clearInterval)},t.clearTimeout=t.clearInterval=function(e){e&&e.close()},i.prototype.unref=i.prototype.ref=function(){},i.prototype.close=function(){this._clearFn.call(r,this._id)},t.enroll=function(e,t){clearTimeout(e._idleTimeoutId),e._idleTimeout=t},t.unenroll=function(e){clearTimeout(e._idleTimeoutId),e._idleTimeout=-1},t._unrefActive=t.active=function(e){clearTimeout(e._idleTimeoutId);var t=e._idleTimeout;t>=0&&(e._idleTimeoutId=setTimeout((function(){e._onTimeout&&e._onTimeout()}),t))},n(276),t.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==e&&e.setImmediate||this&&this.setImmediate,t.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==e&&e.clearImmediate||this&&this.clearImmediate}).call(this,n(7))},function(e,t,n){(function(e,t){!function(e,n){"use strict";if(!e.setImmediate){var r,o,i,a,s,l=1,c={},u=!1,p=e.document,f=Object.getPrototypeOf&&Object.getPrototypeOf(e);f=f&&f.setTimeout?f:e,"[object process]"==={}.toString.call(e.process)?r=function(e){t.nextTick((function(){h(e)}))}:!function(){if(e.postMessage&&!e.importScripts){var t=!0,n=e.onmessage;return e.onmessage=function(){t=!1},e.postMessage("","*"),e.onmessage=n,t}}()?e.MessageChannel?((i=new MessageChannel).port1.onmessage=function(e){h(e.data)},r=function(e){i.port2.postMessage(e)}):p&&"onreadystatechange"in p.createElement("script")?(o=p.documentElement,r=function(e){var t=p.createElement("script");t.onreadystatechange=function(){h(e),t.onreadystatechange=null,o.removeChild(t),t=null},o.appendChild(t)}):r=function(e){setTimeout(h,0,e)}:(a="setImmediate$"+Math.random()+"$",s=function(t){t.source===e&&"string"==typeof t.data&&0===t.data.indexOf(a)&&h(+t.data.slice(a.length))},e.addEventListener?e.addEventListener("message",s,!1):e.attachEvent("onmessage",s),r=function(t){e.postMessage(a+t,"*")}),f.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),n=0;n<t.length;n++)t[n]=arguments[n+1];var o={callback:e,args:t};return c[l]=o,r(l),l++},f.clearImmediate=d}function d(e){delete c[e]}function h(e){if(u)setTimeout(h,0,e);else{var t=c[e];if(t){u=!0;try{!function(e){var t=e.callback,n=e.args;switch(n.length){case 0:t();break;case 1:t(n[0]);break;case 2:t(n[0],n[1]);break;case 3:t(n[0],n[1],n[2]);break;default:t.apply(void 0,n)}}(t)}finally{d(e),u=!1}}}}}("undefined"==typeof self?void 0===e?this:e:self)}).call(this,n(7),n(13))},function(e,t,n){(function(t){function n(e){try{if(!t.localStorage)return!1}catch(e){return!1}var n=t.localStorage[e];return null!=n&&"true"===String(n).toLowerCase()}e.exports=function(e,t){if(n("noDeprecation"))return e;var r=!1;return function(){if(!r){if(n("throwDeprecation"))throw new Error(t);n("traceDeprecation")?console.trace(t):console.warn(t),r=!0}return e.apply(this,arguments)}}}).call(this,n(7))},function(e,t,n){"use strict";e.exports=i;var r=n(145),o=Object.create(n(50));function i(e){if(!(this instanceof i))return new i(e);r.call(this,e)}o.inherits=n(32),o.inherits(i,r),i.prototype._transform=function(e,t,n){n(null,e)}},function(e,t,n){var r=n(14).Buffer;e.exports=function(e){if(e instanceof Uint8Array){if(0===e.byteOffset&&e.byteLength===e.buffer.byteLength)return e.buffer;if("function"==typeof e.buffer.slice)return e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength)}if(r.isBuffer(e)){for(var t=new Uint8Array(e.length),n=e.length,o=0;o<n;o++)t[o]=e[o];return t.buffer}throw new Error("Argument must be a Buffer")}},function(e,t){e.exports=function(){for(var e={},t=0;t<arguments.length;t++){var r=arguments[t];for(var o in r)n.call(r,o)&&(e[o]=r[o])}return e};var n=Object.prototype.hasOwnProperty},function(e,t){e.exports={100:"Continue",101:"Switching Protocols",102:"Processing",200:"OK",201:"Created",202:"Accepted",203:"Non-Authoritative Information",204:"No Content",205:"Reset Content",206:"Partial Content",207:"Multi-Status",208:"Already Reported",226:"IM Used",300:"Multiple Choices",301:"Moved Permanently",302:"Found",303:"See Other",304:"Not Modified",305:"Use Proxy",307:"Temporary Redirect",308:"Permanent Redirect",400:"Bad Request",401:"Unauthorized",402:"Payment Required",403:"Forbidden",404:"Not Found",405:"Method Not Allowed",406:"Not Acceptable",407:"Proxy Authentication Required",408:"Request Timeout",409:"Conflict",410:"Gone",411:"Length Required",412:"Precondition Failed",413:"Payload Too Large",414:"URI Too Long",415:"Unsupported Media Type",416:"Range Not Satisfiable",417:"Expectation Failed",418:"I'm a teapot",421:"Misdirected Request",422:"Unprocessable Entity",423:"Locked",424:"Failed Dependency",425:"Unordered Collection",426:"Upgrade Required",428:"Precondition Required",429:"Too Many Requests",431:"Request Header Fields Too Large",451:"Unavailable For Legal Reasons",500:"Internal Server Error",501:"Not Implemented",502:"Bad Gateway",503:"Service Unavailable",504:"Gateway Timeout",505:"HTTP Version Not Supported",506:"Variant Also Negotiates",507:"Insufficient Storage",508:"Loop Detected",509:"Bandwidth Limit Exceeded",510:"Not Extended",511:"Network Authentication Required"}},function(e,t,n){var r=n(91),o=n(10),i=e.exports;for(var a in r)r.hasOwnProperty(a)&&(i[a]=r[a]);function s(e){if("string"==typeof e&&(e=o.parse(e)),e.protocol||(e.protocol="https:"),"https:"!==e.protocol)throw new Error('Protocol "'+e.protocol+'" not supported. Expected "https:"');return e}i.request=function(e,t){return e=s(e),r.request.call(this,e,t)},i.get=function(e,t){return e=s(e),r.get.call(this,e,t)}},function(e,t,n){"use strict";var r=n(31),o=n(63),i=n(26);function a(){this.circular=!1,this._$refs={},this._root$Ref=null}function s(e,t){var n=Object.keys(e);return(t=Array.isArray(t[0])?t[0]:Array.prototype.slice.call(t)).length>0&&t[0]&&(n=n.filter((function(n){return-1!==t.indexOf(e[n].pathType)}))),n.map((function(t){return{encoded:t,decoded:"file"===e[t].pathType?i.toFileSystemPath(t,!0):t}}))}e.exports=a,a.prototype.paths=function(e){var t=s(this._$refs,arguments);return t.map((function(e){return e.decoded}))},a.prototype.values=function(e){var t=this._$refs,n=s(t,arguments);return n.reduce((function(e,n){return e[n.decoded]=t[n.encoded].value,e}),{})},a.prototype.toJSON=a.prototype.values,a.prototype.exists=function(e,t){try{return this._resolve(e,t),!0}catch(e){return!1}},a.prototype.get=function(e,t){return this._resolve(e,t).value},a.prototype.set=function(e,t){var n=i.resolve(this._root$Ref.path,e),o=i.stripHash(n),a=this._$refs[o];if(!a)throw r('Error resolving $ref pointer "%s". \n"%s" not found.',e,o);a.set(n,t)},a.prototype._add=function(e){var t=i.stripHash(e),n=new o;return n.path=t,n.$refs=this,this._$refs[t]=n,this._root$Ref=this._root$Ref||n,n},a.prototype._resolve=function(e,t){var n=i.resolve(this._root$Ref.path,e),o=i.stripHash(n),a=this._$refs[o];if(!a)throw r('Error resolving $ref pointer "%s". \n"%s" not found.',e,o);return a.resolve(n,t,e)},a.prototype._get$Ref=function(e){e=i.resolve(this._root$Ref.path,e);var t=i.stripHash(e);return this._$refs[t]}},function(e,t,n){"use strict";function r(e,t,n,r){var o=e[t];if("function"==typeof o)return o.apply(e,[n,r]);if(!r){if(o instanceof RegExp)return o.test(n.url);if("string"==typeof o)return o===n.extension;if(Array.isArray(o))return-1!==o.indexOf(n.extension)}return o}t.all=function(e){return Object.keys(e).filter((function(t){return"object"==typeof e[t]})).map((function(t){return e[t].name=t,e[t]}))},t.filter=function(e,t,n){return e.filter((function(e){return!!r(e,t,n)}))},t.sort=function(e){return e.forEach((function(e){e.order=e.order||Number.MAX_SAFE_INTEGER})),e.sort((function(e,t){return e.order-t.order}))},t.run=function(e,t,n){var o,i,a=0;return new Promise((function(s,l){function c(){if(!(o=e[a++]))return l(i);try{var s=r(o,t,n,u);s&&"function"==typeof s.then?s.then(p,f):void 0!==s&&p(s)}catch(e){f(e)}}function u(e,t){e?f(e):p(t)}function p(e){s({plugin:o,result:e})}function f(e){i=e,c()}c()}))}},function(e,t,n){"use strict";var r=n(132);e.exports=function(e){var t,n,o,i;"function"==typeof(e=Array.prototype.slice.call(e))[e.length-1]&&(i=e.pop());"string"==typeof e[0]?(t=e[0],"object"==typeof e[2]?(n=e[1],o=e[2]):(n=void 0,o=e[1])):(t="",n=e[0],o=e[1]);o instanceof r||(o=new r(o));return{path:t,schema:n,options:o,callback:i}}},function(e,t,n){"use strict";var r=n(63),o=n(92),i=n(146),a=n(26);function s(e,t,n,i){var a=[];return e&&"object"==typeof e&&(r.isExternal$Ref(e)?a.push(l(e,t,n,i)):Object.keys(e).forEach((function(c){var u=o.join(t,c),p=e[c];r.isExternal$Ref(p)?a.push(l(p,u,n,i)):a=a.concat(s(p,u,n,i))}))),a}function l(e,t,n,r){var o=a.resolve(t,e.$ref),l=a.stripHash(o);return(e=n._$refs[l])?Promise.resolve(e.value):i(o,n,r).then((function(e){var t=s(e,l+"#",n,r);return Promise.all(t)}))}e.exports=function(e,t){if(!t.resolve.external)return Promise.resolve();try{var n=s(e.schema,e.$refs._root$Ref.path+"#",e.$refs,t);return Promise.all(n)}catch(e){return Promise.reject(e)}}},function(e,t,n){"use strict";var r=n(63),o=n(92),i=n(26);function a(e,t,n,i,l,c,u,p){var f=null===t?e:e[t];f&&"object"==typeof f&&(r.isAllowed$Ref(f)?s(e,t,n,i,l,c,u,p):Object.keys(f).sort((function(e,t){return"definitions"===e?-1:"definitions"===t?1:e.length-t.length})).forEach((function(e){var t=o.join(n,e),d=o.join(i,e),h=f[e];r.isAllowed$Ref(h)?s(f,e,n,d,l,c,u,p):a(f,e,t,d,l,c,u,p)})))}function s(e,t,n,s,l,c,u,p){var f=null===t?e:e[t],d=i.resolve(n,f.$ref),h=u._resolve(d,p),m=o.parse(s).length,g=i.stripHash(h.path),y=i.getHash(h.path),v=g!==u._root$Ref.path,b=r.isExtended$Ref(f);l+=h.indirections;var x=function(e,t,n){for(var r=0;r<e.length;r++){var o=e[r];if(o.parent===t&&o.key===n)return o}}(c,e,t);if(x){if(!(m<x.depth||l<x.indirections))return;!function(e,t){var n=e.indexOf(t);e.splice(n,1)}(c,x)}c.push({$ref:f,parent:e,key:t,pathFromRoot:s,depth:m,file:g,hash:y,value:h.value,circular:h.circular,extended:b,external:v,indirections:l}),a(h.value,null,h.path,s,l+1,c,u,p)}e.exports=function(e,t){var n=[];a(e,"schema",e.$refs._root$Ref.path+"#","#",0,n,e.$refs,t),function(e){var t,n,i;e.sort((function(e,t){if(e.file!==t.file)return e.file<t.file?-1:1;if(e.hash!==t.hash)return e.hash<t.hash?-1:1;if(e.circular!==t.circular)return e.circular?-1:1;if(e.extended!==t.extended)return e.extended?1:-1;if(e.indirections!==t.indirections)return e.indirections-t.indirections;if(e.depth!==t.depth)return e.depth-t.depth;var n=e.pathFromRoot.lastIndexOf("/definitions"),r=t.pathFromRoot.lastIndexOf("/definitions");return n!==r?r-n:e.pathFromRoot.length-t.pathFromRoot.length})),e.forEach((function(e){e.external?e.file===t&&e.hash===n?e.$ref.$ref=i:e.file===t&&0===e.hash.indexOf(n+"/")?e.$ref.$ref=o.join(i,o.parse(e.hash.replace(n,"#"))):(t=e.file,n=e.hash,i=e.pathFromRoot,e.$ref=e.parent[e.key]=r.dereference(e.$ref,e.value),e.circular&&(e.$ref.$ref=e.pathFromRoot)):e.$ref.$ref=e.hash}))}(n)}},function(e,t){e.exports=function(){}},function(e,t,n){"use strict";var r=n(1),o=n(148),i=n(52),a=n(10),s=n(51),l=n(51),c=n(64).jptr,u=n(94).recurse,p=n(65).clone,f=n(290).dereference,d=n(93).isRef,h=n(149);function m(e,t,n,r,o,i){for(var s=i.externalRefs[n+r].paths[0],l=a.parse(o),f={},m=1;m;)m=0,u(e,{identityDetection:!0},(function(e,n,r){if(d(e,n))if(e[n].startsWith("#"))if(f[e[n]]||e.$fixed){if(!e.$fixed){var u=(s+"/"+f[e[n]]).split("/#/").join("/");r.parent[r.pkey]={$ref:u,"x-miro":e[n],$fixed:!0},i.verbose>1&&console.warn("Replacing with",u),m++}}else{var g=p(c(t,e[n]));if(i.verbose>1&&console.warn((!1===g?h.colour.red:h.colour.green)+"Fragment resolution",e[n],h.colour.normal),!1===g){if(r.parent[r.pkey]={},i.fatal){var y=new Error("Fragment $ref resolution failed "+e[n]);if(!i.promise)throw y;i.promise.reject(y)}}else m++,r.parent[r.pkey]=g,f[e[n]]=r.path.replace("/%24ref","")}else if(l.protocol){u=a.resolve(o,e[n]).toString();i.verbose>1&&console.warn(h.colour.yellow+"Rewriting external url ref",e[n],"as",u,h.colour.normal),e["x-miro"]=e[n],e[n]=u}else if(!e["x-miro"]){u=a.resolve(o,e[n]).toString();i.verbose>1&&console.warn(h.colour.yellow+"Rewriting external ref",e[n],"as",u,h.colour.normal),e["x-miro"]=e[n],e[n]=u}}));return u(e,{},(function(e,t,n){d(e,t)&&void 0!==e.$fixed&&delete e.$fixed})),i.verbose>1&&console.warn("Finished fragment resolution"),e}function g(e,t){if(!t.filters||!t.filters.length)return e;for(var n=0,r=t.filters;n<r.length;n++){e=(0,r[n])(e,t)}return e}function y(e,t,n,r){var u=a.parse(n.source),f=n.source.split("\\").join("/").split("/");f.pop()||f.pop();var d="",h=t.split("#");h.length>1&&(d="#"+h[1],t=h[0]),f=f.join("/");var y,v,b,x,w,k=a.parse(t),O=(y=k.protocol,v=u.protocol,y&&y.length>2?y:v&&v.length>2?v:"file:");if(b="file:"===O?i.resolve(f?f+"/":"",t):a.resolve(f?f+"/":"",t),n.cache[b]){n.verbose&&console.warn("CACHED",b,d);var _=p(n.cache[b]),E=n.externalRef=_;if(d&&!1===(E=c(E,d))&&(E={},n.fatal)){var S=new Error("Cached $ref resolution failed "+b+d);if(!n.promise)throw S;n.promise.reject(S)}return E=g(E=m(E,_,t,d,b,n),n),r(p(E),b,n),Promise.resolve(E)}return n.verbose&&console.warn("GET",b,d),n.handlers&&n.handlers[O]?n.handlers[O](f,t,d,n).then((function(e){return n.externalRef=e,e=g(e,n),n.cache[b]=e,r(e,b,n),e})).catch((function(e){throw n.verbose&&console.warn(e),e})):O&&O.startsWith("http")?s(b,{agent:n.agent}).then((function(e){if(200!==e.status)throw new Error("Received status code "+e.status);return e.text()})).then((function(e){try{var o=l.parse(e,{schema:"core",prettyErrors:!0});if(e=n.externalRef=o,n.cache[b]=p(e),d&&!1===(e=c(e,d))&&(e={},n.fatal)){var i=new Error("Remote $ref resolution failed "+b+d);if(!n.promise)throw i;n.promise.reject(i)}e=g(e=m(e,o,t,d,b,n),n)}catch(i){if(n.verbose&&console.warn(i),!n.promise||!n.fatal)throw i;n.promise.reject(i)}return r(e,b,n),e})).catch((function(e){if(n.verbose&&console.warn(e),n.cache[b]={},!n.promise||!n.fatal)throw e;n.promise.reject(e)})):(x=b,w=n.encoding||"utf8",new Promise((function(e,t){o.readFile(x,w,(function(n,r){n?t(n):e(r)}))}))).then((function(e){try{var o=l.parse(e,{schema:"core",prettyErrors:!0});if(e=n.externalRef=o,n.cache[b]=p(e),d&&!1===(e=c(e,d))&&(e={},n.fatal)){var i=new Error("File $ref resolution failed "+b+d);if(!n.promise)throw i;n.promise.reject(i)}e=g(e=m(e,o,t,d,b,n),n)}catch(i){if(n.verbose&&console.warn(i),!n.promise||!n.fatal)throw i;n.promise.reject(i)}return r(e,b,n),e})).catch((function(e){if(n.verbose&&console.warn(e),!n.promise||!n.fatal)throw e;n.promise.reject(e)}))}function v(e){return new Promise((function(t,n){(function(e){return new Promise((function(t,n){function r(t,n,r){if(t[n]&&d(t[n],"$ref")){var i=t[n].$ref;if(!i.startsWith("#")){var a="";if(!o[i]){var s=Object.keys(o).find((function(e,t,n){return i.startsWith(e+"/")}));s&&(e.verbose&&console.warn("Found potential subschema at",s),a=(a="/"+(i.split("#")[1]||"").replace(s.split("#")[1]||"")).split("/undefined").join(""),i=s)}if(o[i]||(o[i]={resolved:!1,paths:[],extras:{},description:t[n].description}),o[i].resolved)if(e.rewriteRefs){var l=o[i].resolvedAt;e.verbose>1&&console.warn("Rewriting ref",i,l),t[n]["x-miro"]=i,t[n].$ref=l+a}else t[n]=p(o[i].data);else o[i].paths.push(r.path),o[i].extras[r.path]=a}}}var o=e.externalRefs;if(e.resolver.depth>0&&e.source===e.resolver.base)return t(o);u(e.openapi.definitions,{identityDetection:!0,path:"#/definitions"},r),u(e.openapi.components,{identityDetection:!0,path:"#/components"},r),u(e.openapi,{identityDetection:!0},r),t(o)}))})(e).then((function(t){var n=function(n){if(!t[n].resolved){var o=e.resolver.depth;o>0&&o++,e.resolver.actions[o].push((function(){return y(e.openapi,n,e,(function(e,o,i){if(!t[n].resolved){var a={};a.context=t[n],a.$ref=n,a.original=p(e),a.updated=e,a.source=o,i.externals.push(a),t[n].resolved=!0}var s=Object.assign({},i,{source:"",resolver:{actions:i.resolver.actions,depth:i.resolver.actions.length-1,base:i.resolver.base}});i.patch&&t[n].description&&!e.description&&"object"==typeof e&&(e.description=t[n].description),t[n].data=e;for(var l,u=(l=t[n].paths,r.__spreadArrays(new Set(l))),f=0,d=u=u.sort((function(e,t){var n=e.startsWith("#/components/")||e.startsWith("#/definitions/"),r=t.startsWith("#/components/")||t.startsWith("#/definitions/");return n&&!r?-1:r&&!n?1:0}));f<d.length;f++){var h=d[f];if(t[n].resolvedAt&&h!==t[n].resolvedAt&&h.indexOf("x-ms-examples/")<0)i.verbose>1&&console.warn("Creating pointer to data at",h),c(i.openapi,h,{$ref:t[n].resolvedAt+t[n].extras[h],"x-miro":n+t[n].extras[h]});else{t[n].resolvedAt?i.verbose>1&&console.warn("Avoiding circular reference"):(t[n].resolvedAt=h,i.verbose>1&&console.warn("Creating initial clone of data at",h));var m=p(e);c(i.openapi,h,m)}}0===i.resolver.actions[s.resolver.depth].length&&i.resolver.actions[s.resolver.depth].push((function(){return v(s)}))}))}))}};for(var o in t)n(o)})).catch((function(t){e.verbose&&console.warn(t),n(t)}));var o={options:e};o.actions=e.resolver.actions[e.resolver.depth],t(o)}))}function b(e,t,n){e.resolver.actions.push([]),v(e).then((function(r){var o;(o=r.actions,o.reduce((function(e,t){return e.then((function(e){return t().then(Array.prototype.concat.bind(e))}))}),Promise.resolve([]))).then((function(){if(e.resolver.depth>=e.resolver.actions.length)return console.warn("Ran off the end of resolver actions"),t(!0);e.resolver.depth++,e.resolver.actions[e.resolver.depth].length?setTimeout((function(){b(r.options,t,n)}),0):(e.verbose>1&&console.warn(h.colour.yellow+"Finished external resolution!",h.colour.normal),e.resolveInternal&&(e.verbose>1&&console.warn(h.colour.yellow+"Starting internal resolution!",h.colour.normal),e.openapi=f(e.openapi,e.original,{verbose:e.verbose-1}),e.verbose>1&&console.warn(h.colour.yellow+"Finished internal resolution!",h.colour.normal)),u(e.openapi,{},(function(t,n,r){d(t,n)&&(e.preserveMiro||delete t["x-miro"])})),t(e))})).catch((function(t){e.verbose&&console.warn(t),n(t)}))})).catch((function(t){e.verbose&&console.warn(t),n(t)}))}function x(e){if(e.cache||(e.cache={}),e.source){var t=a.parse(e.source);(!t.protocol||t.protocol.length<=2)&&(e.source=i.resolve(e.source))}e.externals||(e.externals=[]),e.externalRefs||(e.externalRefs={}),e.rewriteRefs=!0,e.resolver={},e.resolver.depth=0,e.resolver.base=e.source,e.resolver.actions=[[]]}e.exports={optionalResolve:function(e){return x(e),new Promise((function(t,n){e.resolve?b(e,t,n):t(e)}))},resolve:function(e,t,n){return n||(n={}),n.openapi=e,n.source=t,n.resolve=!0,x(n),new Promise((function(e,t){b(n,e,t)}))}}},function(e,t,n){"use strict";var r=n(94).recurse,o=n(65).shallowClone,i=n(64).jptr,a=n(93).isRef;e.exports={dereference:function e(t,n,s){s||(s={}),s.cache||(s.cache={}),s.state||(s.state={}),s.state.identityDetection=!0,s.depth=s.depth?s.depth+1:1;var l=s.depth>1?t:o(t),c={data:l},u=s.depth>1?n:o(n);s.master||(s.master=l);for(var p=function(e){return e&&e.verbose?{warn:function(){var e=Array.prototype.slice.call(arguments);console.warn.apply(console,e)}}:{warn:function(){}}}(s),f=1;f>0;)f=0,r(c,s.state,(function(t,n,r){if(a(t,n)){var o,l=t[n];if(f++,s.cache[l])if((o=s.cache[l]).resolved)p.warn("Patching %s for %s",l,o.path),r.parent[r.pkey]=o.data,s.$ref&&"object"==typeof r.parent[r.pkey]&&(r.parent[r.pkey][s.$ref]=l);else{if(l===o.path)throw new Error("Tight circle at "+o.path);p.warn("Unresolved ref"),r.parent[r.pkey]=i(o.source,o.path),!1===r.parent[r.pkey]&&(r.parent[r.pkey]=i(o.source,o.key)),s.$ref&&"object"==typeof r.parent[r.pkey]&&(r.parent[s.$ref]=l)}else(o={}).path=r.path.split("/$ref")[0],o.key=l,p.warn("Dereffing %s at %s",l,o.path),o.source=u,o.data=i(o.source,o.key),!1===o.data&&(o.data=i(s.master,o.key),o.source=s.master),!1===o.data&&p.warn("Missing $ref target",o.key),s.cache[l]=o,o.data=r.parent[r.pkey]=e(i(o.source,o.key),o.source,s),s.$ref&&"object"==typeof r.parent[r.pkey]&&(r.parent[r.pkey][s.$ref]=l),o.resolved=!0}}));return c.data}}},function(e,t,n){"use strict";function r(){return{depth:0,seen:new WeakMap,top:!0,combine:!1,allowRefSiblings:!1}}e.exports={getDefaultState:r,walkSchema:function e(t,n,o,i){if(void 0===o.depth&&(o=r()),null==t)return t;if(void 0!==t.$ref){var a={$ref:t.$ref};return o.allowRefSiblings&&t.description&&(a.description=t.description),i(a,n,o),a}if(o.combine&&(t.allOf&&Array.isArray(t.allOf)&&1===t.allOf.length&&delete(t=Object.assign({},t.allOf[0],t)).allOf,t.anyOf&&Array.isArray(t.anyOf)&&1===t.anyOf.length&&delete(t=Object.assign({},t.anyOf[0],t)).anyOf,t.oneOf&&Array.isArray(t.oneOf)&&1===t.oneOf.length&&delete(t=Object.assign({},t.oneOf[0],t)).oneOf),i(t,n,o),o.seen.has(t))return t;if("object"==typeof t&&null!==t&&o.seen.set(t,!0),o.top=!1,o.depth++,void 0!==t.items&&(o.property="items",e(t.items,t,o,i)),t.additionalItems&&"object"==typeof t.additionalItems&&(o.property="additionalItems",e(t.additionalItems,t,o,i)),t.additionalProperties&&"object"==typeof t.additionalProperties&&(o.property="additionalProperties",e(t.additionalProperties,t,o,i)),t.properties)for(var s in t.properties){var l=t.properties[s];o.property="properties/"+s,e(l,t,o,i)}if(t.patternProperties)for(var s in t.patternProperties){l=t.patternProperties[s];o.property="patternProperties/"+s,e(l,t,o,i)}if(t.allOf)for(var c in t.allOf){l=t.allOf[c];o.property="allOf/"+c,e(l,t,o,i)}if(t.anyOf)for(var c in t.anyOf){l=t.anyOf[c];o.property="anyOf/"+c,e(l,t,o,i)}if(t.oneOf)for(var c in t.oneOf){l=t.oneOf[c];o.property="oneOf/"+c,e(l,t,o,i)}return t.not&&(o.property="not",e(t.not,t,o,i)),o.depth--,t}}},function(e,t,n){"use strict";var r=n(91);e.exports={statusCodes:Object.assign({},{default:"Default response","1XX":"Informational",103:"Early hints","2XX":"Successful","3XX":"Redirection","4XX":"Client Error","5XX":"Server Error","7XX":"Developer Error"},r.STATUS_CODES)}},function(e){e.exports=JSON.parse('{"name":"swagger2openapi","version":"5.3.4","description":"Convert Swagger 2.0 definitions to OpenApi 3.0 and validate","main":"index.js","bin":{"swagger2openapi":"./swagger2openapi.js","oas-validate":"./oas-validate.js","boast":"./boast.js"},"scripts":{"test":"mocha"},"browserify":{"transform":[["babelify",{"presets":["es2015"]}]]},"repository":{"url":"https://github.com/Mermade/oas-kit.git","type":"git"},"bugs":{"url":"https://github.com/mermade/oas-kit/issues"},"author":"Mike Ralphson <mike.ralphson@gmail.com>","license":"BSD-3-Clause","dependencies":{"better-ajv-errors":"^0.6.1","call-me-maybe":"^1.0.1","node-fetch-h2":"^2.3.0","node-readfiles":"^0.2.0","oas-kit-common":"^1.0.7","oas-resolver":"^2.2.8","oas-schema-walker":"^1.1.3","oas-validator":"^3.3.4","reftools":"^1.0.11","yaml":"^1.8.0","yargs":"^12.0.5"},"keywords":["swagger","openapi","openapi2","openapi3","converter","conversion","validator","validation","resolver","lint","linter"],"gitHead":"3c04d8c190507d806746d45042fcb8d579dfb237","_resolved":"https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-5.3.4.tgz","_integrity":"sha512-4LSutujtmehQFkRG4MAObjnI414S8VHSZ2tDAT88XxK6LhgYWUcYGZ0LNDecx5mkxAn0gOdfCJY0MCUPKJDqlw==","_from":"swagger2openapi@5.3.4"}')},function(e,t){var n=Object.prototype.hasOwnProperty,r=Object.prototype.toString;e.exports=function(e,t,o){if("[object Function]"!==r.call(t))throw new TypeError("iterator must be a function");var i=e.length;if(i===+i)for(var a=0;a<i;a++)t.call(o,e[a],a,e);else for(var s in e)n.call(e,s)&&t.call(o,e[s],s,e)}},function(e,t){!function(e){var t="\\b(?:BASH|BASHOPTS|BASH_ALIASES|BASH_ARGC|BASH_ARGV|BASH_CMDS|BASH_COMPLETION_COMPAT_DIR|BASH_LINENO|BASH_REMATCH|BASH_SOURCE|BASH_VERSINFO|BASH_VERSION|COLORTERM|COLUMNS|COMP_WORDBREAKS|DBUS_SESSION_BUS_ADDRESS|DEFAULTS_PATH|DESKTOP_SESSION|DIRSTACK|DISPLAY|EUID|GDMSESSION|GDM_LANG|GNOME_KEYRING_CONTROL|GNOME_KEYRING_PID|GPG_AGENT_INFO|GROUPS|HISTCONTROL|HISTFILE|HISTFILESIZE|HISTSIZE|HOME|HOSTNAME|HOSTTYPE|IFS|INSTANCE|JOB|LANG|LANGUAGE|LC_ADDRESS|LC_ALL|LC_IDENTIFICATION|LC_MEASUREMENT|LC_MONETARY|LC_NAME|LC_NUMERIC|LC_PAPER|LC_TELEPHONE|LC_TIME|LESSCLOSE|LESSOPEN|LINES|LOGNAME|LS_COLORS|MACHTYPE|MAILCHECK|MANDATORY_PATH|NO_AT_BRIDGE|OLDPWD|OPTERR|OPTIND|ORBIT_SOCKETDIR|OSTYPE|PAPERSIZE|PATH|PIPESTATUS|PPID|PS1|PS2|PS3|PS4|PWD|RANDOM|REPLY|SECONDS|SELINUX_INIT|SESSION|SESSIONTYPE|SESSION_MANAGER|SHELL|SHELLOPTS|SHLVL|SSH_AUTH_SOCK|TERM|UID|UPSTART_EVENTS|UPSTART_INSTANCE|UPSTART_JOB|UPSTART_SESSION|USER|WINDOWID|XAUTHORITY|XDG_CONFIG_DIRS|XDG_CURRENT_DESKTOP|XDG_DATA_DIRS|XDG_GREETER_DATA_DIR|XDG_MENU_PREFIX|XDG_RUNTIME_DIR|XDG_SEAT|XDG_SEAT_PATH|XDG_SESSION_DESKTOP|XDG_SESSION_ID|XDG_SESSION_PATH|XDG_SESSION_TYPE|XDG_VTNR|XMODIFIERS)\\b",n={environment:{pattern:RegExp("\\$"+t),alias:"constant"},variable:[{pattern:/\$?\(\([\s\S]+?\)\)/,greedy:!0,inside:{variable:[{pattern:/(^\$\(\([\s\S]+)\)\)/,lookbehind:!0},/^\$\(\(/],number:/\b0x[\dA-Fa-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:[Ee]-?\d+)?/,operator:/--?|-=|\+\+?|\+=|!=?|~|\*\*?|\*=|\/=?|%=?|<<=?|>>=?|<=?|>=?|==?|&&?|&=|\^=?|\|\|?|\|=|\?|:/,punctuation:/\(\(?|\)\)?|,|;/}},{pattern:/\$\((?:\([^)]+\)|[^()])+\)|`[^`]+`/,greedy:!0,inside:{variable:/^\$\(|^`|\)$|`$/}},{pattern:/\$\{[^}]+\}/,greedy:!0,inside:{operator:/:[-=?+]?|[!\/]|##?|%%?|\^\^?|,,?/,punctuation:/[\[\]]/,environment:{pattern:RegExp("(\\{)"+t),lookbehind:!0,alias:"constant"}}},/\$(?:\w+|[#?*!@$])/],entity:/\\(?:[abceEfnrtv\\"]|O?[0-7]{1,3}|x[0-9a-fA-F]{1,2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})/};e.languages.bash={shebang:{pattern:/^#!\s*\/.*/,alias:"important"},comment:{pattern:/(^|[^"{\\$])#.*/,lookbehind:!0},"function-name":[{pattern:/(\bfunction\s+)\w+(?=(?:\s*\(?:\s*\))?\s*\{)/,lookbehind:!0,alias:"function"},{pattern:/\b\w+(?=\s*\(\s*\)\s*\{)/,alias:"function"}],"for-or-select":{pattern:/(\b(?:for|select)\s+)\w+(?=\s+in\s)/,alias:"variable",lookbehind:!0},"assign-left":{pattern:/(^|[\s;|&]|[<>]\()\w+(?=\+?=)/,inside:{environment:{pattern:RegExp("(^|[\\s;|&]|[<>]\\()"+t),lookbehind:!0,alias:"constant"}},alias:"variable",lookbehind:!0},string:[{pattern:/((?:^|[^<])<<-?\s*)(\w+?)\s*(?:\r?\n|\r)(?:[\s\S])*?(?:\r?\n|\r)\2/,lookbehind:!0,greedy:!0,inside:n},{pattern:/((?:^|[^<])<<-?\s*)(["'])(\w+)\2\s*(?:\r?\n|\r)(?:[\s\S])*?(?:\r?\n|\r)\3/,lookbehind:!0,greedy:!0},{pattern:/(["'])(?:\\[\s\S]|\$\([^)]+\)|`[^`]+`|(?!\1)[^\\])*\1/,greedy:!0,inside:n}],environment:{pattern:RegExp("\\$?"+t),alias:"constant"},variable:n.variable,function:{pattern:/(^|[\s;|&]|[<>]\()(?:add|apropos|apt|aptitude|apt-cache|apt-get|aspell|automysqlbackup|awk|basename|bash|bc|bconsole|bg|bzip2|cal|cat|cfdisk|chgrp|chkconfig|chmod|chown|chroot|cksum|clear|cmp|column|comm|cp|cron|crontab|csplit|curl|cut|date|dc|dd|ddrescue|debootstrap|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|du|egrep|eject|env|ethtool|expand|expect|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|git|gparted|grep|groupadd|groupdel|groupmod|groups|grub-mkconfig|gzip|halt|head|hg|history|host|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|ip|jobs|join|kill|killall|less|link|ln|locate|logname|logrotate|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|lynx|make|man|mc|mdadm|mkconfig|mkdir|mke2fs|mkfifo|mkfs|mkisofs|mknod|mkswap|mmv|more|most|mount|mtools|mtr|mutt|mv|nano|nc|netstat|nice|nl|nohup|notify-send|npm|nslookup|op|open|parted|passwd|paste|pathchk|ping|pkill|pnpm|popd|pr|printcap|printenv|ps|pushd|pv|quota|quotacheck|quotactl|ram|rar|rcp|reboot|remsync|rename|renice|rev|rm|rmdir|rpm|rsync|scp|screen|sdiff|sed|sendmail|seq|service|sftp|sh|shellcheck|shuf|shutdown|sleep|slocate|sort|split|ssh|stat|strace|su|sudo|sum|suspend|swapon|sync|tac|tail|tar|tee|time|timeout|top|touch|tr|traceroute|tsort|tty|umount|uname|unexpand|uniq|units|unrar|unshar|unzip|update-grub|uptime|useradd|userdel|usermod|users|uudecode|uuencode|v|vdir|vi|vim|virsh|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yarn|yes|zenity|zip|zsh|zypper)(?=$|[)\s;|&])/,lookbehind:!0},keyword:{pattern:/(^|[\s;|&]|[<>]\()(?:if|then|else|elif|fi|for|while|in|case|esac|function|select|do|done|until)(?=$|[)\s;|&])/,lookbehind:!0},builtin:{pattern:/(^|[\s;|&]|[<>]\()(?:\.|:|break|cd|continue|eval|exec|exit|export|getopts|hash|pwd|readonly|return|shift|test|times|trap|umask|unset|alias|bind|builtin|caller|command|declare|echo|enable|help|let|local|logout|mapfile|printf|read|readarray|source|type|typeset|ulimit|unalias|set|shopt)(?=$|[)\s;|&])/,lookbehind:!0,alias:"class-name"},boolean:{pattern:/(^|[\s;|&]|[<>]\()(?:true|false)(?=$|[)\s;|&])/,lookbehind:!0},"file-descriptor":{pattern:/\B&\d\b/,alias:"important"},operator:{pattern:/\d?<>|>\||\+=|==?|!=?|=~|<<[<-]?|[&\d]?>>|\d?[<>]&?|&[>&]?|\|[&|]?|<=?|>=?/,inside:{"file-descriptor":{pattern:/^\d/,alias:"important"}}},punctuation:/\$?\(\(?|\)\)?|\.\.|[{}[\];\\]/,number:{pattern:/(^|\s)(?:[1-9]\d*|0)(?:[.,]\d+)?\b/,lookbehind:!0}};for(var r=["comment","function-name","for-or-select","assign-left","string","environment","function","keyword","builtin","boolean","file-descriptor","operator","punctuation","number"],o=n.variable[1].inside,i=0;i<r.length;i++)o[r[i]]=e.languages.bash[r[i]];e.languages.shell=e.languages.bash}(Prism)},function(e,t){Prism.languages.c=Prism.languages.extend("clike",{"class-name":{pattern:/(\b(?:enum|struct)\s+)\w+/,lookbehind:!0},keyword:/\b(?:_Alignas|_Alignof|_Atomic|_Bool|_Complex|_Generic|_Imaginary|_Noreturn|_Static_assert|_Thread_local|asm|typeof|inline|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|union|unsigned|void|volatile|while)\b/,operator:/>>=?|<<=?|->|([-+&|:])\1|[?:~]|[-+*/%&|^!=<>]=?/,number:/(?:\b0x(?:[\da-f]+\.?[\da-f]*|\.[\da-f]+)(?:p[+-]?\d+)?|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?)[ful]*/i}),Prism.languages.insertBefore("c","string",{macro:{pattern:/(^\s*)#\s*[a-z]+(?:[^\r\n\\]|\\(?:\r\n|[\s\S]))*/im,lookbehind:!0,alias:"property",inside:{string:{pattern:/(#\s*include\s*)(?:<.+?>|("|')(?:\\?.)+?\2)/,lookbehind:!0},directive:{pattern:/(#\s*)\b(?:define|defined|elif|else|endif|error|ifdef|ifndef|if|import|include|line|pragma|undef|using)\b/,lookbehind:!0,alias:"keyword"}}},constant:/\b(?:__FILE__|__LINE__|__DATE__|__TIME__|__TIMESTAMP__|__func__|EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|stdin|stdout|stderr)\b/}),delete Prism.languages.c.boolean},function(e,t){Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|interface|extends|implements|trait|instanceof|new)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,boolean:/\b(?:true|false)\b/,function:/\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/}},function(e,t){!function(e){var t=/#(?!\{).+/,n={pattern:/#\{[^}]+\}/,alias:"variable"};e.languages.coffeescript=e.languages.extend("javascript",{comment:t,string:[{pattern:/'(?:\\[\s\S]|[^\\'])*'/,greedy:!0},{pattern:/"(?:\\[\s\S]|[^\\"])*"/,greedy:!0,inside:{interpolation:n}}],keyword:/\b(?:and|break|by|catch|class|continue|debugger|delete|do|each|else|extend|extends|false|finally|for|if|in|instanceof|is|isnt|let|loop|namespace|new|no|not|null|of|off|on|or|own|return|super|switch|then|this|throw|true|try|typeof|undefined|unless|until|when|while|window|with|yes|yield)\b/,"class-member":{pattern:/@(?!\d)\w+/,alias:"variable"}}),e.languages.insertBefore("coffeescript","comment",{"multiline-comment":{pattern:/###[\s\S]+?###/,alias:"comment"},"block-regex":{pattern:/\/{3}[\s\S]*?\/{3}/,alias:"regex",inside:{comment:t,interpolation:n}}}),e.languages.insertBefore("coffeescript","string",{"inline-javascript":{pattern:/`(?:\\[\s\S]|[^\\`])*`/,inside:{delimiter:{pattern:/^`|`$/,alias:"punctuation"},rest:e.languages.javascript}},"multiline-string":[{pattern:/'''[\s\S]*?'''/,greedy:!0,alias:"string"},{pattern:/"""[\s\S]*?"""/,greedy:!0,alias:"string",inside:{interpolation:n}}]}),e.languages.insertBefore("coffeescript","keyword",{property:/(?!\d)\w+(?=\s*:(?!:))/}),delete e.languages.coffeescript["template-string"],e.languages.coffee=e.languages.coffeescript}(Prism)},function(e,t){Prism.languages.cpp=Prism.languages.extend("c",{"class-name":{pattern:/(\b(?:class|enum|struct)\s+)\w+/,lookbehind:!0},keyword:/\b(?:alignas|alignof|asm|auto|bool|break|case|catch|char|char16_t|char32_t|class|compl|const|constexpr|const_cast|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|float|for|friend|goto|if|inline|int|int8_t|int16_t|int32_t|int64_t|uint8_t|uint16_t|uint32_t|uint64_t|long|mutable|namespace|new|noexcept|nullptr|operator|private|protected|public|register|reinterpret_cast|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|union|unsigned|using|virtual|void|volatile|wchar_t|while)\b/,number:{pattern:/(?:\b0b[01']+|\b0x(?:[\da-f']+\.?[\da-f']*|\.[\da-f']+)(?:p[+-]?[\d']+)?|(?:\b[\d']+\.?[\d']*|\B\.[\d']+)(?:e[+-]?[\d']+)?)[ful]*/i,greedy:!0},operator:/>>=?|<<=?|->|([-+&|:])\1|[?:~]|[-+*/%&|^!=<>]=?|\b(?:and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\b/,boolean:/\b(?:true|false)\b/}),Prism.languages.insertBefore("cpp","string",{"raw-string":{pattern:/R"([^()\\ ]{0,16})\([\s\S]*?\)\1"/,alias:"string",greedy:!0}})},function(e,t){Prism.languages.csharp=Prism.languages.extend("clike",{keyword:/\b(?:abstract|add|alias|as|ascending|async|await|base|bool|break|byte|case|catch|char|checked|class|const|continue|decimal|default|delegate|descending|do|double|dynamic|else|enum|event|explicit|extern|false|finally|fixed|float|for|foreach|from|get|global|goto|group|if|implicit|in|int|interface|internal|into|is|join|let|lock|long|namespace|new|null|object|operator|orderby|out|override|params|partial|private|protected|public|readonly|ref|remove|return|sbyte|sealed|select|set|short|sizeof|stackalloc|static|string|struct|switch|this|throw|true|try|typeof|uint|ulong|unchecked|unsafe|ushort|using|value|var|virtual|void|volatile|where|while|yield)\b/,string:[{pattern:/@("|')(?:\1\1|\\[\s\S]|(?!\1)[^\\])*\1/,greedy:!0},{pattern:/("|')(?:\\.|(?!\1)[^\\\r\n])*?\1/,greedy:!0}],"class-name":[{pattern:/\b[A-Z]\w*(?:\.\w+)*\b(?=\s+\w+)/,inside:{punctuation:/\./}},{pattern:/(\[)[A-Z]\w*(?:\.\w+)*\b/,lookbehind:!0,inside:{punctuation:/\./}},{pattern:/(\b(?:class|interface)\s+[A-Z]\w*(?:\.\w+)*\s*:\s*)[A-Z]\w*(?:\.\w+)*\b/,lookbehind:!0,inside:{punctuation:/\./}},{pattern:/((?:\b(?:class|interface|new)\s+)|(?:catch\s+\())[A-Z]\w*(?:\.\w+)*\b/,lookbehind:!0,inside:{punctuation:/\./}}],number:/\b0x[\da-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)f?/i,operator:/>>=?|<<=?|[-=]>|([-+&|?])\1|~|[-+*/%&|^!=<>]=?/,punctuation:/\?\.?|::|[{}[\];(),.:]/}),Prism.languages.insertBefore("csharp","class-name",{"generic-method":{pattern:/\w+\s*<[^>\r\n]+?>\s*(?=\()/,inside:{function:/^\w+/,"class-name":{pattern:/\b[A-Z]\w*(?:\.\w+)*\b/,inside:{punctuation:/\./}},keyword:Prism.languages.csharp.keyword,punctuation:/[<>(),.:]/}},preprocessor:{pattern:/(^\s*)#.*/m,lookbehind:!0,alias:"property",inside:{directive:{pattern:/(\s*#)\b(?:define|elif|else|endif|endregion|error|if|line|pragma|region|undef|warning)\b/,lookbehind:!0,alias:"keyword"}}}}),Prism.languages.dotnet=Prism.languages.cs=Prism.languages.csharp},function(e,t){Prism.languages.go=Prism.languages.extend("clike",{keyword:/\b(?:break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go(?:to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b/,builtin:/\b(?:bool|byte|complex(?:64|128)|error|float(?:32|64)|rune|string|u?int(?:8|16|32|64)?|uintptr|append|cap|close|complex|copy|delete|imag|len|make|new|panic|print(?:ln)?|real|recover)\b/,boolean:/\b(?:_|iota|nil|true|false)\b/,operator:/[*\/%^!=]=?|\+[=+]?|-[=-]?|\|[=|]?|&(?:=|&|\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\.\.\./,number:/(?:\b0x[a-f\d]+|(?:\b\d+\.?\d*|\B\.\d+)(?:e[-+]?\d+)?)i?/i,string:{pattern:/(["'`])(?:\\[\s\S]|(?!\1)[^\\])*\1/,greedy:!0}}),delete Prism.languages.go["class-name"]},function(e,t){!function(e){e.languages.http={"request-line":{pattern:/^(?:POST|GET|PUT|DELETE|OPTIONS|PATCH|TRACE|CONNECT)\s(?:https?:\/\/|\/)\S+\sHTTP\/[0-9.]+/m,inside:{property:/^(?:POST|GET|PUT|DELETE|OPTIONS|PATCH|TRACE|CONNECT)\b/,"attr-name":/:\w+/}},"response-status":{pattern:/^HTTP\/1.[01] \d+.*/m,inside:{property:{pattern:/(^HTTP\/1.[01] )\d+.*/i,lookbehind:!0}}},"header-name":{pattern:/^[\w-]+:(?=.)/m,alias:"keyword"}};var t,n=e.languages,r={"application/javascript":n.javascript,"application/json":n.json||n.javascript,"application/xml":n.xml,"text/xml":n.xml,"text/html":n.html,"text/css":n.css},o={"application/json":!0,"application/xml":!0};function i(e){var t=e.replace(/^[a-z]+\//,"");return"(?:"+e+"|"+("\\w+/(?:[\\w.-]+\\+)+"+t+"(?![+\\w.-])")+")"}for(var a in r)if(r[a]){t=t||{};var s=o[a]?i(a):a;t[a.replace(/\//g,"-")]={pattern:RegExp("(content-type:\\s*"+s+"[\\s\\S]*?)(?:\\r?\\n|\\r){2}[\\s\\S]*","i"),lookbehind:!0,inside:r[a]}}t&&e.languages.insertBefore("http","header-name",t)}(Prism)},function(e,t){!function(e){var t=/\b(?:abstract|assert|boolean|break|byte|case|catch|char|class|const|continue|default|do|double|else|enum|exports|extends|final|finally|float|for|goto|if|implements|import|instanceof|int|interface|long|module|native|new|null|open|opens|package|private|protected|provides|public|requires|return|short|static|strictfp|super|switch|synchronized|this|throw|throws|to|transient|transitive|try|uses|var|void|volatile|while|with|yield)\b/,n=/\b[A-Z](?:\w*[a-z]\w*)?\b/;e.languages.java=e.languages.extend("clike",{"class-name":[n,/\b[A-Z]\w*(?=\s+\w+\s*[;,=())])/],keyword:t,function:[e.languages.clike.function,{pattern:/(\:\:)[a-z_]\w*/,lookbehind:!0}],number:/\b0b[01][01_]*L?\b|\b0x[\da-f_]*\.?[\da-f_p+-]+\b|(?:\b\d[\d_]*\.?[\d_]*|\B\.\d[\d_]*)(?:e[+-]?\d[\d_]*)?[dfl]?/i,operator:{pattern:/(^|[^.])(?:<<=?|>>>?=?|->|--|\+\+|&&|\|\||::|[?:~]|[-+*/%&|^!=<>]=?)/m,lookbehind:!0}}),e.languages.insertBefore("java","string",{"triple-quoted-string":{pattern:/"""[ \t]*[\r\n](?:(?:"|"")?(?:\\.|[^"\\]))*"""/,greedy:!0,alias:"string"}}),e.languages.insertBefore("java","class-name",{annotation:{alias:"punctuation",pattern:/(^|[^.])@\w+/,lookbehind:!0},namespace:{pattern:/(\b(?:exports|import(?:\s+static)?|module|open|opens|package|provides|requires|to|transitive|uses|with)\s+)[a-z]\w*(?:\.[a-z]\w*)+/,lookbehind:!0,inside:{punctuation:/\./}},generics:{pattern:/<(?:[\w\s,.&?]|<(?:[\w\s,.&?]|<(?:[\w\s,.&?]|<[\w\s,.&?]*>)*>)*>)*>/,inside:{"class-name":n,keyword:t,punctuation:/[<>(),.:]/,operator:/[?&|]/}}})}(Prism)},function(e,t){Prism.languages.lua={comment:/^#!.+|--(?:\[(=*)\[[\s\S]*?\]\1\]|.*)/m,string:{pattern:/(["'])(?:(?!\1)[^\\\r\n]|\\z(?:\r\n|\s)|\\(?:\r\n|[\s\S]))*\1|\[(=*)\[[\s\S]*?\]\2\]/,greedy:!0},number:/\b0x[a-f\d]+\.?[a-f\d]*(?:p[+-]?\d+)?\b|\b\d+(?:\.\B|\.?\d*(?:e[+-]?\d+)?\b)|\B\.\d+(?:e[+-]?\d+)?\b/i,keyword:/\b(?:and|break|do|else|elseif|end|false|for|function|goto|if|in|local|nil|not|or|repeat|return|then|true|until|while)\b/,function:/(?!\d)\w+(?=\s*(?:[({]))/,operator:[/[-+*%^&|#]|\/\/?|<[<=]?|>[>=]?|[=~]=?/,{pattern:/(^|[^.])\.\.(?!\.)/,lookbehind:!0}],punctuation:/[\[\](){},;]|\.+|:+/}},function(e,t){!function(e){function t(e,t){return"___"+e.toUpperCase()+t+"___"}Object.defineProperties(e.languages["markup-templating"]={},{buildPlaceholders:{value:function(n,r,o,i){if(n.language===r){var a=n.tokenStack=[];n.code=n.code.replace(o,(function(e){if("function"==typeof i&&!i(e))return e;for(var o,s=a.length;-1!==n.code.indexOf(o=t(r,s));)++s;return a[s]=e,o})),n.grammar=e.languages.markup}}},tokenizePlaceholders:{value:function(n,r){if(n.language===r&&n.tokenStack){n.grammar=e.languages[r];var o=0,i=Object.keys(n.tokenStack);!function a(s){for(var l=0;l<s.length&&!(o>=i.length);l++){var c=s[l];if("string"==typeof c||c.content&&"string"==typeof c.content){var u=i[o],p=n.tokenStack[u],f="string"==typeof c?c:c.content,d=t(r,u),h=f.indexOf(d);if(h>-1){++o;var m=f.substring(0,h),g=new e.Token(r,e.tokenize(p,n.grammar),"language-"+r,p),y=f.substring(h+d.length),v=[];m&&v.push.apply(v,a([m])),v.push(g),y&&v.push.apply(v,a([y])),"string"==typeof c?s.splice.apply(s,[l,1].concat(v)):c.content=v}}else c.content&&a(c.content)}return s}(n.tokens)}}}})}(Prism)},function(e,t){Prism.languages.markup={comment:/<!--[\s\S]*?-->/,prolog:/<\?[\s\S]+?\?>/,doctype:{pattern:/<!DOCTYPE(?:[^>"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:(?!<!--)[^"'\]]|"[^"]*"|'[^']*'|<!--[\s\S]*?-->)*\]\s*)?>/i,greedy:!0},cdata:/<!\[CDATA\[[\s\S]*?]]>/i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/i,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/i,inside:{punctuation:[/^=/,{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/&#?[\da-z]{1,8};/i},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.hooks.add("wrap",(function(e){"entity"===e.type&&(e.attributes.title=e.content.replace(/&/,"&"))})),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(e,t){var n={};n["language-"+t]={pattern:/(^<!\[CDATA\[)[\s\S]+?(?=\]\]>$)/i,lookbehind:!0,inside:Prism.languages[t]},n.cdata=/^<!\[CDATA\[|\]\]>$/i;var r={"included-cdata":{pattern:/<!\[CDATA\[[\s\S]*?\]\]>/i,inside:n}};r["language-"+t]={pattern:/[\s\S]+/,inside:Prism.languages[t]};var o={};o[e]={pattern:RegExp(/(<__[\s\S]*?>)(?:<!\[CDATA\[[\s\S]*?\]\]>\s*|[\s\S])*?(?=<\/__>)/.source.replace(/__/g,e),"i"),lookbehind:!0,greedy:!0,inside:r},Prism.languages.insertBefore("markup","cdata",o)}}),Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup},function(e,t){Prism.languages.objectivec=Prism.languages.extend("c",{keyword:/\b(?:asm|typeof|inline|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|union|unsigned|void|volatile|while|in|self|super)\b|(?:@interface|@end|@implementation|@protocol|@class|@public|@protected|@private|@property|@try|@catch|@finally|@throw|@synthesize|@dynamic|@selector)\b/,string:/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1|@"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,operator:/-[->]?|\+\+?|!=?|<<?=?|>>?=?|==?|&&?|\|\|?|[~^%?*\/@]/}),delete Prism.languages.objectivec["class-name"]},function(e,t){Prism.languages.perl={comment:[{pattern:/(^\s*)=\w+[\s\S]*?=cut.*/m,lookbehind:!0},{pattern:/(^|[^\\$])#.*/,lookbehind:!0}],string:[{pattern:/\b(?:q|qq|qx|qw)\s*([^a-zA-Z0-9\s{(\[<])(?:(?!\1)[^\\]|\\[\s\S])*\1/,greedy:!0},{pattern:/\b(?:q|qq|qx|qw)\s+([a-zA-Z0-9])(?:(?!\1)[^\\]|\\[\s\S])*\1/,greedy:!0},{pattern:/\b(?:q|qq|qx|qw)\s*\((?:[^()\\]|\\[\s\S])*\)/,greedy:!0},{pattern:/\b(?:q|qq|qx|qw)\s*\{(?:[^{}\\]|\\[\s\S])*\}/,greedy:!0},{pattern:/\b(?:q|qq|qx|qw)\s*\[(?:[^[\]\\]|\\[\s\S])*\]/,greedy:!0},{pattern:/\b(?:q|qq|qx|qw)\s*<(?:[^<>\\]|\\[\s\S])*>/,greedy:!0},{pattern:/("|`)(?:(?!\1)[^\\]|\\[\s\S])*\1/,greedy:!0},{pattern:/'(?:[^'\\\r\n]|\\.)*'/,greedy:!0}],regex:[{pattern:/\b(?:m|qr)\s*([^a-zA-Z0-9\s{(\[<])(?:(?!\1)[^\\]|\\[\s\S])*\1[msixpodualngc]*/,greedy:!0},{pattern:/\b(?:m|qr)\s+([a-zA-Z0-9])(?:(?!\1)[^\\]|\\[\s\S])*\1[msixpodualngc]*/,greedy:!0},{pattern:/\b(?:m|qr)\s*\((?:[^()\\]|\\[\s\S])*\)[msixpodualngc]*/,greedy:!0},{pattern:/\b(?:m|qr)\s*\{(?:[^{}\\]|\\[\s\S])*\}[msixpodualngc]*/,greedy:!0},{pattern:/\b(?:m|qr)\s*\[(?:[^[\]\\]|\\[\s\S])*\][msixpodualngc]*/,greedy:!0},{pattern:/\b(?:m|qr)\s*<(?:[^<>\\]|\\[\s\S])*>[msixpodualngc]*/,greedy:!0},{pattern:/(^|[^-]\b)(?:s|tr|y)\s*([^a-zA-Z0-9\s{(\[<])(?:(?!\2)[^\\]|\\[\s\S])*\2(?:(?!\2)[^\\]|\\[\s\S])*\2[msixpodualngcer]*/,lookbehind:!0,greedy:!0},{pattern:/(^|[^-]\b)(?:s|tr|y)\s+([a-zA-Z0-9])(?:(?!\2)[^\\]|\\[\s\S])*\2(?:(?!\2)[^\\]|\\[\s\S])*\2[msixpodualngcer]*/,lookbehind:!0,greedy:!0},{pattern:/(^|[^-]\b)(?:s|tr|y)\s*\((?:[^()\\]|\\[\s\S])*\)\s*\((?:[^()\\]|\\[\s\S])*\)[msixpodualngcer]*/,lookbehind:!0,greedy:!0},{pattern:/(^|[^-]\b)(?:s|tr|y)\s*\{(?:[^{}\\]|\\[\s\S])*\}\s*\{(?:[^{}\\]|\\[\s\S])*\}[msixpodualngcer]*/,lookbehind:!0,greedy:!0},{pattern:/(^|[^-]\b)(?:s|tr|y)\s*\[(?:[^[\]\\]|\\[\s\S])*\]\s*\[(?:[^[\]\\]|\\[\s\S])*\][msixpodualngcer]*/,lookbehind:!0,greedy:!0},{pattern:/(^|[^-]\b)(?:s|tr|y)\s*<(?:[^<>\\]|\\[\s\S])*>\s*<(?:[^<>\\]|\\[\s\S])*>[msixpodualngcer]*/,lookbehind:!0,greedy:!0},{pattern:/\/(?:[^\/\\\r\n]|\\.)*\/[msixpodualngc]*(?=\s*(?:$|[\r\n,.;})&|\-+*~<>!?^]|(?:lt|gt|le|ge|eq|ne|cmp|not|and|or|xor|x)\b))/,greedy:!0}],variable:[/[&*$@%]\{\^[A-Z]+\}/,/[&*$@%]\^[A-Z_]/,/[&*$@%]#?(?=\{)/,/[&*$@%]#?(?:(?:::)*'?(?!\d)[\w$]+)+(?:::)*/i,/[&*$@%]\d+/,/(?!%=)[$@%][!"#$%&'()*+,\-.\/:;<=>?@[\\\]^_`{|}~]/],filehandle:{pattern:/<(?![<=])\S*>|\b_\b/,alias:"symbol"},vstring:{pattern:/v\d+(?:\.\d+)*|\d+(?:\.\d+){2,}/,alias:"string"},function:{pattern:/sub [a-z0-9_]+/i,inside:{keyword:/sub/}},keyword:/\b(?:any|break|continue|default|delete|die|do|else|elsif|eval|for|foreach|given|goto|if|last|local|my|next|our|package|print|redo|require|return|say|state|sub|switch|undef|unless|until|use|when|while)\b/,number:/\b(?:0x[\dA-Fa-f](?:_?[\dA-Fa-f])*|0b[01](?:_?[01])*|(?:\d(?:_?\d)*)?\.?\d(?:_?\d)*(?:[Ee][+-]?\d+)?)\b/,operator:/-[rwxoRWXOezsfdlpSbctugkTBMAC]\b|\+[+=]?|-[-=>]?|\*\*?=?|\/\/?=?|=[=~>]?|~[~=]?|\|\|?=?|&&?=?|<(?:=>?|<=?)?|>>?=?|![~=]?|[%^]=?|\.(?:=|\.\.?)?|[\\?]|\bx(?:=|\b)|\b(?:lt|gt|le|ge|eq|ne|cmp|not|and|or|xor)\b/,punctuation:/[{}[\];(),:]/}},function(e,t){!function(e){e.languages.php=e.languages.extend("clike",{keyword:/\b(?:__halt_compiler|abstract|and|array|as|break|callable|case|catch|class|clone|const|continue|declare|default|die|do|echo|else|elseif|empty|enddeclare|endfor|endforeach|endif|endswitch|endwhile|eval|exit|extends|final|finally|for|foreach|function|global|goto|if|implements|include|include_once|instanceof|insteadof|interface|isset|list|namespace|new|or|parent|print|private|protected|public|require|require_once|return|static|switch|throw|trait|try|unset|use|var|while|xor|yield)\b/i,boolean:{pattern:/\b(?:false|true)\b/i,alias:"constant"},constant:[/\b[A-Z_][A-Z0-9_]*\b/,/\b(?:null)\b/i],comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,lookbehind:!0}}),e.languages.insertBefore("php","string",{"shell-comment":{pattern:/(^|[^\\])#.*/,lookbehind:!0,alias:"comment"}}),e.languages.insertBefore("php","comment",{delimiter:{pattern:/\?>$|^<\?(?:php(?=\s)|=)?/i,alias:"important"}}),e.languages.insertBefore("php","keyword",{variable:/\$+(?:\w+\b|(?={))/i,package:{pattern:/(\\|namespace\s+|use\s+)[\w\\]+/,lookbehind:!0,inside:{punctuation:/\\/}}}),e.languages.insertBefore("php","operator",{property:{pattern:/(->)[\w]+/,lookbehind:!0}});var t={pattern:/{\$(?:{(?:{[^{}]+}|[^{}]+)}|[^{}])+}|(^|[^\\{])\$+(?:\w+(?:\[.+?]|->\w+)*)/,lookbehind:!0,inside:e.languages.php};e.languages.insertBefore("php","string",{"nowdoc-string":{pattern:/<<<'([^']+)'(?:\r\n?|\n)(?:.*(?:\r\n?|\n))*?\1;/,greedy:!0,alias:"string",inside:{delimiter:{pattern:/^<<<'[^']+'|[a-z_]\w*;$/i,alias:"symbol",inside:{punctuation:/^<<<'?|[';]$/}}}},"heredoc-string":{pattern:/<<<(?:"([^"]+)"(?:\r\n?|\n)(?:.*(?:\r\n?|\n))*?\1;|([a-z_]\w*)(?:\r\n?|\n)(?:.*(?:\r\n?|\n))*?\2;)/i,greedy:!0,alias:"string",inside:{delimiter:{pattern:/^<<<(?:"[^"]+"|[a-z_]\w*)|[a-z_]\w*;$/i,alias:"symbol",inside:{punctuation:/^<<<"?|[";]$/}},interpolation:t}},"single-quoted-string":{pattern:/'(?:\\[\s\S]|[^\\'])*'/,greedy:!0,alias:"string"},"double-quoted-string":{pattern:/"(?:\\[\s\S]|[^\\"])*"/,greedy:!0,alias:"string",inside:{interpolation:t}}}),delete e.languages.php.string,e.hooks.add("before-tokenize",(function(t){if(/<\?/.test(t.code)){e.languages["markup-templating"].buildPlaceholders(t,"php",/<\?(?:[^"'/#]|\/(?![*/])|("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|(?:\/\/|#)(?:[^?\n\r]|\?(?!>))*(?=$|\?>|[\r\n])|\/\*[\s\S]*?(?:\*\/|$))*?(?:\?>|$)/gi)}})),e.hooks.add("after-tokenize",(function(t){e.languages["markup-templating"].tokenizePlaceholders(t,"php")}))}(Prism)},function(e,t){Prism.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0},"string-interpolation":{pattern:/(?:f|rf|fr)(?:("""|''')[\s\S]+?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:{{)*){(?!{)(?:[^{}]|{(?!{)(?:[^{}]|{(?!{)(?:[^{}])+})+})+}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=}$)/,lookbehind:!0},"conversion-option":{pattern:/![sra](?=[:}]$)/,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|rb|br)?("""|''')[\s\S]+?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|rb|br)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^\s*)@\w+(?:\.\w+)*/im,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:and|as|assert|async|await|break|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,boolean:/\b(?:True|False|None)\b/,number:/(?:\b(?=\d)|\B(?=\.))(?:0[bo])?(?:(?:\d|0x[\da-f])[\da-f]*\.?\d*|\.\d+)(?:e[+-]?\d+)?j?\b/i,operator:/[-+%=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},Prism.languages.python["string-interpolation"].inside.interpolation.inside.rest=Prism.languages.python,Prism.languages.py=Prism.languages.python},function(e,t){!function(e){e.languages.ruby=e.languages.extend("clike",{comment:[/#.*/,{pattern:/^=begin\s[\s\S]*?^=end/m,greedy:!0}],"class-name":{pattern:/(\b(?:class)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:alias|and|BEGIN|begin|break|case|class|def|define_method|defined|do|each|else|elsif|END|end|ensure|extend|for|if|in|include|module|new|next|nil|not|or|prepend|protected|private|public|raise|redo|require|rescue|retry|return|self|super|then|throw|undef|unless|until|when|while|yield)\b/});var t={pattern:/#\{[^}]+\}/,inside:{delimiter:{pattern:/^#\{|\}$/,alias:"tag"},rest:e.languages.ruby}};delete e.languages.ruby.function,e.languages.insertBefore("ruby","keyword",{regex:[{pattern:/%r([^a-zA-Z0-9\s{(\[<])(?:(?!\1)[^\\]|\\[\s\S])*\1[gim]{0,3}/,greedy:!0,inside:{interpolation:t}},{pattern:/%r\((?:[^()\\]|\\[\s\S])*\)[gim]{0,3}/,greedy:!0,inside:{interpolation:t}},{pattern:/%r\{(?:[^#{}\\]|#(?:\{[^}]+\})?|\\[\s\S])*\}[gim]{0,3}/,greedy:!0,inside:{interpolation:t}},{pattern:/%r\[(?:[^\[\]\\]|\\[\s\S])*\][gim]{0,3}/,greedy:!0,inside:{interpolation:t}},{pattern:/%r<(?:[^<>\\]|\\[\s\S])*>[gim]{0,3}/,greedy:!0,inside:{interpolation:t}},{pattern:/(^|[^/])\/(?!\/)(?:\[.+?]|\\.|[^/\\\r\n])+\/[gim]{0,3}(?=\s*(?:$|[\r\n,.;})]))/,lookbehind:!0,greedy:!0}],variable:/[@$]+[a-zA-Z_]\w*(?:[?!]|\b)/,symbol:{pattern:/(^|[^:]):[a-zA-Z_]\w*(?:[?!]|\b)/,lookbehind:!0},"method-definition":{pattern:/(\bdef\s+)[\w.]+/,lookbehind:!0,inside:{function:/\w+$/,rest:e.languages.ruby}}}),e.languages.insertBefore("ruby","number",{builtin:/\b(?:Array|Bignum|Binding|Class|Continuation|Dir|Exception|FalseClass|File|Stat|Fixnum|Float|Hash|Integer|IO|MatchData|Method|Module|NilClass|Numeric|Object|Proc|Range|Regexp|String|Struct|TMS|Symbol|ThreadGroup|Thread|Time|TrueClass)\b/,constant:/\b[A-Z]\w*(?:[?!]|\b)/}),e.languages.ruby.string=[{pattern:/%[qQiIwWxs]?([^a-zA-Z0-9\s{(\[<])(?:(?!\1)[^\\]|\\[\s\S])*\1/,greedy:!0,inside:{interpolation:t}},{pattern:/%[qQiIwWxs]?\((?:[^()\\]|\\[\s\S])*\)/,greedy:!0,inside:{interpolation:t}},{pattern:/%[qQiIwWxs]?\{(?:[^#{}\\]|#(?:\{[^}]+\})?|\\[\s\S])*\}/,greedy:!0,inside:{interpolation:t}},{pattern:/%[qQiIwWxs]?\[(?:[^\[\]\\]|\\[\s\S])*\]/,greedy:!0,inside:{interpolation:t}},{pattern:/%[qQiIwWxs]?<(?:[^<>\\]|\\[\s\S])*>/,greedy:!0,inside:{interpolation:t}},{pattern:/("|')(?:#\{[^}]+\}|\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0,inside:{interpolation:t}}],e.languages.rb=e.languages.ruby}(Prism)},function(e,t){Prism.languages.scala=Prism.languages.extend("java",{keyword:/<-|=>|\b(?:abstract|case|catch|class|def|do|else|extends|final|finally|for|forSome|if|implicit|import|lazy|match|new|null|object|override|package|private|protected|return|sealed|self|super|this|throw|trait|try|type|val|var|while|with|yield)\b/,"triple-quoted-string":{pattern:/"""[\s\S]*?"""/,greedy:!0,alias:"string"},string:{pattern:/("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,greedy:!0},builtin:/\b(?:String|Int|Long|Short|Byte|Boolean|Double|Float|Char|Any|AnyRef|AnyVal|Unit|Nothing)\b/,number:/\b0x[\da-f]*\.?[\da-f]+|(?:\b\d+\.?\d*|\B\.\d+)(?:e\d+)?[dfl]?/i,symbol:/'[^\d\s\\]\w*/}),delete Prism.languages.scala["class-name"],delete Prism.languages.scala.function},function(e,t){Prism.languages.sql={comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|(?:--|\/\/|#).*)/,lookbehind:!0},variable:[{pattern:/@(["'`])(?:\\[\s\S]|(?!\1)[^\\])+\1/,greedy:!0},/@[\w.$]+/],string:{pattern:/(^|[^@\\])("|')(?:\\[\s\S]|(?!\2)[^\\]|\2\2)*\2/,greedy:!0,lookbehind:!0},function:/\b(?:AVG|COUNT|FIRST|FORMAT|LAST|LCASE|LEN|MAX|MID|MIN|MOD|NOW|ROUND|SUM|UCASE)(?=\s*\()/i,keyword:/\b(?:ACTION|ADD|AFTER|ALGORITHM|ALL|ALTER|ANALYZE|ANY|APPLY|AS|ASC|AUTHORIZATION|AUTO_INCREMENT|BACKUP|BDB|BEGIN|BERKELEYDB|BIGINT|BINARY|BIT|BLOB|BOOL|BOOLEAN|BREAK|BROWSE|BTREE|BULK|BY|CALL|CASCADED?|CASE|CHAIN|CHAR(?:ACTER|SET)?|CHECK(?:POINT)?|CLOSE|CLUSTERED|COALESCE|COLLATE|COLUMNS?|COMMENT|COMMIT(?:TED)?|COMPUTE|CONNECT|CONSISTENT|CONSTRAINT|CONTAINS(?:TABLE)?|CONTINUE|CONVERT|CREATE|CROSS|CURRENT(?:_DATE|_TIME|_TIMESTAMP|_USER)?|CURSOR|CYCLE|DATA(?:BASES?)?|DATE(?:TIME)?|DAY|DBCC|DEALLOCATE|DEC|DECIMAL|DECLARE|DEFAULT|DEFINER|DELAYED|DELETE|DELIMITERS?|DENY|DESC|DESCRIBE|DETERMINISTIC|DISABLE|DISCARD|DISK|DISTINCT|DISTINCTROW|DISTRIBUTED|DO|DOUBLE|DROP|DUMMY|DUMP(?:FILE)?|DUPLICATE|ELSE(?:IF)?|ENABLE|ENCLOSED|END|ENGINE|ENUM|ERRLVL|ERRORS|ESCAPED?|EXCEPT|EXEC(?:UTE)?|EXISTS|EXIT|EXPLAIN|EXTENDED|FETCH|FIELDS|FILE|FILLFACTOR|FIRST|FIXED|FLOAT|FOLLOWING|FOR(?: EACH ROW)?|FORCE|FOREIGN|FREETEXT(?:TABLE)?|FROM|FULL|FUNCTION|GEOMETRY(?:COLLECTION)?|GLOBAL|GOTO|GRANT|GROUP|HANDLER|HASH|HAVING|HOLDLOCK|HOUR|IDENTITY(?:_INSERT|COL)?|IF|IGNORE|IMPORT|INDEX|INFILE|INNER|INNODB|INOUT|INSERT|INT|INTEGER|INTERSECT|INTERVAL|INTO|INVOKER|ISOLATION|ITERATE|JOIN|KEYS?|KILL|LANGUAGE|LAST|LEAVE|LEFT|LEVEL|LIMIT|LINENO|LINES|LINESTRING|LOAD|LOCAL|LOCK|LONG(?:BLOB|TEXT)|LOOP|MATCH(?:ED)?|MEDIUM(?:BLOB|INT|TEXT)|MERGE|MIDDLEINT|MINUTE|MODE|MODIFIES|MODIFY|MONTH|MULTI(?:LINESTRING|POINT|POLYGON)|NATIONAL|NATURAL|NCHAR|NEXT|NO|NONCLUSTERED|NULLIF|NUMERIC|OFF?|OFFSETS?|ON|OPEN(?:DATASOURCE|QUERY|ROWSET)?|OPTIMIZE|OPTION(?:ALLY)?|ORDER|OUT(?:ER|FILE)?|OVER|PARTIAL|PARTITION|PERCENT|PIVOT|PLAN|POINT|POLYGON|PRECEDING|PRECISION|PREPARE|PREV|PRIMARY|PRINT|PRIVILEGES|PROC(?:EDURE)?|PUBLIC|PURGE|QUICK|RAISERROR|READS?|REAL|RECONFIGURE|REFERENCES|RELEASE|RENAME|REPEAT(?:ABLE)?|REPLACE|REPLICATION|REQUIRE|RESIGNAL|RESTORE|RESTRICT|RETURNS?|REVOKE|RIGHT|ROLLBACK|ROUTINE|ROW(?:COUNT|GUIDCOL|S)?|RTREE|RULE|SAVE(?:POINT)?|SCHEMA|SECOND|SELECT|SERIAL(?:IZABLE)?|SESSION(?:_USER)?|SET(?:USER)?|SHARE|SHOW|SHUTDOWN|SIMPLE|SMALLINT|SNAPSHOT|SOME|SONAME|SQL|START(?:ING)?|STATISTICS|STATUS|STRIPED|SYSTEM_USER|TABLES?|TABLESPACE|TEMP(?:ORARY|TABLE)?|TERMINATED|TEXT(?:SIZE)?|THEN|TIME(?:STAMP)?|TINY(?:BLOB|INT|TEXT)|TOP?|TRAN(?:SACTIONS?)?|TRIGGER|TRUNCATE|TSEQUAL|TYPES?|UNBOUNDED|UNCOMMITTED|UNDEFINED|UNION|UNIQUE|UNLOCK|UNPIVOT|UNSIGNED|UPDATE(?:TEXT)?|USAGE|USE|USER|USING|VALUES?|VAR(?:BINARY|CHAR|CHARACTER|YING)|VIEW|WAITFOR|WARNINGS|WHEN|WHERE|WHILE|WITH(?: ROLLUP|IN)?|WORK|WRITE(?:TEXT)?|YEAR)\b/i,boolean:/\b(?:TRUE|FALSE|NULL)\b/i,number:/\b0x[\da-f]+\b|\b\d+\.?\d*|\B\.\d+\b/i,operator:/[-+*\/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?|\b(?:AND|BETWEEN|IN|LIKE|NOT|OR|IS|DIV|REGEXP|RLIKE|SOUNDS LIKE|XOR)\b/i,punctuation:/[;[\]()`,.]/}},function(e,t){Prism.languages.swift=Prism.languages.extend("clike",{string:{pattern:/("|')(?:\\(?:\((?:[^()]|\([^)]+\))+\)|\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0,inside:{interpolation:{pattern:/\\\((?:[^()]|\([^)]+\))+\)/,inside:{delimiter:{pattern:/^\\\(|\)$/,alias:"variable"}}}}},keyword:/\b(?:as|associativity|break|case|catch|class|continue|convenience|default|defer|deinit|didSet|do|dynamic(?:Type)?|else|enum|extension|fallthrough|final|for|func|get|guard|if|import|in|infix|init|inout|internal|is|lazy|left|let|mutating|new|none|nonmutating|operator|optional|override|postfix|precedence|prefix|private|protocol|public|repeat|required|rethrows|return|right|safe|self|Self|set|static|struct|subscript|super|switch|throws?|try|Type|typealias|unowned|unsafe|var|weak|where|while|willSet|__(?:COLUMN__|FILE__|FUNCTION__|LINE__))\b/,number:/\b(?:[\d_]+(?:\.[\de_]+)?|0x[a-f0-9_]+(?:\.[a-f0-9p_]+)?|0b[01_]+|0o[0-7_]+)\b/i,constant:/\b(?:nil|[A-Z_]{2,}|k[A-Z][A-Za-z_]+)\b/,atrule:/@\b(?:IB(?:Outlet|Designable|Action|Inspectable)|class_protocol|exported|noreturn|NS(?:Copying|Managed)|objc|UIApplicationMain|auto_closure)\b/,builtin:/\b(?:[A-Z]\S+|abs|advance|alignof(?:Value)?|assert|contains|count(?:Elements)?|debugPrint(?:ln)?|distance|drop(?:First|Last)|dump|enumerate|equal|filter|find|first|getVaList|indices|isEmpty|join|last|lexicographicalCompare|map|max(?:Element)?|min(?:Element)?|numericCast|overlaps|partition|print(?:ln)?|reduce|reflect|reverse|sizeof(?:Value)?|sort(?:ed)?|split|startsWith|stride(?:of(?:Value)?)?|suffix|swap|toDebugString|toString|transcode|underestimateCount|unsafeBitCast|with(?:ExtendedLifetime|Unsafe(?:MutablePointers?|Pointers?)|VaList))\b/}),Prism.languages.swift.string.inside.interpolation.inside.rest=Prism.languages.swift},function(e,t,n){var r=n(316),o=["add","done","toJS","load","search"];e.exports=function(){var e=new Worker(URL.createObjectURL(new Blob(['/*!\n * ReDoc - OpenAPI/Swagger-generated API Reference Documentation\n * -------------------------------------------------------------\n * Version: "2.0.0-rc.24"\n * Repo: https://github.com/Redocly/redoc\n */!function(e){var t={};function r(n){if(t[n])return t[n].exports;var i=t[n]={i:n,l:!1,exports:{}};return e[n].call(i.exports,i,i.exports,r),i.l=!0,i.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)r.d(n,i,function(t){return e[t]}.bind(null,i));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=97)}([function(e,t,r){(function(t){var r=function(e){return e&&e.Math==Math&&e};e.exports=r("object"==typeof globalThis&&globalThis)||r("object"==typeof window&&window)||r("object"==typeof self&&self)||r("object"==typeof t&&t)||Function("return this")()}).call(this,r(59))},function(e,t,r){var n=r(0),i=r(32),o=r(3),s=r(36),u=r(37),a=r(60),c=i("wks"),l=n.Symbol,f=a?l:l&&l.withoutSetter||s;e.exports=function(e){return o(c,e)||(u&&o(l,e)?c[e]=l[e]:c[e]=f("Symbol."+e)),c[e]}},function(e,t,r){var n=r(7);e.exports=function(e){if(!n(e))throw TypeError(String(e)+" is not an object");return e}},function(e,t){var r={}.hasOwnProperty;e.exports=function(e,t){return r.call(e,t)}},function(e,t,r){var n,i;\n/**\n * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.8\n * Copyright (C) 2019 Oliver Nightingale\n * @license MIT\n */!function(){var o,s,u,a,c,l,f,p,h,d,y,v,m,g,x,w,b,S,P,k,O,E,L,T,Q,j,I=function(e){var t=new I.Builder;return t.pipeline.add(I.trimmer,I.stopWordFilter,I.stemmer),t.searchPipeline.add(I.stemmer),e.call(t,t),t.build()};I.version="2.3.8"\n/*!\n * lunr.utils\n * Copyright (C) 2019 Oliver Nightingale\n */,I.utils={},I.utils.warn=(o=this,function(e){o.console&&console.warn&&console.warn(e)}),I.utils.asString=function(e){return null==e?"":e.toString()},I.utils.clone=function(e){if(null==e)return e;for(var t=Object.create(null),r=Object.keys(e),n=0;n<r.length;n++){var i=r[n],o=e[i];if(Array.isArray(o))t[i]=o.slice();else{if("string"!=typeof o&&"number"!=typeof o&&"boolean"!=typeof o)throw new TypeError("clone is not deep and does not support nested objects");t[i]=o}}return t},I.FieldRef=function(e,t,r){this.docRef=e,this.fieldName=t,this._stringValue=r},I.FieldRef.joiner="/",I.FieldRef.fromString=function(e){var t=e.indexOf(I.FieldRef.joiner);if(-1===t)throw"malformed field ref string";var r=e.slice(0,t),n=e.slice(t+1);return new I.FieldRef(n,r,e)},I.FieldRef.prototype.toString=function(){return null==this._stringValue&&(this._stringValue=this.fieldName+I.FieldRef.joiner+this.docRef),this._stringValue}\n/*!\n * lunr.Set\n * Copyright (C) 2019 Oliver Nightingale\n */,I.Set=function(e){if(this.elements=Object.create(null),e){this.length=e.length;for(var t=0;t<this.length;t++)this.elements[e[t]]=!0}else this.length=0},I.Set.complete={intersect:function(e){return e},union:function(e){return e},contains:function(){return!0}},I.Set.empty={intersect:function(){return this},union:function(e){return e},contains:function(){return!1}},I.Set.prototype.contains=function(e){return!!this.elements[e]},I.Set.prototype.intersect=function(e){var t,r,n,i=[];if(e===I.Set.complete)return this;if(e===I.Set.empty)return e;this.length<e.length?(t=this,r=e):(t=e,r=this),n=Object.keys(t.elements);for(var o=0;o<n.length;o++){var s=n[o];s in r.elements&&i.push(s)}return new I.Set(i)},I.Set.prototype.union=function(e){return e===I.Set.complete?I.Set.complete:e===I.Set.empty?this:new I.Set(Object.keys(this.elements).concat(Object.keys(e.elements)))},I.idf=function(e,t){var r=0;for(var n in e)"_index"!=n&&(r+=Object.keys(e[n]).length);var i=(t-r+.5)/(r+.5);return Math.log(1+Math.abs(i))},I.Token=function(e,t){this.str=e||"",this.metadata=t||{}},I.Token.prototype.toString=function(){return this.str},I.Token.prototype.update=function(e){return this.str=e(this.str,this.metadata),this},I.Token.prototype.clone=function(e){return e=e||function(e){return e},new I.Token(e(this.str,this.metadata),this.metadata)}\n/*!\n * lunr.tokenizer\n * Copyright (C) 2019 Oliver Nightingale\n */,I.tokenizer=function(e,t){if(null==e||null==e)return[];if(Array.isArray(e))return e.map((function(e){return new I.Token(I.utils.asString(e).toLowerCase(),I.utils.clone(t))}));for(var r=e.toString().toLowerCase(),n=r.length,i=[],o=0,s=0;o<=n;o++){var u=o-s;if(r.charAt(o).match(I.tokenizer.separator)||o==n){if(u>0){var a=I.utils.clone(t)||{};a.position=[s,u],a.index=i.length,i.push(new I.Token(r.slice(s,o),a))}s=o+1}}return i},I.tokenizer.separator=/[\\s\\-]+/\n/*!\n * lunr.Pipeline\n * Copyright (C) 2019 Oliver Nightingale\n */,I.Pipeline=function(){this._stack=[]},I.Pipeline.registeredFunctions=Object.create(null),I.Pipeline.registerFunction=function(e,t){t in this.registeredFunctions&&I.utils.warn("Overwriting existing registered function: "+t),e.label=t,I.Pipeline.registeredFunctions[e.label]=e},I.Pipeline.warnIfFunctionNotRegistered=function(e){e.label&&e.label in this.registeredFunctions||I.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\\n",e)},I.Pipeline.load=function(e){var t=new I.Pipeline;return e.forEach((function(e){var r=I.Pipeline.registeredFunctions[e];if(!r)throw new Error("Cannot load unregistered function: "+e);t.add(r)})),t},I.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach((function(e){I.Pipeline.warnIfFunctionNotRegistered(e),this._stack.push(e)}),this)},I.Pipeline.prototype.after=function(e,t){I.Pipeline.warnIfFunctionNotRegistered(t);var r=this._stack.indexOf(e);if(-1==r)throw new Error("Cannot find existingFn");r+=1,this._stack.splice(r,0,t)},I.Pipeline.prototype.before=function(e,t){I.Pipeline.warnIfFunctionNotRegistered(t);var r=this._stack.indexOf(e);if(-1==r)throw new Error("Cannot find existingFn");this._stack.splice(r,0,t)},I.Pipeline.prototype.remove=function(e){var t=this._stack.indexOf(e);-1!=t&&this._stack.splice(t,1)},I.Pipeline.prototype.run=function(e){for(var t=this._stack.length,r=0;r<t;r++){for(var n=this._stack[r],i=[],o=0;o<e.length;o++){var s=n(e[o],o,e);if(null!=s&&""!==s)if(Array.isArray(s))for(var u=0;u<s.length;u++)i.push(s[u]);else i.push(s)}e=i}return e},I.Pipeline.prototype.runString=function(e,t){var r=new I.Token(e,t);return this.run([r]).map((function(e){return e.toString()}))},I.Pipeline.prototype.reset=function(){this._stack=[]},I.Pipeline.prototype.toJSON=function(){return this._stack.map((function(e){return I.Pipeline.warnIfFunctionNotRegistered(e),e.label}))}\n/*!\n * lunr.Vector\n * Copyright (C) 2019 Oliver Nightingale\n */,I.Vector=function(e){this._magnitude=0,this.elements=e||[]},I.Vector.prototype.positionForIndex=function(e){if(0==this.elements.length)return 0;for(var t=0,r=this.elements.length/2,n=r-t,i=Math.floor(n/2),o=this.elements[2*i];n>1&&(o<e&&(t=i),o>e&&(r=i),o!=e);)n=r-t,i=t+Math.floor(n/2),o=this.elements[2*i];return o==e||o>e?2*i:o<e?2*(i+1):void 0},I.Vector.prototype.insert=function(e,t){this.upsert(e,t,(function(){throw"duplicate index"}))},I.Vector.prototype.upsert=function(e,t,r){this._magnitude=0;var n=this.positionForIndex(e);this.elements[n]==e?this.elements[n+1]=r(this.elements[n+1],t):this.elements.splice(n,0,e,t)},I.Vector.prototype.magnitude=function(){if(this._magnitude)return this._magnitude;for(var e=0,t=this.elements.length,r=1;r<t;r+=2){var n=this.elements[r];e+=n*n}return this._magnitude=Math.sqrt(e)},I.Vector.prototype.dot=function(e){for(var t=0,r=this.elements,n=e.elements,i=r.length,o=n.length,s=0,u=0,a=0,c=0;a<i&&c<o;)(s=r[a])<(u=n[c])?a+=2:s>u?c+=2:s==u&&(t+=r[a+1]*n[c+1],a+=2,c+=2);return t},I.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},I.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),t=1,r=0;t<this.elements.length;t+=2,r++)e[r]=this.elements[t];return e},I.Vector.prototype.toJSON=function(){return this.elements}\n/*!\n * lunr.stemmer\n * Copyright (C) 2019 Oliver Nightingale\n * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt\n */,I.stemmer=(s={ational:"ate",tional:"tion",enci:"ence",anci:"ance",izer:"ize",bli:"ble",alli:"al",entli:"ent",eli:"e",ousli:"ous",ization:"ize",ation:"ate",ator:"ate",alism:"al",iveness:"ive",fulness:"ful",ousness:"ous",aliti:"al",iviti:"ive",biliti:"ble",logi:"log"},u={icate:"ic",ative:"",alize:"al",iciti:"ic",ical:"ic",ful:"",ness:""},a="[aeiouy]",c="[^aeiou][^aeiouy]*",l=new RegExp("^([^aeiou][^aeiouy]*)?[aeiouy][aeiou]*[^aeiou][^aeiouy]*"),f=new RegExp("^([^aeiou][^aeiouy]*)?[aeiouy][aeiou]*[^aeiou][^aeiouy]*[aeiouy][aeiou]*[^aeiou][^aeiouy]*"),p=new RegExp("^([^aeiou][^aeiouy]*)?[aeiouy][aeiou]*[^aeiou][^aeiouy]*([aeiouy][aeiou]*)?$"),h=new RegExp("^([^aeiou][^aeiouy]*)?[aeiouy]"),d=/^(.+?)(ss|i)es$/,y=/^(.+?)([^s])s$/,v=/^(.+?)eed$/,m=/^(.+?)(ed|ing)$/,g=/.$/,x=/(at|bl|iz)$/,w=new RegExp("([^aeiouylsz])\\\\1$"),b=new RegExp("^"+c+a+"[^aeiouwxy]$"),S=/^(.+?[^aeiou])y$/,P=/^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/,k=/^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/,O=/^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/,E=/^(.+?)(s|t)(ion)$/,L=/^(.+?)e$/,T=/ll$/,Q=new RegExp("^"+c+a+"[^aeiouwxy]$"),j=function(e){var t,r,n,i,o,a,c;if(e.length<3)return e;if("y"==(n=e.substr(0,1))&&(e=n.toUpperCase()+e.substr(1)),o=y,(i=d).test(e)?e=e.replace(i,"$1$2"):o.test(e)&&(e=e.replace(o,"$1$2")),o=m,(i=v).test(e)){var j=i.exec(e);(i=l).test(j[1])&&(i=g,e=e.replace(i,""))}else o.test(e)&&(t=(j=o.exec(e))[1],(o=h).test(t)&&(a=w,c=b,(o=x).test(e=t)?e+="e":a.test(e)?(i=g,e=e.replace(i,"")):c.test(e)&&(e+="e")));return(i=S).test(e)&&(e=(t=(j=i.exec(e))[1])+"i"),(i=P).test(e)&&(t=(j=i.exec(e))[1],r=j[2],(i=l).test(t)&&(e=t+s[r])),(i=k).test(e)&&(t=(j=i.exec(e))[1],r=j[2],(i=l).test(t)&&(e=t+u[r])),o=E,(i=O).test(e)?(t=(j=i.exec(e))[1],(i=f).test(t)&&(e=t)):o.test(e)&&(t=(j=o.exec(e))[1]+j[2],(o=f).test(t)&&(e=t)),(i=L).test(e)&&(t=(j=i.exec(e))[1],o=p,a=Q,((i=f).test(t)||o.test(t)&&!a.test(t))&&(e=t)),o=f,(i=T).test(e)&&o.test(e)&&(i=g,e=e.replace(i,"")),"y"==n&&(e=n.toLowerCase()+e.substr(1)),e},function(e){return e.update(j)}),I.Pipeline.registerFunction(I.stemmer,"stemmer")\n/*!\n * lunr.stopWordFilter\n * Copyright (C) 2019 Oliver Nightingale\n */,I.generateStopWordFilter=function(e){var t=e.reduce((function(e,t){return e[t]=t,e}),{});return function(e){if(e&&t[e.toString()]!==e.toString())return e}},I.stopWordFilter=I.generateStopWordFilter(["a","able","about","across","after","all","almost","also","am","among","an","and","any","are","as","at","be","because","been","but","by","can","cannot","could","dear","did","do","does","either","else","ever","every","for","from","get","got","had","has","have","he","her","hers","him","his","how","however","i","if","in","into","is","it","its","just","least","let","like","likely","may","me","might","most","must","my","neither","no","nor","not","of","off","often","on","only","or","other","our","own","rather","said","say","says","she","should","since","so","some","than","that","the","their","them","then","there","these","they","this","tis","to","too","twas","us","wants","was","we","were","what","when","where","which","while","who","whom","why","will","with","would","yet","you","your"]),I.Pipeline.registerFunction(I.stopWordFilter,"stopWordFilter")\n/*!\n * lunr.trimmer\n * Copyright (C) 2019 Oliver Nightingale\n */,I.trimmer=function(e){return e.update((function(e){return e.replace(/^\\W+/,"").replace(/\\W+$/,"")}))},I.Pipeline.registerFunction(I.trimmer,"trimmer")\n/*!\n * lunr.TokenSet\n * Copyright (C) 2019 Oliver Nightingale\n */,I.TokenSet=function(){this.final=!1,this.edges={},this.id=I.TokenSet._nextId,I.TokenSet._nextId+=1},I.TokenSet._nextId=1,I.TokenSet.fromArray=function(e){for(var t=new I.TokenSet.Builder,r=0,n=e.length;r<n;r++)t.insert(e[r]);return t.finish(),t.root},I.TokenSet.fromClause=function(e){return"editDistance"in e?I.TokenSet.fromFuzzyString(e.term,e.editDistance):I.TokenSet.fromString(e.term)},I.TokenSet.fromFuzzyString=function(e,t){for(var r=new I.TokenSet,n=[{node:r,editsRemaining:t,str:e}];n.length;){var i=n.pop();if(i.str.length>0){var o,s=i.str.charAt(0);s in i.node.edges?o=i.node.edges[s]:(o=new I.TokenSet,i.node.edges[s]=o),1==i.str.length&&(o.final=!0),n.push({node:o,editsRemaining:i.editsRemaining,str:i.str.slice(1)})}if(0!=i.editsRemaining){if("*"in i.node.edges)var u=i.node.edges["*"];else{u=new I.TokenSet;i.node.edges["*"]=u}if(0==i.str.length&&(u.final=!0),n.push({node:u,editsRemaining:i.editsRemaining-1,str:i.str}),i.str.length>1&&n.push({node:i.node,editsRemaining:i.editsRemaining-1,str:i.str.slice(1)}),1==i.str.length&&(i.node.final=!0),i.str.length>=1){if("*"in i.node.edges)var a=i.node.edges["*"];else{a=new I.TokenSet;i.node.edges["*"]=a}1==i.str.length&&(a.final=!0),n.push({node:a,editsRemaining:i.editsRemaining-1,str:i.str.slice(1)})}if(i.str.length>1){var c,l=i.str.charAt(0),f=i.str.charAt(1);f in i.node.edges?c=i.node.edges[f]:(c=new I.TokenSet,i.node.edges[f]=c),1==i.str.length&&(c.final=!0),n.push({node:c,editsRemaining:i.editsRemaining-1,str:l+i.str.slice(2)})}}}return r},I.TokenSet.fromString=function(e){for(var t=new I.TokenSet,r=t,n=0,i=e.length;n<i;n++){var o=e[n],s=n==i-1;if("*"==o)t.edges[o]=t,t.final=s;else{var u=new I.TokenSet;u.final=s,t.edges[o]=u,t=u}}return r},I.TokenSet.prototype.toArray=function(){for(var e=[],t=[{prefix:"",node:this}];t.length;){var r=t.pop(),n=Object.keys(r.node.edges),i=n.length;r.node.final&&(r.prefix.charAt(0),e.push(r.prefix));for(var o=0;o<i;o++){var s=n[o];t.push({prefix:r.prefix.concat(s),node:r.node.edges[s]})}}return e},I.TokenSet.prototype.toString=function(){if(this._str)return this._str;for(var e=this.final?"1":"0",t=Object.keys(this.edges).sort(),r=t.length,n=0;n<r;n++){var i=t[n];e=e+i+this.edges[i].id}return e},I.TokenSet.prototype.intersect=function(e){for(var t=new I.TokenSet,r=void 0,n=[{qNode:e,output:t,node:this}];n.length;){r=n.pop();for(var i=Object.keys(r.qNode.edges),o=i.length,s=Object.keys(r.node.edges),u=s.length,a=0;a<o;a++)for(var c=i[a],l=0;l<u;l++){var f=s[l];if(f==c||"*"==c){var p=r.node.edges[f],h=r.qNode.edges[c],d=p.final&&h.final,y=void 0;f in r.output.edges?(y=r.output.edges[f]).final=y.final||d:((y=new I.TokenSet).final=d,r.output.edges[f]=y),n.push({qNode:h,output:y,node:p})}}}return t},I.TokenSet.Builder=function(){this.previousWord="",this.root=new I.TokenSet,this.uncheckedNodes=[],this.minimizedNodes={}},I.TokenSet.Builder.prototype.insert=function(e){var t,r=0;if(e<this.previousWord)throw new Error("Out of order word insertion");for(var n=0;n<e.length&&n<this.previousWord.length&&e[n]==this.previousWord[n];n++)r++;this.minimize(r),t=0==this.uncheckedNodes.length?this.root:this.uncheckedNodes[this.uncheckedNodes.length-1].child;for(n=r;n<e.length;n++){var i=new I.TokenSet,o=e[n];t.edges[o]=i,this.uncheckedNodes.push({parent:t,char:o,child:i}),t=i}t.final=!0,this.previousWord=e},I.TokenSet.Builder.prototype.finish=function(){this.minimize(0)},I.TokenSet.Builder.prototype.minimize=function(e){for(var t=this.uncheckedNodes.length-1;t>=e;t--){var r=this.uncheckedNodes[t],n=r.child.toString();n in this.minimizedNodes?r.parent.edges[r.char]=this.minimizedNodes[n]:(r.child._str=n,this.minimizedNodes[n]=r.child),this.uncheckedNodes.pop()}}\n/*!\n * lunr.Index\n * Copyright (C) 2019 Oliver Nightingale\n */,I.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},I.Index.prototype.search=function(e){return this.query((function(t){new I.QueryParser(e,t).parse()}))},I.Index.prototype.query=function(e){for(var t=new I.Query(this.fields),r=Object.create(null),n=Object.create(null),i=Object.create(null),o=Object.create(null),s=Object.create(null),u=0;u<this.fields.length;u++)n[this.fields[u]]=new I.Vector;e.call(t,t);for(u=0;u<t.clauses.length;u++){var a=t.clauses[u],c=null,l=I.Set.complete;c=a.usePipeline?this.pipeline.runString(a.term,{fields:a.fields}):[a.term];for(var f=0;f<c.length;f++){var p=c[f];a.term=p;var h=I.TokenSet.fromClause(a),d=this.tokenSet.intersect(h).toArray();if(0===d.length&&a.presence===I.Query.presence.REQUIRED){for(var y=0;y<a.fields.length;y++){o[R=a.fields[y]]=I.Set.empty}break}for(var v=0;v<d.length;v++){var m=d[v],g=this.invertedIndex[m],x=g._index;for(y=0;y<a.fields.length;y++){var w=g[R=a.fields[y]],b=Object.keys(w),S=m+"/"+R,P=new I.Set(b);if(a.presence==I.Query.presence.REQUIRED&&(l=l.union(P),void 0===o[R]&&(o[R]=I.Set.complete)),a.presence!=I.Query.presence.PROHIBITED){if(n[R].upsert(x,a.boost,(function(e,t){return e+t})),!i[S]){for(var k=0;k<b.length;k++){var O,E=b[k],L=new I.FieldRef(E,R),T=w[E];void 0===(O=r[L])?r[L]=new I.MatchData(m,R,T):O.add(m,R,T)}i[S]=!0}}else void 0===s[R]&&(s[R]=I.Set.empty),s[R]=s[R].union(P)}}}if(a.presence===I.Query.presence.REQUIRED)for(y=0;y<a.fields.length;y++){o[R=a.fields[y]]=o[R].intersect(l)}}var Q=I.Set.complete,j=I.Set.empty;for(u=0;u<this.fields.length;u++){var R;o[R=this.fields[u]]&&(Q=Q.intersect(o[R])),s[R]&&(j=j.union(s[R]))}var _=Object.keys(r),F=[],C=Object.create(null);if(t.isNegated()){_=Object.keys(this.fieldVectors);for(u=0;u<_.length;u++){L=_[u];var N=I.FieldRef.fromString(L);r[L]=new I.MatchData}}for(u=0;u<_.length;u++){var A=(N=I.FieldRef.fromString(_[u])).docRef;if(Q.contains(A)&&!j.contains(A)){var D,M=this.fieldVectors[N],B=n[N.fieldName].similarity(M);if(void 0!==(D=C[A]))D.score+=B,D.matchData.combine(r[N]);else{var V={ref:A,score:B,matchData:r[N]};C[A]=V,F.push(V)}}}return F.sort((function(e,t){return t.score-e.score}))},I.Index.prototype.toJSON=function(){var e=Object.keys(this.invertedIndex).sort().map((function(e){return[e,this.invertedIndex[e]]}),this),t=Object.keys(this.fieldVectors).map((function(e){return[e,this.fieldVectors[e].toJSON()]}),this);return{version:I.version,fields:this.fields,fieldVectors:t,invertedIndex:e,pipeline:this.pipeline.toJSON()}},I.Index.load=function(e){var t={},r={},n=e.fieldVectors,i=Object.create(null),o=e.invertedIndex,s=new I.TokenSet.Builder,u=I.Pipeline.load(e.pipeline);e.version!=I.version&&I.utils.warn("Version mismatch when loading serialised index. Current version of lunr \'"+I.version+"\' does not match serialized index \'"+e.version+"\'");for(var a=0;a<n.length;a++){var c=(f=n[a])[0],l=f[1];r[c]=new I.Vector(l)}for(a=0;a<o.length;a++){var f,p=(f=o[a])[0],h=f[1];s.insert(p),i[p]=h}return s.finish(),t.fields=e.fields,t.fieldVectors=r,t.invertedIndex=i,t.tokenSet=s.root,t.pipeline=u,new I.Index(t)}\n/*!\n * lunr.Builder\n * Copyright (C) 2019 Oliver Nightingale\n */,I.Builder=function(){this._ref="id",this._fields=Object.create(null),this._documents=Object.create(null),this.invertedIndex=Object.create(null),this.fieldTermFrequencies={},this.fieldLengths={},this.tokenizer=I.tokenizer,this.pipeline=new I.Pipeline,this.searchPipeline=new I.Pipeline,this.documentCount=0,this._b=.75,this._k1=1.2,this.termIndex=0,this.metadataWhitelist=[]},I.Builder.prototype.ref=function(e){this._ref=e},I.Builder.prototype.field=function(e,t){if(/\\//.test(e))throw new RangeError("Field \'"+e+"\' contains illegal character \'/\'");this._fields[e]=t||{}},I.Builder.prototype.b=function(e){this._b=e<0?0:e>1?1:e},I.Builder.prototype.k1=function(e){this._k1=e},I.Builder.prototype.add=function(e,t){var r=e[this._ref],n=Object.keys(this._fields);this._documents[r]=t||{},this.documentCount+=1;for(var i=0;i<n.length;i++){var o=n[i],s=this._fields[o].extractor,u=s?s(e):e[o],a=this.tokenizer(u,{fields:[o]}),c=this.pipeline.run(a),l=new I.FieldRef(r,o),f=Object.create(null);this.fieldTermFrequencies[l]=f,this.fieldLengths[l]=0,this.fieldLengths[l]+=c.length;for(var p=0;p<c.length;p++){var h=c[p];if(null==f[h]&&(f[h]=0),f[h]+=1,null==this.invertedIndex[h]){var d=Object.create(null);d._index=this.termIndex,this.termIndex+=1;for(var y=0;y<n.length;y++)d[n[y]]=Object.create(null);this.invertedIndex[h]=d}null==this.invertedIndex[h][o][r]&&(this.invertedIndex[h][o][r]=Object.create(null));for(var v=0;v<this.metadataWhitelist.length;v++){var m=this.metadataWhitelist[v],g=h.metadata[m];null==this.invertedIndex[h][o][r][m]&&(this.invertedIndex[h][o][r][m]=[]),this.invertedIndex[h][o][r][m].push(g)}}}},I.Builder.prototype.calculateAverageFieldLengths=function(){for(var e=Object.keys(this.fieldLengths),t=e.length,r={},n={},i=0;i<t;i++){var o=I.FieldRef.fromString(e[i]),s=o.fieldName;n[s]||(n[s]=0),n[s]+=1,r[s]||(r[s]=0),r[s]+=this.fieldLengths[o]}var u=Object.keys(this._fields);for(i=0;i<u.length;i++){var a=u[i];r[a]=r[a]/n[a]}this.averageFieldLength=r},I.Builder.prototype.createFieldVectors=function(){for(var e={},t=Object.keys(this.fieldTermFrequencies),r=t.length,n=Object.create(null),i=0;i<r;i++){for(var o=I.FieldRef.fromString(t[i]),s=o.fieldName,u=this.fieldLengths[o],a=new I.Vector,c=this.fieldTermFrequencies[o],l=Object.keys(c),f=l.length,p=this._fields[s].boost||1,h=this._documents[o.docRef].boost||1,d=0;d<f;d++){var y,v,m,g=l[d],x=c[g],w=this.invertedIndex[g]._index;void 0===n[g]?(y=I.idf(this.invertedIndex[g],this.documentCount),n[g]=y):y=n[g],v=y*((this._k1+1)*x)/(this._k1*(1-this._b+this._b*(u/this.averageFieldLength[s]))+x),v*=p,v*=h,m=Math.round(1e3*v)/1e3,a.insert(w,m)}e[o]=a}this.fieldVectors=e},I.Builder.prototype.createTokenSet=function(){this.tokenSet=I.TokenSet.fromArray(Object.keys(this.invertedIndex).sort())},I.Builder.prototype.build=function(){return this.calculateAverageFieldLengths(),this.createFieldVectors(),this.createTokenSet(),new I.Index({invertedIndex:this.invertedIndex,fieldVectors:this.fieldVectors,tokenSet:this.tokenSet,fields:Object.keys(this._fields),pipeline:this.searchPipeline})},I.Builder.prototype.use=function(e){var t=Array.prototype.slice.call(arguments,1);t.unshift(this),e.apply(this,t)},I.MatchData=function(e,t,r){for(var n=Object.create(null),i=Object.keys(r||{}),o=0;o<i.length;o++){var s=i[o];n[s]=r[s].slice()}this.metadata=Object.create(null),void 0!==e&&(this.metadata[e]=Object.create(null),this.metadata[e][t]=n)},I.MatchData.prototype.combine=function(e){for(var t=Object.keys(e.metadata),r=0;r<t.length;r++){var n=t[r],i=Object.keys(e.metadata[n]);null==this.metadata[n]&&(this.metadata[n]=Object.create(null));for(var o=0;o<i.length;o++){var s=i[o],u=Object.keys(e.metadata[n][s]);null==this.metadata[n][s]&&(this.metadata[n][s]=Object.create(null));for(var a=0;a<u.length;a++){var c=u[a];null==this.metadata[n][s][c]?this.metadata[n][s][c]=e.metadata[n][s][c]:this.metadata[n][s][c]=this.metadata[n][s][c].concat(e.metadata[n][s][c])}}}},I.MatchData.prototype.add=function(e,t,r){if(!(e in this.metadata))return this.metadata[e]=Object.create(null),void(this.metadata[e][t]=r);if(t in this.metadata[e])for(var n=Object.keys(r),i=0;i<n.length;i++){var o=n[i];o in this.metadata[e][t]?this.metadata[e][t][o]=this.metadata[e][t][o].concat(r[o]):this.metadata[e][t][o]=r[o]}else this.metadata[e][t]=r},I.Query=function(e){this.clauses=[],this.allFields=e},I.Query.wildcard=new String("*"),I.Query.wildcard.NONE=0,I.Query.wildcard.LEADING=1,I.Query.wildcard.TRAILING=2,I.Query.presence={OPTIONAL:1,REQUIRED:2,PROHIBITED:3},I.Query.prototype.clause=function(e){return"fields"in e||(e.fields=this.allFields),"boost"in e||(e.boost=1),"usePipeline"in e||(e.usePipeline=!0),"wildcard"in e||(e.wildcard=I.Query.wildcard.NONE),e.wildcard&I.Query.wildcard.LEADING&&e.term.charAt(0)!=I.Query.wildcard&&(e.term="*"+e.term),e.wildcard&I.Query.wildcard.TRAILING&&e.term.slice(-1)!=I.Query.wildcard&&(e.term=e.term+"*"),"presence"in e||(e.presence=I.Query.presence.OPTIONAL),this.clauses.push(e),this},I.Query.prototype.isNegated=function(){for(var e=0;e<this.clauses.length;e++)if(this.clauses[e].presence!=I.Query.presence.PROHIBITED)return!1;return!0},I.Query.prototype.term=function(e,t){if(Array.isArray(e))return e.forEach((function(e){this.term(e,I.utils.clone(t))}),this),this;var r=t||{};return r.term=e.toString(),this.clause(r),this},I.QueryParseError=function(e,t,r){this.name="QueryParseError",this.message=e,this.start=t,this.end=r},I.QueryParseError.prototype=new Error,I.QueryLexer=function(e){this.lexemes=[],this.str=e,this.length=e.length,this.pos=0,this.start=0,this.escapeCharPositions=[]},I.QueryLexer.prototype.run=function(){for(var e=I.QueryLexer.lexText;e;)e=e(this)},I.QueryLexer.prototype.sliceString=function(){for(var e=[],t=this.start,r=this.pos,n=0;n<this.escapeCharPositions.length;n++)r=this.escapeCharPositions[n],e.push(this.str.slice(t,r)),t=r+1;return e.push(this.str.slice(t,this.pos)),this.escapeCharPositions.length=0,e.join("")},I.QueryLexer.prototype.emit=function(e){this.lexemes.push({type:e,str:this.sliceString(),start:this.start,end:this.pos}),this.start=this.pos},I.QueryLexer.prototype.escapeCharacter=function(){this.escapeCharPositions.push(this.pos-1),this.pos+=1},I.QueryLexer.prototype.next=function(){if(this.pos>=this.length)return I.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},I.QueryLexer.prototype.width=function(){return this.pos-this.start},I.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},I.QueryLexer.prototype.backup=function(){this.pos-=1},I.QueryLexer.prototype.acceptDigitRun=function(){var e,t;do{t=(e=this.next()).charCodeAt(0)}while(t>47&&t<58);e!=I.QueryLexer.EOS&&this.backup()},I.QueryLexer.prototype.more=function(){return this.pos<this.length},I.QueryLexer.EOS="EOS",I.QueryLexer.FIELD="FIELD",I.QueryLexer.TERM="TERM",I.QueryLexer.EDIT_DISTANCE="EDIT_DISTANCE",I.QueryLexer.BOOST="BOOST",I.QueryLexer.PRESENCE="PRESENCE",I.QueryLexer.lexField=function(e){return e.backup(),e.emit(I.QueryLexer.FIELD),e.ignore(),I.QueryLexer.lexText},I.QueryLexer.lexTerm=function(e){if(e.width()>1&&(e.backup(),e.emit(I.QueryLexer.TERM)),e.ignore(),e.more())return I.QueryLexer.lexText},I.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(I.QueryLexer.EDIT_DISTANCE),I.QueryLexer.lexText},I.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(I.QueryLexer.BOOST),I.QueryLexer.lexText},I.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(I.QueryLexer.TERM)},I.QueryLexer.termSeparator=I.tokenizer.separator,I.QueryLexer.lexText=function(e){for(;;){var t=e.next();if(t==I.QueryLexer.EOS)return I.QueryLexer.lexEOS;if(92!=t.charCodeAt(0)){if(":"==t)return I.QueryLexer.lexField;if("~"==t)return e.backup(),e.width()>0&&e.emit(I.QueryLexer.TERM),I.QueryLexer.lexEditDistance;if("^"==t)return e.backup(),e.width()>0&&e.emit(I.QueryLexer.TERM),I.QueryLexer.lexBoost;if("+"==t&&1===e.width())return e.emit(I.QueryLexer.PRESENCE),I.QueryLexer.lexText;if("-"==t&&1===e.width())return e.emit(I.QueryLexer.PRESENCE),I.QueryLexer.lexText;if(t.match(I.QueryLexer.termSeparator))return I.QueryLexer.lexTerm}else e.escapeCharacter()}},I.QueryParser=function(e,t){this.lexer=new I.QueryLexer(e),this.query=t,this.currentClause={},this.lexemeIdx=0},I.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=I.QueryParser.parseClause;e;)e=e(this);return this.query},I.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},I.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},I.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},I.QueryParser.parseClause=function(e){var t=e.peekLexeme();if(null!=t)switch(t.type){case I.QueryLexer.PRESENCE:return I.QueryParser.parsePresence;case I.QueryLexer.FIELD:return I.QueryParser.parseField;case I.QueryLexer.TERM:return I.QueryParser.parseTerm;default:var r="expected either a field or a term, found "+t.type;throw t.str.length>=1&&(r+=" with value \'"+t.str+"\'"),new I.QueryParseError(r,t.start,t.end)}},I.QueryParser.parsePresence=function(e){var t=e.consumeLexeme();if(null!=t){switch(t.str){case"-":e.currentClause.presence=I.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=I.Query.presence.REQUIRED;break;default:var r="unrecognised presence operator\'"+t.str+"\'";throw new I.QueryParseError(r,t.start,t.end)}var n=e.peekLexeme();if(null==n){r="expecting term or field, found nothing";throw new I.QueryParseError(r,t.start,t.end)}switch(n.type){case I.QueryLexer.FIELD:return I.QueryParser.parseField;case I.QueryLexer.TERM:return I.QueryParser.parseTerm;default:r="expecting term or field, found \'"+n.type+"\'";throw new I.QueryParseError(r,n.start,n.end)}}},I.QueryParser.parseField=function(e){var t=e.consumeLexeme();if(null!=t){if(-1==e.query.allFields.indexOf(t.str)){var r=e.query.allFields.map((function(e){return"\'"+e+"\'"})).join(", "),n="unrecognised field \'"+t.str+"\', possible fields: "+r;throw new I.QueryParseError(n,t.start,t.end)}e.currentClause.fields=[t.str];var i=e.peekLexeme();if(null==i){n="expecting term, found nothing";throw new I.QueryParseError(n,t.start,t.end)}switch(i.type){case I.QueryLexer.TERM:return I.QueryParser.parseTerm;default:n="expecting term, found \'"+i.type+"\'";throw new I.QueryParseError(n,i.start,i.end)}}},I.QueryParser.parseTerm=function(e){var t=e.consumeLexeme();if(null!=t){e.currentClause.term=t.str.toLowerCase(),-1!=t.str.indexOf("*")&&(e.currentClause.usePipeline=!1);var r=e.peekLexeme();if(null!=r)switch(r.type){case I.QueryLexer.TERM:return e.nextClause(),I.QueryParser.parseTerm;case I.QueryLexer.FIELD:return e.nextClause(),I.QueryParser.parseField;case I.QueryLexer.EDIT_DISTANCE:return I.QueryParser.parseEditDistance;case I.QueryLexer.BOOST:return I.QueryParser.parseBoost;case I.QueryLexer.PRESENCE:return e.nextClause(),I.QueryParser.parsePresence;default:var n="Unexpected lexeme type \'"+r.type+"\'";throw new I.QueryParseError(n,r.start,r.end)}else e.nextClause()}},I.QueryParser.parseEditDistance=function(e){var t=e.consumeLexeme();if(null!=t){var r=parseInt(t.str,10);if(isNaN(r)){var n="edit distance must be numeric";throw new I.QueryParseError(n,t.start,t.end)}e.currentClause.editDistance=r;var i=e.peekLexeme();if(null!=i)switch(i.type){case I.QueryLexer.TERM:return e.nextClause(),I.QueryParser.parseTerm;case I.QueryLexer.FIELD:return e.nextClause(),I.QueryParser.parseField;case I.QueryLexer.EDIT_DISTANCE:return I.QueryParser.parseEditDistance;case I.QueryLexer.BOOST:return I.QueryParser.parseBoost;case I.QueryLexer.PRESENCE:return e.nextClause(),I.QueryParser.parsePresence;default:n="Unexpected lexeme type \'"+i.type+"\'";throw new I.QueryParseError(n,i.start,i.end)}else e.nextClause()}},I.QueryParser.parseBoost=function(e){var t=e.consumeLexeme();if(null!=t){var r=parseInt(t.str,10);if(isNaN(r)){var n="boost must be numeric";throw new I.QueryParseError(n,t.start,t.end)}e.currentClause.boost=r;var i=e.peekLexeme();if(null!=i)switch(i.type){case I.QueryLexer.TERM:return e.nextClause(),I.QueryParser.parseTerm;case I.QueryLexer.FIELD:return e.nextClause(),I.QueryParser.parseField;case I.QueryLexer.EDIT_DISTANCE:return I.QueryParser.parseEditDistance;case I.QueryLexer.BOOST:return I.QueryParser.parseBoost;case I.QueryLexer.PRESENCE:return e.nextClause(),I.QueryParser.parsePresence;default:n="Unexpected lexeme type \'"+i.type+"\'";throw new I.QueryParseError(n,i.start,i.end)}else e.nextClause()}},void 0===(i="function"==typeof(n=function(){return I})?n.call(t,r,t,e):n)||(e.exports=i)}()},function(e,t){e.exports=function(e){try{return!!e()}catch(e){return!0}}},function(e,t,r){var n=r(8),i=r(9),o=r(22);e.exports=n?function(e,t,r){return i.f(e,t,o(1,r))}:function(e,t,r){return e[t]=r,e}},function(e,t){e.exports=function(e){return"object"==typeof e?null!==e:"function"==typeof e}},function(e,t,r){var n=r(5);e.exports=!n((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]}))},function(e,t,r){var n=r(8),i=r(34),o=r(2),s=r(35),u=Object.defineProperty;t.f=n?u:function(e,t,r){if(o(e),t=s(t,!0),o(r),i)try{return u(e,t,r)}catch(e){}if("get"in r||"set"in r)throw TypeError("Accessors not supported");return"value"in r&&(e[t]=r.value),e}},function(e,t,r){var n=r(0),i=r(6),o=r(3),s=r(20),u=r(23),a=r(16),c=a.get,l=a.enforce,f=String(String).split("String");(e.exports=function(e,t,r,u){var a=!!u&&!!u.unsafe,c=!!u&&!!u.enumerable,p=!!u&&!!u.noTargetGet;"function"==typeof r&&("string"!=typeof t||o(r,"name")||i(r,"name",t),l(r).source=f.join("string"==typeof t?t:"")),e!==n?(a?!p&&e[t]&&(c=!0):delete e[t],c?e[t]=r:i(e,t,r)):c?e[t]=r:s(t,r)})(Function.prototype,"toString",(function(){return"function"==typeof this&&c(this).source||u(this)}))},function(e,t,r){var n=r(40),i=r(0),o=function(e){return"function"==typeof e?e:void 0};e.exports=function(e,t){return arguments.length<2?o(n[e])||o(i[e]):n[e]&&n[e][t]||i[e]&&i[e][t]}},function(e,t){e.exports=!1},function(e,t){var r={}.toString;e.exports=function(e){return r.call(e).slice(8,-1)}},function(e,t){e.exports={}},function(e,t){e.exports=function(e){if("function"!=typeof e)throw TypeError(String(e)+" is not a function");return e}},function(e,t,r){var n,i,o,s=r(61),u=r(0),a=r(7),c=r(6),l=r(3),f=r(24),p=r(25),h=u.WeakMap;if(s){var d=new h,y=d.get,v=d.has,m=d.set;n=function(e,t){return m.call(d,e,t),t},i=function(e){return y.call(d,e)||{}},o=function(e){return v.call(d,e)}}else{var g=f("state");p[g]=!0,n=function(e,t){return c(e,g,t),t},i=function(e){return l(e,g)?e[g]:{}},o=function(e){return l(e,g)}}e.exports={set:n,get:i,has:o,enforce:function(e){return o(e)?i(e):n(e,{})},getterFor:function(e){return function(t){var r;if(!a(t)||(r=i(t)).type!==e)throw TypeError("Incompatible receiver, "+e+" required");return r}}}},function(e,t,r){var n=r(0),i=r(28).f,o=r(6),s=r(10),u=r(20),a=r(67),c=r(43);e.exports=function(e,t){var r,l,f,p,h,d=e.target,y=e.global,v=e.stat;if(r=y?n:v?n[d]||u(d,{}):(n[d]||{}).prototype)for(l in t){if(p=t[l],f=e.noTargetGet?(h=i(r,l))&&h.value:r[l],!c(y?l:d+(v?".":"#")+l,e.forced)&&void 0!==f){if(typeof p==typeof f)continue;a(p,f)}(e.sham||f&&f.sham)&&o(p,"sham",!0),s(r,l,p,e)}}},function(e,t,r){var n=r(66),i=r(27);e.exports=function(e){return n(i(e))}},function(e,t,r){var n={};n[r(1)("toStringTag")]="z",e.exports="[object z]"===String(n)},function(e,t,r){var n=r(0),i=r(6);e.exports=function(e,t){try{i(n,e,t)}catch(r){n[e]=t}return t}},function(e,t,r){var n=r(0),i=r(7),o=n.document,s=i(o)&&i(o.createElement);e.exports=function(e){return s?o.createElement(e):{}}},function(e,t){e.exports=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}}},function(e,t,r){var n=r(33),i=Function.toString;"function"!=typeof n.inspectSource&&(n.inspectSource=function(e){return i.call(e)}),e.exports=n.inspectSource},function(e,t,r){var n=r(32),i=r(36),o=n("keys");e.exports=function(e){return o[e]||(o[e]=i(e))}},function(e,t){e.exports={}},function(e,t){var r=Math.ceil,n=Math.floor;e.exports=function(e){return isNaN(e=+e)?0:(e>0?n:r)(e)}},function(e,t){e.exports=function(e){if(null==e)throw TypeError("Can\'t call method on "+e);return e}},function(e,t,r){var n=r(8),i=r(65),o=r(22),s=r(18),u=r(35),a=r(3),c=r(34),l=Object.getOwnPropertyDescriptor;t.f=n?l:function(e,t){if(e=s(e),t=u(t,!0),c)try{return l(e,t)}catch(e){}if(a(e,t))return o(!i.f.call(e,t),e[t])}},function(e,t){e.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},function(e,t,r){var n=r(9).f,i=r(3),o=r(1)("toStringTag");e.exports=function(e,t,r){e&&!i(e=r?e:e.prototype,o)&&n(e,o,{configurable:!0,value:t})}},function(e,t,r){"use strict";var n=r(15),i=function(e){var t,r;this.promise=new e((function(e,n){if(void 0!==t||void 0!==r)throw TypeError("Bad Promise constructor");t=e,r=n})),this.resolve=n(t),this.reject=n(r)};e.exports.f=function(e){return new i(e)}},function(e,t,r){var n=r(12),i=r(33);(e.exports=function(e,t){return i[e]||(i[e]=void 0!==t?t:{})})("versions",[]).push({version:"3.6.4",mode:n?"pure":"global",copyright:"© 2020 Denis Pushkarev (zloirock.ru)"})},function(e,t,r){var n=r(0),i=r(20),o=n["__core-js_shared__"]||i("__core-js_shared__",{});e.exports=o},function(e,t,r){var n=r(8),i=r(5),o=r(21);e.exports=!n&&!i((function(){return 7!=Object.defineProperty(o("div"),"a",{get:function(){return 7}}).a}))},function(e,t,r){var n=r(7);e.exports=function(e,t){if(!n(e))return e;var r,i;if(t&&"function"==typeof(r=e.toString)&&!n(i=r.call(e)))return i;if("function"==typeof(r=e.valueOf)&&!n(i=r.call(e)))return i;if(!t&&"function"==typeof(r=e.toString)&&!n(i=r.call(e)))return i;throw TypeError("Can\'t convert object to primitive value")}},function(e,t){var r=0,n=Math.random();e.exports=function(e){return"Symbol("+String(void 0===e?"":e)+")_"+(++r+n).toString(36)}},function(e,t,r){var n=r(5);e.exports=!!Object.getOwnPropertySymbols&&!n((function(){return!String(Symbol())}))},function(e,t,r){var n=r(19),i=r(13),o=r(1)("toStringTag"),s="Arguments"==i(function(){return arguments}());e.exports=n?i:function(e){var t,r,n;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(r=function(e,t){try{return e[t]}catch(e){}}(t=Object(e),o))?r:s?i(t):"Object"==(n=i(t))&&"function"==typeof t.callee?"Arguments":n}},function(e,t,r){"use strict";var n=r(17),i=r(73),o=r(45),s=r(78),u=r(30),a=r(6),c=r(10),l=r(1),f=r(12),p=r(14),h=r(44),d=h.IteratorPrototype,y=h.BUGGY_SAFARI_ITERATORS,v=l("iterator"),m=function(){return this};e.exports=function(e,t,r,l,h,g,x){i(r,t,l);var w,b,S,P=function(e){if(e===h&&T)return T;if(!y&&e in E)return E[e];switch(e){case"keys":case"values":case"entries":return function(){return new r(this,e)}}return function(){return new r(this)}},k=t+" Iterator",O=!1,E=e.prototype,L=E[v]||E["@@iterator"]||h&&E[h],T=!y&&L||P(h),Q="Array"==t&&E.entries||L;if(Q&&(w=o(Q.call(new e)),d!==Object.prototype&&w.next&&(f||o(w)===d||(s?s(w,d):"function"!=typeof w[v]&&a(w,v,m)),u(w,k,!0,!0),f&&(p[k]=m))),"values"==h&&L&&"values"!==L.name&&(O=!0,T=function(){return L.call(this)}),f&&!x||E[v]===T||a(E,v,T),p[t]=T,h)if(b={values:P("values"),keys:g?T:P("keys"),entries:P("entries")},x)for(S in b)!y&&!O&&S in E||c(E,S,b[S]);else n({target:t,proto:!0,forced:y||O},b);return b}},function(e,t,r){var n=r(0);e.exports=n},function(e,t,r){var n=r(3),i=r(18),o=r(70).indexOf,s=r(25);e.exports=function(e,t){var r,u=i(e),a=0,c=[];for(r in u)!n(s,r)&&n(u,r)&&c.push(r);for(;t.length>a;)n(u,r=t[a++])&&(~o(c,r)||c.push(r));return c}},function(e,t,r){var n=r(26),i=Math.min;e.exports=function(e){return e>0?i(n(e),9007199254740991):0}},function(e,t,r){var n=r(5),i=/#|\\.prototype\\./,o=function(e,t){var r=u[s(e)];return r==c||r!=a&&("function"==typeof t?n(t):!!t)},s=o.normalize=function(e){return String(e).replace(i,".").toLowerCase()},u=o.data={},a=o.NATIVE="N",c=o.POLYFILL="P";e.exports=o},function(e,t,r){"use strict";var n,i,o,s=r(45),u=r(6),a=r(3),c=r(1),l=r(12),f=c("iterator"),p=!1;[].keys&&("next"in(o=[].keys())?(i=s(s(o)))!==Object.prototype&&(n=i):p=!0),null==n&&(n={}),l||a(n,f)||u(n,f,(function(){return this})),e.exports={IteratorPrototype:n,BUGGY_SAFARI_ITERATORS:p}},function(e,t,r){var n=r(3),i=r(74),o=r(24),s=r(75),u=o("IE_PROTO"),a=Object.prototype;e.exports=s?Object.getPrototypeOf:function(e){return e=i(e),n(e,u)?e[u]:"function"==typeof e.constructor&&e instanceof e.constructor?e.constructor.prototype:e instanceof Object?a:null}},function(e,t,r){var n,i=r(2),o=r(76),s=r(29),u=r(25),a=r(47),c=r(21),l=r(24),f=l("IE_PROTO"),p=function(){},h=function(e){return"<script>"+e+"<\\/script>"},d=function(){try{n=document.domain&&new ActiveXObject("htmlfile")}catch(e){}var e,t;d=n?function(e){e.write(h("")),e.close();var t=e.parentWindow.Object;return e=null,t}(n):((t=c("iframe")).style.display="none",a.appendChild(t),t.src=String("javascript:"),(e=t.contentWindow.document).open(),e.write(h("document.F=Object")),e.close(),e.F);for(var r=s.length;r--;)delete d.prototype[s[r]];return d()};u[f]=!0,e.exports=Object.create||function(e,t){var r;return null!==e?(p.prototype=i(e),r=new p,p.prototype=null,r[f]=e):r=d(),void 0===t?r:o(r,t)}},function(e,t,r){var n=r(11);e.exports=n("document","documentElement")},function(e,t,r){var n=r(0);e.exports=n.Promise},function(e,t,r){var n=r(2),i=r(88),o=r(42),s=r(50),u=r(89),a=r(90),c=function(e,t){this.stopped=e,this.result=t};(e.exports=function(e,t,r,l,f){var p,h,d,y,v,m,g,x=s(t,r,l?2:1);if(f)p=e;else{if("function"!=typeof(h=u(e)))throw TypeError("Target is not iterable");if(i(h)){for(d=0,y=o(e.length);y>d;d++)if((v=l?x(n(g=e[d])[0],g[1]):x(e[d]))&&v instanceof c)return v;return new c(!1)}p=h.call(e)}for(m=p.next;!(g=m.call(p)).done;)if("object"==typeof(v=a(p,x,g.value,l))&&v&&v instanceof c)return v;return new c(!1)}).stop=function(e){return new c(!0,e)}},function(e,t,r){var n=r(15);e.exports=function(e,t,r){if(n(e),void 0===t)return e;switch(r){case 0:return function(){return e.call(t)};case 1:return function(r){return e.call(t,r)};case 2:return function(r,n){return e.call(t,r,n)};case 3:return function(r,n,i){return e.call(t,r,n,i)}}return function(){return e.apply(t,arguments)}}},function(e,t,r){var n=r(2),i=r(15),o=r(1)("species");e.exports=function(e,t){var r,s=n(e).constructor;return void 0===s||null==(r=n(s)[o])?t:i(r)}},function(e,t,r){var n,i,o,s=r(0),u=r(5),a=r(13),c=r(50),l=r(47),f=r(21),p=r(53),h=s.location,d=s.setImmediate,y=s.clearImmediate,v=s.process,m=s.MessageChannel,g=s.Dispatch,x=0,w={},b=function(e){if(w.hasOwnProperty(e)){var t=w[e];delete w[e],t()}},S=function(e){return function(){b(e)}},P=function(e){b(e.data)},k=function(e){s.postMessage(e+"",h.protocol+"//"+h.host)};d&&y||(d=function(e){for(var t=[],r=1;arguments.length>r;)t.push(arguments[r++]);return w[++x]=function(){("function"==typeof e?e:Function(e)).apply(void 0,t)},n(x),x},y=function(e){delete w[e]},"process"==a(v)?n=function(e){v.nextTick(S(e))}:g&&g.now?n=function(e){g.now(S(e))}:m&&!p?(o=(i=new m).port2,i.port1.onmessage=P,n=c(o.postMessage,o,1)):!s.addEventListener||"function"!=typeof postMessage||s.importScripts||u(k)?n="onreadystatechange"in f("script")?function(e){l.appendChild(f("script")).onreadystatechange=function(){l.removeChild(this),b(e)}}:function(e){setTimeout(S(e),0)}:(n=k,s.addEventListener("message",P,!1))),e.exports={set:d,clear:y}},function(e,t,r){var n=r(54);e.exports=/(iphone|ipod|ipad).*applewebkit/i.test(n)},function(e,t,r){var n=r(11);e.exports=n("navigator","userAgent")||""},function(e,t,r){var n=r(2),i=r(7),o=r(31);e.exports=function(e,t){if(n(e),i(t)&&t.constructor===e)return t;var r=o.f(e);return(0,r.resolve)(t),r.promise}},function(e,t){e.exports=function(e){try{return{error:!1,value:e()}}catch(e){return{error:!0,value:e}}}},function(e,t,r){r(58),r(63),r(80),r(84),r(95),r(96);var n=r(40);e.exports=n.Promise},function(e,t,r){var n=r(19),i=r(10),o=r(62);n||i(Object.prototype,"toString",o,{unsafe:!0})},function(e,t){var r;r=function(){return this}();try{r=r||new Function("return this")()}catch(e){"object"==typeof window&&(r=window)}e.exports=r},function(e,t,r){var n=r(37);e.exports=n&&!Symbol.sham&&"symbol"==typeof Symbol.iterator},function(e,t,r){var n=r(0),i=r(23),o=n.WeakMap;e.exports="function"==typeof o&&/native code/.test(i(o))},function(e,t,r){"use strict";var n=r(19),i=r(38);e.exports=n?{}.toString:function(){return"[object "+i(this)+"]"}},function(e,t,r){"use strict";var n=r(64).charAt,i=r(16),o=r(39),s=i.set,u=i.getterFor("String Iterator");o(String,"String",(function(e){s(this,{type:"String Iterator",string:String(e),index:0})}),(function(){var e,t=u(this),r=t.string,i=t.index;return i>=r.length?{value:void 0,done:!0}:(e=n(r,i),t.index+=e.length,{value:e,done:!1})}))},function(e,t,r){var n=r(26),i=r(27),o=function(e){return function(t,r){var o,s,u=String(i(t)),a=n(r),c=u.length;return a<0||a>=c?e?"":void 0:(o=u.charCodeAt(a))<55296||o>56319||a+1===c||(s=u.charCodeAt(a+1))<56320||s>57343?e?u.charAt(a):o:e?u.slice(a,a+2):s-56320+(o-55296<<10)+65536}};e.exports={codeAt:o(!1),charAt:o(!0)}},function(e,t,r){"use strict";var n={}.propertyIsEnumerable,i=Object.getOwnPropertyDescriptor,o=i&&!n.call({1:2},1);t.f=o?function(e){var t=i(this,e);return!!t&&t.enumerable}:n},function(e,t,r){var n=r(5),i=r(13),o="".split;e.exports=n((function(){return!Object("z").propertyIsEnumerable(0)}))?function(e){return"String"==i(e)?o.call(e,""):Object(e)}:Object},function(e,t,r){var n=r(3),i=r(68),o=r(28),s=r(9);e.exports=function(e,t){for(var r=i(t),u=s.f,a=o.f,c=0;c<r.length;c++){var l=r[c];n(e,l)||u(e,l,a(t,l))}}},function(e,t,r){var n=r(11),i=r(69),o=r(72),s=r(2);e.exports=n("Reflect","ownKeys")||function(e){var t=i.f(s(e)),r=o.f;return r?t.concat(r(e)):t}},function(e,t,r){var n=r(41),i=r(29).concat("length","prototype");t.f=Object.getOwnPropertyNames||function(e){return n(e,i)}},function(e,t,r){var n=r(18),i=r(42),o=r(71),s=function(e){return function(t,r,s){var u,a=n(t),c=i(a.length),l=o(s,c);if(e&&r!=r){for(;c>l;)if((u=a[l++])!=u)return!0}else for(;c>l;l++)if((e||l in a)&&a[l]===r)return e||l||0;return!e&&-1}};e.exports={includes:s(!0),indexOf:s(!1)}},function(e,t,r){var n=r(26),i=Math.max,o=Math.min;e.exports=function(e,t){var r=n(e);return r<0?i(r+t,0):o(r,t)}},function(e,t){t.f=Object.getOwnPropertySymbols},function(e,t,r){"use strict";var n=r(44).IteratorPrototype,i=r(46),o=r(22),s=r(30),u=r(14),a=function(){return this};e.exports=function(e,t,r){var c=t+" Iterator";return e.prototype=i(n,{next:o(1,r)}),s(e,c,!1,!0),u[c]=a,e}},function(e,t,r){var n=r(27);e.exports=function(e){return Object(n(e))}},function(e,t,r){var n=r(5);e.exports=!n((function(){function e(){}return e.prototype.constructor=null,Object.getPrototypeOf(new e)!==e.prototype}))},function(e,t,r){var n=r(8),i=r(9),o=r(2),s=r(77);e.exports=n?Object.defineProperties:function(e,t){o(e);for(var r,n=s(t),u=n.length,a=0;u>a;)i.f(e,r=n[a++],t[r]);return e}},function(e,t,r){var n=r(41),i=r(29);e.exports=Object.keys||function(e){return n(e,i)}},function(e,t,r){var n=r(2),i=r(79);e.exports=Object.setPrototypeOf||("__proto__"in{}?function(){var e,t=!1,r={};try{(e=Object.getOwnPropertyDescriptor(Object.prototype,"__proto__").set).call(r,[]),t=r instanceof Array}catch(e){}return function(r,o){return n(r),i(o),t?e.call(r,o):r.__proto__=o,r}}():void 0)},function(e,t,r){var n=r(7);e.exports=function(e){if(!n(e)&&null!==e)throw TypeError("Can\'t set "+String(e)+" as a prototype");return e}},function(e,t,r){var n=r(0),i=r(81),o=r(82),s=r(6),u=r(1),a=u("iterator"),c=u("toStringTag"),l=o.values;for(var f in i){var p=n[f],h=p&&p.prototype;if(h){if(h[a]!==l)try{s(h,a,l)}catch(e){h[a]=l}if(h[c]||s(h,c,f),i[f])for(var d in o)if(h[d]!==o[d])try{s(h,d,o[d])}catch(e){h[d]=o[d]}}}},function(e,t){e.exports={CSSRuleList:0,CSSStyleDeclaration:0,CSSValueList:0,ClientRectList:0,DOMRectList:0,DOMStringList:0,DOMTokenList:1,DataTransferItemList:0,FileList:0,HTMLAllCollection:0,HTMLCollection:0,HTMLFormElement:0,HTMLSelectElement:0,MediaList:0,MimeTypeArray:0,NamedNodeMap:0,NodeList:1,PaintRequestList:0,Plugin:0,PluginArray:0,SVGLengthList:0,SVGNumberList:0,SVGPathSegList:0,SVGPointList:0,SVGStringList:0,SVGTransformList:0,SourceBufferList:0,StyleSheetList:0,TextTrackCueList:0,TextTrackList:0,TouchList:0}},function(e,t,r){"use strict";var n=r(18),i=r(83),o=r(14),s=r(16),u=r(39),a=s.set,c=s.getterFor("Array Iterator");e.exports=u(Array,"Array",(function(e,t){a(this,{type:"Array Iterator",target:n(e),index:0,kind:t})}),(function(){var e=c(this),t=e.target,r=e.kind,n=e.index++;return!t||n>=t.length?(e.target=void 0,{value:void 0,done:!0}):"keys"==r?{value:n,done:!1}:"values"==r?{value:t[n],done:!1}:{value:[n,t[n]],done:!1}}),"values"),o.Arguments=o.Array,i("keys"),i("values"),i("entries")},function(e,t,r){var n=r(1),i=r(46),o=r(9),s=n("unscopables"),u=Array.prototype;null==u[s]&&o.f(u,s,{configurable:!0,value:i(null)}),e.exports=function(e){u[s][e]=!0}},function(e,t,r){"use strict";var n,i,o,s,u=r(17),a=r(12),c=r(0),l=r(11),f=r(48),p=r(10),h=r(85),d=r(30),y=r(86),v=r(7),m=r(15),g=r(87),x=r(13),w=r(23),b=r(49),S=r(91),P=r(51),k=r(52).set,O=r(92),E=r(55),L=r(93),T=r(31),Q=r(56),j=r(16),I=r(43),R=r(1),_=r(94),F=R("species"),C="Promise",N=j.get,A=j.set,D=j.getterFor(C),M=f,B=c.TypeError,V=c.document,z=c.process,W=l("fetch"),$=T.f,q=$,G="process"==x(z),U=!!(V&&V.createEvent&&c.dispatchEvent),H=I(C,(function(){if(!(w(M)!==String(M))){if(66===_)return!0;if(!G&&"function"!=typeof PromiseRejectionEvent)return!0}if(a&&!M.prototype.finally)return!0;if(_>=51&&/native code/.test(M))return!1;var e=M.resolve(1),t=function(e){e((function(){}),(function(){}))};return(e.constructor={})[F]=t,!(e.then((function(){}))instanceof t)})),J=H||!S((function(e){M.all(e).catch((function(){}))})),Y=function(e){var t;return!(!v(e)||"function"!=typeof(t=e.then))&&t},K=function(e,t,r){if(!t.notified){t.notified=!0;var n=t.reactions;O((function(){for(var i=t.value,o=1==t.state,s=0;n.length>s;){var u,a,c,l=n[s++],f=o?l.ok:l.fail,p=l.resolve,h=l.reject,d=l.domain;try{f?(o||(2===t.rejection&&te(e,t),t.rejection=1),!0===f?u=i:(d&&d.enter(),u=f(i),d&&(d.exit(),c=!0)),u===l.promise?h(B("Promise-chain cycle")):(a=Y(u))?a.call(u,p,h):p(u)):h(i)}catch(e){d&&!c&&d.exit(),h(e)}}t.reactions=[],t.notified=!1,r&&!t.rejection&&Z(e,t)}))}},X=function(e,t,r){var n,i;U?((n=V.createEvent("Event")).promise=t,n.reason=r,n.initEvent(e,!1,!0),c.dispatchEvent(n)):n={promise:t,reason:r},(i=c["on"+e])?i(n):"unhandledrejection"===e&&L("Unhandled promise rejection",r)},Z=function(e,t){k.call(c,(function(){var r,n=t.value;if(ee(t)&&(r=Q((function(){G?z.emit("unhandledRejection",n,e):X("unhandledrejection",e,n)})),t.rejection=G||ee(t)?2:1,r.error))throw r.value}))},ee=function(e){return 1!==e.rejection&&!e.parent},te=function(e,t){k.call(c,(function(){G?z.emit("rejectionHandled",e):X("rejectionhandled",e,t.value)}))},re=function(e,t,r,n){return function(i){e(t,r,i,n)}},ne=function(e,t,r,n){t.done||(t.done=!0,n&&(t=n),t.value=r,t.state=2,K(e,t,!0))},ie=function(e,t,r,n){if(!t.done){t.done=!0,n&&(t=n);try{if(e===r)throw B("Promise can\'t be resolved itself");var i=Y(r);i?O((function(){var n={done:!1};try{i.call(r,re(ie,e,n,t),re(ne,e,n,t))}catch(r){ne(e,n,r,t)}})):(t.value=r,t.state=1,K(e,t,!1))}catch(r){ne(e,{done:!1},r,t)}}};H&&(M=function(e){g(this,M,C),m(e),n.call(this);var t=N(this);try{e(re(ie,this,t),re(ne,this,t))}catch(e){ne(this,t,e)}},(n=function(e){A(this,{type:C,done:!1,notified:!1,parent:!1,reactions:[],rejection:!1,state:0,value:void 0})}).prototype=h(M.prototype,{then:function(e,t){var r=D(this),n=$(P(this,M));return n.ok="function"!=typeof e||e,n.fail="function"==typeof t&&t,n.domain=G?z.domain:void 0,r.parent=!0,r.reactions.push(n),0!=r.state&&K(this,r,!1),n.promise},catch:function(e){return this.then(void 0,e)}}),i=function(){var e=new n,t=N(e);this.promise=e,this.resolve=re(ie,e,t),this.reject=re(ne,e,t)},T.f=$=function(e){return e===M||e===o?new i(e):q(e)},a||"function"!=typeof f||(s=f.prototype.then,p(f.prototype,"then",(function(e,t){var r=this;return new M((function(e,t){s.call(r,e,t)})).then(e,t)}),{unsafe:!0}),"function"==typeof W&&u({global:!0,enumerable:!0,forced:!0},{fetch:function(e){return E(M,W.apply(c,arguments))}}))),u({global:!0,wrap:!0,forced:H},{Promise:M}),d(M,C,!1,!0),y(C),o=l(C),u({target:C,stat:!0,forced:H},{reject:function(e){var t=$(this);return t.reject.call(void 0,e),t.promise}}),u({target:C,stat:!0,forced:a||H},{resolve:function(e){return E(a&&this===o?M:this,e)}}),u({target:C,stat:!0,forced:J},{all:function(e){var t=this,r=$(t),n=r.resolve,i=r.reject,o=Q((function(){var r=m(t.resolve),o=[],s=0,u=1;b(e,(function(e){var a=s++,c=!1;o.push(void 0),u++,r.call(t,e).then((function(e){c||(c=!0,o[a]=e,--u||n(o))}),i)})),--u||n(o)}));return o.error&&i(o.value),r.promise},race:function(e){var t=this,r=$(t),n=r.reject,i=Q((function(){var i=m(t.resolve);b(e,(function(e){i.call(t,e).then(r.resolve,n)}))}));return i.error&&n(i.value),r.promise}})},function(e,t,r){var n=r(10);e.exports=function(e,t,r){for(var i in t)n(e,i,t[i],r);return e}},function(e,t,r){"use strict";var n=r(11),i=r(9),o=r(1),s=r(8),u=o("species");e.exports=function(e){var t=n(e),r=i.f;s&&t&&!t[u]&&r(t,u,{configurable:!0,get:function(){return this}})}},function(e,t){e.exports=function(e,t,r){if(!(e instanceof t))throw TypeError("Incorrect "+(r?r+" ":"")+"invocation");return e}},function(e,t,r){var n=r(1),i=r(14),o=n("iterator"),s=Array.prototype;e.exports=function(e){return void 0!==e&&(i.Array===e||s[o]===e)}},function(e,t,r){var n=r(38),i=r(14),o=r(1)("iterator");e.exports=function(e){if(null!=e)return e[o]||e["@@iterator"]||i[n(e)]}},function(e,t,r){var n=r(2);e.exports=function(e,t,r,i){try{return i?t(n(r)[0],r[1]):t(r)}catch(t){var o=e.return;throw void 0!==o&&n(o.call(e)),t}}},function(e,t,r){var n=r(1)("iterator"),i=!1;try{var o=0,s={next:function(){return{done:!!o++}},return:function(){i=!0}};s[n]=function(){return this},Array.from(s,(function(){throw 2}))}catch(e){}e.exports=function(e,t){if(!t&&!i)return!1;var r=!1;try{var o={};o[n]=function(){return{next:function(){return{done:r=!0}}}},e(o)}catch(e){}return r}},function(e,t,r){var n,i,o,s,u,a,c,l,f=r(0),p=r(28).f,h=r(13),d=r(52).set,y=r(53),v=f.MutationObserver||f.WebKitMutationObserver,m=f.process,g=f.Promise,x="process"==h(m),w=p(f,"queueMicrotask"),b=w&&w.value;b||(n=function(){var e,t;for(x&&(e=m.domain)&&e.exit();i;){t=i.fn,i=i.next;try{t()}catch(e){throw i?s():o=void 0,e}}o=void 0,e&&e.enter()},x?s=function(){m.nextTick(n)}:v&&!y?(u=!0,a=document.createTextNode(""),new v(n).observe(a,{characterData:!0}),s=function(){a.data=u=!u}):g&&g.resolve?(c=g.resolve(void 0),l=c.then,s=function(){l.call(c,n)}):s=function(){d.call(f,n)}),e.exports=b||function(e){var t={fn:e,next:void 0};o&&(o.next=t),i||(i=t,s()),o=t}},function(e,t,r){var n=r(0);e.exports=function(e,t){var r=n.console;r&&r.error&&(1===arguments.length?r.error(e):r.error(e,t))}},function(e,t,r){var n,i,o=r(0),s=r(54),u=o.process,a=u&&u.versions,c=a&&a.v8;c?i=(n=c.split("."))[0]+n[1]:s&&(!(n=s.match(/Edge\\/(\\d+)/))||n[1]>=74)&&(n=s.match(/Chrome\\/(\\d+)/))&&(i=n[1]),e.exports=i&&+i},function(e,t,r){"use strict";var n=r(17),i=r(15),o=r(31),s=r(56),u=r(49);n({target:"Promise",stat:!0},{allSettled:function(e){var t=this,r=o.f(t),n=r.resolve,a=r.reject,c=s((function(){var r=i(t.resolve),o=[],s=0,a=1;u(e,(function(e){var i=s++,u=!1;o.push(void 0),a++,r.call(t,e).then((function(e){u||(u=!0,o[i]={status:"fulfilled",value:e},--a||n(o))}),(function(e){u||(u=!0,o[i]={status:"rejected",reason:e},--a||n(o))}))})),--a||n(o)}));return c.error&&a(c.value),r.promise}})},function(e,t,r){"use strict";var n=r(17),i=r(12),o=r(48),s=r(5),u=r(11),a=r(51),c=r(55),l=r(10);n({target:"Promise",proto:!0,real:!0,forced:!!o&&s((function(){o.prototype.finally.call({then:function(){}},(function(){}))}))},{finally:function(e){var t=a(this,u("Promise")),r="function"==typeof e;return this.then(r?function(r){return c(t,e()).then((function(){return r}))}:e,r?function(r){return c(t,e()).then((function(){throw r}))}:e)}}),i||"function"!=typeof o||o.prototype.finally||l(o.prototype,"finally",u("Promise").prototype.finally)},function(e,t,r){"use strict";r.r(t),r.d(t,"add",(function(){return f})),r.d(t,"done",(function(){return p})),r.d(t,"toJS",(function(){return h})),r.d(t,"load",(function(){return d})),r.d(t,"search",(function(){return y}));function n(e,t,r,n){return new(r||(r=Promise))((function(i,o){function s(e){try{a(n.next(e))}catch(e){o(e)}}function u(e){try{a(n.throw(e))}catch(e){o(e)}}function a(e){var t;e.done?i(e.value):(t=e.value,t instanceof r?t:new r((function(e){e(t)}))).then(s,u)}a((n=n.apply(e,t||[])).next())}))}function i(e,t){var r,n,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:u(0),throw:u(1),return:u(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function u(o){return function(u){return function(o){if(r)throw new TypeError("Generator is already executing.");for(;s;)try{if(r=1,n&&(i=2&o[0]?n.return:o[0]?n.throw||((i=n.return)&&i.call(n),0):n.next)&&!(i=i.call(n,o[1])).done)return i;switch(n=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,n=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=(i=s.trys).length>0&&i[i.length-1])&&(6===o[0]||2===o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],n=0}finally{r=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,u])}}}var o=r(4);try{r(57)}catch(e){}var s=function(){this.add=f,this.done=p,this.search=y,this.toJS=h,this.load=d},u=(t.default=s,[]),a=function(){throw new Error("Should not be called")},c=new Promise((function(e){a=e}));o.tokenizer.separator=/\\s+/;var l=new o.Builder;l.field("title"),l.field("description"),l.ref("ref"),l.pipeline.add(o.trimmer,o.stopWordFilter,o.stemmer);function f(e,t,r){var n=u.push(r)-1,i={title:e.toLowerCase(),description:t.toLowerCase(),ref:n};l.add(i)}function p(){return n(this,void 0,void 0,(function(){return i(this,(function(e){return a(l.build()),[2]}))}))}function h(){return n(this,void 0,void 0,(function(){var e;return i(this,(function(t){switch(t.label){case 0:return e={store:u},[4,c];case 1:return[2,(e.index=t.sent().toJSON(),e)]}}))}))}function d(e){return n(this,void 0,void 0,(function(){return i(this,(function(t){return u=e.store,a(o.Index.load(e.index)),[2]}))}))}function y(e,t){return void 0===t&&(t=0),n(this,void 0,void 0,(function(){var r;return i(this,(function(n){switch(n.label){case 0:return 0===e.trim().length?[2,[]]:[4,c];case 1:return r=n.sent().query((function(t){e.trim().toLowerCase().split(/\\s+/).forEach((function(e){var r=function(e){return"*"+o.stemmer(new o.Token(e,{}))+"*"}(e);t.term(r,{})}))})),t>0&&(r=r.slice(0,t)),[2,r.map((function(e){return{meta:u[e.ref],score:e.score}}))]}}))}))}addEventListener("message",(function(e){var r,n=e.data,i=n.type,o=n.method,s=n.id,u=n.params;"RPC"===i&&o&&((r=t[o])?Promise.resolve().then((function(){return r.apply(t,u)})):Promise.reject("No such method")).then((function(e){postMessage({type:"RPC",id:s,result:e})})).catch((function(e){var t={message:e};e.stack&&(t.message=e.message,t.stack=e.stack,t.name=e.name),postMessage({type:"RPC",id:s,error:t})}))})),postMessage({type:"RPC",method:"ready"})}]);\n//# sourceMappingURL=66d14429db4e18077079.worker.js.map'])),{name:"[hash].worker.js"});return r(e,o),e}},function(e,t){e.exports=function(e,t){var n=0,r={};e.addEventListener("message",(function(t){var n=t.data;if("RPC"===n.type)if(n.id){var o=r[n.id];o&&(delete r[n.id],n.error?o[1](Object.assign(Error(n.error.message),n.error)):o[0](n.result))}else{var i=document.createEvent("Event");i.initEvent(n.method,!1,!1),i.data=n.params,e.dispatchEvent(i)}})),t.forEach((function(t){e[t]=function(){for(var o=[],i=arguments.length;i--;)o[i]=arguments[i];return new Promise((function(i,a){var s=++n;r[s]=[i,a],e.postMessage({type:"RPC",id:s,method:t,params:o})}))}}))}},function(e,t,n){"use strict";e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n=function(e,t){var n=e[1]||"",r=e[3];if(!r)return n;if(t&&"function"==typeof btoa){var o=(a=r,s=btoa(unescape(encodeURIComponent(JSON.stringify(a)))),l="sourceMappingURL=data:application/json;charset=utf-8;base64,".concat(s),"/*# ".concat(l," */")),i=r.sources.map((function(e){return"/*# sourceURL=".concat(r.sourceRoot||"").concat(e," */")}));return[n].concat(i).concat([o]).join("\n")}var a,s,l;return[n].join("\n")}(t,e);return t[2]?"@media ".concat(t[2]," {").concat(n,"}"):n})).join("")},t.i=function(e,n,r){"string"==typeof e&&(e=[[null,e,""]]);var o={};if(r)for(var i=0;i<this.length;i++){var a=this[i][0];null!=a&&(o[a]=!0)}for(var s=0;s<e.length;s++){var l=[].concat(e[s]);r&&o[l[0]]||(n&&(l[2]?l[2]="".concat(n," and ").concat(l[2]):l[2]=n),t.push(l))}},t}},function(e,t){ + */var r="function"==typeof Symbol&&Symbol.for,o=r?Symbol.for("react.element"):60103,i=r?Symbol.for("react.portal"):60106,a=r?Symbol.for("react.fragment"):60107,s=r?Symbol.for("react.strict_mode"):60108,l=r?Symbol.for("react.profiler"):60114,c=r?Symbol.for("react.provider"):60109,u=r?Symbol.for("react.context"):60110,p=r?Symbol.for("react.async_mode"):60111,f=r?Symbol.for("react.concurrent_mode"):60111,d=r?Symbol.for("react.forward_ref"):60112,h=r?Symbol.for("react.suspense"):60113,m=r?Symbol.for("react.suspense_list"):60120,g=r?Symbol.for("react.memo"):60115,y=r?Symbol.for("react.lazy"):60116,v=r?Symbol.for("react.block"):60121,b=r?Symbol.for("react.fundamental"):60117,x=r?Symbol.for("react.responder"):60118,w=r?Symbol.for("react.scope"):60119;function k(e){if("object"==typeof e&&null!==e){var t=e.$$typeof;switch(t){case o:switch(e=e.type){case p:case f:case a:case l:case s:case h:return e;default:switch(e=e&&e.$$typeof){case u:case d:case y:case g:case c:return e;default:return t}}case i:return t}}}function O(e){return k(e)===f}t.AsyncMode=p,t.ConcurrentMode=f,t.ContextConsumer=u,t.ContextProvider=c,t.Element=o,t.ForwardRef=d,t.Fragment=a,t.Lazy=y,t.Memo=g,t.Portal=i,t.Profiler=l,t.StrictMode=s,t.Suspense=h,t.isAsyncMode=function(e){return O(e)||k(e)===p},t.isConcurrentMode=O,t.isContextConsumer=function(e){return k(e)===u},t.isContextProvider=function(e){return k(e)===c},t.isElement=function(e){return"object"==typeof e&&null!==e&&e.$$typeof===o},t.isForwardRef=function(e){return k(e)===d},t.isFragment=function(e){return k(e)===a},t.isLazy=function(e){return k(e)===y},t.isMemo=function(e){return k(e)===g},t.isPortal=function(e){return k(e)===i},t.isProfiler=function(e){return k(e)===l},t.isStrictMode=function(e){return k(e)===s},t.isSuspense=function(e){return k(e)===h},t.isValidElementType=function(e){return"string"==typeof e||"function"==typeof e||e===a||e===f||e===l||e===s||e===h||e===m||"object"==typeof e&&null!==e&&(e.$$typeof===y||e.$$typeof===g||e.$$typeof===c||e.$$typeof===u||e.$$typeof===d||e.$$typeof===b||e.$$typeof===x||e.$$typeof===w||e.$$typeof===v)},t.typeOf=k},function(e,t,n){"use strict";t.byteLength=function(e){var t=c(e),n=t[0],r=t[1];return 3*(n+r)/4-r},t.toByteArray=function(e){var t,n,r=c(e),a=r[0],s=r[1],l=new i(function(e,t,n){return 3*(t+n)/4-n}(0,a,s)),u=0,p=s>0?a-4:a;for(n=0;n<p;n+=4)t=o[e.charCodeAt(n)]<<18|o[e.charCodeAt(n+1)]<<12|o[e.charCodeAt(n+2)]<<6|o[e.charCodeAt(n+3)],l[u++]=t>>16&255,l[u++]=t>>8&255,l[u++]=255&t;2===s&&(t=o[e.charCodeAt(n)]<<2|o[e.charCodeAt(n+1)]>>4,l[u++]=255&t);1===s&&(t=o[e.charCodeAt(n)]<<10|o[e.charCodeAt(n+1)]<<4|o[e.charCodeAt(n+2)]>>2,l[u++]=t>>8&255,l[u++]=255&t);return l},t.fromByteArray=function(e){for(var t,n=e.length,o=n%3,i=[],a=0,s=n-o;a<s;a+=16383)i.push(u(e,a,a+16383>s?s:a+16383));1===o?(t=e[n-1],i.push(r[t>>2]+r[t<<4&63]+"==")):2===o&&(t=(e[n-2]<<8)+e[n-1],i.push(r[t>>10]+r[t>>4&63]+r[t<<2&63]+"="));return i.join("")};for(var r=[],o=[],i="undefined"!=typeof Uint8Array?Uint8Array:Array,a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",s=0,l=a.length;s<l;++s)r[s]=a[s],o[a.charCodeAt(s)]=s;function c(e){var t=e.length;if(t%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var n=e.indexOf("=");return-1===n&&(n=t),[n,n===t?0:4-n%4]}function u(e,t,n){for(var o,i,a=[],s=t;s<n;s+=3)o=(e[s]<<16&16711680)+(e[s+1]<<8&65280)+(255&e[s+2]),a.push(r[(i=o)>>18&63]+r[i>>12&63]+r[i>>6&63]+r[63&i]);return a.join("")}o["-".charCodeAt(0)]=62,o["_".charCodeAt(0)]=63},function(e,t){t.read=function(e,t,n,r,o){var i,a,s=8*o-r-1,l=(1<<s)-1,c=l>>1,u=-7,p=n?o-1:0,f=n?-1:1,d=e[t+p];for(p+=f,i=d&(1<<-u)-1,d>>=-u,u+=s;u>0;i=256*i+e[t+p],p+=f,u-=8);for(a=i&(1<<-u)-1,i>>=-u,u+=r;u>0;a=256*a+e[t+p],p+=f,u-=8);if(0===i)i=1-c;else{if(i===l)return a?NaN:1/0*(d?-1:1);a+=Math.pow(2,r),i-=c}return(d?-1:1)*a*Math.pow(2,i-r)},t.write=function(e,t,n,r,o,i){var a,s,l,c=8*i-o-1,u=(1<<c)-1,p=u>>1,f=23===o?Math.pow(2,-24)-Math.pow(2,-77):0,d=r?0:i-1,h=r?1:-1,m=t<0||0===t&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(s=isNaN(t)?1:0,a=u):(a=Math.floor(Math.log(t)/Math.LN2),t*(l=Math.pow(2,-a))<1&&(a--,l*=2),(t+=a+p>=1?f/l:f*Math.pow(2,1-p))*l>=2&&(a++,l/=2),a+p>=u?(s=0,a=u):a+p>=1?(s=(t*l-1)*Math.pow(2,o),a+=p):(s=t*Math.pow(2,p-1)*Math.pow(2,o),a=0));o>=8;e[n+d]=255&s,d+=h,s/=256,o-=8);for(a=a<<o|s,c+=o;c>0;e[n+d]=255&a,d+=h,a/=256,c-=8);e[n+d-h]|=128*m}},function(e,t,n){"use strict";(function(t){e.exports={order:100,allowEmpty:!0,canParse:".json",parse:function(e){return new Promise((function(n,r){var o=e.data;t.isBuffer(o)&&(o=o.toString()),"string"==typeof o?0===o.trim().length?n(void 0):n(JSON.parse(o)):n(o)}))}}}).call(this,n(14).Buffer)},function(e,t,n){"use strict";(function(t){var r=n(133);e.exports={order:200,allowEmpty:!0,canParse:[".yaml",".yml",".json"],parse:function(e){return new Promise((function(n,o){var i=e.data;t.isBuffer(i)&&(i=i.toString()),n("string"==typeof i?r.parse(i):i)}))}}}).call(this,n(14).Buffer)},function(e,t,n){"use strict";var r=n(244);e.exports=r},function(e,t,n){"use strict";var r=n(245),o=n(264);function i(e){return function(){throw new Error("Function "+e+" is deprecated and cannot be used.")}}e.exports.Type=n(6),e.exports.Schema=n(39),e.exports.FAILSAFE_SCHEMA=n(89),e.exports.JSON_SCHEMA=n(135),e.exports.CORE_SCHEMA=n(134),e.exports.DEFAULT_SAFE_SCHEMA=n(60),e.exports.DEFAULT_FULL_SCHEMA=n(90),e.exports.load=r.load,e.exports.loadAll=r.loadAll,e.exports.safeLoad=r.safeLoad,e.exports.safeLoadAll=r.safeLoadAll,e.exports.dump=o.dump,e.exports.safeDump=o.safeDump,e.exports.YAMLException=n(59),e.exports.MINIMAL_SCHEMA=n(89),e.exports.SAFE_SCHEMA=n(60),e.exports.DEFAULT_SCHEMA=n(90),e.exports.scan=i("scan"),e.exports.parse=i("parse"),e.exports.compose=i("compose"),e.exports.addConstructor=i("addConstructor")},function(e,t,n){"use strict";var r=n(49),o=n(59),i=n(246),a=n(60),s=n(90),l=Object.prototype.hasOwnProperty,c=/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x84\x86-\x9F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/,u=/[\x85\u2028\u2029]/,p=/[,\[\]\{\}]/,f=/^(?:!|!!|![a-z\-]+!)$/i,d=/^(?:!|[^,\[\]\{\}])(?:%[0-9a-f]{2}|[0-9a-z\-#;\/\?:@&=\+\$,_\.!~\*'\(\)\[\]])*$/i;function h(e){return Object.prototype.toString.call(e)}function m(e){return 10===e||13===e}function g(e){return 9===e||32===e}function y(e){return 9===e||32===e||10===e||13===e}function v(e){return 44===e||91===e||93===e||123===e||125===e}function b(e){var t;return 48<=e&&e<=57?e-48:97<=(t=32|e)&&t<=102?t-97+10:-1}function x(e){return 48===e?"\0":97===e?"":98===e?"\b":116===e||9===e?"\t":110===e?"\n":118===e?"\v":102===e?"\f":114===e?"\r":101===e?"":32===e?" ":34===e?'"':47===e?"/":92===e?"\\":78===e?"…":95===e?" ":76===e?"\u2028":80===e?"\u2029":""}function w(e){return e<=65535?String.fromCharCode(e):String.fromCharCode(55296+(e-65536>>10),56320+(e-65536&1023))}for(var k=new Array(256),O=new Array(256),_=0;_<256;_++)k[_]=x(_)?1:0,O[_]=x(_);function E(e,t){this.input=e,this.filename=t.filename||null,this.schema=t.schema||s,this.onWarning=t.onWarning||null,this.legacy=t.legacy||!1,this.json=t.json||!1,this.listener=t.listener||null,this.implicitTypes=this.schema.compiledImplicit,this.typeMap=this.schema.compiledTypeMap,this.length=e.length,this.position=0,this.line=0,this.lineStart=0,this.lineIndent=0,this.documents=[]}function S(e,t){return new o(t,new i(e.filename,e.input,e.position,e.line,e.position-e.lineStart))}function T(e,t){throw S(e,t)}function j(e,t){e.onWarning&&e.onWarning.call(null,S(e,t))}var C={YAML:function(e,t,n){var r,o,i;null!==e.version&&T(e,"duplication of %YAML directive"),1!==n.length&&T(e,"YAML directive accepts exactly one argument"),null===(r=/^([0-9]+)\.([0-9]+)$/.exec(n[0]))&&T(e,"ill-formed argument of the YAML directive"),o=parseInt(r[1],10),i=parseInt(r[2],10),1!==o&&T(e,"unacceptable YAML version of the document"),e.version=n[0],e.checkLineBreaks=i<2,1!==i&&2!==i&&j(e,"unsupported YAML version of the document")},TAG:function(e,t,n){var r,o;2!==n.length&&T(e,"TAG directive accepts exactly two arguments"),r=n[0],o=n[1],f.test(r)||T(e,"ill-formed tag handle (first argument) of the TAG directive"),l.call(e.tagMap,r)&&T(e,'there is a previously declared suffix for "'+r+'" tag handle'),d.test(o)||T(e,"ill-formed tag prefix (second argument) of the TAG directive"),e.tagMap[r]=o}};function I(e,t,n,r){var o,i,a,s;if(t<n){if(s=e.input.slice(t,n),r)for(o=0,i=s.length;o<i;o+=1)9===(a=s.charCodeAt(o))||32<=a&&a<=1114111||T(e,"expected valid JSON character");else c.test(s)&&T(e,"the stream contains non-printable characters");e.result+=s}}function A(e,t,n,o){var i,a,s,c;for(r.isObject(n)||T(e,"cannot merge mappings; the provided source object is unacceptable"),s=0,c=(i=Object.keys(n)).length;s<c;s+=1)a=i[s],l.call(t,a)||(t[a]=n[a],o[a]=!0)}function P(e,t,n,r,o,i,a,s){var c,u;if(Array.isArray(o))for(c=0,u=(o=Array.prototype.slice.call(o)).length;c<u;c+=1)Array.isArray(o[c])&&T(e,"nested arrays are not supported inside keys"),"object"==typeof o&&"[object Object]"===h(o[c])&&(o[c]="[object Object]");if("object"==typeof o&&"[object Object]"===h(o)&&(o="[object Object]"),o=String(o),null===t&&(t={}),"tag:yaml.org,2002:merge"===r)if(Array.isArray(i))for(c=0,u=i.length;c<u;c+=1)A(e,t,i[c],n);else A(e,t,i,n);else e.json||l.call(n,o)||!l.call(t,o)||(e.line=a||e.line,e.position=s||e.position,T(e,"duplicated mapping key")),t[o]=i,delete n[o];return t}function R(e){var t;10===(t=e.input.charCodeAt(e.position))?e.position++:13===t?(e.position++,10===e.input.charCodeAt(e.position)&&e.position++):T(e,"a line break is expected"),e.line+=1,e.lineStart=e.position}function N(e,t,n){for(var r=0,o=e.input.charCodeAt(e.position);0!==o;){for(;g(o);)o=e.input.charCodeAt(++e.position);if(t&&35===o)do{o=e.input.charCodeAt(++e.position)}while(10!==o&&13!==o&&0!==o);if(!m(o))break;for(R(e),o=e.input.charCodeAt(e.position),r++,e.lineIndent=0;32===o;)e.lineIndent++,o=e.input.charCodeAt(++e.position)}return-1!==n&&0!==r&&e.lineIndent<n&&j(e,"deficient indentation"),r}function L(e){var t,n=e.position;return!(45!==(t=e.input.charCodeAt(n))&&46!==t||t!==e.input.charCodeAt(n+1)||t!==e.input.charCodeAt(n+2)||(n+=3,0!==(t=e.input.charCodeAt(n))&&!y(t)))}function M(e,t){1===t?e.result+=" ":t>1&&(e.result+=r.repeat("\n",t-1))}function D(e,t){var n,r,o=e.tag,i=e.anchor,a=[],s=!1;for(null!==e.anchor&&(e.anchorMap[e.anchor]=a),r=e.input.charCodeAt(e.position);0!==r&&45===r&&y(e.input.charCodeAt(e.position+1));)if(s=!0,e.position++,N(e,!0,-1)&&e.lineIndent<=t)a.push(null),r=e.input.charCodeAt(e.position);else if(n=e.line,U(e,t,3,!1,!0),a.push(e.result),N(e,!0,-1),r=e.input.charCodeAt(e.position),(e.line===n||e.lineIndent>t)&&0!==r)T(e,"bad indentation of a sequence entry");else if(e.lineIndent<t)break;return!!s&&(e.tag=o,e.anchor=i,e.kind="sequence",e.result=a,!0)}function F(e){var t,n,r,o,i=!1,a=!1;if(33!==(o=e.input.charCodeAt(e.position)))return!1;if(null!==e.tag&&T(e,"duplication of a tag property"),60===(o=e.input.charCodeAt(++e.position))?(i=!0,o=e.input.charCodeAt(++e.position)):33===o?(a=!0,n="!!",o=e.input.charCodeAt(++e.position)):n="!",t=e.position,i){do{o=e.input.charCodeAt(++e.position)}while(0!==o&&62!==o);e.position<e.length?(r=e.input.slice(t,e.position),o=e.input.charCodeAt(++e.position)):T(e,"unexpected end of the stream within a verbatim tag")}else{for(;0!==o&&!y(o);)33===o&&(a?T(e,"tag suffix cannot contain exclamation marks"):(n=e.input.slice(t-1,e.position+1),f.test(n)||T(e,"named tag handle cannot contain such characters"),a=!0,t=e.position+1)),o=e.input.charCodeAt(++e.position);r=e.input.slice(t,e.position),p.test(r)&&T(e,"tag suffix cannot contain flow indicator characters")}return r&&!d.test(r)&&T(e,"tag name cannot contain such characters: "+r),i?e.tag=r:l.call(e.tagMap,n)?e.tag=e.tagMap[n]+r:"!"===n?e.tag="!"+r:"!!"===n?e.tag="tag:yaml.org,2002:"+r:T(e,'undeclared tag handle "'+n+'"'),!0}function z(e){var t,n;if(38!==(n=e.input.charCodeAt(e.position)))return!1;for(null!==e.anchor&&T(e,"duplication of an anchor property"),n=e.input.charCodeAt(++e.position),t=e.position;0!==n&&!y(n)&&!v(n);)n=e.input.charCodeAt(++e.position);return e.position===t&&T(e,"name of an anchor node must contain at least one character"),e.anchor=e.input.slice(t,e.position),!0}function U(e,t,n,o,i){var a,s,c,u,p,f,d,h,x=1,_=!1,E=!1;if(null!==e.listener&&e.listener("open",e),e.tag=null,e.anchor=null,e.kind=null,e.result=null,a=s=c=4===n||3===n,o&&N(e,!0,-1)&&(_=!0,e.lineIndent>t?x=1:e.lineIndent===t?x=0:e.lineIndent<t&&(x=-1)),1===x)for(;F(e)||z(e);)N(e,!0,-1)?(_=!0,c=a,e.lineIndent>t?x=1:e.lineIndent===t?x=0:e.lineIndent<t&&(x=-1)):c=!1;if(c&&(c=_||i),1!==x&&4!==n||(d=1===n||2===n?t:t+1,h=e.position-e.lineStart,1===x?c&&(D(e,h)||function(e,t,n){var r,o,i,a,s,l=e.tag,c=e.anchor,u={},p={},f=null,d=null,h=null,m=!1,v=!1;for(null!==e.anchor&&(e.anchorMap[e.anchor]=u),s=e.input.charCodeAt(e.position);0!==s;){if(r=e.input.charCodeAt(e.position+1),i=e.line,a=e.position,63!==s&&58!==s||!y(r)){if(!U(e,n,2,!1,!0))break;if(e.line===i){for(s=e.input.charCodeAt(e.position);g(s);)s=e.input.charCodeAt(++e.position);if(58===s)y(s=e.input.charCodeAt(++e.position))||T(e,"a whitespace character is expected after the key-value separator within a block mapping"),m&&(P(e,u,p,f,d,null),f=d=h=null),v=!0,m=!1,o=!1,f=e.tag,d=e.result;else{if(!v)return e.tag=l,e.anchor=c,!0;T(e,"can not read an implicit mapping pair; a colon is missed")}}else{if(!v)return e.tag=l,e.anchor=c,!0;T(e,"can not read a block mapping entry; a multiline key may not be an implicit key")}}else 63===s?(m&&(P(e,u,p,f,d,null),f=d=h=null),v=!0,m=!0,o=!0):m?(m=!1,o=!0):T(e,"incomplete explicit mapping pair; a key node is missed; or followed by a non-tabulated empty line"),e.position+=1,s=r;if((e.line===i||e.lineIndent>t)&&(U(e,t,4,!0,o)&&(m?d=e.result:h=e.result),m||(P(e,u,p,f,d,h,i,a),f=d=h=null),N(e,!0,-1),s=e.input.charCodeAt(e.position)),e.lineIndent>t&&0!==s)T(e,"bad indentation of a mapping entry");else if(e.lineIndent<t)break}return m&&P(e,u,p,f,d,null),v&&(e.tag=l,e.anchor=c,e.kind="mapping",e.result=u),v}(e,h,d))||function(e,t){var n,r,o,i,a,s,l,c,u,p,f=!0,d=e.tag,h=e.anchor,m={};if(91===(p=e.input.charCodeAt(e.position)))o=93,s=!1,r=[];else{if(123!==p)return!1;o=125,s=!0,r={}}for(null!==e.anchor&&(e.anchorMap[e.anchor]=r),p=e.input.charCodeAt(++e.position);0!==p;){if(N(e,!0,t),(p=e.input.charCodeAt(e.position))===o)return e.position++,e.tag=d,e.anchor=h,e.kind=s?"mapping":"sequence",e.result=r,!0;f||T(e,"missed comma between flow collection entries"),u=null,i=a=!1,63===p&&y(e.input.charCodeAt(e.position+1))&&(i=a=!0,e.position++,N(e,!0,t)),n=e.line,U(e,t,1,!1,!0),c=e.tag,l=e.result,N(e,!0,t),p=e.input.charCodeAt(e.position),!a&&e.line!==n||58!==p||(i=!0,p=e.input.charCodeAt(++e.position),N(e,!0,t),U(e,t,1,!1,!0),u=e.result),s?P(e,r,m,c,l,u):i?r.push(P(e,null,m,c,l,u)):r.push(l),N(e,!0,t),44===(p=e.input.charCodeAt(e.position))?(f=!0,p=e.input.charCodeAt(++e.position)):f=!1}T(e,"unexpected end of the stream within a flow collection")}(e,d)?E=!0:(s&&function(e,t){var n,o,i,a,s,l=1,c=!1,u=!1,p=t,f=0,d=!1;if(124===(a=e.input.charCodeAt(e.position)))o=!1;else{if(62!==a)return!1;o=!0}for(e.kind="scalar",e.result="";0!==a;)if(43===(a=e.input.charCodeAt(++e.position))||45===a)1===l?l=43===a?3:2:T(e,"repeat of a chomping mode identifier");else{if(!((i=48<=(s=a)&&s<=57?s-48:-1)>=0))break;0===i?T(e,"bad explicit indentation width of a block scalar; it cannot be less than one"):u?T(e,"repeat of an indentation width identifier"):(p=t+i-1,u=!0)}if(g(a)){do{a=e.input.charCodeAt(++e.position)}while(g(a));if(35===a)do{a=e.input.charCodeAt(++e.position)}while(!m(a)&&0!==a)}for(;0!==a;){for(R(e),e.lineIndent=0,a=e.input.charCodeAt(e.position);(!u||e.lineIndent<p)&&32===a;)e.lineIndent++,a=e.input.charCodeAt(++e.position);if(!u&&e.lineIndent>p&&(p=e.lineIndent),m(a))f++;else{if(e.lineIndent<p){3===l?e.result+=r.repeat("\n",c?1+f:f):1===l&&c&&(e.result+="\n");break}for(o?g(a)?(d=!0,e.result+=r.repeat("\n",c?1+f:f)):d?(d=!1,e.result+=r.repeat("\n",f+1)):0===f?c&&(e.result+=" "):e.result+=r.repeat("\n",f):e.result+=r.repeat("\n",c?1+f:f),c=!0,u=!0,f=0,n=e.position;!m(a)&&0!==a;)a=e.input.charCodeAt(++e.position);I(e,n,e.position,!1)}}return!0}(e,d)||function(e,t){var n,r,o;if(39!==(n=e.input.charCodeAt(e.position)))return!1;for(e.kind="scalar",e.result="",e.position++,r=o=e.position;0!==(n=e.input.charCodeAt(e.position));)if(39===n){if(I(e,r,e.position,!0),39!==(n=e.input.charCodeAt(++e.position)))return!0;r=e.position,e.position++,o=e.position}else m(n)?(I(e,r,o,!0),M(e,N(e,!1,t)),r=o=e.position):e.position===e.lineStart&&L(e)?T(e,"unexpected end of the document within a single quoted scalar"):(e.position++,o=e.position);T(e,"unexpected end of the stream within a single quoted scalar")}(e,d)||function(e,t){var n,r,o,i,a,s,l;if(34!==(s=e.input.charCodeAt(e.position)))return!1;for(e.kind="scalar",e.result="",e.position++,n=r=e.position;0!==(s=e.input.charCodeAt(e.position));){if(34===s)return I(e,n,e.position,!0),e.position++,!0;if(92===s){if(I(e,n,e.position,!0),m(s=e.input.charCodeAt(++e.position)))N(e,!1,t);else if(s<256&&k[s])e.result+=O[s],e.position++;else if((a=120===(l=s)?2:117===l?4:85===l?8:0)>0){for(o=a,i=0;o>0;o--)(a=b(s=e.input.charCodeAt(++e.position)))>=0?i=(i<<4)+a:T(e,"expected hexadecimal character");e.result+=w(i),e.position++}else T(e,"unknown escape sequence");n=r=e.position}else m(s)?(I(e,n,r,!0),M(e,N(e,!1,t)),n=r=e.position):e.position===e.lineStart&&L(e)?T(e,"unexpected end of the document within a double quoted scalar"):(e.position++,r=e.position)}T(e,"unexpected end of the stream within a double quoted scalar")}(e,d)?E=!0:!function(e){var t,n,r;if(42!==(r=e.input.charCodeAt(e.position)))return!1;for(r=e.input.charCodeAt(++e.position),t=e.position;0!==r&&!y(r)&&!v(r);)r=e.input.charCodeAt(++e.position);return e.position===t&&T(e,"name of an alias node must contain at least one character"),n=e.input.slice(t,e.position),e.anchorMap.hasOwnProperty(n)||T(e,'unidentified alias "'+n+'"'),e.result=e.anchorMap[n],N(e,!0,-1),!0}(e)?function(e,t,n){var r,o,i,a,s,l,c,u,p=e.kind,f=e.result;if(y(u=e.input.charCodeAt(e.position))||v(u)||35===u||38===u||42===u||33===u||124===u||62===u||39===u||34===u||37===u||64===u||96===u)return!1;if((63===u||45===u)&&(y(r=e.input.charCodeAt(e.position+1))||n&&v(r)))return!1;for(e.kind="scalar",e.result="",o=i=e.position,a=!1;0!==u;){if(58===u){if(y(r=e.input.charCodeAt(e.position+1))||n&&v(r))break}else if(35===u){if(y(e.input.charCodeAt(e.position-1)))break}else{if(e.position===e.lineStart&&L(e)||n&&v(u))break;if(m(u)){if(s=e.line,l=e.lineStart,c=e.lineIndent,N(e,!1,-1),e.lineIndent>=t){a=!0,u=e.input.charCodeAt(e.position);continue}e.position=i,e.line=s,e.lineStart=l,e.lineIndent=c;break}}a&&(I(e,o,i,!1),M(e,e.line-s),o=i=e.position,a=!1),g(u)||(i=e.position+1),u=e.input.charCodeAt(++e.position)}return I(e,o,i,!1),!!e.result||(e.kind=p,e.result=f,!1)}(e,d,1===n)&&(E=!0,null===e.tag&&(e.tag="?")):(E=!0,null===e.tag&&null===e.anchor||T(e,"alias node should not have any properties")),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):0===x&&(E=c&&D(e,h))),null!==e.tag&&"!"!==e.tag)if("?"===e.tag){for(u=0,p=e.implicitTypes.length;u<p;u+=1)if((f=e.implicitTypes[u]).resolve(e.result)){e.result=f.construct(e.result),e.tag=f.tag,null!==e.anchor&&(e.anchorMap[e.anchor]=e.result);break}}else l.call(e.typeMap[e.kind||"fallback"],e.tag)?(f=e.typeMap[e.kind||"fallback"][e.tag],null!==e.result&&f.kind!==e.kind&&T(e,"unacceptable node kind for !<"+e.tag+'> tag; it should be "'+f.kind+'", not "'+e.kind+'"'),f.resolve(e.result)?(e.result=f.construct(e.result),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):T(e,"cannot resolve a node with !<"+e.tag+"> explicit tag")):T(e,"unknown tag !<"+e.tag+">");return null!==e.listener&&e.listener("close",e),null!==e.tag||null!==e.anchor||E}function B(e){var t,n,r,o,i=e.position,a=!1;for(e.version=null,e.checkLineBreaks=e.legacy,e.tagMap={},e.anchorMap={};0!==(o=e.input.charCodeAt(e.position))&&(N(e,!0,-1),o=e.input.charCodeAt(e.position),!(e.lineIndent>0||37!==o));){for(a=!0,o=e.input.charCodeAt(++e.position),t=e.position;0!==o&&!y(o);)o=e.input.charCodeAt(++e.position);for(r=[],(n=e.input.slice(t,e.position)).length<1&&T(e,"directive name must not be less than one character in length");0!==o;){for(;g(o);)o=e.input.charCodeAt(++e.position);if(35===o){do{o=e.input.charCodeAt(++e.position)}while(0!==o&&!m(o));break}if(m(o))break;for(t=e.position;0!==o&&!y(o);)o=e.input.charCodeAt(++e.position);r.push(e.input.slice(t,e.position))}0!==o&&R(e),l.call(C,n)?C[n](e,n,r):j(e,'unknown document directive "'+n+'"')}N(e,!0,-1),0===e.lineIndent&&45===e.input.charCodeAt(e.position)&&45===e.input.charCodeAt(e.position+1)&&45===e.input.charCodeAt(e.position+2)?(e.position+=3,N(e,!0,-1)):a&&T(e,"directives end mark is expected"),U(e,e.lineIndent-1,4,!1,!0),N(e,!0,-1),e.checkLineBreaks&&u.test(e.input.slice(i,e.position))&&j(e,"non-ASCII line breaks are interpreted as content"),e.documents.push(e.result),e.position===e.lineStart&&L(e)?46===e.input.charCodeAt(e.position)&&(e.position+=3,N(e,!0,-1)):e.position<e.length-1&&T(e,"end of the stream or a document separator is expected")}function $(e,t){t=t||{},0!==(e=String(e)).length&&(10!==e.charCodeAt(e.length-1)&&13!==e.charCodeAt(e.length-1)&&(e+="\n"),65279===e.charCodeAt(0)&&(e=e.slice(1)));var n=new E(e,t);for(n.input+="\0";32===n.input.charCodeAt(n.position);)n.lineIndent+=1,n.position+=1;for(;n.position<n.length-1;)B(n);return n.documents}function q(e,t,n){var r,o,i=$(e,n);if("function"!=typeof t)return i;for(r=0,o=i.length;r<o;r+=1)t(i[r])}function W(e,t){var n=$(e,t);if(0!==n.length){if(1===n.length)return n[0];throw new o("expected a single document in the stream, but found more")}}e.exports.loadAll=q,e.exports.load=W,e.exports.safeLoadAll=function(e,t,n){if("function"!=typeof t)return q(e,r.extend({schema:a},n));q(e,t,r.extend({schema:a},n))},e.exports.safeLoad=function(e,t){return W(e,r.extend({schema:a},t))}},function(e,t,n){"use strict";var r=n(49);function o(e,t,n,r,o){this.name=e,this.buffer=t,this.position=n,this.line=r,this.column=o}o.prototype.getSnippet=function(e,t){var n,o,i,a,s;if(!this.buffer)return null;for(e=e||4,t=t||75,n="",o=this.position;o>0&&-1==="\0\r\n…\u2028\u2029".indexOf(this.buffer.charAt(o-1));)if(o-=1,this.position-o>t/2-1){n=" ... ",o+=5;break}for(i="",a=this.position;a<this.buffer.length&&-1==="\0\r\n…\u2028\u2029".indexOf(this.buffer.charAt(a));)if((a+=1)-this.position>t/2-1){i=" ... ",a-=5;break}return s=this.buffer.slice(o,a),r.repeat(" ",e)+n+s+i+"\n"+r.repeat(" ",e+this.position-o+n.length)+"^"},o.prototype.toString=function(e){var t,n="";return this.name&&(n+='in "'+this.name+'" '),n+="at line "+(this.line+1)+", column "+(this.column+1),e||(t=this.getSnippet())&&(n+=":\n"+t),n},e.exports=o},function(e,t,n){"use strict";var r=n(6);e.exports=new r("tag:yaml.org,2002:str",{kind:"scalar",construct:function(e){return null!==e?e:""}})},function(e,t,n){"use strict";var r=n(6);e.exports=new r("tag:yaml.org,2002:seq",{kind:"sequence",construct:function(e){return null!==e?e:[]}})},function(e,t,n){"use strict";var r=n(6);e.exports=new r("tag:yaml.org,2002:map",{kind:"mapping",construct:function(e){return null!==e?e:{}}})},function(e,t,n){"use strict";var r=n(6);e.exports=new r("tag:yaml.org,2002:null",{kind:"scalar",resolve:function(e){if(null===e)return!0;var t=e.length;return 1===t&&"~"===e||4===t&&("null"===e||"Null"===e||"NULL"===e)},construct:function(){return null},predicate:function(e){return null===e},represent:{canonical:function(){return"~"},lowercase:function(){return"null"},uppercase:function(){return"NULL"},camelcase:function(){return"Null"}},defaultStyle:"lowercase"})},function(e,t,n){"use strict";var r=n(6);e.exports=new r("tag:yaml.org,2002:bool",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t=e.length;return 4===t&&("true"===e||"True"===e||"TRUE"===e)||5===t&&("false"===e||"False"===e||"FALSE"===e)},construct:function(e){return"true"===e||"True"===e||"TRUE"===e},predicate:function(e){return"[object Boolean]"===Object.prototype.toString.call(e)},represent:{lowercase:function(e){return e?"true":"false"},uppercase:function(e){return e?"TRUE":"FALSE"},camelcase:function(e){return e?"True":"False"}},defaultStyle:"lowercase"})},function(e,t,n){"use strict";var r=n(49),o=n(6);function i(e){return 48<=e&&e<=55}function a(e){return 48<=e&&e<=57}e.exports=new o("tag:yaml.org,2002:int",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t,n,r=e.length,o=0,s=!1;if(!r)return!1;if("-"!==(t=e[o])&&"+"!==t||(t=e[++o]),"0"===t){if(o+1===r)return!0;if("b"===(t=e[++o])){for(o++;o<r;o++)if("_"!==(t=e[o])){if("0"!==t&&"1"!==t)return!1;s=!0}return s&&"_"!==t}if("x"===t){for(o++;o<r;o++)if("_"!==(t=e[o])){if(!(48<=(n=e.charCodeAt(o))&&n<=57||65<=n&&n<=70||97<=n&&n<=102))return!1;s=!0}return s&&"_"!==t}for(;o<r;o++)if("_"!==(t=e[o])){if(!i(e.charCodeAt(o)))return!1;s=!0}return s&&"_"!==t}if("_"===t)return!1;for(;o<r;o++)if("_"!==(t=e[o])){if(":"===t)break;if(!a(e.charCodeAt(o)))return!1;s=!0}return!(!s||"_"===t)&&(":"!==t||/^(:[0-5]?[0-9])+$/.test(e.slice(o)))},construct:function(e){var t,n,r=e,o=1,i=[];return-1!==r.indexOf("_")&&(r=r.replace(/_/g,"")),"-"!==(t=r[0])&&"+"!==t||("-"===t&&(o=-1),t=(r=r.slice(1))[0]),"0"===r?0:"0"===t?"b"===r[1]?o*parseInt(r.slice(2),2):"x"===r[1]?o*parseInt(r,16):o*parseInt(r,8):-1!==r.indexOf(":")?(r.split(":").forEach((function(e){i.unshift(parseInt(e,10))})),r=0,n=1,i.forEach((function(e){r+=e*n,n*=60})),o*r):o*parseInt(r,10)},predicate:function(e){return"[object Number]"===Object.prototype.toString.call(e)&&e%1==0&&!r.isNegativeZero(e)},represent:{binary:function(e){return e>=0?"0b"+e.toString(2):"-0b"+e.toString(2).slice(1)},octal:function(e){return e>=0?"0"+e.toString(8):"-0"+e.toString(8).slice(1)},decimal:function(e){return e.toString(10)},hexadecimal:function(e){return e>=0?"0x"+e.toString(16).toUpperCase():"-0x"+e.toString(16).toUpperCase().slice(1)}},defaultStyle:"decimal",styleAliases:{binary:[2,"bin"],octal:[8,"oct"],decimal:[10,"dec"],hexadecimal:[16,"hex"]}})},function(e,t,n){"use strict";var r=n(49),o=n(6),i=new RegExp("^(?:[-+]?(?:0|[1-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?|[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\\.[0-9_]*|[-+]?\\.(?:inf|Inf|INF)|\\.(?:nan|NaN|NAN))$");var a=/^[-+]?[0-9]+e/;e.exports=new o("tag:yaml.org,2002:float",{kind:"scalar",resolve:function(e){return null!==e&&!(!i.test(e)||"_"===e[e.length-1])},construct:function(e){var t,n,r,o;return n="-"===(t=e.replace(/_/g,"").toLowerCase())[0]?-1:1,o=[],"+-".indexOf(t[0])>=0&&(t=t.slice(1)),".inf"===t?1===n?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY:".nan"===t?NaN:t.indexOf(":")>=0?(t.split(":").forEach((function(e){o.unshift(parseFloat(e,10))})),t=0,r=1,o.forEach((function(e){t+=e*r,r*=60})),n*t):n*parseFloat(t,10)},predicate:function(e){return"[object Number]"===Object.prototype.toString.call(e)&&(e%1!=0||r.isNegativeZero(e))},represent:function(e,t){var n;if(isNaN(e))switch(t){case"lowercase":return".nan";case"uppercase":return".NAN";case"camelcase":return".NaN"}else if(Number.POSITIVE_INFINITY===e)switch(t){case"lowercase":return".inf";case"uppercase":return".INF";case"camelcase":return".Inf"}else if(Number.NEGATIVE_INFINITY===e)switch(t){case"lowercase":return"-.inf";case"uppercase":return"-.INF";case"camelcase":return"-.Inf"}else if(r.isNegativeZero(e))return"-0.0";return n=e.toString(10),a.test(n)?n.replace("e",".e"):n},defaultStyle:"lowercase"})},function(e,t,n){"use strict";var r=n(6),o=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$"),i=new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:[Tt]|[ \\t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\.([0-9]*))?(?:[ \\t]*(Z|([-+])([0-9][0-9]?)(?::([0-9][0-9]))?))?$");e.exports=new r("tag:yaml.org,2002:timestamp",{kind:"scalar",resolve:function(e){return null!==e&&(null!==o.exec(e)||null!==i.exec(e))},construct:function(e){var t,n,r,a,s,l,c,u,p=0,f=null;if(null===(t=o.exec(e))&&(t=i.exec(e)),null===t)throw new Error("Date resolve error");if(n=+t[1],r=+t[2]-1,a=+t[3],!t[4])return new Date(Date.UTC(n,r,a));if(s=+t[4],l=+t[5],c=+t[6],t[7]){for(p=t[7].slice(0,3);p.length<3;)p+="0";p=+p}return t[9]&&(f=6e4*(60*+t[10]+ +(t[11]||0)),"-"===t[9]&&(f=-f)),u=new Date(Date.UTC(n,r,a,s,l,c,p)),f&&u.setTime(u.getTime()-f),u},instanceOf:Date,represent:function(e){return e.toISOString()}})},function(e,t,n){"use strict";var r=n(6);e.exports=new r("tag:yaml.org,2002:merge",{kind:"scalar",resolve:function(e){return"<<"===e||null===e}})},function(e,t,n){"use strict";var r;try{r=n(14).Buffer}catch(e){}var o=n(6),i="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n\r";e.exports=new o("tag:yaml.org,2002:binary",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t,n,r=0,o=e.length,a=i;for(n=0;n<o;n++)if(!((t=a.indexOf(e.charAt(n)))>64)){if(t<0)return!1;r+=6}return r%8==0},construct:function(e){var t,n,o=e.replace(/[\r\n=]/g,""),a=o.length,s=i,l=0,c=[];for(t=0;t<a;t++)t%4==0&&t&&(c.push(l>>16&255),c.push(l>>8&255),c.push(255&l)),l=l<<6|s.indexOf(o.charAt(t));return 0===(n=a%4*6)?(c.push(l>>16&255),c.push(l>>8&255),c.push(255&l)):18===n?(c.push(l>>10&255),c.push(l>>2&255)):12===n&&c.push(l>>4&255),r?r.from?r.from(c):new r(c):c},predicate:function(e){return r&&r.isBuffer(e)},represent:function(e){var t,n,r="",o=0,a=e.length,s=i;for(t=0;t<a;t++)t%3==0&&t&&(r+=s[o>>18&63],r+=s[o>>12&63],r+=s[o>>6&63],r+=s[63&o]),o=(o<<8)+e[t];return 0===(n=a%3)?(r+=s[o>>18&63],r+=s[o>>12&63],r+=s[o>>6&63],r+=s[63&o]):2===n?(r+=s[o>>10&63],r+=s[o>>4&63],r+=s[o<<2&63],r+=s[64]):1===n&&(r+=s[o>>2&63],r+=s[o<<4&63],r+=s[64],r+=s[64]),r}})},function(e,t,n){"use strict";var r=n(6),o=Object.prototype.hasOwnProperty,i=Object.prototype.toString;e.exports=new r("tag:yaml.org,2002:omap",{kind:"sequence",resolve:function(e){if(null===e)return!0;var t,n,r,a,s,l=[],c=e;for(t=0,n=c.length;t<n;t+=1){if(r=c[t],s=!1,"[object Object]"!==i.call(r))return!1;for(a in r)if(o.call(r,a)){if(s)return!1;s=!0}if(!s)return!1;if(-1!==l.indexOf(a))return!1;l.push(a)}return!0},construct:function(e){return null!==e?e:[]}})},function(e,t,n){"use strict";var r=n(6),o=Object.prototype.toString;e.exports=new r("tag:yaml.org,2002:pairs",{kind:"sequence",resolve:function(e){if(null===e)return!0;var t,n,r,i,a,s=e;for(a=new Array(s.length),t=0,n=s.length;t<n;t+=1){if(r=s[t],"[object Object]"!==o.call(r))return!1;if(1!==(i=Object.keys(r)).length)return!1;a[t]=[i[0],r[i[0]]]}return!0},construct:function(e){if(null===e)return[];var t,n,r,o,i,a=e;for(i=new Array(a.length),t=0,n=a.length;t<n;t+=1)r=a[t],o=Object.keys(r),i[t]=[o[0],r[o[0]]];return i}})},function(e,t,n){"use strict";var r=n(6),o=Object.prototype.hasOwnProperty;e.exports=new r("tag:yaml.org,2002:set",{kind:"mapping",resolve:function(e){if(null===e)return!0;var t,n=e;for(t in n)if(o.call(n,t)&&null!==n[t])return!1;return!0},construct:function(e){return null!==e?e:{}}})},function(e,t,n){"use strict";var r=n(6);e.exports=new r("tag:yaml.org,2002:js/undefined",{kind:"scalar",resolve:function(){return!0},construct:function(){},predicate:function(e){return void 0===e},represent:function(){return""}})},function(e,t,n){"use strict";var r=n(6);e.exports=new r("tag:yaml.org,2002:js/regexp",{kind:"scalar",resolve:function(e){if(null===e)return!1;if(0===e.length)return!1;var t=e,n=/\/([gim]*)$/.exec(e),r="";if("/"===t[0]){if(n&&(r=n[1]),r.length>3)return!1;if("/"!==t[t.length-r.length-1])return!1}return!0},construct:function(e){var t=e,n=/\/([gim]*)$/.exec(e),r="";return"/"===t[0]&&(n&&(r=n[1]),t=t.slice(1,t.length-r.length-1)),new RegExp(t,r)},predicate:function(e){return"[object RegExp]"===Object.prototype.toString.call(e)},represent:function(e){var t="/"+e.source+"/";return e.global&&(t+="g"),e.multiline&&(t+="m"),e.ignoreCase&&(t+="i"),t}})},function(e,t,n){"use strict";var r;try{r=n(263)}catch(e){"undefined"!=typeof window&&(r=window.esprima)}var o=n(6);e.exports=new o("tag:yaml.org,2002:js/function",{kind:"scalar",resolve:function(e){if(null===e)return!1;try{var t="("+e+")",n=r.parse(t,{range:!0});return"Program"===n.type&&1===n.body.length&&"ExpressionStatement"===n.body[0].type&&("ArrowFunctionExpression"===n.body[0].expression.type||"FunctionExpression"===n.body[0].expression.type)}catch(e){return!1}},construct:function(e){var t,n="("+e+")",o=r.parse(n,{range:!0}),i=[];if("Program"!==o.type||1!==o.body.length||"ExpressionStatement"!==o.body[0].type||"ArrowFunctionExpression"!==o.body[0].expression.type&&"FunctionExpression"!==o.body[0].expression.type)throw new Error("Failed to resolve function");return o.body[0].expression.params.forEach((function(e){i.push(e.name)})),t=o.body[0].expression.body.range,"BlockStatement"===o.body[0].expression.body.type?new Function(i,n.slice(t[0]+1,t[1]-1)):new Function(i,"return "+n.slice(t[0],t[1]))},predicate:function(e){return"[object Function]"===Object.prototype.toString.call(e)},represent:function(e){return e.toString()}})},function(e,n){if(void 0===t){var r=new Error("Cannot find module 'esprima'");throw r.code="MODULE_NOT_FOUND",r}e.exports=t},function(e,t){e.exports=function(){}},function(e,t){e.exports=function(e){var t=/(%?)(%([jds]))/g,n=Array.prototype.slice.call(arguments,1);return n.length&&(e=e.replace(t,(function(e,t,r,o){var i=n.shift();switch(o){case"s":i=""+i;break;case"d":i=Number(i);break;case"j":i=JSON.stringify(i)}return t?(n.unshift(i),e):i}))),n.length&&(e+=" "+n.join(" ")),""+(e=e.replace(/%{2,2}/g,"%"))}},function(e,t,n){"use strict";(function(t){var n=/\.(txt|htm|html|md|xml|js|min|map|css|scss|less|svg)$/i;e.exports={order:300,allowEmpty:!0,encoding:"utf8",canParse:function(e){return("string"==typeof e.data||t.isBuffer(e.data))&&n.test(e.url)},parse:function(e){if("string"==typeof e.data)return e.data;if(t.isBuffer(e.data))return e.data.toString(this.encoding);throw new Error("data is not text")}}}).call(this,n(14).Buffer)},function(e,t,n){"use strict";(function(t){var n=/\.(jpeg|jpg|gif|png|bmp|ico)$/i;e.exports={order:400,allowEmpty:!0,canParse:function(e){return t.isBuffer(e.data)&&n.test(e.url)},parse:function(e){return t.isBuffer(e.data)?e.data:new t(e.data)}}}).call(this,n(14).Buffer)},function(e,t,n){"use strict";var r=n(269),o=n(31),i=n(26);e.exports={order:100,canRead:function(e){return i.isFileSystemPath(e.url)},read:function(e){return new Promise((function(t,n){var a;try{a=i.toFileSystemPath(e.url)}catch(t){n(o.uri(t,"Malformed URI: %s",e.url))}try{r.readFile(a,(function(e,r){e?n(o(e,'Error opening file "%s"',a)):t(r)}))}catch(e){n(o(e,'Error opening file "%s"',a))}}))}}},function(e,t){},function(e,t,n){"use strict";(function(t,r){var o=n(91),i=n(282),a=n(31),s=n(26);e.exports={order:200,headers:null,timeout:5e3,redirects:5,withCredentials:!1,canRead:function(e){return s.isHttp(e.url)},read:function(e){var n=s.parse(e.url);return t.browser&&!n.protocol&&(n.protocol=s.parse(location.href).protocol),function e(t,n,l){return new Promise((function(c,u){t=s.parse(t),(l=l||[]).push(t.href),function(e,t){return new Promise((function(n,a){var s=("https:"===e.protocol?i:o).get({hostname:e.hostname,port:e.port,path:e.path,auth:e.auth,protocol:e.protocol,headers:t.headers||{},withCredentials:t.withCredentials});"function"==typeof s.setTimeout&&s.setTimeout(t.timeout),s.on("timeout",(function(){s.abort()})),s.on("error",a),s.once("response",(function(e){e.body=new r(0),e.on("data",(function(t){e.body=r.concat([e.body,new r(t)])})),e.on("error",a),e.on("end",(function(){n(e)}))}))}))}(t,n).then((function(o){if(o.statusCode>=400)throw a({status:o.statusCode},"HTTP ERROR %d",o.statusCode);if(o.statusCode>=300)if(l.length>n.redirects)u(a({status:o.statusCode},"Error downloading %s. \nToo many redirects: \n %s",l[0],l.join(" \n ")));else{if(!o.headers.location)throw a({status:o.statusCode},"HTTP %d redirect with no location header",o.statusCode);var i=s.resolve(t,o.headers.location);e(i,n,l).then(c,u)}else c(o.body||new r(0))})).catch((function(e){u(a(e,"Error downloading",t.href))}))}))}(n,this)}}}).call(this,n(13),n(14).Buffer)},function(e,t,n){(function(t,r,o){var i=n(136),a=n(32),s=n(137),l=n(138),c=n(279),u=s.IncomingMessage,p=s.readyStates;var f=e.exports=function(e){var n,r=this;l.Writable.call(r),r._opts=e,r._body=[],r._headers={},e.auth&&r.setHeader("Authorization","Basic "+new t(e.auth).toString("base64")),Object.keys(e.headers).forEach((function(t){r.setHeader(t,e.headers[t])}));var o=!0;if("disable-fetch"===e.mode||"requestTimeout"in e&&!i.abortController)o=!1,n=!0;else if("prefer-streaming"===e.mode)n=!1;else if("allow-wrong-content-type"===e.mode)n=!i.overrideMimeType;else{if(e.mode&&"default"!==e.mode&&"prefer-fast"!==e.mode)throw new Error("Invalid value for opts.mode");n=!0}r._mode=function(e,t){return i.fetch&&t?"fetch":i.mozchunkedarraybuffer?"moz-chunked-arraybuffer":i.msstream?"ms-stream":i.arraybuffer&&e?"arraybuffer":i.vbArray&&e?"text:vbarray":"text"}(n,o),r._fetchTimer=null,r.on("finish",(function(){r._onFinish()}))};a(f,l.Writable),f.prototype.setHeader=function(e,t){var n=e.toLowerCase();-1===d.indexOf(n)&&(this._headers[n]={name:e,value:t})},f.prototype.getHeader=function(e){var t=this._headers[e.toLowerCase()];return t?t.value:null},f.prototype.removeHeader=function(e){delete this._headers[e.toLowerCase()]},f.prototype._onFinish=function(){var e=this;if(!e._destroyed){var n=e._opts,a=e._headers,s=null;"GET"!==n.method&&"HEAD"!==n.method&&(s=i.arraybuffer?c(t.concat(e._body)):i.blobConstructor?new r.Blob(e._body.map((function(e){return c(e)})),{type:(a["content-type"]||{}).value||""}):t.concat(e._body).toString());var l=[];if(Object.keys(a).forEach((function(e){var t=a[e].name,n=a[e].value;Array.isArray(n)?n.forEach((function(e){l.push([t,e])})):l.push([t,n])})),"fetch"===e._mode){var u=null;if(i.abortController){var f=new AbortController;u=f.signal,e._fetchAbortController=f,"requestTimeout"in n&&0!==n.requestTimeout&&(e._fetchTimer=r.setTimeout((function(){e.emit("requestTimeout"),e._fetchAbortController&&e._fetchAbortController.abort()}),n.requestTimeout))}r.fetch(e._opts.url,{method:e._opts.method,headers:l,body:s||void 0,mode:"cors",credentials:n.withCredentials?"include":"same-origin",signal:u}).then((function(t){e._fetchResponse=t,e._connect()}),(function(t){r.clearTimeout(e._fetchTimer),e._destroyed||e.emit("error",t)}))}else{var d=e._xhr=new r.XMLHttpRequest;try{d.open(e._opts.method,e._opts.url,!0)}catch(t){return void o.nextTick((function(){e.emit("error",t)}))}"responseType"in d&&(d.responseType=e._mode.split(":")[0]),"withCredentials"in d&&(d.withCredentials=!!n.withCredentials),"text"===e._mode&&"overrideMimeType"in d&&d.overrideMimeType("text/plain; charset=x-user-defined"),"requestTimeout"in n&&(d.timeout=n.requestTimeout,d.ontimeout=function(){e.emit("requestTimeout")}),l.forEach((function(e){d.setRequestHeader(e[0],e[1])})),e._response=null,d.onreadystatechange=function(){switch(d.readyState){case p.LOADING:case p.DONE:e._onXHRProgress()}},"moz-chunked-arraybuffer"===e._mode&&(d.onprogress=function(){e._onXHRProgress()}),d.onerror=function(){e._destroyed||e.emit("error",new Error("XHR error"))};try{d.send(s)}catch(t){return void o.nextTick((function(){e.emit("error",t)}))}}}},f.prototype._onXHRProgress=function(){(function(e){try{var t=e.status;return null!==t&&0!==t}catch(e){return!1}})(this._xhr)&&!this._destroyed&&(this._response||this._connect(),this._response._onXHRProgress())},f.prototype._connect=function(){var e=this;e._destroyed||(e._response=new u(e._xhr,e._fetchResponse,e._mode,e._fetchTimer),e._response.on("error",(function(t){e.emit("error",t)})),e.emit("response",e._response))},f.prototype._write=function(e,t,n){this._body.push(e),n()},f.prototype.abort=f.prototype.destroy=function(){this._destroyed=!0,r.clearTimeout(this._fetchTimer),this._response&&(this._response._destroyed=!0),this._xhr?this._xhr.abort():this._fetchAbortController&&this._fetchAbortController.abort()},f.prototype.end=function(e,t,n){"function"==typeof e&&(n=e,e=void 0),l.Writable.prototype.end.call(this,e,t,n)},f.prototype.flushHeaders=function(){},f.prototype.setTimeout=function(){},f.prototype.setNoDelay=function(){},f.prototype.setSocketKeepAlive=function(){};var d=["accept-charset","accept-encoding","access-control-request-headers","access-control-request-method","connection","content-length","cookie","cookie2","date","dnt","expect","host","keep-alive","origin","referer","te","trailer","transfer-encoding","upgrade","via"]}).call(this,n(14).Buffer,n(7),n(13))},function(e,t){},function(e,t,n){"use strict";var r=n(62).Buffer,o=n(274);e.exports=function(){function e(){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.head=null,this.tail=null,this.length=0}return e.prototype.push=function(e){var t={data:e,next:null};this.length>0?this.tail.next=t:this.head=t,this.tail=t,++this.length},e.prototype.unshift=function(e){var t={data:e,next:this.head};0===this.length&&(this.tail=t),this.head=t,++this.length},e.prototype.shift=function(){if(0!==this.length){var e=this.head.data;return 1===this.length?this.head=this.tail=null:this.head=this.head.next,--this.length,e}},e.prototype.clear=function(){this.head=this.tail=null,this.length=0},e.prototype.join=function(e){if(0===this.length)return"";for(var t=this.head,n=""+t.data;t=t.next;)n+=e+t.data;return n},e.prototype.concat=function(e){if(0===this.length)return r.alloc(0);if(1===this.length)return this.head.data;for(var t,n,o,i=r.allocUnsafe(e>>>0),a=this.head,s=0;a;)t=a.data,n=i,o=s,t.copy(n,o),s+=a.data.length,a=a.next;return i},e}(),o&&o.inspect&&o.inspect.custom&&(e.exports.prototype[o.inspect.custom]=function(){var e=o.inspect({length:this.length});return this.constructor.name+" "+e})},function(e,t){},function(e,t,n){(function(e){var r=void 0!==e&&e||"undefined"!=typeof self&&self||window,o=Function.prototype.apply;function i(e,t){this._id=e,this._clearFn=t}t.setTimeout=function(){return new i(o.call(setTimeout,r,arguments),clearTimeout)},t.setInterval=function(){return new i(o.call(setInterval,r,arguments),clearInterval)},t.clearTimeout=t.clearInterval=function(e){e&&e.close()},i.prototype.unref=i.prototype.ref=function(){},i.prototype.close=function(){this._clearFn.call(r,this._id)},t.enroll=function(e,t){clearTimeout(e._idleTimeoutId),e._idleTimeout=t},t.unenroll=function(e){clearTimeout(e._idleTimeoutId),e._idleTimeout=-1},t._unrefActive=t.active=function(e){clearTimeout(e._idleTimeoutId);var t=e._idleTimeout;t>=0&&(e._idleTimeoutId=setTimeout((function(){e._onTimeout&&e._onTimeout()}),t))},n(276),t.setImmediate="undefined"!=typeof self&&self.setImmediate||void 0!==e&&e.setImmediate||this&&this.setImmediate,t.clearImmediate="undefined"!=typeof self&&self.clearImmediate||void 0!==e&&e.clearImmediate||this&&this.clearImmediate}).call(this,n(7))},function(e,t,n){(function(e,t){!function(e,n){"use strict";if(!e.setImmediate){var r,o,i,a,s,l=1,c={},u=!1,p=e.document,f=Object.getPrototypeOf&&Object.getPrototypeOf(e);f=f&&f.setTimeout?f:e,"[object process]"==={}.toString.call(e.process)?r=function(e){t.nextTick((function(){h(e)}))}:!function(){if(e.postMessage&&!e.importScripts){var t=!0,n=e.onmessage;return e.onmessage=function(){t=!1},e.postMessage("","*"),e.onmessage=n,t}}()?e.MessageChannel?((i=new MessageChannel).port1.onmessage=function(e){h(e.data)},r=function(e){i.port2.postMessage(e)}):p&&"onreadystatechange"in p.createElement("script")?(o=p.documentElement,r=function(e){var t=p.createElement("script");t.onreadystatechange=function(){h(e),t.onreadystatechange=null,o.removeChild(t),t=null},o.appendChild(t)}):r=function(e){setTimeout(h,0,e)}:(a="setImmediate$"+Math.random()+"$",s=function(t){t.source===e&&"string"==typeof t.data&&0===t.data.indexOf(a)&&h(+t.data.slice(a.length))},e.addEventListener?e.addEventListener("message",s,!1):e.attachEvent("onmessage",s),r=function(t){e.postMessage(a+t,"*")}),f.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),n=0;n<t.length;n++)t[n]=arguments[n+1];var o={callback:e,args:t};return c[l]=o,r(l),l++},f.clearImmediate=d}function d(e){delete c[e]}function h(e){if(u)setTimeout(h,0,e);else{var t=c[e];if(t){u=!0;try{!function(e){var t=e.callback,n=e.args;switch(n.length){case 0:t();break;case 1:t(n[0]);break;case 2:t(n[0],n[1]);break;case 3:t(n[0],n[1],n[2]);break;default:t.apply(void 0,n)}}(t)}finally{d(e),u=!1}}}}}("undefined"==typeof self?void 0===e?this:e:self)}).call(this,n(7),n(13))},function(e,t,n){(function(t){function n(e){try{if(!t.localStorage)return!1}catch(e){return!1}var n=t.localStorage[e];return null!=n&&"true"===String(n).toLowerCase()}e.exports=function(e,t){if(n("noDeprecation"))return e;var r=!1;return function(){if(!r){if(n("throwDeprecation"))throw new Error(t);n("traceDeprecation")?console.trace(t):console.warn(t),r=!0}return e.apply(this,arguments)}}}).call(this,n(7))},function(e,t,n){"use strict";e.exports=i;var r=n(145),o=Object.create(n(50));function i(e){if(!(this instanceof i))return new i(e);r.call(this,e)}o.inherits=n(32),o.inherits(i,r),i.prototype._transform=function(e,t,n){n(null,e)}},function(e,t,n){var r=n(14).Buffer;e.exports=function(e){if(e instanceof Uint8Array){if(0===e.byteOffset&&e.byteLength===e.buffer.byteLength)return e.buffer;if("function"==typeof e.buffer.slice)return e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength)}if(r.isBuffer(e)){for(var t=new Uint8Array(e.length),n=e.length,o=0;o<n;o++)t[o]=e[o];return t.buffer}throw new Error("Argument must be a Buffer")}},function(e,t){e.exports=function(){for(var e={},t=0;t<arguments.length;t++){var r=arguments[t];for(var o in r)n.call(r,o)&&(e[o]=r[o])}return e};var n=Object.prototype.hasOwnProperty},function(e,t){e.exports={100:"Continue",101:"Switching Protocols",102:"Processing",200:"OK",201:"Created",202:"Accepted",203:"Non-Authoritative Information",204:"No Content",205:"Reset Content",206:"Partial Content",207:"Multi-Status",208:"Already Reported",226:"IM Used",300:"Multiple Choices",301:"Moved Permanently",302:"Found",303:"See Other",304:"Not Modified",305:"Use Proxy",307:"Temporary Redirect",308:"Permanent Redirect",400:"Bad Request",401:"Unauthorized",402:"Payment Required",403:"Forbidden",404:"Not Found",405:"Method Not Allowed",406:"Not Acceptable",407:"Proxy Authentication Required",408:"Request Timeout",409:"Conflict",410:"Gone",411:"Length Required",412:"Precondition Failed",413:"Payload Too Large",414:"URI Too Long",415:"Unsupported Media Type",416:"Range Not Satisfiable",417:"Expectation Failed",418:"I'm a teapot",421:"Misdirected Request",422:"Unprocessable Entity",423:"Locked",424:"Failed Dependency",425:"Unordered Collection",426:"Upgrade Required",428:"Precondition Required",429:"Too Many Requests",431:"Request Header Fields Too Large",451:"Unavailable For Legal Reasons",500:"Internal Server Error",501:"Not Implemented",502:"Bad Gateway",503:"Service Unavailable",504:"Gateway Timeout",505:"HTTP Version Not Supported",506:"Variant Also Negotiates",507:"Insufficient Storage",508:"Loop Detected",509:"Bandwidth Limit Exceeded",510:"Not Extended",511:"Network Authentication Required"}},function(e,t,n){var r=n(91),o=n(10),i=e.exports;for(var a in r)r.hasOwnProperty(a)&&(i[a]=r[a]);function s(e){if("string"==typeof e&&(e=o.parse(e)),e.protocol||(e.protocol="https:"),"https:"!==e.protocol)throw new Error('Protocol "'+e.protocol+'" not supported. Expected "https:"');return e}i.request=function(e,t){return e=s(e),r.request.call(this,e,t)},i.get=function(e,t){return e=s(e),r.get.call(this,e,t)}},function(e,t,n){"use strict";var r=n(31),o=n(63),i=n(26);function a(){this.circular=!1,this._$refs={},this._root$Ref=null}function s(e,t){var n=Object.keys(e);return(t=Array.isArray(t[0])?t[0]:Array.prototype.slice.call(t)).length>0&&t[0]&&(n=n.filter((function(n){return-1!==t.indexOf(e[n].pathType)}))),n.map((function(t){return{encoded:t,decoded:"file"===e[t].pathType?i.toFileSystemPath(t,!0):t}}))}e.exports=a,a.prototype.paths=function(e){var t=s(this._$refs,arguments);return t.map((function(e){return e.decoded}))},a.prototype.values=function(e){var t=this._$refs,n=s(t,arguments);return n.reduce((function(e,n){return e[n.decoded]=t[n.encoded].value,e}),{})},a.prototype.toJSON=a.prototype.values,a.prototype.exists=function(e,t){try{return this._resolve(e,t),!0}catch(e){return!1}},a.prototype.get=function(e,t){return this._resolve(e,t).value},a.prototype.set=function(e,t){var n=i.resolve(this._root$Ref.path,e),o=i.stripHash(n),a=this._$refs[o];if(!a)throw r('Error resolving $ref pointer "%s". \n"%s" not found.',e,o);a.set(n,t)},a.prototype._add=function(e){var t=i.stripHash(e),n=new o;return n.path=t,n.$refs=this,this._$refs[t]=n,this._root$Ref=this._root$Ref||n,n},a.prototype._resolve=function(e,t){var n=i.resolve(this._root$Ref.path,e),o=i.stripHash(n),a=this._$refs[o];if(!a)throw r('Error resolving $ref pointer "%s". \n"%s" not found.',e,o);return a.resolve(n,t,e)},a.prototype._get$Ref=function(e){e=i.resolve(this._root$Ref.path,e);var t=i.stripHash(e);return this._$refs[t]}},function(e,t,n){"use strict";function r(e,t,n,r){var o=e[t];if("function"==typeof o)return o.apply(e,[n,r]);if(!r){if(o instanceof RegExp)return o.test(n.url);if("string"==typeof o)return o===n.extension;if(Array.isArray(o))return-1!==o.indexOf(n.extension)}return o}t.all=function(e){return Object.keys(e).filter((function(t){return"object"==typeof e[t]})).map((function(t){return e[t].name=t,e[t]}))},t.filter=function(e,t,n){return e.filter((function(e){return!!r(e,t,n)}))},t.sort=function(e){return e.forEach((function(e){e.order=e.order||Number.MAX_SAFE_INTEGER})),e.sort((function(e,t){return e.order-t.order}))},t.run=function(e,t,n){var o,i,a=0;return new Promise((function(s,l){function c(){if(!(o=e[a++]))return l(i);try{var s=r(o,t,n,u);s&&"function"==typeof s.then?s.then(p,f):void 0!==s&&p(s)}catch(e){f(e)}}function u(e,t){e?f(e):p(t)}function p(e){s({plugin:o,result:e})}function f(e){i=e,c()}c()}))}},function(e,t,n){"use strict";var r=n(132);e.exports=function(e){var t,n,o,i;"function"==typeof(e=Array.prototype.slice.call(e))[e.length-1]&&(i=e.pop());"string"==typeof e[0]?(t=e[0],"object"==typeof e[2]?(n=e[1],o=e[2]):(n=void 0,o=e[1])):(t="",n=e[0],o=e[1]);o instanceof r||(o=new r(o));return{path:t,schema:n,options:o,callback:i}}},function(e,t,n){"use strict";var r=n(63),o=n(92),i=n(146),a=n(26);function s(e,t,n,i){var a=[];return e&&"object"==typeof e&&(r.isExternal$Ref(e)?a.push(l(e,t,n,i)):Object.keys(e).forEach((function(c){var u=o.join(t,c),p=e[c];r.isExternal$Ref(p)?a.push(l(p,u,n,i)):a=a.concat(s(p,u,n,i))}))),a}function l(e,t,n,r){var o=a.resolve(t,e.$ref),l=a.stripHash(o);return(e=n._$refs[l])?Promise.resolve(e.value):i(o,n,r).then((function(e){var t=s(e,l+"#",n,r);return Promise.all(t)}))}e.exports=function(e,t){if(!t.resolve.external)return Promise.resolve();try{var n=s(e.schema,e.$refs._root$Ref.path+"#",e.$refs,t);return Promise.all(n)}catch(e){return Promise.reject(e)}}},function(e,t,n){"use strict";var r=n(63),o=n(92),i=n(26);function a(e,t,n,i,l,c,u,p){var f=null===t?e:e[t];f&&"object"==typeof f&&(r.isAllowed$Ref(f)?s(e,t,n,i,l,c,u,p):Object.keys(f).sort((function(e,t){return"definitions"===e?-1:"definitions"===t?1:e.length-t.length})).forEach((function(e){var t=o.join(n,e),d=o.join(i,e),h=f[e];r.isAllowed$Ref(h)?s(f,e,n,d,l,c,u,p):a(f,e,t,d,l,c,u,p)})))}function s(e,t,n,s,l,c,u,p){var f=null===t?e:e[t],d=i.resolve(n,f.$ref),h=u._resolve(d,p),m=o.parse(s).length,g=i.stripHash(h.path),y=i.getHash(h.path),v=g!==u._root$Ref.path,b=r.isExtended$Ref(f);l+=h.indirections;var x=function(e,t,n){for(var r=0;r<e.length;r++){var o=e[r];if(o.parent===t&&o.key===n)return o}}(c,e,t);if(x){if(!(m<x.depth||l<x.indirections))return;!function(e,t){var n=e.indexOf(t);e.splice(n,1)}(c,x)}c.push({$ref:f,parent:e,key:t,pathFromRoot:s,depth:m,file:g,hash:y,value:h.value,circular:h.circular,extended:b,external:v,indirections:l}),a(h.value,null,h.path,s,l+1,c,u,p)}e.exports=function(e,t){var n=[];a(e,"schema",e.$refs._root$Ref.path+"#","#",0,n,e.$refs,t),function(e){var t,n,i;e.sort((function(e,t){if(e.file!==t.file)return e.file<t.file?-1:1;if(e.hash!==t.hash)return e.hash<t.hash?-1:1;if(e.circular!==t.circular)return e.circular?-1:1;if(e.extended!==t.extended)return e.extended?1:-1;if(e.indirections!==t.indirections)return e.indirections-t.indirections;if(e.depth!==t.depth)return e.depth-t.depth;var n=e.pathFromRoot.lastIndexOf("/definitions"),r=t.pathFromRoot.lastIndexOf("/definitions");return n!==r?r-n:e.pathFromRoot.length-t.pathFromRoot.length})),e.forEach((function(e){e.external?e.file===t&&e.hash===n?e.$ref.$ref=i:e.file===t&&0===e.hash.indexOf(n+"/")?e.$ref.$ref=o.join(i,o.parse(e.hash.replace(n,"#"))):(t=e.file,n=e.hash,i=e.pathFromRoot,e.$ref=e.parent[e.key]=r.dereference(e.$ref,e.value),e.circular&&(e.$ref.$ref=e.pathFromRoot)):e.$ref.$ref=e.hash}))}(n)}},function(e,t){e.exports=function(){}},function(e,t,n){"use strict";var r=n(1),o=n(148),i=n(52),a=n(10),s=n(51),l=n(51),c=n(64).jptr,u=n(94).recurse,p=n(65).clone,f=n(290).dereference,d=n(93).isRef,h=n(149);function m(e,t,n,r,o,i){for(var s=i.externalRefs[n+r].paths[0],l=a.parse(o),f={},m=1;m;)m=0,u(e,{identityDetection:!0},(function(e,n,r){if(d(e,n))if(e[n].startsWith("#"))if(f[e[n]]||e.$fixed){if(!e.$fixed){var u=(s+"/"+f[e[n]]).split("/#/").join("/");r.parent[r.pkey]={$ref:u,"x-miro":e[n],$fixed:!0},i.verbose>1&&console.warn("Replacing with",u),m++}}else{var g=p(c(t,e[n]));if(i.verbose>1&&console.warn((!1===g?h.colour.red:h.colour.green)+"Fragment resolution",e[n],h.colour.normal),!1===g){if(r.parent[r.pkey]={},i.fatal){var y=new Error("Fragment $ref resolution failed "+e[n]);if(!i.promise)throw y;i.promise.reject(y)}}else m++,r.parent[r.pkey]=g,f[e[n]]=r.path.replace("/%24ref","")}else if(l.protocol){u=a.resolve(o,e[n]).toString();i.verbose>1&&console.warn(h.colour.yellow+"Rewriting external url ref",e[n],"as",u,h.colour.normal),e["x-miro"]=e[n],e[n]=u}else if(!e["x-miro"]){u=a.resolve(o,e[n]).toString();i.verbose>1&&console.warn(h.colour.yellow+"Rewriting external ref",e[n],"as",u,h.colour.normal),e["x-miro"]=e[n],e[n]=u}}));return u(e,{},(function(e,t,n){d(e,t)&&void 0!==e.$fixed&&delete e.$fixed})),i.verbose>1&&console.warn("Finished fragment resolution"),e}function g(e,t){if(!t.filters||!t.filters.length)return e;for(var n=0,r=t.filters;n<r.length;n++){e=(0,r[n])(e,t)}return e}function y(e,t,n,r){var u=a.parse(n.source),f=n.source.split("\\").join("/").split("/");f.pop()||f.pop();var d="",h=t.split("#");h.length>1&&(d="#"+h[1],t=h[0]),f=f.join("/");var y,v,b,x,w,k=a.parse(t),O=(y=k.protocol,v=u.protocol,y&&y.length>2?y:v&&v.length>2?v:"file:");if(b="file:"===O?i.resolve(f?f+"/":"",t):a.resolve(f?f+"/":"",t),n.cache[b]){n.verbose&&console.warn("CACHED",b,d);var _=p(n.cache[b]),E=n.externalRef=_;if(d&&!1===(E=c(E,d))&&(E={},n.fatal)){var S=new Error("Cached $ref resolution failed "+b+d);if(!n.promise)throw S;n.promise.reject(S)}return E=g(E=m(E,_,t,d,b,n),n),r(p(E),b,n),Promise.resolve(E)}return n.verbose&&console.warn("GET",b,d),n.handlers&&n.handlers[O]?n.handlers[O](f,t,d,n).then((function(e){return n.externalRef=e,e=g(e,n),n.cache[b]=e,r(e,b,n),e})).catch((function(e){throw n.verbose&&console.warn(e),e})):O&&O.startsWith("http")?s(b,{agent:n.agent}).then((function(e){if(200!==e.status)throw new Error("Received status code "+e.status);return e.text()})).then((function(e){try{var o=l.parse(e,{schema:"core",prettyErrors:!0});if(e=n.externalRef=o,n.cache[b]=p(e),d&&!1===(e=c(e,d))&&(e={},n.fatal)){var i=new Error("Remote $ref resolution failed "+b+d);if(!n.promise)throw i;n.promise.reject(i)}e=g(e=m(e,o,t,d,b,n),n)}catch(i){if(n.verbose&&console.warn(i),!n.promise||!n.fatal)throw i;n.promise.reject(i)}return r(e,b,n),e})).catch((function(e){if(n.verbose&&console.warn(e),n.cache[b]={},!n.promise||!n.fatal)throw e;n.promise.reject(e)})):(x=b,w=n.encoding||"utf8",new Promise((function(e,t){o.readFile(x,w,(function(n,r){n?t(n):e(r)}))}))).then((function(e){try{var o=l.parse(e,{schema:"core",prettyErrors:!0});if(e=n.externalRef=o,n.cache[b]=p(e),d&&!1===(e=c(e,d))&&(e={},n.fatal)){var i=new Error("File $ref resolution failed "+b+d);if(!n.promise)throw i;n.promise.reject(i)}e=g(e=m(e,o,t,d,b,n),n)}catch(i){if(n.verbose&&console.warn(i),!n.promise||!n.fatal)throw i;n.promise.reject(i)}return r(e,b,n),e})).catch((function(e){if(n.verbose&&console.warn(e),!n.promise||!n.fatal)throw e;n.promise.reject(e)}))}function v(e){return new Promise((function(t,n){(function(e){return new Promise((function(t,n){function r(t,n,r){if(t[n]&&d(t[n],"$ref")){var i=t[n].$ref;if(!i.startsWith("#")){var a="";if(!o[i]){var s=Object.keys(o).find((function(e,t,n){return i.startsWith(e+"/")}));s&&(e.verbose&&console.warn("Found potential subschema at",s),a=(a="/"+(i.split("#")[1]||"").replace(s.split("#")[1]||"")).split("/undefined").join(""),i=s)}if(o[i]||(o[i]={resolved:!1,paths:[],extras:{},description:t[n].description}),o[i].resolved)if(e.rewriteRefs){var l=o[i].resolvedAt;e.verbose>1&&console.warn("Rewriting ref",i,l),t[n]["x-miro"]=i,t[n].$ref=l+a}else t[n]=p(o[i].data);else o[i].paths.push(r.path),o[i].extras[r.path]=a}}}var o=e.externalRefs;if(e.resolver.depth>0&&e.source===e.resolver.base)return t(o);u(e.openapi.definitions,{identityDetection:!0,path:"#/definitions"},r),u(e.openapi.components,{identityDetection:!0,path:"#/components"},r),u(e.openapi,{identityDetection:!0},r),t(o)}))})(e).then((function(t){var n=function(n){if(!t[n].resolved){var o=e.resolver.depth;o>0&&o++,e.resolver.actions[o].push((function(){return y(e.openapi,n,e,(function(e,o,i){if(!t[n].resolved){var a={};a.context=t[n],a.$ref=n,a.original=p(e),a.updated=e,a.source=o,i.externals.push(a),t[n].resolved=!0}var s=Object.assign({},i,{source:"",resolver:{actions:i.resolver.actions,depth:i.resolver.actions.length-1,base:i.resolver.base}});i.patch&&t[n].description&&!e.description&&"object"==typeof e&&(e.description=t[n].description),t[n].data=e;for(var l,u=(l=t[n].paths,r.__spreadArrays(new Set(l))),f=0,d=u=u.sort((function(e,t){var n=e.startsWith("#/components/")||e.startsWith("#/definitions/"),r=t.startsWith("#/components/")||t.startsWith("#/definitions/");return n&&!r?-1:r&&!n?1:0}));f<d.length;f++){var h=d[f];if(t[n].resolvedAt&&h!==t[n].resolvedAt&&h.indexOf("x-ms-examples/")<0)i.verbose>1&&console.warn("Creating pointer to data at",h),c(i.openapi,h,{$ref:t[n].resolvedAt+t[n].extras[h],"x-miro":n+t[n].extras[h]});else{t[n].resolvedAt?i.verbose>1&&console.warn("Avoiding circular reference"):(t[n].resolvedAt=h,i.verbose>1&&console.warn("Creating initial clone of data at",h));var m=p(e);c(i.openapi,h,m)}}0===i.resolver.actions[s.resolver.depth].length&&i.resolver.actions[s.resolver.depth].push((function(){return v(s)}))}))}))}};for(var o in t)n(o)})).catch((function(t){e.verbose&&console.warn(t),n(t)}));var o={options:e};o.actions=e.resolver.actions[e.resolver.depth],t(o)}))}function b(e,t,n){e.resolver.actions.push([]),v(e).then((function(r){var o;(o=r.actions,o.reduce((function(e,t){return e.then((function(e){return t().then(Array.prototype.concat.bind(e))}))}),Promise.resolve([]))).then((function(){if(e.resolver.depth>=e.resolver.actions.length)return console.warn("Ran off the end of resolver actions"),t(!0);e.resolver.depth++,e.resolver.actions[e.resolver.depth].length?setTimeout((function(){b(r.options,t,n)}),0):(e.verbose>1&&console.warn(h.colour.yellow+"Finished external resolution!",h.colour.normal),e.resolveInternal&&(e.verbose>1&&console.warn(h.colour.yellow+"Starting internal resolution!",h.colour.normal),e.openapi=f(e.openapi,e.original,{verbose:e.verbose-1}),e.verbose>1&&console.warn(h.colour.yellow+"Finished internal resolution!",h.colour.normal)),u(e.openapi,{},(function(t,n,r){d(t,n)&&(e.preserveMiro||delete t["x-miro"])})),t(e))})).catch((function(t){e.verbose&&console.warn(t),n(t)}))})).catch((function(t){e.verbose&&console.warn(t),n(t)}))}function x(e){if(e.cache||(e.cache={}),e.source){var t=a.parse(e.source);(!t.protocol||t.protocol.length<=2)&&(e.source=i.resolve(e.source))}e.externals||(e.externals=[]),e.externalRefs||(e.externalRefs={}),e.rewriteRefs=!0,e.resolver={},e.resolver.depth=0,e.resolver.base=e.source,e.resolver.actions=[[]]}e.exports={optionalResolve:function(e){return x(e),new Promise((function(t,n){e.resolve?b(e,t,n):t(e)}))},resolve:function(e,t,n){return n||(n={}),n.openapi=e,n.source=t,n.resolve=!0,x(n),new Promise((function(e,t){b(n,e,t)}))}}},function(e,t,n){"use strict";var r=n(94).recurse,o=n(65).shallowClone,i=n(64).jptr,a=n(93).isRef;e.exports={dereference:function e(t,n,s){s||(s={}),s.cache||(s.cache={}),s.state||(s.state={}),s.state.identityDetection=!0,s.depth=s.depth?s.depth+1:1;var l=s.depth>1?t:o(t),c={data:l},u=s.depth>1?n:o(n);s.master||(s.master=l);for(var p=function(e){return e&&e.verbose?{warn:function(){var e=Array.prototype.slice.call(arguments);console.warn.apply(console,e)}}:{warn:function(){}}}(s),f=1;f>0;)f=0,r(c,s.state,(function(t,n,r){if(a(t,n)){var o,l=t[n];if(f++,s.cache[l])if((o=s.cache[l]).resolved)p.warn("Patching %s for %s",l,o.path),r.parent[r.pkey]=o.data,s.$ref&&"object"==typeof r.parent[r.pkey]&&(r.parent[r.pkey][s.$ref]=l);else{if(l===o.path)throw new Error("Tight circle at "+o.path);p.warn("Unresolved ref"),r.parent[r.pkey]=i(o.source,o.path),!1===r.parent[r.pkey]&&(r.parent[r.pkey]=i(o.source,o.key)),s.$ref&&"object"==typeof r.parent[r.pkey]&&(r.parent[s.$ref]=l)}else(o={}).path=r.path.split("/$ref")[0],o.key=l,p.warn("Dereffing %s at %s",l,o.path),o.source=u,o.data=i(o.source,o.key),!1===o.data&&(o.data=i(s.master,o.key),o.source=s.master),!1===o.data&&p.warn("Missing $ref target",o.key),s.cache[l]=o,o.data=r.parent[r.pkey]=e(i(o.source,o.key),o.source,s),s.$ref&&"object"==typeof r.parent[r.pkey]&&(r.parent[r.pkey][s.$ref]=l),o.resolved=!0}}));return c.data}}},function(e,t,n){"use strict";function r(){return{depth:0,seen:new WeakMap,top:!0,combine:!1,allowRefSiblings:!1}}e.exports={getDefaultState:r,walkSchema:function e(t,n,o,i){if(void 0===o.depth&&(o=r()),null==t)return t;if(void 0!==t.$ref){var a={$ref:t.$ref};return o.allowRefSiblings&&t.description&&(a.description=t.description),i(a,n,o),a}if(o.combine&&(t.allOf&&Array.isArray(t.allOf)&&1===t.allOf.length&&delete(t=Object.assign({},t.allOf[0],t)).allOf,t.anyOf&&Array.isArray(t.anyOf)&&1===t.anyOf.length&&delete(t=Object.assign({},t.anyOf[0],t)).anyOf,t.oneOf&&Array.isArray(t.oneOf)&&1===t.oneOf.length&&delete(t=Object.assign({},t.oneOf[0],t)).oneOf),i(t,n,o),o.seen.has(t))return t;if("object"==typeof t&&null!==t&&o.seen.set(t,!0),o.top=!1,o.depth++,void 0!==t.items&&(o.property="items",e(t.items,t,o,i)),t.additionalItems&&"object"==typeof t.additionalItems&&(o.property="additionalItems",e(t.additionalItems,t,o,i)),t.additionalProperties&&"object"==typeof t.additionalProperties&&(o.property="additionalProperties",e(t.additionalProperties,t,o,i)),t.properties)for(var s in t.properties){var l=t.properties[s];o.property="properties/"+s,e(l,t,o,i)}if(t.patternProperties)for(var s in t.patternProperties){l=t.patternProperties[s];o.property="patternProperties/"+s,e(l,t,o,i)}if(t.allOf)for(var c in t.allOf){l=t.allOf[c];o.property="allOf/"+c,e(l,t,o,i)}if(t.anyOf)for(var c in t.anyOf){l=t.anyOf[c];o.property="anyOf/"+c,e(l,t,o,i)}if(t.oneOf)for(var c in t.oneOf){l=t.oneOf[c];o.property="oneOf/"+c,e(l,t,o,i)}return t.not&&(o.property="not",e(t.not,t,o,i)),o.depth--,t}}},function(e,t,n){"use strict";var r=n(91);e.exports={statusCodes:Object.assign({},{default:"Default response","1XX":"Informational",103:"Early hints","2XX":"Successful","3XX":"Redirection","4XX":"Client Error","5XX":"Server Error","7XX":"Developer Error"},r.STATUS_CODES)}},function(e){e.exports=JSON.parse('{"name":"swagger2openapi","version":"5.3.4","description":"Convert Swagger 2.0 definitions to OpenApi 3.0 and validate","main":"index.js","bin":{"swagger2openapi":"./swagger2openapi.js","oas-validate":"./oas-validate.js","boast":"./boast.js"},"scripts":{"test":"mocha"},"browserify":{"transform":[["babelify",{"presets":["es2015"]}]]},"repository":{"url":"https://github.com/Mermade/oas-kit.git","type":"git"},"bugs":{"url":"https://github.com/mermade/oas-kit/issues"},"author":"Mike Ralphson <mike.ralphson@gmail.com>","license":"BSD-3-Clause","dependencies":{"better-ajv-errors":"^0.6.1","call-me-maybe":"^1.0.1","node-fetch-h2":"^2.3.0","node-readfiles":"^0.2.0","oas-kit-common":"^1.0.7","oas-resolver":"^2.2.8","oas-schema-walker":"^1.1.3","oas-validator":"^3.3.4","reftools":"^1.0.11","yaml":"^1.8.0","yargs":"^12.0.5"},"keywords":["swagger","openapi","openapi2","openapi3","converter","conversion","validator","validation","resolver","lint","linter"],"gitHead":"3c04d8c190507d806746d45042fcb8d579dfb237","_resolved":"https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-5.3.4.tgz","_integrity":"sha512-4LSutujtmehQFkRG4MAObjnI414S8VHSZ2tDAT88XxK6LhgYWUcYGZ0LNDecx5mkxAn0gOdfCJY0MCUPKJDqlw==","_from":"swagger2openapi@5.3.4"}')},function(e,t){var n=Object.prototype.hasOwnProperty,r=Object.prototype.toString;e.exports=function(e,t,o){if("[object Function]"!==r.call(t))throw new TypeError("iterator must be a function");var i=e.length;if(i===+i)for(var a=0;a<i;a++)t.call(o,e[a],a,e);else for(var s in e)n.call(e,s)&&t.call(o,e[s],s,e)}},function(e,t){!function(e){var t="\\b(?:BASH|BASHOPTS|BASH_ALIASES|BASH_ARGC|BASH_ARGV|BASH_CMDS|BASH_COMPLETION_COMPAT_DIR|BASH_LINENO|BASH_REMATCH|BASH_SOURCE|BASH_VERSINFO|BASH_VERSION|COLORTERM|COLUMNS|COMP_WORDBREAKS|DBUS_SESSION_BUS_ADDRESS|DEFAULTS_PATH|DESKTOP_SESSION|DIRSTACK|DISPLAY|EUID|GDMSESSION|GDM_LANG|GNOME_KEYRING_CONTROL|GNOME_KEYRING_PID|GPG_AGENT_INFO|GROUPS|HISTCONTROL|HISTFILE|HISTFILESIZE|HISTSIZE|HOME|HOSTNAME|HOSTTYPE|IFS|INSTANCE|JOB|LANG|LANGUAGE|LC_ADDRESS|LC_ALL|LC_IDENTIFICATION|LC_MEASUREMENT|LC_MONETARY|LC_NAME|LC_NUMERIC|LC_PAPER|LC_TELEPHONE|LC_TIME|LESSCLOSE|LESSOPEN|LINES|LOGNAME|LS_COLORS|MACHTYPE|MAILCHECK|MANDATORY_PATH|NO_AT_BRIDGE|OLDPWD|OPTERR|OPTIND|ORBIT_SOCKETDIR|OSTYPE|PAPERSIZE|PATH|PIPESTATUS|PPID|PS1|PS2|PS3|PS4|PWD|RANDOM|REPLY|SECONDS|SELINUX_INIT|SESSION|SESSIONTYPE|SESSION_MANAGER|SHELL|SHELLOPTS|SHLVL|SSH_AUTH_SOCK|TERM|UID|UPSTART_EVENTS|UPSTART_INSTANCE|UPSTART_JOB|UPSTART_SESSION|USER|WINDOWID|XAUTHORITY|XDG_CONFIG_DIRS|XDG_CURRENT_DESKTOP|XDG_DATA_DIRS|XDG_GREETER_DATA_DIR|XDG_MENU_PREFIX|XDG_RUNTIME_DIR|XDG_SEAT|XDG_SEAT_PATH|XDG_SESSION_DESKTOP|XDG_SESSION_ID|XDG_SESSION_PATH|XDG_SESSION_TYPE|XDG_VTNR|XMODIFIERS)\\b",n={environment:{pattern:RegExp("\\$"+t),alias:"constant"},variable:[{pattern:/\$?\(\([\s\S]+?\)\)/,greedy:!0,inside:{variable:[{pattern:/(^\$\(\([\s\S]+)\)\)/,lookbehind:!0},/^\$\(\(/],number:/\b0x[\dA-Fa-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:[Ee]-?\d+)?/,operator:/--?|-=|\+\+?|\+=|!=?|~|\*\*?|\*=|\/=?|%=?|<<=?|>>=?|<=?|>=?|==?|&&?|&=|\^=?|\|\|?|\|=|\?|:/,punctuation:/\(\(?|\)\)?|,|;/}},{pattern:/\$\((?:\([^)]+\)|[^()])+\)|`[^`]+`/,greedy:!0,inside:{variable:/^\$\(|^`|\)$|`$/}},{pattern:/\$\{[^}]+\}/,greedy:!0,inside:{operator:/:[-=?+]?|[!\/]|##?|%%?|\^\^?|,,?/,punctuation:/[\[\]]/,environment:{pattern:RegExp("(\\{)"+t),lookbehind:!0,alias:"constant"}}},/\$(?:\w+|[#?*!@$])/],entity:/\\(?:[abceEfnrtv\\"]|O?[0-7]{1,3}|x[0-9a-fA-F]{1,2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})/};e.languages.bash={shebang:{pattern:/^#!\s*\/.*/,alias:"important"},comment:{pattern:/(^|[^"{\\$])#.*/,lookbehind:!0},"function-name":[{pattern:/(\bfunction\s+)\w+(?=(?:\s*\(?:\s*\))?\s*\{)/,lookbehind:!0,alias:"function"},{pattern:/\b\w+(?=\s*\(\s*\)\s*\{)/,alias:"function"}],"for-or-select":{pattern:/(\b(?:for|select)\s+)\w+(?=\s+in\s)/,alias:"variable",lookbehind:!0},"assign-left":{pattern:/(^|[\s;|&]|[<>]\()\w+(?=\+?=)/,inside:{environment:{pattern:RegExp("(^|[\\s;|&]|[<>]\\()"+t),lookbehind:!0,alias:"constant"}},alias:"variable",lookbehind:!0},string:[{pattern:/((?:^|[^<])<<-?\s*)(\w+?)\s*(?:\r?\n|\r)(?:[\s\S])*?(?:\r?\n|\r)\2/,lookbehind:!0,greedy:!0,inside:n},{pattern:/((?:^|[^<])<<-?\s*)(["'])(\w+)\2\s*(?:\r?\n|\r)(?:[\s\S])*?(?:\r?\n|\r)\3/,lookbehind:!0,greedy:!0},{pattern:/(["'])(?:\\[\s\S]|\$\([^)]+\)|`[^`]+`|(?!\1)[^\\])*\1/,greedy:!0,inside:n}],environment:{pattern:RegExp("\\$?"+t),alias:"constant"},variable:n.variable,function:{pattern:/(^|[\s;|&]|[<>]\()(?:add|apropos|apt|aptitude|apt-cache|apt-get|aspell|automysqlbackup|awk|basename|bash|bc|bconsole|bg|bzip2|cal|cat|cfdisk|chgrp|chkconfig|chmod|chown|chroot|cksum|clear|cmp|column|comm|cp|cron|crontab|csplit|curl|cut|date|dc|dd|ddrescue|debootstrap|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|du|egrep|eject|env|ethtool|expand|expect|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|git|gparted|grep|groupadd|groupdel|groupmod|groups|grub-mkconfig|gzip|halt|head|hg|history|host|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|ip|jobs|join|kill|killall|less|link|ln|locate|logname|logrotate|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|lynx|make|man|mc|mdadm|mkconfig|mkdir|mke2fs|mkfifo|mkfs|mkisofs|mknod|mkswap|mmv|more|most|mount|mtools|mtr|mutt|mv|nano|nc|netstat|nice|nl|nohup|notify-send|npm|nslookup|op|open|parted|passwd|paste|pathchk|ping|pkill|pnpm|popd|pr|printcap|printenv|ps|pushd|pv|quota|quotacheck|quotactl|ram|rar|rcp|reboot|remsync|rename|renice|rev|rm|rmdir|rpm|rsync|scp|screen|sdiff|sed|sendmail|seq|service|sftp|sh|shellcheck|shuf|shutdown|sleep|slocate|sort|split|ssh|stat|strace|su|sudo|sum|suspend|swapon|sync|tac|tail|tar|tee|time|timeout|top|touch|tr|traceroute|tsort|tty|umount|uname|unexpand|uniq|units|unrar|unshar|unzip|update-grub|uptime|useradd|userdel|usermod|users|uudecode|uuencode|v|vdir|vi|vim|virsh|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yarn|yes|zenity|zip|zsh|zypper)(?=$|[)\s;|&])/,lookbehind:!0},keyword:{pattern:/(^|[\s;|&]|[<>]\()(?:if|then|else|elif|fi|for|while|in|case|esac|function|select|do|done|until)(?=$|[)\s;|&])/,lookbehind:!0},builtin:{pattern:/(^|[\s;|&]|[<>]\()(?:\.|:|break|cd|continue|eval|exec|exit|export|getopts|hash|pwd|readonly|return|shift|test|times|trap|umask|unset|alias|bind|builtin|caller|command|declare|echo|enable|help|let|local|logout|mapfile|printf|read|readarray|source|type|typeset|ulimit|unalias|set|shopt)(?=$|[)\s;|&])/,lookbehind:!0,alias:"class-name"},boolean:{pattern:/(^|[\s;|&]|[<>]\()(?:true|false)(?=$|[)\s;|&])/,lookbehind:!0},"file-descriptor":{pattern:/\B&\d\b/,alias:"important"},operator:{pattern:/\d?<>|>\||\+=|==?|!=?|=~|<<[<-]?|[&\d]?>>|\d?[<>]&?|&[>&]?|\|[&|]?|<=?|>=?/,inside:{"file-descriptor":{pattern:/^\d/,alias:"important"}}},punctuation:/\$?\(\(?|\)\)?|\.\.|[{}[\];\\]/,number:{pattern:/(^|\s)(?:[1-9]\d*|0)(?:[.,]\d+)?\b/,lookbehind:!0}};for(var r=["comment","function-name","for-or-select","assign-left","string","environment","function","keyword","builtin","boolean","file-descriptor","operator","punctuation","number"],o=n.variable[1].inside,i=0;i<r.length;i++)o[r[i]]=e.languages.bash[r[i]];e.languages.shell=e.languages.bash}(Prism)},function(e,t){Prism.languages.c=Prism.languages.extend("clike",{"class-name":{pattern:/(\b(?:enum|struct)\s+)\w+/,lookbehind:!0},keyword:/\b(?:_Alignas|_Alignof|_Atomic|_Bool|_Complex|_Generic|_Imaginary|_Noreturn|_Static_assert|_Thread_local|asm|typeof|inline|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|union|unsigned|void|volatile|while)\b/,operator:/>>=?|<<=?|->|([-+&|:])\1|[?:~]|[-+*/%&|^!=<>]=?/,number:/(?:\b0x(?:[\da-f]+\.?[\da-f]*|\.[\da-f]+)(?:p[+-]?\d+)?|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?)[ful]*/i}),Prism.languages.insertBefore("c","string",{macro:{pattern:/(^\s*)#\s*[a-z]+(?:[^\r\n\\]|\\(?:\r\n|[\s\S]))*/im,lookbehind:!0,alias:"property",inside:{string:{pattern:/(#\s*include\s*)(?:<.+?>|("|')(?:\\?.)+?\2)/,lookbehind:!0},directive:{pattern:/(#\s*)\b(?:define|defined|elif|else|endif|error|ifdef|ifndef|if|import|include|line|pragma|undef|using)\b/,lookbehind:!0,alias:"keyword"}}},constant:/\b(?:__FILE__|__LINE__|__DATE__|__TIME__|__TIMESTAMP__|__func__|EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|stdin|stdout|stderr)\b/}),delete Prism.languages.c.boolean},function(e,t){Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|interface|extends|implements|trait|instanceof|new)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,boolean:/\b(?:true|false)\b/,function:/\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/}},function(e,t){!function(e){var t=/#(?!\{).+/,n={pattern:/#\{[^}]+\}/,alias:"variable"};e.languages.coffeescript=e.languages.extend("javascript",{comment:t,string:[{pattern:/'(?:\\[\s\S]|[^\\'])*'/,greedy:!0},{pattern:/"(?:\\[\s\S]|[^\\"])*"/,greedy:!0,inside:{interpolation:n}}],keyword:/\b(?:and|break|by|catch|class|continue|debugger|delete|do|each|else|extend|extends|false|finally|for|if|in|instanceof|is|isnt|let|loop|namespace|new|no|not|null|of|off|on|or|own|return|super|switch|then|this|throw|true|try|typeof|undefined|unless|until|when|while|window|with|yes|yield)\b/,"class-member":{pattern:/@(?!\d)\w+/,alias:"variable"}}),e.languages.insertBefore("coffeescript","comment",{"multiline-comment":{pattern:/###[\s\S]+?###/,alias:"comment"},"block-regex":{pattern:/\/{3}[\s\S]*?\/{3}/,alias:"regex",inside:{comment:t,interpolation:n}}}),e.languages.insertBefore("coffeescript","string",{"inline-javascript":{pattern:/`(?:\\[\s\S]|[^\\`])*`/,inside:{delimiter:{pattern:/^`|`$/,alias:"punctuation"},rest:e.languages.javascript}},"multiline-string":[{pattern:/'''[\s\S]*?'''/,greedy:!0,alias:"string"},{pattern:/"""[\s\S]*?"""/,greedy:!0,alias:"string",inside:{interpolation:n}}]}),e.languages.insertBefore("coffeescript","keyword",{property:/(?!\d)\w+(?=\s*:(?!:))/}),delete e.languages.coffeescript["template-string"],e.languages.coffee=e.languages.coffeescript}(Prism)},function(e,t){Prism.languages.cpp=Prism.languages.extend("c",{"class-name":{pattern:/(\b(?:class|enum|struct)\s+)\w+/,lookbehind:!0},keyword:/\b(?:alignas|alignof|asm|auto|bool|break|case|catch|char|char16_t|char32_t|class|compl|const|constexpr|const_cast|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|float|for|friend|goto|if|inline|int|int8_t|int16_t|int32_t|int64_t|uint8_t|uint16_t|uint32_t|uint64_t|long|mutable|namespace|new|noexcept|nullptr|operator|private|protected|public|register|reinterpret_cast|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|union|unsigned|using|virtual|void|volatile|wchar_t|while)\b/,number:{pattern:/(?:\b0b[01']+|\b0x(?:[\da-f']+\.?[\da-f']*|\.[\da-f']+)(?:p[+-]?[\d']+)?|(?:\b[\d']+\.?[\d']*|\B\.[\d']+)(?:e[+-]?[\d']+)?)[ful]*/i,greedy:!0},operator:/>>=?|<<=?|->|([-+&|:])\1|[?:~]|[-+*/%&|^!=<>]=?|\b(?:and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\b/,boolean:/\b(?:true|false)\b/}),Prism.languages.insertBefore("cpp","string",{"raw-string":{pattern:/R"([^()\\ ]{0,16})\([\s\S]*?\)\1"/,alias:"string",greedy:!0}})},function(e,t){Prism.languages.csharp=Prism.languages.extend("clike",{keyword:/\b(?:abstract|add|alias|as|ascending|async|await|base|bool|break|byte|case|catch|char|checked|class|const|continue|decimal|default|delegate|descending|do|double|dynamic|else|enum|event|explicit|extern|false|finally|fixed|float|for|foreach|from|get|global|goto|group|if|implicit|in|int|interface|internal|into|is|join|let|lock|long|namespace|new|null|object|operator|orderby|out|override|params|partial|private|protected|public|readonly|ref|remove|return|sbyte|sealed|select|set|short|sizeof|stackalloc|static|string|struct|switch|this|throw|true|try|typeof|uint|ulong|unchecked|unsafe|ushort|using|value|var|virtual|void|volatile|where|while|yield)\b/,string:[{pattern:/@("|')(?:\1\1|\\[\s\S]|(?!\1)[^\\])*\1/,greedy:!0},{pattern:/("|')(?:\\.|(?!\1)[^\\\r\n])*?\1/,greedy:!0}],"class-name":[{pattern:/\b[A-Z]\w*(?:\.\w+)*\b(?=\s+\w+)/,inside:{punctuation:/\./}},{pattern:/(\[)[A-Z]\w*(?:\.\w+)*\b/,lookbehind:!0,inside:{punctuation:/\./}},{pattern:/(\b(?:class|interface)\s+[A-Z]\w*(?:\.\w+)*\s*:\s*)[A-Z]\w*(?:\.\w+)*\b/,lookbehind:!0,inside:{punctuation:/\./}},{pattern:/((?:\b(?:class|interface|new)\s+)|(?:catch\s+\())[A-Z]\w*(?:\.\w+)*\b/,lookbehind:!0,inside:{punctuation:/\./}}],number:/\b0x[\da-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)f?/i,operator:/>>=?|<<=?|[-=]>|([-+&|?])\1|~|[-+*/%&|^!=<>]=?/,punctuation:/\?\.?|::|[{}[\];(),.:]/}),Prism.languages.insertBefore("csharp","class-name",{"generic-method":{pattern:/\w+\s*<[^>\r\n]+?>\s*(?=\()/,inside:{function:/^\w+/,"class-name":{pattern:/\b[A-Z]\w*(?:\.\w+)*\b/,inside:{punctuation:/\./}},keyword:Prism.languages.csharp.keyword,punctuation:/[<>(),.:]/}},preprocessor:{pattern:/(^\s*)#.*/m,lookbehind:!0,alias:"property",inside:{directive:{pattern:/(\s*#)\b(?:define|elif|else|endif|endregion|error|if|line|pragma|region|undef|warning)\b/,lookbehind:!0,alias:"keyword"}}}}),Prism.languages.dotnet=Prism.languages.cs=Prism.languages.csharp},function(e,t){Prism.languages.go=Prism.languages.extend("clike",{keyword:/\b(?:break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go(?:to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b/,builtin:/\b(?:bool|byte|complex(?:64|128)|error|float(?:32|64)|rune|string|u?int(?:8|16|32|64)?|uintptr|append|cap|close|complex|copy|delete|imag|len|make|new|panic|print(?:ln)?|real|recover)\b/,boolean:/\b(?:_|iota|nil|true|false)\b/,operator:/[*\/%^!=]=?|\+[=+]?|-[=-]?|\|[=|]?|&(?:=|&|\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\.\.\./,number:/(?:\b0x[a-f\d]+|(?:\b\d+\.?\d*|\B\.\d+)(?:e[-+]?\d+)?)i?/i,string:{pattern:/(["'`])(?:\\[\s\S]|(?!\1)[^\\])*\1/,greedy:!0}}),delete Prism.languages.go["class-name"]},function(e,t){!function(e){e.languages.http={"request-line":{pattern:/^(?:POST|GET|PUT|DELETE|OPTIONS|PATCH|TRACE|CONNECT)\s(?:https?:\/\/|\/)\S+\sHTTP\/[0-9.]+/m,inside:{property:/^(?:POST|GET|PUT|DELETE|OPTIONS|PATCH|TRACE|CONNECT)\b/,"attr-name":/:\w+/}},"response-status":{pattern:/^HTTP\/1.[01] \d+.*/m,inside:{property:{pattern:/(^HTTP\/1.[01] )\d+.*/i,lookbehind:!0}}},"header-name":{pattern:/^[\w-]+:(?=.)/m,alias:"keyword"}};var t,n=e.languages,r={"application/javascript":n.javascript,"application/json":n.json||n.javascript,"application/xml":n.xml,"text/xml":n.xml,"text/html":n.html,"text/css":n.css},o={"application/json":!0,"application/xml":!0};function i(e){var t=e.replace(/^[a-z]+\//,"");return"(?:"+e+"|"+("\\w+/(?:[\\w.-]+\\+)+"+t+"(?![+\\w.-])")+")"}for(var a in r)if(r[a]){t=t||{};var s=o[a]?i(a):a;t[a.replace(/\//g,"-")]={pattern:RegExp("(content-type:\\s*"+s+"[\\s\\S]*?)(?:\\r?\\n|\\r){2}[\\s\\S]*","i"),lookbehind:!0,inside:r[a]}}t&&e.languages.insertBefore("http","header-name",t)}(Prism)},function(e,t){!function(e){var t=/\b(?:abstract|assert|boolean|break|byte|case|catch|char|class|const|continue|default|do|double|else|enum|exports|extends|final|finally|float|for|goto|if|implements|import|instanceof|int|interface|long|module|native|new|null|open|opens|package|private|protected|provides|public|requires|return|short|static|strictfp|super|switch|synchronized|this|throw|throws|to|transient|transitive|try|uses|var|void|volatile|while|with|yield)\b/,n=/\b[A-Z](?:\w*[a-z]\w*)?\b/;e.languages.java=e.languages.extend("clike",{"class-name":[n,/\b[A-Z]\w*(?=\s+\w+\s*[;,=())])/],keyword:t,function:[e.languages.clike.function,{pattern:/(\:\:)[a-z_]\w*/,lookbehind:!0}],number:/\b0b[01][01_]*L?\b|\b0x[\da-f_]*\.?[\da-f_p+-]+\b|(?:\b\d[\d_]*\.?[\d_]*|\B\.\d[\d_]*)(?:e[+-]?\d[\d_]*)?[dfl]?/i,operator:{pattern:/(^|[^.])(?:<<=?|>>>?=?|->|--|\+\+|&&|\|\||::|[?:~]|[-+*/%&|^!=<>]=?)/m,lookbehind:!0}}),e.languages.insertBefore("java","string",{"triple-quoted-string":{pattern:/"""[ \t]*[\r\n](?:(?:"|"")?(?:\\.|[^"\\]))*"""/,greedy:!0,alias:"string"}}),e.languages.insertBefore("java","class-name",{annotation:{alias:"punctuation",pattern:/(^|[^.])@\w+/,lookbehind:!0},namespace:{pattern:/(\b(?:exports|import(?:\s+static)?|module|open|opens|package|provides|requires|to|transitive|uses|with)\s+)[a-z]\w*(?:\.[a-z]\w*)+/,lookbehind:!0,inside:{punctuation:/\./}},generics:{pattern:/<(?:[\w\s,.&?]|<(?:[\w\s,.&?]|<(?:[\w\s,.&?]|<[\w\s,.&?]*>)*>)*>)*>/,inside:{"class-name":n,keyword:t,punctuation:/[<>(),.:]/,operator:/[?&|]/}}})}(Prism)},function(e,t){Prism.languages.lua={comment:/^#!.+|--(?:\[(=*)\[[\s\S]*?\]\1\]|.*)/m,string:{pattern:/(["'])(?:(?!\1)[^\\\r\n]|\\z(?:\r\n|\s)|\\(?:\r\n|[\s\S]))*\1|\[(=*)\[[\s\S]*?\]\2\]/,greedy:!0},number:/\b0x[a-f\d]+\.?[a-f\d]*(?:p[+-]?\d+)?\b|\b\d+(?:\.\B|\.?\d*(?:e[+-]?\d+)?\b)|\B\.\d+(?:e[+-]?\d+)?\b/i,keyword:/\b(?:and|break|do|else|elseif|end|false|for|function|goto|if|in|local|nil|not|or|repeat|return|then|true|until|while)\b/,function:/(?!\d)\w+(?=\s*(?:[({]))/,operator:[/[-+*%^&|#]|\/\/?|<[<=]?|>[>=]?|[=~]=?/,{pattern:/(^|[^.])\.\.(?!\.)/,lookbehind:!0}],punctuation:/[\[\](){},;]|\.+|:+/}},function(e,t){!function(e){function t(e,t){return"___"+e.toUpperCase()+t+"___"}Object.defineProperties(e.languages["markup-templating"]={},{buildPlaceholders:{value:function(n,r,o,i){if(n.language===r){var a=n.tokenStack=[];n.code=n.code.replace(o,(function(e){if("function"==typeof i&&!i(e))return e;for(var o,s=a.length;-1!==n.code.indexOf(o=t(r,s));)++s;return a[s]=e,o})),n.grammar=e.languages.markup}}},tokenizePlaceholders:{value:function(n,r){if(n.language===r&&n.tokenStack){n.grammar=e.languages[r];var o=0,i=Object.keys(n.tokenStack);!function a(s){for(var l=0;l<s.length&&!(o>=i.length);l++){var c=s[l];if("string"==typeof c||c.content&&"string"==typeof c.content){var u=i[o],p=n.tokenStack[u],f="string"==typeof c?c:c.content,d=t(r,u),h=f.indexOf(d);if(h>-1){++o;var m=f.substring(0,h),g=new e.Token(r,e.tokenize(p,n.grammar),"language-"+r,p),y=f.substring(h+d.length),v=[];m&&v.push.apply(v,a([m])),v.push(g),y&&v.push.apply(v,a([y])),"string"==typeof c?s.splice.apply(s,[l,1].concat(v)):c.content=v}}else c.content&&a(c.content)}return s}(n.tokens)}}}})}(Prism)},function(e,t){Prism.languages.markup={comment:/<!--[\s\S]*?-->/,prolog:/<\?[\s\S]+?\?>/,doctype:{pattern:/<!DOCTYPE(?:[^>"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:(?!<!--)[^"'\]]|"[^"]*"|'[^']*'|<!--[\s\S]*?-->)*\]\s*)?>/i,greedy:!0},cdata:/<!\[CDATA\[[\s\S]*?]]>/i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/i,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/i,inside:{punctuation:[/^=/,{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/&#?[\da-z]{1,8};/i},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.hooks.add("wrap",(function(e){"entity"===e.type&&(e.attributes.title=e.content.replace(/&/,"&"))})),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(e,t){var n={};n["language-"+t]={pattern:/(^<!\[CDATA\[)[\s\S]+?(?=\]\]>$)/i,lookbehind:!0,inside:Prism.languages[t]},n.cdata=/^<!\[CDATA\[|\]\]>$/i;var r={"included-cdata":{pattern:/<!\[CDATA\[[\s\S]*?\]\]>/i,inside:n}};r["language-"+t]={pattern:/[\s\S]+/,inside:Prism.languages[t]};var o={};o[e]={pattern:RegExp(/(<__[\s\S]*?>)(?:<!\[CDATA\[[\s\S]*?\]\]>\s*|[\s\S])*?(?=<\/__>)/.source.replace(/__/g,e),"i"),lookbehind:!0,greedy:!0,inside:r},Prism.languages.insertBefore("markup","cdata",o)}}),Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup},function(e,t){Prism.languages.objectivec=Prism.languages.extend("c",{keyword:/\b(?:asm|typeof|inline|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|union|unsigned|void|volatile|while|in|self|super)\b|(?:@interface|@end|@implementation|@protocol|@class|@public|@protected|@private|@property|@try|@catch|@finally|@throw|@synthesize|@dynamic|@selector)\b/,string:/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1|@"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,operator:/-[->]?|\+\+?|!=?|<<?=?|>>?=?|==?|&&?|\|\|?|[~^%?*\/@]/}),delete Prism.languages.objectivec["class-name"]},function(e,t){Prism.languages.perl={comment:[{pattern:/(^\s*)=\w+[\s\S]*?=cut.*/m,lookbehind:!0},{pattern:/(^|[^\\$])#.*/,lookbehind:!0}],string:[{pattern:/\b(?:q|qq|qx|qw)\s*([^a-zA-Z0-9\s{(\[<])(?:(?!\1)[^\\]|\\[\s\S])*\1/,greedy:!0},{pattern:/\b(?:q|qq|qx|qw)\s+([a-zA-Z0-9])(?:(?!\1)[^\\]|\\[\s\S])*\1/,greedy:!0},{pattern:/\b(?:q|qq|qx|qw)\s*\((?:[^()\\]|\\[\s\S])*\)/,greedy:!0},{pattern:/\b(?:q|qq|qx|qw)\s*\{(?:[^{}\\]|\\[\s\S])*\}/,greedy:!0},{pattern:/\b(?:q|qq|qx|qw)\s*\[(?:[^[\]\\]|\\[\s\S])*\]/,greedy:!0},{pattern:/\b(?:q|qq|qx|qw)\s*<(?:[^<>\\]|\\[\s\S])*>/,greedy:!0},{pattern:/("|`)(?:(?!\1)[^\\]|\\[\s\S])*\1/,greedy:!0},{pattern:/'(?:[^'\\\r\n]|\\.)*'/,greedy:!0}],regex:[{pattern:/\b(?:m|qr)\s*([^a-zA-Z0-9\s{(\[<])(?:(?!\1)[^\\]|\\[\s\S])*\1[msixpodualngc]*/,greedy:!0},{pattern:/\b(?:m|qr)\s+([a-zA-Z0-9])(?:(?!\1)[^\\]|\\[\s\S])*\1[msixpodualngc]*/,greedy:!0},{pattern:/\b(?:m|qr)\s*\((?:[^()\\]|\\[\s\S])*\)[msixpodualngc]*/,greedy:!0},{pattern:/\b(?:m|qr)\s*\{(?:[^{}\\]|\\[\s\S])*\}[msixpodualngc]*/,greedy:!0},{pattern:/\b(?:m|qr)\s*\[(?:[^[\]\\]|\\[\s\S])*\][msixpodualngc]*/,greedy:!0},{pattern:/\b(?:m|qr)\s*<(?:[^<>\\]|\\[\s\S])*>[msixpodualngc]*/,greedy:!0},{pattern:/(^|[^-]\b)(?:s|tr|y)\s*([^a-zA-Z0-9\s{(\[<])(?:(?!\2)[^\\]|\\[\s\S])*\2(?:(?!\2)[^\\]|\\[\s\S])*\2[msixpodualngcer]*/,lookbehind:!0,greedy:!0},{pattern:/(^|[^-]\b)(?:s|tr|y)\s+([a-zA-Z0-9])(?:(?!\2)[^\\]|\\[\s\S])*\2(?:(?!\2)[^\\]|\\[\s\S])*\2[msixpodualngcer]*/,lookbehind:!0,greedy:!0},{pattern:/(^|[^-]\b)(?:s|tr|y)\s*\((?:[^()\\]|\\[\s\S])*\)\s*\((?:[^()\\]|\\[\s\S])*\)[msixpodualngcer]*/,lookbehind:!0,greedy:!0},{pattern:/(^|[^-]\b)(?:s|tr|y)\s*\{(?:[^{}\\]|\\[\s\S])*\}\s*\{(?:[^{}\\]|\\[\s\S])*\}[msixpodualngcer]*/,lookbehind:!0,greedy:!0},{pattern:/(^|[^-]\b)(?:s|tr|y)\s*\[(?:[^[\]\\]|\\[\s\S])*\]\s*\[(?:[^[\]\\]|\\[\s\S])*\][msixpodualngcer]*/,lookbehind:!0,greedy:!0},{pattern:/(^|[^-]\b)(?:s|tr|y)\s*<(?:[^<>\\]|\\[\s\S])*>\s*<(?:[^<>\\]|\\[\s\S])*>[msixpodualngcer]*/,lookbehind:!0,greedy:!0},{pattern:/\/(?:[^\/\\\r\n]|\\.)*\/[msixpodualngc]*(?=\s*(?:$|[\r\n,.;})&|\-+*~<>!?^]|(?:lt|gt|le|ge|eq|ne|cmp|not|and|or|xor|x)\b))/,greedy:!0}],variable:[/[&*$@%]\{\^[A-Z]+\}/,/[&*$@%]\^[A-Z_]/,/[&*$@%]#?(?=\{)/,/[&*$@%]#?(?:(?:::)*'?(?!\d)[\w$]+)+(?:::)*/i,/[&*$@%]\d+/,/(?!%=)[$@%][!"#$%&'()*+,\-.\/:;<=>?@[\\\]^_`{|}~]/],filehandle:{pattern:/<(?![<=])\S*>|\b_\b/,alias:"symbol"},vstring:{pattern:/v\d+(?:\.\d+)*|\d+(?:\.\d+){2,}/,alias:"string"},function:{pattern:/sub [a-z0-9_]+/i,inside:{keyword:/sub/}},keyword:/\b(?:any|break|continue|default|delete|die|do|else|elsif|eval|for|foreach|given|goto|if|last|local|my|next|our|package|print|redo|require|return|say|state|sub|switch|undef|unless|until|use|when|while)\b/,number:/\b(?:0x[\dA-Fa-f](?:_?[\dA-Fa-f])*|0b[01](?:_?[01])*|(?:\d(?:_?\d)*)?\.?\d(?:_?\d)*(?:[Ee][+-]?\d+)?)\b/,operator:/-[rwxoRWXOezsfdlpSbctugkTBMAC]\b|\+[+=]?|-[-=>]?|\*\*?=?|\/\/?=?|=[=~>]?|~[~=]?|\|\|?=?|&&?=?|<(?:=>?|<=?)?|>>?=?|![~=]?|[%^]=?|\.(?:=|\.\.?)?|[\\?]|\bx(?:=|\b)|\b(?:lt|gt|le|ge|eq|ne|cmp|not|and|or|xor)\b/,punctuation:/[{}[\];(),:]/}},function(e,t){!function(e){e.languages.php=e.languages.extend("clike",{keyword:/\b(?:__halt_compiler|abstract|and|array|as|break|callable|case|catch|class|clone|const|continue|declare|default|die|do|echo|else|elseif|empty|enddeclare|endfor|endforeach|endif|endswitch|endwhile|eval|exit|extends|final|finally|for|foreach|function|global|goto|if|implements|include|include_once|instanceof|insteadof|interface|isset|list|namespace|new|or|parent|print|private|protected|public|require|require_once|return|static|switch|throw|trait|try|unset|use|var|while|xor|yield)\b/i,boolean:{pattern:/\b(?:false|true)\b/i,alias:"constant"},constant:[/\b[A-Z_][A-Z0-9_]*\b/,/\b(?:null)\b/i],comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|\/\/.*)/,lookbehind:!0}}),e.languages.insertBefore("php","string",{"shell-comment":{pattern:/(^|[^\\])#.*/,lookbehind:!0,alias:"comment"}}),e.languages.insertBefore("php","comment",{delimiter:{pattern:/\?>$|^<\?(?:php(?=\s)|=)?/i,alias:"important"}}),e.languages.insertBefore("php","keyword",{variable:/\$+(?:\w+\b|(?={))/i,package:{pattern:/(\\|namespace\s+|use\s+)[\w\\]+/,lookbehind:!0,inside:{punctuation:/\\/}}}),e.languages.insertBefore("php","operator",{property:{pattern:/(->)[\w]+/,lookbehind:!0}});var t={pattern:/{\$(?:{(?:{[^{}]+}|[^{}]+)}|[^{}])+}|(^|[^\\{])\$+(?:\w+(?:\[.+?]|->\w+)*)/,lookbehind:!0,inside:e.languages.php};e.languages.insertBefore("php","string",{"nowdoc-string":{pattern:/<<<'([^']+)'(?:\r\n?|\n)(?:.*(?:\r\n?|\n))*?\1;/,greedy:!0,alias:"string",inside:{delimiter:{pattern:/^<<<'[^']+'|[a-z_]\w*;$/i,alias:"symbol",inside:{punctuation:/^<<<'?|[';]$/}}}},"heredoc-string":{pattern:/<<<(?:"([^"]+)"(?:\r\n?|\n)(?:.*(?:\r\n?|\n))*?\1;|([a-z_]\w*)(?:\r\n?|\n)(?:.*(?:\r\n?|\n))*?\2;)/i,greedy:!0,alias:"string",inside:{delimiter:{pattern:/^<<<(?:"[^"]+"|[a-z_]\w*)|[a-z_]\w*;$/i,alias:"symbol",inside:{punctuation:/^<<<"?|[";]$/}},interpolation:t}},"single-quoted-string":{pattern:/'(?:\\[\s\S]|[^\\'])*'/,greedy:!0,alias:"string"},"double-quoted-string":{pattern:/"(?:\\[\s\S]|[^\\"])*"/,greedy:!0,alias:"string",inside:{interpolation:t}}}),delete e.languages.php.string,e.hooks.add("before-tokenize",(function(t){if(/<\?/.test(t.code)){e.languages["markup-templating"].buildPlaceholders(t,"php",/<\?(?:[^"'/#]|\/(?![*/])|("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|(?:\/\/|#)(?:[^?\n\r]|\?(?!>))*(?=$|\?>|[\r\n])|\/\*[\s\S]*?(?:\*\/|$))*?(?:\?>|$)/gi)}})),e.hooks.add("after-tokenize",(function(t){e.languages["markup-templating"].tokenizePlaceholders(t,"php")}))}(Prism)},function(e,t){Prism.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0},"string-interpolation":{pattern:/(?:f|rf|fr)(?:("""|''')[\s\S]+?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:{{)*){(?!{)(?:[^{}]|{(?!{)(?:[^{}]|{(?!{)(?:[^{}])+})+})+}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=}$)/,lookbehind:!0},"conversion-option":{pattern:/![sra](?=[:}]$)/,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|rb|br)?("""|''')[\s\S]+?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|rb|br)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^\s*)@\w+(?:\.\w+)*/im,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:and|as|assert|async|await|break|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,boolean:/\b(?:True|False|None)\b/,number:/(?:\b(?=\d)|\B(?=\.))(?:0[bo])?(?:(?:\d|0x[\da-f])[\da-f]*\.?\d*|\.\d+)(?:e[+-]?\d+)?j?\b/i,operator:/[-+%=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},Prism.languages.python["string-interpolation"].inside.interpolation.inside.rest=Prism.languages.python,Prism.languages.py=Prism.languages.python},function(e,t){!function(e){e.languages.ruby=e.languages.extend("clike",{comment:[/#.*/,{pattern:/^=begin\s[\s\S]*?^=end/m,greedy:!0}],"class-name":{pattern:/(\b(?:class)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:alias|and|BEGIN|begin|break|case|class|def|define_method|defined|do|each|else|elsif|END|end|ensure|extend|for|if|in|include|module|new|next|nil|not|or|prepend|protected|private|public|raise|redo|require|rescue|retry|return|self|super|then|throw|undef|unless|until|when|while|yield)\b/});var t={pattern:/#\{[^}]+\}/,inside:{delimiter:{pattern:/^#\{|\}$/,alias:"tag"},rest:e.languages.ruby}};delete e.languages.ruby.function,e.languages.insertBefore("ruby","keyword",{regex:[{pattern:/%r([^a-zA-Z0-9\s{(\[<])(?:(?!\1)[^\\]|\\[\s\S])*\1[gim]{0,3}/,greedy:!0,inside:{interpolation:t}},{pattern:/%r\((?:[^()\\]|\\[\s\S])*\)[gim]{0,3}/,greedy:!0,inside:{interpolation:t}},{pattern:/%r\{(?:[^#{}\\]|#(?:\{[^}]+\})?|\\[\s\S])*\}[gim]{0,3}/,greedy:!0,inside:{interpolation:t}},{pattern:/%r\[(?:[^\[\]\\]|\\[\s\S])*\][gim]{0,3}/,greedy:!0,inside:{interpolation:t}},{pattern:/%r<(?:[^<>\\]|\\[\s\S])*>[gim]{0,3}/,greedy:!0,inside:{interpolation:t}},{pattern:/(^|[^/])\/(?!\/)(?:\[.+?]|\\.|[^/\\\r\n])+\/[gim]{0,3}(?=\s*(?:$|[\r\n,.;})]))/,lookbehind:!0,greedy:!0}],variable:/[@$]+[a-zA-Z_]\w*(?:[?!]|\b)/,symbol:{pattern:/(^|[^:]):[a-zA-Z_]\w*(?:[?!]|\b)/,lookbehind:!0},"method-definition":{pattern:/(\bdef\s+)[\w.]+/,lookbehind:!0,inside:{function:/\w+$/,rest:e.languages.ruby}}}),e.languages.insertBefore("ruby","number",{builtin:/\b(?:Array|Bignum|Binding|Class|Continuation|Dir|Exception|FalseClass|File|Stat|Fixnum|Float|Hash|Integer|IO|MatchData|Method|Module|NilClass|Numeric|Object|Proc|Range|Regexp|String|Struct|TMS|Symbol|ThreadGroup|Thread|Time|TrueClass)\b/,constant:/\b[A-Z]\w*(?:[?!]|\b)/}),e.languages.ruby.string=[{pattern:/%[qQiIwWxs]?([^a-zA-Z0-9\s{(\[<])(?:(?!\1)[^\\]|\\[\s\S])*\1/,greedy:!0,inside:{interpolation:t}},{pattern:/%[qQiIwWxs]?\((?:[^()\\]|\\[\s\S])*\)/,greedy:!0,inside:{interpolation:t}},{pattern:/%[qQiIwWxs]?\{(?:[^#{}\\]|#(?:\{[^}]+\})?|\\[\s\S])*\}/,greedy:!0,inside:{interpolation:t}},{pattern:/%[qQiIwWxs]?\[(?:[^\[\]\\]|\\[\s\S])*\]/,greedy:!0,inside:{interpolation:t}},{pattern:/%[qQiIwWxs]?<(?:[^<>\\]|\\[\s\S])*>/,greedy:!0,inside:{interpolation:t}},{pattern:/("|')(?:#\{[^}]+\}|\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0,inside:{interpolation:t}}],e.languages.rb=e.languages.ruby}(Prism)},function(e,t){Prism.languages.scala=Prism.languages.extend("java",{keyword:/<-|=>|\b(?:abstract|case|catch|class|def|do|else|extends|final|finally|for|forSome|if|implicit|import|lazy|match|new|null|object|override|package|private|protected|return|sealed|self|super|this|throw|trait|try|type|val|var|while|with|yield)\b/,"triple-quoted-string":{pattern:/"""[\s\S]*?"""/,greedy:!0,alias:"string"},string:{pattern:/("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,greedy:!0},builtin:/\b(?:String|Int|Long|Short|Byte|Boolean|Double|Float|Char|Any|AnyRef|AnyVal|Unit|Nothing)\b/,number:/\b0x[\da-f]*\.?[\da-f]+|(?:\b\d+\.?\d*|\B\.\d+)(?:e\d+)?[dfl]?/i,symbol:/'[^\d\s\\]\w*/}),delete Prism.languages.scala["class-name"],delete Prism.languages.scala.function},function(e,t){Prism.languages.sql={comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|(?:--|\/\/|#).*)/,lookbehind:!0},variable:[{pattern:/@(["'`])(?:\\[\s\S]|(?!\1)[^\\])+\1/,greedy:!0},/@[\w.$]+/],string:{pattern:/(^|[^@\\])("|')(?:\\[\s\S]|(?!\2)[^\\]|\2\2)*\2/,greedy:!0,lookbehind:!0},function:/\b(?:AVG|COUNT|FIRST|FORMAT|LAST|LCASE|LEN|MAX|MID|MIN|MOD|NOW|ROUND|SUM|UCASE)(?=\s*\()/i,keyword:/\b(?:ACTION|ADD|AFTER|ALGORITHM|ALL|ALTER|ANALYZE|ANY|APPLY|AS|ASC|AUTHORIZATION|AUTO_INCREMENT|BACKUP|BDB|BEGIN|BERKELEYDB|BIGINT|BINARY|BIT|BLOB|BOOL|BOOLEAN|BREAK|BROWSE|BTREE|BULK|BY|CALL|CASCADED?|CASE|CHAIN|CHAR(?:ACTER|SET)?|CHECK(?:POINT)?|CLOSE|CLUSTERED|COALESCE|COLLATE|COLUMNS?|COMMENT|COMMIT(?:TED)?|COMPUTE|CONNECT|CONSISTENT|CONSTRAINT|CONTAINS(?:TABLE)?|CONTINUE|CONVERT|CREATE|CROSS|CURRENT(?:_DATE|_TIME|_TIMESTAMP|_USER)?|CURSOR|CYCLE|DATA(?:BASES?)?|DATE(?:TIME)?|DAY|DBCC|DEALLOCATE|DEC|DECIMAL|DECLARE|DEFAULT|DEFINER|DELAYED|DELETE|DELIMITERS?|DENY|DESC|DESCRIBE|DETERMINISTIC|DISABLE|DISCARD|DISK|DISTINCT|DISTINCTROW|DISTRIBUTED|DO|DOUBLE|DROP|DUMMY|DUMP(?:FILE)?|DUPLICATE|ELSE(?:IF)?|ENABLE|ENCLOSED|END|ENGINE|ENUM|ERRLVL|ERRORS|ESCAPED?|EXCEPT|EXEC(?:UTE)?|EXISTS|EXIT|EXPLAIN|EXTENDED|FETCH|FIELDS|FILE|FILLFACTOR|FIRST|FIXED|FLOAT|FOLLOWING|FOR(?: EACH ROW)?|FORCE|FOREIGN|FREETEXT(?:TABLE)?|FROM|FULL|FUNCTION|GEOMETRY(?:COLLECTION)?|GLOBAL|GOTO|GRANT|GROUP|HANDLER|HASH|HAVING|HOLDLOCK|HOUR|IDENTITY(?:_INSERT|COL)?|IF|IGNORE|IMPORT|INDEX|INFILE|INNER|INNODB|INOUT|INSERT|INT|INTEGER|INTERSECT|INTERVAL|INTO|INVOKER|ISOLATION|ITERATE|JOIN|KEYS?|KILL|LANGUAGE|LAST|LEAVE|LEFT|LEVEL|LIMIT|LINENO|LINES|LINESTRING|LOAD|LOCAL|LOCK|LONG(?:BLOB|TEXT)|LOOP|MATCH(?:ED)?|MEDIUM(?:BLOB|INT|TEXT)|MERGE|MIDDLEINT|MINUTE|MODE|MODIFIES|MODIFY|MONTH|MULTI(?:LINESTRING|POINT|POLYGON)|NATIONAL|NATURAL|NCHAR|NEXT|NO|NONCLUSTERED|NULLIF|NUMERIC|OFF?|OFFSETS?|ON|OPEN(?:DATASOURCE|QUERY|ROWSET)?|OPTIMIZE|OPTION(?:ALLY)?|ORDER|OUT(?:ER|FILE)?|OVER|PARTIAL|PARTITION|PERCENT|PIVOT|PLAN|POINT|POLYGON|PRECEDING|PRECISION|PREPARE|PREV|PRIMARY|PRINT|PRIVILEGES|PROC(?:EDURE)?|PUBLIC|PURGE|QUICK|RAISERROR|READS?|REAL|RECONFIGURE|REFERENCES|RELEASE|RENAME|REPEAT(?:ABLE)?|REPLACE|REPLICATION|REQUIRE|RESIGNAL|RESTORE|RESTRICT|RETURNS?|REVOKE|RIGHT|ROLLBACK|ROUTINE|ROW(?:COUNT|GUIDCOL|S)?|RTREE|RULE|SAVE(?:POINT)?|SCHEMA|SECOND|SELECT|SERIAL(?:IZABLE)?|SESSION(?:_USER)?|SET(?:USER)?|SHARE|SHOW|SHUTDOWN|SIMPLE|SMALLINT|SNAPSHOT|SOME|SONAME|SQL|START(?:ING)?|STATISTICS|STATUS|STRIPED|SYSTEM_USER|TABLES?|TABLESPACE|TEMP(?:ORARY|TABLE)?|TERMINATED|TEXT(?:SIZE)?|THEN|TIME(?:STAMP)?|TINY(?:BLOB|INT|TEXT)|TOP?|TRAN(?:SACTIONS?)?|TRIGGER|TRUNCATE|TSEQUAL|TYPES?|UNBOUNDED|UNCOMMITTED|UNDEFINED|UNION|UNIQUE|UNLOCK|UNPIVOT|UNSIGNED|UPDATE(?:TEXT)?|USAGE|USE|USER|USING|VALUES?|VAR(?:BINARY|CHAR|CHARACTER|YING)|VIEW|WAITFOR|WARNINGS|WHEN|WHERE|WHILE|WITH(?: ROLLUP|IN)?|WORK|WRITE(?:TEXT)?|YEAR)\b/i,boolean:/\b(?:TRUE|FALSE|NULL)\b/i,number:/\b0x[\da-f]+\b|\b\d+\.?\d*|\B\.\d+\b/i,operator:/[-+*\/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?|\b(?:AND|BETWEEN|IN|LIKE|NOT|OR|IS|DIV|REGEXP|RLIKE|SOUNDS LIKE|XOR)\b/i,punctuation:/[;[\]()`,.]/}},function(e,t){Prism.languages.swift=Prism.languages.extend("clike",{string:{pattern:/("|')(?:\\(?:\((?:[^()]|\([^)]+\))+\)|\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0,inside:{interpolation:{pattern:/\\\((?:[^()]|\([^)]+\))+\)/,inside:{delimiter:{pattern:/^\\\(|\)$/,alias:"variable"}}}}},keyword:/\b(?:as|associativity|break|case|catch|class|continue|convenience|default|defer|deinit|didSet|do|dynamic(?:Type)?|else|enum|extension|fallthrough|final|for|func|get|guard|if|import|in|infix|init|inout|internal|is|lazy|left|let|mutating|new|none|nonmutating|operator|optional|override|postfix|precedence|prefix|private|protocol|public|repeat|required|rethrows|return|right|safe|self|Self|set|static|struct|subscript|super|switch|throws?|try|Type|typealias|unowned|unsafe|var|weak|where|while|willSet|__(?:COLUMN__|FILE__|FUNCTION__|LINE__))\b/,number:/\b(?:[\d_]+(?:\.[\de_]+)?|0x[a-f0-9_]+(?:\.[a-f0-9p_]+)?|0b[01_]+|0o[0-7_]+)\b/i,constant:/\b(?:nil|[A-Z_]{2,}|k[A-Z][A-Za-z_]+)\b/,atrule:/@\b(?:IB(?:Outlet|Designable|Action|Inspectable)|class_protocol|exported|noreturn|NS(?:Copying|Managed)|objc|UIApplicationMain|auto_closure)\b/,builtin:/\b(?:[A-Z]\S+|abs|advance|alignof(?:Value)?|assert|contains|count(?:Elements)?|debugPrint(?:ln)?|distance|drop(?:First|Last)|dump|enumerate|equal|filter|find|first|getVaList|indices|isEmpty|join|last|lexicographicalCompare|map|max(?:Element)?|min(?:Element)?|numericCast|overlaps|partition|print(?:ln)?|reduce|reflect|reverse|sizeof(?:Value)?|sort(?:ed)?|split|startsWith|stride(?:of(?:Value)?)?|suffix|swap|toDebugString|toString|transcode|underestimateCount|unsafeBitCast|with(?:ExtendedLifetime|Unsafe(?:MutablePointers?|Pointers?)|VaList))\b/}),Prism.languages.swift.string.inside.interpolation.inside.rest=Prism.languages.swift},function(e,t,n){var r=n(316),o=["add","done","toJS","load","search"];e.exports=function(){var e=new Worker(URL.createObjectURL(new Blob(['/*!\n * ReDoc - OpenAPI/Swagger-generated API Reference Documentation\n * -------------------------------------------------------------\n * Version: "2.0.0-rc.29"\n * Repo: https://github.com/Redocly/redoc\n */!function(e){var t={};function r(n){if(t[n])return t[n].exports;var i=t[n]={i:n,l:!1,exports:{}};return e[n].call(i.exports,i,i.exports,r),i.l=!0,i.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)r.d(n,i,function(t){return e[t]}.bind(null,i));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=97)}([function(e,t,r){(function(t){var r=function(e){return e&&e.Math==Math&&e};e.exports=r("object"==typeof globalThis&&globalThis)||r("object"==typeof window&&window)||r("object"==typeof self&&self)||r("object"==typeof t&&t)||Function("return this")()}).call(this,r(59))},function(e,t,r){var n=r(0),i=r(32),o=r(3),s=r(36),u=r(37),a=r(60),c=i("wks"),l=n.Symbol,f=a?l:l&&l.withoutSetter||s;e.exports=function(e){return o(c,e)||(u&&o(l,e)?c[e]=l[e]:c[e]=f("Symbol."+e)),c[e]}},function(e,t,r){var n=r(7);e.exports=function(e){if(!n(e))throw TypeError(String(e)+" is not an object");return e}},function(e,t){var r={}.hasOwnProperty;e.exports=function(e,t){return r.call(e,t)}},function(e,t,r){var n,i;\n/**\n * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 2.3.8\n * Copyright (C) 2019 Oliver Nightingale\n * @license MIT\n */!function(){var o,s,u,a,c,l,f,p,h,d,y,v,m,g,x,w,b,S,P,k,O,E,L,T,Q,j,I=function(e){var t=new I.Builder;return t.pipeline.add(I.trimmer,I.stopWordFilter,I.stemmer),t.searchPipeline.add(I.stemmer),e.call(t,t),t.build()};I.version="2.3.8"\n/*!\n * lunr.utils\n * Copyright (C) 2019 Oliver Nightingale\n */,I.utils={},I.utils.warn=(o=this,function(e){o.console&&console.warn&&console.warn(e)}),I.utils.asString=function(e){return null==e?"":e.toString()},I.utils.clone=function(e){if(null==e)return e;for(var t=Object.create(null),r=Object.keys(e),n=0;n<r.length;n++){var i=r[n],o=e[i];if(Array.isArray(o))t[i]=o.slice();else{if("string"!=typeof o&&"number"!=typeof o&&"boolean"!=typeof o)throw new TypeError("clone is not deep and does not support nested objects");t[i]=o}}return t},I.FieldRef=function(e,t,r){this.docRef=e,this.fieldName=t,this._stringValue=r},I.FieldRef.joiner="/",I.FieldRef.fromString=function(e){var t=e.indexOf(I.FieldRef.joiner);if(-1===t)throw"malformed field ref string";var r=e.slice(0,t),n=e.slice(t+1);return new I.FieldRef(n,r,e)},I.FieldRef.prototype.toString=function(){return null==this._stringValue&&(this._stringValue=this.fieldName+I.FieldRef.joiner+this.docRef),this._stringValue}\n/*!\n * lunr.Set\n * Copyright (C) 2019 Oliver Nightingale\n */,I.Set=function(e){if(this.elements=Object.create(null),e){this.length=e.length;for(var t=0;t<this.length;t++)this.elements[e[t]]=!0}else this.length=0},I.Set.complete={intersect:function(e){return e},union:function(e){return e},contains:function(){return!0}},I.Set.empty={intersect:function(){return this},union:function(e){return e},contains:function(){return!1}},I.Set.prototype.contains=function(e){return!!this.elements[e]},I.Set.prototype.intersect=function(e){var t,r,n,i=[];if(e===I.Set.complete)return this;if(e===I.Set.empty)return e;this.length<e.length?(t=this,r=e):(t=e,r=this),n=Object.keys(t.elements);for(var o=0;o<n.length;o++){var s=n[o];s in r.elements&&i.push(s)}return new I.Set(i)},I.Set.prototype.union=function(e){return e===I.Set.complete?I.Set.complete:e===I.Set.empty?this:new I.Set(Object.keys(this.elements).concat(Object.keys(e.elements)))},I.idf=function(e,t){var r=0;for(var n in e)"_index"!=n&&(r+=Object.keys(e[n]).length);var i=(t-r+.5)/(r+.5);return Math.log(1+Math.abs(i))},I.Token=function(e,t){this.str=e||"",this.metadata=t||{}},I.Token.prototype.toString=function(){return this.str},I.Token.prototype.update=function(e){return this.str=e(this.str,this.metadata),this},I.Token.prototype.clone=function(e){return e=e||function(e){return e},new I.Token(e(this.str,this.metadata),this.metadata)}\n/*!\n * lunr.tokenizer\n * Copyright (C) 2019 Oliver Nightingale\n */,I.tokenizer=function(e,t){if(null==e||null==e)return[];if(Array.isArray(e))return e.map((function(e){return new I.Token(I.utils.asString(e).toLowerCase(),I.utils.clone(t))}));for(var r=e.toString().toLowerCase(),n=r.length,i=[],o=0,s=0;o<=n;o++){var u=o-s;if(r.charAt(o).match(I.tokenizer.separator)||o==n){if(u>0){var a=I.utils.clone(t)||{};a.position=[s,u],a.index=i.length,i.push(new I.Token(r.slice(s,o),a))}s=o+1}}return i},I.tokenizer.separator=/[\\s\\-]+/\n/*!\n * lunr.Pipeline\n * Copyright (C) 2019 Oliver Nightingale\n */,I.Pipeline=function(){this._stack=[]},I.Pipeline.registeredFunctions=Object.create(null),I.Pipeline.registerFunction=function(e,t){t in this.registeredFunctions&&I.utils.warn("Overwriting existing registered function: "+t),e.label=t,I.Pipeline.registeredFunctions[e.label]=e},I.Pipeline.warnIfFunctionNotRegistered=function(e){e.label&&e.label in this.registeredFunctions||I.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\\n",e)},I.Pipeline.load=function(e){var t=new I.Pipeline;return e.forEach((function(e){var r=I.Pipeline.registeredFunctions[e];if(!r)throw new Error("Cannot load unregistered function: "+e);t.add(r)})),t},I.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach((function(e){I.Pipeline.warnIfFunctionNotRegistered(e),this._stack.push(e)}),this)},I.Pipeline.prototype.after=function(e,t){I.Pipeline.warnIfFunctionNotRegistered(t);var r=this._stack.indexOf(e);if(-1==r)throw new Error("Cannot find existingFn");r+=1,this._stack.splice(r,0,t)},I.Pipeline.prototype.before=function(e,t){I.Pipeline.warnIfFunctionNotRegistered(t);var r=this._stack.indexOf(e);if(-1==r)throw new Error("Cannot find existingFn");this._stack.splice(r,0,t)},I.Pipeline.prototype.remove=function(e){var t=this._stack.indexOf(e);-1!=t&&this._stack.splice(t,1)},I.Pipeline.prototype.run=function(e){for(var t=this._stack.length,r=0;r<t;r++){for(var n=this._stack[r],i=[],o=0;o<e.length;o++){var s=n(e[o],o,e);if(null!=s&&""!==s)if(Array.isArray(s))for(var u=0;u<s.length;u++)i.push(s[u]);else i.push(s)}e=i}return e},I.Pipeline.prototype.runString=function(e,t){var r=new I.Token(e,t);return this.run([r]).map((function(e){return e.toString()}))},I.Pipeline.prototype.reset=function(){this._stack=[]},I.Pipeline.prototype.toJSON=function(){return this._stack.map((function(e){return I.Pipeline.warnIfFunctionNotRegistered(e),e.label}))}\n/*!\n * lunr.Vector\n * Copyright (C) 2019 Oliver Nightingale\n */,I.Vector=function(e){this._magnitude=0,this.elements=e||[]},I.Vector.prototype.positionForIndex=function(e){if(0==this.elements.length)return 0;for(var t=0,r=this.elements.length/2,n=r-t,i=Math.floor(n/2),o=this.elements[2*i];n>1&&(o<e&&(t=i),o>e&&(r=i),o!=e);)n=r-t,i=t+Math.floor(n/2),o=this.elements[2*i];return o==e||o>e?2*i:o<e?2*(i+1):void 0},I.Vector.prototype.insert=function(e,t){this.upsert(e,t,(function(){throw"duplicate index"}))},I.Vector.prototype.upsert=function(e,t,r){this._magnitude=0;var n=this.positionForIndex(e);this.elements[n]==e?this.elements[n+1]=r(this.elements[n+1],t):this.elements.splice(n,0,e,t)},I.Vector.prototype.magnitude=function(){if(this._magnitude)return this._magnitude;for(var e=0,t=this.elements.length,r=1;r<t;r+=2){var n=this.elements[r];e+=n*n}return this._magnitude=Math.sqrt(e)},I.Vector.prototype.dot=function(e){for(var t=0,r=this.elements,n=e.elements,i=r.length,o=n.length,s=0,u=0,a=0,c=0;a<i&&c<o;)(s=r[a])<(u=n[c])?a+=2:s>u?c+=2:s==u&&(t+=r[a+1]*n[c+1],a+=2,c+=2);return t},I.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},I.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),t=1,r=0;t<this.elements.length;t+=2,r++)e[r]=this.elements[t];return e},I.Vector.prototype.toJSON=function(){return this.elements}\n/*!\n * lunr.stemmer\n * Copyright (C) 2019 Oliver Nightingale\n * Includes code from - http://tartarus.org/~martin/PorterStemmer/js.txt\n */,I.stemmer=(s={ational:"ate",tional:"tion",enci:"ence",anci:"ance",izer:"ize",bli:"ble",alli:"al",entli:"ent",eli:"e",ousli:"ous",ization:"ize",ation:"ate",ator:"ate",alism:"al",iveness:"ive",fulness:"ful",ousness:"ous",aliti:"al",iviti:"ive",biliti:"ble",logi:"log"},u={icate:"ic",ative:"",alize:"al",iciti:"ic",ical:"ic",ful:"",ness:""},a="[aeiouy]",c="[^aeiou][^aeiouy]*",l=new RegExp("^([^aeiou][^aeiouy]*)?[aeiouy][aeiou]*[^aeiou][^aeiouy]*"),f=new RegExp("^([^aeiou][^aeiouy]*)?[aeiouy][aeiou]*[^aeiou][^aeiouy]*[aeiouy][aeiou]*[^aeiou][^aeiouy]*"),p=new RegExp("^([^aeiou][^aeiouy]*)?[aeiouy][aeiou]*[^aeiou][^aeiouy]*([aeiouy][aeiou]*)?$"),h=new RegExp("^([^aeiou][^aeiouy]*)?[aeiouy]"),d=/^(.+?)(ss|i)es$/,y=/^(.+?)([^s])s$/,v=/^(.+?)eed$/,m=/^(.+?)(ed|ing)$/,g=/.$/,x=/(at|bl|iz)$/,w=new RegExp("([^aeiouylsz])\\\\1$"),b=new RegExp("^"+c+a+"[^aeiouwxy]$"),S=/^(.+?[^aeiou])y$/,P=/^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/,k=/^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/,O=/^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/,E=/^(.+?)(s|t)(ion)$/,L=/^(.+?)e$/,T=/ll$/,Q=new RegExp("^"+c+a+"[^aeiouwxy]$"),j=function(e){var t,r,n,i,o,a,c;if(e.length<3)return e;if("y"==(n=e.substr(0,1))&&(e=n.toUpperCase()+e.substr(1)),o=y,(i=d).test(e)?e=e.replace(i,"$1$2"):o.test(e)&&(e=e.replace(o,"$1$2")),o=m,(i=v).test(e)){var j=i.exec(e);(i=l).test(j[1])&&(i=g,e=e.replace(i,""))}else o.test(e)&&(t=(j=o.exec(e))[1],(o=h).test(t)&&(a=w,c=b,(o=x).test(e=t)?e+="e":a.test(e)?(i=g,e=e.replace(i,"")):c.test(e)&&(e+="e")));return(i=S).test(e)&&(e=(t=(j=i.exec(e))[1])+"i"),(i=P).test(e)&&(t=(j=i.exec(e))[1],r=j[2],(i=l).test(t)&&(e=t+s[r])),(i=k).test(e)&&(t=(j=i.exec(e))[1],r=j[2],(i=l).test(t)&&(e=t+u[r])),o=E,(i=O).test(e)?(t=(j=i.exec(e))[1],(i=f).test(t)&&(e=t)):o.test(e)&&(t=(j=o.exec(e))[1]+j[2],(o=f).test(t)&&(e=t)),(i=L).test(e)&&(t=(j=i.exec(e))[1],o=p,a=Q,((i=f).test(t)||o.test(t)&&!a.test(t))&&(e=t)),o=f,(i=T).test(e)&&o.test(e)&&(i=g,e=e.replace(i,"")),"y"==n&&(e=n.toLowerCase()+e.substr(1)),e},function(e){return e.update(j)}),I.Pipeline.registerFunction(I.stemmer,"stemmer")\n/*!\n * lunr.stopWordFilter\n * Copyright (C) 2019 Oliver Nightingale\n */,I.generateStopWordFilter=function(e){var t=e.reduce((function(e,t){return e[t]=t,e}),{});return function(e){if(e&&t[e.toString()]!==e.toString())return e}},I.stopWordFilter=I.generateStopWordFilter(["a","able","about","across","after","all","almost","also","am","among","an","and","any","are","as","at","be","because","been","but","by","can","cannot","could","dear","did","do","does","either","else","ever","every","for","from","get","got","had","has","have","he","her","hers","him","his","how","however","i","if","in","into","is","it","its","just","least","let","like","likely","may","me","might","most","must","my","neither","no","nor","not","of","off","often","on","only","or","other","our","own","rather","said","say","says","she","should","since","so","some","than","that","the","their","them","then","there","these","they","this","tis","to","too","twas","us","wants","was","we","were","what","when","where","which","while","who","whom","why","will","with","would","yet","you","your"]),I.Pipeline.registerFunction(I.stopWordFilter,"stopWordFilter")\n/*!\n * lunr.trimmer\n * Copyright (C) 2019 Oliver Nightingale\n */,I.trimmer=function(e){return e.update((function(e){return e.replace(/^\\W+/,"").replace(/\\W+$/,"")}))},I.Pipeline.registerFunction(I.trimmer,"trimmer")\n/*!\n * lunr.TokenSet\n * Copyright (C) 2019 Oliver Nightingale\n */,I.TokenSet=function(){this.final=!1,this.edges={},this.id=I.TokenSet._nextId,I.TokenSet._nextId+=1},I.TokenSet._nextId=1,I.TokenSet.fromArray=function(e){for(var t=new I.TokenSet.Builder,r=0,n=e.length;r<n;r++)t.insert(e[r]);return t.finish(),t.root},I.TokenSet.fromClause=function(e){return"editDistance"in e?I.TokenSet.fromFuzzyString(e.term,e.editDistance):I.TokenSet.fromString(e.term)},I.TokenSet.fromFuzzyString=function(e,t){for(var r=new I.TokenSet,n=[{node:r,editsRemaining:t,str:e}];n.length;){var i=n.pop();if(i.str.length>0){var o,s=i.str.charAt(0);s in i.node.edges?o=i.node.edges[s]:(o=new I.TokenSet,i.node.edges[s]=o),1==i.str.length&&(o.final=!0),n.push({node:o,editsRemaining:i.editsRemaining,str:i.str.slice(1)})}if(0!=i.editsRemaining){if("*"in i.node.edges)var u=i.node.edges["*"];else{u=new I.TokenSet;i.node.edges["*"]=u}if(0==i.str.length&&(u.final=!0),n.push({node:u,editsRemaining:i.editsRemaining-1,str:i.str}),i.str.length>1&&n.push({node:i.node,editsRemaining:i.editsRemaining-1,str:i.str.slice(1)}),1==i.str.length&&(i.node.final=!0),i.str.length>=1){if("*"in i.node.edges)var a=i.node.edges["*"];else{a=new I.TokenSet;i.node.edges["*"]=a}1==i.str.length&&(a.final=!0),n.push({node:a,editsRemaining:i.editsRemaining-1,str:i.str.slice(1)})}if(i.str.length>1){var c,l=i.str.charAt(0),f=i.str.charAt(1);f in i.node.edges?c=i.node.edges[f]:(c=new I.TokenSet,i.node.edges[f]=c),1==i.str.length&&(c.final=!0),n.push({node:c,editsRemaining:i.editsRemaining-1,str:l+i.str.slice(2)})}}}return r},I.TokenSet.fromString=function(e){for(var t=new I.TokenSet,r=t,n=0,i=e.length;n<i;n++){var o=e[n],s=n==i-1;if("*"==o)t.edges[o]=t,t.final=s;else{var u=new I.TokenSet;u.final=s,t.edges[o]=u,t=u}}return r},I.TokenSet.prototype.toArray=function(){for(var e=[],t=[{prefix:"",node:this}];t.length;){var r=t.pop(),n=Object.keys(r.node.edges),i=n.length;r.node.final&&(r.prefix.charAt(0),e.push(r.prefix));for(var o=0;o<i;o++){var s=n[o];t.push({prefix:r.prefix.concat(s),node:r.node.edges[s]})}}return e},I.TokenSet.prototype.toString=function(){if(this._str)return this._str;for(var e=this.final?"1":"0",t=Object.keys(this.edges).sort(),r=t.length,n=0;n<r;n++){var i=t[n];e=e+i+this.edges[i].id}return e},I.TokenSet.prototype.intersect=function(e){for(var t=new I.TokenSet,r=void 0,n=[{qNode:e,output:t,node:this}];n.length;){r=n.pop();for(var i=Object.keys(r.qNode.edges),o=i.length,s=Object.keys(r.node.edges),u=s.length,a=0;a<o;a++)for(var c=i[a],l=0;l<u;l++){var f=s[l];if(f==c||"*"==c){var p=r.node.edges[f],h=r.qNode.edges[c],d=p.final&&h.final,y=void 0;f in r.output.edges?(y=r.output.edges[f]).final=y.final||d:((y=new I.TokenSet).final=d,r.output.edges[f]=y),n.push({qNode:h,output:y,node:p})}}}return t},I.TokenSet.Builder=function(){this.previousWord="",this.root=new I.TokenSet,this.uncheckedNodes=[],this.minimizedNodes={}},I.TokenSet.Builder.prototype.insert=function(e){var t,r=0;if(e<this.previousWord)throw new Error("Out of order word insertion");for(var n=0;n<e.length&&n<this.previousWord.length&&e[n]==this.previousWord[n];n++)r++;this.minimize(r),t=0==this.uncheckedNodes.length?this.root:this.uncheckedNodes[this.uncheckedNodes.length-1].child;for(n=r;n<e.length;n++){var i=new I.TokenSet,o=e[n];t.edges[o]=i,this.uncheckedNodes.push({parent:t,char:o,child:i}),t=i}t.final=!0,this.previousWord=e},I.TokenSet.Builder.prototype.finish=function(){this.minimize(0)},I.TokenSet.Builder.prototype.minimize=function(e){for(var t=this.uncheckedNodes.length-1;t>=e;t--){var r=this.uncheckedNodes[t],n=r.child.toString();n in this.minimizedNodes?r.parent.edges[r.char]=this.minimizedNodes[n]:(r.child._str=n,this.minimizedNodes[n]=r.child),this.uncheckedNodes.pop()}}\n/*!\n * lunr.Index\n * Copyright (C) 2019 Oliver Nightingale\n */,I.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},I.Index.prototype.search=function(e){return this.query((function(t){new I.QueryParser(e,t).parse()}))},I.Index.prototype.query=function(e){for(var t=new I.Query(this.fields),r=Object.create(null),n=Object.create(null),i=Object.create(null),o=Object.create(null),s=Object.create(null),u=0;u<this.fields.length;u++)n[this.fields[u]]=new I.Vector;e.call(t,t);for(u=0;u<t.clauses.length;u++){var a=t.clauses[u],c=null,l=I.Set.complete;c=a.usePipeline?this.pipeline.runString(a.term,{fields:a.fields}):[a.term];for(var f=0;f<c.length;f++){var p=c[f];a.term=p;var h=I.TokenSet.fromClause(a),d=this.tokenSet.intersect(h).toArray();if(0===d.length&&a.presence===I.Query.presence.REQUIRED){for(var y=0;y<a.fields.length;y++){o[R=a.fields[y]]=I.Set.empty}break}for(var v=0;v<d.length;v++){var m=d[v],g=this.invertedIndex[m],x=g._index;for(y=0;y<a.fields.length;y++){var w=g[R=a.fields[y]],b=Object.keys(w),S=m+"/"+R,P=new I.Set(b);if(a.presence==I.Query.presence.REQUIRED&&(l=l.union(P),void 0===o[R]&&(o[R]=I.Set.complete)),a.presence!=I.Query.presence.PROHIBITED){if(n[R].upsert(x,a.boost,(function(e,t){return e+t})),!i[S]){for(var k=0;k<b.length;k++){var O,E=b[k],L=new I.FieldRef(E,R),T=w[E];void 0===(O=r[L])?r[L]=new I.MatchData(m,R,T):O.add(m,R,T)}i[S]=!0}}else void 0===s[R]&&(s[R]=I.Set.empty),s[R]=s[R].union(P)}}}if(a.presence===I.Query.presence.REQUIRED)for(y=0;y<a.fields.length;y++){o[R=a.fields[y]]=o[R].intersect(l)}}var Q=I.Set.complete,j=I.Set.empty;for(u=0;u<this.fields.length;u++){var R;o[R=this.fields[u]]&&(Q=Q.intersect(o[R])),s[R]&&(j=j.union(s[R]))}var _=Object.keys(r),F=[],C=Object.create(null);if(t.isNegated()){_=Object.keys(this.fieldVectors);for(u=0;u<_.length;u++){L=_[u];var N=I.FieldRef.fromString(L);r[L]=new I.MatchData}}for(u=0;u<_.length;u++){var A=(N=I.FieldRef.fromString(_[u])).docRef;if(Q.contains(A)&&!j.contains(A)){var D,M=this.fieldVectors[N],B=n[N.fieldName].similarity(M);if(void 0!==(D=C[A]))D.score+=B,D.matchData.combine(r[N]);else{var V={ref:A,score:B,matchData:r[N]};C[A]=V,F.push(V)}}}return F.sort((function(e,t){return t.score-e.score}))},I.Index.prototype.toJSON=function(){var e=Object.keys(this.invertedIndex).sort().map((function(e){return[e,this.invertedIndex[e]]}),this),t=Object.keys(this.fieldVectors).map((function(e){return[e,this.fieldVectors[e].toJSON()]}),this);return{version:I.version,fields:this.fields,fieldVectors:t,invertedIndex:e,pipeline:this.pipeline.toJSON()}},I.Index.load=function(e){var t={},r={},n=e.fieldVectors,i=Object.create(null),o=e.invertedIndex,s=new I.TokenSet.Builder,u=I.Pipeline.load(e.pipeline);e.version!=I.version&&I.utils.warn("Version mismatch when loading serialised index. Current version of lunr \'"+I.version+"\' does not match serialized index \'"+e.version+"\'");for(var a=0;a<n.length;a++){var c=(f=n[a])[0],l=f[1];r[c]=new I.Vector(l)}for(a=0;a<o.length;a++){var f,p=(f=o[a])[0],h=f[1];s.insert(p),i[p]=h}return s.finish(),t.fields=e.fields,t.fieldVectors=r,t.invertedIndex=i,t.tokenSet=s.root,t.pipeline=u,new I.Index(t)}\n/*!\n * lunr.Builder\n * Copyright (C) 2019 Oliver Nightingale\n */,I.Builder=function(){this._ref="id",this._fields=Object.create(null),this._documents=Object.create(null),this.invertedIndex=Object.create(null),this.fieldTermFrequencies={},this.fieldLengths={},this.tokenizer=I.tokenizer,this.pipeline=new I.Pipeline,this.searchPipeline=new I.Pipeline,this.documentCount=0,this._b=.75,this._k1=1.2,this.termIndex=0,this.metadataWhitelist=[]},I.Builder.prototype.ref=function(e){this._ref=e},I.Builder.prototype.field=function(e,t){if(/\\//.test(e))throw new RangeError("Field \'"+e+"\' contains illegal character \'/\'");this._fields[e]=t||{}},I.Builder.prototype.b=function(e){this._b=e<0?0:e>1?1:e},I.Builder.prototype.k1=function(e){this._k1=e},I.Builder.prototype.add=function(e,t){var r=e[this._ref],n=Object.keys(this._fields);this._documents[r]=t||{},this.documentCount+=1;for(var i=0;i<n.length;i++){var o=n[i],s=this._fields[o].extractor,u=s?s(e):e[o],a=this.tokenizer(u,{fields:[o]}),c=this.pipeline.run(a),l=new I.FieldRef(r,o),f=Object.create(null);this.fieldTermFrequencies[l]=f,this.fieldLengths[l]=0,this.fieldLengths[l]+=c.length;for(var p=0;p<c.length;p++){var h=c[p];if(null==f[h]&&(f[h]=0),f[h]+=1,null==this.invertedIndex[h]){var d=Object.create(null);d._index=this.termIndex,this.termIndex+=1;for(var y=0;y<n.length;y++)d[n[y]]=Object.create(null);this.invertedIndex[h]=d}null==this.invertedIndex[h][o][r]&&(this.invertedIndex[h][o][r]=Object.create(null));for(var v=0;v<this.metadataWhitelist.length;v++){var m=this.metadataWhitelist[v],g=h.metadata[m];null==this.invertedIndex[h][o][r][m]&&(this.invertedIndex[h][o][r][m]=[]),this.invertedIndex[h][o][r][m].push(g)}}}},I.Builder.prototype.calculateAverageFieldLengths=function(){for(var e=Object.keys(this.fieldLengths),t=e.length,r={},n={},i=0;i<t;i++){var o=I.FieldRef.fromString(e[i]),s=o.fieldName;n[s]||(n[s]=0),n[s]+=1,r[s]||(r[s]=0),r[s]+=this.fieldLengths[o]}var u=Object.keys(this._fields);for(i=0;i<u.length;i++){var a=u[i];r[a]=r[a]/n[a]}this.averageFieldLength=r},I.Builder.prototype.createFieldVectors=function(){for(var e={},t=Object.keys(this.fieldTermFrequencies),r=t.length,n=Object.create(null),i=0;i<r;i++){for(var o=I.FieldRef.fromString(t[i]),s=o.fieldName,u=this.fieldLengths[o],a=new I.Vector,c=this.fieldTermFrequencies[o],l=Object.keys(c),f=l.length,p=this._fields[s].boost||1,h=this._documents[o.docRef].boost||1,d=0;d<f;d++){var y,v,m,g=l[d],x=c[g],w=this.invertedIndex[g]._index;void 0===n[g]?(y=I.idf(this.invertedIndex[g],this.documentCount),n[g]=y):y=n[g],v=y*((this._k1+1)*x)/(this._k1*(1-this._b+this._b*(u/this.averageFieldLength[s]))+x),v*=p,v*=h,m=Math.round(1e3*v)/1e3,a.insert(w,m)}e[o]=a}this.fieldVectors=e},I.Builder.prototype.createTokenSet=function(){this.tokenSet=I.TokenSet.fromArray(Object.keys(this.invertedIndex).sort())},I.Builder.prototype.build=function(){return this.calculateAverageFieldLengths(),this.createFieldVectors(),this.createTokenSet(),new I.Index({invertedIndex:this.invertedIndex,fieldVectors:this.fieldVectors,tokenSet:this.tokenSet,fields:Object.keys(this._fields),pipeline:this.searchPipeline})},I.Builder.prototype.use=function(e){var t=Array.prototype.slice.call(arguments,1);t.unshift(this),e.apply(this,t)},I.MatchData=function(e,t,r){for(var n=Object.create(null),i=Object.keys(r||{}),o=0;o<i.length;o++){var s=i[o];n[s]=r[s].slice()}this.metadata=Object.create(null),void 0!==e&&(this.metadata[e]=Object.create(null),this.metadata[e][t]=n)},I.MatchData.prototype.combine=function(e){for(var t=Object.keys(e.metadata),r=0;r<t.length;r++){var n=t[r],i=Object.keys(e.metadata[n]);null==this.metadata[n]&&(this.metadata[n]=Object.create(null));for(var o=0;o<i.length;o++){var s=i[o],u=Object.keys(e.metadata[n][s]);null==this.metadata[n][s]&&(this.metadata[n][s]=Object.create(null));for(var a=0;a<u.length;a++){var c=u[a];null==this.metadata[n][s][c]?this.metadata[n][s][c]=e.metadata[n][s][c]:this.metadata[n][s][c]=this.metadata[n][s][c].concat(e.metadata[n][s][c])}}}},I.MatchData.prototype.add=function(e,t,r){if(!(e in this.metadata))return this.metadata[e]=Object.create(null),void(this.metadata[e][t]=r);if(t in this.metadata[e])for(var n=Object.keys(r),i=0;i<n.length;i++){var o=n[i];o in this.metadata[e][t]?this.metadata[e][t][o]=this.metadata[e][t][o].concat(r[o]):this.metadata[e][t][o]=r[o]}else this.metadata[e][t]=r},I.Query=function(e){this.clauses=[],this.allFields=e},I.Query.wildcard=new String("*"),I.Query.wildcard.NONE=0,I.Query.wildcard.LEADING=1,I.Query.wildcard.TRAILING=2,I.Query.presence={OPTIONAL:1,REQUIRED:2,PROHIBITED:3},I.Query.prototype.clause=function(e){return"fields"in e||(e.fields=this.allFields),"boost"in e||(e.boost=1),"usePipeline"in e||(e.usePipeline=!0),"wildcard"in e||(e.wildcard=I.Query.wildcard.NONE),e.wildcard&I.Query.wildcard.LEADING&&e.term.charAt(0)!=I.Query.wildcard&&(e.term="*"+e.term),e.wildcard&I.Query.wildcard.TRAILING&&e.term.slice(-1)!=I.Query.wildcard&&(e.term=e.term+"*"),"presence"in e||(e.presence=I.Query.presence.OPTIONAL),this.clauses.push(e),this},I.Query.prototype.isNegated=function(){for(var e=0;e<this.clauses.length;e++)if(this.clauses[e].presence!=I.Query.presence.PROHIBITED)return!1;return!0},I.Query.prototype.term=function(e,t){if(Array.isArray(e))return e.forEach((function(e){this.term(e,I.utils.clone(t))}),this),this;var r=t||{};return r.term=e.toString(),this.clause(r),this},I.QueryParseError=function(e,t,r){this.name="QueryParseError",this.message=e,this.start=t,this.end=r},I.QueryParseError.prototype=new Error,I.QueryLexer=function(e){this.lexemes=[],this.str=e,this.length=e.length,this.pos=0,this.start=0,this.escapeCharPositions=[]},I.QueryLexer.prototype.run=function(){for(var e=I.QueryLexer.lexText;e;)e=e(this)},I.QueryLexer.prototype.sliceString=function(){for(var e=[],t=this.start,r=this.pos,n=0;n<this.escapeCharPositions.length;n++)r=this.escapeCharPositions[n],e.push(this.str.slice(t,r)),t=r+1;return e.push(this.str.slice(t,this.pos)),this.escapeCharPositions.length=0,e.join("")},I.QueryLexer.prototype.emit=function(e){this.lexemes.push({type:e,str:this.sliceString(),start:this.start,end:this.pos}),this.start=this.pos},I.QueryLexer.prototype.escapeCharacter=function(){this.escapeCharPositions.push(this.pos-1),this.pos+=1},I.QueryLexer.prototype.next=function(){if(this.pos>=this.length)return I.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},I.QueryLexer.prototype.width=function(){return this.pos-this.start},I.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},I.QueryLexer.prototype.backup=function(){this.pos-=1},I.QueryLexer.prototype.acceptDigitRun=function(){var e,t;do{t=(e=this.next()).charCodeAt(0)}while(t>47&&t<58);e!=I.QueryLexer.EOS&&this.backup()},I.QueryLexer.prototype.more=function(){return this.pos<this.length},I.QueryLexer.EOS="EOS",I.QueryLexer.FIELD="FIELD",I.QueryLexer.TERM="TERM",I.QueryLexer.EDIT_DISTANCE="EDIT_DISTANCE",I.QueryLexer.BOOST="BOOST",I.QueryLexer.PRESENCE="PRESENCE",I.QueryLexer.lexField=function(e){return e.backup(),e.emit(I.QueryLexer.FIELD),e.ignore(),I.QueryLexer.lexText},I.QueryLexer.lexTerm=function(e){if(e.width()>1&&(e.backup(),e.emit(I.QueryLexer.TERM)),e.ignore(),e.more())return I.QueryLexer.lexText},I.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(I.QueryLexer.EDIT_DISTANCE),I.QueryLexer.lexText},I.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(I.QueryLexer.BOOST),I.QueryLexer.lexText},I.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(I.QueryLexer.TERM)},I.QueryLexer.termSeparator=I.tokenizer.separator,I.QueryLexer.lexText=function(e){for(;;){var t=e.next();if(t==I.QueryLexer.EOS)return I.QueryLexer.lexEOS;if(92!=t.charCodeAt(0)){if(":"==t)return I.QueryLexer.lexField;if("~"==t)return e.backup(),e.width()>0&&e.emit(I.QueryLexer.TERM),I.QueryLexer.lexEditDistance;if("^"==t)return e.backup(),e.width()>0&&e.emit(I.QueryLexer.TERM),I.QueryLexer.lexBoost;if("+"==t&&1===e.width())return e.emit(I.QueryLexer.PRESENCE),I.QueryLexer.lexText;if("-"==t&&1===e.width())return e.emit(I.QueryLexer.PRESENCE),I.QueryLexer.lexText;if(t.match(I.QueryLexer.termSeparator))return I.QueryLexer.lexTerm}else e.escapeCharacter()}},I.QueryParser=function(e,t){this.lexer=new I.QueryLexer(e),this.query=t,this.currentClause={},this.lexemeIdx=0},I.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=I.QueryParser.parseClause;e;)e=e(this);return this.query},I.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},I.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},I.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},I.QueryParser.parseClause=function(e){var t=e.peekLexeme();if(null!=t)switch(t.type){case I.QueryLexer.PRESENCE:return I.QueryParser.parsePresence;case I.QueryLexer.FIELD:return I.QueryParser.parseField;case I.QueryLexer.TERM:return I.QueryParser.parseTerm;default:var r="expected either a field or a term, found "+t.type;throw t.str.length>=1&&(r+=" with value \'"+t.str+"\'"),new I.QueryParseError(r,t.start,t.end)}},I.QueryParser.parsePresence=function(e){var t=e.consumeLexeme();if(null!=t){switch(t.str){case"-":e.currentClause.presence=I.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=I.Query.presence.REQUIRED;break;default:var r="unrecognised presence operator\'"+t.str+"\'";throw new I.QueryParseError(r,t.start,t.end)}var n=e.peekLexeme();if(null==n){r="expecting term or field, found nothing";throw new I.QueryParseError(r,t.start,t.end)}switch(n.type){case I.QueryLexer.FIELD:return I.QueryParser.parseField;case I.QueryLexer.TERM:return I.QueryParser.parseTerm;default:r="expecting term or field, found \'"+n.type+"\'";throw new I.QueryParseError(r,n.start,n.end)}}},I.QueryParser.parseField=function(e){var t=e.consumeLexeme();if(null!=t){if(-1==e.query.allFields.indexOf(t.str)){var r=e.query.allFields.map((function(e){return"\'"+e+"\'"})).join(", "),n="unrecognised field \'"+t.str+"\', possible fields: "+r;throw new I.QueryParseError(n,t.start,t.end)}e.currentClause.fields=[t.str];var i=e.peekLexeme();if(null==i){n="expecting term, found nothing";throw new I.QueryParseError(n,t.start,t.end)}switch(i.type){case I.QueryLexer.TERM:return I.QueryParser.parseTerm;default:n="expecting term, found \'"+i.type+"\'";throw new I.QueryParseError(n,i.start,i.end)}}},I.QueryParser.parseTerm=function(e){var t=e.consumeLexeme();if(null!=t){e.currentClause.term=t.str.toLowerCase(),-1!=t.str.indexOf("*")&&(e.currentClause.usePipeline=!1);var r=e.peekLexeme();if(null!=r)switch(r.type){case I.QueryLexer.TERM:return e.nextClause(),I.QueryParser.parseTerm;case I.QueryLexer.FIELD:return e.nextClause(),I.QueryParser.parseField;case I.QueryLexer.EDIT_DISTANCE:return I.QueryParser.parseEditDistance;case I.QueryLexer.BOOST:return I.QueryParser.parseBoost;case I.QueryLexer.PRESENCE:return e.nextClause(),I.QueryParser.parsePresence;default:var n="Unexpected lexeme type \'"+r.type+"\'";throw new I.QueryParseError(n,r.start,r.end)}else e.nextClause()}},I.QueryParser.parseEditDistance=function(e){var t=e.consumeLexeme();if(null!=t){var r=parseInt(t.str,10);if(isNaN(r)){var n="edit distance must be numeric";throw new I.QueryParseError(n,t.start,t.end)}e.currentClause.editDistance=r;var i=e.peekLexeme();if(null!=i)switch(i.type){case I.QueryLexer.TERM:return e.nextClause(),I.QueryParser.parseTerm;case I.QueryLexer.FIELD:return e.nextClause(),I.QueryParser.parseField;case I.QueryLexer.EDIT_DISTANCE:return I.QueryParser.parseEditDistance;case I.QueryLexer.BOOST:return I.QueryParser.parseBoost;case I.QueryLexer.PRESENCE:return e.nextClause(),I.QueryParser.parsePresence;default:n="Unexpected lexeme type \'"+i.type+"\'";throw new I.QueryParseError(n,i.start,i.end)}else e.nextClause()}},I.QueryParser.parseBoost=function(e){var t=e.consumeLexeme();if(null!=t){var r=parseInt(t.str,10);if(isNaN(r)){var n="boost must be numeric";throw new I.QueryParseError(n,t.start,t.end)}e.currentClause.boost=r;var i=e.peekLexeme();if(null!=i)switch(i.type){case I.QueryLexer.TERM:return e.nextClause(),I.QueryParser.parseTerm;case I.QueryLexer.FIELD:return e.nextClause(),I.QueryParser.parseField;case I.QueryLexer.EDIT_DISTANCE:return I.QueryParser.parseEditDistance;case I.QueryLexer.BOOST:return I.QueryParser.parseBoost;case I.QueryLexer.PRESENCE:return e.nextClause(),I.QueryParser.parsePresence;default:n="Unexpected lexeme type \'"+i.type+"\'";throw new I.QueryParseError(n,i.start,i.end)}else e.nextClause()}},void 0===(i="function"==typeof(n=function(){return I})?n.call(t,r,t,e):n)||(e.exports=i)}()},function(e,t){e.exports=function(e){try{return!!e()}catch(e){return!0}}},function(e,t,r){var n=r(8),i=r(9),o=r(22);e.exports=n?function(e,t,r){return i.f(e,t,o(1,r))}:function(e,t,r){return e[t]=r,e}},function(e,t){e.exports=function(e){return"object"==typeof e?null!==e:"function"==typeof e}},function(e,t,r){var n=r(5);e.exports=!n((function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]}))},function(e,t,r){var n=r(8),i=r(34),o=r(2),s=r(35),u=Object.defineProperty;t.f=n?u:function(e,t,r){if(o(e),t=s(t,!0),o(r),i)try{return u(e,t,r)}catch(e){}if("get"in r||"set"in r)throw TypeError("Accessors not supported");return"value"in r&&(e[t]=r.value),e}},function(e,t,r){var n=r(0),i=r(6),o=r(3),s=r(20),u=r(23),a=r(16),c=a.get,l=a.enforce,f=String(String).split("String");(e.exports=function(e,t,r,u){var a=!!u&&!!u.unsafe,c=!!u&&!!u.enumerable,p=!!u&&!!u.noTargetGet;"function"==typeof r&&("string"!=typeof t||o(r,"name")||i(r,"name",t),l(r).source=f.join("string"==typeof t?t:"")),e!==n?(a?!p&&e[t]&&(c=!0):delete e[t],c?e[t]=r:i(e,t,r)):c?e[t]=r:s(t,r)})(Function.prototype,"toString",(function(){return"function"==typeof this&&c(this).source||u(this)}))},function(e,t,r){var n=r(40),i=r(0),o=function(e){return"function"==typeof e?e:void 0};e.exports=function(e,t){return arguments.length<2?o(n[e])||o(i[e]):n[e]&&n[e][t]||i[e]&&i[e][t]}},function(e,t){e.exports=!1},function(e,t){var r={}.toString;e.exports=function(e){return r.call(e).slice(8,-1)}},function(e,t){e.exports={}},function(e,t){e.exports=function(e){if("function"!=typeof e)throw TypeError(String(e)+" is not a function");return e}},function(e,t,r){var n,i,o,s=r(61),u=r(0),a=r(7),c=r(6),l=r(3),f=r(24),p=r(25),h=u.WeakMap;if(s){var d=new h,y=d.get,v=d.has,m=d.set;n=function(e,t){return m.call(d,e,t),t},i=function(e){return y.call(d,e)||{}},o=function(e){return v.call(d,e)}}else{var g=f("state");p[g]=!0,n=function(e,t){return c(e,g,t),t},i=function(e){return l(e,g)?e[g]:{}},o=function(e){return l(e,g)}}e.exports={set:n,get:i,has:o,enforce:function(e){return o(e)?i(e):n(e,{})},getterFor:function(e){return function(t){var r;if(!a(t)||(r=i(t)).type!==e)throw TypeError("Incompatible receiver, "+e+" required");return r}}}},function(e,t,r){var n=r(0),i=r(28).f,o=r(6),s=r(10),u=r(20),a=r(67),c=r(43);e.exports=function(e,t){var r,l,f,p,h,d=e.target,y=e.global,v=e.stat;if(r=y?n:v?n[d]||u(d,{}):(n[d]||{}).prototype)for(l in t){if(p=t[l],f=e.noTargetGet?(h=i(r,l))&&h.value:r[l],!c(y?l:d+(v?".":"#")+l,e.forced)&&void 0!==f){if(typeof p==typeof f)continue;a(p,f)}(e.sham||f&&f.sham)&&o(p,"sham",!0),s(r,l,p,e)}}},function(e,t,r){var n=r(66),i=r(27);e.exports=function(e){return n(i(e))}},function(e,t,r){var n={};n[r(1)("toStringTag")]="z",e.exports="[object z]"===String(n)},function(e,t,r){var n=r(0),i=r(6);e.exports=function(e,t){try{i(n,e,t)}catch(r){n[e]=t}return t}},function(e,t,r){var n=r(0),i=r(7),o=n.document,s=i(o)&&i(o.createElement);e.exports=function(e){return s?o.createElement(e):{}}},function(e,t){e.exports=function(e,t){return{enumerable:!(1&e),configurable:!(2&e),writable:!(4&e),value:t}}},function(e,t,r){var n=r(33),i=Function.toString;"function"!=typeof n.inspectSource&&(n.inspectSource=function(e){return i.call(e)}),e.exports=n.inspectSource},function(e,t,r){var n=r(32),i=r(36),o=n("keys");e.exports=function(e){return o[e]||(o[e]=i(e))}},function(e,t){e.exports={}},function(e,t){var r=Math.ceil,n=Math.floor;e.exports=function(e){return isNaN(e=+e)?0:(e>0?n:r)(e)}},function(e,t){e.exports=function(e){if(null==e)throw TypeError("Can\'t call method on "+e);return e}},function(e,t,r){var n=r(8),i=r(65),o=r(22),s=r(18),u=r(35),a=r(3),c=r(34),l=Object.getOwnPropertyDescriptor;t.f=n?l:function(e,t){if(e=s(e),t=u(t,!0),c)try{return l(e,t)}catch(e){}if(a(e,t))return o(!i.f.call(e,t),e[t])}},function(e,t){e.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},function(e,t,r){var n=r(9).f,i=r(3),o=r(1)("toStringTag");e.exports=function(e,t,r){e&&!i(e=r?e:e.prototype,o)&&n(e,o,{configurable:!0,value:t})}},function(e,t,r){"use strict";var n=r(15),i=function(e){var t,r;this.promise=new e((function(e,n){if(void 0!==t||void 0!==r)throw TypeError("Bad Promise constructor");t=e,r=n})),this.resolve=n(t),this.reject=n(r)};e.exports.f=function(e){return new i(e)}},function(e,t,r){var n=r(12),i=r(33);(e.exports=function(e,t){return i[e]||(i[e]=void 0!==t?t:{})})("versions",[]).push({version:"3.6.4",mode:n?"pure":"global",copyright:"© 2020 Denis Pushkarev (zloirock.ru)"})},function(e,t,r){var n=r(0),i=r(20),o=n["__core-js_shared__"]||i("__core-js_shared__",{});e.exports=o},function(e,t,r){var n=r(8),i=r(5),o=r(21);e.exports=!n&&!i((function(){return 7!=Object.defineProperty(o("div"),"a",{get:function(){return 7}}).a}))},function(e,t,r){var n=r(7);e.exports=function(e,t){if(!n(e))return e;var r,i;if(t&&"function"==typeof(r=e.toString)&&!n(i=r.call(e)))return i;if("function"==typeof(r=e.valueOf)&&!n(i=r.call(e)))return i;if(!t&&"function"==typeof(r=e.toString)&&!n(i=r.call(e)))return i;throw TypeError("Can\'t convert object to primitive value")}},function(e,t){var r=0,n=Math.random();e.exports=function(e){return"Symbol("+String(void 0===e?"":e)+")_"+(++r+n).toString(36)}},function(e,t,r){var n=r(5);e.exports=!!Object.getOwnPropertySymbols&&!n((function(){return!String(Symbol())}))},function(e,t,r){var n=r(19),i=r(13),o=r(1)("toStringTag"),s="Arguments"==i(function(){return arguments}());e.exports=n?i:function(e){var t,r,n;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(r=function(e,t){try{return e[t]}catch(e){}}(t=Object(e),o))?r:s?i(t):"Object"==(n=i(t))&&"function"==typeof t.callee?"Arguments":n}},function(e,t,r){"use strict";var n=r(17),i=r(73),o=r(45),s=r(78),u=r(30),a=r(6),c=r(10),l=r(1),f=r(12),p=r(14),h=r(44),d=h.IteratorPrototype,y=h.BUGGY_SAFARI_ITERATORS,v=l("iterator"),m=function(){return this};e.exports=function(e,t,r,l,h,g,x){i(r,t,l);var w,b,S,P=function(e){if(e===h&&T)return T;if(!y&&e in E)return E[e];switch(e){case"keys":case"values":case"entries":return function(){return new r(this,e)}}return function(){return new r(this)}},k=t+" Iterator",O=!1,E=e.prototype,L=E[v]||E["@@iterator"]||h&&E[h],T=!y&&L||P(h),Q="Array"==t&&E.entries||L;if(Q&&(w=o(Q.call(new e)),d!==Object.prototype&&w.next&&(f||o(w)===d||(s?s(w,d):"function"!=typeof w[v]&&a(w,v,m)),u(w,k,!0,!0),f&&(p[k]=m))),"values"==h&&L&&"values"!==L.name&&(O=!0,T=function(){return L.call(this)}),f&&!x||E[v]===T||a(E,v,T),p[t]=T,h)if(b={values:P("values"),keys:g?T:P("keys"),entries:P("entries")},x)for(S in b)!y&&!O&&S in E||c(E,S,b[S]);else n({target:t,proto:!0,forced:y||O},b);return b}},function(e,t,r){var n=r(0);e.exports=n},function(e,t,r){var n=r(3),i=r(18),o=r(70).indexOf,s=r(25);e.exports=function(e,t){var r,u=i(e),a=0,c=[];for(r in u)!n(s,r)&&n(u,r)&&c.push(r);for(;t.length>a;)n(u,r=t[a++])&&(~o(c,r)||c.push(r));return c}},function(e,t,r){var n=r(26),i=Math.min;e.exports=function(e){return e>0?i(n(e),9007199254740991):0}},function(e,t,r){var n=r(5),i=/#|\\.prototype\\./,o=function(e,t){var r=u[s(e)];return r==c||r!=a&&("function"==typeof t?n(t):!!t)},s=o.normalize=function(e){return String(e).replace(i,".").toLowerCase()},u=o.data={},a=o.NATIVE="N",c=o.POLYFILL="P";e.exports=o},function(e,t,r){"use strict";var n,i,o,s=r(45),u=r(6),a=r(3),c=r(1),l=r(12),f=c("iterator"),p=!1;[].keys&&("next"in(o=[].keys())?(i=s(s(o)))!==Object.prototype&&(n=i):p=!0),null==n&&(n={}),l||a(n,f)||u(n,f,(function(){return this})),e.exports={IteratorPrototype:n,BUGGY_SAFARI_ITERATORS:p}},function(e,t,r){var n=r(3),i=r(74),o=r(24),s=r(75),u=o("IE_PROTO"),a=Object.prototype;e.exports=s?Object.getPrototypeOf:function(e){return e=i(e),n(e,u)?e[u]:"function"==typeof e.constructor&&e instanceof e.constructor?e.constructor.prototype:e instanceof Object?a:null}},function(e,t,r){var n,i=r(2),o=r(76),s=r(29),u=r(25),a=r(47),c=r(21),l=r(24),f=l("IE_PROTO"),p=function(){},h=function(e){return"<script>"+e+"<\\/script>"},d=function(){try{n=document.domain&&new ActiveXObject("htmlfile")}catch(e){}var e,t;d=n?function(e){e.write(h("")),e.close();var t=e.parentWindow.Object;return e=null,t}(n):((t=c("iframe")).style.display="none",a.appendChild(t),t.src=String("javascript:"),(e=t.contentWindow.document).open(),e.write(h("document.F=Object")),e.close(),e.F);for(var r=s.length;r--;)delete d.prototype[s[r]];return d()};u[f]=!0,e.exports=Object.create||function(e,t){var r;return null!==e?(p.prototype=i(e),r=new p,p.prototype=null,r[f]=e):r=d(),void 0===t?r:o(r,t)}},function(e,t,r){var n=r(11);e.exports=n("document","documentElement")},function(e,t,r){var n=r(0);e.exports=n.Promise},function(e,t,r){var n=r(2),i=r(88),o=r(42),s=r(50),u=r(89),a=r(90),c=function(e,t){this.stopped=e,this.result=t};(e.exports=function(e,t,r,l,f){var p,h,d,y,v,m,g,x=s(t,r,l?2:1);if(f)p=e;else{if("function"!=typeof(h=u(e)))throw TypeError("Target is not iterable");if(i(h)){for(d=0,y=o(e.length);y>d;d++)if((v=l?x(n(g=e[d])[0],g[1]):x(e[d]))&&v instanceof c)return v;return new c(!1)}p=h.call(e)}for(m=p.next;!(g=m.call(p)).done;)if("object"==typeof(v=a(p,x,g.value,l))&&v&&v instanceof c)return v;return new c(!1)}).stop=function(e){return new c(!0,e)}},function(e,t,r){var n=r(15);e.exports=function(e,t,r){if(n(e),void 0===t)return e;switch(r){case 0:return function(){return e.call(t)};case 1:return function(r){return e.call(t,r)};case 2:return function(r,n){return e.call(t,r,n)};case 3:return function(r,n,i){return e.call(t,r,n,i)}}return function(){return e.apply(t,arguments)}}},function(e,t,r){var n=r(2),i=r(15),o=r(1)("species");e.exports=function(e,t){var r,s=n(e).constructor;return void 0===s||null==(r=n(s)[o])?t:i(r)}},function(e,t,r){var n,i,o,s=r(0),u=r(5),a=r(13),c=r(50),l=r(47),f=r(21),p=r(53),h=s.location,d=s.setImmediate,y=s.clearImmediate,v=s.process,m=s.MessageChannel,g=s.Dispatch,x=0,w={},b=function(e){if(w.hasOwnProperty(e)){var t=w[e];delete w[e],t()}},S=function(e){return function(){b(e)}},P=function(e){b(e.data)},k=function(e){s.postMessage(e+"",h.protocol+"//"+h.host)};d&&y||(d=function(e){for(var t=[],r=1;arguments.length>r;)t.push(arguments[r++]);return w[++x]=function(){("function"==typeof e?e:Function(e)).apply(void 0,t)},n(x),x},y=function(e){delete w[e]},"process"==a(v)?n=function(e){v.nextTick(S(e))}:g&&g.now?n=function(e){g.now(S(e))}:m&&!p?(o=(i=new m).port2,i.port1.onmessage=P,n=c(o.postMessage,o,1)):!s.addEventListener||"function"!=typeof postMessage||s.importScripts||u(k)?n="onreadystatechange"in f("script")?function(e){l.appendChild(f("script")).onreadystatechange=function(){l.removeChild(this),b(e)}}:function(e){setTimeout(S(e),0)}:(n=k,s.addEventListener("message",P,!1))),e.exports={set:d,clear:y}},function(e,t,r){var n=r(54);e.exports=/(iphone|ipod|ipad).*applewebkit/i.test(n)},function(e,t,r){var n=r(11);e.exports=n("navigator","userAgent")||""},function(e,t,r){var n=r(2),i=r(7),o=r(31);e.exports=function(e,t){if(n(e),i(t)&&t.constructor===e)return t;var r=o.f(e);return(0,r.resolve)(t),r.promise}},function(e,t){e.exports=function(e){try{return{error:!1,value:e()}}catch(e){return{error:!0,value:e}}}},function(e,t,r){r(58),r(63),r(80),r(84),r(95),r(96);var n=r(40);e.exports=n.Promise},function(e,t,r){var n=r(19),i=r(10),o=r(62);n||i(Object.prototype,"toString",o,{unsafe:!0})},function(e,t){var r;r=function(){return this}();try{r=r||new Function("return this")()}catch(e){"object"==typeof window&&(r=window)}e.exports=r},function(e,t,r){var n=r(37);e.exports=n&&!Symbol.sham&&"symbol"==typeof Symbol.iterator},function(e,t,r){var n=r(0),i=r(23),o=n.WeakMap;e.exports="function"==typeof o&&/native code/.test(i(o))},function(e,t,r){"use strict";var n=r(19),i=r(38);e.exports=n?{}.toString:function(){return"[object "+i(this)+"]"}},function(e,t,r){"use strict";var n=r(64).charAt,i=r(16),o=r(39),s=i.set,u=i.getterFor("String Iterator");o(String,"String",(function(e){s(this,{type:"String Iterator",string:String(e),index:0})}),(function(){var e,t=u(this),r=t.string,i=t.index;return i>=r.length?{value:void 0,done:!0}:(e=n(r,i),t.index+=e.length,{value:e,done:!1})}))},function(e,t,r){var n=r(26),i=r(27),o=function(e){return function(t,r){var o,s,u=String(i(t)),a=n(r),c=u.length;return a<0||a>=c?e?"":void 0:(o=u.charCodeAt(a))<55296||o>56319||a+1===c||(s=u.charCodeAt(a+1))<56320||s>57343?e?u.charAt(a):o:e?u.slice(a,a+2):s-56320+(o-55296<<10)+65536}};e.exports={codeAt:o(!1),charAt:o(!0)}},function(e,t,r){"use strict";var n={}.propertyIsEnumerable,i=Object.getOwnPropertyDescriptor,o=i&&!n.call({1:2},1);t.f=o?function(e){var t=i(this,e);return!!t&&t.enumerable}:n},function(e,t,r){var n=r(5),i=r(13),o="".split;e.exports=n((function(){return!Object("z").propertyIsEnumerable(0)}))?function(e){return"String"==i(e)?o.call(e,""):Object(e)}:Object},function(e,t,r){var n=r(3),i=r(68),o=r(28),s=r(9);e.exports=function(e,t){for(var r=i(t),u=s.f,a=o.f,c=0;c<r.length;c++){var l=r[c];n(e,l)||u(e,l,a(t,l))}}},function(e,t,r){var n=r(11),i=r(69),o=r(72),s=r(2);e.exports=n("Reflect","ownKeys")||function(e){var t=i.f(s(e)),r=o.f;return r?t.concat(r(e)):t}},function(e,t,r){var n=r(41),i=r(29).concat("length","prototype");t.f=Object.getOwnPropertyNames||function(e){return n(e,i)}},function(e,t,r){var n=r(18),i=r(42),o=r(71),s=function(e){return function(t,r,s){var u,a=n(t),c=i(a.length),l=o(s,c);if(e&&r!=r){for(;c>l;)if((u=a[l++])!=u)return!0}else for(;c>l;l++)if((e||l in a)&&a[l]===r)return e||l||0;return!e&&-1}};e.exports={includes:s(!0),indexOf:s(!1)}},function(e,t,r){var n=r(26),i=Math.max,o=Math.min;e.exports=function(e,t){var r=n(e);return r<0?i(r+t,0):o(r,t)}},function(e,t){t.f=Object.getOwnPropertySymbols},function(e,t,r){"use strict";var n=r(44).IteratorPrototype,i=r(46),o=r(22),s=r(30),u=r(14),a=function(){return this};e.exports=function(e,t,r){var c=t+" Iterator";return e.prototype=i(n,{next:o(1,r)}),s(e,c,!1,!0),u[c]=a,e}},function(e,t,r){var n=r(27);e.exports=function(e){return Object(n(e))}},function(e,t,r){var n=r(5);e.exports=!n((function(){function e(){}return e.prototype.constructor=null,Object.getPrototypeOf(new e)!==e.prototype}))},function(e,t,r){var n=r(8),i=r(9),o=r(2),s=r(77);e.exports=n?Object.defineProperties:function(e,t){o(e);for(var r,n=s(t),u=n.length,a=0;u>a;)i.f(e,r=n[a++],t[r]);return e}},function(e,t,r){var n=r(41),i=r(29);e.exports=Object.keys||function(e){return n(e,i)}},function(e,t,r){var n=r(2),i=r(79);e.exports=Object.setPrototypeOf||("__proto__"in{}?function(){var e,t=!1,r={};try{(e=Object.getOwnPropertyDescriptor(Object.prototype,"__proto__").set).call(r,[]),t=r instanceof Array}catch(e){}return function(r,o){return n(r),i(o),t?e.call(r,o):r.__proto__=o,r}}():void 0)},function(e,t,r){var n=r(7);e.exports=function(e){if(!n(e)&&null!==e)throw TypeError("Can\'t set "+String(e)+" as a prototype");return e}},function(e,t,r){var n=r(0),i=r(81),o=r(82),s=r(6),u=r(1),a=u("iterator"),c=u("toStringTag"),l=o.values;for(var f in i){var p=n[f],h=p&&p.prototype;if(h){if(h[a]!==l)try{s(h,a,l)}catch(e){h[a]=l}if(h[c]||s(h,c,f),i[f])for(var d in o)if(h[d]!==o[d])try{s(h,d,o[d])}catch(e){h[d]=o[d]}}}},function(e,t){e.exports={CSSRuleList:0,CSSStyleDeclaration:0,CSSValueList:0,ClientRectList:0,DOMRectList:0,DOMStringList:0,DOMTokenList:1,DataTransferItemList:0,FileList:0,HTMLAllCollection:0,HTMLCollection:0,HTMLFormElement:0,HTMLSelectElement:0,MediaList:0,MimeTypeArray:0,NamedNodeMap:0,NodeList:1,PaintRequestList:0,Plugin:0,PluginArray:0,SVGLengthList:0,SVGNumberList:0,SVGPathSegList:0,SVGPointList:0,SVGStringList:0,SVGTransformList:0,SourceBufferList:0,StyleSheetList:0,TextTrackCueList:0,TextTrackList:0,TouchList:0}},function(e,t,r){"use strict";var n=r(18),i=r(83),o=r(14),s=r(16),u=r(39),a=s.set,c=s.getterFor("Array Iterator");e.exports=u(Array,"Array",(function(e,t){a(this,{type:"Array Iterator",target:n(e),index:0,kind:t})}),(function(){var e=c(this),t=e.target,r=e.kind,n=e.index++;return!t||n>=t.length?(e.target=void 0,{value:void 0,done:!0}):"keys"==r?{value:n,done:!1}:"values"==r?{value:t[n],done:!1}:{value:[n,t[n]],done:!1}}),"values"),o.Arguments=o.Array,i("keys"),i("values"),i("entries")},function(e,t,r){var n=r(1),i=r(46),o=r(9),s=n("unscopables"),u=Array.prototype;null==u[s]&&o.f(u,s,{configurable:!0,value:i(null)}),e.exports=function(e){u[s][e]=!0}},function(e,t,r){"use strict";var n,i,o,s,u=r(17),a=r(12),c=r(0),l=r(11),f=r(48),p=r(10),h=r(85),d=r(30),y=r(86),v=r(7),m=r(15),g=r(87),x=r(13),w=r(23),b=r(49),S=r(91),P=r(51),k=r(52).set,O=r(92),E=r(55),L=r(93),T=r(31),Q=r(56),j=r(16),I=r(43),R=r(1),_=r(94),F=R("species"),C="Promise",N=j.get,A=j.set,D=j.getterFor(C),M=f,B=c.TypeError,V=c.document,z=c.process,W=l("fetch"),$=T.f,q=$,G="process"==x(z),U=!!(V&&V.createEvent&&c.dispatchEvent),H=I(C,(function(){if(!(w(M)!==String(M))){if(66===_)return!0;if(!G&&"function"!=typeof PromiseRejectionEvent)return!0}if(a&&!M.prototype.finally)return!0;if(_>=51&&/native code/.test(M))return!1;var e=M.resolve(1),t=function(e){e((function(){}),(function(){}))};return(e.constructor={})[F]=t,!(e.then((function(){}))instanceof t)})),J=H||!S((function(e){M.all(e).catch((function(){}))})),Y=function(e){var t;return!(!v(e)||"function"!=typeof(t=e.then))&&t},K=function(e,t,r){if(!t.notified){t.notified=!0;var n=t.reactions;O((function(){for(var i=t.value,o=1==t.state,s=0;n.length>s;){var u,a,c,l=n[s++],f=o?l.ok:l.fail,p=l.resolve,h=l.reject,d=l.domain;try{f?(o||(2===t.rejection&&te(e,t),t.rejection=1),!0===f?u=i:(d&&d.enter(),u=f(i),d&&(d.exit(),c=!0)),u===l.promise?h(B("Promise-chain cycle")):(a=Y(u))?a.call(u,p,h):p(u)):h(i)}catch(e){d&&!c&&d.exit(),h(e)}}t.reactions=[],t.notified=!1,r&&!t.rejection&&Z(e,t)}))}},X=function(e,t,r){var n,i;U?((n=V.createEvent("Event")).promise=t,n.reason=r,n.initEvent(e,!1,!0),c.dispatchEvent(n)):n={promise:t,reason:r},(i=c["on"+e])?i(n):"unhandledrejection"===e&&L("Unhandled promise rejection",r)},Z=function(e,t){k.call(c,(function(){var r,n=t.value;if(ee(t)&&(r=Q((function(){G?z.emit("unhandledRejection",n,e):X("unhandledrejection",e,n)})),t.rejection=G||ee(t)?2:1,r.error))throw r.value}))},ee=function(e){return 1!==e.rejection&&!e.parent},te=function(e,t){k.call(c,(function(){G?z.emit("rejectionHandled",e):X("rejectionhandled",e,t.value)}))},re=function(e,t,r,n){return function(i){e(t,r,i,n)}},ne=function(e,t,r,n){t.done||(t.done=!0,n&&(t=n),t.value=r,t.state=2,K(e,t,!0))},ie=function(e,t,r,n){if(!t.done){t.done=!0,n&&(t=n);try{if(e===r)throw B("Promise can\'t be resolved itself");var i=Y(r);i?O((function(){var n={done:!1};try{i.call(r,re(ie,e,n,t),re(ne,e,n,t))}catch(r){ne(e,n,r,t)}})):(t.value=r,t.state=1,K(e,t,!1))}catch(r){ne(e,{done:!1},r,t)}}};H&&(M=function(e){g(this,M,C),m(e),n.call(this);var t=N(this);try{e(re(ie,this,t),re(ne,this,t))}catch(e){ne(this,t,e)}},(n=function(e){A(this,{type:C,done:!1,notified:!1,parent:!1,reactions:[],rejection:!1,state:0,value:void 0})}).prototype=h(M.prototype,{then:function(e,t){var r=D(this),n=$(P(this,M));return n.ok="function"!=typeof e||e,n.fail="function"==typeof t&&t,n.domain=G?z.domain:void 0,r.parent=!0,r.reactions.push(n),0!=r.state&&K(this,r,!1),n.promise},catch:function(e){return this.then(void 0,e)}}),i=function(){var e=new n,t=N(e);this.promise=e,this.resolve=re(ie,e,t),this.reject=re(ne,e,t)},T.f=$=function(e){return e===M||e===o?new i(e):q(e)},a||"function"!=typeof f||(s=f.prototype.then,p(f.prototype,"then",(function(e,t){var r=this;return new M((function(e,t){s.call(r,e,t)})).then(e,t)}),{unsafe:!0}),"function"==typeof W&&u({global:!0,enumerable:!0,forced:!0},{fetch:function(e){return E(M,W.apply(c,arguments))}}))),u({global:!0,wrap:!0,forced:H},{Promise:M}),d(M,C,!1,!0),y(C),o=l(C),u({target:C,stat:!0,forced:H},{reject:function(e){var t=$(this);return t.reject.call(void 0,e),t.promise}}),u({target:C,stat:!0,forced:a||H},{resolve:function(e){return E(a&&this===o?M:this,e)}}),u({target:C,stat:!0,forced:J},{all:function(e){var t=this,r=$(t),n=r.resolve,i=r.reject,o=Q((function(){var r=m(t.resolve),o=[],s=0,u=1;b(e,(function(e){var a=s++,c=!1;o.push(void 0),u++,r.call(t,e).then((function(e){c||(c=!0,o[a]=e,--u||n(o))}),i)})),--u||n(o)}));return o.error&&i(o.value),r.promise},race:function(e){var t=this,r=$(t),n=r.reject,i=Q((function(){var i=m(t.resolve);b(e,(function(e){i.call(t,e).then(r.resolve,n)}))}));return i.error&&n(i.value),r.promise}})},function(e,t,r){var n=r(10);e.exports=function(e,t,r){for(var i in t)n(e,i,t[i],r);return e}},function(e,t,r){"use strict";var n=r(11),i=r(9),o=r(1),s=r(8),u=o("species");e.exports=function(e){var t=n(e),r=i.f;s&&t&&!t[u]&&r(t,u,{configurable:!0,get:function(){return this}})}},function(e,t){e.exports=function(e,t,r){if(!(e instanceof t))throw TypeError("Incorrect "+(r?r+" ":"")+"invocation");return e}},function(e,t,r){var n=r(1),i=r(14),o=n("iterator"),s=Array.prototype;e.exports=function(e){return void 0!==e&&(i.Array===e||s[o]===e)}},function(e,t,r){var n=r(38),i=r(14),o=r(1)("iterator");e.exports=function(e){if(null!=e)return e[o]||e["@@iterator"]||i[n(e)]}},function(e,t,r){var n=r(2);e.exports=function(e,t,r,i){try{return i?t(n(r)[0],r[1]):t(r)}catch(t){var o=e.return;throw void 0!==o&&n(o.call(e)),t}}},function(e,t,r){var n=r(1)("iterator"),i=!1;try{var o=0,s={next:function(){return{done:!!o++}},return:function(){i=!0}};s[n]=function(){return this},Array.from(s,(function(){throw 2}))}catch(e){}e.exports=function(e,t){if(!t&&!i)return!1;var r=!1;try{var o={};o[n]=function(){return{next:function(){return{done:r=!0}}}},e(o)}catch(e){}return r}},function(e,t,r){var n,i,o,s,u,a,c,l,f=r(0),p=r(28).f,h=r(13),d=r(52).set,y=r(53),v=f.MutationObserver||f.WebKitMutationObserver,m=f.process,g=f.Promise,x="process"==h(m),w=p(f,"queueMicrotask"),b=w&&w.value;b||(n=function(){var e,t;for(x&&(e=m.domain)&&e.exit();i;){t=i.fn,i=i.next;try{t()}catch(e){throw i?s():o=void 0,e}}o=void 0,e&&e.enter()},x?s=function(){m.nextTick(n)}:v&&!y?(u=!0,a=document.createTextNode(""),new v(n).observe(a,{characterData:!0}),s=function(){a.data=u=!u}):g&&g.resolve?(c=g.resolve(void 0),l=c.then,s=function(){l.call(c,n)}):s=function(){d.call(f,n)}),e.exports=b||function(e){var t={fn:e,next:void 0};o&&(o.next=t),i||(i=t,s()),o=t}},function(e,t,r){var n=r(0);e.exports=function(e,t){var r=n.console;r&&r.error&&(1===arguments.length?r.error(e):r.error(e,t))}},function(e,t,r){var n,i,o=r(0),s=r(54),u=o.process,a=u&&u.versions,c=a&&a.v8;c?i=(n=c.split("."))[0]+n[1]:s&&(!(n=s.match(/Edge\\/(\\d+)/))||n[1]>=74)&&(n=s.match(/Chrome\\/(\\d+)/))&&(i=n[1]),e.exports=i&&+i},function(e,t,r){"use strict";var n=r(17),i=r(15),o=r(31),s=r(56),u=r(49);n({target:"Promise",stat:!0},{allSettled:function(e){var t=this,r=o.f(t),n=r.resolve,a=r.reject,c=s((function(){var r=i(t.resolve),o=[],s=0,a=1;u(e,(function(e){var i=s++,u=!1;o.push(void 0),a++,r.call(t,e).then((function(e){u||(u=!0,o[i]={status:"fulfilled",value:e},--a||n(o))}),(function(e){u||(u=!0,o[i]={status:"rejected",reason:e},--a||n(o))}))})),--a||n(o)}));return c.error&&a(c.value),r.promise}})},function(e,t,r){"use strict";var n=r(17),i=r(12),o=r(48),s=r(5),u=r(11),a=r(51),c=r(55),l=r(10);n({target:"Promise",proto:!0,real:!0,forced:!!o&&s((function(){o.prototype.finally.call({then:function(){}},(function(){}))}))},{finally:function(e){var t=a(this,u("Promise")),r="function"==typeof e;return this.then(r?function(r){return c(t,e()).then((function(){return r}))}:e,r?function(r){return c(t,e()).then((function(){throw r}))}:e)}}),i||"function"!=typeof o||o.prototype.finally||l(o.prototype,"finally",u("Promise").prototype.finally)},function(e,t,r){"use strict";r.r(t),r.d(t,"add",(function(){return f})),r.d(t,"done",(function(){return p})),r.d(t,"toJS",(function(){return h})),r.d(t,"load",(function(){return d})),r.d(t,"search",(function(){return y}));function n(e,t,r,n){return new(r||(r=Promise))((function(i,o){function s(e){try{a(n.next(e))}catch(e){o(e)}}function u(e){try{a(n.throw(e))}catch(e){o(e)}}function a(e){var t;e.done?i(e.value):(t=e.value,t instanceof r?t:new r((function(e){e(t)}))).then(s,u)}a((n=n.apply(e,t||[])).next())}))}function i(e,t){var r,n,i,o,s={label:0,sent:function(){if(1&i[0])throw i[1];return i[1]},trys:[],ops:[]};return o={next:u(0),throw:u(1),return:u(2)},"function"==typeof Symbol&&(o[Symbol.iterator]=function(){return this}),o;function u(o){return function(u){return function(o){if(r)throw new TypeError("Generator is already executing.");for(;s;)try{if(r=1,n&&(i=2&o[0]?n.return:o[0]?n.throw||((i=n.return)&&i.call(n),0):n.next)&&!(i=i.call(n,o[1])).done)return i;switch(n=0,i&&(o=[2&o[0],i.value]),o[0]){case 0:case 1:i=o;break;case 4:return s.label++,{value:o[1],done:!1};case 5:s.label++,n=o[1],o=[0];continue;case 7:o=s.ops.pop(),s.trys.pop();continue;default:if(!(i=(i=s.trys).length>0&&i[i.length-1])&&(6===o[0]||2===o[0])){s=0;continue}if(3===o[0]&&(!i||o[1]>i[0]&&o[1]<i[3])){s.label=o[1];break}if(6===o[0]&&s.label<i[1]){s.label=i[1],i=o;break}if(i&&s.label<i[2]){s.label=i[2],s.ops.push(o);break}i[2]&&s.ops.pop(),s.trys.pop();continue}o=t.call(e,s)}catch(e){o=[6,e],n=0}finally{r=i=0}if(5&o[0])throw o[1];return{value:o[0]?o[1]:void 0,done:!0}}([o,u])}}}var o=r(4);try{r(57)}catch(e){}var s=function(){this.add=f,this.done=p,this.search=y,this.toJS=h,this.load=d},u=(t.default=s,[]),a=function(){throw new Error("Should not be called")},c=new Promise((function(e){a=e}));o.tokenizer.separator=/\\s+/;var l=new o.Builder;l.field("title"),l.field("description"),l.ref("ref"),l.pipeline.add(o.trimmer,o.stopWordFilter,o.stemmer);function f(e,t,r){var n=u.push(r)-1,i={title:e.toLowerCase(),description:t.toLowerCase(),ref:n};l.add(i)}function p(){return n(this,void 0,void 0,(function(){return i(this,(function(e){return a(l.build()),[2]}))}))}function h(){return n(this,void 0,void 0,(function(){var e;return i(this,(function(t){switch(t.label){case 0:return e={store:u},[4,c];case 1:return[2,(e.index=t.sent().toJSON(),e)]}}))}))}function d(e){return n(this,void 0,void 0,(function(){return i(this,(function(t){return u=e.store,a(o.Index.load(e.index)),[2]}))}))}function y(e,t){return void 0===t&&(t=0),n(this,void 0,void 0,(function(){var r;return i(this,(function(n){switch(n.label){case 0:return 0===e.trim().length?[2,[]]:[4,c];case 1:return r=n.sent().query((function(t){e.trim().toLowerCase().split(/\\s+/).forEach((function(e){var r=function(e){return"*"+o.stemmer(new o.Token(e,{}))+"*"}(e);t.term(r,{})}))})),t>0&&(r=r.slice(0,t)),[2,r.map((function(e){return{meta:u[e.ref],score:e.score}}))]}}))}))}addEventListener("message",(function(e){var r,n=e.data,i=n.type,o=n.method,s=n.id,u=n.params;"RPC"===i&&o&&((r=t[o])?Promise.resolve().then((function(){return r.apply(t,u)})):Promise.reject("No such method")).then((function(e){postMessage({type:"RPC",id:s,result:e})})).catch((function(e){var t={message:e};e.stack&&(t.message=e.message,t.stack=e.stack,t.name=e.name),postMessage({type:"RPC",id:s,error:t})}))})),postMessage({type:"RPC",method:"ready"})}]);\n//# sourceMappingURL=66d14429db4e18077079.worker.js.map'])),{name:"[hash].worker.js"});return r(e,o),e}},function(e,t){e.exports=function(e,t){var n=0,r={};e.addEventListener("message",(function(t){var n=t.data;if("RPC"===n.type)if(n.id){var o=r[n.id];o&&(delete r[n.id],n.error?o[1](Object.assign(Error(n.error.message),n.error)):o[0](n.result))}else{var i=document.createEvent("Event");i.initEvent(n.method,!1,!1),i.data=n.params,e.dispatchEvent(i)}})),t.forEach((function(t){e[t]=function(){for(var o=[],i=arguments.length;i--;)o[i]=arguments[i];return new Promise((function(i,a){var s=++n;r[s]=[i,a],e.postMessage({type:"RPC",id:s,method:t,params:o})}))}}))}},function(e,t,n){"use strict";e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n=function(e,t){var n=e[1]||"",r=e[3];if(!r)return n;if(t&&"function"==typeof btoa){var o=(a=r,s=btoa(unescape(encodeURIComponent(JSON.stringify(a)))),l="sourceMappingURL=data:application/json;charset=utf-8;base64,".concat(s),"/*# ".concat(l," */")),i=r.sources.map((function(e){return"/*# sourceURL=".concat(r.sourceRoot||"").concat(e," */")}));return[n].concat(i).concat([o]).join("\n")}var a,s,l;return[n].join("\n")}(t,e);return t[2]?"@media ".concat(t[2]," {").concat(n,"}"):n})).join("")},t.i=function(e,n,r){"string"==typeof e&&(e=[[null,e,""]]);var o={};if(r)for(var i=0;i<this.length;i++){var a=this[i][0];null!=a&&(o[a]=!0)}for(var s=0;s<e.length;s++){var l=[].concat(e[s]);r&&o[l[0]]||(n&&(l[2]?l[2]="".concat(n," and ").concat(l[2]):l[2]=n),t.push(l))}},t}},function(e,t){ /*! * Stickyfill -- `position: sticky` polyfill * v. 1.1.1 | https://github.com/wilddeer/stickyfill @@ -141,14 +141,14 @@ * * MIT License */ -e.exports=function(e,t){e||(e=document),t||(t=window);var n,r,o=[],i=!1,a=e.documentElement,s=function(){},l="hidden",c="visibilitychange";void 0!==e.webkitHidden&&(l="webkitHidden",c="webkitvisibilitychange"),t.getComputedStyle||d();for(var u=["","-webkit-","-moz-","-ms-"],p=document.createElement("div"),f=u.length-1;f>=0;f--){try{p.style.position=u[f]+"sticky"}catch(e){}""!=p.style.position&&d()}function d(){A=L=I=P=R=N=s}function h(e){return parseFloat(e)||0}function m(){n={top:t.pageYOffset,left:t.pageXOffset}}function g(){if(t.pageXOffset!=n.left)return m(),void I();t.pageYOffset!=n.top&&(m(),v())}function y(e){setTimeout((function(){t.pageYOffset!=n.top&&(n.top=t.pageYOffset,v())}),0)}function v(){for(var e=o.length-1;e>=0;e--)b(o[e])}function b(e){if(e.inited){var t=n.top<=e.limit.start?0:n.top>=e.limit.end?2:1;e.mode!=t&&function(e,t){var n=e.node.style;switch(t){case 0:n.position="absolute",n.left=e.offset.left+"px",n.right=e.offset.right+"px",n.top=e.offset.top+"px",n.bottom="auto",n.width="auto",n.marginLeft=0,n.marginRight=0,n.marginTop=0;break;case 1:n.position="fixed",n.left=e.box.left+"px",n.right=e.box.right+"px",n.top=e.css.top,n.bottom="auto",n.width="auto",n.marginLeft=0,n.marginRight=0,n.marginTop=0;break;case 2:n.position="absolute",n.left=e.offset.left+"px",n.right=e.offset.right+"px",n.top="auto",n.bottom=0,n.width="auto",n.marginLeft=0,n.marginRight=0}e.mode=t}(e,t)}}function x(e){isNaN(parseFloat(e.computed.top))||e.isCell||(e.inited=!0,e.clone||function(e){e.clone=document.createElement("div");var t=e.node.nextSibling||e.node,n=e.clone.style;n.height=e.height+"px",n.width=e.width+"px",n.marginTop=e.computed.marginTop,n.marginBottom=e.computed.marginBottom,n.marginLeft=e.computed.marginLeft,n.marginRight=e.computed.marginRight,n.padding=n.border=n.borderSpacing=0,n.fontSize="1em",n.position="static",n.cssFloat=e.computed.cssFloat,e.node.parentNode.insertBefore(e.clone,t)}(e),"absolute"!=e.parent.computed.position&&"relative"!=e.parent.computed.position&&(e.parent.node.style.position="relative"),b(e),e.parent.height=e.parent.node.offsetHeight,e.docOffsetTop=E(e.clone))}function w(e){var t=!0;e.clone&&function(e){e.clone.parentNode.removeChild(e.clone),e.clone=void 0}(e),function(e,t){for(key in t)t.hasOwnProperty(key)&&(e[key]=t[key])}(e.node.style,e.css);for(var n=o.length-1;n>=0;n--)if(o[n].node!==e.node&&o[n].parent.node===e.parent.node){t=!1;break}t&&(e.parent.node.style.position=e.parent.css.position),e.mode=-1}function k(){for(var e=o.length-1;e>=0;e--)x(o[e])}function O(){for(var e=o.length-1;e>=0;e--)w(o[e])}function _(e){var t=getComputedStyle(e),n=e.parentNode,r=getComputedStyle(n),o=e.style.position;e.style.position="relative";var i={top:t.top,marginTop:t.marginTop,marginBottom:t.marginBottom,marginLeft:t.marginLeft,marginRight:t.marginRight,cssFloat:t.cssFloat},s={top:h(t.top),marginBottom:h(t.marginBottom),paddingLeft:h(t.paddingLeft),paddingRight:h(t.paddingRight),borderLeftWidth:h(t.borderLeftWidth),borderRightWidth:h(t.borderRightWidth)};e.style.position=o;var l={position:e.style.position,top:e.style.top,bottom:e.style.bottom,left:e.style.left,right:e.style.right,width:e.style.width,marginTop:e.style.marginTop,marginLeft:e.style.marginLeft,marginRight:e.style.marginRight},c=S(e),u=S(n),p={node:n,css:{position:n.style.position},computed:{position:r.position},numeric:{borderLeftWidth:h(r.borderLeftWidth),borderRightWidth:h(r.borderRightWidth),borderTopWidth:h(r.borderTopWidth),borderBottomWidth:h(r.borderBottomWidth)}};return{node:e,box:{left:c.win.left,right:a.clientWidth-c.win.right},offset:{top:c.win.top-u.win.top-p.numeric.borderTopWidth,left:c.win.left-u.win.left-p.numeric.borderLeftWidth,right:-c.win.right+u.win.right-p.numeric.borderRightWidth},css:l,isCell:"table-cell"==t.display,computed:i,numeric:s,width:c.win.right-c.win.left,height:c.win.bottom-c.win.top,mode:-1,inited:!1,parent:p,limit:{start:c.doc.top-s.top,end:u.doc.top+n.offsetHeight-p.numeric.borderBottomWidth-e.offsetHeight-s.top-s.marginBottom}}}function E(e){for(var t=0;e;)t+=e.offsetTop,e=e.offsetParent;return t}function S(e){var n=e.getBoundingClientRect();return{doc:{top:n.top+t.pageYOffset,left:n.left+t.pageXOffset},win:n}}function T(){r=setInterval((function(){!function(){for(var e=o.length-1;e>=0;e--)if(o[e].inited){var t=Math.abs(E(o[e].clone)-o[e].docOffsetTop),n=Math.abs(o[e].parent.node.offsetHeight-o[e].parent.height);if(t>=2||n>=2)return!1}return!0}()&&I()}),500)}function j(){clearInterval(r)}function C(){i&&(document[l]?j():T())}function A(){i||(m(),k(),t.addEventListener("scroll",g),t.addEventListener("wheel",y),t.addEventListener("resize",I),t.addEventListener("orientationchange",I),e.addEventListener(c,C),T(),i=!0)}function I(){if(i){O();for(var e=o.length-1;e>=0;e--)o[e]=_(o[e].node);k()}}function P(){t.removeEventListener("scroll",g),t.removeEventListener("wheel",y),t.removeEventListener("resize",I),t.removeEventListener("orientationchange",I),e.removeEventListener(c,C),j(),i=!1}function R(){P(),O()}function N(){for(R();o.length;)o.pop()}function L(e){for(var t=o.length-1;t>=0;t--)if(o[t].node===e)return;var n=_(e);o.push(n),i?x(n):A()}return m(),{stickies:o,add:L,remove:function(e){for(var t=o.length-1;t>=0;t--)o[t].node===e&&(w(o[t]),o.splice(t,1))},init:A,rebuild:I,pause:P,stop:R,kill:N}}},function(e,t,n){"use strict";n.r(t),n.d(t,"Redoc",(function(){return vc})),n.d(t,"AppStore",(function(){return ps})),n.d(t,"version",(function(){return xc})),n.d(t,"revision",(function(){return wc})),n.d(t,"init",(function(){return Oc})),n.d(t,"hydrate",(function(){return _c}));var r={};n.r(r),n.d(r,"default",(function(){return Do}));var o=n(1),i=n(0),a=n.n(i),s=n(33),l=n(19);function c(){return(c=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function u(e){return(u=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function p(e,t){return(p=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function f(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}function d(e,t,n){return(d=f()?Reflect.construct:function(e,t,n){var r=[null];r.push.apply(r,t);var o=new(Function.bind.apply(e,r));return n&&p(o,n.prototype),o}).apply(null,arguments)}function h(e){var t="function"==typeof Map?new Map:void 0;return(h=function(e){if(null===e||(n=e,-1===Function.toString.call(n).indexOf("[native code]")))return e;var n;if("function"!=typeof e)throw new TypeError("Super expression must either be null or a function");if(void 0!==t){if(t.has(e))return t.get(e);t.set(e,r)}function r(){return d(e,arguments,u(this).constructor)}return r.prototype=Object.create(e.prototype,{constructor:{value:r,enumerable:!1,writable:!0,configurable:!0}}),p(r,e)})(e)}var m=function(e){var t,n;function r(t){return function(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}(e.call(this,"An error occurred. See https://github.com/styled-components/polished/blob/master/src/internalHelpers/errors.md#"+t+" for more information.")||this)}return n=e,(t=r).prototype=Object.create(n.prototype),t.prototype.constructor=t,t.__proto__=n,r}(h(Error));function g(e){return Math.round(255*e)}function y(e,t,n){return g(e)+","+g(t)+","+g(n)}function v(e,t,n,r){if(void 0===r&&(r=y),0===t)return r(n,n,n);var o=(e%360+360)%360/60,i=(1-Math.abs(2*n-1))*t,a=i*(1-Math.abs(o%2-1)),s=0,l=0,c=0;o>=0&&o<1?(s=i,l=a):o>=1&&o<2?(s=a,l=i):o>=2&&o<3?(l=i,c=a):o>=3&&o<4?(l=a,c=i):o>=4&&o<5?(s=a,c=i):o>=5&&o<6&&(s=i,c=a);var u=n-i/2;return r(s+u,l+u,c+u)}var b={aliceblue:"f0f8ff",antiquewhite:"faebd7",aqua:"00ffff",aquamarine:"7fffd4",azure:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"000",blanchedalmond:"ffebcd",blue:"0000ff",blueviolet:"8a2be2",brown:"a52a2a",burlywood:"deb887",cadetblue:"5f9ea0",chartreuse:"7fff00",chocolate:"d2691e",coral:"ff7f50",cornflowerblue:"6495ed",cornsilk:"fff8dc",crimson:"dc143c",cyan:"00ffff",darkblue:"00008b",darkcyan:"008b8b",darkgoldenrod:"b8860b",darkgray:"a9a9a9",darkgreen:"006400",darkgrey:"a9a9a9",darkkhaki:"bdb76b",darkmagenta:"8b008b",darkolivegreen:"556b2f",darkorange:"ff8c00",darkorchid:"9932cc",darkred:"8b0000",darksalmon:"e9967a",darkseagreen:"8fbc8f",darkslateblue:"483d8b",darkslategray:"2f4f4f",darkslategrey:"2f4f4f",darkturquoise:"00ced1",darkviolet:"9400d3",deeppink:"ff1493",deepskyblue:"00bfff",dimgray:"696969",dimgrey:"696969",dodgerblue:"1e90ff",firebrick:"b22222",floralwhite:"fffaf0",forestgreen:"228b22",fuchsia:"ff00ff",gainsboro:"dcdcdc",ghostwhite:"f8f8ff",gold:"ffd700",goldenrod:"daa520",gray:"808080",green:"008000",greenyellow:"adff2f",grey:"808080",honeydew:"f0fff0",hotpink:"ff69b4",indianred:"cd5c5c",indigo:"4b0082",ivory:"fffff0",khaki:"f0e68c",lavender:"e6e6fa",lavenderblush:"fff0f5",lawngreen:"7cfc00",lemonchiffon:"fffacd",lightblue:"add8e6",lightcoral:"f08080",lightcyan:"e0ffff",lightgoldenrodyellow:"fafad2",lightgray:"d3d3d3",lightgreen:"90ee90",lightgrey:"d3d3d3",lightpink:"ffb6c1",lightsalmon:"ffa07a",lightseagreen:"20b2aa",lightskyblue:"87cefa",lightslategray:"789",lightslategrey:"789",lightsteelblue:"b0c4de",lightyellow:"ffffe0",lime:"0f0",limegreen:"32cd32",linen:"faf0e6",magenta:"f0f",maroon:"800000",mediumaquamarine:"66cdaa",mediumblue:"0000cd",mediumorchid:"ba55d3",mediumpurple:"9370db",mediumseagreen:"3cb371",mediumslateblue:"7b68ee",mediumspringgreen:"00fa9a",mediumturquoise:"48d1cc",mediumvioletred:"c71585",midnightblue:"191970",mintcream:"f5fffa",mistyrose:"ffe4e1",moccasin:"ffe4b5",navajowhite:"ffdead",navy:"000080",oldlace:"fdf5e6",olive:"808000",olivedrab:"6b8e23",orange:"ffa500",orangered:"ff4500",orchid:"da70d6",palegoldenrod:"eee8aa",palegreen:"98fb98",paleturquoise:"afeeee",palevioletred:"db7093",papayawhip:"ffefd5",peachpuff:"ffdab9",peru:"cd853f",pink:"ffc0cb",plum:"dda0dd",powderblue:"b0e0e6",purple:"800080",rebeccapurple:"639",red:"f00",rosybrown:"bc8f8f",royalblue:"4169e1",saddlebrown:"8b4513",salmon:"fa8072",sandybrown:"f4a460",seagreen:"2e8b57",seashell:"fff5ee",sienna:"a0522d",silver:"c0c0c0",skyblue:"87ceeb",slateblue:"6a5acd",slategray:"708090",slategrey:"708090",snow:"fffafa",springgreen:"00ff7f",steelblue:"4682b4",tan:"d2b48c",teal:"008080",thistle:"d8bfd8",tomato:"ff6347",turquoise:"40e0d0",violet:"ee82ee",wheat:"f5deb3",white:"fff",whitesmoke:"f5f5f5",yellow:"ff0",yellowgreen:"9acd32"};var x=/^#[a-fA-F0-9]{6}$/,w=/^#[a-fA-F0-9]{8}$/,k=/^#[a-fA-F0-9]{3}$/,O=/^#[a-fA-F0-9]{4}$/,_=/^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/i,E=/^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*([-+]?[0-9]*[.]?[0-9]+)\s*\)$/i,S=/^hsl\(\s*(\d{0,3}[.]?[0-9]+)\s*,\s*(\d{1,3}[.]?[0-9]?)%\s*,\s*(\d{1,3}[.]?[0-9]?)%\s*\)$/i,T=/^hsla\(\s*(\d{0,3}[.]?[0-9]+)\s*,\s*(\d{1,3}[.]?[0-9]?)%\s*,\s*(\d{1,3}[.]?[0-9]?)%\s*,\s*([-+]?[0-9]*[.]?[0-9]+)\s*\)$/i;function j(e){if("string"!=typeof e)throw new m(3);var t=function(e){if("string"!=typeof e)return e;var t=e.toLowerCase();return b[t]?"#"+b[t]:e}(e);if(t.match(x))return{red:parseInt(""+t[1]+t[2],16),green:parseInt(""+t[3]+t[4],16),blue:parseInt(""+t[5]+t[6],16)};if(t.match(w)){var n=parseFloat((parseInt(""+t[7]+t[8],16)/255).toFixed(2));return{red:parseInt(""+t[1]+t[2],16),green:parseInt(""+t[3]+t[4],16),blue:parseInt(""+t[5]+t[6],16),alpha:n}}if(t.match(k))return{red:parseInt(""+t[1]+t[1],16),green:parseInt(""+t[2]+t[2],16),blue:parseInt(""+t[3]+t[3],16)};if(t.match(O)){var r=parseFloat((parseInt(""+t[4]+t[4],16)/255).toFixed(2));return{red:parseInt(""+t[1]+t[1],16),green:parseInt(""+t[2]+t[2],16),blue:parseInt(""+t[3]+t[3],16),alpha:r}}var o=_.exec(t);if(o)return{red:parseInt(""+o[1],10),green:parseInt(""+o[2],10),blue:parseInt(""+o[3],10)};var i=E.exec(t);if(i)return{red:parseInt(""+i[1],10),green:parseInt(""+i[2],10),blue:parseInt(""+i[3],10),alpha:parseFloat(""+i[4])};var a=S.exec(t);if(a){var s="rgb("+v(parseInt(""+a[1],10),parseInt(""+a[2],10)/100,parseInt(""+a[3],10)/100)+")",l=_.exec(s);if(!l)throw new m(4,t,s);return{red:parseInt(""+l[1],10),green:parseInt(""+l[2],10),blue:parseInt(""+l[3],10)}}var c=T.exec(t);if(c){var u="rgb("+v(parseInt(""+c[1],10),parseInt(""+c[2],10)/100,parseInt(""+c[3],10)/100)+")",p=_.exec(u);if(!p)throw new m(4,t,u);return{red:parseInt(""+p[1],10),green:parseInt(""+p[2],10),blue:parseInt(""+p[3],10),alpha:parseFloat(""+c[4])}}throw new m(5)}function C(e){return function(e){var t,n=e.red/255,r=e.green/255,o=e.blue/255,i=Math.max(n,r,o),a=Math.min(n,r,o),s=(i+a)/2;if(i===a)return void 0!==e.alpha?{hue:0,saturation:0,lightness:s,alpha:e.alpha}:{hue:0,saturation:0,lightness:s};var l=i-a,c=s>.5?l/(2-i-a):l/(i+a);switch(i){case n:t=(r-o)/l+(r<o?6:0);break;case r:t=(o-n)/l+2;break;default:t=(n-r)/l+4}return t*=60,void 0!==e.alpha?{hue:t,saturation:c,lightness:s,alpha:e.alpha}:{hue:t,saturation:c,lightness:s}}(j(e))}var A=function(e){return 7===e.length&&e[1]===e[2]&&e[3]===e[4]&&e[5]===e[6]?"#"+e[1]+e[3]+e[5]:e};function I(e){var t=e.toString(16);return 1===t.length?"0"+t:t}function P(e){return I(Math.round(255*e))}function R(e,t,n){return A("#"+P(e)+P(t)+P(n))}function N(e,t,n){return v(e,t,n,R)}function L(e,t,n){if("number"==typeof e&&"number"==typeof t&&"number"==typeof n)return N(e,t,n);if("object"==typeof e&&void 0===t&&void 0===n)return N(e.hue,e.saturation,e.lightness);throw new m(1)}function M(e,t,n,r){if("number"==typeof e&&"number"==typeof t&&"number"==typeof n&&"number"==typeof r)return r>=1?N(e,t,n):"rgba("+v(e,t,n)+","+r+")";if("object"==typeof e&&void 0===t&&void 0===n&&void 0===r)return e.alpha>=1?N(e.hue,e.saturation,e.lightness):"rgba("+v(e.hue,e.saturation,e.lightness)+","+e.alpha+")";throw new m(2)}function D(e,t,n){if("number"==typeof e&&"number"==typeof t&&"number"==typeof n)return A("#"+I(e)+I(t)+I(n));if("object"==typeof e&&void 0===t&&void 0===n)return A("#"+I(e.red)+I(e.green)+I(e.blue));throw new m(6)}function F(e,t,n,r){if("string"==typeof e&&"number"==typeof t){var o=j(e);return"rgba("+o.red+","+o.green+","+o.blue+","+t+")"}if("number"==typeof e&&"number"==typeof t&&"number"==typeof n&&"number"==typeof r)return r>=1?D(e,t,n):"rgba("+e+","+t+","+n+","+r+")";if("object"==typeof e&&void 0===t&&void 0===n&&void 0===r)return e.alpha>=1?D(e.red,e.green,e.blue):"rgba("+e.red+","+e.green+","+e.blue+","+e.alpha+")";throw new m(7)}function z(e){if("object"!=typeof e)throw new m(8);if(function(e){return"number"==typeof e.red&&"number"==typeof e.green&&"number"==typeof e.blue&&"number"==typeof e.alpha}(e))return F(e);if(function(e){return"number"==typeof e.red&&"number"==typeof e.green&&"number"==typeof e.blue&&("number"!=typeof e.alpha||void 0===e.alpha)}(e))return D(e);if(function(e){return"number"==typeof e.hue&&"number"==typeof e.saturation&&"number"==typeof e.lightness&&"number"==typeof e.alpha}(e))return M(e);if(function(e){return"number"==typeof e.hue&&"number"==typeof e.saturation&&"number"==typeof e.lightness&&("number"!=typeof e.alpha||void 0===e.alpha)}(e))return L(e);throw new m(8)}function U(e){return function e(t,n,r){return function(){var o=r.concat(Array.prototype.slice.call(arguments));return o.length>=n?t.apply(this,o):e(t,n,o)}}(e,e.length,[])}function B(e,t,n){return Math.max(e,Math.min(t,n))}function $(e,t){if("transparent"===t)return t;var n=C(t);return z(c({},n,{lightness:B(0,1,n.lightness-parseFloat(e))}))}var q=U($);function W(e,t){if("transparent"===t)return t;var n=C(t);return z(c({},n,{saturation:B(0,1,n.saturation-parseFloat(e))}))}var H=U(W);function V(e){if("transparent"===e)return 0;var t=j(e),n=Object.keys(t).map((function(e){var n=t[e]/255;return n<=.03928?n/12.92:Math.pow((n+.055)/1.055,2.4)})),r=n[0],o=n[1],i=n[2];return parseFloat((.2126*r+.7152*o+.0722*i).toFixed(3))}function Y(e,t){if("transparent"===t)return t;var n=C(t);return z(c({},n,{lightness:B(0,1,n.lightness+parseFloat(e))}))}var Q=U(Y);function G(e,t,n){return void 0===t&&(t="#000"),void 0===n&&(n="#fff"),V(e)>.179?t:n}function X(e,t){if("transparent"===t)return t;var n=j(t);return F(c({},n,{alpha:B(0,1,(100*("number"==typeof n.alpha?n.alpha:1)-100*parseFloat(e))/100)}))}var K=U(X);var Z={spacing:{unit:5,sectionHorizontal:function(e){return 8*e.spacing.unit},sectionVertical:function(e){return 8*e.spacing.unit}},breakpoints:{small:"50rem",medium:"85rem",large:"105rem"},colors:{tonalOffset:.3,primary:{main:"#32329f",light:function(e){var t=e.colors;return Q(t.tonalOffset,t.primary.main)},dark:function(e){var t=e.colors;return q(t.tonalOffset,t.primary.main)},contrastText:function(e){return G(e.colors.primary.main)}},success:{main:"#00aa13",light:function(e){var t=e.colors;return Q(t.tonalOffset,t.success.main)},dark:function(e){var t=e.colors;return q(t.tonalOffset,t.success.main)},contrastText:function(e){return G(e.colors.success.main)}},warning:{main:"#d4ad03",light:function(e){var t=e.colors;return Q(t.tonalOffset,t.warning.main)},dark:function(e){var t=e.colors;return q(t.tonalOffset,t.warning.main)},contrastText:"#ffffff"},error:{main:"#e53935",light:function(e){var t=e.colors;return Q(t.tonalOffset,t.error.main)},dark:function(e){var t=e.colors;return q(t.tonalOffset,t.error.main)},contrastText:function(e){return G(e.colors.error.main)}},text:{primary:"#333333",secondary:function(e){var t=e.colors;return Q(t.tonalOffset,t.text.primary)}},border:{dark:"rgba(0,0,0, 0.1)",light:"#ffffff"},responses:{success:{color:function(e){return e.colors.success.main},backgroundColor:function(e){var t=e.colors;return K(.9,t.success.main)}},error:{color:function(e){return e.colors.error.main},backgroundColor:function(e){var t=e.colors;return K(.9,t.error.main)}},redirect:{color:"#ffa500",backgroundColor:function(e){var t=e.colors;return K(.9,t.responses.redirect.color)}},info:{color:"#87ceeb",backgroundColor:function(e){var t=e.colors;return K(.9,t.responses.info.color)}}},http:{get:"#6bbd5b",post:"#248fb2",put:"#9b708b",options:"#d3ca12",patch:"#e09d43",delete:"#e27a7a",basic:"#999",link:"#31bbb6",head:"#c167e4"}},schema:{linesColor:function(e){return Q(e.colors.tonalOffset,H(e.colors.tonalOffset,e.colors.primary.main))},defaultDetailsWidth:"75%",typeNameColor:function(e){return e.colors.text.secondary},typeTitleColor:function(e){return e.schema.typeNameColor},requireLabelColor:function(e){return e.colors.error.main},labelsTextSize:"0.9em",nestingSpacing:"1em",nestedBackground:"#fafafa",arrow:{size:"1.1em",color:function(e){return e.colors.text.secondary}}},typography:{fontSize:"14px",lineHeight:"1.5em",fontWeightRegular:"400",fontWeightBold:"600",fontWeightLight:"300",fontFamily:"Roboto, sans-serif",smoothing:"antialiased",optimizeSpeed:!0,headings:{fontFamily:"Montserrat, sans-serif",fontWeight:"400",lineHeight:"1.6em"},code:{fontSize:"13px",fontFamily:"Courier, monospace",lineHeight:function(e){return e.typography.lineHeight},fontWeight:function(e){return e.typography.fontWeightRegular},color:"#e53935",backgroundColor:"rgba(38, 50, 56, 0.05)",wrap:!1},links:{color:function(e){return e.colors.primary.main},visited:function(e){return e.typography.links.color},hover:function(e){var t=e.typography;return Q(.2,t.links.color)}}},menu:{width:"260px",backgroundColor:"#fafafa",textColor:"#333333",activeTextColor:function(e){return e.menu.textColor!==Z.menu.textColor?e.menu.textColor:e.colors.primary.main},groupItems:{textTransform:"uppercase"},level1Items:{textTransform:"none"},arrow:{size:"1.5em",color:function(e){return e.menu.textColor}}},logo:{maxHeight:function(e){return e.menu.width},maxWidth:function(e){return e.menu.width},gutter:"2px"},rightPanel:{backgroundColor:"#263238",width:"40%",textColor:"#ffffff"},codeSample:{backgroundColor:function(e){var t=e.rightPanel;return q(.1,t.backgroundColor)}}},J=Z;var ee="undefined"!=typeof window&&"HTMLElement"in window;function te(e){return"undefined"!=typeof document?document.querySelector(e):null}"undefined"==typeof Element||Element.prototype.scrollIntoViewIfNeeded||(Element.prototype.scrollIntoViewIfNeeded=function(e){e=0===arguments.length||!!e;var t=this.parentNode,n=window.getComputedStyle(t,void 0),r=parseInt(n.getPropertyValue("border-top-width"),10),o=parseInt(n.getPropertyValue("border-left-width"),10),i=this.offsetTop-t.offsetTop<t.scrollTop,a=this.offsetTop-t.offsetTop+this.clientHeight-r>t.scrollTop+t.clientHeight,s=this.offsetLeft-t.offsetLeft<t.scrollLeft,l=this.offsetLeft-t.offsetLeft+this.clientWidth-o>t.scrollLeft+t.clientWidth,c=i&&!a;(i||a)&&e&&(t.scrollTop=this.offsetTop-t.offsetTop-t.clientHeight/2-r+this.clientHeight/2),(s||l)&&e&&(t.scrollLeft=this.offsetLeft-t.offsetLeft-t.clientWidth/2-o+this.clientWidth/2),(i||a||s||l)&&!e&&this.scrollIntoView(c)});var ne=n(151),re=n.n(ne),oe=n(10);function ie(e,t){for(var n=[],r=0;r<e.length-1;r++)n.push(t(e[r],!1));return 0!==e.length&&n.push(t(e[e.length-1],!0)),n}function ae(e){return e.endsWith("/")?e.substring(0,e.length-1):e}function se(e){return!isNaN(parseFloat(e))&&isFinite(e)}var le=function(e){for(var t=[],n=1;n<arguments.length;n++)t[n-1]=arguments[n];if(!t.length)return e;var r=t.shift();return void 0===r?e:(ce(e)&&ce(r)&&Object.keys(r).forEach((function(t){ce(r[t])?(e[t]||(e[t]={}),le(e[t],r[t])):e[t]=r[t]})),le.apply(void 0,Object(o.__spreadArrays)([e],t)))},ce=function(e){return function(e){return null!==e&&"object"==typeof e}(e)&&!Array.isArray(e)};function ue(e){return re()(e)||e.toString().toLowerCase().replace(/\s+/g,"-").replace(/&/g,"-and-").replace(/\--+/g,"-").replace(/^-+/,"").replace(/-+$/,"")}function pe(e){return"undefined"==typeof URL?new(n(10).URL)(e):new URL(e)}var fe={enum:"Enum",enumSingleValue:"Value",enumArray:"Items",default:"Default",deprecated:"Deprecated",example:"Example",nullable:"Nullable",recursive:"Recursive",arrayOf:"Array of "};function de(e,t){var n=fe[e];return void 0!==t?n[t]:n}function he(e,t){return void 0===e?t||!1:"string"==typeof e?"false"!==e:e}var me,ge=function(){function e(t,n){void 0===n&&(n={});var r,i,a,s,l,c=(t=Object(o.__assign)(Object(o.__assign)({},n),t)).theme&&t.theme.extensionsHook;this.theme=(r=le({},J,Object(o.__assign)(Object(o.__assign)({},t.theme),{extensionsHook:void 0})),i={},a=0,(s=function(e,t){Object.keys(e).forEach((function(n){var o=(t?t+".":"")+n,l=e[n];"function"==typeof l?Object.defineProperty(e,n,{get:function(){if(!i[o]){if(++a>1e3)throw new Error("Theme probably contains circular dependency at "+o+": "+l.toString());i[o]=l(r)}return i[o]},enumerable:!0}):"object"==typeof l&&s(l,o)}))})(r,""),JSON.parse(JSON.stringify(r))),this.theme.extensionsHook=c,l=t.labels,Object.assign(fe,l),this.scrollYOffset=e.normalizeScrollYOffset(t.scrollYOffset),this.hideHostname=e.normalizeHideHostname(t.hideHostname),this.expandResponses=e.normalizeExpandResponses(t.expandResponses),this.requiredPropsFirst=he(t.requiredPropsFirst),this.sortPropsAlphabetically=he(t.sortPropsAlphabetically),this.noAutoAuth=he(t.noAutoAuth),this.nativeScrollbars=he(t.nativeScrollbars),this.pathInMiddlePanel=he(t.pathInMiddlePanel),this.untrustedSpec=he(t.untrustedSpec),this.hideDownloadButton=he(t.hideDownloadButton),this.disableSearch=he(t.disableSearch),this.onlyRequiredInSamples=he(t.onlyRequiredInSamples),this.showExtensions=e.normalizeShowExtensions(t.showExtensions),this.hideSingleRequestSampleTab=he(t.hideSingleRequestSampleTab),this.menuToggle=he(t.menuToggle,!0),this.jsonSampleExpandLevel=e.normalizeJsonSampleExpandLevel(t.jsonSampleExpandLevel),this.enumSkipQuotes=he(t.enumSkipQuotes),this.hideSchemaTitles=he(t.hideSchemaTitles),this.payloadSampleIdx=e.normalizePayloadSampleIdx(t.payloadSampleIdx),this.expandSingleSchemaField=he(t.expandSingleSchemaField),this.unstable_ignoreMimeParameters=he(t.unstable_ignoreMimeParameters),this.allowedMdComponents=t.allowedMdComponents||{},this.expandDefaultServerVariables=he(t.expandDefaultServerVariables)}return e.normalizeExpandResponses=function(e){if("all"===e)return"all";if("string"==typeof e){var t={};return e.split(",").forEach((function(e){t[e.trim()]=!0})),t}return void 0!==e&&console.warn('expandResponses must be a string but received value "'+e+'" of type '+typeof e),{}},e.normalizeHideHostname=function(e){return!!e},e.normalizeScrollYOffset=function(e){if("string"==typeof e&&!se(e)){var t=te(e);t||console.warn("scrollYOffset value is a selector to non-existing element. Using offset 0 by default");var n=t&&t.getBoundingClientRect().bottom||0;return function(){return n}}return"number"==typeof e||se(e)?function(){return"number"==typeof e?e:parseFloat(e)}:"function"==typeof e?function(){var t=e();return"number"!=typeof t&&console.warn('scrollYOffset should return number but returned value "'+t+'" of type '+typeof t),t}:(void 0!==e&&console.warn("Wrong value for scrollYOffset ReDoc option: should be string, number or function"),function(){return 0})},e.normalizeShowExtensions=function(e){return void 0!==e&&(""===e||("string"==typeof e?e.split(",").map((function(e){return e.trim()})):e))},e.normalizePayloadSampleIdx=function(e){return"number"==typeof e?Math.max(0,e):"string"==typeof e&&isFinite(e)?parseInt(e,10):0},e.normalizeJsonSampleExpandLevel=function(e){return"all"===e?1/0:isNaN(Number(e))?2:Math.ceil(Number(e))},e}(),ye=n(152),ve=ye.default,be=ye.css,xe=ye.createGlobalStyle,we=ye.keyframes,ke=ye.ThemeProvider,Oe=function(e,t){return function(){for(var n=[],r=0;r<arguments.length;r++)n[r]=arguments[r];return be(me||(me=Object(o.__makeTemplateObject)(["\n @media "," screen and (max-width: ",") {\n ",";\n }\n "],["\n @media "," screen and (max-width: ",") {\n ",";\n }\n "])),t?"print, ":"",(function(t){return t.theme.breakpoints[e]}),be.apply(void 0,n))}},_e=ve;function Ee(e){return function(t){if(t.theme.extensionsHook)return t.theme.extensionsHook(e,t)}}var Se,Te,je,Ce,Ae=_e.div(Se||(Se=Object(o.__makeTemplateObject)(["\n padding: 20px;\n color: red;\n"],["\n padding: 20px;\n color: red;\n"]))),Ie=function(e){function t(t){var n=e.call(this,t)||this;return n.state={error:void 0},n}return Object(o.__extends)(t,e),t.prototype.componentDidCatch=function(e){return this.setState({error:e}),!1},t.prototype.render=function(){return this.state.error?i.createElement(Ae,null,i.createElement("h1",null,"Something went wrong..."),i.createElement("small",null," ",this.state.error.message," "),i.createElement("p",null,i.createElement("details",null,i.createElement("summary",null,"Stack trace"),i.createElement("pre",null,this.state.error.stack))),i.createElement("small",null," ReDoc Version: ","2.0.0-rc.24")," ",i.createElement("br",null),i.createElement("small",null," Commit: ","972dc37")):i.Children.only(this.props.children)},t}(i.Component),Pe=we(Te||(Te=Object(o.__makeTemplateObject)(["\n 0% {\n transform: rotate(0deg); }\n 100% {\n transform: rotate(360deg);\n }\n"],["\n 0% {\n transform: rotate(0deg); }\n 100% {\n transform: rotate(360deg);\n }\n"]))),Re=_e((function(e){return i.createElement("svg",{className:e.className,version:"1.1",width:"512",height:"512",viewBox:"0 0 512 512"},i.createElement("path",{d:"M275.682 147.999c0 10.864-8.837 19.661-19.682 19.661v0c-10.875 0-19.681-8.796-19.681-19.661v-96.635c0-10.885 8.806-19.661 19.681-19.661v0c10.844 0 19.682 8.776 19.682 19.661v96.635z"}),i.createElement("path",{d:"M275.682 460.615c0 10.865-8.837 19.682-19.682 19.682v0c-10.875 0-19.681-8.817-19.681-19.682v-96.604c0-10.885 8.806-19.681 19.681-19.681v0c10.844 0 19.682 8.796 19.682 19.682v96.604z"}),i.createElement("path",{d:"M147.978 236.339c10.885 0 19.681 8.755 19.681 19.641v0c0 10.885-8.796 19.702-19.681 19.702h-96.624c-10.864 0-19.661-8.817-19.661-19.702v0c0-10.885 8.796-19.641 19.661-19.641h96.624z"}),i.createElement("path",{d:"M460.615 236.339c10.865 0 19.682 8.755 19.682 19.641v0c0 10.885-8.817 19.702-19.682 19.702h-96.584c-10.885 0-19.722-8.817-19.722-19.702v0c0-10.885 8.837-19.641 19.722-19.641h96.584z"}),i.createElement("path",{d:"M193.546 165.703c7.69 7.66 7.68 20.142 0 27.822v0c-7.701 7.701-20.162 7.701-27.853 0.020l-68.311-68.322c-7.68-7.701-7.68-20.142 0-27.863v0c7.68-7.68 20.121-7.68 27.822 0l68.342 68.342z"}),i.createElement("path",{d:"M414.597 386.775c7.7 7.68 7.7 20.163 0.021 27.863v0c-7.7 7.659-20.142 7.659-27.843-0.062l-68.311-68.26c-7.68-7.7-7.68-20.204 0-27.863v0c7.68-7.7 20.163-7.7 27.842 0l68.291 68.322z"}),i.createElement("path",{d:"M165.694 318.464c7.69-7.7 20.153-7.7 27.853 0v0c7.68 7.659 7.69 20.163 0 27.863l-68.342 68.322c-7.67 7.659-20.142 7.659-27.822-0.062v0c-7.68-7.68-7.68-20.122 0-27.801l68.311-68.322z"}),i.createElement("path",{d:"M386.775 97.362c7.7-7.68 20.142-7.68 27.822 0v0c7.7 7.68 7.7 20.183 0.021 27.863l-68.322 68.311c-7.68 7.68-20.163 7.68-27.843-0.020v0c-7.68-7.68-7.68-20.162 0-27.822l68.322-68.332z"}))}))(je||(je=Object(o.__makeTemplateObject)(["\n animation: 2s "," linear infinite;\n width: 50px;\n height: 50px;\n content: '';\n display: inline-block;\n margin-left: -25px;\n\n path {\n fill: ",";\n }\n"],["\n animation: 2s "," linear infinite;\n width: 50px;\n height: 50px;\n content: '';\n display: inline-block;\n margin-left: -25px;\n\n path {\n fill: ",";\n }\n"])),Pe,(function(e){return e.color})),Ne=_e.div(Ce||(Ce=Object(o.__makeTemplateObject)(["\n font-family: helvetica, sans;\n width: 100%;\n text-align: center;\n font-size: 25px;\n margin: 30px 0 20px 0;\n color: ",";\n"],["\n font-family: helvetica, sans;\n width: 100%;\n text-align: center;\n font-size: 25px;\n margin: 30px 0 20px 0;\n color: ",";\n"])),(function(e){return e.color})),Le=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){return i.createElement("div",{style:{textAlign:"center"}},i.createElement(Ne,{color:this.props.color},"Loading ..."),i.createElement(Re,{color:this.props.color}))},t}(i.PureComponent),Me=i.createContext(new ge({})),De=Me.Provider,Fe=Me.Consumer,ze=n(2),Ue=n(155),Be=n(156);function $e(e){return Object(o.__awaiter)(this,void 0,void 0,(function(){var t;return Object(o.__generator)(this,(function(n){switch(n.label){case 0:return[4,(new Ue).bundle(e,{resolve:{http:{withCredentials:!1}}})];case 1:return void 0!==(t=n.sent()).swagger?[2,qe(t)]:[2,t]}}))}))}function qe(e){return console.warn("[ReDoc Compatibility mode]: Converting OpenAPI 2.0 to OpenAPI 3.0"),new Promise((function(t,n){return Object(Be.convertObj)(e,{patch:!0,warnOnly:!0,text:"{}"},(function(e,r){if(e)return n(e);t(r&&r.openapi)}))}))}var We=n(27),He=n(66),Ve=n(22),Ye=Ve.parse,Qe=function(){function e(){}return e.baseName=function(t,n){void 0===n&&(n=1);var r=e.parse(t);return r[r.length-n]},e.dirName=function(t,n){void 0===n&&(n=1);var r=e.parse(t);return Ve.compile(r.slice(0,r.length-n))},e.relative=function(t,n){var r=e.parse(t);return e.parse(n).slice(r.length)},e.parse=function(e){var t=e;return"#"===t.charAt(0)&&(t=t.substring(1)),Ye(t)},e.join=function(t,n){var r=e.parse(t).concat(n);return Ve.compile(r)},e.get=function(e,t){return Ve.get(e,t)},e.compile=function(e){return Ve.compile(e)},e.escape=function(e){return Ve.escape(e)},e}();Ve.parse=Qe.parse,Object.assign(Qe,Ve);var Ge=n(52),Xe=n(67);function Ke(e){return"string"==typeof e&&/\dxx/i.test(e)}function Ze(e,t){if(void 0===t&&(t=!1),"default"===e)return t?"error":"success";var n="string"==typeof e?parseInt(e,10):e;if(Ke(e)&&(n*=100),n<100||n>599)throw new Error("invalid HTTP code");var r="success";return n>=300&&n<400?r="redirect":n>=400?r="error":n<200&&(r="info"),r}var Je={get:!0,post:!0,put:!0,head:!0,patch:!0,delete:!0,options:!0};function et(e){return e in Je}var tt={multipleOf:"number",maximum:"number",exclusiveMaximum:"number",minimum:"number",exclusiveMinimum:"number",maxLength:"string",minLength:"string",pattern:"string",items:"array",maxItems:"array",minItems:"array",uniqueItems:"array",maxProperties:"object",minProperties:"object",required:"object",additionalProperties:"object",properties:"object"};function nt(e){return-1!==e.search(/json/i)}function rt(e,t,n){return Array.isArray(e)?e.map((function(e){return e.toString()})).join(n):"object"==typeof e?Object.keys(e).map((function(t){return""+t+n+e[t]})).join(n):t+"="+e.toString()}function ot(e,t){return Array.isArray(e)?(console.warn("deepObject style cannot be used with array value:"+e.toString()),""):"object"==typeof e?Object.keys(e).map((function(n){return t+"["+n+"]="+e[n]})).join("&"):(console.warn("deepObject style cannot be used with non-object value:"+e.toString()),"")}function it(e,t,n){var r,o=t?"*":"";return Xe.parse("{?__redoc_param_name__"+o+"}").expand((r={},r.__redoc_param_name__=n,r)).substring(1).replace(/__redoc_param_name__/g,e)}function at(e,t){return nt(t)?JSON.stringify(e):(console.warn("Parameter serialization as "+t+" is not supported"),"")}function st(e,t){var n=e.name,r=e.style,o=e.explode,i=void 0!==o&&o,a=e.serializationMime;if(a)switch(e.in){case"path":case"header":return at(t,a);case"cookie":case"query":return n+"="+at(t,a);default:return console.warn("Unexpected parameter location: "+e.in),""}if(!r)return console.warn("Missing style attribute or content for parameter "+n),"";switch(e.in){case"path":return function(e,t,n,r){var o,i=n?"*":"",a="";return"label"===t?a=".":"matrix"===t&&(a=";"),Xe.parse("{"+a+"__redoc_param_name__"+i+"}").expand((o={},o.__redoc_param_name__=r,o)).replace(/__redoc_param_name__/g,e)}(n,r,i,t);case"query":return function(e,t,n,r){switch(t){case"form":return it(e,n,r);case"spaceDelimited":return Array.isArray(r)?n?it(e,n,r):e+"="+r.join("%20"):(console.warn("The style spaceDelimited is only applicable to arrays"),"");case"pipeDelimited":return Array.isArray(r)?n?it(e,n,r):e+"="+r.join("|"):(console.warn("The style pipeDelimited is only applicable to arrays"),"");case"deepObject":return!n||Array.isArray(r)||"object"!=typeof r?(console.warn("The style deepObject is only applicable for objects with explode=true"),""):ot(r,e);default:return console.warn("Unexpected style for query: "+t),""}}(n,r,i,t);case"header":return function(e,t,n){var r;switch(e){case"simple":var o=t?"*":"",i="__redoc_param_name__",a=Xe.parse("{"+i+o+"}");return decodeURIComponent(a.expand(((r={})[i]=n,r)));default:return console.warn("Unexpected style for header: "+e),""}}(r,i,t);case"cookie":return function(e,t,n,r){switch(t){case"form":return it(e,n,r);default:return console.warn("Unexpected style for cookie: "+t),""}}(n,r,i,t);default:return console.warn("Unexpected parameter location: "+e.in),""}}function lt(e){return/^#\/components\/schemas\/[^\/]+$/.test(e||"")}function ct(e,t,n){var r;return void 0!==t&&void 0!==n?r=t===n?t+" "+e:"[ "+t+" .. "+n+" ] "+e:void 0!==n?r="<= "+n+" "+e:void 0!==t&&(r=1===t?"non-empty":">= "+t+" "+e),r}function ut(e,t){void 0===t&&(t=[]);var n=[],r=[],i=[];return e.forEach((function(e){e.required?t.includes(e.name)?r.push(e):i.push(e):n.push(e)})),r.sort((function(e,n){return t.indexOf(e.name)-t.indexOf(n.name)})),Object(o.__spreadArrays)(r,i,n)}function pt(e,t){return Object(o.__spreadArrays)(e).sort((function(e,n){return e[t].localeCompare(n[t])}))}function ft(e,t){var n=void 0===e?function(e){try{var t=pe(e);return t.search="",t.toString()}catch(t){return e}}(function(){if(!ee)return"";var e=window.location.href;return e.endsWith(".html")?Object(Ge.dirname)(e):e}()):Object(Ge.dirname)(e);function r(e){return function(e,t){var n;if(t.startsWith("//"))n=""+(Object(oe.parse)(e).protocol||"https:")+t;else if(function(e){return/(?:^[a-z][a-z0-9+.-]*:|\/\/)/i.test(e)}(t))n=t;else if(t.startsWith("/")){var r=Object(oe.parse)(e);n=Object(oe.format)(Object(o.__assign)(Object(o.__assign)({},r),{pathname:t}))}else n=ae(e)+"/"+t;return ae(n)}(n,e)}return 0===t.length&&(t=[{url:"/"}]),t.map((function(e){return Object(o.__assign)(Object(o.__assign)({},e),{url:r(e.url),description:e.description||""})}))}var dt="section/Authentication/";function ht(e,t){return Object.keys(e).filter((function(e){return!0===t?e.startsWith("x-")&&!function(e){return e in{"x-circular-ref":!0,"x-code-samples":!0,"x-displayName":!0,"x-examples":!0,"x-ignoredHeaderParameters":!0,"x-logo":!0,"x-nullable":!0,"x-servers":!0,"x-tagGroups":!0,"x-traitTag":!0,"x-additionalPropertiesName":!0}}(e):e.startsWith("x-")&&t.indexOf(e)>-1})).reduce((function(t,n){return t[n]=e[n],t}),{})}var mt=n(41);n(295),n(296),n(297),n(298),n(299),n(300),n(301),n(302),n(303),n(304),n(305),n(306),n(307),n(308),n(309),n(310),n(311),n(312),n(313),n(314);function gt(e,t){void 0===t&&(t="clike"),t=t.toLowerCase();var n=mt.languages[t];return n||(n=mt.languages[function(e){return{json:"js","c++":"cpp","c#":"csharp","objective-c":"objectivec",shell:"bash",viml:"vim"}[e]||"clike"}(t)]),mt.highlight(e,n,t)}function yt(e){return function(t,n,r){var o,i,a,s,l,c,u,p;r.value=(o=r.value,i=e,c=null,u=0,p=function(){u=(new Date).getTime(),c=null,l=o.apply(a,s),c||(a=s=null)},function(){var e=(new Date).getTime(),t=i-(e-u);return a=this,s=arguments,t<=0||t>i?(c&&(clearTimeout(c),c=null),u=e,l=o.apply(a,s),c||(a=s=null)):c||(c=setTimeout(p,t)),l})}}function vt(e){0}function bt(e){0}mt.languages.insertBefore("javascript","string",{"property string":{pattern:/([{,]\s*)"(?:\\.|[^\\"\r\n])*"(?=\s*:)/i,lookbehind:!0}},void 0),mt.languages.insertBefore("javascript","punctuation",{property:{pattern:/([{,]\s*)[a-z]\w*(?=\s*:)/i,lookbehind:!0}},void 0);var xt={};function wt(e,t,n){if("function"==typeof n.value)return function(e,t,n){if(!n.value||n.value.length>0)throw new Error("@memoize decorator can only be applied to methods of zero arguments");var r="_memoized_"+t,i=n.value;return e[r]=xt,Object(o.__assign)(Object(o.__assign)({},n),{value:function(){return this[r]===xt&&(this[r]=i.call(this)),this[r]}})}(e,t,n);if("function"==typeof n.get)return function(e,t,n){var r="_memoized_"+t,i=n.get;return e[r]=xt,Object(o.__assign)(Object(o.__assign)({},n),{get:function(){return this[r]===xt&&(this[r]=i.call(this)),this[r]}})}(e,t,n);throw new Error("@memoize decorator can be applied to methods or getters, got "+String(n.value)+" instead")}var kt="hashchange",Ot=new(function(){function e(){var e=this;this.emit=function(){e._emiter.emit(kt,e.currentId)},this._emiter=new He.EventEmitter,this.bind()}return Object.defineProperty(e.prototype,"currentId",{get:function(){return ee?decodeURIComponent(window.location.hash.substring(1)):""},enumerable:!0,configurable:!0}),e.prototype.linkForId=function(e){return e?"#"+e:""},e.prototype.subscribe=function(e){var t=this._emiter.addListener(kt,e);return function(){return t.removeListener(kt,e)}},e.prototype.bind=function(){ee&&window.addEventListener("hashchange",this.emit,!1)},e.prototype.dispose=function(){ee&&window.removeEventListener("hashchange",this.emit)},e.prototype.replace=function(e,t){void 0===t&&(t=!1),ee&&null!=e&&e!==this.currentId&&(t?window.history.replaceState(null,"",window.location.href.split("#")[0]+this.linkForId(e)):(window.history.pushState(null,"",window.location.href.split("#")[0]+this.linkForId(e)),this.emit()))},Object(o.__decorate)([We.bind,We.debounce],e.prototype,"replace",null),e}());var _t=n(98),Et=function(){function e(){this.map=new Map,this.prevTerm=""}return e.prototype.add=function(e){this.map.set(e,new _t(e))},e.prototype.delete=function(e){this.map.delete(e)},e.prototype.addOnly=function(e){var t=this;this.map.forEach((function(n,r){-1===e.indexOf(r)&&(n.unmark(),t.map.delete(r))}));for(var n=0,r=e;n<r.length;n++){var o=r[n];this.map.has(o)||this.map.set(o,new _t(o))}},e.prototype.clearAll=function(){this.unmark(),this.map.clear()},e.prototype.mark=function(e){var t=this;(e||this.prevTerm)&&(this.map.forEach((function(n){n.unmark(),n.mark(e||t.prevTerm)})),this.prevTerm=e||this.prevTerm)},e.prototype.unmark=function(){this.map.forEach((function(e){return e.unmark()})),this.prevTerm=""},e}(),St=n(53),Tt=new St.Renderer;St.setOptions({renderer:Tt,highlight:function(e,t){return gt(e,t)}});var jt="(?:^ {0,3}\x3c!-- ReDoc-Inject:\\s+?<({component}).*?/?>\\s+?--\x3e\\s*$|(?:^ {0,3}<({component})([\\s\\S]*?)>([\\s\\S]*?)</\\2>|^ {0,3}<({component})([\\s\\S]*?)(?:/>|\\n{2,})))";var Ct=function(){function e(e){var t=this;this.options=e,this.headings=[],this.headingRule=function(e,n,r,o){return 1===n?t.currentTopHeading=t.saveHeading(e,n):2===n&&t.saveHeading(e,n,t.currentTopHeading&&t.currentTopHeading.items,t.currentTopHeading&&t.currentTopHeading.id),t.originalHeadingRule(e,n,r,o)},this.headingEnhanceRenderer=new St.Renderer,this.originalHeadingRule=this.headingEnhanceRenderer.heading.bind(this.headingEnhanceRenderer),this.headingEnhanceRenderer.heading=this.headingRule}return e.containsComponent=function(e,t){return new RegExp(jt.replace(/{component}/g,t),"gmi").test(e)},e.getTextBeforeHading=function(e,t){var n=e.search(new RegExp("^##?\\s+"+t,"m"));return n>-1?e.substring(0,n):e},e.prototype.saveHeading=function(e,t,n,r){void 0===n&&(n=this.headings),e=e.replace(/&#(\d+);/g,(function(e,t){return String.fromCharCode(parseInt(t,10))}));var o={id:r?r+"/"+ue(e):"section/"+ue(e),name:e,level:t,items:[]};return n.push(o),o},e.prototype.flattenHeadings=function(e){if(void 0===e)return[];for(var t=[],n=0,r=e;n<r.length;n++){var o=r[n];t.push(o),t.push.apply(t,this.flattenHeadings(o.items))}return t},e.prototype.attachHeadingsDescriptions=function(e){var t=function(e){return new RegExp("##?\\s+"+e.name.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&"))},n=this.flattenHeadings(this.headings);if(!(n.length<1)){for(var r=n[0],o=t(r),i=e.search(o),a=1;a<n.length;a++){var s=n[a],l=t(s),c=e.substr(i+1).search(l)+i+1;r.description=e.substring(i,c).replace(o,"").trim(),r=s,o=l,i=c}r.description=e.substring(i).replace(o,"").trim()}},e.prototype.renderMd=function(e,t){void 0===t&&(t=!1);var n=t?{renderer:this.headingEnhanceRenderer}:void 0;return St(e.toString(),n)},e.prototype.extractHeadings=function(e){this.renderMd(e,!0),this.attachHeadingsDescriptions(e);var t=this.headings;return this.headings=[],t},e.prototype.renderMdWithComponents=function(e){var t=this.options&&this.options.allowedMdComponents;if(!t||0===Object.keys(t).length)return[this.renderMd(e)];for(var n=Object.keys(t).join("|"),r=new RegExp(jt.replace(/{component}/g,n),"mig"),i=[],a=[],s=r.exec(e),l=0;s;){i.push(e.substring(l,s.index)),l=r.lastIndex;var c=t[s[1]||s[2]||s[5]],u=s[3]||s[6],p=s[4];c&&a.push({component:c.component,propsSelector:c.propsSelector,props:Object(o.__assign)(Object(o.__assign)(Object(o.__assign)({},At(u)),c.props),{children:p})}),s=r.exec(e)}i.push(e.substring(l));for(var f=[],d=0;d<i.length;d++){var h=i[d];h&&f.push(this.renderMd(h)),a[d]&&f.push(a[d])}return f},e}();function At(e){if(!e)return{};for(var t,n=/([\w-]+)\s*=\s*(?:{([^}]+?)}|"([^"]+?)")/gim,r={};null!==(t=n.exec(e));)if(t[3])r[t[1]]=t[3];else if(t[2]){var o=void 0;try{o=JSON.parse(t[2])}catch(e){}r[t[1]]=o}return r}var It=function(){function e(e){this.parser=e,Object.assign(this,e.spec.info),this.description=e.spec.info.description||"";var t=this.description.search(/^##?\s+/m);t>-1&&(this.description=this.description.substring(0,t)),this.downloadLink=this.getDownloadLink(),this.downloadFileName=this.getDownloadFileName()}return e.prototype.getDownloadLink=function(){if(this.parser.specUrl)return this.parser.specUrl;if(ee&&window.Blob&&window.URL&&window.URL.createObjectURL){var e=new Blob([JSON.stringify(this.parser.spec,null,2)],{type:"application/json"});return window.URL.createObjectURL(e)}},e.prototype.getDownloadFileName=function(){if(!this.parser.specUrl)return"swagger.json"},e}(),Pt=function(e,t,n){var r=e.deref(n);this.id=t,this.sectionId=dt+t,this.type=r.type,this.description=r.description||"","apiKey"===r.type&&(this.apiKey={name:r.name,in:r.in}),"http"===r.type&&(this.http={scheme:r.scheme,bearerFormat:r.bearerFormat}),"openIdConnect"===r.type&&(this.openId={connectUrl:r.openIdConnectUrl}),"oauth2"===r.type&&r.flows&&(this.flows=r.flows)},Rt=function(e){var t=e.spec.components&&e.spec.components.securitySchemes||{};this.schemes=Object.keys(t).map((function(n){return new Pt(e,n,t[n])}))},Nt=function(){function e(){this._counter={}}return e.prototype.reset=function(){this._counter={}},e.prototype.visit=function(e){this._counter[e]=this._counter[e]?this._counter[e]+1:1},e.prototype.exit=function(e){this._counter[e]=this._counter[e]&&this._counter[e]-1},e.prototype.visited=function(e){return!!this._counter[e]},e}(),Lt=function(){function e(e,t,n){var r=this;void 0===n&&(n=new ge({})),this.options=n,this._refCounter=new Nt,this.byRef=function(e){var t;if(r.spec){"#"!==e.charAt(0)&&(e="#"+e),e=decodeURIComponent(e);try{t=Qe.get(r.spec,e)}catch(e){}return t||{}}},this.validate(e),this.preprocess(e),this.spec=e,this.mergeRefs=new Set;var o=ee?window.location.href:"";"string"==typeof t&&(this.specUrl=Object(oe.resolve)(o,t))}return e.prototype.validate=function(e){if(void 0===e.openapi)throw new Error("Document must be valid OpenAPI 3.0.0 definition")},e.prototype.preprocess=function(e){if(!this.options.noAutoAuth&&e.info&&e.components&&e.components.securitySchemes){var t=e.info.description||"";if(!Ct.containsComponent(t,"security-definitions")&&!Ct.containsComponent(t,"SecurityDefinitions")){var n="\x3c!-- ReDoc-Inject: <"+"security-definitions"+"> --\x3e";e.info.description=function(e,t,n){var r=new RegExp("(^|\\n)#\\s?"+t+"\\s*\\n","i"),o=new RegExp("((\\n|^)#\\s*"+t+"\\s*(\\n|$)(?:.|\\n)*?)(\\n#|$)","i");if(r.test(e))return e.replace(o,"$1\n\n"+n+"\n$4");var i=""===e||e.endsWith("\n\n")?"":e.endsWith("\n")?"\n":"\n\n";return""+e+i+"# "+t+"\n\n"+n}(t,"Authentication",n)}}},e.prototype.isRef=function(e){return!!e&&(void 0!==e.$ref&&null!==e.$ref)},e.prototype.resetVisited=function(){this._refCounter=new Nt},e.prototype.exitRef=function(e){this.isRef(e)&&this._refCounter.exit(e.$ref)},e.prototype.deref=function(e,t){if(void 0===t&&(t=!1),this.isRef(e)){var n=this.byRef(e.$ref),r=this._refCounter.visited(e.$ref);if(this._refCounter.visit(e.$ref),r&&!t)return Object.assign({},n,{"x-circular-ref":!0});if(this.isRef(n)){var o=this.deref(n);return this.exitRef(n),o}return n}return e},e.prototype.shalowDeref=function(e){return this.isRef(e)?this.byRef(e.$ref):e},e.prototype.mergeAllOf=function(e,t,n,r){var i=this;if(void 0===n&&(n=!1),void 0===r&&(r=new Set),t&&r.add(t),void 0===(e=this.hoistOneOfs(e)).allOf)return e;var a=Object(o.__assign)(Object(o.__assign)({},e),{allOf:void 0,parentRefs:[],title:e.title||(lt(t)?Qe.baseName(t):void 0)});void 0!==a.properties&&"object"==typeof a.properties&&(a.properties=Object(o.__assign)({},a.properties)),void 0!==a.items&&"object"==typeof a.items&&(a.items=Object(o.__assign)({},a.items));for(var s=0,l=e.allOf.map((function(e){var t;if(!(e&&e.$ref&&r.has(e.$ref))){var o=i.deref(e,n),s=e.$ref||void 0,l=i.mergeAllOf(o,s,n,r);return(t=a.parentRefs).push.apply(t,l.parentRefs||[]),{$ref:s,schema:l}}})).filter((function(e){return void 0!==e}));s<l.length;s++){var c=l[s],u=c.$ref,p=c.schema;if(a.type!==p.type&&void 0!==a.type&&void 0!==p.type)throw new Error('Incompatible types in allOf at "'+t+'"');if(void 0!==p.type&&(a.type=p.type),void 0!==p.properties)for(var f in a.properties=a.properties||{},p.properties)a.properties[f]?a.properties[f]=this.mergeAllOf({allOf:[a.properties[f],p.properties[f]]},t+"/properties/"+f):a.properties[f]=p.properties[f];void 0!==p.items&&(a.items=a.items||{},a.items=this.mergeAllOf({allOf:[a.items,p.items]},t+"/items")),void 0!==p.required&&(a.required=(a.required||[]).concat(p.required)),a=Object(o.__assign)(Object(o.__assign)({},p),a),u&&(a.parentRefs.push(u),void 0===a.title&<(u))}return a},e.prototype.findDerived=function(e){var t={},n=this.spec.components&&this.spec.components.schemas||{};for(var r in n){var o=this.deref(n[r]);void 0!==o.allOf&&o.allOf.find((function(t){return void 0!==t.$ref&&e.indexOf(t.$ref)>-1}))&&(t["#/components/schemas/"+r]=[o["x-discriminator-value"]||r])}return t},e.prototype.exitParents=function(e){for(var t=0,n=e.parentRefs||[];t<n.length;t++){var r=n[t];this.exitRef({$ref:r})}},e.prototype.hoistOneOfs=function(e){var t=this;if(void 0===e.allOf)return e;for(var n=e.allOf,r=function(e){var r=n[e];if(Array.isArray(r.oneOf)){var i=n.slice(0,e),a=n.slice(e+1);return{value:{oneOf:r.oneOf.map((function(e){var n=t.mergeAllOf({allOf:Object(o.__spreadArrays)(i,[e],a)});return t.exitParents(n),n}))}}}},i=0;i<n.length;i++){var a=r(i);if("object"==typeof a)return a.value}return e},e}(),Mt=function(e,t,n){this.options=n,this.parser=new Lt(e,t,n),this.info=new It(this.parser),this.externalDocs=this.parser.spec.externalDocs,this.contentItems=Xt.buildStructure(this.parser,this.options),this.securitySchemes=new Rt(this.parser)},Dt=function(){function e(e,t,n){this.items=[],this.active=!1,this.expanded=!1,this.id=t.id||e+"/"+ue(t.name),this.type=e,this.name=t["x-displayName"]||t.name,this.level=t.level||1,this.description=t.description||"";var r=t.items;r&&r.length&&(this.description=Ct.getTextBeforeHading(this.description,r[0].name)),this.parent=n,this.externalDocs=t.externalDocs,"group"===this.type&&(this.expanded=!0)}return e.prototype.activate=function(){this.active=!0},e.prototype.expand=function(){this.parent&&this.parent.expand(),this.expanded=!0},e.prototype.collapse=function(){"group"!==this.type&&(this.expanded=!1)},e.prototype.deactivate=function(){this.active=!1},Object(o.__decorate)([ze.l],e.prototype,"active",void 0),Object(o.__decorate)([ze.l],e.prototype,"expanded",void 0),Object(o.__decorate)([ze.d],e.prototype,"activate",null),Object(o.__decorate)([ze.d],e.prototype,"expand",null),Object(o.__decorate)([ze.d],e.prototype,"collapse",null),Object(o.__decorate)([ze.d],e.prototype,"deactivate",null),e}(),Ft=function(e,t){var n=t.spec.components&&t.spec.components.securitySchemes||{};this.schemes=Object.keys(e||{}).map((function(r){var i=t.deref(n[r]),a=e[r]||[];if(i)return Object(o.__assign)(Object(o.__assign)({},i),{id:r,sectionId:dt+r,scopes:a});console.warn("Non existing security scheme referenced: "+r+". Skipping")})).filter((function(e){return void 0!==e}))},zt=function(){function e(e,t,n,r,o){void 0===o&&(o=!1),this.options=r,this.typePrefix="",this.isCircular=!1,this.activeOneOf=0,this.pointer=t.$ref||n||"",this.rawSchema=e.deref(t),this.schema=e.mergeAllOf(this.rawSchema,this.pointer,o),this.init(e,o),e.exitRef(t),e.exitParents(this.schema),r.showExtensions&&(this.extensions=ht(this.schema,r.showExtensions))}return e.prototype.activateOneOf=function(e){this.activeOneOf=e},e.prototype.init=function(t,n){var r=this,i=this.schema;if(this.isCircular=i["x-circular-ref"],this.title=i.title||lt(this.pointer)&&Qe.baseName(this.pointer)||"",this.description=i.description||"",this.type=i.type||function(e){if(void 0!==e.type)return e.type;for(var t=0,n=Object.keys(tt);t<n.length;t++){var r=n[t],o=tt[r];if(void 0!==e[r])return o}return"any"}(i),this.format=i.format,this.nullable=!!i.nullable,this.enum=i.enum||[],this.example=i.example,this.deprecated=!!i.deprecated,this.pattern=i.pattern,this.externalDocs=i.externalDocs,this.constraints=function(e){var t=[],n=ct("characters",e.minLength,e.maxLength);void 0!==n&&t.push(n);var r=ct("items",e.minItems,e.maxItems);void 0!==r&&t.push(r);var o,i=function(e){if(void 0!==e){var t=e.toString(10);return/^0\.0*1$/.test(t)?"decimal places <= "+t.split(".")[1].length:"multiple of "+t}}(e.multipleOf);return void 0!==i&&t.push(i),void 0!==e.minimum&&void 0!==e.maximum?(o=e.exclusiveMinimum?"( ":"[ ",o+=e.minimum,o+=" .. ",o+=e.maximum,o+=e.exclusiveMaximum?" )":" ]"):void 0!==e.maximum?(o=e.exclusiveMaximum?"< ":"<= ",o+=e.maximum):void 0!==e.minimum&&(o=e.exclusiveMinimum?"> ":">= ",o+=e.minimum),void 0!==o&&t.push(o),t}(i),this.displayType=this.type,this.displayFormat=this.format,this.isPrimitive=function(e,t){return void 0===t&&(t=e.type),void 0===e.oneOf&&void 0===e.anyOf&&("object"===t?void 0!==e.properties?0===Object.keys(e.properties).length:void 0===e.additionalProperties:"array"!==t||void 0===e.items)}(i,this.type),this.default=i.default,this.readOnly=!!i.readOnly,this.writeOnly=!!i.writeOnly,!this.isCircular){if(n||void 0===Ut(i))return n&&Array.isArray(i.oneOf)&&i.oneOf.find((function(e){return e.$ref===r.pointer}))&&delete i.oneOf,void 0!==i.oneOf?(this.initOneOf(i.oneOf,t),this.oneOfType="One of",void(void 0!==i.anyOf&&console.warn("oneOf and anyOf are not supported on the same level. Skipping anyOf at "+this.pointer))):void 0!==i.anyOf?(this.initOneOf(i.anyOf,t),void(this.oneOfType="Any of")):void("object"===this.type?this.fields=function(e,t,n,r){var i=t.properties||{},a=t.additionalProperties,s=t.default||{},l=Object.keys(i||[]).map((function(a){var l=i[a];l||(console.warn('Field "'+a+'" is invalid, skipping.\n Field must be an object but got '+typeof l+' at "'+n+'"'),l={});var c=void 0!==t.required&&t.required.indexOf(a)>-1;return new Bt(e,{name:a,required:c,schema:Object(o.__assign)(Object(o.__assign)({},l),{default:void 0===l.default?s[a]:l.default})},n+"/properties/"+a,r)}));r.sortPropsAlphabetically&&(l=pt(l,"name"));r.requiredPropsFirst&&(l=ut(l,r.sortPropsAlphabetically?void 0:t.required));"object"!=typeof a&&!0!==a||l.push(new Bt(e,{name:("object"==typeof a&&a["x-additionalPropertiesName"]||"property name").concat("*"),required:!1,schema:!0===a?{}:a,kind:"additionalProperties"},n+"/additionalProperties",r));return l}(t,i,this.pointer,this.options):"array"===this.type&&i.items&&(this.items=new e(t,i.items,this.pointer+"/items",this.options),this.displayType=this.items.displayType.split(" or ").map((function(e){return e.replace(/^(string|object|number|integer|array|boolean)s?( ?.*)/,"$1s$2")})).join(" or "),this.displayFormat=this.items.format,this.typePrefix=this.items.typePrefix+de("arrayOf"),this.title=this.title||this.items.title,this.isPrimitive=this.items.isPrimitive,void 0===this.example&&void 0!==this.items.example&&(this.example=[this.items.example]),this.items.isPrimitive&&(this.enum=this.items.enum)));this.initDiscriminator(i,t)}},e.prototype.initOneOf=function(t,n){var r=this;this.oneOf=t.map((function(t,i){var a=n.deref(t),s=n.mergeAllOf(a,r.pointer+"/oneOf/"+i),l=lt(t.$ref)&&!s.title?Qe.baseName(t.$ref):s.title,c=new e(n,Object(o.__assign)(Object(o.__assign)({},s),{title:l,allOf:[Object(o.__assign)(Object(o.__assign)({},r.schema),{oneOf:void 0,anyOf:void 0})]}),r.pointer+"/oneOf/"+i,r.options);return n.exitRef(t),n.exitParents(s),c})),this.displayType=this.oneOf.map((function(e){var t=e.typePrefix+(e.title?e.title+" ("+e.displayType+")":e.displayType);return t.indexOf(" or ")>-1&&(t="("+t+")"),t})).join(" or ")},e.prototype.initDiscriminator=function(t,n){var r=this,i=Ut(t);this.discriminatorProp=i.propertyName;var a=n.findDerived(Object(o.__spreadArrays)(t.parentRefs||[],[this.pointer]));if(t.oneOf)for(var s=0,l=t.oneOf;s<l.length;s++){var c=l[s];if(void 0!==c.$ref){var u=Qe.baseName(c.$ref);a[c.$ref]=u}}var p=i.mapping||{},f={};for(var d in p){var h=p[d];Array.isArray(f[h])?f[h].push(d):f[h]=[d]}for(var m=Object(o.__assign)(Object(o.__assign)({},a),f),g=[],y=0,v=Object.keys(m);y<v.length;y++){var b=m[h=v[y]];if(Array.isArray(b))for(var x=0,w=b;x<w.length;x++){var k=w[x];g.push({$ref:h,name:k})}else g.push({$ref:h,name:b})}this.oneOf=g.map((function(t){var o=t.$ref,i=t.name,a=new e(n,n.byRef(o),o,r.options,!0);return a.title=i,a}))},Object(o.__decorate)([ze.l],e.prototype,"activeOneOf",void 0),Object(o.__decorate)([ze.d],e.prototype,"activateOneOf",null),e}();function Ut(e){return e.discriminator||e["x-discriminator"]}var Bt=function(){function e(e,t,n,r){var o=e.deref(t);this.kind=t.kind||"field",this.name=t.name||o.name,this.in=o.in,this.required=!!o.required;var i=o.schema,a="";!i&&o.in&&o.content&&(a=Object.keys(o.content)[0],i=o.content[a]&&o.content[a].schema),this.schema=new zt(e,i||{},n,r),this.description=void 0===o.description?this.schema.description||"":o.description,this.example=o.example||this.schema.example,a?this.serializationMime=a:o.style?this.style=o.style:this.in&&(this.style=function(e){switch(e){case"header":return"simple";case"query":return"form";case"path":return"simple";default:return"form"}}(this.in)),this.explode=!!o.explode,this.deprecated=void 0===o.deprecated?!!this.schema.deprecated:o.deprecated,e.exitRef(t),r.showExtensions&&(this.extensions=ht(o,r.showExtensions))}return e.prototype.toggle=function(){this.expanded=!this.expanded},Object(o.__decorate)([ze.l],e.prototype,"expanded",void 0),Object(o.__decorate)([ze.d],e.prototype,"toggle",null),e}(),$t=n(99),qt={},Wt=function(){function e(e,t,n,r){this.mime=n;var o=e.deref(t);this.value=o.value,this.summary=o.summary,this.description=o.description,o.externalValue&&(this.externalValueUrl=Object(oe.resolve)(e.specUrl||"",o.externalValue)),e.exitRef(t),"application/x-www-form-urlencoded"===n&&this.value&&"object"==typeof this.value&&(this.value=function(e,t){if(void 0===t&&(t={}),Array.isArray(e))throw new Error("Payload must have fields: "+e.toString());return Object.keys(e).map((function(n){var r=e[n],o=t[n]||{},i=o.style,a=void 0===i?"form":i,s=o.explode,l=void 0===s||s;switch(a){case"form":return it(n,l,r);case"spaceDelimited":return rt(r,n,"%20");case"pipeDelimited":return rt(r,n,"|");case"deepObject":return ot(r,n);default:return console.warn("Incorrect or unsupported encoding style: "+a),""}})).join("&")}(this.value,r))}return e.prototype.getExternalValue=function(e){return this.externalValueUrl?(qt[this.externalValueUrl]||(qt[this.externalValueUrl]=fetch(this.externalValueUrl).then((function(t){return t.text().then((function(n){if(!t.ok)return Promise.reject(new Error(n));if(!nt(e))return n;try{return JSON.parse(n)}catch(e){return n}}))}))),qt[this.externalValueUrl]):Promise.resolve(void 0)},e}(),Ht=function(){function e(e,t,n,r,o){this.name=t,this.isRequestType=n,this.schema=r.schema&&new zt(e,r.schema,"",o),this.onlyRequiredInSamples=o.onlyRequiredInSamples,void 0!==r.examples?this.examples=function(e,t){var n={};for(var r in e)e.hasOwnProperty(r)&&(n[r]=t(e[r],r,e));return n}(r.examples,(function(n){return new Wt(e,n,t,r.encoding)})):void 0!==r.example?this.examples={default:new Wt(e,{value:e.shalowDeref(r.example)},t,r.encoding)}:nt(t)&&this.generateExample(e,r)}return e.prototype.generateExample=function(e,t){var n={skipReadOnly:this.isRequestType,skipNonRequired:this.isRequestType&&this.onlyRequiredInSamples,skipWriteOnly:!this.isRequestType};if(this.schema&&this.schema.oneOf){this.examples={};for(var r=0,o=this.schema.oneOf;r<o.length;r++){var i=o[r],a=$t.sample(i.rawSchema,n,e.spec);this.schema.discriminatorProp&&"object"==typeof a&&a&&(a[this.schema.discriminatorProp]=i.title),this.examples[i.title]=new Wt(e,{value:a},this.name,t.encoding)}}else this.schema&&(this.examples={default:new Wt(e,{value:$t.sample(t.schema,n,e.spec)},this.name,t.encoding)})},e}(),Vt=function(){function e(e,t,n,r){var i,a;this.isRequestType=n,this.activeMimeIdx=0,r.unstable_ignoreMimeParameters&&(i=t,a={},Object.keys(i).forEach((function(e){var t=i[e],n=e.split(";")[0].trim();a[n]?a[n]=Object(o.__assign)(Object(o.__assign)({},a[n]),t):a[n]=t})),t=a),this.mediaTypes=Object.keys(t).map((function(o){var i=t[o];return e.resetVisited(),new Ht(e,o,n,i,r)}))}return e.prototype.activate=function(e){this.activeMimeIdx=e},Object.defineProperty(e.prototype,"active",{get:function(){return this.mediaTypes[this.activeMimeIdx]},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"hasSample",{get:function(){return this.mediaTypes.filter((function(e){return!!e.examples})).length>0},enumerable:!0,configurable:!0}),Object(o.__decorate)([ze.l],e.prototype,"activeMimeIdx",void 0),Object(o.__decorate)([ze.d],e.prototype,"activate",null),Object(o.__decorate)([ze.e],e.prototype,"active",null),e}(),Yt=function(e,t,n){var r=e.deref(t);this.description=r.description||"",this.required=!!r.required,e.exitRef(t),void 0!==r.content&&(this.content=new Vt(e,r.content,!0,n))},Qt=function(){function e(e,t,n,r,i){this.headers=[],this.expanded="all"===i.expandResponses||i.expandResponses[t];var a=e.deref(r);e.exitRef(r),this.code=t,void 0!==a.content&&(this.content=new Vt(e,a.content,!1,i)),void 0!==a["x-summary"]?(this.summary=a["x-summary"],this.description=a.description||""):(this.summary=a.description||"",this.description=""),this.type=Ze(t,n);var s=a.headers;void 0!==s&&(this.headers=Object.keys(s).map((function(t){var n=s[t];return new Bt(e,Object(o.__assign)(Object(o.__assign)({},n),{name:t}),"",i)})))}return e.prototype.toggle=function(){this.expanded=!this.expanded},Object(o.__decorate)([ze.l],e.prototype,"expanded",void 0),Object(o.__decorate)([ze.d],e.prototype,"toggle",null),e}();var Gt=function(){function e(e,t,n,r){var o;this.parser=e,this.operationSpec=t,this.options=r,this.type="operation",this.items=[],this.ready=!0,this.active=!1,this.expanded=!1,this.pointer=Qe.compile(["paths",t.pathName,t.httpVerb]),this.id=void 0!==t.operationId?"operation/"+t.operationId:void 0!==n?n.id+this.pointer:this.pointer,this.name=(o=t).summary||o.operationId||o.description&&o.description.substring(0,50)||"<no summary>",this.description=t.description,this.parent=n,this.externalDocs=t.externalDocs,this.deprecated=!!t.deprecated,this.httpVerb=t.httpVerb,this.deprecated=!!t.deprecated,this.operationId=t.operationId,this.path=t.pathName;var i=e.byRef(Qe.compile(["paths",t.pathName]));this.servers=ft(e.specUrl,t.servers||i&&i.servers||e.spec.servers||[]),this.security=(t.security||e.spec.security||[]).map((function(t){return new Ft(t,e)})),r.showExtensions&&(this.extensions=ht(t,r.showExtensions))}return e.prototype.activate=function(){this.active=!0},e.prototype.deactivate=function(){this.active=!1},e.prototype.expand=function(){this.parent&&this.parent.expand()},e.prototype.collapse=function(){},Object.defineProperty(e.prototype,"requestBody",{get:function(){return this.operationSpec.requestBody&&new Yt(this.parser,this.operationSpec.requestBody,this.options)},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"codeSamples",{get:function(){var e=this.operationSpec["x-code-samples"]||[],t=this.requestBody&&this.requestBody.content;if(t&&t.hasSample){var n=Math.min(e.length,this.options.payloadSampleIdx);e=Object(o.__spreadArrays)(e.slice(0,n),[{lang:"payload",label:"Payload",source:"",requestBodyContent:t}],e.slice(n))}return e},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"parameters",{get:function(){var e=this,t=function(e,t,n){void 0===t&&(t=[]),void 0===n&&(n=[]);var r={};return n.forEach((function(t){t=e.shalowDeref(t),r[t.name+"_"+t.in]=!0})),(t=t.filter((function(t){return t=e.shalowDeref(t),!r[t.name+"_"+t.in]}))).concat(n)}(this.parser,this.operationSpec.pathParameters,this.operationSpec.parameters).map((function(t){return new Bt(e.parser,t,e.pointer,e.options)}));return this.options.sortPropsAlphabetically?pt(t,"name"):this.options.requiredPropsFirst?ut(t):t},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"responses",{get:function(){var e=this,t=!1;return Object.keys(this.operationSpec.responses||[]).filter((function(e){return"default"===e||("success"===Ze(e)&&(t=!0),"default"===(n=e)||se(n)||Ke(n));var n})).map((function(n){return new Qt(e.parser,n,t,e.operationSpec.responses[n],e.options)}))},enumerable:!0,configurable:!0}),Object(o.__decorate)([ze.l],e.prototype,"ready",void 0),Object(o.__decorate)([ze.l],e.prototype,"active",void 0),Object(o.__decorate)([ze.l],e.prototype,"expanded",void 0),Object(o.__decorate)([ze.d],e.prototype,"activate",null),Object(o.__decorate)([ze.d],e.prototype,"deactivate",null),Object(o.__decorate)([wt],e.prototype,"requestBody",null),Object(o.__decorate)([wt],e.prototype,"codeSamples",null),Object(o.__decorate)([wt],e.prototype,"parameters",null),Object(o.__decorate)([wt],e.prototype,"responses",null),e}(),Xt=function(){function e(){}return e.buildStructure=function(t,n){var r=t.spec,o=[],i=e.getTagsWithOperations(r);return o.push.apply(o,e.addMarkdownItems(r.info.description||"",void 0,1,n)),r["x-tagGroups"]&&r["x-tagGroups"].length>0?o.push.apply(o,e.getTagGroupsItems(t,void 0,r["x-tagGroups"],i,n)):o.push.apply(o,e.getTagsItems(t,i,void 0,void 0,n)),o},e.addMarkdownItems=function(e,t,n,r){var o=new Ct(r).extractHeadings(e||"");o.length&&t&&t.description&&(t.description=Ct.getTextBeforeHading(t.description,o[0].name));var i=function(e,t,n){return void 0===n&&(n=1),t.map((function(t){var r,o=new Dt("section",t,e);return o.depth=n,t.items&&(o.items=i(o,t.items,n+1)),Ct.containsComponent(o.description||"","security-definitions")&&(r=o.id+"/",dt=r),o}))};return i(t,o,n)},e.getTagGroupsItems=function(t,n,r,o,i){for(var a=[],s=0,l=r;s<l.length;s++){var c=l[s],u=new Dt("group",c,n);u.depth=0,u.items=e.getTagsItems(t,o,u,c,i),a.push(u)}return a},e.getTagsItems=function(t,n,r,i,a){for(var s=[],l=0,c=(void 0===i?Object.keys(n):i.tags).map((function(e){return n[e]?(n[e].used=!0,n[e]):(console.warn('Non-existing tag "'+e+'" is added to the group "'+i.name+'"'),null)}));l<c.length;l++){var u=c[l];if(u){var p=new Dt("tag",u,r);if(p.depth=1,""!==u.name)p.items=Object(o.__spreadArrays)(e.addMarkdownItems(u.description||"",p,p.depth+1,a),this.getOperationsItems(t,p,u,p.depth+1,a)),s.push(p);else{var f=Object(o.__spreadArrays)(e.addMarkdownItems(u.description||"",p,p.depth+1,a),this.getOperationsItems(t,void 0,u,p.depth+1,a));s.push.apply(s,f)}}}return s},e.getOperationsItems=function(e,t,n,r,o){if(0===n.operations.length)return[];for(var i=[],a=0,s=n.operations;a<s.length;a++){var l=s[a],c=new Gt(e,l,t,o);c.depth=r,i.push(c)}return i},e.getTagsWithOperations=function(e){for(var t={},n=0,r=e.tags||[];n<r.length;n++){t[(y=r[n]).name]=Object(o.__assign)(Object(o.__assign)({},y),{operations:[]})}for(var i=e.paths,a=0,s=Object.keys(i);a<s.length;a++)for(var l=s[a],c=i[l],u=0,p=Object.keys(c).filter(et);u<p.length;u++){var f=p[u],d=c[f],h=d.tags;h&&h.length||(h=[""]);for(var m=0,g=h;m<g.length;m++){var y,v=g[m];void 0===(y=t[v])&&(y={name:v,operations:[]},t[v]=y),y["x-traitTag"]||y.operations.push(Object(o.__assign)(Object(o.__assign)({},d),{pathName:l,httpVerb:f,pathParameters:c.parameters||[]}))}}return t},e}(),Kt=function(){function e(e,t,n){var r,o,i,a,s=this;this.scroll=t,this.history=n,this.activeItemIdx=-1,this.sideBarOpened=!1,this.updateOnScroll=function(e){for(var t=e?1:-1,n=s.activeItemIdx;(-1!==n||e)&&!(n>=s.flatItems.length-1&&e);){if(e){var r=s.getElementAtOrFirstChild(n+1);if(s.scroll.isElementBellow(r))break}else{r=s.getElementAt(n);if(s.scroll.isElementAbove(r))break}n+=t}s.activate(s.flatItems[n],!0,!0)},this.updateOnHistory=function(e){var t;(void 0===e&&(e=s.history.currentId),e)&&((t=s.flatItems.find((function(t){return t.id===e})))?s.activateAndScroll(t,!1):(e.startsWith(dt)&&(t=s.flatItems.find((function(e){return dt.startsWith(e.id)})),s.activate(t)),s.scroll.scrollIntoViewBySelector('[data-section-id="'+e+'"]')))},this.getItemById=function(e){return s.flatItems.find((function(t){return t.id===e}))},this.items=e.contentItems,this.flatItems=(r=this.items||[],o="items",i=[],(a=function(e){for(var t=0,n=e;t<n.length;t++){var r=n[t];i.push(r),r[o]&&a(r[o])}})(r),i),this.flatItems.forEach((function(e,t){return e.absoluteIdx=t})),this.subscribe()}return e.updateOnHistory=function(e,t){void 0===e&&(e=Ot.currentId),e&&t.scrollIntoViewBySelector('[data-section-id="'+e+'"]')},e.prototype.subscribe=function(){this._unsubscribe=this.scroll.subscribe(this.updateOnScroll),this._hashUnsubscribe=this.history.subscribe(this.updateOnHistory)},e.prototype.toggleSidebar=function(){this.sideBarOpened=!this.sideBarOpened},e.prototype.closeSidebar=function(){this.sideBarOpened=!1},e.prototype.getElementAt=function(e){var t=this.flatItems[e];return t&&te('[data-section-id="'+t.id+'"]')||null},e.prototype.getElementAtOrFirstChild=function(e){var t=this.flatItems[e];return t&&"group"===t.type&&(t=t.items[0]),t&&te('[data-section-id="'+t.id+'"]')||null},Object.defineProperty(e.prototype,"activeItem",{get:function(){return this.flatItems[this.activeItemIdx]||void 0},enumerable:!0,configurable:!0}),e.prototype.activate=function(e,t,n){void 0===t&&(t=!0),void 0===n&&(n=!1),(this.activeItem&&this.activeItem.id)!==(e&&e.id)&&(e&&"group"===e.type||(this.deactivate(this.activeItem),e?e.depth<=0||(this.activeItemIdx=e.absoluteIdx,t&&this.history.replace(e.id,n),e.activate(),e.expand()):this.history.replace("",n)))},e.prototype.deactivate=function(e){if(void 0!==e)for(e.deactivate();void 0!==e;)e.collapse(),e=e.parent},e.prototype.activateAndScroll=function(e,t,n){var r=e&&this.getItemById(e.id)||e;this.activate(r,t,n),this.scrollToActive(),r&&r.items.length||this.closeSidebar()},e.prototype.scrollToActive=function(){this.scroll.scrollIntoView(this.getElementAt(this.activeItemIdx))},e.prototype.dispose=function(){this._unsubscribe(),this._hashUnsubscribe()},Object(o.__decorate)([ze.l],e.prototype,"activeItemIdx",void 0),Object(o.__decorate)([ze.l],e.prototype,"sideBarOpened",void 0),Object(o.__decorate)([ze.d],e.prototype,"toggleSidebar",null),Object(o.__decorate)([ze.d],e.prototype,"closeSidebar",null),Object(o.__decorate)([ze.d],e.prototype,"activate",null),Object(o.__decorate)([ze.d.bound],e.prototype,"activateAndScroll",null),e}(),Zt=function(){function e(e){this.options=e,this._prevOffsetY=0,this._scrollParent=ee?window:void 0,this._emiter=new He,this.bind()}return e.prototype.bind=function(){this._prevOffsetY=this.scrollY(),this._scrollParent&&this._scrollParent.addEventListener("scroll",this.handleScroll)},e.prototype.dispose=function(){this._scrollParent&&this._scrollParent.removeEventListener("scroll",this.handleScroll),this._emiter.removeAllListeners("scroll")},e.prototype.scrollY=function(){return"undefined"!=typeof HTMLElement&&this._scrollParent instanceof HTMLElement?this._scrollParent.scrollTop:void 0!==this._scrollParent?this._scrollParent.pageYOffset:0},e.prototype.isElementBellow=function(e){if(null!==e)return e.getBoundingClientRect().top>this.options.scrollYOffset()},e.prototype.isElementAbove=function(e){if(null!==e){var t=e.getBoundingClientRect().top;return(t>0?Math.floor(t):Math.ceil(t))<=this.options.scrollYOffset()}},e.prototype.subscribe=function(e){var t=this._emiter.addListener("scroll",e);return function(){return t.removeListener("scroll",e)}},e.prototype.scrollIntoView=function(e){null!==e&&(e.scrollIntoView(),this._scrollParent&&this._scrollParent.scrollBy&&this._scrollParent.scrollBy(0,1-this.options.scrollYOffset()))},e.prototype.scrollIntoViewBySelector=function(e){var t=te(e);this.scrollIntoView(t)},e.prototype.handleScroll=function(){var e=this.scrollY()-this._prevOffsetY>0;this._prevOffsetY=this.scrollY(),this._emiter.emit("scroll",e)},Object(o.__decorate)([We.bind,yt(100)],e.prototype,"handleScroll",null),e}();var Jt,en,tn,nn,rn,on,an,sn,ln,cn,un,pn,fn,dn,hn,mn,gn,yn=function(){function e(){this.searchWorker=function(){var e;if(ee)try{e=n(315)}catch(t){e=n(150).default}else e=n(150).default;return new e}()}return e.prototype.indexItems=function(e){var t=this,n=function(e){e.forEach((function(e){"group"!==e.type&&t.add(e.name,e.description||"",e.id),n(e.items)}))};n(e),this.searchWorker.done()},e.prototype.add=function(e,t,n){this.searchWorker.add(e,t,n)},e.prototype.dispose=function(){this.searchWorker.terminate()},e.prototype.search=function(e){return this.searchWorker.search(e)},e.prototype.toJS=function(){return Object(o.__awaiter)(this,void 0,void 0,(function(){return Object(o.__generator)(this,(function(e){return[2,this.searchWorker.toJS()]}))}))},e.prototype.load=function(e){this.searchWorker.load(e)},e}(),vn=_e.div(en||(en=Object(o.__makeTemplateObject)(["\n width: calc(100% - ",");\n padding: 0 ","px;\n\n ",";\n"],["\n width: calc(100% - ",");\n padding: 0 ","px;\n\n ",";\n"])),(function(e){return e.theme.rightPanel.width}),(function(e){return e.theme.spacing.sectionHorizontal}),(function(e){var t=e.compact,n=e.theme;return Oe("medium",!0)(Jt||(Jt=Object(o.__makeTemplateObject)(["\n width: 100%;\n padding: ",";\n "],["\n width: 100%;\n padding: ",";\n "])),(t?0:n.spacing.sectionVertical)+"px "+n.spacing.sectionHorizontal+"px")})),bn=_e.div.attrs((function(e){var t;return(t={})["data-section-id"]=e.id,t}))(nn||(nn=Object(o.__makeTemplateObject)(["\n padding: ","px 0;\n\n &:last-child {\n min-height: calc(100vh + 1px);\n }\n\n & > &:last-child {\n min-height: initial;\n }\n\n ","\n ","\n"],["\n padding: ","px 0;\n\n &:last-child {\n min-height: calc(100vh + 1px);\n }\n\n & > &:last-child {\n min-height: initial;\n }\n\n ","\n ","\n"])),(function(e){return e.theme.spacing.sectionVertical}),Oe("medium",!0)(tn||(tn=Object(o.__makeTemplateObject)(["\n padding: 0;\n "],["\n padding: 0;\n "]))),(function(e){return e.underlined?"\n position: relative;\n\n &:not(:last-of-type):after {\n position: absolute;\n bottom: 0;\n width: 100%;\n display: block;\n content: '';\n border-bottom: 1px solid rgba(0, 0, 0, 0.2);\n }\n ":""})),xn=_e.div(on||(on=Object(o.__makeTemplateObject)(["\n width: ",";\n color: ",";\n background-color: ",";\n padding: 0 ","px;\n\n ",";\n"],["\n width: ",";\n color: ",";\n background-color: ",";\n padding: 0 ","px;\n\n ",";\n"])),(function(e){return e.theme.rightPanel.width}),(function(e){return e.theme.rightPanel.textColor}),(function(e){return e.theme.rightPanel.backgroundColor}),(function(e){return e.theme.spacing.sectionHorizontal}),Oe("medium",!0)(rn||(rn=Object(o.__makeTemplateObject)(["\n width: 100%;\n padding: ",";\n "],["\n width: 100%;\n padding: ",";\n "])),(function(e){return e.theme.spacing.sectionVertical+"px "+e.theme.spacing.sectionHorizontal+"px"}))),wn=_e(xn)(an||(an=Object(o.__makeTemplateObject)(["\n background-color: ",";\n"],["\n background-color: ",";\n"])),(function(e){return e.theme.rightPanel.backgroundColor})),kn=_e.div(ln||(ln=Object(o.__makeTemplateObject)(["\n display: flex;\n width: 100%;\n padding: 0;\n\n ",";\n"],["\n display: flex;\n width: 100%;\n padding: 0;\n\n ",";\n"])),Oe("medium",!0)(sn||(sn=Object(o.__makeTemplateObject)(["\n flex-direction: column;\n "],["\n flex-direction: column;\n "])))),On={1:"1.85714em",2:"1.57143em",3:"1.27em"},_n=function(e){return be(cn||(cn=Object(o.__makeTemplateObject)(["\n font-family: ",";\n font-weight: ",";\n font-size: ",";\n line-height: ",";\n"],["\n font-family: ",";\n font-weight: ",";\n font-size: ",";\n line-height: ",";\n"])),(function(e){return e.theme.typography.headings.fontFamily}),(function(e){return e.theme.typography.headings.fontWeight}),On[e],(function(e){return e.theme.typography.headings.lineHeight}))},En=_e.h1(un||(un=Object(o.__makeTemplateObject)(["\n ",";\n color: ",";\n\n ",";\n"],["\n ",";\n color: ",";\n\n ",";\n"])),_n(1),(function(e){return e.theme.colors.primary.main}),Ee("H1")),Sn=_e.h2(pn||(pn=Object(o.__makeTemplateObject)(["\n ",";\n color: black;\n\n ",";\n"],["\n ",";\n color: black;\n\n ",";\n"])),_n(2),Ee("H2")),Tn=(_e.h2(fn||(fn=Object(o.__makeTemplateObject)(["\n ",";\n color: black;\n\n ",";\n"],["\n ",";\n color: black;\n\n ",";\n"])),_n(3),Ee("H3")),_e.h3(dn||(dn=Object(o.__makeTemplateObject)(["\n color: ",";\n\n ",";\n"],["\n color: ",";\n\n ",";\n"])),(function(e){return e.theme.rightPanel.textColor}),Ee("RightPanelHeader"))),jn=_e.h5(hn||(hn=Object(o.__makeTemplateObject)(["\n border-bottom: 1px solid rgba(38, 50, 56, 0.3);\n margin: 1em 0 1em 0;\n color: rgba(38, 50, 56, 0.5);\n font-weight: normal;\n text-transform: uppercase;\n font-size: 0.929em;\n line-height: 20px;\n\n ",";\n"],["\n border-bottom: 1px solid rgba(38, 50, 56, 0.3);\n margin: 1em 0 1em 0;\n color: rgba(38, 50, 56, 0.5);\n font-weight: normal;\n text-transform: uppercase;\n font-size: 0.929em;\n line-height: 20px;\n\n ",";\n"])),Ee("UnderlinedHeader")),Cn=n(157),An=Object(i.createContext)(void 0),In=An.Provider,Pn=An.Consumer,Rn=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.state={loading:!0,resolvedSpec:null},t}return Object(o.__extends)(t,e),t.getDerivedStateFromProps=function(e,t){return e.specUrl!==t.prevSpecUrl||e.spec!==t.prevSpec?{loading:!0,resolvedSpec:null,prevSpec:e.spec,prevSpecUrl:e.specUrl}:null},t.prototype.makeStore=function(e,t,n){if(e)try{return new ps(e,t,n)}catch(e){throw this.props.onLoaded&&this.props.onLoaded(e),e}},t.prototype.componentDidMount=function(){this.load()},t.prototype.componentDidUpdate=function(){null===this.state.resolvedSpec?this.load():!this.state.loading&&this.props.onLoaded&&this.props.onLoaded()},t.prototype.load=function(){return Object(o.__awaiter)(this,void 0,void 0,(function(){var e,t,n,r,i;return Object(o.__generator)(this,(function(o){switch(o.label){case 0:e=this.props,t=e.specUrl,n=e.spec,o.label=1;case 1:return o.trys.push([1,3,,4]),[4,$e(n||t)];case 2:return r=o.sent(),this.setState({resolvedSpec:r,loading:!1}),[3,4];case 3:return i=o.sent(),this.props.onLoaded&&this.props.onLoaded(i),this.setState({error:i}),[3,4];case 4:return[2]}}))}))},t.prototype.render=function(){if(this.state.error)throw this.state.error;var e=this.props,t=e.specUrl,n=e.options,r=this.state,o=r.loading,i=r.resolvedSpec;return this.props.children({loading:o,store:this.makeStore(i,t,n)})},Object(o.__decorate)([Cn],t.prototype,"makeStore",null),t}(i.Component),Nn=function(e){return be(mn||(mn=Object(o.__makeTemplateObject)(["\n "," {\n cursor: pointer;\n margin-left: -20px;\n padding: 0;\n line-height: 1;\n width: 20px;\n display: inline-block;\n }\n ",":before {\n content: '';\n width: 15px;\n height: 15px;\n background-size: contain;\n background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMCIgeT0iMCIgd2lkdGg9IjUxMiIgaGVpZ2h0PSI1MTIiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIj48cGF0aCBmaWxsPSIjMDEwMTAxIiBkPSJNNDU5LjcgMjMzLjRsLTkwLjUgOTAuNWMtNTAgNTAtMTMxIDUwLTE4MSAwIC03LjktNy44LTE0LTE2LjctMTkuNC0yNS44bDQyLjEtNDIuMWMyLTIgNC41LTMuMiA2LjgtNC41IDIuOSA5LjkgOCAxOS4zIDE1LjggMjcuMiAyNSAyNSA2NS42IDI0LjkgOTAuNSAwbDkwLjUtOTAuNWMyNS0yNSAyNS02NS42IDAtOTAuNSAtMjQuOS0yNS02NS41LTI1LTkwLjUgMGwtMzIuMiAzMi4yYy0yNi4xLTEwLjItNTQuMi0xMi45LTgxLjYtOC45bDY4LjYtNjguNmM1MC01MCAxMzEtNTAgMTgxIDBDNTA5LjYgMTAyLjMgNTA5LjYgMTgzLjQgNDU5LjcgMjMzLjR6TTIyMC4zIDM4Mi4ybC0zMi4yIDMyLjJjLTI1IDI0LjktNjUuNiAyNC45LTkwLjUgMCAtMjUtMjUtMjUtNjUuNiAwLTkwLjVsOTAuNS05MC41YzI1LTI1IDY1LjUtMjUgOTAuNSAwIDcuOCA3LjggMTIuOSAxNy4yIDE1LjggMjcuMSAyLjQtMS40IDQuOC0yLjUgNi44LTQuNWw0Mi4xLTQyYy01LjQtOS4yLTExLjYtMTgtMTkuNC0yNS44IC01MC01MC0xMzEtNTAtMTgxIDBsLTkwLjUgOTAuNWMtNTAgNTAtNTAgMTMxIDAgMTgxIDUwIDUwIDEzMSA1MCAxODEgMGw2OC42LTY4LjZDMjc0LjYgMzk1LjEgMjQ2LjQgMzkyLjMgMjIwLjMgMzgyLjJ6Ii8+PC9zdmc+Cg==');\n opacity: 0.5;\n visibility: hidden;\n display: inline-block;\n vertical-align: middle;\n }\n\n h1:hover > ","::before, h2:hover > ","::before, ",":hover::before {\n visibility: visible;\n }\n"],["\n "," {\n cursor: pointer;\n margin-left: -20px;\n padding: 0;\n line-height: 1;\n width: 20px;\n display: inline-block;\n }\n ",":before {\n content: '';\n width: 15px;\n height: 15px;\n background-size: contain;\n background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMCIgeT0iMCIgd2lkdGg9IjUxMiIgaGVpZ2h0PSI1MTIiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIj48cGF0aCBmaWxsPSIjMDEwMTAxIiBkPSJNNDU5LjcgMjMzLjRsLTkwLjUgOTAuNWMtNTAgNTAtMTMxIDUwLTE4MSAwIC03LjktNy44LTE0LTE2LjctMTkuNC0yNS44bDQyLjEtNDIuMWMyLTIgNC41LTMuMiA2LjgtNC41IDIuOSA5LjkgOCAxOS4zIDE1LjggMjcuMiAyNSAyNSA2NS42IDI0LjkgOTAuNSAwbDkwLjUtOTAuNWMyNS0yNSAyNS02NS42IDAtOTAuNSAtMjQuOS0yNS02NS41LTI1LTkwLjUgMGwtMzIuMiAzMi4yYy0yNi4xLTEwLjItNTQuMi0xMi45LTgxLjYtOC45bDY4LjYtNjguNmM1MC01MCAxMzEtNTAgMTgxIDBDNTA5LjYgMTAyLjMgNTA5LjYgMTgzLjQgNDU5LjcgMjMzLjR6TTIyMC4zIDM4Mi4ybC0zMi4yIDMyLjJjLTI1IDI0LjktNjUuNiAyNC45LTkwLjUgMCAtMjUtMjUtMjUtNjUuNiAwLTkwLjVsOTAuNS05MC41YzI1LTI1IDY1LjUtMjUgOTAuNSAwIDcuOCA3LjggMTIuOSAxNy4yIDE1LjggMjcuMSAyLjQtMS40IDQuOC0yLjUgNi44LTQuNWw0Mi4xLTQyYy01LjQtOS4yLTExLjYtMTgtMTkuNC0yNS44IC01MC01MC0xMzEtNTAtMTgxIDBsLTkwLjUgOTAuNWMtNTAgNTAtNTAgMTMxIDAgMTgxIDUwIDUwIDEzMSA1MCAxODEgMGw2OC42LTY4LjZDMjc0LjYgMzk1LjEgMjQ2LjQgMzkyLjMgMjIwLjMgMzgyLjJ6Ii8+PC9zdmc+Cg==');\n opacity: 0.5;\n visibility: hidden;\n display: inline-block;\n vertical-align: middle;\n }\n\n h1:hover > ","::before, h2:hover > ","::before, ",":hover::before {\n visibility: visible;\n }\n"])),e,e,e,e,e)},Ln=function(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)},Mn=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.navigate=function(e,n){n.defaultPrevented||0!==n.button||Ln(n)||(n.preventDefault(),e.replace(t.props.to))},t}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this;return i.createElement(Pn,null,(function(t){return i.createElement("a",{className:e.props.className,href:t.menu.history.linkForId(e.props.to),onClick:e.navigate.bind(e,t.menu.history)},e.props.children)}))},t}(i.Component),Dn=_e(Mn)(gn||(gn=Object(o.__makeTemplateObject)(["\n ",";\n"],["\n ",";\n"])),Nn("&"));function Fn(e){return i.createElement(Dn,{to:e.to})}var zn,Un,Bn,$n,qn,Wn,Hn,Vn,Yn,Qn,Gn,Xn,Kn,Zn,Jn,er,tr,nr,rr,or={left:"90deg",right:"-90deg",up:"-180deg",down:"0"},ir=_e(function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){return i.createElement("svg",{className:this.props.className,style:this.props.style,version:"1.1",viewBox:"0 0 24 24",x:"0",xmlns:"http://www.w3.org/2000/svg",y:"0"},i.createElement("polygon",{points:"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "}))},t}(i.PureComponent))(zn||(zn=Object(o.__makeTemplateObject)(["\n height: ",";\n width: ",";\n vertical-align: middle;\n float: ",";\n transition: transform 0.2s ease-out;\n transform: rotateZ(",");\n\n polygon {\n fill: ",";\n }\n"],["\n height: ",";\n width: ",";\n vertical-align: middle;\n float: ",";\n transition: transform 0.2s ease-out;\n transform: rotateZ(",");\n\n polygon {\n fill: ",";\n }\n"])),(function(e){return e.size||"18px"}),(function(e){return e.size||"18px"}),(function(e){return e.float||""}),(function(e){return or[e.direction||"down"]}),(function(e){return e.color&&e.theme.colors[e.color]&&e.theme.colors[e.color].main||e.color})),ar=_e.span(Un||(Un=Object(o.__makeTemplateObject)(["\n display: inline-block;\n padding: 0 5px;\n margin: 0;\n background-color: ",";\n color: ",";\n font-size: ",";\n vertical-align: text-top;\n"],["\n display: inline-block;\n padding: 0 5px;\n margin: 0;\n background-color: ",";\n color: ",";\n font-size: ",";\n vertical-align: text-top;\n"])),(function(e){return e.theme.colors[e.type].main}),(function(e){return e.theme.colors[e.type].contrastText}),(function(e){return e.theme.typography.code.fontSize})),sr=be(Bn||(Bn=Object(o.__makeTemplateObject)(["\n text-decoration: line-through;\n color: #bdccd3;\n"],["\n text-decoration: line-through;\n color: #bdccd3;\n"]))),lr=_e.caption($n||($n=Object(o.__makeTemplateObject)(["\n text-align: right;\n font-size: 0.9em;\n font-weight: normal;\n color: ",";\n"],["\n text-align: right;\n font-size: 0.9em;\n font-weight: normal;\n color: ",";\n"])),(function(e){return e.theme.colors.text.secondary})),cr=_e.td(qn||(qn=Object(o.__makeTemplateObject)(["\n border-left: 1px solid ",";\n box-sizing: border-box;\n position: relative;\n padding: 10px 10px 10px 0;\n\n tr:first-of-type > &,\n tr.last > & {\n border-left-width: 0;\n background-position: top left;\n background-repeat: no-repeat;\n background-size: 1px 100%;\n }\n\n tr:first-of-type > & {\n background-image: linear-gradient(\n to bottom,\n transparent 0%,\n transparent 22px,\n "," 22px,\n "," 100%\n );\n }\n\n tr.last > & {\n background-image: linear-gradient(\n to bottom,\n "," 0%,\n "," 22px,\n transparent 22px,\n transparent 100%\n );\n }\n\n tr.last + tr > & {\n border-left-color: transparent;\n }\n\n tr.last:first-child > & {\n background: none;\n border-left-color: transparent;\n }\n"],["\n border-left: 1px solid ",";\n box-sizing: border-box;\n position: relative;\n padding: 10px 10px 10px 0;\n\n tr:first-of-type > &,\n tr.last > & {\n border-left-width: 0;\n background-position: top left;\n background-repeat: no-repeat;\n background-size: 1px 100%;\n }\n\n tr:first-of-type > & {\n background-image: linear-gradient(\n to bottom,\n transparent 0%,\n transparent 22px,\n "," 22px,\n "," 100%\n );\n }\n\n tr.last > & {\n background-image: linear-gradient(\n to bottom,\n "," 0%,\n "," 22px,\n transparent 22px,\n transparent 100%\n );\n }\n\n tr.last + tr > & {\n border-left-color: transparent;\n }\n\n tr.last:first-child > & {\n background: none;\n border-left-color: transparent;\n }\n"])),(function(e){return e.theme.schema.linesColor}),(function(e){return e.theme.schema.linesColor}),(function(e){return e.theme.schema.linesColor}),(function(e){return e.theme.schema.linesColor}),(function(e){return e.theme.schema.linesColor})),ur=_e(cr)(Wn||(Wn=Object(o.__makeTemplateObject)(["\n padding: 0;\n"],["\n padding: 0;\n"]))),pr=_e(cr)(Hn||(Hn=Object(o.__makeTemplateObject)(["\n vertical-align: top;\n line-height: 20px;\n white-space: nowrap;\n font-size: 0.929em;\n font-family: ",";\n\n &.deprecated {\n ",";\n }\n\n ",";\n\n ",";\n"],["\n vertical-align: top;\n line-height: 20px;\n white-space: nowrap;\n font-size: 0.929em;\n font-family: ",";\n\n &.deprecated {\n ",";\n }\n\n ",";\n\n ",";\n"])),(function(e){return e.theme.typography.code.fontFamily}),sr,(function(e){return"field"!==e.kind?"font-style: italic":""}),Ee("PropertyNameCell")),fr=_e.td(Vn||(Vn=Object(o.__makeTemplateObject)(["\n border-bottom: 1px solid #9fb4be;\n padding: 10px 0;\n width: ",";\n box-sizing: border-box;\n\n tr.expanded & {\n border-bottom: none;\n }\n"],["\n border-bottom: 1px solid #9fb4be;\n padding: 10px 0;\n width: ",";\n box-sizing: border-box;\n\n tr.expanded & {\n border-bottom: none;\n }\n"])),(function(e){return e.theme.schema.defaultDetailsWidth})),dr=_e.span(Yn||(Yn=Object(o.__makeTemplateObject)(["\n color: ",";\n font-family: ",";\n margin-right: 10px;\n\n &::before {\n content: '';\n display: inline-block;\n vertical-align: middle;\n width: 10px;\n height: 1px;\n background: ",";\n }\n\n &::after {\n content: '';\n display: inline-block;\n vertical-align: middle;\n width: 1px;\n background: ",";\n height: 7px;\n }\n"],["\n color: ",";\n font-family: ",";\n margin-right: 10px;\n\n &::before {\n content: '';\n display: inline-block;\n vertical-align: middle;\n width: 10px;\n height: 1px;\n background: ",";\n }\n\n &::after {\n content: '';\n display: inline-block;\n vertical-align: middle;\n width: 1px;\n background: ",";\n height: 7px;\n }\n"])),(function(e){return e.theme.schema.linesColor}),(function(e){return e.theme.typography.code.fontFamily}),(function(e){return e.theme.schema.linesColor}),(function(e){return e.theme.schema.linesColor})),hr=_e.div(Qn||(Qn=Object(o.__makeTemplateObject)(["\n padding: ",";\n"],["\n padding: ",";\n"])),(function(e){return e.theme.schema.nestingSpacing})),mr=_e.table(Gn||(Gn=Object(o.__makeTemplateObject)(["\n border-collapse: separate;\n border-radius: 3px;\n font-size: ",";\n\n border-spacing: 0;\n width: 100%;\n\n > tr {\n vertical-align: middle;\n }\n\n &\n ",",\n &\n ","\n ","\n ",",\n &\n ","\n ","\n ","\n ","\n "," {\n margin: ",";\n margin-right: 0;\n background: ",";\n }\n\n &\n ","\n ",",\n &\n ","\n ","\n ","\n ",",\n &\n ","\n ","\n ","\n ","\n ","\n "," {\n background: #ffffff;\n }\n"],["\n border-collapse: separate;\n border-radius: 3px;\n font-size: ",";\n\n border-spacing: 0;\n width: 100%;\n\n > tr {\n vertical-align: middle;\n }\n\n &\n ",",\n &\n ","\n ","\n ",",\n &\n ","\n ","\n ","\n ","\n "," {\n margin: ",";\n margin-right: 0;\n background: ",";\n }\n\n &\n ","\n ",",\n &\n ","\n ","\n ","\n ",",\n &\n ","\n ","\n ","\n ","\n ","\n "," {\n background: #ffffff;\n }\n"])),(function(e){return e.theme.typography.fontSize}),hr,hr,hr,hr,hr,hr,hr,hr,hr,(function(e){return e.theme.schema.nestingSpacing}),(function(e){return e.theme.schema.nestedBackground}),hr,hr,hr,hr,hr,hr,hr,hr,hr,hr,hr,hr),gr=_e.ul(Xn||(Xn=Object(o.__makeTemplateObject)(["\n margin: 0 0 3px 0;\n padding: 0;\n list-style: none;\n display: inline-block;\n"],["\n margin: 0 0 3px 0;\n padding: 0;\n list-style: none;\n display: inline-block;\n"]))),yr=_e.span(Kn||(Kn=Object(o.__makeTemplateObject)(["\n font-size: 0.9em;\n margin-right: 10px;\n color: ",";\n font-family: ",";\n}\n"],["\n font-size: 0.9em;\n margin-right: 10px;\n color: ",";\n font-family: ",";\n}\n"])),(function(e){return e.theme.colors.primary.main}),(function(e){return e.theme.typography.headings.fontFamily})),vr=_e.li(Zn||(Zn=Object(o.__makeTemplateObject)(["\n display: inline-block;\n margin-right: 10px;\n margin-bottom: 5px;\n font-size: 0.8em;\n cursor: pointer;\n border: 1px solid ",";\n padding: 2px 10px;\n\n ","\n"],["\n display: inline-block;\n margin-right: 10px;\n margin-bottom: 5px;\n font-size: 0.8em;\n cursor: pointer;\n border: 1px solid ",";\n padding: 2px 10px;\n\n ","\n"])),(function(e){return e.theme.colors.primary.main}),(function(e){return e.active?"\n color: white;\n background-color: "+e.theme.colors.primary.main+";\n ":"\n color: "+e.theme.colors.primary.main+";\n background-color: white;\n "})),br=_e.div(Jn||(Jn=Object(o.__makeTemplateObject)(["\n font-size: 0.9em;\n font-family: ",";\n &::after {\n content: ' [';\n }\n"],["\n font-size: 0.9em;\n font-family: ",";\n &::after {\n content: ' [';\n }\n"])),(function(e){return e.theme.typography.code.fontFamily})),xr=_e.div(er||(er=Object(o.__makeTemplateObject)(["\n font-size: 0.9em;\n font-family: ",";\n &::after {\n content: ']';\n }\n"],["\n font-size: 0.9em;\n font-family: ",";\n &::after {\n content: ']';\n }\n"])),(function(e){return e.theme.typography.code.fontFamily})),wr=n(158),kr=_e(n.n(wr).a)(tr||(tr=Object(o.__makeTemplateObject)(["\n min-width: 100px;\n display: inline-block;\n position: relative;\n width: auto;\n font-family: ",";\n\n .Dropdown-control {\n font-family: ",";\n position: relative;\n font-size: 0.929em;\n width: 100%;\n line-height: 1.5em;\n vertical-align: middle;\n cursor: pointer;\n border-color: rgba(38, 50, 56, 0.5);\n color: #263238;\n outline: none;\n padding: 0.15em 1.5em 0.2em 0.5em;\n border-radius: 2px;\n border-width: 1px;\n border-style: solid;\n margin-top: 5px;\n background: white;\n\n box-sizing: border-box;\n\n &:hover {\n border-color: ",";\n color: ",";\n box-shadow: 0px 2px 4px 0px rgba(34, 36, 38, 0.12);\n }\n }\n\n .Dropdown-arrow {\n border-color: "," transparent transparent;\n border-style: solid;\n border-width: 0.35em 0.35em 0;\n content: ' ';\n display: block;\n height: 0;\n position: absolute;\n right: 0.3em;\n top: 50%;\n margin-top: -0.125em;\n width: 0;\n }\n\n .Dropdown-menu {\n position: absolute;\n margin-top: 2px;\n left: 0;\n right: 0;\n\n z-index: 10;\n min-width: 100px;\n\n background: white;\n border: 1px solid rgba(38, 50, 56, 0.2);\n box-shadow: 0px 2px 4px 0px rgba(34, 36, 38, 0.12), 0px 2px 10px 0px rgba(34, 36, 38, 0.08);\n\n max-height: 220px;\n overflow: auto;\n }\n\n .Dropdown-option {\n font-size: 0.9em;\n color: #263238;\n cursor: pointer;\n padding: 0.4em;\n\n &.is-selected {\n background-color: rgba(0, 0, 0, 0.05);\n }\n\n &:hover {\n background-color: rgba(38, 50, 56, 0.12);\n }\n }\n"],["\n min-width: 100px;\n display: inline-block;\n position: relative;\n width: auto;\n font-family: ",";\n\n .Dropdown-control {\n font-family: ",";\n position: relative;\n font-size: 0.929em;\n width: 100%;\n line-height: 1.5em;\n vertical-align: middle;\n cursor: pointer;\n border-color: rgba(38, 50, 56, 0.5);\n color: #263238;\n outline: none;\n padding: 0.15em 1.5em 0.2em 0.5em;\n border-radius: 2px;\n border-width: 1px;\n border-style: solid;\n margin-top: 5px;\n background: white;\n\n box-sizing: border-box;\n\n &:hover {\n border-color: ",";\n color: ",";\n box-shadow: 0px 2px 4px 0px rgba(34, 36, 38, 0.12);\n }\n }\n\n .Dropdown-arrow {\n border-color: "," transparent transparent;\n border-style: solid;\n border-width: 0.35em 0.35em 0;\n content: ' ';\n display: block;\n height: 0;\n position: absolute;\n right: 0.3em;\n top: 50%;\n margin-top: -0.125em;\n width: 0;\n }\n\n .Dropdown-menu {\n position: absolute;\n margin-top: 2px;\n left: 0;\n right: 0;\n\n z-index: 10;\n min-width: 100px;\n\n background: white;\n border: 1px solid rgba(38, 50, 56, 0.2);\n box-shadow: 0px 2px 4px 0px rgba(34, 36, 38, 0.12), 0px 2px 10px 0px rgba(34, 36, 38, 0.08);\n\n max-height: 220px;\n overflow: auto;\n }\n\n .Dropdown-option {\n font-size: 0.9em;\n color: #263238;\n cursor: pointer;\n padding: 0.4em;\n\n &.is-selected {\n background-color: rgba(0, 0, 0, 0.05);\n }\n\n &:hover {\n background-color: rgba(38, 50, 56, 0.12);\n }\n }\n"])),(function(e){return e.theme.typography.headings.fontFamily}),(function(e){return e.theme.typography.headings.fontFamily}),(function(e){return e.theme.colors.primary.main}),(function(e){return e.theme.colors.primary.main}),(function(e){return e.theme.colors.primary.main})),Or=_e(kr)(nr||(nr=Object(o.__makeTemplateObject)(["\n margin-left: 10px;\n text-transform: none;\n font-size: 0.969em;\n\n .Dropdown-control {\n font-size: 1em;\n border: none;\n padding: 0 1.2em 0 0;\n background: transparent;\n\n &:hover {\n color: ",";\n box-shadow: none;\n }\n }\n"],["\n margin-left: 10px;\n text-transform: none;\n font-size: 0.969em;\n\n .Dropdown-control {\n font-size: 1em;\n border: none;\n padding: 0 1.2em 0 0;\n background: transparent;\n\n &:hover {\n color: ",";\n box-shadow: none;\n }\n }\n"])),(function(e){return e.theme.colors.primary.main})),_r=_e.span(rr||(rr=Object(o.__makeTemplateObject)(["\n margin-left: 10px;\n text-transform: none;\n font-size: 0.929em;\n color: black;\n"],["\n margin-left: 10px;\n text-transform: none;\n font-size: 0.929em;\n color: black;\n"])));function Er(e){return function(t){return!!t.type&&t.type.tabsRole===e}}var Sr=Er("Tab"),Tr=Er("TabList"),jr=Er("TabPanel");function Cr(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function Ar(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function Ir(e,t){return i.Children.map(e,(function(e){return null===e?null:function(e){return Sr(e)||Tr(e)||jr(e)}(e)?t(e):e.props&&e.props.children&&"object"==typeof e.props.children?Object(i.cloneElement)(e,function(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?Cr(Object(n),!0).forEach((function(t){Ar(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):Cr(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}({},e.props,{children:Ir(e.props.children,t)})):e}))}function Pr(e,t){return i.Children.forEach(e,(function(e){null!==e&&(Sr(e)||jr(e)?t(e):e.props&&e.props.children&&"object"==typeof e.props.children&&(Tr(e)&&t(e),Pr(e.props.children,t)))}))}var Rr,Nr=n(20),Lr=n.n(Nr),Mr=0;function Dr(){return"react-tabs-"+Mr++}function Fr(e){var t=0;return Pr(e,(function(e){Sr(e)&&t++})),t}function zr(){return(zr=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function Ur(e){return e&&"getAttribute"in e}function Br(e){return Ur(e)&&"tab"===e.getAttribute("role")}function $r(e){return Ur(e)&&"true"===e.getAttribute("aria-disabled")}try{Rr=!("undefined"==typeof window||!window.document||!window.document.activeElement)}catch(e){Rr=!1}var qr=function(e){var t,n;function r(){for(var t,n=arguments.length,r=new Array(n),o=0;o<n;o++)r[o]=arguments[o];return(t=e.call.apply(e,[this].concat(r))||this).tabNodes=[],t.handleKeyDown=function(e){var n=t.props.direction;if(t.isTabFromContainer(e.target)){var r=t.props.selectedIndex,o=!1,i=!1;32!==e.keyCode&&13!==e.keyCode||(o=!0,i=!1,t.handleClick(e)),37===e.keyCode||38===e.keyCode?(r="rtl"===n?t.getNextTab(r):t.getPrevTab(r),o=!0,i=!0):39===e.keyCode||40===e.keyCode?(r="rtl"===n?t.getPrevTab(r):t.getNextTab(r),o=!0,i=!0):35===e.keyCode?(r=t.getLastTab(),o=!0,i=!0):36===e.keyCode&&(r=t.getFirstTab(),o=!0,i=!0),o&&e.preventDefault(),i&&t.setSelected(r,e)}},t.handleClick=function(e){var n=e.target;do{if(t.isTabFromContainer(n)){if($r(n))return;var r=[].slice.call(n.parentNode.children).filter(Br).indexOf(n);return void t.setSelected(r,e)}}while(null!=(n=n.parentNode))},t}n=e,(t=r).prototype=Object.create(n.prototype),t.prototype.constructor=t,t.__proto__=n;var o=r.prototype;return o.setSelected=function(e,t){if(!(e<0||e>=this.getTabsCount())){var n=this.props;(0,n.onSelect)(e,n.selectedIndex,t)}},o.getNextTab=function(e){for(var t=this.getTabsCount(),n=e+1;n<t;n++)if(!$r(this.getTab(n)))return n;for(var r=0;r<e;r++)if(!$r(this.getTab(r)))return r;return e},o.getPrevTab=function(e){for(var t=e;t--;)if(!$r(this.getTab(t)))return t;for(t=this.getTabsCount();t-- >e;)if(!$r(this.getTab(t)))return t;return e},o.getFirstTab=function(){for(var e=this.getTabsCount(),t=0;t<e;t++)if(!$r(this.getTab(t)))return t;return null},o.getLastTab=function(){for(var e=this.getTabsCount();e--;)if(!$r(this.getTab(e)))return e;return null},o.getTabsCount=function(){return Fr(this.props.children)},o.getPanelsCount=function(){return function(e){var t=0;return Pr(e,(function(e){jr(e)&&t++})),t}(this.props.children)},o.getTab=function(e){return this.tabNodes["tabs-"+e]},o.getChildren=function(){var e=this,t=0,n=this.props,r=n.children,o=n.disabledTabClassName,s=n.focus,l=n.forceRenderTabPanel,c=n.selectedIndex,u=n.selectedTabClassName,p=n.selectedTabPanelClassName;this.tabIds=this.tabIds||[],this.panelIds=this.panelIds||[];for(var f=this.tabIds.length-this.getTabsCount();f++<0;)this.tabIds.push(Dr()),this.panelIds.push(Dr());return Ir(r,(function(n){var r=n;if(Tr(n)){var f=0,d=!1;Rr&&(d=a.a.Children.toArray(n.props.children).filter(Sr).some((function(t,n){return document.activeElement===e.getTab(n)}))),r=Object(i.cloneElement)(n,{children:Ir(n.props.children,(function(t){var n="tabs-"+f,r=c===f,a={tabRef:function(t){e.tabNodes[n]=t},id:e.tabIds[f],panelId:e.panelIds[f],selected:r,focus:r&&(s||d)};return u&&(a.selectedClassName=u),o&&(a.disabledClassName=o),f++,Object(i.cloneElement)(t,a)}))})}else if(jr(n)){var h={id:e.panelIds[t],tabId:e.tabIds[t],selected:c===t};l&&(h.forceRender=l),p&&(h.selectedClassName=p),t++,r=Object(i.cloneElement)(n,h)}return r}))},o.isTabFromContainer=function(e){if(!Br(e))return!1;var t=e.parentElement;do{if(t===this.node)return!0;if(t.getAttribute("data-tabs"))break;t=t.parentElement}while(t);return!1},o.render=function(){var e=this,t=this.props,n=(t.children,t.className),r=(t.disabledTabClassName,t.domRef),o=(t.focus,t.forceRenderTabPanel,t.onSelect,t.selectedIndex,t.selectedTabClassName,t.selectedTabPanelClassName,function(e,t){if(null==e)return{};var n,r,o={},i=Object.keys(e);for(r=0;r<i.length;r++)n=i[r],t.indexOf(n)>=0||(o[n]=e[n]);return o}(t,["children","className","disabledTabClassName","domRef","focus","forceRenderTabPanel","onSelect","selectedIndex","selectedTabClassName","selectedTabPanelClassName"]));return a.a.createElement("div",zr({},o,{className:Lr()(n),onClick:this.handleClick,onKeyDown:this.handleKeyDown,ref:function(t){e.node=t,r&&r(t)},"data-tabs":!0}),this.getChildren())},r}(i.Component);qr.defaultProps={className:"react-tabs",focus:!1},qr.propTypes={};var Wr=function(e){var t,n;function r(t){var n;return(n=e.call(this,t)||this).handleSelected=function(e,t,r){var o=n.props.onSelect,i=n.state.mode;if("function"!=typeof o||!1!==o(e,t,r)){var a={focus:"keydown"===r.type};1===i&&(a.selectedIndex=e),n.setState(a)}},n.state=r.copyPropsToState(n.props,{},t.defaultFocus),n}return n=e,(t=r).prototype=Object.create(n.prototype),t.prototype.constructor=t,t.__proto__=n,r.getDerivedStateFromProps=function(e,t){return r.copyPropsToState(e,t)},r.getModeFromProps=function(e){return null===e.selectedIndex?1:0},r.copyPropsToState=function(e,t,n){void 0===n&&(n=!1);var o={focus:n,mode:r.getModeFromProps(e)};if(1===o.mode){var i=Fr(e.children)-1,a=null;a=null!=t.selectedIndex?Math.min(t.selectedIndex,i):e.defaultIndex||0,o.selectedIndex=a}return o},r.prototype.render=function(){var e=this.props,t=e.children,n=(e.defaultIndex,e.defaultFocus,function(e,t){if(null==e)return{};var n,r,o={},i=Object.keys(e);for(r=0;r<i.length;r++)n=i[r],t.indexOf(n)>=0||(o[n]=e[n]);return o}(e,["children","defaultIndex","defaultFocus"])),r=this.state,o=r.focus,i=r.selectedIndex;return n.focus=o,n.onSelect=this.handleSelected,null!=i&&(n.selectedIndex=i),a.a.createElement(qr,n,t)},r}(i.Component);function Hr(){return(Hr=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}Wr.defaultProps={defaultFocus:!1,forceRenderTabPanel:!1,selectedIndex:null,defaultIndex:null},Wr.propTypes={},Wr.tabsRole="Tabs";var Vr=function(e){var t,n;function r(){return e.apply(this,arguments)||this}return n=e,(t=r).prototype=Object.create(n.prototype),t.prototype.constructor=t,t.__proto__=n,r.prototype.render=function(){var e=this.props,t=e.children,n=e.className,r=function(e,t){if(null==e)return{};var n,r,o={},i=Object.keys(e);for(r=0;r<i.length;r++)n=i[r],t.indexOf(n)>=0||(o[n]=e[n]);return o}(e,["children","className"]);return a.a.createElement("ul",Hr({},r,{className:Lr()(n),role:"tablist"}),t)},r}(i.Component);function Yr(){return(Yr=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}Vr.defaultProps={className:"react-tabs__tab-list"},Vr.propTypes={},Vr.tabsRole="TabList";var Qr=function(e){var t,n;function r(){return e.apply(this,arguments)||this}n=e,(t=r).prototype=Object.create(n.prototype),t.prototype.constructor=t,t.__proto__=n;var o=r.prototype;return o.componentDidMount=function(){this.checkFocus()},o.componentDidUpdate=function(){this.checkFocus()},o.checkFocus=function(){var e=this.props,t=e.selected,n=e.focus;t&&n&&this.node.focus()},o.render=function(){var e,t=this,n=this.props,r=n.children,o=n.className,i=n.disabled,s=n.disabledClassName,l=(n.focus,n.id),c=n.panelId,u=n.selected,p=n.selectedClassName,f=n.tabIndex,d=n.tabRef,h=function(e,t){if(null==e)return{};var n,r,o={},i=Object.keys(e);for(r=0;r<i.length;r++)n=i[r],t.indexOf(n)>=0||(o[n]=e[n]);return o}(n,["children","className","disabled","disabledClassName","focus","id","panelId","selected","selectedClassName","tabIndex","tabRef"]);return a.a.createElement("li",Yr({},h,{className:Lr()(o,(e={},e[p]=u,e[s]=i,e)),ref:function(e){t.node=e,d&&d(e)},role:"tab",id:l,"aria-selected":u?"true":"false","aria-disabled":i?"true":"false","aria-controls":c,tabIndex:f||(u?"0":null)}),r)},r}(i.Component);function Gr(){return(Gr=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}Qr.defaultProps={className:"react-tabs__tab",disabledClassName:"react-tabs__tab--disabled",focus:!1,id:null,panelId:null,selected:!1,selectedClassName:"react-tabs__tab--selected"},Qr.propTypes={},Qr.tabsRole="Tab";var Xr=function(e){var t,n;function r(){return e.apply(this,arguments)||this}return n=e,(t=r).prototype=Object.create(n.prototype),t.prototype.constructor=t,t.__proto__=n,r.prototype.render=function(){var e,t=this.props,n=t.children,r=t.className,o=t.forceRender,i=t.id,s=t.selected,l=t.selectedClassName,c=t.tabId,u=function(e,t){if(null==e)return{};var n,r,o={},i=Object.keys(e);for(r=0;r<i.length;r++)n=i[r],t.indexOf(n)>=0||(o[n]=e[n]);return o}(t,["children","className","forceRender","id","selected","selectedClassName","tabId"]);return a.a.createElement("div",Gr({},u,{className:Lr()(r,(e={},e[l]=s,e)),role:"tabpanel",id:i,"aria-labelledby":c}),o||s?n:null)},r}(i.Component);Xr.defaultProps={className:"react-tabs__tab-panel",forceRender:!1,selectedClassName:"react-tabs__tab-panel--selected"},Xr.propTypes={},Xr.tabsRole="TabPanel";var Kr,Zr,Jr,eo,to,no,ro=_e(Wr)(Kr||(Kr=Object(o.__makeTemplateObject)(["\n > ul {\n list-style: none;\n padding: 0;\n margin: 0;\n margin: 0 -5px;\n\n > li {\n padding: 5px 10px;\n display: inline-block;\n\n background-color: ",";\n border-bottom: 1px solid rgba(0, 0, 0, 0.5);\n cursor: pointer;\n text-align: center;\n outline: none;\n color: ",";\n margin: 0\n ",";\n border: 1px solid ",";\n border-radius: 5px;\n min-width: 60px;\n font-size: 0.9em;\n font-weight: bold;\n\n &.react-tabs__tab--selected {\n color: ",";\n background: ",";\n }\n\n &:only-child {\n flex: none;\n min-width: 100px;\n }\n\n &.tab-success {\n color: ",";\n }\n\n &.tab-redirect {\n color: ",";\n }\n\n &.tab-info {\n color: ",";\n }\n\n &.tab-error {\n color: ",";\n }\n }\n }\n > .react-tabs__tab-panel {\n background: ",";\n & > div,\n & > pre {\n padding: ","px;\n margin: 0;\n }\n\n & > div > pre {\n padding: 0;\n }\n }\n"],["\n > ul {\n list-style: none;\n padding: 0;\n margin: 0;\n margin: 0 -5px;\n\n > li {\n padding: 5px 10px;\n display: inline-block;\n\n background-color: ",";\n border-bottom: 1px solid rgba(0, 0, 0, 0.5);\n cursor: pointer;\n text-align: center;\n outline: none;\n color: ",";\n margin: 0\n ",";\n border: 1px solid ",";\n border-radius: 5px;\n min-width: 60px;\n font-size: 0.9em;\n font-weight: bold;\n\n &.react-tabs__tab--selected {\n color: ",";\n background: ",";\n }\n\n &:only-child {\n flex: none;\n min-width: 100px;\n }\n\n &.tab-success {\n color: ",";\n }\n\n &.tab-redirect {\n color: ",";\n }\n\n &.tab-info {\n color: ",";\n }\n\n &.tab-error {\n color: ",";\n }\n }\n }\n > .react-tabs__tab-panel {\n background: ",";\n & > div,\n & > pre {\n padding: ","px;\n margin: 0;\n }\n\n & > div > pre {\n padding: 0;\n }\n }\n"])),(function(e){return e.theme.codeSample.backgroundColor}),(function(e){var t=e.theme;return q(t.colors.tonalOffset,t.rightPanel.textColor)}),(function(e){var t=e.theme;return t.spacing.unit+"px "+t.spacing.unit+"px "+t.spacing.unit+"px"}),(function(e){var t=e.theme;return q(.05,t.codeSample.backgroundColor)}),(function(e){return e.theme.colors.text.primary}),(function(e){return e.theme.rightPanel.textColor}),(function(e){return e.theme.colors.responses.success.color}),(function(e){return e.theme.colors.responses.redirect.color}),(function(e){return e.theme.colors.responses.info.color}),(function(e){return e.theme.colors.responses.error.color}),(function(e){return e.theme.codeSample.backgroundColor}),(function(e){return 4*e.theme.spacing.unit})),oo=(_e(ro)(Zr||(Zr=Object(o.__makeTemplateObject)(["\n > ul {\n display: block;\n > li {\n padding: 2px 5px;\n min-width: auto;\n margin: 0 15px 0 0;\n font-size: 13px;\n font-weight: normal;\n border-bottom: 1px dashed;\n color: ",";\n border-radius: 0;\n background: none;\n\n &:last-child {\n margin-right: 0;\n }\n\n &.react-tabs__tab--selected {\n color: ",";\n background: none;\n }\n }\n }\n > .react-tabs__tab-panel {\n & > div,\n & > pre {\n padding: ","px 0;\n }\n }\n"],["\n > ul {\n display: block;\n > li {\n padding: 2px 5px;\n min-width: auto;\n margin: 0 15px 0 0;\n font-size: 13px;\n font-weight: normal;\n border-bottom: 1px dashed;\n color: ",";\n border-radius: 0;\n background: none;\n\n &:last-child {\n margin-right: 0;\n }\n\n &.react-tabs__tab--selected {\n color: ",";\n background: none;\n }\n }\n }\n > .react-tabs__tab-panel {\n & > div,\n & > pre {\n padding: ","px 0;\n }\n }\n"])),(function(e){var t=e.theme;return q(t.colors.tonalOffset,t.rightPanel.textColor)}),(function(e){return e.theme.rightPanel.textColor}),(function(e){return 2*e.theme.spacing.unit})),_e.div(Jr||(Jr=Object(o.__makeTemplateObject)(["\n /**\n * Based on prism-dark.css\n */\n\n code[class*='language-'],\n pre[class*='language-'] {\n /* color: white;\n background: none; */\n text-shadow: 0 -0.1em 0.2em black;\n text-align: left;\n white-space: pre;\n word-spacing: normal;\n word-break: normal;\n word-wrap: normal;\n line-height: 1.5;\n\n -moz-tab-size: 4;\n -o-tab-size: 4;\n tab-size: 4;\n\n -webkit-hyphens: none;\n -moz-hyphens: none;\n -ms-hyphens: none;\n hyphens: none;\n }\n\n @media print {\n code[class*='language-'],\n pre[class*='language-'] {\n text-shadow: none;\n }\n }\n\n /* Code blocks */\n pre[class*='language-'] {\n padding: 1em;\n margin: 0.5em 0;\n overflow: auto;\n }\n\n .token.comment,\n .token.prolog,\n .token.doctype,\n .token.cdata {\n color: hsl(30, 20%, 50%);\n }\n\n .token.punctuation {\n opacity: 0.7;\n }\n\n .namespace {\n opacity: 0.7;\n }\n\n .token.property,\n .token.tag,\n .token.number,\n .token.constant,\n .token.symbol {\n color: #4a8bb3;\n }\n\n .token.boolean {\n color: firebrick;\n }\n\n .token.selector,\n .token.attr-name,\n .token.string,\n .token.char,\n .token.builtin,\n .token.inserted {\n color: #a0fbaa;\n & + a,\n & + a:visited {\n color: #4ed2ba;\n text-decoration: underline;\n }\n }\n\n /* .property.token.string {\n color: white;\n } */\n\n .token.operator,\n .token.entity,\n .token.url,\n .token.variable {\n color: hsl(40, 90%, 60%);\n }\n\n .token.atrule,\n .token.attr-value,\n .token.keyword {\n color: hsl(350, 40%, 70%);\n }\n\n .token.regex,\n .token.important {\n color: #e90;\n }\n\n .token.important,\n .token.bold {\n font-weight: bold;\n }\n .token.italic {\n font-style: italic;\n }\n\n .token.entity {\n cursor: help;\n }\n\n .token.deleted {\n color: red;\n }\n\n ",";\n"],["\n /**\n * Based on prism-dark.css\n */\n\n code[class*='language-'],\n pre[class*='language-'] {\n /* color: white;\n background: none; */\n text-shadow: 0 -0.1em 0.2em black;\n text-align: left;\n white-space: pre;\n word-spacing: normal;\n word-break: normal;\n word-wrap: normal;\n line-height: 1.5;\n\n -moz-tab-size: 4;\n -o-tab-size: 4;\n tab-size: 4;\n\n -webkit-hyphens: none;\n -moz-hyphens: none;\n -ms-hyphens: none;\n hyphens: none;\n }\n\n @media print {\n code[class*='language-'],\n pre[class*='language-'] {\n text-shadow: none;\n }\n }\n\n /* Code blocks */\n pre[class*='language-'] {\n padding: 1em;\n margin: 0.5em 0;\n overflow: auto;\n }\n\n .token.comment,\n .token.prolog,\n .token.doctype,\n .token.cdata {\n color: hsl(30, 20%, 50%);\n }\n\n .token.punctuation {\n opacity: 0.7;\n }\n\n .namespace {\n opacity: 0.7;\n }\n\n .token.property,\n .token.tag,\n .token.number,\n .token.constant,\n .token.symbol {\n color: #4a8bb3;\n }\n\n .token.boolean {\n color: firebrick;\n }\n\n .token.selector,\n .token.attr-name,\n .token.string,\n .token.char,\n .token.builtin,\n .token.inserted {\n color: #a0fbaa;\n & + a,\n & + a:visited {\n color: #4ed2ba;\n text-decoration: underline;\n }\n }\n\n /* .property.token.string {\n color: white;\n } */\n\n .token.operator,\n .token.entity,\n .token.url,\n .token.variable {\n color: hsl(40, 90%, 60%);\n }\n\n .token.atrule,\n .token.attr-value,\n .token.keyword {\n color: hsl(350, 40%, 70%);\n }\n\n .token.regex,\n .token.important {\n color: #e90;\n }\n\n .token.important,\n .token.bold {\n font-weight: bold;\n }\n .token.italic {\n font-style: italic;\n }\n\n .token.entity {\n cursor: help;\n }\n\n .token.deleted {\n color: red;\n }\n\n ",";\n"])),Ee("Prism"))),io=_e.div(eo||(eo=Object(o.__makeTemplateObject)(["\n opacity: 0.4;\n transition: opacity 0.3s ease;\n text-align: right;\n\n > span {\n display: inline-block;\n padding: 2px 10px;\n cursor: pointer;\n\n :hover {\n background: rgba(255, 255, 255, 0.1);\n }\n }\n"],["\n opacity: 0.4;\n transition: opacity 0.3s ease;\n text-align: right;\n\n > span {\n display: inline-block;\n padding: 2px 10px;\n cursor: pointer;\n\n :hover {\n background: rgba(255, 255, 255, 0.1);\n }\n }\n"]))),ao=_e.div(to||(to=Object(o.__makeTemplateObject)(["\n &:hover "," {\n opacity: 1;\n }\n"],["\n &:hover "," {\n opacity: 1;\n }\n"])),io),so=_e(oo.withComponent("pre"))(no||(no=Object(o.__makeTemplateObject)(["\n font-family: ",";\n font-size: ",";\n overflow-x: auto;\n margin: 0;\n\n white-space: ",";\n"],["\n font-family: ",";\n font-size: ",";\n overflow-x: auto;\n margin: 0;\n\n white-space: ",";\n"])),(function(e){return e.theme.typography.code.fontFamily}),(function(e){return e.theme.typography.code.fontSize}),(function(e){return e.theme.typography.code.wrap?"pre-wrap":"pre"})); +e.exports=function(e,t){e||(e=document),t||(t=window);var n,r,o=[],i=!1,a=e.documentElement,s=function(){},l="hidden",c="visibilitychange";void 0!==e.webkitHidden&&(l="webkitHidden",c="webkitvisibilitychange"),t.getComputedStyle||d();for(var u=["","-webkit-","-moz-","-ms-"],p=document.createElement("div"),f=u.length-1;f>=0;f--){try{p.style.position=u[f]+"sticky"}catch(e){}""!=p.style.position&&d()}function d(){I=L=A=P=R=N=s}function h(e){return parseFloat(e)||0}function m(){n={top:t.pageYOffset,left:t.pageXOffset}}function g(){if(t.pageXOffset!=n.left)return m(),void A();t.pageYOffset!=n.top&&(m(),v())}function y(e){setTimeout((function(){t.pageYOffset!=n.top&&(n.top=t.pageYOffset,v())}),0)}function v(){for(var e=o.length-1;e>=0;e--)b(o[e])}function b(e){if(e.inited){var t=n.top<=e.limit.start?0:n.top>=e.limit.end?2:1;e.mode!=t&&function(e,t){var n=e.node.style;switch(t){case 0:n.position="absolute",n.left=e.offset.left+"px",n.right=e.offset.right+"px",n.top=e.offset.top+"px",n.bottom="auto",n.width="auto",n.marginLeft=0,n.marginRight=0,n.marginTop=0;break;case 1:n.position="fixed",n.left=e.box.left+"px",n.right=e.box.right+"px",n.top=e.css.top,n.bottom="auto",n.width="auto",n.marginLeft=0,n.marginRight=0,n.marginTop=0;break;case 2:n.position="absolute",n.left=e.offset.left+"px",n.right=e.offset.right+"px",n.top="auto",n.bottom=0,n.width="auto",n.marginLeft=0,n.marginRight=0}e.mode=t}(e,t)}}function x(e){isNaN(parseFloat(e.computed.top))||e.isCell||(e.inited=!0,e.clone||function(e){e.clone=document.createElement("div");var t=e.node.nextSibling||e.node,n=e.clone.style;n.height=e.height+"px",n.width=e.width+"px",n.marginTop=e.computed.marginTop,n.marginBottom=e.computed.marginBottom,n.marginLeft=e.computed.marginLeft,n.marginRight=e.computed.marginRight,n.padding=n.border=n.borderSpacing=0,n.fontSize="1em",n.position="static",n.cssFloat=e.computed.cssFloat,e.node.parentNode.insertBefore(e.clone,t)}(e),"absolute"!=e.parent.computed.position&&"relative"!=e.parent.computed.position&&(e.parent.node.style.position="relative"),b(e),e.parent.height=e.parent.node.offsetHeight,e.docOffsetTop=E(e.clone))}function w(e){var t=!0;e.clone&&function(e){e.clone.parentNode.removeChild(e.clone),e.clone=void 0}(e),function(e,t){for(key in t)t.hasOwnProperty(key)&&(e[key]=t[key])}(e.node.style,e.css);for(var n=o.length-1;n>=0;n--)if(o[n].node!==e.node&&o[n].parent.node===e.parent.node){t=!1;break}t&&(e.parent.node.style.position=e.parent.css.position),e.mode=-1}function k(){for(var e=o.length-1;e>=0;e--)x(o[e])}function O(){for(var e=o.length-1;e>=0;e--)w(o[e])}function _(e){var t=getComputedStyle(e),n=e.parentNode,r=getComputedStyle(n),o=e.style.position;e.style.position="relative";var i={top:t.top,marginTop:t.marginTop,marginBottom:t.marginBottom,marginLeft:t.marginLeft,marginRight:t.marginRight,cssFloat:t.cssFloat},s={top:h(t.top),marginBottom:h(t.marginBottom),paddingLeft:h(t.paddingLeft),paddingRight:h(t.paddingRight),borderLeftWidth:h(t.borderLeftWidth),borderRightWidth:h(t.borderRightWidth)};e.style.position=o;var l={position:e.style.position,top:e.style.top,bottom:e.style.bottom,left:e.style.left,right:e.style.right,width:e.style.width,marginTop:e.style.marginTop,marginLeft:e.style.marginLeft,marginRight:e.style.marginRight},c=S(e),u=S(n),p={node:n,css:{position:n.style.position},computed:{position:r.position},numeric:{borderLeftWidth:h(r.borderLeftWidth),borderRightWidth:h(r.borderRightWidth),borderTopWidth:h(r.borderTopWidth),borderBottomWidth:h(r.borderBottomWidth)}};return{node:e,box:{left:c.win.left,right:a.clientWidth-c.win.right},offset:{top:c.win.top-u.win.top-p.numeric.borderTopWidth,left:c.win.left-u.win.left-p.numeric.borderLeftWidth,right:-c.win.right+u.win.right-p.numeric.borderRightWidth},css:l,isCell:"table-cell"==t.display,computed:i,numeric:s,width:c.win.right-c.win.left,height:c.win.bottom-c.win.top,mode:-1,inited:!1,parent:p,limit:{start:c.doc.top-s.top,end:u.doc.top+n.offsetHeight-p.numeric.borderBottomWidth-e.offsetHeight-s.top-s.marginBottom}}}function E(e){for(var t=0;e;)t+=e.offsetTop,e=e.offsetParent;return t}function S(e){var n=e.getBoundingClientRect();return{doc:{top:n.top+t.pageYOffset,left:n.left+t.pageXOffset},win:n}}function T(){r=setInterval((function(){!function(){for(var e=o.length-1;e>=0;e--)if(o[e].inited){var t=Math.abs(E(o[e].clone)-o[e].docOffsetTop),n=Math.abs(o[e].parent.node.offsetHeight-o[e].parent.height);if(t>=2||n>=2)return!1}return!0}()&&A()}),500)}function j(){clearInterval(r)}function C(){i&&(document[l]?j():T())}function I(){i||(m(),k(),t.addEventListener("scroll",g),t.addEventListener("wheel",y),t.addEventListener("resize",A),t.addEventListener("orientationchange",A),e.addEventListener(c,C),T(),i=!0)}function A(){if(i){O();for(var e=o.length-1;e>=0;e--)o[e]=_(o[e].node);k()}}function P(){t.removeEventListener("scroll",g),t.removeEventListener("wheel",y),t.removeEventListener("resize",A),t.removeEventListener("orientationchange",A),e.removeEventListener(c,C),j(),i=!1}function R(){P(),O()}function N(){for(R();o.length;)o.pop()}function L(e){for(var t=o.length-1;t>=0;t--)if(o[t].node===e)return;var n=_(e);o.push(n),i?x(n):I()}return m(),{stickies:o,add:L,remove:function(e){for(var t=o.length-1;t>=0;t--)o[t].node===e&&(w(o[t]),o.splice(t,1))},init:I,rebuild:A,pause:P,stop:R,kill:N}}},function(e,t,n){"use strict";n.r(t),n.d(t,"Redoc",(function(){return Yc})),n.d(t,"AppStore",(function(){return ts})),n.d(t,"version",(function(){return Gc})),n.d(t,"revision",(function(){return Xc})),n.d(t,"init",(function(){return Zc})),n.d(t,"hydrate",(function(){return Jc}));var r={};n.r(r),n.d(r,"default",(function(){return Bo}));var o=n(1),i=n(0),a=n.n(i),s=n(33),l=n(19);function c(){return(c=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function u(e){return(u=Object.setPrototypeOf?Object.getPrototypeOf:function(e){return e.__proto__||Object.getPrototypeOf(e)})(e)}function p(e,t){return(p=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e})(e,t)}function f(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],(function(){}))),!0}catch(e){return!1}}function d(e,t,n){return(d=f()?Reflect.construct:function(e,t,n){var r=[null];r.push.apply(r,t);var o=new(Function.bind.apply(e,r));return n&&p(o,n.prototype),o}).apply(null,arguments)}function h(e){var t="function"==typeof Map?new Map:void 0;return(h=function(e){if(null===e||(n=e,-1===Function.toString.call(n).indexOf("[native code]")))return e;var n;if("function"!=typeof e)throw new TypeError("Super expression must either be null or a function");if(void 0!==t){if(t.has(e))return t.get(e);t.set(e,r)}function r(){return d(e,arguments,u(this).constructor)}return r.prototype=Object.create(e.prototype,{constructor:{value:r,enumerable:!1,writable:!0,configurable:!0}}),p(r,e)})(e)}var m=function(e){var t,n;function r(t){return function(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}(e.call(this,"An error occurred. See https://github.com/styled-components/polished/blob/master/src/internalHelpers/errors.md#"+t+" for more information.")||this)}return n=e,(t=r).prototype=Object.create(n.prototype),t.prototype.constructor=t,t.__proto__=n,r}(h(Error));function g(e){return Math.round(255*e)}function y(e,t,n){return g(e)+","+g(t)+","+g(n)}function v(e,t,n,r){if(void 0===r&&(r=y),0===t)return r(n,n,n);var o=(e%360+360)%360/60,i=(1-Math.abs(2*n-1))*t,a=i*(1-Math.abs(o%2-1)),s=0,l=0,c=0;o>=0&&o<1?(s=i,l=a):o>=1&&o<2?(s=a,l=i):o>=2&&o<3?(l=i,c=a):o>=3&&o<4?(l=a,c=i):o>=4&&o<5?(s=a,c=i):o>=5&&o<6&&(s=i,c=a);var u=n-i/2;return r(s+u,l+u,c+u)}var b={aliceblue:"f0f8ff",antiquewhite:"faebd7",aqua:"00ffff",aquamarine:"7fffd4",azure:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"000",blanchedalmond:"ffebcd",blue:"0000ff",blueviolet:"8a2be2",brown:"a52a2a",burlywood:"deb887",cadetblue:"5f9ea0",chartreuse:"7fff00",chocolate:"d2691e",coral:"ff7f50",cornflowerblue:"6495ed",cornsilk:"fff8dc",crimson:"dc143c",cyan:"00ffff",darkblue:"00008b",darkcyan:"008b8b",darkgoldenrod:"b8860b",darkgray:"a9a9a9",darkgreen:"006400",darkgrey:"a9a9a9",darkkhaki:"bdb76b",darkmagenta:"8b008b",darkolivegreen:"556b2f",darkorange:"ff8c00",darkorchid:"9932cc",darkred:"8b0000",darksalmon:"e9967a",darkseagreen:"8fbc8f",darkslateblue:"483d8b",darkslategray:"2f4f4f",darkslategrey:"2f4f4f",darkturquoise:"00ced1",darkviolet:"9400d3",deeppink:"ff1493",deepskyblue:"00bfff",dimgray:"696969",dimgrey:"696969",dodgerblue:"1e90ff",firebrick:"b22222",floralwhite:"fffaf0",forestgreen:"228b22",fuchsia:"ff00ff",gainsboro:"dcdcdc",ghostwhite:"f8f8ff",gold:"ffd700",goldenrod:"daa520",gray:"808080",green:"008000",greenyellow:"adff2f",grey:"808080",honeydew:"f0fff0",hotpink:"ff69b4",indianred:"cd5c5c",indigo:"4b0082",ivory:"fffff0",khaki:"f0e68c",lavender:"e6e6fa",lavenderblush:"fff0f5",lawngreen:"7cfc00",lemonchiffon:"fffacd",lightblue:"add8e6",lightcoral:"f08080",lightcyan:"e0ffff",lightgoldenrodyellow:"fafad2",lightgray:"d3d3d3",lightgreen:"90ee90",lightgrey:"d3d3d3",lightpink:"ffb6c1",lightsalmon:"ffa07a",lightseagreen:"20b2aa",lightskyblue:"87cefa",lightslategray:"789",lightslategrey:"789",lightsteelblue:"b0c4de",lightyellow:"ffffe0",lime:"0f0",limegreen:"32cd32",linen:"faf0e6",magenta:"f0f",maroon:"800000",mediumaquamarine:"66cdaa",mediumblue:"0000cd",mediumorchid:"ba55d3",mediumpurple:"9370db",mediumseagreen:"3cb371",mediumslateblue:"7b68ee",mediumspringgreen:"00fa9a",mediumturquoise:"48d1cc",mediumvioletred:"c71585",midnightblue:"191970",mintcream:"f5fffa",mistyrose:"ffe4e1",moccasin:"ffe4b5",navajowhite:"ffdead",navy:"000080",oldlace:"fdf5e6",olive:"808000",olivedrab:"6b8e23",orange:"ffa500",orangered:"ff4500",orchid:"da70d6",palegoldenrod:"eee8aa",palegreen:"98fb98",paleturquoise:"afeeee",palevioletred:"db7093",papayawhip:"ffefd5",peachpuff:"ffdab9",peru:"cd853f",pink:"ffc0cb",plum:"dda0dd",powderblue:"b0e0e6",purple:"800080",rebeccapurple:"639",red:"f00",rosybrown:"bc8f8f",royalblue:"4169e1",saddlebrown:"8b4513",salmon:"fa8072",sandybrown:"f4a460",seagreen:"2e8b57",seashell:"fff5ee",sienna:"a0522d",silver:"c0c0c0",skyblue:"87ceeb",slateblue:"6a5acd",slategray:"708090",slategrey:"708090",snow:"fffafa",springgreen:"00ff7f",steelblue:"4682b4",tan:"d2b48c",teal:"008080",thistle:"d8bfd8",tomato:"ff6347",turquoise:"40e0d0",violet:"ee82ee",wheat:"f5deb3",white:"fff",whitesmoke:"f5f5f5",yellow:"ff0",yellowgreen:"9acd32"};var x=/^#[a-fA-F0-9]{6}$/,w=/^#[a-fA-F0-9]{8}$/,k=/^#[a-fA-F0-9]{3}$/,O=/^#[a-fA-F0-9]{4}$/,_=/^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/i,E=/^rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*([-+]?[0-9]*[.]?[0-9]+)\s*\)$/i,S=/^hsl\(\s*(\d{0,3}[.]?[0-9]+)\s*,\s*(\d{1,3}[.]?[0-9]?)%\s*,\s*(\d{1,3}[.]?[0-9]?)%\s*\)$/i,T=/^hsla\(\s*(\d{0,3}[.]?[0-9]+)\s*,\s*(\d{1,3}[.]?[0-9]?)%\s*,\s*(\d{1,3}[.]?[0-9]?)%\s*,\s*([-+]?[0-9]*[.]?[0-9]+)\s*\)$/i;function j(e){if("string"!=typeof e)throw new m(3);var t=function(e){if("string"!=typeof e)return e;var t=e.toLowerCase();return b[t]?"#"+b[t]:e}(e);if(t.match(x))return{red:parseInt(""+t[1]+t[2],16),green:parseInt(""+t[3]+t[4],16),blue:parseInt(""+t[5]+t[6],16)};if(t.match(w)){var n=parseFloat((parseInt(""+t[7]+t[8],16)/255).toFixed(2));return{red:parseInt(""+t[1]+t[2],16),green:parseInt(""+t[3]+t[4],16),blue:parseInt(""+t[5]+t[6],16),alpha:n}}if(t.match(k))return{red:parseInt(""+t[1]+t[1],16),green:parseInt(""+t[2]+t[2],16),blue:parseInt(""+t[3]+t[3],16)};if(t.match(O)){var r=parseFloat((parseInt(""+t[4]+t[4],16)/255).toFixed(2));return{red:parseInt(""+t[1]+t[1],16),green:parseInt(""+t[2]+t[2],16),blue:parseInt(""+t[3]+t[3],16),alpha:r}}var o=_.exec(t);if(o)return{red:parseInt(""+o[1],10),green:parseInt(""+o[2],10),blue:parseInt(""+o[3],10)};var i=E.exec(t);if(i)return{red:parseInt(""+i[1],10),green:parseInt(""+i[2],10),blue:parseInt(""+i[3],10),alpha:parseFloat(""+i[4])};var a=S.exec(t);if(a){var s="rgb("+v(parseInt(""+a[1],10),parseInt(""+a[2],10)/100,parseInt(""+a[3],10)/100)+")",l=_.exec(s);if(!l)throw new m(4,t,s);return{red:parseInt(""+l[1],10),green:parseInt(""+l[2],10),blue:parseInt(""+l[3],10)}}var c=T.exec(t);if(c){var u="rgb("+v(parseInt(""+c[1],10),parseInt(""+c[2],10)/100,parseInt(""+c[3],10)/100)+")",p=_.exec(u);if(!p)throw new m(4,t,u);return{red:parseInt(""+p[1],10),green:parseInt(""+p[2],10),blue:parseInt(""+p[3],10),alpha:parseFloat(""+c[4])}}throw new m(5)}function C(e){return function(e){var t,n=e.red/255,r=e.green/255,o=e.blue/255,i=Math.max(n,r,o),a=Math.min(n,r,o),s=(i+a)/2;if(i===a)return void 0!==e.alpha?{hue:0,saturation:0,lightness:s,alpha:e.alpha}:{hue:0,saturation:0,lightness:s};var l=i-a,c=s>.5?l/(2-i-a):l/(i+a);switch(i){case n:t=(r-o)/l+(r<o?6:0);break;case r:t=(o-n)/l+2;break;default:t=(n-r)/l+4}return t*=60,void 0!==e.alpha?{hue:t,saturation:c,lightness:s,alpha:e.alpha}:{hue:t,saturation:c,lightness:s}}(j(e))}var I=function(e){return 7===e.length&&e[1]===e[2]&&e[3]===e[4]&&e[5]===e[6]?"#"+e[1]+e[3]+e[5]:e};function A(e){var t=e.toString(16);return 1===t.length?"0"+t:t}function P(e){return A(Math.round(255*e))}function R(e,t,n){return I("#"+P(e)+P(t)+P(n))}function N(e,t,n){return v(e,t,n,R)}function L(e,t,n){if("number"==typeof e&&"number"==typeof t&&"number"==typeof n)return N(e,t,n);if("object"==typeof e&&void 0===t&&void 0===n)return N(e.hue,e.saturation,e.lightness);throw new m(1)}function M(e,t,n,r){if("number"==typeof e&&"number"==typeof t&&"number"==typeof n&&"number"==typeof r)return r>=1?N(e,t,n):"rgba("+v(e,t,n)+","+r+")";if("object"==typeof e&&void 0===t&&void 0===n&&void 0===r)return e.alpha>=1?N(e.hue,e.saturation,e.lightness):"rgba("+v(e.hue,e.saturation,e.lightness)+","+e.alpha+")";throw new m(2)}function D(e,t,n){if("number"==typeof e&&"number"==typeof t&&"number"==typeof n)return I("#"+A(e)+A(t)+A(n));if("object"==typeof e&&void 0===t&&void 0===n)return I("#"+A(e.red)+A(e.green)+A(e.blue));throw new m(6)}function F(e,t,n,r){if("string"==typeof e&&"number"==typeof t){var o=j(e);return"rgba("+o.red+","+o.green+","+o.blue+","+t+")"}if("number"==typeof e&&"number"==typeof t&&"number"==typeof n&&"number"==typeof r)return r>=1?D(e,t,n):"rgba("+e+","+t+","+n+","+r+")";if("object"==typeof e&&void 0===t&&void 0===n&&void 0===r)return e.alpha>=1?D(e.red,e.green,e.blue):"rgba("+e.red+","+e.green+","+e.blue+","+e.alpha+")";throw new m(7)}function z(e){if("object"!=typeof e)throw new m(8);if(function(e){return"number"==typeof e.red&&"number"==typeof e.green&&"number"==typeof e.blue&&"number"==typeof e.alpha}(e))return F(e);if(function(e){return"number"==typeof e.red&&"number"==typeof e.green&&"number"==typeof e.blue&&("number"!=typeof e.alpha||void 0===e.alpha)}(e))return D(e);if(function(e){return"number"==typeof e.hue&&"number"==typeof e.saturation&&"number"==typeof e.lightness&&"number"==typeof e.alpha}(e))return M(e);if(function(e){return"number"==typeof e.hue&&"number"==typeof e.saturation&&"number"==typeof e.lightness&&("number"!=typeof e.alpha||void 0===e.alpha)}(e))return L(e);throw new m(8)}function U(e){return function e(t,n,r){return function(){var o=r.concat(Array.prototype.slice.call(arguments));return o.length>=n?t.apply(this,o):e(t,n,o)}}(e,e.length,[])}function B(e,t,n){return Math.max(e,Math.min(t,n))}function $(e,t){if("transparent"===t)return t;var n=C(t);return z(c({},n,{lightness:B(0,1,n.lightness-parseFloat(e))}))}var q=U($);function W(e,t){if("transparent"===t)return t;var n=C(t);return z(c({},n,{saturation:B(0,1,n.saturation-parseFloat(e))}))}var H=U(W);function V(e){if("transparent"===e)return 0;var t=j(e),n=Object.keys(t).map((function(e){var n=t[e]/255;return n<=.03928?n/12.92:Math.pow((n+.055)/1.055,2.4)})),r=n[0],o=n[1],i=n[2];return parseFloat((.2126*r+.7152*o+.0722*i).toFixed(3))}function Y(e,t){if("transparent"===t)return t;var n=C(t);return z(c({},n,{lightness:B(0,1,n.lightness+parseFloat(e))}))}var Q=U(Y);function G(e,t,n){return void 0===t&&(t="#000"),void 0===n&&(n="#fff"),V(e)>.179?t:n}function X(e,t){if("transparent"===t)return t;var n=j(t);return F(c({},n,{alpha:B(0,1,(100*("number"==typeof n.alpha?n.alpha:1)-100*parseFloat(e))/100)}))}var K=U(X);var Z={spacing:{unit:5,sectionHorizontal:function(e){return 8*e.spacing.unit},sectionVertical:function(e){return 8*e.spacing.unit}},breakpoints:{small:"50rem",medium:"85rem",large:"105rem"},colors:{tonalOffset:.3,primary:{main:"#32329f",light:function(e){var t=e.colors;return Q(t.tonalOffset,t.primary.main)},dark:function(e){var t=e.colors;return q(t.tonalOffset,t.primary.main)},contrastText:function(e){return G(e.colors.primary.main)}},success:{main:"#37d247",light:function(e){var t=e.colors;return Q(2*t.tonalOffset,t.success.main)},dark:function(e){var t=e.colors;return q(t.tonalOffset,t.success.main)},contrastText:function(e){return G(e.colors.success.main)}},warning:{main:"#ffa500",light:function(e){var t=e.colors;return Q(t.tonalOffset,t.warning.main)},dark:function(e){var t=e.colors;return q(t.tonalOffset,t.warning.main)},contrastText:"#ffffff"},error:{main:"#e53935",light:function(e){var t=e.colors;return Q(t.tonalOffset,t.error.main)},dark:function(e){var t=e.colors;return q(t.tonalOffset,t.error.main)},contrastText:function(e){return G(e.colors.error.main)}},gray:{50:"#FAFAFA",100:"#F5F5F5"},text:{primary:"#333333",secondary:function(e){var t=e.colors;return Q(t.tonalOffset,t.text.primary)}},border:{dark:"rgba(0,0,0, 0.1)",light:"#ffffff"},responses:{success:{color:function(e){return e.colors.success.main},backgroundColor:function(e){var t=e.colors;return K(.9,t.success.main)}},error:{color:function(e){return e.colors.error.main},backgroundColor:function(e){var t=e.colors;return K(.9,t.error.main)}},redirect:{color:function(e){return e.colors.warning.main},backgroundColor:function(e){var t=e.colors;return K(.9,t.responses.redirect.color)}},info:{color:"#87ceeb",backgroundColor:function(e){var t=e.colors;return K(.9,t.responses.info.color)}}},http:{get:"#6bbd5b",post:"#248fb2",put:"#9b708b",options:"#d3ca12",patch:"#e09d43",delete:"#e27a7a",basic:"#999",link:"#31bbb6",head:"#c167e4"}},schema:{linesColor:function(e){return Q(e.colors.tonalOffset,H(e.colors.tonalOffset,e.colors.primary.main))},defaultDetailsWidth:"75%",typeNameColor:function(e){return e.colors.text.secondary},typeTitleColor:function(e){return e.schema.typeNameColor},requireLabelColor:function(e){return e.colors.error.main},labelsTextSize:"0.9em",nestingSpacing:"1em",nestedBackground:"#fafafa",arrow:{size:"1.1em",color:function(e){return e.colors.text.secondary}}},typography:{fontSize:"14px",lineHeight:"1.5em",fontWeightRegular:"400",fontWeightBold:"600",fontWeightLight:"300",fontFamily:"Roboto, sans-serif",smoothing:"antialiased",optimizeSpeed:!0,headings:{fontFamily:"Montserrat, sans-serif",fontWeight:"400",lineHeight:"1.6em"},code:{fontSize:"13px",fontFamily:"Courier, monospace",lineHeight:function(e){return e.typography.lineHeight},fontWeight:function(e){return e.typography.fontWeightRegular},color:"#e53935",backgroundColor:"rgba(38, 50, 56, 0.05)",wrap:!1},links:{color:function(e){return e.colors.primary.main},visited:function(e){return e.typography.links.color},hover:function(e){var t=e.typography;return Q(.2,t.links.color)}}},sidebar:{width:"260px",backgroundColor:"#fafafa",textColor:"#333333",activeTextColor:function(e){return e.sidebar.textColor!==Z.sidebar.textColor?e.sidebar.textColor:e.colors.primary.main},groupItems:{textTransform:"uppercase"},level1Items:{textTransform:"none"},arrow:{size:"1.5em",color:function(e){return e.sidebar.textColor}}},logo:{maxHeight:function(e){return e.sidebar.width},maxWidth:function(e){return e.sidebar.width},gutter:"2px"},rightPanel:{backgroundColor:"#263238",width:"40%",textColor:"#ffffff"},codeBlock:{backgroundColor:function(e){var t=e.rightPanel;return q(.1,t.backgroundColor)}}},J=Z;var ee="undefined"!=typeof window&&"HTMLElement"in window;function te(e){return"undefined"!=typeof document?document.querySelector(e):null}"undefined"==typeof Element||Element.prototype.scrollIntoViewIfNeeded||(Element.prototype.scrollIntoViewIfNeeded=function(e){e=0===arguments.length||!!e;var t=this.parentNode,n=window.getComputedStyle(t,void 0),r=parseInt(n.getPropertyValue("border-top-width"),10),o=parseInt(n.getPropertyValue("border-left-width"),10),i=this.offsetTop-t.offsetTop<t.scrollTop,a=this.offsetTop-t.offsetTop+this.clientHeight-r>t.scrollTop+t.clientHeight,s=this.offsetLeft-t.offsetLeft<t.scrollLeft,l=this.offsetLeft-t.offsetLeft+this.clientWidth-o>t.scrollLeft+t.clientWidth,c=i&&!a;(i||a)&&e&&(t.scrollTop=this.offsetTop-t.offsetTop-t.clientHeight/2-r+this.clientHeight/2),(s||l)&&e&&(t.scrollLeft=this.offsetLeft-t.offsetLeft-t.clientWidth/2-o+this.clientWidth/2),(i||a||s||l)&&!e&&this.scrollIntoView(c)});var ne=n(151),re=n.n(ne),oe=n(10);function ie(e,t){for(var n=[],r=0;r<e.length-1;r++)n.push(t(e[r],!1));return 0!==e.length&&n.push(t(e[e.length-1],!0)),n}function ae(e){return e.endsWith("/")?e.substring(0,e.length-1):e}function se(e){return!isNaN(parseFloat(e))&&isFinite(e)}var le=function(e){for(var t=[],n=1;n<arguments.length;n++)t[n-1]=arguments[n];if(!t.length)return e;var r=t.shift();return void 0===r?e:(ce(e)&&ce(r)&&Object.keys(r).forEach((function(t){ce(r[t])?(e[t]||(e[t]={}),le(e[t],r[t])):e[t]=r[t]})),le.apply(void 0,Object(o.__spreadArrays)([e],t)))},ce=function(e){return function(e){return null!==e&&"object"==typeof e}(e)&&!Array.isArray(e)};function ue(e){return re()(e)||e.toString().toLowerCase().replace(/\s+/g,"-").replace(/&/g,"-and-").replace(/\--+/g,"-").replace(/^-+/,"").replace(/-+$/,"")}function pe(e){return"undefined"==typeof URL?new(n(10).URL)(e):new URL(e)}var fe={enum:"Enum",enumSingleValue:"Value",enumArray:"Items",default:"Default",deprecated:"Deprecated",example:"Example",nullable:"Nullable",recursive:"Recursive",arrayOf:"Array of "};function de(e,t){var n=fe[e];return void 0!==t?n[t]:n}function he(e,t){return void 0===e?t||!1:"string"==typeof e?"false"!==e:e}var me,ge=function(){function e(t,n){var r,i,a,s;void 0===n&&(n={});var l,c,u,p,f,d=(t=Object(o.__assign)(Object(o.__assign)({},n),t)).theme&&t.theme.extensionsHook;(null===(r=t.theme)||void 0===r?void 0:r.menu)&&!(null===(i=t.theme)||void 0===i?void 0:i.sidebar)&&(console.warn('Theme setting "menu" is deprecated. Rename to "sidebar"'),t.theme.sidebar=t.theme.menu),(null===(a=t.theme)||void 0===a?void 0:a.codeSample)&&!(null===(s=t.theme)||void 0===s?void 0:s.codeBlock)&&(console.warn('Theme setting "codeSample" is deprecated. Rename to "codeBlock"'),t.theme.codeBlock=t.theme.codeSample),this.theme=(l=le({},J,Object(o.__assign)(Object(o.__assign)({},t.theme),{extensionsHook:void 0})),c={},u=0,(p=function(e,t){Object.keys(e).forEach((function(n){var r=(t?t+".":"")+n,o=e[n];"function"==typeof o?Object.defineProperty(e,n,{get:function(){if(!c[r]){if(++u>1e3)throw new Error("Theme probably contains circular dependency at "+r+": "+o.toString());c[r]=o(l)}return c[r]},enumerable:!0}):"object"==typeof o&&p(o,r)}))})(l,""),JSON.parse(JSON.stringify(l))),this.theme.extensionsHook=d,f=t.labels,Object.assign(fe,f),this.scrollYOffset=e.normalizeScrollYOffset(t.scrollYOffset),this.hideHostname=e.normalizeHideHostname(t.hideHostname),this.expandResponses=e.normalizeExpandResponses(t.expandResponses),this.requiredPropsFirst=he(t.requiredPropsFirst),this.sortPropsAlphabetically=he(t.sortPropsAlphabetically),this.noAutoAuth=he(t.noAutoAuth),this.nativeScrollbars=he(t.nativeScrollbars),this.pathInMiddlePanel=he(t.pathInMiddlePanel),this.untrustedSpec=he(t.untrustedSpec),this.hideDownloadButton=he(t.hideDownloadButton),this.disableSearch=he(t.disableSearch),this.onlyRequiredInSamples=he(t.onlyRequiredInSamples),this.showExtensions=e.normalizeShowExtensions(t.showExtensions),this.hideSingleRequestSampleTab=he(t.hideSingleRequestSampleTab),this.menuToggle=he(t.menuToggle,!0),this.jsonSampleExpandLevel=e.normalizeJsonSampleExpandLevel(t.jsonSampleExpandLevel),this.enumSkipQuotes=he(t.enumSkipQuotes),this.hideSchemaTitles=he(t.hideSchemaTitles),this.payloadSampleIdx=e.normalizePayloadSampleIdx(t.payloadSampleIdx),this.expandSingleSchemaField=he(t.expandSingleSchemaField),this.unstable_ignoreMimeParameters=he(t.unstable_ignoreMimeParameters),this.allowedMdComponents=t.allowedMdComponents||{},this.expandDefaultServerVariables=he(t.expandDefaultServerVariables)}return e.normalizeExpandResponses=function(e){if("all"===e)return"all";if("string"==typeof e){var t={};return e.split(",").forEach((function(e){t[e.trim()]=!0})),t}return void 0!==e&&console.warn('expandResponses must be a string but received value "'+e+'" of type '+typeof e),{}},e.normalizeHideHostname=function(e){return!!e},e.normalizeScrollYOffset=function(e){if("string"==typeof e&&!se(e)){var t=te(e);t||console.warn("scrollYOffset value is a selector to non-existing element. Using offset 0 by default");var n=t&&t.getBoundingClientRect().bottom||0;return function(){return n}}return"number"==typeof e||se(e)?function(){return"number"==typeof e?e:parseFloat(e)}:"function"==typeof e?function(){var t=e();return"number"!=typeof t&&console.warn('scrollYOffset should return number but returned value "'+t+'" of type '+typeof t),t}:(void 0!==e&&console.warn("Wrong value for scrollYOffset ReDoc option: should be string, number or function"),function(){return 0})},e.normalizeShowExtensions=function(e){if(void 0===e)return!1;if(""===e)return!0;if("string"!=typeof e)return e;switch(e){case"true":return!0;case"false":return!1;default:return e.split(",").map((function(e){return e.trim()}))}},e.normalizePayloadSampleIdx=function(e){return"number"==typeof e?Math.max(0,e):"string"==typeof e&&isFinite(e)?parseInt(e,10):0},e.normalizeJsonSampleExpandLevel=function(e){return"all"===e?1/0:isNaN(Number(e))?2:Math.ceil(Number(e))},e}(),ye=n(152),ve=ye.default,be=ye.css,xe=ye.createGlobalStyle,we=ye.keyframes,ke=ye.ThemeProvider,Oe=function(e,t){return function(){for(var n=[],r=0;r<arguments.length;r++)n[r]=arguments[r];return be(me||(me=Object(o.__makeTemplateObject)(["\n @media "," screen and (max-width: ",") {\n ",";\n }\n "],["\n @media "," screen and (max-width: ",") {\n ",";\n }\n "])),t?"print, ":"",(function(t){return t.theme.breakpoints[e]}),be.apply(void 0,n))}},_e=ve;function Ee(e){return function(t){if(t.theme.extensionsHook)return t.theme.extensionsHook(e,t)}}var Se,Te,je,Ce,Ie=_e.div(Se||(Se=Object(o.__makeTemplateObject)(["\n padding: 20px;\n color: red;\n"],["\n padding: 20px;\n color: red;\n"]))),Ae=function(e){function t(t){var n=e.call(this,t)||this;return n.state={error:void 0},n}return Object(o.__extends)(t,e),t.prototype.componentDidCatch=function(e){return this.setState({error:e}),!1},t.prototype.render=function(){return this.state.error?i.createElement(Ie,null,i.createElement("h1",null,"Something went wrong..."),i.createElement("small",null," ",this.state.error.message," "),i.createElement("p",null,i.createElement("details",null,i.createElement("summary",null,"Stack trace"),i.createElement("pre",null,this.state.error.stack))),i.createElement("small",null," ReDoc Version: ","2.0.0-rc.29")," ",i.createElement("br",null),i.createElement("small",null," Commit: ","2c6e3b6")):i.Children.only(this.props.children)},t}(i.Component),Pe=we(Te||(Te=Object(o.__makeTemplateObject)(["\n 0% {\n transform: rotate(0deg); }\n 100% {\n transform: rotate(360deg);\n }\n"],["\n 0% {\n transform: rotate(0deg); }\n 100% {\n transform: rotate(360deg);\n }\n"]))),Re=_e((function(e){return i.createElement("svg",{className:e.className,version:"1.1",width:"512",height:"512",viewBox:"0 0 512 512"},i.createElement("path",{d:"M275.682 147.999c0 10.864-8.837 19.661-19.682 19.661v0c-10.875 0-19.681-8.796-19.681-19.661v-96.635c0-10.885 8.806-19.661 19.681-19.661v0c10.844 0 19.682 8.776 19.682 19.661v96.635z"}),i.createElement("path",{d:"M275.682 460.615c0 10.865-8.837 19.682-19.682 19.682v0c-10.875 0-19.681-8.817-19.681-19.682v-96.604c0-10.885 8.806-19.681 19.681-19.681v0c10.844 0 19.682 8.796 19.682 19.682v96.604z"}),i.createElement("path",{d:"M147.978 236.339c10.885 0 19.681 8.755 19.681 19.641v0c0 10.885-8.796 19.702-19.681 19.702h-96.624c-10.864 0-19.661-8.817-19.661-19.702v0c0-10.885 8.796-19.641 19.661-19.641h96.624z"}),i.createElement("path",{d:"M460.615 236.339c10.865 0 19.682 8.755 19.682 19.641v0c0 10.885-8.817 19.702-19.682 19.702h-96.584c-10.885 0-19.722-8.817-19.722-19.702v0c0-10.885 8.837-19.641 19.722-19.641h96.584z"}),i.createElement("path",{d:"M193.546 165.703c7.69 7.66 7.68 20.142 0 27.822v0c-7.701 7.701-20.162 7.701-27.853 0.020l-68.311-68.322c-7.68-7.701-7.68-20.142 0-27.863v0c7.68-7.68 20.121-7.68 27.822 0l68.342 68.342z"}),i.createElement("path",{d:"M414.597 386.775c7.7 7.68 7.7 20.163 0.021 27.863v0c-7.7 7.659-20.142 7.659-27.843-0.062l-68.311-68.26c-7.68-7.7-7.68-20.204 0-27.863v0c7.68-7.7 20.163-7.7 27.842 0l68.291 68.322z"}),i.createElement("path",{d:"M165.694 318.464c7.69-7.7 20.153-7.7 27.853 0v0c7.68 7.659 7.69 20.163 0 27.863l-68.342 68.322c-7.67 7.659-20.142 7.659-27.822-0.062v0c-7.68-7.68-7.68-20.122 0-27.801l68.311-68.322z"}),i.createElement("path",{d:"M386.775 97.362c7.7-7.68 20.142-7.68 27.822 0v0c7.7 7.68 7.7 20.183 0.021 27.863l-68.322 68.311c-7.68 7.68-20.163 7.68-27.843-0.020v0c-7.68-7.68-7.68-20.162 0-27.822l68.322-68.332z"}))}))(je||(je=Object(o.__makeTemplateObject)(["\n animation: 2s "," linear infinite;\n width: 50px;\n height: 50px;\n content: '';\n display: inline-block;\n margin-left: -25px;\n\n path {\n fill: ",";\n }\n"],["\n animation: 2s "," linear infinite;\n width: 50px;\n height: 50px;\n content: '';\n display: inline-block;\n margin-left: -25px;\n\n path {\n fill: ",";\n }\n"])),Pe,(function(e){return e.color})),Ne=_e.div(Ce||(Ce=Object(o.__makeTemplateObject)(["\n font-family: helvetica, sans;\n width: 100%;\n text-align: center;\n font-size: 25px;\n margin: 30px 0 20px 0;\n color: ",";\n"],["\n font-family: helvetica, sans;\n width: 100%;\n text-align: center;\n font-size: 25px;\n margin: 30px 0 20px 0;\n color: ",";\n"])),(function(e){return e.color})),Le=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){return i.createElement("div",{style:{textAlign:"center"}},i.createElement(Ne,{color:this.props.color},"Loading ..."),i.createElement(Re,{color:this.props.color}))},t}(i.PureComponent),Me=i.createContext(new ge({})),De=Me.Provider,Fe=Me.Consumer,ze=n(2),Ue=n(155),Be=n(156);function $e(e){return Object(o.__awaiter)(this,void 0,void 0,(function(){var t;return Object(o.__generator)(this,(function(n){switch(n.label){case 0:return[4,(new Ue).bundle(e,{resolve:{http:{withCredentials:!1}}})];case 1:return void 0!==(t=n.sent()).swagger?[2,qe(t)]:[2,t]}}))}))}function qe(e){return console.warn("[ReDoc Compatibility mode]: Converting OpenAPI 2.0 to OpenAPI 3.0"),new Promise((function(t,n){return Object(Be.convertObj)(e,{patch:!0,warnOnly:!0,text:"{}"},(function(e,r){if(e)return n(e);t(r&&r.openapi)}))}))}var We=n(27),He=n(66),Ve=n(22),Ye=Ve.parse,Qe=function(){function e(){}return e.baseName=function(t,n){void 0===n&&(n=1);var r=e.parse(t);return r[r.length-n]},e.dirName=function(t,n){void 0===n&&(n=1);var r=e.parse(t);return Ve.compile(r.slice(0,r.length-n))},e.relative=function(t,n){var r=e.parse(t);return e.parse(n).slice(r.length)},e.parse=function(e){var t=e;return"#"===t.charAt(0)&&(t=t.substring(1)),Ye(t)},e.join=function(t,n){var r=e.parse(t).concat(n);return Ve.compile(r)},e.get=function(e,t){return Ve.get(e,t)},e.compile=function(e){return Ve.compile(e)},e.escape=function(e){return Ve.escape(e)},e}();Ve.parse=Qe.parse,Object.assign(Qe,Ve);var Ge=n(52),Xe=n(67);function Ke(e){return"string"==typeof e&&/\dxx/i.test(e)}function Ze(e,t){if(void 0===t&&(t=!1),"default"===e)return t?"error":"success";var n="string"==typeof e?parseInt(e,10):e;if(Ke(e)&&(n*=100),n<100||n>599)throw new Error("invalid HTTP code");var r="success";return n>=300&&n<400?r="redirect":n>=400?r="error":n<200&&(r="info"),r}var Je={get:!0,post:!0,put:!0,head:!0,patch:!0,delete:!0,options:!0};function et(e){return e in Je}var tt={multipleOf:"number",maximum:"number",exclusiveMaximum:"number",minimum:"number",exclusiveMinimum:"number",maxLength:"string",minLength:"string",pattern:"string",items:"array",maxItems:"array",minItems:"array",uniqueItems:"array",maxProperties:"object",minProperties:"object",required:"object",additionalProperties:"object",properties:"object"};function nt(e){return-1!==e.search(/json/i)}function rt(e,t,n){return Array.isArray(e)?e.map((function(e){return e.toString()})).join(n):"object"==typeof e?Object.keys(e).map((function(t){return""+t+n+e[t]})).join(n):t+"="+e.toString()}function ot(e,t){return Array.isArray(e)?(console.warn("deepObject style cannot be used with array value:"+e.toString()),""):"object"==typeof e?Object.keys(e).map((function(n){return t+"["+n+"]="+e[n]})).join("&"):(console.warn("deepObject style cannot be used with non-object value:"+e.toString()),"")}function it(e,t,n){var r,o=t?"*":"";return Xe.parse("{?__redoc_param_name__"+o+"}").expand((r={},r.__redoc_param_name__=n,r)).substring(1).replace(/__redoc_param_name__/g,e)}function at(e,t){return nt(t)?JSON.stringify(e):(console.warn("Parameter serialization as "+t+" is not supported"),"")}function st(e,t){var n=e.name,r=e.style,o=e.explode,i=void 0!==o&&o,a=e.serializationMime;if(a)switch(e.in){case"path":case"header":return at(t,a);case"cookie":case"query":return n+"="+at(t,a);default:return console.warn("Unexpected parameter location: "+e.in),""}if(!r)return console.warn("Missing style attribute or content for parameter "+n),"";switch(e.in){case"path":return function(e,t,n,r){var o,i=n?"*":"",a="";return"label"===t?a=".":"matrix"===t&&(a=";"),Xe.parse("{"+a+"__redoc_param_name__"+i+"}").expand((o={},o.__redoc_param_name__=r,o)).replace(/__redoc_param_name__/g,e)}(n,r,i,t);case"query":return function(e,t,n,r){switch(t){case"form":return it(e,n,r);case"spaceDelimited":return Array.isArray(r)?n?it(e,n,r):e+"="+r.join("%20"):(console.warn("The style spaceDelimited is only applicable to arrays"),"");case"pipeDelimited":return Array.isArray(r)?n?it(e,n,r):e+"="+r.join("|"):(console.warn("The style pipeDelimited is only applicable to arrays"),"");case"deepObject":return!n||Array.isArray(r)||"object"!=typeof r?(console.warn("The style deepObject is only applicable for objects with explode=true"),""):ot(r,e);default:return console.warn("Unexpected style for query: "+t),""}}(n,r,i,t);case"header":return function(e,t,n){var r;switch(e){case"simple":var o=t?"*":"",i="__redoc_param_name__",a=Xe.parse("{"+i+o+"}");return decodeURIComponent(a.expand(((r={})[i]=n,r)));default:return console.warn("Unexpected style for header: "+e),""}}(r,i,t);case"cookie":return function(e,t,n,r){switch(t){case"form":return it(e,n,r);default:return console.warn("Unexpected style for cookie: "+t),""}}(n,r,i,t);default:return console.warn("Unexpected parameter location: "+e.in),""}}function lt(e){return/^#\/components\/schemas\/[^\/]+$/.test(e||"")}function ct(e,t,n){var r;return void 0!==t&&void 0!==n?r=t===n?t+" "+e:"[ "+t+" .. "+n+" ] "+e:void 0!==n?r="<= "+n+" "+e:void 0!==t&&(r=1===t?"non-empty":">= "+t+" "+e),r}function ut(e,t){void 0===t&&(t=[]);var n=[],r=[],i=[];return e.forEach((function(e){e.required?t.includes(e.name)?r.push(e):i.push(e):n.push(e)})),r.sort((function(e,n){return t.indexOf(e.name)-t.indexOf(n.name)})),Object(o.__spreadArrays)(r,i,n)}function pt(e,t){return Object(o.__spreadArrays)(e).sort((function(e,n){return e[t].localeCompare(n[t])}))}function ft(e,t){var n=void 0===e?function(e){try{var t=pe(e);return t.search="",t.toString()}catch(t){return e}}(function(){if(!ee)return"";var e=window.location.href;return e.endsWith(".html")?Object(Ge.dirname)(e):e}()):Object(Ge.dirname)(e);function r(e){return function(e,t){var n;if(t.startsWith("//"))n=""+(Object(oe.parse)(e).protocol||"https:")+t;else if(function(e){return/(?:^[a-z][a-z0-9+.-]*:|\/\/)/i.test(e)}(t))n=t;else if(t.startsWith("/")){var r=Object(oe.parse)(e);n=Object(oe.format)(Object(o.__assign)(Object(o.__assign)({},r),{pathname:t}))}else n=ae(e)+"/"+t;return ae(n)}(n,e)}return 0===t.length&&(t=[{url:"/"}]),t.map((function(e){return Object(o.__assign)(Object(o.__assign)({},e),{url:r(e.url),description:e.description||""})}))}var dt="section/Authentication/";var ht=function(e){return{delete:"del",options:"opts"}[e]||e};function mt(e,t){return Object.keys(e).filter((function(e){return!0===t?e.startsWith("x-")&&!function(e){return e in{"x-circular-ref":!0,"x-code-samples":!0,"x-codeSamples":!0,"x-displayName":!0,"x-examples":!0,"x-ignoredHeaderParameters":!0,"x-logo":!0,"x-nullable":!0,"x-servers":!0,"x-tagGroups":!0,"x-traitTag":!0,"x-additionalPropertiesName":!0,"x-explicitMappingOnly":!0}}(e):e.startsWith("x-")&&t.indexOf(e)>-1})).reduce((function(t,n){return t[n]=e[n],t}),{})}var gt=n(41);n(295),n(296),n(297),n(298),n(299),n(300),n(301),n(302),n(303),n(304),n(305),n(306),n(307),n(308),n(309),n(310),n(311),n(312),n(313),n(314);function yt(e,t){void 0===t&&(t="clike"),t=t.toLowerCase();var n=gt.languages[t];return n||(n=gt.languages[function(e){return{json:"js","c++":"cpp","c#":"csharp","objective-c":"objectivec",shell:"bash",viml:"vim"}[e]||"clike"}(t)]),gt.highlight(e,n,t)}function vt(e){return function(t,n,r){var o,i,a,s,l,c,u,p;r.value=(o=r.value,i=e,c=null,u=0,p=function(){u=(new Date).getTime(),c=null,l=o.apply(a,s),c||(a=s=null)},function(){var e=(new Date).getTime(),t=i-(e-u);return a=this,s=arguments,t<=0||t>i?(c&&(clearTimeout(c),c=null),u=e,l=o.apply(a,s),c||(a=s=null)):c||(c=setTimeout(p,t)),l})}}function bt(e){0}function xt(e){0}gt.languages.insertBefore("javascript","string",{"property string":{pattern:/([{,]\s*)"(?:\\.|[^\\"\r\n])*"(?=\s*:)/i,lookbehind:!0}},void 0),gt.languages.insertBefore("javascript","punctuation",{property:{pattern:/([{,]\s*)[a-z]\w*(?=\s*:)/i,lookbehind:!0}},void 0);var wt={};function kt(e,t,n){if("function"==typeof n.value)return function(e,t,n){if(!n.value||n.value.length>0)throw new Error("@memoize decorator can only be applied to methods of zero arguments");var r="_memoized_"+t,i=n.value;return e[r]=wt,Object(o.__assign)(Object(o.__assign)({},n),{value:function(){return this[r]===wt&&(this[r]=i.call(this)),this[r]}})}(e,t,n);if("function"==typeof n.get)return function(e,t,n){var r="_memoized_"+t,i=n.get;return e[r]=wt,Object(o.__assign)(Object(o.__assign)({},n),{get:function(){return this[r]===wt&&(this[r]=i.call(this)),this[r]}})}(e,t,n);throw new Error("@memoize decorator can be applied to methods or getters, got "+String(n.value)+" instead")}var Ot="hashchange",_t=new(function(){function e(){var e=this;this.emit=function(){e._emiter.emit(Ot,e.currentId)},this._emiter=new He.EventEmitter,this.bind()}return Object.defineProperty(e.prototype,"currentId",{get:function(){return ee?decodeURIComponent(window.location.hash.substring(1)):""},enumerable:!0,configurable:!0}),e.prototype.linkForId=function(e){return e?"#"+e:""},e.prototype.subscribe=function(e){var t=this._emiter.addListener(Ot,e);return function(){return t.removeListener(Ot,e)}},e.prototype.bind=function(){ee&&window.addEventListener("hashchange",this.emit,!1)},e.prototype.dispose=function(){ee&&window.removeEventListener("hashchange",this.emit)},e.prototype.replace=function(e,t){void 0===t&&(t=!1),ee&&null!=e&&e!==this.currentId&&(t?window.history.replaceState(null,"",window.location.href.split("#")[0]+this.linkForId(e)):(window.history.pushState(null,"",window.location.href.split("#")[0]+this.linkForId(e)),this.emit()))},Object(o.__decorate)([We.bind,We.debounce],e.prototype,"replace",null),e}());var Et=n(98),St=function(){function e(){this.map=new Map,this.prevTerm=""}return e.prototype.add=function(e){this.map.set(e,new Et(e))},e.prototype.delete=function(e){this.map.delete(e)},e.prototype.addOnly=function(e){var t=this;this.map.forEach((function(n,r){-1===e.indexOf(r)&&(n.unmark(),t.map.delete(r))}));for(var n=0,r=e;n<r.length;n++){var o=r[n];this.map.has(o)||this.map.set(o,new Et(o))}},e.prototype.clearAll=function(){this.unmark(),this.map.clear()},e.prototype.mark=function(e){var t=this;(e||this.prevTerm)&&(this.map.forEach((function(n){n.unmark(),n.mark(e||t.prevTerm)})),this.prevTerm=e||this.prevTerm)},e.prototype.unmark=function(){this.map.forEach((function(e){return e.unmark()})),this.prevTerm=""},e}(),Tt=n(53),jt=new Tt.Renderer;Tt.setOptions({renderer:jt,highlight:function(e,t){return yt(e,t)}});var Ct="(?:^ {0,3}\x3c!-- ReDoc-Inject:\\s+?<({component}).*?/?>\\s+?--\x3e\\s*$|(?:^ {0,3}<({component})([\\s\\S]*?)>([\\s\\S]*?)</\\2>|^ {0,3}<({component})([\\s\\S]*?)(?:/>|\\n{2,})))";var It=function(){function e(e){var t=this;this.options=e,this.headings=[],this.headingRule=function(e,n,r,o){return 1===n?t.currentTopHeading=t.saveHeading(e,n):2===n&&t.saveHeading(e,n,t.currentTopHeading&&t.currentTopHeading.items,t.currentTopHeading&&t.currentTopHeading.id),t.originalHeadingRule(e,n,r,o)},this.headingEnhanceRenderer=new Tt.Renderer,this.originalHeadingRule=this.headingEnhanceRenderer.heading.bind(this.headingEnhanceRenderer),this.headingEnhanceRenderer.heading=this.headingRule}return e.containsComponent=function(e,t){return new RegExp(Ct.replace(/{component}/g,t),"gmi").test(e)},e.getTextBeforeHading=function(e,t){var n=e.search(new RegExp("^##?\\s+"+t,"m"));return n>-1?e.substring(0,n):e},e.prototype.saveHeading=function(e,t,n,r){void 0===n&&(n=this.headings),e=e.replace(/&#(\d+);/g,(function(e,t){return String.fromCharCode(parseInt(t,10))})).replace(/&/g,"&");var o={id:r?r+"/"+ue(e):"section/"+ue(e),name:e,level:t,items:[]};return n.push(o),o},e.prototype.flattenHeadings=function(e){if(void 0===e)return[];for(var t=[],n=0,r=e;n<r.length;n++){var o=r[n];t.push(o),t.push.apply(t,this.flattenHeadings(o.items))}return t},e.prototype.attachHeadingsDescriptions=function(e){var t=function(e){return new RegExp("##?\\s+"+e.name.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&"))},n=this.flattenHeadings(this.headings);if(!(n.length<1)){for(var r=n[0],o=t(r),i=e.search(o),a=1;a<n.length;a++){var s=n[a],l=t(s),c=e.substr(i+1).search(l)+i+1;r.description=e.substring(i,c).replace(o,"").trim(),r=s,o=l,i=c}r.description=e.substring(i).replace(o,"").trim()}},e.prototype.renderMd=function(e,t){void 0===t&&(t=!1);var n=t?{renderer:this.headingEnhanceRenderer}:void 0;return Tt(e.toString(),n)},e.prototype.extractHeadings=function(e){this.renderMd(e,!0),this.attachHeadingsDescriptions(e);var t=this.headings;return this.headings=[],t},e.prototype.renderMdWithComponents=function(e){var t=this.options&&this.options.allowedMdComponents;if(!t||0===Object.keys(t).length)return[this.renderMd(e)];for(var n=Object.keys(t).join("|"),r=new RegExp(Ct.replace(/{component}/g,n),"mig"),i=[],a=[],s=r.exec(e),l=0;s;){i.push(e.substring(l,s.index)),l=r.lastIndex;var c=t[s[1]||s[2]||s[5]],u=s[3]||s[6],p=s[4];c&&a.push({component:c.component,propsSelector:c.propsSelector,props:Object(o.__assign)(Object(o.__assign)(Object(o.__assign)({},At(u)),c.props),{children:p})}),s=r.exec(e)}i.push(e.substring(l));for(var f=[],d=0;d<i.length;d++){var h=i[d];h&&f.push(this.renderMd(h)),a[d]&&f.push(a[d])}return f},e}();function At(e){if(!e)return{};for(var t,n=/([\w-]+)\s*=\s*(?:{([^}]+?)}|"([^"]+?)")/gim,r={};null!==(t=n.exec(e));)if(t[3])r[t[1]]=t[3];else if(t[2]){var o=void 0;try{o=JSON.parse(t[2])}catch(e){}r[t[1]]=o}return r}var Pt=function(){function e(e){this.parser=e,Object.assign(this,e.spec.info),this.description=e.spec.info.description||"";var t=this.description.search(/^##?\s+/m);t>-1&&(this.description=this.description.substring(0,t)),this.downloadLink=this.getDownloadLink(),this.downloadFileName=this.getDownloadFileName()}return e.prototype.getDownloadLink=function(){if(this.parser.specUrl)return this.parser.specUrl;if(ee&&window.Blob&&window.URL&&window.URL.createObjectURL){var e=new Blob([JSON.stringify(this.parser.spec,null,2)],{type:"application/json"});return window.URL.createObjectURL(e)}},e.prototype.getDownloadFileName=function(){if(!this.parser.specUrl)return"swagger.json"},e}(),Rt=function(e,t,n){var r=e.deref(n);this.id=t,this.sectionId=dt+t,this.type=r.type,this.description=r.description||"","apiKey"===r.type&&(this.apiKey={name:r.name,in:r.in}),"http"===r.type&&(this.http={scheme:r.scheme,bearerFormat:r.bearerFormat}),"openIdConnect"===r.type&&(this.openId={connectUrl:r.openIdConnectUrl}),"oauth2"===r.type&&r.flows&&(this.flows=r.flows)},Nt=function(e){var t=e.spec.components&&e.spec.components.securitySchemes||{};this.schemes=Object.keys(t).map((function(n){return new Rt(e,n,t[n])}))},Lt=function(){function e(){this._counter={}}return e.prototype.reset=function(){this._counter={}},e.prototype.visit=function(e){this._counter[e]=this._counter[e]?this._counter[e]+1:1},e.prototype.exit=function(e){this._counter[e]=this._counter[e]&&this._counter[e]-1},e.prototype.visited=function(e){return!!this._counter[e]},e}(),Mt=function(){function e(e,t,n){var r=this;void 0===n&&(n=new ge({})),this.options=n,this._refCounter=new Lt,this.byRef=function(e){var t;if(r.spec){"#"!==e.charAt(0)&&(e="#"+e),e=decodeURIComponent(e);try{t=Qe.get(r.spec,e)}catch(e){}return t||{}}},this.validate(e),this.preprocess(e),this.spec=e,this.mergeRefs=new Set;var o=ee?window.location.href:"";"string"==typeof t&&(this.specUrl=Object(oe.resolve)(o,t))}return e.prototype.validate=function(e){if(void 0===e.openapi)throw new Error("Document must be valid OpenAPI 3.0.0 definition")},e.prototype.preprocess=function(e){if(!this.options.noAutoAuth&&e.info&&e.components&&e.components.securitySchemes){var t=e.info.description||"";if(!It.containsComponent(t,"security-definitions")&&!It.containsComponent(t,"SecurityDefinitions")){var n="\x3c!-- ReDoc-Inject: <"+"security-definitions"+"> --\x3e";e.info.description=function(e,t,n){var r=new RegExp("(^|\\n)#\\s?"+t+"\\s*\\n","i"),o=new RegExp("((\\n|^)#\\s*"+t+"\\s*(\\n|$)(?:.|\\n)*?)(\\n#|$)","i");if(r.test(e))return e.replace(o,"$1\n\n"+n+"\n$4");var i=""===e||e.endsWith("\n\n")?"":e.endsWith("\n")?"\n":"\n\n";return""+e+i+"# "+t+"\n\n"+n}(t,"Authentication",n)}}},e.prototype.isRef=function(e){return!!e&&(void 0!==e.$ref&&null!==e.$ref)},e.prototype.resetVisited=function(){this._refCounter=new Lt},e.prototype.exitRef=function(e){this.isRef(e)&&this._refCounter.exit(e.$ref)},e.prototype.deref=function(e,t){if(void 0===t&&(t=!1),this.isRef(e)){var n=this.byRef(e.$ref),r=this._refCounter.visited(e.$ref);if(this._refCounter.visit(e.$ref),r&&!t)return Object.assign({},n,{"x-circular-ref":!0});if(this.isRef(n)){var o=this.deref(n);return this.exitRef(n),o}return n}return e},e.prototype.shalowDeref=function(e){return this.isRef(e)?this.byRef(e.$ref):e},e.prototype.mergeAllOf=function(e,t,n,r){var i=this;if(void 0===n&&(n=!1),void 0===r&&(r=new Set),t&&r.add(t),void 0===(e=this.hoistOneOfs(e)).allOf)return e;var a=Object(o.__assign)(Object(o.__assign)({},e),{allOf:void 0,parentRefs:[],title:e.title||(lt(t)?Qe.baseName(t):void 0)});void 0!==a.properties&&"object"==typeof a.properties&&(a.properties=Object(o.__assign)({},a.properties)),void 0!==a.items&&"object"==typeof a.items&&(a.items=Object(o.__assign)({},a.items));for(var s=0,l=e.allOf.map((function(e){var t;if(!(e&&e.$ref&&r.has(e.$ref))){var o=i.deref(e,n),s=e.$ref||void 0,l=i.mergeAllOf(o,s,n,r);return(t=a.parentRefs).push.apply(t,l.parentRefs||[]),{$ref:s,schema:l}}})).filter((function(e){return void 0!==e}));s<l.length;s++){var c=l[s],u=c.$ref,p=c.schema;if(a.type!==p.type&&void 0!==a.type&&void 0!==p.type&&console.warn('Incompatible types in allOf at "'+t+'": "'+a.type+'" and "'+p.type+'"'),void 0!==p.type&&(a.type=p.type),void 0!==p.properties)for(var f in a.properties=a.properties||{},p.properties)a.properties[f]?a.properties[f]=this.mergeAllOf({allOf:[a.properties[f],p.properties[f]]},t+"/properties/"+f):a.properties[f]=p.properties[f];void 0!==p.items&&(a.items=a.items||{},a.items=this.mergeAllOf({allOf:[a.items,p.items]},t+"/items")),void 0!==p.required&&(a.required=(a.required||[]).concat(p.required)),a=Object(o.__assign)(Object(o.__assign)({},p),a),u&&(a.parentRefs.push(u),void 0===a.title&<(u))}return a},e.prototype.findDerived=function(e){var t={},n=this.spec.components&&this.spec.components.schemas||{};for(var r in n){var o=this.deref(n[r]);void 0!==o.allOf&&o.allOf.find((function(t){return void 0!==t.$ref&&e.indexOf(t.$ref)>-1}))&&(t["#/components/schemas/"+r]=[o["x-discriminator-value"]||r])}return t},e.prototype.exitParents=function(e){for(var t=0,n=e.parentRefs||[];t<n.length;t++){var r=n[t];this.exitRef({$ref:r})}},e.prototype.hoistOneOfs=function(e){var t=this;if(void 0===e.allOf)return e;for(var n=e.allOf,r=function(e){var r=n[e];if(Array.isArray(r.oneOf)){var i=n.slice(0,e),a=n.slice(e+1);return{value:{oneOf:r.oneOf.map((function(e){var n=t.mergeAllOf({allOf:Object(o.__spreadArrays)(i,[e],a)});return t.exitParents(n),n}))}}}},i=0;i<n.length;i++){var a=r(i);if("object"==typeof a)return a.value}return e},e}(),Dt=function(e,t,n){this.options=n,this.parser=new Mt(e,t,n),this.info=new Pt(this.parser),this.externalDocs=this.parser.spec.externalDocs,this.contentItems=en.buildStructure(this.parser,this.options),this.securitySchemes=new Nt(this.parser)},Ft=function(){function e(e,t,n){this.items=[],this.active=!1,this.expanded=!1,this.id=t.id||e+"/"+ue(t.name),this.type=e,this.name=t["x-displayName"]||t.name,this.level=t.level||1,this.description=t.description||"";var r=t.items;r&&r.length&&(this.description=It.getTextBeforeHading(this.description,r[0].name)),this.parent=n,this.externalDocs=t.externalDocs,"group"===this.type&&(this.expanded=!0)}return e.prototype.activate=function(){this.active=!0},e.prototype.expand=function(){this.parent&&this.parent.expand(),this.expanded=!0},e.prototype.collapse=function(){"group"!==this.type&&(this.expanded=!1)},e.prototype.deactivate=function(){this.active=!1},Object(o.__decorate)([ze.l],e.prototype,"active",void 0),Object(o.__decorate)([ze.l],e.prototype,"expanded",void 0),Object(o.__decorate)([ze.d],e.prototype,"activate",null),Object(o.__decorate)([ze.d],e.prototype,"expand",null),Object(o.__decorate)([ze.d],e.prototype,"collapse",null),Object(o.__decorate)([ze.d],e.prototype,"deactivate",null),e}(),zt=function(e,t){var n=t.spec.components&&t.spec.components.securitySchemes||{};this.schemes=Object.keys(e||{}).map((function(r){var i=t.deref(n[r]),a=e[r]||[];if(i)return Object(o.__assign)(Object(o.__assign)({},i),{id:r,sectionId:dt+r,scopes:a});console.warn("Non existing security scheme referenced: "+r+". Skipping")})).filter((function(e){return void 0!==e}))},Ut=function(){function e(e,t,n,r,i){this.operations=[],this.name=t;var a=e.deref(n);e.exitRef(n);for(var s=0,l=Object.keys(a);s<l.length;s++)for(var c=l[s],u=a[c],p=0,f=Object.keys(u).filter(et);p<f.length;p++){var d=f[p],h=u[d],m=new Jt(e,Object(o.__assign)(Object(o.__assign)({},h),{pathName:c,pointer:Qe.compile([r,t,c,d]),httpVerb:d,pathParameters:u.parameters||[],pathServers:u.servers}),void 0,i,!0);this.operations.push(m)}}return e.prototype.toggle=function(){this.expanded=!this.expanded},Object(o.__decorate)([ze.l],e.prototype,"expanded",void 0),Object(o.__decorate)([ze.d],e.prototype,"toggle",null),e}(),Bt=function(){function e(e,t,n,r,o){void 0===o&&(o=!1),this.options=r,this.typePrefix="",this.isCircular=!1,this.activeOneOf=0,this.pointer=t.$ref||n||"",this.rawSchema=e.deref(t),this.schema=e.mergeAllOf(this.rawSchema,this.pointer,o),this.init(e,o),e.exitRef(t),e.exitParents(this.schema),r.showExtensions&&(this.extensions=mt(this.schema,r.showExtensions))}return e.prototype.activateOneOf=function(e){this.activeOneOf=e},e.prototype.init=function(t,n){var r=this,i=this.schema;if(this.isCircular=i["x-circular-ref"],this.title=i.title||lt(this.pointer)&&Qe.baseName(this.pointer)||"",this.description=i.description||"",this.type=i.type||function(e){if(void 0!==e.type)return e.type;for(var t=0,n=Object.keys(tt);t<n.length;t++){var r=n[t],o=tt[r];if(void 0!==e[r])return o}return"any"}(i),this.format=i.format,this.nullable=!!i.nullable,this.enum=i.enum||[],this.example=i.example,this.deprecated=!!i.deprecated,this.pattern=i.pattern,this.externalDocs=i.externalDocs,this.constraints=function(e){var t=[],n=ct("characters",e.minLength,e.maxLength);void 0!==n&&t.push(n);var r=ct("items",e.minItems,e.maxItems);void 0!==r&&t.push(r);var o,i=function(e){if(void 0!==e){var t=e.toString(10);return/^0\.0*1$/.test(t)?"decimal places <= "+t.split(".")[1].length:"multiple of "+t}}(e.multipleOf);return void 0!==i&&t.push(i),void 0!==e.minimum&&void 0!==e.maximum?(o=e.exclusiveMinimum?"( ":"[ ",o+=e.minimum,o+=" .. ",o+=e.maximum,o+=e.exclusiveMaximum?" )":" ]"):void 0!==e.maximum?(o=e.exclusiveMaximum?"< ":"<= ",o+=e.maximum):void 0!==e.minimum&&(o=e.exclusiveMinimum?"> ":">= ",o+=e.minimum),void 0!==o&&t.push(o),t}(i),this.displayType=this.type,this.displayFormat=this.format,this.isPrimitive=function(e,t){return void 0===t&&(t=e.type),void 0===e.oneOf&&void 0===e.anyOf&&("object"===t?void 0!==e.properties?0===Object.keys(e.properties).length:void 0===e.additionalProperties:"array"!==t||void 0===e.items)}(i,this.type),this.default=i.default,this.readOnly=!!i.readOnly,this.writeOnly=!!i.writeOnly,!this.isCircular){if(n||void 0===$t(i))return n&&Array.isArray(i.oneOf)&&i.oneOf.find((function(e){return e.$ref===r.pointer}))&&delete i.oneOf,void 0!==i.oneOf?(this.initOneOf(i.oneOf,t),this.oneOfType="One of",void(void 0!==i.anyOf&&console.warn("oneOf and anyOf are not supported on the same level. Skipping anyOf at "+this.pointer))):void 0!==i.anyOf?(this.initOneOf(i.anyOf,t),void(this.oneOfType="Any of")):void("object"===this.type?this.fields=function(e,t,n,r){var i=t.properties||{},a=t.additionalProperties,s=t.default||{},l=Object.keys(i||[]).map((function(a){var l=i[a];l||(console.warn('Field "'+a+'" is invalid, skipping.\n Field must be an object but got '+typeof l+' at "'+n+'"'),l={});var c=void 0!==t.required&&t.required.indexOf(a)>-1;return new qt(e,{name:a,required:c,schema:Object(o.__assign)(Object(o.__assign)({},l),{default:void 0===l.default?s[a]:l.default})},n+"/properties/"+a,r)}));r.sortPropsAlphabetically&&(l=pt(l,"name"));r.requiredPropsFirst&&(l=ut(l,r.sortPropsAlphabetically?void 0:t.required));"object"!=typeof a&&!0!==a||l.push(new qt(e,{name:("object"==typeof a&&a["x-additionalPropertiesName"]||"property name").concat("*"),required:!1,schema:!0===a?{}:a,kind:"additionalProperties"},n+"/additionalProperties",r));return l}(t,i,this.pointer,this.options):"array"===this.type&&i.items&&(this.items=new e(t,i.items,this.pointer+"/items",this.options),this.displayType=this.items.displayType.split(" or ").map((function(e){return e.replace(/^(string|object|number|integer|array|boolean)s?( ?.*)/,"$1s$2")})).join(" or "),this.displayFormat=this.items.format,this.typePrefix=this.items.typePrefix+de("arrayOf"),this.title=this.title||this.items.title,this.isPrimitive=this.items.isPrimitive,void 0===this.example&&void 0!==this.items.example&&(this.example=[this.items.example]),this.items.isPrimitive&&(this.enum=this.items.enum)));this.initDiscriminator(i,t)}},e.prototype.initOneOf=function(t,n){var r=this;this.oneOf=t.map((function(t,i){var a=n.deref(t),s=n.mergeAllOf(a,r.pointer+"/oneOf/"+i),l=lt(t.$ref)&&!s.title?Qe.baseName(t.$ref):s.title,c=new e(n,Object(o.__assign)(Object(o.__assign)({},s),{title:l,allOf:[Object(o.__assign)(Object(o.__assign)({},r.schema),{oneOf:void 0,anyOf:void 0})]}),r.pointer+"/oneOf/"+i,r.options);return n.exitRef(t),n.exitParents(s),c})),this.displayType=this.oneOf.map((function(e){var t=e.typePrefix+(e.title?e.title+" ("+e.displayType+")":e.displayType);return t.indexOf(" or ")>-1&&(t="("+t+")"),t})).join(" or ")},e.prototype.initDiscriminator=function(t,n){var r=this,i=$t(t);this.discriminatorProp=i.propertyName;var a=n.findDerived(Object(o.__spreadArrays)(t.parentRefs||[],[this.pointer]));if(t.oneOf)for(var s=0,l=t.oneOf;s<l.length;s++){var c=l[s];if(void 0!==c.$ref){var u=Qe.baseName(c.$ref);a[c.$ref]=u}}var p=i.mapping||{},f=i["x-explicitMappingOnly"]||!1;0===Object.keys(p).length&&(f=!1);var d={};for(var h in p){var m=p[h];Array.isArray(d[m])?d[m].push(h):d[m]=[h]}for(var g=f?Object(o.__assign)({},d):Object(o.__assign)(Object(o.__assign)({},a),d),y=[],v=0,b=Object.keys(g);v<b.length;v++){var x=g[m=b[v]];if(Array.isArray(x))for(var w=0,k=x;w<k.length;w++){var O=k[w];y.push({$ref:m,name:O})}else y.push({$ref:m,name:x})}var _=Object.keys(p);0!==_.length&&(y=y.sort((function(e,t){var n=_.indexOf(e.name),r=_.indexOf(t.name);return n<0&&r<0?e.name.localeCompare(t.name):n<0?1:r<0?-1:n-r}))),this.oneOf=y.map((function(t){var o=t.$ref,i=t.name,a=new e(n,n.byRef(o),o,r.options,!0);return a.title=i,a}))},Object(o.__decorate)([ze.l],e.prototype,"activeOneOf",void 0),Object(o.__decorate)([ze.d],e.prototype,"activateOneOf",null),e}();function $t(e){return e.discriminator||e["x-discriminator"]}var qt=function(){function e(e,t,n,r){var o=e.deref(t);this.kind=t.kind||"field",this.name=t.name||o.name,this.in=o.in,this.required=!!o.required;var i=o.schema,a="";!i&&o.in&&o.content&&(a=Object.keys(o.content)[0],i=o.content[a]&&o.content[a].schema),this.schema=new Bt(e,i||{},n,r),this.description=void 0===o.description?this.schema.description||"":o.description,this.example=o.example||this.schema.example,a?this.serializationMime=a:o.style?this.style=o.style:this.in&&(this.style=function(e){switch(e){case"header":return"simple";case"query":return"form";case"path":return"simple";default:return"form"}}(this.in)),this.explode=!!o.explode,this.deprecated=void 0===o.deprecated?!!this.schema.deprecated:o.deprecated,e.exitRef(t),r.showExtensions&&(this.extensions=mt(o,r.showExtensions))}return e.prototype.toggle=function(){this.expanded=!this.expanded},Object(o.__decorate)([ze.l],e.prototype,"expanded",void 0),Object(o.__decorate)([ze.d],e.prototype,"toggle",null),e}(),Wt=n(99),Ht={},Vt=function(){function e(e,t,n,r){this.mime=n;var o=e.deref(t);this.value=o.value,this.summary=o.summary,this.description=o.description,o.externalValue&&(this.externalValueUrl=Object(oe.resolve)(e.specUrl||"",o.externalValue)),e.exitRef(t),"application/x-www-form-urlencoded"===n&&this.value&&"object"==typeof this.value&&(this.value=function(e,t){if(void 0===t&&(t={}),Array.isArray(e))throw new Error("Payload must have fields: "+e.toString());return Object.keys(e).map((function(n){var r=e[n],o=t[n]||{},i=o.style,a=void 0===i?"form":i,s=o.explode,l=void 0===s||s;switch(a){case"form":return it(n,l,r);case"spaceDelimited":return rt(r,n,"%20");case"pipeDelimited":return rt(r,n,"|");case"deepObject":return ot(r,n);default:return console.warn("Incorrect or unsupported encoding style: "+a),""}})).join("&")}(this.value,r))}return e.prototype.getExternalValue=function(e){return this.externalValueUrl?(Ht[this.externalValueUrl]||(Ht[this.externalValueUrl]=fetch(this.externalValueUrl).then((function(t){return t.text().then((function(n){if(!t.ok)return Promise.reject(new Error(n));if(!nt(e))return n;try{return JSON.parse(n)}catch(e){return n}}))}))),Ht[this.externalValueUrl]):Promise.resolve(void 0)},e}(),Yt=function(){function e(e,t,n,r,o){this.name=t,this.isRequestType=n,this.schema=r.schema&&new Bt(e,r.schema,"",o),this.onlyRequiredInSamples=o.onlyRequiredInSamples,void 0!==r.examples?this.examples=function(e,t){var n={};for(var r in e)e.hasOwnProperty(r)&&(n[r]=t(e[r],r,e));return n}(r.examples,(function(n){return new Vt(e,n,t,r.encoding)})):void 0!==r.example?this.examples={default:new Vt(e,{value:e.shalowDeref(r.example)},t,r.encoding)}:nt(t)&&this.generateExample(e,r)}return e.prototype.generateExample=function(e,t){var n={skipReadOnly:this.isRequestType,skipNonRequired:this.isRequestType&&this.onlyRequiredInSamples,skipWriteOnly:!this.isRequestType};if(this.schema&&this.schema.oneOf){this.examples={};for(var r=0,o=this.schema.oneOf;r<o.length;r++){var i=o[r],a=Wt.sample(i.rawSchema,n,e.spec);this.schema.discriminatorProp&&"object"==typeof a&&a&&(a[this.schema.discriminatorProp]=i.title),this.examples[i.title]=new Vt(e,{value:a},this.name,t.encoding)}}else this.schema&&(this.examples={default:new Vt(e,{value:Wt.sample(t.schema,n,e.spec)},this.name,t.encoding)})},e}(),Qt=function(){function e(e,t,n,r){var i,a;this.isRequestType=n,this.activeMimeIdx=0,r.unstable_ignoreMimeParameters&&(i=t,a={},Object.keys(i).forEach((function(e){var t=i[e],n=e.split(";")[0].trim();a[n]?a[n]=Object(o.__assign)(Object(o.__assign)({},a[n]),t):a[n]=t})),t=a),this.mediaTypes=Object.keys(t).map((function(o){var i=t[o];return e.resetVisited(),new Yt(e,o,n,i,r)}))}return e.prototype.activate=function(e){this.activeMimeIdx=e},Object.defineProperty(e.prototype,"active",{get:function(){return this.mediaTypes[this.activeMimeIdx]},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"hasSample",{get:function(){return this.mediaTypes.filter((function(e){return!!e.examples})).length>0},enumerable:!0,configurable:!0}),Object(o.__decorate)([ze.l],e.prototype,"activeMimeIdx",void 0),Object(o.__decorate)([ze.d],e.prototype,"activate",null),Object(o.__decorate)([ze.e],e.prototype,"active",null),e}(),Gt=function(e,t,n){var r=e.deref(t);this.description=r.description||"",this.required=!!r.required,e.exitRef(t),void 0!==r.content&&(this.content=new Qt(e,r.content,!0,n))},Xt=function(){function e(e,t,n,r,i){this.headers=[],this.expanded="all"===i.expandResponses||i.expandResponses[t];var a=e.deref(r);e.exitRef(r),this.code=t,void 0!==a.content&&(this.content=new Qt(e,a.content,!1,i)),void 0!==a["x-summary"]?(this.summary=a["x-summary"],this.description=a.description||""):(this.summary=a.description||"",this.description=""),this.type=Ze(t,n);var s=a.headers;void 0!==s&&(this.headers=Object.keys(s).map((function(t){var n=s[t];return new qt(e,Object(o.__assign)(Object(o.__assign)({},n),{name:t}),"",i)})))}return e.prototype.toggle=function(){this.expanded=!this.expanded},Object(o.__decorate)([ze.l],e.prototype,"expanded",void 0),Object(o.__decorate)([ze.d],e.prototype,"toggle",null),e}();function Kt(e){return"payload"===e.lang&&e.requestBodyContent}var Zt=!1,Jt=function(){function e(e,t,n,r,o){var i;void 0===o&&(o=!1),this.parser=e,this.operationSpec=t,this.options=r,this.type="operation",this.items=[],this.ready=!0,this.active=!1,this.expanded=!1,this.pointer=t.pointer,this.description=t.description,this.parent=n,this.externalDocs=t.externalDocs,this.deprecated=!!t.deprecated,this.httpVerb=t.httpVerb,this.deprecated=!!t.deprecated,this.operationId=t.operationId,this.path=t.pathName,this.isCallback=o,this.name=(i=t).summary||i.operationId||i.description&&i.description.substring(0,50)||"<no summary>",this.isCallback?(this.security=(t.security||[]).map((function(t){return new zt(t,e)})),this.servers=ft("",t.servers||t.pathServers||[])):(this.id=void 0!==t.operationId?"operation/"+t.operationId:void 0!==n?n.id+this.pointer:this.pointer,this.security=(t.security||e.spec.security||[]).map((function(t){return new zt(t,e)})),this.servers=ft(e.specUrl,t.servers||t.pathServers||e.spec.servers||[])),r.showExtensions&&(this.extensions=mt(t,r.showExtensions))}return e.prototype.activate=function(){this.active=!0},e.prototype.deactivate=function(){this.active=!1},e.prototype.toggle=function(){this.expanded=!this.expanded},e.prototype.expand=function(){this.parent&&this.parent.expand()},e.prototype.collapse=function(){},Object.defineProperty(e.prototype,"requestBody",{get:function(){return this.operationSpec.requestBody&&new Gt(this.parser,this.operationSpec.requestBody,this.options)},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"codeSamples",{get:function(){var e=this.operationSpec["x-codeSamples"]||this.operationSpec["x-code-samples"]||[];this.operationSpec["x-code-samples"]&&!Zt&&(Zt=!0,console.warn('"x-code-samples" is deprecated. Use "x-codeSamples" instead'));var t=this.requestBody&&this.requestBody.content;if(t&&t.hasSample){var n=Math.min(e.length,this.options.payloadSampleIdx);e=Object(o.__spreadArrays)(e.slice(0,n),[{lang:"payload",label:"Payload",source:"",requestBodyContent:t}],e.slice(n))}return e},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"parameters",{get:function(){var e=this,t=function(e,t,n){void 0===t&&(t=[]),void 0===n&&(n=[]);var r={};return n.forEach((function(t){t=e.shalowDeref(t),r[t.name+"_"+t.in]=!0})),(t=t.filter((function(t){return t=e.shalowDeref(t),!r[t.name+"_"+t.in]}))).concat(n)}(this.parser,this.operationSpec.pathParameters,this.operationSpec.parameters).map((function(t){return new qt(e.parser,t,e.pointer,e.options)}));return this.options.sortPropsAlphabetically?pt(t,"name"):this.options.requiredPropsFirst?ut(t):t},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"responses",{get:function(){var e=this,t=!1;return Object.keys(this.operationSpec.responses||[]).filter((function(e){return"default"===e||("success"===Ze(e)&&(t=!0),"default"===(n=e)||se(n)||Ke(n));var n})).map((function(n){return new Xt(e.parser,n,t,e.operationSpec.responses[n],e.options)}))},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"callbacks",{get:function(){var e=this;return Object.keys(this.operationSpec.callbacks||[]).map((function(t){return new Ut(e.parser,t,e.operationSpec.callbacks[t],e.pointer,e.options)}))},enumerable:!0,configurable:!0}),Object(o.__decorate)([ze.l],e.prototype,"ready",void 0),Object(o.__decorate)([ze.l],e.prototype,"active",void 0),Object(o.__decorate)([ze.l],e.prototype,"expanded",void 0),Object(o.__decorate)([ze.d],e.prototype,"activate",null),Object(o.__decorate)([ze.d],e.prototype,"deactivate",null),Object(o.__decorate)([ze.d],e.prototype,"toggle",null),Object(o.__decorate)([kt],e.prototype,"requestBody",null),Object(o.__decorate)([kt],e.prototype,"codeSamples",null),Object(o.__decorate)([kt],e.prototype,"parameters",null),Object(o.__decorate)([kt],e.prototype,"responses",null),Object(o.__decorate)([kt],e.prototype,"callbacks",null),e}(),en=function(){function e(){}return e.buildStructure=function(t,n){var r=t.spec,o=[],i=e.getTagsWithOperations(r);return o.push.apply(o,e.addMarkdownItems(r.info.description||"",void 0,1,n)),r["x-tagGroups"]&&r["x-tagGroups"].length>0?o.push.apply(o,e.getTagGroupsItems(t,void 0,r["x-tagGroups"],i,n)):o.push.apply(o,e.getTagsItems(t,i,void 0,void 0,n)),o},e.addMarkdownItems=function(e,t,n,r){var o=new It(r).extractHeadings(e||"");o.length&&t&&t.description&&(t.description=It.getTextBeforeHading(t.description,o[0].name));var i=function(e,t,n){return void 0===n&&(n=1),t.map((function(t){var r,o=new Ft("section",t,e);return o.depth=n,t.items&&(o.items=i(o,t.items,n+1)),It.containsComponent(o.description||"","security-definitions")&&(r=o.id+"/",dt=r),o}))};return i(t,o,n)},e.getTagGroupsItems=function(t,n,r,o,i){for(var a=[],s=0,l=r;s<l.length;s++){var c=l[s],u=new Ft("group",c,n);u.depth=0,u.items=e.getTagsItems(t,o,u,c,i),a.push(u)}return a},e.getTagsItems=function(t,n,r,i,a){for(var s=[],l=0,c=(void 0===i?Object.keys(n):i.tags).map((function(e){return n[e]?(n[e].used=!0,n[e]):(console.warn('Non-existing tag "'+e+'" is added to the group "'+i.name+'"'),null)}));l<c.length;l++){var u=c[l];if(u){var p=new Ft("tag",u,r);if(p.depth=1,""!==u.name)p.items=Object(o.__spreadArrays)(e.addMarkdownItems(u.description||"",p,p.depth+1,a),this.getOperationsItems(t,p,u,p.depth+1,a)),s.push(p);else{var f=Object(o.__spreadArrays)(e.addMarkdownItems(u.description||"",p,p.depth+1,a),this.getOperationsItems(t,void 0,u,p.depth+1,a));s.push.apply(s,f)}}}return s},e.getOperationsItems=function(e,t,n,r,o){if(0===n.operations.length)return[];for(var i=[],a=0,s=n.operations;a<s.length;a++){var l=s[a],c=new Jt(e,l,t,o);c.depth=r,i.push(c)}return i},e.getTagsWithOperations=function(e){for(var t={},n=0,r=e.tags||[];n<r.length;n++){t[(y=r[n]).name]=Object(o.__assign)(Object(o.__assign)({},y),{operations:[]})}for(var i=e.paths,a=0,s=Object.keys(i);a<s.length;a++)for(var l=s[a],c=i[l],u=0,p=Object.keys(c).filter(et);u<p.length;u++){var f=p[u],d=c[f],h=d.tags;h&&h.length||(h=[""]);for(var m=0,g=h;m<g.length;m++){var y,v=g[m];void 0===(y=t[v])&&(y={name:v,operations:[]},t[v]=y),y["x-traitTag"]||y.operations.push(Object(o.__assign)(Object(o.__assign)({},d),{pathName:l,pointer:Qe.compile(["paths",l,f]),httpVerb:f,pathParameters:c.parameters||[],pathServers:c.servers}))}}return t},e}(),tn=function(){function e(e,t,n){var r,o,i,a,s=this;this.scroll=t,this.history=n,this.activeItemIdx=-1,this.sideBarOpened=!1,this.updateOnScroll=function(e){for(var t=e?1:-1,n=s.activeItemIdx;(-1!==n||e)&&!(n>=s.flatItems.length-1&&e);){if(e){var r=s.getElementAtOrFirstChild(n+1);if(s.scroll.isElementBellow(r))break}else{r=s.getElementAt(n);if(s.scroll.isElementAbove(r))break}n+=t}s.activate(s.flatItems[n],!0,!0)},this.updateOnHistory=function(e){var t;(void 0===e&&(e=s.history.currentId),e)&&((t=s.flatItems.find((function(t){return t.id===e})))?s.activateAndScroll(t,!1):(e.startsWith(dt)&&(t=s.flatItems.find((function(e){return dt.startsWith(e.id)})),s.activate(t)),s.scroll.scrollIntoViewBySelector('[data-section-id="'+e+'"]')))},this.getItemById=function(e){return s.flatItems.find((function(t){return t.id===e}))},this.items=e.contentItems,this.flatItems=(r=this.items||[],o="items",i=[],(a=function(e){for(var t=0,n=e;t<n.length;t++){var r=n[t];i.push(r),r[o]&&a(r[o])}})(r),i),this.flatItems.forEach((function(e,t){return e.absoluteIdx=t})),this.subscribe()}return e.updateOnHistory=function(e,t){void 0===e&&(e=_t.currentId),e&&t.scrollIntoViewBySelector('[data-section-id="'+e+'"]')},e.prototype.subscribe=function(){this._unsubscribe=this.scroll.subscribe(this.updateOnScroll),this._hashUnsubscribe=this.history.subscribe(this.updateOnHistory)},e.prototype.toggleSidebar=function(){this.sideBarOpened=!this.sideBarOpened},e.prototype.closeSidebar=function(){this.sideBarOpened=!1},e.prototype.getElementAt=function(e){var t=this.flatItems[e];return t&&te('[data-section-id="'+t.id+'"]')||null},e.prototype.getElementAtOrFirstChild=function(e){var t=this.flatItems[e];return t&&"group"===t.type&&(t=t.items[0]),t&&te('[data-section-id="'+t.id+'"]')||null},Object.defineProperty(e.prototype,"activeItem",{get:function(){return this.flatItems[this.activeItemIdx]||void 0},enumerable:!0,configurable:!0}),e.prototype.activate=function(e,t,n){void 0===t&&(t=!0),void 0===n&&(n=!1),(this.activeItem&&this.activeItem.id)!==(e&&e.id)&&(e&&"group"===e.type||(this.deactivate(this.activeItem),e?e.depth<=0||(this.activeItemIdx=e.absoluteIdx,t&&this.history.replace(e.id,n),e.activate(),e.expand()):this.history.replace("",n)))},e.prototype.deactivate=function(e){if(void 0!==e)for(e.deactivate();void 0!==e;)e.collapse(),e=e.parent},e.prototype.activateAndScroll=function(e,t,n){var r=e&&this.getItemById(e.id)||e;this.activate(r,t,n),this.scrollToActive(),r&&r.items.length||this.closeSidebar()},e.prototype.scrollToActive=function(){this.scroll.scrollIntoView(this.getElementAt(this.activeItemIdx))},e.prototype.dispose=function(){this._unsubscribe(),this._hashUnsubscribe()},Object(o.__decorate)([ze.l],e.prototype,"activeItemIdx",void 0),Object(o.__decorate)([ze.l],e.prototype,"sideBarOpened",void 0),Object(o.__decorate)([ze.d],e.prototype,"toggleSidebar",null),Object(o.__decorate)([ze.d],e.prototype,"closeSidebar",null),Object(o.__decorate)([ze.d],e.prototype,"activate",null),Object(o.__decorate)([ze.d.bound],e.prototype,"activateAndScroll",null),e}(),nn=function(){function e(e){this.options=e,this._prevOffsetY=0,this._scrollParent=ee?window:void 0,this._emiter=new He,this.bind()}return e.prototype.bind=function(){this._prevOffsetY=this.scrollY(),this._scrollParent&&this._scrollParent.addEventListener("scroll",this.handleScroll)},e.prototype.dispose=function(){this._scrollParent&&this._scrollParent.removeEventListener("scroll",this.handleScroll),this._emiter.removeAllListeners("scroll")},e.prototype.scrollY=function(){return"undefined"!=typeof HTMLElement&&this._scrollParent instanceof HTMLElement?this._scrollParent.scrollTop:void 0!==this._scrollParent?this._scrollParent.pageYOffset:0},e.prototype.isElementBellow=function(e){if(null!==e)return e.getBoundingClientRect().top>this.options.scrollYOffset()},e.prototype.isElementAbove=function(e){if(null!==e){var t=e.getBoundingClientRect().top;return(t>0?Math.floor(t):Math.ceil(t))<=this.options.scrollYOffset()}},e.prototype.subscribe=function(e){var t=this._emiter.addListener("scroll",e);return function(){return t.removeListener("scroll",e)}},e.prototype.scrollIntoView=function(e){null!==e&&(e.scrollIntoView(),this._scrollParent&&this._scrollParent.scrollBy&&this._scrollParent.scrollBy(0,1-this.options.scrollYOffset()))},e.prototype.scrollIntoViewBySelector=function(e){var t=te(e);this.scrollIntoView(t)},e.prototype.handleScroll=function(){var e=this.scrollY()-this._prevOffsetY>0;this._prevOffsetY=this.scrollY(),this._emiter.emit("scroll",e)},Object(o.__decorate)([We.bind,vt(100)],e.prototype,"handleScroll",null),e}();var rn,on,an,sn,ln,cn,un,pn,fn,dn,hn,mn,gn,yn,vn,bn,xn,wn=function(){function e(){this.searchWorker=function(){var e;if(ee)try{e=n(315)}catch(t){e=n(150).default}else e=n(150).default;return new e}()}return e.prototype.indexItems=function(e){var t=this,n=function(e){e.forEach((function(e){"group"!==e.type&&t.add(e.name,e.description||"",e.id),n(e.items)}))};n(e),this.searchWorker.done()},e.prototype.add=function(e,t,n){this.searchWorker.add(e,t,n)},e.prototype.dispose=function(){this.searchWorker.terminate()},e.prototype.search=function(e){return this.searchWorker.search(e)},e.prototype.toJS=function(){return Object(o.__awaiter)(this,void 0,void 0,(function(){return Object(o.__generator)(this,(function(e){return[2,this.searchWorker.toJS()]}))}))},e.prototype.load=function(e){this.searchWorker.load(e)},e}(),kn=_e.div(on||(on=Object(o.__makeTemplateObject)(["\n width: calc(100% - ",");\n padding: 0 ","px;\n\n ",";\n"],["\n width: calc(100% - ",");\n padding: 0 ","px;\n\n ",";\n"])),(function(e){return e.theme.rightPanel.width}),(function(e){return e.theme.spacing.sectionHorizontal}),(function(e){var t=e.compact,n=e.theme;return Oe("medium",!0)(rn||(rn=Object(o.__makeTemplateObject)(["\n width: 100%;\n padding: ",";\n "],["\n width: 100%;\n padding: ",";\n "])),(t?0:n.spacing.sectionVertical)+"px "+n.spacing.sectionHorizontal+"px")})),On=_e.div.attrs((function(e){var t;return(t={})["data-section-id"]=e.id,t}))(sn||(sn=Object(o.__makeTemplateObject)(["\n padding: ","px 0;\n\n &:last-child {\n min-height: calc(100vh + 1px);\n }\n\n & > &:last-child {\n min-height: initial;\n }\n\n ","\n ","\n"],["\n padding: ","px 0;\n\n &:last-child {\n min-height: calc(100vh + 1px);\n }\n\n & > &:last-child {\n min-height: initial;\n }\n\n ","\n ","\n"])),(function(e){return e.theme.spacing.sectionVertical}),Oe("medium",!0)(an||(an=Object(o.__makeTemplateObject)(["\n padding: 0;\n "],["\n padding: 0;\n "]))),(function(e){return e.underlined?"\n position: relative;\n\n &:not(:last-of-type):after {\n position: absolute;\n bottom: 0;\n width: 100%;\n display: block;\n content: '';\n border-bottom: 1px solid rgba(0, 0, 0, 0.2);\n }\n ":""})),_n=_e.div(cn||(cn=Object(o.__makeTemplateObject)(["\n width: ",";\n color: ",";\n background-color: ",";\n padding: 0 ","px;\n\n ",";\n"],["\n width: ",";\n color: ",";\n background-color: ",";\n padding: 0 ","px;\n\n ",";\n"])),(function(e){return e.theme.rightPanel.width}),(function(e){return e.theme.rightPanel.textColor}),(function(e){return e.theme.rightPanel.backgroundColor}),(function(e){return e.theme.spacing.sectionHorizontal}),Oe("medium",!0)(ln||(ln=Object(o.__makeTemplateObject)(["\n width: 100%;\n padding: ",";\n "],["\n width: 100%;\n padding: ",";\n "])),(function(e){return e.theme.spacing.sectionVertical+"px "+e.theme.spacing.sectionHorizontal+"px"}))),En=_e(_n)(un||(un=Object(o.__makeTemplateObject)(["\n background-color: ",";\n"],["\n background-color: ",";\n"])),(function(e){return e.theme.rightPanel.backgroundColor})),Sn=_e.div(fn||(fn=Object(o.__makeTemplateObject)(["\n display: flex;\n width: 100%;\n padding: 0;\n\n ",";\n"],["\n display: flex;\n width: 100%;\n padding: 0;\n\n ",";\n"])),Oe("medium",!0)(pn||(pn=Object(o.__makeTemplateObject)(["\n flex-direction: column;\n "],["\n flex-direction: column;\n "])))),Tn={1:"1.85714em",2:"1.57143em",3:"1.27em"},jn=function(e){return be(dn||(dn=Object(o.__makeTemplateObject)(["\n font-family: ",";\n font-weight: ",";\n font-size: ",";\n line-height: ",";\n"],["\n font-family: ",";\n font-weight: ",";\n font-size: ",";\n line-height: ",";\n"])),(function(e){return e.theme.typography.headings.fontFamily}),(function(e){return e.theme.typography.headings.fontWeight}),Tn[e],(function(e){return e.theme.typography.headings.lineHeight}))},Cn=_e.h1(hn||(hn=Object(o.__makeTemplateObject)(["\n ",";\n color: ",";\n\n ",";\n"],["\n ",";\n color: ",";\n\n ",";\n"])),jn(1),(function(e){return e.theme.colors.primary.main}),Ee("H1")),In=_e.h2(mn||(mn=Object(o.__makeTemplateObject)(["\n ",";\n color: black;\n\n ",";\n"],["\n ",";\n color: black;\n\n ",";\n"])),jn(2),Ee("H2")),An=(_e.h2(gn||(gn=Object(o.__makeTemplateObject)(["\n ",";\n color: black;\n\n ",";\n"],["\n ",";\n color: black;\n\n ",";\n"])),jn(3),Ee("H3")),_e.h3(yn||(yn=Object(o.__makeTemplateObject)(["\n color: ",";\n\n ",";\n"],["\n color: ",";\n\n ",";\n"])),(function(e){return e.theme.rightPanel.textColor}),Ee("RightPanelHeader"))),Pn=_e.h5(vn||(vn=Object(o.__makeTemplateObject)(["\n border-bottom: 1px solid rgba(38, 50, 56, 0.3);\n margin: 1em 0 1em 0;\n color: rgba(38, 50, 56, 0.5);\n font-weight: normal;\n text-transform: uppercase;\n font-size: 0.929em;\n line-height: 20px;\n\n ",";\n"],["\n border-bottom: 1px solid rgba(38, 50, 56, 0.3);\n margin: 1em 0 1em 0;\n color: rgba(38, 50, 56, 0.5);\n font-weight: normal;\n text-transform: uppercase;\n font-size: 0.929em;\n line-height: 20px;\n\n ",";\n"])),Ee("UnderlinedHeader")),Rn=n(157),Nn=Object(i.createContext)(void 0),Ln=Nn.Provider,Mn=Nn.Consumer,Dn=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.state={loading:!0,resolvedSpec:null},t}return Object(o.__extends)(t,e),t.getDerivedStateFromProps=function(e,t){return e.specUrl!==t.prevSpecUrl||e.spec!==t.prevSpec?{loading:!0,resolvedSpec:null,prevSpec:e.spec,prevSpecUrl:e.specUrl}:null},t.prototype.makeStore=function(e,t,n){if(e)try{return new ts(e,t,n)}catch(e){throw this.props.onLoaded&&this.props.onLoaded(e),e}},t.prototype.componentDidMount=function(){this.load()},t.prototype.componentDidUpdate=function(){null===this.state.resolvedSpec?this.load():!this.state.loading&&this.props.onLoaded&&this.props.onLoaded()},t.prototype.load=function(){return Object(o.__awaiter)(this,void 0,void 0,(function(){var e,t,n,r,i;return Object(o.__generator)(this,(function(o){switch(o.label){case 0:e=this.props,t=e.specUrl,n=e.spec,o.label=1;case 1:return o.trys.push([1,3,,4]),[4,$e(n||t)];case 2:return r=o.sent(),this.setState({resolvedSpec:r,loading:!1}),[3,4];case 3:return i=o.sent(),this.props.onLoaded&&this.props.onLoaded(i),this.setState({error:i}),[3,4];case 4:return[2]}}))}))},t.prototype.render=function(){if(this.state.error)throw this.state.error;var e=this.props,t=e.specUrl,n=e.options,r=this.state,o=r.loading,i=r.resolvedSpec;return this.props.children({loading:o,store:this.makeStore(i,t,n)})},Object(o.__decorate)([Rn],t.prototype,"makeStore",null),t}(i.Component),Fn=function(e){return be(bn||(bn=Object(o.__makeTemplateObject)(["\n "," {\n cursor: pointer;\n margin-left: -20px;\n padding: 0;\n line-height: 1;\n width: 20px;\n display: inline-block;\n }\n ",":before {\n content: '';\n width: 15px;\n height: 15px;\n background-size: contain;\n background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMCIgeT0iMCIgd2lkdGg9IjUxMiIgaGVpZ2h0PSI1MTIiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIj48cGF0aCBmaWxsPSIjMDEwMTAxIiBkPSJNNDU5LjcgMjMzLjRsLTkwLjUgOTAuNWMtNTAgNTAtMTMxIDUwLTE4MSAwIC03LjktNy44LTE0LTE2LjctMTkuNC0yNS44bDQyLjEtNDIuMWMyLTIgNC41LTMuMiA2LjgtNC41IDIuOSA5LjkgOCAxOS4zIDE1LjggMjcuMiAyNSAyNSA2NS42IDI0LjkgOTAuNSAwbDkwLjUtOTAuNWMyNS0yNSAyNS02NS42IDAtOTAuNSAtMjQuOS0yNS02NS41LTI1LTkwLjUgMGwtMzIuMiAzMi4yYy0yNi4xLTEwLjItNTQuMi0xMi45LTgxLjYtOC45bDY4LjYtNjguNmM1MC01MCAxMzEtNTAgMTgxIDBDNTA5LjYgMTAyLjMgNTA5LjYgMTgzLjQgNDU5LjcgMjMzLjR6TTIyMC4zIDM4Mi4ybC0zMi4yIDMyLjJjLTI1IDI0LjktNjUuNiAyNC45LTkwLjUgMCAtMjUtMjUtMjUtNjUuNiAwLTkwLjVsOTAuNS05MC41YzI1LTI1IDY1LjUtMjUgOTAuNSAwIDcuOCA3LjggMTIuOSAxNy4yIDE1LjggMjcuMSAyLjQtMS40IDQuOC0yLjUgNi44LTQuNWw0Mi4xLTQyYy01LjQtOS4yLTExLjYtMTgtMTkuNC0yNS44IC01MC01MC0xMzEtNTAtMTgxIDBsLTkwLjUgOTAuNWMtNTAgNTAtNTAgMTMxIDAgMTgxIDUwIDUwIDEzMSA1MCAxODEgMGw2OC42LTY4LjZDMjc0LjYgMzk1LjEgMjQ2LjQgMzkyLjMgMjIwLjMgMzgyLjJ6Ii8+PC9zdmc+Cg==');\n opacity: 0.5;\n visibility: hidden;\n display: inline-block;\n vertical-align: middle;\n }\n\n h1:hover > ","::before, h2:hover > ","::before, ",":hover::before {\n visibility: visible;\n }\n"],["\n "," {\n cursor: pointer;\n margin-left: -20px;\n padding: 0;\n line-height: 1;\n width: 20px;\n display: inline-block;\n }\n ",":before {\n content: '';\n width: 15px;\n height: 15px;\n background-size: contain;\n background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMCIgeT0iMCIgd2lkdGg9IjUxMiIgaGVpZ2h0PSI1MTIiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIj48cGF0aCBmaWxsPSIjMDEwMTAxIiBkPSJNNDU5LjcgMjMzLjRsLTkwLjUgOTAuNWMtNTAgNTAtMTMxIDUwLTE4MSAwIC03LjktNy44LTE0LTE2LjctMTkuNC0yNS44bDQyLjEtNDIuMWMyLTIgNC41LTMuMiA2LjgtNC41IDIuOSA5LjkgOCAxOS4zIDE1LjggMjcuMiAyNSAyNSA2NS42IDI0LjkgOTAuNSAwbDkwLjUtOTAuNWMyNS0yNSAyNS02NS42IDAtOTAuNSAtMjQuOS0yNS02NS41LTI1LTkwLjUgMGwtMzIuMiAzMi4yYy0yNi4xLTEwLjItNTQuMi0xMi45LTgxLjYtOC45bDY4LjYtNjguNmM1MC01MCAxMzEtNTAgMTgxIDBDNTA5LjYgMTAyLjMgNTA5LjYgMTgzLjQgNDU5LjcgMjMzLjR6TTIyMC4zIDM4Mi4ybC0zMi4yIDMyLjJjLTI1IDI0LjktNjUuNiAyNC45LTkwLjUgMCAtMjUtMjUtMjUtNjUuNiAwLTkwLjVsOTAuNS05MC41YzI1LTI1IDY1LjUtMjUgOTAuNSAwIDcuOCA3LjggMTIuOSAxNy4yIDE1LjggMjcuMSAyLjQtMS40IDQuOC0yLjUgNi44LTQuNWw0Mi4xLTQyYy01LjQtOS4yLTExLjYtMTgtMTkuNC0yNS44IC01MC01MC0xMzEtNTAtMTgxIDBsLTkwLjUgOTAuNWMtNTAgNTAtNTAgMTMxIDAgMTgxIDUwIDUwIDEzMSA1MCAxODEgMGw2OC42LTY4LjZDMjc0LjYgMzk1LjEgMjQ2LjQgMzkyLjMgMjIwLjMgMzgyLjJ6Ii8+PC9zdmc+Cg==');\n opacity: 0.5;\n visibility: hidden;\n display: inline-block;\n vertical-align: middle;\n }\n\n h1:hover > ","::before, h2:hover > ","::before, ",":hover::before {\n visibility: visible;\n }\n"])),e,e,e,e,e)},zn=function(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)},Un=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.navigate=function(e,n){n.defaultPrevented||0!==n.button||zn(n)||(n.preventDefault(),e.replace(t.props.to))},t}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this;return i.createElement(Mn,null,(function(t){return i.createElement("a",{className:e.props.className,href:t.menu.history.linkForId(e.props.to),onClick:e.navigate.bind(e,t.menu.history)},e.props.children)}))},t}(i.Component),Bn=_e(Un)(xn||(xn=Object(o.__makeTemplateObject)(["\n ",";\n"],["\n ",";\n"])),Fn("&"));function $n(e){return i.createElement(Bn,{to:e.to})}var qn,Wn,Hn,Vn,Yn,Qn,Gn,Xn,Kn,Zn,Jn,er,tr,nr,rr,or,ir,ar,sr,lr={left:"90deg",right:"-90deg",up:"-180deg",down:"0"},cr=_e(function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){return i.createElement("svg",{className:this.props.className,style:this.props.style,version:"1.1",viewBox:"0 0 24 24",x:"0",xmlns:"http://www.w3.org/2000/svg",y:"0"},i.createElement("polygon",{points:"17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "}))},t}(i.PureComponent))(qn||(qn=Object(o.__makeTemplateObject)(["\n height: ",";\n width: ",";\n vertical-align: middle;\n float: ",";\n transition: transform 0.2s ease-out;\n transform: rotateZ(",");\n\n polygon {\n fill: ",";\n }\n"],["\n height: ",";\n width: ",";\n vertical-align: middle;\n float: ",";\n transition: transform 0.2s ease-out;\n transform: rotateZ(",");\n\n polygon {\n fill: ",";\n }\n"])),(function(e){return e.size||"18px"}),(function(e){return e.size||"18px"}),(function(e){return e.float||""}),(function(e){return lr[e.direction||"down"]}),(function(e){return e.color&&e.theme.colors[e.color]&&e.theme.colors[e.color].main||e.color})),ur=_e.span(Wn||(Wn=Object(o.__makeTemplateObject)(["\n display: inline-block;\n padding: 0 5px;\n margin: 0;\n background-color: ",";\n color: ",";\n font-size: ",";\n vertical-align: text-top;\n"],["\n display: inline-block;\n padding: 0 5px;\n margin: 0;\n background-color: ",";\n color: ",";\n font-size: ",";\n vertical-align: text-top;\n"])),(function(e){return e.theme.colors[e.type].main}),(function(e){return e.theme.colors[e.type].contrastText}),(function(e){return e.theme.typography.code.fontSize})),pr=be(Hn||(Hn=Object(o.__makeTemplateObject)(["\n text-decoration: line-through;\n color: #bdccd3;\n"],["\n text-decoration: line-through;\n color: #bdccd3;\n"]))),fr=_e.caption(Vn||(Vn=Object(o.__makeTemplateObject)(["\n text-align: right;\n font-size: 0.9em;\n font-weight: normal;\n color: ",";\n"],["\n text-align: right;\n font-size: 0.9em;\n font-weight: normal;\n color: ",";\n"])),(function(e){return e.theme.colors.text.secondary})),dr=_e.td(Yn||(Yn=Object(o.__makeTemplateObject)(["\n border-left: 1px solid ",";\n box-sizing: border-box;\n position: relative;\n padding: 10px 10px 10px 0;\n\n tr:first-of-type > &,\n tr.last > & {\n border-left-width: 0;\n background-position: top left;\n background-repeat: no-repeat;\n background-size: 1px 100%;\n }\n\n tr:first-of-type > & {\n background-image: linear-gradient(\n to bottom,\n transparent 0%,\n transparent 22px,\n "," 22px,\n "," 100%\n );\n }\n\n tr.last > & {\n background-image: linear-gradient(\n to bottom,\n "," 0%,\n "," 22px,\n transparent 22px,\n transparent 100%\n );\n }\n\n tr.last + tr > & {\n border-left-color: transparent;\n }\n\n tr.last:first-child > & {\n background: none;\n border-left-color: transparent;\n }\n"],["\n border-left: 1px solid ",";\n box-sizing: border-box;\n position: relative;\n padding: 10px 10px 10px 0;\n\n tr:first-of-type > &,\n tr.last > & {\n border-left-width: 0;\n background-position: top left;\n background-repeat: no-repeat;\n background-size: 1px 100%;\n }\n\n tr:first-of-type > & {\n background-image: linear-gradient(\n to bottom,\n transparent 0%,\n transparent 22px,\n "," 22px,\n "," 100%\n );\n }\n\n tr.last > & {\n background-image: linear-gradient(\n to bottom,\n "," 0%,\n "," 22px,\n transparent 22px,\n transparent 100%\n );\n }\n\n tr.last + tr > & {\n border-left-color: transparent;\n }\n\n tr.last:first-child > & {\n background: none;\n border-left-color: transparent;\n }\n"])),(function(e){return e.theme.schema.linesColor}),(function(e){return e.theme.schema.linesColor}),(function(e){return e.theme.schema.linesColor}),(function(e){return e.theme.schema.linesColor}),(function(e){return e.theme.schema.linesColor})),hr=_e(dr)(Qn||(Qn=Object(o.__makeTemplateObject)(["\n padding: 0;\n"],["\n padding: 0;\n"]))),mr=_e(dr)(Gn||(Gn=Object(o.__makeTemplateObject)(["\n vertical-align: top;\n line-height: 20px;\n white-space: nowrap;\n font-size: 0.929em;\n font-family: ",";\n\n &.deprecated {\n ",";\n }\n\n ",";\n\n ",";\n"],["\n vertical-align: top;\n line-height: 20px;\n white-space: nowrap;\n font-size: 0.929em;\n font-family: ",";\n\n &.deprecated {\n ",";\n }\n\n ",";\n\n ",";\n"])),(function(e){return e.theme.typography.code.fontFamily}),pr,(function(e){return"field"!==e.kind?"font-style: italic":""}),Ee("PropertyNameCell")),gr=_e.td(Xn||(Xn=Object(o.__makeTemplateObject)(["\n border-bottom: 1px solid #9fb4be;\n padding: 10px 0;\n width: ",";\n box-sizing: border-box;\n\n tr.expanded & {\n border-bottom: none;\n }\n"],["\n border-bottom: 1px solid #9fb4be;\n padding: 10px 0;\n width: ",";\n box-sizing: border-box;\n\n tr.expanded & {\n border-bottom: none;\n }\n"])),(function(e){return e.theme.schema.defaultDetailsWidth})),yr=_e.span(Kn||(Kn=Object(o.__makeTemplateObject)(["\n color: ",";\n font-family: ",";\n margin-right: 10px;\n\n &::before {\n content: '';\n display: inline-block;\n vertical-align: middle;\n width: 10px;\n height: 1px;\n background: ",";\n }\n\n &::after {\n content: '';\n display: inline-block;\n vertical-align: middle;\n width: 1px;\n background: ",";\n height: 7px;\n }\n"],["\n color: ",";\n font-family: ",";\n margin-right: 10px;\n\n &::before {\n content: '';\n display: inline-block;\n vertical-align: middle;\n width: 10px;\n height: 1px;\n background: ",";\n }\n\n &::after {\n content: '';\n display: inline-block;\n vertical-align: middle;\n width: 1px;\n background: ",";\n height: 7px;\n }\n"])),(function(e){return e.theme.schema.linesColor}),(function(e){return e.theme.typography.code.fontFamily}),(function(e){return e.theme.schema.linesColor}),(function(e){return e.theme.schema.linesColor})),vr=_e.div(Zn||(Zn=Object(o.__makeTemplateObject)(["\n padding: ",";\n"],["\n padding: ",";\n"])),(function(e){return e.theme.schema.nestingSpacing})),br=_e.table(Jn||(Jn=Object(o.__makeTemplateObject)(["\n border-collapse: separate;\n border-radius: 3px;\n font-size: ",";\n\n border-spacing: 0;\n width: 100%;\n\n > tr {\n vertical-align: middle;\n }\n\n &\n ",",\n &\n ","\n ","\n ",",\n &\n ","\n ","\n ","\n ","\n "," {\n margin: ",";\n margin-right: 0;\n background: ",";\n }\n\n &\n ","\n ",",\n &\n ","\n ","\n ","\n ",",\n &\n ","\n ","\n ","\n ","\n ","\n "," {\n background: #ffffff;\n }\n"],["\n border-collapse: separate;\n border-radius: 3px;\n font-size: ",";\n\n border-spacing: 0;\n width: 100%;\n\n > tr {\n vertical-align: middle;\n }\n\n &\n ",",\n &\n ","\n ","\n ",",\n &\n ","\n ","\n ","\n ","\n "," {\n margin: ",";\n margin-right: 0;\n background: ",";\n }\n\n &\n ","\n ",",\n &\n ","\n ","\n ","\n ",",\n &\n ","\n ","\n ","\n ","\n ","\n "," {\n background: #ffffff;\n }\n"])),(function(e){return e.theme.typography.fontSize}),vr,vr,vr,vr,vr,vr,vr,vr,vr,(function(e){return e.theme.schema.nestingSpacing}),(function(e){return e.theme.schema.nestedBackground}),vr,vr,vr,vr,vr,vr,vr,vr,vr,vr,vr,vr),xr=_e.ul(er||(er=Object(o.__makeTemplateObject)(["\n margin: 0 0 3px 0;\n padding: 0;\n list-style: none;\n display: inline-block;\n"],["\n margin: 0 0 3px 0;\n padding: 0;\n list-style: none;\n display: inline-block;\n"]))),wr=_e.span(tr||(tr=Object(o.__makeTemplateObject)(["\n font-size: 0.9em;\n margin-right: 10px;\n color: ",";\n font-family: ",";\n}\n"],["\n font-size: 0.9em;\n margin-right: 10px;\n color: ",";\n font-family: ",";\n}\n"])),(function(e){return e.theme.colors.primary.main}),(function(e){return e.theme.typography.headings.fontFamily})),kr=_e.li(nr||(nr=Object(o.__makeTemplateObject)(["\n display: inline-block;\n margin-right: 10px;\n margin-bottom: 5px;\n font-size: 0.8em;\n cursor: pointer;\n border: 1px solid ",";\n padding: 2px 10px;\n\n ","\n"],["\n display: inline-block;\n margin-right: 10px;\n margin-bottom: 5px;\n font-size: 0.8em;\n cursor: pointer;\n border: 1px solid ",";\n padding: 2px 10px;\n\n ","\n"])),(function(e){return e.theme.colors.primary.main}),(function(e){return e.active?"\n color: white;\n background-color: "+e.theme.colors.primary.main+";\n ":"\n color: "+e.theme.colors.primary.main+";\n background-color: white;\n "})),Or=_e.div(rr||(rr=Object(o.__makeTemplateObject)(["\n font-size: 0.9em;\n font-family: ",";\n &::after {\n content: ' [';\n }\n"],["\n font-size: 0.9em;\n font-family: ",";\n &::after {\n content: ' [';\n }\n"])),(function(e){return e.theme.typography.code.fontFamily})),_r=_e.div(or||(or=Object(o.__makeTemplateObject)(["\n font-size: 0.9em;\n font-family: ",";\n &::after {\n content: ']';\n }\n"],["\n font-size: 0.9em;\n font-family: ",";\n &::after {\n content: ']';\n }\n"])),(function(e){return e.theme.typography.code.fontFamily})),Er=n(158),Sr=_e(n.n(Er).a)(ir||(ir=Object(o.__makeTemplateObject)(["\n min-width: 100px;\n display: inline-block;\n position: relative;\n width: auto;\n font-family: ",";\n\n .Dropdown-control {\n font-family: ",";\n position: relative;\n font-size: 0.929em;\n width: 100%;\n line-height: 1.5em;\n vertical-align: middle;\n cursor: pointer;\n border-color: rgba(38, 50, 56, 0.5);\n color: #263238;\n outline: none;\n padding: 0.15em 1.5em 0.2em 0.5em;\n border-radius: 2px;\n border-width: 1px;\n border-style: solid;\n margin-top: 5px;\n background: white;\n\n box-sizing: border-box;\n\n &:hover {\n border-color: ",";\n color: ",";\n box-shadow: 0px 2px 4px 0px rgba(34, 36, 38, 0.12);\n }\n }\n\n .Dropdown-arrow {\n border-color: "," transparent transparent;\n border-style: solid;\n border-width: 0.35em 0.35em 0;\n content: ' ';\n display: block;\n height: 0;\n position: absolute;\n right: 0.3em;\n top: 50%;\n margin-top: -0.125em;\n width: 0;\n }\n\n .Dropdown-menu {\n position: absolute;\n margin-top: 2px;\n left: 0;\n right: 0;\n\n z-index: 10;\n min-width: 100px;\n\n background: white;\n border: 1px solid rgba(38, 50, 56, 0.2);\n box-shadow: 0px 2px 4px 0px rgba(34, 36, 38, 0.12), 0px 2px 10px 0px rgba(34, 36, 38, 0.08);\n\n max-height: 220px;\n overflow: auto;\n }\n\n .Dropdown-option {\n font-size: 0.9em;\n color: #263238;\n cursor: pointer;\n padding: 0.4em;\n\n &.is-selected {\n background-color: rgba(0, 0, 0, 0.05);\n }\n\n &:hover {\n background-color: rgba(38, 50, 56, 0.12);\n }\n }\n"],["\n min-width: 100px;\n display: inline-block;\n position: relative;\n width: auto;\n font-family: ",";\n\n .Dropdown-control {\n font-family: ",";\n position: relative;\n font-size: 0.929em;\n width: 100%;\n line-height: 1.5em;\n vertical-align: middle;\n cursor: pointer;\n border-color: rgba(38, 50, 56, 0.5);\n color: #263238;\n outline: none;\n padding: 0.15em 1.5em 0.2em 0.5em;\n border-radius: 2px;\n border-width: 1px;\n border-style: solid;\n margin-top: 5px;\n background: white;\n\n box-sizing: border-box;\n\n &:hover {\n border-color: ",";\n color: ",";\n box-shadow: 0px 2px 4px 0px rgba(34, 36, 38, 0.12);\n }\n }\n\n .Dropdown-arrow {\n border-color: "," transparent transparent;\n border-style: solid;\n border-width: 0.35em 0.35em 0;\n content: ' ';\n display: block;\n height: 0;\n position: absolute;\n right: 0.3em;\n top: 50%;\n margin-top: -0.125em;\n width: 0;\n }\n\n .Dropdown-menu {\n position: absolute;\n margin-top: 2px;\n left: 0;\n right: 0;\n\n z-index: 10;\n min-width: 100px;\n\n background: white;\n border: 1px solid rgba(38, 50, 56, 0.2);\n box-shadow: 0px 2px 4px 0px rgba(34, 36, 38, 0.12), 0px 2px 10px 0px rgba(34, 36, 38, 0.08);\n\n max-height: 220px;\n overflow: auto;\n }\n\n .Dropdown-option {\n font-size: 0.9em;\n color: #263238;\n cursor: pointer;\n padding: 0.4em;\n\n &.is-selected {\n background-color: rgba(0, 0, 0, 0.05);\n }\n\n &:hover {\n background-color: rgba(38, 50, 56, 0.12);\n }\n }\n"])),(function(e){return e.theme.typography.headings.fontFamily}),(function(e){return e.theme.typography.headings.fontFamily}),(function(e){return e.theme.colors.primary.main}),(function(e){return e.theme.colors.primary.main}),(function(e){return e.theme.colors.primary.main})),Tr=_e(Sr)(ar||(ar=Object(o.__makeTemplateObject)(["\n margin-left: 10px;\n text-transform: none;\n font-size: 0.969em;\n\n .Dropdown-control {\n font-size: 1em;\n border: none;\n padding: 0 1.2em 0 0;\n background: transparent;\n\n &:hover {\n color: ",";\n box-shadow: none;\n }\n }\n"],["\n margin-left: 10px;\n text-transform: none;\n font-size: 0.969em;\n\n .Dropdown-control {\n font-size: 1em;\n border: none;\n padding: 0 1.2em 0 0;\n background: transparent;\n\n &:hover {\n color: ",";\n box-shadow: none;\n }\n }\n"])),(function(e){return e.theme.colors.primary.main})),jr=_e.span(sr||(sr=Object(o.__makeTemplateObject)(["\n margin-left: 10px;\n text-transform: none;\n font-size: 0.929em;\n color: black;\n"],["\n margin-left: 10px;\n text-transform: none;\n font-size: 0.929em;\n color: black;\n"])));function Cr(e){return function(t){return!!t.type&&t.type.tabsRole===e}}var Ir=Cr("Tab"),Ar=Cr("TabList"),Pr=Cr("TabPanel");function Rr(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function Nr(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function Lr(e,t){return i.Children.map(e,(function(e){return null===e?null:function(e){return Ir(e)||Ar(e)||Pr(e)}(e)?t(e):e.props&&e.props.children&&"object"==typeof e.props.children?Object(i.cloneElement)(e,function(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?Rr(Object(n),!0).forEach((function(t){Nr(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):Rr(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}({},e.props,{children:Lr(e.props.children,t)})):e}))}function Mr(e,t){return i.Children.forEach(e,(function(e){null!==e&&(Ir(e)||Pr(e)?t(e):e.props&&e.props.children&&"object"==typeof e.props.children&&(Ar(e)&&t(e),Mr(e.props.children,t)))}))}var Dr,Fr=n(20),zr=n.n(Fr),Ur=0;function Br(){return"react-tabs-"+Ur++}function $r(e){var t=0;return Mr(e,(function(e){Ir(e)&&t++})),t}function qr(){return(qr=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}function Wr(e){return e&&"getAttribute"in e}function Hr(e){return Wr(e)&&"tab"===e.getAttribute("role")}function Vr(e){return Wr(e)&&"true"===e.getAttribute("aria-disabled")}try{Dr=!("undefined"==typeof window||!window.document||!window.document.activeElement)}catch(e){Dr=!1}var Yr=function(e){var t,n;function r(){for(var t,n=arguments.length,r=new Array(n),o=0;o<n;o++)r[o]=arguments[o];return(t=e.call.apply(e,[this].concat(r))||this).tabNodes=[],t.handleKeyDown=function(e){var n=t.props.direction;if(t.isTabFromContainer(e.target)){var r=t.props.selectedIndex,o=!1,i=!1;32!==e.keyCode&&13!==e.keyCode||(o=!0,i=!1,t.handleClick(e)),37===e.keyCode||38===e.keyCode?(r="rtl"===n?t.getNextTab(r):t.getPrevTab(r),o=!0,i=!0):39===e.keyCode||40===e.keyCode?(r="rtl"===n?t.getPrevTab(r):t.getNextTab(r),o=!0,i=!0):35===e.keyCode?(r=t.getLastTab(),o=!0,i=!0):36===e.keyCode&&(r=t.getFirstTab(),o=!0,i=!0),o&&e.preventDefault(),i&&t.setSelected(r,e)}},t.handleClick=function(e){var n=e.target;do{if(t.isTabFromContainer(n)){if(Vr(n))return;var r=[].slice.call(n.parentNode.children).filter(Hr).indexOf(n);return void t.setSelected(r,e)}}while(null!=(n=n.parentNode))},t}n=e,(t=r).prototype=Object.create(n.prototype),t.prototype.constructor=t,t.__proto__=n;var o=r.prototype;return o.setSelected=function(e,t){if(!(e<0||e>=this.getTabsCount())){var n=this.props;(0,n.onSelect)(e,n.selectedIndex,t)}},o.getNextTab=function(e){for(var t=this.getTabsCount(),n=e+1;n<t;n++)if(!Vr(this.getTab(n)))return n;for(var r=0;r<e;r++)if(!Vr(this.getTab(r)))return r;return e},o.getPrevTab=function(e){for(var t=e;t--;)if(!Vr(this.getTab(t)))return t;for(t=this.getTabsCount();t-- >e;)if(!Vr(this.getTab(t)))return t;return e},o.getFirstTab=function(){for(var e=this.getTabsCount(),t=0;t<e;t++)if(!Vr(this.getTab(t)))return t;return null},o.getLastTab=function(){for(var e=this.getTabsCount();e--;)if(!Vr(this.getTab(e)))return e;return null},o.getTabsCount=function(){return $r(this.props.children)},o.getPanelsCount=function(){return function(e){var t=0;return Mr(e,(function(e){Pr(e)&&t++})),t}(this.props.children)},o.getTab=function(e){return this.tabNodes["tabs-"+e]},o.getChildren=function(){var e=this,t=0,n=this.props,r=n.children,o=n.disabledTabClassName,s=n.focus,l=n.forceRenderTabPanel,c=n.selectedIndex,u=n.selectedTabClassName,p=n.selectedTabPanelClassName;this.tabIds=this.tabIds||[],this.panelIds=this.panelIds||[];for(var f=this.tabIds.length-this.getTabsCount();f++<0;)this.tabIds.push(Br()),this.panelIds.push(Br());return Lr(r,(function(n){var r=n;if(Ar(n)){var f=0,d=!1;Dr&&(d=a.a.Children.toArray(n.props.children).filter(Ir).some((function(t,n){return document.activeElement===e.getTab(n)}))),r=Object(i.cloneElement)(n,{children:Lr(n.props.children,(function(t){var n="tabs-"+f,r=c===f,a={tabRef:function(t){e.tabNodes[n]=t},id:e.tabIds[f],panelId:e.panelIds[f],selected:r,focus:r&&(s||d)};return u&&(a.selectedClassName=u),o&&(a.disabledClassName=o),f++,Object(i.cloneElement)(t,a)}))})}else if(Pr(n)){var h={id:e.panelIds[t],tabId:e.tabIds[t],selected:c===t};l&&(h.forceRender=l),p&&(h.selectedClassName=p),t++,r=Object(i.cloneElement)(n,h)}return r}))},o.isTabFromContainer=function(e){if(!Hr(e))return!1;var t=e.parentElement;do{if(t===this.node)return!0;if(t.getAttribute("data-tabs"))break;t=t.parentElement}while(t);return!1},o.render=function(){var e=this,t=this.props,n=(t.children,t.className),r=(t.disabledTabClassName,t.domRef),o=(t.focus,t.forceRenderTabPanel,t.onSelect,t.selectedIndex,t.selectedTabClassName,t.selectedTabPanelClassName,function(e,t){if(null==e)return{};var n,r,o={},i=Object.keys(e);for(r=0;r<i.length;r++)n=i[r],t.indexOf(n)>=0||(o[n]=e[n]);return o}(t,["children","className","disabledTabClassName","domRef","focus","forceRenderTabPanel","onSelect","selectedIndex","selectedTabClassName","selectedTabPanelClassName"]));return a.a.createElement("div",qr({},o,{className:zr()(n),onClick:this.handleClick,onKeyDown:this.handleKeyDown,ref:function(t){e.node=t,r&&r(t)},"data-tabs":!0}),this.getChildren())},r}(i.Component);Yr.defaultProps={className:"react-tabs",focus:!1},Yr.propTypes={};var Qr=function(e){var t,n;function r(t){var n;return(n=e.call(this,t)||this).handleSelected=function(e,t,r){var o=n.props.onSelect,i=n.state.mode;if("function"!=typeof o||!1!==o(e,t,r)){var a={focus:"keydown"===r.type};1===i&&(a.selectedIndex=e),n.setState(a)}},n.state=r.copyPropsToState(n.props,{},t.defaultFocus),n}return n=e,(t=r).prototype=Object.create(n.prototype),t.prototype.constructor=t,t.__proto__=n,r.getDerivedStateFromProps=function(e,t){return r.copyPropsToState(e,t)},r.getModeFromProps=function(e){return null===e.selectedIndex?1:0},r.copyPropsToState=function(e,t,n){void 0===n&&(n=!1);var o={focus:n,mode:r.getModeFromProps(e)};if(1===o.mode){var i=$r(e.children)-1,a=null;a=null!=t.selectedIndex?Math.min(t.selectedIndex,i):e.defaultIndex||0,o.selectedIndex=a}return o},r.prototype.render=function(){var e=this.props,t=e.children,n=(e.defaultIndex,e.defaultFocus,function(e,t){if(null==e)return{};var n,r,o={},i=Object.keys(e);for(r=0;r<i.length;r++)n=i[r],t.indexOf(n)>=0||(o[n]=e[n]);return o}(e,["children","defaultIndex","defaultFocus"])),r=this.state,o=r.focus,i=r.selectedIndex;return n.focus=o,n.onSelect=this.handleSelected,null!=i&&(n.selectedIndex=i),a.a.createElement(Yr,n,t)},r}(i.Component);function Gr(){return(Gr=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}Qr.defaultProps={defaultFocus:!1,forceRenderTabPanel:!1,selectedIndex:null,defaultIndex:null},Qr.propTypes={},Qr.tabsRole="Tabs";var Xr=function(e){var t,n;function r(){return e.apply(this,arguments)||this}return n=e,(t=r).prototype=Object.create(n.prototype),t.prototype.constructor=t,t.__proto__=n,r.prototype.render=function(){var e=this.props,t=e.children,n=e.className,r=function(e,t){if(null==e)return{};var n,r,o={},i=Object.keys(e);for(r=0;r<i.length;r++)n=i[r],t.indexOf(n)>=0||(o[n]=e[n]);return o}(e,["children","className"]);return a.a.createElement("ul",Gr({},r,{className:zr()(n),role:"tablist"}),t)},r}(i.Component);function Kr(){return(Kr=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}Xr.defaultProps={className:"react-tabs__tab-list"},Xr.propTypes={},Xr.tabsRole="TabList";var Zr=function(e){var t,n;function r(){return e.apply(this,arguments)||this}n=e,(t=r).prototype=Object.create(n.prototype),t.prototype.constructor=t,t.__proto__=n;var o=r.prototype;return o.componentDidMount=function(){this.checkFocus()},o.componentDidUpdate=function(){this.checkFocus()},o.checkFocus=function(){var e=this.props,t=e.selected,n=e.focus;t&&n&&this.node.focus()},o.render=function(){var e,t=this,n=this.props,r=n.children,o=n.className,i=n.disabled,s=n.disabledClassName,l=(n.focus,n.id),c=n.panelId,u=n.selected,p=n.selectedClassName,f=n.tabIndex,d=n.tabRef,h=function(e,t){if(null==e)return{};var n,r,o={},i=Object.keys(e);for(r=0;r<i.length;r++)n=i[r],t.indexOf(n)>=0||(o[n]=e[n]);return o}(n,["children","className","disabled","disabledClassName","focus","id","panelId","selected","selectedClassName","tabIndex","tabRef"]);return a.a.createElement("li",Kr({},h,{className:zr()(o,(e={},e[p]=u,e[s]=i,e)),ref:function(e){t.node=e,d&&d(e)},role:"tab",id:l,"aria-selected":u?"true":"false","aria-disabled":i?"true":"false","aria-controls":c,tabIndex:f||(u?"0":null)}),r)},r}(i.Component);function Jr(){return(Jr=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}Zr.defaultProps={className:"react-tabs__tab",disabledClassName:"react-tabs__tab--disabled",focus:!1,id:null,panelId:null,selected:!1,selectedClassName:"react-tabs__tab--selected"},Zr.propTypes={},Zr.tabsRole="Tab";var eo=function(e){var t,n;function r(){return e.apply(this,arguments)||this}return n=e,(t=r).prototype=Object.create(n.prototype),t.prototype.constructor=t,t.__proto__=n,r.prototype.render=function(){var e,t=this.props,n=t.children,r=t.className,o=t.forceRender,i=t.id,s=t.selected,l=t.selectedClassName,c=t.tabId,u=function(e,t){if(null==e)return{};var n,r,o={},i=Object.keys(e);for(r=0;r<i.length;r++)n=i[r],t.indexOf(n)>=0||(o[n]=e[n]);return o}(t,["children","className","forceRender","id","selected","selectedClassName","tabId"]);return a.a.createElement("div",Jr({},u,{className:zr()(r,(e={},e[l]=s,e)),role:"tabpanel",id:i,"aria-labelledby":c}),o||s?n:null)},r}(i.Component);eo.defaultProps={className:"react-tabs__tab-panel",forceRender:!1,selectedClassName:"react-tabs__tab-panel--selected"},eo.propTypes={},eo.tabsRole="TabPanel";var to,no,ro,oo,io,ao,so=_e(Qr)(to||(to=Object(o.__makeTemplateObject)(["\n > ul {\n list-style: none;\n padding: 0;\n margin: 0;\n margin: 0 -5px;\n\n > li {\n padding: 5px 10px;\n display: inline-block;\n\n background-color: ",";\n border-bottom: 1px solid rgba(0, 0, 0, 0.5);\n cursor: pointer;\n text-align: center;\n outline: none;\n color: ",";\n margin: 0\n ",";\n border: 1px solid ",";\n border-radius: 5px;\n min-width: 60px;\n font-size: 0.9em;\n font-weight: bold;\n\n &.react-tabs__tab--selected {\n color: ",";\n background: ",";\n }\n\n &:only-child {\n flex: none;\n min-width: 100px;\n }\n\n &.tab-success {\n color: ",";\n }\n\n &.tab-redirect {\n color: ",";\n }\n\n &.tab-info {\n color: ",";\n }\n\n &.tab-error {\n color: ",";\n }\n }\n }\n > .react-tabs__tab-panel {\n background: ",";\n & > div,\n & > pre {\n padding: ","px;\n margin: 0;\n }\n\n & > div > pre {\n padding: 0;\n }\n }\n"],["\n > ul {\n list-style: none;\n padding: 0;\n margin: 0;\n margin: 0 -5px;\n\n > li {\n padding: 5px 10px;\n display: inline-block;\n\n background-color: ",";\n border-bottom: 1px solid rgba(0, 0, 0, 0.5);\n cursor: pointer;\n text-align: center;\n outline: none;\n color: ",";\n margin: 0\n ",";\n border: 1px solid ",";\n border-radius: 5px;\n min-width: 60px;\n font-size: 0.9em;\n font-weight: bold;\n\n &.react-tabs__tab--selected {\n color: ",";\n background: ",";\n }\n\n &:only-child {\n flex: none;\n min-width: 100px;\n }\n\n &.tab-success {\n color: ",";\n }\n\n &.tab-redirect {\n color: ",";\n }\n\n &.tab-info {\n color: ",";\n }\n\n &.tab-error {\n color: ",";\n }\n }\n }\n > .react-tabs__tab-panel {\n background: ",";\n & > div,\n & > pre {\n padding: ","px;\n margin: 0;\n }\n\n & > div > pre {\n padding: 0;\n }\n }\n"])),(function(e){return e.theme.codeBlock.backgroundColor}),(function(e){var t=e.theme;return q(t.colors.tonalOffset,t.rightPanel.textColor)}),(function(e){var t=e.theme;return t.spacing.unit+"px "+t.spacing.unit+"px "+t.spacing.unit+"px"}),(function(e){var t=e.theme;return q(.05,t.codeBlock.backgroundColor)}),(function(e){return e.theme.colors.text.primary}),(function(e){return e.theme.rightPanel.textColor}),(function(e){return e.theme.colors.responses.success.color}),(function(e){return e.theme.colors.responses.redirect.color}),(function(e){return e.theme.colors.responses.info.color}),(function(e){return e.theme.colors.responses.error.color}),(function(e){return e.theme.codeBlock.backgroundColor}),(function(e){return 4*e.theme.spacing.unit})),lo=(_e(so)(no||(no=Object(o.__makeTemplateObject)(["\n > ul {\n display: block;\n > li {\n padding: 2px 5px;\n min-width: auto;\n margin: 0 15px 0 0;\n font-size: 13px;\n font-weight: normal;\n border-bottom: 1px dashed;\n color: ",";\n border-radius: 0;\n background: none;\n\n &:last-child {\n margin-right: 0;\n }\n\n &.react-tabs__tab--selected {\n color: ",";\n background: none;\n }\n }\n }\n > .react-tabs__tab-panel {\n & > div,\n & > pre {\n padding: ","px 0;\n }\n }\n"],["\n > ul {\n display: block;\n > li {\n padding: 2px 5px;\n min-width: auto;\n margin: 0 15px 0 0;\n font-size: 13px;\n font-weight: normal;\n border-bottom: 1px dashed;\n color: ",";\n border-radius: 0;\n background: none;\n\n &:last-child {\n margin-right: 0;\n }\n\n &.react-tabs__tab--selected {\n color: ",";\n background: none;\n }\n }\n }\n > .react-tabs__tab-panel {\n & > div,\n & > pre {\n padding: ","px 0;\n }\n }\n"])),(function(e){var t=e.theme;return q(t.colors.tonalOffset,t.rightPanel.textColor)}),(function(e){return e.theme.rightPanel.textColor}),(function(e){return 2*e.theme.spacing.unit})),_e.div(ro||(ro=Object(o.__makeTemplateObject)(["\n /**\n * Based on prism-dark.css\n */\n\n code[class*='language-'],\n pre[class*='language-'] {\n /* color: white;\n background: none; */\n text-shadow: 0 -0.1em 0.2em black;\n text-align: left;\n white-space: pre;\n word-spacing: normal;\n word-break: normal;\n word-wrap: normal;\n line-height: 1.5;\n\n -moz-tab-size: 4;\n -o-tab-size: 4;\n tab-size: 4;\n\n -webkit-hyphens: none;\n -moz-hyphens: none;\n -ms-hyphens: none;\n hyphens: none;\n }\n\n @media print {\n code[class*='language-'],\n pre[class*='language-'] {\n text-shadow: none;\n }\n }\n\n /* Code blocks */\n pre[class*='language-'] {\n padding: 1em;\n margin: 0.5em 0;\n overflow: auto;\n }\n\n .token.comment,\n .token.prolog,\n .token.doctype,\n .token.cdata {\n color: hsl(30, 20%, 50%);\n }\n\n .token.punctuation {\n opacity: 0.7;\n }\n\n .namespace {\n opacity: 0.7;\n }\n\n .token.property,\n .token.tag,\n .token.number,\n .token.constant,\n .token.symbol {\n color: #4a8bb3;\n }\n\n .token.boolean {\n color: firebrick;\n }\n\n .token.selector,\n .token.attr-name,\n .token.string,\n .token.char,\n .token.builtin,\n .token.inserted {\n color: #a0fbaa;\n & + a,\n & + a:visited {\n color: #4ed2ba;\n text-decoration: underline;\n }\n }\n\n /* .property.token.string {\n color: white;\n } */\n\n .token.operator,\n .token.entity,\n .token.url,\n .token.variable {\n color: hsl(40, 90%, 60%);\n }\n\n .token.atrule,\n .token.attr-value,\n .token.keyword {\n color: hsl(350, 40%, 70%);\n }\n\n .token.regex,\n .token.important {\n color: #e90;\n }\n\n .token.important,\n .token.bold {\n font-weight: bold;\n }\n .token.italic {\n font-style: italic;\n }\n\n .token.entity {\n cursor: help;\n }\n\n .token.deleted {\n color: red;\n }\n\n ",";\n"],["\n /**\n * Based on prism-dark.css\n */\n\n code[class*='language-'],\n pre[class*='language-'] {\n /* color: white;\n background: none; */\n text-shadow: 0 -0.1em 0.2em black;\n text-align: left;\n white-space: pre;\n word-spacing: normal;\n word-break: normal;\n word-wrap: normal;\n line-height: 1.5;\n\n -moz-tab-size: 4;\n -o-tab-size: 4;\n tab-size: 4;\n\n -webkit-hyphens: none;\n -moz-hyphens: none;\n -ms-hyphens: none;\n hyphens: none;\n }\n\n @media print {\n code[class*='language-'],\n pre[class*='language-'] {\n text-shadow: none;\n }\n }\n\n /* Code blocks */\n pre[class*='language-'] {\n padding: 1em;\n margin: 0.5em 0;\n overflow: auto;\n }\n\n .token.comment,\n .token.prolog,\n .token.doctype,\n .token.cdata {\n color: hsl(30, 20%, 50%);\n }\n\n .token.punctuation {\n opacity: 0.7;\n }\n\n .namespace {\n opacity: 0.7;\n }\n\n .token.property,\n .token.tag,\n .token.number,\n .token.constant,\n .token.symbol {\n color: #4a8bb3;\n }\n\n .token.boolean {\n color: firebrick;\n }\n\n .token.selector,\n .token.attr-name,\n .token.string,\n .token.char,\n .token.builtin,\n .token.inserted {\n color: #a0fbaa;\n & + a,\n & + a:visited {\n color: #4ed2ba;\n text-decoration: underline;\n }\n }\n\n /* .property.token.string {\n color: white;\n } */\n\n .token.operator,\n .token.entity,\n .token.url,\n .token.variable {\n color: hsl(40, 90%, 60%);\n }\n\n .token.atrule,\n .token.attr-value,\n .token.keyword {\n color: hsl(350, 40%, 70%);\n }\n\n .token.regex,\n .token.important {\n color: #e90;\n }\n\n .token.important,\n .token.bold {\n font-weight: bold;\n }\n .token.italic {\n font-style: italic;\n }\n\n .token.entity {\n cursor: help;\n }\n\n .token.deleted {\n color: red;\n }\n\n ",";\n"])),Ee("Prism"))),co=_e.div(oo||(oo=Object(o.__makeTemplateObject)(["\n opacity: 0.4;\n transition: opacity 0.3s ease;\n text-align: right;\n\n > span {\n display: inline-block;\n padding: 2px 10px;\n cursor: pointer;\n\n :hover {\n background: rgba(255, 255, 255, 0.1);\n }\n }\n"],["\n opacity: 0.4;\n transition: opacity 0.3s ease;\n text-align: right;\n\n > span {\n display: inline-block;\n padding: 2px 10px;\n cursor: pointer;\n\n :hover {\n background: rgba(255, 255, 255, 0.1);\n }\n }\n"]))),uo=_e.div(io||(io=Object(o.__makeTemplateObject)(["\n &:hover "," {\n opacity: 1;\n }\n"],["\n &:hover "," {\n opacity: 1;\n }\n"])),co),po=_e(lo.withComponent("pre"))(ao||(ao=Object(o.__makeTemplateObject)(["\n font-family: ",";\n font-size: ",";\n overflow-x: auto;\n margin: 0;\n\n white-space: ",";\n"],["\n font-family: ",";\n font-size: ",";\n overflow-x: auto;\n margin: 0;\n\n white-space: ",";\n"])),(function(e){return e.theme.typography.code.fontFamily}),(function(e){return e.theme.typography.code.fontSize}),(function(e){return e.theme.typography.code.wrap?"pre-wrap":"pre"})); /*! * perfect-scrollbar v1.5.0 * Copyright 2020 Hyunje Jun, MDBootstrap and Contributors * Licensed under MIT */ -function lo(e){return getComputedStyle(e)}function co(e,t){for(var n in t){var r=t[n];"number"==typeof r&&(r+="px"),e.style[n]=r}return e}function uo(e){var t=document.createElement("div");return t.className=e,t}var po="undefined"!=typeof Element&&(Element.prototype.matches||Element.prototype.webkitMatchesSelector||Element.prototype.mozMatchesSelector||Element.prototype.msMatchesSelector);function fo(e,t){if(!po)throw new Error("No element matching method supported");return po.call(e,t)}function ho(e){e.remove?e.remove():e.parentNode&&e.parentNode.removeChild(e)}function mo(e,t){return Array.prototype.filter.call(e.children,(function(e){return fo(e,t)}))}var go="ps",yo="ps__rtl",vo={thumb:function(e){return"ps__thumb-"+e},rail:function(e){return"ps__rail-"+e},consuming:"ps__child--consume"},bo={focus:"ps--focus",clicking:"ps--clicking",active:function(e){return"ps--active-"+e},scrolling:function(e){return"ps--scrolling-"+e}},xo={x:null,y:null};function wo(e,t){var n=e.element.classList,r=bo.scrolling(t);n.contains(r)?clearTimeout(xo[t]):n.add(r)}function ko(e,t){xo[t]=setTimeout((function(){return e.isAlive&&e.element.classList.remove(bo.scrolling(t))}),e.settings.scrollingThreshold)}var Oo=function(e){this.element=e,this.handlers={}},_o={isEmpty:{configurable:!0}};Oo.prototype.bind=function(e,t){void 0===this.handlers[e]&&(this.handlers[e]=[]),this.handlers[e].push(t),this.element.addEventListener(e,t,!1)},Oo.prototype.unbind=function(e,t){var n=this;this.handlers[e]=this.handlers[e].filter((function(r){return!(!t||r===t)||(n.element.removeEventListener(e,r,!1),!1)}))},Oo.prototype.unbindAll=function(){for(var e in this.handlers)this.unbind(e)},_o.isEmpty.get=function(){var e=this;return Object.keys(this.handlers).every((function(t){return 0===e.handlers[t].length}))},Object.defineProperties(Oo.prototype,_o);var Eo=function(){this.eventElements=[]};function So(e){if("function"==typeof window.CustomEvent)return new CustomEvent(e);var t=document.createEvent("CustomEvent");return t.initCustomEvent(e,!1,!1,void 0),t}function To(e,t,n,r,o){var i;if(void 0===r&&(r=!0),void 0===o&&(o=!1),"top"===t)i=["contentHeight","containerHeight","scrollTop","y","up","down"];else{if("left"!==t)throw new Error("A proper axis should be provided");i=["contentWidth","containerWidth","scrollLeft","x","left","right"]}!function(e,t,n,r,o){var i=n[0],a=n[1],s=n[2],l=n[3],c=n[4],u=n[5];void 0===r&&(r=!0);void 0===o&&(o=!1);var p=e.element;e.reach[l]=null,p[s]<1&&(e.reach[l]="start");p[s]>e[i]-e[a]-1&&(e.reach[l]="end");t&&(p.dispatchEvent(So("ps-scroll-"+l)),t<0?p.dispatchEvent(So("ps-scroll-"+c)):t>0&&p.dispatchEvent(So("ps-scroll-"+u)),r&&function(e,t){wo(e,t),ko(e,t)}(e,l));e.reach[l]&&(t||o)&&p.dispatchEvent(So("ps-"+l+"-reach-"+e.reach[l]))}(e,n,i,r,o)}function jo(e){return parseInt(e,10)||0}Eo.prototype.eventElement=function(e){var t=this.eventElements.filter((function(t){return t.element===e}))[0];return t||(t=new Oo(e),this.eventElements.push(t)),t},Eo.prototype.bind=function(e,t,n){this.eventElement(e).bind(t,n)},Eo.prototype.unbind=function(e,t,n){var r=this.eventElement(e);r.unbind(t,n),r.isEmpty&&this.eventElements.splice(this.eventElements.indexOf(r),1)},Eo.prototype.unbindAll=function(){this.eventElements.forEach((function(e){return e.unbindAll()})),this.eventElements=[]},Eo.prototype.once=function(e,t,n){var r=this.eventElement(e),o=function(e){r.unbind(t,o),n(e)};r.bind(t,o)};var Co={isWebKit:"undefined"!=typeof document&&"WebkitAppearance"in document.documentElement.style,supportsTouch:"undefined"!=typeof window&&("ontouchstart"in window||"maxTouchPoints"in window.navigator&&window.navigator.maxTouchPoints>0||window.DocumentTouch&&document instanceof window.DocumentTouch),supportsIePointer:"undefined"!=typeof navigator&&navigator.msMaxTouchPoints,isChrome:"undefined"!=typeof navigator&&/Chrome/i.test(navigator&&navigator.userAgent)};function Ao(e){var t=e.element,n=Math.floor(t.scrollTop),r=t.getBoundingClientRect();e.containerWidth=Math.ceil(r.width),e.containerHeight=Math.ceil(r.height),e.contentWidth=t.scrollWidth,e.contentHeight=t.scrollHeight,t.contains(e.scrollbarXRail)||(mo(t,vo.rail("x")).forEach((function(e){return ho(e)})),t.appendChild(e.scrollbarXRail)),t.contains(e.scrollbarYRail)||(mo(t,vo.rail("y")).forEach((function(e){return ho(e)})),t.appendChild(e.scrollbarYRail)),!e.settings.suppressScrollX&&e.containerWidth+e.settings.scrollXMarginOffset<e.contentWidth?(e.scrollbarXActive=!0,e.railXWidth=e.containerWidth-e.railXMarginWidth,e.railXRatio=e.containerWidth/e.railXWidth,e.scrollbarXWidth=Io(e,jo(e.railXWidth*e.containerWidth/e.contentWidth)),e.scrollbarXLeft=jo((e.negativeScrollAdjustment+t.scrollLeft)*(e.railXWidth-e.scrollbarXWidth)/(e.contentWidth-e.containerWidth))):e.scrollbarXActive=!1,!e.settings.suppressScrollY&&e.containerHeight+e.settings.scrollYMarginOffset<e.contentHeight?(e.scrollbarYActive=!0,e.railYHeight=e.containerHeight-e.railYMarginHeight,e.railYRatio=e.containerHeight/e.railYHeight,e.scrollbarYHeight=Io(e,jo(e.railYHeight*e.containerHeight/e.contentHeight)),e.scrollbarYTop=jo(n*(e.railYHeight-e.scrollbarYHeight)/(e.contentHeight-e.containerHeight))):e.scrollbarYActive=!1,e.scrollbarXLeft>=e.railXWidth-e.scrollbarXWidth&&(e.scrollbarXLeft=e.railXWidth-e.scrollbarXWidth),e.scrollbarYTop>=e.railYHeight-e.scrollbarYHeight&&(e.scrollbarYTop=e.railYHeight-e.scrollbarYHeight),function(e,t){var n={width:t.railXWidth},r=Math.floor(e.scrollTop);t.isRtl?n.left=t.negativeScrollAdjustment+e.scrollLeft+t.containerWidth-t.contentWidth:n.left=e.scrollLeft;t.isScrollbarXUsingBottom?n.bottom=t.scrollbarXBottom-r:n.top=t.scrollbarXTop+r;co(t.scrollbarXRail,n);var o={top:r,height:t.railYHeight};t.isScrollbarYUsingRight?t.isRtl?o.right=t.contentWidth-(t.negativeScrollAdjustment+e.scrollLeft)-t.scrollbarYRight-t.scrollbarYOuterWidth-9:o.right=t.scrollbarYRight-e.scrollLeft:t.isRtl?o.left=t.negativeScrollAdjustment+e.scrollLeft+2*t.containerWidth-t.contentWidth-t.scrollbarYLeft-t.scrollbarYOuterWidth:o.left=t.scrollbarYLeft+e.scrollLeft;co(t.scrollbarYRail,o),co(t.scrollbarX,{left:t.scrollbarXLeft,width:t.scrollbarXWidth-t.railBorderXWidth}),co(t.scrollbarY,{top:t.scrollbarYTop,height:t.scrollbarYHeight-t.railBorderYWidth})}(t,e),e.scrollbarXActive?t.classList.add(bo.active("x")):(t.classList.remove(bo.active("x")),e.scrollbarXWidth=0,e.scrollbarXLeft=0,t.scrollLeft=!0===e.isRtl?e.contentWidth:0),e.scrollbarYActive?t.classList.add(bo.active("y")):(t.classList.remove(bo.active("y")),e.scrollbarYHeight=0,e.scrollbarYTop=0,t.scrollTop=0)}function Io(e,t){return e.settings.minScrollbarLength&&(t=Math.max(t,e.settings.minScrollbarLength)),e.settings.maxScrollbarLength&&(t=Math.min(t,e.settings.maxScrollbarLength)),t}function Po(e,t){var n=t[0],r=t[1],o=t[2],i=t[3],a=t[4],s=t[5],l=t[6],c=t[7],u=t[8],p=e.element,f=null,d=null,h=null;function m(t){t.touches&&t.touches[0]&&(t[o]=t.touches[0].pageY),p[l]=f+h*(t[o]-d),wo(e,c),Ao(e),t.stopPropagation(),t.preventDefault()}function g(){ko(e,c),e[u].classList.remove(bo.clicking),e.event.unbind(e.ownerDocument,"mousemove",m)}function y(t,a){f=p[l],a&&t.touches&&(t[o]=t.touches[0].pageY),d=t[o],h=(e[r]-e[n])/(e[i]-e[s]),a?e.event.bind(e.ownerDocument,"touchmove",m):(e.event.bind(e.ownerDocument,"mousemove",m),e.event.once(e.ownerDocument,"mouseup",g),t.preventDefault()),e[u].classList.add(bo.clicking),t.stopPropagation()}e.event.bind(e[a],"mousedown",(function(e){y(e)})),e.event.bind(e[a],"touchstart",(function(e){y(e,!0)}))}var Ro={"click-rail":function(e){e.element,e.event.bind(e.scrollbarY,"mousedown",(function(e){return e.stopPropagation()})),e.event.bind(e.scrollbarYRail,"mousedown",(function(t){var n=t.pageY-window.pageYOffset-e.scrollbarYRail.getBoundingClientRect().top>e.scrollbarYTop?1:-1;e.element.scrollTop+=n*e.containerHeight,Ao(e),t.stopPropagation()})),e.event.bind(e.scrollbarX,"mousedown",(function(e){return e.stopPropagation()})),e.event.bind(e.scrollbarXRail,"mousedown",(function(t){var n=t.pageX-window.pageXOffset-e.scrollbarXRail.getBoundingClientRect().left>e.scrollbarXLeft?1:-1;e.element.scrollLeft+=n*e.containerWidth,Ao(e),t.stopPropagation()}))},"drag-thumb":function(e){Po(e,["containerWidth","contentWidth","pageX","railXWidth","scrollbarX","scrollbarXWidth","scrollLeft","x","scrollbarXRail"]),Po(e,["containerHeight","contentHeight","pageY","railYHeight","scrollbarY","scrollbarYHeight","scrollTop","y","scrollbarYRail"])},keyboard:function(e){var t=e.element;e.event.bind(e.ownerDocument,"keydown",(function(n){if(!(n.isDefaultPrevented&&n.isDefaultPrevented()||n.defaultPrevented)&&(fo(t,":hover")||fo(e.scrollbarX,":focus")||fo(e.scrollbarY,":focus"))){var r,o=document.activeElement?document.activeElement:e.ownerDocument.activeElement;if(o){if("IFRAME"===o.tagName)o=o.contentDocument.activeElement;else for(;o.shadowRoot;)o=o.shadowRoot.activeElement;if(fo(r=o,"input,[contenteditable]")||fo(r,"select,[contenteditable]")||fo(r,"textarea,[contenteditable]")||fo(r,"button,[contenteditable]"))return}var i=0,a=0;switch(n.which){case 37:i=n.metaKey?-e.contentWidth:n.altKey?-e.containerWidth:-30;break;case 38:a=n.metaKey?e.contentHeight:n.altKey?e.containerHeight:30;break;case 39:i=n.metaKey?e.contentWidth:n.altKey?e.containerWidth:30;break;case 40:a=n.metaKey?-e.contentHeight:n.altKey?-e.containerHeight:-30;break;case 32:a=n.shiftKey?e.containerHeight:-e.containerHeight;break;case 33:a=e.containerHeight;break;case 34:a=-e.containerHeight;break;case 36:a=e.contentHeight;break;case 35:a=-e.contentHeight;break;default:return}e.settings.suppressScrollX&&0!==i||e.settings.suppressScrollY&&0!==a||(t.scrollTop-=a,t.scrollLeft+=i,Ao(e),function(n,r){var o=Math.floor(t.scrollTop);if(0===n){if(!e.scrollbarYActive)return!1;if(0===o&&r>0||o>=e.contentHeight-e.containerHeight&&r<0)return!e.settings.wheelPropagation}var i=t.scrollLeft;if(0===r){if(!e.scrollbarXActive)return!1;if(0===i&&n<0||i>=e.contentWidth-e.containerWidth&&n>0)return!e.settings.wheelPropagation}return!0}(i,a)&&n.preventDefault())}}))},wheel:function(e){var t=e.element;function n(n){var r=function(e){var t=e.deltaX,n=-1*e.deltaY;return void 0!==t&&void 0!==n||(t=-1*e.wheelDeltaX/6,n=e.wheelDeltaY/6),e.deltaMode&&1===e.deltaMode&&(t*=10,n*=10),t!=t&&n!=n&&(t=0,n=e.wheelDelta),e.shiftKey?[-n,-t]:[t,n]}(n),o=r[0],i=r[1];if(!function(e,n,r){if(!Co.isWebKit&&t.querySelector("select:focus"))return!0;if(!t.contains(e))return!1;for(var o=e;o&&o!==t;){if(o.classList.contains(vo.consuming))return!0;var i=lo(o);if(r&&i.overflowY.match(/(scroll|auto)/)){var a=o.scrollHeight-o.clientHeight;if(a>0&&(o.scrollTop>0&&r<0||o.scrollTop<a&&r>0))return!0}if(n&&i.overflowX.match(/(scroll|auto)/)){var s=o.scrollWidth-o.clientWidth;if(s>0&&(o.scrollLeft>0&&n<0||o.scrollLeft<s&&n>0))return!0}o=o.parentNode}return!1}(n.target,o,i)){var a=!1;e.settings.useBothWheelAxes?e.scrollbarYActive&&!e.scrollbarXActive?(i?t.scrollTop-=i*e.settings.wheelSpeed:t.scrollTop+=o*e.settings.wheelSpeed,a=!0):e.scrollbarXActive&&!e.scrollbarYActive&&(o?t.scrollLeft+=o*e.settings.wheelSpeed:t.scrollLeft-=i*e.settings.wheelSpeed,a=!0):(t.scrollTop-=i*e.settings.wheelSpeed,t.scrollLeft+=o*e.settings.wheelSpeed),Ao(e),(a=a||function(n,r){var o=Math.floor(t.scrollTop),i=0===t.scrollTop,a=o+t.offsetHeight===t.scrollHeight,s=0===t.scrollLeft,l=t.scrollLeft+t.offsetWidth===t.scrollWidth;return!(Math.abs(r)>Math.abs(n)?i||a:s||l)||!e.settings.wheelPropagation}(o,i))&&!n.ctrlKey&&(n.stopPropagation(),n.preventDefault())}}void 0!==window.onwheel?e.event.bind(t,"wheel",n):void 0!==window.onmousewheel&&e.event.bind(t,"mousewheel",n)},touch:function(e){if(Co.supportsTouch||Co.supportsIePointer){var t=e.element,n={},r=0,o={},i=null;Co.supportsTouch?(e.event.bind(t,"touchstart",c),e.event.bind(t,"touchmove",u),e.event.bind(t,"touchend",p)):Co.supportsIePointer&&(window.PointerEvent?(e.event.bind(t,"pointerdown",c),e.event.bind(t,"pointermove",u),e.event.bind(t,"pointerup",p)):window.MSPointerEvent&&(e.event.bind(t,"MSPointerDown",c),e.event.bind(t,"MSPointerMove",u),e.event.bind(t,"MSPointerUp",p)))}function a(n,r){t.scrollTop-=r,t.scrollLeft-=n,Ao(e)}function s(e){return e.targetTouches?e.targetTouches[0]:e}function l(e){return(!e.pointerType||"pen"!==e.pointerType||0!==e.buttons)&&(!(!e.targetTouches||1!==e.targetTouches.length)||!(!e.pointerType||"mouse"===e.pointerType||e.pointerType===e.MSPOINTER_TYPE_MOUSE))}function c(e){if(l(e)){var t=s(e);n.pageX=t.pageX,n.pageY=t.pageY,r=(new Date).getTime(),null!==i&&clearInterval(i)}}function u(i){if(l(i)){var c=s(i),u={pageX:c.pageX,pageY:c.pageY},p=u.pageX-n.pageX,f=u.pageY-n.pageY;if(function(e,n,r){if(!t.contains(e))return!1;for(var o=e;o&&o!==t;){if(o.classList.contains(vo.consuming))return!0;var i=lo(o);if(r&&i.overflowY.match(/(scroll|auto)/)){var a=o.scrollHeight-o.clientHeight;if(a>0&&(o.scrollTop>0&&r<0||o.scrollTop<a&&r>0))return!0}if(n&&i.overflowX.match(/(scroll|auto)/)){var s=o.scrollWidth-o.clientWidth;if(s>0&&(o.scrollLeft>0&&n<0||o.scrollLeft<s&&n>0))return!0}o=o.parentNode}return!1}(i.target,p,f))return;a(p,f),n=u;var d=(new Date).getTime(),h=d-r;h>0&&(o.x=p/h,o.y=f/h,r=d),function(n,r){var o=Math.floor(t.scrollTop),i=t.scrollLeft,a=Math.abs(n),s=Math.abs(r);if(s>a){if(r<0&&o===e.contentHeight-e.containerHeight||r>0&&0===o)return 0===window.scrollY&&r>0&&Co.isChrome}else if(a>s&&(n<0&&i===e.contentWidth-e.containerWidth||n>0&&0===i))return!0;return!0}(p,f)&&i.preventDefault()}}function p(){e.settings.swipeEasing&&(clearInterval(i),i=setInterval((function(){e.isInitialized?clearInterval(i):o.x||o.y?Math.abs(o.x)<.01&&Math.abs(o.y)<.01?clearInterval(i):(a(30*o.x,30*o.y),o.x*=.8,o.y*=.8):clearInterval(i)}),10))}}},No=function(e,t){var n=this;if(void 0===t&&(t={}),"string"==typeof e&&(e=document.querySelector(e)),!e||!e.nodeName)throw new Error("no element is specified to initialize PerfectScrollbar");for(var r in this.element=e,e.classList.add(go),this.settings={handlers:["click-rail","drag-thumb","keyboard","wheel","touch"],maxScrollbarLength:null,minScrollbarLength:null,scrollingThreshold:1e3,scrollXMarginOffset:0,scrollYMarginOffset:0,suppressScrollX:!1,suppressScrollY:!1,swipeEasing:!0,useBothWheelAxes:!1,wheelPropagation:!0,wheelSpeed:1},t)this.settings[r]=t[r];this.containerWidth=null,this.containerHeight=null,this.contentWidth=null,this.contentHeight=null;var o,i,a=function(){return e.classList.add(bo.focus)},s=function(){return e.classList.remove(bo.focus)};this.isRtl="rtl"===lo(e).direction,!0===this.isRtl&&e.classList.add(yo),this.isNegativeScroll=(i=e.scrollLeft,e.scrollLeft=-1,o=e.scrollLeft<0,e.scrollLeft=i,o),this.negativeScrollAdjustment=this.isNegativeScroll?e.scrollWidth-e.clientWidth:0,this.event=new Eo,this.ownerDocument=e.ownerDocument||document,this.scrollbarXRail=uo(vo.rail("x")),e.appendChild(this.scrollbarXRail),this.scrollbarX=uo(vo.thumb("x")),this.scrollbarXRail.appendChild(this.scrollbarX),this.scrollbarX.setAttribute("tabindex",0),this.event.bind(this.scrollbarX,"focus",a),this.event.bind(this.scrollbarX,"blur",s),this.scrollbarXActive=null,this.scrollbarXWidth=null,this.scrollbarXLeft=null;var l=lo(this.scrollbarXRail);this.scrollbarXBottom=parseInt(l.bottom,10),isNaN(this.scrollbarXBottom)?(this.isScrollbarXUsingBottom=!1,this.scrollbarXTop=jo(l.top)):this.isScrollbarXUsingBottom=!0,this.railBorderXWidth=jo(l.borderLeftWidth)+jo(l.borderRightWidth),co(this.scrollbarXRail,{display:"block"}),this.railXMarginWidth=jo(l.marginLeft)+jo(l.marginRight),co(this.scrollbarXRail,{display:""}),this.railXWidth=null,this.railXRatio=null,this.scrollbarYRail=uo(vo.rail("y")),e.appendChild(this.scrollbarYRail),this.scrollbarY=uo(vo.thumb("y")),this.scrollbarYRail.appendChild(this.scrollbarY),this.scrollbarY.setAttribute("tabindex",0),this.event.bind(this.scrollbarY,"focus",a),this.event.bind(this.scrollbarY,"blur",s),this.scrollbarYActive=null,this.scrollbarYHeight=null,this.scrollbarYTop=null;var c=lo(this.scrollbarYRail);this.scrollbarYRight=parseInt(c.right,10),isNaN(this.scrollbarYRight)?(this.isScrollbarYUsingRight=!1,this.scrollbarYLeft=jo(c.left)):this.isScrollbarYUsingRight=!0,this.scrollbarYOuterWidth=this.isRtl?function(e){var t=lo(e);return jo(t.width)+jo(t.paddingLeft)+jo(t.paddingRight)+jo(t.borderLeftWidth)+jo(t.borderRightWidth)}(this.scrollbarY):null,this.railBorderYWidth=jo(c.borderTopWidth)+jo(c.borderBottomWidth),co(this.scrollbarYRail,{display:"block"}),this.railYMarginHeight=jo(c.marginTop)+jo(c.marginBottom),co(this.scrollbarYRail,{display:""}),this.railYHeight=null,this.railYRatio=null,this.reach={x:e.scrollLeft<=0?"start":e.scrollLeft>=this.contentWidth-this.containerWidth?"end":null,y:e.scrollTop<=0?"start":e.scrollTop>=this.contentHeight-this.containerHeight?"end":null},this.isAlive=!0,this.settings.handlers.forEach((function(e){return Ro[e](n)})),this.lastScrollTop=Math.floor(e.scrollTop),this.lastScrollLeft=e.scrollLeft,this.event.bind(this.element,"scroll",(function(e){return n.onScroll(e)})),Ao(this)};No.prototype.update=function(){this.isAlive&&(this.negativeScrollAdjustment=this.isNegativeScroll?this.element.scrollWidth-this.element.clientWidth:0,co(this.scrollbarXRail,{display:"block"}),co(this.scrollbarYRail,{display:"block"}),this.railXMarginWidth=jo(lo(this.scrollbarXRail).marginLeft)+jo(lo(this.scrollbarXRail).marginRight),this.railYMarginHeight=jo(lo(this.scrollbarYRail).marginTop)+jo(lo(this.scrollbarYRail).marginBottom),co(this.scrollbarXRail,{display:"none"}),co(this.scrollbarYRail,{display:"none"}),Ao(this),To(this,"top",0,!1,!0),To(this,"left",0,!1,!0),co(this.scrollbarXRail,{display:""}),co(this.scrollbarYRail,{display:""}))},No.prototype.onScroll=function(e){this.isAlive&&(Ao(this),To(this,"top",this.element.scrollTop-this.lastScrollTop),To(this,"left",this.element.scrollLeft-this.lastScrollLeft),this.lastScrollTop=Math.floor(this.element.scrollTop),this.lastScrollLeft=this.element.scrollLeft)},No.prototype.destroy=function(){this.isAlive&&(this.event.unbindAll(),ho(this.scrollbarX),ho(this.scrollbarY),ho(this.scrollbarXRail),ho(this.scrollbarYRail),this.removePsClasses(),this.element=null,this.scrollbarX=null,this.scrollbarY=null,this.scrollbarXRail=null,this.scrollbarYRail=null,this.isAlive=!1)},No.prototype.removePsClasses=function(){this.element.className=this.element.className.split(" ").filter((function(e){return!e.match(/^ps([-_].+|)$/)})).join(" ")};var Lo,Mo,Do=No,Fo=n(100),zo=n.n(Fo),Uo=Do||r,Bo=xe(Lo||(Lo=Object(o.__makeTemplateObject)(["",""],["",""])),zo.a&&zo.a.toString()),$o=_e.div(Mo||(Mo=Object(o.__makeTemplateObject)(["\n position: relative;\n"],["\n position: relative;\n"]))),qo=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.handleRef=function(e){t._container=e},t}return Object(o.__extends)(t,e),t.prototype.componentDidMount=function(){var e=this._container.parentElement&&this._container.parentElement.scrollTop||0;this.inst=new Uo(this._container,this.props.options||{}),this._container.scrollTo&&this._container.scrollTo(0,e)},t.prototype.componentDidUpdate=function(){this.inst.update()},t.prototype.componentWillUnmount=function(){this.inst.destroy()},t.prototype.render=function(){var e=this.props,t=e.children,n=e.className,r=e.updateFn;return r&&r(this.componentDidUpdate.bind(this)),i.createElement(i.Fragment,null,i.createElement(Bo,null),i.createElement($o,{className:"scrollbar-container "+n,ref:this.handleRef},t))},t}(i.Component);function Wo(e){return i.createElement(Me.Consumer,null,(function(t){return t.nativeScrollbars?i.createElement("div",{style:{overflow:"auto",msOverflowStyle:"-ms-autohiding-scrollbar"}},e.children):i.createElement(qo,Object(o.__assign)({},e),e.children)}))}function Ho(e){var t=e.Label,n=void 0===t?_r:t,r=e.Dropdown,a=void 0===r?Or:r;return 1===e.options.length?i.createElement(n,null,e.options[0].label):i.createElement(a,Object(o.__assign)({},e))}var Vo,Yo,Qo=n(159),Go=be(Vo||(Vo=Object(o.__makeTemplateObject)(["\n a {\n text-decoration: none;\n color: ",";\n\n &:visited {\n color: ",";\n }\n\n &:hover {\n color: ",";\n }\n }\n"],["\n a {\n text-decoration: none;\n color: ",";\n\n &:visited {\n color: ",";\n }\n\n &:hover {\n color: ",";\n }\n }\n"])),(function(e){return e.theme.typography.links.color}),(function(e){return e.theme.typography.links.visited}),(function(e){return e.theme.typography.links.hover})),Xo=_e(oo)(Yo||(Yo=Object(o.__makeTemplateObject)(["\n\n font-family: ",";\n font-weight: ",";\n line-height: ",";\n\n p {\n &:last-child {\n margin-bottom: 0;\n }\n }\n\n ","\n\n ","\n\n h1 {\n ",";\n color: ",";\n margin-top: 0;\n }\n\n h2 {\n ",";\n color: ",";\n }\n\n code {\n color: ",";\n background-color: ",";\n\n font-family: ",";\n border-radius: 2px;\n border: 1px solid rgba(38, 50, 56, 0.1);\n padding: 0 ","px;\n font-size: ",";\n font-weight: ",";\n\n word-break: break-word;\n }\n\n pre {\n font-family: ",";\n white-space:",";\n background-color: #263238;\n color: white;\n padding: ","px;\n overflow-x: auto;\n line-height: normal;\n border-radius: 0px\n border: 1px solid rgba(38, 50, 56, 0.1);\n\n code {\n background-color: transparent;\n color: white;\n padding: 0;\n\n &:before,\n &:after {\n content: none;\n }\n }\n }\n\n blockquote {\n margin: 0;\n margin-bottom: 1em;\n padding: 0 15px;\n color: #777;\n border-left: 4px solid #ddd;\n }\n\n img {\n max-width: 100%;\n box-sizing: content-box;\n }\n\n ul,\n ol {\n padding-left: 2em;\n margin: 0;\n margin-bottom: 1em;\n\n ul, ol {\n margin-bottom: 0;\n margin-top: 0;\n }\n }\n\n table {\n display: block;\n width: 100%;\n overflow: auto;\n word-break: normal;\n word-break: keep-all;\n border-collapse: collapse;\n border-spacing: 0;\n margin-top: 1.5em;\n margin-bottom: 1.5em;\n }\n\n table tr {\n background-color: #fff;\n border-top: 1px solid #ccc;\n\n &:nth-child(2n) {\n background-color: ",";\n }\n }\n\n table th,\n table td {\n padding: 6px 13px;\n border: 1px solid #ddd;\n }\n\n table th {\n text-align: left;\n font-weight: bold;\n }\n\n ",";\n\n ","\n\n ",";\n"],["\n\n font-family: ",";\n font-weight: ",";\n line-height: ",";\n\n p {\n &:last-child {\n margin-bottom: 0;\n }\n }\n\n ","\n\n ","\n\n h1 {\n ",";\n color: ",";\n margin-top: 0;\n }\n\n h2 {\n ",";\n color: ",";\n }\n\n code {\n color: ",";\n background-color: ",";\n\n font-family: ",";\n border-radius: 2px;\n border: 1px solid rgba(38, 50, 56, 0.1);\n padding: 0 ","px;\n font-size: ",";\n font-weight: ",";\n\n word-break: break-word;\n }\n\n pre {\n font-family: ",";\n white-space:",";\n background-color: #263238;\n color: white;\n padding: ","px;\n overflow-x: auto;\n line-height: normal;\n border-radius: 0px\n border: 1px solid rgba(38, 50, 56, 0.1);\n\n code {\n background-color: transparent;\n color: white;\n padding: 0;\n\n &:before,\n &:after {\n content: none;\n }\n }\n }\n\n blockquote {\n margin: 0;\n margin-bottom: 1em;\n padding: 0 15px;\n color: #777;\n border-left: 4px solid #ddd;\n }\n\n img {\n max-width: 100%;\n box-sizing: content-box;\n }\n\n ul,\n ol {\n padding-left: 2em;\n margin: 0;\n margin-bottom: 1em;\n\n ul, ol {\n margin-bottom: 0;\n margin-top: 0;\n }\n }\n\n table {\n display: block;\n width: 100%;\n overflow: auto;\n word-break: normal;\n word-break: keep-all;\n border-collapse: collapse;\n border-spacing: 0;\n margin-top: 1.5em;\n margin-bottom: 1.5em;\n }\n\n table tr {\n background-color: #fff;\n border-top: 1px solid #ccc;\n\n &:nth-child(2n) {\n background-color: ",";\n }\n }\n\n table th,\n table td {\n padding: 6px 13px;\n border: 1px solid #ddd;\n }\n\n table th {\n text-align: left;\n font-weight: bold;\n }\n\n ",";\n\n ","\n\n ",";\n"])),(function(e){return e.theme.typography.fontFamily}),(function(e){return e.theme.typography.fontWeightRegular}),(function(e){return e.theme.typography.lineHeight}),(function(e){return e.compact&&"\n p:first-child {\n margin-top: 0;\n }\n p:last-child {\n margin-bottom: 0;\n }\n "}),(function(e){return e.inline&&" p {\n display: inline-block;\n }"}),_n(1),(function(e){return e.theme.colors.primary.main}),_n(2),(function(e){return e.theme.colors.text.primary}),(function(e){return e.theme.typography.code.color}),(function(e){return e.theme.typography.code.backgroundColor}),(function(e){return e.theme.typography.code.fontFamily}),(function(e){return e.theme.spacing.unit}),(function(e){return e.theme.typography.code.fontSize}),(function(e){return e.theme.typography.code.fontWeight}),(function(e){return e.theme.typography.code.fontFamily}),(function(e){return e.theme.typography.code.wrap?"pre-wrap":"pre"}),(function(e){return 4*e.theme.spacing.unit}),(function(e){return e.theme.schema.nestedBackground}),Nn(".share-link"),Go,Ee("Markdown")),Ko=Xo.withComponent("span");function Zo(e){var t=e.inline?Ko:Xo;return i.createElement(Fe,null,(function(n){return i.createElement(t,Object(o.__assign)({className:"redoc-markdown "+(e.className||""),dangerouslySetInnerHTML:{__html:(r=n.untrustedSpec,a=e.html,r?Qo.sanitize(a):a)},"data-role":e["data-role"]},e));var r,a}))}var Jo,ei,ti,ni,ri,oi=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props,t=e.source,n=e.inline,r=e.compact,o=e.className,a=e["data-role"],s=new Ct;return i.createElement(Zo,{html:s.renderMd(t),inline:n,compact:r,className:o,"data-role":a})},t}(i.Component),ii=_e.div(Jo||(Jo=Object(o.__makeTemplateObject)(["\n position: relative;\n"],["\n position: relative;\n"]))),ai=_e.div(ei||(ei=Object(o.__makeTemplateObject)(["\n position: absolute;\n min-width: 80px;\n max-width: 500px;\n background: #fff;\n bottom: 100%;\n left: 50%;\n margin-bottom: 10px;\n transform: translateX(-50%);\n\n border-radius: 4px;\n padding: 0.3em 0.6em;\n text-align: center;\n box-shadow: 0px 0px 5px 0px rgba(204, 204, 204, 1);\n"],["\n position: absolute;\n min-width: 80px;\n max-width: 500px;\n background: #fff;\n bottom: 100%;\n left: 50%;\n margin-bottom: 10px;\n transform: translateX(-50%);\n\n border-radius: 4px;\n padding: 0.3em 0.6em;\n text-align: center;\n box-shadow: 0px 0px 5px 0px rgba(204, 204, 204, 1);\n"]))),si=_e.div(ti||(ti=Object(o.__makeTemplateObject)(["\n background: #fff;\n color: #000;\n display: inline;\n font-size: 0.85em;\n white-space: nowrap;\n"],["\n background: #fff;\n color: #000;\n display: inline;\n font-size: 0.85em;\n white-space: nowrap;\n"]))),li=_e.div(ni||(ni=Object(o.__makeTemplateObject)(["\n position: absolute;\n width: 0;\n height: 0;\n bottom: -5px;\n left: 50%;\n margin-left: -5px;\n border-left: solid transparent 5px;\n border-right: solid transparent 5px;\n border-top: solid #fff 5px;\n"],["\n position: absolute;\n width: 0;\n height: 0;\n bottom: -5px;\n left: 50%;\n margin-left: -5px;\n border-left: solid transparent 5px;\n border-right: solid transparent 5px;\n border-top: solid #fff 5px;\n"]))),ci=_e.div(ri||(ri=Object(o.__makeTemplateObject)(["\n position: absolute;\n width: 100%;\n height: 20px;\n bottom: -20px;\n"],["\n position: absolute;\n width: 100%;\n height: 20px;\n bottom: -20px;\n"]))),ui=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props,t=e.open,n=e.title,r=e.children;return i.createElement(ii,null,r,t&&i.createElement(ai,null,i.createElement(si,null,n),i.createElement(li,null),i.createElement(ci,null)))},t}(i.Component),pi="undefined"!=typeof document&&document.queryCommandSupported&&document.queryCommandSupported("copy"),fi=function(){function e(){}return e.isSupported=function(){return pi},e.selectElement=function(e){var t,n;document.body.createTextRange?((t=document.body.createTextRange()).moveToElementText(e),t.select()):document.createRange&&window.getSelection&&(n=window.getSelection(),(t=document.createRange()).selectNodeContents(e),n.removeAllRanges(),n.addRange(t))},e.deselect=function(){if(document.selection)document.selection.empty();else if(window.getSelection){var e=window.getSelection();e&&e.removeAllRanges()}},e.copySelected=function(){var e;try{e=document.execCommand("copy")}catch(t){e=!1}return e},e.copyElement=function(t){e.selectElement(t);var n=e.copySelected();return n&&e.deselect(),n},e.copyCustom=function(t){var n=document.createElement("textarea");n.style.position="fixed",n.style.top="0",n.style.left="0",n.style.width="2em",n.style.height="2em",n.style.padding="0",n.style.border="none",n.style.outline="none",n.style.boxShadow="none",n.style.background="transparent",n.value=t,document.body.appendChild(n),n.select();var r=e.copySelected();return document.body.removeChild(n),r},e}(),di=function(e){function t(t){var n=e.call(this,t)||this;return n.copy=function(){var e="string"==typeof n.props.data?n.props.data:JSON.stringify(n.props.data,null,2);fi.copyCustom(e),n.showTooltip()},n.renderCopyButton=function(){return i.createElement("span",{onClick:n.copy},i.createElement(ui,{title:fi.isSupported()?"Copied":"Not supported in your browser",open:n.state.tooltipShown},"Copy"))},n.state={tooltipShown:!1},n}return Object(o.__extends)(t,e),t.prototype.render=function(){return this.props.children({renderCopyButton:this.renderCopyButton})},t.prototype.showTooltip=function(){var e=this;this.setState({tooltipShown:!0}),setTimeout((function(){e.setState({tooltipShown:!1})}),1500)},t}(i.PureComponent),hi=1;function mi(e,t){hi=1;var n="";return n+='<div class="redoc-json">',n+="<code>",n+=xi(e,t),n+="</code>",n+="</div>"}function gi(e){return void 0!==e?e.toString().replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">"):""}function yi(e){return JSON.stringify(e).slice(1,-1)}function vi(e,t){return'<span class="'+t+'">'+gi(e)+"</span>"}function bi(e){return'<span class="token punctuation">'+e+"</span>"}function xi(e,t){var n=typeof e,r="";return null==e?r+=vi("null","token keyword"):e&&e.constructor===Array?(hi++,r+=function(e,t){for(var n=hi>t?"collapsed":"",r='<div class="collapser"></div>'+bi("[")+'<span class="ellipsis"></span><ul class="array collapsible">',o=!1,i=e.length,a=0;a<i;a++)o=!0,r+='<li><div class="hoverable '+n+'">',r+=xi(e[a],t),a<i-1&&(r+=","),r+="</div></li>";r+="</ul>"+bi("]"),o||(r=bi("[ ]"));return r}(e,t),hi--):e&&e.constructor===Date?r+=vi('"'+e.toISOString()+'"',"token string"):"object"===n?(hi++,r+=function(e,t){for(var n=hi>t?"collapsed":"",r=Object.keys(e),o=r.length,i='<div class="collapser"></div>'+bi("{")+'<span class="ellipsis"></span><ul class="obj collapsible">',a=!1,s=0;s<o;s++){var l=r[s];a=!0,i+='<li><div class="hoverable '+n+'">',i+='<span class="property token string">"'+gi(l)+'"</span>: ',i+=xi(e[l],t),s<o-1&&(i+=bi(",")),i+="</div></li>"}i+="</ul>"+bi("}"),a||(i=bi("{ }"));return i}(e,t),hi--):"number"===n?r+=vi(e,"token number"):"string"===n?/^(http|https):\/\/[^\s]+$/.test(e)?r+=vi('"',"token string")+'<a href="'+e+'">'+gi(yi(e))+"</a>"+vi('"',"token string"):r+=vi('"'+yi(e)+'"',"token string"):"boolean"===n&&(r+=vi(e,"token boolean")),r}var wi,ki,Oi,_i=be(wi||(wi=Object(o.__makeTemplateObject)(["\n .redoc-json > .collapser {\n display: none;\n }\n\n font-family: ",";\n font-size: ",";\n\n white-space: ",";\n contain: content;\n overflow-x: auto;\n\n .callback-function {\n color: gray;\n }\n\n .collapser:after {\n content: '-';\n cursor: pointer;\n }\n\n .collapsed > .collapser:after {\n content: '+';\n cursor: pointer;\n }\n\n .ellipsis:after {\n content: ' … ';\n }\n\n .collapsible {\n margin-left: 2em;\n }\n\n .hoverable {\n padding-top: 1px;\n padding-bottom: 1px;\n padding-left: 2px;\n padding-right: 2px;\n border-radius: 2px;\n }\n\n .hovered {\n background-color: rgba(235, 238, 249, 1);\n }\n\n .collapser {\n padding-right: 6px;\n padding-left: 6px;\n }\n\n ul {\n list-style-type: none;\n padding: 0px;\n margin: 0px 0px 0px 26px;\n }\n\n li {\n position: relative;\n display: block;\n }\n\n .hoverable {\n display: inline-block;\n }\n\n .selected {\n outline-style: solid;\n outline-width: 1px;\n outline-style: dotted;\n }\n\n .collapsed > .collapsible {\n display: none;\n }\n\n .ellipsis {\n display: none;\n }\n\n .collapsed > .ellipsis {\n display: inherit;\n }\n\n .collapser {\n position: absolute;\n top: 1px;\n left: -1.5em;\n cursor: default;\n user-select: none;\n -webkit-user-select: none;\n }\n"],["\n .redoc-json > .collapser {\n display: none;\n }\n\n font-family: ",";\n font-size: ",";\n\n white-space: ",";\n contain: content;\n overflow-x: auto;\n\n .callback-function {\n color: gray;\n }\n\n .collapser:after {\n content: '-';\n cursor: pointer;\n }\n\n .collapsed > .collapser:after {\n content: '+';\n cursor: pointer;\n }\n\n .ellipsis:after {\n content: ' … ';\n }\n\n .collapsible {\n margin-left: 2em;\n }\n\n .hoverable {\n padding-top: 1px;\n padding-bottom: 1px;\n padding-left: 2px;\n padding-right: 2px;\n border-radius: 2px;\n }\n\n .hovered {\n background-color: rgba(235, 238, 249, 1);\n }\n\n .collapser {\n padding-right: 6px;\n padding-left: 6px;\n }\n\n ul {\n list-style-type: none;\n padding: 0px;\n margin: 0px 0px 0px 26px;\n }\n\n li {\n position: relative;\n display: block;\n }\n\n .hoverable {\n display: inline-block;\n }\n\n .selected {\n outline-style: solid;\n outline-width: 1px;\n outline-style: dotted;\n }\n\n .collapsed > .collapsible {\n display: none;\n }\n\n .ellipsis {\n display: none;\n }\n\n .collapsed > .ellipsis {\n display: inherit;\n }\n\n .collapser {\n position: absolute;\n top: 1px;\n left: -1.5em;\n cursor: default;\n user-select: none;\n -webkit-user-select: none;\n }\n"])),(function(e){return e.theme.typography.code.fontFamily}),(function(e){return e.theme.typography.code.fontSize}),(function(e){return e.theme.typography.code.wrap?"pre-wrap":"pre"})),Ei=_e.div(ki||(ki=Object(o.__makeTemplateObject)(["\n &:hover > "," {\n opacity: 1;\n }\n"],["\n &:hover > "," {\n opacity: 1;\n }\n"])),io),Si=_e(function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.renderInner=function(e){var n=e.renderCopyButton;return i.createElement(Ei,null,i.createElement(io,null,n(),i.createElement("span",{onClick:t.expandAll}," Expand all "),i.createElement("span",{onClick:t.collapseAll}," Collapse all ")),i.createElement(Me.Consumer,null,(function(e){return i.createElement(oo,{className:t.props.className,ref:function(e){return t.node=e},dangerouslySetInnerHTML:{__html:mi(t.props.data,e.jsonSampleExpandLevel)}})})))},t.expandAll=function(){for(var e=t.node.getElementsByClassName("collapsible"),n=0,r=Array.prototype.slice.call(e);n<r.length;n++){r[n].parentNode.classList.remove("collapsed")}},t.collapseAll=function(){for(var e=t.node.getElementsByClassName("collapsible"),n=0,r=Array.prototype.slice.call(e);n<r.length;n++){var o=r[n];o.parentNode.classList.contains("redoc-json")||o.parentNode.classList.add("collapsed")}},t.clickListener=function(e){var t,n=e.target;"collapser"===n.className&&((t=n.parentElement.getElementsByClassName("collapsible")[0]).parentElement.classList.contains("collapsed")?t.parentElement.classList.remove("collapsed"):t.parentElement.classList.add("collapsed"))},t}return Object(o.__extends)(t,e),t.prototype.render=function(){return i.createElement(di,{data:this.props.data},this.renderInner)},t.prototype.componentDidMount=function(){this.node.addEventListener("click",this.clickListener)},t.prototype.componentWillUnmount=function(){this.node.removeEventListener("click",this.clickListener)},t}(i.PureComponent))(Oi||(Oi=Object(o.__makeTemplateObject)(["\n ",";\n"],["\n ",";\n"])),_i),Ti=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props,t=e.source,n=e.lang;return i.createElement(so,{dangerouslySetInnerHTML:{__html:gt(t,n)}})},t}(i.PureComponent),ji=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this;return i.createElement(di,{data:this.props.source},(function(t){var n=t.renderCopyButton;return i.createElement(ao,null,i.createElement(io,null,n()),i.createElement(Ti,{lang:e.props.lang,source:e.props.source}))}))},t}(i.PureComponent);function Ci(e){var t,n=e.value,r=e.mimeType;return nt(r)?i.createElement(Si,{data:n}):("object"==typeof n&&(n=JSON.stringify(n,null,2)),i.createElement(ji,{lang:(t=r,-1!==t.search(/xml/i)?"xml":"clike"),source:n}))}function Ai(e){var t=e.example,n=e.mimeType;return void 0===t.value&&t.externalValueUrl?i.createElement(Ii,{example:t,mimeType:n}):i.createElement(Ci,{value:t.value,mimeType:n})}function Ii(e){var t=e.example,n=e.mimeType,r=function(e,t){var n=this,r=Object(i.useState)(!0)[1],a=Object(i.useRef)(void 0),s=Object(i.useRef)(void 0);return s.current!==e&&(a.current=void 0),s.current=e,Object(i.useEffect)((function(){Object(o.__awaiter)(n,void 0,void 0,(function(){var n,i;return Object(o.__generator)(this,(function(o){switch(o.label){case 0:r(!0),o.label=1;case 1:return o.trys.push([1,3,,4]),n=a,[4,e.getExternalValue(t)];case 2:return n.current=o.sent(),[3,4];case 3:return i=o.sent(),a.current=i,[3,4];case 4:return r(!1),[2]}}))}))}),[e,t]),a.current}(t,n);return void 0===r?i.createElement("span",null,"Loading..."):r instanceof Error?i.createElement(so,null,"Error loading external example: ",i.createElement("br",null),i.createElement("a",{className:"token string",href:t.externalValueUrl,target:"_blank",rel:"noopener noreferrer"},t.externalValueUrl)):i.createElement(Ci,{value:r,mimeType:n})}var Pi,Ri,Ni,Li,Mi,Di,Fi,zi,Ui,Bi,$i,qi,Wi,Hi,Vi,Yi,Qi,Gi,Xi,Ki,Zi,Ji,ea,ta=_e.div(Pi||(Pi=Object(o.__makeTemplateObject)(["\n padding: 12px;\n background-color: ",";\n margin: 0 0 10px 0;\n display: block;\n"],["\n padding: 12px;\n background-color: ",";\n margin: 0 0 10px 0;\n display: block;\n"])),(function(e){var t=e.theme;return K(.6,t.rightPanel.backgroundColor)})),na=_e.span(Ri||(Ri=Object(o.__makeTemplateObject)(["\n font-family: ",";\n font-size: 12px;\n position: absolute;\n z-index: 1;\n top: -11px;\n left: 12px;\n font-weight: ",";\n color: ",";\n"],["\n font-family: ",";\n font-size: 12px;\n position: absolute;\n z-index: 1;\n top: -11px;\n left: 12px;\n font-weight: ",";\n color: ",";\n"])),(function(e){return e.theme.typography.headings.fontFamily}),(function(e){return e.theme.typography.fontWeightBold}),(function(e){var t=e.theme;return K(.6,t.rightPanel.textColor)})),ra=_e.div(Ni||(Ni=Object(o.__makeTemplateObject)(["\n position: relative;\n"],["\n position: relative;\n"]))),oa=_e(kr)(Li||(Li=Object(o.__makeTemplateObject)(["\n margin-left: 10px;\n text-transform: none;\n font-size: 0.929em;\n margin: 0 0 10px 0;\n display: block;\n background-color: ",";\n .Dropdown-control {\n margin-top: 0;\n }\n .Dropdown-control,\n .Dropdown-control:hover {\n font-size: 1em;\n border: none;\n padding: 0.9em 1.6em 0.9em 0.9em;\n background: transparent;\n color: ",";\n box-shadow: none;\n\n .Dropdown-arrow {\n border-top-color: ",";\n }\n }\n .Dropdown-menu {\n margin: 0;\n margin-top: 2px;\n }\n"],["\n margin-left: 10px;\n text-transform: none;\n font-size: 0.929em;\n margin: 0 0 10px 0;\n display: block;\n background-color: ",";\n .Dropdown-control {\n margin-top: 0;\n }\n .Dropdown-control,\n .Dropdown-control:hover {\n font-size: 1em;\n border: none;\n padding: 0.9em 1.6em 0.9em 0.9em;\n background: transparent;\n color: ",";\n box-shadow: none;\n\n .Dropdown-arrow {\n border-top-color: ",";\n }\n }\n .Dropdown-menu {\n margin: 0;\n margin-top: 2px;\n }\n"])),(function(e){var t=e.theme;return K(.6,t.rightPanel.backgroundColor)}),(function(e){return e.theme.rightPanel.textColor}),(function(e){return e.theme.rightPanel.textColor})),ia=_e.div(Mi||(Mi=Object(o.__makeTemplateObject)(["\n font-family: ",";\n font-size: 12px;\n color: #ee807f;\n"],["\n font-family: ",";\n font-size: 12px;\n color: #ee807f;\n"])),(function(e){return e.theme.typography.code.fontFamily})),aa=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.state={activeIdx:0},t.switchMedia=function(e){var n=e.value;t.setState({activeIdx:parseInt(n,10)})},t}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.state.activeIdx,t=this.props.mediaType.examples||{},n=this.props.mediaType.name,r=i.createElement(ia,null,"No sample"),o=Object.keys(t);if(0===o.length)return r;if(o.length>1){var a=o.map((function(e,n){return{label:t[e].summary||e,value:n.toString()}})),s=(l=t[o[e]]).description;return i.createElement(sa,null,i.createElement(ra,null,i.createElement(na,null,"Example"),this.props.renderDropdown({value:a[e],options:a,onChange:this.switchMedia})),i.createElement("div",null,s&&i.createElement(oi,{source:s}),i.createElement(Ai,{example:l,mimeType:n})))}var l=t[o[0]];return i.createElement(sa,null,l.description&&i.createElement(oi,{source:l.description}),i.createElement(Ai,{example:l,mimeType:n}))},t}(i.Component),sa=_e.div(Di||(Di=Object(o.__makeTemplateObject)(["\n margin-top: 15px;\n"],["\n margin-top: 15px;\n"]))),la=n(3),ca=_e(pr)(Fi||(Fi=Object(o.__makeTemplateObject)(["\n cursor: pointer;\n\n "," {\n height: ",";\n width: ",";\n polygon {\n fill: ",";\n }\n }\n"],["\n cursor: pointer;\n\n "," {\n height: ",";\n width: ",";\n polygon {\n fill: ",";\n }\n }\n"])),ir,(function(e){return e.theme.schema.arrow.size}),(function(e){return e.theme.schema.arrow.size}),(function(e){return e.theme.schema.arrow.color})),ua=_e.span(zi||(zi=Object(o.__makeTemplateObject)(["\n vertical-align: middle;\n font-size: ",";\n line-height: 20px;\n"],["\n vertical-align: middle;\n font-size: ",";\n line-height: 20px;\n"])),(function(e){return e.theme.typography.code.fontSize})),pa=_e(ua)(Ui||(Ui=Object(o.__makeTemplateObject)(["\n color: ",";\n"],["\n color: ",";\n"])),(function(e){return K(.2,e.theme.schema.typeNameColor)})),fa=_e(ua)(Bi||(Bi=Object(o.__makeTemplateObject)(["\n color: ",";\n"],["\n color: ",";\n"])),(function(e){return e.theme.schema.typeNameColor})),da=_e(ua)($i||($i=Object(o.__makeTemplateObject)(["\n color: ",";\n word-break: break-word;\n"],["\n color: ",";\n word-break: break-word;\n"])),(function(e){return e.theme.schema.typeTitleColor})),ha=fa,ma=_e(ua.withComponent("div"))(qi||(qi=Object(o.__makeTemplateObject)(["\n color: ",";\n font-size: ",";\n font-weight: normal;\n margin-left: 20px;\n line-height: 1;\n"],["\n color: ",";\n font-size: ",";\n font-weight: normal;\n margin-left: 20px;\n line-height: 1;\n"])),(function(e){return e.theme.schema.requireLabelColor}),(function(e){return e.theme.schema.labelsTextSize})),ga=_e(ua)(Wi||(Wi=Object(o.__makeTemplateObject)(["\n color: ",";\n font-size: 13px;\n"],["\n color: ",";\n font-size: 13px;\n"])),(function(e){return e.theme.colors.warning.main})),ya=_e(ua)(Hi||(Hi=Object(o.__makeTemplateObject)(["\n color: #3195a6;\n font-size: 13px;\n"],["\n color: #3195a6;\n font-size: 13px;\n"]))),va=_e(ua)(Vi||(Vi=Object(o.__makeTemplateObject)(["\n color: #3195a6;\n &::before,\n &::after {\n font-weight: bold;\n }\n"],["\n color: #3195a6;\n &::before,\n &::after {\n font-weight: bold;\n }\n"]))),ba=_e(ua)(Yi||(Yi=Object(o.__makeTemplateObject)(["\n border-radius: 2px;\n ",";\n & + & {\n margin-left: 0;\n }\n ",";\n"],["\n border-radius: 2px;\n ",";\n & + & {\n margin-left: 0;\n }\n ",";\n"])),(function(e){var t=e.theme;return"\n background-color: "+K(.95,t.colors.text.primary)+";\n color: "+K(.1,t.colors.text.primary)+";\n\n padding: 0 "+t.spacing.unit+"px;\n border: 1px solid "+K(.9,t.colors.text.primary)+";\n font-family: "+t.typography.code.fontFamily+";\n}"}),Ee("ExampleValue")),xa=_e(ba)(Qi||(Qi=Object(o.__makeTemplateObject)([""],[""]))),wa=_e(ua)(Gi||(Gi=Object(o.__makeTemplateObject)(["\n border-radius: 2px;\n ",";\n & + & {\n margin-left: 0;\n }\n ",";\n"],["\n border-radius: 2px;\n ",";\n & + & {\n margin-left: 0;\n }\n ",";\n"])),(function(e){var t=e.theme;return"\n background-color: "+K(.95,t.colors.primary.light)+";\n color: "+K(.1,t.colors.primary.main)+";\n\n margin: 0 "+t.spacing.unit+"px;\n padding: 0 "+t.spacing.unit+"px;\n border: 1px solid "+K(.9,t.colors.primary.main)+";\n font-family: "+t.typography.code.fontFamily+";\n}"}),Ee("ConstraintItem")),ka=_e.div(Xi||(Xi=Object(o.__makeTemplateObject)(["\n ",";\n ","\n"],["\n ",";\n ","\n"])),Go,(function(e){return e.compact?"":"margin: 1em 0"})),Oa=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.externalDocs;return e&&e.url?i.createElement(ka,{compact:this.props.compact},i.createElement("a",{href:e.url},e.description||e.url)):null},t=Object(o.__decorate)([la.a],t)}(i.Component),_a=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props,t=e.values,n=e.type,r=this.context.enumSkipQuotes;return t.length?i.createElement("div",null,i.createElement(ua,null,"array"===n?de("enumArray"):""," ",1===t.length?de("enumSingleValue"):de("enum"),":")," ",t.map((function(e,t){var n=r?e:JSON.stringify(e);return i.createElement(i.Fragment,{key:t},i.createElement(ba,null,n)," ")}))):null},t.contextType=Me,t}(i.PureComponent),Ea=_e(Xo)(Ki||(Ki=Object(o.__makeTemplateObject)(["\n margin: 2px 0;\n"],["\n margin: 2px 0;\n"]))),Sa=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.extensions;return i.createElement(Me.Consumer,null,(function(t){return i.createElement(i.Fragment,null,t.showExtensions&&Object.keys(e).map((function(t){return i.createElement(Ea,{key:t},i.createElement(ua,null," ",t.substring(2),": ")," ",i.createElement(xa,null,"string"==typeof e[t]?e[t]:JSON.stringify(e[t])))})))}))},t}(i.PureComponent),Ta=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){return 0===this.props.constraints.length?null:i.createElement("span",null," ",this.props.constraints.map((function(e){return i.createElement(wa,{key:e}," ",e," ")})))},t}(i.PureComponent),ja=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){if(void 0===this.props.value)return null;var e=this.props.raw?this.props.value:JSON.stringify(this.props.value);return i.createElement("div",null,i.createElement(ua,null," ",this.props.label," ")," ",i.createElement(ba,null,e))},t}(i.PureComponent),Ca=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props,t=e.showExamples,n=e.field,r=e.renderDiscriminatorSwitch,a=this.context,s=a.enumSkipQuotes,l=a.hideSchemaTitles,c=n.schema,u=n.description,p=n.example,f=n.deprecated,d=!!s||"header"===n.in,h=null;if(t&&void 0!==p){var m=de("example")+":";if(n.in&&(n.style||n.serializationMime)){var g=decodeURIComponent(st(n,p));h=i.createElement(ja,{label:m,value:g,raw:!0})}else h=i.createElement(ja,{label:m,value:p})}return i.createElement("div",null,i.createElement("div",null,i.createElement(pa,null,c.typePrefix),i.createElement(fa,null,c.displayType),c.displayFormat&&i.createElement(ha,null," ","<",c.displayFormat,">"," "),c.title&&!l&&i.createElement(da,null," (",c.title,") "),i.createElement(Ta,{constraints:c.constraints}),c.nullable&&i.createElement(ya,null," ",de("nullable")," "),c.pattern&&i.createElement(va,null," ",c.pattern," "),c.isCircular&&i.createElement(ga,null," ",de("recursive")," ")),f&&i.createElement("div",null,i.createElement(ar,{type:"warning"}," ",de("deprecated")," ")),i.createElement(ja,{raw:d,label:de("default")+":",value:c.default}),!r&&i.createElement(_a,{type:c.type,values:c.enum})," ",h,i.createElement(Sa,{extensions:Object(o.__assign)(Object(o.__assign)({},n.extensions),c.extensions)}),i.createElement("div",null,i.createElement(oi,{compact:!0,source:u})),c.externalDocs&&i.createElement(Oa,{externalDocs:c.externalDocs,compact:!0}),r&&r(this.props)||null)},t.contextType=Me,t}(i.PureComponent),Aa=_e.div(Zi||(Zi=Object(o.__makeTemplateObject)(["\n padding-left: ","px;\n"],["\n padding-left: ","px;\n"])),(function(e){return 2*e.theme.spacing.unit})),Ia=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.schema.items;return i.createElement("div",null,i.createElement(br,null," Array "),i.createElement(Aa,null,i.createElement(Da,Object(o.__assign)({},this.props,{schema:e}))),i.createElement(xr,null))},t}(i.PureComponent),Pa=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.toggle=function(){void 0===t.props.field.expanded&&t.props.expandByDefault?t.props.field.expanded=!1:t.props.field.toggle()},t}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props,t=e.className,n=e.field,r=e.isLast,a=e.expandByDefault,s=n.name,l=n.deprecated,c=n.required,u=n.kind,p=!n.schema.isPrimitive&&!n.schema.isCircular,f=void 0===n.expanded?a:n.expanded,d=p?i.createElement(ca,{onClick:this.toggle,className:l?"deprecated":"",kind:u,title:s},i.createElement(dr,null),s,i.createElement(ir,{direction:f?"down":"right"}),c&&i.createElement(ma,null," required ")):i.createElement(pr,{className:l?"deprecated":void 0,kind:u,title:s},i.createElement(dr,null),s,c&&i.createElement(ma,null," required "));return i.createElement(i.Fragment,null,i.createElement("tr",{className:r?"last "+t:t},d,i.createElement(fr,null,i.createElement(Ca,Object(o.__assign)({},this.props)))),f&&p&&i.createElement("tr",{key:n.name+"inner"},i.createElement(ur,{colSpan:2},i.createElement(hr,null,i.createElement(Da,{schema:n.schema,skipReadOnly:this.props.skipReadOnly,skipWriteOnly:this.props.skipWriteOnly,showTitle:this.props.showTitle})))))},t=Object(o.__decorate)([la.a],t)}(i.Component),Ra=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.changeActiveChild=function(e){var n=e.value,r=parseInt(n,10);t.props.parent.activateOneOf(r)},t}return Object(o.__extends)(t,e),t.prototype.sortOptions=function(e,t){if(0!==t.length){var n={};t.forEach((function(e,t){n[e]=t})),e.sort((function(e,t){return n[e.label]>n[t.label]?1:-1}))}},t.prototype.render=function(){var e=this.props,t=e.parent,n=e.enumValues;if(void 0===t.oneOf)return null;var r=t.oneOf.map((function(e,t){return{value:t.toString(),label:e.title}})),o=r[t.activeOneOf];return this.sortOptions(r,n),i.createElement(kr,{value:o,options:r,onChange:this.changeActiveChild})},t=Object(o.__decorate)([la.a],t)}(i.Component),Na=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),Object.defineProperty(t.prototype,"parentSchema",{get:function(){return this.props.discriminator.parentSchema},enumerable:!0,configurable:!0}),t.prototype.render=function(){var e=this,t=this.props,n=t.schema.fields,r=void 0===n?[]:n,o=t.showTitle,a=t.discriminator,s=this.props.skipReadOnly||this.props.skipWriteOnly?r.filter((function(t){return!(e.props.skipReadOnly&&t.schema.readOnly||e.props.skipWriteOnly&&t.schema.writeOnly)})):r,l=this.context.expandSingleSchemaField&&1===s.length;return i.createElement(mr,null,o&&i.createElement(lr,null,this.props.schema.title),i.createElement("tbody",null,ie(s,(function(t,n){return i.createElement(Pa,{key:t.name,isLast:n,field:t,expandByDefault:l,renderDiscriminatorSwitch:a&&a.fieldName===t.name&&function(){return i.createElement(Ra,{parent:e.parentSchema,enumValues:t.schema.enum})}||void 0,className:t.expanded?"expanded":void 0,showExamples:!1,skipReadOnly:e.props.skipReadOnly,skipWriteOnly:e.props.skipWriteOnly,showTitle:e.props.showTitle})}))))},t.contextType=Me,t=Object(o.__decorate)([la.a],t)}(i.Component),La=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.activateOneOf=function(){t.props.schema.activateOneOf(t.props.idx)},t}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props,t=e.idx,n=e.schema,r=e.subSchema;return i.createElement(vr,{active:t===n.activeOneOf,onClick:this.activateOneOf},r.title||r.typePrefix+r.displayType)},t=Object(o.__decorate)([la.a],t)}(i.Component),Ma=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props,t=e.schema.oneOf,n=e.schema;return void 0===t?null:i.createElement("div",null,i.createElement(yr,null," ",n.oneOfType," "),i.createElement(gr,null,t.map((function(e,t){return i.createElement(La,{key:e.pointer,schema:n,subSchema:e,idx:t})}))),i.createElement(Da,Object(o.__assign)({},this.props,{schema:t[n.activeOneOf]})))},t=Object(o.__decorate)([la.a],t)}(i.Component),Da=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.schema;if(!e)return i.createElement("em",null," Schema not provided ");var t=e.type,n=e.oneOf,r=e.discriminatorProp;if(e.isCircular)return i.createElement("div",null,i.createElement(fa,null,e.displayType),e.title&&i.createElement(da,null," ",e.title," "),i.createElement(ga,null," ",de("recursive")," "));if(void 0!==r){if(!n||!n.length)throw new Error("Looks like you are using discriminator wrong: you don't have any definition inherited from the "+e.title);return i.createElement(Na,Object(o.__assign)({},Object(o.__assign)(Object(o.__assign)({},this.props),{schema:n[e.activeOneOf]}),{discriminator:{fieldName:r,parentSchema:e}}))}if(void 0!==n)return i.createElement(Ma,Object(o.__assign)({schema:e},this.props));switch(t){case"object":return i.createElement(Na,Object(o.__assign)({},this.props));case"array":return i.createElement(Ia,Object(o.__assign)({},this.props))}var a={schema:e,name:"",required:!1,description:e.description,externalDocs:e.externalDocs,deprecated:!1,toggle:function(){return null},expanded:!1};return i.createElement("div",null,i.createElement(Ca,{field:a}))},t=Object(o.__decorate)([la.a],t)}(i.Component),Fa=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.renderDropdown=function(e){return i.createElement(Ho,Object(o.__assign)({Label:_r,Dropdown:oa},e))},t}return Object(o.__extends)(t,e),t.getMediaType=function(e,t){if(!e)return{};var n={schema:{$ref:e}};return t&&(n.examples={example:{$ref:t}}),n},Object.defineProperty(t.prototype,"mediaModel",{get:function(){var e=this.props,n=e.parser,r=e.schemaRef,o=e.exampleRef,i=e.options;return this._mediaModel||(this._mediaModel=new Ht(n,"json",!1,t.getMediaType(r,o),i)),this._mediaModel},enumerable:!0,configurable:!0}),t.prototype.render=function(){var e=this.props,t=e.showReadOnly,n=void 0===t||t,r=e.showWriteOnly,o=void 0!==r&&r;return i.createElement(bn,null,i.createElement(kn,null,i.createElement(vn,null,i.createElement(Da,{skipWriteOnly:!o,skipReadOnly:!n,schema:this.mediaModel.schema})),i.createElement(wn,null,i.createElement(za,null,i.createElement(aa,{renderDropdown:this.renderDropdown,mediaType:this.mediaModel})))))},t}(i.PureComponent),za=_e.div(Ji||(Ji=Object(o.__makeTemplateObject)(["\n background: ",";\n & > div,\n & > pre {\n padding: ","px;\n margin: 0;\n }\n\n & > div > pre {\n padding: 0;\n }\n"],["\n background: ",";\n & > div,\n & > pre {\n padding: ","px;\n margin: 0;\n }\n\n & > div > pre {\n padding: 0;\n }\n"])),(function(e){return e.theme.codeSample.backgroundColor}),(function(e){return 4*e.theme.spacing.unit})),Ua={oauth2:"OAuth2",apiKey:"API Key",http:"HTTP",openIdConnect:"Open ID Connect"},Ba=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props,t=e.type,n=e.flow;return i.createElement("tr",null,i.createElement("th",null," ",t," OAuth Flow "),i.createElement("td",null,"implicit"===t||"authorizationCode"===t?i.createElement("div",null,i.createElement("strong",null," Authorization URL: "),n.authorizationUrl):null,"password"===t||"clientCredentials"===t||"authorizationCode"===t?i.createElement("div",null,i.createElement("strong",null," Token URL: "),n.tokenUrl):null,n.refreshUrl&&i.createElement("div",null,i.createElement("strong",null," Refresh URL: "),n.refreshUrl),i.createElement("div",null,i.createElement("strong",null," Scopes: ")),i.createElement("ul",null,Object.keys(n.scopes||{}).map((function(e){return i.createElement("li",{key:e},i.createElement("code",null,e)," - ",i.createElement(oi,{inline:!0,source:n.scopes[e]||""}))})))))},t}(i.PureComponent),$a=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){return this.props.securitySchemes.schemes.map((function(e){return i.createElement(bn,{id:e.sectionId,key:e.id},i.createElement(kn,null,i.createElement(vn,null,i.createElement(Sn,null,i.createElement(Fn,{to:e.sectionId}),e.id),i.createElement(oi,{source:e.description||""}),i.createElement(Xo,null,i.createElement("table",{className:"security-details"},i.createElement("tbody",null,i.createElement("tr",null,i.createElement("th",null," Security Scheme Type "),i.createElement("td",null," ",Ua[e.type]||e.type," ")),e.apiKey?i.createElement("tr",null,i.createElement("th",null," ",(t=e.apiKey.in||"").charAt(0).toUpperCase()+t.slice(1)," parameter name:"),i.createElement("td",null," ",e.apiKey.name," ")):e.http?[i.createElement("tr",{key:"scheme"},i.createElement("th",null," HTTP Authorization Scheme "),i.createElement("td",null," ",e.http.scheme," ")),"bearer"===e.http.scheme&&e.http.bearerFormat&&i.createElement("tr",{key:"bearer"},i.createElement("th",null," Bearer format "),i.createElement("td",null,' "',e.http.bearerFormat,'" '))]:e.openId?i.createElement("tr",null,i.createElement("th",null," Connect URL "),i.createElement("td",null,i.createElement("a",{target:"_blank",rel:"noopener noreferrer",href:e.openId.connectUrl},e.openId.connectUrl))):e.flows?Object.keys(e.flows).map((function(t){return i.createElement(Ba,{key:t,type:t,flow:e.flows[t]})})):null))))));var t}))},t}(i.PureComponent);var qa,Wa,Ha,Va,Ya,Qa,Ga,Xa,Ka,Za,Ja,es,ts,ns,rs,os,is,as,ss,ls,cs,us,ps=function(){function e(e,t,n,r){var o=this;void 0===n&&(n={}),void 0===r&&(r=!0),this.marker=new Et,this.disposer=null,this.rawOptions=n,this.options=new ge(n,fs),this.scroll=new Zt(this.options),Kt.updateOnHistory(Ot.currentId,this.scroll),this.spec=new Mt(e,t,this.options),this.menu=new Kt(this.spec,this.scroll,Ot),this.options.disableSearch||(this.search=new yn,r&&this.search.indexItems(this.menu.items),this.disposer=Object(ze.m)(this.menu,"activeItemIdx",(function(e){o.updateMarkOnMenu(e.newValue)})))}return e.fromJS=function(t){var n=new e(t.spec.data,t.spec.url,t.options,!1);return n.menu.activeItemIdx=t.menu.activeItemIdx||0,n.menu.activate(n.menu.flatItems[n.menu.activeItemIdx]),n.options.disableSearch||n.search.load(t.searchIndex),n},e.prototype.onDidMount=function(){this.menu.updateOnHistory(),this.updateMarkOnMenu(this.menu.activeItemIdx)},e.prototype.dispose=function(){this.scroll.dispose(),this.menu.dispose(),this.search&&this.search.dispose(),null!=this.disposer&&this.disposer()},e.prototype.toJS=function(){return Object(o.__awaiter)(this,void 0,void 0,(function(){var e,t;return Object(o.__generator)(this,(function(n){switch(n.label){case 0:return e={menu:{activeItemIdx:this.menu.activeItemIdx},spec:{url:this.spec.parser.specUrl,data:this.spec.parser.spec}},this.search?[4,this.search.toJS()]:[3,2];case 1:return t=n.sent(),[3,3];case 2:t=void 0,n.label=3;case 3:return[2,(e.searchIndex=t,e.options=this.rawOptions,e)]}}))}))},e.prototype.updateMarkOnMenu=function(e){for(var t=Math.max(0,e),n=Math.min(this.menu.flatItems.length,t+5),r=[],o=t;o<n;o++){var i=this.menu.getElementAt(o);i&&r.push(i)}if(-1===e&&ee){var a=document.querySelector('[data-role="redoc-description"]');a&&r.push(a)}this.marker.addOnly(r),this.marker.mark()},e}(),fs={allowedMdComponents:(ea={},ea["security-definitions"]={component:$a,propsSelector:function(e){return{securitySchemes:e.spec.securitySchemes}}},ea.SecurityDefinitions={component:$a,propsSelector:function(e){return{securitySchemes:e.spec.securitySchemes}}},ea.SchemaDefinition={component:Fa,propsSelector:function(e){return{parser:e.spec.parser,options:e.options}}},ea)},ds=_e(En)(qa||(qa=Object(o.__makeTemplateObject)(["\n margin-top: 0;\n margin-bottom: 0.5em;\n\n ",";\n"],["\n margin-top: 0;\n margin-bottom: 0.5em;\n\n ",";\n"])),Ee("ApiHeader")),hs=_e.a(Wa||(Wa=Object(o.__makeTemplateObject)(["\n border: 1px solid ",";\n color: ",";\n font-weight: normal;\n margin-left: 0.5em;\n padding: 4px 8px 4px;\n display: inline-block;\n text-decoration: none;\n cursor: pointer;\n\n ",";\n"],["\n border: 1px solid ",";\n color: ",";\n font-weight: normal;\n margin-left: 0.5em;\n padding: 4px 8px 4px;\n display: inline-block;\n text-decoration: none;\n cursor: pointer;\n\n ",";\n"])),(function(e){return e.theme.colors.primary.main}),(function(e){return e.theme.colors.primary.main}),Ee("DownloadButton")),ms=_e.span(Ha||(Ha=Object(o.__makeTemplateObject)(["\n &::before {\n content: '|';\n display: inline-block;\n opacity: 0.5;\n width: ","px;\n text-align: center;\n }\n\n &:last-child::after {\n display: none;\n }\n"],["\n &::before {\n content: '|';\n display: inline-block;\n opacity: 0.5;\n width: ","px;\n text-align: center;\n }\n\n &:last-child::after {\n display: none;\n }\n"])),15),gs=_e.div(Va||(Va=Object(o.__makeTemplateObject)(["\n overflow: hidden;\n"],["\n overflow: hidden;\n"]))),ys=_e.div(Ya||(Ya=Object(o.__makeTemplateObject)(["\n display: flex;\n flex-wrap: wrap;\n // hide separator on new lines: idea from https://stackoverflow.com/a/31732902/1749888\n margin-left: -","px;\n"],["\n display: flex;\n flex-wrap: wrap;\n // hide separator on new lines: idea from https://stackoverflow.com/a/31732902/1749888\n margin-left: -","px;\n"])),15),vs=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.handleDownloadClick=function(e){e.target.href||(e.target.href=t.props.store.spec.info.downloadLink)},t}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.store,t=e.spec,n=t.info,r=t.externalDocs,o=e.options.hideDownloadButton,a=n.downloadFileName,s=n.downloadLink,l=n.license&&i.createElement(ms,null,"License: ",i.createElement("a",{href:n.license.url},n.license.name))||null,c=n.contact&&n.contact.url&&i.createElement(ms,null,"URL: ",i.createElement("a",{href:n.contact.url},n.contact.url))||null,u=n.contact&&n.contact.email&&i.createElement(ms,null,n.contact.name||"E-mail",":"," ",i.createElement("a",{href:"mailto:"+n.contact.email},n.contact.email))||null,p=n.termsOfService&&i.createElement(ms,null,i.createElement("a",{href:n.termsOfService},"Terms of Service"))||null,f=n.version&&i.createElement("span",null,"(",n.version,")")||null;return i.createElement(bn,null,i.createElement(kn,null,i.createElement(vn,{className:"api-info"},i.createElement(ds,null,n.title," ",f),!o&&i.createElement("p",null,"Download OpenAPI specification:",i.createElement(hs,{download:a,target:"_blank",href:s,onClick:this.handleDownloadClick},"Download")),i.createElement(Xo,null,(n.license||n.contact||n.termsOfService)&&i.createElement(gs,null,i.createElement(ys,null,u," ",c," ",l," ",p))||null),i.createElement(oi,{source:e.spec.info.description,"data-role":"redoc-description"}),r&&i.createElement(Oa,{externalDocs:r}))))},t=Object(o.__decorate)([la.a],t)}(i.Component),bs=_e.img(Qa||(Qa=Object(o.__makeTemplateObject)(["\n max-height: ",";\n max-width: ",";\n padding: ",";\n width: 100%;\n display: block;\n"],["\n max-height: ",";\n max-width: ",";\n padding: ",";\n width: 100%;\n display: block;\n"])),(function(e){return e.theme.logo.maxHeight}),(function(e){return e.theme.logo.maxWidth}),(function(e){return e.theme.logo.gutter})),xs=_e.div(Ga||(Ga=Object(o.__makeTemplateObject)(["\n text-align: center;\n"],["\n text-align: center;\n"]))),ws=_e.a(Xa||(Xa=Object(o.__makeTemplateObject)(["\n display: inline-block;\n"],["\n display: inline-block;\n"]))),ks=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.info,t=e["x-logo"];if(!t||!t.url)return null;var n,r=t.href||e.contact&&e.contact.url,o=t.altText?t.altText:"logo",a=i.createElement(bs,{src:t.url,alt:o});return i.createElement(xs,{style:{backgroundColor:t.backgroundColor}},r?(n=r,function(e){return i.createElement(ws,{href:n},e)})(a):a)},t=Object(o.__decorate)([la.a],t)}(i.Component),Os=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this;return i.createElement(Fe,null,(function(t){return i.createElement(Pn,null,(function(n){return e.renderWithOptionsAndStore(t,n)}))}))},t.prototype.renderWithOptionsAndStore=function(e,t){var n=this.props,r=n.source,a=n.htmlWrap,s=void 0===a?function(e){return e}:a;if(!t)throw new Error("When using components in markdown, store prop must be provided");var l=new Ct(e).renderMdWithComponents(r);return l.length?l.map((function(e,n){return"string"==typeof e?i.cloneElement(s(i.createElement(Zo,{html:e,inline:!1,compact:!1})),{key:n}):i.createElement(e.component,Object(o.__assign)({key:n},Object(o.__assign)(Object(o.__assign)({},e.props),e.propsSelector(t))))})):null},t}(i.Component),_s=_e.code(Ka||(Ka=Object(o.__makeTemplateObject)(["\n font-size: ",";\n font-family: ",";\n border: 1px solid ",";\n margin: 0 3px;\n padding: 0.2em;\n display: inline-block;\n line-height: 1;\n\n &:after {\n content: ',';\n }\n &:last-child:after {\n content: none;\n }\n"],["\n font-size: ",";\n font-family: ",";\n border: 1px solid ",";\n margin: 0 3px;\n padding: 0.2em;\n display: inline-block;\n line-height: 1;\n\n &:after {\n content: ',';\n }\n &:last-child:after {\n content: none;\n }\n"])),(function(e){return e.theme.typography.code.fontSize}),(function(e){return e.theme.typography.code.fontFamily}),(function(e){return e.theme.colors.border.dark})),Es=_e.span(Za||(Za=Object(o.__makeTemplateObject)(["\n &:after {\n content: ' AND ';\n font-weight: bold;\n }\n\n &:last-child:after {\n content: none;\n }\n\n ",";\n"],["\n &:after {\n content: ' AND ';\n font-weight: bold;\n }\n\n &:last-child:after {\n content: none;\n }\n\n ",";\n"])),Go),Ss=_e.span(Ja||(Ja=Object(o.__makeTemplateObject)(["\n &:before {\n content: '( ';\n font-weight: bold;\n }\n &:after {\n content: ' ) OR ';\n font-weight: bold;\n }\n &:last-child:after {\n content: ' )';\n }\n\n &:only-child:before,\n &:only-child:after {\n content: none;\n }\n\n ",";\n"],["\n &:before {\n content: '( ';\n font-weight: bold;\n }\n &:after {\n content: ' ) OR ';\n font-weight: bold;\n }\n &:last-child:after {\n content: ' )';\n }\n\n &:only-child:before,\n &:only-child:after {\n content: none;\n }\n\n ",";\n"])),Go),Ts=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.security;return i.createElement(Ss,null,e.schemes.map((function(e){return i.createElement(Es,{key:e.id},i.createElement(Mn,{to:e.sectionId},e.id),e.scopes.length>0&&" (",e.scopes.map((function(e){return i.createElement(_s,{key:e},e)})),e.scopes.length>0&&") ")})))},t}(i.PureComponent),js=_e.div(es||(es=Object(o.__makeTemplateObject)(["\n flex: 1;\n"],["\n flex: 1;\n"]))),Cs=_e.div(ts||(ts=Object(o.__makeTemplateObject)(["\n width: ",";\n"],["\n width: ",";\n"])),(function(e){return e.theme.schema.defaultDetailsWidth})),As=_e(jn)(ns||(ns=Object(o.__makeTemplateObject)(["\n display: inline-block;\n margin: 0;\n"],["\n display: inline-block;\n margin: 0;\n"]))),Is=_e.div(rs||(rs=Object(o.__makeTemplateObject)(["\n width: 100%;\n display: flex;\n margin: 1em 0;\n"],["\n width: 100%;\n display: flex;\n margin: 1em 0;\n"]))),Ps=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.securities;return e.length?i.createElement(Is,null,i.createElement(js,null,i.createElement(As,null,"Authorizations: ")),i.createElement(Cs,null,e.map((function(e,t){return i.createElement(Ts,{key:t,security:e})})))):null},t}(i.PureComponent),Rs=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.handleClick=function(){fi.selectElement(t.child)},t}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this,t=this.props.children;return i.createElement("div",{ref:function(t){return e.child=t},onClick:this.handleClick},t)},t}(i.PureComponent),Ns=_e.div(os||(os=Object(o.__makeTemplateObject)(["\n cursor: pointer;\n position: relative;\n margin-bottom: 5px;\n"],["\n cursor: pointer;\n position: relative;\n margin-bottom: 5px;\n"]))),Ls=_e.span(is||(is=Object(o.__makeTemplateObject)(["\n font-family: ",";\n margin-left: 10px;\n flex: 1;\n overflow-x: hidden;\n text-overflow: ellipsis;\n"],["\n font-family: ",";\n margin-left: 10px;\n flex: 1;\n overflow-x: hidden;\n text-overflow: ellipsis;\n"])),(function(e){return e.theme.typography.code.fontFamily})),Ms=_e.div(as||(as=Object(o.__makeTemplateObject)(["\n padding: 10px 30px 10px ",";\n border-radius: ",";\n background-color: ",";\n display: flex;\n white-space: nowrap;\n align-items: center;\n border: ",";\n border-bottom: ",";\n transition: border-color 0.25s ease;\n\n ","\n\n ."," {\n color: ","\n }\n"],["\n padding: 10px 30px 10px ",";\n border-radius: ",";\n background-color: ",";\n display: flex;\n white-space: nowrap;\n align-items: center;\n border: ",";\n border-bottom: ",";\n transition: border-color 0.25s ease;\n\n ","\n\n ."," {\n color: ","\n }\n"])),(function(e){return e.inverted?"10px":"20px"}),(function(e){return e.inverted?"0":"4px 4px 0 0"}),(function(e){return e.inverted?"transparent":e.theme.codeSample.backgroundColor}),(function(e){return e.inverted?"0":"1px solid transparent"}),(function(e){return e.inverted?"1px solid #ccc":"0"}),(function(e){return e.expanded&&!e.inverted&&"border-color: "+e.theme.colors.border.dark+";"||""}),Ls,(function(e){return e.inverted?e.theme.colors.text.primary:"#ffffff"})),Ds=_e.span.attrs((function(e){return{className:"http-verb "+e.type}}))(ss||(ss=Object(o.__makeTemplateObject)(["\n font-size: 0.929em;\n line-height: 20px;\n background-color: ",";\n color: #ffffff;\n padding: 3px 10px;\n text-transform: uppercase;\n font-family: ",";\n margin: 0;\n"],["\n font-size: 0.929em;\n line-height: 20px;\n background-color: ",";\n color: #ffffff;\n padding: 3px 10px;\n text-transform: uppercase;\n font-family: ",";\n margin: 0;\n"])),(function(e){return e.theme.colors.http[e.type]||"#999999"}),(function(e){return e.theme.typography.headings.fontFamily})),Fs=_e.div(ls||(ls=Object(o.__makeTemplateObject)(["\n position: absolute;\n width: 100%;\n z-index: 100;\n background: #fafafa;\n color: #263238;\n box-sizing: border-box;\n box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.33);\n overflow: hidden;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n transition: all 0.25s ease;\n\n ","\n"],["\n position: absolute;\n width: 100%;\n z-index: 100;\n background: #fafafa;\n color: #263238;\n box-sizing: border-box;\n box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.33);\n overflow: hidden;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n transition: all 0.25s ease;\n\n ","\n"])),(function(e){return e.expanded?"":"transform: translateY(-50%) scaleY(0);"})),zs=_e.div(cs||(cs=Object(o.__makeTemplateObject)(["\n padding: 10px;\n"],["\n padding: 10px;\n"]))),Us=_e.div(us||(us=Object(o.__makeTemplateObject)(["\n padding: 5px;\n border: 1px solid #ccc;\n background: #fff;\n word-break: break-all;\n color: ",";\n > span {\n color: ",";\n }\n"],["\n padding: 5px;\n border: 1px solid #ccc;\n background: #fff;\n word-break: break-all;\n color: ",";\n > span {\n color: ",";\n }\n"])),(function(e){return e.theme.colors.primary.main}),(function(e){return e.theme.colors.text.primary})),Bs=function(e){function t(t){var n=e.call(this,t)||this;return n.toggle=function(){n.setState({expanded:!n.state.expanded})},n.state={expanded:!1},n}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this,t=this.props,n=t.operation,r=t.inverted,o=t.hideHostname,a=this.state.expanded;return i.createElement(Me.Consumer,null,(function(t){return i.createElement(Ns,null,i.createElement(Ms,{onClick:e.toggle,expanded:a,inverted:r},i.createElement(Ds,{type:n.httpVerb}," ",n.httpVerb)," ",i.createElement(Ls,null,n.path),i.createElement(ir,{float:"right",color:r?"black":"white",size:"20px",direction:a?"up":"down",style:{marginRight:"-25px"}})),i.createElement(Fs,{expanded:a},n.servers.map((function(e){var r,a,s=t.expandDefaultServerVariables?(r=e.url,void 0===(a=e.variables)&&(a={}),r.replace(/(?:{)(\w+)(?:})/g,(function(e,t){return a[t]&&a[t].default||e}))):e.url;return i.createElement(zs,{key:s},i.createElement(oi,{source:e.description||"",compact:!0}),i.createElement(Rs,null,i.createElement(Us,null,i.createElement("span",null,o||t.hideHostname?function(e){try{return pe(e).pathname}catch(t){return e}}(s):s),n.path)))}))))}))},t}(i.Component),$s=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props,t=e.place,n=e.parameters;return n&&n.length?i.createElement("div",{key:t},i.createElement(jn,null,t," Parameters"),i.createElement(mr,null,i.createElement("tbody",null,ie(n,(function(e,t){return i.createElement(Pa,{key:e.name,isLast:t,field:e,showExamples:!0})}))))):null},t}(i.PureComponent),qs=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.switchMedia=function(e){var n=e.value;t.props.content&&t.props.content.activate(parseInt(n,10))},t}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this,t=this.props.content;if(!t||!t.mediaTypes||!t.mediaTypes.length)return null;var n=t.activeMimeIdx,r=t.mediaTypes.map((function(e,t){return{label:e.name,value:t.toString()}}));return i.createElement(i.Fragment,null,i.createElement((function(t){var n=t.children;return e.props.withLabel?i.createElement(ra,null,i.createElement(na,null,"Content type"),n):n}),null,this.props.renderDropdown({value:r[n],options:r,onChange:this.switchMedia})),this.props.children(t.active))},t=Object(o.__decorate)([la.a],t)}(i.Component);var Ws=["path","query","cookie","header"],Hs=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.orderParams=function(e){var t={};return e.forEach((function(e){var n,r,o;n=t,r=e.in,o=e,n[r]||(n[r]=[]),n[r].push(o)})),t},t.prototype.render=function(){var e=this.props,t=e.body,n=e.parameters,r=void 0===n?[]:n;if(void 0===t&&void 0===r)return null;var o=this.orderParams(r),a=r.length>0?Ws:[],s=t&&t.content,l=t&&t.description;return i.createElement(i.Fragment,null,a.map((function(e){return i.createElement($s,{key:e,place:e,parameters:o[e]})})),s&&i.createElement(Ys,{content:s,description:l}))},t}(i.PureComponent);function Vs(e){return i.createElement(jn,{key:"header"},"Request Body schema: ",i.createElement(Ho,Object(o.__assign)({},e)))}function Ys(e){var t=e.content,n=e.description;return i.createElement(qs,{content:t,renderDropdown:Vs},(function(e){var t=e.schema;return i.createElement(i.Fragment,null,void 0!==n&&i.createElement(oi,{source:n}),i.createElement(Da,{skipReadOnly:!0,key:"schema",schema:t}))}))}var Qs,Gs,Xs,Ks,Zs,Js,el=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.renderDropdown=function(e){return i.createElement(Ho,Object(o.__assign)({Label:ta,Dropdown:oa},e))},t}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this,t=this.props.content;return void 0===t?null:i.createElement(qs,{content:t,renderDropdown:this.renderDropdown,withLabel:!0},(function(t){return i.createElement(aa,{key:"samples",mediaType:t,renderDropdown:e.renderDropdown})}))},t=Object(o.__decorate)([la.a],t)}(i.Component),tl=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.operation.codeSamples,t=e.length>0,n=1===e.length&&this.context.hideSingleRequestSampleTab;return t&&i.createElement("div",null,i.createElement(Tn,null," Request samples "),i.createElement(ro,{defaultIndex:0},i.createElement(Vr,{hidden:n},e.map((function(e){return i.createElement(Qr,{key:e.lang+"_"+(e.label||"")},void 0!==e.label?e.label:e.lang)}))),e.map((function(e){return i.createElement(Xr,{key:e.lang+"_"+(e.label||"")},function(e){return"payload"===e.lang&&e.requestBodyContent}(e)?i.createElement("div",null,i.createElement(el,{content:e.requestBodyContent})):i.createElement(ji,{lang:e.lang,source:e.source}))}))))||null},t.contextType=Me,t=Object(o.__decorate)([la.a],t)}(i.Component),nl=_e(function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props,t=e.title,n=e.type,r=e.empty,o=e.code,a=e.opened,s=e.className,l=e.onClick;return i.createElement("div",{className:s,onClick:!r&&l||void 0},!r&&i.createElement(ir,{size:"1.5em",color:n,direction:a?"down":"right",float:"left"}),i.createElement("strong",null,o," "),i.createElement(oi,{compact:!0,inline:!0,source:t}))},t}(i.PureComponent))(Qs||(Qs=Object(o.__makeTemplateObject)(["\n padding: 10px;\n border-radius: 2px;\n margin-bottom: 4px;\n line-height: 1.5em;\n background-color: #f2f2f2;\n cursor: pointer;\n\n color: ",";\n background-color: ",";\n\n ",";\n"],["\n padding: 10px;\n border-radius: 2px;\n margin-bottom: 4px;\n line-height: 1.5em;\n background-color: #f2f2f2;\n cursor: pointer;\n\n color: ",";\n background-color: ",";\n\n ",";\n"])),(function(e){return e.theme.colors.responses[e.type].color}),(function(e){return e.theme.colors.responses[e.type].backgroundColor}),(function(e){return e.empty?'\ncursor: default;\n&::before {\n content: "—";\n font-weight: bold;\n width: 1.5em;\n text-align: center;\n display: inline-block;\n}\n':""})),rl=_e.div(Gs||(Gs=Object(o.__makeTemplateObject)(["\n padding: 10px;\n"],["\n padding: 10px;\n"]))),ol=_e(jn.withComponent("caption"))(Xs||(Xs=Object(o.__makeTemplateObject)(["\n text-align: left;\n margin-top: 1em;\n caption-side: top;\n"],["\n text-align: left;\n margin-top: 1em;\n caption-side: top;\n"]))),il=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.headers;return void 0===e||0===e.length?null:i.createElement(mr,null,i.createElement(ol,null," Response Headers "),i.createElement("tbody",null,ie(e,(function(e,t){return i.createElement(Pa,{isLast:t,key:e.name,field:e,showExamples:!0})}))))},t}(i.PureComponent),al=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.renderDropdown=function(e){return i.createElement(jn,{key:"header"},"Response Schema: ",i.createElement(Ho,Object(o.__assign)({},e)))},t}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.response,t=e.description,n=e.headers,r=e.content;return i.createElement(i.Fragment,null,t&&i.createElement(oi,{source:t}),i.createElement(il,{headers:n}),i.createElement(qs,{content:r,renderDropdown:this.renderDropdown},(function(e){var t=e.schema;return i.createElement(Da,{skipWriteOnly:!0,key:"schema",schema:t})})))},t}(i.PureComponent),sl=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.toggle=function(){t.props.response.toggle()},t}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.response,t=e.headers,n=e.type,r=e.summary,o=e.description,a=e.code,s=e.expanded,l=e.content,c=void 0===l?[]:l.mediaTypes.filter((function(e){return void 0!==e.schema})),u=0===t.length&&0===c.length&&!o;return i.createElement("div",null,i.createElement(nl,{onClick:this.toggle,type:n,empty:u,title:r||"",code:a,opened:s}),s&&!u&&i.createElement(rl,null,i.createElement(al,{response:this.props.response})))},t=Object(o.__decorate)([la.a],t)}(i.Component),ll=_e.h3(Ks||(Ks=Object(o.__makeTemplateObject)(["\n font-size: 18px;\n padding: 0.2em 0;\n margin: 3em 0 1.1em;\n color: #253137;\n font-weight: normal;\n"],["\n font-size: 18px;\n padding: 0.2em 0;\n margin: 3em 0 1.1em;\n color: #253137;\n font-weight: normal;\n"]))),cl=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.responses;return e&&0!==e.length?i.createElement("div",null,i.createElement(ll,null," Responses "),e.map((function(e){return i.createElement(sl,{key:e.code,response:e})}))):null},t}(i.PureComponent),ul=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.operation.responses.filter((function(e){return e.content&&e.content.hasSample}));return e.length>0&&i.createElement("div",null,i.createElement(Tn,null," Response samples "),i.createElement(ro,{defaultIndex:0},i.createElement(Vr,null,e.map((function(e){return i.createElement(Qr,{className:"tab-"+e.type,key:e.code},e.code)}))),e.map((function(e){return i.createElement(Xr,{key:e.code},i.createElement("div",null,i.createElement(el,{content:e.content})))}))))||null},t=Object(o.__decorate)([la.a],t)}(i.Component),pl=_e(kn)(Zs||(Zs=Object(o.__makeTemplateObject)(["\n backface-visibility: hidden;\n contain: content;\n\n overflow: hidden;\n"],["\n backface-visibility: hidden;\n contain: content;\n\n overflow: hidden;\n"]))),fl=_e.div(Js||(Js=Object(o.__makeTemplateObject)(["\n margin-bottom: ","px;\n"],["\n margin-bottom: ","px;\n"])),(function(e){return 6*e.theme.spacing.unit})),dl=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.operation,t=e.name,n=e.description,r=e.deprecated,o=e.externalDocs,a=!(!n&&!o);return i.createElement(Me.Consumer,null,(function(s){return i.createElement(pl,null,i.createElement(vn,null,i.createElement(Sn,null,i.createElement(Fn,{to:e.id}),t," ",r&&i.createElement(ar,{type:"warning"}," Deprecated ")),s.pathInMiddlePanel&&i.createElement(Bs,{operation:e,inverted:!0}),a&&i.createElement(fl,null,void 0!==n&&i.createElement(oi,{source:n}),o&&i.createElement(Oa,{externalDocs:o})),i.createElement(Sa,{extensions:e.extensions}),i.createElement(Ps,{securities:e.security}),i.createElement(Hs,{parameters:e.parameters,body:e.requestBody}),i.createElement(cl,{responses:e.responses})),i.createElement(wn,null,!s.pathInMiddlePanel&&i.createElement(Bs,{operation:e}),i.createElement(tl,{operation:e}),i.createElement(ul,{operation:e})))}))},t=Object(o.__decorate)([la.a],t)}(i.Component),hl=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.items;return 0===e.length?null:e.map((function(e){return i.createElement(ml,{item:e,key:e.id})}))},t=Object(o.__decorate)([la.a],t)}(i.Component),ml=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e,t=this.props.item;switch(t.type){case"group":e=null;break;case"tag":case"section":e=i.createElement(yl,Object(o.__assign)({},this.props));break;case"operation":e=i.createElement(vl,{item:t});break;default:e=i.createElement(yl,Object(o.__assign)({},this.props))}return i.createElement(i.Fragment,null,e&&i.createElement(bn,{id:t.id,underlined:"operation"===t.type},e),t.items&&i.createElement(hl,{items:t.items}))},t=Object(o.__decorate)([la.a],t)}(i.Component),gl=function(e){return i.createElement(vn,{compact:!0},e)},yl=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.item,t=e.name,n=e.description,r=e.externalDocs,o=2===e.level?Sn:En;return i.createElement(i.Fragment,null,i.createElement(kn,null,i.createElement(vn,{compact:!1},i.createElement(o,null,i.createElement(Fn,{to:this.props.item.id}),t))),i.createElement(Os,{source:n||"",htmlWrap:gl}),r&&i.createElement(kn,null,i.createElement(vn,null,i.createElement(Oa,{externalDocs:r}))))},t=Object(o.__decorate)([la.a],t)}(i.Component),vl=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){return i.createElement(dl,{operation:this.props.item})},t=Object(o.__decorate)([la.a],t)}(i.Component),bl=_e.span.attrs((function(e){return{className:"operation-type "+e.type}}))(wl||(wl=Object(o.__makeTemplateObject)(["\n width: 32px;\n display: inline-block;\n height: ",";\n line-height: ",";\n background-color: #333;\n border-radius: 3px;\n background-repeat: no-repeat;\n background-position: 6px 4px;\n font-size: 7px;\n font-family: Verdana; // web-safe\n color: white;\n text-transform: uppercase;\n text-align: center;\n font-weight: bold;\n vertical-align: middle;\n margin-right: 6px;\n margin-top: 2px;\n\n &.get {\n background-color: ",";\n }\n\n &.post {\n background-color: ",";\n }\n\n &.put {\n background-color: ",";\n }\n\n &.options {\n background-color: ",";\n }\n\n &.patch {\n background-color: ",";\n }\n\n &.delete {\n background-color: ",";\n }\n\n &.basic {\n background-color: ",";\n }\n\n &.link {\n background-color: ",";\n }\n\n &.head {\n background-color: ",";\n }\n"],["\n width: 32px;\n display: inline-block;\n height: ",";\n line-height: ",";\n background-color: #333;\n border-radius: 3px;\n background-repeat: no-repeat;\n background-position: 6px 4px;\n font-size: 7px;\n font-family: Verdana; // web-safe\n color: white;\n text-transform: uppercase;\n text-align: center;\n font-weight: bold;\n vertical-align: middle;\n margin-right: 6px;\n margin-top: 2px;\n\n &.get {\n background-color: ",";\n }\n\n &.post {\n background-color: ",";\n }\n\n &.put {\n background-color: ",";\n }\n\n &.options {\n background-color: ",";\n }\n\n &.patch {\n background-color: ",";\n }\n\n &.delete {\n background-color: ",";\n }\n\n &.basic {\n background-color: ",";\n }\n\n &.link {\n background-color: ",";\n }\n\n &.head {\n background-color: ",";\n }\n"])),(function(e){return e.theme.typography.code.fontSize}),(function(e){return e.theme.typography.code.fontSize}),(function(e){return e.theme.colors.http.get}),(function(e){return e.theme.colors.http.post}),(function(e){return e.theme.colors.http.put}),(function(e){return e.theme.colors.http.options}),(function(e){return e.theme.colors.http.patch}),(function(e){return e.theme.colors.http.delete}),(function(e){return e.theme.colors.http.basic}),(function(e){return e.theme.colors.http.link}),(function(e){return e.theme.colors.http.head}));function xl(e,t){var n=t.theme;return e>1?q(.1,n.menu.backgroundColor):1===e?q(.05,n.menu.backgroundColor):""}var wl,kl,Ol,_l,El,Sl,Tl,jl,Cl,Al,Il,Pl=_e.ul(kl||(kl=Object(o.__makeTemplateObject)(["\n margin: 0;\n padding: 0;\n\n & & {\n font-size: 0.929em;\n }\n\n ",";\n"],["\n margin: 0;\n padding: 0;\n\n & & {\n font-size: 0.929em;\n }\n\n ",";\n"])),(function(e){return e.expanded?"":"display: none;"})),Rl=_e.li(Ol||(Ol=Object(o.__makeTemplateObject)(["\n list-style: none inside none;\n overflow: hidden;\n text-overflow: ellipsis;\n padding: 0;\n ",";\n"],["\n list-style: none inside none;\n overflow: hidden;\n text-overflow: ellipsis;\n padding: 0;\n ",";\n"])),(function(e){return 0===e.depth?"margin-top: 15px":""})),Nl={0:be(_l||(_l=Object(o.__makeTemplateObject)(["\n opacity: 0.7;\n text-transform: ",";\n font-size: 0.8em;\n padding-bottom: 0;\n cursor: default;\n color: ",";\n "],["\n opacity: 0.7;\n text-transform: ",";\n font-size: 0.8em;\n padding-bottom: 0;\n cursor: default;\n color: ",";\n "])),(function(e){return e.theme.menu.groupItems.textTransform}),(function(e){return e.theme.menu.textColor})),1:be(El||(El=Object(o.__makeTemplateObject)(["\n font-size: 0.929em;\n text-transform: ",";\n &:hover {\n color: ",";\n }\n "],["\n font-size: 0.929em;\n text-transform: ",";\n &:hover {\n color: ",";\n }\n "])),(function(e){return e.theme.menu.level1Items.textTransform}),(function(e){return e.theme.menu.activeTextColor})),2:be(Sl||(Sl=Object(o.__makeTemplateObject)(["\n color: ",";\n "],["\n color: ",";\n "])),(function(e){return e.theme.menu.textColor}))},Ll=_e.label.attrs((function(e){return{role:"menuitem",className:Nr("-depth"+e.depth,{active:e.active})}}))(Tl||(Tl=Object(o.__makeTemplateObject)(["\n cursor: pointer;\n color: ",";\n margin: 0;\n padding: 12.5px ","px;\n ","\n display: flex;\n justify-content: space-between;\n font-family: ",";\n ",";\n background-color: ",";\n\n ",";\n\n &:hover {\n background-color: ",";\n }\n\n "," {\n height: ",";\n width: ",";\n polygon {\n fill: ",";\n }\n }\n"],["\n cursor: pointer;\n color: ",";\n margin: 0;\n padding: 12.5px ","px;\n ","\n display: flex;\n justify-content: space-between;\n font-family: ",";\n ",";\n background-color: ",";\n\n ",";\n\n &:hover {\n background-color: ",";\n }\n\n "," {\n height: ",";\n width: ",";\n polygon {\n fill: ",";\n }\n }\n"])),(function(e){return e.active?e.theme.menu.activeTextColor:e.theme.menu.textColor}),(function(e){return 4*e.theme.spacing.unit}),(function(e){var t=e.depth,n=e.type,r=e.theme;return"section"===n&&t>1&&"padding-left: "+8*r.spacing.unit+"px;"||""}),(function(e){return e.theme.typography.headings.fontFamily}),(function(e){return Nl[e.depth]}),(function(e){return e.active?xl(e.depth,e):""}),(function(e){return e.deprecated&&sr||""}),(function(e){return xl(e.depth,e)}),ir,(function(e){return e.theme.menu.arrow.size}),(function(e){return e.theme.menu.arrow.size}),(function(e){return e.theme.menu.arrow.color})),Ml=_e.span(jl||(jl=Object(o.__makeTemplateObject)(["\n display: inline-block;\n vertical-align: middle;\n width: ",";\n overflow: hidden;\n text-overflow: ellipsis;\n"],["\n display: inline-block;\n vertical-align: middle;\n width: ",";\n overflow: hidden;\n text-overflow: ellipsis;\n"])),(function(e){return e.width?e.width:"auto"})),Dl=_e.div(Cl||(Cl=Object(o.__makeTemplateObject)(["\n ",";\n"],["\n ",";\n"])),(function(e){var t=e.theme;return"\n font-size: 0.8em;\n margin-top: "+2*t.spacing.unit+"px;\n padding: 0 "+4*t.spacing.unit+"px;\n text-align: left;\n\n opacity: 0.7;\n\n a,\n a:visited,\n a:hover {\n color: "+t.menu.textColor+" !important;\n border-top: 1px solid "+q(.1,t.menu.backgroundColor)+";\n padding: "+t.spacing.unit+"px 0;\n display: block;\n }\n"})),Fl=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.ref=i.createRef(),t.activate=function(e){t.props.onActivate(t.props.item),e.stopPropagation()},t}return Object(o.__extends)(t,e),t.prototype.componentDidMount=function(){this.scrollIntoViewIfActive()},t.prototype.componentDidUpdate=function(){this.scrollIntoViewIfActive()},t.prototype.scrollIntoViewIfActive=function(){this.props.item.active&&this.ref.current&&this.ref.current.scrollIntoViewIfNeeded()},t.prototype.render=function(){var e=this.props,t=e.item,n=e.withoutChildren;return i.createElement(Rl,{onClick:this.activate,depth:t.depth,"data-item-id":t.id},"operation"===t.type?i.createElement(zl,Object(o.__assign)({},this.props,{item:t})):i.createElement(Ll,{depth:t.depth,active:t.active,type:t.type,ref:this.ref},i.createElement(Ml,{title:t.name},t.name,this.props.children),t.depth>0&&t.items.length>0&&i.createElement(ir,{float:"right",direction:t.expanded?"down":"right"})||null),!n&&t.items&&t.items.length>0&&i.createElement(Ul,{expanded:t.expanded,items:t.items,onActivate:this.props.onActivate}))},t=Object(o.__decorate)([la.a],t)}(i.Component),zl=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.ref=i.createRef(),t}return Object(o.__extends)(t,e),t.prototype.componentDidUpdate=function(){this.props.item.active&&this.ref.current&&this.ref.current.scrollIntoViewIfNeeded()},t.prototype.render=function(){var e,t=this.props.item;return i.createElement(Ll,{depth:t.depth,active:t.active,deprecated:t.deprecated,ref:this.ref},i.createElement(bl,{type:t.httpVerb},{delete:"del",options:"opts"}[e=t.httpVerb]||e),i.createElement(Ml,{width:"calc(100% - 38px)"},t.name,this.props.children))},t=Object(o.__decorate)([la.a],t)}(i.Component),Ul=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this,t=this.props,n=t.items,r=t.root,a=t.className,s=null==this.props.expanded||this.props.expanded;return i.createElement(Pl,Object(o.__assign)({className:a,style:this.props.style,expanded:s},r?{role:"navigation"}:{}),n.map((function(t,n){return i.createElement(Fl,{key:n,item:t,onActivate:e.props.onActivate})})))},t=Object(o.__decorate)([la.a],t)}(i.Component),Bl=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.activate=function(e){if(e&&e.active&&t.context.menuToggle)return e.expanded?e.collapse():e.expand();t.props.menu.activateAndScroll(e,!0),setTimeout((function(){t._updateScroll&&t._updateScroll()}))},t.saveScrollUpdate=function(e){t._updateScroll=e},t}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.menu;return i.createElement(Wo,{updateFn:this.saveScrollUpdate,className:this.props.className,options:{wheelPropagation:!1}},i.createElement(Ul,{items:e.items,onActivate:this.activate,root:!0}),i.createElement(Dl,null,i.createElement("a",{target:"_blank",rel:"noopener noreferrer",href:"https://github.com/Redocly/redoc"},"Documentation Powered by ReDoc")))},t.contextType=Me,t=Object(o.__decorate)([la.a],t)}(i.Component),$l=function(e){var t=e.open?8:-4;return i.createElement(Wl,null,i.createElement(ql,{size:15,style:{transform:"translate(2px, "+t+"px) rotate(180deg)",transition:"transform 0.2s ease"}}),i.createElement(ql,{size:15,style:{transform:"translate(2px, "+(0-t)+"px)",transition:"transform 0.2s ease"}}))},ql=function(e){var t=e.size,n=void 0===t?10:t,r=e.className,o=void 0===r?"":r,a=e.style;return i.createElement("svg",{className:o,style:a||{},viewBox:"0 0 926.23699 573.74994",version:"1.1",x:"0px",y:"0px",width:n,height:n},i.createElement("g",{transform:"translate(904.92214,-879.1482)"},i.createElement("path",{d:"\n m -673.67664,1221.6502 -231.2455,-231.24803 55.6165,\n -55.627 c 30.5891,-30.59485 56.1806,-55.627 56.8701,-55.627 0.6894,\n 0 79.8637,78.60862 175.9427,174.68583 l 174.6892,174.6858 174.6892,\n -174.6858 c 96.079,-96.07721 175.253196,-174.68583 175.942696,\n -174.68583 0.6895,0 26.281,25.03215 56.8701,\n 55.627 l 55.6165,55.627 -231.245496,231.24803 c -127.185,127.1864\n -231.5279,231.248 -231.873,231.248 -0.3451,0 -104.688,\n -104.0616 -231.873,-231.248 z\n ",fill:"currentColor"})))},Wl=_e.div(Al||(Al=Object(o.__makeTemplateObject)(["\n user-select: none;\n width: 20px;\n height: 20px;\n align-self: center;\n display: flex;\n flex-direction: column;\n color: ",";\n"],["\n user-select: none;\n width: 20px;\n height: 20px;\n align-self: center;\n display: flex;\n flex-direction: column;\n color: ",";\n"])),(function(e){return e.theme.colors.primary.main}));ee&&(Il=n(318));var Hl,Vl,Yl,Ql,Gl,Xl,Kl,Zl,Jl,ec,tc,nc,rc,oc,ic=Il&&Il(),ac=_e.div(Vl||(Vl=Object(o.__makeTemplateObject)(["\n width: ",";\n background-color: ",";\n overflow: hidden;\n display: flex;\n flex-direction: column;\n\n backface-visibility: hidden;\n /* contain: strict; TODO: breaks layout since Chrome 80*/\n\n height: 100vh;\n position: sticky;\n position: -webkit-sticky;\n top: 0;\n\n ",";\n\n @media print {\n display: none;\n }\n"],["\n width: ",";\n background-color: ",";\n overflow: hidden;\n display: flex;\n flex-direction: column;\n\n backface-visibility: hidden;\n /* contain: strict; TODO: breaks layout since Chrome 80*/\n\n height: 100vh;\n position: sticky;\n position: -webkit-sticky;\n top: 0;\n\n ",";\n\n @media print {\n display: none;\n }\n"])),(function(e){return e.theme.menu.width}),(function(e){return e.theme.menu.backgroundColor}),Oe("small")(Hl||(Hl=Object(o.__makeTemplateObject)(["\n position: fixed;\n z-index: 20;\n width: 100%;\n background: ",";\n display: ",";\n "],["\n position: fixed;\n z-index: 20;\n width: 100%;\n background: ",";\n display: ",";\n "])),(function(e){return e.theme.menu.backgroundColor}),(function(e){return e.open?"flex":"none"}))),sc=_e.div(Ql||(Ql=Object(o.__makeTemplateObject)(["\n outline: none;\n user-select: none;\n background-color: #f2f2f2;\n color: ",";\n display: none;\n cursor: pointer;\n position: fixed;\n right: 20px;\n z-index: 100;\n border-radius: 50%;\n box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);\n ",";\n\n bottom: 44px;\n\n width: 60px;\n height: 60px;\n padding: 0 20px;\n\n @media print {\n display: none;\n }\n"],["\n outline: none;\n user-select: none;\n background-color: #f2f2f2;\n color: ",";\n display: none;\n cursor: pointer;\n position: fixed;\n right: 20px;\n z-index: 100;\n border-radius: 50%;\n box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);\n ",";\n\n bottom: 44px;\n\n width: 60px;\n height: 60px;\n padding: 0 20px;\n\n @media print {\n display: none;\n }\n"])),(function(e){return e.theme.colors.primary.main}),Oe("small")(Yl||(Yl=Object(o.__makeTemplateObject)(["\n display: flex;\n "],["\n display: flex;\n "])))),lc=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.state={offsetTop:"0px"},t.toggleNavMenu=function(){t.props.menu.toggleSidebar()},t}return Object(o.__extends)(t,e),t.prototype.componentDidMount=function(){ic&&ic.add(this.stickyElement),this.setState({offsetTop:this.getScrollYOffset(this.context)})},t.prototype.componentWillUnmount=function(){ic&&ic.remove(this.stickyElement)},t.prototype.getScrollYOffset=function(e){return(void 0!==this.props.scrollYOffset?ge.normalizeScrollYOffset(this.props.scrollYOffset)():e.scrollYOffset())+"px"},t.prototype.render=function(){var e=this,t=this.props.menu.sideBarOpened,n=this.state.offsetTop;return i.createElement(i.Fragment,null,i.createElement(ac,{open:t,className:this.props.className,style:{top:n,height:"calc(100vh - "+n+")"},ref:function(t){e.stickyElement=t}},this.props.children),i.createElement(sc,{onClick:this.toggleNavMenu},i.createElement($l,{open:t})))},t.contextType=Me,t=Object(o.__decorate)([la.a],t)}(i.Component),cc=_e.div(Gl||(Gl=Object(o.__makeTemplateObject)(["\n ",";\n"],["\n ",";\n"])),(function(e){var t=e.theme;return"\n font-family: "+t.typography.fontFamily+";\n font-size: "+t.typography.fontSize+";\n font-weight: "+t.typography.fontWeightRegular+";\n line-height: "+t.typography.lineHeight+";\n color: "+t.colors.text.primary+";\n display: flex;\n position: relative;\n text-align: left;\n\n -webkit-font-smoothing: "+t.typography.smoothing+";\n font-smoothing: "+t.typography.smoothing+";\n "+(t.typography.optimizeSpeed?"text-rendering: optimizeSpeed !important":"")+";\n\n tap-highlight-color: rgba(0, 0, 0, 0);\n text-size-adjust: 100%;\n\n * {\n box-sizing: border-box;\n -webkit-tap-highlight-color: rgba(255, 255, 255, 0);\n }\n"})),uc=_e.div(Kl||(Kl=Object(o.__makeTemplateObject)(["\n z-index: 1;\n position: relative;\n overflow: hidden;\n width: calc(100% - ",");\n ",";\n\n contain: layout;\n"],["\n z-index: 1;\n position: relative;\n overflow: hidden;\n width: calc(100% - ",");\n ",";\n\n contain: layout;\n"])),(function(e){return e.theme.menu.width}),Oe("small",!0)(Xl||(Xl=Object(o.__makeTemplateObject)(["\n width: 100%;\n "],["\n width: 100%;\n "])))),pc=_e.div(Jl||(Jl=Object(o.__makeTemplateObject)(["\n background: ",";\n position: absolute;\n top: 0;\n bottom: 0;\n right: 0;\n width: ",";\n ",";\n"],["\n background: ",";\n position: absolute;\n top: 0;\n bottom: 0;\n right: 0;\n width: ",";\n ",";\n"])),(function(e){return e.theme.rightPanel.backgroundColor}),(function(e){var t=e.theme;if(t.rightPanel.width.endsWith("%")){var n=parseInt(t.rightPanel.width,10);return"calc((100% - "+t.menu.width+") * "+n/100+")"}return t.rightPanel.width}),Oe("medium",!0)(Zl||(Zl=Object(o.__makeTemplateObject)(["\n display: none;\n "],["\n display: none;\n "])))),fc=_e.div(ec||(ec=Object(o.__makeTemplateObject)(["\n padding: 5px 0;\n"],["\n padding: 5px 0;\n"]))),dc=_e.input.attrs((function(){return{className:"search-input"}}))(tc||(tc=Object(o.__makeTemplateObject)(["\n width: calc(100% - ","px);\n box-sizing: border-box;\n margin: 0 ","px;\n padding: 5px ","px 5px\n ","px;\n border: 0;\n border-bottom: 1px solid\n ",";\n font-family: ",";\n font-weight: bold;\n font-size: 13px;\n color: ",";\n background-color: transparent;\n outline: none;\n"],["\n width: calc(100% - ","px);\n box-sizing: border-box;\n margin: 0 ","px;\n padding: 5px ","px 5px\n ","px;\n border: 0;\n border-bottom: 1px solid\n ",";\n font-family: ",";\n font-weight: bold;\n font-size: 13px;\n color: ",";\n background-color: transparent;\n outline: none;\n"])),(function(e){return 8*e.theme.spacing.unit}),(function(e){return 4*e.theme.spacing.unit}),(function(e){return 2*e.theme.spacing.unit}),(function(e){return 4*e.theme.spacing.unit}),(function(e){var t=e.theme;return(V(t.menu.backgroundColor)>.5?q:Q)(.1,t.menu.backgroundColor)}),(function(e){return e.theme.typography.fontFamily}),(function(e){return e.theme.menu.textColor})),hc=_e((function(e){return i.createElement("svg",{className:e.className,version:"1.1",viewBox:"0 0 1000 1000",x:"0px",xmlns:"http://www.w3.org/2000/svg",y:"0px"},i.createElement("path",{d:"M968.2,849.4L667.3,549c83.9-136.5,66.7-317.4-51.7-435.6C477.1-25,252.5-25,113.9,113.4c-138.5,138.3-138.5,362.6,0,501C219.2,730.1,413.2,743,547.6,666.5l301.9,301.4c43.6,43.6,76.9,14.9,104.2-12.4C981,928.3,1011.8,893,968.2,849.4z M524.5,522c-88.9,88.7-233,88.7-321.8,0c-88.9-88.7-88.9-232.6,0-321.3c88.9-88.7,233-88.7,321.8,0C613.4,289.4,613.4,433.3,524.5,522z"}))})).attrs({className:"search-icon"})(nc||(nc=Object(o.__makeTemplateObject)(["\n position: absolute;\n left: ","px;\n height: 1.8em;\n width: 0.9em;\n\n path {\n fill: ",";\n }\n"],["\n position: absolute;\n left: ","px;\n height: 1.8em;\n width: 0.9em;\n\n path {\n fill: ",";\n }\n"])),(function(e){return 4*e.theme.spacing.unit}),(function(e){return e.theme.menu.textColor})),mc=_e.div(rc||(rc=Object(o.__makeTemplateObject)(["\n padding: ","px 0;\n background-color: ","};\n color: ",";\n min-height: 150px;\n max-height: 250px;\n border-top: ","};\n border-bottom: ","};\n margin-top: 10px;\n line-height: 1.4;\n font-size: 0.9em;\n\n "," {\n padding-top: 6px;\n padding-bottom: 6px;\n\n &:hover,\n &.active {\n background-color: ",";\n }\n\n > svg {\n display: none;\n }\n }\n"],["\n padding: ","px 0;\n background-color: ","};\n color: ",";\n min-height: 150px;\n max-height: 250px;\n border-top: ","};\n border-bottom: ","};\n margin-top: 10px;\n line-height: 1.4;\n font-size: 0.9em;\n\n "," {\n padding-top: 6px;\n padding-bottom: 6px;\n\n &:hover,\n &.active {\n background-color: ",";\n }\n\n > svg {\n display: none;\n }\n }\n"])),(function(e){return e.theme.spacing.unit}),(function(e){var t=e.theme;return q(.05,t.menu.backgroundColor)}),(function(e){return e.theme.menu.textColor}),(function(e){var t=e.theme;return q(.1,t.menu.backgroundColor)}),(function(e){var t=e.theme;return q(.1,t.menu.backgroundColor)}),Ll,(function(e){var t=e.theme;return q(.1,t.menu.backgroundColor)})),gc=_e.i(oc||(oc=Object(o.__makeTemplateObject)(["\n position: absolute;\n display: inline-block;\n width: ","px;\n text-align: center;\n right: ","px;\n line-height: 2em;\n vertical-align: middle;\n margin-right: 2px;\n cursor: pointer;\n font-style: normal;\n color: '#666';\n"],["\n position: absolute;\n display: inline-block;\n width: ","px;\n text-align: center;\n right: ","px;\n line-height: 2em;\n vertical-align: middle;\n margin-right: 2px;\n cursor: pointer;\n font-style: normal;\n color: '#666';\n"])),(function(e){return 2*e.theme.spacing.unit}),(function(e){return 4*e.theme.spacing.unit})),yc=function(e){function t(t){var n=e.call(this,t)||this;return n.activeItemRef=null,n.clear=function(){n.setState({results:[],term:"",activeItemIdx:-1}),n.props.marker.unmark()},n.handleKeyDown=function(e){if(27===e.keyCode&&n.clear(),40===e.keyCode&&(n.setState({activeItemIdx:Math.min(n.state.activeItemIdx+1,n.state.results.length-1)}),e.preventDefault()),38===e.keyCode&&(n.setState({activeItemIdx:Math.max(0,n.state.activeItemIdx-1)}),e.preventDefault()),13===e.keyCode){var t=n.state.results[n.state.activeItemIdx];if(t){var r=n.props.getItemById(t.meta);r&&n.props.onActivate(r)}}},n.search=function(e){var t=e.target.value;t.length<3?n.clearResults(t):n.setState({term:t},(function(){return n.searchCallback(n.state.term)}))},n.state={results:[],term:"",activeItemIdx:-1},n}return Object(o.__extends)(t,e),t.prototype.clearResults=function(e){this.setState({results:[],term:e}),this.props.marker.unmark()},t.prototype.setResults=function(e,t){this.setState({results:e}),this.props.marker.mark(t)},t.prototype.searchCallback=function(e){var t=this;this.props.search.search(e).then((function(n){t.setResults(n,e)}))},t.prototype.render=function(){var e=this,t=this.state.activeItemIdx,n=this.state.results.map((function(t){return{item:e.props.getItemById(t.meta),score:t.score}}));return n.sort((function(e,t){return t.score-e.score})),i.createElement(fc,{role:"search"},this.state.term&&i.createElement(gc,{onClick:this.clear},"×"),i.createElement(hc,null),i.createElement(dc,{value:this.state.term,onKeyDown:this.handleKeyDown,placeholder:"Search...",type:"text",onChange:this.search}),n.length>0&&i.createElement(Wo,{options:{wheelPropagation:!1}},i.createElement(mc,{"data-role":"search:results"},n.map((function(n,r){return i.createElement(Fl,{item:Object.create(n.item,{active:{value:r===t}}),onActivate:e.props.onActivate,withoutChildren:!0,key:n.item.id,"data-role":"search:result"})})))))},Object(o.__decorate)([We.bind,Object(We.debounce)(400)],t.prototype,"searchCallback",null),t}(i.PureComponent),vc=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.componentDidMount=function(){this.props.store.onDidMount()},t.prototype.componentWillUnmount=function(){this.props.store.dispose()},t.prototype.render=function(){var e=this.props.store,t=e.spec,n=e.menu,r=e.options,o=e.search,a=e.marker,s=this.props.store;return i.createElement(ke,{theme:r.theme},i.createElement(In,{value:this.props.store},i.createElement(De,{value:r},i.createElement(cc,{className:"redoc-wrap"},i.createElement(lc,{menu:n,className:"menu-content"},i.createElement(ks,{info:t.info}),!r.disableSearch&&i.createElement(yc,{search:o,marker:a,getItemById:n.getItemById,onActivate:n.activateAndScroll})||null,i.createElement(Bl,{menu:n})),i.createElement(uc,{className:"api-content"},i.createElement(vs,{store:s}),i.createElement(hl,{items:n.items})),i.createElement(pc,null)))))},t.propTypes={store:l.instanceOf(ps).isRequired},t}(i.Component),bc=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props,t=e.spec,n=e.specUrl,r=e.options,o=void 0===r?{}:r,a=e.onLoaded,s=void 0!==o.hideLoading,l=new ge(o);return i.createElement(Ie,null,i.createElement(Rn,{spec:t,specUrl:n,options:o,onLoaded:a},(function(e){var t=e.loading,n=e.store;return t?s?null:i.createElement(Le,{color:l.theme.colors.primary.main}):i.createElement(vc,{store:n})})))},t.propTypes={spec:function(e,t,n){return e.spec||e.specUrl?null:new Error("One of props 'spec' or 'specUrl' was not specified in '"+n+"'.")},specUrl:function(e,t,n){return e.spec||e.specUrl?null:new Error("One of props 'spec' or 'specUrl' was not specified in '"+n+"'.")},options:l.any,onLoaded:l.any},t}(i.PureComponent),xc="2.0.0-rc.24",wc="972dc37";function kc(e){var t=function(e){for(var t={},n=e.attributes,r=0;r<n.length;r++){var o=n[r];t[o.name]=o.value}return t}(e),n={};for(var r in t){n[r.replace(/-(.)/g,(function(e,t){return t.toUpperCase()}))]=t[r]}return n}function Oc(e,t,n,r){if(void 0===t&&(t={}),void 0===n&&(n=te("redoc")),null===n)throw new Error('"element" argument is not provided and <redoc> tag is not found on the page');var a,l;"string"==typeof e?a=e:"object"==typeof e&&(l=e),Object(s.render)(i.createElement(bc,{spec:l,onLoaded:r,specUrl:a,options:Object(o.__assign)(Object(o.__assign)({},t),kc(n))},["Loading..."]),n)}function _c(e,t,n){void 0===t&&(t=te("redoc")),vt();var r=ps.fromJS(e);bt(),setTimeout((function(){vt(),Object(s.hydrate)(i.createElement(vc,{store:r}),t,n),bt()}),0)}!function(){var e=te("redoc");if(e){var t=e.getAttribute("spec-url");t&&Oc(t,{},e)}}()}])})); -//# sourceMappingURL=redoc.standalone.js.map</script><style data-styled="bxcHYI jzMYjV YzuTm cCiYxb hZCbNs kKQhLA ibpoCO kzNiFq hclups ghctpd cLEtWf bbViyS iNzLCk gpbcFk fyUykq SmuWE hjRNaf NifDa jjozHG OtKQc cFwMcp nGwee fXybtJ fWqlcz iniCdN dluJDj hiuczA eesUPo cjtbAK kFNigF dTJWQH flfxUM gDsWLk gtbPCV eKrlKP gEjDMA dzbqSt WxWXp kGvRyb ioYTqA bSFXlp boajtD geWpKA dVvUxe jGRUDj fKHsnH hQBRTt bnFPhO espozG fDvFMp byLrBg hLVzSF evvbxn dtUibw ldTMcP dHLKeu jBjYbV bNYCAJ fRsrDc hPskZd iENVAs lkvpfX eftLSo bwgXFh kBWwoV irpqyy ecxnvs dpMbau gzAoUb gwfZGU kZHJcC kwGRVL fCJmC gbTit jCgylq LiUBH hoUoen eCjbJc bIrgla bcLONg kGwPhO fKyGWc hqYVjx lpeYvY bMfIUD jsTAxL beUper hrtKLV dDdNtD bvBDls cMefLx fLUKgj" data-styled-version="4.4.1"> +function fo(e){return getComputedStyle(e)}function ho(e,t){for(var n in t){var r=t[n];"number"==typeof r&&(r+="px"),e.style[n]=r}return e}function mo(e){var t=document.createElement("div");return t.className=e,t}var go="undefined"!=typeof Element&&(Element.prototype.matches||Element.prototype.webkitMatchesSelector||Element.prototype.mozMatchesSelector||Element.prototype.msMatchesSelector);function yo(e,t){if(!go)throw new Error("No element matching method supported");return go.call(e,t)}function vo(e){e.remove?e.remove():e.parentNode&&e.parentNode.removeChild(e)}function bo(e,t){return Array.prototype.filter.call(e.children,(function(e){return yo(e,t)}))}var xo="ps",wo="ps__rtl",ko={thumb:function(e){return"ps__thumb-"+e},rail:function(e){return"ps__rail-"+e},consuming:"ps__child--consume"},Oo={focus:"ps--focus",clicking:"ps--clicking",active:function(e){return"ps--active-"+e},scrolling:function(e){return"ps--scrolling-"+e}},_o={x:null,y:null};function Eo(e,t){var n=e.element.classList,r=Oo.scrolling(t);n.contains(r)?clearTimeout(_o[t]):n.add(r)}function So(e,t){_o[t]=setTimeout((function(){return e.isAlive&&e.element.classList.remove(Oo.scrolling(t))}),e.settings.scrollingThreshold)}var To=function(e){this.element=e,this.handlers={}},jo={isEmpty:{configurable:!0}};To.prototype.bind=function(e,t){void 0===this.handlers[e]&&(this.handlers[e]=[]),this.handlers[e].push(t),this.element.addEventListener(e,t,!1)},To.prototype.unbind=function(e,t){var n=this;this.handlers[e]=this.handlers[e].filter((function(r){return!(!t||r===t)||(n.element.removeEventListener(e,r,!1),!1)}))},To.prototype.unbindAll=function(){for(var e in this.handlers)this.unbind(e)},jo.isEmpty.get=function(){var e=this;return Object.keys(this.handlers).every((function(t){return 0===e.handlers[t].length}))},Object.defineProperties(To.prototype,jo);var Co=function(){this.eventElements=[]};function Io(e){if("function"==typeof window.CustomEvent)return new CustomEvent(e);var t=document.createEvent("CustomEvent");return t.initCustomEvent(e,!1,!1,void 0),t}function Ao(e,t,n,r,o){var i;if(void 0===r&&(r=!0),void 0===o&&(o=!1),"top"===t)i=["contentHeight","containerHeight","scrollTop","y","up","down"];else{if("left"!==t)throw new Error("A proper axis should be provided");i=["contentWidth","containerWidth","scrollLeft","x","left","right"]}!function(e,t,n,r,o){var i=n[0],a=n[1],s=n[2],l=n[3],c=n[4],u=n[5];void 0===r&&(r=!0);void 0===o&&(o=!1);var p=e.element;e.reach[l]=null,p[s]<1&&(e.reach[l]="start");p[s]>e[i]-e[a]-1&&(e.reach[l]="end");t&&(p.dispatchEvent(Io("ps-scroll-"+l)),t<0?p.dispatchEvent(Io("ps-scroll-"+c)):t>0&&p.dispatchEvent(Io("ps-scroll-"+u)),r&&function(e,t){Eo(e,t),So(e,t)}(e,l));e.reach[l]&&(t||o)&&p.dispatchEvent(Io("ps-"+l+"-reach-"+e.reach[l]))}(e,n,i,r,o)}function Po(e){return parseInt(e,10)||0}Co.prototype.eventElement=function(e){var t=this.eventElements.filter((function(t){return t.element===e}))[0];return t||(t=new To(e),this.eventElements.push(t)),t},Co.prototype.bind=function(e,t,n){this.eventElement(e).bind(t,n)},Co.prototype.unbind=function(e,t,n){var r=this.eventElement(e);r.unbind(t,n),r.isEmpty&&this.eventElements.splice(this.eventElements.indexOf(r),1)},Co.prototype.unbindAll=function(){this.eventElements.forEach((function(e){return e.unbindAll()})),this.eventElements=[]},Co.prototype.once=function(e,t,n){var r=this.eventElement(e),o=function(e){r.unbind(t,o),n(e)};r.bind(t,o)};var Ro={isWebKit:"undefined"!=typeof document&&"WebkitAppearance"in document.documentElement.style,supportsTouch:"undefined"!=typeof window&&("ontouchstart"in window||"maxTouchPoints"in window.navigator&&window.navigator.maxTouchPoints>0||window.DocumentTouch&&document instanceof window.DocumentTouch),supportsIePointer:"undefined"!=typeof navigator&&navigator.msMaxTouchPoints,isChrome:"undefined"!=typeof navigator&&/Chrome/i.test(navigator&&navigator.userAgent)};function No(e){var t=e.element,n=Math.floor(t.scrollTop),r=t.getBoundingClientRect();e.containerWidth=Math.ceil(r.width),e.containerHeight=Math.ceil(r.height),e.contentWidth=t.scrollWidth,e.contentHeight=t.scrollHeight,t.contains(e.scrollbarXRail)||(bo(t,ko.rail("x")).forEach((function(e){return vo(e)})),t.appendChild(e.scrollbarXRail)),t.contains(e.scrollbarYRail)||(bo(t,ko.rail("y")).forEach((function(e){return vo(e)})),t.appendChild(e.scrollbarYRail)),!e.settings.suppressScrollX&&e.containerWidth+e.settings.scrollXMarginOffset<e.contentWidth?(e.scrollbarXActive=!0,e.railXWidth=e.containerWidth-e.railXMarginWidth,e.railXRatio=e.containerWidth/e.railXWidth,e.scrollbarXWidth=Lo(e,Po(e.railXWidth*e.containerWidth/e.contentWidth)),e.scrollbarXLeft=Po((e.negativeScrollAdjustment+t.scrollLeft)*(e.railXWidth-e.scrollbarXWidth)/(e.contentWidth-e.containerWidth))):e.scrollbarXActive=!1,!e.settings.suppressScrollY&&e.containerHeight+e.settings.scrollYMarginOffset<e.contentHeight?(e.scrollbarYActive=!0,e.railYHeight=e.containerHeight-e.railYMarginHeight,e.railYRatio=e.containerHeight/e.railYHeight,e.scrollbarYHeight=Lo(e,Po(e.railYHeight*e.containerHeight/e.contentHeight)),e.scrollbarYTop=Po(n*(e.railYHeight-e.scrollbarYHeight)/(e.contentHeight-e.containerHeight))):e.scrollbarYActive=!1,e.scrollbarXLeft>=e.railXWidth-e.scrollbarXWidth&&(e.scrollbarXLeft=e.railXWidth-e.scrollbarXWidth),e.scrollbarYTop>=e.railYHeight-e.scrollbarYHeight&&(e.scrollbarYTop=e.railYHeight-e.scrollbarYHeight),function(e,t){var n={width:t.railXWidth},r=Math.floor(e.scrollTop);t.isRtl?n.left=t.negativeScrollAdjustment+e.scrollLeft+t.containerWidth-t.contentWidth:n.left=e.scrollLeft;t.isScrollbarXUsingBottom?n.bottom=t.scrollbarXBottom-r:n.top=t.scrollbarXTop+r;ho(t.scrollbarXRail,n);var o={top:r,height:t.railYHeight};t.isScrollbarYUsingRight?t.isRtl?o.right=t.contentWidth-(t.negativeScrollAdjustment+e.scrollLeft)-t.scrollbarYRight-t.scrollbarYOuterWidth-9:o.right=t.scrollbarYRight-e.scrollLeft:t.isRtl?o.left=t.negativeScrollAdjustment+e.scrollLeft+2*t.containerWidth-t.contentWidth-t.scrollbarYLeft-t.scrollbarYOuterWidth:o.left=t.scrollbarYLeft+e.scrollLeft;ho(t.scrollbarYRail,o),ho(t.scrollbarX,{left:t.scrollbarXLeft,width:t.scrollbarXWidth-t.railBorderXWidth}),ho(t.scrollbarY,{top:t.scrollbarYTop,height:t.scrollbarYHeight-t.railBorderYWidth})}(t,e),e.scrollbarXActive?t.classList.add(Oo.active("x")):(t.classList.remove(Oo.active("x")),e.scrollbarXWidth=0,e.scrollbarXLeft=0,t.scrollLeft=!0===e.isRtl?e.contentWidth:0),e.scrollbarYActive?t.classList.add(Oo.active("y")):(t.classList.remove(Oo.active("y")),e.scrollbarYHeight=0,e.scrollbarYTop=0,t.scrollTop=0)}function Lo(e,t){return e.settings.minScrollbarLength&&(t=Math.max(t,e.settings.minScrollbarLength)),e.settings.maxScrollbarLength&&(t=Math.min(t,e.settings.maxScrollbarLength)),t}function Mo(e,t){var n=t[0],r=t[1],o=t[2],i=t[3],a=t[4],s=t[5],l=t[6],c=t[7],u=t[8],p=e.element,f=null,d=null,h=null;function m(t){t.touches&&t.touches[0]&&(t[o]=t.touches[0].pageY),p[l]=f+h*(t[o]-d),Eo(e,c),No(e),t.stopPropagation(),t.preventDefault()}function g(){So(e,c),e[u].classList.remove(Oo.clicking),e.event.unbind(e.ownerDocument,"mousemove",m)}function y(t,a){f=p[l],a&&t.touches&&(t[o]=t.touches[0].pageY),d=t[o],h=(e[r]-e[n])/(e[i]-e[s]),a?e.event.bind(e.ownerDocument,"touchmove",m):(e.event.bind(e.ownerDocument,"mousemove",m),e.event.once(e.ownerDocument,"mouseup",g),t.preventDefault()),e[u].classList.add(Oo.clicking),t.stopPropagation()}e.event.bind(e[a],"mousedown",(function(e){y(e)})),e.event.bind(e[a],"touchstart",(function(e){y(e,!0)}))}var Do={"click-rail":function(e){e.element,e.event.bind(e.scrollbarY,"mousedown",(function(e){return e.stopPropagation()})),e.event.bind(e.scrollbarYRail,"mousedown",(function(t){var n=t.pageY-window.pageYOffset-e.scrollbarYRail.getBoundingClientRect().top>e.scrollbarYTop?1:-1;e.element.scrollTop+=n*e.containerHeight,No(e),t.stopPropagation()})),e.event.bind(e.scrollbarX,"mousedown",(function(e){return e.stopPropagation()})),e.event.bind(e.scrollbarXRail,"mousedown",(function(t){var n=t.pageX-window.pageXOffset-e.scrollbarXRail.getBoundingClientRect().left>e.scrollbarXLeft?1:-1;e.element.scrollLeft+=n*e.containerWidth,No(e),t.stopPropagation()}))},"drag-thumb":function(e){Mo(e,["containerWidth","contentWidth","pageX","railXWidth","scrollbarX","scrollbarXWidth","scrollLeft","x","scrollbarXRail"]),Mo(e,["containerHeight","contentHeight","pageY","railYHeight","scrollbarY","scrollbarYHeight","scrollTop","y","scrollbarYRail"])},keyboard:function(e){var t=e.element;e.event.bind(e.ownerDocument,"keydown",(function(n){if(!(n.isDefaultPrevented&&n.isDefaultPrevented()||n.defaultPrevented)&&(yo(t,":hover")||yo(e.scrollbarX,":focus")||yo(e.scrollbarY,":focus"))){var r,o=document.activeElement?document.activeElement:e.ownerDocument.activeElement;if(o){if("IFRAME"===o.tagName)o=o.contentDocument.activeElement;else for(;o.shadowRoot;)o=o.shadowRoot.activeElement;if(yo(r=o,"input,[contenteditable]")||yo(r,"select,[contenteditable]")||yo(r,"textarea,[contenteditable]")||yo(r,"button,[contenteditable]"))return}var i=0,a=0;switch(n.which){case 37:i=n.metaKey?-e.contentWidth:n.altKey?-e.containerWidth:-30;break;case 38:a=n.metaKey?e.contentHeight:n.altKey?e.containerHeight:30;break;case 39:i=n.metaKey?e.contentWidth:n.altKey?e.containerWidth:30;break;case 40:a=n.metaKey?-e.contentHeight:n.altKey?-e.containerHeight:-30;break;case 32:a=n.shiftKey?e.containerHeight:-e.containerHeight;break;case 33:a=e.containerHeight;break;case 34:a=-e.containerHeight;break;case 36:a=e.contentHeight;break;case 35:a=-e.contentHeight;break;default:return}e.settings.suppressScrollX&&0!==i||e.settings.suppressScrollY&&0!==a||(t.scrollTop-=a,t.scrollLeft+=i,No(e),function(n,r){var o=Math.floor(t.scrollTop);if(0===n){if(!e.scrollbarYActive)return!1;if(0===o&&r>0||o>=e.contentHeight-e.containerHeight&&r<0)return!e.settings.wheelPropagation}var i=t.scrollLeft;if(0===r){if(!e.scrollbarXActive)return!1;if(0===i&&n<0||i>=e.contentWidth-e.containerWidth&&n>0)return!e.settings.wheelPropagation}return!0}(i,a)&&n.preventDefault())}}))},wheel:function(e){var t=e.element;function n(n){var r=function(e){var t=e.deltaX,n=-1*e.deltaY;return void 0!==t&&void 0!==n||(t=-1*e.wheelDeltaX/6,n=e.wheelDeltaY/6),e.deltaMode&&1===e.deltaMode&&(t*=10,n*=10),t!=t&&n!=n&&(t=0,n=e.wheelDelta),e.shiftKey?[-n,-t]:[t,n]}(n),o=r[0],i=r[1];if(!function(e,n,r){if(!Ro.isWebKit&&t.querySelector("select:focus"))return!0;if(!t.contains(e))return!1;for(var o=e;o&&o!==t;){if(o.classList.contains(ko.consuming))return!0;var i=fo(o);if(r&&i.overflowY.match(/(scroll|auto)/)){var a=o.scrollHeight-o.clientHeight;if(a>0&&(o.scrollTop>0&&r<0||o.scrollTop<a&&r>0))return!0}if(n&&i.overflowX.match(/(scroll|auto)/)){var s=o.scrollWidth-o.clientWidth;if(s>0&&(o.scrollLeft>0&&n<0||o.scrollLeft<s&&n>0))return!0}o=o.parentNode}return!1}(n.target,o,i)){var a=!1;e.settings.useBothWheelAxes?e.scrollbarYActive&&!e.scrollbarXActive?(i?t.scrollTop-=i*e.settings.wheelSpeed:t.scrollTop+=o*e.settings.wheelSpeed,a=!0):e.scrollbarXActive&&!e.scrollbarYActive&&(o?t.scrollLeft+=o*e.settings.wheelSpeed:t.scrollLeft-=i*e.settings.wheelSpeed,a=!0):(t.scrollTop-=i*e.settings.wheelSpeed,t.scrollLeft+=o*e.settings.wheelSpeed),No(e),(a=a||function(n,r){var o=Math.floor(t.scrollTop),i=0===t.scrollTop,a=o+t.offsetHeight===t.scrollHeight,s=0===t.scrollLeft,l=t.scrollLeft+t.offsetWidth===t.scrollWidth;return!(Math.abs(r)>Math.abs(n)?i||a:s||l)||!e.settings.wheelPropagation}(o,i))&&!n.ctrlKey&&(n.stopPropagation(),n.preventDefault())}}void 0!==window.onwheel?e.event.bind(t,"wheel",n):void 0!==window.onmousewheel&&e.event.bind(t,"mousewheel",n)},touch:function(e){if(Ro.supportsTouch||Ro.supportsIePointer){var t=e.element,n={},r=0,o={},i=null;Ro.supportsTouch?(e.event.bind(t,"touchstart",c),e.event.bind(t,"touchmove",u),e.event.bind(t,"touchend",p)):Ro.supportsIePointer&&(window.PointerEvent?(e.event.bind(t,"pointerdown",c),e.event.bind(t,"pointermove",u),e.event.bind(t,"pointerup",p)):window.MSPointerEvent&&(e.event.bind(t,"MSPointerDown",c),e.event.bind(t,"MSPointerMove",u),e.event.bind(t,"MSPointerUp",p)))}function a(n,r){t.scrollTop-=r,t.scrollLeft-=n,No(e)}function s(e){return e.targetTouches?e.targetTouches[0]:e}function l(e){return(!e.pointerType||"pen"!==e.pointerType||0!==e.buttons)&&(!(!e.targetTouches||1!==e.targetTouches.length)||!(!e.pointerType||"mouse"===e.pointerType||e.pointerType===e.MSPOINTER_TYPE_MOUSE))}function c(e){if(l(e)){var t=s(e);n.pageX=t.pageX,n.pageY=t.pageY,r=(new Date).getTime(),null!==i&&clearInterval(i)}}function u(i){if(l(i)){var c=s(i),u={pageX:c.pageX,pageY:c.pageY},p=u.pageX-n.pageX,f=u.pageY-n.pageY;if(function(e,n,r){if(!t.contains(e))return!1;for(var o=e;o&&o!==t;){if(o.classList.contains(ko.consuming))return!0;var i=fo(o);if(r&&i.overflowY.match(/(scroll|auto)/)){var a=o.scrollHeight-o.clientHeight;if(a>0&&(o.scrollTop>0&&r<0||o.scrollTop<a&&r>0))return!0}if(n&&i.overflowX.match(/(scroll|auto)/)){var s=o.scrollWidth-o.clientWidth;if(s>0&&(o.scrollLeft>0&&n<0||o.scrollLeft<s&&n>0))return!0}o=o.parentNode}return!1}(i.target,p,f))return;a(p,f),n=u;var d=(new Date).getTime(),h=d-r;h>0&&(o.x=p/h,o.y=f/h,r=d),function(n,r){var o=Math.floor(t.scrollTop),i=t.scrollLeft,a=Math.abs(n),s=Math.abs(r);if(s>a){if(r<0&&o===e.contentHeight-e.containerHeight||r>0&&0===o)return 0===window.scrollY&&r>0&&Ro.isChrome}else if(a>s&&(n<0&&i===e.contentWidth-e.containerWidth||n>0&&0===i))return!0;return!0}(p,f)&&i.preventDefault()}}function p(){e.settings.swipeEasing&&(clearInterval(i),i=setInterval((function(){e.isInitialized?clearInterval(i):o.x||o.y?Math.abs(o.x)<.01&&Math.abs(o.y)<.01?clearInterval(i):(a(30*o.x,30*o.y),o.x*=.8,o.y*=.8):clearInterval(i)}),10))}}},Fo=function(e,t){var n=this;if(void 0===t&&(t={}),"string"==typeof e&&(e=document.querySelector(e)),!e||!e.nodeName)throw new Error("no element is specified to initialize PerfectScrollbar");for(var r in this.element=e,e.classList.add(xo),this.settings={handlers:["click-rail","drag-thumb","keyboard","wheel","touch"],maxScrollbarLength:null,minScrollbarLength:null,scrollingThreshold:1e3,scrollXMarginOffset:0,scrollYMarginOffset:0,suppressScrollX:!1,suppressScrollY:!1,swipeEasing:!0,useBothWheelAxes:!1,wheelPropagation:!0,wheelSpeed:1},t)this.settings[r]=t[r];this.containerWidth=null,this.containerHeight=null,this.contentWidth=null,this.contentHeight=null;var o,i,a=function(){return e.classList.add(Oo.focus)},s=function(){return e.classList.remove(Oo.focus)};this.isRtl="rtl"===fo(e).direction,!0===this.isRtl&&e.classList.add(wo),this.isNegativeScroll=(i=e.scrollLeft,e.scrollLeft=-1,o=e.scrollLeft<0,e.scrollLeft=i,o),this.negativeScrollAdjustment=this.isNegativeScroll?e.scrollWidth-e.clientWidth:0,this.event=new Co,this.ownerDocument=e.ownerDocument||document,this.scrollbarXRail=mo(ko.rail("x")),e.appendChild(this.scrollbarXRail),this.scrollbarX=mo(ko.thumb("x")),this.scrollbarXRail.appendChild(this.scrollbarX),this.scrollbarX.setAttribute("tabindex",0),this.event.bind(this.scrollbarX,"focus",a),this.event.bind(this.scrollbarX,"blur",s),this.scrollbarXActive=null,this.scrollbarXWidth=null,this.scrollbarXLeft=null;var l=fo(this.scrollbarXRail);this.scrollbarXBottom=parseInt(l.bottom,10),isNaN(this.scrollbarXBottom)?(this.isScrollbarXUsingBottom=!1,this.scrollbarXTop=Po(l.top)):this.isScrollbarXUsingBottom=!0,this.railBorderXWidth=Po(l.borderLeftWidth)+Po(l.borderRightWidth),ho(this.scrollbarXRail,{display:"block"}),this.railXMarginWidth=Po(l.marginLeft)+Po(l.marginRight),ho(this.scrollbarXRail,{display:""}),this.railXWidth=null,this.railXRatio=null,this.scrollbarYRail=mo(ko.rail("y")),e.appendChild(this.scrollbarYRail),this.scrollbarY=mo(ko.thumb("y")),this.scrollbarYRail.appendChild(this.scrollbarY),this.scrollbarY.setAttribute("tabindex",0),this.event.bind(this.scrollbarY,"focus",a),this.event.bind(this.scrollbarY,"blur",s),this.scrollbarYActive=null,this.scrollbarYHeight=null,this.scrollbarYTop=null;var c=fo(this.scrollbarYRail);this.scrollbarYRight=parseInt(c.right,10),isNaN(this.scrollbarYRight)?(this.isScrollbarYUsingRight=!1,this.scrollbarYLeft=Po(c.left)):this.isScrollbarYUsingRight=!0,this.scrollbarYOuterWidth=this.isRtl?function(e){var t=fo(e);return Po(t.width)+Po(t.paddingLeft)+Po(t.paddingRight)+Po(t.borderLeftWidth)+Po(t.borderRightWidth)}(this.scrollbarY):null,this.railBorderYWidth=Po(c.borderTopWidth)+Po(c.borderBottomWidth),ho(this.scrollbarYRail,{display:"block"}),this.railYMarginHeight=Po(c.marginTop)+Po(c.marginBottom),ho(this.scrollbarYRail,{display:""}),this.railYHeight=null,this.railYRatio=null,this.reach={x:e.scrollLeft<=0?"start":e.scrollLeft>=this.contentWidth-this.containerWidth?"end":null,y:e.scrollTop<=0?"start":e.scrollTop>=this.contentHeight-this.containerHeight?"end":null},this.isAlive=!0,this.settings.handlers.forEach((function(e){return Do[e](n)})),this.lastScrollTop=Math.floor(e.scrollTop),this.lastScrollLeft=e.scrollLeft,this.event.bind(this.element,"scroll",(function(e){return n.onScroll(e)})),No(this)};Fo.prototype.update=function(){this.isAlive&&(this.negativeScrollAdjustment=this.isNegativeScroll?this.element.scrollWidth-this.element.clientWidth:0,ho(this.scrollbarXRail,{display:"block"}),ho(this.scrollbarYRail,{display:"block"}),this.railXMarginWidth=Po(fo(this.scrollbarXRail).marginLeft)+Po(fo(this.scrollbarXRail).marginRight),this.railYMarginHeight=Po(fo(this.scrollbarYRail).marginTop)+Po(fo(this.scrollbarYRail).marginBottom),ho(this.scrollbarXRail,{display:"none"}),ho(this.scrollbarYRail,{display:"none"}),No(this),Ao(this,"top",0,!1,!0),Ao(this,"left",0,!1,!0),ho(this.scrollbarXRail,{display:""}),ho(this.scrollbarYRail,{display:""}))},Fo.prototype.onScroll=function(e){this.isAlive&&(No(this),Ao(this,"top",this.element.scrollTop-this.lastScrollTop),Ao(this,"left",this.element.scrollLeft-this.lastScrollLeft),this.lastScrollTop=Math.floor(this.element.scrollTop),this.lastScrollLeft=this.element.scrollLeft)},Fo.prototype.destroy=function(){this.isAlive&&(this.event.unbindAll(),vo(this.scrollbarX),vo(this.scrollbarY),vo(this.scrollbarXRail),vo(this.scrollbarYRail),this.removePsClasses(),this.element=null,this.scrollbarX=null,this.scrollbarY=null,this.scrollbarXRail=null,this.scrollbarYRail=null,this.isAlive=!1)},Fo.prototype.removePsClasses=function(){this.element.className=this.element.className.split(" ").filter((function(e){return!e.match(/^ps([-_].+|)$/)})).join(" ")};var zo,Uo,Bo=Fo,$o=n(100),qo=n.n($o),Wo=Bo||r,Ho=xe(zo||(zo=Object(o.__makeTemplateObject)(["",""],["",""])),qo.a&&qo.a.toString()),Vo=_e.div(Uo||(Uo=Object(o.__makeTemplateObject)(["\n position: relative;\n"],["\n position: relative;\n"]))),Yo=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.handleRef=function(e){t._container=e},t}return Object(o.__extends)(t,e),t.prototype.componentDidMount=function(){var e=this._container.parentElement&&this._container.parentElement.scrollTop||0;this.inst=new Wo(this._container,this.props.options||{}),this._container.scrollTo&&this._container.scrollTo(0,e)},t.prototype.componentDidUpdate=function(){this.inst.update()},t.prototype.componentWillUnmount=function(){this.inst.destroy()},t.prototype.render=function(){var e=this.props,t=e.children,n=e.className,r=e.updateFn;return r&&r(this.componentDidUpdate.bind(this)),i.createElement(i.Fragment,null,i.createElement(Ho,null),i.createElement(Vo,{className:"scrollbar-container "+n,ref:this.handleRef},t))},t}(i.Component);function Qo(e){return i.createElement(Me.Consumer,null,(function(t){return t.nativeScrollbars?i.createElement("div",{style:{overflow:"auto",msOverflowStyle:"-ms-autohiding-scrollbar"}},e.children):i.createElement(Yo,Object(o.__assign)({},e),e.children)}))}function Go(e){var t=e.Label,n=void 0===t?jr:t,r=e.Dropdown,a=void 0===r?Tr:r;return 1===e.options.length?i.createElement(n,null,e.options[0].label):i.createElement(a,Object(o.__assign)({},e))}var Xo,Ko,Zo=n(159),Jo=be(Xo||(Xo=Object(o.__makeTemplateObject)(["\n a {\n text-decoration: none;\n color: ",";\n\n &:visited {\n color: ",";\n }\n\n &:hover {\n color: ",";\n }\n }\n"],["\n a {\n text-decoration: none;\n color: ",";\n\n &:visited {\n color: ",";\n }\n\n &:hover {\n color: ",";\n }\n }\n"])),(function(e){return e.theme.typography.links.color}),(function(e){return e.theme.typography.links.visited}),(function(e){return e.theme.typography.links.hover})),ei=_e(lo)(Ko||(Ko=Object(o.__makeTemplateObject)(["\n\n font-family: ",";\n font-weight: ",";\n line-height: ",";\n\n p {\n &:last-child {\n margin-bottom: 0;\n }\n }\n\n ","\n\n ","\n\n h1 {\n ",";\n color: ",";\n margin-top: 0;\n }\n\n h2 {\n ",";\n color: ",";\n }\n\n code {\n color: ",";\n background-color: ",";\n\n font-family: ",";\n border-radius: 2px;\n border: 1px solid rgba(38, 50, 56, 0.1);\n padding: 0 ","px;\n font-size: ",";\n font-weight: ",";\n\n word-break: break-word;\n }\n\n pre {\n font-family: ",";\n white-space:",";\n background-color: ",";\n color: white;\n padding: ","px;\n overflow-x: auto;\n line-height: normal;\n border-radius: 0px\n border: 1px solid rgba(38, 50, 56, 0.1);\n\n code {\n background-color: transparent;\n color: white;\n padding: 0;\n\n &:before,\n &:after {\n content: none;\n }\n }\n }\n\n blockquote {\n margin: 0;\n margin-bottom: 1em;\n padding: 0 15px;\n color: #777;\n border-left: 4px solid #ddd;\n }\n\n img {\n max-width: 100%;\n box-sizing: content-box;\n }\n\n ul,\n ol {\n padding-left: 2em;\n margin: 0;\n margin-bottom: 1em;\n\n ul, ol {\n margin-bottom: 0;\n margin-top: 0;\n }\n }\n\n table {\n display: block;\n width: 100%;\n overflow: auto;\n word-break: normal;\n word-break: keep-all;\n border-collapse: collapse;\n border-spacing: 0;\n margin-top: 1.5em;\n margin-bottom: 1.5em;\n }\n\n table tr {\n background-color: #fff;\n border-top: 1px solid #ccc;\n\n &:nth-child(2n) {\n background-color: ",";\n }\n }\n\n table th,\n table td {\n padding: 6px 13px;\n border: 1px solid #ddd;\n }\n\n table th {\n text-align: left;\n font-weight: bold;\n }\n\n ",";\n\n ","\n\n ",";\n"],["\n\n font-family: ",";\n font-weight: ",";\n line-height: ",";\n\n p {\n &:last-child {\n margin-bottom: 0;\n }\n }\n\n ","\n\n ","\n\n h1 {\n ",";\n color: ",";\n margin-top: 0;\n }\n\n h2 {\n ",";\n color: ",";\n }\n\n code {\n color: ",";\n background-color: ",";\n\n font-family: ",";\n border-radius: 2px;\n border: 1px solid rgba(38, 50, 56, 0.1);\n padding: 0 ","px;\n font-size: ",";\n font-weight: ",";\n\n word-break: break-word;\n }\n\n pre {\n font-family: ",";\n white-space:",";\n background-color: ",";\n color: white;\n padding: ","px;\n overflow-x: auto;\n line-height: normal;\n border-radius: 0px\n border: 1px solid rgba(38, 50, 56, 0.1);\n\n code {\n background-color: transparent;\n color: white;\n padding: 0;\n\n &:before,\n &:after {\n content: none;\n }\n }\n }\n\n blockquote {\n margin: 0;\n margin-bottom: 1em;\n padding: 0 15px;\n color: #777;\n border-left: 4px solid #ddd;\n }\n\n img {\n max-width: 100%;\n box-sizing: content-box;\n }\n\n ul,\n ol {\n padding-left: 2em;\n margin: 0;\n margin-bottom: 1em;\n\n ul, ol {\n margin-bottom: 0;\n margin-top: 0;\n }\n }\n\n table {\n display: block;\n width: 100%;\n overflow: auto;\n word-break: normal;\n word-break: keep-all;\n border-collapse: collapse;\n border-spacing: 0;\n margin-top: 1.5em;\n margin-bottom: 1.5em;\n }\n\n table tr {\n background-color: #fff;\n border-top: 1px solid #ccc;\n\n &:nth-child(2n) {\n background-color: ",";\n }\n }\n\n table th,\n table td {\n padding: 6px 13px;\n border: 1px solid #ddd;\n }\n\n table th {\n text-align: left;\n font-weight: bold;\n }\n\n ",";\n\n ","\n\n ",";\n"])),(function(e){return e.theme.typography.fontFamily}),(function(e){return e.theme.typography.fontWeightRegular}),(function(e){return e.theme.typography.lineHeight}),(function(e){return e.compact&&"\n p:first-child {\n margin-top: 0;\n }\n p:last-child {\n margin-bottom: 0;\n }\n "}),(function(e){return e.inline&&" p {\n display: inline-block;\n }"}),jn(1),(function(e){return e.theme.colors.primary.main}),jn(2),(function(e){return e.theme.colors.text.primary}),(function(e){return e.theme.typography.code.color}),(function(e){return e.theme.typography.code.backgroundColor}),(function(e){return e.theme.typography.code.fontFamily}),(function(e){return e.theme.spacing.unit}),(function(e){return e.theme.typography.code.fontSize}),(function(e){return e.theme.typography.code.fontWeight}),(function(e){return e.theme.typography.code.fontFamily}),(function(e){return e.theme.typography.code.wrap?"pre-wrap":"pre"}),(function(e){return e.theme.codeBlock.backgroundColor}),(function(e){return 4*e.theme.spacing.unit}),(function(e){return e.theme.schema.nestedBackground}),Fn(".share-link"),Jo,Ee("Markdown")),ti=ei.withComponent("span");function ni(e){var t=e.inline?ti:ei;return i.createElement(Fe,null,(function(n){return i.createElement(t,Object(o.__assign)({className:"redoc-markdown "+(e.className||""),dangerouslySetInnerHTML:{__html:(r=n.untrustedSpec,a=e.html,r?Zo.sanitize(a):a)},"data-role":e["data-role"]},e));var r,a}))}var ri,oi,ii,ai,si,li=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props,t=e.source,n=e.inline,r=e.compact,o=e.className,a=e["data-role"],s=new It;return i.createElement(ni,{html:s.renderMd(t),inline:n,compact:r,className:o,"data-role":a})},t}(i.Component),ci=_e.div(ri||(ri=Object(o.__makeTemplateObject)(["\n position: relative;\n"],["\n position: relative;\n"]))),ui=_e.div(oi||(oi=Object(o.__makeTemplateObject)(["\n position: absolute;\n min-width: 80px;\n max-width: 500px;\n background: #fff;\n bottom: 100%;\n left: 50%;\n margin-bottom: 10px;\n transform: translateX(-50%);\n\n border-radius: 4px;\n padding: 0.3em 0.6em;\n text-align: center;\n box-shadow: 0px 0px 5px 0px rgba(204, 204, 204, 1);\n"],["\n position: absolute;\n min-width: 80px;\n max-width: 500px;\n background: #fff;\n bottom: 100%;\n left: 50%;\n margin-bottom: 10px;\n transform: translateX(-50%);\n\n border-radius: 4px;\n padding: 0.3em 0.6em;\n text-align: center;\n box-shadow: 0px 0px 5px 0px rgba(204, 204, 204, 1);\n"]))),pi=_e.div(ii||(ii=Object(o.__makeTemplateObject)(["\n background: #fff;\n color: #000;\n display: inline;\n font-size: 0.85em;\n white-space: nowrap;\n"],["\n background: #fff;\n color: #000;\n display: inline;\n font-size: 0.85em;\n white-space: nowrap;\n"]))),fi=_e.div(ai||(ai=Object(o.__makeTemplateObject)(["\n position: absolute;\n width: 0;\n height: 0;\n bottom: -5px;\n left: 50%;\n margin-left: -5px;\n border-left: solid transparent 5px;\n border-right: solid transparent 5px;\n border-top: solid #fff 5px;\n"],["\n position: absolute;\n width: 0;\n height: 0;\n bottom: -5px;\n left: 50%;\n margin-left: -5px;\n border-left: solid transparent 5px;\n border-right: solid transparent 5px;\n border-top: solid #fff 5px;\n"]))),di=_e.div(si||(si=Object(o.__makeTemplateObject)(["\n position: absolute;\n width: 100%;\n height: 20px;\n bottom: -20px;\n"],["\n position: absolute;\n width: 100%;\n height: 20px;\n bottom: -20px;\n"]))),hi=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props,t=e.open,n=e.title,r=e.children;return i.createElement(ci,null,r,t&&i.createElement(ui,null,i.createElement(pi,null,n),i.createElement(fi,null),i.createElement(di,null)))},t}(i.Component),mi="undefined"!=typeof document&&document.queryCommandSupported&&document.queryCommandSupported("copy"),gi=function(){function e(){}return e.isSupported=function(){return mi},e.selectElement=function(e){var t,n;document.body.createTextRange?((t=document.body.createTextRange()).moveToElementText(e),t.select()):document.createRange&&window.getSelection&&(n=window.getSelection(),(t=document.createRange()).selectNodeContents(e),n.removeAllRanges(),n.addRange(t))},e.deselect=function(){if(document.selection)document.selection.empty();else if(window.getSelection){var e=window.getSelection();e&&e.removeAllRanges()}},e.copySelected=function(){var e;try{e=document.execCommand("copy")}catch(t){e=!1}return e},e.copyElement=function(t){e.selectElement(t);var n=e.copySelected();return n&&e.deselect(),n},e.copyCustom=function(t){var n=document.createElement("textarea");n.style.position="fixed",n.style.top="0",n.style.left="0",n.style.width="2em",n.style.height="2em",n.style.padding="0",n.style.border="none",n.style.outline="none",n.style.boxShadow="none",n.style.background="transparent",n.value=t,document.body.appendChild(n),n.select();var r=e.copySelected();return document.body.removeChild(n),r},e}(),yi=function(e){function t(t){var n=e.call(this,t)||this;return n.copy=function(){var e="string"==typeof n.props.data?n.props.data:JSON.stringify(n.props.data,null,2);gi.copyCustom(e),n.showTooltip()},n.renderCopyButton=function(){return i.createElement("span",{onClick:n.copy},i.createElement(hi,{title:gi.isSupported()?"Copied":"Not supported in your browser",open:n.state.tooltipShown},"Copy"))},n.state={tooltipShown:!1},n}return Object(o.__extends)(t,e),t.prototype.render=function(){return this.props.children({renderCopyButton:this.renderCopyButton})},t.prototype.showTooltip=function(){var e=this;this.setState({tooltipShown:!0}),setTimeout((function(){e.setState({tooltipShown:!1})}),1500)},t}(i.PureComponent),vi=1;function bi(e,t){vi=1;var n="";return n+='<div class="redoc-json">',n+="<code>",n+=_i(e,t),n+="</code>",n+="</div>"}function xi(e){return void 0!==e?e.toString().replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">"):""}function wi(e){return JSON.stringify(e).slice(1,-1)}function ki(e,t){return'<span class="'+t+'">'+xi(e)+"</span>"}function Oi(e){return'<span class="token punctuation">'+e+"</span>"}function _i(e,t){var n=typeof e,r="";return null==e?r+=ki("null","token keyword"):e&&e.constructor===Array?(vi++,r+=function(e,t){for(var n=vi>t?"collapsed":"",r='<div class="collapser"></div>'+Oi("[")+'<span class="ellipsis"></span><ul class="array collapsible">',o=!1,i=e.length,a=0;a<i;a++)o=!0,r+='<li><div class="hoverable '+n+'">',r+=_i(e[a],t),a<i-1&&(r+=","),r+="</div></li>";r+="</ul>"+Oi("]"),o||(r=Oi("[ ]"));return r}(e,t),vi--):e&&e.constructor===Date?r+=ki('"'+e.toISOString()+'"',"token string"):"object"===n?(vi++,r+=function(e,t){for(var n=vi>t?"collapsed":"",r=Object.keys(e),o=r.length,i='<div class="collapser"></div>'+Oi("{")+'<span class="ellipsis"></span><ul class="obj collapsible">',a=!1,s=0;s<o;s++){var l=r[s];a=!0,i+='<li><div class="hoverable '+n+'">',i+='<span class="property token string">"'+xi(l)+'"</span>: ',i+=_i(e[l],t),s<o-1&&(i+=Oi(",")),i+="</div></li>"}i+="</ul>"+Oi("}"),a||(i=Oi("{ }"));return i}(e,t),vi--):"number"===n?r+=ki(e,"token number"):"string"===n?/^(http|https):\/\/[^\s]+$/.test(e)?r+=ki('"',"token string")+'<a href="'+encodeURI(e)+'">'+xi(wi(e))+"</a>"+ki('"',"token string"):r+=ki('"'+wi(e)+'"',"token string"):"boolean"===n&&(r+=ki(e,"token boolean")),r}var Ei,Si,Ti,ji=be(Ei||(Ei=Object(o.__makeTemplateObject)(["\n .redoc-json > .collapser {\n display: none;\n }\n\n font-family: ",";\n font-size: ",";\n\n white-space: ",";\n contain: content;\n overflow-x: auto;\n\n .callback-function {\n color: gray;\n }\n\n .collapser:after {\n content: '-';\n cursor: pointer;\n }\n\n .collapsed > .collapser:after {\n content: '+';\n cursor: pointer;\n }\n\n .ellipsis:after {\n content: ' … ';\n }\n\n .collapsible {\n margin-left: 2em;\n }\n\n .hoverable {\n padding-top: 1px;\n padding-bottom: 1px;\n padding-left: 2px;\n padding-right: 2px;\n border-radius: 2px;\n }\n\n .hovered {\n background-color: rgba(235, 238, 249, 1);\n }\n\n .collapser {\n padding-right: 6px;\n padding-left: 6px;\n }\n\n ul {\n list-style-type: none;\n padding: 0px;\n margin: 0px 0px 0px 26px;\n }\n\n li {\n position: relative;\n display: block;\n }\n\n .hoverable {\n display: inline-block;\n }\n\n .selected {\n outline-style: solid;\n outline-width: 1px;\n outline-style: dotted;\n }\n\n .collapsed > .collapsible {\n display: none;\n }\n\n .ellipsis {\n display: none;\n }\n\n .collapsed > .ellipsis {\n display: inherit;\n }\n\n .collapser {\n position: absolute;\n top: 1px;\n left: -1.5em;\n cursor: default;\n user-select: none;\n -webkit-user-select: none;\n }\n"],["\n .redoc-json > .collapser {\n display: none;\n }\n\n font-family: ",";\n font-size: ",";\n\n white-space: ",";\n contain: content;\n overflow-x: auto;\n\n .callback-function {\n color: gray;\n }\n\n .collapser:after {\n content: '-';\n cursor: pointer;\n }\n\n .collapsed > .collapser:after {\n content: '+';\n cursor: pointer;\n }\n\n .ellipsis:after {\n content: ' … ';\n }\n\n .collapsible {\n margin-left: 2em;\n }\n\n .hoverable {\n padding-top: 1px;\n padding-bottom: 1px;\n padding-left: 2px;\n padding-right: 2px;\n border-radius: 2px;\n }\n\n .hovered {\n background-color: rgba(235, 238, 249, 1);\n }\n\n .collapser {\n padding-right: 6px;\n padding-left: 6px;\n }\n\n ul {\n list-style-type: none;\n padding: 0px;\n margin: 0px 0px 0px 26px;\n }\n\n li {\n position: relative;\n display: block;\n }\n\n .hoverable {\n display: inline-block;\n }\n\n .selected {\n outline-style: solid;\n outline-width: 1px;\n outline-style: dotted;\n }\n\n .collapsed > .collapsible {\n display: none;\n }\n\n .ellipsis {\n display: none;\n }\n\n .collapsed > .ellipsis {\n display: inherit;\n }\n\n .collapser {\n position: absolute;\n top: 1px;\n left: -1.5em;\n cursor: default;\n user-select: none;\n -webkit-user-select: none;\n }\n"])),(function(e){return e.theme.typography.code.fontFamily}),(function(e){return e.theme.typography.code.fontSize}),(function(e){return e.theme.typography.code.wrap?"pre-wrap":"pre"})),Ci=_e.div(Si||(Si=Object(o.__makeTemplateObject)(["\n &:hover > "," {\n opacity: 1;\n }\n"],["\n &:hover > "," {\n opacity: 1;\n }\n"])),co),Ii=_e(function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.renderInner=function(e){var n=e.renderCopyButton;return i.createElement(Ci,null,i.createElement(co,null,n(),i.createElement("span",{onClick:t.expandAll}," Expand all "),i.createElement("span",{onClick:t.collapseAll}," Collapse all ")),i.createElement(Me.Consumer,null,(function(e){return i.createElement(lo,{className:t.props.className,ref:function(e){return t.node=e},dangerouslySetInnerHTML:{__html:bi(t.props.data,e.jsonSampleExpandLevel)}})})))},t.expandAll=function(){for(var e=t.node.getElementsByClassName("collapsible"),n=0,r=Array.prototype.slice.call(e);n<r.length;n++){r[n].parentNode.classList.remove("collapsed")}},t.collapseAll=function(){for(var e=t.node.getElementsByClassName("collapsible"),n=0,r=Array.prototype.slice.call(e,1);n<r.length;n++){r[n].parentNode.classList.add("collapsed")}},t.clickListener=function(e){var t,n=e.target;"collapser"===n.className&&((t=n.parentElement.getElementsByClassName("collapsible")[0]).parentElement.classList.contains("collapsed")?t.parentElement.classList.remove("collapsed"):t.parentElement.classList.add("collapsed"))},t}return Object(o.__extends)(t,e),t.prototype.render=function(){return i.createElement(yi,{data:this.props.data},this.renderInner)},t.prototype.componentDidMount=function(){this.node.addEventListener("click",this.clickListener)},t.prototype.componentWillUnmount=function(){this.node.removeEventListener("click",this.clickListener)},t}(i.PureComponent))(Ti||(Ti=Object(o.__makeTemplateObject)(["\n ",";\n"],["\n ",";\n"])),ji),Ai=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props,t=e.source,n=e.lang;return i.createElement(po,{dangerouslySetInnerHTML:{__html:yt(t,n)}})},t}(i.PureComponent),Pi=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this;return i.createElement(yi,{data:this.props.source},(function(t){var n=t.renderCopyButton;return i.createElement(uo,null,i.createElement(co,null,n()),i.createElement(Ai,{lang:e.props.lang,source:e.props.source}))}))},t}(i.PureComponent);function Ri(e){var t,n=e.value,r=e.mimeType;return nt(r)?i.createElement(Ii,{data:n}):("object"==typeof n&&(n=JSON.stringify(n,null,2)),i.createElement(Pi,{lang:(t=r,-1!==t.search(/xml/i)?"xml":"clike"),source:n}))}function Ni(e){var t=e.example,n=e.mimeType;return void 0===t.value&&t.externalValueUrl?i.createElement(Li,{example:t,mimeType:n}):i.createElement(Ri,{value:t.value,mimeType:n})}function Li(e){var t=e.example,n=e.mimeType,r=function(e,t){var n=this,r=Object(i.useState)(!0)[1],a=Object(i.useRef)(void 0),s=Object(i.useRef)(void 0);return s.current!==e&&(a.current=void 0),s.current=e,Object(i.useEffect)((function(){Object(o.__awaiter)(n,void 0,void 0,(function(){var n,i;return Object(o.__generator)(this,(function(o){switch(o.label){case 0:r(!0),o.label=1;case 1:return o.trys.push([1,3,,4]),n=a,[4,e.getExternalValue(t)];case 2:return n.current=o.sent(),[3,4];case 3:return i=o.sent(),a.current=i,[3,4];case 4:return r(!1),[2]}}))}))}),[e,t]),a.current}(t,n);return void 0===r?i.createElement("span",null,"Loading..."):r instanceof Error?i.createElement(po,null,"Error loading external example: ",i.createElement("br",null),i.createElement("a",{className:"token string",href:t.externalValueUrl,target:"_blank",rel:"noopener noreferrer"},t.externalValueUrl)):i.createElement(Ri,{value:r,mimeType:n})}var Mi,Di,Fi,zi,Ui,Bi,$i,qi,Wi,Hi,Vi,Yi,Qi,Gi,Xi,Ki,Zi,Ji,ea,ta,na,ra,oa,ia=_e.div(Mi||(Mi=Object(o.__makeTemplateObject)(["\n padding: 0.9em;\n background-color: ",";\n margin: 0 0 10px 0;\n display: block;\n font-family: ",";\n font-size: 0.929em;\n line-height: 1.5em;\n"],["\n padding: 0.9em;\n background-color: ",";\n margin: 0 0 10px 0;\n display: block;\n font-family: ",";\n font-size: 0.929em;\n line-height: 1.5em;\n"])),(function(e){var t=e.theme;return K(.6,t.rightPanel.backgroundColor)}),(function(e){return e.theme.typography.headings.fontFamily})),aa=_e.span(Di||(Di=Object(o.__makeTemplateObject)(["\n font-family: ",";\n font-size: 12px;\n position: absolute;\n z-index: 1;\n top: -11px;\n left: 12px;\n font-weight: ",";\n color: ",";\n"],["\n font-family: ",";\n font-size: 12px;\n position: absolute;\n z-index: 1;\n top: -11px;\n left: 12px;\n font-weight: ",";\n color: ",";\n"])),(function(e){return e.theme.typography.headings.fontFamily}),(function(e){return e.theme.typography.fontWeightBold}),(function(e){var t=e.theme;return K(.6,t.rightPanel.textColor)})),sa=_e.div(Fi||(Fi=Object(o.__makeTemplateObject)(["\n position: relative;\n"],["\n position: relative;\n"]))),la=_e(Sr)(zi||(zi=Object(o.__makeTemplateObject)(["\n margin-left: 10px;\n text-transform: none;\n font-size: 0.929em;\n margin: 0 0 10px 0;\n display: block;\n background-color: ",";\n .Dropdown-placeholder {\n text-overflow: ellipsis;\n white-space: nowrap;\n overflow: hidden;\n }\n .Dropdown-control {\n margin-top: 0;\n }\n .Dropdown-control,\n .Dropdown-control:hover {\n font-size: 1em;\n border: none;\n padding: 0.9em 1.6em 0.9em 0.9em;\n background: transparent;\n color: ",";\n box-shadow: none;\n\n .Dropdown-arrow {\n border-top-color: ",";\n }\n }\n .Dropdown-menu {\n margin: 0;\n margin-top: 2px;\n .Dropdown-option {\n text-overflow: ellipsis;\n white-space: nowrap;\n overflow: hidden;\n }\n }\n"],["\n margin-left: 10px;\n text-transform: none;\n font-size: 0.929em;\n margin: 0 0 10px 0;\n display: block;\n background-color: ",";\n .Dropdown-placeholder {\n text-overflow: ellipsis;\n white-space: nowrap;\n overflow: hidden;\n }\n .Dropdown-control {\n margin-top: 0;\n }\n .Dropdown-control,\n .Dropdown-control:hover {\n font-size: 1em;\n border: none;\n padding: 0.9em 1.6em 0.9em 0.9em;\n background: transparent;\n color: ",";\n box-shadow: none;\n\n .Dropdown-arrow {\n border-top-color: ",";\n }\n }\n .Dropdown-menu {\n margin: 0;\n margin-top: 2px;\n .Dropdown-option {\n text-overflow: ellipsis;\n white-space: nowrap;\n overflow: hidden;\n }\n }\n"])),(function(e){var t=e.theme;return K(.6,t.rightPanel.backgroundColor)}),(function(e){return e.theme.rightPanel.textColor}),(function(e){return e.theme.rightPanel.textColor})),ca=_e.div(Ui||(Ui=Object(o.__makeTemplateObject)(["\n font-family: ",";\n font-size: 12px;\n color: #ee807f;\n"],["\n font-family: ",";\n font-size: 12px;\n color: #ee807f;\n"])),(function(e){return e.theme.typography.code.fontFamily})),ua=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.state={activeIdx:0},t.switchMedia=function(e){var n=e.value;t.setState({activeIdx:parseInt(n,10)})},t}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.state.activeIdx,t=this.props.mediaType.examples||{},n=this.props.mediaType.name,r=i.createElement(ca,null,"No sample"),o=Object.keys(t);if(0===o.length)return r;if(o.length>1){var a=o.map((function(e,n){return{label:t[e].summary||e,value:n.toString()}})),s=(l=t[o[e]]).description;return i.createElement(pa,null,i.createElement(sa,null,i.createElement(aa,null,"Example"),this.props.renderDropdown({value:a[e],options:a,onChange:this.switchMedia})),i.createElement("div",null,s&&i.createElement(li,{source:s}),i.createElement(Ni,{example:l,mimeType:n})))}var l=t[o[0]];return i.createElement(pa,null,l.description&&i.createElement(li,{source:l.description}),i.createElement(Ni,{example:l,mimeType:n}))},t}(i.Component),pa=_e.div(Bi||(Bi=Object(o.__makeTemplateObject)(["\n margin-top: 15px;\n"],["\n margin-top: 15px;\n"]))),fa=n(3),da=_e(mr)($i||($i=Object(o.__makeTemplateObject)(["\n cursor: pointer;\n\n "," {\n height: ",";\n width: ",";\n polygon {\n fill: ",";\n }\n }\n"],["\n cursor: pointer;\n\n "," {\n height: ",";\n width: ",";\n polygon {\n fill: ",";\n }\n }\n"])),cr,(function(e){return e.theme.schema.arrow.size}),(function(e){return e.theme.schema.arrow.size}),(function(e){return e.theme.schema.arrow.color})),ha=_e.span(qi||(qi=Object(o.__makeTemplateObject)(["\n vertical-align: middle;\n font-size: ",";\n line-height: 20px;\n"],["\n vertical-align: middle;\n font-size: ",";\n line-height: 20px;\n"])),(function(e){return e.theme.typography.code.fontSize})),ma=_e(ha)(Wi||(Wi=Object(o.__makeTemplateObject)(["\n color: ",";\n"],["\n color: ",";\n"])),(function(e){return K(.2,e.theme.schema.typeNameColor)})),ga=_e(ha)(Hi||(Hi=Object(o.__makeTemplateObject)(["\n color: ",";\n"],["\n color: ",";\n"])),(function(e){return e.theme.schema.typeNameColor})),ya=_e(ha)(Vi||(Vi=Object(o.__makeTemplateObject)(["\n color: ",";\n word-break: break-word;\n"],["\n color: ",";\n word-break: break-word;\n"])),(function(e){return e.theme.schema.typeTitleColor})),va=ga,ba=_e(ha.withComponent("div"))(Yi||(Yi=Object(o.__makeTemplateObject)(["\n color: ",";\n font-size: ",";\n font-weight: normal;\n margin-left: 20px;\n line-height: 1;\n"],["\n color: ",";\n font-size: ",";\n font-weight: normal;\n margin-left: 20px;\n line-height: 1;\n"])),(function(e){return e.theme.schema.requireLabelColor}),(function(e){return e.theme.schema.labelsTextSize})),xa=_e(ha)(Qi||(Qi=Object(o.__makeTemplateObject)(["\n color: ",";\n font-size: 13px;\n"],["\n color: ",";\n font-size: 13px;\n"])),(function(e){return e.theme.colors.warning.main})),wa=_e(ha)(Gi||(Gi=Object(o.__makeTemplateObject)(["\n color: #3195a6;\n font-size: 13px;\n"],["\n color: #3195a6;\n font-size: 13px;\n"]))),ka=_e(ha)(Xi||(Xi=Object(o.__makeTemplateObject)(["\n color: #3195a6;\n &::before,\n &::after {\n font-weight: bold;\n }\n"],["\n color: #3195a6;\n &::before,\n &::after {\n font-weight: bold;\n }\n"]))),Oa=_e(ha)(Ki||(Ki=Object(o.__makeTemplateObject)(["\n border-radius: 2px;\n ",";\n & + & {\n margin-left: 0;\n }\n ",";\n"],["\n border-radius: 2px;\n ",";\n & + & {\n margin-left: 0;\n }\n ",";\n"])),(function(e){var t=e.theme;return"\n background-color: "+K(.95,t.colors.text.primary)+";\n color: "+K(.1,t.colors.text.primary)+";\n\n padding: 0 "+t.spacing.unit+"px;\n border: 1px solid "+K(.9,t.colors.text.primary)+";\n font-family: "+t.typography.code.fontFamily+";\n}"}),Ee("ExampleValue")),_a=_e(Oa)(Zi||(Zi=Object(o.__makeTemplateObject)([""],[""]))),Ea=_e(ha)(Ji||(Ji=Object(o.__makeTemplateObject)(["\n border-radius: 2px;\n ",";\n & + & {\n margin-left: 0;\n }\n ",";\n"],["\n border-radius: 2px;\n ",";\n & + & {\n margin-left: 0;\n }\n ",";\n"])),(function(e){var t=e.theme;return"\n background-color: "+K(.95,t.colors.primary.light)+";\n color: "+K(.1,t.colors.primary.main)+";\n\n margin: 0 "+t.spacing.unit+"px;\n padding: 0 "+t.spacing.unit+"px;\n border: 1px solid "+K(.9,t.colors.primary.main)+";\n font-family: "+t.typography.code.fontFamily+";\n}"}),Ee("ConstraintItem")),Sa=_e.div(ea||(ea=Object(o.__makeTemplateObject)(["\n ",";\n ","\n"],["\n ",";\n ","\n"])),Jo,(function(e){return e.compact?"":"margin: 1em 0"})),Ta=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.externalDocs;return e&&e.url?i.createElement(Sa,{compact:this.props.compact},i.createElement("a",{href:e.url},e.description||e.url)):null},t=Object(o.__decorate)([fa.a],t)}(i.Component),ja=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props,t=e.values,n=e.type,r=this.context.enumSkipQuotes;return t.length?i.createElement("div",null,i.createElement(ha,null,"array"===n?de("enumArray"):""," ",1===t.length?de("enumSingleValue"):de("enum"),":")," ",t.map((function(e,t){var n=r?e:JSON.stringify(e);return i.createElement(i.Fragment,{key:t},i.createElement(Oa,null,n)," ")}))):null},t.contextType=Me,t}(i.PureComponent),Ca=_e(ei)(ta||(ta=Object(o.__makeTemplateObject)(["\n margin: 2px 0;\n"],["\n margin: 2px 0;\n"]))),Ia=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.extensions;return i.createElement(Me.Consumer,null,(function(t){return i.createElement(i.Fragment,null,t.showExtensions&&Object.keys(e).map((function(t){return i.createElement(Ca,{key:t},i.createElement(ha,null," ",t.substring(2),": ")," ",i.createElement(_a,null,"string"==typeof e[t]?e[t]:JSON.stringify(e[t])))})))}))},t}(i.PureComponent),Aa=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){return 0===this.props.constraints.length?null:i.createElement("span",null," ",this.props.constraints.map((function(e){return i.createElement(Ea,{key:e}," ",e," ")})))},t}(i.PureComponent),Pa=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){if(void 0===this.props.value)return null;var e=this.props.raw?this.props.value:JSON.stringify(this.props.value);return i.createElement("div",null,i.createElement(ha,null," ",this.props.label," ")," ",i.createElement(Oa,null,e))},t}(i.PureComponent),Ra=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props,t=e.showExamples,n=e.field,r=e.renderDiscriminatorSwitch,a=this.context,s=a.enumSkipQuotes,l=a.hideSchemaTitles,c=n.schema,u=n.description,p=n.example,f=n.deprecated,d=!!s||"header"===n.in,h=null;if(t&&void 0!==p){var m=de("example")+":";if(n.in&&(n.style||n.serializationMime)){var g=decodeURIComponent(st(n,p));h=i.createElement(Pa,{label:m,value:g,raw:!0})}else h=i.createElement(Pa,{label:m,value:p})}return i.createElement("div",null,i.createElement("div",null,i.createElement(ma,null,c.typePrefix),i.createElement(ga,null,c.displayType),c.displayFormat&&i.createElement(va,null," ","<",c.displayFormat,">"," "),c.title&&!l&&i.createElement(ya,null," (",c.title,") "),i.createElement(Aa,{constraints:c.constraints}),c.nullable&&i.createElement(wa,null," ",de("nullable")," "),c.pattern&&i.createElement(ka,null," ",c.pattern," "),c.isCircular&&i.createElement(xa,null," ",de("recursive")," ")),f&&i.createElement("div",null,i.createElement(ur,{type:"warning"}," ",de("deprecated")," ")),i.createElement(Pa,{raw:d,label:de("default")+":",value:c.default}),!r&&i.createElement(ja,{type:c.type,values:c.enum})," ",h,i.createElement(Ia,{extensions:Object(o.__assign)(Object(o.__assign)({},n.extensions),c.extensions)}),i.createElement("div",null,i.createElement(li,{compact:!0,source:u})),c.externalDocs&&i.createElement(Ta,{externalDocs:c.externalDocs,compact:!0}),r&&r(this.props)||null)},t.contextType=Me,t}(i.PureComponent),Na=_e.div(na||(na=Object(o.__makeTemplateObject)(["\n padding-left: ","px;\n"],["\n padding-left: ","px;\n"])),(function(e){return 2*e.theme.spacing.unit})),La=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.schema.items;return i.createElement("div",null,i.createElement(Or,null," Array "),i.createElement(Na,null,i.createElement(Ba,Object(o.__assign)({},this.props,{schema:e}))),i.createElement(_r,null))},t}(i.PureComponent),Ma=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.toggle=function(){void 0===t.props.field.expanded&&t.props.expandByDefault?t.props.field.expanded=!1:t.props.field.toggle()},t}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props,t=e.className,n=e.field,r=e.isLast,a=e.expandByDefault,s=n.name,l=n.deprecated,c=n.required,u=n.kind,p=!n.schema.isPrimitive&&!n.schema.isCircular,f=void 0===n.expanded?a:n.expanded,d=p?i.createElement(da,{onClick:this.toggle,className:l?"deprecated":"",kind:u,title:s},i.createElement(yr,null),s,i.createElement(cr,{direction:f?"down":"right"}),c&&i.createElement(ba,null," required ")):i.createElement(mr,{className:l?"deprecated":void 0,kind:u,title:s},i.createElement(yr,null),s,c&&i.createElement(ba,null," required "));return i.createElement(i.Fragment,null,i.createElement("tr",{className:r?"last "+t:t},d,i.createElement(gr,null,i.createElement(Ra,Object(o.__assign)({},this.props)))),f&&p&&i.createElement("tr",{key:n.name+"inner"},i.createElement(hr,{colSpan:2},i.createElement(vr,null,i.createElement(Ba,{schema:n.schema,skipReadOnly:this.props.skipReadOnly,skipWriteOnly:this.props.skipWriteOnly,showTitle:this.props.showTitle})))))},t=Object(o.__decorate)([fa.a],t)}(i.Component),Da=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.changeActiveChild=function(e){var n=e.value,r=parseInt(n,10);t.props.parent.activateOneOf(r)},t}return Object(o.__extends)(t,e),t.prototype.sortOptions=function(e,t){if(0!==t.length){var n={};t.forEach((function(e,t){n[e]=t})),e.sort((function(e,t){return n[e.label]>n[t.label]?1:-1}))}},t.prototype.render=function(){var e=this.props,t=e.parent,n=e.enumValues;if(void 0===t.oneOf)return null;var r=t.oneOf.map((function(e,t){return{value:t.toString(),label:e.title}})),o=r[t.activeOneOf];return this.sortOptions(r,n),i.createElement(Sr,{value:o,options:r,onChange:this.changeActiveChild})},t=Object(o.__decorate)([fa.a],t)}(i.Component),Fa=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),Object.defineProperty(t.prototype,"parentSchema",{get:function(){return this.props.discriminator.parentSchema},enumerable:!0,configurable:!0}),t.prototype.render=function(){var e=this,t=this.props,n=t.schema.fields,r=void 0===n?[]:n,o=t.showTitle,a=t.discriminator,s=this.props.skipReadOnly||this.props.skipWriteOnly?r.filter((function(t){return!(e.props.skipReadOnly&&t.schema.readOnly||e.props.skipWriteOnly&&t.schema.writeOnly)})):r,l=this.context.expandSingleSchemaField&&1===s.length;return i.createElement(br,null,o&&i.createElement(fr,null,this.props.schema.title),i.createElement("tbody",null,ie(s,(function(t,n){return i.createElement(Ma,{key:t.name,isLast:n,field:t,expandByDefault:l,renderDiscriminatorSwitch:a&&a.fieldName===t.name&&function(){return i.createElement(Da,{parent:e.parentSchema,enumValues:t.schema.enum})}||void 0,className:t.expanded?"expanded":void 0,showExamples:!1,skipReadOnly:e.props.skipReadOnly,skipWriteOnly:e.props.skipWriteOnly,showTitle:e.props.showTitle})}))))},t.contextType=Me,t=Object(o.__decorate)([fa.a],t)}(i.Component),za=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.activateOneOf=function(){t.props.schema.activateOneOf(t.props.idx)},t}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props,t=e.idx,n=e.schema,r=e.subSchema;return i.createElement(kr,{active:t===n.activeOneOf,onClick:this.activateOneOf},r.title||r.typePrefix+r.displayType)},t=Object(o.__decorate)([fa.a],t)}(i.Component),Ua=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props,t=e.schema.oneOf,n=e.schema;return void 0===t?null:i.createElement("div",null,i.createElement(wr,null," ",n.oneOfType," "),i.createElement(xr,null,t.map((function(e,t){return i.createElement(za,{key:e.pointer,schema:n,subSchema:e,idx:t})}))),i.createElement(Ba,Object(o.__assign)({},this.props,{schema:t[n.activeOneOf]})))},t=Object(o.__decorate)([fa.a],t)}(i.Component),Ba=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.schema;if(!e)return i.createElement("em",null," Schema not provided ");var t=e.type,n=e.oneOf,r=e.discriminatorProp;if(e.isCircular)return i.createElement("div",null,i.createElement(ga,null,e.displayType),e.title&&i.createElement(ya,null," ",e.title," "),i.createElement(xa,null," ",de("recursive")," "));if(void 0!==r)return n&&n.length?i.createElement(Fa,Object(o.__assign)({},Object(o.__assign)(Object(o.__assign)({},this.props),{schema:n[e.activeOneOf]}),{discriminator:{fieldName:r,parentSchema:e}})):(console.warn("Looks like you are using discriminator wrong: you don't have any definition inherited from the "+e.title),null);if(void 0!==n)return i.createElement(Ua,Object(o.__assign)({schema:e},this.props));switch(t){case"object":return i.createElement(Fa,Object(o.__assign)({},this.props));case"array":return i.createElement(La,Object(o.__assign)({},this.props))}var a={schema:e,name:"",required:!1,description:e.description,externalDocs:e.externalDocs,deprecated:!1,toggle:function(){return null},expanded:!1};return i.createElement("div",null,i.createElement(Ra,{field:a}))},t=Object(o.__decorate)([fa.a],t)}(i.Component),$a=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.renderDropdown=function(e){return i.createElement(Go,Object(o.__assign)({Label:jr,Dropdown:la},e))},t}return Object(o.__extends)(t,e),t.getMediaType=function(e,t){if(!e)return{};var n={schema:{$ref:e}};return t&&(n.examples={example:{$ref:t}}),n},Object.defineProperty(t.prototype,"mediaModel",{get:function(){var e=this.props,n=e.parser,r=e.schemaRef,o=e.exampleRef,i=e.options;return this._mediaModel||(this._mediaModel=new Yt(n,"json",!1,t.getMediaType(r,o),i)),this._mediaModel},enumerable:!0,configurable:!0}),t.prototype.render=function(){var e=this.props,t=e.showReadOnly,n=void 0===t||t,r=e.showWriteOnly,o=void 0!==r&&r;return i.createElement(On,null,i.createElement(Sn,null,i.createElement(kn,null,i.createElement(Ba,{skipWriteOnly:!o,skipReadOnly:!n,schema:this.mediaModel.schema})),i.createElement(En,null,i.createElement(qa,null,i.createElement(ua,{renderDropdown:this.renderDropdown,mediaType:this.mediaModel})))))},t}(i.PureComponent),qa=_e.div(ra||(ra=Object(o.__makeTemplateObject)(["\n background: ",";\n & > div,\n & > pre {\n padding: ","px;\n margin: 0;\n }\n\n & > div > pre {\n padding: 0;\n }\n"],["\n background: ",";\n & > div,\n & > pre {\n padding: ","px;\n margin: 0;\n }\n\n & > div > pre {\n padding: 0;\n }\n"])),(function(e){return e.theme.codeBlock.backgroundColor}),(function(e){return 4*e.theme.spacing.unit})),Wa={oauth2:"OAuth2",apiKey:"API Key",http:"HTTP",openIdConnect:"Open ID Connect"},Ha=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props,t=e.type,n=e.flow;return i.createElement("tr",null,i.createElement("th",null," ",t," OAuth Flow "),i.createElement("td",null,"implicit"===t||"authorizationCode"===t?i.createElement("div",null,i.createElement("strong",null," Authorization URL: "),n.authorizationUrl):null,"password"===t||"clientCredentials"===t||"authorizationCode"===t?i.createElement("div",null,i.createElement("strong",null," Token URL: "),n.tokenUrl):null,n.refreshUrl&&i.createElement("div",null,i.createElement("strong",null," Refresh URL: "),n.refreshUrl),i.createElement("div",null,i.createElement("strong",null," Scopes: ")),i.createElement("ul",null,Object.keys(n.scopes||{}).map((function(e){return i.createElement("li",{key:e},i.createElement("code",null,e)," - ",i.createElement(li,{inline:!0,source:n.scopes[e]||""}))})))))},t}(i.PureComponent),Va=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){return this.props.securitySchemes.schemes.map((function(e){return i.createElement(On,{id:e.sectionId,key:e.id},i.createElement(Sn,null,i.createElement(kn,null,i.createElement(In,null,i.createElement($n,{to:e.sectionId}),e.id),i.createElement(li,{source:e.description||""}),i.createElement(ei,null,i.createElement("table",{className:"security-details"},i.createElement("tbody",null,i.createElement("tr",null,i.createElement("th",null," Security Scheme Type "),i.createElement("td",null," ",Wa[e.type]||e.type," ")),e.apiKey?i.createElement("tr",null,i.createElement("th",null," ",(t=e.apiKey.in||"").charAt(0).toUpperCase()+t.slice(1)," parameter name:"),i.createElement("td",null," ",e.apiKey.name," ")):e.http?[i.createElement("tr",{key:"scheme"},i.createElement("th",null," HTTP Authorization Scheme "),i.createElement("td",null," ",e.http.scheme," ")),"bearer"===e.http.scheme&&e.http.bearerFormat&&i.createElement("tr",{key:"bearer"},i.createElement("th",null," Bearer format "),i.createElement("td",null,' "',e.http.bearerFormat,'" '))]:e.openId?i.createElement("tr",null,i.createElement("th",null," Connect URL "),i.createElement("td",null,i.createElement("a",{target:"_blank",rel:"noopener noreferrer",href:e.openId.connectUrl},e.openId.connectUrl))):e.flows?Object.keys(e.flows).map((function(t){return i.createElement(Ha,{key:t,type:t,flow:e.flows[t]})})):null))))));var t}))},t}(i.PureComponent);var Ya,Qa,Ga,Xa,Ka,Za,Ja,es,ts=function(){function e(e,t,n,r){var o=this;void 0===n&&(n={}),void 0===r&&(r=!0),this.marker=new St,this.disposer=null,this.rawOptions=n,this.options=new ge(n,ns),this.scroll=new nn(this.options),tn.updateOnHistory(_t.currentId,this.scroll),this.spec=new Dt(e,t,this.options),this.menu=new tn(this.spec,this.scroll,_t),this.options.disableSearch||(this.search=new wn,r&&this.search.indexItems(this.menu.items),this.disposer=Object(ze.m)(this.menu,"activeItemIdx",(function(e){o.updateMarkOnMenu(e.newValue)})))}return e.fromJS=function(t){var n=new e(t.spec.data,t.spec.url,t.options,!1);return n.menu.activeItemIdx=t.menu.activeItemIdx||0,n.menu.activate(n.menu.flatItems[n.menu.activeItemIdx]),n.options.disableSearch||n.search.load(t.searchIndex),n},e.prototype.onDidMount=function(){this.menu.updateOnHistory(),this.updateMarkOnMenu(this.menu.activeItemIdx)},e.prototype.dispose=function(){this.scroll.dispose(),this.menu.dispose(),this.search&&this.search.dispose(),null!=this.disposer&&this.disposer()},e.prototype.toJS=function(){return Object(o.__awaiter)(this,void 0,void 0,(function(){var e,t;return Object(o.__generator)(this,(function(n){switch(n.label){case 0:return e={menu:{activeItemIdx:this.menu.activeItemIdx},spec:{url:this.spec.parser.specUrl,data:this.spec.parser.spec}},this.search?[4,this.search.toJS()]:[3,2];case 1:return t=n.sent(),[3,3];case 2:t=void 0,n.label=3;case 3:return[2,(e.searchIndex=t,e.options=this.rawOptions,e)]}}))}))},e.prototype.updateMarkOnMenu=function(e){for(var t=Math.max(0,e),n=Math.min(this.menu.flatItems.length,t+5),r=[],o=t;o<n;o++){var i=this.menu.getElementAt(o);i&&r.push(i)}if(-1===e&&ee){var a=document.querySelector('[data-role="redoc-description"]');a&&r.push(a)}this.marker.addOnly(r),this.marker.mark()},e}(),ns={allowedMdComponents:(oa={},oa["security-definitions"]={component:Va,propsSelector:function(e){return{securitySchemes:e.spec.securitySchemes}}},oa.SecurityDefinitions={component:Va,propsSelector:function(e){return{securitySchemes:e.spec.securitySchemes}}},oa.SchemaDefinition={component:$a,propsSelector:function(e){return{parser:e.spec.parser,options:e.options}}},oa)},rs=_e(Cn)(Ya||(Ya=Object(o.__makeTemplateObject)(["\n margin-top: 0;\n margin-bottom: 0.5em;\n\n ",";\n"],["\n margin-top: 0;\n margin-bottom: 0.5em;\n\n ",";\n"])),Ee("ApiHeader")),os=_e.a(Qa||(Qa=Object(o.__makeTemplateObject)(["\n border: 1px solid ",";\n color: ",";\n font-weight: normal;\n margin-left: 0.5em;\n padding: 4px 8px 4px;\n display: inline-block;\n text-decoration: none;\n cursor: pointer;\n\n ",";\n"],["\n border: 1px solid ",";\n color: ",";\n font-weight: normal;\n margin-left: 0.5em;\n padding: 4px 8px 4px;\n display: inline-block;\n text-decoration: none;\n cursor: pointer;\n\n ",";\n"])),(function(e){return e.theme.colors.primary.main}),(function(e){return e.theme.colors.primary.main}),Ee("DownloadButton")),is=_e.span(Ga||(Ga=Object(o.__makeTemplateObject)(["\n &::before {\n content: '|';\n display: inline-block;\n opacity: 0.5;\n width: ","px;\n text-align: center;\n }\n\n &:last-child::after {\n display: none;\n }\n"],["\n &::before {\n content: '|';\n display: inline-block;\n opacity: 0.5;\n width: ","px;\n text-align: center;\n }\n\n &:last-child::after {\n display: none;\n }\n"])),15),as=_e.div(Xa||(Xa=Object(o.__makeTemplateObject)(["\n overflow: hidden;\n"],["\n overflow: hidden;\n"]))),ss=_e.div(Ka||(Ka=Object(o.__makeTemplateObject)(["\n display: flex;\n flex-wrap: wrap;\n // hide separator on new lines: idea from https://stackoverflow.com/a/31732902/1749888\n margin-left: -","px;\n"],["\n display: flex;\n flex-wrap: wrap;\n // hide separator on new lines: idea from https://stackoverflow.com/a/31732902/1749888\n margin-left: -","px;\n"])),15),ls=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.handleDownloadClick=function(e){e.target.href||(e.target.href=t.props.store.spec.info.downloadLink)},t}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.store,t=e.spec,n=t.info,r=t.externalDocs,o=e.options.hideDownloadButton,a=n.downloadFileName,s=n.downloadLink,l=n.license&&i.createElement(is,null,"License: ",i.createElement("a",{href:n.license.url},n.license.name))||null,c=n.contact&&n.contact.url&&i.createElement(is,null,"URL: ",i.createElement("a",{href:n.contact.url},n.contact.url))||null,u=n.contact&&n.contact.email&&i.createElement(is,null,n.contact.name||"E-mail",":"," ",i.createElement("a",{href:"mailto:"+n.contact.email},n.contact.email))||null,p=n.termsOfService&&i.createElement(is,null,i.createElement("a",{href:n.termsOfService},"Terms of Service"))||null,f=n.version&&i.createElement("span",null,"(",n.version,")")||null;return i.createElement(On,null,i.createElement(Sn,null,i.createElement(kn,{className:"api-info"},i.createElement(rs,null,n.title," ",f),!o&&i.createElement("p",null,"Download OpenAPI specification:",i.createElement(os,{download:a||!0,target:"_blank",href:s,onClick:this.handleDownloadClick},"Download")),i.createElement(ei,null,(n.license||n.contact||n.termsOfService)&&i.createElement(as,null,i.createElement(ss,null,u," ",c," ",l," ",p))||null),i.createElement(li,{source:e.spec.info.description,"data-role":"redoc-description"}),r&&i.createElement(Ta,{externalDocs:r}))))},t=Object(o.__decorate)([fa.a],t)}(i.Component),cs=_e.img(Za||(Za=Object(o.__makeTemplateObject)(["\n max-height: ",";\n max-width: ",";\n padding: ",";\n width: 100%;\n display: block;\n"],["\n max-height: ",";\n max-width: ",";\n padding: ",";\n width: 100%;\n display: block;\n"])),(function(e){return e.theme.logo.maxHeight}),(function(e){return e.theme.logo.maxWidth}),(function(e){return e.theme.logo.gutter})),us=_e.div(Ja||(Ja=Object(o.__makeTemplateObject)(["\n text-align: center;\n"],["\n text-align: center;\n"]))),ps=_e.a(es||(es=Object(o.__makeTemplateObject)(["\n display: inline-block;\n"],["\n display: inline-block;\n"]))),fs=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.info,t=e["x-logo"];if(!t||!t.url)return null;var n,r=t.href||e.contact&&e.contact.url,o=t.altText?t.altText:"logo",a=i.createElement(cs,{src:t.url,alt:o});return i.createElement(us,{style:{backgroundColor:t.backgroundColor}},r?(n=r,function(e){return i.createElement(ps,{href:n},e)})(a):a)},t=Object(o.__decorate)([fa.a],t)}(i.Component),ds=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this;return i.createElement(Fe,null,(function(t){return i.createElement(Mn,null,(function(n){return e.renderWithOptionsAndStore(t,n)}))}))},t.prototype.renderWithOptionsAndStore=function(e,t){var n=this.props,r=n.source,a=n.htmlWrap,s=void 0===a?function(e){return e}:a;if(!t)throw new Error("When using components in markdown, store prop must be provided");var l=new It(e).renderMdWithComponents(r);return l.length?l.map((function(e,n){return"string"==typeof e?i.cloneElement(s(i.createElement(ni,{html:e,inline:!1,compact:!1})),{key:n}):i.createElement(e.component,Object(o.__assign)({key:n},Object(o.__assign)(Object(o.__assign)({},e.props),e.propsSelector(t))))})):null},t}(i.Component),hs=_e.span.attrs((function(e){return{className:"operation-type "+e.type}}))(gs||(gs=Object(o.__makeTemplateObject)(["\n width: 32px;\n display: inline-block;\n height: ",";\n line-height: ",";\n background-color: #333;\n border-radius: 3px;\n background-repeat: no-repeat;\n background-position: 6px 4px;\n font-size: 7px;\n font-family: Verdana; // web-safe\n color: white;\n text-transform: uppercase;\n text-align: center;\n font-weight: bold;\n vertical-align: middle;\n margin-right: 6px;\n margin-top: 2px;\n\n &.get {\n background-color: ",";\n }\n\n &.post {\n background-color: ",";\n }\n\n &.put {\n background-color: ",";\n }\n\n &.options {\n background-color: ",";\n }\n\n &.patch {\n background-color: ",";\n }\n\n &.delete {\n background-color: ",";\n }\n\n &.basic {\n background-color: ",";\n }\n\n &.link {\n background-color: ",";\n }\n\n &.head {\n background-color: ",";\n }\n"],["\n width: 32px;\n display: inline-block;\n height: ",";\n line-height: ",";\n background-color: #333;\n border-radius: 3px;\n background-repeat: no-repeat;\n background-position: 6px 4px;\n font-size: 7px;\n font-family: Verdana; // web-safe\n color: white;\n text-transform: uppercase;\n text-align: center;\n font-weight: bold;\n vertical-align: middle;\n margin-right: 6px;\n margin-top: 2px;\n\n &.get {\n background-color: ",";\n }\n\n &.post {\n background-color: ",";\n }\n\n &.put {\n background-color: ",";\n }\n\n &.options {\n background-color: ",";\n }\n\n &.patch {\n background-color: ",";\n }\n\n &.delete {\n background-color: ",";\n }\n\n &.basic {\n background-color: ",";\n }\n\n &.link {\n background-color: ",";\n }\n\n &.head {\n background-color: ",";\n }\n"])),(function(e){return e.theme.typography.code.fontSize}),(function(e){return e.theme.typography.code.fontSize}),(function(e){return e.theme.colors.http.get}),(function(e){return e.theme.colors.http.post}),(function(e){return e.theme.colors.http.put}),(function(e){return e.theme.colors.http.options}),(function(e){return e.theme.colors.http.patch}),(function(e){return e.theme.colors.http.delete}),(function(e){return e.theme.colors.http.basic}),(function(e){return e.theme.colors.http.link}),(function(e){return e.theme.colors.http.head}));function ms(e,t){var n=t.theme;return e>1?q(.1,n.sidebar.backgroundColor):1===e?q(.05,n.sidebar.backgroundColor):""}var gs,ys,vs,bs,xs,ws,ks,Os,_s,Es,Ss,Ts,js,Cs,Is,As,Ps,Rs,Ns,Ls,Ms,Ds=_e.ul(ys||(ys=Object(o.__makeTemplateObject)(["\n margin: 0;\n padding: 0;\n\n & & {\n font-size: 0.929em;\n }\n\n ",";\n"],["\n margin: 0;\n padding: 0;\n\n & & {\n font-size: 0.929em;\n }\n\n ",";\n"])),(function(e){return e.expanded?"":"display: none;"})),Fs=_e.li(vs||(vs=Object(o.__makeTemplateObject)(["\n list-style: none inside none;\n overflow: hidden;\n text-overflow: ellipsis;\n padding: 0;\n ",";\n"],["\n list-style: none inside none;\n overflow: hidden;\n text-overflow: ellipsis;\n padding: 0;\n ",";\n"])),(function(e){return 0===e.depth?"margin-top: 15px":""})),zs={0:be(bs||(bs=Object(o.__makeTemplateObject)(["\n opacity: 0.7;\n text-transform: ",";\n font-size: 0.8em;\n padding-bottom: 0;\n cursor: default;\n color: ",";\n "],["\n opacity: 0.7;\n text-transform: ",";\n font-size: 0.8em;\n padding-bottom: 0;\n cursor: default;\n color: ",";\n "])),(function(e){return e.theme.sidebar.groupItems.textTransform}),(function(e){return e.theme.sidebar.textColor})),1:be(xs||(xs=Object(o.__makeTemplateObject)(["\n font-size: 0.929em;\n text-transform: ",";\n &:hover {\n color: ",";\n }\n "],["\n font-size: 0.929em;\n text-transform: ",";\n &:hover {\n color: ",";\n }\n "])),(function(e){return e.theme.sidebar.level1Items.textTransform}),(function(e){return e.theme.sidebar.activeTextColor})),2:be(ws||(ws=Object(o.__makeTemplateObject)(["\n color: ",";\n "],["\n color: ",";\n "])),(function(e){return e.theme.sidebar.textColor}))},Us=_e.label.attrs((function(e){return{role:"menuitem",className:Fr("-depth"+e.depth,{active:e.active})}}))(ks||(ks=Object(o.__makeTemplateObject)(["\n cursor: pointer;\n color: ",";\n margin: 0;\n padding: 12.5px ","px;\n ","\n display: flex;\n justify-content: space-between;\n font-family: ",";\n ",";\n background-color: ",";\n\n ",";\n\n &:hover {\n background-color: ",";\n }\n\n "," {\n height: ",";\n width: ",";\n polygon {\n fill: ",";\n }\n }\n"],["\n cursor: pointer;\n color: ",";\n margin: 0;\n padding: 12.5px ","px;\n ","\n display: flex;\n justify-content: space-between;\n font-family: ",";\n ",";\n background-color: ",";\n\n ",";\n\n &:hover {\n background-color: ",";\n }\n\n "," {\n height: ",";\n width: ",";\n polygon {\n fill: ",";\n }\n }\n"])),(function(e){return e.active?e.theme.sidebar.activeTextColor:e.theme.sidebar.textColor}),(function(e){return 4*e.theme.spacing.unit}),(function(e){var t=e.depth,n=e.type,r=e.theme;return"section"===n&&t>1&&"padding-left: "+8*r.spacing.unit+"px;"||""}),(function(e){return e.theme.typography.headings.fontFamily}),(function(e){return zs[e.depth]}),(function(e){return e.active?ms(e.depth,e):""}),(function(e){return e.deprecated&&pr||""}),(function(e){return ms(e.depth,e)}),cr,(function(e){return e.theme.sidebar.arrow.size}),(function(e){return e.theme.sidebar.arrow.size}),(function(e){return e.theme.sidebar.arrow.color})),Bs=_e.span(Os||(Os=Object(o.__makeTemplateObject)(["\n display: inline-block;\n vertical-align: middle;\n width: ",";\n overflow: hidden;\n text-overflow: ellipsis;\n"],["\n display: inline-block;\n vertical-align: middle;\n width: ",";\n overflow: hidden;\n text-overflow: ellipsis;\n"])),(function(e){return e.width?e.width:"auto"})),$s=_e.div(_s||(_s=Object(o.__makeTemplateObject)(["\n ",";\n"],["\n ",";\n"])),(function(e){var t=e.theme;return"\n font-size: 0.8em;\n margin-top: "+2*t.spacing.unit+"px;\n padding: 0 "+4*t.spacing.unit+"px;\n text-align: left;\n\n opacity: 0.7;\n\n a,\n a:visited,\n a:hover {\n color: "+t.sidebar.textColor+" !important;\n border-top: 1px solid "+q(.1,t.sidebar.backgroundColor)+";\n padding: "+t.spacing.unit+"px 0;\n display: block;\n }\n"})),qs=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props,t=e.name,n=e.opened,r=e.className,o=e.onClick,a=e.httpVerb,s=e.deprecated;return i.createElement(Ws,{className:r,onClick:o||void 0},i.createElement(Vs,{type:a},ht(a)),i.createElement(cr,{size:"1.5em",direction:n?"down":"right",float:"left"}),i.createElement(Hs,{deprecated:s},t),s?i.createElement(ur,{type:"warning"}," ",de("deprecated")," "):null)},t}(i.PureComponent),Ws=_e.div(Es||(Es=Object(o.__makeTemplateObject)(["\n & > * {\n vertical-align: middle;\n }\n\n "," {\n polygon {\n fill: ",";\n }\n }\n"],["\n & > * {\n vertical-align: middle;\n }\n\n "," {\n polygon {\n fill: ",";\n }\n }\n"])),cr,(function(e){var t=e.theme;return q(t.colors.tonalOffset,t.colors.gray[100])})),Hs=_e.span(Ss||(Ss=Object(o.__makeTemplateObject)(["\n text-decoration: ",";\n margin-right: 8px;\n"],["\n text-decoration: ",";\n margin-right: 8px;\n"])),(function(e){return e.deprecated?"line-through":"none"})),Vs=_e(hs)(Ts||(Ts=Object(o.__makeTemplateObject)(["\n margin: 0px 5px 0px 0px;\n"],["\n margin: 0px 5px 0px 0px;\n"]))),Ys=_e(qs)(js||(js=Object(o.__makeTemplateObject)(["\n padding: 10px;\n border-radius: 2px;\n margin-bottom: 4px;\n line-height: 1.5em;\n background-color: ",";\n cursor: pointer;\n"],["\n padding: 10px;\n border-radius: 2px;\n margin-bottom: 4px;\n line-height: 1.5em;\n background-color: ",";\n cursor: pointer;\n"])),(function(e){return e.theme.colors.gray[100]})),Qs=_e.div(Cs||(Cs=Object(o.__makeTemplateObject)(["\n padding: 10px 25px;\n background-color: ",";\n margin-bottom: 5px;\n margin-top: 5px;\n"],["\n padding: 10px 25px;\n background-color: ",";\n margin-bottom: 5px;\n margin-top: 5px;\n"])),(function(e){return e.theme.colors.gray[50]})),Gs=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.handleClick=function(){gi.selectElement(t.child)},t}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this,t=this.props.children;return i.createElement("div",{ref:function(t){return e.child=t},onClick:this.handleClick},t)},t}(i.PureComponent),Xs=_e.div(Is||(Is=Object(o.__makeTemplateObject)(["\n cursor: pointer;\n position: relative;\n margin-bottom: 5px;\n"],["\n cursor: pointer;\n position: relative;\n margin-bottom: 5px;\n"]))),Ks=_e.span(As||(As=Object(o.__makeTemplateObject)(["\n font-family: ",";\n margin-left: 10px;\n flex: 1;\n overflow-x: hidden;\n text-overflow: ellipsis;\n"],["\n font-family: ",";\n margin-left: 10px;\n flex: 1;\n overflow-x: hidden;\n text-overflow: ellipsis;\n"])),(function(e){return e.theme.typography.code.fontFamily})),Zs=_e.div(Ps||(Ps=Object(o.__makeTemplateObject)(["\n padding: 10px 30px 10px ",";\n border-radius: ",";\n background-color: ",";\n display: flex;\n white-space: nowrap;\n align-items: center;\n border: ",";\n border-bottom: ",";\n transition: border-color 0.25s ease;\n\n ","\n\n ."," {\n color: ","\n }\n"],["\n padding: 10px 30px 10px ",";\n border-radius: ",";\n background-color: ",";\n display: flex;\n white-space: nowrap;\n align-items: center;\n border: ",";\n border-bottom: ",";\n transition: border-color 0.25s ease;\n\n ","\n\n ."," {\n color: ","\n }\n"])),(function(e){return e.inverted?"10px":"20px"}),(function(e){return e.inverted?"0":"4px 4px 0 0"}),(function(e){return e.inverted?"transparent":e.theme.codeBlock.backgroundColor}),(function(e){return e.inverted?"0":"1px solid transparent"}),(function(e){return e.inverted?"1px solid #ccc":"0"}),(function(e){return e.expanded&&!e.inverted&&"border-color: "+e.theme.colors.border.dark+";"||""}),Ks,(function(e){return e.inverted?e.theme.colors.text.primary:"#ffffff"})),Js=_e.span.attrs((function(e){return{className:"http-verb "+e.type}}))(Rs||(Rs=Object(o.__makeTemplateObject)(["\n font-size: ",";\n line-height: ",";\n background-color: ",";\n color: #ffffff;\n padding: ",";\n text-transform: uppercase;\n font-family: ",";\n margin: 0;\n"],["\n font-size: ",";\n line-height: ",";\n background-color: ",";\n color: #ffffff;\n padding: ",";\n text-transform: uppercase;\n font-family: ",";\n margin: 0;\n"])),(function(e){return e.compact?"0.8em":"0.929em"}),(function(e){return e.compact?"18px":"20px"}),(function(e){return e.theme.colors.http[e.type]||"#999999"}),(function(e){return e.compact?"2px 8px":"3px 10px"}),(function(e){return e.theme.typography.headings.fontFamily})),el=_e.div(Ns||(Ns=Object(o.__makeTemplateObject)(["\n position: absolute;\n width: 100%;\n z-index: 100;\n background: #fafafa;\n color: #263238;\n box-sizing: border-box;\n box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.33);\n overflow: hidden;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n transition: all 0.25s ease;\n ","\n"],["\n position: absolute;\n width: 100%;\n z-index: 100;\n background: #fafafa;\n color: #263238;\n box-sizing: border-box;\n box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.33);\n overflow: hidden;\n border-bottom-left-radius: 4px;\n border-bottom-right-radius: 4px;\n transition: all 0.25s ease;\n ","\n"])),(function(e){return e.expanded?"":"transform: translateY(-50%) scaleY(0);"})),tl=_e.div(Ls||(Ls=Object(o.__makeTemplateObject)(["\n padding: 10px;\n"],["\n padding: 10px;\n"]))),nl=_e.div(Ms||(Ms=Object(o.__makeTemplateObject)(["\n padding: 5px;\n border: 1px solid #ccc;\n background: #fff;\n word-break: break-all;\n color: ",";\n > span {\n color: ",";\n }\n"],["\n padding: 5px;\n border: 1px solid #ccc;\n background: #fff;\n word-break: break-all;\n color: ",";\n > span {\n color: ",";\n }\n"])),(function(e){return e.theme.colors.primary.main}),(function(e){return e.theme.colors.text.primary})),rl=function(e){function t(t){var n=e.call(this,t)||this;return n.toggle=function(){n.setState({expanded:!n.state.expanded})},n.state={expanded:!1},n}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this,t=this.props,n=t.operation,r=t.inverted,o=t.hideHostname,a=this.state.expanded;return i.createElement(Me.Consumer,null,(function(t){return i.createElement(Xs,null,i.createElement(Zs,{onClick:e.toggle,expanded:a,inverted:r},i.createElement(Js,{type:n.httpVerb,compact:e.props.compact},n.httpVerb),i.createElement(Ks,null,n.path),i.createElement(cr,{float:"right",color:r?"black":"white",size:"20px",direction:a?"up":"down",style:{marginRight:"-25px"}})),i.createElement(el,{expanded:a},n.servers.map((function(e){var r,a,s=t.expandDefaultServerVariables?(r=e.url,void 0===(a=e.variables)&&(a={}),r.replace(/(?:{)(\w+)(?:})/g,(function(e,t){return a[t]&&a[t].default||e}))):e.url;return i.createElement(tl,{key:s},i.createElement(li,{source:e.description||"",compact:!0}),i.createElement(Gs,null,i.createElement(nl,null,i.createElement("span",null,o||t.hideHostname?function(e){try{return pe(e).pathname}catch(t){return e}}(s):s),n.path)))}))))}))},t}(i.Component),ol=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props,t=e.place,n=e.parameters;return n&&n.length?i.createElement("div",{key:t},i.createElement(Pn,null,t," Parameters"),i.createElement(br,null,i.createElement("tbody",null,ie(n,(function(e,t){return i.createElement(Ma,{key:e.name,isLast:t,field:e,showExamples:!0})}))))):null},t}(i.PureComponent),il=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.switchMedia=function(e){var n=e.value;t.props.content&&t.props.content.activate(parseInt(n,10))},t}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this,t=this.props.content;if(!t||!t.mediaTypes||!t.mediaTypes.length)return null;var n=t.activeMimeIdx,r=t.mediaTypes.map((function(e,t){return{label:e.name,value:t.toString()}}));return i.createElement(i.Fragment,null,i.createElement((function(t){var n=t.children;return e.props.withLabel?i.createElement(sa,null,i.createElement(aa,null,"Content type"),n):n}),null,this.props.renderDropdown({value:r[n],options:r,onChange:this.switchMedia})),this.props.children(t.active))},t=Object(o.__decorate)([fa.a],t)}(i.Component);var al=["path","query","cookie","header"],sl=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.orderParams=function(e){var t={};return e.forEach((function(e){var n,r,o;n=t,r=e.in,o=e,n[r]||(n[r]=[]),n[r].push(o)})),t},t.prototype.render=function(){var e=this.props,t=e.body,n=e.parameters,r=void 0===n?[]:n;if(void 0===t&&void 0===r)return null;var o=this.orderParams(r),a=r.length>0?al:[],s=t&&t.content,l=t&&t.description;return i.createElement(i.Fragment,null,a.map((function(e){return i.createElement(ol,{key:e,place:e,parameters:o[e]})})),s&&i.createElement(cl,{content:s,description:l}))},t}(i.PureComponent);function ll(e){return i.createElement(Pn,{key:"header"},"Request Body schema: ",i.createElement(Go,Object(o.__assign)({},e)))}function cl(e){var t=e.content,n=e.description;return i.createElement(il,{content:t,renderDropdown:ll},(function(e){var t=e.schema;return i.createElement(i.Fragment,null,void 0!==n&&i.createElement(li,{source:n}),i.createElement(Ba,{skipReadOnly:!0,key:"schema",schema:t}))}))}var ul,pl,fl,dl,hl,ml,gl,yl,vl,bl,xl,wl,kl,Ol,_l,El,Sl,Tl,jl,Cl=_e(function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props,t=e.title,n=e.type,r=e.empty,o=e.code,a=e.opened,s=e.className,l=e.onClick;return i.createElement("div",{className:s,onClick:!r&&l||void 0},!r&&i.createElement(cr,{size:"1.5em",color:n,direction:a?"down":"right",float:"left"}),i.createElement("strong",null,o," "),i.createElement(li,{compact:!0,inline:!0,source:t}))},t}(i.PureComponent))(ul||(ul=Object(o.__makeTemplateObject)(["\n padding: 10px;\n border-radius: 2px;\n margin-bottom: 4px;\n line-height: 1.5em;\n background-color: #f2f2f2;\n cursor: pointer;\n\n color: ",";\n background-color: ",";\n\n ",";\n"],["\n padding: 10px;\n border-radius: 2px;\n margin-bottom: 4px;\n line-height: 1.5em;\n background-color: #f2f2f2;\n cursor: pointer;\n\n color: ",";\n background-color: ",";\n\n ",";\n"])),(function(e){return e.theme.colors.responses[e.type].color}),(function(e){return e.theme.colors.responses[e.type].backgroundColor}),(function(e){return e.empty?'\ncursor: default;\n&::before {\n content: "—";\n font-weight: bold;\n width: 1.5em;\n text-align: center;\n display: inline-block;\n}\n':""})),Il=_e.div(pl||(pl=Object(o.__makeTemplateObject)(["\n padding: 10px;\n"],["\n padding: 10px;\n"]))),Al=_e(Pn.withComponent("caption"))(fl||(fl=Object(o.__makeTemplateObject)(["\n text-align: left;\n margin-top: 1em;\n caption-side: top;\n"],["\n text-align: left;\n margin-top: 1em;\n caption-side: top;\n"]))),Pl=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.headers;return void 0===e||0===e.length?null:i.createElement(br,null,i.createElement(Al,null," Response Headers "),i.createElement("tbody",null,ie(e,(function(e,t){return i.createElement(Ma,{isLast:t,key:e.name,field:e,showExamples:!0})}))))},t}(i.PureComponent),Rl=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.renderDropdown=function(e){return i.createElement(Pn,{key:"header"},"Response Schema: ",i.createElement(Go,Object(o.__assign)({},e)))},t}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.response,t=e.description,n=e.headers,r=e.content;return i.createElement(i.Fragment,null,t&&i.createElement(li,{source:t}),i.createElement(Pl,{headers:n}),i.createElement(il,{content:r,renderDropdown:this.renderDropdown},(function(e){var t=e.schema;return i.createElement(Ba,{skipWriteOnly:!0,key:"schema",schema:t})})))},t}(i.PureComponent),Nl=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.toggle=function(){t.props.response.toggle()},t}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.response,t=e.headers,n=e.type,r=e.summary,o=e.description,a=e.code,s=e.expanded,l=e.content,c=void 0===l?[]:l.mediaTypes.filter((function(e){return void 0!==e.schema})),u=0===t.length&&0===c.length&&!o;return i.createElement("div",null,i.createElement(Cl,{onClick:this.toggle,type:n,empty:u,title:r||"",code:a,opened:s}),s&&!u&&i.createElement(Il,null,i.createElement(Rl,{response:this.props.response})))},t=Object(o.__decorate)([fa.a],t)}(i.Component),Ll=_e.h3(dl||(dl=Object(o.__makeTemplateObject)(["\n font-size: 1.3em;\n padding: 0.2em 0;\n margin: 3em 0 1.1em;\n color: ",";\n font-weight: normal;\n"],["\n font-size: 1.3em;\n padding: 0.2em 0;\n margin: 3em 0 1.1em;\n color: ",";\n font-weight: normal;\n"])),(function(e){return e.theme.colors.text.primary})),Ml=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props,t=e.responses,n=e.isCallback;return t&&0!==t.length?i.createElement("div",null,i.createElement(Ll,null,n?"Callback responses":"Responses"),t.map((function(e){return i.createElement(Nl,{key:e.code,response:e})}))):null},t}(i.PureComponent),Dl=_e.code(hl||(hl=Object(o.__makeTemplateObject)(["\n font-size: ",";\n font-family: ",";\n border: 1px solid ",";\n margin: 0 3px;\n padding: 0.2em;\n display: inline-block;\n line-height: 1;\n\n &:after {\n content: ',';\n }\n &:last-child:after {\n content: none;\n }\n"],["\n font-size: ",";\n font-family: ",";\n border: 1px solid ",";\n margin: 0 3px;\n padding: 0.2em;\n display: inline-block;\n line-height: 1;\n\n &:after {\n content: ',';\n }\n &:last-child:after {\n content: none;\n }\n"])),(function(e){return e.theme.typography.code.fontSize}),(function(e){return e.theme.typography.code.fontFamily}),(function(e){return e.theme.colors.border.dark})),Fl=_e.span(ml||(ml=Object(o.__makeTemplateObject)(["\n &:after {\n content: ' AND ';\n font-weight: bold;\n }\n\n &:last-child:after {\n content: none;\n }\n\n ",";\n"],["\n &:after {\n content: ' AND ';\n font-weight: bold;\n }\n\n &:last-child:after {\n content: none;\n }\n\n ",";\n"])),Jo),zl=_e.span(gl||(gl=Object(o.__makeTemplateObject)(["\n &:before {\n content: '( ';\n font-weight: bold;\n }\n &:after {\n content: ' ) OR ';\n font-weight: bold;\n }\n &:last-child:after {\n content: ' )';\n }\n\n &:only-child:before,\n &:only-child:after {\n content: none;\n }\n\n ",";\n"],["\n &:before {\n content: '( ';\n font-weight: bold;\n }\n &:after {\n content: ' ) OR ';\n font-weight: bold;\n }\n &:last-child:after {\n content: ' )';\n }\n\n &:only-child:before,\n &:only-child:after {\n content: none;\n }\n\n ",";\n"])),Jo),Ul=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.security;return i.createElement(zl,null,e.schemes.map((function(e){return i.createElement(Fl,{key:e.id},i.createElement(Un,{to:e.sectionId},e.id),e.scopes.length>0&&" (",e.scopes.map((function(e){return i.createElement(Dl,{key:e},e)})),e.scopes.length>0&&") ")})))},t}(i.PureComponent),Bl=_e.div(yl||(yl=Object(o.__makeTemplateObject)(["\n flex: 1;\n"],["\n flex: 1;\n"]))),$l=_e.div(vl||(vl=Object(o.__makeTemplateObject)(["\n width: ",";\n"],["\n width: ",";\n"])),(function(e){return e.theme.schema.defaultDetailsWidth})),ql=_e(Pn)(bl||(bl=Object(o.__makeTemplateObject)(["\n display: inline-block;\n margin: 0;\n"],["\n display: inline-block;\n margin: 0;\n"]))),Wl=_e.div(xl||(xl=Object(o.__makeTemplateObject)(["\n width: 100%;\n display: flex;\n margin: 1em 0;\n"],["\n width: 100%;\n display: flex;\n margin: 1em 0;\n"]))),Hl=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.securities;return e.length?i.createElement(Wl,null,i.createElement(Bl,null,i.createElement(ql,null,"Authorizations: ")),i.createElement($l,null,e.map((function(e,t){return i.createElement(Ul,{key:t,security:e})})))):null},t}(i.PureComponent),Vl=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.operation,t=e.description,n=e.externalDocs,r=!(!t&&!n);return i.createElement(Qs,null,r&&i.createElement(Yl,null,void 0!==t&&i.createElement(li,{source:t}),n&&i.createElement(Ta,{externalDocs:n})),i.createElement(rl,{operation:this.props.operation,inverted:!0,compact:!0}),i.createElement(Ia,{extensions:e.extensions}),i.createElement(Hl,{securities:e.security}),i.createElement(sl,{parameters:e.parameters,body:e.requestBody}),i.createElement(Ml,{responses:e.responses,isCallback:e.isCallback}))},t=Object(o.__decorate)([fa.a],t)}(i.Component),Yl=_e.div(wl||(wl=Object(o.__makeTemplateObject)(["\n margin-bottom: ","px;\n"],["\n margin-bottom: ","px;\n"])),(function(e){return 3*e.theme.spacing.unit})),Ql=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.toggle=function(){t.props.callbackOperation.toggle()},t}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.callbackOperation,t=e.name,n=e.expanded,r=e.httpVerb,o=e.deprecated;return i.createElement(i.Fragment,null,i.createElement(Ys,{onClick:this.toggle,name:t,opened:n,httpVerb:r,deprecated:o}),n&&i.createElement(Vl,{operation:this.props.callbackOperation}))},t=Object(o.__decorate)([fa.a],t)}(i.Component),Gl=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.callbacks;return e&&0!==e.length?i.createElement("div",null,i.createElement(Xl,null," Callbacks "),e.map((function(e){return e.operations.map((function(t,n){return i.createElement(Ql,{key:e.name+"_"+n,callbackOperation:t})}))}))):null},t}(i.PureComponent),Xl=_e.h3(kl||(kl=Object(o.__makeTemplateObject)(["\n font-size: 1.3em;\n padding: 0.2em 0;\n margin: 3em 0 1.1em;\n color: ",";\n font-weight: normal;\n"],["\n font-size: 1.3em;\n padding: 0.2em 0;\n margin: 3em 0 1.1em;\n color: ",";\n font-weight: normal;\n"])),(function(e){return e.theme.colors.text.primary})),Kl=function(e){function t(t){var n=e.call(this,t)||this;return n.switchItem=function(e){var t=e.value;n.props.items&&n.setState({activeItemIdx:parseInt(t,10)})},n.state={activeItemIdx:0},n}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this,t=this.props.items;if(!t||!t.length)return null;return i.createElement(i.Fragment,null,i.createElement((function(t){var n=t.children;return e.props.label?i.createElement(sa,null,i.createElement(aa,null,e.props.label),n):n}),null,this.props.renderDropdown({value:this.props.options[this.state.activeItemIdx],options:this.props.options,onChange:this.switchItem})),this.props.children(t[this.state.activeItemIdx]))},t=Object(o.__decorate)([fa.a],t)}(i.Component),Zl=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.renderDropdown=function(e){return i.createElement(Go,Object(o.__assign)({Label:ia,Dropdown:la},e))},t}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this,t=this.props.content;return void 0===t?null:i.createElement(il,{content:t,renderDropdown:this.renderDropdown,withLabel:!0},(function(t){return i.createElement(ua,{key:"samples",mediaType:t,renderDropdown:e.renderDropdown})}))},t=Object(o.__decorate)([fa.a],t)}(i.Component),Jl=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.callback.codeSamples.find((function(e){return Kt(e)}));return e?i.createElement(ec,null,i.createElement(Zl,{content:e.requestBodyContent})):null},t}(i.Component),ec=_e.div(Ol||(Ol=Object(o.__makeTemplateObject)(["\n margin-top: 15px;\n"],["\n margin-top: 15px;\n"]))),tc=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.renderDropdown=function(e){return i.createElement(Go,Object(o.__assign)({Label:ia,Dropdown:la},e))},t}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this,t=this.props.callbacks;if(!t||0===t.length)return null;var n=t.map((function(e){return e.operations.map((function(e){return e}))})).reduce((function(e,t){return e.concat(t)}),[]);if(!n.some((function(e){return e.codeSamples.length>0})))return null;var r=n.map((function(e,t){return{label:e.httpVerb.toUpperCase()+": "+e.name,value:t.toString()}}));return i.createElement("div",null,i.createElement(An,null," Callback payload samples "),i.createElement(nc,null,i.createElement(Kl,{items:n,renderDropdown:this.renderDropdown,label:"Callback",options:r},(function(t){return i.createElement(Jl,{key:"callbackPayloadSample",callback:t,renderDropdown:e.renderDropdown})}))))},t.contextType=Me,t=Object(o.__decorate)([fa.a],t)}(i.Component),nc=_e.div(_l||(_l=Object(o.__makeTemplateObject)(["\n background: ",";\n padding: ","px;\n"],["\n background: ",";\n padding: ","px;\n"])),(function(e){return e.theme.codeBlock.backgroundColor}),(function(e){return 4*e.theme.spacing.unit})),rc=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.operation.codeSamples,t=e.length>0,n=1===e.length&&this.context.hideSingleRequestSampleTab;return t&&i.createElement("div",null,i.createElement(An,null," Request samples "),i.createElement(so,{defaultIndex:0},i.createElement(Xr,{hidden:n},e.map((function(e){return i.createElement(Zr,{key:e.lang+"_"+(e.label||"")},void 0!==e.label?e.label:e.lang)}))),e.map((function(e){return i.createElement(eo,{key:e.lang+"_"+(e.label||"")},Kt(e)?i.createElement("div",null,i.createElement(Zl,{content:e.requestBodyContent})):i.createElement(Pi,{lang:e.lang,source:e.source}))}))))||null},t.contextType=Me,t=Object(o.__decorate)([fa.a],t)}(i.Component),oc=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.operation.responses.filter((function(e){return e.content&&e.content.hasSample}));return e.length>0&&i.createElement("div",null,i.createElement(An,null," Response samples "),i.createElement(so,{defaultIndex:0},i.createElement(Xr,null,e.map((function(e){return i.createElement(Zr,{className:"tab-"+e.type,key:e.code},e.code)}))),e.map((function(e){return i.createElement(eo,{key:e.code},i.createElement("div",null,i.createElement(Zl,{content:e.content})))}))))||null},t=Object(o.__decorate)([fa.a],t)}(i.Component),ic=_e(Sn)(El||(El=Object(o.__makeTemplateObject)(["\n backface-visibility: hidden;\n contain: content;\n overflow: hidden;\n"],["\n backface-visibility: hidden;\n contain: content;\n overflow: hidden;\n"]))),ac=_e.div(Sl||(Sl=Object(o.__makeTemplateObject)(["\n margin-bottom: ","px;\n"],["\n margin-bottom: ","px;\n"])),(function(e){return 6*e.theme.spacing.unit})),sc=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.operation,t=e.name,n=e.description,r=e.deprecated,o=e.externalDocs,a=!(!n&&!o);return i.createElement(Me.Consumer,null,(function(s){return i.createElement(ic,null,i.createElement(kn,null,i.createElement(In,null,i.createElement($n,{to:e.id}),t," ",r&&i.createElement(ur,{type:"warning"}," Deprecated ")),s.pathInMiddlePanel&&i.createElement(rl,{operation:e,inverted:!0}),a&&i.createElement(ac,null,void 0!==n&&i.createElement(li,{source:n}),o&&i.createElement(Ta,{externalDocs:o})),i.createElement(Ia,{extensions:e.extensions}),i.createElement(Hl,{securities:e.security}),i.createElement(sl,{parameters:e.parameters,body:e.requestBody}),i.createElement(Ml,{responses:e.responses}),i.createElement(Gl,{callbacks:e.callbacks})),i.createElement(En,null,!s.pathInMiddlePanel&&i.createElement(rl,{operation:e}),i.createElement(rc,{operation:e}),i.createElement(oc,{operation:e}),i.createElement(tc,{callbacks:e.callbacks})))}))},t=Object(o.__decorate)([fa.a],t)}(i.Component),lc=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.items;return 0===e.length?null:e.map((function(e){return i.createElement(cc,{key:e.id,item:e})}))},t=Object(o.__decorate)([fa.a],t)}(i.Component),cc=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e,t=this.props.item;switch(t.type){case"group":e=null;break;case"tag":case"section":e=i.createElement(pc,Object(o.__assign)({},this.props));break;case"operation":e=i.createElement(fc,{item:t});break;default:e=i.createElement(pc,Object(o.__assign)({},this.props))}return i.createElement(i.Fragment,null,e&&i.createElement(On,{id:t.id,underlined:"operation"===t.type},e),t.items&&i.createElement(lc,{items:t.items}))},t=Object(o.__decorate)([fa.a],t)}(i.Component),uc=function(e){return i.createElement(kn,{compact:!0},e)},pc=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.item,t=e.name,n=e.description,r=e.externalDocs,o=2===e.level?In:Cn;return i.createElement(i.Fragment,null,i.createElement(Sn,null,i.createElement(kn,{compact:!1},i.createElement(o,null,i.createElement($n,{to:this.props.item.id}),t))),i.createElement(ds,{source:n||"",htmlWrap:uc}),r&&i.createElement(Sn,null,i.createElement(kn,null,i.createElement(Ta,{externalDocs:r}))))},t=Object(o.__decorate)([fa.a],t)}(i.Component),fc=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){return i.createElement(sc,{operation:this.props.item})},t=Object(o.__decorate)([fa.a],t)}(i.Component),dc=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.ref=i.createRef(),t.activate=function(e){t.props.onActivate(t.props.item),e.stopPropagation()},t}return Object(o.__extends)(t,e),t.prototype.componentDidMount=function(){this.scrollIntoViewIfActive()},t.prototype.componentDidUpdate=function(){this.scrollIntoViewIfActive()},t.prototype.scrollIntoViewIfActive=function(){this.props.item.active&&this.ref.current&&this.ref.current.scrollIntoViewIfNeeded()},t.prototype.render=function(){var e=this.props,t=e.item,n=e.withoutChildren;return i.createElement(Fs,{onClick:this.activate,depth:t.depth,"data-item-id":t.id},"operation"===t.type?i.createElement(hc,Object(o.__assign)({},this.props,{item:t})):i.createElement(Us,{depth:t.depth,active:t.active,type:t.type,ref:this.ref},i.createElement(Bs,{title:t.name},t.name,this.props.children),t.depth>0&&t.items.length>0&&i.createElement(cr,{float:"right",direction:t.expanded?"down":"right"})||null),!n&&t.items&&t.items.length>0&&i.createElement(mc,{expanded:t.expanded,items:t.items,onActivate:this.props.onActivate}))},t=Object(o.__decorate)([fa.a],t)}(i.Component),hc=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.ref=i.createRef(),t}return Object(o.__extends)(t,e),t.prototype.componentDidUpdate=function(){this.props.item.active&&this.ref.current&&this.ref.current.scrollIntoViewIfNeeded()},t.prototype.render=function(){var e=this.props.item;return i.createElement(Us,{depth:e.depth,active:e.active,deprecated:e.deprecated,ref:this.ref},i.createElement(hs,{type:e.httpVerb},ht(e.httpVerb)),i.createElement(Bs,{width:"calc(100% - 38px)"},e.name,this.props.children))},t=Object(o.__decorate)([fa.a],t)}(i.Component),mc=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this,t=this.props,n=t.items,r=t.root,a=t.className,s=null==this.props.expanded||this.props.expanded;return i.createElement(Ds,Object(o.__assign)({className:a,style:this.props.style,expanded:s},r?{role:"navigation"}:{}),n.map((function(t,n){return i.createElement(dc,{key:n,item:t,onActivate:e.props.onActivate})})))},t=Object(o.__decorate)([fa.a],t)}(i.Component),gc=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.activate=function(e){if(e&&e.active&&t.context.menuToggle)return e.expanded?e.collapse():e.expand();t.props.menu.activateAndScroll(e,!0),setTimeout((function(){t._updateScroll&&t._updateScroll()}))},t.saveScrollUpdate=function(e){t._updateScroll=e},t}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props.menu;return i.createElement(Qo,{updateFn:this.saveScrollUpdate,className:this.props.className,options:{wheelPropagation:!1}},i.createElement(mc,{items:e.items,onActivate:this.activate,root:!0}),i.createElement($s,null,i.createElement("a",{target:"_blank",rel:"noopener noreferrer",href:"https://github.com/Redocly/redoc"},"Documentation Powered by ReDoc")))},t.contextType=Me,t=Object(o.__decorate)([fa.a],t)}(i.Component),yc=function(e){var t=e.open?8:-4;return i.createElement(bc,null,i.createElement(vc,{size:15,style:{transform:"translate(2px, "+t+"px) rotate(180deg)",transition:"transform 0.2s ease"}}),i.createElement(vc,{size:15,style:{transform:"translate(2px, "+(0-t)+"px)",transition:"transform 0.2s ease"}}))},vc=function(e){var t=e.size,n=void 0===t?10:t,r=e.className,o=void 0===r?"":r,a=e.style;return i.createElement("svg",{className:o,style:a||{},viewBox:"0 0 926.23699 573.74994",version:"1.1",x:"0px",y:"0px",width:n,height:n},i.createElement("g",{transform:"translate(904.92214,-879.1482)"},i.createElement("path",{d:"\n m -673.67664,1221.6502 -231.2455,-231.24803 55.6165,\n -55.627 c 30.5891,-30.59485 56.1806,-55.627 56.8701,-55.627 0.6894,\n 0 79.8637,78.60862 175.9427,174.68583 l 174.6892,174.6858 174.6892,\n -174.6858 c 96.079,-96.07721 175.253196,-174.68583 175.942696,\n -174.68583 0.6895,0 26.281,25.03215 56.8701,\n 55.627 l 55.6165,55.627 -231.245496,231.24803 c -127.185,127.1864\n -231.5279,231.248 -231.873,231.248 -0.3451,0 -104.688,\n -104.0616 -231.873,-231.248 z\n ",fill:"currentColor"})))},bc=_e.div(Tl||(Tl=Object(o.__makeTemplateObject)(["\n user-select: none;\n width: 20px;\n height: 20px;\n align-self: center;\n display: flex;\n flex-direction: column;\n color: ",";\n"],["\n user-select: none;\n width: 20px;\n height: 20px;\n align-self: center;\n display: flex;\n flex-direction: column;\n color: ",";\n"])),(function(e){return e.theme.colors.primary.main}));ee&&(jl=n(318));var xc,wc,kc,Oc,_c,Ec,Sc,Tc,jc,Cc,Ic,Ac,Pc,Rc,Nc=jl&&jl(),Lc=_e.div(wc||(wc=Object(o.__makeTemplateObject)(["\n width: ",";\n background-color: ",";\n overflow: hidden;\n display: flex;\n flex-direction: column;\n\n backface-visibility: hidden;\n /* contain: strict; TODO: breaks layout since Chrome 80*/\n\n height: 100vh;\n position: sticky;\n position: -webkit-sticky;\n top: 0;\n\n ",";\n\n @media print {\n display: none;\n }\n"],["\n width: ",";\n background-color: ",";\n overflow: hidden;\n display: flex;\n flex-direction: column;\n\n backface-visibility: hidden;\n /* contain: strict; TODO: breaks layout since Chrome 80*/\n\n height: 100vh;\n position: sticky;\n position: -webkit-sticky;\n top: 0;\n\n ",";\n\n @media print {\n display: none;\n }\n"])),(function(e){return e.theme.sidebar.width}),(function(e){return e.theme.sidebar.backgroundColor}),Oe("small")(xc||(xc=Object(o.__makeTemplateObject)(["\n position: fixed;\n z-index: 20;\n width: 100%;\n background: ",";\n display: ",";\n "],["\n position: fixed;\n z-index: 20;\n width: 100%;\n background: ",";\n display: ",";\n "])),(function(e){return e.theme.sidebar.backgroundColor}),(function(e){return e.open?"flex":"none"}))),Mc=_e.div(Oc||(Oc=Object(o.__makeTemplateObject)(["\n outline: none;\n user-select: none;\n background-color: #f2f2f2;\n color: ",";\n display: none;\n cursor: pointer;\n position: fixed;\n right: 20px;\n z-index: 100;\n border-radius: 50%;\n box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);\n ",";\n\n bottom: 44px;\n\n width: 60px;\n height: 60px;\n padding: 0 20px;\n\n @media print {\n display: none;\n }\n"],["\n outline: none;\n user-select: none;\n background-color: #f2f2f2;\n color: ",";\n display: none;\n cursor: pointer;\n position: fixed;\n right: 20px;\n z-index: 100;\n border-radius: 50%;\n box-shadow: 0 0 20px rgba(0, 0, 0, 0.3);\n ",";\n\n bottom: 44px;\n\n width: 60px;\n height: 60px;\n padding: 0 20px;\n\n @media print {\n display: none;\n }\n"])),(function(e){return e.theme.colors.primary.main}),Oe("small")(kc||(kc=Object(o.__makeTemplateObject)(["\n display: flex;\n "],["\n display: flex;\n "])))),Dc=function(e){function t(){var t=null!==e&&e.apply(this,arguments)||this;return t.state={offsetTop:"0px"},t.toggleNavMenu=function(){t.props.menu.toggleSidebar()},t}return Object(o.__extends)(t,e),t.prototype.componentDidMount=function(){Nc&&Nc.add(this.stickyElement),this.setState({offsetTop:this.getScrollYOffset(this.context)})},t.prototype.componentWillUnmount=function(){Nc&&Nc.remove(this.stickyElement)},t.prototype.getScrollYOffset=function(e){return(void 0!==this.props.scrollYOffset?ge.normalizeScrollYOffset(this.props.scrollYOffset)():e.scrollYOffset())+"px"},t.prototype.render=function(){var e=this,t=this.props.menu.sideBarOpened,n=this.state.offsetTop;return i.createElement(i.Fragment,null,i.createElement(Lc,{open:t,className:this.props.className,style:{top:n,height:"calc(100vh - "+n+")"},ref:function(t){e.stickyElement=t}},this.props.children),i.createElement(Mc,{onClick:this.toggleNavMenu},i.createElement(yc,{open:t})))},t.contextType=Me,t=Object(o.__decorate)([fa.a],t)}(i.Component),Fc=_e.div(_c||(_c=Object(o.__makeTemplateObject)(["\n ",";\n"],["\n ",";\n"])),(function(e){var t=e.theme;return"\n font-family: "+t.typography.fontFamily+";\n font-size: "+t.typography.fontSize+";\n font-weight: "+t.typography.fontWeightRegular+";\n line-height: "+t.typography.lineHeight+";\n color: "+t.colors.text.primary+";\n display: flex;\n position: relative;\n text-align: left;\n\n -webkit-font-smoothing: "+t.typography.smoothing+";\n font-smoothing: "+t.typography.smoothing+";\n "+(t.typography.optimizeSpeed?"text-rendering: optimizeSpeed !important":"")+";\n\n tap-highlight-color: rgba(0, 0, 0, 0);\n text-size-adjust: 100%;\n\n * {\n box-sizing: border-box;\n -webkit-tap-highlight-color: rgba(255, 255, 255, 0);\n }\n"})),zc=_e.div(Sc||(Sc=Object(o.__makeTemplateObject)(["\n z-index: 1;\n position: relative;\n overflow: hidden;\n width: calc(100% - ",");\n ",";\n\n contain: layout;\n"],["\n z-index: 1;\n position: relative;\n overflow: hidden;\n width: calc(100% - ",");\n ",";\n\n contain: layout;\n"])),(function(e){return e.theme.sidebar.width}),Oe("small",!0)(Ec||(Ec=Object(o.__makeTemplateObject)(["\n width: 100%;\n "],["\n width: 100%;\n "])))),Uc=_e.div(jc||(jc=Object(o.__makeTemplateObject)(["\n background: ",";\n position: absolute;\n top: 0;\n bottom: 0;\n right: 0;\n width: ",";\n ",";\n"],["\n background: ",";\n position: absolute;\n top: 0;\n bottom: 0;\n right: 0;\n width: ",";\n ",";\n"])),(function(e){return e.theme.rightPanel.backgroundColor}),(function(e){var t=e.theme;if(t.rightPanel.width.endsWith("%")){var n=parseInt(t.rightPanel.width,10);return"calc((100% - "+t.sidebar.width+") * "+n/100+")"}return t.rightPanel.width}),Oe("medium",!0)(Tc||(Tc=Object(o.__makeTemplateObject)(["\n display: none;\n "],["\n display: none;\n "])))),Bc=_e.div(Cc||(Cc=Object(o.__makeTemplateObject)(["\n padding: 5px 0;\n"],["\n padding: 5px 0;\n"]))),$c=_e.input.attrs((function(){return{className:"search-input"}}))(Ic||(Ic=Object(o.__makeTemplateObject)(["\n width: calc(100% - ","px);\n box-sizing: border-box;\n margin: 0 ","px;\n padding: 5px ","px 5px\n ","px;\n border: 0;\n border-bottom: 1px solid\n ",";\n font-family: ",";\n font-weight: bold;\n font-size: 13px;\n color: ",";\n background-color: transparent;\n outline: none;\n"],["\n width: calc(100% - ","px);\n box-sizing: border-box;\n margin: 0 ","px;\n padding: 5px ","px 5px\n ","px;\n border: 0;\n border-bottom: 1px solid\n ",";\n font-family: ",";\n font-weight: bold;\n font-size: 13px;\n color: ",";\n background-color: transparent;\n outline: none;\n"])),(function(e){return 8*e.theme.spacing.unit}),(function(e){return 4*e.theme.spacing.unit}),(function(e){return 2*e.theme.spacing.unit}),(function(e){return 4*e.theme.spacing.unit}),(function(e){var t=e.theme;return(V(t.sidebar.backgroundColor)>.5?q:Q)(.1,t.sidebar.backgroundColor)}),(function(e){return e.theme.typography.fontFamily}),(function(e){return e.theme.sidebar.textColor})),qc=_e((function(e){return i.createElement("svg",{className:e.className,version:"1.1",viewBox:"0 0 1000 1000",x:"0px",xmlns:"http://www.w3.org/2000/svg",y:"0px"},i.createElement("path",{d:"M968.2,849.4L667.3,549c83.9-136.5,66.7-317.4-51.7-435.6C477.1-25,252.5-25,113.9,113.4c-138.5,138.3-138.5,362.6,0,501C219.2,730.1,413.2,743,547.6,666.5l301.9,301.4c43.6,43.6,76.9,14.9,104.2-12.4C981,928.3,1011.8,893,968.2,849.4z M524.5,522c-88.9,88.7-233,88.7-321.8,0c-88.9-88.7-88.9-232.6,0-321.3c88.9-88.7,233-88.7,321.8,0C613.4,289.4,613.4,433.3,524.5,522z"}))})).attrs({className:"search-icon"})(Ac||(Ac=Object(o.__makeTemplateObject)(["\n position: absolute;\n left: ","px;\n height: 1.8em;\n width: 0.9em;\n\n path {\n fill: ",";\n }\n"],["\n position: absolute;\n left: ","px;\n height: 1.8em;\n width: 0.9em;\n\n path {\n fill: ",";\n }\n"])),(function(e){return 4*e.theme.spacing.unit}),(function(e){return e.theme.sidebar.textColor})),Wc=_e.div(Pc||(Pc=Object(o.__makeTemplateObject)(["\n padding: ","px 0;\n background-color: ","};\n color: ",";\n min-height: 150px;\n max-height: 250px;\n border-top: ","};\n border-bottom: ","};\n margin-top: 10px;\n line-height: 1.4;\n font-size: 0.9em;\n\n "," {\n padding-top: 6px;\n padding-bottom: 6px;\n\n &:hover,\n &.active {\n background-color: ",";\n }\n\n > svg {\n display: none;\n }\n }\n"],["\n padding: ","px 0;\n background-color: ","};\n color: ",";\n min-height: 150px;\n max-height: 250px;\n border-top: ","};\n border-bottom: ","};\n margin-top: 10px;\n line-height: 1.4;\n font-size: 0.9em;\n\n "," {\n padding-top: 6px;\n padding-bottom: 6px;\n\n &:hover,\n &.active {\n background-color: ",";\n }\n\n > svg {\n display: none;\n }\n }\n"])),(function(e){return e.theme.spacing.unit}),(function(e){var t=e.theme;return q(.05,t.sidebar.backgroundColor)}),(function(e){return e.theme.sidebar.textColor}),(function(e){var t=e.theme;return q(.1,t.sidebar.backgroundColor)}),(function(e){var t=e.theme;return q(.1,t.sidebar.backgroundColor)}),Us,(function(e){var t=e.theme;return q(.1,t.sidebar.backgroundColor)})),Hc=_e.i(Rc||(Rc=Object(o.__makeTemplateObject)(["\n position: absolute;\n display: inline-block;\n width: ","px;\n text-align: center;\n right: ","px;\n line-height: 2em;\n vertical-align: middle;\n margin-right: 2px;\n cursor: pointer;\n font-style: normal;\n color: '#666';\n"],["\n position: absolute;\n display: inline-block;\n width: ","px;\n text-align: center;\n right: ","px;\n line-height: 2em;\n vertical-align: middle;\n margin-right: 2px;\n cursor: pointer;\n font-style: normal;\n color: '#666';\n"])),(function(e){return 2*e.theme.spacing.unit}),(function(e){return 4*e.theme.spacing.unit})),Vc=function(e){function t(t){var n=e.call(this,t)||this;return n.activeItemRef=null,n.clear=function(){n.setState({results:[],term:"",activeItemIdx:-1}),n.props.marker.unmark()},n.handleKeyDown=function(e){if(27===e.keyCode&&n.clear(),40===e.keyCode&&(n.setState({activeItemIdx:Math.min(n.state.activeItemIdx+1,n.state.results.length-1)}),e.preventDefault()),38===e.keyCode&&(n.setState({activeItemIdx:Math.max(0,n.state.activeItemIdx-1)}),e.preventDefault()),13===e.keyCode){var t=n.state.results[n.state.activeItemIdx];if(t){var r=n.props.getItemById(t.meta);r&&n.props.onActivate(r)}}},n.search=function(e){var t=e.target.value;t.length<3?n.clearResults(t):n.setState({term:t},(function(){return n.searchCallback(n.state.term)}))},n.state={results:[],term:"",activeItemIdx:-1},n}return Object(o.__extends)(t,e),t.prototype.clearResults=function(e){this.setState({results:[],term:e}),this.props.marker.unmark()},t.prototype.setResults=function(e,t){this.setState({results:e}),this.props.marker.mark(t)},t.prototype.searchCallback=function(e){var t=this;this.props.search.search(e).then((function(n){t.setResults(n,e)}))},t.prototype.render=function(){var e=this,t=this.state.activeItemIdx,n=this.state.results.map((function(t){return{item:e.props.getItemById(t.meta),score:t.score}}));return n.sort((function(e,t){return t.score-e.score})),i.createElement(Bc,{role:"search"},this.state.term&&i.createElement(Hc,{onClick:this.clear},"×"),i.createElement(qc,null),i.createElement($c,{value:this.state.term,onKeyDown:this.handleKeyDown,placeholder:"Search...",type:"text",onChange:this.search}),n.length>0&&i.createElement(Qo,{options:{wheelPropagation:!1}},i.createElement(Wc,{"data-role":"search:results"},n.map((function(n,r){return i.createElement(dc,{item:Object.create(n.item,{active:{value:r===t}}),onActivate:e.props.onActivate,withoutChildren:!0,key:n.item.id,"data-role":"search:result"})})))))},Object(o.__decorate)([We.bind,Object(We.debounce)(400)],t.prototype,"searchCallback",null),t}(i.PureComponent),Yc=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.componentDidMount=function(){this.props.store.onDidMount()},t.prototype.componentWillUnmount=function(){this.props.store.dispose()},t.prototype.render=function(){var e=this.props.store,t=e.spec,n=e.menu,r=e.options,o=e.search,a=e.marker,s=this.props.store;return i.createElement(ke,{theme:r.theme},i.createElement(Ln,{value:this.props.store},i.createElement(De,{value:r},i.createElement(Fc,{className:"redoc-wrap"},i.createElement(Dc,{menu:n,className:"menu-content"},i.createElement(fs,{info:t.info}),!r.disableSearch&&i.createElement(Vc,{search:o,marker:a,getItemById:n.getItemById,onActivate:n.activateAndScroll})||null,i.createElement(gc,{menu:n})),i.createElement(zc,{className:"api-content"},i.createElement(ls,{store:s}),i.createElement(lc,{items:n.items})),i.createElement(Uc,null)))))},t.propTypes={store:l.instanceOf(ts).isRequired},t}(i.Component),Qc=function(e){function t(){return null!==e&&e.apply(this,arguments)||this}return Object(o.__extends)(t,e),t.prototype.render=function(){var e=this.props,t=e.spec,n=e.specUrl,r=e.options,o=void 0===r?{}:r,a=e.onLoaded,s=void 0!==o.hideLoading,l=new ge(o);return i.createElement(Ae,null,i.createElement(Dn,{spec:t,specUrl:n,options:o,onLoaded:a},(function(e){var t=e.loading,n=e.store;return t?s?null:i.createElement(Le,{color:l.theme.colors.primary.main}):i.createElement(Yc,{store:n})})))},t.propTypes={spec:function(e,t,n){return e.spec||e.specUrl?null:new Error("One of props 'spec' or 'specUrl' was not specified in '"+n+"'.")},specUrl:function(e,t,n){return e.spec||e.specUrl?null:new Error("One of props 'spec' or 'specUrl' was not specified in '"+n+"'.")},options:l.any,onLoaded:l.any},t}(i.PureComponent),Gc="2.0.0-rc.29",Xc="2c6e3b6";function Kc(e){var t=function(e){for(var t={},n=e.attributes,r=0;r<n.length;r++){var o=n[r];t[o.name]=o.value}return t}(e),n={};for(var r in t){n[r.replace(/-(.)/g,(function(e,t){return t.toUpperCase()}))]=t[r]}return n}function Zc(e,t,n,r){if(void 0===t&&(t={}),void 0===n&&(n=te("redoc")),null===n)throw new Error('"element" argument is not provided and <redoc> tag is not found on the page');var a,l;"string"==typeof e?a=e:"object"==typeof e&&(l=e),Object(s.render)(i.createElement(Qc,{spec:l,onLoaded:r,specUrl:a,options:Object(o.__assign)(Object(o.__assign)({},t),Kc(n))},["Loading..."]),n)}function Jc(e,t,n){void 0===t&&(t=te("redoc")),bt();var r=ts.fromJS(e);xt(),setTimeout((function(){bt(),Object(s.hydrate)(i.createElement(Yc,{store:r}),t,n),xt()}),0)}!function(){var e=te("redoc");if(e){var t=e.getAttribute("spec-url");t&&Zc(t,{},e)}}()}])})); +//# sourceMappingURL=redoc.standalone.js.map</script><style data-styled="bIbMYr cTutD YzuTm cCiYxb hZCbNs eWtOBi jOVKNn bJNzQd hclups kpIQpF kBBDeQ hndQyM cjqQLX bLMrnV cLxwxL gpHEtH hjRNaf hhjjYI jjozHG OtKQc gBhLRG kxjqzZ cxRqCB iXutXb bPGAgL jHaAsr dluJDj hiuczA eesUPo cjtbAK kFNigF dTJWQH eHtzbE kjegA gtbPCV eKrlKP gEjDMA dzbqSt WxWXp kGvRyb ioYTqA hcTXxz bemheR PDnUY ceJGIt lmVwfJ iYKvkC jtJYnZ eFFwMa chVREB bTuXIq gKvVuj dhsNFH fwYGJM dtUibw fBopsv hgxMbQ hzxych dDFDWJ jUNafm ifUWNX dpRRAO bVSqpC hNiVmK jdQHlL kBWwoV eulAfj ecxnvs dpMbau lcundD gwfZGU kZHJcC kwGRVL fCJmC gbTit jCgylq LiUBH hoUoen eCjbJc bIrgla bcLONg kGwPhO fKyGWc hqYVjx lpeYvY bMfIUD jsTAxL beUper iIEWPt dDdNtD bvBDls cMefLx iNoDtm kTYKTV" data-styled-version="4.4.1"> /* sc-component-id: sc-bxivhb */ .cjtbAK{width:calc(100% - 40%);padding:0 40px;} @media print,screen and (max-width:85rem){.cjtbAK{width:100%;padding:40px 40px;}}.kFNigF{width:calc(100% - 40%);padding:0 40px;} @media print,screen and (max-width:85rem){.kFNigF{width:100%;padding:0px 40px;}} /* sc-component-id: sc-ifAKCX */ @@ -168,7 +168,7 @@ /* sc-component-id: sc-VigVT */ .kGvRyb{cursor:pointer;margin-left:-20px;padding:0;line-height:1;width:20px;display:inline-block;} .kGvRyb:before{content:'';width:15px;height:15px;background-size:contain;background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMCIgeT0iMCIgd2lkdGg9IjUxMiIgaGVpZ2h0PSI1MTIiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIj48cGF0aCBmaWxsPSIjMDEwMTAxIiBkPSJNNDU5LjcgMjMzLjRsLTkwLjUgOTAuNWMtNTAgNTAtMTMxIDUwLTE4MSAwIC03LjktNy44LTE0LTE2LjctMTkuNC0yNS44bDQyLjEtNDIuMWMyLTIgNC41LTMuMiA2LjgtNC41IDIuOSA5LjkgOCAxOS4zIDE1LjggMjcuMiAyNSAyNSA2NS42IDI0LjkgOTAuNSAwbDkwLjUtOTAuNWMyNS0yNSAyNS02NS42IDAtOTAuNSAtMjQuOS0yNS02NS41LTI1LTkwLjUgMGwtMzIuMiAzMi4yYy0yNi4xLTEwLjItNTQuMi0xMi45LTgxLjYtOC45bDY4LjYtNjguNmM1MC01MCAxMzEtNTAgMTgxIDBDNTA5LjYgMTAyLjMgNTA5LjYgMTgzLjQgNDU5LjcgMjMzLjR6TTIyMC4zIDM4Mi4ybC0zMi4yIDMyLjJjLTI1IDI0LjktNjUuNiAyNC45LTkwLjUgMCAtMjUtMjUtMjUtNjUuNiAwLTkwLjVsOTAuNS05MC41YzI1LTI1IDY1LjUtMjUgOTAuNSAwIDcuOCA3LjggMTIuOSAxNy4yIDE1LjggMjcuMSAyLjQtMS40IDQuOC0yLjUgNi44LTQuNWw0Mi4xLTQyYy01LjQtOS4yLTExLjYtMTgtMTkuNC0yNS44IC01MC01MC0xMzEtNTAtMTgxIDBsLTkwLjUgOTAuNWMtNTAgNTAtNTAgMTMxIDAgMTgxIDUwIDUwIDEzMSA1MCAxODEgMGw2OC42LTY4LjZDMjc0LjYgMzk1LjEgMjQ2LjQgMzkyLjMgMjIwLjMgMzgyLjJ6Ii8+PC9zdmc+Cg==');opacity:0.5;visibility:hidden;display:inline-block;vertical-align:middle;} h1:hover > .kGvRyb::before,h2:hover > .kGvRyb::before,.kGvRyb:hover::before{visibility:visible;} /* sc-component-id: sc-jTzLTM */ -.hjRNaf{height:18px;width:18px;vertical-align:middle;float:right;-webkit-transition:-webkit-transform 0.2s ease-out;-webkit-transition:transform 0.2s ease-out;transition:transform 0.2s ease-out;-webkit-transform:rotateZ(-90deg);-ms-transform:rotateZ(-90deg);transform:rotateZ(-90deg);}.NifDa{height:1.5em;width:1.5em;vertical-align:middle;float:left;-webkit-transition:-webkit-transform 0.2s ease-out;-webkit-transition:transform 0.2s ease-out;transition:transform 0.2s ease-out;-webkit-transform:rotateZ(-90deg);-ms-transform:rotateZ(-90deg);transform:rotateZ(-90deg);} .NifDa polygon{fill:#00aa13;}.jjozHG{height:1.5em;width:1.5em;vertical-align:middle;float:left;-webkit-transition:-webkit-transform 0.2s ease-out;-webkit-transition:transform 0.2s ease-out;transition:transform 0.2s ease-out;-webkit-transform:rotateZ(-90deg);-ms-transform:rotateZ(-90deg);transform:rotateZ(-90deg);} .jjozHG polygon{fill:#e53935;}.OtKQc{height:20px;width:20px;vertical-align:middle;float:right;-webkit-transition:-webkit-transform 0.2s ease-out;-webkit-transition:transform 0.2s ease-out;transition:transform 0.2s ease-out;-webkit-transform:rotateZ(0);-ms-transform:rotateZ(0);transform:rotateZ(0);} .OtKQc polygon{fill:white;} +.hjRNaf{height:18px;width:18px;vertical-align:middle;float:right;-webkit-transition:-webkit-transform 0.2s ease-out;-webkit-transition:transform 0.2s ease-out;transition:transform 0.2s ease-out;-webkit-transform:rotateZ(-90deg);-ms-transform:rotateZ(-90deg);transform:rotateZ(-90deg);}.hhjjYI{height:1.5em;width:1.5em;vertical-align:middle;float:left;-webkit-transition:-webkit-transform 0.2s ease-out;-webkit-transition:transform 0.2s ease-out;transition:transform 0.2s ease-out;-webkit-transform:rotateZ(-90deg);-ms-transform:rotateZ(-90deg);transform:rotateZ(-90deg);} .hhjjYI polygon{fill:#37d247;}.jjozHG{height:1.5em;width:1.5em;vertical-align:middle;float:left;-webkit-transition:-webkit-transform 0.2s ease-out;-webkit-transition:transform 0.2s ease-out;transition:transform 0.2s ease-out;-webkit-transform:rotateZ(-90deg);-ms-transform:rotateZ(-90deg);transform:rotateZ(-90deg);} .jjozHG polygon{fill:#e53935;}.OtKQc{height:20px;width:20px;vertical-align:middle;float:right;-webkit-transition:-webkit-transform 0.2s ease-out;-webkit-transition:transform 0.2s ease-out;transition:transform 0.2s ease-out;-webkit-transform:rotateZ(0);-ms-transform:rotateZ(0);transform:rotateZ(0);} .OtKQc polygon{fill:white;}.gBhLRG{height:18px;width:18px;vertical-align:middle;-webkit-transition:-webkit-transform 0.2s ease-out;-webkit-transition:transform 0.2s ease-out;transition:transform 0.2s ease-out;-webkit-transform:rotateZ(-90deg);-ms-transform:rotateZ(-90deg);transform:rotateZ(-90deg);} /* sc-component-id: sc-chPdSV */ .bIrgla{border-left:1px solid #a4a4c6;box-sizing:border-box;position:relative;padding:10px 10px 10px 0;vertical-align:top;line-height:20px;white-space:nowrap;font-size:0.929em;font-family:Courier,monospace;} tr:first-of-type > .bIrgla,tr.last > .bIrgla{border-left-width:0;background-position:top left;background-repeat:no-repeat;background-size:1px 100%;} tr:first-of-type > .bIrgla{background-image:linear-gradient( to bottom, transparent 0%, transparent 22px, #a4a4c6 22px, #a4a4c6 100% );} tr.last > .bIrgla{background-image:linear-gradient( to bottom, #a4a4c6 0%, #a4a4c6 22px, transparent 22px, transparent 100% );} tr.last + tr > .bIrgla{border-left-color:transparent;} tr.last:first-child > .bIrgla{background:none;border-left-color:transparent;} .bIrgla.deprecated{-webkit-text-decoration:line-through;text-decoration:line-through;color:#bdccd3;} /* sc-component-id: sc-kgoBCf */ @@ -184,7 +184,7 @@ /* sc-component-id: sc-hSdWYo */ .hoUoen{margin-left:10px;text-transform:none;font-size:0.929em;color:black;} /* sc-component-id: sc-eHgmQL */ -.irpqyy > ul{list-style:none;padding:0;margin:0;margin:0 -5px;} .irpqyy > ul > li{padding:5px 10px;display:inline-block;background-color:#11171a;border-bottom:1px solid rgba(0,0,0,0.5);cursor:pointer;text-align:center;outline:none;color:#b3b3b3;margin:0 5px 5px 5px;border:1px solid #07090b;border-radius:5px;min-width:60px;font-size:0.9em;font-weight:bold;} .irpqyy > ul > li.react-tabs__tab--selected{color:#333333;background:#ffffff;} .irpqyy > ul > li:only-child{-webkit-flex:none;-ms-flex:none;flex:none;min-width:100px;} .irpqyy > ul > li.tab-success{color:#00aa13;} .irpqyy > ul > li.tab-redirect{color:#ffa500;} .irpqyy > ul > li.tab-info{color:#87ceeb;} .irpqyy > ul > li.tab-error{color:#e53935;} .irpqyy > .react-tabs__tab-panel{background:#11171a;} .irpqyy > .react-tabs__tab-panel > div,.irpqyy > .react-tabs__tab-panel > pre{padding:20px;margin:0;} .irpqyy > .react-tabs__tab-panel > div > pre{padding:0;} +.eulAfj > ul{list-style:none;padding:0;margin:0;margin:0 -5px;} .eulAfj > ul > li{padding:5px 10px;display:inline-block;background-color:#11171a;border-bottom:1px solid rgba(0,0,0,0.5);cursor:pointer;text-align:center;outline:none;color:#b3b3b3;margin:0 5px 5px 5px;border:1px solid #07090b;border-radius:5px;min-width:60px;font-size:0.9em;font-weight:bold;} .eulAfj > ul > li.react-tabs__tab--selected{color:#333333;background:#ffffff;} .eulAfj > ul > li:only-child{-webkit-flex:none;-ms-flex:none;flex:none;min-width:100px;} .eulAfj > ul > li.tab-success{color:#37d247;} .eulAfj > ul > li.tab-redirect{color:#ffa500;} .eulAfj > ul > li.tab-info{color:#87ceeb;} .eulAfj > ul > li.tab-error{color:#e53935;} .eulAfj > .react-tabs__tab-panel{background:#11171a;} .eulAfj > .react-tabs__tab-panel > div,.eulAfj > .react-tabs__tab-panel > pre{padding:20px;margin:0;} .eulAfj > .react-tabs__tab-panel > div > pre{padding:0;} /* sc-component-id: sc-jWBwVP */ .jCgylq code[class*='language-'],.jCgylq pre[class*='language-']{text-shadow:0 -0.1em 0.2em black;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none;} @media print{.jCgylq code[class*='language-'],.jCgylq pre[class*='language-']{text-shadow:none;}} .jCgylq pre[class*='language-']{padding:1em;margin:0.5em 0;overflow:auto;} .jCgylq .token.comment,.jCgylq .token.prolog,.jCgylq .token.doctype,.jCgylq .token.cdata{color:hsl(30,20%,50%);} .jCgylq .token.punctuation{opacity:0.7;} .jCgylq .namespace{opacity:0.7;} .jCgylq .token.property,.jCgylq .token.tag,.jCgylq .token.number,.jCgylq .token.constant,.jCgylq .token.symbol{color:#4a8bb3;} .jCgylq .token.boolean{color:firebrick;} .jCgylq .token.selector,.jCgylq .token.attr-name,.jCgylq .token.string,.jCgylq .token.char,.jCgylq .token.builtin,.jCgylq .token.inserted{color:#a0fbaa;} .jCgylq .token.selector + a,.jCgylq .token.attr-name + a,.jCgylq .token.string + a,.jCgylq .token.char + a,.jCgylq .token.builtin + a,.jCgylq .token.inserted + a,.jCgylq .token.selector + a:visited,.jCgylq .token.attr-name + a:visited,.jCgylq .token.string + a:visited,.jCgylq .token.char + a:visited,.jCgylq .token.builtin + a:visited,.jCgylq .token.inserted + a:visited{color:#4ed2ba;-webkit-text-decoration:underline;text-decoration:underline;} .jCgylq .token.operator,.jCgylq .token.entity,.jCgylq .token.url,.jCgylq .token.variable{color:hsl(40,90%,60%);} .jCgylq .token.atrule,.jCgylq .token.attr-value,.jCgylq .token.keyword{color:hsl(350,40%,70%);} .jCgylq .token.regex,.jCgylq .token.important{color:#e90;} .jCgylq .token.important,.jCgylq .token.bold{font-weight:bold;} .jCgylq .token.italic{font-style:italic;} .jCgylq .token.entity{cursor:help;} .jCgylq .token.deleted{color:red;} /* sc-component-id: sc-brqgnP */ @@ -194,9 +194,9 @@ /* sc-component-id: sc-gPEVay */ .hclups{position:relative;} /* sc-component-id: sc-iRbamj */ -.flfxUM{font-family:Roboto,sans-serif;font-weight:400;line-height:1.5em;} .flfxUM code[class*='language-'],.flfxUM pre[class*='language-']{text-shadow:0 -0.1em 0.2em black;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none;} @media print{.flfxUM code[class*='language-'],.flfxUM pre[class*='language-']{text-shadow:none;}} .flfxUM pre[class*='language-']{padding:1em;margin:0.5em 0;overflow:auto;} .flfxUM .token.comment,.flfxUM .token.prolog,.flfxUM .token.doctype,.flfxUM .token.cdata{color:hsl(30,20%,50%);} .flfxUM .token.punctuation{opacity:0.7;} .flfxUM .namespace{opacity:0.7;} .flfxUM .token.property,.flfxUM .token.tag,.flfxUM .token.number,.flfxUM .token.constant,.flfxUM .token.symbol{color:#4a8bb3;} .flfxUM .token.boolean{color:firebrick;} .flfxUM .token.selector,.flfxUM .token.attr-name,.flfxUM .token.string,.flfxUM .token.char,.flfxUM .token.builtin,.flfxUM .token.inserted{color:#a0fbaa;} .flfxUM .token.selector + a,.flfxUM .token.attr-name + a,.flfxUM .token.string + a,.flfxUM .token.char + a,.flfxUM .token.builtin + a,.flfxUM .token.inserted + a,.flfxUM .token.selector + a:visited,.flfxUM .token.attr-name + a:visited,.flfxUM .token.string + a:visited,.flfxUM .token.char + a:visited,.flfxUM .token.builtin + a:visited,.flfxUM .token.inserted + a:visited{color:#4ed2ba;-webkit-text-decoration:underline;text-decoration:underline;} .flfxUM .token.operator,.flfxUM .token.entity,.flfxUM .token.url,.flfxUM .token.variable{color:hsl(40,90%,60%);} .flfxUM .token.atrule,.flfxUM .token.attr-value,.flfxUM .token.keyword{color:hsl(350,40%,70%);} .flfxUM .token.regex,.flfxUM .token.important{color:#e90;} .flfxUM .token.important,.flfxUM .token.bold{font-weight:bold;} .flfxUM .token.italic{font-style:italic;} .flfxUM .token.entity{cursor:help;} .flfxUM .token.deleted{color:red;} .flfxUM p:last-child{margin-bottom:0;} .flfxUM h1{font-family:Montserrat,sans-serif;font-weight:400;font-size:1.85714em;line-height:1.6em;color:#32329f;margin-top:0;} .flfxUM h2{font-family:Montserrat,sans-serif;font-weight:400;font-size:1.57143em;line-height:1.6em;color:#333333;} .flfxUM code{color:#e53935;background-color:rgba(38,50,56,0.05);font-family:Courier,monospace;border-radius:2px;border:1px solid rgba(38,50,56,0.1);padding:0 5px;font-size:13px;font-weight:400;word-break:break-word;} .flfxUM pre{font-family:Courier,monospace;white-space:pre;background-color:#263238;color:white;padding:20px;overflow-x:auto;line-height:normal;border-radius:0px;border:1px solid rgba(38,50,56,0.1);} .flfxUM pre code{background-color:transparent;color:white;padding:0;} .flfxUM pre code:before,.flfxUM pre code:after{content:none;} .flfxUM blockquote{margin:0;margin-bottom:1em;padding:0 15px;color:#777;border-left:4px solid #ddd;} .flfxUM img{max-width:100%;box-sizing:content-box;} .flfxUM ul,.flfxUM ol{padding-left:2em;margin:0;margin-bottom:1em;} .flfxUM ul ul,.flfxUM ol ul,.flfxUM ul ol,.flfxUM ol ol{margin-bottom:0;margin-top:0;} .flfxUM table{display:block;width:100%;overflow:auto;word-break:normal;word-break:keep-all;border-collapse:collapse;border-spacing:0;margin-top:1.5em;margin-bottom:1.5em;} .flfxUM table tr{background-color:#fff;border-top:1px solid #ccc;} .flfxUM table tr:nth-child(2n){background-color:#fafafa;} .flfxUM table th,.flfxUM table td{padding:6px 13px;border:1px solid #ddd;} .flfxUM table th{text-align:left;font-weight:bold;} .flfxUM .share-link{cursor:pointer;margin-left:-20px;padding:0;line-height:1;width:20px;display:inline-block;} .flfxUM .share-link:before{content:'';width:15px;height:15px;background-size:contain;background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMCIgeT0iMCIgd2lkdGg9IjUxMiIgaGVpZ2h0PSI1MTIiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIj48cGF0aCBmaWxsPSIjMDEwMTAxIiBkPSJNNDU5LjcgMjMzLjRsLTkwLjUgOTAuNWMtNTAgNTAtMTMxIDUwLTE4MSAwIC03LjktNy44LTE0LTE2LjctMTkuNC0yNS44bDQyLjEtNDIuMWMyLTIgNC41LTMuMiA2LjgtNC41IDIuOSA5LjkgOCAxOS4zIDE1LjggMjcuMiAyNSAyNSA2NS42IDI0LjkgOTAuNSAwbDkwLjUtOTAuNWMyNS0yNSAyNS02NS42IDAtOTAuNSAtMjQuOS0yNS02NS41LTI1LTkwLjUgMGwtMzIuMiAzMi4yYy0yNi4xLTEwLjItNTQuMi0xMi45LTgxLjYtOC45bDY4LjYtNjguNmM1MC01MCAxMzEtNTAgMTgxIDBDNTA5LjYgMTAyLjMgNTA5LjYgMTgzLjQgNDU5LjcgMjMzLjR6TTIyMC4zIDM4Mi4ybC0zMi4yIDMyLjJjLTI1IDI0LjktNjUuNiAyNC45LTkwLjUgMCAtMjUtMjUtMjUtNjUuNiAwLTkwLjVsOTAuNS05MC41YzI1LTI1IDY1LjUtMjUgOTAuNSAwIDcuOCA3LjggMTIuOSAxNy4yIDE1LjggMjcuMSAyLjQtMS40IDQuOC0yLjUgNi44LTQuNWw0Mi4xLTQyYy01LjQtOS4yLTExLjYtMTgtMTkuNC0yNS44IC01MC01MC0xMzEtNTAtMTgxIDBsLTkwLjUgOTAuNWMtNTAgNTAtNTAgMTMxIDAgMTgxIDUwIDUwIDEzMSA1MCAxODEgMGw2OC42LTY4LjZDMjc0LjYgMzk1LjEgMjQ2LjQgMzkyLjMgMjIwLjMgMzgyLjJ6Ii8+PC9zdmc+Cg==');opacity:0.5;visibility:hidden;display:inline-block;vertical-align:middle;} .flfxUM h1:hover > .share-link::before,.flfxUM h2:hover > .share-link::before,.flfxUM .share-link:hover::before{visibility:visible;} .flfxUM a{-webkit-text-decoration:none;text-decoration:none;color:#32329f;} .flfxUM a:visited{color:#32329f;} .flfxUM a:hover{color:#6868cf;}.gDsWLk{font-family:Roboto,sans-serif;font-weight:400;line-height:1.5em;} .gDsWLk code[class*='language-'],.gDsWLk pre[class*='language-']{text-shadow:0 -0.1em 0.2em black;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none;} @media print{.gDsWLk code[class*='language-'],.gDsWLk pre[class*='language-']{text-shadow:none;}} .gDsWLk pre[class*='language-']{padding:1em;margin:0.5em 0;overflow:auto;} .gDsWLk .token.comment,.gDsWLk .token.prolog,.gDsWLk .token.doctype,.gDsWLk .token.cdata{color:hsl(30,20%,50%);} .gDsWLk .token.punctuation{opacity:0.7;} .gDsWLk .namespace{opacity:0.7;} .gDsWLk .token.property,.gDsWLk .token.tag,.gDsWLk .token.number,.gDsWLk .token.constant,.gDsWLk .token.symbol{color:#4a8bb3;} .gDsWLk .token.boolean{color:firebrick;} .gDsWLk .token.selector,.gDsWLk .token.attr-name,.gDsWLk .token.string,.gDsWLk .token.char,.gDsWLk .token.builtin,.gDsWLk .token.inserted{color:#a0fbaa;} .gDsWLk .token.selector + a,.gDsWLk .token.attr-name + a,.gDsWLk .token.string + a,.gDsWLk .token.char + a,.gDsWLk .token.builtin + a,.gDsWLk .token.inserted + a,.gDsWLk .token.selector + a:visited,.gDsWLk .token.attr-name + a:visited,.gDsWLk .token.string + a:visited,.gDsWLk .token.char + a:visited,.gDsWLk .token.builtin + a:visited,.gDsWLk .token.inserted + a:visited{color:#4ed2ba;-webkit-text-decoration:underline;text-decoration:underline;} .gDsWLk .token.operator,.gDsWLk .token.entity,.gDsWLk .token.url,.gDsWLk .token.variable{color:hsl(40,90%,60%);} .gDsWLk .token.atrule,.gDsWLk .token.attr-value,.gDsWLk .token.keyword{color:hsl(350,40%,70%);} .gDsWLk .token.regex,.gDsWLk .token.important{color:#e90;} .gDsWLk .token.important,.gDsWLk .token.bold{font-weight:bold;} .gDsWLk .token.italic{font-style:italic;} .gDsWLk .token.entity{cursor:help;} .gDsWLk .token.deleted{color:red;} .gDsWLk p:last-child{margin-bottom:0;} .gDsWLk p:first-child{margin-top:0;} .gDsWLk p:last-child{margin-bottom:0;} .gDsWLk h1{font-family:Montserrat,sans-serif;font-weight:400;font-size:1.85714em;line-height:1.6em;color:#32329f;margin-top:0;} .gDsWLk h2{font-family:Montserrat,sans-serif;font-weight:400;font-size:1.57143em;line-height:1.6em;color:#333333;} .gDsWLk code{color:#e53935;background-color:rgba(38,50,56,0.05);font-family:Courier,monospace;border-radius:2px;border:1px solid rgba(38,50,56,0.1);padding:0 5px;font-size:13px;font-weight:400;word-break:break-word;} .gDsWLk pre{font-family:Courier,monospace;white-space:pre;background-color:#263238;color:white;padding:20px;overflow-x:auto;line-height:normal;border-radius:0px;border:1px solid rgba(38,50,56,0.1);} .gDsWLk pre code{background-color:transparent;color:white;padding:0;} .gDsWLk pre code:before,.gDsWLk pre code:after{content:none;} .gDsWLk blockquote{margin:0;margin-bottom:1em;padding:0 15px;color:#777;border-left:4px solid #ddd;} .gDsWLk img{max-width:100%;box-sizing:content-box;} .gDsWLk ul,.gDsWLk ol{padding-left:2em;margin:0;margin-bottom:1em;} .gDsWLk ul ul,.gDsWLk ol ul,.gDsWLk ul ol,.gDsWLk ol ol{margin-bottom:0;margin-top:0;} .gDsWLk table{display:block;width:100%;overflow:auto;word-break:normal;word-break:keep-all;border-collapse:collapse;border-spacing:0;margin-top:1.5em;margin-bottom:1.5em;} .gDsWLk table tr{background-color:#fff;border-top:1px solid #ccc;} .gDsWLk table tr:nth-child(2n){background-color:#fafafa;} .gDsWLk table th,.gDsWLk table td{padding:6px 13px;border:1px solid #ddd;} .gDsWLk table th{text-align:left;font-weight:bold;} .gDsWLk .share-link{cursor:pointer;margin-left:-20px;padding:0;line-height:1;width:20px;display:inline-block;} .gDsWLk .share-link:before{content:'';width:15px;height:15px;background-size:contain;background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMCIgeT0iMCIgd2lkdGg9IjUxMiIgaGVpZ2h0PSI1MTIiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIj48cGF0aCBmaWxsPSIjMDEwMTAxIiBkPSJNNDU5LjcgMjMzLjRsLTkwLjUgOTAuNWMtNTAgNTAtMTMxIDUwLTE4MSAwIC03LjktNy44LTE0LTE2LjctMTkuNC0yNS44bDQyLjEtNDIuMWMyLTIgNC41LTMuMiA2LjgtNC41IDIuOSA5LjkgOCAxOS4zIDE1LjggMjcuMiAyNSAyNSA2NS42IDI0LjkgOTAuNSAwbDkwLjUtOTAuNWMyNS0yNSAyNS02NS42IDAtOTAuNSAtMjQuOS0yNS02NS41LTI1LTkwLjUgMGwtMzIuMiAzMi4yYy0yNi4xLTEwLjItNTQuMi0xMi45LTgxLjYtOC45bDY4LjYtNjguNmM1MC01MCAxMzEtNTAgMTgxIDBDNTA5LjYgMTAyLjMgNTA5LjYgMTgzLjQgNDU5LjcgMjMzLjR6TTIyMC4zIDM4Mi4ybC0zMi4yIDMyLjJjLTI1IDI0LjktNjUuNiAyNC45LTkwLjUgMCAtMjUtMjUtMjUtNjUuNiAwLTkwLjVsOTAuNS05MC41YzI1LTI1IDY1LjUtMjUgOTAuNSAwIDcuOCA3LjggMTIuOSAxNy4yIDE1LjggMjcuMSAyLjQtMS40IDQuOC0yLjUgNi44LTQuNWw0Mi4xLTQyYy01LjQtOS4yLTExLjYtMTgtMTkuNC0yNS44IC01MC01MC0xMzEtNTAtMTgxIDBsLTkwLjUgOTAuNWMtNTAgNTAtNTAgMTMxIDAgMTgxIDUwIDUwIDEzMSA1MCAxODEgMGw2OC42LTY4LjZDMjc0LjYgMzk1LjEgMjQ2LjQgMzkyLjMgMjIwLjMgMzgyLjJ6Ii8+PC9zdmc+Cg==');opacity:0.5;visibility:hidden;display:inline-block;vertical-align:middle;} .gDsWLk h1:hover > .share-link::before,.gDsWLk h2:hover > .share-link::before,.gDsWLk .share-link:hover::before{visibility:visible;} .gDsWLk a{-webkit-text-decoration:none;text-decoration:none;color:#32329f;} .gDsWLk a:visited{color:#32329f;} .gDsWLk a:hover{color:#6868cf;} +.eHtzbE{font-family:Roboto,sans-serif;font-weight:400;line-height:1.5em;} .eHtzbE code[class*='language-'],.eHtzbE pre[class*='language-']{text-shadow:0 -0.1em 0.2em black;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none;} @media print{.eHtzbE code[class*='language-'],.eHtzbE pre[class*='language-']{text-shadow:none;}} .eHtzbE pre[class*='language-']{padding:1em;margin:0.5em 0;overflow:auto;} .eHtzbE .token.comment,.eHtzbE .token.prolog,.eHtzbE .token.doctype,.eHtzbE .token.cdata{color:hsl(30,20%,50%);} .eHtzbE .token.punctuation{opacity:0.7;} .eHtzbE .namespace{opacity:0.7;} .eHtzbE .token.property,.eHtzbE .token.tag,.eHtzbE .token.number,.eHtzbE .token.constant,.eHtzbE .token.symbol{color:#4a8bb3;} .eHtzbE .token.boolean{color:firebrick;} .eHtzbE .token.selector,.eHtzbE .token.attr-name,.eHtzbE .token.string,.eHtzbE .token.char,.eHtzbE .token.builtin,.eHtzbE .token.inserted{color:#a0fbaa;} .eHtzbE .token.selector + a,.eHtzbE .token.attr-name + a,.eHtzbE .token.string + a,.eHtzbE .token.char + a,.eHtzbE .token.builtin + a,.eHtzbE .token.inserted + a,.eHtzbE .token.selector + a:visited,.eHtzbE .token.attr-name + a:visited,.eHtzbE .token.string + a:visited,.eHtzbE .token.char + a:visited,.eHtzbE .token.builtin + a:visited,.eHtzbE .token.inserted + a:visited{color:#4ed2ba;-webkit-text-decoration:underline;text-decoration:underline;} .eHtzbE .token.operator,.eHtzbE .token.entity,.eHtzbE .token.url,.eHtzbE .token.variable{color:hsl(40,90%,60%);} .eHtzbE .token.atrule,.eHtzbE .token.attr-value,.eHtzbE .token.keyword{color:hsl(350,40%,70%);} .eHtzbE .token.regex,.eHtzbE .token.important{color:#e90;} .eHtzbE .token.important,.eHtzbE .token.bold{font-weight:bold;} .eHtzbE .token.italic{font-style:italic;} .eHtzbE .token.entity{cursor:help;} .eHtzbE .token.deleted{color:red;} .eHtzbE p:last-child{margin-bottom:0;} .eHtzbE h1{font-family:Montserrat,sans-serif;font-weight:400;font-size:1.85714em;line-height:1.6em;color:#32329f;margin-top:0;} .eHtzbE h2{font-family:Montserrat,sans-serif;font-weight:400;font-size:1.57143em;line-height:1.6em;color:#333333;} .eHtzbE code{color:#e53935;background-color:rgba(38,50,56,0.05);font-family:Courier,monospace;border-radius:2px;border:1px solid rgba(38,50,56,0.1);padding:0 5px;font-size:13px;font-weight:400;word-break:break-word;} .eHtzbE pre{font-family:Courier,monospace;white-space:pre;background-color:#11171a;color:white;padding:20px;overflow-x:auto;line-height:normal;border-radius:0px;border:1px solid rgba(38,50,56,0.1);} .eHtzbE pre code{background-color:transparent;color:white;padding:0;} .eHtzbE pre code:before,.eHtzbE pre code:after{content:none;} .eHtzbE blockquote{margin:0;margin-bottom:1em;padding:0 15px;color:#777;border-left:4px solid #ddd;} .eHtzbE img{max-width:100%;box-sizing:content-box;} .eHtzbE ul,.eHtzbE ol{padding-left:2em;margin:0;margin-bottom:1em;} .eHtzbE ul ul,.eHtzbE ol ul,.eHtzbE ul ol,.eHtzbE ol ol{margin-bottom:0;margin-top:0;} .eHtzbE table{display:block;width:100%;overflow:auto;word-break:normal;word-break:keep-all;border-collapse:collapse;border-spacing:0;margin-top:1.5em;margin-bottom:1.5em;} .eHtzbE table tr{background-color:#fff;border-top:1px solid #ccc;} .eHtzbE table tr:nth-child(2n){background-color:#fafafa;} .eHtzbE table th,.eHtzbE table td{padding:6px 13px;border:1px solid #ddd;} .eHtzbE table th{text-align:left;font-weight:bold;} .eHtzbE .share-link{cursor:pointer;margin-left:-20px;padding:0;line-height:1;width:20px;display:inline-block;} .eHtzbE .share-link:before{content:'';width:15px;height:15px;background-size:contain;background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMCIgeT0iMCIgd2lkdGg9IjUxMiIgaGVpZ2h0PSI1MTIiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIj48cGF0aCBmaWxsPSIjMDEwMTAxIiBkPSJNNDU5LjcgMjMzLjRsLTkwLjUgOTAuNWMtNTAgNTAtMTMxIDUwLTE4MSAwIC03LjktNy44LTE0LTE2LjctMTkuNC0yNS44bDQyLjEtNDIuMWMyLTIgNC41LTMuMiA2LjgtNC41IDIuOSA5LjkgOCAxOS4zIDE1LjggMjcuMiAyNSAyNSA2NS42IDI0LjkgOTAuNSAwbDkwLjUtOTAuNWMyNS0yNSAyNS02NS42IDAtOTAuNSAtMjQuOS0yNS02NS41LTI1LTkwLjUgMGwtMzIuMiAzMi4yYy0yNi4xLTEwLjItNTQuMi0xMi45LTgxLjYtOC45bDY4LjYtNjguNmM1MC01MCAxMzEtNTAgMTgxIDBDNTA5LjYgMTAyLjMgNTA5LjYgMTgzLjQgNDU5LjcgMjMzLjR6TTIyMC4zIDM4Mi4ybC0zMi4yIDMyLjJjLTI1IDI0LjktNjUuNiAyNC45LTkwLjUgMCAtMjUtMjUtMjUtNjUuNiAwLTkwLjVsOTAuNS05MC41YzI1LTI1IDY1LjUtMjUgOTAuNSAwIDcuOCA3LjggMTIuOSAxNy4yIDE1LjggMjcuMSAyLjQtMS40IDQuOC0yLjUgNi44LTQuNWw0Mi4xLTQyYy01LjQtOS4yLTExLjYtMTgtMTkuNC0yNS44IC01MC01MC0xMzEtNTAtMTgxIDBsLTkwLjUgOTAuNWMtNTAgNTAtNTAgMTMxIDAgMTgxIDUwIDUwIDEzMSA1MCAxODEgMGw2OC42LTY4LjZDMjc0LjYgMzk1LjEgMjQ2LjQgMzkyLjMgMjIwLjMgMzgyLjJ6Ii8+PC9zdmc+Cg==');opacity:0.5;visibility:hidden;display:inline-block;vertical-align:middle;} .eHtzbE h1:hover > .share-link::before,.eHtzbE h2:hover > .share-link::before,.eHtzbE .share-link:hover::before{visibility:visible;} .eHtzbE a{-webkit-text-decoration:none;text-decoration:none;color:#32329f;} .eHtzbE a:visited{color:#32329f;} .eHtzbE a:hover{color:#6868cf;}.kjegA{font-family:Roboto,sans-serif;font-weight:400;line-height:1.5em;} .kjegA code[class*='language-'],.kjegA pre[class*='language-']{text-shadow:0 -0.1em 0.2em black;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none;} @media print{.kjegA code[class*='language-'],.kjegA pre[class*='language-']{text-shadow:none;}} .kjegA pre[class*='language-']{padding:1em;margin:0.5em 0;overflow:auto;} .kjegA .token.comment,.kjegA .token.prolog,.kjegA .token.doctype,.kjegA .token.cdata{color:hsl(30,20%,50%);} .kjegA .token.punctuation{opacity:0.7;} .kjegA .namespace{opacity:0.7;} .kjegA .token.property,.kjegA .token.tag,.kjegA .token.number,.kjegA .token.constant,.kjegA .token.symbol{color:#4a8bb3;} .kjegA .token.boolean{color:firebrick;} .kjegA .token.selector,.kjegA .token.attr-name,.kjegA .token.string,.kjegA .token.char,.kjegA .token.builtin,.kjegA .token.inserted{color:#a0fbaa;} .kjegA .token.selector + a,.kjegA .token.attr-name + a,.kjegA .token.string + a,.kjegA .token.char + a,.kjegA .token.builtin + a,.kjegA .token.inserted + a,.kjegA .token.selector + a:visited,.kjegA .token.attr-name + a:visited,.kjegA .token.string + a:visited,.kjegA .token.char + a:visited,.kjegA .token.builtin + a:visited,.kjegA .token.inserted + a:visited{color:#4ed2ba;-webkit-text-decoration:underline;text-decoration:underline;} .kjegA .token.operator,.kjegA .token.entity,.kjegA .token.url,.kjegA .token.variable{color:hsl(40,90%,60%);} .kjegA .token.atrule,.kjegA .token.attr-value,.kjegA .token.keyword{color:hsl(350,40%,70%);} .kjegA .token.regex,.kjegA .token.important{color:#e90;} .kjegA .token.important,.kjegA .token.bold{font-weight:bold;} .kjegA .token.italic{font-style:italic;} .kjegA .token.entity{cursor:help;} .kjegA .token.deleted{color:red;} .kjegA p:last-child{margin-bottom:0;} .kjegA p:first-child{margin-top:0;} .kjegA p:last-child{margin-bottom:0;} .kjegA h1{font-family:Montserrat,sans-serif;font-weight:400;font-size:1.85714em;line-height:1.6em;color:#32329f;margin-top:0;} .kjegA h2{font-family:Montserrat,sans-serif;font-weight:400;font-size:1.57143em;line-height:1.6em;color:#333333;} .kjegA code{color:#e53935;background-color:rgba(38,50,56,0.05);font-family:Courier,monospace;border-radius:2px;border:1px solid rgba(38,50,56,0.1);padding:0 5px;font-size:13px;font-weight:400;word-break:break-word;} .kjegA pre{font-family:Courier,monospace;white-space:pre;background-color:#11171a;color:white;padding:20px;overflow-x:auto;line-height:normal;border-radius:0px;border:1px solid rgba(38,50,56,0.1);} .kjegA pre code{background-color:transparent;color:white;padding:0;} .kjegA pre code:before,.kjegA pre code:after{content:none;} .kjegA blockquote{margin:0;margin-bottom:1em;padding:0 15px;color:#777;border-left:4px solid #ddd;} .kjegA img{max-width:100%;box-sizing:content-box;} .kjegA ul,.kjegA ol{padding-left:2em;margin:0;margin-bottom:1em;} .kjegA ul ul,.kjegA ol ul,.kjegA ul ol,.kjegA ol ol{margin-bottom:0;margin-top:0;} .kjegA table{display:block;width:100%;overflow:auto;word-break:normal;word-break:keep-all;border-collapse:collapse;border-spacing:0;margin-top:1.5em;margin-bottom:1.5em;} .kjegA table tr{background-color:#fff;border-top:1px solid #ccc;} .kjegA table tr:nth-child(2n){background-color:#fafafa;} .kjegA table th,.kjegA table td{padding:6px 13px;border:1px solid #ddd;} .kjegA table th{text-align:left;font-weight:bold;} .kjegA .share-link{cursor:pointer;margin-left:-20px;padding:0;line-height:1;width:20px;display:inline-block;} .kjegA .share-link:before{content:'';width:15px;height:15px;background-size:contain;background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMCIgeT0iMCIgd2lkdGg9IjUxMiIgaGVpZ2h0PSI1MTIiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIj48cGF0aCBmaWxsPSIjMDEwMTAxIiBkPSJNNDU5LjcgMjMzLjRsLTkwLjUgOTAuNWMtNTAgNTAtMTMxIDUwLTE4MSAwIC03LjktNy44LTE0LTE2LjctMTkuNC0yNS44bDQyLjEtNDIuMWMyLTIgNC41LTMuMiA2LjgtNC41IDIuOSA5LjkgOCAxOS4zIDE1LjggMjcuMiAyNSAyNSA2NS42IDI0LjkgOTAuNSAwbDkwLjUtOTAuNWMyNS0yNSAyNS02NS42IDAtOTAuNSAtMjQuOS0yNS02NS41LTI1LTkwLjUgMGwtMzIuMiAzMi4yYy0yNi4xLTEwLjItNTQuMi0xMi45LTgxLjYtOC45bDY4LjYtNjguNmM1MC01MCAxMzEtNTAgMTgxIDBDNTA5LjYgMTAyLjMgNTA5LjYgMTgzLjQgNDU5LjcgMjMzLjR6TTIyMC4zIDM4Mi4ybC0zMi4yIDMyLjJjLTI1IDI0LjktNjUuNiAyNC45LTkwLjUgMCAtMjUtMjUtMjUtNjUuNiAwLTkwLjVsOTAuNS05MC41YzI1LTI1IDY1LjUtMjUgOTAuNSAwIDcuOCA3LjggMTIuOSAxNy4yIDE1LjggMjcuMSAyLjQtMS40IDQuOC0yLjUgNi44LTQuNWw0Mi4xLTQyYy01LjQtOS4yLTExLjYtMTgtMTkuNC0yNS44IC01MC01MC0xMzEtNTAtMTgxIDBsLTkwLjUgOTAuNWMtNTAgNTAtNTAgMTMxIDAgMTgxIDUwIDUwIDEzMSA1MCAxODEgMGw2OC42LTY4LjZDMjc0LjYgMzk1LjEgMjQ2LjQgMzkyLjMgMjIwLjMgMzgyLjJ6Ii8+PC9zdmc+Cg==');opacity:0.5;visibility:hidden;display:inline-block;vertical-align:middle;} .kjegA h1:hover > .share-link::before,.kjegA h2:hover > .share-link::before,.kjegA .share-link:hover::before{visibility:visible;} .kjegA a{-webkit-text-decoration:none;text-decoration:none;color:#32329f;} .kjegA a:visited{color:#32329f;} .kjegA a:hover{color:#6868cf;} /* sc-component-id: sc-jlyJG */ -.evvbxn{font-family:Roboto,sans-serif;font-weight:400;line-height:1.5em;} .evvbxn p:last-child{margin-bottom:0;} .evvbxn p:first-child{margin-top:0;} .evvbxn p:last-child{margin-bottom:0;} .evvbxn p{display:inline-block;} .evvbxn h1{font-family:Montserrat,sans-serif;font-weight:400;font-size:1.85714em;line-height:1.6em;color:#32329f;margin-top:0;} .evvbxn h2{font-family:Montserrat,sans-serif;font-weight:400;font-size:1.57143em;line-height:1.6em;color:#333333;} .evvbxn code{color:#e53935;background-color:rgba(38,50,56,0.05);font-family:Courier,monospace;border-radius:2px;border:1px solid rgba(38,50,56,0.1);padding:0 5px;font-size:13px;font-weight:400;word-break:break-word;} .evvbxn pre{font-family:Courier,monospace;white-space:pre;background-color:#263238;color:white;padding:20px;overflow-x:auto;line-height:normal;border-radius:0px;border:1px solid rgba(38,50,56,0.1);} .evvbxn pre code{background-color:transparent;color:white;padding:0;} .evvbxn pre code:before,.evvbxn pre code:after{content:none;} .evvbxn blockquote{margin:0;margin-bottom:1em;padding:0 15px;color:#777;border-left:4px solid #ddd;} .evvbxn img{max-width:100%;box-sizing:content-box;} .evvbxn ul,.evvbxn ol{padding-left:2em;margin:0;margin-bottom:1em;} .evvbxn ul ul,.evvbxn ol ul,.evvbxn ul ol,.evvbxn ol ol{margin-bottom:0;margin-top:0;} .evvbxn table{display:block;width:100%;overflow:auto;word-break:normal;word-break:keep-all;border-collapse:collapse;border-spacing:0;margin-top:1.5em;margin-bottom:1.5em;} .evvbxn table tr{background-color:#fff;border-top:1px solid #ccc;} .evvbxn table tr:nth-child(2n){background-color:#fafafa;} .evvbxn table th,.evvbxn table td{padding:6px 13px;border:1px solid #ddd;} .evvbxn table th{text-align:left;font-weight:bold;} .evvbxn .share-link{cursor:pointer;margin-left:-20px;padding:0;line-height:1;width:20px;display:inline-block;} .evvbxn .share-link:before{content:'';width:15px;height:15px;background-size:contain;background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMCIgeT0iMCIgd2lkdGg9IjUxMiIgaGVpZ2h0PSI1MTIiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIj48cGF0aCBmaWxsPSIjMDEwMTAxIiBkPSJNNDU5LjcgMjMzLjRsLTkwLjUgOTAuNWMtNTAgNTAtMTMxIDUwLTE4MSAwIC03LjktNy44LTE0LTE2LjctMTkuNC0yNS44bDQyLjEtNDIuMWMyLTIgNC41LTMuMiA2LjgtNC41IDIuOSA5LjkgOCAxOS4zIDE1LjggMjcuMiAyNSAyNSA2NS42IDI0LjkgOTAuNSAwbDkwLjUtOTAuNWMyNS0yNSAyNS02NS42IDAtOTAuNSAtMjQuOS0yNS02NS41LTI1LTkwLjUgMGwtMzIuMiAzMi4yYy0yNi4xLTEwLjItNTQuMi0xMi45LTgxLjYtOC45bDY4LjYtNjguNmM1MC01MCAxMzEtNTAgMTgxIDBDNTA5LjYgMTAyLjMgNTA5LjYgMTgzLjQgNDU5LjcgMjMzLjR6TTIyMC4zIDM4Mi4ybC0zMi4yIDMyLjJjLTI1IDI0LjktNjUuNiAyNC45LTkwLjUgMCAtMjUtMjUtMjUtNjUuNiAwLTkwLjVsOTAuNS05MC41YzI1LTI1IDY1LjUtMjUgOTAuNSAwIDcuOCA3LjggMTIuOSAxNy4yIDE1LjggMjcuMSAyLjQtMS40IDQuOC0yLjUgNi44LTQuNWw0Mi4xLTQyYy01LjQtOS4yLTExLjYtMTgtMTkuNC0yNS44IC01MC01MC0xMzEtNTAtMTgxIDBsLTkwLjUgOTAuNWMtNTAgNTAtNTAgMTMxIDAgMTgxIDUwIDUwIDEzMSA1MCAxODEgMGw2OC42LTY4LjZDMjc0LjYgMzk1LjEgMjQ2LjQgMzkyLjMgMjIwLjMgMzgyLjJ6Ii8+PC9zdmc+Cg==');opacity:0.5;visibility:hidden;display:inline-block;vertical-align:middle;} .evvbxn h1:hover > .share-link::before,.evvbxn h2:hover > .share-link::before,.evvbxn .share-link:hover::before{visibility:visible;} .evvbxn a{-webkit-text-decoration:none;text-decoration:none;color:#32329f;} .evvbxn a:visited{color:#32329f;} .evvbxn a:hover{color:#6868cf;} +.fwYGJM{font-family:Roboto,sans-serif;font-weight:400;line-height:1.5em;} .fwYGJM p:last-child{margin-bottom:0;} .fwYGJM p:first-child{margin-top:0;} .fwYGJM p:last-child{margin-bottom:0;} .fwYGJM p{display:inline-block;} .fwYGJM h1{font-family:Montserrat,sans-serif;font-weight:400;font-size:1.85714em;line-height:1.6em;color:#32329f;margin-top:0;} .fwYGJM h2{font-family:Montserrat,sans-serif;font-weight:400;font-size:1.57143em;line-height:1.6em;color:#333333;} .fwYGJM code{color:#e53935;background-color:rgba(38,50,56,0.05);font-family:Courier,monospace;border-radius:2px;border:1px solid rgba(38,50,56,0.1);padding:0 5px;font-size:13px;font-weight:400;word-break:break-word;} .fwYGJM pre{font-family:Courier,monospace;white-space:pre;background-color:#11171a;color:white;padding:20px;overflow-x:auto;line-height:normal;border-radius:0px;border:1px solid rgba(38,50,56,0.1);} .fwYGJM pre code{background-color:transparent;color:white;padding:0;} .fwYGJM pre code:before,.fwYGJM pre code:after{content:none;} .fwYGJM blockquote{margin:0;margin-bottom:1em;padding:0 15px;color:#777;border-left:4px solid #ddd;} .fwYGJM img{max-width:100%;box-sizing:content-box;} .fwYGJM ul,.fwYGJM ol{padding-left:2em;margin:0;margin-bottom:1em;} .fwYGJM ul ul,.fwYGJM ol ul,.fwYGJM ul ol,.fwYGJM ol ol{margin-bottom:0;margin-top:0;} .fwYGJM table{display:block;width:100%;overflow:auto;word-break:normal;word-break:keep-all;border-collapse:collapse;border-spacing:0;margin-top:1.5em;margin-bottom:1.5em;} .fwYGJM table tr{background-color:#fff;border-top:1px solid #ccc;} .fwYGJM table tr:nth-child(2n){background-color:#fafafa;} .fwYGJM table th,.fwYGJM table td{padding:6px 13px;border:1px solid #ddd;} .fwYGJM table th{text-align:left;font-weight:bold;} .fwYGJM .share-link{cursor:pointer;margin-left:-20px;padding:0;line-height:1;width:20px;display:inline-block;} .fwYGJM .share-link:before{content:'';width:15px;height:15px;background-size:contain;background-image:url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgeD0iMCIgeT0iMCIgd2lkdGg9IjUxMiIgaGVpZ2h0PSI1MTIiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIj48cGF0aCBmaWxsPSIjMDEwMTAxIiBkPSJNNDU5LjcgMjMzLjRsLTkwLjUgOTAuNWMtNTAgNTAtMTMxIDUwLTE4MSAwIC03LjktNy44LTE0LTE2LjctMTkuNC0yNS44bDQyLjEtNDIuMWMyLTIgNC41LTMuMiA2LjgtNC41IDIuOSA5LjkgOCAxOS4zIDE1LjggMjcuMiAyNSAyNSA2NS42IDI0LjkgOTAuNSAwbDkwLjUtOTAuNWMyNS0yNSAyNS02NS42IDAtOTAuNSAtMjQuOS0yNS02NS41LTI1LTkwLjUgMGwtMzIuMiAzMi4yYy0yNi4xLTEwLjItNTQuMi0xMi45LTgxLjYtOC45bDY4LjYtNjguNmM1MC01MCAxMzEtNTAgMTgxIDBDNTA5LjYgMTAyLjMgNTA5LjYgMTgzLjQgNDU5LjcgMjMzLjR6TTIyMC4zIDM4Mi4ybC0zMi4yIDMyLjJjLTI1IDI0LjktNjUuNiAyNC45LTkwLjUgMCAtMjUtMjUtMjUtNjUuNiAwLTkwLjVsOTAuNS05MC41YzI1LTI1IDY1LjUtMjUgOTAuNSAwIDcuOCA3LjggMTIuOSAxNy4yIDE1LjggMjcuMSAyLjQtMS40IDQuOC0yLjUgNi44LTQuNWw0Mi4xLTQyYy01LjQtOS4yLTExLjYtMTgtMTkuNC0yNS44IC01MC01MC0xMzEtNTAtMTgxIDBsLTkwLjUgOTAuNWMtNTAgNTAtNTAgMTMxIDAgMTgxIDUwIDUwIDEzMSA1MCAxODEgMGw2OC42LTY4LjZDMjc0LjYgMzk1LjEgMjQ2LjQgMzkyLjMgMjIwLjMgMzgyLjJ6Ii8+PC9zdmc+Cg==');opacity:0.5;visibility:hidden;display:inline-block;vertical-align:middle;} .fwYGJM h1:hover > .share-link::before,.fwYGJM h2:hover > .share-link::before,.fwYGJM .share-link:hover::before{visibility:visible;} .fwYGJM a{-webkit-text-decoration:none;text-decoration:none;color:#32329f;} .fwYGJM a:visited{color:#32329f;} .fwYGJM a:hover{color:#6868cf;} /* sc-component-id: sc-gipzik */ .gbTit{position:relative;} /* sc-component-id: sc-jhAzac */ @@ -204,15 +204,17 @@ /* sc-component-id: sc-fBuWsC */ .kZHJcC{font-family:Courier,monospace;font-size:13px;white-space:pre;contain:content;overflow-x:auto;} .kZHJcC .redoc-json > .collapser{display:none;} .kZHJcC .callback-function{color:gray;} .kZHJcC .collapser:after{content:'-';cursor:pointer;} .kZHJcC .collapsed > .collapser:after{content:'+';cursor:pointer;} .kZHJcC .ellipsis:after{content:' … ';} .kZHJcC .collapsible{margin-left:2em;} .kZHJcC .hoverable{padding-top:1px;padding-bottom:1px;padding-left:2px;padding-right:2px;border-radius:2px;} .kZHJcC .hovered{background-color:rgba(235,238,249,1);} .kZHJcC .collapser{padding-right:6px;padding-left:6px;} .kZHJcC ul{list-style-type:none;padding:0px;margin:0px 0px 0px 26px;} .kZHJcC li{position:relative;display:block;} .kZHJcC .hoverable{display:inline-block;} .kZHJcC .selected{outline-style:solid;outline-width:1px;outline-style:dotted;} .kZHJcC .collapsed > .collapsible{display:none;} .kZHJcC .ellipsis{display:none;} .kZHJcC .collapsed > .ellipsis{display:inherit;} .kZHJcC .collapser{position:absolute;top:1px;left:-1.5em;cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-user-select:none;} /* sc-component-id: sc-fMiknA */ -.gzAoUb{padding:12px;background-color:rgba(38,50,56,0.4);margin:0 0 10px 0;display:block;} +.lcundD{padding:0.9em;background-color:rgba(38,50,56,0.4);margin:0 0 10px 0;display:block;font-family:Montserrat,sans-serif;font-size:0.929em;line-height:1.5em;} /* sc-component-id: sc-dVhcbM */ .dpMbau{font-family:Montserrat,sans-serif;font-size:12px;position:absolute;z-index:1;top:-11px;left:12px;font-weight:600;color:rgba(255,255,255,0.4);} /* sc-component-id: sc-eqIVtm */ .ecxnvs{position:relative;} /* sc-component-id: sc-fAjcbJ */ -.hrtKLV{min-width:100px;display:inline-block;position:relative;width:auto;font-family:Montserrat,sans-serif;margin-left:10px;text-transform:none;font-size:0.929em;margin:0 0 10px 0;display:block;background-color:rgba(38,50,56,0.4);} .hrtKLV .Dropdown-control{font-family:Montserrat,sans-serif;position:relative;font-size:0.929em;width:100%;line-height:1.5em;vertical-align:middle;cursor:pointer;border-color:rgba(38,50,56,0.5);color:#263238;outline:none;padding:0.15em 1.5em 0.2em 0.5em;border-radius:2px;border-width:1px;border-style:solid;margin-top:5px;background:white;box-sizing:border-box;} .hrtKLV .Dropdown-control:hover{border-color:#32329f;color:#32329f;box-shadow:0px 2px 4px 0px rgba(34,36,38,0.12);} .hrtKLV .Dropdown-arrow{border-color:#32329f transparent transparent;border-style:solid;border-width:0.35em 0.35em 0;content:' ';display:block;height:0;position:absolute;right:0.3em;top:50%;margin-top:-0.125em;width:0;} .hrtKLV .Dropdown-menu{position:absolute;margin-top:2px;left:0;right:0;z-index:10;min-width:100px;background:white;border:1px solid rgba(38,50,56,0.2);box-shadow:0px 2px 4px 0px rgba(34,36,38,0.12),0px 2px 10px 0px rgba(34,36,38,0.08);max-height:220px;overflow:auto;} .hrtKLV .Dropdown-option{font-size:0.9em;color:#263238;cursor:pointer;padding:0.4em;} .hrtKLV .Dropdown-option.is-selected{background-color:rgba(0,0,0,0.05);} .hrtKLV .Dropdown-option:hover{background-color:rgba(38,50,56,0.12);} .hrtKLV .Dropdown-control{margin-top:0;} .hrtKLV .Dropdown-control,.hrtKLV .Dropdown-control:hover{font-size:1em;border:none;padding:0.9em 1.6em 0.9em 0.9em;background:transparent;color:#ffffff;box-shadow:none;} .hrtKLV .Dropdown-control .Dropdown-arrow,.hrtKLV .Dropdown-control:hover .Dropdown-arrow{border-top-color:#ffffff;} .hrtKLV .Dropdown-menu{margin:0;margin-top:2px;} +.iIEWPt{min-width:100px;display:inline-block;position:relative;width:auto;font-family:Montserrat,sans-serif;margin-left:10px;text-transform:none;font-size:0.929em;margin:0 0 10px 0;display:block;background-color:rgba(38,50,56,0.4);} .iIEWPt .Dropdown-control{font-family:Montserrat,sans-serif;position:relative;font-size:0.929em;width:100%;line-height:1.5em;vertical-align:middle;cursor:pointer;border-color:rgba(38,50,56,0.5);color:#263238;outline:none;padding:0.15em 1.5em 0.2em 0.5em;border-radius:2px;border-width:1px;border-style:solid;margin-top:5px;background:white;box-sizing:border-box;} .iIEWPt .Dropdown-control:hover{border-color:#32329f;color:#32329f;box-shadow:0px 2px 4px 0px rgba(34,36,38,0.12);} .iIEWPt .Dropdown-arrow{border-color:#32329f transparent transparent;border-style:solid;border-width:0.35em 0.35em 0;content:' ';display:block;height:0;position:absolute;right:0.3em;top:50%;margin-top:-0.125em;width:0;} .iIEWPt .Dropdown-menu{position:absolute;margin-top:2px;left:0;right:0;z-index:10;min-width:100px;background:white;border:1px solid rgba(38,50,56,0.2);box-shadow:0px 2px 4px 0px rgba(34,36,38,0.12),0px 2px 10px 0px rgba(34,36,38,0.08);max-height:220px;overflow:auto;} .iIEWPt .Dropdown-option{font-size:0.9em;color:#263238;cursor:pointer;padding:0.4em;} .iIEWPt .Dropdown-option.is-selected{background-color:rgba(0,0,0,0.05);} .iIEWPt .Dropdown-option:hover{background-color:rgba(38,50,56,0.12);} .iIEWPt .Dropdown-placeholder{text-overflow:ellipsis;white-space:nowrap;overflow:hidden;} .iIEWPt .Dropdown-control{margin-top:0;} .iIEWPt .Dropdown-control,.iIEWPt .Dropdown-control:hover{font-size:1em;border:none;padding:0.9em 1.6em 0.9em 0.9em;background:transparent;color:#ffffff;box-shadow:none;} .iIEWPt .Dropdown-control .Dropdown-arrow,.iIEWPt .Dropdown-control:hover .Dropdown-arrow{border-top-color:#ffffff;} .iIEWPt .Dropdown-menu{margin:0;margin-top:2px;} .iIEWPt .Dropdown-menu .Dropdown-option{text-overflow:ellipsis;white-space:nowrap;overflow:hidden;} /* sc-component-id: sc-gisBJw */ .gwfZGU{margin-top:15px;} +/* sc-component-id: sc-kjoXOD */ +.iNoDtm{border-left:1px solid #a4a4c6;box-sizing:border-box;position:relative;padding:10px 10px 10px 0;vertical-align:top;line-height:20px;white-space:nowrap;font-size:0.929em;font-family:Courier,monospace;cursor:pointer;} tr:first-of-type > .iNoDtm,tr.last > .iNoDtm{border-left-width:0;background-position:top left;background-repeat:no-repeat;background-size:1px 100%;} tr:first-of-type > .iNoDtm{background-image:linear-gradient( to bottom, transparent 0%, transparent 22px, #a4a4c6 22px, #a4a4c6 100% );} tr.last > .iNoDtm{background-image:linear-gradient( to bottom, #a4a4c6 0%, #a4a4c6 22px, transparent 22px, transparent 100% );} tr.last + tr > .iNoDtm{border-left-color:transparent;} tr.last:first-child > .iNoDtm{background:none;border-left-color:transparent;} .iNoDtm.deprecated{-webkit-text-decoration:line-through;text-decoration:line-through;color:#bdccd3;} .iNoDtm .sc-jTzLTM{height:1.1em;width:1.1em;} .iNoDtm .sc-jTzLTM polygon{fill:#808080;} /* sc-component-id: sc-cHGsZl */ .lpeYvY{vertical-align:middle;font-size:13px;line-height:20px;} /* sc-component-id: sc-TOsTZ */ @@ -243,76 +245,76 @@ .YzuTm{text-align:center;} /* sc-component-id: sc-iELTvK */ .cCiYxb{display:inline-block;} +/* sc-component-id: sc-cmTdod */ +.kxjqzZ{width:32px;display:inline-block;height:13px;line-height:13px;background-color:#333;border-radius:3px;background-repeat:no-repeat;background-position:6px 4px;font-size:7px;font-family:Verdana;color:white;text-transform:uppercase;text-align:center;font-weight:bold;vertical-align:middle;margin-right:6px;margin-top:2px;} .kxjqzZ.get{background-color:#6bbd5b;} .kxjqzZ.post{background-color:#248fb2;} .kxjqzZ.put{background-color:#9b708b;} .kxjqzZ.options{background-color:#d3ca12;} .kxjqzZ.patch{background-color:#e09d43;} .kxjqzZ.delete{background-color:#e27a7a;} .kxjqzZ.basic{background-color:#999;} .kxjqzZ.link{background-color:#31bbb6;} .kxjqzZ.head{background-color:#c167e4;} /* sc-component-id: sc-jwKygS */ -.bnFPhO:after{content:' AND ';font-weight:bold;} .bnFPhO:last-child:after{content:none;} .bnFPhO a{-webkit-text-decoration:none;text-decoration:none;color:#32329f;} .bnFPhO a:visited{color:#32329f;} .bnFPhO a:hover{color:#6868cf;} +.kpIQpF{margin:0;padding:0;} .kpIQpF .sc-jwKygS{font-size:0.929em;}.kBBDeQ{margin:0;padding:0;display:none;} .kBBDeQ .sc-jwKygS{font-size:0.929em;} /* sc-component-id: sc-btzYZH */ -.hQBRTt:before{content:'( ';font-weight:bold;} .hQBRTt:after{content:' ) OR ';font-weight:bold;} .hQBRTt:last-child:after{content:' )';} .hQBRTt:only-child:before,.hQBRTt:only-child:after{content:none;} .hQBRTt a{-webkit-text-decoration:none;text-decoration:none;color:#32329f;} .hQBRTt a:visited{color:#32329f;} .hQBRTt a:hover{color:#6868cf;} +.hndQyM{list-style:none inside none;overflow:hidden;text-overflow:ellipsis;padding:0;} /* sc-component-id: sc-lhVmIH */ -.dVvUxe{-webkit-flex:1;-ms-flex:1;flex:1;} +.cjqQLX{cursor:pointer;color:#333333;margin:0;padding:12.5px 20px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;font-family:Montserrat,sans-serif;font-size:0.929em;text-transform:none;} .cjqQLX:hover{color:#32329f;} .cjqQLX:hover{background-color:#ededed;} .cjqQLX .sc-jTzLTM{height:1.5em;width:1.5em;} .cjqQLX .sc-jTzLTM polygon{fill:#333333;}.bLMrnV{cursor:pointer;color:#333333;margin:0;padding:12.5px 20px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;font-family:Montserrat,sans-serif;color:#333333;} .bLMrnV:hover{background-color:#e1e1e1;} .bLMrnV .sc-jTzLTM{height:1.5em;width:1.5em;} .bLMrnV .sc-jTzLTM polygon{fill:#333333;} /* sc-component-id: sc-bYSBpT */ -.fKHsnH{width:75%;} +.cLxwxL{display:inline-block;vertical-align:middle;width:auto;overflow:hidden;text-overflow:ellipsis;}.gpHEtH{display:inline-block;vertical-align:middle;width:calc(100% - 38px);overflow:hidden;text-overflow:ellipsis;} /* sc-component-id: sc-elJkPf */ -.jGRUDj{border-bottom:1px solid rgba(38,50,56,0.3);margin:1em 0 1em 0;color:rgba(38,50,56,0.5);font-weight:normal;text-transform:uppercase;font-size:0.929em;line-height:20px;display:inline-block;margin:0;} -/* sc-component-id: sc-jtRfpW */ -.geWpKA{width:100%;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;margin:1em 0;} -/* sc-component-id: sc-kTUwUJ */ -.ldTMcP{cursor:pointer;position:relative;margin-bottom:5px;} -/* sc-component-id: sc-dqBHgY */ -.iENVAs{font-family:Courier,monospace;margin-left:10px;-webkit-flex:1;-ms-flex:1;flex:1;overflow-x:hidden;text-overflow:ellipsis;} -/* sc-component-id: sc-gxMtzJ */ -.dHLKeu{padding:10px 30px 10px 20px;border-radius:4px 4px 0 0;background-color:#11171a;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;white-space:nowrap;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;border:1px solid transparent;border-bottom:0;-webkit-transition:border-color 0.25s ease;transition:border-color 0.25s ease;} .dHLKeu ..sc-dqBHgY{color:#ffffff;} -/* sc-component-id: sc-dfVpRl */ -.jBjYbV{font-size:0.929em;line-height:20px;background-color:#6bbd5b;color:#ffffff;padding:3px 10px;text-transform:uppercase;font-family:Montserrat,sans-serif;margin:0;}.bNYCAJ{font-size:0.929em;line-height:20px;background-color:#248fb2;color:#ffffff;padding:3px 10px;text-transform:uppercase;font-family:Montserrat,sans-serif;margin:0;}.fRsrDc{font-size:0.929em;line-height:20px;background-color:#e09d43;color:#ffffff;padding:3px 10px;text-transform:uppercase;font-family:Montserrat,sans-serif;margin:0;}.hPskZd{font-size:0.929em;line-height:20px;background-color:#e27a7a;color:#ffffff;padding:3px 10px;text-transform:uppercase;font-family:Montserrat,sans-serif;margin:0;} +.cxRqCB{font-size:0.8em;margin-top:10px;padding:0 20px;text-align:left;opacity:0.7;} .cxRqCB a,.cxRqCB a:visited,.cxRqCB a:hover{color:#333333 !important;border-top:1px solid #e1e1e1;padding:5px 0;display:block;} /* sc-component-id: sc-gzOgki */ -.lkvpfX{position:absolute;width:100%;z-index:100;background:#fafafa;color:#263238;box-sizing:border-box;box-shadow:0px 0px 6px rgba(0,0,0,0.33);overflow:hidden;border-bottom-left-radius:4px;border-bottom-right-radius:4px;-webkit-transition:all 0.25s ease;transition:all 0.25s ease;-webkit-transform:translateY(-50%) scaleY(0);-ms-transform:translateY(-50%) scaleY(0);transform:translateY(-50%) scaleY(0);} +.fBopsv{cursor:pointer;position:relative;margin-bottom:5px;} /* sc-component-id: sc-iyvyFf */ -.eftLSo{padding:10px;} +.dpRRAO{font-family:Courier,monospace;margin-left:10px;-webkit-flex:1;-ms-flex:1;flex:1;overflow-x:hidden;text-overflow:ellipsis;} /* sc-component-id: sc-hwwEjo */ -.bwgXFh{padding:5px;border:1px solid #ccc;background:#fff;word-break:break-all;color:#32329f;} .bwgXFh > span{color:#333333;} +.hgxMbQ{padding:10px 30px 10px 20px;border-radius:4px 4px 0 0;background-color:#11171a;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;white-space:nowrap;-webkit-align-items:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;border:1px solid transparent;border-bottom:0;-webkit-transition:border-color 0.25s ease;transition:border-color 0.25s ease;} .hgxMbQ ..sc-iyvyFf{color:#ffffff;} /* sc-component-id: sc-kPVwWT */ -.fDvFMp{padding:10px;border-radius:2px;margin-bottom:4px;line-height:1.5em;background-color:#f2f2f2;cursor:pointer;color:#00aa13;background-color:rgba(0,170,19,0.1);}.byLrBg{padding:10px;border-radius:2px;margin-bottom:4px;line-height:1.5em;background-color:#f2f2f2;cursor:pointer;color:#e53935;background-color:rgba(229,57,53,0.1);}.hLVzSF{padding:10px;border-radius:2px;margin-bottom:4px;line-height:1.5em;background-color:#f2f2f2;cursor:pointer;color:#00aa13;background-color:rgba(0,170,19,0.1);cursor:default;} .hLVzSF::before{content:"—";font-weight:bold;width:1.5em;text-align:center;display:inline-block;} +.hzxych{font-size:0.929em;line-height:20px;background-color:#6bbd5b;color:#ffffff;padding:3px 10px;text-transform:uppercase;font-family:Montserrat,sans-serif;margin:0;}.dDFDWJ{font-size:0.929em;line-height:20px;background-color:#248fb2;color:#ffffff;padding:3px 10px;text-transform:uppercase;font-family:Montserrat,sans-serif;margin:0;}.jUNafm{font-size:0.929em;line-height:20px;background-color:#e09d43;color:#ffffff;padding:3px 10px;text-transform:uppercase;font-family:Montserrat,sans-serif;margin:0;}.ifUWNX{font-size:0.929em;line-height:20px;background-color:#e27a7a;color:#ffffff;padding:3px 10px;text-transform:uppercase;font-family:Montserrat,sans-serif;margin:0;} +/* sc-component-id: sc-kfGgVZ */ +.bVSqpC{position:absolute;width:100%;z-index:100;background:#fafafa;color:#263238;box-sizing:border-box;box-shadow:0px 0px 6px rgba(0,0,0,0.33);overflow:hidden;border-bottom-left-radius:4px;border-bottom-right-radius:4px;-webkit-transition:all 0.25s ease;transition:all 0.25s ease;-webkit-transform:translateY(-50%) scaleY(0);-ms-transform:translateY(-50%) scaleY(0);transform:translateY(-50%) scaleY(0);} +/* sc-component-id: sc-esjQYD */ +.hNiVmK{padding:10px;} +/* sc-component-id: sc-kIPQKe */ +.jdQHlL{padding:5px;border:1px solid #ccc;background:#fff;word-break:break-all;color:#32329f;} .jdQHlL > span{color:#333333;} /* sc-component-id: sc-eXEjpC */ -.espozG{font-size:18px;padding:0.2em 0;margin:3em 0 1.1em;color:#253137;font-weight:normal;} -/* sc-component-id: sc-ibxdXY */ -.bSFXlp{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:100%;padding:0;-webkit-backface-visibility:hidden;backface-visibility:hidden;contain:content;overflow:hidden;} @media print,screen and (max-width:85rem){.bSFXlp{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;}} -/* sc-component-id: sc-RefOD */ -.boajtD{margin-bottom:30px;} -/* sc-component-id: sc-iQKALj */ -.cFwMcp{width:32px;display:inline-block;height:13px;line-height:13px;background-color:#333;border-radius:3px;background-repeat:no-repeat;background-position:6px 4px;font-size:7px;font-family:Verdana;color:white;text-transform:uppercase;text-align:center;font-weight:bold;vertical-align:middle;margin-right:6px;margin-top:2px;} .cFwMcp.get{background-color:#6bbd5b;} .cFwMcp.post{background-color:#248fb2;} .cFwMcp.put{background-color:#9b708b;} .cFwMcp.options{background-color:#d3ca12;} .cFwMcp.patch{background-color:#e09d43;} .cFwMcp.delete{background-color:#e27a7a;} .cFwMcp.basic{background-color:#999;} .cFwMcp.link{background-color:#31bbb6;} .cFwMcp.head{background-color:#c167e4;} +.bTuXIq{padding:10px;border-radius:2px;margin-bottom:4px;line-height:1.5em;background-color:#f2f2f2;cursor:pointer;color:#37d247;background-color:rgba(55,210,71,0.1);}.gKvVuj{padding:10px;border-radius:2px;margin-bottom:4px;line-height:1.5em;background-color:#f2f2f2;cursor:pointer;color:#e53935;background-color:rgba(229,57,53,0.1);}.dhsNFH{padding:10px;border-radius:2px;margin-bottom:4px;line-height:1.5em;background-color:#f2f2f2;cursor:pointer;color:#37d247;background-color:rgba(55,210,71,0.1);cursor:default;} .dhsNFH::before{content:"—";font-weight:bold;width:1.5em;text-align:center;display:inline-block;} /* sc-component-id: sc-bwCtUz */ -.ghctpd{margin:0;padding:0;} .ghctpd .sc-bwCtUz{font-size:0.929em;}.cLEtWf{margin:0;padding:0;display:none;} .cLEtWf .sc-bwCtUz{font-size:0.929em;} -/* sc-component-id: sc-hrWEMg */ -.bbViyS{list-style:none inside none;overflow:hidden;text-overflow:ellipsis;padding:0;} +.chVREB{font-size:1.3em;padding:0.2em 0;margin:3em 0 1.1em;color:#333333;font-weight:normal;} /* sc-component-id: sc-eTuwsz */ -.iNzLCk{cursor:pointer;color:#333333;margin:0;padding:12.5px 20px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;font-family:Montserrat,sans-serif;font-size:0.929em;text-transform:none;} .iNzLCk:hover{color:#32329f;} .iNzLCk:hover{background-color:#ededed;} .iNzLCk .sc-jTzLTM{height:1.5em;width:1.5em;} .iNzLCk .sc-jTzLTM polygon{fill:#333333;}.gpbcFk{cursor:pointer;color:#333333;margin:0;padding:12.5px 20px;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between;font-family:Montserrat,sans-serif;color:#333333;} .gpbcFk:hover{background-color:#e1e1e1;} .gpbcFk .sc-jTzLTM{height:1.5em;width:1.5em;} .gpbcFk .sc-jTzLTM polygon{fill:#333333;} +.eFFwMa:after{content:' AND ';font-weight:bold;} .eFFwMa:last-child:after{content:none;} .eFFwMa a{-webkit-text-decoration:none;text-decoration:none;color:#32329f;} .eFFwMa a:visited{color:#32329f;} .eFFwMa a:hover{color:#6868cf;} /* sc-component-id: sc-gwVKww */ -.fyUykq{display:inline-block;vertical-align:middle;width:auto;overflow:hidden;text-overflow:ellipsis;}.SmuWE{display:inline-block;vertical-align:middle;width:calc(100% - 38px);overflow:hidden;text-overflow:ellipsis;} +.jtJYnZ:before{content:'( ';font-weight:bold;} .jtJYnZ:after{content:' ) OR ';font-weight:bold;} .jtJYnZ:last-child:after{content:' )';} .jtJYnZ:only-child:before,.jtJYnZ:only-child:after{content:none;} .jtJYnZ a{-webkit-text-decoration:none;text-decoration:none;color:#32329f;} .jtJYnZ a:visited{color:#32329f;} .jtJYnZ a:hover{color:#6868cf;} /* sc-component-id: sc-hXRMBi */ -.nGwee{font-size:0.8em;margin-top:10px;padding:0 20px;text-align:left;opacity:0.7;} .nGwee a,.nGwee a:visited,.nGwee a:hover{color:#333333 !important;border-top:1px solid #e1e1e1;padding:5px 0;display:block;} +.ceJGIt{-webkit-flex:1;-ms-flex:1;flex:1;} /* sc-component-id: sc-epnACN */ -.fWqlcz{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;width:20px;height:20px;-webkit-align-self:center;-ms-flex-item-align:center;align-self:center;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;color:#32329f;} +.iYKvkC{width:75%;} /* sc-component-id: sc-iQNlJl */ -.jzMYjV{width:260px;background-color:#fafafa;overflow:hidden;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-backface-visibility:hidden;backface-visibility:hidden;height:100vh;position:-webkit-sticky;position:sticky;position:-webkit-sticky;top:0;} @media screen and (max-width:50rem){.jzMYjV{position:fixed;z-index:20;width:100%;background:#fafafa;display:none;}} @media print{.jzMYjV{display:none;}} +.lmVwfJ{border-bottom:1px solid rgba(38,50,56,0.3);margin:1em 0 1em 0;color:rgba(38,50,56,0.5);font-weight:normal;text-transform:uppercase;font-size:0.929em;line-height:20px;display:inline-block;margin:0;} /* sc-component-id: sc-bsbRJL */ -.fXybtJ{outline:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:#f2f2f2;color:#32329f;display:none;cursor:pointer;position:fixed;right:20px;z-index:100;border-radius:50%;box-shadow:0 0 20px rgba(0,0,0,0.3);bottom:44px;width:60px;height:60px;padding:0 20px;} @media screen and (max-width:50rem){.fXybtJ{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;}} @media print{.fXybtJ{display:none;}} -/* sc-component-id: sc-hZSUBg */ -.bxcHYI{font-family:Roboto,sans-serif;font-size:14px;font-weight:400;line-height:1.5em;color:#333333;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;position:relative;text-align:left;-webkit-font-smoothing:antialiased;font-smoothing:antialiased;text-rendering:optimizeSpeed !important;tap-highlight-color:rgba(0,0,0,0);-webkit-text-size-adjust:100%;text-size-adjust:100%;} .bxcHYI *{box-sizing:border-box;-webkit-tap-highlight-color:rgba(255,255,255,0);} -/* sc-component-id: sc-cMhqgX */ -.iniCdN{z-index:1;position:relative;overflow:hidden;width:calc(100% - 260px);contain:layout;} @media print,screen and (max-width:50rem){.iniCdN{width:100%;}} -/* sc-component-id: sc-iuJeZd */ -.fLUKgj{background:#263238;position:absolute;top:0;bottom:0;right:0;width:calc((100% - 260px) * 0.4);} @media print,screen and (max-width:85rem){.fLUKgj{display:none;}} -/* sc-component-id: sc-esOvli */ -.kKQhLA{padding:5px 0;} +.PDnUY{width:100%;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;margin:1em 0;} /* sc-component-id: sc-cmthru */ -.kzNiFq{width:calc(100% - 40px);box-sizing:border-box;margin:0 20px;padding:5px 10px 5px 20px;border:0;border-bottom:1px solid #e1e1e1;font-family:Roboto,sans-serif;font-weight:bold;font-size:13px;color:#333333;background-color:transparent;outline:none;} +.hcTXxz{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;width:100%;padding:0;-webkit-backface-visibility:hidden;backface-visibility:hidden;contain:content;overflow:hidden;} @media print,screen and (max-width:85rem){.hcTXxz{-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;}} /* sc-component-id: sc-hMFtBS */ -.ibpoCO{position:absolute;left:20px;height:1.8em;width:0.9em;} .ibpoCO path{fill:#333333;}</style> +.bemheR{margin-bottom:30px;} +/* sc-component-id: sc-cLQEGU */ +.bPGAgL{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;width:20px;height:20px;-webkit-align-self:center;-ms-flex-item-align:center;align-self:center;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;color:#32329f;} +/* sc-component-id: sc-gqPbQI */ +.cTutD{width:260px;background-color:#fafafa;overflow:hidden;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-backface-visibility:hidden;backface-visibility:hidden;height:100vh;position:-webkit-sticky;position:sticky;position:-webkit-sticky;top:0;} @media screen and (max-width:50rem){.cTutD{position:fixed;z-index:20;width:100%;background:#fafafa;display:none;}} @media print{.cTutD{display:none;}} +/* sc-component-id: sc-hORach */ +.iXutXb{outline:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:#f2f2f2;color:#32329f;display:none;cursor:pointer;position:fixed;right:20px;z-index:100;border-radius:50%;box-shadow:0 0 20px rgba(0,0,0,0.3);bottom:44px;width:60px;height:60px;padding:0 20px;} @media screen and (max-width:50rem){.iXutXb{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;}} @media print{.iXutXb{display:none;}} +/* sc-component-id: sc-bMVAic */ +.bIbMYr{font-family:Roboto,sans-serif;font-size:14px;font-weight:400;line-height:1.5em;color:#333333;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;position:relative;text-align:left;-webkit-font-smoothing:antialiased;font-smoothing:antialiased;text-rendering:optimizeSpeed !important;tap-highlight-color:rgba(0,0,0,0);-webkit-text-size-adjust:100%;text-size-adjust:100%;} .bIbMYr *{box-sizing:border-box;-webkit-tap-highlight-color:rgba(255,255,255,0);} +/* sc-component-id: sc-bAeIUo */ +.jHaAsr{z-index:1;position:relative;overflow:hidden;width:calc(100% - 260px);contain:layout;} @media print,screen and (max-width:50rem){.jHaAsr{width:100%;}} +/* sc-component-id: sc-iujRgT */ +.kTYKTV{background:#263238;position:absolute;top:0;bottom:0;right:0;width:calc((100% - 260px) * 0.4);} @media print,screen and (max-width:85rem){.kTYKTV{display:none;}} +/* sc-component-id: sc-GMQeP */ +.eWtOBi{padding:5px 0;} +/* sc-component-id: sc-exAgwC */ +.bJNzQd{width:calc(100% - 40px);box-sizing:border-box;margin:0 20px;padding:5px 10px 5px 20px;border:0;border-bottom:1px solid #e1e1e1;font-family:Roboto,sans-serif;font-weight:bold;font-size:13px;color:#333333;background-color:transparent;outline:none;} +/* sc-component-id: sc-cQFLBn */ +.jOVKNn{position:absolute;left:20px;height:1.8em;width:0.9em;} .jOVKNn path{fill:#333333;}</style> <link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet"> </head> <body> - <div id="redoc"><div class="sc-hZSUBg bxcHYI redoc-wrap"><div class="sc-iQNlJl jzMYjV menu-content" style="top:0px;height:calc(100vh - 0px)"><div class="sc-feJyhm YzuTm"><a href="https://www.centreon.com" class="sc-iELTvK cCiYxb"><img src="./centreon-logo.png" alt="logo" class="sc-kafWEX hZCbNs"/></a></div><div role="search" class="sc-esOvli kKQhLA"><svg class="sc-hMFtBS ibpoCO search-icon" version="1.1" viewBox="0 0 1000 1000" x="0px" xmlns="http://www.w3.org/2000/svg" y="0px"><path d="M968.2,849.4L667.3,549c83.9-136.5,66.7-317.4-51.7-435.6C477.1-25,252.5-25,113.9,113.4c-138.5,138.3-138.5,362.6,0,501C219.2,730.1,413.2,743,547.6,666.5l301.9,301.4c43.6,43.6,76.9,14.9,104.2-12.4C981,928.3,1011.8,893,968.2,849.4z M524.5,522c-88.9,88.7-233,88.7-321.8,0c-88.9-88.7-88.9-232.6,0-321.3c88.9-88.7,233-88.7,321.8,0C613.4,289.4,613.4,433.3,524.5,522z"></path></svg><input type="text" value="" placeholder="Search..." class="sc-cmthru kzNiFq search-input"/></div><div class="sc-gPEVay hclups scrollbar-container undefined"><ul class="sc-bwCtUz ghctpd" role="navigation"><li data-item-id="section/Information" class="sc-hrWEMg bbViyS"><label type="section" role="menuitem" class="sc-eTuwsz iNzLCk -depth1"><span title="Information" class="sc-gwVKww fyUykq">Information</span></label></li><li data-item-id="section/Authentication" class="sc-hrWEMg bbViyS"><label type="section" role="menuitem" class="sc-eTuwsz iNzLCk -depth1"><span title="Authentication" class="sc-gwVKww fyUykq">Authentication</span></label></li><li data-item-id="tag/Internal" class="sc-hrWEMg bbViyS"><label type="tag" role="menuitem" class="sc-eTuwsz iNzLCk -depth1"><span title="Internal" class="sc-gwVKww fyUykq">Internal</span><svg class="sc-jTzLTM hjRNaf" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></label><ul class="sc-bwCtUz cLEtWf"><li data-item-id="tag/Internal/paths/~1internal~1constatus/get" class="sc-hrWEMg bbViyS"><label role="menuitem" class="sc-eTuwsz gpbcFk -depth2"><span type="get" class="sc-iQKALj cFwMcp operation-type get">get</span><span width="calc(100% - 38px)" class="sc-gwVKww SmuWE">Get nodes connection status</span></label></li><li data-item-id="tag/Internal/paths/~1internal~1information/get" class="sc-hrWEMg bbViyS"><label role="menuitem" class="sc-eTuwsz gpbcFk -depth2"><span type="get" class="sc-iQKALj cFwMcp operation-type get">get</span><span width="calc(100% - 38px)" class="sc-gwVKww SmuWE">Get runtime informations and statistics</span></label></li><li data-item-id="tag/Internal/paths/~1internal~1thumbprint/get" class="sc-hrWEMg bbViyS"><label role="menuitem" class="sc-eTuwsz gpbcFk -depth2"><span type="get" class="sc-iQKALj cFwMcp operation-type get">get</span><span width="calc(100% - 38px)" class="sc-gwVKww SmuWE">Get public key thumbprint</span></label></li><li data-item-id="tag/Internal/paths/~1internal~1logger/post" class="sc-hrWEMg bbViyS"><label role="menuitem" class="sc-eTuwsz gpbcFk -depth2"><span type="post" class="sc-iQKALj cFwMcp operation-type post">post</span><span width="calc(100% - 38px)" class="sc-gwVKww SmuWE">Set logger severity level</span></label></li></ul></li><li data-item-id="tag/Logs" class="sc-hrWEMg bbViyS"><label type="tag" role="menuitem" class="sc-eTuwsz iNzLCk -depth1"><span title="Logs" class="sc-gwVKww fyUykq">Logs</span><svg class="sc-jTzLTM hjRNaf" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></label><ul class="sc-bwCtUz cLEtWf"><li data-item-id="tag/Logs/paths/~1log~1{token}/get" class="sc-hrWEMg bbViyS"><label role="menuitem" class="sc-eTuwsz gpbcFk -depth2"><span type="get" class="sc-iQKALj cFwMcp operation-type get">get</span><span width="calc(100% - 38px)" class="sc-gwVKww SmuWE">Retrieve event's logs</span></label></li></ul></li><li data-item-id="tag/Cron" class="sc-hrWEMg bbViyS"><label type="tag" role="menuitem" class="sc-eTuwsz iNzLCk -depth1"><span title="Cron" class="sc-gwVKww fyUykq">Cron</span><svg class="sc-jTzLTM hjRNaf" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></label><ul class="sc-bwCtUz cLEtWf"><li data-item-id="tag/Cron/paths/~1core~1cron~1definitions/get" class="sc-hrWEMg bbViyS"><label role="menuitem" class="sc-eTuwsz gpbcFk -depth2"><span type="get" class="sc-iQKALj cFwMcp operation-type get">get</span><span width="calc(100% - 38px)" class="sc-gwVKww SmuWE">List definitions</span></label></li><li data-item-id="tag/Cron/paths/~1core~1cron~1definitions/post" class="sc-hrWEMg bbViyS"><label role="menuitem" class="sc-eTuwsz gpbcFk -depth2"><span type="post" class="sc-iQKALj cFwMcp operation-type post">post</span><span width="calc(100% - 38px)" class="sc-gwVKww SmuWE">Add definitions</span></label></li><li data-item-id="tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}/get" class="sc-hrWEMg bbViyS"><label role="menuitem" class="sc-eTuwsz gpbcFk -depth2"><span type="get" class="sc-iQKALj cFwMcp operation-type get">get</span><span width="calc(100% - 38px)" class="sc-gwVKww SmuWE">Get a definition</span></label></li><li data-item-id="tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}/patch" class="sc-hrWEMg bbViyS"><label role="menuitem" class="sc-eTuwsz gpbcFk -depth2"><span type="patch" class="sc-iQKALj cFwMcp operation-type patch">patch</span><span width="calc(100% - 38px)" class="sc-gwVKww SmuWE">Update a definition</span></label></li><li data-item-id="tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}/delete" class="sc-hrWEMg bbViyS"><label role="menuitem" class="sc-eTuwsz gpbcFk -depth2"><span type="delete" class="sc-iQKALj cFwMcp operation-type delete">del</span><span width="calc(100% - 38px)" class="sc-gwVKww SmuWE">Delete a definition</span></label></li><li data-item-id="tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}~1status/get" class="sc-hrWEMg bbViyS"><label role="menuitem" class="sc-eTuwsz gpbcFk -depth2"><span type="get" class="sc-iQKALj cFwMcp operation-type get">get</span><span width="calc(100% - 38px)" class="sc-gwVKww SmuWE">Get a definition status</span></label></li></ul></li><li data-item-id="tag/Action" class="sc-hrWEMg bbViyS"><label type="tag" role="menuitem" class="sc-eTuwsz iNzLCk -depth1"><span title="Action" class="sc-gwVKww fyUykq">Action</span><svg class="sc-jTzLTM hjRNaf" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></label><ul class="sc-bwCtUz cLEtWf"><li data-item-id="tag/Action/paths/~1core~1action~1command/post" class="sc-hrWEMg bbViyS"><label role="menuitem" class="sc-eTuwsz gpbcFk -depth2"><span type="post" class="sc-iQKALj cFwMcp operation-type post">post</span><span width="calc(100% - 38px)" class="sc-gwVKww SmuWE">Execute one or several command lines</span></label></li></ul></li><li data-item-id="tag/Engine" class="sc-hrWEMg bbViyS"><label type="tag" role="menuitem" class="sc-eTuwsz iNzLCk -depth1"><span title="Engine" class="sc-gwVKww fyUykq">Engine</span><svg class="sc-jTzLTM hjRNaf" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></label><ul class="sc-bwCtUz cLEtWf"><li data-item-id="tag/Engine/paths/~1centreon~1engine~1command/post" class="sc-hrWEMg bbViyS"><label role="menuitem" class="sc-eTuwsz gpbcFk -depth2"><span type="post" class="sc-iQKALj cFwMcp operation-type post">post</span><span width="calc(100% - 38px)" class="sc-gwVKww SmuWE">Send one or several external commands</span></label></li></ul></li><li data-item-id="tag/Statistics" class="sc-hrWEMg bbViyS"><label type="tag" role="menuitem" class="sc-eTuwsz iNzLCk -depth1"><span title="Statistics" class="sc-gwVKww fyUykq">Statistics</span><svg class="sc-jTzLTM hjRNaf" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></label><ul class="sc-bwCtUz cLEtWf"><li data-item-id="tag/Statistics/paths/~1centreon~1statistics~1broker/get" class="sc-hrWEMg bbViyS"><label role="menuitem" class="sc-eTuwsz gpbcFk -depth2"><span type="get" class="sc-iQKALj cFwMcp operation-type get">get</span><span width="calc(100% - 38px)" class="sc-gwVKww SmuWE">Launch Broker statistics collection</span></label></li><li data-item-id="tag/Statistics/paths/~1centreon~1statistics~1broker~1{monitoring_server_id}/get" class="sc-hrWEMg bbViyS"><label role="menuitem" class="sc-eTuwsz gpbcFk -depth2"><span type="get" class="sc-iQKALj cFwMcp operation-type get">get</span><span width="calc(100% - 38px)" class="sc-gwVKww SmuWE">Launch Broker statistics collection of a specific monitoring server</span></label></li></ul></li><li data-item-id="tag/Autodiscovery" class="sc-hrWEMg bbViyS"><label type="tag" role="menuitem" class="sc-eTuwsz iNzLCk -depth1"><span title="Autodiscovery" class="sc-gwVKww fyUykq">Autodiscovery</span><svg class="sc-jTzLTM hjRNaf" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></label><ul class="sc-bwCtUz cLEtWf"><li data-item-id="tag/Autodiscovery/paths/~1centreon~1autodiscovery~1job/post" class="sc-hrWEMg bbViyS"><label role="menuitem" class="sc-eTuwsz gpbcFk -depth2"><span type="post" class="sc-iQKALj cFwMcp operation-type post">post</span><span width="calc(100% - 38px)" class="sc-gwVKww SmuWE">Add a discovery job</span></label></li></ul></li></ul><div class="sc-hXRMBi nGwee"><a target="_blank" rel="noopener noreferrer" href="https://github.com/Redocly/redoc">Documentation Powered by ReDoc</a></div></div></div><div class="sc-bsbRJL fXybtJ"><div class="sc-epnACN fWqlcz"><svg class="" style="transform:translate(2px, -4px) rotate(180deg);transition:transform 0.2s ease" viewBox="0 0 926.23699 573.74994" version="1.1" x="0px" y="0px" width="15" height="15"><g transform="translate(904.92214,-879.1482)"><path d=" + <div id="redoc"><div class="sc-bMVAic bIbMYr redoc-wrap"><div class="sc-gqPbQI cTutD menu-content" style="top:0px;height:calc(100vh - 0px)"><div class="sc-feJyhm YzuTm"><a href="https://www.centreon.com" class="sc-iELTvK cCiYxb"><img src="./centreon-logo.png" alt="logo" class="sc-kafWEX hZCbNs"/></a></div><div role="search" class="sc-GMQeP eWtOBi"><svg class="sc-cQFLBn jOVKNn search-icon" version="1.1" viewBox="0 0 1000 1000" x="0px" xmlns="http://www.w3.org/2000/svg" y="0px"><path d="M968.2,849.4L667.3,549c83.9-136.5,66.7-317.4-51.7-435.6C477.1-25,252.5-25,113.9,113.4c-138.5,138.3-138.5,362.6,0,501C219.2,730.1,413.2,743,547.6,666.5l301.9,301.4c43.6,43.6,76.9,14.9,104.2-12.4C981,928.3,1011.8,893,968.2,849.4z M524.5,522c-88.9,88.7-233,88.7-321.8,0c-88.9-88.7-88.9-232.6,0-321.3c88.9-88.7,233-88.7,321.8,0C613.4,289.4,613.4,433.3,524.5,522z"></path></svg><input type="text" value="" placeholder="Search..." class="sc-exAgwC bJNzQd search-input"/></div><div class="sc-gPEVay hclups scrollbar-container undefined"><ul class="sc-jwKygS kpIQpF" role="navigation"><li data-item-id="section/Information" class="sc-btzYZH hndQyM"><label type="section" role="menuitem" class="sc-lhVmIH cjqQLX -depth1"><span title="Information" class="sc-bYSBpT cLxwxL">Information</span></label></li><li data-item-id="section/Authentication" class="sc-btzYZH hndQyM"><label type="section" role="menuitem" class="sc-lhVmIH cjqQLX -depth1"><span title="Authentication" class="sc-bYSBpT cLxwxL">Authentication</span></label></li><li data-item-id="tag/Internal" class="sc-btzYZH hndQyM"><label type="tag" role="menuitem" class="sc-lhVmIH cjqQLX -depth1"><span title="Internal" class="sc-bYSBpT cLxwxL">Internal</span><svg class="sc-jTzLTM hjRNaf" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></label><ul class="sc-jwKygS kBBDeQ"><li data-item-id="tag/Internal/paths/~1internal~1constatus/get" class="sc-btzYZH hndQyM"><label role="menuitem" class="sc-lhVmIH bLMrnV -depth2"><span type="get" class="sc-cmTdod kxjqzZ operation-type get">get</span><span width="calc(100% - 38px)" class="sc-bYSBpT gpHEtH">Get nodes connection status</span></label></li><li data-item-id="tag/Internal/paths/~1internal~1information/get" class="sc-btzYZH hndQyM"><label role="menuitem" class="sc-lhVmIH bLMrnV -depth2"><span type="get" class="sc-cmTdod kxjqzZ operation-type get">get</span><span width="calc(100% - 38px)" class="sc-bYSBpT gpHEtH">Get runtime informations and statistics</span></label></li><li data-item-id="tag/Internal/paths/~1internal~1thumbprint/get" class="sc-btzYZH hndQyM"><label role="menuitem" class="sc-lhVmIH bLMrnV -depth2"><span type="get" class="sc-cmTdod kxjqzZ operation-type get">get</span><span width="calc(100% - 38px)" class="sc-bYSBpT gpHEtH">Get public key thumbprint</span></label></li><li data-item-id="tag/Internal/paths/~1internal~1logger/post" class="sc-btzYZH hndQyM"><label role="menuitem" class="sc-lhVmIH bLMrnV -depth2"><span type="post" class="sc-cmTdod kxjqzZ operation-type post">post</span><span width="calc(100% - 38px)" class="sc-bYSBpT gpHEtH">Set logger severity level</span></label></li></ul></li><li data-item-id="tag/Logs" class="sc-btzYZH hndQyM"><label type="tag" role="menuitem" class="sc-lhVmIH cjqQLX -depth1"><span title="Logs" class="sc-bYSBpT cLxwxL">Logs</span><svg class="sc-jTzLTM hjRNaf" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></label><ul class="sc-jwKygS kBBDeQ"><li data-item-id="tag/Logs/paths/~1log~1{token}/get" class="sc-btzYZH hndQyM"><label role="menuitem" class="sc-lhVmIH bLMrnV -depth2"><span type="get" class="sc-cmTdod kxjqzZ operation-type get">get</span><span width="calc(100% - 38px)" class="sc-bYSBpT gpHEtH">Retrieve event's logs</span></label></li></ul></li><li data-item-id="tag/Cron" class="sc-btzYZH hndQyM"><label type="tag" role="menuitem" class="sc-lhVmIH cjqQLX -depth1"><span title="Cron" class="sc-bYSBpT cLxwxL">Cron</span><svg class="sc-jTzLTM hjRNaf" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></label><ul class="sc-jwKygS kBBDeQ"><li data-item-id="tag/Cron/paths/~1core~1cron~1definitions/get" class="sc-btzYZH hndQyM"><label role="menuitem" class="sc-lhVmIH bLMrnV -depth2"><span type="get" class="sc-cmTdod kxjqzZ operation-type get">get</span><span width="calc(100% - 38px)" class="sc-bYSBpT gpHEtH">List definitions</span></label></li><li data-item-id="tag/Cron/paths/~1core~1cron~1definitions/post" class="sc-btzYZH hndQyM"><label role="menuitem" class="sc-lhVmIH bLMrnV -depth2"><span type="post" class="sc-cmTdod kxjqzZ operation-type post">post</span><span width="calc(100% - 38px)" class="sc-bYSBpT gpHEtH">Add definitions</span></label></li><li data-item-id="tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}/get" class="sc-btzYZH hndQyM"><label role="menuitem" class="sc-lhVmIH bLMrnV -depth2"><span type="get" class="sc-cmTdod kxjqzZ operation-type get">get</span><span width="calc(100% - 38px)" class="sc-bYSBpT gpHEtH">Get a definition</span></label></li><li data-item-id="tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}/patch" class="sc-btzYZH hndQyM"><label role="menuitem" class="sc-lhVmIH bLMrnV -depth2"><span type="patch" class="sc-cmTdod kxjqzZ operation-type patch">patch</span><span width="calc(100% - 38px)" class="sc-bYSBpT gpHEtH">Update a definition</span></label></li><li data-item-id="tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}/delete" class="sc-btzYZH hndQyM"><label role="menuitem" class="sc-lhVmIH bLMrnV -depth2"><span type="delete" class="sc-cmTdod kxjqzZ operation-type delete">del</span><span width="calc(100% - 38px)" class="sc-bYSBpT gpHEtH">Delete a definition</span></label></li><li data-item-id="tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}~1status/get" class="sc-btzYZH hndQyM"><label role="menuitem" class="sc-lhVmIH bLMrnV -depth2"><span type="get" class="sc-cmTdod kxjqzZ operation-type get">get</span><span width="calc(100% - 38px)" class="sc-bYSBpT gpHEtH">Get a definition status</span></label></li></ul></li><li data-item-id="tag/Action" class="sc-btzYZH hndQyM"><label type="tag" role="menuitem" class="sc-lhVmIH cjqQLX -depth1"><span title="Action" class="sc-bYSBpT cLxwxL">Action</span><svg class="sc-jTzLTM hjRNaf" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></label><ul class="sc-jwKygS kBBDeQ"><li data-item-id="tag/Action/paths/~1core~1action~1command/post" class="sc-btzYZH hndQyM"><label role="menuitem" class="sc-lhVmIH bLMrnV -depth2"><span type="post" class="sc-cmTdod kxjqzZ operation-type post">post</span><span width="calc(100% - 38px)" class="sc-bYSBpT gpHEtH">Execute one or several command lines</span></label></li></ul></li><li data-item-id="tag/Engine" class="sc-btzYZH hndQyM"><label type="tag" role="menuitem" class="sc-lhVmIH cjqQLX -depth1"><span title="Engine" class="sc-bYSBpT cLxwxL">Engine</span><svg class="sc-jTzLTM hjRNaf" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></label><ul class="sc-jwKygS kBBDeQ"><li data-item-id="tag/Engine/paths/~1centreon~1engine~1command/post" class="sc-btzYZH hndQyM"><label role="menuitem" class="sc-lhVmIH bLMrnV -depth2"><span type="post" class="sc-cmTdod kxjqzZ operation-type post">post</span><span width="calc(100% - 38px)" class="sc-bYSBpT gpHEtH">Send one or several external commands</span></label></li></ul></li><li data-item-id="tag/Statistics" class="sc-btzYZH hndQyM"><label type="tag" role="menuitem" class="sc-lhVmIH cjqQLX -depth1"><span title="Statistics" class="sc-bYSBpT cLxwxL">Statistics</span><svg class="sc-jTzLTM hjRNaf" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></label><ul class="sc-jwKygS kBBDeQ"><li data-item-id="tag/Statistics/paths/~1centreon~1statistics~1broker/get" class="sc-btzYZH hndQyM"><label role="menuitem" class="sc-lhVmIH bLMrnV -depth2"><span type="get" class="sc-cmTdod kxjqzZ operation-type get">get</span><span width="calc(100% - 38px)" class="sc-bYSBpT gpHEtH">Launch Broker statistics collection</span></label></li><li data-item-id="tag/Statistics/paths/~1centreon~1statistics~1broker~1{monitoring_server_id}/get" class="sc-btzYZH hndQyM"><label role="menuitem" class="sc-lhVmIH bLMrnV -depth2"><span type="get" class="sc-cmTdod kxjqzZ operation-type get">get</span><span width="calc(100% - 38px)" class="sc-bYSBpT gpHEtH">Launch Broker statistics collection of a specific monitoring server</span></label></li></ul></li><li data-item-id="tag/Autodiscovery" class="sc-btzYZH hndQyM"><label type="tag" role="menuitem" class="sc-lhVmIH cjqQLX -depth1"><span title="Autodiscovery" class="sc-bYSBpT cLxwxL">Autodiscovery</span><svg class="sc-jTzLTM hjRNaf" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></label><ul class="sc-jwKygS kBBDeQ"><li data-item-id="tag/Autodiscovery/paths/~1centreon~1autodiscovery~1hosts/post" class="sc-btzYZH hndQyM"><label role="menuitem" class="sc-lhVmIH bLMrnV -depth2"><span type="post" class="sc-cmTdod kxjqzZ operation-type post">post</span><span width="calc(100% - 38px)" class="sc-bYSBpT gpHEtH">Add a host discovery job</span></label></li><li data-item-id="tag/Autodiscovery/paths/~1centreon~1autodiscovery~1hosts~1{job_id}~1schedule/get" class="sc-btzYZH hndQyM"><label role="menuitem" class="sc-lhVmIH bLMrnV -depth2"><span type="get" class="sc-cmTdod kxjqzZ operation-type get">get</span><span width="calc(100% - 38px)" class="sc-bYSBpT gpHEtH">Launch a host discovery job</span></label></li><li data-item-id="tag/Autodiscovery/paths/~1centreon~1autodiscovery~1hosts~1{token}/delete" class="sc-btzYZH hndQyM"><label role="menuitem" class="sc-lhVmIH bLMrnV -depth2"><span type="delete" class="sc-cmTdod kxjqzZ operation-type delete">del</span><span width="calc(100% - 38px)" class="sc-bYSBpT gpHEtH">Delete a host discovery job</span></label></li></ul></li></ul><div class="sc-elJkPf cxRqCB"><a target="_blank" rel="noopener noreferrer" href="https://github.com/Redocly/redoc">Documentation Powered by ReDoc</a></div></div></div><div class="sc-hORach iXutXb"><div class="sc-cLQEGU bPGAgL"><svg class="" style="transform:translate(2px, -4px) rotate(180deg);transition:transform 0.2s ease" viewBox="0 0 926.23699 573.74994" version="1.1" x="0px" y="0px" width="15" height="15"><g transform="translate(904.92214,-879.1482)"><path d=" m -673.67664,1221.6502 -231.2455,-231.24803 55.6165, -55.627 c 30.5891,-30.59485 56.1806,-55.627 56.8701,-55.627 0.6894, 0 79.8637,78.60862 175.9427,174.68583 l 174.6892,174.6858 174.6892, @@ -330,7 +332,7 @@ 55.627 l 55.6165,55.627 -231.245496,231.24803 c -127.185,127.1864 -231.5279,231.248 -231.873,231.248 -0.3451,0 -104.688, -104.0616 -231.873,-231.248 z - " fill="currentColor"></path></g></svg></div></div><div class="sc-cMhqgX iniCdN api-content"><div class="sc-ifAKCX dluJDj"><div class="sc-gzVnrw eesUPo"><div class="sc-bxivhb cjtbAK api-info"><h1 class="sc-htoDjs sc-fYxtnH dTJWQH">Centreon Gorgone RestAPI<!-- --> <span>(<!-- -->1.0<!-- -->)</span></h1><div class="sc-jWBwVP sc-iRbamj flfxUM"><div class="sc-ktHwxA gtbPCV"><div class="sc-cIShpX eKrlKP"> <span class="sc-hEsumM gEjDMA">URL: <a href="https://www.centreon.com">https://www.centreon.com</a></span> <span class="sc-hEsumM gEjDMA">License: <a href="http://www.apache.org/licenses/LICENSE-2.0.html">Apache 2.0</a></span> </div></div></div><div class="sc-jWBwVP sc-iRbamj flfxUM" data-role="redoc-description"></div><div class="sc-uJMKN dzbqSt"><a href="https://centreon.slack.com/messages/CCRGLQSE5">You can contact us on our community Slack</a></div></div></div></div><div id="section/Information" data-section-id="section/Information" class="sc-ifAKCX dluJDj"><div class="sc-gzVnrw eesUPo"><div class="sc-bxivhb cjtbAK"><h1 class="sc-htoDjs WxWXp"><a class="sc-VigVT kGvRyb" href="#section/Information"></a>Information</h1></div></div><div class="sc-bxivhb kFNigF"><div class="sc-jWBwVP sc-iRbamj flfxUM redoc-markdown "><p>Centreon Gorgone and his "gorgoned" daemon is a lightweight, distributed, modular tasks handler.</p> + " fill="currentColor"></path></g></svg></div></div><div class="sc-bAeIUo jHaAsr api-content"><div class="sc-ifAKCX dluJDj"><div class="sc-gzVnrw eesUPo"><div class="sc-bxivhb cjtbAK api-info"><h1 class="sc-htoDjs sc-fYxtnH dTJWQH">Centreon Gorgone RestAPI<!-- --> <span>(<!-- -->1.0<!-- -->)</span></h1><div class="sc-jWBwVP sc-iRbamj eHtzbE"><div class="sc-ktHwxA gtbPCV"><div class="sc-cIShpX eKrlKP"> <span class="sc-hEsumM gEjDMA">URL: <a href="https://www.centreon.com">https://www.centreon.com</a></span> <span class="sc-hEsumM gEjDMA">License: <a href="http://www.apache.org/licenses/LICENSE-2.0.html">Apache 2.0</a></span> </div></div></div><div class="sc-jWBwVP sc-iRbamj eHtzbE" data-role="redoc-description"></div><div class="sc-uJMKN dzbqSt"><a href="https://centreon.slack.com/messages/CCRGLQSE5">You can contact us on our community Slack</a></div></div></div></div><div id="section/Information" data-section-id="section/Information" class="sc-ifAKCX dluJDj"><div class="sc-gzVnrw eesUPo"><div class="sc-bxivhb cjtbAK"><h1 class="sc-htoDjs WxWXp"><a class="sc-VigVT kGvRyb" href="#section/Information"></a>Information</h1></div></div><div class="sc-bxivhb kFNigF"><div class="sc-jWBwVP sc-iRbamj eHtzbE redoc-markdown "><p>Centreon Gorgone and his "gorgoned" daemon is a lightweight, distributed, modular tasks handler.</p> <p>It provides a set of actions like:</p> <ul> <li>Execute commands</li> @@ -340,143 +342,158 @@ </ul> <p>The daemon can be installed on Centreon environments like Centreon Central, Remote and Poller servers.</p> <p>It uses ZeroMQ library.</p> -</div></div></div><div id="section/Authentication" data-section-id="section/Authentication" class="sc-ifAKCX dluJDj"><div class="sc-gzVnrw eesUPo"><div class="sc-bxivhb cjtbAK"><h1 class="sc-htoDjs WxWXp"><a class="sc-VigVT kGvRyb" href="#section/Authentication"></a>Authentication</h1></div></div><div id="section/Authentication/Basic Authentication" data-section-id="section/Authentication/Basic Authentication" class="sc-ifAKCX dluJDj"><div class="sc-gzVnrw eesUPo"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#section/Authentication/Basic Authentication"></a>Basic Authentication</h2><div class="sc-jWBwVP sc-iRbamj flfxUM"></div><div class="sc-jWBwVP sc-iRbamj flfxUM"><table class="security-details"><tbody><tr><th> Security Scheme Type </th><td> <!-- -->HTTP<!-- --> </td></tr><tr><th> HTTP Authorization Scheme </th><td> <!-- -->basic<!-- --> </td></tr></tbody></table></div></div></div></div></div><div id="tag/Internal" data-section-id="tag/Internal" class="sc-ifAKCX dluJDj"><div class="sc-gzVnrw eesUPo"><div class="sc-bxivhb cjtbAK"><h1 class="sc-htoDjs WxWXp"><a class="sc-VigVT kGvRyb" href="#tag/Internal"></a>Internal</h1></div></div><div class="sc-bxivhb kFNigF"><div class="sc-jWBwVP sc-iRbamj flfxUM redoc-markdown "><p>Internal events.</p> -</div></div></div><div id="tag/Internal/paths/~1internal~1constatus/get" data-section-id="tag/Internal/paths/~1internal~1constatus/get" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-ibxdXY bSFXlp"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Internal/paths/~1internal~1constatus/get"></a>Get nodes connection status<!-- --> </h2><div class="sc-RefOD boajtD"><div class="sc-jWBwVP sc-iRbamj flfxUM"><p>Get the connection status of all nodes managed by the Gorgone daemon.</p> -</div></div><div class="sc-jtRfpW geWpKA"><div class="sc-lhVmIH dVvUxe"><h5 class="sc-gqjmRU sc-elJkPf jGRUDj">Authorizations: </h5></div><div class="sc-bYSBpT fKHsnH"><span class="sc-btzYZH hQBRTt"><span class="sc-jwKygS bnFPhO"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><div><h3 class="sc-eXEjpC espozG"> Responses </h3><div><div class="sc-kPVwWT fDvFMp"><svg class="sc-jTzLTM NifDa" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>200<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>OK</p> -</span></div></div><div><div class="sc-kPVwWT byLrBg"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>Unauthorized</p> -</span></div></div><div><div class="sc-kPVwWT byLrBg"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>Forbidden</p> -</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-kTUwUJ ldTMcP"><div class="sc-gxMtzJ dHLKeu"><span type="get" class="sc-dfVpRl jBjYbV http-verb get"> <!-- -->get</span> <span class="sc-dqBHgY iENVAs">/internal/constatus</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-gzOgki lkvpfX"><div class="sc-iyvyFf eftLSo"><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Local Gorgone instance</p> -</div><div><div class="sc-hwwEjo bwgXFh"><span>{protocol}://{server}:{port}/api</span>/internal/constatus</div></div></div><div class="sc-iyvyFf eftLSo"><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Remote Gorgone instance</p> -</div><div><div class="sc-hwwEjo bwgXFh"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/internal/constatus</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL irpqyy" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="react-tabs-0" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-1" tabindex="0">200</li><li class="tab-error" role="tab" id="react-tabs-2" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-3">401</li><li class="tab-error" role="tab" id="react-tabs-4" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-5">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-1" aria-labelledby="react-tabs-0"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA gzAoUb">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"action"</span>: <span class="token string">"constatus"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"message"</span>: <span class="token string">"ok"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"data"</span>: <div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"id"</span>: <div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"last_ping_sent"</span>: <span class="token number">1577726040</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"type"</span>: <span class="token string">"push_zmq"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"nodes"</span>: <span class="token punctuation">{ }</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"last_ping_recv"</span>: <span class="token number">1577726040</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-3" aria-labelledby="react-tabs-2"></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-5" aria-labelledby="react-tabs-4"></div></div></div></div></div></div><div id="tag/Internal/paths/~1internal~1information/get" data-section-id="tag/Internal/paths/~1internal~1information/get" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-ibxdXY bSFXlp"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Internal/paths/~1internal~1information/get"></a>Get runtime informations and statistics<!-- --> </h2><div class="sc-RefOD boajtD"><div class="sc-jWBwVP sc-iRbamj flfxUM"><p>Get informations and statistics about loaded modules, available endpoints and number of events computed at runtime.</p> -</div></div><div class="sc-jtRfpW geWpKA"><div class="sc-lhVmIH dVvUxe"><h5 class="sc-gqjmRU sc-elJkPf jGRUDj">Authorizations: </h5></div><div class="sc-bYSBpT fKHsnH"><span class="sc-btzYZH hQBRTt"><span class="sc-jwKygS bnFPhO"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><div><h3 class="sc-eXEjpC espozG"> Responses </h3><div><div class="sc-kPVwWT fDvFMp"><svg class="sc-jTzLTM NifDa" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>200<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>OK</p> -</span></div></div><div><div class="sc-kPVwWT byLrBg"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>Unauthorized</p> -</span></div></div><div><div class="sc-kPVwWT byLrBg"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>Forbidden</p> -</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-kTUwUJ ldTMcP"><div class="sc-gxMtzJ dHLKeu"><span type="get" class="sc-dfVpRl jBjYbV http-verb get"> <!-- -->get</span> <span class="sc-dqBHgY iENVAs">/internal/information</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-gzOgki lkvpfX"><div class="sc-iyvyFf eftLSo"><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Local Gorgone instance</p> -</div><div><div class="sc-hwwEjo bwgXFh"><span>{protocol}://{server}:{port}/api</span>/internal/information</div></div></div><div class="sc-iyvyFf eftLSo"><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Remote Gorgone instance</p> -</div><div><div class="sc-hwwEjo bwgXFh"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/internal/information</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL irpqyy" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="react-tabs-6" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-7" tabindex="0">200</li><li class="tab-error" role="tab" id="react-tabs-8" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-9">401</li><li class="tab-error" role="tab" id="react-tabs-10" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-11">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-7" aria-labelledby="react-tabs-6"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA gzAoUb">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"action"</span>: <span class="token string">"information"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"message"</span>: <span class="token string">"ok"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"data"</span>: <div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"modules"</span>: <div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"httpserver"</span>: <span class="token string">"gorgone::modules::core::httpserver::hooks"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"dbcleaner"</span>: <span class="token string">"gorgone::modules::core::dbcleaner::hooks"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"cron"</span>: <span class="token string">"gorgone::modules::core::cron::hooks"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"engine"</span>: <span class="token string">"gorgone::modules::centreon::engine::hooks"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"action"</span>: <span class="token string">"gorgone::modules::core::action::hooks"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"statistics"</span>: <span class="token string">"gorgone::modules::centreon::statistics::hooks"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"nodes"</span>: <span class="token string">"gorgone::modules::centreon::nodes::hooks"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"legacycmd"</span>: <span class="token string">"gorgone::modules::centreon::legacycmd::hooks"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"proxy"</span>: <span class="token string">"gorgone::modules::core::proxy::hooks"</span></div></li></ul><span class="token punctuation">}</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"api_endpoints"</span>: <div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"POST_/internal/logger"</span>: <span class="token string">"BCASTLOGGER"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"GET_/centreon/statistics/broker"</span>: <span class="token string">"BROKERSTATS"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"GET_/internal/thumbprint"</span>: <span class="token string">"GETTHUMBPRINT"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"GET_/core/cron/definitions"</span>: <span class="token string">"GETCRON"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"GET_/internal/information"</span>: <span class="token string">"INFORMATION"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"POST_/core/cron/definitions"</span>: <span class="token string">"ADDCRON"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"POST_/core/action/command"</span>: <span class="token string">"COMMAND"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"POST_/core/proxy/remotecopy"</span>: <span class="token string">"REMOTECOPY"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"POST_/centreon/engine/command"</span>: <span class="token string">"ENGINECOMMAND"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"PATCH_/core/cron/definitions"</span>: <span class="token string">"UPDATECRON"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"DELETE_/core/cron/definitions"</span>: <span class="token string">"DELETECRON"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"GET_/internal/constatus"</span>: <span class="token string">"CONSTATUS"</span></div></li></ul><span class="token punctuation">}</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"counters"</span>: <div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"total"</span>: <span class="token number">40210</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"external"</span>: <div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"total"</span>: <span class="token number">0</span></div></li></ul><span class="token punctuation">}</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"internal"</span>: <div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"legacycmdready"</span>: <span class="token number">1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"setlogs"</span>: <span class="token number">7841</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"enginecommand"</span>: <span class="token number">20</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"registernodes"</span>: <span class="token number">443</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"pong"</span>: <span class="token number">3397</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"proxyready"</span>: <span class="token number">5</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"statisticsready"</span>: <span class="token number">1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"addcron"</span>: <span class="token number">1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"cronready"</span>: <span class="token number">1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"getthumbprint"</span>: <span class="token number">2</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"centreonnodesready"</span>: <span class="token number">1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"httpserverready"</span>: <span class="token number">1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"command"</span>: <span class="token number">4446</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"putlog"</span>: <span class="token number">9809</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"dbcleanerready"</span>: <span class="token number">1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"information"</span>: <span class="token number">6</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"brokerstats"</span>: <span class="token number">4446</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"constatus"</span>: <span class="token number">1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"total"</span>: <span class="token number">40210</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"setcoreid"</span>: <span class="token number">443</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"getlog"</span>: <span class="token number">8893</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"engineready"</span>: <span class="token number">1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"unregisternodes"</span>: <span class="token number">443</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"actionready"</span>: <span class="token number">1</span></div></li></ul><span class="token punctuation">}</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"proxy"</span>: <div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"enginecommand"</span>: <span class="token number">10</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"getlog"</span>: <span class="token number">4446</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"total"</span>: <span class="token number">8902</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"command"</span>: <span class="token number">4446</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-9" aria-labelledby="react-tabs-8"></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-11" aria-labelledby="react-tabs-10"></div></div></div></div></div></div><div id="tag/Internal/paths/~1internal~1thumbprint/get" data-section-id="tag/Internal/paths/~1internal~1thumbprint/get" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-ibxdXY bSFXlp"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Internal/paths/~1internal~1thumbprint/get"></a>Get public key thumbprint<!-- --> </h2><div class="sc-RefOD boajtD"><div class="sc-jWBwVP sc-iRbamj flfxUM"><p>Get the thumbprint of the public key of the Gorgone daemon.</p> -</div></div><div class="sc-jtRfpW geWpKA"><div class="sc-lhVmIH dVvUxe"><h5 class="sc-gqjmRU sc-elJkPf jGRUDj">Authorizations: </h5></div><div class="sc-bYSBpT fKHsnH"><span class="sc-btzYZH hQBRTt"><span class="sc-jwKygS bnFPhO"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><div><h3 class="sc-eXEjpC espozG"> Responses </h3><div><div class="sc-kPVwWT fDvFMp"><svg class="sc-jTzLTM NifDa" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>200<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>OK</p> -</span></div></div><div><div class="sc-kPVwWT byLrBg"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>Unauthorized</p> -</span></div></div><div><div class="sc-kPVwWT byLrBg"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>Forbidden</p> -</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-kTUwUJ ldTMcP"><div class="sc-gxMtzJ dHLKeu"><span type="get" class="sc-dfVpRl jBjYbV http-verb get"> <!-- -->get</span> <span class="sc-dqBHgY iENVAs">/internal/thumbprint</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-gzOgki lkvpfX"><div class="sc-iyvyFf eftLSo"><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Local Gorgone instance</p> -</div><div><div class="sc-hwwEjo bwgXFh"><span>{protocol}://{server}:{port}/api</span>/internal/thumbprint</div></div></div><div class="sc-iyvyFf eftLSo"><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Remote Gorgone instance</p> -</div><div><div class="sc-hwwEjo bwgXFh"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/internal/thumbprint</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL irpqyy" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="react-tabs-12" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-13" tabindex="0">200</li><li class="tab-error" role="tab" id="react-tabs-14" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-15">401</li><li class="tab-error" role="tab" id="react-tabs-16" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-17">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-13" aria-labelledby="react-tabs-12"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA gzAoUb">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"action"</span>: <span class="token string">"getthumbprint"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"message"</span>: <span class="token string">"ok"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"data"</span>: <div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"thumbprint"</span>: <span class="token string">"cS4B3lZq96qcP4FTMhVMuwAhztqRBQERKyhnEitnTFM"</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-15" aria-labelledby="react-tabs-14"></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-17" aria-labelledby="react-tabs-16"></div></div></div></div></div></div><div id="tag/Internal/paths/~1internal~1logger/post" data-section-id="tag/Internal/paths/~1internal~1logger/post" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-ibxdXY bSFXlp"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Internal/paths/~1internal~1logger/post"></a>Set logger severity level<!-- --> </h2><div class="sc-RefOD boajtD"><div class="sc-jWBwVP sc-iRbamj flfxUM"><p>Set the logger severity level for all modules.</p> -</div></div><div class="sc-jtRfpW geWpKA"><div class="sc-lhVmIH dVvUxe"><h5 class="sc-gqjmRU sc-elJkPf jGRUDj">Authorizations: </h5></div><div class="sc-bYSBpT fKHsnH"><span class="sc-btzYZH hQBRTt"><span class="sc-jwKygS bnFPhO"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><h5 class="sc-gqjmRU LiUBH">Request Body schema: <span class="sc-hSdWYo hoUoen">application/json</span></h5><div class="sc-jWBwVP sc-iRbamj flfxUM"></div><table class="sc-dxgOiQ eCjbJc"><tbody><tr class="last undefined"><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="severity"><span class="sc-kGXeez bcLONg"></span>severity</td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div><div><span class="sc-cHGsZl lpeYvY"> <!-- -->Enum<!-- -->:</span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">"info"</span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">"error"</span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">"debug"</span> </div> <div><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Severity level to be defined for all loaded modules</p> -</div></div></div></td></tr></tbody></table><div><h3 class="sc-eXEjpC espozG"> Responses </h3><div><div class="sc-kPVwWT hLVzSF"><strong>204<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>OK</p> -</span></div></div><div><div class="sc-kPVwWT byLrBg"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>Unauthorized</p> -</span></div></div><div><div class="sc-kPVwWT byLrBg"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>Forbidden</p> -</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-kTUwUJ ldTMcP"><div class="sc-gxMtzJ dHLKeu"><span type="post" class="sc-dfVpRl bNYCAJ http-verb post"> <!-- -->post</span> <span class="sc-dqBHgY iENVAs">/internal/logger</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-gzOgki lkvpfX"><div class="sc-iyvyFf eftLSo"><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Local Gorgone instance</p> -</div><div><div class="sc-hwwEjo bwgXFh"><span>{protocol}://{server}:{port}/api</span>/internal/logger</div></div></div><div class="sc-iyvyFf eftLSo"><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Remote Gorgone instance</p> -</div><div><div class="sc-hwwEjo bwgXFh"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/internal/logger</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Request samples </h3><div class="sc-eHgmQL irpqyy" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="react-tabs__tab react-tabs__tab--selected" role="tab" id="react-tabs-18" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-19" tabindex="0">Payload</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-19" aria-labelledby="react-tabs-18"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA gzAoUb">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"severity"</span>: <span class="token string">"info"</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL irpqyy" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-error react-tabs__tab--selected" role="tab" id="react-tabs-20" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-21" tabindex="0">401</li><li class="tab-error" role="tab" id="react-tabs-22" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-23">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-21" aria-labelledby="react-tabs-20"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA gzAoUb">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"error"</span>: <span class="token string">"http_error_401"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"message"</span>: <span class="token string">"unauthorized"</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-23" aria-labelledby="react-tabs-22"></div></div></div></div></div></div><div id="tag/Logs" data-section-id="tag/Logs" class="sc-ifAKCX dluJDj"><div class="sc-gzVnrw eesUPo"><div class="sc-bxivhb cjtbAK"><h1 class="sc-htoDjs WxWXp"><a class="sc-VigVT kGvRyb" href="#tag/Logs"></a>Logs</h1></div></div><div class="sc-bxivhb kFNigF"><div class="sc-jWBwVP sc-iRbamj flfxUM redoc-markdown "><p>Logs management.</p> -</div></div></div><div id="tag/Logs/paths/~1log~1{token}/get" data-section-id="tag/Logs/paths/~1log~1{token}/get" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-ibxdXY bSFXlp"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Logs/paths/~1log~1{token}/get"></a>Retrieve event's logs<!-- --> </h2><div class="sc-RefOD boajtD"><div class="sc-jWBwVP sc-iRbamj flfxUM"><p>Retrieve the event's logs based on event's token.</p> -</div></div><div class="sc-jtRfpW geWpKA"><div class="sc-lhVmIH dVvUxe"><h5 class="sc-gqjmRU sc-elJkPf jGRUDj">Authorizations: </h5></div><div class="sc-bYSBpT fKHsnH"><span class="sc-btzYZH hQBRTt"><span class="sc-jwKygS bnFPhO"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><div><h5 class="sc-gqjmRU LiUBH">path<!-- --> Parameters</h5><table class="sc-dxgOiQ eCjbJc"><tbody><tr class="last undefined"><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="token"><span class="sc-kGXeez bcLONg"></span>token<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div> <div><span class="sc-cHGsZl lpeYvY"> <!-- -->Example:<!-- --> </span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165</span></div><div><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Token of the event</p> -</div></div></div></td></tr></tbody></table></div><div><h5 class="sc-gqjmRU LiUBH">query<!-- --> Parameters</h5><table class="sc-dxgOiQ eCjbJc"><tbody><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="code"><span class="sc-kGXeez bcLONg"></span>code</td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">integer</span></div><div><span class="sc-cHGsZl lpeYvY"> <!-- -->Enum<!-- -->:</span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">0</span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">1</span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">2</span> </div> <div><span class="sc-cHGsZl lpeYvY"> <!-- -->Example:<!-- --> </span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">code=2</span></div><div><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Only retrieve logs with defined code</p> -</div></div></div></td></tr><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="limit"><span class="sc-kGXeez bcLONg"></span>limit</td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">integer</span><span> <span class="sc-cHGsZl sc-jqCOkK beUper"> <!-- -->>= 1<!-- --> </span></span></div> <div><span class="sc-cHGsZl lpeYvY"> <!-- -->Example:<!-- --> </span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">limit=1</span></div><div><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Only retrieve the last x logs</p> -</div></div></div></td></tr><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="ctime"><span class="sc-kGXeez bcLONg"></span>ctime</td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">integer</span><span class="sc-cHGsZl sc-kgAjT hqYVjx"> <!-- --><<!-- -->int64<!-- -->><!-- --> </span></div> <div><span class="sc-cHGsZl lpeYvY"> <!-- -->Example:<!-- --> </span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">ctime=1577726040</span></div><div><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Only retrieve logs with a creation time equal or superior to a timestamp</p> -</div></div></div></td></tr><tr class="last undefined"><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="etime"><span class="sc-kGXeez bcLONg"></span>etime</td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">integer</span><span class="sc-cHGsZl sc-kgAjT hqYVjx"> <!-- --><<!-- -->int64<!-- -->><!-- --> </span></div> <div><span class="sc-cHGsZl lpeYvY"> <!-- -->Example:<!-- --> </span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">etime=1577726040</span></div><div><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Only retrieve logs of an event time superior to a timestamp</p> -</div></div></div></td></tr></tbody></table></div><div><h3 class="sc-eXEjpC espozG"> Responses </h3><div><div class="sc-kPVwWT fDvFMp"><svg class="sc-jTzLTM NifDa" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>200<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>OK</p> -</span></div></div><div><div class="sc-kPVwWT byLrBg"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>Unauthorized</p> -</span></div></div><div><div class="sc-kPVwWT byLrBg"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>Forbidden</p> -</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-kTUwUJ ldTMcP"><div class="sc-gxMtzJ dHLKeu"><span type="get" class="sc-dfVpRl jBjYbV http-verb get"> <!-- -->get</span> <span class="sc-dqBHgY iENVAs">/log/{token}</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-gzOgki lkvpfX"><div class="sc-iyvyFf eftLSo"><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Local Gorgone instance</p> -</div><div><div class="sc-hwwEjo bwgXFh"><span>{protocol}://{server}:{port}/api</span>/log/{token}</div></div></div><div class="sc-iyvyFf eftLSo"><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Remote Gorgone instance</p> -</div><div><div class="sc-hwwEjo bwgXFh"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/log/{token}</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL irpqyy" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="react-tabs-24" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-25" tabindex="0">200</li><li class="tab-error" role="tab" id="react-tabs-26" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-27">401</li><li class="tab-error" role="tab" id="react-tabs-28" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-29">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-25" aria-labelledby="react-tabs-24"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA gzAoUb">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Example</span><div class="Dropdown-root sc-kkGfuU sc-fAjcbJ hrtKLV"><div class="Dropdown-control" aria-haspopup="listbox"><div class="Dropdown-placeholder is-selected">Logs</div><div class="Dropdown-arrow-wrapper"><span class="Dropdown-arrow"></span></div></div></div></div><div><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"message"</span>: <span class="token string">"Logs found"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"token"</span>: <span class="token string">"03008486ba50b52e529ff5828d1432e5578dd18bb530c145b133dc902c8cfa6b8aac4d58fffb0c5ed44b943d2acbfb7cd1b18c55fcebce62e51999db460112c7"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"data"</span>: <div class="collapser"></div><span class="token punctuation">[</span><span class="ellipsis"></span><ul class="array collapsible"><li><div class="hoverable collapsed"><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"ctime"</span>: <span class="token number">1577727699</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"etime"</span>: <span class="token number">1577727699</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"id"</span>: <span class="token number">101483</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"instant"</span>: <span class="token number">0</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"data"</span>: <span class="token punctuation">{ }</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"token"</span>: <span class="token string">"03008486ba50b52e529ff5828d1432e5578dd18bb530c145b133dc902c8cfa6b8aac4d58fffb0c5ed44b943d2acbfb7cd1b18c55fcebce62e51999db460112c7"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"code"</span>: <span class="token number">2</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">]</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-27" aria-labelledby="react-tabs-26"></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-29" aria-labelledby="react-tabs-28"></div></div></div></div></div></div><div id="tag/Cron" data-section-id="tag/Cron" class="sc-ifAKCX dluJDj"><div class="sc-gzVnrw eesUPo"><div class="sc-bxivhb cjtbAK"><h1 class="sc-htoDjs WxWXp"><a class="sc-VigVT kGvRyb" href="#tag/Cron"></a>Cron</h1></div></div><div class="sc-bxivhb kFNigF"><div class="sc-jWBwVP sc-iRbamj flfxUM redoc-markdown "><p>Module aiming to reproduce a cron-like scheduler that can send events to other Gorgone modules.</p> -</div></div></div><div id="tag/Cron/paths/~1core~1cron~1definitions/get" data-section-id="tag/Cron/paths/~1core~1cron~1definitions/get" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-ibxdXY bSFXlp"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Cron/paths/~1core~1cron~1definitions/get"></a>List definitions<!-- --> </h2><div class="sc-RefOD boajtD"><div class="sc-jWBwVP sc-iRbamj flfxUM"><p>List all cron definitions.</p> -</div></div><div class="sc-jtRfpW geWpKA"><div class="sc-lhVmIH dVvUxe"><h5 class="sc-gqjmRU sc-elJkPf jGRUDj">Authorizations: </h5></div><div class="sc-bYSBpT fKHsnH"><span class="sc-btzYZH hQBRTt"><span class="sc-jwKygS bnFPhO"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><div><h3 class="sc-eXEjpC espozG"> Responses </h3><div><div class="sc-kPVwWT fDvFMp"><svg class="sc-jTzLTM NifDa" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>200<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>OK</p> -</span></div></div><div><div class="sc-kPVwWT byLrBg"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>Unauthorized</p> -</span></div></div><div><div class="sc-kPVwWT byLrBg"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>Forbidden</p> -</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-kTUwUJ ldTMcP"><div class="sc-gxMtzJ dHLKeu"><span type="get" class="sc-dfVpRl jBjYbV http-verb get"> <!-- -->get</span> <span class="sc-dqBHgY iENVAs">/core/cron/definitions</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-gzOgki lkvpfX"><div class="sc-iyvyFf eftLSo"><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Local Gorgone instance</p> -</div><div><div class="sc-hwwEjo bwgXFh"><span>{protocol}://{server}:{port}/api</span>/core/cron/definitions</div></div></div><div class="sc-iyvyFf eftLSo"><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Remote Gorgone instance</p> -</div><div><div class="sc-hwwEjo bwgXFh"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/core/cron/definitions</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL irpqyy" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="react-tabs-30" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-31" tabindex="0">200</li><li class="tab-error" role="tab" id="react-tabs-32" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-33">401</li><li class="tab-error" role="tab" id="react-tabs-34" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-35">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-31" aria-labelledby="react-tabs-30"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA gzAoUb">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Example</span><div class="Dropdown-root sc-kkGfuU sc-fAjcbJ hrtKLV"><div class="Dropdown-control" aria-haspopup="listbox"><div class="Dropdown-placeholder is-selected">Token</div><div class="Dropdown-arrow-wrapper"><span class="Dropdown-arrow"></span></div></div></div></div><div><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"token"</span>: <span class="token string">"1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-33" aria-labelledby="react-tabs-32"></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-35" aria-labelledby="react-tabs-34"></div></div></div></div></div></div><div id="tag/Cron/paths/~1core~1cron~1definitions/post" data-section-id="tag/Cron/paths/~1core~1cron~1definitions/post" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-ibxdXY bSFXlp"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Cron/paths/~1core~1cron~1definitions/post"></a>Add definitions<!-- --> </h2><div class="sc-RefOD boajtD"><div class="sc-jWBwVP sc-iRbamj flfxUM"><p>Add one or multiple cron definitions to runtime.</p> -</div></div><div class="sc-jtRfpW geWpKA"><div class="sc-lhVmIH dVvUxe"><h5 class="sc-gqjmRU sc-elJkPf jGRUDj">Authorizations: </h5></div><div class="sc-bYSBpT fKHsnH"><span class="sc-btzYZH hQBRTt"><span class="sc-jwKygS bnFPhO"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><h5 class="sc-gqjmRU LiUBH">Request Body schema: <span class="sc-hSdWYo hoUoen">application/json</span></h5><div class="sc-jWBwVP sc-iRbamj flfxUM"></div><div><div class="sc-hMqMXs dDdNtD"> Array </div><div class="sc-gGBfsJ bvBDls"><table class="sc-dxgOiQ eCjbJc"><tbody><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="timespec"><span class="sc-kGXeez bcLONg"></span>timespec<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div> <div><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Cron-like time specification</p> -</div></div></div></td></tr><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="id"><span class="sc-kGXeez bcLONg"></span>id<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div> <div><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Unique identifier of the cron definition</p> -</div></div></div></td></tr><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="action"><span class="sc-kGXeez bcLONg"></span>action<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div> <div><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Action/event to call at job execution</p> -</div></div></div></td></tr><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="parameters"><span class="sc-kGXeez bcLONg"></span>parameters<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">object</span></div> <div><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Parameters needed by the called action/event</p> -</div></div></div></td></tr><tr class="last undefined"><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="keep_token"><span class="sc-kGXeez bcLONg"></span>keep_token</td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">boolean</span></div> <div><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Boolean to define whether or not the ID of the definition will be used as token for the command</p> -</div></div></div></td></tr></tbody></table></div><div class="sc-kEYyzF cMefLx"></div></div><div><h3 class="sc-eXEjpC espozG"> Responses </h3><div><div class="sc-kPVwWT fDvFMp"><svg class="sc-jTzLTM NifDa" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>200<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>OK</p> -</span></div></div><div><div class="sc-kPVwWT byLrBg"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>Unauthorized</p> -</span></div></div><div><div class="sc-kPVwWT byLrBg"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>Forbidden</p> -</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-kTUwUJ ldTMcP"><div class="sc-gxMtzJ dHLKeu"><span type="post" class="sc-dfVpRl bNYCAJ http-verb post"> <!-- -->post</span> <span class="sc-dqBHgY iENVAs">/core/cron/definitions</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-gzOgki lkvpfX"><div class="sc-iyvyFf eftLSo"><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Local Gorgone instance</p> -</div><div><div class="sc-hwwEjo bwgXFh"><span>{protocol}://{server}:{port}/api</span>/core/cron/definitions</div></div></div><div class="sc-iyvyFf eftLSo"><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Remote Gorgone instance</p> -</div><div><div class="sc-hwwEjo bwgXFh"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/core/cron/definitions</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Request samples </h3><div class="sc-eHgmQL irpqyy" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="react-tabs__tab react-tabs__tab--selected" role="tab" id="react-tabs-36" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-37" tabindex="0">Payload</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-37" aria-labelledby="react-tabs-36"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA gzAoUb">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">[</span><span class="ellipsis"></span><ul class="array collapsible"><li><div class="hoverable "><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"timespec"</span>: <span class="token string">"string"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"id"</span>: <span class="token string">"string"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"action"</span>: <span class="token string">"string"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"parameters"</span>: <span class="token punctuation">{ }</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"keep_token"</span>: <span class="token boolean">true</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">]</span></code></div></div></div></div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL irpqyy" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="react-tabs-38" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-39" tabindex="0">200</li><li class="tab-error" role="tab" id="react-tabs-40" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-41">401</li><li class="tab-error" role="tab" id="react-tabs-42" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-43">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-39" aria-labelledby="react-tabs-38"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA gzAoUb">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Example</span><div class="Dropdown-root sc-kkGfuU sc-fAjcbJ hrtKLV"><div class="Dropdown-control" aria-haspopup="listbox"><div class="Dropdown-placeholder is-selected">Token</div><div class="Dropdown-arrow-wrapper"><span class="Dropdown-arrow"></span></div></div></div></div><div><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"token"</span>: <span class="token string">"1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-41" aria-labelledby="react-tabs-40"></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-43" aria-labelledby="react-tabs-42"></div></div></div></div></div></div><div id="tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}/get" data-section-id="tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}/get" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-ibxdXY bSFXlp"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}/get"></a>Get a definition<!-- --> </h2><div class="sc-RefOD boajtD"><div class="sc-jWBwVP sc-iRbamj flfxUM"><p>List cron definition identified by id.</p> -</div></div><div class="sc-jtRfpW geWpKA"><div class="sc-lhVmIH dVvUxe"><h5 class="sc-gqjmRU sc-elJkPf jGRUDj">Authorizations: </h5></div><div class="sc-bYSBpT fKHsnH"><span class="sc-btzYZH hQBRTt"><span class="sc-jwKygS bnFPhO"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><div><h5 class="sc-gqjmRU LiUBH">path<!-- --> Parameters</h5><table class="sc-dxgOiQ eCjbJc"><tbody><tr class="last undefined"><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="definition_id"><span class="sc-kGXeez bcLONg"></span>definition_id<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div> <div><span class="sc-cHGsZl lpeYvY"> <!-- -->Example:<!-- --> </span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">broker_stats</span></div><div><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>ID of the definition</p> -</div></div></div></td></tr></tbody></table></div><div><h3 class="sc-eXEjpC espozG"> Responses </h3><div><div class="sc-kPVwWT fDvFMp"><svg class="sc-jTzLTM NifDa" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>200<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>OK</p> -</span></div></div><div><div class="sc-kPVwWT byLrBg"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>Unauthorized</p> -</span></div></div><div><div class="sc-kPVwWT byLrBg"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>Forbidden</p> -</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-kTUwUJ ldTMcP"><div class="sc-gxMtzJ dHLKeu"><span type="get" class="sc-dfVpRl jBjYbV http-verb get"> <!-- -->get</span> <span class="sc-dqBHgY iENVAs">/core/cron/definitions/{definition_id}</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-gzOgki lkvpfX"><div class="sc-iyvyFf eftLSo"><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Local Gorgone instance</p> -</div><div><div class="sc-hwwEjo bwgXFh"><span>{protocol}://{server}:{port}/api</span>/core/cron/definitions/{definition_id}</div></div></div><div class="sc-iyvyFf eftLSo"><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Remote Gorgone instance</p> -</div><div><div class="sc-hwwEjo bwgXFh"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/core/cron/definitions/{definition_id}</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL irpqyy" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="react-tabs-44" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-45" tabindex="0">200</li><li class="tab-error" role="tab" id="react-tabs-46" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-47">401</li><li class="tab-error" role="tab" id="react-tabs-48" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-49">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-45" aria-labelledby="react-tabs-44"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA gzAoUb">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Example</span><div class="Dropdown-root sc-kkGfuU sc-fAjcbJ hrtKLV"><div class="Dropdown-control" aria-haspopup="listbox"><div class="Dropdown-placeholder is-selected">Token</div><div class="Dropdown-arrow-wrapper"><span class="Dropdown-arrow"></span></div></div></div></div><div><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"token"</span>: <span class="token string">"1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-47" aria-labelledby="react-tabs-46"></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-49" aria-labelledby="react-tabs-48"></div></div></div></div></div></div><div id="tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}/patch" data-section-id="tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}/patch" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-ibxdXY bSFXlp"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}/patch"></a>Update a definition<!-- --> </h2><div class="sc-RefOD boajtD"><div class="sc-jWBwVP sc-iRbamj flfxUM"><p>Update a cron definition.</p> -</div></div><div class="sc-jtRfpW geWpKA"><div class="sc-lhVmIH dVvUxe"><h5 class="sc-gqjmRU sc-elJkPf jGRUDj">Authorizations: </h5></div><div class="sc-bYSBpT fKHsnH"><span class="sc-btzYZH hQBRTt"><span class="sc-jwKygS bnFPhO"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><div><h5 class="sc-gqjmRU LiUBH">path<!-- --> Parameters</h5><table class="sc-dxgOiQ eCjbJc"><tbody><tr class="last undefined"><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="definition_id"><span class="sc-kGXeez bcLONg"></span>definition_id<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div> <div><span class="sc-cHGsZl lpeYvY"> <!-- -->Example:<!-- --> </span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">broker_stats</span></div><div><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>ID of the definition</p> -</div></div></div></td></tr></tbody></table></div><h5 class="sc-gqjmRU LiUBH">Request Body schema: <span class="sc-hSdWYo hoUoen">application/json</span></h5><div class="sc-jWBwVP sc-iRbamj flfxUM"></div><table class="sc-dxgOiQ eCjbJc"><tbody><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="timespec"><span class="sc-kGXeez bcLONg"></span>timespec<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div> <div><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Cron-like time specification</p> -</div></div></div></td></tr><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="id"><span class="sc-kGXeez bcLONg"></span>id<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div> <div><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Unique identifier of the cron definition</p> -</div></div></div></td></tr><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="action"><span class="sc-kGXeez bcLONg"></span>action<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div> <div><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Action/event to call at job execution</p> -</div></div></div></td></tr><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="parameters"><span class="sc-kGXeez bcLONg"></span>parameters<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">object</span></div> <div><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Parameters needed by the called action/event</p> -</div></div></div></td></tr><tr class="last undefined"><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="keep_token"><span class="sc-kGXeez bcLONg"></span>keep_token</td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">boolean</span></div> <div><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Boolean to define whether or not the ID of the definition will be used as token for the command</p> -</div></div></div></td></tr></tbody></table><div><h3 class="sc-eXEjpC espozG"> Responses </h3><div><div class="sc-kPVwWT fDvFMp"><svg class="sc-jTzLTM NifDa" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>200<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>OK</p> -</span></div></div><div><div class="sc-kPVwWT byLrBg"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>Unauthorized</p> -</span></div></div><div><div class="sc-kPVwWT byLrBg"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>Forbidden</p> -</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-kTUwUJ ldTMcP"><div class="sc-gxMtzJ dHLKeu"><span type="patch" class="sc-dfVpRl fRsrDc http-verb patch"> <!-- -->patch</span> <span class="sc-dqBHgY iENVAs">/core/cron/definitions/{definition_id}</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-gzOgki lkvpfX"><div class="sc-iyvyFf eftLSo"><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Local Gorgone instance</p> -</div><div><div class="sc-hwwEjo bwgXFh"><span>{protocol}://{server}:{port}/api</span>/core/cron/definitions/{definition_id}</div></div></div><div class="sc-iyvyFf eftLSo"><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Remote Gorgone instance</p> -</div><div><div class="sc-hwwEjo bwgXFh"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/core/cron/definitions/{definition_id}</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Request samples </h3><div class="sc-eHgmQL irpqyy" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="react-tabs__tab react-tabs__tab--selected" role="tab" id="react-tabs-50" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-51" tabindex="0">Payload</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-51" aria-labelledby="react-tabs-50"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA gzAoUb">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"timespec"</span>: <span class="token string">"string"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"id"</span>: <span class="token string">"string"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"action"</span>: <span class="token string">"string"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"parameters"</span>: <span class="token punctuation">{ }</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"keep_token"</span>: <span class="token boolean">true</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL irpqyy" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="react-tabs-52" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-53" tabindex="0">200</li><li class="tab-error" role="tab" id="react-tabs-54" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-55">401</li><li class="tab-error" role="tab" id="react-tabs-56" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-57">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-53" aria-labelledby="react-tabs-52"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA gzAoUb">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Example</span><div class="Dropdown-root sc-kkGfuU sc-fAjcbJ hrtKLV"><div class="Dropdown-control" aria-haspopup="listbox"><div class="Dropdown-placeholder is-selected">Token</div><div class="Dropdown-arrow-wrapper"><span class="Dropdown-arrow"></span></div></div></div></div><div><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"token"</span>: <span class="token string">"1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-55" aria-labelledby="react-tabs-54"></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-57" aria-labelledby="react-tabs-56"></div></div></div></div></div></div><div id="tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}/delete" data-section-id="tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}/delete" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-ibxdXY bSFXlp"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}/delete"></a>Delete a definition<!-- --> </h2><div class="sc-RefOD boajtD"><div class="sc-jWBwVP sc-iRbamj flfxUM"><p>Delete a cron definition.</p> -</div></div><div class="sc-jtRfpW geWpKA"><div class="sc-lhVmIH dVvUxe"><h5 class="sc-gqjmRU sc-elJkPf jGRUDj">Authorizations: </h5></div><div class="sc-bYSBpT fKHsnH"><span class="sc-btzYZH hQBRTt"><span class="sc-jwKygS bnFPhO"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><div><h5 class="sc-gqjmRU LiUBH">path<!-- --> Parameters</h5><table class="sc-dxgOiQ eCjbJc"><tbody><tr class="last undefined"><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="definition_id"><span class="sc-kGXeez bcLONg"></span>definition_id<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div> <div><span class="sc-cHGsZl lpeYvY"> <!-- -->Example:<!-- --> </span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">broker_stats</span></div><div><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>ID of the definition</p> -</div></div></div></td></tr></tbody></table></div><div><h3 class="sc-eXEjpC espozG"> Responses </h3><div><div class="sc-kPVwWT fDvFMp"><svg class="sc-jTzLTM NifDa" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>200<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>OK</p> -</span></div></div><div><div class="sc-kPVwWT byLrBg"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>Unauthorized</p> -</span></div></div><div><div class="sc-kPVwWT byLrBg"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>Forbidden</p> -</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-kTUwUJ ldTMcP"><div class="sc-gxMtzJ dHLKeu"><span type="delete" class="sc-dfVpRl hPskZd http-verb delete"> <!-- -->delete</span> <span class="sc-dqBHgY iENVAs">/core/cron/definitions/{definition_id}</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-gzOgki lkvpfX"><div class="sc-iyvyFf eftLSo"><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Local Gorgone instance</p> -</div><div><div class="sc-hwwEjo bwgXFh"><span>{protocol}://{server}:{port}/api</span>/core/cron/definitions/{definition_id}</div></div></div><div class="sc-iyvyFf eftLSo"><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Remote Gorgone instance</p> -</div><div><div class="sc-hwwEjo bwgXFh"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/core/cron/definitions/{definition_id}</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL irpqyy" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="react-tabs-58" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-59" tabindex="0">200</li><li class="tab-error" role="tab" id="react-tabs-60" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-61">401</li><li class="tab-error" role="tab" id="react-tabs-62" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-63">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-59" aria-labelledby="react-tabs-58"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA gzAoUb">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Example</span><div class="Dropdown-root sc-kkGfuU sc-fAjcbJ hrtKLV"><div class="Dropdown-control" aria-haspopup="listbox"><div class="Dropdown-placeholder is-selected">Token</div><div class="Dropdown-arrow-wrapper"><span class="Dropdown-arrow"></span></div></div></div></div><div><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"token"</span>: <span class="token string">"1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-61" aria-labelledby="react-tabs-60"></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-63" aria-labelledby="react-tabs-62"></div></div></div></div></div></div><div id="tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}~1status/get" data-section-id="tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}~1status/get" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-ibxdXY bSFXlp"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}~1status/get"></a>Get a definition status<!-- --> </h2><div class="sc-RefOD boajtD"><div class="sc-jWBwVP sc-iRbamj flfxUM"><p>Get a definition execution status.</p> -</div></div><div class="sc-jtRfpW geWpKA"><div class="sc-lhVmIH dVvUxe"><h5 class="sc-gqjmRU sc-elJkPf jGRUDj">Authorizations: </h5></div><div class="sc-bYSBpT fKHsnH"><span class="sc-btzYZH hQBRTt"><span class="sc-jwKygS bnFPhO"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><div><h5 class="sc-gqjmRU LiUBH">path<!-- --> Parameters</h5><table class="sc-dxgOiQ eCjbJc"><tbody><tr class="last undefined"><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="definition_id"><span class="sc-kGXeez bcLONg"></span>definition_id<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div> <div><span class="sc-cHGsZl lpeYvY"> <!-- -->Example:<!-- --> </span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">broker_stats</span></div><div><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>ID of the definition</p> -</div></div></div></td></tr></tbody></table></div><div><h3 class="sc-eXEjpC espozG"> Responses </h3><div><div class="sc-kPVwWT fDvFMp"><svg class="sc-jTzLTM NifDa" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>200<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>OK</p> -</span></div></div><div><div class="sc-kPVwWT byLrBg"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>Unauthorized</p> -</span></div></div><div><div class="sc-kPVwWT byLrBg"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>Forbidden</p> -</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-kTUwUJ ldTMcP"><div class="sc-gxMtzJ dHLKeu"><span type="get" class="sc-dfVpRl jBjYbV http-verb get"> <!-- -->get</span> <span class="sc-dqBHgY iENVAs">/core/cron/definitions/{definition_id}/status</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-gzOgki lkvpfX"><div class="sc-iyvyFf eftLSo"><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Local Gorgone instance</p> -</div><div><div class="sc-hwwEjo bwgXFh"><span>{protocol}://{server}:{port}/api</span>/core/cron/definitions/{definition_id}/status</div></div></div><div class="sc-iyvyFf eftLSo"><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Remote Gorgone instance</p> -</div><div><div class="sc-hwwEjo bwgXFh"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/core/cron/definitions/{definition_id}/status</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL irpqyy" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="react-tabs-64" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-65" tabindex="0">200</li><li class="tab-error" role="tab" id="react-tabs-66" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-67">401</li><li class="tab-error" role="tab" id="react-tabs-68" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-69">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-65" aria-labelledby="react-tabs-64"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA gzAoUb">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Example</span><div class="Dropdown-root sc-kkGfuU sc-fAjcbJ hrtKLV"><div class="Dropdown-control" aria-haspopup="listbox"><div class="Dropdown-placeholder is-selected">Token</div><div class="Dropdown-arrow-wrapper"><span class="Dropdown-arrow"></span></div></div></div></div><div><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"token"</span>: <span class="token string">"1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-67" aria-labelledby="react-tabs-66"></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-69" aria-labelledby="react-tabs-68"></div></div></div></div></div></div><div id="tag/Action" data-section-id="tag/Action" class="sc-ifAKCX dluJDj"><div class="sc-gzVnrw eesUPo"><div class="sc-bxivhb cjtbAK"><h1 class="sc-htoDjs WxWXp"><a class="sc-VigVT kGvRyb" href="#tag/Action"></a>Action</h1></div></div><div class="sc-bxivhb kFNigF"><div class="sc-jWBwVP sc-iRbamj flfxUM redoc-markdown "><p>Module aiming to execute actions on the server running the Gorgone daemon or remotly using SSH.</p> -</div></div></div><div id="tag/Action/paths/~1core~1action~1command/post" data-section-id="tag/Action/paths/~1core~1action~1command/post" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-ibxdXY bSFXlp"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Action/paths/~1core~1action~1command/post"></a>Execute one or several command lines<!-- --> </h2><div class="sc-RefOD boajtD"><div class="sc-jWBwVP sc-iRbamj flfxUM"><p>Execute a command or a set of commands on server running Gorgone.</p> -</div></div><div class="sc-jtRfpW geWpKA"><div class="sc-lhVmIH dVvUxe"><h5 class="sc-gqjmRU sc-elJkPf jGRUDj">Authorizations: </h5></div><div class="sc-bYSBpT fKHsnH"><span class="sc-btzYZH hQBRTt"><span class="sc-jwKygS bnFPhO"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><h5 class="sc-gqjmRU LiUBH">Request Body schema: <span class="sc-hSdWYo hoUoen">application/json</span></h5><div class="sc-jWBwVP sc-iRbamj flfxUM"></div><div><div class="sc-hMqMXs dDdNtD"> Array </div><div class="sc-gGBfsJ bvBDls"><table class="sc-dxgOiQ eCjbJc"><tbody><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="command"><span class="sc-kGXeez bcLONg"></span>command<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div> <div><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Command to execute</p> -</div></div></div></td></tr><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="timeout"><span class="sc-kGXeez bcLONg"></span>timeout</td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">integer</span></div><div><span class="sc-cHGsZl lpeYvY"> <!-- -->Default:<!-- --> </span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">30</span></div> <div><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Time in seconds before a command is considered timed out</p> -</div></div></div></td></tr><tr class="last undefined"><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="continue_on_error"><span class="sc-kGXeez bcLONg"></span>continue_on_error</td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">boolean</span></div><div><span class="sc-cHGsZl lpeYvY"> <!-- -->Default:<!-- --> </span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">false</span></div> <div><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Behaviour in case of execution issue</p> -</div></div></div></td></tr></tbody></table></div><div class="sc-kEYyzF cMefLx"></div></div><div><h3 class="sc-eXEjpC espozG"> Responses </h3><div><div class="sc-kPVwWT fDvFMp"><svg class="sc-jTzLTM NifDa" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>200<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>OK</p> -</span></div></div><div><div class="sc-kPVwWT byLrBg"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>Unauthorized</p> -</span></div></div><div><div class="sc-kPVwWT byLrBg"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>Forbidden</p> -</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-kTUwUJ ldTMcP"><div class="sc-gxMtzJ dHLKeu"><span type="post" class="sc-dfVpRl bNYCAJ http-verb post"> <!-- -->post</span> <span class="sc-dqBHgY iENVAs">/core/action/command</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-gzOgki lkvpfX"><div class="sc-iyvyFf eftLSo"><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Local Gorgone instance</p> -</div><div><div class="sc-hwwEjo bwgXFh"><span>{protocol}://{server}:{port}/api</span>/core/action/command</div></div></div><div class="sc-iyvyFf eftLSo"><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Remote Gorgone instance</p> -</div><div><div class="sc-hwwEjo bwgXFh"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/core/action/command</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Request samples </h3><div class="sc-eHgmQL irpqyy" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="react-tabs__tab react-tabs__tab--selected" role="tab" id="react-tabs-70" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-71" tabindex="0">Payload</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-71" aria-labelledby="react-tabs-70"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA gzAoUb">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">[</span><span class="ellipsis"></span><ul class="array collapsible"><li><div class="hoverable "><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"command"</span>: <span class="token string">"echo data > /tmp/date.log"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"timeout"</span>: <span class="token number">5</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"continue_on_error"</span>: <span class="token boolean">true</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">]</span></code></div></div></div></div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL irpqyy" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="react-tabs-72" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-73" tabindex="0">200</li><li class="tab-error" role="tab" id="react-tabs-74" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-75">401</li><li class="tab-error" role="tab" id="react-tabs-76" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-77">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-73" aria-labelledby="react-tabs-72"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA gzAoUb">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Example</span><div class="Dropdown-root sc-kkGfuU sc-fAjcbJ hrtKLV"><div class="Dropdown-control" aria-haspopup="listbox"><div class="Dropdown-placeholder is-selected">Token</div><div class="Dropdown-arrow-wrapper"><span class="Dropdown-arrow"></span></div></div></div></div><div><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"token"</span>: <span class="token string">"1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-75" aria-labelledby="react-tabs-74"></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-77" aria-labelledby="react-tabs-76"></div></div></div></div></div></div><div id="tag/Engine" data-section-id="tag/Engine" class="sc-ifAKCX dluJDj"><div class="sc-gzVnrw eesUPo"><div class="sc-bxivhb cjtbAK"><h1 class="sc-htoDjs WxWXp"><a class="sc-VigVT kGvRyb" href="#tag/Engine"></a>Engine</h1></div></div><div class="sc-bxivhb kFNigF"><div class="sc-jWBwVP sc-iRbamj flfxUM redoc-markdown "><p>Module aiming to provide a bridge to communicate with Centreon Engine daemon.</p> -</div></div></div><div id="tag/Engine/paths/~1centreon~1engine~1command/post" data-section-id="tag/Engine/paths/~1centreon~1engine~1command/post" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-ibxdXY bSFXlp"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Engine/paths/~1centreon~1engine~1command/post"></a>Send one or several external commands<!-- --> </h2><div class="sc-RefOD boajtD"><div class="sc-jWBwVP sc-iRbamj flfxUM"><p>Send an external command or a set of external commands to a running Centreon Engine instance using command file pipe. +</div></div></div><div id="section/Authentication" data-section-id="section/Authentication" class="sc-ifAKCX dluJDj"><div class="sc-gzVnrw eesUPo"><div class="sc-bxivhb cjtbAK"><h1 class="sc-htoDjs WxWXp"><a class="sc-VigVT kGvRyb" href="#section/Authentication"></a>Authentication</h1></div></div><div id="section/Authentication/Basic Authentication" data-section-id="section/Authentication/Basic Authentication" class="sc-ifAKCX dluJDj"><div class="sc-gzVnrw eesUPo"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#section/Authentication/Basic Authentication"></a>Basic Authentication</h2><div class="sc-jWBwVP sc-iRbamj eHtzbE"></div><div class="sc-jWBwVP sc-iRbamj eHtzbE"><table class="security-details"><tbody><tr><th> Security Scheme Type </th><td> <!-- -->HTTP<!-- --> </td></tr><tr><th> HTTP Authorization Scheme </th><td> <!-- -->basic<!-- --> </td></tr></tbody></table></div></div></div></div></div><div id="tag/Internal" data-section-id="tag/Internal" class="sc-ifAKCX dluJDj"><div class="sc-gzVnrw eesUPo"><div class="sc-bxivhb cjtbAK"><h1 class="sc-htoDjs WxWXp"><a class="sc-VigVT kGvRyb" href="#tag/Internal"></a>Internal</h1></div></div><div class="sc-bxivhb kFNigF"><div class="sc-jWBwVP sc-iRbamj eHtzbE redoc-markdown "><p>Internal events.</p> +</div></div></div><div id="tag/Internal/paths/~1internal~1constatus/get" data-section-id="tag/Internal/paths/~1internal~1constatus/get" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-cmthru hcTXxz"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Internal/paths/~1internal~1constatus/get"></a>Get nodes connection status<!-- --> </h2><div class="sc-hMFtBS bemheR"><div class="sc-jWBwVP sc-iRbamj eHtzbE"><p>Get the connection status of all nodes managed by the Gorgone daemon.</p> +</div></div><div class="sc-bsbRJL PDnUY"><div class="sc-hXRMBi ceJGIt"><h5 class="sc-gqjmRU sc-iQNlJl lmVwfJ">Authorizations: </h5></div><div class="sc-epnACN iYKvkC"><span class="sc-gwVKww jtJYnZ"><span class="sc-eTuwsz eFFwMa"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><div><h3 class="sc-bwCtUz chVREB">Responses</h3><div><div class="sc-eXEjpC bTuXIq"><svg class="sc-jTzLTM hhjjYI" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>200<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>OK</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Unauthorized</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Forbidden</p> +</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-gzOgki fBopsv"><div class="sc-hwwEjo hgxMbQ"><span type="get" class="sc-kPVwWT hzxych http-verb get">get</span><span class="sc-iyvyFf dpRRAO">/internal/constatus</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-kfGgVZ bVSqpC"><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Local Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api</span>/internal/constatus</div></div></div><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Remote Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/internal/constatus</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL eulAfj" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="react-tabs-0" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-1" tabindex="0">200</li><li class="tab-error" role="tab" id="react-tabs-2" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-3">401</li><li class="tab-error" role="tab" id="react-tabs-4" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-5">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-1" aria-labelledby="react-tabs-0"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA lcundD">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"action"</span>: <span class="token string">"constatus"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"message"</span>: <span class="token string">"ok"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"data"</span>: <div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"id"</span>: <div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"last_ping_sent"</span>: <span class="token number">1577726040</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"type"</span>: <span class="token string">"push_zmq"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"nodes"</span>: <span class="token punctuation">{ }</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"last_ping_recv"</span>: <span class="token number">1577726040</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-3" aria-labelledby="react-tabs-2"></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-5" aria-labelledby="react-tabs-4"></div></div></div></div></div></div><div id="tag/Internal/paths/~1internal~1information/get" data-section-id="tag/Internal/paths/~1internal~1information/get" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-cmthru hcTXxz"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Internal/paths/~1internal~1information/get"></a>Get runtime informations and statistics<!-- --> </h2><div class="sc-hMFtBS bemheR"><div class="sc-jWBwVP sc-iRbamj eHtzbE"><p>Get informations and statistics about loaded modules, available endpoints and number of events computed at runtime.</p> +</div></div><div class="sc-bsbRJL PDnUY"><div class="sc-hXRMBi ceJGIt"><h5 class="sc-gqjmRU sc-iQNlJl lmVwfJ">Authorizations: </h5></div><div class="sc-epnACN iYKvkC"><span class="sc-gwVKww jtJYnZ"><span class="sc-eTuwsz eFFwMa"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><div><h3 class="sc-bwCtUz chVREB">Responses</h3><div><div class="sc-eXEjpC bTuXIq"><svg class="sc-jTzLTM hhjjYI" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>200<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>OK</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Unauthorized</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Forbidden</p> +</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-gzOgki fBopsv"><div class="sc-hwwEjo hgxMbQ"><span type="get" class="sc-kPVwWT hzxych http-verb get">get</span><span class="sc-iyvyFf dpRRAO">/internal/information</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-kfGgVZ bVSqpC"><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Local Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api</span>/internal/information</div></div></div><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Remote Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/internal/information</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL eulAfj" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="react-tabs-6" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-7" tabindex="0">200</li><li class="tab-error" role="tab" id="react-tabs-8" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-9">401</li><li class="tab-error" role="tab" id="react-tabs-10" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-11">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-7" aria-labelledby="react-tabs-6"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA lcundD">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"action"</span>: <span class="token string">"information"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"message"</span>: <span class="token string">"ok"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"data"</span>: <div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"modules"</span>: <div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"httpserver"</span>: <span class="token string">"gorgone::modules::core::httpserver::hooks"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"dbcleaner"</span>: <span class="token string">"gorgone::modules::core::dbcleaner::hooks"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"cron"</span>: <span class="token string">"gorgone::modules::core::cron::hooks"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"engine"</span>: <span class="token string">"gorgone::modules::centreon::engine::hooks"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"action"</span>: <span class="token string">"gorgone::modules::core::action::hooks"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"statistics"</span>: <span class="token string">"gorgone::modules::centreon::statistics::hooks"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"nodes"</span>: <span class="token string">"gorgone::modules::centreon::nodes::hooks"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"legacycmd"</span>: <span class="token string">"gorgone::modules::centreon::legacycmd::hooks"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"proxy"</span>: <span class="token string">"gorgone::modules::core::proxy::hooks"</span></div></li></ul><span class="token punctuation">}</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"api_endpoints"</span>: <div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"POST_/internal/logger"</span>: <span class="token string">"BCASTLOGGER"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"GET_/centreon/statistics/broker"</span>: <span class="token string">"BROKERSTATS"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"GET_/internal/thumbprint"</span>: <span class="token string">"GETTHUMBPRINT"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"GET_/core/cron/definitions"</span>: <span class="token string">"GETCRON"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"GET_/internal/information"</span>: <span class="token string">"INFORMATION"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"POST_/core/cron/definitions"</span>: <span class="token string">"ADDCRON"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"POST_/core/action/command"</span>: <span class="token string">"COMMAND"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"POST_/core/proxy/remotecopy"</span>: <span class="token string">"REMOTECOPY"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"POST_/centreon/engine/command"</span>: <span class="token string">"ENGINECOMMAND"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"PATCH_/core/cron/definitions"</span>: <span class="token string">"UPDATECRON"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"DELETE_/core/cron/definitions"</span>: <span class="token string">"DELETECRON"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"GET_/internal/constatus"</span>: <span class="token string">"CONSTATUS"</span></div></li></ul><span class="token punctuation">}</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"counters"</span>: <div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"total"</span>: <span class="token number">40210</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"external"</span>: <div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"total"</span>: <span class="token number">0</span></div></li></ul><span class="token punctuation">}</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"internal"</span>: <div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"legacycmdready"</span>: <span class="token number">1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"setlogs"</span>: <span class="token number">7841</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"enginecommand"</span>: <span class="token number">20</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"registernodes"</span>: <span class="token number">443</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"pong"</span>: <span class="token number">3397</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"proxyready"</span>: <span class="token number">5</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"statisticsready"</span>: <span class="token number">1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"addcron"</span>: <span class="token number">1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"cronready"</span>: <span class="token number">1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"getthumbprint"</span>: <span class="token number">2</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"centreonnodesready"</span>: <span class="token number">1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"httpserverready"</span>: <span class="token number">1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"command"</span>: <span class="token number">4446</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"putlog"</span>: <span class="token number">9809</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"dbcleanerready"</span>: <span class="token number">1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"information"</span>: <span class="token number">6</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"brokerstats"</span>: <span class="token number">4446</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"constatus"</span>: <span class="token number">1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"total"</span>: <span class="token number">40210</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"setcoreid"</span>: <span class="token number">443</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"getlog"</span>: <span class="token number">8893</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"engineready"</span>: <span class="token number">1</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"unregisternodes"</span>: <span class="token number">443</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"actionready"</span>: <span class="token number">1</span></div></li></ul><span class="token punctuation">}</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"proxy"</span>: <div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"enginecommand"</span>: <span class="token number">10</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"getlog"</span>: <span class="token number">4446</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"total"</span>: <span class="token number">8902</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"command"</span>: <span class="token number">4446</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-9" aria-labelledby="react-tabs-8"></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-11" aria-labelledby="react-tabs-10"></div></div></div></div></div></div><div id="tag/Internal/paths/~1internal~1thumbprint/get" data-section-id="tag/Internal/paths/~1internal~1thumbprint/get" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-cmthru hcTXxz"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Internal/paths/~1internal~1thumbprint/get"></a>Get public key thumbprint<!-- --> </h2><div class="sc-hMFtBS bemheR"><div class="sc-jWBwVP sc-iRbamj eHtzbE"><p>Get the thumbprint of the public key of the Gorgone daemon.</p> +</div></div><div class="sc-bsbRJL PDnUY"><div class="sc-hXRMBi ceJGIt"><h5 class="sc-gqjmRU sc-iQNlJl lmVwfJ">Authorizations: </h5></div><div class="sc-epnACN iYKvkC"><span class="sc-gwVKww jtJYnZ"><span class="sc-eTuwsz eFFwMa"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><div><h3 class="sc-bwCtUz chVREB">Responses</h3><div><div class="sc-eXEjpC bTuXIq"><svg class="sc-jTzLTM hhjjYI" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>200<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>OK</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Unauthorized</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Forbidden</p> +</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-gzOgki fBopsv"><div class="sc-hwwEjo hgxMbQ"><span type="get" class="sc-kPVwWT hzxych http-verb get">get</span><span class="sc-iyvyFf dpRRAO">/internal/thumbprint</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-kfGgVZ bVSqpC"><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Local Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api</span>/internal/thumbprint</div></div></div><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Remote Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/internal/thumbprint</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL eulAfj" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="react-tabs-12" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-13" tabindex="0">200</li><li class="tab-error" role="tab" id="react-tabs-14" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-15">401</li><li class="tab-error" role="tab" id="react-tabs-16" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-17">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-13" aria-labelledby="react-tabs-12"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA lcundD">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"action"</span>: <span class="token string">"getthumbprint"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"message"</span>: <span class="token string">"ok"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"data"</span>: <div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"thumbprint"</span>: <span class="token string">"cS4B3lZq96qcP4FTMhVMuwAhztqRBQERKyhnEitnTFM"</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-15" aria-labelledby="react-tabs-14"></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-17" aria-labelledby="react-tabs-16"></div></div></div></div></div></div><div id="tag/Internal/paths/~1internal~1logger/post" data-section-id="tag/Internal/paths/~1internal~1logger/post" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-cmthru hcTXxz"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Internal/paths/~1internal~1logger/post"></a>Set logger severity level<!-- --> </h2><div class="sc-hMFtBS bemheR"><div class="sc-jWBwVP sc-iRbamj eHtzbE"><p>Set the logger severity level for all modules.</p> +</div></div><div class="sc-bsbRJL PDnUY"><div class="sc-hXRMBi ceJGIt"><h5 class="sc-gqjmRU sc-iQNlJl lmVwfJ">Authorizations: </h5></div><div class="sc-epnACN iYKvkC"><span class="sc-gwVKww jtJYnZ"><span class="sc-eTuwsz eFFwMa"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><h5 class="sc-gqjmRU LiUBH">Request Body schema: <span class="sc-hSdWYo hoUoen">application/json</span></h5><div class="sc-jWBwVP sc-iRbamj eHtzbE"></div><table class="sc-dxgOiQ eCjbJc"><tbody><tr class="last undefined"><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="severity"><span class="sc-kGXeez bcLONg"></span>severity</td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div><div><span class="sc-cHGsZl lpeYvY"> <!-- -->Enum<!-- -->:</span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">"info"</span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">"error"</span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">"debug"</span> </div> <div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Severity level to be defined for all loaded modules</p> +</div></div></div></td></tr></tbody></table><div><h3 class="sc-bwCtUz chVREB">Responses</h3><div><div class="sc-eXEjpC dhsNFH"><strong>204<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>OK</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Unauthorized</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Forbidden</p> +</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-gzOgki fBopsv"><div class="sc-hwwEjo hgxMbQ"><span type="post" class="sc-kPVwWT dDFDWJ http-verb post">post</span><span class="sc-iyvyFf dpRRAO">/internal/logger</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-kfGgVZ bVSqpC"><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Local Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api</span>/internal/logger</div></div></div><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Remote Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/internal/logger</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Request samples </h3><div class="sc-eHgmQL eulAfj" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="react-tabs__tab react-tabs__tab--selected" role="tab" id="react-tabs-18" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-19" tabindex="0">Payload</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-19" aria-labelledby="react-tabs-18"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA lcundD">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"severity"</span>: <span class="token string">"info"</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL eulAfj" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-error react-tabs__tab--selected" role="tab" id="react-tabs-20" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-21" tabindex="0">401</li><li class="tab-error" role="tab" id="react-tabs-22" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-23">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-21" aria-labelledby="react-tabs-20"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA lcundD">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"error"</span>: <span class="token string">"http_error_401"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"message"</span>: <span class="token string">"unauthorized"</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-23" aria-labelledby="react-tabs-22"></div></div></div></div></div></div><div id="tag/Logs" data-section-id="tag/Logs" class="sc-ifAKCX dluJDj"><div class="sc-gzVnrw eesUPo"><div class="sc-bxivhb cjtbAK"><h1 class="sc-htoDjs WxWXp"><a class="sc-VigVT kGvRyb" href="#tag/Logs"></a>Logs</h1></div></div><div class="sc-bxivhb kFNigF"><div class="sc-jWBwVP sc-iRbamj eHtzbE redoc-markdown "><p>Logs management.</p> +</div></div></div><div id="tag/Logs/paths/~1log~1{token}/get" data-section-id="tag/Logs/paths/~1log~1{token}/get" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-cmthru hcTXxz"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Logs/paths/~1log~1{token}/get"></a>Retrieve event's logs<!-- --> </h2><div class="sc-hMFtBS bemheR"><div class="sc-jWBwVP sc-iRbamj eHtzbE"><p>Retrieve the event's logs based on event's token.</p> +</div></div><div class="sc-bsbRJL PDnUY"><div class="sc-hXRMBi ceJGIt"><h5 class="sc-gqjmRU sc-iQNlJl lmVwfJ">Authorizations: </h5></div><div class="sc-epnACN iYKvkC"><span class="sc-gwVKww jtJYnZ"><span class="sc-eTuwsz eFFwMa"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><div><h5 class="sc-gqjmRU LiUBH">path<!-- --> Parameters</h5><table class="sc-dxgOiQ eCjbJc"><tbody><tr class="last undefined"><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="token"><span class="sc-kGXeez bcLONg"></span>token<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div> <div><span class="sc-cHGsZl lpeYvY"> <!-- -->Example:<!-- --> </span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165</span></div><div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Token of the event</p> +</div></div></div></td></tr></tbody></table></div><div><h5 class="sc-gqjmRU LiUBH">query<!-- --> Parameters</h5><table class="sc-dxgOiQ eCjbJc"><tbody><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="code"><span class="sc-kGXeez bcLONg"></span>code</td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">integer</span></div><div><span class="sc-cHGsZl lpeYvY"> <!-- -->Enum<!-- -->:</span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">0</span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">1</span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">2</span> </div> <div><span class="sc-cHGsZl lpeYvY"> <!-- -->Example:<!-- --> </span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">code=2</span></div><div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Only retrieve logs with defined code</p> +</div></div></div></td></tr><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="limit"><span class="sc-kGXeez bcLONg"></span>limit</td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">integer</span><span> <span class="sc-cHGsZl sc-jqCOkK beUper"> <!-- -->>= 1<!-- --> </span></span></div> <div><span class="sc-cHGsZl lpeYvY"> <!-- -->Example:<!-- --> </span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">limit=1</span></div><div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Only retrieve the last x logs</p> +</div></div></div></td></tr><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="ctime"><span class="sc-kGXeez bcLONg"></span>ctime</td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">integer</span><span class="sc-cHGsZl sc-kgAjT hqYVjx"> <!-- --><<!-- -->int64<!-- -->><!-- --> </span></div> <div><span class="sc-cHGsZl lpeYvY"> <!-- -->Example:<!-- --> </span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">ctime=1577726040</span></div><div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Only retrieve logs with a creation time equal or superior to a timestamp</p> +</div></div></div></td></tr><tr class="last undefined"><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="etime"><span class="sc-kGXeez bcLONg"></span>etime</td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">integer</span><span class="sc-cHGsZl sc-kgAjT hqYVjx"> <!-- --><<!-- -->int64<!-- -->><!-- --> </span></div> <div><span class="sc-cHGsZl lpeYvY"> <!-- -->Example:<!-- --> </span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">etime=1577726040</span></div><div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Only retrieve logs of an event time superior to a timestamp</p> +</div></div></div></td></tr></tbody></table></div><div><h3 class="sc-bwCtUz chVREB">Responses</h3><div><div class="sc-eXEjpC bTuXIq"><svg class="sc-jTzLTM hhjjYI" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>200<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>OK</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Unauthorized</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Forbidden</p> +</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-gzOgki fBopsv"><div class="sc-hwwEjo hgxMbQ"><span type="get" class="sc-kPVwWT hzxych http-verb get">get</span><span class="sc-iyvyFf dpRRAO">/log/{token}</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-kfGgVZ bVSqpC"><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Local Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api</span>/log/{token}</div></div></div><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Remote Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/log/{token}</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL eulAfj" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="react-tabs-24" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-25" tabindex="0">200</li><li class="tab-error" role="tab" id="react-tabs-26" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-27">401</li><li class="tab-error" role="tab" id="react-tabs-28" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-29">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-25" aria-labelledby="react-tabs-24"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA lcundD">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Example</span><div class="Dropdown-root sc-kkGfuU sc-fAjcbJ iIEWPt"><div class="Dropdown-control" aria-haspopup="listbox"><div class="Dropdown-placeholder is-selected">Logs</div><div class="Dropdown-arrow-wrapper"><span class="Dropdown-arrow"></span></div></div></div></div><div><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"message"</span>: <span class="token string">"Logs found"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"token"</span>: <span class="token string">"03008486ba50b52e529ff5828d1432e5578dd18bb530c145b133dc902c8cfa6b8aac4d58fffb0c5ed44b943d2acbfb7cd1b18c55fcebce62e51999db460112c7"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"data"</span>: <div class="collapser"></div><span class="token punctuation">[</span><span class="ellipsis"></span><ul class="array collapsible"><li><div class="hoverable collapsed"><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"ctime"</span>: <span class="token number">1577727699</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"etime"</span>: <span class="token number">1577727699</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"id"</span>: <span class="token number">101483</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"instant"</span>: <span class="token number">0</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"data"</span>: <span class="token punctuation">{ }</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"token"</span>: <span class="token string">"03008486ba50b52e529ff5828d1432e5578dd18bb530c145b133dc902c8cfa6b8aac4d58fffb0c5ed44b943d2acbfb7cd1b18c55fcebce62e51999db460112c7"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"code"</span>: <span class="token number">2</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">]</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-27" aria-labelledby="react-tabs-26"></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-29" aria-labelledby="react-tabs-28"></div></div></div></div></div></div><div id="tag/Cron" data-section-id="tag/Cron" class="sc-ifAKCX dluJDj"><div class="sc-gzVnrw eesUPo"><div class="sc-bxivhb cjtbAK"><h1 class="sc-htoDjs WxWXp"><a class="sc-VigVT kGvRyb" href="#tag/Cron"></a>Cron</h1></div></div><div class="sc-bxivhb kFNigF"><div class="sc-jWBwVP sc-iRbamj eHtzbE redoc-markdown "><p>Module aiming to reproduce a cron-like scheduler that can send events to other Gorgone modules.</p> +</div></div></div><div id="tag/Cron/paths/~1core~1cron~1definitions/get" data-section-id="tag/Cron/paths/~1core~1cron~1definitions/get" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-cmthru hcTXxz"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Cron/paths/~1core~1cron~1definitions/get"></a>List definitions<!-- --> </h2><div class="sc-hMFtBS bemheR"><div class="sc-jWBwVP sc-iRbamj eHtzbE"><p>List all cron definitions.</p> +</div></div><div class="sc-bsbRJL PDnUY"><div class="sc-hXRMBi ceJGIt"><h5 class="sc-gqjmRU sc-iQNlJl lmVwfJ">Authorizations: </h5></div><div class="sc-epnACN iYKvkC"><span class="sc-gwVKww jtJYnZ"><span class="sc-eTuwsz eFFwMa"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><div><h3 class="sc-bwCtUz chVREB">Responses</h3><div><div class="sc-eXEjpC bTuXIq"><svg class="sc-jTzLTM hhjjYI" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>200<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>OK</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Unauthorized</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Forbidden</p> +</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-gzOgki fBopsv"><div class="sc-hwwEjo hgxMbQ"><span type="get" class="sc-kPVwWT hzxych http-verb get">get</span><span class="sc-iyvyFf dpRRAO">/core/cron/definitions</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-kfGgVZ bVSqpC"><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Local Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api</span>/core/cron/definitions</div></div></div><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Remote Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/core/cron/definitions</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL eulAfj" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="react-tabs-30" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-31" tabindex="0">200</li><li class="tab-error" role="tab" id="react-tabs-32" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-33">401</li><li class="tab-error" role="tab" id="react-tabs-34" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-35">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-31" aria-labelledby="react-tabs-30"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA lcundD">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Example</span><div class="Dropdown-root sc-kkGfuU sc-fAjcbJ iIEWPt"><div class="Dropdown-control" aria-haspopup="listbox"><div class="Dropdown-placeholder is-selected">Token</div><div class="Dropdown-arrow-wrapper"><span class="Dropdown-arrow"></span></div></div></div></div><div><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"token"</span>: <span class="token string">"1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-33" aria-labelledby="react-tabs-32"></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-35" aria-labelledby="react-tabs-34"></div></div></div></div></div></div><div id="tag/Cron/paths/~1core~1cron~1definitions/post" data-section-id="tag/Cron/paths/~1core~1cron~1definitions/post" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-cmthru hcTXxz"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Cron/paths/~1core~1cron~1definitions/post"></a>Add definitions<!-- --> </h2><div class="sc-hMFtBS bemheR"><div class="sc-jWBwVP sc-iRbamj eHtzbE"><p>Add one or multiple cron definitions to runtime.</p> +</div></div><div class="sc-bsbRJL PDnUY"><div class="sc-hXRMBi ceJGIt"><h5 class="sc-gqjmRU sc-iQNlJl lmVwfJ">Authorizations: </h5></div><div class="sc-epnACN iYKvkC"><span class="sc-gwVKww jtJYnZ"><span class="sc-eTuwsz eFFwMa"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><h5 class="sc-gqjmRU LiUBH">Request Body schema: <span class="sc-hSdWYo hoUoen">application/json</span></h5><div class="sc-jWBwVP sc-iRbamj eHtzbE"></div><div><div class="sc-hMqMXs dDdNtD"> Array </div><div class="sc-gGBfsJ bvBDls"><table class="sc-dxgOiQ eCjbJc"><tbody><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="timespec"><span class="sc-kGXeez bcLONg"></span>timespec<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div> <div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Cron-like time specification</p> +</div></div></div></td></tr><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="id"><span class="sc-kGXeez bcLONg"></span>id<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div> <div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Unique identifier of the cron definition</p> +</div></div></div></td></tr><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="action"><span class="sc-kGXeez bcLONg"></span>action<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div> <div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Action/event to call at job execution</p> +</div></div></div></td></tr><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="parameters"><span class="sc-kGXeez bcLONg"></span>parameters<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">object</span></div> <div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Parameters needed by the called action/event</p> +</div></div></div></td></tr><tr class="last undefined"><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="keep_token"><span class="sc-kGXeez bcLONg"></span>keep_token</td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">boolean</span></div> <div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Boolean to define whether or not the ID of the definition will be used as token for the command</p> +</div></div></div></td></tr></tbody></table></div><div class="sc-kEYyzF cMefLx"></div></div><div><h3 class="sc-bwCtUz chVREB">Responses</h3><div><div class="sc-eXEjpC bTuXIq"><svg class="sc-jTzLTM hhjjYI" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>200<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>OK</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Unauthorized</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Forbidden</p> +</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-gzOgki fBopsv"><div class="sc-hwwEjo hgxMbQ"><span type="post" class="sc-kPVwWT dDFDWJ http-verb post">post</span><span class="sc-iyvyFf dpRRAO">/core/cron/definitions</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-kfGgVZ bVSqpC"><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Local Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api</span>/core/cron/definitions</div></div></div><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Remote Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/core/cron/definitions</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Request samples </h3><div class="sc-eHgmQL eulAfj" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="react-tabs__tab react-tabs__tab--selected" role="tab" id="react-tabs-36" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-37" tabindex="0">Payload</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-37" aria-labelledby="react-tabs-36"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA lcundD">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">[</span><span class="ellipsis"></span><ul class="array collapsible"><li><div class="hoverable "><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"timespec"</span>: <span class="token string">"string"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"id"</span>: <span class="token string">"string"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"action"</span>: <span class="token string">"string"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"parameters"</span>: <span class="token punctuation">{ }</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"keep_token"</span>: <span class="token boolean">true</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">]</span></code></div></div></div></div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL eulAfj" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="react-tabs-38" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-39" tabindex="0">200</li><li class="tab-error" role="tab" id="react-tabs-40" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-41">401</li><li class="tab-error" role="tab" id="react-tabs-42" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-43">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-39" aria-labelledby="react-tabs-38"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA lcundD">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Example</span><div class="Dropdown-root sc-kkGfuU sc-fAjcbJ iIEWPt"><div class="Dropdown-control" aria-haspopup="listbox"><div class="Dropdown-placeholder is-selected">Token</div><div class="Dropdown-arrow-wrapper"><span class="Dropdown-arrow"></span></div></div></div></div><div><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"token"</span>: <span class="token string">"1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-41" aria-labelledby="react-tabs-40"></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-43" aria-labelledby="react-tabs-42"></div></div></div></div></div></div><div id="tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}/get" data-section-id="tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}/get" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-cmthru hcTXxz"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}/get"></a>Get a definition<!-- --> </h2><div class="sc-hMFtBS bemheR"><div class="sc-jWBwVP sc-iRbamj eHtzbE"><p>List cron definition identified by id.</p> +</div></div><div class="sc-bsbRJL PDnUY"><div class="sc-hXRMBi ceJGIt"><h5 class="sc-gqjmRU sc-iQNlJl lmVwfJ">Authorizations: </h5></div><div class="sc-epnACN iYKvkC"><span class="sc-gwVKww jtJYnZ"><span class="sc-eTuwsz eFFwMa"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><div><h5 class="sc-gqjmRU LiUBH">path<!-- --> Parameters</h5><table class="sc-dxgOiQ eCjbJc"><tbody><tr class="last undefined"><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="definition_id"><span class="sc-kGXeez bcLONg"></span>definition_id<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div> <div><span class="sc-cHGsZl lpeYvY"> <!-- -->Example:<!-- --> </span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">broker_stats</span></div><div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>ID of the definition</p> +</div></div></div></td></tr></tbody></table></div><div><h3 class="sc-bwCtUz chVREB">Responses</h3><div><div class="sc-eXEjpC bTuXIq"><svg class="sc-jTzLTM hhjjYI" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>200<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>OK</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Unauthorized</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Forbidden</p> +</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-gzOgki fBopsv"><div class="sc-hwwEjo hgxMbQ"><span type="get" class="sc-kPVwWT hzxych http-verb get">get</span><span class="sc-iyvyFf dpRRAO">/core/cron/definitions/{definition_id}</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-kfGgVZ bVSqpC"><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Local Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api</span>/core/cron/definitions/{definition_id}</div></div></div><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Remote Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/core/cron/definitions/{definition_id}</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL eulAfj" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="react-tabs-44" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-45" tabindex="0">200</li><li class="tab-error" role="tab" id="react-tabs-46" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-47">401</li><li class="tab-error" role="tab" id="react-tabs-48" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-49">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-45" aria-labelledby="react-tabs-44"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA lcundD">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Example</span><div class="Dropdown-root sc-kkGfuU sc-fAjcbJ iIEWPt"><div class="Dropdown-control" aria-haspopup="listbox"><div class="Dropdown-placeholder is-selected">Token</div><div class="Dropdown-arrow-wrapper"><span class="Dropdown-arrow"></span></div></div></div></div><div><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"token"</span>: <span class="token string">"1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-47" aria-labelledby="react-tabs-46"></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-49" aria-labelledby="react-tabs-48"></div></div></div></div></div></div><div id="tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}/patch" data-section-id="tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}/patch" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-cmthru hcTXxz"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}/patch"></a>Update a definition<!-- --> </h2><div class="sc-hMFtBS bemheR"><div class="sc-jWBwVP sc-iRbamj eHtzbE"><p>Update a cron definition.</p> +</div></div><div class="sc-bsbRJL PDnUY"><div class="sc-hXRMBi ceJGIt"><h5 class="sc-gqjmRU sc-iQNlJl lmVwfJ">Authorizations: </h5></div><div class="sc-epnACN iYKvkC"><span class="sc-gwVKww jtJYnZ"><span class="sc-eTuwsz eFFwMa"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><div><h5 class="sc-gqjmRU LiUBH">path<!-- --> Parameters</h5><table class="sc-dxgOiQ eCjbJc"><tbody><tr class="last undefined"><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="definition_id"><span class="sc-kGXeez bcLONg"></span>definition_id<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div> <div><span class="sc-cHGsZl lpeYvY"> <!-- -->Example:<!-- --> </span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">broker_stats</span></div><div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>ID of the definition</p> +</div></div></div></td></tr></tbody></table></div><h5 class="sc-gqjmRU LiUBH">Request Body schema: <span class="sc-hSdWYo hoUoen">application/json</span></h5><div class="sc-jWBwVP sc-iRbamj eHtzbE"></div><table class="sc-dxgOiQ eCjbJc"><tbody><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="timespec"><span class="sc-kGXeez bcLONg"></span>timespec<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div> <div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Cron-like time specification</p> +</div></div></div></td></tr><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="id"><span class="sc-kGXeez bcLONg"></span>id<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div> <div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Unique identifier of the cron definition</p> +</div></div></div></td></tr><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="action"><span class="sc-kGXeez bcLONg"></span>action<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div> <div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Action/event to call at job execution</p> +</div></div></div></td></tr><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="parameters"><span class="sc-kGXeez bcLONg"></span>parameters<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">object</span></div> <div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Parameters needed by the called action/event</p> +</div></div></div></td></tr><tr class="last undefined"><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="keep_token"><span class="sc-kGXeez bcLONg"></span>keep_token</td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">boolean</span></div> <div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Boolean to define whether or not the ID of the definition will be used as token for the command</p> +</div></div></div></td></tr></tbody></table><div><h3 class="sc-bwCtUz chVREB">Responses</h3><div><div class="sc-eXEjpC bTuXIq"><svg class="sc-jTzLTM hhjjYI" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>200<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>OK</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Unauthorized</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Forbidden</p> +</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-gzOgki fBopsv"><div class="sc-hwwEjo hgxMbQ"><span type="patch" class="sc-kPVwWT jUNafm http-verb patch">patch</span><span class="sc-iyvyFf dpRRAO">/core/cron/definitions/{definition_id}</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-kfGgVZ bVSqpC"><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Local Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api</span>/core/cron/definitions/{definition_id}</div></div></div><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Remote Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/core/cron/definitions/{definition_id}</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Request samples </h3><div class="sc-eHgmQL eulAfj" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="react-tabs__tab react-tabs__tab--selected" role="tab" id="react-tabs-50" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-51" tabindex="0">Payload</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-51" aria-labelledby="react-tabs-50"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA lcundD">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"timespec"</span>: <span class="token string">"string"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"id"</span>: <span class="token string">"string"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"action"</span>: <span class="token string">"string"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"parameters"</span>: <span class="token punctuation">{ }</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"keep_token"</span>: <span class="token boolean">true</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL eulAfj" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="react-tabs-52" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-53" tabindex="0">200</li><li class="tab-error" role="tab" id="react-tabs-54" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-55">401</li><li class="tab-error" role="tab" id="react-tabs-56" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-57">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-53" aria-labelledby="react-tabs-52"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA lcundD">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Example</span><div class="Dropdown-root sc-kkGfuU sc-fAjcbJ iIEWPt"><div class="Dropdown-control" aria-haspopup="listbox"><div class="Dropdown-placeholder is-selected">Token</div><div class="Dropdown-arrow-wrapper"><span class="Dropdown-arrow"></span></div></div></div></div><div><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"token"</span>: <span class="token string">"1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-55" aria-labelledby="react-tabs-54"></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-57" aria-labelledby="react-tabs-56"></div></div></div></div></div></div><div id="tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}/delete" data-section-id="tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}/delete" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-cmthru hcTXxz"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}/delete"></a>Delete a definition<!-- --> </h2><div class="sc-hMFtBS bemheR"><div class="sc-jWBwVP sc-iRbamj eHtzbE"><p>Delete a cron definition.</p> +</div></div><div class="sc-bsbRJL PDnUY"><div class="sc-hXRMBi ceJGIt"><h5 class="sc-gqjmRU sc-iQNlJl lmVwfJ">Authorizations: </h5></div><div class="sc-epnACN iYKvkC"><span class="sc-gwVKww jtJYnZ"><span class="sc-eTuwsz eFFwMa"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><div><h5 class="sc-gqjmRU LiUBH">path<!-- --> Parameters</h5><table class="sc-dxgOiQ eCjbJc"><tbody><tr class="last undefined"><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="definition_id"><span class="sc-kGXeez bcLONg"></span>definition_id<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div> <div><span class="sc-cHGsZl lpeYvY"> <!-- -->Example:<!-- --> </span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">broker_stats</span></div><div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>ID of the definition</p> +</div></div></div></td></tr></tbody></table></div><div><h3 class="sc-bwCtUz chVREB">Responses</h3><div><div class="sc-eXEjpC bTuXIq"><svg class="sc-jTzLTM hhjjYI" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>200<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>OK</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Unauthorized</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Forbidden</p> +</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-gzOgki fBopsv"><div class="sc-hwwEjo hgxMbQ"><span type="delete" class="sc-kPVwWT ifUWNX http-verb delete">delete</span><span class="sc-iyvyFf dpRRAO">/core/cron/definitions/{definition_id}</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-kfGgVZ bVSqpC"><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Local Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api</span>/core/cron/definitions/{definition_id}</div></div></div><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Remote Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/core/cron/definitions/{definition_id}</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL eulAfj" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="react-tabs-58" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-59" tabindex="0">200</li><li class="tab-error" role="tab" id="react-tabs-60" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-61">401</li><li class="tab-error" role="tab" id="react-tabs-62" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-63">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-59" aria-labelledby="react-tabs-58"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA lcundD">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Example</span><div class="Dropdown-root sc-kkGfuU sc-fAjcbJ iIEWPt"><div class="Dropdown-control" aria-haspopup="listbox"><div class="Dropdown-placeholder is-selected">Token</div><div class="Dropdown-arrow-wrapper"><span class="Dropdown-arrow"></span></div></div></div></div><div><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"token"</span>: <span class="token string">"1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-61" aria-labelledby="react-tabs-60"></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-63" aria-labelledby="react-tabs-62"></div></div></div></div></div></div><div id="tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}~1status/get" data-section-id="tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}~1status/get" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-cmthru hcTXxz"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}~1status/get"></a>Get a definition status<!-- --> </h2><div class="sc-hMFtBS bemheR"><div class="sc-jWBwVP sc-iRbamj eHtzbE"><p>Get a definition execution status.</p> +</div></div><div class="sc-bsbRJL PDnUY"><div class="sc-hXRMBi ceJGIt"><h5 class="sc-gqjmRU sc-iQNlJl lmVwfJ">Authorizations: </h5></div><div class="sc-epnACN iYKvkC"><span class="sc-gwVKww jtJYnZ"><span class="sc-eTuwsz eFFwMa"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><div><h5 class="sc-gqjmRU LiUBH">path<!-- --> Parameters</h5><table class="sc-dxgOiQ eCjbJc"><tbody><tr class="last undefined"><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="definition_id"><span class="sc-kGXeez bcLONg"></span>definition_id<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div> <div><span class="sc-cHGsZl lpeYvY"> <!-- -->Example:<!-- --> </span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">broker_stats</span></div><div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>ID of the definition</p> +</div></div></div></td></tr></tbody></table></div><div><h3 class="sc-bwCtUz chVREB">Responses</h3><div><div class="sc-eXEjpC bTuXIq"><svg class="sc-jTzLTM hhjjYI" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>200<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>OK</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Unauthorized</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Forbidden</p> +</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-gzOgki fBopsv"><div class="sc-hwwEjo hgxMbQ"><span type="get" class="sc-kPVwWT hzxych http-verb get">get</span><span class="sc-iyvyFf dpRRAO">/core/cron/definitions/{definition_id}/status</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-kfGgVZ bVSqpC"><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Local Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api</span>/core/cron/definitions/{definition_id}/status</div></div></div><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Remote Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/core/cron/definitions/{definition_id}/status</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL eulAfj" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="react-tabs-64" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-65" tabindex="0">200</li><li class="tab-error" role="tab" id="react-tabs-66" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-67">401</li><li class="tab-error" role="tab" id="react-tabs-68" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-69">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-65" aria-labelledby="react-tabs-64"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA lcundD">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Example</span><div class="Dropdown-root sc-kkGfuU sc-fAjcbJ iIEWPt"><div class="Dropdown-control" aria-haspopup="listbox"><div class="Dropdown-placeholder is-selected">Token</div><div class="Dropdown-arrow-wrapper"><span class="Dropdown-arrow"></span></div></div></div></div><div><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"token"</span>: <span class="token string">"1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-67" aria-labelledby="react-tabs-66"></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-69" aria-labelledby="react-tabs-68"></div></div></div></div></div></div><div id="tag/Action" data-section-id="tag/Action" class="sc-ifAKCX dluJDj"><div class="sc-gzVnrw eesUPo"><div class="sc-bxivhb cjtbAK"><h1 class="sc-htoDjs WxWXp"><a class="sc-VigVT kGvRyb" href="#tag/Action"></a>Action</h1></div></div><div class="sc-bxivhb kFNigF"><div class="sc-jWBwVP sc-iRbamj eHtzbE redoc-markdown "><p>Module aiming to execute actions on the server running the Gorgone daemon or remotly using SSH.</p> +</div></div></div><div id="tag/Action/paths/~1core~1action~1command/post" data-section-id="tag/Action/paths/~1core~1action~1command/post" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-cmthru hcTXxz"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Action/paths/~1core~1action~1command/post"></a>Execute one or several command lines<!-- --> </h2><div class="sc-hMFtBS bemheR"><div class="sc-jWBwVP sc-iRbamj eHtzbE"><p>Execute a command or a set of commands on server running Gorgone.</p> +</div></div><div class="sc-bsbRJL PDnUY"><div class="sc-hXRMBi ceJGIt"><h5 class="sc-gqjmRU sc-iQNlJl lmVwfJ">Authorizations: </h5></div><div class="sc-epnACN iYKvkC"><span class="sc-gwVKww jtJYnZ"><span class="sc-eTuwsz eFFwMa"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><h5 class="sc-gqjmRU LiUBH">Request Body schema: <span class="sc-hSdWYo hoUoen">application/json</span></h5><div class="sc-jWBwVP sc-iRbamj eHtzbE"></div><div><div class="sc-hMqMXs dDdNtD"> Array </div><div class="sc-gGBfsJ bvBDls"><table class="sc-dxgOiQ eCjbJc"><tbody><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="command"><span class="sc-kGXeez bcLONg"></span>command<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div> <div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Command to execute</p> +</div></div></div></td></tr><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="timeout"><span class="sc-kGXeez bcLONg"></span>timeout</td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">integer</span></div><div><span class="sc-cHGsZl lpeYvY"> <!-- -->Default:<!-- --> </span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">30</span></div> <div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Time in seconds before a command is considered timed out</p> +</div></div></div></td></tr><tr class="last undefined"><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="continue_on_error"><span class="sc-kGXeez bcLONg"></span>continue_on_error</td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">boolean</span></div><div><span class="sc-cHGsZl lpeYvY"> <!-- -->Default:<!-- --> </span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">false</span></div> <div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Behaviour in case of execution issue</p> +</div></div></div></td></tr></tbody></table></div><div class="sc-kEYyzF cMefLx"></div></div><div><h3 class="sc-bwCtUz chVREB">Responses</h3><div><div class="sc-eXEjpC bTuXIq"><svg class="sc-jTzLTM hhjjYI" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>200<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>OK</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Unauthorized</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Forbidden</p> +</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-gzOgki fBopsv"><div class="sc-hwwEjo hgxMbQ"><span type="post" class="sc-kPVwWT dDFDWJ http-verb post">post</span><span class="sc-iyvyFf dpRRAO">/core/action/command</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-kfGgVZ bVSqpC"><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Local Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api</span>/core/action/command</div></div></div><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Remote Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/core/action/command</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Request samples </h3><div class="sc-eHgmQL eulAfj" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="react-tabs__tab react-tabs__tab--selected" role="tab" id="react-tabs-70" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-71" tabindex="0">Payload</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-71" aria-labelledby="react-tabs-70"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA lcundD">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">[</span><span class="ellipsis"></span><ul class="array collapsible"><li><div class="hoverable "><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"command"</span>: <span class="token string">"echo data > /tmp/date.log"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"timeout"</span>: <span class="token number">5</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"continue_on_error"</span>: <span class="token boolean">true</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">]</span></code></div></div></div></div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL eulAfj" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="react-tabs-72" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-73" tabindex="0">200</li><li class="tab-error" role="tab" id="react-tabs-74" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-75">401</li><li class="tab-error" role="tab" id="react-tabs-76" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-77">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-73" aria-labelledby="react-tabs-72"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA lcundD">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Example</span><div class="Dropdown-root sc-kkGfuU sc-fAjcbJ iIEWPt"><div class="Dropdown-control" aria-haspopup="listbox"><div class="Dropdown-placeholder is-selected">Token</div><div class="Dropdown-arrow-wrapper"><span class="Dropdown-arrow"></span></div></div></div></div><div><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"token"</span>: <span class="token string">"1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-75" aria-labelledby="react-tabs-74"></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-77" aria-labelledby="react-tabs-76"></div></div></div></div></div></div><div id="tag/Engine" data-section-id="tag/Engine" class="sc-ifAKCX dluJDj"><div class="sc-gzVnrw eesUPo"><div class="sc-bxivhb cjtbAK"><h1 class="sc-htoDjs WxWXp"><a class="sc-VigVT kGvRyb" href="#tag/Engine"></a>Engine</h1></div></div><div class="sc-bxivhb kFNigF"><div class="sc-jWBwVP sc-iRbamj eHtzbE redoc-markdown "><p>Module aiming to provide a bridge to communicate with Centreon Engine daemon.</p> +</div></div></div><div id="tag/Engine/paths/~1centreon~1engine~1command/post" data-section-id="tag/Engine/paths/~1centreon~1engine~1command/post" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-cmthru hcTXxz"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Engine/paths/~1centreon~1engine~1command/post"></a>Send one or several external commands<!-- --> </h2><div class="sc-hMFtBS bemheR"><div class="sc-jWBwVP sc-iRbamj eHtzbE"><p>Send an external command or a set of external commands to a running Centreon Engine instance using command file pipe. This method needs the commands to be preformatted as Nagios external commands format.</p> -</div></div><div class="sc-jtRfpW geWpKA"><div class="sc-lhVmIH dVvUxe"><h5 class="sc-gqjmRU sc-elJkPf jGRUDj">Authorizations: </h5></div><div class="sc-bYSBpT fKHsnH"><span class="sc-btzYZH hQBRTt"><span class="sc-jwKygS bnFPhO"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><h5 class="sc-gqjmRU LiUBH">Request Body schema: <span class="sc-hSdWYo hoUoen">application/json</span></h5><div class="sc-jWBwVP sc-iRbamj flfxUM"></div><table class="sc-dxgOiQ eCjbJc"><tbody><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="command_file"><span class="sc-kGXeez bcLONg"></span>command_file</td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div> <div><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Path to the Centreon Engine command file pipe</p> -</div></div></div></td></tr><tr class="last undefined"><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="command"><span class="sc-kGXeez bcLONg"></span>command</td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc">Array of </span><span class="sc-cHGsZl sc-kgAjT hqYVjx">strings</span></div> <div><div class="sc-jWBwVP sc-iRbamj gDsWLk"></div></div></div></td></tr></tbody></table><div><h3 class="sc-eXEjpC espozG"> Responses </h3><div><div class="sc-kPVwWT fDvFMp"><svg class="sc-jTzLTM NifDa" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>200<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>OK</p> -</span></div></div><div><div class="sc-kPVwWT byLrBg"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>Unauthorized</p> -</span></div></div><div><div class="sc-kPVwWT byLrBg"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>Forbidden</p> -</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-kTUwUJ ldTMcP"><div class="sc-gxMtzJ dHLKeu"><span type="post" class="sc-dfVpRl bNYCAJ http-verb post"> <!-- -->post</span> <span class="sc-dqBHgY iENVAs">/centreon/engine/command</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-gzOgki lkvpfX"><div class="sc-iyvyFf eftLSo"><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Local Gorgone instance</p> -</div><div><div class="sc-hwwEjo bwgXFh"><span>{protocol}://{server}:{port}/api</span>/centreon/engine/command</div></div></div><div class="sc-iyvyFf eftLSo"><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Remote Gorgone instance</p> -</div><div><div class="sc-hwwEjo bwgXFh"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/centreon/engine/command</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Request samples </h3><div class="sc-eHgmQL irpqyy" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="react-tabs__tab react-tabs__tab--selected" role="tab" id="react-tabs-78" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-79" tabindex="0">Payload</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-79" aria-labelledby="react-tabs-78"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA gzAoUb">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"command_file"</span>: <span class="token string">"/var/lib/centreon-engine/rw/centengine.cmd"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"command"</span>: <div class="collapser"></div><span class="token punctuation">[</span><span class="ellipsis"></span><ul class="array collapsible"><li><div class="hoverable collapsed"><span class="token string">"[653284380] SCHEDULE_SVC_CHECK;host1;service1;653284380"</span></div></li></ul><span class="token punctuation">]</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL irpqyy" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="react-tabs-80" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-81" tabindex="0">200</li><li class="tab-error" role="tab" id="react-tabs-82" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-83">401</li><li class="tab-error" role="tab" id="react-tabs-84" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-85">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-81" aria-labelledby="react-tabs-80"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA gzAoUb">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Example</span><div class="Dropdown-root sc-kkGfuU sc-fAjcbJ hrtKLV"><div class="Dropdown-control" aria-haspopup="listbox"><div class="Dropdown-placeholder is-selected">Token</div><div class="Dropdown-arrow-wrapper"><span class="Dropdown-arrow"></span></div></div></div></div><div><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"token"</span>: <span class="token string">"1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-83" aria-labelledby="react-tabs-82"></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-85" aria-labelledby="react-tabs-84"></div></div></div></div></div></div><div id="tag/Statistics" data-section-id="tag/Statistics" class="sc-ifAKCX dluJDj"><div class="sc-gzVnrw eesUPo"><div class="sc-bxivhb cjtbAK"><h1 class="sc-htoDjs WxWXp"><a class="sc-VigVT kGvRyb" href="#tag/Statistics"></a>Statistics</h1></div></div><div class="sc-bxivhb kFNigF"><div class="sc-jWBwVP sc-iRbamj flfxUM redoc-markdown "><p>Module aiming to deal with statistics collection of Centreon Engine and Broker.</p> -</div></div></div><div id="tag/Statistics/paths/~1centreon~1statistics~1broker/get" data-section-id="tag/Statistics/paths/~1centreon~1statistics~1broker/get" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-ibxdXY bSFXlp"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Statistics/paths/~1centreon~1statistics~1broker/get"></a>Launch Broker statistics collection<!-- --> </h2><div class="sc-RefOD boajtD"><div class="sc-jWBwVP sc-iRbamj flfxUM"><p>Launch Broker statistics collection and store the result on disk.</p> -</div></div><div class="sc-jtRfpW geWpKA"><div class="sc-lhVmIH dVvUxe"><h5 class="sc-gqjmRU sc-elJkPf jGRUDj">Authorizations: </h5></div><div class="sc-bYSBpT fKHsnH"><span class="sc-btzYZH hQBRTt"><span class="sc-jwKygS bnFPhO"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><div><h3 class="sc-eXEjpC espozG"> Responses </h3><div><div class="sc-kPVwWT fDvFMp"><svg class="sc-jTzLTM NifDa" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>200<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>OK</p> -</span></div></div><div><div class="sc-kPVwWT byLrBg"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>Unauthorized</p> -</span></div></div><div><div class="sc-kPVwWT byLrBg"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>Forbidden</p> -</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-kTUwUJ ldTMcP"><div class="sc-gxMtzJ dHLKeu"><span type="get" class="sc-dfVpRl jBjYbV http-verb get"> <!-- -->get</span> <span class="sc-dqBHgY iENVAs">/centreon/statistics/broker</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-gzOgki lkvpfX"><div class="sc-iyvyFf eftLSo"><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Local Gorgone instance</p> -</div><div><div class="sc-hwwEjo bwgXFh"><span>{protocol}://{server}:{port}/api</span>/centreon/statistics/broker</div></div></div><div class="sc-iyvyFf eftLSo"><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Remote Gorgone instance</p> -</div><div><div class="sc-hwwEjo bwgXFh"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/centreon/statistics/broker</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL irpqyy" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="react-tabs-86" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-87" tabindex="0">200</li><li class="tab-error" role="tab" id="react-tabs-88" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-89">401</li><li class="tab-error" role="tab" id="react-tabs-90" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-91">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-87" aria-labelledby="react-tabs-86"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA gzAoUb">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Example</span><div class="Dropdown-root sc-kkGfuU sc-fAjcbJ hrtKLV"><div class="Dropdown-control" aria-haspopup="listbox"><div class="Dropdown-placeholder is-selected">Token</div><div class="Dropdown-arrow-wrapper"><span class="Dropdown-arrow"></span></div></div></div></div><div><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"token"</span>: <span class="token string">"1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-89" aria-labelledby="react-tabs-88"></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-91" aria-labelledby="react-tabs-90"></div></div></div></div></div></div><div id="tag/Statistics/paths/~1centreon~1statistics~1broker~1{monitoring_server_id}/get" data-section-id="tag/Statistics/paths/~1centreon~1statistics~1broker~1{monitoring_server_id}/get" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-ibxdXY bSFXlp"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Statistics/paths/~1centreon~1statistics~1broker~1{monitoring_server_id}/get"></a>Launch Broker statistics collection of a specific monitoring server<!-- --> </h2><div class="sc-RefOD boajtD"><div class="sc-jWBwVP sc-iRbamj flfxUM"><p>Launch Broker statistics collection and store the result on disk.</p> -</div></div><div class="sc-jtRfpW geWpKA"><div class="sc-lhVmIH dVvUxe"><h5 class="sc-gqjmRU sc-elJkPf jGRUDj">Authorizations: </h5></div><div class="sc-bYSBpT fKHsnH"><span class="sc-btzYZH hQBRTt"><span class="sc-jwKygS bnFPhO"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><div><h5 class="sc-gqjmRU LiUBH">path<!-- --> Parameters</h5><table class="sc-dxgOiQ eCjbJc"><tbody><tr class="last undefined"><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="monitoring_server_id"><span class="sc-kGXeez bcLONg"></span>monitoring_server_id<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">integer</span></div> <div><span class="sc-cHGsZl lpeYvY"> <!-- -->Example:<!-- --> </span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">2</span></div><div><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>ID of the monitoring server</p> -</div></div></div></td></tr></tbody></table></div><div><h3 class="sc-eXEjpC espozG"> Responses </h3><div><div class="sc-kPVwWT fDvFMp"><svg class="sc-jTzLTM NifDa" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>200<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>OK</p> -</span></div></div><div><div class="sc-kPVwWT byLrBg"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>Unauthorized</p> -</span></div></div><div><div class="sc-kPVwWT byLrBg"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>Forbidden</p> -</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-kTUwUJ ldTMcP"><div class="sc-gxMtzJ dHLKeu"><span type="get" class="sc-dfVpRl jBjYbV http-verb get"> <!-- -->get</span> <span class="sc-dqBHgY iENVAs">/centreon/statistics/broker/{monitoring_server_id}</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-gzOgki lkvpfX"><div class="sc-iyvyFf eftLSo"><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Local Gorgone instance</p> -</div><div><div class="sc-hwwEjo bwgXFh"><span>{protocol}://{server}:{port}/api</span>/centreon/statistics/broker/{monitoring_server_id}</div></div></div><div class="sc-iyvyFf eftLSo"><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Remote Gorgone instance</p> -</div><div><div class="sc-hwwEjo bwgXFh"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/centreon/statistics/broker/{monitoring_server_id}</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL irpqyy" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="react-tabs-92" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-93" tabindex="0">200</li><li class="tab-error" role="tab" id="react-tabs-94" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-95">401</li><li class="tab-error" role="tab" id="react-tabs-96" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-97">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-93" aria-labelledby="react-tabs-92"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA gzAoUb">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Example</span><div class="Dropdown-root sc-kkGfuU sc-fAjcbJ hrtKLV"><div class="Dropdown-control" aria-haspopup="listbox"><div class="Dropdown-placeholder is-selected">Token</div><div class="Dropdown-arrow-wrapper"><span class="Dropdown-arrow"></span></div></div></div></div><div><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"token"</span>: <span class="token string">"1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-95" aria-labelledby="react-tabs-94"></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-97" aria-labelledby="react-tabs-96"></div></div></div></div></div></div><div id="tag/Autodiscovery" data-section-id="tag/Autodiscovery" class="sc-ifAKCX dluJDj"><div class="sc-gzVnrw eesUPo"><div class="sc-bxivhb cjtbAK"><h1 class="sc-htoDjs WxWXp"><a class="sc-VigVT kGvRyb" href="#tag/Autodiscovery"></a>Autodiscovery</h1></div></div><div class="sc-bxivhb kFNigF"><div class="sc-jWBwVP sc-iRbamj flfxUM redoc-markdown "><p>Module aiming to extend Centreon Autodiscovery server functionalities.</p> -</div></div></div><div id="tag/Autodiscovery/paths/~1centreon~1autodiscovery~1job/post" data-section-id="tag/Autodiscovery/paths/~1centreon~1autodiscovery~1job/post" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-ibxdXY bSFXlp"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Autodiscovery/paths/~1centreon~1autodiscovery~1job/post"></a>Add a discovery job<!-- --> </h2><div class="sc-RefOD boajtD"><div class="sc-jWBwVP sc-iRbamj flfxUM"><p>Add one Centreon Autodiscovery job.</p> -</div></div><div class="sc-jtRfpW geWpKA"><div class="sc-lhVmIH dVvUxe"><h5 class="sc-gqjmRU sc-elJkPf jGRUDj">Authorizations: </h5></div><div class="sc-bYSBpT fKHsnH"><span class="sc-btzYZH hQBRTt"><span class="sc-jwKygS bnFPhO"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><h5 class="sc-gqjmRU LiUBH">Request Body schema: <span class="sc-hSdWYo hoUoen">application/json</span></h5><div class="sc-jWBwVP sc-iRbamj flfxUM"></div><table class="sc-dxgOiQ eCjbJc"><tbody><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="id"><span class="sc-kGXeez bcLONg"></span>id</td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div> <div><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Identifier of the job (random if empty)</p> -</div></div></div></td></tr><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="execution_mode"><span class="sc-kGXeez bcLONg"></span>execution_mode<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div> <div><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Execution mode of this job ('0': execute immediately, '1': schedule with cron)</p> -</div></div></div></td></tr><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="command"><span class="sc-kGXeez bcLONg"></span>command<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div> <div><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Command line to execute to perform the discovery</p> -</div></div></div></td></tr><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="timeout"><span class="sc-kGXeez bcLONg"></span>timeout</td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">integer</span></div> <div><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Time in seconds before the command is considered timed out</p> -</div></div></div></td></tr><tr class="last undefined"><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="target"><span class="sc-kGXeez bcLONg"></span>target<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">integer</span></div> <div><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Identifier of the target on which to execute the command</p> -</div></div></div></td></tr></tbody></table><div><h3 class="sc-eXEjpC espozG"> Responses </h3><div><div class="sc-kPVwWT fDvFMp"><svg class="sc-jTzLTM NifDa" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>200<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>OK</p> -</span></div></div><div><div class="sc-kPVwWT byLrBg"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>Unauthorized</p> -</span></div></div><div><div class="sc-kPVwWT byLrBg"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG evvbxn"><p>Forbidden</p> -</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-kTUwUJ ldTMcP"><div class="sc-gxMtzJ dHLKeu"><span type="post" class="sc-dfVpRl bNYCAJ http-verb post"> <!-- -->post</span> <span class="sc-dqBHgY iENVAs">/centreon/autodiscovery/job</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-gzOgki lkvpfX"><div class="sc-iyvyFf eftLSo"><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Local Gorgone instance</p> -</div><div><div class="sc-hwwEjo bwgXFh"><span>{protocol}://{server}:{port}/api</span>/centreon/autodiscovery/job</div></div></div><div class="sc-iyvyFf eftLSo"><div class="sc-jWBwVP sc-iRbamj gDsWLk"><p>Remote Gorgone instance</p> -</div><div><div class="sc-hwwEjo bwgXFh"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/centreon/autodiscovery/job</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Request samples </h3><div class="sc-eHgmQL irpqyy" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="react-tabs__tab react-tabs__tab--selected" role="tab" id="react-tabs-98" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-99" tabindex="0">Payload</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-99" aria-labelledby="react-tabs-98"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA gzAoUb">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"id"</span>: <span class="token string">"Job-SNMP-10.1.2.3"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"execution_mode"</span>: <span class="token number">0</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"command"</span>: <span class="token string">"perl /usr/lib/centreon/plugins/centreon_generic_snmp.pl --plugin=os::linux::local::plugin --mode=discovery-snmp --subnet='10.1.2.3/24' --snmp-port='161' --snmp-version='2c' --snmp-community='public'\n"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"timeout"</span>: <span class="token number">300</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"target"</span>: <span class="token number">2</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL irpqyy" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="react-tabs-100" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-101" tabindex="0">200</li><li class="tab-error" role="tab" id="react-tabs-102" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-103">401</li><li class="tab-error" role="tab" id="react-tabs-104" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-105">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-101" aria-labelledby="react-tabs-100"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA gzAoUb">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Example</span><div class="Dropdown-root sc-kkGfuU sc-fAjcbJ hrtKLV"><div class="Dropdown-control" aria-haspopup="listbox"><div class="Dropdown-placeholder is-selected">Token</div><div class="Dropdown-arrow-wrapper"><span class="Dropdown-arrow"></span></div></div></div></div><div><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"token"</span>: <span class="token string">"1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-103" aria-labelledby="react-tabs-102"></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-105" aria-labelledby="react-tabs-104"></div></div></div></div></div></div></div><div class="sc-iuJeZd fLUKgj"></div></div></div> +</div></div><div class="sc-bsbRJL PDnUY"><div class="sc-hXRMBi ceJGIt"><h5 class="sc-gqjmRU sc-iQNlJl lmVwfJ">Authorizations: </h5></div><div class="sc-epnACN iYKvkC"><span class="sc-gwVKww jtJYnZ"><span class="sc-eTuwsz eFFwMa"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><h5 class="sc-gqjmRU LiUBH">Request Body schema: <span class="sc-hSdWYo hoUoen">application/json</span></h5><div class="sc-jWBwVP sc-iRbamj eHtzbE"></div><table class="sc-dxgOiQ eCjbJc"><tbody><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="command_file"><span class="sc-kGXeez bcLONg"></span>command_file</td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div> <div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Path to the Centreon Engine command file pipe</p> +</div></div></div></td></tr><tr class="last undefined"><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="command"><span class="sc-kGXeez bcLONg"></span>command</td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc">Array of </span><span class="sc-cHGsZl sc-kgAjT hqYVjx">strings</span></div> <div><div class="sc-jWBwVP sc-iRbamj kjegA"></div></div></div></td></tr></tbody></table><div><h3 class="sc-bwCtUz chVREB">Responses</h3><div><div class="sc-eXEjpC bTuXIq"><svg class="sc-jTzLTM hhjjYI" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>200<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>OK</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Unauthorized</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Forbidden</p> +</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-gzOgki fBopsv"><div class="sc-hwwEjo hgxMbQ"><span type="post" class="sc-kPVwWT dDFDWJ http-verb post">post</span><span class="sc-iyvyFf dpRRAO">/centreon/engine/command</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-kfGgVZ bVSqpC"><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Local Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api</span>/centreon/engine/command</div></div></div><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Remote Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/centreon/engine/command</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Request samples </h3><div class="sc-eHgmQL eulAfj" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="react-tabs__tab react-tabs__tab--selected" role="tab" id="react-tabs-78" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-79" tabindex="0">Payload</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-79" aria-labelledby="react-tabs-78"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA lcundD">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"command_file"</span>: <span class="token string">"/var/lib/centreon-engine/rw/centengine.cmd"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"command"</span>: <div class="collapser"></div><span class="token punctuation">[</span><span class="ellipsis"></span><ul class="array collapsible"><li><div class="hoverable collapsed"><span class="token string">"[653284380] SCHEDULE_SVC_CHECK;host1;service1;653284380"</span></div></li></ul><span class="token punctuation">]</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL eulAfj" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="react-tabs-80" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-81" tabindex="0">200</li><li class="tab-error" role="tab" id="react-tabs-82" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-83">401</li><li class="tab-error" role="tab" id="react-tabs-84" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-85">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-81" aria-labelledby="react-tabs-80"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA lcundD">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Example</span><div class="Dropdown-root sc-kkGfuU sc-fAjcbJ iIEWPt"><div class="Dropdown-control" aria-haspopup="listbox"><div class="Dropdown-placeholder is-selected">Token</div><div class="Dropdown-arrow-wrapper"><span class="Dropdown-arrow"></span></div></div></div></div><div><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"token"</span>: <span class="token string">"1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-83" aria-labelledby="react-tabs-82"></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-85" aria-labelledby="react-tabs-84"></div></div></div></div></div></div><div id="tag/Statistics" data-section-id="tag/Statistics" class="sc-ifAKCX dluJDj"><div class="sc-gzVnrw eesUPo"><div class="sc-bxivhb cjtbAK"><h1 class="sc-htoDjs WxWXp"><a class="sc-VigVT kGvRyb" href="#tag/Statistics"></a>Statistics</h1></div></div><div class="sc-bxivhb kFNigF"><div class="sc-jWBwVP sc-iRbamj eHtzbE redoc-markdown "><p>Module aiming to deal with statistics collection of Centreon Engine and Broker.</p> +</div></div></div><div id="tag/Statistics/paths/~1centreon~1statistics~1broker/get" data-section-id="tag/Statistics/paths/~1centreon~1statistics~1broker/get" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-cmthru hcTXxz"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Statistics/paths/~1centreon~1statistics~1broker/get"></a>Launch Broker statistics collection<!-- --> </h2><div class="sc-hMFtBS bemheR"><div class="sc-jWBwVP sc-iRbamj eHtzbE"><p>Launch Broker statistics collection and store the result on disk.</p> +</div></div><div class="sc-bsbRJL PDnUY"><div class="sc-hXRMBi ceJGIt"><h5 class="sc-gqjmRU sc-iQNlJl lmVwfJ">Authorizations: </h5></div><div class="sc-epnACN iYKvkC"><span class="sc-gwVKww jtJYnZ"><span class="sc-eTuwsz eFFwMa"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><div><h3 class="sc-bwCtUz chVREB">Responses</h3><div><div class="sc-eXEjpC bTuXIq"><svg class="sc-jTzLTM hhjjYI" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>200<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>OK</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Unauthorized</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Forbidden</p> +</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-gzOgki fBopsv"><div class="sc-hwwEjo hgxMbQ"><span type="get" class="sc-kPVwWT hzxych http-verb get">get</span><span class="sc-iyvyFf dpRRAO">/centreon/statistics/broker</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-kfGgVZ bVSqpC"><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Local Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api</span>/centreon/statistics/broker</div></div></div><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Remote Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/centreon/statistics/broker</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL eulAfj" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="react-tabs-86" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-87" tabindex="0">200</li><li class="tab-error" role="tab" id="react-tabs-88" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-89">401</li><li class="tab-error" role="tab" id="react-tabs-90" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-91">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-87" aria-labelledby="react-tabs-86"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA lcundD">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Example</span><div class="Dropdown-root sc-kkGfuU sc-fAjcbJ iIEWPt"><div class="Dropdown-control" aria-haspopup="listbox"><div class="Dropdown-placeholder is-selected">Token</div><div class="Dropdown-arrow-wrapper"><span class="Dropdown-arrow"></span></div></div></div></div><div><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"token"</span>: <span class="token string">"1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-89" aria-labelledby="react-tabs-88"></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-91" aria-labelledby="react-tabs-90"></div></div></div></div></div></div><div id="tag/Statistics/paths/~1centreon~1statistics~1broker~1{monitoring_server_id}/get" data-section-id="tag/Statistics/paths/~1centreon~1statistics~1broker~1{monitoring_server_id}/get" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-cmthru hcTXxz"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Statistics/paths/~1centreon~1statistics~1broker~1{monitoring_server_id}/get"></a>Launch Broker statistics collection of a specific monitoring server<!-- --> </h2><div class="sc-hMFtBS bemheR"><div class="sc-jWBwVP sc-iRbamj eHtzbE"><p>Launch Broker statistics collection and store the result on disk.</p> +</div></div><div class="sc-bsbRJL PDnUY"><div class="sc-hXRMBi ceJGIt"><h5 class="sc-gqjmRU sc-iQNlJl lmVwfJ">Authorizations: </h5></div><div class="sc-epnACN iYKvkC"><span class="sc-gwVKww jtJYnZ"><span class="sc-eTuwsz eFFwMa"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><div><h5 class="sc-gqjmRU LiUBH">path<!-- --> Parameters</h5><table class="sc-dxgOiQ eCjbJc"><tbody><tr class="last undefined"><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="monitoring_server_id"><span class="sc-kGXeez bcLONg"></span>monitoring_server_id<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">integer</span></div> <div><span class="sc-cHGsZl lpeYvY"> <!-- -->Example:<!-- --> </span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">2</span></div><div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>ID of the monitoring server</p> +</div></div></div></td></tr></tbody></table></div><div><h3 class="sc-bwCtUz chVREB">Responses</h3><div><div class="sc-eXEjpC bTuXIq"><svg class="sc-jTzLTM hhjjYI" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>200<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>OK</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Unauthorized</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Forbidden</p> +</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-gzOgki fBopsv"><div class="sc-hwwEjo hgxMbQ"><span type="get" class="sc-kPVwWT hzxych http-verb get">get</span><span class="sc-iyvyFf dpRRAO">/centreon/statistics/broker/{monitoring_server_id}</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-kfGgVZ bVSqpC"><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Local Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api</span>/centreon/statistics/broker/{monitoring_server_id}</div></div></div><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Remote Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/centreon/statistics/broker/{monitoring_server_id}</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL eulAfj" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="react-tabs-92" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-93" tabindex="0">200</li><li class="tab-error" role="tab" id="react-tabs-94" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-95">401</li><li class="tab-error" role="tab" id="react-tabs-96" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-97">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-93" aria-labelledby="react-tabs-92"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA lcundD">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Example</span><div class="Dropdown-root sc-kkGfuU sc-fAjcbJ iIEWPt"><div class="Dropdown-control" aria-haspopup="listbox"><div class="Dropdown-placeholder is-selected">Token</div><div class="Dropdown-arrow-wrapper"><span class="Dropdown-arrow"></span></div></div></div></div><div><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"token"</span>: <span class="token string">"1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-95" aria-labelledby="react-tabs-94"></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-97" aria-labelledby="react-tabs-96"></div></div></div></div></div></div><div id="tag/Autodiscovery" data-section-id="tag/Autodiscovery" class="sc-ifAKCX dluJDj"><div class="sc-gzVnrw eesUPo"><div class="sc-bxivhb cjtbAK"><h1 class="sc-htoDjs WxWXp"><a class="sc-VigVT kGvRyb" href="#tag/Autodiscovery"></a>Autodiscovery</h1></div></div><div class="sc-bxivhb kFNigF"><div class="sc-jWBwVP sc-iRbamj eHtzbE redoc-markdown "><p>Module aiming to extend Centreon Autodiscovery server functionalities.</p> +</div></div></div><div id="tag/Autodiscovery/paths/~1centreon~1autodiscovery~1hosts/post" data-section-id="tag/Autodiscovery/paths/~1centreon~1autodiscovery~1hosts/post" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-cmthru hcTXxz"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Autodiscovery/paths/~1centreon~1autodiscovery~1hosts/post"></a>Add a host discovery job<!-- --> </h2><div class="sc-hMFtBS bemheR"><div class="sc-jWBwVP sc-iRbamj eHtzbE"><p>Add one Centreon Autodiscovery job to discover hosts.</p> +</div></div><div class="sc-bsbRJL PDnUY"><div class="sc-hXRMBi ceJGIt"><h5 class="sc-gqjmRU sc-iQNlJl lmVwfJ">Authorizations: </h5></div><div class="sc-epnACN iYKvkC"><span class="sc-gwVKww jtJYnZ"><span class="sc-eTuwsz eFFwMa"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><h5 class="sc-gqjmRU LiUBH">Request Body schema: <span class="sc-hSdWYo hoUoen">application/json</span></h5><div class="sc-jWBwVP sc-iRbamj eHtzbE"></div><table class="sc-dxgOiQ eCjbJc"><tbody><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="job_id"><span class="sc-kGXeez bcLONg"></span>job_id<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">integer</span></div> <div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>ID of the Host Discovery job</p> +</div></div></div></td></tr><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="target"><span class="sc-kGXeez bcLONg"></span>target<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">integer</span></div> <div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Identifier of the target on which to execute the command</p> +</div></div></div></td></tr><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="command_line"><span class="sc-kGXeez bcLONg"></span>command_line<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div> <div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Command line to execute to perform the discovery</p> +</div></div></div></td></tr><tr><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="timeout"><span class="sc-kGXeez bcLONg"></span>timeout</td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">integer</span></div> <div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Time in seconds before the command is considered timed out</p> +</div></div></div></td></tr><tr><td class="sc-cSHVUG sc-chPdSV sc-kjoXOD iNoDtm" kind="field" title="execution"><span class="sc-kGXeez bcLONg"></span>execution<svg class="sc-jTzLTM gBhLRG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">object</span></div> <div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Execution mode of this job ('0': execute immediately, '1': schedule with cron)</p> +</div></div></div></td></tr><tr class="last undefined"><td class="sc-cSHVUG sc-chPdSV sc-kjoXOD iNoDtm" kind="field" title="post_execution"><span class="sc-kGXeez bcLONg"></span>post_execution<svg class="sc-jTzLTM gBhLRG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">object</span></div> <div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Post-execution settings</p> +</div></div></div></td></tr></tbody></table><div><h3 class="sc-bwCtUz chVREB">Responses</h3><div><div class="sc-eXEjpC bTuXIq"><svg class="sc-jTzLTM hhjjYI" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>200<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>OK</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Unauthorized</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Forbidden</p> +</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-gzOgki fBopsv"><div class="sc-hwwEjo hgxMbQ"><span type="post" class="sc-kPVwWT dDFDWJ http-verb post">post</span><span class="sc-iyvyFf dpRRAO">/centreon/autodiscovery/hosts</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-kfGgVZ bVSqpC"><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Local Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api</span>/centreon/autodiscovery/hosts</div></div></div><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Remote Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/centreon/autodiscovery/hosts</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Request samples </h3><div class="sc-eHgmQL eulAfj" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="react-tabs__tab react-tabs__tab--selected" role="tab" id="react-tabs-98" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-99" tabindex="0">Payload</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-99" aria-labelledby="react-tabs-98"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA lcundD">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"job_id"</span>: <span class="token number">14</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"target"</span>: <span class="token number">2</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"command_line"</span>: <span class="token string">"perl /usr/lib/centreon/plugins/centreon_generic_snmp.pl --plugin=os::linux::local::plugin --mode=discovery-snmp --subnet='10.1.2.3/24' --snmp-port='161' --snmp-version='2c' --snmp-community='public'"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"timeout"</span>: <span class="token number">300</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"execution"</span>: <div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"mode"</span>: <span class="token number">0</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"parameters"</span>: <div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"cron_definition"</span>: <span class="token string">"*/10 * * * *"</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">}</span><span class="token punctuation">,</span></div></li><li><div class="hoverable "><span class="property token string">"post_execution"</span>: <div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"commands"</span>: <div class="collapser"></div><span class="token punctuation">[</span><span class="ellipsis"></span><ul class="array collapsible"><li><div class="hoverable collapsed"><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable collapsed"><span class="property token string">"action"</span>: <span class="token string">"COMMAND"</span><span class="token punctuation">,</span></div></li><li><div class="hoverable collapsed"><span class="property token string">"command_line"</span>: <span class="token string">"/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --job-id=14"</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">]</span></div></li></ul><span class="token punctuation">}</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL eulAfj" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="react-tabs-100" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-101" tabindex="0">200</li><li class="tab-error" role="tab" id="react-tabs-102" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-103">401</li><li class="tab-error" role="tab" id="react-tabs-104" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-105">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-101" aria-labelledby="react-tabs-100"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA lcundD">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Example</span><div class="Dropdown-root sc-kkGfuU sc-fAjcbJ iIEWPt"><div class="Dropdown-control" aria-haspopup="listbox"><div class="Dropdown-placeholder is-selected">Token</div><div class="Dropdown-arrow-wrapper"><span class="Dropdown-arrow"></span></div></div></div></div><div><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"token"</span>: <span class="token string">"1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-103" aria-labelledby="react-tabs-102"></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-105" aria-labelledby="react-tabs-104"></div></div></div></div></div></div><div id="tag/Autodiscovery/paths/~1centreon~1autodiscovery~1hosts~1{job_id}~1schedule/get" data-section-id="tag/Autodiscovery/paths/~1centreon~1autodiscovery~1hosts~1{job_id}~1schedule/get" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-cmthru hcTXxz"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Autodiscovery/paths/~1centreon~1autodiscovery~1hosts~1{job_id}~1schedule/get"></a>Launch a host discovery job<!-- --> </h2><div class="sc-hMFtBS bemheR"><div class="sc-jWBwVP sc-iRbamj eHtzbE"><p>Launch a host discovery job identified by id (even if in cron mode).</p> +</div></div><div class="sc-bsbRJL PDnUY"><div class="sc-hXRMBi ceJGIt"><h5 class="sc-gqjmRU sc-iQNlJl lmVwfJ">Authorizations: </h5></div><div class="sc-epnACN iYKvkC"><span class="sc-gwVKww jtJYnZ"><span class="sc-eTuwsz eFFwMa"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><div><h5 class="sc-gqjmRU LiUBH">path<!-- --> Parameters</h5><table class="sc-dxgOiQ eCjbJc"><tbody><tr class="last undefined"><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="job_id"><span class="sc-kGXeez bcLONg"></span>job_id<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">integer</span></div> <div><span class="sc-cHGsZl lpeYvY"> <!-- -->Example:<!-- --> </span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">2</span></div><div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>ID of the job</p> +</div></div></div></td></tr></tbody></table></div><div><h3 class="sc-bwCtUz chVREB">Responses</h3><div><div class="sc-eXEjpC bTuXIq"><svg class="sc-jTzLTM hhjjYI" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>200<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>OK</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Unauthorized</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Forbidden</p> +</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-gzOgki fBopsv"><div class="sc-hwwEjo hgxMbQ"><span type="get" class="sc-kPVwWT hzxych http-verb get">get</span><span class="sc-iyvyFf dpRRAO">/centreon/autodiscovery/hosts/{job_id}/schedule</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-kfGgVZ bVSqpC"><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Local Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api</span>/centreon/autodiscovery/hosts/{job_id}/schedule</div></div></div><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Remote Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/centreon/autodiscovery/hosts/{job_id}/schedule</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL eulAfj" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="react-tabs-106" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-107" tabindex="0">200</li><li class="tab-error" role="tab" id="react-tabs-108" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-109">401</li><li class="tab-error" role="tab" id="react-tabs-110" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-111">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-107" aria-labelledby="react-tabs-106"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA lcundD">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Example</span><div class="Dropdown-root sc-kkGfuU sc-fAjcbJ iIEWPt"><div class="Dropdown-control" aria-haspopup="listbox"><div class="Dropdown-placeholder is-selected">Token</div><div class="Dropdown-arrow-wrapper"><span class="Dropdown-arrow"></span></div></div></div></div><div><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"token"</span>: <span class="token string">"1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-109" aria-labelledby="react-tabs-108"></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-111" aria-labelledby="react-tabs-110"></div></div></div></div></div></div><div id="tag/Autodiscovery/paths/~1centreon~1autodiscovery~1hosts~1{token}/delete" data-section-id="tag/Autodiscovery/paths/~1centreon~1autodiscovery~1hosts~1{token}/delete" class="sc-ifAKCX hiuczA"><div class="sc-gzVnrw sc-cmthru hcTXxz"><div class="sc-bxivhb cjtbAK"><h2 class="sc-dnqmqq ioYTqA"><a class="sc-VigVT kGvRyb" href="#tag/Autodiscovery/paths/~1centreon~1autodiscovery~1hosts~1{token}/delete"></a>Delete a host discovery job<!-- --> </h2><div class="sc-hMFtBS bemheR"><div class="sc-jWBwVP sc-iRbamj eHtzbE"><p>Delete one Centreon Autodiscovery scheduled job.</p> +</div></div><div class="sc-bsbRJL PDnUY"><div class="sc-hXRMBi ceJGIt"><h5 class="sc-gqjmRU sc-iQNlJl lmVwfJ">Authorizations: </h5></div><div class="sc-epnACN iYKvkC"><span class="sc-gwVKww jtJYnZ"><span class="sc-eTuwsz eFFwMa"><a href="#section/Authentication/Basic Authentication">Basic Authentication</a></span></span></div></div><div><h5 class="sc-gqjmRU LiUBH">path<!-- --> Parameters</h5><table class="sc-dxgOiQ eCjbJc"><tbody><tr class="last undefined"><td class="sc-cSHVUG sc-chPdSV bIrgla" kind="field" title="token"><span class="sc-kGXeez bcLONg"></span>token<div class="sc-ksYbfQ sc-hmzhuo jsTAxL"> required </div></td><td class="sc-kgoBCf kGwPhO"><div><div><span class="sc-cHGsZl sc-TOsTZ fKyGWc"></span><span class="sc-cHGsZl sc-kgAjT hqYVjx">string</span></div> <div><span class="sc-cHGsZl lpeYvY"> <!-- -->Example:<!-- --> </span> <span class="sc-cHGsZl sc-jbKcbu bMfIUD">discovery_14_6b7d1bb8</span></div><div><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Token of the scheduled job</p> +</div></div></div></td></tr></tbody></table></div><div><h3 class="sc-bwCtUz chVREB">Responses</h3><div><div class="sc-eXEjpC bTuXIq"><svg class="sc-jTzLTM hhjjYI" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>200<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>OK</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>401<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Unauthorized</p> +</span></div></div><div><div class="sc-eXEjpC gKvVuj"><svg class="sc-jTzLTM jjozHG" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg><strong>403<!-- --> </strong><span class="sc-jlyJG fwYGJM"><p>Forbidden</p> +</span></div></div></div></div><div class="sc-EHOje sc-bZQynM dtUibw"><div class="sc-gzOgki fBopsv"><div class="sc-hwwEjo hgxMbQ"><span type="delete" class="sc-kPVwWT ifUWNX http-verb delete">delete</span><span class="sc-iyvyFf dpRRAO">/centreon/autodiscovery/hosts/{token}</span><svg class="sc-jTzLTM OtKQc" style="margin-right:-25px" version="1.1" viewBox="0 0 24 24" x="0" xmlns="http://www.w3.org/2000/svg" y="0"><polygon points="17.3 8.3 12 13.6 6.7 8.3 5.3 9.7 12 16.4 18.7 9.7 "></polygon></svg></div><div class="sc-kfGgVZ bVSqpC"><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Local Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api</span>/centreon/autodiscovery/hosts/{token}</div></div></div><div class="sc-esjQYD hNiVmK"><div class="sc-jWBwVP sc-iRbamj kjegA"><p>Remote Gorgone instance</p> +</div><div><div class="sc-kIPQKe jdQHlL"><span>{protocol}://{server}:{port}/api/nodes/{id}</span>/centreon/autodiscovery/hosts/{token}</div></div></div></div></div><div><h3 class="sc-gZMcBi kBWwoV"> Response samples </h3><div class="sc-eHgmQL eulAfj" data-tabs="true"><ul class="react-tabs__tab-list" role="tablist"><li class="tab-success react-tabs__tab--selected" role="tab" id="react-tabs-112" aria-selected="true" aria-disabled="false" aria-controls="react-tabs-113" tabindex="0">200</li><li class="tab-error" role="tab" id="react-tabs-114" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-115">401</li><li class="tab-error" role="tab" id="react-tabs-116" aria-selected="false" aria-disabled="false" aria-controls="react-tabs-117">403</li></ul><div class="react-tabs__tab-panel react-tabs__tab-panel--selected" role="tabpanel" id="react-tabs-113" aria-labelledby="react-tabs-112"><div><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Content type</span><div class="sc-fMiknA lcundD">application/json</div></div><div class="sc-gisBJw gwfZGU"><div class="sc-eqIVtm ecxnvs"><span class="sc-dVhcbM dpMbau">Example</span><div class="Dropdown-root sc-kkGfuU sc-fAjcbJ iIEWPt"><div class="Dropdown-control" aria-haspopup="listbox"><div class="Dropdown-placeholder is-selected">Token</div><div class="Dropdown-arrow-wrapper"><span class="Dropdown-arrow"></span></div></div></div></div><div><div class="sc-jhAzac kwGRVL"><div class="sc-brqgnP fCJmC"><span><div class="sc-gipzik gbTit">Copy</div></span><span> Expand all </span><span> Collapse all </span></div><div class="sc-jWBwVP jCgylq sc-fBuWsC kZHJcC"><div class="redoc-json"><code><div class="collapser"></div><span class="token punctuation">{</span><span class="ellipsis"></span><ul class="obj collapsible"><li><div class="hoverable "><span class="property token string">"token"</span>: <span class="token string">"1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"</span></div></li></ul><span class="token punctuation">}</span></code></div></div></div></div></div></div></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-115" aria-labelledby="react-tabs-114"></div><div class="react-tabs__tab-panel" role="tabpanel" id="react-tabs-117" aria-labelledby="react-tabs-116"></div></div></div></div></div></div></div><div class="sc-iujRgT kTYKTV"></div></div></div> <script> - const __redoc_state = {"menu":{"activeItemIdx":-1},"spec":{"data":{"openapi":"3.0.1","info":{"title":"Centreon Gorgone RestAPI","description":"# Information\nCentreon Gorgone and his \"gorgoned\" daemon is a lightweight, distributed, modular tasks handler.\n\nIt provides a set of actions like:\n\n - Execute commands\n - Send files/directories,\n - Schedule cron-like tasks,\n - Push or execute tasks through SSH.\n\nThe daemon can be installed on Centreon environments like Centreon Central, Remote and Poller servers.\n\nIt uses ZeroMQ library.\n\n# Authentication\n\n<!-- ReDoc-Inject: <security-definitions> -->","x-logo":{"url":"./centreon-logo.png"},"contact":{"url":"https://www.centreon.com"},"license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0.html"},"version":"1.0"},"externalDocs":{"description":"You can contact us on our community Slack","url":"https://centreon.slack.com/messages/CCRGLQSE5"},"servers":[{"url":"{protocol}://{server}:{port}/api","description":"Local Gorgone instance","variables":{"protocol":{"enum":["http","https"],"default":"http","description":"HTTP schema"},"server":{"default":"localhost","description":"IP address or hostname of Gorgone instance"},"port":{"default":"8085","description":"Port used by HTTP server"}}},{"url":"{protocol}://{server}:{port}/api/nodes/{id}","description":"Remote Gorgone instance","variables":{"protocol":{"enum":["http","https"],"default":"http","description":"HTTP schema"},"server":{"default":"localhost","description":"IP address or hostname of Gorgone instance"},"port":{"default":"8085","description":"Port used by HTTP server"},"id":{"default":"1","description":"ID of the remote Gorgone node"}}}],"tags":[{"name":"Internal","description":"Internal events."},{"name":"Logs","description":"Logs management."},{"name":"Cron","description":"Module aiming to reproduce a cron-like scheduler that can send events to other Gorgone modules."},{"name":"Action","description":"Module aiming to execute actions on the server running the Gorgone daemon or remotly using SSH."},{"name":"Engine","description":"Module aiming to provide a bridge to communicate with Centreon Engine daemon."},{"name":"Statistics","description":"Module aiming to deal with statistics collection of Centreon Engine and Broker."},{"name":"Autodiscovery","description":"Module aiming to extend Centreon Autodiscovery server functionalities."}],"security":[{"Basic Authentication":[]}],"paths":{"/internal/constatus":{"get":{"tags":["Internal"],"summary":"Get nodes connection status","description":"Get the connection status of all nodes managed by the Gorgone daemon.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NodesStatus"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/internal/information":{"get":{"tags":["Internal"],"summary":"Get runtime informations and statistics","description":"Get informations and statistics about loaded modules, available endpoints and number of events computed at runtime.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Information"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/internal/thumbprint":{"get":{"tags":["Internal"],"summary":"Get public key thumbprint","description":"Get the thumbprint of the public key of the Gorgone daemon.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Thumbprint"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/internal/logger":{"post":{"tags":["Internal"],"summary":"Set logger severity level","description":"Set the logger severity level for all modules.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SeverityLevel"}}}},"responses":{"204":{"description":"OK"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/log/{token}":{"get":{"tags":["Logs"],"summary":"Retrieve event's logs","description":"Retrieve the event's logs based on event's token.","parameters":[{"$ref":"#/components/parameters/Token"},{"$ref":"#/components/parameters/Code"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Ctime"},{"$ref":"#/components/parameters/Etime"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Logs"},{"$ref":"#/components/schemas/NoLogs"},{"$ref":"#/components/schemas/Error"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/core/cron/definitions":{"get":{"tags":["Cron"],"summary":"List definitions","description":"List all cron definitions.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Token"},{"$ref":"#/components/schemas/Logs"},{"$ref":"#/components/schemas/NoLogs"},{"$ref":"#/components/schemas/Error"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}},"post":{"tags":["Cron"],"summary":"Add definitions","description":"Add one or multiple cron definitions to runtime.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CronDefinitions"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Token"},{"$ref":"#/components/schemas/Logs"},{"$ref":"#/components/schemas/NoLogs"},{"$ref":"#/components/schemas/Error"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/core/cron/definitions/{definition_id}":{"get":{"tags":["Cron"],"summary":"Get a definition","description":"List cron definition identified by id.","parameters":[{"$ref":"#/components/parameters/DefinitionId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Token"},{"$ref":"#/components/schemas/Logs"},{"$ref":"#/components/schemas/NoLogs"},{"$ref":"#/components/schemas/Error"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}},"patch":{"tags":["Cron"],"summary":"Update a definition","description":"Update a cron definition.","parameters":[{"$ref":"#/components/parameters/DefinitionId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CronDefinition"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Token"},{"$ref":"#/components/schemas/Logs"},{"$ref":"#/components/schemas/NoLogs"},{"$ref":"#/components/schemas/Error"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}},"delete":{"tags":["Cron"],"summary":"Delete a definition","description":"Delete a cron definition.","parameters":[{"$ref":"#/components/parameters/DefinitionId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Token"},{"$ref":"#/components/schemas/Logs"},{"$ref":"#/components/schemas/NoLogs"},{"$ref":"#/components/schemas/Error"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/core/cron/definitions/{definition_id}/status":{"get":{"tags":["Cron"],"summary":"Get a definition status","description":"Get a definition execution status.","parameters":[{"$ref":"#/components/parameters/DefinitionId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Token"},{"$ref":"#/components/schemas/Logs"},{"$ref":"#/components/schemas/NoLogs"},{"$ref":"#/components/schemas/Error"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/core/action/command":{"post":{"tags":["Action"],"summary":"Execute one or several command lines","description":"Execute a command or a set of commands on server running Gorgone.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActionCommands"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Token"},{"$ref":"#/components/schemas/Logs"},{"$ref":"#/components/schemas/NoLogs"},{"$ref":"#/components/schemas/Error"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/centreon/engine/command":{"post":{"tags":["Engine"],"summary":"Send one or several external commands","description":"Send an external command or a set of external commands to a running Centreon Engine instance using command file pipe.\nThis method needs the commands to be preformatted as Nagios external commands format.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EngineCommands"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Token"},{"$ref":"#/components/schemas/Logs"},{"$ref":"#/components/schemas/NoLogs"},{"$ref":"#/components/schemas/Error"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/centreon/statistics/broker":{"get":{"tags":["Statistics"],"summary":"Launch Broker statistics collection","description":"Launch Broker statistics collection and store the result on disk.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Token"},{"$ref":"#/components/schemas/Logs"},{"$ref":"#/components/schemas/NoLogs"},{"$ref":"#/components/schemas/Error"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/centreon/statistics/broker/{monitoring_server_id}":{"get":{"tags":["Statistics"],"summary":"Launch Broker statistics collection of a specific monitoring server","description":"Launch Broker statistics collection and store the result on disk.","parameters":[{"$ref":"#/components/parameters/MonitoringServerId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Token"},{"$ref":"#/components/schemas/Logs"},{"$ref":"#/components/schemas/NoLogs"},{"$ref":"#/components/schemas/Error"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/centreon/autodiscovery/job":{"post":{"tags":["Autodiscovery"],"summary":"Add a discovery job","description":"Add one Centreon Autodiscovery job.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutodiscoveryJob"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Token"},{"$ref":"#/components/schemas/Logs"},{"$ref":"#/components/schemas/NoLogs"},{"$ref":"#/components/schemas/Error"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}}},"components":{"securitySchemes":{"Basic Authentication":{"type":"http","scheme":"basic"}},"parameters":{"Token":{"in":"path","name":"token","required":true,"description":"Token of the event","schema":{"type":"string","example":"1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"}},"Code":{"in":"query","name":"code","required":false,"description":"Only retrieve logs with defined code","schema":{"type":"integer","enum":[0,1,2],"example":2}},"Limit":{"in":"query","name":"limit","required":false,"description":"Only retrieve the last x logs","schema":{"type":"integer","minimum":1,"example":1}},"Ctime":{"in":"query","name":"ctime","required":false,"description":"Only retrieve logs with a creation time equal or superior to a timestamp","schema":{"type":"integer","format":"int64","example":1577726040}},"Etime":{"in":"query","name":"etime","required":false,"description":"Only retrieve logs of an event time superior to a timestamp","schema":{"type":"integer","format":"int64","example":1577726040}},"DefinitionId":{"in":"path","name":"definition_id","required":true,"description":"ID of the definition","schema":{"type":"string","example":"broker_stats"}},"MonitoringServerId":{"in":"path","name":"monitoring_server_id","required":true,"description":"ID of the monitoring server","schema":{"type":"integer","example":2}}},"responses":{"NotFound":{"description":"The specified resource was not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Unauthorized","headers":{"WWW-Authenticate":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Short error description","example":"http_error_401"},"message":{"type":"string","description":"Message explaining the error","example":"unauthorized"}},"required":["error","message"]}}}},"Forbidden":{"description":"Forbidden","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Short error description","example":"http_error_403"},"message":{"type":"string","description":"Message explaining the error","example":"forbidden"}},"required":["error","message"]}}}},"UnknownEndpoint":{"description":"Unknown endpoint","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Short error description","example":"method_unknown"},"message":{"type":"string","description":"Message explaining the error","example":"Method not implemented"}},"required":["error","message"]}}}},"UnknownMethod":{"description":"Unknown method","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Short error description","example":"endpoint_unknown"},"message":{"type":"string","description":"Message explaining the error","example":"endpoint not implemented"}},"required":["error","message"]}}}}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string","description":"Short error description"},"message":{"type":"string","description":"Message explaining the error"}},"required":["error","message"]},"Token":{"type":"object","properties":{"token":{"type":"string","format":"byte","description":"Token related to the event's result","example":"1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"}}},"Logs":{"type":"object","properties":{"message":{"type":"string","description":"Additionnal message","example":"Logs found"},"token":{"type":"string","format":"byte","description":"Token related to the event's result","example":"03008486ba50b52e529ff5828d1432e5578dd18bb530c145b133dc902c8cfa6b8aac4d58fffb0c5ed44b943d2acbfb7cd1b18c55fcebce62e51999db460112c7"},"data":{"type":"array","description":"Results array containing all logs related to token","items":{"$ref":"#/components/schemas/Log"}}}},"Log":{"type":"object","properties":{"ctime":{"type":"string","format":"timestamp","description":"Time when the server has stored the log in its database","example":1577727699},"etime":{"type":"string","format":"timestamp","description":"Time when the event has occured","example":1577727699},"id":{"type":"integer","description":"ID of the event","example":101483},"instant":{"type":"integer","example":0},"data":{"type":"object","description":"Data stored for this event"},"token":{"type":"string","format":"byte","description":"Token related to the event","example":"03008486ba50b52e529ff5828d1432e5578dd18bb530c145b133dc902c8cfa6b8aac4d58fffb0c5ed44b943d2acbfb7cd1b18c55fcebce62e51999db460112c7"},"code":{"type":"integer","description":"Returned code of the event","example":2}}},"NoLogs":{"type":"object","properties":{"error":{"type":"string","description":"Short error description","example":"no_log"},"message":{"type":"string","description":"Message explaining the error","example":"No log found for token"},"token":{"type":"string","description":"Token related to the event's result","example":"03008486ba50b52e529ff5828d1432e5578dd18bb530c145b133dc902c8cfa6b8aac4d58fffb0c5ed44b943d2acbfb7cd1b18c55fcebce62e51999db460112c7"},"data":{"type":"array","description":"Empty array","items":{"type":"object"}}}},"NodesStatus":{"type":"object","properties":{"action":{"type":"string","description":"Event sent to retrieve data","example":"constatus"},"message":{"type":"string","description":"Response message","example":"ok"},"data":{"type":"object","properties":{"id":{"$ref":"#/components/schemas/NodeStatus"}}}}},"NodeStatus":{"type":"object","properties":{"last_ping_sent":{"type":"string","format":"timestamp","description":"Last ping sent timestamp","example":1577726040},"type":{"type":"string","enum":["push_zmq","pull_zmq","ssh"],"description":"Communication type","example":"push_zmq"},"nodes":{"type":"object","description":"Nodes managed by this Gorgone daemon"},"last_ping_recv":{"type":"string","format":"timestamp","description":"Last ping received timestamp","example":1577726040}}},"Information":{"type":"object","properties":{"action":{"type":"string","description":"Event sent to retrieve data","example":"information"},"message":{"type":"string","description":"Response message","example":"ok"},"data":{"type":"object","properties":{"modules":{"$ref":"#/components/schemas/Modules"},"api_endpoints":{"$ref":"#/components/schemas/ApiEndpoints"},"counters":{"$ref":"#/components/schemas/Counters"}}}}},"Modules":{"type":"object","description":"List of loaded modules","additionalProperties":{"type":"string"},"example":{"httpserver":"gorgone::modules::core::httpserver::hooks","dbcleaner":"gorgone::modules::core::dbcleaner::hooks","cron":"gorgone::modules::core::cron::hooks","engine":"gorgone::modules::centreon::engine::hooks","action":"gorgone::modules::core::action::hooks","statistics":"gorgone::modules::centreon::statistics::hooks","nodes":"gorgone::modules::centreon::nodes::hooks","legacycmd":"gorgone::modules::centreon::legacycmd::hooks","proxy":"gorgone::modules::core::proxy::hooks"}},"ApiEndpoints":{"type":"object","description":"List of available endpoints","additionalProperties":{"type":"string"},"example":{"POST_/internal/logger":"BCASTLOGGER","GET_/centreon/statistics/broker":"BROKERSTATS","GET_/internal/thumbprint":"GETTHUMBPRINT","GET_/core/cron/definitions":"GETCRON","GET_/internal/information":"INFORMATION","POST_/core/cron/definitions":"ADDCRON","POST_/core/action/command":"COMMAND","POST_/core/proxy/remotecopy":"REMOTECOPY","POST_/centreon/engine/command":"ENGINECOMMAND","PATCH_/core/cron/definitions":"UPDATECRON","DELETE_/core/cron/definitions":"DELETECRON","GET_/internal/constatus":"CONSTATUS"}},"Counters":{"type":"object","description":"List of metric counters","properties":{"total":{"type":"integer","description":"Total number of events processed since startup","example":40210},"external":{"type":"object","description":"Number of external events since startup","additionalProperties":{"type":"string"},"example":{"total":0}},"internal":{"type":"object","description":"Number of internal events since startup","additionalProperties":{"type":"string"},"example":{"legacycmdready":1,"setlogs":7841,"enginecommand":20,"registernodes":443,"pong":3397,"proxyready":5,"statisticsready":1,"addcron":1,"cronready":1,"getthumbprint":2,"centreonnodesready":1,"httpserverready":1,"command":4446,"putlog":9809,"dbcleanerready":1,"information":6,"brokerstats":4446,"constatus":1,"total":40210,"setcoreid":443,"getlog":8893,"engineready":1,"unregisternodes":443,"actionready":1}},"proxy":{"type":"object","description":"Number of events passed through proxy since startup","additionalProperties":{"type":"string"},"example":{"enginecommand":10,"getlog":4446,"total":8902,"command":4446}}}},"Thumbprint":{"type":"object","properties":{"action":{"type":"string","description":"Event sent to retrieve data","example":"getthumbprint"},"message":{"type":"string","description":"Response message","example":"ok"},"data":{"type":"object","properties":{"thumbprint":{"type":"string","description":"Thumbprint of the public key","example":"cS4B3lZq96qcP4FTMhVMuwAhztqRBQERKyhnEitnTFM"}}}}},"SeverityLevel":{"type":"object","properties":{"severity":{"type":"string","description":"Severity level to be defined for all loaded modules","enum":["info","error","debug"]}}},"CronDefinitions":{"type":"array","items":{"$ref":"#/components/schemas/CronDefinition"}},"CronDefinition":{"type":"object","properties":{"timespec":{"type":"string","description":"Cron-like time specification"},"id":{"type":"string","description":"Unique identifier of the cron definition"},"action":{"type":"string","description":"Action/event to call at job execution"},"parameters":{"type":"object","description":"Parameters needed by the called action/event"},"keep_token":{"type":"boolean","description":"Boolean to define whether or not the ID of the definition will be used as token for the command"}},"required":["timespec","id","action","parameters"]},"ActionCommands":{"type":"array","items":{"$ref":"#/components/schemas/ActionCommand"}},"ActionCommand":{"type":"object","properties":{"command":{"type":"string","description":"Command to execute","example":"echo data > /tmp/date.log"},"timeout":{"type":"integer","description":"Time in seconds before a command is considered timed out","example":5,"default":30},"continue_on_error":{"type":"boolean","description":"Behaviour in case of execution issue","example":true,"default":false}},"required":["command"]},"EngineCommands":{"type":"object","properties":{"command_file":{"type":"string","description":"Path to the Centreon Engine command file pipe","example":"/var/lib/centreon-engine/rw/centengine.cmd"},"command":{"type":"array","items":{"type":"string","description":"External command","example":"[653284380] SCHEDULE_SVC_CHECK;host1;service1;653284380"}}}},"AutodiscoveryJob":{"type":"object","properties":{"id":{"type":"string","description":"Identifier of the job (random if empty)","example":"Job-SNMP-10.1.2.3"},"execution_mode":{"type":"string","description":"Execution mode of this job ('0': execute immediately, '1': schedule with cron)","example":0},"command":{"type":"string","description":"Command line to execute to perform the discovery","example":"perl /usr/lib/centreon/plugins/centreon_generic_snmp.pl --plugin=os::linux::local::plugin --mode=discovery-snmp --subnet='10.1.2.3/24' --snmp-port='161' --snmp-version='2c' --snmp-community='public'\n"},"timeout":{"type":"integer","description":"Time in seconds before the command is considered timed out","example":300},"target":{"type":"integer","description":"Identifier of the target on which to execute the command","example":2}},"required":["execution_mode","command","target"]}}}}},"searchIndex":{"store":["section/Information","section/Authentication","tag/Internal","tag/Internal/paths/~1internal~1constatus/get","tag/Internal/paths/~1internal~1information/get","tag/Internal/paths/~1internal~1thumbprint/get","tag/Internal/paths/~1internal~1logger/post","tag/Logs","tag/Logs/paths/~1log~1{token}/get","tag/Cron","tag/Cron/paths/~1core~1cron~1definitions/get","tag/Cron/paths/~1core~1cron~1definitions/post","tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}/get","tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}/patch","tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}/delete","tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}~1status/get","tag/Action","tag/Action/paths/~1core~1action~1command/post","tag/Engine","tag/Engine/paths/~1centreon~1engine~1command/post","tag/Statistics","tag/Statistics/paths/~1centreon~1statistics~1broker/get","tag/Statistics/paths/~1centreon~1statistics~1broker~1{monitoring_server_id}/get","tag/Autodiscovery","tag/Autodiscovery/paths/~1centreon~1autodiscovery~1job/post"],"index":{"version":"2.3.8","fields":["title","description"],"fieldVectors":[["title/0",[0,2.634]],["description/0",[1,1.152,2,0.879,3,0.985,4,1.057,5,1.057,6,1.057,7,2.371,8,1.057,9,0.867,10,0.575,11,0.743,12,2.305,13,0.985,14,0.575,15,0.65,16,1.057,17,0.867,18,0.867,19,1.057,20,1.057,21,0.867,22,1.057,23,1.057,24,1.057,25,1.057,26,1.057,27,0.575,28,0.743,29,1.057,30,1.057]],["title/1",[31,3.747]],["description/1",[12,3.728,32,3.558,33,3.558]],["title/2",[34,3.076]],["description/2",[34,3.363,35,2.88]],["title/3",[36,2.125,37,2.125,38,1.591]],["description/3",[2,1.528,3,1.712,36,2.581,37,2.581,38,1.933,39,2.581]],["title/4",[0,1.819,40,1.819,41,1.014]],["description/4",[0,1.794,35,1.794,40,1.794,41,1,42,2.551,43,1.112,44,2.551,45,2.551,46,2.551,47,2.551]],["title/5",[48,2.125,49,2.125,50,2.125]],["description/5",[2,1.622,3,1.818,48,2.741,49,2.741,50,2.741]],["title/6",[10,1.22,51,1.84,52,1.378,53,1.84]],["description/6",[10,1.818,43,1.455,51,2.741,52,2.053,53,2.741]],["title/7",[54,2.304]],["description/7",[39,3.363,54,2.519]],["title/8",[54,1.591,55,2.125,56,2.125]],["description/8",[54,1.933,55,2.581,56,3.439,57,3.144,58,3.144]],["title/9",[59,1.821]],["description/9",[2,1.301,15,1.646,17,2.198,18,2.198,35,1.882,43,1.636,60,1.458,61,2.678]],["title/10",[62,2.152,63,0.786]],["description/10",[59,1.851,62,2.677,63,0.978]],["title/11",[63,0.786,64,1.882]],["description/11",[40,2.21,59,1.528,63,0.807,64,1.933,65,1.933,66,3.144]],["title/12",[63,0.962]],["description/12",[59,1.622,62,2.347,63,0.857,67,3.338,68,3.338]],["title/13",[63,0.786,69,2.513]],["description/13",[59,1.851,63,0.978,69,3.126]],["title/14",[63,0.786,70,2.513]],["description/14",[59,1.851,63,0.978,70,3.126]],["title/15",[38,1.882,63,0.786]],["description/15",[13,2.074,38,2.342,63,0.978]],["title/16",[11,2.634]],["description/16",[2,1.184,3,1.327,11,1.713,13,1.327,21,2,27,1.327,28,1.713,43,1.062,60,1.327,71,1.713,72,2.436]],["title/17",[13,1.076,14,1.076,52,1.215,65,1.215,73,1.977]],["description/17",[2,1.444,10,1.618,13,1.618,14,2.196,27,1.618,71,2.089]],["title/18",[74,2.304]],["description/18",[1,1.369,3,1.534,9,2.313,43,1.228,60,1.534,74,1.732,75,2.817,76,2.817]],["title/19",[14,1.076,15,1.215,52,1.215,65,1.215,77,1.623]],["description/19",[1,0.792,10,0.887,14,2.176,15,1.002,28,1.145,71,1.145,74,1.002,77,2.641,78,1.629,79,1.629,80,1.629,81,1.629,82,1.629,83,1.629,84,1.629,85,1.629]],["title/20",[41,1.468]],["description/20",[1,1.369,41,1.104,43,1.228,60,1.534,74,1.732,86,2.817,87,1.534,88,1.534]],["title/21",[41,0.878,87,1.22,88,1.22,89,1.378]],["description/21",[41,1.165,87,1.618,88,1.618,89,1.827,90,2.44,91,2.44,92,2.44]],["title/22",[27,0.871,41,0.627,87,0.871,88,0.871,89,0.983,93,1.599,94,1.599]],["description/22",[41,1.165,87,1.618,88,1.618,89,1.827,90,2.44,91,2.44,92,2.44]],["title/23",[95,2.634]],["description/23",[1,1.444,27,1.618,43,1.295,60,1.618,95,2.089,96,2.972,97,2.972]],["title/24",[64,1.591,98,2.588,99,2.125]],["description/24",[1,1.622,64,2.053,65,2.053,95,2.347,99,2.741]]],"invertedIndex":[["",{"_index":12,"title":{},"description":{"0":{},"1":{}}}],["action",{"_index":11,"title":{"16":{}},"description":{"0":{},"16":{}}}],["add",{"_index":64,"title":{"11":{},"24":{}},"description":{"11":{},"24":{}}}],["aim",{"_index":60,"title":{},"description":{"9":{},"16":{},"18":{},"20":{},"23":{}}}],["authent",{"_index":31,"title":{"1":{}},"description":{}}],["autodiscoveri",{"_index":95,"title":{"23":{}},"description":{"23":{},"24":{}}}],["avail",{"_index":44,"title":{},"description":{"4":{}}}],["base",{"_index":57,"title":{},"description":{"8":{}}}],["bridg",{"_index":75,"title":{},"description":{"18":{}}}],["broker",{"_index":88,"title":{"21":{},"22":{}},"description":{"20":{},"21":{},"22":{}}}],["central",{"_index":24,"title":{},"description":{"0":{}}}],["centreon",{"_index":1,"title":{},"description":{"0":{},"18":{},"19":{},"20":{},"23":{},"24":{}}}],["collect",{"_index":87,"title":{"21":{},"22":{}},"description":{"20":{},"21":{},"22":{}}}],["command",{"_index":14,"title":{"17":{},"19":{}},"description":{"0":{},"17":{},"19":{}}}],["commun",{"_index":76,"title":{},"description":{"18":{}}}],["comput",{"_index":47,"title":{},"description":{"4":{}}}],["connect",{"_index":37,"title":{"3":{}},"description":{"3":{}}}],["cron",{"_index":59,"title":{"9":{}},"description":{"10":{},"11":{},"12":{},"13":{},"14":{}}}],["cron-lik",{"_index":18,"title":{},"description":{"0":{},"9":{}}}],["daemon",{"_index":3,"title":{},"description":{"0":{},"3":{},"5":{},"16":{},"18":{}}}],["deal",{"_index":86,"title":{},"description":{"20":{}}}],["definit",{"_index":63,"title":{"10":{},"11":{},"12":{},"13":{},"14":{},"15":{}},"description":{"10":{},"11":{},"12":{},"13":{},"14":{},"15":{}}}],["delet",{"_index":70,"title":{"14":{}},"description":{"14":{}}}],["discoveri",{"_index":98,"title":{"24":{}},"description":{}}],["disk",{"_index":92,"title":{},"description":{"21":{},"22":{}}}],["distribut",{"_index":5,"title":{},"description":{"0":{}}}],["endpoint",{"_index":45,"title":{},"description":{"4":{}}}],["engin",{"_index":74,"title":{"18":{}},"description":{"18":{},"19":{},"20":{}}}],["environ",{"_index":23,"title":{},"description":{"0":{}}}],["event",{"_index":35,"title":{},"description":{"2":{},"4":{},"9":{}}}],["event'",{"_index":56,"title":{"8":{}},"description":{"8":{}}}],["execut",{"_index":13,"title":{"17":{}},"description":{"0":{},"15":{},"16":{},"17":{}}}],["extend",{"_index":96,"title":{},"description":{"23":{}}}],["extern",{"_index":77,"title":{"19":{}},"description":{"19":{}}}],["file",{"_index":79,"title":{},"description":{"19":{}}}],["files/directori",{"_index":16,"title":{},"description":{"0":{}}}],["format",{"_index":85,"title":{},"description":{"19":{}}}],["function",{"_index":97,"title":{},"description":{"23":{}}}],["gorgon",{"_index":2,"title":{},"description":{"0":{},"3":{},"5":{},"9":{},"16":{},"17":{}}}],["handler",{"_index":8,"title":{},"description":{"0":{}}}],["id",{"_index":68,"title":{},"description":{"12":{}}}],["identifi",{"_index":67,"title":{},"description":{"12":{}}}],["inform",{"_index":0,"title":{"0":{},"4":{}},"description":{"4":{}}}],["instal",{"_index":22,"title":{},"description":{"0":{}}}],["instanc",{"_index":78,"title":{},"description":{"19":{}}}],["intern",{"_index":34,"title":{"2":{}},"description":{"2":{}}}],["job",{"_index":99,"title":{"24":{}},"description":{"24":{}}}],["key",{"_index":49,"title":{"5":{}},"description":{"5":{}}}],["launch",{"_index":89,"title":{"21":{},"22":{}},"description":{"21":{},"22":{}}}],["level",{"_index":53,"title":{"6":{}},"description":{"6":{}}}],["librari",{"_index":30,"title":{},"description":{"0":{}}}],["lightweight",{"_index":4,"title":{},"description":{"0":{}}}],["line",{"_index":73,"title":{"17":{}},"description":{}}],["list",{"_index":62,"title":{"10":{}},"description":{"10":{},"12":{}}}],["load",{"_index":42,"title":{},"description":{"4":{}}}],["log",{"_index":54,"title":{"7":{},"8":{}},"description":{"7":{},"8":{}}}],["logger",{"_index":51,"title":{"6":{}},"description":{"6":{}}}],["manag",{"_index":39,"title":{},"description":{"3":{},"7":{}}}],["method",{"_index":81,"title":{},"description":{"19":{}}}],["modul",{"_index":43,"title":{},"description":{"4":{},"6":{},"9":{},"16":{},"18":{},"20":{},"23":{}}}],["modular",{"_index":6,"title":{},"description":{"0":{}}}],["monitor",{"_index":94,"title":{"22":{}},"description":{}}],["multipl",{"_index":66,"title":{},"description":{"11":{}}}],["nagio",{"_index":84,"title":{},"description":{"19":{}}}],["need",{"_index":82,"title":{},"description":{"19":{}}}],["node",{"_index":36,"title":{"3":{}},"description":{"3":{}}}],["number",{"_index":46,"title":{},"description":{"4":{}}}],["on",{"_index":65,"title":{"17":{},"19":{}},"description":{"11":{},"24":{}}}],["pipe",{"_index":80,"title":{},"description":{"19":{}}}],["poller",{"_index":26,"title":{},"description":{"0":{}}}],["preformat",{"_index":83,"title":{},"description":{"19":{}}}],["provid",{"_index":9,"title":{},"description":{"0":{},"18":{}}}],["public",{"_index":48,"title":{"5":{}},"description":{"5":{}}}],["push",{"_index":19,"title":{},"description":{"0":{}}}],["redoc-inject",{"_index":32,"title":{},"description":{"1":{}}}],["remot",{"_index":25,"title":{},"description":{"0":{}}}],["remotli",{"_index":72,"title":{},"description":{"16":{}}}],["reproduc",{"_index":61,"title":{},"description":{"9":{}}}],["result",{"_index":91,"title":{},"description":{"21":{},"22":{}}}],["retriev",{"_index":55,"title":{"8":{}},"description":{"8":{}}}],["run",{"_index":71,"title":{},"description":{"16":{},"17":{},"19":{}}}],["runtim",{"_index":40,"title":{"4":{}},"description":{"4":{},"11":{}}}],["schedul",{"_index":17,"title":{},"description":{"0":{},"9":{}}}],["security-definit",{"_index":33,"title":{},"description":{"1":{}}}],["send",{"_index":15,"title":{"19":{}},"description":{"0":{},"9":{},"19":{}}}],["server",{"_index":27,"title":{"22":{}},"description":{"0":{},"16":{},"17":{},"23":{}}}],["set",{"_index":10,"title":{"6":{}},"description":{"0":{},"6":{},"17":{},"19":{}}}],["sever",{"_index":52,"title":{"6":{},"17":{},"19":{}},"description":{"6":{}}}],["specif",{"_index":93,"title":{"22":{}},"description":{}}],["ssh",{"_index":21,"title":{},"description":{"0":{},"16":{}}}],["statist",{"_index":41,"title":{"4":{},"20":{},"21":{},"22":{}},"description":{"4":{},"20":{},"21":{},"22":{}}}],["statu",{"_index":38,"title":{"3":{},"15":{}},"description":{"3":{},"15":{}}}],["store",{"_index":90,"title":{},"description":{"21":{},"22":{}}}],["task",{"_index":7,"title":{},"description":{"0":{}}}],["through",{"_index":20,"title":{},"description":{"0":{}}}],["thumbprint",{"_index":50,"title":{"5":{}},"description":{"5":{}}}],["token",{"_index":58,"title":{},"description":{"8":{}}}],["updat",{"_index":69,"title":{"13":{}},"description":{"13":{}}}],["us",{"_index":28,"title":{},"description":{"0":{},"16":{},"19":{}}}],["zeromq",{"_index":29,"title":{},"description":{"0":{}}}]],"pipeline":[]}},"options":{"hideDownloadButton":"true"}}; + const __redoc_state = {"menu":{"activeItemIdx":-1},"spec":{"data":{"openapi":"3.0.1","info":{"title":"Centreon Gorgone RestAPI","description":"# Information\nCentreon Gorgone and his \"gorgoned\" daemon is a lightweight, distributed, modular tasks handler.\n\nIt provides a set of actions like:\n\n - Execute commands\n - Send files/directories,\n - Schedule cron-like tasks,\n - Push or execute tasks through SSH.\n\nThe daemon can be installed on Centreon environments like Centreon Central, Remote and Poller servers.\n\nIt uses ZeroMQ library.\n\n# Authentication\n\n<!-- ReDoc-Inject: <security-definitions> -->","x-logo":{"url":"./centreon-logo.png"},"contact":{"url":"https://www.centreon.com"},"license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0.html"},"version":"1.0"},"externalDocs":{"description":"You can contact us on our community Slack","url":"https://centreon.slack.com/messages/CCRGLQSE5"},"servers":[{"url":"{protocol}://{server}:{port}/api","description":"Local Gorgone instance","variables":{"protocol":{"enum":["http","https"],"default":"http","description":"HTTP schema"},"server":{"default":"localhost","description":"IP address or hostname of Gorgone instance"},"port":{"default":"8085","description":"Port used by HTTP server"}}},{"url":"{protocol}://{server}:{port}/api/nodes/{id}","description":"Remote Gorgone instance","variables":{"protocol":{"enum":["http","https"],"default":"http","description":"HTTP schema"},"server":{"default":"localhost","description":"IP address or hostname of Gorgone instance"},"port":{"default":"8085","description":"Port used by HTTP server"},"id":{"default":"1","description":"ID of the remote Gorgone node"}}}],"tags":[{"name":"Internal","description":"Internal events."},{"name":"Logs","description":"Logs management."},{"name":"Cron","description":"Module aiming to reproduce a cron-like scheduler that can send events to other Gorgone modules."},{"name":"Action","description":"Module aiming to execute actions on the server running the Gorgone daemon or remotly using SSH."},{"name":"Engine","description":"Module aiming to provide a bridge to communicate with Centreon Engine daemon."},{"name":"Statistics","description":"Module aiming to deal with statistics collection of Centreon Engine and Broker."},{"name":"Autodiscovery","description":"Module aiming to extend Centreon Autodiscovery server functionalities."}],"security":[{"Basic Authentication":[]}],"paths":{"/internal/constatus":{"get":{"tags":["Internal"],"summary":"Get nodes connection status","description":"Get the connection status of all nodes managed by the Gorgone daemon.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NodesStatus"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/internal/information":{"get":{"tags":["Internal"],"summary":"Get runtime informations and statistics","description":"Get informations and statistics about loaded modules, available endpoints and number of events computed at runtime.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Information"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/internal/thumbprint":{"get":{"tags":["Internal"],"summary":"Get public key thumbprint","description":"Get the thumbprint of the public key of the Gorgone daemon.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Thumbprint"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/internal/logger":{"post":{"tags":["Internal"],"summary":"Set logger severity level","description":"Set the logger severity level for all modules.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SeverityLevel"}}}},"responses":{"204":{"description":"OK"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/log/{token}":{"get":{"tags":["Logs"],"summary":"Retrieve event's logs","description":"Retrieve the event's logs based on event's token.","parameters":[{"$ref":"#/components/parameters/Token"},{"$ref":"#/components/parameters/Code"},{"$ref":"#/components/parameters/Limit"},{"$ref":"#/components/parameters/Ctime"},{"$ref":"#/components/parameters/Etime"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Logs"},{"$ref":"#/components/schemas/NoLogs"},{"$ref":"#/components/schemas/Error"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/core/cron/definitions":{"get":{"tags":["Cron"],"summary":"List definitions","description":"List all cron definitions.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Token"},{"$ref":"#/components/schemas/Logs"},{"$ref":"#/components/schemas/NoLogs"},{"$ref":"#/components/schemas/Error"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}},"post":{"tags":["Cron"],"summary":"Add definitions","description":"Add one or multiple cron definitions to runtime.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CronDefinitions"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Token"},{"$ref":"#/components/schemas/Logs"},{"$ref":"#/components/schemas/NoLogs"},{"$ref":"#/components/schemas/Error"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/core/cron/definitions/{definition_id}":{"get":{"tags":["Cron"],"summary":"Get a definition","description":"List cron definition identified by id.","parameters":[{"$ref":"#/components/parameters/DefinitionId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Token"},{"$ref":"#/components/schemas/Logs"},{"$ref":"#/components/schemas/NoLogs"},{"$ref":"#/components/schemas/Error"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}},"patch":{"tags":["Cron"],"summary":"Update a definition","description":"Update a cron definition.","parameters":[{"$ref":"#/components/parameters/DefinitionId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CronDefinition"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Token"},{"$ref":"#/components/schemas/Logs"},{"$ref":"#/components/schemas/NoLogs"},{"$ref":"#/components/schemas/Error"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}},"delete":{"tags":["Cron"],"summary":"Delete a definition","description":"Delete a cron definition.","parameters":[{"$ref":"#/components/parameters/DefinitionId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Token"},{"$ref":"#/components/schemas/Logs"},{"$ref":"#/components/schemas/NoLogs"},{"$ref":"#/components/schemas/Error"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/core/cron/definitions/{definition_id}/status":{"get":{"tags":["Cron"],"summary":"Get a definition status","description":"Get a definition execution status.","parameters":[{"$ref":"#/components/parameters/DefinitionId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Token"},{"$ref":"#/components/schemas/Logs"},{"$ref":"#/components/schemas/NoLogs"},{"$ref":"#/components/schemas/Error"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/core/action/command":{"post":{"tags":["Action"],"summary":"Execute one or several command lines","description":"Execute a command or a set of commands on server running Gorgone.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActionCommands"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Token"},{"$ref":"#/components/schemas/Logs"},{"$ref":"#/components/schemas/NoLogs"},{"$ref":"#/components/schemas/Error"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/centreon/engine/command":{"post":{"tags":["Engine"],"summary":"Send one or several external commands","description":"Send an external command or a set of external commands to a running Centreon Engine instance using command file pipe.\nThis method needs the commands to be preformatted as Nagios external commands format.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EngineCommands"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Token"},{"$ref":"#/components/schemas/Logs"},{"$ref":"#/components/schemas/NoLogs"},{"$ref":"#/components/schemas/Error"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/centreon/statistics/broker":{"get":{"tags":["Statistics"],"summary":"Launch Broker statistics collection","description":"Launch Broker statistics collection and store the result on disk.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Token"},{"$ref":"#/components/schemas/Logs"},{"$ref":"#/components/schemas/NoLogs"},{"$ref":"#/components/schemas/Error"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/centreon/statistics/broker/{monitoring_server_id}":{"get":{"tags":["Statistics"],"summary":"Launch Broker statistics collection of a specific monitoring server","description":"Launch Broker statistics collection and store the result on disk.","parameters":[{"$ref":"#/components/parameters/MonitoringServerId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Token"},{"$ref":"#/components/schemas/Logs"},{"$ref":"#/components/schemas/NoLogs"},{"$ref":"#/components/schemas/Error"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/centreon/autodiscovery/hosts":{"post":{"tags":["Autodiscovery"],"summary":"Add a host discovery job","description":"Add one Centreon Autodiscovery job to discover hosts.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HostDiscoveryJob"}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Token"},{"$ref":"#/components/schemas/Logs"},{"$ref":"#/components/schemas/NoLogs"},{"$ref":"#/components/schemas/Error"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/centreon/autodiscovery/hosts/{job_id}/schedule":{"get":{"tags":["Autodiscovery"],"summary":"Launch a host discovery job","description":"Launch a host discovery job identified by id (even if in cron mode).","parameters":[{"$ref":"#/components/parameters/HostDiscoveryId"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Token"},{"$ref":"#/components/schemas/Logs"},{"$ref":"#/components/schemas/NoLogs"},{"$ref":"#/components/schemas/Error"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}},"/centreon/autodiscovery/hosts/{token}":{"delete":{"tags":["Autodiscovery"],"summary":"Delete a host discovery job","description":"Delete one Centreon Autodiscovery scheduled job.","parameters":[{"$ref":"#/components/parameters/HostDiscoveryToken"}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/Token"},{"$ref":"#/components/schemas/Logs"},{"$ref":"#/components/schemas/NoLogs"},{"$ref":"#/components/schemas/Error"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}}},"components":{"securitySchemes":{"Basic Authentication":{"type":"http","scheme":"basic"}},"parameters":{"Token":{"in":"path","name":"token","required":true,"description":"Token of the event","schema":{"type":"string","example":"1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"}},"Code":{"in":"query","name":"code","required":false,"description":"Only retrieve logs with defined code","schema":{"type":"integer","enum":[0,1,2],"example":2}},"Limit":{"in":"query","name":"limit","required":false,"description":"Only retrieve the last x logs","schema":{"type":"integer","minimum":1,"example":1}},"Ctime":{"in":"query","name":"ctime","required":false,"description":"Only retrieve logs with a creation time equal or superior to a timestamp","schema":{"type":"integer","format":"int64","example":1577726040}},"Etime":{"in":"query","name":"etime","required":false,"description":"Only retrieve logs of an event time superior to a timestamp","schema":{"type":"integer","format":"int64","example":1577726040}},"DefinitionId":{"in":"path","name":"definition_id","required":true,"description":"ID of the definition","schema":{"type":"string","example":"broker_stats"}},"MonitoringServerId":{"in":"path","name":"monitoring_server_id","required":true,"description":"ID of the monitoring server","schema":{"type":"integer","example":2}},"HostDiscoveryId":{"in":"path","name":"job_id","required":true,"description":"ID of the job","schema":{"type":"integer","example":2}},"HostDiscoveryToken":{"in":"path","name":"token","required":true,"description":"Token of the scheduled job","schema":{"type":"string","example":"discovery_14_6b7d1bb8"}}},"responses":{"NotFound":{"description":"The specified resource was not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Unauthorized","headers":{"WWW-Authenticate":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Short error description","example":"http_error_401"},"message":{"type":"string","description":"Message explaining the error","example":"unauthorized"}},"required":["error","message"]}}}},"Forbidden":{"description":"Forbidden","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Short error description","example":"http_error_403"},"message":{"type":"string","description":"Message explaining the error","example":"forbidden"}},"required":["error","message"]}}}},"UnknownEndpoint":{"description":"Unknown endpoint","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Short error description","example":"method_unknown"},"message":{"type":"string","description":"Message explaining the error","example":"Method not implemented"}},"required":["error","message"]}}}},"UnknownMethod":{"description":"Unknown method","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string","description":"Short error description","example":"endpoint_unknown"},"message":{"type":"string","description":"Message explaining the error","example":"endpoint not implemented"}},"required":["error","message"]}}}}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"string","description":"Short error description"},"message":{"type":"string","description":"Message explaining the error"}},"required":["error","message"]},"Token":{"type":"object","properties":{"token":{"type":"string","format":"byte","description":"Token related to the event's result","example":"1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"}}},"Logs":{"type":"object","properties":{"message":{"type":"string","description":"Additionnal message","example":"Logs found"},"token":{"type":"string","format":"byte","description":"Token related to the event's result","example":"03008486ba50b52e529ff5828d1432e5578dd18bb530c145b133dc902c8cfa6b8aac4d58fffb0c5ed44b943d2acbfb7cd1b18c55fcebce62e51999db460112c7"},"data":{"type":"array","description":"Results array containing all logs related to token","items":{"$ref":"#/components/schemas/Log"}}}},"Log":{"type":"object","properties":{"ctime":{"type":"string","format":"timestamp","description":"Time when the server has stored the log in its database","example":1577727699},"etime":{"type":"string","format":"timestamp","description":"Time when the event has occured","example":1577727699},"id":{"type":"integer","description":"ID of the event","example":101483},"instant":{"type":"integer","example":0},"data":{"type":"object","description":"Data stored for this event"},"token":{"type":"string","format":"byte","description":"Token related to the event","example":"03008486ba50b52e529ff5828d1432e5578dd18bb530c145b133dc902c8cfa6b8aac4d58fffb0c5ed44b943d2acbfb7cd1b18c55fcebce62e51999db460112c7"},"code":{"type":"integer","description":"Returned code of the event","example":2}}},"NoLogs":{"type":"object","properties":{"error":{"type":"string","description":"Short error description","example":"no_log"},"message":{"type":"string","description":"Message explaining the error","example":"No log found for token"},"token":{"type":"string","description":"Token related to the event's result","example":"03008486ba50b52e529ff5828d1432e5578dd18bb530c145b133dc902c8cfa6b8aac4d58fffb0c5ed44b943d2acbfb7cd1b18c55fcebce62e51999db460112c7"},"data":{"type":"array","description":"Empty array","items":{"type":"object"}}}},"NodesStatus":{"type":"object","properties":{"action":{"type":"string","description":"Event sent to retrieve data","example":"constatus"},"message":{"type":"string","description":"Response message","example":"ok"},"data":{"type":"object","properties":{"id":{"$ref":"#/components/schemas/NodeStatus"}}}}},"NodeStatus":{"type":"object","properties":{"last_ping_sent":{"type":"string","format":"timestamp","description":"Last ping sent timestamp","example":1577726040},"type":{"type":"string","enum":["push_zmq","pull_zmq","ssh"],"description":"Communication type","example":"push_zmq"},"nodes":{"type":"object","description":"Nodes managed by this Gorgone daemon"},"last_ping_recv":{"type":"string","format":"timestamp","description":"Last ping received timestamp","example":1577726040}}},"Information":{"type":"object","properties":{"action":{"type":"string","description":"Event sent to retrieve data","example":"information"},"message":{"type":"string","description":"Response message","example":"ok"},"data":{"type":"object","properties":{"modules":{"$ref":"#/components/schemas/Modules"},"api_endpoints":{"$ref":"#/components/schemas/ApiEndpoints"},"counters":{"$ref":"#/components/schemas/Counters"}}}}},"Modules":{"type":"object","description":"List of loaded modules","additionalProperties":{"type":"string"},"example":{"httpserver":"gorgone::modules::core::httpserver::hooks","dbcleaner":"gorgone::modules::core::dbcleaner::hooks","cron":"gorgone::modules::core::cron::hooks","engine":"gorgone::modules::centreon::engine::hooks","action":"gorgone::modules::core::action::hooks","statistics":"gorgone::modules::centreon::statistics::hooks","nodes":"gorgone::modules::centreon::nodes::hooks","legacycmd":"gorgone::modules::centreon::legacycmd::hooks","proxy":"gorgone::modules::core::proxy::hooks"}},"ApiEndpoints":{"type":"object","description":"List of available endpoints","additionalProperties":{"type":"string"},"example":{"POST_/internal/logger":"BCASTLOGGER","GET_/centreon/statistics/broker":"BROKERSTATS","GET_/internal/thumbprint":"GETTHUMBPRINT","GET_/core/cron/definitions":"GETCRON","GET_/internal/information":"INFORMATION","POST_/core/cron/definitions":"ADDCRON","POST_/core/action/command":"COMMAND","POST_/core/proxy/remotecopy":"REMOTECOPY","POST_/centreon/engine/command":"ENGINECOMMAND","PATCH_/core/cron/definitions":"UPDATECRON","DELETE_/core/cron/definitions":"DELETECRON","GET_/internal/constatus":"CONSTATUS"}},"Counters":{"type":"object","description":"List of metric counters","properties":{"total":{"type":"integer","description":"Total number of events processed since startup","example":40210},"external":{"type":"object","description":"Number of external events since startup","additionalProperties":{"type":"string"},"example":{"total":0}},"internal":{"type":"object","description":"Number of internal events since startup","additionalProperties":{"type":"string"},"example":{"legacycmdready":1,"setlogs":7841,"enginecommand":20,"registernodes":443,"pong":3397,"proxyready":5,"statisticsready":1,"addcron":1,"cronready":1,"getthumbprint":2,"centreonnodesready":1,"httpserverready":1,"command":4446,"putlog":9809,"dbcleanerready":1,"information":6,"brokerstats":4446,"constatus":1,"total":40210,"setcoreid":443,"getlog":8893,"engineready":1,"unregisternodes":443,"actionready":1}},"proxy":{"type":"object","description":"Number of events passed through proxy since startup","additionalProperties":{"type":"string"},"example":{"enginecommand":10,"getlog":4446,"total":8902,"command":4446}}}},"Thumbprint":{"type":"object","properties":{"action":{"type":"string","description":"Event sent to retrieve data","example":"getthumbprint"},"message":{"type":"string","description":"Response message","example":"ok"},"data":{"type":"object","properties":{"thumbprint":{"type":"string","description":"Thumbprint of the public key","example":"cS4B3lZq96qcP4FTMhVMuwAhztqRBQERKyhnEitnTFM"}}}}},"SeverityLevel":{"type":"object","properties":{"severity":{"type":"string","description":"Severity level to be defined for all loaded modules","enum":["info","error","debug"]}}},"CronDefinitions":{"type":"array","items":{"$ref":"#/components/schemas/CronDefinition"}},"CronDefinition":{"type":"object","properties":{"timespec":{"type":"string","description":"Cron-like time specification"},"id":{"type":"string","description":"Unique identifier of the cron definition"},"action":{"type":"string","description":"Action/event to call at job execution"},"parameters":{"type":"object","description":"Parameters needed by the called action/event"},"keep_token":{"type":"boolean","description":"Boolean to define whether or not the ID of the definition will be used as token for the command"}},"required":["timespec","id","action","parameters"]},"ActionCommands":{"type":"array","items":{"$ref":"#/components/schemas/ActionCommand"}},"ActionCommand":{"type":"object","properties":{"command":{"type":"string","description":"Command to execute","example":"echo data > /tmp/date.log"},"timeout":{"type":"integer","description":"Time in seconds before a command is considered timed out","example":5,"default":30},"continue_on_error":{"type":"boolean","description":"Behaviour in case of execution issue","example":true,"default":false}},"required":["command"]},"EngineCommands":{"type":"object","properties":{"command_file":{"type":"string","description":"Path to the Centreon Engine command file pipe","example":"/var/lib/centreon-engine/rw/centengine.cmd"},"command":{"type":"array","items":{"type":"string","description":"External command","example":"[653284380] SCHEDULE_SVC_CHECK;host1;service1;653284380"}}}},"HostDiscoveryJob":{"type":"object","properties":{"job_id":{"type":"integer","description":"ID of the Host Discovery job","example":14},"target":{"type":"integer","description":"Identifier of the target on which to execute the command","example":2},"command_line":{"type":"string","description":"Command line to execute to perform the discovery","example":"perl /usr/lib/centreon/plugins/centreon_generic_snmp.pl --plugin=os::linux::local::plugin --mode=discovery-snmp --subnet='10.1.2.3/24' --snmp-port='161' --snmp-version='2c' --snmp-community='public'"},"timeout":{"type":"integer","description":"Time in seconds before the command is considered timed out","example":300},"execution":{"type":"object","description":"Execution mode of this job ('0': execute immediately, '1': schedule with cron)","properties":{"mode":{"type":"integer","description":"Execution mode ('0': immediate, '1': scheduled)","example":0},"parameters":{"type":"object","description":"Parameters needed by execution mode","properties":{"cron_definition":{"type":"string","description":"Cron definition","example":"*/10 * * * *"}}}}},"post_execution":{"type":"object","description":"Post-execution settings","properties":{"commands":{"type":"array","description":"Array of commands (content depends on command)","items":{"type":"object","description":"Command","properties":{"action":{"type":"string","description":"Action to perform","example":"COMMAND"},"command_line":{"type":"string","description":"Command line to execute","example":"/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --job-id=14"}}}}}}},"required":["job_id","target","command_line","execution"]}}}}},"searchIndex":{"store":["section/Information","section/Authentication","tag/Internal","tag/Internal/paths/~1internal~1constatus/get","tag/Internal/paths/~1internal~1information/get","tag/Internal/paths/~1internal~1thumbprint/get","tag/Internal/paths/~1internal~1logger/post","tag/Logs","tag/Logs/paths/~1log~1{token}/get","tag/Cron","tag/Cron/paths/~1core~1cron~1definitions/get","tag/Cron/paths/~1core~1cron~1definitions/post","tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}/get","tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}/patch","tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}/delete","tag/Cron/paths/~1core~1cron~1definitions~1{definition_id}~1status/get","tag/Action","tag/Action/paths/~1core~1action~1command/post","tag/Engine","tag/Engine/paths/~1centreon~1engine~1command/post","tag/Statistics","tag/Statistics/paths/~1centreon~1statistics~1broker/get","tag/Statistics/paths/~1centreon~1statistics~1broker~1{monitoring_server_id}/get","tag/Autodiscovery","tag/Autodiscovery/paths/~1centreon~1autodiscovery~1hosts/post","tag/Autodiscovery/paths/~1centreon~1autodiscovery~1hosts~1{job_id}~1schedule/get","tag/Autodiscovery/paths/~1centreon~1autodiscovery~1hosts~1{token}/delete"],"index":{"version":"2.3.8","fields":["title","description"],"fieldVectors":[["title/0",[0,2.769]],["description/0",[1,1.1,2,0.93,3,1.037,4,1.09,5,1.09,6,1.09,7,2.443,8,1.09,9,0.9,10,0.606,11,0.775,12,2.387,13,1.037,14,0.606,15,0.681,16,1.09,17,0.775,18,0.9,19,1.09,20,1.09,21,0.9,22,1.09,23,1.09,24,1.09,25,1.09,26,1.09,27,0.606,28,0.775,29,1.09,30,1.09]],["title/1",[31,3.897]],["description/1",[12,3.851,32,3.657,33,3.657]],["title/2",[34,3.217]],["description/2",[34,3.473,35,2.989]],["title/3",[36,2.255,37,2.255,38,1.707]],["description/3",[2,1.614,3,1.798,36,2.67,37,2.67,38,2.02,39,2.67]],["title/4",[0,1.941,40,1.941,41,1.113]],["description/4",[0,1.866,35,1.866,40,1.866,41,1.07,42,2.626,43,1.182,44,2.626,45,2.626,46,2.626,47,2.626]],["title/5",[48,2.255,49,2.255,50,2.255]],["description/5",[2,1.713,3,1.909,48,2.833,49,2.833,50,2.833]],["title/6",[10,1.322,51,1.962,52,1.485,53,1.962]],["description/6",[10,1.909,43,1.545,51,2.833,52,2.144,53,2.833]],["title/7",[54,2.434]],["description/7",[39,3.473,54,2.628]],["title/8",[54,1.707,55,2.255,56,2.255]],["description/8",[54,2.02,55,2.67,56,3.554,57,3.234,58,3.234]],["title/9",[59,1.754]],["description/9",[2,1.375,15,1.721,17,1.958,18,2.275,35,1.958,43,1.737,60,1.532,61,2.756]],["title/10",[62,2.282,63,0.885]],["description/10",[59,1.761,62,2.78,63,1.078]],["title/11",[63,0.885,64,2.007]],["description/11",[40,2.298,59,1.456,63,0.891,64,2.02,65,1.798,66,3.234]],["title/12",[63,1.074]],["description/12",[59,1.545,62,2.439,63,0.946,67,2.833,68,2.833]],["title/13",[63,0.885,69,2.652]],["description/13",[59,1.761,63,1.078,69,3.23]],["title/14",[63,0.885,70,2.007]],["description/14",[59,1.761,63,1.078,70,2.444]],["title/15",[38,2.007,63,0.885]],["description/15",[13,2.176,38,2.444,63,1.078]],["title/16",[11,2.769]],["description/16",[2,1.252,3,1.395,11,1.782,13,1.395,21,2.071,27,1.395,28,1.782,43,1.129,60,1.395,71,1.782,72,2.508]],["title/17",[13,1.17,14,1.17,52,1.314,65,1.17,73,2.104]],["description/17",[2,1.525,10,1.7,13,1.7,14,2.305,27,1.7,71,2.172]],["title/18",[74,2.434]],["description/18",[1,1.305,3,1.612,9,2.393,43,1.305,60,1.612,74,1.811,75,2.899,76,2.899]],["title/19",[14,1.17,15,1.314,52,1.314,65,1.17,77,1.736]],["description/19",[1,0.756,10,0.934,14,2.285,15,1.049,28,1.193,71,1.193,74,1.049,77,2.733,78,1.68,79,1.68,80,1.68,81,1.68,82,1.68,83,1.68,84,1.68,85,1.68]],["title/20",[41,1.587]],["description/20",[1,1.305,41,1.181,43,1.305,60,1.612,74,1.811,86,2.899,87,1.612,88,1.612]],["title/21",[41,0.968,87,1.322,88,1.322,89,1.186]],["description/21",[41,1.245,87,1.7,88,1.7,89,1.525,90,2.524,91,2.524,92,2.524]],["title/22",[27,0.951,41,0.697,87,0.951,88,0.951,89,0.853,93,1.71,94,1.71]],["description/22",[41,1.245,87,1.7,88,1.7,89,1.525,90,2.524,91,2.524,92,2.524]],["title/23",[95,2.434]],["description/23",[1,1.376,27,1.7,43,1.376,60,1.7,95,1.91,96,3.057,97,3.057]],["title/24",[64,1.485,98,1.322,99,1.485,100,1.186]],["description/24",[1,1.376,64,1.91,65,1.7,95,1.91,98,1.7,100,1.525,101,3.057]],["title/25",[89,1.186,98,1.322,99,1.485,100,1.186]],["description/25",[59,1.24,67,2.275,68,2.275,89,1.375,98,1.532,99,1.721,100,1.375,102,2.756,103,2.756]],["title/26",[70,1.485,98,1.322,99,1.485,100,1.186]],["description/26",[1,1.456,17,2.298,65,1.798,70,2.02,95,2.02,100,1.614]]],"invertedIndex":[["",{"_index":12,"title":{},"description":{"0":{},"1":{}}}],["action",{"_index":11,"title":{"16":{}},"description":{"0":{},"16":{}}}],["add",{"_index":64,"title":{"11":{},"24":{}},"description":{"11":{},"24":{}}}],["aim",{"_index":60,"title":{},"description":{"9":{},"16":{},"18":{},"20":{},"23":{}}}],["authent",{"_index":31,"title":{"1":{}},"description":{}}],["autodiscoveri",{"_index":95,"title":{"23":{}},"description":{"23":{},"24":{},"26":{}}}],["avail",{"_index":44,"title":{},"description":{"4":{}}}],["base",{"_index":57,"title":{},"description":{"8":{}}}],["bridg",{"_index":75,"title":{},"description":{"18":{}}}],["broker",{"_index":88,"title":{"21":{},"22":{}},"description":{"20":{},"21":{},"22":{}}}],["central",{"_index":24,"title":{},"description":{"0":{}}}],["centreon",{"_index":1,"title":{},"description":{"0":{},"18":{},"19":{},"20":{},"23":{},"24":{},"26":{}}}],["collect",{"_index":87,"title":{"21":{},"22":{}},"description":{"20":{},"21":{},"22":{}}}],["command",{"_index":14,"title":{"17":{},"19":{}},"description":{"0":{},"17":{},"19":{}}}],["commun",{"_index":76,"title":{},"description":{"18":{}}}],["comput",{"_index":47,"title":{},"description":{"4":{}}}],["connect",{"_index":37,"title":{"3":{}},"description":{"3":{}}}],["cron",{"_index":59,"title":{"9":{}},"description":{"10":{},"11":{},"12":{},"13":{},"14":{},"25":{}}}],["cron-lik",{"_index":18,"title":{},"description":{"0":{},"9":{}}}],["daemon",{"_index":3,"title":{},"description":{"0":{},"3":{},"5":{},"16":{},"18":{}}}],["deal",{"_index":86,"title":{},"description":{"20":{}}}],["definit",{"_index":63,"title":{"10":{},"11":{},"12":{},"13":{},"14":{},"15":{}},"description":{"10":{},"11":{},"12":{},"13":{},"14":{},"15":{}}}],["delet",{"_index":70,"title":{"14":{},"26":{}},"description":{"14":{},"26":{}}}],["discov",{"_index":101,"title":{},"description":{"24":{}}}],["discoveri",{"_index":99,"title":{"24":{},"25":{},"26":{}},"description":{"25":{}}}],["disk",{"_index":92,"title":{},"description":{"21":{},"22":{}}}],["distribut",{"_index":5,"title":{},"description":{"0":{}}}],["endpoint",{"_index":45,"title":{},"description":{"4":{}}}],["engin",{"_index":74,"title":{"18":{}},"description":{"18":{},"19":{},"20":{}}}],["environ",{"_index":23,"title":{},"description":{"0":{}}}],["even",{"_index":102,"title":{},"description":{"25":{}}}],["event",{"_index":35,"title":{},"description":{"2":{},"4":{},"9":{}}}],["event'",{"_index":56,"title":{"8":{}},"description":{"8":{}}}],["execut",{"_index":13,"title":{"17":{}},"description":{"0":{},"15":{},"16":{},"17":{}}}],["extend",{"_index":96,"title":{},"description":{"23":{}}}],["extern",{"_index":77,"title":{"19":{}},"description":{"19":{}}}],["file",{"_index":79,"title":{},"description":{"19":{}}}],["files/directori",{"_index":16,"title":{},"description":{"0":{}}}],["format",{"_index":85,"title":{},"description":{"19":{}}}],["function",{"_index":97,"title":{},"description":{"23":{}}}],["gorgon",{"_index":2,"title":{},"description":{"0":{},"3":{},"5":{},"9":{},"16":{},"17":{}}}],["handler",{"_index":8,"title":{},"description":{"0":{}}}],["host",{"_index":98,"title":{"24":{},"25":{},"26":{}},"description":{"24":{},"25":{}}}],["id",{"_index":68,"title":{},"description":{"12":{},"25":{}}}],["identifi",{"_index":67,"title":{},"description":{"12":{},"25":{}}}],["inform",{"_index":0,"title":{"0":{},"4":{}},"description":{"4":{}}}],["instal",{"_index":22,"title":{},"description":{"0":{}}}],["instanc",{"_index":78,"title":{},"description":{"19":{}}}],["intern",{"_index":34,"title":{"2":{}},"description":{"2":{}}}],["job",{"_index":100,"title":{"24":{},"25":{},"26":{}},"description":{"24":{},"25":{},"26":{}}}],["key",{"_index":49,"title":{"5":{}},"description":{"5":{}}}],["launch",{"_index":89,"title":{"21":{},"22":{},"25":{}},"description":{"21":{},"22":{},"25":{}}}],["level",{"_index":53,"title":{"6":{}},"description":{"6":{}}}],["librari",{"_index":30,"title":{},"description":{"0":{}}}],["lightweight",{"_index":4,"title":{},"description":{"0":{}}}],["line",{"_index":73,"title":{"17":{}},"description":{}}],["list",{"_index":62,"title":{"10":{}},"description":{"10":{},"12":{}}}],["load",{"_index":42,"title":{},"description":{"4":{}}}],["log",{"_index":54,"title":{"7":{},"8":{}},"description":{"7":{},"8":{}}}],["logger",{"_index":51,"title":{"6":{}},"description":{"6":{}}}],["manag",{"_index":39,"title":{},"description":{"3":{},"7":{}}}],["method",{"_index":81,"title":{},"description":{"19":{}}}],["mode",{"_index":103,"title":{},"description":{"25":{}}}],["modul",{"_index":43,"title":{},"description":{"4":{},"6":{},"9":{},"16":{},"18":{},"20":{},"23":{}}}],["modular",{"_index":6,"title":{},"description":{"0":{}}}],["monitor",{"_index":94,"title":{"22":{}},"description":{}}],["multipl",{"_index":66,"title":{},"description":{"11":{}}}],["nagio",{"_index":84,"title":{},"description":{"19":{}}}],["need",{"_index":82,"title":{},"description":{"19":{}}}],["node",{"_index":36,"title":{"3":{}},"description":{"3":{}}}],["number",{"_index":46,"title":{},"description":{"4":{}}}],["on",{"_index":65,"title":{"17":{},"19":{}},"description":{"11":{},"24":{},"26":{}}}],["pipe",{"_index":80,"title":{},"description":{"19":{}}}],["poller",{"_index":26,"title":{},"description":{"0":{}}}],["preformat",{"_index":83,"title":{},"description":{"19":{}}}],["provid",{"_index":9,"title":{},"description":{"0":{},"18":{}}}],["public",{"_index":48,"title":{"5":{}},"description":{"5":{}}}],["push",{"_index":19,"title":{},"description":{"0":{}}}],["redoc-inject",{"_index":32,"title":{},"description":{"1":{}}}],["remot",{"_index":25,"title":{},"description":{"0":{}}}],["remotli",{"_index":72,"title":{},"description":{"16":{}}}],["reproduc",{"_index":61,"title":{},"description":{"9":{}}}],["result",{"_index":91,"title":{},"description":{"21":{},"22":{}}}],["retriev",{"_index":55,"title":{"8":{}},"description":{"8":{}}}],["run",{"_index":71,"title":{},"description":{"16":{},"17":{},"19":{}}}],["runtim",{"_index":40,"title":{"4":{}},"description":{"4":{},"11":{}}}],["schedul",{"_index":17,"title":{},"description":{"0":{},"9":{},"26":{}}}],["security-definit",{"_index":33,"title":{},"description":{"1":{}}}],["send",{"_index":15,"title":{"19":{}},"description":{"0":{},"9":{},"19":{}}}],["server",{"_index":27,"title":{"22":{}},"description":{"0":{},"16":{},"17":{},"23":{}}}],["set",{"_index":10,"title":{"6":{}},"description":{"0":{},"6":{},"17":{},"19":{}}}],["sever",{"_index":52,"title":{"6":{},"17":{},"19":{}},"description":{"6":{}}}],["specif",{"_index":93,"title":{"22":{}},"description":{}}],["ssh",{"_index":21,"title":{},"description":{"0":{},"16":{}}}],["statist",{"_index":41,"title":{"4":{},"20":{},"21":{},"22":{}},"description":{"4":{},"20":{},"21":{},"22":{}}}],["statu",{"_index":38,"title":{"3":{},"15":{}},"description":{"3":{},"15":{}}}],["store",{"_index":90,"title":{},"description":{"21":{},"22":{}}}],["task",{"_index":7,"title":{},"description":{"0":{}}}],["through",{"_index":20,"title":{},"description":{"0":{}}}],["thumbprint",{"_index":50,"title":{"5":{}},"description":{"5":{}}}],["token",{"_index":58,"title":{},"description":{"8":{}}}],["updat",{"_index":69,"title":{"13":{}},"description":{"13":{}}}],["us",{"_index":28,"title":{},"description":{"0":{},"16":{},"19":{}}}],["zeromq",{"_index":29,"title":{},"description":{"0":{}}}]],"pipeline":[]}},"options":{"hideDownloadButton":"true"}}; var container = document.getElementById('redoc'); Redoc.hydrate(__redoc_state, container);; diff --git a/gorgone/docs/modules/centreon/autodiscovery.md b/gorgone/docs/modules/centreon/autodiscovery.md index 50cb86868ad..7a7be1f3f53 100644 --- a/gorgone/docs/modules/centreon/autodiscovery.md +++ b/gorgone/docs/modules/centreon/autodiscovery.md @@ -6,7 +6,10 @@ This module aims to extend Centreon Autodiscovery server functionalities. ## Configuration -No specific configuration. +| Directive | Description | Default value | +|:----------------|:-----------------------------------------------------------------------|:--------------| +| global\_timeout | Time in seconds before a discovery command is considered timed out | `300` | +| check\_interval | Time in seconds defining frequency at which results will be search for | `15` | #### Example @@ -14,92 +17,247 @@ No specific configuration. name: autodiscovery package: "gorgone::modules::centreon::autodiscovery::hooks" enable: true +global_timeout: 60 +check_interval: 10 ``` ## Events -| Event | Description | -| :- | :- | +| Event | Description | +|:-------------------------|:------------------------------------------------| | AUTODISCOVERYREADY | Internal event to notify the core | -| AUTODISCOVERYLISTENER | Internal event to get host discovery results | +| HOSTDISCOVERYLISTENER | Internal event to get host discovery results | | SERVICEDISCOVERYLISTENER | Internal event to get service discovery results | -| ADDDISCOVERYJOB | Add a host discovery job | -| LAUNCHDISCOVERY | execute a host discovery job | -| LAUNCHSERVICEDISCOVERY | execute a service discovery job | +| ADDHOSTDISCOVERYJOB | Add a host discovery job | +| DELETEHOSTDISCOVERYJOB | Delete a host discovery job | +| LAUNCHHOSTDISCOVERY | Execute a host discovery job | +| LAUNCHSERVICEDISCOVERY | Execute a service discovery job | ## API -### Add a discovery job +### Add a host discovery job -| Endpoint | Method | -| :- | :- | -| /centreon/autodiscovery/job | `POST` | +| Endpoint | Method | +|:------------------------------|:-------| +| /centreon/autodiscovery/hosts | `POST` | #### Headers -| Header | Value | -| :- | :- | -| Accept | application/json | +| Header | Value | +|:-------------|:-----------------| +| Accept | application/json | | Content-Type | application/json | #### Body -| Key | Value | -| :- | :- | -| id | Identifier of the task (random if empty) | -| timespec | Cron-like time specification | -| command | Command line to execute to perform the discovery | -| timeout | Time in seconds before the command is considered timed out | -| target | Identifier of the target on which to execute the command | +| Key | Value | +|:----------------|:-----------------------------------------------------------| +| job\_id | ID of the Host Discovery job | +| target | Identifier of the target on which to execute the command | +| command_line | Command line to execute to perform the discovery | +| timeout | Time in seconds before the command is considered timed out | +| execution | Execution settings | +| post\_execution | Post-execution settings | + +With the following keys for the `execution` entry: + +| Key | Value | +|:-----------|:------------------------------------------------| +| mode | Execution mode ('0': immediate, '1': scheduled) | +| parameters | Parameters needed by execution mode | + +With the following keys for the `post_execution` entry: + +| Key | Value | +|:---------|:---------------------------------| +| commands | Array of commands to be executed | ```json { - "id": "<id of the task>", - "timespec": "<cron-like time specification>", - "command": "<command to execute>", + "job_id": "<id of the job>", + "target": "<target id>", + "command_line": "<command to execute>", "timeout": "<timeout in seconds>", - "target": "<target id>" + "execution": { + "mode": "<execution mode>", + "parameters": "<execution parameters>", + }, + "post_execution": { + "commands": "<array of commands>", + } } ``` -#### Example +#### Examples + +#### Execute immediately without post-execution commands + +```bash +curl --request POST "https://hostname:8443/api/centreon/autodiscovery/hosts" \ + --header "Accept: application/json" \ + --header "Content-Type: application/json" \ + --data "{ + \"job_id\": 14, + \"target\": 3, + \"command_line\": \"perl /usr/lib/centreon/plugins/centreon_generic_snmp.pl --plugin=os::linux::local::plugin --mode=discovery-snmp --subnet='10.1.2.3/24' --snmp-port='161' --snmp-version='2c' --snmp-community='public'\", + \"timeout\": 300, + \"execution\": { + \"mode\": 0, + \"parameters\": {} + }, + \"post_execution\": {} +}" +``` + +#### Execute immediately with post-execution commands + +```bash +curl --request POST "https://hostname:8443/api/centreon/autodiscovery/hosts" \ + --header "Accept: application/json" \ + --header "Content-Type: application/json" \ + --data "{ + \"job_id\": 14, + \"target\": 3, + \"command_line\": \"perl /usr/lib/centreon/plugins/centreon_generic_snmp.pl --plugin=os::linux::local::plugin --mode=discovery-snmp --subnet='10.1.2.3/24' --snmp-port='161' --snmp-version='2c' --snmp-community='public'\", + \"timeout\": 300, + \"execution\": { + \"mode\": 0, + \"parameters\": {} + }, + \"post_execution\": { + \"commands\": [ + { + \"action\": \"COMMAND\", + \"command_line\": \"/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --job-id=14\" + } + ] + } +}" +``` + +#### Schedule execution without post-execution commands + +```bash +curl --request POST "https://hostname:8443/api/centreon/autodiscovery/hosts" \ + --header "Accept: application/json" \ + --header "Content-Type: application/json" \ + --data "{ + \"job_id\": 14, + \"target\": 3, + \"command_line\": \"perl /usr/lib/centreon/plugins/centreon_generic_snmp.pl --plugin=os::linux::local::plugin --mode=discovery-snmp --subnet='10.1.2.3/24' --snmp-port='161' --snmp-version='2c' --snmp-community='public'\", + \"timeout\": 300, + \"execution\": { + \"mode\": 1, + \"parameters\": { + \"cron_definition\": \"*/10 * * * *\" + } + }, + \"post_execution\": {} +}" +``` + +#### Schedule execution with post-execution commands ```bash -curl --request POST "https://hostname:8443/api/centreon/autodiscovery/job" \ +curl --request POST "https://hostname:8443/api/centreon/autodiscovery/hosts" \ --header "Accept: application/json" \ --header "Content-Type: application/json" \ --data "{ - \"timespec\": \"0 10 * * *\", - \"command\": \"perl /usr/lib/centreon/plugins/centreon_generic_snmp.pl --plugin=os::linux::local::plugin --mode=discovery-snmp --subnet='10.1.2.3/24' --snmp-port='161' --snmp-version='2c' --snmp-community='public'\", + \"job_id\": 14, + \"target\": 3, + \"command_line\": \"perl /usr/lib/centreon/plugins/centreon_generic_snmp.pl --plugin=os::linux::local::plugin --mode=discovery-snmp --subnet='10.1.2.3/24' --snmp-port='161' --snmp-version='2c' --snmp-community='public'\", \"timeout\": 300, - \"target\": 2 + \"execution\": { + \"mode\": 1, + \"parameters\": { + \"cron_definition\": \"*/10 * * * *\" + } + }, + \"post_execution\": { + \"commands\": [ + { + \"action\": \"COMMAND\", + \"command_line\": \"/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --job-id=14\" + } + ] + } }" ``` +### Launch a host discovery job + +| Endpoint | Method | +|:-------------------------------------------|:-------| +| /centreon/autodiscovery/hosts/:id/schedule | `GET` | + +#### Headers + +| Header | Value | +|:-------------|:-----------------| +| Accept | application/json | + +#### Path variables + +| Variable | Description | +|:---------|:----------------------| +| id | Identifier of the job | + +#### Example + +```bash +curl --request GET "https://hostname:8443/api/centreon/autodiscovery/hosts/:id/schedule" \ + --header "Accept: application/json" +``` + +### Delete a host discovery job + +| Endpoint | Method | +|:-------------------------------------|:---------| +| /centreon/autodiscovery/hosts/:token | `DELETE` | + +#### Headers + +| Header | Value | +|:-------|:-----------------| +| Accept | application/json | + +#### Path variables + +| Variable | Description | +|:---------|:---------------------------| +| token | Token of the scheduled job | + +#### Example + +```bash +curl --request DELETE "https://hostname:8443/api/centreon/autodiscovery/hosts/discovery_14_6b7d1bb8" \ + --header "Accept: application/json" +``` + ### Execute a service discovery job -| Endpoint | Method | -| :- | :- | +| Endpoint | Method | +|:---------------------------------|:-------| | /centreon/autodiscovery/services | `POST` | #### Headers -| Header | Value | -| :- | :- | -| Accept | application/json | +| Header | Value | +|:-------------|:-----------------| +| Accept | application/json | | Content-Type | application/json | #### Body -| Key | Value | Description | -| :- | :- | :- | -| filter_rules | array | Run the selected rule of discovery | -| force_rule | `1|0` | Run also disabled rules | -| filter_hosts | array | Run all discovery rules linked to all templates of host used by selected host | -| filter_pollers | array | Run all discovery rules linked to all poller linked with rule | -| manual | `1|0` | Run discovery for manual scan | -| dry_run | `1|0` | Run discovery without configuration change | -| no_generate_config | `1|0` | No configuration generation (even if there is some changes) | +| Key | Value | Description | +|:---------------------|:------|:------------------------------------------------------------------------------| +| filter\_rules | array | Run the selected rule of discovery | +| force\_rule | `1|0` | Run also disabled rules | +| filter\_hosts | array | Run all discovery rules linked to all templates of host used by selected host | +| filter\_pollers | array | Run all discovery rules linked to all poller linked with rule | +| manual | `1|0` | Run discovery for manual scan | +| dry\_run | `1|0` | Run discovery without configuration change | +| no\_generate\_config | `1|0` | No configuration generation (even if there is some changes) | ```json { diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index e4864de9a3b..fd8528b75c2 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -190,7 +190,7 @@ sub init { force => 2, logger => $gorgone->{logger} ); - + $self->{hostname} = $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{hostname}; if (!defined($self->{hostname}) || $self->{hostname} eq '') { $self->{hostname} = hostname(); @@ -322,7 +322,7 @@ sub load_module { my ($loaded, $namespace, $name, $events) = $self->{modules_register}->{$package}->{register}->( config => $options{config_module}, - config_core => $self->{config}->{configuration}->{gorgone}->{gorgonecore}, + config_core => $self->{config}->{configuration}->{gorgone}, config_db_centreon => $self->{config}->{configuration}->{centreon}->{database}->{db_configuration}, config_db_centstorage => $self->{config}->{configuration}->{centreon}->{database}->{db_realtime}, logger => $self->{logger}, diff --git a/gorgone/gorgone/class/http/backend/curl.pm b/gorgone/gorgone/class/http/backend/curl.pm index 3780a020182..203d2d52e43 100644 --- a/gorgone/gorgone/class/http/backend/curl.pm +++ b/gorgone/gorgone/class/http/backend/curl.pm @@ -149,6 +149,7 @@ sub set_method { my ($self, %options) = @_; $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_CUSTOMREQUEST'), parameter => undef); + $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_POSTFIELDS'), parameter => undef); $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_HTTPGET'), parameter => 1); if ($options{request}->{method} eq 'GET') { @@ -337,7 +338,7 @@ sub request { $self->{response_headers} = [{}]; $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_HEADERDATA'), parameter => $self); $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_HEADERFUNCTION'), parameter => \&cb_get_header); - + eval { $self->{curl_easy}->perform(); }; diff --git a/gorgone/gorgone/class/http/http.pm b/gorgone/gorgone/class/http/http.pm index a6c2b5360c4..fc659354edb 100644 --- a/gorgone/gorgone/class/http/http.pm +++ b/gorgone/gorgone/class/http/http.pm @@ -169,7 +169,7 @@ sub request { foreach (keys %options) { $request_options->{$_} = $options{$_} if (defined($options{$_})); } - $self->check_options(request => $request_options); + return 1 if ($self->check_options(request => $request_options)); return $self->{'backend_' . $self->{http_backend}}->request(request => $request_options); } diff --git a/gorgone/gorgone/class/listener.pm b/gorgone/gorgone/class/listener.pm index 74bdfee214a..3ff37553fda 100644 --- a/gorgone/gorgone/class/listener.pm +++ b/gorgone/gorgone/class/listener.pm @@ -59,6 +59,7 @@ sub add_listener { my ($self, %options) = @_; $self->{logger}->writeLogDebug("[listener] add token '$options{token}'"); + # an issue can happen if the event is unknown (recursive loop) if (!defined($self->{tokens}->{$options{token}})) { my ($log_pace, $timeout) = (30, 600); $log_pace = $1 if (defined($options{log_pace}) && $options{log_pace} =~ /(\d+)/); diff --git a/gorgone/gorgone/class/logger.pm b/gorgone/gorgone/class/logger.pm index c0b553b83dd..6af66a89017 100644 --- a/gorgone/gorgone/class/logger.pm +++ b/gorgone/gorgone/class/logger.pm @@ -134,6 +134,12 @@ sub redirect_output { } } +sub flush_output { + my ($self, %options) = @_; + + $| = 1 if (defined($options{enabled})); +} + sub force_default_severity { my ($self, %options) = @_; diff --git a/gorgone/gorgone/class/module.pm b/gorgone/gorgone/class/module.pm index 8b840344efb..57b64faf94b 100644 --- a/gorgone/gorgone/class/module.pm +++ b/gorgone/gorgone/class/module.pm @@ -26,8 +26,30 @@ use warnings; use gorgone::standard::constants qw(:all); use gorgone::standard::library; use gorgone::standard::misc; +use gorgone::class::tpapi; use JSON::XS; +sub new { + my ($class, %options) = @_; + my $self = {}; + bless $self, $class; + + $self->{internal_socket} = undef; + $self->{module_id} = $options{module_id}; + $self->{core_id} = $options{core_id}; + $self->{logger} = $options{logger}; + $self->{config} = $options{config}; + $self->{config_core} = $options{config_core}->{gorgonecore}; + $self->{config_db_centreon} = $options{config_db_centreon}; + $self->{config_db_centstorage} = $options{config_db_centstorage}; + $self->{stop} = 0; + + $self->{tpapi} = gorgone::class::tpapi->new(); + $self->{tpapi}->load_configuration(configuration => $options{config_core}->{tpapi}); + + return $self; +} + sub generate_token { my ($self, %options) = @_; diff --git a/gorgone/gorgone/class/script.pm b/gorgone/gorgone/class/script.pm index 4c54847e88e..3727ecf1f67 100644 --- a/gorgone/gorgone/class/script.pm +++ b/gorgone/gorgone/class/script.pm @@ -55,10 +55,11 @@ sub new { $self->{name} = $name; $self->{logger} = gorgone::class::logger->new(); $self->{options} = { - 'config=s' => \$self->{config_file}, - 'logfile=s' => \$self->{log_file}, - 'severity=s' => \$self->{severity}, - 'help|?' => \$self->{help} + 'config=s' => \$self->{config_file}, + 'logfile=s' => \$self->{log_file}, + 'severity=s' => \$self->{severity}, + 'flushoutput' => \$self->{flushoutput}, + 'help|?' => \$self->{help} }; return $self; } @@ -69,6 +70,7 @@ sub init { if (defined $self->{log_file}) { $self->{logger}->file_mode($self->{log_file}); } + $self->{logger}->flush_output(enabled => $self->{flushoutput}); $self->{logger}->severity($self->{severity}); $self->{logger}->force_default_severity(); diff --git a/gorgone/gorgone/class/tpapi.pm b/gorgone/gorgone/class/tpapi.pm new file mode 100644 index 00000000000..27b6697e848 --- /dev/null +++ b/gorgone/gorgone/class/tpapi.pm @@ -0,0 +1,55 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::class::tpapi; + +use strict; +use warnings; + +sub new { + my ($class, %options) = @_; + my $self = {}; + bless $self, $class; + + $self->{configs} = {}; + + return $self; +} + +sub get_configuration { + my ($self, %options) = @_; + + return $self->{configs}->{ $options{name} }; +} + +sub load_configuration { + my ($self, %options) = @_; + + $self->{configs} = {}; + return if (!defined($options{configuration})); + + foreach my $config (@{$options{configuration}}) { + next if (!defined($config->{name})); + + $self->{configs}->{ $config->{name} } = $config; + } +} + +1; diff --git a/gorgone/gorgone/class/tpapi/centreonv2.pm b/gorgone/gorgone/class/tpapi/centreonv2.pm new file mode 100644 index 00000000000..31432f8cb33 --- /dev/null +++ b/gorgone/gorgone/class/tpapi/centreonv2.pm @@ -0,0 +1,295 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::class::tpapi::centreonv2; + +use strict; +use warnings; +use JSON::XS; + +sub new { + my ($class, %options) = @_; + my $self = {}; + bless $self, $class; + + $self->{is_error} = 1; + $self->{error} = 'configuration missing'; + $self->{is_logged} = 0; + + return $self; +} + +sub json_decode { + my ($self, %options) = @_; + + my $decoded; + eval { + $decoded = JSON::XS->new->utf8->decode($options{content}); + }; + if ($@) { + $self->{is_error} = 1; + $self->{error} = "cannot decode json response: $@"; + return undef; + } + + return $decoded; +} + +sub error { + my ($self, %options) = @_; + + return $self->{error}; +} + +sub set_configuration { + my ($self, %options) = @_; + + if (!defined($options{config})) { + return 1; + } + + foreach (('base_url', 'username', 'password')) { + if (!defined($options{config}->{$_}) || + $options{config}->{$_} eq '') { + $self->{error} = $_ . ' configuration missing'; + return 1; + } + + $self->{$_} = $options{config}->{$_}; + } + + $self->{base_url} =~ s/\/$//; + + $self->{http_backend} = defined($options{config}->{backend}) ? $options{config}->{backend} : 'curl'; + + $self->{curl_opts} = ['CURLOPT_SSL_VERIFYPEER => 0']; + my $curl_opts = []; + if (defined($options{config}->{curlopts})) { + foreach (keys %{$options{config}->{curlopts}}) { + push @{$curl_opts}, $_ . ' => ' . $options{config}->{curlopts}->{$_}; + } + } + if (scalar(@$curl_opts) > 0) { + $self->{curl_opts} = $curl_opts; + } + + $self->{http} = gorgone::class::http::http->new(logger => $options{logger}); + $self->{is_error} = 0; + return 0; +} + +sub authenticate { + my ($self, %options) = @_; + + my $json_request = { + security => { + credentials => { + login => $self->{username}, + password => $self->{password} + } + } + }; + my $encoded; + eval { + $encoded = encode_json($json_request); + }; + if ($@) { + $self->{is_error} = 1; + $self->{error} = "cannot encode json request: $@"; + return undef; + } + + my ($code, $content) = $self->{http}->request( + http_backend => $self->{http_backend}, + method => 'POST', + hostname => '', + full_url => $self->{base_url} . '/login', + query_form_post => $encoded, + header => [ + 'Accept-Type: application/json; charset=utf-8', + 'Content-Type: application/json; charset=utf-8', + ], + curl_opt => $self->{curl_opts}, + warning_status => '', + unknown_status => '', + critical_status => '' + ); + if ($code) { + $self->{is_error} = 1; + $self->{error} = 'http request error'; + return undef; + } + if ($self->{http}->get_code() < 200 || $self->{http}->get_code() >= 300) { + $self->{is_error} = 1; + $self->{error} = "Login error [code: '" . $self->{http}->get_code() . "'] [message: '" . $self->{http}->get_message() . "']"; + return undef; + } + + my $decoded = $self->json_decode(content => $content); + return if (!defined($decoded)); + + my $token = defined($decoded->{security}->{token}) ? $decoded->{security}->{token} : undef; + if (!defined($token)) { + $self->{is_error} = 1; + $self->{error} = 'authenticate issue - cannot get token'; + return undef; + } + + $self->{token} = $token; + $self->{is_logged} = 1; +} + +sub request { + my ($self, %options) = @_; + + if (!defined($self->{base_url})) { + $self->{is_error} = 1; + $self->{error} = 'configuration missing'; + return 1; + } + + $self->{is_error} = 0; + if ($self->{is_logged} == 0) { + $self->authenticate(); + } + + return 1 if ($self->{is_logged} == 0); + + # TODO: manage it properly + my $get_param = ['page=1', 'limit=10000']; + if (defined($options{get_param})) { + push @$get_param, @{$options{get_param}}; + } + + my ($code, $content) = $self->{http}->request( + http_backend => $self->{http_backend}, + method => $options{method}, + hostname => '', + full_url => $self->{base_url} . $options{endpoint}, + query_form_post => $options{query_form_post}, + get_param => $get_param, + header => [ + 'Accept-Type: application/json; charset=utf-8', + 'Content-Type: application/json; charset=utf-8', + 'X-AUTH-TOKEN: ' . $self->{token} + ], + curl_opt => $self->{curl_opts}, + warning_status => '', + unknown_status => '', + critical_status => '' + ); + + # code 403 means forbidden (token not good maybe) + if ($self->{http}->get_code() == 403) { + $self->{token} = undef; + $self->{is_logged} = 0; + $self->{is_error} = 1; + $self->{error} = 'token forbidden'; + return 1; + } + + if ($self->{http}->get_code() < 200 || $self->{http}->get_code() >= 300) { + $self->{is_error} = 1; + $self->{error} = "request error [code: '" . $self->{http}->get_code() . "'] [message: '" . $self->{http}->get_message() . "']"; + return 1; + } + + my $decoded = $self->json_decode(content => $content); + return 1 if (!defined($decoded)); + + return (0, $decoded); +} + +sub get_monitoring_hosts { + my ($self, %options) = @_; + + my $endpoint = '/monitoring/hosts'; + $endpoint .= '/' . $options{host_id} if (defined($options{host_id})); + + my $get_param; + if (defined($options{search})) { + $get_param = ['search=' . $options{search}]; + } + + return $self->request( + method => 'GET', + endpoint => $endpoint, + get_param => $get_param + ); +} + +sub get_scheduling_jobs { + my ($self, %options) = @_; + +=pod + my $results = [ + { + execution => { + parameters => { + cron_definition => "* * * * *", + is_paused => 0 + }, + mode => 1 + }, + post_execution => { + commands => [ + { + action => 'COMMAND', + command_line => '/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=9' + } + ] + }, + job_id => 9, + token => "discovery_9_f2b0ea11", + command_line => "/usr/lib/centreon/plugins/centreon_generic_snmp.pl --plugin=os::linux::local::plugin --mode=discovery-snmp --subnet='127.0.0.1/32' --snmp-community='public' --snmp-version='2c' --snmp-port='161' --snmp-timeout='1' \$_EXTRAOPTIONS\$", + target => 1, + status => 1, + last_execution => undef, + uuid_attributes => ["hostname", "ip"] + } + ]; + return (0, $results); +=cut + + my $get_param; + if (defined($options{search})) { + $get_param = ['search=' . $options{search}]; + } + + my $endpoint = '/auto-discovery/scheduling/jobs'; + return $self->request( + method => 'GET', + endpoint => $endpoint, + get_param => $get_param + ); +} + +sub DESTROY { + my ($self) = @_; + + if ($self->{is_logged} == 1) { + $self->request( + method => 'GET', + endpoint => '/logout' + ); + } +} + +1; diff --git a/gorgone/gorgone/class/tpapi/clapi.pm b/gorgone/gorgone/class/tpapi/clapi.pm new file mode 100644 index 00000000000..0b91ebc716d --- /dev/null +++ b/gorgone/gorgone/class/tpapi/clapi.pm @@ -0,0 +1,87 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::class::tpapi::clapi; + +use strict; +use warnings; + +sub new { + my ($class, %options) = @_; + my $self = {}; + bless $self, $class; + + $self->{is_error} = 1; + $self->{error} = 'configuration missing'; + $self->{username} = undef; + $self->{password} = undef; + + return $self; +} + +sub error { + my ($self, %options) = @_; + + return $self->{error}; +} + +sub get_username { + my ($self, %options) = @_; + + if ($self->{is_error} == 1) { + return undef; + } + + return $self->{username}; +} + +sub set_configuration { + my ($self, %options) = @_; + + if (!defined($options{config}) || + !defined($options{config}->{username}) || + $options{config}->{username} eq '') { + $self->{error} = 'username configuration missing'; + return 1; + } + + if (!defined($options{config}->{password}) || + $options{config}->{password} eq '') { + $self->{error} = 'password configuration missing'; + return 1; + } + + $self->{is_error} = 0; + $self->{username} = $options{config}->{username}; + $self->{password} = $options{config}->{password}; + return 0; +} + +sub get_applycfg_command { + my ($self, %options) = @_; + + if ($self->{is_error} == 1) { + return undef; + } + + return 'centreon -u ' . $options{config}->{username} . ' -p ' . $options{config}->{password} . ' -a APPLYCFG -v ' . $options{poller_id}; +} + +1; diff --git a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm index 10b0d029457..488474f0dff 100644 --- a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm +++ b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm @@ -39,15 +39,8 @@ my ($connector); sub new { my ($class, %options) = @_; - - $connector = {}; - $connector->{internal_socket} = undef; - $connector->{module_id} = $options{module_id}; - $connector->{logger} = $options{logger}; - $connector->{config} = $options{config}; - $connector->{config_core} = $options{config_core}; - $connector->{config_db_centreon} = $options{config_db_centreon}; - $connector->{stop} = 0; + $connector = $class->SUPER::new(%options); + bless $connector, $class; $connector->{resync_time} = (defined($options{config}->{resync_time}) && $options{config}->{resync_time} =~ /(\d+)/) ? $1 : 600; $connector->{last_resync_time} = -1; @@ -57,7 +50,6 @@ sub new { $connector->{centreon_metrics} = {}; $connector->{unregister_metrics_centreon} = {}; - bless $connector, $class; $connector->set_signal_handlers(); return $connector; } diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm index 43eb35b7286..c2173d1fd47 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm @@ -27,6 +27,8 @@ use warnings; use gorgone::standard::library; use gorgone::standard::constants qw(:all); use gorgone::modules::centreon::autodiscovery::services::discovery; +use gorgone::class::tpapi::clapi; +use gorgone::class::tpapi::centreonv2; use gorgone::class::sqlquery; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); @@ -35,30 +37,48 @@ use Time::HiRes; use POSIX qw(strftime); use Digest::MD5 qw(md5_hex); +use constant JOB_SCHEDULED => 0; +use constant JOB_FINISH => 1; +use constant JOB_FAILED => 2; +use constant JOB_RUNNING => 3; +use constant SAVE_RUNNING => 4; +use constant SAVE_FINISH => 5; +use constant SAVE_FAILED => 6; + +use constant CRON_ADDED_NONE => 0; +use constant CRON_ADDED_OK => 1; +use constant CRON_ADDED_KO => 2; +use constant CRON_ADDED_PROGRESS => 3; + +use constant EXECUTION_MODE_IMMEDIATE => 0; +use constant EXECUTION_MODE_CRON => 1; +use constant EXECUTION_MODE_PAUSE => 2; + +use constant MAX_INSERT_BY_QUERY => 100; + my %handlers = (TERM => {}, HUP => {}); my ($connector); sub new { my ($class, %options) = @_; - - $connector = {}; - $connector->{internal_socket} = undef; - $connector->{module_id} = $options{module_id}; - $connector->{logger} = $options{logger}; - $connector->{config} = $options{config}; - $connector->{config_core} = $options{config_core}; - $connector->{config_db_centreon} = $options{config_db_centreon}; - $connector->{config_db_centstorage} = $options{config_db_centstorage}; - $connector->{stop} = 0; + $connector = $class->SUPER::new(%options); + bless $connector, $class; $connector->{global_timeout} = (defined($options{config}->{global_timeout}) && $options{config}->{global_timeout} =~ /(\d+)/) ? $1 : 300; $connector->{check_interval} = (defined($options{config}->{check_interval}) && $options{config}->{check_interval} =~ /(\d+)/) ? $1 : 15; + $connector->{tpapi_clapi_name} = defined($options{config}->{tpapi_clapi}) && $options{config}->{tpapi_clapi} ne '' ? $options{config}->{tpapi_clapi} : 'clapi'; + $connector->{tpapi_centreonv2_name} = defined($options{config}->{tpapi_centreonv2}) && $options{config}->{tpapi_centreonv2} ne '' ? + $options{config}->{tpapi_centreonv2} : 'centreonv2'; + + $connector->{hdisco_synced} = 0; + $connector->{hdisco_synced_time} = -1; + $connector->{hdisco_jobs_tokens} = {}; + $connector->{hdisco_jobs_ids} = {}; $connector->{service_discoveries} = {}; - bless $connector, $class; $connector->set_signal_handlers(); return $connector; } @@ -101,111 +121,345 @@ sub class_handle_HUP { Host Discovery part ******************* +For cron job, we use discovery token as cron ID. + =cut -sub action_adddiscoveryjob { +sub hdisco_is_running_job { + my ($self, %options) = @_; + + if ($options{status} == JOB_RUNNING || + $options{status} == SAVE_RUNNING) { + return 1; + } + + return 0; +} + +sub hdisco_add_cron { + my ($self, %options) = @_; + + if (!defined($options{job}->{execution}->{parameters}->{cron_definition}) || + $options{job}->{execution}->{parameters}->{cron_definition} eq '') { + return (1, "missing 'cron_definition' parameter"); + } + + $self->send_internal_action( + action => 'ADDLISTENER', + data => [ + { + identity => 'gorgoneautodiscovery', + event => 'HOSTDISCOVERYCRONLISTENER', + token => 'cron-' . $options{discovery_token} + } + ] + ); + + $self->{logger}->writeLogDebug("[autodiscovery] -class- host discovery - add cron for job '" . $options{job}->{job_id} . "'"); + my $definition = { + id => $options{discovery_token}, + timespec => $options{job}->{execution}->{parameters}->{cron_definition}, + action => 'LAUNCHHOSTDISCOVERY', + parameters => { + job_id => $options{job}->{job_id}, + timeout => $self->{global_timeout} + } + }; + $self->send_internal_action( + action => 'ADDCRON', + token => 'cron-' . $options{discovery_token}, + data => { + content => [ $definition ] + } + ); + + return 0; +} + +sub hdisco_addupdate_job { + my ($self, %options) = @_; + my ($status, $message); + + my $update = 0; + my $extra_infos = { cron_added => CRON_ADDED_NONE, listener_added => 0 }; + if (defined($self->{hdisco_jobs_ids}->{ $options{job}->{job_id} })) { + $extra_infos = $self->{hdisco_jobs_ids}->{ $options{job}->{job_id} }->{extra_infos}; + $update = 1; + } else { + $self->{logger}->writeLogDebug("[autodiscovery] -class- host discovery - new job '" . $options{job}->{job_id} . "'"); + # it's running so we have a token + if ($self->hdisco_is_running_job(status => $options{job}->{status})) { + $extra_infos->{listener_added} = 1; + $self->hdisco_add_joblistener( + jobs => [ + { job_id => $options{job}->{job_id}, target => $options{job}->{target}, token => $options{job}->{token} } + ] + ); + } + } + + # cron changed: we remove old definition + # right now: can be immediate or schedule (not both) + if ($update == 1 && + ($self->{hdisco_jobs_ids}->{ $options{job}->{job_id} }->{execution}->{mode} == EXECUTION_MODE_IMMEDIATE || + (defined($self->{hdisco_jobs_ids}->{ $options{job}->{job_id} }->{execution}->{parameters}->{cron_definition}) && + defined($options{job}->{execution}->{parameters}->{cron_definition}) && + $self->{hdisco_jobs_ids}->{ $options{job}->{job_id} }->{execution}->{parameters}->{cron_definition} ne $options{job}->{execution}->{parameters}->{cron_definition} + ) + ) + ) { + $self->hdisco_delete_cron(discovery_token => $options{job}->{token}); + $extra_infos->{cron_added} = CRON_ADDED_NONE; + } + + $self->{hdisco_jobs_ids}->{ $options{job}->{job_id} } = $options{job}; + $self->{hdisco_jobs_ids}->{ $options{job}->{job_id} }->{extra_infos} = $extra_infos; + if (!defined($options{job}->{token})) { + my $discovery_token = 'discovery_' . $options{job}->{job_id} . '_' . $self->generate_token(length => 4); + if ($self->update_job_information( + values => { + token => $discovery_token + }, + where_clause => [ + { id => $options{job}->{job_id} } + ] + ) == -1) { + return (1, 'cannot add discovery token'); + } + + $self->{hdisco_jobs_ids}->{ $options{job}->{job_id} }->{token} = $discovery_token; + $options{job}->{token} = $discovery_token; + } + + if (defined($options{job}->{token})) { + $self->{hdisco_jobs_tokens}->{ $options{job}->{token} } = $options{job}->{job_id}; + } + + if ($self->{hdisco_jobs_ids}->{ $options{job}->{job_id} }->{execution}->{mode} == EXECUTION_MODE_CRON && + ($extra_infos->{cron_added} == CRON_ADDED_NONE || $extra_infos->{cron_added} == CRON_ADDED_KO) + ) { + ($status, $message) = $self->hdisco_add_cron(job => $options{job}, discovery_token => $options{job}->{token}); + return ($status, $message) if ($status); + $self->{hdisco_jobs_ids}->{ $options{job}->{job_id} }->{extra_infos}->{cron_added} = CRON_ADDED_PROGRESS; + } + + return 0; +} + +sub hdisco_sync { + my ($self, %options) = @_; + + return if ($self->{hdisco_synced} == 1 && (time() - $self->{hdisco_synced_time}) < 600); + + $self->{logger}->writeLogInfo('[autodiscovery] -class- host discovery - sync started'); + my ($status, $results, $message); + + ($status, $results) = $self->{tpapi_centreonv2}->get_scheduling_jobs(); + if ($status != 0) { + $self->{logger}->writeLogError('[autodiscovery] -class- host discovery - cannot get host discovery jobs - ' . $self->{tpapi_centreonv2}->error()); + return ; + } + + my $jobs = {}; + foreach my $job (@{$results->{result}}) { + ($status, $message) = $self->hdisco_addupdate_job(job => $job); + if ($status) { + $self->{logger}->writeLogError('[autodiscovery] -class- host discovery - addupdate job - ' . $message); + } + + $jobs->{ $job->{job_id} } = 1; + } + + foreach my $job_id (keys %{$self->{hdisco_jobs_ids}}) { + next if (defined($jobs->{$job_id})); + + $self->{logger}->writeLogDebug("[autodiscovery] -class- host discovery - delete job '" . $job_id . "'"); + if (defined($self->{hdisco_jobs_ids}->{$job_id}->{token})) { + $self->hdisco_delete_cron(discovery_token => $self->{hdisco_jobs_ids}->{$job_id}->{token}); + delete $self->{hdisco_jobs_tokens}->{ $self->{hdisco_jobs_ids}->{$job_id}->{token} }; + } + delete $self->{hdisco_jobs_ids}->{$job_id}; + } + + $self->{hdisco_synced_time} = time(); + $self->{hdisco_synced} = 1; +} + +sub get_host_job { + my ($self, %options) = @_; + + my ($status, $results) = $self->{tpapi_centreonv2}->get_scheduling_jobs(search => '{"id": ' . $options{job_id} . '}'); + if ($status != 0) { + return (1, "cannot get host discovery job '$options{data}->{content}->{job_id}' - " . $self->{tpapi_centreonv2}->error()); + } + + my $job; + foreach my $entry (@{$results->{result}}) { + if ($entry->{job_id} == $options{job_id}) { + $job = $entry; + last; + } + } + + return (0, 'ok', $job); +} + +sub hdisco_delete_cron { + my ($self, %options) = @_; + + return if (!defined($self->{hdisco_jobs_tokens}->{ $options{discovery_token} })); + my $job_id = $self->{hdisco_jobs_tokens}->{ $options{discovery_token} }; + return if ( + $self->{hdisco_jobs_ids}->{$job_id}->{extra_infos}->{cron_added} == CRON_ADDED_NONE || + $self->{hdisco_jobs_ids}->{$job_id}->{extra_infos}->{cron_added} == CRON_ADDED_KO + ); + + $self->{logger}->writeLogInfo("[autodiscovery] -class- host discovery - delete job '" . $job_id . "'"); + + $self->send_internal_action( + action => 'DELETECRON', + token => $options{token}, + data => { + variables => [ $options{discovery_token} ] + } + ); +} + +sub action_addhostdiscoveryjob { my ($self, %options) = @_; - - return if (!$self->is_module_installed()); $options{token} = $self->generate_token() if (!defined($options{token})); - my $token = 'autodiscovery_' . $self->generate_token(length => 8); - - # Scheduled - my $query = "UPDATE mod_host_disco_job " . - "SET status = '0', duration = '0', discovered_items = '0', message = 'Scheduled' " . - "WHERE id = '" . $options{data}->{content}->{job_id} . "'"; - my $status = $self->{class_object_centreon}->transaction_query(request => $query); - if ($status == -1) { - $self->{logger}->writeLogError('[autodiscovery] Failed to update job status'); + if (!$self->is_hdisco_synced()) { + $self->send_log( + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + data => { + message => 'host discovery synchronization issue' + } + ); + return ; + } + + my ($status, $message, $job); + ($status, $message, $job) = $self->get_host_job(job_id => $options{data}->{content}->{job_id}); + if ($status != 0) { + $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - cannot get host discovery job '$options{data}->{content}->{job_id}' - " . $self->{tpapi_centreonv2}->error()); + $self->send_log( + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + data => { + message => "cannot get job '$options{data}->{content}->{job_id}'" + } + ); return 1; } - # Retrieve uuid attributes - my $result; - $query = "SELECT uuid_attributes " . - "FROM mod_host_disco_provider mhdp " . - "JOIN mod_host_disco_job mhdj " . - "WHERE mhdj.provider_id = mhdp.id AND mhdj.id = '" . $options{data}->{content}->{job_id} . "'"; - ($status, $result) = $self->{class_object_centreon}->custom_execute(request => $query, mode => 2); - if ($status == -1) { - $self->{logger}->writeLogError('[autodiscovery] Failed to retrieve uuid attributes'); + if (!defined($job)) { + $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - cannot get host discovery job '$options{data}->{content}->{job_id}' - " . $self->{tpapi_centreonv2}->error()); + $self->send_log( + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + data => { + message => "cannot get job '$options{data}->{content}->{job_id}'" + } + ); return 1; } - - my $uuid_attributes = $result->[0]->[0]; - my $timeout = (defined($options{data}->{content}->{timeout}) && $options{data}->{content}->{timeout} =~ /(\d+)/) ? - $options{data}->{content}->{timeout} : $self->{global_timeout}; + ($status, $message) = $self->hdisco_addupdate_job(job => $job); + if ($status) { + $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - add job '$options{data}->{content}->{job_id}' - $message"); + $self->send_log( + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + data => { + message => "add job '$options{data}->{content}->{job_id}' - $message" + } + ); + return 1; + } - if ($options{data}->{content}->{execution_mode} == 0) { - # Execute immediately - $self->action_launchdiscovery( + # Launch a immediate job. + if ($self->{hdisco_jobs_ids}->{ $options{data}->{content}->{job_id} }->{execution}->{mode} == EXECUTION_MODE_IMMEDIATE) { + my $timeout = (defined($options{data}->{content}->{timeout}) && $options{data}->{content}->{timeout} =~ /(\d+)/) ? + $options{data}->{content}->{timeout} : $self->{global_timeout}; + ($status, $message) = $self->action_launchhostdiscovery( data => { content => { - target => $options{data}->{content}->{target}, - command => $options{data}->{content}->{command}, timeout => $timeout, - job_id => $options{data}->{content}->{job_id}, - uuid_attributes => $uuid_attributes, - token => $token + job_id => $options{data}->{content}->{job_id} } } ); - } else { - # Schedule with cron - $self->{logger}->writeLogInfo("[autodiscovery] Add cron '" . $token . "' for job '" . $options{data}->{content}->{job_id} . "'"); - my $definition = { - id => $token, - target => '1', - timespec => $options{data}->{content}->{timespec}, - action => 'LAUNCHDISCOVERY', - parameters => { - target => $options{data}->{content}->{target}, - command => $options{data}->{content}->{command}, - timeout => $timeout, - job_id => $options{data}->{content}->{job_id}, - uuid_attributes => $uuid_attributes, - token => $token - }, - keep_token => 1, - }; - - $self->send_internal_action( - action => 'ADDCRON', - token => $options{token}, - data => { - content => [ $definition ], - } - ); + if ($status) { + $self->send_log( + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + data => { + message => "launch issue - $message" + } + ); + return 1; + } } $self->send_log( code => GORGONE_ACTION_FINISH_OK, token => $options{token}, data => { - id => $token + message => 'job ' . $options{data}->{content}->{job_id} . ' added' } ); return 0; } -sub action_launchdiscovery { +sub launchhostdiscovery { my ($self, %options) = @_; - return if (!$self->is_module_installed()); + return (1, 'host discovery sync not done') if (!$self->is_hdisco_synced()); + + my $job_id = $options{job_id}; - $self->{logger}->writeLogInfo("[autodiscovery] Launching discovery for job '" . $options{data}->{content}->{job_id} . "'"); + if (!defined($job_id) || !defined($self->{hdisco_jobs_ids}->{$job_id})) { + return (1, 'trying to launch discovery for inexistant job'); + } + if ($self->hdisco_is_running_job(status => $self->{hdisco_jobs_ids}->{$job_id}->{status})) { + return (1, 'job is already running'); + } + if ($self->{hdisco_jobs_ids}->{$job_id}->{execution}->{mode} == EXECUTION_MODE_PAUSE) { + return (1, 'job is paused'); + } + + $self->{logger}->writeLogInfo("[autodiscovery] -class- host discovery - launching discovery for job '" . $job_id . "'"); + + # Running + if ($self->update_job_information( + values => { + status => JOB_RUNNING, + message => 'Running', + last_execution => strftime("%F %H:%M:%S", localtime), + duration => 0, + discovered_items => 0 + }, + where_clause => [ + { + id => $job_id + } + ] + ) == -1) { + return (1, 'cannot update job status'); + } + $self->{hdisco_jobs_ids}->{$job_id}->{status} = JOB_RUNNING; $self->send_internal_action( action => 'ADDLISTENER', data => [ { identity => 'gorgoneautodiscovery', - event => 'AUTODISCOVERYLISTENER', - target => $options{data}->{content}->{target}, - token => $options{data}->{content}->{token}, + event => 'HOSTDISCOVERYJOBLISTENER', + target => $self->{hdisco_jobs_ids}->{$job_id}->{target}, + token => $self->{hdisco_jobs_ids}->{$job_id}->{token}, timeout => defined($options{data}->{content}->{timeout}) && $options{data}->{content}->{timeout} =~ /(\d+)/ ? $1 + $self->{check_interval} + 15 : undef, log_pace => $self->{check_interval} @@ -215,35 +469,143 @@ sub action_launchdiscovery { $self->send_internal_action( action => 'COMMAND', - target => $options{data}->{content}->{target}, - token => $options{data}->{content}->{token}, + target => $self->{hdisco_jobs_ids}->{$job_id}->{target}, + token => $self->{hdisco_jobs_ids}->{$job_id}->{token}, data => { content => [ { instant => 1, - command => $options{data}->{content}->{command}, - timeout => $options{data}->{content}->{timeout}, + command => $self->{hdisco_jobs_ids}->{$job_id}->{command_line}, + timeout => defined($options{data}->{content}->{timeout}) && $options{data}->{content}->{timeout} =~ /(\d+)/ ? $1 : undef, metadata => { - job_id => $options{data}->{content}->{job_id}, - uuid_attributes => $options{data}->{content}->{uuid_attributes}, - source => 'autodiscovery' + job_id => $job_id, + source => 'autodiscovery-host-job-discovery' } } ] } ); - # Running - my $query = "UPDATE mod_host_disco_job " . - "SET status = '3', duration = '0', discovered_items = '0', " . - "creation_date = '" . strftime("%F %H:%M:%S", localtime) . "', " . - "token = '" . $options{data}->{content}->{token} . "', message = 'Running' " . - "WHERE id = '" . $options{data}->{content}->{job_id} . "'"; - my $status = $self->{class_object_centreon}->transaction_query(request => $query); - if ($status == -1) { - $self->{logger}->writeLogError('[autodiscovery] Failed to update job status'); + return 0; +} + +sub action_launchhostdiscovery { + my ($self, %options) = @_; + + $options{token} = $self->generate_token() if (!defined($options{token})); + if (!$self->is_hdisco_synced()) { + $self->send_log( + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + data => { + message => 'host discovery synchronization issue' + } + ); + return ; + } + + my $job_id; + if (defined($options{data}->{variables}->[0]) && + defined($options{data}->{variables}->[1]) && $options{data}->{variables}->[1] eq 'schedule') { + $job_id = $options{data}->{variables}->[0]; + } elsif (defined($options{data}->{content}->{job_id})) { + $job_id = $options{data}->{content}->{job_id}; + } + + my ($status, $message, $job); + ($status, $message, $job) = $self->get_host_job(job_id => $job_id); + if ($status != 0) { + $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - cannot get host discovery job '$job_id' - " . $self->{tpapi_centreonv2}->error()); + $self->send_log( + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + data => { + message => "cannot get job '$job_id'" + } + ); + return 1; + } + + if (!defined($job)) { + $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - cannot get host discovery job '$job_id' - " . $self->{tpapi_centreonv2}->error()); + $self->send_log( + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + data => { + message => "cannot get job '$job_id'" + } + ); + return 1; + } + + ($status, $message) = $self->hdisco_addupdate_job(job => $job); + if ($status) { + $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - add job '$job_id' - $message"); + $self->send_log( + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + data => { + message => "add job '$job_id' - $message" + } + ); + return 1; + } + + ($status, $message) = $self->launchhostdiscovery(job_id => $job_id, %options); + if ($status) { + $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - launch discovery job '$job_id' - $message"); + $self->send_log( + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + instant => 1, + data => { + message => $message + } + ); + return 1; + } + + $self->send_log( + code => GORGONE_ACTION_FINISH_OK, + token => $options{token}, + instant => 1, + data => { + message => "job '$job_id' launched" + } + ); +} + +sub discovery_postcommand_result { + my ($self, %options) = @_; + + return 1 if (!defined($options{data}->{data}->{metadata}->{job_id})); + + my $job_id = $options{data}->{data}->{metadata}->{job_id}; + if (!defined($self->{hdisco_jobs_ids}->{$job_id})) { + $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - found result for inexistant job '" . $job_id . "'"); return 1; } + + my $exit_code = $options{data}->{data}->{result}->{exit_code}; + my $output = (defined($options{data}->{data}->{result}->{stderr}) && $options{data}->{data}->{result}->{stderr} ne '') ? + $options{data}->{data}->{result}->{stderr} : $options{data}->{data}->{result}->{stdout}; + + if ($exit_code != 0) { + $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - execute discovery postcommand failed job '$job_id'"); + $self->update_job_status( + job_id => $job_id, + status => SAVE_FAILED, + message => $output + ); + return 1; + } + + $self->{logger}->writeLogDebug("[autodiscovery] -class- host discovery - finished discovery postcommand job '$job_id'"); + $self->update_job_status( + job_id => $job_id, + status => SAVE_FINISH, + message => 'Finished' + ); } sub discovery_command_result { @@ -251,20 +613,25 @@ sub discovery_command_result { return 1 if (!defined($options{data}->{data}->{metadata}->{job_id})); - $self->{logger}->writeLogInfo("[autodiscovery] Found result for job '" . $options{data}->{data}->{metadata}->{job_id} . "'"); - my $uuid_attributes = JSON::XS->new->utf8->decode($options{data}->{data}->{metadata}->{uuid_attributes}); my $job_id = $options{data}->{data}->{metadata}->{job_id}; + if (!defined($self->{hdisco_jobs_ids}->{$job_id})) { + $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - found result for inexistant job '" . $job_id . "'"); + return 1; + } + + $self->{logger}->writeLogInfo("[autodiscovery] -class- host discovery - found result for job '" . $job_id . "'"); + my $uuid_attributes = $self->{hdisco_jobs_ids}->{$job_id}->{uuid_attributes}; my $exit_code = $options{data}->{data}->{result}->{exit_code}; my $output = (defined($options{data}->{data}->{result}->{stderr}) && $options{data}->{data}->{result}->{stderr} ne '') ? $options{data}->{data}->{result}->{stderr} : $options{data}->{data}->{result}->{stdout}; if ($exit_code != 0) { - my $query = "UPDATE mod_host_disco_job " . - "SET status = '2', duration = '0', discovered_items = '0', " . - "message = " . $self->{class_object_centreon}->quote(value => $output) . " " . - "WHERE id = " . $self->{class_object_centreon}->quote(value => $job_id); - my $status = $self->{class_object_centreon}->transaction_query(request => $query); - $self->{logger}->writeLogError('[autodiscovery] Failed to update job status') if ($status == -1); + $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - execute discovery plugin failed job '$job_id'"); + $self->update_job_status( + job_id => $job_id, + status => JOB_FAILED, + message => $output + ); return 1; } @@ -274,42 +641,53 @@ sub discovery_command_result { }; if ($@) { - # Failed - $self->{logger}->writeLogError('[autodiscovery] Failed to decode discovery plugin response: ' . $@); - my $query = "UPDATE mod_host_disco_job " . - "SET status = '2', duration = '0', discovered_items = '0', " . - "message = 'Failed to decode discovery plugin response' " . - "WHERE id = '" . $job_id ."'"; - my $status = $self->{class_object_centreon}->transaction_query(request => $query); - $self->{logger}->writeLogError('[autodiscovery] Failed to update job status') if ($status == -1); - return 1; - } - - # Finished - my $query = "UPDATE mod_host_disco_job SET status = '1', duration = '" . $result->{duration} ."', " . - "discovered_items = '" . $result->{discovered_items} ."', message = 'Finished' " . - "WHERE id = '" . $job_id ."'"; - my $status = $self->{class_object_centreon}->transaction_query(request => $query); - if ($status == -1) { - $self->{logger}->writeLogError('[autodiscovery] Failed to update job status'); + $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - failed to decode discovery plugin response job '$job_id'"); + $self->update_job_status( + job_id => $job_id, + status => JOB_FAILED, + message => 'Failed to decode discovery plugin response' + ); return 1; } # Delete previous results - $query = "DELETE FROM mod_host_disco_host WHERE job_id = '" . $job_id ."'"; - $status = $self->{class_object_centreon}->transaction_query(request => $query); + my $query = "DELETE FROM mod_host_disco_host WHERE job_id = " . $self->{class_object_centreon}->quote(value => $job_id); + my ($status) = $self->{class_object_centreon}->custom_execute(request => $query); if ($status == -1) { - $self->{logger}->writeLogError('[autodiscovery] Failed to delete previous job results'); + $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - failed to delete previous job '$job_id' results"); + $self->update_job_status( + job_id => $job_id, + status => JOB_FAILED, + message => 'Failed to delete previous job results' + ); return 1; } # Add new results - my $values; + my $number_of_lines = 0; + my $values = ''; my $append = ''; + $query = "INSERT INTO mod_host_disco_host (job_id, discovery_result, uuid) VALUES "; foreach my $host (@{$result->{results}}) { + if ($number_of_lines == MAX_INSERT_BY_QUERY) { + ($status) = $self->{class_object_centreon}->custom_execute(request => $query . $values); + if ($status == -1) { + $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - failed to insert job '$job_id' results"); + $self->update_job_status( + job_id => $job_id, + status => JOB_FAILED, + message => 'Failed to insert job results' + ); + return 1; + } + $number_of_lines = 0; + $values = ''; + $append = ''; + } + # Generate uuid based on attributs my $uuid_char = ''; - foreach (@{$uuid_attributes}) { + foreach (@$uuid_attributes) { $uuid_char .= $host->{$_} if (defined($host->{$_}) && $host->{$_} ne ''); } my $ctx = Digest::MD5->new; @@ -319,76 +697,253 @@ sub discovery_command_result { substr($digest, 16, 4) . '-' . substr($digest, 20, 12); my $encoded_host = JSON::XS->new->utf8->encode($host); - $values .= $append . "('" . $job_id . "', " . - $self->{class_object_centreon}->quote(value => $encoded_host) . ", '" . $uuid . "')"; + # Build bulk insert + $values .= $append . "(" . $self->{class_object_centreon}->quote(value => $job_id) . ", " . + $self->{class_object_centreon}->quote(value => $encoded_host) . ", " . + $self->{class_object_centreon}->quote(value => $uuid) . ")"; $append = ', '; + $number_of_lines++; } - if (defined($values) && $values ne '') { - $query = "INSERT INTO mod_host_disco_host (job_id, discovery_result, uuid) VALUES " . $values; - $status = $self->{class_object_centreon}->transaction_query(request => $query); + if ($values ne '') { + ($status) = $self->{class_object_centreon}->custom_execute(request => $query . $values); if ($status == -1) { - $self->{logger}->writeLogError('[autodiscovery] Failed to insert job results'); + $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - failed to insert job '$job_id' results"); + $self->update_job_status( + job_id => $job_id, + status => JOB_FAILED, + message => 'Failed to insert job results' + ); return 1; } } - return 1; + if (defined($self->{hdisco_jobs_ids}->{$job_id}->{post_execution}->{commands}) && + scalar(@{$self->{hdisco_jobs_ids}->{$job_id}->{post_execution}->{commands}}) > 0) { + $self->{logger}->writeLogDebug("[autodiscovery] -class- host discovery - execute post command job '$job_id'"); + my $post_command = $self->{hdisco_jobs_ids}->{$job_id}->{post_execution}->{commands}->[0]; + + $self->send_internal_action( + action => $post_command->{action}, + token => $self->{hdisco_jobs_ids}->{$job_id}->{token}, + data => { + content => [ + { + instant => 1, + command => $post_command->{command_line}, + metadata => { + job_id => $job_id, + source => 'autodiscovery-host-job-postcommand' + } + } + ] + } + ); + } + + $self->{logger}->writeLogDebug("[autodiscovery] -class- host discovery - finished discovery command job '$job_id'"); + $self->update_job_status( + job_id => $job_id, + status => JOB_FINISH, + message => 'Finished', + duration => $result->{duration}, + discovered_items => $result->{discovered_items} + ); + + return 0; } -sub action_autodiscoverylistener { +sub action_deletehostdiscoveryjob { my ($self, %options) = @_; - return if (!$self->is_module_installed()); + # delete is call when it's in pause (execution_mode 2). + # in fact, we do a curl to sync. If don't exist in database, we remove it. otherwise we do nothing + $options{token} = $self->generate_token() if (!defined($options{token})); + if (!$self->is_hdisco_synced()) { + $self->send_log( + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + data => { + message => 'host discovery synchronization issue' + } + ); + return ; + } + + my $discovery_token = $options{data}->{variables}->[0]; + my $job_id = (defined($discovery_token) && defined($self->{hdisco_jobs_tokens}->{$discovery_token})) ? + $self->{hdisco_jobs_tokens}->{$discovery_token} : undef; + if (!defined($discovery_token) || $discovery_token eq '') { + $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - missing ':token' variable to delete discovery"); + $self->send_log( + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'missing discovery token' } + ); + return 1; + } + + my ($status, $message, $job); + ($status, $message, $job) = $self->get_host_job(job_id => $job_id); + if ($status != 0) { + $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - cannot get host discovery job '$job_id' - " . $self->{tpapi_centreonv2}->error()); + $self->send_log( + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + data => { + message => "cannot get job '$job_id'" + } + ); + return 1; + } + + if (!defined($job)) { + $self->{logger}->writeLogDebug("[autodiscovery] -class- host discovery - delete job '" . $job_id . "'"); + if (defined($self->{hdisco_jobs_ids}->{$job_id}->{token})) { + $self->hdisco_delete_cron(discovery_token => $discovery_token); + delete $self->{hdisco_jobs_tokens}->{$discovery_token}; + } + delete $self->{hdisco_jobs_ids}->{$job_id}; + } else { + $self->hdisco_addupdate_job(job => $job); + } + + $self->send_log( + code => GORGONE_ACTION_FINISH_OK, + token => $options{token}, + data => { message => 'job ' . $discovery_token . ' deleted' } + ); + + return 0; +} + +sub update_job_status { + my ($self, %options) = @_; + + my $values = { status => $options{status}, message => $options{message} }; + $values->{duration} = $options{duration} if (defined($options{duration})); + $values->{discovered_items} = $options{discovered_items} if (defined($options{discovered_items})); + $self->update_job_information( + values => $values, + where_clause => [ + { + id => $options{job_id} + } + ] + ); + $self->{hdisco_jobs_ids}->{$options{job_id}}->{status} = $options{status}; +} + +sub update_job_information { + my ($self, %options) = @_; + + return 1 if (!defined($options{where_clause}) || ref($options{where_clause}) ne 'ARRAY' || scalar($options{where_clause}) < 1); + return 1 if (!defined($options{values}) || ref($options{values}) ne 'HASH' || !keys %{$options{values}}); + + my $query = "UPDATE mod_host_disco_job SET "; + my $append = ''; + foreach (keys %{$options{values}}) { + $query .= $append . $_ . " = " . $self->{class_object_centreon}->quote(value => $options{values}->{$_}); + $append = ', '; + } + + $query .= " WHERE "; + $append = ''; + foreach (@{$options{where_clause}}) { + my ($key, $value) = each %{$_}; + $query .= $append . $key . " = " . $self->{class_object_centreon}->quote(value => $value); + $append = 'AND '; + } + + my ($status) = $self->{class_object_centreon}->custom_execute(request => $query); + if ($status == -1) { + $self->{logger}->writeLogError('[autodiscovery] Failed to update job information'); + return -1; + } + + return 0; +} + +sub action_hostdiscoveryjoblistener { + my ($self, %options) = @_; + + return 0 if (!$self->is_hdisco_synced()); return 0 if (!defined($options{token})); + return 0 if (!defined($self->{hdisco_jobs_tokens}->{$options{token}})); - if ($options{data}->{code} == GORGONE_MODULE_ACTION_COMMAND_RESULT) { + my $job_id = $self->{hdisco_jobs_tokens}->{$options{token}}; + if ($options{data}->{code} == GORGONE_MODULE_ACTION_COMMAND_RESULT && + $options{data}->{data}->{metadata}->{source} eq 'autodiscovery-host-job-discovery') { $self->discovery_command_result(%options); return 1; } + #if ($options{data}->{code} == GORGONE_MODULE_ACTION_COMMAND_RESULT && + # $options{data}->{data}->{metadata}->{source} eq 'autodiscovery-host-job-postcommand') { + # $self->discovery_postcommand_result(%options); + # return 1; + #} + if ($options{data}->{code} == GORGONE_ACTION_FINISH_KO) { - my $query = "UPDATE mod_host_disco_job " . - "SET status = '2', duration = '0', discovered_items = '0', " . - "message = " . $self->{class_object_centreon}->quote(value => $options{data}->{message}) . " " . - "WHERE token = " . $self->{class_object_centreon}->quote(value => $options{token}); - my $status = $self->{class_object_centreon}->transaction_query(request => $query); - $self->{logger}->writeLogError('[autodiscovery] Failed to update job status') if ($status == -1); + $self->{hdisco_jobs_ids}->{$job_id}->{status} = JOB_FAILED; + $self->update_job_information( + values => { + status => JOB_FAILED, + message => $options{data}->{message}, + duration => 0, + discovered_items => 0 + }, + where_clause => [ + { + id => $job_id + } + ] + ); return 1; } return 1; } -sub register_listener { +sub action_hostdiscoverycronlistener { my ($self, %options) = @_; - - return if (!$self->is_module_installed()); - - # List running jobs - my ($status, $data) = $self->{class_object_centreon}->custom_execute( - request => "SELECT id, monitoring_server_id, token FROM mod_host_disco_job WHERE status = '3'", - mode => 1, - keys => 'id' - ); - # Sync logs and try to retrieve results for each jobs - foreach my $job_id (keys %$data) { - $self->{logger}->writeLogDebug("[autodiscovery] register listener for '" . $job_id . "'"); + return 0 if (!defined($options{token}) || $options{token} !~ /^cron-(.*)/); + my $discovery_token = $1; + + return 0 if (!defined($self->{hdisco_jobs_tokens}->{ $discovery_token })); + + my $job_id = $self->{hdisco_jobs_tokens}->{ $discovery_token }; + if ($options{data}->{code} == GORGONE_ACTION_FINISH_KO) { + $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - job '" . $job_id . "' add cron error"); + $self->{hdisco_jobs_ids}->{$job_id}->{extra_infos}->{cron_added} = CRON_ADDED_KO; + } elsif ($options{data}->{code} == GORGONE_ACTION_FINISH_OK) { + $self->{logger}->writeLogInfo("[autodiscovery] -class- host discovery - job '" . $job_id . "' add cron ok"); + $self->{hdisco_jobs_ids}->{$job_id}->{extra_infos}->{cron_added} = CRON_ADDED_OK; + } + + return 1; +} + +sub hdisco_add_joblistener { + my ($self, %options) = @_; + + foreach (@{$options{jobs}}) { + $self->{logger}->writeLogDebug("[autodiscovery] -class- host discovery - register listener for '" . $_->{job_id} . "'"); $self->send_internal_action( action => 'ADDLISTENER', data => [ { identity => 'gorgoneautodiscovery', - event => 'AUTODISCOVERYLISTENER', - target => $data->{$job_id}->{monitoring_server_id}, - token => $data->{$job_id}->{token}, + event => 'HOSTDISCOVERYJOBLISTENER', + target => $_->{target}, + token => $_->{token}, log_pace => $self->{check_interval} } ] ); } - + return 0; } @@ -431,6 +986,7 @@ sub action_launchservicediscovery { my $svc_discovery = gorgone::modules::centreon::autodiscovery::services::discovery->new( module_id => $self->{module_id}, logger => $self->{logger}, + tpapi_clapi => $self->{tpapi_clapi}, internal_socket => $self->{internal_socket}, config => $self->{config}, config_core => $self->{config_core}, @@ -453,15 +1009,10 @@ sub action_launchservicediscovery { } } -sub is_module_installed { +sub is_hdisco_synced { my ($self) = @_; - my ($status, $data) = $self->{class_object_centreon}->custom_execute( - request => "SELECT id FROM modules_informations WHERE name = 'centreon-autodiscovery-server'", - mode => 2 - ); - - (defined($data->[0]) && scalar($data->[0]) > 0) ? return 1 : return 0; + return $self->{hdisco_synced} == 1 ? 1 : 0; } sub event { @@ -484,7 +1035,20 @@ sub event { sub run { my ($self, %options) = @_; - + + $self->{tpapi_clapi} = gorgone::class::tpapi::clapi->new(); + $self->{tpapi_clapi}->set_configuration( + config => $self->{tpapi}->get_configuration(name => $self->{tpapi_clapi_name}) + ); + $self->{tpapi_centreonv2} = gorgone::class::tpapi::centreonv2->new(); + my ($status) = $self->{tpapi_centreonv2}->set_configuration( + config => $self->{tpapi}->get_configuration(name => $self->{tpapi_centreonv2_name}), + logger => $self->{logger} + ); + if ($status) { + $self->{logger}->writeLogError('[autodiscovery] -class- host discovery - configure api centreonv2 - ' . $self->{tpapi_centreonv2}->error()); + } + $self->{db_centreon} = gorgone::class::db->new( dsn => $self->{config_db_centreon}->{dsn}, user => $self->{config_db_centreon}->{username}, @@ -529,9 +1093,9 @@ sub run { } ]; - $self->register_listener(); - while (1) { + $self->hdisco_sync(); + # we try to do all we can my $rev = zmq_poll($self->{poll}, 5000); if (defined($rev) && $rev == 0 && $self->{stop} == 1) { diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm b/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm index eea49973d32..c17895a2c4d 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm @@ -31,11 +31,13 @@ use constant NAMESPACE => 'centreon'; use constant NAME => 'autodiscovery'; use constant EVENTS => [ { event => 'AUTODISCOVERYREADY' }, - { event => 'AUTODISCOVERYLISTENER' }, + { event => 'HOSTDISCOVERYJOBLISTENER' }, + { event => 'HOSTDISCOVERYCRONLISTENER' }, { event => 'SERVICEDISCOVERYLISTENER' }, - { event => 'ADDDISCOVERYJOB', uri => '/job', method => 'POST' }, - { event => 'LAUNCHDISCOVERY' }, - { event => 'LAUNCHSERVICEDISCOVERY', uri => '/services', method => 'POST' }, + { event => 'ADDHOSTDISCOVERYJOB', uri => '/hosts', method => 'POST' }, + { event => 'DELETEHOSTDISCOVERYJOB', uri => '/hosts', method => 'DELETE' }, + { event => 'LAUNCHHOSTDISCOVERY', uri => '/hosts', method => 'GET' }, + { event => 'LAUNCHSERVICEDISCOVERY', uri => '/services', method => 'POST' } ]; my $config_core; diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm index 4254e2af7e1..3aec7aeb779 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm @@ -40,6 +40,7 @@ sub new { $connector->{logger} = $options{logger}; $connector->{config} = $options{config}; $connector->{config_core} = $options{config_core}; + $connector->{tpapi_clapi} = $options{tpapi_clapi}; $connector->{class_object_centreon} = $options{class_object_centreon}; $connector->{class_object_centstorage} = $options{class_object_centstorage}; $connector->{mail_command} = defined($connector->{config}->{mail_command}) ? $connector->{config}->{mail_command} : '/bin/mail'; @@ -110,19 +111,6 @@ sub is_finished { return $self->{finished}; } -sub get_clapi_user { - my ($self, %options) = @_; - - $self->{clapi_user} = $self->{config}->{clapi_user}; - $self->{clapi_password} = $self->{config}->{clapi_password}; - - if (!defined($self->{clapi_password})) { - return (-1, 'cannot get configuration for CLAPI user'); - } - - return 0; -} - sub send_email { my ($self, %options) = @_; @@ -188,7 +176,7 @@ sub restart_pollers { data => { content => [ { - command => 'centreon -u ' . $self->{clapi_user} . ' -p ' . $self->{clapi_password} . ' -a APPLYCFG -v ' . $poller_id + command => $self->{tpapi_clapi}->get_applycfg_command(poller_id => $poller_id) } ] } @@ -801,14 +789,13 @@ sub launchdiscovery { return -1; } - ($status, $message) = $self->get_clapi_user(); - if ($status < 0) { - $self->send_log_msg_error(token => $options{token}, subname => 'servicediscovery', number => $self->{uuid}, message => $message); + if (!defined($self->{tpapi_clapi}->get_username())) { + $self->send_log_msg_error(token => $options{token}, subname => 'servicediscovery', number => $self->{uuid}, message => 'clapi ' . $self->{tpapi_clapi}->error()); return -1; } ($status, $message, my $user_id) = gorgone::modules::centreon::autodiscovery::services::resources::get_audit_user_id( class_object_centreon => $self->{class_object_centreon}, - clapi_user => $self->{clapi_user} + clapi_user => $self->{tpapi_clapi}->get_username() ); if ($status < 0) { $self->send_log_msg_error(token => $options{token}, subname => 'servicediscovery', number => $self->{uuid}, message => $message); diff --git a/gorgone/gorgone/modules/centreon/engine/class.pm b/gorgone/gorgone/modules/centreon/engine/class.pm index ddb13c71821..f9041bd75f5 100644 --- a/gorgone/gorgone/modules/centreon/engine/class.pm +++ b/gorgone/gorgone/modules/centreon/engine/class.pm @@ -36,16 +36,11 @@ my ($connector); sub new { my ($class, %options) = @_; - $connector = {}; - $connector->{internal_socket} = undef; - $connector->{logger} = $options{logger}; - $connector->{config} = $options{config}; - $connector->{config_core} = $options{config_core}; - $connector->{stop} = 0; + $connector = $class->SUPER::new(%options); + bless $connector, $class; $connector->{timeout} = defined($connector->{config}->{timeout}) ? $connector->{config}->{timeout} : 5; - bless $connector, $class; $connector->set_signal_handlers; return $connector; } diff --git a/gorgone/gorgone/modules/centreon/judge/class.pm b/gorgone/gorgone/modules/centreon/judge/class.pm index 8f3bc112400..519f27be327 100644 --- a/gorgone/gorgone/modules/centreon/judge/class.pm +++ b/gorgone/gorgone/modules/centreon/judge/class.pm @@ -38,22 +38,14 @@ my ($connector); sub new { my ($class, %options) = @_; - $connector = {}; + $connector = $class->SUPER::new(%options); bless $connector, $class; - $connector->{internal_socket} = undef; - $connector->{module_id} = $options{module_id}; - $connector->{logger} = $options{logger}; - $connector->{config} = $options{config}; - $connector->{config_core} = $options{config_core}; - $connector->{config_db_centstorage} = $options{config_db_centstorage}; - $connector->{config_db_centreon} = $options{config_db_centreon}; - $connector->{stop} = 0; $connector->{timeout} = 600; $connector->{check_alive_sync} = defined($connector->{config}->{check_alive}) && $connector->{config}->{check_alive} =~ /(\d+)/ ? $1 : 60; $connector->{check_alive_last} = -1; $connector->{check_alive} = 0; - + $connector->{cache_dir} = (defined($connector->{config}->{cache_dir}) && $connector->{config}->{cache_dir} ne '') ? $connector->{config}->{cache_dir} : '/var/cache/centreon'; diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index 01bf72deb74..bb1bfc359d4 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -37,10 +37,9 @@ my ($connector); sub new { my ($class, %options) = @_; - $connector = {}; - $connector->{internal_socket} = undef; - $connector->{logger} = $options{logger}; - $connector->{config} = $options{config}; + $connector = $class->SUPER::new(%options); + bless $connector, $class; + if (!defined($connector->{config}->{cmd_file}) || $connector->{config}->{cmd_file} eq '') { $connector->{config}->{cmd_file} = '/var/lib/centreon/centcore.cmd'; } @@ -48,12 +47,8 @@ sub new { $connector->{config}->{cmd_dir} = '/var/lib/centreon/centcore/'; } $connector->{config}->{dirty_mode} = defined($connector->{config}->{dirty_mode}) ? $connector->{config}->{dirty_mode} : 1; - $connector->{config_core} = $options{config_core}; - $connector->{config_db_centreon} = $options{config_db_centreon}; - $connector->{stop} = 0; $connector->{gorgone_illegal_characters} = '`'; - bless $connector, $class; $connector->set_signal_handlers; return $connector; } diff --git a/gorgone/gorgone/modules/centreon/nodes/class.pm b/gorgone/gorgone/modules/centreon/nodes/class.pm index 3c879449caa..870fc5e4bda 100644 --- a/gorgone/gorgone/modules/centreon/nodes/class.pm +++ b/gorgone/gorgone/modules/centreon/nodes/class.pm @@ -38,22 +38,15 @@ my ($connector); sub new { my ($class, %options) = @_; + $connector = $class->SUPER::new(%options); + bless $connector, $class; - $connector = {}; - $connector->{internal_socket} = undef; - $connector->{module_id} = $options{module_id}; - $connector->{logger} = $options{logger}; - $connector->{config} = $options{config}; - $connector->{config_core} = $options{config_core}; - $connector->{config_db_centreon} = $options{config_db_centreon}; - $connector->{stop} = 0; $connector->{register_nodes} = {}; $connector->{default_resync_time} = (defined($options{config}->{resync_time}) && $options{config}->{resync_time} =~ /(\d+)/) ? $1 : 600; $connector->{resync_time} = $connector->{default_resync_time}; $connector->{last_resync_time} = -1; - bless $connector, $class; $connector->set_signal_handlers(); return $connector; } diff --git a/gorgone/gorgone/modules/centreon/statistics/class.pm b/gorgone/gorgone/modules/centreon/statistics/class.pm index 4cf0f40d1bb..75000db641c 100644 --- a/gorgone/gorgone/modules/centreon/statistics/class.pm +++ b/gorgone/gorgone/modules/centreon/statistics/class.pm @@ -40,19 +40,11 @@ my ($connector); sub new { my ($class, %options) = @_; + $connector = $class->SUPER::new(%options); + bless $connector, $class; - $connector = {}; - $connector->{internal_socket} = undef; - $connector->{module_id} = $options{module_id}; - $connector->{logger} = $options{logger}; - $connector->{config} = $options{config}; - $connector->{config_core} = $options{config_core}; - $connector->{config_db_centreon} = $options{config_db_centreon}; - $connector->{config_db_centstorage} = $options{config_db_centstorage}; $connector->{log_pace} = 3; - $connector->{stop} = 0; - bless $connector, $class; $connector->set_signal_handlers(); return $connector; } diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index 9cfc8fefcf9..a6cd80e4e89 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -43,18 +43,14 @@ my ($connector); sub new { my ($class, %options) = @_; - $connector = {}; - $connector->{internal_socket} = undef; - $connector->{logger} = $options{logger}; - $connector->{config} = $options{config}; - $connector->{config_core} = $options{config_core}; - $connector->{stop} = 0; + $connector = $class->SUPER::new(%options); + bless $connector, $class; + $connector->{process_copy_files_error} = {}; $connector->{command_timeout} = defined($connector->{config}->{command_timeout}) ? $connector->{config}->{command_timeout} : 30; - bless $connector, $class; $connector->set_signal_handlers; return $connector; } diff --git a/gorgone/gorgone/modules/core/cron/class.pm b/gorgone/gorgone/modules/core/cron/class.pm index 95e106143aa..3070dd3ec56 100644 --- a/gorgone/gorgone/modules/core/cron/class.pm +++ b/gorgone/gorgone/modules/core/cron/class.pm @@ -36,15 +36,10 @@ my ($connector); sub new { my ($class, %options) = @_; - $connector = {}; - $connector->{internal_socket} = undef; - $connector->{logger} = $options{logger}; - $connector->{config} = $options{config}; - $connector->{config_core} = $options{config_core}; - $connector->{stop} = 0; - + $connector = $class->SUPER::new(%options); bless $connector, $class; - $connector->set_signal_handlers; + + $connector->set_signal_handlers(); return $connector; } @@ -319,7 +314,7 @@ sub action_deletecron { $self->{logger}->writeLogDebug("[cron] Cron delete start"); - my $id = $options{data}->{variables}[0]; + my $id = $options{data}->{variables}->[0]; if (!defined($id) || $id eq '') { $self->{logger}->writeLogError("[cron] Cron delete missing id"); $self->send_log( diff --git a/gorgone/gorgone/modules/core/dbcleaner/class.pm b/gorgone/gorgone/modules/core/dbcleaner/class.pm index 0d815fb4743..eea384684bd 100644 --- a/gorgone/gorgone/modules/core/dbcleaner/class.pm +++ b/gorgone/gorgone/modules/core/dbcleaner/class.pm @@ -36,17 +36,11 @@ my ($connector); sub new { my ($class, %options) = @_; + $connector = $class->SUPER::new(%options); + bless $connector, $class; - $connector = {}; - $connector->{internal_socket} = undef; - $connector->{module_id} = $options{module_id}; - $connector->{logger} = $options{logger}; - $connector->{config} = $options{config}; - $connector->{config_core} = $options{config_core}; - $connector->{stop} = 0; $connector->{purge_timer} = time(); - bless $connector, $class; $connector->set_signal_handlers(); return $connector; } diff --git a/gorgone/gorgone/modules/core/httpserver/class.pm b/gorgone/gorgone/modules/core/httpserver/class.pm index 1719ad592f5..fbbce0cb02b 100644 --- a/gorgone/gorgone/modules/core/httpserver/class.pm +++ b/gorgone/gorgone/modules/core/httpserver/class.pm @@ -44,12 +44,9 @@ my %dispatch; sub new { my ($class, %options) = @_; - $connector = {}; - $connector->{internal_socket} = undef; - $connector->{logger} = $options{logger}; - $connector->{config} = $options{config}; - $connector->{config_core} = $options{config_core}; - $connector->{stop} = 0; + $connector = $class->SUPER::new(%options); + bless $connector, $class; + $connector->{api_endpoints} = $options{api_endpoints}; if ($connector->{config}->{ssl} eq 'true') { @@ -71,7 +68,6 @@ sub new { $connector->{allowed_hosts_enabled} = 0; } - bless $connector, $class; $connector->set_signal_handlers; return $connector; } diff --git a/gorgone/gorgone/modules/core/pipeline/class.pm b/gorgone/gorgone/modules/core/pipeline/class.pm index 9df43113a0f..b1e2cc07ec0 100644 --- a/gorgone/gorgone/modules/core/pipeline/class.pm +++ b/gorgone/gorgone/modules/core/pipeline/class.pm @@ -36,19 +36,12 @@ my ($connector); sub new { my ($class, %options) = @_; + $connector = $class->SUPER::new(%options); + bless $connector, $class; - $connector = {}; - $connector->{internal_socket} = undef; - $connector->{module_id} = $options{module_id}; - $connector->{logger} = $options{logger}; - $connector->{config} = $options{config}; - $connector->{config_core} = $options{config_core}; - $connector->{stop} = 0; $connector->{timeout} = 600; - $connector->{pipelines} = {}; - bless $connector, $class; $connector->set_signal_handlers(); return $connector; } diff --git a/gorgone/gorgone/modules/core/proxy/class.pm b/gorgone/gorgone/modules/core/proxy/class.pm index 425424c8df9..fa68693cedd 100644 --- a/gorgone/gorgone/modules/core/proxy/class.pm +++ b/gorgone/gorgone/modules/core/proxy/class.pm @@ -37,19 +37,12 @@ my ($connector); sub new { my ($class, %options) = @_; + $connector = $class->SUPER::new(%options); + bless $connector, $class; - $connector = {}; - $connector->{module_id} = $options{module_id}; - $connector->{internal_socket} = undef; - $connector->{logger} = $options{logger}; - $connector->{core_id} = $options{core_id}; $connector->{pool_id} = $options{pool_id}; - $connector->{config} = $options{config}; - $connector->{config_core} = $options{config_core}; - $connector->{stop} = 0; $connector->{clients} = {}; - - bless $connector, $class; + $connector->set_signal_handlers(); return $connector; } diff --git a/gorgone/gorgone/modules/core/pull/hooks.pm b/gorgone/gorgone/modules/core/pull/hooks.pm index 6553b3ead2f..21421dd3457 100644 --- a/gorgone/gorgone/modules/core/pull/hooks.pm +++ b/gorgone/gorgone/modules/core/pull/hooks.pm @@ -40,7 +40,7 @@ sub register { my (%options) = @_; $config = $options{config}; - $config_core = $options{config_core}; + $config_core = $options{config_core}->{gorgonecore}; return (1, NAMESPACE, NAME, EVENTS); } diff --git a/gorgone/gorgone/modules/core/register/class.pm b/gorgone/gorgone/modules/core/register/class.pm index 5bffe5bdb77..587ca07232d 100644 --- a/gorgone/gorgone/modules/core/register/class.pm +++ b/gorgone/gorgone/modules/core/register/class.pm @@ -35,17 +35,11 @@ my ($connector); sub new { my ($class, %options) = @_; + $connector = $class->SUPER::new(%options); + bless $connector, $class; - $connector = {}; - $connector->{internal_socket} = undef; - $connector->{module_id} = $options{module_id}; - $connector->{logger} = $options{logger}; - $connector->{config} = $options{config}; - $connector->{config_core} = $options{config_core}; - $connector->{stop} = 0; $connector->{register_nodes} = {}; - bless $connector, $class; $connector->set_signal_handlers(); return $connector; } diff --git a/gorgone/gorgone/modules/plugins/newtest/class.pm b/gorgone/gorgone/modules/plugins/newtest/class.pm index 745523b252e..5320a7ffdd1 100644 --- a/gorgone/gorgone/modules/plugins/newtest/class.pm +++ b/gorgone/gorgone/modules/plugins/newtest/class.pm @@ -42,18 +42,11 @@ my ($connector); sub new { my ($class, %options) = @_; + $connector = $class->SUPER::new(%options); + bless $connector, $class; - $connector = {}; - $connector->{internal_socket} = undef; - $connector->{module_id} = $options{module_id}; - $connector->{logger} = $options{logger}; $connector->{container_id} = $options{container_id}; - $connector->{config} = $options{config}; - $connector->{config_core} = $options{config_core}; $connector->{config_newtest} = $options{config_newtest}; - $connector->{config_db_centstorage} = $options{config_db_centstorage}; - $connector->{config_db_centreon} = $options{config_db_centreon}; - $connector->{stop} = 0; $connector->{resync_time} = $options{config_newtest}->{resync_time}; $connector->{last_resync_time} = time() - $connector->{resync_time}; @@ -78,7 +71,6 @@ sub new { $connector->{cmdFile} = defined($options{config}->{centcore_cmd}) && $options{config}->{centcore_cmd} ne '' ? $options{config}->{centcore_cmd} : '/var/lib/centreon/centcore.cmd'; $connector->{illegal_characters} = defined($options{config}->{illegal_characters}) && $options{config}->{illegal_characters} ne '' ? $options{config}->{illegal_characters} : '~!$%^&*"|\'<>?,()='; - bless $connector, $class; $connector->set_signal_handlers(); return $connector; } diff --git a/gorgone/gorgone/modules/plugins/scom/class.pm b/gorgone/gorgone/modules/plugins/scom/class.pm index 44a29f8eb47..ab76ce7f4d3 100644 --- a/gorgone/gorgone/modules/plugins/scom/class.pm +++ b/gorgone/gorgone/modules/plugins/scom/class.pm @@ -39,17 +39,11 @@ my ($connector); sub new { my ($class, %options) = @_; + $connector = $class->SUPER::new(%options); + bless $connector, $class; - $connector = {}; - $connector->{internal_socket} = undef; - $connector->{module_id} = $options{module_id}; - $connector->{logger} = $options{logger}; $connector->{container_id} = $options{container_id}; - $connector->{config} = $options{config}; - $connector->{config_core} = $options{config_core}; $connector->{config_scom} = $options{config_scom}; - $connector->{config_db_centstorage} = $options{config_db_centstorage}; - $connector->{stop} = 0; $connector->{api_version} = $options{config_scom}->{api_version}; $connector->{dsmhost} = $options{config_scom}->{dsmhost}; @@ -67,7 +61,6 @@ sub new { $connector->{dsmclient_bin} = defined($connector->{config}->{dsmclient_bin}) ? $connector->{config}->{dsmclient_bin} : '/usr/share/centreon/bin/dsmclient.pl'; - bless $connector, $class; $connector->set_signal_handlers(); return $connector; } From d82be97de75655c32c5a9ff3f336519d00b55fa8 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire <cgagnaire@centreon.com> Date: Mon, 28 Sep 2020 11:25:33 +0200 Subject: [PATCH 525/948] feat(autodiscovery): add default config for api connections (#47) --- gorgone/packaging/centreon-api.yaml | 9 +++++++++ gorgone/packaging/centreon-gorgone.spectemplate | 2 ++ 2 files changed, 11 insertions(+) create mode 100644 gorgone/packaging/centreon-api.yaml diff --git a/gorgone/packaging/centreon-api.yaml b/gorgone/packaging/centreon-api.yaml new file mode 100644 index 00000000000..d52e6bd182a --- /dev/null +++ b/gorgone/packaging/centreon-api.yaml @@ -0,0 +1,9 @@ +gorgone: + tpapi: + - name: centreonv2 + base_url: "http://127.0.0.1/centreon/api/beta/" + username: admin + password: centreon + - name: clapi + username: admin + password: centreon diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 5f112e9b9a8..a603622374e 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -76,6 +76,7 @@ mkdir -p %{buildroot}/%{perl_vendorlib}/gorgone %{__install} -d %buildroot%{_localstatedir}/cache/centreon-gorgone/autodiscovery %{__cp} packaging/config.yaml %{buildroot}%{_sysconfdir}/centreon-gorgone/ %{__cp} packaging/centreon.yaml %{buildroot}%{_sysconfdir}/centreon-gorgone/config.d/30-centreon.yaml +%{__cp} packaging/centreon-api.yaml %{buildroot}%{_sysconfdir}/centreon-gorgone/config.d/31-centreon-api.yaml %{_fixperms} $RPM_BUILD_ROOT/* @@ -108,6 +109,7 @@ rm -rf %{buildroot} %files centreon-config %{_sysconfdir}/centreon-gorgone/config.d/30-centreon.yaml +%{_sysconfdir}/centreon-gorgone/config.d/31-centreon-api.yaml %defattr(-, centreon-gorgone, centreon-gorgone, -) %{_localstatedir}/cache/centreon-gorgone/autodiscovery From 57f37e6c8c179c83781ff8f6476123efcd60d7cd Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Tue, 29 Sep 2020 16:19:27 +0200 Subject: [PATCH 526/948] MON-6055: fix disconnect db query (#49) --- gorgone/gorgone/class/db.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/gorgone/class/db.pm b/gorgone/gorgone/class/db.pm index 16138b86378..f8108569124 100644 --- a/gorgone/gorgone/class/db.pm +++ b/gorgone/gorgone/class/db.pm @@ -280,7 +280,7 @@ sub query { $status = $self->connect(); if ($status != -1) { for (my $i = 0; $i < scalar(@{$self->{args}}); $i++) { - my $str_quoted = $self->quote(${$self->{args}}[0]); + my $str_quoted = $self->quote(${$self->{args}}[$i]); $query =~ s/##__ARG__$i##/$str_quoted/; } $self->{args} = []; From 64bf0f97a2dc0871771affb9ef0945529cc9614b Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Wed, 30 Sep 2020 10:54:20 +0200 Subject: [PATCH 527/948] MON-6061: enhance security files (#50) --- gorgone/packaging/centreon-gorgone.spectemplate | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index a603622374e..240275616c1 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -116,6 +116,8 @@ rm -rf %{buildroot} %post centreon-config %{_bindir}/getent passwd centreon &>/dev/null && %{_sbindir}/usermod -a -G centreon-gorgone centreon 2> /dev/null +%{_bindir}/getent passwd centreon-engine &>/dev/null && %{_sbindir}/usermod -a -G centreon-gorgone centreon-engine 2> /dev/null +%{_bindir}/getent passwd centreon-broker &>/dev/null && %{_sbindir}/usermod -a -G centreon-gorgone centreon-broker 2> /dev/null %{_bindir}/getent passwd centreon-gorgone &>/dev/null && %{_sbindir}/usermod -a -G centreon centreon-gorgone 2> /dev/null if [ \( \! -d %{_localstatedir}/lib/centreon-gorgone/.ssh \) -a \( -d %{_localstatedir}/spool/centreon/.ssh \) ] ; then %{__cp} -r %{_localstatedir}/spool/centreon/.ssh %{_localstatedir}/lib/centreon-gorgone/.ssh From dd6847489d811a0f4b756499b4e94f1b4e779b17 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Wed, 30 Sep 2020 13:29:49 +0200 Subject: [PATCH 528/948] fix(core): fix listener timeout (#51) --- gorgone/gorgone/class/listener.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/gorgone/class/listener.pm b/gorgone/gorgone/class/listener.pm index 3ff37553fda..57bf8b871ec 100644 --- a/gorgone/gorgone/class/listener.pm +++ b/gorgone/gorgone/class/listener.pm @@ -103,7 +103,6 @@ sub check { foreach my $token (keys %{$self->{tokens}}) { if (time() - $self->{tokens}->{$token}->{created} > $self->{tokens}->{$token}->{timeout}) { - delete $self->{tokens}->{$token}; $self->{logger}->writeLogDebug("[listener] delete token '$token': timeout"); gorgone::standard::library::add_history( dbh => $self->{gorgone_core}->{db_gorgone}, @@ -111,6 +110,7 @@ sub check { token => $token, data => '{ "message": "listener token ' . $token . ' timeout reached" }' ); + delete $self->{tokens}->{$token}; next; } $self->check_getlog_token(token => $token); From 6548432ae45517e598a93f737616834ed139c771 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Wed, 30 Sep 2020 15:27:51 +0200 Subject: [PATCH 529/948] enh(tapi): centreonv2 - manage redirect login default (#53) --- gorgone/gorgone/class/tpapi/centreonv2.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/gorgone/class/tpapi/centreonv2.pm b/gorgone/gorgone/class/tpapi/centreonv2.pm index 31432f8cb33..86566e62a31 100644 --- a/gorgone/gorgone/class/tpapi/centreonv2.pm +++ b/gorgone/gorgone/class/tpapi/centreonv2.pm @@ -79,7 +79,7 @@ sub set_configuration { $self->{http_backend} = defined($options{config}->{backend}) ? $options{config}->{backend} : 'curl'; - $self->{curl_opts} = ['CURLOPT_SSL_VERIFYPEER => 0']; + $self->{curl_opts} = ['CURLOPT_SSL_VERIFYPEER => 0', 'CURLOPT_POSTREDIR => CURL_REDIR_POST_ALL']; my $curl_opts = []; if (defined($options{config}->{curlopts})) { foreach (keys %{$options{config}->{curlopts}}) { From 019bff090e73eef80c9bc4a036197eea7df067ab Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Wed, 30 Sep 2020 17:01:28 +0200 Subject: [PATCH 530/948] enh(discovery): check module installed + paused not an error (#54) --- gorgone/gorgone/class/tpapi/centreonv2.pm | 40 +++++-------------- .../modules/centreon/autodiscovery/class.pm | 36 +++++++++++++++-- 2 files changed, 42 insertions(+), 34 deletions(-) diff --git a/gorgone/gorgone/class/tpapi/centreonv2.pm b/gorgone/gorgone/class/tpapi/centreonv2.pm index 86566e62a31..fbf749115b8 100644 --- a/gorgone/gorgone/class/tpapi/centreonv2.pm +++ b/gorgone/gorgone/class/tpapi/centreonv2.pm @@ -235,38 +235,18 @@ sub get_monitoring_hosts { ); } -sub get_scheduling_jobs { + +sub get_platform_versions { my ($self, %options) = @_; -=pod - my $results = [ - { - execution => { - parameters => { - cron_definition => "* * * * *", - is_paused => 0 - }, - mode => 1 - }, - post_execution => { - commands => [ - { - action => 'COMMAND', - command_line => '/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=9' - } - ] - }, - job_id => 9, - token => "discovery_9_f2b0ea11", - command_line => "/usr/lib/centreon/plugins/centreon_generic_snmp.pl --plugin=os::linux::local::plugin --mode=discovery-snmp --subnet='127.0.0.1/32' --snmp-community='public' --snmp-version='2c' --snmp-port='161' --snmp-timeout='1' \$_EXTRAOPTIONS\$", - target => 1, - status => 1, - last_execution => undef, - uuid_attributes => ["hostname", "ip"] - } - ]; - return (0, $results); -=cut + return $self->request( + method => 'GET', + endpoint => '/platform/versions' + ); +} + +sub get_scheduling_jobs { + my ($self, %options) = @_; my $get_param; if (defined($options{search})) { diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm index c2173d1fd47..f3ec256b1e4 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm @@ -72,6 +72,10 @@ sub new { $connector->{tpapi_centreonv2_name} = defined($options{config}->{tpapi_centreonv2}) && $options{config}->{tpapi_centreonv2} ne '' ? $options{config}->{tpapi_centreonv2} : 'centreonv2'; + $connector->{is_module_installed} = 0; + $connector->{is_module_installed_check_interval} = 60; + $connector->{is_module_installed_last_check} = -1; + $connector->{hdisco_synced} = 0; $connector->{hdisco_synced_time} = -1; $connector->{hdisco_jobs_tokens} = {}; @@ -249,6 +253,7 @@ sub hdisco_addupdate_job { sub hdisco_sync { my ($self, %options) = @_; + return if ($self->{is_module_installed} == 0); return if ($self->{hdisco_synced} == 1 && (time() - $self->{hdisco_synced_time}) < 600); $self->{logger}->writeLogInfo('[autodiscovery] -class- host discovery - sync started'); @@ -428,7 +433,7 @@ sub launchhostdiscovery { return (1, 'job is already running'); } if ($self->{hdisco_jobs_ids}->{$job_id}->{execution}->{mode} == EXECUTION_MODE_PAUSE) { - return (1, 'job is paused'); + return (0, "job '$job_id' is paused"); } $self->{logger}->writeLogInfo("[autodiscovery] -class- host discovery - launching discovery for job '" . $job_id . "'"); @@ -486,7 +491,7 @@ sub launchhostdiscovery { } ); - return 0; + return (0, "job '$job_id' launched"); } sub action_launchhostdiscovery { @@ -570,7 +575,7 @@ sub action_launchhostdiscovery { token => $options{token}, instant => 1, data => { - message => "job '$job_id' launched" + message => $message } ); } @@ -1009,6 +1014,29 @@ sub action_launchservicediscovery { } } +sub is_module_installed { + my ($self) = @_; + + return 1 if ($self->{is_module_installed} == 1); + return 0 if ((time() - $self->{is_module_installed_check_interval}) < $self->{is_module_installed_last_check}); + + $self->{logger}->writeLogDebug('[autodiscovery] -class- host discovery - check centreon module installed'); + $self->{is_module_installed_last_check} = time(); + + my ($status, $results) = $self->{tpapi_centreonv2}->get_platform_versions(); + if ($status != 0) { + $self->{logger}->writeLogError('[autodiscovery] -class- host discovery - cannot get platform versions - ' . $self->{tpapi_centreonv2}->error()); + return 0; + } + + if (defined($results->{modules}->{'centreon-autodiscovery-server'})) { + $self->{logger}->writeLogDebug('[autodiscovery] -class- host discovery - module autodiscovery installed'); + $self->{is_module_installed} = 1; + } + + return $self->{is_module_installed}; +} + sub is_hdisco_synced { my ($self) = @_; @@ -1094,9 +1122,9 @@ sub run { ]; while (1) { + $self->is_module_installed(); $self->hdisco_sync(); - # we try to do all we can my $rev = zmq_poll($self->{poll}, 5000); if (defined($rev) && $rev == 0 && $self->{stop} == 1) { $self->{logger}->writeLogInfo("[autodiscovery] $$ has quit"); From 4b33753fd232b09d100d9086ef820d911a7e2db8 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire <cgagnaire@centreon.com> Date: Wed, 30 Sep 2020 17:35:10 +0200 Subject: [PATCH 531/948] fix(packaging): add missing xml simple dep --- gorgone/packaging/centreon-gorgone.spectemplate | 1 + 1 file changed, 1 insertion(+) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 240275616c1..c51b7936b15 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -17,6 +17,7 @@ Requires: perl(ZMQ::Constants) Requires: perl(Crypt::CBC) Requires: perl(JSON::XS) Requires: perl(JSON::PP) +Requires: perl(XML::Simple) Requires: perl(YAML) Requires: perl(YAML::XS) Requires: perl(DBD::SQLite) From 071f02e71f41e4c13285743c96d1bedf7c8fd492 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire <cgagnaire@centreon.com> Date: Thu, 1 Oct 2020 10:23:18 +0200 Subject: [PATCH 532/948] fix(autodiscovery): use uuid_parameters instead of uuid_attributes (#55) --- gorgone/gorgone/modules/centreon/autodiscovery/class.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm index f3ec256b1e4..f123b497eb7 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm @@ -625,7 +625,7 @@ sub discovery_command_result { } $self->{logger}->writeLogInfo("[autodiscovery] -class- host discovery - found result for job '" . $job_id . "'"); - my $uuid_attributes = $self->{hdisco_jobs_ids}->{$job_id}->{uuid_attributes}; + my $uuid_parameters = $self->{hdisco_jobs_ids}->{$job_id}->{uuid_parameters}; my $exit_code = $options{data}->{data}->{result}->{exit_code}; my $output = (defined($options{data}->{data}->{result}->{stderr}) && $options{data}->{data}->{result}->{stderr} ne '') ? $options{data}->{data}->{result}->{stderr} : $options{data}->{data}->{result}->{stdout}; @@ -692,7 +692,7 @@ sub discovery_command_result { # Generate uuid based on attributs my $uuid_char = ''; - foreach (@$uuid_attributes) { + foreach (@$uuid_parameters) { $uuid_char .= $host->{$_} if (defined($host->{$_}) && $host->{$_} ne ''); } my $ctx = Digest::MD5->new; From 51c99516084bab3336a68d61ab729cca3e69f069 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Thu, 1 Oct 2020 11:42:28 +0200 Subject: [PATCH 533/948] enh(legacycmd): add cache system - enhance performance (#56) * enh(legacycmd): add cache system - enhance performance --- .../modules/centreon/legacycmd/class.pm | 64 +++++++++---------- 1 file changed, 30 insertions(+), 34 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index bb1bfc359d4..33cc559389f 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -48,8 +48,10 @@ sub new { } $connector->{config}->{dirty_mode} = defined($connector->{config}->{dirty_mode}) ? $connector->{config}->{dirty_mode} : 1; $connector->{gorgone_illegal_characters} = '`'; + $connector->{cache_refresh_interval} = 60; + $connector->{cache_refresh_last} = -1; - $connector->set_signal_handlers; + $connector->set_signal_handlers(); return $connector; } @@ -85,10 +87,14 @@ sub class_handle_HUP { } } -sub get_pollers_config { +sub cache_refresh { my ($self, %options) = @_; - $self->{pollers} = {}; + return if ((time() - $self->{cache_refresh_interval}) < $self->{cache_refresh_last}); + $self->{cache_refresh_last} = time(); + + # get pollers config + $self->{pollers} = undef; my ($status, $datas) = $self->{class_object_centreon}->custom_execute( request => 'SELECT nagios_server_id, command_file, cfg_dir, centreonbroker_cfg_path, snmp_trapd_path_conf, ' . 'engine_start_command, engine_stop_command, engine_restart_command, engine_reload_command, ' . @@ -101,20 +107,15 @@ sub get_pollers_config { ); if ($status == -1 || !defined($datas)) { $self->{logger}->writeLogError('[legacycmd] Cannot get configuration for pollers'); - return -1; + return ; } $self->{pollers} = $datas; - return 0; -} - -sub get_clapi_user { - my ($self, %options) = @_; - + # get clapi user $self->{clapi_user} = undef; $self->{clapi_password} = undef; - my ($status, $datas) = $self->{class_object_centreon}->custom_execute( + ($status, $datas) = $self->{class_object_centreon}->custom_execute( request => "SELECT contact_alias, contact_passwd " . "FROM `contact` " . "WHERE `contact_admin` = '1' " . @@ -126,38 +127,36 @@ sub get_clapi_user { if ($status == -1 || !defined($datas->[0]->[0])) { $self->{logger}->writeLogInfo('[legacycmd] Cannot get configuration for CLAPI user'); - return 0; - } + } else { + my $clapi_user = $datas->[0]->[0]; + my $clapi_password = $datas->[0]->[1]; + if ($clapi_password =~ m/^md5__(.*)/) { + $clapi_password = $1; + } - my $clapi_user = $datas->[0]->[0]; - my $clapi_password = $datas->[0]->[1]; - if ($clapi_password =~ m/^md5__(.*)/) { - $clapi_password = $1; + $self->{clapi_user} = $clapi_user; + $self->{clapi_password} = $clapi_password; } - $self->{clapi_user} = $clapi_user; - $self->{clapi_password} = $clapi_password; - - return 0; -} - -sub get_illegal_characters { - my ($self, %options) = @_; - - my ($status, $datas) = $self->{class_object_centreon}->custom_execute( + # check illegal characters + ($status, $datas) = $self->{class_object_centreon}->custom_execute( request => "SELECT `value` FROM options WHERE `key` = 'gorgone_illegal_characters'", mode => 2 ); if ($status == -1) { $self->{logger}->writeLogError('[legacycmd] Cannot get illegal characters'); - return -1; + return ; } if (defined($datas->[0]->[0])) { $self->{gorgone_illegal_characters} = $datas->[0]->[0]; } +} - return 0; +sub check_pollers_config { + my ($self, %options) = @_; + + return defined($self->{pollers}) ? 1 : 0; } sub execute_cmd { @@ -590,11 +589,7 @@ sub handle_centcore_dir { sub handle_cmd_files { my ($self, %options) = @_; - return if ( - $self->get_pollers_config() == -1 || - $self->get_clapi_user() == -1 || - $self->get_illegal_characters() == -1 - ); + return if ($self->check_pollers_config() == 0); $self->handle_centcore_cmd(); $self->handle_centcore_dir(); } @@ -707,6 +702,7 @@ sub run { exit(0); } + $self->cache_refresh(); $self->handle_cmd_files(); } } From ad9f1f1fe40d67f226488261988327ce86fef4a9 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Thu, 1 Oct 2020 15:39:25 +0200 Subject: [PATCH 534/948] fix(legacycmd): dont use disable centengine configuration (#57) --- gorgone/gorgone/modules/centreon/legacycmd/class.pm | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index 33cc559389f..1a283b6153d 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -99,9 +99,8 @@ sub cache_refresh { request => 'SELECT nagios_server_id, command_file, cfg_dir, centreonbroker_cfg_path, snmp_trapd_path_conf, ' . 'engine_start_command, engine_stop_command, engine_restart_command, engine_reload_command, ' . 'broker_reload_command, init_script_centreontrapd ' . - 'FROM cfg_nagios ' . - 'JOIN nagios_server ' . - 'WHERE id = nagios_server_id', + 'FROM cfg_nagios, nagios_server ' . + "WHERE nagios_server.id = cfg_nagios.nagios_server_id AND cfg_nagios.nagios_activate = '1'", mode => 1, keys => 'nagios_server_id' ); From 357b57c8f049f2fc122ecc2856350b4bac7acdce Mon Sep 17 00:00:00 2001 From: Laurent Pinsivy <lpinsivy@centreon.com> Date: Fri, 2 Oct 2020 10:31:46 +0200 Subject: [PATCH 535/948] enh(legacy): use full gorgone to export/import conf of Remote Server (#58) * enh(legacy): use full gorgone to export/import conf of Remote Server --- .../modules/centreon/legacycmd/class.pm | 102 +++++++++++------- .../modules/centreon/legacycmd/hooks.pm | 3 +- 2 files changed, 65 insertions(+), 40 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index 1a283b6153d..951672efa9a 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -255,25 +255,18 @@ sub execute_cmd { }, ); - my $centreon_dir = (defined($connector->{config}->{centreon_dir})) ? - $connector->{config}->{centreon_dir} : '/usr/share/centreon'; - my $task_id = $options{param}; - my $cmd = $centreon_dir . '/bin/centreon -u ' . $self->{clapi_user} . ' -p ' . - $self->{clapi_password} . ' -w -o CentreonWorker -a createRemoteTask -v ' . $task_id; + # Forward data use to be done by createRemoteTask as well as task_id in a gorgone command + # Command name: AddImportTaskWithParent + # Data: ['parent_id' => $task->getId()] $self->send_internal_action( - action => 'COMMAND', - target => undef, - token => $token, + action => 'ADDIMPORTTASKWITHPARENT', + token => $options{token}, + target => $options{target}, data => { - content => [ - { - command => $cmd, - metadata => { - centcore_cmd => 'SENDEXPORTFILE', - } - } - ] - }, + content => { + parent_id => $options{param}, + } + } ); } elsif ($options{cmd} eq 'SYNCTRAP') { my $cache_dir = (defined($connector->{config}->{cache_dir}) && $connector->{config}->{cache_dir} ne '') ? @@ -448,32 +441,63 @@ sub execute_cmd { ] }, ); - } elsif ($options{cmd} eq 'CREATEREMOTETASK') { - if (!defined($self->{clapi_password})) { - return (-1, 'need centreon clapi password to execute CREATEREMOTETASK command'); - } - my $centreon_dir = (defined($connector->{config}->{centreon_dir})) ? - $connector->{config}->{centreon_dir} : '/usr/share/centreon'; - my $task_id = $options{target}; - my $cmd = $centreon_dir . '/bin/centreon -u ' . $self->{clapi_user} . ' -p ' . - $self->{clapi_password} . ' -w -o CentreonWorker -a createRemoteTask -v ' . $task_id; - $self->send_internal_action( - action => 'COMMAND', - target => undef, - token => $token, + } + + return 0; +} + +sub action_addimporttaskwithparent { + my ($self, %options) = @_; + + if (!defined($options{data}->{content}->{parent_id})) { + $self->send_log( + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, data => { - content => [ - { - command => $cmd, - metadata => { - centcore_cmd => 'CREATEREMOTETASK', - } - } - ] - }, + message => "expected parent_id task ID, found '" . $options{data}->{content}->{parent_id} . "'", + } + ); + return -1; + } + + my ($status, $datas) = $self->{class_object_centreon}->custom_execute( + request => "INSERT INTO task (`type`, `status`, `parent_id`) VALUES ('import', 'pending', '" . $options{data}->{content}->{parent_id} . "')" + ); + if ($status == -1) { + $self->send_log( + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + data => { + message => "Cannot add import task on Remote Server.", + } ); + return -1; } + my $centreon_dir = (defined($connector->{config}->{centreon_dir})) ? + $connector->{config}->{centreon_dir} : '/usr/share/centreon'; + my $cmd = $centreon_dir . '/bin/centreon -u ' . $self->{clapi_user} . ' -p ' . + $self->{clapi_password} . ' -w -o CentreonWorker -a processQueue'; + $self->send_internal_action( + action => 'COMMAND', + token => $options{token}, + data => { + content => [ + { + command => $cmd, + } + ] + }, + ); + + $self->send_log( + code => GORGONE_ACTION_FINISH_OK, + token => $options{token}, + data => { + message => 'Task inserted on Remote Server', + } + ); + return 0; } diff --git a/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm b/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm index e8d0eff20f7..34c5ee280bb 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm @@ -31,7 +31,8 @@ use constant NAMESPACE => 'centreon'; use constant NAME => 'legacycmd'; use constant EVENTS => [ { event => 'CENTREONCOMMAND', uri => '/command', method => 'POST' }, - { event => 'LEGACYCMDREADY' } + { event => 'LEGACYCMDREADY' }, + { event => 'ADDIMPORTTASKWITHPARENT' } ]; my $config_core; From 6bd0e8a3951bf700c646c5d91142c7f4092d6083 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Wed, 7 Oct 2020 13:41:26 +0200 Subject: [PATCH 536/948] MON-6109: failed command because of timeout does not store message (#59) --- gorgone/gorgone/modules/centreon/autodiscovery/class.pm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm index f123b497eb7..63a5679b677 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm @@ -888,12 +888,14 @@ sub action_hostdiscoveryjoblistener { # return 1; #} + # Can happen if we have a execution command timeout + my $message = defined($options{data}->{data}->{result}->{stdout}) ? $options{data}->{data}->{result}->{stdout} : $options{data}->{message}; if ($options{data}->{code} == GORGONE_ACTION_FINISH_KO) { $self->{hdisco_jobs_ids}->{$job_id}->{status} = JOB_FAILED; $self->update_job_information( values => { status => JOB_FAILED, - message => $options{data}->{message}, + message => $message, duration => 0, discovered_items => 0 }, From c41b594079e430e5b07e56916bcf0de029dfb5bf Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Wed, 7 Oct 2020 14:10:16 +0200 Subject: [PATCH 537/948] MON-6110: timeout is not retrieved for forced execution (#60) --- .../modules/centreon/autodiscovery/class.pm | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm index 63a5679b677..c274c8a0a0e 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm @@ -166,7 +166,7 @@ sub hdisco_add_cron { action => 'LAUNCHHOSTDISCOVERY', parameters => { job_id => $options{job}->{job_id}, - timeout => $self->{global_timeout} + timeout => (defined($options{job}->{timeout}) && $options{job}->{timeout} =~ /(\d+)/) ? $1 : $self->{global_timeout} } }; $self->send_internal_action( @@ -242,7 +242,10 @@ sub hdisco_addupdate_job { if ($self->{hdisco_jobs_ids}->{ $options{job}->{job_id} }->{execution}->{mode} == EXECUTION_MODE_CRON && ($extra_infos->{cron_added} == CRON_ADDED_NONE || $extra_infos->{cron_added} == CRON_ADDED_KO) ) { - ($status, $message) = $self->hdisco_add_cron(job => $options{job}, discovery_token => $options{job}->{token}); + ($status, $message) = $self->hdisco_add_cron( + job => $options{job}, + discovery_token => $options{job}->{token} + ); return ($status, $message) if ($status); $self->{hdisco_jobs_ids}->{ $options{job}->{job_id} }->{extra_infos}->{cron_added} = CRON_ADDED_PROGRESS; } @@ -371,6 +374,7 @@ sub action_addhostdiscoveryjob { return 1; } + $job->{timeout} = $options{data}->{content}->{timeout}; ($status, $message) = $self->hdisco_addupdate_job(job => $job); if ($status) { $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - add job '$options{data}->{content}->{job_id}' - $message"); @@ -386,15 +390,9 @@ sub action_addhostdiscoveryjob { # Launch a immediate job. if ($self->{hdisco_jobs_ids}->{ $options{data}->{content}->{job_id} }->{execution}->{mode} == EXECUTION_MODE_IMMEDIATE) { - my $timeout = (defined($options{data}->{content}->{timeout}) && $options{data}->{content}->{timeout} =~ /(\d+)/) ? - $options{data}->{content}->{timeout} : $self->{global_timeout}; - ($status, $message) = $self->action_launchhostdiscovery( - data => { - content => { - timeout => $timeout, - job_id => $options{data}->{content}->{job_id} - } - } + ($status, $message) = $self->launchhostdiscovery( + job_id => $options{data}->{content}->{job_id}, + timeout => $options{data}->{content}->{timeout} ); if ($status) { $self->send_log( @@ -456,6 +454,7 @@ sub launchhostdiscovery { return (1, 'cannot update job status'); } $self->{hdisco_jobs_ids}->{$job_id}->{status} = JOB_RUNNING; + my $timeout = (defined($timeout) && $timeout =~ /(\d+)/) ? $1 : $self->{global_timeout}; $self->send_internal_action( action => 'ADDLISTENER', @@ -465,8 +464,7 @@ sub launchhostdiscovery { event => 'HOSTDISCOVERYJOBLISTENER', target => $self->{hdisco_jobs_ids}->{$job_id}->{target}, token => $self->{hdisco_jobs_ids}->{$job_id}->{token}, - timeout => defined($options{data}->{content}->{timeout}) && $options{data}->{content}->{timeout} =~ /(\d+)/ ? - $1 + $self->{check_interval} + 15 : undef, + timeout => $timeout + $self->{check_interval} + 15, log_pace => $self->{check_interval} } ] @@ -481,7 +479,7 @@ sub launchhostdiscovery { { instant => 1, command => $self->{hdisco_jobs_ids}->{$job_id}->{command_line}, - timeout => defined($options{data}->{content}->{timeout}) && $options{data}->{content}->{timeout} =~ /(\d+)/ ? $1 : undef, + timeout => $timeout, metadata => { job_id => $job_id, source => 'autodiscovery-host-job-discovery' @@ -509,12 +507,13 @@ sub action_launchhostdiscovery { return ; } - my $job_id; + my ($job_id, $timeout); if (defined($options{data}->{variables}->[0]) && defined($options{data}->{variables}->[1]) && $options{data}->{variables}->[1] eq 'schedule') { $job_id = $options{data}->{variables}->[0]; } elsif (defined($options{data}->{content}->{job_id})) { $job_id = $options{data}->{content}->{job_id}; + $timeout = $options{data}->{content}->{timeout}; } my ($status, $message, $job); @@ -556,7 +555,10 @@ sub action_launchhostdiscovery { return 1; } - ($status, $message) = $self->launchhostdiscovery(job_id => $job_id, %options); + ($status, $message) = $self->launchhostdiscovery( + job_id => $job_id, + timeout => $timeout + ); if ($status) { $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - launch discovery job '$job_id' - $message"); $self->send_log( From 954a2e716f48430111e431e5d1f346e7c9a2d29b Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Wed, 7 Oct 2020 14:20:15 +0200 Subject: [PATCH 538/948] Mon 6111 (#61) * MON-6111: force execution does not overload paused job --- .../modules/centreon/autodiscovery/class.pm | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm index c274c8a0a0e..b2967b62563 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm @@ -392,7 +392,8 @@ sub action_addhostdiscoveryjob { if ($self->{hdisco_jobs_ids}->{ $options{data}->{content}->{job_id} }->{execution}->{mode} == EXECUTION_MODE_IMMEDIATE) { ($status, $message) = $self->launchhostdiscovery( job_id => $options{data}->{content}->{job_id}, - timeout => $options{data}->{content}->{timeout} + timeout => $options{data}->{content}->{timeout}, + source => 'immediate' ); if ($status) { $self->send_log( @@ -430,7 +431,7 @@ sub launchhostdiscovery { if ($self->hdisco_is_running_job(status => $self->{hdisco_jobs_ids}->{$job_id}->{status})) { return (1, 'job is already running'); } - if ($self->{hdisco_jobs_ids}->{$job_id}->{execution}->{mode} == EXECUTION_MODE_PAUSE) { + if ($self->{hdisco_jobs_ids}->{$job_id}->{execution}->{mode} == EXECUTION_MODE_PAUSE && $options{source} eq 'cron') { return (0, "job '$job_id' is paused"); } @@ -454,7 +455,7 @@ sub launchhostdiscovery { return (1, 'cannot update job status'); } $self->{hdisco_jobs_ids}->{$job_id}->{status} = JOB_RUNNING; - my $timeout = (defined($timeout) && $timeout =~ /(\d+)/) ? $1 : $self->{global_timeout}; + my $timeout = (defined($options{timeout}) && $options{timeout} =~ /(\d+)/) ? $1 : $self->{global_timeout}; $self->send_internal_action( action => 'ADDLISTENER', @@ -507,13 +508,15 @@ sub action_launchhostdiscovery { return ; } - my ($job_id, $timeout); + my ($job_id, $timeout, $source); if (defined($options{data}->{variables}->[0]) && defined($options{data}->{variables}->[1]) && $options{data}->{variables}->[1] eq 'schedule') { $job_id = $options{data}->{variables}->[0]; + $source = 'immediate'; } elsif (defined($options{data}->{content}->{job_id})) { $job_id = $options{data}->{content}->{job_id}; $timeout = $options{data}->{content}->{timeout}; + $source = 'cron'; } my ($status, $message, $job); @@ -557,7 +560,8 @@ sub action_launchhostdiscovery { ($status, $message) = $self->launchhostdiscovery( job_id => $job_id, - timeout => $timeout + timeout => $timeout, + source => $source ); if ($status) { $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - launch discovery job '$job_id' - $message"); From 27625d5759bb915a1f90de661f7e9b6b89b2de47 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Wed, 7 Oct 2020 15:02:55 +0200 Subject: [PATCH 539/948] MON-6114: catch API returned message instead of request message (#62) --- gorgone/gorgone/class/tpapi/centreonv2.pm | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/class/tpapi/centreonv2.pm b/gorgone/gorgone/class/tpapi/centreonv2.pm index fbf749115b8..9051bb0cdca 100644 --- a/gorgone/gorgone/class/tpapi/centreonv2.pm +++ b/gorgone/gorgone/class/tpapi/centreonv2.pm @@ -196,22 +196,26 @@ sub request { critical_status => '' ); + my $decoded = $self->json_decode(content => $content); + # code 403 means forbidden (token not good maybe) if ($self->{http}->get_code() == 403) { $self->{token} = undef; $self->{is_logged} = 0; $self->{is_error} = 1; $self->{error} = 'token forbidden'; + $self->{error} = $decoded->{message} if (defined($decoded) && defined($decoded->{message})); return 1; } if ($self->{http}->get_code() < 200 || $self->{http}->get_code() >= 300) { $self->{is_error} = 1; - $self->{error} = "request error [code: '" . $self->{http}->get_code() . "'] [message: '" . $self->{http}->get_message() . "']"; + my $message = $self->{http}->get_message(); + $message = $decoded->{message} if (defined($decoded) && defined($decoded->{message})); + $self->{error} = "request error [code: '" . $self->{http}->get_code() . "'] [message: '" . $message . "']"; return 1; } - my $decoded = $self->json_decode(content => $content); return 1 if (!defined($decoded)); return (0, $decoded); From 6d04b50cde6d9b2f0e334ac6f07d9b3ae93e8988 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire <cgagnaire@centreon.com> Date: Wed, 7 Oct 2020 15:36:28 +0200 Subject: [PATCH 540/948] fix(doc): add ssh_connect_timeout option to register module --- gorgone/docs/modules/core/register.md | 1 + 1 file changed, 1 insertion(+) diff --git a/gorgone/docs/modules/core/register.md b/gorgone/docs/modules/core/register.md index cdb4430e8d7..9c5bfdd5f9b 100644 --- a/gorgone/docs/modules/core/register.md +++ b/gorgone/docs/modules/core/register.md @@ -69,6 +69,7 @@ nodes: | ssh\_identity | Path to the identity file | | ssh\_username | SSH username | | ssh\_password | SSH password (if no SSH key) | +| ssh\_connect\_timeout | Time is seconds before a connection is considered timed out | | strict\_serverkey\_check | Boolean to strictly check the node fingerprint | | prevail | Defines if this configuration prevails on `nodes` module configuration | From e5117d9d96d34ea51aace16fdb2cf9d7790fab58 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Wed, 7 Oct 2020 15:52:36 +0200 Subject: [PATCH 541/948] MON-6121: request errors filling up logs on standard install (#63) --- .../gorgone/modules/centreon/autodiscovery/class.pm | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm index b2967b62563..646ef94d822 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm @@ -77,7 +77,8 @@ sub new { $connector->{is_module_installed_last_check} = -1; $connector->{hdisco_synced} = 0; - $connector->{hdisco_synced_time} = -1; + $connector->{hdisco_synced_failed_time} = -1; + $connector->{hdisco_synced_ok_time} = -1; $connector->{hdisco_jobs_tokens} = {}; $connector->{hdisco_jobs_ids} = {}; @@ -257,13 +258,16 @@ sub hdisco_sync { my ($self, %options) = @_; return if ($self->{is_module_installed} == 0); - return if ($self->{hdisco_synced} == 1 && (time() - $self->{hdisco_synced_time}) < 600); + return if ($self->{hdisco_synced} == 0 && (time() - $self->{hdisco_synced_failed_time}) < 60); + return if ($self->{hdisco_synced} == 1 && (time() - $self->{hdisco_synced_ok_time}) < 600); $self->{logger}->writeLogInfo('[autodiscovery] -class- host discovery - sync started'); my ($status, $results, $message); + $self->{hdisco_synced} = 0; ($status, $results) = $self->{tpapi_centreonv2}->get_scheduling_jobs(); if ($status != 0) { + $self->{hdisco_synced_failed_time} = time(); $self->{logger}->writeLogError('[autodiscovery] -class- host discovery - cannot get host discovery jobs - ' . $self->{tpapi_centreonv2}->error()); return ; } @@ -289,7 +293,7 @@ sub hdisco_sync { delete $self->{hdisco_jobs_ids}->{$job_id}; } - $self->{hdisco_synced_time} = time(); + $self->{hdisco_synced_ok_time} = time(); $self->{hdisco_synced} = 1; } From 62324ae3e456bc5408bd76b28fce2ce73588875c Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Thu, 8 Oct 2020 16:04:41 +0200 Subject: [PATCH 542/948] MON-6116: decode JSON fails when not utf8 (#64) --- gorgone/gorgone/modules/centreon/autodiscovery/class.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm index 646ef94d822..c2e7437b174 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm @@ -652,7 +652,7 @@ sub discovery_command_result { my $result; eval { - $result = JSON::XS->new->utf8->decode($output); + $result = JSON::XS->new->decode($output); }; if ($@) { @@ -710,7 +710,7 @@ sub discovery_command_result { my $digest = $ctx->hexdigest; my $uuid = substr($digest, 0, 8) . '-' . substr($digest, 8, 4) . '-' . substr($digest, 12, 4) . '-' . substr($digest, 16, 4) . '-' . substr($digest, 20, 12); - my $encoded_host = JSON::XS->new->utf8->encode($host); + my $encoded_host = JSON::XS->new->encode($host); # Build bulk insert $values .= $append . "(" . $self->{class_object_centreon}->quote(value => $job_id) . ", " . From 24de5576be58b9962c9150e62f3989809f4c8b81 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire <cgagnaire@centreon.com> Date: Tue, 13 Oct 2020 09:14:21 +0200 Subject: [PATCH 543/948] enh(packaging): add noreplace on config files (#67) --- gorgone/packaging/centreon-gorgone.spectemplate | 4 ++-- gorgone/packaging/config.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index c51b7936b15..94135c0f3c2 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -109,8 +109,8 @@ rm -rf %{buildroot} %{_bindir}/getent passwd centreon-gorgone &>/dev/null || %{_sbindir}/useradd -g centreon-gorgone -m -d %{_localstatedir}/lib/centreon-gorgone -r centreon-gorgone 2> /dev/null %files centreon-config -%{_sysconfdir}/centreon-gorgone/config.d/30-centreon.yaml -%{_sysconfdir}/centreon-gorgone/config.d/31-centreon-api.yaml +%config(noreplace) %{_sysconfdir}/centreon-gorgone/config.d/30-centreon.yaml +%config(noreplace) %{_sysconfdir}/centreon-gorgone/config.d/31-centreon-api.yaml %defattr(-, centreon-gorgone, centreon-gorgone, -) %{_localstatedir}/cache/centreon-gorgone/autodiscovery diff --git a/gorgone/packaging/config.yaml b/gorgone/packaging/config.yaml index 18486132a1d..d5fb3439db9 100644 --- a/gorgone/packaging/config.yaml +++ b/gorgone/packaging/config.yaml @@ -1,3 +1,3 @@ name: config.yaml -description: Configuration brought by Centreon Gorgone +description: Configuration brought by Centreon Gorgone package. SHOULD NOT BE EDITED! USE CONFIG.D DIRECTORY! configuration: !include /etc/centreon-gorgone/config.d/*.yaml From 48b45eaeeea1883b7e28a9f0c63855eac05ea205 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Wed, 14 Oct 2020 11:17:27 +0200 Subject: [PATCH 544/948] fix(core): set linger option properly (#68) --- .../gorgone/modules/centreon/engine/class.pm | 2 +- gorgone/gorgone/modules/core/action/class.pm | 21 +++++++------------ gorgone/gorgone/modules/core/pull/hooks.pm | 2 +- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/engine/class.pm b/gorgone/gorgone/modules/centreon/engine/class.pm index f9041bd75f5..463abbaae28 100644 --- a/gorgone/gorgone/modules/centreon/engine/class.pm +++ b/gorgone/gorgone/modules/centreon/engine/class.pm @@ -202,7 +202,7 @@ sub action_run { zmq_type => 'ZMQ_DEALER', name => 'gorgoneengine-'. $$, logger => $self->{logger}, - linger => 5000, + zmq_linger => 5000, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index a6cd80e4e89..3361d56857b 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -47,7 +47,7 @@ sub new { bless $connector, $class; $connector->{process_copy_files_error} = {}; - + $connector->{command_timeout} = defined($connector->{config}->{command_timeout}) ? $connector->{config}->{command_timeout} : 30; @@ -348,7 +348,7 @@ sub action_run { zmq_type => 'ZMQ_DEALER', name => 'gorgoneaction-'. $$, logger => $self->{logger}, - linger => 5000, + zmq_linger => 5000, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); @@ -371,14 +371,9 @@ sub action_run { sub create_child { my ($self, %options) = @_; - $options{message} =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; - - my ($action, $token) = ($1, $2); - my $data = JSON::XS->new->utf8->decode($3); - - if ($action =~ /^BCAST.*/) { - if ((my $method = $self->can('action_' . lc($action)))) { - $method->($self, token => $token, data => $data); + if ($options{action} =~ /^BCAST.*/) { + if ((my $method = $self->can('action_' . lc($options{action})))) { + $method->($self, token => $options{token}, data => $options{data}); } return undef; } @@ -389,14 +384,14 @@ sub create_child { $self->{logger}->writeLogError("[action] Cannot fork process: $!"); $self->send_log( code => GORGONE_ACTION_FINISH_KO, - token => $token, + token => $options{token}, data => { message => "cannot fork: $!" } ); return undef; } if ($child_pid == 0) { - $self->action_run(action => $action, token => $token, data => $data); + $self->action_run(action => $options{action}, token => $options{token}, data => $options{data}); exit(0); } } @@ -417,7 +412,7 @@ sub event { $method->($connector, token => $token, data => $data); } } else{ - $connector->create_child(message => $message); + $connector->create_child(action => $action, token => $token, data => $data); } } diff --git a/gorgone/gorgone/modules/core/pull/hooks.pm b/gorgone/gorgone/modules/core/pull/hooks.pm index 21421dd3457..3305b884a35 100644 --- a/gorgone/gorgone/modules/core/pull/hooks.pm +++ b/gorgone/gorgone/modules/core/pull/hooks.pm @@ -55,7 +55,7 @@ sub init { logger => $options{logger}, type => $config_core->{internal_com_type}, path => $config_core->{internal_com_path}, - linger => $config->{linger} + zmq_linger => $config->{linger} ); $client = gorgone::class::clientzmq->new( identity => $config_core->{id}, From 086aafea19070527c94d575fd46390f27359b425 Mon Sep 17 00:00:00 2001 From: garnier-quentin <garnier.quentin@gmail.com> Date: Mon, 19 Oct 2020 14:36:33 +0200 Subject: [PATCH 545/948] fix(legacycmd): add cache system - enhance performance --- gorgone/gorgone/modules/centreon/legacycmd/class.pm | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index 951672efa9a..563a89af2fd 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -635,11 +635,7 @@ sub action_centreoncommand { return -1; } - if ( - $self->get_pollers_config() == -1 || - $self->get_clapi_user() == -1 || - $self->get_illegal_characters() == -1 - ) { + if ($self->check_pollers_config() == 0) { $self->send_log(code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot get centreon database configuration' }); return 1; } From e22c21d3bee00b187b475aa3f2ee33920df0d055 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Tue, 27 Oct 2020 09:54:38 +0100 Subject: [PATCH 546/948] MON-6217: some errors not catched when timeout (#69) --- gorgone/gorgone/modules/centreon/autodiscovery/class.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm index c2e7437b174..b3cc91ec268 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm @@ -899,7 +899,8 @@ sub action_hostdiscoveryjoblistener { #} # Can happen if we have a execution command timeout - my $message = defined($options{data}->{data}->{result}->{stdout}) ? $options{data}->{data}->{result}->{stdout} : $options{data}->{message}; + my $message = defined($options{data}->{data}->{result}->{stdout}) ? $options{data}->{data}->{result}->{stdout} : $options{data}->{data}->{message}; + $message = $options{data}->{message} if (!defined($message)); if ($options{data}->{code} == GORGONE_ACTION_FINISH_KO) { $self->{hdisco_jobs_ids}->{$job_id}->{status} = JOB_FAILED; $self->update_job_information( From 0b28b270e790e165982675458a04cf65a708ca89 Mon Sep 17 00:00:00 2001 From: Adrien Morais <amorais@centreon.com> Date: Fri, 30 Oct 2020 11:21:10 +0100 Subject: [PATCH 547/948] chore(install): update version to 21.04.0 --- gorgone/Jenkinsfile | 2 +- gorgone/packaging/centreon-gorgone.spectemplate | 2 +- gorgone/sonar-project.properties | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gorgone/Jenkinsfile b/gorgone/Jenkinsfile index cf5b9b9f75d..2c0608e6bca 100644 --- a/gorgone/Jenkinsfile +++ b/gorgone/Jenkinsfile @@ -1,7 +1,7 @@ /* ** Variables. */ -def serie = '20.10' +def serie = '21.04' def maintenanceBranch = "${serie}.x" if (env.BRANCH_NAME.startsWith('release-')) { env.BUILD = 'RELEASE' diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 94135c0f3c2..b9788a52ca3 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -1,5 +1,5 @@ Name: centreon-gorgone -Version: 20.10.0 +Version: 21.04.0 Release: @RELEASE@%{?dist} Summary: Perl daemon task handlers Group: Applications/System diff --git a/gorgone/sonar-project.properties b/gorgone/sonar-project.properties index ae9761bb59e..358e82d7c62 100644 --- a/gorgone/sonar-project.properties +++ b/gorgone/sonar-project.properties @@ -1,3 +1,3 @@ -sonar.projectKey=centreon-gorgone-20.10 -sonar.projectName=Centreon Gorgone 20.10 +sonar.projectKey=centreon-gorgone-21.04 +sonar.projectName=Centreon Gorgone 21.04 sonar.sources=. From 7e7d8650d89a156a6c81462be441a4ac10ab1ce3 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Thu, 5 Nov 2020 09:29:31 +0100 Subject: [PATCH 548/948] Mon 6281: Service discovery email not working properly (#73) --- gorgone/gorgone/class/tpapi/clapi.pm | 2 +- .../autodiscovery/services/discovery.pm | 51 ++++++++++--------- .../autodiscovery/services/resources.pm | 2 +- .../packaging/centreon-gorgone.spectemplate | 1 + 4 files changed, 31 insertions(+), 25 deletions(-) diff --git a/gorgone/gorgone/class/tpapi/clapi.pm b/gorgone/gorgone/class/tpapi/clapi.pm index 0b91ebc716d..81d3e817037 100644 --- a/gorgone/gorgone/class/tpapi/clapi.pm +++ b/gorgone/gorgone/class/tpapi/clapi.pm @@ -81,7 +81,7 @@ sub get_applycfg_command { return undef; } - return 'centreon -u ' . $options{config}->{username} . ' -p ' . $options{config}->{password} . ' -a APPLYCFG -v ' . $options{poller_id}; + return 'centreon -u ' . $self->{username} . ' -p ' . $self->{password} . ' -a APPLYCFG -v ' . $options{poller_id}; } 1; diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm index 3aec7aeb779..9adbb1ad772 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm @@ -29,7 +29,9 @@ use gorgone::standard::constants qw(:all); use gorgone::modules::centreon::autodiscovery::services::resources; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); +use Net::SMTP; use XML::Simple; +use POSIX qw(strftime); sub new { my ($class, %options) = @_; @@ -43,7 +45,6 @@ sub new { $connector->{tpapi_clapi} = $options{tpapi_clapi}; $connector->{class_object_centreon} = $options{class_object_centreon}; $connector->{class_object_centstorage} = $options{class_object_centstorage}; - $connector->{mail_command} = defined($connector->{config}->{mail_command}) ? $connector->{config}->{mail_command} : '/bin/mail'; $connector->{mail_subject} = defined($connector->{config}->{mail_subject}) ? $connector->{config}->{mail_subject} : 'Centreon Auto Discovery'; $connector->{mail_from} = defined($connector->{config}->{mail_from}) ? $connector->{config}->{mail_from} : 'centreon-autodisco'; @@ -116,8 +117,8 @@ sub send_email { my $messages = {}; foreach my $journal (@{$self->{discovery}->{journal}}) { - $messages->{ $journal->{rule_id } } = '' if (!defined($messages->{ $journal->{rule_id } })); - $messages->{ $journal->{rule_id } } .= $journal->{type} . " service '" . $journal->{service_name} . "' on host '" . $journal->{host_name} . "'.\n"; + $messages->{ $journal->{rule_id } } = [] if (!defined($messages->{ $journal->{rule_id } })); + push @{$messages->{ $journal->{rule_id } }}, $journal->{type} . " service '" . $journal->{service_name} . "' on host '" . $journal->{host_name} . "'."; } my $contact_send = {}; @@ -129,34 +130,38 @@ sub send_email { next if (defined($contact_send->{$contact_id})); $contact_send->{$contact_id} = 1; - my $message = ''; + my $body = []; foreach my $rule_id2 (keys %{$messages}) { if (defined($self->{discovery}->{rules}->{$rule_id2}->{contact}->{$contact_id})) { - $message .= $messages->{$rule_id2} . '\n'; + push @$body, @{$messages->{$rule_id2}}; } } - if ($message ne '') { + if (scalar(@$body) > 0) { $self->{logger}->writeLogInfo("[autodiscovery] -servicediscovery- $self->{uuid} send email to '" . $contact_id . "' (" . $self->{discovery}->{rules}->{$rule_id}->{contact}->{$contact_id}->{contact_email} . ")"); - $self->send_internal_action( - action => 'COMMAND', - token => $self->{discovery}->{token} . ':email', - data => { - content => [ - { - command => sprintf( - "/usr/bin/printf '%s' | %s -s '%s' -r %s %s", - $message, - $self->{mail_command}, - $self->{mail_subject}, - $self->{mail_from}, - $self->{discovery}->{rules}->{$rule_id}->{contact}->{$contact_id}->{contact_email} - ) - } - ] - } + my $smtp = Net::SMTP->new('localhost', Timeout => 15); + if (!defined($smtp)) { + $self->{logger}->writeLogError("[autodiscovery] -servicediscovery- sent email error - " . $@); + next; + } + $smtp->mail($self->{mail_from}); + if (!$smtp->to($self->{discovery}->{rules}->{$rule_id}->{contact}->{$contact_id}->{contact_email})) { + $self->{logger}->writeLogError("[autodiscovery] -servicediscovery- sent email error - " . $smtp->message()); + next; + } + + $smtp->data(); + $smtp->datasend( + 'Date: ' . strftime('%a, %d %b %Y %H:%M:%S %z', localtime(time())) . "\n" . + 'From: ' . $self->{mail_from} . "\n" . + 'To: ' . $self->{discovery}->{rules}->{$rule_id}->{contact}->{$contact_id}->{contact_email} . "\n" . + 'Subject: ' . $self->{mail_subject} . "\n" . + "\n" . + join("\n", @$body) . "\n" ); + $smtp->dataend(); + $smtp->quit(); } } } diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm b/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm index ff67f20048a..0975459c375 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm @@ -288,7 +288,7 @@ sub get_contact { return 0; } - return defined($datas->[0]) ? $datas->[0] : undef; + return defined($datas->{ $options{contact_id} }) ? $datas->{ $options{contact_id} } : undef; } my $done_macro_host = {}; diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index b9788a52ca3..a27a5bed835 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -18,6 +18,7 @@ Requires: perl(Crypt::CBC) Requires: perl(JSON::XS) Requires: perl(JSON::PP) Requires: perl(XML::Simple) +Requires: perl(Net::SMTP) Requires: perl(YAML) Requires: perl(YAML::XS) Requires: perl(DBD::SQLite) From 75758cea44ccc93923af67dbce683a5a968388d1 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Mon, 9 Nov 2020 17:24:03 +0100 Subject: [PATCH 549/948] MON-6303: Decoding error (#74) --- gorgone/gorgone/class/listener.pm | 8 ++++++++ gorgone/gorgone/modules/core/proxy/hooks.pm | 3 ++- gorgone/gorgone/standard/library.pm | 3 ++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/class/listener.pm b/gorgone/gorgone/class/listener.pm index 57bf8b871ec..de0ba4d86f3 100644 --- a/gorgone/gorgone/class/listener.pm +++ b/gorgone/gorgone/class/listener.pm @@ -24,6 +24,7 @@ use strict; use warnings; use gorgone::standard::constants qw(:all); use gorgone::standard::library; +use Encode; sub new { my ($class, %options) = @_; @@ -42,9 +43,16 @@ sub event_log { return if (!defined($self->{tokens}->{$options{token}})); + my $encoded = 0; foreach (keys %{$self->{tokens}->{ $options{token} }->{events}}) { $self->{logger}->writeLogDebug("[listener] trigger event '$options{token}'"); + # it will be decoded by module (hooks.pm). So in some case, we want to avoid double utf8 decode + if ($encoded == 0 && defined($options{encode_utf8}) && $options{encode_utf8} == 1) { + $options{data} = Encode::encode('UTF-8', $options{data}); + $encoded = 1; + } + $self->{gorgone_core}->message_run( message => '[' . $_ . '] [' . $options{token} . '] [] { "code": ' . $options{code} . ', "data": ' . $options{data} . ' }', router_type => 'internal' diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index 963630fa45b..f5326e19b54 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -544,7 +544,8 @@ sub setlogs { code => $_->{code}, token => $_->{token}, instant => $_->{instant}, - data => $_->{data} + data => $_->{data}, + encode_utf8 => 1 ); last if ($status == -1); $ctime_recent = $_->{ctime} if ($ctime_recent < $_->{ctime}); diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index f84cb176b62..176d746b7db 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -671,7 +671,8 @@ sub add_history { $listener->event_log( token => $options{token}, code => $options{code}, - data => $options{data} + data => $options{data}, + encode_utf8 => $options{encode_utf8} ); } return $status; From 668f56a4283e12e6c66b7065558f1cb3428bc3c0 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Tue, 10 Nov 2020 09:02:34 +0100 Subject: [PATCH 550/948] MON-6304: Remove target for zmq direct message (#75) --- gorgone/gorgone/modules/core/proxy/class.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/gorgone/modules/core/proxy/class.pm b/gorgone/gorgone/modules/core/proxy/class.pm index fa68693cedd..1ebc0369310 100644 --- a/gorgone/gorgone/modules/core/proxy/class.pm +++ b/gorgone/gorgone/modules/core/proxy/class.pm @@ -360,7 +360,7 @@ sub proxy { my ($status, $msg) = $connector->{clients}->{$target_client}->{class}->send_message( action => $action, token => $token, - target => $target, + target => $target_direct == 0 ? $target : undef, data => $data ); if ($status != 0) { From 93a4b610df1733be7fd48189c16fa1345578b08f Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Fri, 13 Nov 2020 16:14:49 +0100 Subject: [PATCH 551/948] MON-6317: services discovered flaps (#76) --- .../modules/centreon/autodiscovery/services/discovery.pm | 3 ++- .../modules/centreon/autodiscovery/services/resources.pm | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm index 9adbb1ad772..69ebb6af84c 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm @@ -490,7 +490,8 @@ sub disable_services { return if ($self->{discovery}->{rules}->{ $options{rule_id} }->{rule_disable} != 1 || !defined($self->{discovery}->{rules}->{ $options{rule_id} }->{linked_services}->{ $options{host_id} })); foreach my $service (keys %{$self->{discovery}->{rules}->{ $options{rule_id} }->{linked_services}->{ $options{host_id} }}) { my $service_description = $self->{discovery}->{rules}->{ $options{rule_id} }->{linked_services}->{ $options{host_id} }->{$service}->{service_description}; - if (!defined($options{discovered_services}->{discovered_services}->{$service_description}) && + + if (!defined($options{discovery_svc}->{discovered_services}->{$service_description}) && $self->{discovery}->{rules}->{ $options{rule_id} }->{linked_services}->{ $options{host_id} }->{$service}->{service_activate} == 1) { $self->{logger}->writeLogInfo("$options{logger_pre_message} -> disable service '" . $service_description . "'"); next if ($self->{discovery}->{dry_run} == 1); diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm b/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm index 0975459c375..55d7a2a7634 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm @@ -215,7 +215,7 @@ sub get_rules { $rules->{ $_->[0] }->{linked_services} = {} if (!defined($rules->{ $_->[0] }->{linked_services})); $rules->{ $_->[0] }->{linked_services}->{ $_->[1] } = {} if (!defined($rules->{ $_->[0] }->{linked_services}->{ $_->[1] })); $rules->{ $_->[0] }->{linked_services}->{ $_->[1] }->{ $_->[2] } = { - service_activate => $_->[3], service_description => $_->[3] + service_activate => $_->[3], service_description => $_->[4] }; } From 19dad0ae637d51b8f5ec76f6ae1379e2704e1680 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Thu, 19 Nov 2020 16:08:30 +0100 Subject: [PATCH 552/948] MON-6356: Versions endpoint returns empty array instead of hash (#78) --- gorgone/gorgone/modules/centreon/autodiscovery/class.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm index b3cc91ec268..473967f8812 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm @@ -1042,7 +1042,8 @@ sub is_module_installed { return 0; } - if (defined($results->{modules}->{'centreon-autodiscovery-server'})) { + if (defined($results->{modules}) && ref($results->{modules}) eq 'HASH' && + defined($results->{modules}->{'centreon-autodiscovery-server'})) { $self->{logger}->writeLogDebug('[autodiscovery] -class- host discovery - module autodiscovery installed'); $self->{is_module_installed} = 1; } From 514068f62877e85ab6229f4031dc770af6b32c0d Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Fri, 20 Nov 2020 13:17:02 +0100 Subject: [PATCH 553/948] MON-6365: Force tcp reconnection after 3 ping timeout (#79) --- gorgone/gorgone/modules/core/proxy/class.pm | 20 +++++++- gorgone/gorgone/modules/core/proxy/hooks.pm | 56 +++++++++++++++------ gorgone/gorgone/standard/library.pm | 3 +- 3 files changed, 62 insertions(+), 17 deletions(-) diff --git a/gorgone/gorgone/modules/core/proxy/class.pm b/gorgone/gorgone/modules/core/proxy/class.pm index 1ebc0369310..fbc7f9a168a 100644 --- a/gorgone/gorgone/modules/core/proxy/class.pm +++ b/gorgone/gorgone/modules/core/proxy/class.pm @@ -119,7 +119,7 @@ sub read_message { if ($@) { return undef; } - + if (defined($data->{data}->{action}) && $data->{data}->{action} eq 'getlog') { $connector->send_internal_action( action => 'SETLOGS', @@ -228,6 +228,21 @@ sub action_proxydelnode { } } +sub action_proxycloseconnection { + my ($self, %options) = @_; + + my ($code, $data) = $self->json_decode(argument => $options{data}); + return if ($code == 1); + + return if (!defined($self->{clients}->{ $data->{id} })); + + $self->{logger}->writeLogInfo("[proxy] Close connectionn for $data->{id}"); + + $self->{clients}->{ $data->{id} }->{class}->close(); + $self->{clients}->{ $data->{id} }->{delete} = 0; + $self->{clients}->{ $data->{id} }->{class} = undef; +} + sub close_connections { my ($self, %options) = @_; @@ -326,6 +341,9 @@ sub proxy { (undef, $data) = $connector->json_decode(argument => $data); $connector->action_bcastlogger(data => $data); return ; + } elsif ($action eq 'PROXYCLOSECONNECTION') { + $connector->action_proxycloseconnection(data => $data); + return ; } if ($target_complete !~ /^(.+)~~(.+)$/) { diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index f5326e19b54..49f6bd40988 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -47,6 +47,7 @@ use constant EVENTS => [ { event => 'PROXYDELNODE' }, # internal. Shouldn't be used by third party clients { event => 'PROXYADDSUBNODE' }, # internal. Shouldn't be used by third party clients { event => 'PONGRESET' }, # internal. Shouldn't be used by third party clients + { event => 'PROXYCLOSECONNECTION' } ]; my $config_core; @@ -100,6 +101,7 @@ sub register { $synctimeout_option = defined($config->{synchistory_timeout}) ? $config->{synchistory_timeout} : 30; $ping_option = defined($config->{ping}) ? $config->{ping} : 60; $config->{pong_discard_timeout} = defined($config->{pong_discard_timeout}) ? $config->{pong_discard_timeout} : 300; + $config->{pong_max_timeout} = defined($config->{pong_max_timeout}) ? $config->{pong_max_timeout} : 3; $config->{pool} = defined($config->{pool}) && $config->{pool} =~ /(\d+)/ ? $1 : 5; return (1, NAMESPACE, NAME, EVENTS); } @@ -138,6 +140,7 @@ sub routing { if ($options{action} eq 'PONG') { return undef if (!defined($data->{data}->{id}) || $data->{data}->{id} eq ''); $synctime_nodes->{$data->{data}->{id}}->{in_progress_ping} = 0; + $synctime_nodes->{$data->{data}->{id}}->{ping_timeout} = 0; $last_pong->{$data->{data}->{id}} = time(); $constatus_ping->{$data->{data}->{id}}->{last_ping_recv} = time(); $constatus_ping->{$data->{data}->{id}}->{nodes} = $data->{data}->{data}; @@ -148,21 +151,24 @@ sub routing { if ($options{action} eq 'PONGRESET') { return undef if (!defined($data->{data}->{id}) || $data->{data}->{id} eq ''); - $synctime_nodes->{ $data->{data}->{id} }->{in_progress_ping} = 0 if (defined($synctime_nodes->{ $data->{data}->{id} })); + if (defined($synctime_nodes->{ $data->{data}->{id} })) { + $synctime_nodes->{ $data->{data}->{id} }->{in_progress_ping} = 0; + $synctime_nodes->{ $data->{data}->{id} }->{ping_timeout} = 0; + } $options{logger}->writeLogInfo("[proxy] PongReset received from '" . $data->{data}->{id} . "'"); return undef; } - + if ($options{action} eq 'UNREGISTERNODES') { unregister_nodes(%options, data => $data); return undef; } - + if ($options{action} eq 'REGISTERNODES') { register_nodes(%options, data => $data); return undef; } - + if ($options{action} eq 'PROXYREADY') { $pools->{$data->{pool_id}}->{ready} = 1; # we sent proxyaddnode to sync @@ -179,7 +185,7 @@ sub routing { } return undef; } - + if ($options{action} eq 'SETLOGS') { setlogs(dbh => $options{dbh}, data => $data, token => $options{token}, logger => $options{logger}); return undef; @@ -249,7 +255,7 @@ sub routing { my $action = $options{action}; my $bulk_actions; push @{$bulk_actions}, $data; - + if ($options{action} eq 'REMOTECOPY' && defined($register_nodes->{$target_parent}) && $register_nodes->{$target_parent}->{type} ne 'push_ssh') { $action = 'PROCESSCOPY'; @@ -376,6 +382,19 @@ sub check { if (time() - $constatus_ping->{ $_ }->{last_ping_sent} > $config->{pong_discard_timeout}) { $options{logger}->writeLogInfo("[proxy] Ping timeout from '" . $_ . "'"); $synctime_nodes->{$_}->{in_progress_ping} = 0; + $synctime_nodes->{$_}->{ping_timeout}++; + if (($synctime_nodes->{$_}->{ping_timeout} % $config->{pong_max_timeout}) == 0) { + $options{logger}->writeLogInfo("[proxy] Ping max timeout reached from '" . $_ . "'"); + routing( + socket => $internal_socket, + target => $_, + action => 'PROXYCLOSECONNECTION', + data => JSON::XS->new->utf8->encode({ id => $_ }), + dbh => $options{dbh}, + logger => $options{logger} + ); + $synctime_nodes->{$_}->{ping_timeout}++; + } } } @@ -499,7 +518,7 @@ sub pathway { sub setlogs { my (%options) = @_; - + if (!defined($options{data}->{data}->{id}) || $options{data}->{data}->{id} eq '') { gorgone::standard::library::add_history( dbh => $options{dbh}, @@ -523,6 +542,7 @@ sub setlogs { # we have received the setlogs (it's like a pong response. not a problem if we received the pong after) $synctime_nodes->{ $options{data}->{data}->{id} }->{in_progress_ping} = 0; + $synctime_nodes->{ $options{data}->{data}->{id} }->{ping_timeout} = 0; $constatus_ping->{ $options{data}->{data}->{id} }->{last_ping_recv} = time(); $last_pong->{ $options{data}->{data}->{id} } = time() if (defined($last_pong->{ $options{data}->{data}->{id} })); @@ -629,7 +649,7 @@ sub update_sync_time { sub get_sync_time { my (%options) = @_; - + my ($status, $sth) = $options{dbh}->query("SELECT * FROM gorgone_synchistory WHERE id = '" . $options{node_id} . "'"); if ($status == -1) { $synctime_nodes->{$options{node_id}}->{synctime_error} = -1; @@ -642,7 +662,7 @@ sub get_sync_time { $synctime_nodes->{$row->{id}}->{in_progress} = 0; $synctime_nodes->{$row->{id}}->{in_progress_time} = -1; } - + return 0; } @@ -659,7 +679,7 @@ sub is_all_proxy_ready { sub rr_pool { my (%options) = @_; - + while (1) { $rr_current = $rr_current % $config->{pool}; if ($pools->{$rr_current + 1}->{ready} == 1) { @@ -698,7 +718,6 @@ sub create_child { $pools_pid->{$child_pid} = $options{pool_id}; } -# we don't manage (yet) the target from pull connection sub pull_request { my (%options) = @_; @@ -737,8 +756,8 @@ sub pull_request { gorgone::standard::library::zmq_send_message( socket => $external_socket, - cipher => $config_core->{cipher}, - vector => $config_core->{vector}, + cipher => $config_core->{gorgonecore}->{cipher}, + vector => $config_core->{gorgonecore}->{vector}, symkey => $key, identity => $register_nodes->{ $options{target_parent} }->{identity}, message => $message @@ -883,7 +902,14 @@ sub register_nodes { $last_pong->{ $node->{id} } = 0 if (!defined($last_pong->{ $node->{id} })); if (!defined($synctime_nodes->{ $node->{id} })) { - $synctime_nodes->{ $node->{id} } = { ctime => 0, in_progress_ping => 0, in_progress => 0, in_progress_time => -1, synctime_error => 0 }; + $synctime_nodes->{ $node->{id} } = { + ctime => 0, + in_progress_ping => 0, + in_progress => 0, + in_progress_time => -1, + synctime_error => 0, + ping_timeout => 0 + }; get_sync_time(node_id => $node->{id}, dbh => $options{dbh}); } @@ -1010,7 +1036,7 @@ sub prepare_remote_copy { }; } close FH; - + push @actions, { content => { status => 'end', diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index 176d746b7db..e93eb26f2a4 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -39,6 +39,7 @@ use Time::HiRes; our $listener; my %zmq_type = ('ZMQ_ROUTER' => ZMQ_ROUTER, 'ZMQ_DEALER' => ZMQ_DEALER); +my $ZMQ_CONNECT_TIMEOUT = 79; my $ZMQ_ROUTER_HANDOVER = 56; sub read_config { @@ -734,8 +735,8 @@ sub connect_com { zmq_setsockopt($socket, ZMQ_LINGER, defined($options{zmq_linger}) ? $options{zmq_linger} : 0); # 0 we discard zmq_setsockopt($socket, ZMQ_SNDHWM, defined($options{zmq_sndhwm}) ? $options{zmq_sndhwm} : 0); zmq_setsockopt($socket, ZMQ_RCVHWM, defined($options{zmq_rcvhwm}) ? $options{zmq_rcvhwm} : 0); - #zmq_setsockopt($socket, ZMQ_CONNECT_TIMEOUT, 60000); # for tcp: 60 seconds zmq_setsockopt($socket, ZMQ_RECONNECT_IVL, 1000); + ZMQ::LibZMQ4::zmq_setsockopt_int($socket, $ZMQ_CONNECT_TIMEOUT, defined($options{zmq_connect_timeout}) ? $options{zmq_connect_timeout} : 30000); ZMQ::LibZMQ4::zmq_setsockopt_int($socket, $ZMQ_ROUTER_HANDOVER, defined($options{zmq_router_handover}) ? $options{zmq_router_handover} : 1); zmq_connect($socket, $options{type} . '://' . $options{path}); return $socket; From f06c4bb1785c207c43a572a1989c0e0d3a915cc4 Mon Sep 17 00:00:00 2001 From: garnier-quentin <garnier.quentin@gmail.com> Date: Fri, 27 Nov 2020 11:52:55 +0100 Subject: [PATCH 554/948] add(doc): poller pull configuration sample --- gorgone/docs/poller_pull_configuration.md | 91 +++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 gorgone/docs/poller_pull_configuration.md diff --git a/gorgone/docs/poller_pull_configuration.md b/gorgone/docs/poller_pull_configuration.md new file mode 100644 index 00000000000..8e9aeae19b8 --- /dev/null +++ b/gorgone/docs/poller_pull_configuration.md @@ -0,0 +1,91 @@ +# Architecture + +We are showing how to configure gorgone to manage that architecture: + +```text +Central server <------- Distant Poller +``` + +In our case, we have the following configuration (need to adatp to your configuration). + +* Central server: + * address: 10.30.2.203 +* Distant Poller: + * id: 6 (configured in Centreon interface as **zmq**. You get it in the Centreon interface) + * address: 10.30.2.179 + * rsa public key thumbprint: nJSH9nZN2ugQeksHif7Jtv19RQA58yjxfX-Cpnhx09s + +# Distant Poller + +## Installation + +The Distant Poller is already installed and Gorgone also. + +## Configuration + +We configure the file **/etc/centreon-gorgone/config.d/40-gorgoned.yaml**: + +```yaml +name: distant-server +description: Configuration for distant server +gorgone: + gorgonecore: + id: 6 + privkey: "/var/lib/centreon-gorgone/.keys/rsakey.priv.pem" + pubkey: "/var/lib/centreon-gorgone/.keys/rsakey.pub.pem" + + modules: + - name: action + package: gorgone::modules::core::action::hooks + enable: true + + - name: engine + package: gorgone::modules::centreon::engine::hooks + enable: true + command_file: "/var/lib/centreon-engine/rw/centengine.cmd" + + - name: pull + package: "gorgone::modules::core::pull::hooks" + enable: true + target_type: tcp + target_path: 10.30.2.203:5556 + ping: 1 +``` + +# Central server + +## Installation + +The Central server is already installed and Gorgone also. + +## Configuration + +We configure the file **/etc/centreon-gorgone/config.d/40-gorgoned.yaml**: + +```yaml +... +gorgone: + gorgonecore: + ... + external_com_type: tcp + external_com_path: "*:5556" + authorized_clients: + - key: nJSH9nZN2ugQeksHif7Jtv19RQA58yjxfX-Cpnhx09s + ... + modules: + ... + - name: register + package: "gorgone::modules::core::register::hooks" + enable: true + config_file: /etc/centreon-gorgone/nodes-register-override.yml + ... +``` + +We create the file **/etc/centreon-gorgone/nodes-register-override.yml**: + +```yaml +nodes: + - id: 1024 + type: pull + prevail: 1 +``` From 6deaee874107425586183c1f7f70274f2be84f11 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Mon, 30 Nov 2020 16:03:27 +0100 Subject: [PATCH 555/948] fix(core): DB transaction issue + clean some log (#80) --- gorgone/gorgone/class/db.pm | 19 ++++++++++++++++--- gorgone/gorgone/class/listener.pm | 11 +++++++---- .../centreon/anomalydetection/hooks.pm | 2 +- .../modules/centreon/autodiscovery/hooks.pm | 2 +- .../gorgone/modules/centreon/engine/hooks.pm | 2 +- .../modules/centreon/legacycmd/hooks.pm | 2 +- .../gorgone/modules/centreon/nodes/hooks.pm | 2 +- .../modules/centreon/statistics/hooks.pm | 2 +- gorgone/gorgone/modules/core/action/hooks.pm | 2 +- gorgone/gorgone/modules/core/cron/hooks.pm | 2 +- .../gorgone/modules/core/dbcleaner/class.pm | 7 ++++--- .../gorgone/modules/core/httpserver/hooks.pm | 2 +- .../gorgone/modules/core/register/hooks.pm | 2 +- gorgone/gorgone/standard/library.pm | 2 +- 14 files changed, 38 insertions(+), 21 deletions(-) diff --git a/gorgone/gorgone/class/db.pm b/gorgone/gorgone/class/db.pm index f8108569124..d5ef7d10d7f 100644 --- a/gorgone/gorgone/class/db.pm +++ b/gorgone/gorgone/class/db.pm @@ -47,6 +47,7 @@ sub new { } $self->{instance} = undef; + $self->{transaction_begin} = 0; $self->{args} = []; bless $self, $class; return $self; @@ -98,6 +99,7 @@ sub user { } # Getter/Setter DB force +# force 2 should'nt be used with transaction sub force { my $self = shift; if (@_) { @@ -144,19 +146,27 @@ sub transaction_mode { if ($status) { $self->{instance}->begin_work; + $self->{transaction_begin} = 1; $self->{instance}->{RaiseError} = 1; } else { + $self->{transaction_begin} = 0; $self->{instance}->{AutoCommit} = 1; $self->{instance}->{RaiseError} = 0; } } -sub commit { shift->{instance}->commit; } +sub commit { + my ($self) = @_; + + $self->{instance}->commit; + $self->{transaction_begin} = 0; +} sub rollback { my ($self) = @_; $self->{instance}->rollback() if (defined($self->{instance})); + $self->{transaction_begin} = 0; } sub kill { @@ -264,6 +274,9 @@ sub error { SQL error: $error (caller: $package:$filename:$line) Query: $query EOE + if ($self->{transaction_begin} == 1) { + $self->rollback(); + } $self->disconnect(); $self->{instance} = undef; } @@ -300,12 +313,12 @@ sub query { sleep(1); next; } - + if (defined($options{prepare_only})) { return ($status, $statement_handle); } - my $rv = $statement_handle->execute; + my $rv = $statement_handle->execute(); if (!$rv) { $self->error($statement_handle->errstr, $query); $status = -1; diff --git a/gorgone/gorgone/class/listener.pm b/gorgone/gorgone/class/listener.pm index de0ba4d86f3..50471993c29 100644 --- a/gorgone/gorgone/class/listener.pm +++ b/gorgone/gorgone/class/listener.pm @@ -43,8 +43,14 @@ sub event_log { return if (!defined($self->{tokens}->{$options{token}})); + # we want to avoid loop + my $events = $self->{tokens}->{ $options{token} }; + if ($options{code} == GORGONE_ACTION_FINISH_KO || $options{code} == GORGONE_ACTION_FINISH_OK) { + delete $self->{tokens}->{ $options{token} }; + } + my $encoded = 0; - foreach (keys %{$self->{tokens}->{ $options{token} }->{events}}) { + foreach (keys %{$events->{events}}) { $self->{logger}->writeLogDebug("[listener] trigger event '$options{token}'"); # it will be decoded by module (hooks.pm). So in some case, we want to avoid double utf8 decode @@ -58,9 +64,6 @@ sub event_log { router_type => 'internal' ); } - if ($options{code} == GORGONE_ACTION_FINISH_KO || $options{code} == GORGONE_ACTION_FINISH_OK) { - delete $self->{tokens}->{$options{token}}; - } } sub add_listener { diff --git a/gorgone/gorgone/modules/centreon/anomalydetection/hooks.pm b/gorgone/gorgone/modules/centreon/anomalydetection/hooks.pm index b80228fad6a..4a59f906a80 100644 --- a/gorgone/gorgone/modules/centreon/anomalydetection/hooks.pm +++ b/gorgone/gorgone/modules/centreon/anomalydetection/hooks.pm @@ -130,7 +130,7 @@ sub check { my $count = 0; foreach my $pid (keys %{$options{dead_childs}}) { # Not me - next if ($process->{pid} != $pid); + next if (!defined($process->{pid}) || $process->{pid} != $pid); $process = {}; delete $options{dead_childs}->{$pid}; diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm b/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm index c17895a2c4d..4d7df81b34e 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm @@ -136,7 +136,7 @@ sub check { my $count = 0; foreach my $pid (keys %{$options{dead_childs}}) { # Not me - next if ($autodiscovery->{pid} != $pid); + next if (!defined($autodiscovery->{pid}) || $autodiscovery->{pid} != $pid); $autodiscovery = {}; delete $options{dead_childs}->{$pid}; diff --git a/gorgone/gorgone/modules/centreon/engine/hooks.pm b/gorgone/gorgone/modules/centreon/engine/hooks.pm index dc8e185b865..29942acfe03 100644 --- a/gorgone/gorgone/modules/centreon/engine/hooks.pm +++ b/gorgone/gorgone/modules/centreon/engine/hooks.pm @@ -128,7 +128,7 @@ sub check { my $count = 0; foreach my $pid (keys %{$options{dead_childs}}) { # Not me - next if ($engine->{pid} != $pid); + next if (!defined($engine->{pid}) || $engine->{pid} != $pid); $engine = {}; delete $options{dead_childs}->{$pid}; diff --git a/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm b/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm index 34c5ee280bb..e94ceb5deac 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm @@ -134,7 +134,7 @@ sub check { my $count = 0; foreach my $pid (keys %{$options{dead_childs}}) { # Not me - next if ($legacycmd->{pid} != $pid); + next if (!defined($legacycmd->{pid}) || $legacycmd->{pid} != $pid); $legacycmd = {}; delete $options{dead_childs}->{$pid}; diff --git a/gorgone/gorgone/modules/centreon/nodes/hooks.pm b/gorgone/gorgone/modules/centreon/nodes/hooks.pm index db4f5e9f19f..8687cd7882a 100644 --- a/gorgone/gorgone/modules/centreon/nodes/hooks.pm +++ b/gorgone/gorgone/modules/centreon/nodes/hooks.pm @@ -130,7 +130,7 @@ sub check { my $count = 0; foreach my $pid (keys %{$options{dead_childs}}) { # Not me - next if ($nodes->{pid} != $pid); + next if (!defined($nodes->{pid}) || $nodes->{pid} != $pid); $nodes = {}; delete $options{dead_childs}->{$pid}; diff --git a/gorgone/gorgone/modules/centreon/statistics/hooks.pm b/gorgone/gorgone/modules/centreon/statistics/hooks.pm index db926a86c5a..026f0ce10f1 100644 --- a/gorgone/gorgone/modules/centreon/statistics/hooks.pm +++ b/gorgone/gorgone/modules/centreon/statistics/hooks.pm @@ -144,7 +144,7 @@ sub check { my $count = 0; foreach my $pid (keys %{$options{dead_childs}}) { # Not me - next if ($statistics->{pid} != $pid); + next if (!defined($statistics->{pid}) || $statistics->{pid} != $pid); $statistics = {}; delete $options{dead_childs}->{$pid}; diff --git a/gorgone/gorgone/modules/core/action/hooks.pm b/gorgone/gorgone/modules/core/action/hooks.pm index 88914f1fcac..f80464e8ea9 100644 --- a/gorgone/gorgone/modules/core/action/hooks.pm +++ b/gorgone/gorgone/modules/core/action/hooks.pm @@ -128,7 +128,7 @@ sub check { my $count = 0; foreach my $pid (keys %{$options{dead_childs}}) { # Not me - next if ($action->{pid} != $pid); + next if (!defined($action->{pid}) || $action->{pid} != $pid); $action = {}; delete $options{dead_childs}->{$pid}; diff --git a/gorgone/gorgone/modules/core/cron/hooks.pm b/gorgone/gorgone/modules/core/cron/hooks.pm index 4b040a0f1b4..21464107d1c 100644 --- a/gorgone/gorgone/modules/core/cron/hooks.pm +++ b/gorgone/gorgone/modules/core/cron/hooks.pm @@ -130,7 +130,7 @@ sub check { my $count = 0; foreach my $pid (keys %{$options{dead_childs}}) { # Not me - next if ($cron->{pid} != $pid); + next if (!defined($cron->{pid}) || $cron->{pid} != $pid); $cron = {}; delete $options{dead_childs}->{$pid}; diff --git a/gorgone/gorgone/modules/core/dbcleaner/class.pm b/gorgone/gorgone/modules/core/dbcleaner/class.pm index eea384684bd..8cfb4d9a578 100644 --- a/gorgone/gorgone/modules/core/dbcleaner/class.pm +++ b/gorgone/gorgone/modules/core/dbcleaner/class.pm @@ -96,13 +96,14 @@ sub action_dbclean { $self->{logger}->writeLogDebug("[dbcleaner] Purge database in progress..."); my ($status) = $self->{db_gorgone}->query("DELETE FROM gorgone_identity WHERE `ctime` < " . $self->{db_gorgone}->quote(time() - $self->{config}->{purge_sessions_time})); - my ($status2) = $self->{db_gorgone}->query("DELETE FROM gorgone_history WHERE `ctime` < " . $self->{db_gorgone}->quote(time() - $self->{config}->{purge_history_time})); - my ($status3) = $self->{db_gorgone}->query("DELETE FROM gorgone_history WHERE instant = 1 and `ctime` < " . (time() - 86400)); + my ($status2) = $self->{db_gorgone}->query( + "DELETE FROM gorgone_history WHERE (instant = 1 AND `ctime` < " . (time() - 86400) . ") OR `ctime` < " . $self->{db_gorgone}->quote(time() - $self->{config}->{purge_history_time}) + ); $self->{purge_timer} = time(); $self->{logger}->writeLogDebug("[dbcleaner] Purge finished"); - if ($status == -1 || $status2 == -1 || $status3 == -1) { + if ($status == -1 || $status2 == -1) { $self->send_log( code => GORGONE_ACTION_FINISH_KO, token => $options{token}, diff --git a/gorgone/gorgone/modules/core/httpserver/hooks.pm b/gorgone/gorgone/modules/core/httpserver/hooks.pm index 9f11b4e3eab..8db3d94927b 100644 --- a/gorgone/gorgone/modules/core/httpserver/hooks.pm +++ b/gorgone/gorgone/modules/core/httpserver/hooks.pm @@ -140,7 +140,7 @@ sub check { my $count = 0; foreach my $pid (keys %{$options{dead_childs}}) { # Not me - next if ($httpserver->{pid} != $pid); + next if (!defined($httpserver->{pid}) || $httpserver->{pid} != $pid); $httpserver = {}; delete $options{dead_childs}->{$pid}; diff --git a/gorgone/gorgone/modules/core/register/hooks.pm b/gorgone/gorgone/modules/core/register/hooks.pm index 84642484613..5565b741321 100644 --- a/gorgone/gorgone/modules/core/register/hooks.pm +++ b/gorgone/gorgone/modules/core/register/hooks.pm @@ -132,7 +132,7 @@ sub check { my $count = 0; foreach my $pid (keys %{$options{dead_childs}}) { # Not me - next if ($register->{pid} != $pid); + next if (!defined($register->{pid}) || $register->{pid} != $pid); $register = {}; delete $options{dead_childs}->{$pid}; diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index e93eb26f2a4..8affbd86882 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -877,7 +877,7 @@ sub init_database { port => $options{port}, user => $options{user}, password => $options{password}, - force => 2, + force => 1, logger => $options{logger} ); $options{gorgone}->{db_gorgone}->set_inactive_destroy(); From b2a52def02e907523b347d29f407067f01986279 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Tue, 1 Dec 2020 15:03:52 +0100 Subject: [PATCH 556/948] fix(proxy): gorgone-proxy stucks when exits (#81) --- gorgone/gorgone/class/module.pm | 1 + gorgone/gorgone/modules/core/proxy/class.pm | 20 +++++++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/gorgone/gorgone/class/module.pm b/gorgone/gorgone/class/module.pm index 57b64faf94b..78e54c711b8 100644 --- a/gorgone/gorgone/class/module.pm +++ b/gorgone/gorgone/class/module.pm @@ -39,6 +39,7 @@ sub new { $self->{core_id} = $options{core_id}; $self->{logger} = $options{logger}; $self->{config} = $options{config}; + $self->{exit_timeout} = (defined($options{config}->{exit_timeout}) && $options{config}->{exit_timeout} =~ /(\d+)/) ? $1 : 30; $self->{config_core} = $options{config_core}->{gorgonecore}; $self->{config_db_centreon} = $options{config_db_centreon}; $self->{config_db_centstorage} = $options{config_db_centstorage}; diff --git a/gorgone/gorgone/modules/core/proxy/class.pm b/gorgone/gorgone/modules/core/proxy/class.pm index fbc7f9a168a..99633673108 100644 --- a/gorgone/gorgone/modules/core/proxy/class.pm +++ b/gorgone/gorgone/modules/core/proxy/class.pm @@ -65,6 +65,7 @@ sub handle_TERM { my $self = shift; $self->{logger}->writeLogInfo("[proxy] $$ Receiving order to stop..."); $self->{stop} = 1; + $self->{stop_time} = time(); } sub class_handle_TERM { @@ -79,6 +80,15 @@ sub class_handle_HUP { } } +sub exit_process { + my ($self, %options) = @_; + + $self->{logger}->writeLogInfo("[proxy] $$ has quit"); + $self->close_connections(); + zmq_close($self->{internal_socket}); + exit(0); +} + sub read_message { my (%options) = @_; @@ -408,7 +418,10 @@ sub event_internal { while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); - proxy(message => $message); + proxy(message => $message); + if ($connector->{stop} == 1 && (time() - $connector->{exit_timeout}) > $connector->{stop_time}) { + $connector->exit_process(); + } last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); } } @@ -464,10 +477,7 @@ sub run { next if (!defined($rev)); if ($rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("[proxy] $$ has quit"); - $self->close_connections(); - zmq_close($self->{internal_socket}); - exit(0); + $self->exit_process(); } } } From 3f55421137035685162d9bda4b93d09b87aa1cc8 Mon Sep 17 00:00:00 2001 From: garnier-quentin <garnier.quentin@gmail.com> Date: Wed, 2 Dec 2020 08:45:57 +0100 Subject: [PATCH 557/948] fix(doc): wrong node id in poller_pull_configuration.md --- gorgone/docs/poller_pull_configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/docs/poller_pull_configuration.md b/gorgone/docs/poller_pull_configuration.md index 8e9aeae19b8..43e9c709cfe 100644 --- a/gorgone/docs/poller_pull_configuration.md +++ b/gorgone/docs/poller_pull_configuration.md @@ -85,7 +85,7 @@ We create the file **/etc/centreon-gorgone/nodes-register-override.yml**: ```yaml nodes: - - id: 1024 + - id: 6 type: pull prevail: 1 ``` From f3fe79953f8a0859df2183bef79d93a3037fe146 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Fri, 4 Dec 2020 12:10:49 +0100 Subject: [PATCH 558/948] fix(core): DB transaction issue (#84) --- gorgone/gorgone/class/core.pm | 19 ++++++++- gorgone/gorgone/class/db.pm | 41 +++++++++++++++---- gorgone/gorgone/class/sqlquery.pm | 41 +++++++++++++++---- .../autodiscovery/services/discovery.pm | 17 ++++---- .../gorgone/modules/core/dbcleaner/class.pm | 32 +++++++++++++-- gorgone/gorgone/modules/core/proxy/hooks.pm | 20 ++++++--- gorgone/gorgone/standard/library.pm | 2 +- 7 files changed, 133 insertions(+), 39 deletions(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index fd8528b75c2..05f0cbef748 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -37,7 +37,7 @@ my ($gorgone); use base qw(gorgone::class::script); my $VERSION = '1.0'; -my %handlers = (TERM => {}, HUP => {}, CHLD => {}); +my %handlers = (TERM => {}, HUP => {}, CHLD => {}, DIE => {}); sub new { my $class = shift; @@ -214,6 +214,8 @@ sub set_signal_handlers { $handlers{HUP}->{$self} = sub { $self->handle_HUP() }; $SIG{CHLD} = \&class_handle_CHLD; $handlers{CHLD}->{$self} = sub { $self->handle_CHLD() }; + $SIG{__DIE__} = \&class_handle_DIE; + $handlers{DIE}->{$self} = sub { $self->handle_DIE($_[0]) }; } sub class_handle_TERM { @@ -234,6 +236,14 @@ sub class_handle_CHLD { } } +sub class_handle_DIE { + my ($msg) = @_; + + foreach (keys %{$handlers{DIE}}) { + &{$handlers{DIE}->{$_}}($msg); + } +} + sub handle_TERM { my ($self) = @_; $self->{logger}->writeLogInfo("[core] $$ Receiving order to stop..."); @@ -263,6 +273,13 @@ sub handle_CHLD { $SIG{CHLD} = \&class_handle_CHLD; } +sub handle_DIE { + my $self = shift; + my $msg = shift; + + $self->{logger}->writeLogError("[core] Receiving DIE: $msg"); +} + sub unload_module { my ($self, %options) = @_; diff --git a/gorgone/gorgone/class/db.pm b/gorgone/gorgone/class/db.pm index d5ef7d10d7f..f35acdf282f 100644 --- a/gorgone/gorgone/class/db.pm +++ b/gorgone/gorgone/class/db.pm @@ -142,24 +142,46 @@ sub set_inactive_destroy { } sub transaction_mode { - my ($self, $status) = @_; + my ($self, $mode) = @_; - if ($status) { - $self->{instance}->begin_work; + my $status; + if (!defined($self->{instance})) { + $status = $self->connect(); + return -1 if ($status == -1); + } + + if ($mode) { + $status = $self->{instance}->begin_work(); + if (!$status) { + $self->error($self->{instance}->errstr, 'begin work'); + return -1; + } $self->{transaction_begin} = 1; - $self->{instance}->{RaiseError} = 1; } else { $self->{transaction_begin} = 0; $self->{instance}->{AutoCommit} = 1; - $self->{instance}->{RaiseError} = 0; } + + return 0; } sub commit { my ($self) = @_; - $self->{instance}->commit; + if (!defined($self->{instance})) { + $self->{transaction_begin} = 0; + return -1; + } + + my $status = $self->{instance}->commit(); $self->{transaction_begin} = 0; + + if (!$status) { + $self->error($self->{instance}->errstr, 'commit'); + return -1; + } + + return 0; } sub rollback { @@ -238,6 +260,7 @@ sub connect() { sleep(1); $count++; } + return $status; } @@ -254,7 +277,7 @@ sub disconnect { sub do { my ($self, $query) = @_; - if (!defined $self->{instance}) { + if (!defined($self->{instance})) { if ($self->connect() == -1) { $self->{logger}->writeLogError("Cannot connect to database"); return -1; @@ -298,7 +321,7 @@ sub query { } $self->{args} = []; } - if ($status == -1 && $self->{force} != 1) { + if ($status == -1) { $self->{args} = []; last; } @@ -326,8 +349,10 @@ sub query { sleep(1); next; } + last; } + return ($status, $statement_handle); } diff --git a/gorgone/gorgone/class/sqlquery.pm b/gorgone/gorgone/class/sqlquery.pm index 9e6c571c1a9..045cb901928 100644 --- a/gorgone/gorgone/class/sqlquery.pm +++ b/gorgone/gorgone/class/sqlquery.pm @@ -85,8 +85,12 @@ sub execute { sub transaction_query_multi { my ($self, %options) = @_; - $self->transaction_mode(1); - my ($status, $sth) = $self->{db_centreon}->query($options{request}, prepare_only => 1); + my ($status, $sth); + + $status = $self->transaction_mode(1); + return -1 if ($status == -1); + + ($status, $sth) = $self->{db_centreon}->query($options{request}, prepare_only => 1); if ($status == -1) { $self->rollback(); return -1; @@ -100,21 +104,28 @@ sub transaction_query_multi { } } while ($sth->more_results); - $self->commit(); + $status = $self->commit(); + return -1 if ($status == -1); + return 0; } sub transaction_query { my ($self, %options) = @_; + my $status; - $self->transaction_mode(1); - my ($status) = $self->do(request => $options{request}); + $status = $self->transaction_mode(1); + return -1 if ($status == -1); + + ($status) = $self->do(request => $options{request}); if ($status == -1) { $self->rollback(); return -1; } - $self->commit(); + $status = $self->commit(); + return -1 if ($status == -1); + return 0; } @@ -124,10 +135,22 @@ sub quote { return $self->{db_centreon}->quote($options{value}); } -sub transaction_mode { shift->{db_centreon}->transaction_mode($_[0]); }; +sub transaction_mode { + my ($self) = @_; + + return $self->{db_centreon}->transaction_mode($_[1]); +}; -sub commit { shift->{db_centreon}->commit(); } +sub commit { + my ($self, %options) = @_; + + return $self->{db_centreon}->commit(); +} -sub rollback { shift->{db_centreon}->rollback(); } +sub rollback { + my ($self, %options) = @_; + + return $self->{db_centreon}->rollback(); +} 1; diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm index 69ebb6af84c..36d44410a63 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm @@ -61,11 +61,9 @@ sub new { sub database_init_transaction { my ($self, %options) = @_; - - eval { - $self->{class_object_centreon}->{db_centreon}->transaction_mode(1); - }; - if ($@) { + + my $status = $self->{class_object_centreon}->{db_centreon}->transaction_mode(1); + if ($status == -1) { $self->{logger}->writeLogError("$@"); return -1; } @@ -75,14 +73,13 @@ sub database_init_transaction { sub database_commit_transaction { my ($self, %options) = @_; - eval { - $self->{class_object_centreon}->commit(); - $self->{class_object_centreon}->transaction_mode(0); - }; - if ($@) { + my $status = $self->{class_object_centreon}->commit(); + if ($status == -1) { $self->{logger}->writeLogError("$@"); return -1; } + + $self->{class_object_centreon}->transaction_mode(0); return 0; } diff --git a/gorgone/gorgone/modules/core/dbcleaner/class.pm b/gorgone/gorgone/modules/core/dbcleaner/class.pm index 8cfb4d9a578..46a9f8924fa 100644 --- a/gorgone/gorgone/modules/core/dbcleaner/class.pm +++ b/gorgone/gorgone/modules/core/dbcleaner/class.pm @@ -31,7 +31,7 @@ use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); use JSON::XS; -my %handlers = (TERM => {}, HUP => {}); +my %handlers = (TERM => {}, HUP => {}, DIE => {}); my ($connector); sub new { @@ -52,6 +52,8 @@ sub set_signal_handlers { $handlers{TERM}->{$self} = sub { $self->handle_TERM() }; $SIG{HUP} = \&class_handle_HUP; $handlers{HUP}->{$self} = sub { $self->handle_HUP() }; + $SIG{__DIE__} = \&class_handle_DIE; + $handlers{DIE}->{$self} = sub { $self->handle_DIE($_[0]) }; } sub handle_HUP { @@ -65,6 +67,22 @@ sub handle_TERM { $self->{stop} = 1; } +sub handle_DIE { + my $self = shift; + my $msg = shift; + + $self->{logger}->writeLogError("[dbcleaner] Receiving DIE: $msg"); + $self->exit_process(); +} + +sub class_handle_DIE { + my ($msg) = @_; + + foreach (keys %{$handlers{DIE}}) { + &{$handlers{DIE}->{$_}}($msg); + } +} + sub class_handle_TERM { foreach (keys %{$handlers{TERM}}) { &{$handlers{TERM}->{$_}}(); @@ -77,6 +95,14 @@ sub class_handle_HUP { } } +sub exit_process { + my ($self, %options) = @_; + + $self->{logger}->writeLogInfo("[dbcleaner] $$ has quit"); + zmq_close($self->{internal_socket}); + exit(0); +} + sub action_dbclean { my ($self, %options) = @_; @@ -180,9 +206,7 @@ sub run { # we try to do all we can my $rev = zmq_poll($self->{poll}, 5000); if (defined($rev) && $rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("[dbcleaner] $$ has quit"); - zmq_close($connector->{internal_socket}); - exit(0); + $self->exit_process(); } $self->action_dbclean(cycle => 1); diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index 49f6bd40988..b183794e1af 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -549,9 +549,11 @@ sub setlogs { $synctime_nodes->{ $options{data}->{data}->{id} }->{in_progress} = 0; my $ctime_recent = 0; - # Transaction. We don't use last_id (problem if it's clean the sqlite table - $options{dbh}->transaction_mode(1); - my $status = 0; + # Transaction. We don't use last_id (problem if it's clean the sqlite table). + my $status; + $status = $options{dbh}->transaction_mode(1); + return -1 if ($status == -1); + foreach (@{$options{data}->{data}->{result}}) { # wrong timestamp inserted. we skip it if ($_->{ctime} !~ /[0-9\.]/) { @@ -571,13 +573,17 @@ sub setlogs { $ctime_recent = $_->{ctime} if ($ctime_recent < $_->{ctime}); } if ($status == 0 && update_sync_time(dbh => $options{dbh}, id => $options{data}->{data}->{id}, ctime => $ctime_recent) == 0) { - $options{dbh}->commit(); + $status = $options{dbh}->commit(); + return -1 if ($status == -1); + $options{dbh}->transaction_mode(0); + $synctime_nodes->{$options{data}->{data}->{id}}->{ctime} = $ctime_recent if ($ctime_recent != 0); } else { $options{dbh}->rollback(); + $options{dbh}->transaction_mode(0); + return -1; } - $options{dbh}->transaction_mode(0); - + # We try to send it to parents foreach (keys %{$parent_ping}) { gorgone::class::core::send_message_parent( @@ -589,6 +595,8 @@ sub setlogs { token => undef, ); } + + return 0; } sub ping_send { diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index 8affbd86882..e93eb26f2a4 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -877,7 +877,7 @@ sub init_database { port => $options{port}, user => $options{user}, password => $options{password}, - force => 1, + force => 2, logger => $options{logger} ); $options{gorgone}->{db_gorgone}->set_inactive_destroy(); From cae051fc00a338041b8e9af94253581a0911a438 Mon Sep 17 00:00:00 2001 From: jeremyjaouen <61694165+jeremyjaouen@users.noreply.github.com> Date: Wed, 9 Dec 2020 10:12:41 +0100 Subject: [PATCH 559/948] enh(anomalydetecton): change AD sync threshold value (#85) --- gorgone/gorgone/modules/centreon/anomalydetection/class.pm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm index 488474f0dff..0d9590fc335 100644 --- a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm +++ b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm @@ -43,6 +43,7 @@ sub new { bless $connector, $class; $connector->{resync_time} = (defined($options{config}->{resync_time}) && $options{config}->{resync_time} =~ /(\d+)/) ? $1 : 600; + $connector->{thresholds_sync_time} = (defined($options{config}->{thresholds_sync_time}) && $options{config}->{thresholds_sync_time} =~ /(\d+)/) ? $1 : 28800; $connector->{last_resync_time} = -1; $connector->{saas_token} = undef; $connector->{saas_url} = undef; @@ -481,8 +482,8 @@ sub saas_get_predicts { next if ($self->{centreon_metrics}->{$_}->{saas_to_delete} == 1); #next if (!defined($self->{centreon_metrics}->{$_}->{thresholds_file}) || # $self->{centreon_metrics}->{$_}->{thresholds_file} eq ''); - next if (!defined($self->{centreon_metrics}->{$_}->{saas_update_date}) || - $self->{centreon_metrics}->{$_}->{saas_update_date} > time() - 86400); + next if (!defined($self->{centreon_metrics}->{$_}->{saas_update_date}) || + $self->{centreon_metrics}->{$_}->{saas_update_date} > time() - $self->{thresholds_sync_time}); ($status, my $result) = $self->saas_api_request( endpoint => '/machinelearning/' . $self->{centreon_metrics}->{$_}->{saas_model_id} . '/predicts', From e6126281262b8e1f65bce72dcb89b679b9a71ad4 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Wed, 9 Dec 2020 12:08:35 +0100 Subject: [PATCH 560/948] fix(servicediscovery): contact group is not working (#86) --- .../modules/centreon/autodiscovery/services/resources.pm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm b/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm index 55d7a2a7634..5eaf8544ecf 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm @@ -246,10 +246,10 @@ sub get_rules { } foreach my $row (@$datas2) { # Already add it - next if (defined($rules->{ $_->[0] }->{contact}->{ $row->[1] })); - if ((my $contact = get_contact(class_object_centreon => $options{class_object_centreon}, contact_id => $row->[1]))) { + next if (defined($rules->{ $_->[0] }->{contact}->{ $row->[0] })); + if ((my $contact = get_contact(class_object_centreon => $options{class_object_centreon}, contact_id => $row->[0]))) { $rules->{ $_->[0] }->{contact} = {} if (!defined($rules->{ $_->[0] }->{contact})); - $rules->{ $_->[0] }->{contact}->{$contact->{contact_id}} = { contact_email => $contact->{contact_email} }; + $rules->{ $_->[0] }->{contact}->{ $contact->{contact_id} } = { contact_email => $contact->{contact_email} }; } } } From e4351c77a5502fea31b84a6eef6ed659ce2447ab Mon Sep 17 00:00:00 2001 From: garnier-quentin <garnier.quentin@gmail.com> Date: Wed, 9 Dec 2020 16:52:42 +0100 Subject: [PATCH 561/948] enh(proxy): better management of utf8 for ssh nodes --- .../modules/centreon/autodiscovery/services/discovery.pm | 1 - gorgone/gorgone/modules/centreon/legacycmd/class.pm | 4 ++-- gorgone/gorgone/modules/core/proxy/sshclient.pm | 3 +++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm index 36d44410a63..b824cfcb4e3 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm @@ -412,7 +412,6 @@ sub create_service { return $self->database_error_rollback(message => "$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> cannot create service"); } my $service_id = $self->{class_object_centreon}->{db_centreon}->last_insert_id(); - $self->{logger}->writeLogError("$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> PLOP last insert id = $service_id"); $query = 'INSERT INTO host_service_relation (host_host_id, service_service_id) VALUES (' . $options{host_id} . ', ' . $service_id . ')'; ($status) = $self->{class_object_centreon}->custom_execute(request => $query); diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index 563a89af2fd..0de0246f179 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -504,9 +504,9 @@ sub action_addimporttaskwithparent { sub move_cmd_file { my ($self, %options) = @_; - my $operator = '+<'; + my $operator = '+<:encoding(UTF-8)'; if ($self->{config}->{dirty_mode} == 1) { - $operator = '<'; + $operator = '<:encoding(UTF-8)'; } my $handle; if (-e $options{dst}) { diff --git a/gorgone/gorgone/modules/core/proxy/sshclient.pm b/gorgone/gorgone/modules/core/proxy/sshclient.pm index 748536f5f28..592a7d179b8 100644 --- a/gorgone/gorgone/modules/core/proxy/sshclient.pm +++ b/gorgone/gorgone/modules/core/proxy/sshclient.pm @@ -30,6 +30,7 @@ use gorgone::standard::misc; use File::Basename; use Time::HiRes; use gorgone::standard::constants qw(:all); +use Encode; sub new { my ($class, %options) = @_; @@ -295,6 +296,7 @@ sub action_enginecommand { if ($options{target_direct} == 0) { foreach my $command (@{$options{data}->{content}->{commands}}) { chomp $command; + $command = Encode::decode('UTF-8', $command); my $msg = "[sshclient] Handling command 'EXTERNALCMD'"; $msg .= ", Target: '" . $options{target} . "'" if (defined($options{target})); $msg .= ", Parameters: '" . $command . "'" if (defined($command)); @@ -343,6 +345,7 @@ sub action_enginecommand { }; foreach my $command (@{$options{data}->{content}->{commands}}) { + $command = Encode::decode('UTF-8', $command); $self->{logger}->writeLogInfo("[sshclient] Processing external command '" . $command . "'"); if ($self->{sftp}->write(handle_file => $file, data => $command . "\n") != Libssh::Session::SSH_OK) { $self->{logger}->writeLogError("[sshclient] Command file '$command_file' must be writeable"); From c39637f667f1710a67e4a71e1e2b297a622163be Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Thu, 10 Dec 2020 16:26:43 +0100 Subject: [PATCH 562/948] enh(zmq): increment ZMQ_LINGER period for some modules (#87) --- gorgone/gorgone/modules/centreon/engine/class.pm | 2 +- gorgone/gorgone/modules/core/action/class.pm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/engine/class.pm b/gorgone/gorgone/modules/centreon/engine/class.pm index 463abbaae28..b0720ab5e10 100644 --- a/gorgone/gorgone/modules/centreon/engine/class.pm +++ b/gorgone/gorgone/modules/centreon/engine/class.pm @@ -202,7 +202,7 @@ sub action_run { zmq_type => 'ZMQ_DEALER', name => 'gorgoneengine-'. $$, logger => $self->{logger}, - zmq_linger => 5000, + zmq_linger => 60000, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index 3361d56857b..f2729592d16 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -348,7 +348,7 @@ sub action_run { zmq_type => 'ZMQ_DEALER', name => 'gorgoneaction-'. $$, logger => $self->{logger}, - zmq_linger => 5000, + zmq_linger => 60000, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} ); From a459725cbbf0e902b28fc829c3d41a5337c280f6 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Fri, 11 Dec 2020 16:09:32 +0100 Subject: [PATCH 563/948] fix(proxy): setlogs failed with node without id (#88) --- gorgone/gorgone/modules/core/proxy/class.pm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/modules/core/proxy/class.pm b/gorgone/gorgone/modules/core/proxy/class.pm index 99633673108..17e0e9c1b27 100644 --- a/gorgone/gorgone/modules/core/proxy/class.pm +++ b/gorgone/gorgone/modules/core/proxy/class.pm @@ -130,11 +130,12 @@ sub read_message { return undef; } + # we set the id (distant node can not have id in configuration) + $data->{data}->{id} = $client_identity; if (defined($data->{data}->{action}) && $data->{data}->{action} eq 'getlog') { $connector->send_internal_action( action => 'SETLOGS', - data => $2, - data_noencode => 1, + data => $data, token => $1, target => '' ); From d5e3bffc8ccb70032190b385ad6500b80b083f0c Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Thu, 17 Dec 2020 15:05:35 +0100 Subject: [PATCH 564/948] enh(core): add/set ZMQ_TCP_KEEPALIVE option (#90) --- gorgone/gorgone/class/core.pm | 4 ++++ gorgone/gorgone/standard/library.pm | 2 ++ 2 files changed, 6 insertions(+) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 05f0cbef748..5005ec696c9 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -149,6 +149,9 @@ sub init { ); $self->init_server_keys(); + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_zmq_tcp_keepalive} = + defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_zmq_tcp_keepalive}) && $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_zmq_tcp_keepalive} =~ /^(0|1)$/ ? $1 : 1; + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_type} = 'ipc' if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_type}) || $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_type} eq ''); $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_path} = '/tmp/gorgone/routing.ipc' @@ -785,6 +788,7 @@ sub run { path => $gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_path}, zmq_type => 'ZMQ_ROUTER', zmq_router_handover => $gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_zmq_router_handover}, + zmq_tcp_keepalive => $gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_zmq_tcp_keepalive}, name => 'router-external', logger => $gorgone->{logger} ); diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index e93eb26f2a4..b7974126899 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -41,6 +41,7 @@ our $listener; my %zmq_type = ('ZMQ_ROUTER' => ZMQ_ROUTER, 'ZMQ_DEALER' => ZMQ_DEALER); my $ZMQ_CONNECT_TIMEOUT = 79; my $ZMQ_ROUTER_HANDOVER = 56; +my $ZMQ_TCP_KEEPALIVE = 34; sub read_config { my (%options) = @_; @@ -756,6 +757,7 @@ sub create_com { zmq_setsockopt($socket, ZMQ_LINGER, 0); # we discard ZMQ::LibZMQ4::zmq_setsockopt_int($socket, $ZMQ_ROUTER_HANDOVER, defined($options{zmq_router_handover}) ? $options{zmq_router_handover} : 1); if ($options{type} eq 'tcp') { + ZMQ::LibZMQ4::zmq_setsockopt_int($socket, $ZMQ_TCP_KEEPALIVE, defined($options{zmq_tcp_keepalive}) ? $options{zmq_tcp_keepalive} : -1); zmq_bind($socket, 'tcp://' . $options{path}); } elsif ($options{type} eq 'ipc') { if (zmq_bind($socket, 'ipc://' . $options{path}) == -1) { From 668a7c80d0f20d2b12fc8efa4064b36f2bdf5331 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Fri, 18 Dec 2020 16:08:32 +0100 Subject: [PATCH 565/948] fix(sshclient): wrong code for OK command (#91) --- gorgone/gorgone/modules/core/proxy/sshclient.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/gorgone/modules/core/proxy/sshclient.pm b/gorgone/gorgone/modules/core/proxy/sshclient.pm index 592a7d179b8..b617a260f3d 100644 --- a/gorgone/gorgone/modules/core/proxy/sshclient.pm +++ b/gorgone/gorgone/modules/core/proxy/sshclient.pm @@ -239,7 +239,7 @@ sub action_command { if ($ret->{exit} == Libssh::Session::SSH_OK) { $data->{data}->{message} = "command has finished successfully"; - $data->{code} = GORGONE_ACTION_FINISH_OK; + $data->{code} = GORGONE_MODULE_ACTION_COMMAND_RESULT; } elsif ($ret->{exit} == Libssh::Session::SSH_AGAIN) { # AGAIN means timeout $code = -1; $data->{data}->{message} = "command has timed out"; From 9bcaa8156abeddc983802470b615c8576194c454 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Mon, 28 Dec 2020 11:20:21 +0100 Subject: [PATCH 566/948] enh(core): Harden ZMQ communication (#92) --- gorgone/gorgone/class/core.pm | 13 +++++++++++-- gorgone/gorgone/class/module.pm | 19 +++++++++++++++++++ .../centreon/anomalydetection/hooks.pm | 1 + .../gorgone/modules/centreon/engine/hooks.pm | 1 + .../gorgone/modules/centreon/judge/hooks.pm | 1 + .../modules/centreon/legacycmd/hooks.pm | 1 + .../gorgone/modules/centreon/nodes/hooks.pm | 1 + .../modules/centreon/statistics/hooks.pm | 1 + gorgone/gorgone/modules/core/action/hooks.pm | 1 + gorgone/gorgone/modules/core/cron/hooks.pm | 1 + .../gorgone/modules/core/dbcleaner/hooks.pm | 1 + .../gorgone/modules/core/httpserver/hooks.pm | 1 + .../gorgone/modules/core/pipeline/hooks.pm | 1 + .../gorgone/modules/core/register/hooks.pm | 1 + gorgone/gorgone/standard/library.pm | 10 +++++++++- 15 files changed, 51 insertions(+), 3 deletions(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 5005ec696c9..3f92bae25e9 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -542,7 +542,12 @@ sub message_run { sub router_internal_event { while (1) { - my ($identity, $message) = gorgone::standard::library::zmq_read_message(socket => $gorgone->{internal_socket}); + my ($identity, $message) = gorgone::standard::library::zmq_read_message( + socket => $gorgone->{internal_socket}, + logger => $gorgone->{logger} + ); + last if (!defined($identity)); + my ($token, $code, $response, $response_type) = $gorgone->message_run( message => $message, identity => $identity, @@ -563,7 +568,11 @@ sub router_internal_event { sub handshake { my ($self, %options) = @_; - my ($identity, $message) = gorgone::standard::library::zmq_read_message(socket => $self->{external_socket}); + my ($identity, $message) = gorgone::standard::library::zmq_read_message( + socket => $self->{external_socket}, + logger => $self->{logger} + ); + return undef if (!defined($identity)); # Test if it asks for the pubkey if ($message =~ /^\s*\[GETPUBKEY\]/) { diff --git a/gorgone/gorgone/class/module.pm b/gorgone/gorgone/class/module.pm index 78e54c711b8..74b5403d978 100644 --- a/gorgone/gorgone/class/module.pm +++ b/gorgone/gorgone/class/module.pm @@ -29,6 +29,8 @@ use gorgone::standard::misc; use gorgone::class::tpapi; use JSON::XS; +my %handlers = (DIE => {}); + sub new { my ($class, %options) = @_; my $self = {}; @@ -48,9 +50,26 @@ sub new { $self->{tpapi} = gorgone::class::tpapi->new(); $self->{tpapi}->load_configuration(configuration => $options{config_core}->{tpapi}); + $SIG{__DIE__} = \&class_handle_DIE; + $handlers{DIE}->{$self} = sub { $self->handle_DIE($_[0]) }; + return $self; } +sub class_handle_DIE { + my ($msg) = @_; + + foreach (keys %{$handlers{DIE}}) { + &{$handlers{DIE}->{$_}}($msg); + } +} + +sub handle_DIE { + my ($self, $msg) = @_; + + $self->{logger}->writeLogError("[$self->{module_id}] Receiving DIE: $msg"); +} + sub generate_token { my ($self, %options) = @_; diff --git a/gorgone/gorgone/modules/centreon/anomalydetection/hooks.pm b/gorgone/gorgone/modules/centreon/anomalydetection/hooks.pm index 4a59f906a80..362eb062c20 100644 --- a/gorgone/gorgone/modules/centreon/anomalydetection/hooks.pm +++ b/gorgone/gorgone/modules/centreon/anomalydetection/hooks.pm @@ -160,6 +160,7 @@ sub create_child { $0 = 'gorgone-anomalydetection'; my $module = gorgone::modules::centreon::anomalydetection::class->new( logger => $options{logger}, + module_id => NAME, config_core => $config_core, config => $config, config_db_centreon => $config_db_centreon, diff --git a/gorgone/gorgone/modules/centreon/engine/hooks.pm b/gorgone/gorgone/modules/centreon/engine/hooks.pm index 29942acfe03..e7ff707c11a 100644 --- a/gorgone/gorgone/modules/centreon/engine/hooks.pm +++ b/gorgone/gorgone/modules/centreon/engine/hooks.pm @@ -158,6 +158,7 @@ sub create_child { $0 = 'gorgone-engine'; my $module = gorgone::modules::centreon::engine::class->new( logger => $options{logger}, + module_id => NAME, config_core => $config_core, config => $config, ); diff --git a/gorgone/gorgone/modules/centreon/judge/hooks.pm b/gorgone/gorgone/modules/centreon/judge/hooks.pm index cc7b6659a55..9f675eef94f 100644 --- a/gorgone/gorgone/modules/centreon/judge/hooks.pm +++ b/gorgone/gorgone/modules/centreon/judge/hooks.pm @@ -163,6 +163,7 @@ sub create_child { $0 = 'gorgone-judge'; my $module = gorgone::modules::centreon::judge::class->new( logger => $options{logger}, + module_id => NAME, config_core => $config_core, config => $config, config_db_centreon => $config_db_centreon, diff --git a/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm b/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm index e94ceb5deac..091c9f32a2a 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm @@ -165,6 +165,7 @@ sub create_child { $0 = 'gorgone-legacycmd'; my $module = gorgone::modules::centreon::legacycmd::class->new( logger => $options{logger}, + module_id => NAME, config_core => $config_core, config => $config, config_db_centreon => $config_db_centreon, diff --git a/gorgone/gorgone/modules/centreon/nodes/hooks.pm b/gorgone/gorgone/modules/centreon/nodes/hooks.pm index 8687cd7882a..b58040a2187 100644 --- a/gorgone/gorgone/modules/centreon/nodes/hooks.pm +++ b/gorgone/gorgone/modules/centreon/nodes/hooks.pm @@ -160,6 +160,7 @@ sub create_child { $0 = 'gorgone-nodes'; my $module = gorgone::modules::centreon::nodes::class->new( logger => $options{logger}, + module_id => NAME, config_core => $config_core, config => $config, config_db_centreon => $config_db_centreon, diff --git a/gorgone/gorgone/modules/centreon/statistics/hooks.pm b/gorgone/gorgone/modules/centreon/statistics/hooks.pm index 026f0ce10f1..6ea0a58602a 100644 --- a/gorgone/gorgone/modules/centreon/statistics/hooks.pm +++ b/gorgone/gorgone/modules/centreon/statistics/hooks.pm @@ -174,6 +174,7 @@ sub create_child { $0 = 'gorgone-statistics'; my $module = gorgone::modules::centreon::statistics::class->new( logger => $options{logger}, + module_id => NAME, config_core => $config_core, config => $config, config_db_centreon => $config_db_centreon, diff --git a/gorgone/gorgone/modules/core/action/hooks.pm b/gorgone/gorgone/modules/core/action/hooks.pm index f80464e8ea9..fbd147eadc8 100644 --- a/gorgone/gorgone/modules/core/action/hooks.pm +++ b/gorgone/gorgone/modules/core/action/hooks.pm @@ -158,6 +158,7 @@ sub create_child { $0 = 'gorgone-action'; my $module = gorgone::modules::core::action::class->new( logger => $options{logger}, + module_id => NAME, config_core => $config_core, config => $config, ); diff --git a/gorgone/gorgone/modules/core/cron/hooks.pm b/gorgone/gorgone/modules/core/cron/hooks.pm index 21464107d1c..c53c988e1cd 100644 --- a/gorgone/gorgone/modules/core/cron/hooks.pm +++ b/gorgone/gorgone/modules/core/cron/hooks.pm @@ -160,6 +160,7 @@ sub create_child { $0 = 'gorgone-cron'; my $module = gorgone::modules::core::cron::class->new( logger => $options{logger}, + module_id => NAME, config_core => $config_core, config => $config, ); diff --git a/gorgone/gorgone/modules/core/dbcleaner/hooks.pm b/gorgone/gorgone/modules/core/dbcleaner/hooks.pm index 2e3b95c7455..712e6e74acf 100644 --- a/gorgone/gorgone/modules/core/dbcleaner/hooks.pm +++ b/gorgone/gorgone/modules/core/dbcleaner/hooks.pm @@ -167,6 +167,7 @@ sub create_child { $0 = 'gorgone-dbcleaner'; my $module = gorgone::modules::core::dbcleaner::class->new( logger => $options{logger}, + module_id => NAME, config_core => $config_core, config => $config, ); diff --git a/gorgone/gorgone/modules/core/httpserver/hooks.pm b/gorgone/gorgone/modules/core/httpserver/hooks.pm index 8db3d94927b..5adc6a509d3 100644 --- a/gorgone/gorgone/modules/core/httpserver/hooks.pm +++ b/gorgone/gorgone/modules/core/httpserver/hooks.pm @@ -168,6 +168,7 @@ sub create_child { $0 = 'gorgone-httpserver'; my $module = gorgone::modules::core::httpserver::class->new( logger => $options{logger}, + module_id => NAME, config_core => $config_core, config => $config, api_endpoints => $options{api_endpoints} diff --git a/gorgone/gorgone/modules/core/pipeline/hooks.pm b/gorgone/gorgone/modules/core/pipeline/hooks.pm index e6b2e2b5de6..b0810c1f07a 100644 --- a/gorgone/gorgone/modules/core/pipeline/hooks.pm +++ b/gorgone/gorgone/modules/core/pipeline/hooks.pm @@ -168,6 +168,7 @@ sub create_child { $0 = 'gorgone-pipeline'; my $module = gorgone::modules::core::pipeline::class->new( logger => $options{logger}, + module_id => NAME, config_core => $config_core, config => $config, ); diff --git a/gorgone/gorgone/modules/core/register/hooks.pm b/gorgone/gorgone/modules/core/register/hooks.pm index 5565b741321..038480eeb5e 100644 --- a/gorgone/gorgone/modules/core/register/hooks.pm +++ b/gorgone/gorgone/modules/core/register/hooks.pm @@ -162,6 +162,7 @@ sub create_child { $0 = 'gorgone-register'; my $module = gorgone::modules::core::register::class->new( logger => $options{logger}, + module_id => NAME, config_core => $config_core, config => $config, ); diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index b7974126899..dff2997fc23 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -293,7 +293,7 @@ sub is_client_can_connect { my $plaintext; if ($options{message} !~ /\[(.+)\]\s+\[(.+)\]\s+\[(.+)\]$/ms) { - $options{logger}->writeLogError("[core] Decoding issue. Protocol not good"); + $options{logger}->writeLogError("[core] Decoding issue. Protocol not good: $options{message}"); return -1; } @@ -842,8 +842,16 @@ sub zmq_read_message { # Process all parts of the message my $message = zmq_recvmsg($options{socket}); + if (!defined($message)) { + $options{logger}->writeLogError("[core] zmq_recvmsg error: $!"); + return undef; + } my $identity = zmq_msg_data($message); $message = zmq_recvmsg($options{socket}); + if (!defined($message)) { + $options{logger}->writeLogError("[core] zmq_recvmsg error: $!"); + return undef; + } my $data = zmq_msg_data($message); return (unpack('H*', $identity), $data); From 4028e015d180bc4ea004daa4e5a39613475e22e3 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Mon, 4 Jan 2021 15:35:37 +0100 Subject: [PATCH 567/948] enh(core): more harden ZMQ communication (#95) --- gorgone/gorgone/modules/centreon/autodiscovery/class.pm | 2 +- gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm | 2 +- gorgone/gorgone/modules/centreon/engine/class.pm | 4 ++-- gorgone/gorgone/modules/centreon/engine/hooks.pm | 2 +- gorgone/gorgone/modules/centreon/judge/class.pm | 2 +- gorgone/gorgone/modules/centreon/judge/hooks.pm | 2 +- gorgone/gorgone/modules/centreon/legacycmd/class.pm | 2 +- gorgone/gorgone/modules/centreon/legacycmd/hooks.pm | 2 +- gorgone/gorgone/modules/centreon/nodes/class.pm | 2 +- gorgone/gorgone/modules/centreon/nodes/hooks.pm | 2 +- gorgone/gorgone/modules/centreon/statistics/class.pm | 2 +- gorgone/gorgone/modules/centreon/statistics/hooks.pm | 2 +- gorgone/gorgone/modules/core/action/class.pm | 4 ++-- gorgone/gorgone/modules/core/action/hooks.pm | 2 +- gorgone/gorgone/modules/core/cron/class.pm | 2 +- gorgone/gorgone/modules/core/cron/hooks.pm | 2 +- gorgone/gorgone/modules/core/dbcleaner/class.pm | 2 +- gorgone/gorgone/modules/core/dbcleaner/hooks.pm | 2 +- gorgone/gorgone/modules/core/httpserver/class.pm | 2 +- gorgone/gorgone/modules/core/httpserver/hooks.pm | 2 +- gorgone/gorgone/modules/core/pipeline/class.pm | 2 +- gorgone/gorgone/modules/core/pipeline/hooks.pm | 2 +- gorgone/gorgone/modules/core/proxy/class.pm | 6 +++--- gorgone/gorgone/modules/core/proxy/hooks.pm | 4 ++-- gorgone/gorgone/modules/core/pull/hooks.pm | 4 ++-- gorgone/gorgone/modules/core/register/class.pm | 2 +- gorgone/gorgone/modules/core/register/hooks.pm | 2 +- gorgone/gorgone/modules/plugins/newtest/class.pm | 3 ++- gorgone/gorgone/modules/plugins/newtest/hooks.pm | 4 ++-- gorgone/gorgone/modules/plugins/scom/class.pm | 2 +- gorgone/gorgone/modules/plugins/scom/hooks.pm | 4 ++-- gorgone/gorgone/standard/library.pm | 5 +++++ 32 files changed, 45 insertions(+), 39 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm index 473967f8812..b6fd9afd5ef 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm @@ -1118,7 +1118,7 @@ sub run { # Connect internal $connector->{internal_socket} = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', - name => 'gorgoneautodiscovery', + name => 'gorgone-autodiscovery', logger => $self->{logger}, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm b/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm index 4d7df81b34e..2ecce9eb9ba 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm @@ -99,7 +99,7 @@ sub routing { gorgone::standard::library::zmq_send_message( socket => $options{socket}, - identity => 'gorgoneautodiscovery', + identity => 'gorgone-autodiscovery', action => $options{action}, data => $options{data}, token => $options{token}, diff --git a/gorgone/gorgone/modules/centreon/engine/class.pm b/gorgone/gorgone/modules/centreon/engine/class.pm index b0720ab5e10..a7e73d49ec1 100644 --- a/gorgone/gorgone/modules/centreon/engine/class.pm +++ b/gorgone/gorgone/modules/centreon/engine/class.pm @@ -200,7 +200,7 @@ sub action_run { my $socket_log = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', - name => 'gorgoneengine-'. $$, + name => 'gorgone-engine-'. $$, logger => $self->{logger}, zmq_linger => 60000, type => $self->{config_core}->{internal_com_type}, @@ -275,7 +275,7 @@ sub run { # Connect internal $connector->{internal_socket} = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', - name => 'gorgoneengine', + name => 'gorgone-engine', logger => $self->{logger}, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} diff --git a/gorgone/gorgone/modules/centreon/engine/hooks.pm b/gorgone/gorgone/modules/centreon/engine/hooks.pm index e7ff707c11a..cd49d379833 100644 --- a/gorgone/gorgone/modules/centreon/engine/hooks.pm +++ b/gorgone/gorgone/modules/centreon/engine/hooks.pm @@ -91,7 +91,7 @@ sub routing { gorgone::standard::library::zmq_send_message( socket => $options{socket}, - identity => 'gorgoneengine', + identity => 'gorgone-engine', action => $options{action}, data => $options{data}, token => $options{token}, diff --git a/gorgone/gorgone/modules/centreon/judge/class.pm b/gorgone/gorgone/modules/centreon/judge/class.pm index 519f27be327..0a1aa1a74ad 100644 --- a/gorgone/gorgone/modules/centreon/judge/class.pm +++ b/gorgone/gorgone/modules/centreon/judge/class.pm @@ -538,7 +538,7 @@ sub run { $connector->{internal_socket} = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', - name => 'gorgonejudge', + name => 'gorgone-judge', logger => $self->{logger}, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} diff --git a/gorgone/gorgone/modules/centreon/judge/hooks.pm b/gorgone/gorgone/modules/centreon/judge/hooks.pm index 9f675eef94f..0dc3c7d480c 100644 --- a/gorgone/gorgone/modules/centreon/judge/hooks.pm +++ b/gorgone/gorgone/modules/centreon/judge/hooks.pm @@ -96,7 +96,7 @@ sub routing { gorgone::standard::library::zmq_send_message( socket => $options{socket}, - identity => 'gorgonejudge', + identity => 'gorgone-judge', action => $options{action}, data => $options{data}, token => $options{token} diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index 0de0246f179..95f9f2f9b7f 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -683,7 +683,7 @@ sub run { # Connect internal $connector->{internal_socket} = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', - name => 'gorgonelegacycmd', + name => 'gorgone-legacycmd', logger => $self->{logger}, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} diff --git a/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm b/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm index 091c9f32a2a..91a65065af5 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm @@ -97,7 +97,7 @@ sub routing { gorgone::standard::library::zmq_send_message( socket => $options{socket}, - identity => 'gorgonelegacycmd', + identity => 'gorgone-legacycmd', action => $options{action}, data => $options{data}, token => $options{token}, diff --git a/gorgone/gorgone/modules/centreon/nodes/class.pm b/gorgone/gorgone/modules/centreon/nodes/class.pm index 870fc5e4bda..7f004b682eb 100644 --- a/gorgone/gorgone/modules/centreon/nodes/class.pm +++ b/gorgone/gorgone/modules/centreon/nodes/class.pm @@ -243,7 +243,7 @@ sub run { # Connect internal $connector->{internal_socket} = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', - name => 'gorgonenodes', + name => 'gorgone-nodes', logger => $self->{logger}, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} diff --git a/gorgone/gorgone/modules/centreon/nodes/hooks.pm b/gorgone/gorgone/modules/centreon/nodes/hooks.pm index b58040a2187..53fc5f8fd28 100644 --- a/gorgone/gorgone/modules/centreon/nodes/hooks.pm +++ b/gorgone/gorgone/modules/centreon/nodes/hooks.pm @@ -93,7 +93,7 @@ sub routing { gorgone::standard::library::zmq_send_message( socket => $options{socket}, - identity => 'gorgonenodes', + identity => 'gorgone-nodes', action => $options{action}, data => $options{data}, token => $options{token}, diff --git a/gorgone/gorgone/modules/centreon/statistics/class.pm b/gorgone/gorgone/modules/centreon/statistics/class.pm index 75000db641c..0f40f934da9 100644 --- a/gorgone/gorgone/modules/centreon/statistics/class.pm +++ b/gorgone/gorgone/modules/centreon/statistics/class.pm @@ -606,7 +606,7 @@ sub run { # Connect internal $connector->{internal_socket} = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', - name => 'gorgonestatistics', + name => 'gorgone-statistics', logger => $self->{logger}, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} diff --git a/gorgone/gorgone/modules/centreon/statistics/hooks.pm b/gorgone/gorgone/modules/centreon/statistics/hooks.pm index 6ea0a58602a..e6d90a0616d 100644 --- a/gorgone/gorgone/modules/centreon/statistics/hooks.pm +++ b/gorgone/gorgone/modules/centreon/statistics/hooks.pm @@ -107,7 +107,7 @@ sub routing { gorgone::standard::library::zmq_send_message( socket => $options{socket}, - identity => 'gorgonestatistics', + identity => 'gorgone-statistics', action => $options{action}, data => $options{data}, token => $options{token}, diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index f2729592d16..75e4351216d 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -346,7 +346,7 @@ sub action_run { my $socket_log = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', - name => 'gorgoneaction-'. $$, + name => 'gorgone-action-'. $$, logger => $self->{logger}, zmq_linger => 60000, type => $self->{config_core}->{internal_com_type}, @@ -426,7 +426,7 @@ sub run { # Connect internal $connector->{internal_socket} = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', - name => 'gorgoneaction', + name => 'gorgone-action', logger => $self->{logger}, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} diff --git a/gorgone/gorgone/modules/core/action/hooks.pm b/gorgone/gorgone/modules/core/action/hooks.pm index fbd147eadc8..46f9d4249aa 100644 --- a/gorgone/gorgone/modules/core/action/hooks.pm +++ b/gorgone/gorgone/modules/core/action/hooks.pm @@ -91,7 +91,7 @@ sub routing { gorgone::standard::library::zmq_send_message( socket => $options{socket}, - identity => 'gorgoneaction', + identity => 'gorgone-action', action => $options{action}, data => $options{data}, token => $options{token}, diff --git a/gorgone/gorgone/modules/core/cron/class.pm b/gorgone/gorgone/modules/core/cron/class.pm index 3070dd3ec56..f1fedc25d0b 100644 --- a/gorgone/gorgone/modules/core/cron/class.pm +++ b/gorgone/gorgone/modules/core/cron/class.pm @@ -440,7 +440,7 @@ sub run { # Connect internal $connector->{internal_socket} = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', - name => 'gorgonecron', + name => 'gorgone-cron', logger => $self->{logger}, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} diff --git a/gorgone/gorgone/modules/core/cron/hooks.pm b/gorgone/gorgone/modules/core/cron/hooks.pm index c53c988e1cd..8764b209471 100644 --- a/gorgone/gorgone/modules/core/cron/hooks.pm +++ b/gorgone/gorgone/modules/core/cron/hooks.pm @@ -93,7 +93,7 @@ sub routing { gorgone::standard::library::zmq_send_message( socket => $options{socket}, - identity => 'gorgonecron', + identity => 'gorgone-cron', action => $options{action}, data => $options{data}, token => $options{token}, diff --git a/gorgone/gorgone/modules/core/dbcleaner/class.pm b/gorgone/gorgone/modules/core/dbcleaner/class.pm index 46a9f8924fa..04b35c1ae8c 100644 --- a/gorgone/gorgone/modules/core/dbcleaner/class.pm +++ b/gorgone/gorgone/modules/core/dbcleaner/class.pm @@ -174,7 +174,7 @@ sub run { # Connect internal $connector->{internal_socket} = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', - name => 'gorgonedbcleaner', + name => 'gorgone-dbcleaner', logger => $self->{logger}, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} diff --git a/gorgone/gorgone/modules/core/dbcleaner/hooks.pm b/gorgone/gorgone/modules/core/dbcleaner/hooks.pm index 712e6e74acf..2c91abf4725 100644 --- a/gorgone/gorgone/modules/core/dbcleaner/hooks.pm +++ b/gorgone/gorgone/modules/core/dbcleaner/hooks.pm @@ -100,7 +100,7 @@ sub routing { gorgone::standard::library::zmq_send_message( socket => $options{socket}, - identity => 'gorgonedbcleaner', + identity => 'gorgone-dbcleaner', action => $options{action}, data => $options{data}, token => $options{token}, diff --git a/gorgone/gorgone/modules/core/httpserver/class.pm b/gorgone/gorgone/modules/core/httpserver/class.pm index fbbce0cb02b..be7fb31e051 100644 --- a/gorgone/gorgone/modules/core/httpserver/class.pm +++ b/gorgone/gorgone/modules/core/httpserver/class.pm @@ -159,7 +159,7 @@ sub run { # Connect internal $connector->{internal_socket} = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', - name => 'gorgonehttpserver', + name => 'gorgone-httpserver', logger => $self->{logger}, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} diff --git a/gorgone/gorgone/modules/core/httpserver/hooks.pm b/gorgone/gorgone/modules/core/httpserver/hooks.pm index 5adc6a509d3..adcc360b048 100644 --- a/gorgone/gorgone/modules/core/httpserver/hooks.pm +++ b/gorgone/gorgone/modules/core/httpserver/hooks.pm @@ -103,7 +103,7 @@ sub routing { gorgone::standard::library::zmq_send_message( socket => $options{socket}, - identity => 'gorgonehttpserver', + identity => 'gorgone-httpserver', action => $options{action}, data => $options{data}, token => $options{token}, diff --git a/gorgone/gorgone/modules/core/pipeline/class.pm b/gorgone/gorgone/modules/core/pipeline/class.pm index b1e2cc07ec0..b2bea3a6b1c 100644 --- a/gorgone/gorgone/modules/core/pipeline/class.pm +++ b/gorgone/gorgone/modules/core/pipeline/class.pm @@ -236,7 +236,7 @@ sub run { # Connect internal $connector->{internal_socket} = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', - name => 'gorgonepipeline', + name => 'gorgone-pipeline', logger => $self->{logger}, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} diff --git a/gorgone/gorgone/modules/core/pipeline/hooks.pm b/gorgone/gorgone/modules/core/pipeline/hooks.pm index b0810c1f07a..b91a73323e1 100644 --- a/gorgone/gorgone/modules/core/pipeline/hooks.pm +++ b/gorgone/gorgone/modules/core/pipeline/hooks.pm @@ -101,7 +101,7 @@ sub routing { gorgone::standard::library::zmq_send_message( socket => $options{socket}, - identity => 'gorgonepipeline', + identity => 'gorgone-pipeline', action => $options{action}, data => $options{data}, token => $options{token} diff --git a/gorgone/gorgone/modules/core/proxy/class.pm b/gorgone/gorgone/modules/core/proxy/class.pm index 17e0e9c1b27..e9b4e1e33bc 100644 --- a/gorgone/gorgone/modules/core/proxy/class.pm +++ b/gorgone/gorgone/modules/core/proxy/class.pm @@ -92,7 +92,7 @@ sub exit_process { sub read_message { my (%options) = @_; - return undef if (!defined($options{identity}) || $options{identity} !~ /^proxy-(.*?)-(.*?)$/); + return undef if (!defined($options{identity}) || $options{identity} !~ /^gorgone-proxy-(.*?)-(.*?)$/); my ($client_identity) = ($2); if ($options{data} =~ /^\[PONG\]/) { @@ -148,7 +148,7 @@ sub connect { if ($self->{clients}->{$options{id}}->{type} eq 'push_zmq') { $self->{clients}->{$options{id}}->{class} = gorgone::class::clientzmq->new( - identity => 'proxy-' . $self->{core_id} . '-' . $options{id}, + identity => 'gorgone-proxy-' . $self->{core_id} . '-' . $options{id}, cipher => $self->{clients}->{$options{id}}->{cipher}, vector => $self->{clients}->{$options{id}}->{vector}, client_pubkey => @@ -433,7 +433,7 @@ sub run { # Connect internal $self->{internal_socket} = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', - name => 'gorgoneproxy-' . $self->{pool_id}, + name => 'gorgone-proxy-' . $self->{pool_id}, logger => $self->{logger}, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index b183794e1af..e8651507ad2 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -294,7 +294,7 @@ sub routing { gorgone::standard::library::zmq_send_message( socket => $options{socket}, - identity => 'gorgoneproxy-' . $identity, + identity => 'gorgone-proxy-' . $identity, action => $action, data => $data, token => $options{token}, @@ -442,7 +442,7 @@ sub broadcast { gorgone::standard::library::zmq_send_message( socket => $options{socket}, - identity => 'gorgoneproxy-' . $pool_id, + identity => 'gorgone-proxy-' . $pool_id, action => $options{action}, data => $options{data}, token => $options{token}, diff --git a/gorgone/gorgone/modules/core/pull/hooks.pm b/gorgone/gorgone/modules/core/pull/hooks.pm index 3305b884a35..8fcaca98217 100644 --- a/gorgone/gorgone/modules/core/pull/hooks.pm +++ b/gorgone/gorgone/modules/core/pull/hooks.pm @@ -51,14 +51,14 @@ sub init { # Connect internal $socket_to_internal = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', - name => 'gorgonepull', + name => 'gorgone-pull', logger => $options{logger}, type => $config_core->{internal_com_type}, path => $config_core->{internal_com_path}, zmq_linger => $config->{linger} ); $client = gorgone::class::clientzmq->new( - identity => $config_core->{id}, + identity => 'gorgone-' . $config_core->{id}, cipher => $config->{cipher}, vector => $config->{vector}, client_pubkey => diff --git a/gorgone/gorgone/modules/core/register/class.pm b/gorgone/gorgone/modules/core/register/class.pm index 587ca07232d..2e438f193d1 100644 --- a/gorgone/gorgone/modules/core/register/class.pm +++ b/gorgone/gorgone/modules/core/register/class.pm @@ -161,7 +161,7 @@ sub run { # Connect internal $connector->{internal_socket} = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', - name => 'gorgoneregister', + name => 'gorgone-register', logger => $self->{logger}, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} diff --git a/gorgone/gorgone/modules/core/register/hooks.pm b/gorgone/gorgone/modules/core/register/hooks.pm index 038480eeb5e..1fbd01112f2 100644 --- a/gorgone/gorgone/modules/core/register/hooks.pm +++ b/gorgone/gorgone/modules/core/register/hooks.pm @@ -95,7 +95,7 @@ sub routing { gorgone::standard::library::zmq_send_message( socket => $options{socket}, - identity => 'gorgoneregister', + identity => 'gorgone-register', action => $options{action}, data => $options{data}, token => $options{token}, diff --git a/gorgone/gorgone/modules/plugins/newtest/class.pm b/gorgone/gorgone/modules/plugins/newtest/class.pm index 5320a7ffdd1..c68afeb446f 100644 --- a/gorgone/gorgone/modules/plugins/newtest/class.pm +++ b/gorgone/gorgone/modules/plugins/newtest/class.pm @@ -631,7 +631,8 @@ sub run { # Connect internal $connector->{internal_socket} = gorgone::standard::library::connect_com( - zmq_type => 'ZMQ_DEALER', name => 'gorgonenewtest-' . $self->{container_id}, + zmq_type => 'ZMQ_DEALER', + name => 'gorgone-newtest-' . $self->{container_id}, logger => $self->{logger}, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} diff --git a/gorgone/gorgone/modules/plugins/newtest/hooks.pm b/gorgone/gorgone/modules/plugins/newtest/hooks.pm index 92e7ef45665..e1ee2fb66fd 100644 --- a/gorgone/gorgone/modules/plugins/newtest/hooks.pm +++ b/gorgone/gorgone/modules/plugins/newtest/hooks.pm @@ -110,7 +110,7 @@ sub routing { } gorgone::standard::library::zmq_send_message( - socket => $options{socket}, identity => 'gorgonenewtest-' . $data->{container_id}, + socket => $options{socket}, identity => 'gorgone-newtest-' . $data->{container_id}, action => $options{action}, data => $options{data}, token => $options{token}, ); } @@ -175,7 +175,7 @@ sub broadcast { foreach my $container_id (keys %$containers) { if ($containers->{$container_id}->{running} == 1) { gorgone::standard::library::zmq_send_message( - socket => $options{socket}, identity => 'gorgonenewtest-' . $container_id, + socket => $options{socket}, identity => 'gorgone-newtest-' . $container_id, action => $options{action}, data => $options{data}, token => $options{token} ); } diff --git a/gorgone/gorgone/modules/plugins/scom/class.pm b/gorgone/gorgone/modules/plugins/scom/class.pm index ab76ce7f4d3..1a3225847e9 100644 --- a/gorgone/gorgone/modules/plugins/scom/class.pm +++ b/gorgone/gorgone/modules/plugins/scom/class.pm @@ -511,7 +511,7 @@ sub run { # Connect internal $connector->{internal_socket} = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', - name => 'gorgonescom-' . $self->{container_id}, + name => 'gorgone-scom-' . $self->{container_id}, logger => $self->{logger}, type => $self->{config_core}->{internal_com_type}, path => $self->{config_core}->{internal_com_path} diff --git a/gorgone/gorgone/modules/plugins/scom/hooks.pm b/gorgone/gorgone/modules/plugins/scom/hooks.pm index a146baa8f15..bd6be0fa358 100644 --- a/gorgone/gorgone/modules/plugins/scom/hooks.pm +++ b/gorgone/gorgone/modules/plugins/scom/hooks.pm @@ -109,7 +109,7 @@ sub routing { } gorgone::standard::library::zmq_send_message( - socket => $options{socket}, identity => 'gorgonescom-' . $data->{container_id}, + socket => $options{socket}, identity => 'gorgone-scom-' . $data->{container_id}, action => $options{action}, data => $options{data}, token => $options{token}, ); } @@ -174,7 +174,7 @@ sub broadcast { foreach my $container_id (keys %$containers) { if ($containers->{$container_id}->{running} == 1) { gorgone::standard::library::zmq_send_message( - socket => $options{socket}, identity => 'gorgonescom-' . $container_id, + socket => $options{socket}, identity => 'gorgone-scom-' . $container_id, action => $options{action}, data => $options{data}, token => $options{token} ); } diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index dff2997fc23..a1548dfb504 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -847,6 +847,11 @@ sub zmq_read_message { return undef; } my $identity = zmq_msg_data($message); + $identity = defined($identity) ? $identity : 'undef'; + if ($identity !~ /^gorgone-/) { + $options{logger}->writeLogError("[core] unknown identity: $identity"); + return undef; + } $message = zmq_recvmsg($options{socket}); if (!defined($message)) { $options{logger}->writeLogError("[core] zmq_recvmsg error: $!"); From 84ef9badfab1082892e4ee348683587a21e8766a Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Tue, 5 Jan 2021 13:57:07 +0100 Subject: [PATCH 568/948] enh(legacycmd): bulk external command from files (#97) --- .../modules/centreon/legacycmd/class.pm | 94 ++++++++++++++++--- 1 file changed, 79 insertions(+), 15 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index 95f9f2f9b7f..e6b2f8afcd9 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -46,10 +46,15 @@ sub new { if (!defined($connector->{config}->{cmd_dir}) || $connector->{config}->{cmd_dir} eq '') { $connector->{config}->{cmd_dir} = '/var/lib/centreon/centcore/'; } + $connector->{config}->{bulk_external_cmd} = + defined($connector->{config}->{bulk_external_cmd}) && $connector->{config}->{bulk_external_cmd} =~ /(\d+)/ ? $1 : 50; + $connector->{config}->{bulk_external_cmd_sequential} = + defined($connector->{config}->{bulk_external_cmd_sequential}) && $connector->{config}->{bulk_external_cmd_sequential} =~ /^False|0$/i ? 0 : 1; $connector->{config}->{dirty_mode} = defined($connector->{config}->{dirty_mode}) ? $connector->{config}->{dirty_mode} : 1; $connector->{gorgone_illegal_characters} = '`'; $connector->{cache_refresh_interval} = 60; $connector->{cache_refresh_last} = -1; + $connector->{bulk_commands} = {}; $connector->set_signal_handlers(); return $connector; @@ -158,6 +163,66 @@ sub check_pollers_config { return defined($self->{pollers}) ? 1 : 0; } +sub send_external_commands { + my ($self, %options) = @_; + my $token = $options{token}; + $token = $self->generate_token() if (!defined($token)); + + my $targets = []; + $targets = [$options{target}] if (defined($options{target})); + if (scalar(@$targets) <= 0) { + $targets = [keys %{$self->{bulk_commands}}]; + } + + foreach my $target (@$targets) { + next if (!defined($self->{bulk_commands}->{$target}) || scalar(@{$self->{bulk_commands}->{$target}}) <= 0); + $self->send_internal_action( + action => 'ENGINECOMMAND', + target => $target, + token => $token, + data => { + content => { + command_file => $self->{pollers}->{$target}->{command_file}, + commands => [ + join("\n", @{$self->{bulk_commands}->{$target}}) + ] + } + } + ); + + $self->{logger}->writeLogDebug("[legacycmd] send external commands for '$target'"); + $self->{bulk_commands}->{$target} = []; + } +} + +sub add_external_command { + my ($self, %options) = @_; + + $options{param} =~ s/[\Q$self->{gorgone_illegal_characters}\E]//g + if (defined($self->{gorgone_illegal_characters}) && $self->{gorgone_illegal_characters} ne ''); + if ($options{action} == 1) { + $self->send_internal_action( + action => 'ENGINECOMMAND', + target => $options{target}, + token => $options{token}, + data => { + content => { + command_file => $self->{pollers}->{ $options{target} }->{command_file}, + commands => [ + $options{param} + ] + } + } + ); + } else { + $self->{bulk_commands}->{ $options{target} } = [] if (!defined($self->{bulk_commands}->{ $options{target} })); + push @{$self->{bulk_commands}->{ $options{target} }}, $options{param}; + if (scalar(@{$self->{bulk_commands}->{ $options{target} }}) > $self->{config}->{bulk_external_cmd}) { + $self->send_external_commands(%options); + } + } +} + sub execute_cmd { my ($self, %options) = @_; @@ -172,22 +237,19 @@ sub execute_cmd { $self->{logger}->writeLogInfo($msg); if ($options{cmd} eq 'EXTERNALCMD') { - $options{param} =~ s/[\Q$self->{gorgone_illegal_characters}\E]//g - if (defined($self->{gorgone_illegal_characters}) && $self->{gorgone_illegal_characters} ne ''); - $self->send_internal_action( - action => 'ENGINECOMMAND', + $self->add_external_command( + action => $options{action}, + param => $options{param}, target => $options{target}, - token => $token, - data => { - content => { - command_file => $self->{pollers}->{$options{target}}->{command_file}, - commands => [ - $options{param} - ] - } - }, + token => $options{token} ); - } elsif ($options{cmd} eq 'SENDCFGFILE') { + return 0; + } + + $self->send_external_commands(target => $options{target}) + if (defined($options{target}) && $self->{config}->{bulk_external_cmd_sequential} == 1); + + if ($options{cmd} eq 'SENDCFGFILE') { my $cache_dir = (defined($connector->{config}->{cache_dir}) && $connector->{config}->{cache_dir} ne '') ? $connector->{config}->{cache_dir} : '/var/cache/centreon'; # engine @@ -547,7 +609,7 @@ sub handle_file { } if ($line =~ /^(.*?):([^:]*)(?::(.*)){0,1}/) { - $self->execute_cmd(cmd => $1, target => $2, param => $3); + $self->execute_cmd(action => 0, cmd => $1, target => $2, param => $3); if ($self->{config}->{dirty_mode} != 1) { my $current_pos = tell($handle); seek($handle, $current_pos - bytes::length($line), 0); @@ -615,6 +677,7 @@ sub handle_cmd_files { return if ($self->check_pollers_config() == 0); $self->handle_centcore_cmd(); $self->handle_centcore_dir(); + $self->send_external_commands(); } sub action_centreoncommand { @@ -642,6 +705,7 @@ sub action_centreoncommand { foreach my $command (@{$options{data}->{content}}) { my ($code, $message) = $self->execute_cmd( + action => 1, token => $options{token}, target => $command->{target}, cmd => $command->{command}, From adc6576cd9b17c8a7c37cbdda754c8503a18c7e9 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Tue, 5 Jan 2021 14:12:45 +0100 Subject: [PATCH 569/948] enh(doc): add configuration options for legacycmd module (#98) --- gorgone/docs/modules/centreon/legacycmd.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/gorgone/docs/modules/centreon/legacycmd.md b/gorgone/docs/modules/centreon/legacycmd.md index 783c0f57045..d7221114f75 100644 --- a/gorgone/docs/modules/centreon/legacycmd.md +++ b/gorgone/docs/modules/centreon/legacycmd.md @@ -14,13 +14,15 @@ The module relies on the following modules to process commands: ## Configuration -| Directive | Description | Default value | -| :- | :- | :- | -| cmd_file | *Command file* to read commands from | `/var/lib/centreon/centcore.cmd` | -| cmd_dir | Directory where to watch for *command files* | `/var/lib/centreon/` | -| cache_dir | Directory where to process Centreon configuration files | `/var/cache/centreon/` | -| cache_dir_trap | Directory where to process Centreontrapd databases | `/etc/snmp/centreon_traps/` | -| remote_dir | Directory where to export Remote Servers configuration | `/var/cache/centreon/config/remote-data/` | +| Directive | Description | Default value | +| :--------------------------- | :----------------------------------------------------------- | :---------------------------------------- | +| cmd_file | *Command file* to read commands from | `/var/lib/centreon/centcore.cmd` | +| cmd_dir | Directory where to watch for *command files* | `/var/lib/centreon/` | +| cache_dir | Directory where to process Centreon configuration files | `/var/cache/centreon/` | +| cache_dir_trap | Directory where to process Centreontrapd databases | `/etc/snmp/centreon_traps/` | +| remote_dir | Directory where to export Remote Servers configuration | `/var/cache/centreon/config/remote-data/` | +| bulk_external_cmd | Bulk external commands (DOWNTIME, ACK,...) | `50` | +| bulk_external_cmd_sequential | Order bulk external commands and other commands (Eg. RELOAD) | `1` | #### Example @@ -37,8 +39,8 @@ remote_dir: "/var/cache/centreon/config/remote-data/" ## Events -| Event | Description | -| :- | :- | +| Event | Description | +| :------------- | :-------------------------------- | | LEGACYCMDREADY | Internal event to notify the core | ## API From d59456116d751440e9b923aa68a698e9ca7876d0 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Fri, 15 Jan 2021 13:40:26 +0100 Subject: [PATCH 570/948] Proxy internal communication (#99) --- gorgone/gorgone/class/clientzmq.pm | 2 +- gorgone/gorgone/modules/core/proxy/class.pm | 146 ++++++++++++++------ gorgone/gorgone/modules/core/proxy/hooks.pm | 99 ++++++++----- 3 files changed, 167 insertions(+), 80 deletions(-) diff --git a/gorgone/gorgone/class/clientzmq.pm b/gorgone/gorgone/class/clientzmq.pm index 6d67996c4a0..c5b427bb262 100644 --- a/gorgone/gorgone/class/clientzmq.pm +++ b/gorgone/gorgone/class/clientzmq.pm @@ -304,7 +304,7 @@ sub send_message { zmq_sendmsg($sockets->{$self->{identity}}, $ciphertext, ZMQ_DONTWAIT); zmq_poll([$self->get_poll()], 10000); } - + if ($self->{handshake} < 2) { $self->{handshake} = 0; return (-1, $self->{verbose_last_message}); diff --git a/gorgone/gorgone/modules/core/proxy/class.pm b/gorgone/gorgone/modules/core/proxy/class.pm index e9b4e1e33bc..01938c98cf3 100644 --- a/gorgone/gorgone/modules/core/proxy/class.pm +++ b/gorgone/gorgone/modules/core/proxy/class.pm @@ -42,6 +42,7 @@ sub new { $connector->{pool_id} = $options{pool_id}; $connector->{clients} = {}; + $connector->{internal_channels} = {}; $connector->set_signal_handlers(); return $connector; @@ -101,6 +102,8 @@ sub read_message { } my ($action, $token, $data) = ($1, $2, $3); + # if we get a pong response, we can open the internal com read + $connector->{clients}->{ $client_identity }->{com_read_internal} = 1; $connector->send_internal_action( action => 'PONG', data => $data, @@ -149,25 +152,25 @@ sub connect { if ($self->{clients}->{$options{id}}->{type} eq 'push_zmq') { $self->{clients}->{$options{id}}->{class} = gorgone::class::clientzmq->new( identity => 'gorgone-proxy-' . $self->{core_id} . '-' . $options{id}, - cipher => $self->{clients}->{$options{id}}->{cipher}, - vector => $self->{clients}->{$options{id}}->{vector}, + cipher => $self->{clients}->{ $options{id} }->{cipher}, + vector => $self->{clients}->{ $options{id} }->{vector}, client_pubkey => - defined($self->{clients}->{$options{id}}->{client_pubkey}) && $self->{clients}->{$options{id}}->{client_pubkey} ne '' - ? $self->{clients}->{$options{id}}->{client_pubkey} : $self->{config_core}->{pubkey}, + defined($self->{clients}->{ $options{id} }->{client_pubkey}) && $self->{clients}->{ $options{id} }->{client_pubkey} ne '' + ? $self->{clients}->{ $options{id} }->{client_pubkey} : $self->{config_core}->{pubkey}, client_privkey => - defined($self->{clients}->{$options{id}}->{client_privkey}) && $self->{clients}->{$options{id}}->{client_privkey} ne '' - ? $self->{clients}->{$options{id}}->{client_privkey} : $self->{config_core}->{privkey}, - target_type => defined($self->{clients}->{$options{id}}->{target_type}) ? - $self->{clients}->{$options{id}}->{target_type} : + defined($self->{clients}->{ $options{id} }->{client_privkey}) && $self->{clients}->{ $options{id} }->{client_privkey} ne '' + ? $self->{clients}->{ $options{id} }->{client_privkey} : $self->{config_core}->{privkey}, + target_type => defined($self->{clients}->{ $options{id} }->{target_type}) ? + $self->{clients}->{ $options{id} }->{target_type} : 'tcp', - target_path => defined($self->{clients}->{$options{id}}->{target_path}) ? - $self->{clients}->{$options{id}}->{target_path} : - $self->{clients}->{$options{id}}->{address} . ':' . $self->{clients}->{$options{id}}->{port}, + target_path => defined($self->{clients}->{ $options{id} }->{target_path}) ? + $self->{clients}->{ $options{id} }->{target_path} : + $self->{clients}->{ $options{id} }->{address} . ':' . $self->{clients}->{ $options{id} }->{port}, config_core => $self->{config_core}, logger => $self->{logger} ); - $self->{clients}->{$options{id}}->{class}->init(callback => \&read_message); - } elsif ($self->{clients}->{$options{id}}->{type} eq 'push_ssh') { + $self->{clients}->{ $options{id} }->{class}->init(callback => \&read_message); + } elsif ($self->{clients}->{ $options{id} }->{type} eq 'push_ssh') { $self->{clients}->{$options{id}}->{class} = gorgone::modules::core::proxy::sshclient->new(logger => $self->{logger}); my $code = $self->{clients}->{$options{id}}->{class}->open_session( ssh_host => $self->{clients}->{$options{id}}->{address}, @@ -181,7 +184,7 @@ sub connect { ssh_connect_timeout => $self->{clients}->{$options{id}}->{ssh_connect_timeout} ); if ($code != 0) { - $self->{clients}->{$options{id}}->{delete} = 1; + $self->{clients}->{ $options{id} }->{delete} = 1; return -1; } } @@ -195,7 +198,7 @@ sub action_proxyaddnode { my ($code, $data) = $self->json_decode(argument => $options{data}); return if ($code == 1); - if (defined($self->{clients}->{$data->{id}}->{class})) { + if (defined($self->{clients}->{ $data->{id} }->{class})) { # test if a connection parameter changed my $changed = 0; foreach (keys %$data) { @@ -220,12 +223,27 @@ sub action_proxyaddnode { target => '' ); - $self->{clients}->{$data->{id}}->{class}->close(); + $self->{clients}->{ $data->{id} }->{class}->close(); + } else { + $self->{internal_channels}->{ $data->{id} } = gorgone::standard::library::connect_com( + zmq_type => 'ZMQ_DEALER', + name => 'gorgone-proxy-channel-' . $data->{id}, + logger => $self->{logger}, + type => $self->{config_core}->{internal_com_type}, + path => $self->{config_core}->{internal_com_path} + ); + $self->send_internal_action( + action => 'PROXYREADY', + data => { + node_id => $data->{id} + } + ); } - $self->{clients}->{$data->{id}} = $data; - $self->{clients}->{$data->{id}}->{delete} = 0; - $self->{clients}->{$data->{id}}->{class} = undef; + $self->{clients}->{ $data->{id} } = $data; + $self->{clients}->{ $data->{id} }->{delete} = 0; + $self->{clients}->{ $data->{id} }->{class} = undef; + $self->{clients}->{ $data->{id} }->{com_read_internal} = 1; } sub action_proxydelnode { @@ -235,7 +253,7 @@ sub action_proxydelnode { return if ($code == 1); if (defined($self->{clients}->{$data->{id}})) { - $self->{clients}->{$data->{id}}->{delete} = 1; + $self->{clients}->{ $data->{id} }->{delete} = 1; } } @@ -254,6 +272,19 @@ sub action_proxycloseconnection { $self->{clients}->{ $data->{id} }->{class} = undef; } +sub action_proxystopreadchannel { + my ($self, %options) = @_; + + my ($code, $data) = $self->json_decode(argument => $options{data}); + return if ($code == 1); + + return if (!defined($self->{clients}->{ $data->{id} })); + + $self->{logger}->writeLogInfo("[proxy] Stop read channel for $data->{id}"); + + $self->{clients}->{ $data->{id} }->{com_read_internal} = 0; +} + sub close_connections { my ($self, %options) = @_; @@ -274,17 +305,8 @@ sub proxy_ssh { if ($options{action} eq 'PING') { my $action = 'PONG'; if ($self->{clients}->{ $options{target_client} }->{class}->ping() == -1) { - $action = 'PONGRESET'; - $self->{clients}->{ $options{target_client} }->{class}->close(); - $self->{clients}->{ $options{target_client} }->{class} = undef; - $self->{clients}->{ $options{target_client} }->{delete} = 0; + $self->{clients}->{ $options{target_client} }->{delete} = 1; } - $self->send_internal_action( - action => $action, - data => { data => { id => $options{target_client} } }, - token => $options{token}, - target => '' - ); return ; } @@ -339,7 +361,7 @@ sub proxy { } my ($action, $token, $target_complete, $data) = ($1, $2, $3, $4); $connector->{logger}->writeLogDebug( - "[proxy] Send message: [action = $action] [token = $token] [target = $target_complete] [data = $data]" + "[proxy] Send message: [channel = $options{channel}] [action = $action] [token = $token] [target = $target_complete] [data = $data]" ); if ($action eq 'PROXYADDNODE') { @@ -355,6 +377,9 @@ sub proxy { } elsif ($action eq 'PROXYCLOSECONNECTION') { $connector->action_proxycloseconnection(data => $data); return ; + } elsif ($action eq 'PROXYSTOPREADCHANNEL') { + $connector->action_proxystopreadchannel(data => $data); + return ; } if ($target_complete !~ /^(.+)~~(.+)$/) { @@ -415,22 +440,47 @@ sub proxy { } } +=begin comment + perl binding zmq_poll order fired events (first of the array,...) + the first array element is: the control channel +=cut sub event_internal { + my (%options) = @_; + + return if ( + defined($connector->{clients}->{ $options{channel} }) && + ($connector->{clients}->{ $options{channel} }->{com_read_internal} == 0 || $connector->{clients}->{ $options{channel} }->{delete} == 1) + ); + + my $socket = $options{channel} eq 'control' ? $connector->{internal_socket} : $connector->{internal_channels}->{ $options{channel} }; while (1) { - my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); + my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $socket); - proxy(message => $message); + proxy(message => $message, channel => $options{channel}); if ($connector->{stop} == 1 && (time() - $connector->{exit_timeout}) > $connector->{stop_time}) { $connector->exit_process(); } - last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); + return if ( + defined($connector->{clients}->{ $options{channel} }) && + ($connector->{clients}->{ $options{channel} }->{com_read_internal} == 0 || $connector->{clients}->{ $options{channel} }->{delete} == 1) + ); + last unless (gorgone::standard::library::zmq_still_read(socket => $socket)); } } +sub generate_internal_cb { + my (%options) = @_; + + my $channel = $options{channel}; + return sub { + event_internal(channel => $channel); + }; +} + sub run { my ($self, %options) = @_; - # Connect internal + # Connect canal internal $self->{internal_socket} = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', name => 'gorgone-proxy-' . $self->{pool_id}, @@ -442,12 +492,14 @@ sub run { action => 'PROXYREADY', data => { pool_id => $self->{pool_id} - }, + } ); my $poll = { - socket => $self->{internal_socket}, - events => ZMQ_POLLIN, - callback => \&event_internal, + socket => $self->{internal_socket}, + events => ZMQ_POLLIN, + callback => sub { + event_internal(channel => 'control'); + } }; while (1) { my $polls = [$poll]; @@ -463,21 +515,31 @@ sub run { $self->{clients}->{$_}->{class}->close() if (defined($self->{clients}->{$_}->{class})); $self->{clients}->{$_}->{class} = undef; $self->{clients}->{$_}->{delete} = 0; + $self->{clients}->{$_}->{com_read_internal} = 0; next; } + if ($self->{clients}->{$_}->{com_read_internal} == 1) { + push @$polls, { + socket => $self->{internal_channels}->{$_}, + events => ZMQ_POLLIN, + callback => generate_internal_cb(channel => $self->{clients}->{$_}->{id}) + }; + } if (defined($self->{clients}->{$_}->{class}) && $self->{clients}->{$_}->{type} eq 'push_zmq') { push @$polls, $self->{clients}->{$_}->{class}->get_poll(); } } # we try to do all we can - my $rev = zmq_poll($polls, 5000); + my $rv = scalar(zmq_poll($polls, 5000)); # Sometimes (with big message) we have a undef ??!!! - next if (!defined($rev)); + if ($rv == -1) { + $self->{logger}->writeLogError("[proxy] zmq_poll failed: $!"); + } - if ($rev == 0 && $self->{stop} == 1) { + if ($rv == 0 && $self->{stop} == 1) { $self->exit_process(); } } diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index e8651507ad2..8553546cf20 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -34,6 +34,12 @@ use Digest::MD5::File qw(file_md5_hex); use Fcntl; use Time::HiRes; +=begin comment +for each proxy processus, we have: + one control channel (DEALER identity: gorgone-proxy-$poolid) + one channel by client (DEALER identity: gorgone-proxy-channel-$nodeid) +=cut + use constant NAMESPACE => 'core'; use constant NAME => 'proxy'; use constant EVENTS => [ @@ -47,7 +53,8 @@ use constant EVENTS => [ { event => 'PROXYDELNODE' }, # internal. Shouldn't be used by third party clients { event => 'PROXYADDSUBNODE' }, # internal. Shouldn't be used by third party clients { event => 'PONGRESET' }, # internal. Shouldn't be used by third party clients - { event => 'PROXYCLOSECONNECTION' } + { event => 'PROXYCLOSECONNECTION' }, + { event => 'PROXYSTOPREADCHANNEL' } ]; my $config_core; @@ -170,18 +177,22 @@ sub routing { } if ($options{action} eq 'PROXYREADY') { - $pools->{$data->{pool_id}}->{ready} = 1; - # we sent proxyaddnode to sync - foreach my $node_id (keys %$nodes_pool) { - next if ($nodes_pool->{$node_id} != $data->{pool_id}); - routing( - socket => $internal_socket, - action => 'PROXYADDNODE', - target => $node_id, - data => JSON::XS->new->utf8->encode($register_nodes->{$node_id}), - dbh => $options{dbh}, - logger => $options{logger} - ); + if (defined($data->{pool_id})) { + $pools->{ $data->{pool_id} }->{ready} = 1; + # we sent proxyaddnode to sync + foreach my $node_id (keys %$nodes_pool) { + next if ($nodes_pool->{$node_id} != $data->{pool_id}); + routing( + socket => $internal_socket, + action => 'PROXYADDNODE', + target => $node_id, + data => JSON::XS->new->utf8->encode($register_nodes->{$node_id}), + dbh => $options{dbh}, + logger => $options{logger} + ); + } + } elsif (defined($data->{node_id}) && defined($synctime_nodes->{ $data->{node_id} })) { + $synctime_nodes->{ $data->{node_id} }->{channel_ready} = 1; } return undef; } @@ -191,7 +202,7 @@ sub routing { return undef; } - my ($code, $target_complete, $target_parent, $target) = pathway( + my ($code, $is_ctrl_channel, $target_complete, $target_parent, $target) = pathway( action => $options{action}, target => $options{target}, dbh => $options{dbh}, @@ -269,6 +280,19 @@ sub routing { return if ($code == -1); } + my $pool_id; + if (defined($nodes_pool->{$target_parent})) { + $pool_id = $nodes_pool->{$target_parent}; + } else { + $pool_id = rr_pool(); + $nodes_pool->{$target_parent} = $pool_id; + } + + my $identity = 'gorgone-proxy-' . $pool_id; + if ($is_ctrl_channel == 0 && $synctime_nodes->{$target_parent}->{channel_ready} == 1) { + $identity = 'gorgone-proxy-channel-' . $target_parent; + } + foreach my $data (@{$bulk_actions}) { # Mode zmq pull if ($register_nodes->{$target_parent}->{type} eq 'pull') { @@ -284,17 +308,9 @@ sub routing { next; } - my $identity; - if (defined($nodes_pool->{$target_parent})) { - $identity = $nodes_pool->{$target_parent}; - } else { - $identity = rr_pool(); - $nodes_pool->{$target_parent} = $identity; - } - gorgone::standard::library::zmq_send_message( socket => $options{socket}, - identity => 'gorgone-proxy-' . $identity, + identity => $identity, action => $action, data => $data, token => $options{token}, @@ -445,7 +461,7 @@ sub broadcast { identity => 'gorgone-proxy-' . $pool_id, action => $options{action}, data => $options{data}, - token => $options{token}, + token => $options{token} ); } } @@ -488,32 +504,39 @@ sub pathway { push @targets, keys %{$register_subnodes->{$target}->{dynamic}}; } + my $first_target; foreach (@targets) { if ($register_nodes->{$_}->{type} eq 'pull' && !defined($register_nodes->{$_}->{identity})) { $options{logger}->writeLogDebug("[proxy] skip node pull target '$_' for node '$target' - never connected"); next; } - # we let the ping passthrough - if (defined($last_pong->{$_}) && (time() - $config->{pong_discard_timeout} >= $last_pong->{$_}) && $options{action} eq 'PING' && $_ eq $target) { - return (1, $_ . '~~' . $target, $_, $target); + # we let passthrough. it's for control channel + if ($options{action} =~ /^(?:PING|PROXYADDNODE|PROXYDELNODE|PROXYADDSUBNODE|PROXYCLOSECONNECTION|PROXYSTOPREADCHANNEL)$/ && $_ eq $target) { + return (1, 1, $_ . '~~' . $target, $_, $target); } if (!defined($last_pong->{$_}) || $last_pong->{$_} == 0 || (time() - $config->{pong_discard_timeout} < $last_pong->{$_})) { $options{logger}->writeLogDebug("[proxy] choose node target '$_' for node '$target'"); - return (1, $_ . '~~' . $target, $_, $target); + return (1, 0, $_ . '~~' . $target, $_, $target); } - $options{logger}->writeLogDebug("[proxy] skip node target '$_' for node '$target'"); + $first_target = $_ if (!defined($first_target)); + if ($synctime_nodes->{$_}->{channel_read_stop} == 0) { + $synctime_nodes->{$_}->{channel_read_stop} = 1; + routing( + socket => $internal_socket, + target => $_, + action => 'PROXYCLOSEREADCHANNEL', + data => JSON::XS->new->utf8->encode({ id => $_ }), + dbh => $options{dbh}, + logger => $options{logger} + ); + } } - gorgone::standard::library::add_history( - dbh => $options{dbh}, - code => GORGONE_ACTION_FINISH_KO, token => $options{token}, - data => { message => 'proxy - discard message. target peer(s) seems disconnected' }, - json_encode => 1 - ); - return -1; + # if there are here, we use the first pathway (because all pathways had an issue) + return (1, 0, $first_target . '~~' . $target, $first_target, $target); } sub setlogs { @@ -916,7 +939,9 @@ sub register_nodes { in_progress => 0, in_progress_time => -1, synctime_error => 0, - ping_timeout => 0 + ping_timeout => 0, + channel_read_stop => 0, + channel_ready => 0 }; get_sync_time(node_id => $node->{id}, dbh => $options{dbh}); } From a83e4946b1f44290b215c958a6ab181296f4eeb9 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Fri, 15 Jan 2021 16:04:46 +0100 Subject: [PATCH 571/948] enh(proxy): random ping window (#100) --- gorgone/gorgone/modules/core/proxy/class.pm | 24 ++++--- gorgone/gorgone/modules/core/proxy/hooks.pm | 78 +++++++++++---------- 2 files changed, 56 insertions(+), 46 deletions(-) diff --git a/gorgone/gorgone/modules/core/proxy/class.pm b/gorgone/gorgone/modules/core/proxy/class.pm index 01938c98cf3..c386f15d469 100644 --- a/gorgone/gorgone/modules/core/proxy/class.pm +++ b/gorgone/gorgone/modules/core/proxy/class.pm @@ -100,14 +100,17 @@ sub read_message { if ($options{data} !~ /^\[(.+?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)/m) { return undef; } - my ($action, $token, $data) = ($1, $2, $3); + my ($action, $token) = ($1, $2); + my ($code, $data) = $connector->json_decode(argument => $3); + return undef if ($code == 1); + + $data->{data}->{id} = $client_identity; # if we get a pong response, we can open the internal com read $connector->{clients}->{ $client_identity }->{com_read_internal} = 1; $connector->send_internal_action( action => 'PONG', data => $data, - data_noencode => 1, token => $token, target => '' ); @@ -125,13 +128,8 @@ sub read_message { target => '' ); } elsif ($options{data} =~ /^\[ACK\]\s+\[(.*?)\]\s+(.*)/ms) { - my $data; - eval { - $data = JSON::XS->new->utf8->decode($2); - }; - if ($@) { - return undef; - } + my ($code, $data) = $connector->json_decode(argument => $2); + return undef if ($code == 1); # we set the id (distant node can not have id in configuration) $data->{data}->{id} = $client_identity; @@ -303,9 +301,15 @@ sub proxy_ssh { return if ($code == 1); if ($options{action} eq 'PING') { - my $action = 'PONG'; if ($self->{clients}->{ $options{target_client} }->{class}->ping() == -1) { $self->{clients}->{ $options{target_client} }->{delete} = 1; + } else { + $self->send_internal_action( + action => 'PONG', + data => { data => { id => $options{target_client} } }, + token => $options{token}, + target => '' + ); } return ; } diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index 8553546cf20..52a89fa3b68 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -65,8 +65,7 @@ my $synctime_nodes = {}; # get last time retrieved my $synctime_lasttime; my $synctime_option; my $synctimeout_option; -my $ping_option; -my $ping_time = 0; +my $ping_interval; my $last_pong = {}; my $register_nodes = {}; @@ -106,7 +105,7 @@ sub register { $synctime_option = defined($config->{synchistory_time}) ? $config->{synchistory_time} : 60; $synctimeout_option = defined($config->{synchistory_timeout}) ? $config->{synchistory_timeout} : 30; - $ping_option = defined($config->{ping}) ? $config->{ping} : 60; + $ping_interval = defined($config->{ping}) ? $config->{ping} : 60; $config->{pong_discard_timeout} = defined($config->{pong_discard_timeout}) ? $config->{pong_discard_timeout} : 300; $config->{pong_max_timeout} = defined($config->{pong_max_timeout}) ? $config->{pong_max_timeout} : 3; $config->{pool} = defined($config->{pool}) && $config->{pool} =~ /(\d+)/ ? $1 : 5; @@ -146,11 +145,12 @@ sub routing { if ($options{action} eq 'PONG') { return undef if (!defined($data->{data}->{id}) || $data->{data}->{id} eq ''); - $synctime_nodes->{$data->{data}->{id}}->{in_progress_ping} = 0; - $synctime_nodes->{$data->{data}->{id}}->{ping_timeout} = 0; + $constatus_ping->{ $data->{data}->{id} }->{in_progress_ping} = 0; + $constatus_ping->{ $data->{data}->{id} }->{ping_timeout} = 0; $last_pong->{$data->{data}->{id}} = time(); - $constatus_ping->{$data->{data}->{id}}->{last_ping_recv} = time(); - $constatus_ping->{$data->{data}->{id}}->{nodes} = $data->{data}->{data}; + $constatus_ping->{ $data->{data}->{id} }->{last_ping_recv} = time(); + $constatus_ping->{ $data->{data}->{id} }->{nodes} = $data->{data}->{data}; + $constatus_ping->{ $data->{data}->{id} }->{ping_ok}++; register_subnodes(%options, id => $data->{data}->{id}, subnodes => $data->{data}->{data}); $options{logger}->writeLogInfo("[proxy] Pong received from '" . $data->{data}->{id} . "'"); return undef; @@ -158,9 +158,10 @@ sub routing { if ($options{action} eq 'PONGRESET') { return undef if (!defined($data->{data}->{id}) || $data->{data}->{id} eq ''); - if (defined($synctime_nodes->{ $data->{data}->{id} })) { - $synctime_nodes->{ $data->{data}->{id} }->{in_progress_ping} = 0; - $synctime_nodes->{ $data->{data}->{id} }->{ping_timeout} = 0; + if (defined($constatus_ping->{ $data->{data}->{id} })) { + $constatus_ping->{ $data->{data}->{id} }->{in_progress_ping} = 0; + $constatus_ping->{ $data->{data}->{id} }->{ping_timeout} = 0; + $constatus_ping->{ $data->{data}->{id} }->{ping_failed}++; } $options{logger}->writeLogInfo("[proxy] PongReset received from '" . $data->{data}->{id} . "'"); return undef; @@ -387,18 +388,19 @@ sub check { # We check synclog/ping/ping request timeout foreach (keys %$synctime_nodes) { - if ($register_nodes->{$_}->{type} eq 'pull' && $synctime_nodes->{$_}->{in_progress_ping} == 1) { + if ($register_nodes->{$_}->{type} eq 'pull' && $constatus_ping->{$_}->{in_progress_ping} == 1) { my $ping_timeout = defined($register_nodes->{$_}->{ping_timeout}) ? $register_nodes->{$_}->{ping_timeout} : 30; - if ((time() - $synctime_nodes->{$_}->{in_progress_ping_pull}) > $ping_timeout) { - $synctime_nodes->{$_}->{in_progress_ping} = 0; + if ((time() - $constatus_ping->{$_}->{in_progress_ping_pull}) > $ping_timeout) { + $constatus_ping->{$_}->{in_progress_ping} = 0; $options{logger}->writeLogInfo("[proxy] Ping timeout from '" . $_ . "'"); } } - if ($register_nodes->{$_}->{type} ne 'pull' && $synctime_nodes->{$_}->{in_progress_ping} == 1) { + if ($register_nodes->{$_}->{type} ne 'pull' && $constatus_ping->{$_}->{in_progress_ping} == 1) { if (time() - $constatus_ping->{ $_ }->{last_ping_sent} > $config->{pong_discard_timeout}) { $options{logger}->writeLogInfo("[proxy] Ping timeout from '" . $_ . "'"); - $synctime_nodes->{$_}->{in_progress_ping} = 0; - $synctime_nodes->{$_}->{ping_timeout}++; + $constatus_ping->{$_}->{in_progress_ping} = 0; + $constatus_ping->{$_}->{ping_timeout}++; + $constatus_ping->{$_}->{ping_failed}++; if (($synctime_nodes->{$_}->{ping_timeout} % $config->{pong_max_timeout}) == 0) { $options{logger}->writeLogInfo("[proxy] Ping max timeout reached from '" . $_ . "'"); routing( @@ -409,7 +411,6 @@ sub check { dbh => $options{dbh}, logger => $options{logger} ); - $synctime_nodes->{$_}->{ping_timeout}++; } } } @@ -433,10 +434,7 @@ sub check { full_sync_history(dbh => $options{dbh}, logger => $options{logger}); } - if ($stop == 0 && - time() - $ping_time > $ping_option) { - $options{logger}->writeLogInfo("[proxy] Send pings"); - $ping_time = time(); + if ($stop == 0) { ping_send(dbh => $options{dbh}, logger => $options{logger}); } @@ -564,8 +562,8 @@ sub setlogs { $options{logger}->writeLogInfo("[proxy] Received setlogs for '$options{data}->{data}->{id}'"); # we have received the setlogs (it's like a pong response. not a problem if we received the pong after) - $synctime_nodes->{ $options{data}->{data}->{id} }->{in_progress_ping} = 0; - $synctime_nodes->{ $options{data}->{data}->{id} }->{ping_timeout} = 0; + $constatus_ping->{ $options{data}->{data}->{id} }->{in_progress_ping} = 0; + $constatus_ping->{ $options{data}->{data}->{id} }->{ping_timeout} = 0; $constatus_ping->{ $options{data}->{data}->{id} }->{last_ping_recv} = time(); $last_pong->{ $options{data}->{data}->{id} } = time() if (defined($last_pong->{ $options{data}->{data}->{id} })); @@ -627,16 +625,18 @@ sub ping_send { my $nodes_id = [keys %$register_nodes]; $nodes_id = [$options{node_id}] if (defined($options{node_id})); + my $current_time = time(); foreach my $id (@$nodes_id) { - next if (defined($synctime_nodes->{$id}) && $synctime_nodes->{$id}->{in_progress_ping} == 1); + next if ($constatus_ping->{$id}->{in_progress_ping} == 1 || $current_time < $constatus_ping->{$id}->{next_ping}); - $constatus_ping->{$id}->{last_ping_sent} = time(); + $constatus_ping->{$id}->{last_ping_sent} = $current_time; + $constatus_ping->{$id}->{next_ping} = $current_time + $ping_interval; if ($register_nodes->{$id}->{type} eq 'push_zmq' || $register_nodes->{$id}->{type} eq 'push_ssh') { - $synctime_nodes->{$id}->{in_progress_ping} = 1; + $constatus_ping->{$id}->{in_progress_ping} = 1; routing(socket => $internal_socket, action => 'PING', target => $id, data => '{}', dbh => $options{dbh}, logger => $options{logger}); } elsif ($register_nodes->{$id}->{type} eq 'pull') { - $synctime_nodes->{$id}->{in_progress_ping} = 1; - $synctime_nodes->{$id}->{in_progress_ping_pull} = time(); + $constatus_ping->{$id}->{in_progress_ping} = 1; + $constatus_ping->{$id}->{in_progress_ping_pull} = time(); routing(action => 'PING', target => $id, data => '{}', dbh => $options{dbh}, logger => $options{logger}); } } @@ -689,9 +689,9 @@ sub get_sync_time { $synctime_nodes->{$options{node_id}}->{synctime_error} = 0; if (my $row = $sth->fetchrow_hashref()) { - $synctime_nodes->{$row->{id}}->{ctime} = $row->{ctime}; - $synctime_nodes->{$row->{id}}->{in_progress} = 0; - $synctime_nodes->{$row->{id}}->{in_progress_time} = -1; + $synctime_nodes->{ $row->{id} }->{ctime} = $row->{ctime}; + $synctime_nodes->{ $row->{id} }->{in_progress} = 0; + $synctime_nodes->{ $row->{id} }->{in_progress_time} = -1; } return 0; @@ -935,11 +935,9 @@ sub register_nodes { if (!defined($synctime_nodes->{ $node->{id} })) { $synctime_nodes->{ $node->{id} } = { ctime => 0, - in_progress_ping => 0, in_progress => 0, in_progress_time => -1, synctime_error => 0, - ping_timeout => 0, channel_read_stop => 0, channel_ready => 0 }; @@ -954,9 +952,17 @@ sub register_nodes { } } if ($new_node == 1) { - $constatus_ping->{ $node->{id} } = { type => $node->{type}, last_ping_sent => 0, last_ping_recv => 0, nodes => {} }; - # we provide information to the proxy class - ping_send(node_id => $node->{id}, dbh => $options{dbh}, logger => $options{logger}); + $constatus_ping->{ $node->{id} } = { + type => $node->{type}, + in_progress_ping => 0, + ping_timeout => 0, + last_ping_sent => 0, + last_ping_recv => 0, + next_ping => time() + int(rand($ping_interval)), + ping_ok => 0, + ping_failed => 0, + nodes => {} + }; $options{logger}->writeLogInfo("[proxy] Node '" . $node->{id} . "' is registered"); } } From 85255b67f3757f962dc25d300e42316fd0b07a28 Mon Sep 17 00:00:00 2001 From: Mareau Bastien <bmareau@centreon.com> Date: Tue, 19 Jan 2021 08:59:43 +0100 Subject: [PATCH 572/948] Fix documentation (#102) "filter_rules" and not ""filters_rules" (line 281) --- gorgone/docs/modules/centreon/autodiscovery.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/docs/modules/centreon/autodiscovery.md b/gorgone/docs/modules/centreon/autodiscovery.md index 7a7be1f3f53..b021703dd3d 100644 --- a/gorgone/docs/modules/centreon/autodiscovery.md +++ b/gorgone/docs/modules/centreon/autodiscovery.md @@ -278,6 +278,6 @@ curl --request POST "https://hostname:8443/api/centreon/autodiscovery/services" --header "Accept: application/json" \ --header "Content-Type: application/json" \ --data '{ - "filters_rules": ["OS-Linux-SNMP-Disk-Name", "OS-Linux-SNMP-Traffic-Name"] + "filter_rules": ["OS-Linux-SNMP-Disk-Name", "OS-Linux-SNMP-Traffic-Name"] }' ``` From ab5fa6e47f750751458f3ecfee37c3adec0e7674 Mon Sep 17 00:00:00 2001 From: sc979 <34628915+sc979@users.noreply.github.com> Date: Tue, 19 Jan 2021 09:00:14 +0100 Subject: [PATCH 573/948] enh(chore): update security policy GPG key (#101) --- gorgone/SECURITY.md | 58 ++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/gorgone/SECURITY.md b/gorgone/SECURITY.md index 1aed97a9295..33a273e67ea 100644 --- a/gorgone/SECURITY.md +++ b/gorgone/SECURITY.md @@ -12,7 +12,7 @@ Send an email to security@centreon.com. If possible, encrypt your message with o You should receive a response within 48 hours. If for some reason you do not, please follow up via email to ensure we received your original message. -To help us better understand the nature and scope of the possible issue, please describe as much as you can: +To help us better understand the nature and scope of the possible issue, please describe as much as you can: * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) * Full paths of source file(s) related to the manifestation of the issue @@ -41,19 +41,19 @@ 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 @@ -64,20 +64,20 @@ anLabHP6EsM2kHeO8J1nHiNJ2b58lxVcUBTMkkoQLxN1shOozkYiapX33Cxv+Smy cCC6t+Yzlrv3EadFUTAeXiHjpxjef3Lv7BWLZDr5psaCgvRzO0Ffn4hniUiKqTTb wHJxDA1iP9vfEAh61kpBQ8p+C6h4+hn5OWCbz6GBp+wEG1Rswrz734K7Aiywr8pH la1+oXYkRrAZop9Oagh9weimbR0ZZ76kD4duSq3blV6mhh7Cs94e4HINB6NzMfXg -YYk4Dwr6aiW2Np5MLQARAQABiQI8BBgBCAAmFiEEw3fp1S1cE3091XO1vq9uv2MR -BvkFAl5CgfMCGwwFCQHhM4AACgkQvq9uv2MRBvnkkw//UbXbzC0tcStxnSqRXmdL -Lv11lFa2hFcAP3/aLoHyryF4qoiqhGc0K6nrd9ApE2tMia1PKV3jiBlhTlXWeR8m -Y7/y3OJSqjSg8qu9L14AQhLzvYNEAc3TVhAAMNQoekDg/QapQSPxpqlPjhFyF91K -jjvSXPHVuFWKmYvPqldceTX8J032fOGDrhAPmzaXo487CDPVuQe3Xg8V+yZdLcpw -KBU800tQ/GkI5Zb2HrxIQ/qEPmcFHcpQQnbu7nuBe8OzfwWCIZ3clN55LYF9FgmQ -MoefpoBeEpWxNAY23KT6MkjeX/VxtRF+gwGTPGAoRoiRrrywLSPAlzj0V4iglSs7 -wPqugycY7sfOGZ2KzciLmUkM8pd2/Kv1AFF3zYznvSmov71el8hh46FCE4a2xo71 -kmh+//+obMyASf6npTIeNwBKV0sSZ49AoEd/kA6agYXbEjRIPLgVyvCyEHGhmAOS -U1UYTpy6qXTX2Xj0rCvOXlxDCWIMyesjjBxKyuoe4TYMPp+D9ZEBndN45lYNmfBG -Vl95htR6juC4rRBtQZDgZFzD9y20shRsXFZ9t9GSpO5wmKOxuFwPq6c9pyiUM83T -Y6odD3X30YsLpvwBEXlvs0pLXVjlJn3PZsKE5qEbOIy29elhjoMDuZ9F3kWD4ykv -oWAyTvK/VwbB77CTz1yTUSE= -=3kAj +YYk4Dwr6aiW2Np5MLQARAQABiQI8BBgBCAAmAhsMFiEEw3fp1S1cE3091XO1vq9u +v2MRBvkFAl/+0/AFCQV+uP0ACgkQvq9uv2MRBvlNgRAAmU5cxvP38BbCdlhN9gyH +wZwHi2kSeROFeKc2GCL2ixVGS0dANmMPZEV/jNj75shF4tK0NDZWWWDFZm/2bsFI +M5QvNJm8OYTcTCfcbFj7uHG1wYRGKRq0PxYKJ5WY1RAqgAjuGNBME/16z5E042Co +puh2z1PvV/CKhIOFBMTjofCVWYkDMg1iUQN3pS55FuIz6sw8DZPfnxHUaCKNYX3a +we7rooia1dUl2yXkSqwo99IMqrDqFyLye1kISh/Kg83zocVLdNnbaNvpj/FgXYII +xXxsvGXEE463/Rr+rpCyuMY8L+VnCQWFFoammrgN86L5iHh9sY5q7YRWnwqrra78 +BsWm+QjKxlY+UpjZTm+rppijBsRU00DpfnKds0a9zLAZCaTBJ6UGpIW4nsjV7rDj +5t420dOe5LfqIgS/zUw8K3gGgvyOJbm8D4E4fCMn5A5/mOSkAIiWzx2+hzhfmbFt +omSMCHOCNQbSF/7PoU6Z4J6sq5AkZOf9qQU+UHszAHtqW9A5iSIsjJEW7Poplsd1 +QpaRONGuU0UPk6MQEDMq8YYYINpNHwU3ZxgQuq5PrUVZd48NdAPFhssRjwZ3rYsQ +3Nl02J4RHppsPsDjxkmvMc4X2uCfgenTG/Vc/gNTQhPSozSKG8yFoClMlb3onsOF +SL4taAGY0BDuA3zhB7p5tP8= +=YBvx -----END PGP PUBLIC KEY BLOCK----- ``` @@ -87,7 +87,7 @@ oWAyTvK/VwbB77CTz1yTUSE= | Type | RSA | | Size | 4096 | | Created | 2020-02-11 | -| Expires | 2021-02-10 | +| Expires | 2022-01-13 | | Cipher |AES-256| | Fingerprint | C377 E9D5 2D5C 137D 3DD5 73B5 BEA F6EBF 6311 06F9 | From f4b81e50d33fec6622839d7ca0a4d75e7949447e Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Wed, 20 Jan 2021 11:01:12 +0100 Subject: [PATCH 574/948] enh(core): create random ipc file (#103) --- gorgone/gorgone/class/core.pm | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 3f92bae25e9..dc790f3fea5 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -31,6 +31,7 @@ use gorgone::standard::constants qw(:all); use gorgone::standard::misc; use gorgone::class::db; use gorgone::class::listener; +use Time::HiRes; my ($gorgone); @@ -152,9 +153,11 @@ sub init { $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_zmq_tcp_keepalive} = defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_zmq_tcp_keepalive}) && $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_zmq_tcp_keepalive} =~ /^(0|1)$/ ? $1 : 1; + my $time_hi = Time::HiRes::time(); + $time_hi =~ s/\.//; $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_type} = 'ipc' if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_type}) || $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_type} eq ''); - $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_path} = '/tmp/gorgone/routing.ipc' + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_path} = '/tmp/gorgone/routing-' . $time_hi . '.ipc' if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_path}) || $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_path} eq ''); $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{timeout} = defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{timeout}) && $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{timeout} =~ /(\d+)/ ? $1 : 50; @@ -760,6 +763,9 @@ sub quit { if (defined($self->{external_socket})) { zmq_close($self->{external_socket}); } + if ($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_type} eq 'ipc') { + unlink($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_path}); + } exit(0); } From 1a61862bc2d666b8a383dab599f7bb6b3c672d57 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Wed, 20 Jan 2021 11:10:48 +0100 Subject: [PATCH 575/948] fix(cron): keep_token attribute false value (#104) --- gorgone/gorgone/modules/core/cron/class.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gorgone/gorgone/modules/core/cron/class.pm b/gorgone/gorgone/modules/core/cron/class.pm index f1fedc25d0b..3e34edff989 100644 --- a/gorgone/gorgone/modules/core/cron/class.pm +++ b/gorgone/gorgone/modules/core/cron/class.pm @@ -410,7 +410,8 @@ sub dispatcher { $options->{logger}->writeLogInfo("[cron] Launching job '" . $id . "'"); - my $token = (defined($options->{definition}->{keep_token})) ? $options->{definition}->{id} : undef; + my $token = (defined($options->{definition}->{keep_token})) && $options->{definition}->{keep_token} =~ /true|1/i + ? $options->{definition}->{id} : undef; gorgone::standard::library::zmq_send_message( socket => $options->{socket}, From 14ea2ca22e3d0c966d8af89dd9405d58ad6a831d Mon Sep 17 00:00:00 2001 From: Colin Gagnaire <cgagnaire@centreon.com> Date: Wed, 24 Feb 2021 16:57:04 +0100 Subject: [PATCH 576/948] enh(install): rework install.sh script (#108) --- gorgone/inputvars.env | 27 + gorgone/install.sh | 641 +++++++---- gorgone/install/functions | 1122 +++++++++++++++++++ gorgone/install/inputvars.centos.env | 4 + gorgone/install/inputvars.debian.env | 7 + gorgone/install/inputvars.default.env | 27 + gorgone/install/inputvars.opensuse-leap.env | 5 + gorgone/install/inputvars.ubuntu.env | 7 + gorgone/install/src/centreon-api.yaml | 9 + gorgone/install/src/centreon.yaml | 3 + gorgone/install/src/config.yaml | 3 + gorgone/install/src/gorgoned.logrotate | 10 + gorgone/install/src/gorgoned.sysconfig | 4 + gorgone/install/src/gorgoned.systemd | 33 + gorgone/install/src/instGorgone.conf | 22 + gorgone/sourceInstall/functions | 1038 ----------------- gorgone/sourceInstall/vars | 52 - 17 files changed, 1698 insertions(+), 1316 deletions(-) create mode 100644 gorgone/inputvars.env create mode 100755 gorgone/install/functions create mode 100644 gorgone/install/inputvars.centos.env create mode 100644 gorgone/install/inputvars.debian.env create mode 100755 gorgone/install/inputvars.default.env create mode 100644 gorgone/install/inputvars.opensuse-leap.env create mode 100644 gorgone/install/inputvars.ubuntu.env create mode 100644 gorgone/install/src/centreon-api.yaml create mode 100644 gorgone/install/src/centreon.yaml create mode 100644 gorgone/install/src/config.yaml create mode 100644 gorgone/install/src/gorgoned.logrotate create mode 100644 gorgone/install/src/gorgoned.sysconfig create mode 100644 gorgone/install/src/gorgoned.systemd create mode 100644 gorgone/install/src/instGorgone.conf delete mode 100755 gorgone/sourceInstall/functions delete mode 100755 gorgone/sourceInstall/vars diff --git a/gorgone/inputvars.env b/gorgone/inputvars.env new file mode 100644 index 00000000000..b679f114365 --- /dev/null +++ b/gorgone/inputvars.env @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# +# Centreon installation variables user specific values. +# Uncomment variables to define values. Values are defaults. + +# INSTALLATION_TYPE="central" +# GORGONE_USER="centreon-gorgone" +# GORGONE_GROUP="centreon-gorgone" +# GORGONE_ETC_DIR="/etc/centreon-gorgone" +# GORGONE_LOG_DIR="/var/log/centreon-gorgone" +# GORGONE_VARLIB_DIR="/var/lib/centreon-gorgone" +# GORGONE_CACHE_DIR="/var/cache/centreon-gorgone" +# CENTREON_USER="centreon" +# CENTREON_HOME="/var/spool/centreon" +# CENTREON_ETC_DIR="/etc/centreon" +# CENTREON_SERVICE="centreon" +# ENGINE_USER="centreon-engine" +# ENGINE_GROUP="centreon-engine" +# BROKER_USER="centreon-broker" +# BROKER_GROUP="centreon-broker" +# BINARY_DIR="/usr/bin" +# PERL_BINARY="/usr/bin/perl" +# SYSTEMD_ETC_DIR="/etc/systemd/system" +# SYSCONFIG_ETC_DIR="/etc/sysconfig" +# LOGROTATED_ETC_DIR="/etc/logrotate.d" +# TMP_DIR="/tmp/centreon-setup" +# LOG_FILE="$BASE_DIR/log/install.log" \ No newline at end of file diff --git a/gorgone/install.sh b/gorgone/install.sh index ce455a46184..149f46d5773 100755 --- a/gorgone/install.sh +++ b/gorgone/install.sh @@ -1,12 +1,17 @@ #!/bin/bash #---- -## @Synopsis Install Script for Centreon Gorgone module -## @Copyright Copyright 2008, Guillaume Watteeux -## @Copyright Copyright 2008-2020, Centreon -## @License GPL : http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt +## @Synopsis Install Script for Gorgone project +## @Copyright Copyright 2008, Guillaume Watteeux +## @Copyright Copyright 2008-2021, Centreon +## @License GPL : http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt +## Centreon Install Script #---- ## Centreon is developed with GPL Licence 2.0 +## +## GPL License: http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt +## ## Developed by : Julien Mathis - Romain Le Merlus +## Contributors : Guillaume Watteeux - Maximilien Bersoult ## ## This program is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License @@ -20,287 +25,471 @@ ## ## For information : infos@centreon.com # -# @TODO -# - add the silent mode install -# - add the upgrade mode -# - add the kill and clean system #---- -## define the available options and usage of the script -## @param the chosen option +## Usage information for install.sh +## @Sdtout Usage information #---- -display_help() { - echo - echo -e "Usage: $0 [option...]" - echo -e "\t-i\tRun the installation script\n" - echo -e "\t-f\tRun with file with all variable" +usage() { + local program=$0 + echo -e "Usage: $program" + echo -e " -i\tinstall Gorgone with interactive interface" + echo -e " -u\tupgrade Gorgone specifying the directory of instGorgone.conf file" + echo -e " -s\tinstall/upgrade Gorgone silently" + echo -e " -e\textra variables, 'VAR=value' format (overrides input files)" exit 1 } -#---- -## Test if the file exists, is readable and not empty -## As the functions file has not been imported yet, we need to duplicate it here -## @param file -## @return 0 file exist -## @return 1 file does not exist -#---- -test_file() { - local file="$1" - if [ -r "$file" ] && [ -s "$file" ]; then - return 0 - else - echo -e "The file named '$file' is missing or empty\nPlease check your sources" - exit 1 +## Use TRAPs to call clean_and_exit when user press +## CRTL+C or exec kill -TERM. +trap clean_and_exit SIGINT SIGTERM + +## Valid if you are root +if [ "${FORCE_NO_ROOT:-0}" -ne 0 ]; then + USERID=$(id -u) + if [ "$USERID" != "0" ]; then + echo -e "You must launch this script using a root user" + exit 1 fi -} +fi -## define and check the current source location +## Define where are Gorgone sources BASE_DIR=$(dirname $0) BASE_DIR=$( cd $BASE_DIR; pwd ) -export BASE_DIR - if [ -z "${BASE_DIR#/}" ] ; then - echo -e "You shouldn't install Centreon from the root filesystem folder\nPlease move the sources to another folder" - exit 1 -fi - -## define, test and load functions and vars files -INSTALL_DIR="$BASE_DIR/sourceInstall" -export INSTALL_DIR -test_file "$INSTALL_DIR/functions" -test_file "$INSTALL_DIR/vars" -. $INSTALL_DIR/functions -. $INSTALL_DIR/vars - -## Valid if launched as root -if [ "${FORCE_NO_ROOT:-0}" -ne 0 ]; then - USERID=$(id -u) - if [ "$USERID" != "0" ]; then - echo -e "You must execute the script using root user" - exit 1 - fi + echo -e "You cannot select the filesystem root folder" + exit 1 fi +INSTALL_DIR="$BASE_DIR/install" -## get and check the chosen script's option -process_install="0" +_tmp_install_opts="0" silent_install="0" -user_install_vars="" +upgrade="0" -if [ "$#" -eq 0 ] ; then - echo -e "Install : please select one option" - display_help - exit 1 -fi - -while getopts "if:h" Options; +## Get options +while getopts "isu:e:h" Options do case ${Options} in i ) silent_install="0" - process_install="1" + _tmp_install_opts="1" ;; - f ) silent_install="1" - user_install_vars="${OPTARG}" - process_install="1" + s ) silent_install="1" + _tmp_install_opts="1" ;; - \?|h ) display_help; - exit 0 + u ) silent_install="0" + UPGRADE_FILE="${OPTARG%/}" + upgrade="1" + _tmp_install_opts="1" ;; - * ) display_help ; - exit 1 + e ) env_opts+=("$OPTARG") ;; + \?|h) usage ; exit 0 ;; + * ) usage ; exit 1 ;; esac done +shift $((OPTIND -1)) - -if [ "$process_install" -ne 1 ]; then - echo "Install : option not found" +if [ "$_tmp_install_opts" -eq 0 ] ; then + usage exit 1 -fi; +fi -## init LOG_FILE +INSTALLATION_MODE="install" +if [ ! -z "$upgrade" ] && [ "$upgrade" -eq 1 ]; then + INSTALLATION_MODE="upgrade" +fi + +## Load default input variables +source $INSTALL_DIR/inputvars.default.env +## Load all functions used in this script +source $INSTALL_DIR/functions + +## Define a default log file +if [ ! -z $LOG_FILE ] ; then + LOG_FILE="$BASE_DIR/log/install.log" +fi +LOG_DIR=$(dirname $LOG_FILE) [ ! -d "$LOG_DIR" ] && mkdir -p "$LOG_DIR" + +## Init LOG_FILE if [ -e "$LOG_FILE" ] ; then - mv "$LOG_FILE" "$LOG_FILE.`date +%Y%m%d-%H%M%S`" + mv "$LOG_FILE" "$LOG_FILE.`date +%Y%m%d-%H%M%S`" fi ${CAT} << __EOL__ > "$LOG_FILE" __EOL__ -## Init GREP,CAT,SED,CHMOD,CHOWN variables -log "INFO" "Check mandatory binaries" -define_specific_binary_vars - -## display header banner -${CAT} << __EOT__ +# Checking installation script requirements +BINARIES="rm cp mv chmod chown echo more mkdir find grep cat sed tr" +binary_fail="0" +# For the moment, I check if all binary exists in PATH. +# After, I must look a solution to use complet path by binary +for binary in $BINARIES; do + if [ ! -e ${binary} ] ; then + pathfind_ret "$binary" "PATH_BIN" + if [ "$?" -ne 0 ] ; then + echo_error "${binary}" "FAILED" + binary_fail=1 + fi + fi +done -############################################################################### -# # -# # -# Centreon Gorgone daemon module # -# # -# # -############################################################################### +## Script stop if one binary is not found +if [ "$binary_fail" -eq 1 ] ; then + echo_info "Please check failed binary and retry" + exit 1 +else + echo_success "Script requirements" "OK" +fi -__EOT__ +## Search distribution and version +if [ -z "$DISTRIB" ] || [ -z "$DISTRIB_VERSION" ] ; then + find_os +fi +echo_info "Found distribution" "$DISTRIB $DISTRIB_VERSION" -if [ "$silent_install" -ne 1 ] ; then - ## displaying the license - echo -e "\nPlease read the license.\\n\\tPress enter to continue." - read - tput clear - more "$BASE_DIR/LICENSE.txt" - - yes_no_default "Do you accept the license ?" - if [ "$?" -ne 0 ] ; then - echo_info "As you did not accept the license, we cannot continue." - log "INFO" "Installation aborted - License not accepted" - exit 1 - else - log "INFO" "Accepted the license" - fi -else - . $user_install_vars +## Load specific variables based on distribution +if [ -f $INSTALL_DIR/inputvars.$DISTRIB.env ]; then + echo_info "Loading distribution specific input variables" "install/inputvars.$DISTRIB.env" + source $INSTALL_DIR/inputvars.$DISTRIB.env fi -## Test all binaries -BINARIES="rm cp mv ${CHMOD} ${CHOWN} echo more mkdir find ${GREP} ${CAT} ${SED}" +## Load specific variables based on version +if [ -f $INSTALL_DIR/inputvars.$DISTRIB.$DISTRIB_VERSION.env ]; then + echo_info "Loading version specific input variables" "install/inputvars.$DISTRIB.$DISTRIB_VERSION.env" + source $INSTALL_DIR/inputvars.$DISTRIB.$DISTRIB_VERSION.env +fi -line="------------------------------------------------------------------------" +## Load specific variables defined by user +if [ -f $INSTALL_DIR/../inputvars.env ]; then + echo_info "Loading user specific input variables" "inputvars.env" + source $INSTALL_DIR/../inputvars.env +fi -echo "$line" -echo -e "\tChecking all needed binaries" -echo "$line" +## Load previous installation input variables if upgrade +if [ "$upgrade" -eq 1 ] ; then + test_file "$UPGRADE_FILE" "Gorgone upgrade file" + if [ "$?" -eq 0 ] ; then + echo_info "Loading previous installation input variables" "$UPGRADE_FILE" + source $UPGRADE_FILE + else + echo_error "Missing previous installation input variables" "FAILED" + echo_info "Either specify it in command line or using UPGRADE_FILE input variable" + exit 1 + fi +fi -binary_fail="0" -for binary in $BINARIES; do - if [ ! -e ${binary} ] ; then - pathfind "$binary" - if [ "$?" -eq 0 ] ; then - echo_success "${binary}" "$ok" - else - echo_failure "${binary}" "$fail" - log "ERROR" "\$binary not found in \$PATH" - binary_fail=1 +## Load variables provided in command line +for env_opt in "${env_opts[@]}"; do + if [[ "${env_opt}" =~ .+=.+ ]] ; then + variable=$(echo $env_opt | cut -f1 -d "=") + value=$(echo $env_opt | cut -f2 -d "=") + if [ ! -z "$variable" ] && [ ! -z "$value" ] ; then + echo_info "Loading command line input variables" "${variable}=${value}" + eval ${variable}=${value} fi - else - echo_success "${binary}" "$ok" fi done -## Script stop if one binary wasn't found -if [ "$binary_fail" -eq 1 ] ; then - echo_failure "Please check failing binary and retry" - echo -e "\tThe logs are available in this file :\n$LOG_FILE" - exit 1 +## Check installation mode +if [ -z "$INSTALLATION_TYPE" ] ; then + echo_error "Installation mode" "NOT DEFINED" + exit 1 +fi +if [[ ! "${INSTALLATION_TYPE}" =~ ^central|poller$ ]] ; then + echo_error "Installation mode" "$INSTALLATION_TYPE" + exit 1 +fi +echo_info "Installation type" "$INSTALLATION_TYPE" +echo_info "Installation mode" "$INSTALLATION_MODE" + +## Check space of tmp dir +check_tmp_disk_space +if [ "$?" -eq 1 ] ; then + if [ "$silent_install" -eq 1 ] ; then + purge_centreon_tmp_dir "silent" + else + purge_centreon_tmp_dir + fi fi -echo -e "\n$line" -echo -e "\tChecking the mandatory folders" -echo -e "$line" - -## check filesystem space -check_disk_space +## Installation is interactive if [ "$silent_install" -ne 1 ] ; then - allow_creation_of_missing_resources -else - CREATION_ALLOWED=1 - export CREATION_ALLOWED + echo -e "\n" + echo_info "Welcome to Centreon installation script!" + yes_no_default "Should we start?" "$yes" + if [ "$?" -ne 0 ] ; then + echo_info "Exiting" + exit 1 + fi fi -## define destination folders -locate_gorgone_logdir -locate_gorgone_varlib -locate_gorgone_etcdir -locate_gorgone_bindir -locate_gorgone_perldir -locate_cron_d -locate_logrotate_d -locate_system_d -locate_sysconfig +# Start installation -echo "$line" -echo -e "\tChecking the required users" -echo "$line" +ERROR_MESSAGE="" -## create gorgone user -check_gorgone_group -check_gorgone_user +# Centreon installation requirements +echo_title "Centreon installation requirements" -echo -e "\n$line" -echo -e "\tAdding Gorgone user to the mandatory folders" -echo -e "$line" +if [[ "${INSTALLATION_TYPE}" =~ ^central|poller$ ]] ; then + # System + test_dir_from_var "LOGROTATED_ETC_DIR" "Logrotate directory" + test_dir_from_var "SYSTEMD_ETC_DIR" "SystemD directory" + test_dir_from_var "SYSCONFIG_ETC_DIR" "Sysconfig directory" + test_dir_from_var "BINARY_DIR" "System binary directory" -change_rights "$GORGONE_USER" "$GORGONE_GROUP" "775" "$GORGONE_LOG" -change_rights "$GORGONE_USER" "$GORGONE_GROUP" "775" "$GORGONE_VARLIB" + ## Perl information + find_perl_info + test_file_from_var "PERL_BINARY" "Perl binary" + test_dir_from_var "PERL_LIB_DIR" "Perl libraries directory" +fi -#---- -## installation of Gorgone files -#---- -echo "$line" -echo -e "\tInstalling Gorgone daemon" -echo "$line" +if [ ! -z "$ERROR_MESSAGE" ] ; then + echo_error "Installation requirements" "FAILED" + echo_error "\nErrors:" + echo_error "$ERROR_MESSAGE" + exit 1 +fi + +echo_success "Installation requirements" "OK" + +## Gorgone information +echo_title "Gorgone information" + +if [[ "${INSTALLATION_TYPE}" =~ ^central|poller$ ]] ; then + test_var_and_show "GORGONE_USER" "Gorgone user" + test_var_and_show "GORGONE_GROUP" "Gorgone group" + test_var_and_show "GORGONE_ETC_DIR" "Gorgone configuration directory" + test_var_and_show "GORGONE_LOG_DIR" "Gorgone log directory" + test_var_and_show "GORGONE_VARLIB_DIR" "Gorgone variable library directory" + test_var_and_show "GORGONE_CACHE_DIR" "Gorgone cache directory" + test_var_and_show "CENTREON_USER" "Centreon user" + test_var_and_show "CENTREON_HOME" "Centreon home directory" + test_var_and_show "CENTREON_ETC_DIR" "Centreon configuration directory" + test_var_and_show "CENTREON_SERVICE" "Centreon service" + test_var_and_show "ENGINE_USER" "Engine user" + test_var_and_show "ENGINE_GROUP" "Engine group" + test_var_and_show "BROKER_USER" "Broker user" + test_var_and_show "BROKER_GROUP" "Broker group" +fi -# modify rights on etc folder -${CHMOD} -R "775" "$GORGONE_ETC" -${CHOWN} -R "$GORGONE_USER:$GORGONE_GROUP" "$GORGONE_ETC" -if [ $? -ne 0 ] ; then - echo_failure "$(gettext "Cannot modify the owner of the files in $GORGONE_ETC folder")" "$fail" +if [ ! -z "$ERROR_MESSAGE" ] ; then + echo_error "\nErrors:" + echo_error "$ERROR_MESSAGE" + exit 1 fi -# modify the gorgoned file to take in account the chosen user path -change_environment_file_path +if [ "$silent_install" -ne 1 ] ; then + yes_no_default "Everything looks good, proceed to installation?" + if [ "$?" -ne 0 ] ; then + purge_centreon_tmp_dir "silent" + exit 1 + fi +fi -## Copy the files in destination folders and modify rights -copy_and_modify_rights "$BASE_DIR/config/systemd" "gorgoned-service" "$SYSTEM_D" "gorgoned.service" "664" -copy_and_modify_rights "$BASE_DIR/config/systemd" "gorgoned-sysconfig" "$SYSCONFIG" "gorgoned" "664" -copy_and_modify_rights "$BASE_DIR/config/logrotate" "gorgoned" "$LOGROTATE_D" "gorgoned" "775" -copy_and_modify_rights "$BASE_DIR/packaging" "config.yaml" "$GORGONE_ETC" "config.yaml" "755" -copy_and_modify_rights "$BASE_DIR" "gorgoned" "$GORGONE_BINDIR" "gorgoned" "755" -copy_and_modify_rights "$BASE_DIR/contrib" "gorgone_config_init.pl" "$GORGONE_BINDIR" "gorgone_config_init.pl" "775" +# Start installation -## Recursively copy perl files -cp -R "$BASE_DIR/gorgone" "$GORGONE_PERL" -${CHMOD} -R "775" "$GORGONE_PERL/gorgone" -${CHOWN} -R "$GORGONE_USER:$GORGONE_GROUP" "$GORGONE_PERL/gorgone" +## Build files +echo_title "Build files" +echo_line "Copying files to '$TMP_DIR'" + +if [ -d $TMP_DIR ] ; then + mv $TMP_DIR $TMP_DIR.`date +%Y%m%d-%k%m%S` +fi + +create_dir "$TMP_DIR/source" + +if [[ "${INSTALLATION_TYPE}" =~ ^central|poller$ ]] ; then + { + copy_dir "$BASE_DIR/config" "$TMP_DIR/source/" && + copy_dir "$BASE_DIR/gorgone" "$TMP_DIR/source/" && + copy_dir "$BASE_DIR/install" "$TMP_DIR/source/" && + copy_file "$BASE_DIR/gorgoned" "$TMP_DIR/source/" + } || { + echo_error_on_line "FAILED" + if [ ! -z "$ERROR_MESSAGE" ] ; then + echo_error "\nErrors:" + echo_error "$ERROR_MESSAGE" + fi + purge_centreon_tmp_dir "silent" + exit 1 + } +fi +echo_success_on_line "OK" + +echo_line "Replacing macros" +eval "echo \"$(cat "$TMP_DIR/source/install/src/instGorgone.conf")\"" > $TMP_DIR/source/install/src/instGorgone.conf +if [[ "${INSTALLATION_TYPE}" =~ ^central|poller$ ]] ; then + { + replace_macro "install/src" + } || { + echo_error_on_line "FAILED" + if [ ! -z "$ERROR_MESSAGE" ] ; then + echo_error "\nErrors:" + echo_error "$ERROR_MESSAGE" + fi + purge_centreon_tmp_dir "silent" + exit 1 + } +fi +echo_success_on_line "OK" + +test_user "$GORGONE_USER" +if [ $? -ne 0 ]; then + { + ### Create user and group + create_dir "$GORGONE_VARLIB_DIR" && + create_group "$GORGONE_GROUP" && + create_user "$GORGONE_USER" "$GORGONE_GROUP" "$GORGONE_VARLIB_DIR" && + set_ownership "$GORGONE_VARLIB_DIR" "$GORGONE_USER" "$GORGONE_GROUP" && + set_permissions "$GORGONE_VARLIB_DIR" "755" + } || { + if [ ! -z "$ERROR_MESSAGE" ] ; then + echo_error "\nErrors:" + echo_error "$ERROR_MESSAGE" + fi + purge_centreon_tmp_dir "silent" + exit 1 + } +fi + +echo_line "Building installation tree" +BUILD_DIR="$TMP_DIR/build" +create_dir "$BUILD_DIR" + +if [[ "${INSTALLATION_TYPE}" =~ ^central|poller$ ]] ; then + { + ### Configuration diretories and base file + create_dir "$BUILD_DIR/$GORGONE_ETC_DIR" "$GORGONE_USER" "$GORGONE_GROUP" "755" && + create_dir "$BUILD_DIR/$GORGONE_ETC_DIR/config.d" "$GORGONE_USER" "$GORGONE_GROUP" "775" && + create_dir "$BUILD_DIR/$GORGONE_ETC_DIR/config.d/cron.d" "$GORGONE_USER" "$GORGONE_GROUP" "775" && + copy_file "$TMP_DIR/source/install/src/config.yaml" "$BUILD_DIR/$GORGONE_ETC_DIR/config.yaml" \ + "$GORGONE_USER" "$GORGONE_GROUP" && + + ### Install save file + copy_file "$TMP_DIR/source/install/src/instGorgone.conf" \ + "$BUILD_DIR/$GORGONE_ETC_DIR/instGorgone.conf" \ + "$GORGONE_USER" "$GORGONE_GROUP" "644" && + + ### Log directory + create_dir "$BUILD_DIR/$GORGONE_LOG_DIR" "$GORGONE_USER" "$GORGONE_GROUP" "755" && + + ### Cache directories + create_dir "$BUILD_DIR/$GORGONE_CACHE_DIR" "$GORGONE_USER" "$GORGONE_GROUP" "755" && + create_dir "$BUILD_DIR/$GORGONE_CACHE_DIR/autodiscovery" "$GORGONE_USER" "$GORGONE_GROUP" "755" + } || { + echo_error_on_line "FAILED" + if [ ! -z "$ERROR_MESSAGE" ] ; then + echo_error "\nErrors:" + echo_error "$ERROR_MESSAGE" + fi + purge_centreon_tmp_dir "silent" + exit 1 + } +fi +echo_success_on_line "OK" + +## Install files +echo_title "Install builded files" +echo_line "Copying files from '$TMP_DIR' to final directory" +copy_dir "$BUILD_DIR/*" "/" +if [ "$?" -ne 0 ] ; then + echo_error_on_line "FAILED" + if [ ! -z "$ERROR_MESSAGE" ] ; then + echo_error "\nErrors:" + echo_error "$ERROR_MESSAGE" + fi + purge_centreon_tmp_dir "silent" + exit 1 +fi +echo_success_on_line "OK" + +## Install remaining files +echo_title "Install remaining files" + +if [[ "${INSTALLATION_TYPE}" =~ ^central|poller$ ]] ; then + ### Configurations + copy_file_no_replace "$TMP_DIR/source/install/src/centreon.yaml" \ + "$GORGONE_ETC_DIR/config.d/30-centreon.yaml" \ + "Centreon configuration" \ + "$GORGONE_USER" "$GORGONE_GROUP" "644" + copy_file_no_replace "$TMP_DIR/source/install/src/centreon-api.yaml" \ + "$GORGONE_ETC_DIR/config.d/31-centreon-api.yaml" \ + "Centreon API configuration" \ + "$GORGONE_USER" "$GORGONE_GROUP" "644" + + ### Perl libraries + copy_dir "$TMP_DIR/source/gorgone" "$PERL_LIB_DIR/gorgone" + + ### Gorgoned binary + copy_file "$TMP_DIR/source/gorgoned" "$BINARY_DIR" + + ### Systemd files + restart_gorgoned="0" + copy_file "$TMP_DIR/source/install/src/gorgoned.systemd" \ + "$SYSTEMD_ETC_DIR/gorgoned.service" && restart_gorgoned="1" + copy_file_no_replace "$TMP_DIR/source/install/src/gorgoned.sysconfig" "$SYSCONFIG_ETC_DIR/gorgoned" \ + "Sysconfig Gorgoned configuration" && restart_gorgoned="1" + + ### Logrotate configuration + copy_file_no_replace "$TMP_DIR/source/install/src/gorgoned.logrotate" "$LOGROTATED_ETC_DIR/gorgoned" \ + "Logrotate Gorgoned configuration" +fi + +if [ ! -z "$ERROR_MESSAGE" ] ; then + echo_error "\nErrors:" + echo_error "$ERROR_MESSAGE" + ERROR_MESSAGE="" +fi + +## Update groups memberships +echo_title "Update groups memberships" +if [[ "${INSTALLATION_TYPE}" =~ ^central|poller$ ]] ; then + add_user_to_group "$GORGONE_USER" "$BROKER_GROUP" + add_user_to_group "$GORGONE_USER" "$ENGINE_GROUP" + add_user_to_group "$ENGINE_USER" "$GORGONE_GROUP" + add_user_to_group "$BROKER_USER" "$GORGONE_GROUP" +fi + +if [ ! -z "$ERROR_MESSAGE" ] ; then + echo_error "\nErrors:" + echo_error "$ERROR_MESSAGE" + ERROR_MESSAGE="" +fi + +## Retrieve Centreon SSH key +if [ ! -d "$GORGONE_VARLIB_DIR/.ssh" ] && [ -d "$CENTREON_HOME/.ssh" ] ; then + echo_title "Retrieve Centreon SSH key" + copy_file "$CENTREON_HOME/.ssh/*" "$GORGONE_VARLIB_DIR/.ssh" "$GORGONE_USER" "$GORGONE_GROUP" && + set_permissions "$GORGONE_VARLIB_DIR/.ssh/id_rsa" "600" +fi + +## Configure and restart services +echo_title "Configure and restart services" +if [[ "${INSTALLATION_TYPE}" =~ ^central|poller$ ]] ; then + ### Gorgoned + enable_service "gorgoned" + + if [ "$restart_gorgoned" -eq 1 ] ; then + reload_daemon + restart_service "gorgoned" + fi +fi + +if [ ! -z "$ERROR_MESSAGE" ] ; then + echo_error "\nErrors:" + echo_error "$ERROR_MESSAGE" + ERROR_MESSAGE="" +fi + +## Purge working directories +purge_centreon_tmp_dir "silent" + +# End +echo_title "You're done!" +echo_info "" +echo_info "Take a look at the documentation" +echo_info "https://docs.centreon.com/current." +echo_info "Thanks for using Gorgone!" +echo_info "Follow us on https://github.com/centreon/centreon-gorgone!" -#---- -## starting the service -#---- -echo "$line" -echo -e "\tStarting gorgoned.service" -echo "$line" - -## check the OS and launch the service -foundOS="" -find_OS "foundOS" -install_init_service "gorgoned" - -## display footer banner -${CAT} << __EOT__ - -############################################################################### -# # -# Thanks for using Gorgone. # -# ----------------------- # -# # -# Please add the configuration in a file in the folder : # -# $GORGONE_ETC/config.d # -# Then start the gorgoned.service # -# # -# You can read the documentation available here : # -# https://github.com/centreon/centreon-gorgone/blob/master/README.md # -# # -# ------------------------------------------------------------------ # -# # -# Report bugs at https://github.com/centreon/centreon-gorgone/issues # -# # -# Contact : contact@centreon.com # -# http://www.centreon.com # -# # -# ----------------------- # -# For security issues, please read our security policy # -# https://github.com/centreon/centreon-gorgone/security/policy # -# # -############################################################################### - -__EOT__ exit 0 diff --git a/gorgone/install/functions b/gorgone/install/functions new file mode 100755 index 00000000000..97e5495f60b --- /dev/null +++ b/gorgone/install/functions @@ -0,0 +1,1122 @@ +#!/bin/bash +#---- +## @Synopsis This file contains functions to be used by Gorgone install script +## @Copyright Copyright 2008, Guillaume Watteeux +## @Copyright Copyright 2008-2021, Centreon +## @Licence GPLv2 +## This file contains functions to be used by Centreon install script +#---- +## Centreon is developed with GPL Licence 2.0 +## +## GPL License: http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt +## +## Developed by : Julien Mathis - Romain Le Merlus +## Contributors : Guillaume Watteeux - Maximilien Bersoult +## +## 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, or (at your option) any later version. +## +## 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. +## +## For information : infos@centreon.com + +## VARS +yes="y" +no="n" + +## COLOR FUNCTIONS +RES_COL="70" +MOVE_TO_COL="\\033[${RES_COL}G" +SETCOLOR_INFO="\\033[1;38m" +SETCOLOR_SUCCESS="\\033[1;32m" +SETCOLOR_ERROR="\\033[1;31m" +SETCOLOR_WARNING="\\033[1;33m" +SETCOLOR_NORMAL="\\033[0;39m" + +#---- +## echo_title +## Print string in a title way. Also log in log file. +## @param string to display +## @stdout titled string +#---- +echo_title() { + [ "$silent_install" -eq 0 ] && echo -e "\n" + [ "$silent_install" -eq 0 ] && echo -e "$1" + [ "$silent_install" -eq 0 ] && printf '%*s\n' "${COLUMNS:-$(tput cols)}" '' | tr ' ' - + log "INFO" "$1" +} + +#---- +## echo_line +## Print message to screen and keep position, and in log file. +## @param message +## @stdout message +#---- +echo_line() { + [ "$silent_install" -eq 0 ] && echo -en "${1}" + log "INFO" "$1" +} + +#---- +## echo_success_on_line +## Print message to screen on right-end side, and in log file. +## @param message +## @stdout message +#---- +echo_success_on_line() { + [ "$silent_install" -eq 0 ] && echo -e "${MOVE_TO_COL}${SETCOLOR_SUCCESS}${1}${SETCOLOR_NORMAL}" + log "SUCCESS" "$1" +} + +#---- +## echo_succeecho_error_on_liness_on_line +## Print message to screen on right-end side, and in log file. +## @param message +## @stdout message +#---- +echo_error_on_line() { + [ "$silent_install" -eq 0 ] && echo -e "${MOVE_TO_COL}${SETCOLOR_ERROR}${1}${SETCOLOR_NORMAL}" + log "ERROR" "$1" +} + +#---- +## echo_info +## Print info message to screen and in log file. +## @param message +## @param type info (ex: INFO, username...) +## @stdout info message +#---- +echo_info() { + [ "$silent_install" -eq 0 ] && echo -e "${1}${MOVE_TO_COL}${SETCOLOR_INFO}${2}${SETCOLOR_NORMAL}" + log "INFO" "$1 : $2" +} + +#---- +## echo_success +## Print success message to screen and in log file. +## @param message +## @param word to specify success (ex: OK) +## @stdout success message +#---- +echo_success() { + [ "$silent_install" -eq 0 ] && echo -e "${1}${MOVE_TO_COL}${SETCOLOR_SUCCESS}${2}${SETCOLOR_NORMAL}" + log "SUCCESSS" "$1 : $2" +} + +#---- +## echo_warning +## Print warning message to screen and in log file. +## @param message +## @param word to specify warning (ex: warn) +## @stdout warning message +#---- +echo_warning() { + [ "$silent_install" -eq 0 ] && echo -e "${1}${MOVE_TO_COL}${SETCOLOR_WARNING}${2}${SETCOLOR_NORMAL}" + log "WARNING" "$1 : $2" +} + +#---- +## echo_error +## Print failure message to screen and in log file. +## @param message +## @param word to specify failure (ex: fail) +## @stdout failure message +#---- +echo_error() { + [ "$silent_install" -eq 0 ] && echo -e "${1}${MOVE_TO_COL}${SETCOLOR_ERROR}${2}${SETCOLOR_NORMAL}" + log "ERROR" "$1 : $2" +} + +#---- +## log +## Add message in log file +## @param type of message level (debug, info, ...) +## @param message +## @globals LOG_FILE +#---- +log() { + local type="$1" + shift + local message="$@" + echo -e "["`date +"%m-%d-%y %T"`"] [$type] $message" >> $LOG_FILE +} + +#---- +## trim +## Trim whitespaces and tabulations +## @param string to trim +## @return string +#---- +trim() { + echo "$1" | sed 's/^[ \t]*\(.*\)[ \t]*$/\1/' +} + +#---- +## yes_no_default +## Create a question with yes/no possiblity. Uses "no" response by default. +## @param message to print +## @param default response (default to no) +## @return 0 yes +## @return 1 no +#---- +yes_no_default() { + local message=$1 + local default=${2:-$no} + local res="not_define" + + while [ "$res" != "$yes" ] && [ "$res" != "$no" ] && [ ! -z "$res" ] ; do + echo -en "\n$message" + [ "$default" = "$yes" ] && echo " [Y/n]" + [ "$default" = "$no" ] && echo " [y/N]" + echo -en "> " + read res + [ -z "$res" ] && res="$default" + done + if [ "$res" = "$yes" ] ; then + return 0 + else + return 1 + fi +} + +#---- +## add_error_message +## Add an error message in global variable ERROR_MESSAGE. +## See this as an exceptions management. Used by test_* functions. +## @param message +## @globals ERROR_MESSAGE +#---- +add_error_message() { + local append="" + local message="$1" + + if [ ! -z "$ERROR_MESSAGE" ] ; then + append="\n" + fi + ERROR_MESSAGE="${ERROR_MESSAGE}$append $message" +} + +#---- +## test_var +## Test a global variable valueexists. +## @param global variable (as string) +## @param message to display as part of the returned error +## @return 0 show the message and value +## @return 1 add an error using add_error_message +#---- +test_var() { + local var="$1" + local message="$2" + local value=$(eval echo \$$var) + + if [ -z "$value" ] ; then + add_error_message "Missing value for variable '$var' ($message)" + return 1 + fi + + return 0 +} + +#---- +## test_var_and_show +## Test a global variable value exists and show this value in a echo_info format. +## @param global variable (as string) +## @param message to display as part of the echo_info or returned error +## @return 0 show the message and value +## @return 1 add an error using add_error_message +#---- +test_var_and_show() { + local var="$1" + local message="$2" + local value=$(eval echo \$$var) + + if [ -z "$value" ] ; then + add_error_message "Missing value for variable '$var' ($message)" + return 1 + fi + + echo_info "$message ($var)" "$value" + + return 0 +} + +#---- +## test_file +## Test a file existence. +## @param file absolute path +## @param message to display as part of the returned error +## @return 0 file found +## @return 1 add an error using add_error_message +#---- +test_file() { + local file="$1" + local message="$2" + + if [ -z "$file" ] ; then + add_error_message "Missing value for test_file function" + return 1 + fi + if [ ! -f $file ] ; then + add_error_message "Cannot find file '$file' ($message)" + return 1 + fi + + return 0 +} + +#---- +## test_file_from_var +## Test a file existence from a global variable. +## @param global variable (as string) +## @param message to display as part of the returned error +## @return 0 file found +## @return 1 add an error using add_error_message +#---- +test_file_from_var() { + local var="$1" + local message="$2" + local file=$(eval echo \$$var) + + if [ -z "$file" ] ; then + add_error_message "Missing value for variable '$var' ($message)" + return 1 + fi + if [ ! -f $file ] ; then + add_error_message "Cannot find file '$file' from variable '$var' ($message)" + return 1 + fi + + return 0 +} + +#---- +## test_dir +## Test a directory existence. +## @param directory absolute path +## @param message to display as part of the returned error +## @return 0 directory found +## @return 1 add an error using add_error_message +#---- +test_dir() { + local dir="$1" + local message="$2" + + if [ -z "$dir" ] ; then + add_error_message "Missing value for test_dir function" + return 1 + fi + if [ ! -d "$dir" ] ; then + add_error_message "Cannot find directory '$dir' ($message)" + return 1 + fi + + return 0 +} + +#---- +## test_dir_from_var +## Test a directory existence from a global variable. +## @param global variable (as string) +## @param message to display as part of the returned error +## @return 0 directory found +## @return 1 add an error using add_error_message +#---- +test_dir_from_var() { + local var="$1" + local message="$2" + local dir=$(eval echo \$$var) + + if [ -z "$dir" ] ; then + add_error_message "Missing value for variable '$var' ($message)" + return 1 + fi + if [ ! -d "$dir" ] ; then + add_error_message "Cannot find directory '$dir' from variable '$var' ($message)" + return 1 + fi + + return 0 +} + +#---- +## test_user_from_var +## Test a user existence from a global variable. +## @param global variable (as string) +## @param message to display as part of the returned error +## @return 0 user found +## @return 1 add an error using add_error_message +#---- +test_user_from_var() { + local var="$1" + local message="$2" + local user=$(eval echo \$$var) + + if [ -z "$user" ] ; then + add_error_message "Missing value for variable '$var' ($message)" + return 1 + fi + grep "^$user:" /etc/passwd &>/dev/null + if [ $? -ne 0 ] ; then + add_error_message "Cannot find user '$user' from variable '$var' ($message)" + return 1 + fi + + return 0 +} + +#---- +## test_group_from_var +## Test a group existence from a global variable. +## @param global variable (as string) +## @param message to display as part of the returned error +## @return 0 group found +## @return 1 add an error using add_error_message +#---- +test_group_from_var() { + local var="$1" + local message="$2" + local group=$(eval echo \$$var) + + if [ -z "$group" ] ; then + add_error_message "Missing value for variable '$var' ($message)" + return 1 + fi + grep "^$group:" /etc/group &>/dev/null + if [ $? -ne 0 ] ; then + add_error_message "Cannot find group '$group' from variable '$var' ($message)" + return 1 + fi + + return 0 +} + +#---- +## create_dir +## Create a directory if it does not exist. +## @param directory absolute path +## @param user to set ownership (optional) +## @param group to set ownership (optional) +## @param mode to set permisions (optional) +## @return 0 directory created +## @return 1 error message using echo_error +#---- +create_dir() { + local dirname="$1" + local user="$2" + local group="$3" + local mode="$4" + + if [ ! -d "$dirname" ] ; then + result="$(mkdir -p "$dirname" > /dev/null)" + if [ $? -ne 0 ] ; then + add_error_message "Could not create directory '$dirname': $result" + return 1 + fi + fi + if [ ! -z "$user" ] && [ ! -z "$group" ] ; then + set_ownership "$dirname" "$user" "$group" + [ $? -ne 0 ] && return 1 + fi + if [ ! -z "$mode" ] ; then + set_permissions "$dirname" "$mode" + [ $? -ne 0 ] && return 1 + fi + + return 0 +} + +#---- +## delete_file +## Delete a file or multiple files if wildcard specified. +## @param file absolute path +## @return 0 file deleted +## @return 1 error message using echo_error +#---- +delete_file() { + local file="$1" + + if [ ! -f "$file" ] && [[ ! "$file" =~ \*$ ]] ; then + echo_error "Not a file '$file'" "FAILED" + return 1 + else + result="$(rm -f $file 2>&1 > /dev/null)" + if [ $? -ne 0 ] ; then + echo_error "Could not delete file '$file'" "FAILED" + echo_error "$result" + return 1 + fi + fi + + return 0 +} + +#---- +## copy_file +## Copy a file or multiple files (using wildcard) to a defined location. +## Simplistic but handles the needed cases. +## @param source, unique file absolute path or directory absolute path plus wildcard +## @param destination, can be unique file absolute path or directory absolute path +## @param user to set ownership (optional) +## @param group to set ownership (optional) +## @param mode to set permisions (optional) +## @return 0 copy done successfully +## @return 1 error message using echo_error +#---- +copy_file() { + local file="$1" + local dest="$2" + local user="$3" + local group="$4" + local mode="$5" + + if [ ! -f "$file" ] && [[ ! "$file" =~ \*$ ]] ; then + add_error_message "File '$file' does not exist" + return 1 + else + result="$(cp -f $file $dest 2>&1 > /dev/null)" + if [ $? -ne 0 ] ; then + add_error_message "Copy of '$file' to '$dest' failed: $result" + return 1 + fi + if [ ! -z "$user" ] && [ ! -z "$group" ] ; then + set_ownership "$dest" "$user" "$group" + [ $? -ne 0 ] && return 1 + fi + if [ ! -z "$mode" ] ; then + set_permissions "$dest" "$mode" + [ $? -ne 0 ] && return 1 + fi + fi + + return 0 +} + +#---- +## copy_file_no_replace +## Copy a file to a defined location. +## Simplistic but handles the needed cases. +## @param source, unique file absolute path +## @param destination, unique file absolute path +## @return 0 copy done successfully, returning echo_success message +## @return 1 error message using echo_error +## @return 2 message copied as .new, returning echo_info message +#---- +copy_file_no_replace() { + local file="$1" + local dest="$2" + local message="$3" + local exist=0 + + if [ ! -f "$file" ] ; then + add_error_message "File '$file' does not exist" + return 1 + elif [ -f "$dest" ] ; then + dest=${dest}".new" + exist=1 + fi + result="$(cp -f $file $dest 2>&1 > /dev/null)" + if [ $? -ne 0 ] ; then + add_error_message "Copy of '$file' to '$dest' failed: $result" + return 1 + elif [ $exist == "1" ] ; then + echo_info "$message" "$dest" + return 2 + else + echo_success "$message" "OK" + return 0 + fi +} + +#---- +## copy_dir +## Copy a directory or a directory content (using wildcard) to a defined location. +## Simplistic but handles the needed cases. +## @param source, unique directory absolute path or directory absolute path plus wildcard +## @param destination, directory absolute path +## @param user to set ownership (optional) +## @param group to set ownership (optional) +## @param mode to set permisions (optional) +## @return 0 copy done successfully +## @return 1 error message using echo_error +#---- +copy_dir() { + local dir="$1" + local dest="$2" + local user="$3" + local group="$4" + local mode="$5" + + if [ ! -d "$dir" ] && [[ ! "$dir" =~ \*$ ]] ; then + add_error_message "Directory '$dir' does not exist" + return 1 + else + result="$(cp -rpf $dir $dest 2>&1 > /dev/null)" + if [ $? -ne 0 ] ; then + add_error_message "Copy of '$dir' to '$dest' failed: $result" + return 1 + fi + if [ ! -z "$user" ] && [ ! -z "$group" ] ; then + set_ownership "$dest" "$user" "$group" + [ $? -ne 0 ] && return 1 + fi + if [ ! -z "$mode" ] ; then + set_permissions "$dest" "$mode" + [ $? -ne 0 ] && return 1 + fi + fi + + return 0 +} + +#---- +## create_symlink +## Create a symbolic link for a file. +## @param file absolute path +## @param link absolute path +## @param user to set ownership (optional) +## @param group to set ownership (optional) +## @param mode to set permisions (optional) +## @return 0 directory created +## @return 1 error message using echo_error +#---- +create_symlink() { + local file="$1" + local link="$2" + local user="$3" + local group="$4" + local mode="$5" + + if [ -f "$file" ] && [ ! -L "$link" ]; then + result="$(ln -s "$file" "$link" 2>&1 > /dev/null)" + if [ $? -ne 0 ] ; then + add_error_message "Could not create symbolic link '$file' to '$link': $result" + return 1 + fi + if [ ! -z "$user" ] && [ ! -z "$group" ] ; then + set_ownership "$link" "$user" "$group" + [ $? -ne 0 ] && return 1 + fi + if [ ! -z "$mode" ] ; then + set_permissions "$link" "$mode" + [ $? -ne 0 ] && return 1 + fi + fi + + return 0 +} + +#---- +## set_ownership +## Set the ownership on a unique file or on a directory. +## Simplistic but handles the needed cases. +## @param file or directory +## @param user +## @param group +## @return 0 ownership set successfully +## @return 1 error message using echo_error +#---- +set_ownership() { + local dir_file="$1" + local user="$2" + local group="$3" + + if [ -z "$dir_file" ] ; then + echo_info "File or directory not defined" + return 1 + fi + if [ -f "$dir_file" ] || [[ "$dir_file" =~ \*$ ]] ; then + result="$(chown -h $user:$group $dir_file 2>&1 > /dev/null)" + if [ $? -ne 0 ] ; then + add_error_message "Set ownership '$user:$group' on file '$dir_file' failed: $result" + return 1 + fi + elif [ -d "$dir_file" ] ; then + result="$(chown -R $user:$group $dir_file 2>&1 > /dev/null)" + if [ $? -ne 0 ] ; then + add_error_message "Set ownership '$user:$group' on directory '$dir_file' failed: $result" + return 1 + fi + fi + + return 0 +} + +#---- +## set_permissions +## Set the permissions on a unique file, on a directory and its content (recursively) +## or on files in directories (recursively) if using wildcard. +## Simplistic but handles the needed cases. +## @param file or directory +## @param mode +## @return 0 permissions set successfully +## @return 1 error message using echo_error +#---- +set_permissions() { + local dir_file="$1" + local mode="$2" + + if [ -z "$dir_file" ] ; then + add_error_message "File or directory not defined" + return 1 + fi + if [ -f "$dir_file" ] ; then + result="$(chmod $mode $dir_file 2>&1 > /dev/null)" + if [ $? -ne 0 ] ; then + add_error_message "Set permissions '$mode' on file '$dir_file' failed: $result" + return 1 + fi + elif [ -d "$dir_file" ] ; then + result="$(find $dir_file -type d -print | xargs -I '{}' chmod $mode '{}' 2>&1 > /dev/null)" + if [ $? -ne 0 ] ; then + add_error_message "Set permissions '$mode' on directories in '$dir_file' failed: $result" + return 1 + fi + elif [[ "$dir_file" =~ \*$ ]] ; then + result="$(find $dir_file -type f -print | xargs -I '{}' chmod $mode '{}' 2>&1 > /dev/null)" + if [ $? -ne 0 ] ; then + add_error_message "Set permissions '$mode' on files in '$dir_file' failed: $result" + return 1 + fi + else + add_error_message "Not a file or a directory '$dir_file'" + return 1 + fi + + return 0 +} + +#---- +## create_user +## Create a user if does not exist (checked using test_user). +## @param username +## @param groupname +## @param user's home +## @return 0 user created successfully +## @return 1 creation failed +#---- +create_user() { + local username="$1" + local groupname="$2" + local home="$3" + + test_user $username + if [ $? -ne 0 ]; then + echo_line "Create user '$username'" + result="$(useradd -r -s "/bin/sh" -d "$home" -g "$groupname" "$username" 2>&1 > /dev/null)" + if [ $? -ne 0 ] ; then + echo_error_on_line "FAILED" + add_error_message "Create user '$username' failed: $result" + return 1 + fi + echo_success_on_line "OK" + fi + + return 0 +} + +#---- +## create_group +## Create a group if does not exist (checked using test_group). +## @param groupname +## @return 0 group created successfully +## @return 1 creation failed +#---- +create_group() { + local groupname="$1" + + test_group $groupname + if [ $? -ne 0 ]; then + echo_line "Create group '$groupname'" + result="$(groupadd -r "$groupname" 2>&1 > /dev/null)" + if [ $? -ne 0 ] ; then + echo_error_on_line "FAILED" + add_error_message "Create group '$groupname' failed: $result" + return 1 + fi + echo_success_on_line "OK" + fi + + return 0 +} + +#---- +## test_user +## Test a user existence. +## @param user +## @return 0 user exists +## @return 1 user does not exist +#---- +test_user() { + result="$(grep "^$1:" /etc/passwd 2>&1 > /dev/null)" + return $? +} + +#---- +## test_group +## Test a group existence. +## @param user +## @return 0 group exists +## @return 1 group does not exist +#---- +test_group() { + result="$(grep "^$1:" /etc/group 2>&1 > /dev/null)" + return $? +} + +#---- +## add_user_to_group +## Add a user in a group +## @param user +## @param group +## @return 0 add successfull +## @return 1 add failed +#---- +add_user_to_group() { + local user=$1 + local group=$2 + echo_line "Add user '$user' to group '$group'" + if [ -z "$user" -o -z "$group" ]; then + echo_error_on_line "FAILED" + add_error_message "User or group not defined" + return 1 + fi + test_user $user + if [ $? -ne 0 ]; then + echo_error_on_line "FAILED" + add_error_message "Add user '$user' to group '$group' failed: user '$user' does not exist" + return 1 + fi + test_group $group + if [ $? -ne 0 ]; then + echo_error_on_line "FAILED" + add_error_message "Add user '$user' to group '$group' failed: group '$group' does not exist" + return 1 + fi + + result="$(usermod -a -G $group $user 2>&1 > /dev/null)" + local ret=$? + if [ "$ret" -ne 0 ] ; then + echo_error_on_line "FAILED" + add_error_message "Add user '$user' to group '$group' failed: $result" + else + echo_success_on_line "OK" + fi + return $ret +} + +#---- +## find_perl_info +## Find Perl information. +## @return 0 search done +## @globals PERL_LIB_DIR +#---- +find_perl_info() { + if [ -z $PERL_LIB_DIR ] ; then + PERL_LIB_DIR=$(perl -V:installvendorlib | cut -d "'" -f 2) + # for freebsd + if [ "$PERL_LIB_DIR" = "" -o "$PERL_LIB_DIR" = "UNKNOWN" ]; then + PERL_LIB_DIR=$(perl -V:installsitelib | cut -d "'" -f 2) + fi + fi + + PERL_LIB_DIR=${PERL_LIB_DIR%/} + + return 0 +} + +#---- +## enable_service +## Enable a systemd service. +## @return 0 enabling ok +## @return 0 enabling failed +#---- +enable_service() { + local service="$1" + + if [ -x /bin/systemctl ] ; then + echo_line "Enabling service '$service'" + result="$(/bin/systemctl enable $service 2>&1 > /dev/null)" + local ret=$? + if [ "$ret" -ne 0 ] ; then + echo_error_on_line "FAILED" + add_error_message "Enabling service '$service' failed: $result" + else + echo_success_on_line "OK" + fi + return $ret + fi + + return 1 +} + +#---- +## reload_service +## Reload a systemd service. +## @return 0 reloading ok +## @return 0 reloading failed +#---- +reload_service() { + local service="$1" + + if [ -x /bin/systemctl ] ; then + echo_line "Reloading service '$service'" + result="$(/bin/systemctl reload $service 2>&1 > /dev/null)" + local ret=$? + if [ "$ret" -ne 0 ] ; then + echo_error_on_line "FAILED" + add_error_message "Reloading service '$service' failed: $result" + else + echo_success_on_line "OK" + fi + return $ret + fi + + return 1 +} + +#---- +## restart_service +## Restart a systemd service. +## @return 0 restarting ok +## @return 0 restarting failed +#---- +restart_service() { + local service="$1" + + if [ -x /bin/systemctl ] ; then + echo_line "Restarting service '$service'" + result="$(/bin/systemctl restart $service 2>&1 > /dev/null)" + local ret=$? + if [ "$ret" -ne 0 ] ; then + echo_error_on_line "FAILED" + add_error_message "Restarting service '$service' failed: $result" + else + echo_success_on_line "OK" + fi + return $ret + fi + + return 1 +} + +#---- +## reload_daemon +## Reload systemd daemon. +## @return 0 reload ok +## @return 0 reload failed +#---- +reload_daemon() { + if [ -x /bin/systemctl ] ; then + echo_line "Reloading systemctl daemon" + result="$(/bin/systemctl daemon-reload 2>&1 > /dev/null)" + local ret=$? + if [ "$ret" -ne 0 ] ; then + echo_error_on_line "FAILED" + add_error_message "Reloading systemctl daemon failed: $result" + else + echo_success_on_line "OK" + fi + return $ret + fi + + return 1 +} + +#---- +## replace_macro +## Replace @@ macros in all needed files in temporary directory. +## @return 0 replacement done successfully +## @return 1 replacement failed +## @globals TMP_DIR +#---- +replace_macro() { + local srclistcp="$1" + + { + for folder in $srclistcp ; do + result="$(find $TMP_DIR/source/$folder -type f | xargs --delimiter='\n' sed -i \ + -e 's|@GORGONE_USER@|'"$GORGONE_USER"'|gi' \ + -e 's|@GORGONE_LOG_DIR@|'"$GORGONE_LOG_DIR"'|gi' \ + -e 's|@GORGONE_ETC_DIR@|'"$GORGONE_ETC_DIR"'|gi' \ + -e 's|@CENTREON_ETC_DIR@|'"$CENTREON_ETC_DIR"'|gi' \ + -e 's|@CENTREON_SERVICE@|'"$CENTREON_SERVICE"'|gi' \ + -e 's|@SYSCONFIG_ETC_DIR@|'"$SYSCONFIG_ETC_DIR"'|gi' \ + -e 's|@PERL_BINARY@|'"$PERL_BINARY"'|gi' \ + -e 's|@BINARY_DIR@|'"$BINARY_DIR"'|gi' 2>&1 > /dev/null)" + done + } || { + add_error_message "Replacing macros failed: $result" + return 1 + } + + return 0 +} + +#---- +## find_os +## Search OS distribution and version. +## @return 0 search done +## @globals DISTRIB DISTRIB_VERSION +#---- +find_os() { + # From https://unix.stackexchange.com/questions/6345/how-can-i-get-distribution-name-and-version-number-in-a-simple-shell-script + if [ -f /etc/os-release ]; then + # freedesktop.org and systemd + . /etc/os-release + DISTRIB=${ID} + DISTRIB_VERSION=${VERSION_ID} + elif type lsb_release >/dev/null 2>&1; then + # linuxbase.org + DISTRIB=$(lsb_release -si | sed -e 's/\(.*\)/\L\1/') + DISTRIB_VERSION=$(lsb_release -sr) + elif [ -f /etc/lsb-release ]; then + # For some versions of Debian/Ubuntu without lsb_release command + . /etc/lsb-release + DISTRIB=${DISTRIB_ID} + DISTRIB_VERSION=${DISTRIB_RELEASE} + elif [ -f /etc/debian_version ]; then + # Older Debian/Ubuntu/etc. + DISTRIB=debian + DISTRIB_VERSION=$(cat /etc/debian_version | cut -d "." -f 1) + elif [ -f /etc/centos-release ]; then + # CentOS + DISTRIB=centos + DISTRIB_VERSION=$(cat /etc/centos-release | cut -d " " -f 4 | cut -d "." -f 1) + elif [ -f /etc/redhat-release ]; then + # Older Red Hat, CentOS, etc. + DISTRIB=centos + DISTRIB_VERSION=$(cat /etc/redhat-release | cut -d " " -f 4 | cut -d "." -f 1) + else + # Fall back to uname, e.g. "Linux <version>", also works for BSD, etc. + DISTRIB=$(uname -s) + DISTRIB_VERSION=$(uname -r) + fi + + return 0 +} + +#---- +## clean_and_exit +## Function to clean and exit Centreon install using purge_centreon_tmp_dir functionn, and exit. +#---- +clean_and_exit() { + local trap_sig=${1:-0} + + if [ $trap_sig -eq 0 ] ; then + echo -e "\nTrap interrupt, Centreon'll exit now and clean installation" + yes_no_default "Do you really want to quit Centreon installation?" "$no" + if [ $? -eq 1 ] ; then + echo "Continue..." + return 1 + fi + fi + + purge_centreon_tmp_dir "silent" + + exit 1 +} + +#---- +## check_tmp_disk_space +## Check space left for working directory. +## @return 0 space ok +## @return 1 no Space left +## @globals TMP_DIR +#---- +check_tmp_disk_space() { + local min_space="35584" + local free_space="" + local tmp_dir="" + + tmp_dir=$(dirname $TMP_DIR) + + free_space=$(df -P $tmp_dir | tail -1 | awk '{print $4}') + + if [ "$free_space" -lt "$min_space" ] ; then + echo_error "No space left on temporary directory '$tmp_dir' (<$min_space Ko)" "FAILED" + return 1 + else + return 0 + fi +} + +#---- +## purge_centreon_tmp_dir +## Ask to remove all temporaries working directory. +## @param silent option (silent) +## @return 0 remove done +## @return 1 don't remove (abort by user) +## @globals TMP_DIR +#---- +purge_centreon_tmp_dir() { + local silent="$1" + local not_clean="1" + local rc="0" + while [ $not_clean -ne 0 ] ; do + if [ "$silent" != "silent" ] ; then + yes_no_default "Do you want to remove the Centreon temporary working space to continue installation?" "$yes" + rc=$? + else + rc=0 + fi + if [ $rc -eq 0 ] ; then + local tmp_base_dir=`dirname $TMP_DIR` + local tmp_dir=`basename $TMP_DIR` + find $tmp_base_dir -name "$tmp_dir*" -type d \ + -exec rm -rf {} \; 2>/dev/null + not_clean="0" + else + return 1 + fi + done + return 0 +} + +#---- +## pathfind_ret +## Find in $PATH if binary exist and return dirname. +## @param file to test +## @param global variable to set a result +## @return 0 found +## @return 1 not found +## @Globals PATH +#---- +pathfind_ret() { + local bin=$1 + local var_ref=$2 + local OLDIFS="$IFS" + IFS=: + for p in $PATH; do + if [ -x "$p/$bin" ]; then + IFS="$OLDIFS" + eval $var_ref=$p + return 0 + fi + done + IFS="$OLDIFS" + return 1 +} + +#---- +## check_result +## Check result and print a message using echo_success or echo_error +## @param return code to check +## @param message to print +#---- +check_result() { + local code=$1 + shift + local message=$@ + + if [ $code -eq 0 ] ; then + echo_success "$message" "OK" + else + echo_error "$message" "FAILED" + fi + return 0 +} diff --git a/gorgone/install/inputvars.centos.env b/gorgone/install/inputvars.centos.env new file mode 100644 index 00000000000..04bbf24bd68 --- /dev/null +++ b/gorgone/install/inputvars.centos.env @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +# +# Centreon installation variables specific values for CentOS. +# DO NOT EDIT! Edit inputvars.env file instead! diff --git a/gorgone/install/inputvars.debian.env b/gorgone/install/inputvars.debian.env new file mode 100644 index 00000000000..f42ab29d103 --- /dev/null +++ b/gorgone/install/inputvars.debian.env @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +# +# Centreon installation variables specific values for Debian. +# DO NOT EDIT! Edit inputvars.env file instead! + +SYSTEMD_ETC_DIR="/lib/systemd/system" +SYSCONFIG_ETC_DIR="/etc/default" diff --git a/gorgone/install/inputvars.default.env b/gorgone/install/inputvars.default.env new file mode 100755 index 00000000000..b0c27111c5d --- /dev/null +++ b/gorgone/install/inputvars.default.env @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# +# Gorgone installation variables default values. +# DO NOT EDIT! Edit inputvars.env file instead! + +INSTALLATION_TYPE="central" +GORGONE_USER="centreon-gorgone" +GORGONE_GROUP="centreon-gorgone" +GORGONE_ETC_DIR="/etc/centreon-gorgone" +GORGONE_LOG_DIR="/var/log/centreon-gorgone" +GORGONE_VARLIB_DIR="/var/lib/centreon-gorgone" +GORGONE_CACHE_DIR="/var/cache/centreon-gorgone" +CENTREON_USER="centreon" +CENTREON_HOME="/var/spool/centreon" +CENTREON_ETC_DIR="/etc/centreon" +CENTREON_SERVICE="centreon" +ENGINE_USER="centreon-engine" +ENGINE_GROUP="centreon-engine" +BROKER_USER="centreon-broker" +BROKER_GROUP="centreon-broker" +BINARY_DIR="/usr/bin" +PERL_BINARY="/usr/bin/perl" +SYSTEMD_ETC_DIR="/etc/systemd/system" +SYSCONFIG_ETC_DIR="/etc/sysconfig" +LOGROTATED_ETC_DIR="/etc/logrotate.d" +TMP_DIR="/tmp/centreon-setup" +LOG_FILE="$BASE_DIR/log/install.log" diff --git a/gorgone/install/inputvars.opensuse-leap.env b/gorgone/install/inputvars.opensuse-leap.env new file mode 100644 index 00000000000..e8b10d5b58f --- /dev/null +++ b/gorgone/install/inputvars.opensuse-leap.env @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +# +# Centreon installation variables specific values for OpenSUSE Leap. +# DO NOT EDIT! Edit inputvars.env file instead! + diff --git a/gorgone/install/inputvars.ubuntu.env b/gorgone/install/inputvars.ubuntu.env new file mode 100644 index 00000000000..9cd0068550e --- /dev/null +++ b/gorgone/install/inputvars.ubuntu.env @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +# +# Centreon installation variables specific values for Ubuntu. +# DO NOT EDIT! Edit inputvars.env file instead! + +SYSTEMD_ETC_DIR="/lib/systemd/system" +SYSCONFIG_ETC_DIR="/etc/default" diff --git a/gorgone/install/src/centreon-api.yaml b/gorgone/install/src/centreon-api.yaml new file mode 100644 index 00000000000..d52e6bd182a --- /dev/null +++ b/gorgone/install/src/centreon-api.yaml @@ -0,0 +1,9 @@ +gorgone: + tpapi: + - name: centreonv2 + base_url: "http://127.0.0.1/centreon/api/beta/" + username: admin + password: centreon + - name: clapi + username: admin + password: centreon diff --git a/gorgone/install/src/centreon.yaml b/gorgone/install/src/centreon.yaml new file mode 100644 index 00000000000..4cb705e38d8 --- /dev/null +++ b/gorgone/install/src/centreon.yaml @@ -0,0 +1,3 @@ +name: centreon.yaml +description: Configure Centreon Gorgone to work with Centreon Web. +centreon: !include @CENTREON_ETC_DIR@/config.d/*.yaml diff --git a/gorgone/install/src/config.yaml b/gorgone/install/src/config.yaml new file mode 100644 index 00000000000..7675ec7b230 --- /dev/null +++ b/gorgone/install/src/config.yaml @@ -0,0 +1,3 @@ +name: config.yaml +description: Configuration brought by Centreon Gorgone install. SHOULD NOT BE EDITED! USE CONFIG.D DIRECTORY! +configuration: !include @GORGONE_ETC_DIR@/config.d/*.yaml diff --git a/gorgone/install/src/gorgoned.logrotate b/gorgone/install/src/gorgoned.logrotate new file mode 100644 index 00000000000..ee2210cc144 --- /dev/null +++ b/gorgone/install/src/gorgoned.logrotate @@ -0,0 +1,10 @@ +@GORGONE_LOG_DIR@/gorgoned.log { + copytruncate + weekly + rotate 52 + compress + delaycompress + notifempty + missingok + su root root +} diff --git a/gorgone/install/src/gorgoned.sysconfig b/gorgone/install/src/gorgoned.sysconfig new file mode 100644 index 00000000000..b1200066352 --- /dev/null +++ b/gorgone/install/src/gorgoned.sysconfig @@ -0,0 +1,4 @@ +# Configuration file for Centreon Gorgone. + +# OPTIONS for the daemon launch +OPTIONS="--config=@GORGONE_ETC_DIR@/config.yaml --logfile=@GORGONE_LOG_DIR@/gorgoned.log --severity=info" diff --git a/gorgone/install/src/gorgoned.systemd b/gorgone/install/src/gorgoned.systemd new file mode 100644 index 00000000000..6c228be2bbd --- /dev/null +++ b/gorgone/install/src/gorgoned.systemd @@ -0,0 +1,33 @@ +## +## Copyright 2019-2021 Centreon +## +## Licensed under the Apache License, Version 2.0 (the "License"); +## you may not use this file except in compliance with the License. +## You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. +## +## For more information : contact@centreon.com +## + +[Unit] +Description=Centreon Gorgone +PartOf=@CENTREON_SERVICE@.service +After=@CENTREON_SERVICE@.service +ReloadPropagatedFrom=@CENTREON_SERVICE@.service + +[Service] +EnvironmentFile=@SYSCONFIG_ETC_DIR@/gorgoned +ExecStart=@PERL_BINARY@ @BINARY_DIR@/gorgoned $OPTIONS +Type=simple +User=@GORGONE_USER@ + +[Install] +WantedBy=multi-user.target +WantedBy=@CENTREON_SERVICE@.service diff --git a/gorgone/install/src/instGorgone.conf b/gorgone/install/src/instGorgone.conf new file mode 100644 index 00000000000..8dea74f7bcb --- /dev/null +++ b/gorgone/install/src/instGorgone.conf @@ -0,0 +1,22 @@ +# Centreon installation variables saved from previous installation. + +INSTALLATION_TYPE=$INSTALLATION_TYPE +GORGONE_USER=$GORGONE_USER +GORGONE_GROUP=$GORGONE_GROUP +GORGONE_ETC_DIR=$GORGONE_ETC_DIR +GORGONE_LOG_DIR=$GORGONE_LOG_DIR +GORGONE_VARLIB_DIR=$GORGONE_VARLIB_DIR +GORGONE_CACHE_DIR=$GORGONE_CACHE_DIR +CENTREON_USER=$CENTREON_USER +CENTREON_HOME=$CENTREON_HOME +CENTREON_ETC_DIR=$CENTREON_ETC_DIR +CENTREON_SERVICE=$CENTREON_SERVICE +ENGINE_USER=$ENGINE_USER +ENGINE_GROUP=$ENGINE_GROUP +BROKER_USER=$BROKER_USER +BROKER_GROUP=$BROKER_GROUP +BINARY_DIR=$BINARY_DIR +PERL_BINARY=$PERL_BINARY +SYSTEMD_ETC_DIR=$SYSTEMD_ETC_DIR +SYSCONFIG_ETC_DIR=$SYSCONFIG_ETC_DIR +LOGROTATED_ETC_DIR=$LOGROTATED_ETC_DIR \ No newline at end of file diff --git a/gorgone/sourceInstall/functions b/gorgone/sourceInstall/functions deleted file mode 100755 index 7a3e34d24fa..00000000000 --- a/gorgone/sourceInstall/functions +++ /dev/null @@ -1,1038 +0,0 @@ -#!/bin/bash -#---- -## @Synopsis Functions needed in the installation script of Centreon Gorgone module -## @Copyright Copyright 2008, Guillaume Watteeux -## @Copyright Copyright 2008-2020, Centreon -## @License GPL : http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt -#---- -## Centreon is developed with GPL Licence 2.0 -## Developed by : Julien Mathis - Romain Le Merlus -## -## 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, or (at your option) any later version. -## -## 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. -## -## For information : infos@centreon.com -# - -## VARS -yes="y" -no="n" -ok="OK" -fail="FAIL" -passed="PASSED" -warning="WARNING" -critical="CRITICAL" -# Init binary to empty to use pathfind or manual define -GREP="" -CAT="" -SED="" -CHMOD="" -CHOWN="" - -## COLOR FUNCTIONS -RES_COL="60" -MOVE_TO_COL="\\033[${RES_COL}G" -SETCOLOR_INFO="\\033[1;38m" -SETCOLOR_SUCCESS="\\033[1;32m" -SETCOLOR_FAILURE="\\033[1;31m" -SETCOLOR_WARNING="\\033[1;33m" -SETCOLOR_NORMAL="\\033[0;39m" - -#---- -## make a question with yes/no possibility -## use "no" response by default -## @param message to print -## @param default response (default to no) -## @return 0 yes -## @return 1 no -#---- -yes_no_default() { - local message=$1 - local default=${2:-$no} - local res="not_define" - while [ "$res" != "$yes" ] && [ "$res" != "$no" ] && [ ! -z "$res" ] ; do - echo -e "\n$message\n[y/n], default to [$default]:" - echo -en "> " - read res - [ -z "$res" ] && res="$default" - done - if [ "$res" = "$yes" ] ; then - return 0 - else - return 1 - fi -} - -#---- -## print info message and add it to log file -## @param message info -## @param type info (ex: INFO, username...) -## @Stdout info message -## @Globals LOG_FILE -#---- -echo_info() { - echo -e "${1}${MOVE_TO_COL}${SETCOLOR_INFO}${2}${SETCOLOR_NORMAL}" - log "$1" "$2" -} - -#---- -## print success message -## add success message to log file -## @param message -## @param word to specify success (ex: OK) -## @Stdout success message -## @Globals LOG_FILE -#---- -echo_success() { - echo -e "${1}${MOVE_TO_COL}${SETCOLOR_SUCCESS}${2}${SETCOLOR_NORMAL}" - log "$1" "$2" -} - -#---- -## print failure message -## add failure message to log file -## @param message -## @param word to specify failure (ex: fail) -## @Stdout failure message -## @Globals LOG_FILE -#---- -echo_failure() { - echo -e "${1}${MOVE_TO_COL}${SETCOLOR_FAILURE}${2}${SETCOLOR_NORMAL}" - log "$1" "$2" -} - -#---- -## print passed message -## add passed message to log file -## @param message -## @param word to specify pass (ex: passed) -## @Stdout passed message -## @Globals LOG_FILE -#---- -echo_passed() { - echo -e "${1}${MOVE_TO_COL}${SETCOLOR_WARNING}${2}${SETCOLOR_NORMAL}" - log "$1" "$2" -} - -#---- -## print warning message -## add warning message to log file -## @param message -## @param word to specify warning (ex: warn) -## @Stdout warning message -## @Globals LOG_FILE -#---- -echo_warning() { - echo -e "${1}${MOVE_TO_COL}${SETCOLOR_WARNING}${2}${SETCOLOR_NORMAL}" - log "$1" "$2" -} - -#---- -## Trim whitespaces and tabulations -## @param string to trim -## @return string -#---- -trim() { - echo "$1" | sed 's/^[ \t]*\(.*\)[ \t]*$/\1/' -} - -#---- -## add message on log file -## @param type of message level (debug, info, ...) -## @param message -## @Globals LOG_FILE -#---- -log() { - local program="$0" - local type="$1" - shift - local message="$@" - echo -e "[$program] - $type: $message" >> $LOG_FILE -} - -#---- -## find in $PATH if binary exist -## @param file to test -## @return 0 found -## @return 1 not found -## @Globals PATH -#---- -pathfind() { - OLDIFS="$IFS" - IFS=: - for p in $PATH; do - if [ -x "$p/$*" ] ; then - IFS="$OLDIFS" - return 0 - fi - done - IFS="$OLDIFS" - return 1 -} - -#---- -## find in $PATH if binary exist and return dirname -## @param file to test -## @param global variable to set a result -## @return 0 found -## @return 1 not found -## @Globals PATH -#---- -pathfind_ret() { - local bin=$1 - local var_ref=$2 - local OLDIFS="$IFS" - IFS=: - for p in $PATH; do - if [ -x "$p/$bin" ] ; then - IFS="$OLDIFS" - eval $var_ref=$p - return 0 - fi - done - IFS="$OLDIFS" - return 1 -} - -#---- -## define a specific variables for grep,cat,sed,... binaries -## This functions was been use in first line on your script -## @return 0 All is't ok -## @return 1 problem with one variable -## @Globals GREP, CAT, SED, CHMOD, CHOWN -#---- -define_specific_binary_vars() { - local vars_bin="GREP CAT SED CHMOD CHOWN" - local var_bin_tolower="" - for var_bin in $vars_bin ; do - if [ -z $(eval echo \$$var_bin) ] ; then - var_bin_tolower="$(echo $var_bin | tr [:upper:] [:lower:])" - pathfind_ret "$var_bin_tolower" "$(echo -n $var_bin)" - if [ "$?" -eq 0 ] ; then - eval "$var_bin='$(eval echo \$$var_bin)/$var_bin_tolower'" - export $(echo $var_bin) - log "PATH" "$var_bin=$(eval echo \$$var_bin)" - else - return 1 - fi - fi - done - return 0 -} - -#---- -## Check space left before installing -## @return 0 Space ok -## @return 1 No Space left -## @Globals INSTALL_DIR -#---- -check_disk_space() { - local min_space="35584" - local free_space="" - local tmp_dir="" - dest_dir=$(dirname $INSTALL_DIR) - free_space=$(df -P $dest_dir | tail -1 | awk '{print $4}') - if [ "$free_space" -lt "$min_space" ] ; then - echo_failure "No space left on : $dest_dir (<$min_space Ko)" "$fail" - return 1 - else - return 0 - fi -} - -#---- -## print a message, test directory if answer is correct -## Loop if not a valid directory -## @param message -## @param default value (use "NO_DEFAULT" if not default value) -## @param global variable to set a result -## @return 0 end -#---- -answer_with_testdir() { - local message=$1 - local default=$2 - local var_ref=$3 - local res="not_defined" - local first=0 - while [ ! -d "$res" ] ; do - [ $first -eq 1 ] && echo_passed "$res is not a directory or does not exist" "$critical" - echo -e "\n$message" - [ "$default" != "NO_DEFAULT" ] && echo -e "default to [$default]" - echo -en "> " - read res - if [ -z "$res" ] ; then - [ $default != "NO_DEFAULT" ] && res=$default - fi - if [ -z ${res#/} ] ; then - echo_passed "You cannot select the filesystem root folder" - res="not_defined" - else - first=1 - fi - done - eval $var_ref=$res - return 0 -} - -#---- -## ask if the creation of missing folders should be automated or not -#---- -allow_creation_of_missing_resources() { - CREATION_ALLOWED=0 - yes_no_default "Do you want to be asked for confirmation before creating missing resources ?" "$yes" - if [ "$?" -eq 1 ] ; then - CREATION_ALLOWED=1 - echo_warning "Ask confirmation before creation" "NO" - fi - export CREATION_ALLOWED -} - -#---- -## print a message, create directory with answer -## Loop if not a valid directory (slash...) -## @param message -## @param default value (use "NO_DEFAULT" if not default value) -## @param global variable to set a result -## @return 0 end -#---- -answer_with_createdir() { - local message=$1 - local default=$2 - local var_ref=$3 - local res="not_defined" - local first=0 - while [ ! -d "$res" ] ; do - [ $first -eq 1 ] && echo_passed "Directory $res does not exists." "$critical" - echo -e "\n$message" - [ "$default" != "NO_DEFAULT" ] && echo -e "default to [$default]" - echo -en "> " - read res - if [ -z "$res" ] ; then - [ "$default" != "NO_DEFAULT" ] && res=$default - fi - if [ -z "${res#/}" -o "$yes" = "$res" -o "$no" = "$res" ] ; then - echo_passed "You cannot select the filesystem root folder" - res="not_defined" - else - first=1 - if [ $CREATION_ALLOWED -eq 0 ] ; then - [ -d "$res" ] && break - yes_no_default "Do you want to create this directory ? [$res]" - fi - if [ $? -eq 0 ] || [ $CREATION_ALLOWED -eq 1 ] ; then - mkdir -p $res - log "INFO" "Creating $res" - elif [ $? -ne 0 ] ; then - echo_passed "Could not create directory." "$critical" - #continue - fi - fi - done - eval $var_ref=$res - return 0 -} - -#---- -## print a message, test if file exists -## Loop if not a valid file -## @param message -## @param default value (use "NO_DEFAULT" if not default value) -## @param global variable to set a result -## @return 0 end -#---- -answer_with_testfile() { - local message=$1 - local default=$2 - local var_ref=$3 - local res="not_define" - local first=0 - while [ ! -f "$res" ] ; do - [ $first -eq 1 ] && echo_passed "$res is not a valid file." "$critical" - echo -e "\n$message" - [ "$default" != "NO_DEFAULT" ] && echo -e "default to [$default]" - echo -en "> " - read res - if [ -z "$res" ] ; then - [ "$default" != "NO_DEFAULT" ] && res=$default - fi - first=1 - done - eval $var_ref=$res - return 0 -} - -#---- -## Test if file exist -## @param file -## @param variable to reset -## @return 0 file exist -## @return 1 file does not exist -#---- -testfile_clean() { - local file="$1" - local var="$2" - [ -z "$file" ] && return 1 - if [ ! -e "$file" ] ; then - eval $var="" - return 1 - else - return 0 - fi -} - -#---- -## Test if directory exist -## @param directory -## @param variable to reset -## @return 0 directory exist -## @return 1 directory does not exist -#---- -testdir_clean() { - local dir="$1" - local var="$2" - [ -z "$dir" ] && return 1 - if [ ! -d "$dir" ] ; then - eval $var="" - return 1 - else - return 0 - fi -} - -#---- -## Check equality of two arguments -## @param value1 -## @param value2 -#---- -test_answer() { - if [ ! -z $2 ] ; then - if [ $2 != "" ] ; then - eval $1=$2 - fi - fi -} - -#---- -## Try to create a new folder -## @param directory -## @return 0 on success -## @return 1 on failure -#---- -dir_test_create() { - local dirname="$1" - if [ ! -d "$1" ] ; then - mkdir -p "$dirname" - if [ $? -ne 0 ] ; then - echo_failure "Could not create folder $dirname" "$fail" - return 1 - fi - echo_success "Creating folder $dirname" "$ok" - fi - return 0 -} - -#---- -## Change rights on specific file/folders -#---- -change_rights() { - local user=$1 - local group=$2 - local rights=$3 - local target=$4 - - ${CHOWN} $user.$group $target - if [ $? -eq 0 ] ; then - echo_success "Modify owner of $target" "$ok" - else - echo_failure "Modify owner of $target" "$fail" - fi - - ${CHMOD} $rights $target - if [ $? -eq 0 ] ; then - echo_success "Modify rights of $target" "$ok" - else - echo_failure "Modify rights of $target" "$fail" - fi -} - -#---- -## Define where is Gorgone log folder -#---- -locate_gorgone_logdir() { - if [ -n "$GORGONE_LOG" ] ; then - dir_test_create "$GORGONE_LOG" - if [ $? -ne 0 ] ; then - GORGONE_LOG="" - fi - fi - if [ -z "$GORGONE_LOG" ] ; then - answer_with_createdir "Where is your Gorgone log folder" "$DEFAULT_GORGONE_LOG" "GORGONE_LOG" - echo_success "Path $GORGONE_LOG" "$ok" - fi - GORGONE_LOG=`trim ${GORGONE_LOG%/}` - export GORGONE_LOG - log "VALUE OF" "GORGONE_LOG folder = $GORGONE_LOG" -} - -#---- -## Define where is Gorgone database folder -#---- -locate_gorgone_varlib() { - if [ -n "$GORGONE_VARLIB" ] ; then - dir_test_create "$GORGONE_VARLIB" - if [ $? -ne 0 ] ; then - GORGONE_VARLIB="" - fi - fi - if [ -z "$GORGONE_VARLIB" ] ; then - answer_with_createdir "Where is your Gorgone database folder" "$DEFAULT_GORGONE_VARLIB" "GORGONE_VARLIB" - echo_success "Path $GORGONE_VARLIB" "$ok" - fi - GORGONE_VARLIB=`trim ${GORGONE_VARLIB%/}` - export GORGONE_VARLIB - log "VALUE OF" "GORGONE_VARLIB folder = $GORGONE_VARLIB" -} - - -#---- -## Define where is Gorgone config (etc) folder -#---- -locate_gorgone_etcdir() { - if [ -n "$GORGONE_ETC" ] ; then - dir_test_create "$GORGONE_ETC" - if [ $? -ne 0 ] ; then - GORGONE_ETC="" - fi - fi - if [ -z "$GORGONE_ETC" ] ; then - answer_with_createdir "Where is your Gorgone config (etc) folder" "$DEFAULT_GORGONE_ETC" "GORGONE_ETC" - echo_success "Path $GORGONE_ETC" "$ok" - fi - GORGONE_ETC=`trim ${GORGONE_ETC%/}` - export GORGONE_ETC - log "VALUE OF" "GORGONE_ETC folder = $GORGONE_ETC" - - # create the sub-folder for specific config files - dir_test_create "$GORGONE_ETC/config.d" - echo_success "Path $GORGONE_ETC/config.d" "$ok" -} - -#---- -## Define where is Gorgone user's bin folder -#---- -locate_gorgone_bindir() { - if [ -n "$GORGONE_BINDIR" ] ; then - dir_test_create "$GORGONE_BINDIR" - if [ $? -ne 0 ] ; then - GORGONE_BINDIR="" - fi - fi - if [ -z "$GORGONE_BINDIR" ] ; then - answer_with_createdir "Where are your Gorgone user's folder" "$DEFAULT_GORGONE_BINDIR" "GORGONE_BINDIR" - echo_success "Path $GORGONE_BINDIR" "$ok" - fi - GORGONE_BINDIR=`trim ${GORGONE_BINDIR%/}` - export GORGONE_BINDIR - log "VALUE OF" "GORGONE_PERL folder = $GORGONE_BINDIR" -} - -#---- -## Define where is Gorgone perl folder -#---- -locate_gorgone_perldir() { - if [ -n "$GORGONE_PERL" ] ; then - dir_test_create "$GORGONE_PERL" - if [ $? -ne 0 ] ; then - GORGONE_PERL="" - fi - fi - if [ -z "$GORGONE_PERL" ] ; then - answer_with_createdir "Where are your Gorgone's perl files" "$DEFAULT_GORGONE_PERL" "GORGONE_PERL" - echo_success "Path $GORGONE_PERL" "$ok" - fi - GORGONE_PERL=`trim ${GORGONE_PERL%/}` - export GORGONE_PERL - log "VALUE OF" "GORGONE_PERL folder = $GORGONE_PERL" -} - -#---- -## Define where is perl binary -## find perl in PATH -#---- -locate_perl() { - testfile_clean "$BIN_PERL" "BIN_PERL" - if [ -z "$BIN_PERL" ] ; then - pathfind_ret "perl" "BIN_PERL" - if [ "$?" -ne 0 ] ; then - answer_with_testfile "Where is perl" "$DEFAULT_BIN_PERL" "BIN_PERL" - else - BIN_PERL="$BIN_PERL/perl" - fi - echo_success "$BIN_PERL" "$ok" - fi - BIN_PERL=${BIN_PERL%/} - export BIN_PERL - log "VALUE OF" "BIN_PERL = $BIN_PERL" -} - -#---- -## Define where is your cron.d directory -#---- -locate_cron_d() { - testdir_clean "$CRON_D" "CRON_D" - if [ -z "$CRON_D" ] ; then - if [ -d "/etc/cron.d" ] ; then - CRON_D="/etc/cron.d" - # Add most of init.d - else - answer_with_testdir "Where is your cron.d directory ?" "$DEFAULT_CRON_D" "CRON_D" - echo_success "Location $CRON_D" "$ok" - fi - fi - CRON_D=${CRON_D%/} - export CRON_D - log "VALUE OF" "CRON_D = $CRON_D" -} - -#---- -## Define where is your logrotate.d directory -## @Globals LOGROTATE_D, DEFAULT_LOGROTATE_D -#---- -locate_logrotate_d() { - testdir_clean "$LOGROTATE_D" "LOGROTATE_D" - if [ -z "$LOGROTATE_D" ] ; then - if [ -d "/etc/logrotate.d" ] ; then - LOGROTATE_D="/etc/logrotate.d" - # Add most of init.d - else - answer_with_testdir "Where is your logrotate.d folder ?" "$DEFAULT_LOGROTATE_D" "LOGROTATE_D" - echo_success "Path $LOGROTATE_D" "$ok" - fi - fi - LOGROTATE_D=`trim ${LOGROTATE_D%/}` - export LOGROTATE_D - log "VALUE OF" "LOGROTATE_D = $LOGROTATE_D" -} - -#---- -## Define where is your systemd folder -## @Globals SYSTEM_D, DEFAULT_SYSTEM_D -#---- -locate_system_d() { - testdir_clean "$SYSTEM_D" "SYSTEM_D" - if [ -z "$SYSTEM_D" ] ; then - if [ -d "/etc/systemd/system" ] ; then - SYSTEM_D="/etc/systemd/system" - # Add most of init.d - else - answer_with_testdir "Where is your systemd folder ?" "$DEFAULT_SYSTEM_D" "SYSTEM_D" - echo_success "Path $SYSTEM_D" "$ok" - fi - fi - SYSTEM_D=`trim ${SYSTEM_D%/}` - export SYSTEM_D - log "VALUE OF" "SYSTEM_D = $SYSTEM_D" -} - -#---- -## Define where is your sysconfig folder -## @Globals SYSCONFIG, DEFAULT_SYSCONFIG -#---- -locate_sysconfig() { - testdir_clean "$SYSCONFIG" "SYSCONFIG" - if [ -z "$SYSCONFIG" ] ; then - if [ -d "/etc/sysconfig" ] ; then - SYSCONFIG="/etc/sysconfig" - # Add most of init.d - else - answer_with_testdir "Where is your sysconfig folder ?" "$DEFAULT_SYSCONFIG" "SYSCONFIG" - echo_success "Path $SYSCONFIG" "$ok" - fi - fi - SYSCONFIG=`trim ${SYSCONFIG%/}` - export SYSCONFIG - log "VALUE OF" "SYSCONFIG = $SYSCONFIG" -} - -#---- -## Ask for centreon user, and create it if not exists -## @Globals GORGONE_GROUP, DEFAULT_GORGONE_GROUP -#---- -check_gorgone_group() -{ - if [ -n "$GORGONE_GROUP" ] ; then - group_test "$GORGONE_GROUP" - if [ $? -ne 0 ] ; then - group_create "$GORGONE_GROUP" - if [ $? -ne 0 ] ; then - GORGONE_GROUP="" - fi - fi - fi - if [ -z "$GORGONE_GROUP" ] ; then - answer_with_creategroup "What is the Gorgone group ?" "$DEFAULT_GORGONE_GROUP" "GORGONE_GROUP" - fi - log "VALUE OF" "GORGONE_GROUP = $GORGONE_GROUP" - return 0 -} - -group_test() { - grep "^$1:" /etc/group &>/dev/null - return $? -} - -group_create() { - local groupname="$1" - groupadd -r "$groupname" - if [ $? -ne 0 ] ; then - echo_failure "Could not create group $groupname" "$fail" - return 1 - fi - echo_success "Creating group $groupname" "$ok" - log "VALUE OF" "Creating group $groupname" - return 0 -} - -#---- -## print a message for get information with create a group system -## @param message -## @param default value (use "NO_DEFAULT" if not default value) -## @param global variable to set a result -## @return 0 end -#---- -answer_with_creategroup() { - local message=$1 - local default=$2 - local var_ref=$3 - local res="not_def" - local first=0 - local group_tested=1 - while [ "$group_tested" -ne 0 ] ; do - [ $first -eq 1 ] && echo_passed "The group $res does not exist" "$critical" - echo -e "\n$message" - [ "$default" != "NO_DEFAULT" ] && echo -e "default to [$default]" - echo -en "> " - read res - if [ -z "$res" ] ; then - [ "$default" != "NO_DEFAULT" ] && res=$default - fi - first=1 - if [ -n "$res" ] ; then - group_test "$res" - if [ $? -ne 0 ] ; then - if [ $CREATION_ALLOWED -eq 0 ] ; then - yes_no_default "Do you want to create this group ? [$res]" - fi - if [ $? -eq 0 ] || [ $CREATION_ALLOWED -eq 1 ] ; then - group_create "$res" - fi - fi - else - res="not_def" - fi - group_test "$res" - group_tested=$? - done - eval $var_ref=$res - return 0 -} - -#---- -## Ask for centreon user -## @Globals GORGONE_USER, DEFAULT_GORGONE_USER -#---- -check_gorgone_user() -{ - local groups="$GORGONE_GROUP" - local description="Gorgone user" - local home="$GORGONE_VARLIB" - if [ -n "$GORGONE_USER" ] ; then - user_test "$GORGONE_USER" - if [ $? -ne 0 ] ; then - user_create "$GORGONE_USER" "$groups" "$description" "$home" - if [ $? -ne 0 ] ; then - GORGONE_USER="" - fi - fi - fi - if [ -z "$GORGONE_USER" ] ; then - answer_with_createuser "What is the Gorgone user ?" "$DEFAULT_GORGONE_USER" "GORGONE_USER" "$groups" "$description" "$home" - fi - log "VALUE OF" "GORGONE_USER = $GORGONE_USER" - return 0 -} - -user_test() { - grep "^$1:" /etc/passwd &>/dev/null - return $? -} - -user_create() { - local username="$1" - local groupname="$2" - local description="$3" - local home="$4" - useradd -r -c "$description" -s "/bin/sh" -d "$home" -g "$groupname" "$username" - if [ $? -ne 0 ] ; then - echo_failure "Could not create user $username" "$fail" - return 1 - fi - echo_success "Creating user $username ($description)" "$ok" - log "INFO" "Creating user $username" - return 0 -} - -#---- -## print a message for get information with create a user system -## @param message -## @param default value (use "NO_DEFAULT" if not default value) -## @param global variable to set a result -## @return 0 end -#---- -answer_with_createuser() { - local message=$1 - local default=$2 - local var_ref=$3 - local groups="$4" - local description="$5" - local home="$6" - local res="not_def" - local first=0 - local user_tested=1 - while [ "$user_tested" -ne 0 ] ; do - [ $first -eq 1 ] && echo_passed "The user $res does not exist" "$critical" - echo -e "\n$message" - [ "$default" != "NO_DEFAULT" ] && echo -e "default to [$default]" - echo -en "> " - read res - if [ -z "$res" ] ; then - [ "$default" != "NO_DEFAULT" ] && res=$default - fi - first=1 - if [ -n "$res" ] ; then - user_test "$res" - if [ $? -ne 0 ] ; then - if [ $CREATION_ALLOWED -eq 0 ] ; then - yes_no_default "Do you want to create this user ? [$res]" - fi - if [ $? -eq 0 ] || [ $CREATION_ALLOWED -eq 1 ] ; then - user_create "$res" "$groups" "$description" "$home" - fi - fi - else - res="not_def" - fi - user_test "$res" - user_tested=$? - done - eval $var_ref=$res - return 0 -} - -#---- -## install init script on distrib -## use this fonction to install a init script on your system -## it call <@find_OS> to define install command -## @param name of service -## @return 0 install service ok -## @return 1 install service fail -#---- -install_init_service() { - # how to find methode to install in rc.d directory a correct link ? - # debian update-rc.d - # redhat chkconfig - # Suse chkconfig - # FreeBSD add ${service}_enable=YES in /etc/rc.conf - local service=$1 - - if [ -z $2 ]; then - OS="" - find_OS "OS" - else - OS=$2 - fi - - if [ "$OS" = "DEBIAN" ] ; then - systemctl enable $service - systemctl start $service - elif [ "$OS" = "SUSE" ] ; then - chkconfig --add $service - elif [ "$OS" = "REDHAT" ] ; then - systemctl enable $service - systemctl start $service - elif [ "$OS" = "FREEBSD" ] ; then - echo_info "You must configure your /etc/rc.conf with: ${service}_enable=YES" - else - echo_passed "Impossible to install your run level for $service" "$fail" - return 1 - fi - return 0 -} - -#---- -## Change gorgone service "environment file" value -## replace the default sysconfig path with the chosen custom path -## @Globals DEFAULT_SYSCONFIG, SYSCONFIG -#---- -change_environment_file_path() { - sed -i -e "s,$DEFAULT_SYSCONFIG,$SYSCONFIG,g" "$BASE_DIR/config/systemd/gorgoned-service" -} - -#---- -## Define OS -## check on etc to find a specific file <p> -## Debian, Suse, Redhat, FreeBSD -## @param variable to set a result -## @return 0 OS found -## @return 1 OS not found -#---- -find_OS() { - local distrib=$1 - local dist_found="" - local system="" - local lsb_release="" - system="$(uname -s)" - if [ "$system" = "Linux" ] ; then - if [ "$(pathfind lsb_release; echo $?)" -eq "0" ] ; then - lsb_release="$(lsb_release -i -s)" - else - lsb_release="NOT_FOUND" - fi - if [ "$lsb_release" = "Debian" ] || \ - [ "$lsb_release" = "Ubuntu" ] || \ - [ -e "/etc/debian_version" ] ; then - dist_found="DEBIAN" - log "INFO" "GNU/Linux Debian Distribution" - elif [ "$lsb_release" = "SUSE LINUX" ] || \ - [ -e "/etc/SuSE-release" ] ; then - dist_found="SUSE" - log "INFO" "GNU/Linux Suse Distribution" - elif [ "$lsb_release" = "RedHatEnterpriseES" ] || \ - [ "$lsb_release" = "Fedora" ] || \ - [ -e "/etc/redhat-release" ] ; then - dist_found="REDHAT" - log "INFO" "GNU/Linux Redhat Distribution" - else - dist_found="NOT_FOUND" - log "INFO" "GNU/Linux distribution not found" - return 1 - fi - elif [ "$system" = "FreeBSD" ] ; then - dist_found="FREEBSD" - log "INFO" "FreeBSD System" - elif [ "$system" = "AIX" ] ; then - dist_found="AIX" - log "INFO" "AIX System" - elif [ "$system" = "SunOS" ] ; then - dist_found="SUNOS" - log "INFO" "SunOS System" - else - dist_found="NOT_FOUND" - log "INFO" "System not found" - fi - - eval $distrib=$dist_found - return 0 -} - -#---- -## Check result and print a message -## @param return code to check -## @param message to print -#---- -check_result() { - local code=$1 - shift - local message=$@ - - if [ $code -eq 0 ] ; then - echo_success "$message" "$ok" - else - echo_failure "$message" "$fail" - fi - return 0 -} - -#---- -## Test if the file exists, is readable and not empty -## @param file -## @return 0 file exist -## @return 1 file does not exist -#---- -test_file() { - local file="$1" - if [ -r "$file" ] && [ -s "$file" ]; then - return 0 - else - echo -e "The file named '$file' is missing or empty\nPlease check your sources" - exit 1 - fi -} - -#---- -## Change rights and copy file -## @param origin path of the file -## @param source filename -## @param destination path -## @param destination filename -## @param chmod rights -## @Globals GORGONE_USER, GORGONE_GROUP -## return 0 is no errors -## return 1 on errors -#---- -copy_and_modify_rights() { - local origin=$1 - local src_name=$2 - local destination=$3 - local filename=$4 - local rights=$5 - - # check for empty source folder string - origin=`trim ${origin%/}` - if [ -z "$origin" ] ; then - echo_failure "Empty source folder defined for file : $src_name" "$fail" - return 1 - fi - - # check that the file exists in the source folder - file="$origin/$src_name" - test_file "$file" - [ $? -ne 0 ] && return 1 - - # check for empty destination folder string - destination=`trim ${destination%/}` - if [ -z "$origin" ] ; then - echo_failure "Empty destination folder defined for file : $src_name" "$fail" - return 1 - fi - dir_test_create "$destination" - [ $? -ne 0 ] && return 1 - - cp -f $file "$destination/$filename" - if [ $? -ne 0 ] ; then - echo_failure "Could not copy the file : $filename to : $destination" "$fail" - return 1 - fi - - ${CHMOD} "$rights" "$destination/$filename" - if [ $? -ne 0 ] ; then - echo_failure "Could not modify the rights of the file : $filename" "$fail" - return 1 - fi - - ${CHOWN} "$GORGONE_USER:$GORGONE_GROUP" "$destination/$filename" - if [ $? -ne 0 ] ; then - echo_failure "Could not modify the owner of the file : $filename" "$fail" - return 1 - fi - - echo_success "Creating and adding rights on $filename" "$ok" - return 0 -} diff --git a/gorgone/sourceInstall/vars b/gorgone/sourceInstall/vars deleted file mode 100755 index a195f49ddbe..00000000000 --- a/gorgone/sourceInstall/vars +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/bash -#---- -## @Synopsis Installation variables needed for Centreon Gorgone module -## @Copyright Copyright 2008, Guillaume Watteeux -## @Copyright Copyright 2008-2020, Centreon -## @License GPL : http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt -#---- -## Centreon is developed with GPL Licence 2.0 -## Developed by : Julien Mathis - Romain Le Merlus -## -## 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, or (at your option) any later version. -## -## 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. -## -## For information : infos@centreon.com -# - -#---- -## install_vars -#---- - -# sources and log paths -LOG_DIR="$BASE_DIR/log" -LOG_FILE="$LOG_DIR/gorgone_install.log" - -# deployment paths -DEFAULT_GORGONE_LOG="/var/log/centreon-gorgone" -DEFAULT_GORGONE_VARLIB="/var/lib/centreon-gorgone" -DEFAULT_GORGONE_ETC="/etc/centreon-gorgone" -DEFAULT_GORGONE_CONF_FILE="/config.yaml" -DEFAULT_GORGONE_BINDIR="/usr/bin/" - -# perl related paths -DEFAULT_PERL_BIN="/usr/bin/perl" -DEFAULT_GORGONE_PERL=`eval "\`perl -V:installvendorlib\`"; echo $installvendorlib` - -# system tools paths -DEFAULT_INIT_D="/etc/init.d" -DEFAULT_CRON_D="/etc/cron.d" -DEFAULT_LOGROTATE_D="/etc/logrotate.d" -DEFAULT_SYSTEM_D="/etc/systemd/system" -DEFAULT_SYSCONFIG="/etc/sysconfig" - -# user and group -DEFAULT_GORGONE_USER="centreon-gorgone" -DEFAULT_GORGONE_GROUP="centreon-gorgone" From f35754102e45bf12900ef036d92d5dbb6dec0697 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Wed, 24 Feb 2021 16:59:54 +0100 Subject: [PATCH 577/948] enh(packaging): update requirement to use latest perl(Libssh::Session) (#109) --- gorgone/packaging/centreon-gorgone.spectemplate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index a27a5bed835..af376a68706 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -10,6 +10,7 @@ BuildArch: noarch BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) Requires: bzip2 +Requires: perl-Libssh-Session >= 0.8 Requires: perl-CryptX Requires: perl(Schedule::Cron) Requires: perl(ZMQ::LibZMQ4) @@ -29,7 +30,6 @@ Requires: perl(HTTP::Daemon) Requires: perl(HTTP::Status) Requires: perl(MIME::Base64) Requires: perl(Digest::MD5::File) -Requires: perl(Libssh::Session) >= 0.6 Requires: perl(Net::Curl::Easy) Requires: perl(HTTP::Daemon::SSL) Requires: perl(NetAddr::IP) From 25cd30fae5a7d891997534f75a871ae74478ee79 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Thu, 25 Feb 2021 11:46:49 +0100 Subject: [PATCH 578/948] enh(core): optimize zmq communication (MON-6761) (#110) --- gorgone/gorgone/class/clientzmq.pm | 7 +- gorgone/gorgone/class/core.pm | 2 - .../centreon/anomalydetection/class.pm | 3 +- .../modules/centreon/autodiscovery/class.pm | 5 +- .../gorgone/modules/centreon/engine/class.pm | 7 +- .../gorgone/modules/centreon/judge/class.pm | 5 +- .../modules/centreon/legacycmd/class.pm | 3 +- .../gorgone/modules/centreon/nodes/class.pm | 5 +- .../modules/centreon/statistics/class.pm | 5 +- gorgone/gorgone/modules/core/action/class.pm | 7 +- gorgone/gorgone/modules/core/cron/class.pm | 5 +- .../gorgone/modules/core/dbcleaner/class.pm | 5 +- .../gorgone/modules/core/httpserver/class.pm | 3 +- .../gorgone/modules/core/pipeline/class.pm | 5 +- gorgone/gorgone/modules/core/proxy/class.pm | 2 +- gorgone/gorgone/modules/core/pull/hooks.pm | 3 +- .../gorgone/modules/core/register/class.pm | 5 +- .../gorgone/modules/plugins/newtest/class.pm | 5 +- gorgone/gorgone/modules/plugins/scom/class.pm | 5 +- gorgone/gorgone/standard/api.pm | 520 +++++++++--------- gorgone/gorgone/standard/library.pm | 14 +- 21 files changed, 295 insertions(+), 326 deletions(-) diff --git a/gorgone/gorgone/class/clientzmq.pm b/gorgone/gorgone/class/clientzmq.pm index c5b427bb262..5b5ed7b2357 100644 --- a/gorgone/gorgone/class/clientzmq.pm +++ b/gorgone/gorgone/class/clientzmq.pm @@ -230,7 +230,8 @@ sub event { $connectors->{$options{identity}}->{ping_time} = time(); while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $sockets->{$options{identity}}); - + last if (!defined($message)); + # in progress if ($connectors->{$options{identity}}->{handshake} == 0) { $connectors->{$options{identity}}->{handshake} = 1; @@ -272,9 +273,7 @@ sub event { if (defined($callbacks->{$options{identity}})) { $callbacks->{$options{identity}}->(identity => $options{identity}, data => $data); } - } - - last unless (gorgone::standard::library::zmq_still_read(socket => $sockets->{$options{identity}})); + } } } diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index dc790f3fea5..d9992533e1b 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -564,7 +564,6 @@ sub router_internal_event { code => $code, token => $token ); - last unless (gorgone::standard::library::zmq_still_read(socket => $gorgone->{internal_socket})); } } @@ -713,7 +712,6 @@ sub router_external_event { data => $response ); } - last unless (gorgone::standard::library::zmq_still_read(socket => $gorgone->{external_socket})); } } diff --git a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm index 0d9590fc335..e20a68f6d9c 100644 --- a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm +++ b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm @@ -626,6 +626,7 @@ sub action_saasregister { sub event { while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); + last if (!defined($message)); $connector->{logger}->writeLogDebug("[anomalydetection] Event: $message"); if ($message =~ /^\[(.*?)\]/) { @@ -636,8 +637,6 @@ sub event { $method->($connector, token => $token, data => $data); } } - - last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); } } diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm index b6fd9afd5ef..19f3cbdb352 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm @@ -1060,7 +1060,8 @@ sub is_hdisco_synced { sub event { while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); - + last if (!defined($message)); + $connector->{logger}->writeLogDebug("[autodiscovery] Event: $message"); if ($message =~ /^\[(.*?)\]/) { if ((my $method = $connector->can('action_' . lc($1)))) { @@ -1070,8 +1071,6 @@ sub event { $method->($connector, token => $token, data => $data); } } - - last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); } } diff --git a/gorgone/gorgone/modules/centreon/engine/class.pm b/gorgone/gorgone/modules/centreon/engine/class.pm index a7e73d49ec1..cce95f6eaaf 100644 --- a/gorgone/gorgone/modules/centreon/engine/class.pm +++ b/gorgone/gorgone/modules/centreon/engine/class.pm @@ -258,14 +258,13 @@ sub create_child { sub event { while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); - + last if (!defined($message)); + $connector->{logger}->writeLogDebug("[engine] Event: $message"); if ($message !~ /^\[ACK\]/) { $connector->create_child(message => $message); - } - - last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); + } } } diff --git a/gorgone/gorgone/modules/centreon/judge/class.pm b/gorgone/gorgone/modules/centreon/judge/class.pm index 0a1aa1a74ad..7bb41971aee 100644 --- a/gorgone/gorgone/modules/centreon/judge/class.pm +++ b/gorgone/gorgone/modules/centreon/judge/class.pm @@ -387,7 +387,8 @@ sub action_judgelistener { sub event { while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); - + last if (!defined($message)); + $connector->{logger}->writeLogDebug("[judge] -class- event: $message"); if ($message =~ /^\[(.*?)\]/) { if ((my $method = $connector->can('action_' . lc($1)))) { @@ -397,8 +398,6 @@ sub event { $method->($connector, token => $token, data => $data); } } - - last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); } } diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index e6b2f8afcd9..3481de517df 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -726,6 +726,7 @@ sub action_centreoncommand { sub event { while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); + last if (!defined($message)); $connector->{logger}->writeLogDebug("[legacycmd] Event: $message"); if ($message =~ /^\[(.*?)\]/) { @@ -736,8 +737,6 @@ sub event { $method->($connector, token => $token, data => $data); } } - - last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); } } diff --git a/gorgone/gorgone/modules/centreon/nodes/class.pm b/gorgone/gorgone/modules/centreon/nodes/class.pm index 7f004b682eb..141f94ba318 100644 --- a/gorgone/gorgone/modules/centreon/nodes/class.pm +++ b/gorgone/gorgone/modules/centreon/nodes/class.pm @@ -211,7 +211,8 @@ sub action_nodesresync { sub event { while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); - + last if (!defined($message)); + $connector->{logger}->writeLogDebug("[nodes] Event: $message"); if ($message =~ /^\[(.*?)\]/) { if ((my $method = $connector->can('action_' . lc($1)))) { @@ -221,8 +222,6 @@ sub event { $method->($connector, token => $token, data => $data); } } - - last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); } } diff --git a/gorgone/gorgone/modules/centreon/statistics/class.pm b/gorgone/gorgone/modules/centreon/statistics/class.pm index 0f40f934da9..4ed00fcc13c 100644 --- a/gorgone/gorgone/modules/centreon/statistics/class.pm +++ b/gorgone/gorgone/modules/centreon/statistics/class.pm @@ -585,7 +585,8 @@ sub rrd_update { sub event { while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); - + last if (!defined($message)); + $connector->{logger}->writeLogDebug("[statistics] Event: $message"); if ($message =~ /^\[(.*?)\]/) { if ((my $method = $connector->can('action_' . lc($1)))) { @@ -595,8 +596,6 @@ sub event { $method->($connector, token => $token, data => $data); } } - - last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); } } diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index 75e4351216d..910e47f5c13 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -398,8 +398,9 @@ sub create_child { sub event { while (1) { - my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); - + my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); + last if (!defined($message)); + $connector->{logger}->writeLogDebug("[action] Event: $message"); if ($message !~ /^\[ACK\]/) { @@ -415,8 +416,6 @@ sub event { $connector->create_child(action => $action, token => $token, data => $data); } } - - last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); } } diff --git a/gorgone/gorgone/modules/core/cron/class.pm b/gorgone/gorgone/modules/core/cron/class.pm index 3e34edff989..1b2dc5786f5 100644 --- a/gorgone/gorgone/modules/core/cron/class.pm +++ b/gorgone/gorgone/modules/core/cron/class.pm @@ -373,6 +373,7 @@ sub action_deletecron { sub event { while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); + last if (!defined($message)); $connector->{logger}->writeLogDebug("[cron] Event: $message"); if ($message =~ /^\[ACK\]\s+\[(.*?)\]\s+(.*)$/m) { @@ -390,9 +391,7 @@ sub event { my $data = JSON::XS->new->utf8->decode($3); $method->($connector, token => $token, data => $data); } - } - - last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); + } } } diff --git a/gorgone/gorgone/modules/core/dbcleaner/class.pm b/gorgone/gorgone/modules/core/dbcleaner/class.pm index 04b35c1ae8c..2676db0705d 100644 --- a/gorgone/gorgone/modules/core/dbcleaner/class.pm +++ b/gorgone/gorgone/modules/core/dbcleaner/class.pm @@ -153,7 +153,8 @@ sub action_dbclean { sub event { while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); - + last if (!defined($message)); + $connector->{logger}->writeLogDebug("[dbcleaner] Event: $message"); if ($message =~ /^\[(.*?)\]/) { if ((my $method = $connector->can('action_' . lc($1)))) { @@ -163,8 +164,6 @@ sub event { $method->($connector, token => $token, data => $data); } } - - last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); } } diff --git a/gorgone/gorgone/modules/core/httpserver/class.pm b/gorgone/gorgone/modules/core/httpserver/class.pm index be7fb31e051..47598877c65 100644 --- a/gorgone/gorgone/modules/core/httpserver/class.pm +++ b/gorgone/gorgone/modules/core/httpserver/class.pm @@ -107,10 +107,9 @@ sub class_handle_HUP { sub event { while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); + last if (!defined($message)); $connector->{logger}->writeLogDebug("[httpserver] Event: $message"); - - last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); } } diff --git a/gorgone/gorgone/modules/core/pipeline/class.pm b/gorgone/gorgone/modules/core/pipeline/class.pm index b2bea3a6b1c..cdfd30573f2 100644 --- a/gorgone/gorgone/modules/core/pipeline/class.pm +++ b/gorgone/gorgone/modules/core/pipeline/class.pm @@ -215,7 +215,8 @@ sub check_timeout { sub event { while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); - + last if (!defined($message)); + $connector->{logger}->writeLogDebug("[pipeline] -class- event: $message"); if ($message =~ /^\[(.*?)\]/) { if ((my $method = $connector->can('action_' . lc($1)))) { @@ -225,8 +226,6 @@ sub event { $method->($connector, token => $token, data => $data); } } - - last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); } } diff --git a/gorgone/gorgone/modules/core/proxy/class.pm b/gorgone/gorgone/modules/core/proxy/class.pm index c386f15d469..883b8d402f4 100644 --- a/gorgone/gorgone/modules/core/proxy/class.pm +++ b/gorgone/gorgone/modules/core/proxy/class.pm @@ -459,6 +459,7 @@ sub event_internal { my $socket = $options{channel} eq 'control' ? $connector->{internal_socket} : $connector->{internal_channels}->{ $options{channel} }; while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $socket); + last if (!defined($message)); proxy(message => $message, channel => $options{channel}); if ($connector->{stop} == 1 && (time() - $connector->{exit_timeout}) > $connector->{stop_time}) { @@ -468,7 +469,6 @@ sub event_internal { defined($connector->{clients}->{ $options{channel} }) && ($connector->{clients}->{ $options{channel} }->{com_read_internal} == 0 || $connector->{clients}->{ $options{channel} }->{delete} == 1) ); - last unless (gorgone::standard::library::zmq_still_read(socket => $socket)); } } diff --git a/gorgone/gorgone/modules/core/pull/hooks.pm b/gorgone/gorgone/modules/core/pull/hooks.pm index 8fcaca98217..389544f0944 100644 --- a/gorgone/gorgone/modules/core/pull/hooks.pm +++ b/gorgone/gorgone/modules/core/pull/hooks.pm @@ -168,12 +168,13 @@ sub transmit_back { sub from_router { while (1) { my $message = transmit_back(message => gorgone::standard::library::zmq_dealer_read_message(socket => $socket_to_internal)); + last if (!defined($message)); + # Only send back SETLOGS and PONG if (defined($message)) { $logger->writeLogDebug("[pull] Read message from internal: $message"); $client->send_message(message => $message); } - last unless (gorgone::standard::library::zmq_still_read(socket => $socket_to_internal)); } } diff --git a/gorgone/gorgone/modules/core/register/class.pm b/gorgone/gorgone/modules/core/register/class.pm index 2e438f193d1..e5ec3207a2c 100644 --- a/gorgone/gorgone/modules/core/register/class.pm +++ b/gorgone/gorgone/modules/core/register/class.pm @@ -140,7 +140,8 @@ sub action_registerresync { sub event { while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); - + last if (!defined($message)); + $connector->{logger}->writeLogDebug("[register] Event: $message"); if ($message =~ /^\[(.*?)\]/) { if ((my $method = $connector->can('action_' . lc($1)))) { @@ -150,8 +151,6 @@ sub event { $method->($connector, token => $token, data => $data); } } - - last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); } } diff --git a/gorgone/gorgone/modules/plugins/newtest/class.pm b/gorgone/gorgone/modules/plugins/newtest/class.pm index c68afeb446f..85a91cda2d6 100644 --- a/gorgone/gorgone/modules/plugins/newtest/class.pm +++ b/gorgone/gorgone/modules/plugins/newtest/class.pm @@ -590,7 +590,8 @@ sub action_newtestresync { sub event { while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); - + last if (!defined($message)); + $connector->{logger}->writeLogDebug("gorgone-newtest: class: $message"); if ($message =~ /^\[(.*?)\]/) { if ((my $method = $connector->can('action_' . lc($1)))) { @@ -600,8 +601,6 @@ sub event { $method->($connector, token => $token, data => $data); } } - - last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); } } diff --git a/gorgone/gorgone/modules/plugins/scom/class.pm b/gorgone/gorgone/modules/plugins/scom/class.pm index 1a3225847e9..bbeddec737e 100644 --- a/gorgone/gorgone/modules/plugins/scom/class.pm +++ b/gorgone/gorgone/modules/plugins/scom/class.pm @@ -478,7 +478,8 @@ sub action_scomresync { sub event { while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); - + last if (!defined($message)); + $connector->{logger}->writeLogDebug("[scom] Event: $message"); if ($message =~ /^\[(.*?)\]/) { if ((my $method = $connector->can('action_' . lc($1)))) { @@ -488,8 +489,6 @@ sub event { $method->($connector, token => $token, data => $data); } } - - last unless (gorgone::standard::library::zmq_still_read(socket => $connector->{internal_socket})); } } diff --git a/gorgone/gorgone/standard/api.pm b/gorgone/gorgone/standard/api.pm index 8d3a0dc2880..d5508302067 100644 --- a/gorgone/gorgone/standard/api.pm +++ b/gorgone/gorgone/standard/api.pm @@ -1,267 +1,253 @@ -# -# Copyright 2019 Centreon (http://www.centreon.com/) -# -# Centreon is a full-fledged industry-strength solution that meets -# the needs in IT infrastructure and application monitoring for -# service performance. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -package gorgone::standard::api; - -use strict; -use warnings; -use gorgone::standard::library; -use ZMQ::LibZMQ4; -use ZMQ::Constants qw(:all); -use Time::HiRes; -use JSON::XS; - -my $socket; -my $result; - -sub root { - my (%options) = @_; - - $options{logger}->writeLogInfo("[api] Requesting '" . $options{uri} . "' [" . $options{method} . "]"); - - my $response; - if ($options{method} eq 'GET' && $options{uri} =~ /^\/api\/(nodes\/(\w*)\/)?log\/(.*)$/) { - $response = get_log( - socket => $options{socket}, - target => $2, - token => $3, - sync_wait => (defined($options{parameters}->{sync_wait})) ? $options{parameters}->{sync_wait} : undef, - parameters => $options{parameters} - ); - } elsif ($options{uri} =~ /^\/api\/(nodes\/(\w*)\/)?internal\/(\w+)\/?([\w\/]*?)$/ - && defined($options{api_endpoints}->{$options{method} . '_/internal/' . $3})) { - my @variables = split(/\//, $4); - $response = call_internal( - socket => $options{socket}, - action => $options{api_endpoints}->{$options{method} . '_/internal/' . $3}, - target => $2, - data => { - content => $options{content}, - parameters => $options{parameters}, - variables => \@variables, - }, - log_wait => (defined($options{parameters}->{log_wait})) ? $options{parameters}->{log_wait} : undef, - sync_wait => (defined($options{parameters}->{sync_wait})) ? $options{parameters}->{sync_wait} : undef - ); - } elsif ($options{uri} =~ /^\/api\/(nodes\/(\w*)\/)?(\w+)\/(\w+)\/(\w+)\/?([\w\/]*?)$/ - && defined($options{api_endpoints}->{$options{method} . '_/' . $3 . '/' . $4 . '/' . $5})) { - my @variables = split(/\//, $6); - $response = call_action( - socket => $options{socket}, - action => $options{api_endpoints}->{$options{method} . '_/' . $3 . '/' . $4 . '/' . $5}, - target => $2, - data => { - content => $options{content}, - parameters => $options{parameters}, - variables => \@variables, - }, - log_wait => (defined($options{parameters}->{log_wait})) ? $options{parameters}->{log_wait} : undef, - sync_wait => (defined($options{parameters}->{sync_wait})) ? $options{parameters}->{sync_wait} : undef, - ); - } else { - $response = '{"error":"method_unknown","message":"Method not implemented"}'; - } - - return $response; -} - -sub call_action { - my (%options) = @_; - - gorgone::standard::library::zmq_send_message( - socket => $options{socket}, - action => $options{action}, - target => $options{target}, - data => $options{data}, - json_encode => 1 - ); - - $socket = $options{socket}; - my $poll = [ - { - socket => $options{socket}, - events => ZMQ_POLLIN, - callback => \&event, - } - ]; - - my $rev = zmq_poll($poll, 5000); - - my $response = '{"error":"no_token","message":"Cannot retrieve token from ack"}'; - if (defined($result->{token}) && $result->{token} ne '') { - if (defined($options{log_wait}) && $options{log_wait} ne '') { - Time::HiRes::usleep($options{log_wait}); - $response = get_log( - socket => $options{socket}, - target => $options{target}, - token => $result->{token}, - sync_wait => $options{sync_wait}, - parameters => $options{data}->{parameters} - ); - } else { - $response = '{"token":"' . $result->{token} . '"}'; - } - } - - return $response; -} - -sub call_internal { - my (%options) = @_; - - $socket = $options{socket}; - my $poll = [ - { - socket => $options{socket}, - events => ZMQ_POLLIN, - callback => \&event, - } - ]; - - if (defined($options{target}) && $options{target} ne '') { - return call_action( - socket => $options{socket}, - target => $options{target}, - action => $options{action}, - data => $options{data}, - json_encode => 1, - log_wait => $options{log_wait}, - sync_wait => $options{sync_wait} - ); - } - - gorgone::standard::library::zmq_send_message( - socket => $options{socket}, - action => $options{action}, - data => $options{data}, - json_encode => 1 - ); - - my $rev = zmq_poll($poll, 5000); - - my $response = '{"error":"no_result", "message":"No result found for action \'' . $options{action} . '\'"}'; - if (defined($result->{data})) { - my $content; - eval { - $content = JSON::XS->new->utf8->decode($result->{data}); - }; - if ($@) { - $response = '{"error":"decode_error","message":"Cannot decode response"}'; - } else { - if (defined($content->{data})) { - eval { - $response = JSON::XS->new->utf8->encode($content->{data}); - }; - if ($@) { - $response = '{"error":"encode_error","message":"Cannot encode response"}'; - } - } else { - $response = ''; - } - } - } - - return $response; -} - -sub get_log { - my (%options) = @_; - - $socket = $options{socket}; - my $poll = [ - { - socket => $options{socket}, - events => ZMQ_POLLIN, - callback => \&event, - } - ]; - - if (defined($options{target}) && $options{target} ne '') { - gorgone::standard::library::zmq_send_message( - socket => $options{socket}, - target => $options{target}, - action => 'GETLOG', - json_encode => 1 - ); - - my $sync_wait = (defined($options{sync_wait}) && $options{sync_wait} ne '') ? $options{sync_wait} : '10000'; - Time::HiRes::usleep($sync_wait); - - my $rev = zmq_poll($poll, 5000); - } - - gorgone::standard::library::zmq_send_message( - socket => $options{socket}, - action => 'GETLOG', - data => { - token => $options{token}, - %{$options{parameters}} - }, - json_encode => 1 - ); - - my $rev = zmq_poll($poll, 5000); - - my $response = '{"error":"no_log","message":"No log found for token","data":[],"token":"' . $options{token} . '"}'; - if (defined($result->{data})) { - my $content; - eval { - $content = JSON::XS->new->utf8->decode($result->{data}); - }; - if ($@) { - $response = '{"error":"decode_error","message":"Cannot decode response"}'; - } elsif (defined($content->{data}->{result}) && scalar(@{$content->{data}->{result}}) > 0) { - eval { - $response = JSON::XS->new->utf8->encode( - { - message => "Logs found", - token => $options{token}, - data => $content->{data}->{result} - } - ); - }; - if ($@) { - $response = '{"error":"encode_error","message":"Cannot encode response"}'; - } - } - } - - return $response; -} - -sub event { - while (1) { - my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $socket); - - $result = {}; - if ($message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m || - $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+(.*)$/m) { - $result = { - action => $1, - token => $2, - data => $3, - }; - } - - last unless (gorgone::standard::library::zmq_still_read(socket => $socket)); - } -} - -1; +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::standard::api; + +use strict; +use warnings; +use gorgone::standard::library; +use ZMQ::LibZMQ4; +use ZMQ::Constants qw(:all); +use Time::HiRes; +use JSON::XS; + +my $socket; +my $results; +my $action_token; + +sub root { + my (%options) = @_; + + $options{logger}->writeLogInfo("[api] Requesting '" . $options{uri} . "' [" . $options{method} . "]"); + + $action_token = undef; + $socket = $options{socket}; + $results = {}; + + my $response; + if ($options{method} eq 'GET' && $options{uri} =~ /^\/api\/(nodes\/(\w*)\/)?log\/(.*)$/) { + $response = get_log( + target => $2, + token => $3, + sync_wait => (defined($options{parameters}->{sync_wait})) ? $options{parameters}->{sync_wait} : undef, + parameters => $options{parameters} + ); + } elsif ($options{uri} =~ /^\/api\/(nodes\/(\w*)\/)?internal\/(\w+)\/?([\w\/]*?)$/ + && defined($options{api_endpoints}->{$options{method} . '_/internal/' . $3})) { + my @variables = split(/\//, $4); + $response = call_internal( + action => $options{api_endpoints}->{$options{method} . '_/internal/' . $3}, + target => $2, + data => { + content => $options{content}, + parameters => $options{parameters}, + variables => \@variables + }, + log_wait => (defined($options{parameters}->{log_wait})) ? $options{parameters}->{log_wait} : undef, + sync_wait => (defined($options{parameters}->{sync_wait})) ? $options{parameters}->{sync_wait} : undef + ); + } elsif ($options{uri} =~ /^\/api\/(nodes\/(\w*)\/)?(\w+)\/(\w+)\/(\w+)\/?([\w\/]*?)$/ + && defined($options{api_endpoints}->{$options{method} . '_/' . $3 . '/' . $4 . '/' . $5})) { + my @variables = split(/\//, $6); + $response = call_action( + action => $options{api_endpoints}->{$options{method} . '_/' . $3 . '/' . $4 . '/' . $5}, + target => $2, + data => { + content => $options{content}, + parameters => $options{parameters}, + variables => \@variables + }, + log_wait => (defined($options{parameters}->{log_wait})) ? $options{parameters}->{log_wait} : undef, + sync_wait => (defined($options{parameters}->{sync_wait})) ? $options{parameters}->{sync_wait} : undef + ); + } else { + $response = '{"error":"method_unknown","message":"Method not implemented"}'; + } + + return $response; +} + +sub call_action { + my (%options) = @_; + + $action_token = gorgone::standard::library::generate_token() if (!defined($options{token})); + + gorgone::standard::library::zmq_send_message( + socket => $socket, + action => $options{action}, + target => $options{target}, + token => $action_token, + data => $options{data}, + json_encode => 1 + ); + + my $response = '{"token":"' . $action_token . '"}'; + if (defined($options{log_wait}) && $options{log_wait} ne '') { + Time::HiRes::usleep($options{log_wait}); + $response = get_log( + target => $options{target}, + token => $action_token, + sync_wait => $options{sync_wait}, + parameters => $options{data}->{parameters} + ); + } + + return $response; +} + +sub call_internal { + my (%options) = @_; + + my $poll = [ + { + socket => $socket, + events => ZMQ_POLLIN, + callback => \&event + } + ]; + + $action_token = gorgone::standard::library::generate_token(); + if (defined($options{target}) && $options{target} ne '') { + return call_action( + target => $options{target}, + action => $options{action}, + token => $action_token, + data => $options{data}, + json_encode => 1, + log_wait => $options{log_wait}, + sync_wait => $options{sync_wait} + ); + } + + gorgone::standard::library::zmq_send_message( + socket => $socket, + action => $options{action}, + token => $action_token, + data => $options{data}, + json_encode => 1 + ); + + my $rev = zmq_poll($poll, 5000); + + my $response = '{"error":"no_result", "message":"No result found for action \'' . $options{action} . '\'"}'; + if (defined($results->{$action_token}->{data})) { + my $content; + eval { + $content = JSON::XS->new->utf8->decode($results->{$action_token}->{data}); + }; + if ($@) { + $response = '{"error":"decode_error","message":"Cannot decode response"}'; + } else { + if (defined($content->{data})) { + eval { + $response = JSON::XS->new->utf8->encode($content->{data}); + }; + if ($@) { + $response = '{"error":"encode_error","message":"Cannot encode response"}'; + } + } else { + $response = ''; + } + } + } + + return $response; +} + +sub get_log { + my (%options) = @_; + + my $poll = [ + { + socket => $socket, + events => ZMQ_POLLIN, + callback => \&event + } + ]; + + if (defined($options{target}) && $options{target} ne '') { + gorgone::standard::library::zmq_send_message( + socket => $socket, + target => $options{target}, + action => 'GETLOG', + json_encode => 1 + ); + + my $sync_wait = (defined($options{sync_wait}) && $options{sync_wait} ne '') ? $options{sync_wait} : 10000; + Time::HiRes::usleep($sync_wait); + } + + gorgone::standard::library::zmq_send_message( + socket => $socket, + action => 'GETLOG', + token => $options{token}, + data => { + token => $options{token}, + %{$options{parameters}} + }, + json_encode => 1 + ); + + my $rev = zmq_poll($poll, 5000); + + my $response = '{"error":"no_log","message":"No log found for token","data":[],"token":"' . $options{token} . '"}'; + if (defined($results->{ $options{token} }) && defined($results->{ $options{token} }->{data})) { + my $content; + eval { + $content = JSON::XS->new->utf8->decode($results->{ $options{token} }->{data}); + }; + if ($@) { + $response = '{"error":"decode_error","message":"Cannot decode response"}'; + } elsif (defined($content->{data}->{result}) && scalar(@{$content->{data}->{result}}) > 0) { + eval { + $response = JSON::XS->new->utf8->encode( + { + message => "Logs found", + token => $options{token}, + data => $content->{data}->{result} + } + ); + }; + if ($@) { + $response = '{"error":"encode_error","message":"Cannot encode response"}'; + } + } + } + + return $response; +} + +sub event { + while (1) { + my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $socket); + last if (!defined($message)); + + if ($message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m || + $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+(.*)$/m) { + $results->{$2} = { + action => $1, + token => $2, + data => $3 + }; + } + } +} + +1; diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index a1548dfb504..c78ddada054 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -830,10 +830,10 @@ sub zmq_send_message { sub zmq_dealer_read_message { my (%options) = @_; - # Process all parts of the message - my $message = zmq_recvmsg($options{socket}); - my $data = zmq_msg_data($message); + my $message = zmq_recvmsg($options{socket}, ZMQ_DONTWAIT); + return undef if (!defined($message)); + my $data = zmq_msg_data($message); return $data; } @@ -841,9 +841,9 @@ sub zmq_read_message { my (%options) = @_; # Process all parts of the message - my $message = zmq_recvmsg($options{socket}); + my $message = zmq_recvmsg($options{socket}, ZMQ_DONTWAIT); if (!defined($message)) { - $options{logger}->writeLogError("[core] zmq_recvmsg error: $!"); + $options{logger}->writeLogDebug("[core] zmq_recvmsg error: $!"); return undef; } my $identity = zmq_msg_data($message); @@ -852,9 +852,9 @@ sub zmq_read_message { $options{logger}->writeLogError("[core] unknown identity: $identity"); return undef; } - $message = zmq_recvmsg($options{socket}); + $message = zmq_recvmsg($options{socket}, ZMQ_DONTWAIT); if (!defined($message)) { - $options{logger}->writeLogError("[core] zmq_recvmsg error: $!"); + $options{logger}->writeLogDebug("[core] zmq_recvmsg error: $!"); return undef; } my $data = zmq_msg_data($message); From 97051b4852c1285ceae5d199655d9f81db903a27 Mon Sep 17 00:00:00 2001 From: garnier-quentin <garnier.quentin@gmail.com> Date: Thu, 25 Feb 2021 16:09:02 +0100 Subject: [PATCH 579/948] fix(core): zmq communication issue --- gorgone/gorgone/class/core.pm | 51 ++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index d9992533e1b..5f65c32082d 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -570,44 +570,38 @@ sub router_internal_event { sub handshake { my ($self, %options) = @_; - my ($identity, $message) = gorgone::standard::library::zmq_read_message( - socket => $self->{external_socket}, - logger => $self->{logger} - ); - return undef if (!defined($identity)); - # Test if it asks for the pubkey - if ($message =~ /^\s*\[GETPUBKEY\]/) { + if ($options{message} =~ /^\s*\[GETPUBKEY\]/) { gorgone::standard::library::zmq_core_pubkey_response( socket => $self->{external_socket}, - identity => $identity, + identity => $options{identity}, pubkey => $self->{server_pubkey} ); return undef; } - my ($status, $key) = gorgone::standard::library::is_handshake_done(dbh => $self->{db_gorgone}, identity => $identity); + my ($status, $key) = gorgone::standard::library::is_handshake_done(dbh => $self->{db_gorgone}, identity => $options{identity}); if ($status == 1) { ($status, my $response) = gorgone::standard::library::uncrypt_message( cipher => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{cipher}, - message => $message, + message => $options{message}, symkey => $key, vector => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{vector} ); if ($status == 0 && $response =~ /^(?:[\[a-zA-Z-_]+?\]\s+\[.*?\]|[\[a-zA-Z-_]+?\]\s*$)/) { - gorgone::standard::library::update_identity(dbh => $self->{db_gorgone}, identity => $identity); - return ($identity, $key, $response); + gorgone::standard::library::update_identity(dbh => $self->{db_gorgone}, identity => $options{identity}); + return ($key, $response); } # Maybe he want to redo a handshake $status = 0; } - + if ($status == -1) { gorgone::standard::library::zmq_core_response( socket => $self->{external_socket}, - identity => $identity, + identity => $options{identity}, code => GORGONE_ACTION_FINISH_KO, data => { message => 'Database issue' } ); @@ -616,14 +610,14 @@ sub handshake { # We try to uncrypt ($status, my $client_pubkey) = gorgone::standard::library::is_client_can_connect( privkey => $self->{server_privkey}, - message => $message, + message => $options{message}, logger => $self->{logger}, authorized_clients => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{authorized_clients} ); if ($status == -1) { gorgone::standard::library::zmq_core_response( socket => $self->{external_socket}, - identity => $identity, + identity => $options{identity}, code => GORGONE_ACTION_FINISH_KO, data => { message => 'handshake issue' } ); @@ -636,25 +630,25 @@ sub handshake { ); if ($status == -1) { gorgone::standard::library::zmq_core_response( - socket => $self->{external_socket}, identity => $identity, + socket => $self->{external_socket}, identity => $options{identity}, code => GORGONE_ACTION_FINISH_KO, data => { message => 'handshake issue' } ); } - if (gorgone::standard::library::add_identity(dbh => $self->{db_gorgone}, identity => $identity, symkey => $symkey) == -1) { + if (gorgone::standard::library::add_identity(dbh => $self->{db_gorgone}, identity => $options{identity}, symkey => $symkey) == -1) { gorgone::standard::library::zmq_core_response( - socket => $self->{external_socket}, identity => $identity, + socket => $self->{external_socket}, identity => $options{identity}, code => GORGONE_ACTION_FINISH_KO, data => { message => 'handshake issue' } ); } if (gorgone::standard::library::zmq_core_key_response( - logger => $self->{logger}, socket => $self->{external_socket}, identity => $identity, + logger => $self->{logger}, socket => $self->{external_socket}, identity => $options{identity}, client_pubkey => $client_pubkey, hostname => $self->{hostname}, symkey => $symkey) == -1 ) { gorgone::standard::library::zmq_core_response( - socket => $self->{external_socket}, identity => $identity, + socket => $self->{external_socket}, identity => $options{identity}, code => GORGONE_ACTION_FINISH_KO, data => { message => 'handshake issue' } ); @@ -695,10 +689,19 @@ sub send_message_parent { sub router_external_event { while (1) { - my ($identity, $key, $message) = $gorgone->handshake(); - if (defined($message)) { + my ($identity, $message) = gorgone::standard::library::zmq_read_message( + socket => $gorgone->{external_socket}, + logger => $gorgone->{logger} + ); + last if (!defined($identity)); + + my ($key, $uncrypt_message) = $gorgone->handshake( + identity => $identity, + message => $message, + ); + if (defined($uncrypt_message)) { my ($token, $code, $response, $response_type) = $gorgone->message_run( - message => $message, + message => $uncrypt_message, identity => $identity, router_type => 'external', ); From 7b42fb4aa06a7f45cc62ba92b534e3cb154db2a0 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire <cgagnaire@centreon.com> Date: Wed, 3 Mar 2021 16:18:01 +0100 Subject: [PATCH 580/948] enh(doc): update autodiscovery doc (#111) --- .../docs/modules/centreon/autodiscovery.md | 83 +++++++++++++------ 1 file changed, 59 insertions(+), 24 deletions(-) diff --git a/gorgone/docs/modules/centreon/autodiscovery.md b/gorgone/docs/modules/centreon/autodiscovery.md index b021703dd3d..c81c03ec99a 100644 --- a/gorgone/docs/modules/centreon/autodiscovery.md +++ b/gorgone/docs/modules/centreon/autodiscovery.md @@ -90,7 +90,7 @@ With the following keys for the `post_execution` entry: #### Examples -#### Execute immediately without post-execution commands +##### Execute immediately without post-execution commands ```bash curl --request POST "https://hostname:8443/api/centreon/autodiscovery/hosts" \ @@ -109,7 +109,7 @@ curl --request POST "https://hostname:8443/api/centreon/autodiscovery/hosts" \ }" ``` -#### Execute immediately with post-execution commands +##### Execute immediately with post-execution commands ```bash curl --request POST "https://hostname:8443/api/centreon/autodiscovery/hosts" \ @@ -135,7 +135,7 @@ curl --request POST "https://hostname:8443/api/centreon/autodiscovery/hosts" \ }" ``` -#### Schedule execution without post-execution commands +##### Schedule execution without post-execution commands ```bash curl --request POST "https://hostname:8443/api/centreon/autodiscovery/hosts" \ @@ -156,7 +156,7 @@ curl --request POST "https://hostname:8443/api/centreon/autodiscovery/hosts" \ }" ``` -#### Schedule execution with post-execution commands +##### Schedule execution with post-execution commands ```bash curl --request POST "https://hostname:8443/api/centreon/autodiscovery/hosts" \ @@ -249,35 +249,70 @@ curl --request DELETE "https://hostname:8443/api/centreon/autodiscovery/hosts/di #### Body -| Key | Value | Description | -|:---------------------|:------|:------------------------------------------------------------------------------| -| filter\_rules | array | Run the selected rule of discovery | -| force\_rule | `1|0` | Run also disabled rules | -| filter\_hosts | array | Run all discovery rules linked to all templates of host used by selected host | -| filter\_pollers | array | Run all discovery rules linked to all poller linked with rule | -| manual | `1|0` | Run discovery for manual scan | -| dry\_run | `1|0` | Run discovery without configuration change | -| no\_generate\_config | `1|0` | No configuration generation (even if there is some changes) | +| Key | Value | +|:---------------------|:--------------------------------------------------------------------------------------------------| +| filter\_rules | Array of rules to use for discovery (empty means all) | +| force\_rule | Run disabled rules ('0': not forced, '1': forced) | +| filter\_hosts | Array of hosts against which run the discovery (empty means all) | +| filter\_pollers | Array of pollers for which linked hosts will be discovered against (empty means all) | +| manual | Run discovery for manual scan from web UI ('0': automatic, '1': manual) | +| dry\_run | Run discovery without configuration change ('0': changes, '1': dry run) | +| no\_generate\_config | No configuration generation (even if there is some changes) ('0': generation, '1': no generation) | ```json { - "filter_rules": ["<rule1>", "<rule2>", ...], - "force_rule": <1|0>, - "filter_hosts": ["<host1>", "<host2>", ...], - "filter_pollers": ["<poller1>", "<poller2>", ...], - "manual": <1|0>, - "dry_run": <1|0>, - "no_generate_config": <1|0> + "filter_rules": "<array of rules>", + "force_rule": "<run disabled rules>", + "filter_hosts": "<array of hosts>", + "filter_pollers": "<array of pollers>", + "manual": "<manual scan>", + "dry_run": "<run without changes>", + "no_generate_config": "<no configuration generation>" } ``` -#### Example +#### Examples + +##### Execute discovery with defined rules (even if disabled) + +```bash +curl --request POST "https://hostname:8443/api/centreon/autodiscovery/services" \ + --header "Accept: application/json" \ + --header "Content-Type: application/json" \ + --data "{ + \"filter_rules\": [ + \"OS-Linux-SNMP-Disk-Name\", + \"OS-Linux-SNMP-Traffic-Name\" + ], + \"force_rule\": 1 +}" +``` + +##### Execute discovery for defined hosts ```bash curl --request POST "https://hostname:8443/api/centreon/autodiscovery/services" \ --header "Accept: application/json" \ --header "Content-Type: application/json" \ - --data '{ - "filter_rules": ["OS-Linux-SNMP-Disk-Name", "OS-Linux-SNMP-Traffic-Name"] -}' + --data "{ + \"filter_hosts\": [ + \"Host-1\", + \"Host-2\", + \"Host-3\" + ] +}" +``` + +##### Execute discovery for defined poller (without changes) + +```bash +curl --request POST "https://hostname:8443/api/centreon/autodiscovery/services" \ + --header "Accept: application/json" \ + --header "Content-Type: application/json" \ + --data "{ + \"filter_pollers\": [ + \"Poller-1\" + ], + \"dry_run\": 1 +}" ``` From cb16ce7b07160ae581b9636477a60ed4f8ac5d1b Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Thu, 4 Mar 2021 10:34:12 +0100 Subject: [PATCH 581/948] Fix com chain zmq zmq ssh (#112) (MON-6762) * wip * enh(sshclient): processcopy action working --- .../modules/centreon/legacycmd/class.pm | 45 +++++------ gorgone/gorgone/modules/core/proxy/class.pm | 3 +- .../gorgone/modules/core/proxy/sshclient.pm | 79 ++++++++++++++++++- 3 files changed, 100 insertions(+), 27 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index 3481de517df..f19005a293a 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -265,7 +265,8 @@ sub execute_cmd { owner => 'centreon-engine', group => 'centreon-engine', metadata => { - centcore_cmd => 'SENDCFGFILE', + centcore_proxy => 1, + centcore_cmd => 'SENDCFGFILE' } } }, @@ -284,10 +285,10 @@ sub execute_cmd { group => 'centreon-broker', metadata => { centcore_proxy => 1, - centcore_cmd => 'SENDCFGFILE', + centcore_cmd => 'SENDCFGFILE' } } - }, + } ); } elsif ($options{cmd} eq 'SENDEXPORTFILE') { if (!defined($self->{clapi_password})) { @@ -311,10 +312,10 @@ sub execute_cmd { owner => 'centreon', group => 'centreon', metadata => { - centcore_cmd => 'SENDEXPORTFILE', + centcore_cmd => 'SENDEXPORTFILE' } } - }, + } ); # Forward data use to be done by createRemoteTask as well as task_id in a gorgone command @@ -326,7 +327,7 @@ sub execute_cmd { target => $options{target}, data => { content => { - parent_id => $options{param}, + parent_id => $options{param} } } ); @@ -349,10 +350,10 @@ sub execute_cmd { group => 'centreon', metadata => { centcore_proxy => 1, - centcore_cmd => 'SYNCTRAP', + centcore_cmd => 'SYNCTRAP' } } - }, + } ); } elsif ($options{cmd} eq 'RESTART') { my $cmd = $self->{pollers}->{$options{target}}->{engine_restart_command}; @@ -366,11 +367,11 @@ sub execute_cmd { command => 'sudo ' . $cmd, metadata => { centcore_proxy => 1, - centcore_cmd => 'RESTART', + centcore_cmd => 'RESTART' } } ] - }, + } ); } elsif ($options{cmd} eq 'RELOAD') { my $cmd = $self->{pollers}->{$options{target}}->{engine_reload_command}; @@ -402,11 +403,11 @@ sub execute_cmd { command => 'sudo ' . $cmd, metadata => { centcore_proxy => 1, - centcore_cmd => 'START', + centcore_cmd => 'START' } } ] - }, + } ); } elsif ($options{cmd} eq 'STOP') { my $cmd = $self->{pollers}->{$options{target}}->{engine_stop_command}; @@ -420,11 +421,11 @@ sub execute_cmd { command => 'sudo ' . $cmd, metadata => { centcore_proxy => 1, - centcore_cmd => 'STOP', + centcore_cmd => 'STOP' } } ] - }, + } ); } elsif ($options{cmd} eq 'RELOADBROKER') { my $cmd = $self->{pollers}->{$options{target}}->{broker_reload_command}; @@ -438,11 +439,11 @@ sub execute_cmd { command => 'sudo ' . $cmd, metadata => { centcore_proxy => 1, - centcore_cmd => 'RELOADBROKER', + centcore_cmd => 'RELOADBROKER' } } ] - }, + } ); } elsif ($options{cmd} eq 'RESTARTCENTREONTRAPD') { my $cmd = $self->{pollers}->{$options{target}}->{init_script_centreontrapd}; @@ -456,11 +457,11 @@ sub execute_cmd { command => 'sudo service ' . $cmd . ' restart', metadata => { centcore_proxy => 1, - centcore_cmd => 'RESTARTCENTREONTRAPD', + centcore_cmd => 'RESTARTCENTREONTRAPD' } } ] - }, + } ); } elsif ($options{cmd} eq 'RELOADCENTREONTRAPD') { my $cmd = $self->{pollers}->{$options{target}}->{init_script_centreontrapd}; @@ -474,11 +475,11 @@ sub execute_cmd { command => 'sudo service ' . $cmd . ' reload', metadata => { centcore_proxy => 1, - centcore_cmd => 'RELOADCENTREONTRAPD', + centcore_cmd => 'RELOADCENTREONTRAPD' } } ] - }, + } ); } elsif ($options{cmd} eq 'STARTWORKER') { if (!defined($self->{clapi_password})) { @@ -497,11 +498,11 @@ sub execute_cmd { { command => $cmd, metadata => { - centcore_cmd => 'STARTWORKER', + centcore_cmd => 'STARTWORKER' } } ] - }, + } ); } diff --git a/gorgone/gorgone/modules/core/proxy/class.pm b/gorgone/gorgone/modules/core/proxy/class.pm index 883b8d402f4..e543075466d 100644 --- a/gorgone/gorgone/modules/core/proxy/class.pm +++ b/gorgone/gorgone/modules/core/proxy/class.pm @@ -320,7 +320,8 @@ sub proxy_ssh { action => $options{action}, data => $decoded_data, target_direct => $options{target_direct}, - target => $options{target} + target => $options{target}, + token => $options{token} ); if (ref($data_ret) eq 'ARRAY') { diff --git a/gorgone/gorgone/modules/core/proxy/sshclient.pm b/gorgone/gorgone/modules/core/proxy/sshclient.pm index b617a260f3d..868b07868b2 100644 --- a/gorgone/gorgone/modules/core/proxy/sshclient.pm +++ b/gorgone/gorgone/modules/core/proxy/sshclient.pm @@ -31,6 +31,7 @@ use File::Basename; use Time::HiRes; use gorgone::standard::constants qw(:all); use Encode; +use MIME::Base64; sub new { my ($class, %options) = @_; @@ -380,15 +381,84 @@ sub action_enginecommand { return (0, $results); } +sub action_processcopy { + my ($self, %options) = @_; + + if (!defined($options{data}->{content}->{status}) || $options{data}->{content}->{status} !~ /^(?:inprogress|end)$/) { + $self->{logger}->writeLogError('[sshclient] Action process copy - need status'); + return (-1, { message => 'please set status' }); + } + if (!defined($options{data}->{content}->{type}) || $options{data}->{content}->{type} !~ /^(?:archive|regular)$/) { + $self->{logger}->writeLogError('[sshclient] Action process copy - need type'); + return (-1, { message => 'please set type' }); + } + if (!defined($options{data}->{content}->{cache_dir}) || $options{data}->{content}->{cache_dir} eq '') { + $self->{logger}->writeLogError('[sshclient] Action process copy - need cache_dir'); + return (-1, { message => 'please set cache_dir' }); + } + if ($options{data}->{content}->{status} eq 'end' && + (!defined($options{data}->{content}->{destination}) || $options{data}->{content}->{destination} eq '')) { + $self->{logger}->writeLogError('[sshclient] Action process copy - need destination'); + return (-1, { message => 'please set destination' }); + } + + my $copy_local_file = $options{data}->{content}->{cache_dir} . '/copy_local_' . $options{token}; + if ($options{data}->{content}->{status} eq 'inprogress') { + my $fh; + if (!sysopen($fh, $copy_local_file, O_RDWR|O_APPEND|O_CREAT, 0660)) { + return (-1, { message => "file '$copy_local_file' open failed: $!" }); + } + binmode($fh); + syswrite( + $fh, + MIME::Base64::decode_base64($options{data}->{content}->{chunk}->{data}), + $options{data}->{content}->{chunk}->{size} + ); + close $fh; + + return (0, [{ + code => GORGONE_MODULE_ACTION_PROCESSCOPY_INPROGRESS, + data => { + message => 'process copy inprogress' + } + }]); + } + if ($options{data}->{content}->{status} eq 'end') { + my $copy_file = $options{data}->{content}->{cache_dir} . '/copy_' . $options{token}; + my $code = $self->{sftp}->copy_file(src => $copy_local_file, dst => $copy_file); + unlink($copy_local_file); + if ($code == -1) { + return (-1, { message => "cannot sftp copy file : " . $self->{sftp}->error() }); + } + + if ($options{data}->{content}->{type} eq 'archive') { + return $self->action_command( + data => { + content => [ { command => "tar zxf $copy_file -C '" . $options{data}->{content}->{destination} . "' ." } ] + } + ); + } + if ($options{data}->{content}->{type} eq 'regular') { + return $self->action_command( + data => { + content => [ { command => "cp -f $copy_file '$options{data}->{content}->{destination}'" } ] + } + ); + } + } + + return (-1, { message => 'process copy unknown error' }); +} + sub action_remotecopy { my ($self, %options) = @_; if (!defined($options{data}->{content}->{source}) || $options{data}->{content}->{source} eq '') { - $self->{logger}->writeLogError('[sshclient] Action remote copy - Need source'); + $self->{logger}->writeLogError('[sshclient] Action remote copy - need source'); return (-1, { message => 'please set source' }); } if (!defined($options{data}->{content}->{destination}) || $options{data}->{content}->{destination} eq '') { - $self->{logger}->writeLogError('[sshclient] Action remote copy - Need destination'); + $self->{logger}->writeLogError('[sshclient] Action remote copy - need destination'); return (-1, { message => 'please set destination' }); } @@ -426,7 +496,7 @@ sub action_remotecopy { ($code, $data) = $self->action_command( data => { content => [ { command => "tar zxf $dst_sftp -C '" . $dst . "' ." } ] - }, + } ); return ($code, $data) if ($code == -1); } @@ -454,7 +524,8 @@ sub action { $self, data => $options{data}, target_direct => $options{target_direct}, - target => $options{target} + target => $options{target}, + token => $options{token} ); } From 102e778daa16bd6058107a4d98f999b62569fb9b Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Fri, 5 Mar 2021 13:45:00 +0100 Subject: [PATCH 582/948] enh(debug): curl ssl binary debug removed + httpserver ssl added (#113) (MON-6814) --- gorgone/gorgone/class/http/backend/curl.pm | 6 +- .../gorgone/modules/core/httpserver/class.pm | 118 ++++++++++-------- 2 files changed, 67 insertions(+), 57 deletions(-) diff --git a/gorgone/gorgone/class/http/backend/curl.pm b/gorgone/gorgone/class/http/backend/curl.pm index 203d2d52e43..3c12e824491 100644 --- a/gorgone/gorgone/class/http/backend/curl.pm +++ b/gorgone/gorgone/class/http/backend/curl.pm @@ -118,7 +118,8 @@ sub cb_debug { $msg = sprintf("=> Send data: %s", $data); } if ($type == $uservar->{constant_cb}->(name => 'CURLINFO_SSL_DATA_OUT')) { - $msg = sprintf("=> Send SSL data: %s", $data); + #$msg = sprintf("=> Send SSL data: %s", $data); + return 0; } if ($type == $uservar->{constant_cb}->(name => 'CURLINFO_HEADER_IN')) { $msg = sprintf("=> Recv header: %s", $data); @@ -127,7 +128,8 @@ sub cb_debug { $msg = sprintf("=> Recv data: %s", $data); } if ($type == $uservar->{constant_cb}->(name => 'CURLINFO_SSL_DATA_IN')) { - $msg = sprintf("=> Recv SSL data: %s", $data); + #$msg = sprintf("=> Recv SSL data: %s", $data); + return 0; } $uservar->{logger}->writeLogDebug($msg); diff --git a/gorgone/gorgone/modules/core/httpserver/class.pm b/gorgone/gorgone/modules/core/httpserver/class.pm index 47598877c65..9994114bd13 100644 --- a/gorgone/gorgone/modules/core/httpserver/class.pm +++ b/gorgone/gorgone/modules/core/httpserver/class.pm @@ -134,19 +134,19 @@ sub check_allowed_host { sub load_peer_subnets { my ($self, %options) = @_; - return if ($connector->{allowed_hosts_enabled} == 0); + return if ($self->{allowed_hosts_enabled} == 0); - $connector->{peer_subnets} = []; + $self->{peer_subnets} = []; return if (!defined($connector->{config}->{allowed_hosts}->{subnets})); - foreach (@{$connector->{config}->{allowed_hosts}->{subnets}}) { + foreach (@{$self->{config}->{allowed_hosts}->{subnets}}) { my $subnet = NetAddr::IP->new($_); if (!defined($subnet)) { $self->{logger}->writeLogError("[httpserver] Cannot load subnet: $_"); next; } - push @{$connector->{peer_subnets}}, $subnet; + push @{$self->{peer_subnets}}, $subnet; } } @@ -181,14 +181,16 @@ sub run { $self->init_dispatch(); # HTTP daemon - my $daemon; + my ($daemon, $message_error); if ($self->{config}->{ssl} eq 'false') { + $message_error = '$@'; $daemon = HTTP::Daemon->new( LocalAddr => $self->{config}->{address} . ':' . $self->{config}->{port}, ReusePort => 1, Timeout => 5 ); } elsif ($self->{config}->{ssl} eq 'true') { + $message_error = '$!, ssl_error=$IO::Socket::SSL::SSL_ERROR'; $daemon = HTTP::Daemon::SSL->new( LocalAddr => $self->{config}->{address} . ':' . $self->{config}->{port}, SSL_cert_file => $self->{config}->{ssl_cert_file}, @@ -199,79 +201,85 @@ sub run { ); } - if (defined($daemon)) { - while (1) { - my ($connection) = $daemon->accept(); + if (!defined($daemon)) { + eval "\$message_error = \"$message_error\""; + $connector->{logger}->writeLogError("[httpserver] can't construct socket: $message_error"); + exit(1); + } - if ($self->{stop} == 1) { - $self->{logger}->writeLogInfo("[httpserver] $$ has quit"); - $connection->close() if (defined($connection)); - zmq_close($connector->{internal_socket}); - exit(0); - } + while (1) { + my ($connection) = $daemon->accept(); - next if (!defined($connection)); + if ($self->{stop} == 1) { + $self->{logger}->writeLogInfo("[httpserver] $$ has quit"); + $connection->close() if (defined($connection)); + zmq_close($connector->{internal_socket}); + exit(0); + } - while (my $request = $connection->get_request) { - if ($connection->antique_client eq '1') { - $connection->force_last_request; - next; - } + next if (!defined($connection)); - my $msg = "[httpserver] " . $connection->peerhost() . " " . $request->method . " '" . $request->uri->path . "'"; - $msg .= " '" . $request->header("User-Agent") . "'" if (defined($request->header("User-Agent")) && $request->header("User-Agent") ne ''); - $connector->{logger}->writeLogInfo($msg); - - if ($connector->{allowed_hosts_enabled} == 1) { - if ($connector->check_allowed_host(peer_addr => inet_ntoa($connection->peeraddr())) == 0) { - $connector->{logger}->writeLogError("[httpserver] " . $connection->peerhost() . " Unauthorized"); - $self->send_error( - connection => $connection, - code => "401", - response => '{"error":"http_error_401","message":"unauthorized"}' - ); - next; - } - } + while (my $request = $connection->get_request) { + if ($connection->antique_client eq '1') { + $connection->force_last_request; + next; + } - if ($self->authentication($request->header('Authorization'))) { # Check Basic authentication - my ($root) = ($request->uri->path =~ /^(\/\w+)/); - - if ($root eq "/api") { # API - $self->send_response(connection => $connection, response => $self->api_call($request)); - } elsif (defined($self->{dispatch}->{$root})) { # Other dispatch definition - $self->send_response(connection => $connection, response => $self->dispatch_call(root => $root, request => $request)); - } else { # Forbidden - $connector->{logger}->writeLogError("[httpserver] " . $connection->peerhost() . " '" . $request->uri->path . "' Forbidden"); - $self->send_error( - connection => $connection, - code => "403", - response => '{"error":"http_error_403","message":"forbidden"}' - ); - } - } else { # Authen error + my $msg = "[httpserver] " . $connection->peerhost() . " " . $request->method . " '" . $request->uri->path . "'"; + $msg .= " '" . $request->header("User-Agent") . "'" if (defined($request->header("User-Agent")) && $request->header("User-Agent") ne ''); + $connector->{logger}->writeLogInfo($msg); + + if ($connector->{allowed_hosts_enabled} == 1) { + if ($connector->check_allowed_host(peer_addr => inet_ntoa($connection->peeraddr())) == 0) { $connector->{logger}->writeLogError("[httpserver] " . $connection->peerhost() . " Unauthorized"); $self->send_error( connection => $connection, code => "401", response => '{"error":"http_error_401","message":"unauthorized"}' ); + next; } - $connection->force_last_request; } - $connection->close; - undef($connection); + + if ($self->authentication($request->header('Authorization'))) { # Check Basic authentication + my ($root) = ($request->uri->path =~ /^(\/\w+)/); + + if ($root eq "/api") { # API + $self->send_response(connection => $connection, response => $self->api_call($request)); + } elsif (defined($self->{dispatch}->{$root})) { # Other dispatch definition + $self->send_response(connection => $connection, response => $self->dispatch_call(root => $root, request => $request)); + } else { # Forbidden + $connector->{logger}->writeLogError("[httpserver] " . $connection->peerhost() . " '" . $request->uri->path . "' Forbidden"); + $self->send_error( + connection => $connection, + code => "403", + response => '{"error":"http_error_403","message":"forbidden"}' + ); + } + } else { # Authen error + $connector->{logger}->writeLogError("[httpserver] " . $connection->peerhost() . " Unauthorized"); + $self->send_error( + connection => $connection, + code => "401", + response => '{"error":"http_error_401","message":"unauthorized"}' + ); + } + $connection->force_last_request; } + $connection->close; + undef($connection); } } sub ssl_error { my ($self, $error) = @_; + chomp $error; + $connector->{logger}->writeLogError("[httpserver] ssl error: $error"); ${*$self}{httpd_client_proto} = 1000; ${*$self}{httpd_daemon} = HTTP::Daemon::SSL::DummyDaemon->new(); $self->send_error(RC_BAD_REQUEST); - $self->close; + $self->close(); } sub authentication { From b61503594bd6986d6556488cdd2526619a0e98cd Mon Sep 17 00:00:00 2001 From: sc979 <34628915+sc979@users.noreply.github.com> Date: Thu, 1 Apr 2021 10:01:46 +0200 Subject: [PATCH 583/948] enh(chore): integrate sonarQube dev edition (#114) --- gorgone/Jenkinsfile | 20 ++++++++++++++++---- gorgone/sonar-project.properties | 6 ++++-- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/gorgone/Jenkinsfile b/gorgone/Jenkinsfile index 2c0608e6bca..9cb61e05d2d 100644 --- a/gorgone/Jenkinsfile +++ b/gorgone/Jenkinsfile @@ -24,15 +24,27 @@ stage('Source') { source = readProperties file: 'source.properties' env.VERSION = "${source.VERSION}" env.RELEASE = "${source.RELEASE}" - if ((env.BUILD == 'RELEASE') || (env.BUILD == 'REFERENCE')) { - withSonarQubeEnv('SonarQube') { - sh "./centreon-build/jobs/gorgone/${serie}/gorgone-analysis.sh" - } + // Run sonarQube analysis + withSonarQubeEnv('SonarQubeDev') { + sh "./centreon-build/jobs/gorgone/${serie}/gorgone-analysis.sh" } } } try { + // sonarQube step to get qualityGate result + stage('Quality gate') { + timeout(time: 10, unit: 'MINUTES') { + def qualityGate = waitForQualityGate() + if (qualityGate.status != 'OK') { + currentBuild.result = 'FAIL' + } + } + if ((currentBuild.result ?: 'SUCCESS') != 'SUCCESS') { + error('Quality gate failure: ${qualityGate.status}.'); + } + } + stage('Package') { parallel 'centos7': { node { diff --git a/gorgone/sonar-project.properties b/gorgone/sonar-project.properties index 358e82d7c62..e3ad7c69b6b 100644 --- a/gorgone/sonar-project.properties +++ b/gorgone/sonar-project.properties @@ -1,3 +1,5 @@ -sonar.projectKey=centreon-gorgone-21.04 -sonar.projectName=Centreon Gorgone 21.04 +# project +sonar.projectKey={PROJECT_TITLE} +sonar.projectName={PROJECT_NAME} +sonar.projectVersion={PROJECT_VERSION} sonar.sources=. From 5826e3859b405e676b1296627fe38af98242922e Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Mon, 12 Apr 2021 17:20:53 +0200 Subject: [PATCH 584/948] chore(version): upgrade to 21.04.0-beta.2 --- gorgone/packaging/centreon-gorgone.spectemplate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index af376a68706..8944677571e 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -1,5 +1,5 @@ Name: centreon-gorgone -Version: 21.04.0 +Version: 21.04.0-beta.2 Release: @RELEASE@%{?dist} Summary: Perl daemon task handlers Group: Applications/System From b0291ccffe94dca26585be76e5bcfa700616172a Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Mon, 12 Apr 2021 17:28:50 +0200 Subject: [PATCH 585/948] chore(packaging): fix release number --- gorgone/packaging/centreon-gorgone.spectemplate | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 8944677571e..94c1a23715b 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -1,6 +1,6 @@ Name: centreon-gorgone -Version: 21.04.0-beta.2 -Release: @RELEASE@%{?dist} +Version: 21.04.0 +Release: beta.2.@RELEASE@%{?dist} Summary: Perl daemon task handlers Group: Applications/System License: Apache2 From 8e923337332084ef8308e19136869dd26395ff65 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Fri, 16 Apr 2021 09:12:11 +0200 Subject: [PATCH 586/948] MON-7137: add logging options (#120) --- gorgone/gorgone/class/module.pm | 2 + .../modules/centreon/autodiscovery/class.pm | 4 +- .../autodiscovery/services/discovery.pm | 2 +- .../gorgone/modules/centreon/engine/class.pm | 8 ++++ .../modules/centreon/legacycmd/class.pm | 38 +++++++++++++++---- .../modules/centreon/statistics/class.pm | 4 +- gorgone/gorgone/modules/core/action/class.pm | 19 +++++++++- gorgone/gorgone/modules/core/proxy/class.pm | 5 ++- gorgone/gorgone/modules/core/proxy/hooks.pm | 5 ++- .../gorgone/modules/core/proxy/sshclient.pm | 1 - 10 files changed, 71 insertions(+), 17 deletions(-) diff --git a/gorgone/gorgone/class/module.pm b/gorgone/gorgone/class/module.pm index 74b5403d978..9240406bb49 100644 --- a/gorgone/gorgone/class/module.pm +++ b/gorgone/gorgone/class/module.pm @@ -109,6 +109,8 @@ sub send_log { return if (!defined($options{token})); + return if (defined($options{logging}) && $options{logging} =~ /^(?:false|0)$/); + gorgone::standard::library::zmq_send_message( socket => (defined($options{socket})) ? $options{socket} : $self->{internal_socket}, action => 'PUTLOG', diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm index 19f3cbdb352..c86be239ac5 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm @@ -480,9 +480,9 @@ sub launchhostdiscovery { target => $self->{hdisco_jobs_ids}->{$job_id}->{target}, token => $self->{hdisco_jobs_ids}->{$job_id}->{token}, data => { + instant => 1, content => [ { - instant => 1, command => $self->{hdisco_jobs_ids}->{$job_id}->{command_line}, timeout => $timeout, metadata => { @@ -742,9 +742,9 @@ sub discovery_command_result { action => $post_command->{action}, token => $self->{hdisco_jobs_ids}->{$job_id}->{token}, data => { + instant => 1, content => [ { - instant => 1, command => $post_command->{command_line}, metadata => { job_id => $job_id, diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm index b824cfcb4e3..6b773c64d14 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm @@ -739,9 +739,9 @@ sub service_execute_commands { target => $poller_id, token => 'svc-disco-' . $self->{uuid} . '-' . $rule_id . '-' . $host_id, data => { + instant => 1, content => [ { - instant => 1, command => $command, timeout => 90 } diff --git a/gorgone/gorgone/modules/centreon/engine/class.pm b/gorgone/gorgone/modules/centreon/engine/class.pm index cce95f6eaaf..7a1a0d869c8 100644 --- a/gorgone/gorgone/modules/centreon/engine/class.pm +++ b/gorgone/gorgone/modules/centreon/engine/class.pm @@ -91,6 +91,7 @@ sub action_enginecommand { socket => $options{socket_log}, code => GORGONE_ACTION_BEGIN, token => $options{token}, + logging => $options{data}->{logging}, data => { message => "commands processing has started", request_content => $options{data}->{content} @@ -103,6 +104,7 @@ sub action_enginecommand { socket => $options{socket_log}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, + logging => $options{data}->{logging}, data => { message => "need command_file (config or call) argument" } @@ -115,6 +117,7 @@ sub action_enginecommand { socket => $options{socket_log}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, + logging => $options{data}->{logging}, data => { message => "command file '$command_file' must exist" } @@ -127,6 +130,7 @@ sub action_enginecommand { socket => $options{socket_log}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, + logging => $options{data}->{logging}, data => { message => "command file '$command_file' must be a pipe file" } @@ -139,6 +143,7 @@ sub action_enginecommand { socket => $options{socket_log}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, + logging => $options{data}->{logging}, data => { message => "command file '$command_file' must be writeable" } @@ -159,6 +164,7 @@ sub action_enginecommand { socket => $options{socket_log}, code => GORGONE_ACTION_FINISH_OK, token => $options{token}, + logging => $options{data}->{logging}, data => { message => "command has been submitted", command => $command @@ -176,6 +182,7 @@ sub action_enginecommand { socket => $options{socket_log}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, + logging => $options{data}->{logging}, data => { message => "submit engine command issue: $@" } @@ -187,6 +194,7 @@ sub action_enginecommand { socket => $options{socket_log}, code => GORGONE_ACTION_FINISH_OK, token => $options{token}, + logging => $options{data}->{logging}, data => { message => "commands processing has finished" } diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index f19005a293a..a933e8d01c7 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -181,6 +181,7 @@ sub send_external_commands { target => $target, token => $token, data => { + logging => $options{logging}, content => { command_file => $self->{pollers}->{$target}->{command_file}, commands => [ @@ -206,6 +207,7 @@ sub add_external_command { target => $options{target}, token => $options{token}, data => { + logging => $options{logging}, content => { command_file => $self->{pollers}->{ $options{target} }->{command_file}, commands => [ @@ -241,7 +243,8 @@ sub execute_cmd { action => $options{action}, param => $options{param}, target => $options{target}, - token => $options{token} + token => $options{token}, + logging => $options{logging} ); return 0; } @@ -258,6 +261,7 @@ sub execute_cmd { target => $options{target}, token => $token, data => { + logging => $options{logging}, content => { source => $cache_dir . '/config/engine/' . $options{target}, destination => $self->{pollers}->{$options{target}}->{cfg_dir} . '/', @@ -269,7 +273,7 @@ sub execute_cmd { centcore_cmd => 'SENDCFGFILE' } } - }, + } ); # broker $self->send_internal_action( @@ -277,6 +281,7 @@ sub execute_cmd { target => $options{target}, token => $token, data => { + logging => $options{logging}, content => { source => $cache_dir . '/config/broker/' . $options{target}, destination => $self->{pollers}->{$options{target}}->{centreonbroker_cfg_path} . '/', @@ -305,6 +310,7 @@ sub execute_cmd { target => $options{target}, token => $token, data => { + logging => $options{logging}, content => { source => $cache_dir . '/config/export/' . $options{target}, destination => $remote_dir, @@ -326,6 +332,7 @@ sub execute_cmd { token => $options{token}, target => $options{target}, data => { + logging => $options{logging}, content => { parent_id => $options{param} } @@ -342,6 +349,7 @@ sub execute_cmd { target => $options{target}, token => $token, data => { + logging => $options{logging}, content => { source => $cache_dir_trap . '/' . $options{target} . '/centreontrapd.sdb', destination => $self->{pollers}->{$options{target}}->{snmp_trapd_path_conf} . '/', @@ -362,6 +370,7 @@ sub execute_cmd { target => $options{target}, token => $token, data => { + logging => $options{logging}, content => [ { command => 'sudo ' . $cmd, @@ -380,6 +389,7 @@ sub execute_cmd { target => $options{target}, token => $token, data => { + logging => $options{logging}, content => [ { command => 'sudo ' . $cmd, @@ -389,7 +399,7 @@ sub execute_cmd { } } ] - }, + } ); } elsif ($options{cmd} eq 'START') { my $cmd = $self->{pollers}->{$options{target}}->{engine_start_command}; @@ -398,6 +408,7 @@ sub execute_cmd { target => $options{target}, token => $token, data => { + logging => $options{logging}, content => [ { command => 'sudo ' . $cmd, @@ -416,6 +427,7 @@ sub execute_cmd { target => $options{target}, token => $token, data => { + logging => $options{logging}, content => [ { command => 'sudo ' . $cmd, @@ -434,6 +446,7 @@ sub execute_cmd { target => $options{target}, token => $token, data => { + logging => $options{logging}, content => [ { command => 'sudo ' . $cmd, @@ -452,6 +465,7 @@ sub execute_cmd { target => $options{target}, token => $token, data => { + logging => $options{logging}, content => [ { command => 'sudo service ' . $cmd . ' restart', @@ -470,6 +484,7 @@ sub execute_cmd { target => $options{target}, token => $token, data => { + logging => $options{logging}, content => [ { command => 'sudo service ' . $cmd . ' reload', @@ -494,6 +509,7 @@ sub execute_cmd { target => undef, token => $token, data => { + logging => $options{logging}, content => [ { command => $cmd, @@ -516,6 +532,7 @@ sub action_addimporttaskwithparent { $self->send_log( code => GORGONE_ACTION_FINISH_KO, token => $options{token}, + logging => $options{data}->{logging}, data => { message => "expected parent_id task ID, found '" . $options{data}->{content}->{parent_id} . "'", } @@ -530,6 +547,7 @@ sub action_addimporttaskwithparent { $self->send_log( code => GORGONE_ACTION_FINISH_KO, token => $options{token}, + logging => $options{data}->{logging}, data => { message => "Cannot add import task on Remote Server.", } @@ -545,17 +563,19 @@ sub action_addimporttaskwithparent { action => 'COMMAND', token => $options{token}, data => { + logging => $options{data}->{logging}, content => [ { - command => $cmd, + command => $cmd } ] - }, + } ); $self->send_log( code => GORGONE_ACTION_FINISH_OK, token => $options{token}, + logging => $options{data}->{logging}, data => { message => 'Task inserted on Remote Server', } @@ -610,7 +630,7 @@ sub handle_file { } if ($line =~ /^(.*?):([^:]*)(?::(.*)){0,1}/) { - $self->execute_cmd(action => 0, cmd => $1, target => $2, param => $3); + $self->execute_cmd(action => 0, cmd => $1, target => $2, param => $3, logging => 0); if ($self->{config}->{dirty_mode} != 1) { my $current_pos = tell($handle); seek($handle, $current_pos - bytes::length($line), 0); @@ -678,7 +698,7 @@ sub handle_cmd_files { return if ($self->check_pollers_config() == 0); $self->handle_centcore_cmd(); $self->handle_centcore_dir(); - $self->send_external_commands(); + $self->send_external_commands(logging => 0); } sub action_centreoncommand { @@ -700,6 +720,7 @@ sub action_centreoncommand { } if ($self->check_pollers_config() == 0) { + $self->{logger}->writeLogError('[legacycmd] cannot get centreon database configuration'); $self->send_log(code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot get centreon database configuration' }); return 1; } @@ -710,7 +731,8 @@ sub action_centreoncommand { token => $options{token}, target => $command->{target}, cmd => $command->{command}, - param => $command->{param} + param => $command->{param}, + logging => 1 ); if ($code == -1) { diff --git a/gorgone/gorgone/modules/centreon/statistics/class.pm b/gorgone/gorgone/modules/centreon/statistics/class.pm index 4ed00fcc13c..0863511d30b 100644 --- a/gorgone/gorgone/modules/centreon/statistics/class.pm +++ b/gorgone/gorgone/modules/centreon/statistics/class.pm @@ -195,9 +195,9 @@ sub action_brokerstats { action => 'COMMAND', token => $options{token} . '-' . $target, data => { + instant => 1, content => [ { - instant => 1, command => 'cat ' . $statistics_file, timeout => $options{data}->{content}->{timeout}, metadata => { @@ -267,9 +267,9 @@ sub action_enginestats { action => 'COMMAND', token => $options{token} . '-' . $target, data => { + instant => 1, content => [ { - instant => 1, command => $enginestats_file . ' -c ' . $config_file, timeout => $options{data}->{content}->{timeout}, metadata => { diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index 910e47f5c13..f5ef206db6a 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -95,6 +95,7 @@ sub action_command { socket => $options{socket_log}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, + logging => $options{data}->{logging}, data => { message => "expected array, found '" . ref($options{data}->{content}) . "'", } @@ -109,6 +110,7 @@ sub action_command { socket => $options{socket_log}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, + logging => $options{data}->{logging}, data => { message => "need command argument at array index '" . $index . "'", } @@ -122,6 +124,7 @@ sub action_command { socket => $options{socket_log}, code => GORGONE_ACTION_BEGIN, token => $options{token}, + logging => $options{data}->{logging}, data => { message => "commands processing has started", request_content => $options{data}->{content} @@ -135,6 +138,7 @@ sub action_command { socket => $options{socket_log}, code => GORGONE_ACTION_BEGIN, token => $options{token}, + logging => $options{data}->{logging}, data => { message => "command has started", command => $command->{command}, @@ -156,6 +160,7 @@ sub action_command { socket => $options{socket_log}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, + logging => $options{data}->{logging}, data => { message => "command execution issue", command => $command->{command}, @@ -177,6 +182,7 @@ sub action_command { socket => $options{socket_log}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, + logging => $options{data}->{logging}, data => { message => "commands processing has been interrupted because of error" } @@ -190,7 +196,8 @@ sub action_command { socket => $options{socket_log}, code => GORGONE_MODULE_ACTION_COMMAND_RESULT, token => $options{token}, - instant => $command->{instant}, + logging => $options{data}->{logging}, + instant => $options{data}->{instant}, data => { message => "command has finished successfully", command => $command->{command}, @@ -214,6 +221,7 @@ sub action_command { socket => $options{socket_log}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, + logging => $options{data}->{logging}, data => { message => "commands processing has finished with errors" } @@ -225,6 +233,7 @@ sub action_command { socket => $options{socket_log}, code => GORGONE_ACTION_FINISH_OK, token => $options{token}, + logging => $options{data}->{logging}, data => { message => "commands processing has finished successfully" } @@ -240,6 +249,7 @@ sub action_processcopy { $self->send_log( code => GORGONE_ACTION_FINISH_KO, token => $options{token}, + logging => $options{data}->{logging}, data => { message => 'no content' } ); return -1; @@ -255,6 +265,7 @@ sub action_processcopy { $self->send_log( code => GORGONE_ACTION_FINISH_KO, token => $options{token}, + logging => $options{data}->{logging}, data => { message => "file '$cache_file' open failed: $!" } ); @@ -273,6 +284,7 @@ sub action_processcopy { $self->send_log( code => GORGONE_MODULE_ACTION_PROCESSCOPY_INPROGRESS, token => $options{token}, + logging => $options{data}->{logging}, data => { message => 'process copy inprogress', } @@ -297,6 +309,7 @@ sub action_processcopy { $self->send_log( code => GORGONE_ACTION_FINISH_KO, token => $options{token}, + logging => $options{data}->{logging}, data => { message => "untar failed: $stdout" } ); $self->{logger}->writeLogError('[action] Copy processing - Untar failed: ' . $stdout); @@ -306,6 +319,7 @@ sub action_processcopy { $self->send_log( code => GORGONE_ACTION_FINISH_KO, token => $options{token}, + logging => $options{data}->{logging}, data => { message => "untar failed ($exit_code): $stdout" } ); $self->{logger}->writeLogError('[action] Copy processing - Untar failed: ' . $stdout); @@ -321,6 +335,7 @@ sub action_processcopy { $self->send_log( code => GORGONE_ACTION_FINISH_KO, token => $options{token}, + logging => $options{data}->{logging}, data => { message => 'md5 does not match' } ); $self->{logger}->writeLogError('[action] Copy processing - MD5 does not match'); @@ -333,6 +348,7 @@ sub action_processcopy { $self->send_log( code => GORGONE_ACTION_FINISH_OK, token => $options{token}, + logging => $options{data}->{logging}, data => { message => "process copy finished successfully", } @@ -360,6 +376,7 @@ sub action_run { socket => $socket_log, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, + logging => $options{data}->{logging}, data => { message => "action unknown" } ); return -1; diff --git a/gorgone/gorgone/modules/core/proxy/class.pm b/gorgone/gorgone/modules/core/proxy/class.pm index e543075466d..9b47118a280 100644 --- a/gorgone/gorgone/modules/core/proxy/class.pm +++ b/gorgone/gorgone/modules/core/proxy/class.pm @@ -329,7 +329,8 @@ sub proxy_ssh { $self->send_log( code => $_->{code}, token => $options{token}, - instant => $_->{instant}, + logging => $decoded_data->{logging}, + instant => $decoded_data->{instant}, data => $_->{data} ); } @@ -341,6 +342,7 @@ sub proxy_ssh { $self->send_log( code => GORGONE_ACTION_FINISH_OK, token => $options{token}, + logging => $decoded_data->{logging}, data => $data_ret ); last; @@ -349,6 +351,7 @@ sub proxy_ssh { $self->send_log( code => GORGONE_ACTION_FINISH_KO, token => $options{token}, + logging => $decoded_data->{logging}, data => $data_ret ); diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index 52a89fa3b68..d516fed3af3 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -1060,7 +1060,9 @@ sub prepare_remote_copy { my $buffer_size = (defined($config->{buffer_size})) ? $config->{buffer_size} : 500_000; my $buffer; while (my $bytes = sysread(FH, $buffer, $buffer_size)) { - push @actions, { content => { + push @actions, { + logging => $options{data}->{logging}, + content => { status => 'inprogress', type => $type, chunk => { @@ -1077,6 +1079,7 @@ sub prepare_remote_copy { close FH; push @actions, { + logging => $options{data}->{logging}, content => { status => 'end', type => $type, diff --git a/gorgone/gorgone/modules/core/proxy/sshclient.pm b/gorgone/gorgone/modules/core/proxy/sshclient.pm index 868b07868b2..fe13346b151 100644 --- a/gorgone/gorgone/modules/core/proxy/sshclient.pm +++ b/gorgone/gorgone/modules/core/proxy/sshclient.pm @@ -221,7 +221,6 @@ sub action_command { my $end = time(); $data = { - instant => $command->{instant}, data => { command => $command->{command}, metadata => $command->{metadata}, From 4da178d6be593df64926d7f8877f4f5b9ba1cc58 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Mon, 19 Apr 2021 15:38:40 +0200 Subject: [PATCH 587/948] fix(autodiscovery): filter_pollers option (#122) (MON-7157) --- .../modules/centreon/autodiscovery/services/resources.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm b/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm index 5eaf8544ecf..7731f262e95 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm @@ -324,7 +324,7 @@ sub get_hosts { my $filter_poller = ''; my $join_table = ''; - if (defined($options{poller_lookup}) && ref($options{host_lookup}) eq 'ARRAY' && scalar(@{$options{poller_lookup}}) > 0) { + if (defined($options{poller_lookup}) && ref($options{poller_lookup}) eq 'ARRAY' && scalar(@{$options{poller_lookup}}) > 0) { my $filter_append = ''; foreach (@{$options{poller_lookup}}) { $filter_poller .= $filter_append . $options{class_object_centreon}->quote(value => $_); From f7ffc3f06c1647f55ad885758a2d5bf5347e0d07 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Tue, 20 Apr 2021 08:38:05 +0200 Subject: [PATCH 588/948] chore(version): upgrade to 21.04.0 (#123) --- gorgone/packaging/centreon-gorgone.spectemplate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 94c1a23715b..af376a68706 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -1,6 +1,6 @@ Name: centreon-gorgone Version: 21.04.0 -Release: beta.2.@RELEASE@%{?dist} +Release: @RELEASE@%{?dist} Summary: Perl daemon task handlers Group: Applications/System License: Apache2 From c400efd2da239db49e7635994727cd1797f1520d Mon Sep 17 00:00:00 2001 From: Adrien Morais <31647811+adr-mo@users.noreply.github.com> Date: Wed, 21 Apr 2021 17:12:40 +0200 Subject: [PATCH 589/948] chore(version): prepare 21.10 serie (#124) --- gorgone/Jenkinsfile | 2 +- gorgone/packaging/centreon-gorgone.spectemplate | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/Jenkinsfile b/gorgone/Jenkinsfile index 9cb61e05d2d..756a8497957 100644 --- a/gorgone/Jenkinsfile +++ b/gorgone/Jenkinsfile @@ -1,7 +1,7 @@ /* ** Variables. */ -def serie = '21.04' +def serie = '21.10' def maintenanceBranch = "${serie}.x" if (env.BRANCH_NAME.startsWith('release-')) { env.BUILD = 'RELEASE' diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index af376a68706..b0137618ede 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -1,5 +1,5 @@ Name: centreon-gorgone -Version: 21.04.0 +Version: 21.10.0 Release: @RELEASE@%{?dist} Summary: Perl daemon task handlers Group: Applications/System From 7f6ed01f598b0988a75cc7842a8a0b5641ee8d7f Mon Sep 17 00:00:00 2001 From: sc979 <34628915+sc979@users.noreply.github.com> Date: Thu, 29 Apr 2021 09:20:50 +0200 Subject: [PATCH 590/948] fix(chore): define db extension files as SQL (#125) --- gorgone/sonar-project.properties | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gorgone/sonar-project.properties b/gorgone/sonar-project.properties index e3ad7c69b6b..99364113dec 100644 --- a/gorgone/sonar-project.properties +++ b/gorgone/sonar-project.properties @@ -3,3 +3,6 @@ sonar.projectKey={PROJECT_TITLE} sonar.projectName={PROJECT_NAME} sonar.projectVersion={PROJECT_VERSION} sonar.sources=. + +sonar.tsql.file.suffixes=sql,tsql +sonar.plsql.file.suffixes=pks,pkb From 210e1772c786607fe3fc9f1d885bdf7f7b55b912 Mon Sep 17 00:00:00 2001 From: Laurent Pinsivy <lpinsivy@centreon.com> Date: Mon, 3 May 2021 10:46:00 +0200 Subject: [PATCH 591/948] fix(anomaly): prevent empty host_id (#126) --- gorgone/gorgone/modules/centreon/anomalydetection/class.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm index e20a68f6d9c..8ba8f7cf66e 100644 --- a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm +++ b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm @@ -431,6 +431,7 @@ sub generate_lua_filter_file { foreach (values %{$self->{centreon_metrics}}) { next if ($_->{saas_to_delete} == 1); next if (!defined($_->{saas_creation_date})); + next if (!defined($_->{host_id})); $data->{filters}->{ $_->{host_id} } = {} if (!defined($data->{filters}->{ $_->{host_id} })); From d36b410d4c3eae34825e1f3768ee209cc31836d1 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Fri, 4 Jun 2021 09:27:59 +0200 Subject: [PATCH 592/948] enh(core): add ipv6 support (#118) --- gorgone/gorgone/class/clientzmq.pm | 6 ++++-- gorgone/gorgone/class/core.pm | 1 + gorgone/gorgone/standard/library.pm | 5 +++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/class/clientzmq.pm b/gorgone/gorgone/class/clientzmq.pm index 5b5ed7b2357..21800dacd95 100644 --- a/gorgone/gorgone/class/clientzmq.pm +++ b/gorgone/gorgone/class/clientzmq.pm @@ -98,10 +98,12 @@ sub init { $self->{handshake} = 0; $sockets->{$self->{identity}} = gorgone::standard::library::connect_com( - zmq_type => 'ZMQ_DEALER', name => $self->{identity} . '-' . $self->{extra_identity}, + zmq_type => 'ZMQ_DEALER', + name => $self->{identity} . '-' . $self->{extra_identity}, logger => $self->{logger}, type => $self->{target_type}, - path => $self->{target_path} + path => $self->{target_path}, + zmq_ipv6 => $self->{config_core}->{ipv6} ); $callbacks->{$self->{identity}} = $options{callback} if (defined($options{callback})); } diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 5f65c32082d..907b2f99970 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -805,6 +805,7 @@ sub run { zmq_type => 'ZMQ_ROUTER', zmq_router_handover => $gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_zmq_router_handover}, zmq_tcp_keepalive => $gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_zmq_tcp_keepalive}, + zmq_ipv6 => $gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{ipv6}, name => 'router-external', logger => $gorgone->{logger} ); diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index c78ddada054..cfb56b4f68c 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -41,6 +41,7 @@ our $listener; my %zmq_type = ('ZMQ_ROUTER' => ZMQ_ROUTER, 'ZMQ_DEALER' => ZMQ_DEALER); my $ZMQ_CONNECT_TIMEOUT = 79; my $ZMQ_ROUTER_HANDOVER = 56; +my $ZMQ_IPV6 = 42; my $ZMQ_TCP_KEEPALIVE = 34; sub read_config { @@ -739,6 +740,9 @@ sub connect_com { zmq_setsockopt($socket, ZMQ_RECONNECT_IVL, 1000); ZMQ::LibZMQ4::zmq_setsockopt_int($socket, $ZMQ_CONNECT_TIMEOUT, defined($options{zmq_connect_timeout}) ? $options{zmq_connect_timeout} : 30000); ZMQ::LibZMQ4::zmq_setsockopt_int($socket, $ZMQ_ROUTER_HANDOVER, defined($options{zmq_router_handover}) ? $options{zmq_router_handover} : 1); + if ($options{type} eq 'tcp') { + ZMQ::LibZMQ4::zmq_setsockopt_int($socket, $ZMQ_IPV6, defined($options{zmq_ipv6}) && $options{zmq_ipv6} =~ /true|1/i ? 1 : 0); + } zmq_connect($socket, $options{type} . '://' . $options{path}); return $socket; } @@ -757,6 +761,7 @@ sub create_com { zmq_setsockopt($socket, ZMQ_LINGER, 0); # we discard ZMQ::LibZMQ4::zmq_setsockopt_int($socket, $ZMQ_ROUTER_HANDOVER, defined($options{zmq_router_handover}) ? $options{zmq_router_handover} : 1); if ($options{type} eq 'tcp') { + ZMQ::LibZMQ4::zmq_setsockopt_int($socket, $ZMQ_IPV6, defined($options{zmq_ipv6}) && $options{zmq_ipv6} =~ /true|1/i ? 1 : 0); ZMQ::LibZMQ4::zmq_setsockopt_int($socket, $ZMQ_TCP_KEEPALIVE, defined($options{zmq_tcp_keepalive}) ? $options{zmq_tcp_keepalive} : -1); zmq_bind($socket, 'tcp://' . $options{path}); } elsif ($options{type} eq 'ipc') { From 2681379477929703564767dcd6a343252264e5fe Mon Sep 17 00:00:00 2001 From: garnier-quentin <garnier.quentin@gmail.com> Date: Fri, 4 Jun 2021 14:08:03 +0200 Subject: [PATCH 593/948] enh(action): add whitelist commands (MON-7412) --- .../docs/modules/centreon/autodiscovery.md | 32 +++--- gorgone/docs/modules/centreon/engine.md | 26 ++--- gorgone/docs/modules/centreon/nodes.md | 4 +- gorgone/docs/modules/centreon/statistics.md | 30 +++--- gorgone/docs/modules/core/action.md | 44 +++++---- gorgone/docs/modules/core/cron.md | 98 +++++++++---------- gorgone/docs/modules/core/dbcleaner.md | 12 +-- gorgone/docs/modules/core/httpserver.md | 22 ++--- gorgone/docs/modules/core/proxy.md | 58 +++++------ gorgone/docs/modules/core/register.md | 6 +- gorgone/gorgone/modules/core/action/class.pm | 32 +++++- 11 files changed, 201 insertions(+), 163 deletions(-) diff --git a/gorgone/docs/modules/centreon/autodiscovery.md b/gorgone/docs/modules/centreon/autodiscovery.md index c81c03ec99a..e9446458fa3 100644 --- a/gorgone/docs/modules/centreon/autodiscovery.md +++ b/gorgone/docs/modules/centreon/autodiscovery.md @@ -7,7 +7,7 @@ This module aims to extend Centreon Autodiscovery server functionalities. ## Configuration | Directive | Description | Default value | -|:----------------|:-----------------------------------------------------------------------|:--------------| +| :-------------- | :--------------------------------------------------------------------- | :------------ | | global\_timeout | Time in seconds before a discovery command is considered timed out | `300` | | check\_interval | Time in seconds defining frequency at which results will be search for | `15` | @@ -24,7 +24,7 @@ check_interval: 10 ## Events | Event | Description | -|:-------------------------|:------------------------------------------------| +| :----------------------- | :---------------------------------------------- | | AUTODISCOVERYREADY | Internal event to notify the core | | HOSTDISCOVERYLISTENER | Internal event to get host discovery results | | SERVICEDISCOVERYLISTENER | Internal event to get service discovery results | @@ -38,20 +38,20 @@ check_interval: 10 ### Add a host discovery job | Endpoint | Method | -|:------------------------------|:-------| +| :---------------------------- | :----- | | /centreon/autodiscovery/hosts | `POST` | #### Headers | Header | Value | -|:-------------|:-----------------| +| :----------- | :--------------- | | Accept | application/json | | Content-Type | application/json | #### Body | Key | Value | -|:----------------|:-----------------------------------------------------------| +| :-------------- | :--------------------------------------------------------- | | job\_id | ID of the Host Discovery job | | target | Identifier of the target on which to execute the command | | command_line | Command line to execute to perform the discovery | @@ -62,14 +62,14 @@ check_interval: 10 With the following keys for the `execution` entry: | Key | Value | -|:-----------|:------------------------------------------------| +| :--------- | :---------------------------------------------- | | mode | Execution mode ('0': immediate, '1': scheduled) | | parameters | Parameters needed by execution mode | With the following keys for the `post_execution` entry: | Key | Value | -|:---------|:---------------------------------| +| :------- | :------------------------------- | | commands | Array of commands to be executed | ```json @@ -187,19 +187,19 @@ curl --request POST "https://hostname:8443/api/centreon/autodiscovery/hosts" \ ### Launch a host discovery job | Endpoint | Method | -|:-------------------------------------------|:-------| +| :----------------------------------------- | :----- | | /centreon/autodiscovery/hosts/:id/schedule | `GET` | #### Headers | Header | Value | -|:-------------|:-----------------| +| :----------- | :--------------- | | Accept | application/json | #### Path variables | Variable | Description | -|:---------|:----------------------| +| :------- | :-------------------- | | id | Identifier of the job | #### Example @@ -212,19 +212,19 @@ curl --request GET "https://hostname:8443/api/centreon/autodiscovery/hosts/:id/s ### Delete a host discovery job | Endpoint | Method | -|:-------------------------------------|:---------| +| :----------------------------------- | :------- | | /centreon/autodiscovery/hosts/:token | `DELETE` | #### Headers | Header | Value | -|:-------|:-----------------| +| :----- | :--------------- | | Accept | application/json | #### Path variables | Variable | Description | -|:---------|:---------------------------| +| :------- | :------------------------- | | token | Token of the scheduled job | #### Example @@ -237,20 +237,20 @@ curl --request DELETE "https://hostname:8443/api/centreon/autodiscovery/hosts/di ### Execute a service discovery job | Endpoint | Method | -|:---------------------------------|:-------| +| :------------------------------- | :----- | | /centreon/autodiscovery/services | `POST` | #### Headers | Header | Value | -|:-------------|:-----------------| +| :----------- | :--------------- | | Accept | application/json | | Content-Type | application/json | #### Body | Key | Value | -|:---------------------|:--------------------------------------------------------------------------------------------------| +| :------------------- | :------------------------------------------------------------------------------------------------ | | filter\_rules | Array of rules to use for discovery (empty means all) | | force\_rule | Run disabled rules ('0': not forced, '1': forced) | | filter\_hosts | Array of hosts against which run the discovery (empty means all) | diff --git a/gorgone/docs/modules/centreon/engine.md b/gorgone/docs/modules/centreon/engine.md index c09baca074f..4c8c561d5bc 100644 --- a/gorgone/docs/modules/centreon/engine.md +++ b/gorgone/docs/modules/centreon/engine.md @@ -6,8 +6,8 @@ This module aims to provide a bridge to communicate with Centreon Engine daemon. ## Configuration -| Directive | Description | Default value | -| :- | :- | :- | +| Directive | Description | Default value | +| :----------- | :-------------------------------------------- | :------------------------------------------- | | command_file | Path to the Centreon Engine command file pipe | `/var/lib/centreon-engine/rw/centengine.cmd` | #### Example @@ -21,32 +21,32 @@ command_file: "/var/lib/centreon-engine/rw/centengine.cmd" ## Events -| Event | Description | -| :- | :- | -| ENGINEREADY | Internal event to notify the core | +| Event | Description | +| :------------ | :--------------------------------------------------------------------------- | +| ENGINEREADY | Internal event to notify the core | | ENGINECOMMAND | Send a Centreon external command to Centreon Engine daemon command file pipe | ## API ### Execute a command line -| Endpoint | Method | -| :- | :- | +| Endpoint | Method | +| :----------------------- | :----- | | /centreon/engine/command | `POST` | #### Headers -| Header | Value | -| :- | :- | -| Accept | application/json | +| Header | Value | +| :----------- | :--------------- | +| Accept | application/json | | Content-Type | application/json | #### Body -| Key | Value | -| :- | :- | +| Key | Value | +| :----------- | :-------------------------------------------- | | command_file | Path to the Centreon Engine command file pipe | -| commands | Array of external commands (old-style format) | +| commands | Array of external commands (old-style format) | ```json { diff --git a/gorgone/docs/modules/centreon/nodes.md b/gorgone/docs/modules/centreon/nodes.md index 342bb480cf1..bb90c46e668 100644 --- a/gorgone/docs/modules/centreon/nodes.md +++ b/gorgone/docs/modules/centreon/nodes.md @@ -20,8 +20,8 @@ enable: true ## Events -| Event | Description | -| :- | :- | +| Event | Description | +| :----------------- | :-------------------------------- | | CENTREONNODESREADY | Internal event to notify the core | ## API diff --git a/gorgone/docs/modules/centreon/statistics.md b/gorgone/docs/modules/centreon/statistics.md index 9451b7a2f8a..82ce80de5d4 100644 --- a/gorgone/docs/modules/centreon/statistics.md +++ b/gorgone/docs/modules/centreon/statistics.md @@ -6,8 +6,8 @@ This module aims to deal with statistics collection of Centreon Engine and Broke ## Configuration -| Directive | Description | Default value | -| :- | :- | :- | +| Directive | Description | Default value | +| :--------------- | :--------------------------------------------------------------------------------------------- | :-------------------------------- | | broker_cache_dir | Path to the Centreon Broker statistics directory (local) use to store node's broker statistics | `/var/lib/centreon/broker-stats/` | The configuration needs a cron definition to unsure that statistics collection will be done cyclically. @@ -30,31 +30,31 @@ cron: ## Events -| Event | Description | -| :- | :- | -| STATISTICSREADY | Internal event to notify the core | -| BROKERSTATS | Collect Centreon Broker statistics files on node | +| Event | Description | +| :-------------- | :----------------------------------------------- | +| STATISTICSREADY | Internal event to notify the core | +| BROKERSTATS | Collect Centreon Broker statistics files on node | ## API ### Collect Centreon Broker statistics on one or several nodes -| Endpoint | Method | -| :- | :- | -| /centreon/statistics/broker | `GET` | -| /centreon/statistics/broker/:id | `GET` | +| Endpoint | Method | +| :------------------------------ | :----- | +| /centreon/statistics/broker | `GET` | +| /centreon/statistics/broker/:id | `GET` | #### Headers -| Header | Value | -| :- | :- | +| Header | Value | +| :----- | :--------------- | | Accept | application/json | #### Path variables -| Variable | Description | -| :- | :- | -| id | Identifier of the node | +| Variable | Description | +| :------- | :--------------------- | +| id | Identifier of the node | #### Example diff --git a/gorgone/docs/modules/core/action.md b/gorgone/docs/modules/core/action.md index d53d7a2ef5e..79a362b9dec 100644 --- a/gorgone/docs/modules/core/action.md +++ b/gorgone/docs/modules/core/action.md @@ -6,9 +6,11 @@ This module aims to execute actions on the server running the Gorgone daemon or ## Configuration -| Directive | Description | Default value | -| :- | :- | :- | -| command_timeout | Time in seconds before a command is considered timed out | `30` | +| Directive | Description | Default value | +| :-------------- | :------------------------------------------------------- | :------------ | +| command_timeout | Time in seconds before a command is considered timed out | `30` | +| whitelist_cmds | Boolean to enable commands whitelist | `false` | +| allowed_cmds | Regexp list of allowed commands | | #### Example @@ -17,38 +19,44 @@ name: action package: "gorgone::modules::core::action::hooks" enable: true command_timeout: 30 +whitelist_cmds: false +allowed_cmds: + - ^sudo\s+(/bin/)?systemctl\s+(reload|restart)\s+(centengine|centreontrapd|cbd)\s*$ + - ^sudo\s+(/usr/bin/)?service\s+(centengine|centreontrapd|cbd)\s+(reload|restart)\s*$ + - ^/usr/sbin/centenginestats\s+-c\s+/etc/centreon-engine/centengine.cfg\s*$ + - ^cat\s+/var/lib/centreon-engine/[a-zA-Z0-9\-]+-stats.json\s*$ ``` ## Events -| Event | Description | -| :- | :- | -| ACTIONREADY | Internal event to notify the core | -| PROCESSCOPY | Process file or archive received from another daemon | -| COMMAND | Execute a shell command on the server running the daemon or on another server using SSH | +| Event | Description | +| :---------- | :-------------------------------------------------------------------------------------- | +| ACTIONREADY | Internal event to notify the core | +| PROCESSCOPY | Process file or archive received from another daemon | +| COMMAND | Execute a shell command on the server running the daemon or on another server using SSH | ## API ### Execute a command line -| Endpoint | Method | -| :- | :- | +| Endpoint | Method | +| :------------------- | :----- | | /core/action/command | `POST` | #### Headers -| Header | Value | -| :- | :- | -| Accept | application/json | +| Header | Value | +| :----------- | :--------------- | +| Accept | application/json | | Content-Type | application/json | #### Body -| Key | Value | -| :- | :- | -| command | Command to execute | -| timeout | Time in seconds before a command is considered timed out | -| continue_on_error | Behaviour in case of execution issue | +| Key | Value | +| :---------------- | :------------------------------------------------------- | +| command | Command to execute | +| timeout | Time in seconds before a command is considered timed out | +| continue_on_error | Behaviour in case of execution issue | ```json [ diff --git a/gorgone/docs/modules/core/cron.md b/gorgone/docs/modules/core/cron.md index d1b90090769..b4e5be4d224 100644 --- a/gorgone/docs/modules/core/cron.md +++ b/gorgone/docs/modules/core/cron.md @@ -10,12 +10,12 @@ No specific configuration is needed. Below the configuration to add cron definitions: -| Directive | Description | -| :- | :- | -| id | Unique identifier of the cron definition | -| timespec | Cron-like time specification | -| action | Action/event to call at job execution | -| parameters | Parameters needed by the called action/event | +| Directive | Description | +| :--------- | :---------------------------------------------------------------------------------------------- | +| id | Unique identifier of the cron definition | +| timespec | Cron-like time specification | +| action | Action/event to call at job execution | +| parameters | Parameters needed by the called action/event | | keep_token | Boolean to define whether or not the ID of the definition will be used as token for the command | #### Example @@ -48,22 +48,22 @@ cron: ### Get one or all definitions configuration -| Endpoint | Method | -| :- | :- | -| /core/cron/definitions | `GET` | -| /core/cron/definitions/:id | `GET` | +| Endpoint | Method | +| :------------------------- | :----- | +| /core/cron/definitions | `GET` | +| /core/cron/definitions/:id | `GET` | #### Headers -| Header | Value | -| :- | :- | +| Header | Value | +| :----- | :--------------- | | Accept | application/json | #### Path variables -| Variable | Description | -| :- | :- | -| id | Identifier of the cron definition | +| Variable | Description | +| :------- | :-------------------------------- | +| id | Identifier of the cron definition | #### Example @@ -79,21 +79,21 @@ curl --request GET "https://hostname:8443/api/core/cron/definitions/echo_date" \ ### Get one definition status -| Endpoint | Method | -| :- | :- | -| /core/cron/definitions/:id/status | `GET` | +| Endpoint | Method | +| :-------------------------------- | :----- | +| /core/cron/definitions/:id/status | `GET` | #### Headers -| Header | Value | -| :- | :- | +| Header | Value | +| :----- | :--------------- | | Accept | application/json | #### Path variables -| Variable | Description | -| :- | :- | -| id | Identifier of the cron definition | +| Variable | Description | +| :------- | :-------------------------------- | +| id | Identifier of the cron definition | #### Example @@ -104,25 +104,25 @@ curl --request GET "https://hostname:8443/api/core/cron/definitions/echo_date/st ### Add one or several cron definitions -| Endpoint | Method | -| :- | :- | +| Endpoint | Method | +| :--------------------- | :----- | | /core/cron/definitions | `POST` | #### Headers -| Header | Value | -| :- | :- | -| Accept | application/json | +| Header | Value | +| :----------- | :--------------- | +| Accept | application/json | | Content-Type | application/json | #### Body -| Key | Value | -| :- | :- | -| id | ID of the definition | -| timespec | Cron-like time specification | -| command | Action/event to call at job execution | -| parameters | Parameters needed by the called action/event | +| Key | Value | +| :--------- | :---------------------------------------------------------------------------------------------- | +| id | ID of the definition | +| timespec | Cron-like time specification | +| command | Action/event to call at job execution | +| parameters | Parameters needed by the called action/event | | keep_token | Boolean to define whether or not the ID of the definition will be used as token for the command | ```json @@ -161,22 +161,22 @@ curl --request POST "https://hostname:8443/api/core/cron/definitions" \ ### Update a definition -| Endpoint | Method | -| :- | :- | +| Endpoint | Method | +| :------------------------- | :------ | | /core/cron/definitions/:id | `PATCH` | #### Headers -| Header | Value | -| :- | :- | -| Accept | application/json | +| Header | Value | +| :----------- | :--------------- | +| Accept | application/json | | Content-Type | application/json | #### Path variables -| Variable | Description | -| :- | :- | -| id | Identifier of the cron definition | +| Variable | Description | +| :------- | :-------------------------------- | +| id | Identifier of the cron definition | #### Body @@ -205,21 +205,21 @@ curl --request PATCH "https://hostname:8443/api/core/cron/definitions/job_123" \ ### Delete a definition -| Endpoint | Method | -| :- | :- | +| Endpoint | Method | +| :------------------------- | :------- | | /core/cron/definitions/:id | `DELETE` | #### Headers -| Header | Value | -| :- | :- | +| Header | Value | +| :----- | :--------------- | | Accept | application/json | #### Path variables -| Variable | Description | -| :- | :- | -| id | Identifier of the cron definition | +| Variable | Description | +| :------- | :-------------------------------- | +| id | Identifier of the cron definition | #### Example diff --git a/gorgone/docs/modules/core/dbcleaner.md b/gorgone/docs/modules/core/dbcleaner.md index d42ac06a170..c80af1aad66 100644 --- a/gorgone/docs/modules/core/dbcleaner.md +++ b/gorgone/docs/modules/core/dbcleaner.md @@ -8,10 +8,10 @@ The module is loaded by default. Adding it to the configuration will overload da ## Configuration -| Directive | Description | Default value | -| :- | :- | :- | -| purge_sessions_time | Time in seconds before deleting sessions in the `gorgone_identity` table | `3600` | -| purge_history_time | Time in seconds before deleting history in the `gorgone_history` table | `604800` | +| Directive | Description | Default value | +| :------------------ | :----------------------------------------------------------------------- | :------------ | +| purge_sessions_time | Time in seconds before deleting sessions in the `gorgone_identity` table | `3600` | +| purge_history_time | Time in seconds before deleting history in the `gorgone_history` table | `604800` | #### Example @@ -25,8 +25,8 @@ purge_history_time: 604800 ## Events -| Event | Description | -| :- | :- | +| Event | Description | +| :------------- | :-------------------------------- | | DBCLEANERREADY | Internal event to notify the core | ## API diff --git a/gorgone/docs/modules/core/httpserver.md b/gorgone/docs/modules/core/httpserver.md index bb36041362b..cae8e874bdd 100644 --- a/gorgone/docs/modules/core/httpserver.md +++ b/gorgone/docs/modules/core/httpserver.md @@ -8,15 +8,15 @@ It relies on a core API module to server Gorgone events and can dispatch any oth ## Configuration -| Directive | Description | Default value | -| :- | :- | :- | -| address | IP address for the server to bind to | `0.0.0.0` | -| port | Port on which the server will listen to requests | `8080` | -| ssl | Boolean to enable SSL terminaison | `false` | -| ssl_cert_file | Path to the SSL certificate (if SSL enabled) | | -| ssl_key_file | Path to the SSL key (if SSL enabled) | | -| auth | Basic credentials to access the server | | -| allowed_hosts | Peer address to access the server | | +| Directive | Description | Default value | +| :------------ | :----------------------------------------------- | :------------ | +| address | IP address for the server to bind to | `0.0.0.0` | +| port | Port on which the server will listen to requests | `8080` | +| ssl | Boolean to enable SSL terminaison | `false` | +| ssl_cert_file | Path to the SSL certificate (if SSL enabled) | | +| ssl_key_file | Path to the SSL key (if SSL enabled) | | +| auth | Basic credentials to access the server | | +| allowed_hosts | Peer address to access the server | | #### Example @@ -51,6 +51,6 @@ dispatch: ## Events -| Event | Description | -| :- | :- | +| Event | Description | +| :-------------- | :-------------------------------- | | HTTPSERVERREADY | Internal event to notify the core | diff --git a/gorgone/docs/modules/core/proxy.md b/gorgone/docs/modules/core/proxy.md index c023e6aff40..5891cee4fb3 100644 --- a/gorgone/docs/modules/core/proxy.md +++ b/gorgone/docs/modules/core/proxy.md @@ -12,13 +12,13 @@ A SSH client library make routing to non-gorgoned nodes possible. ## Configuration -| Directive | Description | Default value | -| :- | :- | :- | -| pool | Number of childs to instantiate to process events | `5` | -| synchistory_time | Time in seconds between two logs synchronisation | `60` | -| synchistory_timeout | Time in seconds before logs synchronisation is considered timed out | `30` | -| ping | Time in seconds between two node pings | `60` | -| pong_discard_timeout | Time in seconds before a node is considered dead | `300` | +| Directive | Description | Default value | +| :------------------- | :------------------------------------------------------------------ | :------------ | +| pool | Number of childs to instantiate to process events | `5` | +| synchistory_time | Time in seconds between two logs synchronisation | `60` | +| synchistory_timeout | Time in seconds before logs synchronisation is considered timed out | `30` | +| ping | Time in seconds between two node pings | `60` | +| pong_discard_timeout | Time in seconds before a node is considered dead | `300` | #### Example @@ -35,41 +35,41 @@ pong_discard_timeout: 300 ## Events -| Event | Description | -| :- | :- | -| PROXYREADY | Internal event to notify the core | -| REMOTECOPY | Copy files or directories from the server running the daemon to another server | -| SETLOGS | Internal event to insert logs into the database | -| PONG | Internal event to handle node ping response | -| REGISTERNODES | Internal event to register nodes | -| UNREGISTERNODES | Internal event to unregister nodes | -| PROXYADDNODE | Internal event to add nodes for proxying | -| PROXYDELNODE | Internal event to delete nodes from proxying | -| PROXYADDSUBNODE | Internal event to add nodes of nodes for proxying | -| PONGRESET | Internal event to deal with no pong nodes | +| Event | Description | +| :-------------- | :----------------------------------------------------------------------------- | +| PROXYREADY | Internal event to notify the core | +| REMOTECOPY | Copy files or directories from the server running the daemon to another server | +| SETLOGS | Internal event to insert logs into the database | +| PONG | Internal event to handle node ping response | +| REGISTERNODES | Internal event to register nodes | +| UNREGISTERNODES | Internal event to unregister nodes | +| PROXYADDNODE | Internal event to add nodes for proxying | +| PROXYDELNODE | Internal event to delete nodes from proxying | +| PROXYADDSUBNODE | Internal event to add nodes of nodes for proxying | +| PONGRESET | Internal event to deal with no pong nodes | ## API ### Copy files or directory to remote server -| Endpoint | Method | -| :- | :- | +| Endpoint | Method | +| :------------------------- | :----- | | /api/core/proxy/remotecopy | `POST` | #### Headers -| Header | Value | -| :- | :- | -| Accept | application/json | +| Header | Value | +| :----------- | :--------------- | +| Accept | application/json | | Content-Type | application/json | #### Body -| Key | Value | -| :- | :- | -| source | Path of the source file or directory | -| destination | Path of the destination file or directory | -| cache_dir | Path to the cache directory for archiving purpose | +| Key | Value | +| :---------- | :------------------------------------------------ | +| source | Path of the source file or directory | +| destination | Path of the destination file or directory | +| cache_dir | Path to the cache directory for archiving purpose | ```json { diff --git a/gorgone/docs/modules/core/register.md b/gorgone/docs/modules/core/register.md index 9c5bfdd5f9b..e1db2ebe8e0 100644 --- a/gorgone/docs/modules/core/register.md +++ b/gorgone/docs/modules/core/register.md @@ -11,7 +11,7 @@ Nodes are either servers running Gorgone daemon or simple equipment with SSH ser There is no specific configuration in the Gorgone daemon configuration file, only a directive to set a path to a dedicated configuration file. | Directive | Description | Default value | -|:-------------|:---------------------------------------------|:--------------| +| :----------- | :------------------------------------------- | :------------ | | config\_file | Path to the configuration file listing nodes | | #### Example @@ -28,7 +28,7 @@ Nodes are listed in a separate configuration file in a `nodes` table as below: ##### Using ZMQ (Gorgone running on node) | Directive | Description | -|:----------------|:---------------------------------------------------------------------------| +| :-------------- | :------------------------------------------------------------------------- | | id | Unique identifier of the node (can be Poller’s ID if using prevail option) | | type | Way for the daemon to connect to the node (push\_zmq) | | address | IP address of the node | @@ -59,7 +59,7 @@ nodes: ##### Using SSH | Directive | Description | -|:-------------------------|:--------------------------------------------------------------------------------------------------| +| :----------------------- | :------------------------------------------------------------------------------------------------ | | id | Unique identifier of the node (can be Poller’s ID if using prevail option) | | type | Way for the daemon to connect to the node (push\_ssh) | | address | IP address of the node | diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index f5ef206db6a..a6bd555d4c6 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -50,7 +50,12 @@ sub new { $connector->{command_timeout} = defined($connector->{config}->{command_timeout}) ? $connector->{config}->{command_timeout} : 30; - + $connector->{whitelist_cmds} = defined($connector->{config}->{whitelist_cmds}) && $connector->{config}->{whitelist_cmds} =~ /true|1/i ? + 1 : 0; + $connector->{allowed_cmds} = []; + $connector->{allowed_cmds} = $connector->{config}->{allowed_cmds} + if (defined($connector->{config}->{allowed_cmds}) && ref($connector->{config}->{allowed_cmds}) eq 'ARRAY'); + $connector->set_signal_handlers; return $connector; } @@ -117,6 +122,31 @@ sub action_command { ); return -1; } + + if ($self->{whitelist_cmds} == 1) { + my $matched = 0; + foreach my $regexp (@{$self->{allowed_cmds}}) { + if ($command->{command} =~ /$regexp/) { + $matched = 1; + last; + } + } + + if ($matched == 0) { + $self->{logger}->writeLogInfo("[action] command not allowed (whitelist): " . $command->{command}); + $self->send_log( + socket => $options{socket_log}, + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + logging => $options{data}->{logging}, + data => { + message => "command not allowed (whitelist) at array index '" . $index . "'", + } + ); + return -1; + } + } + $index++; } From a3d3037cde70477d83c1519f7fb010644611a52b Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Tue, 15 Jun 2021 15:05:37 +0200 Subject: [PATCH 594/948] fix(api): empty response (#134) --- gorgone/gorgone/standard/api.pm | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/gorgone/gorgone/standard/api.pm b/gorgone/gorgone/standard/api.pm index d5508302067..ceb4fc88f62 100644 --- a/gorgone/gorgone/standard/api.pm +++ b/gorgone/gorgone/standard/api.pm @@ -144,7 +144,13 @@ sub call_internal { json_encode => 1 ); - my $rev = zmq_poll($poll, 5000); + my $timeout = 5000; + while ($timeout > 100) { + my $t1 = Time::HiRes::time(); + my $rev = zmq_poll($poll, $timeout); + last if (defined($results->{ $action_token })); + $timeout -= ($t1 - Time::HiRes::time()); + } my $response = '{"error":"no_result", "message":"No result found for action \'' . $options{action} . '\'"}'; if (defined($results->{$action_token}->{data})) { @@ -194,10 +200,11 @@ sub get_log { Time::HiRes::usleep($sync_wait); } + my $token_log = $options{token} . '-log'; gorgone::standard::library::zmq_send_message( socket => $socket, action => 'GETLOG', - token => $options{token}, + token => $token_log, data => { token => $options{token}, %{$options{parameters}} @@ -205,13 +212,19 @@ sub get_log { json_encode => 1 ); - my $rev = zmq_poll($poll, 5000); + my $timeout = 5000; + while ($timeout > 100) { + my $t1 = Time::HiRes::time(); + my $rev = zmq_poll($poll, $timeout); + last if (defined($results->{ $token_log })); + $timeout -= ($t1 - Time::HiRes::time()); + } my $response = '{"error":"no_log","message":"No log found for token","data":[],"token":"' . $options{token} . '"}'; - if (defined($results->{ $options{token} }) && defined($results->{ $options{token} }->{data})) { + if (defined($results->{ $token_log }) && defined($results->{ $token_log }->{data})) { my $content; eval { - $content = JSON::XS->new->utf8->decode($results->{ $options{token} }->{data}); + $content = JSON::XS->new->utf8->decode($results->{ $token_log }->{data}); }; if ($@) { $response = '{"error":"decode_error","message":"Cannot decode response"}'; From bd3bbb761a9fd20f93c868c528ecbf9f3a49f36a Mon Sep 17 00:00:00 2001 From: Zakaria Guennoune <83596451+zguennoune02@users.noreply.github.com> Date: Tue, 29 Jun 2021 14:39:41 +0200 Subject: [PATCH 595/948] fix(ci): manage Quality Gate timeout (#135) --- gorgone/Jenkinsfile | 43 +++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/gorgone/Jenkinsfile b/gorgone/Jenkinsfile index 756a8497957..a74bfd30325 100644 --- a/gorgone/Jenkinsfile +++ b/gorgone/Jenkinsfile @@ -1,3 +1,4 @@ +import groovy.json.JsonSlurper /* ** Variables. */ @@ -14,7 +15,7 @@ if (env.BRANCH_NAME.startsWith('release-')) { /* ** Pipeline code. */ -stage('Source') { +stage('Sonar analysis') { node { sh 'setup_centreon_build.sh' dir('centreon-gorgone') { @@ -28,23 +29,37 @@ stage('Source') { withSonarQubeEnv('SonarQubeDev') { sh "./centreon-build/jobs/gorgone/${serie}/gorgone-analysis.sh" } + def reportFilePath = "target/sonar/report-task.txt" + def reportTaskFileExists = fileExists "${reportFilePath}" + if (reportTaskFileExists) { + echo "Found report task file" + def taskProps = readProperties file: "${reportFilePath}" + echo "taskId[${taskProps['ceTaskId']}]" + timeout(time: 10, unit: 'MINUTES') { + while (true) { + sleep 5 + def taskStatusResult = + sh(returnStdout: true, + script: "curl -s -X GET -u ${authString} \'${sonarProps['sonar.host.url']}/api/ce/task?id=${taskProps['ceTaskId']}\'") + echo "taskStatusResult[${taskStatusResult}]" + def taskStatus = new JsonSlurper().parseText(taskStatusResult).task.status + echo "taskStatus[${taskStatus}]" + // Status can be SUCCESS, ERROR, PENDING, or IN_PROGRESS. The last two indicate it's + // not done yet. + if (taskStatus != "IN_PROGRESS" && taskStatus != "PENDING") { + break; + } + def qualityGate = waitForQualityGate() + if (qualityGate.status != 'OK') { + currentBuild.result = 'FAIL' + } + } + } + } } } try { - // sonarQube step to get qualityGate result - stage('Quality gate') { - timeout(time: 10, unit: 'MINUTES') { - def qualityGate = waitForQualityGate() - if (qualityGate.status != 'OK') { - currentBuild.result = 'FAIL' - } - } - if ((currentBuild.result ?: 'SUCCESS') != 'SUCCESS') { - error('Quality gate failure: ${qualityGate.status}.'); - } - } - stage('Package') { parallel 'centos7': { node { From c9565589d68828ab324ca5d0d298b8eaf787b569 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Wed, 21 Jul 2021 09:32:36 +0200 Subject: [PATCH 596/948] add(module): centreon audit (#137) --- gorgone/contrib/gorgone_audit.pl | 193 +++++++++ .../gorgone/modules/centreon/audit/class.pm | 389 ++++++++++++++++++ .../gorgone/modules/centreon/audit/hooks.pm | 179 ++++++++ .../audit/metrics/centreon/database.pm | 110 +++++ .../audit/metrics/centreon/packages.pm | 92 +++++ .../audit/metrics/centreon/pluginpacks.pm | 53 +++ .../audit/metrics/centreon/realtime.pm | 99 +++++ .../centreon/audit/metrics/centreon/rrd.pm | 68 +++ .../centreon/audit/metrics/system/cpu.pm | 62 +++ .../centreon/audit/metrics/system/disk.pm | 68 +++ .../centreon/audit/metrics/system/diskio.pm | 74 ++++ .../centreon/audit/metrics/system/load.pm | 53 +++ .../centreon/audit/metrics/system/memory.pm | 70 ++++ .../centreon/audit/metrics/system/os.pm | 56 +++ .../centreon/audit/sampling/system/cpu.pm | 68 +++ .../centreon/audit/sampling/system/diskio.pm | 63 +++ gorgone/gorgone/standard/constants.pm | 4 +- gorgone/gorgone/standard/misc.pm | 62 +++ 18 files changed, 1762 insertions(+), 1 deletion(-) create mode 100644 gorgone/contrib/gorgone_audit.pl create mode 100644 gorgone/gorgone/modules/centreon/audit/class.pm create mode 100644 gorgone/gorgone/modules/centreon/audit/hooks.pm create mode 100644 gorgone/gorgone/modules/centreon/audit/metrics/centreon/database.pm create mode 100644 gorgone/gorgone/modules/centreon/audit/metrics/centreon/packages.pm create mode 100644 gorgone/gorgone/modules/centreon/audit/metrics/centreon/pluginpacks.pm create mode 100644 gorgone/gorgone/modules/centreon/audit/metrics/centreon/realtime.pm create mode 100644 gorgone/gorgone/modules/centreon/audit/metrics/centreon/rrd.pm create mode 100644 gorgone/gorgone/modules/centreon/audit/metrics/system/cpu.pm create mode 100644 gorgone/gorgone/modules/centreon/audit/metrics/system/disk.pm create mode 100644 gorgone/gorgone/modules/centreon/audit/metrics/system/diskio.pm create mode 100644 gorgone/gorgone/modules/centreon/audit/metrics/system/load.pm create mode 100644 gorgone/gorgone/modules/centreon/audit/metrics/system/memory.pm create mode 100644 gorgone/gorgone/modules/centreon/audit/metrics/system/os.pm create mode 100644 gorgone/gorgone/modules/centreon/audit/sampling/system/cpu.pm create mode 100644 gorgone/gorgone/modules/centreon/audit/sampling/system/diskio.pm diff --git a/gorgone/contrib/gorgone_audit.pl b/gorgone/contrib/gorgone_audit.pl new file mode 100644 index 00000000000..81946bf9360 --- /dev/null +++ b/gorgone/contrib/gorgone_audit.pl @@ -0,0 +1,193 @@ +#!/usr/bin/perl + +use warnings; +use strict; +use FindBin; +use lib "$FindBin::Bin"; +# to be launched from contrib directory +use lib "$FindBin::Bin/../"; + +gorgone::script::gorgone_audit->new()->run(); + +package gorgone::script::gorgone_audit; + +use strict; +use warnings; +use Data::Dumper; +use gorgone::standard::misc; +use gorgone::class::http::http; +use JSON::XS; + +use base qw(gorgone::class::script); + +sub new { + my $class = shift; + my $self = $class->SUPER::new('gorgone_audit', + centreon_db_conn => 0, + centstorage_db_conn => 0, + noconfig => 0 + ); + + bless $self, $class; + $self->add_options( + 'url:s' => \$self->{url} + ); + return $self; +} + +sub init { + my $self = shift; + $self->SUPER::init(); + + $self->{url} = 'http://127.0.0.1:8085' if (!defined($self->{url}) || $self->{url} eq ''); + $self->{http} = gorgone::class::http::http->new(logger => $self->{logger}); +} + +sub json_decode { + my ($self, %options) = @_; + + my $decoded; + eval { + $decoded = JSON::XS->new->utf8->decode($options{content}); + }; + if ($@) { + $self->{logger}->writeLogError("cannot decode json response: $@"); + exit(1); + } + + return $decoded; +} + +sub schedule_audit { + my ($self) = @_; + + my ($code, $content) = $self->{http}->request( + http_backend => 'curl', + method => 'POST', + hostname => '', + full_url => $self->{url} . '/api/centreon/audit/schedule', + query_form_post => '{}', + header => [ + 'Accept-Type: application/json; charset=utf-8', + 'Content-Type: application/json; charset=utf-8', + ], + curl_opt => ['CURLOPT_SSL_VERIFYPEER => 0', 'CURLOPT_POSTREDIR => CURL_REDIR_POST_ALL'], + warning_status => '', + unknown_status => '', + critical_status => '' + ); + + if ($code) { + $self->{logger}->writeLogError("http request error"); + exit(1); + } + if ($self->{http}->get_code() < 200 || $self->{http}->get_code() >= 300) { + $self->{logger}->writeLogError("Login error [code: '" . $self->{http}->get_code() . "'] [message: '" . $self->{http}->get_message() . "']"); + exit(1); + } + + my $decoded = $self->json_decode(content => $content); + if (!defined($decoded->{token})) { + $self->{logger}->writeLogError('cannot get token'); + exit(1); + } + + $self->{token} = $decoded->{token}; +} + +sub get_audit_log { + my ($self) = @_; + + my $progress = 0; + while (1) { + my ($code, $content) = $self->{http}->request( + http_backend => 'curl', + method => 'GET', + hostname => '', + full_url => $self->{url} . '/api/log/' . $self->{token}, + header => [ + 'Accept-Type: application/json; charset=utf-8' + ], + curl_opt => ['CURLOPT_SSL_VERIFYPEER => 0', 'CURLOPT_POSTREDIR => CURL_REDIR_POST_ALL'], + warning_status => '', + unknown_status => '', + critical_status => '' + ); + + if ($code) { + $self->{logger}->writeLogError("Login error [code: '" . $self->{http}->get_code() . "'] [message: '" . $self->{http}->get_message() . "']"); + exit(1); + } + if ($self->{http}->get_code() < 200 || $self->{http}->get_code() >= 300) { + $self->{logger}->writeLogError("Login error [code: '" . $self->{http}->get_code() . "'] [message: '" . $self->{http}->get_message() . "']"); + exit(1); + } + + my $decoded = $self->json_decode(content => $content); + if (!defined($decoded->{data})) { + $self->{logger}->writeLogError("Cannot get log information"); + exit(1); + } + + my $stop = 0; + foreach (@{$decoded->{data}}) { + my $data = $self->json_decode(content => $_->{data}); + if ($_->{code} == 500 && $progress < $data->{complete}) { + $self->{logger}->writeLogInfo("audit completed: $data->{complete}\%"); + $progress = $data->{complete}; + } elsif ($_->{code} == 1) { + $self->{logger}->writeLogError("audit execution: $data->{message}"); + $stop = 1; + } elsif ($_->{code} == 2) { + $self->{logger}->writeLogInfo("audit result: " . Data::Dumper::Dumper($data->{audit})); + $stop = 1; + } + } + + last if ($stop == 1); + sleep(10); + } +} + +sub run { + my $self = shift; + + $self->SUPER::run(); + $self->schedule_audit(); + $self->get_audit_log(); +} + +__END__ + +=head1 NAME + +gorgone_audit.pl - script to execute and get audit + +=head1 SYNOPSIS + +gorgone_audit.pl [options] + +=head1 OPTIONS + +=over 8 + +=item B<--url> + +Specify the api url (default: 'http://127.0.0.1:8085'). + +=item B<--severity> + +Set the script log severity (default: 'info'). + +=item B<--help> + +Print a brief help message and exits. + +=back + +=head1 DESCRIPTION + +B<gorgone_audit.pl> + +=cut + diff --git a/gorgone/gorgone/modules/centreon/audit/class.pm b/gorgone/gorgone/modules/centreon/audit/class.pm new file mode 100644 index 00000000000..ec8b30af028 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/audit/class.pm @@ -0,0 +1,389 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::centreon::audit::class; + +use base qw(gorgone::class::module); + +use strict; +use warnings; +use gorgone::standard::library; +use gorgone::standard::constants qw(:all); +use gorgone::standard::misc; +use gorgone::class::sqlquery; +use ZMQ::LibZMQ4; +use ZMQ::Constants qw(:all); + +my %handlers = (TERM => {}, HUP => {}); +my ($connector); + +my @sampling_modules = ( + 'system::cpu', + 'system::diskio' +); +my @metrics_modules = ( + 'centreon::database', + 'centreon::packages', + 'centreon::pluginpacks', + 'centreon::realtime', + 'centreon::rrd', + 'system::cpu', + 'system::disk', + 'system::diskio', + 'system::load', + 'system::memory', + 'system::os' +); + +sub new { + my ($class, %options) = @_; + $connector = $class->SUPER::new(%options); + bless $connector, $class; + + $connector->{audit_tokens} = {}; + $connector->{sampling} = {}; + $connector->{sampling_modules} = {}; + $connector->{metrics_modules} = {}; + + $connector->set_signal_handlers(); + return $connector; +} + +sub set_signal_handlers { + my $self = shift; + + $SIG{TERM} = \&class_handle_TERM; + $handlers{TERM}->{$self} = sub { $self->handle_TERM() }; + $SIG{HUP} = \&class_handle_HUP; + $handlers{HUP}->{$self} = sub { $self->handle_HUP() }; +} + +sub handle_HUP { + my $self = shift; + $self->{reload} = 0; +} + +sub handle_TERM { + my $self = shift; + $self->{logger}->writeLogDebug("[audit] $$ Receiving order to stop..."); + $self->{stop} = 1; +} + +sub class_handle_TERM { + foreach (keys %{$handlers{TERM}}) { + &{$handlers{TERM}->{$_}}(); + } +} + +sub class_handle_HUP { + foreach (keys %{$handlers{HUP}}) { + &{$handlers{HUP}->{$_}}(); + } +} + +sub load_modules { + my ($self, %options) = @_; + + foreach (@sampling_modules) { + my $mod_name = 'gorgone::modules::centreon::audit::sampling::' . $_; + my $ret = gorgone::standard::misc::mymodule_load( + logger => $self->{logger}, + module => $mod_name, + error_msg => "Cannot load sampling module '$_'" + ); + next if ($ret == 1); + $self->{sampling_modules}->{$_} = $mod_name->can('sample'); + } + + foreach (@metrics_modules) { + my $mod_name = 'gorgone::modules::centreon::audit::metrics::' . $_; + my $ret = gorgone::standard::misc::mymodule_load( + logger => $self->{logger}, + module => $mod_name, + error_msg => "Cannot load metrics module '$_'" + ); + next if ($ret == 1); + $self->{metrics_modules}->{$_} = $mod_name->can('metrics'); + } +} + +sub action_centreonauditnode { + my ($self, %options) = @_; + + $self->{logger}->writeLogDebug('[audit] action node starting'); + $options{token} = $self->generate_token() if (!defined($options{token})); + + $self->send_log(code => GORGONE_ACTION_BEGIN, token => $options{token}, data => { message => 'action node starting' }); + + my $metrics = {}; + foreach my $name (keys %{$self->{metrics_modules}}) { + my $result = $self->{metrics_modules}->{$name}->( + os => $self->{os}, + centreon_sqlquery => $self->{centreon_sqlquery}, + centstorage_sqlquery => $self->{centstorage_sqlquery}, + sampling => $self->{sampling}, + params => $options{data}->{content}, + logger => $self->{logger} + ); + next if (!defined($result)); + $metrics->{$name} = $result; + } + + $self->send_log( + code => GORGONE_ACTION_FINISH_OK, + token => $options{token}, + data => { + message => 'action node finished', + metrics => $metrics + } + ); + $self->{logger}->writeLogDebug('[audit] action node finished'); +} + +sub action_centreonauditnodelistener { + my ($self, %options) = @_; + + return 0 if (!defined($options{token}) || $options{token} !~ /^audit-(.*?)-(.*)$/); + my ($audit_token, $audit_node) = ($1, $2); + + return 0 if (!defined($self->{audit_tokens}->{ $audit_token }) || !defined($self->{audit_tokens}->{ $audit_token }->{nodes}->{ $audit_node })); + + if ($options{data}->{code} == GORGONE_ACTION_FINISH_KO) { + $self->{logger}->writeLogError("[audit] audit node listener - node '" . $audit_node . "' error"); + $self->{audit_tokens}->{ $audit_token }->{nodes}->{ $audit_node }->{status_code} = 2; + $self->{audit_tokens}->{ $audit_token }->{nodes}->{ $audit_node }->{status_message} = $options{data}->{data}->{message}; + } elsif ($options{data}->{code} == GORGONE_ACTION_FINISH_OK) { + $self->{logger}->writeLogDebug("[audit] audit node listener - node '" . $audit_node . "' ok"); + $self->{audit_tokens}->{ $audit_token }->{nodes}->{ $audit_node }->{status_code} = 0; + $self->{audit_tokens}->{ $audit_token }->{nodes}->{ $audit_node }->{status_message} = 'ok'; + $self->{audit_tokens}->{ $audit_token }->{nodes}->{ $audit_node }->{metrics} = $options{data}->{data}->{metrics}; + } else { + return 0; + } + $self->{audit_tokens}->{ $audit_token }->{done_nodes}++; + + if ($self->{audit_tokens}->{ $audit_token }->{done_nodes} == $self->{audit_tokens}->{ $audit_token }->{count_nodes}) { + $self->send_log( + code => GORGONE_ACTION_FINISH_OK, + token => $audit_token, + instant => 1, + data => { + message => 'finished', + audit => $self->{audit_tokens}->{ $audit_token } + } + ); + delete $self->{audit_tokens}->{ $audit_token }; + return 1; + } + + my $progress = $self->{audit_tokens}->{ $audit_token }->{done_nodes} * 100 / $self->{audit_tokens}->{ $audit_token }->{count_nodes}; + my $div = int(int($progress) / 5); + if (int($progress) % 3 == 0) { + $self->send_log( + code => GORGONE_MODULE_CENTREON_AUDIT_PROGRESS, + token => $audit_token, + instant => 1, + data => { + message => 'current progress', + complete => sprintf('%.2f', $progress) + } + ); + } + + return 1; +} + +sub action_centreonauditschedule { + my ($self, %options) = @_; + + $self->{logger}->writeLogDebug('[audit] starting schedule action'); + $options{token} = $self->generate_token() if (!defined($options{token})); + $self->send_log(code => GORGONE_ACTION_BEGIN, token => $options{token}, data => { message => 'action schedule proceed' }); + + my $params = {}; + + my ($status, $datas) = $self->{centstorage_sqlquery}->custom_execute( + request => 'SELECT RRDdatabase_path, RRDdatabase_status_path FROM config', + mode => 2 + ); + if ($status == -1) { + $self->send_log(code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot find centstorage config' }); + $self->{logger}->writeLogError('[audit] Cannot find centstorage configuration'); + return 1; + } + $params->{rrd_metrics_path} = $datas->[0]->[0]; + $params->{rrd_status_path} = $datas->[0]->[1]; + + ($status, $datas) = $self->{centreon_sqlquery}->custom_execute( + request => "SELECT id, name FROM nagios_server WHERE ns_activate = '1'", + mode => 2 + ); + if ($status == -1) { + $self->send_log(code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot find nodes configuration' }); + $self->{logger}->writeLogError('[audit] Cannot find nodes configuration'); + return 1; + } + + $self->{audit_tokens}->{ $options{token} } = { + started => time(), + count_nodes => 0, + done_nodes => 0, + nodes => {} + }; + foreach (@$datas) { + $self->send_internal_action( + action => 'ADDLISTENER', + data => [ + { + identity => 'gorgone-audit', + event => 'CENTREONAUDITNODELISTENER', + token => 'audit-' . $options{token} . '-' . $_->[0], + timeout => 300 + } + ] + ); + $self->send_internal_action( + action => 'CENTREONAUDITNODE', + target => $_->[0], + token => 'audit-' . $options{token} . '-' . $_->[0], + data => { + instant => 1, + content => $params + } + ); + + $self->{audit_tokens}->{ $options{token} }->{nodes}->{$_->[0]} = { + name => $_->[1], + status_code => 1, + status_message => 'wip' + }; + $self->{audit_tokens}->{ $options{token} }->{count_nodes}++; + } + + return 0; +} + +sub event { + while (1) { + my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); + last if (!defined($message)); + + $connector->{logger}->writeLogDebug("[audit] Event: $message"); + if ($message =~ /^\[(.*?)\]/) { + if ((my $method = $connector->can('action_' . lc($1)))) { + $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; + my ($action, $token) = ($1, $2); + my $data = JSON::XS->new->utf8->decode($3); + $method->($connector, token => $token, data => $data); + } + } + } +} + +sub sampling { + my ($self, %options) = @_; + + return if (defined($self->{sampling_last}) && (time() - $self->{sampling_last}) < 60); + $self->{logger}->writeLogDebug('[audit] sampling starting'); + foreach (keys %{$self->{sampling_modules}}) { + $self->{sampling_modules}->{$_}->(sampling => $self->{sampling}); + } + + $self->{sampling_last} = time(); +} + +sub get_system { + my ($self, %options) = @_; + + $self->{os} = 'unknown'; + my ($error, $stdout, $return_code) = gorgone::standard::misc::backtick( + command => 'lsb_release -a', + timeout => 5, + wait_exit => 1, + redirect_stderr => 1, + logger => $options{logger} + ); + if ($error == 0 && $stdout =~ /^Description:\s+(.*)$/mi) { + $self->{os} = $1; + } +} + +sub run { + my ($self, %options) = @_; + + # Connect internal + $connector->{internal_socket} = gorgone::standard::library::connect_com( + zmq_type => 'ZMQ_DEALER', + name => 'gorgone-audit', + logger => $self->{logger}, + type => $self->{config_core}->{internal_com_type}, + path => $self->{config_core}->{internal_com_path} + ); + $connector->send_internal_action( + action => 'CENTREONAUDITREADY', + data => {} + ); + + if (defined($self->{config_db_centreon})) { + $self->{db_centreon} = gorgone::class::db->new( + dsn => $self->{config_db_centreon}->{dsn}, + user => $self->{config_db_centreon}->{username}, + password => $self->{config_db_centreon}->{password}, + force => 0, + logger => $self->{logger} + ); + $self->{centreon_sqlquery} = gorgone::class::sqlquery->new(logger => $self->{logger}, db_centreon => $self->{db_centreon}); + } + + if (defined($self->{config_db_centstorage})) { + $self->{db_centstorage} = gorgone::class::db->new( + dsn => $self->{config_db_centstorage}->{dsn}, + user => $self->{config_db_centstorage}->{username}, + password => $self->{config_db_centstorage}->{password}, + force => 0, + logger => $self->{logger} + ); + $self->{centstorage_sqlquery} = gorgone::class::sqlquery->new(logger => $self->{logger}, db_centreon => $self->{db_centstorage}); + } + + $self->load_modules(); + $self->get_system(); + + $self->{poll} = [ + { + socket => $connector->{internal_socket}, + events => ZMQ_POLLIN, + callback => \&event, + } + ]; + while (1) { + # we try to do all we can + my $rev = zmq_poll($self->{poll}, 5000); + if ($rev == 0 && $self->{stop} == 1) { + $self->{logger}->writeLogInfo("[audit] $$ has quit"); + zmq_close($connector->{internal_socket}); + exit(0); + } + + $self->sampling(); + } +} + +1; diff --git a/gorgone/gorgone/modules/centreon/audit/hooks.pm b/gorgone/gorgone/modules/centreon/audit/hooks.pm new file mode 100644 index 00000000000..db6ca9996d5 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/audit/hooks.pm @@ -0,0 +1,179 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::centreon::audit::hooks; + +use warnings; +use strict; +use gorgone::class::core; +use gorgone::modules::centreon::audit::class; +use gorgone::standard::constants qw(:all); +use JSON::XS; + +use constant NAMESPACE => 'centreon'; +use constant NAME => 'audit'; +use constant EVENTS => [ + { event => 'CENTREONAUDITSCHEDULE', uri => '/schedule', method => 'POST' }, + { event => 'CENTREONAUDITNODE', uri => '/node', method => 'POST' }, + { event => 'CENTREONAUDITNODELISTENER' }, + { event => 'CENTREONAUDITREADY' } +]; + +my $config_core; +my $config; +my $audit = {}; +my $stop = 0; +my ($config_db_centreon, $config_db_centstorage); + +sub register { + my (%options) = @_; + + $config = $options{config}; + $config_core = $options{config_core}; + $config_db_centstorage = $options{config_db_centstorage}; + $config_db_centreon = $options{config_db_centreon}; + return (1, NAMESPACE, NAME, EVENTS); +} + +sub init { + my (%options) = @_; + + create_child(logger => $options{logger}); +} + +sub routing { + my (%options) = @_; + + my $data; + eval { + $data = JSON::XS->new->utf8->decode($options{data}); + }; + if ($@) { + $options{logger}->writeLogError("[audit] Cannot decode json data: $@"); + gorgone::standard::library::add_history( + dbh => $options{dbh}, + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'gorgone-audit: cannot decode json' }, + json_encode => 1 + ); + return undef; + } + + if ($options{action} eq 'CENTREONAUDITREADY') { + $audit->{ready} = 1; + return undef; + } + + if (gorgone::class::core::waiting_ready(ready => \$audit->{ready}) == 0) { + gorgone::standard::library::add_history( + dbh => $options{dbh}, + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'gorgone-audit: still no ready' }, + json_encode => 1 + ); + return undef; + } + + gorgone::standard::library::zmq_send_message( + socket => $options{socket}, + identity => 'gorgone-audit', + action => $options{action}, + data => $options{data}, + token => $options{token}, + ); +} + +sub gently { + my (%options) = @_; + + $stop = 1; + $options{logger}->writeLogDebug("[audit] Send TERM signal"); + if ($audit->{running} == 1) { + CORE::kill('TERM', $audit->{pid}); + } +} + +sub kill { + my (%options) = @_; + + if ($audit->{running} == 1) { + $options{logger}->writeLogDebug("[audit] Send KILL signal for child"); + CORE::kill('KILL', $audit->{pid}); + } +} + +sub kill_internal { + my (%options) = @_; + +} + +sub check { + my (%options) = @_; + + my $count = 0; + foreach my $pid (keys %{$options{dead_childs}}) { + # Not me + next if (!defined($audit->{pid}) || $audit->{pid} != $pid); + + $audit = {}; + delete $options{dead_childs}->{$pid}; + if ($stop == 0) { + create_child(logger => $options{logger}); + } + } + + $count++ if (defined($audit->{running}) && $audit->{running} == 1); + + return $count; +} + +sub broadcast { + my (%options) = @_; + + routing(%options); +} + +# Specific functions +sub create_child { + my (%options) = @_; + + $options{logger}->writeLogInfo("[audit] Create module 'audit' process"); + + my $child_pid = fork(); + if ($child_pid == 0) { + $0 = 'gorgone-audit'; + my $module = gorgone::modules::centreon::audit::class->new( + logger => $options{logger}, + module_id => NAME, + config_core => $config_core, + config => $config, + config_db_centreon => $config_db_centreon, + config_db_centstorage => $config_db_centstorage + ); + $module->run(); + exit(0); + } + $options{logger}->writeLogDebug("[audit] PID $child_pid (gorgone-audit)"); + $audit = { pid => $child_pid, ready => 0, running => 1 }; +} + +1; diff --git a/gorgone/gorgone/modules/centreon/audit/metrics/centreon/database.pm b/gorgone/gorgone/modules/centreon/audit/metrics/centreon/database.pm new file mode 100644 index 00000000000..de32ac931ad --- /dev/null +++ b/gorgone/gorgone/modules/centreon/audit/metrics/centreon/database.pm @@ -0,0 +1,110 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::centreon::audit::metrics::centreon::database; + +use warnings; +use strict; + +sub metrics { + my (%options) = @_; + + return undef if (!defined($options{centstorage_sqlquery})); + + my $metrics = { + status_code => 0, + status_message => 'ok', + space_free_bytes => 0, + space_used_bytes => 0, + databases => {} + }; + + my ($status, $datas) = $options{centstorage_sqlquery}->custom_execute( + request => q{show variables like 'innodb_file_per_table'}, + mode => 2 + ); + if ($status == -1 || !defined($datas->[0])) { + $metrics->{status_code} = 1; + $metrics->{status_message} = 'cannot get innodb_file_per_table configuration'; + return $metrics; + } + my $innodb_per_table = 0; + $innodb_per_table = 1 if ($datas->[0]->[1] =~ /on/i); + + ($status, $datas) = $options{centstorage_sqlquery}->custom_execute( + request => q{SELECT table_schema, table_name, engine, data_free, data_length+index_length as data_used, (DATA_FREE / (DATA_LENGTH+INDEX_LENGTH)) as TAUX_FRAG FROM information_schema.tables WHERE table_type = 'BASE TABLE' AND engine IN ('InnoDB', 'MyISAM')}, + mode => 2 + ); + if ($status == -1 || !defined($datas->[0])) { + $metrics->{status_code} = 1; + $metrics->{status_message} = 'cannot get schema information'; + return $metrics; + } + + my $innodb_ibdata_done = 0; + foreach my $row (@$datas) { + if (!defined($metrics->{databases}->{ $row->[0] })) { + $metrics->{databases}->{ $row->[0] } = { + space_free_bytes => 0, + space_used_bytes => 0, + tables => {} + }; + } + + $metrics->{databases}->{ $row->[0] }->{tables}->{ $row->[1] } = {}; + + # For a table located in the shared tablespace, this is the free space of the shared tablespace. + if ($row->[2] !~ /innodb/i || $innodb_per_table == 1) { + $metrics->{space_free_bytes} += $row->[3]; + $metrics->{databases}->{ $row->[0] }->{space_free_bytes} += $row->[3]; + $metrics->{databases}->{ $row->[0] }->{tables}->{ $row->[1] }->{space_free_bytes} = $row->[3]; + $metrics->{databases}->{ $row->[0] }->{tables}->{ $row->[1] }->{frag} = $row->[5]; + } elsif ($innodb_ibdata_done == 0) { + $metrics->{space_free_bytes} += $row->[3]; + $innodb_ibdata_done = 1; + } + $metrics->{space_used_bytes} += $row->[4]; + $metrics->{databases}->{ $row->[0] }->{space_used_bytes} += $row->[4]; + $metrics->{databases}->{ $row->[0] }->{tables}->{ $row->[1] }->{space_used_bytes} = $row->[4]; + $metrics->{databases}->{ $row->[0] }->{tables}->{ $row->[1] }->{engine} = $row->[2]; + } + + my $rm_table_size = 10 * 1024 * 1024; + + $metrics->{space_free_human} = join('', gorgone::standard::misc::scale(value => $metrics->{space_free_bytes}, format => '%.2f')); + $metrics->{space_used_human} = join('', gorgone::standard::misc::scale(value => $metrics->{space_used_bytes}, format => '%.2f')); + foreach my $db (keys %{$metrics->{databases}}) { + $metrics->{databases}->{$db}->{space_used_human} = join('', gorgone::standard::misc::scale(value => $metrics->{databases}->{$db}->{space_used_bytes}, format => '%.2f')); + $metrics->{databases}->{$db}->{space_free_human} = join('', gorgone::standard::misc::scale(value => $metrics->{databases}->{$db}->{space_free_bytes}, format => '%.2f')); + foreach my $table (keys %{$metrics->{databases}->{$db}->{tables}}) { + if ($metrics->{databases}->{$db}->{tables}->{$table}->{space_used_bytes} < $rm_table_size) { + delete $metrics->{databases}->{$db}->{tables}->{$table}; + next; + } + $metrics->{databases}->{$db}->{tables}->{$table}->{space_free_human} = join('', gorgone::standard::misc::scale(value => $metrics->{databases}->{$db}->{tables}->{$table}->{space_free_bytes}, format => '%.2f')) + if (defined($metrics->{databases}->{$db}->{tables}->{$table}->{space_free_bytes})); + $metrics->{databases}->{$db}->{tables}->{$table}->{space_used_human} = join('', gorgone::standard::misc::scale(value => $metrics->{databases}->{$db}->{tables}->{$table}->{space_used_bytes}, format => '%.2f')); + } + } + + return $metrics; +} + +1; diff --git a/gorgone/gorgone/modules/centreon/audit/metrics/centreon/packages.pm b/gorgone/gorgone/modules/centreon/audit/metrics/centreon/packages.pm new file mode 100644 index 00000000000..52049b0f419 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/audit/metrics/centreon/packages.pm @@ -0,0 +1,92 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::centreon::audit::metrics::centreon::packages; + +use warnings; +use strict; +use gorgone::standard::misc; + +sub dpkg_list { + my (%options) = @_; + + my ($error, $stdout, $return_code) = gorgone::standard::misc::backtick( + command => "dpkg-query -W -f='\${binary:Package}\\t\${Version}\\n' 'centreon*'", + timeout => 30, + wait_exit => 1, + redirect_stderr => 1, + logger => $options{logger} + ); + if ($error != 0 || $return_code != 0) { + $options{metrics}->{status_code} = 1; + $options{metrics}->{status_message} = $stdout; + return ; + } + + foreach (split(/\n/, $stdout)) { + my ($name, $version) = split(/\t/); + push @{$options{metrics}->{list}}, [$name, $version]; + } +} + +sub rpm_list { + my (%options) = @_; + + my ($error, $stdout, $return_code) = gorgone::standard::misc::backtick( + command => 'rpm -qa --queryformat "%{NAME}\t%{RPMTAG_VERSION}-%{RPMTAG_RELEASE}\n" | grep centreon', + timeout => 30, + wait_exit => 1, + redirect_stderr => 1, + logger => $options{logger} + ); + if ($error != 0 || $return_code != 0) { + $options{metrics}->{status_code} = 1; + $options{metrics}->{status_message} = $stdout; + return ; + } + + foreach (split(/\n/, $stdout)) { + my ($name, $version) = split(/\t/); + push @{$options{metrics}->{list}}, [$name, $version]; + } +} + +sub metrics { + my (%options) = @_; + + my $metrics = { + status_code => 0, + status_message => 'ok', + list => [] + }; + + if ($options{os} =~ /Debian|Ubuntu/i) { + dpkg_list(metrics => $metrics); + } elsif ($options{os} =~ /CentOS|Redhat/i) { + rpm_list(metrics => $metrics); + } else { + $metrics->{status_code} = 1; + $metrics->{status_message} = 'unsupported os'; + } + + return $metrics; +} + +1; diff --git a/gorgone/gorgone/modules/centreon/audit/metrics/centreon/pluginpacks.pm b/gorgone/gorgone/modules/centreon/audit/metrics/centreon/pluginpacks.pm new file mode 100644 index 00000000000..fa790e20225 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/audit/metrics/centreon/pluginpacks.pm @@ -0,0 +1,53 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::centreon::audit::metrics::centreon::pluginpacks; + +use warnings; +use strict; + +sub metrics { + my (%options) = @_; + + return undef if (!defined($options{centreon_sqlquery})); + + my $metrics = { + status_code => 0, + status_message => 'ok', + installed => [] + }; + + my ($status, $datas) = $options{centreon_sqlquery}->custom_execute( + request => "SELECT slug, version FROM mod_ppm_pluginpack", + mode => 2 + ); + if ($status == -1) { + $metrics->{status_code} = 1; + $metrics->{status_message} = 'cannot get plugin-packs installed'; + return $metrics; + } + foreach (@$datas) { + push @{$metrics->{installed}}, { slug => $_->[0], version => $_->[1] }; + } + + return $metrics; +} + +1; diff --git a/gorgone/gorgone/modules/centreon/audit/metrics/centreon/realtime.pm b/gorgone/gorgone/modules/centreon/audit/metrics/centreon/realtime.pm new file mode 100644 index 00000000000..41567275d25 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/audit/metrics/centreon/realtime.pm @@ -0,0 +1,99 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::centreon::audit::metrics::centreon::realtime; + +use warnings; +use strict; + +sub metrics { + my (%options) = @_; + + return undef if (!defined($options{centstorage_sqlquery})); + + my $metrics = { + status_code => 0, + status_message => 'ok', + hosts_count => 0, + services_count => 0, + hostgroups_count => 0, + servicegroups_count => 0, + acl_count => 0 + }; + + my ($status, $datas) = $options{centstorage_sqlquery}->custom_execute( + request => "SELECT count(*) FROM instances, hosts, services WHERE instances.running = '1' AND hosts.instance_id = instances.instance_id AND hosts.enabled = '1' AND services.host_id = hosts.host_id AND services.enabled = '1'", + mode => 2 + ); + if ($status == -1 || !defined($datas->[0])) { + $metrics->{status_code} = 1; + $metrics->{status_message} = 'cannot get number of services'; + return $metrics; + } + $metrics->{services_count} = $datas->[0]->[0]; + + ($status, $datas) = $options{centstorage_sqlquery}->custom_execute( + request => "SELECT count(*) FROM instances, hosts WHERE instances.running = '1' AND hosts.instance_id = instances.instance_id AND hosts.enabled = '1'", + mode => 2 + ); + if ($status == -1 || !defined($datas->[0])) { + $metrics->{status_code} = 1; + $metrics->{status_message} = 'cannot get number of hosts'; + return $metrics; + } + $metrics->{hosts_count} = $datas->[0]->[0]; + + ($status, $datas) = $options{centstorage_sqlquery}->custom_execute( + request => 'SELECT count(*) FROM hostgroups', + mode => 2 + ); + if ($status == -1 || !defined($datas->[0])) { + $metrics->{status_code} = 1; + $metrics->{status_message} = 'cannot get number of hostgroups'; + return $metrics; + } + $metrics->{hostgroups_count} = $datas->[0]->[0]; + + ($status, $datas) = $options{centstorage_sqlquery}->custom_execute( + request => 'SELECT count(*) FROM servicegroups', + mode => 2 + ); + if ($status == -1 || !defined($datas->[0])) { + $metrics->{status_code} = 1; + $metrics->{status_message} = 'cannot get number of servicegroups'; + return $metrics; + } + $metrics->{servicegroups_count} = $datas->[0]->[0]; + + ($status, $datas) = $options{centstorage_sqlquery}->custom_execute( + request => 'SELECT count(*) FROM centreon_acl', + mode => 2 + ); + if ($status == -1 || !defined($datas->[0])) { + $metrics->{status_code} = 1; + $metrics->{status_message} = 'cannot get number of acl'; + return $metrics; + } + $metrics->{acl_count} = $datas->[0]->[0]; + + return $metrics; +} + +1; diff --git a/gorgone/gorgone/modules/centreon/audit/metrics/centreon/rrd.pm b/gorgone/gorgone/modules/centreon/audit/metrics/centreon/rrd.pm new file mode 100644 index 00000000000..c2c961b190e --- /dev/null +++ b/gorgone/gorgone/modules/centreon/audit/metrics/centreon/rrd.pm @@ -0,0 +1,68 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::centreon::audit::metrics::centreon::rrd; + +use warnings; +use strict; +use gorgone::standard::misc; + +sub metrics { + my (%options) = @_; + + return undef if (!defined($options{params}->{rrd_metrics_path})); + return undef if (! -d $options{params}->{rrd_metrics_path}); + + my $metrics = { + status_code => 0, + status_message => 'ok', + rrd_metrics_count => 0, + rrd_status_count => 0, + rrd_metrics_bytes => 0, + rrd_status_bytes => 0, + rrd_metrics_outdated => 0, + rrd_status_outdated => 0 + }; + + my $outdated_time = time() - (180 * 86400); + my $dh; + foreach my $type (('metrics', 'status')) { + if (!opendir($dh, $options{params}->{'rrd_' . $type . '_path'})) { + $metrics->{status_code} = 1; + $metrics->{status_message} = "Could not open directoy for reading: $!"; + next; + } + while (my $file = readdir($dh)) { + next if ($file !~ /\.rrd/); + $metrics->{'rrd_' . $type . '_count'}++; + my @attrs = stat($options{params}->{'rrd_' . $type . '_path'} . '/' . $file); + $metrics->{'rrd_' . $type . '_bytes'} += $attrs[7] if (defined($attrs[7])); + $metrics->{'rrd_' . $type . '_outdated'}++ if ($attrs[9] < $outdated_time); + } + closedir($dh); + } + + $metrics->{rrd_metrics_human} = join('', gorgone::standard::misc::scale(value => $metrics->{rrd_metrics_bytes}, format => '%.2f')); + $metrics->{rrd_status_human} = join('', gorgone::standard::misc::scale(value => $metrics->{rrd_status_bytes}, format => '%.2f')); + + return $metrics; +} + +1; diff --git a/gorgone/gorgone/modules/centreon/audit/metrics/system/cpu.pm b/gorgone/gorgone/modules/centreon/audit/metrics/system/cpu.pm new file mode 100644 index 00000000000..ea8fad5bc0f --- /dev/null +++ b/gorgone/gorgone/modules/centreon/audit/metrics/system/cpu.pm @@ -0,0 +1,62 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::centreon::audit::metrics::system::cpu; + +use warnings; +use strict; + +sub metrics { + my (%options) = @_; + + my $metrics = { + status_code => 0, + status_message => 'ok', + num_cpu => 0 + }; + if ($options{sampling}->{cpu}->{status_code} != 0) { + $metrics->{status_code} = $options{sampling}->{cpu}->{status_code}; + $metrics->{status_message} = $options{sampling}->{cpu}->{status_message}; + return $metrics; + } + + $metrics->{num_cpu} = $options{sampling}->{cpu}->{num_cpu}; + foreach (([1, '1min'], [4, '5min'], [14, '15min'], [59, '60min'])) { + $metrics->{ 'avg_used_' . $_->[1] } = 'n/a'; + $metrics->{ 'avg_iowait_' . $_->[1] } = 'n/a'; + next if (!defined($options{sampling}->{cpu}->{values}->[ $_->[0] ])); + $metrics->{ 'avg_used_' . $_->[1] } = sprintf( + '%.2f', + 100 - ( + 100 * ($options{sampling}->{cpu}->{values}->[0]->[1] - $options{sampling}->{cpu}->{values}->[ $_->[0] ]->[1]) + / ($options{sampling}->{cpu}->{values}->[0]->[0] - $options{sampling}->{cpu}->{values}->[ $_->[0] ]->[0]) + ) + ); + $metrics->{ 'avg_iowait_' . $_->[1] } = sprintf( + '%.2f', + 100 * ($options{sampling}->{cpu}->{values}->[0]->[2] - $options{sampling}->{cpu}->{values}->[ $_->[0] ]->[2]) + / ($options{sampling}->{cpu}->{values}->[0]->[0] - $options{sampling}->{cpu}->{values}->[ $_->[0] ]->[0]) + ); + } + + return $metrics; +} + +1; diff --git a/gorgone/gorgone/modules/centreon/audit/metrics/system/disk.pm b/gorgone/gorgone/modules/centreon/audit/metrics/system/disk.pm new file mode 100644 index 00000000000..ad9a59433d0 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/audit/metrics/system/disk.pm @@ -0,0 +1,68 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::centreon::audit::metrics::system::disk; + +use warnings; +use strict; +use gorgone::standard::misc; + +sub metrics { + my (%options) = @_; + + my $metrics = { + status_code => 0, + status_message => 'ok', + partitions => {} + }; + + my ($error, $stdout, $return_code) = gorgone::standard::misc::backtick( + command => 'df -P -k -T', + timeout => 5, + wait_exit => 1, + redirect_stderr => 1, + logger => $options{logger} + ); + if ($error != 0) { + $metrics->{status_code} = 1; + $metrics->{status_message} = $stdout; + return $metrics; + } + + foreach my $line (split(/\n/, $stdout)) { + next if ($line !~ /^(\S+)\s+(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(.*)/); + $metrics->{partitions}->{$7} = { + mount => $7, + filesystem => $1, + type => $2, + space_size_bytes => $3 * 1024, + space_size_human => join('', gorgone::standard::misc::scale(value => $3 * 1024, format => '%.2f')), + space_used_bytes => $4 * 1024, + space_used_human => join('', gorgone::standard::misc::scale(value => $4 * 1024, format => '%.2f')), + space_free_bytes => $5 * 1024, + space_free_human => join('', gorgone::standard::misc::scale(value => $5 * 1024, format => '%.2f')), + inodes_used_percent => $6 + }; + } + + return $metrics; +} + +1; diff --git a/gorgone/gorgone/modules/centreon/audit/metrics/system/diskio.pm b/gorgone/gorgone/modules/centreon/audit/metrics/system/diskio.pm new file mode 100644 index 00000000000..31df14c6e7b --- /dev/null +++ b/gorgone/gorgone/modules/centreon/audit/metrics/system/diskio.pm @@ -0,0 +1,74 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::centreon::audit::metrics::system::diskio; + +use warnings; +use strict; + +sub metrics { + my (%options) = @_; + + my $metrics = { + status_code => 0, + status_message => 'ok', + partitions => {} + }; + if ($options{sampling}->{diskio}->{status_code} != 0) { + $metrics->{status_code} = $options{sampling}->{diskio}->{status_code}; + $metrics->{status_message} = $options{sampling}->{diskio}->{status_message}; + return $metrics; + } + + foreach my $partname (keys %{$options{sampling}->{diskio}->{partitions}}) { + $metrics->{partitions}->{$partname} = {}; + foreach (([1, '1min'], [4, '5min'], [14, '15min'], [59, '60min'])) { + $metrics->{partitions}->{$partname}->{ 'read_iops_' . $_->[1] . '_bytes' } = 'n/a'; + $metrics->{partitions}->{$partname}->{ 'write_iops_' . $_->[1] . '_bytes' } = 'n/a'; + $metrics->{partitions}->{$partname}->{ 'read_time_' . $_->[1] . '_ms' } = 'n/a'; + $metrics->{partitions}->{$partname}->{ 'write_time_' . $_->[1] . '_ms' } = 'n/a'; + next if (!defined($options{sampling}->{diskio}->{partitions}->{$partname}->[ $_->[0] ])); + + $metrics->{partitions}->{$partname}->{ 'read_iops_' . $_->[1] . '_bytes' } = sprintf( + '%.2f', + ($options{sampling}->{diskio}->{partitions}->{$partname}->[0]->[1] - $options{sampling}->{diskio}->{partitions}->{$partname}->[ $_->[0] ]->[1]) + / ($options{sampling}->{diskio}->{partitions}->{$partname}->[0]->[0] - $options{sampling}->{diskio}->{partitions}->{$partname}->[ $_->[0] ]->[0]) + ); + $metrics->{partitions}->{$partname}->{ 'read_iops_' . $_->[1] . '_human' } = join('', gorgone::standard::misc::scale(value => $metrics->{partitions}->{$partname}->{ 'read_iops_' . $_->[1] . '_bytes' }, format => '%.2f')); + $metrics->{partitions}->{$partname}->{ 'write_iops_' . $_->[1] . '_bytes' } = sprintf( + '%.2f', + ($options{sampling}->{diskio}->{partitions}->{$partname}->[0]->[2] - $options{sampling}->{diskio}->{partitions}->{$partname}->[ $_->[0] ]->[2]) + / ($options{sampling}->{diskio}->{partitions}->{$partname}->[0]->[0] - $options{sampling}->{diskio}->{partitions}->{$partname}->[ $_->[0] ]->[0]) + ); + $metrics->{partitions}->{$partname}->{ 'write_iops_' . $_->[1] . '_human' } = join('', gorgone::standard::misc::scale(value => $metrics->{partitions}->{$partname}->{ 'write_iops_' . $_->[1] . '_bytes' }, format => '%.2f')); + + $metrics->{partitions}->{$partname}->{ 'read_time_' . $_->[1] . '_ms' } = sprintf( + '%s', ($options{sampling}->{diskio}->{partitions}->{$partname}->[0]->[3] - $options{sampling}->{diskio}->{partitions}->{$partname}->[ $_->[0] ]->[3]) + ); + $metrics->{partitions}->{$partname}->{ 'read_time_' . $_->[1] . '_ms' } = sprintf( + '%s', ($options{sampling}->{diskio}->{partitions}->{$partname}->[0]->[4] - $options{sampling}->{diskio}->{partitions}->{$partname}->[ $_->[0] ]->[4]) + ); + } + } + + return $metrics; +} + +1; diff --git a/gorgone/gorgone/modules/centreon/audit/metrics/system/load.pm b/gorgone/gorgone/modules/centreon/audit/metrics/system/load.pm new file mode 100644 index 00000000000..eb4dba4a5b3 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/audit/metrics/system/load.pm @@ -0,0 +1,53 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::centreon::audit::metrics::system::load; + +use warnings; +use strict; +use gorgone::standard::misc; + +sub metrics { + my (%options) = @_; + + my $metrics = { + status_code => 0, + status_message => 'ok' + }; + my ($ret, $message, $buffer) = gorgone::standard::misc::slurp(file => '/proc/loadavg'); + if ($ret == 0) { + $metrics->{status_code} = 1; + $metrics->{status_message} = $message; + return $metrics; + } + + if ($buffer !~ /^([0-9\.]+)\s+([0-9\.]+)\s+([0-9\.]+)/mi) { + $metrics->{status_code} = 1; + $metrics->{status_message} = 'cannot find load information'; + return $metrics; + } + + $metrics->{load1m} = $1; + $metrics->{load5m} = $2; + $metrics->{load15m} = $3; + return $metrics; +} + +1; diff --git a/gorgone/gorgone/modules/centreon/audit/metrics/system/memory.pm b/gorgone/gorgone/modules/centreon/audit/metrics/system/memory.pm new file mode 100644 index 00000000000..98f5a734ea8 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/audit/metrics/system/memory.pm @@ -0,0 +1,70 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::centreon::audit::metrics::system::memory; + +use warnings; +use strict; +use gorgone::standard::misc; + +sub metrics { + my (%options) = @_; + + my $metrics = { + status_code => 0, + status_message => 'ok', + ram_total_bytes => 0, + ram_available_bytes => 0, + swap_total_bytes => 0, + swap_free_bytes => 0 + }; + my ($ret, $message, $buffer) = gorgone::standard::misc::slurp(file => '/proc/meminfo'); + if ($ret == 0) { + $metrics->{status_code} = 1; + $metrics->{status_message} = $message; + return $metrics; + } + + if ($buffer !~ /^MemTotal:\s+(\d+)/mi) { + $metrics->{status_code} = 1; + $metrics->{status_message} = 'cannot find memory information'; + return $metrics; + } + + $metrics->{ram_total_bytes} = $1 * 1024; + $metrics->{ram_total_human} = join('', gorgone::standard::misc::scale(value => $metrics->{ram_total_bytes}, format => '%.2f')); + + if ($buffer =~ /^MemAvailable:\s+(\d+)/mi) { + $metrics->{ram_available_bytes} = $1 * 1024; + $metrics->{ram_available_human} = join('', gorgone::standard::misc::scale(value => $metrics->{ram_available_bytes}, format => '%.2f')); + } + if ($buffer =~ /^SwapTotal:\s+(\d+)/mi) { + $metrics->{swap_total_bytes} = $1 * 1024; + $metrics->{swap_total_human} = join('', gorgone::standard::misc::scale(value => $metrics->{swap_total_bytes}, format => '%.2f')); + } + if ($buffer =~ /^SwapFree:\s+(\d+)/mi) { + $metrics->{swap_free_bytes} = $1 * 1024; + $metrics->{swap_free_human} = join('', gorgone::standard::misc::scale(value => $metrics->{swap_free_bytes}, format => '%.2f')); + } + + return $metrics; +} + +1; diff --git a/gorgone/gorgone/modules/centreon/audit/metrics/system/os.pm b/gorgone/gorgone/modules/centreon/audit/metrics/system/os.pm new file mode 100644 index 00000000000..1bd0d4a5b1b --- /dev/null +++ b/gorgone/gorgone/modules/centreon/audit/metrics/system/os.pm @@ -0,0 +1,56 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::centreon::audit::metrics::system::os; + +use warnings; +use strict; + +sub metrics { + my (%options) = @_; + + my $metrics = { + kernel => { + status_code => 0, + status_message => 'ok', + value => 'n/a' + } + }; + + my ($error, $stdout, $return_code) = gorgone::standard::misc::backtick( + command => 'uname -a', + timeout => 5, + wait_exit => 1, + redirect_stderr => 1, + logger => $options{logger} + ); + if ($error != 0) { + $metrics->{kernel}->{status_code} = 1; + $metrics->{kernel}->{status_message} = $stdout; + } else { + $metrics->{kernel}->{value} = $stdout; + } + + $metrics->{os}->{value} = $options{os}; + + return $metrics; +} + +1; diff --git a/gorgone/gorgone/modules/centreon/audit/sampling/system/cpu.pm b/gorgone/gorgone/modules/centreon/audit/sampling/system/cpu.pm new file mode 100644 index 00000000000..3dd99e412bc --- /dev/null +++ b/gorgone/gorgone/modules/centreon/audit/sampling/system/cpu.pm @@ -0,0 +1,68 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::centreon::audit::sampling::system::cpu; + +use warnings; +use strict; +use gorgone::standard::misc; + +sub sample { + my (%options) = @_; + + if (!defined($options{sampling}->{cpu})) { + $options{sampling}->{cpu} = { + status_code => 0, + status_message => 'ok', + round => 0, + values => [] + }; + } + + $options{sampling}->{cpu}->{round}++; + my ($ret, $message, $buffer) = gorgone::standard::misc::slurp(file => '/proc/stat'); + if ($ret == 0) { + $options{sampling}->{cpu}->{status_code} = 1; + $options{sampling}->{cpu}->{status_message} = $message; + return ; + } + + if ($buffer !~ /^cpu\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/) { + $options{sampling}->{cpu}->{status_code} = 1; + $options{sampling}->{cpu}->{status_message} = 'cannot find cpu information'; + return ; + } + + $options{sampling}->{cpu}->{num_cpu} = 0; + while ($buffer =~ /^cpu(\d+)/mg) { + $options{sampling}->{cpu}->{num_cpu}++; + } + + unshift @{$options{sampling}->{cpu}->{values}}, [ + $1 + $2 + $3 + $4 + $5 + $6 + $7, + $4, + $5 + ]; + if (scalar(@{$options{sampling}->{cpu}->{values}}) > 60) { + pop @{$options{sampling}->{cpu}->{values}}; + } +} + +1; diff --git a/gorgone/gorgone/modules/centreon/audit/sampling/system/diskio.pm b/gorgone/gorgone/modules/centreon/audit/sampling/system/diskio.pm new file mode 100644 index 00000000000..7ca7dac342e --- /dev/null +++ b/gorgone/gorgone/modules/centreon/audit/sampling/system/diskio.pm @@ -0,0 +1,63 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::centreon::audit::sampling::system::diskio; + +use warnings; +use strict; +use gorgone::standard::misc; + +sub sample { + my (%options) = @_; + + if (!defined($options{sampling}->{diskio})) { + $options{sampling}->{diskio} = { + status_code => 0, + status_message => 'ok', + partitions => {} + }; + } + + my $time = time(); + my ($ret, $message, $buffer) = gorgone::standard::misc::slurp(file => '/proc/diskstats'); + if ($ret == 0) { + $options{sampling}->{diskio}->{status_code} = 1; + $options{sampling}->{diskio}->{status_message} = $message; + return ; + } + + while ($buffer =~ /^\s*\S+\s+\S+\s+(\S+)\s+\d+\s+\d+\s+(\d+)\s+(\d+)\s+\d+\s+\d+\s+(\d+)\s+(\d+)\s+\d+\s+\d+\s+(\d+)/mg) { + my ($partition_name, $read_sector, $write_sector, $read_ms, $write_ms) = ($1, $2, $4, $3, $5); + next if ($read_sector == 0 && $write_sector == 0); + if (!defined($options{sampling}->{diskio}->{partitions}->{$partition_name})) { + $options{sampling}->{diskio}->{partitions}->{$partition_name} = []; + } + unshift @{$options{sampling}->{diskio}->{partitions}->{$partition_name}}, [ + $time, + $read_sector, $write_sector, + $read_ms, $write_ms + ]; + if (scalar(@{$options{sampling}->{diskio}->{partitions}->{$partition_name}}) > 60) { + pop @{$options{sampling}->{diskio}->{partitions}->{$partition_name}}; + } + } +} + +1; diff --git a/gorgone/gorgone/standard/constants.pm b/gorgone/gorgone/standard/constants.pm index 09fb164587c..c7303435066 100644 --- a/gorgone/gorgone/standard/constants.pm +++ b/gorgone/gorgone/standard/constants.pm @@ -41,7 +41,9 @@ BEGIN { GORGONE_MODULE_CENTREON_JUDGE_FAILOVER_RUNNING => 300, GORGONE_MODULE_CENTREON_JUDGE_FAILBACK_RUNNING => 301, - GORGONE_MODULE_CENTREON_AUTODISCO_SVC_PROGRESS => 400 + GORGONE_MODULE_CENTREON_AUTODISCO_SVC_PROGRESS => 400, + + GORGONE_MODULE_CENTREON_AUDIT_PROGRESS => 500 ); } diff --git a/gorgone/gorgone/standard/misc.pm b/gorgone/gorgone/standard/misc.pm index 09367ce3652..db75393dcf2 100644 --- a/gorgone/gorgone/standard/misc.pm +++ b/gorgone/gorgone/standard/misc.pm @@ -254,4 +254,66 @@ sub trim { return $value; } +sub slurp { + my (%options) = @_; + + my ($fh, $size); + if (!open($fh, '<', $options{file})) { + return (0, "Could not open $options{file}: $!"); + } + my $buffer = do { local $/; <$fh> }; + close $fh; + return (1, 'ok', $buffer); +} + +sub scale { + my (%options) = @_; + + my ($src_quantity, $src_unit) = (undef, 'B'); + if (defined($options{src_unit}) && $options{src_unit} =~ /([kmgtpe])?(b)/i) { + $src_quantity = $1; + $src_unit = $2; + } + my ($dst_quantity, $dst_unit) = ('auto', $src_unit); + if (defined($options{dst_unit}) && $options{dst_unit} =~ /([kmgtpe])?(b)/i) { + $dst_quantity = $1; + $dst_unit = $2; + } + + my $base = 1024; + $options{value} *= 8 if ($dst_unit eq 'b' && $src_unit eq 'B'); + $options{value} /= 8 if ($dst_unit eq 'B' && $src_unit eq 'b'); + $base = 1000 if ($dst_unit eq 'b'); + + my %expo = (k => 1, m => 2, g => 3, t => 4, p => 5, e => 6); + my $src_expo = 0; + $src_expo = $expo{ lc($src_quantity) } if (defined($src_quantity)); + + if (defined($dst_quantity) && $dst_quantity eq 'auto') { + my @auto = ('', 'k', 'm', 'g', 't', 'p', 'e'); + for (; $src_expo < scalar(@auto); $src_expo++) { + last if ($options{value} < $base); + $options{value} = $options{value} / $base; + } + + if (defined($options{format}) && $options{format} ne '') { + $options{value} = sprintf($options{format}, $options{value}); + } + return ($options{value}, uc($auto[$src_expo]) . $dst_unit); + } + + my $dst_expo = 0; + $dst_expo = $expo{ lc($dst_quantity) } if (defined($dst_quantity)); + if ($dst_expo - $src_expo > 0) { + $options{value} = $options{value} / ($base ** ($dst_expo - $src_expo)); + } elsif ($dst_expo - $src_expo < 0) { + $options{value} = $options{value} * ($base ** (($dst_expo - $src_expo) * -1)); + } + + if (defined($options{format}) && $options{format} ne '') { + $options{value} = sprintf($options{format}, $options{value}); + } + return ($options{value}, $options{dst_unit}); +} + 1; From 8bc88db1951689d36187ddee5e5cc262d8e36672 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Wed, 21 Jul 2021 10:23:14 +0200 Subject: [PATCH 597/948] enh(module): centreon nodes - add endpoint for synchronization (#138) (MON-10830) --- gorgone/docs/modules/centreon/nodes.md | 26 ++++++++++++++++++- .../gorgone/modules/centreon/nodes/class.pm | 6 ++--- .../gorgone/modules/centreon/nodes/hooks.pm | 3 ++- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/gorgone/docs/modules/centreon/nodes.md b/gorgone/docs/modules/centreon/nodes.md index bb90c46e668..b7eb23bbaa0 100644 --- a/gorgone/docs/modules/centreon/nodes.md +++ b/gorgone/docs/modules/centreon/nodes.md @@ -26,4 +26,28 @@ enable: true ## API -No API endpoints. +### Synchronize centreon nodes configuration + +| Endpoint | Method | +| :------------------- | :----- | +| /centreon/nodes/sync | `POST` | + +#### Headers + +| Header | Value | +| :----------- | :--------------- | +| Accept | application/json | +| Content-Type | application/json | + +#### Body + +No parameters. + +#### Example + +```bash +curl --request POST "https://hostname:8443/api/centreon/nodes/sync" \ + --header "Accept: application/json" \ + --header "Content-Type: application/json" \ + --data "{}" +``` diff --git a/gorgone/gorgone/modules/centreon/nodes/class.pm b/gorgone/gorgone/modules/centreon/nodes/class.pm index 141f94ba318..939a808ab65 100644 --- a/gorgone/gorgone/modules/centreon/nodes/class.pm +++ b/gorgone/gorgone/modules/centreon/nodes/class.pm @@ -105,7 +105,7 @@ sub check_debug { return 0; } -sub action_nodesresync { +sub action_centreonnodessync { my ($self, %options) = @_; $options{token} = $self->generate_token() if (!defined($options{token})); @@ -255,7 +255,7 @@ sub run { { socket => $connector->{internal_socket}, events => ZMQ_POLLIN, - callback => \&event, + callback => \&event } ]; while (1) { @@ -269,7 +269,7 @@ sub run { if (time() - $self->{resync_time} > $self->{last_resync_time}) { $self->{last_resync_time} = time(); - $self->action_nodesresync(); + $self->action_centreonnodessync(); } } } diff --git a/gorgone/gorgone/modules/centreon/nodes/hooks.pm b/gorgone/gorgone/modules/centreon/nodes/hooks.pm index 53fc5f8fd28..fe7cd6a20e6 100644 --- a/gorgone/gorgone/modules/centreon/nodes/hooks.pm +++ b/gorgone/gorgone/modules/centreon/nodes/hooks.pm @@ -30,7 +30,8 @@ use gorgone::standard::constants qw(:all); use constant NAMESPACE => 'centreon'; use constant NAME => 'nodes'; use constant EVENTS => [ - { event => 'CENTREONNODESREADY' }, + { event => 'CENTREONNODESSYNC', uri => '/sync', method => 'POST' }, + { event => 'CENTREONNODESREADY' } ]; my $config_core; From f503800a2a8ccdabe3672df7fdef7776a8380ec5 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Wed, 21 Jul 2021 16:26:43 +0200 Subject: [PATCH 598/948] fix(proxy): better error handling when pull node never connected (#139) --- gorgone/gorgone/modules/core/proxy/hooks.pm | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index d516fed3af3..1395dbaa2d6 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -533,6 +533,17 @@ sub pathway { } } + if (!defined($first_target)) { + $options{logger}->writeLogDebug("[proxy] no pathway for target '$target'"); + gorgone::standard::library::add_history( + dbh => $options{dbh}, + code => GORGONE_ACTION_FINISH_KO, token => $options{token}, + data => { message => 'proxy - no pathway for target ' . $target }, + json_encode => 1 + ); + return -1; + } + # if there are here, we use the first pathway (because all pathways had an issue) return (1, 0, $first_target . '~~' . $target, $first_target, $target); } From ca41ef8ca90e260635d27b7060d48ff331426123 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Thu, 22 Jul 2021 14:32:00 +0200 Subject: [PATCH 599/948] enh(audit): add markdown output + fix metric diskio (#140) (MON-10845) --- gorgone/contrib/gorgone_audit.pl | 447 +++++++++++++++++- .../centreon/audit/metrics/system/diskio.pm | 3 +- 2 files changed, 447 insertions(+), 3 deletions(-) diff --git a/gorgone/contrib/gorgone_audit.pl b/gorgone/contrib/gorgone_audit.pl index 81946bf9360..74eb6e5fef5 100644 --- a/gorgone/contrib/gorgone_audit.pl +++ b/gorgone/contrib/gorgone_audit.pl @@ -30,7 +30,8 @@ sub new { bless $self, $class; $self->add_options( - 'url:s' => \$self->{url} + 'url:s' => \$self->{url}, + 'markdown:s' => \$self->{markdown} ); return $self; } @@ -40,6 +41,7 @@ sub init { $self->SUPER::init(); $self->{url} = 'http://127.0.0.1:8085' if (!defined($self->{url}) || $self->{url} eq ''); + $self->{markdown} = 'audit.md' if (defined($self->{markdown}) && $self->{markdown} eq ''); $self->{http} = gorgone::class::http::http->new(logger => $self->{logger}); } @@ -139,7 +141,7 @@ sub get_audit_log { $self->{logger}->writeLogError("audit execution: $data->{message}"); $stop = 1; } elsif ($_->{code} == 2) { - $self->{logger}->writeLogInfo("audit result: " . Data::Dumper::Dumper($data->{audit})); + $self->{audit} = $data->{audit}; $stop = 1; } } @@ -147,6 +149,443 @@ sub get_audit_log { last if ($stop == 1); sleep(10); } + + if (defined($self->{audit})) { + $self->{logger}->writeLogInfo("audit result: " . JSON::XS->new->utf8->encode($self->{audit})); + if (defined($self->{markdown})) { + $self->md_output(); + } + } +} + +sub md_node_system_cpu { + my ($self, %options) = @_; + + return '' if (!defined($options{entry})); + + my $cpu = <<"END_CPU"; + <tr> + <td colspan="2">Cpu</td> + </tr> +END_CPU + + if ($options{entry}->{status_code} != 0) { + my $message = '_**Error:** cannot get informations ' . $options{node}->{status_message}; + $cpu .= <<"END_CPU"; + <tr> + <td colspan="2">$message</td> + </tr> +END_CPU + return $cpu; + } + + my $used = sprintf( + '%s/%s/%s/%s (1m/5m/15m/60m)', + defined($options{entry}->{avg_used_1min}) && $options{entry}->{avg_used_1min} =~ /\d/ ? $options{entry}->{avg_used_1min} . '%' : '-', + defined($options{entry}->{avg_used_5min}) && $options{entry}->{avg_used_5min} =~ /\d/ ? $options{entry}->{avg_used_5min} . '%' : '-', + defined($options{entry}->{avg_used_15min}) && $options{entry}->{avg_used_15min} =~ /\d/ ? $options{entry}->{avg_used_15min} . '%' : '-', + defined($options{entry}->{avg_used_60min}) && $options{entry}->{avg_used_60min} =~ /\d/ ? $options{entry}->{avg_used_60min} . '%' : '-' + ); + my $iowait = sprintf( + '%s/%s/%s/%s (1m/5m/15m/60m)', + defined($options{entry}->{avg_iowait_1min}) && $options{entry}->{avg_iowait_1min} =~ /\d/ ? $options{entry}->{avg_iowait_1min} . '%' : '-', + defined($options{entry}->{avg_iowait_5min}) && $options{entry}->{avg_iowait_5min} =~ /\d/ ? $options{entry}->{avg_iowait_5min} . '%' : '-', + defined($options{entry}->{avg_iowait_15min}) && $options{entry}->{avg_iowait_15min} =~ /\d/ ? $options{entry}->{avg_iowait_15min} . '%' : '-', + defined($options{entry}->{avg_iowait_60min}) && $options{entry}->{avg_iowait_60min} =~ /\d/ ? $options{entry}->{avg_iowait_60min} . '%' : '-' + ); + $cpu .= <<"END_CPU"; + <tr> + <td>number of cores</td> + <td>$options{entry}->{num_cpu}</td> + </tr> + <tr> + <td>used</td> + <td>$used</td> + </tr> + <tr> + <td>iowait</td> + <td>$iowait</td> + </tr> +END_CPU + + return $cpu; +} + +sub md_node_system_load { + my ($self, %options) = @_; + + return '' if (!defined($options{entry})); + + my $load = <<"END_LOAD"; + <tr> + <td colspan="2">Load</td> + </tr> +END_LOAD + + if ($options{entry}->{status_code} != 0) { + my $message = '_**Error:** cannot get informations ' . $options{node}->{status_message}; + $load .= <<"END_LOAD"; + <tr> + <td colspan="2">$message</td> + </tr> +END_LOAD + return $load; + } + + $load .= <<"END_LOAD"; + <tr> + <td>load average</td> + <td>$options{entry}->{load1m}/$options{entry}->{load5m}/$options{entry}->{load15m} (1m/5m/15m)</td> + </tr> +END_LOAD + return $load; +} + +sub md_node_system_memory { + my ($self, %options) = @_; + + return '' if (!defined($options{entry})); + + my $memory = <<"END_MEMORY"; + <tr> + <td colspan="2">Memory</td> + </tr> +END_MEMORY + + if ($options{entry}->{status_code} != 0) { + my $message = '_**Error:** cannot get informations ' . $options{node}->{status_message}; + $memory .= <<"END_MEMORY"; + <tr> + <td colspan="2">$message</td> + </tr> +END_MEMORY + return $memory; + } + + $memory .= <<"END_MEMORY"; + <tr> + <td>memory total</td> + <td>$options{entry}->{ram_total_human}</td> + </tr> + <tr> + <td>memory available</td> + <td>$options{entry}->{ram_available_human}</td> + </tr> + <tr> + <td>swap total</td> + <td>$options{entry}->{swap_total_human}</td> + </tr> + <tr> + <td>swap free</td> + <td>$options{entry}->{swap_free_human}</td> + </tr> +END_MEMORY + return $memory; +} + +sub md_node_system_disk { + my ($self, %options) = @_; + + return '' if (!defined($options{entry})); + + my $disk = "#### Filesystems\n\n"; + if ($options{entry}->{status_code} != 0) { + $disk .= '_**Error:** cannot get informations ' . $options{node}->{status_message} . "\n\n"; + return $disk; + } + + $disk .= <<"END_DISK"; +| Filesystem | Type | Size | Used | Avail | Inodes | Mounted | +| :---------- | :---- | :----- | :--- | :----- | :------ | :------ | +END_DISK + + foreach my $mount (sort keys %{$options{entry}->{partitions}}) { + my $values = $options{entry}->{partitions}->{$mount}; + $disk .= <<"END_DISK"; +| $values->{filesystem} | $values->{type} | $values->{space_size_human} | $values->{space_used_human} | $values->{space_free_human} | $values->{inodes_used_percent} | $values->{mount} | +END_DISK + } + + $disk .= "\n"; + return $disk; +} + +sub md_node_system_diskio { + my ($self, %options) = @_; + + return '' if (!defined($options{entry})); + + my $diskio = "#### Disks I/O\n\n"; + if ($options{entry}->{status_code} != 0) { + $diskio .= '_**Error:** cannot get informations ' . $options{node}->{status_message} . "\n\n"; + return $diskio; + } + + $diskio .= <<"END_DISKIO"; +| Device | Read IOPs | Write IOPs | Read Time | Write Time | +| :---------- | :--------- | :----------- | :-------- | :---------- | +END_DISKIO + + foreach my $dev (sort keys %{$options{entry}->{partitions}}) { + my $values = $options{entry}->{partitions}->{$dev}; + $diskio .= "| $dev | " . + sprintf( + '%s/%s/%s/%s', + defined($values->{read_iops_1min_human}) && $values->{read_iops_1min_human} =~ /\d/ ? $values->{read_iops_1min_human} : '-', + defined($values->{read_iops_5min_human}) && $values->{read_iops_5min_human} =~ /\d/ ? $values->{read_iops_5min_human} : '-', + defined($values->{read_iops_15min_human}) && $values->{read_iops_15min_human} =~ /\d/ ? $values->{read_iops_15min_human} : '-', + defined($values->{read_iops_60min_human}) && $values->{read_iops_60min_human} =~ /\d/ ? $values->{read_iops_60min_human} : '-', + ) . '| ' . + sprintf( + '%s/%s/%s/%s', + defined($values->{write_iops_1min_human}) && $values->{write_iops_1min_human} =~ /\d/ ? $values->{write_iops_1min_human} : '-', + defined($values->{write_iops_5min_human}) && $values->{write_iops_5min_human} =~ /\d/ ? $values->{write_iops_5min_human} : '-', + defined($values->{write_iops_15min_human}) && $values->{write_iops_15min_human} =~ /\d/ ? $values->{write_iops_15min_human} : '-', + defined($values->{write_iops_60min_human}) && $values->{write_iops_60min_human} =~ /\d/ ? $values->{write_iops_60min_human} : '-', + ) . '| ' . + sprintf( + '%s/%s/%s/%s', + defined($values->{read_time_1min_ms}) && $values->{read_time_1min_ms} =~ /\d/ ? $values->{read_time_1min_ms} . 'ms' : '-', + defined($values->{read_time_5min_ms}) && $values->{read_time_5min_ms} =~ /\d/ ? $values->{read_time_5min_ms} . 'ms' : '-', + defined($values->{read_time_15min_ms}) && $values->{read_time_15min_ms} =~ /\d/ ? $values->{read_time_15min_ms} . 'ms' : '-', + defined($values->{read_time_60min_ms}) && $values->{read_time_60min_ms} =~ /\d/ ? $values->{read_time_60min_ms} . 'ms' : '-' + ) . '| ' . + sprintf( + '%s/%s/%s/%s', + defined($values->{write_time_1min_ms}) && $values->{write_time_1min_ms} =~ /\d/ ? $values->{write_time_1min_ms} . 'ms' : '-', + defined($values->{write_time_5min_ms}) && $values->{write_time_5min_ms} =~ /\d/ ? $values->{write_time_5min_ms} . 'ms' : '-', + defined($values->{write_time_15min_ms}) && $values->{write_time_15min_ms} =~ /\d/ ? $values->{write_time_15min_ms} . 'ms' : '-', + defined($values->{write_time_60min_ms}) && $values->{write_time_60min_ms} =~ /\d/ ? $values->{write_time_60min_ms} . 'ms' : '-' + ) . "|\n"; + } + + $diskio .= "\n"; + return $diskio; +} + +sub md_node_centreon_packages { + my ($self, %options) = @_; + + return '' if (!defined($options{entry})); + + my $packages = "#### Packages\n\n"; + if ($options{entry}->{status_code} != 0) { + $packages .= '_**Error:** cannot get informations ' . $options{node}->{status_message} . "\n\n"; + return $packages; + } + + $packages .= <<"END_PACKAGES"; +| Name | Version | +| :---- | :---- | +END_PACKAGES + + foreach my $entry (sort { $a->[0] cmp $b->[0] } @{$options{entry}->{list}}) { + $packages .= <<"END_PACKAGES"; +| $entry->[0] | $entry->[1] | +END_PACKAGES + } + + $packages .= "\n"; + return $packages; +} + +sub md_node_centreon_realtime { + my ($self, %options) = @_; + + return '' if (!defined($options{entry})); + + my $realtime = "#### Realtime\n\n"; + if ($options{entry}->{status_code} != 0) { + $realtime .= '_**Error:** cannot get informations ' . $options{node}->{status_message} . "\n\n"; + return $realtime; + } + + $realtime .= <<"END_REALTIME"; +number of hosts: $options{entry}->{hosts_count} \\ +number of services: $options{entry}->{services_count} \\ +number of hostgroups: $options{entry}->{hostgroups_count} \\ +number of servicegroups: $options{entry}->{servicegroups_count} \\ +number of acl: $options{entry}->{acl_count} + +END_REALTIME + + return $realtime; +} + +sub md_node_centreon_rrd { + my ($self, %options) = @_; + + return '' if (!defined($options{entry})); + + my $rrd = "#### Rrd\n\n"; + if ($options{entry}->{status_code} != 0) { + $rrd .= '_**Error:** cannot get informations ' . $options{node}->{status_message} . "\n\n"; + return $rrd; + } + + $rrd .= <<"END_RRD"; +number of metrics rrd: $options{entry}->{rrd_metrics_count} \\ +number of metrics rrd outdated: $options{entry}->{rrd_metrics_outdated} \\ +size of metrics rrd: $options{entry}->{rrd_metrics_human} \\ +number of status rrd: $options{entry}->{rrd_status_count} \\ +number of status rrd outdated: $options{entry}->{rrd_status_outdated} \\ +size of metrics rrd: $options{entry}->{rrd_status_human} + +END_RRD + + return $rrd; +} + +sub md_node_centreon_database { + my ($self, %options) = @_; + + return '' if (!defined($options{entry})); + + my $db = "#### Database\n\n"; + if ($options{entry}->{status_code} != 0) { + $db .= '_**Error:** cannot get informations ' . $options{node}->{status_message} . "\n\n"; + return $db; + } + + $db .= <<"END_DATABASE"; +Total databases space used: $options{entry}->{space_used_human} \\ +Total databases space free: $options{entry}->{space_free_human} + +END_DATABASE + + $db .= <<"END_DATABASE"; +| Database | Used | Free | +| :-------- | :--- | :--- | +END_DATABASE + + foreach my $dbname (sort keys %{$options{entry}->{databases}}) { + $db .= sprintf( + '| %s | %s | %s |' . "\n", + $dbname, + $options{entry}->{databases}->{$dbname}->{space_used_human}, + $options{entry}->{databases}->{$dbname}->{space_free_human} + ); + } + + $db .= <<"END_DATABASE"; + +| Table | Engine | Used | Free | Frag | +| :-------- | :----- | :--- | :--- | :--- | +END_DATABASE + + foreach my $dbname (sort keys %{$options{entry}->{databases}}) { + foreach my $table (sort keys %{$options{entry}->{databases}->{$dbname}->{tables}}) { + $db .= sprintf( + '| %s | %s | %s | %s | %.2f%% |' . "\n", + $dbname . '.' . $table, + $options{entry}->{databases}->{$dbname}->{tables}->{$table}->{engine}, + $options{entry}->{databases}->{$dbname}->{tables}->{$table}->{space_used_human}, + $options{entry}->{databases}->{$dbname}->{tables}->{$table}->{space_free_human}, + $options{entry}->{databases}->{$dbname}->{tables}->{$table}->{frag} + ); + } + } + + $db .= "\n"; + return $db; +} + +sub md_node_centreon_pluginpacks { + my ($self, %options) = @_; + + return '' if (!defined($options{entry})); + + my $pp = "#### Plugin-Packs\n\n"; + if ($options{entry}->{status_code} != 0) { + $pp .= '_**Error:** cannot get informations ' . $options{node}->{status_message} . "\n\n"; + return $pp; + } + + $pp .= <<"END_PP"; +| Pack installed | Version | +| :-------------- | :------ | +END_PP + + foreach my $entry (sort { $a->{slug} cmp $b->{slug} } @{$options{entry}->{installed}}) { + $pp .= <<"END_PP"; +| $entry->{slug} | $entry->{version} | +END_PP + } + + $pp .= "\n"; + return $pp; +} + +sub md_node_system { + my ($self, %options) = @_; + + my $os = defined($options{node}->{metrics}->{'system::os'}) ? $options{node}->{metrics}->{'system::os'}->{os}->{value} : '-'; + my $kernel = defined($options{node}->{metrics}->{'system::os'}) ? $options{node}->{metrics}->{'system::os'}->{kernel}->{value} : '-'; + + my $cpu = $self->md_node_system_cpu(entry => $options{node}->{metrics}->{'system::cpu'}); + my $load = $self->md_node_system_load(entry => $options{node}->{metrics}->{'system::load'}); + my $memory = $self->md_node_system_memory(entry => $options{node}->{metrics}->{'system::memory'}); + my $disks = $self->md_node_system_disk(entry => $options{node}->{metrics}->{'system::disk'}); + my $disks_io = $self->md_node_system_diskio(entry => $options{node}->{metrics}->{'system::diskio'}); + + $self->{md_content} .= "### System + +#### Overall + +os: $os \\ +kernel: $kernel + +<table> +${cpu}${load}${memory} +</table> + +${disks}${disks_io}"; + +} + +sub md_node_centreon { + my ($self, %options) = @_; + + my $realtime = $self->md_node_centreon_realtime(entry => $options{node}->{metrics}->{'centreon::realtime'}); + my $rrd = $self->md_node_centreon_rrd(entry => $options{node}->{metrics}->{'centreon::rrd'}); + my $database = $self->md_node_centreon_database(entry => $options{node}->{metrics}->{'centreon::database'}); + my $packages = $self->md_node_centreon_packages(entry => $options{node}->{metrics}->{'centreon::packages'}); + my $pp = $self->md_node_centreon_pluginpacks(entry => $options{node}->{metrics}->{'centreon::pluginpacks'}); + + $self->{md_content} .= "### Centreon + +${realtime}${rrd}${database}${packages}${pp}"; + +} + +sub md_node { + my ($self, %options) = @_; + + $self->{md_content} .= "## " . $options{node}->{name} . "\n\n"; + if ($options{node}->{status_code} != 0) { + $self->{md_content} .= '_**Error:** cannot get informations ' . $options{node}->{status_message} . "\n\n"; + return ; + } + + $self->md_node_system(%options); + $self->md_node_centreon(%options); +} + +sub md_output { + my ($self) = @_; + + if (!open(FH, '>', $self->{markdown})) { + $self->{logger}->writeLogError("cannot open file '" . $self->{markdown} . "': $!"); + exit(1); + } + $self->{md_content} = "# Audit\n\n"; + + foreach my $node_id (sort { $self->{audit}->{nodes}->{$a}->{name} cmp $self->{audit}->{nodes}->{$b}->{name} } keys %{$self->{audit}->{nodes}}) { + $self->md_node(node => $self->{audit}->{nodes}->{$node_id}); + } + + print FH $self->{md_content}; + close FH; } sub run { @@ -175,6 +614,10 @@ =head1 OPTIONS Specify the api url (default: 'http://127.0.0.1:8085'). +=item B<--markdown> + +Markdown output format (default: 'audit.md'). + =item B<--severity> Set the script log severity (default: 'info'). diff --git a/gorgone/gorgone/modules/centreon/audit/metrics/system/diskio.pm b/gorgone/gorgone/modules/centreon/audit/metrics/system/diskio.pm index 31df14c6e7b..387d41dea6a 100644 --- a/gorgone/gorgone/modules/centreon/audit/metrics/system/diskio.pm +++ b/gorgone/gorgone/modules/centreon/audit/metrics/system/diskio.pm @@ -52,6 +52,7 @@ sub metrics { / ($options{sampling}->{diskio}->{partitions}->{$partname}->[0]->[0] - $options{sampling}->{diskio}->{partitions}->{$partname}->[ $_->[0] ]->[0]) ); $metrics->{partitions}->{$partname}->{ 'read_iops_' . $_->[1] . '_human' } = join('', gorgone::standard::misc::scale(value => $metrics->{partitions}->{$partname}->{ 'read_iops_' . $_->[1] . '_bytes' }, format => '%.2f')); + $metrics->{partitions}->{$partname}->{ 'write_iops_' . $_->[1] . '_bytes' } = sprintf( '%.2f', ($options{sampling}->{diskio}->{partitions}->{$partname}->[0]->[2] - $options{sampling}->{diskio}->{partitions}->{$partname}->[ $_->[0] ]->[2]) @@ -62,7 +63,7 @@ sub metrics { $metrics->{partitions}->{$partname}->{ 'read_time_' . $_->[1] . '_ms' } = sprintf( '%s', ($options{sampling}->{diskio}->{partitions}->{$partname}->[0]->[3] - $options{sampling}->{diskio}->{partitions}->{$partname}->[ $_->[0] ]->[3]) ); - $metrics->{partitions}->{$partname}->{ 'read_time_' . $_->[1] . '_ms' } = sprintf( + $metrics->{partitions}->{$partname}->{ 'write_time_' . $_->[1] . '_ms' } = sprintf( '%s', ($options{sampling}->{diskio}->{partitions}->{$partname}->[0]->[4] - $options{sampling}->{diskio}->{partitions}->{$partname}->[ $_->[0] ]->[4]) ); } From 114730649721ac7a83598b673ca4272ef040152c Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Thu, 22 Jul 2021 14:46:00 +0200 Subject: [PATCH 600/948] enh(deps): use only YAML::XS library (#141) --- gorgone/gorgone/standard/library.pm | 6 ++++-- gorgone/packaging/centreon-gorgone.spectemplate | 1 - 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index cfb56b4f68c..a4306e0b724 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -31,11 +31,13 @@ use Crypt::PK::RSA; use Crypt::PRNG; use Crypt::CBC; use Data::Dumper; -use YAML 'LoadFile'; use File::Path; use File::Basename; use MIME::Base64; use Time::HiRes; +use YAML::XS; +$YAML::XS::Boolean = 'JSON::PP'; +$YAML::XS::LoadBlessed = 1; our $listener; my %zmq_type = ('ZMQ_ROUTER' => ZMQ_ROUTER, 'ZMQ_DEALER' => ZMQ_DEALER); @@ -49,7 +51,7 @@ sub read_config { my $config; eval { - $config = LoadFile($options{config_file}); + $config = YAML::XS::LoadFile($options{config_file}); }; if ($@) { $options{logger}->writeLogError("[core] Parsing config file error:"); diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index b0137618ede..748924c7398 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -20,7 +20,6 @@ Requires: perl(JSON::XS) Requires: perl(JSON::PP) Requires: perl(XML::Simple) Requires: perl(Net::SMTP) -Requires: perl(YAML) Requires: perl(YAML::XS) Requires: perl(DBD::SQLite) Requires: perl(DBD::mysql) From 9b3835be79e6422f9150e9d079e521fcbe872fee Mon Sep 17 00:00:00 2001 From: Laurent Calvet <lcalvet@centreon.com> Date: Mon, 6 Sep 2021 09:17:02 +0200 Subject: [PATCH 601/948] enh(conf): replace the API version 'beta' by 'latest' (#146) --- gorgone/install/src/centreon-api.yaml | 2 +- gorgone/packaging/centreon-api.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/install/src/centreon-api.yaml b/gorgone/install/src/centreon-api.yaml index d52e6bd182a..4d335ee5d19 100644 --- a/gorgone/install/src/centreon-api.yaml +++ b/gorgone/install/src/centreon-api.yaml @@ -1,7 +1,7 @@ gorgone: tpapi: - name: centreonv2 - base_url: "http://127.0.0.1/centreon/api/beta/" + base_url: "http://127.0.0.1/centreon/api/latest/" username: admin password: centreon - name: clapi diff --git a/gorgone/packaging/centreon-api.yaml b/gorgone/packaging/centreon-api.yaml index d52e6bd182a..4d335ee5d19 100644 --- a/gorgone/packaging/centreon-api.yaml +++ b/gorgone/packaging/centreon-api.yaml @@ -1,7 +1,7 @@ gorgone: tpapi: - name: centreonv2 - base_url: "http://127.0.0.1/centreon/api/beta/" + base_url: "http://127.0.0.1/centreon/api/latest/" username: admin password: centreon - name: clapi From 642c05e246e4ff371f6fa2cca074699c400917d1 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Thu, 9 Sep 2021 13:22:19 +0200 Subject: [PATCH 602/948] fix(pull): uninitialized value (#147) --- gorgone/gorgone/modules/core/pull/hooks.pm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gorgone/gorgone/modules/core/pull/hooks.pm b/gorgone/gorgone/modules/core/pull/hooks.pm index 389544f0944..45c1ebf4ff0 100644 --- a/gorgone/gorgone/modules/core/pull/hooks.pm +++ b/gorgone/gorgone/modules/core/pull/hooks.pm @@ -146,6 +146,8 @@ sub broadcast {} sub transmit_back { my (%options) = @_; + return undef if (!defined($options{message})); + if ($options{message} =~ /^\[ACK\]\s+\[(.*?)\]\s+(.*)/m) { my $data; eval { From 508e20ec98c080c2708b25ea9e70043bc86eb165 Mon Sep 17 00:00:00 2001 From: Zakaria Guennoune <83596451+zguennoune02@users.noreply.github.com> Date: Thu, 16 Sep 2021 14:43:39 +0200 Subject: [PATCH 603/948] new workflow (#148) --- gorgone/Jenkinsfile | 98 +++++++++++++++++++++++++++------------------ 1 file changed, 58 insertions(+), 40 deletions(-) diff --git a/gorgone/Jenkinsfile b/gorgone/Jenkinsfile index a74bfd30325..187b82b50d1 100644 --- a/gorgone/Jenkinsfile +++ b/gorgone/Jenkinsfile @@ -1,23 +1,59 @@ -import groovy.json.JsonSlurper /* ** Variables. */ def serie = '21.10' def maintenanceBranch = "${serie}.x" +def qaBranch = "dev-${serie}.x" +env.REF_BRANCH = 'master' +env.PROJECT='centreon-gorgone' if (env.BRANCH_NAME.startsWith('release-')) { env.BUILD = 'RELEASE' -} else if ((env.BRANCH_NAME == 'master') || (env.BRANCH_NAME == maintenanceBranch)) { +} else if ((env.BRANCH_NAME == env.REF_BRANCH) || (env.BRANCH_NAME == maintenanceBranch)) { env.BUILD = 'REFERENCE' +} else if ((env.BRANCH_NAME == 'develop') || (env.BRANCH_NAME == qaBranch)) { + env.BUILD = 'QA' } else { env.BUILD = 'CI' } +def buildBranch = env.BRANCH_NAME +if (env.CHANGE_BRANCH) { + buildBranch = env.CHANGE_BRANCH +} + +/* +** Functions +*/ +def isStableBuild() { + return ((env.BUILD == 'REFERENCE') || (env.BUILD == 'QA')) +} + +def checkoutCentreonBuild(buildBranch) { + def getCentreonBuildGitConfiguration = { branchName -> [ + $class: 'GitSCM', + branches: [[name: "refs/heads/${branchName}"]], + doGenerateSubmoduleConfigurations: false, + userRemoteConfigs: [[ + $class: 'UserRemoteConfig', + url: "ssh://git@github.com/centreon/centreon-build.git" + ]] + ]} + + dir('centreon-build') { + try { + checkout(getCentreonBuildGitConfiguration(buildBranch)) + } catch(e) { + echo "branch '${buildBranch}' does not exist in centreon-build, then fallback to master" + checkout(getCentreonBuildGitConfiguration('master')) + } + } +} /* ** Pipeline code. */ -stage('Sonar analysis') { +stage('Deliver sources // Sonar analysis') { node { - sh 'setup_centreon_build.sh' + checkoutCentreonBuild(buildBranch) dir('centreon-gorgone') { checkout scm } @@ -25,54 +61,34 @@ stage('Sonar analysis') { source = readProperties file: 'source.properties' env.VERSION = "${source.VERSION}" env.RELEASE = "${source.RELEASE}" - // Run sonarQube analysis withSonarQubeEnv('SonarQubeDev') { sh "./centreon-build/jobs/gorgone/${serie}/gorgone-analysis.sh" } - def reportFilePath = "target/sonar/report-task.txt" - def reportTaskFileExists = fileExists "${reportFilePath}" - if (reportTaskFileExists) { - echo "Found report task file" - def taskProps = readProperties file: "${reportFilePath}" - echo "taskId[${taskProps['ceTaskId']}]" - timeout(time: 10, unit: 'MINUTES') { - while (true) { - sleep 5 - def taskStatusResult = - sh(returnStdout: true, - script: "curl -s -X GET -u ${authString} \'${sonarProps['sonar.host.url']}/api/ce/task?id=${taskProps['ceTaskId']}\'") - echo "taskStatusResult[${taskStatusResult}]" - def taskStatus = new JsonSlurper().parseText(taskStatusResult).task.status - echo "taskStatus[${taskStatus}]" - // Status can be SUCCESS, ERROR, PENDING, or IN_PROGRESS. The last two indicate it's - // not done yet. - if (taskStatus != "IN_PROGRESS" && taskStatus != "PENDING") { - break; - } - def qualityGate = waitForQualityGate() - if (qualityGate.status != 'OK') { - currentBuild.result = 'FAIL' - } - } - } - } + def qualityGate = waitForQualityGate() + if (qualityGate.status != 'OK') { + currentBuild.result = 'FAIL' + } } -} +} try { - stage('Package') { - parallel 'centos7': { + stage('RPM Packaging') { + parallel 'Packaging centos7': { node { - sh 'setup_centreon_build.sh' + checkoutCentreonBuild(buildBranch) sh "./centreon-build/jobs/gorgone/${serie}/gorgone-package.sh centos7" archiveArtifacts artifacts: 'rpms-centos7.tar.gz' + stash name: "rpms-centos7", includes: 'output/noarch/*.rpm' + sh 'rm -rf output' } }, - 'centos8': { + 'Packaging centos8': { node { - sh 'setup_centreon_build.sh' + checkoutCentreonBuild(buildBranch) sh "./centreon-build/jobs/gorgone/${serie}/gorgone-package.sh centos8" archiveArtifacts artifacts: 'rpms-centos8.tar.gz' + stash name: "rpms-centos8", includes: 'output/noarch/*.rpm' + sh 'rm -rf output' } } if ((currentBuild.result ?: 'SUCCESS') != 'SUCCESS') { @@ -80,10 +96,12 @@ try { } } - if ((env.BUILD == 'RELEASE') || (env.BUILD == 'REFERENCE')) { + if ((env.BUILD == 'RELEASE') || (env.BUILD == 'QA') || (env.BUILD == 'CI')) { stage('Delivery') { node { - sh 'setup_centreon_build.sh' + checkoutCentreonBuild(buildBranch) + unstash 'rpms-centos8' + unstash 'rpms-centos7' sh "./centreon-build/jobs/gorgone/${serie}/gorgone-delivery.sh" } if ((currentBuild.result ?: 'SUCCESS') != 'SUCCESS') { From 628c82caecafa3c8bf604d04924f7bcce0de396d Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Wed, 22 Sep 2021 13:48:53 +0200 Subject: [PATCH 604/948] fix(action): use Archive::Tar module instead of tar command (#151) --- gorgone/gorgone/modules/core/action/class.pm | 29 +++++++------------ .../packaging/centreon-gorgone.spectemplate | 1 + 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index a6bd555d4c6..b9a2e70b128 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -35,8 +35,11 @@ use File::Copy; use File::Path qw(make_path); use MIME::Base64; use Digest::MD5::File qw(file_md5_hex); +use Archive::Tar; use Fcntl; +$Archive::Tar::SAME_PERMISSIONS = 1; +$Archive::Tar::WARN = 0; $Digest::MD5::File::NOFATALS = 1; my %handlers = (TERM => {}, HUP => {}); my ($connector); @@ -329,30 +332,18 @@ sub action_processcopy { if (! -d $options{data}->{content}->{destination}) { make_path($options{data}->{content}->{destination}); } - my ($error, $stdout, $exit_code) = gorgone::standard::misc::backtick( - command => "tar --no-overwrite-dir -zxf $cache_file -C '" . $options{data}->{content}->{destination} . "' .", - timeout => (defined($options{timeout})) ? $options{timeout} : 10, - wait_exit => 1, - redirect_stderr => 1, - ); - if ($error <= -1000) { - $self->send_log( - code => GORGONE_ACTION_FINISH_KO, - token => $options{token}, - logging => $options{data}->{logging}, - data => { message => "untar failed: $stdout" } - ); - $self->{logger}->writeLogError('[action] Copy processing - Untar failed: ' . $stdout); - return -1; - } - if ($exit_code != 0) { + + my $tar = Archive::Tar->new(); + $tar->setcwd($options{data}->{content}->{destination}); + unless ($tar->read($cache_file, undef, { extract => 1 })) { + my $tar_error = $tar->error(); $self->send_log( code => GORGONE_ACTION_FINISH_KO, token => $options{token}, logging => $options{data}->{logging}, - data => { message => "untar failed ($exit_code): $stdout" } + data => { message => "untar failed: $tar_error" } ); - $self->{logger}->writeLogError('[action] Copy processing - Untar failed: ' . $stdout); + $self->{logger}->writeLogError("[action] Copy processing - Untar failed: $tar_error"); return -1; } } elsif ($options{data}->{content}->{type} eq 'regular') { diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 748924c7398..ee6c4f8f48d 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -12,6 +12,7 @@ BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) Requires: bzip2 Requires: perl-Libssh-Session >= 0.8 Requires: perl-CryptX +Requires: perl(Archive::Tar) Requires: perl(Schedule::Cron) Requires: perl(ZMQ::LibZMQ4) Requires: perl(ZMQ::Constants) From 17200f58f9bf538c59821ba33364c5a163487e29 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Thu, 23 Sep 2021 14:04:37 +0200 Subject: [PATCH 605/948] fix(proxy): no event passthrough after a ssh reconnection (#152) --- gorgone/gorgone/modules/core/proxy/class.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/gorgone/gorgone/modules/core/proxy/class.pm b/gorgone/gorgone/modules/core/proxy/class.pm index 9b47118a280..523fc4daed2 100644 --- a/gorgone/gorgone/modules/core/proxy/class.pm +++ b/gorgone/gorgone/modules/core/proxy/class.pm @@ -304,6 +304,7 @@ sub proxy_ssh { if ($self->{clients}->{ $options{target_client} }->{class}->ping() == -1) { $self->{clients}->{ $options{target_client} }->{delete} = 1; } else { + $self->{clients}->{ $options{target_client} }->{com_read_internal} = 1; $self->send_internal_action( action => 'PONG', data => { data => { id => $options{target_client} } }, From a3eac079b33db117190b77f9f18b8e5d4ac1fbd7 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Thu, 23 Sep 2021 17:05:02 +0200 Subject: [PATCH 606/948] fix(centreon): centreonv2 class missing module load (#153) --- gorgone/gorgone/class/tpapi/centreonv2.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/gorgone/gorgone/class/tpapi/centreonv2.pm b/gorgone/gorgone/class/tpapi/centreonv2.pm index 9051bb0cdca..c681f873074 100644 --- a/gorgone/gorgone/class/tpapi/centreonv2.pm +++ b/gorgone/gorgone/class/tpapi/centreonv2.pm @@ -22,6 +22,7 @@ package gorgone::class::tpapi::centreonv2; use strict; use warnings; +use gorgone::class::http::http; use JSON::XS; sub new { From b56e24a314eb5120e76da8a172ae801f02842f26 Mon Sep 17 00:00:00 2001 From: sc979 <34628915+sc979@users.noreply.github.com> Date: Tue, 28 Sep 2021 18:01:28 +0200 Subject: [PATCH 607/948] enh(chore): automate dependabot ticket creation (#156) --- gorgone/.github/workflows/dependabot_jira.yml | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 gorgone/.github/workflows/dependabot_jira.yml diff --git a/gorgone/.github/workflows/dependabot_jira.yml b/gorgone/.github/workflows/dependabot_jira.yml new file mode 100644 index 00000000000..eef0974b475 --- /dev/null +++ b/gorgone/.github/workflows/dependabot_jira.yml @@ -0,0 +1,54 @@ +name: Create Dependabot Ticket on Jira + +on: + pull_request: + types: [ opened ] + branches: [ develop, dev-2* ] + +env: + JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} + JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} + JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} + JIRA_PROJECT_KEY: "MON" + JIRA_ISSUE_TYPE: "Vulnerability" + +jobs: + create_ticket: + name: Create Jira ticket on dependaBot PR + if: github.event.pull_request.user.id == 49699333 + runs-on: ubuntu-latest + steps: + - name: Get current date + id: date + run: echo "CURRENT_DATE=$(date +'%Y-%m-%dT%H:%M:%S')" >> $GITHUB_ENV + + - name: Login to Jira + uses: atlassian/gajira-login@v2.0.0 + env: + JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} + JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} + JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} + + - name: Create Jira Issue + id: create + uses: atlassian/gajira-create@v2.0.1 + with: + project: ${{ env.JIRA_PROJECT_KEY }} + issuetype: ${{ env.JIRA_ISSUE_TYPE }} + summary: | + [Dependency to upgrade on : centreon/${{ github.event.repository.name }}] - ${{ github.event.pull_request.title }} + description: | + + {panel:title=Dependency to upgrade} + ${{ github.event.pull_request.title }} + {panel} + + More details are available in the *PR n°${{ github.event.pull_request.number }}* + + The link is: ${{ github.event.pull_request.html_url }} + fields: + '{ + "customfield_10880": "Internal", + "customfield_10881": "dependabot", + "customfield_10866": "${{ env.CURRENT_DATE }}" + }' From ae32778432f75a715909d0f04ff9f2344bcf5c54 Mon Sep 17 00:00:00 2001 From: sc979 <34628915+sc979@users.noreply.github.com> Date: Wed, 29 Sep 2021 11:46:20 +0200 Subject: [PATCH 608/948] fix(chore): old dependabot's PR ticket creation (#157) --- gorgone/.github/workflows/dependabot_jira.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/.github/workflows/dependabot_jira.yml b/gorgone/.github/workflows/dependabot_jira.yml index eef0974b475..fe5153f3d82 100644 --- a/gorgone/.github/workflows/dependabot_jira.yml +++ b/gorgone/.github/workflows/dependabot_jira.yml @@ -2,7 +2,7 @@ name: Create Dependabot Ticket on Jira on: pull_request: - types: [ opened ] + types: [ opened, reopened ] branches: [ develop, dev-2* ] env: From 8c6203f3b11e3c65fd61269d619c76966a7c5c21 Mon Sep 17 00:00:00 2001 From: sc979 <34628915+sc979@users.noreply.github.com> Date: Mon, 18 Oct 2021 20:05:06 +0200 Subject: [PATCH 609/948] enh(chore): issueType, feature_team and issue description (#160) --- gorgone/.github/workflows/dependabot_jira.yml | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/gorgone/.github/workflows/dependabot_jira.yml b/gorgone/.github/workflows/dependabot_jira.yml index fe5153f3d82..55eef4b4e3d 100644 --- a/gorgone/.github/workflows/dependabot_jira.yml +++ b/gorgone/.github/workflows/dependabot_jira.yml @@ -3,14 +3,14 @@ name: Create Dependabot Ticket on Jira on: pull_request: types: [ opened, reopened ] - branches: [ develop, dev-2* ] + branches: [ develop ] env: JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} JIRA_PROJECT_KEY: "MON" - JIRA_ISSUE_TYPE: "Vulnerability" + JIRA_ISSUE_TYPE: "Technical" jobs: create_ticket: @@ -36,19 +36,30 @@ jobs: project: ${{ env.JIRA_PROJECT_KEY }} issuetype: ${{ env.JIRA_ISSUE_TYPE }} summary: | - [Dependency to upgrade on : centreon/${{ github.event.repository.name }}] - ${{ github.event.pull_request.title }} + [${{ github.event.repository.name }}] - ${{ github.event.pull_request.title }} description: | - {panel:title=Dependency to upgrade} - ${{ github.event.pull_request.title }} + {panel:title=Recommandation} + ${{ github.event.pull_request.title }} {panel} More details are available in the *PR n°${{ github.event.pull_request.number }}* - The link is: ${{ github.event.pull_request.html_url }} + Github link is: ${{ github.event.pull_request.html_url }} + + {panel:title=CVSS details} + More details are available on snyk + {panel} + + *Github Advisory* + + fields: '{ "customfield_10880": "Internal", - "customfield_10881": "dependabot", - "customfield_10866": "${{ env.CURRENT_DATE }}" + "customfield_10881": "Dependabot", + "customfield_10866": "${{ env.CURRENT_DATE }}", + "labels": ["Dependabot"], + "priority": {"name": "Highest"}, + "components":[{"name": "centreon-gorgone"}] }' From 007ab6ed6c59c39b2a213d9bac4593cc7b6ae0ec Mon Sep 17 00:00:00 2001 From: ponchoh <hmorales@centreon.com> Date: Thu, 4 Nov 2021 09:04:14 -0400 Subject: [PATCH 610/948] fix(doc): update link (#161) --- gorgone/docs/modules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/docs/modules.md b/gorgone/docs/modules.md index 56ee20e2ca3..fb1f1ee940a 100644 --- a/gorgone/docs/modules.md +++ b/gorgone/docs/modules.md @@ -12,7 +12,7 @@ List of the available modules: * [Register](../docs/modules/core/register.md) * Centreon * [Autodiscovery](../docs/modules/centreon/autodiscovery.md) - * [Broker](../docs/modules/centreon/broker.md) + * [Broker](../docs/modules/centreon/statistics.md) * [Engine](../docs/modules/centreon/engine.md) * [Legacy Cmd](../docs/modules/centreon/legacycmd.md) * [Nodes](../docs/modules/centreon/nodes.md) From 94a540596ec93443b078cc184ce17a33aa924a02 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Tue, 9 Nov 2021 10:00:08 +0100 Subject: [PATCH 611/948] fix(proxy): close zmq internal connections (#163) --- gorgone/gorgone/modules/core/proxy/class.pm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gorgone/gorgone/modules/core/proxy/class.pm b/gorgone/gorgone/modules/core/proxy/class.pm index 523fc4daed2..1adfe9c2807 100644 --- a/gorgone/gorgone/modules/core/proxy/class.pm +++ b/gorgone/gorgone/modules/core/proxy/class.pm @@ -86,6 +86,11 @@ sub exit_process { $self->{logger}->writeLogInfo("[proxy] $$ has quit"); $self->close_connections(); + foreach (keys %{$self->{internal_channels}}) { + $self->{logger}->writeLogInfo("[proxy] Close internal connection for $_"); + zmq_close($self->{internal_channels}->{$_}); + } + $self->{logger}->writeLogInfo("[proxy] Close control connection"); zmq_close($self->{internal_socket}); exit(0); } From 95604926bfc9492484e23182f491bfa81375a1ab Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Tue, 9 Nov 2021 15:53:50 +0100 Subject: [PATCH 612/948] fix(proxy): uninitialized value in modulus log (#164) MON-11593 --- gorgone/gorgone/modules/core/proxy/class.pm | 4 ++-- gorgone/gorgone/modules/core/proxy/hooks.pm | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gorgone/gorgone/modules/core/proxy/class.pm b/gorgone/gorgone/modules/core/proxy/class.pm index 1adfe9c2807..44ce8e234da 100644 --- a/gorgone/gorgone/modules/core/proxy/class.pm +++ b/gorgone/gorgone/modules/core/proxy/class.pm @@ -547,12 +547,12 @@ sub run { # we try to do all we can my $rv = scalar(zmq_poll($polls, 5000)); - + # Sometimes (with big message) we have a undef ??!!! if ($rv == -1) { $self->{logger}->writeLogError("[proxy] zmq_poll failed: $!"); } - + if ($rv == 0 && $self->{stop} == 1) { $self->exit_process(); } diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index 1395dbaa2d6..991a1bf0e2f 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -401,7 +401,7 @@ sub check { $constatus_ping->{$_}->{in_progress_ping} = 0; $constatus_ping->{$_}->{ping_timeout}++; $constatus_ping->{$_}->{ping_failed}++; - if (($synctime_nodes->{$_}->{ping_timeout} % $config->{pong_max_timeout}) == 0) { + if (($constatus_ping->{$_}->{ping_timeout} % $config->{pong_max_timeout}) == 0) { $options{logger}->writeLogInfo("[proxy] Ping max timeout reached from '" . $_ . "'"); routing( socket => $internal_socket, From 12bff0849f2aead659fa8d4736502bcc80ebae16 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Wed, 10 Nov 2021 14:36:26 +0100 Subject: [PATCH 613/948] security(core): rsa keys no more world readable (#165) --- gorgone/gorgone/class/core.pm | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 907b2f99970..aeb08f040af 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -109,6 +109,15 @@ sub init_server_keys { $self->{logger}->writeLogInfo("[core] Public key file '$self->{config}->{configuration}->{gorgone}->{gorgonecore}->{pubkey}' written"); } + my $rv = chmod(0600, $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{privkey}); + if ($rv == 0) { + $self->{logger}->writeLogInfo("[core] chmod private key file '$self->{config}->{configuration}->{gorgone}->{gorgonecore}->{privkey}': $!"); + } + $rv = chmod(0640, $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{pubkey}); + if ($rv == 0) { + $self->{logger}->writeLogInfo("[core] chmod public key file '$self->{config}->{configuration}->{gorgone}->{gorgonecore}->{pubkey}': $!"); + } + ($code, $self->{server_privkey}) = gorgone::standard::library::loadprivkey( logger => $self->{logger}, privkey => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{privkey}, From 7519a4a065a760331746c5bd290f38aa5504678d Mon Sep 17 00:00:00 2001 From: Zakaria Guennoune <83596451+zguennoune02@users.noreply.github.com> Date: Mon, 15 Nov 2021 11:06:02 +0100 Subject: [PATCH 614/948] update to 22.04 --- gorgone/Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/Jenkinsfile b/gorgone/Jenkinsfile index 187b82b50d1..dc26ff7ce7f 100644 --- a/gorgone/Jenkinsfile +++ b/gorgone/Jenkinsfile @@ -1,7 +1,7 @@ /* ** Variables. */ -def serie = '21.10' +def serie = '22.04' def maintenanceBranch = "${serie}.x" def qaBranch = "dev-${serie}.x" env.REF_BRANCH = 'master' From 46843c3d1fceb14c8678550c1d0745dc5c91902f Mon Sep 17 00:00:00 2001 From: Zakaria Guennoune <83596451+zguennoune02@users.noreply.github.com> Date: Mon, 15 Nov 2021 11:16:01 +0100 Subject: [PATCH 615/948] update to 22.04 --- gorgone/packaging/centreon-gorgone.spectemplate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index ee6c4f8f48d..88c477f6709 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -1,5 +1,5 @@ Name: centreon-gorgone -Version: 21.10.0 +Version: 22.04.0 Release: @RELEASE@%{?dist} Summary: Perl daemon task handlers Group: Applications/System From 4cc76807f06873e994f463f1a5c6d57c13436772 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Tue, 16 Nov 2021 18:24:01 +0100 Subject: [PATCH 616/948] fix(core): signal race condition during stop (#166) --- gorgone/gorgone/class/core.pm | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index aeb08f040af..197ff31d8e3 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -886,8 +886,12 @@ sub run { } $cb_timer_check = time(); - # We can clean return_child. - $gorgone->{return_child} = {}; + # We can clean old return_child. + foreach my $pid (keys %{$gorgone->{return_child}}) { + if (($cb_timer_check - $gorgone->{return_child}->{$pid}) > 300) { + delete $gorgone->{return_child}->{$pid}; + } + } } if ($gorgone->{stop} == 1) { From f25b3b6c3d669532d413c642e715e3d9321de49f Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Fri, 19 Nov 2021 15:20:54 +0100 Subject: [PATCH 617/948] fix(core): signal race condition during stop - should be ok now (#167) --- gorgone/gorgone/class/core.pm | 117 ++++++++++-------- .../centreon/anomalydetection/class.pm | 5 +- .../centreon/anomalydetection/hooks.pm | 4 +- .../gorgone/modules/centreon/audit/class.pm | 3 +- .../gorgone/modules/centreon/audit/hooks.pm | 4 +- .../modules/centreon/autodiscovery/hooks.pm | 4 +- .../gorgone/modules/centreon/engine/class.pm | 3 +- .../gorgone/modules/centreon/engine/hooks.pm | 4 +- .../gorgone/modules/centreon/judge/class.pm | 5 +- .../gorgone/modules/centreon/judge/hooks.pm | 4 +- .../modules/centreon/legacycmd/class.pm | 3 +- .../modules/centreon/legacycmd/hooks.pm | 4 +- .../gorgone/modules/centreon/nodes/class.pm | 3 +- .../gorgone/modules/centreon/nodes/hooks.pm | 4 +- .../modules/centreon/statistics/class.pm | 5 +- .../modules/centreon/statistics/hooks.pm | 4 +- gorgone/gorgone/modules/core/action/class.pm | 3 +- gorgone/gorgone/modules/core/action/hooks.pm | 4 +- gorgone/gorgone/modules/core/cron/class.pm | 2 +- gorgone/gorgone/modules/core/cron/hooks.pm | 4 +- .../gorgone/modules/core/dbcleaner/class.pm | 5 +- .../gorgone/modules/core/dbcleaner/hooks.pm | 4 +- .../gorgone/modules/core/httpserver/hooks.pm | 4 +- .../gorgone/modules/core/pipeline/class.pm | 5 +- .../gorgone/modules/core/pipeline/hooks.pm | 4 +- gorgone/gorgone/modules/core/proxy/hooks.pm | 6 +- gorgone/gorgone/modules/core/pull/hooks.pm | 2 +- .../gorgone/modules/core/register/class.pm | 4 +- .../gorgone/modules/core/register/hooks.pm | 4 +- .../gorgone/modules/plugins/newtest/class.pm | 5 +- .../gorgone/modules/plugins/newtest/hooks.pm | 4 +- gorgone/gorgone/modules/plugins/scom/class.pm | 5 +- gorgone/gorgone/modules/plugins/scom/hooks.pm | 4 +- 33 files changed, 121 insertions(+), 124 deletions(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 197ff31d8e3..1cea987b1f9 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -58,7 +58,8 @@ sub new { $self->{modules_id} = {}; $self->{purge_timer} = time(); $self->{history_timer} = time(); - $self->{kill_timer} = undef; + $self->{sigterm_start_time} = undef; + $self->{sigterm_last_time} = undef; $self->{server_privkey} = undef; $self->{register_parent_nodes} = {}; $self->{counters} = { total => 0, internal => { total => 0 }, external => { total => 0 }, proxy => { total => 0 } }; @@ -262,12 +263,8 @@ sub class_handle_DIE { sub handle_TERM { my ($self) = @_; $self->{logger}->writeLogInfo("[core] $$ Receiving order to stop..."); + $self->{stop} = 1; - - foreach my $name (keys %{$self->{modules_register}}) { - $self->{modules_register}->{$name}->{gently}->(logger => $self->{logger}); - } - $self->{kill_timer} = time(); } sub handle_HUP { @@ -737,6 +734,7 @@ sub waiting_ready_pool { while (time() - $time < 60) { return 1 if ($method->() == 100); zmq_poll($gorgone->{poll}, 5000); + $gorgone->check_exit_modules(); } if ($method->() > 0) { @@ -756,6 +754,7 @@ sub waiting_ready { while (${$options{ready}} == 0 && time() - $time < 10) { zmq_poll($gorgone->{poll}, 5000); + $gorgone->check_exit_modules(); } if (${$options{ready}} == 0) { @@ -779,6 +778,61 @@ sub quit { exit(0); } +sub check_exit_modules { + my ($self, %options) = @_; + + my $count = 0; + my $current_time = time(); + if (time() - $self->{cb_timer_check} > 15 || $self->{stop} == 1) { + if ($self->{stop} == 1 && (!defined($self->{sigterm_last_time}) || ($current_time - $self->{sigterm_last_time}) >= 10)) { + $self->{sigterm_start_time} = time() if (!defined($self->{sigterm_start_time})); + $self->{sigterm_last_time} = time(); + foreach my $name (keys %{$self->{modules_register}}) { + $self->{modules_register}->{$name}->{gently}->(logger => $gorgone->{logger}); + } + } + + foreach my $name (keys %{$self->{modules_register}}) { + my ($count_module, $keepalive) = $self->{modules_register}->{$name}->{check}->( + gorgone => $self, + logger => $self->{logger}, + dead_childs => $self->{return_child}, + internal_socket => $self->{internal_socket}, + dbh => $self->{db_gorgone}, + api_endpoints => $self->{api_endpoints} + ); + + $count += $count_module; + if ($count_module == 0 && (!defined($keepalive) || $keepalive == 0)) { + $self->unload_module(package => $name); + } + } + + $self->{cb_timer_check} = time(); + # We can clean old return_child. + foreach my $pid (keys %{$self->{return_child}}) { + if (($self->{cb_timer_check} - $self->{return_child}->{$pid}) > 300) { + delete $self->{return_child}->{$pid}; + } + } + } + + if ($self->{stop} == 1) { + # No childs + if ($count == 0) { + $self->quit(); + } + + # Send KILL + if (time() - $self->{sigterm_start_time} > $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{timeout}) { + foreach my $name (keys %{$self->{modules_register}}) { + $self->{modules_register}->{$name}->{kill_internal}->(logger => $gorgone->{logger}); + } + $self->quit(); + } + } +} + sub run { $gorgone = shift; @@ -861,56 +915,11 @@ sub run { $gorgone::standard::library::listener = $gorgone->{listener}; $gorgone->{logger}->writeLogInfo("[core] Server accepting clients"); - my $cb_timer_check = time(); + $gorgone->{cb_timer_check} = time(); while (1) { - my $count = 0; - my $poll = []; - - my $current_time = time(); - if (time() - $cb_timer_check > 15 || $gorgone->{stop} == 1) { - foreach my $name (keys %{$gorgone->{modules_register}}) { - my ($count_module, $keepalive) = $gorgone->{modules_register}->{$name}->{check}->( - gorgone => $gorgone, - logger => $gorgone->{logger}, - dead_childs => $gorgone->{return_child}, - internal_socket => $gorgone->{internal_socket}, - dbh => $gorgone->{db_gorgone}, - poll => $poll, - api_endpoints => $gorgone->{api_endpoints}, - ); - - $count += $count_module; - if ($count_module == 0 && (!defined($keepalive) || $keepalive == 0)) { - $gorgone->unload_module(package => $name); - } - } + $gorgone->check_exit_modules(); - $cb_timer_check = time(); - # We can clean old return_child. - foreach my $pid (keys %{$gorgone->{return_child}}) { - if (($cb_timer_check - $gorgone->{return_child}->{$pid}) > 300) { - delete $gorgone->{return_child}->{$pid}; - } - } - } - - if ($gorgone->{stop} == 1) { - # No childs - if ($count == 0) { - $gorgone->quit(); - } - - # Send KILL - if (time() - $gorgone->{kill_timer} > $gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{timeout}) { - foreach my $name (keys %{$gorgone->{modules_register}}) { - $gorgone->{modules_register}->{$name}->{kill_internal}->(logger => $gorgone->{logger}); - } - $gorgone->quit(); - } - } - - push @$poll, @{$gorgone->{poll}}; - zmq_poll($poll, 5000); + zmq_poll($gorgone->{poll}, 5000); $gorgone->{listener}->check(); } diff --git a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm index 8ba8f7cf66e..670ab53a83e 100644 --- a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm +++ b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm @@ -676,9 +676,8 @@ sub run { } ]; while (1) { - # we try to do all we can - my $rev = zmq_poll($self->{poll}, 5000); - if (defined($rev) && $rev == 0 && $self->{stop} == 1) { + my $rev = scalar(zmq_poll($self->{poll}, 5000)); + if ($rev == 0 && $self->{stop} == 1) { $self->{logger}->writeLogInfo("[anomalydetection] -class- $$ has quit"); zmq_close($connector->{internal_socket}); exit(0); diff --git a/gorgone/gorgone/modules/centreon/anomalydetection/hooks.pm b/gorgone/gorgone/modules/centreon/anomalydetection/hooks.pm index 362eb062c20..8ccf4f9976e 100644 --- a/gorgone/gorgone/modules/centreon/anomalydetection/hooks.pm +++ b/gorgone/gorgone/modules/centreon/anomalydetection/hooks.pm @@ -104,8 +104,8 @@ sub gently { my (%options) = @_; $stop = 1; - $options{logger}->writeLogDebug("[anomalydetection] Send TERM signal"); - if ($process->{running} == 1) { + if (defined($process->{running}) && $process->{running} == 1) { + $options{logger}->writeLogDebug("[anomalydetection] Send TERM signal $process->{pid}"); CORE::kill('TERM', $process->{pid}); } } diff --git a/gorgone/gorgone/modules/centreon/audit/class.pm b/gorgone/gorgone/modules/centreon/audit/class.pm index ec8b30af028..e94a3470e02 100644 --- a/gorgone/gorgone/modules/centreon/audit/class.pm +++ b/gorgone/gorgone/modules/centreon/audit/class.pm @@ -374,8 +374,7 @@ sub run { } ]; while (1) { - # we try to do all we can - my $rev = zmq_poll($self->{poll}, 5000); + my $rev = scalar(zmq_poll($self->{poll}, 5000)); if ($rev == 0 && $self->{stop} == 1) { $self->{logger}->writeLogInfo("[audit] $$ has quit"); zmq_close($connector->{internal_socket}); diff --git a/gorgone/gorgone/modules/centreon/audit/hooks.pm b/gorgone/gorgone/modules/centreon/audit/hooks.pm index db6ca9996d5..7b5d427f206 100644 --- a/gorgone/gorgone/modules/centreon/audit/hooks.pm +++ b/gorgone/gorgone/modules/centreon/audit/hooks.pm @@ -106,8 +106,8 @@ sub gently { my (%options) = @_; $stop = 1; - $options{logger}->writeLogDebug("[audit] Send TERM signal"); - if ($audit->{running} == 1) { + if (defined($audit->{running}) && $audit->{running} == 1) { + $options{logger}->writeLogDebug("[audit] Send TERM signal $audit->{pid}"); CORE::kill('TERM', $audit->{pid}); } } diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm b/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm index 2ecce9eb9ba..e2e65f26242 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm @@ -110,8 +110,8 @@ sub gently { my (%options) = @_; $stop = 1; - $options{logger}->writeLogDebug("[autodiscovery] Send TERM signal"); - if ($autodiscovery->{running} == 1) { + if (defined($autodiscovery->{running}) && $autodiscovery->{running} == 1) { + $options{logger}->writeLogDebug("[autodiscovery] Send TERM signal $autodiscovery->{pid}"); CORE::kill('TERM', $autodiscovery->{pid}); } } diff --git a/gorgone/gorgone/modules/centreon/engine/class.pm b/gorgone/gorgone/modules/centreon/engine/class.pm index 7a1a0d869c8..a5cdfb48fba 100644 --- a/gorgone/gorgone/modules/centreon/engine/class.pm +++ b/gorgone/gorgone/modules/centreon/engine/class.pm @@ -299,8 +299,7 @@ sub run { } ]; while (1) { - # we try to do all we can - my $rev = zmq_poll($self->{poll}, 5000); + my $rev = scalar(zmq_poll($self->{poll}, 5000)); if ($rev == 0 && $self->{stop} == 1) { $self->{logger}->writeLogInfo("[engine] $$ has quit"); zmq_close($connector->{internal_socket}); diff --git a/gorgone/gorgone/modules/centreon/engine/hooks.pm b/gorgone/gorgone/modules/centreon/engine/hooks.pm index cd49d379833..e52c9490bca 100644 --- a/gorgone/gorgone/modules/centreon/engine/hooks.pm +++ b/gorgone/gorgone/modules/centreon/engine/hooks.pm @@ -102,8 +102,8 @@ sub gently { my (%options) = @_; $stop = 1; - $options{logger}->writeLogDebug("[engine] Send TERM signal"); - if ($engine->{running} == 1) { + if (defined($engine->{running}) && $engine->{running} == 1) { + $options{logger}->writeLogDebug("[engine] Send TERM signal $engine->{pid}"); CORE::kill('TERM', $engine->{pid}); } } diff --git a/gorgone/gorgone/modules/centreon/judge/class.pm b/gorgone/gorgone/modules/centreon/judge/class.pm index 7bb41971aee..d34cfce166e 100644 --- a/gorgone/gorgone/modules/centreon/judge/class.pm +++ b/gorgone/gorgone/modules/centreon/judge/class.pm @@ -583,9 +583,8 @@ sub run { ); while (1) { - # we try to do all we can - my $rev = zmq_poll($self->{poll}, 5000); - if (defined($rev) && $rev == 0 && $self->{stop} == 1) { + my $rev = scalar(zmq_poll($self->{poll}, 5000)); + if ($rev == 0 && $self->{stop} == 1) { $self->{logger}->writeLogInfo("[judge] -class- $$ has quit"); zmq_close($connector->{internal_socket}); exit(0); diff --git a/gorgone/gorgone/modules/centreon/judge/hooks.pm b/gorgone/gorgone/modules/centreon/judge/hooks.pm index 0dc3c7d480c..8bac6ebe583 100644 --- a/gorgone/gorgone/modules/centreon/judge/hooks.pm +++ b/gorgone/gorgone/modules/centreon/judge/hooks.pm @@ -107,8 +107,8 @@ sub gently { my (%options) = @_; $stop = 1; - $options{logger}->writeLogDebug('[judge] Send TERM signal'); - if ($judge->{running} == 1) { + if (defined($judge->{running}) && $judge->{running} == 1) { + $options{logger}->writeLogDebug("[judge] Send TERM signal $judge->{pid}"); CORE::kill('TERM', $judge->{pid}); } } diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index a933e8d01c7..f6a73913a00 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -799,8 +799,7 @@ sub run { } ]; while (1) { - # we try to do all we can - my $rev = zmq_poll($self->{poll}, 2000); + my $rev = scalar(zmq_poll($self->{poll}, 2000)); if ($rev == 0 && $self->{stop} == 1) { $self->{logger}->writeLogInfo("[legacycmd] $$ has quit"); zmq_close($connector->{internal_socket}); diff --git a/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm b/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm index 91a65065af5..cc293bfdf53 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm @@ -108,8 +108,8 @@ sub gently { my (%options) = @_; $stop = 1; - $options{logger}->writeLogDebug("[legacycmd] Send TERM signal"); - if ($legacycmd->{running} == 1) { + if (defined($legacycmd->{running}) && $legacycmd->{running} == 1) { + $options{logger}->writeLogDebug("[legacycmd] Send TERM signal $legacycmd->{running}"); CORE::kill('TERM', $legacycmd->{pid}); } } diff --git a/gorgone/gorgone/modules/centreon/nodes/class.pm b/gorgone/gorgone/modules/centreon/nodes/class.pm index 939a808ab65..1964e3a885c 100644 --- a/gorgone/gorgone/modules/centreon/nodes/class.pm +++ b/gorgone/gorgone/modules/centreon/nodes/class.pm @@ -259,8 +259,7 @@ sub run { } ]; while (1) { - # we try to do all we can - my $rev = zmq_poll($self->{poll}, 5000); + my $rev = scalar(zmq_poll($self->{poll}, 5000)); if (defined($rev) && $rev == 0 && $self->{stop} == 1) { $self->{logger}->writeLogInfo("[nodes] -class- $$ has quit"); zmq_close($connector->{internal_socket}); diff --git a/gorgone/gorgone/modules/centreon/nodes/hooks.pm b/gorgone/gorgone/modules/centreon/nodes/hooks.pm index fe7cd6a20e6..489a3bb1cfe 100644 --- a/gorgone/gorgone/modules/centreon/nodes/hooks.pm +++ b/gorgone/gorgone/modules/centreon/nodes/hooks.pm @@ -105,8 +105,8 @@ sub gently { my (%options) = @_; $stop = 1; - $options{logger}->writeLogDebug("[nodes] Send TERM signal"); - if ($nodes->{running} == 1) { + if (defined($nodes->{running}) && $nodes->{running} == 1) { + $options{logger}->writeLogDebug("[nodes] Send TERM signal $nodes->{pid}"); CORE::kill('TERM', $nodes->{pid}); } } diff --git a/gorgone/gorgone/modules/centreon/statistics/class.pm b/gorgone/gorgone/modules/centreon/statistics/class.pm index 0863511d30b..4ed15d14704 100644 --- a/gorgone/gorgone/modules/centreon/statistics/class.pm +++ b/gorgone/gorgone/modules/centreon/statistics/class.pm @@ -657,9 +657,8 @@ sub run { } while (1) { - # we try to do all we can - my $rev = zmq_poll($self->{poll}, 5000); - if (defined($rev) && $rev == 0 && $self->{stop} == 1) { + my $rev = scalar(zmq_poll($self->{poll}, 5000)); + if ($rev == 0 && $self->{stop} == 1) { $self->{logger}->writeLogInfo("[statistics] $$ has quit"); zmq_close($connector->{internal_socket}); exit(0); diff --git a/gorgone/gorgone/modules/centreon/statistics/hooks.pm b/gorgone/gorgone/modules/centreon/statistics/hooks.pm index e6d90a0616d..1629ece065a 100644 --- a/gorgone/gorgone/modules/centreon/statistics/hooks.pm +++ b/gorgone/gorgone/modules/centreon/statistics/hooks.pm @@ -118,8 +118,8 @@ sub gently { my (%options) = @_; $stop = 1; - $options{logger}->writeLogDebug("[statistics] Send TERM signal"); - if ($statistics->{running} == 1) { + if (defined($statistics->{running}) && $statistics->{running} == 1) { + $options{logger}->writeLogDebug("[statistics] Send TERM signal $statistics->{pid}"); CORE::kill('TERM', $statistics->{pid}); } } diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index b9a2e70b128..1e40e13c378 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -480,8 +480,7 @@ sub run { } ]; while (1) { - # we try to do all we can - my $rev = zmq_poll($self->{poll}, 5000); + my $rev = scalar(zmq_poll($self->{poll}, 5000)); if ($rev == 0 && $self->{stop} == 1) { $self->{logger}->writeLogInfo("[action] $$ has quit"); zmq_close($connector->{internal_socket}); diff --git a/gorgone/gorgone/modules/core/action/hooks.pm b/gorgone/gorgone/modules/core/action/hooks.pm index 46f9d4249aa..817e36a1a1e 100644 --- a/gorgone/gorgone/modules/core/action/hooks.pm +++ b/gorgone/gorgone/modules/core/action/hooks.pm @@ -102,8 +102,8 @@ sub gently { my (%options) = @_; $stop = 1; - $options{logger}->writeLogDebug("[action] Send TERM signal"); - if ($action->{running} == 1) { + if (defined($action->{running}) && $action->{running} == 1) { + $options{logger}->writeLogDebug("[action] Send TERM signal $action->{running}"); CORE::kill('TERM', $action->{pid}); } } diff --git a/gorgone/gorgone/modules/core/cron/class.pm b/gorgone/gorgone/modules/core/cron/class.pm index 1b2dc5786f5..83ec673ecee 100644 --- a/gorgone/gorgone/modules/core/cron/class.pm +++ b/gorgone/gorgone/modules/core/cron/class.pm @@ -478,7 +478,7 @@ sub run { } ); } - + $self->{cron}->run(sleep => \&cron_sleep); zmq_close($connector->{internal_socket}); diff --git a/gorgone/gorgone/modules/core/cron/hooks.pm b/gorgone/gorgone/modules/core/cron/hooks.pm index 8764b209471..6a87eb14974 100644 --- a/gorgone/gorgone/modules/core/cron/hooks.pm +++ b/gorgone/gorgone/modules/core/cron/hooks.pm @@ -104,8 +104,8 @@ sub gently { my (%options) = @_; $stop = 1; - $options{logger}->writeLogDebug("[cron] Send TERM signal"); - if ($cron->{running} == 1) { + if (defined($cron->{running}) && $cron->{running} == 1) { + $options{logger}->writeLogDebug("[cron] Send TERM signal $cron->{pid}"); CORE::kill('TERM', $cron->{pid}); } } diff --git a/gorgone/gorgone/modules/core/dbcleaner/class.pm b/gorgone/gorgone/modules/core/dbcleaner/class.pm index 2676db0705d..f6a1ca9001e 100644 --- a/gorgone/gorgone/modules/core/dbcleaner/class.pm +++ b/gorgone/gorgone/modules/core/dbcleaner/class.pm @@ -202,9 +202,8 @@ sub run { ); while (1) { - # we try to do all we can - my $rev = zmq_poll($self->{poll}, 5000); - if (defined($rev) && $rev == 0 && $self->{stop} == 1) { + my $rev = scalar(zmq_poll($self->{poll}, 5000)); + if ($rev == 0 && $self->{stop} == 1) { $self->exit_process(); } diff --git a/gorgone/gorgone/modules/core/dbcleaner/hooks.pm b/gorgone/gorgone/modules/core/dbcleaner/hooks.pm index 2c91abf4725..a661252f420 100644 --- a/gorgone/gorgone/modules/core/dbcleaner/hooks.pm +++ b/gorgone/gorgone/modules/core/dbcleaner/hooks.pm @@ -111,8 +111,8 @@ sub gently { my (%options) = @_; $stop = 1; - $options{logger}->writeLogDebug("[dbcleaner] Send TERM signal"); - if ($dbcleaner->{running} == 1) { + if (defined($dbcleaner->{running}) && $dbcleaner->{running} == 1) { + $options{logger}->writeLogDebug("[dbcleaner] Send TERM signal $dbcleaner->{pid}"); CORE::kill('TERM', $dbcleaner->{pid}); } } diff --git a/gorgone/gorgone/modules/core/httpserver/hooks.pm b/gorgone/gorgone/modules/core/httpserver/hooks.pm index adcc360b048..12020b6dd42 100644 --- a/gorgone/gorgone/modules/core/httpserver/hooks.pm +++ b/gorgone/gorgone/modules/core/httpserver/hooks.pm @@ -114,8 +114,8 @@ sub gently { my (%options) = @_; $stop = 1; - $options{logger}->writeLogDebug("[httpserver] Send TERM signal"); - if ($httpserver->{running} == 1) { + if (defined($httpserver->{running}) && $httpserver->{running} == 1) { + $options{logger}->writeLogDebug("[httpserver] Send TERM signal $httpserver->{pid}"); CORE::kill('TERM', $httpserver->{pid}); } } diff --git a/gorgone/gorgone/modules/core/pipeline/class.pm b/gorgone/gorgone/modules/core/pipeline/class.pm index cdfd30573f2..a9d061a9fb5 100644 --- a/gorgone/gorgone/modules/core/pipeline/class.pm +++ b/gorgone/gorgone/modules/core/pipeline/class.pm @@ -253,9 +253,8 @@ sub run { ]; while (1) { - # we try to do all we can - my $rev = zmq_poll($self->{poll}, 5000); - if (defined($rev) && $rev == 0 && $self->{stop} == 1) { + my $rev = scalar(zmq_poll($self->{poll}, 5000)); + if ($rev == 0 && $self->{stop} == 1) { $self->{logger}->writeLogInfo("[pipeline] -class- $$ has quit"); zmq_close($connector->{internal_socket}); exit(0); diff --git a/gorgone/gorgone/modules/core/pipeline/hooks.pm b/gorgone/gorgone/modules/core/pipeline/hooks.pm index b91a73323e1..8e4fe6ce318 100644 --- a/gorgone/gorgone/modules/core/pipeline/hooks.pm +++ b/gorgone/gorgone/modules/core/pipeline/hooks.pm @@ -112,8 +112,8 @@ sub gently { my (%options) = @_; $stop = 1; - $options{logger}->writeLogDebug('[pipeline] Send TERM signal'); - if ($pipeline->{running} == 1) { + if (defined($pipeline->{running}) && $pipeline->{running} == 1) { + $options{logger}->writeLogDebug("[pipeline] Send TERM signal $pipeline->{pid}"); CORE::kill('TERM', $pipeline->{pid}); } } diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index 991a1bf0e2f..14bdb865f09 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -325,9 +325,9 @@ sub gently { my (%options) = @_; $stop = 1; - foreach my $pool_id (keys %{$pools}) { - $options{logger}->writeLogDebug("[proxy] Send TERM signal for pool '" . $pool_id . "'"); - if ($pools->{$pool_id}->{running} == 1) { + foreach my $pool_id (keys %$pools) { + if (defined($pools->{$pool_id}->{running}) && $pools->{$pool_id}->{running} == 1) { + $options{logger}->writeLogDebug("[proxy] Send TERM signal for pool '" . $pool_id . "'"); CORE::kill('TERM', $pools->{$pool_id}->{pid}); } } diff --git a/gorgone/gorgone/modules/core/pull/hooks.pm b/gorgone/gorgone/modules/core/pull/hooks.pm index 45c1ebf4ff0..8f38486752e 100644 --- a/gorgone/gorgone/modules/core/pull/hooks.pm +++ b/gorgone/gorgone/modules/core/pull/hooks.pm @@ -126,7 +126,7 @@ sub check { my (%options) = @_; if ($stop == 0) { - # If distant server restart, it's a not problem. It save the key. + # If distant server restart, it's not problem. It save the key. # But i don't have the registernode anymore. The ping is the 'registernode' for pull mode. $client->ping( poll => $options{gorgone}->{poll}, diff --git a/gorgone/gorgone/modules/core/register/class.pm b/gorgone/gorgone/modules/core/register/class.pm index e5ec3207a2c..0f473aa1e03 100644 --- a/gorgone/gorgone/modules/core/register/class.pm +++ b/gorgone/gorgone/modules/core/register/class.pm @@ -180,8 +180,8 @@ sub run { $self->action_registerresync(); while (1) { # we try to do all we can - my $rev = zmq_poll($self->{poll}, 5000); - if (defined($rev) && $rev == 0 && $self->{stop} == 1) { + my $rev = scalar(zmq_poll($self->{poll}, 5000)); + if ($rev == 0 && $self->{stop} == 1) { $self->{logger}->writeLogInfo("[register] $$ has quit"); zmq_close($connector->{internal_socket}); exit(0); diff --git a/gorgone/gorgone/modules/core/register/hooks.pm b/gorgone/gorgone/modules/core/register/hooks.pm index 1fbd01112f2..c3f0c9efb9e 100644 --- a/gorgone/gorgone/modules/core/register/hooks.pm +++ b/gorgone/gorgone/modules/core/register/hooks.pm @@ -106,8 +106,8 @@ sub gently { my (%options) = @_; $stop = 1; - $options{logger}->writeLogDebug("[register] Send TERM signal"); - if ($register->{running} == 1) { + if (defined($register->{running}) && $register->{running} == 1) { + $options{logger}->writeLogDebug("[register] Send TERM signal $register->{pid}"); CORE::kill('TERM', $register->{pid}); } } diff --git a/gorgone/gorgone/modules/plugins/newtest/class.pm b/gorgone/gorgone/modules/plugins/newtest/class.pm index 85a91cda2d6..bc7c0a9c61e 100644 --- a/gorgone/gorgone/modules/plugins/newtest/class.pm +++ b/gorgone/gorgone/modules/plugins/newtest/class.pm @@ -648,9 +648,8 @@ sub run { } ]; while (1) { - # we try to do all we can - my $rev = zmq_poll($self->{poll}, 5000); - if (defined($rev) && $rev == 0 && $self->{stop} == 1) { + my $rev = scalar(zmq_poll($self->{poll}, 5000)); + if ($rev == 0 && $self->{stop} == 1) { $self->{logger}->writeLogInfo("[newtest] $$ has quit"); zmq_close($connector->{internal_socket}); zmq_close($self->{socket_log}) if (defined($self->{socket_log})); diff --git a/gorgone/gorgone/modules/plugins/newtest/hooks.pm b/gorgone/gorgone/modules/plugins/newtest/hooks.pm index e1ee2fb66fd..75598f201f5 100644 --- a/gorgone/gorgone/modules/plugins/newtest/hooks.pm +++ b/gorgone/gorgone/modules/plugins/newtest/hooks.pm @@ -120,8 +120,8 @@ sub gently { $stop = 1; foreach my $container_id (keys %$containers) { - $options{logger}->writeLogDebug("[newtest] Send TERM signal for container '" . $container_id . "'"); - if ($containers->{$container_id}->{running} == 1) { + if (defined($containers->{$container_id}->{running}) && $containers->{$container_id}->{running} == 1) { + $options{logger}->writeLogDebug("[newtest] Send TERM signal for container '" . $container_id . "'"); CORE::kill('TERM', $containers->{$container_id}->{pid}); } } diff --git a/gorgone/gorgone/modules/plugins/scom/class.pm b/gorgone/gorgone/modules/plugins/scom/class.pm index bbeddec737e..65fd85af631 100644 --- a/gorgone/gorgone/modules/plugins/scom/class.pm +++ b/gorgone/gorgone/modules/plugins/scom/class.pm @@ -527,9 +527,8 @@ sub run { } ]; while (1) { - # we try to do all we can - my $rev = zmq_poll($self->{poll}, 5000); - if (defined($rev) && $rev == 0 && $self->{stop} == 1) { + my $rev = scalar(zmq_poll($self->{poll}, 5000)); + if ($rev == 0 && $self->{stop} == 1) { $self->{logger}->writeLogInfo("[scom] $$ has quit"); zmq_close($connector->{internal_socket}); exit(0); diff --git a/gorgone/gorgone/modules/plugins/scom/hooks.pm b/gorgone/gorgone/modules/plugins/scom/hooks.pm index bd6be0fa358..37185548c45 100644 --- a/gorgone/gorgone/modules/plugins/scom/hooks.pm +++ b/gorgone/gorgone/modules/plugins/scom/hooks.pm @@ -119,8 +119,8 @@ sub gently { $stop = 1; foreach my $container_id (keys %$containers) { - $options{logger}->writeLogInfo("[scom] Send TERM signal for container '" . $container_id . "'"); - if ($containers->{$container_id}->{running} == 1) { + if (defined($containers->{$container_id}->{running}) && $containers->{$container_id}->{running} == 1) { + $options{logger}->writeLogInfo("[scom] Send TERM signal for container '" . $container_id . "'"); CORE::kill('TERM', $containers->{$container_id}->{pid}); } } From d4caed5224c4da4493ee65ad8e6f76b091001580 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Fri, 26 Nov 2021 15:21:06 +0100 Subject: [PATCH 618/948] enh(hostdisco): add token to post command (#169) --- gorgone/gorgone/class/tpapi/centreonv2.pm | 6 ++++++ gorgone/gorgone/modules/centreon/autodiscovery/class.pm | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/gorgone/gorgone/class/tpapi/centreonv2.pm b/gorgone/gorgone/class/tpapi/centreonv2.pm index c681f873074..d3939b12734 100644 --- a/gorgone/gorgone/class/tpapi/centreonv2.pm +++ b/gorgone/gorgone/class/tpapi/centreonv2.pm @@ -222,6 +222,12 @@ sub request { return (0, $decoded); } +sub get_token { + my ($self, %options) = @_; + + return $self->{token}; +} + sub get_monitoring_hosts { my ($self, %options) = @_; diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm index c86be239ac5..d601b97104e 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm @@ -745,7 +745,7 @@ sub discovery_command_result { instant => 1, content => [ { - command => $post_command->{command_line}, + command => $post_command->{command_line} . ' --token=' . $self->{tpapi_centreonv2}->get_token(), metadata => { job_id => $job_id, source => 'autodiscovery-host-job-postcommand' From ed40057afba0aba54d103509056e86a4e4280e9f Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Tue, 30 Nov 2021 12:08:20 +0100 Subject: [PATCH 619/948] fix(autodisco): handle advanced option for service discovery - use Safe for security (#170) --- .../autodiscovery/services/discovery.pm | 66 +++++++++++++------ .../autodiscovery/services/resources.pm | 14 ---- 2 files changed, 47 insertions(+), 33 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm index 6b773c64d14..8dfec7d9cd7 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm @@ -32,19 +32,17 @@ use ZMQ::Constants qw(:all); use Net::SMTP; use XML::Simple; use POSIX qw(strftime); +use Safe; sub new { my ($class, %options) = @_; + my $connector = $class->SUPER::new(%options); + bless $connector, $class; - my $connector = {}; $connector->{internal_socket} = $options{internal_socket}; - $connector->{module_id} = $options{module_id}; - $connector->{logger} = $options{logger}; - $connector->{config} = $options{config}; - $connector->{config_core} = $options{config_core}; - $connector->{tpapi_clapi} = $options{tpapi_clapi}; $connector->{class_object_centreon} = $options{class_object_centreon}; $connector->{class_object_centstorage} = $options{class_object_centstorage}; + $connector->{tpapi_clapi} = $options{tpapi_clapi}; $connector->{mail_subject} = defined($connector->{config}->{mail_subject}) ? $connector->{config}->{mail_subject} : 'Centreon Auto Discovery'; $connector->{mail_from} = defined($connector->{config}->{mail_from}) ? $connector->{config}->{mail_from} : 'centreon-autodisco'; @@ -54,7 +52,19 @@ sub new { $connector->{service_current_commands_poller} = {}; $connector->{finished} = 0; - bless $connector, $class; + $connector->{safe_display} = Safe->new(); + $connector->{safe_display}->share('$values'); + $connector->{safe_display}->share('$description'); + $connector->{safe_display}->permit_only(':default'); + $connector->{safe_display}->share_from( + 'gorgone::modules::centreon::autodiscovery::services::resources', + ['change_bytes'] + ); + + $connector->{safe_cv} = Safe->new(); + $connector->{safe_cv}->share('$values'); + $connector->{safe_cv}->permit_only(':default'); + $connector->{uuid} = $connector->generate_token(length => 4) . ':' . $options{service_number}; return $connector; } @@ -216,21 +226,40 @@ sub audit_update { } } +sub custom_variables { + my ($self, %options) = @_; + + if (defined($options{rule}->{rule_variable_custom}) && $options{rule}->{rule_variable_custom} ne '') { + local $SIG{__DIE__} = 'IGNORE'; + + our $values = { attributes => $options{discovery_svc}->{attributes}, service_name => $options{discovery_svc}->{service_name} }; + $self->{safe_cv}->reval($options{rule}->{rule_variable_custom}, 1); + if ($@) { + $self->{logger}->writeLogError("$options{logger_pre_message} custom variable code execution problem: " . $@); + } else { + $options{discovery_svc}->{attributes} = $values->{attributes}; + } + } +} + sub get_description { my ($self, %options) = @_; - my $description = $options{discovery_svc}->{service_name}; + my $desc = $options{discovery_svc}->{service_name}; if (defined($self->{discovery}->{rules}->{ $options{rule_id} }->{rule_scan_display_custom}) && $self->{discovery}->{rules}->{ $options{rule_id} }->{rule_scan_display_custom} ne '') { - my $error; - local $SIG{__DIE__} = sub { $error = $_[0]; }; + local $SIG{__DIE__} = 'IGNORE'; - eval "$self->{discovery}->{rules}->{ $options{rule_id} }->{rule_scan_display_custom}"; - if (defined($error)) { - $self->{logger}->writeLogError("$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] custom description code execution problem: " . $error); + our $description = $desc; + our $values = { attributes => $options{discovery_svc}->{attributes}, service_name => $options{discovery_svc}->{service_name} }; + $self->{safe_display}->reval($self->{discovery}->{rules}->{ $options{rule_id} }->{rule_scan_display_custom}, 1); + if ($@) { + $self->{logger}->writeLogError("$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] custom description code execution problem: " . $@); + } else { + $desc = $description; } } - - return $description; + + return $desc; } sub link_service_autodisco { @@ -569,10 +598,9 @@ sub service_response_parsing { logger_pre_message => $logger_pre_message ) ); - gorgone::modules::centreon::autodiscovery::services::resources::custom_variables( + $self->custom_variables( discovery_svc => $discovery_svc, rule => $self->{discovery}->{rules}->{ $options{rule_id} }, - logger => $self->{logger}, logger_pre_message => $logger_pre_message ); my $macros = gorgone::modules::centreon::autodiscovery::services::resources::get_macros( @@ -851,8 +879,8 @@ sub launchdiscovery { foreach (('rule_scan_display_custom', 'rule_variable_custom')) { if (defined($rules->{$rule_id}->{$_}) && $rules->{$rule_id}->{$_} ne '') { - $rules->{$rule_id}->{$_} =~ s/\$([a-zA-Z_\-\.]*?)\$/\$options{discovery_svc}->{attributes}->{$1}/msg; - $rules->{$rule_id}->{$_} =~ s/\@SERVICENAME\@/\$options{discovery_svc}->{service_name}/msg; + $rules->{$rule_id}->{$_} =~ s/\$([a-zA-Z_\-\.]*?)\$/\$values->{attributes}->{$1}/msg; + $rules->{$rule_id}->{$_} =~ s/\@SERVICENAME\@/\$values->{service_name}/msg; } } } diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm b/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm index 7731f262e95..961b8beecad 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm @@ -533,20 +533,6 @@ sub change_bytes { return (sprintf("%.2f", $options{value}), $unit . (defined($options{network}) ? 'b' : 'B')); } -sub custom_variables { - my (%options) = @_; - - if (defined($options{rule}->{rule_variable_custom}) && $options{rule}->{rule_variable_custom} ne '') { - my $error; - local $SIG{__DIE__} = sub { $error = $_[0]; }; - - eval "$options{rule}->{rule_variable_custom}"; - if (defined($error)) { - $options{logger}->writeLogError("$options{logger_pre_message} custom variable code execution problem: " . $error); - } - } -} - sub check_exinc { my (%options) = @_; From bcd2b563118d91cd9e859c906229c60249b23387 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Tue, 30 Nov 2021 17:12:56 +0100 Subject: [PATCH 620/948] add(package): centreon audit module configuration (#171) --- gorgone/packaging/centreon-audit.yaml | 6 ++++++ gorgone/packaging/centreon-gorgone.spectemplate | 2 ++ 2 files changed, 8 insertions(+) create mode 100644 gorgone/packaging/centreon-audit.yaml diff --git a/gorgone/packaging/centreon-audit.yaml b/gorgone/packaging/centreon-audit.yaml new file mode 100644 index 00000000000..852f0f85fda --- /dev/null +++ b/gorgone/packaging/centreon-audit.yaml @@ -0,0 +1,6 @@ +gorgone: + modules: + - name: audit + package: "gorgone::modules::centreon::audit::hooks" + enable: true + diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 88c477f6709..36befec6612 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -79,6 +79,7 @@ mkdir -p %{buildroot}/%{perl_vendorlib}/gorgone %{__cp} packaging/config.yaml %{buildroot}%{_sysconfdir}/centreon-gorgone/ %{__cp} packaging/centreon.yaml %{buildroot}%{_sysconfdir}/centreon-gorgone/config.d/30-centreon.yaml %{__cp} packaging/centreon-api.yaml %{buildroot}%{_sysconfdir}/centreon-gorgone/config.d/31-centreon-api.yaml +%{__cp} packaging/centreon-audit.yaml %{buildroot}%{_sysconfdir}/centreon-gorgone/config.d/50-centreon-audit.yaml %{_fixperms} $RPM_BUILD_ROOT/* @@ -112,6 +113,7 @@ rm -rf %{buildroot} %files centreon-config %config(noreplace) %{_sysconfdir}/centreon-gorgone/config.d/30-centreon.yaml %config(noreplace) %{_sysconfdir}/centreon-gorgone/config.d/31-centreon-api.yaml +%config(noreplace) %{_sysconfdir}/centreon-gorgone/config.d/50-centreon-audit.yaml %defattr(-, centreon-gorgone, centreon-gorgone, -) %{_localstatedir}/cache/centreon-gorgone/autodiscovery From 3e149af139bb8ebad4f185534dd1665a1b256f11 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Tue, 30 Nov 2021 17:25:00 +0100 Subject: [PATCH 621/948] add(package): centreon audit script (#172) --- gorgone/packaging/centreon-gorgone.spectemplate | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 36befec6612..c3c69da90d0 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -71,6 +71,7 @@ mkdir -p %{buildroot}/%{perl_vendorlib}/gorgone %{__cp} -R gorgone/* %{buildroot}/%{perl_vendorlib}/gorgone/ %{__cp} gorgoned %{buildroot}%{_bindir}/ %{__cp} contrib/gorgone_config_init.pl %{buildroot}%{_usr}/local/bin/ +%{__cp} contrib/gorgone_audit.pl %{buildroot}%{_usr}/local/bin/ %{__install} -d %{buildroot}%{_sysconfdir}/centreon-gorgone %{__install} -d %{buildroot}%{_sysconfdir}/centreon-gorgone/config.d/ @@ -93,6 +94,7 @@ rm -rf %{buildroot} %{perl_vendorlib}/gorgone/ %attr(755, root, root) %{_bindir}/gorgoned %attr(755, root, root) %{_usr}/local/bin/gorgone_config_init.pl +%attr(755, root, root) %{_usr}/local/bin/gorgone_audit.pl %attr(755, root, root) %{_sysconfdir}/systemd/system/gorgoned.service %config(noreplace) %{_sysconfdir}/sysconfig/gorgoned %config(noreplace) %{_sysconfdir}/logrotate.d/gorgoned From 2aa5457d0fbe82e04208b6752800bb430e49b7d7 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Tue, 14 Dec 2021 09:04:47 +0100 Subject: [PATCH 622/948] add(module): new httpserver (#173) --- gorgone/TODO | 1 - gorgone/contrib/mojolicious_client.pl | 9 +- gorgone/contrib/mojolicious_server.pl | 2 +- gorgone/gorgone/class/module.pm | 7 + .../centreon/anomalydetection/class.pm | 4 +- .../centreon/anomalydetection/hooks.pm | 21 +- .../gorgone/modules/centreon/audit/class.pm | 4 +- .../gorgone/modules/centreon/audit/hooks.pm | 19 +- .../modules/centreon/autodiscovery/class.pm | 4 +- .../modules/centreon/autodiscovery/hooks.pm | 19 +- .../gorgone/modules/centreon/engine/class.pm | 3 +- .../gorgone/modules/centreon/engine/hooks.pm | 21 +- .../gorgone/modules/centreon/judge/class.pm | 4 +- .../gorgone/modules/centreon/judge/hooks.pm | 17 - .../modules/centreon/legacycmd/class.pm | 4 +- .../modules/centreon/legacycmd/hooks.pm | 19 +- .../gorgone/modules/centreon/nodes/class.pm | 4 +- .../gorgone/modules/centreon/nodes/hooks.pm | 19 +- .../modules/centreon/statistics/class.pm | 4 +- .../modules/centreon/statistics/hooks.pm | 21 +- gorgone/gorgone/modules/core/action/class.pm | 6 +- gorgone/gorgone/modules/core/action/hooks.pm | 19 +- gorgone/gorgone/modules/core/cron/class.pm | 10 +- gorgone/gorgone/modules/core/cron/hooks.pm | 19 +- .../gorgone/modules/core/dbcleaner/class.pm | 4 +- .../gorgone/modules/core/dbcleaner/hooks.pm | 17 - .../gorgone/modules/core/httpserver/hooks.pm | 17 - .../modules/core/httpserverng/class.pm | 698 ++++++++++++++++++ .../modules/core/httpserverng/hooks.pm | 167 +++++ .../gorgone/modules/core/pipeline/class.pm | 4 +- .../gorgone/modules/core/pipeline/hooks.pm | 17 - .../gorgone/modules/core/register/class.pm | 4 +- .../gorgone/modules/core/register/hooks.pm | 19 +- .../gorgone/modules/plugins/newtest/hooks.pm | 2 +- gorgone/gorgone/standard/library.pm | 12 + 35 files changed, 948 insertions(+), 273 deletions(-) create mode 100644 gorgone/gorgone/modules/core/httpserverng/class.pm create mode 100644 gorgone/gorgone/modules/core/httpserverng/hooks.pm diff --git a/gorgone/TODO b/gorgone/TODO index b4826becdef..da16b751a13 100644 --- a/gorgone/TODO +++ b/gorgone/TODO @@ -1,3 +1,2 @@ - gorgone-newtest: don't use centcore.cmd. use ssh system. - Add redis backend to store logs (we could disable synclog in redis mode) -- Add a websocket module to call gorgone diff --git a/gorgone/contrib/mojolicious_client.pl b/gorgone/contrib/mojolicious_client.pl index 84872a822c1..79c349ee3ce 100644 --- a/gorgone/contrib/mojolicious_client.pl +++ b/gorgone/contrib/mojolicious_client.pl @@ -3,8 +3,9 @@ use Mojo::UserAgent; my $ua = Mojo::UserAgent->new(); +# ws or wss $ua->websocket( - 'wss://127.0.0.1:3000/echo' => sub { + 'ws://127.0.0.1:8086/' => sub { my ($ua, $tx) = @_; print "error: ", $tx->res->error->{message}, "\n" if $tx->res->error; @@ -20,12 +21,14 @@ message => sub { my ($tx, $msg) = @_; print "WebSocket message: $msg\n"; - #$tx->finish; } ); - $tx->send('Hi!'); + + $tx->send({json => { username => 'admin', password => 'plop' } }); + $tx->send({json => { method => 'POST', uri => '/core/action/command', userdata => 'command1', data => [ { command => 'ls' } ] } }); } ); +$ua->inactivity_timeout(120); Mojo::IOLoop->start() unless (Mojo::IOLoop->is_running); exit(0); diff --git a/gorgone/contrib/mojolicious_server.pl b/gorgone/contrib/mojolicious_server.pl index ceb79b30ef4..3f0c60d8026 100644 --- a/gorgone/contrib/mojolicious_server.pl +++ b/gorgone/contrib/mojolicious_server.pl @@ -49,7 +49,7 @@ sub sigalrm_handler get '/' => sub { my $self = shift; - + $self->render(json => { message => 'ok' }) if $self->basic_auth( "Realm Name" => { diff --git a/gorgone/gorgone/class/module.pm b/gorgone/gorgone/class/module.pm index 9240406bb49..c66f7d126a0 100644 --- a/gorgone/gorgone/class/module.pm +++ b/gorgone/gorgone/class/module.pm @@ -148,6 +148,13 @@ sub json_decode { my $container = ''; $container = 'container ' . $self->{container_id} . ': ' if (defined($self->{container_id})); $self->{logger}->writeLogError("[$self->{module_id}] ${container}$options{method} - cannot decode json: $@"); + if (defined($options{token})) { + $self->send_log( + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'cannot decode json' } + ); + } return 1; } diff --git a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm index 670ab53a83e..3c51a2ffe19 100644 --- a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm +++ b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm @@ -634,7 +634,9 @@ sub event { if ((my $method = $connector->can('action_' . lc($1)))) { $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; my ($action, $token) = ($1, $2); - my $data = JSON::XS->new->utf8->decode($3); + my ($rv, $data) = $connector->json_decode(argument => $3, token => $token); + next if ($rv); + $method->($connector, token => $token, data => $data); } } diff --git a/gorgone/gorgone/modules/centreon/anomalydetection/hooks.pm b/gorgone/gorgone/modules/centreon/anomalydetection/hooks.pm index 8ccf4f9976e..27c2e256aaf 100644 --- a/gorgone/gorgone/modules/centreon/anomalydetection/hooks.pm +++ b/gorgone/gorgone/modules/centreon/anomalydetection/hooks.pm @@ -22,7 +22,6 @@ package gorgone::modules::centreon::anomalydetection::hooks; use warnings; use strict; -use JSON::XS; use gorgone::class::core; use gorgone::modules::centreon::anomalydetection::class; use gorgone::standard::constants qw(:all); @@ -30,7 +29,7 @@ use gorgone::standard::constants qw(:all); use constant NAMESPACE => 'centreon'; use constant NAME => 'anomalydetection'; use constant EVENTS => [ - { event => 'CENTREONADREADY' }, + { event => 'CENTREONADREADY' } ]; my $config_core; @@ -58,22 +57,6 @@ sub init { sub routing { my (%options) = @_; - - my $data; - eval { - $data = JSON::XS->new->utf8->decode($options{data}); - }; - if ($@) { - $options{logger}->writeLogError("[anomalydetection] Cannot decode json data: $@"); - gorgone::standard::library::add_history( - dbh => $options{dbh}, - code => GORGONE_ACTION_FINISH_KO, - token => $options{token}, - data => { message => 'gorgone-anomalydetection: cannot decode json' }, - json_encode => 1 - ); - return undef; - } if ($options{action} eq 'CENTREONADREADY') { $process->{ready} = 1; @@ -96,7 +79,7 @@ sub routing { identity => 'gorgone-anomalydetection', action => $options{action}, data => $options{data}, - token => $options{token}, + token => $options{token} ); } diff --git a/gorgone/gorgone/modules/centreon/audit/class.pm b/gorgone/gorgone/modules/centreon/audit/class.pm index e94a3470e02..b13d6f222f6 100644 --- a/gorgone/gorgone/modules/centreon/audit/class.pm +++ b/gorgone/gorgone/modules/centreon/audit/class.pm @@ -290,7 +290,9 @@ sub event { if ((my $method = $connector->can('action_' . lc($1)))) { $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; my ($action, $token) = ($1, $2); - my $data = JSON::XS->new->utf8->decode($3); + my ($rv, $data) = $connector->json_decode(argument => $3, token => $token); + next if ($rv); + $method->($connector, token => $token, data => $data); } } diff --git a/gorgone/gorgone/modules/centreon/audit/hooks.pm b/gorgone/gorgone/modules/centreon/audit/hooks.pm index 7b5d427f206..c74c030db11 100644 --- a/gorgone/gorgone/modules/centreon/audit/hooks.pm +++ b/gorgone/gorgone/modules/centreon/audit/hooks.pm @@ -25,7 +25,6 @@ use strict; use gorgone::class::core; use gorgone::modules::centreon::audit::class; use gorgone::standard::constants qw(:all); -use JSON::XS; use constant NAMESPACE => 'centreon'; use constant NAME => 'audit'; @@ -60,22 +59,6 @@ sub init { sub routing { my (%options) = @_; - - my $data; - eval { - $data = JSON::XS->new->utf8->decode($options{data}); - }; - if ($@) { - $options{logger}->writeLogError("[audit] Cannot decode json data: $@"); - gorgone::standard::library::add_history( - dbh => $options{dbh}, - code => GORGONE_ACTION_FINISH_KO, - token => $options{token}, - data => { message => 'gorgone-audit: cannot decode json' }, - json_encode => 1 - ); - return undef; - } if ($options{action} eq 'CENTREONAUDITREADY') { $audit->{ready} = 1; @@ -98,7 +81,7 @@ sub routing { identity => 'gorgone-audit', action => $options{action}, data => $options{data}, - token => $options{token}, + token => $options{token} ); } diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm index d601b97104e..b336c553824 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm @@ -1067,7 +1067,9 @@ sub event { if ((my $method = $connector->can('action_' . lc($1)))) { $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; my ($action, $token) = ($1, $2); - my $data = JSON::XS->new->utf8->decode($3); + my ($rv, $data) = $connector->json_decode(argument => $3, token => $token); + next if ($rv); + $method->($connector, token => $token, data => $data); } } diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm b/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm index e2e65f26242..1bf86604b4c 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm @@ -22,7 +22,6 @@ package gorgone::modules::centreon::autodiscovery::hooks; use warnings; use strict; -use JSON::XS; use gorgone::class::core; use gorgone::modules::centreon::autodiscovery::class; use gorgone::standard::constants qw(:all); @@ -65,22 +64,6 @@ sub init { sub routing { my (%options) = @_; - my $data; - eval { - $data = JSON::XS->new->utf8->decode($options{data}); - }; - if ($@) { - $options{logger}->writeLogError("[autodiscovery] Cannot decode json data: $@"); - gorgone::standard::library::add_history( - dbh => $options{dbh}, - code => GORGONE_ACTION_FINISH_KO, - token => $options{token}, - data => { msg => 'gorgoneautodiscovery: cannot decode json' }, - json_encode => 1 - ); - return undef; - } - if ($options{action} eq 'AUTODISCOVERYREADY') { $autodiscovery->{ready} = 1; return undef; @@ -102,7 +85,7 @@ sub routing { identity => 'gorgone-autodiscovery', action => $options{action}, data => $options{data}, - token => $options{token}, + token => $options{token} ); } diff --git a/gorgone/gorgone/modules/centreon/engine/class.pm b/gorgone/gorgone/modules/centreon/engine/class.pm index a5cdfb48fba..96122a8a9fc 100644 --- a/gorgone/gorgone/modules/centreon/engine/class.pm +++ b/gorgone/gorgone/modules/centreon/engine/class.pm @@ -236,7 +236,8 @@ sub create_child { $options{message} =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; my ($action, $token) = ($1, $2); - my $data = JSON::XS->new->utf8->decode($3); + my ($rv, $data) = $self->json_decode(argument => $3, token => $token); + return undef if ($rv); if ($action =~ /^BCAST.*/) { if ((my $method = $self->can('action_' . lc($action)))) { diff --git a/gorgone/gorgone/modules/centreon/engine/hooks.pm b/gorgone/gorgone/modules/centreon/engine/hooks.pm index e52c9490bca..3f98e9f6dc8 100644 --- a/gorgone/gorgone/modules/centreon/engine/hooks.pm +++ b/gorgone/gorgone/modules/centreon/engine/hooks.pm @@ -22,7 +22,6 @@ package gorgone::modules::centreon::engine::hooks; use warnings; use strict; -use JSON::XS; use gorgone::class::core; use gorgone::standard::constants qw(:all); use gorgone::modules::centreon::engine::class; @@ -31,7 +30,7 @@ use constant NAMESPACE => 'centreon'; use constant NAME => 'engine'; use constant EVENTS => [ { event => 'ENGINEREADY' }, - { event => 'ENGINECOMMAND', uri => '/command', method => 'POST' }, + { event => 'ENGINECOMMAND', uri => '/command', method => 'POST' } ]; my $config_core; @@ -56,22 +55,6 @@ sub init { sub routing { my (%options) = @_; - - my $data; - eval { - $data = JSON::XS->new->utf8->decode($options{data}); - }; - if ($@) { - $options{logger}->writeLogError("[engine] Cannot decode json data: $@"); - gorgone::standard::library::add_history( - dbh => $options{dbh}, - code => GORGONE_ACTION_FINISH_KO, - token => $options{token}, - data => { msg => 'gorgoneengine: cannot decode json' }, - json_encode => 1 - ); - return undef; - } if ($options{action} eq 'ENGINEREADY') { $engine->{ready} = 1; @@ -94,7 +77,7 @@ sub routing { identity => 'gorgone-engine', action => $options{action}, data => $options{data}, - token => $options{token}, + token => $options{token} ); } diff --git a/gorgone/gorgone/modules/centreon/judge/class.pm b/gorgone/gorgone/modules/centreon/judge/class.pm index d34cfce166e..15cbaa43206 100644 --- a/gorgone/gorgone/modules/centreon/judge/class.pm +++ b/gorgone/gorgone/modules/centreon/judge/class.pm @@ -394,7 +394,9 @@ sub event { if ((my $method = $connector->can('action_' . lc($1)))) { $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; my ($action, $token) = ($1, $2); - my $data = JSON::XS->new->utf8->decode($3); + my ($rv, $data) = $connector->json_decode(argument => $3, token => $token); + next if ($rv); + $method->($connector, token => $token, data => $data); } } diff --git a/gorgone/gorgone/modules/centreon/judge/hooks.pm b/gorgone/gorgone/modules/centreon/judge/hooks.pm index 8bac6ebe583..25d1f4835c0 100644 --- a/gorgone/gorgone/modules/centreon/judge/hooks.pm +++ b/gorgone/gorgone/modules/centreon/judge/hooks.pm @@ -22,7 +22,6 @@ package gorgone::modules::centreon::judge::hooks; use warnings; use strict; -use JSON::XS; use gorgone::class::core; use gorgone::modules::centreon::judge::class; use gorgone::standard::constants qw(:all); @@ -61,22 +60,6 @@ sub init { sub routing { my (%options) = @_; - - my $data; - eval { - $data = JSON::XS->new->utf8->decode($options{data}); - }; - if ($@) { - $options{logger}->writeLogError("[judge] Cannot decode json data: $@"); - gorgone::standard::library::add_history( - dbh => $options{dbh}, - code => GORGONE_ACTION_FINISH_KO, - token => $options{token}, - data => { message => 'gorgone-judge cannot decode json' }, - json_encode => 1 - ); - return undef; - } if ($options{action} eq 'JUDGEREADY') { $judge->{ready} = 1; diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index f6a73913a00..f98b2cedfc0 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -756,7 +756,9 @@ sub event { if ((my $method = $connector->can('action_' . lc($1)))) { $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; my ($action, $token) = ($1, $2); - my $data = JSON::XS->new->utf8->decode($3); + my ($rv, $data) = $connector->json_decode(argument => $3, token => $token); + next if ($rv); + $method->($connector, token => $token, data => $data); } } diff --git a/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm b/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm index cc293bfdf53..73638c07017 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm @@ -25,7 +25,6 @@ use strict; use gorgone::class::core; use gorgone::modules::centreon::legacycmd::class; use gorgone::standard::constants qw(:all); -use JSON::XS; use constant NAMESPACE => 'centreon'; use constant NAME => 'legacycmd'; @@ -63,22 +62,6 @@ sub init { sub routing { my (%options) = @_; - my $data; - eval { - $data = JSON::XS->new->utf8->decode($options{data}); - }; - if ($@) { - $options{logger}->writeLogError("[legacycmd] Cannot decode json data: $@"); - gorgone::standard::library::add_history( - dbh => $options{dbh}, - code => GORGONE_ACTION_FINISH_KO, - token => $options{token}, - data => { message => 'gorgone-legacycmd: cannot decode json' }, - json_encode => 1 - ); - return undef; - } - if ($options{action} eq 'LEGACYCMDREADY') { $legacycmd->{ready} = 1; return undef; @@ -100,7 +83,7 @@ sub routing { identity => 'gorgone-legacycmd', action => $options{action}, data => $options{data}, - token => $options{token}, + token => $options{token} ); } diff --git a/gorgone/gorgone/modules/centreon/nodes/class.pm b/gorgone/gorgone/modules/centreon/nodes/class.pm index 1964e3a885c..9f29949ba79 100644 --- a/gorgone/gorgone/modules/centreon/nodes/class.pm +++ b/gorgone/gorgone/modules/centreon/nodes/class.pm @@ -218,7 +218,9 @@ sub event { if ((my $method = $connector->can('action_' . lc($1)))) { $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; my ($action, $token) = ($1, $2); - my $data = JSON::XS->new->utf8->decode($3); + my ($rv, $data) = $connector->json_decode(argument => $3, token => $token); + next if ($rv); + $method->($connector, token => $token, data => $data); } } diff --git a/gorgone/gorgone/modules/centreon/nodes/hooks.pm b/gorgone/gorgone/modules/centreon/nodes/hooks.pm index 489a3bb1cfe..309acc0643d 100644 --- a/gorgone/gorgone/modules/centreon/nodes/hooks.pm +++ b/gorgone/gorgone/modules/centreon/nodes/hooks.pm @@ -22,7 +22,6 @@ package gorgone::modules::centreon::nodes::hooks; use warnings; use strict; -use JSON::XS; use gorgone::class::core; use gorgone::modules::centreon::nodes::class; use gorgone::standard::constants qw(:all); @@ -60,22 +59,6 @@ sub init { sub routing { my (%options) = @_; - my $data; - eval { - $data = JSON::XS->new->utf8->decode($options{data}); - }; - if ($@) { - $options{logger}->writeLogError("[nodes] Cannot decode json data: $@"); - gorgone::standard::library::add_history( - dbh => $options{dbh}, - code => GORGONE_ACTION_FINISH_KO, - token => $options{token}, - data => { message => 'gorgonenodes: cannot decode json' }, - json_encode => 1 - ); - return undef; - } - if ($options{action} eq 'CENTREONNODESREADY') { $nodes->{ready} = 1; return undef; @@ -97,7 +80,7 @@ sub routing { identity => 'gorgone-nodes', action => $options{action}, data => $options{data}, - token => $options{token}, + token => $options{token} ); } diff --git a/gorgone/gorgone/modules/centreon/statistics/class.pm b/gorgone/gorgone/modules/centreon/statistics/class.pm index 4ed15d14704..e7976f9669f 100644 --- a/gorgone/gorgone/modules/centreon/statistics/class.pm +++ b/gorgone/gorgone/modules/centreon/statistics/class.pm @@ -592,7 +592,9 @@ sub event { if ((my $method = $connector->can('action_' . lc($1)))) { $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; my ($action, $token) = ($1, $2); - my $data = JSON::XS->new->utf8->decode($3); + my ($rv, $data) = $connector->json_decode(argument => $3, token => $token); + next if ($rv); + $method->($connector, token => $token, data => $data); } } diff --git a/gorgone/gorgone/modules/centreon/statistics/hooks.pm b/gorgone/gorgone/modules/centreon/statistics/hooks.pm index 1629ece065a..f862b23a950 100644 --- a/gorgone/gorgone/modules/centreon/statistics/hooks.pm +++ b/gorgone/gorgone/modules/centreon/statistics/hooks.pm @@ -22,7 +22,6 @@ package gorgone::modules::centreon::statistics::hooks; use warnings; use strict; -use JSON::XS; use gorgone::class::core; use gorgone::standard::constants qw(:all); use gorgone::modules::centreon::statistics::class; @@ -33,7 +32,7 @@ use constant EVENTS => [ { event => 'STATISTICSREADY' }, { event => 'STATISTICSLISTENER' }, { event => 'BROKERSTATS', uri => '/broker', method => 'GET' }, - { event => 'ENGINESTATS', uri => '/engine', method => 'GET' }, + { event => 'ENGINESTATS', uri => '/engine', method => 'GET' } ]; my $config_core; @@ -73,22 +72,6 @@ sub init { sub routing { my (%options) = @_; - my $data; - eval { - $data = JSON::XS->new->utf8->decode($options{data}); - }; - if ($@) { - $options{logger}->writeLogError("[statistics] Cannot decode json data: $@"); - gorgone::standard::library::add_history( - dbh => $options{dbh}, - code => GORGONE_ACTION_FINISH_KO, - token => $options{token}, - data => { msg => 'gorgonestatistics: cannot decode json' }, - json_encode => 1 - ); - return undef; - } - if ($options{action} eq 'STATISTICSREADY') { $statistics->{ready} = 1; return undef; @@ -110,7 +93,7 @@ sub routing { identity => 'gorgone-statistics', action => $options{action}, data => $options{data}, - token => $options{token}, + token => $options{token} ); } diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index 1e40e13c378..813efbb2cc0 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -445,12 +445,14 @@ sub event { $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; my ($action, $token) = ($1, $2); - my $data = JSON::XS->new->utf8->decode($3); + my ($rv, $data) = $connector->json_decode(argument => $3, token => $token); + next if ($rv); + if (defined($data->{parameters}->{no_fork})) { if ((my $method = $connector->can('action_' . lc($action)))) { $method->($connector, token => $token, data => $data); } - } else{ + } else { $connector->create_child(action => $action, token => $token, data => $data); } } diff --git a/gorgone/gorgone/modules/core/action/hooks.pm b/gorgone/gorgone/modules/core/action/hooks.pm index 817e36a1a1e..d19ffc9357a 100644 --- a/gorgone/gorgone/modules/core/action/hooks.pm +++ b/gorgone/gorgone/modules/core/action/hooks.pm @@ -25,7 +25,6 @@ use strict; use gorgone::class::core; use gorgone::modules::core::action::class; use gorgone::standard::constants qw(:all); -use JSON::XS; use constant NAMESPACE => 'core'; use constant NAME => 'action'; @@ -56,22 +55,6 @@ sub init { sub routing { my (%options) = @_; - - my $data; - eval { - $data = JSON::XS->new->utf8->decode($options{data}); - }; - if ($@) { - $options{logger}->writeLogError("[action] Cannot decode json data: $@"); - gorgone::standard::library::add_history( - dbh => $options{dbh}, - code => GORGONE_ACTION_FINISH_KO, - token => $options{token}, - data => { msg => 'gorgoneaction: cannot decode json' }, - json_encode => 1 - ); - return undef; - } if ($options{action} eq 'ACTIONREADY') { $action->{ready} = 1; @@ -94,7 +77,7 @@ sub routing { identity => 'gorgone-action', action => $options{action}, data => $options{data}, - token => $options{token}, + token => $options{token} ); } diff --git a/gorgone/gorgone/modules/core/cron/class.pm b/gorgone/gorgone/modules/core/cron/class.pm index 83ec673ecee..a9fc1d21b15 100644 --- a/gorgone/gorgone/modules/core/cron/class.pm +++ b/gorgone/gorgone/modules/core/cron/class.pm @@ -378,17 +378,21 @@ sub event { $connector->{logger}->writeLogDebug("[cron] Event: $message"); if ($message =~ /^\[ACK\]\s+\[(.*?)\]\s+(.*)$/m) { my $token = $1; - my $data = JSON::XS->new->utf8->decode($2); + my ($rv, $data) = $connector->json_decode(argument => $2, token => $token); + next if ($rv); + $connector->{ack} = { token => $token, - data => $data, + data => $data }; } else { $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; if ((my $method = $connector->can('action_' . lc($1)))) { $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; my ($action, $token) = ($1, $2); - my $data = JSON::XS->new->utf8->decode($3); + my ($rv, $data) = $connector->json_decode(argument => $3, token => $token); + next if ($rv); + $method->($connector, token => $token, data => $data); } } diff --git a/gorgone/gorgone/modules/core/cron/hooks.pm b/gorgone/gorgone/modules/core/cron/hooks.pm index 6a87eb14974..75bc1d57cfd 100644 --- a/gorgone/gorgone/modules/core/cron/hooks.pm +++ b/gorgone/gorgone/modules/core/cron/hooks.pm @@ -25,7 +25,6 @@ use strict; use gorgone::class::core; use gorgone::modules::core::cron::class; use gorgone::standard::constants qw(:all); -use JSON::XS; use constant NAMESPACE => 'core'; use constant NAME => 'cron'; @@ -58,22 +57,6 @@ sub init { sub routing { my (%options) = @_; - - my $data; - eval { - $data = JSON::XS->new->utf8->decode($options{data}); - }; - if ($@) { - $options{logger}->writeLogError("[cron] Cannot decode json data: $@"); - gorgone::standard::library::add_history( - dbh => $options{dbh}, - code => GORGONE_ACTION_FINISH_KO, - token => $options{token}, - data => { message => 'gorgonecron: cannot decode json' }, - json_encode => 1 - ); - return undef; - } if ($options{action} eq 'CRONREADY') { $cron->{ready} = 1; @@ -96,7 +79,7 @@ sub routing { identity => 'gorgone-cron', action => $options{action}, data => $options{data}, - token => $options{token}, + token => $options{token} ); } diff --git a/gorgone/gorgone/modules/core/dbcleaner/class.pm b/gorgone/gorgone/modules/core/dbcleaner/class.pm index f6a1ca9001e..26c1aff0c57 100644 --- a/gorgone/gorgone/modules/core/dbcleaner/class.pm +++ b/gorgone/gorgone/modules/core/dbcleaner/class.pm @@ -160,7 +160,9 @@ sub event { if ((my $method = $connector->can('action_' . lc($1)))) { $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; my ($action, $token) = ($1, $2); - my $data = JSON::XS->new->utf8->decode($3); + my ($rv, $data) = $connector->json_decode(argument => $3, token => $token); + next if ($rv); + $method->($connector, token => $token, data => $data); } } diff --git a/gorgone/gorgone/modules/core/dbcleaner/hooks.pm b/gorgone/gorgone/modules/core/dbcleaner/hooks.pm index a661252f420..63feac14528 100644 --- a/gorgone/gorgone/modules/core/dbcleaner/hooks.pm +++ b/gorgone/gorgone/modules/core/dbcleaner/hooks.pm @@ -22,7 +22,6 @@ package gorgone::modules::core::dbcleaner::hooks; use warnings; use strict; -use JSON::XS; use gorgone::class::core; use gorgone::modules::core::dbcleaner::class; use gorgone::standard::constants qw(:all); @@ -65,22 +64,6 @@ sub init { sub routing { my (%options) = @_; - - my $data; - eval { - $data = JSON::XS->new->utf8->decode($options{data}); - }; - if ($@) { - $options{logger}->writeLogError("[dbcleaner] Cannot decode json data: $@"); - gorgone::standard::library::add_history( - dbh => $options{dbh}, - code => GORGONE_ACTION_FINISH_KO, - token => $options{token}, - data => { message => 'gorgonedbcleaner cannot decode json' }, - json_encode => 1 - ); - return undef; - } if ($options{action} eq 'DBCLEANERREADY') { $dbcleaner->{ready} = 1; diff --git a/gorgone/gorgone/modules/core/httpserver/hooks.pm b/gorgone/gorgone/modules/core/httpserver/hooks.pm index 12020b6dd42..242e383796d 100644 --- a/gorgone/gorgone/modules/core/httpserver/hooks.pm +++ b/gorgone/gorgone/modules/core/httpserver/hooks.pm @@ -25,7 +25,6 @@ use strict; use gorgone::class::core; use gorgone::modules::core::httpserver::class; use gorgone::standard::constants qw(:all); -use JSON::XS; use constant NAMESPACE => 'core'; use constant NAME => 'httpserver'; @@ -68,22 +67,6 @@ sub init { sub routing { my (%options) = @_; - - my $data; - eval { - $data = JSON::XS->new->utf8->decode($options{data}); - }; - if ($@) { - $options{logger}->writeLogError("[httpserver] Cannot decode json data: $@"); - gorgone::standard::library::add_history( - dbh => $options{dbh}, - code => GORGONE_ACTION_FINISH_KO, - token => $options{token}, - data => { message => 'gorgonehttpserver: cannot decode json' }, - json_encode => 1 - ); - return undef; - } if ($options{action} eq 'HTTPSERVERREADY') { $httpserver->{ready} = 1; diff --git a/gorgone/gorgone/modules/core/httpserverng/class.pm b/gorgone/gorgone/modules/core/httpserverng/class.pm new file mode 100644 index 00000000000..fc70868cc1a --- /dev/null +++ b/gorgone/gorgone/modules/core/httpserverng/class.pm @@ -0,0 +1,698 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::core::httpserverng::class; + +use base qw(gorgone::class::module); + +use strict; +use warnings; +use gorgone::standard::library; +use gorgone::standard::constants qw(:all); +use gorgone::standard::misc; +use ZMQ::Constants qw(:all); +use ZMQ::LibZMQ4; +use Mojolicious::Lite; +use Mojo::Server::Daemon; +use Authen::Simple::Password; +use IO::Socket::SSL; +use IO::Handle; +use JSON::XS; + +my %handlers = (TERM => {}, HUP => {}); +my ($connector); + +plugin('basic_auth_plus'); + +websocket '/' => sub { + my $mojo = shift; + + $connector->{logger}->writeLogDebug('[httpserverng] websocket client connected: ' . $mojo->tx->connection); + + if ($connector->{allowed_hosts_enabled} == 1) { + if ($connector->check_allowed_host(peer_addr => $mojo->tx->remote_address) == 0) { + $connector->{logger}->writeLogError("[httpserverng] " . $mojo->tx->remote_address . " Unauthorized"); + $mojo->tx->send({json => { + code => 401, + message => 'unauthorized', + }}); + return ; + } + } + + $connector->{ws_clients}->{ $mojo->tx->connection } = { + tx => $mojo->tx, + logged => 0, + last_update => time(), + tokens => {} + }; + + $mojo->on(message => sub { + my ($mojo, $msg) = @_; + + $connector->{ws_clients}->{ $mojo->tx->connection }->{last_update} = time(); + + my $content; + eval { + $content = JSON::XS->new->utf8->decode($msg); + }; + if ($@) { + $connector->close_websocket( + code => 500, + message => 'decode error: unsupported format', + ws_id => $mojo->tx->connection + ); + return ; + } + + my $rv = $connector->is_logged_websocket(ws_id => $mojo->tx->connection, content => $content); + return if ($rv != 1); + + $connector->api_root_ws(ws_id => $mojo->tx->connection, content => $content); + }); + + $mojo->on(finish => sub { + my ($mojo, $code, $reason) = @_; + + $connector->{logger}->writeLogDebug('[httpserverng] websocket client disconnected: ' . $mojo->tx->connection); + $connector->clean_websocket(ws_id => $mojo->tx->connection, finish => 1); + }); +}; + +patch '/*' => sub { + my $mojo = shift; + + $connector->api_call( + mojo => $mojo, + method => 'PATCH' + ); +}; + +post '/*' => sub { + my $mojo = shift; + + $connector->api_call( + mojo => $mojo, + method => 'POST' + ); +}; + +get '/*' => sub { + my $mojo = shift; + + $connector->api_call( + mojo => $mojo, + method => 'GET' + ); +}; + +sub construct { + my ($class, %options) = @_; + $connector = $class->SUPER::new(%options); + bless $connector, $class; + + $connector->{api_endpoints} = $options{api_endpoints}; + $connector->{auth_enabled} = (defined($connector->{config}->{auth}->{enabled}) && $connector->{config}->{auth}->{enabled} eq 'true') ? 1 : 0; + $connector->{allowed_hosts_enabled} = (defined($connector->{config}->{allowed_hosts}->{enabled}) && $connector->{config}->{allowed_hosts}->{enabled} eq 'true') ? 1 : 0; + $connector->{clients} = {}; + $connector->{token_watch} = {}; + $connector->{ws_clients} = {}; + + if (gorgone::standard::misc::mymodule_load( + logger => $connector->{logger}, + module => 'NetAddr::IP', + error_msg => "[httpserverng] -class- cannot load module 'NetAddr::IP'. Cannot use allowed_hosts configuration.") + ) { + $connector->{allowed_hosts_enabled} = 0; + } + + $connector->set_signal_handlers(); + return $connector; +} + +sub set_signal_handlers { + my $self = shift; + + $SIG{TERM} = \&class_handle_TERM; + $handlers{TERM}->{$self} = sub { $self->handle_TERM() }; + $SIG{HUP} = \&class_handle_HUP; + $handlers{HUP}->{$self} = sub { $self->handle_HUP() }; +} + +sub handle_HUP { + my $self = shift; + $self->{reload} = 0; +} + +sub handle_TERM { + my $self = shift; + $self->{logger}->writeLogDebug("[httpserver] $$ Receiving order to stop..."); + $self->{stop} = 1; +} + +sub class_handle_TERM { + foreach (keys %{$handlers{TERM}}) { + &{$handlers{TERM}->{$_}}(); + } +} + +sub class_handle_HUP { + foreach (keys %{$handlers{HUP}}) { + &{$handlers{HUP}->{$_}}(); + } +} + +sub check_allowed_host { + my ($self, %options) = @_; + + my $subnet = NetAddr::IP->new($options{peer_addr} . '/32'); + foreach (@{$self->{peer_subnets}}) { + return 1 if ($_->contains($subnet)); + } + + return 0; +} + +sub load_peer_subnets { + my ($self, %options) = @_; + + return if ($self->{allowed_hosts_enabled} == 0); + + $self->{peer_subnets} = []; + return if (!defined($connector->{config}->{allowed_hosts}->{subnets})); + + foreach (@{$self->{config}->{allowed_hosts}->{subnets}}) { + my $subnet = NetAddr::IP->new($_); + if (!defined($subnet)) { + $self->{logger}->writeLogError("[httpserverng] Cannot load subnet: $_"); + next; + } + + push @{$self->{peer_subnets}}, $subnet; + } +} + +sub run { + my ($self, %options) = @_; + + $self->load_peer_subnets(); + + my $listen = 'reuse=1'; + if ($self->{config}->{ssl} eq 'true') { + if (!defined($self->{config}->{ssl_cert_file}) || $self->{config}->{ssl_cert_file} eq '' || + -r "$self->{config}->{ssl_cert_file}") { + $connector->{logger}->writeLogError("[httpserverng] cannot read/find ssl-cert-file"); + exit(1); + } + if (!defined($self->{config}->{ssl_key_file}) || $self->{config}->{ssl_key_file} eq '' || + -r "$self->{config}->{ssl_key_file}") { + $connector->{logger}->writeLogError("[httpserverng] cannot read/find ssl-key-file"); + exit(1); + } + $listen .= '&cert=' . $self->{config}->{ssl_cert_file} . '&key=' . $self->{config}->{ssl_key_file}; + } + my $proto = 'http'; + if ($self->{config}->{ssl} eq 'true') { + $proto = 'https'; + if (defined($self->{config}->{passphrase}) && $self->{config}->{passphrase} ne '') { + IO::Socket::SSL::set_defaults(SSL_passwd_cb => sub { return $connector->{config}->{passphrase} } ); + } + } + + # Connect internal + $self->{internal_socket} = gorgone::standard::library::connect_com( + zmq_type => 'ZMQ_DEALER', + name => 'gorgone-httpserverng', + logger => $self->{logger}, + type => $self->{config_core}->{internal_com_type}, + path => $self->{config_core}->{internal_com_path} + ); + $self->send_internal_action( + action => 'HTTPSERVERNGREADY', + data => {} + ); + $self->read_zmq_events(); + + my $socket_fd = gorgone::standard::library::zmq_getfd(socket => $self->{internal_socket}); + my $socket = IO::Handle->new_from_fd($socket_fd, 'r'); + Mojo::IOLoop->singleton->reactor->io($socket => sub { + $connector->read_zmq_events(); + }); + Mojo::IOLoop->singleton->reactor->watch($socket, 1, 0); + + Mojo::IOLoop->singleton->recurring(60 => sub { + $connector->{logger}->writeLogDebug('[httpserverng] recurring timeout loop'); + my $ctime = time(); + foreach my $ws_id (keys %{$connector->{ws_clients}}) { + if (scalar(keys %{$connector->{ws_clients}->{$ws_id}->{tokens}}) <= 0 && ($ctime - $connector->{ws_clients}->{$ws_id}->{last_update}) > 300) { + $connector->{logger}->writeLogDebug('[httpserverng] websocket client timeout reached: ' . $ws_id); + $connector->close_websocket( + code => 500, + message => 'timeout reached', + ws_id => $ws_id + ); + } + } + }); + + app->mode('production'); + my $daemon = Mojo::Server::Daemon->new( + app => app, + listen => [$proto . '://' . $self->{config}->{address} . ':' . $self->{config}->{port} . '?' . $listen] + ); + # more than 2 minutes, need to use async system + $daemon->inactivity_timeout(120); + + #my $loop = Mojo::IOLoop->new(); + #my $reactor = Mojo::Reactor::EV->new(); + #$reactor->io($socket => sub { + # my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); + #}); + #$reactor->watch($socket, 1, 0); + #$loop->reactor($reactor); + #$daemon->ioloop($loop); + + $daemon->run(); + + zmq_close($self->{internal_socket}); + exit(0); +} + +sub read_log_event { + my ($self, %options) = @_; + + my $token = $options{token}; + $token =~ s/-log$//; + my $response = { error => 'no_log', message => 'No log found for token', data => [], token => $token }; + if (defined($options{data})) { + my $content; + eval { + $content = JSON::XS->new->utf8->decode($options{data}); + }; + if ($@) { + $response = { error => 'decode_error', message => 'Cannot decode response' }; + } elsif (defined($content->{data}->{result}) && scalar(@{$content->{data}->{result}}) > 0) { + $response = { + message => 'Logs found', + token => $token, + data => $content->{data}->{result} + }; + } + } + + if (defined($self->{token_watch}->{ $options{token} }->{ws_id})) { + $response->{userdata} = $self->{token_watch}->{ $options{token} }->{userdata}; + $self->{ws_clients}->{ $self->{token_watch}->{ $options{token} }->{ws_id} }->{last_update} = time(); + $self->{ws_clients}->{ $self->{token_watch}->{ $options{token} }->{ws_id} }->{tx}->send({json => $response }); + delete $self->{ws_clients}->{ $self->{token_watch}->{ $options{token} }->{ws_id} }->{tokens}->{ $options{token} }; + } else { + $self->{token_watch}->{ $options{token} }->{mojo}->render(json => $response); + } + delete $self->{token_watch}->{ $options{token} }; +} + +sub read_listener { + my ($self, %options) = @_; + + my $content; + eval { + $content = JSON::XS->new->utf8->decode($options{data}); + }; + if ($@) { + $self->{token_watch}->{ $options{token} }->{mojo}->render(json => { error => 'decode_error', message => 'Cannot decode response' }); + delete $self->{token_watch}->{ $options{token} }; + return ; + } + + push @{$self->{token_watch}->{ $options{token} }->{results}}, $content; + if (defined($self->{token_watch}->{ $options{token} }->{ws_id})) { + $self->{ws_clients}->{ $self->{token_watch}->{ $options{token} }->{ws_id} }->{last_update} = time(); + } + + if ($content->{code} == GORGONE_ACTION_FINISH_KO || $content->{code} == GORGONE_ACTION_FINISH_OK) { + my $json = { data => $self->{token_watch}->{ $options{token} }->{results} }; + if (defined($self->{token_watch}->{ $options{token} }->{internal}) && $content->{code} == GORGONE_ACTION_FINISH_OK) { + $json = $content->{data}; + } + + if (defined($self->{token_watch}->{ $options{token} }->{ws_id})) { + $json->{userdata} = $self->{token_watch}->{ $options{token} }->{userdata}; + $self->{ws_clients}->{ $self->{token_watch}->{ $options{token} }->{ws_id} }->{tx}->send({json => $json }); + delete $self->{ws_clients}->{ $self->{token_watch}->{ $options{token} }->{ws_id} }->{tokens}->{ $options{token} }; + } else { + $self->{token_watch}->{ $options{token} }->{mojo}->render(json => $json); + } + delete $self->{token_watch}->{ $options{token} }; + } +} + +sub read_zmq_events { + my ($self, %options) = @_; + + while (my $events = gorgone::standard::library::zmq_events(socket => $self->{internal_socket})) { + if ($events & ZMQ_POLLIN) { + my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); + $connector->{logger}->writeLogDebug('[httpserverng] zmq message received: ' . $message); + if ($message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m || + $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+(.*)$/m) { + my ($action, $token, $data) = ($1, $2, $3); + if (defined($connector->{token_watch}->{$token})) { + if ($action eq 'HTTPSERVERNGLISTENER') { + $connector->read_listener(token => $token, data => $data); + } elsif ($token =~ /-log$/) { + $connector->read_log_event(token => $token, data => $data); + } + } + } + } else { + last; + } + } +} + +sub api_call { + my ($self, %options) = @_; + + if ($self->{allowed_hosts_enabled} == 1) { + if ($self->check_allowed_host(peer_addr => $options{mojo}->tx->remote_address) == 0) { + $connector->{logger}->writeLogError("[httpserverng] " . $options{mojo}->tx->remote_address . " Unauthorized"); + return $options{mojo}->render(json => { message => 'unauthorized' }, status => 401); + } + } + + if ($self->{auth_enabled} == 1) { + my ($hash_ref, $auth_ok) = $options{mojo}->basic_auth( + 'Realm Name' => { + username => $self->{config}->{auth}->{user}, + password => $self->{config}->{auth}->{password} + } + ); + if (!$auth_ok) { + return $options{mojo}->render(json => { message => 'unauthorized' }, status => 401); + } + } + + my $path = $options{mojo}->tx->req->url->path; + my $names = $options{mojo}->req->params->names(); + my $params = {}; + foreach (@$names) { + $params->{$_} = $options{mojo}->param($_); + } + + my $content = $options{mojo}->req->json(); + + $self->api_root( + mojo => $options{mojo}, + method => $options{method}, + uri => $path, + parameters => $params, + content => $content + ); +} + +sub get_log { + my ($self, %options) = @_; + + if (defined($options{target}) && $options{target} ne '') { + gorgone::standard::library::zmq_send_message( + socket => $self->{internal_socket}, + target => $options{target}, + action => 'GETLOG', + json_encode => 1 + ); + $self->read_zmq_events(); + } + + my $token_log = $options{token} . '-log'; + + if (defined($options{ws_id})) { + $self->{ws_clients}->{ $options{ws_id} }->{tokens}->{$token_log} = 1; + } + $self->{token_watch}->{$token_log} = { + ws_id => $options{ws_id}, + userdata => $options{userdata}, + mojo => $options{mojo} + }; + + gorgone::standard::library::zmq_send_message( + socket => $self->{internal_socket}, + action => 'GETLOG', + token => $token_log, + data => { + token => $options{token}, + %{$options{parameters}} + }, + json_encode => 1 + ); + $self->read_zmq_events(); + + # keep reference tx to avoid "Transaction already destroyed" + $self->{token_watch}->{$token_log}->{tx} = $options{mojo}->render_later()->tx if (!defined($options{ws_id})); +} + +sub call_action { + my ($self, %options) = @_; + + my $action_token = gorgone::standard::library::generate_token(); + + if ($options{async} == 0) { + if (defined($options{ws_id})) { + $self->{ws_clients}->{ $options{ws_id} }->{tokens}->{$action_token} = 1; + } + $self->{token_watch}->{$action_token} = { + ws_id => $options{ws_id}, + userdata => $options{userdata}, + mojo => $options{mojo}, + internal => $options{internal}, + results => [] + }; + + $self->send_internal_action( + action => 'ADDLISTENER', + data => [ + { + identity => 'gorgone-httpserverng', + event => 'HTTPSERVERNGLISTENER', + token => $action_token, + target => $options{target}, + log_pace => 5, + timeout => 110 + } + ] + ); + $self->read_zmq_events(); + } + + $self->send_internal_action( + action => $options{action}, + target => $options{target}, + token => $action_token, + data => $options{data} + ); + $self->read_zmq_events(); + + if ($options{async} == 1) { + $options{mojo}->render(json => { token => $action_token }, status => 200); + } else { + # keep reference tx to avoid "Transaction already destroyed" + $self->{token_watch}->{$action_token}->{tx} = $options{mojo}->render_later()->tx if (!defined($options{ws_id})); + } +} + +sub is_logged_websocket { + my ($self, %options) = @_; + + return 1 if ($self->{ws_clients}->{ $options{ws_id} }->{logged} == 1); + + if ($self->{auth_enabled} == 1) { + if (!defined($options{content}->{username}) || $options{content}->{username} eq '' || + !defined($options{content}->{password}) || $options{content}->{password} eq '') { + $self->close_websocket( + code => 500, + message => 'please set username/password', + ws_id => $options{ws_id} + ); + return 0; + } + + unless ($options{content}->{username} eq $self->{config}->{auth}->{user} && + Authen::Simple::Password->check($options{content}->{password}, $self->{config}->{auth}->{password})) { + $self->close_websocket( + code => 401, + message => 'unauthorized user', + ws_id => $options{ws_id} + ); + return 0; + } + } + + $self->{ws_clients}->{ $options{ws_id} }->{logged} = 1; + return 2; +} + +sub clean_websocket { + my ($self, %options) = @_; + + return if (!defined($self->{ws_clients}->{ $options{ws_id} })); + + $self->{ws_clients}->{ $options{ws_id} }->{tx}->finish() if (!defined($options{finish})); + foreach (keys %{$self->{ws_clients}->{ $options{ws_id} }->{tokens}}) { + delete $self->{token_watch}->{$_}; + } + delete $self->{ws_clients}->{ $options{ws_id} }; +} + +sub close_websocket { + my ($self, %options) = @_; + + $self->{ws_clients}->{ $options{ws_id} }->{tx}->send({json => { + code => $options{code}, + message => $options{message} + }}); + $self->clean_websocket(ws_id => $options{ws_id}); +} + +sub api_root_ws { + my ($self, %options) = @_; + + use Data::Dumper; print Data::Dumper::Dumper($options{content}); + + if (!defined($options{content}->{method})) { + $self->{ws_clients}->{ $options{ws_id} }->{tx}->send({json => { + code => 500, + message => 'unknown method', + userdata => $options{content}->{userdata} + }}); + return ; + } + if (!defined($options{content}->{uri})) { + $self->{ws_clients}->{ $options{ws_id} }->{tx}->send({json => { + code => 500, + message => 'unknown uri', + userdata => $options{content}->{userdata} + }}); + return ; + } + + $self->{logger}->writeLogInfo("[api] Requesting '" . $options{content}->{uri} . "' [" . $options{content}->{method} . "]"); + + if ($options{content}->{method} eq 'GET' && $options{content}->{uri} =~ /^\/api\/log\/?$/) { + $self->get_log( + ws_id => $options{ws_id}, + userdata => $options{content}->{userdata}, + target => $options{target}, + token => $options{content}->{token}, + parameters => $options{content}->{parameters} + ); + } elsif ($options{content}->{uri} =~ /^\/internal\/(\w+)\/?$/ + && defined($self->{api_endpoints}->{ $options{content}->{method} . '_/internal/' . $1 })) { + $self->call_action( + ws_id => $options{ws_id}, + userdata => $options{content}->{userdata}, + async => 0, + action => $self->{api_endpoints}->{ $options{content}->{method} . '_/internal/' . $1 }, + internal => $1, + target => $options{target}, + data => { + content => $options{content}->{data}, + parameters => $options{content}->{parameters}, + variables => $options{content}->{variable} + } + ); + } elsif ($options{content}->{uri} =~ /^\/(\w+)\/(\w+)\/(\w+)\/?$/ + && defined($self->{api_endpoints}->{ $options{content}->{method} . '_/' . $1 . '/' . $2 . '/' . $3 })) { + $self->call_action( + ws_id => $options{ws_id}, + userdata => $options{content}->{userdata}, + async => 0, + action => $self->{api_endpoints}->{ $options{content}->{method} . '_/' . $1 . '/' . $2 . '/' . $3 }, + target => $options{target}, + data => { + content => $options{content}->{data}, + parameters => $options{content}->{parameters}, + variables => $options{content}->{variable} + } + ); + } else { + $self->{ws_clients}->{ $options{ws_id} }->{tx}->send({json => { + code => 500, + message => 'method not implemented', + userdata => $options{userdata} + }}); + } +} + +sub api_root { + my ($self, %options) = @_; + + $self->{logger}->writeLogInfo("[api] Requesting '" . $options{uri} . "' [" . $options{method} . "]"); + + my $async = 0; + $async = 1 if (defined($options{parameters}->{async}) && $options{parameters}->{async} == 1); + + # async mode: + # provide the token directly and close the connection. need to call GETLOG on the token + # not working with GETLOG + + # listener is used for other case. + + if ($options{method} eq 'GET' && $options{uri} =~ /^\/api\/(nodes\/(\w*)\/)?log\/(.*)$/) { + $self->get_log( + mojo => $options{mojo}, + target => $2, + token => $3, + parameters => $options{parameters} + ); + } elsif ($options{uri} =~ /^\/api\/(nodes\/(\w*)\/)?internal\/(\w+)\/?([\w\/]*?)$/ + && defined($self->{api_endpoints}->{ $options{method} . '_/internal/' . $3 })) { + my @variables = split(/\//, $4); + $self->call_action( + mojo => $options{mojo}, + async => $async, + action => $self->{api_endpoints}->{ $options{method} . '_/internal/' . $3 }, + internal => $3, + target => $2, + data => { + content => $options{content}, + parameters => $options{parameters}, + variables => \@variables + } + ); + } elsif ($options{uri} =~ /^\/api\/(nodes\/(\w*)\/)?(\w+)\/(\w+)\/(\w+)\/?([\w\/]*?)$/ + && defined($self->{api_endpoints}->{ $options{method} . '_/' . $3 . '/' . $4 . '/' . $5 })) { + my @variables = split(/\//, $6); + $self->call_action( + mojo => $options{mojo}, + async => $async, + action => $self->{api_endpoints}->{ $options{method} . '_/' . $3 . '/' . $4 . '/' . $5 }, + target => $2, + data => { + content => $options{content}, + parameters => $options{parameters}, + variables => \@variables + } + ); + } else { + $options{mojo}->render(json => { error => 'method_unknown', message => 'Method not implemented' }, status => 200); + return ; + } +} + +1; diff --git a/gorgone/gorgone/modules/core/httpserverng/hooks.pm b/gorgone/gorgone/modules/core/httpserverng/hooks.pm new file mode 100644 index 00000000000..c0b38ce3e62 --- /dev/null +++ b/gorgone/gorgone/modules/core/httpserverng/hooks.pm @@ -0,0 +1,167 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::core::httpserverng::hooks; + +use warnings; +use strict; +use gorgone::class::core; +use gorgone::modules::core::httpserverng::class; +use gorgone::standard::constants qw(:all); + +use constant NAMESPACE => 'core'; +use constant NAME => 'httpserverng'; +use constant EVENTS => [ + { event => 'HTTPSERVERNGLISTENER' }, + { event => 'HTTPSERVERNGREADY' } +]; + +my $config_core; +my $config; +my $httpserverng = {}; +my $stop = 0; + +sub register { + my (%options) = @_; + + my $loaded = 1; + $config = $options{config}; + $config_core = $options{config_core}; + $config->{address} = defined($config->{address}) && $config->{address} ne '' ? $config->{address} : '0.0.0.0'; + $config->{port} = defined($config->{port}) && $config->{port} =~ /(\d+)/ ? $1 : 8080; + if (defined($config->{auth}->{enabled}) && $config->{auth}->{enabled} eq 'true') { + if (!defined($config->{auth}->{user}) || $config->{auth}->{user} =~ /^\s*$/) { + $options{logger}->writeLogError('[httpserverng] User option mandatory if authentication is enabled'); + $loaded = 0; + } + if (!defined($config->{auth}->{password}) || $config->{auth}->{password} =~ /^\s*$/) { + $options{logger}->writeLogError('[httpserverng] Password option mandatory if authentication is enabled'); + $loaded = 0; + } + } + + return ($loaded, NAMESPACE, NAME, EVENTS); +} + +sub init { + my (%options) = @_; + + create_child(logger => $options{logger}, api_endpoints => $options{api_endpoints}); +} + +sub routing { + my (%options) = @_; + + if ($options{action} eq 'HTTPSERVERNGREADY') { + $httpserverng->{ready} = 1; + return undef; + } + + if (gorgone::class::core::waiting_ready(ready => \$httpserverng->{ready}) == 0) { + gorgone::standard::library::add_history( + dbh => $options{dbh}, + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'gorgone-httpserverng: still no ready' }, + json_encode => 1 + ); + return undef; + } + + gorgone::standard::library::zmq_send_message( + socket => $options{socket}, + identity => 'gorgone-httpserverng', + action => $options{action}, + data => $options{data}, + token => $options{token} + ); +} + +sub gently { + my (%options) = @_; + + $stop = 1; + if (defined($httpserverng->{running}) && $httpserverng->{running} == 1) { + $options{logger}->writeLogDebug("[httpserverng] Send TERM signal $httpserverng->{pid}"); + CORE::kill('TERM', $httpserverng->{pid}); + } +} + +sub kill { + my (%options) = @_; + + if ($httpserverng->{running} == 1) { + $options{logger}->writeLogDebug("[httpserverng] Send KILL signal for pool"); + CORE::kill('KILL', $httpserverng->{pid}); + } +} + +sub kill_internal { + my (%options) = @_; + +} + +sub check { + my (%options) = @_; + + my $count = 0; + foreach my $pid (keys %{$options{dead_childs}}) { + # Not me + next if (!defined($httpserverng->{pid}) || $httpserverng->{pid} != $pid); + + $httpserverng = {}; + delete $options{dead_childs}->{$pid}; + if ($stop == 0) { + create_child(logger => $options{logger}, api_endpoints => $options{api_endpoints}); + } + + last; + } + + $count++ if (defined($httpserverng->{running}) && $httpserverng->{running} == 1); + + return $count; +} + +sub broadcast {} + +# Specific functions +sub create_child { + my (%options) = @_; + + $options{logger}->writeLogInfo("[httpserverng] Create module 'httpserverng' process"); + my $child_pid = fork(); + if ($child_pid == 0) { + $0 = 'gorgone-httpserverng'; + my $module = gorgone::modules::core::httpserverng::class->construct( + logger => $options{logger}, + module_id => NAME, + config_core => $config_core, + config => $config, + api_endpoints => $options{api_endpoints} + ); + $module->run(); + exit(0); + } + $options{logger}->writeLogDebug("[httpserverng] PID $child_pid (gorgone-httpserverng)"); + $httpserverng = { pid => $child_pid, ready => 0, running => 1 }; +} + +1; diff --git a/gorgone/gorgone/modules/core/pipeline/class.pm b/gorgone/gorgone/modules/core/pipeline/class.pm index a9d061a9fb5..852b25e4b07 100644 --- a/gorgone/gorgone/modules/core/pipeline/class.pm +++ b/gorgone/gorgone/modules/core/pipeline/class.pm @@ -222,7 +222,9 @@ sub event { if ((my $method = $connector->can('action_' . lc($1)))) { $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; my ($action, $token) = ($1, $2); - my $data = JSON::XS->new->utf8->decode($3); + my ($rv, $data) = $connector->json_decode(argument => $3, token => $token); + next if ($rv); + $method->($connector, token => $token, data => $data); } } diff --git a/gorgone/gorgone/modules/core/pipeline/hooks.pm b/gorgone/gorgone/modules/core/pipeline/hooks.pm index 8e4fe6ce318..7aea7ae81e6 100644 --- a/gorgone/gorgone/modules/core/pipeline/hooks.pm +++ b/gorgone/gorgone/modules/core/pipeline/hooks.pm @@ -22,7 +22,6 @@ package gorgone::modules::core::pipeline::hooks; use warnings; use strict; -use JSON::XS; use gorgone::class::core; use gorgone::modules::core::pipeline::class; use gorgone::standard::constants qw(:all); @@ -66,22 +65,6 @@ sub init { sub routing { my (%options) = @_; - - my $data; - eval { - $data = JSON::XS->new->utf8->decode($options{data}); - }; - if ($@) { - $options{logger}->writeLogError("[pipeline] Cannot decode json data: $@"); - gorgone::standard::library::add_history( - dbh => $options{dbh}, - code => GORGONE_ACTION_FINISH_KO, - token => $options{token}, - data => { message => 'gorgone-pipeline cannot decode json' }, - json_encode => 1 - ); - return undef; - } if ($options{action} eq 'PIPELINEREADY') { $pipeline->{ready} = 1; diff --git a/gorgone/gorgone/modules/core/register/class.pm b/gorgone/gorgone/modules/core/register/class.pm index 0f473aa1e03..7e7d8782593 100644 --- a/gorgone/gorgone/modules/core/register/class.pm +++ b/gorgone/gorgone/modules/core/register/class.pm @@ -147,7 +147,9 @@ sub event { if ((my $method = $connector->can('action_' . lc($1)))) { $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; my ($action, $token) = ($1, $2); - my $data = JSON::XS->new->utf8->decode($3); + my ($rv, $data) = $connector->json_decode(argument => $3, token => $token); + next if ($rv); + $method->($connector, token => $token, data => $data); } } diff --git a/gorgone/gorgone/modules/core/register/hooks.pm b/gorgone/gorgone/modules/core/register/hooks.pm index c3f0c9efb9e..de0be8f3a7f 100644 --- a/gorgone/gorgone/modules/core/register/hooks.pm +++ b/gorgone/gorgone/modules/core/register/hooks.pm @@ -22,7 +22,6 @@ package gorgone::modules::core::register::hooks; use warnings; use strict; -use JSON::XS; use gorgone::class::core; use gorgone::modules::core::register::class; use gorgone::standard::constants qw(:all); @@ -60,22 +59,6 @@ sub init { sub routing { my (%options) = @_; - - my $data; - eval { - $data = JSON::XS->new->utf8->decode($options{data}); - }; - if ($@) { - $options{logger}->writeLogError("[register] Cannot decode json data: $@"); - gorgone::standard::library::add_history( - dbh => $options{dbh}, - code => GORGONE_ACTION_FINISH_KO, - token => $options{token}, - data => { message => 'gorgoneregister: cannot decode json' }, - json_encode => 1 - ); - return undef; - } if ($options{action} eq 'REGISTERREADY') { $register->{ready} = 1; @@ -98,7 +81,7 @@ sub routing { identity => 'gorgone-register', action => $options{action}, data => $options{data}, - token => $options{token}, + token => $options{token} ); } diff --git a/gorgone/gorgone/modules/plugins/newtest/hooks.pm b/gorgone/gorgone/modules/plugins/newtest/hooks.pm index 75598f201f5..822ab878c02 100644 --- a/gorgone/gorgone/modules/plugins/newtest/hooks.pm +++ b/gorgone/gorgone/modules/plugins/newtest/hooks.pm @@ -111,7 +111,7 @@ sub routing { gorgone::standard::library::zmq_send_message( socket => $options{socket}, identity => 'gorgone-newtest-' . $data->{container_id}, - action => $options{action}, data => $options{data}, token => $options{token}, + action => $options{action}, data => $options{data}, token => $options{token} ); } diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index a4306e0b724..d165375bf79 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -214,6 +214,18 @@ sub zmq_core_response { zmq_sendmsg($options{socket}, $msg, ZMQ_DONTWAIT); } +sub zmq_getfd { + my (%options) = @_; + + return zmq_getsockopt($options{socket}, ZMQ_FD); +} + +sub zmq_events { + my (%options) = @_; + + return zmq_getsockopt($options{socket}, ZMQ_EVENTS); +} + sub uncrypt_message { my (%options) = @_; my $plaintext; From 9d3829eacd5e7e3a692b47841e054e646fffa0f7 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Tue, 14 Dec 2021 09:20:58 +0100 Subject: [PATCH 623/948] add(spec): mojolicious dependencies (#176) --- gorgone/packaging/centreon-gorgone.spectemplate | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index c3c69da90d0..235ccf5417c 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -36,6 +36,9 @@ Requires: perl(NetAddr::IP) Requires: perl(Hash::Merge) Requires: perl(Clone) Requires: perl(Sys::Syslog) +Requires: perl(Mojolicious::Lite) +Requires: perl(Mojo::Server::Daemon) +Requires: perl(Authen::Simple::Password) AutoReqProv: no %description From f667a5673419f86b11ce035687720895f6f578e7 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Tue, 14 Dec 2021 09:41:00 +0100 Subject: [PATCH 624/948] enh(httpserverng): manage not installed basic_auth_plus (#177) --- .../gorgone/modules/core/httpserverng/class.pm | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/gorgone/gorgone/modules/core/httpserverng/class.pm b/gorgone/gorgone/modules/core/httpserverng/class.pm index fc70868cc1a..bf14763ba9e 100644 --- a/gorgone/gorgone/modules/core/httpserverng/class.pm +++ b/gorgone/gorgone/modules/core/httpserverng/class.pm @@ -39,8 +39,6 @@ use JSON::XS; my %handlers = (TERM => {}, HUP => {}); my ($connector); -plugin('basic_auth_plus'); - websocket '/' => sub { my $mojo = shift; @@ -272,6 +270,20 @@ sub run { } }); + $self->{basic_auth_plus} = 1; + eval { + local $SIG{__DIE__} = 'IGNORE'; + + app->plugin('basic_auth_plus'); + }; + if ($@) { + $self->{basic_auth_plus} = 0; + } + if ($self->{auth_enabled} == 1 && $self->{basic_auth_plus} == 0 && $self->{allowed_hosts_enabled} == 0) { + $connector->{logger}->writeLogError("[httpserverng] need to install the module basic_auth_plus"); + exit(1); + } + app->mode('production'); my $daemon = Mojo::Server::Daemon->new( app => app, @@ -397,7 +409,7 @@ sub api_call { } } - if ($self->{auth_enabled} == 1) { + if ($self->{auth_enabled} == 1 && $self->{basic_auth_plus} == 1) { my ($hash_ref, $auth_ok) = $options{mojo}->basic_auth( 'Realm Name' => { username => $self->{config}->{auth}->{user}, From ab24276764adada22085e024d92ceb21e149c311 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Wed, 29 Dec 2021 11:42:21 +0100 Subject: [PATCH 625/948] fix(core): wrong module management (#178) --- gorgone/gorgone/class/core.pm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 1cea987b1f9..3c2f6284c4b 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -344,6 +344,7 @@ sub load_module { foreach my $method_name (('register', 'routing', 'kill', 'kill_internal', 'gently', 'check', 'init', 'broadcast')) { unless ($self->{modules_register}->{$package}->{$method_name} = $package->can($method_name)) { + delete $self->{modules_register}->{$package}; $self->{logger}->writeLogError("[core] No function '$method_name' for module '" . $options{config_module}->{name} . "'"); return 0; } @@ -354,9 +355,10 @@ sub load_module { config_core => $self->{config}->{configuration}->{gorgone}, config_db_centreon => $self->{config}->{configuration}->{centreon}->{database}->{db_configuration}, config_db_centstorage => $self->{config}->{configuration}->{centreon}->{database}->{db_realtime}, - logger => $self->{logger}, + logger => $self->{logger} ); if ($loaded == 0) { + delete $self->{modules_register}->{$package}; $self->{logger}->writeLogError("[core] Module '" . $options{config_module}->{name} . "' cannot be loaded"); return 0; } From 1f53b9a4a115a638fec799a0e6eb9f9b6ffcf3ba Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Wed, 29 Dec 2021 16:12:43 +0100 Subject: [PATCH 626/948] fix(proxy): pull mode communication crash (#179) --- gorgone/gorgone/modules/core/proxy/hooks.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index 14bdb865f09..5d55f7d9b8b 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -648,7 +648,7 @@ sub ping_send { } elsif ($register_nodes->{$id}->{type} eq 'pull') { $constatus_ping->{$id}->{in_progress_ping} = 1; $constatus_ping->{$id}->{in_progress_ping_pull} = time(); - routing(action => 'PING', target => $id, data => '{}', dbh => $options{dbh}, logger => $options{logger}); + routing(socket => $internal_socket, action => 'PING', target => $id, data => '{}', dbh => $options{dbh}, logger => $options{logger}); } } } @@ -670,7 +670,7 @@ sub full_sync_history { if ($register_nodes->{$id}->{type} eq 'push_zmq') { routing(socket => $internal_socket, action => 'GETLOG', target => $id, data => '{}', dbh => $options{dbh}, logger => $options{logger}); } elsif ($register_nodes->{$id}->{type} eq 'pull') { - routing(action => 'GETLOG', target => $id, data => '{}', dbh => $options{dbh}, logger => $options{logger}); + routing(socket => $internal_socket, action => 'GETLOG', target => $id, data => '{}', dbh => $options{dbh}, logger => $options{logger}); } } } From 681a1e1296c33895507e1f176c72491a21bfb8a2 Mon Sep 17 00:00:00 2001 From: sc979 <34628915+sc979@users.noreply.github.com> Date: Mon, 3 Jan 2022 17:12:50 +0100 Subject: [PATCH 627/948] enh(secu): update GPG key (#180) --- gorgone/SECURITY.md | 100 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 85 insertions(+), 15 deletions(-) diff --git a/gorgone/SECURITY.md b/gorgone/SECURITY.md index 33a273e67ea..30f554b5b7d 100644 --- a/gorgone/SECURITY.md +++ b/gorgone/SECURITY.md @@ -12,7 +12,7 @@ Send an email to security@centreon.com. If possible, encrypt your message with o You should receive a response within 48 hours. If for some reason you do not, please follow up via email to ensure we received your original message. -To help us better understand the nature and scope of the possible issue, please describe as much as you can: +To help us better understand the nature and scope of the possible issue, please describe as much as you can: * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) * Full paths of source file(s) related to the manifestation of the issue @@ -22,10 +22,94 @@ To help us better understand the nature and scope of the possible issue, please * Proof-of-concept or exploit code (if possible) * Impact of the issue, including how an attacker might exploit the issue +## Bug bounty + +We don't have a bug bounty program but this is something we are thinking about. + ## PGP information ### Public key +| Tag | Value | +| -- | -- | +| ID | 8F7D860C6C32543A | +| Type | RSA | +| Size | 4096 | +| Created | 2022-12-28 | +| Expires | 2023-01-22 | +| Fingerprint | F4A2 2EA4 1FA1 F308 A66A C046 8F7D 860C 6C32 543A | + +``` +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBGHLO4IBEADB5ZlFUNNH/Y5TEVHAAHIMjHEt63M5hA+C94EYv89R2+swz212 +Hla4f5sVl5wPNSwiIAed+bJNKnGiaDM/508aMcTHurGRu3x5/MyvuxpXmzOSY1Mt +JZxLBBkonL1iX0tCytWriOhgAty9gi58DPKA6f7sVDgt3Hm/NtIEULSbXy6xfDYo +m+Sz39+hb0PcKKEkacRGzOGDIR0UgOAUGDBEbDoLPxjM6flHjXcjs4fZNY2HHQXO +AB65qM9my4ALxxsrIbsKfu25HY52qSZoqZD90AxKdNtRFlnkXClWN0l26fVqiGjv +oCPMYGPp80OYvymE2QhtlD+jRAepwyWx1YY96VFIA9LsZtjmoRxw/KLghdeP4Q7p +/BUCVkT393OOTayNhoNa7iCqbK0lmB6mequi7KV3vNXn79WP0Hm8AQ9/9bEJaY8x +oNTKAxsR6gLP1fc7S/zg9iIHUuTj6XU9CbW45ADrCJRel5LoM+MZ3DWXh+kd0Iuw +yANU+XVgC1fXQOf76BJeYSalZS8Ln1vpYjDwEZBSmLdyefCYBjspxjDNzpCAy+wH +gc/vpQbjmFxgkbZ3AroGDaNu1JVhA3yy8oXAEwAxl8BzsYye2YbhAUb2RgZhIndd +TCcWkwhEWey3XYMCtnFWxsXnteA1cvWD8PvCiWy53yc/Ng59H3XyB3sJbQARAQAB +tJJDZW50cmVvbiBTZWN1cml0eSAoRW1haWwgdXNlZCB0byByZXBvcnQgYSB2dWxu +ZXJhYmlsaXR5IG9uIENlbnRyZW9uJ3MgcHJvZHVjdHMgb3IgdG8gY29udGFjdCB0 +aGUgQ2VudHJlb24ncyBzZWN1cml0eSB0ZWFtKSA8c2VjdXJpdHlAY2VudHJlb24u +Y29tPokCVAQTAQoAPhYhBPSiLqQfofMIpmrARo99hgxsMlQ6BQJhyzuCAhsDBQkC +AikABQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEI99hgxsMlQ6Ms0P/1lJfoaj +5/mRIKvaWZnTZm6OCpJtRT/9WrBGxbVi0TfyFb8M5OHeoz2oXc7WEPDJNNW9aRat +i40oHfQExW0UBsMvpfGlhPc5nwIxzpvFuckPrSTU3Y9ZLQvCoyxPIWMsXlghwwHD +OFW5oYg+rAvtmFuLyM18MdkH3YmrUHs7wyZczvi5hqdv5yB92kEjDTRbCYG8k9ep +P1rr3U/WyQ6u5BEZLTFNceK//VQsEfN7l8QLQV5CtG7PtdnLh5V4lkDHV7DW3PCg +TXyy8A5IatLh7z+ODQ/GDwCw7NNQm5M8dB1T/tLZn7+kTf8KqjHz4Qbh2PFBu5R7 +sDPEM81EMSAN/dfuZyQKbABnJk0JalvZeuZYUQYZWTgK6mSFJ3Rac8aX4TX7fwfS +kjh0ivG/FWzKaDa/dGfpH0J69IE2SACLRF+022hPLz7dRmSyNQSFZ4McXC+ihQps +mDJrqLmBPSNIRkfTwczINPSAX051w3GsJDSoAID+X0iqmoZTPYuf9XJO689vx+5B +g4FwhFwETkCqq6sZLzBI8+8dHKCn7imCtmrJ7JN8uy4mPS4Y2yVTBr1sZiAuvHzN +4314o3N30OZeqG61OuE6XO67mlw5Rk5Kay7P/s3uu5wXEL01z5+VKH/hjzVV+GXq +9J+VKYfXOcvu9PDhVBzlXgtP5xNuhh/IlvoDuQINBGHLPM4BEADdN4QbeuBSFts7 +9iIJXBmYiwcfDUOyZYaam6tI1fi1MbWCTAwpDpR0e8wdAon5yrF5jF6f6PIzqSfV +jc1rtLfdftVlzCMobXyjPxO9LkwChSm+b1tR6R7FyxfkUu5Og7TdrTpzzbTPN6TA +0BReEy20NpU6b3xC96BdaxE2ePlDOm26C5ygmAWszGD29ztxVtbBq7w0M+q7MO5Q +UH286bNQXKL5C5wZuA18li/hk7Cri4XwtRyMMh0dlT7hYYuKKJN5d8swx793JdlP +uYxmL93bM7rlka+W9fHYEbW5Zr7KUKwGygju/R2kx567iZyNwhFdeNsMFXFRIxCL +sEqNBc6EexafaxoJc4Ms0FD5WXiy9Je5+c5ue5Qb3SzavRu8u/z4bnjmpXqodBOS +jB4KPPGp4iCZPIOD98HyQ4XlYPSFc4hFEDxM6JGjfiKzFGaSmroSqwoKYYUjLsI+ +SuFPciOnH73KvwGX0uDYOKKZp5kmhKwu+AWaOfjoMGC2j+aOCQswziYBwr32bgyf +S/CM4uha7XhY4vi5IaRgXrSSsCRFwskTRQwtAHtu8m17D0CH7K5blKXtx+pHB+6x +cYTX9u2N2rqnOTma6+KEXFzVj0JiXsT3OwFmAFRvDU+erGSlAF3bQkZcu0hoFZmh +XBeRV+vA3D/rkwXal8vMRcn2V+XZ5wARAQABiQI8BBgBCgAmFiEE9KIupB+h8wim +asBGj32GDGwyVDoFAmHLPM4CGwwFCQICKQAACgkQj32GDGwyVDqkbg/+NVEZw2A4 +Uk6h4Exo9T0+ttd/ywi8P5aGnoiJ9Fw92RHgmSNUwIwgdeGKrgBbhVaO/V4CDGJp +iiwIAxzU/xCNibEGTUkH79AZvFHXxXwRKf/vWW1w3gyh9ppRLBlUw3S2DdEkxlzJ +5R1ryYTeV4yFAVK1Ln1v/UCA2WHho3IN/PIgDt701zONUEn1OOxHrMlKsgHIBAAk +NA6IQ1Tr8RW9abK3uAtJxxnyOqEMkiE03sJfd1dAUtvirGxr7g3t1Gfi8BPnQR5T +ZNqDOblM6fiY05AngPOLtV0n6LazK/buNenvUUhT0R9noMX6ZcApGpS4fFhADw1q +vrFYSG/4rLSGKvLqw5pQ7PzLDHPfn/HIME//SPuBnYrTYjiupzdmgtjGOc1iMV0X +YVXuA1yj+aJFaObLnVD31v2GIKvVS4WMsG74Mf5vMiMkbc0Zg2ULGun3sXscW0Yh +2MnvI5oYQcKmzjmhPdKHrmkiy9QC4442PbE8Bn9KUpcVoxCtFr/Zsc18iUVHYyIG +rrmZBE8MF1tGGBsdFC4Aktujuj3EevBo26QLozyfOLXXATHhmGh4SWsH68iyzynw +ARzB/pCyvB1Y/QbRn3ClFIksAyjrMxiNkSQXgToc8Ph+vLnHS3Y4399c74WZCHCH +i51yIfcTAPmxOst/YN5WXOxWHZjZ/STVi0Y= +=8Gny +-----END PGP PUBLIC KEY BLOCK----- +``` + +### Revoked Public key + +**_Kindly use the new key instead_** + +| Tag | Value | +| -- | -- | +| ID | BEAF6EBF631106F9 | +| Type | RSA | +| Size | 4096 | +| Created | 2020-02-11 | +| Expires | 2022-01-13 | +| Cipher |AES-256| +| Fingerprint | C377 E9D5 2D5C 137D 3DD5 73B5 BEA F6EBF 6311 06F9 | + ``` -----BEGIN PGP PUBLIC KEY BLOCK----- @@ -80,17 +164,3 @@ SL4taAGY0BDuA3zhB7p5tP8= =YBvx -----END PGP PUBLIC KEY BLOCK----- ``` - -| Tag | Value | -| -- | -- | -| ID | BEAF6EBF631106F9 | -| Type | RSA | -| Size | 4096 | -| Created | 2020-02-11 | -| Expires | 2022-01-13 | -| Cipher |AES-256| -| Fingerprint | C377 E9D5 2D5C 137D 3DD5 73B5 BEA F6EBF 6311 06F9 | - -## Bug bounty - -We don't have a bug bounty program but this is something we are thinking about. From ae386a144afb100979b04514de60046a3831b687 Mon Sep 17 00:00:00 2001 From: sc979 <34628915+sc979@users.noreply.github.com> Date: Mon, 3 Jan 2022 18:23:38 +0100 Subject: [PATCH 628/948] fix(secu): change fingerprint key (#181) --- gorgone/SECURITY.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/SECURITY.md b/gorgone/SECURITY.md index 30f554b5b7d..089eb2157e9 100644 --- a/gorgone/SECURITY.md +++ b/gorgone/SECURITY.md @@ -32,12 +32,12 @@ We don't have a bug bounty program but this is something we are thinking about. | Tag | Value | | -- | -- | -| ID | 8F7D860C6C32543A | +| ID | F92686A9EC269C1A | | Type | RSA | | Size | 4096 | | Created | 2022-12-28 | | Expires | 2023-01-22 | -| Fingerprint | F4A2 2EA4 1FA1 F308 A66A C046 8F7D 860C 6C32 543A | +| Fingerprint | 3552 91EA 7DAF 9E2A 192C 62B6 F926 86A9 EC26 9C1A | ``` -----BEGIN PGP PUBLIC KEY BLOCK----- From 580d16c356eec3d22f25356b8bce7b4e548ad766 Mon Sep 17 00:00:00 2001 From: sc979 <34628915+sc979@users.noreply.github.com> Date: Wed, 5 Jan 2022 15:23:54 +0100 Subject: [PATCH 629/948] fix(secu): add SQ pipeline timeout (#182) --- gorgone/Jenkinsfile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/gorgone/Jenkinsfile b/gorgone/Jenkinsfile index dc26ff7ce7f..8e8d10380cd 100644 --- a/gorgone/Jenkinsfile +++ b/gorgone/Jenkinsfile @@ -64,9 +64,11 @@ stage('Deliver sources // Sonar analysis') { withSonarQubeEnv('SonarQubeDev') { sh "./centreon-build/jobs/gorgone/${serie}/gorgone-analysis.sh" } - def qualityGate = waitForQualityGate() - if (qualityGate.status != 'OK') { - currentBuild.result = 'FAIL' + timeout(time: 10, unit: 'MINUTES') { + def qualityGate = waitForQualityGate() + if (qualityGate.status != 'OK') { + currentBuild.result = 'FAIL' + } } } } From 58bfc4dacfddbac0381506cb134b3427ea966162 Mon Sep 17 00:00:00 2001 From: sc979 <34628915+sc979@users.noreply.github.com> Date: Wed, 5 Jan 2022 19:55:31 +0100 Subject: [PATCH 630/948] fix(chore): use github action env usage (#183) --- gorgone/.github/workflows/dependabot_jira.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gorgone/.github/workflows/dependabot_jira.yml b/gorgone/.github/workflows/dependabot_jira.yml index 55eef4b4e3d..1bcc25b7b2e 100644 --- a/gorgone/.github/workflows/dependabot_jira.yml +++ b/gorgone/.github/workflows/dependabot_jira.yml @@ -6,9 +6,6 @@ on: branches: [ develop ] env: - JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} - JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} - JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} JIRA_PROJECT_KEY: "MON" JIRA_ISSUE_TYPE: "Technical" @@ -24,15 +21,19 @@ jobs: - name: Login to Jira uses: atlassian/gajira-login@v2.0.0 - env: + id: login + with: JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} - name: Create Jira Issue - id: create uses: atlassian/gajira-create@v2.0.1 + id: create with: + JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} + JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} + JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} project: ${{ env.JIRA_PROJECT_KEY }} issuetype: ${{ env.JIRA_ISSUE_TYPE }} summary: | @@ -53,7 +54,6 @@ jobs: *Github Advisory* - fields: '{ "customfield_10880": "Internal", From 49db2f63c13c80e0ef1814ed56d42615725e4969 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Wed, 12 Jan 2022 10:30:03 +0100 Subject: [PATCH 631/948] add mechanism that will perform the installation of the plugins (#184) --- gorgone/contrib/gorgone_install_plugins.pl | 50 +++ .../modules/centreon/legacycmd/class.pm | 38 +- gorgone/gorgone/modules/core/action/class.pm | 344 ++++++++++++++++-- gorgone/gorgone/modules/core/action/hooks.pm | 1 + .../packaging/centreon-gorgone.spectemplate | 7 + gorgone/packaging/sudoers.d/centreon-gorgone | 6 + 6 files changed, 420 insertions(+), 26 deletions(-) create mode 100644 gorgone/contrib/gorgone_install_plugins.pl create mode 100644 gorgone/packaging/sudoers.d/centreon-gorgone diff --git a/gorgone/contrib/gorgone_install_plugins.pl b/gorgone/contrib/gorgone_install_plugins.pl new file mode 100644 index 00000000000..5f8a5023012 --- /dev/null +++ b/gorgone/contrib/gorgone_install_plugins.pl @@ -0,0 +1,50 @@ +#!/usr/bin/perl +# +# Copyright 2022 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; + +my $plugins = []; +for (my $i = 0; $i < scalar(@ARGV); $i++) { + if ($ARGV[$i] =~ /^centreon-plugin-([A-Za-z\-_0-9]+)$/) { + push @$plugins, '"' . $ARGV[$i] . '"'; + } +} + +if (scalar(@$plugins) <= 0) { + print "nothing to install\n"; + exit(0); +} + +my $command = 'yum -y install ' . join(' ', @$plugins) . ' 2>&1'; +my $output = `$command`; +if ($? == -1) { + print "failed to execute: $!\n"; + exit(1); +} elsif ($? & 127) { + printf "child died with signal %d, %s coredump\n", + ($? & 127), ($? & 128) ? 'with' : 'without'; + exit(1); +} + +my $exit = $? >> 8; +print "succeeded command (code: $exit): " . $output; +exit(0); diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index f98b2cedfc0..395441abbed 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -363,6 +363,24 @@ sub execute_cmd { } } ); + } elsif ($options{cmd} eq 'ENGINERESTART') { + my $cmd = $self->{pollers}->{$options{target}}->{engine_restart_command}; + $self->send_internal_action( + action => 'ACTIONENGINE', + target => $options{target}, + token => $token, + data => { + logging => $options{logging}, + content => { + command => 'sudo ' . $cmd, + plugins => $self->{pollers}->{ $options{target} }->{cfg_dir} . '/plugins.json', + metadata => { + centcore_proxy => 1, + centcore_cmd => 'ENGINERESTART' + } + } + } + ); } elsif ($options{cmd} eq 'RESTART') { my $cmd = $self->{pollers}->{$options{target}}->{engine_restart_command}; $self->send_internal_action( @@ -382,6 +400,24 @@ sub execute_cmd { ] } ); + } elsif ($options{cmd} eq 'ENGINERELOAD') { + my $cmd = $self->{pollers}->{ $options{target} }->{engine_reload_command}; + $self->send_internal_action( + action => 'ACTIONENGINE', + target => $options{target}, + token => $token, + data => { + logging => $options{logging}, + content => { + command => 'sudo ' . $cmd, + plugins => $self->{pollers}->{ $options{target} }->{cfg_dir} . '/plugins.json', + metadata => { + centcore_proxy => 1, + centcore_cmd => 'ENGINERELOAD' + } + } + } + ); } elsif ($options{cmd} eq 'RELOAD') { my $cmd = $self->{pollers}->{$options{target}}->{engine_reload_command}; $self->send_internal_action( @@ -395,7 +431,7 @@ sub execute_cmd { command => 'sudo ' . $cmd, metadata => { centcore_proxy => 1, - centcore_cmd => 'RELOAD', + centcore_cmd => 'RELOAD' } } ] diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index 813efbb2cc0..4bfeb0ebbb4 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -33,6 +33,7 @@ use JSON::XS; use File::Basename; use File::Copy; use File::Path qw(make_path); +use POSIX ":sys_wait_h"; use MIME::Base64; use Digest::MD5::File qw(file_md5_hex); use Archive::Tar; @@ -41,7 +42,7 @@ use Fcntl; $Archive::Tar::SAME_PERMISSIONS = 1; $Archive::Tar::WARN = 0; $Digest::MD5::File::NOFATALS = 1; -my %handlers = (TERM => {}, HUP => {}); +my %handlers = (TERM => {}, HUP => {}, CHLD => {}); my ($connector); sub new { @@ -59,7 +60,12 @@ sub new { $connector->{allowed_cmds} = $connector->{config}->{allowed_cmds} if (defined($connector->{config}->{allowed_cmds}) && ref($connector->{config}->{allowed_cmds}) eq 'ARRAY'); - $connector->set_signal_handlers; + $connector->{return_childs} = {}; + $connector->{engine_childs} = {}; + $connector->{max_concurrent_engine} = defined($connector->{config}->{max_concurrent_engine}) ? + $connector->{config}->{max_concurrent_engine} : 3; + + $connector->set_signal_handlers(); return $connector; } @@ -70,6 +76,8 @@ sub set_signal_handlers { $handlers{TERM}->{$self} = sub { $self->handle_TERM() }; $SIG{HUP} = \&class_handle_HUP; $handlers{HUP}->{$self} = sub { $self->handle_HUP() }; + $SIG{CHLD} = \&class_handle_CHLD; + $handlers{CHLD}->{$self} = sub { $self->handle_CHLD() }; } sub handle_HUP { @@ -83,6 +91,18 @@ sub handle_TERM { $self->{stop} = 1; } +sub handle_CHLD { + my $self = shift; + my $child_pid; + + while (($child_pid = waitpid(-1, &WNOHANG)) > 0) { + $self->{logger}->writeLogDebug("[action] Received SIGCLD signal (pid: $child_pid)"); + $self->{return_child}->{$child_pid} = 1; + } + + $SIG{CHLD} = \&class_handle_CHLD; +} + sub class_handle_TERM { foreach (keys %{$handlers{TERM}}) { &{$handlers{TERM}->{$_}}(); @@ -95,6 +115,163 @@ sub class_handle_HUP { } } +sub class_handle_CHLD { + foreach (keys %{$handlers{CHLD}}) { + &{$handlers{CHLD}->{$_}}(); + } +} + +sub check_childs { + my ($self, %options) = @_; + + foreach (keys %{$self->{return_child}}) { + delete $self->{engine_childs}->{$_} if (defined($self->{engine_childs}->{$_})); + } + + $self->{return_child} = {}; +} + +sub get_package_manager { + my ($self, %options) = @_; + + $self->{package_manager} = 'unknown'; + my ($error, $stdout, $return_code) = gorgone::standard::misc::backtick( + command => 'lsb_release -a', + timeout => 5, + wait_exit => 1, + redirect_stderr => 1, + logger => $options{logger} + ); + if ($error == 0 && $stdout =~ /^Description:\s+(.*)$/mi) { + my $os = $1; + if ($os =~ /Debian|Ubuntu/i) { + $self->{package_manager} = 'deb'; + } elsif ($os =~ /CentOS|Redhat/i) { + $self->{package_manager} = 'rpm'; + } + } +} + +sub check_plugins_rpm { + my ($self, %options) = @_; + + #rpm -q centreon-plugin-Network-Microsens-G6-Snmp test centreon-plugin-Network-Generic-Bluecoat-Snmp + #centreon-plugin-Network-Microsens-G6-Snmp-20211228-150846.el7.centos.noarch + #package test is not installed + #centreon-plugin-Network-Generic-Bluecoat-Snmp-20211102-130335.el7.centos.noarch + my ($error, $stdout, $return_code) = gorgone::standard::misc::backtick( + command => 'rpm', + arguments => ['-q', keys %{$options{plugins}}], + timeout => 60, + wait_exit => 1, + redirect_stderr => 1, + logger => $self->{logger} + ); + if ($error != 0) { + return (-1, 'check rpm plugins command issue: ' . $stdout); + } + + my $installed = []; + foreach my $package_name (keys %{$options{plugins}}) { + if ($stdout =~ /^$package_name-(\d+)-/m) { + my $current_version = $1; + if ($current_version < $options{plugins}->{$package_name}) { + push @$installed, $package_name . '-' . $options{plugins}->{$package_name}; + } + } else { + push @$installed, $package_name . '-' . $options{plugins}->{$package_name}; + } + } + + if (scalar(@$installed) > 0) { + return (1, 'install', $installed); + } + + $self->{logger}->writeLogInfo("[action] validate plugins - nothing to install"); + return 0; +} + +sub install_plugins { + my ($self, %options) = @_; + + $self->{logger}->writeLogInfo("[action] validate plugins - install " . join(' ', @{$options{installed}})); + my ($error, $stdout, $return_code) = gorgone::standard::misc::backtick( + command => 'sudo', + arguments => ['/usr/local/bin/gorgone_install_plugins.pl', @{$options{installed}}], + timeout => 300, + wait_exit => 1, + redirect_stderr => 1, + logger => $self->{logger} + ); + if ($error != 0) { + return (-1, 'install plugins command issue: ' . $stdout); + } + + return 0; +} + +sub validate_plugins_rpm { + my ($self, %options) = @_; + + my ($rv, $message, $installed) = $self->check_plugins_rpm(%options); + return ($rv, $message) if ($rv == -1); + return 0 if ($rv == 0); + + if ($rv == 1) { + ($rv, $message) = $self->install_plugins(installed => $installed); + return ($rv, $message) if ($rv == -1); + } + + ($rv, $message, $installed) = $self->check_plugins_rpm(%options); + return ($rv, $message) if ($rv == -1); + if ($rv == 1) { + $self->{logger}->writeLogError("[action] validate plugins - still some to install: " . join(' ', @$installed)); + } + + return 0; +} + +sub validate_plugins { + my ($self, %options) = @_; + + my ($rv, $message, $content) = gorgone::standard::misc::slurp(file => $options{file}); + return (1, $message) if (!$rv); + + my $plugins; + eval { + $plugins = JSON::XS->new->utf8->decode($content); + }; + if ($@) { + return (1, 'cannot decode json'); + } + + # nothing to validate. so it's ok, show must go on!! :) + if (ref($plugins) ne 'HASH' || scalar(keys %$plugins) <= 0) { + return 0; + } + + if ($self->{package_manager} eq 'rpm') { + ($rv, $message) = $self->validate_plugins_rpm(plugins => $plugins); + } else { + # for debian/ubuntu: apt-get install centreon-plugin-test=version1 centreon-plugin-test=version2 + ($rv, $message) = (1, 'validate plugins - unsupported operating system'); + } + + return ($rv, $message); +} + +sub is_command_authorized { + my ($self, %options) = @_; + + return 0 if ($self->{whitelist_cmds} == 0); + + foreach my $regexp (@{$self->{allowed_cmds}}) { + return 0 if ($options{command} =~ /$regexp/); + } + + return 1; +} + sub action_command { my ($self, %options) = @_; @@ -105,7 +282,7 @@ sub action_command { token => $options{token}, logging => $options{data}->{logging}, data => { - message => "expected array, found '" . ref($options{data}->{content}) . "'", + message => "expected array, found '" . ref($options{data}->{content}) . "'" } ); return -1; @@ -120,34 +297,24 @@ sub action_command { token => $options{token}, logging => $options{data}->{logging}, data => { - message => "need command argument at array index '" . $index . "'", + message => "need command argument at array index '" . $index . "'" } ); return -1; } - if ($self->{whitelist_cmds} == 1) { - my $matched = 0; - foreach my $regexp (@{$self->{allowed_cmds}}) { - if ($command->{command} =~ /$regexp/) { - $matched = 1; - last; + if ($self->is_command_authorized(command => $command->{command})) { + $self->{logger}->writeLogInfo("[action] command not allowed (whitelist): " . $command->{command}); + $self->send_log( + socket => $options{socket_log}, + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + logging => $options{data}->{logging}, + data => { + message => "command not allowed (whitelist) at array index '" . $index . "'" } - } - - if ($matched == 0) { - $self->{logger}->writeLogInfo("[action] command not allowed (whitelist): " . $command->{command}); - $self->send_log( - socket => $options{socket_log}, - code => GORGONE_ACTION_FINISH_KO, - token => $options{token}, - logging => $options{data}->{logging}, - data => { - message => "command not allowed (whitelist) at array index '" . $index . "'", - } - ); - return -1; - } + ); + return -1; } $index++; @@ -378,6 +545,109 @@ sub action_processcopy { return 0; } +sub action_actionengine { + my ($self, %options) = @_; + + if (!defined($options{data}->{content}) || $options{data}->{content} eq '') { + $self->send_log( + socket => $options{socket_log}, + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + logging => $options{data}->{logging}, + data => { message => 'no content' } + ); + return -1; + } + + if (!defined($options{data}->{content}->{command})) { + $self->send_log( + socket => $options{socket_log}, + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + logging => $options{data}->{logging}, + data => { + message => "need valid command argument" + } + ); + return -1; + } + + if ($self->is_command_authorized(command => $options{data}->{content}->{command})) { + $self->{logger}->writeLogInfo("[action] command not allowed (whitelist): " . $options{data}->{content}->{command}); + $self->send_log( + socket => $options{socket_log}, + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + logging => $options{data}->{logging}, + data => { + message => 'command not allowed (whitelist)' + } + ); + return -1; + } + + if (defined($options{data}->{content}->{plugins}) && $options{data}->{content}->{plugins} ne '') { + my ($rv, $message) = $self->validate_plugins(file => $options{data}->{content}->{plugins}); + if ($rv) { + $self->{logger}->writeLogError("[action] $message"); + $self->send_log( + socket => $options{socket_log}, + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + logging => $options{data}->{logging}, + data => { + message => $message + } + ); + return -1; + } + } + + my $start = time(); + my ($error, $stdout, $return_code) = gorgone::standard::misc::backtick( + command => $options{data}->{content}->{command}, + timeout => $self->{command_timeout}, + wait_exit => 1, + redirect_stderr => 1, + logger => $self->{logger} + ); + my $end = time(); + if ($error != 0) { + $self->send_log( + socket => $options{socket_log}, + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + logging => $options{data}->{logging}, + data => { + message => "command execution issue", + command => $options{data}->{content}->{command}, + result => { + exit_code => $return_code, + stdout => $stdout + }, + metrics => { + start => $start, + end => $end, + duration => $end - $start + } + } + ); + return -1; + } + + $self->send_log( + socket => $options{socket_log}, + code => GORGONE_ACTION_FINISH_OK, + token => $options{token}, + logging => $options{data}->{logging}, + data => { + message => 'actionengine has finished successfully' + } + ); + + return 0; +} + sub action_run { my ($self, %options) = @_; @@ -392,6 +662,8 @@ sub action_run { if ($options{action} eq 'COMMAND') { $self->action_command(%options, socket_log => $socket_log); + } elsif ($options{action} eq 'ACTIONENGINE') { + $self->action_actionengine(%options, socket_log => $socket_log); } else { $self->send_log( socket => $socket_log, @@ -416,6 +688,19 @@ sub create_child { return undef; } + if ($options{action} eq 'ACTIONENGINE') { + my $num = scalar(keys %{$self->{engine_childs}}); + if ($num > $self->{max_concurrent_engine}) { + $self->{logger}->writeLogInfo("[action] max_concurrent_engine limit reached ($num/$self->{max_concurrent_engine})"); + $self->send_log( + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + data => { message => "max_concurrent_engine limit reached ($num/$self->{max_concurrent_engine})" } + ); + return undef; + } + } + $self->{logger}->writeLogDebug("[action] Create sub-process"); my $child_pid = fork(); if (!defined($child_pid)) { @@ -431,6 +716,10 @@ sub create_child { if ($child_pid == 0) { $self->action_run(action => $options{action}, token => $options{token}, data => $options{data}); exit(0); + } else { + if ($options{action} eq 'ACTIONENGINE') { + $self->{engine_childs}->{$child_pid} = 1; + } } } @@ -481,8 +770,13 @@ sub run { callback => \&event, } ]; + + $self->get_package_manager(); + while (1) { my $rev = scalar(zmq_poll($self->{poll}, 5000)); + $self->check_childs(); + if ($rev == 0 && $self->{stop} == 1) { $self->{logger}->writeLogInfo("[action] $$ has quit"); zmq_close($connector->{internal_socket}); diff --git a/gorgone/gorgone/modules/core/action/hooks.pm b/gorgone/gorgone/modules/core/action/hooks.pm index d19ffc9357a..1817786d193 100644 --- a/gorgone/gorgone/modules/core/action/hooks.pm +++ b/gorgone/gorgone/modules/core/action/hooks.pm @@ -32,6 +32,7 @@ use constant EVENTS => [ { event => 'ACTIONREADY' }, { event => 'PROCESSCOPY' }, { event => 'COMMAND', uri => '/command', method => 'POST' }, + { event => 'ACTIONENGINE', uri => '/engine', method => 'POST' } ]; my $config_core; diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 235ccf5417c..5113517347d 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -75,6 +75,7 @@ mkdir -p %{buildroot}/%{perl_vendorlib}/gorgone %{__cp} gorgoned %{buildroot}%{_bindir}/ %{__cp} contrib/gorgone_config_init.pl %{buildroot}%{_usr}/local/bin/ %{__cp} contrib/gorgone_audit.pl %{buildroot}%{_usr}/local/bin/ +%{__cp} contrib/gorgone_install_plugins.pl %{buildroot}%{_usr}/local/bin/ %{__install} -d %{buildroot}%{_sysconfdir}/centreon-gorgone %{__install} -d %{buildroot}%{_sysconfdir}/centreon-gorgone/config.d/ @@ -85,6 +86,9 @@ mkdir -p %{buildroot}/%{perl_vendorlib}/gorgone %{__cp} packaging/centreon-api.yaml %{buildroot}%{_sysconfdir}/centreon-gorgone/config.d/31-centreon-api.yaml %{__cp} packaging/centreon-audit.yaml %{buildroot}%{_sysconfdir}/centreon-gorgone/config.d/50-centreon-audit.yaml +%{__install} -d %buildroot%{_sysconfdir}/sudoers.d/ +%{__cp} packaging/sudoers.d/centreon-gorgone %buildroot%{_sysconfdir}/sudoers.d/centreon-gorgone + %{_fixperms} $RPM_BUILD_ROOT/* %check @@ -98,6 +102,7 @@ rm -rf %{buildroot} %attr(755, root, root) %{_bindir}/gorgoned %attr(755, root, root) %{_usr}/local/bin/gorgone_config_init.pl %attr(755, root, root) %{_usr}/local/bin/gorgone_audit.pl +%attr(750, root, root) %{_usr}/local/bin/gorgone_install_plugins.pl %attr(755, root, root) %{_sysconfdir}/systemd/system/gorgoned.service %config(noreplace) %{_sysconfdir}/sysconfig/gorgoned %config(noreplace) %{_sysconfdir}/logrotate.d/gorgoned @@ -120,6 +125,8 @@ rm -rf %{buildroot} %config(noreplace) %{_sysconfdir}/centreon-gorgone/config.d/31-centreon-api.yaml %config(noreplace) %{_sysconfdir}/centreon-gorgone/config.d/50-centreon-audit.yaml +%attr(0600, root, root) %{_sysconfdir}/sudoers.d/centreon-gorgone + %defattr(-, centreon-gorgone, centreon-gorgone, -) %{_localstatedir}/cache/centreon-gorgone/autodiscovery diff --git a/gorgone/packaging/sudoers.d/centreon-gorgone b/gorgone/packaging/sudoers.d/centreon-gorgone new file mode 100644 index 00000000000..ead5adc64dd --- /dev/null +++ b/gorgone/packaging/sudoers.d/centreon-gorgone @@ -0,0 +1,6 @@ +## BEGIN: GORGONE SUDO + +User_Alias GORGONE=centreon-gorgone +Defaults:GORGONE !requiretty + +GORGONE ALL = NOPASSWD: /usr/local/bin/gorgone_install_plugins.pl From 9e216b1ce0a35a9ffcb5ed0ff9cb8c56ae4a5f45 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Wed, 12 Jan 2022 10:59:41 +0100 Subject: [PATCH 632/948] can use ssh pollers with installation plugins mechanism (#185) --- gorgone/gorgone/modules/core/proxy/sshclient.pm | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/gorgone/gorgone/modules/core/proxy/sshclient.pm b/gorgone/gorgone/modules/core/proxy/sshclient.pm index fe13346b151..7c371dda50d 100644 --- a/gorgone/gorgone/modules/core/proxy/sshclient.pm +++ b/gorgone/gorgone/modules/core/proxy/sshclient.pm @@ -160,6 +160,23 @@ sub action_centcore { return (0, { message => 'send action_centcore succeeded' }); } +sub action_actionengine { + my ($self, %options) = @_; + + # validate plugins unsupported with ssh + $self->action_command( + data => { + logging => $options{data}->{logging}, + content => [ + $options{data}->{content} + ] + }, + target_direct => $options{target_direct}, + target => $options{target}, + token => $options{token} + ); +} + sub action_command { my ($self, %options) = @_; From 9303741f83c29c09e545a9c93a7650873ae12df5 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Tue, 25 Jan 2022 10:39:05 +0100 Subject: [PATCH 633/948] fix(httpserver): timeout and parse token (#186) --- gorgone/gorgone/standard/api.pm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gorgone/gorgone/standard/api.pm b/gorgone/gorgone/standard/api.pm index ceb4fc88f62..5f07f504229 100644 --- a/gorgone/gorgone/standard/api.pm +++ b/gorgone/gorgone/standard/api.pm @@ -149,7 +149,7 @@ sub call_internal { my $t1 = Time::HiRes::time(); my $rev = zmq_poll($poll, $timeout); last if (defined($results->{ $action_token })); - $timeout -= ($t1 - Time::HiRes::time()); + $timeout -= ((Time::HiRes::time() - $t1) * 1000); } my $response = '{"error":"no_result", "message":"No result found for action \'' . $options{action} . '\'"}'; @@ -252,8 +252,8 @@ sub event { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $socket); last if (!defined($message)); - if ($message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m || - $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+(.*)$/m) { + if ($message =~ /^\[(.*?)\]\s+\[([a-zA-Z0-9:\-_]*?)\]\s+\[.*?\]\s+(.*)$/m || + $message =~ /^\[(.*?)\]\s+\[([a-zA-Z0-9:\-_]*?)\]\s+(.*)$/m) { $results->{$2} = { action => $1, token => $2, From 1670c1c2aac0b531de9e16841574b05930849f56 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Wed, 26 Jan 2022 17:47:48 +0100 Subject: [PATCH 634/948] fix(contrib): gorgone_audit.pl unitialized value (#187) --- gorgone/contrib/gorgone_audit.pl | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/gorgone/contrib/gorgone_audit.pl b/gorgone/contrib/gorgone_audit.pl index 74eb6e5fef5..75e82ea787e 100644 --- a/gorgone/contrib/gorgone_audit.pl +++ b/gorgone/contrib/gorgone_audit.pl @@ -170,7 +170,7 @@ sub md_node_system_cpu { END_CPU if ($options{entry}->{status_code} != 0) { - my $message = '_**Error:** cannot get informations ' . $options{node}->{status_message}; + my $message = '_**Error:** cannot get informations ' . $options{entry}->{status_message}; $cpu .= <<"END_CPU"; <tr> <td colspan="2">$message</td> @@ -223,7 +223,7 @@ sub md_node_system_load { END_LOAD if ($options{entry}->{status_code} != 0) { - my $message = '_**Error:** cannot get informations ' . $options{node}->{status_message}; + my $message = '_**Error:** cannot get informations ' . $options{entry}->{status_message}; $load .= <<"END_LOAD"; <tr> <td colspan="2">$message</td> @@ -253,7 +253,7 @@ sub md_node_system_memory { END_MEMORY if ($options{entry}->{status_code} != 0) { - my $message = '_**Error:** cannot get informations ' . $options{node}->{status_message}; + my $message = '_**Error:** cannot get informations ' . $options{entry}->{status_message}; $memory .= <<"END_MEMORY"; <tr> <td colspan="2">$message</td> @@ -290,7 +290,7 @@ sub md_node_system_disk { my $disk = "#### Filesystems\n\n"; if ($options{entry}->{status_code} != 0) { - $disk .= '_**Error:** cannot get informations ' . $options{node}->{status_message} . "\n\n"; + $disk .= '_**Error:** cannot get informations ' . $options{entry}->{status_message} . "\n\n"; return $disk; } @@ -317,7 +317,7 @@ sub md_node_system_diskio { my $diskio = "#### Disks I/O\n\n"; if ($options{entry}->{status_code} != 0) { - $diskio .= '_**Error:** cannot get informations ' . $options{node}->{status_message} . "\n\n"; + $diskio .= '_**Error:** cannot get informations ' . $options{entry}->{status_message} . "\n\n"; return $diskio; } @@ -370,7 +370,7 @@ sub md_node_centreon_packages { my $packages = "#### Packages\n\n"; if ($options{entry}->{status_code} != 0) { - $packages .= '_**Error:** cannot get informations ' . $options{node}->{status_message} . "\n\n"; + $packages .= '_**Error:** cannot get informations ' . $options{entry}->{status_message} . "\n\n"; return $packages; } @@ -396,7 +396,7 @@ sub md_node_centreon_realtime { my $realtime = "#### Realtime\n\n"; if ($options{entry}->{status_code} != 0) { - $realtime .= '_**Error:** cannot get informations ' . $options{node}->{status_message} . "\n\n"; + $realtime .= '_**Error:** cannot get informations ' . $options{entry}->{status_message} . "\n\n"; return $realtime; } @@ -419,7 +419,7 @@ sub md_node_centreon_rrd { my $rrd = "#### Rrd\n\n"; if ($options{entry}->{status_code} != 0) { - $rrd .= '_**Error:** cannot get informations ' . $options{node}->{status_message} . "\n\n"; + $rrd .= '_**Error:** cannot get informations ' . $options{entry}->{status_message} . "\n\n"; return $rrd; } @@ -443,7 +443,7 @@ sub md_node_centreon_database { my $db = "#### Database\n\n"; if ($options{entry}->{status_code} != 0) { - $db .= '_**Error:** cannot get informations ' . $options{node}->{status_message} . "\n\n"; + $db .= '_**Error:** cannot get informations ' . $options{entry}->{status_message} . "\n\n"; return $db; } @@ -497,7 +497,7 @@ sub md_node_centreon_pluginpacks { my $pp = "#### Plugin-Packs\n\n"; if ($options{entry}->{status_code} != 0) { - $pp .= '_**Error:** cannot get informations ' . $options{node}->{status_message} . "\n\n"; + $pp .= '_**Error:** cannot get informations ' . $options{entry}->{status_message} . "\n\n"; return $pp; } From d3cc70c7a35639ea31eabd22557d84aae356bd2b Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Thu, 27 Jan 2022 09:34:05 +0100 Subject: [PATCH 635/948] enh(audit): better system detection (#188) --- gorgone/gorgone/modules/centreon/audit/class.pm | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/gorgone/gorgone/modules/centreon/audit/class.pm b/gorgone/gorgone/modules/centreon/audit/class.pm index b13d6f222f6..4ad704accb9 100644 --- a/gorgone/gorgone/modules/centreon/audit/class.pm +++ b/gorgone/gorgone/modules/centreon/audit/class.pm @@ -315,6 +315,13 @@ sub get_system { my ($self, %options) = @_; $self->{os} = 'unknown'; + + my ($rv, $message, $content) = gorgone::standard::misc::slurp(file => '/etc/os-release'); + if ($rv && $content =~ /^ID="(.*?)"/mi) { + $self->{os} = $1 + return ; + } + my ($error, $stdout, $return_code) = gorgone::standard::misc::backtick( command => 'lsb_release -a', timeout => 5, From 8560d00df4763f8db94fe1a4b4a3cf2fabb936f4 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Thu, 27 Jan 2022 10:57:50 +0100 Subject: [PATCH 636/948] enh(audit): better system detection 2 (#189) --- gorgone/gorgone/modules/centreon/audit/class.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/gorgone/modules/centreon/audit/class.pm b/gorgone/gorgone/modules/centreon/audit/class.pm index 4ad704accb9..8bb4b2733a1 100644 --- a/gorgone/gorgone/modules/centreon/audit/class.pm +++ b/gorgone/gorgone/modules/centreon/audit/class.pm @@ -318,7 +318,7 @@ sub get_system { my ($rv, $message, $content) = gorgone::standard::misc::slurp(file => '/etc/os-release'); if ($rv && $content =~ /^ID="(.*?)"/mi) { - $self->{os} = $1 + $self->{os} = $1; return ; } From c330725fe58d48839404eebd325d987a4c852b1c Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Thu, 27 Jan 2022 11:18:19 +0100 Subject: [PATCH 637/948] enhance plugin detection os mechanism (#190) --- gorgone/gorgone/modules/core/action/class.pm | 35 ++++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index 4bfeb0ebbb4..482081857ed 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -134,22 +134,29 @@ sub check_childs { sub get_package_manager { my ($self, %options) = @_; - $self->{package_manager} = 'unknown'; - my ($error, $stdout, $return_code) = gorgone::standard::misc::backtick( - command => 'lsb_release -a', - timeout => 5, - wait_exit => 1, - redirect_stderr => 1, - logger => $options{logger} - ); - if ($error == 0 && $stdout =~ /^Description:\s+(.*)$/mi) { - my $os = $1; - if ($os =~ /Debian|Ubuntu/i) { - $self->{package_manager} = 'deb'; - } elsif ($os =~ /CentOS|Redhat/i) { - $self->{package_manager} = 'rpm'; + my $os = 'unknown'; + my ($rv, $message, $content) = gorgone::standard::misc::slurp(file => '/etc/os-release'); + if ($rv && $content =~ /^ID="(.*?)"/mi) { + $os = $1; + } else { + my ($error, $stdout, $return_code) = gorgone::standard::misc::backtick( + command => 'lsb_release -a', + timeout => 5, + wait_exit => 1, + redirect_stderr => 1, + logger => $options{logger} + ); + if ($error == 0 && $stdout =~ /^Description:\s+(.*)$/mi) { + $os = $1; } } + + $self->{package_manager} = 'unknown'; + if ($os =~ /Debian|Ubuntu/i) { + $self->{package_manager} = 'deb'; + } elsif ($os =~ /CentOS|Redhat/i) { + $self->{package_manager} = 'rpm'; + } } sub check_plugins_rpm { From a849c46f550309f0773ee767c3e6762112345df0 Mon Sep 17 00:00:00 2001 From: tuntoja <58987095+tuntoja@users.noreply.github.com> Date: Mon, 31 Jan 2022 19:54:23 +0100 Subject: [PATCH 638/948] fix(build): disable centos8 packaging (#191) * fix(build): disable centos8 packaging * Update Jenkinsfile --- gorgone/Jenkinsfile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gorgone/Jenkinsfile b/gorgone/Jenkinsfile index 8e8d10380cd..44d7d6b8048 100644 --- a/gorgone/Jenkinsfile +++ b/gorgone/Jenkinsfile @@ -83,7 +83,8 @@ try { stash name: "rpms-centos7", includes: 'output/noarch/*.rpm' sh 'rm -rf output' } - }, + } +/* 'Packaging centos8': { node { checkoutCentreonBuild(buildBranch) @@ -93,6 +94,7 @@ try { sh 'rm -rf output' } } +*/ if ((currentBuild.result ?: 'SUCCESS') != 'SUCCESS') { error('Package stage failure.'); } @@ -102,7 +104,7 @@ try { stage('Delivery') { node { checkoutCentreonBuild(buildBranch) - unstash 'rpms-centos8' +// unstash 'rpms-centos8' unstash 'rpms-centos7' sh "./centreon-build/jobs/gorgone/${serie}/gorgone-delivery.sh" } From 1dddce9570578de1f08c3187f17b39ac5fc95595 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Wed, 2 Feb 2022 16:25:58 +0100 Subject: [PATCH 639/948] enh(clapi): use clapi password from configuration (#199) --- gorgone/gorgone/class/tpapi/clapi.pm | 10 +++++ .../modules/centreon/legacycmd/class.pm | 42 +++++++------------ 2 files changed, 24 insertions(+), 28 deletions(-) diff --git a/gorgone/gorgone/class/tpapi/clapi.pm b/gorgone/gorgone/class/tpapi/clapi.pm index 81d3e817037..1d5e0a14141 100644 --- a/gorgone/gorgone/class/tpapi/clapi.pm +++ b/gorgone/gorgone/class/tpapi/clapi.pm @@ -52,6 +52,16 @@ sub get_username { return $self->{username}; } +sub get_password { + my ($self, %options) = @_; + + if ($self->{is_error} == 1) { + return undef; + } + + return $self->{password}; +} + sub set_configuration { my ($self, %options) = @_; diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index 395441abbed..98a38d1ea34 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -28,6 +28,7 @@ use gorgone::standard::library; use gorgone::standard::constants qw(:all); use gorgone::standard::misc; use gorgone::class::sqlquery; +use gorgone::class::tpapi::clapi; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); use File::Copy; @@ -40,6 +41,9 @@ sub new { $connector = $class->SUPER::new(%options); bless $connector, $class; + $connector->{tpapi_clapi_name} = defined($options{config}->{tpapi_clapi}) && $options{config}->{tpapi_clapi} ne '' + ? $options{config}->{tpapi_clapi} + : 'clapi'; if (!defined($connector->{config}->{cmd_file}) || $connector->{config}->{cmd_file} eq '') { $connector->{config}->{cmd_file} = '/var/lib/centreon/centcore.cmd'; } @@ -97,7 +101,7 @@ sub cache_refresh { return if ((time() - $self->{cache_refresh_interval}) < $self->{cache_refresh_last}); $self->{cache_refresh_last} = time(); - + # get pollers config $self->{pollers} = undef; my ($status, $datas) = $self->{class_object_centreon}->custom_execute( @@ -116,38 +120,12 @@ sub cache_refresh { $self->{pollers} = $datas; - # get clapi user - $self->{clapi_user} = undef; - $self->{clapi_password} = undef; - ($status, $datas) = $self->{class_object_centreon}->custom_execute( - request => "SELECT contact_alias, contact_passwd " . - "FROM `contact` " . - "WHERE `contact_admin` = '1' " . - "AND `contact_activate` = '1' " . - "AND `contact_passwd` IS NOT NULL " . - "LIMIT 1 ", - mode => 2 - ); - - if ($status == -1 || !defined($datas->[0]->[0])) { - $self->{logger}->writeLogInfo('[legacycmd] Cannot get configuration for CLAPI user'); - } else { - my $clapi_user = $datas->[0]->[0]; - my $clapi_password = $datas->[0]->[1]; - if ($clapi_password =~ m/^md5__(.*)/) { - $clapi_password = $1; - } - - $self->{clapi_user} = $clapi_user; - $self->{clapi_password} = $clapi_password; - } - # check illegal characters ($status, $datas) = $self->{class_object_centreon}->custom_execute( request => "SELECT `value` FROM options WHERE `key` = 'gorgone_illegal_characters'", mode => 2 ); - if ($status == -1) { + if ($status == -1) { $self->{logger}->writeLogError('[legacycmd] Cannot get illegal characters'); return ; } @@ -804,6 +782,14 @@ sub event { sub run { my ($self, %options) = @_; + $self->{tpapi_clapi} = gorgone::class::tpapi::clapi->new(); + $self->{tpapi_clapi}->set_configuration( + config => $self->{tpapi}->get_configuration(name => $self->{tpapi_clapi_name}) + ); + + $self->{clapi_user} = $self->{tpapi_clapi}->get_username(); + $self->{clapi_password} = $self->{tpapi_clapi}->get_password(); + # Connect internal $connector->{internal_socket} = gorgone::standard::library::connect_com( zmq_type => 'ZMQ_DEALER', From c54430060da0f31bf7c07c08ec0a74903f3c326e Mon Sep 17 00:00:00 2001 From: sc979 <34628915+sc979@users.noreply.github.com> Date: Wed, 2 Feb 2022 17:25:15 +0100 Subject: [PATCH 640/948] fix(chore): dependabot github automation (#200) --- gorgone/.github/workflows/dependabot_jira.yml | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/gorgone/.github/workflows/dependabot_jira.yml b/gorgone/.github/workflows/dependabot_jira.yml index 1bcc25b7b2e..acaec2996f4 100644 --- a/gorgone/.github/workflows/dependabot_jira.yml +++ b/gorgone/.github/workflows/dependabot_jira.yml @@ -5,9 +5,13 @@ on: types: [ opened, reopened ] branches: [ develop ] +permissions: + pull-requests: read + env: JIRA_PROJECT_KEY: "MON" JIRA_ISSUE_TYPE: "Technical" + COMPONENT_NAME: "${{ github.event.pull_request.base.repo.name }}" jobs: create_ticket: @@ -19,10 +23,19 @@ jobs: id: date run: echo "CURRENT_DATE=$(date +'%Y-%m-%dT%H:%M:%S')" >> $GITHUB_ENV + - name: Check components name + id: name + if: ${{ env.COMPONENT_NAME == 'centreon'}} + run: echo "COMPONENT_NAME=centreon-web" >> $GITHUB_ENV + + - name: debug + id: debug + run: echo "component is ${{ env.COMPONENT_NAME }}." + - name: Login to Jira uses: atlassian/gajira-login@v2.0.0 id: login - with: + env: JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} @@ -30,10 +43,11 @@ jobs: - name: Create Jira Issue uses: atlassian/gajira-create@v2.0.1 id: create - with: + env: JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} + with: project: ${{ env.JIRA_PROJECT_KEY }} issuetype: ${{ env.JIRA_ISSUE_TYPE }} summary: | @@ -61,5 +75,5 @@ jobs: "customfield_10866": "${{ env.CURRENT_DATE }}", "labels": ["Dependabot"], "priority": {"name": "Highest"}, - "components":[{"name": "centreon-gorgone"}] + "components":[{"name": "${{ env.COMPONENT_NAME }}"}] }' From c2722183429b6a6b84501bacaf81c26579e4dbd1 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Fri, 18 Feb 2022 11:37:40 +0100 Subject: [PATCH 641/948] add capability to crypt internal communication (#205) --- gorgone/docs/configuration.md | 82 ++++--- gorgone/gorgone/class/core.pm | 197 ++++++++++++++-- gorgone/gorgone/class/module.pm | 174 ++++++++++++-- .../centreon/anomalydetection/class.pm | 6 +- .../centreon/anomalydetection/hooks.pm | 3 +- .../gorgone/modules/centreon/audit/class.pm | 6 +- .../gorgone/modules/centreon/audit/hooks.pm | 3 +- .../modules/centreon/autodiscovery/class.pm | 6 +- .../modules/centreon/autodiscovery/hooks.pm | 3 +- .../gorgone/modules/centreon/engine/class.pm | 11 +- .../gorgone/modules/centreon/engine/hooks.pm | 3 +- .../gorgone/modules/centreon/judge/class.pm | 18 +- .../gorgone/modules/centreon/judge/hooks.pm | 3 +- .../modules/centreon/legacycmd/class.pm | 6 +- .../modules/centreon/legacycmd/hooks.pm | 3 +- .../gorgone/modules/centreon/nodes/class.pm | 6 +- .../gorgone/modules/centreon/nodes/hooks.pm | 3 +- .../modules/centreon/statistics/class.pm | 6 +- .../modules/centreon/statistics/hooks.pm | 3 +- gorgone/gorgone/modules/core/action/class.pm | 11 +- gorgone/gorgone/modules/core/action/hooks.pm | 7 +- gorgone/gorgone/modules/core/cron/class.pm | 10 +- gorgone/gorgone/modules/core/cron/hooks.pm | 3 +- .../gorgone/modules/core/dbcleaner/class.pm | 18 +- .../gorgone/modules/core/dbcleaner/hooks.pm | 7 +- .../gorgone/modules/core/httpserver/class.pm | 9 +- .../gorgone/modules/core/httpserver/hooks.pm | 5 +- .../modules/core/httpserverng/class.pm | 23 +- .../modules/core/httpserverng/hooks.pm | 3 +- .../gorgone/modules/core/pipeline/class.pm | 6 +- .../gorgone/modules/core/pipeline/hooks.pm | 3 +- gorgone/gorgone/modules/core/proxy/class.pm | 24 +- gorgone/gorgone/modules/core/proxy/hooks.pm | 60 +++-- gorgone/gorgone/modules/core/pull/class.pm | 219 ++++++++++++++++++ gorgone/gorgone/modules/core/pull/hooks.pm | 190 ++++++--------- .../gorgone/modules/core/register/class.pm | 6 +- .../gorgone/modules/core/register/hooks.pm | 5 +- .../gorgone/modules/plugins/newtest/class.pm | 7 +- .../gorgone/modules/plugins/newtest/hooks.pm | 20 +- gorgone/gorgone/modules/plugins/scom/class.pm | 7 +- gorgone/gorgone/modules/plugins/scom/hooks.pm | 18 +- gorgone/gorgone/standard/api.pm | 41 ++-- gorgone/gorgone/standard/library.pm | 90 ++++++- 43 files changed, 960 insertions(+), 374 deletions(-) create mode 100644 gorgone/gorgone/modules/core/pull/class.pm diff --git a/gorgone/docs/configuration.md b/gorgone/docs/configuration.md index 81f10656ef8..214c959ea75 100644 --- a/gorgone/docs/configuration.md +++ b/gorgone/docs/configuration.md @@ -1,24 +1,24 @@ # Configuration -| Directive | Description | -| :- | :- | -| name | Name of the configuration | -| description | Short string to decribe the configuration | -| configuration | First configuration entry point | -| centreon | Entry point to set Centreon configuration | -| database | Entry point to set Centreon databases data source names and credentials | -| gorgonecore | Entry point to set Gorgone main configuration | -| modules | Table to load and configuration Gorgone modules | +| Directive | Description | +| :------------ | :---------------------------------------------------------------------- | +| name | Name of the configuration | +| description | Short string to decribe the configuration | +| configuration | First configuration entry point | +| centreon | Entry point to set Centreon configuration | +| database | Entry point to set Centreon databases data source names and credentials | +| gorgonecore | Entry point to set Gorgone main configuration | +| modules | Table to load and configuration Gorgone modules | ## *database* Usefull in a Centreon Central installation to access Centreon databases. -| Directive | Description | -| :- | :- | -| dsn | Data source name of the database | -| username | Username to access the database | -| password | Username's password | +| Directive | Description | +| :-------- | :------------------------------- | +| dsn | Data source name of the database | +| username | Username to access the database | +| password | Username's password | #### Example @@ -38,30 +38,36 @@ configuration: ## *gorgonecore* -| Directive | Description | Default value -| :- | :- | :- | -| internal_com_type | Type of the internal ZMQ socket | `ipc` | -| internal_com_path | Path to the internal ZMQ socket | `/tmp/gorgone/routing.ipc` | -| external_com_type | Type of the external ZMQ socket | `tcp` | -| external_com_path | Path to the external ZMQ socket | `*:5555` | -| timeout | Time in seconds before killing child processes when stopping Gorgone | `50` | -| gorgone_db_type | Type of the Gorgone database | `SQLite` | -| gorgone_db_name | Path and name of the database | `dbname=/var/lib/centreon-gorgone/history.sdb` | -| gorgone_db_host | Hostname/IP address of the server hosting the database | | -| gorgone_db_port | Port of the database listener | | -| gorgone_db_user | Username to access the database | | -| gorgone_db_password | Username's password | | -| hostname | Hostname of the server running Gorgone | Result of *hostname* system function. | -| id | Identifier of server running Gorgone | None. Must be unique over all Gorgone daemons. | -| privkey | Path to the Gorgone core private key | `keys/rsakey.priv.pem` | -| privkey | Path to the Gorgone core public key | `keys/rsakey.pub.pem` | -| cipher | Cipher used for encryption | `Cipher::AES` | -| keysize | Size in bytes of the symmetric encryption key | `32` | -| vector | Encryption vector | `0123456789012345` | -| fingerprint_mode | Validation mode of zmq nodes to connect (can be: always, first, strict) | `first` | -| fingerprint_mgr | Hash of the definition class to store fingerprints | | -| authorized_clients | Table of string-formated JWK thumbprints of clients public key | | -| proxy_name | Name of the proxy module definition | `proxy` (loaded internally) | +| Directive | Description | Default value | +| :-------------------- | :---------------------------------------------------------------------- | :--------------------------------------------- | +| internal_com_type | Type of the internal ZMQ socket | `ipc` | +| internal_com_path | Path to the internal ZMQ socket | `/tmp/gorgone/routing.ipc` | +| internal_com_crypt | Internal communication crypt enabled | `false` | +| internal_com_cipher | Internal communication cipher | `AES` | +| internal_com_padding | Internal communication padding | `1` (mean: PKCS5) | +| internal_com_keysize | Internal communication key size | `32` (bytes) | +| internal_com_rotation | Internal communication time before key rotation | `1440` (minutes) | +| internal_com_crypt | Crypt internal communication | `false` | +| external_com_type | Type of the external ZMQ socket | `tcp` | +| external_com_path | Path to the external ZMQ socket | `*:5555` | +| timeout | Time in seconds before killing child processes when stopping Gorgone | `50` | +| gorgone_db_type | Type of the Gorgone database | `SQLite` | +| gorgone_db_name | Path and name of the database | `dbname=/var/lib/centreon-gorgone/history.sdb` | +| gorgone_db_host | Hostname/IP address of the server hosting the database | | +| gorgone_db_port | Port of the database listener | | +| gorgone_db_user | Username to access the database | | +| gorgone_db_password | Username's password | | +| hostname | Hostname of the server running Gorgone | Result of *hostname* system function. | +| id | Identifier of server running Gorgone | None. Must be unique over all Gorgone daemons. | +| privkey | Path to the Gorgone core private key | `keys/rsakey.priv.pem` | +| pubkey | Path to the Gorgone core public key | `keys/rsakey.pub.pem` | +| cipher | Cipher used for encryption | `Cipher::AES` | +| keysize | Size in bytes of the symmetric encryption key | `32` | +| vector | Encryption vector | `0123456789012345` | +| fingerprint_mode | Validation mode of zmq nodes to connect (can be: always, first, strict) | `first` | +| fingerprint_mgr | Hash of the definition class to store fingerprints | | +| authorized_clients | Table of string-formated JWK thumbprints of clients public key | | +| proxy_name | Name of the proxy module definition | `proxy` (loaded internally) | #### Example diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 3c2f6284c4b..56310ada8c1 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -24,6 +24,7 @@ use strict; use warnings; use POSIX ":sys_wait_h"; use Sys::Hostname; +use Crypt::Mode::CBC; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); use gorgone::standard::library; @@ -169,6 +170,51 @@ sub init { if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_type}) || $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_type} eq ''); $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_path} = '/tmp/gorgone/routing-' . $time_hi . '.ipc' if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_path}) || $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_path} eq ''); + + if (defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_crypt}) && $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_crypt} =~ /^(?:true|1)$/i) { + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_crypt} = 1; + } else { + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_crypt} = 0; + } + + $self->{internal_crypt} = { enabled => 0 }; + if ($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_crypt} == 1) { + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_cipher} = 'AES' + if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_cipher}) || $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_cipher} eq ''); + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_padding} = 1 # PKCS5 padding + if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_padding}) || $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_padding} eq ''); + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_keysize} = 32 + if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_keysize}) || $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_keysize} eq ''); + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_rotation} = 1440 # minutes + if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_rotation}) || $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_rotation} eq ''); + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_rotation} *= 60; + + $self->{cipher} = Crypt::Mode::CBC->new( + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_cipher}, + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_padding} + ); + + my ($rv, $symkey, $iv); + ($rv, $symkey) = gorgone::standard::library::generate_symkey( + keysize => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_keysize} + ); + ($rv, $iv) = gorgone::standard::library::generate_symkey( + keysize => 16 + ); + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_core_key_ctime} = time(); + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_core_key} = $symkey; + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_core_oldkey} = undef; + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_identity_keys} = {}; + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_iv} = $iv; + + $self->{internal_crypt} = { + enabled => 1, + cipher => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_cipher}, + padding => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_padding}, + iv => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_iv} + }; + } + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{timeout} = defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{timeout}) && $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{timeout} =~ /(\d+)/ ? $1 : 50; @@ -393,7 +439,7 @@ sub load_modules { # Load internal functions foreach my $method_name (('addlistener', 'putlog', 'getlog', 'kill', 'ping', - 'getthumbprint', 'constatus', 'setcoreid', 'synclogs', 'loadmodule', 'unloadmodule', 'information')) { + 'getthumbprint', 'constatus', 'setcoreid', 'synclogs', 'loadmodule', 'unloadmodule', 'information', 'setmodulekey')) { unless ($self->{internal_register}->{$method_name} = gorgone::standard::library->can($method_name)) { $self->{logger}->writeLogError("[core] No function '$method_name'"); exit(1); @@ -401,13 +447,114 @@ sub load_modules { } } +sub broadcast_core_key { + my ($self, %options) = @_; + + my ($rv, $key) = gorgone::standard::library::generate_symkey( + keysize => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_keysize} + ); + $self->message_run( + message => '[BCASTCOREKEY] [] [] { "key": "' . unpack('H*', $key). '"}', + router_type => 'internal' + ); +} + +sub read_internal_message { + my ($self, %options) = @_; + + my ($identity, $message) = gorgone::standard::library::zmq_read_message( + socket => $self->{internal_socket}, + logger => $self->{logger} + ); + return undef if (!defined($identity)); + + if ($self->{internal_crypt}->{enabled} == 1) { + my $id = pack('H*', $identity); + my $keys; + if (defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_identity_keys}->{$id})) { + $keys = [ $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_identity_keys}->{$id}->{key} ]; + } else { + $keys = [ $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_core_key} ]; + push @$keys, $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_core_oldkey} + if (defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_core_oldkey})); + } + foreach my $key (@$keys) { + my $plaintext; + eval { + $plaintext = $self->{cipher}->decrypt($message, $key, $self->{internal_crypt}->{iv}); + }; + if (defined($plaintext) && $plaintext =~ /^\[[A-Za-z_\-]+?\]/) { + return ($identity, $plaintext); + } + } + + $self->{logger}->writeLogError("[core] decrypt issue ($id): " . ($@ ? $@ : 'no message')); + return undef; + } + + return ($identity, $message); +} + +sub send_internal_response { + my ($self, %options) = @_; + + zmq_sendmsg($self->{internal_socket}, pack('H*', $options{identity}), ZMQ_DONTWAIT | ZMQ_SNDMORE); + + my $response_type = defined($options{response_type}) ? $options{response_type} : 'ACK'; + my $data = gorgone::standard::library::json_encode(data => { code => $options{code}, data => $options{data} }); + # We add 'target' for 'PONG', 'SYNCLOGS'. Like that 'gorgone-proxy can get it + my $message = '[' . $response_type . '] [' . (defined($options{token}) ? $options{token} : '') . '] ' . ($response_type =~ /^PONG|SYNCLOGS$/ ? '[] ' : '') . $data; + + if ($self->{internal_crypt}->{enabled} == 1) { + eval { + $message = $self->{cipher}->encrypt( + $message, + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_core_key}, + $self->{internal_crypt}->{iv} + ); + }; + if ($@) { + $self->{logger}->writeLogError("[core] encrypt issue: " . $@); + return undef; + } + } + + zmq_sendmsg($self->{internal_socket}, $message, ZMQ_DONTWAIT); +} + +sub send_internal_message { + my ($self, %options) = @_; + + my $message = $options{message}; + if (!defined($message)) { + $message = gorgone::standard::library::build_protocol(%options); + } + zmq_sendmsg($self->{internal_socket}, $options{identity}, ZMQ_DONTWAIT | ZMQ_SNDMORE); + + if ($self->{internal_crypt}->{enabled} == 1) { + eval { + $message = $self->{cipher}->encrypt( + $message, + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_core_key}, + $self->{internal_crypt}->{iv} + ); + }; + if ($@) { + $self->{logger}->writeLogError("[core] encrypt issue: " . $@); + return undef; + } + } + + zmq_sendmsg($self->{internal_socket}, $message, ZMQ_DONTWAIT); +} + sub broadcast_run { my ($self, %options) = @_; - if ($options{action} eq 'BCASTLOGGER') { - my $data = gorgone::standard::library::json_decode(data => $options{data}, logger => $self->{logger}); - return if (!defined($data)); + my $data = gorgone::standard::library::json_decode(data => $options{data}, logger => $self->{logger}); + return if (!defined($data)); + if ($options{action} eq 'BCASTLOGGER') { if (defined($data->{content}->{severity}) && $data->{content}->{severity} ne '') { if ($data->{content}->{severity} eq 'default') { $self->{logger}->set_default_severity(); @@ -419,7 +566,7 @@ sub broadcast_run { foreach (keys %{$self->{modules_register}}) { $self->{modules_register}->{$_}->{broadcast}->( - socket => $self->{internal_socket}, + gorgone => $self, dbh => $self->{db_gorgone}, action => $options{action}, logger => $self->{logger}, @@ -427,6 +574,12 @@ sub broadcast_run { token => $options{token} ); } + + if ($options{action} eq 'BCASTCOREKEY') { + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_core_key_ctime} = time(); + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_core_oldkey} = $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_core_key}; + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_core_key} = pack('H*', $data->{key}); + } } sub message_run { @@ -447,7 +600,7 @@ sub message_run { $token = gorgone::standard::library::generate_token(); } - if ($action !~ /^(?:ADDLISTENER|PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID|SYNCLOGS|LOADMODULE|UNLOADMODULE|INFORMATION|GETTHUMBPRINT|BCAST.*)$/ && + if ($action !~ /^(?:ADDLISTENER|PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID|SETMODULEKEY|SYNCLOGS|LOADMODULE|UNLOADMODULE|INFORMATION|GETTHUMBPRINT|BCAST.*)$/ && !defined($target) && !defined($self->{modules_events}->{$action})) { gorgone::standard::library::add_history( dbh => $self->{db_gorgone}, @@ -494,7 +647,7 @@ sub message_run { $self->{counters}->{proxy}->{total}++; $self->{modules_register}->{ $self->{modules_id}->{ $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{proxy_name} } }->{routing}->( - socket => $self->{internal_socket}, + gorgone => $self, dbh => $self->{db_gorgone}, logger => $self->{logger}, action => $action, @@ -506,7 +659,7 @@ sub message_run { return ($token, 0); } - if ($action =~ /^(?:ADDLISTENER|PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID|SYNCLOGS|LOADMODULE|UNLOADMODULE|INFORMATION|GETTHUMBPRINT)$/) { + if ($action =~ /^(?:ADDLISTENER|PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID|SETMODULEKEY|SYNCLOGS|LOADMODULE|UNLOADMODULE|INFORMATION|GETTHUMBPRINT)$/) { my ($code, $response, $response_type) = $self->{internal_register}->{lc($action)}->( gorgone => $self, gorgone_config => $self->{config}->{configuration}->{gorgone}, @@ -530,7 +683,7 @@ sub message_run { return ($token, $code, $response, $response_type); } elsif ($action =~ /^BCAST(.*)$/) { - return (undef, 1, { message => "action '$action' is not known" }) if ($1 !~ /^LOGGER$/); + return (undef, 1, { message => "action '$action' is not known" }) if ($1 !~ /^(?:LOGGER|COREKEY)$/); $self->broadcast_run( action => $action, data => $data, @@ -538,14 +691,14 @@ sub message_run { ); } else { $self->{modules_register}->{$self->{modules_events}->{$action}->{module}->{package}}->{routing}->( - socket => $self->{internal_socket}, + gorgone => $self, dbh => $self->{db_gorgone}, logger => $self->{logger}, action => $action, token => $token, target => $target, data => $data, - hostname => $self->{hostname} + hostname => $self->{hostname}, ); } return ($token, 0); @@ -553,10 +706,7 @@ sub message_run { sub router_internal_event { while (1) { - my ($identity, $message) = gorgone::standard::library::zmq_read_message( - socket => $gorgone->{internal_socket}, - logger => $gorgone->{logger} - ); + my ($identity, $message) = $gorgone->read_internal_message(); last if (!defined($identity)); my ($token, $code, $response, $response_type) = $gorgone->message_run( @@ -564,8 +714,7 @@ sub router_internal_event { identity => $identity, router_type => 'internal', ); - gorgone::standard::library::zmq_core_response( - socket => $gorgone->{internal_socket}, + $gorgone->send_internal_response( identity => $identity, response_type => $response_type, data => $response, @@ -669,8 +818,7 @@ sub send_message_parent { my (%options) = @_; if ($options{router_type} eq 'internal') { - gorgone::standard::library::zmq_core_response( - socket => $gorgone->{internal_socket}, + $gorgone->send_internal_response( identity => $options{identity}, response_type => $options{response_type}, data => $options{data}, @@ -783,8 +931,15 @@ sub quit { sub check_exit_modules { my ($self, %options) = @_; - my $count = 0; my $current_time = time(); + + # check key rotate + if ($self->{internal_crypt}->{enabled} == 1 && + ($current_time - $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_core_key_ctime}) > $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_rotation}) { + $self->broadcast_core_key(); + } + + my $count = 0; if (time() - $self->{cb_timer_check} > 15 || $self->{stop} == 1) { if ($self->{stop} == 1 && (!defined($self->{sigterm_last_time}) || ($current_time - $self->{sigterm_last_time}) >= 10)) { $self->{sigterm_start_time} = time() if (!defined($self->{sigterm_start_time})); @@ -799,7 +954,6 @@ sub check_exit_modules { gorgone => $self, logger => $self->{logger}, dead_childs => $self->{return_child}, - internal_socket => $self->{internal_socket}, dbh => $self->{db_gorgone}, api_endpoints => $self->{api_endpoints} ); @@ -900,6 +1054,7 @@ sub run { foreach my $name (keys %{$gorgone->{modules_register}}) { $gorgone->{logger}->writeLogDebug("[core] Call init function from module '$name'"); $gorgone->{modules_register}->{$name}->{init}->( + gorgone => $gorgone, id => $gorgone->{id}, logger => $gorgone->{logger}, poll => $gorgone->{poll}, diff --git a/gorgone/gorgone/class/module.pm b/gorgone/gorgone/class/module.pm index c66f7d126a0..fc9835eb6e0 100644 --- a/gorgone/gorgone/class/module.pm +++ b/gorgone/gorgone/class/module.pm @@ -28,6 +28,9 @@ use gorgone::standard::library; use gorgone::standard::misc; use gorgone::class::tpapi; use JSON::XS; +use Crypt::Mode::CBC; +use ZMQ::LibZMQ4; +use ZMQ::Constants qw(:all); my %handlers = (DIE => {}); @@ -38,14 +41,37 @@ sub new { $self->{internal_socket} = undef; $self->{module_id} = $options{module_id}; + $self->{container_id} = $options{container_id}; + $self->{container} = ''; + $self->{container} = ' container ' . $self->{container_id} . ':' if (defined($self->{container_id})); + $self->{core_id} = $options{core_id}; $self->{logger} = $options{logger}; $self->{config} = $options{config}; $self->{exit_timeout} = (defined($options{config}->{exit_timeout}) && $options{config}->{exit_timeout} =~ /(\d+)/) ? $1 : 30; - $self->{config_core} = $options{config_core}->{gorgonecore}; + $self->{config_core} = $options{config_core}; $self->{config_db_centreon} = $options{config_db_centreon}; $self->{config_db_centstorage} = $options{config_db_centstorage}; $self->{stop} = 0; + $self->{fork} = 0; + + $self->{internal_crypt} = { enabled => 0 }; + if ($self->get_core_config(name => 'internal_com_crypt') == 1) { + $self->{cipher} = Crypt::Mode::CBC->new( + $self->get_core_config(name => 'internal_com_cipher'), + $self->get_core_config(name => 'internal_com_padding') + ); + + $self->{internal_crypt} = { + enabled => 1, + rotation => $self->get_core_config(name => 'internal_com_rotation'), + cipher => $self->get_core_config(name => 'internal_com_cipher'), + padding => $self->get_core_config(name => 'internal_com_padding'), + iv => $self->get_core_config(name => 'internal_com_iv'), + core_keys => [$self->get_core_config(name => 'internal_com_core_key'), $self->get_core_config(name => 'internal_com_core_oldkey')], + identity_keys => $self->get_core_config(name => 'internal_com_identity_keys') + }; + } $self->{tpapi} = gorgone::class::tpapi->new(); $self->{tpapi}->load_configuration(configuration => $options{config_core}->{tpapi}); @@ -67,26 +93,122 @@ sub class_handle_DIE { sub handle_DIE { my ($self, $msg) = @_; - $self->{logger}->writeLogError("[$self->{module_id}] Receiving DIE: $msg"); + $self->{logger}->writeLogError("[$self->{module_id}]$self->{container} Receiving DIE: $msg"); } sub generate_token { my ($self, %options) = @_; - + return gorgone::standard::library::generate_token(length => $options{length}); } -sub send_internal_action { +sub set_fork { my ($self, %options) = @_; - gorgone::standard::library::zmq_send_message( - socket => $self->{internal_socket}, - token => $options{token}, - action => $options{action}, - target => $options{target}, - data => $options{data}, - json_encode => defined($options{data_noencode}) ? undef : 1 + $self->{fork} = 1; +} + +sub get_core_config { + my ($self, %options) = @_; + + return $self->{config_core}->{gorgonecore} if (!defined($options{name})); + + return $self->{config_core}->{gorgonecore}->{ $options{name} }; +} + +sub read_message { + my ($self, %options) = @_; + + my $message = gorgone::standard::library::zmq_dealer_read_message(socket => defined($options{socket}) ? $options{socket} : $self->{internal_socket}); + return undef if (!defined($message)); + return $message if ($self->{internal_crypt}->{enabled} == 0); + + foreach my $key (@{$self->{internal_crypt}->{core_keys}}) { + next if (!defined($key)); + + my $plaintext; + eval { + $plaintext = $self->{cipher}->decrypt($message, $key, $self->{internal_crypt}->{iv}); + }; + if (defined($plaintext) && $plaintext =~ /^\[[A-Za-z_\-]+?\]/) { + return $plaintext; + } + } + + $self->{logger}->writeLogError("[$self->{module_id}]$self->{container} decrypt issue: " . ($@ ? $@ : 'no message')); + return undef; +} + +sub send_internal_key { + my ($self, %options) = @_; + + my $message = gorgone::standard::library::build_protocol( + action => 'SETMODULEKEY', + data => { key => unpack('H*', $options{key}) }, + json_encode => 1 ); + eval { + $message = $self->{cipher}->encrypt($message, $options{encrypt_key}, $self->{internal_crypt}->{iv}); + }; + if ($@) { + $self->{logger}->writeLogError("[$self->{module_id}]$self->{container} encrypt issue: " . $@); + return -1; + } + + zmq_sendmsg($options{socket}, $message, ZMQ_DONTWAIT); + return 0; +} + +sub send_internal_action { + my ($self, %options) = @_; + + my $message = $options{message}; + if (!defined($message)) { + $message = gorgone::standard::library::build_protocol( + token => $options{token}, + action => $options{action}, + target => $options{target}, + data => $options{data}, + json_encode => defined($options{data_noencode}) ? undef : 1 + ); + } + + my $socket = defined($options{socket}) ? $options{socket} : $self->{internal_socket}; + if ($self->{internal_crypt}->{enabled} == 1) { + my $identity = gorgone::standard::library::zmq_get_routing_id(socket => $socket); + + my $key = $self->{internal_crypt}->{core_keys}->[0]; + if ($self->{fork} == 0) { + if (!defined($self->{internal_crypt}->{identity_keys}->{$identity}) || + (time() - $self->{internal_crypt}->{identity_keys}->{$identity}->{ctime}) > ($self->{internal_crypt}->{rotation})) { + my ($rv, $genkey) = gorgone::standard::library::generate_symkey( + keysize => $self->get_core_config(name => 'internal_com_keysize') + ); + ($rv) = $self->send_internal_key( + socket => $socket, + key => $genkey, + encrypt_key => defined($self->{internal_crypt}->{identity_keys}->{$identity}) ? + $self->{internal_crypt}->{identity_keys}->{$identity}->{key} : $self->{internal_crypt}->{core_keys}->[0] + ); + return undef if ($rv == -1); + $self->{internal_crypt}->{identity_keys}->{$identity} = { + key => $genkey, + ctime => time() + }; + } + $key = $self->{internal_crypt}->{identity_keys}->{$identity}->{key}; + } + + eval { + $message = $self->{cipher}->encrypt($message, $key, $self->{internal_crypt}->{iv}); + }; + if ($@) { + $self->{logger}->writeLogError("[$self->{module_id}]$self->{container} encrypt issue: " . $@); + return undef; + } + } + + zmq_sendmsg($socket, $message, ZMQ_DONTWAIT); } sub send_log_msg_error { @@ -94,8 +216,8 @@ sub send_log_msg_error { return if (!defined($options{token})); - $self->{logger}->writeLogError("[$self->{module_id}] -$options{subname}- $options{number} $options{message}"); - gorgone::standard::library::zmq_send_message( + $self->{logger}->writeLogError("[$self->{module_id}]$self->{container} -$options{subname}- $options{number} $options{message}"); + $self->send_internal_action( socket => (defined($options{socket})) ? $options{socket} : $self->{internal_socket}, action => 'PUTLOG', token => $options{token}, @@ -111,7 +233,7 @@ sub send_log { return if (defined($options{logging}) && $options{logging} =~ /^(?:false|0)$/); - gorgone::standard::library::zmq_send_message( + $self->send_internal_action( socket => (defined($options{socket})) ? $options{socket} : $self->{internal_socket}, action => 'PUTLOG', token => $options{token}, @@ -128,9 +250,7 @@ sub json_encode { $encoded_arguments = JSON::XS->new->utf8->encode($options{argument}); }; if ($@) { - my $container = ''; - $container = 'container ' . $self->{container_id} . ': ' if (defined($self->{container_id})); - $self->{logger}->writeLogError("[$self->{module_id}] ${container}$options{method} - cannot encode json: $@"); + $self->{logger}->writeLogError("[$self->{module_id}]$self->{container} $options{method} - cannot encode json: $@"); return 1; } @@ -145,9 +265,7 @@ sub json_decode { $decoded_arguments = JSON::XS->new->utf8->decode($options{argument}); }; if ($@) { - my $container = ''; - $container = 'container ' . $self->{container_id} . ': ' if (defined($self->{container_id})); - $self->{logger}->writeLogError("[$self->{module_id}] ${container}$options{method} - cannot decode json: $@"); + $self->{logger}->writeLogError("[$self->{module_id}]$self->{container} $options{method} - cannot decode json: $@"); if (defined($options{token})) { $self->send_log( code => GORGONE_ACTION_FINISH_KO, @@ -172,9 +290,7 @@ sub execute_shell_cmd { wait_exit => 1, ); if ($lerror == -1 || ($exit_code >> 8) != 0) { - my $container = ''; - $container = 'container ' . $self->{container_id} . ': ' if (defined($self->{container_id})); - $self->{logger}->writeLogError("[$self->{module_id}] ${container}command execution issue $options{cmd} : " . $stdout); + $self->{logger}->writeLogError("[$self->{module_id}]$self->{container} command execution issue $options{cmd} : " . $stdout); return -1; } @@ -203,4 +319,16 @@ sub action_bcastlogger { } } +sub action_bcastcorekey { + my ($self, %options) = @_; + + return if ($self->{internal_crypt}->{enabled} == 0); + + if (defined($options{data}->{key})) { + $self->{logger}->writeLogDebug("[$self->{module_id}]$self->{container} core key changed"); + $self->{internal_crypt}->{core_keys}->[1] = $self->{internal_crypt}->{core_keys}->[0]; + $self->{internal_crypt}->{core_keys}->[0] = pack('H*', $options{data}->{key}); + } +} + 1; diff --git a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm index 3c51a2ffe19..f39a22e5c06 100644 --- a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm +++ b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm @@ -626,7 +626,7 @@ sub action_saasregister { sub event { while (1) { - my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); + my $message = $connector->read_message(); last if (!defined($message)); $connector->{logger}->writeLogDebug("[anomalydetection] Event: $message"); @@ -663,8 +663,8 @@ sub run { zmq_type => 'ZMQ_DEALER', name => 'gorgone-anomalydetection', logger => $self->{logger}, - type => $self->{config_core}->{internal_com_type}, - path => $self->{config_core}->{internal_com_path} + type => $self->get_core_config(name => 'internal_com_type'), + path => $self->get_core_config(name => 'internal_com_path') ); $connector->send_internal_action( action => 'CENTREONADREADY', diff --git a/gorgone/gorgone/modules/centreon/anomalydetection/hooks.pm b/gorgone/gorgone/modules/centreon/anomalydetection/hooks.pm index 27c2e256aaf..c2b9b6b739a 100644 --- a/gorgone/gorgone/modules/centreon/anomalydetection/hooks.pm +++ b/gorgone/gorgone/modules/centreon/anomalydetection/hooks.pm @@ -74,8 +74,7 @@ sub routing { return undef; } - gorgone::standard::library::zmq_send_message( - socket => $options{socket}, + $options{gorgone}->send_internal_message( identity => 'gorgone-anomalydetection', action => $options{action}, data => $options{data}, diff --git a/gorgone/gorgone/modules/centreon/audit/class.pm b/gorgone/gorgone/modules/centreon/audit/class.pm index 8bb4b2733a1..75bbc10001e 100644 --- a/gorgone/gorgone/modules/centreon/audit/class.pm +++ b/gorgone/gorgone/modules/centreon/audit/class.pm @@ -282,7 +282,7 @@ sub action_centreonauditschedule { sub event { while (1) { - my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); + my $message = $connector->read_message(); last if (!defined($message)); $connector->{logger}->writeLogDebug("[audit] Event: $message"); @@ -342,8 +342,8 @@ sub run { zmq_type => 'ZMQ_DEALER', name => 'gorgone-audit', logger => $self->{logger}, - type => $self->{config_core}->{internal_com_type}, - path => $self->{config_core}->{internal_com_path} + type => $self->get_core_config(name => 'internal_com_type'), + path => $self->get_core_config(name => 'internal_com_path') ); $connector->send_internal_action( action => 'CENTREONAUDITREADY', diff --git a/gorgone/gorgone/modules/centreon/audit/hooks.pm b/gorgone/gorgone/modules/centreon/audit/hooks.pm index c74c030db11..1118c65c244 100644 --- a/gorgone/gorgone/modules/centreon/audit/hooks.pm +++ b/gorgone/gorgone/modules/centreon/audit/hooks.pm @@ -76,8 +76,7 @@ sub routing { return undef; } - gorgone::standard::library::zmq_send_message( - socket => $options{socket}, + $options{gorgone}->send_internal_message( identity => 'gorgone-audit', action => $options{action}, data => $options{data}, diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm index b336c553824..beeab52eaca 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm @@ -1059,7 +1059,7 @@ sub is_hdisco_synced { sub event { while (1) { - my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); + my $message = $connector->read_message(); last if (!defined($message)); $connector->{logger}->writeLogDebug("[autodiscovery] Event: $message"); @@ -1121,8 +1121,8 @@ sub run { zmq_type => 'ZMQ_DEALER', name => 'gorgone-autodiscovery', logger => $self->{logger}, - type => $self->{config_core}->{internal_com_type}, - path => $self->{config_core}->{internal_com_path} + type => $self->get_core_config(name => 'internal_com_type'), + path => $self->get_core_config(name => 'internal_com_path') ); $connector->send_internal_action( action => 'AUTODISCOVERYREADY', diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm b/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm index 1bf86604b4c..52c370513cd 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm @@ -80,8 +80,7 @@ sub routing { return undef; } - gorgone::standard::library::zmq_send_message( - socket => $options{socket}, + $options{gorgone}->send_internal_message( identity => 'gorgone-autodiscovery', action => $options{action}, data => $options{data}, diff --git a/gorgone/gorgone/modules/centreon/engine/class.pm b/gorgone/gorgone/modules/centreon/engine/class.pm index 96122a8a9fc..9e530274faa 100644 --- a/gorgone/gorgone/modules/centreon/engine/class.pm +++ b/gorgone/gorgone/modules/centreon/engine/class.pm @@ -211,8 +211,8 @@ sub action_run { name => 'gorgone-engine-'. $$, logger => $self->{logger}, zmq_linger => 60000, - type => $self->{config_core}->{internal_com_type}, - path => $self->{config_core}->{internal_com_path} + type => $self->get_core_config(name => 'internal_com_type'), + path => $self->get_core_config(name => 'internal_com_path') ); if ($options{action} eq 'ENGINECOMMAND') { @@ -259,6 +259,7 @@ sub create_child { } if ($child_pid == 0) { + $self->set_fork(); $self->action_run(action => $action, token => $token, data => $data); exit(0); } @@ -266,7 +267,7 @@ sub create_child { sub event { while (1) { - my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); + my $message = $connector->read_message(); last if (!defined($message)); $connector->{logger}->writeLogDebug("[engine] Event: $message"); @@ -285,8 +286,8 @@ sub run { zmq_type => 'ZMQ_DEALER', name => 'gorgone-engine', logger => $self->{logger}, - type => $self->{config_core}->{internal_com_type}, - path => $self->{config_core}->{internal_com_path} + type => $self->get_core_config(name => 'internal_com_type'), + path => $self->get_core_config(name => 'internal_com_path') ); $connector->send_internal_action( action => 'ENGINEREADY', diff --git a/gorgone/gorgone/modules/centreon/engine/hooks.pm b/gorgone/gorgone/modules/centreon/engine/hooks.pm index 3f98e9f6dc8..de9f2129831 100644 --- a/gorgone/gorgone/modules/centreon/engine/hooks.pm +++ b/gorgone/gorgone/modules/centreon/engine/hooks.pm @@ -72,8 +72,7 @@ sub routing { return undef; } - gorgone::standard::library::zmq_send_message( - socket => $options{socket}, + $options{gorgone}->send_internal_message( identity => 'gorgone-engine', action => $options{action}, data => $options{data}, diff --git a/gorgone/gorgone/modules/centreon/judge/class.pm b/gorgone/gorgone/modules/centreon/judge/class.pm index 15cbaa43206..5f2ea5d23df 100644 --- a/gorgone/gorgone/modules/centreon/judge/class.pm +++ b/gorgone/gorgone/modules/centreon/judge/class.pm @@ -386,7 +386,7 @@ sub action_judgelistener { sub event { while (1) { - my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); + my $message = $connector->read_message(); last if (!defined($message)); $connector->{logger}->writeLogDebug("[judge] -class- event: $message"); @@ -541,8 +541,8 @@ sub run { zmq_type => 'ZMQ_DEALER', name => 'gorgone-judge', logger => $self->{logger}, - type => $self->{config_core}->{internal_com_type}, - path => $self->{config_core}->{internal_com_path} + type => $self->get_core_config(name => 'internal_com_type'), + path => $self->get_core_config(name => 'internal_com_path') ); $connector->send_internal_action( action => 'JUDGEREADY', @@ -574,12 +574,12 @@ sub run { $self->{class_object_centreon} = gorgone::class::sqlquery->new(logger => $self->{logger}, db_centreon => $self->{db_centreon}); $self->{db_gorgone} = gorgone::class::db->new( - type => $self->{config_core}->{gorgone_db_type}, - db => $self->{config_core}->{gorgone_db_name}, - host => $self->{config_core}->{gorgone_db_host}, - port => $self->{config_core}->{gorgone_db_port}, - user => $self->{config_core}->{gorgone_db_user}, - password => $self->{config_core}->{gorgone_db_password}, + type => $self->get_core_config(name => 'gorgone_db_type'), + db => $self->get_core_config(name => 'gorgone_db_name'), + host => $self->get_core_config(name => 'gorgone_db_host'), + port => $self->get_core_config(name => 'gorgone_db_port'), + user => $self->get_core_config(name => 'gorgone_db_user'), + password => $self->get_core_config(name => 'gorgone_db_password'), force => 2, logger => $self->{logger} ); diff --git a/gorgone/gorgone/modules/centreon/judge/hooks.pm b/gorgone/gorgone/modules/centreon/judge/hooks.pm index 25d1f4835c0..63bb9d75077 100644 --- a/gorgone/gorgone/modules/centreon/judge/hooks.pm +++ b/gorgone/gorgone/modules/centreon/judge/hooks.pm @@ -77,8 +77,7 @@ sub routing { return undef; } - gorgone::standard::library::zmq_send_message( - socket => $options{socket}, + $options{gorgone}->send_internal_message( identity => 'gorgone-judge', action => $options{action}, data => $options{data}, diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index 98a38d1ea34..e64a1d792b2 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -762,7 +762,7 @@ sub action_centreoncommand { sub event { while (1) { - my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); + my $message = $connector->read_message(); last if (!defined($message)); $connector->{logger}->writeLogDebug("[legacycmd] Event: $message"); @@ -795,8 +795,8 @@ sub run { zmq_type => 'ZMQ_DEALER', name => 'gorgone-legacycmd', logger => $self->{logger}, - type => $self->{config_core}->{internal_com_type}, - path => $self->{config_core}->{internal_com_path} + type => $self->get_core_config(name => 'internal_com_type'), + path => $self->get_core_config(name => 'internal_com_path') ); $connector->send_internal_action( action => 'LEGACYCMDREADY', diff --git a/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm b/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm index 73638c07017..4cba1bdfedd 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm @@ -78,8 +78,7 @@ sub routing { return undef; } - gorgone::standard::library::zmq_send_message( - socket => $options{socket}, + $options{gorgone}->send_internal_message( identity => 'gorgone-legacycmd', action => $options{action}, data => $options{data}, diff --git a/gorgone/gorgone/modules/centreon/nodes/class.pm b/gorgone/gorgone/modules/centreon/nodes/class.pm index 9f29949ba79..3e8bf50fae6 100644 --- a/gorgone/gorgone/modules/centreon/nodes/class.pm +++ b/gorgone/gorgone/modules/centreon/nodes/class.pm @@ -210,7 +210,7 @@ sub action_centreonnodessync { sub event { while (1) { - my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); + my $message = $connector->read_message(); last if (!defined($message)); $connector->{logger}->writeLogDebug("[nodes] Event: $message"); @@ -246,8 +246,8 @@ sub run { zmq_type => 'ZMQ_DEALER', name => 'gorgone-nodes', logger => $self->{logger}, - type => $self->{config_core}->{internal_com_type}, - path => $self->{config_core}->{internal_com_path} + type => $self->get_core_config(name => 'internal_com_type'), + path => $self->get_core_config(name => 'internal_com_path') ); $connector->send_internal_action( action => 'CENTREONNODESREADY', diff --git a/gorgone/gorgone/modules/centreon/nodes/hooks.pm b/gorgone/gorgone/modules/centreon/nodes/hooks.pm index 309acc0643d..8c3b9797731 100644 --- a/gorgone/gorgone/modules/centreon/nodes/hooks.pm +++ b/gorgone/gorgone/modules/centreon/nodes/hooks.pm @@ -75,8 +75,7 @@ sub routing { return undef; } - gorgone::standard::library::zmq_send_message( - socket => $options{socket}, + $options{gorgone}->send_internal_message( identity => 'gorgone-nodes', action => $options{action}, data => $options{data}, diff --git a/gorgone/gorgone/modules/centreon/statistics/class.pm b/gorgone/gorgone/modules/centreon/statistics/class.pm index e7976f9669f..cdbf1851279 100644 --- a/gorgone/gorgone/modules/centreon/statistics/class.pm +++ b/gorgone/gorgone/modules/centreon/statistics/class.pm @@ -584,7 +584,7 @@ sub rrd_update { sub event { while (1) { - my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); + my $message = $connector->read_message(); last if (!defined($message)); $connector->{logger}->writeLogDebug("[statistics] Event: $message"); @@ -609,8 +609,8 @@ sub run { zmq_type => 'ZMQ_DEALER', name => 'gorgone-statistics', logger => $self->{logger}, - type => $self->{config_core}->{internal_com_type}, - path => $self->{config_core}->{internal_com_path} + type => $self->get_core_config(name => 'internal_com_type'), + path => $self->get_core_config(name => 'internal_com_path') ); $connector->send_internal_action( action => 'STATISTICSREADY', diff --git a/gorgone/gorgone/modules/centreon/statistics/hooks.pm b/gorgone/gorgone/modules/centreon/statistics/hooks.pm index f862b23a950..edb3d0c4960 100644 --- a/gorgone/gorgone/modules/centreon/statistics/hooks.pm +++ b/gorgone/gorgone/modules/centreon/statistics/hooks.pm @@ -88,8 +88,7 @@ sub routing { return undef; } - gorgone::standard::library::zmq_send_message( - socket => $options{socket}, + $options{gorgone}->send_internal_message( identity => 'gorgone-statistics', action => $options{action}, data => $options{data}, diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index 482081857ed..7a92a1946b7 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -663,8 +663,8 @@ sub action_run { name => 'gorgone-action-'. $$, logger => $self->{logger}, zmq_linger => 60000, - type => $self->{config_core}->{internal_com_type}, - path => $self->{config_core}->{internal_com_path} + type => $self->get_core_config(name => 'internal_com_type'), + path => $self->get_core_config(name => 'internal_com_path') ); if ($options{action} eq 'COMMAND') { @@ -721,6 +721,7 @@ sub create_child { } if ($child_pid == 0) { + $self->set_fork(); $self->action_run(action => $options{action}, token => $options{token}, data => $options{data}); exit(0); } else { @@ -732,7 +733,7 @@ sub create_child { sub event { while (1) { - my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); + my $message = $connector->read_message(); last if (!defined($message)); $connector->{logger}->writeLogDebug("[action] Event: $message"); @@ -763,8 +764,8 @@ sub run { zmq_type => 'ZMQ_DEALER', name => 'gorgone-action', logger => $self->{logger}, - type => $self->{config_core}->{internal_com_type}, - path => $self->{config_core}->{internal_com_path} + type => $self->get_core_config(name => 'internal_com_type'), + path => $self->get_core_config(name => 'internal_com_path') ); $connector->send_internal_action( action => 'ACTIONREADY', diff --git a/gorgone/gorgone/modules/core/action/hooks.pm b/gorgone/gorgone/modules/core/action/hooks.pm index 1817786d193..68cd265920a 100644 --- a/gorgone/gorgone/modules/core/action/hooks.pm +++ b/gorgone/gorgone/modules/core/action/hooks.pm @@ -73,8 +73,7 @@ sub routing { return undef; } - gorgone::standard::library::zmq_send_message( - socket => $options{socket}, + $options{gorgone}->send_internal_message( identity => 'gorgone-action', action => $options{action}, data => $options{data}, @@ -135,7 +134,7 @@ sub broadcast { # Specific functions sub create_child { my (%options) = @_; - + $options{logger}->writeLogInfo("[action] Create module 'action' process"); my $child_pid = fork(); if ($child_pid == 0) { @@ -144,7 +143,7 @@ sub create_child { logger => $options{logger}, module_id => NAME, config_core => $config_core, - config => $config, + config => $config ); $module->run(); exit(0); diff --git a/gorgone/gorgone/modules/core/cron/class.pm b/gorgone/gorgone/modules/core/cron/class.pm index a9fc1d21b15..9d893476302 100644 --- a/gorgone/gorgone/modules/core/cron/class.pm +++ b/gorgone/gorgone/modules/core/cron/class.pm @@ -200,6 +200,7 @@ sub action_addcron { $definition->{timespec}, $definition->{id}, { + connector => $connector, socket => $connector->{internal_socket}, logger => $self->{logger}, definition => $definition @@ -372,7 +373,7 @@ sub action_deletecron { sub event { while (1) { - my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); + my $message = $connector->read_message(); last if (!defined($message)); $connector->{logger}->writeLogDebug("[cron] Event: $message"); @@ -416,7 +417,7 @@ sub dispatcher { my $token = (defined($options->{definition}->{keep_token})) && $options->{definition}->{keep_token} =~ /true|1/i ? $options->{definition}->{id} : undef; - gorgone::standard::library::zmq_send_message( + $options->{connector}->send_internal_action( socket => $options->{socket}, token => $token, action => $options->{definition}->{action}, @@ -446,8 +447,8 @@ sub run { zmq_type => 'ZMQ_DEALER', name => 'gorgone-cron', logger => $self->{logger}, - type => $self->{config_core}->{internal_com_type}, - path => $self->{config_core}->{internal_com_path} + type => $self->get_core_config(name => 'internal_com_type'), + path => $self->get_core_config(name => 'internal_com_path') ); $connector->send_internal_action( action => 'CRONREADY', @@ -476,6 +477,7 @@ sub run { $definition->{timespec}, $definition->{id}, { + connector => $connector, socket => $connector->{internal_socket}, logger => $self->{logger}, definition => $definition diff --git a/gorgone/gorgone/modules/core/cron/hooks.pm b/gorgone/gorgone/modules/core/cron/hooks.pm index 75bc1d57cfd..924f676f603 100644 --- a/gorgone/gorgone/modules/core/cron/hooks.pm +++ b/gorgone/gorgone/modules/core/cron/hooks.pm @@ -74,8 +74,7 @@ sub routing { return undef; } - gorgone::standard::library::zmq_send_message( - socket => $options{socket}, + $options{gorgone}->send_internal_message( identity => 'gorgone-cron', action => $options{action}, data => $options{data}, diff --git a/gorgone/gorgone/modules/core/dbcleaner/class.pm b/gorgone/gorgone/modules/core/dbcleaner/class.pm index 26c1aff0c57..9bdf3c7a37c 100644 --- a/gorgone/gorgone/modules/core/dbcleaner/class.pm +++ b/gorgone/gorgone/modules/core/dbcleaner/class.pm @@ -152,7 +152,7 @@ sub action_dbclean { sub event { while (1) { - my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); + my $message = $connector->read_message(); last if (!defined($message)); $connector->{logger}->writeLogDebug("[dbcleaner] Event: $message"); @@ -177,8 +177,8 @@ sub run { zmq_type => 'ZMQ_DEALER', name => 'gorgone-dbcleaner', logger => $self->{logger}, - type => $self->{config_core}->{internal_com_type}, - path => $self->{config_core}->{internal_com_path} + type => $self->get_core_config(name => 'internal_com_type'), + path => $self->get_core_config(name => 'internal_com_path') ); $connector->send_internal_action( action => 'DBCLEANERREADY', @@ -193,12 +193,12 @@ sub run { ]; $self->{db_gorgone} = gorgone::class::db->new( - type => $self->{config_core}->{gorgone_db_type}, - db => $self->{config_core}->{gorgone_db_name}, - host => $self->{config_core}->{gorgone_db_host}, - port => $self->{config_core}->{gorgone_db_port}, - user => $self->{config_core}->{gorgone_db_user}, - password => $self->{config_core}->{gorgone_db_password}, + type => $self->get_core_config(name => 'gorgone_db_type'), + db => $self->get_core_config(name => 'gorgone_db_name'), + host => $self->get_core_config(name => 'gorgone_db_host'), + port => $self->get_core_config(name => 'gorgone_db_port'), + user => $self->get_core_config(name => 'gorgone_db_user'), + password => $self->get_core_config(name => 'gorgone_db_password'), force => 2, logger => $self->{logger} ); diff --git a/gorgone/gorgone/modules/core/dbcleaner/hooks.pm b/gorgone/gorgone/modules/core/dbcleaner/hooks.pm index 63feac14528..5dd51d9b092 100644 --- a/gorgone/gorgone/modules/core/dbcleaner/hooks.pm +++ b/gorgone/gorgone/modules/core/dbcleaner/hooks.pm @@ -29,7 +29,7 @@ use gorgone::standard::constants qw(:all); use constant NAMESPACE => 'core'; use constant NAME => 'dbcleaner'; use constant EVENTS => [ - { event => 'DBCLEANERREADY' }, + { event => 'DBCLEANERREADY' } ]; my $config_core; @@ -81,12 +81,11 @@ sub routing { return undef; } - gorgone::standard::library::zmq_send_message( - socket => $options{socket}, + $options{gorgone}->send_internal_message( identity => 'gorgone-dbcleaner', action => $options{action}, data => $options{data}, - token => $options{token}, + token => $options{token} ); } diff --git a/gorgone/gorgone/modules/core/httpserver/class.pm b/gorgone/gorgone/modules/core/httpserver/class.pm index 9994114bd13..1ed7a797bee 100644 --- a/gorgone/gorgone/modules/core/httpserver/class.pm +++ b/gorgone/gorgone/modules/core/httpserver/class.pm @@ -106,7 +106,7 @@ sub class_handle_HUP { sub event { while (1) { - my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); + my $message = $connector->read_message(); last if (!defined($message)); $connector->{logger}->writeLogDebug("[httpserver] Event: $message"); @@ -160,8 +160,8 @@ sub run { zmq_type => 'ZMQ_DEALER', name => 'gorgone-httpserver', logger => $self->{logger}, - type => $self->{config_core}->{internal_com_type}, - path => $self->{config_core}->{internal_com_path} + type => $self->get_core_config(name => 'internal_com_type'), + path => $self->get_core_config(name => 'internal_com_path') ); $connector->send_internal_action( action => 'HTTPSERVERREADY', @@ -340,7 +340,8 @@ sub api_call { content => $content, socket => $connector->{internal_socket}, logger => $self->{logger}, - api_endpoints => $self->{api_endpoints} + api_endpoints => $self->{api_endpoints}, + module => $self ); return $response; diff --git a/gorgone/gorgone/modules/core/httpserver/hooks.pm b/gorgone/gorgone/modules/core/httpserver/hooks.pm index 242e383796d..c36515aa036 100644 --- a/gorgone/gorgone/modules/core/httpserver/hooks.pm +++ b/gorgone/gorgone/modules/core/httpserver/hooks.pm @@ -84,12 +84,11 @@ sub routing { return undef; } - gorgone::standard::library::zmq_send_message( - socket => $options{socket}, + $options{gorgone}->send_internal_message( identity => 'gorgone-httpserver', action => $options{action}, data => $options{data}, - token => $options{token}, + token => $options{token} ); } diff --git a/gorgone/gorgone/modules/core/httpserverng/class.pm b/gorgone/gorgone/modules/core/httpserverng/class.pm index bf14763ba9e..0352e952f4b 100644 --- a/gorgone/gorgone/modules/core/httpserverng/class.pm +++ b/gorgone/gorgone/modules/core/httpserverng/class.pm @@ -239,8 +239,8 @@ sub run { zmq_type => 'ZMQ_DEALER', name => 'gorgone-httpserverng', logger => $self->{logger}, - type => $self->{config_core}->{internal_com_type}, - path => $self->{config_core}->{internal_com_path} + type => $self->get_core_config(name => 'internal_com_type'), + path => $self->get_core_config(name => 'internal_com_path') ); $self->send_internal_action( action => 'HTTPSERVERNGREADY', @@ -380,7 +380,7 @@ sub read_zmq_events { while (my $events = gorgone::standard::library::zmq_events(socket => $self->{internal_socket})) { if ($events & ZMQ_POLLIN) { - my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); + my $message = $connector->read_message(); $connector->{logger}->writeLogDebug('[httpserverng] zmq message received: ' . $message); if ($message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m || $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+(.*)$/m) { @@ -392,6 +392,9 @@ sub read_zmq_events { $connector->read_log_event(token => $token, data => $data); } } + if ((my $method = $connector->can('action_' . lc($action)))) { + $method->($connector, token => $token, data => $data); + } } } else { last; @@ -442,12 +445,11 @@ sub api_call { sub get_log { my ($self, %options) = @_; - if (defined($options{target}) && $options{target} ne '') { - gorgone::standard::library::zmq_send_message( - socket => $self->{internal_socket}, + if (defined($options{target}) && $options{target} ne '') { + $self->send_internal_action( target => $options{target}, action => 'GETLOG', - json_encode => 1 + data => {} ); $self->read_zmq_events(); } @@ -463,16 +465,15 @@ sub get_log { mojo => $options{mojo} }; - gorgone::standard::library::zmq_send_message( - socket => $self->{internal_socket}, + $self->send_internal_action( action => 'GETLOG', token => $token_log, data => { token => $options{token}, %{$options{parameters}} - }, - json_encode => 1 + } ); + $self->read_zmq_events(); # keep reference tx to avoid "Transaction already destroyed" diff --git a/gorgone/gorgone/modules/core/httpserverng/hooks.pm b/gorgone/gorgone/modules/core/httpserverng/hooks.pm index c0b38ce3e62..b43a6018fca 100644 --- a/gorgone/gorgone/modules/core/httpserverng/hooks.pm +++ b/gorgone/gorgone/modules/core/httpserverng/hooks.pm @@ -85,8 +85,7 @@ sub routing { return undef; } - gorgone::standard::library::zmq_send_message( - socket => $options{socket}, + $options{gorgone}->send_internal_message( identity => 'gorgone-httpserverng', action => $options{action}, data => $options{data}, diff --git a/gorgone/gorgone/modules/core/pipeline/class.pm b/gorgone/gorgone/modules/core/pipeline/class.pm index 852b25e4b07..82c412fd01a 100644 --- a/gorgone/gorgone/modules/core/pipeline/class.pm +++ b/gorgone/gorgone/modules/core/pipeline/class.pm @@ -214,7 +214,7 @@ sub check_timeout { sub event { while (1) { - my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); + my $message = $connector->read_message(); last if (!defined($message)); $connector->{logger}->writeLogDebug("[pipeline] -class- event: $message"); @@ -239,8 +239,8 @@ sub run { zmq_type => 'ZMQ_DEALER', name => 'gorgone-pipeline', logger => $self->{logger}, - type => $self->{config_core}->{internal_com_type}, - path => $self->{config_core}->{internal_com_path} + type => $self->get_core_config(name => 'internal_com_type'), + path => $self->get_core_config(name => 'internal_com_path') ); $connector->send_internal_action( action => 'PIPELINEREADY', diff --git a/gorgone/gorgone/modules/core/pipeline/hooks.pm b/gorgone/gorgone/modules/core/pipeline/hooks.pm index 7aea7ae81e6..38facc35c4e 100644 --- a/gorgone/gorgone/modules/core/pipeline/hooks.pm +++ b/gorgone/gorgone/modules/core/pipeline/hooks.pm @@ -82,8 +82,7 @@ sub routing { return undef; } - gorgone::standard::library::zmq_send_message( - socket => $options{socket}, + $options{gorgone}->send_internal_message( identity => 'gorgone-pipeline', action => $options{action}, data => $options{data}, diff --git a/gorgone/gorgone/modules/core/proxy/class.pm b/gorgone/gorgone/modules/core/proxy/class.pm index 44ce8e234da..0029ea88e18 100644 --- a/gorgone/gorgone/modules/core/proxy/class.pm +++ b/gorgone/gorgone/modules/core/proxy/class.pm @@ -95,7 +95,7 @@ sub exit_process { exit(0); } -sub read_message { +sub read_message_client { my (%options) = @_; return undef if (!defined($options{identity}) || $options{identity} !~ /^gorgone-proxy-(.*?)-(.*?)$/); @@ -159,20 +159,20 @@ sub connect { vector => $self->{clients}->{ $options{id} }->{vector}, client_pubkey => defined($self->{clients}->{ $options{id} }->{client_pubkey}) && $self->{clients}->{ $options{id} }->{client_pubkey} ne '' - ? $self->{clients}->{ $options{id} }->{client_pubkey} : $self->{config_core}->{pubkey}, + ? $self->{clients}->{ $options{id} }->{client_pubkey} : $self->get_core_config(name => 'pubkey'), client_privkey => defined($self->{clients}->{ $options{id} }->{client_privkey}) && $self->{clients}->{ $options{id} }->{client_privkey} ne '' - ? $self->{clients}->{ $options{id} }->{client_privkey} : $self->{config_core}->{privkey}, + ? $self->{clients}->{ $options{id} }->{client_privkey} : $self->get_core_config(name => 'privkey'), target_type => defined($self->{clients}->{ $options{id} }->{target_type}) ? $self->{clients}->{ $options{id} }->{target_type} : 'tcp', target_path => defined($self->{clients}->{ $options{id} }->{target_path}) ? $self->{clients}->{ $options{id} }->{target_path} : $self->{clients}->{ $options{id} }->{address} . ':' . $self->{clients}->{ $options{id} }->{port}, - config_core => $self->{config_core}, + config_core => $self->get_core_config(), logger => $self->{logger} ); - $self->{clients}->{ $options{id} }->{class}->init(callback => \&read_message); + $self->{clients}->{ $options{id} }->{class}->init(callback => \&read_message_client); } elsif ($self->{clients}->{ $options{id} }->{type} eq 'push_ssh') { $self->{clients}->{$options{id}}->{class} = gorgone::modules::core::proxy::sshclient->new(logger => $self->{logger}); my $code = $self->{clients}->{$options{id}}->{class}->open_session( @@ -232,8 +232,8 @@ sub action_proxyaddnode { zmq_type => 'ZMQ_DEALER', name => 'gorgone-proxy-channel-' . $data->{id}, logger => $self->{logger}, - type => $self->{config_core}->{internal_com_type}, - path => $self->{config_core}->{internal_com_path} + type => $self->get_core_config(name => 'internal_com_type'), + path => $self->get_core_config(name => 'internal_com_path') ); $self->send_internal_action( action => 'PROXYREADY', @@ -388,6 +388,10 @@ sub proxy { (undef, $data) = $connector->json_decode(argument => $data); $connector->action_bcastlogger(data => $data); return ; + } elsif ($action eq 'BCASTCOREKEY' && $target_complete eq '') { + (undef, $data) = $connector->json_decode(argument => $data); + $connector->action_bcastcorekey(data => $data); + return ; } elsif ($action eq 'PROXYCLOSECONNECTION') { $connector->action_proxycloseconnection(data => $data); return ; @@ -468,7 +472,7 @@ sub event_internal { my $socket = $options{channel} eq 'control' ? $connector->{internal_socket} : $connector->{internal_channels}->{ $options{channel} }; while (1) { - my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $socket); + my $message = $connector->read_message(socket => $socket); last if (!defined($message)); proxy(message => $message, channel => $options{channel}); @@ -499,8 +503,8 @@ sub run { zmq_type => 'ZMQ_DEALER', name => 'gorgone-proxy-' . $self->{pool_id}, logger => $self->{logger}, - type => $self->{config_core}->{internal_com_type}, - path => $self->{config_core}->{internal_com_path} + type => $self->get_core_config(name => 'internal_com_type'), + path => $self->get_core_config(name => 'internal_com_path') ); $self->send_internal_action( action => 'PROXYREADY', diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index 5d55f7d9b8b..c011fc8d3ba 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -95,7 +95,7 @@ my $prevails = {}; my $prevails_subnodes = {}; my $rr_current = 0; my $stop = 0; -my ($external_socket, $internal_socket, $core_id); +my ($external_socket, $core_id); sub register { my (%options) = @_; @@ -118,7 +118,6 @@ sub init { $synctime_lasttime = Time::HiRes::time(); $core_id = $options{id}; $external_socket = $options{external_socket}; - $internal_socket = $options{internal_socket}; for my $pool_id (1..$config->{pool}) { create_child(dbh => $options{dbh}, pool_id => $pool_id, logger => $options{logger}); } @@ -184,10 +183,10 @@ sub routing { foreach my $node_id (keys %$nodes_pool) { next if ($nodes_pool->{$node_id} != $data->{pool_id}); routing( - socket => $internal_socket, action => 'PROXYADDNODE', target => $node_id, data => JSON::XS->new->utf8->encode($register_nodes->{$node_id}), + gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger} ); @@ -208,6 +207,7 @@ sub routing { target => $options{target}, dbh => $options{dbh}, token => $options{token}, + gorgone => $options{gorgone}, logger => $options{logger} ); return if ($code == -1); @@ -309,8 +309,7 @@ sub routing { next; } - gorgone::standard::library::zmq_send_message( - socket => $options{socket}, + $options{gorgone}->send_internal_message( identity => $identity, action => $action, data => $data, @@ -404,10 +403,10 @@ sub check { if (($constatus_ping->{$_}->{ping_timeout} % $config->{pong_max_timeout}) == 0) { $options{logger}->writeLogInfo("[proxy] Ping max timeout reached from '" . $_ . "'"); routing( - socket => $internal_socket, target => $_, action => 'PROXYCLOSECONNECTION', data => JSON::XS->new->utf8->encode({ id => $_ }), + gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger} ); @@ -431,11 +430,11 @@ sub check { if ($stop == 0 && time() - $synctime_lasttime > $synctime_option) { $synctime_lasttime = time(); - full_sync_history(dbh => $options{dbh}, logger => $options{logger}); + full_sync_history(gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger}); } if ($stop == 0) { - ping_send(dbh => $options{dbh}, logger => $options{logger}); + ping_send(gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger}); } # We clean all parents @@ -454,8 +453,7 @@ sub broadcast { foreach my $pool_id (keys %$pools) { next if ($pools->{$pool_id}->{ready} != 1); - gorgone::standard::library::zmq_send_message( - socket => $options{socket}, + $options{gorgone}->send_internal_message( identity => 'gorgone-proxy-' . $pool_id, action => $options{action}, data => $options{data}, @@ -523,10 +521,10 @@ sub pathway { if ($synctime_nodes->{$_}->{channel_read_stop} == 0) { $synctime_nodes->{$_}->{channel_read_stop} = 1; routing( - socket => $internal_socket, target => $_, action => 'PROXYCLOSEREADCHANNEL', data => JSON::XS->new->utf8->encode({ id => $_ }), + gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger} ); @@ -644,11 +642,11 @@ sub ping_send { $constatus_ping->{$id}->{next_ping} = $current_time + $ping_interval; if ($register_nodes->{$id}->{type} eq 'push_zmq' || $register_nodes->{$id}->{type} eq 'push_ssh') { $constatus_ping->{$id}->{in_progress_ping} = 1; - routing(socket => $internal_socket, action => 'PING', target => $id, data => '{}', dbh => $options{dbh}, logger => $options{logger}); + routing(action => 'PING', target => $id, data => '{}', gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger}); } elsif ($register_nodes->{$id}->{type} eq 'pull') { $constatus_ping->{$id}->{in_progress_ping} = 1; $constatus_ping->{$id}->{in_progress_ping_pull} = time(); - routing(socket => $internal_socket, action => 'PING', target => $id, data => '{}', dbh => $options{dbh}, logger => $options{logger}); + routing(action => 'PING', target => $id, data => '{}', gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger}); } } } @@ -668,9 +666,9 @@ sub full_sync_history { foreach my $id (keys %{$register_nodes}) { if ($register_nodes->{$id}->{type} eq 'push_zmq') { - routing(socket => $internal_socket, action => 'GETLOG', target => $id, data => '{}', dbh => $options{dbh}, logger => $options{logger}); + routing(action => 'GETLOG', target => $id, data => '{}', gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger}); } elsif ($register_nodes->{$id}->{type} eq 'pull') { - routing(socket => $internal_socket, action => 'GETLOG', target => $id, data => '{}', dbh => $options{dbh}, logger => $options{logger}); + routing(action => 'GETLOG', target => $id, data => '{}', gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger}); } } } @@ -750,7 +748,8 @@ sub create_child { config_core => $config_core, config => $config, pool_id => $options{pool_id}, - core_id => $core_id + core_id => $core_id, + container_id => $options{pool_id} ); $module->run(); exit(0); @@ -819,7 +818,14 @@ sub unregister_nodes { foreach my $node (@{$options{data}->{nodes}}) { if (defined($register_nodes->{ $node->{id} }) && $register_nodes->{ $node->{id} }->{type} ne 'pull') { - routing(socket => $internal_socket, action => 'PROXYDELNODE', target => $node->{id}, data => JSON::XS->new->utf8->encode($node), dbh => $options{dbh}, logger => $options{logger}); + routing( + action => 'PROXYDELNODE', + target => $node->{id}, + data => JSON::XS->new->utf8->encode($node), + gorgone => $options{gorgone}, + dbh => $options{dbh}, + logger => $options{logger} + ); } my $prevail = 0; @@ -911,6 +917,7 @@ sub register_nodes { unregister_nodes( data => { nodes => [ { id => $node->{id} } ] }, + gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger} ) if ($register_nodes->{ $node->{id} }->{type} ne 'pull' && $node->{type} eq 'pull'); @@ -927,6 +934,7 @@ sub register_nodes { if (defined($node->{prevail}) && $node->{prevail} == 1) { unregister_nodes( data => { nodes => [ { id => $subnode->{id} } ] }, + gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger} ); @@ -957,9 +965,23 @@ sub register_nodes { if ($register_nodes->{ $node->{id} }->{type} ne 'pull') { if ($prevail == 1) { - routing(socket => $internal_socket, action => 'PROXYADDNODE', target => $node->{id}, data => JSON::XS->new->utf8->encode($register_nodes->{ $node->{id} }), dbh => $options{dbh}, logger => $options{logger}); + routing( + action => 'PROXYADDNODE', + target => $node->{id}, + data => JSON::XS->new->utf8->encode($register_nodes->{ $node->{id} }), + gorgone => $options{gorgone}, + dbh => $options{dbh}, + logger => $options{logger} + ); } else { - routing(socket => $internal_socket, action => 'PROXYADDNODE', target => $node->{id}, data => JSON::XS->new->utf8->encode($node), dbh => $options{dbh}, logger => $options{logger}); + routing( + action => 'PROXYADDNODE', + target => $node->{id}, + data => JSON::XS->new->utf8->encode($node), + gorgone => $options{gorgone}, + dbh => $options{dbh}, + logger => $options{logger} + ); } } if ($new_node == 1) { diff --git a/gorgone/gorgone/modules/core/pull/class.pm b/gorgone/gorgone/modules/core/pull/class.pm new file mode 100644 index 00000000000..ac481a5d234 --- /dev/null +++ b/gorgone/gorgone/modules/core/pull/class.pm @@ -0,0 +1,219 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::core::pull::class; + +use base qw(gorgone::class::module); + +use strict; +use warnings; +use gorgone::class::db; +use gorgone::standard::library; +use gorgone::standard::constants qw(:all); +use gorgone::class::clientzmq; +use ZMQ::LibZMQ4; +use ZMQ::Constants qw(:all); +use JSON::XS; + +my %handlers = (TERM => {}, HUP => {}); +my ($connector); + +sub new { + my ($class, %options) = @_; + $connector = $class->SUPER::new(%options); + bless $connector, $class; + + $connector->{ping_timer} = time(); + + $connector->set_signal_handlers(); + return $connector; +} + +sub set_signal_handlers { + my $self = shift; + + $SIG{TERM} = \&class_handle_TERM; + $handlers{TERM}->{$self} = sub { $self->handle_TERM() }; + $SIG{HUP} = \&class_handle_HUP; + $handlers{HUP}->{$self} = sub { $self->handle_HUP() }; +} + +sub handle_HUP { + my $self = shift; + $self->{reload} = 0; +} + +sub handle_TERM { + my $self = shift; + $self->{logger}->writeLogDebug("[pipeline] -class- $$ Receiving order to stop..."); + $self->{stop} = 1; +} + +sub class_handle_TERM { + foreach (keys %{$handlers{TERM}}) { + &{$handlers{TERM}->{$_}}(); + } +} + +sub class_handle_HUP { + foreach (keys %{$handlers{HUP}}) { + &{$handlers{HUP}->{$_}}(); + } +} + +sub exit_process { + my ($self, %options) = @_; + + $self->{logger}->writeLogInfo("[pull] $$ has quit"); + + $self->{client}->send_message( + action => 'UNREGISTERNODES', + data => { nodes => [ { id => $self->get_core_config(name => 'id') } ] }, + json_encode => 1 + ); + $self->{client}->close(); + + zmq_close($self->{internal_socket}); + exit(0); +} + +sub ping { + my ($self, %options) = @_; + + return if ((time() - $self->{ping_timer}) < 60); + + $self->{ping_timer} = time(); + + $self->{client}->ping( + poll => $self->{poll}, + action => 'REGISTERNODES', + data => { nodes => [ { id => $self->get_core_config(name => 'id'), type => 'pull', identity => $self->{client}->get_connect_identity() } ] }, + json_encode => 1 + ); +} + +sub transmit_back { + my (%options) = @_; + + return undef if (!defined($options{message})); + + if ($options{message} =~ /^\[ACK\]\s+\[(.*?)\]\s+(.*)/m) { + my $data; + eval { + $data = JSON::XS->new->utf8->decode($2); + }; + if ($@) { + return $options{message}; + } + + if (defined($data->{data}->{action}) && $data->{data}->{action} eq 'getlog') { + return '[SETLOGS] [' . $1 . '] [] ' . $2; + } + return undef; + } elsif ($options{message} =~ /^\[(PONG|SYNCLOGS)\]/) { + return $options{message}; + } + return undef; +} + +sub read_message_client { + my (%options) = @_; + + # We skip. Dont need to send it in gorgone-core + if ($options{data} =~ /^\[ACK\]/) { + return undef; + } + + $connector->{logger}->writeLogDebug("[pull] read message from external: $options{data}"); + $connector->send_internal_action(message => $options{data}); +} + +sub event { + while (1) { + my $message = transmit_back(message => $connector->read_message()); + last if (!defined($message)); + + # Only send back SETLOGS and PONG + $connector->{logger}->writeLogDebug("[pull] read message from internal: $message"); + $connector->{client}->send_message(message => $message); + } +} + +sub run { + my ($self, %options) = @_; + + # Connect internal + $self->{internal_socket} = gorgone::standard::library::connect_com( + zmq_type => 'ZMQ_DEALER', + name => 'gorgone-pull', + logger => $self->{logger}, + type => $self->get_core_config(name => 'internal_com_type'), + path => $self->get_core_config(name => 'internal_com_path') + ); + $self->send_internal_action( + action => 'PULLREADY', + data => {} + ); + + $self->{client} = gorgone::class::clientzmq->new( + identity => 'gorgone-' . $self->get_core_config(name => 'id'), + cipher => $self->{config}->{cipher}, + vector => $self->{config}->{vector}, + client_pubkey => + defined($self->{config}->{client_pubkey}) && $self->{config}->{client_pubkey} ne '' ? + $self->{config}->{client_pubkey} : $self->get_core_config(name => 'pubkey'), + client_privkey => + defined($self->{config}->{client_privkey}) && $self->{config}->{client_privkey} ne '' ? + $self->{config}->{client_privkey} : $self->get_core_config(name => 'privkey'), + target_type => $self->{config}->{target_type}, + target_path => $self->{config}->{target_path}, + config_core => $self->get_core_config(), + logger => $self->{logger}, + ping => $self->{config}->{ping}, + ping_timeout => $self->{config}->{ping_timeout} + ); + $self->{client}->init(callback => \&read_message_client); + + $self->{client}->send_message( + action => 'REGISTERNODES', + data => { nodes => [ { id => $self->get_core_config(name => 'id'), type => 'pull', identity => $self->{client}->get_connect_identity() } ] }, + json_encode => 1 + ); + + $self->{poll} = [ + { + socket => $self->{internal_socket}, + events => ZMQ_POLLIN, + callback => \&event + }, + $self->{client}->get_poll() + ]; + + while (1) { + my $rv = scalar(zmq_poll($self->{poll}, 5000)); + if ($rv == 0 && $self->{stop} == 1) { + $self->exit_process(); + } + + $self->ping(); + } +} + +1; diff --git a/gorgone/gorgone/modules/core/pull/hooks.pm b/gorgone/gorgone/modules/core/pull/hooks.pm index 8f38486752e..ad31498e149 100644 --- a/gorgone/gorgone/modules/core/pull/hooks.pm +++ b/gorgone/gorgone/modules/core/pull/hooks.pm @@ -22,98 +22,79 @@ package gorgone::modules::core::pull::hooks; use warnings; use strict; -use gorgone::class::clientzmq; -use JSON::XS; +use gorgone::class::core; +use gorgone::modules::core::pull::class; +use gorgone::standard::constants qw(:all); use constant NAMESPACE => 'core'; use constant NAME => 'pull'; -use constant EVENTS => []; +use constant EVENTS => [ + { event => 'PULLREADY' } +]; my $config_core; my $config; +my $pull = {}; my $stop = 0; -my $client; -my $socket_to_internal; -my $logger; sub register { my (%options) = @_; - + $config = $options{config}; - $config_core = $options{config_core}->{gorgonecore}; + $config_core = $options{config_core}; return (1, NAMESPACE, NAME, EVENTS); } sub init { my (%options) = @_; - $logger = $options{logger}; - # Connect internal - $socket_to_internal = gorgone::standard::library::connect_com( - zmq_type => 'ZMQ_DEALER', - name => 'gorgone-pull', - logger => $options{logger}, - type => $config_core->{internal_com_type}, - path => $config_core->{internal_com_path}, - zmq_linger => $config->{linger} - ); - $client = gorgone::class::clientzmq->new( - identity => 'gorgone-' . $config_core->{id}, - cipher => $config->{cipher}, - vector => $config->{vector}, - client_pubkey => - defined($config->{client_pubkey}) && $config->{client_pubkey} ne '' ? - $config->{client_pubkey} : $config_core->{pubkey}, - client_privkey => - defined($config->{client_privkey}) && $config->{client_privkey} ne '' ? - $config->{client_privkey} : $config_core->{privkey}, - target_type => $config->{target_type}, - target_path => $config->{target_path}, - config_core => $config_core, - logger => $options{logger}, - ping => $config->{ping}, - ping_timeout => $config->{ping_timeout} - ); - $client->init(callback => \&read_message); - - $client->send_message( - action => 'REGISTERNODES', - data => { nodes => [ { id => $config_core->{id}, type => 'pull', identity => $client->get_connect_identity() } ] }, - json_encode => 1 - ); - - # put client dealer in global look - push @{$options{poll}}, $client->get_poll(); - - gorgone::standard::library::add_zmq_pollin( - socket => $socket_to_internal, - callback => \&from_router, - poll => $options{poll} - ); + create_child(logger => $options{logger}); } sub routing { my (%options) = @_; + + if ($options{action} eq 'PULLREADY') { + $pull->{ready} = 1; + return undef; + } + + if (gorgone::class::core::waiting_ready(ready => \$pull->{ready}) == 0) { + gorgone::standard::library::add_history( + dbh => $options{dbh}, + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'gorgone-pull: still no ready' }, + json_encode => 1 + ); + return undef; + } + $options{gorgone}->send_internal_message( + identity => 'gorgone-pull', + action => $options{action}, + data => $options{data}, + token => $options{token} + ); } sub gently { my (%options) = @_; $stop = 1; - $client->send_message( - action => 'UNREGISTERNODES', - data => { nodes => [ { id => $config_core->{id} } ] }, - json_encode => 1 - ); - $client->close(); - return 0; + if (defined($pull->{running}) && $pull->{running} == 1) { + $options{logger}->writeLogDebug("[pull] Send TERM signal $pull->{pid}"); + CORE::kill('TERM', $pull->{pid}); + } } sub kill { my (%options) = @_; - return 0; + if ($pull->{running} == 1) { + $options{logger}->writeLogDebug("[pull] Send KILL signal for pool"); + CORE::kill('KILL', $pull->{pid}); + } } sub kill_internal { @@ -125,75 +106,48 @@ sub kill_internal { sub check { my (%options) = @_; - if ($stop == 0) { - # If distant server restart, it's not problem. It save the key. - # But i don't have the registernode anymore. The ping is the 'registernode' for pull mode. - $client->ping( - poll => $options{gorgone}->{poll}, - action => 'REGISTERNODES', - data => { nodes => [ { id => $config_core->{id}, type => 'pull', identity => $client->get_connect_identity() } ] }, - json_encode => 1 - ); - } - - return (0, 1); -} - -sub broadcast {} - -####### specific - -sub transmit_back { - my (%options) = @_; - - return undef if (!defined($options{message})); - - if ($options{message} =~ /^\[ACK\]\s+\[(.*?)\]\s+(.*)/m) { - my $data; - eval { - $data = JSON::XS->new->utf8->decode($2); - }; - if ($@) { - return $options{message}; - } + my $count = 0; + foreach my $pid (keys %{$options{dead_childs}}) { + # Not me + next if (!defined($pull->{pid}) || $pull->{pid} != $pid); - if (defined($data->{data}->{action}) && $data->{data}->{action} eq 'getlog') { - return '[SETLOGS] [' . $1 . '] [] ' . $2; + $pull = {}; + delete $options{dead_childs}->{$pid}; + if ($stop == 0) { + create_child(logger => $options{logger}); } - return undef; - } elsif ($options{message} =~ /^\[(PONG|SYNCLOGS)\]/) { - return $options{message}; - } - return undef; + } + + $count++ if (defined($pull->{running}) && $pull->{running} == 1); + + return $count; } -sub from_router { - while (1) { - my $message = transmit_back(message => gorgone::standard::library::zmq_dealer_read_message(socket => $socket_to_internal)); - last if (!defined($message)); +sub broadcast { + my (%options) = @_; - # Only send back SETLOGS and PONG - if (defined($message)) { - $logger->writeLogDebug("[pull] Read message from internal: $message"); - $client->send_message(message => $message); - } - } + routing(%options); } -sub read_message { +# Specific functions +sub create_child { my (%options) = @_; - # We skip. Dont need to send it in gorgone-core - if ($options{data} =~ /^\[ACK\]/) { - return undef; + $options{logger}->writeLogInfo("[pull] Create module 'pull' process"); + my $child_pid = fork(); + if ($child_pid == 0) { + $0 = 'gorgone-pull'; + my $module = gorgone::modules::core::pull::class->new( + logger => $options{logger}, + module_id => NAME, + config_core => $config_core, + config => $config + ); + $module->run(); + exit(0); } - - $logger->writeLogDebug("[pull] Read message from external: $options{data}"); - gorgone::standard::library::zmq_send_message( - socket => $socket_to_internal, - message => $options{data} - ); + $options{logger}->writeLogDebug("[pull] PID $child_pid (gorgone-pull)"); + $pull = { pid => $child_pid, ready => 0, running => 1 }; } - 1; diff --git a/gorgone/gorgone/modules/core/register/class.pm b/gorgone/gorgone/modules/core/register/class.pm index 7e7d8782593..97afaefd44a 100644 --- a/gorgone/gorgone/modules/core/register/class.pm +++ b/gorgone/gorgone/modules/core/register/class.pm @@ -139,7 +139,7 @@ sub action_registerresync { sub event { while (1) { - my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); + my $message = $connector->read_message(); last if (!defined($message)); $connector->{logger}->writeLogDebug("[register] Event: $message"); @@ -164,8 +164,8 @@ sub run { zmq_type => 'ZMQ_DEALER', name => 'gorgone-register', logger => $self->{logger}, - type => $self->{config_core}->{internal_com_type}, - path => $self->{config_core}->{internal_com_path} + type => $self->get_core_config(name => 'internal_com_type'), + path => $self->get_core_config(name => 'internal_com_path') ); $connector->send_internal_action( action => 'REGISTERREADY', diff --git a/gorgone/gorgone/modules/core/register/hooks.pm b/gorgone/gorgone/modules/core/register/hooks.pm index de0be8f3a7f..b5b79b90d5f 100644 --- a/gorgone/gorgone/modules/core/register/hooks.pm +++ b/gorgone/gorgone/modules/core/register/hooks.pm @@ -75,9 +75,8 @@ sub routing { ); return undef; } - - gorgone::standard::library::zmq_send_message( - socket => $options{socket}, + + $options{gorgone}->send_internal_message( identity => 'gorgone-register', action => $options{action}, data => $options{data}, diff --git a/gorgone/gorgone/modules/plugins/newtest/class.pm b/gorgone/gorgone/modules/plugins/newtest/class.pm index bc7c0a9c61e..80951619526 100644 --- a/gorgone/gorgone/modules/plugins/newtest/class.pm +++ b/gorgone/gorgone/modules/plugins/newtest/class.pm @@ -45,7 +45,6 @@ sub new { $connector = $class->SUPER::new(%options); bless $connector, $class; - $connector->{container_id} = $options{container_id}; $connector->{config_newtest} = $options{config_newtest}; $connector->{resync_time} = $options{config_newtest}->{resync_time}; @@ -589,7 +588,7 @@ sub action_newtestresync { sub event { while (1) { - my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); + my $message = $connector->read_message(); last if (!defined($message)); $connector->{logger}->writeLogDebug("gorgone-newtest: class: $message"); @@ -633,8 +632,8 @@ sub run { zmq_type => 'ZMQ_DEALER', name => 'gorgone-newtest-' . $self->{container_id}, logger => $self->{logger}, - type => $self->{config_core}->{internal_com_type}, - path => $self->{config_core}->{internal_com_path} + type => $self->get_core_config(name => 'internal_com_type'), + path => $self->get_core_config(name => 'internal_com_path') ); $connector->send_internal_action( action => 'NEWTESTREADY', diff --git a/gorgone/gorgone/modules/plugins/newtest/hooks.pm b/gorgone/gorgone/modules/plugins/newtest/hooks.pm index 822ab878c02..f0e14133754 100644 --- a/gorgone/gorgone/modules/plugins/newtest/hooks.pm +++ b/gorgone/gorgone/modules/plugins/newtest/hooks.pm @@ -109,9 +109,11 @@ sub routing { return undef; } - gorgone::standard::library::zmq_send_message( - socket => $options{socket}, identity => 'gorgone-newtest-' . $data->{container_id}, - action => $options{action}, data => $options{data}, token => $options{token} + $options{gorgone}->send_internal_message( + identity => 'gorgone-newtest-' . $data->{container_id}, + action => $options{action}, + data => $options{data}, + token => $options{token} ); } @@ -174,9 +176,13 @@ sub broadcast { foreach my $container_id (keys %$containers) { if ($containers->{$container_id}->{running} == 1) { - gorgone::standard::library::zmq_send_message( - socket => $options{socket}, identity => 'gorgone-newtest-' . $container_id, - action => $options{action}, data => $options{data}, token => $options{token} + gorgone::standard::library::zmq_send_internal_message( + socket => $options{socket}, + internal_crypt => $options{internal_crypt}, + identity => 'gorgone-newtest-' . $container_id, + action => $options{action}, + data => $options{data}, + token => $options{token} ); } } @@ -275,7 +281,7 @@ sub create_child { config_db_centreon => $config_db_centreon, config_db_centstorage => $config_db_centstorage, config_newtest => $last_containers->{$options{container_id}}, - container_id => $options{container_id}, + container_id => $options{container_id} ); $module->run(); exit(0); diff --git a/gorgone/gorgone/modules/plugins/scom/class.pm b/gorgone/gorgone/modules/plugins/scom/class.pm index 65fd85af631..2585f9c6cbc 100644 --- a/gorgone/gorgone/modules/plugins/scom/class.pm +++ b/gorgone/gorgone/modules/plugins/scom/class.pm @@ -42,7 +42,6 @@ sub new { $connector = $class->SUPER::new(%options); bless $connector, $class; - $connector->{container_id} = $options{container_id}; $connector->{config_scom} = $options{config_scom}; $connector->{api_version} = $options{config_scom}->{api_version}; @@ -477,7 +476,7 @@ sub action_scomresync { sub event { while (1) { - my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $connector->{internal_socket}); + my $message = $connector->read_message(); last if (!defined($message)); $connector->{logger}->writeLogDebug("[scom] Event: $message"); @@ -512,8 +511,8 @@ sub run { zmq_type => 'ZMQ_DEALER', name => 'gorgone-scom-' . $self->{container_id}, logger => $self->{logger}, - type => $self->{config_core}->{internal_com_type}, - path => $self->{config_core}->{internal_com_path} + type => $self->get_core_config(name => 'internal_com_type'), + path => $self->get_core_config(name => 'internal_com_path') ); $connector->send_internal_action( action => 'SCOMREADY', diff --git a/gorgone/gorgone/modules/plugins/scom/hooks.pm b/gorgone/gorgone/modules/plugins/scom/hooks.pm index 37185548c45..cabc43982e9 100644 --- a/gorgone/gorgone/modules/plugins/scom/hooks.pm +++ b/gorgone/gorgone/modules/plugins/scom/hooks.pm @@ -108,9 +108,11 @@ sub routing { return undef; } - gorgone::standard::library::zmq_send_message( - socket => $options{socket}, identity => 'gorgone-scom-' . $data->{container_id}, - action => $options{action}, data => $options{data}, token => $options{token}, + $options{gorgone}->send_internal_message( + identity => 'gorgone-scom-' . $data->{container_id}, + action => $options{action}, + data => $options{data}, + token => $options{token} ); } @@ -173,9 +175,13 @@ sub broadcast { foreach my $container_id (keys %$containers) { if ($containers->{$container_id}->{running} == 1) { - gorgone::standard::library::zmq_send_message( - socket => $options{socket}, identity => 'gorgone-scom-' . $container_id, - action => $options{action}, data => $options{data}, token => $options{token} + gorgone::standard::library::zmq_send_internal_message( + socket => $options{socket}, + internal_crypt => $options{internal_crypt}, + identity => 'gorgone-scom-' . $container_id, + action => $options{action}, + data => $options{data}, + token => $options{token} ); } } diff --git a/gorgone/gorgone/standard/api.pm b/gorgone/gorgone/standard/api.pm index 5f07f504229..bd4f9410151 100644 --- a/gorgone/gorgone/standard/api.pm +++ b/gorgone/gorgone/standard/api.pm @@ -28,6 +28,7 @@ use ZMQ::Constants qw(:all); use Time::HiRes; use JSON::XS; +my $module; my $socket; my $results; my $action_token; @@ -39,6 +40,7 @@ sub root { $action_token = undef; $socket = $options{socket}; + $module = $options{module}; $results = {}; my $response; @@ -47,7 +49,8 @@ sub root { target => $2, token => $3, sync_wait => (defined($options{parameters}->{sync_wait})) ? $options{parameters}->{sync_wait} : undef, - parameters => $options{parameters} + parameters => $options{parameters}, + module => $options{module} ); } elsif ($options{uri} =~ /^\/api\/(nodes\/(\w*)\/)?internal\/(\w+)\/?([\w\/]*?)$/ && defined($options{api_endpoints}->{$options{method} . '_/internal/' . $3})) { @@ -61,7 +64,8 @@ sub root { variables => \@variables }, log_wait => (defined($options{parameters}->{log_wait})) ? $options{parameters}->{log_wait} : undef, - sync_wait => (defined($options{parameters}->{sync_wait})) ? $options{parameters}->{sync_wait} : undef + sync_wait => (defined($options{parameters}->{sync_wait})) ? $options{parameters}->{sync_wait} : undef, + module => $options{module} ); } elsif ($options{uri} =~ /^\/api\/(nodes\/(\w*)\/)?(\w+)\/(\w+)\/(\w+)\/?([\w\/]*?)$/ && defined($options{api_endpoints}->{$options{method} . '_/' . $3 . '/' . $4 . '/' . $5})) { @@ -75,7 +79,8 @@ sub root { variables => \@variables }, log_wait => (defined($options{parameters}->{log_wait})) ? $options{parameters}->{log_wait} : undef, - sync_wait => (defined($options{parameters}->{sync_wait})) ? $options{parameters}->{sync_wait} : undef + sync_wait => (defined($options{parameters}->{sync_wait})) ? $options{parameters}->{sync_wait} : undef, + module => $options{module} ); } else { $response = '{"error":"method_unknown","message":"Method not implemented"}'; @@ -89,7 +94,7 @@ sub call_action { $action_token = gorgone::standard::library::generate_token() if (!defined($options{token})); - gorgone::standard::library::zmq_send_message( + $options{module}->send_internal_action( socket => $socket, action => $options{action}, target => $options{target}, @@ -105,7 +110,8 @@ sub call_action { target => $options{target}, token => $action_token, sync_wait => $options{sync_wait}, - parameters => $options{data}->{parameters} + parameters => $options{data}->{parameters}, + module => $options{module} ); } @@ -132,11 +138,12 @@ sub call_internal { data => $options{data}, json_encode => 1, log_wait => $options{log_wait}, - sync_wait => $options{sync_wait} + sync_wait => $options{sync_wait}, + module => $options{module} ); } - gorgone::standard::library::zmq_send_message( + $options{module}->send_internal_action( socket => $socket, action => $options{action}, token => $action_token, @@ -188,8 +195,8 @@ sub get_log { } ]; - if (defined($options{target}) && $options{target} ne '') { - gorgone::standard::library::zmq_send_message( + if (defined($options{target}) && $options{target} ne '') { + $options{module}->send_internal_action( socket => $socket, target => $options{target}, action => 'GETLOG', @@ -201,7 +208,7 @@ sub get_log { } my $token_log = $options{token} . '-log'; - gorgone::standard::library::zmq_send_message( + $options{module}->send_internal_action( socket => $socket, action => 'GETLOG', token => $token_log, @@ -249,16 +256,20 @@ sub get_log { sub event { while (1) { - my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $socket); + my $message = $module->read_message(); last if (!defined($message)); if ($message =~ /^\[(.*?)\]\s+\[([a-zA-Z0-9:\-_]*?)\]\s+\[.*?\]\s+(.*)$/m || $message =~ /^\[(.*?)\]\s+\[([a-zA-Z0-9:\-_]*?)\]\s+(.*)$/m) { - $results->{$2} = { - action => $1, - token => $2, - data => $3 + my ($action, $token, $data) = ($1, $2, $3); + $results->{$token} = { + action => $action, + token => $token, + data => $data }; + if ((my $method = $module->can('action_' . lc($action)))) { + $method->($module, token => $token, data => $data); + } } } } diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index d165375bf79..cbe2f913a84 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -29,11 +29,13 @@ use JSON::XS; use File::Basename; use Crypt::PK::RSA; use Crypt::PRNG; +use Crypt::Mode::CBC; use Crypt::CBC; use Data::Dumper; use File::Path; use File::Basename; use MIME::Base64; +use Errno; use Time::HiRes; use YAML::XS; $YAML::XS::Boolean = 'JSON::PP'; @@ -214,6 +216,12 @@ sub zmq_core_response { zmq_sendmsg($options{socket}, $msg, ZMQ_DONTWAIT); } +sub zmq_get_routing_id { + my (%options) = @_; + + return zmq_getsockopt($options{socket}, ZMQ_IDENTITY); +} + sub zmq_getfd { my (%options) = @_; @@ -226,6 +234,23 @@ sub zmq_events { return zmq_getsockopt($options{socket}, ZMQ_EVENTS); } +sub cipher_encrypt { + my (%options) = @_; + + my $m = Crypt::Mode::CBC->new($options{cipher}, $options{padding}); + my $ciphertext; + eval { + $ciphertext = $m->encrypt($options{message}, $options{key}, $options{iv}); + }; + if ($@) { + if (defined($options{logger})) { + $options{logger}->writeLogError("[$options{module}] Sym encrypt issue: " . $@); + } + return (-1, $@); + } + return (0, $ciphertext); +} + sub uncrypt_message { my (%options) = @_; my $plaintext; @@ -509,6 +534,31 @@ sub constatus { return (GORGONE_ACTION_FINISH_KO, { action => 'constatus', message => 'cannot get value' }, 'CONSTATUS'); } +sub setmodulekey { + my (%options) = @_; + + my $data; + eval { + $data = JSON::XS->new->utf8->decode($options{data}); + }; + if ($@) { + return (GORGONE_ACTION_FINISH_KO, { message => 'request not well formatted' }); + } + + if (!defined($data->{key})) { + return (GORGONE_ACTION_FINISH_KO, { action => 'setmodulekey', message => 'please set key' }); + } + + my $id = pack('H*', $options{identity}); + $options{gorgone}->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_identity_keys}->{$id} = { + key => pack('H*', $data->{key}), + ctime => time() + }; + + $options{logger}->writeLogInfo('[core] module key ' . $id . ' changed'); + return (GORGONE_ACTION_FINISH_OK, { action => 'setmodulekey', message => 'setmodulekey changed' }); +} + sub setcoreid { my (%options) = @_; @@ -821,6 +871,38 @@ sub build_protocol { return '[' . $action . '] [' . $token . '] [' . $target . '] ' . $data; } +sub zmq_send_internal_message { + my (%options) = @_; + my ($message, $rv) = ($options{message}); + + if (!defined($message)) { + $message = build_protocol(%options); + } + if (defined($options{identity})) { + zmq_sendmsg($options{socket}, $options{identity}, ZMQ_DONTWAIT | ZMQ_SNDMORE); + } + + if (defined($options{internal_crypt}) && $options{internal_crypt}->{enable} == 1) { + my $key = $options{internal_crypt}->{symkeys}->{ $options{internal_crypt}->{current} }; + if (defined($options{identity})) { + if (defined($options{internal_crypt}->{identity}->{ $options{identity} })) { + $key = $options{internal_crypt}->{symkeys}->{ $options{internal_crypt}->{identity}->{ $options{identity} } }; + } + } + ($rv, $message) = cipher_encrypt( + logger => $options{logger}, + module => $options{module_id}, + key => $key, + cipher => $options{internal_crypt}->{cipher}, + padding => $options{internal_crypt}->{padding}, + iv => $options{internal_crypt}->{iv} + ); + return if ($rv == -1); + } + + zmq_sendmsg($options{socket}, $message, ZMQ_DONTWAIT); +} + sub zmq_send_message { my (%options) = @_; my $message = $options{message}; @@ -862,7 +944,9 @@ sub zmq_read_message { # Process all parts of the message my $message = zmq_recvmsg($options{socket}, ZMQ_DONTWAIT); if (!defined($message)) { - $options{logger}->writeLogDebug("[core] zmq_recvmsg error: $!"); + return undef if ($! == Errno::EAGAIN); + + $options{logger}->writeLogError("[core] zmq_recvmsg error: $!"); return undef; } my $identity = zmq_msg_data($message); @@ -873,7 +957,9 @@ sub zmq_read_message { } $message = zmq_recvmsg($options{socket}, ZMQ_DONTWAIT); if (!defined($message)) { - $options{logger}->writeLogDebug("[core] zmq_recvmsg error: $!"); + return undef if ($! == Errno::EAGAIN); + + $options{logger}->writeLogError("[core] zmq_recvmsg error: $!"); return undef; } my $data = zmq_msg_data($message); From 3a25b11e7cc18fb0287ff59dfa34d40e1edb3d76 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Wed, 23 Feb 2022 15:45:38 +0100 Subject: [PATCH 642/948] Update external protocol communication (#207) --- gorgone/docs/configuration.md | 7 +- gorgone/docs/guide.md | 2 +- gorgone/gorgone/class/clientzmq.pm | 166 +++++-- gorgone/gorgone/class/core.pm | 335 +++++++++++--- gorgone/gorgone/class/logger.pm | 1 + gorgone/gorgone/class/script.pm | 7 +- .../gorgone/modules/core/dbcleaner/class.pm | 2 +- gorgone/gorgone/modules/core/proxy/hooks.pm | 19 +- .../gorgone/modules/plugins/newtest/hooks.pm | 18 +- gorgone/gorgone/modules/plugins/scom/hooks.pm | 18 +- gorgone/gorgone/standard/library.pm | 411 ++++++++---------- gorgone/gorgoned | 4 + .../packaging/centreon-gorgone.spectemplate | 1 - gorgone/schema/gorgone_database.sql | 11 +- 14 files changed, 615 insertions(+), 387 deletions(-) diff --git a/gorgone/docs/configuration.md b/gorgone/docs/configuration.md index 214c959ea75..0b0fa458b24 100644 --- a/gorgone/docs/configuration.md +++ b/gorgone/docs/configuration.md @@ -50,6 +50,10 @@ configuration: | internal_com_crypt | Crypt internal communication | `false` | | external_com_type | Type of the external ZMQ socket | `tcp` | | external_com_path | Path to the external ZMQ socket | `*:5555` | +| external_com_cipher | Cipher used for encryption | `AES` | +| external_com_keysize | Size in bytes of the symmetric encryption key | `32` | +| external_com_padding | External communication padding | `1` (mean: PKCS5) | +| external_com_rotation | External communication time before key rotation | `1440` (minutes) | | timeout | Time in seconds before killing child processes when stopping Gorgone | `50` | | gorgone_db_type | Type of the Gorgone database | `SQLite` | | gorgone_db_name | Path and name of the database | `dbname=/var/lib/centreon-gorgone/history.sdb` | @@ -61,9 +65,6 @@ configuration: | id | Identifier of server running Gorgone | None. Must be unique over all Gorgone daemons. | | privkey | Path to the Gorgone core private key | `keys/rsakey.priv.pem` | | pubkey | Path to the Gorgone core public key | `keys/rsakey.pub.pem` | -| cipher | Cipher used for encryption | `Cipher::AES` | -| keysize | Size in bytes of the symmetric encryption key | `32` | -| vector | Encryption vector | `0123456789012345` | | fingerprint_mode | Validation mode of zmq nodes to connect (can be: always, first, strict) | `first` | | fingerprint_mgr | Hash of the definition class to store fingerprints | | | authorized_clients | Table of string-formated JWK thumbprints of clients public key | | diff --git a/gorgone/docs/guide.md b/gorgone/docs/guide.md index 8607ff62afd..a60df55deab 100644 --- a/gorgone/docs/guide.md +++ b/gorgone/docs/guide.md @@ -39,7 +39,7 @@ Third-party clients have to use the ZeroMQ library and the following process: * If uncrypted message result is "HELO", server accepts the connection if the clientpubkey is authorized. It creates a symmetric key and send the following message crypted with client pubkey: ```text - [KEY] [HOSTNAME] [symmetric key] + [KEY] { "hostname": "xxxx", "key": "ab0182xxxx", "iv": "ab0182xxx", "cipher": "AES", "padding": 1 } ``` 4. Client: uncrypts the server message with its private key. diff --git a/gorgone/gorgone/class/clientzmq.pm b/gorgone/gorgone/class/clientzmq.pm index 21800dacd95..9b3f9d48841 100644 --- a/gorgone/gorgone/class/clientzmq.pm +++ b/gorgone/gorgone/class/clientzmq.pm @@ -26,6 +26,7 @@ use gorgone::standard::library; use gorgone::standard::misc; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); +use Crypt::Mode::CBC; use MIME::Base64; use Scalar::Util; @@ -40,10 +41,6 @@ sub new { $connector->{identity} = $options{identity}; $connector->{extra_identity} = gorgone::standard::library::generate_token(length => 12); - $connector->{cipher} = defined($options{cipher}) && $options{cipher} ne '' ? $options{cipher} : 'Cipher::AES'; - $connector->{vector} = defined($options{vector}) && $options{vector} ne '' ? $options{vector} : '0123456789012345'; - - $connector->{symkey} = undef; $connector->{verbose_last_message} = ''; $connector->{config_core} = $options{config_core}; @@ -88,7 +85,7 @@ sub new { $connector->{logger}->writeLogDebug('[core] JWK thumbprint = ' . $connector->{client_pubkey}->export_key_jwk_thumbprint('SHA256')); } - $connectors->{$options{identity}} = $connector; + $connectors->{ $options{identity} } = $connector; bless $connector, $class; return $connector; } @@ -127,11 +124,75 @@ sub get_server_pubkey { zmq_poll([$self->get_poll()], 10000); } +sub read_key_protocol { + my ($self, %options) = @_; + + $self->{logger}->writeLogDebug('[clientzmq] read key protocol: ' . $options{text}); + + return (-1, 'Wrong protocol') if ($options{text} !~ /^\[KEY\]\s+(.*)$/); + + my $data = gorgone::standard::library::json_decode(module => 'clientzmq', data => $1, logger => $self->{logger}); + return (-1, 'Wrong protocol') if (!defined($data)); + + return (-1, 'Wrong protocol') if ( + !defined($data->{hostname}) || + !defined($data->{key}) || $data->{key} eq '' || + !defined($data->{cipher}) || $data->{cipher} eq '' || + !defined($data->{iv}) || $data->{iv} eq '' || + !defined($data->{padding}) || $data->{padding} eq '' + ); + + $self->{key} = pack('H*', $data->{key}); + $self->{iv} = pack('H*', $data->{iv}); + $self->{cipher} = $data->{cipher}; + $self->{padding} = $data->{padding}; + + $self->{crypt_mode} = Crypt::Mode::CBC->new( + $self->{cipher}, + $self->{padding} + ); + + return (0, 'ok'); +} + +sub decrypt_message { + my ($self, %options) = @_; + + my $plaintext; + eval { + $plaintext = $self->{crypt_mode}->decrypt( + MIME::Base64::decode_base64($options{message}), + $self->{key}, + $self->{iv} + ); + }; + if ($@) { + $self->{logger}->writeLogError("[clientzmq] decrypt message issue: " . $@); + return (-1, $@); + } + return (0, $plaintext); +} + +sub client_get_secret { + my ($self, %options) = @_; + + my $plaintext; + eval { + my $cryptedtext = MIME::Base64::decode_base64($options{message}); + $plaintext = $self->{client_privkey}->decrypt($cryptedtext, 'v1.5'); + }; + if ($@) { + return (-1, "Decoding issue: $@"); + } + + return $self->read_key_protocol(text => $plaintext); +} + sub check_server_pubkey { my ($self, %options) = @_; if ($options{message} !~ /^\s*\[PUBKEY\]\s+\[(.*?)\]/) { - $self->{logger}->writeLogError('[core] Cannot read pubbkey response from server: ' . $options{message}) if (defined($self->{logger})); + $self->{logger}->writeLogError('[clientzmq] Cannot read pubbkey response from server: ' . $options{message}) if (defined($self->{logger})); $self->{verbose_last_message} = 'cannot read pubkey response from server'; return 0; } @@ -145,7 +206,7 @@ sub check_server_pubkey { ); if ($code == 0) { - $self->{logger}->writeLogError('[core] Cannot load pubbkey') if (defined($self->{logger})); + $self->{logger}->writeLogError('[clientzmq] Cannot load pubbkey') if (defined($self->{logger})); $self->{verbose_last_message} = 'cannot load pubkey'; return 0; } @@ -191,7 +252,7 @@ sub ping { } if ($self->{ping_progress} == 1 && time() - $self->{ping_timeout_time} > $self->{ping_timeout}) { - $self->{logger}->writeLogError("[core] No ping response") if (defined($self->{logger})); + $self->{logger}->writeLogError("[clientzmq] No ping response") if (defined($self->{logger})); $self->{ping_progress} = 0; # we delete the old one for (my $i = 0; $i < scalar(@{$options{poll}}); $i++) { @@ -226,59 +287,79 @@ sub event { my (%options) = @_; # We have a response. So it's ok :) - if ($connectors->{$options{identity}}->{ping_progress} == 1) { - $connectors->{$options{identity}}->{ping_progress} = 0; + if ($connectors->{ $options{identity} }->{ping_progress} == 1) { + $connectors->{ $options{identity} }->{ping_progress} = 0; } - $connectors->{$options{identity}}->{ping_time} = time(); + + $connectors->{ $options{identity} }->{ping_time} = time(); while (1) { my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $sockets->{$options{identity}}); last if (!defined($message)); # in progress - if ($connectors->{$options{identity}}->{handshake} == 0) { - $connectors->{$options{identity}}->{handshake} = 1; - if ($connectors->{$options{identity}}->check_server_pubkey(message => $message) == 0) { - $connectors->{$options{identity}}->{handshake} = -1; + if ($connectors->{ $options{identity} }->{handshake} == 0) { + $connectors->{ $options{identity} }->{handshake} = 1; + if ($connectors->{ $options{identity} }->check_server_pubkey(message => $message) == 0) { + $connectors->{ $options{identity} }->{handshake} = -1; } - } elsif ($connectors->{$options{identity}}->{handshake} == 1) { - my ($status, $verbose, $symkey, $hostname) = gorgone::standard::library::client_get_secret( - privkey => $connectors->{$options{identity}}->{client_privkey}, + } elsif ($connectors->{ $options{identity} }->{handshake} == 1) { + my ($status, $verbose, $symkey, $hostname) = $connectors->{ $options{identity} }->client_get_secret( message => $message ); if ($status == -1) { - $connectors->{$options{identity}}->{handshake} = -1; - $connectors->{$options{identity}}->{verbose_last_message} = $verbose; + $connectors->{ $options{identity} }->{handshake} = -1; + $connectors->{ $options{identity} }->{verbose_last_message} = $verbose; return ; } - $connectors->{$options{identity}}->{symkey} = $symkey; - $connectors->{$options{identity}}->{handshake} = 2; - if (defined($connectors->{$options{identity}}->{logger})) { - $connectors->{$options{identity}}->{logger}->writeLogInfo( - "[zmqclient] Client connected successfully to '" . $connectors->{$options{identity}}->{target_type} . - "://" . $connectors->{$options{identity}}->{target_path} . "'" + $connectors->{ $options{identity} }->{handshake} = 2; + if (defined($connectors->{ $options{identity} }->{logger})) { + $connectors->{ $options{identity} }->{logger}->writeLogInfo( + "[clientzmq] Client connected successfully to '" . $connectors->{ $options{identity} }->{target_type} . + "://" . $connectors->{ $options{identity} }->{target_path} . "'" ); } } else { - my ($status, $data) = gorgone::standard::library::uncrypt_message( - message => $message, - cipher => $connectors->{$options{identity}}->{cipher}, - vector => $connectors->{$options{identity}}->{vector}, - symkey => $connectors->{$options{identity}}->{symkey} - ); - - if ($status == -1 || $data !~ /^\[(.+?)\]\s+\[(.*?)\]\s+(?:\[(.*?)\]\s*(.*)|(.*))$/m) { - $connectors->{$options{identity}}->{handshake} = -1; - $connectors->{$options{identity}}->{verbose_last_message} = 'uncrypt issue: ' . $data; + my ($rv, $data) = $connectors->{ $options{identity} }->decrypt_message(message => $message); + + if ($rv == -1 || $data !~ /^\[([a-zA-Z0-9:\-_]+?)\]\s+/) { + $connectors->{ $options{identity} }->{handshake} = -1; + $connectors->{ $options{identity} }->{verbose_last_message} = 'decrypt issue: ' . $data; return ; } - - if (defined($callbacks->{$options{identity}})) { + + if ($1 eq 'KEY') { + ($rv) = $connectors->{ $options{identity} }->read_key_protocol(text => $data); + } elsif (defined($callbacks->{$options{identity}})) { $callbacks->{$options{identity}}->(identity => $options{identity}, data => $data); } } } } +sub zmq_send_message { + my ($self, %options) = @_; + + my $message = $options{message}; + if (!defined($message)) { + $message = gorgone::standard::library::build_protocol(%options); + } + + eval { + $message = $self->{crypt_mode}->encrypt( + $message, + $self->{key}, + $self->{iv} + ); + $message = MIME::Base64::encode_base64($message, ''); + }; + if ($@) { + $self->{logger}->writeLogError("[clientzmq] encrypt message issue: " . $@); + return undef; + } + + zmq_sendmsg($options{socket}, $message, ZMQ_DONTWAIT); +} + sub send_message { my ($self, %options) = @_; @@ -310,12 +391,9 @@ sub send_message { $self->{handshake} = 0; return (-1, $self->{verbose_last_message}); } - - gorgone::standard::library::zmq_send_message( - socket => $sockets->{$self->{identity}}, - cipher => $self->{cipher}, - symkey => $self->{symkey}, - vector => $self->{vector}, + + $self->zmq_send_message( + socket => $sockets->{ $self->{identity} }, %options ); return 0; diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 56310ada8c1..5241fbb10b2 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -24,6 +24,7 @@ use strict; use warnings; use POSIX ":sys_wait_h"; use Sys::Hostname; +use MIME::Base64; use Crypt::Mode::CBC; use ZMQ::LibZMQ4; use ZMQ::Constants qw(:all); @@ -38,12 +39,13 @@ my ($gorgone); use base qw(gorgone::class::script); -my $VERSION = '1.0'; +my $VERSION = '22.04.0'; my %handlers = (TERM => {}, HUP => {}, CHLD => {}, DIE => {}); sub new { my $class = shift; - my $self = $class->SUPER::new('gorgoned', + my $self = $class->SUPER::new( + 'gorgoned', centreon_db_conn => 0, centstorage_db_conn => 0, noconfig => 1 @@ -75,6 +77,12 @@ sub new { return $self; } +sub get_version { + my ($self, %options) = @_; + + return $VERSION; +} + sub init_server_keys { my ($self, %options) = @_; @@ -218,12 +226,15 @@ sub init { $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{timeout} = defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{timeout}) && $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{timeout} =~ /(\d+)/ ? $1 : 50; - $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{cipher} = 'Cipher::AES' - if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{cipher}) || $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{cipher} eq ''); - $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{keysize} = 32 - if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{keysize}) || $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{keysize} eq ''); - $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{vector} = '0123456789012345' - if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{vector}) || $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{vector} eq ''); + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_cipher} = 'AES' + if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_cipher}) || $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_cipher} eq ''); + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_padding} = 1 # PKCS5 padding + if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_padding}) || $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_padding} eq ''); + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_keysize} = 32 + if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_keysize}) || $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_keysize} eq ''); + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_rotation} = 1440 # minutes + if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_rotation}) || $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_rotation} eq ''); + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_rotation} *= 60; $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{fingerprint_mode} = defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{fingerprint_mode}) && $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{fingerprint_mode} =~ /^\s*(always|firt|strict)\s*/i ? lc($1) : 'first'; @@ -242,6 +253,7 @@ sub init { defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_autocreate_schema}) && $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_autocreate_schema} =~ /(\d+)/ ? $1 : 1; gorgone::standard::library::init_database( gorgone => $gorgone, + version => $self->get_version(), type => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_type}, db => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_name}, host => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{gorgone_db_host}, @@ -267,6 +279,39 @@ sub init { $self->set_signal_handlers(); } +sub init_external_informations { + my ($self) = @_; + + my ($status, $sth) = $self->{db_gorgone}->query( + "SELECT `identity`, `ctime`, `mtime`, `key`, `oldkey`, `iv`, `oldiv` FROM gorgone_identity ORDER BY id DESC" + ); + if ($status == -1) { + $self->{logger}->writeLogError("[core] cannot load gorgone_identity"); + return 0; + } + + $self->{identity_infos} = {}; + while (my $row = $sth->fetchrow_arrayref()) { + next if (!defined($row->[3])); + + if (!defined($self->{identity_infos}->{ $row->[0] })) { + $self->{identity_infos}->{ $row->[0] } = { + ctime => $row->[1], + mtime => $row->[2], + key => pack('H*', $row->[3]), + oldkey => defined($row->[4]) ? pack('H*', $row->[4]) : undef, + iv => pack('H*', $row->[5]), + oldiv => defined($row->[6]) ? pack('H*', $row->[6]) : undef + }; + } + } + + $self->{external_crypt_mode} = Crypt::Mode::CBC->new( + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_cipher}, + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_padding} + ); +} + sub set_signal_handlers { my ($self) = @_; @@ -483,7 +528,7 @@ sub read_internal_message { eval { $plaintext = $self->{cipher}->decrypt($message, $key, $self->{internal_crypt}->{iv}); }; - if (defined($plaintext) && $plaintext =~ /^\[[A-Za-z_\-]+?\]/) { + if (defined($plaintext) && $plaintext =~ /^\[[A-Za-z0-9_\-]+?\]/) { return ($identity, $plaintext); } } @@ -551,7 +596,7 @@ sub send_internal_message { sub broadcast_run { my ($self, %options) = @_; - my $data = gorgone::standard::library::json_decode(data => $options{data}, logger => $self->{logger}); + my $data = gorgone::standard::library::json_decode(module => 'core', data => $options{data}, logger => $self->{logger}); return if (!defined($data)); if ($options{action} eq 'BCASTLOGGER') { @@ -724,9 +769,160 @@ sub router_internal_event { } } +sub is_handshake_done { + my ($self, %options) = @_; + + if (defined($self->{identity_infos}->{ $options{identity} })) { + return (1, $self->{identity_infos}->{ $options{identity} }); + } + + return 0; +} + +sub check_external_rotate_keys { + my ($self, %options) = @_; + + my $time = time(); + my ($rv, $key, $iv); + foreach my $id (keys %{$self->{identity_infos}}) { + if ($self->{identity_infos}->{$id}->{mtime} < ($time - 86400)) { + $self->{logger}->writeLogDebug('[core] clean external key for ' . $id); + delete $self->{identity_infos}->{$id}; + next; + } + next if ($self->{identity_infos}->{$id}->{ctime} > ($time - $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_rotation})); + + $self->{logger}->writeLogDebug('[core] rotate external key for ' . $id); + + ($rv, $key) = gorgone::standard::library::generate_symkey( + keysize => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_keysize} + ); + ($rv, $iv) = gorgone::standard::library::generate_symkey(keysize => 16); + $rv = gorgone::standard::library::update_identity_attrs( + dbh => $self->{db_gorgone}, + identity => $id, + ctime => $time, + oldkey => unpack('H*', $self->{identity_infos}->{$id}->{key}), + oldiv => unpack('H*', $self->{identity_infos}->{$id}->{iv}), + key => unpack('H*', $key), + iv => unpack('H*', $iv) + ); + next if ($rv == -1); + + my $message = gorgone::standard::library::json_encode( + data => { + hostname => $self->{hostname}, + cipher => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_cipher}, + padding => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_padding}, + key => unpack('H*', $key), + iv => unpack('H*', $iv) + } + ); + + $self->external_core_response( + message => '[KEY] ' . $message, + identity => $id, + cipher_infos => { + key => $self->{identity_infos}->{$id}->{key}, + iv => $self->{identity_infos}->{$id}->{iv} + } + ); + + $self->{identity_infos}->{$id}->{ctime} = $time; + $self->{identity_infos}->{$id}->{oldkey} = $self->{identity_infos}->{$id}->{key}; + $self->{identity_infos}->{$id}->{oldiv} = $self->{identity_infos}->{$id}->{iv}; + $self->{identity_infos}->{$id}->{key} = $key; + $self->{identity_infos}->{$id}->{iv} = $iv; + } +} + +sub external_decrypt_message { + my ($self, %options) = @_; + + my $crypt = MIME::Base64::decode_base64($options{message}); + + my $keys = [ { key => $options{cipher_infos}->{key}, iv => $options{cipher_infos}->{iv} } ]; + if (defined($options{cipher_infos}->{oldkey})) { + push @$keys, { key => $options{cipher_infos}->{oldkey}, iv => $options{cipher_infos}->{oldiv} } + } + foreach my $key (@$keys) { + my $plaintext; + eval { + $plaintext = $self->{external_crypt_mode}->decrypt($crypt, $key->{key}, $key->{iv}); + }; + if (defined($plaintext) && $plaintext =~ /^\[[A-Za-z0-9_\-]+?\]/) { + return (0, $plaintext); + } + } + + $self->{logger}->writeLogError("[core] external decrypt issue: " . ($@ ? $@ : 'no message')); + return (-1, ($@ ? $@ : 'no message')); +} + +sub external_core_response { + my ($self, %options) = @_; + + my $message = $options{message}; + if (!defined($message)) { + my $response_type = defined($options{response_type}) ? $options{response_type} : 'ACK'; + my $data = gorgone::standard::library::json_encode(data => { code => $options{code}, data => $options{data} }); + # We add 'target' for 'PONG', 'SYNCLOGS'. Like that 'gorgone-proxy can get it + $message = '[' . $response_type . '] [' . (defined($options{token}) ? $options{token} : '') . '] ' . ($response_type =~ /^PONG|SYNCLOGS$/ ? '[] ' : '') . $data; + } + + if (defined($options{cipher_infos})) { + eval { + $message = $self->{external_crypt_mode}->encrypt( + $message, + $options{cipher_infos}->{key}, + $options{cipher_infos}->{iv} + ); + }; + if ($@) { + $self->{logger}->writeLogError("[core] external_core_response encrypt issue: " . $@); + return undef; + } + + $message = MIME::Base64::encode_base64($message, ''); + } + + zmq_sendmsg($self->{external_socket}, pack('H*', $options{identity}), ZMQ_DONTWAIT|ZMQ_SNDMORE); + zmq_sendmsg($self->{external_socket}, $message, ZMQ_DONTWAIT); +} + +sub external_core_key_response { + my ($self, %options) = @_; + + my $data = gorgone::standard::library::json_encode( + data => { + hostname => $self->{hostname}, + cipher => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_cipher}, + padding => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_padding}, + key => unpack('H*', $options{key}), + iv => unpack('H*', $options{iv}) + } + ); + return -1 if (!defined($data)); + + my $crypttext; + eval { + $crypttext = $options{client_pubkey}->encrypt("[KEY] " . $data, 'v1.5'); + }; + if ($@) { + $self->{logger}->writeLogError("[core] core key response encrypt issue: " . $@); + return -1; + } + + zmq_sendmsg($self->{external_socket}, pack('H*', $options{identity}), ZMQ_DONTWAIT | ZMQ_SNDMORE); + zmq_sendmsg($self->{external_socket}, MIME::Base64::encode_base64($crypttext, ''), ZMQ_DONTWAIT); + return 0; +} + sub handshake { my ($self, %options) = @_; + my ($rv, $cipher_infos); + # Test if it asks for the pubkey if ($options{message} =~ /^\s*\[GETPUBKEY\]/) { gorgone::standard::library::zmq_core_pubkey_response( @@ -737,81 +933,80 @@ sub handshake { return undef; } - my ($status, $key) = gorgone::standard::library::is_handshake_done(dbh => $self->{db_gorgone}, identity => $options{identity}); + ($rv, $cipher_infos) = $self->is_handshake_done(identity => $options{identity}); + + if ($rv == 1) { + my $response; - if ($status == 1) { - ($status, my $response) = gorgone::standard::library::uncrypt_message( - cipher => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{cipher}, + ($rv, $response) = $self->external_decrypt_message( message => $options{message}, - symkey => $key, - vector => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{vector} + cipher_infos => $cipher_infos ); - if ($status == 0 && $response =~ /^(?:[\[a-zA-Z-_]+?\]\s+\[.*?\]|[\[a-zA-Z-_]+?\]\s*$)/) { - gorgone::standard::library::update_identity(dbh => $self->{db_gorgone}, identity => $options{identity}); - return ($key, $response); + if ($rv == 0 && $response =~ /^(?:[\[a-zA-Z-_]+?\]\s+\[.*?\]|[\[a-zA-Z-_]+?\]\s*$)/) { + gorgone::standard::library::update_identity_mtime(dbh => $self->{db_gorgone}, identity => $options{identity}); + return ($cipher_infos, $response); } # Maybe he want to redo a handshake - $status = 0; + $rv = 0; } - if ($status == -1) { - gorgone::standard::library::zmq_core_response( - socket => $self->{external_socket}, - identity => $options{identity}, - code => GORGONE_ACTION_FINISH_KO, - data => { message => 'Database issue' } - ); - return undef; - } elsif ($status == 0) { + if ($rv == 0) { + my ($client_pubkey, $key, $iv); + # We try to uncrypt - ($status, my $client_pubkey) = gorgone::standard::library::is_client_can_connect( + ($rv, $client_pubkey) = gorgone::standard::library::is_client_can_connect( privkey => $self->{server_privkey}, message => $options{message}, logger => $self->{logger}, authorized_clients => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{authorized_clients} ); - if ($status == -1) { - gorgone::standard::library::zmq_core_response( - socket => $self->{external_socket}, + if ($rv == -1) { + $self->external_core_response( identity => $options{identity}, code => GORGONE_ACTION_FINISH_KO, data => { message => 'handshake issue' } ); return undef; } - my ($status, $symkey) = gorgone::standard::library::generate_symkey( - logger => $self->{logger}, - cipher => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{cipher}, - keysize => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{keysize} + ($rv, $key) = gorgone::standard::library::generate_symkey( + keysize => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_keysize} ); - if ($status == -1) { - gorgone::standard::library::zmq_core_response( - socket => $self->{external_socket}, identity => $options{identity}, - code => GORGONE_ACTION_FINISH_KO, - data => { message => 'handshake issue' } - ); - } - if (gorgone::standard::library::add_identity(dbh => $self->{db_gorgone}, identity => $options{identity}, symkey => $symkey) == -1) { - gorgone::standard::library::zmq_core_response( - socket => $self->{external_socket}, identity => $options{identity}, + ($rv, $iv) = gorgone::standard::library::generate_symkey(keysize => 16); + + if (gorgone::standard::library::add_identity(dbh => $self->{db_gorgone}, identity => $options{identity}, key => $key, iv => $iv) == -1) { + $self->external_core_response( + identity => $options{identity}, code => GORGONE_ACTION_FINISH_KO, data => { message => 'handshake issue' } ); } - if (gorgone::standard::library::zmq_core_key_response( - logger => $self->{logger}, socket => $self->{external_socket}, identity => $options{identity}, - client_pubkey => $client_pubkey, hostname => $self->{hostname}, symkey => $symkey) == -1 - ) { - gorgone::standard::library::zmq_core_response( - socket => $self->{external_socket}, identity => $options{identity}, + $self->{identity_infos}->{ $options{identity} } = { + ctime => time(), + mtime => time(), + key => $key, + oldkey => undef, + iv => $iv, + oldiv => undef + }; + + $rv = $self->external_core_key_response( + identity => $options{identity}, + client_pubkey => $client_pubkey, + key => $key, + iv => $iv + ); + if ($rv == -1) { + $self->external_core_response( + identity => $options{identity}, code => GORGONE_ACTION_FINISH_KO, data => { message => 'handshake issue' } ); } - return undef; - } + } + + return undef; } sub send_message_parent { @@ -827,15 +1022,12 @@ sub send_message_parent { ); } if ($options{router_type} eq 'external') { - my ($status, $key) = gorgone::standard::library::is_handshake_done(dbh => $gorgone->{db_gorgone}, identity => $options{identity}); - return if ($status == 0); - gorgone::standard::library::zmq_core_response( - socket => $gorgone->{external_socket}, + my ($rv, $cipher_infos) = $gorgone->is_handshake_done(identity => $options{identity}); + return if ($rv == 0); + $gorgone->external_core_response( + cipher_infos => $cipher_infos, identity => $options{identity}, response_type => $options{response_type}, - cipher => $gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{cipher}, - vector => $gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{vector}, - symkey => $key, token => $options{token}, code => $options{code}, data => $options{data} @@ -851,7 +1043,7 @@ sub router_external_event { ); last if (!defined($identity)); - my ($key, $uncrypt_message) = $gorgone->handshake( + my ($cipher_infos, $uncrypt_message) = $gorgone->handshake( identity => $identity, message => $message, ); @@ -861,12 +1053,10 @@ sub router_external_event { identity => $identity, router_type => 'external', ); - gorgone::standard::library::zmq_core_response( - socket => $gorgone->{external_socket}, - identity => $identity, response_type => $response_type, - cipher => $gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{cipher}, - vector => $gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{vector}, - symkey => $key, + $gorgone->external_core_response( + identity => $identity, + cipher_infos => $cipher_infos, + response_type => $response_type, token => $token, code => $code, data => $response ); @@ -938,7 +1128,10 @@ sub check_exit_modules { ($current_time - $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_core_key_ctime}) > $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_rotation}) { $self->broadcast_core_key(); } - + if (defined($self->{external_socket})) { + $self->check_external_rotate_keys(); + } + my $count = 0; if (time() - $self->{cb_timer_check} > 15 || $self->{stop} == 1) { if ($self->{stop} == 1 && (!defined($self->{sigterm_last_time}) || ($current_time - $self->{sigterm_last_time}) >= 10)) { @@ -1018,6 +1211,8 @@ sub run { ); if (defined($gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_type}) && $gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_type} ne '') { if ($gorgone->{keys_loaded}) { + $gorgone->init_external_informations(); + $gorgone->{external_socket} = gorgone::standard::library::create_com( type => $gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_type}, path => $gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_path}, diff --git a/gorgone/gorgone/class/logger.pm b/gorgone/gorgone/class/logger.pm index 6af66a89017..c169033b1ba 100644 --- a/gorgone/gorgone/class/logger.pm +++ b/gorgone/gorgone/class/logger.pm @@ -211,6 +211,7 @@ sub writeLog { } $newmsg = encode('UTF-8', $newmsg); + chomp($newmsg); if ($self->{log_mode} == 0) { print "$newmsg\n"; } elsif ($self->{log_mode} == 1) { diff --git a/gorgone/gorgone/class/script.pm b/gorgone/gorgone/class/script.pm index 3727ecf1f67..d7ff1ff8a4b 100644 --- a/gorgone/gorgone/class/script.pm +++ b/gorgone/gorgone/class/script.pm @@ -59,7 +59,8 @@ sub new { 'logfile=s' => \$self->{log_file}, 'severity=s' => \$self->{severity}, 'flushoutput' => \$self->{flushoutput}, - 'help|?' => \$self->{help} + 'help|?' => \$self->{help}, + 'version' => \$self->{version} }; return $self; } @@ -127,6 +128,10 @@ sub parse_options { Getopt::Long::Configure('bundling'); die "Command line error" if (!GetOptions(%{$self->{options}})); pod2usage(-exitval => 1, -input => $FindBin::Bin . "/" . $FindBin::Script) if ($self->{help}); + if ($self->{version}) { + print "version: " . $self->get_version() . "\n"; + exit(0); + } } sub run { diff --git a/gorgone/gorgone/modules/core/dbcleaner/class.pm b/gorgone/gorgone/modules/core/dbcleaner/class.pm index 9bdf3c7a37c..c04d1fcf219 100644 --- a/gorgone/gorgone/modules/core/dbcleaner/class.pm +++ b/gorgone/gorgone/modules/core/dbcleaner/class.pm @@ -121,7 +121,7 @@ sub action_dbclean { ) if (!defined($options{cycle})); $self->{logger}->writeLogDebug("[dbcleaner] Purge database in progress..."); - my ($status) = $self->{db_gorgone}->query("DELETE FROM gorgone_identity WHERE `ctime` < " . $self->{db_gorgone}->quote(time() - $self->{config}->{purge_sessions_time})); + my ($status) = $self->{db_gorgone}->query("DELETE FROM gorgone_identity WHERE `mtime` < " . $self->{db_gorgone}->quote(time() - $self->{config}->{purge_sessions_time})); my ($status2) = $self->{db_gorgone}->query( "DELETE FROM gorgone_history WHERE (instant = 1 AND `ctime` < " . (time() - 86400) . ") OR `ctime` < " . $self->{db_gorgone}->quote(time() - $self->{config}->{purge_history_time}) ); diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index c011fc8d3ba..ddc1464585d 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -298,6 +298,7 @@ sub routing { # Mode zmq pull if ($register_nodes->{$target_parent}->{type} eq 'pull') { pull_request( + gorgone => $options{gorgone}, dbh => $options{dbh}, action => $action, data => $data, @@ -780,11 +781,12 @@ sub pull_request { ); return undef; } - my ($status, $key) = gorgone::standard::library::is_handshake_done( - dbh => $options{dbh}, - identity => unpack('H*', $register_nodes->{ $options{target_parent} }->{identity}) + + my $identity = unpack('H*', $register_nodes->{ $options{target_parent} }->{identity}); + my ($rv, $cipher_infos) = $options{gorgone}->is_handshake_done( + identity => $identity ); - if ($status == 0) { + if ($rv == 0) { gorgone::standard::library::add_history( dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, @@ -795,12 +797,9 @@ sub pull_request { return undef; } - gorgone::standard::library::zmq_send_message( - socket => $external_socket, - cipher => $config_core->{gorgonecore}->{cipher}, - vector => $config_core->{gorgonecore}->{vector}, - symkey => $key, - identity => $register_nodes->{ $options{target_parent} }->{identity}, + $options{gorgone}->external_core_response( + cipher_infos => $cipher_infos, + identity => $identity, message => $message ); } diff --git a/gorgone/gorgone/modules/plugins/newtest/hooks.pm b/gorgone/gorgone/modules/plugins/newtest/hooks.pm index f0e14133754..33b7007d791 100644 --- a/gorgone/gorgone/modules/plugins/newtest/hooks.pm +++ b/gorgone/gorgone/modules/plugins/newtest/hooks.pm @@ -175,16 +175,14 @@ sub broadcast { my (%options) = @_; foreach my $container_id (keys %$containers) { - if ($containers->{$container_id}->{running} == 1) { - gorgone::standard::library::zmq_send_internal_message( - socket => $options{socket}, - internal_crypt => $options{internal_crypt}, - identity => 'gorgone-newtest-' . $container_id, - action => $options{action}, - data => $options{data}, - token => $options{token} - ); - } + next if ($containers->{$container_id}->{ready} != 1); + + $options{gorgone}->send_internal_message( + identity => 'gorgone-newtest-' . $container_id, + action => $options{action}, + data => $options{data}, + token => $options{token} + ); } } diff --git a/gorgone/gorgone/modules/plugins/scom/hooks.pm b/gorgone/gorgone/modules/plugins/scom/hooks.pm index cabc43982e9..3af21277c16 100644 --- a/gorgone/gorgone/modules/plugins/scom/hooks.pm +++ b/gorgone/gorgone/modules/plugins/scom/hooks.pm @@ -174,16 +174,14 @@ sub broadcast { my (%options) = @_; foreach my $container_id (keys %$containers) { - if ($containers->{$container_id}->{running} == 1) { - gorgone::standard::library::zmq_send_internal_message( - socket => $options{socket}, - internal_crypt => $options{internal_crypt}, - identity => 'gorgone-scom-' . $container_id, - action => $options{action}, - data => $options{data}, - token => $options{token} - ); - } + next if ($containers->{$container_id}->{ready} != 1); + + $options{gorgone}->send_internal_message( + identity => 'gorgone-scom-' . $container_id, + action => $options{action}, + data => $options{data}, + token => $options{token} + ); } } diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index cbe2f913a84..769822a6f4d 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -30,8 +30,6 @@ use File::Basename; use Crypt::PK::RSA; use Crypt::PRNG; use Crypt::Mode::CBC; -use Crypt::CBC; -use Data::Dumper; use File::Path; use File::Basename; use MIME::Base64; @@ -188,34 +186,6 @@ sub zmq_core_key_response { return 0; } -sub zmq_core_response { - my (%options) = @_; - my $msg; - my $response_type = defined($options{response_type}) ? $options{response_type} : 'ACK'; - - if (defined($options{identity})) { - zmq_sendmsg($options{socket}, pack('H*', $options{identity}), ZMQ_DONTWAIT | ZMQ_SNDMORE); - } - - my $data = json_encode(data => { code => $options{code}, data => $options{data} }); - # We add 'target' for 'PONG', 'SYNCLOGS'. Like that 'gorgone-proxy can get it - $msg = '[' . $response_type . '] [' . (defined($options{token}) ? $options{token} : '') . '] ' . ($response_type =~ /^PONG|SYNCLOGS$/ ? '[] ' : '') . $data; - - if (defined($options{cipher})) { - my $cipher = Crypt::CBC->new( - -key => $options{symkey}, - -keysize => length($options{symkey}), - -cipher => $options{cipher}, - -iv => $options{vector}, - -header => 'none', - -literal_key => 1 - ); - $msg = $cipher->encrypt($msg); - $msg = MIME::Base64::encode_base64($msg, ''); - } - zmq_sendmsg($options{socket}, $msg, ZMQ_DONTWAIT); -} - sub zmq_get_routing_id { my (%options) = @_; @@ -234,48 +204,6 @@ sub zmq_events { return zmq_getsockopt($options{socket}, ZMQ_EVENTS); } -sub cipher_encrypt { - my (%options) = @_; - - my $m = Crypt::Mode::CBC->new($options{cipher}, $options{padding}); - my $ciphertext; - eval { - $ciphertext = $m->encrypt($options{message}, $options{key}, $options{iv}); - }; - if ($@) { - if (defined($options{logger})) { - $options{logger}->writeLogError("[$options{module}] Sym encrypt issue: " . $@); - } - return (-1, $@); - } - return (0, $ciphertext); -} - -sub uncrypt_message { - my (%options) = @_; - my $plaintext; - - my $cipher = Crypt::CBC->new( - -key => $options{symkey}, - -keysize => length($options{symkey}), - -cipher => $options{cipher}, - -iv => $options{vector}, - -header => 'none', - -literal_key => 1 - ); - eval { - my $cryptedmessage = MIME::Base64::decode_base64($options{message}); - $plaintext = $cipher->decrypt($cryptedmessage); - }; - if ($@) { - if (defined($options{logger})) { - $options{logger}->writeLogError("[core] Sym encrypt issue: " . $@); - } - return (-1, $@); - } - return (0, $plaintext); -} - sub generate_token { my (%options) = @_; @@ -291,28 +219,6 @@ sub generate_symkey { return (0, $random_key); } -sub client_get_secret { - my (%options) = @_; - my $plaintext; - - eval { - my $cryptedtext = MIME::Base64::decode_base64($options{message}); - $plaintext = $options{privkey}->decrypt($cryptedtext, 'v1.5'); - }; - if ($@) { - return (-1, "Decoding issue: $@"); - } - - $plaintext = unpack('H*', $plaintext); - if ($plaintext !~ /^5b(.*?)5d(.*?)5b(.*?)5d(.*?)5b(.*)5d$/i) { - return (-1, 'Wrong protocol'); - } - - my $hostname = pack('H*', $3); - my $symkey = pack('H*', $5); - return (0, 'ok', $symkey, $hostname); -} - sub client_helo_encrypt { my (%options) = @_; my $ciphertext; @@ -380,18 +286,6 @@ sub is_client_can_connect { return (0, $client_pubkey); } -sub is_handshake_done { - my (%options) = @_; - - my ($status, $sth) = $options{dbh}->query("SELECT `key` FROM gorgone_identity WHERE identity = " . $options{dbh}->quote($options{identity}) . " ORDER BY id DESC LIMIT 1"); - return if ($status == -1); - if (my $row = $sth->fetchrow_hashref()) { - return 0 if (!defined($row->{key}) || $row->{key} eq ''); - return (1, pack('H*', $row->{key})); - } - return 0; -} - ####################### # internal functions ####################### @@ -685,11 +579,34 @@ sub kill { # Database functions ####################### -sub update_identity { +sub update_identity_attrs { + my (%options) = @_; + + my $values = []; + foreach ('key', 'oldkey', 'iv', 'oldiv', 'ctime') { + next if (!defined($options{$_})); + + if ($options{$_} eq 'NULL') { + push @$values, "`$_` = NULL"; + } else { + push @$values, "`$_` = " . $options{dbh}->quote($options{$_}); + } + } + + my ($status, $sth) = $options{dbh}->query( + "UPDATE gorgone_identity SET " . + join(', ', @$values) . + " WHERE `identity` = " . $options{dbh}->quote($options{identity}) . " AND " . + " `id` = (SELECT `id` FROM gorgone_identity WHERE `identity` = " . $options{dbh}->quote($options{identity}) . " ORDER BY `id` DESC LIMIT 1)" + ); + return $status; +} + +sub update_identity_mtime { my (%options) = @_; my ($status, $sth) = $options{dbh}->query( - "UPDATE gorgone_identity SET `ctime` = " . $options{dbh}->quote(time()) . + "UPDATE gorgone_identity SET `mtime` = " . $options{dbh}->quote(time()) . " WHERE `identity` = " . $options{dbh}->quote($options{identity}) . " AND " . " `id` = (SELECT `id` FROM gorgone_identity WHERE `identity` = " . $options{dbh}->quote($options{identity}) . " ORDER BY `id` DESC LIMIT 1)" ); @@ -699,11 +616,14 @@ sub update_identity { sub add_identity { my (%options) = @_; + my $time = time(); my ($status, $sth) = $options{dbh}->query( - "INSERT INTO gorgone_identity (`ctime`, `identity`, `key`) VALUES (" . - $options{dbh}->quote(time()) . ", " . + "INSERT INTO gorgone_identity (`ctime`, `mtime`, `identity`, `key`, `iv`) VALUES (" . + $options{dbh}->quote($time) . ", " . + $options{dbh}->quote($time) . ", " . $options{dbh}->quote($options{identity}) . ", " . - $options{dbh}->quote(unpack('H*', $options{symkey})) . ")" + $options{dbh}->quote(unpack('H*', $options{key})) . ", " . + $options{dbh}->quote(unpack('H*', $options{iv})) . ")", ); return $status; } @@ -775,7 +695,7 @@ sub json_decode { }; if ($@) { if (defined($options{logger})) { - $options{logger}->writeLogError("[core] Cannot decode json data: $@"); + $options{logger}->writeLogError("[$options{module}] Cannot decode json data: $@"); } return undef; } @@ -871,63 +791,6 @@ sub build_protocol { return '[' . $action . '] [' . $token . '] [' . $target . '] ' . $data; } -sub zmq_send_internal_message { - my (%options) = @_; - my ($message, $rv) = ($options{message}); - - if (!defined($message)) { - $message = build_protocol(%options); - } - if (defined($options{identity})) { - zmq_sendmsg($options{socket}, $options{identity}, ZMQ_DONTWAIT | ZMQ_SNDMORE); - } - - if (defined($options{internal_crypt}) && $options{internal_crypt}->{enable} == 1) { - my $key = $options{internal_crypt}->{symkeys}->{ $options{internal_crypt}->{current} }; - if (defined($options{identity})) { - if (defined($options{internal_crypt}->{identity}->{ $options{identity} })) { - $key = $options{internal_crypt}->{symkeys}->{ $options{internal_crypt}->{identity}->{ $options{identity} } }; - } - } - ($rv, $message) = cipher_encrypt( - logger => $options{logger}, - module => $options{module_id}, - key => $key, - cipher => $options{internal_crypt}->{cipher}, - padding => $options{internal_crypt}->{padding}, - iv => $options{internal_crypt}->{iv} - ); - return if ($rv == -1); - } - - zmq_sendmsg($options{socket}, $message, ZMQ_DONTWAIT); -} - -sub zmq_send_message { - my (%options) = @_; - my $message = $options{message}; - - if (!defined($message)) { - $message = build_protocol(%options); - } - if (defined($options{identity})) { - zmq_sendmsg($options{socket}, $options{identity}, ZMQ_DONTWAIT | ZMQ_SNDMORE); - } - if (defined($options{cipher})) { - my $cipher = Crypt::CBC->new( - -key => $options{symkey}, - -keysize => length($options{symkey}), - -cipher => $options{cipher}, - -iv => $options{vector}, - -header => 'none', - -literal_key => 1 - ); - $message = $cipher->encrypt($message); - $message = MIME::Base64::encode_base64($message, ''); - } - zmq_sendmsg($options{socket}, $message, ZMQ_DONTWAIT); -} - sub zmq_dealer_read_message { my (%options) = @_; @@ -983,6 +846,111 @@ sub add_zmq_pollin { }; } +sub create_schema { + my (%options) = @_; + + $options{logger}->writeLogInfo("[core] create schema $options{version}"); + my $schema = [ + q{ + PRAGMA encoding = "UTF-8" + }, + q{ + CREATE TABLE `gorgone_information` ( + `key` varchar(1024) DEFAULT NULL, + `value` varchar(1024) DEFAULT NULL + ); + }, + qq{ + INSERT INTO gorgone_information (`key`, `value`) VALUES ('version', '$options{version}'); + }, + q{ + CREATE TABLE `gorgone_identity` ( + `id` INTEGER PRIMARY KEY, + `ctime` int(11) DEFAULT NULL, + `mtime` int(11) DEFAULT NULL, + `identity` varchar(2048) DEFAULT NULL, + `key` varchar(1024) DEFAULT NULL, + `oldkey` varchar(1024) DEFAULT NULL, + `iv` varchar(1024) DEFAULT NULL, + `oldiv` varchar(1024) DEFAULT NULL, + `parent` int(11) DEFAULT '0' + ); + }, + q{ + CREATE INDEX idx_gorgone_identity ON gorgone_identity (identity); + }, + q{ + CREATE INDEX idx_gorgone_parent ON gorgone_identity (parent); + }, + q{ + CREATE TABLE `gorgone_history` ( + `id` INTEGER PRIMARY KEY, + `token` varchar(2048) DEFAULT NULL, + `code` int(11) DEFAULT NULL, + `etime` int(11) DEFAULT NULL, + `ctime` FLOAT DEFAULT NULL, + `instant` int(11) DEFAULT '0', + `data` TEXT DEFAULT NULL + ); + }, + q{ + CREATE INDEX idx_gorgone_history_id ON gorgone_history (id); + }, + q{ + CREATE INDEX idx_gorgone_history_token ON gorgone_history (token); + }, + q{ + CREATE INDEX idx_gorgone_history_etime ON gorgone_history (etime); + }, + q{ + CREATE INDEX idx_gorgone_history_code ON gorgone_history (code); + }, + q{ + CREATE INDEX idx_gorgone_history_ctime ON gorgone_history (ctime); + }, + q{ + CREATE INDEX idx_gorgone_history_instant ON gorgone_history (instant); + }, + q{ + CREATE TABLE `gorgone_synchistory` ( + `id` int(11) NOT NULL, + `ctime` FLOAT DEFAULT NULL, + `last_id` int(11) DEFAULT NULL + ); + }, + q{ + CREATE UNIQUE INDEX idx_gorgone_synchistory_id ON gorgone_synchistory (id); + }, + q{ + CREATE TABLE `gorgone_target_fingerprint` ( + `id` INTEGER PRIMARY KEY, + `target` varchar(2048) DEFAULT NULL, + `fingerprint` varchar(4096) DEFAULT NULL + ); + }, + q{ + CREATE INDEX idx_gorgone_target_fingerprint_target ON gorgone_target_fingerprint (target); + }, + q{ + CREATE TABLE `gorgone_centreon_judge_spare` ( + `cluster_name` varchar(2048) NOT NULL, + `status` int(11) NOT NULL, + `data` TEXT DEFAULT NULL + ); + }, + q{ + CREATE INDEX idx_gorgone_centreon_judge_spare_cluster_name ON gorgone_centreon_judge_spare (cluster_name); + } + ]; + foreach (@$schema) { + my ($status, $sth) = $options{gorgone}->{db_gorgone}->query($_); + if ($status == -1) { + $options{logger}->writeLogError("[core] create schema issue"); + exit(1); + } + } +} + sub init_database { my (%options) = @_; @@ -1006,89 +974,62 @@ sub init_database { exit(1); } - if (defined($options{autocreate_schema}) && $options{autocreate_schema} == 1) { - my $requests = [ + return if (!defined($options{autocreate_schema}) || $options{autocreate_schema} != 1); + + my $db_version = '1.0'; + my ($status, $sth) = $options{gorgone}->{db_gorgone}->query(q{SELECT `value` FROM gorgone_information WHERE `key` = 'version'}); + if ($status == -1) { + ($status, $sth) = $options{gorgone}->{db_gorgone}->query(q{SELECT 1 FROM gorgone_identity LIMIT 1}); + if ($status == -1) { + create_schema(gorgone => $options{gorgone}, logger => $options{logger}, version => $options{version}); + return ; + } + } else { + my $row = $sth->fetchrow_arrayref(); + $db_version = $row->[0] if (defined($row)); + } + + $options{logger}->writeLogInfo("[core] update schema $db_version -> $options{version}"); + + if ($db_version eq '1.0') { + my $schema = [ q{ PRAGMA encoding = "UTF-8" }, q{ - CREATE TABLE IF NOT EXISTS `gorgone_identity` ( - `id` INTEGER PRIMARY KEY, - `ctime` int(11) DEFAULT NULL, - `identity` varchar(2048) DEFAULT NULL, - `key` varchar(4096) DEFAULT NULL, - `parent` int(11) DEFAULT '0' + CREATE TABLE `gorgone_information` ( + `key` varchar(1024) DEFAULT NULL, + `value` varchar(1024) DEFAULT NULL ); }, - q{ - CREATE INDEX IF NOT EXISTS idx_gorgone_identity ON gorgone_identity (identity); + qq{ + INSERT INTO gorgone_information (`key`, `value`) VALUES ('version', '$options{version}'); }, q{ - CREATE INDEX IF NOT EXISTS idx_gorgone_parent ON gorgone_identity (parent); + ALTER TABLE `gorgone_identity` ADD COLUMN `mtime` int(11) DEFAULT NULL DEFAULT NULL; }, q{ - CREATE TABLE IF NOT EXISTS `gorgone_history` ( - `id` INTEGER PRIMARY KEY, - `token` varchar(2048) DEFAULT NULL, - `code` int(11) DEFAULT NULL, - `etime` int(11) DEFAULT NULL, - `ctime` FLOAT DEFAULT NULL, - `instant` int(11) DEFAULT '0', - `data` TEXT DEFAULT NULL - ); - }, - q{ - CREATE INDEX IF NOT EXISTS idx_gorgone_history_id ON gorgone_history (id); + ALTER TABLE `gorgone_identity` ADD COLUMN `oldkey` varchar(1024) DEFAULT NULL; }, q{ - CREATE INDEX IF NOT EXISTS idx_gorgone_history_token ON gorgone_history (token); + ALTER TABLE `gorgone_identity` ADD COLUMN `oldiv` varchar(1024) DEFAULT NULL; }, q{ - CREATE INDEX IF NOT EXISTS idx_gorgone_history_etime ON gorgone_history (etime); - }, - q{ - CREATE INDEX IF NOT EXISTS idx_gorgone_history_code ON gorgone_history (code); - }, - q{ - CREATE INDEX IF NOT EXISTS idx_gorgone_history_ctime ON gorgone_history (ctime); - }, - q{ - CREATE INDEX IF NOT EXISTS idx_gorgone_history_instant ON gorgone_history (instant); - }, - q{ - CREATE TABLE IF NOT EXISTS `gorgone_synchistory` ( - `id` int(11) NOT NULL, - `ctime` FLOAT DEFAULT NULL, - `last_id` int(11) DEFAULT NULL - ); - }, - q{ - CREATE UNIQUE INDEX IF NOT EXISTS idx_gorgone_synchistory_id ON gorgone_synchistory (id); - }, - q{ - CREATE TABLE IF NOT EXISTS `gorgone_target_fingerprint` ( - `id` INTEGER PRIMARY KEY, - `target` varchar(2048) DEFAULT NULL, - `fingerprint` varchar(4096) DEFAULT NULL - ); - }, - q{ - CREATE INDEX IF NOT EXISTS idx_gorgone_target_fingerprint_target ON gorgone_target_fingerprint (target); - }, - q{ - CREATE TABLE IF NOT EXISTS `gorgone_centreon_judge_spare` ( - `cluster_name` varchar(2048) NOT NULL, - `status` int(11) NOT NULL, - `data` TEXT DEFAULT NULL - ); - }, - q{ - CREATE INDEX IF NOT EXISTS idx_gorgone_centreon_judge_spare_cluster_name ON gorgone_centreon_judge_spare (cluster_name); + ALTER TABLE `gorgone_identity` ADD COLUMN `iv` varchar(1024) DEFAULT NULL; } ]; - foreach (@$requests) { - $options{gorgone}->{db_gorgone}->query($_); + foreach (@$schema) { + my ($status, $sth) = $options{gorgone}->{db_gorgone}->query($_); + if ($status == -1) { + $options{logger}->writeLogError("[core] update schema issue"); + exit(1); + } } + $db_version = '22.04.0'; + } + + if ($db_version ne $options{version}) { + $options{gorgone}->{db_gorgone}->query("UPDATE gorgone_information SET `value` = '$options{version}' WHERE `key` = 'version'"); } } diff --git a/gorgone/gorgoned b/gorgone/gorgoned index 85d2929291e..fdb423af470 100644 --- a/gorgone/gorgoned +++ b/gorgone/gorgoned @@ -50,6 +50,10 @@ Specify the path to the yaml configuration file (default: ''). Print a brief help message and exits. +=item B<--version> + +Print version message and exits. + =back =head1 DESCRIPTION diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 5113517347d..a3171a62ca6 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -16,7 +16,6 @@ Requires: perl(Archive::Tar) Requires: perl(Schedule::Cron) Requires: perl(ZMQ::LibZMQ4) Requires: perl(ZMQ::Constants) -Requires: perl(Crypt::CBC) Requires: perl(JSON::XS) Requires: perl(JSON::PP) Requires: perl(XML::Simple) diff --git a/gorgone/schema/gorgone_database.sql b/gorgone/schema/gorgone_database.sql index 42a15bdeb0e..7487a8b831d 100644 --- a/gorgone/schema/gorgone_database.sql +++ b/gorgone/schema/gorgone_database.sql @@ -1,10 +1,19 @@ PRAGMA encoding = "UTF-8"; +CREATE TABLE `gorgone_information` ( + `key` varchar(1024) DEFAULT NULL, + `value` varchar(1024) DEFAULT NULL +); + CREATE TABLE IF NOT EXISTS `gorgone_identity` ( `id` INTEGER PRIMARY KEY, `ctime` int(11) DEFAULT NULL, + `mtime` int(11) DEFAULT NULL, `identity` varchar(2048) DEFAULT NULL, - `key` varchar(4096) DEFAULT NULL, + `key` varchar(1024) DEFAULT NULL, + `oldkey` varchar(1024) DEFAULT NULL, + `iv` varchar(1024) DEFAULT NULL, + `oldiv` varchar(1024) DEFAULT NULL, `parent` int(11) DEFAULT '0' ); From ad7e34d0bda86e76d6f8df3cca539e5b5a25358e Mon Sep 17 00:00:00 2001 From: Tom Darneix <tomdar87@outlook.com> Date: Tue, 8 Mar 2022 09:38:08 +0100 Subject: [PATCH 643/948] Update Centreon API password (#209) --- gorgone/install/src/centreon-api.yaml | 4 ++-- gorgone/packaging/centreon-api.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gorgone/install/src/centreon-api.yaml b/gorgone/install/src/centreon-api.yaml index 4d335ee5d19..39b7eb1ab0f 100644 --- a/gorgone/install/src/centreon-api.yaml +++ b/gorgone/install/src/centreon-api.yaml @@ -3,7 +3,7 @@ gorgone: - name: centreonv2 base_url: "http://127.0.0.1/centreon/api/latest/" username: admin - password: centreon + password: Centreon!2021 - name: clapi username: admin - password: centreon + password: Centreon!2021 diff --git a/gorgone/packaging/centreon-api.yaml b/gorgone/packaging/centreon-api.yaml index 4d335ee5d19..39b7eb1ab0f 100644 --- a/gorgone/packaging/centreon-api.yaml +++ b/gorgone/packaging/centreon-api.yaml @@ -3,7 +3,7 @@ gorgone: - name: centreonv2 base_url: "http://127.0.0.1/centreon/api/latest/" username: admin - password: centreon + password: Centreon!2021 - name: clapi username: admin - password: centreon + password: Centreon!2021 From 5798e5983b987a228b4212c20c41e23e6b7a7aac Mon Sep 17 00:00:00 2001 From: Laurent Pinsivy <lpinsivy@centreon.com> Date: Tue, 8 Mar 2022 16:26:32 +0100 Subject: [PATCH 644/948] enh(build): enable alma8 (#208) * enh(build): enable alma8 * enh(build): enable alma8 * enh(build): fix build Co-authored-by: Zakaria Guennoune <83596451+zguennoune02@users.noreply.github.com> --- gorgone/Jenkinsfile | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/gorgone/Jenkinsfile b/gorgone/Jenkinsfile index 44d7d6b8048..4b37a3f3ccd 100644 --- a/gorgone/Jenkinsfile +++ b/gorgone/Jenkinsfile @@ -83,18 +83,18 @@ try { stash name: "rpms-centos7", includes: 'output/noarch/*.rpm' sh 'rm -rf output' } - } -/* - 'Packaging centos8': { + }, + + 'Packaging alma8': { node { checkoutCentreonBuild(buildBranch) - sh "./centreon-build/jobs/gorgone/${serie}/gorgone-package.sh centos8" - archiveArtifacts artifacts: 'rpms-centos8.tar.gz' - stash name: "rpms-centos8", includes: 'output/noarch/*.rpm' + sh "./centreon-build/jobs/gorgone/${serie}/gorgone-package.sh alma8" + archiveArtifacts artifacts: 'rpms-alma8.tar.gz' + stash name: "rpms-alma8", includes: 'output/noarch/*.rpm' sh 'rm -rf output' } } -*/ + if ((currentBuild.result ?: 'SUCCESS') != 'SUCCESS') { error('Package stage failure.'); } @@ -104,7 +104,7 @@ try { stage('Delivery') { node { checkoutCentreonBuild(buildBranch) -// unstash 'rpms-centos8' + unstash 'rpms-alma8' unstash 'rpms-centos7' sh "./centreon-build/jobs/gorgone/${serie}/gorgone-delivery.sh" } From 8e028dc38a67d6414c8239f0e63d18b44d27bba1 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Wed, 30 Mar 2022 11:51:12 +0200 Subject: [PATCH 645/948] enh(config): can include multiple directories (#213) --- gorgone/gorgone/class/script.pm | 42 ++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/gorgone/gorgone/class/script.pm b/gorgone/gorgone/class/script.pm index d7ff1ff8a4b..a5891101799 100644 --- a/gorgone/gorgone/class/script.pm +++ b/gorgone/gorgone/class/script.pm @@ -144,26 +144,34 @@ sub run { sub yaml_get_include { my ($self, %options) = @_; - my $dir = File::Basename::dirname($options{include}); - $dir = $options{current_dir} . '/' . $dir if ($dir !~ /^\//); - my $match_files = File::Basename::basename($options{include}); - $match_files =~ s/\*/\\E.*\\Q/g; - $match_files = '\Q' . $match_files . '\E'; + my @all_files = (); + my @dirs = split(/,/, $options{include}); + foreach my $dir (@dirs) { + next if ($dir eq ''); + my $dirname = File::Basename::dirname($dir); + $dirname = $options{current_dir} . '/' . $dirname if ($dirname !~ /^\//); + my $match_files = File::Basename::basename($dir); + $match_files =~ s/\*/\\E.*\\Q/g; + $match_files = '\Q' . $match_files . '\E'; - my $DIR; - if (!opendir($DIR, $dir)) { - $self->{logger}->writeLogError("config - cannot opendir '$dir' error: $!"); - return (); - } - my @sorted_files = (); - while (readdir($DIR)) { - if (-f "$dir/$_" && eval "/^$match_files\$/") { - push @sorted_files, "$dir/$_"; + my @sorted_files = (); + my $DIR; + if (!opendir($DIR, $dirname)) { + $self->{logger}->writeLogError("config - cannot opendir '$dirname' error: $!"); + return (); } + + while (readdir($DIR)) { + if (-f "$dirname/$_" && eval "/^$match_files\$/") { + push @sorted_files, "$dirname/$_"; + } + } + closedir($DIR); + @sorted_files = sort { $a cmp $b } @sorted_files; + push @all_files, @sorted_files; } - closedir($DIR); - @sorted_files = sort { $a cmp $b } @sorted_files; - return @sorted_files; + + return @all_files; } sub yaml_parse_config { From c8288d0e9297db6dc721a9b432cfa2d173ca7cbf Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Thu, 31 Mar 2022 14:33:42 +0200 Subject: [PATCH 646/948] enh(core): add websocket communication (#214) MON-12844 : remove mojo dependencies --- .../modules/core/httpserverng/class.pm | 8 +- gorgone/gorgone/modules/core/proxy/hooks.pm | 92 ++++- .../gorgone/modules/core/proxy/httpserver.pm | 378 ++++++++++++++++++ gorgone/gorgone/modules/core/pull/class.pm | 2 +- gorgone/gorgone/modules/core/pullwss/class.pm | 249 ++++++++++++ gorgone/gorgone/modules/core/pullwss/hooks.pm | 165 ++++++++ .../packaging/centreon-gorgone.spectemplate | 3 - 7 files changed, 873 insertions(+), 24 deletions(-) create mode 100644 gorgone/gorgone/modules/core/proxy/httpserver.pm create mode 100644 gorgone/gorgone/modules/core/pullwss/class.pm create mode 100644 gorgone/gorgone/modules/core/pullwss/hooks.pm diff --git a/gorgone/gorgone/modules/core/httpserverng/class.pm b/gorgone/gorgone/modules/core/httpserverng/class.pm index 0352e952f4b..7b17745b85a 100644 --- a/gorgone/gorgone/modules/core/httpserverng/class.pm +++ b/gorgone/gorgone/modules/core/httpserverng/class.pm @@ -161,7 +161,7 @@ sub handle_HUP { sub handle_TERM { my $self = shift; - $self->{logger}->writeLogDebug("[httpserver] $$ Receiving order to stop..."); + $self->{logger}->writeLogDebug("[httpserverng] $$ Receiving order to stop..."); $self->{stop} = 1; } @@ -215,12 +215,12 @@ sub run { my $listen = 'reuse=1'; if ($self->{config}->{ssl} eq 'true') { if (!defined($self->{config}->{ssl_cert_file}) || $self->{config}->{ssl_cert_file} eq '' || - -r "$self->{config}->{ssl_cert_file}") { + ! -r "$self->{config}->{ssl_cert_file}") { $connector->{logger}->writeLogError("[httpserverng] cannot read/find ssl-cert-file"); exit(1); } if (!defined($self->{config}->{ssl_key_file}) || $self->{config}->{ssl_key_file} eq '' || - -r "$self->{config}->{ssl_key_file}") { + ! -r "$self->{config}->{ssl_key_file}") { $connector->{logger}->writeLogError("[httpserverng] cannot read/find ssl-key-file"); exit(1); } @@ -585,8 +585,6 @@ sub close_websocket { sub api_root_ws { my ($self, %options) = @_; - use Data::Dumper; print Data::Dumper::Dumper($options{content}); - if (!defined($options{content}->{method})) { $self->{ws_clients}->{ $options{ws_id} }->{tx}->send({json => { code => 500, diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index ddc1464585d..524b408eb6e 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -28,6 +28,7 @@ use gorgone::class::core; use gorgone::standard::library; use gorgone::standard::constants qw(:all); use gorgone::modules::core::proxy::class; +use gorgone::modules::core::proxy::httpserver; use File::Basename; use MIME::Base64; use Digest::MD5::File qw(file_md5_hex); @@ -95,11 +96,15 @@ my $prevails = {}; my $prevails_subnodes = {}; my $rr_current = 0; my $stop = 0; + +# httpserver is only for pull wss client +my $httpserver = {}; + my ($external_socket, $core_id); sub register { my (%options) = @_; - + $config = $options{config}; $config_core = $options{config_core}; @@ -121,6 +126,9 @@ sub init { for my $pool_id (1..$config->{pool}) { create_child(dbh => $options{dbh}, pool_id => $pool_id, logger => $options{logger}); } + if (defined($config->{httpserver}->{enable}) && $config->{httpserver}->{enable} eq 'true') { + create_httpserver_child(dbh => $options{dbh}, logger => $options{logger}); + } } sub routing { @@ -191,6 +199,8 @@ sub routing { logger => $options{logger} ); } + } elsif (defined($data->{httpserver})) { + $httpserver->{ready} = 1; } elsif (defined($data->{node_id}) && defined($synctime_nodes->{ $data->{node_id} })) { $synctime_nodes->{ $data->{node_id} }->{channel_ready} = 1; } @@ -293,6 +303,9 @@ sub routing { if ($is_ctrl_channel == 0 && $synctime_nodes->{$target_parent}->{channel_ready} == 1) { $identity = 'gorgone-proxy-channel-' . $target_parent; } + if ($register_nodes->{$target_parent}->{type} eq 'wss') { + $identity = 'gorgone-proxy-httpserver'; + } foreach my $data (@{$bulk_actions}) { # Mode zmq pull @@ -331,6 +344,11 @@ sub gently { CORE::kill('TERM', $pools->{$pool_id}->{pid}); } } + + if (defined($httpserver->{running}) && $httpserver->{running} == 1) { + $options{logger}->writeLogDebug("[action] Send TERM signal for httpserver"); + CORE::kill('TERM', $httpserver->{pid}); + } } sub kill { @@ -342,6 +360,11 @@ sub kill { CORE::kill('KILL', $pools->{$_}->{pid}); } } + + if (defined($httpserver->{running}) && $httpserver->{running} == 1) { + $options{logger}->writeLogDebug("[action] Send KILL signal for httpserver"); + CORE::kill('KILL', $httpserver->{pid}); + } } sub kill_internal { @@ -367,6 +390,15 @@ sub check { my $count = 0; foreach my $pid (keys %{$options{dead_childs}}) { + if (defined($httpserver->{pid}) && $httpserver->{pid} == $pid) { + $httpserver = {}; + delete $options{dead_childs}->{$pid}; + if ($stop == 0) { + create_httpserver_child(logger => $options{logger}); + } + next; + } + # Not me next if (!defined($pools_pid->{$pid})); @@ -382,20 +414,21 @@ sub check { check_create_child(dbh => $options{dbh}, logger => $options{logger}); + $count++ if (defined($httpserver->{running}) && $httpserver->{running} == 1); foreach (keys %$pools) { $count++ if ($pools->{$_}->{running} == 1); } # We check synclog/ping/ping request timeout foreach (keys %$synctime_nodes) { - if ($register_nodes->{$_}->{type} eq 'pull' && $constatus_ping->{$_}->{in_progress_ping} == 1) { + if ($register_nodes->{$_}->{type} =~ /^(?:pull|wss)$/ && $constatus_ping->{$_}->{in_progress_ping} == 1) { my $ping_timeout = defined($register_nodes->{$_}->{ping_timeout}) ? $register_nodes->{$_}->{ping_timeout} : 30; if ((time() - $constatus_ping->{$_}->{in_progress_ping_pull}) > $ping_timeout) { $constatus_ping->{$_}->{in_progress_ping} = 0; $options{logger}->writeLogInfo("[proxy] Ping timeout from '" . $_ . "'"); } } - if ($register_nodes->{$_}->{type} ne 'pull' && $constatus_ping->{$_}->{in_progress_ping} == 1) { + if ($register_nodes->{$_}->{type} !~ /^(?:pull|wss)$/ && $constatus_ping->{$_}->{in_progress_ping} == 1) { if (time() - $constatus_ping->{ $_ }->{last_ping_sent} > $config->{pong_discard_timeout}) { $options{logger}->writeLogInfo("[proxy] Ping timeout from '" . $_ . "'"); $constatus_ping->{$_}->{in_progress_ping} = 0; @@ -461,6 +494,15 @@ sub broadcast { token => $options{token} ); } + + if (defined($httpserver->{ready}) && $httpserver->{ready} == 1) { + $options{gorgone}->send_internal_message( + identity => 'gorgone-proxy-httpserver', + action => $options{action}, + data => $options{data}, + token => $options{token} + ); + } } # Specific functions @@ -503,8 +545,8 @@ sub pathway { my $first_target; foreach (@targets) { - if ($register_nodes->{$_}->{type} eq 'pull' && !defined($register_nodes->{$_}->{identity})) { - $options{logger}->writeLogDebug("[proxy] skip node pull target '$_' for node '$target' - never connected"); + if ($register_nodes->{$_}->{type} =~ /^(?:pull|wss)$/ && !defined($register_nodes->{$_}->{identity})) { + $options{logger}->writeLogDebug("[proxy] skip node " . $register_nodes->{$_}->{type} . " target '$_' for node '$target' - never connected"); next; } @@ -644,7 +686,7 @@ sub ping_send { if ($register_nodes->{$id}->{type} eq 'push_zmq' || $register_nodes->{$id}->{type} eq 'push_ssh') { $constatus_ping->{$id}->{in_progress_ping} = 1; routing(action => 'PING', target => $id, data => '{}', gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger}); - } elsif ($register_nodes->{$id}->{type} eq 'pull') { + } elsif ($register_nodes->{$id}->{type} =~ /^(?:pull|wss)$/) { $constatus_ping->{$id}->{in_progress_ping} = 1; $constatus_ping->{$id}->{in_progress_ping_pull} = time(); routing(action => 'PING', target => $id, data => '{}', gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger}); @@ -668,7 +710,7 @@ sub full_sync_history { foreach my $id (keys %{$register_nodes}) { if ($register_nodes->{$id}->{type} eq 'push_zmq') { routing(action => 'GETLOG', target => $id, data => '{}', gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger}); - } elsif ($register_nodes->{$id}->{type} eq 'pull') { + } elsif ($register_nodes->{$id}->{type} =~ /^(?:pull|wss)$/) { routing(action => 'GETLOG', target => $id, data => '{}', gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger}); } } @@ -760,6 +802,27 @@ sub create_child { $pools_pid->{$child_pid} = $options{pool_id}; } +sub create_httpserver_child { + my (%options) = @_; + + $options{logger}->writeLogInfo("[proxy] Create module 'proxy' httpserver child process"); + my $child_pid = fork(); + if ($child_pid == 0) { + $0 = 'gorgone-proxy-httpserver'; + my $module = gorgone::modules::core::proxy::httpserver->construct( + logger => $options{logger}, + module_id => NAME, + config_core => $config_core, + config => $config, + container_id => 'httpserver' + ); + $module->run(); + exit(0); + } + $options{logger}->writeLogDebug("[proxy] PID $child_pid (gorgone-proxy-httpserver)"); + $httpserver = { pid => $child_pid, ready => 0, running => 1 }; +} + sub pull_request { my (%options) = @_; @@ -816,7 +879,7 @@ sub unregister_nodes { return if (!defined($options{data}->{nodes})); foreach my $node (@{$options{data}->{nodes}}) { - if (defined($register_nodes->{ $node->{id} }) && $register_nodes->{ $node->{id} }->{type} ne 'pull') { + if (defined($register_nodes->{ $node->{id} }) && $register_nodes->{ $node->{id} }->{type} !~ /^(?:pull|wss)$/) { routing( action => 'PROXYDELNODE', target => $node->{id}, @@ -830,7 +893,7 @@ sub unregister_nodes { my $prevail = 0; $prevail = 1 if (defined($prevails->{ $node->{id} })); - if (defined($register_nodes->{ $node->{id} }) && $register_nodes->{ $node->{id} }->{type} eq 'pull' && $prevail == 1) { + if (defined($register_nodes->{ $node->{id} }) && $register_nodes->{ $node->{id} }->{type} =~ /^(?:pull|wss)$/ && $prevail == 1) { $register_nodes->{ $node->{id} }->{identity} = undef; } @@ -877,7 +940,6 @@ sub register_subnodes { } } - # 'pull' type: # - it does a REGISTERNODES without subnodes (if it already exist, no new entry created, otherwise create an entry). We save the uniq identity # - PING done by proxy and with PONG we get subnodes @@ -919,7 +981,7 @@ sub register_nodes { gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger} - ) if ($register_nodes->{ $node->{id} }->{type} ne 'pull' && $node->{type} eq 'pull'); + ) if ($register_nodes->{ $node->{id} }->{type} !~ /^(?:pull|wss)$/ && $node->{type} =~ /^(?:pull|wss)$/); } if ($prevail == 0) { @@ -944,7 +1006,7 @@ sub register_nodes { } # we update identity in all cases (already created or not) - if ($node->{type} eq 'pull' && defined($node->{identity})) { + if ($node->{type} =~ /^(?:pull|wss)$/ && defined($node->{identity})) { $register_nodes->{ $node->{id} }->{identity} = $node->{identity}; $last_pong->{ $node->{id} } = time() if (defined($last_pong->{ $node->{id} })); } @@ -962,7 +1024,7 @@ sub register_nodes { get_sync_time(node_id => $node->{id}, dbh => $options{dbh}); } - if ($register_nodes->{ $node->{id} }->{type} ne 'pull') { + if ($register_nodes->{ $node->{id} }->{type} !~ /^(?:pull|wss)$/) { if ($prevail == 1) { routing( action => 'PROXYADDNODE', @@ -1004,7 +1066,7 @@ sub prepare_remote_copy { my (%options) = @_; my @actions; - + if (!defined($options{data}->{content}->{source}) || $options{data}->{content}->{source} eq '') { $options{logger}->writeLogError('[proxy] Need source for remote copy'); gorgone::standard::library::add_history( @@ -1139,7 +1201,7 @@ sub add_parent_ping { my (%options) = @_; $options{logger}->writeLogDebug("[proxy] Parent ping '" . $options{identity} . "' is registered"); - $parent_ping->{$options{identity}} = { last_time => time(), router_type => $options{router_type} }; + $parent_ping->{ $options{identity} } = { last_time => time(), router_type => $options{router_type} }; } 1; diff --git a/gorgone/gorgone/modules/core/proxy/httpserver.pm b/gorgone/gorgone/modules/core/proxy/httpserver.pm new file mode 100644 index 00000000000..139995f3bfe --- /dev/null +++ b/gorgone/gorgone/modules/core/proxy/httpserver.pm @@ -0,0 +1,378 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::core::proxy::httpserver; + +use base qw(gorgone::class::module); + +use strict; +use warnings; +use gorgone::standard::library; +use gorgone::standard::constants qw(:all); +use gorgone::standard::misc; +use ZMQ::Constants qw(:all); +use ZMQ::LibZMQ4; +use Mojolicious::Lite; +use Mojo::Server::Daemon; +use IO::Socket::SSL; +use IO::Handle; +use JSON::XS; + +my %handlers = (TERM => {}, HUP => {}); +my ($connector); + +websocket '/' => sub { + my $mojo = shift; + + $connector->{logger}->writeLogDebug('[proxy] httpserver websocket client connected: ' . $mojo->tx->connection); + + $connector->{ws_clients}->{ $mojo->tx->connection } = { + tx => $mojo->tx, + logged => 0, + last_update => time(), + authentication => $mojo->tx->req->headers->header('authentication') + }; + + $mojo->on(message => sub { + my ($mojo, $msg) = @_; + + $connector->{ws_clients}->{ $mojo->tx->connection }->{last_update} = time(); + + $connector->{logger}->writeLogDebug("[proxy] httpserver receiving message: " . $msg); + + my $rv = $connector->is_logged_websocket(ws_id => $mojo->tx->connection, data => $msg); + return if ($rv == 0); + + read_message_client(data => $msg); + }); + + $mojo->on(finish => sub { + my ($mojo, $code, $reason) = @_; + + $connector->{logger}->writeLogDebug('[proxy] httpserver websocket client disconnected: ' . $mojo->tx->connection); + $connector->clean_websocket(ws_id => $mojo->tx->connection, finish => 1); + }); +}; + +sub construct { + my ($class, %options) = @_; + $connector = $class->SUPER::new(%options); + bless $connector, $class; + + $connector->{ws_clients} = {}; + $connector->{identities} = {}; + + $connector->set_signal_handlers(); + return $connector; +} + +sub set_signal_handlers { + my $self = shift; + + $SIG{TERM} = \&class_handle_TERM; + $handlers{TERM}->{$self} = sub { $self->handle_TERM() }; + $SIG{HUP} = \&class_handle_HUP; + $handlers{HUP}->{$self} = sub { $self->handle_HUP() }; +} + +sub handle_HUP { + my $self = shift; + $self->{reload} = 0; +} + +sub handle_TERM { + my $self = shift; + $self->{logger}->writeLogDebug("[proxy] $$ Receiving order to stop..."); + $self->{stop} = 1; +} + +sub class_handle_TERM { + foreach (keys %{$handlers{TERM}}) { + &{$handlers{TERM}->{$_}}(); + } +} + +sub class_handle_HUP { + foreach (keys %{$handlers{HUP}}) { + &{$handlers{HUP}->{$_}}(); + } +} + +sub run { + my ($self, %options) = @_; + + my $listen = 'reuse=1'; + if ($self->{config}->{httpserver}->{ssl} eq 'true') { + if (!defined($self->{config}->{httpserver}->{ssl_cert_file}) || $self->{config}->{httpserver}->{ssl_cert_file} eq '' || + ! -r "$self->{config}->{httpserver}->{ssl_cert_file}") { + $connector->{logger}->writeLogError("[proxy] httpserver cannot read/find ssl-cert-file"); + exit(1); + } + if (!defined($self->{config}->{httpserver}->{ssl_key_file}) || $self->{config}->{httpserver}->{ssl_key_file} eq '' || + ! -r "$self->{config}->{httpserver}->{ssl_key_file}") { + $connector->{logger}->writeLogError("[proxy] httpserver cannot read/find ssl-key-file"); + exit(1); + } + $listen .= '&cert=' . $self->{config}->{httpserver}->{ssl_cert_file} . '&key=' . $self->{config}->{httpserver}->{ssl_key_file}; + } + my $proto = 'http'; + if ($self->{config}->{httpserver}->{ssl} eq 'true') { + $proto = 'https'; + if (defined($self->{config}->{httpserver}->{passphrase}) && $self->{config}->{httpserver}->{passphrase} ne '') { + IO::Socket::SSL::set_defaults(SSL_passwd_cb => sub { return $connector->{config}->{httpserver}->{passphrase} } ); + } + } + + # Connect internal + $self->{internal_socket} = gorgone::standard::library::connect_com( + zmq_type => 'ZMQ_DEALER', + name => 'gorgone-proxy-httpserver', + logger => $self->{logger}, + type => $self->get_core_config(name => 'internal_com_type'), + path => $self->get_core_config(name => 'internal_com_path') + ); + $self->send_internal_action( + action => 'PROXYREADY', + data => { + httpserver => 1 + } + ); + $self->read_zmq_events(); + + my $socket_fd = gorgone::standard::library::zmq_getfd(socket => $self->{internal_socket}); + my $socket = IO::Handle->new_from_fd($socket_fd, 'r'); + Mojo::IOLoop->singleton->reactor->io($socket => sub { + $connector->read_zmq_events(); + }); + Mojo::IOLoop->singleton->reactor->watch($socket, 1, 0); + + Mojo::IOLoop->singleton->recurring(60 => sub { + $connector->{logger}->writeLogDebug('[proxy] httpserver recurring timeout loop'); + my $ctime = time(); + foreach my $ws_id (keys %{$connector->{ws_clients}}) { + if (($ctime - $connector->{ws_clients}->{$ws_id}->{last_update}) > 300) { + $connector->{logger}->writeLogDebug('[proxy] httpserver websocket client timeout reached: ' . $ws_id); + $connector->close_websocket( + code => 500, + message => 'timeout reached', + ws_id => $ws_id + ); + } + } + }); + + app->mode('production'); + my $daemon = Mojo::Server::Daemon->new( + app => app, + listen => [$proto . '://' . $self->{config}->{httpserver}->{address} . ':' . $self->{config}->{httpserver}->{port} . '?' . $listen] + ); + $daemon->inactivity_timeout(180); + + $daemon->run(); + + zmq_close($self->{internal_socket}); + exit(0); +} + +sub read_message_client { + my (%options) = @_; + + if ($options{data} =~ /^\[PONG\]/) { + return undef if ($options{data} !~ /^\[(.+?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)/m); + + my ($action, $token) = ($1, $2); + my ($rv, $data) = $connector->json_decode(argument => $3); + return undef if ($rv == 1); + + $connector->send_internal_action( + action => 'PONG', + data => $data, + token => $token, + target => '' + ); + $connector->read_zmq_events(); + } elsif ($options{data} =~ /^\[(?:REGISTERNODES|UNREGISTERNODES|SYNCLOGS)\]/) { + return undef if ($options{data} !~ /^\[(.+?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)/ms); + + my ($action, $token, $data) = ($1, $2, $3); + + $connector->send_internal_action( + action => $action, + data => $data, + data_noencode => 1, + token => $token, + target => '' + ); + $connector->read_zmq_events(); + } elsif ($options{data} =~ /^\[ACK\]\s+\[(.*?)\]\s+(.*)/ms) { + my ($rv, $data) = $connector->json_decode(argument => $2); + return undef if ($rv == 1); + + if (defined($data->{data}->{action}) && $data->{data}->{action} eq 'getlog') { + $connector->send_internal_action( + action => 'SETLOGS', + data => $data, + token => $1, + target => '' + ); + $connector->read_zmq_events(); + } + } +} + +sub proxy { + my (%options) = @_; + + return undef if ($options{message} !~ /^\[(.+?)\]\s+\[(.*?)\]\s+\[(.*?)\]\s+(.*)$/m); + + my ($action, $token, $target_complete, $data) = ($1, $2, $3, $4); + $connector->{logger}->writeLogDebug( + "[proxy] httpserver send message: [action = $action] [token = $token] [target = $target_complete] [data = $data]" + ); + + if ($action eq 'BCASTLOGGER' && $target_complete eq '') { + (undef, $data) = $connector->json_decode(argument => $data); + $connector->action_bcastlogger(data => $data); + return ; + } elsif ($action eq 'BCASTCOREKEY' && $target_complete eq '') { + (undef, $data) = $connector->json_decode(argument => $data); + $connector->action_bcastcorekey(data => $data); + return ; + } + + if ($target_complete !~ /^(.+)~~(.+)$/) { + $connector->send_log( + code => GORGONE_ACTION_FINISH_KO, + token => $token, + data => { + message => "unknown target format '$target_complete'" + } + ); + $connector->read_zmq_events(); + return ; + } + + my ($target_client, $target, $target_direct) = ($1, $2, 1); + if ($target_client ne $target) { + $target_direct = 0; + } + + if (!defined($connector->{identities}->{$target_client})) { + $connector->send_log( + code => GORGONE_ACTION_FINISH_KO, + token => $token, + data => { + message => "cannot get connection from target node '$target_client'" + } + ); + $connector->read_zmq_events(); + return ; + } + + my $message = gorgone::standard::library::build_protocol( + action => $action, + token => $token, + target => $target_direct == 0 ? $target : undef, + data => $data + ); + + $connector->{ws_clients}->{ $connector->{identities}->{$target_client} }->{tx}->send({text => $message}); +} + +sub read_zmq_events { + my ($self, %options) = @_; + + while (my $events = gorgone::standard::library::zmq_events(socket => $self->{internal_socket})) { + if ($events & ZMQ_POLLIN) { + my $message = $connector->read_message(); + proxy(message => $message); + } else { + last; + } + } +} + +sub is_logged_websocket { + my ($self, %options) = @_; + + return 1 if ($self->{ws_clients}->{ $options{ws_id} }->{logged} == 1); + + if (!defined($self->{ws_clients}->{ $options{ws_id} }->{authentication}) || + $self->{ws_clients}->{ $options{ws_id} }->{authentication} !~ /^\s*Bearer\s+$self->{config}->{httpserver}->{token}/) { + $self->close_websocket( + code => 500, + message => 'token authentication unallowed', + ws_id => $options{ws_id} + ); + return 0; + } + + if ($options{data} !~ /^\[REGISTERNODES\]\s+\[(?:.*?)\]\s+\[.*?\]\s+(.*)/ms) { + $self->close_websocket( + code => 500, + message => 'please registernodes', + ws_id => $options{ws_id} + ); + return 0; + } + + my $content; + eval { + $content = JSON::XS->new->utf8->decode($1); + }; + if ($@) { + $self->close_websocket( + code => 500, + message => 'decode error: unsupported format', + ws_id => $options{ws_id} + ); + return 0; + } + + $self->{logger}->writeLogDebug("[proxy] httpserver client " . $content->{nodes}->[0]->{id} . " is logged"); + + $self->{ws_clients}->{ $options{ws_id} }->{identity} = $content->{nodes}->[0]->{id}; + $self->{identities}->{ $content->{nodes}->[0]->{id} } = $options{ws_id}; + $self->{ws_clients}->{ $options{ws_id} }->{logged} = 1; + return 2; +} + +sub clean_websocket { + my ($self, %options) = @_; + + return if (!defined($self->{ws_clients}->{ $options{ws_id} })); + + $self->{ws_clients}->{ $options{ws_id} }->{tx}->finish() if (!defined($options{finish})); + delete $self->{identities}->{ $self->{ws_clients}->{ $options{ws_id} }->{identity} } + if (defined($self->{ws_clients}->{ $options{ws_id} }->{identity})); + delete $self->{ws_clients}->{ $options{ws_id} }; +} + +sub close_websocket { + my ($self, %options) = @_; + + $self->{ws_clients}->{ $options{ws_id} }->{tx}->send({json => { + code => $options{code}, + message => $options{message} + }}); + $self->clean_websocket(ws_id => $options{ws_id}); +} + +1; diff --git a/gorgone/gorgone/modules/core/pull/class.pm b/gorgone/gorgone/modules/core/pull/class.pm index ac481a5d234..bf02a130a5d 100644 --- a/gorgone/gorgone/modules/core/pull/class.pm +++ b/gorgone/gorgone/modules/core/pull/class.pm @@ -129,7 +129,7 @@ sub transmit_back { return undef; } elsif ($options{message} =~ /^\[(PONG|SYNCLOGS)\]/) { return $options{message}; - } + } return undef; } diff --git a/gorgone/gorgone/modules/core/pullwss/class.pm b/gorgone/gorgone/modules/core/pullwss/class.pm new file mode 100644 index 00000000000..df00be7195c --- /dev/null +++ b/gorgone/gorgone/modules/core/pullwss/class.pm @@ -0,0 +1,249 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::core::pullwss::class; + +use base qw(gorgone::class::module); + +use strict; +use warnings; +use gorgone::standard::library; +use gorgone::standard::constants qw(:all); +use gorgone::standard::misc; +use ZMQ::Constants qw(:all); +use ZMQ::LibZMQ4; +use Mojo::UserAgent; +use IO::Socket::SSL; +use IO::Handle; +use JSON::XS; + +my %handlers = (TERM => {}, HUP => {}); +my ($connector); + +sub new { + my ($class, %options) = @_; + $connector = $class->SUPER::new(%options); + bless $connector, $class; + + $connector->{ping_timer} = -1; + $connector->{connected} = 0; + + $connector->set_signal_handlers(); + return $connector; +} + +sub set_signal_handlers { + my $self = shift; + + $SIG{TERM} = \&class_handle_TERM; + $handlers{TERM}->{$self} = sub { $self->handle_TERM() }; + $SIG{HUP} = \&class_handle_HUP; + $handlers{HUP}->{$self} = sub { $self->handle_HUP() }; +} + +sub handle_HUP { + my $self = shift; + $self->{reload} = 0; +} + +sub handle_TERM { + my $self = shift; + $self->{logger}->writeLogDebug("[pullwss] $$ Receiving order to stop..."); + $self->{stop} = 1; +} + +sub class_handle_TERM { + foreach (keys %{$handlers{TERM}}) { + &{$handlers{TERM}->{$_}}(); + } +} + +sub class_handle_HUP { + foreach (keys %{$handlers{HUP}}) { + &{$handlers{HUP}->{$_}}(); + } +} + +sub send_message { + my ($self, %options) = @_; + + $self->{tx}->send({text => $options{message} }); +} + +sub ping { + my ($self, %options) = @_; + + return if ($self->{ping_timer} != -1 && (time() - $self->{ping_timer}) < 30); + + $self->{ping_timer} = time(); + + my $message = gorgone::standard::library::build_protocol( + action => 'REGISTERNODES', + data => { + nodes => [ + { + id => $self->get_core_config(name => 'id'), + type => 'wss', + identity => $self->get_core_config(name => 'id') + } + ] + }, + json_encode => 1 + ); + + $self->{tx}->send({text => $message }) if ($self->{connected} == 1); +} + +sub wss_connect { + my ($self, %options) = @_; + + return if ($connector->{connected} == 1); + + $self->{ua} = Mojo::UserAgent->new(); + $self->{ua}->transactor->name('gorgone mojo'); + + my $proto = 'ws'; + if (defined($self->{config}->{ssl}) && $self->{config}->{ssl} eq 'true') { + $proto = 'wss'; + $self->{ua}->insecure(1); + } + + $self->{ua}->websocket( + $proto . '://' . $self->{config}->{address} . ':' . $self->{config}->{port} . '/' => { Authentication => 'Bearer ' . $self->{config}->{token} } => sub { + my ($ua, $tx) = @_; + + $connector->{tx} = $tx; + $connector->{logger}->writeLogError('[pullwss] ' . $tx->res->error->{message}) if $tx->res->error; + $connector->{logger}->writeLogError('[pullwss] webSocket handshake failed') and return unless $tx->is_websocket; + + $connector->{tx}->on( + finish => sub { + my ($tx, $code, $reason) = @_; + + $connector->{connected} = 0; + $connector->{logger}->writeLogError('[pullwss] websocket closed with status ' . $code); + } + ); + $connector->{tx}->on( + message => sub { + my ($tx, $msg) = @_; + + # We skip. Dont need to send it in gorgone-core + return undef if ($msg =~ /^\[ACK\]/); + + if ($msg =~ /^\[.*\]/) { + $connector->{logger}->writeLogDebug('[pullwss] websocket message: ' . $msg); + $connector->send_internal_action(message => $msg); + $self->read_zmq_events(); + } else { + $connector->{logger}->writeLogInfo('[pullwss] websocket message: ' . $msg); + } + } + ); + + $connector->{logger}->writeLogInfo('[pullwss] websocket connected'); + $connector->{connected} = 1; + $connector->{ping_timer} = -1; + $connector->ping(); + } + ); + $self->{ua}->inactivity_timeout(120); +} + +sub run { + my ($self, %options) = @_; + + # Connect internal + $self->{internal_socket} = gorgone::standard::library::connect_com( + zmq_type => 'ZMQ_DEALER', + name => 'gorgone-pullwss', + logger => $self->{logger}, + type => $self->get_core_config(name => 'internal_com_type'), + path => $self->get_core_config(name => 'internal_com_path') + ); + $self->send_internal_action( + action => 'PULLWSSREADY', + data => {} + ); + $self->read_zmq_events(); + + $self->wss_connect(); + + my $socket_fd = gorgone::standard::library::zmq_getfd(socket => $self->{internal_socket}); + my $socket = IO::Handle->new_from_fd($socket_fd, 'r'); + Mojo::IOLoop->singleton->reactor->io($socket => sub { + $connector->read_zmq_events(); + }); + Mojo::IOLoop->singleton->reactor->watch($socket, 1, 0); + + Mojo::IOLoop->singleton->recurring(60 => sub { + $connector->{logger}->writeLogDebug('[pullwss] recurring timeout loop'); + $connector->wss_connect(); + $connector->ping(); + }); + + Mojo::IOLoop->start() unless (Mojo::IOLoop->is_running); + + zmq_close($self->{internal_socket}); + exit(0); +} + +sub transmit_back { + my (%options) = @_; + + return undef if (!defined($options{message})); + + if ($options{message} =~ /^\[ACK\]\s+\[(.*?)\]\s+(.*)/m) { + my $data; + eval { + $data = JSON::XS->new->utf8->decode($2); + }; + if ($@) { + return $options{message}; + } + + if (defined($data->{data}->{action}) && $data->{data}->{action} eq 'getlog') { + return '[SETLOGS] [' . $1 . '] [] ' . $2; + } + return undef; + } elsif ($options{message} =~ /^\[(PONG|SYNCLOGS)\]/) { + return $options{message}; + } + return undef; +} + +sub read_zmq_events { + my ($self, %options) = @_; + + while (my $events = gorgone::standard::library::zmq_events(socket => $self->{internal_socket})) { + if ($events & ZMQ_POLLIN) { + my $message = transmit_back(message => $connector->read_message()); + next if (!defined($message)); + + # Only send back SETLOGS and PONG + $connector->{logger}->writeLogDebug("[pullwss] read message from internal: $message"); + $connector->send_message(message => $message); + } else { + last; + } + } +} + +1; diff --git a/gorgone/gorgone/modules/core/pullwss/hooks.pm b/gorgone/gorgone/modules/core/pullwss/hooks.pm new file mode 100644 index 00000000000..07770466883 --- /dev/null +++ b/gorgone/gorgone/modules/core/pullwss/hooks.pm @@ -0,0 +1,165 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::core::pullwss::hooks; + +use warnings; +use strict; +use gorgone::class::core; +use gorgone::modules::core::pullwss::class; +use gorgone::standard::constants qw(:all); + +use constant NAMESPACE => 'core'; +use constant NAME => 'pullwss'; +use constant EVENTS => [ + { event => 'PULLWSSREADY' } +]; + +my $config_core; +my $config; +my $pullwss = {}; +my $stop = 0; + +sub register { + my (%options) = @_; + + my $loaded = 1; + $config = $options{config}; + $config_core = $options{config_core}; + + if (!defined($config->{address}) || $config->{address} =~ /^\s*$/) { + $options{logger}->writeLogError('[pullwss] address option mandatory'); + $loaded = 0; + } + if (!defined($config->{port}) || $config->{port} !~ /^\d+$/) { + $options{logger}->writeLogError('[pullwss] port option mandatory'); + $loaded = 0; + } + if (!defined($config->{token}) || $config->{token} =~ /^\s*$/) { + $options{logger}->writeLogError('[pullwss] token option mandatory'); + $loaded = 0; + } + + return ($loaded, NAMESPACE, NAME, EVENTS); +} + +sub init { + my (%options) = @_; + + create_child(logger => $options{logger}); +} + +sub routing { + my (%options) = @_; + + if ($options{action} eq 'PULLWSSREADY') { + $pullwss->{ready} = 1; + return undef; + } + + if (gorgone::class::core::waiting_ready(ready => \$pullwss->{ready}) == 0) { + gorgone::standard::library::add_history( + dbh => $options{dbh}, + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'gorgone-pullwss: still no ready' }, + json_encode => 1 + ); + return undef; + } + + $options{gorgone}->send_internal_message( + identity => 'gorgone-pullwss', + action => $options{action}, + data => $options{data}, + token => $options{token} + ); +} + +sub gently { + my (%options) = @_; + + $stop = 1; + if (defined($pullwss->{running}) && $pullwss->{running} == 1) { + $options{logger}->writeLogDebug("[pullwss] Send TERM signal $pullwss->{pid}"); + CORE::kill('TERM', $pullwss->{pid}); + } +} + +sub kill { + my (%options) = @_; + + if ($pullwss->{running} == 1) { + $options{logger}->writeLogDebug("[pullwss] Send KILL signal for $pullwss->{pid}"); + CORE::kill('KILL', $pullwss->{pid}); + } +} + +sub kill_internal { + my (%options) = @_; + +} + +sub check { + my (%options) = @_; + + my $count = 0; + foreach my $pid (keys %{$options{dead_childs}}) { + # Not me + next if (!defined($pullwss->{pid}) || $pullwss->{pid} != $pid); + + $pullwss = {}; + delete $options{dead_childs}->{$pid}; + if ($stop == 0) { + create_child(logger => $options{logger}); + } + + last; + } + + $count++ if (defined($pullwss->{running}) && $pullwss->{running} == 1); + + return $count; +} + +sub broadcast {} + +# Specific functions +sub create_child { + my (%options) = @_; + + $options{logger}->writeLogInfo("[pullwss] Create module 'pullwss' process"); + my $child_pid = fork(); + if ($child_pid == 0) { + $0 = 'gorgone-pullwss'; + my $module = gorgone::modules::core::pullwss::class->new( + logger => $options{logger}, + module_id => NAME, + config_core => $config_core, + config => $config + ); + $module->run(); + exit(0); + } + $options{logger}->writeLogDebug("[pullwss] PID $child_pid (gorgone-pullwss)"); + $pullwss = { pid => $child_pid, ready => 0, running => 1 }; +} + +1; diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index a3171a62ca6..8df6690f3de 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -35,9 +35,6 @@ Requires: perl(NetAddr::IP) Requires: perl(Hash::Merge) Requires: perl(Clone) Requires: perl(Sys::Syslog) -Requires: perl(Mojolicious::Lite) -Requires: perl(Mojo::Server::Daemon) -Requires: perl(Authen::Simple::Password) AutoReqProv: no %description From 015a532195a78407b4d564dfab1d5fadc3e702a9 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Thu, 31 Mar 2022 17:17:17 +0200 Subject: [PATCH 647/948] enh(core): add websocket communication next --- gorgone/gorgone/modules/core/proxy/httpserver.pm | 15 +-------------- gorgone/gorgone/modules/core/pullwss/class.pm | 4 ++++ 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/gorgone/gorgone/modules/core/proxy/httpserver.pm b/gorgone/gorgone/modules/core/proxy/httpserver.pm index 139995f3bfe..884598e91d3 100644 --- a/gorgone/gorgone/modules/core/proxy/httpserver.pm +++ b/gorgone/gorgone/modules/core/proxy/httpserver.pm @@ -208,7 +208,7 @@ sub read_message_client { target => '' ); $connector->read_zmq_events(); - } elsif ($options{data} =~ /^\[(?:REGISTERNODES|UNREGISTERNODES|SYNCLOGS)\]/) { + } elsif ($options{data} =~ /^\[(?:REGISTERNODES|UNREGISTERNODES|SYNCLOGS|SETLOGS)\]/) { return undef if ($options{data} !~ /^\[(.+?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)/ms); my ($action, $token, $data) = ($1, $2, $3); @@ -221,19 +221,6 @@ sub read_message_client { target => '' ); $connector->read_zmq_events(); - } elsif ($options{data} =~ /^\[ACK\]\s+\[(.*?)\]\s+(.*)/ms) { - my ($rv, $data) = $connector->json_decode(argument => $2); - return undef if ($rv == 1); - - if (defined($data->{data}->{action}) && $data->{data}->{action} eq 'getlog') { - $connector->send_internal_action( - action => 'SETLOGS', - data => $data, - token => $1, - target => '' - ); - $connector->read_zmq_events(); - } } } diff --git a/gorgone/gorgone/modules/core/pullwss/class.pm b/gorgone/gorgone/modules/core/pullwss/class.pm index df00be7195c..c957663e6b8 100644 --- a/gorgone/gorgone/modules/core/pullwss/class.pm +++ b/gorgone/gorgone/modules/core/pullwss/class.pm @@ -119,6 +119,10 @@ sub wss_connect { $self->{ua} = Mojo::UserAgent->new(); $self->{ua}->transactor->name('gorgone mojo'); + if (defined($self->{config}->{proxy}) && $self->{config}->{proxy} ne '') { + $self->{ua}->proxy->http($self->{config}->{proxy})->https($self->{config}->{proxy}); + } + my $proto = 'ws'; if (defined($self->{config}->{ssl}) && $self->{config}->{ssl} eq 'true') { $proto = 'wss'; From 873f48fe96eb37974eb23489fdceea303846332b Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Mon, 4 Apr 2022 11:10:24 +0200 Subject: [PATCH 648/948] enh(core): websocket communication - dont load it by default (#216) --- gorgone/gorgone/modules/core/proxy/hooks.pm | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index 524b408eb6e..7b141e74fab 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -28,7 +28,6 @@ use gorgone::class::core; use gorgone::standard::library; use gorgone::standard::constants qw(:all); use gorgone::modules::core::proxy::class; -use gorgone::modules::core::proxy::httpserver; use File::Basename; use MIME::Base64; use Digest::MD5::File qw(file_md5_hex); @@ -806,6 +805,14 @@ sub create_httpserver_child { my (%options) = @_; $options{logger}->writeLogInfo("[proxy] Create module 'proxy' httpserver child process"); + + my $rv = gorgone::standard::misc::mymodule_load( + logger => $options{logger}, + module => 'gorgone::modules::core::proxy::httpserver', + error_msg => "Cannot load module 'gorgone::modules::core::proxy::httpserver'" + ); + return if ($rv != 0); + my $child_pid = fork(); if ($child_pid == 0) { $0 = 'gorgone-proxy-httpserver'; From 59d7629322b35080105d1e7a4d0ab972f57cab73 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Tue, 12 Apr 2022 09:15:34 +0200 Subject: [PATCH 649/948] enh(packaging): use macros for web user/password (#218) Refs: MON-12842 --- gorgone/packaging/centreon-api.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gorgone/packaging/centreon-api.yaml b/gorgone/packaging/centreon-api.yaml index 39b7eb1ab0f..e0c47e50e1d 100644 --- a/gorgone/packaging/centreon-api.yaml +++ b/gorgone/packaging/centreon-api.yaml @@ -2,8 +2,8 @@ gorgone: tpapi: - name: centreonv2 base_url: "http://127.0.0.1/centreon/api/latest/" - username: admin - password: Centreon!2021 + username: "@GORGONE_USER@" + password: "@GORGONE_PASSWORD@" - name: clapi - username: admin - password: Centreon!2021 + username: "@GORGONE_USER@" + password: "@GORGONE_PASSWORD@" From 184b553282772b3d5e0383e436ca3647a0b21049 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Tue, 12 Apr 2022 16:02:03 +0200 Subject: [PATCH 650/948] fix(packaging): fix rights on configuration files (#219) Refs: MON-12483 --- gorgone/packaging/centreon-gorgone.spectemplate | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 8df6690f3de..009a37ab95f 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -103,11 +103,12 @@ rm -rf %{buildroot} %config(noreplace) %{_sysconfdir}/sysconfig/gorgoned %config(noreplace) %{_sysconfdir}/logrotate.d/gorgoned -%defattr(-, centreon-gorgone, centreon-gorgone, -) +%defattr(0664, centreon-gorgone, centreon-gorgone, 0775) %dir %{_sysconfdir}/centreon-gorgone -%dir %attr(775, centreon-gorgone, centreon-gorgone) %{_sysconfdir}/centreon-gorgone/config.d -%dir %attr(775, centreon-gorgone, centreon-gorgone) %{_sysconfdir}/centreon-gorgone/config.d/cron.d +%dir %{_sysconfdir}/centreon-gorgone/config.d +%dir %{_sysconfdir}/centreon-gorgone/config.d/cron.d %{_sysconfdir}/centreon-gorgone/config.yaml +%defattr(-, centreon-gorgone, centreon-gorgone, -) %{_localstatedir}/lib/centreon-gorgone %{_localstatedir}/log/centreon-gorgone %{_localstatedir}/cache/centreon-gorgone @@ -117,6 +118,7 @@ rm -rf %{buildroot} %{_bindir}/getent passwd centreon-gorgone &>/dev/null || %{_sbindir}/useradd -g centreon-gorgone -m -d %{_localstatedir}/lib/centreon-gorgone -r centreon-gorgone 2> /dev/null %files centreon-config +%defattr(0664, centreon-gorgone, centreon-gorgone, 0775) %config(noreplace) %{_sysconfdir}/centreon-gorgone/config.d/30-centreon.yaml %config(noreplace) %{_sysconfdir}/centreon-gorgone/config.d/31-centreon-api.yaml %config(noreplace) %{_sysconfdir}/centreon-gorgone/config.d/50-centreon-audit.yaml From 59363c618f80df01bd26188b1e8ad5c55fc20c5b Mon Sep 17 00:00:00 2001 From: Zakaria Guennoune <83596451+zguennoune02@users.noreply.github.com> Date: Tue, 19 Apr 2022 11:22:06 +0200 Subject: [PATCH 651/948] [WP] Debian package building with Jenkins (#217) * sync code from local repo and make debian packages with Jenkins * add extra depedencies * Update Jenkinsfile * fix node perl * add permissions * add some missing perl modules * change docker image name * add revision variable * add revision variable * add revision variable * fix paths * fix path * fix path * add -x * add -x * add -x * add -x * add -x * fix * add debug and fix source issues * add a fixed path to make package * fix path * add delivery * add delivery Co-authored-by: Luiz Costa <me@luizgustavo.pro.br> --- gorgone/Jenkinsfile | 24 ++++++- gorgone/ci/Jenkinsfile | 17 +++++ gorgone/ci/debian/centreon-gorgone.dirs | 6 ++ gorgone/ci/debian/centreon-gorgone.install | 8 +++ gorgone/ci/debian/centreon-gorgone.logrotate | 10 +++ gorgone/ci/debian/centreon-gorgone.postinst | 44 +++++++++++++ gorgone/ci/debian/control | 61 ++++++++++++++++++ gorgone/ci/debian/copyright | 29 +++++++++ gorgone/ci/debian/extra/gorgoned | 6 ++ gorgone/ci/debian/extra/gorgoned.service | 33 ++++++++++ gorgone/ci/debian/rules | 6 ++ gorgone/ci/debian/source/format | 1 + .../Dockerfile.gorgone-debian11-dependencies | 28 ++++++++ gorgone/ci/scripts/gorgone-deb-package.sh | 64 +++++++++++++++++++ 14 files changed, 336 insertions(+), 1 deletion(-) create mode 100644 gorgone/ci/Jenkinsfile create mode 100644 gorgone/ci/debian/centreon-gorgone.dirs create mode 100644 gorgone/ci/debian/centreon-gorgone.install create mode 100644 gorgone/ci/debian/centreon-gorgone.logrotate create mode 100755 gorgone/ci/debian/centreon-gorgone.postinst create mode 100644 gorgone/ci/debian/control create mode 100644 gorgone/ci/debian/copyright create mode 100644 gorgone/ci/debian/extra/gorgoned create mode 100644 gorgone/ci/debian/extra/gorgoned.service create mode 100755 gorgone/ci/debian/rules create mode 100644 gorgone/ci/debian/source/format create mode 100644 gorgone/ci/docker/Dockerfile.gorgone-debian11-dependencies create mode 100755 gorgone/ci/scripts/gorgone-deb-package.sh diff --git a/gorgone/Jenkinsfile b/gorgone/Jenkinsfile index 4b37a3f3ccd..4fb83e66e2a 100644 --- a/gorgone/Jenkinsfile +++ b/gorgone/Jenkinsfile @@ -8,10 +8,12 @@ env.REF_BRANCH = 'master' env.PROJECT='centreon-gorgone' if (env.BRANCH_NAME.startsWith('release-')) { env.BUILD = 'RELEASE' + env.REPO = 'testing' } else if ((env.BRANCH_NAME == env.REF_BRANCH) || (env.BRANCH_NAME == maintenanceBranch)) { env.BUILD = 'REFERENCE' } else if ((env.BRANCH_NAME == 'develop') || (env.BRANCH_NAME == qaBranch)) { env.BUILD = 'QA' + env.REPO = 'unstable' } else { env.BUILD = 'CI' } @@ -74,7 +76,7 @@ stage('Deliver sources // Sonar analysis') { } try { - stage('RPM Packaging') { + stage('DEB/RPM Packaging') { parallel 'Packaging centos7': { node { checkoutCentreonBuild(buildBranch) @@ -93,6 +95,17 @@ try { stash name: "rpms-alma8", includes: 'output/noarch/*.rpm' sh 'rm -rf output' } + }, + + 'Debian bullseye packaging and signing': { + node { + dir('centreon-gorgone') { + checkout scm + } + sh 'docker run -i --entrypoint "/src/centreon-gorgone/ci/scripts/gorgone-deb-package.sh" -v "$PWD:/src" -e "DISTRIB=Debian11" -e "VERSION=$VERSION" -e "RELEASE=$RELEASE" registry.centreon.com/centreon-gorgone-debian11-dependencies:22.04' + stash name: 'Debian11', includes: '*.deb' + archiveArtifacts artifacts: "*.deb" + } } if ((currentBuild.result ?: 'SUCCESS') != 'SUCCESS') { @@ -107,6 +120,15 @@ try { unstash 'rpms-alma8' unstash 'rpms-centos7' sh "./centreon-build/jobs/gorgone/${serie}/gorgone-delivery.sh" + withCredentials([usernamePassword(credentialsId: 'nexus-credentials', passwordVariable: 'NEXUS_PASSWORD', usernameVariable: 'NEXUS_USERNAME')]) { + checkout scm + unstash "Debian11" + sh '''for i in $(echo *.deb) + do + curl -u $NEXUS_USERNAME:$NEXUS_PASSWORD -H "Content-Type: multipart/form-data" --data-binary "@./$i" https://apt.centreon.com/repository/22.04-$REPO/ + done + ''' + } } if ((currentBuild.result ?: 'SUCCESS') != 'SUCCESS') { error('Delivery stage failure.'); diff --git a/gorgone/ci/Jenkinsfile b/gorgone/ci/Jenkinsfile new file mode 100644 index 00000000000..9796213fe75 --- /dev/null +++ b/gorgone/ci/Jenkinsfile @@ -0,0 +1,17 @@ +/* +** Pipeline code. +*/ + +stage('Dependencies containers creation') { + parallel 'debian 11 dependencies': { + node { + dir('centreon-gorgone-debian11') { + checkout scm + dir ('ci/docker') { + sh 'docker build --no-cache . -f Dockerfile.gorgone-debian11-dependencies -t registry.centreon.com/centreon-gorgone-debian11-dependencies:22.04' + /*sh 'docker push registry.centreon.com/centreon-gorgone-debian10-dependencies:22.04'*/ + } + } + } + } +} diff --git a/gorgone/ci/debian/centreon-gorgone.dirs b/gorgone/ci/debian/centreon-gorgone.dirs new file mode 100644 index 00000000000..326d08ce2ea --- /dev/null +++ b/gorgone/ci/debian/centreon-gorgone.dirs @@ -0,0 +1,6 @@ +etc/centreon-gorgone/config.d +etc/centreon-gorgone/config.d/cron.d +var/cache/centreon-gorgone +var/cache/centreon-gorgone/autodiscovery +var/lib/centreon-gorgone +var/log/centreon-gorgone diff --git a/gorgone/ci/debian/centreon-gorgone.install b/gorgone/ci/debian/centreon-gorgone.install new file mode 100644 index 00000000000..f42dc635f17 --- /dev/null +++ b/gorgone/ci/debian/centreon-gorgone.install @@ -0,0 +1,8 @@ +gorgoned usr/bin +contrib/* usr/sbin +packaging/config.yaml etc/centreon-gorgone +debian/extra/gorgoned.service lib/systemd/system +debian/extra/gorgoned etc/default +gorgone/class/* usr/share/perl5/gorgone/class +gorgone/modules/* usr/share/perl5/gorgone/modules +gorgone/standard/* usr/share/perl5/gorgone/standard diff --git a/gorgone/ci/debian/centreon-gorgone.logrotate b/gorgone/ci/debian/centreon-gorgone.logrotate new file mode 100644 index 00000000000..e6f56b7475f --- /dev/null +++ b/gorgone/ci/debian/centreon-gorgone.logrotate @@ -0,0 +1,10 @@ +/var/log/centreon-gorgone/gorgoned.log { + copytruncate + weekly + rotate 52 + compress + delaycompress + notifempty + missingok + su root root +} diff --git a/gorgone/ci/debian/centreon-gorgone.postinst b/gorgone/ci/debian/centreon-gorgone.postinst new file mode 100755 index 00000000000..14235214250 --- /dev/null +++ b/gorgone/ci/debian/centreon-gorgone.postinst @@ -0,0 +1,44 @@ +#!/bin/sh + +if [ "$1" = "configure" ] ; then + + if [ ! "$(getent passwd centreon-gorgone)" ]; then + adduser --system --group --home /var/lib/centreon-gorgone --no-create-home centreon-gorgone + fi + + if [ "$(getent passwd centreon)" ]; then + usermod -a -G centreon-gorgone centreon + usermod -a -G centreon centreon-gorgone + fi + + if [ "$(getent passwd centreon-engine)" ]; then + usermod -a -G centreon-gorgone centreon-engine + fi + + if [ "$(getent passwd centreon-broker)" ]; then + usermod -a -G centreon-gorgone centreon-broker + fi + + chown -vR centreon-gorgone:centreon-gorgone \ + /etc/centreon-gorgone \ + /var/cache/centreon-gorgone \ + /var/cache/centreon-gorgone/autodiscovery \ + /var/lib/centreon-gorgone \ + /var/log/centreon-gorgone + chmod -vR g+w \ + /etc/centreon-gorgone \ + /var/cache/centreon-gorgone \ + /var/cache/centreon-gorgone/autodiscovery \ + /var/lib/centreon-gorgone \ + /var/log/centreon-gorgone + + if [ ! -d /var/lib/centreon-gorgone/.ssh -a -d /var/spool/centreon/.ssh ] ; then + /usr/bin/cp -r /var/spool/centreon/.ssh /var/lib/centreon-gorgone/.ssh + /usr/bin/chown -R centreon-gorgone:centreon-gorgone /var/lib/centreon-gorgone/.ssh + /usr/bin/chmod 600 /var/lib/centreon-gorgone/.ssh/id_rsa + fi + + systemctl preset gorgoned.service || : >/dev/null 2>&1 || : + +fi +exit 0 diff --git a/gorgone/ci/debian/control b/gorgone/ci/debian/control new file mode 100644 index 00000000000..217106e3f65 --- /dev/null +++ b/gorgone/ci/debian/control @@ -0,0 +1,61 @@ +Source: centreon-gorgone +Section: net +Priority: optional +Maintainer: Luiz Costa <me@luizgustavo.pro.br> +Build-Depends: + debhelper-compat (=12), + lsb-base, + perl:native, + libnet-ssh2-perl, + libuuid-perl, + libdigest-md5-file-perl, + libjson-pp-perl, + libjson-xs-perl, + libyaml-perl, + libyaml-libyaml-perl, + libdbd-sqlite3-perl, + libdbd-mysql-perl, + libcrypt-cbc-perl, + libhttp-daemon-perl, + libhttp-daemon-ssl-perl, + libmime-base64-urlsafe-perl, + libnetaddr-ip-perl, + libschedule-cron-perl, + libhash-merge-perl, + libcryptx-perl, + libzmq-constants-perl, + zmq-libzmq4-perl +Standards-Version: 4.5.0 +Homepage: https://wwww.centreon.com + +Package: centreon-gorgone +Architecture: any +Depends: + centreon-common, + libnet-ssh2-perl, + libnet-ldap-perl, + libuuid-perl, + libdigest-md5-file-perl, + libjson-pp-perl, + libjson-xs-perl, + libyaml-perl, + libyaml-libyaml-perl, + libdbd-sqlite3-perl, + libdbd-mysql-perl, + libcrypt-cbc-perl, + libhttp-daemon-perl, + libhttp-daemon-ssl-perl, + libmime-base64-urlsafe-perl, + libnetaddr-ip-perl, + libschedule-cron-perl, + libhash-merge-perl, + libcryptx-perl, + libmojolicious-perl, + libauthen-simple-perl, + libauthen-simple-net-perl, + libzmq-constants-perl, + libzmq5, + zmq-libzmq4-perl, + ${shlibs:Depends}, + ${misc:Depends} +Description: Centreon Gorgone. diff --git a/gorgone/ci/debian/copyright b/gorgone/ci/debian/copyright new file mode 100644 index 00000000000..9331ebebd3d --- /dev/null +++ b/gorgone/ci/debian/copyright @@ -0,0 +1,29 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: centreon-gorgone +Upstream-Contact: Luiz Costa <me@luizgustavo.pro.br> +Source: https://www.centreon.com + +Files: * +Copyright: 2022 Centreon +License: Apache-2.0 + +Files: debian/* +Copyright: 2022 Centreon +License: Apache-2.0 + +License: Apache-2.0 + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + . + https://www.apache.org/licenses/LICENSE-2.0 + . + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + . + On Debian systems, the complete text of the Apache version 2.0 license + can be found in "/usr/share/common-licenses/Apache-2.0". + diff --git a/gorgone/ci/debian/extra/gorgoned b/gorgone/ci/debian/extra/gorgoned new file mode 100644 index 00000000000..d6fb7a6ebe2 --- /dev/null +++ b/gorgone/ci/debian/extra/gorgoned @@ -0,0 +1,6 @@ +# Configuration file for Centreon Gorgone. + +# OPTIONS for the daemon launch +CONFIG="/etc/centreon-gorgone/config.yaml" +LOGFILE="/var/log/centreon-gorgone/gorgoned.log" +SEVERITY="info" diff --git a/gorgone/ci/debian/extra/gorgoned.service b/gorgone/ci/debian/extra/gorgoned.service new file mode 100644 index 00000000000..a226b0256c4 --- /dev/null +++ b/gorgone/ci/debian/extra/gorgoned.service @@ -0,0 +1,33 @@ +## +## Copyright 2019-2021 Centreon +## +## Licensed under the Apache License, Version 2.0 (the "License"); +## you may not use this file except in compliance with the License. +## You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. +## +## For more information : contact@centreon.com +## + +[Unit] +Description=Centreon Gorgone +PartOf=centreon.service +After=centreon.service +ReloadPropagatedFrom=centreon.service + +[Service] +EnvironmentFile=/etc/default/gorgoned +ExecStart=/usr/bin/perl /usr/bin/gorgoned --config=${CONFIG} --logfile=${LOGFILE} --severity=${SEVERITY} +Type=simple +User=centreon-gorgone + +[Install] +WantedBy=multi-user.target +WantedBy=centreon.service diff --git a/gorgone/ci/debian/rules b/gorgone/ci/debian/rules new file mode 100755 index 00000000000..d8309f67d01 --- /dev/null +++ b/gorgone/ci/debian/rules @@ -0,0 +1,6 @@ +#!/usr/bin/make -f + +export DEB_BUILD_MAINT_OPTIONS = hardening=+all + +%: + dh $@ diff --git a/gorgone/ci/debian/source/format b/gorgone/ci/debian/source/format new file mode 100644 index 00000000000..163aaf8d82b --- /dev/null +++ b/gorgone/ci/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/gorgone/ci/docker/Dockerfile.gorgone-debian11-dependencies b/gorgone/ci/docker/Dockerfile.gorgone-debian11-dependencies new file mode 100644 index 00000000000..31f4c618878 --- /dev/null +++ b/gorgone/ci/docker/Dockerfile.gorgone-debian11-dependencies @@ -0,0 +1,28 @@ +FROM debian:bullseye + +# fix locale +RUN apt-get update && apt-get install -y locales && rm -rf /var/lib/apt/lists/* \ +&& localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 +ENV LANG en_US.utf8 + +RUN apt-get update && apt-get install -y \ +dh-make \ +dh-make-perl \ +libtest-simple-perl \ +libmodule-install-perl \ +libnet-ldap-perl \ +libauthen-simple-passwd-perl \ +libmojolicious-perl \ +aptitude \ +lintian \ +pbuilder \ +quilt \ +git-buildpackage \ +debmake \ +devscripts \ +fakeroot \ +curl \ +python3 \ +python3-pip \ +&& pip3 install conan \ +&& ln -s /usr/local/bin/conan /usr/bin/conan diff --git a/gorgone/ci/scripts/gorgone-deb-package.sh b/gorgone/ci/scripts/gorgone-deb-package.sh new file mode 100755 index 00000000000..9f629069c26 --- /dev/null +++ b/gorgone/ci/scripts/gorgone-deb-package.sh @@ -0,0 +1,64 @@ +#!/bin/sh +set -ex + +if [ -z "$VERSION" -o -z "$RELEASE" -o -z "$DISTRIB" ] ; then + echo "You need to specify VERSION / RELEASE / DISTRIB variables" + exit 1 +fi + +echo "################################################## PACKAGING COLLECT ##################################################" + +AUTHOR="Luiz Costa" +AUTHOR_EMAIL="me@luizgustavo.pro.br" + +pwd + +if [ -d /build ]; then + rm -rf /build +fi +mkdir -p /build + +mkdir -p /build/tmp +cd /build/tmp +apt-cache dumpavail | dpkg --merge-avail + +yes | dh-make-perl make --build --version "0.11.3-${RELEASE}" --cpan Mojolicious::Plugin::BasicAuthPlus +dpkg -i libmojolicious-plugin-basicauthplus-perl_0.11.3-${RELEASE}_all.deb + +yes | dh-make-perl make --build --revision ${RELEASE} --cpan ZMQ::Constants +dpkg -i libzmq-constants-perl_1.04-${RELEASE}_all.deb + +git clone https://github.com/centreon-lab/zmq-libzmq4-perl.git zmq-libzmq4-perl-0.02 +mkdir zmq-libzmq4-perl +mv -v zmq-libzmq4-perl-0.02 zmq-libzmq4-perl/ +cd zmq-libzmq4-perl/ +tar czpvf zmq-libzmq4-perl-0.02.tar.gz zmq-libzmq4-perl-0.02 +cd zmq-libzmq4-perl-0.02 +rm -rf debian/changelog +debmake -f "${AUTHOR}" -e "${AUTHOR_EMAIL}" -b ":perl" -r $RELEASE -y +debuild-pbuilder -uc -us +cd .. +dpkg -i zmq-libzmq4-perl_0.02-${RELEASE}_all.deb +cd /build + +mkdir -p /build/centreon-gorgone +(cd /src && tar czvpf - centreon-gorgone) | dd of=centreon-gorgone-$VERSION.tar.gz +cp -rv /src/centreon-gorgone /build/ +cp -rv /src/centreon-gorgone/ci/debian /build/centreon-gorgone/ + +pwd +ls -lart +cd centreon-gorgone +debmake -f "${AUTHOR}" -e "${AUTHOR_EMAIL}" -u "$VERSION" -b ":perl" -y -r "$RELEASE" +debuild-pbuilder +cd /build + +if [ -d "$DISTRIB" ] ; then + rm -rf "$DISTRIB" +fi +mkdir $DISTRIB +mv /build/tmp/libmojolicious-plugin-basicauthplus-perl_0.11.3-${RELEASE}_all.deb $DISTRIB/ +mv /build/tmp/zmq-libzmq4-perl/zmq-libzmq4-perl_0.02-${RELEASE}_all.deb $DISTRIB/ +mv /build/tmp/libzmq-constants-perl_1.04-${RELEASE}_all.deb $DISTRIB/ +mv /build/*.deb $DISTRIB/ +mv /build/$DISTRIB/*.deb /src From 76f0cfe53f52e44ccc7437f09a4ef8d1a358771c Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Tue, 19 Apr 2022 15:27:30 +0200 Subject: [PATCH 652/948] MON-12908: add centreon-mbi etl optimized (#220) --- gorgone/contrib/mbi/centreonBIETL | 275 ++++++ gorgone/contrib/mbi/dimensionBuilder.pl | 237 ++++++ gorgone/contrib/mbi/eventStatisticsBuilder.pl | 247 ++++++ gorgone/contrib/mbi/importData.pl | 250 ++++++ .../contrib/mbi/perfdataStatisticsBuilder.pl | 240 ++++++ gorgone/gorgone/class/core.pm | 2 +- gorgone/gorgone/class/db.pm | 49 +- gorgone/gorgone/class/http/backend/curl.pm | 21 +- .../gorgone/modules/centreon/mbi/etl/class.pm | 805 ++++++++++++++++++ .../modules/centreon/mbi/etl/event/main.pm | 279 ++++++ .../gorgone/modules/centreon/mbi/etl/hooks.pm | 154 ++++ .../modules/centreon/mbi/etl/import/main.pm | 412 +++++++++ .../modules/centreon/mbi/etl/perfdata/main.pm | 410 +++++++++ .../modules/centreon/mbi/etlworkers/class.pm | 349 ++++++++ .../mbi/etlworkers/dimensions/main.pm | 263 ++++++ .../centreon/mbi/etlworkers/event/main.pm | 259 ++++++ .../modules/centreon/mbi/etlworkers/hooks.pm | 239 ++++++ .../centreon/mbi/etlworkers/import/main.pm | 86 ++ .../centreon/mbi/etlworkers/perfdata/main.pm | 190 +++++ .../modules/centreon/mbi/libs/Messages.pm | 55 ++ .../modules/centreon/mbi/libs/Utils.pm | 251 ++++++ .../modules/centreon/mbi/libs/bi/BIHost.pm | 233 +++++ .../centreon/mbi/libs/bi/BIHostCategory.pm | 129 +++ .../centreon/mbi/libs/bi/BIHostGroup.pm | 131 +++ .../centreon/mbi/libs/bi/BIHostStateEvents.pm | 240 ++++++ .../modules/centreon/mbi/libs/bi/BIMetric.pm | 199 +++++ .../modules/centreon/mbi/libs/bi/BIService.pm | 221 +++++ .../centreon/mbi/libs/bi/BIServiceCategory.pm | 130 +++ .../mbi/libs/bi/BIServiceStateEvents.pm | 247 ++++++ .../centreon/mbi/libs/bi/DBConfigParser.pm | 85 ++ .../centreon/mbi/libs/bi/DataQuality.pm | 99 +++ .../modules/centreon/mbi/libs/bi/Dumper.pm | 132 +++ .../mbi/libs/bi/HGMonthAvailability.pm | 92 ++ .../mbi/libs/bi/HGServiceMonthAvailability.pm | 93 ++ .../centreon/mbi/libs/bi/HostAvailability.pm | 175 ++++ .../centreon/mbi/libs/bi/LiveService.pm | 223 +++++ .../modules/centreon/mbi/libs/bi/Loader.pm | 121 +++ .../mbi/libs/bi/MetricCentileValue.pm | 182 ++++ .../centreon/mbi/libs/bi/MetricDailyValue.pm | 146 ++++ .../centreon/mbi/libs/bi/MetricHourlyValue.pm | 72 ++ .../mbi/libs/bi/MetricMonthCapacity.pm | 90 ++ .../centreon/mbi/libs/bi/MySQLTables.pm | 309 +++++++ .../mbi/libs/bi/ServiceAvailability.pm | 238 ++++++ .../modules/centreon/mbi/libs/bi/Time.pm | 264 ++++++ .../mbi/libs/centreon/CentileProperties.pm | 60 ++ .../mbi/libs/centreon/ETLProperties.pm | 119 +++ .../centreon/mbi/libs/centreon/Host.pm | 307 +++++++ .../mbi/libs/centreon/HostCategory.pm | 70 ++ .../centreon/mbi/libs/centreon/HostGroup.pm | 136 +++ .../centreon/mbi/libs/centreon/Service.pm | 214 +++++ .../mbi/libs/centreon/ServiceCategory.pm | 96 +++ .../centreon/mbi/libs/centreon/Timeperiod.pm | 247 ++++++ .../mbi/libs/centstorage/HostStateEvents.pm | 184 ++++ .../centreon/mbi/libs/centstorage/Metrics.pm | 232 +++++ .../libs/centstorage/ServiceStateEvents.pm | 179 ++++ gorgone/gorgone/standard/constants.pm | 4 +- .../packaging/centreon-gorgone.spectemplate | 2 + 57 files changed, 10767 insertions(+), 7 deletions(-) create mode 100644 gorgone/contrib/mbi/centreonBIETL create mode 100644 gorgone/contrib/mbi/dimensionBuilder.pl create mode 100644 gorgone/contrib/mbi/eventStatisticsBuilder.pl create mode 100644 gorgone/contrib/mbi/importData.pl create mode 100644 gorgone/contrib/mbi/perfdataStatisticsBuilder.pl create mode 100644 gorgone/gorgone/modules/centreon/mbi/etl/class.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/etl/event/main.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/etl/hooks.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/etl/import/main.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/etl/perfdata/main.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/etlworkers/class.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/etlworkers/dimensions/main.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/etlworkers/event/main.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/etlworkers/hooks.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/etlworkers/import/main.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/etlworkers/perfdata/main.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/Messages.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/Utils.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHost.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHostCategory.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHostGroup.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHostStateEvents.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/bi/BIMetric.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/bi/BIService.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/bi/BIServiceCategory.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/bi/BIServiceStateEvents.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/bi/DBConfigParser.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/bi/DataQuality.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/bi/Dumper.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/bi/HGMonthAvailability.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/bi/HGServiceMonthAvailability.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/bi/HostAvailability.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/bi/LiveService.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/bi/Loader.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricCentileValue.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricDailyValue.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricHourlyValue.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricMonthCapacity.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/bi/MySQLTables.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/bi/ServiceAvailability.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/bi/Time.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/centreon/CentileProperties.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/centreon/ETLProperties.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/centreon/Host.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/centreon/HostCategory.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/centreon/HostGroup.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/centreon/Service.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/centreon/ServiceCategory.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/centreon/Timeperiod.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/centstorage/HostStateEvents.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/centstorage/Metrics.pm create mode 100644 gorgone/gorgone/modules/centreon/mbi/libs/centstorage/ServiceStateEvents.pm diff --git a/gorgone/contrib/mbi/centreonBIETL b/gorgone/contrib/mbi/centreonBIETL new file mode 100644 index 00000000000..3732ea72e92 --- /dev/null +++ b/gorgone/contrib/mbi/centreonBIETL @@ -0,0 +1,275 @@ +#!/usr/bin/perl + +use warnings; +use strict; +use FindBin; +use lib "$FindBin::Bin"; +# to be launched from contrib directory +use lib "$FindBin::Bin/../"; + +gorgone::script::centreonBIETL->new()->run(); + +package gorgone::script::centreonBIETL; + +use strict; +use warnings; +use Data::Dumper; +use gorgone::modules::centreon::mbi::libs::Utils; +use gorgone::standard::misc; +use gorgone::class::http::http; +use JSON::XS; + +use base qw(gorgone::class::script); + +sub new { + my $class = shift; + my $self = $class->SUPER::new( + 'centreonBIETL', + centreon_db_conn => 0, + centstorage_db_conn => 0, + noconfig => 0 + ); + + bless $self, $class; + + $self->{moptions}->{rebuild} = 0; + $self->{moptions}->{daily} = 0; + $self->{moptions}->{import} = 0; + $self->{moptions}->{dimensions} = 0; + $self->{moptions}->{event} = 0; + $self->{moptions}->{perfdata} = 0; + $self->{moptions}->{start} = ''; + $self->{moptions}->{end} = ''; + $self->{moptions}->{create_tables} = 0; + $self->{moptions}->{ignore_databin} = 0; + $self->{moptions}->{centreon_only} = 0; + $self->{moptions}->{nopurge} = 0; + + $self->add_options( + 'url:s' => \$self->{url}, + 'r' => \$self->{moptions}->{rebuild}, + 'd' => \$self->{moptions}->{daily}, + 'I' => \$self->{moptions}->{import}, + 'D' => \$self->{moptions}->{dimensions}, + 'E' => \$self->{moptions}->{event}, + 'P' => \$self->{moptions}->{perfdata}, + 's:s' => \$self->{moptions}->{start}, + 'e:s' => \$self->{moptions}->{end}, + 'c' => \$self->{moptions}->{create_tables}, + 'i' => \$self->{moptions}->{ignore_databin}, + 'C' => \$self->{moptions}->{centreon_only}, + 'p' => \$self->{moptions}->{nopurge} + ); + return $self; +} + +sub init { + my $self = shift; + $self->SUPER::init(); + + $self->{url} = 'http://127.0.0.1:8085' if (!defined($self->{url}) || $self->{url} eq ''); + $self->{http} = gorgone::class::http::http->new(logger => $self->{logger}); + + my $utils = gorgone::modules::centreon::mbi::libs::Utils->new($self->{logger}); + if ($utils->checkBasicOptions($self->{moptions}) == 1) { + exit(1); + } + + if ($self->{moptions}->{create_tables} == 0 && + $self->{moptions}->{import} == 0 && + $self->{moptions}->{dimensions} == 0 && + $self->{moptions}->{event} == 0 && + $self->{moptions}->{perfdata} == 0) { + $self->{moptions}->{import} = 1; + $self->{moptions}->{dimensions} = 1; + $self->{moptions}->{event} = 1; + $self->{moptions}->{perfdata} = 1; + } +} + +sub json_decode { + my ($self, %options) = @_; + + my $decoded; + eval { + $decoded = JSON::XS->new->utf8->decode($options{content}); + }; + if ($@) { + $self->{logger}->writeLogError("cannot decode json response: $@"); + exit(1); + } + + return $decoded; +} + +sub run_etl { + my ($self) = @_; + + my ($code, $content) = $self->{http}->request( + http_backend => 'curl', + method => 'POST', + hostname => '', + full_url => $self->{url} . '/api/centreon/mbietl/run', + query_form_post => JSON::XS->new->utf8->encode($self->{moptions}), + header => [ + 'Accept-Type: application/json; charset=utf-8', + 'Content-Type: application/json; charset=utf-8', + ], + curl_opt => ['CURLOPT_SSL_VERIFYPEER => 0', 'CURLOPT_POSTREDIR => CURL_REDIR_POST_ALL'], + warning_status => '', + unknown_status => '', + critical_status => '' + ); + + if ($self->{http}->get_code() < 200 || $self->{http}->get_code() >= 300) { + $self->{logger}->writeLogError("Login error [code: '" . $self->{http}->get_code() . "'] [message: '" . $self->{http}->get_message() . "']"); + exit(1); + } + + my $decoded = $self->json_decode(content => $content); + if (!defined($decoded->{token})) { + $self->{logger}->writeLogError('cannot get token'); + exit(1); + } + + $self->{token} = $decoded->{token}; +} + +sub display_messages { + my ($self, %options) = @_; + + if (defined($options{data}->{messages})) { + foreach (@{$options{data}->{messages}}) { + if ($_->[0] eq 'D') { + $self->{logger}->writeLogDebug($_->[1]) + } elsif ($_->[0] eq 'I') { + $self->{logger}->writeLogInfo($_->[1]); + } elsif ($_->[0] eq 'E') { + $self->{logger}->writeLogError($_->[1]); + } + } + } +} + +sub get_etl_log { + my ($self) = @_; + + my $log_id; + while (1) { + my $get_param = []; + if (defined($log_id)) { + $get_param = ['id=' . $log_id]; + } + + my ($code, $content) = $self->{http}->request( + http_backend => 'curl', + method => 'GET', + hostname => '', + full_url => $self->{url} . '/api/log/' . $self->{token}, + get_param => $get_param, + header => [ + 'Accept-Type: application/json; charset=utf-8' + ], + curl_opt => ['CURLOPT_SSL_VERIFYPEER => 0', 'CURLOPT_POSTREDIR => CURL_REDIR_POST_ALL'], + warning_status => '', + unknown_status => '', + critical_status => '' + ); + + if ($self->{http}->get_code() < 200 || $self->{http}->get_code() >= 300) { + $self->{logger}->writeLogError("Login error [code: '" . $self->{http}->get_code() . "'] [message: '" . $self->{http}->get_message() . "']"); + exit(1); + } + + my $decoded = $self->json_decode(content => $content); + if (!defined($decoded->{data})) { + $self->{logger}->writeLogError("Cannot get log information"); + exit(1); + } + + my $stop = 0; + foreach (@{$decoded->{data}}) { + my $data = $self->json_decode(content => $_->{data}); + next if (defined($log_id) && $log_id >= $_->{id}); + $log_id = $_->{id}; + + if ($_->{code} == 600) { + $self->display_messages(data => $data); + } elsif ($_->{code} == 1) { + $self->display_messages(data => $data); + $stop = 1; + } elsif ($_->{code} == 2) { + $self->display_messages(data => $data); + $stop = 1; + } + } + + last if ($stop == 1); + sleep(2); + } +} + +sub run { + my $self = shift; + + $self->SUPER::run(); + $self->run_etl(); + $self->get_etl_log(); +} + +__END__ + +=head1 NAME + +centreonBIETL - script to execute mbi etl + +=head1 SYNOPSIS + +centreonBIETL [options] + +=head1 OPTIONS + +=over 8 + +=item B<--url> + +Specify the api url (default: 'http://127.0.0.1:8085'). + +=item B<--severity> + +Set the script log severity (default: 'info'). + +=item B<--help> + +Print a brief help message and exits. + +Execution modes + + -c Create the reporting database model + -d Daily execution to calculate statistics on yesterday + -r Rebuild mode to calculate statitics on a historical period. Can be used with: + Extra arguments for options -d and -r (if none of the following is specified, these one are selected by default: -IDEP): + -I Extract data from the monitoring server + Extra arguments for option -I: + -C Extract only Centreon configuration database only. Works with option -I. + -i Ignore perfdata extraction from monitoring server + -o Extract only perfdata from monitoring server + + -D Calculate dimensions + -E Calculate event and availability statistics + -P Calculate perfdata statistics + Common options for -rIDEP: + -s Start date in format YYYY-MM-DD. + By default, the program uses the data retention period from Centreon BI configuration + -e End date in format YYYY-MM-DD. + By default, the program uses the data retention period from Centreon BI configuration + -p Do not empty statistic tables, delete only entries for the processed period. + Does not work on raw data tables, only on Centreon BI statistics tables. + +=back + +=head1 DESCRIPTION + +B<centreonBIETL> + +=cut diff --git a/gorgone/contrib/mbi/dimensionBuilder.pl b/gorgone/contrib/mbi/dimensionBuilder.pl new file mode 100644 index 00000000000..d92edb8f8b5 --- /dev/null +++ b/gorgone/contrib/mbi/dimensionBuilder.pl @@ -0,0 +1,237 @@ +#!/usr/bin/perl + +use warnings; +use strict; +use FindBin; +use lib "$FindBin::Bin"; +# to be launched from contrib directory +use lib "$FindBin::Bin/../"; + +gorgone::script::dimensionsBuilder->new()->run(); + +package gorgone::script::dimensionsBuilder; + +use strict; +use warnings; +use Data::Dumper; +use gorgone::modules::centreon::mbi::libs::Utils; +use gorgone::standard::misc; +use gorgone::class::http::http; +use JSON::XS; + +use base qw(gorgone::class::script); + +sub new { + my $class = shift; + my $self = $class->SUPER::new( + 'dimensionsBuilder', + centreon_db_conn => 0, + centstorage_db_conn => 0, + noconfig => 0 + ); + + bless $self, $class; + + $self->{moptions}->{rebuild} = 0; + $self->{moptions}->{daily} = 0; + $self->{moptions}->{import} = 0; + $self->{moptions}->{dimensions} = 1; + $self->{moptions}->{event} = 0; + $self->{moptions}->{perfdata} = 0; + $self->{moptions}->{start} = ''; + $self->{moptions}->{end} = ''; + $self->{moptions}->{nopurge} = 0; + $self->{moptions}->{centile} = 0; + + $self->add_options( + 'url:s' => \$self->{url}, + 'r|rebuild' => \$self->{moptions}->{rebuild}, + 'd|daily' => \$self->{moptions}->{daily}, + 'centile' => \$self->{moptions}->{centile}, + 'p|no-purge' => \$self->{moptions}->{nopurge} + ); + return $self; +} + +sub init { + my $self = shift; + $self->SUPER::init(); + + $self->{url} = 'http://127.0.0.1:8085' if (!defined($self->{url}) || $self->{url} eq ''); + $self->{http} = gorgone::class::http::http->new(logger => $self->{logger}); + my $utils = gorgone::modules::centreon::mbi::libs::Utils->new($self->{logger}); + if ($utils->checkBasicOptions($self->{moptions}) == 1) { + exit(1); + } +} + +sub json_decode { + my ($self, %options) = @_; + + my $decoded; + eval { + $decoded = JSON::XS->new->utf8->decode($options{content}); + }; + if ($@) { + $self->{logger}->writeLogError("cannot decode json response: $@"); + exit(1); + } + + return $decoded; +} + +sub run_etl { + my ($self) = @_; + + my ($code, $content) = $self->{http}->request( + http_backend => 'curl', + method => 'POST', + hostname => '', + full_url => $self->{url} . '/api/centreon/mbietl/run', + query_form_post => JSON::XS->new->utf8->encode($self->{moptions}), + header => [ + 'Accept-Type: application/json; charset=utf-8', + 'Content-Type: application/json; charset=utf-8', + ], + curl_opt => ['CURLOPT_SSL_VERIFYPEER => 0', 'CURLOPT_POSTREDIR => CURL_REDIR_POST_ALL'], + warning_status => '', + unknown_status => '', + critical_status => '' + ); + + if ($self->{http}->get_code() < 200 || $self->{http}->get_code() >= 300) { + $self->{logger}->writeLogError("Login error [code: '" . $self->{http}->get_code() . "'] [message: '" . $self->{http}->get_message() . "']"); + exit(1); + } + + my $decoded = $self->json_decode(content => $content); + if (!defined($decoded->{token})) { + $self->{logger}->writeLogError('cannot get token'); + exit(1); + } + + $self->{token} = $decoded->{token}; +} + +sub display_messages { + my ($self, %options) = @_; + + if (defined($options{data}->{messages})) { + foreach (@{$options{data}->{messages}}) { + if ($_->[0] eq 'D') { + $self->{logger}->writeLogDebug($_->[1]) + } elsif ($_->[0] eq 'I') { + $self->{logger}->writeLogInfo($_->[1]); + } elsif ($_->[0] eq 'E') { + $self->{logger}->writeLogError($_->[1]); + } + } + } +} + +sub get_etl_log { + my ($self) = @_; + + my $log_id; + while (1) { + my $get_param = []; + if (defined($log_id)) { + $get_param = ['id=' . $log_id]; + } + + my ($code, $content) = $self->{http}->request( + http_backend => 'curl', + method => 'GET', + hostname => '', + full_url => $self->{url} . '/api/log/' . $self->{token}, + get_param => $get_param, + header => [ + 'Accept-Type: application/json; charset=utf-8' + ], + curl_opt => ['CURLOPT_SSL_VERIFYPEER => 0', 'CURLOPT_POSTREDIR => CURL_REDIR_POST_ALL'], + warning_status => '', + unknown_status => '', + critical_status => '' + ); + + if ($self->{http}->get_code() < 200 || $self->{http}->get_code() >= 300) { + $self->{logger}->writeLogError("Login error [code: '" . $self->{http}->get_code() . "'] [message: '" . $self->{http}->get_message() . "']"); + exit(1); + } + + my $decoded = $self->json_decode(content => $content); + if (!defined($decoded->{data})) { + $self->{logger}->writeLogError("Cannot get log information"); + exit(1); + } + + my $stop = 0; + foreach (@{$decoded->{data}}) { + my $data = $self->json_decode(content => $_->{data}); + next if (defined($log_id) && $log_id >= $_->{id}); + $log_id = $_->{id}; + + if ($_->{code} == 600) { + $self->display_messages(data => $data); + } elsif ($_->{code} == 1) { + $self->display_messages(data => $data); + $stop = 1; + } elsif ($_->{code} == 2) { + $self->display_messages(data => $data); + $stop = 1; + } + } + + last if ($stop == 1); + sleep(2); + } +} + +sub run { + my $self = shift; + + $self->SUPER::run(); + $self->run_etl(); + $self->get_etl_log(); +} + +__END__ + +=head1 NAME + +dimensionsBuilder.pl - script to compute dimensions + +=head1 SYNOPSIS + +dimensionsBuilder.pl [options] + +=head1 OPTIONS + +=over 8 + +=item B<--url> + +Specify the api url (default: 'http://127.0.0.1:8085'). + +=item B<--severity> + +Set the script log severity (default: 'info'). + +=item B<--help> + +Print a brief help message and exits. + +=back + + Rebuild options: + [-r|--rebuild] : Rebuild dimensions + [--no-purge] : Do not delete previous dimensions while rebuilding + [--centile] : import only centile dimensions without deleting other dimensions + Daily run options: + [-d|--daily] + +=head1 DESCRIPTION + +B<importData.pl> + +=cut diff --git a/gorgone/contrib/mbi/eventStatisticsBuilder.pl b/gorgone/contrib/mbi/eventStatisticsBuilder.pl new file mode 100644 index 00000000000..7c56d566146 --- /dev/null +++ b/gorgone/contrib/mbi/eventStatisticsBuilder.pl @@ -0,0 +1,247 @@ +#!/usr/bin/perl + +use warnings; +use strict; +use FindBin; +use lib "$FindBin::Bin"; +# to be launched from contrib directory +use lib "$FindBin::Bin/../"; + +gorgone::script::eventStatisticsBuilder->new()->run(); + +package gorgone::script::eventStatisticsBuilder; + +use strict; +use warnings; +use Data::Dumper; +use gorgone::modules::centreon::mbi::libs::Utils; +use gorgone::standard::misc; +use gorgone::class::http::http; +use JSON::XS; + +use base qw(gorgone::class::script); + +sub new { + my $class = shift; + my $self = $class->SUPER::new( + 'eventStatisticsBuilder', + centreon_db_conn => 0, + centstorage_db_conn => 0, + noconfig => 0 + ); + + bless $self, $class; + + $self->{moptions}->{rebuild} = 0; + $self->{moptions}->{daily} = 0; + $self->{moptions}->{import} = 0; + $self->{moptions}->{dimensions} = 0; + $self->{moptions}->{event} = 1; + $self->{moptions}->{perfdata} = 0; + $self->{moptions}->{start} = ''; + $self->{moptions}->{end} = ''; + $self->{moptions}->{nopurge} = 0; + $self->{moptions}->{host_only} = 0; + $self->{moptions}->{service_only} = 0; + $self->{moptions}->{availability_only} = 0; + $self->{moptions}->{events_only} = 0; + + $self->add_options( + 'url:s' => \$self->{url}, + 'r|rebuild' => \$self->{moptions}->{rebuild}, + 'd|daily' => \$self->{moptions}->{daily}, + 's:s' => \$self->{moptions}->{start}, + 'e:s' => \$self->{moptions}->{end}, + 'host-only' => \$self->{moptions}->{host_only}, + 'service-only' => \$self->{moptions}->{service_only}, + 'availability-only' => \$self->{moptions}->{availability_only}, + 'events-only' => \$self->{moptions}->{events_only} + ); + return $self; +} + +sub init { + my $self = shift; + $self->SUPER::init(); + + $self->{url} = 'http://127.0.0.1:8085' if (!defined($self->{url}) || $self->{url} eq ''); + $self->{http} = gorgone::class::http::http->new(logger => $self->{logger}); + my $utils = gorgone::modules::centreon::mbi::libs::Utils->new($self->{logger}); + if ($utils->checkBasicOptions($self->{moptions}) == 1) { + exit(1); + } +} + +sub json_decode { + my ($self, %options) = @_; + + my $decoded; + eval { + $decoded = JSON::XS->new->utf8->decode($options{content}); + }; + if ($@) { + $self->{logger}->writeLogError("cannot decode json response: $@"); + exit(1); + } + + return $decoded; +} + +sub run_etl { + my ($self) = @_; + + my ($code, $content) = $self->{http}->request( + http_backend => 'curl', + method => 'POST', + hostname => '', + full_url => $self->{url} . '/api/centreon/mbietl/run', + query_form_post => JSON::XS->new->utf8->encode($self->{moptions}), + header => [ + 'Accept-Type: application/json; charset=utf-8', + 'Content-Type: application/json; charset=utf-8', + ], + curl_opt => ['CURLOPT_SSL_VERIFYPEER => 0', 'CURLOPT_POSTREDIR => CURL_REDIR_POST_ALL'], + warning_status => '', + unknown_status => '', + critical_status => '' + ); + + if ($self->{http}->get_code() < 200 || $self->{http}->get_code() >= 300) { + $self->{logger}->writeLogError("Login error [code: '" . $self->{http}->get_code() . "'] [message: '" . $self->{http}->get_message() . "']"); + exit(1); + } + + my $decoded = $self->json_decode(content => $content); + if (!defined($decoded->{token})) { + $self->{logger}->writeLogError('cannot get token'); + exit(1); + } + + $self->{token} = $decoded->{token}; +} + +sub display_messages { + my ($self, %options) = @_; + + if (defined($options{data}->{messages})) { + foreach (@{$options{data}->{messages}}) { + if ($_->[0] eq 'D') { + $self->{logger}->writeLogDebug($_->[1]) + } elsif ($_->[0] eq 'I') { + $self->{logger}->writeLogInfo($_->[1]); + } elsif ($_->[0] eq 'E') { + $self->{logger}->writeLogError($_->[1]); + } + } + } +} + +sub get_etl_log { + my ($self) = @_; + + my $log_id; + while (1) { + my $get_param = []; + if (defined($log_id)) { + $get_param = ['id=' . $log_id]; + } + + my ($code, $content) = $self->{http}->request( + http_backend => 'curl', + method => 'GET', + hostname => '', + full_url => $self->{url} . '/api/log/' . $self->{token}, + get_param => $get_param, + header => [ + 'Accept-Type: application/json; charset=utf-8' + ], + curl_opt => ['CURLOPT_SSL_VERIFYPEER => 0', 'CURLOPT_POSTREDIR => CURL_REDIR_POST_ALL'], + warning_status => '', + unknown_status => '', + critical_status => '' + ); + + if ($self->{http}->get_code() < 200 || $self->{http}->get_code() >= 300) { + $self->{logger}->writeLogError("Login error [code: '" . $self->{http}->get_code() . "'] [message: '" . $self->{http}->get_message() . "']"); + exit(1); + } + + my $decoded = $self->json_decode(content => $content); + if (!defined($decoded->{data})) { + $self->{logger}->writeLogError("Cannot get log information"); + exit(1); + } + + my $stop = 0; + foreach (@{$decoded->{data}}) { + my $data = $self->json_decode(content => $_->{data}); + next if (defined($log_id) && $log_id >= $_->{id}); + $log_id = $_->{id}; + + if ($_->{code} == 600) { + $self->display_messages(data => $data); + } elsif ($_->{code} == 1) { + $self->display_messages(data => $data); + $stop = 1; + } elsif ($_->{code} == 2) { + $self->display_messages(data => $data); + $stop = 1; + } + } + + last if ($stop == 1); + sleep(2); + } +} + +sub run { + my $self = shift; + + $self->SUPER::run(); + $self->run_etl(); + $self->get_etl_log(); +} + +__END__ + +=head1 NAME + +eventStatisticsBuilder.pl - script to calculate events and availbility statistics + +=head1 SYNOPSIS + +eventStatisticsBuilder.pl [options] + +=head1 OPTIONS + +=over 8 + +=item B<--url> + +Specify the api url (default: 'http://127.0.0.1:8085'). + +=item B<--severity> + +Set the script log severity (default: 'info'). + +=item B<--help> + +Print a brief help message and exits. + +=back + + Rebuild options: + [-s|--start] <YYYY-MM-DD> [-e|--end] <YYYY-MM-DD> [-r|--rebuild] [--no-purge] + Daily run options: + [-d|--daily] + Other options:\n"; + --host-only Process only host events and availability statistics + --service-only Process only service events and availability statistics + --availability-only Build only availability statistics + --events-only Build only event statistics + +=head1 DESCRIPTION + +B<eventStatisticsBuilder.pl> + +=cut diff --git a/gorgone/contrib/mbi/importData.pl b/gorgone/contrib/mbi/importData.pl new file mode 100644 index 00000000000..734dd3d3de0 --- /dev/null +++ b/gorgone/contrib/mbi/importData.pl @@ -0,0 +1,250 @@ +#!/usr/bin/perl + +use warnings; +use strict; +use FindBin; +use lib "$FindBin::Bin"; +# to be launched from contrib directory +use lib "$FindBin::Bin/../"; + +gorgone::script::importData->new()->run(); + +package gorgone::script::importData; + +use strict; +use warnings; +use Data::Dumper; +use gorgone::modules::centreon::mbi::libs::Utils; +use gorgone::standard::misc; +use gorgone::class::http::http; +use JSON::XS; + +use base qw(gorgone::class::script); + +sub new { + my $class = shift; + my $self = $class->SUPER::new( + 'importData', + centreon_db_conn => 0, + centstorage_db_conn => 0, + noconfig => 0 + ); + + bless $self, $class; + + $self->{moptions}->{rebuild} = 0; + $self->{moptions}->{daily} = 0; + $self->{moptions}->{import} = 1; + $self->{moptions}->{dimensions} = 0; + $self->{moptions}->{event} = 0; + $self->{moptions}->{perfdata} = 0; + $self->{moptions}->{start} = ''; + $self->{moptions}->{end} = ''; + $self->{moptions}->{create_tables} = 0; + $self->{moptions}->{databin_only} = 0; + $self->{moptions}->{ignore_databin} = 0; + $self->{moptions}->{centreon_only} = 0; + $self->{moptions}->{nopurge} = 0; + $self->{moptions}->{bam_only} = 0; + + $self->add_options( + 'url:s' => \$self->{url}, + 'r|rebuild' => \$self->{moptions}->{rebuild}, + 'd|daily' => \$self->{moptions}->{daily}, + 's:s' => \$self->{moptions}->{start}, + 'e:s' => \$self->{moptions}->{end}, + 'c|create-tables' => \$self->{moptions}->{create_tables}, + 'databin-only' => \$self->{moptions}->{databin_only}, + 'i|ignore-databin' => \$self->{moptions}->{ignore_databin}, + 'C|centreon-only' => \$self->{moptions}->{centreon_only}, + 'p|no-purge' => \$self->{moptions}->{nopurge}, + 'bam-only' => \$self->{moptions}->{bam_only} + ); + return $self; +} + +sub init { + my $self = shift; + $self->SUPER::init(); + + $self->{url} = 'http://127.0.0.1:8085' if (!defined($self->{url}) || $self->{url} eq ''); + $self->{http} = gorgone::class::http::http->new(logger => $self->{logger}); + my $utils = gorgone::modules::centreon::mbi::libs::Utils->new($self->{logger}); + if ($utils->checkBasicOptions($self->{moptions}) == 1) { + exit(1); + } +} + +sub json_decode { + my ($self, %options) = @_; + + my $decoded; + eval { + $decoded = JSON::XS->new->utf8->decode($options{content}); + }; + if ($@) { + $self->{logger}->writeLogError("cannot decode json response: $@"); + exit(1); + } + + return $decoded; +} + +sub run_etl { + my ($self) = @_; + + my ($code, $content) = $self->{http}->request( + http_backend => 'curl', + method => 'POST', + hostname => '', + full_url => $self->{url} . '/api/centreon/mbietl/run', + query_form_post => JSON::XS->new->utf8->encode($self->{moptions}), + header => [ + 'Accept-Type: application/json; charset=utf-8', + 'Content-Type: application/json; charset=utf-8', + ], + curl_opt => ['CURLOPT_SSL_VERIFYPEER => 0', 'CURLOPT_POSTREDIR => CURL_REDIR_POST_ALL'], + warning_status => '', + unknown_status => '', + critical_status => '' + ); + + if ($self->{http}->get_code() < 200 || $self->{http}->get_code() >= 300) { + $self->{logger}->writeLogError("Login error [code: '" . $self->{http}->get_code() . "'] [message: '" . $self->{http}->get_message() . "']"); + exit(1); + } + + my $decoded = $self->json_decode(content => $content); + if (!defined($decoded->{token})) { + $self->{logger}->writeLogError('cannot get token'); + exit(1); + } + + $self->{token} = $decoded->{token}; +} + +sub display_messages { + my ($self, %options) = @_; + + if (defined($options{data}->{messages})) { + foreach (@{$options{data}->{messages}}) { + if ($_->[0] eq 'D') { + $self->{logger}->writeLogDebug($_->[1]) + } elsif ($_->[0] eq 'I') { + $self->{logger}->writeLogInfo($_->[1]); + } elsif ($_->[0] eq 'E') { + $self->{logger}->writeLogError($_->[1]); + } + } + } +} + +sub get_etl_log { + my ($self) = @_; + + my $log_id; + while (1) { + my $get_param = []; + if (defined($log_id)) { + $get_param = ['id=' . $log_id]; + } + + my ($code, $content) = $self->{http}->request( + http_backend => 'curl', + method => 'GET', + hostname => '', + full_url => $self->{url} . '/api/log/' . $self->{token}, + get_param => $get_param, + header => [ + 'Accept-Type: application/json; charset=utf-8' + ], + curl_opt => ['CURLOPT_SSL_VERIFYPEER => 0', 'CURLOPT_POSTREDIR => CURL_REDIR_POST_ALL'], + warning_status => '', + unknown_status => '', + critical_status => '' + ); + + if ($self->{http}->get_code() < 200 || $self->{http}->get_code() >= 300) { + $self->{logger}->writeLogError("Login error [code: '" . $self->{http}->get_code() . "'] [message: '" . $self->{http}->get_message() . "']"); + exit(1); + } + + my $decoded = $self->json_decode(content => $content); + if (!defined($decoded->{data})) { + $self->{logger}->writeLogError("Cannot get log information"); + exit(1); + } + + my $stop = 0; + foreach (@{$decoded->{data}}) { + my $data = $self->json_decode(content => $_->{data}); + next if (defined($log_id) && $log_id >= $_->{id}); + $log_id = $_->{id}; + + if ($_->{code} == 600) { + $self->display_messages(data => $data); + } elsif ($_->{code} == 1) { + $self->display_messages(data => $data); + $stop = 1; + } elsif ($_->{code} == 2) { + $self->display_messages(data => $data); + $stop = 1; + } + } + + last if ($stop == 1); + sleep(2); + } +} + +sub run { + my $self = shift; + + $self->SUPER::run(); + $self->run_etl(); + $self->get_etl_log(); +} + +__END__ + +=head1 NAME + +importData.pl - script to execute import centreon datas + +=head1 SYNOPSIS + +importData.pl [options] + +=head1 OPTIONS + +=over 8 + +=item B<--url> + +Specify the api url (default: 'http://127.0.0.1:8085'). + +=item B<--severity> + +Set the script log severity (default: 'info'). + +=item B<--help> + +Print a brief help message and exits. + +=back + + First run + [-c|--create-tables] + Rebuild options: + [-r|--rebuild] [--databin-only] [--centreon-only] [--ignore-databin] [--bam-only] + [-s|--start] [-e|--end] Not mandatory : if you don't use these options, the retention parameters will be taken into account + [--no-purge] Only use this mode with rebuild mode to import missing data. + This command may create duplicate entries if executed on a non appropriate period + Daily run options: + [-d|--daily] + +=head1 DESCRIPTION + +B<importData.pl> + +=cut diff --git a/gorgone/contrib/mbi/perfdataStatisticsBuilder.pl b/gorgone/contrib/mbi/perfdataStatisticsBuilder.pl new file mode 100644 index 00000000000..f5d516c7b39 --- /dev/null +++ b/gorgone/contrib/mbi/perfdataStatisticsBuilder.pl @@ -0,0 +1,240 @@ +#!/usr/bin/perl + +use warnings; +use strict; +use FindBin; +use lib "$FindBin::Bin"; +# to be launched from contrib directory +use lib "$FindBin::Bin/../"; + +gorgone::script::perfdataStatisticsBuilder->new()->run(); + +package gorgone::script::perfdataStatisticsBuilder; + +use strict; +use warnings; +use Data::Dumper; +use gorgone::modules::centreon::mbi::libs::Utils; +use gorgone::standard::misc; +use gorgone::class::http::http; +use JSON::XS; + +use base qw(gorgone::class::script); + +sub new { + my $class = shift; + my $self = $class->SUPER::new( + 'perfdataStatisticsBuilder', + centreon_db_conn => 0, + centstorage_db_conn => 0, + noconfig => 0 + ); + + bless $self, $class; + + $self->{moptions}->{rebuild} = 0; + $self->{moptions}->{daily} = 0; + $self->{moptions}->{import} = 0; + $self->{moptions}->{dimensions} = 0; + $self->{moptions}->{event} = 0; + $self->{moptions}->{perfdata} = 1; + $self->{moptions}->{start} = ''; + $self->{moptions}->{end} = ''; + $self->{moptions}->{nopurge} = 0; + $self->{moptions}->{month_only} = 0; + $self->{moptions}->{centile_only} = 0; + $self->{moptions}->{no_centile} = 0; + + $self->add_options( + 'url:s' => \$self->{url}, + 'r|rebuild' => \$self->{moptions}->{rebuild}, + 'd|daily' => \$self->{moptions}->{daily}, + 's:s' => \$self->{moptions}->{start}, + 'e:s' => \$self->{moptions}->{end}, + 'month-only' => \$self->{moptions}->{month_only}, + 'centile-only' => \$self->{moptions}->{centile_only}, + 'no-centile' => \$self->{moptions}->{no_centile} + ); + return $self; +} + +sub init { + my $self = shift; + $self->SUPER::init(); + + $self->{url} = 'http://127.0.0.1:8085' if (!defined($self->{url}) || $self->{url} eq ''); + $self->{http} = gorgone::class::http::http->new(logger => $self->{logger}); + my $utils = gorgone::modules::centreon::mbi::libs::Utils->new($self->{logger}); + if ($utils->checkBasicOptions($self->{moptions}) == 1) { + exit(1); + } +} + +sub json_decode { + my ($self, %options) = @_; + + my $decoded; + eval { + $decoded = JSON::XS->new->utf8->decode($options{content}); + }; + if ($@) { + $self->{logger}->writeLogError("cannot decode json response: $@"); + exit(1); + } + + return $decoded; +} + +sub run_etl { + my ($self) = @_; + + my ($code, $content) = $self->{http}->request( + http_backend => 'curl', + method => 'POST', + hostname => '', + full_url => $self->{url} . '/api/centreon/mbietl/run', + query_form_post => JSON::XS->new->utf8->encode($self->{moptions}), + header => [ + 'Accept-Type: application/json; charset=utf-8', + 'Content-Type: application/json; charset=utf-8', + ], + curl_opt => ['CURLOPT_SSL_VERIFYPEER => 0', 'CURLOPT_POSTREDIR => CURL_REDIR_POST_ALL'], + warning_status => '', + unknown_status => '', + critical_status => '' + ); + + if ($self->{http}->get_code() < 200 || $self->{http}->get_code() >= 300) { + $self->{logger}->writeLogError("Login error [code: '" . $self->{http}->get_code() . "'] [message: '" . $self->{http}->get_message() . "']"); + exit(1); + } + + my $decoded = $self->json_decode(content => $content); + if (!defined($decoded->{token})) { + $self->{logger}->writeLogError('cannot get token'); + exit(1); + } + + $self->{token} = $decoded->{token}; +} + +sub display_messages { + my ($self, %options) = @_; + + if (defined($options{data}->{messages})) { + foreach (@{$options{data}->{messages}}) { + if ($_->[0] eq 'D') { + $self->{logger}->writeLogDebug($_->[1]) + } elsif ($_->[0] eq 'I') { + $self->{logger}->writeLogInfo($_->[1]); + } elsif ($_->[0] eq 'E') { + $self->{logger}->writeLogError($_->[1]); + } + } + } +} + +sub get_etl_log { + my ($self) = @_; + + my $log_id; + while (1) { + my $get_param = []; + if (defined($log_id)) { + $get_param = ['id=' . $log_id]; + } + + my ($code, $content) = $self->{http}->request( + http_backend => 'curl', + method => 'GET', + hostname => '', + full_url => $self->{url} . '/api/log/' . $self->{token}, + get_param => $get_param, + header => [ + 'Accept-Type: application/json; charset=utf-8' + ], + curl_opt => ['CURLOPT_SSL_VERIFYPEER => 0', 'CURLOPT_POSTREDIR => CURL_REDIR_POST_ALL'], + warning_status => '', + unknown_status => '', + critical_status => '' + ); + + if ($self->{http}->get_code() < 200 || $self->{http}->get_code() >= 300) { + $self->{logger}->writeLogError("Login error [code: '" . $self->{http}->get_code() . "'] [message: '" . $self->{http}->get_message() . "']"); + exit(1); + } + + my $decoded = $self->json_decode(content => $content); + if (!defined($decoded->{data})) { + $self->{logger}->writeLogError("Cannot get log information"); + exit(1); + } + + my $stop = 0; + foreach (@{$decoded->{data}}) { + my $data = $self->json_decode(content => $_->{data}); + next if (defined($log_id) && $log_id >= $_->{id}); + $log_id = $_->{id}; + + if ($_->{code} == 600) { + $self->display_messages(data => $data); + } elsif ($_->{code} == 1) { + $self->display_messages(data => $data); + $stop = 1; + } elsif ($_->{code} == 2) { + $self->display_messages(data => $data); + $stop = 1; + } + } + + last if ($stop == 1); + sleep(2); + } +} + +sub run { + my $self = shift; + + $self->SUPER::run(); + $self->run_etl(); + $self->get_etl_log(); +} + +__END__ + +=head1 NAME + +perfdataStatisticsBuilder.pl - script to calculate perfdata statistics + +=head1 SYNOPSIS + +perfdataStatisticsBuilder.pl [options] + +=head1 OPTIONS + +=over 8 + +=item B<--url> + +Specify the api url (default: 'http://127.0.0.1:8085'). + +=item B<--severity> + +Set the script log severity (default: 'info'). + +=item B<--help> + +Print a brief help message and exits. + +=back + + Rebuild options: + [-r | --rebuild] [-s|--start] <YYYY-MM-DD> [-e|--end] <YYYY-MM-DD> [--no-purge] [--month-only] [--centile-only] [--no-centile] + Daily run options: + [-d | --daily] + +=head1 DESCRIPTION + +B<perfdataStatisticsBuilder.pl> + +=cut diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 5241fbb10b2..76e665cb491 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -92,7 +92,7 @@ sub init_server_keys { $self->{keys_loaded} = 0; $self->{config} = { configuration => {} } if (!defined($self->{config}->{configuration})); $self->{config}->{configuration} = { gorgone => {} } if (!defined($self->{config}->{configuration}->{gorgone})); - $self->{config}->{configuration}->{gorgone} = { gorgonecore => {} } if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore})); + $self->{config}->{configuration}->{gorgone}->{gorgonecore} = {} if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore})); $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{privkey} = '/var/lib/centreon-gorgone/.keys/rsakey.priv.pem' if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{privkey}) || $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{privkey} eq ''); diff --git a/gorgone/gorgone/class/db.pm b/gorgone/gorgone/class/db.pm index f35acdf282f..0fc2cc8b3cf 100644 --- a/gorgone/gorgone/class/db.pm +++ b/gorgone/gorgone/class/db.pm @@ -46,6 +46,7 @@ sub new { $self->{dsn} =~ s/"\s*$//; } + $self->{die} = defined($options{die}) ? 1 : 0; $self->{instance} = undef; $self->{transaction_begin} = 0; $self->{args} = []; @@ -62,6 +63,12 @@ sub type { return $self->{type}; } +sub getInstance { + my ($self) = @_; + + return $self->{instance}; +} + # Getter/Setter DB name sub db { my $self = shift; @@ -71,6 +78,28 @@ sub db { return $self->{db}; } +sub sameParams { + my ($self, %options) = @_; + + my $params = ''; + if (defined($self->{dsn})) { + $params = $self->{dsn}; + } else { + $params = $self->{host} . ':' . $self->{port} . ':' . $self->{db}; + } + $params .= ':' . $self->{user} . ':' . $self->{password}; + + my $paramsNew = ''; + if (defined($options{dsn})) { + $paramsNew = $options{dsn}; + } else { + $paramsNew = $options{host} . ':' . $options{port} . ':' . $options{db}; + } + $params .= ':' . $options{user} . ':' . $options{password}; + + return ($paramsNew eq $params) ? 1 : 0; +} + # Getter/Setter DB host sub host { my $self = shift; @@ -254,6 +283,8 @@ sub connect() { (defined($self->{db}) ? $self->{db} : $self->{dsn}) . "': " . $DBI::errstr . " (caller: $package:$filename:$line) (try: $count)" ); if ($self->{force} == 0 || ($self->{force} == 2 && $count == 1)) { + $self->{lastError} = "MySQL error : cannot connect to database '" . + (defined($self->{db}) ? $self->{db} : $self->{dsn}) . "': " . $DBI::errstr; $status = -1; last; } @@ -293,10 +324,10 @@ sub error { my ($package, $filename, $line) = caller 1; chomp($query); - $self->{logger}->writeLogError(<<"EOE"); -SQL error: $error (caller: $package:$filename:$line) + $self->{lastError} = "SQL error: $error (caller: $package:$filename:$line) Query: $query -EOE +"; + $self->{logger}->writeLogError($error); if ($self->{transaction_begin} == 1) { $self->rollback(); } @@ -304,6 +335,12 @@ EOE $self->{instance} = undef; } +sub prepare { + my ($self, $query) = @_; + + return $self->query($query, prepare_only => 1); +} + sub query { my $self = shift; my $query = shift; @@ -338,6 +375,7 @@ sub query { } if (defined($options{prepare_only})) { + return $statement_handle if ($self->{die} == 1); return ($status, $statement_handle); } @@ -353,6 +391,11 @@ sub query { last; } + if ($self->{die} == 1) { + die $self->{lastError} if ($status == -1); + return $statement_handle; + } + return ($status, $statement_handle); } diff --git a/gorgone/gorgone/class/http/backend/curl.pm b/gorgone/gorgone/class/http/backend/curl.pm index 3c12e824491..f2801bafd45 100644 --- a/gorgone/gorgone/class/http/backend/curl.pm +++ b/gorgone/gorgone/class/http/backend/curl.pm @@ -93,12 +93,14 @@ my $http_code_explained = { 415 => 'Unsupported Media Type', 416 => 'Requested Range Not Satisfiable', 417 => 'Expectation Failed', + 450 => 'Timeout reached', # custom code + 451 => 'Failed Connection Host', # custom code 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', - 505 => 'HTTP Version Not Supported', + 505 => 'HTTP Version Not Supported' }; sub cb_debug { @@ -342,10 +344,25 @@ sub request { $self->curl_setopt(option => $self->{constant_cb}->(name => 'CURLOPT_HEADERFUNCTION'), parameter => \&cb_get_header); eval { + $SIG{__DIE__} = sub {}; + $self->{curl_easy}->perform(); }; if ($@) { - $self->{logger}->writeLogError('curl perform error : ' . $@); + my $err = $@; + if (ref($@) eq "Net::Curl::Easy::Code") { + my $num = $@; + if ($num == $self->{constant_cb}->(name => 'CURLE_OPERATION_TIMEDOUT')) { + $self->{response_code} = 450; + } elsif ($num == $self->{constant_cb}->(name => 'CURLE_COULDNT_CONNECT')) { + $self->{response_code} = 451; + } + } + + if (!defined($self->{response_code})) { + $self->{logger}->writeLogError('curl perform error : ' . $err); + } + return 1; } diff --git a/gorgone/gorgone/modules/centreon/mbi/etl/class.pm b/gorgone/gorgone/modules/centreon/mbi/etl/class.pm new file mode 100644 index 00000000000..37fac03f0b7 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/etl/class.pm @@ -0,0 +1,805 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::centreon::mbi::etl::class; + +use base qw(gorgone::class::module); + +use strict; +use warnings; +use gorgone::standard::library; +use gorgone::standard::constants qw(:all); +use gorgone::class::sqlquery; +use gorgone::class::http::http; +use ZMQ::LibZMQ4; +use ZMQ::Constants qw(:all); +use XML::LibXML::Simple; +use JSON::XS; +use gorgone::modules::centreon::mbi::libs::Messages; +use gorgone::modules::centreon::mbi::etl::import::main; +use gorgone::modules::centreon::mbi::etl::event::main; +use gorgone::modules::centreon::mbi::etl::perfdata::main; +use gorgone::modules::centreon::mbi::libs::centreon::ETLProperties; +use Try::Tiny; + +use constant NONE => 0; +use constant RUNNING => 1; +use constant STOP => 2; + +use constant NOTDONE => 0; +use constant DONE => 1; + +use constant UNPLANNED => -1; +use constant PLANNED => 0; +#use constant RUNNING => 1; +use constant FINISHED => 2; + +my %handlers = (TERM => {}, HUP => {}); +my ($connector); + +sub new { + my ($class, %options) = @_; + $connector = $class->SUPER::new(%options); + bless $connector, $class; + + $connector->{cbis_profile} = (defined($connector->{config}->{cbis_profile}) && $connector->{config}->{cbis_profile} ne '') ? + $connector->{config}->{cbis_profile} : '/etc/centreon-bi/cbis-profile.xml'; + $connector->{reports_profile} = (defined($connector->{config}->{reports_profile}) && $connector->{config}->{reports_profile} ne '') ? + $connector->{config}->{reports_profile} : '/etc/centreon-bi/reports-profile.xml'; + + $connector->{run} = { status => NONE }; + + $connector->set_signal_handlers(); + return $connector; +} + +sub set_signal_handlers { + my $self = shift; + + $SIG{TERM} = \&class_handle_TERM; + $handlers{TERM}->{$self} = sub { $self->handle_TERM() }; + $SIG{HUP} = \&class_handle_HUP; + $handlers{HUP}->{$self} = sub { $self->handle_HUP() }; +} + +sub handle_HUP { + my $self = shift; + $self->{reload} = 0; +} + +sub handle_TERM { + my $self = shift; + $self->{logger}->writeLogDebug("[nodes] $$ Receiving order to stop..."); + $self->{stop} = 1; +} + +sub class_handle_TERM { + foreach (keys %{$handlers{TERM}}) { + &{$handlers{TERM}->{$_}}(); + } +} + +sub class_handle_HUP { + foreach (keys %{$handlers{HUP}}) { + &{$handlers{HUP}->{$_}}(); + } +} + +sub reset { + my ($self, %options) = @_; + + $self->{run} = { status => NONE }; +} + +sub runko { + my ($self, %options) = @_; + + $self->send_log( + code => GORGONE_ACTION_FINISH_KO, + token => defined($options{token}) ? $options{token} : $self->{run}->{token}, + data => { + messages => [ ['E', $options{msg} ] ] + } + ); + + $self->check_stopped_ko(); + return 1; +} + +sub db_parse_xml { + my ($self, %options) = @_; + + my ($rv, $message, $content) = gorgone::standard::misc::slurp(file => $options{file}); + return (0, $message) if (!$rv); + eval { + $SIG{__WARN__} = sub {}; + $content = XMLin($content, ForceArray => [], KeyAttr => []); + }; + if ($@) { + die 'cannot read xml file: ' . $@; + } + + my $dbcon = {}; + if (!defined($content->{profile})) { + die 'no profile'; + } + foreach my $profile (@{$content->{profile}}) { + my $name = lc($profile->{name}); + $name =~ s/censtorage/centstorage/; + $dbcon->{$name} = { port => 3306 }; + foreach my $prop (@{$profile->{baseproperties}->{property}}) { + if ($prop->{name} eq 'odaURL' && $prop->{value} =~ /jdbc\:[a-z]+\:\/\/([^:]*)(\:\d+)?\/(.*)/) { + $dbcon->{$name}->{host} = $1; + $dbcon->{$name}->{db} = $3; + if (defined($2) && $2 ne '') { + $dbcon->{$name}->{port} = $2; + $dbcon->{$name}->{port} =~ s/\://; + } + $dbcon->{$name}->{db} =~ s/\?autoReconnect\=true//; + } elsif ($prop->{name} eq 'odaUser') { + $dbcon->{$name}->{user} = $prop->{value}; + } elsif ($prop->{name} eq 'odaPassword') { + $dbcon->{$name}->{password} = $prop->{value}; + } + } + } + foreach my $profile ('centreon', 'centstorage') { + die 'cannot find profile ' . $profile if (!defined($dbcon->{$profile})); + foreach ('host', 'db', 'port', 'user', 'password') { + die "property $_ for profile $profile must be defined" + if (!defined($dbcon->{$profile}->{$_}) || $dbcon->{$profile}->{$_} eq ''); + } + } + + return $dbcon; +} + +sub execute_action { + my ($self, %options) = @_; + + $self->send_internal_action( + action => 'ADDLISTENER', + data => [ + { + identity => 'gorgone-' . $self->{module_id}, + event => 'CENTREONMBIETLLISTENER', + token => $self->{module_id} . '-' . $self->{run}->{token} . '-' . $options{substep}, + timeout => 43200 + } + ] + ); + + my $content = { + dbmon => $self->{run}->{dbmon}, + dbbi => $self->{run}->{dbbi}, + params => $options{params} + }; + if (defined($options{etlProperties})) { + $content->{etlProperties} = $self->{run}->{etlProperties}; + } + if (defined($options{dataRetention})) { + $content->{dataRetention} = $self->{run}->{dataRetention}; + } + if (defined($options{options})) { + $content->{options} = $self->{run}->{options}; + } + + $self->send_internal_action( + action => $options{action}, + token => $self->{module_id} . '-' . $self->{run}->{token} . '-' . $options{substep}, + data => { + instant => 1, + content => $content + } + ); +} + +sub watch_etl_event { + my ($self, %options) = @_; + + if (defined($options{indexes})) { + $self->{run}->{schedule}->{event}->{substeps_executed}++; + my ($idx, $idx2) = split(/-/, $options{indexes}); + $self->{run}->{schedule}->{event}->{stages}->[$idx]->[$idx2]->{status} = FINISHED; + } + + return if (!$self->check_stopped_ko()); + + if ($self->{run}->{schedule}->{event}->{substeps_executed} >= $self->{run}->{schedule}->{event}->{substeps_total}) { + $self->send_log(code => GORGONE_MODULE_CENTREON_MBIETL_PROGRESS, token => $self->{run}->{token}, data => { messages => [ ['I', '[SCHEDULER][EVENT] <<<<<<< end'] ] }); + $self->{run}->{schedule}->{event}->{status} = FINISHED; + $self->check_stopped_ok(); + return ; + } + + my $stage = $self->{run}->{schedule}->{event}->{current_stage}; + my $stage_finished = 0; + while ($stage <= 2) { + while (my ($idx, $val) = each(@{$self->{run}->{schedule}->{event}->{stages}->[$stage]})) { + if (!defined($val->{status})) { + $self->{logger}->writeLogDebug("[mbi-etl] execute substep event-$stage-$idx"); + $self->{run}->{schedule}->{event}->{substeps_execute}++; + $self->execute_action( + action => 'CENTREONMBIETLWORKERSEVENT', + substep => "event-$stage-$idx", + etlProperties => 1, + options => 1, + params => $self->{run}->{schedule}->{event}->{stages}->[$stage]->[$idx] + ); + $self->{run}->{schedule}->{event}->{stages}->[$stage]->[$idx]->{status} = RUNNING; + } elsif ($val->{status} == FINISHED) { + $stage_finished++; + } + } + + if ($stage_finished >= scalar(@{$self->{run}->{schedule}->{event}->{stages}->[$stage]})) { + $self->{run}->{schedule}->{event}->{current_stage}++; + $stage = $self->{run}->{schedule}->{event}->{current_stage}; + } else { + last; + } + } +} + +sub watch_etl_perfdata { + my ($self, %options) = @_; + + if (defined($options{indexes})) { + $self->{run}->{schedule}->{perfdata}->{substeps_executed}++; + my ($idx, $idx2) = split(/-/, $options{indexes}); + $self->{run}->{schedule}->{perfdata}->{stages}->[$idx]->[$idx2]->{status} = FINISHED; + } + + return if (!$self->check_stopped_ko()); + + if ($self->{run}->{schedule}->{perfdata}->{substeps_executed} >= $self->{run}->{schedule}->{perfdata}->{substeps_total}) { + $self->send_log(code => GORGONE_MODULE_CENTREON_MBIETL_PROGRESS, token => $self->{run}->{token}, data => { messages => [ ['I', '[SCHEDULER][PERFDATA] <<<<<<< end'] ] }); + $self->{run}->{schedule}->{perfdata}->{status} = FINISHED; + $self->check_stopped_ok(); + return ; + } + + my $stage = $self->{run}->{schedule}->{perfdata}->{current_stage}; + my $stage_finished = 0; + while ($stage <= 2) { + while (my ($idx, $val) = each(@{$self->{run}->{schedule}->{perfdata}->{stages}->[$stage]})) { + if (!defined($val->{status})) { + $self->{logger}->writeLogDebug("[mbi-etl] execute substep perfdata-$stage-$idx"); + $self->{run}->{schedule}->{perfdata}->{substeps_execute}++; + $self->execute_action( + action => 'CENTREONMBIETLWORKERSPERFDATA', + substep => "perfdata-$stage-$idx", + etlProperties => 1, + options => 1, + params => $self->{run}->{schedule}->{perfdata}->{stages}->[$stage]->[$idx] + ); + $self->{run}->{schedule}->{perfdata}->{stages}->[$stage]->[$idx]->{status} = RUNNING; + } elsif ($val->{status} == FINISHED) { + $stage_finished++; + } + } + + if ($stage_finished >= scalar(@{$self->{run}->{schedule}->{perfdata}->{stages}->[$stage]})) { + $self->{run}->{schedule}->{perfdata}->{current_stage}++; + $stage = $self->{run}->{schedule}->{perfdata}->{current_stage}; + } else { + last; + } + } +} + +sub watch_etl_dimensions { + my ($self, %options) = @_; + + if (defined($options{indexes})) { + $self->{run}->{schedule}->{dimensions}->{substeps_executed}++; + } + + return if (!$self->check_stopped_ko()); + + if ($self->{run}->{schedule}->{dimensions}->{substeps_executed} >= $self->{run}->{schedule}->{dimensions}->{substeps_total}) { + $self->send_log(code => GORGONE_MODULE_CENTREON_MBIETL_PROGRESS, token => $self->{run}->{token}, data => { messages => [ ['I', '[SCHEDULER][DIMENSIONS] <<<<<<< end'] ] }); + $self->{run}->{schedule}->{dimensions}->{status} = FINISHED; + $self->run_etl(); + $self->check_stopped_ok(); + return ; + } + + $self->{run}->{schedule}->{dimensions}->{substeps_execute}++; + $self->execute_action( + action => 'CENTREONMBIETLWORKERSDIMENSIONS', + substep => 'dimensions-1', + etlProperties => 1, + options => 1, + params => {} + ); +} + +sub watch_etl_import { + my ($self, %options) = @_; + + if (defined($options{indexes})) { + $self->{run}->{schedule}->{import}->{substeps_executed}++; + my ($idx, $idx2) = split(/-/, $options{indexes}); + if (defined($idx) && defined($idx2)) { + $self->{run}->{schedule}->{import}->{actions}->[$idx]->{actions}->[$idx2]->{status} = FINISHED; + } else { + $self->{run}->{schedule}->{import}->{actions}->[$idx]->{status} = FINISHED; + } + } + + return if (!$self->check_stopped_ko()); + + if ($self->{run}->{schedule}->{import}->{substeps_executed} >= $self->{run}->{schedule}->{import}->{substeps_total}) { + $self->send_log(code => GORGONE_MODULE_CENTREON_MBIETL_PROGRESS, token => $self->{run}->{token}, data => { messages => [ ['I', '[SCHEDULER][IMPORT] <<<<<<< end'] ] }); + $self->{run}->{schedule}->{import}->{status} = FINISHED; + $self->run_etl(); + $self->check_stopped_ok(); + return ; + } + + while (my ($idx, $val) = each(@{$self->{run}->{schedule}->{import}->{actions}})) { + if (!defined($val->{status})) { + $self->{logger}->writeLogDebug("[mbi-etl] execute substep import-$idx"); + $self->{run}->{schedule}->{import}->{substeps_execute}++; + $self->{run}->{schedule}->{import}->{actions}->[$idx]->{status} = RUNNING; + $self->execute_action( + action => 'CENTREONMBIETLWORKERSIMPORT', + substep => "import-$idx", + params => { + type => $val->{type}, + db => $val->{db}, + sql => $val->{sql}, + command => $val->{command}, + message => $val->{message} + } + ); + } elsif ($val->{status} == FINISHED) { + while (my ($idx2, $val2) = each(@{$val->{actions}})) { + next if (defined($val2->{status})); + + $self->{logger}->writeLogDebug("[mbi-etl] execute substep import-$idx-$idx2"); + $self->{run}->{schedule}->{import}->{substeps_execute}++; + $self->{run}->{schedule}->{import}->{actions}->[$idx]->{actions}->[$idx2]->{status} = RUNNING; + $self->execute_action( + action => 'CENTREONMBIETLWORKERSIMPORT', + substep => "import-$idx-$idx2", + params => $val2 + ); + } + } + } +} + +sub run_etl_import { + my ($self, %options) = @_; + + if ((defined($self->{run}->{etlProperties}->{'host.dedicated'}) && $self->{run}->{etlProperties}->{'host.dedicated'} eq 'false') + || ($self->{run}->{dbbi}->{centstorage}->{host} . ':' . $self->{run}->{dbbi}->{centstorage}->{port} eq $self->{run}->{dbmon}->{centstorage}->{host} . ':' . $self->{run}->{dbmon}->{centstorage}->{port}) + || ($self->{run}->{dbbi}->{centreon}->{host} . ':' . $self->{run}->{dbbi}->{centreon}->{port} eq $self->{run}->{dbmon}->{centreon}->{host} . ':' . $self->{run}->{dbmon}->{centreon}->{port})) { + die 'Do not execute this script if the reporting engine is installed on the monitoring server. In case of "all in one" installation, do not consider this message'; + } + + $self->send_log(code => GORGONE_MODULE_CENTREON_MBIETL_PROGRESS, token => $self->{run}->{token}, data => { messages => [ ['I', '[SCHEDULER][IMPORT] >>>>>>> start' ] ] }); + + gorgone::modules::centreon::mbi::etl::import::main::prepare($self); + + $self->{run}->{schedule}->{import}->{status} = RUNNING; + + $self->{run}->{schedule}->{import}->{substeps_execute} = 0; + $self->{run}->{schedule}->{import}->{substeps_executed} = 0; + $self->{run}->{schedule}->{import}->{substeps_total} = 0; + foreach (@{$self->{run}->{schedule}->{import}->{actions}}) { + $self->{run}->{schedule}->{import}->{substeps_total}++; + my $num = defined($_->{actions}) ? scalar(@{$_->{actions}}) : 0; + $self->{run}->{schedule}->{import}->{substeps_total} += $num if ($num > 0); + } + + $self->{logger}->writeLogDebug("[mbi-etl] import substeps " . $self->{run}->{schedule}->{import}->{substeps_total}); + + $self->watch_etl_import(); +} + +sub run_etl_dimensions { + my ($self, %options) = @_; + + $self->send_log(code => GORGONE_MODULE_CENTREON_MBIETL_PROGRESS, token => $self->{run}->{token}, data => { messages => [ ['I', '[SCHEDULER][DIMENSIONS] >>>>>>> start' ] ] }); + $self->{run}->{schedule}->{dimensions}->{status} = RUNNING; + $self->{run}->{schedule}->{dimensions}->{substeps_execute} = 0; + $self->{run}->{schedule}->{dimensions}->{substeps_executed} = 0; + $self->{run}->{schedule}->{dimensions}->{substeps_total} = 1; + $self->watch_etl_dimensions(); +} + +sub run_etl_event { + my ($self, %options) = @_; + + $self->send_log(code => GORGONE_MODULE_CENTREON_MBIETL_PROGRESS, token => $self->{run}->{token}, data => { messages => [ ['I', '[SCHEDULER][EVENT] >>>>>>> start' ] ] }); + + gorgone::modules::centreon::mbi::etl::event::main::prepare($self); + + $self->{run}->{schedule}->{event}->{status} = RUNNING; + $self->{run}->{schedule}->{event}->{current_stage} = 0; + $self->{run}->{schedule}->{event}->{substeps_execute} = 0; + $self->{run}->{schedule}->{event}->{substeps_executed} = 0; + $self->{run}->{schedule}->{event}->{substeps_total} = + scalar(@{$self->{run}->{schedule}->{event}->{stages}->[0]}) + scalar(@{$self->{run}->{schedule}->{event}->{stages}->[1]}) + scalar(@{$self->{run}->{schedule}->{event}->{stages}->[2]}); + + $self->{logger}->writeLogDebug("[mbi-etl] event substeps " . $self->{run}->{schedule}->{event}->{substeps_total}); + + $self->watch_etl_event(); +} + +sub run_etl_perfdata { + my ($self, %options) = @_; + + $self->send_log(code => GORGONE_MODULE_CENTREON_MBIETL_PROGRESS, token => $self->{run}->{token}, data => { messages => [ ['I', '[SCHEDULER][PERFDATA] >>>>>>> start' ] ] }); + + gorgone::modules::centreon::mbi::etl::perfdata::main::prepare($self); + + $self->{run}->{schedule}->{perfdata}->{status} = RUNNING; + $self->{run}->{schedule}->{perfdata}->{current_stage} = 0; + $self->{run}->{schedule}->{perfdata}->{substeps_execute} = 0; + $self->{run}->{schedule}->{perfdata}->{substeps_executed} = 0; + $self->{run}->{schedule}->{perfdata}->{substeps_total} = + scalar(@{$self->{run}->{schedule}->{perfdata}->{stages}->[0]}) + scalar(@{$self->{run}->{schedule}->{perfdata}->{stages}->[1]}) + scalar(@{$self->{run}->{schedule}->{perfdata}->{stages}->[2]}); + + $self->{logger}->writeLogDebug("[mbi-etl] perfdata substeps " . $self->{run}->{schedule}->{perfdata}->{substeps_total}); + + $self->watch_etl_perfdata(); +} + +sub run_etl { + my ($self, %options) = @_; + + if ($self->{run}->{schedule}->{import}->{status} == PLANNED) { + $self->run_etl_import(); + return ; + } elsif ($self->{run}->{schedule}->{dimensions}->{status} == PLANNED) { + $self->run_etl_dimensions(); + return ; + } + if ($self->{run}->{schedule}->{event}->{status} == PLANNED) { + $self->run_etl_event(); + } + if ($self->{run}->{schedule}->{perfdata}->{status} == PLANNED) { + $self->run_etl_perfdata(); + } +} + +sub check_stopped_ko_import { + my ($self, %options) = @_; + + return 0 if ($self->{run}->{schedule}->{import}->{substeps_executed} >= $self->{run}->{schedule}->{import}->{substeps_execute}); + + return 1; +} + +sub check_stopped_ko_dimensions { + my ($self, %options) = @_; + + return 0 if ($self->{run}->{schedule}->{dimensions}->{substeps_executed} >= $self->{run}->{schedule}->{dimensions}->{substeps_execute}); + + return 1; +} + +sub check_stopped_ko_event { + my ($self, %options) = @_; + + return 0 if ($self->{run}->{schedule}->{event}->{substeps_executed} >= $self->{run}->{schedule}->{event}->{substeps_execute}); + + return 1; +} + +sub check_stopped_ko_perfdata { + my ($self, %options) = @_; + + return 0 if ($self->{run}->{schedule}->{perfdata}->{substeps_executed} >= $self->{run}->{schedule}->{perfdata}->{substeps_execute}); + + return 1; +} + +sub check_stopped_ko { + my ($self, %options) = @_; + + # if nothing planned. we stop + if ($self->{run}->{schedule}->{planned} == NOTDONE) { + $self->reset(); + return 0; + } + + return 1 if ($self->{run}->{status} != STOP); + + my $stopped = 0; + $stopped += $self->check_stopped_ko_import() + if ($self->{run}->{schedule}->{import}->{status} == RUNNING); + $stopped += $self->check_stopped_ko_dimensions() + if ($self->{run}->{schedule}->{dimensions}->{status} == RUNNING); + $stopped += $self->check_stopped_ko_event() + if ($self->{run}->{schedule}->{event}->{status} == RUNNING); + $stopped += $self->check_stopped_ko_perfdata() + if ($self->{run}->{schedule}->{perfdata}->{status} == RUNNING); + + if ($stopped == 0) { + $self->reset(); + return 0; + } + + return 1; +} + +sub check_stopped_ok_import { + my ($self, %options) = @_; + + return 0 if ($self->{run}->{schedule}->{import}->{substeps_executed} >= $self->{run}->{schedule}->{import}->{substeps_total}); + + return 1; +} + +sub check_stopped_ok_dimensions { + my ($self, %options) = @_; + + return 0 if ($self->{run}->{schedule}->{dimensions}->{substeps_executed} >= $self->{run}->{schedule}->{dimensions}->{substeps_total}); + + return 1; +} + +sub check_stopped_ok_event { + my ($self, %options) = @_; + + return 0 if ($self->{run}->{schedule}->{event}->{substeps_executed} >= $self->{run}->{schedule}->{event}->{substeps_total}); + + return 1; +} + +sub check_stopped_ok_perfdata { + my ($self, %options) = @_; + + return 0 if ($self->{run}->{schedule}->{perfdata}->{substeps_executed} >= $self->{run}->{schedule}->{perfdata}->{substeps_total}); + + return 1; +} + +sub check_stopped_ok { + my ($self, %options) = @_; + + return 1 if ($self->{run}->{status} == STOP); + + my $stopped = 0; + $stopped += $self->check_stopped_ok_import() + if ($self->{run}->{schedule}->{import}->{status} == RUNNING); + $stopped += $self->check_stopped_ok_dimensions() + if ($self->{run}->{schedule}->{dimensions}->{status} == RUNNING); + $stopped += $self->check_stopped_ok_event() + if ($self->{run}->{schedule}->{event}->{status} == RUNNING); + $stopped += $self->check_stopped_ok_perfdata() + if ($self->{run}->{schedule}->{perfdata}->{status} == RUNNING); + + if ($stopped == 0) { + $self->send_log( + code => GORGONE_ACTION_FINISH_OK, + token => $self->{run}->{token}, + data => { + messages => [ ['I', '[SCHEDULER] <<<<<<< end' ] ] + } + ); + $self->reset(); + return 0; + } + + return 1; +} + +sub planning { + my ($self, %options) = @_; + + if ($self->{run}->{options}->{import} == 1) { + $self->{run}->{schedule}->{import}->{status} = PLANNED; + $self->{run}->{schedule}->{steps_total}++; + } + if ($self->{run}->{options}->{dimensions} == 1) { + $self->{run}->{schedule}->{dimensions}->{status} = PLANNED; + $self->{run}->{schedule}->{steps_total}++; + } + if ($self->{run}->{options}->{event} == 1) { + $self->{run}->{schedule}->{event}->{status} = PLANNED; + $self->{run}->{schedule}->{steps_total}++; + } + if ($self->{run}->{options}->{perfdata} == 1) { + $self->{run}->{schedule}->{perfdata}->{status} = PLANNED; + $self->{run}->{schedule}->{steps_total}++; + } + + if ($self->{run}->{schedule}->{steps_total} == 0) { + die "[SCHEDULING] nothing planned"; + } + + $self->{run}->{schedule}->{steps_executed} = 0; + $self->{run}->{schedule}->{planned} = DONE; +} + +sub check_basic_options { + my ($self, %options) = @_; + + if (($options{daily} == 0 && $options{rebuild} == 0 && $options{create_tables} == 0 && !defined($options{centile})) + || ($options{daily} == 1 && $options{rebuild} == 1)) { + die "Specify one execution method"; + } + if (($options{rebuild} == 1 || $options{create_tables} == 1) + && (($options{start} ne '' && $options{end} eq '') + || ($options{start} eq '' && $options{end} ne ''))) { + die "Specify both options start and end or neither of them to use default data retention options"; + } + if ($options{rebuild} == 1 && $options{start} ne '' && $options{end} ne '' + && ($options{start} !~ /[1-2][0-9]{3}\-[0-1][0-9]\-[0-3][0-9]/ || $options{end} !~ /[1-2][0-9]{3}\-[0-1][0-9]\-[0-3][0-9]/)) { + die "Verify period start or end date format"; + } +} + +sub action_centreonmbietlrun { + my ($self, %options) = @_; + + try { + $options{token} = $self->generate_token() if (!defined($options{token})); + + return $self->runko(token => $options{token}, msg => '[SCHEDULER] already running') if ($self->{run}->{status} == RUNNING); + return $self->runko(token => $options{token}, msg => '[SCHEDULER] currently wait previous execution finished - can restart gorgone mbi process') if ($self->{run}->{status} == STOP); + + $self->{run}->{token} = $options{token}; + $self->{run}->{messages} = gorgone::modules::centreon::mbi::libs::Messages->new(); + + $self->check_basic_options(%{$options{data}->{content}}); + + $self->{run}->{schedule} = { + steps_total => 0, + steps_executed => 0, + planned => NOTDONE, + import => { status => UNPLANNED, actions => [] }, + dimensions => { status => UNPLANNED }, + event => { status => UNPLANNED, stages => [ [], [], [] ] }, + perfdata => { status => UNPLANNED, stages => [ [], [], [] ] } + }; + $self->{run}->{status} = RUNNING; + + $self->{run}->{options} = $options{data}->{content}; + + $self->send_log(code => GORGONE_MODULE_CENTREON_MBIETL_PROGRESS, token => $self->{run}->{token}, data => { messages => [ ['I', '[SCHEDULER] >>>>>>> start' ] ] }); + + $self->{run}->{dbmon} = $self->db_parse_xml(file => $self->{cbis_profile}); + $self->{run}->{dbbi} = $self->db_parse_xml(file => $self->{reports_profile}); + + $self->{run}->{dbmon_centreon_con} = gorgone::class::db->new( + type => 'mysql', + force => 2, + logger => $self->{logger}, + die => 1, + %{$self->{run}->{dbmon}->{centreon}} + ); + $self->{run}->{dbmon_centstorage_con} = gorgone::class::db->new( + type => 'mysql', + force => 2, + logger => $self->{logger}, + die => 1, + %{$self->{run}->{dbmon}->{centstorage}} + ); + $self->{run}->{dbbi_centstorage_con} = gorgone::class::db->new( + type => 'mysql', + force => 2, + logger => $self->{logger}, + die => 1, + %{$self->{run}->{dbbi}->{centstorage}} + ); + + $self->{etlProp} = gorgone::modules::centreon::mbi::libs::centreon::ETLProperties->new($self->{logger}, $self->{run}->{dbmon_centreon_con}); + ($self->{run}->{etlProperties}, $self->{run}->{dataRetention}) = $self->{etlProp}->getProperties(); + + $self->planning(); + $self->run_etl(); + } catch { + $self->runko(msg => $_) + }; + + #use Data::Dumper; + #print Data::Dumper::Dumper($self->{run}); + + return 0; +} + +sub action_centreonmbietllistener { + my ($self, %options) = @_; + + return 0 if (!defined($options{token}) || $options{token} !~ /^$self->{module_id}-$self->{run}->{token}-(.*?)-(.*)$/); + my ($type, $indexes) = ($1, $2); + + if ($options{data}->{code} == GORGONE_ACTION_FINISH_KO) { + $self->{run}->{status} = STOP; + $self->send_log(code => GORGONE_ACTION_FINISH_KO, token => $self->{run}->{token}, data => $options{data}->{data}); + } elsif ($options{data}->{code} == GORGONE_ACTION_FINISH_OK) { + $self->send_log(code => GORGONE_MODULE_CENTREON_MBIETL_PROGRESS, token => $self->{run}->{token}, data => $options{data}->{data}); + } else { + return 0; + } + + if ($type eq 'import') { + $self->watch_etl_import(indexes => $indexes); + } elsif ($type eq 'dimensions') { + $self->watch_etl_dimensions(indexes => $indexes); + } elsif ($type eq 'event') { + $self->watch_etl_event(indexes => $indexes); + } elsif ($type eq 'perfdata') { + $self->watch_etl_perfdata(indexes => $indexes); + } + + return 1; +} + +sub event { + while (1) { + my $message = $connector->read_message(); + last if (!defined($message)); + + $connector->{logger}->writeLogDebug("[mbi-etl] Event: $message"); + if ($message =~ /^\[(.*?)\]/) { + if ((my $method = $connector->can('action_' . lc($1)))) { + $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; + my ($action, $token) = ($1, $2); + my ($rv, $data) = $connector->json_decode(argument => $3, token => $token); + next if ($rv); + + $method->($connector, token => $token, data => $data); + } + } + } +} + +sub run { + my ($self, %options) = @_; + + # Connect internal + $connector->{internal_socket} = gorgone::standard::library::connect_com( + zmq_type => 'ZMQ_DEALER', + name => 'gorgone-' . $self->{module_id}, + logger => $self->{logger}, + type => $self->get_core_config(name => 'internal_com_type'), + path => $self->get_core_config(name => 'internal_com_path') + ); + $connector->send_internal_action( + action => 'CENTREONMBIETLREADY', + data => {} + ); + $self->{poll} = [ + { + socket => $connector->{internal_socket}, + events => ZMQ_POLLIN, + callback => \&event + } + ]; + + while (1) { + my $rev = scalar(zmq_poll($self->{poll}, 5000)); + if (defined($rev) && $rev == 0 && $self->{stop} == 1) { + $self->{logger}->writeLogInfo("[" . $self->{module_id} . "] $$ has quit"); + zmq_close($connector->{internal_socket}); + exit(0); + } + } +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/etl/event/main.pm b/gorgone/gorgone/modules/centreon/mbi/etl/event/main.pm new file mode 100644 index 00000000000..7f6d760ba1f --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/etl/event/main.pm @@ -0,0 +1,279 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::centreon::mbi::etl::event::main; + +use strict; +use warnings; + +use gorgone::modules::centreon::mbi::libs::bi::Time; +use gorgone::modules::centreon::mbi::libs::bi::LiveService; +use gorgone::modules::centreon::mbi::libs::bi::MySQLTables; +use gorgone::modules::centreon::mbi::libs::Utils; + +my ($biTables, $utils, $liveService, $time); +my ($start, $end); + +sub initVars { + my ($etl) = @_; + + $biTables = gorgone::modules::centreon::mbi::libs::bi::MySQLTables->new($etl->{run}->{messages}, $etl->{run}->{dbbi_centstorage_con}); + $utils = gorgone::modules::centreon::mbi::libs::Utils->new($etl->{run}->{messages}); + $liveService = gorgone::modules::centreon::mbi::libs::bi::LiveService->new($etl->{run}->{messages}, $etl->{run}->{dbbi_centstorage_con}); + $time = gorgone::modules::centreon::mbi::libs::bi::Time->new($etl->{run}->{messages}, $etl->{run}->{dbbi_centstorage_con}); +} + +sub emptyTableForRebuild { + my ($etl, %options) = @_; + + my $sql = [ [ '[CREATE] Deleting table [' . $options{name} . ']', 'DROP TABLE IF EXISTS `' . $options{name} . '`' ] ]; + + my $structure = $biTables->dumpTableStructure($options{name}); + $structure =~ s/KEY.*\(\`$options{column}\`\)\,//g; + $structure =~ s/KEY.*\(\`$options{column}\`\)//g; + $structure =~ s/\,[\n\s+]+\)/\n\)/g; + + if (defined($options{start})) { + $structure =~ s/\n.*PARTITION.*//g; + $structure =~ s/\,[\n\s]+\)/\)/; + $structure .= ' PARTITION BY RANGE(`' . $options{column} . '`) ('; + + my $partitionsPerf = $utils->getRangePartitionDate($options{start}, $options{end}); + + my $append = ''; + foreach (@$partitionsPerf) { + $structure .= $append . "PARTITION p" . $_->{name} . " VALUES LESS THAN (" . $_->{epoch} . ")"; + $append = ','; + } + $structure .= ');'; + } + + push @$sql, + [ '[CREATE] Add table [' . $options{name} . ']', $structure ], + [ "[INDEXING] Adding index [idx_$options{name}_$options{column}] on table [$options{name}]", "ALTER TABLE `$options{name}` ADD INDEX `idx_$options{name}_$options{column}` (`$options{column}`)" ]; + + push @{$etl->{run}->{schedule}->{event}->{stages}->[0]}, { type => 'sql', db => 'centstorage', sql => $sql }; +} + +sub deleteEntriesForRebuild { + my ($etl, %options) = @_; + + my $sql = []; + if (!$biTables->isTablePartitioned($options{name})) { + push @$sql, + [ + "[PURGE] Delete table [$options{name}] from $options{start} to $options{end}", + "DELETE FROM $options{name} WHERE time_id >= " . $utils->getDateEpoch($options{start}) . " AND time_id < " . $utils->getDateEpoch($options{end}) + ]; + } else { + my $partitionsPerf = $utils->getRangePartitionDate($options{start}, $options{end}); + foreach (@$partitionsPerf) { + push @$sql, + [ + "[PURGE] Truncate partition $_->{name} on table [$options{name}]", + "ALTER TABLE $options{name} TRUNCATE PARTITION p$_->{name}" + ]; + } + } + + push @{$etl->{run}->{schedule}->{event}->{stages}->[0]}, { type => 'sql', db => 'centstorage', sql => $sql }; +} + +sub purgeAvailabilityTables { + my ($etl, $start, $end) = @_; + + my $firstDayOfMonth = $start; + $firstDayOfMonth =~ s/([1-2][0-9]{3})\-([0-1][0-9])\-[0-3][0-9]/$1\-$2\-01/; + + if ($etl->{run}->{options}->{nopurge} == 0) { + if (!defined($etl->{run}->{options}->{service_only}) || $etl->{run}->{options}->{service_only} == 0) { + if (!defined($etl->{run}->{options}->{month_only}) || $etl->{run}->{options}->{month_only} == 0) { + emptyTableForRebuild($etl, name => 'mod_bi_hostavailability', column => 'time_id', start => $start, end => $end); + } + + emptyTableForRebuild($etl, name => 'mod_bi_hgmonthavailability', column => 'time_id'); + } + if (!defined($etl->{run}->{options}->{host_only}) || $etl->{run}->{options}->{host_only} == 0) { + if (!defined($etl->{run}->{options}->{month_only}) || $etl->{run}->{options}->{month_only} == 0) { + emptyTableForRebuild($etl, name => 'mod_bi_serviceavailability', column => 'time_id', start => $start, end => $end); + } + + emptyTableForRebuild($etl, name => 'mod_bi_hgservicemonthavailability', column => 'time_id'); + } + } else { + if (!defined($etl->{run}->{options}->{service_only}) || $etl->{run}->{options}->{service_only} == 0) { + if (!defined($etl->{run}->{options}->{month_only}) || $etl->{run}->{options}->{month_only} == 0) { + deleteEntriesForRebuild($etl, name => 'mod_bi_hostavailability', start => $start, end => $end); + } + + deleteEntriesForRebuild($etl, name => 'mod_bi_hgmonthavailability', start => $firstDayOfMonth, end => $end); + } + if (!defined($etl->{run}->{options}->{host_only}) || $etl->{run}->{options}->{host_only} == 0) { + if (!defined($etl->{run}->{options}->{month_only}) || $etl->{run}->{options}->{month_only} == 0) { + deleteEntriesForRebuild($etl, name => 'mod_bi_serviceavailability', start => $start, end => $end); + } + deleteEntriesForRebuild($etl, name => 'mod_bi_hgservicemonthavailability', start => $firstDayOfMonth, end => $end); + } + } +} + +sub processByDay { + my ($etl, $liveServices, $start, $end) = @_; + + while (my ($liveserviceName, $liveserviceId) = each (%$liveServices)) { + if (!defined($etl->{run}->{options}->{service_only}) || $etl->{run}->{options}->{service_only} == 0) { + push @{$etl->{run}->{schedule}->{event}->{stages}->[1]}, { + type => 'availability_day_hosts', + liveserviceName => $liveserviceName, + liveserviceId => $liveserviceId, + start => $start, + end => $end + }; + } + + if (!defined($etl->{run}->{options}->{host_only}) || $etl->{run}->{options}->{host_only} == 0) { + push @{$etl->{run}->{schedule}->{event}->{stages}->[1]}, { + type => 'availability_day_services', + liveserviceName => $liveserviceName, + liveserviceId => $liveserviceId, + start => $start, + end => $end + }; + } + } +} + +sub processHostgroupAvailability { + my ($etl, $start, $end) = @_; + + $time->insertTimeEntriesForPeriod($start, $end); + if (!defined($etl->{run}->{options}->{service_only}) || $etl->{run}->{options}->{service_only} == 0) { + push @{$etl->{run}->{schedule}->{event}->{stages}->[2]}, { + type => 'availability_month_services', + start => $start, + end => $end + }; + } + if (!defined($etl->{run}->{options}->{host_only}) || $etl->{run}->{options}->{host_only} == 0) { + push @{$etl->{run}->{schedule}->{event}->{stages}->[2]}, { + type => 'availability_month_hosts', + start => $start, + end => $end + }; + } +} + +sub dailyProcessing { + my ($etl, $liveServices) = @_; + + # getting yesterday start and end date to process yesterday data + my ($start, $end) = $utils->getYesterdayTodayDate(); + # daily mod_bi_time table filling + $time->insertTimeEntriesForPeriod($start, $end); + + my ($epoch, $partName) = $utils->getDateEpoch($end); + push @{$etl->{run}->{schedule}->{event}->{stages}->[0]}, { + type => 'sql', + db => 'centstorage', + sql => [ + '[PARTITIONS] Add partition [p' . $partName . '] on table [mod_bi_hostavailability]', + "ALTER TABLE `mod_bi_hostavailability` ADD PARTITION (PARTITION `p$partName` VALUES LESS THAN(" . $epoch . "))" + ] + }; + push @{$etl->{run}->{schedule}->{event}->{stages}->[0]}, { + type => 'sql', + db => 'centstorage', + sql => [ + '[PARTITIONS] Add partition [p' . $partName . '] on table [mod_bi_serviceavailability]', + "ALTER TABLE `mod_bi_serviceavailability` ADD PARTITION (PARTITION `p$partName` VALUES LESS THAN(" . $epoch . "))" + ] + }; + + # Calculating availability of hosts and services for the current day + processByDay($etl, $liveServices, $start, $end); + + # Calculating statistics for last month if day of month si 1 + my ($year, $mon, $day) = split('-', $end); + if ($day == 1) { + processHostgroupAvailability($etl, $utils->subtractDateMonths($end, 1), $utils->subtractDateDays($end, 1)); + } + + push @{$etl->{run}->{schedule}->{event}->{stages}->[0]}, + { type => 'events', services => 1, start => $start, end => $end }, { type => 'events', hosts => 1, start => $start, end => $end }; +} + +# rebuild availability statistics +sub rebuildAvailability { + my ($etl, $start, $end, $liveServices) = @_; + + my $days = $utils->getRangePartitionDate($start, $end); + foreach (@$days) { + $end = $_->{date}; + processByDay($etl, $liveServices, $start, $end); + + my ($year, $mon, $day) = split('-', $end); + if ($day == 1) { + processHostgroupAvailability($etl, $utils->subtractDateMonths($end, 1), $utils->subtractDateDays($end, 1)); + } + + $start = $end; + } +} + +sub rebuildProcessing { + my ($etl, $liveServices) = @_; + + if ($etl->{run}->{options}->{start} ne '' && $etl->{run}->{options}->{end} ne '') { + # setting manually start and end dates for each granularity of perfdata + ($start, $end) = ($etl->{run}->{options}->{start}, $etl->{run}->{options}->{end}); + }else { + # getting max perfdata retention period to fill mod_bi_time + my $periods = $etl->{etlProp}->getRetentionPeriods(); + ($start, $end) = ($periods->{'availability.daily'}->{start}, $periods->{'availability.daily'}->{end}); + } + + # insert entries into table mod_bi_time + $time->insertTimeEntriesForPeriod($start, $end); + if (!defined($etl->{run}->{options}->{events_only}) || $etl->{run}->{options}->{events_only} == 0) { + purgeAvailabilityTables($etl, $start, $end); + rebuildAvailability($etl, $start, $end, $liveServices); + } + + if (!defined($etl->{run}->{options}->{availability_only}) || $etl->{run}->{options}->{availability_only} == 0) { + push @{$etl->{run}->{schedule}->{event}->{stages}->[0]}, + { type => 'events', services => 1, start => $start, end => $end }, { type => 'events', hosts => 1, start => $start, end => $end }; + } +} + +sub prepare { + my ($etl) = @_; + + initVars($etl); + + my $liveServiceList = $liveService->getLiveServicesByNameForTpIds($etl->{run}->{etlProperties}->{'liveservices.availability'}); + + if ($etl->{run}->{options}->{daily} == 1) { + dailyProcessing($etl, $liveServiceList); + } elsif ($etl->{run}->{options}->{rebuild} == 1) { + rebuildProcessing($etl, $liveServiceList); + } +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/etl/hooks.pm b/gorgone/gorgone/modules/centreon/mbi/etl/hooks.pm new file mode 100644 index 00000000000..d768fed2445 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/etl/hooks.pm @@ -0,0 +1,154 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::centreon::mbi::etl::hooks; + +use warnings; +use strict; +use gorgone::class::core; +use gorgone::modules::centreon::mbi::etl::class; +use gorgone::standard::constants qw(:all); + +use constant NAMESPACE => 'centreon'; +use constant NAME => 'mbietl'; +use constant EVENTS => [ + { event => 'CENTREONMBIETLRUN', uri => '/run', method => 'POST' }, + { event => 'CENTREONMBIETLLISTENER' }, + { event => 'CENTREONMBIETLREADY' } +]; + +my $config_core; +my $config; +my $run = {}; +my $stop = 0; + +sub register { + my (%options) = @_; + + $config = $options{config}; + $config_core = $options{config_core}; + return (1, NAMESPACE, NAME, EVENTS); +} + +sub init { + my (%options) = @_; + + create_child(logger => $options{logger}); +} + +sub routing { + my (%options) = @_; + + if ($options{action} eq 'CENTREONMBIETLREADY') { + $run->{ready} = 1; + return undef; + } + + if (gorgone::class::core::waiting_ready(ready => \$run->{ready}) == 0) { + gorgone::standard::library::add_history( + dbh => $options{dbh}, + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + data => { message => 'gorgone-' . NAME . ': still no ready' }, + json_encode => 1 + ); + return undef; + } + + $options{gorgone}->send_internal_message( + identity => 'gorgone-' . NAME, + action => $options{action}, + data => $options{data}, + token => $options{token} + ); +} + +sub gently { + my (%options) = @_; + + $stop = 1; + if (defined($run->{running}) && $run->{running} == 1) { + $options{logger}->writeLogDebug("[" . NAME . "] Send TERM signal $run->{pid}"); + CORE::kill('TERM', $run->{pid}); + } +} + +sub kill { + my (%options) = @_; + + if ($run->{running} == 1) { + $options{logger}->writeLogDebug("[" . NAME . "] Send KILL signal $run->{pid}"); + CORE::kill('KILL', $run->{pid}); + } +} + +sub kill_internal { + my (%options) = @_; + +} + +sub check { + my (%options) = @_; + + my $count = 0; + foreach my $pid (keys %{$options{dead_childs}}) { + # Not me + next if (!defined($run->{pid}) || $run->{pid} != $pid); + + $run = {}; + delete $options{dead_childs}->{$pid}; + if ($stop == 0) { + create_child(logger => $options{logger}); + } + } + + $count++ if (defined($run->{running}) && $run->{running} == 1); + + return $count; +} + +sub broadcast { + my (%options) = @_; + + routing(%options); +} + +# Specific functions +sub create_child { + my (%options) = @_; + + $options{logger}->writeLogInfo("[" . NAME . "] Create module '" . NAME . "' process"); + my $child_pid = fork(); + if ($child_pid == 0) { + $0 = 'gorgone-' . NAME; + my $module = gorgone::modules::centreon::mbi::etl::class->new( + logger => $options{logger}, + module_id => NAME, + config_core => $config_core, + config => $config + ); + $module->run(); + exit(0); + } + $options{logger}->writeLogDebug("[" . NAME . "] PID $child_pid (gorgone-" . NAME . ")"); + $run = { pid => $child_pid, ready => 0, running => 1 }; +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/etl/import/main.pm b/gorgone/gorgone/modules/centreon/mbi/etl/import/main.pm new file mode 100644 index 00000000000..b31636854b8 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/etl/import/main.pm @@ -0,0 +1,412 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::centreon::mbi::etl::import::main; + +use strict; +use warnings; + +use gorgone::modules::centreon::mbi::libs::bi::MySQLTables; +use gorgone::modules::centreon::mbi::libs::Utils; + +my ($biTables, $monTables, $utils); +my ($argsMon, $argsBi); + +sub initVars { + my ($etl) = @_; + + $biTables = gorgone::modules::centreon::mbi::libs::bi::MySQLTables->new($etl->{run}->{messages}, $etl->{run}->{dbbi_centstorage_con}); + $monTables = gorgone::modules::centreon::mbi::libs::bi::MySQLTables->new($etl->{run}->{messages}, $etl->{run}->{dbmon_centstorage_con}); + $utils = gorgone::modules::centreon::mbi::libs::Utils->new($etl->{run}->{messages}); + $argsMon = $utils->buildCliMysqlArgs($etl->{run}->{dbmon}->{centstorage}); + $argsBi = $utils->buildCliMysqlArgs($etl->{run}->{dbbi}->{centstorage}); +} + +# Create tables for centstorage database on reporting server +sub createTables { + my ($etl, $periods, $options, $notTimedTables) = @_; + + #Creating all centreon bi tables exept the one already created + my $sth = $etl->{run}->{dbmon_centstorage_con}->query("SHOW TABLES LIKE 'mod_bi_%'"); + while (my @row = $sth->fetchrow_array()) { + my $name = $row[0]; + if (!$biTables->tableExists($name)) { + my $structure = $monTables->dumpTableStructure($name); + push @{$etl->{run}->{schedule}->{import}->{actions}}, + { + type => 1, db => 'centstorage', sql => [ ["[CREATE] add table [$name]", $structure] ], actions => [] + }; + } + } + + # Manage centreonAcl + my $action; + if ($options->{create_tables} == 0) { + #Update centreon_acl table each time centreon-only is started - not the best way but need for Widgets + my $cmd = sprintf( + "mysqldump --replace --no-create-info --skip-add-drop-table --skip-add-locks --skip-comments %s '%s' %s | mysql %s '%s'", + $argsMon, + $etl->{run}->{dbmon}->{centstorage}->{db}, + 'centreon_acl', + $argsBi, + $etl->{run}->{dbbi}->{centstorage}->{db} + ); + $action = { type => 2, message => '[LOAD] import table [centreon_acl]', command => $cmd }; + } + + if (!$biTables->tableExists('centreon_acl')) { + my $structure = $monTables->dumpTableStructure('centreon_acl'); + push @{$etl->{run}->{schedule}->{import}->{actions}}, + { + type => 1, db => 'centstorage', sql => [ ["[CREATE] add table [centreon_acl]", $structure] ], actions => defined($action) ? [$action] : [] + }; + } elsif (defined($action)) { + push @{$etl->{run}->{schedule}->{import}->{actions}}, $action; + } + + my $tables = join('|', @$notTimedTables); + $sth = $etl->{run}->{dbmon_centstorage_con}->query("SHOW TABLES LIKE 'mod_bam_reporting_%'"); + while (my @row = $sth->fetchrow_array()) { + my $name = $row[0]; + next if ($name =~ /^(?:$tables)$/); + + if (!$biTables->tableExists($name)) { + my $structure = $monTables->dumpTableStructure($name); + push @{$etl->{run}->{schedule}->{import}->{actions}}, + { + type => 1, db => 'centstorage', sql => [ ["[CREATE] Add table [$name]", $structure] ], actions => [] + }; + } + } +} + +# Extract data from Centreon DB server +sub extractData { + my ($etl, $options, $notTimedTables) = @_; + + foreach my $name (@$notTimedTables) { + my $action = { type => 1, db => 'centstorage', sql => [], actions => [] }; + + push @{$action->{sql}}, [ '[CREATE] Deleting table [' . $name . ']', 'DROP TABLE IF EXISTS `' . $name . '`' ]; + + my $structure = $monTables->dumpTableStructure($name); + $structure =~ s/(CONSTRAINT.*\n)//g; + $structure =~ s/(\,\n\s+\))/\)/g; + $structure =~ s/auto_increment\=[0-9]+//i; + $structure =~ s/auto_increment//i; + + push @{$action->{sql}}, [ "[CREATE] Add table [$name]", $structure ]; + if ($name eq 'hoststateevents' || $name eq 'servicestateevents') { + # add drop indexes + my $indexes = $etl->{run}->{dbmon_centstorage_con}->query("SHOW INDEX FROM " . $name); + my $previous = ''; + while (my $row = $indexes->fetchrow_hashref()) { + if ($row->{Key_name} ne $previous) { + if (lc($row->{Key_name}) eq lc('PRIMARY')) { + push @{$action->{sql}}, + [ + "[INDEXING] Deleting index [PRIMARY KEY] on table [".$name."]", + "ALTER TABLE `" . $name . "` DROP PRIMARY KEY" + ]; + } else { + push @{$action->{sql}}, + [ + "[INDEXING] Deleting index [$row->{Key_name}] on table [".$name."]", + "ALTER TABLE `" . $name . "` DROP INDEX " . $row->{Key_name} + ]; + } + } + $previous = $row->{Key_name}; + } + + push @{$action->{sql}}, + [ + "[INDEXING] Adding index [in_downtime, start_time, end_time] on table [" . $name . "]", + "ALTER TABLE `" . $name . "` ADD INDEX `idx_" . $name . "_downtime_start_end_time` (in_downtime, start_time, end_time)" + ], + [ + "[INDEXING] Adding index [end_time] on table [" . $name . "]", + "ALTER TABLE `" . $name . "` ADD INDEX `idx_" . $name . "_end_time` (`end_time`)" + ]; + if ($name eq 'servicestateevents') { + push @{$action->{sql}}, + [ + "[INDEXING] Adding index [host_id, service_id, start_time, end_time, ack_time, state, last_update] on table [servicestateevents]", + "ALTER TABLE `servicestateevents` ADD INDEX `idx_servicestateevents_multi` (host_id, service_id, start_time, end_time, ack_time, state, last_update)" + ]; + } + } + + my $cmd = sprintf( + "mysqldump --no-create-info --skip-add-drop-table --skip-add-locks --skip-comments %s '%s' %s | mysql %s '%s'", + $argsMon, + $etl->{run}->{dbmon}->{centstorage}->{db}, + $name, + $argsBi, + $etl->{run}->{dbbi}->{centstorage}->{db} + ); + push @{$action->{actions}}, { type => 2, message => '[LOAD] import table [' . $name . ']', command => $cmd }; + push @{$etl->{run}->{schedule}->{import}->{actions}}, $action; + } +} + +# load data into the reporting server from files copied from the monitoring server +sub extractCentreonDB { + my ($etl, $etlProperties) = @_; + + my $tables = 'host hostgroup_relation hostgroup hostcategories_relation hostcategories ' . + 'host_service_relation service service_categories service_categories_relation ' . + 'timeperiod mod_bi_options servicegroup mod_bi_options_centiles servicegroup_relation contact contactgroup_service_relation '. + 'host_template_relation command contact_host_relation contactgroup_host_relation contactgroup contact_service_relation'; + + my $mon = $utils->buildCliMysqlArgs($etl->{run}->{dbmon}->{centreon}); + my $bi = $utils->buildCliMysqlArgs($etl->{run}->{dbbi}->{centreon}); + + my $cmd = sprintf( + "mysqldump --replace --skip-add-drop-table --skip-add-locks --skip-comments %s '%s' %s | mysql --force %s '%s'", + $mon, + $etl->{run}->{dbmon}->{centreon}->{db}, + $tables, + $bi, + $etl->{run}->{dbbi}->{centreon}->{db} + ); + + push @{$etl->{run}->{schedule}->{import}->{actions}}, + { type => 2, message => '[LOAD] import table [' . $tables . ']', command => $cmd }; +} + +sub dataBin { + my ($etl, $etlProperties, $options, $periods) = @_; + + return if ($options->{ignore_databin} == 1 || $options->{centreon_only} == 1 || (defined($options->{bam_only}) && $options->{bam_only} == 1)); + + my $action = { type => 1, db => 'centstorage', sql => [], actions => [] }; + + my $drop = 0; + if ($options->{rebuild} == 1 && $options->{nopurge} == 0) { + push @{$action->{sql}}, [ '[CREATE] Deleting table [data_bin]', 'DROP TABLE IF EXISTS `data_bin`' ]; + $drop = 1; + } + + my $isExists = 0; + $isExists = 1 if ($biTables->tableExists('data_bin')); + + my $partitionsPerf = $utils->getRangePartitionDate($periods->{raw_perfdata}->{start}, $periods->{raw_perfdata}->{end}); + + if ($isExists == 0 || $drop == 1) { + $action->{create} = 1; + + my $structure = $monTables->dumpTableStructure('data_bin'); + $structure =~ s/KEY.*\(\`id_metric\`\)\,//g; + $structure =~ s/KEY.*\(\`id_metric\`\)//g; + $structure =~ s/\n.*PARTITION.*//g; + $structure =~ s/\,[\n\s]+\)/\)/; + $structure .= " PARTITION BY RANGE(`ctime`) ("; + + my $append = ''; + foreach (@$partitionsPerf) { + $structure .= $append . "PARTITION p" . $_->{name} . " VALUES LESS THAN (" . $_->{epoch} . ")"; + $append = ','; + } + $structure .= ');'; + + push @{$action->{sql}}, + [ '[CREATE] Add table [data_bin]', $structure ], + [ '[INDEXING] Adding index [ctime] on table [data_bin]', "ALTER TABLE `data_bin` ADD INDEX `idx_data_bin_ctime` (`ctime`)" ], + [ '[INDEXING] Adding index [id_metric_id, ctime] on table [data_bin]', "ALTER TABLE `data_bin` ADD INDEX `idx_data_bin_idmetric_ctime` (`id_metric`,`ctime`)" ]; + } + + if ($isExists == 1 && $drop == 0) { + my $start = $biTables->getLastPartRange('data_bin'); + my $partitions = $utils->getRangePartitionDate($start, $periods->{raw_perfdata}->{end}); + foreach (@$partitions) { + push @{$action->{sql}}, + [ '[PARTITIONS] Add partition [' . $_->{name} . '] on table [data_bin]', "ALTER TABLE `data_bin` ADD PARTITION (PARTITION `p$_->{name}` VALUES LESS THAN($_->{epoch}))"]; + } + } + + if ($etl->{run}->{options}->{create_tables} == 0 && ($etlProperties->{'statistics.type'} eq 'all' || $etlProperties->{'statistics.type'} eq 'perfdata')) { + my $epoch = $utils->getDateEpoch($periods->{raw_perfdata}->{start}); + + my $overCond = 'ctime >= ' . $epoch . ' AND '; + foreach (@$partitionsPerf) { + my $cmd = sprintf( + "mysqldump --insert-ignore --single-transaction --no-create-info --skip-add-drop-table --skip-disable-keys --skip-add-locks --skip-comments %s --databases '%s' --tables %s --where=\"%s\" | mysql --init-command='SET SESSION unique_checks=0' %s '%s'", + $argsMon, + $etl->{run}->{dbmon}->{centstorage}->{db}, + 'data_bin', + $overCond . 'ctime < ' . $_->{epoch}, + $argsBi, + $etl->{run}->{dbbi}->{centstorage}->{db} + ); + $overCond = 'ctime >= ' . $_->{epoch} . ' AND '; + push @{$action->{actions}}, { type => 2, message => '[LOAD] partition [' . $_->{name} . '] on table [data_bin]', command => $cmd }; + } + + #my $file = $etlProperties->{'reporting.storage.directory'} . '/data_bin.sql'; + #push @{$action->{actions}}, { + # type => 3, + # message => '[LOAD] table [data_bin]', + # table => 'data_bin', + # db => 'centstorage', + # dump => $cmd, + # file => $file, + # load => "LOAD DATA LOCAL INFILE '" . $file . "' INTO TABLE `data_bin` CHARACTER SET UTF8 IGNORE 1 LINES" + #}; + } + + push @{$etl->{run}->{schedule}->{import}->{actions}}, $action; +} + +sub selectTables { + my ($etl, $etlProperties, $options) = @_; + + my @notTimedTables = (); + my %timedTables = (); + + my @ctime = ('ctime', 'ctime'); + my @startEnd = ('date_start', 'date_end'); + my @timeId = ('time_id', 'time_id'); + my $importComment = $etlProperties->{'import.comments'}; + my $importDowntimes = $etlProperties->{'import.downtimes'}; + + if (!defined($etlProperties->{'statistics.type'})) { + die 'cannot determine statistics type or compatibility mode for data integration'; + } + + if (!defined($options->{databin_only}) || $options->{databin_only} == 0) { + if (!defined($options->{bam_only}) || $options->{bam_only} == 0) { + if ($etlProperties->{'statistics.type'} eq 'all') { + push @notTimedTables, 'index_data'; + push @notTimedTables, 'metrics'; + push @notTimedTables, 'hoststateevents'; + push @notTimedTables, 'servicestateevents'; + push @notTimedTables, 'instances'; + push @notTimedTables, 'hosts'; + + if ($importComment eq 'true'){ + push @notTimedTables, 'comments'; + } + if ($importDowntimes eq 'true'){ + push @notTimedTables, 'downtimes'; + } + + push @notTimedTables, 'acknowledgements'; + } + if ($etlProperties->{'statistics.type'} eq 'availability') { + push @notTimedTables, 'hoststateevents'; + push @notTimedTables, 'servicestateevents'; + push @notTimedTables, 'instances'; + push @notTimedTables, 'hosts'; + if ($importComment eq 'true'){ + push @notTimedTables, 'comments'; + } + push @notTimedTables, 'acknowledgements'; + } + if ($etlProperties->{'statistics.type'} eq "perfdata") { + push @notTimedTables, 'index_data'; + push @notTimedTables, 'metrics'; + push @notTimedTables, 'instances'; + push @notTimedTables, 'hosts'; + push @notTimedTables, 'acknowledgements'; + + } + } + + my $sth = $etl->{run}->{dbmon_centreon_con}->query("SELECT id FROM modules_informations WHERE name='centreon-bam-server'"); + if (my $row = $sth->fetchrow_array() && $etlProperties->{'statistics.type'} ne 'perfdata') { + push @notTimedTables, "mod_bam_reporting_ba_availabilities"; + push @notTimedTables, "mod_bam_reporting_ba"; + push @notTimedTables, "mod_bam_reporting_ba_events"; + push @notTimedTables, "mod_bam_reporting_ba_events_durations"; + push @notTimedTables, "mod_bam_reporting_bv"; + push @notTimedTables, "mod_bam_reporting_kpi"; + push @notTimedTables, "mod_bam_reporting_kpi_events"; + push @notTimedTables, "mod_bam_reporting_relations_ba_bv"; + push @notTimedTables, "mod_bam_reporting_relations_ba_kpi_events"; + push @notTimedTables, "mod_bam_reporting_timeperiods"; + } + } + + return (\@notTimedTables, \%timedTables); +} + +sub prepare { + my ($etl) = @_; + + initVars($etl); + + # define data extraction period based on program options --start & --end or on data retention period + my %periods; + if ($etl->{run}->{options}->{rebuild} == 1 || $etl->{run}->{options}->{create_tables}) { + if ($etl->{run}->{options}->{start} eq '' && $etl->{run}->{options}->{end} eq '') { + # get max values for retention by type of statistics in order to be able to rebuild hourly and daily stats + my ($start, $end) = $etl->{etlProp}->getMaxRetentionPeriodFor('perfdata'); + + $periods{raw_perfdata} = { start => $start, end => $end }; + ($start, $end) = $etl->{etlProp}->getMaxRetentionPeriodFor('availability'); + $periods{raw_availabilitydata} = { start => $start, end => $end}; + } elsif ($etl->{run}->{options}->{start} ne '' && $etl->{run}->{options}->{end} ne '') { + # set period defined manually + my %dates = (start => $etl->{run}->{options}->{start}, end => $etl->{run}->{options}->{end}); + $periods{raw_perfdata} = \%dates; + $periods{raw_availabilitydata} = \%dates; + } + } else { + # set yesterday start and end dates as period (--daily) + my %dates; + ($dates{start}, $dates{end}) = $utils->getYesterdayTodayDate(); + $periods{raw_perfdata} = \%dates; + $periods{raw_availabilitydata} = \%dates; + } + + # identify the Centreon Storage DB tables to extract based on ETL properties + my ($notTimedTables, $timedTables) = selectTables( + $etl, + $etl->{run}->{etlProperties}, + $etl->{run}->{options} + ); + + dataBin( + $etl, + $etl->{run}->{etlProperties}, + $etl->{run}->{options}, + \%periods + ); + + # create non existing tables + createTables($etl, \%periods, $etl->{run}->{options}, $notTimedTables); + + # If we only need to create empty tables, create them then exit program + return if ($etl->{run}->{options}->{create_tables} == 1); + + # extract raw availability and perfdata from monitoring server and insert it into reporting server + if ($etl->{run}->{options}->{centreon_only} == 0) { + extractData($etl, $etl->{run}->{options}, $notTimedTables); + } + + # extract Centreon configuration DB from monitoring server and insert it into reporting server + if ((!defined($etl->{run}->{options}->{databin_only}) || $etl->{run}->{options}->{databin_only} == 0) + && (!defined($etl->{run}->{options}->{bam_only}) || $etl->{run}->{options}->{bam_only} == 0)) { + extractCentreonDB($etl, $etl->{run}->{etlProperties}); + } +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/etl/perfdata/main.pm b/gorgone/gorgone/modules/centreon/mbi/etl/perfdata/main.pm new file mode 100644 index 00000000000..1142bc8ece2 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/etl/perfdata/main.pm @@ -0,0 +1,410 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::centreon::mbi::etl::perfdata::main; + +use strict; +use warnings; + +use gorgone::modules::centreon::mbi::libs::bi::Time; +use gorgone::modules::centreon::mbi::libs::bi::LiveService; +use gorgone::modules::centreon::mbi::libs::bi::MySQLTables; +use gorgone::modules::centreon::mbi::libs::Utils; +use gorgone::standard::constants qw(:all); + +my ($biTables, $utils, $liveService, $time); + +sub initVars { + my ($etl) = @_; + + $biTables = gorgone::modules::centreon::mbi::libs::bi::MySQLTables->new($etl->{run}->{messages}, $etl->{run}->{dbbi_centstorage_con}); + $utils = gorgone::modules::centreon::mbi::libs::Utils->new($etl->{run}->{messages}); + $liveService = gorgone::modules::centreon::mbi::libs::bi::LiveService->new($etl->{run}->{messages}, $etl->{run}->{dbbi_centstorage_con}); + $time = gorgone::modules::centreon::mbi::libs::bi::Time->new($etl->{run}->{messages}, $etl->{run}->{dbbi_centstorage_con}); +} + +sub emptyTableForRebuild { + my ($etl, %options) = @_; + + my $sql = [ [ '[CREATE] Deleting table [' . $options{name} . ']', 'DROP TABLE IF EXISTS `' . $options{name} . '`' ] ]; + + my $structure = $biTables->dumpTableStructure($options{name}); + $structure =~ s/KEY.*\(\`$options{column}\`\)\,//g; + $structure =~ s/KEY.*\(\`$options{column}\`\)//g; + $structure =~ s/\,[\n\s+]+\)/\n\)/g; + + if (defined($options{start})) { + $structure =~ s/\n.*PARTITION.*//g; + $structure =~ s/\,[\n\s]+\)/\)/; + $structure .= ' PARTITION BY RANGE(`' . $options{column} . '`) ('; + + my $partitionsPerf = $utils->getRangePartitionDate($options{start}, $options{end}); + + my $append = ''; + foreach (@$partitionsPerf) { + $structure .= $append . "PARTITION p" . $_->{name} . " VALUES LESS THAN (" . $_->{epoch} . ")"; + $append = ','; + } + $structure .= ');'; + } + + push @$sql, + [ '[CREATE] Add table [' . $options{name} . ']', $structure ], + [ "[INDEXING] Adding index [idx_$options{name}_$options{column}] on table [$options{name}]", "ALTER TABLE `$options{name}` ADD INDEX `idx_$options{name}_$options{column}` (`$options{column}`)" ]; + + push @{$etl->{run}->{schedule}->{perfdata}->{stages}->[0]}, { type => 'sql', db => 'centstorage', sql => $sql }; +} + +sub deleteEntriesForRebuild { + my ($etl, %options) = @_; + + my $sql = []; + if (!$biTables->isTablePartitioned($options{name})) { + push @$sql, + [ + "[PURGE] Delete table [$options{name}] from $options{start} to $options{end}", + "DELETE FROM $options{name} WHERE time_id >= " . $utils->getDateEpoch($options{start}) . " AND time_id < " . $utils->getDateEpoch($options{end}) + ]; + } else { + my $partitionsPerf = $utils->getRangePartitionDate($options{start}, $options{end}); + foreach (@$partitionsPerf) { + push @$sql, + [ + "[PURGE] Truncate partition $_->{name} on table [$options{name}]", + "ALTER TABLE $options{name} TRUNCATE PARTITION p$_->{name}" + ]; + } + } + + push @{$etl->{run}->{schedule}->{perfdata}->{stages}->[0]}, { type => 'sql', db => 'centstorage', sql => $sql }; +} + +sub purgeTables { + my ($etl, $periods) = @_; + + my ($daily_start, $daily_end) = ($periods->{'perfdata.daily'}->{'start'}, $periods->{'perfdata.daily'}->{'end'}); + my ($hourly_start, $hourly_end) = ($periods->{'perfdata.hourly'}->{'start'}, $periods->{'perfdata.hourly'}->{'end'}); + + #To prevent from purging monthly data when the no-purge rebuild is made inside one month + my $firstDayOfMonth = $daily_start; + my $firstDayOfMonthEnd = $daily_end; + my $startAndEndSameMonth = 0; + $firstDayOfMonth =~ s/([1-2][0-9]{3})\-([0-1][0-9])\-[0-3][0-9]/$1\-$2\-01/; + $firstDayOfMonthEnd =~ s/([1-2][0-9]{3})\-([0-1][0-9])\-[0-3][0-9]/$1\-$2\-01/; + + if ($firstDayOfMonth eq $firstDayOfMonthEnd) { + $startAndEndSameMonth = 1; + } + + if ($etl->{run}->{options}->{nopurge} == 1) { + # deleting data that will be rewritten + if ($etl->{run}->{etlProperties}->{'perfdata.granularity'} ne 'hour' && (!defined($etl->{run}->{options}->{month_only}) || $etl->{run}->{options}->{month_only} == 0)) { + if ((!defined($etl->{run}->{options}->{centile_only}) || $etl->{run}->{options}->{centile_only} == 0)) { + deleteEntriesForRebuild($etl, name => 'mod_bi_metricdailyvalue', start => $daily_start, end => $daily_end); + + if ($etl->{run}->{etlProperties}->{'perfdata.granularity'} ne "day" && (!defined($etl->{run}->{options}->{month_only}) || $etl->{run}->{options}->{month_only} == 0)) { + deleteEntriesForRebuild($etl, name => 'mod_bi_metrichourlyvalue', start => $hourly_start, end => $hourly_end); + } + + #Deleting monthly data only if start and end are not in the same month + if (!$startAndEndSameMonth) { + deleteEntriesForRebuild($etl, name => 'mod_bi_metricmonthcapacity', start => $firstDayOfMonth, end => $daily_end); + } + } + + if ((!defined($etl->{run}->{options}->{no_centile}) || $etl->{run}->{options}->{no_centile} == 0)) { + if (defined($etl->{run}->{etlProperties}->{'centile.day'}) && $etl->{run}->{etlProperties}->{'centile.day'} eq '1') { + deleteEntriesForRebuild($etl, name => 'mod_bi_metriccentiledailyvalue', start => $daily_start, end => $daily_end); + } + if (defined($etl->{run}->{etlProperties}->{'centile.week'}) && $etl->{run}->{etlProperties}->{'centile.week'} eq '1') { + deleteEntriesForRebuild($etl, name => 'mod_bi_metriccentileweeklyvalue', start => $daily_start, end => $daily_end); + } + + if (defined($etl->{run}->{etlProperties}->{'centile.month'}) && $etl->{run}->{etlProperties}->{'centile.month'} eq '1' && !$startAndEndSameMonth) { + deleteEntriesForRebuild($etl, name => 'mod_bi_metriccentilemonthlyvalue', start => $firstDayOfMonth, end => $daily_end); + } + } + } + } else { + # deleting and recreating tables, recreating partitions for daily and hourly tables + if ($etl->{run}->{etlProperties}->{'perfdata.granularity'} ne "hour" && (!defined($etl->{run}->{options}->{month_only}) || $etl->{run}->{options}->{month_only} == 0)) { + if ((!defined($etl->{run}->{options}->{centile_only}) || $etl->{run}->{options}->{centile_only} == 0)) { + emptyTableForRebuild($etl, name => 'mod_bi_metricdailyvalue', column => 'time_id', start => $daily_start, end => $daily_end); + + emptyTableForRebuild($etl, name => 'mod_bi_metricmonthcapacity', column => 'time_id'); + } + + if ((!defined($etl->{run}->{options}->{no_centile}) || $etl->{run}->{options}->{no_centile} == 0)) { + #Managing Daily Centile table + if (defined($etl->{run}->{etlProperties}->{'centile.day'}) && $etl->{run}->{etlProperties}->{'centile.day'} eq '1') { + emptyTableForRebuild($etl, name => 'mod_bi_metriccentiledailyvalue', column => 'time_id', start => $daily_start, end => $daily_end); + } + #Managing Weekly Centile table + if (defined($etl->{run}->{etlProperties}->{'centile.week'}) && $etl->{run}->{etlProperties}->{'centile.week'} eq '1') { + emptyTableForRebuild($etl, name => 'mod_bi_metriccentileweeklyvalue', column => 'time_id', start => $daily_start, end => $daily_end); + } + #Managing Monthly Centile table + if (defined($etl->{run}->{etlProperties}->{'centile.month'}) && $etl->{run}->{etlProperties}->{'centile.month'} eq '1') { + emptyTableForRebuild($etl, name => 'mod_bi_metriccentilemonthlyvalue', column => 'time_id', start => $daily_start, end => $daily_end); + } + } + } + + if ($etl->{run}->{etlProperties}->{'perfdata.granularity'} ne "day" && + (!defined($etl->{run}->{options}->{month_only}) || $etl->{run}->{options}->{month_only} == 0) && + (!defined($etl->{run}->{options}->{no_centile}) || $etl->{run}->{options}->{no_centile} == 0)) { + emptyTableForRebuild($etl, name => 'mod_bi_metrichourlyvalue', column => 'time_id', start => $hourly_start, end => $hourly_end); + } + } +} + +sub processDay { + my ($etl, $liveServices, $start, $end) = @_; + + if ($etl->{run}->{etlProperties}->{'perfdata.granularity'} eq 'hour' || + (defined($etl->{run}->{options}->{month_only}) && $etl->{run}->{options}->{month_only} == 1)) { + return 1; + } + + my ($currentDayId, $currentDayUtime) = $time->getEntryID($start); + + if ((!defined($etl->{run}->{options}->{centile_only}) || $etl->{run}->{options}->{centile_only} == 0)) { + while (my ($liveServiceName, $liveServiceId) = each (%$liveServices)) { + push @{$etl->{run}->{schedule}->{perfdata}->{stages}->[1]}, { + type => 'perfdata_day', + liveserviceName => $liveServiceName, + liveserviceId => $liveServiceId, + start => $start, + end => $end + }; + } + } + + if ((!defined($etl->{run}->{options}->{no_centile}) || $etl->{run}->{options}->{no_centile} == 0)) { + if (defined($etl->{run}->{etlProperties}->{'centile.include.servicecategories'}) && $etl->{run}->{etlProperties}->{'centile.include.servicecategories'} ne '') { + if (defined($etl->{run}->{etlProperties}->{'centile.day'}) && $etl->{run}->{etlProperties}->{'centile.day'} eq '1') { + push @{$etl->{run}->{schedule}->{perfdata}->{stages}->[2]}, { + type => 'centile_day', + start => $start, + end => $end + }; + } + if (defined($etl->{run}->{etlProperties}->{'centile.week'}) && $etl->{run}->{etlProperties}->{'centile.week'} eq '1') { + if ($utils->getDayOfWeek($end) eq $etl->{run}->{etlProperties}->{'centile.weekFirstDay'}) { + processWeek($etl, $end); + } + } + } + } +} + +sub processWeek { + my ($etl, $date) = @_; + + my $start = $utils->subtractDateDays($date, 7); + my $end = $utils->subtractDateDays($date, 1); + + $time->insertTimeEntriesForPeriod($start, $end); + + push @{$etl->{run}->{schedule}->{perfdata}->{stages}->[2]}, { + type => 'centile_week', + start => $start, + end => $end + }; +} + +sub processMonth { + my ($etl, $liveServices, $date) = @_; + + my $start = $utils->subtractDateMonths($date, 1); + my $end = $utils->subtractDateDays($date, 1); + + $time->insertTimeEntriesForPeriod($start, $end); + + my ($previousMonthStartTimeId, $previousMonthStartUtime) = $time->getEntryID($start); + my ($previousMonthEndTimeId, $previousMonthEndUtime) = $time->getEntryID($end); + + if (!defined($etl->{run}->{etlProperties}->{'capacity.include.servicecategories'}) || $etl->{run}->{etlProperties}->{'capacity.include.servicecategories'} eq "" + || !defined($etl->{run}->{etlProperties}->{'capacity.include.liveservices'}) || $etl->{run}->{etlProperties}->{'capacity.include.liveservices'} eq "") { + $etl->send_log(code => GORGONE_MODULE_CENTREON_MBIETL_PROGRESS, token => $etl->{run}->{token}, data => { messages => [ ['I', "[SCHEDULER][PERFDATA] Skipping month: [" . $start . "] to [" . $end . "]" ] ] }); + return ; + } + + if ((!defined($etl->{run}->{options}->{centile_only}) || $etl->{run}->{options}->{centile_only} == 0) && + $etl->{run}->{etlProperties}->{'perfdata.granularity'} ne 'hour') { + push @{$etl->{run}->{schedule}->{perfdata}->{stages}->[2]}, { + type => 'perfdata_month', + start => $start, + end => $end + }; + } + + if ((!defined($etl->{run}->{options}->{no_centile}) || $etl->{run}->{options}->{no_centile} == 0) && + $etl->{run}->{etlProperties}->{'centile.month'} && $etl->{run}->{etlProperties}->{'perfdata.granularity'} ne 'hour') { + if (defined($etl->{run}->{etlProperties}->{'centile.include.servicecategories'}) && $etl->{run}->{etlProperties}->{'centile.include.servicecategories'} ne '') { + push @{$etl->{run}->{schedule}->{perfdata}->{stages}->[2]}, { + type => 'centile_month', + start => $start, + end => $end + }; + } + } +} + +sub processHours { + my ($etl, $start, $end) = @_; + + if ($etl->{run}->{etlProperties}->{'perfdata.granularity'} eq 'day' || + (defined($etl->{run}->{options}->{month_only}) && $etl->{run}->{options}->{month_only} == 1) || + (defined($etl->{run}->{options}->{centile_only}) && $etl->{run}->{options}->{centile_only} == 1)) { + return 1; + } + + push @{$etl->{run}->{schedule}->{perfdata}->{stages}->[2]}, { + type => 'perfdata_hour', + start => $start, + end => $end + }; +} + +sub processDayAndMonthAgregation { + my ($etl, $liveServices, $start, $end) = @_; + + processDay($etl, $liveServices, $start, $end); + my ($year, $mon, $day) = split ("-", $end); + if ($day == 1) { + processMonth($etl, $liveServices, $end); + } +} + +sub dailyProcessing { + my ($etl, $liveServices) = @_; + + # getting yesterday start and end date to process yesterday data + my ($start, $end) = $utils->getYesterdayTodayDate(); + # daily mod_bi_time table filling + $time->insertTimeEntriesForPeriod($start, $end); + + my ($epoch, $partName) = $utils->getDateEpoch($end); + push @{$etl->{run}->{schedule}->{perfdata}->{stages}->[0]}, { + type => 'sql', + db => 'centstorage', + sql => [ + '[PARTITIONS] Add partition [p' . $partName . '] on table [mod_bi_metricdailyvalue]', + "ALTER TABLE `mod_bi_metricdailyvalue` ADD PARTITION (PARTITION `p$partName` VALUES LESS THAN(" . $epoch . "))" + ] + }; + if ($etl->{run}->{etlProperties}->{'perfdata.granularity'} ne 'day') { + push @{$etl->{run}->{schedule}->{perfdata}->{stages}->[0]}, { + type => 'sql', + db => 'centstorage', + sql => [ + '[PARTITIONS] Add partition [p' . $partName . '] on table [mod_bi_metrichourlyvalue]', + "ALTER TABLE `mod_bi_metrichourlyvalue` ADD PARTITION (PARTITION `p$partName` VALUES LESS THAN(" . $epoch . "))" + ] + }; + } + if (defined($etl->{run}->{etlProperties}->{'centile.day'}) && $etl->{run}->{etlProperties}->{'centile.day'} eq '1') { + push @{$etl->{run}->{schedule}->{perfdata}->{stages}->[0]}, { + type => 'sql', + db => 'centstorage', + sql => [ + '[PARTITIONS] Add partition [p' . $partName . '] on table [mod_bi_metriccentiledailyvalue]', + "ALTER TABLE `mod_bi_metriccentiledailyvalue` ADD PARTITION (PARTITION `p$partName` VALUES LESS THAN(" . $epoch . "))" + ] + }; + } + + # processing agregation by month. If the day is the first day of the month, also processing agregation by month + processDayAndMonthAgregation($etl, $liveServices, $start, $end); + + # processing agregation by hour + processHours($etl, $start, $end); +} + +sub rebuildProcessing { + my ($etl, $liveServices) = @_; + + # getting rebuild period by granularity of perfdata from data retention rules + my $periods = $etl->{etlProp}->getRetentionPeriods(); + + my ($start, $end); + if ($etl->{run}->{options}->{start} ne '' && $etl->{run}->{options}->{end} ne '') { + ($start, $end) = ($etl->{run}->{options}->{start}, $etl->{run}->{options}->{end}); + while (my ($key, $values) = each %$periods) { + $values->{start} = $etl->{run}->{options}->{start}; + $values->{end} = $etl->{run}->{options}->{end}; + } + } else { + # getting max perfdata retention period to fill mod_bi_time + ($start, $end) = $etl->{etlProp}->getMaxRetentionPeriodFor('perfdata'); + } + + # insert entries into table mod_bi_time + $time->insertTimeEntriesForPeriod($start, $end); + + purgeTables($etl, $periods); + + # rebuilding statistics by day and by month + ($start, $end) = ($periods->{'perfdata.daily'}->{start}, $periods->{'perfdata.daily'}->{end}); + + my $days = $utils->getRangePartitionDate($start, $end); + foreach (@$days) { + $end = $_->{date}; + processDayAndMonthAgregation($etl, $liveServices, $start, $end); + $start = $end; + } + + # rebuilding statistics by hour + ($start, $end) = ($periods->{'perfdata.hourly'}->{start}, $periods->{'perfdata.hourly'}->{'end'}); + + $days = $utils->getRangePartitionDate($start, $end); + foreach (@$days) { + $end = $_->{date}; + processHours($etl, $start, $end); + $start = $end; + } +} + +sub prepare { + my ($etl) = @_; + + initVars($etl); + + if (!defined($etl->{run}->{etlProperties}->{'statistics.type'}) || $etl->{run}->{etlProperties}->{'statistics.type'} eq "availability") { + $etl->send_log(code => GORGONE_MODULE_CENTREON_MBIETL_PROGRESS, token => $etl->{run}->{token}, data => { messages => [ ['I', '[SCHEDULER][PERFDATA] Performance statistics calculation disabled' ] ] }); + return ; + } + + if ((!defined($etl->{run}->{options}->{no_centile}) || $etl->{run}->{options}->{no_centile} == 0) && + defined($etl->{run}->{etlProperties}->{'centile.include.servicecategories'}) and $etl->{run}->{etlProperties}->{'centile.include.servicecategories'} eq '') { + $etl->send_log(code => GORGONE_MODULE_CENTREON_MBIETL_PROGRESS, token => $etl->{run}->{token}, data => { messages => [ ['I', '[SCHEDULER][PERFDATA] No service categories selected for centile calculation - centile agregation will not be calculated' ] ] }); + } + + my $liveServiceList = $liveService->getLiveServicesByNameForTpIds($etl->{run}->{etlProperties}->{'liveservices.perfdata'}); + + if ($etl->{run}->{options}->{daily} == 1) { + dailyProcessing($etl, $liveServiceList); + } elsif ($etl->{run}->{options}->{rebuild} == 1) { + rebuildProcessing($etl, $liveServiceList); + } +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/etlworkers/class.pm b/gorgone/gorgone/modules/centreon/mbi/etlworkers/class.pm new file mode 100644 index 00000000000..3bbadca7476 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/etlworkers/class.pm @@ -0,0 +1,349 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::centreon::mbi::etlworkers::class; + +use base qw(gorgone::class::module); + +use strict; +use warnings; +use gorgone::standard::library; +use gorgone::standard::constants qw(:all); +use gorgone::class::http::http; +use ZMQ::LibZMQ4; +use ZMQ::Constants qw(:all); +use JSON::XS; +use Try::Tiny; +use gorgone::modules::centreon::mbi::etlworkers::import::main; +use gorgone::modules::centreon::mbi::etlworkers::dimensions::main; +use gorgone::modules::centreon::mbi::etlworkers::event::main; +use gorgone::modules::centreon::mbi::etlworkers::perfdata::main; +use gorgone::modules::centreon::mbi::libs::Messages; + +my %handlers = (TERM => {}, HUP => {}); +my ($connector); + +sub new { + my ($class, %options) = @_; + $connector = $class->SUPER::new(%options); + bless $connector, $class; + + $connector->{pool_id} = $options{pool_id}; + + $connector->set_signal_handlers(); + return $connector; +} + +sub set_signal_handlers { + my $self = shift; + + $SIG{TERM} = \&class_handle_TERM; + $handlers{TERM}->{$self} = sub { $self->handle_TERM() }; + $SIG{HUP} = \&class_handle_HUP; + $handlers{HUP}->{$self} = sub { $self->handle_HUP() }; +} + +sub handle_HUP { + my $self = shift; + $self->{reload} = 0; +} + +sub handle_TERM { + my $self = shift; + $self->{logger}->writeLogDebug("[nodes] $$ Receiving order to stop..."); + $self->{stop} = 1; +} + +sub class_handle_TERM { + foreach (keys %{$handlers{TERM}}) { + &{$handlers{TERM}->{$_}}(); + } +} + +sub class_handle_HUP { + foreach (keys %{$handlers{HUP}}) { + &{$handlers{HUP}->{$_}}(); + } +} + +sub db_connections { + my ($self, %options) = @_; + + if (!defined($self->{dbmon_centstorage_con}) || $self->{dbmon_centstorage_con}->sameParams(%{$options{dbmon}->{centstorage}}) == 0) { + $self->{dbmon_centstorage_con} = gorgone::class::db->new( + type => 'mysql', + force => 2, + logger => $self->{logger}, + die => 1, + %{$options{dbmon}->{centstorage}} + ); + } + if (!defined($self->{dbbi_centstorage_con}) || $self->{dbbi_centstorage_con}->sameParams(%{$options{dbbi}->{centstorage}}) == 0) { + $self->{dbbi_centstorage_con} = gorgone::class::db->new( + type => 'mysql', + force => 2, + logger => $self->{logger}, + die => 1, + %{$options{dbbi}->{centstorage}} + ); + } + + if (!defined($self->{dbmon_centreon_con}) || $self->{dbmon_centreon_con}->sameParams(%{$options{dbmon}->{centreon}}) == 0) { + $self->{dbmon_centreon_con} = gorgone::class::db->new( + type => 'mysql', + force => 2, + logger => $self->{logger}, + die => 1, + %{$options{dbmon}->{centreon}} + ); + } + if (!defined($self->{dbbi_centreon_con}) || $self->{dbbi_centreon_con}->sameParams(%{$options{dbbi}->{centreon}}) == 0) { + $self->{dbbi_centreon_con} = gorgone::class::db->new( + type => 'mysql', + force => 2, + logger => $self->{logger}, + die => 1, + %{$options{dbbi}->{centreon}} + ); + } +} + +sub action_centreonmbietlworkersimport { + my ($self, %options) = @_; + + $options{token} = $self->generate_token() if (!defined($options{token})); + + $self->{messages} = gorgone::modules::centreon::mbi::libs::Messages->new(); + my $code = GORGONE_ACTION_FINISH_OK; + + try { + $self->db_connections( + dbmon => $options{data}->{content}->{dbmon}, + dbbi => $options{data}->{content}->{dbbi} + ); + if ($options{data}->{content}->{params}->{type} == 1) { + gorgone::modules::centreon::mbi::etlworkers::import::main::sql($self, params => $options{data}->{content}->{params}); + } elsif ($options{data}->{content}->{params}->{type} == 2) { + gorgone::modules::centreon::mbi::etlworkers::import::main::command($self, params => $options{data}->{content}->{params}); + } elsif ($options{data}->{content}->{params}->{type} == 3) { + gorgone::modules::centreon::mbi::etlworkers::import::main::load($self, params => $options{data}->{content}->{params}); + } + } catch { + $code = GORGONE_ACTION_FINISH_KO; + $self->{messages}->writeLog('ERROR', $_, 1); + }; + + $self->send_log( + code => $code, + token => $options{token}, + data => { + messages => $self->{messages}->getLogs() + } + ); +} + +sub action_centreonmbietlworkersdimensions { + my ($self, %options) = @_; + + $options{token} = $self->generate_token() if (!defined($options{token})); + + $self->{messages} = gorgone::modules::centreon::mbi::libs::Messages->new(); + my $code = GORGONE_ACTION_FINISH_OK; + + try { + $self->db_connections( + dbmon => $options{data}->{content}->{dbmon}, + dbbi => $options{data}->{content}->{dbbi} + ); + + gorgone::modules::centreon::mbi::etlworkers::dimensions::main::execute( + $self, + dbmon => $options{data}->{content}->{dbmon}, + dbbi => $options{data}->{content}->{dbbi}, + params => $options{data}->{content}->{params}, + etlProperties => $options{data}->{content}->{etlProperties}, + options => $options{data}->{content}->{options} + ); + } catch { + $code = GORGONE_ACTION_FINISH_KO; + $self->{messages}->writeLog('ERROR', $_, 1); + }; + + $self->send_log( + code => $code, + token => $options{token}, + data => { + messages => $self->{messages}->getLogs() + } + ); +} + +sub action_centreonmbietlworkersevent { + my ($self, %options) = @_; + + $options{token} = $self->generate_token() if (!defined($options{token})); + + $self->{messages} = gorgone::modules::centreon::mbi::libs::Messages->new(); + my $code = GORGONE_ACTION_FINISH_OK; + + try { + $self->db_connections( + dbmon => $options{data}->{content}->{dbmon}, + dbbi => $options{data}->{content}->{dbbi} + ); + if ($options{data}->{content}->{params}->{type} eq 'sql') { + gorgone::modules::centreon::mbi::etlworkers::event::main::sql($self, params => $options{data}->{content}->{params}); + } elsif ($options{data}->{content}->{params}->{type} eq 'events') { + gorgone::modules::centreon::mbi::etlworkers::event::main::events( + $self, + dbmon => $options{data}->{content}->{dbmon}, + dbbi => $options{data}->{content}->{dbbi}, + etlProperties => $options{data}->{content}->{etlProperties}, + params => $options{data}->{content}->{params}, + options => $options{data}->{content}->{options} + ); + } elsif ($options{data}->{content}->{params}->{type} =~ /^availability_/) { + gorgone::modules::centreon::mbi::etlworkers::event::main::availability( + $self, + dbmon => $options{data}->{content}->{dbmon}, + dbbi => $options{data}->{content}->{dbbi}, + etlProperties => $options{data}->{content}->{etlProperties}, + params => $options{data}->{content}->{params} + ); + } + } catch { + $code = GORGONE_ACTION_FINISH_KO; + $self->{messages}->writeLog('ERROR', $_, 1); + }; + + $self->send_log( + code => $code, + token => $options{token}, + data => { + messages => $self->{messages}->getLogs() + } + ); +} + +sub action_centreonmbietlworkersperfdata { + my ($self, %options) = @_; + + $options{token} = $self->generate_token() if (!defined($options{token})); + + $self->{messages} = gorgone::modules::centreon::mbi::libs::Messages->new(); + my $code = GORGONE_ACTION_FINISH_OK; + + try { + $self->db_connections( + dbmon => $options{data}->{content}->{dbmon}, + dbbi => $options{data}->{content}->{dbbi} + ); + + if ($options{data}->{content}->{params}->{type} eq 'sql') { + gorgone::modules::centreon::mbi::etlworkers::perfdata::main::sql($self, params => $options{data}->{content}->{params}); + } elsif ($options{data}->{content}->{params}->{type} =~ /^perfdata_/) { + gorgone::modules::centreon::mbi::etlworkers::perfdata::main::perfdata( + $self, + dbmon => $options{data}->{content}->{dbmon}, + dbbi => $options{data}->{content}->{dbbi}, + etlProperties => $options{data}->{content}->{etlProperties}, + params => $options{data}->{content}->{params}, + options => $options{data}->{content}->{options}, + pool_id => $self->{pool_id} + ); + } elsif ($options{data}->{content}->{params}->{type} =~ /^centile_/) { + gorgone::modules::centreon::mbi::etlworkers::perfdata::main::centile( + $self, + dbmon => $options{data}->{content}->{dbmon}, + dbbi => $options{data}->{content}->{dbbi}, + etlProperties => $options{data}->{content}->{etlProperties}, + params => $options{data}->{content}->{params}, + pool_id => $self->{pool_id} + ); + } + } catch { + $code = GORGONE_ACTION_FINISH_KO; + $self->{messages}->writeLog('ERROR', $_, 1); + }; + + $self->send_log( + code => $code, + token => $options{token}, + data => { + messages => $self->{messages}->getLogs() + } + ); +} + +sub event { + while (1) { + my $message = $connector->read_message(); + last if (!defined($message)); + + $connector->{logger}->writeLogDebug("[mbi-etlworkers] Event: $message"); + if ($message =~ /^\[(.*?)\]/) { + if ((my $method = $connector->can('action_' . lc($1)))) { + $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; + my ($action, $token) = ($1, $2); + my ($rv, $data) = $connector->json_decode(argument => $3, token => $token); + next if ($rv); + + $method->($connector, token => $token, data => $data); + } + } + } +} + +sub run { + my ($self, %options) = @_; + + # Connect internal + $connector->{internal_socket} = gorgone::standard::library::connect_com( + zmq_type => 'ZMQ_DEALER', + name => 'gorgone-' . $self->{module_id} . '-' . $self->{pool_id}, + logger => $self->{logger}, + type => $self->get_core_config(name => 'internal_com_type'), + path => $self->get_core_config(name => 'internal_com_path') + ); + $connector->send_internal_action( + action => 'CENTREONMBIETLWORKERSREADY', + data => { + pool_id => $self->{pool_id} + } + ); + $self->{poll} = [ + { + socket => $connector->{internal_socket}, + events => ZMQ_POLLIN, + callback => \&event + } + ]; + + while (1) { + my $rev = scalar(zmq_poll($self->{poll}, 5000)); + if (defined($rev) && $rev == 0 && $self->{stop} == 1) { + $self->{logger}->writeLogInfo("[" . $self->{module_id} . "] $$ has quit"); + zmq_close($connector->{internal_socket}); + exit(0); + } + } +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/etlworkers/dimensions/main.pm b/gorgone/gorgone/modules/centreon/mbi/etlworkers/dimensions/main.pm new file mode 100644 index 00000000000..ec9c5dc9a89 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/etlworkers/dimensions/main.pm @@ -0,0 +1,263 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::centreon::mbi::etlworkers::dimensions::main; + +use strict; +use warnings; + +use IO::Socket::INET; + +use gorgone::modules::centreon::mbi::libs::centreon::Host; +use gorgone::modules::centreon::mbi::libs::centreon::HostGroup; +use gorgone::modules::centreon::mbi::libs::centreon::HostCategory; +use gorgone::modules::centreon::mbi::libs::centreon::ServiceCategory; +use gorgone::modules::centreon::mbi::libs::centreon::Service; +use gorgone::modules::centreon::mbi::libs::centreon::Timeperiod; +use gorgone::modules::centreon::mbi::libs::bi::BIHost; +use gorgone::modules::centreon::mbi::libs::bi::BIHostGroup; +use gorgone::modules::centreon::mbi::libs::bi::BIHostCategory; +use gorgone::modules::centreon::mbi::libs::bi::BIServiceCategory; +use gorgone::modules::centreon::mbi::libs::bi::BIService; +use gorgone::modules::centreon::mbi::libs::bi::BIMetric; +use gorgone::modules::centreon::mbi::libs::bi::Time; +use gorgone::modules::centreon::mbi::libs::bi::LiveService; +use gorgone::modules::centreon::mbi::libs::bi::DataQuality; + +my ($time, $liveService, $host, $service); +my ($hostBI, $biHost, $hostCentreon, $biService, $timePeriod, $biMetric); +my ($biHostgroup, $biServicecategory, $biHostcategory, $hostgroup, $servicecategory, $hostcategory, $biDataQuality); + +# Initialize objects for program +sub initVars { + my ($etlwk, %options) = @_; + + # instance of + $host = gorgone::modules::centreon::mbi::libs::centreon::Host->new($etlwk->{messages}, $etlwk->{dbbi_centreon_con}); + $hostcategory = gorgone::modules::centreon::mbi::libs::centreon::HostCategory->new($etlwk->{messages}, $etlwk->{dbbi_centreon_con}); + $servicecategory = gorgone::modules::centreon::mbi::libs::centreon::ServiceCategory->new($etlwk->{messages}, $etlwk->{dbbi_centreon_con}); + $hostgroup = gorgone::modules::centreon::mbi::libs::centreon::HostGroup->new($etlwk->{messages}, $etlwk->{dbbi_centreon_con}); + $service = gorgone::modules::centreon::mbi::libs::centreon::Service->new($etlwk->{messages}, $etlwk->{dbbi_centreon_con}); + $timePeriod = gorgone::modules::centreon::mbi::libs::centreon::Timeperiod->new($etlwk->{messages}, $etlwk->{dbbi_centreon_con}); + $biHost = gorgone::modules::centreon::mbi::libs::bi::BIHost->new($etlwk->{messages}, $etlwk->{dbbi_centstorage_con}); + $biHostgroup = gorgone::modules::centreon::mbi::libs::bi::BIHostGroup->new($etlwk->{messages}, $etlwk->{dbbi_centstorage_con}); + $biHostcategory = gorgone::modules::centreon::mbi::libs::bi::BIHostCategory->new($etlwk->{messages}, $etlwk->{dbbi_centstorage_con}); + $biServicecategory = gorgone::modules::centreon::mbi::libs::bi::BIServiceCategory->new($etlwk->{messages}, $etlwk->{dbbi_centstorage_con}); + $biService = gorgone::modules::centreon::mbi::libs::bi::BIService->new($etlwk->{messages}, $etlwk->{dbbi_centstorage_con}); + $time = gorgone::modules::centreon::mbi::libs::bi::Time->new($etlwk->{messages}, $etlwk->{dbbi_centstorage_con}); + $liveService = gorgone::modules::centreon::mbi::libs::bi::LiveService->new($etlwk->{messages}, $etlwk->{dbbi_centstorage_con}); + $biMetric = gorgone::modules::centreon::mbi::libs::bi::BIMetric->new($etlwk->{messages}, $etlwk->{dbbi_centstorage_con}); + $biDataQuality = gorgone::modules::centreon::mbi::libs::bi::DataQuality->new($etlwk->{messages}, $etlwk->{dbbi_centreon_con}); +} + +# temporary method to list liveservices for job configuration in Centreon +sub copyLiveServicesToMonitoringDB { + my ($etlwk, %options) = @_; + + return if ($etlwk->{dbmon_centstorage_con}->sameParams(%{$options{dbbi}->{centstorage}}) == 1); + + $etlwk->{dbmon_centstorage_con}->query("TRUNCATE TABLE mod_bi_liveservice"); + my $sth = $etlwk->{dbbi_centstorage_con}->query("SELECT id, name, timeperiod_id FROM mod_bi_liveservice"); + while (my $row = $sth->fetchrow_hashref()) { + my $insertQuery = "INSERT INTO mod_bi_liveservice (id, name, timeperiod_id) VALUES (". + $row->{'id'} . ",'" . $row->{name} . "'," . $row->{timeperiod_id} . ")"; + $etlwk->{dbmon_centstorage_con}->query($insertQuery); + } +} + +sub truncateDimensionTables { + my ($etlwk, %options) = @_; + + if ($options{options}->{rebuild} == 1 && $options{options}->{nopurge} == 0) { + $biHostgroup->truncateTable(); + $biHostcategory->truncateTable(); + $biServicecategory->truncateTable(); + $biHost->truncateTable(); + $biService->truncateTable(); + $biMetric->truncateTable(); + $time->truncateTable(); + $liveService->truncateTable(); + } +} + +sub denormalizeDimensionsFromCentreon { + my ($etlwk, %options) = @_; + + #set etlProperties for all dimensions object to be able to use it when filtering on hg/hc/sc + $host->setEtlProperties($options{etlProperties}); + $hostcategory->setEtlProperties($options{etlProperties}); + $servicecategory->setEtlProperties($options{etlProperties}); + $hostgroup->setEtlProperties($options{etlProperties}); + $service->setEtlProperties($options{etlProperties}); + + $etlwk->{messages}->writeLog("INFO", "Getting host properties from Centreon database"); + my $rows = $host->getHostGroupAndCategories(); + $etlwk->{messages}->writeLog("INFO", "Updating host dimension in Centstorage"); + if ($options{options}->{rebuild} == 1 && $options{options}->{nopurge} == 0) { + $biHost->insert($rows); + } else { + $biHost->update($rows, $options{etlProperties}->{'tmp.storage.memory'}); + } + + $etlwk->{messages}->writeLog("INFO", "Getting hostgroup properties from Centreon database"); + $rows = $hostgroup->getAllEntries(); + $etlwk->{messages}->writeLog("INFO", "Updating hostgroup dimension in Centstorage"); + $biHostgroup->insert($rows); + + $etlwk->{messages}->writeLog("INFO", "Getting hostcategories properties from Centreon database"); + $rows = $hostcategory->getAllEntries(); + $etlwk->{messages}->writeLog("INFO", "Updating hostcategories dimension in Centstorage"); + $biHostcategory->insert($rows); + + $etlwk->{messages}->writeLog("INFO", "Getting servicecategories properties from Centreon database"); + $rows = $servicecategory->getAllEntries(); + $etlwk->{messages}->writeLog("INFO", "Updating servicecategories dimension in Centstorage"); + $biServicecategory->insert($rows); + $etlwk->{messages}->writeLog("INFO", "Getting service properties from Centreon database"); + + my $hostRows = $biHost->getHostsInfo(); + my $serviceRows = $service->getServicesWithHostAndCategory($hostRows); + $etlwk->{messages}->writeLog("INFO", "Updating service dimension in Centstorage"); + if ($options{options}->{rebuild} == 1 && $options{options}->{nopurge} == 0) { + $biService->insert($serviceRows); + } else { + $biService->update($serviceRows, $options{etlProperties}->{'tmp.storage.memory'}); + } + + if (!defined($options{etlProperties}->{'statistics.type'}) || $options{etlProperties}->{'statistics.type'} ne 'availability') { + $etlwk->{messages}->writeLog("INFO", "Updating metric dimension in Centstorage"); + if ($options{options}->{rebuild} == 1 && $options{options}->{nopurge} == 0) { + $biMetric->insert(); + } else { + $biMetric->update($options{etlProperties}->{'tmp.storage.memory'}); + } + } + + # Getting live services to calculate reporting by time range + $etlwk->{messages}->writeLog("INFO", "Updating liveservice dimension in Centstorage"); + + my $timeperiods = $timePeriod->getPeriods($options{etlProperties}->{'liveservices.availability'}); + $liveService->insertList($timeperiods); + $timeperiods = $timePeriod->getPeriods($options{etlProperties}->{'liveservices.perfdata'}); + $liveService->insertList($timeperiods); + $timeperiods = $timePeriod->getCentilePeriods(); + $liveService->insertList($timeperiods); +} + +sub insertCentileParamToBIStorage{ + my ($etlwk, %options) = @_; + + my %result; + my $sth; + + #Insert potential missing time periods related to centile calculation in mod_bi_liveservices + $sth = $etlwk->{dbbi_centreon_con}->query("SELECT tp_id, tp_name FROM timeperiod WHERE tp_id IN (SELECT timeperiod_id FROM mod_bi_options_centiles)"); + while (my $row = $sth->fetchrow_hashref()) { + $result{$row->{tp_id}} = $row->{tp_name}; + } + + #If not time period is found in centile configuration, exit the function + if (%result eq 0){ + $etlwk->{messages}->writeLog("INFO", "No configuration found for centile calculation"); + return; + } + $etlwk->{messages}->writeLog("INFO", "Updating centile properties"); + + my $timeperiods = $timePeriod->getPeriods(\%result); + $liveService->insertList($timeperiods); + + #In case of rebuild, delete all centile parameters + if ($options{options}->{rebuild} == 1){ + $etlwk->{dbbi_centstorage_con}->query("TRUNCATE TABLE mod_bi_centiles"); + } + $sth = $etlwk->{dbbi_centreon_con}->query("select * from mod_bi_options_centiles"); + while (my $row = $sth->fetchrow_hashref()) { + my ($tpName,$liveServiceId) = $liveService->getLiveServicesByNameForTpId($row->{'timeperiod_id'}); + my $insertQuery = "INSERT IGNORE INTO mod_bi_centiles (id, centile_param, liveservice_id,tp_name) VALUES (".$row->{'id'}.",'".$row->{'centile_param'}."',".$liveServiceId.",'".$tpName."')"; + $etlwk->{dbbi_centstorage_con}->query($insertQuery); + } +} + +sub copyCentileToMonitoringDB { + my ($etlwk, %options) = @_; + + return if ($etlwk->{dbmon_centstorage_con}->sameParams(%{$options{dbbi}->{centstorage}}) == 1); + + $etlwk->{dbmon_centstorage_con}->query("TRUNCATE TABLE mod_bi_centiles"); + my $sth = $etlwk->{dbbi_centstorage_con}->query("SELECT id, centile_param, liveservice_id, tp_name FROM mod_bi_centiles"); + while (my $row = $sth->fetchrow_hashref()) { + my $insertQuery = "INSERT INTO mod_bi_centiles (id, centile_param, liveservice_id,tp_name) VALUES (". + $row->{id} . ",'" . $row->{centile_param} . "'," . $row->{liveservice_id} . ",'" . $row->{tp_name} . "')"; + $etlwk->{dbmon_centstorage_con}->query($insertQuery); + } +} + +sub startCbisAclSync{ + my ($etlwk, %options) = @_; + + # create a connecting socket + my $socket = new IO::Socket::INET( + PeerHost => 'localhost', + PeerPort => '1234', + Proto => 'tcp' + ); + + if (!$socket){ + $etlwk->{messages}->writeLog("WARNING", "Can't start ACL synchronization, make sure CBIS is started on port 1234"); + return 0; + } + #die "[ERROR] Cannot connect to CBIS on port 1234" unless $socket; + # XML ACL request + my $req = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n". + "<data>\n". + " <action type=\"updateResourceAcl\">\n". + " <task success=\"true\" message=\"Synchronizing resources ACL. It may take few minutes.\" />\n". + " </action>\n". + "</data>\n"; + $etlwk->{messages}->writeLog("INFO", "Send ACL synchronization signal to CBIS"); + my $size = $socket->send($req); + + # notify server that request has been sent + shutdown($socket, 1); + + # receive a response of up to 1024 characters from server + my $response = ""; + $socket->recv($response, 1024); + $socket->close(); +} + +sub execute { + my ($etlwk, %options) = @_; + + initVars($etlwk, %options); + + $biDataQuality->searchAndDeleteDuplicateEntries(); + if (!defined($options{options}->{centile}) || $options{options}->{centile} == 0) { + truncateDimensionTables($etlwk, %options); + denormalizeDimensionsFromCentreon($etlwk, %options); + copyLiveServicesToMonitoringDB($etlwk, %options); + } + + insertCentileParamToBIStorage($etlwk, %options); + copyCentileToMonitoringDB($etlwk, %options); + startCbisAclSync($etlwk, %options); +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/etlworkers/event/main.pm b/gorgone/gorgone/modules/centreon/mbi/etlworkers/event/main.pm new file mode 100644 index 00000000000..68fab49864c --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/etlworkers/event/main.pm @@ -0,0 +1,259 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::centreon::mbi::etlworkers::event::main; + +use strict; +use warnings; +use gorgone::modules::centreon::mbi::libs::centreon::Timeperiod; +use gorgone::modules::centreon::mbi::libs::bi::HostAvailability; +use gorgone::modules::centreon::mbi::libs::bi::ServiceAvailability; +use gorgone::modules::centreon::mbi::libs::bi::HGMonthAvailability; +use gorgone::modules::centreon::mbi::libs::bi::HGServiceMonthAvailability; +use gorgone::modules::centreon::mbi::libs::bi::Time; +use gorgone::modules::centreon::mbi::libs::bi::MySQLTables; +use gorgone::modules::centreon::mbi::libs::bi::BIHostStateEvents; +use gorgone::modules::centreon::mbi::libs::bi::BIServiceStateEvents; +use gorgone::modules::centreon::mbi::libs::bi::LiveService; +use gorgone::modules::centreon::mbi::libs::centstorage::HostStateEvents; +use gorgone::modules::centreon::mbi::libs::centstorage::ServiceStateEvents; +use gorgone::modules::centreon::mbi::libs::Utils; +use gorgone::standard::misc; + +my ($utils, $time, $tablesManager, $timePeriod); +my ($hostAv, $serviceAv); +my ($hgAv, $hgServiceAv); +my ($biHostEvents, $biServiceEvents); +my ($hostEvents, $serviceEvents); +my ($liveService); + +sub initVars { + my ($etlwk, %options) = @_; + + $utils = gorgone::modules::centreon::mbi::libs::Utils->new($etlwk->{messages}); + $timePeriod = gorgone::modules::centreon::mbi::libs::centreon::Timeperiod->new($etlwk->{messages}, $etlwk->{dbbi_centreon_con}); + $time = gorgone::modules::centreon::mbi::libs::bi::Time->new($etlwk->{messages}, $etlwk->{dbbi_centstorage_con}); + $tablesManager = gorgone::modules::centreon::mbi::libs::bi::MySQLTables->new($etlwk->{messages}, $etlwk->{dbbi_centstorage_con}); + $biHostEvents = gorgone::modules::centreon::mbi::libs::bi::BIHostStateEvents->new($etlwk->{messages}, $etlwk->{dbbi_centstorage_con}, $timePeriod); + $biServiceEvents = gorgone::modules::centreon::mbi::libs::bi::BIServiceStateEvents->new($etlwk->{messages}, $etlwk->{dbbi_centstorage_con}, $timePeriod); + $liveService = gorgone::modules::centreon::mbi::libs::bi::LiveService->new($etlwk->{messages}, $etlwk->{dbbi_centstorage_con}); + $hostEvents = gorgone::modules::centreon::mbi::libs::centstorage::HostStateEvents->new($etlwk->{messages}, $etlwk->{dbbi_centstorage_con}, $biHostEvents, $timePeriod); + $serviceEvents = gorgone::modules::centreon::mbi::libs::centstorage::ServiceStateEvents->new($etlwk->{messages}, $etlwk->{dbbi_centstorage_con}, $biServiceEvents, $timePeriod); + $hostAv = gorgone::modules::centreon::mbi::libs::bi::HostAvailability->new($etlwk->{messages}, $etlwk->{dbbi_centstorage_con}); + $serviceAv = gorgone::modules::centreon::mbi::libs::bi::ServiceAvailability->new($etlwk->{messages}, $etlwk->{dbbi_centstorage_con}); + $hgAv = gorgone::modules::centreon::mbi::libs::bi::HGMonthAvailability->new($etlwk->{messages}, $etlwk->{dbbi_centstorage_con}); + $hgServiceAv = gorgone::modules::centreon::mbi::libs::bi::HGServiceMonthAvailability->new($etlwk->{messages}, $etlwk->{dbbi_centstorage_con}); +} + +sub sql { + my ($etlwk, %options) = @_; + + return if (!defined($options{params}->{sql})); + + foreach (@{$options{params}->{sql}}) { + $etlwk->{messages}->writeLog('INFO', $_->[0]); + if ($options{params}->{db} eq 'centstorage') { + $etlwk->{dbbi_centstorage_con}->query($_->[1]); + } elsif ($options{params}->{db} eq 'centreon') { + $etlwk->{dbbi_centreon_con}->query($_->[1]); + } + } +} + +sub processEventsHosts { + my ($etlwk, %options) = @_; + + my $mode = 'daily'; + if ($options{options}->{rebuild} == 1) { + $tablesManager->emptyTableForRebuild($biHostEvents->getName(), $tablesManager->dumpTableStructure($biHostEvents->getName()), $biHostEvents->getTimeColumn()); + $mode = 'rebuild'; + } else { + $biHostEvents->deleteUnfinishedEvents(); + } + + if ($options{options}->{rebuild} == 1) { + $tablesManager->dropIndexesFromReportingTable('mod_bi_hoststateevents'); + } + + #Agreggate events by TP and store them into a temporary table (mod_bi_hoststateevents_tmp) + $etlwk->{messages}->writeLog("INFO", "[HOST] Processing host events"); + $hostEvents->agreggateEventsByTimePeriod( + $options{etlProperties}->{'liveservices.availability'}, + $options{start}, + $options{end}, + $options{liveServices}, + $mode + ); + + #Dump the result of aggregated data join to dimensions and load this to the final mod_bi_hoststateevents table + my $request = "INSERT INTO mod_bi_hoststateevents "; + $request .= " SELECT id, t1.modbiliveservice_id, t1.state, t1.start_time, t1.end_time, t1.duration, t1.sla_duration,"; + $request .= " t1.ack_time, t1.last_update from mod_bi_hoststateevents_tmp t1"; + $request .= " INNER JOIN mod_bi_tmp_today_hosts t2 on t1.host_id = t2.host_id"; + + $etlwk->{messages}->writeLog("INFO", "[HOST] Loading calculated events in reporting table"); + $etlwk->{dbbi_centstorage_con}->query($request); + + if ($options{options}->{rebuild} == 1 && $options{options}->{rebuild} == 0) { + $etlwk->{messages}->writeLog("DEBUG", "[HOST] Creating index"); + $etlwk->{dbbi_centstorage_con}->query('ALTER TABLE mod_bi_hoststateevents ADD INDEX `modbihost_id` (`modbihost_id`,`modbiliveservice_id`,`state`,`start_time`,`end_time`)'); + $etlwk->{dbbi_centstorage_con}->query('ALTER TABLE mod_bi_hoststateevents ADD INDEX `state` (`state`,`modbiliveservice_id`,`start_time`,`end_time`)'); + $etlwk->{dbbi_centstorage_con}->query('ALTER TABLE mod_bi_hoststateevents ADD INDEX `idx_mod_bi_hoststateevents_end_time` (`end_time`)'); + } +} + +sub processEventsServices { + my ($etlwk, %options) = @_; + + my $mode = 'daily'; + if ($options{options}->{rebuild} == 1) { + $tablesManager->emptyTableForRebuild($biServiceEvents->getName(), $tablesManager->dumpTableStructure($biServiceEvents->getName()), $biServiceEvents->getTimeColumn()); + $mode = 'rebuild'; + } else { + $biServiceEvents->deleteUnfinishedEvents(); + } + + if ($options{options}->{rebuild} == 1) { + $tablesManager->dropIndexesFromReportingTable('mod_bi_servicestateevents'); + } + + #Agreggate events by TP and store them into a temporary table (mod_bi_hoststateevents_tmp) + $etlwk->{messages}->writeLog("INFO", "[SERVICE] Processing service events"); + $serviceEvents->agreggateEventsByTimePeriod( + $options{etlProperties}->{'liveservices.availability'}, + $options{start}, + $options{end}, + $options{liveServices}, + $mode + ); + + #Dump the result of aggregated data join to dimensions and load this to the final mod_bi_hoststateevents table + my $request = "INSERT INTO mod_bi_servicestateevents "; + $request .= " SELECT id,t1.modbiliveservice_id,t1.state,t1.start_time,t1.end_time,t1.duration,t1.sla_duration,"; + $request .= " t1.ack_time,t1.last_update FROM mod_bi_servicestateevents_tmp t1 INNER JOIN mod_bi_tmp_today_services t2 "; + $request .= " ON t1.host_id = t2.host_id AND t1.service_id = t2.service_id"; + + $etlwk->{messages}->writeLog("INFO", "[SERVICE] Loading calculated events in reporting table"); + $etlwk->{dbbi_centstorage_con}->query($request); + + if ($options{options}->{rebuild} == 1 && $options{options}->{rebuild} == 0) { + $etlwk->{messages}->writeLog("DEBUG", "[SERVICE] Creating index"); + $etlwk->{dbbi_centstorage_con}->query('ALTER TABLE mod_bi_servicestateevents ADD INDEX `modbiservice_id` (`modbiservice_id`,`modbiliveservice_id`,`state`,`start_time`,`end_time`)'); + $etlwk->{dbbi_centstorage_con}->query('ALTER TABLE mod_bi_servicestateevents ADD INDEX `state` (`state`,`modbiliveservice_id`,`start_time`,`end_time`)'); + $etlwk->{dbbi_centstorage_con}->query('ALTER TABLE mod_bi_servicestateevents ADD INDEX `idx_mod_bi_servicestateevents_end_time` (`end_time`)'); + } +} + +sub events { + my ($etlwk, %options) = @_; + + initVars($etlwk, %options); + + my ($startTimeId, $startUtime) = $time->getEntryID($options{params}->{start}); + my ($endTimeId, $endUtime) = $time->getEntryID($options{params}->{end}); + + my $liveServices = $liveService->getLiveServicesByTpId(); + + if (defined($options{params}->{hosts}) && $options{params}->{hosts} == 1) { + processEventsHosts($etlwk, start => $startUtime, end => $endUtime, liveServices => $liveServices, %options); + } elsif (defined($options{params}->{services}) && $options{params}->{services} == 1) { + processEventsServices($etlwk, start => $startUtime, end => $endUtime, liveServices => $liveServices, %options); + } +} + +sub availabilityDayHosts { + my ($etlwk, %options) = @_; + + $etlwk->{messages}->writeLog("INFO", "[AVAILABILITY] Processing hosts day: $options{params}->{start} => $options{params}->{end} [$options{params}->{liveserviceName}]"); + my $ranges = $timePeriod->getTimeRangesForDay($options{startWeekDay}, $options{params}->{liveserviceName}, $options{startUtime}); + my $dayEvents = $biHostEvents->getDayEvents($options{startUtime}, $options{endUtime}, $options{params}->{liveserviceId}, $ranges); + $hostAv->insertStats($dayEvents, $options{startTimeId}, $options{params}->{liveserviceId}); +} + +sub availabilityDayServices { + my ($etlwk, %options) = @_; + + $etlwk->{messages}->writeLog("INFO", "[AVAILABILITY] Processing services day: $options{params}->{start} => $options{params}->{end} [$options{params}->{liveserviceName}]"); + my $ranges = $timePeriod->getTimeRangesForDay($options{startWeekDay}, $options{params}->{liveserviceName}, $options{startUtime}); + my $dayEvents = $biServiceEvents->getDayEvents($options{startUtime}, $options{endUtime}, $options{params}->{liveserviceId}, $ranges); + $serviceAv->insertStats($dayEvents, $options{startTimeId}, $options{params}->{liveserviceId}); +} + +sub availabilityMonthHosts { + my ($etlwk, %options) = @_; + + $etlwk->{messages}->writeLog("INFO", "[AVAILABILITY] Processing services month: $options{params}->{start} => $options{params}->{end}"); + my $data = $hostAv->getHGMonthAvailability($options{params}->{start}, $options{params}->{end}, $biHostEvents); + $hgAv->insertStats($options{startTimeId}, $data); +} + +sub availabilityMonthServices { + my ($etlwk, %options) = @_; + + $etlwk->{messages}->writeLog("INFO", "[AVAILABILITY] Processing hosts month: $options{params}->{start} => $options{params}->{end}"); + my $data = $serviceAv->getHGMonthAvailability_optimised($options{params}->{start}, $options{params}->{end}, $biServiceEvents); + $hgServiceAv->insertStats($options{startTimeId}, $data); +} + +sub availability { + my ($etlwk, %options) = @_; + + initVars($etlwk, %options); + + my ($startTimeId, $startUtime) = $time->getEntryID($options{params}->{start}); + my ($endTimeId, $endUtime) = $time->getEntryID($options{params}->{end}); + my $startWeekDay = $utils->getDayOfWeek($options{params}->{start}); + + if ($options{params}->{type} eq 'availability_day_hosts') { + availabilityDayHosts( + $etlwk, + startTimeId => $startTimeId, + startUtime => $startUtime, + endTimeId => $endTimeId, + endUtime => $endUtime, + startWeekDay => $startWeekDay, + %options + ); + } elsif ($options{params}->{type} eq 'availability_day_services') { + availabilityDayServices( + $etlwk, + startTimeId => $startTimeId, + startUtime => $startUtime, + endTimeId => $endTimeId, + endUtime => $endUtime, + startWeekDay => $startWeekDay, + %options + ); + } elsif ($options{params}->{type} eq 'availability_month_services') { + availabilityMonthServices( + $etlwk, + startTimeId => $startTimeId, + %options + ); + } elsif ($options{params}->{type} eq 'availability_month_hosts') { + availabilityMonthHosts( + $etlwk, + startTimeId => $startTimeId, + %options + ); + } +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/etlworkers/hooks.pm b/gorgone/gorgone/modules/centreon/mbi/etlworkers/hooks.pm new file mode 100644 index 00000000000..ec2004da8d8 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/etlworkers/hooks.pm @@ -0,0 +1,239 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::centreon::mbi::etlworkers::hooks; + +use warnings; +use strict; +use JSON::XS; +use gorgone::class::core; +use gorgone::standard::library; +use gorgone::standard::constants qw(:all); +use gorgone::modules::centreon::mbi::etlworkers::class; + +use constant NAMESPACE => 'centreon'; +use constant NAME => 'mbi-etlworkers'; +use constant EVENTS => [ + { event => 'CENTREONMBIETLWORKERSIMPORT' }, + { event => 'CENTREONMBIETLWORKERSDIMENSIONS' }, + { event => 'CENTREONMBIETLWORKERSEVENT' }, + { event => 'CENTREONMBIETLWORKERSPERFDATA' }, + { event => 'CENTREONMBIETLWORKERSREADY' } +]; + +my $config_core; +my $config; + +my $pools = {}; +my $pools_pid = {}; +my $rr_current = 0; +my $stop = 0; + +sub register { + my (%options) = @_; + + $config = $options{config}; + $config_core = $options{config_core}; + + $config->{pool} = defined($config->{pool}) && $config->{pool} =~ /(\d+)/ ? $1 : 8; + return (1, NAMESPACE, NAME, EVENTS); +} + +sub init { + my (%options) = @_; + + for my $pool_id (1..$config->{pool}) { + create_child(dbh => $options{dbh}, pool_id => $pool_id, logger => $options{logger}); + } +} + +sub routing { + my (%options) = @_; + + my $data; + eval { + $data = JSON::XS->new->utf8->decode($options{data}); + }; + if ($@) { + $options{logger}->writeLogError("[proxy] Cannot decode json data: $@"); + gorgone::standard::library::add_history( + dbh => $options{dbh}, + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + data => { message => NAME . ' - cannot decode json' }, + json_encode => 1 + ); + return undef; + } + + if ($options{action} eq 'CENTREONMBIETLWORKERSREADY') { + if (defined($data->{pool_id})) { + $pools->{ $data->{pool_id} }->{ready} = 1; + } + return undef; + } + + my $pool_id = rr_pool(); + if (!defined($pool_id)) { + gorgone::standard::library::add_history( + dbh => $options{dbh}, + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + data => { message => NAME . ' - no pool ready' }, + json_encode => 1 + ); + return undef; + } + + my $identity = 'gorgone-' . NAME . '-' . $pool_id; + + $options{gorgone}->send_internal_message( + identity => $identity, + action => $options{action}, + data => $options{data}, + token => $options{token} + ); +} + +sub gently { + my (%options) = @_; + + $stop = 1; + foreach my $pool_id (keys %$pools) { + if (defined($pools->{$pool_id}->{running}) && $pools->{$pool_id}->{running} == 1) { + $options{logger}->writeLogDebug("[" . NAME . "] Send TERM signal for pool '" . $pool_id . "'"); + CORE::kill('TERM', $pools->{$pool_id}->{pid}); + } + } +} + +sub kill { + my (%options) = @_; + + foreach (keys %{$pools}) { + if ($pools->{$_}->{running} == 1) { + $options{logger}->writeLogDebug("[" . NAME . "] Send KILL signal for pool '" . $_ . "'"); + CORE::kill('KILL', $pools->{$_}->{pid}); + } + } +} + +sub kill_internal { + my (%options) = @_; + +} + +sub check_create_child { + my (%options) = @_; + + return if ($stop == 1); + + # Check if we need to create a child + for my $pool_id (1..$config->{pool}) { + if (!defined($pools->{$pool_id})) { + create_child(dbh => $options{dbh}, pool_id => $pool_id, logger => $options{logger}); + } + } +} + +sub check { + my (%options) = @_; + + my $count = 0; + foreach my $pid (keys %{$options{dead_childs}}) { + # Not me + next if (!defined($pools_pid->{$pid})); + + # If someone dead, we recreate + my $pool_id = $pools_pid->{$pid}; + delete $pools->{$pools_pid->{$pid}}; + delete $pools_pid->{$pid}; + delete $options{dead_childs}->{$pid}; + if ($stop == 0) { + create_child(dbh => $options{dbh}, pool_id => $pool_id, logger => $options{logger}); + } + } + + check_create_child(dbh => $options{dbh}, logger => $options{logger}); + + foreach (keys %$pools) { + $count++ if ($pools->{$_}->{running} == 1); + } + + return ($count, 1); +} + +sub broadcast { + my (%options) = @_; + + foreach my $pool_id (keys %$pools) { + next if ($pools->{$pool_id}->{ready} != 1); + + $options{gorgone}->send_internal_message( + identity => 'gorgone-' . NAME . '-' . $pool_id, + action => $options{action}, + data => $options{data}, + token => $options{token} + ); + } +} + +# Specific functions +sub rr_pool { + my (%options) = @_; + + my ($loop, $i) = ($config->{pool}, 0); + while ($i <= $loop) { + $rr_current = $rr_current % $config->{pool}; + if ($pools->{$rr_current + 1}->{ready} == 1) { + $rr_current++; + return $rr_current; + } + $rr_current++; + $i++; + } + + return undef; +} + +sub create_child { + my (%options) = @_; + + $options{logger}->writeLogInfo("[" . NAME . "] Create module '" . NAME . "' child process for pool id '" . $options{pool_id} . "'"); + my $child_pid = fork(); + if ($child_pid == 0) { + $0 = 'gorgone-' . NAME; + my $module = gorgone::modules::centreon::mbi::etlworkers::class->new( + logger => $options{logger}, + module_id => NAME, + config_core => $config_core, + config => $config, + pool_id => $options{pool_id}, + container_id => $options{pool_id} + ); + $module->run(); + exit(0); + } + $options{logger}->writeLogDebug("[" . NAME . "] PID $child_pid (gorgone-" . NAME . ") for pool id '" . $options{pool_id} . "'"); + $pools->{$options{pool_id}} = { pid => $child_pid, ready => 0, running => 1 }; + $pools_pid->{$child_pid} = $options{pool_id}; +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/etlworkers/import/main.pm b/gorgone/gorgone/modules/centreon/mbi/etlworkers/import/main.pm new file mode 100644 index 00000000000..c3dc86a4182 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/etlworkers/import/main.pm @@ -0,0 +1,86 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::centreon::mbi::etlworkers::import::main; + +use strict; +use warnings; +use gorgone::standard::misc; +use File::Basename; + +sub sql { + my ($etlwk, %options) = @_; + + return if (!defined($options{params}->{sql})); + + foreach (@{$options{params}->{sql}}) { + $etlwk->{messages}->writeLog('INFO', $_->[0]); + if ($options{params}->{db} eq 'centstorage') { + $etlwk->{dbbi_centstorage_con}->query($_->[1]); + } elsif ($options{params}->{db} eq 'centreon') { + $etlwk->{dbbi_centreon_con}->query($_->[1]); + } + } +} + +sub command { + my ($etlwk, %options) = @_; + + return if (!defined($options{params}->{command}) || $options{params}->{command} eq ''); + + my ($error, $stdout, $return_code) = gorgone::standard::misc::backtick( + command => $options{params}->{command}, + timeout => 7200, + wait_exit => 1, + redirect_stderr => 1, + logger => $options{logger} + ); + + if ($error != 0) { + die $options{params}->{message} . ": execution failed: $stdout"; + } + + $etlwk->{messages}->writeLog('INFO', $options{params}->{message}); + $etlwk->{logger}->writeLogDebug("[mbi-etlworkers] succeeded command (code: $return_code): $stdout"); +} + +sub load { + my ($etlwk, %options) = @_; + + return if (!defined($options{params}->{file})); + + my ($file, $dir) = File::Basename::fileparse($options{params}->{file}); + + if (! -d "$dir" && ! -w "$dir") { + $etlwk->{messages}->writeLog('ERROR', "Cannot write into directory " . $dir); + } + + command($etlwk, params => { command => $options{params}->{dump}, message => $options{params}->{message} }); + + if ($options{params}->{db} eq 'centstorage') { + $etlwk->{dbbi_centstorage_con}->query($options{params}->{load}); + } elsif ($options{params}->{db} eq 'centreon') { + $etlwk->{dbbi_centreon_con}->query($options{params}->{load}); + } + + unlink($options{params}->{file}); +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/etlworkers/perfdata/main.pm b/gorgone/gorgone/modules/centreon/mbi/etlworkers/perfdata/main.pm new file mode 100644 index 00000000000..d82c6c4b52c --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/etlworkers/perfdata/main.pm @@ -0,0 +1,190 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::centreon::mbi::etlworkers::perfdata::main; + +use strict; +use warnings; + +use gorgone::modules::centreon::mbi::libs::centreon::Timeperiod; +use gorgone::modules::centreon::mbi::libs::centreon::CentileProperties; +use gorgone::modules::centreon::mbi::libs::bi::LiveService; +use gorgone::modules::centreon::mbi::libs::bi::Time; +use gorgone::modules::centreon::mbi::libs::Utils; +use gorgone::modules::centreon::mbi::libs::centstorage::Metrics; +use gorgone::modules::centreon::mbi::libs::bi::MetricDailyValue; +use gorgone::modules::centreon::mbi::libs::bi::MetricHourlyValue; +use gorgone::modules::centreon::mbi::libs::bi::MetricCentileValue; +use gorgone::modules::centreon::mbi::libs::bi::MetricMonthCapacity; +use gorgone::standard::misc; + +my ($utils, $time, $timePeriod, $centileProperties, $liveService); +my ($metrics); +my ($dayAgregates, $hourAgregates, $centileAgregates, $metricMonthCapacity); + +sub initVars { + my ($etlwk, %options) = @_; + + $timePeriod = gorgone::modules::centreon::mbi::libs::centreon::Timeperiod->new($etlwk->{messages}, $etlwk->{dbbi_centreon_con}); + $centileProperties = gorgone::modules::centreon::mbi::libs::centreon::CentileProperties->new($etlwk->{messages}, $etlwk->{dbbi_centreon_con}); + $liveService = gorgone::modules::centreon::mbi::libs::bi::LiveService->new($etlwk->{messages}, $etlwk->{dbbi_centstorage_con}); + $time = gorgone::modules::centreon::mbi::libs::bi::Time->new($etlwk->{messages}, $etlwk->{dbbi_centstorage_con}); + $utils = gorgone::modules::centreon::mbi::libs::Utils->new($etlwk->{messages}); + $metrics = gorgone::modules::centreon::mbi::libs::centstorage::Metrics->new($etlwk->{messages}, $etlwk->{dbbi_centstorage_con}, $options{pool_id}); + $dayAgregates = gorgone::modules::centreon::mbi::libs::bi::MetricDailyValue->new($etlwk->{messages}, $etlwk->{dbbi_centstorage_con}, $options{pool_id}); + $hourAgregates = gorgone::modules::centreon::mbi::libs::bi::MetricHourlyValue->new($etlwk->{messages}, $etlwk->{dbbi_centstorage_con}, $options{pool_id}); + $metricMonthCapacity = gorgone::modules::centreon::mbi::libs::bi::MetricMonthCapacity->new($etlwk->{messages}, $etlwk->{dbbi_centstorage_con}); + + $centileAgregates = gorgone::modules::centreon::mbi::libs::bi::MetricCentileValue->new( + logger => $etlwk->{messages}, + centstorage => $etlwk->{dbbi_centstorage_con}, + centreon => $etlwk->{dbbi_centreon_con}, + time => $time, + centileProperties => $centileProperties, + timePeriod => $timePeriod, + liveService => $liveService + ); +} + +sub sql { + my ($etlwk, %options) = @_; + + return if (!defined($options{params}->{sql})); + + foreach (@{$options{params}->{sql}}) { + $etlwk->{messages}->writeLog('INFO', $_->[0]); + if ($options{params}->{db} eq 'centstorage') { + $etlwk->{dbbi_centstorage_con}->query($_->[1]); + } elsif ($options{params}->{db} eq 'centreon') { + $etlwk->{dbbi_centreon_con}->query($_->[1]); + } + } +} + +sub perfdataDay { + my ($etlwk, %options) = @_; + + my ($currentDayId, $currentDayUtime) = $time->getEntryID($options{params}->{start}); + my $ranges = $timePeriod->getTimeRangesForDayByDateTime( + $options{params}->{liveserviceName}, + $options{params}->{start}, + $utils->getDayOfWeek($options{params}->{start}) + ); + if (scalar(@$ranges)) { + $etlwk->{messages}->writeLog("INFO", "[PERFDATA] Processing day: $options{params}->{start} => $options{params}->{end} [$options{params}->{liveserviceName}]"); + $metrics->getMetricsValueByDay($ranges, $options{etlProperties}->{'tmp.storage.memory'}); + $dayAgregates->insertValues($options{params}->{liveserviceId}, $currentDayId); + } +} + +sub perfdataMonth { + my ($etlwk, %options) = @_; + + my ($previousMonthStartTimeId, $previousMonthStartUtime) = $time->getEntryID($options{params}->{start}); + my ($previousMonthEndTimeId, $previousMonthEndUtime) = $time->getEntryID($options{params}->{end}); + + $etlwk->{messages}->writeLog("INFO", "[PERFDATA] Processing month: $options{params}->{start} => $options{params}->{end}"); + my $data = $dayAgregates->getMetricCapacityValuesOnPeriod($previousMonthStartTimeId, $previousMonthEndTimeId, $options{etlProperties}); + $metricMonthCapacity->insertStats($previousMonthStartTimeId, $data); +} + +sub perfdataHour { + my ($etlwk, %options) = @_; + + $etlwk->{messages}->writeLog("INFO", "[PERFDATA] Processing hours: $options{params}->{start} => $options{params}->{end}"); + + $metrics->getMetricValueByHour($options{params}->{start}, $options{params}->{end}, $options{etlProperties}->{'tmp.storage.memory'}); + $hourAgregates->insertValues(); +} + +sub perfdata { + my ($etlwk, %options) = @_; + + initVars($etlwk, %options); + + if ($options{params}->{type} eq 'perfdata_day') { + perfdataDay($etlwk, %options); + } elsif ($options{params}->{type} eq 'perfdata_month') { + perfdataMonth($etlwk, %options); + } elsif ($options{params}->{type} eq 'perfdata_hour') { + perfdataHour($etlwk, %options); + } +} + +sub centileDay { + my ($etlwk, %options) = @_; + + my ($currentDayId) = $time->getEntryID($options{params}->{start}); + + my $metricsId = $centileAgregates->getMetricsCentile(etlProperties => $options{etlProperties}); + $centileAgregates->calcMetricsCentileValueMultipleDays( + metricsId => $metricsId, + start => $options{params}->{start}, + end => $options{params}->{end}, + granularity => 'day', + timeId => $currentDayId + ); +} + +sub centileMonth { + my ($etlwk, %options) = @_; + + my ($previousMonthStartTimeId) = $time->getEntryID($options{params}->{start}); + + my $metricsId = $centileAgregates->getMetricsCentile(etlProperties => $options{etlProperties}); + $centileAgregates->calcMetricsCentileValueMultipleDays( + metricsId => $metricsId, + start => $options{params}->{start}, + end => $options{params}->{end}, + granularity => 'month', + timeId => $previousMonthStartTimeId + ); +} + +sub centileWeek { + my ($etlwk, %options) = @_; + + my ($currentDayId) = $time->getEntryID($options{params}->{start}); + + my $metricsId = $centileAgregates->getMetricsCentile(etlProperties => $options{etlProperties}); + $centileAgregates->calcMetricsCentileValueMultipleDays( + metricsId => $metricsId, + start => $options{params}->{start}, + end => $options{params}->{end}, + granularity => 'week', + timeId => $currentDayId + ); +} + +sub centile { + my ($etlwk, %options) = @_; + + initVars($etlwk, %options); + + if ($options{params}->{type} eq 'centile_day') { + centileDay($etlwk, %options); + } elsif ($options{params}->{type} eq 'centile_month') { + centileMonth($etlwk, %options); + } elsif ($options{params}->{type} eq 'centile_week') { + centileWeek($etlwk, %options); + } +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/Messages.pm b/gorgone/gorgone/modules/centreon/mbi/libs/Messages.pm new file mode 100644 index 00000000000..52fa032ae4c --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/Messages.pm @@ -0,0 +1,55 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; + +package gorgone::modules::centreon::mbi::libs::Messages; + +sub new { + my $class = shift; + my $self = {}; + + $self->{messages} = []; + + bless $self, $class; + return $self; +} + +sub writeLog { + my ($self, $severity, $message, $nodie) = @_; + + $severity = lc($severity); + + my %severities = ('debug' => 'D', 'info' => 'I', 'warning' => 'I', 'error' => 'E', 'fatal' => 'F'); + if ($severities{$severity} eq 'E' || $severities{$severity} eq 'F') { + die $message if (!defined($nodie) || $nodie == 0); + } + + push @{$self->{messages}}, [$severities{$severity}, $message]; +} + +sub getLogs { + my ($self) = @_; + + return $self->{messages}; +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/Utils.pm b/gorgone/gorgone/modules/centreon/mbi/libs/Utils.pm new file mode 100644 index 00000000000..51d0cb198b2 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/Utils.pm @@ -0,0 +1,251 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; +use POSIX; +use Time::Local; +use Tie::File; +use DateTime; + +package gorgone::modules::centreon::mbi::libs::Utils; + +sub new { + my $class = shift; + my $self = {}; + bless $self, $class; + + $self->{logger} = shift; + $self->{tz} = DateTime::TimeZone->new(name => 'local')->name(); + return $self; +} + +sub checkBasicOptions { + my ($self, $options) = @_; + + # check execution mode daily to extract yesterday data or rebuild to get more historical data + if (($options->{daily} == 0 && $options->{rebuild} == 0 && $options->{create_tables} == 0 && (!defined($options->{centile}) || $options->{centile} == 0)) + || ($options->{daily} == 1 && $options->{rebuild} == 1)) { + $self->{logger}->writeLogError("Specify one execution method. Check program help for more informations"); + return 1; + } + + # check if options are set correctly for rebuild mode + if (($options->{rebuild} == 1 || $options->{create_tables} == 1) + && ($options->{start} ne '' && $options->{end} eq '') + || ($options->{start} eq '' && $options->{end} ne '')) { + $self->{logger}->writeLogError("Specify both options --start and --end or neither of them to use default data retention options"); + return 1; + } + # check start and end dates format + if ($options->{rebuild} == 1 && $options->{start} ne '' && $options->{end} ne '' + && !$self->checkDateFormat($options->{start}, $options->{end})) { + $self->{logger}->writeLogError("Verify period start or end date format"); + return 1; + } + + return 0; +} + +sub buildCliMysqlArgs { + my ($self, $con) = @_; + + my $args = '-u "' . $con->{user} . '" ' . + '-p"' . $con->{password} . '" ' . + '-h "' . $con->{host} . '" ' . + '-P ' . $con->{port}; + return $args; +} + +sub getYesterdayTodayDate { + my ($self) = @_; + + my $dt = DateTime->from_epoch( + epoch => time(), + time_zone => $self->{tz} + ); + + my $month = $dt->month(); + $month = '0' . $month if ($month < 10); + my $day = $dt->day(); + $day = '0' . $day if ($day < 10); + my $today = $dt->year() . '-' . $month . '-' . $day; + + $dt->subtract(days => 1); + $month = $dt->month(); + $month = '0' . $month if ($month < 10); + $day = $dt->day(); + $day = '0' . $day if ($day < 10); + my $yesterday = $dt->year() . '-' . $month . '-' . $day; + + return ($yesterday, $today); +} + +sub subtractDateMonths { + my ($self, $date, $num) = @_; + + if ($date !~ /(\d{4})-(\d{2})-(\d{2})/) { + $self->{logger}->writeLog('ERROR', "Verify date format"); + } + + my $dt = DateTime->new(year => $1, month => $2, day => $3, hour => 0, minute => 0, second => 0, time_zone => $self->{tz})->subtract(months => $num); + + my $month = $dt->month(); + $month = '0' . $month if ($month < 10); + my $day = $dt->day(); + $day = '0' . $day if ($day < 10); + return $dt->year() . '-' . $month . '-' . $day; +} + +sub subtractDateDays { + my ($self, $date, $num) = @_; + + if ($date !~ /(\d{4})-(\d{2})-(\d{2})/) { + $self->{logger}->writeLog('ERROR', "Verify date format"); + } + + my $dt = DateTime->new(year => $1, month => $2, day => $3, hour => 0, minute => 0, second => 0, time_zone => $self->{tz})->subtract(days => $num); + + my $month = $dt->month(); + $month = '0' . $month if ($month < 10); + my $day = $dt->day(); + $day = '0' . $day if ($day < 10); + return $dt->year() . '-' . $month . '-' . $day; +} + +sub getDayOfWeek { + my ($self, $date) = @_; + + if ($date !~ /(\d{4})-(\d{2})-(\d{2})/) { + $self->{logger}->writeLog('ERROR', "Verify date format"); + } + + return lc(DateTime->new(year => $1, month => $2, day => $3, hour => 0, minute => 0, second => 0, time_zone => $self->{tz})->day_name()); +} + +sub getDateEpoch { + my ($self, $date) = @_; + + if ($date !~ /(\d{4})-(\d{2})-(\d{2})/) { + $self->{logger}->writeLog('ERROR', "Verify date format"); + } + + my $epoch = DateTime->new(year => $1, month => $2, day => $3, hour => 0, minute => 0, second => 0, time_zone => $self->{tz})->epoch(); + $date =~ s/-//g; + + return wantarray ? ($epoch, $date) : $epoch; +} + +sub getRangePartitionDate { + my ($self, $start, $end) = @_; + + if ($start !~ /(\d{4})-(\d{2})-(\d{2})/) { + $self->{logger}->writeLog('ERROR', "Verify period start format"); + } + my $dt1 = DateTime->new(year => $1, month => $2, day => $3, hour => 0, minute => 0, second => 0, time_zone => $self->{tz}); + + if ($end !~ /(\d{4})-(\d{2})-(\d{2})/) { + $self->{logger}->writeLog('ERROR', "Verify period end format"); + } + my $dt2 = DateTime->new(year => $1, month => $2, day => $3, hour => 0, minute => 0, second => 0, time_zone => $self->{tz}); + + my $epoch = $dt1->epoch(); + my $epoch_end = $dt2->epoch(); + if ($epoch_end <= $epoch) { + $self->{logger}->writeLog('ERROR', "Period end date is older"); + } + + my $partitions = []; + while ($epoch < $epoch_end) { + $dt1->add(days => 1); + + $epoch = $dt1->epoch(); + my $month = $dt1->month(); + $month = '0' . $month if ($month < 10); + my $day = $dt1->day(); + $day = '0' . $day if ($day < 10); + + push @$partitions, { + name => $dt1->year() . $month . $day, + date => $dt1->year() . '-' . $month . '-' . $day, + epoch => $epoch + }; + } + + return $partitions; +} + +sub checkDateFormat { + my ($self, $start, $end) = @_; + + if (defined($start) && $start =~ /[1-2][0-9]{3}\-[0-1][0-9]\-[0-3][0-9]/ + && defined($end) && $end =~ /[1-2][0-9]{3}\-[0-1][0-9]\-[0-3][0-9]/) { + return 1; + } + return 0; +} + +sub getRebuildPeriods { + my ($self, $start, $end) = @_; + + my ($day,$month,$year) = (localtime($start))[3,4,5]; + $start = POSIX::mktime(0,0,0,$day,$month,$year,0,0,-1); + my $previousDay = POSIX::mktime(0,0,0,$day - 1,$month,$year,0,0,-1); + my @days = (); + while ($start < $end) { + # if there is few hour gap (time change : winter/summer), we also readjust it + if ($start == $previousDay) { + $start = POSIX::mktime(0,0,0, ++$day, $month, $year,0,0,-1); + } + my $dayEnd = POSIX::mktime(0, 0, 0, ++$day, $month, $year, 0, 0, -1); + + my %period = ("start" => $start, "end" => $dayEnd); + $days[scalar(@days)] = \%period; + $previousDay = $start; + $start = $dayEnd; + } + return (\@days); +} + +#parseFlatFile (file, key,value) : replace a line with a key by a value (entire line) to the specified file +sub parseAndReplaceFlatFile{ + my $self = shift; + my $file = shift; + my $key = shift; + my $value = shift; + + if (!-e $file) { + $self->{logger}->writeLog('ERROR', "File missing [".$file."]. Make sure you installed all the pre-requisites before executing this script"); + } + + tie my @flatfile, 'Tie::File', $file or die $!; + + foreach my $line(@flatfile) + { + if( $line =~ m/$key/ ) { + my $previousLine = $line; + $line =~ s/$key/$value/g; + $self->{logger}->writeLog('DEBUG', "[".$file."]"); + $self->{logger}->writeLog('DEBUG', "Replacing [".$previousLine."] by [".$value."]"); + } + } +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHost.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHost.pm new file mode 100644 index 00000000000..114783fb194 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHost.pm @@ -0,0 +1,233 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; + +package gorgone::modules::centreon::mbi::libs::bi::BIHost; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database +sub new { + my $class = shift; + my $self = {}; + $self->{"logger"} = shift; + $self->{"centstorage"} = shift; + if (@_) { + $self->{"centreon"} = shift; + } + $self->{"today_table"} = "mod_bi_tmp_today_hosts"; + $self->{"tmp_comp"} = "mod_bi_tmp_hosts"; + $self->{"tmp_comp_storage"} = "mod_bi_tmp_hosts_storage"; + $self->{"table"} = "mod_bi_hosts"; + bless $self, $class; + return $self; +} + +sub getHostsInfo { + my $self = shift; + my $db = $self->{"centstorage"}; + + my $query = "SELECT `id`, `host_id`, `host_name`, `hc_id`, `hc_name`, `hg_id`, `hg_name`"; + $query .= " FROM `".$self->{"today_table"}."`"; + my $sth = $db->query($query); + my %result = (); + while (my $row = $sth->fetchrow_hashref()) { + if (defined($result{$row->{'host_id'}})) { + my $tab_ref = $result{$row->{'host_id'}}; + my @tab = @$tab_ref; + push @tab , $row->{"host_id"}.";".$row->{"host_name"}.";". + $row->{"hg_id"}.";".$row->{"hg_name"}.";". + $row->{"hc_id"}.";".$row->{"hc_name"}; + $result{$row->{'host_id'}} = \@tab; + }else { + my @tab = ($row->{"host_id"}.";".$row->{"host_name"}.";". + $row->{"hg_id"}.";".$row->{"hg_name"}.";". + $row->{"hc_id"}.";".$row->{"hc_name"}); + $result{$row->{'host_id'}} = \@tab; + } + } + $sth->finish(); + return (\%result); +} + +sub insert { + my $self = shift; + my $data = shift; + my $db = $self->{"centstorage"}; + $self->insertIntoTable("".$self->{"table"}."", $data); + $self->createTempTodayTable("false"); + my $fields = "id, host_name, host_id, hc_id, hc_name, hg_id, hg_name"; + my $query = "INSERT INTO ".$self->{"today_table"}." (".$fields.")"; + $query .= " SELECT ".$fields." FROM ".$self->{"table"}." "; + $db->query($query); +} + +sub update { + my ($self, $data, $useMemory) = @_; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + + $self->createTempComparisonTable($useMemory); + $self->insertIntoTable($self->{"tmp_comp"}, $data); + $self->createTempStorageTable($useMemory); + $self->joinNewAndCurrentEntries(); + $self->insertNewEntries(); + $db->query("DROP TABLE `".$self->{"tmp_comp_storage"}."`"); + $self->createTempTodayTable("false"); + $self->insertTodayEntries(); + $db->query("DROP TABLE `".$self->{"tmp_comp"}."`"); +} + +sub insertIntoTable { + my $self = shift; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + my $table = shift; + my $data = shift; + my $query = "INSERT INTO `".$table."`". + " (`host_id`, `host_name`, `hg_id`, `hg_name`, `hc_id`, `hc_name`)". + " VALUES (?,?,?,?,?,?)"; + my $sth = $db->prepare($query); + my $inst = $db->getInstance; + $inst->begin_work; + my $counter = 0; + + foreach (@$data) { + my ($host_id, $host_name, $hg_id, $hg_name, $hc_id, $hc_name) = split(";", $_); + $sth->bind_param(1, $host_id); + $sth->bind_param(2, $host_name); + $sth->bind_param(3, $hg_id); + $sth->bind_param(4, $hg_name); + $sth->bind_param(5, $hc_id); + $sth->bind_param(6, $hc_name); + $sth->execute; + if (defined($inst->errstr)) { + $logger->writeLog("FATAL", $self->{"table"}." insertion execute error : ".$inst->errstr); + } + if ($counter >= 1000) { + $counter = 0; + $inst->commit; + if (defined($inst->errstr)) { + $logger->writeLog("FATAL", $self->{"table"}." insertion commit error : ".$inst->errstr); + } + $inst->begin_work; + } + $counter++; + } + $inst->commit; +} + +sub createTempComparisonTable { + my ($self, $useMemory) = @_; + my $db = $self->{"centstorage"}; + $db->query("DROP TABLE IF EXISTS `".$self->{"tmp_comp"}."`"); + my $query = "CREATE TABLE `".$self->{"tmp_comp"}."` ("; + $query .= "`host_id` int(11) NOT NULL,`host_name` varchar(255) NOT NULL,"; + $query .= "`hc_id` int(11) DEFAULT NULL, `hc_name` varchar(255) NOT NULL,"; + $query .= "`hg_id` int(11) DEFAULT NULL, `hg_name` varchar(255) NOT NULL"; + if (defined($useMemory) && $useMemory eq "true") { + $query .= ") ENGINE=MEMORY DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;"; + }else { + $query .= ") ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;"; + } + $db->query($query); +} + +sub createTempStorageTable { + my ($self,$useMemory) = @_; + my $db = $self->{"centstorage"}; + + $db->query("DROP TABLE IF EXISTS `".$self->{"tmp_comp_storage"}."`"); + my $query = "CREATE TABLE `".$self->{"tmp_comp_storage"}."` ("; + $query .= "`id` INT NOT NULL,"; + $query .= "`host_id` int(11) NOT NULL,`host_name` varchar(255) NOT NULL,"; + $query .= "`hc_id` int(11) DEFAULT NULL, `hc_name` varchar(255) NOT NULL,"; + $query .= "`hg_id` int(11) DEFAULT NULL, `hg_name` varchar(255) NOT NULL,"; + $query .= " KEY `id` (`id`)"; + if (defined($useMemory) && $useMemory eq "true") { + $query .= ") ENGINE=MEMORY DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;"; + }else { + $query .= ") ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;"; + } + $db->query($query); +} + +sub createTempTodayTable { + my ($self,$useMemory) = @_; + my $db = $self->{"centstorage"}; + + $db->query("DROP TABLE IF EXISTS `".$self->{"today_table"}."`"); + my $query = "CREATE TABLE `".$self->{"today_table"}."` ("; + $query .= "`id` INT NOT NULL,"; + $query .= "`host_id` int(11) NOT NULL,`host_name` varchar(255) NOT NULL,"; + $query .= "`hc_id` int(11) DEFAULT NULL, `hc_name` varchar(255) NOT NULL,"; + $query .= "`hg_id` int(11) DEFAULT NULL, `hg_name` varchar(255) NOT NULL,"; + $query .= " KEY `id` (`host_id`)"; + if (defined($useMemory) && $useMemory eq "true") { + $query .= ") ENGINE=MEMORY DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;"; + }else { + $query .= ") ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;"; + } + $db->query($query); +} + +sub joinNewAndCurrentEntries { + my ($self) = @_; + my $db = $self->{"centstorage"}; + + my $query = "INSERT INTO ".$self->{"tmp_comp_storage"}. " (id, host_name, host_id, hc_id, hc_name, hg_id, hg_name)"; + $query .= " SELECT IFNULL(h.id, 0), t.host_name, t.host_id, t.hc_id, t.hc_name, t.hg_id, t.hg_name FROM ".$self->{"tmp_comp"}." t"; + $query .= " LEFT JOIN ".$self->{"table"}." h USING (host_name, host_id, hc_id, hc_name, hg_id, hg_name)"; + $db->query($query); +} + +sub insertNewEntries { + my ($self) = @_; + my $db = $self->{"centstorage"}; + my $fields = "host_name, host_id, hc_id, hc_name, hg_id, hg_name"; + my $query = " INSERT INTO `".$self->{"table"}."` (".$fields.") "; + $query .= " SELECT ".$fields." FROM ".$self->{"tmp_comp_storage"}; + $query .= " WHERE id = 0"; + $db->query($query); +} + +sub insertTodayEntries { + my ($self) = @_; + my $db = $self->{"centstorage"}; + my $fields = "host_name, host_id, hc_id, hc_name, hg_id, hg_name"; + my $query = "INSERT INTO ".$self->{"today_table"}." (id, host_name, host_id, hc_id, hc_name, hg_id, hg_name)"; + $query .= " SELECT h.id, t.host_name, t.host_id, t.hc_id, t.hc_name, t.hg_id, t.hg_name FROM ".$self->{"tmp_comp"}." t"; + $query .= " JOIN ".$self->{"table"}." h USING (host_name, host_id, hc_id, hc_name, hg_id, hg_name)"; + $db->query($query); +} + +sub truncateTable { + my $self = shift; + my $db = $self->{"centstorage"}; + + $db->query("TRUNCATE TABLE `".$self->{"table"}."`"); + $db->query("ALTER TABLE `".$self->{"table"}."` AUTO_INCREMENT=1"); +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHostCategory.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHostCategory.pm new file mode 100644 index 00000000000..dec1d24d88e --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHostCategory.pm @@ -0,0 +1,129 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; + +package gorgone::modules::centreon::mbi::libs::bi::BIHostCategory; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database +sub new { + my $class = shift; + my $self = {}; + $self->{"logger"} = shift; + $self->{"centstorage"} = shift; + if (@_) { + $self->{"centreon"} = shift; + } + bless $self, $class; + return $self; +} + +sub getAllEntries { + my $self = shift; + my $db = $self->{"centstorage"}; + + my $query = "SELECT `hc_id`, `hc_name`"; + $query .= " FROM `mod_bi_hostcategories`"; + my $sth = $db->query($query); + my @entries = (); + while (my $row = $sth->fetchrow_hashref()) { + push @entries, $row->{"hc_id"}.";".$row->{"hc_name"}; + } + $sth->finish(); + return (\@entries); +} + +sub getEntryIds { + my $self = shift; + my $db = $self->{"centstorage"}; + + my $query = "SELECT `id`, `hc_id`, `hc_name`"; + $query .= " FROM `mod_bi_hostcategories`"; + my $sth = $db->query($query); + my %entries = (); + while (my $row = $sth->fetchrow_hashref()) { + $entries{$row->{"hc_id"}.";".$row->{"hc_name"}} = $row->{"id"}; + } + $sth->finish(); + return (\%entries); +} + +sub entryExists { + my $self = shift; + my ($value, $entries) = (shift, shift); + foreach(@$entries) { + if ($value eq $_) { + return 1; + } + } + return 0; +} +sub insert { + my $self = shift; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + my $data = shift; + my $query = "INSERT INTO `mod_bi_hostcategories`". + " (`hc_id`, `hc_name`)". + " VALUES (?,?)"; + my $sth = $db->prepare($query); + my $inst = $db->getInstance; + $inst->begin_work; + my $counter = 0; + + my $existingEntries = $self->getAllEntries; + foreach (@$data) { + if (!$self->entryExists($_, $existingEntries)) { + my ($hc_id, $hc_name) = split(";", $_); + $sth->bind_param(1, $hc_id); + $sth->bind_param(2, $hc_name); + $sth->execute; + if (defined($inst->errstr)) { + $logger->writeLog("FATAL", "hostcategories insertion execute error : ".$inst->errstr); + } + if ($counter >= 1000) { + $counter = 0; + $inst->commit; + if (defined($inst->errstr)) { + $logger->writeLog("FATAL", "hostcategories insertion commit error : ".$inst->errstr); + } + $inst->begin_work; + } + $counter++; + } + } + $inst->commit; +} + +sub truncateTable { + my $self = shift; + my $db = $self->{"centstorage"}; + + my $query = "TRUNCATE TABLE `mod_bi_hostcategories`"; + $db->query($query); + $db->query("ALTER TABLE `mod_bi_hostcategories` AUTO_INCREMENT=1"); +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHostGroup.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHostGroup.pm new file mode 100644 index 00000000000..acc6e2d3f55 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHostGroup.pm @@ -0,0 +1,131 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; + +package gorgone::modules::centreon::mbi::libs::bi::BIHostGroup; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database +sub new { + my $class = shift; + my $self = {}; + $self->{"logger"} = shift; + $self->{"centstorage"} = shift; + if (@_) { + $self->{"centreon"} = shift; + } + bless $self, $class; + return $self; +} + + +sub getAllEntries { + my $self = shift; + my $db = $self->{"centstorage"}; + + my $query = "SELECT `id`, `hg_id`, `hg_name`"; + $query .= " FROM `mod_bi_hostgroups`"; + my $sth = $db->query($query); + my @entries = (); + while (my $row = $sth->fetchrow_hashref()) { + push @entries, $row->{"hg_id"}.";".$row->{"hg_name"}; + } + $sth->finish(); + return (\@entries); +} + +sub getEntryIds { + my $self = shift; + my $db = $self->{"centstorage"}; + + my $query = "SELECT `id`, `hg_id`, `hg_name`"; + $query .= " FROM `mod_bi_hostgroups`"; + my $sth = $db->query($query); + my %entries = (); + while (my $row = $sth->fetchrow_hashref()) { + $entries{$row->{"hg_id"}.";".$row->{"hg_name"}} = $row->{"id"}; + } + $sth->finish(); + return (\%entries); +} + +sub entryExists { + my $self = shift; + my ($value, $entries) = (shift, shift); + foreach(@$entries) { + if ($value eq $_) { + return 1; + } + } + return 0; +} +sub insert { + my $self = shift; + + my $db = $self->{centstorage}; + my $logger = $self->{logger}; + my $data = shift; + my $query = "INSERT INTO `mod_bi_hostgroups`". + " (`hg_id`, `hg_name`)". + " VALUES (?,?)"; + my $sth = $db->prepare($query); + my $inst = $db->getInstance(); + $inst->begin_work(); + my $counter = 0; + + my $existingEntries = $self->getAllEntries(); + foreach (@$data) { + if (!$self->entryExists($_, $existingEntries)) { + my ($hg_id, $hg_name) = split(";", $_); + $sth->bind_param(1, $hg_id); + $sth->bind_param(2, $hg_name); + $sth->execute; + if (defined($inst->errstr)) { + $logger->writeLog("FATAL", "hostgroups insertion execute error : ".$inst->errstr); + } + if ($counter >= 1000) { + $counter = 0; + $inst->commit; + if (defined($inst->errstr)) { + $logger->writeLog("FATAL", "hostgroups insertion commit error : ".$inst->errstr); + } + $inst->begin_work; + } + $counter++; + } + } + $inst->commit(); +} + +sub truncateTable { + my $self = shift; + my $db = $self->{"centstorage"}; + + my $query = "TRUNCATE TABLE `mod_bi_hostgroups`"; + $db->query($query); + $db->query("ALTER TABLE `mod_bi_hostgroups` AUTO_INCREMENT=1"); +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHostStateEvents.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHostStateEvents.pm new file mode 100644 index 00000000000..e6c44fcaa3b --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHostStateEvents.pm @@ -0,0 +1,240 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; + +package gorgone::modules::centreon::mbi::libs::bi::BIHostStateEvents; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database +sub new { + my $class = shift; + my $self = {}; + $self->{"logger"} = shift; + $self->{"centstorage"} = shift; + $self->{'timeperiod'} = shift; + $self->{'bind_counter'} = 0; + $self->{'statement'} = undef; + $self->{'name'} = "mod_bi_hoststateevents"; + $self->{'tmp_name'} = "mod_bi_hoststateevents_tmp"; + $self->{'timeColumn'} = "end_time"; + bless $self, $class; + return $self; +} + +sub getName() { + my $self = shift; + return $self->{'name'}; +} + +sub getTimeColumn() { + my $self = shift; + return $self->{'timeColumn'}; +} + +sub createTempBIEventsTable{ + my ($self) = @_; + my $db = $self->{"centstorage"}; + $db->query("DROP TABLE IF EXISTS `mod_bi_hoststateevents_tmp`"); + my $createTable = " CREATE TABLE `mod_bi_hoststateevents_tmp` ("; + $createTable .= " `host_id` int(11) NOT NULL,"; + $createTable .= " `modbiliveservice_id` tinyint(4) NOT NULL,"; + $createTable .= " `state` tinyint(4) NOT NULL,"; + $createTable .= " `start_time` int(11) NOT NULL,"; + $createTable .= " `end_time` int(11) DEFAULT NULL,"; + $createTable .= " `duration` int(11) NOT NULL,"; + $createTable .= " `sla_duration` int(11) NOT NULL,"; + $createTable .= " `ack_time` int(11) DEFAULT NULL,"; + $createTable .= " `last_update` tinyint(4) NOT NULL DEFAULT '0',"; + $createTable .= " KEY `modbihost_id` (`host_id`)"; + $createTable .= " ) ENGINE=InnoDB DEFAULT CHARSET=utf8"; + $db->query($createTable); +} + +sub prepareTempQuery { + my $self = shift; + my $db = $self->{"centstorage"}; + + my $query = "INSERT INTO `".$self->{'tmp_name'}."`". + " (`host_id`, `modbiliveservice_id`,". + " `state`, `start_time`, `sla_duration`,". + " `end_time`, `ack_time`, `last_update`, `duration`) ". + " VALUES (?,?,?,?,?,?,?,?, TIMESTAMPDIFF(SECOND, FROM_UNIXTIME(?), FROM_UNIXTIME(?)))"; + $self->{'statement'} = $db->prepare($query); + $self->{'dbinstance'} = $db->getInstance; + ($self->{'dbinstance'})->begin_work; +} + +sub prepareQuery { + my $self = shift; + my $db = $self->{"centstorage"}; + + my $query = "INSERT INTO `".$self->{'name'}."`". + " (`modbihost_id`, `modbiliveservice_id`,". + " `state`, `start_time`, `sla_duration`,". + " `end_time`, `ack_time`, `last_update`, `duration`) ". + " VALUES (?,?,?,?,?,?,?,?, TIMESTAMPDIFF(SECOND, FROM_UNIXTIME(?), FROM_UNIXTIME(?)))"; + $self->{'statement'} = $db->prepare($query); + $self->{'dbinstance'} = $db->getInstance; + ($self->{'dbinstance'})->begin_work; +} + +sub bindParam { + my ($self, $row) = @_; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + + my $size = scalar(@$row); + my $sth = $self->{'statement'}; + for (my $i = 0; $i < $size; $i++) { + $sth->bind_param($i + 1, $row->[$i]); + } + $sth->bind_param($size+1, $row->[3]); + $sth->bind_param($size+2, $row->[5]); + ($self->{'statement'})->execute; + if (defined(($self->{'dbinstance'})->errstr)) { + $logger->writeLog("FATAL", $self->{'name'}." insertion execute error : ".($self->{'dbinstance'})->errstr); + } + if ($self->{'bind_counter'} >= 1000) { + $self->{'bind_counter'} = 0; + ($self->{'dbinstance'})->commit; + if (defined(($self->{'dbinstance'})->errstr)) { + $logger->writeLog("FATAL", $self->{'name'}." insertion commit error : ".($self->{'dbinstance'})->errstr); + } + ($self->{'dbinstance'})->begin_work; + } + $self->{'bind_counter'} += 1; + +} + +sub getDayEvents { + my $self = shift; + my $db = $self->{"centstorage"}; + my $timeperiod = $self->{'timeperiod'}; + my ($start, $end, $liveserviceId, $ranges) = @_; + my %results = (); + + my $query = "SELECT start_time, end_time, state, modbihost_id"; + $query .= " FROM `".$self->{'name'}."`"; + $query .= " WHERE `start_time` < ".$end.""; + $query .= " AND `end_time` > ".$start.""; + $query .= " AND `state` in (0,1,2)"; + $query .= " AND modbiliveservice_id = ".$liveserviceId; + my $sth = $db->query($query); + + #For each events, for the current day, calculate statistics for the day + while (my $row = $sth->fetchrow_hashref()) { + + my $entryID = $row->{"modbihost_id"}; + + my ($started, $ended) = (0, 0); + my $rangeSize = scalar(@$ranges); + my $eventDuration = 0; + for(my $count = 0; $count < $rangeSize; $count++) { + my $currentStart = $row->{"start_time"}; + my $currentEnd = $row->{"end_time"}; + + my $range = $ranges->[$count]; + my ($rangeStart, $rangeEnd) = ($range->[0], $range->[1]); + if ($currentStart < $rangeEnd && $currentEnd > $rangeStart) { + if ($currentStart < $rangeStart) { + $currentStart = $rangeStart; + }elsif ($count == 0) { + $started = 1; + } + if ($currentEnd > $rangeEnd) { + $currentEnd = $rangeEnd; + }elsif ($count == $rangeSize - 1) { + $ended = 1; + } + $eventDuration += $currentEnd - $currentStart; + } + } + if (!defined($results{$entryID})) { + my @tab = (0, 0, 0, 0, 0, 0, 0); + + #New version - sync with tables in database + # 0: UP, 1: DOWN time, 2: Unreachable time , 3 : DOWN alerts opened + # 4: Down time alerts closed, 5: unreachable alerts started, 6 : unreachable alerts ended + $results{$entryID} = \@tab; + } + + my $stats = $results{$entryID}; + my $state = $row->{'state'}; + + if ($state == 0) { + $stats->[0] += $eventDuration; + }elsif ($state == 1) { + $stats->[1] += $eventDuration; + $stats->[3] += $started; + $stats->[4] += $ended; + }elsif ($state == 2) { + $stats->[2] += $eventDuration; + $stats->[5] += $started; + $stats->[6] += $ended; + } + + $results{$entryID} = $stats; + } + + return (\%results); +} + +#Deprecated +sub getNbEvents { + my ($self, $start, $end, $groupId, $catId, $liveServiceID) = @_; + my $db = $self->{"centstorage"}; + + my $query = "SELECT count(state) as nbEvents, state"; + $query .= " FROM mod_bi_hosts h, ".$self->{'name'}." e"; + $query .= " WHERE h.hg_id = ".$groupId." AND h.hc_id=".$catId; + $query .= " AND h.id = e.modbihost_id"; + $query .= " AND e.modbiliveservice_id=".$liveServiceID; + $query .= " AND start_time < UNIX_TIMESTAMP('".$end."')"; + $query .= " AND end_time > UNIX_TIMESTAMP('".$start."')"; + $query .= " AND state in (1,2)"; + $query .= " GROUP BY state"; + my $sth = $db->query($query); + + my ($downEvents, $unrEvents) = (undef, undef); + while (my $row = $sth->fetchrow_hashref()) { + if ($row->{'state'} == 1) { + $downEvents = $row->{'nbEvents'}; + }else { + $unrEvents = $row->{'nbEvents'}; + } + } + return ($downEvents, $unrEvents); +} + +sub deleteUnfinishedEvents { + my $self = shift; + my $db = $self->{"centstorage"}; + + my $query = "DELETE FROM `mod_bi_hoststateevents`"; + $query .= " WHERE last_update = 1 OR end_time is null"; + $db->query($query); +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIMetric.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIMetric.pm new file mode 100644 index 00000000000..4462aa3d322 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIMetric.pm @@ -0,0 +1,199 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; + +package gorgone::modules::centreon::mbi::libs::bi::BIMetric; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database +sub new { + my $class = shift; + my $self = {}; + + $self->{logger} = shift; + $self->{centstorage} = shift; + if (@_) { + $self->{centreon} = shift; + } + $self->{today_table} = "mod_bi_tmp_today_servicemetrics"; + $self->{tmpTable} = "mod_bi_tmp_servicemetrics"; + $self->{CRC32} = "mod_bi_tmp_servicemetrics_crc32"; + $self->{table} = "mod_bi_servicemetrics"; + + bless $self, $class; + return $self; +} + +sub insert { + my $self = shift; + my $db = $self->{centstorage}; + + $self->insertMetricsIntoTable("mod_bi_servicemetrics"); + $self->createTodayTable("false"); + my $query = "INSERT INTO ".$self->{today_table}. " (id, metric_id, metric_name, sc_id,hg_id,hc_id)"; + $query .= " SELECT id, metric_id, metric_name,sc_id,hg_id,hc_id FROM " . $self->{table} . " "; + $db->query($query); +} + +sub update { + my ($self,$useMemory) = @_; + + my $db = $self->{centstorage}; + + $self->createTempTable($useMemory); + $self->insertMetricsIntoTable($self->{tmpTable}); + $self->createCRC32Table(); + $self->insertNewEntries(); + $self->createCRC32Table(); + $self->createTodayTable("false"); + $self->insertTodayEntries(); + $db->query("DROP TABLE `".$self->{"tmpTable"}."`"); + $db->query("DROP TABLE `".$self->{"CRC32"}."`"); +} + +sub insertMetricsIntoTable { + my $self = shift; + my $db = $self->{"centstorage"}; + my $table = shift; + my $query = "INSERT INTO `".$table."` (`metric_id`, `metric_name`, `metric_unit`, `service_id`, `service_description`,"; + $query .= " `sc_id`, `sc_name`, `host_id`, `host_name`, `hc_id`, `hc_name`, `hg_id`, `hg_name`)"; + $query .= " SELECT `metric_id`, `metric_name`, `unit_name`, s.`service_id`, s.`service_description`, "; + $query .= " s.`sc_id`, s.`sc_name`, s.`host_id`, s.`host_name`, `hc_id`, `hc_name`, `hg_id`, `hg_name`"; + $query .= " FROM `mod_bi_tmp_today_services` s, `metrics` m, `index_data` i"; + $query .= " WHERE i.id = m.index_id and i.host_id=s.host_id and i.service_id=s.service_id"; + $query .= " group by s.hg_id, s.hc_id, s.sc_id, m.index_id, m.metric_id"; + my $sth = $db->query($query); + return $sth; +} + +sub createTempTable { + my ($self, $useMemory) = @_; + + my $db = $self->{"centstorage"}; + $db->query("DROP TABLE IF EXISTS `".$self->{"tmpTable"}."`"); + my $query = "CREATE TABLE `".$self->{"tmpTable"}."` ("; + $query .= "`metric_id` int(11) NOT NULL,`metric_name` varchar(255) NOT NULL,`metric_unit` char(10) DEFAULT NULL,"; + $query .= "`service_id` int(11) NOT NULL,`service_description` varchar(255) DEFAULT NULL,"; + $query .= "`sc_id` int(11) DEFAULT NULL,`sc_name` varchar(255) DEFAULT NULL,"; + $query .= "`host_id` int(11) DEFAULT NULL,`host_name` varchar(255) DEFAULT NULL,"; + $query .= "`hc_id` int(11) DEFAULT NULL,`hc_name` varchar(255) DEFAULT NULL,"; + $query .= "`hg_id` int(11) DEFAULT NULL,`hg_name` varchar(255) DEFAULT NULL"; + if (defined($useMemory) && $useMemory eq "true") { + $query .= ") ENGINE=MEMORY DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;"; + }else { + $query .= ") ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;"; + } + $db->query($query); +} + +sub createCRC32Table { + my ($self) = @_; + my $db = $self->{"centstorage"}; + + $db->query("DROP TABLE IF EXISTS `".$self->{"CRC32"}."`"); + my $query = "CREATE TABLE `".$self->{"CRC32"}."` CHARSET=utf8 COLLATE=utf8_general_ci"; + $query .= " SELECT `id`, CRC32(CONCAT_WS('-', COALESCE(metric_id, '?'),"; + $query .= " COALESCE(service_id, '?'),COALESCE(service_description, '?'),"; + $query .= " COALESCE(host_id, '?'),COALESCE(host_name, '?'), COALESCE(sc_id, '?'),COALESCE(sc_name, '?'),"; + $query .= " COALESCE(hc_id, '?'),COALESCE(hc_name, '?'), COALESCE(hg_id, '?'),COALESCE(hg_name, '?'))) as mycrc"; + $query .= " FROM ".$self->{"table"}; + $db->query($query); + $query = "ALTER TABLE `".$self->{"CRC32"}."` ADD INDEX (`mycrc`)"; + $db->query($query); +} + +sub insertNewEntries { + my ($self) = @_; + my $db = $self->{"centstorage"}; + my $fields = "metric_id, metric_name, metric_unit, service_id, service_description, host_name, host_id, sc_id, sc_name, hc_id, hc_name, hg_id, hg_name"; + my $tmpTableFields = "tmpTable.metric_id, tmpTable.metric_name,tmpTable.metric_unit,"; + $tmpTableFields .= " tmpTable.service_id, tmpTable.service_description, tmpTable.host_name, tmpTable.host_id, tmpTable.sc_id,"; + $tmpTableFields .= "tmpTable.sc_name, tmpTable.hc_id, tmpTable.hc_name, tmpTable.hg_id, tmpTable.hg_name"; + my $query = " INSERT INTO `".$self->{"table"}."` (".$fields.") "; + $query .= " SELECT ".$tmpTableFields." FROM ".$self->{"tmpTable"}." as tmpTable"; + $query .= " LEFT JOIN (".$self->{"CRC32"}. " INNER JOIN ".$self->{"table"}." as finalTable using (id))"; + $query .= " ON CRC32(CONCAT_WS('-', COALESCE(tmpTable.metric_id, '?'), COALESCE(tmpTable.service_id, '?'),COALESCE(tmpTable.service_description, '?'),"; + $query .= " COALESCE(tmpTable.host_id, '?'),COALESCE(tmpTable.host_name, '?'), COALESCE(tmpTable.sc_id, '?'),COALESCE(tmpTable.sc_name, '?'),"; + $query .= " COALESCE(tmpTable.hc_id, '?'),COALESCE(tmpTable.hc_name, '?'), COALESCE(tmpTable.hg_id, '?'),COALESCE(tmpTable.hg_name, '?'))) = mycrc"; + $query .= " AND tmpTable.metric_id=finalTable.metric_id"; + $query .= " AND tmpTable.service_id=finalTable.service_id AND tmpTable.service_description=finalTable.service_description"; + $query .= " AND tmpTable.host_id=finalTable.host_id AND tmpTable.host_name=finalTable.host_name"; + $query .= " AND tmpTable.sc_id=finalTable.sc_id AND tmpTable.sc_name=finalTable.sc_name"; + $query .= " AND tmpTable.hc_id=finalTable.hc_id AND tmpTable.hc_name=finalTable.hc_name"; + $query .= " AND tmpTable.hg_id=finalTable.hg_id AND tmpTable.hg_name=finalTable.hg_name"; + $query .= " WHERE finalTable.id is null"; + $db->query($query); +} + +sub createTodayTable { + my ($self,$useMemory) = @_; + my $db = $self->{"centstorage"}; + + $db->query("DROP TABLE IF EXISTS `".$self->{"today_table"}."`"); + my $query = "CREATE TABLE `" . $self->{"today_table"} . "` ("; + $query .= "`id` INT NOT NULL,"; + $query .= "`metric_id` int(11) NOT NULL,"; + $query .= "`metric_name` varchar(255) NOT NULL,"; + $query .= "`sc_id` int(11) NOT NULL,"; + $query .= "`hg_id` int(11) NOT NULL,"; + $query .= "`hc_id` int(11) NOT NULL,"; + $query .= " KEY `metric_id` (`metric_id`),"; + $query .= " KEY `schghc_id` (`sc_id`,`hg_id`,`hc_id`)"; + if (defined($useMemory) && $useMemory eq "true") { + $query .= ") ENGINE=MEMORY DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;"; + }else { + $query .= ") ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;"; + } + $db->query($query); +} + +sub insertTodayEntries { + my ($self) = @_; + my $db = $self->{"centstorage"}; + my $query = "INSERT INTO ".$self->{"today_table"}. " (id, metric_id, metric_name, sc_id,hg_id,hc_id)"; + $query .= " SELECT finalTable.id, finalTable.metric_id, finalTable.metric_name, finalTable.sc_id, finalTable.hg_id, finalTable.hc_id FROM ".$self->{"tmpTable"}." t"; + $query .= " LEFT JOIN (".$self->{"CRC32"}." INNER JOIN ".$self->{"table"}." finalTable USING (id))"; + $query .= " ON CRC32(CONCAT_WS('-', COALESCE(t.metric_id, '?'), COALESCE(t.service_id, '?'),COALESCE(t.service_description, '?'),"; + $query .= " COALESCE(t.host_id, '?'),COALESCE(t.host_name, '?'), COALESCE(t.sc_id, '?'),COALESCE(t.sc_name, '?'),"; + $query .= " COALESCE(t.hc_id, '?'),COALESCE(t.hc_name, '?'), COALESCE(t.hg_id, '?'),COALESCE(t.hg_name, '?'))) = mycrc"; + $query .= " AND finalTable.metric_id=t.metric_id"; + $query .= " AND finalTable.service_id=t.service_id AND finalTable.service_description=t.service_description "; + $query .= " AND finalTable.host_id=t.host_id AND finalTable.host_name=t.host_name "; + $query .= " AND finalTable.sc_id=t.sc_id AND finalTable.sc_name=t.sc_name "; + $query .= " AND finalTable.hc_id=t.hc_id AND finalTable.hc_name=t.hc_name "; + $query .= " AND finalTable.hg_id=t.hg_id AND finalTable.hg_name=t.hg_name "; + $db->query($query); +} + +sub truncateTable { + my $self = shift; + my $db = $self->{"centstorage"}; + + my $query = "TRUNCATE TABLE `".$self->{"table"}."`"; + $db->query($query); + $db->query("ALTER TABLE `".$self->{"table"}."` AUTO_INCREMENT=1"); +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIService.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIService.pm new file mode 100644 index 00000000000..df607375d3d --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIService.pm @@ -0,0 +1,221 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; + +package gorgone::modules::centreon::mbi::libs::bi::BIService; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database +sub new { + my $class = shift; + my $self = {}; + $self->{"logger"} = shift; + $self->{"centstorage"} = shift; + if (@_) { + $self->{"centreon"} = shift; + } + $self->{"today_table"} = "mod_bi_tmp_today_services"; + $self->{"tmpTable"} = "mod_bi_tmp_services"; + $self->{"CRC32"} = "mod_bi_tmp_services_crc32"; + $self->{"table"} = "mod_bi_services"; + + bless $self, $class; + return $self; +} + +sub insert { + my $self = shift; + my $data = shift; + my $db = $self->{"centstorage"}; + $self->insertIntoTable($self->{"table"}, $data); + $self->createTodayTable("false"); + my $fields = "id, service_id, service_description, host_name, host_id, sc_id, sc_name, hc_id, hc_name, hg_id, hg_name"; + my $query = "INSERT INTO ".$self->{"today_table"}. "(".$fields.")"; + $query .= " SELECT ".$fields." FROM ".$self->{"table"}; + $db->query($query); +} + +sub update { + my ($self, $data, $useMemory) = @_; + my $db = $self->{"centstorage"}; + + $self->createTempTable($useMemory); + $self->insertIntoTable($self->{"tmpTable"}, $data); + $self->createCRC32Table(); + $self->insertNewEntries(); + $self->createCRC32Table(); + $self->createTodayTable("false"); + $self->insertTodayEntries(); + $db->query("DROP TABLE `".$self->{"tmpTable"}."`"); + $db->query("DROP TABLE `".$self->{"CRC32"}."`"); +} + +sub insertIntoTable { + my $self = shift; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + my $table = shift; + my $data = shift; + my $name = shift; + my $id = shift; + my $query = "INSERT INTO `".$table."`". + " (`service_id`, `service_description`, `sc_id`, `sc_name`,". + " `host_id`, `host_name`,`hg_id`, `hg_name`, `hc_id`, `hc_name`)". + " VALUES (?,?,?,?,?,?,?,?,?,?)"; + my $sth = $db->prepare($query); + my $inst = $db->getInstance; + $inst->begin_work; + my $counter = 0; + + foreach (@$data) { + my ($service_id, $service_description, $sc_id, $sc_name, $host_id, $host_name, $hg_id, $hg_name, $hc_id, $hc_name) = split(";", $_); + $sth->bind_param(1, $service_id); + $sth->bind_param(2, $service_description); + $sth->bind_param(3, $sc_id); + $sth->bind_param(4, $sc_name); + $sth->bind_param(5, $host_id); + $sth->bind_param(6, $host_name); + $sth->bind_param(7, $hg_id); + $sth->bind_param(8, $hg_name); + $sth->bind_param(9, $hc_id); + $sth->bind_param(10, $hc_name); + $sth->execute; + if (defined($inst->errstr)) { + $logger->writeLog("FATAL", $table." insertion execute error : ".$inst->errstr); + } + if ($counter >= 1000) { + $counter = 0; + $inst->commit; + if (defined($inst->errstr)) { + $logger->writeLog("FATAL", $table." insertion commit error : ".$inst->errstr); + } + $inst->begin_work; + } + $counter++; + } + $inst->commit; +} +sub createTempTable { + my ($self, $useMemory) = @_; + my $db = $self->{"centstorage"}; + $db->query("DROP TABLE IF EXISTS `".$self->{"tmpTable"}."`"); + my $query = "CREATE TABLE `".$self->{"tmpTable"}."` ("; + $query .= "`service_id` int(11) NOT NULL,`service_description` varchar(255) NOT NULL,"; + $query .= "`sc_id` int(11) NOT NULL,`sc_name` varchar(255) NOT NULL,"; + $query .= "`host_id` int(11) DEFAULT NULL,`host_name` varchar(255) NOT NULL,"; + $query .= "`hc_id` int(11) DEFAULT NULL,`hc_name` varchar(255) NOT NULL,"; + $query .= "`hg_id` int(11) DEFAULT NULL,`hg_name` varchar(255) NOT NULL"; + if (defined($useMemory) && $useMemory eq "true") { + $query .= ") ENGINE=MEMORY DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;"; + }else { + $query .= ") ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;"; + } + $db->query($query); +} + +sub createCRC32Table { + my ($self) = @_; + my $db = $self->{"centstorage"}; + + $db->query("DROP TABLE IF EXISTS `".$self->{"CRC32"}."`"); + my $query = "CREATE TABLE `".$self->{"CRC32"}."` CHARSET=utf8 COLLATE=utf8_general_ci"; + $query .= " SELECT `id`, CRC32(CONCAT_WS('-', COALESCE(service_id, '?'),COALESCE(service_description, '?'),"; + $query .= " COALESCE(host_id, '?'),COALESCE(host_name, '?'), COALESCE(sc_id, '?'),COALESCE(sc_name, '?'),"; + $query .= " COALESCE(hc_id, '?'),COALESCE(hc_name, '?'), COALESCE(hg_id, '?'),COALESCE(hg_name, '?'))) as mycrc"; + $query .= " FROM ".$self->{"table"}; + $db->query($query); + $query = "ALTER TABLE `".$self->{"CRC32"}."` ADD INDEX (`mycrc`)"; + $db->query($query); +} + +sub insertNewEntries { + my ($self) = @_; + my $db = $self->{"centstorage"}; + my $fields = "service_id, service_description, host_name, host_id, sc_id, sc_name, hc_id, hc_name, hg_id, hg_name"; + my $tmpTableFields = "tmpTable.service_id, tmpTable.service_description, tmpTable.host_name, tmpTable.host_id, tmpTable.sc_id,"; + $tmpTableFields .= "tmpTable.sc_name, tmpTable.hc_id, tmpTable.hc_name, tmpTable.hg_id, tmpTable.hg_name"; + my $query = " INSERT INTO `".$self->{"table"}."` (".$fields.") "; + $query .= " SELECT ".$tmpTableFields." FROM ".$self->{"tmpTable"}." as tmpTable"; + $query .= " LEFT JOIN (".$self->{"CRC32"}. " INNER JOIN ".$self->{"table"}." as finalTable using (id))"; + $query .= " ON CRC32(CONCAT_WS('-', COALESCE(tmpTable.service_id, '?'),COALESCE(tmpTable.service_description, '?'),"; + $query .= " COALESCE(tmpTable.host_id, '?'),COALESCE(tmpTable.host_name, '?'), COALESCE(tmpTable.sc_id, '?'),COALESCE(tmpTable.sc_name, '?'),"; + $query .= " COALESCE(tmpTable.hc_id, '?'),COALESCE(tmpTable.hc_name, '?'), COALESCE(tmpTable.hg_id, '?'),COALESCE(tmpTable.hg_name, '?'))) = mycrc"; + $query .= " AND tmpTable.service_id=finalTable.service_id AND tmpTable.service_description=finalTable.service_description"; + $query .= " AND tmpTable.host_id=finalTable.host_id AND tmpTable.host_name=finalTable.host_name"; + $query .= " AND tmpTable.sc_id=finalTable.sc_id AND tmpTable.sc_name=finalTable.sc_name"; + $query .= " AND tmpTable.hc_id=finalTable.hc_id AND tmpTable.hc_name=finalTable.hc_name"; + $query .= " AND tmpTable.hg_id=finalTable.hg_id AND tmpTable.hg_name=finalTable.hg_name"; + $query .= " WHERE finalTable.id is null"; + $db->query($query); +} + +sub createTodayTable { + my ($self,$useMemory) = @_; + my $db = $self->{"centstorage"}; + + $db->query("DROP TABLE IF EXISTS `".$self->{"today_table"}."`"); + my $query = "CREATE TABLE `".$self->{"today_table"}."` ("; + $query .= "`id` INT NOT NULL,"; + $query .= "`service_id` int(11) NOT NULL,`service_description` varchar(255) NOT NULL,"; + $query .= "`sc_id` int(11) NOT NULL,`sc_name` varchar(255) NOT NULL,"; + $query .= "`host_id` int(11) DEFAULT NULL,`host_name` varchar(255) NOT NULL,"; + $query .= "`hc_id` int(11) DEFAULT NULL,`hc_name` varchar(255) NOT NULL,"; + $query .= "`hg_id` int(11) DEFAULT NULL,`hg_name` varchar(255) NOT NULL,"; + $query .= " KEY `host_service` (`host_id`, `service_id`)"; + if (defined($useMemory) && $useMemory eq "true") { + $query .= ") ENGINE=MEMORY DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;"; + }else { + $query .= ") ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;"; + } + $db->query($query); +} + +sub insertTodayEntries { + my ($self) = @_; + my $db = $self->{"centstorage"}; + my $query = "INSERT INTO ".$self->{"today_table"}. " (id, service_id, service_description, host_name, host_id, sc_id, sc_name, hc_id, hc_name, hg_id, hg_name)"; + $query .= " SELECT s.id, t.service_id, t.service_description, t.host_name, t.host_id, t.sc_id, t.sc_name, t.hc_id, t.hc_name, t.hg_id, t.hg_name FROM ".$self->{"tmpTable"}." t"; + $query .= " LEFT JOIN (".$self->{"CRC32"}." INNER JOIN ".$self->{"table"}." s USING (id))"; + $query .= " ON CRC32(CONCAT_WS('-', COALESCE(t.service_id, '?'),COALESCE(t.service_description, '?'),"; + $query .= " COALESCE(t.host_id, '?'),COALESCE(t.host_name, '?'), COALESCE(t.sc_id, '?'),COALESCE(t.sc_name, '?'),"; + $query .= " COALESCE(t.hc_id, '?'),COALESCE(t.hc_name, '?'), COALESCE(t.hg_id, '?'),COALESCE(t.hg_name, '?'))) = mycrc"; + $query .= " AND s.service_id=t.service_id AND s.service_description=t.service_description "; + $query .= " AND s.host_id=t.host_id AND s.host_name=t.host_name "; + $query .= " AND s.sc_id=t.sc_id AND s.sc_name=t.sc_name "; + $query .= " AND s.hc_id=t.hc_id AND s.hc_name=t.hc_name "; + $query .= " AND s.hg_id=t.hg_id AND s.hg_name=t.hg_name "; + $db->query($query); +} + +sub truncateTable { + my $self = shift; + my $db = $self->{"centstorage"}; + + my $query = "TRUNCATE TABLE `".$self->{"table"}."`"; + $db->query($query); + $db->query("ALTER TABLE `".$self->{"table"}."` AUTO_INCREMENT=1"); +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIServiceCategory.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIServiceCategory.pm new file mode 100644 index 00000000000..a95e7854baf --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIServiceCategory.pm @@ -0,0 +1,130 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; + +package gorgone::modules::centreon::mbi::libs::bi::BIServiceCategory; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database +sub new { + my $class = shift; + my $self = {}; + $self->{"logger"} = shift; + $self->{"centstorage"} = shift; + if (@_) { + $self->{"centreon"} = shift; + } + bless $self, $class; + return $self; +} + + +sub getAllEntries { + my $self = shift; + my $db = $self->{"centstorage"}; + + my $query = "SELECT `sc_id`, `sc_name`"; + $query .= " FROM `mod_bi_servicecategories`"; + my $sth = $db->query($query); + my @entries = (); + while (my $row = $sth->fetchrow_hashref()) { + push @entries, $row->{"sc_id"}.";".$row->{"sc_name"}; + } + $sth->finish(); + return (\@entries); +} + +sub getEntryIds { + my $self = shift; + my $db = $self->{"centstorage"}; + + my $query = "SELECT `id`, `sc_id`, `sc_name`"; + $query .= " FROM `mod_bi_servicecategories`"; + my $sth = $db->query($query); + my %entries = (); + while (my $row = $sth->fetchrow_hashref()) { + $entries{$row->{"sc_id"}.";".$row->{"sc_name"}} = $row->{"id"}; + } + $sth->finish(); + return (\%entries); +} + +sub entryExists { + my $self = shift; + my ($value, $entries) = (shift, shift); + foreach(@$entries) { + if ($value eq $_) { + return 1; + } + } + return 0; +} +sub insert { + my $self = shift; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + my $data = shift; + my $query = "INSERT INTO `mod_bi_servicecategories`". + " (`sc_id`, `sc_name`)". + " VALUES (?,?)"; + my $sth = $db->prepare($query); + my $inst = $db->getInstance; + $inst->begin_work; + my $counter = 0; + + my $existingEntries = $self->getAllEntries; + foreach (@$data) { + if (!$self->entryExists($_, $existingEntries)) { + my ($sc_id, $sc_name) = split(";", $_); + $sth->bind_param(1, $sc_id); + $sth->bind_param(2, $sc_name); + $sth->execute; + if (defined($inst->errstr)) { + $logger->writeLog("FATAL", "servicecategories insertion execute error : ".$inst->errstr); + } + if ($counter >= 1000) { + $counter = 0; + $inst->commit; + if (defined($inst->errstr)) { + $logger->writeLog("FATAL", "servicecategories insertion commit error : ".$inst->errstr); + } + $inst->begin_work; + } + $counter++; + } + } + $inst->commit; +} + +sub truncateTable { + my $self = shift; + my $db = $self->{"centstorage"}; + + my $query = "TRUNCATE TABLE `mod_bi_servicecategories`"; + $db->query($query); + $db->query("ALTER TABLE `mod_bi_servicecategories` AUTO_INCREMENT=1"); +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIServiceStateEvents.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIServiceStateEvents.pm new file mode 100644 index 00000000000..939d46c522e --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIServiceStateEvents.pm @@ -0,0 +1,247 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; + +package gorgone::modules::centreon::mbi::libs::bi::BIServiceStateEvents; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database +sub new { + my $class = shift; + my $self = {}; + $self->{"logger"} = shift; + $self->{"centstorage"} = shift; + $self->{'timeperiod'} = shift; + $self->{'bind_counter'} = 0; + $self->{'name'} = "mod_bi_servicestateevents"; + $self->{'tmp_name'} = "mod_bi_servicestateevents_tmp"; + $self->{'timeColumn'} = "end_time"; + bless $self, $class; + return $self; +} + +sub getName() { + my $self = shift; + return $self->{'name'}; +} + +sub getTimeColumn() { + my $self = shift; + return $self->{'timeColumn'}; +} + +sub prepareQuery { + my $self = shift; + my $db = $self->{"centstorage"}; + + my $query = "INSERT INTO `".$self->{'name'}."`". + " (`modbiservice_id`, `modbiliveservice_id`,". + " `state`, `start_time`, `sla_duration`,". + " `end_time`, `ack_time`, `last_update`, `duration`) ". + " VALUES (?,?,?,?,?,?,?,?, TIMESTAMPDIFF(SECOND, FROM_UNIXTIME(?), FROM_UNIXTIME(?)))"; + $self->{'statement'} = $db->prepare($query); + $self->{'dbinstance'} = $db->getInstance; + ($self->{'dbinstance'})->begin_work; +} + +sub createTempBIEventsTable { + my ($self) = @_; + my $db = $self->{"centstorage"}; + $db->query("DROP TABLE IF EXISTS `mod_bi_servicestateevents_tmp`"); + my $createTable = " CREATE TABLE `mod_bi_servicestateevents_tmp` ("; + $createTable .= " `host_id` int(11) NOT NULL,"; + $createTable .= " `service_id` int(11) NOT NULL,"; + $createTable .= " `modbiliveservice_id` tinyint(4) NOT NULL,"; + $createTable .= " `state` tinyint(4) NOT NULL,"; + $createTable .= " `start_time` int(11) NOT NULL,"; + $createTable .= " `end_time` int(11) DEFAULT NULL,"; + $createTable .= " `duration` int(11) NOT NULL,"; + $createTable .= " `sla_duration` int(11) NOT NULL,"; + $createTable .= " `ack_time` int(11) DEFAULT NULL,"; + $createTable .= " `last_update` tinyint(4) DEFAULT '0',"; + $createTable .= " KEY `modbiservice_id` (`host_id`,`service_id`)"; + $createTable .= " ) ENGINE=InnoDB DEFAULT CHARSET=utf8"; + $db->query($createTable); +} + +sub prepareTempQuery { + my $self = shift; + my $db = $self->{"centstorage"}; + + my $query = "INSERT INTO `".$self->{'tmp_name'}."`". + " (`host_id`,`service_id`,`modbiliveservice_id`,". + " `state`, `start_time`, `sla_duration`,". + " `end_time`, `ack_time`, `last_update`, `duration`) ". + " VALUES (?,?,?,?,?,?,?,?,?, TIMESTAMPDIFF(SECOND, FROM_UNIXTIME(?), FROM_UNIXTIME(?)))"; + $self->{'statement'} = $db->prepare($query); + $self->{'dbinstance'} = $db->getInstance; + ($self->{'dbinstance'})->begin_work; +} + +sub bindParam { + my ($self, $row) = @_; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + + my $size = scalar(@$row); + my $sth = $self->{'statement'}; + for (my $i = 0; $i < $size; $i++) { + $sth->bind_param($i + 1, $row->[$i]); + } + $sth->bind_param($size + 1, $row->[4]); + $sth->bind_param($size + 2, $row->[6]); + + ($self->{'statement'})->execute; + if (defined(($self->{'dbinstance'})->errstr)) { + $logger->writeLog("FATAL", $self->{'name'}." insertion execute error : ".($self->{'dbinstance'})->errstr); + } + if ($self->{'bind_counter'} >= 1000) { + $self->{'bind_counter'} = 0; + ($self->{'dbinstance'})->commit; + if (defined(($self->{'dbinstance'})->errstr)) { + $logger->writeLog("FATAL", $self->{'name'}." insertion commit error : ".($self->{'dbinstance'})->errstr); + } + ($self->{'dbinstance'})->begin_work; + } + $self->{'bind_counter'} += 1; + +} + +sub getDayEvents { + my $self = shift; + my $db = $self->{"centstorage"}; + my $timeperiod = $self->{'timeperiod'}; + my ($start, $end, $liveserviceId, $ranges) = @_; + my $liveServiceList = shift; + my %results = (); + + my $query = "SELECT start_time, end_time, state, modbiservice_id"; + $query .= " FROM `" . $self->{'name'} . "`"; + $query .= " WHERE `start_time` < " . $end; + $query .= " AND `end_time` > " . $start; + $query .= " AND `state` IN (0,1,2,3)"; + $query .= " AND modbiliveservice_id=" . $liveserviceId; + my $sth = $db->query($query); + + if (!scalar(@$ranges)) { + return \%results; + } + + while (my $row = $sth->fetchrow_hashref()) { + my $entryID = $row->{modbiservice_id}; + + my ($started, $ended) = (0,0); + my $rangeSize = scalar(@$ranges); + my $eventDuration = 0; + for (my $count = 0; $count < $rangeSize; $count++) { + my $currentStart = $row->{start_time}; + my $currentEnd = $row->{end_time}; + + my $range = $ranges->[$count]; + my ($rangeStart, $rangeEnd) = ($range->[0], $range->[1]); + if ($currentStart < $rangeEnd && $currentEnd > $rangeStart) { + if ($currentStart < $rangeStart) { + $currentStart = $rangeStart; + } elsif ($count == 0) { + $started = 1; + } + if ($currentEnd > $rangeEnd) { + $currentEnd = $rangeEnd; + } elsif ($count == $rangeSize - 1) { + $ended = 1; + } + $eventDuration += $currentEnd - $currentStart; + } + } + if (!defined($results{$entryID})) { + my @tab = (0, 0, 0, 0, 0, 0, 0, 0, 0); + + #New table - sync with the real table in centreon_storage database + # 0: OK time , 1: CRITICAL time, 2 : DEGRADED time 3 : alert_unavailable_opened + # 4: alert unavailable_closed 5 : alert_degraded_opened 6 : alertes_degraded_closed + # 7 : alert_unknown_opened 8 : alert_unknown_closed + $results{$entryID} = \@tab; + } + my $stats = $results{$entryID}; + my $state = $row->{state}; + if ($state == 0) { + $stats->[0] += $eventDuration; + } elsif ($state == 1) { + $stats->[2] += $eventDuration; + $stats->[5] += $started; + $stats->[6] += $ended; + } elsif ($state == 2) { + $stats->[1] += $eventDuration; + $stats->[3] += $started; + $stats->[4] += $ended; + } else { + $stats->[7] += $started; + $stats->[8] += $ended; + } + $results{$entryID} = $stats; + } + + return (\%results); +} + +#Deprecated +sub getNbEvents { + my ($self, $start, $end, $groupId, $hcatId, $scatId, $liveServiceID) = @_; + my $db = $self->{"centstorage"}; + + my $query = "SELECT count(state) as nbEvents, state"; + $query .= " FROM mod_bi_services s, ".$self->{'name'}." e"; + $query .= " WHERE s.hg_id = ".$groupId." AND s.hc_id=".$hcatId." AND s.sc_id=".$scatId; + $query .= " AND s.id = e.modbiservice_id"; + $query .= " AND start_time < UNIX_TIMESTAMP('".$end."')"; + $query .= " AND end_time > UNIX_TIMESTAMP('".$start."')"; + $query .= " AND e.modbiliveservice_id=".$liveServiceID; + $query .= " AND e.state in (1,2,3)"; + $query .= " GROUP BY e.state"; + my $sth = $db->query($query); + + my ($warnEvents, $criticalEvents, $otherEvents) = (undef, undef, undef); + while (my $row = $sth->fetchrow_hashref()) { + if ($row->{'state'} == 1) { + $warnEvents = $row->{'nbEvents'}; + }elsif ($row->{'state'} == 2) { + $criticalEvents = $row->{'nbEvents'}; + }else { + $otherEvents = $row->{'nbEvents'}; + } + } + return ($warnEvents, $criticalEvents, $otherEvents); +} + +sub deleteUnfinishedEvents { + my $self = shift; + my $db = $self->{"centstorage"}; + + my $query = "DELETE FROM `".$self->{'name'}."`"; + $query .= " WHERE last_update = 1 OR end_time is null"; + $db->query($query); +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/DBConfigParser.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/DBConfigParser.pm new file mode 100644 index 00000000000..6c5571b4807 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/DBConfigParser.pm @@ -0,0 +1,85 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; +use POSIX; +use XML::LibXML; +use Data::Dumper; + +package gorgone::modules::centreon::mbi::libs::bi::DBConfigParser; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database + +sub new { + my $class = shift; + my $self = {}; + $self->{"logger"} = shift; + bless $self, $class; + return $self; +} + +sub parseFile { + my $self = shift; + my $logger = $self->{"logger"}; + my $file = shift; + + my %connProfiles = (); + if (! -r $file) { + $logger->writeLog("ERROR", "Cannot read file ".$file); + } + my $parser = XML::LibXML->new(); + my $root = $parser->parse_file($file); + foreach my $profile ($root->findnodes('/DataTools.ServerProfiles/profile')) { + my $base = $profile->findnodes('@name'); + + foreach my $property ($profile->findnodes('./baseproperties/property')) { + my $name = $property->findnodes('@name')->to_literal; + my $value = $property->findnodes('@value')->to_literal; + if ($name eq 'odaURL') { + if ($value =~ /jdbc\:[a-z]+\:\/\/([^:]*)(\:\d+)?\/(.*)/) { + $connProfiles{$base."_host"} = $1; + if(defined($2) && $2 ne ''){ + $connProfiles{$base."_port"} = $2; + $connProfiles{$base."_port"} =~ s/\://; + }else{ + $connProfiles{$base."_port"} = '3306'; + } + $connProfiles{$base."_db"} = $3; + $connProfiles{$base."_db"} =~ s/\?autoReconnect\=true//; + } + } + if ($name eq 'odaUser') { + $connProfiles{$base."_user"} = sprintf('%s',$value); + } + if ($name eq 'odaPassword') { + $connProfiles{$base."_pass"} = sprintf('%s', $value); + } + } + } + + return (\%connProfiles); +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/DataQuality.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/DataQuality.pm new file mode 100644 index 00000000000..746ec7452a9 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/DataQuality.pm @@ -0,0 +1,99 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; + +package gorgone::modules::centreon::mbi::libs::bi::DataQuality; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database + +sub new { + my $class = shift; + my $self = {}; + + $self->{logger} = shift; + $self->{centreon} = shift; + bless $self, $class; + return $self; +} + +sub searchAndDeleteDuplicateEntries { + my $self = shift; + + $self->{logger}->writeLog("INFO", "Searching for duplicate host/service entries"); + my $relationIDS = $self->getDuplicateRelations(); + if (@$relationIDS) { + $self->deleteDuplicateEntries($relationIDS); + } +} + +# return table of IDs to delete +sub getDuplicateRelations { + my $self = shift; + + my @relationIDS; + #Get duplicated relations and exclude BAM or Metaservices data + my $duplicateEntriesQuery = "SELECT host_host_id, service_service_id, count(*) as nbRelations ". + "FROM host_service_relation t1, host t2 WHERE t1.host_host_id = t2.host_id ". + "AND t2.host_name not like '_Module%' group by host_host_id, service_service_id HAVING COUNT(*) > 1"; + + my $sth = $self->{centreon}->query($duplicateEntriesQuery); + while (my $row = $sth->fetchrow_hashref()) { + if (defined($row->{host_host_id})) { + $self->{logger}->writeLog( + "WARNING", + "Found the following duplicate data (host-service) : " . $row->{host_host_id}." - ".$row->{service_service_id}." - Cleaning data" + ); + #Get all relation IDs related to duplicated data + my $relationIdQuery = "SELECT hsr_id from host_service_relation ". + "WHERE host_host_id = ".$row->{host_host_id}." AND service_service_id = ".$row->{service_service_id}; + my $sth2 = $self->{centreon}->query($relationIdQuery); + while (my $hsr = $sth2->fetchrow_hashref()) { + if (defined($hsr->{hsr_id})) { + push(@relationIDS,$hsr->{hsr_id}); + } + } + $self->deleteDuplicateEntries(\@relationIDS); + @relationIDS = (); + } + } + return (\@relationIDS); +} + +# Delete N-1 duplicate entry +sub deleteDuplicateEntries { + my $self = shift; + + my @relationIDS = @{$_[0]}; + #WARNING : very important so at least 1 relation is kept + pop @relationIDS; + foreach (@relationIDS) { + my $idToDelete = $_; + my $deleteQuery = "DELETE FROM host_service_relation WHERE hsr_id = ".$idToDelete; + $self->{centreon}->query($deleteQuery) + } +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/Dumper.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/Dumper.pm new file mode 100644 index 00000000000..6f4d114f778 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/Dumper.pm @@ -0,0 +1,132 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; + +package gorgone::modules::centreon::mbi::libs::bi::Dumper; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database +sub new { + my $class = shift; + my $self = {}; + $self->{"logger"} = shift; + $self->{"centstorage"} = shift; + if (@_) { + $self->{"centreon"} = shift; + } + $self->{'tempFolder'} = "/tmp/"; + bless $self, $class; + return $self; +} + +sub setStorageDir { + my $self = shift; + my $logger = $self->{'logger'}; + my $tempFolder = shift; + + if (!defined($tempFolder)) { + $logger->writeLog("ERROR", "Temporary storage folder is not defined"); + } + if (! -d $tempFolder && ! -w $tempFolder) { + $logger->writeLog("ERROR", "Cannot write into directory ".$tempFolder); + } + if ($tempFolder !~ /\/$/) { + $tempFolder .= "/"; + } + $self->{'tempFolder'} = $tempFolder; +} + +# Dump data in a MySQL table. (db connection,table name, [not mandatory] start column, end column,start date,end date,exclude end time?) +# and return the file name created +# Ex $file = $dumper->dumpData($hostCentreon, 'toto', 'data_start', 'date_end', '2015-01-02', '2015-02-01', 0); +sub dumpData { + my $self = shift; + my $db = $self->{"centstorage"}; + my ($hostCentreon, $tableName) = (shift, shift); + my ($day,$month,$year,$hour,$min) = (localtime(time))[3,4,5,2,1]; + my $fileName = $self->{'tempFolder'}.$tableName; + my $query = "SELECT * FROM ".$tableName." "; + my $logger = $self->{'logger'}; + if (@_) { + my ($startColumn, $endColumn, $startTime, $endTime, $excludeEndTime) = @_; + $query .= " WHERE ".$startColumn." >= UNIX_TIMESTAMP('".$startTime."') "; + if ($excludeEndTime == 0) { + $query .= "AND ".$endColumn." <= UNIX_TIMESTAMP('".$endTime."')"; + }else { + $query .= "AND ".$endColumn." < UNIX_TIMESTAMP('".$endTime."')"; + } + } + my @loadCmdArgs = ('mysql', "-q", "-u", $hostCentreon->{'Censtorage_user'}, "-p".$hostCentreon->{'Censtorage_pass'}, + "-h", $hostCentreon->{'Censtorage_host'}, $hostCentreon->{'Censtorage_db'}, + "-e", $query.">".$fileName); + system("mysql -q -u".$hostCentreon->{'Censtorage_user'}." -p".$hostCentreon->{'Censtorage_pass'}." -P".$hostCentreon->{'Censtorage_port'}." -h".$hostCentreon->{'Censtorage_host'}. + " ".$hostCentreon->{'Censtorage_db'}." -e \"".$query."\" > ".$fileName); + $logger->writeLog("DEBUG","mysql -q -u".$hostCentreon->{'Censtorage_user'}." -p".$hostCentreon->{'Censtorage_pass'}." -P".$hostCentreon->{'Censtorage_port'}." -h".$hostCentreon->{'Censtorage_host'}. + " ".$hostCentreon->{'Censtorage_db'}." -e \"".$query."\" > ".$fileName); + return ($fileName); +} + +sub dumpRequest{ + my $self = shift; + my $db = $self->{"centstorage"}; + my ($hostCentreon, $requestName,$query) = (shift, shift,shift); + my $fileName = $self->{'tempFolder'}.$requestName; + my $logger = $self->{'logger'}; + system("mysql -q -u".$hostCentreon->{'Censtorage_user'}." -p".$hostCentreon->{'Censtorage_pass'}." -h".$hostCentreon->{'Censtorage_host'}. " -P".$hostCentreon->{'Censtorage_port'}. + " ".$hostCentreon->{'Censtorage_db'}." -e \"".$query."\" > ".$fileName); + return ($fileName); +} + +sub dumpTableStructure { + my $self = shift; + my $db = $self->{"centstorage"}; + my $logger = $self->{'logger'}; + my ($tableName) = (shift); + + my $sql = ""; + my $sth = $db->query("SHOW CREATE TABLE ".$tableName); + if (my $row = $sth->fetchrow_hashref()) { + $sql = $row->{'Create Table'}; + $sql =~ s/(CONSTRAINT.*\n)//g; + $sql =~ s/(\,\n\s+\))/\)/g; + }else { + $logger->writeLog("WARNING", "Cannot get structure for table : ".$tableName); + return (undef); + } + $sth->finish; + return ($sql); +} + +sub insertData { + my $self = shift; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + + my ($tableName, $inFile) = (shift, shift); + my $query = "LOAD DATA INFILE '".$inFile."' INTO TABLE `".$tableName."`"; + my $sth = $db->query($query); +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/HGMonthAvailability.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/HGMonthAvailability.pm new file mode 100644 index 00000000000..69157de8cdc --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/HGMonthAvailability.pm @@ -0,0 +1,92 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; + +package gorgone::modules::centreon::mbi::libs::bi::HGMonthAvailability; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database +sub new { + my $class = shift; + my $self = {}; + $self->{"logger"} = shift; + $self->{"centstorage"} = shift; + if (@_) { + $self->{"centreon"} = shift; + } + $self->{'name'} = "mod_bi_hgmonthavailability"; + $self->{'timeColumn'} = "time_id"; + bless $self, $class; + return $self; +} + +sub getName() { + my $self = shift; + return $self->{'name'}; +} + +sub getTimeColumn() { + my $self = shift; + return $self->{'timeColumn'}; +} + + +sub insertStats { + my $self = shift; + my ($time_id, $data) = @_; + my $insertParam = 1000; + + my $query_start = "INSERT INTO `".$self->{'name'}."`". + " (`time_id`, `modbihg_id`, `modbihc_id`, `liveservice_id`, `available`, `unavailable_time`,". + " `alert_unavailable_opened`, `alert_unavailable_closed`, ". + " `alert_unreachable_opened`, `alert_unreachable_closed`,". + " `alert_unavailable_total`, `alert_unreachable_total`,". + " `mtrs`, `mtbf`, `mtbsi`)". + " VALUES "; + my $counter = 0; + my $query = $query_start; + my $append = ''; + + foreach my $entry (@$data) { + my $size = scalar(@$entry); + $query .= $append . "($time_id"; + for (my $i = 0; $i < $size; $i++) { + $query .= ', ' . (defined($entry->[$i]) ? $entry->[$i] : 'NULL'); + } + $query .= ')'; + + $append = ','; + $counter++; + if ($counter >= $insertParam) { + $self->{centstorage}->query($query); + $query = $query_start; + $counter = 0; + $append = ''; + } + } + $self->{centstorage}->query($query) if ($counter > 0); +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/HGServiceMonthAvailability.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/HGServiceMonthAvailability.pm new file mode 100644 index 00000000000..b8d23c3b413 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/HGServiceMonthAvailability.pm @@ -0,0 +1,93 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; + +package gorgone::modules::centreon::mbi::libs::bi::HGServiceMonthAvailability; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database +sub new { + my $class = shift; + my $self = {}; + $self->{"logger"} = shift; + $self->{"centstorage"} = shift; + if (@_) { + $self->{"centreon"} = shift; + } + $self->{'name'} = "mod_bi_hgservicemonthavailability"; + $self->{'timeColumn'} = "time_id"; + bless $self, $class; + return $self; +} + +sub getName() { + my $self = shift; + return $self->{'name'}; +} + +sub getTimeColumn() { + my $self = shift; + return $self->{'timeColumn'}; +} + +sub insertStats { + my $self = shift; + my ($time_id, $data) = @_; + my $insertParam = 1000; + + my $query_start = "INSERT INTO `".$self->{'name'}."`". + " (`time_id`, `modbihg_id`, `modbihc_id`, `modbisc_id`, `liveservice_id`, `available`,". + " `unavailable_time`, `degraded_time`, `alert_unavailable_opened`, `alert_unavailable_closed`, ". + " `alert_degraded_opened`, `alert_degraded_closed`, ". + " `alert_other_opened`, `alert_other_closed`, ". + " `alert_degraded_total`, `alert_unavailable_total`,". + " `alert_other_total`, `mtrs`, `mtbf`, `mtbsi`)". + " VALUES "; + my $counter = 0; + my $query = $query_start; + my $append = ''; + + foreach my $entry (@$data) { + my $size = scalar(@$entry); + + $query .= $append . "($time_id"; + for (my $i = 0; $i < $size; $i++) { + $query .= ', ' . (defined($entry->[$i]) ? $entry->[$i] : 'NULL'); + } + $query .= ')'; + + $append = ','; + $counter++; + if ($counter >= $insertParam) { + $self->{centstorage}->query($query); + $query = $query_start; + $counter = 0; + $append = ''; + } + } + $self->{centstorage}->query($query) if ($counter > 0); +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/HostAvailability.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/HostAvailability.pm new file mode 100644 index 00000000000..e6a2d7bfe85 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/HostAvailability.pm @@ -0,0 +1,175 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; +use POSIX; +use Time::Local; + +package gorgone::modules::centreon::mbi::libs::bi::HostAvailability; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database +sub new { + my $class = shift; + my $self = {}; + $self->{"logger"} = shift; + $self->{"centstorage"} = shift; + if (@_) { + $self->{"centreon"} = shift; + } + $self->{"name"} = "mod_bi_hostavailability"; + $self->{"timeColumn"} = "time_id"; + $self->{"nbLinesInFile"} = 0; + $self->{"commitParam"} = 500000; + bless $self, $class; + return $self; +} + +sub getName() { + my $self = shift; + return $self->{'name'}; +} + +sub getTimeColumn() { + my $self = shift; + return $self->{'timeColumn'}; +} + +#Only for daily mode +sub insertStats { + my $self = shift; + my ($data, $time_id, $liveserviceId) = @_; + my $insertParam = 10000; + + my $query_start = "INSERT INTO `" . $self->{name} . "`". + " (`modbihost_id`, `time_id`, `liveservice_id`, `available`, ". + " `unavailable`,`unreachable`, `alert_unavailable_opened`, `alert_unavailable_closed`, ". + " `alert_unreachable_opened`, `alert_unreachable_closed`) ". + " VALUES "; + my $counter = 0; + my $query = $query_start; + my $append = ''; + + while (my ($modBiHostId, $stats) = each %$data) { + my @tab = @$stats; + if ($stats->[0] + $stats->[1] + $stats->[2] == 0) { + next; + } + + $query .= $append . "($modBiHostId, $time_id, $liveserviceId"; + for (my $i = 0; $i < scalar(@$stats); $i++) { + $query .= ', ' . $stats->[$i]; + } + $query .= ')'; + + $append = ','; + $counter++; + if ($counter >= $insertParam) { + $self->{centstorage}->query($query); + $query = $query_start; + $counter = 0; + $append = ''; + } + } + $self->{centstorage}->query($query) if ($counter > 0); +} + +sub saveStatsInFile { + my $self = shift; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + my ($data, $time_id, $liveserviceId,$fh) = @_; + my $query; + my $row; + + while (my ($modBiHostId, $stats) = each %$data) { + my @tab = @$stats; + if ($stats->[0]+$stats->[1]+$stats->[4] == 0) { + next; + } + + #Filling the dump file with data + $row = $modBiHostId."\t".$time_id."\t".$liveserviceId; + for (my $i = 0; $i < scalar(@$stats); $i++) { + $row.= "\t".$stats->[$i] + } + $row .= "\n"; + + #Write row into file + print $fh $row; + $self->{"nbLinesInFile"}+=1; + } +} + +sub getCurrentNbLines{ + my $self = shift; + return $self->{"nbLinesInFile"}; +} + +sub getCommitParam{ + my $self = shift; + return $self->{"commitParam"}; +} +sub setCurrentNbLines{ + my $self = shift; + my $nbLines = shift; + $self->{"nbLinesInFile"} = $nbLines; +} + +sub getHGMonthAvailability { + my ($self, $start, $end, $eventObj) = @_; + my $db = $self->{"centstorage"}; + + $self->{"logger"}->writeLog("DEBUG","[HOST] Calculating availability for hosts"); + my $query = "SELECT h.hg_id, h.hc_id, hc.id as cat_id, hg.id as group_id, ha.liveservice_id, avg(available/(available+unavailable+unreachable)) as av_percent,"; + $query .= " sum(available) as av_time, sum(unavailable) as unav_time, sum(alert_unavailable_opened) as unav_opened, sum(alert_unavailable_closed) as unav_closed,"; + $query .= " sum(alert_unreachable_opened) as unr_opened, sum(alert_unreachable_closed) as unr_closed"; + $query .= " FROM ".$self->{"name"}." ha"; + $query .= " STRAIGHT_JOIN mod_bi_time t ON (t.id = ha.time_id )"; + $query .= " STRAIGHT_JOIN mod_bi_hosts h ON (ha.modbihost_id = h.id)"; + $query .= " STRAIGHT_JOIN mod_bi_hostgroups hg ON (h.hg_name=hg.hg_name AND h.hg_id=hg.hg_id)"; + $query .= " STRAIGHT_JOIN mod_bi_hostcategories hc ON (h.hc_name=hc.hc_name AND h.hc_id=hc.hc_id)"; + $query .= " WHERE t.year = YEAR('".$start."') AND t.month = MONTH('".$start."') and t.hour=0"; + $query .= " GROUP BY h.hg_id, h.hc_id, ha.liveservice_id"; + my $sth = $db->query($query); + + $self->{"logger"}->writeLog("DEBUG","[HOST] Calculating MTBF/MTRS/MTBSI for Host"); + my @data = (); + while (my $row = $sth->fetchrow_hashref()) { + my ($totalDownEvents, $totalUnrEvents) = $eventObj->getNbEvents($start, $end, $row->{'hg_id'}, $row->{'hc_id'}, $row->{'liveservice_id'}); + my ($mtrs, $mtbf, $mtbsi) = (undef, undef, undef); + if (defined($totalDownEvents) && $totalDownEvents != 0) { + $mtrs = $row->{'unav_time'}/$totalDownEvents; + $mtbf = $row->{'av_time'}/$totalDownEvents; + $mtbsi = ($row->{'unav_time'}+$row->{'av_time'})/$totalDownEvents; + } + my @tab = ($row->{'group_id'}, $row->{'cat_id'}, $row->{'liveservice_id'}, $row->{'av_percent'}, $row->{'unav_time'}, + $row->{'unav_opened'}, $row->{'unav_closed'}, $row->{'unr_opened'}, $row->{'unr_closed'}, + $totalDownEvents, $totalUnrEvents, $mtrs, $mtbf, $mtbsi); + push @data, \@tab; + } + + return \@data; +} +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/LiveService.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/LiveService.pm new file mode 100644 index 00000000000..18849c177b5 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/LiveService.pm @@ -0,0 +1,223 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; + +package gorgone::modules::centreon::mbi::libs::bi::LiveService; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database +sub new { + my $class = shift; + my $self = {}; + + $self->{logger} = shift; + $self->{centstorage} = shift; + if (@_) { + $self->{centreon} = shift; + } + bless $self, $class; + return $self; +} + +sub getLiveServicesByName { + my $self = shift; + my $db = $self->{"centstorage"}; + my $name = shift; + my $interval = shift; + my $query = "SELECT `id`, `name`"; + $query .= " FROM `mod_bi_liveservice`"; + $query .= " WHERE `name` like '".$name."%'"; + my $sth = $db->query($query); + my %result = (); + while (my $row = $sth->fetchrow_hashref()) { + $result{ $row->{name} } = $row->{id}; + } + return (\%result); +} + +sub getLiveServicesByTpId { + my $self = shift; + my $db = $self->{"centstorage"}; + my $name = shift; + my $interval = shift; + my $query = "SELECT `id`, `timeperiod_id`"; + $query .= " FROM `mod_bi_liveservice` "; + my $sth = $db->query($query); + my %result = (); + while (my $row = $sth->fetchrow_hashref()) { + $result{$row->{'timeperiod_id'}} = $row->{"id"}; + } + return (\%result); +} + +sub getLiveServicesByNameForTpId { + my $self = shift; + my $db = $self->{"centstorage"}; + my $tpId = shift; + my $query = "SELECT `id`, `name`"; + $query .= " FROM `mod_bi_liveservice` "; + $query .= "WHERE timeperiod_id = ".$tpId; + my $sth = $db->query($query); + my ($name, $id); + + while (my $row = $sth->fetchrow_hashref()) { + ($name, $id) = ($row->{'name'}, $row->{'id'}); + } + return ($name,$id); +} + +sub getLiveServiceIdsInString { + my $self = shift; + my $db = $self->{"centstorage"}; + my $logger = $self->{'logger'}; + my $ids = shift; + + my $idStr = ""; + + my $query = "SELECT `id`"; + $query .= " FROM mod_bi_liveservice"; + $query .= " WHERE timeperiod_id IN (".$ids.")"; + my $sth = $db->query($query); + my %result = (); + while (my $row = $sth->fetchrow_hashref()) { + $idStr .= $row->{'id'}.","; + } + $idStr =~ s/\,$//; + return $idStr; +} + +sub getLiveServicesByNameForTpIds { + my $self = shift; + my $db = $self->{"centstorage"}; + my $ids = shift; + + my $idStr = ""; + + foreach my $key (keys %$ids) { + if ($idStr eq "") { + $idStr .= $key; + }else { + $idStr .= ",".$key; + } + } + if ($idStr eq "") { + $self->{logger}->writeLog("ERROR", "Select a timeperiod in the ETL configuration menu"); + } + my $query = "SELECT `id`, `name`"; + $query .= " FROM mod_bi_liveservice"; + $query .= " WHERE timeperiod_id IN (".$idStr.")"; + my $sth = $db->query($query); + my %result = (); + while (my $row = $sth->fetchrow_hashref()) { + $result{ $row->{name} } = $row->{id}; + } + return \%result; +} + +sub getTimeperiodName { + my $self = shift; + my $db = $self->{"centstorage"}; + + my $id = shift; + my $query = "SELECT name FROM mod_bi_liveservice WHERE timeperiod_id=".$id; + my $sth = $db->query($query); + my $name = ""; + if (my $row = $sth->fetchrow_hashref()) { + $name = $row->{'name'}; + } + return($name); +} + +sub getTimeperiodId { + my $self = shift; + my $db = $self->{"centstorage"}; + + my $name = shift; + my $query = "SELECT timeperiod_id FROM mod_bi_liveservice WHERE name='".$name."'"; + my $sth = $db->query($query); + my $id = 0; + if (my $row = $sth->fetchrow_hashref()) { + $id = $row->{'timeperiod_id'}; + } + return($id); +} + +sub insert { + my $self = shift; + my $db = $self->{"centstorage"}; + + my $name = shift; + my $id = shift; + my $query = "INSERT INTO `mod_bi_liveservice` (`name`, `timeperiod_id`) VALUES ('".$name."', ".$id.")"; + my $sth = $db->query($query); +} + +sub insertList { + my $self = shift; + my $db = $self->{"centstorage"}; + my $list = shift; + + while (my ($id, $name) = each %$list) { + my $tpName = $self->getTimeperiodName($id); + my $tpId = $self->getTimeperiodId($name); + if ($tpName ne "" && $name ne $tpName) { + $self->updateById($id, $name); + }elsif ($tpId > 0 && $tpId != $id) { + $self->update($name, $id); + }elsif ($tpId == 0 && $tpName eq "") { + $self->insert($name, $id); + } + } +} + +sub update { + my $self = shift; + my $db = $self->{"centstorage"}; + + my $name = shift; + my $id = shift; + my $query = "UPDATE `mod_bi_liveservice` SET `timeperiod_id`=".$id." WHERE name='".$name."'"; + $db->query($query); +} + +sub updateById { + my $self = shift; + my $db = $self->{"centstorage"}; + + my ($id, $name) = (shift, shift); + my $query = "UPDATE `mod_bi_liveservice` SET `name`='".$name."' WHERE timeperiod_id=".$id; + $db->query($query); +} + +sub truncateTable { + my $self = shift; + my $db = $self->{"centstorage"}; + + my $query = "TRUNCATE TABLE `mod_bi_liveservice`"; + $db->query($query); + $db->query("ALTER TABLE `mod_bi_liveservice` AUTO_INCREMENT=1"); +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/Loader.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/Loader.pm new file mode 100644 index 00000000000..9cfe6d334a3 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/Loader.pm @@ -0,0 +1,121 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; +use POSIX; + +package gorgone::modules::centreon::mbi::libs::bi::Loader; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database +sub new { + my $class = shift; + my $self = {}; + $self->{"logger"} = shift; + $self->{"centstorage"} = shift; + if (@_) { + $self->{"centreon"} = shift; + } + $self->{'tempFolder'} = "/tmp/"; + bless $self, $class; + return $self; +} + +sub setStorageDir { + my $self = shift; + my $logger = $self->{'logger'}; + my $tempFolder = shift; + if (!defined($tempFolder)) { + $logger->writeLog("ERROR", "Temporary storage folder is not defined"); + } + if (! -d $tempFolder && ! -w $tempFolder) { + $logger->writeLog("ERROR", "Cannot write into directory ".$tempFolder); + } + if ($tempFolder !~ /\/$/) { + $tempFolder .= "/"; + } + $self->{'tempFolder'} = $tempFolder; +} +sub getStorageDir { + my $self = shift; + return $self->{'tempFolder'}; +} +sub loadData { + my $self = shift; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + my ($tableName, $inFile) = (shift, shift); + my $query = "LOAD DATA LOCAL INFILE '".$inFile."' INTO TABLE `".$tableName."` CHARACTER SET UTF8 IGNORE 1 LINES"; + my $sth = $db->query($query); +} +sub disableKeys { + my $self = shift; + my $db = $self->{"centstorage"}; + my $tableName = shift; + my $query = "ALTER TABLE `".$tableName."` DISABLE KEYS"; + my $sth = $db->query($query); +} + +sub enableKeys { + my $self = shift; + my $db = $self->{"centstorage"}; + my $tableName = shift; + my $query = "ALTER TABLE `".$tableName."` ENABLE KEYS"; + my $sth = $db->query($query); +} + +sub dumpTableStructure { + my $self = shift; + my $db = $self->{"centstorage"}; + my $logger = $self->{'logger'}; + my ($tableName) = (shift); + + my $sql = ""; + my $sth = $db->query("SHOW CREATE TABLE ".$tableName); + if (my $row = $sth->fetchrow_hashref()) { + $sql = $row->{'Create Table'}; + }else { + $logger->writeLog("WARNING", "Cannot get structure for table : ".$tableName); + return (undef); + } + $sth->finish; + return ($sql); +} + +sub truncateTable { + my $self = shift; + my $db = $self->{"centstorage"}; + my $tableName = shift; + my $query = "TRUNCATE TABLE `".$tableName."`"; + my $sth = $db->query($query); +} +sub dropTable { + my $self = shift; + my $db = $self->{"centstorage"}; + my $tableName = shift; + my $query = "DROP TABLE IF EXISTS `".$tableName."`"; + my $sth = $db->query($query); +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricCentileValue.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricCentileValue.pm new file mode 100644 index 00000000000..79db771c68e --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricCentileValue.pm @@ -0,0 +1,182 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; + +package gorgone::modules::centreon::mbi::libs::bi::MetricCentileValue; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centstorage: Instance of centreonDB class for connection to Centreon database +# $centreon: Instance of centreonDB class for connection to Centstorage database +sub new { + my ($class, %options) = (shift, @_); + my $self = {}; + $self->{logger} = $options{logger}; + $self->{centstorage} = $options{centstorage}; + $self->{centreon} = $options{centreon}; + $self->{time} = $options{time}; + $self->{centileProperties} = $options{centileProperties}; + $self->{timePeriod} = $options{timePeriod}; + $self->{liveService} = $options{liveService}; + + $self->{today_servicemetrics} = "mod_bi_tmp_today_servicemetrics"; #BIMetric -> createTodayTable + + #Daily values + $self->{name} = "mod_bi_metriccentiledailyvalue"; + + #Week values + $self->{name_week} = "mod_bi_metriccentileweeklyvalue"; + + #Month values + $self->{name_month} = "mod_bi_metriccentilemonthlyvalue"; + + $self->{timeColumn} = "time_id"; + bless $self, $class; + return $self; +} + +#getName($granularity) : "month","week" +sub getName { + my $self = shift; + my $granularity = shift; + my $name = $self->{name}; + + if (defined($granularity) && ($granularity eq "month" || $granularity eq "week")) { + my $key = 'name_' . $granularity; + $name = $self->{$key}; + } + return $name; +} + +sub getTmpName { + my ($self, $granularity) = @_; + my $name = $self->{tmp_name}; + if (defined $granularity && ($granularity eq "month" || $granularity eq "week")) { + my $key = 'tmp_name_' . $granularity; + $name = $self->{$key}; + } + + return $name; +} + +sub getTimeColumn { + my $self = shift; + + return $self->{timeColumn}; +} + +sub getMetricsCentile { + my ($self, %options) = @_; + + my $results = {}; + my $centileServiceCategories = $options{etlProperties}->{'centile.include.servicecategories'}; + my $query = 'SELECT id, metric_id FROM ' . $self->{today_servicemetrics} . ' sm ' . + ' WHERE sm.sc_id IN (' . $centileServiceCategories . ')'; + my $sth = $self->{centstorage}->query($query); + while (my $row = $sth->fetchrow_arrayref()) { + $results->{$$row[1]} = [] if (!defined($results->{$$row[1]})); + push @{$results->{$$row[1]}}, $$row[0]; + } + + return $results; +} + +sub getTimePeriodQuery { + my ($self, %options) = @_; + + my $subQuery = ''; + # Get the time period to apply to each days of the period given in parameter + my $totalDays = $self->{time}->getTotalDaysInPeriod($options{start}, $options{end}) + 1; # +1 because geTotalDaysInPeriod return the number of day between start 00:00 and end 00:00 + my $counter = 1; + my $currentStart = $options{start}; + my $append = ''; + while ($counter <= $totalDays) { + my $rangeDay = $self->{timePeriod}->getTimeRangesForDayByDateTime($options{liveServiceName}, $currentStart, $self->{time}->getDayOfWeek($currentStart)); + if (scalar($rangeDay)) { + my @tabPeriod = @$rangeDay; + my ($start_date, $end_date); + my $tabSize = scalar(@tabPeriod); + for (my $count = 0; $count < $tabSize; $count++) { + my $range = $tabPeriod[$count]; + if ($count == 0) { + $start_date = $range->[0]; + } + if ($count == $tabSize - 1) { + $end_date = $range->[1]; + } + $subQuery .= $append . "(ctime >= UNIX_TIMESTAMP(" . ($range->[0]) . ") AND ctime < UNIX_TIMESTAMP(" . ($range->[1]) . "))"; + $append = ' OR '; + } + } + $currentStart = $self->{time}->addDateInterval($currentStart, 1, "DAY"); + $counter++; + } + + return $subQuery; +} + +sub calcMetricsCentileValueMultipleDays { + my ($self, %options) = @_; + + my $centileParam = $self->{centileProperties}->getCentileParams(); + foreach (@$centileParam) { + my ($centile, $timeperiodId) = ($_->{centile_param}, $_->{timeperiod_id}); + my ($liveServiceName, $liveServiceId) = $self->{liveService}->getLiveServicesByNameForTpId($timeperiodId); + + #Get Id for the couple centile / timeperiod + my $centileId; + my $query = "SELECT id FROM mod_bi_centiles WHERE centile_param = " . $centile . " AND liveservice_id = (SELECT id FROM mod_bi_liveservice WHERE timeperiod_id = " . $timeperiodId . ")"; + my $sth = $self->{centstorage}->query($query); + while (my $row = $sth->fetchrow_hashref()) { + if (defined($row->{id})) { + $centileId = $row->{id}; + } + } + + next if (!defined($centileId)); + + my $total = scalar(keys %{$options{metricsId}}); + $self->{logger}->writeLog("INFO", "Processing " . $options{granularity} . " for Centile: [" . $options{start} . "] to [" . $options{end} . "] - " . $liveServiceName . " - " . $centile . ' (' . $total . ' metrics)'); + my $sub_query_timeperiod = $self->getTimePeriodQuery(start => $options{start}, end => $options{end}, liveServiceName => $liveServiceName); + $query = 'SELECT value FROM (SELECT value, @counter := @counter + 1 AS counter FROM (select @counter := 0) AS initvar, data_bin WHERE id_metric = ? AND (' . $sub_query_timeperiod . ') ORDER BY value ASC) AS X where counter = ceil(' . $centile . ' * @counter / 100)'; + my $sth_centile = $self->{centstorage}->prepare($query); + my $current = 1; + foreach my $metricId (keys %{$options{metricsId}}) { + $self->{logger}->writeLog("DEBUG", "Processing metric id for Centile: " . $metricId . " ($current/$total)"); + $sth_centile->execute($metricId); + my $row = $sth_centile->fetchrow_arrayref(); + $current++; + next if (!defined($row)); + + foreach (@{$options{metricsId}->{$metricId}}) { + my $query_insert = 'INSERT INTO ' . $self->getName($options{granularity}) . + '(servicemetric_id, time_id, liveservice_id, centile_value, centile_param, centile_id, total, warning_treshold, critical_treshold)' . + "SELECT '" . $_ . "', '" . $options{timeId} . "', '" . $liveServiceId . "', '" . $$row[0] . "', '" . $centile . "', '" . $centileId . "', " . + 'm.max, m.warn, m.crit FROM metrics m WHERE m.metric_id = ' . $metricId; + $self->{centstorage}->query($query_insert); + } + } + } +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricDailyValue.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricDailyValue.pm new file mode 100644 index 00000000000..f221ee8b136 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricDailyValue.pm @@ -0,0 +1,146 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; + +package gorgone::modules::centreon::mbi::libs::bi::MetricDailyValue; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database +sub new { + my $class = shift; + my $self = {}; + $self->{logger} = shift; + $self->{centstorage} = shift; + + $self->{name_minmaxavg_tmp} = 'mod_bi_tmp_minmaxavgvalue'; + $self->{name_firstlast_tmp} = 'mod_bi_tmp_firstlastvalues'; + if (@_) { + $self->{name_minmaxavg_tmp} .= $_[0]; + $self->{name_firstlast_tmp} .= $_[0]; + } + + $self->{today_servicemetrics} = "mod_bi_tmp_today_servicemetrics"; + $self->{name} = "mod_bi_metricdailyvalue"; + $self->{timeColumn} = "time_id"; + bless $self, $class; + return $self; +} + +sub getName() { + my $self = shift; + return $self->{'name'}; +} + +sub getTimeColumn() { + my $self = shift; + return $self->{'timeColumn'}; +} + +sub dropTempTables { + my $self = shift; + my $db = $self->{"centstorage"}; + my $query = "DROP TABLE `" . $self->{name_minmaxavg_tmp} . "`"; + $db->query($query); + $query = "DROP TABLE `" . $self->{name_firstlast_tmp} . "`"; + $db->query($query); +} + +sub insertValues { + my $self = shift; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + my $liveServiceId = shift; + my $timeId = shift; + + my $query = "INSERT INTO " . $self->{"name"}; + $query .= " SELECT sm.id as servicemetric_id, '".$timeId."', ".$liveServiceId." as liveservice_id,"; + $query .= " mmavt.avg_value, mmavt.min_value, mmavt.max_value, flvt.`first_value`, flvt.`last_value`, m.max,"; + $query .= " m.warn, m.crit"; + $query .= " FROM " . $self->{name_minmaxavg_tmp} . " mmavt"; + $query .= " JOIN (metrics m, " . $self->{'today_servicemetrics'} . " sm)"; + $query .= " ON (mmavt.id_metric = m.metric_id and mmavt.id_metric = sm.metric_id)"; + $query .= " LEFT JOIN " . $self->{name_firstlast_tmp} . " flvt ON (mmavt.id_metric = flvt.id_metric)"; + $db->query($query); + + $self->dropTempTables(); +} + +sub getMetricCapacityValuesOnPeriod { + my $self = shift; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + my ($start_time_id, $end_time_id, $etlProperties) = @_; + + my $query = " SELECT servicemetric_id, liveservice_id, "; + $query .= " `first_value`, total"; + $query .= " FROM mod_bi_liveservice l, mod_bi_servicemetrics m, ".$self->{"name"}." v"; + $query .= " WHERE timeperiod_id IN (".$etlProperties->{'capacity.include.liveservices'}.")"; + $query .= " AND l.id = v.liveservice_id"; + $query .= " AND time_id = ".$start_time_id; + if (defined($etlProperties->{'capacity.exclude.metrics'}) && $etlProperties->{'capacity.exclude.metrics'} ne "") { + $query .= " AND metric_name NOT IN (".$etlProperties->{'capacity.exclude.metrics'}.")"; + } + $query .= " AND sc_id IN (".$etlProperties->{'capacity.include.servicecategories'}.")"; + $query .= " AND v.servicemetric_id = m.id"; + $query .= " GROUP BY servicemetric_id, liveservice_id"; + my $sth = $db->query($query); + my %data = (); + while (my $row = $sth->fetchrow_hashref()) { + my @table = ($row->{"servicemetric_id"}, $row->{"liveservice_id"}, $row->{first_value}, $row->{"total"}); + $data{$row->{"servicemetric_id"}.";".$row->{"liveservice_id"}} = \@table; + } + + $query = " SELECT servicemetric_id, liveservice_id, "; + $query .= "`last_value`, total"; + $query .= " FROM mod_bi_liveservice l, mod_bi_servicemetrics m, ".$self->{"name"}." v"; + $query .= " WHERE timeperiod_id IN (".$etlProperties->{'capacity.include.liveservices'}.")"; + $query .= " AND l.id = v.liveservice_id"; + $query .= " AND time_id = ".$end_time_id; + if (defined($etlProperties->{'capacity.exclude.metrics'}) && $etlProperties->{'capacity.exclude.metrics'} ne "") { + $query .= " AND metric_name NOT IN (".$etlProperties->{'capacity.exclude.metrics'}.")"; + } + $query .= " AND sc_id IN (".$etlProperties->{'capacity.include.servicecategories'}.")"; + $query .= " AND v.servicemetric_id = m.id"; + $query .= " GROUP BY servicemetric_id, liveservice_id"; + + $sth = $db->query($query); + while (my $row = $sth->fetchrow_hashref()) { + my $entry = $data{$row->{"servicemetric_id"}.";".$row->{"liveservice_id"}}; + if (defined($entry)) { + $entry->[4] = $row->{"last_value"}; + $entry->[5] = $row->{"total"}; + }else { + my @table; + $table[0] = $row->{"servicemetric_id"}; + $table[1] = $row->{"liveservice_id"}; + $table[4] = $row->{"last_value"}; + $table[5] = $row->{"total"}; + $data{$row->{"servicemetric_id"}.";".$row->{"liveservice_id"}} = \@table; + } + } + return \%data; +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricHourlyValue.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricHourlyValue.pm new file mode 100644 index 00000000000..037e52c85f3 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricHourlyValue.pm @@ -0,0 +1,72 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; + +package gorgone::modules::centreon::mbi::libs::bi::MetricHourlyValue; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database +sub new { + my $class = shift; + my $self = {}; + $self->{logger} = shift; + $self->{centstorage} = shift; + + $self->{name_minmaxavg_tmp} = 'mod_bi_tmp_minmaxavgvalue'; + if (@_) { + $self->{name_minmaxavg_tmp} .= $_[0]; + } + + $self->{servicemetrics} = "mod_bi_tmp_today_servicemetrics"; + $self->{name} = "mod_bi_metrichourlyvalue"; + $self->{timeColumn} = "time_id"; + bless $self, $class; + return $self; +} + +sub getName() { + my $self = shift; + return $self->{'name'}; +} + +sub getTimeColumn() { + my $self = shift; + return $self->{'timeColumn'}; +} + +sub insertValues { + my $self = shift; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + + my $query = "INSERT INTO ".$self->{"name"}; + $query .= " SELECT sm.id as servicemetric_id, t.id as time_id, mmavt.avg_value, mmavt.min_value, mmavt.max_value, m.max , m.warn, m.crit"; + $query .= " FROM " . $self->{name_minmaxavg_tmp} . " mmavt"; + $query .= " JOIN (metrics m, " . $self->{servicemetrics} . " sm, mod_bi_time t)"; + $query .= " ON (mmavt.id_metric = m.metric_id and mmavt.id_metric = sm.metric_id AND mmavt.valueTime = t.dtime)"; + $db->query($query); +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricMonthCapacity.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricMonthCapacity.pm new file mode 100644 index 00000000000..4462e0d2883 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricMonthCapacity.pm @@ -0,0 +1,90 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; + +package gorgone::modules::centreon::mbi::libs::bi::MetricMonthCapacity; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database +sub new { + my $class = shift; + my $self = {}; + $self->{"logger"} = shift; + $self->{"centstorage"} = shift; + if (@_) { + $self->{"centreon"} = shift; + } + $self->{"name"} = "mod_bi_metricmonthcapacity"; + $self->{"timeColumn"} = "time_id"; + bless $self, $class; + return $self; +} + +sub getName() { + my $self = shift; + return $self->{'name'}; +} + +sub getTimeColumn() { + my $self = shift; + return $self->{'timeColumn'}; +} + +sub insertStats { + my $self = shift; + my $db = $self->{centstorage}; + my ($time_id, $data) = @_; + my $insertParam = 5000; + + my $query_start = "INSERT INTO `" . $self->{name} . "`". + "(`time_id`, `servicemetric_id`, `liveservice_id`,". + " `first_value`, `first_total`, `last_value`, `last_total`)". + " VALUES "; + my $counter = 0; + my $query = $query_start; + my $append = ''; + + while (my ($key, $entry) = each %$data) { + $query .= $append . "($time_id"; + + my $size = scalar(@$entry); + for (my $i = 0; $i < $size; $i++) { + $query .= ', ' . (defined($entry->[$i]) ? $entry->[$i] : 'NULL'); + } + $query .= ')'; + + $append = ','; + $counter++; + if ($counter >= $insertParam) { + $db->query($query); + $query = $query_start; + $counter = 0; + $append = ''; + } + } + $db->query($query) if ($counter > 0); +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/MySQLTables.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/MySQLTables.pm new file mode 100644 index 00000000000..b91981c3f86 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/MySQLTables.pm @@ -0,0 +1,309 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::centreon::mbi::libs::bi::MySQLTables; + +use strict; +use warnings; +use POSIX; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database +sub new { + my $class = shift; + my $self = {}; + + $self->{logger} = shift; + $self->{centstorage} = shift; + if (@_) { + $self->{centreon} = shift; + } + bless $self, $class; + return $self; +} + +sub tableExists { + my $self = shift; + + my ($name) = (shift); + my $statement = $self->{centstorage}->query("SHOW TABLES LIKE '".$name."'"); + + if (!(my @row = $statement->fetchrow_array())) { + return 0; + } else { + return 1; + } +} + +sub createTable { + my $self = shift; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + my ($name, $structure, $mode) = @_; + my $statement = $db->query("SHOW TABLES LIKE '".$name."'"); + if (!$self->tableExists($name)) { + if (defined($structure)) { + $logger->writeLog("DEBUG", "[CREATE] table [".$name."]"); + $db->query($structure); + return 0; + }else { + $logger->writeLog("FATAL", "[CREATE] Cannot find table [".$name."] structure"); + } + } + return 1; +} + +sub dumpTableStructure { + my $self = shift; + my ($tableName) = (shift); + + my $sql = ""; + my $sth = $self->{centstorage}->query("SHOW CREATE TABLE " . $tableName); + if (my $row = $sth->fetchrow_hashref()) { + $sql = $row->{'Create Table'}; + $sql =~ s/(CONSTRAINT.*\n)//g; + $sql =~ s/(\,\n\s+\))/\)/g; + }else { + die "Cannot get structure for table : ".$tableName; + } + return ($sql); +} + +# create table data_bin with partitions +sub createParts { + my $self = shift; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + + my ($start, $end, $tableStructure, $tableName, $column) = @_; + if (!defined($tableStructure)) { + $logger->writeLog("FATAL", "[CREATE] Cannot find table [".$tableName."] structure"); + } + if ($self->tableExists($tableName)) { + return 1; + } + $tableStructure =~ s/\n.*PARTITION.*//g; + $tableStructure =~ s/\,[\n\s]+\)/\)/; + $tableStructure .= " PARTITION BY RANGE(`".$column."`) ("; + my $timeObj = Time->new($logger,$db); + my $runningStart = $timeObj->addDateInterval($start, 1, "DAY"); + while ($timeObj->compareDates($end, $runningStart) > 0) { + my @partName = split (/\-/, $runningStart); + $tableStructure .= "PARTITION p" . $partName[0] . $partName[1] . $partName[2] . " VALUES LESS THAN (FLOOR(UNIX_TIMESTAMP('".$runningStart."'))),"; + $runningStart= $timeObj->addDateInterval($runningStart, 1, "DAY"); + } + my @partName = split (/\-/, $runningStart); + $tableStructure .= "PARTITION p".$partName[0].$partName[1].$partName[2]." VALUES LESS THAN (FLOOR(UNIX_TIMESTAMP('".$runningStart."'))));"; + $logger->writeLog("DEBUG", "[CREATE] table partitionned [".$tableName."] min value: ".$start.", max value: ".$runningStart.", range: 1 DAY\n"); + $db->query($tableStructure); + return 0; +} + +sub updateParts { + my $self = shift; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + my ($rangeEnd, $tableName) = @_; + my $timeObj = Time->new($logger,$db); + + my $isPartitioned = $self->isTablePartitioned($tableName); + if (!$isPartitioned) { + $logger->writeLog("WARNING", "[UPDATE PARTS] partitioning is not activated for table [".$tableName."]"); + } else { + my $range = $self->getLastPartRange($tableName); + $range = $timeObj->addDateInterval($range, 1, "DAY"); + while ($timeObj->compareDates($rangeEnd, $range) >= 0) { + $logger->writeLog("DEBUG", "[UPDATE PARTS] Updating partitions for table [".$tableName."] (last range : ".$range.")"); + my @partName = split (/\-/, $range); + my $query = "ALTER TABLE `".$tableName."` ADD PARTITION (PARTITION `p".$partName[0].$partName[1].$partName[2]."` VALUES LESS THAN(FLOOR(UNIX_TIMESTAMP('".$range."'))))"; + $db->query($query); + $range = $timeObj->addDateInterval($range, 1, "DAY"); + } + } +} + +sub isTablePartitioned { + my $self = shift; + my $tableName = shift; + my $db = $self->{"centstorage"}; + + my $sth = $db->query("SHOW TABLE STATUS LIKE '".$tableName."'"); + if (my $row = $sth->fetchrow_hashref()) { + my $createOptions = $row->{"Create_options"}; + if (defined($createOptions) && $createOptions =~ m/partitioned/i) { + return 1; + } elsif (!defined($createOptions) || $createOptions !~ m/partitioned/i) { + return 0; + } + } + die "[TABLE STATUS CHECK] Cannot check if table is partitioned [".$tableName."]"; +} + +sub getLastPartRange { + my $self = shift; + my $tableName = shift; + + my $query = "SHOW CREATE TABLE $tableName"; + + my $partName; + my $sth = $self->{centstorage}->query($query); + if (my $row = $sth->fetchrow_hashref()) { + while ($row->{'Create Table'} =~ /PARTITION `(.*?)` VALUES LESS THAN \(([0-9]+?)\)/g) { + $partName = $1; + $partName =~ s/p(\d{4})(\d{2})(\d{2})/$1-$2-$3/; + } + } + + if (!defined($partName)) { + die "[UPDATE PARTS] Cannot find table [data_bin] in database"; + } + + return $partName; +} + +sub deleteEntriesForRebuild { + my $self = shift; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + my ($start, $end, $tableName) = @_; + + if (!$self->isTablePartitioned($tableName)) { + $db->query("DELETE FROM ".$tableName." WHERE time_id >= UNIX_TIMESTAMP('".$start."') AND time_id < UNIX_TIMESTAMP('".$end."')"); + }else { + my $query = "SELECT partition_name FROM information_schema.partitions "; + $query .= "WHERE table_name='".$tableName."' AND table_schema='".$db->db."'"; + $query .= " AND CONVERT(PARTITION_DESCRIPTION, SIGNED INTEGER) > UNIX_TIMESTAMP('".$start."')"; + $query .= " AND CONVERT(PARTITION_DESCRIPTION, SIGNED INTEGER) <= UNIX_TIMESTAMP('".$end."')"; + my $sth = $db->query($query); + while(my $row = $sth->fetchrow_hashref()) { + $db->query("ALTER TABLE ".$tableName." TRUNCATE PARTITION ".$row->{'partition_name'}); + } + $self->updateParts($end, $tableName); + } +} + +sub emptyTableForRebuild { + my $self = shift; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + my $tableName = shift; + my $structure = shift; + my $column = shift; + + $structure =~ s/KEY.*\(\`$column\`\)\,//g; + $structure =~ s/KEY.*\(\`$column\`\)//g; + $structure =~ s/\,[\n\s+]+\)/\n\)/g; + if (!defined($_[0]) || !$self->isPartitionEnabled()) { + $db->query("DROP TABLE IF EXISTS ".$tableName); + $db->query($structure); + }else { + my ($start, $end) = @_; + $db->query("DROP TABLE IF EXISTS ".$tableName); + $self->createParts($start, $end, $structure, $tableName, $column); + } + $db->query("ALTER TABLE `".$tableName."` ADD INDEX `idx_".$tableName."_".$column."` (`".$column."`)"); +} + +sub dailyPurge { + my $self = shift; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + + my ($retentionDate, $tableName, $column) = @_; + if (!$self->isTablePartitioned($tableName)) { + $db->query("DELETE FROM `".$tableName."` WHERE ".$column." < UNIX_TIMESTAMP('".$retentionDate."')"); + }else { + my $query = "SELECT GROUP_CONCAT(partition_name SEPARATOR ',') as partition_names FROM information_schema.partitions "; + $query .= "WHERE table_name='".$tableName."' AND table_schema='".$db->db."'"; + $query .= " AND CONVERT(PARTITION_DESCRIPTION, SIGNED INTEGER) < UNIX_TIMESTAMP('".$retentionDate."')"; + my $sth = $db->query($query); + if(my $row = $sth->fetchrow_hashref()) { + if (defined($row->{'partition_names'}) && $row->{'partition_names'} ne "") { + $db->query("ALTER TABLE ".$tableName." DROP PARTITION ".$row->{'partition_names'}); + } + } + } +} + +sub checkPartitionContinuity { + my $self = shift; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + my ($table) = @_; + my $message = ""; + my $query = "select CONVERT(1+datediff(curdate(),(select from_unixtime(PARTITION_DESCRIPTION) from information_schema.partitions"; + $query .= " where table_schema = '".$db->{"db"}."' and table_name = '".$table."' and PARTITION_ORDINAL_POSITION=1)), SIGNED INTEGER) as nbDays,"; + $query .= " CONVERT(PARTITION_ORDINAL_POSITION, SIGNED INTEGER) as ordinalPosition "; + $query .= " from information_schema.partitions where table_schema = '".$db->{"db"}."' and table_name = '".$table."' order by PARTITION_ORDINAL_POSITION desc limit 1 "; + my $sth = $db->query($query); + while (my $row = $sth->fetchrow_hashref()) { + my $nbDays = int($row->{'nbDays'}); + my $ordinalPosition = int($row->{'ordinalPosition'}); + my $dif = int($nbDays - $ordinalPosition); + if($dif > 0){ + $message .= "[".$table.", last partition:".$self->checkLastTablePartition($table)." missing ".$dif." part.]"; + } + } + $sth->finish; + return($message); +} + +sub checkLastTablePartition{ + my $self = shift; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + my ($table) = @_; + my $message = ""; + my $query = "select from_unixtime(PARTITION_DESCRIPTION) as last_partition, IF(from_unixtime(PARTITION_DESCRIPTION)=CURDATE() AND HOUR(from_unixtime(PARTITION_DESCRIPTION))=0,1,0) as partition_uptodate "; + $query .="from information_schema.partitions where table_schema = '".$db->{"db"}."'"; + $query .= "and table_name = '".$table."'order by PARTITION_ORDINAL_POSITION desc limit 1"; + my $sth = $db->query($query); + while (my $row = $sth->fetchrow_hashref()) { + if($row->{'partition_uptodate'} == 0){ + $message = $row->{'last_partition'}; + } + } + $sth->finish; + return($message); +} + +sub dropIndexesFromReportingTable { + my $self = shift; + my $table = shift; + my $db = $self->{"centstorage"}; + my $indexes = $db->query("SHOW INDEX FROM ".$table); + my $previous = ""; + while (my $row = $indexes->fetchrow_hashref()) { + + if ($row->{"Key_name"} ne $previous) { + if (lc($row->{"Key_name"}) eq lc("PRIMARY")) { + $db->query("ALTER TABLE `".$table."` DROP PRIMARY KEY"); + }else { + $db->query("ALTER TABLE `".$table."` DROP INDEX ".$row->{"Key_name"}); + } + } + $previous = $row->{"Key_name"}; + } +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/ServiceAvailability.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/ServiceAvailability.pm new file mode 100644 index 00000000000..ad4a674908b --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/ServiceAvailability.pm @@ -0,0 +1,238 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; + +package gorgone::modules::centreon::mbi::libs::bi::ServiceAvailability; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database +sub new { + my $class = shift; + my $self = {}; + $self->{"logger"} = shift; + $self->{"centstorage"} = shift; + if (@_) { + $self->{"centreon"} = shift; + } + $self->{"name"} = "mod_bi_serviceavailability"; + $self->{"timeColumn"} = "time_id"; + $self->{"nbLinesInFile"} = 0; + $self->{"commitParam"} = 500000; + bless $self, $class; + return $self; +} + +sub getName { + my $self = shift; + return $self->{'name'}; +} + +sub getTimeColumn { + my $self = shift; + return $self->{'timeColumn'}; +} + +sub saveStatsInFile { + my $self = shift; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + my ($data, $time_id, $liveserviceId,$fh) = @_; + my $query; + my $row; + + while (my ($modBiServiceId, $stats) = each %$data) { + my @tab = @$stats; + if ($stats->[0]+$stats->[1]+$stats->[2] == 0) { + next; + } + + #Filling the dump file with data + $row = $modBiServiceId."\t".$time_id."\t".$liveserviceId; + for (my $i = 0; $i < scalar(@$stats); $i++) { + $row.= "\t".$stats->[$i] + } + $row .= "\n"; + + #Write row into file + print $fh $row; + $self->{"nbLinesInFile"}++; + } +} + +sub insertStats { + my $self = shift; + my ($data, $time_id, $liveserviceId) = @_; + my $insertParam = 10000; + my $query_start = "INSERT INTO `" . $self->{name} . "`". + " (`modbiservice_id`, `time_id`, `liveservice_id`, `available`, ". + " `unavailable`, `degraded`, `alert_unavailable_opened`, `alert_unavailable_closed`, ". + " `alert_degraded_opened`, `alert_degraded_closed`, ". + " `alert_other_opened`, `alert_other_closed`)". + " VALUES "; + + #available+unvailable+alert_unavailable_closed + + my $counter = 0; + my $query = $query_start; + my $append = ''; + while (my ($modBiServiceId, $stats) = each %$data) { + my @tab = @$stats; + if ($stats->[0] + $stats->[1] + $stats->[2] == 0) { + next; + } + + $query .= $append . "($modBiServiceId, $time_id, $liveserviceId"; + for (my $i = 0; $i < scalar(@$stats); $i++) { + $query .= ', ' . $stats->[$i]; + } + $query .= ')'; + $append = ','; + $counter++; + + if ($counter >= $insertParam) { + $self->{centstorage}->query($query); + $query = $query_start; + $counter = 0; + $append = ''; + } + } + + $self->{centstorage}->query($query) if ($counter > 0); +} + +sub getCurrentNbLines { + my $self = shift; + return $self->{"nbLinesInFile"}; +} + +sub getCommitParam { + my $self = shift; + return $self->{"commitParam"}; +} + +sub setCurrentNbLines { + my $self = shift; + my $nbLines = shift; + $self->{"nbLinesInFile"} = $nbLines; +} + +sub getHGMonthAvailability { + my ($self, $start, $end, $eventObj) = @_; + my $db = $self->{"centstorage"}; + + my $query = "SELECT s.hg_id, s.hc_id, s.sc_id, sa.liveservice_id,"; + $query .= " hc.id as hcat_id, hg.id as group_id, sc.id as scat_id,"; + $query .= " avg((available+degraded)/(available+unavailable+degraded)) as av_percent,"; + $query .= " sum(available) as av_time, sum(unavailable) as unav_time, sum(degraded) as degraded_time,"; + $query .= " sum(alert_unavailable_opened) as unav_opened,sum(alert_unavailable_closed) as unav_closed,"; + $query .= " sum(alert_degraded_opened) as deg_opened,sum(alert_degraded_closed) as deg_closed,"; + $query .= " sum(alert_other_opened) as other_opened,sum(alert_other_closed) as other_closed "; + $query .= " FROM ".$self->{'name'}." sa"; + $query .= " STRAIGHT_JOIN mod_bi_time t ON (t.id = sa.time_id )"; + $query .= " STRAIGHT_JOIN mod_bi_services s ON (sa.modbiservice_id = s.id)"; + $query .= " STRAIGHT_JOIN mod_bi_hostgroups hg ON (s.hg_name=hg.hg_name AND s.hg_id=hg.hg_id)"; + $query .= " STRAIGHT_JOIN mod_bi_hostcategories hc ON (s.hc_name=hc.hc_name AND s.hc_id=hc.hc_id)"; + $query .= " STRAIGHT_JOIN mod_bi_servicecategories sc ON (s.sc_id=sc.sc_id AND s.sc_name=sc.sc_name)"; + $query .= " WHERE t.year = YEAR('".$start."') AND t.month = MONTH('".$start."') and t.hour=0"; + $query .= " GROUP BY s.hg_id, s.hc_id, s.sc_id, sa.liveservice_id"; + my $sth = $db->query($query); + + my @data = (); + while (my $row = $sth->fetchrow_hashref()) { + my ($totalwarnEvents, $totalCritEvents, $totalOtherEvents) = $eventObj->getNbEvents($start, $end, $row->{'hg_id'}, $row->{'hc_id'}, $row->{'sc_id'}, $row->{'liveservice_id'}); + + my ($mtrs, $mtbf, $mtbsi) = (undef, undef, undef); + if (defined($totalCritEvents) && $totalCritEvents != 0) { + $mtrs = $row->{'unav_time'}/$totalCritEvents; + $mtbf = $row->{'av_time'}/$totalCritEvents; + $mtbsi = ($row->{'unav_time'}+$row->{'av_time'})/$totalCritEvents; + } + my @tab = ($row->{'group_id'}, $row->{'hcat_id'}, $row->{'scat_id'}, $row->{'liveservice_id'}, + $row->{'av_percent'}, $row->{'unav_time'}, $row->{'degraded_time'}, + $row->{'unav_opened'}, $row->{'unav_closed'}, $row->{'deg_opened'}, $row->{'deg_closed'}, $row->{'other_opened'}, $row->{'other_closed'}, + $totalwarnEvents, $totalCritEvents, $totalOtherEvents, $mtrs, $mtbf, $mtbsi); + push @data, \@tab; + } + return \@data; +} + +sub getHGMonthAvailability_optimised { + my ($self, $start, $end, $eventObj) = @_; + my $db = $self->{"centstorage"}; + + my $query = "SELECT * from ( SELECT s.hg_id, s.hc_id, s.sc_id, sa.liveservice_id, hc.id as hcat_id, hg.id as group_id, sc.id as scat_id,"; + $query .= "avg((available+degraded)/(available+unavailable+degraded)) as av_percent, "; + $query .= "sum(available) as av_time, sum(unavailable) as unav_time, sum(degraded) as degraded_time, "; + $query .= "sum(alert_unavailable_opened) as unav_opened,sum(alert_unavailable_closed) as unav_closed, "; + $query .= "sum(alert_degraded_opened) as deg_opened,sum(alert_degraded_closed) as deg_closed, "; + $query .= "sum(alert_other_opened) as other_opened,sum(alert_other_closed) as other_closed "; + $query .= "FROM mod_bi_serviceavailability sa "; + $query .= "STRAIGHT_JOIN mod_bi_services s ON (sa.modbiservice_id = s.id) "; + $query .= "STRAIGHT_JOIN mod_bi_hostgroups hg ON (s.hg_name=hg.hg_name AND s.hg_id=hg.hg_id) "; + $query .= "STRAIGHT_JOIN mod_bi_hostcategories hc ON (s.hc_name=hc.hc_name AND s.hc_id=hc.hc_id) "; + $query .= "STRAIGHT_JOIN mod_bi_servicecategories sc ON (s.sc_id=sc.sc_id AND s.sc_name=sc.sc_name)"; + $query .= " WHERE YEAR(from_unixtime(time_id)) = YEAR('".$start."') AND MONTH(from_unixtime(time_id)) = MONTH('".$start."') and hour(from_unixtime(time_id)) = 0 "; + $query .= "GROUP BY s.hg_id, s.hc_id, s.sc_id, sa.liveservice_id ) availability "; + $query .= "LEFT JOIN ( SELECT s.hg_id,s.hc_id,s.sc_id,e.modbiliveservice_id, "; + $query .= "SUM(IF(state=1,1,0)) as warningEvents, SUM(IF(state=2,1,0)) as criticalEvents, "; + $query .= "SUM(IF(state=3,1,0)) as unknownEvents FROM mod_bi_servicestateevents e "; + $query .= "STRAIGHT_JOIN mod_bi_services s ON (e.modbiservice_id = s.id) "; + $query .= "STRAIGHT_JOIN mod_bi_hostgroups hg ON (s.hg_name=hg.hg_name AND s.hg_id=hg.hg_id) "; + $query .= "STRAIGHT_JOIN mod_bi_hostcategories hc ON (s.hc_name=hc.hc_name AND s.hc_id=hc.hc_id) "; + $query .= "STRAIGHT_JOIN mod_bi_servicecategories sc ON (s.sc_id=sc.sc_id AND s.sc_name=sc.sc_name) "; + $query .= "AND s.id = e.modbiservice_id AND start_time < UNIX_TIMESTAMP('".$end."') "; + $query .= "AND end_time > UNIX_TIMESTAMP('".$start."') AND e.state in (1,2,3) "; + $query .= "GROUP BY s.hg_id, s.hc_id, s.sc_id, e.modbiliveservice_id ) events "; + $query .= "ON availability.hg_id = events.hg_id AND availability.hc_id = events.hc_id "; + $query .= "AND availability.sc_id = events.sc_id "; + $query .= "AND availability.liveservice_id = events.modbiliveservice_id"; + + #Fields returned : + #hg_id | hc_id | sc_id | liveservice_id | hcat_id | group_id | scat_id | av_percent | av_time | unav_time | degraded_time | + #unav_opened | unav_closed | deg_opened | deg_closed | other_opened | other_closed | hg_id | hc_id | sc_id | + #modbiliveservice_id | warningEvents | criticalEvents | unknownEvents + my $sth = $db->query($query); + + my @data = (); + while (my $row = $sth->fetchrow_hashref()) { + my ($totalwarnEvents, $totalCritEvents, $totalUnknownEvents) = ($row->{'warningEvents'},$row->{'criticalEvents'},$row->{'unknownEvents'}); + + + my ($mtrs, $mtbf, $mtbsi) = (undef, undef, undef); + if (defined($totalCritEvents) && $totalCritEvents != 0) { + $mtrs = $row->{'unav_time'}/$totalCritEvents; + $mtbf = $row->{'av_time'}/$totalCritEvents; + $mtbsi = ($row->{'unav_time'}+$row->{'av_time'})/$totalCritEvents; + } + my @tab = ($row->{'group_id'}, $row->{'hcat_id'}, $row->{'scat_id'}, $row->{'liveservice_id'}, + $row->{'av_percent'}, $row->{'unav_time'}, $row->{'degraded_time'}, + $row->{'unav_opened'}, $row->{'unav_closed'}, $row->{'deg_opened'}, $row->{'deg_closed'}, $row->{'other_opened'}, $row->{'other_closed'}, + $totalwarnEvents, $totalCritEvents, $totalUnknownEvents, $mtrs, $mtbf, $mtbsi); + push @data, \@tab; + } + return \@data; +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/Time.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/Time.pm new file mode 100644 index 00000000000..c7313269417 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/Time.pm @@ -0,0 +1,264 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::centreon::mbi::libs::bi::Time; + +use strict; +use warnings; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database +sub new { + my $class = shift; + my $self = {}; + $self->{logger} = shift; + $self->{centstorage} = shift; + if (@_) { + $self->{centreon} = shift; + } + $self->{insertQuery} = "INSERT IGNORE INTO `mod_bi_time` (id, hour, day, month_label, month, year, week, dayofweek, utime, dtime) VALUES "; + bless $self, $class; + return $self; +} + +sub getEntriesDtime { + my $self = shift; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + + my ($start, $end) = @_; + my $query = "SELECT date_format('%Y-%m-%d', dtime) as dtime"; + $query .= " FROM `mod_bi_time`"; + $query .= " WHERE dtime >= '".$start."' AND dtime <'".$end."'"; + + my $sth = $db->query($query); + my @results = (); + if (my $row = $sth->fetchrow_hashref()) { + push @results, $row->{dtime}; + } + $sth->finish(); + return (@results); +} + +sub getEntryID { + my $self = shift; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + + my $dtime = shift; + my ($interval, $type); + if (@_) { + $interval = shift; + $type = shift; + } + my $query = "SELECT `id`, `utime`, date_format(dtime,'%Y-%m-%d') as dtime"; + $query .= " FROM `mod_bi_time`"; + if (!defined($interval)) { + $query .= " WHERE dtime = '".$dtime."'"; + }else { + $query .= " WHERE dtime = DATE_ADD('".$dtime."', INTERVAL ".$interval." ".$type.")"; + } + my $sth = $db->query($query); + my @results = (); + if (my $row = $sth->fetchrow_hashref()) { + $results[0] = $row->{'id'}; + $results[1] = $row->{'utime'}; + } + $sth->finish(); + if (!scalar(@results)) { + $logger->writeLog("ERROR", "Cannot get time ID for date:".$dtime); + } + return (@results); +} + +sub getDayOfWeek { + my $self = shift; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + my $date = shift; + + my $sth = $db->query("SELECT LOWER(DAYNAME('".$date."')) as dayOfWeek"); + my $dayofweek; + if (my $row = $sth->fetchrow_hashref()) { + $dayofweek = $row->{"dayOfWeek"}; + }else { + $logger->writeLog("ERROR", "TIME: Cannot get day of week for date :".$date); + } + if (!defined($dayofweek)) { + $logger->writeLog("ERROR", "TIME: day of week for date ".$date." is null"); + } + return $dayofweek; +} + +sub getYesterdayTodayDate { + my $self = shift; + + # get yesterday date. date format : YYYY-MM-DD + my $sth = $self->{centstorage}->query("SELECT CURRENT_DATE() as today, DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY) as yesterday"); + + my $yesterday; + my $today; + if (my $row = $sth->fetchrow_hashref()) { + $yesterday = $row->{yesterday}; + $today = $row->{today}; + } else { + $self->{logger}->writeLog('ERROR', "TIME: cannot get yesterday date"); + } + if (!defined($yesterday)) { + $self->{logger}->writeLog('ERROR', "TIME: Yesterday start date is null"); + } + if (!defined($today)) { + $self->{logger}->writeLog('ERROR', "TIME: today start date is null"); + } + return ($yesterday, $today); +} + +sub addDateInterval { + my $self = shift; + my ($date, $interval, $intervalType) = @_; + + # get new date. date format : YYYY-MM-DD + my $sth = $self->{centstorage}->query("SELECT DATE_ADD('".$date."', INTERVAL ".$interval." ".$intervalType.") as newDate"); + + my $newDate; + if (my $row = $sth->fetchrow_hashref()) { + $newDate = $row->{newDate}; + } + if (!defined($newDate)) { + $self->{logger}->writeLog('ERROR', "TIME: DATE_ADD('".$date."', INTERVAL ".$interval." ".$intervalType.") returns null value"); + } + return $newDate; +} + +sub compareDates { + my $self = shift; + my ($date1, $date2) = @_; + + my $sth = $self->{centstorage}->query("SELECT DATEDIFF('".$date1."','".$date2."') as nbDays"); + if (my $row = $sth->fetchrow_hashref()) { + return $row->{nbDays}; + } + + $self->{logger}->writeLog('ERROR', "TIME: Cannot compare two dates : ".$date1." and ".$date2); +} + +sub insertTimeEntriesForPeriod { + my $self = shift; + my $db = $self->{"centstorage"}; + my ($start, $end) = @_; + + my $interval = $self->getTotalDaysInPeriod($start, $end) * 24; + my $counter = 0; + my $date = "ADDDATE('".$start."',INTERVAL ".$counter." HOUR)"; + my $query_suffix = ""; + while ($counter <= $interval) { + $query_suffix .= "(UNIX_TIMESTAMP(".$date."),"; + $query_suffix .= "HOUR(".$date."),"; + $query_suffix .= "DAYOFMONTH(".$date."),"; + $query_suffix .= "LOWER(DATE_FORMAT(".$date.",'%M')),"; + $query_suffix .= "MONTH(".$date."),"; + $query_suffix .= "YEAR(".$date."),"; + $query_suffix .= "WEEK(".$date.", 3),"; + $query_suffix .= "LOWER(DAYNAME(".$date.")),"; + $query_suffix .= "UNIX_TIMESTAMP(".$date."),"; + $query_suffix .= "".$date."),"; + $counter++; + $date = "ADDDATE('".$start."',INTERVAL ".$counter." HOUR)"; + if ($counter % 30 == 0) { + chop($query_suffix); + $db->query($self->{insertQuery} . $query_suffix); + $query_suffix = ""; + } + } + chop($query_suffix); + if ($query_suffix ne "") { + $db->query($self->{insertQuery} . $query_suffix); + } +} + +# Delete duplicated entries inserted on winter/summer time change (same timestamp for 02:00 and 03:00) +sub deleteDuplicateEntries { + my $self = shift; + my $db = $self->{"centstorage"}; + my ($start, $end) = @_; + my $query = "SELECT max(id) as id"; + $query .= " FROM mod_bi_time"; + $query .= " WHERE dtime >='".$start."'"; + $query .= " AND dtime <= '".$end."'"; + $query .= " GROUP BY utime"; + $query .= " HAVING COUNT(utime) > 1"; + my $sth = $db->query($query); + my $ids_to_delete = ""; + while (my $row = $sth->fetchrow_hashref()) { + $ids_to_delete .= $row->{'id'}.","; + } + if ($ids_to_delete ne "") { + chop ($ids_to_delete); + $db->query("DELETE FROM mod_bi_time WHERE id IN (".$ids_to_delete.")"); + } +} + +sub getTotalDaysInPeriod { + my $self = shift; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + my ($start, $end) = @_; + + my $query = "SELECT DATEDIFF('".$end."', '".$start."') diff"; + my $sth = $db->query($query); + my $diff; + if (my $row = $sth->fetchrow_hashref()) { + $diff = $row->{'diff'}; + }else { + $logger->writeLog("ERROR", "TIME : Cannot get difference between period start and end"); + } + if (!defined($diff)){ + $logger->writeLog("ERROR", "TIME : Cannot get difference between period start and end"); + } + if($diff == 0) { + $logger->writeLog("ERROR", "TIME : start date is equal to end date"); + }elsif ($diff < 0) { + $logger->writeLog("ERROR", "TIME : start date is greater than end date"); + } + return $diff; +} + +sub truncateTable { + my $self = shift; + my $db = $self->{"centstorage"}; + + my $query = "TRUNCATE TABLE `mod_bi_time`"; + $db->query($query); + $db->query("ALTER TABLE `mod_bi_time` AUTO_INCREMENT=1"); +} + +sub deleteEntriesForPeriod { + my $self = shift; + my $db = $self->{"centstorage"}; + my ($start, $end) = @_; + + my $query = "DELETE FROM `mod_bi_time` WHERE dtime >= '".$start."' AND dtime < '".$end."'"; + $db->query($query); +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/centreon/CentileProperties.pm b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/CentileProperties.pm new file mode 100644 index 00000000000..a719c9539d3 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/CentileProperties.pm @@ -0,0 +1,60 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; + +package gorgone::modules::centreon::mbi::libs::centreon::CentileProperties; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database +sub new { + my $class = shift; + my $self = {}; + $self->{logger} = shift; + $self->{centreon} = shift; + if (@_) { + $self->{centstorage} = shift; + } + bless $self, $class; + return $self; +} + +sub getCentileParams { + my $self = shift; + my $centreon = $self->{centreon}; + my $logger = $self->{logger}; + + my $centileParams = []; + my $query = "SELECT `centile_param`, `timeperiod_id` FROM `mod_bi_options_centiles`"; + my $sth = $centreon->query($query); + while (my $row = $sth->fetchrow_hashref()) { + if (defined($row->{centile_param}) && $row->{centile_param} ne '0' && defined($row->{timeperiod_id}) && $row->{timeperiod_id} ne '0'){ + push @{$centileParams}, { centile_param => $row->{centile_param}, timeperiod_id => $row->{timeperiod_id} }; + } + } + + return $centileParams; +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/centreon/ETLProperties.pm b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/ETLProperties.pm new file mode 100644 index 00000000000..1181394b2ee --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/ETLProperties.pm @@ -0,0 +1,119 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; + +package gorgone::modules::centreon::mbi::libs::centreon::ETLProperties; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database +sub new { + my $class = shift; + my $self = {}; + + $self->{logger} = shift; + $self->{centreon} = shift; + if (@_) { + $self->{centstorage} = shift; + } + bless $self, $class; + return $self; +} + +# returns two references to two hash tables => hosts indexed by id and hosts indexed by name +sub getProperties { + my $self = shift; + + my $activated = 1; + if (@_) { + $activated = 0; + } + my (%etlProperties, %dataRetention); + + my $query = "SELECT `opt_key`, `opt_value` FROM `mod_bi_options` WHERE `opt_key` like 'etl.%'"; + my $sth = $self->{centreon}->query($query); + while (my $row = $sth->fetchrow_hashref()) { + if ($row->{opt_key} =~ /etl.retention.(.*)/) { + $dataRetention{$1} = $row->{opt_value}; + } elsif ($row->{opt_key} =~ /etl.list.(.*)/) { + my @tab = split (/,/, $row->{opt_value}); + my %hashtab = (); + foreach(@tab) { + $hashtab{$_} = 1; + } + $etlProperties{$1} = \%hashtab; + } elsif ($row->{opt_key} =~ /etl.(.*)/) { + $etlProperties{$1} = $row->{opt_value}; + } + } + if (defined($etlProperties{'capacity.exclude.metrics'})) { + $etlProperties{'capacity.exclude.metrics'} =~ s/^/\'/; + $etlProperties{'capacity.exclude.metrics'} =~ s/$/\'/; + $etlProperties{'capacity.exclude.metrics'} =~ s/,/\',\'/; + } + + return (\%etlProperties, \%dataRetention); +} + +# returns the max retention period defined by type of statistics, monthly stats are excluded +sub getMaxRetentionPeriodFor { + my $self = shift; + my $logger = $self->{'logger'}; + + my $type = shift; + my $query = "SELECT date_format(NOW(), '%Y-%m-%d') as period_end,"; + $query .= " date_format(DATE_ADD(NOW(), INTERVAL MAX(CAST(`opt_value` as SIGNED INTEGER))*-1 DAY), '%Y-%m-%d') as period_start"; + $query .= " FROM `mod_bi_options` "; + $query .= " WHERE `opt_key` IN ('etl.retention.".$type.".hourly','etl.retention.".$type.".daily', 'etl.retention.".$type.".raw')"; + my $sth = $self->{centreon}->query($query); + + if (my $row = $sth->fetchrow_hashref()) { + return ($row->{period_start}, $row->{period_end}); + } + + die 'Cannot get max perfdata retention period. Verify your data retention options'; +} + +# Returns a start and a end date for each retention period +sub getRetentionPeriods { + my $self = shift; + my $logger = $self->{'logger'}; + + my $query = "SELECT date_format(NOW(), '%Y-%m-%d') as period_end,"; + $query .= " date_format(DATE_ADD(NOW(), INTERVAL (`opt_value`)*-1 DAY), '%Y-%m-%d') as period_start,"; + $query .= " opt_key "; + $query .= " FROM `mod_bi_options` "; + $query .= " WHERE `opt_key` like ('etl.retention.%')"; + my $sth = $self->{centreon}->query($query); + my %periods = (); + while (my $row = $sth->fetchrow_hashref()) { + $row->{'opt_key'} =~ s/etl.retention.//; + $periods{$row->{'opt_key'}} = { start => $row->{period_start}, end => $row->{period_end}} ; + } + if (!scalar(keys %periods)){ + $logger->writeLog("FATAL", "Cannot retention periods information. Verify your data retention options"); + } + return (\%periods); +} +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Host.pm b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Host.pm new file mode 100644 index 00000000000..2b869ce5338 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Host.pm @@ -0,0 +1,307 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::modules::centreon::mbi::libs::centreon::Host; + +use strict; +use warnings; +use Data::Dumper; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database +sub new { + my $class = shift; + my $self = {}; + $self->{"logger"} = shift; + $self->{"centreon"} = shift; + $self->{'etlProperties'} = undef; + #Hash that will contains all relation between host and hostcategories after calling the function getHostCategoriesWithTemplate + $self->{"hostCategoriesWithTemplates"} = undef; + if (@_) { + $self->{"centstorage"} = shift; + } + bless $self, $class; + return $self; +} + +#Set the etl properties as a variable of the class +sub setEtlProperties{ + my $self = shift; + $self->{'etlProperties'} = shift; +} + +# returns two references to two hash tables => hosts indexed by id and hosts indexed by name +sub getAllHosts { + my $self = shift; + my $centreon = $self->{"centreon"}; + my $activated = 1; + if (@_) { + $activated = 0; + } + my (%host_ids, %host_names); + + my $query = "SELECT `host_id`, `host_name`". + " FROM `host`". + " WHERE `host_register`='1'"; + if ($activated == 1) { + $query .= " AND `host_activate` ='1'"; + } + my $sth = $centreon->query($query); + while (my $row = $sth->fetchrow_hashref()) { + $host_ids{$row->{"host_name"}} = $row->{"host_id"}; + $host_names{$row->{"host_id"}} = $row->{"host_name"}; + } + $sth->finish(); + return (\%host_ids,\%host_names); +} + +# Get all hosts, keys are IDs +sub getAllHostsByID { + my $self = shift; + my ($host_ids, $host_names) = $self->getAllHosts(); + return ($host_ids); +} + +# Get all hosts, keys are names +sub getAllHostsByName { + my $self = shift; + my ($host_ids, $host_names) = $self->getAllHosts(); + return ($host_names); +} + + +# returns host groups linked to hosts +# all hosts will be stored in a hash table +# each key of the hash table is a host id +# each key is linked to a table containing entries like : "hostgroup_id;hostgroup_name" +sub getHostGroups { + my $self = shift; + my $centreon = $self->{"centreon"}; + my $activated = 1; + my $etlProperties = $self->{'etlProperties'}; + if (@_) { + $activated = 0; + } + my %result = (); + + my $query = "SELECT `host_id`, `host_name`, `hg_id`, `hg_name`". + " FROM `host`, `hostgroup_relation`, `hostgroup`". + " WHERE `host_register`='1'". + " AND `hostgroup_hg_id` = `hg_id`". + " AND `host_id`= `host_host_id`"; + if ($activated == 1) { + $query .= " AND `host_activate` ='1'"; + } + if(!defined($etlProperties->{'dimension.all.hostgroups'}) && $etlProperties->{'dimension.hostgroups'} ne ''){ + $query .= " AND `hg_id` IN (".$etlProperties->{'dimension.hostgroups'}.")"; + } + my $sth = $centreon->query($query); + while (my $row = $sth->fetchrow_hashref()) { + my $new_entry = $row->{"hg_id"}.";".$row->{"hg_name"}; + if (defined($result{$row->{"host_id"}})) { + my $tab_ref = $result{$row->{"host_id"}}; + my @tab = @$tab_ref; + my $exists = 0; + foreach(@tab) { + if ($_ eq $new_entry) { + $exists = 1; + last; + } + } + if (!$exists) { + push @tab, $new_entry; + } + $result{$row->{"host_id"}} = \@tab; + }else { + my @tab = ($new_entry); + $result{$row->{"host_id"}} = \@tab; + } + } + $sth->finish(); + return (\%result); +} + +#Get the link between host and categories using templates +sub getRecursiveCategoriesForOneHost{ + my $self = shift; + my $host_id = shift; + my $ref_hostCat = shift; + my $centreon = $self->{"centreon"}; + my $etlProperties = $self->{"etlProperties"}; + + + #Get all categories linked to the templates associated with the host or just template associated with host to be able to call the method recursively + + my $query = "SELECT host_id, host_name, template_id,template_name, categories.hc_id as category_id, categories.hc_activate as hc_activate,". + " categories.hc_name as category_name ". + " FROM ( SELECT t1.host_id,t1.host_name,templates.host_id as template_id,templates.host_name as template_name ". + " FROM host t1, host_template_relation t2, host templates ". + " WHERE t1.host_id = t2.host_host_id AND t2.host_tpl_id = templates.host_id AND t1.host_activate ='1' AND t1.host_id = ".$host_id." ) r1 ". + " LEFT JOIN hostcategories_relation t3 ON t3.host_host_id = r1.template_id LEFT JOIN hostcategories categories ON t3.hostcategories_hc_id = categories.hc_id "; + + + my @hostCategoriesAllowed = split /,/, $etlProperties->{'dimension.hostcategories'}; + + my $sth = $centreon->query($query); + while (my $row = $sth->fetchrow_hashref()) { + my @tab = (); + my $new_entry; + my $categoryId = $row->{"category_id"}; + my $categoryName = $row->{"category_name"}; + my $categoryActivate = $row->{"hc_activate"}; + + #If current category is in allowed categories in ETL configuration + #add it to the categories link to the host, + #Then check for templates categories recursively + if(defined($categoryId) && defined($categoryName) && $categoryActivate=='1'){ + if ((grep {$_ eq $categoryId} @hostCategoriesAllowed) || (defined($etlProperties->{'dimension.all.hostcategories'}) && $etlProperties->{'dimension.all.hostcategories'} ne '')){ + $new_entry = $categoryId.";".$categoryName; + #If no hostcat has been found for the host, create the line + if (!scalar(@$ref_hostCat)){ + @$ref_hostCat = ($new_entry); + }else { #If the tab is not empty, check wether the combination already exists in the tab + @tab = @$ref_hostCat; + my $exists = 0; + foreach(@$ref_hostCat) { + if ($_ eq $new_entry) { + $exists = 1; + last; + } + } + #If the host category did not exist, add it to the table @$ref_hostCat + if (!$exists) { + push @$ref_hostCat, $new_entry; + } + } + } + } + $self->getRecursiveCategoriesForOneHost($row->{"template_id"},$ref_hostCat); + } + $sth->finish(); +} + +#Get the link between host and categories using direct link hc <> host +sub getDirectLinkedCategories{ + my $self = shift; + my $host_id = shift; + my $ref_hostCat = shift; + my $centreon = $self->{"centreon"}; + my $etlProperties = $self->{"etlProperties"}; + my @tab = (); + + my $query = "SELECT `host_id`, `host_name`, `hc_id`, `hc_name`". + " FROM `host`, `hostcategories_relation`, `hostcategories`". + " WHERE `host_register`='1'". + " AND `hostcategories_hc_id` = `hc_id`". + " AND `host_id`= `host_host_id`". + " AND `host_id`= ".$host_id." ". + " AND `host_activate` ='1' AND hostcategories.hc_activate = '1' "; + + if(!defined($etlProperties->{'dimension.all.hostcategories'}) && $etlProperties->{'dimension.hostcategories'} ne ''){ + $query .= " AND `hc_id` IN (".$etlProperties->{'dimension.hostcategories'}.")"; + } + + my $sth = $centreon->query($query); + while (my $row = $sth->fetchrow_hashref()) { + my $new_entry = $row->{"hc_id"}.";".$row->{"hc_name"}; + if (!scalar(@$ref_hostCat)){ + @$ref_hostCat = ($new_entry); + }else { + @tab = @$ref_hostCat; + my $exists = 0; + foreach(@$ref_hostCat) { + if ($_ eq $new_entry) { + $exists = 1; + last; + } + } + if (!$exists) { + push @$ref_hostCat, $new_entry; + } + } + } + $sth->finish(); +} + +#Fill a class Hash table that contains the relation between host_id and table[hc_id,hc_name] +sub getHostCategoriesWithTemplate{ + my $self = shift; + my $centreon = $self->{"centreon"}; + my $activated = 1; + + #Hash : each key of the hash table is a host id + #each key is linked to a table containing entries like : "hc_id,hc_name" + my $hostCategoriesWithTemplate = $self->{'hostCategoriesWithTemplates'}; + if (@_) { + $activated = 0; + } + + my $query = "SELECT `host_id`". + " FROM `host`". + " WHERE `host_activate` ='1'"; + + my $sth = $centreon->query($query); + while (my $row = $sth->fetchrow_hashref()) { + my @tab = (); + my $host_id = $row->{"host_id"}; + $self->getRecursiveCategoriesForOneHost($host_id,\@tab); + $self->getDirectLinkedCategories($host_id,\@tab); + $hostCategoriesWithTemplate->{$row->{"host_id"}} = [@tab]; + undef @tab; + } + $self->{'hostCategoriesWithTemplates'} = $hostCategoriesWithTemplate; + $sth->finish(); +} + +sub getHostGroupAndCategories { + my $self = shift; + + my $hostGroups = $self->getHostGroups(); + $self->getHostCategoriesWithTemplate(); + my $hostCategories = $self->{"hostCategoriesWithTemplates"}; + my $hosts = $self->getAllHostsByName; + my @results; + + while (my ($hostId, $groups) = each (%$hostGroups)) { + my $categories_ref = $hostCategories->{$hostId}; + my @categoriesTab = (); + if (defined($categories_ref) && scalar(@$categories_ref)) { + @categoriesTab = @$categories_ref; + } + my $hostName = $hosts->{$hostId}; + foreach(@$groups) { + my $group = $_; + if (scalar(@categoriesTab)) { + foreach(@categoriesTab) { + push @results, $hostId.";".$hostName.";".$group.";".$_; + } + }else { + #If there is no category + push @results, $hostId.";".$hostName.";".$group.";0;NoCategory"; + } + } + } + return \@results; +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/centreon/HostCategory.pm b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/HostCategory.pm new file mode 100644 index 00000000000..d881daa3ecd --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/HostCategory.pm @@ -0,0 +1,70 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; + +package gorgone::modules::centreon::mbi::libs::centreon::HostCategory; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database +sub new { + my $class = shift; + my $self = {}; + $self->{"logger"} = shift; + $self->{"centreon"} = shift; + $self->{'etlProperties'} = undef; + if (@_) { + $self->{"centstorage"} = shift; + } + bless $self, $class; + return $self; +} + +#Set the etl properties as a variable of the class +sub setEtlProperties{ + my $self = shift; + $self->{'etlProperties'} = shift; +} + + +sub getAllEntries { + my $self = shift; + my $db = $self->{"centreon"}; + my $etlProperties = $self->{'etlProperties'}; + + my $query = "SELECT `hc_id`, `hc_name`"; + $query .= " FROM `hostcategories`"; + if(!defined($etlProperties->{'dimension.all.hostcategories'}) && $etlProperties->{'dimension.hostcategories'} ne ''){ + $query .= " WHERE `hc_id` IN (".$etlProperties->{'dimension.hostcategories'}.")"; + } + my $sth = $db->query($query); + my @entries = (); + while (my $row = $sth->fetchrow_hashref()) { + push @entries, $row->{"hc_id"}.";".$row->{"hc_name"}; + } + $sth->finish(); + return (\@entries); +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/centreon/HostGroup.pm b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/HostGroup.pm new file mode 100644 index 00000000000..5c91af36021 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/HostGroup.pm @@ -0,0 +1,136 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; + +package gorgone::modules::centreon::mbi::libs::centreon::HostGroup; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database +sub new { + my $class = shift; + my $self = {}; + $self->{"logger"} = shift; + $self->{"centreon"} = shift; + $self->{'etlProperties'} = undef; + if (@_) { + $self->{"centstorage"} = shift; + } + bless $self, $class; + return $self; +} + +#Set the etl properties as a variable of the class +sub setEtlProperties{ + my $self = shift; + $self->{'etlProperties'} = shift; +} + +# returns in a table all host/service of a group of host +sub getHostgroupServices { + my $self = shift; + my $db = $self->{"centreon"}; + my $etlProperties = $self->{'etlProperties'}; + my $hgId = 0; + if (@_) { + $hgId = shift; + } + my %result = (); + my $query = "SELECT h.`host_id`, h.`host_name`, s.`service_id`, s.`service_description`"; + $query .= " FROM `hostgroup` hg, `host_service_relation` hsr, `service` s, `hostgroup_relation` hgr, `host` h"; + $query .= " WHERE hg.`hg_id` = ".$hgId; + $query .= " AND hg.`hg_id` = hsr.`hostgroup_hg_id`"; + $query .= " AND hsr.`service_service_id` = s.`service_id`"; + $query .= " AND s.`service_activate` = '1'"; + $query .= " AND s.`service_register` = '1'"; + $query .= " AND hg.hg_id = hgr.`hostgroup_hg_id`"; + $query .= " AND hgr.`host_host_id` = h.`host_id`"; + $query .= " AND h.`host_activate` = '1'"; + $query .= " AND h.`host_register` = '1'"; + if(!defined($etlProperties->{'dimension.all.hostgroups'}) && $etlProperties->{'dimension.hostgroups'} ne ''){ + $query .= " AND hg.`hg_id` IN (".$etlProperties->{'dimension.hostgroups'}.")"; + } + my $sth = $db->query($query); + while (my $row = $sth->fetchrow_hashref()) { + $result{$row->{"host_id"}.";".$row->{"service_id"}} = 1; + } + $sth->finish(); + return (\%result); +} + + +# returns in a table all host/service of a group of host +sub getHostgroupHostServices { + my $self = shift; + my $db = $self->{"centreon"}; + my %etlProperties = $self->{'etlProperties'}; + + my $hgId = 0; + if (@_) { + $hgId = shift; + } + my %result = (); + my $query = "SELECT h.`host_id`, s.`service_id`"; + $query .= " FROM `host` h, `hostgroup` hg, `hostgroup_relation` hgr, `host_service_relation` hsr, `service` s"; + $query .= " WHERE hg.`hg_id` = ".$hgId; + $query .= " AND hgr.`hostgroup_hg_id` = hg.`hg_id`"; + $query .= " AND hgr.`host_host_id` = h.`host_id`"; + $query .= " AND h.`host_activate` = '1'"; + $query .= " AND h.`host_register` = '1'"; + $query .= " AND h.`host_id` = hsr.`host_host_id`"; + $query .= " AND hsr.`service_service_id` = s.`service_id`"; + $query .= " AND s.`service_activate` = '1'"; + $query .= " AND s.`service_register` = '1'"; + if(!defined($etlProperties{'etl.dimension.all.hostgroups'}) && $etlProperties{'etl.dimension.hostgroups'} ne ''){ + $query .= " AND hg.`hg_id` IN (".$etlProperties{'etl.dimension.hostgroups'}.")"; + } + my $sth = $db->query($query); + while (my $row = $sth->fetchrow_hashref()) { + $result{$row->{"host_id"}.";".$row->{"service_id"}} = 1; + } + %result = (%result, $self->getHostgroupServices($hgId)); + return (\%result); +} + +sub getAllEntries { + my $self = shift; + my $db = $self->{"centreon"}; + my $etlProperties = $self->{'etlProperties'}; + + my $query = "SELECT `hg_id`, `hg_name`"; + $query .= " FROM `hostgroup`"; + if(!defined($etlProperties->{'dimension.all.hostgroups'}) && $etlProperties->{'dimension.hostgroups'} ne ''){ + $query .= " WHERE `hg_id` IN (".$etlProperties->{'dimension.hostgroups'}.")"; + } + my $sth = $db->query($query); + my @entries = (); + while (my $row = $sth->fetchrow_hashref()) { + push @entries, $row->{"hg_id"}.";".$row->{"hg_name"}; + } + $sth->finish(); + return (\@entries); +} + + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Service.pm b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Service.pm new file mode 100644 index 00000000000..447151726ae --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Service.pm @@ -0,0 +1,214 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; + +package gorgone::modules::centreon::mbi::libs::centreon::Service; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database +sub new { + my $class = shift; + my $self = {}; + $self->{"logger"} = shift; + $self->{"centreon"} = shift; + $self->{'etlProperties'} = undef; + + if (@_) { + $self->{"centstorage"} = shift; + } + bless $self, $class; + return $self; +} + +sub setEtlProperties{ + my $self = shift; + $self->{'etlProperties'} = shift; +} + +# returns two references to two hash tables => services indexed by id and services indexed by name +sub getServicesWithHostAndCategory { + my $self = shift; + my $centreon = $self->{"centreon"}; + my $serviceId = ""; + my $hosts = shift; + if (@_) { + $serviceId = shift; + } + my $templateCategories = $self->getServicesTemplatesCategories; + + my (@results); + # getting services linked to hosts + my $query = "SELECT service_description, service_id, host_id, service_template_model_stm_id as tpl". + " FROM host, service, host_service_relation". + " WHERE host_id = host_host_id and service_service_id = service_id". + " AND service_register = '1'". + " AND host_activate = '1'". + " AND service_activate = '1'"; + + + my $sth = $centreon->query($query); + while(my $row = $sth->fetchrow_hashref()) { + # getting all host entries + my $serviceHostTable = $hosts->{$row->{"host_id"}}; + # getting all Categories entries + my @categoriesTable = (); + # getting categories directly linked to service + my $categories = $self->getServiceCategories($row->{"service_id"}); + while(my ($sc_id, $sc_name) = each(%$categories)) { + push @categoriesTable, $sc_id.";".$sc_name; + } + # getting categories linked to template + if (defined($row->{"tpl"}) && defined($templateCategories->{$row->{"tpl"}})) { + my $tplCategories = $templateCategories->{$row->{"tpl"}}; + while(my ($sc_id, $sc_name) = each(%$tplCategories)) { + if(!defined($categories->{$sc_id})) { + push @categoriesTable, $sc_id.";".$sc_name; + } + } + } + if (!scalar(@categoriesTable)) { + #ToDo push @categoriesTable, "0;NULL"; + } + if (defined($serviceHostTable)) { + foreach(@$serviceHostTable) { + my $hostInfos = $_; + foreach(@categoriesTable) { + push @results, $row->{"service_id"}.";".$row->{"service_description"}.";".$_.";".$hostInfos; + } + } + } + } + #getting services linked to hostgroup + $query = "SELECT DISTINCT service_description, service_id, host_id, service_template_model_stm_id as tpl". + " FROM host, service, host_service_relation hr, hostgroup_relation hgr". + " WHERE hr.hostgroup_hg_id is not null". + " AND hr.service_service_id = service_id". + " AND hr.hostgroup_hg_id = hgr.hostgroup_hg_id". + " AND hgr.host_host_id = host_id". + " AND service_register = '1'". + " AND host_activate = '1'". + " AND service_activate = '1'"; + + $sth = $centreon->query($query); + while(my $row = $sth->fetchrow_hashref()) { + # getting all host entries + my $serviceHostTable = $hosts->{$row->{"host_id"}}; + # getting all Categories entries + my @categoriesTable = (); + # getting categories directly linked to service + my $categories = $self->getServiceCategories($row->{"service_id"}); + while(my ($sc_id, $sc_name) = each(%$categories)) { + push @categoriesTable, $sc_id.";".$sc_name; + } + # getting categories linked to template + if (defined($row->{"tpl"}) && defined($templateCategories->{$row->{"tpl"}})) { + my $tplCategories = $templateCategories->{$row->{"tpl"}}; + while(my ($sc_id, $sc_name) = each(%$tplCategories)) { + if(!defined($categories->{$sc_id})) { + push @categoriesTable, $sc_id.";".$sc_name; + } + } + } + if (!scalar(@categoriesTable)) { + push @categoriesTable, "0;NULL"; + } + if (defined($serviceHostTable)) { + foreach(@$serviceHostTable) { + my $hostInfos = $_; + foreach(@categoriesTable) { + push @results, $row->{"service_id"}.";".$row->{"service_description"}.";".$_.";".$hostInfos; + } + } + } + } + $sth->finish(); + return (\@results); +} + +sub getServicesTemplatesCategories { + my $self = shift; + my $db = $self->{"centreon"}; + my %results = (); + + my $query = "SELECT service_id, service_description, service_template_model_stm_id FROM service WHERE service_register = '0'"; + my $sth = $db->query($query); + while(my $row = $sth->fetchrow_hashref()) { + my $currentTemplate = $row->{"service_id"}; + my $categories = $self->getServiceCategories($row->{"service_id"}); + my $parentId = $row->{"service_template_model_stm_id"}; + if (defined($parentId)) { + my $hasParent = 1; + # getting all parent templates category relations + while ($hasParent) { + my $parentQuery = "SELECT service_id, service_template_model_stm_id "; + $parentQuery .= "FROM service "; + $parentQuery .= "WHERE service_register = '0' and service_id=".$parentId; + my $sthparentQuery = $db->query($parentQuery); + if(my $parentQueryRow = $sthparentQuery->fetchrow_hashref()) { + my $newCategories = $self->getServiceCategories($parentQueryRow->{"service_id"}); + while(my ($sc_id, $sc_name) = each(%$newCategories)) { + if (!defined($categories->{$sc_id})) { + $categories->{$sc_id} = $sc_name; + } + } + if (!defined($parentQueryRow->{'service_template_model_stm_id'})) { + $hasParent = 0; + last; + } + $parentId = $parentQueryRow->{'service_template_model_stm_id'}; + $sthparentQuery->finish(); + }else { + $hasParent = 0; + } + } + } + $results{$currentTemplate} = $categories; + } + $sth->finish(); + return \%results; +} + +sub getServiceCategories { + my $self = shift; + my $db = $self->{"centreon"}; + my $id = shift; + my %results = (); + my $etlProperties = $self->{'etlProperties'}; + + my $query = "SELECT sc.sc_id, sc_name "; + $query .= " FROM service_categories sc, service_categories_relation scr"; + $query .= " WHERE service_service_id = ".$id; + $query .= " AND sc.sc_id = scr.sc_id"; + if(!defined($etlProperties->{'dimension.all.servicecategories'}) && $etlProperties->{'dimension.servicecategories'} ne ''){ + $query .= " AND sc.sc_id IN (".$etlProperties->{'dimension.servicecategories'}.")"; + } + my $sth = $db->query($query); + while(my $row = $sth->fetchrow_hashref()) { + $results{$row->{"sc_id"}} = $row->{"sc_name"}; + } + return (\%results); +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/centreon/ServiceCategory.pm b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/ServiceCategory.pm new file mode 100644 index 00000000000..a013174d15b --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/ServiceCategory.pm @@ -0,0 +1,96 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; + +package gorgone::modules::centreon::mbi::libs::centreon::ServiceCategory; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database +sub new { + my $class = shift; + my $self = {}; + $self->{"logger"} = shift; + $self->{"centreon"} = shift; + $self->{'etlProperties'} = undef; + if (@_) { + $self->{"centstorage"} = shift; + } + bless $self, $class; + return $self; +} + +#Set the etl properties as a variable of the class +sub setEtlProperties{ + my $self = shift; + $self->{'etlProperties'} = shift; +} + +# returns two references to two hash tables => services indexed by id and services indexed by name +sub getCategory { + my $self = shift; + my $db = $self->{"centreon"}; + my $etlProperties = $self->{'etlProperties'}; + my $scName = ""; + if (@_) { + $scName = shift; + } + + my $result = ""; + # getting services linked to hosts + my $query = "SELECT sc_id from service_categories WHERE sc_name='".$scName."'"; + if(!defined($etlProperties->{'dimension.all.servicecategories'}) && $etlProperties->{'dimension.servicecategories'} ne ''){ + $query .= " WHERE `sc_id` IN (".$etlProperties->{'dimension.servicecategories'}.")"; + } + my $sth = $db->query($query); + if(my $row = $sth->fetchrow_hashref()) { + $result = $row->{"sc_id"}; + }else { + ($self->{"logger"})->writeLog("error", "Cannot find service category '".."' in database"); + } + $sth->finish(); + + return ($result); +} + +sub getAllEntries { + my $self = shift; + my $db = $self->{"centreon"}; + my $etlProperties = $self->{'etlProperties'}; + + my $query = "SELECT `sc_id`, `sc_name`"; + $query .= " FROM `service_categories`"; + if(!defined($etlProperties->{'dimension.all.servicecategories'}) && $etlProperties->{'dimension.servicecategories'} ne ''){ + $query .= " WHERE `sc_id` IN (".$etlProperties->{'dimension.servicecategories'}.")"; + } + my $sth = $db->query($query); + my @entries = (); + while (my $row = $sth->fetchrow_hashref()) { + push @entries, $row->{"sc_id"}.";".$row->{"sc_name"}; + } + $sth->finish(); + return (\@entries); +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Timeperiod.pm b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Timeperiod.pm new file mode 100644 index 00000000000..97e0f84efc8 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Timeperiod.pm @@ -0,0 +1,247 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; +use Time::Local; +use gorgone::modules::centreon::mbi::libs::Utils; + +package gorgone::modules::centreon::mbi::libs::centreon::Timeperiod; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database +sub new { + my $class = shift; + my $self = {}; + $self->{"logger"} = shift; + $self->{"centreon"} = shift; + if (@_) { + $self->{"centstorage"} = shift; + } + bless $self, $class; + return $self; +} + +sub getTimeRangesForDay { + my $self = shift; + my $db = $self->{"centreon"}; + my ($weekDay, $name, $unixtime) = @_; + my @results = (); + + my @weekDays = ("sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"); + my $query = "SELECT tp_" . $weekDay; + $query .= " FROM timeperiod"; + $query .= " WHERE tp_name = '" . $name . "'"; + my $sth = $db->query($query); + if (my $row = $sth->fetchrow_hashref()) { + if (defined($row->{'tp_'.$weekDay})) { + my @ranges = split(",", $row->{'tp_' . $weekDay}); + foreach (@ranges) { + my ($start, $end) = split("-", $_); + my ($start_hour, $start_min) = split(':', $start); + my ($end_hour, $end_min) = split(':', $end); + my @range = ($unixtime+ $start_hour * 60 * 60 + $start_min * 60, $unixtime + $end_hour * 60 * 60 + $end_min * 60); + $results[scalar(@results)] = \@range; + } + } + } + + return (\@results); +} + +sub getTimeRangesForDayByDateTime { + my $self = shift; + my $db = $self->{"centreon"}; + my ($name, $dateTime, $weekDay) = @_; + my @results = (); + + my $query = "SELECT tp_".$weekDay; + $query .= " FROM timeperiod"; + $query .= " WHERE tp_name='".$name."'"; + my $sth = $db->query($query); + if(my $row = $sth->fetchrow_hashref()) { + if (defined($row->{'tp_'.$weekDay})) { + my @ranges = split(",", $row->{'tp_'.$weekDay}); + foreach(@ranges) { + my ($start, $end) = split("-", $_); + my $range_end = "'".$dateTime." ".$end.":00'"; + if ($end eq '24:00') { + $range_end = "DATE_ADD('".$dateTime."', INTERVAL 1 DAY)"; + } + my @range = ("'".$dateTime." ".$start.":00'", $range_end); + $results[scalar(@results)] = \@range; + } + } + } + $sth->finish(); + + return (\@results); +} + +sub getRangeTable { + my ($self, $rangeStr) = @_; + if (!defined($rangeStr)) { + $rangeStr = ""; + } + my @ranges = split(",", $rangeStr); + + my @results = (); + foreach(@ranges) { + my ($start, $end) = split("-", $_); + my ($start_hour, $start_min) = split(":", $start); + my ($end_hour, $end_min) = split(":", $end); + push @results, [$start_hour * 60 * 60 + $start_min * 60, $end_hour * 60 * 60 + $end_min * 60]; + } + return [@results]; +} + +sub getAllRangesForTpId { + my ($self, $timeperiod_id) = @_; + my $db = $self->{"centreon"}; + my $logger = $self->{"logger"}; + my $query = "SELECT tp_monday, tp_tuesday, tp_wednesday, tp_thursday, tp_friday, tp_saturday, tp_sunday"; + $query .= " FROM timeperiod"; + $query .= " WHERE tp_id='".$timeperiod_id."'"; + my $sth = $db->query($query); + + my @results = (); + if(my $row = $sth->fetchrow_hashref()) { + $results[0] = $self->getRangeTable($row->{'tp_sunday'}); + $results[1] = $self->getRangeTable($row->{'tp_monday'}); + $results[2] = $self->getRangeTable($row->{'tp_tuesday'}); + $results[3] = $self->getRangeTable($row->{'tp_wednesday'}); + $results[4] = $self->getRangeTable($row->{'tp_thursday'}); + $results[5] = $self->getRangeTable($row->{'tp_friday'}); + $results[6] = $self->getRangeTable($row->{'tp_saturday'}); + }else { + $logger->writeLog("ERROR", "Cannot find time period with id '".$timeperiod_id."' in Centreon Database"); + } + return [@results]; +} + +sub getTimeRangesForPeriod { + my $self = shift; + my ($timeperiodId, $start, $end) = @_; + my @results = (); + my @weekDays = ("sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"); + my $days = gorgone::modules::centreon::mbi::libs::Utils->getRebuildPeriods($start, $end); + my $weekRanges = $self->getAllRangesForTpId($timeperiodId); + foreach (@$days) { + my $dayStart = $_->{'start'}; + my $dayRanges = $weekRanges->[(localtime($dayStart))[6]]; + foreach(@$dayRanges) { + push @results, [$dayStart+$_->[0], $dayStart+$_->[1]]; + } + } + return [@results]; +} + +sub getTimeRangesForPeriodAndTpList { + my $self = shift; + my ($timeperiodList, $start, $end) = @_; + + my %rangesByTP = (); + while (my ($key, $value) = each %$timeperiodList) { + $rangesByTP{$key} = $self->getTimeRangesForPeriod($key, $start, $end); + } + return \%rangesByTP; +} + +sub getId { + my $self = shift; + my $db = $self->{"centreon"}; + my $name = shift; + + my $query = "SELECT tp_id"; + $query .= " FROM timeperiod"; + $query .= " WHERE tp_name = '".$name."'"; + my $sth = $db->query($query); + my $result = -1; + if(my $row = $sth->fetchrow_hashref()) { + $result = $row->{'tp_id'}; + } + return $result; +} + +sub getPeriodsLike { + my $self = shift; + my $db = $self->{"centreon"}; + my $name = shift; + + my $query = "SELECT tp_id, tp_name"; + $query .= " FROM timeperiod"; + $query .= " WHERE tp_name like '".$name."%'"; + my $sth = $db->query($query); + my %result = (); + while (my $row = $sth->fetchrow_hashref()) { + $result{$row->{'tp_id'}} = $row->{'tp_name'}; + } + return \%result; +} + +sub getPeriods { + my $self = shift; + my $db = $self->{"centreon"}; + my $logger = $self->{'logger'}; + my $ids = shift; + + my $idStr = ""; + + foreach my $key (keys %$ids) { + if ($idStr eq "") { + $idStr .= $key; + }else { + $idStr .= ",".$key; + } + } + if ($idStr eq "") { + $logger->writeLog("ERROR", "Select a timeperiod in the ETL configuration menu"); + } + my $query = "SELECT tp_id, tp_name"; + $query .= " FROM timeperiod"; + $query .= " WHERE tp_id IN (".$idStr.")"; + my $sth = $db->query($query); + my %result = (); + while (my $row = $sth->fetchrow_hashref()) { + $result{$row->{'tp_id'}} = $row->{'tp_name'}; + } + return \%result; +} + +sub getCentilePeriods { + my $self = shift; + my $db = $self->{"centreon"}; + my $logger = $self->{'logger'}; + + my $query = "SELECT tp_id, tp_name"; + $query .= " FROM timeperiod"; + $query .= " WHERE tp_id IN (select timeperiod_id from mod_bi_options_centiles)"; + my $sth = $db->query($query); + my %result = (); + while (my $row = $sth->fetchrow_hashref()) { + $result{$row->{'tp_id'}} = $row->{'tp_name'}; + } + return \%result; +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/centstorage/HostStateEvents.pm b/gorgone/gorgone/modules/centreon/mbi/libs/centstorage/HostStateEvents.pm new file mode 100644 index 00000000000..a43002eab32 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/centstorage/HostStateEvents.pm @@ -0,0 +1,184 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; + +package gorgone::modules::centreon::mbi::libs::centstorage::HostStateEvents; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database +sub new { + my $class = shift; + my $self = {}; + $self->{"logger"} = shift; + $self->{"centstorage"} = shift; + $self->{"biHostStateEventsObj"} = shift; + $self->{"timePeriodObj"} = shift; + if (@_) { + $self->{"centreon"} = shift; + } + $self->{"name"} = "hoststateevents"; + $self->{"timeColumn"} = "end_time"; + bless $self, $class; + return $self; +} + +sub getName() { + my $self = shift; + return $self->{'name'}; +} + +sub getTimeColumn() { + my $self = shift; + return $self->{'timeColumn'}; +} +sub agreggateEventsByTimePeriod { + my ($self, $timeperiodList, $start, $end, $liveServiceByTpId, $mode) = @_; + my $logger = $self->{"logger"}; + my $nbEvents; + my $db = $self->{"centstorage"}; + + my $rangesByTP = ($self->{"timePeriodObj"})->getTimeRangesForPeriodAndTpList($timeperiodList, $start, $end); + my $query = " SELECT e.host_id, start_time, end_time, ack_time, state, last_update"; + $query .= " FROM `hoststateevents` e"; + $query .= " RIGHT JOIN (select host_id from mod_bi_tmp_today_hosts group by host_id) t2"; + $query .= " ON e.host_id = t2.host_id"; + $query .= " WHERE start_time < ".$end.""; + $query .= " AND end_time > ".$start.""; + $query .= " AND in_downtime = 0 "; + $query .= " ORDER BY start_time "; + + + my $hostEventObjects = $self->{"biHostStateEventsObj"}; + my $sth = $db->query($query); + $hostEventObjects->createTempBIEventsTable(); + $hostEventObjects->prepareTempQuery(); + + while (my $row = $sth->fetchrow_hashref()) { + if (!defined($row->{'end_time'})) { + $row->{'end_time'} = $end; + } + while (my ($timeperiodID, $timeRanges) = each %$rangesByTP) { + my @tab = (); + $tab[0] = $row->{'host_id'}; + $tab[1] = $liveServiceByTpId->{$timeperiodID}; + $tab[2] = $row->{'state'}; + if ($mode eq "daily") { + $timeRanges = ($self->{"timePeriodObj"})->getTimeRangesForPeriod($timeperiodID, $row->{'start_time'}, $row->{'end_time'}); + } + ($tab[3], $tab[4]) = $self->processIncidentForTp($timeRanges,$row->{'start_time'}, $row->{'end_time'}); + $tab[5] = $row->{'end_time'}; + $tab[6] = defined($row->{ack_time}) ? $row->{ack_time} : 0; + $tab[7] = $row->{'last_update'}; + if (defined($tab[3]) && $tab[3] != -1) { + $hostEventObjects->bindParam(\@tab); + } + + } + } + ($db->getInstance)->commit; +} + +sub processIncidentForTp { + my ($self, $timeRanges, $start, $end) = @_; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + + my $rangeSize = scalar(@$timeRanges); + my $duration = 0; + my $slaDuration = 0; + my $range = 0; + my $i = 0; + my $processed = 0; + my $slaStart = $start; + my $slaStartModified = 0; + + foreach(@$timeRanges) { + my $currentStart = $start; + my $currentEnd = $end; + + $range = $_; + my ($rangeStart, $rangeEnd) = ($range->[0], $range->[1]); + + if ($currentStart < $rangeEnd && $currentEnd > $rangeStart) { + $processed = 1; + if ($currentStart > $rangeStart) { + $slaStartModified = 1; + } elsif ($currentStart < $rangeStart) { + $currentStart = $rangeStart; + if (!$slaStartModified) { + $slaStart = $currentStart; + $slaStartModified = 1; + } + } + if ($currentEnd > $rangeEnd) { + $currentEnd = $rangeEnd; + } + $slaDuration += $currentEnd - $currentStart; + } + } + if (!$processed) { + return (-1, -1, -1); + } + + return ($slaStart, $slaDuration); +} + + +sub dailyPurge { + my $self = shift; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + my ($end) = @_; + + $logger->writeLog("DEBUG", "[PURGE] [hoststateevents] purging data older than ".$end); + my $query = "DELETE FROM `hoststateevents` where end_time < UNIX_TIMESTAMP('".$end."')"; + $db->query($query); +} + +sub getNbEvents{ + my $self = shift; + my $db = $self->{"centstorage"}; + my ($start, $end) = @_; + my $logger = $self->{"logger"}; + my $nbEvents = 0; + + my $query = "SELECT count(*) as nbEvents"; + $query .= " FROM `hoststateevents` e"; + $query .= " RIGHT JOIN (select host_id from mod_bi_tmp_today_hosts group by host_id) t2"; + $query .= " ON e.host_id = t2.host_id"; + $query .= " WHERE start_time < ".$end.""; + $query .= " AND end_time > ".$start.""; + $query .= " AND in_downtime = 0 "; + + + my $sth = $db->query($query); + + while (my $row = $sth->fetchrow_hashref()) { + $nbEvents = $row->{'nbEvents'}; + } + return $nbEvents; +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/centstorage/Metrics.pm b/gorgone/gorgone/modules/centreon/mbi/libs/centstorage/Metrics.pm new file mode 100644 index 00000000000..90d9bdba383 --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/centstorage/Metrics.pm @@ -0,0 +1,232 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; + +package gorgone::modules::centreon::mbi::libs::centstorage::Metrics; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database +sub new { + my $class = shift; + my $self = {}; + $self->{logger} = shift; + $self->{centstorage} = shift; + + $self->{metrics} = (); + $self->{name} = 'data_bin'; + $self->{timeColumn} = 'ctime'; + $self->{name_minmaxavg_tmp} = 'mod_bi_tmp_minmaxavgvalue'; + $self->{name_firstlast_tmp} = 'mod_bi_tmp_firstlastvalues'; + $self->{name_minmaxctime_tmp} = 'mod_bi_tmp_minmaxctime'; + if (@_) { + $self->{name_minmaxavg_tmp} .= $_[0]; + $self->{name_firstlast_tmp} .= $_[0]; + $self->{name_minmaxctime_tmp} .= $_[0]; + } + bless $self, $class; + return $self; +} + +sub getName() { + my $self = shift; + return $self->{'name'}; +} + +sub getTimeColumn() { + my $self = shift; + return $self->{'timeColumn'}; +} + +sub createTempTableMetricMinMaxAvgValues { + my ($self, $useMemory, $granularity) = @_; + my $db = $self->{"centstorage"}; + $db->query("DROP TABLE IF EXISTS `" . $self->{name_minmaxavg_tmp} . "`"); + my $createTable = " CREATE TABLE `" . $self->{name_minmaxavg_tmp} . "` ("; + $createTable .= " id_metric INT NULL,"; + $createTable .= " avg_value FLOAT NULL,"; + $createTable .= " min_value FLOAT NULL,"; + $createTable .= " max_value FLOAT NULL"; + if ($granularity eq "hour") { + $createTable .= ", valueTime DATETIME NULL"; + } + if (defined($useMemory) && $useMemory eq "true") { + $createTable .= ") ENGINE=MEMORY CHARSET=utf8 COLLATE=utf8_general_ci;"; + }else { + $createTable .= ") ENGINE=INNODB CHARSET=utf8 COLLATE=utf8_general_ci;"; + } + $db->query($createTable); +} + +sub getMetricValueByHour { + my $self = shift; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + + my ($start, $end, $useMemory) = @_; + my $dateFormat = "%Y-%c-%e %k:00:00"; + + # Getting min, max, average + $self->createTempTableMetricMinMaxAvgValues($useMemory, "hour"); + my $query = "INSERT INTO `" . $self->{name_minmaxavg_tmp} . "` SELECT id_metric, avg(value) as avg_value, min(value) as min_value, max(value) as max_value, "; + $query .= " date_format(FROM_UNIXTIME(ctime), '".$dateFormat."') as valueTime "; + $query .= "FROM data_bin "; + $query .= "WHERE "; + $query .= "ctime >=UNIX_TIMESTAMP('".$start."') AND ctime < UNIX_TIMESTAMP('".$end."') "; + $query .= "GROUP BY id_metric, date_format(FROM_UNIXTIME(ctime), '".$dateFormat."')"; + + $logger->writeLog("DEBUG", "Getting min, max, avg values by metric into temporary tables"); + $db->query($query); + $self->addIndexTempTableMetricMinMaxAvgValues("hour"); +} + +sub getMetricsValueByDay { + my $self = shift; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + + my ($period, $useMemory) = @_; + my $dateFormat = "%Y-%c-%e"; + + # Getting min, max, average + $self->createTempTableMetricMinMaxAvgValues($useMemory, "day"); + my $query = "INSERT INTO `" . $self->{name_minmaxavg_tmp} . "` SELECT id_metric, avg(value) as avg_value, min(value) as min_value, max(value) as max_value "; + #$query .= " date_format(FROM_UNIXTIME(ctime), '".$dateFormat."') as valueTime "; + $query .= "FROM data_bin "; + $query .= "WHERE "; + my @tabPeriod = @$period; + my ($start_date, $end_date); + my $tabSize = scalar(@tabPeriod); + for (my $count = 0; $count < $tabSize; $count++) { + my $range = $tabPeriod[$count]; + if ($count == 0) { + $start_date = $range->[0]; + } + if ($count == $tabSize - 1) { + $end_date = $range->[1]; + } + $query .= "(ctime >= UNIX_TIMESTAMP(".($range->[0]). ") AND ctime < UNIX_TIMESTAMP(".($range->[1]) .")) OR "; + } + + $query =~ s/OR $//; + $query .= "GROUP BY id_metric"; + + $logger->writeLog("DEBUG", "Getting min, max, avg values by metric"); + $db->query($query); + $self->addIndexTempTableMetricMinMaxAvgValues("day"); + $self->getFirstAndLastValues($start_date, $end_date, $useMemory); +} + +sub createTempTableMetricDayFirstLastValues { + my ($self, $useMemory) = @_; + my $db = $self->{"centstorage"}; + $db->query("DROP TABLE IF EXISTS `" . $self->{name_firstlast_tmp} . "`"); + my $createTable = " CREATE TABLE `" . $self->{name_firstlast_tmp} . "` ("; + $createTable .= " `first_value` FLOAT NULL,"; + $createTable .= " `last_value` FLOAT NULL,"; + $createTable .= " id_metric INT NULL"; + if (defined($useMemory) && $useMemory eq "true") { + $createTable .= ") ENGINE=MEMORY CHARSET=utf8 COLLATE=utf8_general_ci;"; + } else { + $createTable .= ") ENGINE=INNODB CHARSET=utf8 COLLATE=utf8_general_ci;"; + } + $db->query($createTable); +} + +sub addIndexTempTableMetricDayFirstLastValues { + my $self = shift; + my $db = $self->{"centstorage"}; + $db->query("ALTER TABLE " . $self->{name_firstlast_tmp} . " ADD INDEX (`id_metric`)"); +} + +sub addIndexTempTableMetricMinMaxAvgValues { + my $self = shift; + my $granularity = shift; + my $db = $self->{"centstorage"}; + my $index = "id_metric"; + if ($granularity eq "hour") { + $index .= ", valueTime"; + } + my $query = "ALTER TABLE " . $self->{name_minmaxavg_tmp} . " ADD INDEX (" . $index . ")"; + $db->query($query); +} + +sub createTempTableCtimeMinMaxValues { + my ($self, $useMemory) = @_; + my $db = $self->{"centstorage"}; + $db->query("DROP TABLE IF EXISTS `" . $self->{name_minmaxctime_tmp} . "`"); + my $createTable = " CREATE TABLE `" . $self->{name_minmaxctime_tmp} . "` ("; + $createTable .= " min_val INT NULL,"; + $createTable .= " max_val INT NULL,"; + $createTable .= " id_metric INT NULL"; + if (defined($useMemory) && $useMemory eq "true") { + $createTable .= ") ENGINE=MEMORY CHARSET=utf8 COLLATE=utf8_general_ci;"; + } else { + $createTable .= ") ENGINE=INNODB CHARSET=utf8 COLLATE=utf8_general_ci;"; + } + $db->query($createTable); +} + +sub dropTempTableCtimeMinMaxValues { + my $self = shift; + my $db = $self->{"centstorage"}; + $db->query("DROP TABLE `" . $self->{name_minmaxctime_tmp} . "`"); +} + +sub getFirstAndLastValues { + my $self = shift; + my $db = $self->{"centstorage"}; + + my ($start_date, $end_date, $useMemory) = @_; + + $self->createTempTableCtimeMinMaxValues($useMemory); + my $query = "INSERT INTO `" . $self->{name_minmaxctime_tmp} . "` SELECT min(ctime) as min_val, max(ctime) as max_val, id_metric "; + $query .= " FROM `data_bin`"; + $query .= " WHERE ctime >= UNIX_TIMESTAMP(" . $start_date . ") AND ctime < UNIX_TIMESTAMP(" . $end_date . ")"; + $query .= " GROUP BY id_metric"; + $db->query($query); + + $self->createTempTableMetricDayFirstLastValues($useMemory); + $query = "INSERT INTO " . $self->{name_firstlast_tmp} . " SELECT d.value as `first_value`, d2.value as `last_value`, d.id_metric"; + $query .= " FROM data_bin as d, data_bin as d2, " . $self->{name_minmaxctime_tmp} . " as db"; + $query .= " WHERE db.id_metric=d.id_metric AND db.min_val=d.ctime"; + $query .= " AND db.id_metric=d2.id_metric AND db.max_val=d2.ctime"; + $query .= " GROUP BY db.id_metric"; + my $sth = $db->query($query); + $self->addIndexTempTableMetricDayFirstLastValues(); + $self->dropTempTableCtimeMinMaxValues(); +} + +sub dailyPurge { + my $self = shift; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + my ($end) = @_; + + my $query = "DELETE FROM `data_bin` where ctime < UNIX_TIMESTAMP('" . $end . "')"; + $logger->writeLog("DEBUG", "[PURGE] [data_bin] purging data older than " . $end); + $db->query($query); +} + +1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/centstorage/ServiceStateEvents.pm b/gorgone/gorgone/modules/centreon/mbi/libs/centstorage/ServiceStateEvents.pm new file mode 100644 index 00000000000..af192330e9a --- /dev/null +++ b/gorgone/gorgone/modules/centreon/mbi/libs/centstorage/ServiceStateEvents.pm @@ -0,0 +1,179 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +use strict; +use warnings; + +package gorgone::modules::centreon::mbi::libs::centstorage::ServiceStateEvents; + +# Constructor +# parameters: +# $logger: instance of class CentreonLogger +# $centreon: Instance of centreonDB class for connection to Centreon database +# $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database +sub new { + my $class = shift; + my $self = {}; + $self->{"logger"} = shift; + $self->{"centstorage"} = shift; + $self->{"biServiceStateEventsObj"} = shift; + $self->{"timePeriodObj"} = shift; + if (@_) { + $self->{"centreon"} = shift; + } + + $self->{"name"} = "servicestateevents"; + $self->{"timeColumn"} = "end_time"; + bless $self, $class; + return $self; +} + +sub getName() { + my $self = shift; + return $self->{'name'}; +} + +sub getTimeColumn() { + my $self = shift; + return $self->{'timeColumn'}; +} + +sub agreggateEventsByTimePeriod { + my ($self, $timeperiodList, $start, $end, $liveServiceByTpId, $mode) = @_; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + + my $rangesByTP = ($self->{"timePeriodObj"})->getTimeRangesForPeriodAndTpList($timeperiodList, $start, $end); + my $query = "SELECT e.host_id,e.service_id, start_time, end_time, ack_time, state, last_update"; + $query .= " FROM `servicestateevents` e"; + $query .= " RIGHT JOIN (select host_id,service_id from mod_bi_tmp_today_services group by host_id,service_id) t2"; + $query .= " ON e.host_id = t2.host_id AND e.service_id = t2.service_id"; + $query .= " WHERE start_time < ".$end.""; + $query .= " AND end_time > ".$start.""; + $query .= " AND in_downtime = 0 "; + $query .= " ORDER BY start_time "; + + my $serviceEventObjects = $self->{"biServiceStateEventsObj"}; + my $sth = $db->query($query); + $serviceEventObjects->createTempBIEventsTable(); + $serviceEventObjects->prepareTempQuery(); + + while (my $row = $sth->fetchrow_hashref()) { + if (!defined($row->{'end_time'})) { + $row->{'end_time'} = $end; + } + while (my ($timeperiodID, $timeRanges) = each %$rangesByTP) { + my @tab = (); + $tab[0] = $row->{'host_id'}; + $tab[1] = $row->{'service_id'}; + $tab[2] = $liveServiceByTpId->{$timeperiodID}; + $tab[3] = $row->{'state'}; + if ($mode eq 'daily') { + $timeRanges = ($self->{"timePeriodObj"})->getTimeRangesForPeriod($timeperiodID, $row->{'start_time'}, $row->{'end_time'}); + } + ($tab[4], $tab[5]) = $self->processIncidentForTp($timeRanges,$row->{'start_time'}, $row->{'end_time'}); + $tab[6] = $row->{'end_time'}; + $tab[7] = defined($row->{ack_time}) ? $row->{ack_time} : 0; + $tab[8] = $row->{last_update}; + if (defined($tab[4]) && $tab[4] != -1) { + $serviceEventObjects->bindParam(\@tab); + } + } + } + ($db->getInstance)->commit; +} + +sub processIncidentForTp { + my ($self, $timeRanges, $start, $end) = @_; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + + my $rangeSize = scalar(@$timeRanges); + my $duration = 0; + my $slaDuration = 0; + my $range = 0; + my $i = 0; + my $processed = 0; + my $slaStart = $start; + my $slaStartModified = 0; + + foreach(@$timeRanges) { + my $currentStart = $start; + my $currentEnd = $end; + $range = $_; + my ($rangeStart, $rangeEnd) = ($range->[0], $range->[1]); + if ($currentStart < $rangeEnd && $currentEnd > $rangeStart) { + $processed = 1; + if ($currentStart > $rangeStart) { + $slaStartModified = 1; + } elsif ($currentStart < $rangeStart) { + $currentStart = $rangeStart; + if (!$slaStartModified) { + $slaStart = $currentStart; + $slaStartModified = 1; + } + } + if ($currentEnd > $rangeEnd) { + $currentEnd = $rangeEnd; + } + $slaDuration += $currentEnd - $currentStart; + } + } + if (!$processed) { + return (-1, -1, -1); + } + return ($slaStart, $slaDuration); +} + +sub dailyPurge { + my $self = shift; + my $db = $self->{"centstorage"}; + my $logger = $self->{"logger"}; + my ($end) = @_; + + $logger->writeLog("DEBUG", "[PURGE] [servicestateevents] purging data older than ".$end); + my $query = "DELETE FROM `servicestateevents` where end_time < UNIX_TIMESTAMP('".$end."')"; + $db->query($query); +} + +sub getNbEvents { + my $self = shift; + my $db = $self->{"centstorage"}; + my ($start, $end) = @_; + my $nbEvents = 0; + my $logger = $self->{"logger"}; + + my $query = "SELECT count(*) as nbEvents"; + $query .= " FROM `servicestateevents` e"; + $query .= " RIGHT JOIN (select host_id,service_id from mod_bi_tmp_today_services group by host_id,service_id) t2"; + $query .= " ON e.host_id = t2.host_id AND e.service_id = t2.service_id"; + $query .= " WHERE start_time < ".$end.""; + $query .= " AND end_time > ".$start.""; + $query .= " AND in_downtime = 0 "; + + my $sth = $db->query($query); + + while (my $row = $sth->fetchrow_hashref()) { + $nbEvents = $row->{'nbEvents'}; + } + return $nbEvents; +} + +1; diff --git a/gorgone/gorgone/standard/constants.pm b/gorgone/gorgone/standard/constants.pm index c7303435066..c37b860590c 100644 --- a/gorgone/gorgone/standard/constants.pm +++ b/gorgone/gorgone/standard/constants.pm @@ -43,7 +43,9 @@ BEGIN { GORGONE_MODULE_CENTREON_AUTODISCO_SVC_PROGRESS => 400, - GORGONE_MODULE_CENTREON_AUDIT_PROGRESS => 500 + GORGONE_MODULE_CENTREON_AUDIT_PROGRESS => 500, + + GORGONE_MODULE_CENTREON_MBIETL_PROGRESS => 600 ); } diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 009a37ab95f..a17e6e921a7 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -19,6 +19,7 @@ Requires: perl(ZMQ::Constants) Requires: perl(JSON::XS) Requires: perl(JSON::PP) Requires: perl(XML::Simple) +Requires: perl(XML::LibXML::Simple) Requires: perl(Net::SMTP) Requires: perl(YAML::XS) Requires: perl(DBD::SQLite) @@ -35,6 +36,7 @@ Requires: perl(NetAddr::IP) Requires: perl(Hash::Merge) Requires: perl(Clone) Requires: perl(Sys::Syslog) +Requires: perl(DateTime) AutoReqProv: no %description From 6d1a0e35525e54f6a592028085a7565b8f0824b4 Mon Sep 17 00:00:00 2001 From: Luiz Costa <luizgustavo@luizgustavo.pro.br> Date: Tue, 19 Apr 2022 14:59:31 +0100 Subject: [PATCH 653/948] Add two missing perl packages to deb (#221) Co-authored-by: Luiz Costa <me@luizgustavo.pro.br> --- gorgone/ci/debian/control | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gorgone/ci/debian/control b/gorgone/ci/debian/control index 217106e3f65..1a59e3330a1 100644 --- a/gorgone/ci/debian/control +++ b/gorgone/ci/debian/control @@ -34,6 +34,8 @@ Depends: centreon-common, libnet-ssh2-perl, libnet-ldap-perl, + libdatetime-perl, + libxml-libxml-simple-perl, libuuid-perl, libdigest-md5-file-perl, libjson-pp-perl, From be4ee72ea37af3ad9c5f92c3258ac1ecc5cd78c1 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Tue, 19 Apr 2022 16:19:28 +0200 Subject: [PATCH 654/948] MON-12908: add centreon-mbi deps Try::Tiny (#222) --- gorgone/packaging/centreon-gorgone.spectemplate | 1 + 1 file changed, 1 insertion(+) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index a17e6e921a7..8b5631aa0a4 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -37,6 +37,7 @@ Requires: perl(Hash::Merge) Requires: perl(Clone) Requires: perl(Sys::Syslog) Requires: perl(DateTime) +Requires: perl(Try::Tiny) AutoReqProv: no %description From da92b69f9f47fc8afdabca83c2bc110c2931c959 Mon Sep 17 00:00:00 2001 From: Luiz Costa <luizgustavo@luizgustavo.pro.br> Date: Tue, 19 Apr 2022 15:30:25 +0100 Subject: [PATCH 655/948] add missing perl package to deb (#223) --- gorgone/ci/debian/control | 1 + 1 file changed, 1 insertion(+) diff --git a/gorgone/ci/debian/control b/gorgone/ci/debian/control index 1a59e3330a1..bef3b5f3fea 100644 --- a/gorgone/ci/debian/control +++ b/gorgone/ci/debian/control @@ -35,6 +35,7 @@ Depends: libnet-ssh2-perl, libnet-ldap-perl, libdatetime-perl, + libtry-tiny-perl, libxml-libxml-simple-perl, libuuid-perl, libdigest-md5-file-perl, From b9ddeb30860a9a9541b61634139dd0174a552cec Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Tue, 19 Apr 2022 17:06:30 +0200 Subject: [PATCH 656/948] MON-12908: centreon-mbi etl - fix daily option (#224) --- .../modules/centreon/mbi/etl/event/main.pm | 12 ++++++++---- .../modules/centreon/mbi/etl/perfdata/main.pm | 18 ++++++++++++------ .../gorgone/modules/centreon/mbi/libs/Utils.pm | 4 ++-- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/mbi/etl/event/main.pm b/gorgone/gorgone/modules/centreon/mbi/etl/event/main.pm index 7f6d760ba1f..bc59ac696b7 100644 --- a/gorgone/gorgone/modules/centreon/mbi/etl/event/main.pm +++ b/gorgone/gorgone/modules/centreon/mbi/etl/event/main.pm @@ -193,16 +193,20 @@ sub dailyProcessing { type => 'sql', db => 'centstorage', sql => [ - '[PARTITIONS] Add partition [p' . $partName . '] on table [mod_bi_hostavailability]', - "ALTER TABLE `mod_bi_hostavailability` ADD PARTITION (PARTITION `p$partName` VALUES LESS THAN(" . $epoch . "))" + [ + '[PARTITIONS] Add partition [p' . $partName . '] on table [mod_bi_hostavailability]', + "ALTER TABLE `mod_bi_hostavailability` ADD PARTITION (PARTITION `p$partName` VALUES LESS THAN(" . $epoch . "))" + ] ] }; push @{$etl->{run}->{schedule}->{event}->{stages}->[0]}, { type => 'sql', db => 'centstorage', sql => [ - '[PARTITIONS] Add partition [p' . $partName . '] on table [mod_bi_serviceavailability]', - "ALTER TABLE `mod_bi_serviceavailability` ADD PARTITION (PARTITION `p$partName` VALUES LESS THAN(" . $epoch . "))" + [ + '[PARTITIONS] Add partition [p' . $partName . '] on table [mod_bi_serviceavailability]', + "ALTER TABLE `mod_bi_serviceavailability` ADD PARTITION (PARTITION `p$partName` VALUES LESS THAN(" . $epoch . "))" + ] ] }; diff --git a/gorgone/gorgone/modules/centreon/mbi/etl/perfdata/main.pm b/gorgone/gorgone/modules/centreon/mbi/etl/perfdata/main.pm index 1142bc8ece2..c5fbd0ab3a8 100644 --- a/gorgone/gorgone/modules/centreon/mbi/etl/perfdata/main.pm +++ b/gorgone/gorgone/modules/centreon/mbi/etl/perfdata/main.pm @@ -307,8 +307,10 @@ sub dailyProcessing { type => 'sql', db => 'centstorage', sql => [ - '[PARTITIONS] Add partition [p' . $partName . '] on table [mod_bi_metricdailyvalue]', - "ALTER TABLE `mod_bi_metricdailyvalue` ADD PARTITION (PARTITION `p$partName` VALUES LESS THAN(" . $epoch . "))" + [ + '[PARTITIONS] Add partition [p' . $partName . '] on table [mod_bi_metricdailyvalue]', + "ALTER TABLE `mod_bi_metricdailyvalue` ADD PARTITION (PARTITION `p$partName` VALUES LESS THAN(" . $epoch . "))" + ] ] }; if ($etl->{run}->{etlProperties}->{'perfdata.granularity'} ne 'day') { @@ -316,8 +318,10 @@ sub dailyProcessing { type => 'sql', db => 'centstorage', sql => [ - '[PARTITIONS] Add partition [p' . $partName . '] on table [mod_bi_metrichourlyvalue]', - "ALTER TABLE `mod_bi_metrichourlyvalue` ADD PARTITION (PARTITION `p$partName` VALUES LESS THAN(" . $epoch . "))" + [ + '[PARTITIONS] Add partition [p' . $partName . '] on table [mod_bi_metrichourlyvalue]', + "ALTER TABLE `mod_bi_metrichourlyvalue` ADD PARTITION (PARTITION `p$partName` VALUES LESS THAN(" . $epoch . "))" + ] ] }; } @@ -326,8 +330,10 @@ sub dailyProcessing { type => 'sql', db => 'centstorage', sql => [ - '[PARTITIONS] Add partition [p' . $partName . '] on table [mod_bi_metriccentiledailyvalue]', - "ALTER TABLE `mod_bi_metriccentiledailyvalue` ADD PARTITION (PARTITION `p$partName` VALUES LESS THAN(" . $epoch . "))" + [ + '[PARTITIONS] Add partition [p' . $partName . '] on table [mod_bi_metriccentiledailyvalue]', + "ALTER TABLE `mod_bi_metriccentiledailyvalue` ADD PARTITION (PARTITION `p$partName` VALUES LESS THAN(" . $epoch . "))" + ] ] }; } diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/Utils.pm b/gorgone/gorgone/modules/centreon/mbi/libs/Utils.pm index 51d0cb198b2..ef2a404d3dc 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/Utils.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/Utils.pm @@ -41,14 +41,14 @@ sub checkBasicOptions { my ($self, $options) = @_; # check execution mode daily to extract yesterday data or rebuild to get more historical data - if (($options->{daily} == 0 && $options->{rebuild} == 0 && $options->{create_tables} == 0 && (!defined($options->{centile}) || $options->{centile} == 0)) + if (($options->{daily} == 0 && $options->{rebuild} == 0 && (!defined($options->{create_tables}) || $options->{create_tables} == 0) && (!defined($options->{centile}) || $options->{centile} == 0)) || ($options->{daily} == 1 && $options->{rebuild} == 1)) { $self->{logger}->writeLogError("Specify one execution method. Check program help for more informations"); return 1; } # check if options are set correctly for rebuild mode - if (($options->{rebuild} == 1 || $options->{create_tables} == 1) + if (($options->{rebuild} == 1 || (defined($options->{create_tables}) && $options->{create_tables} == 1)) && ($options->{start} ne '' && $options->{end} eq '') || ($options->{start} eq '' && $options->{end} ne '')) { $self->{logger}->writeLogError("Specify both options --start and --end or neither of them to use default data retention options"); From 5b2b7612f5c30a8ac0924ee56ee8249d89d92920 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Fri, 22 Apr 2022 13:58:45 +0200 Subject: [PATCH 657/948] MON-12908: centreon-mbi etl - fix monthly perfdata (#226) --- .../centreon/mbi/libs/bi/BIHostStateEvents.pm | 17 +++++---- .../mbi/libs/bi/BIServiceStateEvents.pm | 14 +++++--- .../centreon/mbi/libs/bi/MetricDailyValue.pm | 18 +++++----- .../mbi/libs/bi/MetricMonthCapacity.pm | 35 +++++++++---------- .../mbi/libs/bi/ServiceAvailability.pm | 1 - .../centreon/mbi/libs/centstorage/Metrics.pm | 4 +-- 6 files changed, 46 insertions(+), 43 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHostStateEvents.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHostStateEvents.pm index e6c44fcaa3b..2593910f37f 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHostStateEvents.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHostStateEvents.pm @@ -136,7 +136,7 @@ sub getDayEvents { my %results = (); my $query = "SELECT start_time, end_time, state, modbihost_id"; - $query .= " FROM `".$self->{'name'}."`"; + $query .= " FROM `" . $self->{name} . "`"; $query .= " WHERE `start_time` < ".$end.""; $query .= " AND `end_time` > ".$start.""; $query .= " AND `state` in (0,1,2)"; @@ -144,16 +144,19 @@ sub getDayEvents { my $sth = $db->query($query); #For each events, for the current day, calculate statistics for the day - while (my $row = $sth->fetchrow_hashref()) { - - my $entryID = $row->{"modbihost_id"}; + my $rows = []; + while (my $row = ( + shift(@$rows) || + shift(@{$rows = $sth->fetchall_arrayref(undef,10_000) || []}) ) + ) { + my $entryID = $row->[3]; my ($started, $ended) = (0, 0); my $rangeSize = scalar(@$ranges); my $eventDuration = 0; for(my $count = 0; $count < $rangeSize; $count++) { - my $currentStart = $row->{"start_time"}; - my $currentEnd = $row->{"end_time"}; + my $currentStart = $row->[0]; + my $currentEnd = $row->[1]; my $range = $ranges->[$count]; my ($rangeStart, $rangeEnd) = ($range->[0], $range->[1]); @@ -181,7 +184,7 @@ sub getDayEvents { } my $stats = $results{$entryID}; - my $state = $row->{'state'}; + my $state = $row->[2]; if ($state == 0) { $stats->[0] += $eventDuration; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIServiceStateEvents.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIServiceStateEvents.pm index 939d46c522e..3840e1c9525 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIServiceStateEvents.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIServiceStateEvents.pm @@ -149,15 +149,19 @@ sub getDayEvents { return \%results; } - while (my $row = $sth->fetchrow_hashref()) { - my $entryID = $row->{modbiservice_id}; + my $rows = []; + while (my $row = ( + shift(@$rows) || + shift(@{$rows = $sth->fetchall_arrayref(undef,10_000) || []}) ) + ) { + my $entryID = $row->[3]; my ($started, $ended) = (0,0); my $rangeSize = scalar(@$ranges); my $eventDuration = 0; for (my $count = 0; $count < $rangeSize; $count++) { - my $currentStart = $row->{start_time}; - my $currentEnd = $row->{end_time}; + my $currentStart = $row->[0]; + my $currentEnd = $row->[1]; my $range = $ranges->[$count]; my ($rangeStart, $rangeEnd) = ($range->[0], $range->[1]); @@ -185,7 +189,7 @@ sub getDayEvents { $results{$entryID} = \@tab; } my $stats = $results{$entryID}; - my $state = $row->{state}; + my $state = $row->[2]; if ($state == 0) { $stats->[0] += $eventDuration; } elsif ($state == 1) { diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricDailyValue.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricDailyValue.pm index f221ee8b136..c66700589da 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricDailyValue.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricDailyValue.pm @@ -127,17 +127,17 @@ sub getMetricCapacityValuesOnPeriod { $sth = $db->query($query); while (my $row = $sth->fetchrow_hashref()) { - my $entry = $data{$row->{"servicemetric_id"}.";".$row->{"liveservice_id"}}; + my $entry = $data{$row->{servicemetric_id} . ';' . $row->{liveservice_id}}; if (defined($entry)) { - $entry->[4] = $row->{"last_value"}; - $entry->[5] = $row->{"total"}; - }else { + $entry->[4] = $row->{last_value}; + $entry->[5] = $row->{total}; + } else { my @table; - $table[0] = $row->{"servicemetric_id"}; - $table[1] = $row->{"liveservice_id"}; - $table[4] = $row->{"last_value"}; - $table[5] = $row->{"total"}; - $data{$row->{"servicemetric_id"}.";".$row->{"liveservice_id"}} = \@table; + $table[0] = $row->{servicemetric_id}; + $table[1] = $row->{liveservice_id}; + $table[4] = $row->{last_value}; + $table[5] = $row->{total}; + $data{$row->{servicemetric_id} . ';' . $row->{liveservice_id}} = \@table; } } return \%data; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricMonthCapacity.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricMonthCapacity.pm index 4462e0d2883..89b3f208150 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricMonthCapacity.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricMonthCapacity.pm @@ -53,38 +53,37 @@ sub getTimeColumn() { } sub insertStats { - my $self = shift; - my $db = $self->{centstorage}; - my ($time_id, $data) = @_; + my $self = shift; + my $db = $self->{centstorage}; + my ($time_id, $data) = @_; my $insertParam = 5000; - my $query_start = "INSERT INTO `" . $self->{name} . "`". - "(`time_id`, `servicemetric_id`, `liveservice_id`,". - " `first_value`, `first_total`, `last_value`, `last_total`)". - " VALUES "; - my $counter = 0; + my $query_start = "INSERT INTO `" . $self->{name} . "`". + "(`time_id`, `servicemetric_id`, `liveservice_id`,". + " `first_value`, `first_total`, `last_value`, `last_total`)". + " VALUES "; + my $counter = 0; my $query = $query_start; my $append = ''; - while (my ($key, $entry) = each %$data) { + while (my ($key, $entry) = each %$data) { $query .= $append . "($time_id"; - my $size = scalar(@$entry); - for (my $i = 0; $i < $size; $i++) { - $query .= ', ' . (defined($entry->[$i]) ? $entry->[$i] : 'NULL'); - } + for (my $i = 0; $i <= 5; $i++) { + $query .= ', ' . (defined($entry->[$i]) ? $entry->[$i] : 'NULL'); + } $query .= ')'; $append = ','; - $counter++; + $counter++; if ($counter >= $insertParam) { $db->query($query); $query = $query_start; - $counter = 0; + $counter = 0; $append = ''; - } - } - $db->query($query) if ($counter > 0); + } + } + $db->query($query) if ($counter > 0); } 1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/ServiceAvailability.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/ServiceAvailability.pm index ad4a674908b..5f4b1167aac 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/bi/ServiceAvailability.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/ServiceAvailability.pm @@ -219,7 +219,6 @@ sub getHGMonthAvailability_optimised { while (my $row = $sth->fetchrow_hashref()) { my ($totalwarnEvents, $totalCritEvents, $totalUnknownEvents) = ($row->{'warningEvents'},$row->{'criticalEvents'},$row->{'unknownEvents'}); - my ($mtrs, $mtbf, $mtbsi) = (undef, undef, undef); if (defined($totalCritEvents) && $totalCritEvents != 0) { $mtrs = $row->{'unav_time'}/$totalCritEvents; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/centstorage/Metrics.pm b/gorgone/gorgone/modules/centreon/mbi/libs/centstorage/Metrics.pm index 90d9bdba383..d4fc5bf2955 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/centstorage/Metrics.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/centstorage/Metrics.pm @@ -96,7 +96,6 @@ sub getMetricValueByHour { $query .= "ctime >=UNIX_TIMESTAMP('".$start."') AND ctime < UNIX_TIMESTAMP('".$end."') "; $query .= "GROUP BY id_metric, date_format(FROM_UNIXTIME(ctime), '".$dateFormat."')"; - $logger->writeLog("DEBUG", "Getting min, max, avg values by metric into temporary tables"); $db->query($query); $self->addIndexTempTableMetricMinMaxAvgValues("hour"); } @@ -131,8 +130,7 @@ sub getMetricsValueByDay { $query =~ s/OR $//; $query .= "GROUP BY id_metric"; - - $logger->writeLog("DEBUG", "Getting min, max, avg values by metric"); + $db->query($query); $self->addIndexTempTableMetricMinMaxAvgValues("day"); $self->getFirstAndLastValues($start_date, $end_date, $useMemory); From b0713ae2d16fd54a59115dc7d3d5950beee948ab Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Thu, 28 Apr 2022 15:19:21 +0200 Subject: [PATCH 658/948] fix(clapi): protect password for shell execution (#228) --- gorgone/gorgone/class/tpapi/clapi.pm | 9 ++++++++- gorgone/gorgone/modules/centreon/legacycmd/class.pm | 10 +++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/gorgone/gorgone/class/tpapi/clapi.pm b/gorgone/gorgone/class/tpapi/clapi.pm index 1d5e0a14141..d7c1810be8e 100644 --- a/gorgone/gorgone/class/tpapi/clapi.pm +++ b/gorgone/gorgone/class/tpapi/clapi.pm @@ -59,6 +59,13 @@ sub get_password { return undef; } + if (defined($options{protected}) && $options{protected} == 1) { + my $password = $self->{password}; + $password =~ s/\$/\\\$/g; + $password =~ s/"/\\"/g; + return $password; + } + return $self->{password}; } @@ -91,7 +98,7 @@ sub get_applycfg_command { return undef; } - return 'centreon -u ' . $self->{username} . ' -p ' . $self->{password} . ' -a APPLYCFG -v ' . $options{poller_id}; + return 'centreon -u "' . $self->{username} . '" -p "' . $self->get_password(protected => 1) . '" -a APPLYCFG -v ' . $options{poller_id}; } 1; diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index e64a1d792b2..cdf024a72e6 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -516,8 +516,8 @@ sub execute_cmd { } my $centreon_dir = (defined($connector->{config}->{centreon_dir})) ? $connector->{config}->{centreon_dir} : '/usr/share/centreon'; - my $cmd = $centreon_dir . '/bin/centreon -u ' . $self->{clapi_user} . ' -p ' . - $self->{clapi_password} . ' -w -o CentreonWorker -a processQueue'; + my $cmd = $centreon_dir . '/bin/centreon -u "' . $self->{clapi_user} . '" -p "' . + $self->{clapi_password} . '" -w -o CentreonWorker -a processQueue'; $self->send_internal_action( action => 'COMMAND', target => undef, @@ -571,8 +571,8 @@ sub action_addimporttaskwithparent { my $centreon_dir = (defined($connector->{config}->{centreon_dir})) ? $connector->{config}->{centreon_dir} : '/usr/share/centreon'; - my $cmd = $centreon_dir . '/bin/centreon -u ' . $self->{clapi_user} . ' -p ' . - $self->{clapi_password} . ' -w -o CentreonWorker -a processQueue'; + my $cmd = $centreon_dir . '/bin/centreon -u "' . $self->{clapi_user} . '" -p "' . + $self->{clapi_password} . '" -w -o CentreonWorker -a processQueue'; $self->send_internal_action( action => 'COMMAND', token => $options{token}, @@ -788,7 +788,7 @@ sub run { ); $self->{clapi_user} = $self->{tpapi_clapi}->get_username(); - $self->{clapi_password} = $self->{tpapi_clapi}->get_password(); + $self->{clapi_password} = $self->{tpapi_clapi}->get_password(protected => 1); # Connect internal $connector->{internal_socket} = gorgone::standard::library::connect_com( From a32e766118239ebddddf3dcfc1f6cbe107c444ca Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Thu, 28 Apr 2022 16:53:47 +0200 Subject: [PATCH 659/948] fix(proxy): die during synclogs (#229) --- gorgone/gorgone/modules/core/proxy/hooks.pm | 2 +- gorgone/gorgone/standard/library.pm | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index 7b141e74fab..eab23328bd4 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -699,7 +699,7 @@ sub synclog { # We check if we need synclogs if ($stop == 0) { $synctime_lasttime = time(); - full_sync_history(dbh => $options{dbh}, logger => $options{logger}); + full_sync_history(gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger}); } } diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index 769822a6f4d..7a464f55fb9 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -406,7 +406,11 @@ sub synclogs { my $name = $options{gorgone}->{modules_id}->{$options{gorgone_config}->{gorgonecore}->{proxy_name}}; my $method; if (defined($name) && ($method = $name->can('synclog'))) { - $method->(dbh => $options{gorgone}->{db_gorgone}, logger => $options{gorgone}->{logger}); + $method->( + gorgone => $options{gorgone}, + dbh => $options{gorgone}->{db_gorgone}, + logger => $options{gorgone}->{logger} + ); return (GORGONE_ACTION_BEGIN, { action => 'synclog', message => 'synclog launched' }); } } From 098d4080da689984e2a8f6425ee7dbdc5abc859d Mon Sep 17 00:00:00 2001 From: Thomas Arnaud <38663853+Nohzoh@users.noreply.github.com> Date: Wed, 4 May 2022 09:44:06 +0200 Subject: [PATCH 660/948] fix(ad): use string for ml config label values (#230) --- gorgone/gorgone/modules/centreon/anomalydetection/class.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm index f39a22e5c06..ec14f28ddc3 100644 --- a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm +++ b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm @@ -298,8 +298,8 @@ sub saas_register_metrics { { name => $self->{centreon_metrics}->{$_}->{metric_name}, labels => { - host_id => $self->{centreon_metrics}->{$_}->{host_id}, - service_id => $self->{centreon_metrics}->{$_}->{service_id} + host_id => "" . $self->{centreon_metrics}->{$_}->{host_id}, + service_id => "" . $self->{centreon_metrics}->{$_}->{service_id} }, preprocessingOptions => { bucketize => { From 280c34705762699f8bf97bf60f14e655f38e9ef9 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Tue, 10 May 2022 08:49:16 +0200 Subject: [PATCH 661/948] enh: handle almalinux os for audit and plugins install (#231) --- .../gorgone/modules/centreon/audit/metrics/centreon/packages.pm | 2 +- gorgone/gorgone/modules/core/action/class.pm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/audit/metrics/centreon/packages.pm b/gorgone/gorgone/modules/centreon/audit/metrics/centreon/packages.pm index 52049b0f419..19eb8d9188c 100644 --- a/gorgone/gorgone/modules/centreon/audit/metrics/centreon/packages.pm +++ b/gorgone/gorgone/modules/centreon/audit/metrics/centreon/packages.pm @@ -79,7 +79,7 @@ sub metrics { if ($options{os} =~ /Debian|Ubuntu/i) { dpkg_list(metrics => $metrics); - } elsif ($options{os} =~ /CentOS|Redhat/i) { + } elsif ($options{os} =~ /CentOS|Redhat|almalinux/i) { rpm_list(metrics => $metrics); } else { $metrics->{status_code} = 1; diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index 7a92a1946b7..fb6a59e3d0a 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -154,7 +154,7 @@ sub get_package_manager { $self->{package_manager} = 'unknown'; if ($os =~ /Debian|Ubuntu/i) { $self->{package_manager} = 'deb'; - } elsif ($os =~ /CentOS|Redhat/i) { + } elsif ($os =~ /CentOS|Redhat|almalinux/i) { $self->{package_manager} = 'rpm'; } } From bad7dda6438d3c2a8a132311d94e29267552178a Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Tue, 10 May 2022 09:13:28 +0200 Subject: [PATCH 662/948] enh(legacy): cbd reload after remote-server import (#232) --- .../modules/centreon/legacycmd/class.pm | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index cdf024a72e6..f5f9adde2dd 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -312,7 +312,8 @@ sub execute_cmd { data => { logging => $options{logging}, content => { - parent_id => $options{param} + parent_id => $options{param}, + cbd_reload => 'sudo ' . $self->{pollers}->{ $options{target} }->{broker_reload_command} } } ); @@ -582,10 +583,23 @@ sub action_addimporttaskwithparent { { command => $cmd } + ], + parameters => { no_fork => 1 } + } + ); + $self->send_internal_action( + action => 'COMMAND', + token => $options{token}, + data => { + logging => $options{data}->{logging}, + content => [ + { + command => $options{data}->{content}->{cbd_reload} + } ] } ); - + $self->send_log( code => GORGONE_ACTION_FINISH_OK, token => $options{token}, From f995f1243af8f32de25bcf62412ed50c77440c61 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Tue, 10 May 2022 14:53:36 +0200 Subject: [PATCH 663/948] enh: handle oracle linux os for audit and plugins install (#233) --- .../gorgone/modules/centreon/audit/metrics/centreon/packages.pm | 2 ++ gorgone/gorgone/modules/core/action/class.pm | 2 ++ 2 files changed, 4 insertions(+) diff --git a/gorgone/gorgone/modules/centreon/audit/metrics/centreon/packages.pm b/gorgone/gorgone/modules/centreon/audit/metrics/centreon/packages.pm index 19eb8d9188c..51281478b79 100644 --- a/gorgone/gorgone/modules/centreon/audit/metrics/centreon/packages.pm +++ b/gorgone/gorgone/modules/centreon/audit/metrics/centreon/packages.pm @@ -81,6 +81,8 @@ sub metrics { dpkg_list(metrics => $metrics); } elsif ($options{os} =~ /CentOS|Redhat|almalinux/i) { rpm_list(metrics => $metrics); + } elsif ($options{os} eq 'ol' || $options{os} =~ /Oracle Linux/i) { + rpm_list(metrics => $metrics); } else { $metrics->{status_code} = 1; $metrics->{status_message} = 'unsupported os'; diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index fb6a59e3d0a..91ebc75544a 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -156,6 +156,8 @@ sub get_package_manager { $self->{package_manager} = 'deb'; } elsif ($os =~ /CentOS|Redhat|almalinux/i) { $self->{package_manager} = 'rpm'; + } elsif ($os eq 'ol' || $os =~ /Oracle Linux/i) { + $self->{package_manager} = 'rpm'; } } From e33b3aab7cfea3cb6fc992904e6129eb2428bf90 Mon Sep 17 00:00:00 2001 From: Luiz Costa <luizgustavo@luizgustavo.pro.br> Date: Tue, 10 May 2022 14:37:58 +0100 Subject: [PATCH 664/948] =?UTF-8?q?Add=20dynamic=20variable=20and=20new=20?= =?UTF-8?q?files=20to=20=E2=80=A6=20(#227)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add dynamic variable version to debian control file and new files to install * missing permissions * (fix) bypass process usrlocal rules by debian Co-authored-by: Luiz Costa <me@luizgustavo.pro.br> Co-authored-by: Zakaria Guennoune <83596451+zguennoune02@users.noreply.github.com> --- gorgone/ci/debian/centreon-gorgone.install | 20 ++++++++++++-------- gorgone/ci/debian/centreon-gorgone.postinst | 17 +++++++++++++++++ gorgone/ci/debian/control | 3 ++- gorgone/ci/debian/rules | 5 +++++ gorgone/ci/debian/substvars | 1 + gorgone/ci/scripts/gorgone-deb-package.sh | 1 + 6 files changed, 38 insertions(+), 9 deletions(-) create mode 100644 gorgone/ci/debian/substvars diff --git a/gorgone/ci/debian/centreon-gorgone.install b/gorgone/ci/debian/centreon-gorgone.install index f42dc635f17..664fd3010ed 100644 --- a/gorgone/ci/debian/centreon-gorgone.install +++ b/gorgone/ci/debian/centreon-gorgone.install @@ -1,8 +1,12 @@ -gorgoned usr/bin -contrib/* usr/sbin -packaging/config.yaml etc/centreon-gorgone -debian/extra/gorgoned.service lib/systemd/system -debian/extra/gorgoned etc/default -gorgone/class/* usr/share/perl5/gorgone/class -gorgone/modules/* usr/share/perl5/gorgone/modules -gorgone/standard/* usr/share/perl5/gorgone/standard +gorgoned usr/bin +contrib/* usr/local/bin +packaging/config.yaml etc/centreon-gorgone +packaging/centreon.yaml etc/centreon-gorgone/config.d +packaging/centreon-api.yaml etc/centreon-gorgone/config.d +packaging/centreon-audit.yaml etc/centreon-gorgone/config.d +packaging/sudoers.d/centreon-gorgone etc/sudoers.d +debian/extra/gorgoned.service lib/systemd/system +debian/extra/gorgoned etc/default +gorgone/class/* usr/share/perl5/gorgone/class +gorgone/modules/* usr/share/perl5/gorgone/modules +gorgone/standard/* usr/share/perl5/gorgone/standard diff --git a/gorgone/ci/debian/centreon-gorgone.postinst b/gorgone/ci/debian/centreon-gorgone.postinst index 14235214250..06dd74bbc00 100755 --- a/gorgone/ci/debian/centreon-gorgone.postinst +++ b/gorgone/ci/debian/centreon-gorgone.postinst @@ -32,12 +32,29 @@ if [ "$1" = "configure" ] ; then /var/lib/centreon-gorgone \ /var/log/centreon-gorgone + chown root:root \ + /usr/local/bin/gorgone_config_init.pl \ + /usr/local/bin/gorgone_audit.pl \ + /usr/local/bin/gorgone_install_plugins.pl + + chmod 0755 \ + /usr/local/bin/gorgone_config_init.pl \ + /usr/local/bin/gorgone_audit.pl + + chmod 0750 \ + /usr/local/bin/gorgone_install_plugins.pl + if [ ! -d /var/lib/centreon-gorgone/.ssh -a -d /var/spool/centreon/.ssh ] ; then /usr/bin/cp -r /var/spool/centreon/.ssh /var/lib/centreon-gorgone/.ssh /usr/bin/chown -R centreon-gorgone:centreon-gorgone /var/lib/centreon-gorgone/.ssh /usr/bin/chmod 600 /var/lib/centreon-gorgone/.ssh/id_rsa fi + # rename files to priority + mv /etc/centreon-gorgone/config.d/centreon.yaml /etc/centreon-gorgone/config.d/30-centreon.yaml + mv /etc/centreon-gorgone/config.d/centreon-api.yaml /etc/centreon-gorgone/config.d/31-centreon-api.yaml + mv /etc/centreon-gorgone/config.d/centreon-audit.yaml /etc/centreon-gorgone/config.d/50-centreon-audit.yaml + systemctl preset gorgoned.service || : >/dev/null 2>&1 || : fi diff --git a/gorgone/ci/debian/control b/gorgone/ci/debian/control index bef3b5f3fea..6830fc3d231 100644 --- a/gorgone/ci/debian/control +++ b/gorgone/ci/debian/control @@ -31,7 +31,7 @@ Homepage: https://wwww.centreon.com Package: centreon-gorgone Architecture: any Depends: - centreon-common, + centreon-common (>= ${centreon:version}~), libnet-ssh2-perl, libnet-ldap-perl, libdatetime-perl, @@ -59,6 +59,7 @@ Depends: libzmq-constants-perl, libzmq5, zmq-libzmq4-perl, + sudo, ${shlibs:Depends}, ${misc:Depends} Description: Centreon Gorgone. diff --git a/gorgone/ci/debian/rules b/gorgone/ci/debian/rules index d8309f67d01..95af63e770c 100755 --- a/gorgone/ci/debian/rules +++ b/gorgone/ci/debian/rules @@ -4,3 +4,8 @@ export DEB_BUILD_MAINT_OPTIONS = hardening=+all %: dh $@ + +override_dh_gencontrol: + dh_gencontrol -- -Tdebian/substvars + +override_dh_usrlocal: diff --git a/gorgone/ci/debian/substvars b/gorgone/ci/debian/substvars new file mode 100644 index 00000000000..929c65b6607 --- /dev/null +++ b/gorgone/ci/debian/substvars @@ -0,0 +1 @@ +centreon:version= diff --git a/gorgone/ci/scripts/gorgone-deb-package.sh b/gorgone/ci/scripts/gorgone-deb-package.sh index 9f629069c26..15f394d8906 100755 --- a/gorgone/ci/scripts/gorgone-deb-package.sh +++ b/gorgone/ci/scripts/gorgone-deb-package.sh @@ -45,6 +45,7 @@ mkdir -p /build/centreon-gorgone (cd /src && tar czvpf - centreon-gorgone) | dd of=centreon-gorgone-$VERSION.tar.gz cp -rv /src/centreon-gorgone /build/ cp -rv /src/centreon-gorgone/ci/debian /build/centreon-gorgone/ +sed -i "s/^centreon:version=.*$/centreon:version=${VERSION}/" /build/centreon-gorgone/debian/substvars pwd ls -lart From 4e66743898a2a43ab0242e599e13b3bfe008be3f Mon Sep 17 00:00:00 2001 From: Laurent Pinsivy <lpinsivy@centreon.com> Date: Mon, 16 May 2022 17:24:59 +0200 Subject: [PATCH 665/948] enh(spec): add TAR require (#234) --- gorgone/packaging/centreon-gorgone.spectemplate | 1 + 1 file changed, 1 insertion(+) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 8b5631aa0a4..0cb73b184cf 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -38,6 +38,7 @@ Requires: perl(Clone) Requires: perl(Sys::Syslog) Requires: perl(DateTime) Requires: perl(Try::Tiny) +Requires: tar AutoReqProv: no %description From 7d31df56489361f2d0feade51a21dc8ce3fbbedf Mon Sep 17 00:00:00 2001 From: Luiz Costa <luizgustavo@luizgustavo.pro.br> Date: Wed, 18 May 2022 15:05:45 +0100 Subject: [PATCH 666/948] Normalize VERSION variable to Debian format (#235) * Normalize VERSION variable to Debian format * fix suffix version from main version * fix path location Co-authored-by: Luiz Costa <me@luizgustavo.pro.br> --- gorgone/Jenkinsfile | 2 +- gorgone/ci/scripts/gorgone-deb-package.sh | 25 +++++++++++++---------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/gorgone/Jenkinsfile b/gorgone/Jenkinsfile index 4fb83e66e2a..8189ce43294 100644 --- a/gorgone/Jenkinsfile +++ b/gorgone/Jenkinsfile @@ -102,7 +102,7 @@ try { dir('centreon-gorgone') { checkout scm } - sh 'docker run -i --entrypoint "/src/centreon-gorgone/ci/scripts/gorgone-deb-package.sh" -v "$PWD:/src" -e "DISTRIB=Debian11" -e "VERSION=$VERSION" -e "RELEASE=$RELEASE" registry.centreon.com/centreon-gorgone-debian11-dependencies:22.04' + sh 'docker run -i --entrypoint "/src/centreon-gorgone/ci/scripts/gorgone-deb-package.sh" -v "$PWD:/src" -e "DISTRIB=bullseye" -e "VERSION=$VERSION" -e "RELEASE=$RELEASE" registry.centreon.com/centreon-gorgone-debian11-dependencies:22.04' stash name: 'Debian11', includes: '*.deb' archiveArtifacts artifacts: "*.deb" } diff --git a/gorgone/ci/scripts/gorgone-deb-package.sh b/gorgone/ci/scripts/gorgone-deb-package.sh index 15f394d8906..5503225c0e6 100755 --- a/gorgone/ci/scripts/gorgone-deb-package.sh +++ b/gorgone/ci/scripts/gorgone-deb-package.sh @@ -22,11 +22,11 @@ mkdir -p /build/tmp cd /build/tmp apt-cache dumpavail | dpkg --merge-avail -yes | dh-make-perl make --build --version "0.11.3-${RELEASE}" --cpan Mojolicious::Plugin::BasicAuthPlus -dpkg -i libmojolicious-plugin-basicauthplus-perl_0.11.3-${RELEASE}_all.deb +yes | dh-make-perl make --build --version "0.11.3-${DISTRIB}" --cpan Mojolicious::Plugin::BasicAuthPlus +dpkg -i libmojolicious-plugin-basicauthplus-perl_0.11.3-${DISTRIB}_all.deb -yes | dh-make-perl make --build --revision ${RELEASE} --cpan ZMQ::Constants -dpkg -i libzmq-constants-perl_1.04-${RELEASE}_all.deb +yes | dh-make-perl make --build --revision ${DISTRIB} --cpan ZMQ::Constants +dpkg -i libzmq-constants-perl_1.04-${DISTRIB}_all.deb git clone https://github.com/centreon-lab/zmq-libzmq4-perl.git zmq-libzmq4-perl-0.02 mkdir zmq-libzmq4-perl @@ -35,22 +35,25 @@ cd zmq-libzmq4-perl/ tar czpvf zmq-libzmq4-perl-0.02.tar.gz zmq-libzmq4-perl-0.02 cd zmq-libzmq4-perl-0.02 rm -rf debian/changelog -debmake -f "${AUTHOR}" -e "${AUTHOR_EMAIL}" -b ":perl" -r $RELEASE -y +debmake -f "${AUTHOR}" -e "${AUTHOR_EMAIL}" -b ":perl" -r ${DISTRIB} -y debuild-pbuilder -uc -us cd .. -dpkg -i zmq-libzmq4-perl_0.02-${RELEASE}_all.deb +dpkg -i zmq-libzmq4-perl_0.02-${DISTRIB}_all.deb cd /build +# fix version to debian format accept +VERSION="$(echo $VERSION | sed 's/-/./g')" + mkdir -p /build/centreon-gorgone (cd /src && tar czvpf - centreon-gorgone) | dd of=centreon-gorgone-$VERSION.tar.gz cp -rv /src/centreon-gorgone /build/ cp -rv /src/centreon-gorgone/ci/debian /build/centreon-gorgone/ -sed -i "s/^centreon:version=.*$/centreon:version=${VERSION}/" /build/centreon-gorgone/debian/substvars +sed -i "s/^centreon:version=.*$/centreon:version=$(echo $VERSION | egrep -o '^[0-9][0-9].[0-9][0-9]')/" /build/centreon-gorgone/debian/substvars pwd ls -lart cd centreon-gorgone -debmake -f "${AUTHOR}" -e "${AUTHOR_EMAIL}" -u "$VERSION" -b ":perl" -y -r "$RELEASE" +debmake -f "${AUTHOR}" -e "${AUTHOR_EMAIL}" -u "$VERSION" -b ":perl" -y -r "${DISTRIB}" debuild-pbuilder cd /build @@ -58,8 +61,8 @@ if [ -d "$DISTRIB" ] ; then rm -rf "$DISTRIB" fi mkdir $DISTRIB -mv /build/tmp/libmojolicious-plugin-basicauthplus-perl_0.11.3-${RELEASE}_all.deb $DISTRIB/ -mv /build/tmp/zmq-libzmq4-perl/zmq-libzmq4-perl_0.02-${RELEASE}_all.deb $DISTRIB/ -mv /build/tmp/libzmq-constants-perl_1.04-${RELEASE}_all.deb $DISTRIB/ +mv /build/tmp/libmojolicious-plugin-basicauthplus-perl_0.11.3-${DISTRIB}_all.deb $DISTRIB/ +mv /build/tmp/zmq-libzmq4-perl/zmq-libzmq4-perl_0.02-${DISTRIB}_all.deb $DISTRIB/ +mv /build/tmp/libzmq-constants-perl_1.04-${DISTRIB}_all.deb $DISTRIB/ mv /build/*.deb $DISTRIB/ mv /build/$DISTRIB/*.deb /src From f74bd05202719937fcddc41b9a66429517f8e49e Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Fri, 20 May 2022 10:02:00 +0200 Subject: [PATCH 667/948] enh: handle rhel os for audit and plugins install (#236) --- .../gorgone/modules/centreon/audit/metrics/centreon/packages.pm | 2 +- gorgone/gorgone/modules/core/action/class.pm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/audit/metrics/centreon/packages.pm b/gorgone/gorgone/modules/centreon/audit/metrics/centreon/packages.pm index 51281478b79..753aaf5d59c 100644 --- a/gorgone/gorgone/modules/centreon/audit/metrics/centreon/packages.pm +++ b/gorgone/gorgone/modules/centreon/audit/metrics/centreon/packages.pm @@ -79,7 +79,7 @@ sub metrics { if ($options{os} =~ /Debian|Ubuntu/i) { dpkg_list(metrics => $metrics); - } elsif ($options{os} =~ /CentOS|Redhat|almalinux/i) { + } elsif ($options{os} =~ /CentOS|Redhat|rhel|almalinux/i) { rpm_list(metrics => $metrics); } elsif ($options{os} eq 'ol' || $options{os} =~ /Oracle Linux/i) { rpm_list(metrics => $metrics); diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index 91ebc75544a..11ed12fd20c 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -154,7 +154,7 @@ sub get_package_manager { $self->{package_manager} = 'unknown'; if ($os =~ /Debian|Ubuntu/i) { $self->{package_manager} = 'deb'; - } elsif ($os =~ /CentOS|Redhat|almalinux/i) { + } elsif ($os =~ /CentOS|Redhat|rhel|almalinux/i) { $self->{package_manager} = 'rpm'; } elsif ($os eq 'ol' || $os =~ /Oracle Linux/i) { $self->{package_manager} = 'rpm'; From 3c3df6c5492f1aad47f29f65815b71b3df2e0a00 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Tue, 24 May 2022 10:57:20 +0200 Subject: [PATCH 668/948] fix(core): uninitialized value in numeric lt (#238) --- gorgone/gorgone/class/core.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 76e665cb491..a6d6dd7b9e6 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -292,7 +292,7 @@ sub init_external_informations { $self->{identity_infos} = {}; while (my $row = $sth->fetchrow_arrayref()) { - next if (!defined($row->[3])); + next if (!defined($row->[3]) || !defined($row->[2])); if (!defined($self->{identity_infos}->{ $row->[0] })) { $self->{identity_infos}->{ $row->[0] } = { From 2d4e08bc9199498f4b1748daa9c8efb26242828e Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Wed, 1 Jun 2022 15:38:31 +0200 Subject: [PATCH 669/948] enh(debian): remove useless dependencies --- gorgone/ci/debian/control | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/gorgone/ci/debian/control b/gorgone/ci/debian/control index 6830fc3d231..fdda61d66aa 100644 --- a/gorgone/ci/debian/control +++ b/gorgone/ci/debian/control @@ -6,19 +6,15 @@ Build-Depends: debhelper-compat (=12), lsb-base, perl:native, - libnet-ssh2-perl, - libuuid-perl, libdigest-md5-file-perl, libjson-pp-perl, libjson-xs-perl, - libyaml-perl, libyaml-libyaml-perl, + libdbi-perl, libdbd-sqlite3-perl, libdbd-mysql-perl, - libcrypt-cbc-perl, libhttp-daemon-perl, libhttp-daemon-ssl-perl, - libmime-base64-urlsafe-perl, libnetaddr-ip-perl, libschedule-cron-perl, libhash-merge-perl, @@ -32,23 +28,18 @@ Package: centreon-gorgone Architecture: any Depends: centreon-common (>= ${centreon:version}~), - libnet-ssh2-perl, - libnet-ldap-perl, libdatetime-perl, libtry-tiny-perl, libxml-libxml-simple-perl, - libuuid-perl, libdigest-md5-file-perl, libjson-pp-perl, libjson-xs-perl, - libyaml-perl, libyaml-libyaml-perl, + libdbi-perl, libdbd-sqlite3-perl, libdbd-mysql-perl, - libcrypt-cbc-perl, libhttp-daemon-perl, libhttp-daemon-ssl-perl, - libmime-base64-urlsafe-perl, libnetaddr-ip-perl, libschedule-cron-perl, libhash-merge-perl, From 3e2504cd259957bb97a6ae958dd1fd60d5b2401b Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Wed, 1 Jun 2022 16:39:59 +0200 Subject: [PATCH 670/948] fix(debug): missing service category name in debug message (#241) --- .../modules/centreon/mbi/libs/centreon/ServiceCategory.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/centreon/ServiceCategory.pm b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/ServiceCategory.pm index a013174d15b..47a532d0c13 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/centreon/ServiceCategory.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/ServiceCategory.pm @@ -67,7 +67,7 @@ sub getCategory { if(my $row = $sth->fetchrow_hashref()) { $result = $row->{"sc_id"}; }else { - ($self->{"logger"})->writeLog("error", "Cannot find service category '".."' in database"); + ($self->{"logger"})->writeLog("error", "Cannot find service category '" . $scName . "' in database"); } $sth->finish(); From c8be04fbc9b24d9d7c5487b594a73843549bd680 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Thu, 2 Jun 2022 17:25:09 +0200 Subject: [PATCH 671/948] fix(mbi): manage mariadb 10.3.x (#242) --- .../centreon/mbi/libs/bi/MySQLTables.pm | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/MySQLTables.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/MySQLTables.pm index b91981c3f86..a4f94e7ab11 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/bi/MySQLTables.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/MySQLTables.pm @@ -160,25 +160,24 @@ sub isTablePartitioned { } sub getLastPartRange { - my $self = shift; - my $tableName = shift; - - my $query = "SHOW CREATE TABLE $tableName"; + my $self = shift; + my $tableName = shift; - my $partName; - my $sth = $self->{centstorage}->query($query); - if (my $row = $sth->fetchrow_hashref()) { - while ($row->{'Create Table'} =~ /PARTITION `(.*?)` VALUES LESS THAN \(([0-9]+?)\)/g) { - $partName = $1; - $partName =~ s/p(\d{4})(\d{2})(\d{2})/$1-$2-$3/; + my $query = "SHOW CREATE TABLE $tableName"; + + my $partName; + my $sth = $self->{centstorage}->query($query); + if (my $row = $sth->fetchrow_hashref()) { + while ($row->{'Create Table'} =~ /PARTITION.*?p(\d{4})(\d{2})(\d{2}).*?VALUES LESS THAN \([0-9]+?\)/g) { + $partName = "$1-$2-$3"; } - } + } if (!defined($partName)) { die "[UPDATE PARTS] Cannot find table [data_bin] in database"; } - return $partName; + return $partName; } sub deleteEntriesForRebuild { From 724c445bb4d548a5efe7ff08b93b0c1a82cf1032 Mon Sep 17 00:00:00 2001 From: Adrien Morais-Mestre <31647811+adr-mo@users.noreply.github.com> Date: Tue, 7 Jun 2022 14:57:22 +0200 Subject: [PATCH 672/948] core(version): prepare the 22.10 version (#243) --- gorgone/Jenkinsfile | 6 +++--- gorgone/ci/Jenkinsfile | 2 +- gorgone/packaging/centreon-gorgone.spectemplate | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/gorgone/Jenkinsfile b/gorgone/Jenkinsfile index 8189ce43294..40b5f1b87bd 100644 --- a/gorgone/Jenkinsfile +++ b/gorgone/Jenkinsfile @@ -1,7 +1,7 @@ /* ** Variables. */ -def serie = '22.04' +def serie = '22.10' def maintenanceBranch = "${serie}.x" def qaBranch = "dev-${serie}.x" env.REF_BRANCH = 'master' @@ -102,7 +102,7 @@ try { dir('centreon-gorgone') { checkout scm } - sh 'docker run -i --entrypoint "/src/centreon-gorgone/ci/scripts/gorgone-deb-package.sh" -v "$PWD:/src" -e "DISTRIB=bullseye" -e "VERSION=$VERSION" -e "RELEASE=$RELEASE" registry.centreon.com/centreon-gorgone-debian11-dependencies:22.04' + sh 'docker run -i --entrypoint "/src/centreon-gorgone/ci/scripts/gorgone-deb-package.sh" -v "$PWD:/src" -e "DISTRIB=bullseye" -e "VERSION=$VERSION" -e "RELEASE=$RELEASE" registry.centreon.com/centreon-gorgone-debian11-dependencies:22.10' stash name: 'Debian11', includes: '*.deb' archiveArtifacts artifacts: "*.deb" } @@ -125,7 +125,7 @@ try { unstash "Debian11" sh '''for i in $(echo *.deb) do - curl -u $NEXUS_USERNAME:$NEXUS_PASSWORD -H "Content-Type: multipart/form-data" --data-binary "@./$i" https://apt.centreon.com/repository/22.04-$REPO/ + curl -u $NEXUS_USERNAME:$NEXUS_PASSWORD -H "Content-Type: multipart/form-data" --data-binary "@./$i" https://apt.centreon.com/repository/22.10-$REPO/ done ''' } diff --git a/gorgone/ci/Jenkinsfile b/gorgone/ci/Jenkinsfile index 9796213fe75..37bdb060851 100644 --- a/gorgone/ci/Jenkinsfile +++ b/gorgone/ci/Jenkinsfile @@ -8,7 +8,7 @@ stage('Dependencies containers creation') { dir('centreon-gorgone-debian11') { checkout scm dir ('ci/docker') { - sh 'docker build --no-cache . -f Dockerfile.gorgone-debian11-dependencies -t registry.centreon.com/centreon-gorgone-debian11-dependencies:22.04' + sh 'docker build --no-cache . -f Dockerfile.gorgone-debian11-dependencies -t registry.centreon.com/centreon-gorgone-debian11-dependencies:22.10' /*sh 'docker push registry.centreon.com/centreon-gorgone-debian10-dependencies:22.04'*/ } } diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 0cb73b184cf..36225c921d7 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -1,5 +1,5 @@ Name: centreon-gorgone -Version: 22.04.0 +Version: 22.10.0 Release: @RELEASE@%{?dist} Summary: Perl daemon task handlers Group: Applications/System From 539a6966037cfc7600909a178acfffe47aff4f17 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Thu, 9 Jun 2022 11:46:37 +0200 Subject: [PATCH 673/948] add debian plugins auto-install (#245) --- gorgone/contrib/gorgone_install_plugins.pl | 28 +++++++-- gorgone/gorgone/modules/core/action/class.pm | 66 +++++++++++++++++++- 2 files changed, 87 insertions(+), 7 deletions(-) diff --git a/gorgone/contrib/gorgone_install_plugins.pl b/gorgone/contrib/gorgone_install_plugins.pl index 5f8a5023012..970d25f55f4 100644 --- a/gorgone/contrib/gorgone_install_plugins.pl +++ b/gorgone/contrib/gorgone_install_plugins.pl @@ -23,9 +23,16 @@ use warnings; my $plugins = []; -for (my $i = 0; $i < scalar(@ARGV); $i++) { - if ($ARGV[$i] =~ /^centreon-plugin-([A-Za-z\-_0-9]+)$/) { - push @$plugins, '"' . $ARGV[$i] . '"'; +my $type; +if ($ARGV[0] !~ /^--type=(deb|rpm)$/) { + print "need to set option --type=[deb|rpm]\n"; + exit(1); +} +$type = $1; + +for (my $i = 1; $i < scalar(@ARGV); $i++) { + if ($ARGV[$i] =~ /^centreon-plugin-([A-Za-z\-_=0-9]+)$/) { + push @$plugins, $ARGV[$i]; } } @@ -34,7 +41,20 @@ exit(0); } -my $command = 'yum -y install ' . join(' ', @$plugins) . ' 2>&1'; +my $command; +if ($type eq 'rpm') { + $command = 'yum -y install'; + foreach (@$plugins) { + $command .= " '" . $_ . "-*'" + } +} elsif ($type eq 'deb') { + $command = 'apt-get -y install'; + foreach (@$plugins) { + $command .= " '" . $_ . "-*'" + } +} +$command .= ' 2>&1'; + my $output = `$command`; if ($? == -1) { print "failed to execute: $!\n"; diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index 11ed12fd20c..74db97a4b6a 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -200,13 +200,46 @@ sub check_plugins_rpm { return 0; } +sub check_plugins_deb { + my ($self, %options) = @_; + + #dpkg -l centreon-plugin-* + my ($error, $stdout, $return_code) = gorgone::standard::misc::backtick( + command => 'dpkg', + arguments => ['-l', 'centreon-plugin-*'], + timeout => 60, + wait_exit => 1, + redirect_stderr => 1, + logger => $self->{logger} + ); + + my $installed = []; + foreach my $package_name (keys %{$options{plugins}}) { + if ($stdout =~ /\s+$package_name\s+(\d+)-/m) { + my $current_version = $1; + if ($current_version < $options{plugins}->{$package_name}) { + push @$installed, $package_name . '=' . $options{plugins}->{$package_name}; + } + } else { + push @$installed, $package_name . '=' . $options{plugins}->{$package_name}; + } + } + + if (scalar(@$installed) > 0) { + return (1, 'install', $installed); + } + + $self->{logger}->writeLogInfo("[action] validate plugins - nothing to install"); + return 0; +} + sub install_plugins { my ($self, %options) = @_; $self->{logger}->writeLogInfo("[action] validate plugins - install " . join(' ', @{$options{installed}})); my ($error, $stdout, $return_code) = gorgone::standard::misc::backtick( command => 'sudo', - arguments => ['/usr/local/bin/gorgone_install_plugins.pl', @{$options{installed}}], + arguments => ['/usr/local/bin/gorgone_install_plugins.pl', '--type=' . $options{type}, @{$options{installed}}], timeout => 300, wait_exit => 1, redirect_stderr => 1, @@ -227,7 +260,7 @@ sub validate_plugins_rpm { return 0 if ($rv == 0); if ($rv == 1) { - ($rv, $message) = $self->install_plugins(installed => $installed); + ($rv, $message) = $self->install_plugins(type => 'rpm', installed => $installed); return ($rv, $message) if ($rv == -1); } @@ -240,6 +273,32 @@ sub validate_plugins_rpm { return 0; } +sub validate_plugins_deb { + my ($self, %options) = @_; + + my $plugins = {}; + foreach (keys %{$options{plugins}}) { + $plugins->{ lc($_) } = $options{plugins}->{$_}; + } + + my ($rv, $message, $installed) = $self->check_plugins_deb(plugins => $plugins); + return ($rv, $message) if ($rv == -1); + return 0 if ($rv == 0); + + if ($rv == 1) { + ($rv, $message) = $self->install_plugins(type => 'deb', installed => $installed); + return ($rv, $message) if ($rv == -1); + } + + ($rv, $message, $installed) = $self->check_plugins_deb(plugins => $plugins); + return ($rv, $message) if ($rv == -1); + if ($rv == 1) { + $self->{logger}->writeLogError("[action] validate plugins - still some to install: " . join(' ', @$installed)); + } + + return 0; +} + sub validate_plugins { my ($self, %options) = @_; @@ -261,8 +320,9 @@ sub validate_plugins { if ($self->{package_manager} eq 'rpm') { ($rv, $message) = $self->validate_plugins_rpm(plugins => $plugins); + } elsif ($self->{package_manager} eq 'deb') { + ($rv, $message) = $self->validate_plugins_deb(plugins => $plugins); } else { - # for debian/ubuntu: apt-get install centreon-plugin-test=version1 centreon-plugin-test=version2 ($rv, $message) = (1, 'validate plugins - unsupported operating system'); } From d1a90ae40ef54be1fdcd3797ce1808aee00f1708 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Fri, 10 Jun 2022 10:45:24 +0200 Subject: [PATCH 674/948] enh(mbi): add etl kill action (#246) --- .../gorgone/modules/centreon/mbi/etl/class.pm | 46 +++++++++++++++++-- .../gorgone/modules/centreon/mbi/etl/hooks.pm | 1 + .../modules/centreon/mbi/etlworkers/hooks.pm | 2 +- gorgone/gorgone/standard/constants.pm | 1 + gorgone/gorgone/standard/library.pm | 20 ++++++++ 5 files changed, 66 insertions(+), 4 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/mbi/etl/class.pm b/gorgone/gorgone/modules/centreon/mbi/etl/class.pm index 37fac03f0b7..cf87bdf649f 100644 --- a/gorgone/gorgone/modules/centreon/mbi/etl/class.pm +++ b/gorgone/gorgone/modules/centreon/mbi/etl/class.pm @@ -716,9 +716,6 @@ sub action_centreonmbietlrun { $self->runko(msg => $_) }; - #use Data::Dumper; - #print Data::Dumper::Dumper($self->{run}); - return 0; } @@ -750,6 +747,49 @@ sub action_centreonmbietllistener { return 1; } +sub action_centreonmbietlkill { + my ($self, %options) = @_; + + $options{token} = $self->generate_token() if (!defined($options{token})); + + if ($self->{run}->{status} == NONE) { + $self->{logger}->writeLogDebug('[mbi-etl] kill action - etl not running'); + $self->send_log( + code => GORGONE_ACTION_FINISH_OK, + token => $options{token}, + data => { + messages => 'etl not running' + } + ); + return 0; + } + + $self->{logger}->writeLogDebug('[mbi-etl] kill sent to the module etlworkers'); + + $self->send_internal_action( + action => 'KILL', + token => $options{token}, + data => { + content => { + package => 'gorgone::modules::centreon::mbi::etlworkers::hooks' + } + } + ); + + # RUNNING or STOP + $self->send_log( + code => GORGONE_ACTION_CONTINUE, + token => $options{token}, + data => { + messages => 'kill sent to the module etlworkers' + } + ); + + $self->reset(); + + return 0; +} + sub event { while (1) { my $message = $connector->read_message(); diff --git a/gorgone/gorgone/modules/centreon/mbi/etl/hooks.pm b/gorgone/gorgone/modules/centreon/mbi/etl/hooks.pm index d768fed2445..e264f5da8a2 100644 --- a/gorgone/gorgone/modules/centreon/mbi/etl/hooks.pm +++ b/gorgone/gorgone/modules/centreon/mbi/etl/hooks.pm @@ -30,6 +30,7 @@ use constant NAMESPACE => 'centreon'; use constant NAME => 'mbietl'; use constant EVENTS => [ { event => 'CENTREONMBIETLRUN', uri => '/run', method => 'POST' }, + { event => 'CENTREONMBIETLKILL', uri => '/kill', method => 'GET' }, { event => 'CENTREONMBIETLLISTENER' }, { event => 'CENTREONMBIETLREADY' } ]; diff --git a/gorgone/gorgone/modules/centreon/mbi/etlworkers/hooks.pm b/gorgone/gorgone/modules/centreon/mbi/etlworkers/hooks.pm index ec2004da8d8..742a938d81a 100644 --- a/gorgone/gorgone/modules/centreon/mbi/etlworkers/hooks.pm +++ b/gorgone/gorgone/modules/centreon/mbi/etlworkers/hooks.pm @@ -127,7 +127,7 @@ sub gently { sub kill { my (%options) = @_; - foreach (keys %{$pools}) { + foreach (keys %$pools) { if ($pools->{$_}->{running} == 1) { $options{logger}->writeLogDebug("[" . NAME . "] Send KILL signal for pool '" . $_ . "'"); CORE::kill('KILL', $pools->{$_}->{pid}); diff --git a/gorgone/gorgone/standard/constants.pm b/gorgone/gorgone/standard/constants.pm index c37b860590c..789863f944b 100644 --- a/gorgone/gorgone/standard/constants.pm +++ b/gorgone/gorgone/standard/constants.pm @@ -31,6 +31,7 @@ BEGIN { GORGONE_ACTION_FINISH_KO => 1, GORGONE_ACTION_FINISH_OK => 2, GORGONE_STARTED => 3, + GORGONE_ACTION_CONTINUE => 4, GORGONE_MODULE_ACTION_COMMAND_RESULT => 100, GORGONE_MODULE_ACTION_PROCESSCOPY_INPROGRESS => 101, diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index 7a464f55fb9..ddc70c941dc 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -577,6 +577,26 @@ sub getlog { sub kill { my (%options) = @_; + my $data; + eval { + $data = JSON::XS->new->utf8->decode($options{data}); + }; + if ($@) { + return (GORGONE_ACTION_FINISH_KO, { message => 'request not well formatted' }); + } + + if (defined($data->{content}->{package}) && defined($options{gorgone}->{modules_register}->{ $data->{content}->{package} })) { + $options{gorgone}->{modules_register}->{ $data->{content}->{package} }->{kill}->(logger => $options{gorgone}->{logger}); + return (GORGONE_ACTION_FINISH_OK, { action => 'kill', message => "module '$data->{content}->{package}' kill in progress" }); + } + if (defined($data->{content}->{name}) && + defined($options{gorgone}->{modules_id}->{ $data->{content}->{name} }) && + defined($options{gorgone}->{modules_register}->{ $options{gorgone}->{modules_id}->{ $data->{content}->{name} } })) { + $options{gorgone}->{modules_register}->{ $options{gorgone}->{modules_id}->{ $data->{content}->{name} } }->{kill}->(logger => $options{gorgone}->{logger}); + return (GORGONE_ACTION_FINISH_OK, { action => 'kill', message => "module '$data->{content}->{name}' kill in progress" }); + } + + return (GORGONE_ACTION_FINISH_KO, { action => 'kill', message => 'cannot find module' }); } ####################### From 3b33d1e06fe394c0ddef45f9922233ada4ffe29f Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Fri, 10 Jun 2022 14:22:48 +0200 Subject: [PATCH 675/948] enh(mbi): add etl status action (#247) --- gorgone/contrib/mbi/centreonBIETL | 139 ++++++++++++++++-- .../gorgone/modules/centreon/mbi/etl/class.pm | 56 +++++++ .../gorgone/modules/centreon/mbi/etl/hooks.pm | 1 + 3 files changed, 180 insertions(+), 16 deletions(-) diff --git a/gorgone/contrib/mbi/centreonBIETL b/gorgone/contrib/mbi/centreonBIETL index 3732ea72e92..5a813d76a8e 100644 --- a/gorgone/contrib/mbi/centreonBIETL +++ b/gorgone/contrib/mbi/centreonBIETL @@ -31,7 +31,7 @@ sub new { ); bless $self, $class; - + $self->{moptions}->{rebuild} = 0; $self->{moptions}->{daily} = 0; $self->{moptions}->{import} = 0; @@ -46,19 +46,20 @@ sub new { $self->{moptions}->{nopurge} = 0; $self->add_options( - 'url:s' => \$self->{url}, - 'r' => \$self->{moptions}->{rebuild}, - 'd' => \$self->{moptions}->{daily}, - 'I' => \$self->{moptions}->{import}, - 'D' => \$self->{moptions}->{dimensions}, - 'E' => \$self->{moptions}->{event}, - 'P' => \$self->{moptions}->{perfdata}, - 's:s' => \$self->{moptions}->{start}, - 'e:s' => \$self->{moptions}->{end}, - 'c' => \$self->{moptions}->{create_tables}, - 'i' => \$self->{moptions}->{ignore_databin}, - 'C' => \$self->{moptions}->{centreon_only}, - 'p' => \$self->{moptions}->{nopurge} + 'url:s' => \$self->{url}, + 'status' => \$self->{status}, + 'r' => \$self->{moptions}->{rebuild}, + 'd' => \$self->{moptions}->{daily}, + 'I' => \$self->{moptions}->{import}, + 'D' => \$self->{moptions}->{dimensions}, + 'E' => \$self->{moptions}->{event}, + 'P' => \$self->{moptions}->{perfdata}, + 's:s' => \$self->{moptions}->{start}, + 'e:s' => \$self->{moptions}->{end}, + 'c' => \$self->{moptions}->{create_tables}, + 'i' => \$self->{moptions}->{ignore_databin}, + 'C' => \$self->{moptions}->{centreon_only}, + 'p' => \$self->{moptions}->{nopurge} ); return $self; } @@ -70,6 +71,8 @@ sub init { $self->{url} = 'http://127.0.0.1:8085' if (!defined($self->{url}) || $self->{url} eq ''); $self->{http} = gorgone::class::http::http->new(logger => $self->{logger}); + return if (defined($self->{status})); + my $utils = gorgone::modules::centreon::mbi::libs::Utils->new($self->{logger}); if ($utils->checkBasicOptions($self->{moptions}) == 1) { exit(1); @@ -209,12 +212,116 @@ sub get_etl_log { } } +sub get_etl_status { + my ($self) = @_; + + my ($code, $content) = $self->{http}->request( + http_backend => 'curl', + method => 'GET', + hostname => '', + full_url => $self->{url} . '/api/centreon/mbietl/status', + header => [ + 'Accept-Type: application/json; charset=utf-8', + 'Content-Type: application/json; charset=utf-8', + ], + curl_opt => ['CURLOPT_SSL_VERIFYPEER => 0', 'CURLOPT_POSTREDIR => CURL_REDIR_POST_ALL'], + warning_status => '', + unknown_status => '', + critical_status => '' + ); + + if ($self->{http}->get_code() < 200 || $self->{http}->get_code() >= 300) { + $self->{logger}->writeLogError("Login error [code: '" . $self->{http}->get_code() . "'] [message: '" . $self->{http}->get_message() . "']"); + exit(1); + } + + my $decoded = $self->json_decode(content => $content); + if (!defined($decoded->{token})) { + $self->{logger}->writeLogError('cannot get token'); + exit(1); + } + + my $token = $decoded->{token}; + my $log_id; + my $result; + + while (1) { + my $get_param = []; + if (defined($log_id)) { + $get_param = ['id=' . $log_id]; + } + + my ($code, $content) = $self->{http}->request( + http_backend => 'curl', + method => 'GET', + hostname => '', + full_url => $self->{url} . '/api/log/' . $token, + get_param => $get_param, + header => [ + 'Accept-Type: application/json; charset=utf-8' + ], + curl_opt => ['CURLOPT_SSL_VERIFYPEER => 0', 'CURLOPT_POSTREDIR => CURL_REDIR_POST_ALL'], + warning_status => '', + unknown_status => '', + critical_status => '' + ); + + if ($self->{http}->get_code() < 200 || $self->{http}->get_code() >= 300) { + $self->{logger}->writeLogError("Login error [code: '" . $self->{http}->get_code() . "'] [message: '" . $self->{http}->get_message() . "']"); + exit(1); + } + + my $decoded = $self->json_decode(content => $content); + if (!defined($decoded->{data})) { + $self->{logger}->writeLogError("Cannot get log information"); + exit(1); + } + + my $stop = 0; + foreach (@{$decoded->{data}}) { + my $data = $self->json_decode(content => $_->{data}); + next if (defined($log_id) && $log_id >= $_->{id}); + $log_id = $_->{id}; + + if ($_->{code} == 1) { + $self->{logger}->writeLogError('cannot get etl status'); + exit(1); + } elsif ($_->{code} == 2) { + $result = $data; + $stop = 1; + } + } + + last if ($stop == 1); + sleep(2); + } + + print "ETL status: $result->{statusStr}\n"; + if ($result->{statusStr} ne 'ready') { + print "planning: $result->{planningStr}\n"; + foreach ('import', 'dimensions', 'event', 'perfdata') { + next if (!defined($result->{sections}->{$_})); + + print " $_ status: $result->{sections}->{$_}->{statusStr}"; + if (defined($result->{sections}->{$_}->{steps_total})) { + print " ($result->{sections}->{$_}->{steps_executed}/$result->{sections}->{$_}->{steps_total})"; + } + print "\n"; + } + } +} + sub run { my $self = shift; $self->SUPER::run(); - $self->run_etl(); - $self->get_etl_log(); + + if (defined($self->{status})) { + $self->get_etl_status(); + } else { + $self->run_etl(); + $self->get_etl_log(); + } } __END__ diff --git a/gorgone/gorgone/modules/centreon/mbi/etl/class.pm b/gorgone/gorgone/modules/centreon/mbi/etl/class.pm index cf87bdf649f..c9ef0dbe346 100644 --- a/gorgone/gorgone/modules/centreon/mbi/etl/class.pm +++ b/gorgone/gorgone/modules/centreon/mbi/etl/class.pm @@ -790,6 +790,62 @@ sub action_centreonmbietlkill { return 0; } +sub action_centreonmbietlstatus { + my ($self, %options) = @_; + + $options{token} = $self->generate_token() if (!defined($options{token})); + + my $map_etl_status = { + 0 => 'ready', + 1 => 'running', + 2 => 'stopping' + }; + + my $map_planning_status = { + 0 => 'running', + 1 => 'ok' + }; + + my $map_section_status = { + -1 => 'unplanned', + 0 => 'planned', + 1 => 'running', + 2 => 'ok' + }; + + my $section = {}; + foreach ('import', 'dimensions', 'event', 'perfdata') { + next if (!defined($self->{run}->{schedule})); + + $section->{$_} = { + status => $self->{run}->{schedule}->{$_}->{status}, + statusStr => $map_section_status->{ $self->{run}->{schedule}->{$_}->{status} } + }; + if ($self->{run}->{schedule}->{$_}->{status} == RUNNING) { + $section->{$_}->{steps_total} = $self->{run}->{schedule}->{$_}->{substeps_total}; + $section->{$_}->{steps_executed} = $self->{run}->{schedule}->{$_}->{substeps_executed}; + } + } + + $self->send_log( + code => GORGONE_ACTION_FINISH_OK, + token => $options{token}, + data => { + token => defined($self->{run}->{token}) ? $self->{run}->{token} : undef, + + status => $self->{run}->{status}, + statusStr => $map_etl_status->{ $self->{run}->{status} }, + + planning => defined($self->{run}->{schedule}->{planned}) ? $self->{run}->{schedule}->{planned} : undef, + planningStr => defined($self->{run}->{schedule}->{planned}) ? $map_planning_status->{ $self->{run}->{schedule}->{planned} } : undef, + + sections => $section + } + ); + + return 0; +} + sub event { while (1) { my $message = $connector->read_message(); diff --git a/gorgone/gorgone/modules/centreon/mbi/etl/hooks.pm b/gorgone/gorgone/modules/centreon/mbi/etl/hooks.pm index e264f5da8a2..48d8471b10b 100644 --- a/gorgone/gorgone/modules/centreon/mbi/etl/hooks.pm +++ b/gorgone/gorgone/modules/centreon/mbi/etl/hooks.pm @@ -31,6 +31,7 @@ use constant NAME => 'mbietl'; use constant EVENTS => [ { event => 'CENTREONMBIETLRUN', uri => '/run', method => 'POST' }, { event => 'CENTREONMBIETLKILL', uri => '/kill', method => 'GET' }, + { event => 'CENTREONMBIETLSTATUS', uri => '/status', method => 'GET' }, { event => 'CENTREONMBIETLLISTENER' }, { event => 'CENTREONMBIETLREADY' } ]; From af5fe30dae5a48179cbcfdc10bcaf1012bf4ecaf Mon Sep 17 00:00:00 2001 From: Luiz Costa <luizgustavo@luizgustavo.pro.br> Date: Mon, 13 Jun 2022 14:34:46 +0100 Subject: [PATCH 676/948] Add missing dependencies (#248) --- gorgone/ci/debian/control | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gorgone/ci/debian/control b/gorgone/ci/debian/control index fdda61d66aa..47f0700cf2f 100644 --- a/gorgone/ci/debian/control +++ b/gorgone/ci/debian/control @@ -30,6 +30,7 @@ Depends: centreon-common (>= ${centreon:version}~), libdatetime-perl, libtry-tiny-perl, + libxml-simple-perl, libxml-libxml-simple-perl, libdigest-md5-file-perl, libjson-pp-perl, @@ -47,6 +48,8 @@ Depends: libmojolicious-perl, libauthen-simple-perl, libauthen-simple-net-perl, + libnet-curl-perl, + libssh-session-perl, libzmq-constants-perl, libzmq5, zmq-libzmq4-perl, From 9fbfa36e692596631e44b82f72bf0218aa1c3bb5 Mon Sep 17 00:00:00 2001 From: Laurent Pinsivy <lpinsivy@centreon.com> Date: Wed, 15 Jun 2022 12:13:58 +0200 Subject: [PATCH 677/948] Update Jenkinsfile (#249) * Update Jenkinsfile * Update Jenkinsfile Co-authored-by: Zakaria Guennoune <83596451+zguennoune02@users.noreply.github.com> --- gorgone/Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/Jenkinsfile b/gorgone/Jenkinsfile index 40b5f1b87bd..92e0ff810c6 100644 --- a/gorgone/Jenkinsfile +++ b/gorgone/Jenkinsfile @@ -9,9 +9,9 @@ env.PROJECT='centreon-gorgone' if (env.BRANCH_NAME.startsWith('release-')) { env.BUILD = 'RELEASE' env.REPO = 'testing' -} else if ((env.BRANCH_NAME == env.REF_BRANCH) || (env.BRANCH_NAME == maintenanceBranch)) { +} else if (env.BRANCH_NAME == maintenanceBranch) { env.BUILD = 'REFERENCE' -} else if ((env.BRANCH_NAME == 'develop') || (env.BRANCH_NAME == qaBranch)) { +} else if (env.BRANCH_NAME == qaBranch) { env.BUILD = 'QA' env.REPO = 'unstable' } else { From 4b67923d4a4d3c522d8e2660d795e515dd153925 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Mon, 11 Jul 2022 14:53:08 +0200 Subject: [PATCH 678/948] fix(mbi): stuck if there is an issue during the planning (#250) --- gorgone/gorgone/modules/centreon/mbi/etl/class.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gorgone/gorgone/modules/centreon/mbi/etl/class.pm b/gorgone/gorgone/modules/centreon/mbi/etl/class.pm index c9ef0dbe346..d957546c6a3 100644 --- a/gorgone/gorgone/modules/centreon/mbi/etl/class.pm +++ b/gorgone/gorgone/modules/centreon/mbi/etl/class.pm @@ -713,7 +713,8 @@ sub action_centreonmbietlrun { $self->planning(); $self->run_etl(); } catch { - $self->runko(msg => $_) + $self->runko(msg => $_); + $self->reset(); }; return 0; From 5ac793f1b879c37b33f8f6d4136b05fed09a4c96 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Mon, 11 Jul 2022 15:38:36 +0200 Subject: [PATCH 679/948] (plugins) add rocky linux support (#253) --- .../gorgone/modules/centreon/audit/metrics/centreon/packages.pm | 2 +- gorgone/gorgone/modules/core/action/class.pm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/audit/metrics/centreon/packages.pm b/gorgone/gorgone/modules/centreon/audit/metrics/centreon/packages.pm index 753aaf5d59c..a8bacc19397 100644 --- a/gorgone/gorgone/modules/centreon/audit/metrics/centreon/packages.pm +++ b/gorgone/gorgone/modules/centreon/audit/metrics/centreon/packages.pm @@ -79,7 +79,7 @@ sub metrics { if ($options{os} =~ /Debian|Ubuntu/i) { dpkg_list(metrics => $metrics); - } elsif ($options{os} =~ /CentOS|Redhat|rhel|almalinux/i) { + } elsif ($options{os} =~ /CentOS|Redhat|rhel|almalinux|rocky/i) { rpm_list(metrics => $metrics); } elsif ($options{os} eq 'ol' || $options{os} =~ /Oracle Linux/i) { rpm_list(metrics => $metrics); diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index 74db97a4b6a..30cfeb55795 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -154,7 +154,7 @@ sub get_package_manager { $self->{package_manager} = 'unknown'; if ($os =~ /Debian|Ubuntu/i) { $self->{package_manager} = 'deb'; - } elsif ($os =~ /CentOS|Redhat|rhel|almalinux/i) { + } elsif ($os =~ /CentOS|Redhat|rhel|almalinux|rocky/i) { $self->{package_manager} = 'rpm'; } elsif ($os eq 'ol' || $os =~ /Oracle Linux/i) { $self->{package_manager} = 'rpm'; From 90b15f55b336874368af5937ef9e53057c1e6582 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Mon, 11 Jul 2022 15:42:09 +0200 Subject: [PATCH 680/948] (action) add tar_insecure_extra_mode option (#254) --- gorgone/docs/modules/core/action.md | 11 ++++++----- gorgone/gorgone/modules/core/action/class.pm | 4 ++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/gorgone/docs/modules/core/action.md b/gorgone/docs/modules/core/action.md index 79a362b9dec..68443d5f0d7 100644 --- a/gorgone/docs/modules/core/action.md +++ b/gorgone/docs/modules/core/action.md @@ -6,11 +6,12 @@ This module aims to execute actions on the server running the Gorgone daemon or ## Configuration -| Directive | Description | Default value | -| :-------------- | :------------------------------------------------------- | :------------ | -| command_timeout | Time in seconds before a command is considered timed out | `30` | -| whitelist_cmds | Boolean to enable commands whitelist | `false` | -| allowed_cmds | Regexp list of allowed commands | | +| Directive | Description | Default value | +| :---------------------- | :------------------------------------------------------------------ | :------------ | +| command_timeout | Time in seconds before a command is considered timed out | `30` | +| whitelist_cmds | Boolean to enable commands whitelist | `false` | +| allowed_cmds | Regexp list of allowed commands | | +| tar_insecure_extra_mode | allow files to be extracted outside their current working directory | | #### Example diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index 30cfeb55795..f4fe7202b5d 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -60,6 +60,10 @@ sub new { $connector->{allowed_cmds} = $connector->{config}->{allowed_cmds} if (defined($connector->{config}->{allowed_cmds}) && ref($connector->{config}->{allowed_cmds}) eq 'ARRAY'); + if (defined($connector->{config}->{tar_insecure_extra_mode}) && $connector->{config}->{tar_insecure_extra_mode} =~ /^(?:1|true)$/) { + $Archive::Tar::INSECURE_EXTRACT_MODE = 1; + } + $connector->{return_childs} = {}; $connector->{engine_childs} = {}; $connector->{max_concurrent_engine} = defined($connector->{config}->{max_concurrent_engine}) ? From 65306d1e83a773aa29579f23afee8ee7fe09d4e7 Mon Sep 17 00:00:00 2001 From: tuntoja <58987095+tuntoja@users.noreply.github.com> Date: Thu, 28 Jul 2022 11:06:49 +0200 Subject: [PATCH 681/948] Fix apt update (#257) * fix(ci): add apt update action * fix(ci): update gorgone-deb-package.sh * fix(ci): update gorgone-deb-package.sh --- gorgone/ci/scripts/gorgone-deb-package.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gorgone/ci/scripts/gorgone-deb-package.sh b/gorgone/ci/scripts/gorgone-deb-package.sh index 5503225c0e6..aa510a66d61 100755 --- a/gorgone/ci/scripts/gorgone-deb-package.sh +++ b/gorgone/ci/scripts/gorgone-deb-package.sh @@ -11,6 +11,8 @@ echo "################################################## PACKAGING COLLECT ##### AUTHOR="Luiz Costa" AUTHOR_EMAIL="me@luizgustavo.pro.br" +apt-get update + pwd if [ -d /build ]; then From 496c339aa907843ee8c08c5b940319f08e23aba6 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Thu, 28 Jul 2022 17:00:35 +0200 Subject: [PATCH 682/948] fix(wss): change header token name + token ending regexp (#256) Co-authored-by: omercier <32134301+omercier@users.noreply.github.com> --- gorgone/gorgone/modules/core/proxy/httpserver.pm | 8 ++++---- gorgone/gorgone/modules/core/pullwss/class.pm | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/gorgone/gorgone/modules/core/proxy/httpserver.pm b/gorgone/gorgone/modules/core/proxy/httpserver.pm index 884598e91d3..b7aac1072ce 100644 --- a/gorgone/gorgone/modules/core/proxy/httpserver.pm +++ b/gorgone/gorgone/modules/core/proxy/httpserver.pm @@ -47,7 +47,7 @@ websocket '/' => sub { tx => $mojo->tx, logged => 0, last_update => time(), - authentication => $mojo->tx->req->headers->header('authentication') + authorization => $mojo->tx->req->headers->header('authorization') }; $mojo->on(message => sub { @@ -301,11 +301,11 @@ sub is_logged_websocket { return 1 if ($self->{ws_clients}->{ $options{ws_id} }->{logged} == 1); - if (!defined($self->{ws_clients}->{ $options{ws_id} }->{authentication}) || - $self->{ws_clients}->{ $options{ws_id} }->{authentication} !~ /^\s*Bearer\s+$self->{config}->{httpserver}->{token}/) { + if (!defined($self->{ws_clients}->{ $options{ws_id} }->{authorization}) || + $self->{ws_clients}->{ $options{ws_id} }->{authorization} !~ /^\s*Bearer\s+$self->{config}->{httpserver}->{token}\s*$/) { $self->close_websocket( code => 500, - message => 'token authentication unallowed', + message => 'token authorization unallowed', ws_id => $options{ws_id} ); return 0; diff --git a/gorgone/gorgone/modules/core/pullwss/class.pm b/gorgone/gorgone/modules/core/pullwss/class.pm index c957663e6b8..a31d12661d0 100644 --- a/gorgone/gorgone/modules/core/pullwss/class.pm +++ b/gorgone/gorgone/modules/core/pullwss/class.pm @@ -130,7 +130,7 @@ sub wss_connect { } $self->{ua}->websocket( - $proto . '://' . $self->{config}->{address} . ':' . $self->{config}->{port} . '/' => { Authentication => 'Bearer ' . $self->{config}->{token} } => sub { + $proto . '://' . $self->{config}->{address} . ':' . $self->{config}->{port} . '/' => { Authorization => 'Bearer ' . $self->{config}->{token} } => sub { my ($ua, $tx) = @_; $connector->{tx} = $tx; From 5df4a17328f02fba88c64000c631f85d733e9525 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Wed, 3 Aug 2022 09:19:44 +0200 Subject: [PATCH 683/948] fix(pullwss): stop IOLoop when stopping the module (#259) --- gorgone/gorgone/modules/core/pullwss/class.pm | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/gorgone/gorgone/modules/core/pullwss/class.pm b/gorgone/gorgone/modules/core/pullwss/class.pm index a31d12661d0..92e3921a923 100644 --- a/gorgone/gorgone/modules/core/pullwss/class.pm +++ b/gorgone/gorgone/modules/core/pullwss/class.pm @@ -67,6 +67,27 @@ sub handle_TERM { my $self = shift; $self->{logger}->writeLogDebug("[pullwss] $$ Receiving order to stop..."); $self->{stop} = 1; + + my $message = gorgone::standard::library::build_protocol( + action => 'UNREGISTERNODES', + data => { + nodes => [ + { + id => $self->get_core_config(name => 'id'), + type => 'wss', + identity => $self->get_core_config(name => 'id') + } + ] + }, + json_encode => 1 + ); + + if ($self->{connected} == 1) { + $self->{tx}->send({text => $message }); + $self->{tx}->on(drain => sub { Mojo::IOLoop->stop_gracefully(); }); + } else { + Mojo::IOLoop->stop_gracefully(); + } } sub class_handle_TERM { From 058ea94e44f4934786a4b67841a9374fbb9d660c Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Wed, 3 Aug 2022 11:56:48 +0200 Subject: [PATCH 684/948] fix(action): avoid restart/reload if missing plugins deps (#260) --- gorgone/gorgone/modules/core/action/class.pm | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index f4fe7202b5d..bc3ba044c8e 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -271,7 +271,9 @@ sub validate_plugins_rpm { ($rv, $message, $installed) = $self->check_plugins_rpm(%options); return ($rv, $message) if ($rv == -1); if ($rv == 1) { - $self->{logger}->writeLogError("[action] validate plugins - still some to install: " . join(' ', @$installed)); + $message = 'validate plugins - still some to install: ' . join(' ', @$installed); + $self->{logger}->writeLogError("[action] $message"); + return (1, $message); } return 0; @@ -297,7 +299,9 @@ sub validate_plugins_deb { ($rv, $message, $installed) = $self->check_plugins_deb(plugins => $plugins); return ($rv, $message) if ($rv == -1); if ($rv == 1) { - $self->{logger}->writeLogError("[action] validate plugins - still some to install: " . join(' ', @$installed)); + $message = 'validate plugins - still some to install: ' . join(' ', @$installed); + $self->{logger}->writeLogError("[action] $message"); + return (1, $message); } return 0; From b5b3918aba8b06ed882105e4f62a398360def7eb Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Thu, 11 Aug 2022 18:00:15 +0200 Subject: [PATCH 685/948] fix(action): revert reload/restart + paranoid option added (#261) --- gorgone/docs/modules/core/action.md | 12 ++++++------ gorgone/gorgone/modules/core/action/class.pm | 19 +++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/gorgone/docs/modules/core/action.md b/gorgone/docs/modules/core/action.md index 68443d5f0d7..fc1e298f8d2 100644 --- a/gorgone/docs/modules/core/action.md +++ b/gorgone/docs/modules/core/action.md @@ -6,12 +6,12 @@ This module aims to execute actions on the server running the Gorgone daemon or ## Configuration -| Directive | Description | Default value | -| :---------------------- | :------------------------------------------------------------------ | :------------ | -| command_timeout | Time in seconds before a command is considered timed out | `30` | -| whitelist_cmds | Boolean to enable commands whitelist | `false` | -| allowed_cmds | Regexp list of allowed commands | | -| tar_insecure_extra_mode | allow files to be extracted outside their current working directory | | +| Directive | Description | Default value | +| :--------------- | :------------------------------------------------------------- | :------------ | +| command_timeout | Time in seconds before a command is considered timed out | `30` | +| whitelist_cmds | Boolean to enable commands whitelist | `false` | +| allowed_cmds | Regexp list of allowed commands | | +| paranoid_plugins | Block centengine restart/reload if plugin dependencies missing | `false` | #### Example diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index bc3ba044c8e..7995b0ecaff 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -60,9 +60,8 @@ sub new { $connector->{allowed_cmds} = $connector->{config}->{allowed_cmds} if (defined($connector->{config}->{allowed_cmds}) && ref($connector->{config}->{allowed_cmds}) eq 'ARRAY'); - if (defined($connector->{config}->{tar_insecure_extra_mode}) && $connector->{config}->{tar_insecure_extra_mode} =~ /^(?:1|true)$/) { - $Archive::Tar::INSECURE_EXTRACT_MODE = 1; - } + $connector->{paranoid_plugins} = defined($connector->{config}->{paranoid_plugins}) && $connector->{config}->{paranoid_plugins} =~ /true|1/i ? + 1 : 0; $connector->{return_childs} = {}; $connector->{engine_childs} = {}; @@ -260,16 +259,16 @@ sub validate_plugins_rpm { my ($self, %options) = @_; my ($rv, $message, $installed) = $self->check_plugins_rpm(%options); - return ($rv, $message) if ($rv == -1); + return (1, $message) if ($rv == -1); return 0 if ($rv == 0); if ($rv == 1) { ($rv, $message) = $self->install_plugins(type => 'rpm', installed => $installed); - return ($rv, $message) if ($rv == -1); + return (1, $message) if ($rv == -1); } ($rv, $message, $installed) = $self->check_plugins_rpm(%options); - return ($rv, $message) if ($rv == -1); + return (1, $message) if ($rv == -1); if ($rv == 1) { $message = 'validate plugins - still some to install: ' . join(' ', @$installed); $self->{logger}->writeLogError("[action] $message"); @@ -288,16 +287,16 @@ sub validate_plugins_deb { } my ($rv, $message, $installed) = $self->check_plugins_deb(plugins => $plugins); - return ($rv, $message) if ($rv == -1); + return (1, $message) if ($rv == -1); return 0 if ($rv == 0); if ($rv == 1) { ($rv, $message) = $self->install_plugins(type => 'deb', installed => $installed); - return ($rv, $message) if ($rv == -1); + return (1, $message) if ($rv == -1); } ($rv, $message, $installed) = $self->check_plugins_deb(plugins => $plugins); - return ($rv, $message) if ($rv == -1); + return (1, $message) if ($rv == -1); if ($rv == 1) { $message = 'validate plugins - still some to install: ' . join(' ', @$installed); $self->{logger}->writeLogError("[action] $message"); @@ -665,7 +664,7 @@ sub action_actionengine { if (defined($options{data}->{content}->{plugins}) && $options{data}->{content}->{plugins} ne '') { my ($rv, $message) = $self->validate_plugins(file => $options{data}->{content}->{plugins}); - if ($rv) { + if ($rv && $self->{paranoid_plugins} == 1) { $self->{logger}->writeLogError("[action] $message"); $self->send_log( socket => $options{socket_log}, From 3867458970d8e79de48bfafceda9201b5f1070cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Chapron?= <34628915+sc979@users.noreply.github.com> Date: Mon, 22 Aug 2022 16:33:31 +0200 Subject: [PATCH 686/948] chore(policy): remove policy to uses common .github files (#262) --- gorgone/SECURITY.md | 166 -------------------------------------------- 1 file changed, 166 deletions(-) delete mode 100644 gorgone/SECURITY.md diff --git a/gorgone/SECURITY.md b/gorgone/SECURITY.md deleted file mode 100644 index 089eb2157e9..00000000000 --- a/gorgone/SECURITY.md +++ /dev/null @@ -1,166 +0,0 @@ -# Security Policy - -Centreon takes the security of our software products seriously. - -If you believe you have found a security vulnerability, please report it to us as described below. - -## Reporting a Vulnerability - -**Please do not report security vulnerabilities through public GitHub issues.** - -Send an email to security@centreon.com. If possible, encrypt your message with our PGP key below. - -You should receive a response within 48 hours. If for some reason you do not, please follow up via email to ensure we received your original message. - -To help us better understand the nature and scope of the possible issue, please describe as much as you can: - -* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) -* Full paths of source file(s) related to the manifestation of the issue -* The location of the affected source code (tag/branch/commit or direct URL) -* Any special configuration required to reproduce the issue -* Step-by-step instructions to reproduce the issue -* Proof-of-concept or exploit code (if possible) -* Impact of the issue, including how an attacker might exploit the issue - -## Bug bounty - -We don't have a bug bounty program but this is something we are thinking about. - -## PGP information - -### Public key - -| Tag | Value | -| -- | -- | -| ID | F92686A9EC269C1A | -| Type | RSA | -| Size | 4096 | -| Created | 2022-12-28 | -| Expires | 2023-01-22 | -| Fingerprint | 3552 91EA 7DAF 9E2A 192C 62B6 F926 86A9 EC26 9C1A | - -``` ------BEGIN PGP PUBLIC KEY BLOCK----- - -mQINBGHLO4IBEADB5ZlFUNNH/Y5TEVHAAHIMjHEt63M5hA+C94EYv89R2+swz212 -Hla4f5sVl5wPNSwiIAed+bJNKnGiaDM/508aMcTHurGRu3x5/MyvuxpXmzOSY1Mt -JZxLBBkonL1iX0tCytWriOhgAty9gi58DPKA6f7sVDgt3Hm/NtIEULSbXy6xfDYo -m+Sz39+hb0PcKKEkacRGzOGDIR0UgOAUGDBEbDoLPxjM6flHjXcjs4fZNY2HHQXO -AB65qM9my4ALxxsrIbsKfu25HY52qSZoqZD90AxKdNtRFlnkXClWN0l26fVqiGjv -oCPMYGPp80OYvymE2QhtlD+jRAepwyWx1YY96VFIA9LsZtjmoRxw/KLghdeP4Q7p -/BUCVkT393OOTayNhoNa7iCqbK0lmB6mequi7KV3vNXn79WP0Hm8AQ9/9bEJaY8x -oNTKAxsR6gLP1fc7S/zg9iIHUuTj6XU9CbW45ADrCJRel5LoM+MZ3DWXh+kd0Iuw -yANU+XVgC1fXQOf76BJeYSalZS8Ln1vpYjDwEZBSmLdyefCYBjspxjDNzpCAy+wH -gc/vpQbjmFxgkbZ3AroGDaNu1JVhA3yy8oXAEwAxl8BzsYye2YbhAUb2RgZhIndd -TCcWkwhEWey3XYMCtnFWxsXnteA1cvWD8PvCiWy53yc/Ng59H3XyB3sJbQARAQAB -tJJDZW50cmVvbiBTZWN1cml0eSAoRW1haWwgdXNlZCB0byByZXBvcnQgYSB2dWxu -ZXJhYmlsaXR5IG9uIENlbnRyZW9uJ3MgcHJvZHVjdHMgb3IgdG8gY29udGFjdCB0 -aGUgQ2VudHJlb24ncyBzZWN1cml0eSB0ZWFtKSA8c2VjdXJpdHlAY2VudHJlb24u -Y29tPokCVAQTAQoAPhYhBPSiLqQfofMIpmrARo99hgxsMlQ6BQJhyzuCAhsDBQkC -AikABQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEI99hgxsMlQ6Ms0P/1lJfoaj -5/mRIKvaWZnTZm6OCpJtRT/9WrBGxbVi0TfyFb8M5OHeoz2oXc7WEPDJNNW9aRat -i40oHfQExW0UBsMvpfGlhPc5nwIxzpvFuckPrSTU3Y9ZLQvCoyxPIWMsXlghwwHD -OFW5oYg+rAvtmFuLyM18MdkH3YmrUHs7wyZczvi5hqdv5yB92kEjDTRbCYG8k9ep -P1rr3U/WyQ6u5BEZLTFNceK//VQsEfN7l8QLQV5CtG7PtdnLh5V4lkDHV7DW3PCg -TXyy8A5IatLh7z+ODQ/GDwCw7NNQm5M8dB1T/tLZn7+kTf8KqjHz4Qbh2PFBu5R7 -sDPEM81EMSAN/dfuZyQKbABnJk0JalvZeuZYUQYZWTgK6mSFJ3Rac8aX4TX7fwfS -kjh0ivG/FWzKaDa/dGfpH0J69IE2SACLRF+022hPLz7dRmSyNQSFZ4McXC+ihQps -mDJrqLmBPSNIRkfTwczINPSAX051w3GsJDSoAID+X0iqmoZTPYuf9XJO689vx+5B -g4FwhFwETkCqq6sZLzBI8+8dHKCn7imCtmrJ7JN8uy4mPS4Y2yVTBr1sZiAuvHzN -4314o3N30OZeqG61OuE6XO67mlw5Rk5Kay7P/s3uu5wXEL01z5+VKH/hjzVV+GXq -9J+VKYfXOcvu9PDhVBzlXgtP5xNuhh/IlvoDuQINBGHLPM4BEADdN4QbeuBSFts7 -9iIJXBmYiwcfDUOyZYaam6tI1fi1MbWCTAwpDpR0e8wdAon5yrF5jF6f6PIzqSfV -jc1rtLfdftVlzCMobXyjPxO9LkwChSm+b1tR6R7FyxfkUu5Og7TdrTpzzbTPN6TA -0BReEy20NpU6b3xC96BdaxE2ePlDOm26C5ygmAWszGD29ztxVtbBq7w0M+q7MO5Q -UH286bNQXKL5C5wZuA18li/hk7Cri4XwtRyMMh0dlT7hYYuKKJN5d8swx793JdlP -uYxmL93bM7rlka+W9fHYEbW5Zr7KUKwGygju/R2kx567iZyNwhFdeNsMFXFRIxCL -sEqNBc6EexafaxoJc4Ms0FD5WXiy9Je5+c5ue5Qb3SzavRu8u/z4bnjmpXqodBOS -jB4KPPGp4iCZPIOD98HyQ4XlYPSFc4hFEDxM6JGjfiKzFGaSmroSqwoKYYUjLsI+ -SuFPciOnH73KvwGX0uDYOKKZp5kmhKwu+AWaOfjoMGC2j+aOCQswziYBwr32bgyf -S/CM4uha7XhY4vi5IaRgXrSSsCRFwskTRQwtAHtu8m17D0CH7K5blKXtx+pHB+6x -cYTX9u2N2rqnOTma6+KEXFzVj0JiXsT3OwFmAFRvDU+erGSlAF3bQkZcu0hoFZmh -XBeRV+vA3D/rkwXal8vMRcn2V+XZ5wARAQABiQI8BBgBCgAmFiEE9KIupB+h8wim -asBGj32GDGwyVDoFAmHLPM4CGwwFCQICKQAACgkQj32GDGwyVDqkbg/+NVEZw2A4 -Uk6h4Exo9T0+ttd/ywi8P5aGnoiJ9Fw92RHgmSNUwIwgdeGKrgBbhVaO/V4CDGJp -iiwIAxzU/xCNibEGTUkH79AZvFHXxXwRKf/vWW1w3gyh9ppRLBlUw3S2DdEkxlzJ -5R1ryYTeV4yFAVK1Ln1v/UCA2WHho3IN/PIgDt701zONUEn1OOxHrMlKsgHIBAAk -NA6IQ1Tr8RW9abK3uAtJxxnyOqEMkiE03sJfd1dAUtvirGxr7g3t1Gfi8BPnQR5T -ZNqDOblM6fiY05AngPOLtV0n6LazK/buNenvUUhT0R9noMX6ZcApGpS4fFhADw1q -vrFYSG/4rLSGKvLqw5pQ7PzLDHPfn/HIME//SPuBnYrTYjiupzdmgtjGOc1iMV0X -YVXuA1yj+aJFaObLnVD31v2GIKvVS4WMsG74Mf5vMiMkbc0Zg2ULGun3sXscW0Yh -2MnvI5oYQcKmzjmhPdKHrmkiy9QC4442PbE8Bn9KUpcVoxCtFr/Zsc18iUVHYyIG -rrmZBE8MF1tGGBsdFC4Aktujuj3EevBo26QLozyfOLXXATHhmGh4SWsH68iyzynw -ARzB/pCyvB1Y/QbRn3ClFIksAyjrMxiNkSQXgToc8Ph+vLnHS3Y4399c74WZCHCH -i51yIfcTAPmxOst/YN5WXOxWHZjZ/STVi0Y= -=8Gny ------END PGP PUBLIC KEY BLOCK----- -``` - -### Revoked Public key - -**_Kindly use the new key instead_** - -| Tag | Value | -| -- | -- | -| ID | BEAF6EBF631106F9 | -| Type | RSA | -| Size | 4096 | -| Created | 2020-02-11 | -| Expires | 2022-01-13 | -| Cipher |AES-256| -| Fingerprint | C377 E9D5 2D5C 137D 3DD5 73B5 BEA F6EBF 6311 06F9 | - -``` ------BEGIN PGP PUBLIC KEY BLOCK----- - -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 -SL4taAGY0BDuA3zhB7p5tP8= -=YBvx ------END PGP PUBLIC KEY BLOCK----- -``` From a452bbf05adfbc8067dda8bc80264347d927ee49 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Thu, 1 Sep 2022 11:01:25 +0200 Subject: [PATCH 687/948] MON-14818: encoding fixed (MON-14894) (#263) --- gorgone/contrib/gorgone_audit.pl | 4 +-- gorgone/contrib/mbi/centreonBIETL | 4 +-- gorgone/contrib/mbi/dimensionBuilder.pl | 4 +-- gorgone/contrib/mbi/eventStatisticsBuilder.pl | 4 +-- gorgone/contrib/mbi/importData.pl | 4 +-- .../contrib/mbi/perfdataStatisticsBuilder.pl | 4 +-- gorgone/docs/configuration.md | 3 +-- gorgone/gorgone/class/core.pm | 5 ++-- gorgone/gorgone/class/listener.pm | 8 ------ gorgone/gorgone/class/logger.pm | 1 - gorgone/gorgone/class/module.pm | 4 +-- gorgone/gorgone/class/tpapi/centreonv2.pm | 2 +- .../modules/centreon/legacycmd/class.pm | 4 +-- .../modules/centreon/mbi/etlworkers/hooks.pm | 2 +- gorgone/gorgone/modules/core/action/class.pm | 2 +- .../gorgone/modules/core/httpserver/class.pm | 7 ++++-- .../gorgone/modules/core/httpserver/hooks.pm | 6 ++++- .../modules/core/httpserverng/class.pm | 11 +++++--- .../modules/core/httpserverng/hooks.pm | 6 ++++- gorgone/gorgone/modules/core/proxy/hooks.pm | 17 ++++++------- .../gorgone/modules/core/proxy/httpserver.pm | 2 +- .../gorgone/modules/core/proxy/sshclient.pm | 3 --- gorgone/gorgone/modules/core/pull/class.pm | 2 +- gorgone/gorgone/modules/core/pullwss/class.pm | 2 +- .../gorgone/modules/plugins/newtest/class.pm | 2 +- .../gorgone/modules/plugins/newtest/hooks.pm | 4 +-- gorgone/gorgone/modules/plugins/scom/class.pm | 2 +- gorgone/gorgone/modules/plugins/scom/hooks.pm | 2 +- gorgone/gorgone/standard/api.pm | 21 ++++++++++------ gorgone/gorgone/standard/library.pm | 25 +++++++++---------- gorgone/gorgone/standard/misc.pm | 1 - 31 files changed, 85 insertions(+), 83 deletions(-) diff --git a/gorgone/contrib/gorgone_audit.pl b/gorgone/contrib/gorgone_audit.pl index 75e82ea787e..f6d86fa3fbd 100644 --- a/gorgone/contrib/gorgone_audit.pl +++ b/gorgone/contrib/gorgone_audit.pl @@ -50,7 +50,7 @@ sub json_decode { my $decoded; eval { - $decoded = JSON::XS->new->utf8->decode($options{content}); + $decoded = JSON::XS->new->decode($options{content}); }; if ($@) { $self->{logger}->writeLogError("cannot decode json response: $@"); @@ -151,7 +151,7 @@ sub get_audit_log { } if (defined($self->{audit})) { - $self->{logger}->writeLogInfo("audit result: " . JSON::XS->new->utf8->encode($self->{audit})); + $self->{logger}->writeLogInfo("audit result: " . JSON::XS->new->encode($self->{audit})); if (defined($self->{markdown})) { $self->md_output(); } diff --git a/gorgone/contrib/mbi/centreonBIETL b/gorgone/contrib/mbi/centreonBIETL index 5a813d76a8e..4e666a0f926 100644 --- a/gorgone/contrib/mbi/centreonBIETL +++ b/gorgone/contrib/mbi/centreonBIETL @@ -95,7 +95,7 @@ sub json_decode { my $decoded; eval { - $decoded = JSON::XS->new->utf8->decode($options{content}); + $decoded = JSON::XS->new->decode($options{content}); }; if ($@) { $self->{logger}->writeLogError("cannot decode json response: $@"); @@ -113,7 +113,7 @@ sub run_etl { method => 'POST', hostname => '', full_url => $self->{url} . '/api/centreon/mbietl/run', - query_form_post => JSON::XS->new->utf8->encode($self->{moptions}), + query_form_post => JSON::XS->new->encode($self->{moptions}), header => [ 'Accept-Type: application/json; charset=utf-8', 'Content-Type: application/json; charset=utf-8', diff --git a/gorgone/contrib/mbi/dimensionBuilder.pl b/gorgone/contrib/mbi/dimensionBuilder.pl index d92edb8f8b5..1e81760852d 100644 --- a/gorgone/contrib/mbi/dimensionBuilder.pl +++ b/gorgone/contrib/mbi/dimensionBuilder.pl @@ -70,7 +70,7 @@ sub json_decode { my $decoded; eval { - $decoded = JSON::XS->new->utf8->decode($options{content}); + $decoded = JSON::XS->new->decode($options{content}); }; if ($@) { $self->{logger}->writeLogError("cannot decode json response: $@"); @@ -88,7 +88,7 @@ sub run_etl { method => 'POST', hostname => '', full_url => $self->{url} . '/api/centreon/mbietl/run', - query_form_post => JSON::XS->new->utf8->encode($self->{moptions}), + query_form_post => JSON::XS->new->encode($self->{moptions}), header => [ 'Accept-Type: application/json; charset=utf-8', 'Content-Type: application/json; charset=utf-8', diff --git a/gorgone/contrib/mbi/eventStatisticsBuilder.pl b/gorgone/contrib/mbi/eventStatisticsBuilder.pl index 7c56d566146..992d454af6e 100644 --- a/gorgone/contrib/mbi/eventStatisticsBuilder.pl +++ b/gorgone/contrib/mbi/eventStatisticsBuilder.pl @@ -77,7 +77,7 @@ sub json_decode { my $decoded; eval { - $decoded = JSON::XS->new->utf8->decode($options{content}); + $decoded = JSON::XS->new->decode($options{content}); }; if ($@) { $self->{logger}->writeLogError("cannot decode json response: $@"); @@ -95,7 +95,7 @@ sub run_etl { method => 'POST', hostname => '', full_url => $self->{url} . '/api/centreon/mbietl/run', - query_form_post => JSON::XS->new->utf8->encode($self->{moptions}), + query_form_post => JSON::XS->new->encode($self->{moptions}), header => [ 'Accept-Type: application/json; charset=utf-8', 'Content-Type: application/json; charset=utf-8', diff --git a/gorgone/contrib/mbi/importData.pl b/gorgone/contrib/mbi/importData.pl index 734dd3d3de0..82e429c4abe 100644 --- a/gorgone/contrib/mbi/importData.pl +++ b/gorgone/contrib/mbi/importData.pl @@ -80,7 +80,7 @@ sub json_decode { my $decoded; eval { - $decoded = JSON::XS->new->utf8->decode($options{content}); + $decoded = JSON::XS->new->decode($options{content}); }; if ($@) { $self->{logger}->writeLogError("cannot decode json response: $@"); @@ -98,7 +98,7 @@ sub run_etl { method => 'POST', hostname => '', full_url => $self->{url} . '/api/centreon/mbietl/run', - query_form_post => JSON::XS->new->utf8->encode($self->{moptions}), + query_form_post => JSON::XS->new->encode($self->{moptions}), header => [ 'Accept-Type: application/json; charset=utf-8', 'Content-Type: application/json; charset=utf-8', diff --git a/gorgone/contrib/mbi/perfdataStatisticsBuilder.pl b/gorgone/contrib/mbi/perfdataStatisticsBuilder.pl index f5d516c7b39..801e004b18c 100644 --- a/gorgone/contrib/mbi/perfdataStatisticsBuilder.pl +++ b/gorgone/contrib/mbi/perfdataStatisticsBuilder.pl @@ -75,7 +75,7 @@ sub json_decode { my $decoded; eval { - $decoded = JSON::XS->new->utf8->decode($options{content}); + $decoded = JSON::XS->new->decode($options{content}); }; if ($@) { $self->{logger}->writeLogError("cannot decode json response: $@"); @@ -93,7 +93,7 @@ sub run_etl { method => 'POST', hostname => '', full_url => $self->{url} . '/api/centreon/mbietl/run', - query_form_post => JSON::XS->new->utf8->encode($self->{moptions}), + query_form_post => JSON::XS->new->encode($self->{moptions}), header => [ 'Accept-Type: application/json; charset=utf-8', 'Content-Type: application/json; charset=utf-8', diff --git a/gorgone/docs/configuration.md b/gorgone/docs/configuration.md index 0b0fa458b24..0789429e9b7 100644 --- a/gorgone/docs/configuration.md +++ b/gorgone/docs/configuration.md @@ -42,12 +42,11 @@ configuration: | :-------------------- | :---------------------------------------------------------------------- | :--------------------------------------------- | | internal_com_type | Type of the internal ZMQ socket | `ipc` | | internal_com_path | Path to the internal ZMQ socket | `/tmp/gorgone/routing.ipc` | -| internal_com_crypt | Internal communication crypt enabled | `false` | +| internal_com_crypt | Crypt internal communication | `true` | | internal_com_cipher | Internal communication cipher | `AES` | | internal_com_padding | Internal communication padding | `1` (mean: PKCS5) | | internal_com_keysize | Internal communication key size | `32` (bytes) | | internal_com_rotation | Internal communication time before key rotation | `1440` (minutes) | -| internal_com_crypt | Crypt internal communication | `false` | | external_com_type | Type of the external ZMQ socket | `tcp` | | external_com_path | Path to the external ZMQ socket | `*:5555` | | external_com_cipher | Cipher used for encryption | `AES` | diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index a6d6dd7b9e6..43e2b72f774 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -179,9 +179,8 @@ sub init { $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_path} = '/tmp/gorgone/routing-' . $time_hi . '.ipc' if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_path}) || $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_path} eq ''); - if (defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_crypt}) && $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_crypt} =~ /^(?:true|1)$/i) { - $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_crypt} = 1; - } else { + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_crypt} = 1; + if (defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_crypt}) && $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_crypt} =~ /^(?:false|0)$/i) { $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_crypt} = 0; } diff --git a/gorgone/gorgone/class/listener.pm b/gorgone/gorgone/class/listener.pm index 50471993c29..4c523648469 100644 --- a/gorgone/gorgone/class/listener.pm +++ b/gorgone/gorgone/class/listener.pm @@ -24,7 +24,6 @@ use strict; use warnings; use gorgone::standard::constants qw(:all); use gorgone::standard::library; -use Encode; sub new { my ($class, %options) = @_; @@ -49,16 +48,9 @@ sub event_log { delete $self->{tokens}->{ $options{token} }; } - my $encoded = 0; foreach (keys %{$events->{events}}) { $self->{logger}->writeLogDebug("[listener] trigger event '$options{token}'"); - # it will be decoded by module (hooks.pm). So in some case, we want to avoid double utf8 decode - if ($encoded == 0 && defined($options{encode_utf8}) && $options{encode_utf8} == 1) { - $options{data} = Encode::encode('UTF-8', $options{data}); - $encoded = 1; - } - $self->{gorgone_core}->message_run( message => '[' . $_ . '] [' . $options{token} . '] [] { "code": ' . $options{code} . ', "data": ' . $options{data} . ' }', router_type => 'internal' diff --git a/gorgone/gorgone/class/logger.pm b/gorgone/gorgone/class/logger.pm index c169033b1ba..f5ea65ecf79 100644 --- a/gorgone/gorgone/class/logger.pm +++ b/gorgone/gorgone/class/logger.pm @@ -210,7 +210,6 @@ sub writeLog { return; } - $newmsg = encode('UTF-8', $newmsg); chomp($newmsg); if ($self->{log_mode} == 0) { print "$newmsg\n"; diff --git a/gorgone/gorgone/class/module.pm b/gorgone/gorgone/class/module.pm index fc9835eb6e0..1672197e991 100644 --- a/gorgone/gorgone/class/module.pm +++ b/gorgone/gorgone/class/module.pm @@ -247,7 +247,7 @@ sub json_encode { my $encoded_arguments; eval { - $encoded_arguments = JSON::XS->new->utf8->encode($options{argument}); + $encoded_arguments = JSON::XS->new->encode($options{argument}); }; if ($@) { $self->{logger}->writeLogError("[$self->{module_id}]$self->{container} $options{method} - cannot encode json: $@"); @@ -262,7 +262,7 @@ sub json_decode { my $decoded_arguments; eval { - $decoded_arguments = JSON::XS->new->utf8->decode($options{argument}); + $decoded_arguments = JSON::XS->new->decode($options{argument}); }; if ($@) { $self->{logger}->writeLogError("[$self->{module_id}]$self->{container} $options{method} - cannot decode json: $@"); diff --git a/gorgone/gorgone/class/tpapi/centreonv2.pm b/gorgone/gorgone/class/tpapi/centreonv2.pm index d3939b12734..9bd5d84a240 100644 --- a/gorgone/gorgone/class/tpapi/centreonv2.pm +++ b/gorgone/gorgone/class/tpapi/centreonv2.pm @@ -42,7 +42,7 @@ sub json_decode { my $decoded; eval { - $decoded = JSON::XS->new->utf8->decode($options{content}); + $decoded = JSON::XS->new->decode($options{content}); }; if ($@) { $self->{is_error} = 1; diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index f5f9adde2dd..463eef1c19e 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -615,9 +615,9 @@ sub action_addimporttaskwithparent { sub move_cmd_file { my ($self, %options) = @_; - my $operator = '+<:encoding(UTF-8)'; + my $operator = '+<'; if ($self->{config}->{dirty_mode} == 1) { - $operator = '<:encoding(UTF-8)'; + $operator = '<'; } my $handle; if (-e $options{dst}) { diff --git a/gorgone/gorgone/modules/centreon/mbi/etlworkers/hooks.pm b/gorgone/gorgone/modules/centreon/mbi/etlworkers/hooks.pm index 742a938d81a..c75b4a22ba6 100644 --- a/gorgone/gorgone/modules/centreon/mbi/etlworkers/hooks.pm +++ b/gorgone/gorgone/modules/centreon/mbi/etlworkers/hooks.pm @@ -69,7 +69,7 @@ sub routing { my $data; eval { - $data = JSON::XS->new->utf8->decode($options{data}); + $data = JSON::XS->new->decode($options{data}); }; if ($@) { $options{logger}->writeLogError("[proxy] Cannot decode json data: $@"); diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index 7995b0ecaff..b28c24462cf 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -314,7 +314,7 @@ sub validate_plugins { my $plugins; eval { - $plugins = JSON::XS->new->utf8->decode($content); + $plugins = JSON::XS->new->decode($content); }; if ($@) { return (1, 'cannot decode json'); diff --git a/gorgone/gorgone/modules/core/httpserver/class.pm b/gorgone/gorgone/modules/core/httpserver/class.pm index 1ed7a797bee..4eeb09b784b 100644 --- a/gorgone/gorgone/modules/core/httpserver/class.pm +++ b/gorgone/gorgone/modules/core/httpserver/class.pm @@ -217,7 +217,10 @@ sub run { exit(0); } - next if (!defined($connection)); + if (!defined($connection)) { + gorgone::standard::api::event(httpserver => $self); + next; + } while (my $request = $connection->get_request) { if ($connection->antique_client eq '1') { @@ -325,7 +328,7 @@ sub api_call { my $content; eval { - $content = JSON::XS->new->utf8->decode($request->content) + $content = JSON::XS->new->decode($request->content) if ($request->method =~ /POST|PATCH/ && defined($request->content)); }; if ($@) { diff --git a/gorgone/gorgone/modules/core/httpserver/hooks.pm b/gorgone/gorgone/modules/core/httpserver/hooks.pm index c36515aa036..2258753d150 100644 --- a/gorgone/gorgone/modules/core/httpserver/hooks.pm +++ b/gorgone/gorgone/modules/core/httpserver/hooks.pm @@ -138,7 +138,11 @@ sub check { return $count; } -sub broadcast {} +sub broadcast { + my (%options) = @_; + + routing(%options); +} # Specific functions sub create_child { diff --git a/gorgone/gorgone/modules/core/httpserverng/class.pm b/gorgone/gorgone/modules/core/httpserverng/class.pm index 7b17745b85a..b57bbdbb52d 100644 --- a/gorgone/gorgone/modules/core/httpserverng/class.pm +++ b/gorgone/gorgone/modules/core/httpserverng/class.pm @@ -69,7 +69,7 @@ websocket '/' => sub { my $content; eval { - $content = JSON::XS->new->utf8->decode($msg); + $content = JSON::XS->new->decode($msg); }; if ($@) { $connector->close_websocket( @@ -316,7 +316,7 @@ sub read_log_event { if (defined($options{data})) { my $content; eval { - $content = JSON::XS->new->utf8->decode($options{data}); + $content = JSON::XS->new->decode($options{data}); }; if ($@) { $response = { error => 'decode_error', message => 'Cannot decode response' }; @@ -345,7 +345,7 @@ sub read_listener { my $content; eval { - $content = JSON::XS->new->utf8->decode($options{data}); + $content = JSON::XS->new->decode($options{data}); }; if ($@) { $self->{token_watch}->{ $options{token} }->{mojo}->render(json => { error => 'decode_error', message => 'Cannot decode response' }); @@ -393,7 +393,10 @@ sub read_zmq_events { } } if ((my $method = $connector->can('action_' . lc($action)))) { - $method->($connector, token => $token, data => $data); + my ($rv, $decoded) = $connector->json_decode(argument => $data, token => $token); + if (!$rv) { + $method->($connector, token => $token, data => $decoded); + } } } } else { diff --git a/gorgone/gorgone/modules/core/httpserverng/hooks.pm b/gorgone/gorgone/modules/core/httpserverng/hooks.pm index b43a6018fca..fc2e69898f9 100644 --- a/gorgone/gorgone/modules/core/httpserverng/hooks.pm +++ b/gorgone/gorgone/modules/core/httpserverng/hooks.pm @@ -139,7 +139,11 @@ sub check { return $count; } -sub broadcast {} +sub broadcast { + my (%options) = @_; + + routing(%options); +} # Specific functions sub create_child { diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index eab23328bd4..374fc3ce72f 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -135,7 +135,7 @@ sub routing { my $data; eval { - $data = JSON::XS->new->utf8->decode($options{data}); + $data = JSON::XS->new->decode($options{data}); }; if ($@) { $options{logger}->writeLogError("[proxy] Cannot decode json data: $@"); @@ -192,7 +192,7 @@ sub routing { routing( action => 'PROXYADDNODE', target => $node_id, - data => JSON::XS->new->utf8->encode($register_nodes->{$node_id}), + data => JSON::XS->new->encode($register_nodes->{$node_id}), gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger} @@ -438,7 +438,7 @@ sub check { routing( target => $_, action => 'PROXYCLOSECONNECTION', - data => JSON::XS->new->utf8->encode({ id => $_ }), + data => JSON::XS->new->encode({ id => $_ }), gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger} @@ -565,7 +565,7 @@ sub pathway { routing( target => $_, action => 'PROXYCLOSEREADCHANNEL', - data => JSON::XS->new->utf8->encode({ id => $_ }), + data => JSON::XS->new->encode({ id => $_ }), gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger} @@ -638,8 +638,7 @@ sub setlogs { code => $_->{code}, token => $_->{token}, instant => $_->{instant}, - data => $_->{data}, - encode_utf8 => 1 + data => $_->{data} ); last if ($status == -1); $ctime_recent = $_->{ctime} if ($ctime_recent < $_->{ctime}); @@ -890,7 +889,7 @@ sub unregister_nodes { routing( action => 'PROXYDELNODE', target => $node->{id}, - data => JSON::XS->new->utf8->encode($node), + data => JSON::XS->new->encode($node), gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger} @@ -1036,7 +1035,7 @@ sub register_nodes { routing( action => 'PROXYADDNODE', target => $node->{id}, - data => JSON::XS->new->utf8->encode($register_nodes->{ $node->{id} }), + data => JSON::XS->new->encode($register_nodes->{ $node->{id} }), gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger} @@ -1045,7 +1044,7 @@ sub register_nodes { routing( action => 'PROXYADDNODE', target => $node->{id}, - data => JSON::XS->new->utf8->encode($node), + data => JSON::XS->new->encode($node), gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger} diff --git a/gorgone/gorgone/modules/core/proxy/httpserver.pm b/gorgone/gorgone/modules/core/proxy/httpserver.pm index b7aac1072ce..90ef57aad8e 100644 --- a/gorgone/gorgone/modules/core/proxy/httpserver.pm +++ b/gorgone/gorgone/modules/core/proxy/httpserver.pm @@ -322,7 +322,7 @@ sub is_logged_websocket { my $content; eval { - $content = JSON::XS->new->utf8->decode($1); + $content = JSON::XS->new->decode($1); }; if ($@) { $self->close_websocket( diff --git a/gorgone/gorgone/modules/core/proxy/sshclient.pm b/gorgone/gorgone/modules/core/proxy/sshclient.pm index 7c371dda50d..d0f40303e12 100644 --- a/gorgone/gorgone/modules/core/proxy/sshclient.pm +++ b/gorgone/gorgone/modules/core/proxy/sshclient.pm @@ -30,7 +30,6 @@ use gorgone::standard::misc; use File::Basename; use Time::HiRes; use gorgone::standard::constants qw(:all); -use Encode; use MIME::Base64; sub new { @@ -313,7 +312,6 @@ sub action_enginecommand { if ($options{target_direct} == 0) { foreach my $command (@{$options{data}->{content}->{commands}}) { chomp $command; - $command = Encode::decode('UTF-8', $command); my $msg = "[sshclient] Handling command 'EXTERNALCMD'"; $msg .= ", Target: '" . $options{target} . "'" if (defined($options{target})); $msg .= ", Parameters: '" . $command . "'" if (defined($command)); @@ -362,7 +360,6 @@ sub action_enginecommand { }; foreach my $command (@{$options{data}->{content}->{commands}}) { - $command = Encode::decode('UTF-8', $command); $self->{logger}->writeLogInfo("[sshclient] Processing external command '" . $command . "'"); if ($self->{sftp}->write(handle_file => $file, data => $command . "\n") != Libssh::Session::SSH_OK) { $self->{logger}->writeLogError("[sshclient] Command file '$command_file' must be writeable"); diff --git a/gorgone/gorgone/modules/core/pull/class.pm b/gorgone/gorgone/modules/core/pull/class.pm index bf02a130a5d..7baa1bad95d 100644 --- a/gorgone/gorgone/modules/core/pull/class.pm +++ b/gorgone/gorgone/modules/core/pull/class.pm @@ -117,7 +117,7 @@ sub transmit_back { if ($options{message} =~ /^\[ACK\]\s+\[(.*?)\]\s+(.*)/m) { my $data; eval { - $data = JSON::XS->new->utf8->decode($2); + $data = JSON::XS->new->decode($2); }; if ($@) { return $options{message}; diff --git a/gorgone/gorgone/modules/core/pullwss/class.pm b/gorgone/gorgone/modules/core/pullwss/class.pm index 92e3921a923..793aee080a6 100644 --- a/gorgone/gorgone/modules/core/pullwss/class.pm +++ b/gorgone/gorgone/modules/core/pullwss/class.pm @@ -238,7 +238,7 @@ sub transmit_back { if ($options{message} =~ /^\[ACK\]\s+\[(.*?)\]\s+(.*)/m) { my $data; eval { - $data = JSON::XS->new->utf8->decode($2); + $data = JSON::XS->new->decode($2); }; if ($@) { return $options{message}; diff --git a/gorgone/gorgone/modules/plugins/newtest/class.pm b/gorgone/gorgone/modules/plugins/newtest/class.pm index 80951619526..1fcef6e2ce4 100644 --- a/gorgone/gorgone/modules/plugins/newtest/class.pm +++ b/gorgone/gorgone/modules/plugins/newtest/class.pm @@ -596,7 +596,7 @@ sub event { if ((my $method = $connector->can('action_' . lc($1)))) { $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; my ($action, $token) = ($1, $2); - my $data = JSON::XS->new->utf8->decode($3); + my $data = JSON::XS->new->decode($3); $method->($connector, token => $token, data => $data); } } diff --git a/gorgone/gorgone/modules/plugins/newtest/hooks.pm b/gorgone/gorgone/modules/plugins/newtest/hooks.pm index 33b7007d791..b6ca486a702 100644 --- a/gorgone/gorgone/modules/plugins/newtest/hooks.pm +++ b/gorgone/gorgone/modules/plugins/newtest/hooks.pm @@ -68,7 +68,7 @@ sub routing { my $data; eval { - $data = JSON::XS->new->utf8->decode($options{data}); + $data = JSON::XS->new->decode($options{data}); }; if ($@) { $options{logger}->writeLogError("[newtest] Cannot decode json data: $@"); @@ -209,7 +209,7 @@ sub get_containers { my $list_scenario; eval { - $list_scenario = JSON::XS->new->utf8->decode($_->{list_scenario_status}); + $list_scenario = JSON::XS->new->decode($_->{list_scenario_status}); }; if ($@) { $options{logger}->writeLogError("[newtest] cannot load container '" . $_->{name} . "' - cannot decode list scenario option"); diff --git a/gorgone/gorgone/modules/plugins/scom/class.pm b/gorgone/gorgone/modules/plugins/scom/class.pm index 2585f9c6cbc..c59fe3eac6e 100644 --- a/gorgone/gorgone/modules/plugins/scom/class.pm +++ b/gorgone/gorgone/modules/plugins/scom/class.pm @@ -484,7 +484,7 @@ sub event { if ((my $method = $connector->can('action_' . lc($1)))) { $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; my ($action, $token) = ($1, $2); - my $data = JSON::XS->new->utf8->decode($3); + my $data = JSON::XS->new->decode($3); $method->($connector, token => $token, data => $data); } } diff --git a/gorgone/gorgone/modules/plugins/scom/hooks.pm b/gorgone/gorgone/modules/plugins/scom/hooks.pm index 3af21277c16..a8b830677f4 100644 --- a/gorgone/gorgone/modules/plugins/scom/hooks.pm +++ b/gorgone/gorgone/modules/plugins/scom/hooks.pm @@ -67,7 +67,7 @@ sub routing { my $data; eval { - $data = JSON::XS->new->utf8->decode($options{data}); + $data = JSON::XS->new->decode($options{data}); }; if ($@) { $options{logger}->writeLogError("[scom] Cannot decode json data: $@"); diff --git a/gorgone/gorgone/standard/api.pm b/gorgone/gorgone/standard/api.pm index bd4f9410151..651aaa58b0a 100644 --- a/gorgone/gorgone/standard/api.pm +++ b/gorgone/gorgone/standard/api.pm @@ -30,7 +30,7 @@ use JSON::XS; my $module; my $socket; -my $results; +my $results = {}; my $action_token; sub root { @@ -163,14 +163,14 @@ sub call_internal { if (defined($results->{$action_token}->{data})) { my $content; eval { - $content = JSON::XS->new->utf8->decode($results->{$action_token}->{data}); + $content = JSON::XS->new->decode($results->{$action_token}->{data}); }; if ($@) { $response = '{"error":"decode_error","message":"Cannot decode response"}'; } else { if (defined($content->{data})) { eval { - $response = JSON::XS->new->utf8->encode($content->{data}); + $response = JSON::XS->new->encode($content->{data}); }; if ($@) { $response = '{"error":"encode_error","message":"Cannot encode response"}'; @@ -231,13 +231,13 @@ sub get_log { if (defined($results->{ $token_log }) && defined($results->{ $token_log }->{data})) { my $content; eval { - $content = JSON::XS->new->utf8->decode($results->{ $token_log }->{data}); + $content = JSON::XS->new->decode($results->{ $token_log }->{data}); }; if ($@) { $response = '{"error":"decode_error","message":"Cannot decode response"}'; } elsif (defined($content->{data}->{result}) && scalar(@{$content->{data}->{result}}) > 0) { eval { - $response = JSON::XS->new->utf8->encode( + $response = JSON::XS->new->encode( { message => "Logs found", token => $options{token}, @@ -255,8 +255,11 @@ sub get_log { } sub event { + my (%options) = @_; + + my $httpserver = defined($options{httpserver}) ? $options{httpserver} : $module; while (1) { - my $message = $module->read_message(); + my $message = $httpserver->read_message(); last if (!defined($message)); if ($message =~ /^\[(.*?)\]\s+\[([a-zA-Z0-9:\-_]*?)\]\s+\[.*?\]\s+(.*)$/m || @@ -267,8 +270,10 @@ sub event { token => $token, data => $data }; - if ((my $method = $module->can('action_' . lc($action)))) { - $method->($module, token => $token, data => $data); + if ((my $method = $httpserver->can('action_' . lc($action)))) { + my ($rv, $decoded) = $httpserver->json_decode(argument => $data, token => $token); + next if ($rv); + $method->($httpserver, token => $token, data => $decoded); } } } diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index ddc70c941dc..622e4897999 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -295,7 +295,7 @@ sub addlistener { my $data; eval { - $data = JSON::XS->new->utf8->decode($options{data}); + $data = JSON::XS->new->decode($options{data}); }; if ($@) { return (GORGONE_ACTION_FINISH_KO, { message => 'request not well formatted' }); @@ -340,7 +340,7 @@ sub unloadmodule { my $data; eval { - $data = JSON::XS->new->utf8->decode($options{data}); + $data = JSON::XS->new->decode($options{data}); }; if ($@) { return (GORGONE_ACTION_FINISH_KO, { message => 'request not well formatted' }); @@ -365,7 +365,7 @@ sub loadmodule { my $data; eval { - $data = JSON::XS->new->utf8->decode($options{data}); + $data = JSON::XS->new->decode($options{data}); }; if ($@) { return (GORGONE_ACTION_FINISH_KO, { message => 'request not well formatted' }); @@ -392,7 +392,7 @@ sub synclogs { my $data; eval { - $data = JSON::XS->new->utf8->decode($options{data}); + $data = JSON::XS->new->decode($options{data}); }; if ($@) { return (GORGONE_ACTION_FINISH_KO, { message => 'request not well formatted' }); @@ -437,7 +437,7 @@ sub setmodulekey { my $data; eval { - $data = JSON::XS->new->utf8->decode($options{data}); + $data = JSON::XS->new->decode($options{data}); }; if ($@) { return (GORGONE_ACTION_FINISH_KO, { message => 'request not well formatted' }); @@ -467,7 +467,7 @@ sub setcoreid { my $data; eval { - $data = JSON::XS->new->utf8->decode($options{data}); + $data = JSON::XS->new->decode($options{data}); }; if ($@) { return (GORGONE_ACTION_FINISH_KO, { message => 'request not well formatted' }); @@ -513,7 +513,7 @@ sub putlog { my $data; eval { - $data = JSON::XS->new->utf8->decode($options{data}); + $data = JSON::XS->new->decode($options{data}); }; if ($@) { return (GORGONE_ACTION_FINISH_KO, { message => 'request not well formatted' }); @@ -538,7 +538,7 @@ sub getlog { my $data; eval { - $data = JSON::XS->new->utf8->decode($options{data}); + $data = JSON::XS->new->decode($options{data}); }; if ($@) { return (GORGONE_ACTION_FINISH_KO, { message => 'request not well formatted' }); @@ -579,7 +579,7 @@ sub kill { my $data; eval { - $data = JSON::XS->new->utf8->decode($options{data}); + $data = JSON::XS->new->decode($options{data}); }; if ($@) { return (GORGONE_ACTION_FINISH_KO, { message => 'request not well formatted' }); @@ -682,8 +682,7 @@ sub add_history { $listener->event_log( token => $options{token}, code => $options{code}, - data => $options{data}, - encode_utf8 => $options{encode_utf8} + data => $options{data} ); } return $status; @@ -698,7 +697,7 @@ sub json_encode { my $data; eval { - $data = JSON::XS->new->utf8->encode($options{data}); + $data = JSON::XS->new->encode($options{data}); }; if ($@) { if (defined($options{logger})) { @@ -715,7 +714,7 @@ sub json_decode { my $data; eval { - $data = JSON::XS->new->utf8->decode($options{data}); + $data = JSON::XS->new->decode($options{data}); }; if ($@) { if (defined($options{logger})) { diff --git a/gorgone/gorgone/standard/misc.pm b/gorgone/gorgone/standard/misc.pm index db75393dcf2..689a90733c8 100644 --- a/gorgone/gorgone/standard/misc.pm +++ b/gorgone/gorgone/standard/misc.pm @@ -164,7 +164,6 @@ sub backtick { } if ($pid) { - binmode(KID, ":encoding(UTF-8)"); eval { local $SIG{ALRM} = sub { die "Timeout by signal ALARM\n"; }; alarm( $arg{timeout} ); From dfb81c196f6acbef4ce69b4ad722c8df53efc08e Mon Sep 17 00:00:00 2001 From: lchrdn <89968908+lchrdn@users.noreply.github.com> Date: Thu, 1 Sep 2022 14:07:42 +0200 Subject: [PATCH 688/948] Adding some debug for plugin auto install (#264) --- gorgone/gorgone/modules/core/action/class.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index b28c24462cf..4fe381c145a 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -248,6 +248,7 @@ sub install_plugins { redirect_stderr => 1, logger => $self->{logger} ); + $self->{logger}->writeLogDebug("[action] install plugins. Command output: [\"$stdout\"]"); if ($error != 0) { return (-1, 'install plugins command issue: ' . $stdout); } From 7b7029f6231967f25b63afa134f965e21faa1e2d Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Mon, 12 Sep 2022 11:34:38 +0200 Subject: [PATCH 689/948] fix regression with insecure tar action (#266) --- gorgone/gorgone/class/core.pm | 3 ++- gorgone/gorgone/modules/core/action/class.pm | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 43e2b72f774..ecf89fc8fce 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -179,9 +179,10 @@ sub init { $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_path} = '/tmp/gorgone/routing-' . $time_hi . '.ipc' if (!defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_path}) || $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_path} eq ''); - $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_crypt} = 1; if (defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_crypt}) && $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_crypt} =~ /^(?:false|0)$/i) { $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_crypt} = 0; + } else { + $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_crypt} = 1; } $self->{internal_crypt} = { enabled => 0 }; diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index 4fe381c145a..875029760a8 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -60,6 +60,10 @@ sub new { $connector->{allowed_cmds} = $connector->{config}->{allowed_cmds} if (defined($connector->{config}->{allowed_cmds}) && ref($connector->{config}->{allowed_cmds}) eq 'ARRAY'); + if (defined($connector->{config}->{tar_insecure_extra_mode}) && $connector->{config}->{tar_insecure_extra_mode} =~ /^(?:1|true)$/) { + $Archive::Tar::INSECURE_EXTRACT_MODE = 1; + } + $connector->{paranoid_plugins} = defined($connector->{config}->{paranoid_plugins}) && $connector->{config}->{paranoid_plugins} =~ /true|1/i ? 1 : 0; From 290b0a564d8f16b4199959fc91e50730211419b4 Mon Sep 17 00:00:00 2001 From: tuntoja <58987095+tuntoja@users.noreply.github.com> Date: Mon, 19 Sep 2022 21:01:53 +0200 Subject: [PATCH 690/948] update jenkins file with 22.10 branches names (#268) --- gorgone/Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/Jenkinsfile b/gorgone/Jenkinsfile index 92e0ff810c6..5dda4569bf1 100644 --- a/gorgone/Jenkinsfile +++ b/gorgone/Jenkinsfile @@ -2,8 +2,8 @@ ** Variables. */ def serie = '22.10' -def maintenanceBranch = "${serie}.x" -def qaBranch = "dev-${serie}.x" +def maintenanceBranch = "master" +def qaBranch = "develop" env.REF_BRANCH = 'master' env.PROJECT='centreon-gorgone' if (env.BRANCH_NAME.startsWith('release-')) { From 8db0ae05446b96274bf62ba7e8d61cc41db44076 Mon Sep 17 00:00:00 2001 From: Thomas Arnaud <38663853+Nohzoh@users.noreply.github.com> Date: Tue, 27 Sep 2022 14:05:41 +0200 Subject: [PATCH 691/948] gorgone to get predictions files in new format (#273) --- .../centreon/anomalydetection/class.pm | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm index ec14f28ddc3..2202045dbca 100644 --- a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm +++ b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm @@ -1,4 +1,4 @@ -# +# # Copyright 2020 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets @@ -145,6 +145,7 @@ sub saas_api_request { ($status, $payload) = $self->json_encode(argument => $options{payload}); return 1 if ($status == 1); } + my $accept = defined $options{accept} ? $options{accept} : '*/*'; ($status, my $response) = $self->{http}->request( method => $options{method}, hostname => '', @@ -153,6 +154,7 @@ sub saas_api_request { header => [ 'Accept-Type: application/json; charset=utf-8', 'Content-Type: application/json; charset=utf-8', + 'Accept: ' . $accept, 'x-api-key: ' . $self->{saas_token} ], proxyurl => $self->{proxy_url}, @@ -232,8 +234,8 @@ sub get_centreon_anomaly_metrics { request => ' SELECT mas.*, hsr.host_host_id as host_id, nhr.nagios_server_id as instance_id FROM mod_anomaly_service mas - LEFT JOIN (host_service_relation hsr, ns_host_relation nhr) ON - (mas.service_id = hsr.service_service_id AND hsr.host_host_id = nhr.host_host_id) + LEFT JOIN (host_service_relation hsr, ns_host_relation nhr) ON + (mas.service_id = hsr.service_service_id AND hsr.host_host_id = nhr.host_host_id) ', keys => 'id', mode => 1 @@ -253,7 +255,7 @@ sub save_centreon_previous_register { my ($query, $query_append) = ('', ''); foreach (keys %{$self->{unregister_metrics_centreon}}) { - $query .= $query_append . + $query .= $query_append . 'UPDATE mod_anomaly_service SET' . ' saas_model_id = ' . $self->{class_object_centreon}->quote(value => $self->{unregister_metrics_centreon}->{$_}->{saas_model_id}) . ',' . ' saas_metric_id = ' . $self->{class_object_centreon}->quote(value => $self->{unregister_metrics_centreon}->{$_}->{saas_metric_id}) . ',' . @@ -286,7 +288,7 @@ sub saas_register_metrics { my $register_centreon_metrics = {}; my ($query, $query_append) = ('', ''); - + $self->{generate_metrics_lua} = 0; foreach (keys %{$self->{centreon_metrics}}) { # saas_creation_date is set when we need to register it @@ -356,7 +358,7 @@ sub saas_register_metrics { saas_metric_id => $result->{metrics}->[0]->{id} }; - $query .= $query_append . + $query .= $query_append . 'UPDATE mod_anomaly_service SET' . ' saas_model_id = ' . $self->{class_object_centreon}->quote(value => $register_centreon_metrics->{$_}->{saas_model_id}) . ',' . ' saas_metric_id = ' . $self->{class_object_centreon}->quote(value => $register_centreon_metrics->{$_}->{saas_metric_id}) . ',' . @@ -489,7 +491,8 @@ sub saas_get_predicts { ($status, my $result) = $self->saas_api_request( endpoint => '/machinelearning/' . $self->{centreon_metrics}->{$_}->{saas_model_id} . '/predicts', method => 'GET', - http_code_continue => '^2' + http_code_continue => '^2', + accept => 'application/vnd.centreon.v2+json' ); next if ($status); @@ -531,7 +534,7 @@ sub saas_get_predicts { $engine_reload->{ $self->{centreon_metrics}->{$_}->{instance_id} } = [] if (!defined($engine_reload->{ $self->{centreon_metrics}->{$_}->{instance_id} })); push @{$engine_reload->{ $self->{centreon_metrics}->{$_}->{instance_id} }}, $poller->{cfg_dir} . '/anomaly/' . $_ . '.json'; - $query .= $query_append . + $query .= $query_append . 'UPDATE mod_anomaly_service SET' . ' saas_update_date = ' . time() . ' WHERE `id` = ' . $_; From bbae54d248e7d270198c0e211f050613fe7b82fa Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Fri, 25 Nov 2022 13:39:06 +0100 Subject: [PATCH 692/948] chore(ci): add gorgone workflow --- .github/workflows/gorgone.yml | 101 ++++++++++++++++++ .../debian/centreon-gorgone.dirs | 0 .../debian/centreon-gorgone.install | 0 .../debian/centreon-gorgone.logrotate | 0 .../debian/centreon-gorgone.postinst | 0 gorgone/{ci => packaging}/debian/control | 0 gorgone/{ci => packaging}/debian/copyright | 0 .../{ci => packaging}/debian/extra/gorgoned | 0 .../debian/extra/gorgoned.service | 0 gorgone/{ci => packaging}/debian/rules | 0 .../{ci => packaging}/debian/source/format | 0 gorgone/{ci => packaging}/debian/substvars | 0 12 files changed, 101 insertions(+) create mode 100644 .github/workflows/gorgone.yml rename gorgone/{ci => packaging}/debian/centreon-gorgone.dirs (100%) rename gorgone/{ci => packaging}/debian/centreon-gorgone.install (100%) rename gorgone/{ci => packaging}/debian/centreon-gorgone.logrotate (100%) rename gorgone/{ci => packaging}/debian/centreon-gorgone.postinst (100%) rename gorgone/{ci => packaging}/debian/control (100%) rename gorgone/{ci => packaging}/debian/copyright (100%) rename gorgone/{ci => packaging}/debian/extra/gorgoned (100%) rename gorgone/{ci => packaging}/debian/extra/gorgoned.service (100%) rename gorgone/{ci => packaging}/debian/rules (100%) rename gorgone/{ci => packaging}/debian/source/format (100%) rename gorgone/{ci => packaging}/debian/substvars (100%) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml new file mode 100644 index 00000000000..44d065178c0 --- /dev/null +++ b/.github/workflows/gorgone.yml @@ -0,0 +1,101 @@ +name: gorgone + +on: + workflow_dispatch: + pull_request: + paths: + - ".github/workflows/gorgone.yml" + - "centreon-gorgone/**" + push: + branches: + - develop + paths: + - ".github/workflows/gorgone.yml" + - "centreon-gorgone/**" + tags: + - centreon-gorgone-* + +env: + base_directory: centreon-gorgone + +jobs: + get-version: + uses: ./.github/workflows/get-version.yml + with: + version_file: centreon/www/install/insertBaseConf.sql + + package: + needs: [get-version] + + strategy: + fail-fast: false + matrix: + distrib: [el7, el8, bullseye] + include: + - package_extension: rpm + image: packaging-centos7 + distrib: el7 + - package_extension: rpm + image: packaging-alma8 + distrib: el8 + - package_extension: deb + image: packaging-bullseye + distrib: bullseye + + uses: ./.github/workflows/package.yml + with: + base_directory: centreon + spec_file: centreon-gorgone/packaging/centreon-gorgone.spectemplate + package_extension: ${{ matrix.package_extension }} + image_name: ${{ matrix.image }} + module_name: centreon-gorgone + major_version: ${{ needs.get-version.outputs.major_version }} + minor_version: ${{ needs.get-version.outputs.minor_version }} + release: ${{ needs.get-version.outputs.release }} + cache_key: ${{ github.sha }}-${{ github.run_id }}-${{ matrix.package_extension }}-${{ matrix.distrib }} + secrets: + artifactory_username: ${{ secrets.REPOS_USERNAME }} + artifactory_password: ${{ secrets.REPOS_PASSWORD }} + + deliver-rpm: + runs-on: [self-hosted, common] + needs: [package] + strategy: + matrix: + distrib: [el7, el8] + + steps: + - name: Checkout sources + uses: actions/checkout@v3 + + - name: Delivery + uses: ./.github/actions/rpm-delivery + with: + module_name: centreon-gorgone + distrib: ${{ matrix.distrib }} + version: ${{ needs.get-version.outputs.major_version }} + nexus_username: ${{ secrets.REPOS_USERNAME }} + nexus_password: ${{ secrets.REPOS_PASSWORD }} + cache_key: ${{ github.sha }}-${{ github.run_id }}-rpm-${{ matrix.distrib }} + + deliver-deb: + runs-on: [self-hosted, common] + needs: [get-version, api-integration-test, legacy-e2e-test, e2e-test, performances-test] + if: ${{ always() && (contains(join(needs.*.result, ','), 'success') || contains(join(needs.*.result, ','), 'skipped')) }} + strategy: + matrix: + distrib: [bullseye] + + steps: + - name: Checkout sources + uses: actions/checkout@v3 + + - name: Delivery + uses: ./.github/actions/deb-delivery + with: + module_name: centreon-gorgone + distrib: ${{ matrix.distrib }} + version: ${{ needs.get-version.outputs.major_version }} + nexus_username: ${{ secrets.REPOS_USERNAME }} + nexus_password: ${{ secrets.REPOS_PASSWORD }} + cache_key: ${{ github.sha }}-${{ github.run_id }}-deb-${{ matrix.distrib }} diff --git a/gorgone/ci/debian/centreon-gorgone.dirs b/gorgone/packaging/debian/centreon-gorgone.dirs similarity index 100% rename from gorgone/ci/debian/centreon-gorgone.dirs rename to gorgone/packaging/debian/centreon-gorgone.dirs diff --git a/gorgone/ci/debian/centreon-gorgone.install b/gorgone/packaging/debian/centreon-gorgone.install similarity index 100% rename from gorgone/ci/debian/centreon-gorgone.install rename to gorgone/packaging/debian/centreon-gorgone.install diff --git a/gorgone/ci/debian/centreon-gorgone.logrotate b/gorgone/packaging/debian/centreon-gorgone.logrotate similarity index 100% rename from gorgone/ci/debian/centreon-gorgone.logrotate rename to gorgone/packaging/debian/centreon-gorgone.logrotate diff --git a/gorgone/ci/debian/centreon-gorgone.postinst b/gorgone/packaging/debian/centreon-gorgone.postinst similarity index 100% rename from gorgone/ci/debian/centreon-gorgone.postinst rename to gorgone/packaging/debian/centreon-gorgone.postinst diff --git a/gorgone/ci/debian/control b/gorgone/packaging/debian/control similarity index 100% rename from gorgone/ci/debian/control rename to gorgone/packaging/debian/control diff --git a/gorgone/ci/debian/copyright b/gorgone/packaging/debian/copyright similarity index 100% rename from gorgone/ci/debian/copyright rename to gorgone/packaging/debian/copyright diff --git a/gorgone/ci/debian/extra/gorgoned b/gorgone/packaging/debian/extra/gorgoned similarity index 100% rename from gorgone/ci/debian/extra/gorgoned rename to gorgone/packaging/debian/extra/gorgoned diff --git a/gorgone/ci/debian/extra/gorgoned.service b/gorgone/packaging/debian/extra/gorgoned.service similarity index 100% rename from gorgone/ci/debian/extra/gorgoned.service rename to gorgone/packaging/debian/extra/gorgoned.service diff --git a/gorgone/ci/debian/rules b/gorgone/packaging/debian/rules similarity index 100% rename from gorgone/ci/debian/rules rename to gorgone/packaging/debian/rules diff --git a/gorgone/ci/debian/source/format b/gorgone/packaging/debian/source/format similarity index 100% rename from gorgone/ci/debian/source/format rename to gorgone/packaging/debian/source/format diff --git a/gorgone/ci/debian/substvars b/gorgone/packaging/debian/substvars similarity index 100% rename from gorgone/ci/debian/substvars rename to gorgone/packaging/debian/substvars From f936ff912b2ce1c2cc25aa73e33a11e078efc864 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Fri, 25 Nov 2022 13:42:47 +0100 Subject: [PATCH 693/948] chore(ci): fix deb delivery for gorgone --- .github/workflows/gorgone.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 44d065178c0..60bb8edae01 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -80,8 +80,7 @@ jobs: deliver-deb: runs-on: [self-hosted, common] - needs: [get-version, api-integration-test, legacy-e2e-test, e2e-test, performances-test] - if: ${{ always() && (contains(join(needs.*.result, ','), 'success') || contains(join(needs.*.result, ','), 'skipped')) }} + needs: [package] strategy: matrix: distrib: [bullseye] From 6b76e1a9712f11acd8125560b9fc3fa99cbe7771 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Fri, 25 Nov 2022 13:49:53 +0100 Subject: [PATCH 694/948] fix(packaging): fix gorgone packaging --- .github/workflows/gorgone.yml | 2 +- gorgone/packaging/centreon-gorgone.spectemplate | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 60bb8edae01..1c7f83f7c03 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -44,7 +44,7 @@ jobs: uses: ./.github/workflows/package.yml with: - base_directory: centreon + base_directory: centreon-gorgone spec_file: centreon-gorgone/packaging/centreon-gorgone.spectemplate package_extension: ${{ matrix.package_extension }} image_name: ${{ matrix.image }} diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 36225c921d7..8f3abda68c4 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -1,6 +1,6 @@ Name: centreon-gorgone Version: 22.10.0 -Release: @RELEASE@%{?dist} +Release: %{PACKAGE_RELEASE}%{?dist} Summary: Perl daemon task handlers Group: Applications/System License: Apache2 From 7dc88cdbae4741804ce1959bcc6eb24fb4f6ade9 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Fri, 25 Nov 2022 13:57:38 +0100 Subject: [PATCH 695/948] fix(packaging): fix gorgone packaging --- .../packaging/centreon-gorgone.spectemplate | 26 +++++++++---------- .../debian/centreon-gorgone.postinst | 2 +- gorgone/packaging/debian/control | 22 +++------------- 3 files changed, 17 insertions(+), 33 deletions(-) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 8f3abda68c4..b5d93fa62d2 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -65,29 +65,29 @@ mkdir -p %{buildroot}/%{perl_vendorlib}/gorgone %{__install} -d %buildroot%{_localstatedir}/lib/centreon-gorgone %{__install} -d %buildroot%{_localstatedir}/log/centreon-gorgone %{__install} -d %buildroot%{_localstatedir}/cache/centreon-gorgone -%{__cp} config/systemd/gorgoned-service %{buildroot}%{_sysconfdir}/systemd/system/gorgoned.service +%{__cp} centreon-gorgone/config/systemd/gorgoned-service %{buildroot}%{_sysconfdir}/systemd/system/gorgoned.service %{__install} -d %{buildroot}%{_sysconfdir}/sysconfig -%{__cp} config/systemd/gorgoned-sysconfig %{buildroot}%{_sysconfdir}/sysconfig/gorgoned +%{__cp} centreon-gorgone/config/systemd/gorgoned-sysconfig %{buildroot}%{_sysconfdir}/sysconfig/gorgoned %{__install} -d %{buildroot}%{_sysconfdir}/logrotate.d -%{__cp} config/logrotate/gorgoned %{buildroot}%{_sysconfdir}/logrotate.d/gorgoned +%{__cp} centreon-gorgone/config/logrotate/gorgoned %{buildroot}%{_sysconfdir}/logrotate.d/gorgoned -%{__cp} -R gorgone/* %{buildroot}/%{perl_vendorlib}/gorgone/ -%{__cp} gorgoned %{buildroot}%{_bindir}/ -%{__cp} contrib/gorgone_config_init.pl %{buildroot}%{_usr}/local/bin/ -%{__cp} contrib/gorgone_audit.pl %{buildroot}%{_usr}/local/bin/ -%{__cp} contrib/gorgone_install_plugins.pl %{buildroot}%{_usr}/local/bin/ +%{__cp} -R centreon-gorgone/gorgone/* %{buildroot}/%{perl_vendorlib}/gorgone/ +%{__cp} centreon-gorgone/gorgoned %{buildroot}%{_bindir}/ +%{__cp} centreon-gorgone/contrib/gorgone_config_init.pl %{buildroot}%{_usr}/local/bin/ +%{__cp} centreon-gorgone/contrib/gorgone_audit.pl %{buildroot}%{_usr}/local/bin/ +%{__cp} centreon-gorgone/contrib/gorgone_install_plugins.pl %{buildroot}%{_usr}/local/bin/ %{__install} -d %{buildroot}%{_sysconfdir}/centreon-gorgone %{__install} -d %{buildroot}%{_sysconfdir}/centreon-gorgone/config.d/ %{__install} -d %{buildroot}%{_sysconfdir}/centreon-gorgone/config.d/cron.d/ %{__install} -d %buildroot%{_localstatedir}/cache/centreon-gorgone/autodiscovery -%{__cp} packaging/config.yaml %{buildroot}%{_sysconfdir}/centreon-gorgone/ -%{__cp} packaging/centreon.yaml %{buildroot}%{_sysconfdir}/centreon-gorgone/config.d/30-centreon.yaml -%{__cp} packaging/centreon-api.yaml %{buildroot}%{_sysconfdir}/centreon-gorgone/config.d/31-centreon-api.yaml -%{__cp} packaging/centreon-audit.yaml %{buildroot}%{_sysconfdir}/centreon-gorgone/config.d/50-centreon-audit.yaml +%{__cp} centreon-gorgone/packaging/config.yaml %{buildroot}%{_sysconfdir}/centreon-gorgone/ +%{__cp} centreon-gorgone/packaging/centreon.yaml %{buildroot}%{_sysconfdir}/centreon-gorgone/config.d/30-centreon.yaml +%{__cp} centreon-gorgone/packaging/centreon-api.yaml %{buildroot}%{_sysconfdir}/centreon-gorgone/config.d/31-centreon-api.yaml +%{__cp} centreon-gorgone/packaging/centreon-audit.yaml %{buildroot}%{_sysconfdir}/centreon-gorgone/config.d/50-centreon-audit.yaml %{__install} -d %buildroot%{_sysconfdir}/sudoers.d/ -%{__cp} packaging/sudoers.d/centreon-gorgone %buildroot%{_sysconfdir}/sudoers.d/centreon-gorgone +%{__cp} centreon-gorgone/packaging/sudoers.d/centreon-gorgone %buildroot%{_sysconfdir}/sudoers.d/centreon-gorgone %{_fixperms} $RPM_BUILD_ROOT/* diff --git a/gorgone/packaging/debian/centreon-gorgone.postinst b/gorgone/packaging/debian/centreon-gorgone.postinst index 06dd74bbc00..a61a8ff3960 100755 --- a/gorgone/packaging/debian/centreon-gorgone.postinst +++ b/gorgone/packaging/debian/centreon-gorgone.postinst @@ -40,7 +40,7 @@ if [ "$1" = "configure" ] ; then chmod 0755 \ /usr/local/bin/gorgone_config_init.pl \ /usr/local/bin/gorgone_audit.pl - + chmod 0750 \ /usr/local/bin/gorgone_install_plugins.pl diff --git a/gorgone/packaging/debian/control b/gorgone/packaging/debian/control index 47f0700cf2f..e9f1d9815b7 100644 --- a/gorgone/packaging/debian/control +++ b/gorgone/packaging/debian/control @@ -2,25 +2,9 @@ Source: centreon-gorgone Section: net Priority: optional Maintainer: Luiz Costa <me@luizgustavo.pro.br> -Build-Depends: +Build-Depends: debhelper-compat (=12), - lsb-base, - perl:native, - libdigest-md5-file-perl, - libjson-pp-perl, - libjson-xs-perl, - libyaml-libyaml-perl, - libdbi-perl, - libdbd-sqlite3-perl, - libdbd-mysql-perl, - libhttp-daemon-perl, - libhttp-daemon-ssl-perl, - libnetaddr-ip-perl, - libschedule-cron-perl, - libhash-merge-perl, - libcryptx-perl, - libzmq-constants-perl, - zmq-libzmq4-perl + lsb-base Standards-Version: 4.5.0 Homepage: https://wwww.centreon.com @@ -54,6 +38,6 @@ Depends: libzmq5, zmq-libzmq4-perl, sudo, - ${shlibs:Depends}, + ${shlibs:Depends}, ${misc:Depends} Description: Centreon Gorgone. From 3b23cf5cd8ec1ba4a21080aac15543571f58abe7 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Fri, 25 Nov 2022 15:12:28 +0100 Subject: [PATCH 696/948] remove legacy scripts --- gorgone/Jenkinsfile | 140 ------------------ gorgone/ci/Jenkinsfile | 17 --- gorgone/ci/debian/centreon-gorgone.dirs | 6 + gorgone/ci/debian/centreon-gorgone.install | 12 ++ gorgone/ci/debian/centreon-gorgone.logrotate | 10 ++ gorgone/ci/debian/centreon-gorgone.postinst | 61 ++++++++ gorgone/ci/debian/control | 43 ++++++ gorgone/ci/debian/copyright | 29 ++++ gorgone/ci/debian/extra/gorgoned | 6 + gorgone/ci/debian/extra/gorgoned.service | 33 +++++ gorgone/ci/debian/rules | 11 ++ gorgone/ci/debian/source/format | 1 + gorgone/ci/debian/substvars | 1 + .../Dockerfile.gorgone-debian11-dependencies | 28 ---- gorgone/ci/scripts/gorgone-deb-package.sh | 70 --------- 15 files changed, 213 insertions(+), 255 deletions(-) delete mode 100644 gorgone/Jenkinsfile delete mode 100644 gorgone/ci/Jenkinsfile create mode 100644 gorgone/ci/debian/centreon-gorgone.dirs create mode 100644 gorgone/ci/debian/centreon-gorgone.install create mode 100644 gorgone/ci/debian/centreon-gorgone.logrotate create mode 100755 gorgone/ci/debian/centreon-gorgone.postinst create mode 100644 gorgone/ci/debian/control create mode 100644 gorgone/ci/debian/copyright create mode 100644 gorgone/ci/debian/extra/gorgoned create mode 100644 gorgone/ci/debian/extra/gorgoned.service create mode 100755 gorgone/ci/debian/rules create mode 100644 gorgone/ci/debian/source/format create mode 100644 gorgone/ci/debian/substvars delete mode 100644 gorgone/ci/docker/Dockerfile.gorgone-debian11-dependencies delete mode 100755 gorgone/ci/scripts/gorgone-deb-package.sh diff --git a/gorgone/Jenkinsfile b/gorgone/Jenkinsfile deleted file mode 100644 index 5dda4569bf1..00000000000 --- a/gorgone/Jenkinsfile +++ /dev/null @@ -1,140 +0,0 @@ -/* -** Variables. -*/ -def serie = '22.10' -def maintenanceBranch = "master" -def qaBranch = "develop" -env.REF_BRANCH = 'master' -env.PROJECT='centreon-gorgone' -if (env.BRANCH_NAME.startsWith('release-')) { - env.BUILD = 'RELEASE' - env.REPO = 'testing' -} else if (env.BRANCH_NAME == maintenanceBranch) { - env.BUILD = 'REFERENCE' -} else if (env.BRANCH_NAME == qaBranch) { - env.BUILD = 'QA' - env.REPO = 'unstable' -} else { - env.BUILD = 'CI' -} - -def buildBranch = env.BRANCH_NAME -if (env.CHANGE_BRANCH) { - buildBranch = env.CHANGE_BRANCH -} - -/* -** Functions -*/ -def isStableBuild() { - return ((env.BUILD == 'REFERENCE') || (env.BUILD == 'QA')) -} - -def checkoutCentreonBuild(buildBranch) { - def getCentreonBuildGitConfiguration = { branchName -> [ - $class: 'GitSCM', - branches: [[name: "refs/heads/${branchName}"]], - doGenerateSubmoduleConfigurations: false, - userRemoteConfigs: [[ - $class: 'UserRemoteConfig', - url: "ssh://git@github.com/centreon/centreon-build.git" - ]] - ]} - - dir('centreon-build') { - try { - checkout(getCentreonBuildGitConfiguration(buildBranch)) - } catch(e) { - echo "branch '${buildBranch}' does not exist in centreon-build, then fallback to master" - checkout(getCentreonBuildGitConfiguration('master')) - } - } -} -/* -** Pipeline code. -*/ -stage('Deliver sources // Sonar analysis') { - node { - checkoutCentreonBuild(buildBranch) - dir('centreon-gorgone') { - checkout scm - } - sh "./centreon-build/jobs/gorgone/${serie}/gorgone-source.sh" - source = readProperties file: 'source.properties' - env.VERSION = "${source.VERSION}" - env.RELEASE = "${source.RELEASE}" - withSonarQubeEnv('SonarQubeDev') { - sh "./centreon-build/jobs/gorgone/${serie}/gorgone-analysis.sh" - } - timeout(time: 10, unit: 'MINUTES') { - def qualityGate = waitForQualityGate() - if (qualityGate.status != 'OK') { - currentBuild.result = 'FAIL' - } - } - } -} - -try { - stage('DEB/RPM Packaging') { - parallel 'Packaging centos7': { - node { - checkoutCentreonBuild(buildBranch) - sh "./centreon-build/jobs/gorgone/${serie}/gorgone-package.sh centos7" - archiveArtifacts artifacts: 'rpms-centos7.tar.gz' - stash name: "rpms-centos7", includes: 'output/noarch/*.rpm' - sh 'rm -rf output' - } - }, - - 'Packaging alma8': { - node { - checkoutCentreonBuild(buildBranch) - sh "./centreon-build/jobs/gorgone/${serie}/gorgone-package.sh alma8" - archiveArtifacts artifacts: 'rpms-alma8.tar.gz' - stash name: "rpms-alma8", includes: 'output/noarch/*.rpm' - sh 'rm -rf output' - } - }, - - 'Debian bullseye packaging and signing': { - node { - dir('centreon-gorgone') { - checkout scm - } - sh 'docker run -i --entrypoint "/src/centreon-gorgone/ci/scripts/gorgone-deb-package.sh" -v "$PWD:/src" -e "DISTRIB=bullseye" -e "VERSION=$VERSION" -e "RELEASE=$RELEASE" registry.centreon.com/centreon-gorgone-debian11-dependencies:22.10' - stash name: 'Debian11', includes: '*.deb' - archiveArtifacts artifacts: "*.deb" - } - } - - if ((currentBuild.result ?: 'SUCCESS') != 'SUCCESS') { - error('Package stage failure.'); - } - } - - if ((env.BUILD == 'RELEASE') || (env.BUILD == 'QA') || (env.BUILD == 'CI')) { - stage('Delivery') { - node { - checkoutCentreonBuild(buildBranch) - unstash 'rpms-alma8' - unstash 'rpms-centos7' - sh "./centreon-build/jobs/gorgone/${serie}/gorgone-delivery.sh" - withCredentials([usernamePassword(credentialsId: 'nexus-credentials', passwordVariable: 'NEXUS_PASSWORD', usernameVariable: 'NEXUS_USERNAME')]) { - checkout scm - unstash "Debian11" - sh '''for i in $(echo *.deb) - do - curl -u $NEXUS_USERNAME:$NEXUS_PASSWORD -H "Content-Type: multipart/form-data" --data-binary "@./$i" https://apt.centreon.com/repository/22.10-$REPO/ - done - ''' - } - } - if ((currentBuild.result ?: 'SUCCESS') != 'SUCCESS') { - error('Delivery stage failure.'); - } - } - } -} catch(e) { - currentBuild.result = 'FAILURE' -} diff --git a/gorgone/ci/Jenkinsfile b/gorgone/ci/Jenkinsfile deleted file mode 100644 index 37bdb060851..00000000000 --- a/gorgone/ci/Jenkinsfile +++ /dev/null @@ -1,17 +0,0 @@ -/* -** Pipeline code. -*/ - -stage('Dependencies containers creation') { - parallel 'debian 11 dependencies': { - node { - dir('centreon-gorgone-debian11') { - checkout scm - dir ('ci/docker') { - sh 'docker build --no-cache . -f Dockerfile.gorgone-debian11-dependencies -t registry.centreon.com/centreon-gorgone-debian11-dependencies:22.10' - /*sh 'docker push registry.centreon.com/centreon-gorgone-debian10-dependencies:22.04'*/ - } - } - } - } -} diff --git a/gorgone/ci/debian/centreon-gorgone.dirs b/gorgone/ci/debian/centreon-gorgone.dirs new file mode 100644 index 00000000000..326d08ce2ea --- /dev/null +++ b/gorgone/ci/debian/centreon-gorgone.dirs @@ -0,0 +1,6 @@ +etc/centreon-gorgone/config.d +etc/centreon-gorgone/config.d/cron.d +var/cache/centreon-gorgone +var/cache/centreon-gorgone/autodiscovery +var/lib/centreon-gorgone +var/log/centreon-gorgone diff --git a/gorgone/ci/debian/centreon-gorgone.install b/gorgone/ci/debian/centreon-gorgone.install new file mode 100644 index 00000000000..664fd3010ed --- /dev/null +++ b/gorgone/ci/debian/centreon-gorgone.install @@ -0,0 +1,12 @@ +gorgoned usr/bin +contrib/* usr/local/bin +packaging/config.yaml etc/centreon-gorgone +packaging/centreon.yaml etc/centreon-gorgone/config.d +packaging/centreon-api.yaml etc/centreon-gorgone/config.d +packaging/centreon-audit.yaml etc/centreon-gorgone/config.d +packaging/sudoers.d/centreon-gorgone etc/sudoers.d +debian/extra/gorgoned.service lib/systemd/system +debian/extra/gorgoned etc/default +gorgone/class/* usr/share/perl5/gorgone/class +gorgone/modules/* usr/share/perl5/gorgone/modules +gorgone/standard/* usr/share/perl5/gorgone/standard diff --git a/gorgone/ci/debian/centreon-gorgone.logrotate b/gorgone/ci/debian/centreon-gorgone.logrotate new file mode 100644 index 00000000000..e6f56b7475f --- /dev/null +++ b/gorgone/ci/debian/centreon-gorgone.logrotate @@ -0,0 +1,10 @@ +/var/log/centreon-gorgone/gorgoned.log { + copytruncate + weekly + rotate 52 + compress + delaycompress + notifempty + missingok + su root root +} diff --git a/gorgone/ci/debian/centreon-gorgone.postinst b/gorgone/ci/debian/centreon-gorgone.postinst new file mode 100755 index 00000000000..a61a8ff3960 --- /dev/null +++ b/gorgone/ci/debian/centreon-gorgone.postinst @@ -0,0 +1,61 @@ +#!/bin/sh + +if [ "$1" = "configure" ] ; then + + if [ ! "$(getent passwd centreon-gorgone)" ]; then + adduser --system --group --home /var/lib/centreon-gorgone --no-create-home centreon-gorgone + fi + + if [ "$(getent passwd centreon)" ]; then + usermod -a -G centreon-gorgone centreon + usermod -a -G centreon centreon-gorgone + fi + + if [ "$(getent passwd centreon-engine)" ]; then + usermod -a -G centreon-gorgone centreon-engine + fi + + if [ "$(getent passwd centreon-broker)" ]; then + usermod -a -G centreon-gorgone centreon-broker + fi + + chown -vR centreon-gorgone:centreon-gorgone \ + /etc/centreon-gorgone \ + /var/cache/centreon-gorgone \ + /var/cache/centreon-gorgone/autodiscovery \ + /var/lib/centreon-gorgone \ + /var/log/centreon-gorgone + chmod -vR g+w \ + /etc/centreon-gorgone \ + /var/cache/centreon-gorgone \ + /var/cache/centreon-gorgone/autodiscovery \ + /var/lib/centreon-gorgone \ + /var/log/centreon-gorgone + + chown root:root \ + /usr/local/bin/gorgone_config_init.pl \ + /usr/local/bin/gorgone_audit.pl \ + /usr/local/bin/gorgone_install_plugins.pl + + chmod 0755 \ + /usr/local/bin/gorgone_config_init.pl \ + /usr/local/bin/gorgone_audit.pl + + chmod 0750 \ + /usr/local/bin/gorgone_install_plugins.pl + + if [ ! -d /var/lib/centreon-gorgone/.ssh -a -d /var/spool/centreon/.ssh ] ; then + /usr/bin/cp -r /var/spool/centreon/.ssh /var/lib/centreon-gorgone/.ssh + /usr/bin/chown -R centreon-gorgone:centreon-gorgone /var/lib/centreon-gorgone/.ssh + /usr/bin/chmod 600 /var/lib/centreon-gorgone/.ssh/id_rsa + fi + + # rename files to priority + mv /etc/centreon-gorgone/config.d/centreon.yaml /etc/centreon-gorgone/config.d/30-centreon.yaml + mv /etc/centreon-gorgone/config.d/centreon-api.yaml /etc/centreon-gorgone/config.d/31-centreon-api.yaml + mv /etc/centreon-gorgone/config.d/centreon-audit.yaml /etc/centreon-gorgone/config.d/50-centreon-audit.yaml + + systemctl preset gorgoned.service || : >/dev/null 2>&1 || : + +fi +exit 0 diff --git a/gorgone/ci/debian/control b/gorgone/ci/debian/control new file mode 100644 index 00000000000..e9f1d9815b7 --- /dev/null +++ b/gorgone/ci/debian/control @@ -0,0 +1,43 @@ +Source: centreon-gorgone +Section: net +Priority: optional +Maintainer: Luiz Costa <me@luizgustavo.pro.br> +Build-Depends: + debhelper-compat (=12), + lsb-base +Standards-Version: 4.5.0 +Homepage: https://wwww.centreon.com + +Package: centreon-gorgone +Architecture: any +Depends: + centreon-common (>= ${centreon:version}~), + libdatetime-perl, + libtry-tiny-perl, + libxml-simple-perl, + libxml-libxml-simple-perl, + libdigest-md5-file-perl, + libjson-pp-perl, + libjson-xs-perl, + libyaml-libyaml-perl, + libdbi-perl, + libdbd-sqlite3-perl, + libdbd-mysql-perl, + libhttp-daemon-perl, + libhttp-daemon-ssl-perl, + libnetaddr-ip-perl, + libschedule-cron-perl, + libhash-merge-perl, + libcryptx-perl, + libmojolicious-perl, + libauthen-simple-perl, + libauthen-simple-net-perl, + libnet-curl-perl, + libssh-session-perl, + libzmq-constants-perl, + libzmq5, + zmq-libzmq4-perl, + sudo, + ${shlibs:Depends}, + ${misc:Depends} +Description: Centreon Gorgone. diff --git a/gorgone/ci/debian/copyright b/gorgone/ci/debian/copyright new file mode 100644 index 00000000000..9331ebebd3d --- /dev/null +++ b/gorgone/ci/debian/copyright @@ -0,0 +1,29 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: centreon-gorgone +Upstream-Contact: Luiz Costa <me@luizgustavo.pro.br> +Source: https://www.centreon.com + +Files: * +Copyright: 2022 Centreon +License: Apache-2.0 + +Files: debian/* +Copyright: 2022 Centreon +License: Apache-2.0 + +License: Apache-2.0 + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + . + https://www.apache.org/licenses/LICENSE-2.0 + . + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + . + On Debian systems, the complete text of the Apache version 2.0 license + can be found in "/usr/share/common-licenses/Apache-2.0". + diff --git a/gorgone/ci/debian/extra/gorgoned b/gorgone/ci/debian/extra/gorgoned new file mode 100644 index 00000000000..d6fb7a6ebe2 --- /dev/null +++ b/gorgone/ci/debian/extra/gorgoned @@ -0,0 +1,6 @@ +# Configuration file for Centreon Gorgone. + +# OPTIONS for the daemon launch +CONFIG="/etc/centreon-gorgone/config.yaml" +LOGFILE="/var/log/centreon-gorgone/gorgoned.log" +SEVERITY="info" diff --git a/gorgone/ci/debian/extra/gorgoned.service b/gorgone/ci/debian/extra/gorgoned.service new file mode 100644 index 00000000000..a226b0256c4 --- /dev/null +++ b/gorgone/ci/debian/extra/gorgoned.service @@ -0,0 +1,33 @@ +## +## Copyright 2019-2021 Centreon +## +## Licensed under the Apache License, Version 2.0 (the "License"); +## you may not use this file except in compliance with the License. +## You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. +## +## For more information : contact@centreon.com +## + +[Unit] +Description=Centreon Gorgone +PartOf=centreon.service +After=centreon.service +ReloadPropagatedFrom=centreon.service + +[Service] +EnvironmentFile=/etc/default/gorgoned +ExecStart=/usr/bin/perl /usr/bin/gorgoned --config=${CONFIG} --logfile=${LOGFILE} --severity=${SEVERITY} +Type=simple +User=centreon-gorgone + +[Install] +WantedBy=multi-user.target +WantedBy=centreon.service diff --git a/gorgone/ci/debian/rules b/gorgone/ci/debian/rules new file mode 100755 index 00000000000..95af63e770c --- /dev/null +++ b/gorgone/ci/debian/rules @@ -0,0 +1,11 @@ +#!/usr/bin/make -f + +export DEB_BUILD_MAINT_OPTIONS = hardening=+all + +%: + dh $@ + +override_dh_gencontrol: + dh_gencontrol -- -Tdebian/substvars + +override_dh_usrlocal: diff --git a/gorgone/ci/debian/source/format b/gorgone/ci/debian/source/format new file mode 100644 index 00000000000..163aaf8d82b --- /dev/null +++ b/gorgone/ci/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/gorgone/ci/debian/substvars b/gorgone/ci/debian/substvars new file mode 100644 index 00000000000..929c65b6607 --- /dev/null +++ b/gorgone/ci/debian/substvars @@ -0,0 +1 @@ +centreon:version= diff --git a/gorgone/ci/docker/Dockerfile.gorgone-debian11-dependencies b/gorgone/ci/docker/Dockerfile.gorgone-debian11-dependencies deleted file mode 100644 index 31f4c618878..00000000000 --- a/gorgone/ci/docker/Dockerfile.gorgone-debian11-dependencies +++ /dev/null @@ -1,28 +0,0 @@ -FROM debian:bullseye - -# fix locale -RUN apt-get update && apt-get install -y locales && rm -rf /var/lib/apt/lists/* \ -&& localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 -ENV LANG en_US.utf8 - -RUN apt-get update && apt-get install -y \ -dh-make \ -dh-make-perl \ -libtest-simple-perl \ -libmodule-install-perl \ -libnet-ldap-perl \ -libauthen-simple-passwd-perl \ -libmojolicious-perl \ -aptitude \ -lintian \ -pbuilder \ -quilt \ -git-buildpackage \ -debmake \ -devscripts \ -fakeroot \ -curl \ -python3 \ -python3-pip \ -&& pip3 install conan \ -&& ln -s /usr/local/bin/conan /usr/bin/conan diff --git a/gorgone/ci/scripts/gorgone-deb-package.sh b/gorgone/ci/scripts/gorgone-deb-package.sh deleted file mode 100755 index aa510a66d61..00000000000 --- a/gorgone/ci/scripts/gorgone-deb-package.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/sh -set -ex - -if [ -z "$VERSION" -o -z "$RELEASE" -o -z "$DISTRIB" ] ; then - echo "You need to specify VERSION / RELEASE / DISTRIB variables" - exit 1 -fi - -echo "################################################## PACKAGING COLLECT ##################################################" - -AUTHOR="Luiz Costa" -AUTHOR_EMAIL="me@luizgustavo.pro.br" - -apt-get update - -pwd - -if [ -d /build ]; then - rm -rf /build -fi -mkdir -p /build - -mkdir -p /build/tmp -cd /build/tmp -apt-cache dumpavail | dpkg --merge-avail - -yes | dh-make-perl make --build --version "0.11.3-${DISTRIB}" --cpan Mojolicious::Plugin::BasicAuthPlus -dpkg -i libmojolicious-plugin-basicauthplus-perl_0.11.3-${DISTRIB}_all.deb - -yes | dh-make-perl make --build --revision ${DISTRIB} --cpan ZMQ::Constants -dpkg -i libzmq-constants-perl_1.04-${DISTRIB}_all.deb - -git clone https://github.com/centreon-lab/zmq-libzmq4-perl.git zmq-libzmq4-perl-0.02 -mkdir zmq-libzmq4-perl -mv -v zmq-libzmq4-perl-0.02 zmq-libzmq4-perl/ -cd zmq-libzmq4-perl/ -tar czpvf zmq-libzmq4-perl-0.02.tar.gz zmq-libzmq4-perl-0.02 -cd zmq-libzmq4-perl-0.02 -rm -rf debian/changelog -debmake -f "${AUTHOR}" -e "${AUTHOR_EMAIL}" -b ":perl" -r ${DISTRIB} -y -debuild-pbuilder -uc -us -cd .. -dpkg -i zmq-libzmq4-perl_0.02-${DISTRIB}_all.deb -cd /build - -# fix version to debian format accept -VERSION="$(echo $VERSION | sed 's/-/./g')" - -mkdir -p /build/centreon-gorgone -(cd /src && tar czvpf - centreon-gorgone) | dd of=centreon-gorgone-$VERSION.tar.gz -cp -rv /src/centreon-gorgone /build/ -cp -rv /src/centreon-gorgone/ci/debian /build/centreon-gorgone/ -sed -i "s/^centreon:version=.*$/centreon:version=$(echo $VERSION | egrep -o '^[0-9][0-9].[0-9][0-9]')/" /build/centreon-gorgone/debian/substvars - -pwd -ls -lart -cd centreon-gorgone -debmake -f "${AUTHOR}" -e "${AUTHOR_EMAIL}" -u "$VERSION" -b ":perl" -y -r "${DISTRIB}" -debuild-pbuilder -cd /build - -if [ -d "$DISTRIB" ] ; then - rm -rf "$DISTRIB" -fi -mkdir $DISTRIB -mv /build/tmp/libmojolicious-plugin-basicauthplus-perl_0.11.3-${DISTRIB}_all.deb $DISTRIB/ -mv /build/tmp/zmq-libzmq4-perl/zmq-libzmq4-perl_0.02-${DISTRIB}_all.deb $DISTRIB/ -mv /build/tmp/libzmq-constants-perl_1.04-${DISTRIB}_all.deb $DISTRIB/ -mv /build/*.deb $DISTRIB/ -mv /build/$DISTRIB/*.deb /src From 58c6e64ad099cb7d61f77ca028360dea495b6cdb Mon Sep 17 00:00:00 2001 From: tuntoja <58987095+tuntoja@users.noreply.github.com> Date: Mon, 28 Nov 2022 11:44:31 +0100 Subject: [PATCH 697/948] Update major version to 23.04 (#308) Co-authored-by: Kevin Duret <kduret@centreon.com> --- .github/workflows/gorgone.yml | 6 +++--- gorgone/packaging/centreon-gorgone.spectemplate | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 1c7f83f7c03..7b7ded3e40e 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -22,7 +22,7 @@ jobs: get-version: uses: ./.github/workflows/get-version.yml with: - version_file: centreon/www/install/insertBaseConf.sql + version_file: centreon-gorgone/packaging/centreon-gorgone.spectemplate package: needs: [get-version] @@ -59,7 +59,7 @@ jobs: deliver-rpm: runs-on: [self-hosted, common] - needs: [package] + needs: [get-version, package] strategy: matrix: distrib: [el7, el8] @@ -80,7 +80,7 @@ jobs: deliver-deb: runs-on: [self-hosted, common] - needs: [package] + needs: [get-version,package] strategy: matrix: distrib: [bullseye] diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index b5d93fa62d2..1f5f9c229a9 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -1,5 +1,5 @@ Name: centreon-gorgone -Version: 22.10.0 +Version: 23.04.0 Release: %{PACKAGE_RELEASE}%{?dist} Summary: Perl daemon task handlers Group: Applications/System From a64977c118ca58dd8bad267cc53a0475b44b318e Mon Sep 17 00:00:00 2001 From: tuntoja <58987095+tuntoja@users.noreply.github.com> Date: Thu, 1 Dec 2022 09:19:00 +0100 Subject: [PATCH 698/948] Use legacy yum repositories (#335) Co-authored-by: Kevin Duret <kduret@centreon.com> --- .github/workflows/gorgone.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 7b7ded3e40e..aac9d1c43b9 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -72,11 +72,16 @@ jobs: uses: ./.github/actions/rpm-delivery with: module_name: centreon-gorgone + repository_name: standard distrib: ${{ matrix.distrib }} version: ${{ needs.get-version.outputs.major_version }} nexus_username: ${{ secrets.REPOS_USERNAME }} nexus_password: ${{ secrets.REPOS_PASSWORD }} cache_key: ${{ github.sha }}-${{ github.run_id }}-rpm-${{ matrix.distrib }} + update_repo_path: ${{ secrets.UPDATE_REPO_PATH }} + cloudfront_id: ${{ secrets.CLOUDFRONT_ID }} + yum_repo_address: ${{ secrets.YUM_REPO_ADDRESS }} + yum_repo_key: ${{ secrets.YUM_REPO_KEY }} deliver-deb: runs-on: [self-hosted, common] From 06f552d0c27f7d80e7d31b07cf5d0bd27456cbf3 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Wed, 7 Dec 2022 10:20:42 +0100 Subject: [PATCH 699/948] enh(ci): cancel previous PR workflow runs (#409) --- .github/workflows/gorgone.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index aac9d1c43b9..a69ca2cb4d4 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -1,5 +1,9 @@ name: gorgone +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + on: workflow_dispatch: pull_request: From e93dddd7ab4180fc3b569ec92734d027015840dc Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Thu, 8 Dec 2022 12:49:19 +0100 Subject: [PATCH 700/948] enh(ci): do not run delivery job when not needed (#427) --- .github/workflows/gorgone.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index a69ca2cb4d4..6dd5aae9d45 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -64,6 +64,7 @@ jobs: deliver-rpm: runs-on: [self-hosted, common] needs: [get-version, package] + if: ${{ contains(fromJson('["stable", "testing", "unstable"]'), needs.get-version.outputs.stability) }} strategy: matrix: distrib: [el7, el8] @@ -86,10 +87,12 @@ jobs: cloudfront_id: ${{ secrets.CLOUDFRONT_ID }} yum_repo_address: ${{ secrets.YUM_REPO_ADDRESS }} yum_repo_key: ${{ secrets.YUM_REPO_KEY }} + stability: ${{ needs.get-version.outputs.stability }} deliver-deb: runs-on: [self-hosted, common] needs: [get-version,package] + if: ${{ contains(fromJson('["stable", "testing", "unstable"]'), needs.get-version.outputs.stability) }} strategy: matrix: distrib: [bullseye] @@ -107,3 +110,4 @@ jobs: nexus_username: ${{ secrets.REPOS_USERNAME }} nexus_password: ${{ secrets.REPOS_PASSWORD }} cache_key: ${{ github.sha }}-${{ github.run_id }}-deb-${{ matrix.distrib }} + stability: ${{ needs.get-version.outputs.stability }} From f0c1652a4ab06eb64c6671701dfbcdcc4bc4f251 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Thu, 8 Dec 2022 18:11:17 +0100 Subject: [PATCH 701/948] fix(packaging): fix gorgone perl libraries path (#449) --- gorgone/packaging/centreon-gorgone.spectemplate | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 1f5f9c229a9..11b1c674fa2 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -9,6 +9,8 @@ Source0: %{name}-%{version}.tar.gz BuildArch: noarch BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) +BuildRequires: perl-devel + Requires: bzip2 Requires: perl-Libssh-Session >= 0.8 Requires: perl-CryptX From 0758821c7ed3b5781614338fb738c9980fd9436e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Chapron?= <34628915+sc979@users.noreply.github.com> Date: Fri, 9 Dec 2022 10:52:49 +0100 Subject: [PATCH 702/948] chore(clean): remove gorgone folder's deprecated files (#422) --- gorgone/.github/workflows/dependabot_jira.yml | 79 ------------------- gorgone/sonar-project.properties | 8 -- 2 files changed, 87 deletions(-) delete mode 100644 gorgone/.github/workflows/dependabot_jira.yml delete mode 100644 gorgone/sonar-project.properties diff --git a/gorgone/.github/workflows/dependabot_jira.yml b/gorgone/.github/workflows/dependabot_jira.yml deleted file mode 100644 index acaec2996f4..00000000000 --- a/gorgone/.github/workflows/dependabot_jira.yml +++ /dev/null @@ -1,79 +0,0 @@ -name: Create Dependabot Ticket on Jira - -on: - pull_request: - types: [ opened, reopened ] - branches: [ develop ] - -permissions: - pull-requests: read - -env: - JIRA_PROJECT_KEY: "MON" - JIRA_ISSUE_TYPE: "Technical" - COMPONENT_NAME: "${{ github.event.pull_request.base.repo.name }}" - -jobs: - create_ticket: - name: Create Jira ticket on dependaBot PR - if: github.event.pull_request.user.id == 49699333 - runs-on: ubuntu-latest - steps: - - name: Get current date - id: date - run: echo "CURRENT_DATE=$(date +'%Y-%m-%dT%H:%M:%S')" >> $GITHUB_ENV - - - name: Check components name - id: name - if: ${{ env.COMPONENT_NAME == 'centreon'}} - run: echo "COMPONENT_NAME=centreon-web" >> $GITHUB_ENV - - - name: debug - id: debug - run: echo "component is ${{ env.COMPONENT_NAME }}." - - - name: Login to Jira - uses: atlassian/gajira-login@v2.0.0 - id: login - env: - JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} - JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} - JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} - - - name: Create Jira Issue - uses: atlassian/gajira-create@v2.0.1 - id: create - env: - JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} - JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} - JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} - with: - project: ${{ env.JIRA_PROJECT_KEY }} - issuetype: ${{ env.JIRA_ISSUE_TYPE }} - summary: | - [${{ github.event.repository.name }}] - ${{ github.event.pull_request.title }} - description: | - - {panel:title=Recommandation} - ${{ github.event.pull_request.title }} - {panel} - - More details are available in the *PR n°${{ github.event.pull_request.number }}* - - Github link is: ${{ github.event.pull_request.html_url }} - - {panel:title=CVSS details} - More details are available on snyk - {panel} - - *Github Advisory* - - fields: - '{ - "customfield_10880": "Internal", - "customfield_10881": "Dependabot", - "customfield_10866": "${{ env.CURRENT_DATE }}", - "labels": ["Dependabot"], - "priority": {"name": "Highest"}, - "components":[{"name": "${{ env.COMPONENT_NAME }}"}] - }' diff --git a/gorgone/sonar-project.properties b/gorgone/sonar-project.properties deleted file mode 100644 index 99364113dec..00000000000 --- a/gorgone/sonar-project.properties +++ /dev/null @@ -1,8 +0,0 @@ -# project -sonar.projectKey={PROJECT_TITLE} -sonar.projectName={PROJECT_NAME} -sonar.projectVersion={PROJECT_VERSION} -sonar.sources=. - -sonar.tsql.file.suffixes=sql,tsql -sonar.plsql.file.suffixes=pks,pkb From 06b5d807aa3c0b75137ba55b39d11d58a84c9320 Mon Sep 17 00:00:00 2001 From: tuntoja <58987095+tuntoja@users.noreply.github.com> Date: Fri, 16 Dec 2022 10:18:56 +0100 Subject: [PATCH 703/948] enh(ci): migrate delivery to nexus for debian packages (#506) Co-authored-by: Kevin Duret <kduret@centreon.com> --- .github/workflows/gorgone.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 6dd5aae9d45..5cdf005b883 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -107,7 +107,7 @@ jobs: module_name: centreon-gorgone distrib: ${{ matrix.distrib }} version: ${{ needs.get-version.outputs.major_version }} - nexus_username: ${{ secrets.REPOS_USERNAME }} - nexus_password: ${{ secrets.REPOS_PASSWORD }} + nexus_username: ${{ secrets.NEXUS_USER }} + nexus_password: ${{ secrets.NEXUS_PASSWD }} cache_key: ${{ github.sha }}-${{ github.run_id }}-deb-${{ matrix.distrib }} stability: ${{ needs.get-version.outputs.stability }} From 875d3dd1d738e80764adc6e9b25a63bc0be84f86 Mon Sep 17 00:00:00 2001 From: tuntoja <58987095+tuntoja@users.noreply.github.com> Date: Mon, 2 Jan 2023 15:35:38 +0100 Subject: [PATCH 704/948] enh(ci): Add rpm delivery cleanup and structure (#577) Co-authored-by: Kevin Duret <kduret@centreon.com> --- .github/workflows/gorgone.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 5cdf005b883..ddd5a879fd3 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -80,6 +80,8 @@ jobs: repository_name: standard distrib: ${{ matrix.distrib }} version: ${{ needs.get-version.outputs.major_version }} + minor_version: ${{ needs.get-version.outputs.minor_version }} + release: ${{ needs.get-version.outputs.release }} nexus_username: ${{ secrets.REPOS_USERNAME }} nexus_password: ${{ secrets.REPOS_PASSWORD }} cache_key: ${{ github.sha }}-${{ github.run_id }}-rpm-${{ matrix.distrib }} From 96a8c7824f31b0b69f7cab7e9295bcce63f7fc5f Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Tue, 10 Jan 2023 10:15:20 +0100 Subject: [PATCH 705/948] fix(ci): allow to deliver on stable branches (#635) --- .github/workflows/gorgone.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index ddd5a879fd3..7b33950a1f3 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -13,6 +13,9 @@ on: push: branches: - develop + - dev-[2-9][0-9].[0-9][0-9].x + - master + - "[2-9][0-9].[0-9][0-9].x" paths: - ".github/workflows/gorgone.yml" - "centreon-gorgone/**" From 1313bd3607f1c0b78b9cbf33b729d52f06335768 Mon Sep 17 00:00:00 2001 From: tuntoja <58987095+tuntoja@users.noreply.github.com> Date: Mon, 16 Jan 2023 11:41:55 +0100 Subject: [PATCH 706/948] enh(ci): remove paths to workflow files in workflows for components (#689) Signed-off-by: Technique CI <technique-ci@centreon.com> Co-authored-by: Nouha-ElAbrouki <97687698+Noha-ElAbrouki@users.noreply.github.com> Co-authored-by: technique-ci <technique-ci@centreon.com> Co-authored-by: Tom Darneix <tomdar87@outlook.com> Co-authored-by: Adrien Morais <amorais@centreon.com> Co-authored-by: technique-ci <87815335+technique-ci@users.noreply.github.com> Co-authored-by: Kevin Duret <kduret@centreon.com> Co-authored-by: Adrien Morais-Mestre <31647811+adr-mo@users.noreply.github.com> Co-authored-by: wtermellil <110023866+wtermellil@users.noreply.github.com> Co-authored-by: abuathier <114949454+abuathier@users.noreply.github.com> --- .github/workflows/gorgone.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 7b33950a1f3..3784ccab1ad 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -8,7 +8,6 @@ on: workflow_dispatch: pull_request: paths: - - ".github/workflows/gorgone.yml" - "centreon-gorgone/**" push: branches: @@ -17,7 +16,6 @@ on: - master - "[2-9][0-9].[0-9][0-9].x" paths: - - ".github/workflows/gorgone.yml" - "centreon-gorgone/**" tags: - centreon-gorgone-* From f1939c49433c6f8c0bba648d8b556b771cccc7bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Chapron?= <34628915+sc979@users.noreply.github.com> Date: Wed, 25 Jan 2023 15:20:09 +0100 Subject: [PATCH 707/948] chore(license): update maintainer and email addresses (#758) --- gorgone/ci/debian/control | 2 +- gorgone/ci/debian/copyright | 2 +- gorgone/packaging/debian/control | 2 +- gorgone/packaging/debian/copyright | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gorgone/ci/debian/control b/gorgone/ci/debian/control index e9f1d9815b7..d051da7dd8f 100644 --- a/gorgone/ci/debian/control +++ b/gorgone/ci/debian/control @@ -1,7 +1,7 @@ Source: centreon-gorgone Section: net Priority: optional -Maintainer: Luiz Costa <me@luizgustavo.pro.br> +Maintainer: Centreon <contact@centreon.com> Build-Depends: debhelper-compat (=12), lsb-base diff --git a/gorgone/ci/debian/copyright b/gorgone/ci/debian/copyright index 9331ebebd3d..9dcff2f907b 100644 --- a/gorgone/ci/debian/copyright +++ b/gorgone/ci/debian/copyright @@ -1,6 +1,6 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: centreon-gorgone -Upstream-Contact: Luiz Costa <me@luizgustavo.pro.br> +Upstream-Contact: Centreon <contact@centreon.com> Source: https://www.centreon.com Files: * diff --git a/gorgone/packaging/debian/control b/gorgone/packaging/debian/control index e9f1d9815b7..d051da7dd8f 100644 --- a/gorgone/packaging/debian/control +++ b/gorgone/packaging/debian/control @@ -1,7 +1,7 @@ Source: centreon-gorgone Section: net Priority: optional -Maintainer: Luiz Costa <me@luizgustavo.pro.br> +Maintainer: Centreon <contact@centreon.com> Build-Depends: debhelper-compat (=12), lsb-base diff --git a/gorgone/packaging/debian/copyright b/gorgone/packaging/debian/copyright index 9331ebebd3d..9dcff2f907b 100644 --- a/gorgone/packaging/debian/copyright +++ b/gorgone/packaging/debian/copyright @@ -1,6 +1,6 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: centreon-gorgone -Upstream-Contact: Luiz Costa <me@luizgustavo.pro.br> +Upstream-Contact: Centreon <contact@centreon.com> Source: https://www.centreon.com Files: * From 00dd2e90713d38466b0a2f3833a149818b67f47b Mon Sep 17 00:00:00 2001 From: tuntoja <58987095+tuntoja@users.noreply.github.com> Date: Wed, 1 Feb 2023 15:42:26 +0100 Subject: [PATCH 708/948] MON-16653 Add extra delivery to artifactory (#765) Co-authored-by: Kevin Duret <kduret@centreon.com> --- .github/workflows/gorgone.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 3784ccab1ad..b24e298b6c9 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -85,6 +85,7 @@ jobs: release: ${{ needs.get-version.outputs.release }} nexus_username: ${{ secrets.REPOS_USERNAME }} nexus_password: ${{ secrets.REPOS_PASSWORD }} + artifactory_token: ${{ secrets.ARTI_APT_TEST_TOKEN }} cache_key: ${{ github.sha }}-${{ github.run_id }}-rpm-${{ matrix.distrib }} update_repo_path: ${{ secrets.UPDATE_REPO_PATH }} cloudfront_id: ${{ secrets.CLOUDFRONT_ID }} @@ -112,5 +113,6 @@ jobs: version: ${{ needs.get-version.outputs.major_version }} nexus_username: ${{ secrets.NEXUS_USER }} nexus_password: ${{ secrets.NEXUS_PASSWD }} + artifactory_token: ${{ secrets.ARTI_APT_TEST_TOKEN }} cache_key: ${{ github.sha }}-${{ github.run_id }}-deb-${{ matrix.distrib }} stability: ${{ needs.get-version.outputs.stability }} From 566f3d2d4faae8cd2336e0964217aba7a6143fe2 Mon Sep 17 00:00:00 2001 From: tuntoja <58987095+tuntoja@users.noreply.github.com> Date: Wed, 1 Feb 2023 18:26:13 +0100 Subject: [PATCH 709/948] chore(ci): Update delivery credentials (#834) --- .github/workflows/gorgone.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index b24e298b6c9..fe1e89d67b2 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -85,7 +85,7 @@ jobs: release: ${{ needs.get-version.outputs.release }} nexus_username: ${{ secrets.REPOS_USERNAME }} nexus_password: ${{ secrets.REPOS_PASSWORD }} - artifactory_token: ${{ secrets.ARTI_APT_TEST_TOKEN }} + artifactory_token: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }} cache_key: ${{ github.sha }}-${{ github.run_id }}-rpm-${{ matrix.distrib }} update_repo_path: ${{ secrets.UPDATE_REPO_PATH }} cloudfront_id: ${{ secrets.CLOUDFRONT_ID }} @@ -111,8 +111,8 @@ jobs: module_name: centreon-gorgone distrib: ${{ matrix.distrib }} version: ${{ needs.get-version.outputs.major_version }} - nexus_username: ${{ secrets.NEXUS_USER }} - nexus_password: ${{ secrets.NEXUS_PASSWD }} - artifactory_token: ${{ secrets.ARTI_APT_TEST_TOKEN }} + nexus_username: ${{ secrets.NEXUS_USERNAME }} + nexus_password: ${{ secrets.NEXUS_PASSWORD }} + artifactory_token: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }} cache_key: ${{ github.sha }}-${{ github.run_id }}-deb-${{ matrix.distrib }} stability: ${{ needs.get-version.outputs.stability }} From a56ba1544c5e7c6e960e1a5ae5b04a5303e52431 Mon Sep 17 00:00:00 2001 From: Paul LOUIS THERESE <53221698+paloth@users.noreply.github.com> Date: Wed, 1 Feb 2023 22:39:30 +0100 Subject: [PATCH 710/948] fix(ci): missing creds for registry (#838) From fc54791171daf2d1d4f08e0d5f00721f1a3fd0f3 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Thu, 2 Feb 2023 12:39:14 +0100 Subject: [PATCH 711/948] chore(ci): package centreon-web to el9 (#797) Refs: MON-16456 --- .github/workflows/gorgone.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index fe1e89d67b2..2ae38cdf9bf 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -35,7 +35,7 @@ jobs: strategy: fail-fast: false matrix: - distrib: [el7, el8, bullseye] + distrib: [el7, el8, el9, bullseye] include: - package_extension: rpm image: packaging-centos7 @@ -43,6 +43,9 @@ jobs: - package_extension: rpm image: packaging-alma8 distrib: el8 + - package_extension: rpm + image: packaging-alma9 + distrib: el9 - package_extension: deb image: packaging-bullseye distrib: bullseye @@ -68,7 +71,7 @@ jobs: if: ${{ contains(fromJson('["stable", "testing", "unstable"]'), needs.get-version.outputs.stability) }} strategy: matrix: - distrib: [el7, el8] + distrib: [el7, el8, el9] steps: - name: Checkout sources From 332300608a73d614309a24178b1a825922525d88 Mon Sep 17 00:00:00 2001 From: tuntoja <58987095+tuntoja@users.noreply.github.com> Date: Fri, 3 Feb 2023 13:42:11 +0100 Subject: [PATCH 712/948] chore(ci): Update inputs for credentials (#845) --- .github/workflows/gorgone.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 2ae38cdf9bf..4bac2bc7958 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -62,8 +62,8 @@ jobs: release: ${{ needs.get-version.outputs.release }} cache_key: ${{ github.sha }}-${{ github.run_id }}-${{ matrix.package_extension }}-${{ matrix.distrib }} secrets: - artifactory_username: ${{ secrets.REPOS_USERNAME }} - artifactory_password: ${{ secrets.REPOS_PASSWORD }} + registry_username: ${{ secrets.AUTOMATION_USERNAME }} + registry_password: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }} deliver-rpm: runs-on: [self-hosted, common] @@ -86,8 +86,8 @@ jobs: version: ${{ needs.get-version.outputs.major_version }} minor_version: ${{ needs.get-version.outputs.minor_version }} release: ${{ needs.get-version.outputs.release }} - nexus_username: ${{ secrets.REPOS_USERNAME }} - nexus_password: ${{ secrets.REPOS_PASSWORD }} + nexus_username: ${{ secrets.NEXUS_USERNAME }} + nexus_password: ${{ secrets.NEXUS_PASSWORD }} artifactory_token: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }} cache_key: ${{ github.sha }}-${{ github.run_id }}-rpm-${{ matrix.distrib }} update_repo_path: ${{ secrets.UPDATE_REPO_PATH }} From 2cb5b6f56bc3cb045ba2db9e05016e38ef84d2f2 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Thu, 9 Feb 2023 08:19:58 +0100 Subject: [PATCH 713/948] chore(package): remove el7 leftovers (#860) Refs: MON-16456 --- .github/workflows/gorgone.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 4bac2bc7958..06cae772722 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -35,11 +35,8 @@ jobs: strategy: fail-fast: false matrix: - distrib: [el7, el8, el9, bullseye] + distrib: [el8, el9, bullseye] include: - - package_extension: rpm - image: packaging-centos7 - distrib: el7 - package_extension: rpm image: packaging-alma8 distrib: el8 @@ -71,7 +68,7 @@ jobs: if: ${{ contains(fromJson('["stable", "testing", "unstable"]'), needs.get-version.outputs.stability) }} strategy: matrix: - distrib: [el7, el8, el9] + distrib: [el8, el9] steps: - name: Checkout sources From dbcfa91d3cea192c938d5281fef644d61716ac99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Chapron?= <34628915+sc979@users.noreply.github.com> Date: Mon, 13 Feb 2023 10:52:27 +0100 Subject: [PATCH 714/948] chore(license): update package licenses (#861) --- gorgone/packaging/centreon-gorgone.spectemplate | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 11b1c674fa2..efd957acc7e 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -3,7 +3,7 @@ Version: 23.04.0 Release: %{PACKAGE_RELEASE}%{?dist} Summary: Perl daemon task handlers Group: Applications/System -License: Apache2 +License: Apache-2.0 URL: http://www.centreon.com Source0: %{name}-%{version}.tar.gz BuildArch: noarch @@ -48,6 +48,7 @@ AutoReqProv: no %package centreon-config Summary: Configure Centreon Gorgone for use with Centreon Web Group: Networking/Other +License: Apache-2.0 Requires: centreon-common Requires: centreon-gorgone From 2541c67d0fd5eb78f59d13874fe2975e8001c4d6 Mon Sep 17 00:00:00 2001 From: tuntoja <58987095+tuntoja@users.noreply.github.com> Date: Thu, 16 Feb 2023 09:40:04 +0100 Subject: [PATCH 715/948] fix(ci): sync delivery (#911) --- .github/workflows/gorgone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 06cae772722..b3b30a984cb 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -77,7 +77,7 @@ jobs: - name: Delivery uses: ./.github/actions/rpm-delivery with: - module_name: centreon-gorgone + module_name: gorgone repository_name: standard distrib: ${{ matrix.distrib }} version: ${{ needs.get-version.outputs.major_version }} From 0b9aa203a1a3598e631701bc3876f8ca546af193 Mon Sep 17 00:00:00 2001 From: Paul LOUIS THERESE <53221698+paloth@users.noreply.github.com> Date: Thu, 23 Feb 2023 18:46:41 +0100 Subject: [PATCH 716/948] refactor(registry): change docker registry (#987) Co-authored-by: tuntoja <58987095+tuntoja@users.noreply.github.com> Co-authored-by: Kevin Duret <kduret@centreon.com> --- .github/workflows/gorgone.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index b3b30a984cb..34ddcc8addd 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -59,8 +59,8 @@ jobs: release: ${{ needs.get-version.outputs.release }} cache_key: ${{ github.sha }}-${{ github.run_id }}-${{ matrix.package_extension }}-${{ matrix.distrib }} secrets: - registry_username: ${{ secrets.AUTOMATION_USERNAME }} - registry_password: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }} + registry_username: ${{ secrets.DOCKER_REGISTRY_ID }} + registry_password: ${{ secrets.DOCKER_REGISTRY_PASSWD }} deliver-rpm: runs-on: [self-hosted, common] From 45630bf90433bb771669b2d9787fe70b5ac6ce8f Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Wed, 8 Mar 2023 13:49:45 +0100 Subject: [PATCH 717/948] fix(packaging): add consistency between deb and el packaging (#1036) Refs: MON-14472 --- gorgone/ci/debian/centreon-gorgone.dirs | 6 -- gorgone/ci/debian/centreon-gorgone.install | 12 ---- gorgone/ci/debian/centreon-gorgone.logrotate | 10 --- gorgone/ci/debian/centreon-gorgone.postinst | 61 ------------------- gorgone/ci/debian/control | 43 ------------- gorgone/ci/debian/copyright | 29 --------- gorgone/ci/debian/extra/gorgoned | 6 -- gorgone/ci/debian/extra/gorgoned.service | 33 ---------- gorgone/ci/debian/rules | 11 ---- gorgone/ci/debian/source/format | 1 - gorgone/ci/debian/substvars | 1 - gorgone/config/systemd/gorgoned-sysconfig | 2 +- .../packaging/debian/centreon-gorgone.install | 7 ++- gorgone/packaging/debian/control | 2 +- gorgone/packaging/debian/extra/gorgoned | 6 -- .../packaging/debian/extra/gorgoned.service | 33 ---------- 16 files changed, 7 insertions(+), 256 deletions(-) delete mode 100644 gorgone/ci/debian/centreon-gorgone.dirs delete mode 100644 gorgone/ci/debian/centreon-gorgone.install delete mode 100644 gorgone/ci/debian/centreon-gorgone.logrotate delete mode 100755 gorgone/ci/debian/centreon-gorgone.postinst delete mode 100644 gorgone/ci/debian/control delete mode 100644 gorgone/ci/debian/copyright delete mode 100644 gorgone/ci/debian/extra/gorgoned delete mode 100644 gorgone/ci/debian/extra/gorgoned.service delete mode 100755 gorgone/ci/debian/rules delete mode 100644 gorgone/ci/debian/source/format delete mode 100644 gorgone/ci/debian/substvars mode change 100644 => 100755 gorgone/packaging/debian/centreon-gorgone.install delete mode 100644 gorgone/packaging/debian/extra/gorgoned delete mode 100644 gorgone/packaging/debian/extra/gorgoned.service diff --git a/gorgone/ci/debian/centreon-gorgone.dirs b/gorgone/ci/debian/centreon-gorgone.dirs deleted file mode 100644 index 326d08ce2ea..00000000000 --- a/gorgone/ci/debian/centreon-gorgone.dirs +++ /dev/null @@ -1,6 +0,0 @@ -etc/centreon-gorgone/config.d -etc/centreon-gorgone/config.d/cron.d -var/cache/centreon-gorgone -var/cache/centreon-gorgone/autodiscovery -var/lib/centreon-gorgone -var/log/centreon-gorgone diff --git a/gorgone/ci/debian/centreon-gorgone.install b/gorgone/ci/debian/centreon-gorgone.install deleted file mode 100644 index 664fd3010ed..00000000000 --- a/gorgone/ci/debian/centreon-gorgone.install +++ /dev/null @@ -1,12 +0,0 @@ -gorgoned usr/bin -contrib/* usr/local/bin -packaging/config.yaml etc/centreon-gorgone -packaging/centreon.yaml etc/centreon-gorgone/config.d -packaging/centreon-api.yaml etc/centreon-gorgone/config.d -packaging/centreon-audit.yaml etc/centreon-gorgone/config.d -packaging/sudoers.d/centreon-gorgone etc/sudoers.d -debian/extra/gorgoned.service lib/systemd/system -debian/extra/gorgoned etc/default -gorgone/class/* usr/share/perl5/gorgone/class -gorgone/modules/* usr/share/perl5/gorgone/modules -gorgone/standard/* usr/share/perl5/gorgone/standard diff --git a/gorgone/ci/debian/centreon-gorgone.logrotate b/gorgone/ci/debian/centreon-gorgone.logrotate deleted file mode 100644 index e6f56b7475f..00000000000 --- a/gorgone/ci/debian/centreon-gorgone.logrotate +++ /dev/null @@ -1,10 +0,0 @@ -/var/log/centreon-gorgone/gorgoned.log { - copytruncate - weekly - rotate 52 - compress - delaycompress - notifempty - missingok - su root root -} diff --git a/gorgone/ci/debian/centreon-gorgone.postinst b/gorgone/ci/debian/centreon-gorgone.postinst deleted file mode 100755 index a61a8ff3960..00000000000 --- a/gorgone/ci/debian/centreon-gorgone.postinst +++ /dev/null @@ -1,61 +0,0 @@ -#!/bin/sh - -if [ "$1" = "configure" ] ; then - - if [ ! "$(getent passwd centreon-gorgone)" ]; then - adduser --system --group --home /var/lib/centreon-gorgone --no-create-home centreon-gorgone - fi - - if [ "$(getent passwd centreon)" ]; then - usermod -a -G centreon-gorgone centreon - usermod -a -G centreon centreon-gorgone - fi - - if [ "$(getent passwd centreon-engine)" ]; then - usermod -a -G centreon-gorgone centreon-engine - fi - - if [ "$(getent passwd centreon-broker)" ]; then - usermod -a -G centreon-gorgone centreon-broker - fi - - chown -vR centreon-gorgone:centreon-gorgone \ - /etc/centreon-gorgone \ - /var/cache/centreon-gorgone \ - /var/cache/centreon-gorgone/autodiscovery \ - /var/lib/centreon-gorgone \ - /var/log/centreon-gorgone - chmod -vR g+w \ - /etc/centreon-gorgone \ - /var/cache/centreon-gorgone \ - /var/cache/centreon-gorgone/autodiscovery \ - /var/lib/centreon-gorgone \ - /var/log/centreon-gorgone - - chown root:root \ - /usr/local/bin/gorgone_config_init.pl \ - /usr/local/bin/gorgone_audit.pl \ - /usr/local/bin/gorgone_install_plugins.pl - - chmod 0755 \ - /usr/local/bin/gorgone_config_init.pl \ - /usr/local/bin/gorgone_audit.pl - - chmod 0750 \ - /usr/local/bin/gorgone_install_plugins.pl - - if [ ! -d /var/lib/centreon-gorgone/.ssh -a -d /var/spool/centreon/.ssh ] ; then - /usr/bin/cp -r /var/spool/centreon/.ssh /var/lib/centreon-gorgone/.ssh - /usr/bin/chown -R centreon-gorgone:centreon-gorgone /var/lib/centreon-gorgone/.ssh - /usr/bin/chmod 600 /var/lib/centreon-gorgone/.ssh/id_rsa - fi - - # rename files to priority - mv /etc/centreon-gorgone/config.d/centreon.yaml /etc/centreon-gorgone/config.d/30-centreon.yaml - mv /etc/centreon-gorgone/config.d/centreon-api.yaml /etc/centreon-gorgone/config.d/31-centreon-api.yaml - mv /etc/centreon-gorgone/config.d/centreon-audit.yaml /etc/centreon-gorgone/config.d/50-centreon-audit.yaml - - systemctl preset gorgoned.service || : >/dev/null 2>&1 || : - -fi -exit 0 diff --git a/gorgone/ci/debian/control b/gorgone/ci/debian/control deleted file mode 100644 index d051da7dd8f..00000000000 --- a/gorgone/ci/debian/control +++ /dev/null @@ -1,43 +0,0 @@ -Source: centreon-gorgone -Section: net -Priority: optional -Maintainer: Centreon <contact@centreon.com> -Build-Depends: - debhelper-compat (=12), - lsb-base -Standards-Version: 4.5.0 -Homepage: https://wwww.centreon.com - -Package: centreon-gorgone -Architecture: any -Depends: - centreon-common (>= ${centreon:version}~), - libdatetime-perl, - libtry-tiny-perl, - libxml-simple-perl, - libxml-libxml-simple-perl, - libdigest-md5-file-perl, - libjson-pp-perl, - libjson-xs-perl, - libyaml-libyaml-perl, - libdbi-perl, - libdbd-sqlite3-perl, - libdbd-mysql-perl, - libhttp-daemon-perl, - libhttp-daemon-ssl-perl, - libnetaddr-ip-perl, - libschedule-cron-perl, - libhash-merge-perl, - libcryptx-perl, - libmojolicious-perl, - libauthen-simple-perl, - libauthen-simple-net-perl, - libnet-curl-perl, - libssh-session-perl, - libzmq-constants-perl, - libzmq5, - zmq-libzmq4-perl, - sudo, - ${shlibs:Depends}, - ${misc:Depends} -Description: Centreon Gorgone. diff --git a/gorgone/ci/debian/copyright b/gorgone/ci/debian/copyright deleted file mode 100644 index 9dcff2f907b..00000000000 --- a/gorgone/ci/debian/copyright +++ /dev/null @@ -1,29 +0,0 @@ -Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: centreon-gorgone -Upstream-Contact: Centreon <contact@centreon.com> -Source: https://www.centreon.com - -Files: * -Copyright: 2022 Centreon -License: Apache-2.0 - -Files: debian/* -Copyright: 2022 Centreon -License: Apache-2.0 - -License: Apache-2.0 - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - . - https://www.apache.org/licenses/LICENSE-2.0 - . - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - . - On Debian systems, the complete text of the Apache version 2.0 license - can be found in "/usr/share/common-licenses/Apache-2.0". - diff --git a/gorgone/ci/debian/extra/gorgoned b/gorgone/ci/debian/extra/gorgoned deleted file mode 100644 index d6fb7a6ebe2..00000000000 --- a/gorgone/ci/debian/extra/gorgoned +++ /dev/null @@ -1,6 +0,0 @@ -# Configuration file for Centreon Gorgone. - -# OPTIONS for the daemon launch -CONFIG="/etc/centreon-gorgone/config.yaml" -LOGFILE="/var/log/centreon-gorgone/gorgoned.log" -SEVERITY="info" diff --git a/gorgone/ci/debian/extra/gorgoned.service b/gorgone/ci/debian/extra/gorgoned.service deleted file mode 100644 index a226b0256c4..00000000000 --- a/gorgone/ci/debian/extra/gorgoned.service +++ /dev/null @@ -1,33 +0,0 @@ -## -## Copyright 2019-2021 Centreon -## -## Licensed under the Apache License, Version 2.0 (the "License"); -## you may not use this file except in compliance with the License. -## You may obtain a copy of the License at -## -## http://www.apache.org/licenses/LICENSE-2.0 -## -## Unless required by applicable law or agreed to in writing, software -## distributed under the License is distributed on an "AS IS" BASIS, -## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -## See the License for the specific language governing permissions and -## limitations under the License. -## -## For more information : contact@centreon.com -## - -[Unit] -Description=Centreon Gorgone -PartOf=centreon.service -After=centreon.service -ReloadPropagatedFrom=centreon.service - -[Service] -EnvironmentFile=/etc/default/gorgoned -ExecStart=/usr/bin/perl /usr/bin/gorgoned --config=${CONFIG} --logfile=${LOGFILE} --severity=${SEVERITY} -Type=simple -User=centreon-gorgone - -[Install] -WantedBy=multi-user.target -WantedBy=centreon.service diff --git a/gorgone/ci/debian/rules b/gorgone/ci/debian/rules deleted file mode 100755 index 95af63e770c..00000000000 --- a/gorgone/ci/debian/rules +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/make -f - -export DEB_BUILD_MAINT_OPTIONS = hardening=+all - -%: - dh $@ - -override_dh_gencontrol: - dh_gencontrol -- -Tdebian/substvars - -override_dh_usrlocal: diff --git a/gorgone/ci/debian/source/format b/gorgone/ci/debian/source/format deleted file mode 100644 index 163aaf8d82b..00000000000 --- a/gorgone/ci/debian/source/format +++ /dev/null @@ -1 +0,0 @@ -3.0 (quilt) diff --git a/gorgone/ci/debian/substvars b/gorgone/ci/debian/substvars deleted file mode 100644 index 929c65b6607..00000000000 --- a/gorgone/ci/debian/substvars +++ /dev/null @@ -1 +0,0 @@ -centreon:version= diff --git a/gorgone/config/systemd/gorgoned-sysconfig b/gorgone/config/systemd/gorgoned-sysconfig index 4e14ad8b36b..3ee7e99a48a 100644 --- a/gorgone/config/systemd/gorgoned-sysconfig +++ b/gorgone/config/systemd/gorgoned-sysconfig @@ -1,4 +1,4 @@ # Configuration file for Centreon Gorgone. # OPTIONS for the daemon launch -OPTIONS="--config=/etc/centreon-gorgone/config.yaml --logfile=/var/log/centreon-gorgone/gorgoned.log --severity=info" +OPTIONS="--config=/etc/centreon-gorgone/config.yaml --logfile=/var/log/centreon-gorgone/gorgoned.log --severity=error" diff --git a/gorgone/packaging/debian/centreon-gorgone.install b/gorgone/packaging/debian/centreon-gorgone.install old mode 100644 new mode 100755 index 664fd3010ed..5ee819ef90e --- a/gorgone/packaging/debian/centreon-gorgone.install +++ b/gorgone/packaging/debian/centreon-gorgone.install @@ -1,3 +1,5 @@ +#!/usr/bin/dh-exec + gorgoned usr/bin contrib/* usr/local/bin packaging/config.yaml etc/centreon-gorgone @@ -5,8 +7,9 @@ packaging/centreon.yaml etc/centreon-gorgone/config.d packaging/centreon-api.yaml etc/centreon-gorgone/config.d packaging/centreon-audit.yaml etc/centreon-gorgone/config.d packaging/sudoers.d/centreon-gorgone etc/sudoers.d -debian/extra/gorgoned.service lib/systemd/system -debian/extra/gorgoned etc/default gorgone/class/* usr/share/perl5/gorgone/class gorgone/modules/* usr/share/perl5/gorgone/modules gorgone/standard/* usr/share/perl5/gorgone/standard + +config/systemd/gorgoned-service => lib/systemd/system/gorgoned.service +config/systemd/gorgoned-sysconfig => etc/default/gorgoned diff --git a/gorgone/packaging/debian/control b/gorgone/packaging/debian/control index d051da7dd8f..62a5e608c55 100644 --- a/gorgone/packaging/debian/control +++ b/gorgone/packaging/debian/control @@ -36,7 +36,7 @@ Depends: libssh-session-perl, libzmq-constants-perl, libzmq5, - zmq-libzmq4-perl, + libzmq-libzmq4-perl, sudo, ${shlibs:Depends}, ${misc:Depends} diff --git a/gorgone/packaging/debian/extra/gorgoned b/gorgone/packaging/debian/extra/gorgoned deleted file mode 100644 index d6fb7a6ebe2..00000000000 --- a/gorgone/packaging/debian/extra/gorgoned +++ /dev/null @@ -1,6 +0,0 @@ -# Configuration file for Centreon Gorgone. - -# OPTIONS for the daemon launch -CONFIG="/etc/centreon-gorgone/config.yaml" -LOGFILE="/var/log/centreon-gorgone/gorgoned.log" -SEVERITY="info" diff --git a/gorgone/packaging/debian/extra/gorgoned.service b/gorgone/packaging/debian/extra/gorgoned.service deleted file mode 100644 index a226b0256c4..00000000000 --- a/gorgone/packaging/debian/extra/gorgoned.service +++ /dev/null @@ -1,33 +0,0 @@ -## -## Copyright 2019-2021 Centreon -## -## Licensed under the Apache License, Version 2.0 (the "License"); -## you may not use this file except in compliance with the License. -## You may obtain a copy of the License at -## -## http://www.apache.org/licenses/LICENSE-2.0 -## -## Unless required by applicable law or agreed to in writing, software -## distributed under the License is distributed on an "AS IS" BASIS, -## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -## See the License for the specific language governing permissions and -## limitations under the License. -## -## For more information : contact@centreon.com -## - -[Unit] -Description=Centreon Gorgone -PartOf=centreon.service -After=centreon.service -ReloadPropagatedFrom=centreon.service - -[Service] -EnvironmentFile=/etc/default/gorgoned -ExecStart=/usr/bin/perl /usr/bin/gorgoned --config=${CONFIG} --logfile=${LOGFILE} --severity=${SEVERITY} -Type=simple -User=centreon-gorgone - -[Install] -WantedBy=multi-user.target -WantedBy=centreon.service From b011f03ec86fa425e58d8d389ee4f77a4f8fd75c Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Fri, 10 Mar 2023 10:07:18 +0000 Subject: [PATCH 718/948] enh(gorgone): use zmq ffi binding (#1057) Co-authored-by: Kevin Duret <kduret@centreon.com> Co-authored-by: Laurent Pinsivy <lpinsivy@centreon.com> --- gorgone/gorgone/class/clientzmq.pm | 134 ++++-- gorgone/gorgone/class/core.pm | 335 +++++++------- gorgone/gorgone/class/db.pm | 41 +- .../gorgone/class/fingerprint/backend/sql.pm | 11 +- gorgone/gorgone/class/frame.pm | 189 ++++++++ gorgone/gorgone/class/listener.pm | 35 +- gorgone/gorgone/class/lock.pm | 6 +- gorgone/gorgone/class/logger.pm | 47 +- gorgone/gorgone/class/module.pm | 168 ++++--- gorgone/gorgone/class/sqlquery.pm | 17 +- .../centreon/anomalydetection/class.pm | 104 ++--- .../centreon/anomalydetection/hooks.pm | 6 +- .../gorgone/modules/centreon/audit/class.pm | 67 +-- .../gorgone/modules/centreon/audit/hooks.pm | 6 +- .../modules/centreon/autodiscovery/class.pm | 362 ++++++++------- .../modules/centreon/autodiscovery/hooks.pm | 6 +- .../autodiscovery/services/discovery.pm | 102 +++-- .../autodiscovery/services/resources.pm | 46 +- .../gorgone/modules/centreon/engine/class.pm | 53 ++- .../gorgone/modules/centreon/engine/hooks.pm | 6 +- .../gorgone/modules/centreon/judge/class.pm | 64 +-- .../gorgone/modules/centreon/judge/hooks.pm | 6 +- .../modules/centreon/judge/type/distribute.pm | 5 +- .../modules/centreon/judge/type/spare.pm | 53 ++- .../modules/centreon/legacycmd/class.pm | 130 +++--- .../modules/centreon/legacycmd/hooks.pm | 6 +- .../gorgone/modules/centreon/mbi/etl/class.pm | 61 +-- .../gorgone/modules/centreon/mbi/etl/hooks.pm | 6 +- .../modules/centreon/mbi/etl/import/main.pm | 8 +- .../modules/centreon/mbi/etlworkers/class.pm | 51 +-- .../mbi/etlworkers/dimensions/main.pm | 20 +- .../centreon/mbi/etlworkers/event/main.pm | 20 +- .../modules/centreon/mbi/etlworkers/hooks.pm | 19 +- .../centreon/mbi/etlworkers/import/main.pm | 8 +- .../centreon/mbi/etlworkers/perfdata/main.pm | 4 +- .../modules/centreon/mbi/libs/bi/BIHost.pm | 30 +- .../centreon/mbi/libs/bi/BIHostCategory.pm | 8 +- .../centreon/mbi/libs/bi/BIHostGroup.pm | 8 +- .../centreon/mbi/libs/bi/BIHostStateEvents.pm | 18 +- .../modules/centreon/mbi/libs/bi/BIMetric.pm | 30 +- .../modules/centreon/mbi/libs/bi/BIService.pm | 28 +- .../centreon/mbi/libs/bi/BIServiceCategory.pm | 10 +- .../mbi/libs/bi/BIServiceStateEvents.pm | 10 +- .../centreon/mbi/libs/bi/DataQuality.pm | 6 +- .../modules/centreon/mbi/libs/bi/Dumper.pm | 4 +- .../mbi/libs/bi/HGMonthAvailability.pm | 4 +- .../mbi/libs/bi/HGServiceMonthAvailability.pm | 4 +- .../centreon/mbi/libs/bi/HostAvailability.pm | 6 +- .../centreon/mbi/libs/bi/LiveService.pm | 24 +- .../modules/centreon/mbi/libs/bi/Loader.pm | 12 +- .../mbi/libs/bi/MetricCentileValue.pm | 6 +- .../centreon/mbi/libs/bi/MetricDailyValue.pm | 10 +- .../centreon/mbi/libs/bi/MetricHourlyValue.pm | 2 +- .../mbi/libs/bi/MetricMonthCapacity.pm | 4 +- .../centreon/mbi/libs/bi/MySQLTables.pm | 57 ++- .../mbi/libs/bi/ServiceAvailability.pm | 8 +- .../modules/centreon/mbi/libs/bi/Time.pm | 28 +- .../mbi/libs/centreon/CentileProperties.pm | 2 +- .../mbi/libs/centreon/ETLProperties.pm | 6 +- .../centreon/mbi/libs/centreon/Host.pm | 47 +- .../mbi/libs/centreon/HostCategory.pm | 2 +- .../centreon/mbi/libs/centreon/HostGroup.pm | 6 +- .../centreon/mbi/libs/centreon/Service.pm | 39 +- .../mbi/libs/centreon/ServiceCategory.pm | 4 +- .../centreon/mbi/libs/centreon/Timeperiod.pm | 14 +- .../mbi/libs/centstorage/HostStateEvents.pm | 9 +- .../centreon/mbi/libs/centstorage/Metrics.pm | 28 +- .../libs/centstorage/ServiceStateEvents.pm | 6 +- .../gorgone/modules/centreon/nodes/class.pm | 72 ++- .../gorgone/modules/centreon/nodes/hooks.pm | 6 +- .../modules/centreon/statistics/class.pm | 72 +-- .../modules/centreon/statistics/hooks.pm | 6 +- gorgone/gorgone/modules/core/action/class.pm | 76 ++-- gorgone/gorgone/modules/core/action/hooks.pm | 6 +- gorgone/gorgone/modules/core/cron/class.pm | 77 ++-- gorgone/gorgone/modules/core/cron/hooks.pm | 6 +- .../gorgone/modules/core/dbcleaner/class.pm | 63 +-- .../gorgone/modules/core/dbcleaner/hooks.pm | 10 +- .../gorgone/modules/core/httpserver/class.pm | 36 +- .../gorgone/modules/core/httpserver/hooks.pm | 6 +- .../modules/core/httpserverng/class.pm | 92 ++-- .../modules/core/httpserverng/hooks.pm | 6 +- .../gorgone/modules/core/pipeline/class.pm | 61 +-- .../gorgone/modules/core/pipeline/hooks.pm | 6 +- gorgone/gorgone/modules/core/proxy/class.pm | 170 ++++---- gorgone/gorgone/modules/core/proxy/hooks.pm | 250 ++++++----- .../gorgone/modules/core/proxy/httpserver.pm | 59 ++- gorgone/gorgone/modules/core/pull/class.pm | 56 +-- gorgone/gorgone/modules/core/pull/hooks.pm | 6 +- gorgone/gorgone/modules/core/pullwss/class.pm | 29 +- gorgone/gorgone/modules/core/pullwss/hooks.pm | 12 +- .../gorgone/modules/core/register/class.pm | 58 +-- .../gorgone/modules/core/register/hooks.pm | 6 +- .../gorgone/modules/plugins/newtest/class.pm | 67 ++- .../gorgone/modules/plugins/newtest/hooks.pm | 12 +- gorgone/gorgone/modules/plugins/scom/class.pm | 59 +-- gorgone/gorgone/modules/plugins/scom/hooks.pm | 12 +- gorgone/gorgone/standard/api.pm | 70 ++- gorgone/gorgone/standard/library.pm | 412 ++++++++---------- gorgone/gorgone/standard/misc.pm | 35 +- .../packaging/centreon-gorgone.spectemplate | 4 +- gorgone/packaging/debian/control | 5 +- .../Digest-MD5-File-0.08-1.el8.noarch.rpm | Bin 14344 -> 0 bytes .../packages/perl-Clone-0.45-1.el8.x86_64.rpm | Bin 19900 -> 0 bytes .../perl-Clone-Choose-0.010-1.el7.noarch.rpm | Bin 9708 -> 0 bytes .../perl-CryptX-0.064-1.el7.x86_64.rpm | Bin 720852 -> 0 bytes .../perl-CryptX-0.068-1.el8.x86_64.rpm | Bin 697776 -> 0 bytes ...MQ-LibZMQ4.spec => perl-FFI-CheckLib.spec} | 31 +- .../packaging/packages/perl-FFI-Platypus.spec | 58 +++ .../perl-HTTP-Daemon-6.06-1.el8.noarch.rpm | Bin 24608 -> 0 bytes .../perl-Hash-Merge-0.300-1.el7.noarch.rpm | Bin 14768 -> 0 bytes .../perl-JSON-XS-4.02-1.el8.x86_64.rpm | Bin 99448 -> 0 bytes .../perl-Net-Curl-0.44-1.el8.x86_64.rpm | Bin 98844 -> 0 bytes .../perl-Schedule-Cron-1.01-1.el7.noarch.rpm | Bin 44950 -> 0 bytes ...l-Time-ParseDate-2015.103-1.el7.noarch.rpm | Bin 40012 -> 0 bytes ...perl-Types-Serialiser-1.0-1.el8.noarch.rpm | Bin 17420 -> 0 bytes .../packages/perl-UUID-0.28-1.el8.x86_64.rpm | Bin 24384 -> 0 bytes .../perl-YAML-LibYAML-0.80-1.el7.x86_64.rpm | Bin 77612 -> 0 bytes .../perl-YAML-LibYAML-0.80-1.el8.x86_64.rpm | Bin 81484 -> 0 bytes .../perl-ZMQ-Constants-1.04-1.el8.noarch.rpm | Bin 15508 -> 0 bytes .../packages/perl-ZMQ-Constants.spec | 56 --- gorgone/packaging/packages/perl-ZMQ-FFI.spec | 62 +++ .../perl-ZMQ-LibZMQ4-0.01-1.el8.x86_64.rpm | Bin 46812 -> 0 bytes .../perl-common-sense-3.75-1.el8.noarch.rpm | Bin 15072 -> 0 bytes 124 files changed, 2477 insertions(+), 2335 deletions(-) create mode 100644 gorgone/gorgone/class/frame.pm delete mode 100644 gorgone/packaging/packages/Digest-MD5-File-0.08-1.el8.noarch.rpm delete mode 100644 gorgone/packaging/packages/perl-Clone-0.45-1.el8.x86_64.rpm delete mode 100644 gorgone/packaging/packages/perl-Clone-Choose-0.010-1.el7.noarch.rpm delete mode 100644 gorgone/packaging/packages/perl-CryptX-0.064-1.el7.x86_64.rpm delete mode 100644 gorgone/packaging/packages/perl-CryptX-0.068-1.el8.x86_64.rpm rename gorgone/packaging/packages/{perl-ZMQ-LibZMQ4.spec => perl-FFI-CheckLib.spec} (50%) create mode 100644 gorgone/packaging/packages/perl-FFI-Platypus.spec delete mode 100644 gorgone/packaging/packages/perl-HTTP-Daemon-6.06-1.el8.noarch.rpm delete mode 100644 gorgone/packaging/packages/perl-Hash-Merge-0.300-1.el7.noarch.rpm delete mode 100644 gorgone/packaging/packages/perl-JSON-XS-4.02-1.el8.x86_64.rpm delete mode 100644 gorgone/packaging/packages/perl-Net-Curl-0.44-1.el8.x86_64.rpm delete mode 100644 gorgone/packaging/packages/perl-Schedule-Cron-1.01-1.el7.noarch.rpm delete mode 100644 gorgone/packaging/packages/perl-Time-ParseDate-2015.103-1.el7.noarch.rpm delete mode 100644 gorgone/packaging/packages/perl-Types-Serialiser-1.0-1.el8.noarch.rpm delete mode 100644 gorgone/packaging/packages/perl-UUID-0.28-1.el8.x86_64.rpm delete mode 100644 gorgone/packaging/packages/perl-YAML-LibYAML-0.80-1.el7.x86_64.rpm delete mode 100644 gorgone/packaging/packages/perl-YAML-LibYAML-0.80-1.el8.x86_64.rpm delete mode 100644 gorgone/packaging/packages/perl-ZMQ-Constants-1.04-1.el8.noarch.rpm delete mode 100644 gorgone/packaging/packages/perl-ZMQ-Constants.spec create mode 100644 gorgone/packaging/packages/perl-ZMQ-FFI.spec delete mode 100644 gorgone/packaging/packages/perl-ZMQ-LibZMQ4-0.01-1.el8.x86_64.rpm delete mode 100644 gorgone/packaging/packages/perl-common-sense-3.75-1.el8.noarch.rpm diff --git a/gorgone/gorgone/class/clientzmq.pm b/gorgone/gorgone/class/clientzmq.pm index 9b3f9d48841..a293c8695c5 100644 --- a/gorgone/gorgone/class/clientzmq.pm +++ b/gorgone/gorgone/class/clientzmq.pm @@ -24,11 +24,11 @@ use strict; use warnings; use gorgone::standard::library; use gorgone::standard::misc; -use ZMQ::LibZMQ4; -use ZMQ::Constants qw(:all); use Crypt::Mode::CBC; use MIME::Base64; use Scalar::Util; +use ZMQ::FFI qw(ZMQ_DONTWAIT); +use EV; my $connectors = {}; my $callbacks = {}; @@ -37,9 +37,11 @@ my $sockets = {}; sub new { my ($class, %options) = @_; my $connector = {}; + $connector->{context} = $options{context}; $connector->{logger} = $options{logger}; $connector->{identity} = $options{identity}; $connector->{extra_identity} = gorgone::standard::library::generate_token(length => 12); + $connector->{core_loop} = $options{core_loop}; $connector->{verbose_last_message} = ''; $connector->{config_core} = $options{config_core}; @@ -92,9 +94,11 @@ sub new { sub init { my ($self, %options) = @_; - + $self->{handshake} = 0; - $sockets->{$self->{identity}} = gorgone::standard::library::connect_com( + delete $self->{server_pubkey}; + $sockets->{ $self->{identity} } = gorgone::standard::library::connect_com( + context => $self->{context}, zmq_type => 'ZMQ_DEALER', name => $self->{identity} . '-' . $self->{extra_identity}, logger => $self->{logger}, @@ -102,13 +106,14 @@ sub init { path => $self->{target_path}, zmq_ipv6 => $self->{config_core}->{ipv6} ); - $callbacks->{$self->{identity}} = $options{callback} if (defined($options{callback})); + $callbacks->{ $self->{identity} } = $options{callback} if (defined($options{callback})); } sub close { my ($self, %options) = @_; - zmq_close($sockets->{$self->{identity}}); + delete $self->{core_watcher}; + $sockets->{ $self->{identity} }->close(); } sub get_connect_identity { @@ -120,14 +125,23 @@ sub get_connect_identity { sub get_server_pubkey { my ($self, %options) = @_; - zmq_sendmsg($sockets->{$self->{identity}}, '[GETPUBKEY]', ZMQ_DONTWAIT); - zmq_poll([$self->get_poll()], 10000); + $sockets->{ $self->{identity} }->send('[GETPUBKEY]', ZMQ_DONTWAIT); + $self->event(identity => $self->{identity}); + + my $w1 = $self->{connect_loop}->timer( + 10, + 0, + sub { + $self->{connect_loop}->break(); + } + ); + $self->{connect_loop}->run(); } sub read_key_protocol { my ($self, %options) = @_; - $self->{logger}->writeLogDebug('[clientzmq] read key protocol: ' . $options{text}); + $self->{logger}->writeLogDebug('[clientzmq] ' . $self->{identity} . ' - read key protocol: ' . $options{text}); return (-1, 'Wrong protocol') if ($options{text} !~ /^\[KEY\]\s+(.*)$/); @@ -167,7 +181,7 @@ sub decrypt_message { ); }; if ($@) { - $self->{logger}->writeLogError("[clientzmq] decrypt message issue: " . $@); + $self->{logger}->writeLogError("[clientzmq] $self->{identity} - decrypt message issue: " . $@); return (-1, $@); } return (0, $plaintext); @@ -176,6 +190,11 @@ sub decrypt_message { sub client_get_secret { my ($self, %options) = @_; + # there is an issue + if ($options{message} =~ /^\[ACK\]/) { + return (-1, "issue: $options{message}"); + } + my $plaintext; eval { my $cryptedtext = MIME::Base64::decode_base64($options{message}); @@ -191,8 +210,10 @@ sub client_get_secret { sub check_server_pubkey { my ($self, %options) = @_; + $self->{logger}->writeLogDebug("[clientzmq] $self->{identity} - get_server_pubkey check [1]"); + if ($options{message} !~ /^\s*\[PUBKEY\]\s+\[(.*?)\]/) { - $self->{logger}->writeLogError('[clientzmq] Cannot read pubbkey response from server: ' . $options{message}) if (defined($self->{logger})); + $self->{logger}->writeLogError('[clientzmq] ' . $self->{identity} . ' - cannot read pubbkey response from server: ' . $options{message}) if (defined($self->{logger})); $self->{verbose_last_message} = 'cannot read pubkey response from server'; return 0; } @@ -206,7 +227,7 @@ sub check_server_pubkey { ); if ($code == 0) { - $self->{logger}->writeLogError('[clientzmq] Cannot load pubbkey') if (defined($self->{logger})); + $self->{logger}->writeLogError('[clientzmq] ' . $self->{identity} . ' cannot load pubbkey') if (defined($self->{logger})); $self->{verbose_last_message} = 'cannot load pubkey'; return 0; } @@ -225,6 +246,8 @@ sub check_server_pubkey { } } + $self->{logger}->writeLogDebug("[clientzmq] $self->{identity} - get_server_pubkey ok [1]"); + return 1; } @@ -250,63 +273,63 @@ sub ping { $self->send_message(action => $action, data => $options{data}, json_encode => $options{json_encode}); $status = 1; } + if ($self->{ping_progress} == 1 && time() - $self->{ping_timeout_time} > $self->{ping_timeout}) { $self->{logger}->writeLogError("[clientzmq] No ping response") if (defined($self->{logger})); $self->{ping_progress} = 0; - # we delete the old one - for (my $i = 0; $i < scalar(@{$options{poll}}); $i++) { - if (Scalar::Util::refaddr($sockets->{$self->{identity}}) eq Scalar::Util::refaddr($options{poll}->[$i]->{socket})) { - splice @{$options{poll}}, $i, 1; - last; - } - } - zmq_close($sockets->{$self->{identity}}); + $sockets->{ $self->{identity} }->close(); + delete $self->{core_watcher}; $self->init(); - push @{$options{poll}}, $self->get_poll(); $status = 1; } - + return $status; } -sub get_poll { +sub add_watcher { my ($self, %options) = @_; - return { - socket => $sockets->{$self->{identity}}, - events => ZMQ_POLLIN, - callback => sub { - event(identity => $self->{identity}); + $self->{core_watcher} = $self->{core_loop}->io( + $sockets->{ $self->{identity} }->get_fd(), + EV::READ, + sub { + $self->event(identity => $self->{identity}); } - }; + ); } sub event { - my (%options) = @_; - - # We have a response. So it's ok :) - if ($connectors->{ $options{identity} }->{ping_progress} == 1) { - $connectors->{ $options{identity} }->{ping_progress} = 0; - } + my ($self, %options) = @_; $connectors->{ $options{identity} }->{ping_time} = time(); - while (1) { - my $message = gorgone::standard::library::zmq_dealer_read_message(socket => $sockets->{$options{identity}}); - last if (!defined($message)); + while ($sockets->{ $options{identity} }->has_pollin()) { + # We have a response. So it's ok :) + if ($connectors->{ $options{identity} }->{ping_progress} == 1) { + $connectors->{ $options{identity} }->{ping_progress} = 0; + } + + my ($rv, $message) = gorgone::standard::library::zmq_dealer_read_message(socket => $sockets->{ $options{identity} }); + last if ($rv); # in progress if ($connectors->{ $options{identity} }->{handshake} == 0) { + $self->{connect_loop}->break(); $connectors->{ $options{identity} }->{handshake} = 1; if ($connectors->{ $options{identity} }->check_server_pubkey(message => $message) == 0) { $connectors->{ $options{identity} }->{handshake} = -1; + } } elsif ($connectors->{ $options{identity} }->{handshake} == 1) { + $self->{connect_loop}->break(); + + $self->{logger}->writeLogDebug("[clientzmq] $self->{identity} - client_get_secret recv [3]"); my ($status, $verbose, $symkey, $hostname) = $connectors->{ $options{identity} }->client_get_secret( message => $message ); if ($status == -1) { + $self->{logger}->writeLogDebug("[clientzmq] $self->{identity} - client_get_secret $verbose [3]"); $connectors->{ $options{identity} }->{handshake} = -1; $connectors->{ $options{identity} }->{verbose_last_message} = $verbose; return ; @@ -314,9 +337,10 @@ sub event { $connectors->{ $options{identity} }->{handshake} = 2; if (defined($connectors->{ $options{identity} }->{logger})) { $connectors->{ $options{identity} }->{logger}->writeLogInfo( - "[clientzmq] Client connected successfully to '" . $connectors->{ $options{identity} }->{target_type} . + "[clientzmq] $self->{identity} - Client connected successfully to '" . $connectors->{ $options{identity} }->{target_type} . "://" . $connectors->{ $options{identity} }->{target_path} . "'" ); + $self->add_watcher(); } } else { my ($rv, $data) = $connectors->{ $options{identity} }->decrypt_message(message => $message); @@ -332,7 +356,7 @@ sub event { } elsif (defined($callbacks->{$options{identity}})) { $callbacks->{$options{identity}}->(identity => $options{identity}, data => $data); } - } + } } } @@ -357,14 +381,25 @@ sub zmq_send_message { return undef; } - zmq_sendmsg($options{socket}, $message, ZMQ_DONTWAIT); + $options{socket}->send($message, ZMQ_DONTWAIT); + $self->event(identity => $self->{identity}); } sub send_message { my ($self, %options) = @_; if ($self->{handshake} == 0) { + $self->{connect_loop} = new EV::Loop(); + $self->{connect_watcher} = $self->{connect_loop}->io( + $sockets->{ $self->{identity} }->get_fd(), + EV::READ, + sub { + $self->event(identity => $self->{identity}); + } + ); + if (!defined($self->{server_pubkey})) { + $self->{logger}->writeLogDebug("[clientzmq] $self->{identity} - get_server_pubkey sent [1]"); $self->get_server_pubkey(); } else { $self->{handshake} = 1; @@ -378,15 +413,27 @@ sub send_message { client_pubkey => $self->{client_pubkey}, ); if ($status == -1) { + $self->{logger}->writeLogDebug("[clientzmq] $self->{identity} - client_helo crypt handshake issue [2]"); $self->{verbose_last_message} = 'crypt handshake issue'; return (-1, $self->{verbose_last_message}); } + $self->{logger}->writeLogDebug("[clientzmq] $self->{identity} - client_helo sent [2]"); + $self->{verbose_last_message} = 'Handshake timeout'; - zmq_sendmsg($sockets->{$self->{identity}}, $ciphertext, ZMQ_DONTWAIT); - zmq_poll([$self->get_poll()], 10000); + $sockets->{ $self->{identity} }->send($ciphertext, ZMQ_DONTWAIT); + $self->event(identity => $self->{identity}); + + my $w1 = $self->{connect_loop}->timer( + 10, + 0, + sub { $self->{connect_loop}->break(); } + ); + $self->{connect_loop}->run(); } + undef $self->{connect_loop} if (defined($self->{connect_loop})); + if ($self->{handshake} < 2) { $self->{handshake} = 0; return (-1, $self->{verbose_last_message}); @@ -396,6 +443,7 @@ sub send_message { socket => $sockets->{ $self->{identity} }, %options ); + return 0; } diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index ecf89fc8fce..0aba0051240 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -26,14 +26,16 @@ use POSIX ":sys_wait_h"; use Sys::Hostname; use MIME::Base64; use Crypt::Mode::CBC; -use ZMQ::LibZMQ4; -use ZMQ::Constants qw(:all); +use ZMQ::FFI qw(ZMQ_DONTWAIT ZMQ_SNDMORE); +use EV; use gorgone::standard::library; use gorgone::standard::constants qw(:all); use gorgone::standard::misc; use gorgone::class::db; use gorgone::class::listener; +use gorgone::class::frame; use Time::HiRes; +use Try::Tiny; my ($gorgone); @@ -282,9 +284,9 @@ sub init { sub init_external_informations { my ($self) = @_; - my ($status, $sth) = $self->{db_gorgone}->query( - "SELECT `identity`, `ctime`, `mtime`, `key`, `oldkey`, `iv`, `oldiv` FROM gorgone_identity ORDER BY id DESC" - ); + my ($status, $sth) = $self->{db_gorgone}->query({ + query => "SELECT `identity`, `ctime`, `mtime`, `key`, `oldkey`, `iv`, `oldiv` FROM gorgone_identity ORDER BY id DESC" + }); if ($status == -1) { $self->{logger}->writeLogError("[core] cannot load gorgone_identity"); return 0; @@ -498,16 +500,23 @@ sub broadcast_core_key { my ($rv, $key) = gorgone::standard::library::generate_symkey( keysize => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_keysize} ); + + my $message = '[BCASTCOREKEY] [] [] { "key": "' . unpack('H*', $key). '"}'; + my $frame = gorgone::class::frame->new(); + $frame->setFrame(\$message); + $self->message_run( - message => '[BCASTCOREKEY] [] [] { "key": "' . unpack('H*', $key). '"}', - router_type => 'internal' + { + frame => $frame, + router_type => 'internal' + } ); } sub read_internal_message { my ($self, %options) = @_; - my ($identity, $message) = gorgone::standard::library::zmq_read_message( + my ($identity, $frame) = gorgone::standard::library::zmq_read_message( socket => $self->{internal_socket}, logger => $self->{logger} ); @@ -524,47 +533,42 @@ sub read_internal_message { if (defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_core_oldkey})); } foreach my $key (@$keys) { - my $plaintext; - eval { - $plaintext = $self->{cipher}->decrypt($message, $key, $self->{internal_crypt}->{iv}); - }; - if (defined($plaintext) && $plaintext =~ /^\[[A-Za-z0-9_\-]+?\]/) { - return ($identity, $plaintext); + if ($frame->decrypt({ cipher => $self->{cipher}, key => $key, iv => $self->{internal_crypt}->{iv} }) == 0) { + return ($identity, $frame); } } - $self->{logger}->writeLogError("[core] decrypt issue ($id): " . ($@ ? $@ : 'no message')); + $self->{logger}->writeLogError("[core] decrypt issue ($id): " . $frame->getLastError()); return undef; } - return ($identity, $message); + return ($identity, $frame); } sub send_internal_response { my ($self, %options) = @_; - zmq_sendmsg($self->{internal_socket}, pack('H*', $options{identity}), ZMQ_DONTWAIT | ZMQ_SNDMORE); - my $response_type = defined($options{response_type}) ? $options{response_type} : 'ACK'; my $data = gorgone::standard::library::json_encode(data => { code => $options{code}, data => $options{data} }); # We add 'target' for 'PONG', 'SYNCLOGS'. Like that 'gorgone-proxy can get it my $message = '[' . $response_type . '] [' . (defined($options{token}) ? $options{token} : '') . '] ' . ($response_type =~ /^PONG|SYNCLOGS$/ ? '[] ' : '') . $data; if ($self->{internal_crypt}->{enabled} == 1) { - eval { + try { $message = $self->{cipher}->encrypt( $message, $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_core_key}, $self->{internal_crypt}->{iv} ); - }; - if ($@) { - $self->{logger}->writeLogError("[core] encrypt issue: " . $@); + } catch { + $self->{logger}->writeLogError("[core] encrypt issue: $_"); return undef; - } + }; } - zmq_sendmsg($self->{internal_socket}, $message, ZMQ_DONTWAIT); + $self->{internal_socket}->send(pack('H*', $options{identity}), ZMQ_DONTWAIT | ZMQ_SNDMORE); + $self->{internal_socket}->send($message, ZMQ_DONTWAIT); + $self->router_internal_event(); } sub send_internal_message { @@ -574,29 +578,29 @@ sub send_internal_message { if (!defined($message)) { $message = gorgone::standard::library::build_protocol(%options); } - zmq_sendmsg($self->{internal_socket}, $options{identity}, ZMQ_DONTWAIT | ZMQ_SNDMORE); if ($self->{internal_crypt}->{enabled} == 1) { - eval { + try { $message = $self->{cipher}->encrypt( $message, $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_core_key}, $self->{internal_crypt}->{iv} ); - }; - if ($@) { - $self->{logger}->writeLogError("[core] encrypt issue: " . $@); + } catch { + $self->{logger}->writeLogError("[core] encrypt issue: $_"); return undef; - } + }; } - zmq_sendmsg($self->{internal_socket}, $message, ZMQ_DONTWAIT); + $self->{internal_socket}->send($options{identity}, ZMQ_DONTWAIT | ZMQ_SNDMORE); + $self->{internal_socket}->send($message, ZMQ_DONTWAIT); + $self->router_internal_event() if (!defined($options{nosync})); } sub broadcast_run { my ($self, %options) = @_; - my $data = gorgone::standard::library::json_decode(module => 'core', data => $options{data}, logger => $self->{logger}); + my $data = $options{frame}->decodeData(); return if (!defined($data)); if ($options{action} eq 'BCASTLOGGER') { @@ -615,6 +619,7 @@ sub broadcast_run { dbh => $self->{db_gorgone}, action => $options{action}, logger => $self->{logger}, + frame => $options{frame}, data => $options{data}, token => $options{token} ); @@ -628,13 +633,16 @@ sub broadcast_run { } sub message_run { - my ($self, %options) = @_; + my ($self, $options) = (shift, shift); - $self->{logger}->writeLogDebug('[core] Message received - ' . $options{message}); - if ($options{message} !~ /^\[(.+?)\]\s+\[(.*?)\]\s+\[(.*?)\]\s+(.*)$/) { + if ($self->{logger}->is_debug()) { + my $frame_ref = $options->{frame}->getFrame(); + $self->{logger}->writeLogDebug('[core] Message received - ' . $$frame_ref); + } + if ($options->{frame}->parse({ releaseFrame => 1 }) != 0) { return (undef, 1, { message => 'request not well formatted' }); } - my ($action, $token, $target, $data) = ($1, $2, $3, $4); + my ($action, $token, $target) = ($options->{frame}->getAction(), $options->{frame}->getToken(), $options->{frame}->getTarget()); # Check if not myself ;) if (defined($target) && ($target eq '' || (defined($self->{id}) && $target eq $self->{id}))) { @@ -647,29 +655,29 @@ sub message_run { if ($action !~ /^(?:ADDLISTENER|PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID|SETMODULEKEY|SYNCLOGS|LOADMODULE|UNLOADMODULE|INFORMATION|GETTHUMBPRINT|BCAST.*)$/ && !defined($target) && !defined($self->{modules_events}->{$action})) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $self->{db_gorgone}, code => GORGONE_ACTION_FINISH_KO, token => $token, data => { error => "unknown_action", message => "action '$action' is unknown" }, json_encode => 1 - ); + }); return (undef, 1, { error => "unknown_action", message => "action '$action' is unknown" }); } - $self->{counters}->{$options{router_type}}->{lc($action)} = 0 if (!defined($self->{counters}->{$options{router_type}}->{lc($action)})); - $self->{counters}->{$options{router_type}}->{lc($action)}++; + $self->{counters}->{ $options->{router_type} }->{lc($action)} = 0 if (!defined($self->{counters}->{ $options->{router_type} }->{lc($action)})); + $self->{counters}->{ $options->{router_type} }->{lc($action)}++; $self->{counters}->{total}++; - $self->{counters}->{$options{router_type}}->{total}++; + $self->{counters}->{ $options->{router_type} }->{total}++; if ($self->{stop} == 1) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $self->{db_gorgone}, code => GORGONE_ACTION_FINISH_KO, token => $token, data => { message => 'gorgone is stopping/restarting. Cannot proceed request.' }, json_encode => 1 - ); + }); return ($token, 1, { message => 'gorgone is stopping/restarting. Cannot proceed request.' }); } @@ -677,13 +685,13 @@ sub message_run { if (defined($target)) { if (!defined($self->{modules_id}->{ $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{proxy_name} }) || !defined($self->{modules_register}->{ $self->{modules_id}->{ $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{proxy_name} } })) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $self->{db_gorgone}, code => GORGONE_ACTION_FINISH_KO, token => $token, data => { error => "no_proxy", message => 'no proxy configured. cannot manage target.' }, json_encode => 1 - ); + }); return ($token, 1, { error => "no_proxy", message => 'no proxy configured. cannot manage target.' }); } @@ -698,7 +706,7 @@ sub message_run { action => $action, token => $token, target => $target, - data => $data, + frame => $options->{frame}, hostname => $self->{hostname} ); return ($token, 0); @@ -708,22 +716,22 @@ sub message_run { my ($code, $response, $response_type) = $self->{internal_register}->{lc($action)}->( gorgone => $self, gorgone_config => $self->{config}->{configuration}->{gorgone}, - identity => $options{identity}, - router_type => $options{router_type}, + identity => $options->{identity}, + router_type => $options->{router_type}, id => $self->{id}, - data => $data, + frame => $options->{frame}, token => $token, logger => $self->{logger} ); if ($action =~ /^(?:CONSTATUS|INFORMATION|GETTHUMBPRINT)$/) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $self->{db_gorgone}, code => $code, token => $token, data => $response, json_encode => 1 - ); + }); } return ($token, $code, $response, $response_type); @@ -731,35 +739,40 @@ sub message_run { return (undef, 1, { message => "action '$action' is not known" }) if ($1 !~ /^(?:LOGGER|COREKEY)$/); $self->broadcast_run( action => $action, - data => $data, + frame => $options->{frame}, token => $token ); } else { - $self->{modules_register}->{$self->{modules_events}->{$action}->{module}->{package}}->{routing}->( + $self->{modules_register}->{ $self->{modules_events}->{$action}->{module}->{package} }->{routing}->( gorgone => $self, dbh => $self->{db_gorgone}, logger => $self->{logger}, action => $action, token => $token, target => $target, - data => $data, - hostname => $self->{hostname}, + frame => $options->{frame}, + hostname => $self->{hostname} ); } + return ($token, 0); } sub router_internal_event { - while (1) { - my ($identity, $message) = $gorgone->read_internal_message(); - last if (!defined($identity)); + my ($self, %options) = @_; - my ($token, $code, $response, $response_type) = $gorgone->message_run( - message => $message, - identity => $identity, - router_type => 'internal', + while ($self->{internal_socket}->has_pollin()) { + my ($identity, $frame) = $self->read_internal_message(); + next if (!defined($identity)); + + my ($token, $code, $response, $response_type) = $self->message_run( + { + frame => $frame, + identity => $identity, + router_type => 'internal' + } ); - $gorgone->send_internal_response( + $self->send_internal_response( identity => $identity, response_type => $response_type, data => $response, @@ -839,7 +852,9 @@ sub check_external_rotate_keys { sub external_decrypt_message { my ($self, %options) = @_; - my $crypt = MIME::Base64::decode_base64($options{message}); + my $message = $options{frame}->getFrame(); + + my $crypt = MIME::Base64::decode_base64($$message); my $keys = [ { key => $options{cipher_infos}->{key}, iv => $options{cipher_infos}->{iv} } ]; if (defined($options{cipher_infos}->{oldkey})) { @@ -847,16 +862,17 @@ sub external_decrypt_message { } foreach my $key (@$keys) { my $plaintext; - eval { + try { $plaintext = $self->{external_crypt_mode}->decrypt($crypt, $key->{key}, $key->{iv}); }; if (defined($plaintext) && $plaintext =~ /^\[[A-Za-z0-9_\-]+?\]/) { - return (0, $plaintext); + $options{frame}->setFrame(\$plaintext); + return 0; } } - $self->{logger}->writeLogError("[core] external decrypt issue: " . ($@ ? $@ : 'no message')); - return (-1, ($@ ? $@ : 'no message')); + $self->{logger}->writeLogError("[core] external decrypt issue: " . ($_ ? $_ : 'no message')); + return -1; } sub external_core_response { @@ -871,23 +887,23 @@ sub external_core_response { } if (defined($options{cipher_infos})) { - eval { + try { $message = $self->{external_crypt_mode}->encrypt( $message, $options{cipher_infos}->{key}, $options{cipher_infos}->{iv} ); - }; - if ($@) { - $self->{logger}->writeLogError("[core] external_core_response encrypt issue: " . $@); + } catch { + $self->{logger}->writeLogError("[core] external_core_response encrypt issue: $_"); return undef; - } + }; $message = MIME::Base64::encode_base64($message, ''); } - zmq_sendmsg($self->{external_socket}, pack('H*', $options{identity}), ZMQ_DONTWAIT|ZMQ_SNDMORE); - zmq_sendmsg($self->{external_socket}, $message, ZMQ_DONTWAIT); + $self->{external_socket}->send(pack('H*', $options{identity}), ZMQ_DONTWAIT|ZMQ_SNDMORE); + $self->{external_socket}->send($message, ZMQ_DONTWAIT); + $self->router_external_event(); } sub external_core_key_response { @@ -905,16 +921,16 @@ sub external_core_key_response { return -1 if (!defined($data)); my $crypttext; - eval { + try { $crypttext = $options{client_pubkey}->encrypt("[KEY] " . $data, 'v1.5'); - }; - if ($@) { - $self->{logger}->writeLogError("[core] core key response encrypt issue: " . $@); + } catch { + $self->{logger}->writeLogError("[core] core key response encrypt issue: $_"); return -1; - } + }; - zmq_sendmsg($self->{external_socket}, pack('H*', $options{identity}), ZMQ_DONTWAIT | ZMQ_SNDMORE); - zmq_sendmsg($self->{external_socket}, MIME::Base64::encode_base64($crypttext, ''), ZMQ_DONTWAIT); + $self->{external_socket}->send(pack('H*', $options{identity}), ZMQ_DONTWAIT | ZMQ_SNDMORE); + $self->{external_socket}->send(MIME::Base64::encode_base64($crypttext, ''), ZMQ_DONTWAIT); + $self->router_external_event(); return 0; } @@ -922,15 +938,17 @@ sub handshake { my ($self, %options) = @_; my ($rv, $cipher_infos); + my $first_message = $options{frame}->getFrame(); # Test if it asks for the pubkey - if ($options{message} =~ /^\s*\[GETPUBKEY\]/) { + if ($$first_message =~ /^\s*\[GETPUBKEY\]/) { gorgone::standard::library::zmq_core_pubkey_response( socket => $self->{external_socket}, identity => $options{identity}, pubkey => $self->{server_pubkey} ); - return undef; + $self->router_external_event(); + return 1; } ($rv, $cipher_infos) = $self->is_handshake_done(identity => $options{identity}); @@ -938,13 +956,15 @@ sub handshake { if ($rv == 1) { my $response; - ($rv, $response) = $self->external_decrypt_message( - message => $options{message}, + ($rv) = $self->external_decrypt_message( + frame => $options{frame}, cipher_infos => $cipher_infos ); - if ($rv == 0 && $response =~ /^(?:[\[a-zA-Z-_]+?\]\s+\[.*?\]|[\[a-zA-Z-_]+?\]\s*$)/) { + + my $message = $options{frame}->getFrame(); + if ($rv == 0 && $$message =~ /^(?:[\[a-zA-Z-_]+?\]\s+\[.*?\]|[\[a-zA-Z-_]+?\]\s*$)/) { gorgone::standard::library::update_identity_mtime(dbh => $self->{db_gorgone}, identity => $options{identity}); - return ($cipher_infos, $response); + return (0, $cipher_infos); } # Maybe he want to redo a handshake @@ -957,7 +977,7 @@ sub handshake { # We try to uncrypt ($rv, $client_pubkey) = gorgone::standard::library::is_client_can_connect( privkey => $self->{server_privkey}, - message => $options{message}, + message => $$first_message, logger => $self->{logger}, authorized_clients => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{authorized_clients} ); @@ -967,7 +987,7 @@ sub handshake { code => GORGONE_ACTION_FINISH_KO, data => { message => 'handshake issue' } ); - return undef; + return -1; } ($rv, $key) = gorgone::standard::library::generate_symkey( keysize => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_keysize} @@ -1006,7 +1026,7 @@ sub handshake { } } - return undef; + return -1; } sub send_message_parent { @@ -1036,28 +1056,33 @@ sub send_message_parent { } sub router_external_event { - while (1) { - my ($identity, $message) = gorgone::standard::library::zmq_read_message( - socket => $gorgone->{external_socket}, - logger => $gorgone->{logger} + my ($self, %options) = @_; + + while ($self->{external_socket}->has_pollin()) { + my ($identity, $frame) = gorgone::standard::library::zmq_read_message( + socket => $self->{external_socket}, + logger => $self->{logger} ); - last if (!defined($identity)); + next if (!defined($identity)); - my ($cipher_infos, $uncrypt_message) = $gorgone->handshake( + my ($rv, $cipher_infos) = $self->handshake( identity => $identity, - message => $message, + frame => $frame ); - if (defined($uncrypt_message)) { - my ($token, $code, $response, $response_type) = $gorgone->message_run( - message => $uncrypt_message, - identity => $identity, - router_type => 'external', + if ($rv == 0) { + my ($token, $code, $response, $response_type) = $self->message_run( + { + frame => $frame, + identity => $identity, + router_type => 'external' + } ); - $gorgone->external_core_response( + $self->external_core_response( identity => $identity, cipher_infos => $cipher_infos, response_type => $response_type, - token => $token, code => $code, + token => $token, + code => $code, data => $response ); } @@ -1070,51 +1095,59 @@ sub waiting_ready_pool { my $name = $gorgone->{modules_id}->{$gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{proxy_name}}; my $method = $name->can('is_all_proxy_ready'); - my $time = time(); - while (time() - $time < 60) { - return 1 if ($method->() == 100); - zmq_poll($gorgone->{poll}, 5000); - $gorgone->check_exit_modules(); - } - if ($method->() > 0) { return 1; } + my $iteration = 10; + while ($iteration > 0) { + my $watcher_timer = $gorgone->{loop}->timer(1, 0, \&stop_ev); + $gorgone->{loop}->run(); + $iteration--; + if ($method->() > 0) { + return 1; + } + } + return 0; } +sub stop_ev { + $gorgone->{loop}->break(); + $gorgone->check_exit_modules(); +} + sub waiting_ready { my (%options) = @_; return 1 if (${$options{ready}} == 1); - - my $time = time(); - # We wait 10 seconds - while (${$options{ready}} == 0 && - time() - $time < 10) { - zmq_poll($gorgone->{poll}, 5000); - $gorgone->check_exit_modules(); - } - if (${$options{ready}} == 0) { - return 0; + my $iteration = 10; + while ($iteration > 0) { + my $watcher_timer = $gorgone->{loop}->timer(1, 0, \&stop_ev); + $gorgone->{loop}->run(); + if (${$options{ready}} == 1) { + return 1; + } } - return 1; + return 0; } sub quit { my ($self, %options) = @_; $self->{logger}->writeLogInfo("[core] Quit main process"); - zmq_close($self->{internal_socket}); - if (defined($self->{external_socket})) { - zmq_close($self->{external_socket}); - } + if ($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_type} eq 'ipc') { unlink($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_path}); } + + $self->{internal_socket}->close(); + if (defined($self->{external_socket})) { + $self->{external_socket}->close(); + } + exit(0); } @@ -1182,6 +1215,11 @@ sub check_exit_modules { } } +sub periodic_exec { + $gorgone->check_exit_modules(); + $gorgone->{listener}->check(); +} + sub run { $gorgone = shift; @@ -1191,17 +1229,23 @@ sub run { $gorgone->{logger}->writeLogInfo("[core] Gorgoned started"); $gorgone->{logger}->writeLogInfo("[core] PID $$"); - if (gorgone::standard::library::add_history( + if (gorgone::standard::library::add_history({ dbh => $gorgone->{db_gorgone}, code => GORGONE_STARTED, data => { message => 'gorgoned is starting...' }, - json_encode => 1) == -1 + json_encode => 1}) == -1 ) { $gorgone->{logger}->writeLogInfo("[core] Cannot write in history. We quit!!"); exit(1); } - + + { + local $SIG{__DIE__}; + $gorgone->{zmq_context} = ZMQ::FFI->new(); + } + $gorgone->{internal_socket} = gorgone::standard::library::create_com( + context => $gorgone->{zmq_context}, type => $gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_type}, path => $gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_path}, zmq_type => 'ZMQ_ROUTER', @@ -1209,11 +1253,13 @@ sub run { zmq_router_handover => $gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_zmq_router_handover}, logger => $gorgone->{logger} ); + if (defined($gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_type}) && $gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_type} ne '') { if ($gorgone->{keys_loaded}) { $gorgone->init_external_informations(); $gorgone->{external_socket} = gorgone::standard::library::create_com( + context => $gorgone->{zmq_context}, type => $gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_type}, path => $gorgone->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_path}, zmq_type => 'ZMQ_ROUTER', @@ -1228,23 +1274,6 @@ sub run { } } - # Initialize poll set - $gorgone->{poll} = [ - { - socket => $gorgone->{internal_socket}, - events => ZMQ_POLLIN, - callback => \&router_internal_event, - } - ]; - - if (defined($gorgone->{external_socket})) { - push @{$gorgone->{poll}}, { - socket => $gorgone->{external_socket}, - events => ZMQ_POLLIN, - callback => \&router_external_event, - }; - } - # init all modules foreach my $name (keys %{$gorgone->{modules_register}}) { $gorgone->{logger}->writeLogDebug("[core] Call init function from module '$name'"); @@ -1268,13 +1297,15 @@ sub run { $gorgone->{logger}->writeLogInfo("[core] Server accepting clients"); $gorgone->{cb_timer_check} = time(); - while (1) { - $gorgone->check_exit_modules(); - - zmq_poll($gorgone->{poll}, 5000); - $gorgone->{listener}->check(); + $gorgone->{loop} = new EV::Loop(); + $gorgone->{watcher_timer} = $gorgone->{loop}->timer(5, 5, \&periodic_exec); + $gorgone->{watcher_io_internal} = $gorgone->{loop}->io($gorgone->{internal_socket}->get_fd(), EV::READ, sub { $gorgone->router_internal_event() }); + if (defined($gorgone->{external_socket})) { + $gorgone->{watcher_io_external} = $gorgone->{loop}->io($gorgone->{external_socket}->get_fd(), EV::READ, sub { $gorgone->router_external_event() }); } + + $gorgone->{loop}->run(); } 1; diff --git a/gorgone/gorgone/class/db.pm b/gorgone/gorgone/class/db.pm index 0fc2cc8b3cf..53ff07d37a3 100644 --- a/gorgone/gorgone/class/db.pm +++ b/gorgone/gorgone/class/db.pm @@ -49,7 +49,6 @@ sub new { $self->{die} = defined($options{die}) ? 1 : 0; $self->{instance} = undef; $self->{transaction_begin} = 0; - $self->{args} = []; bless $self, $class; return $self; } @@ -151,17 +150,6 @@ sub last_insert_id { 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; @@ -338,50 +326,45 @@ Query: $query sub prepare { my ($self, $query) = @_; - return $self->query($query, prepare_only => 1); + return $self->query({ query => $query, prepare_only => 1 }); } sub query { - my $self = shift; - my $query = shift; - my (%options) = @_; + my ($self) = shift; my ($status, $count) = (0, -1); 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}}[$i]); - $query =~ s/##__ARG__$i##/$str_quoted/; - } - $self->{args} = []; - } if ($status == -1) { - $self->{args} = []; last; } } $count++; - $statement_handle = $self->{instance}->prepare($query); + $statement_handle = $self->{instance}->prepare($_[0]->{query}); if (!defined($statement_handle)) { - $self->error($self->{instance}->errstr, $query); + $self->error($self->{instance}->errstr, $_[0]->{query}); $status = -1; last if ($self->{force} == 0 || ($self->{force} == 2 && $count == 1)); sleep(1); next; } - if (defined($options{prepare_only})) { + if (defined($_[0]->{prepare_only})) { return $statement_handle if ($self->{die} == 1); return ($status, $statement_handle); } - my $rv = $statement_handle->execute(); + my $rv; + if (defined($_[0]->{bind_values}) && scalar(@{$_[0]->{bind_values}}) > 0) { + $rv = $statement_handle->execute(@{$_[0]->{bind_values}}); + } else { + $rv = $statement_handle->execute(); + } if (!$rv) { - $self->error($statement_handle->errstr, $query); + $self->error($statement_handle->errstr, $_[0]->{query}); $status = -1; last if ($self->{force} == 0 || ($self->{force} == 2 && $count == 1)); sleep(1); diff --git a/gorgone/gorgone/class/fingerprint/backend/sql.pm b/gorgone/gorgone/class/fingerprint/backend/sql.pm index d316114a562..a36542cd7c9 100644 --- a/gorgone/gorgone/class/fingerprint/backend/sql.pm +++ b/gorgone/gorgone/class/fingerprint/backend/sql.pm @@ -55,7 +55,10 @@ sub check_fingerprint { return 1 if ($self->{fingerprint_mode} eq 'always'); - my ($status, $sth) = $self->query("SELECT `id`, `fingerprint` FROM gorgone_target_fingerprint WHERE target = " . $self->quote($options{target}) . " ORDER BY id ASC LIMIT 1"); + my ($status, $sth) = $self->query({ + query => "SELECT `id`, `fingerprint` FROM gorgone_target_fingerprint WHERE target = ? ORDER BY id ASC LIMIT 1", + bind_values => [$options{target}] + }); return (0, "cannot get fingerprint for target '$options{target}'") if ($status == -1); my $row = $sth->fetchrow_hashref(); @@ -63,8 +66,10 @@ sub check_fingerprint { if ($self->{fingerprint_mode} eq 'strict') { return (0, "no fingerprint found for target '" . $options{target} . "' [strict mode] [fingerprint: $options{fingerprint}]"); } - ($status) = $self->query("INSERT INTO gorgone_target_fingerprint (`target`, `fingerprint`) VALUES (" - . $self->quote($options{target}) . ', ' . $self->quote($options{fingerprint}) . ')'); + ($status) = $self->query({ + query => "INSERT INTO gorgone_target_fingerprint (`target`, `fingerprint`) VALUES (?, ?)", + bind_values => [$options{target}, $options{fingerprint}] + }); return (0, "cannot insert target '$options{target}' fingerprint") if ($status == -1); return 1; } diff --git a/gorgone/gorgone/class/frame.pm b/gorgone/gorgone/class/frame.pm new file mode 100644 index 00000000000..f96e18dd5a5 --- /dev/null +++ b/gorgone/gorgone/class/frame.pm @@ -0,0 +1,189 @@ +# +# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +package gorgone::class::frame; + +use strict; +use warnings; + +use JSON::XS; +use Try::Tiny; + +sub new { + my ($class, %options) = @_; + my $self = {}; + bless $self, $class; + + if (defined($options{rawData})) { + $self->setRawData($options{rawData}); + } + if (defined($options{data})) { + $self->setData($options{data}); + } + + return $self; +} + +sub setData { + my ($self) = shift; + + $self->{data} = $_[0]; +} + +sub setRawData { + my ($self) = shift; + + $self->{rawData} = $_[0]; +} + +sub setFrame { + my ($self) = shift; + + $self->{frame} = $_[0]; +} + +sub getFrame { + my ($self) = shift; + + return $self->{frame}; +} + +sub getLastError { + my ($self) = shift; + + return $self->{lastError}; +} + +sub decrypt { + my ($self, $options) = (shift, shift); + + my $plaintext; + try { + $plaintext = $options->{cipher}->decrypt(${$self->{frame}}, $options->{key}, $options->{iv}); + }; + if (defined($plaintext) && $plaintext =~ /^\[[A-Za-z0-9_\-]+?\]/) { + $self->{frame} = \$plaintext; + return 0; + } + + $self->{lastError} = $_ ? $_ : 'no message'; + return 1; +} + +sub parse { + my ($self, $options) = (shift, shift); + + if (${$self->{frame}} =~ /^\[(.+?)\]\s+\[(.*?)\]\s+\[(.*?)\]\s+/g) { + $self->{action} = $1; + $self->{token} = $2; + $self->{target} = $3; + + if (defined($options) && defined($options->{decode})) { + try { + $self->{data} = JSON::XS->new->decode(substr(${$self->{frame}}, pos(${$self->{frame}}))); + } catch { + $self->{lastError} = $_; + return 1; + } + } else { + $self->{rawData} = substr(${$self->{frame}}, pos(${$self->{frame}})); + } + + if (defined($options) && defined($options->{releaseFrame})) { + $self->{frame} = undef; + } + + return 0; + } + + return 1; +} + +sub getData { + my ($self) = shift; + + if (!defined($self->{data})) { + try { + $self->{data} = JSON::XS->new->decode($self->{rawData}); + } catch { + $self->{lastError} = $_; + return undef; + } + } + + return $self->{data}; +} + +sub decodeData { + my ($self) = shift; + + if (!defined($self->{data})) { + try { + $self->{data} = JSON::XS->new->decode($self->{rawData}); + } catch { + $self->{lastError} = $_; + return undef; + } + } + + return $self->{data}; +} + +sub getRawData { + my ($self) = shift; + + if (!defined($self->{rawData})) { + try { + $self->{rawData} = JSON::XS->new->encode($self->{data}); + } catch { + $self->{lastError} = $_; + return undef; + } + } + return \$self->{rawData}; +} + +sub getAction { + my ($self) = shift; + + return $self->{action}; +} + +sub getToken { + my ($self) = shift; + + return $self->{token}; +} + +sub getTarget { + my ($self) = shift; + + return $self->{target}; +} + +sub DESTROY { + my ($self) = shift; + + $self->{frame} = undef; + $self->{data} = undef; + $self->{rawData} = undef; +} + +1; diff --git a/gorgone/gorgone/class/listener.pm b/gorgone/gorgone/class/listener.pm index 4c523648469..61d5421001d 100644 --- a/gorgone/gorgone/class/listener.pm +++ b/gorgone/gorgone/class/listener.pm @@ -24,6 +24,7 @@ use strict; use warnings; use gorgone::standard::constants qw(:all); use gorgone::standard::library; +use gorgone::class::frame; sub new { my ($class, %options) = @_; @@ -38,23 +39,24 @@ sub new { } sub event_log { - my ($self, %options) = @_; + my ($self) = shift; - return if (!defined($self->{tokens}->{$options{token}})); + return if (!defined($self->{tokens}->{ $_[0]->{token}})); # we want to avoid loop - my $events = $self->{tokens}->{ $options{token} }; - if ($options{code} == GORGONE_ACTION_FINISH_KO || $options{code} == GORGONE_ACTION_FINISH_OK) { - delete $self->{tokens}->{ $options{token} }; + my $events = $self->{tokens}->{ $_[0]->{token} }; + if ($_[0]->{code} == GORGONE_ACTION_FINISH_KO || $_[0]->{code} == GORGONE_ACTION_FINISH_OK) { + delete $self->{tokens}->{ $_[0]->{token} }; } foreach (keys %{$events->{events}}) { - $self->{logger}->writeLogDebug("[listener] trigger event '$options{token}'"); + $self->{logger}->writeLogDebug("[listener] trigger event '$_[0]->{token}'"); + + my $message = '[' . $_ . '] [' . $_[0]->{token} . '] [] { "code": ' . $_[0]->{code} . ', "data": ' . ${$_[0]->{data}} . ' }'; + my $frame = gorgone::class::frame->new(); + $frame->setFrame(\$message); - $self->{gorgone_core}->message_run( - message => '[' . $_ . '] [' . $options{token} . '] [] { "code": ' . $options{code} . ', "data": ' . $options{data} . ' }', - router_type => 'internal' - ); + $self->{gorgone_core}->message_run({ frame => $frame, router_type => 'internal' }); } } @@ -91,10 +93,11 @@ sub check_getlog_token { return if (defined($self->{gorgone_core}->{id}) && $self->{gorgone_core}->{id} == $self->{tokens}->{$options{token}}->{target}); if ((time() - $self->{tokens}->{$options{token}}->{log_pace}) > $self->{tokens}->{$options{token}}->{getlog_last}) { - $self->{gorgone_core}->message_run( - message => "[GETLOG] [] [$self->{tokens}->{$options{token}}->{target}] {}", - router_type => 'internal' - ); + my $message = "[GETLOG] [] [$self->{tokens}->{$options{token}}->{target}] {}"; + my $frame = gorgone::class::frame->new(); + $frame->setFrame(\$message); + + $self->{gorgone_core}->message_run({ frame => $frame, router_type => 'internal' }); $self->{tokens}->{$options{token}}->{getlog_last} = time(); } @@ -107,12 +110,12 @@ sub check { foreach my $token (keys %{$self->{tokens}}) { if (time() - $self->{tokens}->{$token}->{created} > $self->{tokens}->{$token}->{timeout}) { $self->{logger}->writeLogDebug("[listener] delete token '$token': timeout"); - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $self->{gorgone_core}->{db_gorgone}, code => GORGONE_ACTION_FINISH_KO, token => $token, data => '{ "message": "listener token ' . $token . ' timeout reached" }' - ); + }); delete $self->{tokens}->{$token}; next; } diff --git a/gorgone/gorgone/class/lock.pm b/gorgone/gorgone/class/lock.pm index 064aafc1677..6b84e07423a 100644 --- a/gorgone/gorgone/class/lock.pm +++ b/gorgone/gorgone/class/lock.pm @@ -100,9 +100,9 @@ sub new { sub is_set { my $self = shift; - my ($status, $sth) = $self->{dbc}->query( - "SELECT id,running,pid,time_launch FROM cron_operation WHERE name LIKE '$self->{name}'" - ); + my ($status, $sth) = $self->{dbc}->query({ + query => "SELECT id,running,pid,time_launch FROM cron_operation WHERE name LIKE '$self->{name}'" + }); return 1 if ($status == -1); my $data = $sth->fetchrow_hashref(); diff --git a/gorgone/gorgone/class/logger.pm b/gorgone/gorgone/class/logger.pm index f5ea65ecf79..90b13859819 100644 --- a/gorgone/gorgone/class/logger.pm +++ b/gorgone/gorgone/class/logger.pm @@ -194,50 +194,55 @@ sub get_date { } sub writeLog { - my ($self, %options) = @_; + my ($self) = shift; - my $withdate = (defined $options{withdate}) ? $options{withdate} : 1; - my $withseverity = (defined $options{withseverity}) ? $options{withseverity} : 1; + my $withdate = (defined $_[0]->{withdate}) ? $_[0]->{withdate} : 1; + my $withseverity = (defined $_[0]->{withseverity}) ? $_[0]->{withseverity} : 1; - my $msg = $options{message}; - $msg = (($self->{withpid} == 1) ? "$$ - $msg " : $msg); - my $newmsg = ($withseverity) - ? $options{severity_str} . " - $msg" : $msg; - $newmsg = ($withdate) - ? $self->get_date . " - $newmsg" : $newmsg; + if (($self->{severity} & $_[0]->{severity}) == 0) { + return; + } - if (($self->{severity} & $options{severity}) == 0) { + if (length($_[0]->{message}) > 20000) { + $_[0]->{message} = substr($_[0]->{message}, 0, 20000) . '...'; + } + if ($self->{log_mode} == 2) { + syslog($severities{$_[0]->{severity}}, $_[0]->{message}); return; } - chomp($newmsg); + $_[0]->{message} = (($self->{withpid} == 1) ? "$$ - $_[0]->{message} " : $_[0]->{message}); + $_[0]->{message} = ($withseverity) + ? $_[0]->{severity_str} . " - $_[0]->{message}" : $_[0]->{message}; + $_[0]->{message} = ($withdate) + ? $self->get_date . " - $_[0]->{message}" : $_[0]->{message}; + + chomp($_[0]->{message}); if ($self->{log_mode} == 0) { - print "$newmsg\n"; + print "$_[0]->{message}\n"; } elsif ($self->{log_mode} == 1) { if (defined $self->{filehandler}) { - print { $self->{filehandler} } "$newmsg\n"; + print { $self->{filehandler} } "$_[0]->{message}\n"; } - } elsif ($self->{log_mode} == 2) { - syslog($severities{$options{severity}}, $msg); } } sub writeLogDebug { - my ($self, $msg) = @_; + my ($self) = shift; - $self->writeLog(severity => 4, severity_str => 'DEBUG', message => $msg); + $self->writeLog({ severity => 4, severity_str => 'DEBUG', message => $_[0] }); } sub writeLogInfo { - my ($self, $msg) = @_; + my ($self) = shift; - $self->writeLog(severity => 2, severity_str => 'INFO', message => $msg); + $self->writeLog({ severity => 2, severity_str => 'INFO', message => $_[0] }); } sub writeLogError { - my ($self, $msg) = @_; + my ($self) = shift; - $self->writeLog(severity => 1, severity_str => 'ERROR', message => $msg); + $self->writeLog({ severity => 1, severity_str => 'ERROR', message => $_[0] }); } sub DESTROY { diff --git a/gorgone/gorgone/class/module.pm b/gorgone/gorgone/class/module.pm index 1672197e991..042682cbc79 100644 --- a/gorgone/gorgone/class/module.pm +++ b/gorgone/gorgone/class/module.pm @@ -27,10 +27,11 @@ use gorgone::standard::constants qw(:all); use gorgone::standard::library; use gorgone::standard::misc; use gorgone::class::tpapi; +use ZMQ::FFI qw(ZMQ_DONTWAIT); use JSON::XS; use Crypt::Mode::CBC; -use ZMQ::LibZMQ4; -use ZMQ::Constants qw(:all); +use Try::Tiny; +use EV; my %handlers = (DIE => {}); @@ -39,6 +40,11 @@ sub new { my $self = {}; bless $self, $class; + { + local $SIG{__DIE__}; + $self->{zmq_context} = ZMQ::FFI->new(); + } + $self->{internal_socket} = undef; $self->{module_id} = $options{module_id}; $self->{container_id} = $options{container_id}; @@ -55,6 +61,8 @@ sub new { $self->{stop} = 0; $self->{fork} = 0; + $self->{loop} = new EV::Loop(); + $self->{internal_crypt} = { enabled => 0 }; if ($self->get_core_config(name => 'internal_com_crypt') == 1) { $self->{cipher} = Crypt::Mode::CBC->new( @@ -108,6 +116,28 @@ sub set_fork { $self->{fork} = 1; } +sub event { + my ($self, %options) = @_; + + my $socket = defined($options{socket}) ? $options{socket} : $self->{internal_socket}; + while ($socket->has_pollin()) { + my ($message) = $self->read_message(); + next if (!defined($message)); + + $self->{logger}->writeLogDebug("[$self->{module_id}]$self->{container} Event: $message"); + if ($message =~ /^\[(.*?)\]/) { + if ((my $method = $self->can('action_' . lc($1)))) { + $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; + my ($action, $token) = ($1, $2); + my ($rv, $data) = $self->json_decode(argument => $3, token => $token); + next if ($rv); + + $method->($self, token => $token, data => $data); + } + } + } +} + sub get_core_config { my ($self, %options) = @_; @@ -119,24 +149,43 @@ sub get_core_config { sub read_message { my ($self, %options) = @_; - my $message = gorgone::standard::library::zmq_dealer_read_message(socket => defined($options{socket}) ? $options{socket} : $self->{internal_socket}); - return undef if (!defined($message)); - return $message if ($self->{internal_crypt}->{enabled} == 0); + my ($rv, $message) = gorgone::standard::library::zmq_dealer_read_message( + socket => defined($options{socket}) ? $options{socket} : $self->{internal_socket}, + frame => $options{frame} + ); + return (undef, 1) if ($rv); + if ($self->{internal_crypt}->{enabled} == 0) { + if (defined($options{frame})) { + return (undef, 0); + } + return ($message, 0); + } foreach my $key (@{$self->{internal_crypt}->{core_keys}}) { next if (!defined($key)); - my $plaintext; - eval { - $plaintext = $self->{cipher}->decrypt($message, $key, $self->{internal_crypt}->{iv}); - }; - if (defined($plaintext) && $plaintext =~ /^\[[A-Za-z_\-]+?\]/) { - return $plaintext; + if (defined($options{frame})) { + if ($options{frame}->decrypt({ cipher => $self->{cipher}, key => $key, iv => $self->{internal_crypt}->{iv} }) == 0) { + return (undef, 0); + } + } else { + my $plaintext; + try { + $plaintext = $self->{cipher}->decrypt($message, $key, $self->{internal_crypt}->{iv}); + }; + if (defined($plaintext) && $plaintext =~ /^\[[A-Za-z_\-]+?\]/) { + $message = undef; + return ($plaintext, 0); + } } } - $self->{logger}->writeLogError("[$self->{module_id}]$self->{container} decrypt issue: " . ($@ ? $@ : 'no message')); - return undef; + if (defined($options{frame})) { + $self->{logger}->writeLogError("[$self->{module_id}]$self->{container} decrypt issue: " . $options{frame}->getLastError()); + } else { + $self->{logger}->writeLogError("[$self->{module_id}]$self->{container} decrypt issue: " . ($_ ? $_ : 'no message')); + } + return (undef, 1); } sub send_internal_key { @@ -147,33 +196,32 @@ sub send_internal_key { data => { key => unpack('H*', $options{key}) }, json_encode => 1 ); - eval { + try { $message = $self->{cipher}->encrypt($message, $options{encrypt_key}, $self->{internal_crypt}->{iv}); - }; - if ($@) { - $self->{logger}->writeLogError("[$self->{module_id}]$self->{container} encrypt issue: " . $@); + } catch { + $self->{logger}->writeLogError("[$self->{module_id}]$self->{container} encrypt issue: $_"); return -1; - } + }; - zmq_sendmsg($options{socket}, $message, ZMQ_DONTWAIT); + $options{socket}->send($message, ZMQ_DONTWAIT); + $self->event(socket => $options{socket}); return 0; } sub send_internal_action { - my ($self, %options) = @_; - - my $message = $options{message}; - if (!defined($message)) { - $message = gorgone::standard::library::build_protocol( - token => $options{token}, - action => $options{action}, - target => $options{target}, - data => $options{data}, - json_encode => defined($options{data_noencode}) ? undef : 1 + my ($self, $options) = (shift, shift); + + if (!defined($options->{message})) { + $options->{message} = gorgone::standard::library::build_protocol( + token => $options->{token}, + action => $options->{action}, + target => $options->{target}, + data => $options->{data}, + json_encode => defined($options->{data_noencode}) ? undef : 1 ); } - my $socket = defined($options{socket}) ? $options{socket} : $self->{internal_socket}; + my $socket = defined($options->{socket}) ? $options->{socket} : $self->{internal_socket}; if ($self->{internal_crypt}->{enabled} == 1) { my $identity = gorgone::standard::library::zmq_get_routing_id(socket => $socket); @@ -199,16 +247,16 @@ sub send_internal_action { $key = $self->{internal_crypt}->{identity_keys}->{$identity}->{key}; } - eval { - $message = $self->{cipher}->encrypt($message, $key, $self->{internal_crypt}->{iv}); - }; - if ($@) { - $self->{logger}->writeLogError("[$self->{module_id}]$self->{container} encrypt issue: " . $@); + try { + $options->{message} = $self->{cipher}->encrypt($options->{message}, $key, $self->{internal_crypt}->{iv}); + } catch { + $self->{logger}->writeLogError("[$self->{module_id}]$self->{container} encrypt issue: $_"); return undef; - } + }; } - zmq_sendmsg($socket, $message, ZMQ_DONTWAIT); + $socket->send($options->{message}, ZMQ_DONTWAIT); + $self->event(socket => $socket); } sub send_log_msg_error { @@ -217,13 +265,13 @@ sub send_log_msg_error { return if (!defined($options{token})); $self->{logger}->writeLogError("[$self->{module_id}]$self->{container} -$options{subname}- $options{number} $options{message}"); - $self->send_internal_action( + $self->send_internal_action({ socket => (defined($options{socket})) ? $options{socket} : $self->{internal_socket}, action => 'PUTLOG', token => $options{token}, data => { code => GORGONE_ACTION_FINISH_KO, etime => time(), instant => $options{instant}, token => $options{token}, data => { message => $options{message} } }, json_encode => 1 - ); + }); } sub send_log { @@ -233,26 +281,25 @@ sub send_log { return if (defined($options{logging}) && $options{logging} =~ /^(?:false|0)$/); - $self->send_internal_action( + $self->send_internal_action({ socket => (defined($options{socket})) ? $options{socket} : $self->{internal_socket}, action => 'PUTLOG', token => $options{token}, data => { code => $options{code}, etime => time(), instant => $options{instant}, token => $options{token}, data => $options{data} }, json_encode => 1 - ); + }); } sub json_encode { my ($self, %options) = @_; my $encoded_arguments; - eval { + try { $encoded_arguments = JSON::XS->new->encode($options{argument}); - }; - if ($@) { - $self->{logger}->writeLogError("[$self->{module_id}]$self->{container} $options{method} - cannot encode json: $@"); + } catch { + $self->{logger}->writeLogError("[$self->{module_id}]$self->{container} $options{method} - cannot encode json: $_"); return 1; - } + }; return (0, $encoded_arguments); } @@ -261,11 +308,10 @@ sub json_decode { my ($self, %options) = @_; my $decoded_arguments; - eval { + try { $decoded_arguments = JSON::XS->new->decode($options{argument}); - }; - if ($@) { - $self->{logger}->writeLogError("[$self->{module_id}]$self->{container} $options{method} - cannot decode json: $@"); + } catch { + $self->{logger}->writeLogError("[$self->{module_id}]$self->{container} $options{method} - cannot decode json: $_"); if (defined($options{token})) { $self->send_log( code => GORGONE_ACTION_FINISH_KO, @@ -274,7 +320,7 @@ sub json_decode { ); } return 1; - } + }; return (0, $decoded_arguments); } @@ -310,11 +356,16 @@ sub change_macros { sub action_bcastlogger { my ($self, %options) = @_; - if (defined($options{data}->{content}->{severity}) && $options{data}->{content}->{severity} ne '') { - if ($options{data}->{content}->{severity} eq 'default') { + my $data = $options{data}; + if (defined($options{frame})) { + $data = $options{frame}->decodeData(); + } + + if (defined($data->{content}->{severity}) && $data->{content}->{severity} ne '') { + if ($data->{content}->{severity} eq 'default') { $self->{logger}->set_default_severity(); } else { - $self->{logger}->severity($options{data}->{content}->{severity}); + $self->{logger}->severity($data->{content}->{severity}); } } } @@ -324,10 +375,15 @@ sub action_bcastcorekey { return if ($self->{internal_crypt}->{enabled} == 0); - if (defined($options{data}->{key})) { + my $data = $options{data}; + if (defined($options{frame})) { + $data = $options{frame}->decodeData(); + } + + if (defined($data->{key})) { $self->{logger}->writeLogDebug("[$self->{module_id}]$self->{container} core key changed"); $self->{internal_crypt}->{core_keys}->[1] = $self->{internal_crypt}->{core_keys}->[0]; - $self->{internal_crypt}->{core_keys}->[0] = pack('H*', $options{data}->{key}); + $self->{internal_crypt}->{core_keys}->[0] = pack('H*', $data->{key}); } } diff --git a/gorgone/gorgone/class/sqlquery.pm b/gorgone/gorgone/class/sqlquery.pm index 045cb901928..e9a84675913 100644 --- a/gorgone/gorgone/class/sqlquery.pm +++ b/gorgone/gorgone/class/sqlquery.pm @@ -47,7 +47,7 @@ sub do { my ($self, %options) = @_; my $mode = defined($options{mode}) ? $options{mode} : 0; - my ($status, $sth) = $self->{db_centreon}->query($options{request}); + my ($status, $sth) = $self->{db_centreon}->query({ query => $options{request}, bind_values => $options{bind_values} }); if ($status == -1) { return (-1, undef); } @@ -90,12 +90,17 @@ sub transaction_query_multi { $status = $self->transaction_mode(1); return -1 if ($status == -1); - ($status, $sth) = $self->{db_centreon}->query($options{request}, prepare_only => 1); + ($status, $sth) = $self->{db_centreon}->query({ query => $options{request}, prepare_only => 1 }); if ($status == -1) { $self->rollback(); return -1; } - $sth->execute(); + + if (defined($options{bind_values}) && scalar(@{$options{bind_values}}) > 0) { + $sth->execute(@{$options{bind_values}}); + } else { + $sth->execute(); + } do { if ($sth->err) { $self->rollback(); @@ -129,12 +134,6 @@ sub transaction_query { return 0; } -sub quote { - my ($self, %options) = @_; - - return $self->{db_centreon}->quote($options{value}); -} - sub transaction_mode { my ($self) = @_; diff --git a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm index 2202045dbca..c29fc615f41 100644 --- a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm +++ b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm @@ -28,11 +28,10 @@ use gorgone::standard::library; use gorgone::standard::constants qw(:all); use gorgone::class::sqlquery; use gorgone::class::http::http; -use ZMQ::LibZMQ4; -use ZMQ::Constants qw(:all); use JSON::XS; use IO::Compress::Bzip2; use MIME::Base64; +use EV; my %handlers = (TERM => {}, HUP => {}); my ($connector); @@ -254,18 +253,22 @@ sub save_centreon_previous_register { my ($self, %options) = @_; my ($query, $query_append) = ('', ''); + my @bind_values = (); foreach (keys %{$self->{unregister_metrics_centreon}}) { $query .= $query_append . 'UPDATE mod_anomaly_service SET' . - ' saas_model_id = ' . $self->{class_object_centreon}->quote(value => $self->{unregister_metrics_centreon}->{$_}->{saas_model_id}) . ',' . - ' saas_metric_id = ' . $self->{class_object_centreon}->quote(value => $self->{unregister_metrics_centreon}->{$_}->{saas_metric_id}) . ',' . - ' saas_creation_date = ' . $self->{unregister_metrics_centreon}->{$_}->{creation_date} . ',' . - ' saas_update_date = ' . $self->{unregister_metrics_centreon}->{$_}->{creation_date} . - ' WHERE `id` = ' . $_; + ' saas_model_id = ?,' . + ' saas_metric_id = ?,' . + ' saas_creation_date = ?, ' . + ' saas_update_date = ?' + ' WHERE `id` = ?'; $query_append = ';'; + push @bind_values, $self->{unregister_metrics_centreon}->{$_}->{saas_model_id}, $self->{unregister_metrics_centreon}->{$_}->{saas_metric_id}, + $self->{unregister_metrics_centreon}->{$_}->{creation_date}, $self->{unregister_metrics_centreon}->{$_}->{creation_date}, $_; } + if ($query ne '') { - my $status = $self->{class_object_centreon}->transaction_query_multi(request => $query); + my $status = $self->{class_object_centreon}->transaction_query_multi(request => $query, bind_values => \@bind_values); if ($status == -1) { $self->{logger}->writeLogError('[anomalydetection] -class- database: cannot save centreon previous register'); return 1; @@ -288,6 +291,7 @@ sub saas_register_metrics { my $register_centreon_metrics = {}; my ($query, $query_append) = ('', ''); + my @bind_values = (); $self->{generate_metrics_lua} = 0; foreach (keys %{$self->{centreon_metrics}}) { @@ -360,17 +364,19 @@ sub saas_register_metrics { $query .= $query_append . 'UPDATE mod_anomaly_service SET' . - ' saas_model_id = ' . $self->{class_object_centreon}->quote(value => $register_centreon_metrics->{$_}->{saas_model_id}) . ',' . - ' saas_metric_id = ' . $self->{class_object_centreon}->quote(value => $register_centreon_metrics->{$_}->{saas_metric_id}) . ',' . - ' saas_creation_date = ' . $register_centreon_metrics->{$_}->{saas_creation_date} . ',' . - ' saas_update_date = ' . $register_centreon_metrics->{$_}->{saas_creation_date} . - ' WHERE `id` = ' . $_; + ' saas_model_id = ?,' . + ' saas_metric_id = ?,' . + ' saas_creation_date = ?,' . + ' saas_update_date = ?' . + ' WHERE `id` = ?'; $query_append = ';'; + push @bind_values, $register_centreon_metrics->{$_}->{saas_model_id}, $register_centreon_metrics->{$_}->{saas_metric_id}, + $register_centreon_metrics->{$_}->{saas_creation_date}, $register_centreon_metrics->{$_}->{saas_creation_date}, $_; } return 0 if ($query eq ''); - my $status = $self->{class_object_centreon}->transaction_query_multi(request => $query); + my $status = $self->{class_object_centreon}->transaction_query_multi(request => $query, bind_values => \@bind_values); if ($status == -1) { $self->{unregister_metrics_centreon} = $register_centreon_metrics; $self->{logger}->writeLogError('[anomalydetection] -class- database: cannot update centreon register'); @@ -465,13 +471,13 @@ sub generate_lua_filter_file { $self->{logger}->writeLogDebug('[anomalydetection] -class- reload centreon-broker'); - $self->send_internal_action( + $self->send_internal_action({ action => 'COMMAND', token => $options{token}, data => { content => [ { command => 'sudo ' . $poller->{broker_reload_command} } ] - }, - ); + } + }); return 0; } @@ -522,14 +528,14 @@ sub saas_get_predicts { $encoded_content = MIME::Base64::encode_base64($encoded_content, ''); my $poller = $self->get_poller(instance => $self->{centreon_metrics}->{$_}->{instance_id}); - $self->send_internal_action( + $self->send_internal_action({ action => 'COMMAND', target => $self->{centreon_metrics}->{$_}->{instance_id}, token => $options{token}, data => { content => [ { command => 'mkdir -p ' . $poller->{cfg_dir} . '/anomaly/' . '; echo -n ' . $encoded_content . ' | base64 -d | bzcat -d > "' . $poller->{cfg_dir} . '/anomaly/' . $_ . '.json"' } ] } - ); + }); $engine_reload->{ $self->{centreon_metrics}->{$_}->{instance_id} } = [] if (!defined($engine_reload->{ $self->{centreon_metrics}->{$_}->{instance_id} })); push @{$engine_reload->{ $self->{centreon_metrics}->{$_}->{instance_id} }}, $poller->{cfg_dir} . '/anomaly/' . $_ . '.json'; @@ -554,13 +560,13 @@ sub saas_get_predicts { }; } - $self->send_internal_action( + $self->send_internal_action({ action => 'CENTREONCOMMAND', token => $options{token}, data => { content => $contents } - ); + }); } $status = $self->{class_object_centreon}->transaction_query_multi(request => $query); @@ -627,22 +633,16 @@ sub action_saasregister { return 0; } -sub event { - while (1) { - my $message = $connector->read_message(); - last if (!defined($message)); - - $connector->{logger}->writeLogDebug("[anomalydetection] Event: $message"); - if ($message =~ /^\[(.*?)\]/) { - if ((my $method = $connector->can('action_' . lc($1)))) { - $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; - my ($action, $token) = ($1, $2); - my ($rv, $data) = $connector->json_decode(argument => $3, token => $token); - next if ($rv); +sub periodic_exec { + if ($connector->{stop} == 1) { + $connector->{logger}->writeLogInfo("[anomalydetection] -class- $$ has quit"); + exit(0); + } - $method->($connector, token => $token, data => $data); - } - } + if (time() - $connector->{resync_time} > $connector->{last_resync_time}) { + $connector->{last_resync_time} = time(); + $connector->action_saasregister(); + $connector->action_saaspredict(); } } @@ -657,43 +657,25 @@ sub run { logger => $self->{logger} ); - ##### Load objects ##### $self->{class_object_centreon} = gorgone::class::sqlquery->new(logger => $self->{logger}, db_centreon => $self->{db_centreon}); $self->{http} = gorgone::class::http::http->new(logger => $self->{logger}); - # Connect internal - $connector->{internal_socket} = gorgone::standard::library::connect_com( + $self->{internal_socket} = gorgone::standard::library::connect_com( + context => $self->{zmq_context}, zmq_type => 'ZMQ_DEALER', name => 'gorgone-anomalydetection', logger => $self->{logger}, type => $self->get_core_config(name => 'internal_com_type'), path => $self->get_core_config(name => 'internal_com_path') ); - $connector->send_internal_action( + $self->send_internal_action({ action => 'CENTREONADREADY', data => {} - ); - $self->{poll} = [ - { - socket => $connector->{internal_socket}, - events => ZMQ_POLLIN, - callback => \&event, - } - ]; - while (1) { - my $rev = scalar(zmq_poll($self->{poll}, 5000)); - if ($rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("[anomalydetection] -class- $$ has quit"); - zmq_close($connector->{internal_socket}); - exit(0); - } + }); - if (time() - $self->{resync_time} > $self->{last_resync_time}) { - $self->{last_resync_time} = time(); - $self->action_saasregister(); - $self->action_saaspredict(); - } - } + my $watcher_timer = $self->{loop}->timer(5, 5, \&periodic_exec); + my $watcher_io = $self->{loop}->io($connector->{internal_socket}->get_fd(), EV::READ, sub { $connector->event() } ); + $self->{loop}->run(); } 1; diff --git a/gorgone/gorgone/modules/centreon/anomalydetection/hooks.pm b/gorgone/gorgone/modules/centreon/anomalydetection/hooks.pm index c2b9b6b739a..479287383ca 100644 --- a/gorgone/gorgone/modules/centreon/anomalydetection/hooks.pm +++ b/gorgone/gorgone/modules/centreon/anomalydetection/hooks.pm @@ -64,20 +64,20 @@ sub routing { } if (gorgone::class::core::waiting_ready(ready => \$process->{ready}) == 0) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'gorgone-anomalydetection: still no ready' }, json_encode => 1 - ); + }); return undef; } $options{gorgone}->send_internal_message( identity => 'gorgone-anomalydetection', action => $options{action}, - data => $options{data}, + raw_data_ref => $options{frame}->getRawData(), token => $options{token} ); } diff --git a/gorgone/gorgone/modules/centreon/audit/class.pm b/gorgone/gorgone/modules/centreon/audit/class.pm index 75bbc10001e..b579299e72a 100644 --- a/gorgone/gorgone/modules/centreon/audit/class.pm +++ b/gorgone/gorgone/modules/centreon/audit/class.pm @@ -28,8 +28,7 @@ use gorgone::standard::library; use gorgone::standard::constants qw(:all); use gorgone::standard::misc; use gorgone::class::sqlquery; -use ZMQ::LibZMQ4; -use ZMQ::Constants qw(:all); +use EV; my %handlers = (TERM => {}, HUP => {}); my ($connector); @@ -248,7 +247,7 @@ sub action_centreonauditschedule { nodes => {} }; foreach (@$datas) { - $self->send_internal_action( + $self->send_internal_action({ action => 'ADDLISTENER', data => [ { @@ -258,8 +257,8 @@ sub action_centreonauditschedule { timeout => 300 } ] - ); - $self->send_internal_action( + }); + $self->send_internal_action({ action => 'CENTREONAUDITNODE', target => $_->[0], token => 'audit-' . $options{token} . '-' . $_->[0], @@ -267,7 +266,7 @@ sub action_centreonauditschedule { instant => 1, content => $params } - ); + }); $self->{audit_tokens}->{ $options{token} }->{nodes}->{$_->[0]} = { name => $_->[1], @@ -280,25 +279,6 @@ sub action_centreonauditschedule { return 0; } -sub event { - while (1) { - my $message = $connector->read_message(); - last if (!defined($message)); - - $connector->{logger}->writeLogDebug("[audit] Event: $message"); - if ($message =~ /^\[(.*?)\]/) { - if ((my $method = $connector->can('action_' . lc($1)))) { - $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; - my ($action, $token) = ($1, $2); - my ($rv, $data) = $connector->json_decode(argument => $3, token => $token); - next if ($rv); - - $method->($connector, token => $token, data => $data); - } - } - } -} - sub sampling { my ($self, %options) = @_; @@ -334,21 +314,30 @@ sub get_system { } } +sub periodic_exec { + if ($connector->{stop} == 1) { + $connector->{logger}->writeLogInfo("[audit] $$ has quit"); + exit(0); + } + + $connector->sampling(); +} + sub run { my ($self, %options) = @_; - # Connect internal - $connector->{internal_socket} = gorgone::standard::library::connect_com( + $self->{internal_socket} = gorgone::standard::library::connect_com( + context => $self->{zmq_context}, zmq_type => 'ZMQ_DEALER', name => 'gorgone-audit', logger => $self->{logger}, type => $self->get_core_config(name => 'internal_com_type'), path => $self->get_core_config(name => 'internal_com_path') ); - $connector->send_internal_action( + $self->send_internal_action({ action => 'CENTREONAUDITREADY', data => {} - ); + }); if (defined($self->{config_db_centreon})) { $self->{db_centreon} = gorgone::class::db->new( @@ -375,23 +364,9 @@ sub run { $self->load_modules(); $self->get_system(); - $self->{poll} = [ - { - socket => $connector->{internal_socket}, - events => ZMQ_POLLIN, - callback => \&event, - } - ]; - while (1) { - my $rev = scalar(zmq_poll($self->{poll}, 5000)); - if ($rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("[audit] $$ has quit"); - zmq_close($connector->{internal_socket}); - exit(0); - } - - $self->sampling(); - } + my $watcher_timer = $self->{loop}->timer(5, 5, \&periodic_exec); + my $watcher_io = $self->{loop}->io($connector->{internal_socket}->get_fd(), EV::READ, sub { $connector->event() } ); + $self->{loop}->run(); } 1; diff --git a/gorgone/gorgone/modules/centreon/audit/hooks.pm b/gorgone/gorgone/modules/centreon/audit/hooks.pm index 1118c65c244..b95de9dedd1 100644 --- a/gorgone/gorgone/modules/centreon/audit/hooks.pm +++ b/gorgone/gorgone/modules/centreon/audit/hooks.pm @@ -66,20 +66,20 @@ sub routing { } if (gorgone::class::core::waiting_ready(ready => \$audit->{ready}) == 0) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'gorgone-audit: still no ready' }, json_encode => 1 - ); + }); return undef; } $options{gorgone}->send_internal_message( identity => 'gorgone-audit', action => $options{action}, - data => $options{data}, + raw_data_ref => $options{frame}->getRawData(), token => $options{token} ); } diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm index beeab52eaca..6d4e7bdad2d 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm @@ -30,12 +30,13 @@ use gorgone::modules::centreon::autodiscovery::services::discovery; use gorgone::class::tpapi::clapi; use gorgone::class::tpapi::centreonv2; use gorgone::class::sqlquery; -use ZMQ::LibZMQ4; -use ZMQ::Constants qw(:all); +use gorgone::class::frame; use JSON::XS; use Time::HiRes; use POSIX qw(strftime); use Digest::MD5 qw(md5_hex); +use Try::Tiny; +use EV; use constant JOB_SCHEDULED => 0; use constant JOB_FINISH => 1; @@ -149,7 +150,7 @@ sub hdisco_add_cron { return (1, "missing 'cron_definition' parameter"); } - $self->send_internal_action( + $self->send_internal_action({ action => 'ADDLISTENER', data => [ { @@ -158,7 +159,7 @@ sub hdisco_add_cron { token => 'cron-' . $options{discovery_token} } ] - ); + }); $self->{logger}->writeLogDebug("[autodiscovery] -class- host discovery - add cron for job '" . $options{job}->{job_id} . "'"); my $definition = { @@ -170,13 +171,13 @@ sub hdisco_add_cron { timeout => (defined($options{job}->{timeout}) && $options{job}->{timeout} =~ /(\d+)/) ? $1 : $self->{global_timeout} } }; - $self->send_internal_action( + $self->send_internal_action({ action => 'ADDCRON', token => 'cron-' . $options{discovery_token}, data => { content => [ $definition ] } - ); + }); return 0; } @@ -302,7 +303,7 @@ sub get_host_job { my ($status, $results) = $self->{tpapi_centreonv2}->get_scheduling_jobs(search => '{"id": ' . $options{job_id} . '}'); if ($status != 0) { - return (1, "cannot get host discovery job '$options{data}->{content}->{job_id}' - " . $self->{tpapi_centreonv2}->error()); + return (1, "cannot get host discovery job '$options{job_id}' - " . $self->{tpapi_centreonv2}->error()); } my $job; @@ -328,13 +329,13 @@ sub hdisco_delete_cron { $self->{logger}->writeLogInfo("[autodiscovery] -class- host discovery - delete job '" . $job_id . "'"); - $self->send_internal_action( + $self->send_internal_action({ action => 'DELETECRON', token => $options{token}, data => { variables => [ $options{discovery_token} ] } - ); + }); } sub action_addhostdiscoveryjob { @@ -352,51 +353,53 @@ sub action_addhostdiscoveryjob { return ; } + my $data = $options{frame}->getData(); + my ($status, $message, $job); - ($status, $message, $job) = $self->get_host_job(job_id => $options{data}->{content}->{job_id}); + ($status, $message, $job) = $self->get_host_job(job_id => $data->{content}->{job_id}); if ($status != 0) { - $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - cannot get host discovery job '$options{data}->{content}->{job_id}' - " . $self->{tpapi_centreonv2}->error()); + $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - cannot get host discovery job '$data->{content}->{job_id}' - " . $self->{tpapi_centreonv2}->error()); $self->send_log( code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { - message => "cannot get job '$options{data}->{content}->{job_id}'" + message => "cannot get job '$data->{content}->{job_id}'" } ); return 1; } if (!defined($job)) { - $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - cannot get host discovery job '$options{data}->{content}->{job_id}' - " . $self->{tpapi_centreonv2}->error()); + $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - cannot get host discovery job '$data->{content}->{job_id}' - " . $self->{tpapi_centreonv2}->error()); $self->send_log( code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { - message => "cannot get job '$options{data}->{content}->{job_id}'" + message => "cannot get job '$data->{content}->{job_id}'" } ); return 1; } - $job->{timeout} = $options{data}->{content}->{timeout}; + $job->{timeout} = $data->{content}->{timeout}; ($status, $message) = $self->hdisco_addupdate_job(job => $job); if ($status) { - $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - add job '$options{data}->{content}->{job_id}' - $message"); + $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - add job '$data->{content}->{job_id}' - $message"); $self->send_log( code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { - message => "add job '$options{data}->{content}->{job_id}' - $message" + message => "add job '$data->{content}->{job_id}' - $message" } ); return 1; } # Launch a immediate job. - if ($self->{hdisco_jobs_ids}->{ $options{data}->{content}->{job_id} }->{execution}->{mode} == EXECUTION_MODE_IMMEDIATE) { + if ($self->{hdisco_jobs_ids}->{ $data->{content}->{job_id} }->{execution}->{mode} == EXECUTION_MODE_IMMEDIATE) { ($status, $message) = $self->launchhostdiscovery( - job_id => $options{data}->{content}->{job_id}, - timeout => $options{data}->{content}->{timeout}, + job_id => $data->{content}->{job_id}, + timeout => $data->{content}->{timeout}, source => 'immediate' ); if ($status) { @@ -415,7 +418,7 @@ sub action_addhostdiscoveryjob { code => GORGONE_ACTION_FINISH_OK, token => $options{token}, data => { - message => 'job ' . $options{data}->{content}->{job_id} . ' added' + message => 'job ' . $data->{content}->{job_id} . ' added' } ); @@ -461,7 +464,7 @@ sub launchhostdiscovery { $self->{hdisco_jobs_ids}->{$job_id}->{status} = JOB_RUNNING; my $timeout = (defined($options{timeout}) && $options{timeout} =~ /(\d+)/) ? $1 : $self->{global_timeout}; - $self->send_internal_action( + $self->send_internal_action({ action => 'ADDLISTENER', data => [ { @@ -473,9 +476,9 @@ sub launchhostdiscovery { log_pace => $self->{check_interval} } ] - ); + }); - $self->send_internal_action( + $self->send_internal_action({ action => 'COMMAND', target => $self->{hdisco_jobs_ids}->{$job_id}->{target}, token => $self->{hdisco_jobs_ids}->{$job_id}->{token}, @@ -492,7 +495,7 @@ sub launchhostdiscovery { } ] } - ); + }); return (0, "job '$job_id' launched"); } @@ -512,14 +515,16 @@ sub action_launchhostdiscovery { return ; } + my $data = $options{frame}->getData(); + my ($job_id, $timeout, $source); - if (defined($options{data}->{variables}->[0]) && - defined($options{data}->{variables}->[1]) && $options{data}->{variables}->[1] eq 'schedule') { - $job_id = $options{data}->{variables}->[0]; + if (defined($data->{variables}->[0]) && + defined($data->{variables}->[1]) && $data->{variables}->[1] eq 'schedule') { + $job_id = $data->{variables}->[0]; $source = 'immediate'; - } elsif (defined($options{data}->{content}->{job_id})) { - $job_id = $options{data}->{content}->{job_id}; - $timeout = $options{data}->{content}->{timeout}; + } elsif (defined($data->{content}->{job_id})) { + $job_id = $data->{content}->{job_id}; + $timeout = $data->{content}->{timeout}; $source = 'cron'; } @@ -593,17 +598,19 @@ sub action_launchhostdiscovery { sub discovery_postcommand_result { my ($self, %options) = @_; - return 1 if (!defined($options{data}->{data}->{metadata}->{job_id})); + my $data = $options{frame}->getData(); + + return 1 if (!defined($data->{data}->{metadata}->{job_id})); - my $job_id = $options{data}->{data}->{metadata}->{job_id}; + my $job_id = $data->{data}->{metadata}->{job_id}; if (!defined($self->{hdisco_jobs_ids}->{$job_id})) { $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - found result for inexistant job '" . $job_id . "'"); return 1; } - my $exit_code = $options{data}->{data}->{result}->{exit_code}; - my $output = (defined($options{data}->{data}->{result}->{stderr}) && $options{data}->{data}->{result}->{stderr} ne '') ? - $options{data}->{data}->{result}->{stderr} : $options{data}->{data}->{result}->{stdout}; + my $exit_code = $data->{data}->{result}->{exit_code}; + my $output = (defined($data->{data}->{result}->{stderr}) && $data->{data}->{result}->{stderr} ne '') ? + $data->{data}->{result}->{stderr} : $data->{data}->{result}->{stdout}; if ($exit_code != 0) { $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - execute discovery postcommand failed job '$job_id'"); @@ -623,12 +630,75 @@ sub discovery_postcommand_result { ); } +sub discovery_add_host_result { + my ($self, %options) = @_; + + if ($options{builder}->{num_lines} == MAX_INSERT_BY_QUERY) { + my ($status) = $self->{class_object_centreon}->custom_execute( + request => $options{builder}->{query} . $options{builder}->{values}, + bind_values => $options{builder}->{bind_values} + ); + if ($status == -1) { + $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - failed to insert job '$options{job_id}' results"); + $self->update_job_status( + job_id => $options{job_id}, + status => JOB_FAILED, + message => 'Failed to insert job results' + ); + return 1; + } + $options{builder}->{num_lines} = 0; + $options{builder}->{values} = ''; + $options{builder}->{append} = ''; + $options{builder}->{bind_values} = (); + } + + # Generate uuid based on attributs + my $uuid_char = ''; + foreach (@{$options{uuid_parameters}}) { + $uuid_char .= $options{host}->{$_} if (defined($options{host}->{$_}) && $options{host}->{$_} ne ''); + } + my $ctx = Digest::MD5->new; + $ctx->add($uuid_char); + my $digest = $ctx->hexdigest; + my $uuid = substr($digest, 0, 8) . '-' . substr($digest, 8, 4) . '-' . substr($digest, 12, 4) . '-' . + substr($digest, 16, 4) . '-' . substr($digest, 20, 12); + my $encoded_host = JSON::XS->new->encode($options{host}); + + # Build bulk insert + $options{builder}->{values} .= $options{builder}->{append} . '(?, ?, ?)'; + $options{builder}->{append} = ', '; + push @{$options{builder}->{bind_values}}, $options{job_id}, $encoded_host, $uuid; + $options{builder}->{num_lines}++; + $options{builder}->{total_lines}++; + + return 0; +} + sub discovery_command_result { my ($self, %options) = @_; - return 1 if (!defined($options{data}->{data}->{metadata}->{job_id})); +=pod + use Devel::Size; + print "frame = " . (Devel::Size::total_size($options{frame}) / 1024 / 1024) . "==\n"; + + my $data = $options{frame}->getData(); + print "data = " . (Devel::Size::total_size($data) / 1024 / 1024) . "==\n"; + + my $frame = $options{frame}->getFrame(); + print "frame data = " . (Devel::Size::total_size($frame) / 1024 / 1024) . "==\n"; - my $job_id = $options{data}->{data}->{metadata}->{job_id}; + my $raw = $options{frame}->getRawData(); + print "raw data = " . (Devel::Size::total_size($raw) / 1024 / 1024) . "==\n"; + + return 1; +=cut + + my $data = $options{frame}->getData(); + + return 1 if (!defined($data->{data}->{metadata}->{job_id})); + + my $job_id = $data->{data}->{metadata}->{job_id}; if (!defined($self->{hdisco_jobs_ids}->{$job_id})) { $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - found result for inexistant job '" . $job_id . "'"); return 1; @@ -636,38 +706,22 @@ sub discovery_command_result { $self->{logger}->writeLogInfo("[autodiscovery] -class- host discovery - found result for job '" . $job_id . "'"); my $uuid_parameters = $self->{hdisco_jobs_ids}->{$job_id}->{uuid_parameters}; - my $exit_code = $options{data}->{data}->{result}->{exit_code}; - my $output = (defined($options{data}->{data}->{result}->{stderr}) && $options{data}->{data}->{result}->{stderr} ne '') ? - $options{data}->{data}->{result}->{stderr} : $options{data}->{data}->{result}->{stdout}; + my $exit_code = $data->{data}->{result}->{exit_code}; if ($exit_code != 0) { $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - execute discovery plugin failed job '$job_id'"); $self->update_job_status( job_id => $job_id, status => JOB_FAILED, - message => $output - ); - return 1; - } - - my $result; - eval { - $result = JSON::XS->new->decode($output); - }; - - if ($@) { - $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - failed to decode discovery plugin response job '$job_id'"); - $self->update_job_status( - job_id => $job_id, - status => JOB_FAILED, - message => 'Failed to decode discovery plugin response' + message => (defined($data->{data}->{result}->{stderr}) && $data->{data}->{result}->{stderr} ne '') ? + $data->{data}->{result}->{stderr} : $data->{data}->{result}->{stdout} ); return 1; } # Delete previous results - my $query = "DELETE FROM mod_host_disco_host WHERE job_id = " . $self->{class_object_centreon}->quote(value => $job_id); - my ($status) = $self->{class_object_centreon}->custom_execute(request => $query); + my $query = "DELETE FROM mod_host_disco_host WHERE job_id = ?"; + my ($status) = $self->{class_object_centreon}->custom_execute(request => $query, bind_values => [$job_id]); if ($status == -1) { $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - failed to delete previous job '$job_id' results"); $self->update_job_status( @@ -679,49 +733,45 @@ sub discovery_command_result { } # Add new results - my $number_of_lines = 0; - my $values = ''; - my $append = ''; - $query = "INSERT INTO mod_host_disco_host (job_id, discovery_result, uuid) VALUES "; - foreach my $host (@{$result->{results}}) { - if ($number_of_lines == MAX_INSERT_BY_QUERY) { - ($status) = $self->{class_object_centreon}->custom_execute(request => $query . $values); - if ($status == -1) { - $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - failed to insert job '$job_id' results"); - $self->update_job_status( - job_id => $job_id, - status => JOB_FAILED, - message => 'Failed to insert job results' - ); - return 1; + my $builder = { + query => "INSERT INTO mod_host_disco_host (job_id, discovery_result, uuid) VALUES ", + num_lines => 0, + total_lines => 0, + values => '', + append => '', + bind_values => [] + }; + my $duration = 0; + + try { + my $json = JSON::XS->new(); + $json->incr_parse($data->{data}->{result}->{stdout}); + while (my $obj = $json->incr_parse()) { + if (ref($obj) eq 'HASH') { + foreach my $host (@{$obj->{results}}) { + my $rv = $self->discovery_add_host_result(host => $host, job_id => $job_id, uuid_parameters => $uuid_parameters, builder => $builder); + return 1 if ($rv); + } + $duration = $obj->{duration}; + } elsif (ref($obj) eq 'ARRAY') { + foreach my $host (@$obj) { + my $rv = $self->discovery_add_host_result(host => $host, job_id => $job_id, uuid_parameters => $uuid_parameters, builder => $builder); + return 1 if ($rv); + } } - $number_of_lines = 0; - $values = ''; - $append = ''; - } - - # Generate uuid based on attributs - my $uuid_char = ''; - foreach (@$uuid_parameters) { - $uuid_char .= $host->{$_} if (defined($host->{$_}) && $host->{$_} ne ''); } - my $ctx = Digest::MD5->new; - $ctx->add($uuid_char); - my $digest = $ctx->hexdigest; - my $uuid = substr($digest, 0, 8) . '-' . substr($digest, 8, 4) . '-' . substr($digest, 12, 4) . '-' . - substr($digest, 16, 4) . '-' . substr($digest, 20, 12); - my $encoded_host = JSON::XS->new->encode($host); - - # Build bulk insert - $values .= $append . "(" . $self->{class_object_centreon}->quote(value => $job_id) . ", " . - $self->{class_object_centreon}->quote(value => $encoded_host) . ", " . - $self->{class_object_centreon}->quote(value => $uuid) . ")"; - $append = ', '; - $number_of_lines++; - } + } catch { + $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - failed to decode discovery plugin response job '$job_id'"); + $self->update_job_status( + job_id => $job_id, + status => JOB_FAILED, + message => 'Failed to decode discovery plugin response' + ); + return 1; + }; - if ($values ne '') { - ($status) = $self->{class_object_centreon}->custom_execute(request => $query . $values); + if ($builder->{values} ne '') { + ($status) = $self->{class_object_centreon}->custom_execute(request => $builder->{query} . $builder->{values}, bind_values => $builder->{bind_values}); if ($status == -1) { $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - failed to insert job '$job_id' results"); $self->update_job_status( @@ -738,7 +788,7 @@ sub discovery_command_result { $self->{logger}->writeLogDebug("[autodiscovery] -class- host discovery - execute post command job '$job_id'"); my $post_command = $self->{hdisco_jobs_ids}->{$job_id}->{post_execution}->{commands}->[0]; - $self->send_internal_action( + $self->send_internal_action({ action => $post_command->{action}, token => $self->{hdisco_jobs_ids}->{$job_id}->{token}, data => { @@ -753,7 +803,7 @@ sub discovery_command_result { } ] } - ); + }); } $self->{logger}->writeLogDebug("[autodiscovery] -class- host discovery - finished discovery command job '$job_id'"); @@ -761,8 +811,8 @@ sub discovery_command_result { job_id => $job_id, status => JOB_FINISH, message => 'Finished', - duration => $result->{duration}, - discovered_items => $result->{discovered_items} + duration => $duration, + discovered_items => $builder->{total_lines} ); return 0; @@ -785,7 +835,9 @@ sub action_deletehostdiscoveryjob { return ; } - my $discovery_token = $options{data}->{variables}->[0]; + my $data = $options{frame}->getData(); + + my $discovery_token = $data->{variables}->[0]; my $job_id = (defined($discovery_token) && defined($self->{hdisco_jobs_tokens}->{$discovery_token})) ? $self->{hdisco_jobs_tokens}->{$discovery_token} : undef; if (!defined($discovery_token) || $discovery_token eq '') { @@ -856,21 +908,24 @@ sub update_job_information { return 1 if (!defined($options{values}) || ref($options{values}) ne 'HASH' || !keys %{$options{values}}); my $query = "UPDATE mod_host_disco_job SET "; + my @bind_values = (); my $append = ''; foreach (keys %{$options{values}}) { - $query .= $append . $_ . " = " . $self->{class_object_centreon}->quote(value => $options{values}->{$_}); + $query .= $append . $_ . ' = ?'; $append = ', '; + push @bind_values, $options{values}->{$_}; } $query .= " WHERE "; $append = ''; foreach (@{$options{where_clause}}) { my ($key, $value) = each %{$_}; - $query .= $append . $key . " = " . $self->{class_object_centreon}->quote(value => $value); + $query .= $append . $key . " = ?"; $append = 'AND '; + push @bind_values, $value; } - my ($status) = $self->{class_object_centreon}->custom_execute(request => $query); + my ($status) = $self->{class_object_centreon}->custom_execute(request => $query, bind_values => \@bind_values); if ($status == -1) { $self->{logger}->writeLogError('[autodiscovery] Failed to update job information'); return -1; @@ -884,24 +939,26 @@ sub action_hostdiscoveryjoblistener { return 0 if (!$self->is_hdisco_synced()); return 0 if (!defined($options{token})); - return 0 if (!defined($self->{hdisco_jobs_tokens}->{$options{token}})); + return 0 if (!defined($self->{hdisco_jobs_tokens}->{ $options{token} })); + + my $data = $options{frame}->getData(); - my $job_id = $self->{hdisco_jobs_tokens}->{$options{token}}; - if ($options{data}->{code} == GORGONE_MODULE_ACTION_COMMAND_RESULT && - $options{data}->{data}->{metadata}->{source} eq 'autodiscovery-host-job-discovery') { + my $job_id = $self->{hdisco_jobs_tokens}->{ $options{token} }; + if ($data->{code} == GORGONE_MODULE_ACTION_COMMAND_RESULT && + $data->{data}->{metadata}->{source} eq 'autodiscovery-host-job-discovery') { $self->discovery_command_result(%options); return 1; } - #if ($options{data}->{code} == GORGONE_MODULE_ACTION_COMMAND_RESULT && - # $options{data}->{data}->{metadata}->{source} eq 'autodiscovery-host-job-postcommand') { + #if ($data->{code} == GORGONE_MODULE_ACTION_COMMAND_RESULT && + # $data->{data}->{metadata}->{source} eq 'autodiscovery-host-job-postcommand') { # $self->discovery_postcommand_result(%options); # return 1; #} # Can happen if we have a execution command timeout - my $message = defined($options{data}->{data}->{result}->{stdout}) ? $options{data}->{data}->{result}->{stdout} : $options{data}->{data}->{message}; - $message = $options{data}->{message} if (!defined($message)); - if ($options{data}->{code} == GORGONE_ACTION_FINISH_KO) { + my $message = defined($data->{data}->{result}->{stdout}) ? $data->{data}->{result}->{stdout} : $data->{data}->{message}; + $message = $data->{message} if (!defined($message)); + if ($data->{code} == GORGONE_ACTION_FINISH_KO) { $self->{hdisco_jobs_ids}->{$job_id}->{status} = JOB_FAILED; $self->update_job_information( values => { @@ -930,11 +987,13 @@ sub action_hostdiscoverycronlistener { return 0 if (!defined($self->{hdisco_jobs_tokens}->{ $discovery_token })); + my $data = $options{frame}->getData(); + my $job_id = $self->{hdisco_jobs_tokens}->{ $discovery_token }; - if ($options{data}->{code} == GORGONE_ACTION_FINISH_KO) { + if ($data->{code} == GORGONE_ACTION_FINISH_KO) { $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - job '" . $job_id . "' add cron error"); $self->{hdisco_jobs_ids}->{$job_id}->{extra_infos}->{cron_added} = CRON_ADDED_KO; - } elsif ($options{data}->{code} == GORGONE_ACTION_FINISH_OK) { + } elsif ($data->{code} == GORGONE_ACTION_FINISH_OK) { $self->{logger}->writeLogInfo("[autodiscovery] -class- host discovery - job '" . $job_id . "' add cron ok"); $self->{hdisco_jobs_ids}->{$job_id}->{extra_infos}->{cron_added} = CRON_ADDED_OK; } @@ -948,7 +1007,7 @@ sub hdisco_add_joblistener { foreach (@{$options{jobs}}) { $self->{logger}->writeLogDebug("[autodiscovery] -class- host discovery - register listener for '" . $_->{job_id} . "'"); - $self->send_internal_action( + $self->send_internal_action({ action => 'ADDLISTENER', data => [ { @@ -959,7 +1018,7 @@ sub hdisco_add_joblistener { log_pace => $self->{check_interval} } ] - ); + }); } return 0; @@ -1014,7 +1073,7 @@ sub action_launchservicediscovery { ); my $status = $svc_discovery->launchdiscovery( token => $options{token}, - data => $options{data} + frame => $options{frame} ); if ($status == -1) { $self->send_log( @@ -1058,24 +1117,35 @@ sub is_hdisco_synced { } sub event { - while (1) { - my $message = $connector->read_message(); - last if (!defined($message)); + my ($self, %options) = @_; - $connector->{logger}->writeLogDebug("[autodiscovery] Event: $message"); - if ($message =~ /^\[(.*?)\]/) { + while ($self->{internal_socket}->has_pollin()) { + my $frame = gorgone::class::frame->new(); + my (undef, $rv) = $self->read_message(frame => $frame); + next if ($rv); + + my $raw = $frame->getFrame(); + $self->{logger}->writeLogDebug("[autodiscovery] Event: " . $$raw) if ($connector->{logger}->is_debug()); + if ($$raw =~ /^\[(.*?)\]/) { if ((my $method = $connector->can('action_' . lc($1)))) { - $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; - my ($action, $token) = ($1, $2); - my ($rv, $data) = $connector->json_decode(argument => $3, token => $token); - next if ($rv); + next if ($frame->parse({ releaseFrame => 1, decode => 1 })); - $method->($connector, token => $token, data => $data); + $method->($self, token => $frame->getToken(), frame => $frame); } } } } +sub periodic_exec { + $connector->is_module_installed(); + $connector->hdisco_sync(); + + if ($connector->{stop} == 1) { + $connector->{logger}->writeLogInfo("[autodiscovery] $$ has quit"); + exit(0); + } +} + sub run { my ($self, %options) = @_; @@ -1116,37 +1186,25 @@ sub run { db_centreon => $self->{db_centstorage} ); - # Connect internal - $connector->{internal_socket} = gorgone::standard::library::connect_com( + $self->{internal_socket} = gorgone::standard::library::connect_com( + context => $self->{zmq_context}, zmq_type => 'ZMQ_DEALER', name => 'gorgone-autodiscovery', logger => $self->{logger}, type => $self->get_core_config(name => 'internal_com_type'), path => $self->get_core_config(name => 'internal_com_path') ); - $connector->send_internal_action( + $self->send_internal_action({ action => 'AUTODISCOVERYREADY', data => {} - ); - $self->{poll} = [ - { - socket => $connector->{internal_socket}, - events => ZMQ_POLLIN, - callback => \&event, - } - ]; + }); - while (1) { - $self->is_module_installed(); - $self->hdisco_sync(); + $self->is_module_installed(); + $self->hdisco_sync(); - my $rev = zmq_poll($self->{poll}, 5000); - if (defined($rev) && $rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("[autodiscovery] $$ has quit"); - zmq_close($connector->{internal_socket}); - exit(0); - } - } + my $watcher_timer = $self->{loop}->timer(5, 5, \&periodic_exec); + my $watcher_io = $self->{loop}->io($self->{internal_socket}->get_fd(), EV::READ, sub { $connector->event() } ); + $self->{loop}->run(); } 1; diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm b/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm index 52c370513cd..24a4b8007c3 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm @@ -70,20 +70,20 @@ sub routing { } if (gorgone::class::core::waiting_ready(ready => \$autodiscovery->{ready}) == 0) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { msg => 'gorgoneautodiscovery: still no ready' }, json_encode => 1 - ); + }); return undef; } $options{gorgone}->send_internal_message( identity => 'gorgone-autodiscovery', action => $options{action}, - data => $options{data}, + raw_data_ref => $options{frame}->getRawData(), token => $options{token} ); } diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm index 8dfec7d9cd7..2077b9800e6 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm @@ -27,8 +27,6 @@ use warnings; use gorgone::standard::library; use gorgone::standard::constants qw(:all); use gorgone::modules::centreon::autodiscovery::services::resources; -use ZMQ::LibZMQ4; -use ZMQ::Constants qw(:all); use Net::SMTP; use XML::Simple; use POSIX qw(strftime); @@ -182,7 +180,7 @@ sub restart_pollers { my $poller_ids = {}; foreach my $poller_id (keys %{$self->{discovery}->{pollers_reload}}) { $self->{logger}->writeLogInfo("[autodiscovery] -servicediscovery- $self->{uuid} generate poller config '" . $poller_id . "'"); - $self->send_internal_action( + $self->send_internal_action({ action => 'COMMAND', token => $self->{discovery}->{token} . ':config', data => { @@ -192,7 +190,7 @@ sub restart_pollers { } ] } - ); + }); } } @@ -201,25 +199,21 @@ sub audit_update { return if ($self->{discovery}->{audit_enable} != 1); - my $query = 'INSERT INTO log_action (action_log_date, object_type, object_id, object_name, action_type, log_contact_id) VALUES (' . - time() . ', ' . $self->{class_object_centstorage}->quote(value => $options{object_type}) . ',' . - $self->{class_object_centstorage}->quote(value => $options{object_id}) . ',' . - $self->{class_object_centstorage}->quote(value => $options{object_name}) . ',' . - $self->{class_object_centstorage}->quote(value => $options{action_type}) . ',' . - $self->{class_object_centstorage}->quote(value => $options{contact_id}) . - ')'; - my ($status, $sth) = $self->{class_object_centstorage}->custom_execute(request => $query); + my $query = 'INSERT INTO log_action (action_log_date, object_type, object_id, object_name, action_type, log_contact_id) VALUES (?, ?, ?, ?, ?, ?)'; + my ($status, $sth) = $self->{class_object_centstorage}->custom_execute( + request => $query, + bind_values => [time(), $options{object_type}, $options{object_id}, $options{object_name}, $options{action_type}, $options{contact_id}] + ); return if (!defined($options{fields})); my $action_log_id = $self->{class_object_centstorage}->{db_centreon}->last_insert_id(); foreach (keys %{$options{fields}}) { - $query = 'INSERT INTO log_action_modification (action_log_id, field_name, field_value) VALUES (' . - $action_log_id . ', '. - $self->{class_object_centstorage}->quote(value => $_) . ', ' . - $self->{class_object_centstorage}->quote(value => $options{fields}->{$_}) . - ')'; - ($status) = $self->{class_object_centstorage}->custom_execute(request => $query); + $query = 'INSERT INTO log_action_modification (action_log_id, field_name, field_value) VALUES (?, ?, ?)'; + ($status) = $self->{class_object_centstorage}->custom_execute( + request => $query, + bind_values => [$action_log_id, $_, $options{fields}->{$_}] + ); if ($status == -1) { return -1; } @@ -366,15 +360,21 @@ sub update_service { } foreach (@update_macros) { - my $query = 'UPDATE on_demand_macro_service SET svc_macro_value = ' . $self->{class_object_centreon}->quote(value => $_->{value}) . ' WHERE svc_svc_id = ' . $options{service}->{id} . ' AND svc_macro_name = ' . $self->{class_object_centreon}->quote(value => '$_SERVICE' . $_->{name} . '$'); - my ($status) = $self->{class_object_centreon}->custom_execute(request => $query); + my $query = 'UPDATE on_demand_macro_service SET svc_macro_value = ? WHERE svc_svc_id = ' . $options{service}->{id} . ' AND svc_macro_name = ?'; + my ($status) = $self->{class_object_centreon}->custom_execute( + request => $query, + bind_values => [$_->{value}, '$_SERVICE' . $_->{name} . '$'] + ); if ($status == -1) { return $self->database_error_rollback(message => "$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> cannot update macro"); } } foreach (@insert_macros) { - my $query = 'INSERT on_demand_macro_service (svc_svc_id, svc_macro_name, svc_macro_value) VALUES (' . $options{service}->{id} . ', ' . $self->{class_object_centreon}->quote(value => '$_SERVICE' . $_->{name} . '$') . ', ' . $self->{class_object_centreon}->quote(value => $_->{value}) . ')'; - my ($status) = $self->{class_object_centreon}->custom_execute(request => $query); + my $query = 'INSERT on_demand_macro_service (svc_svc_id, svc_macro_name, svc_macro_value) VALUES (' . $options{service}->{id} . ', ?, ?)'; + my ($status) = $self->{class_object_centreon}->custom_execute( + request => $query, + bind_values => ['$_SERVICE' . $_->{name} . '$', $_->{value}] + ); if ($status == -1) { return $self->database_error_rollback(message => "$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> cannot insert macro"); } @@ -435,8 +435,11 @@ sub create_service { return -1 if ($self->database_init_transaction() == -1); - my $query = 'INSERT INTO service (service_template_model_stm_id, service_description, service_register) VALUES (' . $self->{class_object_centreon}->quote(value => $self->{discovery}->{rules}->{ $options{rule_id} }->{service_template_model_id}) . ', ' . $self->{class_object_centreon}->quote(value => $options{discovery_svc}->{service_name}) . ", '1')"; - my ($status, $sth) = $self->{class_object_centreon}->custom_execute(request => $query); + my $query = "INSERT INTO service (service_template_model_stm_id, service_description, service_register) VALUES (?, ?, '1')"; + my ($status, $sth) = $self->{class_object_centreon}->custom_execute( + request => $query, + bind_values => [$self->{discovery}->{rules}->{ $options{rule_id} }->{service_template_model_id}, $options{discovery_svc}->{service_name}] + ); if ($status == -1) { return $self->database_error_rollback(message => "$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> cannot create service"); } @@ -455,8 +458,11 @@ sub create_service { } foreach (keys %{$options{macros}}) { - $query = 'INSERT INTO on_demand_macro_service (svc_svc_id, svc_macro_name, svc_macro_value) VALUES (' . $service_id . ', ' . $self->{class_object_centreon}->quote(value => '$_SERVICE' . $_ . '$') . ', ' . $self->{class_object_centreon}->quote(value => $options{macros}->{$_}) . ')'; - ($status) = $self->{class_object_centreon}->custom_execute(request => $query); + $query = 'INSERT INTO on_demand_macro_service (svc_svc_id, svc_macro_name, svc_macro_value) VALUES (' . $service_id . ', ?, ?)'; + ($status) = $self->{class_object_centreon}->custom_execute( + request => $query, + bind_values => ['$_SERVICE' . $_ . '$', $options{macros}->{$_}] + ); if ($status == -1) { return $self->database_error_rollback(message => "$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> cannot create macro '$_' => '$options{macros}->{$_}'"); } @@ -640,7 +646,9 @@ sub service_response_parsing { sub discoverylistener { my ($self, %options) = @_; - return 0 if ($options{data}->{code} != GORGONE_MODULE_ACTION_COMMAND_RESULT && $options{data}->{code} != GORGONE_ACTION_FINISH_KO); + my $data = $options{frame}->getData(); + + return 0 if ($data->{code} != GORGONE_MODULE_ACTION_COMMAND_RESULT && $data->{code} != GORGONE_ACTION_FINISH_KO); if ($self->{discovery}->{is_manual} == 1) { $self->{discovery}->{manual}->{ $options{host_id} } = { rules => {} } if (!defined($self->{discovery}->{manual}->{ $options{host_id} })); @@ -648,27 +656,27 @@ sub discoverylistener { } # if i have GORGONE_MODULE_ACTION_COMMAND_RESULT, i can't have GORGONE_ACTION_FINISH_KO - if ($options{data}->{code} == GORGONE_MODULE_ACTION_COMMAND_RESULT) { - my $exit_code = $options{data}->{data}->{result}->{exit_code}; + if ($data->{code} == GORGONE_MODULE_ACTION_COMMAND_RESULT) { + my $exit_code = $data->{data}->{result}->{exit_code}; if ($exit_code == 0) { $self->service_response_parsing( rule_id => $options{rule_id}, host_id => $options{host_id}, poller_id => $self->{discovery}->{hosts}->{ $options{host_id} }->{poller_id}, - response => $options{data}->{data}->{result}->{stdout} + response => $data->{data}->{result}->{stdout} ); } else { $self->{discovery}->{failed_discoveries}++; if ($self->{discovery}->{is_manual} == 1) { $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{failed} = 1; - $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{message} = $options{data}->{data}->{message}; - $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{data} = $options{data}->{data}; + $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{message} = $data->{data}->{message}; + $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{data} = $data->{data}; } } - } elsif ($options{data}->{code} == GORGONE_ACTION_FINISH_KO) { + } elsif ($data->{code} == GORGONE_ACTION_FINISH_KO) { if ($self->{discovery}->{is_manual} == 1) { $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{failed} = 1; - $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{message} = $options{data}->{data}->{message}; + $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{message} = $data->{data}->{message}; } $self->{discovery}->{failed_discoveries}++; } else { @@ -748,7 +756,7 @@ sub service_execute_commands { $host->{host_name} . "] -> substitute string: " . $command ); - $self->send_internal_action( + $self->send_internal_action({ action => 'ADDLISTENER', data => [ { @@ -760,9 +768,9 @@ sub service_execute_commands { log_pace => 15 } ] - ); + }); - $self->send_internal_action( + $self->send_internal_action({ action => 'COMMAND', target => $poller_id, token => 'svc-disco-' . $self->{uuid} . '-' . $rule_id . '-' . $host_id, @@ -775,7 +783,7 @@ sub service_execute_commands { } ] } - ); + }); } } } @@ -784,6 +792,8 @@ sub service_execute_commands { sub launchdiscovery { my ($self, %options) = @_; + my $data = $options{frame}->getData(); + $options{token} = $self->generate_token() if (!defined($options{token})); $self->{logger}->writeLogInfo("[autodiscovery] -servicediscovery- $self->{uuid} discovery start"); @@ -840,8 +850,8 @@ sub launchdiscovery { ($status, $message, my $rules) = gorgone::modules::centreon::autodiscovery::services::resources::get_rules( class_object_centreon => $self->{class_object_centreon}, - filter_rules => $options{data}->{content}->{filter_rules}, - force_rule => (defined($options{data}->{content}->{force_rule}) && $options{data}->{content}->{force_rule} =~ /^1$/) ? 1 : 0 + filter_rules => $data->{content}->{filter_rules}, + force_rule => (defined($data->{content}->{force_rule}) && $data->{content}->{force_rule} =~ /^1$/) ? 1 : 0 ); if ($status < 0) { $self->send_log_msg_error(token => $options{token}, subname => 'servicediscovery', number => $self->{uuid}, message => $message); @@ -860,8 +870,8 @@ sub launchdiscovery { poller_id => $rules->{$rule_id}->{poller_id}, class_object_centreon => $self->{class_object_centreon}, with_macro => 1, - host_lookup => $options{data}->{content}->{filter_hosts}, - poller_lookup => $options{data}->{content}->{filter_pollers} + host_lookup => $data->{content}->{filter_hosts}, + poller_lookup => $data->{content}->{filter_pollers} ); if ($status < 0) { $self->send_log_msg_error(token => $options{token}, subname => 'servicediscovery', number => $self->{uuid}, message => $message); @@ -898,11 +908,11 @@ sub launchdiscovery { progress_div => 0, rules => $rules, manual => {}, - is_manual => (defined($options{data}->{content}->{manual}) && $options{data}->{content}->{manual} =~ /^1$/) ? 1 : 0, - dry_run => (defined($options{data}->{content}->{dry_run}) && $options{data}->{content}->{dry_run} =~ /^1$/) ? 1 : 0, + is_manual => (defined($data->{content}->{manual}) && $data->{content}->{manual} =~ /^1$/) ? 1 : 0, + dry_run => (defined($data->{content}->{dry_run}) && $data->{content}->{dry_run} =~ /^1$/) ? 1 : 0, audit_enable => $audit_enable, - no_generate_config => (defined($options{data}->{content}->{no_generate_config}) && $options{data}->{content}->{no_generate_config} =~ /^1$/) ? 1 : 0, - options => defined($options{data}->{content}) ? $options{data}->{content} : {}, + no_generate_config => (defined($data->{content}->{no_generate_config}) && $data->{content}->{no_generate_config} =~ /^1$/) ? 1 : 0, + options => defined($data->{content}) ? $data->{content} : {}, hosts => $all_hosts, journal => [], pollers_reload => {} diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm b/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm index 961b8beecad..1e76f54f1eb 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm @@ -43,8 +43,9 @@ sub get_pollers { $pollers->{$poller_id}->{resources} = {}; ($status, my $resources) = $options{class_object_centreon}->custom_execute( request => - 'SELECT resource_name, resource_line FROM cfg_resource_instance_relations, cfg_resource WHERE cfg_resource_instance_relations.instance_id = ' . - $options{class_object_centreon}->quote(value => $poller_id) . " AND cfg_resource_instance_relations.resource_id = cfg_resource.resource_id AND resource_activate = '1'", + 'SELECT resource_name, resource_line FROM cfg_resource_instance_relations, cfg_resource WHERE cfg_resource_instance_relations.instance_id = ?' . + " AND cfg_resource_instance_relations.resource_id = cfg_resource.resource_id AND resource_activate = '1'", + bind_values => [$poller_id], mode => 2 ); if ($status == -1) { @@ -83,9 +84,8 @@ sub get_audit_user_id { my $user_id = 0; my ($status, $contacts) = $options{class_object_centreon}->custom_execute( - request => - 'SELECT contact_id FROM contact WHERE contact_alias = ' . - $options{class_object_centreon}->quote(value => $options{clapi_user}), + request => 'SELECT contact_id FROM contact WHERE contact_alias = ?', + bind_values => [$options{clapi_user}], mode => 2 ); if ($status == -1) { @@ -106,12 +106,15 @@ sub get_rules { if (defined($options{force_rule}) && $options{force_rule} == 1) { $filter = ''; } + + my @bind_values = (); if (defined($options{filter_rules}) && scalar(@{$options{filter_rules}}) > 0) { my $append = ''; $filter .= 'rule_alias IN ('; foreach my $rule (@{$options{filter_rules}}) { - $filter .= $append . $options{class_object_centreon}->quote(value => $rule); + $filter .= $append . '?'; $append = ', '; + push @bind_values, $rule; } $filter .= ') AND '; } @@ -120,6 +123,7 @@ sub get_rules { request => "SELECT rule_id, rule_alias, service_display_name, rule_disable, rule_update, command_line, service_template_model_id, rule_scan_display_custom, rule_variable_custom FROM mod_auto_disco_rule, command WHERE " . $filter . " mod_auto_disco_rule.command_command_id = command.command_id", + bind_values => \@bind_values, mode => 1, keys => 'rule_id' ); @@ -306,37 +310,43 @@ sub get_hosts { my $filter = ''; my $filter_append = ''; - foreach (@{$options{host_template}}) { - $filter .= $filter_append . $options{class_object_centreon}->quote(value => $_); - $filter_append = ', '; - } - $filter = ' host_template_relation.host_tpl_id IN (' . $filter . ') AND '; + my @bind_values = (); my $filter_host = ''; if (defined($options{host_lookup}) && ref($options{host_lookup}) eq 'ARRAY' && scalar(@{$options{host_lookup}}) > 0) { my $filter_append = ''; foreach (@{$options{host_lookup}}) { - $filter_host .= $filter_append .$options{class_object_centreon}->quote(value => $_); + $filter_host .= $filter_append . '?'; $filter_append = ', '; + push @bind_values, $_; } $filter_host = ' host.host_name IN (' . $filter_host . ') AND '; } - + + foreach (@{$options{host_template}}) { + $filter .= $filter_append . '?'; + $filter_append = ', '; + push @bind_values, $_; + } + $filter = ' host_template_relation.host_tpl_id IN (' . $filter . ') AND '; + my $filter_poller = ''; my $join_table = ''; if (defined($options{poller_lookup}) && ref($options{poller_lookup}) eq 'ARRAY' && scalar(@{$options{poller_lookup}}) > 0) { my $filter_append = ''; foreach (@{$options{poller_lookup}}) { - $filter_poller .= $filter_append . $options{class_object_centreon}->quote(value => $_); + $filter_poller .= $filter_append . '?'; $filter_append = ', '; + push @bind_values, $_; } $filter_poller = ' nagios_server.name IN ('. $filter_poller .') AND nagios_server.id = ns_host_relation.nagios_server_id AND '; $join_table = ', nagios_server '; } elsif (defined($options{poller_id}) && scalar(@{$options{poller_id}}) > 0){ my $filter_append = ''; foreach (@{$options{poller_id}}) { - $filter_poller .= $filter_append . $options{class_object_centreon}->quote(value => $_); + $filter_poller .= $filter_append . '?'; $filter_append = ', '; + push @bind_values, $_; } $filter_poller =' ns_host_relation.nagios_server_id IN (' . $filter_poller . ') AND nagios_server.id = ns_host_relation.nagios_server_id AND '; $join_table = ', nagios_server '; @@ -349,8 +359,9 @@ sub get_hosts { AND " . $filter_poller . " host.host_id = ns_host_relation.host_host_id AND `host_activate` = '1' ", + bind_values => \@bind_values, mode => 1, - keys => 'host_id', + keys => 'host_id' ); if ($status == -1) { return (-1, 'cannot host list'); @@ -578,7 +589,8 @@ sub get_service { my $service; my ($status, $datas) = $options{class_object_centreon}->custom_execute( request => 'SELECT service_id, service_template_model_stm_id, service_activate, svc_macro_name, svc_macro_value FROM host, host_service_relation, service LEFT JOIN on_demand_macro_service ON on_demand_macro_service.svc_svc_id = service.service_id WHERE host_id = ' . $options{host_id} . - " AND host.host_id = host_service_relation.host_host_id AND host_service_relation.service_service_id = service.service_id AND service.service_description = " . $options{class_object_centreon}->quote(value => $options{service_name}), + " AND host.host_id = host_service_relation.host_host_id AND host_service_relation.service_service_id = service.service_id AND service.service_description = ?", + bind_values => [$options{service_name}], mode => 2 ); if ($status == -1) { diff --git a/gorgone/gorgone/modules/centreon/engine/class.pm b/gorgone/gorgone/modules/centreon/engine/class.pm index 9e530274faa..85dea2b85ca 100644 --- a/gorgone/gorgone/modules/centreon/engine/class.pm +++ b/gorgone/gorgone/modules/centreon/engine/class.pm @@ -28,8 +28,7 @@ use JSON::XS; use gorgone::standard::library; use gorgone::standard::constants qw(:all); use gorgone::standard::misc; -use ZMQ::LibZMQ4; -use ZMQ::Constants qw(:all); +use EV; my %handlers = (TERM => {}, HUP => {}); my ($connector); @@ -227,7 +226,7 @@ sub action_run { return -1; } - zmq_close($socket_log); + $socket_log->close(); } sub create_child { @@ -266,48 +265,46 @@ sub create_child { } sub event { - while (1) { - my $message = $connector->read_message(); - last if (!defined($message)); + my ($self, %options) = @_; + + while ($self->{internal_socket}->has_pollin()) { + my ($message) = $self->read_message(); + next if (!defined($message)); - $connector->{logger}->writeLogDebug("[engine] Event: $message"); + $self->{logger}->writeLogDebug("[engine] Event: $message"); if ($message !~ /^\[ACK\]/) { - $connector->create_child(message => $message); - } + $self->create_child(message => $message); + } + } +} + +sub periodic_exec { + if ($connector->{stop} == 1) { + $connector->{logger}->writeLogInfo("[engine] $$ has quit"); + exit(0); } } sub run { my ($self, %options) = @_; - # Connect internal - $connector->{internal_socket} = gorgone::standard::library::connect_com( + $self->{internal_socket} = gorgone::standard::library::connect_com( + context => $self->{zmq_context}, zmq_type => 'ZMQ_DEALER', name => 'gorgone-engine', logger => $self->{logger}, type => $self->get_core_config(name => 'internal_com_type'), path => $self->get_core_config(name => 'internal_com_path') ); - $connector->send_internal_action( + $self->send_internal_action({ action => 'ENGINEREADY', data => {} - ); - $self->{poll} = [ - { - socket => $connector->{internal_socket}, - events => ZMQ_POLLIN, - callback => \&event, - } - ]; - while (1) { - my $rev = scalar(zmq_poll($self->{poll}, 5000)); - if ($rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("[engine] $$ has quit"); - zmq_close($connector->{internal_socket}); - exit(0); - } - } + }); + + my $watcher_timer = $self->{loop}->timer(5, 5, \&periodic_exec); + my $watcher_io = $self->{loop}->io($self->{internal_socket}->get_fd(), EV::READ, sub { $connector->event() } ); + $self->{loop}->run(); } 1; diff --git a/gorgone/gorgone/modules/centreon/engine/hooks.pm b/gorgone/gorgone/modules/centreon/engine/hooks.pm index de9f2129831..ef402d9b0be 100644 --- a/gorgone/gorgone/modules/centreon/engine/hooks.pm +++ b/gorgone/gorgone/modules/centreon/engine/hooks.pm @@ -62,20 +62,20 @@ sub routing { } if (gorgone::class::core::waiting_ready(ready => \$engine->{ready}) == 0) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { msg => 'gorgoneengine: still no ready' }, json_encode => 1 - ); + }); return undef; } $options{gorgone}->send_internal_message( identity => 'gorgone-engine', action => $options{action}, - data => $options{data}, + raw_data_ref => $options{frame}->getRawData(), token => $options{token} ); } diff --git a/gorgone/gorgone/modules/centreon/judge/class.pm b/gorgone/gorgone/modules/centreon/judge/class.pm index 5f2ea5d23df..45fc4b23f64 100644 --- a/gorgone/gorgone/modules/centreon/judge/class.pm +++ b/gorgone/gorgone/modules/centreon/judge/class.pm @@ -27,11 +27,10 @@ use warnings; use gorgone::class::db; use gorgone::standard::library; use gorgone::standard::constants qw(:all); -use ZMQ::LibZMQ4; -use ZMQ::Constants qw(:all); use JSON::XS; use gorgone::modules::centreon::judge::type::distribute; use gorgone::modules::centreon::judge::type::spare; +use EV; my %handlers = (TERM => {}, HUP => {}); my ($connector); @@ -384,25 +383,6 @@ sub action_judgelistener { return 1; } -sub event { - while (1) { - my $message = $connector->read_message(); - last if (!defined($message)); - - $connector->{logger}->writeLogDebug("[judge] -class- event: $message"); - if ($message =~ /^\[(.*?)\]/) { - if ((my $method = $connector->can('action_' . lc($1)))) { - $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; - my ($action, $token) = ($1, $2); - my ($rv, $data) = $connector->json_decode(argument => $3, token => $token); - next if ($rv); - - $method->($connector, token => $token, data => $data); - } - } - } -} - sub check_alive { my ($self, %options) = @_; @@ -493,12 +473,12 @@ sub add_pipeline_config_reload_poller { }; } - $self->send_internal_action( + $self->send_internal_action({ action => 'ADDPIPELINE', token => $options{token}, timeout => $options{pipeline_timeout}, data => $actions - ); + }); } sub test_types { @@ -534,27 +514,31 @@ sub test_types { ); } +sub periodic_exec { + if ($connector->{stop} == 1) { + $connector->{logger}->writeLogInfo("[judge] -class- $$ has quit"); + exit(0); + } + + $connector->check_alive(); + $connector->test_types(); +} + sub run { my ($self, %options) = @_; - $connector->{internal_socket} = gorgone::standard::library::connect_com( + $self->{internal_socket} = gorgone::standard::library::connect_com( + context => $self->{zmq_context}, zmq_type => 'ZMQ_DEALER', name => 'gorgone-judge', logger => $self->{logger}, type => $self->get_core_config(name => 'internal_com_type'), path => $self->get_core_config(name => 'internal_com_path') ); - $connector->send_internal_action( + $connector->send_internal_action({ action => 'JUDGEREADY', data => {} - ); - $self->{poll} = [ - { - socket => $connector->{internal_socket}, - events => ZMQ_POLLIN, - callback => \&event, - } - ]; + }); $self->{db_centstorage} = gorgone::class::db->new( dsn => $self->{config_db_centstorage}->{dsn}, @@ -584,17 +568,9 @@ sub run { logger => $self->{logger} ); - while (1) { - my $rev = scalar(zmq_poll($self->{poll}, 5000)); - if ($rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("[judge] -class- $$ has quit"); - zmq_close($connector->{internal_socket}); - exit(0); - } - - $self->check_alive(); - $self->test_types(); - } + my $watcher_timer = $self->{loop}->timer(5, 5, \&periodic_exec); + my $watcher_io = $self->{loop}->io($connector->{internal_socket}->get_fd(), EV::READ, sub { $connector->event() } ); + $self->{loop}->run(); } 1; diff --git a/gorgone/gorgone/modules/centreon/judge/hooks.pm b/gorgone/gorgone/modules/centreon/judge/hooks.pm index 63bb9d75077..29154a078d5 100644 --- a/gorgone/gorgone/modules/centreon/judge/hooks.pm +++ b/gorgone/gorgone/modules/centreon/judge/hooks.pm @@ -67,20 +67,20 @@ sub routing { } if (gorgone::class::core::waiting_ready(ready => \$judge->{ready}) == 0) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'gorgone-judge: still no ready' }, json_encode => 1 - ); + }); return undef; } $options{gorgone}->send_internal_message( identity => 'gorgone-judge', action => $options{action}, - data => $options{data}, + raw_data_ref => $options{frame}->getRawData(), token => $options{token} ); } diff --git a/gorgone/gorgone/modules/centreon/judge/type/distribute.pm b/gorgone/gorgone/modules/centreon/judge/type/distribute.pm index e3b34c8e26a..910c3694c65 100644 --- a/gorgone/gorgone/modules/centreon/judge/type/distribute.pm +++ b/gorgone/gorgone/modules/centreon/judge/type/distribute.pm @@ -75,7 +75,7 @@ sub assign { my $request = " SELECT nhr.host_host_id FROM hostcategories hc, hostcategories_relation hcr, ns_host_relation nhr, nagios_server ns - WHERE hc.hc_activate = '1' AND hc.hc_name = " . $options{module}->{class_object_centreon}->quote(value => $options{cluster}->{hcategory}) . " + WHERE hc.hc_activate = '1' AND hc.hc_name = ? AND hc.hc_id = hcr.hostcategories_hc_id AND hcr.host_host_id = nhr.host_host_id AND nhr.nagios_server_id = ns.id @@ -83,7 +83,8 @@ sub assign { AND ns.ns_activate = '0' "; my ($status, $datas) = $options{module}->{class_object_centreon}->custom_execute( - request => $request, + request => $request, + bind_values => [$options{cluster}->{hcategory}], mode => 2 ); if ($status == -1) { diff --git a/gorgone/gorgone/modules/centreon/judge/type/spare.pm b/gorgone/gorgone/modules/centreon/judge/type/spare.pm index 0d7a09b0915..9dc28907964 100644 --- a/gorgone/gorgone/modules/centreon/judge/type/spare.pm +++ b/gorgone/gorgone/modules/centreon/judge/type/spare.pm @@ -97,8 +97,10 @@ sub init { foreach (keys %{$options{clusters}}) { next if ($options{clusters}->{$_}->{live}->{status} != NOTREADY_STATUS); - my $query = 'SELECT `status` FROM gorgone_centreon_judge_spare WHERE cluster_name = ' . $options{module}->{db_gorgone}->quote($options{clusters}->{$_}->{name}); - my ($status, $sth) = $options{module}->{db_gorgone}->query($query); + my ($status, $sth) = $options{module}->{db_gorgone}->query({ + query => 'SELECT `status` FROM gorgone_centreon_judge_spare WHERE cluster_name = ?', + bind_values => [$options{clusters}->{$_}->{name}] + }); if ($status == -1) { $options{module}->{logger}->writeLogError("[judge] -class- sqlite error to get cluster information '" . $options{clusters}->{$_}->{name} . "': cannot select"); next; @@ -107,11 +109,10 @@ sub init { if (my $row = $sth->fetchrow_hashref()) { $options{clusters}->{$_}->{live}->{status} = $row->{status}; } else { - ($status) = $options{module}->{db_gorgone}->query( - 'INSERT INTO gorgone_centreon_judge_spare (`cluster_name`, `status`) VALUES (' . - $options{module}->{db_gorgone}->quote($options{clusters}->{$_}->{name}) . ', ' . - READY_STATUS . ')' - ); + ($status) = $options{module}->{db_gorgone}->query({ + query => 'INSERT INTO gorgone_centreon_judge_spare (`cluster_name`, `status`) VALUES (?, ' . READY_STATUS . ')', + bind_values => [$options{clusters}->{$_}->{name}] + }); if ($status == -1) { $options{module}->{logger}->writeLogError("[judge] -class- sqlite error to get cluster information '" . $options{clusters}->{$_}->{name} . "': cannot insert"); next; @@ -169,10 +170,10 @@ sub is_spare_ready { sub update_status { my (%options) = @_; - my ($status) = $options{module}->{db_gorgone}->query( - 'UPDATE gorgone_centreon_judge_spare SET `status` = ' . $options{status} . ' ' . - 'WHERE `cluster_name` = ' . $options{module}->{db_gorgone}->quote($options{cluster}) - ); + my ($status) = $options{module}->{db_gorgone}->query({ + query => 'UPDATE gorgone_centreon_judge_spare SET `status` = ' . $options{status} . ' WHERE `cluster_name` = ?', + bind_values => [$options{cluster}] + }); if ($status == -1) { $options{module}->{logger}->writeLogError("[judge] -class- cluster '" . $options{cluster} . "' step $options{step}: cannot update status"); return -1; @@ -292,7 +293,8 @@ sub migrate_steps_1_2_3 { my ($status, $datas) = $options{module}->{class_object_centreon}->custom_execute( request => 'SELECT host_host_id ' . 'FROM ns_host_relation ' . - 'WHERE nagios_server_id = ' . $options{module}->{class_object_centreon}->quote(value => $options{node_src}), + 'WHERE nagios_server_id = ?', + bind_values => [$options{node_src}], mode => 2 ); if ($status == -1) { @@ -344,10 +346,10 @@ sub migrate_steps_1_2_3 { return -1; } - ($status) = $options{module}->{db_gorgone}->query( - 'UPDATE gorgone_centreon_judge_spare SET `status` = ' . FAILOVER_RUNNING_STATUS . ', `data` = ' . $options{module}->{db_gorgone}->quote($encoded) . ' ' . - 'WHERE `cluster_name` = ' . $options{module}->{db_gorgone}->quote($options{cluster}) - ); + ($status) = $options{module}->{db_gorgone}->query({ + query => 'UPDATE gorgone_centreon_judge_spare SET `status` = ' . FAILOVER_RUNNING_STATUS . ', `data` = ? WHERE `cluster_name` = ?', + bind_values => [$encoded, $options{cluster}] + }); if ($status == -1) { $options{module}->{logger}->writeLogError("[judge] -class- cluster '" . $options{clusters}->{ $options{cluster} }->{name} . "' step STATE_MIGRATION_UPDATE_SQLITE: cannot update sqlite"); send_log( @@ -373,8 +375,9 @@ sub migrate_steps_1_2_3 { send_log(module => $options{module}, code => GORGONE_MODULE_CENTREON_JUDGE_FAILOVER_RUNNING, live => $options{clusters}->{ $options{cluster} }->{live}); ($status) = $options{module}->{class_object_centreon}->custom_execute( - request => 'UPDATE ns_host_relation SET nagios_server_id = ' . $options{module}->{class_object_centreon}->quote(value => $options{clusters}->{ $options{cluster} }->{spare}) . - ' WHERE host_host_id IN (' . join(',', @{$data->{hosts}}) . ')' + request => 'UPDATE ns_host_relation SET nagios_server_id = ?' . + ' WHERE host_host_id IN (' . join(',', @{$data->{hosts}}) . ')', + bind_values => [$options{clusters}->{ $options{cluster} }->{spare}] ); if ($status == -1) { $options{module}->{logger}->writeLogError("[judge] -class- cluster '" . $options{clusters}->{ $options{cluster} }->{name} . "' step STATE_MIGRATION_UPDATE_CENTREON_DB: cannot update database"); @@ -601,7 +604,8 @@ sub migrate_step_5 { $options{clusters}->{ $options{cluster} }->{live}->{no_update_running_failed} != 1) { my ($status) = $options{module}->{class_object_centstorage}->custom_execute( request => 'UPDATE instances SET running = 0 ' . - ' WHERE instance_id = ' . $options{module}->{class_object_centstorage}->quote(value => $options{clusters}->{ $options{cluster} }->{live}->{node_src}) + ' WHERE instance_id = ?', + bind_values => [$options{clusters}->{ $options{cluster} }->{live}->{node_src}] ); if ($status == -1) { $options{module}->{logger}->writeLogError("[judge] -class- cluster '" . $options{clusters}->{ $options{cluster} }->{name} . "' step STATE_MIGRATION_UPDATE_RUNNING_POLLER_FAILED: cannot update database"); @@ -684,8 +688,10 @@ sub failback_start { $options{clusters}->{ $options{cluster} }->{live}->{state} = STATE_FAILBACK_GET_SQLITE; send_log(module => $options{module}, code => GORGONE_MODULE_CENTREON_JUDGE_FAILBACK_RUNNING, live => $options{clusters}->{ $options{cluster} }->{live}); - my $query = 'SELECT `status`, `data` FROM gorgone_centreon_judge_spare WHERE cluster_name = ' . $options{module}->{db_gorgone}->quote($options{clusters}->{ $options{cluster} }->{name}); - my ($status, $sth) = $options{module}->{db_gorgone}->query($query); + my ($status, $sth) = $options{module}->{db_gorgone}->query({ + query => 'SELECT `status`, `data` FROM gorgone_centreon_judge_spare WHERE cluster_name = ?', + bind_values => [$options{clusters}->{ $options{cluster} }->{name}] + }); if ($status == -1) { $options{module}->{logger}->writeLogError("[judge] -class- cluster '" . $options{clusters}->{ $options{cluster} }->{name} . "' cannot get sqlite information"); send_log( @@ -738,8 +744,9 @@ sub failback_start { send_log(module => $options{module}, code => GORGONE_MODULE_CENTREON_JUDGE_FAILBACK_RUNNING, live => $options{clusters}->{ $options{cluster} }->{live}); ($status) = $options{module}->{class_object_centreon}->custom_execute( - request => 'UPDATE ns_host_relation SET nagios_server_id = ' . $options{module}->{class_object_centreon}->quote(value => $options{clusters}->{ $options{cluster} }->{live}->{node_dst}) . - ' WHERE host_host_id IN (' . join(',', @{$decoded->{hosts}}) . ')' + request => 'UPDATE ns_host_relation SET nagios_server_id = ?' . + ' WHERE host_host_id IN (' . join(',', @{$decoded->{hosts}}) . ')', + bind_values => [$options{clusters}->{ $options{cluster} }->{live}->{node_dst}] ); if ($status == -1) { $options{module}->{logger}->writeLogError("[judge] -class- cluster '" . $options{clusters}->{ $options{cluster} }->{name} . "' step STATE_FAILBACK_UPDATE_CENTREON_DB: cannot update database"); diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index 463eef1c19e..c1392662097 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -29,9 +29,8 @@ use gorgone::standard::constants qw(:all); use gorgone::standard::misc; use gorgone::class::sqlquery; use gorgone::class::tpapi::clapi; -use ZMQ::LibZMQ4; -use ZMQ::Constants qw(:all); use File::Copy; +use EV; my %handlers = (TERM => {}, HUP => {}); my ($connector); @@ -154,7 +153,7 @@ sub send_external_commands { foreach my $target (@$targets) { next if (!defined($self->{bulk_commands}->{$target}) || scalar(@{$self->{bulk_commands}->{$target}}) <= 0); - $self->send_internal_action( + $self->send_internal_action({ action => 'ENGINECOMMAND', target => $target, token => $token, @@ -167,7 +166,7 @@ sub send_external_commands { ] } } - ); + }); $self->{logger}->writeLogDebug("[legacycmd] send external commands for '$target'"); $self->{bulk_commands}->{$target} = []; @@ -180,7 +179,7 @@ sub add_external_command { $options{param} =~ s/[\Q$self->{gorgone_illegal_characters}\E]//g if (defined($self->{gorgone_illegal_characters}) && $self->{gorgone_illegal_characters} ne ''); if ($options{action} == 1) { - $self->send_internal_action( + $self->send_internal_action({ action => 'ENGINECOMMAND', target => $options{target}, token => $options{token}, @@ -193,7 +192,7 @@ sub add_external_command { ] } } - ); + }); } else { $self->{bulk_commands}->{ $options{target} } = [] if (!defined($self->{bulk_commands}->{ $options{target} })); push @{$self->{bulk_commands}->{ $options{target} }}, $options{param}; @@ -234,7 +233,7 @@ sub execute_cmd { my $cache_dir = (defined($connector->{config}->{cache_dir}) && $connector->{config}->{cache_dir} ne '') ? $connector->{config}->{cache_dir} : '/var/cache/centreon'; # engine - $self->send_internal_action( + $self->send_internal_action({ action => 'REMOTECOPY', target => $options{target}, token => $token, @@ -252,9 +251,9 @@ sub execute_cmd { } } } - ); + }); # broker - $self->send_internal_action( + $self->send_internal_action({ action => 'REMOTECOPY', target => $options{target}, token => $token, @@ -272,7 +271,7 @@ sub execute_cmd { } } } - ); + }); } elsif ($options{cmd} eq 'SENDEXPORTFILE') { if (!defined($self->{clapi_password})) { return (-1, 'need centreon clapi password to execute SENDEXPORTFILE command'); @@ -283,7 +282,7 @@ sub execute_cmd { my $remote_dir = (defined($connector->{config}->{remote_dir})) ? $connector->{config}->{remote_dir} : '/var/cache/centreon/config/remote-data/'; # remote server - $self->send_internal_action( + $self->send_internal_action({ action => 'REMOTECOPY', target => $options{target}, token => $token, @@ -300,12 +299,12 @@ sub execute_cmd { } } } - ); + }); # Forward data use to be done by createRemoteTask as well as task_id in a gorgone command # Command name: AddImportTaskWithParent # Data: ['parent_id' => $task->getId()] - $self->send_internal_action( + $self->send_internal_action({ action => 'ADDIMPORTTASKWITHPARENT', token => $options{token}, target => $options{target}, @@ -316,14 +315,14 @@ sub execute_cmd { cbd_reload => 'sudo ' . $self->{pollers}->{ $options{target} }->{broker_reload_command} } } - ); + }); } elsif ($options{cmd} eq 'SYNCTRAP') { my $cache_dir = (defined($connector->{config}->{cache_dir}) && $connector->{config}->{cache_dir} ne '') ? $connector->{config}->{cache_dir} : '/var/cache/centreon'; my $cache_dir_trap = (defined($connector->{config}->{cache_dir_trap}) && $connector->{config}->{cache_dir_trap} ne '') ? $connector->{config}->{cache_dir_trap} : '/etc/snmp/centreon_traps/'; # centreontrapd - $self->send_internal_action( + $self->send_internal_action({ action => 'REMOTECOPY', target => $options{target}, token => $token, @@ -341,10 +340,10 @@ sub execute_cmd { } } } - ); + }); } elsif ($options{cmd} eq 'ENGINERESTART') { my $cmd = $self->{pollers}->{$options{target}}->{engine_restart_command}; - $self->send_internal_action( + $self->send_internal_action({ action => 'ACTIONENGINE', target => $options{target}, token => $token, @@ -359,10 +358,10 @@ sub execute_cmd { } } } - ); + }); } elsif ($options{cmd} eq 'RESTART') { my $cmd = $self->{pollers}->{$options{target}}->{engine_restart_command}; - $self->send_internal_action( + $self->send_internal_action({ action => 'COMMAND', target => $options{target}, token => $token, @@ -378,10 +377,10 @@ sub execute_cmd { } ] } - ); + }); } elsif ($options{cmd} eq 'ENGINERELOAD') { my $cmd = $self->{pollers}->{ $options{target} }->{engine_reload_command}; - $self->send_internal_action( + $self->send_internal_action({ action => 'ACTIONENGINE', target => $options{target}, token => $token, @@ -396,10 +395,10 @@ sub execute_cmd { } } } - ); + }); } elsif ($options{cmd} eq 'RELOAD') { my $cmd = $self->{pollers}->{$options{target}}->{engine_reload_command}; - $self->send_internal_action( + $self->send_internal_action({ action => 'COMMAND', target => $options{target}, token => $token, @@ -415,10 +414,10 @@ sub execute_cmd { } ] } - ); + }); } elsif ($options{cmd} eq 'START') { my $cmd = $self->{pollers}->{$options{target}}->{engine_start_command}; - $self->send_internal_action( + $self->send_internal_action({ action => 'COMMAND', target => $options{target}, token => $token, @@ -434,10 +433,10 @@ sub execute_cmd { } ] } - ); + }); } elsif ($options{cmd} eq 'STOP') { my $cmd = $self->{pollers}->{$options{target}}->{engine_stop_command}; - $self->send_internal_action( + $self->send_internal_action({ action => 'COMMAND', target => $options{target}, token => $token, @@ -453,10 +452,10 @@ sub execute_cmd { } ] } - ); + }); } elsif ($options{cmd} eq 'RELOADBROKER') { my $cmd = $self->{pollers}->{$options{target}}->{broker_reload_command}; - $self->send_internal_action( + $self->send_internal_action({ action => 'COMMAND', target => $options{target}, token => $token, @@ -472,10 +471,10 @@ sub execute_cmd { } ] } - ); + }); } elsif ($options{cmd} eq 'RESTARTCENTREONTRAPD') { my $cmd = $self->{pollers}->{$options{target}}->{init_script_centreontrapd}; - $self->send_internal_action( + $self->send_internal_action({ action => 'COMMAND', target => $options{target}, token => $token, @@ -491,10 +490,10 @@ sub execute_cmd { } ] } - ); + }); } elsif ($options{cmd} eq 'RELOADCENTREONTRAPD') { my $cmd = $self->{pollers}->{$options{target}}->{init_script_centreontrapd}; - $self->send_internal_action( + $self->send_internal_action({ action => 'COMMAND', target => $options{target}, token => $token, @@ -510,7 +509,7 @@ sub execute_cmd { } ] } - ); + }); } elsif ($options{cmd} eq 'STARTWORKER') { if (!defined($self->{clapi_password})) { return (-1, 'need centreon clapi password to execute STARTWORKER command'); @@ -519,7 +518,7 @@ sub execute_cmd { $connector->{config}->{centreon_dir} : '/usr/share/centreon'; my $cmd = $centreon_dir . '/bin/centreon -u "' . $self->{clapi_user} . '" -p "' . $self->{clapi_password} . '" -w -o CentreonWorker -a processQueue'; - $self->send_internal_action( + $self->send_internal_action({ action => 'COMMAND', target => undef, token => $token, @@ -534,7 +533,7 @@ sub execute_cmd { } ] } - ); + }); } return 0; @@ -574,7 +573,7 @@ sub action_addimporttaskwithparent { $connector->{config}->{centreon_dir} : '/usr/share/centreon'; my $cmd = $centreon_dir . '/bin/centreon -u "' . $self->{clapi_user} . '" -p "' . $self->{clapi_password} . '" -w -o CentreonWorker -a processQueue'; - $self->send_internal_action( + $self->send_internal_action({ action => 'COMMAND', token => $options{token}, data => { @@ -586,8 +585,8 @@ sub action_addimporttaskwithparent { ], parameters => { no_fork => 1 } } - ); - $self->send_internal_action( + }); + $self->send_internal_action({ action => 'COMMAND', token => $options{token}, data => { @@ -598,7 +597,7 @@ sub action_addimporttaskwithparent { } ] } - ); + }); $self->send_log( code => GORGONE_ACTION_FINISH_OK, @@ -774,23 +773,14 @@ sub action_centreoncommand { return 0; } -sub event { - while (1) { - my $message = $connector->read_message(); - last if (!defined($message)); - - $connector->{logger}->writeLogDebug("[legacycmd] Event: $message"); - if ($message =~ /^\[(.*?)\]/) { - if ((my $method = $connector->can('action_' . lc($1)))) { - $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; - my ($action, $token) = ($1, $2); - my ($rv, $data) = $connector->json_decode(argument => $3, token => $token); - next if ($rv); - - $method->($connector, token => $token, data => $data); - } - } +sub periodic_exec { + if ($connector->{stop} == 1) { + $connector->{logger}->writeLogInfo("[legacycmd] $$ has quit"); + exit(0); } + + $connector->cache_refresh(); + $connector->handle_cmd_files(); } sub run { @@ -805,17 +795,18 @@ sub run { $self->{clapi_password} = $self->{tpapi_clapi}->get_password(protected => 1); # Connect internal - $connector->{internal_socket} = gorgone::standard::library::connect_com( + $self->{internal_socket} = gorgone::standard::library::connect_com( + context => $self->{zmq_context}, zmq_type => 'ZMQ_DEALER', name => 'gorgone-legacycmd', logger => $self->{logger}, type => $self->get_core_config(name => 'internal_com_type'), path => $self->get_core_config(name => 'internal_com_path') ); - $connector->send_internal_action( + $self->send_internal_action({ action => 'LEGACYCMDREADY', data => {} - ); + }); $self->{db_centreon} = gorgone::class::db->new( dsn => $self->{config_db_centreon}->{dsn}, @@ -829,24 +820,9 @@ sub run { db_centreon => $self->{db_centreon} ); - $self->{poll} = [ - { - socket => $connector->{internal_socket}, - events => ZMQ_POLLIN, - callback => \&event, - } - ]; - while (1) { - my $rev = scalar(zmq_poll($self->{poll}, 2000)); - if ($rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("[legacycmd] $$ has quit"); - zmq_close($connector->{internal_socket}); - exit(0); - } - - $self->cache_refresh(); - $self->handle_cmd_files(); - } + my $watcher_timer = $self->{loop}->timer(5, 5, \&periodic_exec); + my $watcher_io = $self->{loop}->io($connector->{internal_socket}->get_fd(), EV::READ, sub { $connector->event() } ); + $self->{loop}->run(); } 1; diff --git a/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm b/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm index 4cba1bdfedd..aac8608661a 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm @@ -68,20 +68,20 @@ sub routing { } if (gorgone::class::core::waiting_ready(ready => \$legacycmd->{ready}) == 0) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'gorgone-legacycmd: still no ready' }, json_encode => 1 - ); + }); return undef; } $options{gorgone}->send_internal_message( identity => 'gorgone-legacycmd', action => $options{action}, - data => $options{data}, + raw_data_ref => $options{frame}->getRawData(), token => $options{token} ); } diff --git a/gorgone/gorgone/modules/centreon/mbi/etl/class.pm b/gorgone/gorgone/modules/centreon/mbi/etl/class.pm index d957546c6a3..420342fcc25 100644 --- a/gorgone/gorgone/modules/centreon/mbi/etl/class.pm +++ b/gorgone/gorgone/modules/centreon/mbi/etl/class.pm @@ -28,8 +28,6 @@ use gorgone::standard::library; use gorgone::standard::constants qw(:all); use gorgone::class::sqlquery; use gorgone::class::http::http; -use ZMQ::LibZMQ4; -use ZMQ::Constants qw(:all); use XML::LibXML::Simple; use JSON::XS; use gorgone::modules::centreon::mbi::libs::Messages; @@ -38,6 +36,7 @@ use gorgone::modules::centreon::mbi::etl::event::main; use gorgone::modules::centreon::mbi::etl::perfdata::main; use gorgone::modules::centreon::mbi::libs::centreon::ETLProperties; use Try::Tiny; +use EV; use constant NONE => 0; use constant RUNNING => 1; @@ -174,7 +173,7 @@ sub db_parse_xml { sub execute_action { my ($self, %options) = @_; - $self->send_internal_action( + $self->send_internal_action({ action => 'ADDLISTENER', data => [ { @@ -184,7 +183,7 @@ sub execute_action { timeout => 43200 } ] - ); + }); my $content = { dbmon => $self->{run}->{dbmon}, @@ -201,14 +200,14 @@ sub execute_action { $content->{options} = $self->{run}->{options}; } - $self->send_internal_action( + $self->send_internal_action({ action => $options{action}, token => $self->{module_id} . '-' . $self->{run}->{token} . '-' . $options{substep}, data => { instant => 1, content => $content } - ); + }); } sub watch_etl_event { @@ -767,7 +766,7 @@ sub action_centreonmbietlkill { $self->{logger}->writeLogDebug('[mbi-etl] kill sent to the module etlworkers'); - $self->send_internal_action( + $self->send_internal_action({ action => 'KILL', token => $options{token}, data => { @@ -775,7 +774,7 @@ sub action_centreonmbietlkill { package => 'gorgone::modules::centreon::mbi::etlworkers::hooks' } } - ); + }); # RUNNING or STOP $self->send_log( @@ -847,56 +846,34 @@ sub action_centreonmbietlstatus { return 0; } -sub event { - while (1) { - my $message = $connector->read_message(); - last if (!defined($message)); - $connector->{logger}->writeLogDebug("[mbi-etl] Event: $message"); - if ($message =~ /^\[(.*?)\]/) { - if ((my $method = $connector->can('action_' . lc($1)))) { - $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; - my ($action, $token) = ($1, $2); - my ($rv, $data) = $connector->json_decode(argument => $3, token => $token); - next if ($rv); - $method->($connector, token => $token, data => $data); - } - } +sub periodic_exec { + if ($connector->{stop} == 1) { + $connector->{logger}->writeLogInfo("[" . $connector->{module_id} . "] $$ has quit"); + exit(0); } } sub run { my ($self, %options) = @_; - # Connect internal - $connector->{internal_socket} = gorgone::standard::library::connect_com( + $self->{internal_socket} = gorgone::standard::library::connect_com( + context => $self->{zmq_context}, zmq_type => 'ZMQ_DEALER', name => 'gorgone-' . $self->{module_id}, logger => $self->{logger}, type => $self->get_core_config(name => 'internal_com_type'), path => $self->get_core_config(name => 'internal_com_path') ); - $connector->send_internal_action( + $self->send_internal_action({ action => 'CENTREONMBIETLREADY', data => {} - ); - $self->{poll} = [ - { - socket => $connector->{internal_socket}, - events => ZMQ_POLLIN, - callback => \&event - } - ]; - - while (1) { - my $rev = scalar(zmq_poll($self->{poll}, 5000)); - if (defined($rev) && $rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("[" . $self->{module_id} . "] $$ has quit"); - zmq_close($connector->{internal_socket}); - exit(0); - } - } + }); + + my $watcher_timer = $self->{loop}->timer(5, 5, \&periodic_exec); + my $watcher_io = $self->{loop}->io($self->{internal_socket}->get_fd(), EV::READ, sub { $connector->event() } ); + $self->{loop}->run(); } 1; diff --git a/gorgone/gorgone/modules/centreon/mbi/etl/hooks.pm b/gorgone/gorgone/modules/centreon/mbi/etl/hooks.pm index 48d8471b10b..bc210ca41e6 100644 --- a/gorgone/gorgone/modules/centreon/mbi/etl/hooks.pm +++ b/gorgone/gorgone/modules/centreon/mbi/etl/hooks.pm @@ -64,20 +64,20 @@ sub routing { } if (gorgone::class::core::waiting_ready(ready => \$run->{ready}) == 0) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'gorgone-' . NAME . ': still no ready' }, json_encode => 1 - ); + }); return undef; } $options{gorgone}->send_internal_message( identity => 'gorgone-' . NAME, action => $options{action}, - data => $options{data}, + raw_data_ref => $options{frame}->getRawData(), token => $options{token} ); } diff --git a/gorgone/gorgone/modules/centreon/mbi/etl/import/main.pm b/gorgone/gorgone/modules/centreon/mbi/etl/import/main.pm index b31636854b8..39551763b6d 100644 --- a/gorgone/gorgone/modules/centreon/mbi/etl/import/main.pm +++ b/gorgone/gorgone/modules/centreon/mbi/etl/import/main.pm @@ -44,7 +44,7 @@ sub createTables { my ($etl, $periods, $options, $notTimedTables) = @_; #Creating all centreon bi tables exept the one already created - my $sth = $etl->{run}->{dbmon_centstorage_con}->query("SHOW TABLES LIKE 'mod_bi_%'"); + my $sth = $etl->{run}->{dbmon_centstorage_con}->query({ query => "SHOW TABLES LIKE 'mod_bi_%'" }); while (my @row = $sth->fetchrow_array()) { my $name = $row[0]; if (!$biTables->tableExists($name)) { @@ -82,7 +82,7 @@ sub createTables { } my $tables = join('|', @$notTimedTables); - $sth = $etl->{run}->{dbmon_centstorage_con}->query("SHOW TABLES LIKE 'mod_bam_reporting_%'"); + $sth = $etl->{run}->{dbmon_centstorage_con}->query({ query => "SHOW TABLES LIKE 'mod_bam_reporting_%'" }); while (my @row = $sth->fetchrow_array()) { my $name = $row[0]; next if ($name =~ /^(?:$tables)$/); @@ -115,7 +115,7 @@ sub extractData { push @{$action->{sql}}, [ "[CREATE] Add table [$name]", $structure ]; if ($name eq 'hoststateevents' || $name eq 'servicestateevents') { # add drop indexes - my $indexes = $etl->{run}->{dbmon_centstorage_con}->query("SHOW INDEX FROM " . $name); + my $indexes = $etl->{run}->{dbmon_centstorage_con}->query({ query => "SHOW INDEX FROM " . $name }); my $previous = ''; while (my $row = $indexes->fetchrow_hashref()) { if ($row->{Key_name} ne $previous) { @@ -330,7 +330,7 @@ sub selectTables { } } - my $sth = $etl->{run}->{dbmon_centreon_con}->query("SELECT id FROM modules_informations WHERE name='centreon-bam-server'"); + my $sth = $etl->{run}->{dbmon_centreon_con}->query({ query => "SELECT id FROM modules_informations WHERE name='centreon-bam-server'" }); if (my $row = $sth->fetchrow_array() && $etlProperties->{'statistics.type'} ne 'perfdata') { push @notTimedTables, "mod_bam_reporting_ba_availabilities"; push @notTimedTables, "mod_bam_reporting_ba"; diff --git a/gorgone/gorgone/modules/centreon/mbi/etlworkers/class.pm b/gorgone/gorgone/modules/centreon/mbi/etlworkers/class.pm index 3bbadca7476..e7368c5a79e 100644 --- a/gorgone/gorgone/modules/centreon/mbi/etlworkers/class.pm +++ b/gorgone/gorgone/modules/centreon/mbi/etlworkers/class.pm @@ -27,8 +27,6 @@ use warnings; use gorgone::standard::library; use gorgone::standard::constants qw(:all); use gorgone::class::http::http; -use ZMQ::LibZMQ4; -use ZMQ::Constants qw(:all); use JSON::XS; use Try::Tiny; use gorgone::modules::centreon::mbi::etlworkers::import::main; @@ -36,6 +34,7 @@ use gorgone::modules::centreon::mbi::etlworkers::dimensions::main; use gorgone::modules::centreon::mbi::etlworkers::event::main; use gorgone::modules::centreon::mbi::etlworkers::perfdata::main; use gorgone::modules::centreon::mbi::libs::Messages; +use EV; my %handlers = (TERM => {}, HUP => {}); my ($connector); @@ -292,58 +291,34 @@ sub action_centreonmbietlworkersperfdata { ); } -sub event { - while (1) { - my $message = $connector->read_message(); - last if (!defined($message)); - - $connector->{logger}->writeLogDebug("[mbi-etlworkers] Event: $message"); - if ($message =~ /^\[(.*?)\]/) { - if ((my $method = $connector->can('action_' . lc($1)))) { - $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; - my ($action, $token) = ($1, $2); - my ($rv, $data) = $connector->json_decode(argument => $3, token => $token); - next if ($rv); - - $method->($connector, token => $token, data => $data); - } - } +sub periodic_exec { + if ($connector->{stop} == 1) { + $connector->{logger}->writeLogInfo("[" . $connector->{module_id} . "] $$ has quit"); + exit(0); } } sub run { my ($self, %options) = @_; - # Connect internal - $connector->{internal_socket} = gorgone::standard::library::connect_com( + $self->{internal_socket} = gorgone::standard::library::connect_com( + context => $self->{zmq_context}, zmq_type => 'ZMQ_DEALER', name => 'gorgone-' . $self->{module_id} . '-' . $self->{pool_id}, logger => $self->{logger}, type => $self->get_core_config(name => 'internal_com_type'), path => $self->get_core_config(name => 'internal_com_path') ); - $connector->send_internal_action( + $self->send_internal_action({ action => 'CENTREONMBIETLWORKERSREADY', data => { pool_id => $self->{pool_id} } - ); - $self->{poll} = [ - { - socket => $connector->{internal_socket}, - events => ZMQ_POLLIN, - callback => \&event - } - ]; - - while (1) { - my $rev = scalar(zmq_poll($self->{poll}, 5000)); - if (defined($rev) && $rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("[" . $self->{module_id} . "] $$ has quit"); - zmq_close($connector->{internal_socket}); - exit(0); - } - } + }); + + my $watcher_timer = $self->{loop}->timer(5, 5, \&periodic_exec); + my $watcher_io = $self->{loop}->io($self->{internal_socket}->get_fd(), EV::READ, sub { $connector->event() } ); + $self->{loop}->run(); } 1; diff --git a/gorgone/gorgone/modules/centreon/mbi/etlworkers/dimensions/main.pm b/gorgone/gorgone/modules/centreon/mbi/etlworkers/dimensions/main.pm index ec9c5dc9a89..f0206a4c2fd 100644 --- a/gorgone/gorgone/modules/centreon/mbi/etlworkers/dimensions/main.pm +++ b/gorgone/gorgone/modules/centreon/mbi/etlworkers/dimensions/main.pm @@ -73,12 +73,12 @@ sub copyLiveServicesToMonitoringDB { return if ($etlwk->{dbmon_centstorage_con}->sameParams(%{$options{dbbi}->{centstorage}}) == 1); - $etlwk->{dbmon_centstorage_con}->query("TRUNCATE TABLE mod_bi_liveservice"); - my $sth = $etlwk->{dbbi_centstorage_con}->query("SELECT id, name, timeperiod_id FROM mod_bi_liveservice"); + $etlwk->{dbmon_centstorage_con}->query({ query => "TRUNCATE TABLE mod_bi_liveservice" }); + my $sth = $etlwk->{dbbi_centstorage_con}->query({ query => "SELECT id, name, timeperiod_id FROM mod_bi_liveservice" }); while (my $row = $sth->fetchrow_hashref()) { my $insertQuery = "INSERT INTO mod_bi_liveservice (id, name, timeperiod_id) VALUES (". $row->{'id'} . ",'" . $row->{name} . "'," . $row->{timeperiod_id} . ")"; - $etlwk->{dbmon_centstorage_con}->query($insertQuery); + $etlwk->{dbmon_centstorage_con}->query({ query => $insertQuery }); } } @@ -168,7 +168,7 @@ sub insertCentileParamToBIStorage{ my $sth; #Insert potential missing time periods related to centile calculation in mod_bi_liveservices - $sth = $etlwk->{dbbi_centreon_con}->query("SELECT tp_id, tp_name FROM timeperiod WHERE tp_id IN (SELECT timeperiod_id FROM mod_bi_options_centiles)"); + $sth = $etlwk->{dbbi_centreon_con}->query({ query => "SELECT tp_id, tp_name FROM timeperiod WHERE tp_id IN (SELECT timeperiod_id FROM mod_bi_options_centiles)" }); while (my $row = $sth->fetchrow_hashref()) { $result{$row->{tp_id}} = $row->{tp_name}; } @@ -185,13 +185,13 @@ sub insertCentileParamToBIStorage{ #In case of rebuild, delete all centile parameters if ($options{options}->{rebuild} == 1){ - $etlwk->{dbbi_centstorage_con}->query("TRUNCATE TABLE mod_bi_centiles"); + $etlwk->{dbbi_centstorage_con}->query({ query => "TRUNCATE TABLE mod_bi_centiles" }); } - $sth = $etlwk->{dbbi_centreon_con}->query("select * from mod_bi_options_centiles"); + $sth = $etlwk->{dbbi_centreon_con}->query({ query => "select * from mod_bi_options_centiles" }); while (my $row = $sth->fetchrow_hashref()) { my ($tpName,$liveServiceId) = $liveService->getLiveServicesByNameForTpId($row->{'timeperiod_id'}); my $insertQuery = "INSERT IGNORE INTO mod_bi_centiles (id, centile_param, liveservice_id,tp_name) VALUES (".$row->{'id'}.",'".$row->{'centile_param'}."',".$liveServiceId.",'".$tpName."')"; - $etlwk->{dbbi_centstorage_con}->query($insertQuery); + $etlwk->{dbbi_centstorage_con}->query({ query => $insertQuery }); } } @@ -200,12 +200,12 @@ sub copyCentileToMonitoringDB { return if ($etlwk->{dbmon_centstorage_con}->sameParams(%{$options{dbbi}->{centstorage}}) == 1); - $etlwk->{dbmon_centstorage_con}->query("TRUNCATE TABLE mod_bi_centiles"); - my $sth = $etlwk->{dbbi_centstorage_con}->query("SELECT id, centile_param, liveservice_id, tp_name FROM mod_bi_centiles"); + $etlwk->{dbmon_centstorage_con}->query({ query => "TRUNCATE TABLE mod_bi_centiles" }); + my $sth = $etlwk->{dbbi_centstorage_con}->query({ query => "SELECT id, centile_param, liveservice_id, tp_name FROM mod_bi_centiles" }); while (my $row = $sth->fetchrow_hashref()) { my $insertQuery = "INSERT INTO mod_bi_centiles (id, centile_param, liveservice_id,tp_name) VALUES (". $row->{id} . ",'" . $row->{centile_param} . "'," . $row->{liveservice_id} . ",'" . $row->{tp_name} . "')"; - $etlwk->{dbmon_centstorage_con}->query($insertQuery); + $etlwk->{dbmon_centstorage_con}->query({ query => $insertQuery }); } } diff --git a/gorgone/gorgone/modules/centreon/mbi/etlworkers/event/main.pm b/gorgone/gorgone/modules/centreon/mbi/etlworkers/event/main.pm index 68fab49864c..b83dd818a5b 100644 --- a/gorgone/gorgone/modules/centreon/mbi/etlworkers/event/main.pm +++ b/gorgone/gorgone/modules/centreon/mbi/etlworkers/event/main.pm @@ -70,9 +70,9 @@ sub sql { foreach (@{$options{params}->{sql}}) { $etlwk->{messages}->writeLog('INFO', $_->[0]); if ($options{params}->{db} eq 'centstorage') { - $etlwk->{dbbi_centstorage_con}->query($_->[1]); + $etlwk->{dbbi_centstorage_con}->query({ query => $_->[1] }); } elsif ($options{params}->{db} eq 'centreon') { - $etlwk->{dbbi_centreon_con}->query($_->[1]); + $etlwk->{dbbi_centreon_con}->query({ query => $_->[1] }); } } } @@ -109,13 +109,13 @@ sub processEventsHosts { $request .= " INNER JOIN mod_bi_tmp_today_hosts t2 on t1.host_id = t2.host_id"; $etlwk->{messages}->writeLog("INFO", "[HOST] Loading calculated events in reporting table"); - $etlwk->{dbbi_centstorage_con}->query($request); + $etlwk->{dbbi_centstorage_con}->query({ query => $request }); if ($options{options}->{rebuild} == 1 && $options{options}->{rebuild} == 0) { $etlwk->{messages}->writeLog("DEBUG", "[HOST] Creating index"); - $etlwk->{dbbi_centstorage_con}->query('ALTER TABLE mod_bi_hoststateevents ADD INDEX `modbihost_id` (`modbihost_id`,`modbiliveservice_id`,`state`,`start_time`,`end_time`)'); - $etlwk->{dbbi_centstorage_con}->query('ALTER TABLE mod_bi_hoststateevents ADD INDEX `state` (`state`,`modbiliveservice_id`,`start_time`,`end_time`)'); - $etlwk->{dbbi_centstorage_con}->query('ALTER TABLE mod_bi_hoststateevents ADD INDEX `idx_mod_bi_hoststateevents_end_time` (`end_time`)'); + $etlwk->{dbbi_centstorage_con}->query({ query => 'ALTER TABLE mod_bi_hoststateevents ADD INDEX `modbihost_id` (`modbihost_id`,`modbiliveservice_id`,`state`,`start_time`,`end_time`)' }); + $etlwk->{dbbi_centstorage_con}->query({ query => 'ALTER TABLE mod_bi_hoststateevents ADD INDEX `state` (`state`,`modbiliveservice_id`,`start_time`,`end_time`)' }); + $etlwk->{dbbi_centstorage_con}->query({ query => 'ALTER TABLE mod_bi_hoststateevents ADD INDEX `idx_mod_bi_hoststateevents_end_time` (`end_time`)' }); } } @@ -151,13 +151,13 @@ sub processEventsServices { $request .= " ON t1.host_id = t2.host_id AND t1.service_id = t2.service_id"; $etlwk->{messages}->writeLog("INFO", "[SERVICE] Loading calculated events in reporting table"); - $etlwk->{dbbi_centstorage_con}->query($request); + $etlwk->{dbbi_centstorage_con}->query({ query => $request }); if ($options{options}->{rebuild} == 1 && $options{options}->{rebuild} == 0) { $etlwk->{messages}->writeLog("DEBUG", "[SERVICE] Creating index"); - $etlwk->{dbbi_centstorage_con}->query('ALTER TABLE mod_bi_servicestateevents ADD INDEX `modbiservice_id` (`modbiservice_id`,`modbiliveservice_id`,`state`,`start_time`,`end_time`)'); - $etlwk->{dbbi_centstorage_con}->query('ALTER TABLE mod_bi_servicestateevents ADD INDEX `state` (`state`,`modbiliveservice_id`,`start_time`,`end_time`)'); - $etlwk->{dbbi_centstorage_con}->query('ALTER TABLE mod_bi_servicestateevents ADD INDEX `idx_mod_bi_servicestateevents_end_time` (`end_time`)'); + $etlwk->{dbbi_centstorage_con}->query({ query => 'ALTER TABLE mod_bi_servicestateevents ADD INDEX `modbiservice_id` (`modbiservice_id`,`modbiliveservice_id`,`state`,`start_time`,`end_time`)' }); + $etlwk->{dbbi_centstorage_con}->query({ query => 'ALTER TABLE mod_bi_servicestateevents ADD INDEX `state` (`state`,`modbiliveservice_id`,`start_time`,`end_time`)' }); + $etlwk->{dbbi_centstorage_con}->query({ query => 'ALTER TABLE mod_bi_servicestateevents ADD INDEX `idx_mod_bi_servicestateevents_end_time` (`end_time`)' }); } } diff --git a/gorgone/gorgone/modules/centreon/mbi/etlworkers/hooks.pm b/gorgone/gorgone/modules/centreon/mbi/etlworkers/hooks.pm index c75b4a22ba6..21a861e5aa2 100644 --- a/gorgone/gorgone/modules/centreon/mbi/etlworkers/hooks.pm +++ b/gorgone/gorgone/modules/centreon/mbi/etlworkers/hooks.pm @@ -67,19 +67,16 @@ sub init { sub routing { my (%options) = @_; - my $data; - eval { - $data = JSON::XS->new->decode($options{data}); - }; - if ($@) { - $options{logger}->writeLogError("[proxy] Cannot decode json data: $@"); - gorgone::standard::library::add_history( + my $data = $options{frame}->decodeData(); + if (!defined($data)) { + $options{logger}->writeLogError("[" . NAME . "] Cannot decode json data: " . $options{frame}->getLastError()); + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => NAME . ' - cannot decode json' }, json_encode => 1 - ); + }); return undef; } @@ -92,13 +89,13 @@ sub routing { my $pool_id = rr_pool(); if (!defined($pool_id)) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => NAME . ' - no pool ready' }, json_encode => 1 - ); + }); return undef; } @@ -107,7 +104,7 @@ sub routing { $options{gorgone}->send_internal_message( identity => $identity, action => $options{action}, - data => $options{data}, + raw_data_ref => $options{frame}->getRawData(), token => $options{token} ); } diff --git a/gorgone/gorgone/modules/centreon/mbi/etlworkers/import/main.pm b/gorgone/gorgone/modules/centreon/mbi/etlworkers/import/main.pm index c3dc86a4182..2430aea7229 100644 --- a/gorgone/gorgone/modules/centreon/mbi/etlworkers/import/main.pm +++ b/gorgone/gorgone/modules/centreon/mbi/etlworkers/import/main.pm @@ -33,9 +33,9 @@ sub sql { foreach (@{$options{params}->{sql}}) { $etlwk->{messages}->writeLog('INFO', $_->[0]); if ($options{params}->{db} eq 'centstorage') { - $etlwk->{dbbi_centstorage_con}->query($_->[1]); + $etlwk->{dbbi_centstorage_con}->query({ query => $_->[1] }); } elsif ($options{params}->{db} eq 'centreon') { - $etlwk->{dbbi_centreon_con}->query($_->[1]); + $etlwk->{dbbi_centreon_con}->query({ query => $_->[1] }); } } } @@ -75,9 +75,9 @@ sub load { command($etlwk, params => { command => $options{params}->{dump}, message => $options{params}->{message} }); if ($options{params}->{db} eq 'centstorage') { - $etlwk->{dbbi_centstorage_con}->query($options{params}->{load}); + $etlwk->{dbbi_centstorage_con}->query({ query => $options{params}->{load} }); } elsif ($options{params}->{db} eq 'centreon') { - $etlwk->{dbbi_centreon_con}->query($options{params}->{load}); + $etlwk->{dbbi_centreon_con}->query({ query => $options{params}->{load} }); } unlink($options{params}->{file}); diff --git a/gorgone/gorgone/modules/centreon/mbi/etlworkers/perfdata/main.pm b/gorgone/gorgone/modules/centreon/mbi/etlworkers/perfdata/main.pm index d82c6c4b52c..ead6bbd9c61 100644 --- a/gorgone/gorgone/modules/centreon/mbi/etlworkers/perfdata/main.pm +++ b/gorgone/gorgone/modules/centreon/mbi/etlworkers/perfdata/main.pm @@ -71,9 +71,9 @@ sub sql { foreach (@{$options{params}->{sql}}) { $etlwk->{messages}->writeLog('INFO', $_->[0]); if ($options{params}->{db} eq 'centstorage') { - $etlwk->{dbbi_centstorage_con}->query($_->[1]); + $etlwk->{dbbi_centstorage_con}->query({ query => $_->[1] }); } elsif ($options{params}->{db} eq 'centreon') { - $etlwk->{dbbi_centreon_con}->query($_->[1]); + $etlwk->{dbbi_centreon_con}->query({ query => $_->[1] }); } } } diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHost.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHost.pm index 114783fb194..5ba2c40e667 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHost.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHost.pm @@ -50,7 +50,7 @@ sub getHostsInfo { my $query = "SELECT `id`, `host_id`, `host_name`, `hc_id`, `hc_name`, `hg_id`, `hg_name`"; $query .= " FROM `".$self->{"today_table"}."`"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); my %result = (); while (my $row = $sth->fetchrow_hashref()) { if (defined($result{$row->{'host_id'}})) { @@ -80,7 +80,7 @@ sub insert { my $fields = "id, host_name, host_id, hc_id, hc_name, hg_id, hg_name"; my $query = "INSERT INTO ".$self->{"today_table"}." (".$fields.")"; $query .= " SELECT ".$fields." FROM ".$self->{"table"}." "; - $db->query($query); + $db->query({ query => $query }); } sub update { @@ -93,10 +93,10 @@ sub update { $self->createTempStorageTable($useMemory); $self->joinNewAndCurrentEntries(); $self->insertNewEntries(); - $db->query("DROP TABLE `".$self->{"tmp_comp_storage"}."`"); + $db->query({ query => "DROP TABLE `".$self->{"tmp_comp_storage"}."`" }); $self->createTempTodayTable("false"); $self->insertTodayEntries(); - $db->query("DROP TABLE `".$self->{"tmp_comp"}."`"); + $db->query({ query => "DROP TABLE `".$self->{"tmp_comp"}."`" }); } sub insertIntoTable { @@ -141,7 +141,7 @@ sub insertIntoTable { sub createTempComparisonTable { my ($self, $useMemory) = @_; my $db = $self->{"centstorage"}; - $db->query("DROP TABLE IF EXISTS `".$self->{"tmp_comp"}."`"); + $db->query({ query => "DROP TABLE IF EXISTS `" . $self->{"tmp_comp"} . "`" }); my $query = "CREATE TABLE `".$self->{"tmp_comp"}."` ("; $query .= "`host_id` int(11) NOT NULL,`host_name` varchar(255) NOT NULL,"; $query .= "`hc_id` int(11) DEFAULT NULL, `hc_name` varchar(255) NOT NULL,"; @@ -151,14 +151,14 @@ sub createTempComparisonTable { }else { $query .= ") ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;"; } - $db->query($query); + $db->query({ query => $query }); } sub createTempStorageTable { my ($self,$useMemory) = @_; my $db = $self->{"centstorage"}; - $db->query("DROP TABLE IF EXISTS `".$self->{"tmp_comp_storage"}."`"); + $db->query({ query => "DROP TABLE IF EXISTS `" . $self->{"tmp_comp_storage"} . "`" }); my $query = "CREATE TABLE `".$self->{"tmp_comp_storage"}."` ("; $query .= "`id` INT NOT NULL,"; $query .= "`host_id` int(11) NOT NULL,`host_name` varchar(255) NOT NULL,"; @@ -170,14 +170,14 @@ sub createTempStorageTable { }else { $query .= ") ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;"; } - $db->query($query); + $db->query({ query => $query }); } sub createTempTodayTable { my ($self,$useMemory) = @_; my $db = $self->{"centstorage"}; - $db->query("DROP TABLE IF EXISTS `".$self->{"today_table"}."`"); + $db->query({ query => "DROP TABLE IF EXISTS `".$self->{"today_table"}."`" }); my $query = "CREATE TABLE `".$self->{"today_table"}."` ("; $query .= "`id` INT NOT NULL,"; $query .= "`host_id` int(11) NOT NULL,`host_name` varchar(255) NOT NULL,"; @@ -189,7 +189,7 @@ sub createTempTodayTable { }else { $query .= ") ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;"; } - $db->query($query); + $db->query({ query => $query }); } sub joinNewAndCurrentEntries { @@ -199,7 +199,7 @@ sub joinNewAndCurrentEntries { my $query = "INSERT INTO ".$self->{"tmp_comp_storage"}. " (id, host_name, host_id, hc_id, hc_name, hg_id, hg_name)"; $query .= " SELECT IFNULL(h.id, 0), t.host_name, t.host_id, t.hc_id, t.hc_name, t.hg_id, t.hg_name FROM ".$self->{"tmp_comp"}." t"; $query .= " LEFT JOIN ".$self->{"table"}." h USING (host_name, host_id, hc_id, hc_name, hg_id, hg_name)"; - $db->query($query); + $db->query({ query => $query }); } sub insertNewEntries { @@ -209,7 +209,7 @@ sub insertNewEntries { my $query = " INSERT INTO `".$self->{"table"}."` (".$fields.") "; $query .= " SELECT ".$fields." FROM ".$self->{"tmp_comp_storage"}; $query .= " WHERE id = 0"; - $db->query($query); + $db->query({ query => $query }); } sub insertTodayEntries { @@ -219,15 +219,15 @@ sub insertTodayEntries { my $query = "INSERT INTO ".$self->{"today_table"}." (id, host_name, host_id, hc_id, hc_name, hg_id, hg_name)"; $query .= " SELECT h.id, t.host_name, t.host_id, t.hc_id, t.hc_name, t.hg_id, t.hg_name FROM ".$self->{"tmp_comp"}." t"; $query .= " JOIN ".$self->{"table"}." h USING (host_name, host_id, hc_id, hc_name, hg_id, hg_name)"; - $db->query($query); + $db->query({ query => $query }); } sub truncateTable { my $self = shift; my $db = $self->{"centstorage"}; - $db->query("TRUNCATE TABLE `".$self->{"table"}."`"); - $db->query("ALTER TABLE `".$self->{"table"}."` AUTO_INCREMENT=1"); + $db->query({ query => "TRUNCATE TABLE `".$self->{"table"}."`" }); + $db->query({ query => "ALTER TABLE `".$self->{"table"}."` AUTO_INCREMENT=1" }); } 1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHostCategory.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHostCategory.pm index dec1d24d88e..ad0a4442b6a 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHostCategory.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHostCategory.pm @@ -46,7 +46,7 @@ sub getAllEntries { my $query = "SELECT `hc_id`, `hc_name`"; $query .= " FROM `mod_bi_hostcategories`"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); my @entries = (); while (my $row = $sth->fetchrow_hashref()) { push @entries, $row->{"hc_id"}.";".$row->{"hc_name"}; @@ -61,7 +61,7 @@ sub getEntryIds { my $query = "SELECT `id`, `hc_id`, `hc_name`"; $query .= " FROM `mod_bi_hostcategories`"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); my %entries = (); while (my $row = $sth->fetchrow_hashref()) { $entries{$row->{"hc_id"}.";".$row->{"hc_name"}} = $row->{"id"}; @@ -122,8 +122,8 @@ sub truncateTable { my $db = $self->{"centstorage"}; my $query = "TRUNCATE TABLE `mod_bi_hostcategories`"; - $db->query($query); - $db->query("ALTER TABLE `mod_bi_hostcategories` AUTO_INCREMENT=1"); + $db->query({ query => $query }); + $db->query({ query => "ALTER TABLE `mod_bi_hostcategories` AUTO_INCREMENT=1" }); } 1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHostGroup.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHostGroup.pm index acc6e2d3f55..f5cdcf21b58 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHostGroup.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHostGroup.pm @@ -47,7 +47,7 @@ sub getAllEntries { my $query = "SELECT `id`, `hg_id`, `hg_name`"; $query .= " FROM `mod_bi_hostgroups`"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); my @entries = (); while (my $row = $sth->fetchrow_hashref()) { push @entries, $row->{"hg_id"}.";".$row->{"hg_name"}; @@ -62,7 +62,7 @@ sub getEntryIds { my $query = "SELECT `id`, `hg_id`, `hg_name`"; $query .= " FROM `mod_bi_hostgroups`"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); my %entries = (); while (my $row = $sth->fetchrow_hashref()) { $entries{$row->{"hg_id"}.";".$row->{"hg_name"}} = $row->{"id"}; @@ -124,8 +124,8 @@ sub truncateTable { my $db = $self->{"centstorage"}; my $query = "TRUNCATE TABLE `mod_bi_hostgroups`"; - $db->query($query); - $db->query("ALTER TABLE `mod_bi_hostgroups` AUTO_INCREMENT=1"); + $db->query({ query => $query }); + $db->query({ query => "ALTER TABLE `mod_bi_hostgroups` AUTO_INCREMENT=1" }); } 1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHostStateEvents.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHostStateEvents.pm index 2593910f37f..e89e20bf069 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHostStateEvents.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIHostStateEvents.pm @@ -56,7 +56,7 @@ sub getTimeColumn() { sub createTempBIEventsTable{ my ($self) = @_; my $db = $self->{"centstorage"}; - $db->query("DROP TABLE IF EXISTS `mod_bi_hoststateevents_tmp`"); + $db->query({ query => "DROP TABLE IF EXISTS `mod_bi_hoststateevents_tmp`" }); my $createTable = " CREATE TABLE `mod_bi_hoststateevents_tmp` ("; $createTable .= " `host_id` int(11) NOT NULL,"; $createTable .= " `modbiliveservice_id` tinyint(4) NOT NULL,"; @@ -69,7 +69,7 @@ sub createTempBIEventsTable{ $createTable .= " `last_update` tinyint(4) NOT NULL DEFAULT '0',"; $createTable .= " KEY `modbihost_id` (`host_id`)"; $createTable .= " ) ENGINE=InnoDB DEFAULT CHARSET=utf8"; - $db->query($createTable); + $db->query({ query => $createTable }); } sub prepareTempQuery { @@ -77,10 +77,10 @@ sub prepareTempQuery { my $db = $self->{"centstorage"}; my $query = "INSERT INTO `".$self->{'tmp_name'}."`". - " (`host_id`, `modbiliveservice_id`,". - " `state`, `start_time`, `sla_duration`,". - " `end_time`, `ack_time`, `last_update`, `duration`) ". - " VALUES (?,?,?,?,?,?,?,?, TIMESTAMPDIFF(SECOND, FROM_UNIXTIME(?), FROM_UNIXTIME(?)))"; + " (`host_id`, `modbiliveservice_id`,". + " `state`, `start_time`, `sla_duration`,". + " `end_time`, `ack_time`, `last_update`, `duration`) ". + " VALUES (?,?,?,?,?,?,?,?, TIMESTAMPDIFF(SECOND, FROM_UNIXTIME(?), FROM_UNIXTIME(?)))"; $self->{'statement'} = $db->prepare($query); $self->{'dbinstance'} = $db->getInstance; ($self->{'dbinstance'})->begin_work; @@ -141,7 +141,7 @@ sub getDayEvents { $query .= " AND `end_time` > ".$start.""; $query .= " AND `state` in (0,1,2)"; $query .= " AND modbiliveservice_id = ".$liveserviceId; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); #For each events, for the current day, calculate statistics for the day my $rows = []; @@ -218,7 +218,7 @@ sub getNbEvents { $query .= " AND end_time > UNIX_TIMESTAMP('".$start."')"; $query .= " AND state in (1,2)"; $query .= " GROUP BY state"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); my ($downEvents, $unrEvents) = (undef, undef); while (my $row = $sth->fetchrow_hashref()) { @@ -237,7 +237,7 @@ sub deleteUnfinishedEvents { my $query = "DELETE FROM `mod_bi_hoststateevents`"; $query .= " WHERE last_update = 1 OR end_time is null"; - $db->query($query); + $db->query({ query => $query }); } 1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIMetric.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIMetric.pm index 4462aa3d322..caf4fb8503e 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIMetric.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIMetric.pm @@ -54,7 +54,7 @@ sub insert { $self->createTodayTable("false"); my $query = "INSERT INTO ".$self->{today_table}. " (id, metric_id, metric_name, sc_id,hg_id,hc_id)"; $query .= " SELECT id, metric_id, metric_name,sc_id,hg_id,hc_id FROM " . $self->{table} . " "; - $db->query($query); + $db->query({ query => $query }); } sub update { @@ -69,8 +69,8 @@ sub update { $self->createCRC32Table(); $self->createTodayTable("false"); $self->insertTodayEntries(); - $db->query("DROP TABLE `".$self->{"tmpTable"}."`"); - $db->query("DROP TABLE `".$self->{"CRC32"}."`"); + $db->query({ query => "DROP TABLE `".$self->{"tmpTable"}."`" }); + $db->query({ query => "DROP TABLE `".$self->{"CRC32"}."`" }); } sub insertMetricsIntoTable { @@ -84,7 +84,7 @@ sub insertMetricsIntoTable { $query .= " FROM `mod_bi_tmp_today_services` s, `metrics` m, `index_data` i"; $query .= " WHERE i.id = m.index_id and i.host_id=s.host_id and i.service_id=s.service_id"; $query .= " group by s.hg_id, s.hc_id, s.sc_id, m.index_id, m.metric_id"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); return $sth; } @@ -92,7 +92,7 @@ sub createTempTable { my ($self, $useMemory) = @_; my $db = $self->{"centstorage"}; - $db->query("DROP TABLE IF EXISTS `".$self->{"tmpTable"}."`"); + $db->query({ query => "DROP TABLE IF EXISTS `".$self->{"tmpTable"}."`" }); my $query = "CREATE TABLE `".$self->{"tmpTable"}."` ("; $query .= "`metric_id` int(11) NOT NULL,`metric_name` varchar(255) NOT NULL,`metric_unit` char(10) DEFAULT NULL,"; $query .= "`service_id` int(11) NOT NULL,`service_description` varchar(255) DEFAULT NULL,"; @@ -105,23 +105,23 @@ sub createTempTable { }else { $query .= ") ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;"; } - $db->query($query); + $db->query({ query => $query }); } sub createCRC32Table { my ($self) = @_; my $db = $self->{"centstorage"}; - $db->query("DROP TABLE IF EXISTS `".$self->{"CRC32"}."`"); + $db->query({ query => "DROP TABLE IF EXISTS `".$self->{"CRC32"}."`" }); my $query = "CREATE TABLE `".$self->{"CRC32"}."` CHARSET=utf8 COLLATE=utf8_general_ci"; $query .= " SELECT `id`, CRC32(CONCAT_WS('-', COALESCE(metric_id, '?'),"; $query .= " COALESCE(service_id, '?'),COALESCE(service_description, '?'),"; $query .= " COALESCE(host_id, '?'),COALESCE(host_name, '?'), COALESCE(sc_id, '?'),COALESCE(sc_name, '?'),"; $query .= " COALESCE(hc_id, '?'),COALESCE(hc_name, '?'), COALESCE(hg_id, '?'),COALESCE(hg_name, '?'))) as mycrc"; $query .= " FROM ".$self->{"table"}; - $db->query($query); + $db->query({ query => $query }); $query = "ALTER TABLE `".$self->{"CRC32"}."` ADD INDEX (`mycrc`)"; - $db->query($query); + $db->query({ query => $query }); } sub insertNewEntries { @@ -144,14 +144,14 @@ sub insertNewEntries { $query .= " AND tmpTable.hc_id=finalTable.hc_id AND tmpTable.hc_name=finalTable.hc_name"; $query .= " AND tmpTable.hg_id=finalTable.hg_id AND tmpTable.hg_name=finalTable.hg_name"; $query .= " WHERE finalTable.id is null"; - $db->query($query); + $db->query({ query => $query }); } sub createTodayTable { my ($self,$useMemory) = @_; my $db = $self->{"centstorage"}; - $db->query("DROP TABLE IF EXISTS `".$self->{"today_table"}."`"); + $db->query({ query => "DROP TABLE IF EXISTS `".$self->{"today_table"}."`" }); my $query = "CREATE TABLE `" . $self->{"today_table"} . "` ("; $query .= "`id` INT NOT NULL,"; $query .= "`metric_id` int(11) NOT NULL,"; @@ -166,7 +166,7 @@ sub createTodayTable { }else { $query .= ") ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;"; } - $db->query($query); + $db->query({ query => $query }); } sub insertTodayEntries { @@ -184,7 +184,7 @@ sub insertTodayEntries { $query .= " AND finalTable.sc_id=t.sc_id AND finalTable.sc_name=t.sc_name "; $query .= " AND finalTable.hc_id=t.hc_id AND finalTable.hc_name=t.hc_name "; $query .= " AND finalTable.hg_id=t.hg_id AND finalTable.hg_name=t.hg_name "; - $db->query($query); + $db->query({ query => $query }); } sub truncateTable { @@ -192,8 +192,8 @@ sub truncateTable { my $db = $self->{"centstorage"}; my $query = "TRUNCATE TABLE `".$self->{"table"}."`"; - $db->query($query); - $db->query("ALTER TABLE `".$self->{"table"}."` AUTO_INCREMENT=1"); + $db->query({ query => $query }); + $db->query({ query => "ALTER TABLE `".$self->{"table"}."` AUTO_INCREMENT=1" }); } 1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIService.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIService.pm index df607375d3d..981f5663bd8 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIService.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIService.pm @@ -54,7 +54,7 @@ sub insert { my $fields = "id, service_id, service_description, host_name, host_id, sc_id, sc_name, hc_id, hc_name, hg_id, hg_name"; my $query = "INSERT INTO ".$self->{"today_table"}. "(".$fields.")"; $query .= " SELECT ".$fields." FROM ".$self->{"table"}; - $db->query($query); + $db->query({ query => $query }); } sub update { @@ -68,8 +68,8 @@ sub update { $self->createCRC32Table(); $self->createTodayTable("false"); $self->insertTodayEntries(); - $db->query("DROP TABLE `".$self->{"tmpTable"}."`"); - $db->query("DROP TABLE `".$self->{"CRC32"}."`"); + $db->query({ query => "DROP TABLE `".$self->{"tmpTable"}."`" }); + $db->query({ query => "DROP TABLE `".$self->{"CRC32"}."`" }); } sub insertIntoTable { @@ -120,7 +120,7 @@ sub insertIntoTable { sub createTempTable { my ($self, $useMemory) = @_; my $db = $self->{"centstorage"}; - $db->query("DROP TABLE IF EXISTS `".$self->{"tmpTable"}."`"); + $db->query({ query => "DROP TABLE IF EXISTS `".$self->{"tmpTable"}."`" }); my $query = "CREATE TABLE `".$self->{"tmpTable"}."` ("; $query .= "`service_id` int(11) NOT NULL,`service_description` varchar(255) NOT NULL,"; $query .= "`sc_id` int(11) NOT NULL,`sc_name` varchar(255) NOT NULL,"; @@ -132,22 +132,22 @@ sub createTempTable { }else { $query .= ") ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;"; } - $db->query($query); + $db->query({ query => $query }); } sub createCRC32Table { my ($self) = @_; my $db = $self->{"centstorage"}; - $db->query("DROP TABLE IF EXISTS `".$self->{"CRC32"}."`"); + $db->query({ query => "DROP TABLE IF EXISTS `".$self->{"CRC32"}."`" }); my $query = "CREATE TABLE `".$self->{"CRC32"}."` CHARSET=utf8 COLLATE=utf8_general_ci"; $query .= " SELECT `id`, CRC32(CONCAT_WS('-', COALESCE(service_id, '?'),COALESCE(service_description, '?'),"; $query .= " COALESCE(host_id, '?'),COALESCE(host_name, '?'), COALESCE(sc_id, '?'),COALESCE(sc_name, '?'),"; $query .= " COALESCE(hc_id, '?'),COALESCE(hc_name, '?'), COALESCE(hg_id, '?'),COALESCE(hg_name, '?'))) as mycrc"; $query .= " FROM ".$self->{"table"}; - $db->query($query); + $db->query({ query => $query }); $query = "ALTER TABLE `".$self->{"CRC32"}."` ADD INDEX (`mycrc`)"; - $db->query($query); + $db->query({ query => $query }); } sub insertNewEntries { @@ -168,14 +168,14 @@ sub insertNewEntries { $query .= " AND tmpTable.hc_id=finalTable.hc_id AND tmpTable.hc_name=finalTable.hc_name"; $query .= " AND tmpTable.hg_id=finalTable.hg_id AND tmpTable.hg_name=finalTable.hg_name"; $query .= " WHERE finalTable.id is null"; - $db->query($query); + $db->query({ query => $query }); } sub createTodayTable { my ($self,$useMemory) = @_; my $db = $self->{"centstorage"}; - $db->query("DROP TABLE IF EXISTS `".$self->{"today_table"}."`"); + $db->query({ query => "DROP TABLE IF EXISTS `".$self->{"today_table"}."`" }); my $query = "CREATE TABLE `".$self->{"today_table"}."` ("; $query .= "`id` INT NOT NULL,"; $query .= "`service_id` int(11) NOT NULL,`service_description` varchar(255) NOT NULL,"; @@ -189,7 +189,7 @@ sub createTodayTable { }else { $query .= ") ENGINE=INNODB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;"; } - $db->query($query); + $db->query({ query => $query }); } sub insertTodayEntries { @@ -206,7 +206,7 @@ sub insertTodayEntries { $query .= " AND s.sc_id=t.sc_id AND s.sc_name=t.sc_name "; $query .= " AND s.hc_id=t.hc_id AND s.hc_name=t.hc_name "; $query .= " AND s.hg_id=t.hg_id AND s.hg_name=t.hg_name "; - $db->query($query); + $db->query({ query => $query }); } sub truncateTable { @@ -214,8 +214,8 @@ sub truncateTable { my $db = $self->{"centstorage"}; my $query = "TRUNCATE TABLE `".$self->{"table"}."`"; - $db->query($query); - $db->query("ALTER TABLE `".$self->{"table"}."` AUTO_INCREMENT=1"); + $db->query({ query => $query }); + $db->query({ query => "ALTER TABLE `".$self->{"table"}."` AUTO_INCREMENT=1" }); } 1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIServiceCategory.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIServiceCategory.pm index a95e7854baf..74e5d7f2e3f 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIServiceCategory.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIServiceCategory.pm @@ -47,12 +47,11 @@ sub getAllEntries { my $query = "SELECT `sc_id`, `sc_name`"; $query .= " FROM `mod_bi_servicecategories`"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); my @entries = (); while (my $row = $sth->fetchrow_hashref()) { push @entries, $row->{"sc_id"}.";".$row->{"sc_name"}; } - $sth->finish(); return (\@entries); } @@ -62,12 +61,11 @@ sub getEntryIds { my $query = "SELECT `id`, `sc_id`, `sc_name`"; $query .= " FROM `mod_bi_servicecategories`"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); my %entries = (); while (my $row = $sth->fetchrow_hashref()) { $entries{$row->{"sc_id"}.";".$row->{"sc_name"}} = $row->{"id"}; } - $sth->finish(); return (\%entries); } @@ -123,8 +121,8 @@ sub truncateTable { my $db = $self->{"centstorage"}; my $query = "TRUNCATE TABLE `mod_bi_servicecategories`"; - $db->query($query); - $db->query("ALTER TABLE `mod_bi_servicecategories` AUTO_INCREMENT=1"); + $db->query({ query => $query }); + $db->query({ query => "ALTER TABLE `mod_bi_servicecategories` AUTO_INCREMENT=1" }); } 1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIServiceStateEvents.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIServiceStateEvents.pm index 3840e1c9525..567634b680a 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIServiceStateEvents.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIServiceStateEvents.pm @@ -69,7 +69,7 @@ sub prepareQuery { sub createTempBIEventsTable { my ($self) = @_; my $db = $self->{"centstorage"}; - $db->query("DROP TABLE IF EXISTS `mod_bi_servicestateevents_tmp`"); + $db->query({ query => "DROP TABLE IF EXISTS `mod_bi_servicestateevents_tmp`" }); my $createTable = " CREATE TABLE `mod_bi_servicestateevents_tmp` ("; $createTable .= " `host_id` int(11) NOT NULL,"; $createTable .= " `service_id` int(11) NOT NULL,"; @@ -83,7 +83,7 @@ sub createTempBIEventsTable { $createTable .= " `last_update` tinyint(4) DEFAULT '0',"; $createTable .= " KEY `modbiservice_id` (`host_id`,`service_id`)"; $createTable .= " ) ENGINE=InnoDB DEFAULT CHARSET=utf8"; - $db->query($createTable); + $db->query({ query => $createTable }); } sub prepareTempQuery { @@ -143,7 +143,7 @@ sub getDayEvents { $query .= " AND `end_time` > " . $start; $query .= " AND `state` IN (0,1,2,3)"; $query .= " AND modbiliveservice_id=" . $liveserviceId; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); if (!scalar(@$ranges)) { return \%results; @@ -224,7 +224,7 @@ sub getNbEvents { $query .= " AND e.modbiliveservice_id=".$liveServiceID; $query .= " AND e.state in (1,2,3)"; $query .= " GROUP BY e.state"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); my ($warnEvents, $criticalEvents, $otherEvents) = (undef, undef, undef); while (my $row = $sth->fetchrow_hashref()) { @@ -245,7 +245,7 @@ sub deleteUnfinishedEvents { my $query = "DELETE FROM `".$self->{'name'}."`"; $query .= " WHERE last_update = 1 OR end_time is null"; - $db->query($query); + $db->query({ query => $query }); } 1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/DataQuality.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/DataQuality.pm index 746ec7452a9..f9f87a36728 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/bi/DataQuality.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/DataQuality.pm @@ -59,7 +59,7 @@ sub getDuplicateRelations { "FROM host_service_relation t1, host t2 WHERE t1.host_host_id = t2.host_id ". "AND t2.host_name not like '_Module%' group by host_host_id, service_service_id HAVING COUNT(*) > 1"; - my $sth = $self->{centreon}->query($duplicateEntriesQuery); + my $sth = $self->{centreon}->query({ query => $duplicateEntriesQuery }); while (my $row = $sth->fetchrow_hashref()) { if (defined($row->{host_host_id})) { $self->{logger}->writeLog( @@ -69,7 +69,7 @@ sub getDuplicateRelations { #Get all relation IDs related to duplicated data my $relationIdQuery = "SELECT hsr_id from host_service_relation ". "WHERE host_host_id = ".$row->{host_host_id}." AND service_service_id = ".$row->{service_service_id}; - my $sth2 = $self->{centreon}->query($relationIdQuery); + my $sth2 = $self->{centreon}->query({ query => $relationIdQuery }); while (my $hsr = $sth2->fetchrow_hashref()) { if (defined($hsr->{hsr_id})) { push(@relationIDS,$hsr->{hsr_id}); @@ -92,7 +92,7 @@ sub deleteDuplicateEntries { foreach (@relationIDS) { my $idToDelete = $_; my $deleteQuery = "DELETE FROM host_service_relation WHERE hsr_id = ".$idToDelete; - $self->{centreon}->query($deleteQuery) + $self->{centreon}->query({ query => $deleteQuery }) } } diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/Dumper.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/Dumper.pm index 6f4d114f778..198c52cc99e 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/bi/Dumper.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/Dumper.pm @@ -106,7 +106,7 @@ sub dumpTableStructure { my ($tableName) = (shift); my $sql = ""; - my $sth = $db->query("SHOW CREATE TABLE ".$tableName); + my $sth = $db->query({ query => "SHOW CREATE TABLE ".$tableName }); if (my $row = $sth->fetchrow_hashref()) { $sql = $row->{'Create Table'}; $sql =~ s/(CONSTRAINT.*\n)//g; @@ -126,7 +126,7 @@ sub insertData { my ($tableName, $inFile) = (shift, shift); my $query = "LOAD DATA INFILE '".$inFile."' INTO TABLE `".$tableName."`"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); } 1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/HGMonthAvailability.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/HGMonthAvailability.pm index 69157de8cdc..e0f3d1a82de 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/bi/HGMonthAvailability.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/HGMonthAvailability.pm @@ -80,13 +80,13 @@ sub insertStats { $append = ','; $counter++; if ($counter >= $insertParam) { - $self->{centstorage}->query($query); + $self->{centstorage}->query({ query => $query }); $query = $query_start; $counter = 0; $append = ''; } } - $self->{centstorage}->query($query) if ($counter > 0); + $self->{centstorage}->query({ query => $query }) if ($counter > 0); } 1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/HGServiceMonthAvailability.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/HGServiceMonthAvailability.pm index b8d23c3b413..7422107a833 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/bi/HGServiceMonthAvailability.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/HGServiceMonthAvailability.pm @@ -81,13 +81,13 @@ sub insertStats { $append = ','; $counter++; if ($counter >= $insertParam) { - $self->{centstorage}->query($query); + $self->{centstorage}->query({ query => $query }); $query = $query_start; $counter = 0; $append = ''; } } - $self->{centstorage}->query($query) if ($counter > 0); + $self->{centstorage}->query({ query => $query }) if ($counter > 0); } 1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/HostAvailability.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/HostAvailability.pm index e6a2d7bfe85..f908982669a 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/bi/HostAvailability.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/HostAvailability.pm @@ -86,13 +86,13 @@ sub insertStats { $append = ','; $counter++; if ($counter >= $insertParam) { - $self->{centstorage}->query($query); + $self->{centstorage}->query({ query => $query }); $query = $query_start; $counter = 0; $append = ''; } } - $self->{centstorage}->query($query) if ($counter > 0); + $self->{centstorage}->query({ query => $query }) if ($counter > 0); } sub saveStatsInFile { @@ -152,7 +152,7 @@ sub getHGMonthAvailability { $query .= " STRAIGHT_JOIN mod_bi_hostcategories hc ON (h.hc_name=hc.hc_name AND h.hc_id=hc.hc_id)"; $query .= " WHERE t.year = YEAR('".$start."') AND t.month = MONTH('".$start."') and t.hour=0"; $query .= " GROUP BY h.hg_id, h.hc_id, ha.liveservice_id"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); $self->{"logger"}->writeLog("DEBUG","[HOST] Calculating MTBF/MTRS/MTBSI for Host"); my @data = (); diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/LiveService.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/LiveService.pm index 18849c177b5..79a97c086d6 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/bi/LiveService.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/LiveService.pm @@ -49,7 +49,7 @@ sub getLiveServicesByName { my $query = "SELECT `id`, `name`"; $query .= " FROM `mod_bi_liveservice`"; $query .= " WHERE `name` like '".$name."%'"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); my %result = (); while (my $row = $sth->fetchrow_hashref()) { $result{ $row->{name} } = $row->{id}; @@ -64,7 +64,7 @@ sub getLiveServicesByTpId { my $interval = shift; my $query = "SELECT `id`, `timeperiod_id`"; $query .= " FROM `mod_bi_liveservice` "; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); my %result = (); while (my $row = $sth->fetchrow_hashref()) { $result{$row->{'timeperiod_id'}} = $row->{"id"}; @@ -79,7 +79,7 @@ sub getLiveServicesByNameForTpId { my $query = "SELECT `id`, `name`"; $query .= " FROM `mod_bi_liveservice` "; $query .= "WHERE timeperiod_id = ".$tpId; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); my ($name, $id); while (my $row = $sth->fetchrow_hashref()) { @@ -99,7 +99,7 @@ sub getLiveServiceIdsInString { my $query = "SELECT `id`"; $query .= " FROM mod_bi_liveservice"; $query .= " WHERE timeperiod_id IN (".$ids.")"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); my %result = (); while (my $row = $sth->fetchrow_hashref()) { $idStr .= $row->{'id'}.","; @@ -128,7 +128,7 @@ sub getLiveServicesByNameForTpIds { my $query = "SELECT `id`, `name`"; $query .= " FROM mod_bi_liveservice"; $query .= " WHERE timeperiod_id IN (".$idStr.")"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); my %result = (); while (my $row = $sth->fetchrow_hashref()) { $result{ $row->{name} } = $row->{id}; @@ -142,7 +142,7 @@ sub getTimeperiodName { my $id = shift; my $query = "SELECT name FROM mod_bi_liveservice WHERE timeperiod_id=".$id; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); my $name = ""; if (my $row = $sth->fetchrow_hashref()) { $name = $row->{'name'}; @@ -156,7 +156,7 @@ sub getTimeperiodId { my $name = shift; my $query = "SELECT timeperiod_id FROM mod_bi_liveservice WHERE name='".$name."'"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); my $id = 0; if (my $row = $sth->fetchrow_hashref()) { $id = $row->{'timeperiod_id'}; @@ -171,7 +171,7 @@ sub insert { my $name = shift; my $id = shift; my $query = "INSERT INTO `mod_bi_liveservice` (`name`, `timeperiod_id`) VALUES ('".$name."', ".$id.")"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); } sub insertList { @@ -199,7 +199,7 @@ sub update { my $name = shift; my $id = shift; my $query = "UPDATE `mod_bi_liveservice` SET `timeperiod_id`=".$id." WHERE name='".$name."'"; - $db->query($query); + $db->query({ query => $query }); } sub updateById { @@ -208,7 +208,7 @@ sub updateById { my ($id, $name) = (shift, shift); my $query = "UPDATE `mod_bi_liveservice` SET `name`='".$name."' WHERE timeperiod_id=".$id; - $db->query($query); + $db->query({ query => $query }); } sub truncateTable { @@ -216,8 +216,8 @@ sub truncateTable { my $db = $self->{"centstorage"}; my $query = "TRUNCATE TABLE `mod_bi_liveservice`"; - $db->query($query); - $db->query("ALTER TABLE `mod_bi_liveservice` AUTO_INCREMENT=1"); + $db->query({ query => $query }); + $db->query({ query => "ALTER TABLE `mod_bi_liveservice` AUTO_INCREMENT=1" }); } 1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/Loader.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/Loader.pm index 9cfe6d334a3..6f4c7b421c0 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/bi/Loader.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/Loader.pm @@ -67,14 +67,14 @@ sub loadData { my $logger = $self->{"logger"}; my ($tableName, $inFile) = (shift, shift); my $query = "LOAD DATA LOCAL INFILE '".$inFile."' INTO TABLE `".$tableName."` CHARACTER SET UTF8 IGNORE 1 LINES"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); } sub disableKeys { my $self = shift; my $db = $self->{"centstorage"}; my $tableName = shift; my $query = "ALTER TABLE `".$tableName."` DISABLE KEYS"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); } sub enableKeys { @@ -82,7 +82,7 @@ sub enableKeys { my $db = $self->{"centstorage"}; my $tableName = shift; my $query = "ALTER TABLE `".$tableName."` ENABLE KEYS"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); } sub dumpTableStructure { @@ -92,7 +92,7 @@ sub dumpTableStructure { my ($tableName) = (shift); my $sql = ""; - my $sth = $db->query("SHOW CREATE TABLE ".$tableName); + my $sth = $db->query({ query => "SHOW CREATE TABLE ".$tableName }); if (my $row = $sth->fetchrow_hashref()) { $sql = $row->{'Create Table'}; }else { @@ -108,14 +108,14 @@ sub truncateTable { my $db = $self->{"centstorage"}; my $tableName = shift; my $query = "TRUNCATE TABLE `".$tableName."`"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); } sub dropTable { my $self = shift; my $db = $self->{"centstorage"}; my $tableName = shift; my $query = "DROP TABLE IF EXISTS `".$tableName."`"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); } 1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricCentileValue.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricCentileValue.pm index 79db771c68e..a4348a7ce1c 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricCentileValue.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricCentileValue.pm @@ -92,7 +92,7 @@ sub getMetricsCentile { my $centileServiceCategories = $options{etlProperties}->{'centile.include.servicecategories'}; my $query = 'SELECT id, metric_id FROM ' . $self->{today_servicemetrics} . ' sm ' . ' WHERE sm.sc_id IN (' . $centileServiceCategories . ')'; - my $sth = $self->{centstorage}->query($query); + my $sth = $self->{centstorage}->query({ query => $query }); while (my $row = $sth->fetchrow_arrayref()) { $results->{$$row[1]} = [] if (!defined($results->{$$row[1]})); push @{$results->{$$row[1]}}, $$row[0]; @@ -146,7 +146,7 @@ sub calcMetricsCentileValueMultipleDays { #Get Id for the couple centile / timeperiod my $centileId; my $query = "SELECT id FROM mod_bi_centiles WHERE centile_param = " . $centile . " AND liveservice_id = (SELECT id FROM mod_bi_liveservice WHERE timeperiod_id = " . $timeperiodId . ")"; - my $sth = $self->{centstorage}->query($query); + my $sth = $self->{centstorage}->query({ query => $query }); while (my $row = $sth->fetchrow_hashref()) { if (defined($row->{id})) { $centileId = $row->{id}; @@ -173,7 +173,7 @@ sub calcMetricsCentileValueMultipleDays { '(servicemetric_id, time_id, liveservice_id, centile_value, centile_param, centile_id, total, warning_treshold, critical_treshold)' . "SELECT '" . $_ . "', '" . $options{timeId} . "', '" . $liveServiceId . "', '" . $$row[0] . "', '" . $centile . "', '" . $centileId . "', " . 'm.max, m.warn, m.crit FROM metrics m WHERE m.metric_id = ' . $metricId; - $self->{centstorage}->query($query_insert); + $self->{centstorage}->query({ query => $query_insert }); } } } diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricDailyValue.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricDailyValue.pm index c66700589da..55c2def7b69 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricDailyValue.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricDailyValue.pm @@ -62,9 +62,9 @@ sub dropTempTables { my $self = shift; my $db = $self->{"centstorage"}; my $query = "DROP TABLE `" . $self->{name_minmaxavg_tmp} . "`"; - $db->query($query); + $db->query({ query => $query }); $query = "DROP TABLE `" . $self->{name_firstlast_tmp} . "`"; - $db->query($query); + $db->query({ query => $query }); } sub insertValues { @@ -82,7 +82,7 @@ sub insertValues { $query .= " JOIN (metrics m, " . $self->{'today_servicemetrics'} . " sm)"; $query .= " ON (mmavt.id_metric = m.metric_id and mmavt.id_metric = sm.metric_id)"; $query .= " LEFT JOIN " . $self->{name_firstlast_tmp} . " flvt ON (mmavt.id_metric = flvt.id_metric)"; - $db->query($query); + $db->query({ query => $query }); $self->dropTempTables(); } @@ -105,7 +105,7 @@ sub getMetricCapacityValuesOnPeriod { $query .= " AND sc_id IN (".$etlProperties->{'capacity.include.servicecategories'}.")"; $query .= " AND v.servicemetric_id = m.id"; $query .= " GROUP BY servicemetric_id, liveservice_id"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); my %data = (); while (my $row = $sth->fetchrow_hashref()) { my @table = ($row->{"servicemetric_id"}, $row->{"liveservice_id"}, $row->{first_value}, $row->{"total"}); @@ -125,7 +125,7 @@ sub getMetricCapacityValuesOnPeriod { $query .= " AND v.servicemetric_id = m.id"; $query .= " GROUP BY servicemetric_id, liveservice_id"; - $sth = $db->query($query); + $sth = $db->query({ query => $query }); while (my $row = $sth->fetchrow_hashref()) { my $entry = $data{$row->{servicemetric_id} . ';' . $row->{liveservice_id}}; if (defined($entry)) { diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricHourlyValue.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricHourlyValue.pm index 037e52c85f3..a28483544d6 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricHourlyValue.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricHourlyValue.pm @@ -66,7 +66,7 @@ sub insertValues { $query .= " FROM " . $self->{name_minmaxavg_tmp} . " mmavt"; $query .= " JOIN (metrics m, " . $self->{servicemetrics} . " sm, mod_bi_time t)"; $query .= " ON (mmavt.id_metric = m.metric_id and mmavt.id_metric = sm.metric_id AND mmavt.valueTime = t.dtime)"; - $db->query($query); + $db->query({ query => $query }); } 1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricMonthCapacity.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricMonthCapacity.pm index 89b3f208150..9740d0c69f4 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricMonthCapacity.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/MetricMonthCapacity.pm @@ -77,13 +77,13 @@ sub insertStats { $append = ','; $counter++; if ($counter >= $insertParam) { - $db->query($query); + $db->query({ query => $query }); $query = $query_start; $counter = 0; $append = ''; } } - $db->query($query) if ($counter > 0); + $db->query({ query => $query }) if ($counter > 0); } 1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/MySQLTables.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/MySQLTables.pm index a4f94e7ab11..05638b625dc 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/bi/MySQLTables.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/MySQLTables.pm @@ -46,7 +46,7 @@ sub tableExists { my $self = shift; my ($name) = (shift); - my $statement = $self->{centstorage}->query("SHOW TABLES LIKE '".$name."'"); + my $statement = $self->{centstorage}->query({ query => "SHOW TABLES LIKE '".$name."'" }); if (!(my @row = $statement->fetchrow_array())) { return 0; @@ -60,11 +60,11 @@ sub createTable { my $db = $self->{"centstorage"}; my $logger = $self->{"logger"}; my ($name, $structure, $mode) = @_; - my $statement = $db->query("SHOW TABLES LIKE '".$name."'"); + my $statement = $db->query({ query => "SHOW TABLES LIKE '".$name."'" }); if (!$self->tableExists($name)) { if (defined($structure)) { $logger->writeLog("DEBUG", "[CREATE] table [".$name."]"); - $db->query($structure); + $db->query({ query => $structure }); return 0; }else { $logger->writeLog("FATAL", "[CREATE] Cannot find table [".$name."] structure"); @@ -78,7 +78,7 @@ sub dumpTableStructure { my ($tableName) = (shift); my $sql = ""; - my $sth = $self->{centstorage}->query("SHOW CREATE TABLE " . $tableName); + my $sth = $self->{centstorage}->query({ query => "SHOW CREATE TABLE " . $tableName }); if (my $row = $sth->fetchrow_hashref()) { $sql = $row->{'Create Table'}; $sql =~ s/(CONSTRAINT.*\n)//g; @@ -115,7 +115,7 @@ sub createParts { my @partName = split (/\-/, $runningStart); $tableStructure .= "PARTITION p".$partName[0].$partName[1].$partName[2]." VALUES LESS THAN (FLOOR(UNIX_TIMESTAMP('".$runningStart."'))));"; $logger->writeLog("DEBUG", "[CREATE] table partitionned [".$tableName."] min value: ".$start.", max value: ".$runningStart.", range: 1 DAY\n"); - $db->query($tableStructure); + $db->query({ query => $tableStructure }); return 0; } @@ -136,7 +136,7 @@ sub updateParts { $logger->writeLog("DEBUG", "[UPDATE PARTS] Updating partitions for table [".$tableName."] (last range : ".$range.")"); my @partName = split (/\-/, $range); my $query = "ALTER TABLE `".$tableName."` ADD PARTITION (PARTITION `p".$partName[0].$partName[1].$partName[2]."` VALUES LESS THAN(FLOOR(UNIX_TIMESTAMP('".$range."'))))"; - $db->query($query); + $db->query({ query => $query }); $range = $timeObj->addDateInterval($range, 1, "DAY"); } } @@ -147,7 +147,7 @@ sub isTablePartitioned { my $tableName = shift; my $db = $self->{"centstorage"}; - my $sth = $db->query("SHOW TABLE STATUS LIKE '".$tableName."'"); + my $sth = $db->query({ query => "SHOW TABLE STATUS LIKE '".$tableName."'" }); if (my $row = $sth->fetchrow_hashref()) { my $createOptions = $row->{"Create_options"}; if (defined($createOptions) && $createOptions =~ m/partitioned/i) { @@ -166,7 +166,7 @@ sub getLastPartRange { my $query = "SHOW CREATE TABLE $tableName"; my $partName; - my $sth = $self->{centstorage}->query($query); + my $sth = $self->{centstorage}->query({ query => $query }); if (my $row = $sth->fetchrow_hashref()) { while ($row->{'Create Table'} =~ /PARTITION.*?p(\d{4})(\d{2})(\d{2}).*?VALUES LESS THAN \([0-9]+?\)/g) { $partName = "$1-$2-$3"; @@ -187,15 +187,15 @@ sub deleteEntriesForRebuild { my ($start, $end, $tableName) = @_; if (!$self->isTablePartitioned($tableName)) { - $db->query("DELETE FROM ".$tableName." WHERE time_id >= UNIX_TIMESTAMP('".$start."') AND time_id < UNIX_TIMESTAMP('".$end."')"); - }else { + $db->query({ query => "DELETE FROM ".$tableName." WHERE time_id >= UNIX_TIMESTAMP('".$start."') AND time_id < UNIX_TIMESTAMP('".$end."')" }); + } else { my $query = "SELECT partition_name FROM information_schema.partitions "; $query .= "WHERE table_name='".$tableName."' AND table_schema='".$db->db."'"; $query .= " AND CONVERT(PARTITION_DESCRIPTION, SIGNED INTEGER) > UNIX_TIMESTAMP('".$start."')"; $query .= " AND CONVERT(PARTITION_DESCRIPTION, SIGNED INTEGER) <= UNIX_TIMESTAMP('".$end."')"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); while(my $row = $sth->fetchrow_hashref()) { - $db->query("ALTER TABLE ".$tableName." TRUNCATE PARTITION ".$row->{'partition_name'}); + $db->query({ query => "ALTER TABLE ".$tableName." TRUNCATE PARTITION ".$row->{'partition_name'} }); } $self->updateParts($end, $tableName); } @@ -213,14 +213,14 @@ sub emptyTableForRebuild { $structure =~ s/KEY.*\(\`$column\`\)//g; $structure =~ s/\,[\n\s+]+\)/\n\)/g; if (!defined($_[0]) || !$self->isPartitionEnabled()) { - $db->query("DROP TABLE IF EXISTS ".$tableName); - $db->query($structure); - }else { + $db->query({ query => "DROP TABLE IF EXISTS ".$tableName }); + $db->query({ query => $structure }); + } else { my ($start, $end) = @_; - $db->query("DROP TABLE IF EXISTS ".$tableName); + $db->query({ query => "DROP TABLE IF EXISTS ".$tableName }); $self->createParts($start, $end, $structure, $tableName, $column); } - $db->query("ALTER TABLE `".$tableName."` ADD INDEX `idx_".$tableName."_".$column."` (`".$column."`)"); + $db->query({ query => "ALTER TABLE `".$tableName."` ADD INDEX `idx_".$tableName."_".$column."` (`".$column."`)" }); } sub dailyPurge { @@ -230,16 +230,16 @@ sub dailyPurge { my ($retentionDate, $tableName, $column) = @_; if (!$self->isTablePartitioned($tableName)) { - $db->query("DELETE FROM `".$tableName."` WHERE ".$column." < UNIX_TIMESTAMP('".$retentionDate."')"); - }else { + $db->query({ query => "DELETE FROM `".$tableName."` WHERE ".$column." < UNIX_TIMESTAMP('".$retentionDate."')" }); + } else { my $query = "SELECT GROUP_CONCAT(partition_name SEPARATOR ',') as partition_names FROM information_schema.partitions "; $query .= "WHERE table_name='".$tableName."' AND table_schema='".$db->db."'"; $query .= " AND CONVERT(PARTITION_DESCRIPTION, SIGNED INTEGER) < UNIX_TIMESTAMP('".$retentionDate."')"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); if(my $row = $sth->fetchrow_hashref()) { if (defined($row->{'partition_names'}) && $row->{'partition_names'} ne "") { - $db->query("ALTER TABLE ".$tableName." DROP PARTITION ".$row->{'partition_names'}); - } + $db->query({ query => "ALTER TABLE ".$tableName." DROP PARTITION ".$row->{'partition_names'} }); + } } } } @@ -254,7 +254,7 @@ sub checkPartitionContinuity { $query .= " where table_schema = '".$db->{"db"}."' and table_name = '".$table."' and PARTITION_ORDINAL_POSITION=1)), SIGNED INTEGER) as nbDays,"; $query .= " CONVERT(PARTITION_ORDINAL_POSITION, SIGNED INTEGER) as ordinalPosition "; $query .= " from information_schema.partitions where table_schema = '".$db->{"db"}."' and table_name = '".$table."' order by PARTITION_ORDINAL_POSITION desc limit 1 "; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); while (my $row = $sth->fetchrow_hashref()) { my $nbDays = int($row->{'nbDays'}); my $ordinalPosition = int($row->{'ordinalPosition'}); @@ -276,7 +276,7 @@ sub checkLastTablePartition{ my $query = "select from_unixtime(PARTITION_DESCRIPTION) as last_partition, IF(from_unixtime(PARTITION_DESCRIPTION)=CURDATE() AND HOUR(from_unixtime(PARTITION_DESCRIPTION))=0,1,0) as partition_uptodate "; $query .="from information_schema.partitions where table_schema = '".$db->{"db"}."'"; $query .= "and table_name = '".$table."'order by PARTITION_ORDINAL_POSITION desc limit 1"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); while (my $row = $sth->fetchrow_hashref()) { if($row->{'partition_uptodate'} == 0){ $message = $row->{'last_partition'}; @@ -290,15 +290,14 @@ sub dropIndexesFromReportingTable { my $self = shift; my $table = shift; my $db = $self->{"centstorage"}; - my $indexes = $db->query("SHOW INDEX FROM ".$table); + my $indexes = $db->query({ query => "SHOW INDEX FROM ".$table }); my $previous = ""; while (my $row = $indexes->fetchrow_hashref()) { - if ($row->{"Key_name"} ne $previous) { if (lc($row->{"Key_name"}) eq lc("PRIMARY")) { - $db->query("ALTER TABLE `".$table."` DROP PRIMARY KEY"); - }else { - $db->query("ALTER TABLE `".$table."` DROP INDEX ".$row->{"Key_name"}); + $db->query({ query => "ALTER TABLE `".$table."` DROP PRIMARY KEY" }); + } else { + $db->query({ query => "ALTER TABLE `".$table."` DROP INDEX ".$row->{"Key_name"} }); } } $previous = $row->{"Key_name"}; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/ServiceAvailability.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/ServiceAvailability.pm index 5f4b1167aac..dee44a610b3 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/bi/ServiceAvailability.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/ServiceAvailability.pm @@ -112,14 +112,14 @@ sub insertStats { $counter++; if ($counter >= $insertParam) { - $self->{centstorage}->query($query); + $self->{centstorage}->query({ query => $query }); $query = $query_start; $counter = 0; $append = ''; } } - $self->{centstorage}->query($query) if ($counter > 0); + $self->{centstorage}->query({ query => $query }) if ($counter > 0); } sub getCurrentNbLines { @@ -157,7 +157,7 @@ sub getHGMonthAvailability { $query .= " STRAIGHT_JOIN mod_bi_servicecategories sc ON (s.sc_id=sc.sc_id AND s.sc_name=sc.sc_name)"; $query .= " WHERE t.year = YEAR('".$start."') AND t.month = MONTH('".$start."') and t.hour=0"; $query .= " GROUP BY s.hg_id, s.hc_id, s.sc_id, sa.liveservice_id"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); my @data = (); while (my $row = $sth->fetchrow_hashref()) { @@ -213,7 +213,7 @@ sub getHGMonthAvailability_optimised { #hg_id | hc_id | sc_id | liveservice_id | hcat_id | group_id | scat_id | av_percent | av_time | unav_time | degraded_time | #unav_opened | unav_closed | deg_opened | deg_closed | other_opened | other_closed | hg_id | hc_id | sc_id | #modbiliveservice_id | warningEvents | criticalEvents | unknownEvents - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); my @data = (); while (my $row = $sth->fetchrow_hashref()) { diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/Time.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/Time.pm index c7313269417..e7f8e6dffe1 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/bi/Time.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/Time.pm @@ -51,7 +51,7 @@ sub getEntriesDtime { $query .= " FROM `mod_bi_time`"; $query .= " WHERE dtime >= '".$start."' AND dtime <'".$end."'"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); my @results = (); if (my $row = $sth->fetchrow_hashref()) { push @results, $row->{dtime}; @@ -78,7 +78,7 @@ sub getEntryID { }else { $query .= " WHERE dtime = DATE_ADD('".$dtime."', INTERVAL ".$interval." ".$type.")"; } - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); my @results = (); if (my $row = $sth->fetchrow_hashref()) { $results[0] = $row->{'id'}; @@ -97,7 +97,7 @@ sub getDayOfWeek { my $logger = $self->{"logger"}; my $date = shift; - my $sth = $db->query("SELECT LOWER(DAYNAME('".$date."')) as dayOfWeek"); + my $sth = $db->query({ query => "SELECT LOWER(DAYNAME('".$date."')) as dayOfWeek" }); my $dayofweek; if (my $row = $sth->fetchrow_hashref()) { $dayofweek = $row->{"dayOfWeek"}; @@ -114,7 +114,7 @@ sub getYesterdayTodayDate { my $self = shift; # get yesterday date. date format : YYYY-MM-DD - my $sth = $self->{centstorage}->query("SELECT CURRENT_DATE() as today, DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY) as yesterday"); + my $sth = $self->{centstorage}->query({ query => "SELECT CURRENT_DATE() as today, DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY) as yesterday" }); my $yesterday; my $today; @@ -138,7 +138,7 @@ sub addDateInterval { my ($date, $interval, $intervalType) = @_; # get new date. date format : YYYY-MM-DD - my $sth = $self->{centstorage}->query("SELECT DATE_ADD('".$date."', INTERVAL ".$interval." ".$intervalType.") as newDate"); + my $sth = $self->{centstorage}->query({ query => "SELECT DATE_ADD('".$date."', INTERVAL ".$interval." ".$intervalType.") as newDate" }); my $newDate; if (my $row = $sth->fetchrow_hashref()) { @@ -154,7 +154,7 @@ sub compareDates { my $self = shift; my ($date1, $date2) = @_; - my $sth = $self->{centstorage}->query("SELECT DATEDIFF('".$date1."','".$date2."') as nbDays"); + my $sth = $self->{centstorage}->query({ query => "SELECT DATEDIFF('".$date1."','".$date2."') as nbDays" }); if (my $row = $sth->fetchrow_hashref()) { return $row->{nbDays}; } @@ -186,13 +186,13 @@ sub insertTimeEntriesForPeriod { $date = "ADDDATE('".$start."',INTERVAL ".$counter." HOUR)"; if ($counter % 30 == 0) { chop($query_suffix); - $db->query($self->{insertQuery} . $query_suffix); + $db->query({ query => $self->{insertQuery} . $query_suffix }); $query_suffix = ""; } } chop($query_suffix); if ($query_suffix ne "") { - $db->query($self->{insertQuery} . $query_suffix); + $db->query({ query => $self->{insertQuery} . $query_suffix }); } } @@ -207,14 +207,14 @@ sub deleteDuplicateEntries { $query .= " AND dtime <= '".$end."'"; $query .= " GROUP BY utime"; $query .= " HAVING COUNT(utime) > 1"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); my $ids_to_delete = ""; while (my $row = $sth->fetchrow_hashref()) { $ids_to_delete .= $row->{'id'}.","; } if ($ids_to_delete ne "") { chop ($ids_to_delete); - $db->query("DELETE FROM mod_bi_time WHERE id IN (".$ids_to_delete.")"); + $db->query({ query => "DELETE FROM mod_bi_time WHERE id IN (".$ids_to_delete.")" }); } } @@ -225,7 +225,7 @@ sub getTotalDaysInPeriod { my ($start, $end) = @_; my $query = "SELECT DATEDIFF('".$end."', '".$start."') diff"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); my $diff; if (my $row = $sth->fetchrow_hashref()) { $diff = $row->{'diff'}; @@ -248,8 +248,8 @@ sub truncateTable { my $db = $self->{"centstorage"}; my $query = "TRUNCATE TABLE `mod_bi_time`"; - $db->query($query); - $db->query("ALTER TABLE `mod_bi_time` AUTO_INCREMENT=1"); + $db->query({ query => $query }); + $db->query({ query => "ALTER TABLE `mod_bi_time` AUTO_INCREMENT=1" }); } sub deleteEntriesForPeriod { @@ -258,7 +258,7 @@ sub deleteEntriesForPeriod { my ($start, $end) = @_; my $query = "DELETE FROM `mod_bi_time` WHERE dtime >= '".$start."' AND dtime < '".$end."'"; - $db->query($query); + $db->query({ query => $query }); } 1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/centreon/CentileProperties.pm b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/CentileProperties.pm index a719c9539d3..d0d393891c4 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/centreon/CentileProperties.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/CentileProperties.pm @@ -47,7 +47,7 @@ sub getCentileParams { my $centileParams = []; my $query = "SELECT `centile_param`, `timeperiod_id` FROM `mod_bi_options_centiles`"; - my $sth = $centreon->query($query); + my $sth = $centreon->query({ query => $query }); while (my $row = $sth->fetchrow_hashref()) { if (defined($row->{centile_param}) && $row->{centile_param} ne '0' && defined($row->{timeperiod_id}) && $row->{timeperiod_id} ne '0'){ push @{$centileParams}, { centile_param => $row->{centile_param}, timeperiod_id => $row->{timeperiod_id} }; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/centreon/ETLProperties.pm b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/ETLProperties.pm index 1181394b2ee..b196b8dd6e5 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/centreon/ETLProperties.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/ETLProperties.pm @@ -52,7 +52,7 @@ sub getProperties { my (%etlProperties, %dataRetention); my $query = "SELECT `opt_key`, `opt_value` FROM `mod_bi_options` WHERE `opt_key` like 'etl.%'"; - my $sth = $self->{centreon}->query($query); + my $sth = $self->{centreon}->query({ query => $query }); while (my $row = $sth->fetchrow_hashref()) { if ($row->{opt_key} =~ /etl.retention.(.*)/) { $dataRetention{$1} = $row->{opt_value}; @@ -86,7 +86,7 @@ sub getMaxRetentionPeriodFor { $query .= " date_format(DATE_ADD(NOW(), INTERVAL MAX(CAST(`opt_value` as SIGNED INTEGER))*-1 DAY), '%Y-%m-%d') as period_start"; $query .= " FROM `mod_bi_options` "; $query .= " WHERE `opt_key` IN ('etl.retention.".$type.".hourly','etl.retention.".$type.".daily', 'etl.retention.".$type.".raw')"; - my $sth = $self->{centreon}->query($query); + my $sth = $self->{centreon}->query({ query => $query }); if (my $row = $sth->fetchrow_hashref()) { return ($row->{period_start}, $row->{period_end}); @@ -105,7 +105,7 @@ sub getRetentionPeriods { $query .= " opt_key "; $query .= " FROM `mod_bi_options` "; $query .= " WHERE `opt_key` like ('etl.retention.%')"; - my $sth = $self->{centreon}->query($query); + my $sth = $self->{centreon}->query({ query => $query }); my %periods = (); while (my $row = $sth->fetchrow_hashref()) { $row->{'opt_key'} =~ s/etl.retention.//; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Host.pm b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Host.pm index 2b869ce5338..89a03a14384 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Host.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Host.pm @@ -66,7 +66,7 @@ sub getAllHosts { if ($activated == 1) { $query .= " AND `host_activate` ='1'"; } - my $sth = $centreon->query($query); + my $sth = $centreon->query({ query => $query }); while (my $row = $sth->fetchrow_hashref()) { $host_ids{$row->{"host_name"}} = $row->{"host_id"}; $host_names{$row->{"host_id"}} = $row->{"host_name"}; @@ -105,17 +105,17 @@ sub getHostGroups { my %result = (); my $query = "SELECT `host_id`, `host_name`, `hg_id`, `hg_name`". - " FROM `host`, `hostgroup_relation`, `hostgroup`". - " WHERE `host_register`='1'". - " AND `hostgroup_hg_id` = `hg_id`". - " AND `host_id`= `host_host_id`"; - if ($activated == 1) { - $query .= " AND `host_activate` ='1'"; - } - if(!defined($etlProperties->{'dimension.all.hostgroups'}) && $etlProperties->{'dimension.hostgroups'} ne ''){ - $query .= " AND `hg_id` IN (".$etlProperties->{'dimension.hostgroups'}.")"; - } - my $sth = $centreon->query($query); + " FROM `host`, `hostgroup_relation`, `hostgroup`". + " WHERE `host_register`='1'". + " AND `hostgroup_hg_id` = `hg_id`". + " AND `host_id`= `host_host_id`"; + if ($activated == 1) { + $query .= " AND `host_activate` ='1'"; + } + if (!defined($etlProperties->{'dimension.all.hostgroups'}) && $etlProperties->{'dimension.hostgroups'} ne '') { + $query .= " AND `hg_id` IN (".$etlProperties->{'dimension.hostgroups'}.")"; + } + my $sth = $centreon->query({ query => $query }); while (my $row = $sth->fetchrow_hashref()) { my $new_entry = $row->{"hg_id"}.";".$row->{"hg_name"}; if (defined($result{$row->{"host_id"}})) { @@ -153,16 +153,15 @@ sub getRecursiveCategoriesForOneHost{ #Get all categories linked to the templates associated with the host or just template associated with host to be able to call the method recursively my $query = "SELECT host_id, host_name, template_id,template_name, categories.hc_id as category_id, categories.hc_activate as hc_activate,". - " categories.hc_name as category_name ". - " FROM ( SELECT t1.host_id,t1.host_name,templates.host_id as template_id,templates.host_name as template_name ". - " FROM host t1, host_template_relation t2, host templates ". - " WHERE t1.host_id = t2.host_host_id AND t2.host_tpl_id = templates.host_id AND t1.host_activate ='1' AND t1.host_id = ".$host_id." ) r1 ". - " LEFT JOIN hostcategories_relation t3 ON t3.host_host_id = r1.template_id LEFT JOIN hostcategories categories ON t3.hostcategories_hc_id = categories.hc_id "; - - + " categories.hc_name as category_name ". + " FROM ( SELECT t1.host_id,t1.host_name,templates.host_id as template_id,templates.host_name as template_name ". + " FROM host t1, host_template_relation t2, host templates ". + " WHERE t1.host_id = t2.host_host_id AND t2.host_tpl_id = templates.host_id AND t1.host_activate ='1' AND t1.host_id = ".$host_id." ) r1 ". + " LEFT JOIN hostcategories_relation t3 ON t3.host_host_id = r1.template_id LEFT JOIN hostcategories categories ON t3.hostcategories_hc_id = categories.hc_id "; + my @hostCategoriesAllowed = split /,/, $etlProperties->{'dimension.hostcategories'}; - my $sth = $centreon->query($query); + my $sth = $centreon->query({ query => $query }); while (my $row = $sth->fetchrow_hashref()) { my @tab = (); my $new_entry; @@ -221,7 +220,7 @@ sub getDirectLinkedCategories{ $query .= " AND `hc_id` IN (".$etlProperties->{'dimension.hostcategories'}.")"; } - my $sth = $centreon->query($query); + my $sth = $centreon->query({ query => $query }); while (my $row = $sth->fetchrow_hashref()) { my $new_entry = $row->{"hc_id"}.";".$row->{"hc_name"}; if (!scalar(@$ref_hostCat)){ @@ -257,10 +256,10 @@ sub getHostCategoriesWithTemplate{ } my $query = "SELECT `host_id`". - " FROM `host`". - " WHERE `host_activate` ='1'"; + " FROM `host`". + " WHERE `host_activate` ='1'"; - my $sth = $centreon->query($query); + my $sth = $centreon->query({ query => $query }); while (my $row = $sth->fetchrow_hashref()) { my @tab = (); my $host_id = $row->{"host_id"}; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/centreon/HostCategory.pm b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/HostCategory.pm index d881daa3ecd..8751350f763 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/centreon/HostCategory.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/HostCategory.pm @@ -58,7 +58,7 @@ sub getAllEntries { if(!defined($etlProperties->{'dimension.all.hostcategories'}) && $etlProperties->{'dimension.hostcategories'} ne ''){ $query .= " WHERE `hc_id` IN (".$etlProperties->{'dimension.hostcategories'}.")"; } - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); my @entries = (); while (my $row = $sth->fetchrow_hashref()) { push @entries, $row->{"hc_id"}.";".$row->{"hc_name"}; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/centreon/HostGroup.pm b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/HostGroup.pm index 5c91af36021..e11579f363e 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/centreon/HostGroup.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/HostGroup.pm @@ -71,7 +71,7 @@ sub getHostgroupServices { if(!defined($etlProperties->{'dimension.all.hostgroups'}) && $etlProperties->{'dimension.hostgroups'} ne ''){ $query .= " AND hg.`hg_id` IN (".$etlProperties->{'dimension.hostgroups'}.")"; } - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); while (my $row = $sth->fetchrow_hashref()) { $result{$row->{"host_id"}.";".$row->{"service_id"}} = 1; } @@ -105,7 +105,7 @@ sub getHostgroupHostServices { if(!defined($etlProperties{'etl.dimension.all.hostgroups'}) && $etlProperties{'etl.dimension.hostgroups'} ne ''){ $query .= " AND hg.`hg_id` IN (".$etlProperties{'etl.dimension.hostgroups'}.")"; } - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); while (my $row = $sth->fetchrow_hashref()) { $result{$row->{"host_id"}.";".$row->{"service_id"}} = 1; } @@ -123,7 +123,7 @@ sub getAllEntries { if(!defined($etlProperties->{'dimension.all.hostgroups'}) && $etlProperties->{'dimension.hostgroups'} ne ''){ $query .= " WHERE `hg_id` IN (".$etlProperties->{'dimension.hostgroups'}.")"; } - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); my @entries = (); while (my $row = $sth->fetchrow_hashref()) { push @entries, $row->{"hg_id"}.";".$row->{"hg_name"}; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Service.pm b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Service.pm index 447151726ae..fc2f3138149 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Service.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Service.pm @@ -61,14 +61,13 @@ sub getServicesWithHostAndCategory { my (@results); # getting services linked to hosts my $query = "SELECT service_description, service_id, host_id, service_template_model_stm_id as tpl". - " FROM host, service, host_service_relation". - " WHERE host_id = host_host_id and service_service_id = service_id". - " AND service_register = '1'". - " AND host_activate = '1'". - " AND service_activate = '1'"; - + " FROM host, service, host_service_relation". + " WHERE host_id = host_host_id and service_service_id = service_id". + " AND service_register = '1'". + " AND host_activate = '1'". + " AND service_activate = '1'"; - my $sth = $centreon->query($query); + my $sth = $centreon->query({ query => $query }); while(my $row = $sth->fetchrow_hashref()) { # getting all host entries my $serviceHostTable = $hosts->{$row->{"host_id"}}; @@ -102,16 +101,16 @@ sub getServicesWithHostAndCategory { } #getting services linked to hostgroup $query = "SELECT DISTINCT service_description, service_id, host_id, service_template_model_stm_id as tpl". - " FROM host, service, host_service_relation hr, hostgroup_relation hgr". - " WHERE hr.hostgroup_hg_id is not null". - " AND hr.service_service_id = service_id". - " AND hr.hostgroup_hg_id = hgr.hostgroup_hg_id". - " AND hgr.host_host_id = host_id". - " AND service_register = '1'". - " AND host_activate = '1'". - " AND service_activate = '1'"; - - $sth = $centreon->query($query); + " FROM host, service, host_service_relation hr, hostgroup_relation hgr". + " WHERE hr.hostgroup_hg_id is not null". + " AND hr.service_service_id = service_id". + " AND hr.hostgroup_hg_id = hgr.hostgroup_hg_id". + " AND hgr.host_host_id = host_id". + " AND service_register = '1'". + " AND host_activate = '1'". + " AND service_activate = '1'"; + + $sth = $centreon->query({ query => $query }); while(my $row = $sth->fetchrow_hashref()) { # getting all host entries my $serviceHostTable = $hosts->{$row->{"host_id"}}; @@ -153,7 +152,7 @@ sub getServicesTemplatesCategories { my %results = (); my $query = "SELECT service_id, service_description, service_template_model_stm_id FROM service WHERE service_register = '0'"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); while(my $row = $sth->fetchrow_hashref()) { my $currentTemplate = $row->{"service_id"}; my $categories = $self->getServiceCategories($row->{"service_id"}); @@ -165,7 +164,7 @@ sub getServicesTemplatesCategories { my $parentQuery = "SELECT service_id, service_template_model_stm_id "; $parentQuery .= "FROM service "; $parentQuery .= "WHERE service_register = '0' and service_id=".$parentId; - my $sthparentQuery = $db->query($parentQuery); + my $sthparentQuery = $db->query({ query => $parentQuery }); if(my $parentQueryRow = $sthparentQuery->fetchrow_hashref()) { my $newCategories = $self->getServiceCategories($parentQueryRow->{"service_id"}); while(my ($sc_id, $sc_name) = each(%$newCategories)) { @@ -204,7 +203,7 @@ sub getServiceCategories { if(!defined($etlProperties->{'dimension.all.servicecategories'}) && $etlProperties->{'dimension.servicecategories'} ne ''){ $query .= " AND sc.sc_id IN (".$etlProperties->{'dimension.servicecategories'}.")"; } - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); while(my $row = $sth->fetchrow_hashref()) { $results{$row->{"sc_id"}} = $row->{"sc_name"}; } diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/centreon/ServiceCategory.pm b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/ServiceCategory.pm index 47a532d0c13..637ec4fb08a 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/centreon/ServiceCategory.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/ServiceCategory.pm @@ -63,7 +63,7 @@ sub getCategory { if(!defined($etlProperties->{'dimension.all.servicecategories'}) && $etlProperties->{'dimension.servicecategories'} ne ''){ $query .= " WHERE `sc_id` IN (".$etlProperties->{'dimension.servicecategories'}.")"; } - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); if(my $row = $sth->fetchrow_hashref()) { $result = $row->{"sc_id"}; }else { @@ -84,7 +84,7 @@ sub getAllEntries { if(!defined($etlProperties->{'dimension.all.servicecategories'}) && $etlProperties->{'dimension.servicecategories'} ne ''){ $query .= " WHERE `sc_id` IN (".$etlProperties->{'dimension.servicecategories'}.")"; } - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); my @entries = (); while (my $row = $sth->fetchrow_hashref()) { push @entries, $row->{"sc_id"}.";".$row->{"sc_name"}; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Timeperiod.pm b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Timeperiod.pm index 97e0f84efc8..2dab0526666 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Timeperiod.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Timeperiod.pm @@ -52,7 +52,7 @@ sub getTimeRangesForDay { my $query = "SELECT tp_" . $weekDay; $query .= " FROM timeperiod"; $query .= " WHERE tp_name = '" . $name . "'"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); if (my $row = $sth->fetchrow_hashref()) { if (defined($row->{'tp_'.$weekDay})) { my @ranges = split(",", $row->{'tp_' . $weekDay}); @@ -78,7 +78,7 @@ sub getTimeRangesForDayByDateTime { my $query = "SELECT tp_".$weekDay; $query .= " FROM timeperiod"; $query .= " WHERE tp_name='".$name."'"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); if(my $row = $sth->fetchrow_hashref()) { if (defined($row->{'tp_'.$weekDay})) { my @ranges = split(",", $row->{'tp_'.$weekDay}); @@ -122,7 +122,7 @@ sub getAllRangesForTpId { my $query = "SELECT tp_monday, tp_tuesday, tp_wednesday, tp_thursday, tp_friday, tp_saturday, tp_sunday"; $query .= " FROM timeperiod"; $query .= " WHERE tp_id='".$timeperiod_id."'"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); my @results = (); if(my $row = $sth->fetchrow_hashref()) { @@ -175,7 +175,7 @@ sub getId { my $query = "SELECT tp_id"; $query .= " FROM timeperiod"; $query .= " WHERE tp_name = '".$name."'"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); my $result = -1; if(my $row = $sth->fetchrow_hashref()) { $result = $row->{'tp_id'}; @@ -191,7 +191,7 @@ sub getPeriodsLike { my $query = "SELECT tp_id, tp_name"; $query .= " FROM timeperiod"; $query .= " WHERE tp_name like '".$name."%'"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); my %result = (); while (my $row = $sth->fetchrow_hashref()) { $result{$row->{'tp_id'}} = $row->{'tp_name'}; @@ -220,7 +220,7 @@ sub getPeriods { my $query = "SELECT tp_id, tp_name"; $query .= " FROM timeperiod"; $query .= " WHERE tp_id IN (".$idStr.")"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); my %result = (); while (my $row = $sth->fetchrow_hashref()) { $result{$row->{'tp_id'}} = $row->{'tp_name'}; @@ -236,7 +236,7 @@ sub getCentilePeriods { my $query = "SELECT tp_id, tp_name"; $query .= " FROM timeperiod"; $query .= " WHERE tp_id IN (select timeperiod_id from mod_bi_options_centiles)"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); my %result = (); while (my $row = $sth->fetchrow_hashref()) { $result{$row->{'tp_id'}} = $row->{'tp_name'}; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/centstorage/HostStateEvents.pm b/gorgone/gorgone/modules/centreon/mbi/libs/centstorage/HostStateEvents.pm index a43002eab32..55adc8324b1 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/centstorage/HostStateEvents.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/centstorage/HostStateEvents.pm @@ -71,7 +71,7 @@ sub agreggateEventsByTimePeriod { my $hostEventObjects = $self->{"biHostStateEventsObj"}; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); $hostEventObjects->createTempBIEventsTable(); $hostEventObjects->prepareTempQuery(); @@ -154,7 +154,7 @@ sub dailyPurge { $logger->writeLog("DEBUG", "[PURGE] [hoststateevents] purging data older than ".$end); my $query = "DELETE FROM `hoststateevents` where end_time < UNIX_TIMESTAMP('".$end."')"; - $db->query($query); + $db->query({ query => $query }); } sub getNbEvents{ @@ -171,9 +171,8 @@ sub getNbEvents{ $query .= " WHERE start_time < ".$end.""; $query .= " AND end_time > ".$start.""; $query .= " AND in_downtime = 0 "; - - - my $sth = $db->query($query); + + my $sth = $db->query({ query => $query }); while (my $row = $sth->fetchrow_hashref()) { $nbEvents = $row->{'nbEvents'}; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/centstorage/Metrics.pm b/gorgone/gorgone/modules/centreon/mbi/libs/centstorage/Metrics.pm index d4fc5bf2955..93f6b2df733 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/centstorage/Metrics.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/centstorage/Metrics.pm @@ -62,7 +62,7 @@ sub getTimeColumn() { sub createTempTableMetricMinMaxAvgValues { my ($self, $useMemory, $granularity) = @_; my $db = $self->{"centstorage"}; - $db->query("DROP TABLE IF EXISTS `" . $self->{name_minmaxavg_tmp} . "`"); + $db->query({ query => "DROP TABLE IF EXISTS `" . $self->{name_minmaxavg_tmp} . "`" }); my $createTable = " CREATE TABLE `" . $self->{name_minmaxavg_tmp} . "` ("; $createTable .= " id_metric INT NULL,"; $createTable .= " avg_value FLOAT NULL,"; @@ -76,7 +76,7 @@ sub createTempTableMetricMinMaxAvgValues { }else { $createTable .= ") ENGINE=INNODB CHARSET=utf8 COLLATE=utf8_general_ci;"; } - $db->query($createTable); + $db->query({ query => $createTable }); } sub getMetricValueByHour { @@ -96,7 +96,7 @@ sub getMetricValueByHour { $query .= "ctime >=UNIX_TIMESTAMP('".$start."') AND ctime < UNIX_TIMESTAMP('".$end."') "; $query .= "GROUP BY id_metric, date_format(FROM_UNIXTIME(ctime), '".$dateFormat."')"; - $db->query($query); + $db->query({ query => $query }); $self->addIndexTempTableMetricMinMaxAvgValues("hour"); } @@ -131,7 +131,7 @@ sub getMetricsValueByDay { $query =~ s/OR $//; $query .= "GROUP BY id_metric"; - $db->query($query); + $db->query({ query => $query }); $self->addIndexTempTableMetricMinMaxAvgValues("day"); $self->getFirstAndLastValues($start_date, $end_date, $useMemory); } @@ -139,7 +139,7 @@ sub getMetricsValueByDay { sub createTempTableMetricDayFirstLastValues { my ($self, $useMemory) = @_; my $db = $self->{"centstorage"}; - $db->query("DROP TABLE IF EXISTS `" . $self->{name_firstlast_tmp} . "`"); + $db->query({ query => "DROP TABLE IF EXISTS `" . $self->{name_firstlast_tmp} . "`" }); my $createTable = " CREATE TABLE `" . $self->{name_firstlast_tmp} . "` ("; $createTable .= " `first_value` FLOAT NULL,"; $createTable .= " `last_value` FLOAT NULL,"; @@ -149,13 +149,13 @@ sub createTempTableMetricDayFirstLastValues { } else { $createTable .= ") ENGINE=INNODB CHARSET=utf8 COLLATE=utf8_general_ci;"; } - $db->query($createTable); + $db->query({ query => $createTable }); } sub addIndexTempTableMetricDayFirstLastValues { my $self = shift; my $db = $self->{"centstorage"}; - $db->query("ALTER TABLE " . $self->{name_firstlast_tmp} . " ADD INDEX (`id_metric`)"); + $db->query({ query => "ALTER TABLE " . $self->{name_firstlast_tmp} . " ADD INDEX (`id_metric`)" }); } sub addIndexTempTableMetricMinMaxAvgValues { @@ -167,13 +167,13 @@ sub addIndexTempTableMetricMinMaxAvgValues { $index .= ", valueTime"; } my $query = "ALTER TABLE " . $self->{name_minmaxavg_tmp} . " ADD INDEX (" . $index . ")"; - $db->query($query); + $db->query({ query => $query }); } sub createTempTableCtimeMinMaxValues { my ($self, $useMemory) = @_; my $db = $self->{"centstorage"}; - $db->query("DROP TABLE IF EXISTS `" . $self->{name_minmaxctime_tmp} . "`"); + $db->query({ query => "DROP TABLE IF EXISTS `" . $self->{name_minmaxctime_tmp} . "`" }); my $createTable = " CREATE TABLE `" . $self->{name_minmaxctime_tmp} . "` ("; $createTable .= " min_val INT NULL,"; $createTable .= " max_val INT NULL,"; @@ -183,13 +183,13 @@ sub createTempTableCtimeMinMaxValues { } else { $createTable .= ") ENGINE=INNODB CHARSET=utf8 COLLATE=utf8_general_ci;"; } - $db->query($createTable); + $db->query({ query => $createTable }); } sub dropTempTableCtimeMinMaxValues { my $self = shift; my $db = $self->{"centstorage"}; - $db->query("DROP TABLE `" . $self->{name_minmaxctime_tmp} . "`"); + $db->query({ query => "DROP TABLE `" . $self->{name_minmaxctime_tmp} . "`" }); } sub getFirstAndLastValues { @@ -203,7 +203,7 @@ sub getFirstAndLastValues { $query .= " FROM `data_bin`"; $query .= " WHERE ctime >= UNIX_TIMESTAMP(" . $start_date . ") AND ctime < UNIX_TIMESTAMP(" . $end_date . ")"; $query .= " GROUP BY id_metric"; - $db->query($query); + $db->query({ query => $query }); $self->createTempTableMetricDayFirstLastValues($useMemory); $query = "INSERT INTO " . $self->{name_firstlast_tmp} . " SELECT d.value as `first_value`, d2.value as `last_value`, d.id_metric"; @@ -211,7 +211,7 @@ sub getFirstAndLastValues { $query .= " WHERE db.id_metric=d.id_metric AND db.min_val=d.ctime"; $query .= " AND db.id_metric=d2.id_metric AND db.max_val=d2.ctime"; $query .= " GROUP BY db.id_metric"; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); $self->addIndexTempTableMetricDayFirstLastValues(); $self->dropTempTableCtimeMinMaxValues(); } @@ -224,7 +224,7 @@ sub dailyPurge { my $query = "DELETE FROM `data_bin` where ctime < UNIX_TIMESTAMP('" . $end . "')"; $logger->writeLog("DEBUG", "[PURGE] [data_bin] purging data older than " . $end); - $db->query($query); + $db->query({ query => $query }); } 1; diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/centstorage/ServiceStateEvents.pm b/gorgone/gorgone/modules/centreon/mbi/libs/centstorage/ServiceStateEvents.pm index af192330e9a..b70130d1d1d 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/centstorage/ServiceStateEvents.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/centstorage/ServiceStateEvents.pm @@ -71,7 +71,7 @@ sub agreggateEventsByTimePeriod { $query .= " ORDER BY start_time "; my $serviceEventObjects = $self->{"biServiceStateEventsObj"}; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); $serviceEventObjects->createTempBIEventsTable(); $serviceEventObjects->prepareTempQuery(); @@ -150,7 +150,7 @@ sub dailyPurge { $logger->writeLog("DEBUG", "[PURGE] [servicestateevents] purging data older than ".$end); my $query = "DELETE FROM `servicestateevents` where end_time < UNIX_TIMESTAMP('".$end."')"; - $db->query($query); + $db->query({ query => $query }); } sub getNbEvents { @@ -168,7 +168,7 @@ sub getNbEvents { $query .= " AND end_time > ".$start.""; $query .= " AND in_downtime = 0 "; - my $sth = $db->query($query); + my $sth = $db->query({ query => $query }); while (my $row = $sth->fetchrow_hashref()) { $nbEvents = $row->{'nbEvents'}; diff --git a/gorgone/gorgone/modules/centreon/nodes/class.pm b/gorgone/gorgone/modules/centreon/nodes/class.pm index 3e8bf50fae6..d9deecb5ff6 100644 --- a/gorgone/gorgone/modules/centreon/nodes/class.pm +++ b/gorgone/gorgone/modules/centreon/nodes/class.pm @@ -28,10 +28,9 @@ use gorgone::standard::library; use gorgone::standard::constants qw(:all); use gorgone::class::sqlquery; use gorgone::class::http::http; -use ZMQ::LibZMQ4; -use ZMQ::Constants qw(:all); use MIME::Base64; use JSON::XS; +use EV; my %handlers = (TERM => {}, HUP => {}); my ($connector); @@ -98,7 +97,7 @@ sub check_debug { my $debug_gorgone = 0; $debug_gorgone = $datas->[0]->[0] if (defined($datas->[0]->[0])); if (!defined($self->{debug_gorgone}) || $self->{debug_gorgone} != $debug_gorgone) { - $self->send_internal_action(action => 'BCASTLOGGER', data => { content => { severity => $map_values->{$debug_gorgone} } } ); + $self->send_internal_action({ action => 'BCASTLOGGER', data => { content => { severity => $map_values->{$debug_gorgone} } } }); } $self->{debug_gorgone} = $debug_gorgone; @@ -197,9 +196,9 @@ sub action_centreonnodessync { } } - $self->send_internal_action(action => 'SETCOREID', data => { id => $core_id } ) if (defined($core_id)); - $self->send_internal_action(action => 'REGISTERNODES', data => { nodes => $register_nodes } ); - $self->send_internal_action(action => 'UNREGISTERNODES', data => { nodes => $unregister_nodes } ); + $self->send_internal_action({ action => 'SETCOREID', data => { id => $core_id } }) if (defined($core_id)); + $self->send_internal_action({ action => 'REGISTERNODES', data => { nodes => $register_nodes } }); + $self->send_internal_action({ action => 'UNREGISTERNODES', data => { nodes => $unregister_nodes } }); $self->{logger}->writeLogDebug("[nodes] Finish resync"); $self->send_log(code => GORGONE_ACTION_FINISH_OK, token => $options{token}, data => { message => 'action nodesresync finished' }); @@ -208,29 +207,23 @@ sub action_centreonnodessync { return 0; } -sub event { - while (1) { - my $message = $connector->read_message(); - last if (!defined($message)); - - $connector->{logger}->writeLogDebug("[nodes] Event: $message"); - if ($message =~ /^\[(.*?)\]/) { - if ((my $method = $connector->can('action_' . lc($1)))) { - $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; - my ($action, $token) = ($1, $2); - my ($rv, $data) = $connector->json_decode(argument => $3, token => $token); - next if ($rv); - - $method->($connector, token => $token, data => $data); - } - } +sub periodic_exec { + my ($self, %options) = @_; + + if ($self->{stop} == 1) { + $self->{logger}->writeLogInfo("[nodes] -class- $$ has quit"); + exit(0); + } + + if (time() - $self->{resync_time} > $self->{last_resync_time}) { + $self->{last_resync_time} = time(); + $self->action_centreonnodessync(); } } sub run { my ($self, %options) = @_; - # Database creation. We stay in the loop still there is an error $self->{db_centreon} = gorgone::class::db->new( dsn => $self->{config_db_centreon}->{dsn}, user => $self->{config_db_centreon}->{username}, @@ -238,41 +231,26 @@ sub run { force => 0, logger => $self->{logger} ); - ##### Load objects ##### $self->{class_object} = gorgone::class::sqlquery->new(logger => $self->{logger}, db_centreon => $self->{db_centreon}); - # Connect internal - $connector->{internal_socket} = gorgone::standard::library::connect_com( + $self->{internal_socket} = gorgone::standard::library::connect_com( + context => $self->{zmq_context}, zmq_type => 'ZMQ_DEALER', name => 'gorgone-nodes', logger => $self->{logger}, type => $self->get_core_config(name => 'internal_com_type'), path => $self->get_core_config(name => 'internal_com_path') ); - $connector->send_internal_action( + $self->send_internal_action({ action => 'CENTREONNODESREADY', data => {} - ); - $self->{poll} = [ - { - socket => $connector->{internal_socket}, - events => ZMQ_POLLIN, - callback => \&event - } - ]; - while (1) { - my $rev = scalar(zmq_poll($self->{poll}, 5000)); - if (defined($rev) && $rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("[nodes] -class- $$ has quit"); - zmq_close($connector->{internal_socket}); - exit(0); - } + }); - if (time() - $self->{resync_time} > $self->{last_resync_time}) { - $self->{last_resync_time} = time(); - $self->action_centreonnodessync(); - } - } + $self->periodic_exec(); + + my $watcher_timer = $self->{loop}->timer(5, 5, sub { $self->periodic_exec() } ); + my $watcher_io = $self->{loop}->io($self->{internal_socket}->get_fd(), EV::READ, sub { $connector->event() } ); + $self->{loop}->run(); } 1; diff --git a/gorgone/gorgone/modules/centreon/nodes/hooks.pm b/gorgone/gorgone/modules/centreon/nodes/hooks.pm index 8c3b9797731..f7806358d3a 100644 --- a/gorgone/gorgone/modules/centreon/nodes/hooks.pm +++ b/gorgone/gorgone/modules/centreon/nodes/hooks.pm @@ -65,20 +65,20 @@ sub routing { } if (gorgone::class::core::waiting_ready(ready => \$nodes->{ready}) == 0) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'gorgonenodes: still no ready' }, json_encode => 1 - ); + }); return undef; } $options{gorgone}->send_internal_message( identity => 'gorgone-nodes', action => $options{action}, - data => $options{data}, + raw_data_ref => $options{frame}->getRawData(), token => $options{token} ); } diff --git a/gorgone/gorgone/modules/centreon/statistics/class.pm b/gorgone/gorgone/modules/centreon/statistics/class.pm index cdbf1851279..517c7c6fab6 100644 --- a/gorgone/gorgone/modules/centreon/statistics/class.pm +++ b/gorgone/gorgone/modules/centreon/statistics/class.pm @@ -27,12 +27,11 @@ use warnings; use gorgone::standard::library; use gorgone::standard::constants qw(:all); use gorgone::class::sqlquery; -use ZMQ::LibZMQ4; -use ZMQ::Constants qw(:all); use File::Path qw(make_path); use JSON::XS; use Time::HiRes; use RRDs; +use EV; my $result; my %handlers = (TERM => {}, HUP => {}); @@ -175,7 +174,7 @@ sub action_brokerstats { "[statistics] Collecting Broker statistics file '" . $statistics_file . "' from target '" . $target . "'" ); - $self->send_internal_action( + $self->send_internal_action({ action => 'ADDLISTENER', data => [ { @@ -188,9 +187,9 @@ sub action_brokerstats { log_pace => $self->{log_pace} } ] - ); + }); - $self->send_internal_action( + $self->send_internal_action({ target => $target, action => 'COMMAND', token => $options{token} . '-' . $target, @@ -208,7 +207,7 @@ sub action_brokerstats { } ] } - ); + }); } $self->send_log( @@ -247,7 +246,7 @@ sub action_enginestats { "[statistics] Collecting Engine statistics from target '" . $target . "'" ); - $self->send_internal_action( + $self->send_internal_action({ action => 'ADDLISTENER', data => [ { @@ -260,9 +259,9 @@ sub action_enginestats { log_pace => $self->{log_pace} } ] - ); + }); - $self->send_internal_action( + $self->send_internal_action({ target => $target, action => 'COMMAND', token => $options{token} . '-' . $target, @@ -279,7 +278,7 @@ sub action_enginestats { } ] } - ); + }); } $self->send_log( @@ -582,40 +581,28 @@ sub rrd_update { return 0; } -sub event { - while (1) { - my $message = $connector->read_message(); - last if (!defined($message)); - - $connector->{logger}->writeLogDebug("[statistics] Event: $message"); - if ($message =~ /^\[(.*?)\]/) { - if ((my $method = $connector->can('action_' . lc($1)))) { - $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; - my ($action, $token) = ($1, $2); - my ($rv, $data) = $connector->json_decode(argument => $3, token => $token); - next if ($rv); - - $method->($connector, token => $token, data => $data); - } - } +sub periodic_exec { + if ($connector->{stop} == 1) { + $connector->{logger}->writeLogInfo("[statistics] $$ has quit"); + exit(0); } } sub run { my ($self, %options) = @_; - # Connect internal - $connector->{internal_socket} = gorgone::standard::library::connect_com( + $self->{internal_socket} = gorgone::standard::library::connect_com( + context => $self->{zmq_context}, zmq_type => 'ZMQ_DEALER', name => 'gorgone-statistics', logger => $self->{logger}, type => $self->get_core_config(name => 'internal_com_type'), path => $self->get_core_config(name => 'internal_com_path') ); - $connector->send_internal_action( + $self->send_internal_action({ action => 'STATISTICSREADY', data => {} - ); + }); $self->{db_centreon} = gorgone::class::db->new( dsn => $self->{config_db_centreon}->{dsn}, @@ -641,31 +628,18 @@ sub run { db_centreon => $self->{db_centstorage} ); - $self->{poll} = [ - { - socket => $connector->{internal_socket}, - events => ZMQ_POLLIN, - callback => \&event, - } - ]; - if (defined($self->{config}->{cron})) { - $self->send_internal_action( + $self->send_internal_action({ action => 'ADDCRON', data => { - content => $self->{config}->{cron}, + content => $self->{config}->{cron} } - ); + }); } - while (1) { - my $rev = scalar(zmq_poll($self->{poll}, 5000)); - if ($rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("[statistics] $$ has quit"); - zmq_close($connector->{internal_socket}); - exit(0); - } - } + my $watcher_timer = $self->{loop}->timer(5, 5, \&periodic_exec); + my $watcher_io = $self->{loop}->io($self->{internal_socket}->get_fd(), EV::READ, sub { $connector->event() } ); + $self->{loop}->run(); } 1; diff --git a/gorgone/gorgone/modules/centreon/statistics/hooks.pm b/gorgone/gorgone/modules/centreon/statistics/hooks.pm index edb3d0c4960..8d13dd0837f 100644 --- a/gorgone/gorgone/modules/centreon/statistics/hooks.pm +++ b/gorgone/gorgone/modules/centreon/statistics/hooks.pm @@ -78,20 +78,20 @@ sub routing { } if (gorgone::class::core::waiting_ready(ready => \$statistics->{ready}) == 0) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { msg => 'gorgonestatistics: still no ready' }, json_encode => 1 - ); + }); return undef; } $options{gorgone}->send_internal_message( identity => 'gorgone-statistics', action => $options{action}, - data => $options{data}, + raw_data_ref => $options{frame}->getRawData(), token => $options{token} ); } diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index 875029760a8..3c386d16cdf 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -27,8 +27,6 @@ use warnings; use gorgone::standard::library; use gorgone::standard::constants qw(:all); use gorgone::standard::misc; -use ZMQ::LibZMQ4; -use ZMQ::Constants qw(:all); use JSON::XS; use File::Basename; use File::Copy; @@ -38,6 +36,8 @@ use MIME::Base64; use Digest::MD5::File qw(file_md5_hex); use Archive::Tar; use Fcntl; +use Try::Tiny; +use EV; $Archive::Tar::SAME_PERMISSIONS = 1; $Archive::Tar::WARN = 0; @@ -318,12 +318,11 @@ sub validate_plugins { return (1, $message) if (!$rv); my $plugins; - eval { + try { $plugins = JSON::XS->new->decode($content); - }; - if ($@) { + } catch { return (1, 'cannot decode json'); - } + }; # nothing to validate. so it's ok, show must go on!! :) if (ref($plugins) ne 'HASH' || scalar(keys %$plugins) <= 0) { @@ -731,8 +730,15 @@ sub action_actionengine { sub action_run { my ($self, %options) = @_; - + + my $context; + { + local $SIG{__DIE__}; + $context = ZMQ::FFI->new(); + } + my $socket_log = gorgone::standard::library::connect_com( + context => $context, zmq_type => 'ZMQ_DEALER', name => 'gorgone-action-'. $$, logger => $self->{logger}, @@ -755,8 +761,6 @@ sub action_run { ); return -1; } - - zmq_close($socket_log); } sub create_child { @@ -806,65 +810,61 @@ sub create_child { } sub event { - while (1) { - my $message = $connector->read_message(); - last if (!defined($message)); + my ($self, %options) = @_; + + while ($self->{internal_socket}->has_pollin()) { + my ($message) = $self->read_message(); + next if (!defined($message)); - $connector->{logger}->writeLogDebug("[action] Event: $message"); + $self->{logger}->writeLogDebug("[action] Event: $message"); if ($message !~ /^\[ACK\]/) { $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; my ($action, $token) = ($1, $2); - my ($rv, $data) = $connector->json_decode(argument => $3, token => $token); + my ($rv, $data) = $self->json_decode(argument => $3, token => $token); next if ($rv); if (defined($data->{parameters}->{no_fork})) { - if ((my $method = $connector->can('action_' . lc($action)))) { - $method->($connector, token => $token, data => $data); + if ((my $method = $self->can('action_' . lc($action)))) { + $method->($self, token => $token, data => $data); } } else { - $connector->create_child(action => $action, token => $token, data => $data); + $self->create_child(action => $action, token => $token, data => $data); } } } } +sub periodic_exec { + $connector->check_childs(); + if ($connector->{stop} == 1) { + $connector->{logger}->writeLogInfo("[action] $$ has quit"); + exit(0); + } +} + sub run { my ($self, %options) = @_; - # Connect internal - $connector->{internal_socket} = gorgone::standard::library::connect_com( + $self->{internal_socket} = gorgone::standard::library::connect_com( + context => $self->{zmq_context}, zmq_type => 'ZMQ_DEALER', name => 'gorgone-action', logger => $self->{logger}, type => $self->get_core_config(name => 'internal_com_type'), path => $self->get_core_config(name => 'internal_com_path') ); - $connector->send_internal_action( + $self->send_internal_action({ action => 'ACTIONREADY', data => {} - ); - $self->{poll} = [ - { - socket => $connector->{internal_socket}, - events => ZMQ_POLLIN, - callback => \&event, - } - ]; + }); $self->get_package_manager(); - while (1) { - my $rev = scalar(zmq_poll($self->{poll}, 5000)); - $self->check_childs(); - - if ($rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("[action] $$ has quit"); - zmq_close($connector->{internal_socket}); - exit(0); - } - } + my $watcher_timer = $self->{loop}->timer(5, 5, \&periodic_exec); + my $watcher_io = $self->{loop}->io($connector->{internal_socket}->get_fd(), EV::READ, sub { $connector->event() } ); + $self->{loop}->run(); } 1; diff --git a/gorgone/gorgone/modules/core/action/hooks.pm b/gorgone/gorgone/modules/core/action/hooks.pm index 68cd265920a..b803880f1e4 100644 --- a/gorgone/gorgone/modules/core/action/hooks.pm +++ b/gorgone/gorgone/modules/core/action/hooks.pm @@ -63,20 +63,20 @@ sub routing { } if (gorgone::class::core::waiting_ready(ready => \$action->{ready}) == 0) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { msg => 'gorgoneaction: still no ready' }, json_encode => 1 - ); + }); return undef; } $options{gorgone}->send_internal_message( identity => 'gorgone-action', action => $options{action}, - data => $options{data}, + raw_data_ref => $options{frame}->getRawData(), token => $options{token} ); } diff --git a/gorgone/gorgone/modules/core/cron/class.pm b/gorgone/gorgone/modules/core/cron/class.pm index 9d893476302..21c7078faac 100644 --- a/gorgone/gorgone/modules/core/cron/class.pm +++ b/gorgone/gorgone/modules/core/cron/class.pm @@ -27,9 +27,8 @@ use warnings; use gorgone::standard::library; use gorgone::standard::constants qw(:all); use gorgone::standard::misc; -use ZMQ::LibZMQ4; -use ZMQ::Constants qw(:all); use Schedule::Cron; +use EV; my %handlers = (TERM => {}, HUP => {}); my ($connector); @@ -86,7 +85,7 @@ sub action_getcron { if (defined($id) && $id ne '') { if (defined($parameter) && $parameter =~ /^status$/) { $self->{logger}->writeLogInfo("[cron] Get logs results for definition '" . $id . "'"); - $self->send_internal_action( + $self->send_internal_action({ action => 'GETLOG', token => $options{token}, data => { @@ -96,8 +95,10 @@ sub action_getcron { limit => $options{data}->{parameters}->{limit}, code => $options{data}->{parameters}->{code} } - ); - my $rev = zmq_poll($connector->{poll}, 5000); + }); + my $watcher_timer = $self->{loop}->timer(5, 0, \&stop_ev); + $self->{loop}->run(); + $data = $connector->{ack}->{data}->{data}->{result}; } else { my $idx; @@ -372,39 +373,46 @@ sub action_deletecron { } sub event { - while (1) { - my $message = $connector->read_message(); - last if (!defined($message)); + my ($self, %options) = @_; + + while ($self->{internal_socket}->has_pollin()) { + my ($message) = $self->read_message(); + next if (!defined($message)); - $connector->{logger}->writeLogDebug("[cron] Event: $message"); + $self->{logger}->writeLogDebug("[cron] Event: $message"); if ($message =~ /^\[ACK\]\s+\[(.*?)\]\s+(.*)$/m) { my $token = $1; - my ($rv, $data) = $connector->json_decode(argument => $2, token => $token); + my ($rv, $data) = $self->json_decode(argument => $2, token => $token); next if ($rv); - $connector->{ack} = { + $self->{ack} = { token => $token, data => $data }; } else { $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; - if ((my $method = $connector->can('action_' . lc($1)))) { + if ((my $method = $self->can('action_' . lc($1)))) { $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; my ($action, $token) = ($1, $2); - my ($rv, $data) = $connector->json_decode(argument => $3, token => $token); + my ($rv, $data) = $self->json_decode(argument => $3, token => $token); next if ($rv); - $method->($connector, token => $token, data => $data); + $method->($self, token => $token, data => $data); } - } + } } } +sub stop_ev { + $connector->{loop}->break(); +} + sub cron_sleep { - my $rev = zmq_poll($connector->{poll}, 1000); - if ($rev == 0 && $connector->{stop} == 1) { + my $watcher_timer = $connector->{loop}->timer(1, 0, \&stop_ev); + $connector->{loop}->run(); + + if ($connector->{stop} == 1) { $connector->{logger}->writeLogInfo("[cron] $$ has quit"); - zmq_close($connector->{internal_socket}); exit(0); } } @@ -417,7 +425,7 @@ sub dispatcher { my $token = (defined($options->{definition}->{keep_token})) && $options->{definition}->{keep_token} =~ /true|1/i ? $options->{definition}->{id} : undef; - $options->{connector}->send_internal_action( + $options->{connector}->send_internal_action({ socket => $options->{socket}, token => $token, action => $options->{definition}->{action}, @@ -426,41 +434,27 @@ sub dispatcher { content => $options->{definition}->{parameters} }, json_encode => 1 - ); + }); - my $poll = [ - { - socket => $options->{socket}, - events => ZMQ_POLLIN, - callback => \&event, - } - ]; - - my $rev = zmq_poll($poll, 5000); + my $watcher_timer = $options->{connector}->{loop}->timer(5, 0, \&stop_ev); + $options->{connector}->{loop}->run(); } sub run { my ($self, %options) = @_; - # Connect internal - $connector->{internal_socket} = gorgone::standard::library::connect_com( + $self->{internal_socket} = gorgone::standard::library::connect_com( + context => $self->{zmq_context}, zmq_type => 'ZMQ_DEALER', name => 'gorgone-cron', logger => $self->{logger}, type => $self->get_core_config(name => 'internal_com_type'), path => $self->get_core_config(name => 'internal_com_path') ); - $connector->send_internal_action( + $self->send_internal_action({ action => 'CRONREADY', data => {} - ); - $connector->{poll} = [ - { - socket => $connector->{internal_socket}, - events => ZMQ_POLLIN, - callback => \&event, - } - ]; + }); # need at least one cron to get sleep working push @{$self->{config}->{cron}}, { @@ -485,9 +479,10 @@ sub run { ); } + my $watcher_io = $self->{loop}->io($connector->{internal_socket}->get_fd(), EV::READ, sub { $connector->event() } ); + $self->{cron}->run(sleep => \&cron_sleep); - zmq_close($connector->{internal_socket}); exit(0); } diff --git a/gorgone/gorgone/modules/core/cron/hooks.pm b/gorgone/gorgone/modules/core/cron/hooks.pm index 924f676f603..da332ef42d2 100644 --- a/gorgone/gorgone/modules/core/cron/hooks.pm +++ b/gorgone/gorgone/modules/core/cron/hooks.pm @@ -64,20 +64,20 @@ sub routing { } if (gorgone::class::core::waiting_ready(ready => \$cron->{ready}) == 0) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'gorgonecron: still no ready' }, json_encode => 1 - ); + }); return undef; } $options{gorgone}->send_internal_message( identity => 'gorgone-cron', action => $options{action}, - data => $options{data}, + raw_data_ref => $options{frame}->getRawData(), token => $options{token} ); } diff --git a/gorgone/gorgone/modules/core/dbcleaner/class.pm b/gorgone/gorgone/modules/core/dbcleaner/class.pm index c04d1fcf219..8e4c8350aeb 100644 --- a/gorgone/gorgone/modules/core/dbcleaner/class.pm +++ b/gorgone/gorgone/modules/core/dbcleaner/class.pm @@ -27,9 +27,8 @@ use warnings; use gorgone::class::db; use gorgone::standard::library; use gorgone::standard::constants qw(:all); -use ZMQ::LibZMQ4; -use ZMQ::Constants qw(:all); use JSON::XS; +use EV; my %handlers = (TERM => {}, HUP => {}, DIE => {}); my ($connector); @@ -99,7 +98,6 @@ sub exit_process { my ($self, %options) = @_; $self->{logger}->writeLogInfo("[dbcleaner] $$ has quit"); - zmq_close($self->{internal_socket}); exit(0); } @@ -121,10 +119,14 @@ sub action_dbclean { ) if (!defined($options{cycle})); $self->{logger}->writeLogDebug("[dbcleaner] Purge database in progress..."); - my ($status) = $self->{db_gorgone}->query("DELETE FROM gorgone_identity WHERE `mtime` < " . $self->{db_gorgone}->quote(time() - $self->{config}->{purge_sessions_time})); - my ($status2) = $self->{db_gorgone}->query( - "DELETE FROM gorgone_history WHERE (instant = 1 AND `ctime` < " . (time() - 86400) . ") OR `ctime` < " . $self->{db_gorgone}->quote(time() - $self->{config}->{purge_history_time}) - ); + my ($status) = $self->{db_gorgone}->query({ + query => 'DELETE FROM gorgone_identity WHERE `mtime` < ?', + bind_values => [time() - $self->{config}->{purge_sessions_time}] + }); + my ($status2) = $self->{db_gorgone}->query({ + query => "DELETE FROM gorgone_history WHERE (instant = 1 AND `ctime` < " . (time() - 86400) . ") OR `ctime` < ?", + bind_values => [time() - $self->{config}->{purge_history_time}] + }); $self->{purge_timer} = time(); $self->{logger}->writeLogDebug("[dbcleaner] Purge finished"); @@ -150,47 +152,29 @@ sub action_dbclean { return 0; } -sub event { - while (1) { - my $message = $connector->read_message(); - last if (!defined($message)); - - $connector->{logger}->writeLogDebug("[dbcleaner] Event: $message"); - if ($message =~ /^\[(.*?)\]/) { - if ((my $method = $connector->can('action_' . lc($1)))) { - $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; - my ($action, $token) = ($1, $2); - my ($rv, $data) = $connector->json_decode(argument => $3, token => $token); - next if ($rv); - - $method->($connector, token => $token, data => $data); - } - } +sub periodic_exec { + if ($connector->{stop} == 1) { + $connector->exit_process(); } + + $connector->action_dbclean(cycle => 1); } sub run { my ($self, %options) = @_; - # Connect internal - $connector->{internal_socket} = gorgone::standard::library::connect_com( + $self->{internal_socket} = gorgone::standard::library::connect_com( + context => $self->{zmq_context}, zmq_type => 'ZMQ_DEALER', name => 'gorgone-dbcleaner', logger => $self->{logger}, type => $self->get_core_config(name => 'internal_com_type'), path => $self->get_core_config(name => 'internal_com_path') ); - $connector->send_internal_action( + $self->send_internal_action({ action => 'DBCLEANERREADY', data => {} - ); - $self->{poll} = [ - { - socket => $connector->{internal_socket}, - events => ZMQ_POLLIN, - callback => \&event, - } - ]; + }); $self->{db_gorgone} = gorgone::class::db->new( type => $self->get_core_config(name => 'gorgone_db_type'), @@ -203,14 +187,9 @@ sub run { logger => $self->{logger} ); - while (1) { - my $rev = scalar(zmq_poll($self->{poll}, 5000)); - if ($rev == 0 && $self->{stop} == 1) { - $self->exit_process(); - } - - $self->action_dbclean(cycle => 1); - } + my $watcher_timer = $self->{loop}->timer(5, 5, \&periodic_exec); + my $watcher_io = $self->{loop}->io($connector->{internal_socket}->get_fd(), EV::READ, sub { $connector->event() } ); + $self->{loop}->run(); } 1; diff --git a/gorgone/gorgone/modules/core/dbcleaner/hooks.pm b/gorgone/gorgone/modules/core/dbcleaner/hooks.pm index 5dd51d9b092..dba893cb3a4 100644 --- a/gorgone/gorgone/modules/core/dbcleaner/hooks.pm +++ b/gorgone/gorgone/modules/core/dbcleaner/hooks.pm @@ -69,22 +69,22 @@ sub routing { $dbcleaner->{ready} = 1; return undef; } - + if (gorgone::class::core::waiting_ready(ready => \$dbcleaner->{ready}) == 0) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'gorgonedbcleaner: still no ready' }, json_encode => 1 - ); + }); return undef; } - + $options{gorgone}->send_internal_message( identity => 'gorgone-dbcleaner', action => $options{action}, - data => $options{data}, + raw_data_ref => $options{frame}->getRawData(), token => $options{token} ); } diff --git a/gorgone/gorgone/modules/core/httpserver/class.pm b/gorgone/gorgone/modules/core/httpserver/class.pm index 4eeb09b784b..74461ead10a 100644 --- a/gorgone/gorgone/modules/core/httpserver/class.pm +++ b/gorgone/gorgone/modules/core/httpserver/class.pm @@ -27,13 +27,12 @@ use warnings; use gorgone::standard::library; use gorgone::standard::misc; use gorgone::standard::api; -use ZMQ::LibZMQ4; -use ZMQ::Constants qw(:all); use HTTP::Daemon; use HTTP::Status; use MIME::Base64; use JSON::XS; use Socket; +use EV; my $time = time(); @@ -104,15 +103,6 @@ sub class_handle_HUP { } } -sub event { - while (1) { - my $message = $connector->read_message(); - last if (!defined($message)); - - $connector->{logger}->writeLogDebug("[httpserver] Event: $message"); - } -} - sub init_dispatch { my ($self, $config_dispatch) = @_; @@ -150,33 +140,34 @@ sub load_peer_subnets { } } +sub stop_ev { + $connector->{loop}->break(); +} + sub run { my ($self, %options) = @_; $self->load_peer_subnets(); # Connect internal - $connector->{internal_socket} = gorgone::standard::library::connect_com( + $self->{internal_socket} = gorgone::standard::library::connect_com( + context => $connector->{zmq_context}, zmq_type => 'ZMQ_DEALER', name => 'gorgone-httpserver', logger => $self->{logger}, type => $self->get_core_config(name => 'internal_com_type'), path => $self->get_core_config(name => 'internal_com_path') ); - $connector->send_internal_action( + $self->send_internal_action({ action => 'HTTPSERVERREADY', data => {} - ); + }); - $self->{poll} = [ - { - socket => $connector->{internal_socket}, - events => ZMQ_POLLIN, - callback => \&event, - } - ]; + gorgone::standard::api::set_module($self); - my $rev = zmq_poll($self->{poll}, 4000); + my $watcher_timer = $self->{loop}->timer(4, 0, \&stop_ev); + my $watcher_io = $self->{loop}->io($connector->{internal_socket}->get_fd(), EV::READ, \&gorgone::standard::api::event); + $self->{loop}->run(); $self->init_dispatch(); @@ -213,7 +204,6 @@ sub run { if ($self->{stop} == 1) { $self->{logger}->writeLogInfo("[httpserver] $$ has quit"); $connection->close() if (defined($connection)); - zmq_close($connector->{internal_socket}); exit(0); } diff --git a/gorgone/gorgone/modules/core/httpserver/hooks.pm b/gorgone/gorgone/modules/core/httpserver/hooks.pm index 2258753d150..9f751180f67 100644 --- a/gorgone/gorgone/modules/core/httpserver/hooks.pm +++ b/gorgone/gorgone/modules/core/httpserver/hooks.pm @@ -74,20 +74,20 @@ sub routing { } if (gorgone::class::core::waiting_ready(ready => \$httpserver->{ready}) == 0) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'gorgonehttpserver: still no ready' }, json_encode => 1 - ); + }); return undef; } $options{gorgone}->send_internal_message( identity => 'gorgone-httpserver', action => $options{action}, - data => $options{data}, + raw_data_ref => $options{frame}->getRawData(), token => $options{token} ); } diff --git a/gorgone/gorgone/modules/core/httpserverng/class.pm b/gorgone/gorgone/modules/core/httpserverng/class.pm index b57bbdbb52d..57ef32290ad 100644 --- a/gorgone/gorgone/modules/core/httpserverng/class.pm +++ b/gorgone/gorgone/modules/core/httpserverng/class.pm @@ -27,14 +27,14 @@ use warnings; use gorgone::standard::library; use gorgone::standard::constants qw(:all); use gorgone::standard::misc; -use ZMQ::Constants qw(:all); -use ZMQ::LibZMQ4; use Mojolicious::Lite; use Mojo::Server::Daemon; use Authen::Simple::Password; use IO::Socket::SSL; use IO::Handle; use JSON::XS; +use IO::Poll qw(POLLIN POLLPRI); +use EV; my %handlers = (TERM => {}, HUP => {}); my ($connector); @@ -236,24 +236,43 @@ sub run { # Connect internal $self->{internal_socket} = gorgone::standard::library::connect_com( + context => $connector->{zmq_context}, zmq_type => 'ZMQ_DEALER', name => 'gorgone-httpserverng', logger => $self->{logger}, type => $self->get_core_config(name => 'internal_com_type'), path => $self->get_core_config(name => 'internal_com_path') ); - $self->send_internal_action( + $self->send_internal_action({ action => 'HTTPSERVERNGREADY', data => {} - ); + }); $self->read_zmq_events(); - my $socket_fd = gorgone::standard::library::zmq_getfd(socket => $self->{internal_socket}); - my $socket = IO::Handle->new_from_fd($socket_fd, 'r'); - Mojo::IOLoop->singleton->reactor->io($socket => sub { - $connector->read_zmq_events(); - }); - Mojo::IOLoop->singleton->reactor->watch($socket, 1, 0); + my $type = ref(Mojo::IOLoop->singleton->reactor); + my $watcher_io; + if ($type eq 'Mojo::Reactor::Poll') { + Mojo::IOLoop->singleton->reactor->{io}{ $self->{internal_socket}->get_fd()} = { + cb => sub { $connector->read_zmq_events(); }, + mode => POLLIN | POLLPRI + }; + } else { + # need EV version 4.32 + $watcher_io = EV::io( + $self->{internal_socket}->get_fd(), + EV::READ, + sub { + $connector->read_zmq_events(); + } + ); + } + + #my $socket_fd = gorgone::standard::library::zmq_getfd(socket => $self->{internal_socket}); + #my $socket = IO::Handle->new_from_fd($socket_fd, 'r'); + #Mojo::IOLoop->singleton->reactor->io($socket => sub { + # $connector->read_zmq_events(); + #}); + #Mojo::IOLoop->singleton->reactor->watch($socket, 1, 0); Mojo::IOLoop->singleton->recurring(60 => sub { $connector->{logger}->writeLogDebug('[httpserverng] recurring timeout loop'); @@ -303,7 +322,6 @@ sub run { $daemon->run(); - zmq_close($self->{internal_socket}); exit(0); } @@ -378,29 +396,25 @@ sub read_listener { sub read_zmq_events { my ($self, %options) = @_; - while (my $events = gorgone::standard::library::zmq_events(socket => $self->{internal_socket})) { - if ($events & ZMQ_POLLIN) { - my $message = $connector->read_message(); - $connector->{logger}->writeLogDebug('[httpserverng] zmq message received: ' . $message); - if ($message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m || - $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+(.*)$/m) { - my ($action, $token, $data) = ($1, $2, $3); - if (defined($connector->{token_watch}->{$token})) { - if ($action eq 'HTTPSERVERNGLISTENER') { - $connector->read_listener(token => $token, data => $data); - } elsif ($token =~ /-log$/) { - $connector->read_log_event(token => $token, data => $data); - } + while ($self->{internal_socket}->has_pollin()) { + my ($message) = $connector->read_message(); + $connector->{logger}->writeLogDebug('[httpserverng] zmq message received: ' . $message); + if ($message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m || + $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+(.*)$/m) { + my ($action, $token, $data) = ($1, $2, $3); + if (defined($connector->{token_watch}->{$token})) { + if ($action eq 'HTTPSERVERNGLISTENER') { + $connector->read_listener(token => $token, data => $data); + } elsif ($token =~ /-log$/) { + $connector->read_log_event(token => $token, data => $data); } - if ((my $method = $connector->can('action_' . lc($action)))) { - my ($rv, $decoded) = $connector->json_decode(argument => $data, token => $token); - if (!$rv) { - $method->($connector, token => $token, data => $decoded); - } + } + if ((my $method = $connector->can('action_' . lc($action)))) { + my ($rv, $decoded) = $connector->json_decode(argument => $data, token => $token); + if (!$rv) { + $method->($connector, token => $token, data => $decoded); } } - } else { - last; } } } @@ -449,11 +463,11 @@ sub get_log { my ($self, %options) = @_; if (defined($options{target}) && $options{target} ne '') { - $self->send_internal_action( + $self->send_internal_action({ target => $options{target}, action => 'GETLOG', data => {} - ); + }); $self->read_zmq_events(); } @@ -468,14 +482,14 @@ sub get_log { mojo => $options{mojo} }; - $self->send_internal_action( + $self->send_internal_action({ action => 'GETLOG', token => $token_log, data => { token => $options{token}, %{$options{parameters}} } - ); + }); $self->read_zmq_events(); @@ -500,7 +514,7 @@ sub call_action { results => [] }; - $self->send_internal_action( + $self->send_internal_action({ action => 'ADDLISTENER', data => [ { @@ -512,16 +526,16 @@ sub call_action { timeout => 110 } ] - ); + }); $self->read_zmq_events(); } - $self->send_internal_action( + $self->send_internal_action({ action => $options{action}, target => $options{target}, token => $action_token, data => $options{data} - ); + }); $self->read_zmq_events(); if ($options{async} == 1) { diff --git a/gorgone/gorgone/modules/core/httpserverng/hooks.pm b/gorgone/gorgone/modules/core/httpserverng/hooks.pm index fc2e69898f9..14525e1c747 100644 --- a/gorgone/gorgone/modules/core/httpserverng/hooks.pm +++ b/gorgone/gorgone/modules/core/httpserverng/hooks.pm @@ -75,20 +75,20 @@ sub routing { } if (gorgone::class::core::waiting_ready(ready => \$httpserverng->{ready}) == 0) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'gorgone-httpserverng: still no ready' }, json_encode => 1 - ); + }); return undef; } $options{gorgone}->send_internal_message( identity => 'gorgone-httpserverng', action => $options{action}, - data => $options{data}, + raw_data_ref => $options{frame}->getRawData(), token => $options{token} ); } diff --git a/gorgone/gorgone/modules/core/pipeline/class.pm b/gorgone/gorgone/modules/core/pipeline/class.pm index 82c412fd01a..bb80a24b0c0 100644 --- a/gorgone/gorgone/modules/core/pipeline/class.pm +++ b/gorgone/gorgone/modules/core/pipeline/class.pm @@ -27,9 +27,8 @@ use warnings; use gorgone::class::db; use gorgone::standard::library; use gorgone::standard::constants qw(:all); -use ZMQ::LibZMQ4; -use ZMQ::Constants qw(:all); use JSON::XS; +use EV; my %handlers = (TERM => {}, HUP => {}); my ($connector); @@ -84,7 +83,7 @@ sub send_listener { my $current = $self->{pipelines}->{ $options{token} }->{current}; $self->{pipelines}->{ $options{token} }->{pipe}->[$current]->{created} = time(); - $self->send_internal_action( + $self->send_internal_action({ action => 'ADDLISTENER', data => [ { @@ -96,14 +95,14 @@ sub send_listener { log_pace => $self->{pipelines}->{ $options{token} }->{pipe}->[$current]->{log_pace} } ] - ); + }); - $self->send_internal_action( + $self->send_internal_action({ action => $self->{pipelines}->{ $options{token} }->{pipe}->[$current]->{action}, target => $self->{pipelines}->{ $options{token} }->{pipe}->[$current]->{target}, token => $options{token} . '-' . $current, data => $self->{pipelines}->{ $options{token} }->{pipe}->[$current]->{data} - ); + }); $self->{logger}->writeLogDebug("[pipeline] -class- pipeline '$options{token}' run $current"); $self->send_log( @@ -212,58 +211,34 @@ sub check_timeout { } } -sub event { - while (1) { - my $message = $connector->read_message(); - last if (!defined($message)); - - $connector->{logger}->writeLogDebug("[pipeline] -class- event: $message"); - if ($message =~ /^\[(.*?)\]/) { - if ((my $method = $connector->can('action_' . lc($1)))) { - $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; - my ($action, $token) = ($1, $2); - my ($rv, $data) = $connector->json_decode(argument => $3, token => $token); - next if ($rv); - - $method->($connector, token => $token, data => $data); - } - } +sub periodic_exec { + if ($connector->{stop} == 1) { + $connector->{logger}->writeLogInfo("[pipeline] -class- $$ has quit"); + exit(0); } + + $connector->check_timeout(); } sub run { my ($self, %options) = @_; - # Connect internal - $connector->{internal_socket} = gorgone::standard::library::connect_com( + $self->{internal_socket} = gorgone::standard::library::connect_com( + context => $self->{zmq_context}, zmq_type => 'ZMQ_DEALER', name => 'gorgone-pipeline', logger => $self->{logger}, type => $self->get_core_config(name => 'internal_com_type'), path => $self->get_core_config(name => 'internal_com_path') ); - $connector->send_internal_action( + $self->send_internal_action({ action => 'PIPELINEREADY', data => {} - ); - $self->{poll} = [ - { - socket => $connector->{internal_socket}, - events => ZMQ_POLLIN, - callback => \&event, - } - ]; - - while (1) { - my $rev = scalar(zmq_poll($self->{poll}, 5000)); - if ($rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("[pipeline] -class- $$ has quit"); - zmq_close($connector->{internal_socket}); - exit(0); - } + }); - $self->check_timeout(); - } + my $watcher_timer = $self->{loop}->timer(5, 5, \&periodic_exec); + my $watcher_io = $self->{loop}->io($connector->{internal_socket}->get_fd(), EV::READ, sub { $connector->event() } ); + $self->{loop}->run(); } 1; diff --git a/gorgone/gorgone/modules/core/pipeline/hooks.pm b/gorgone/gorgone/modules/core/pipeline/hooks.pm index 38facc35c4e..83aed872a2c 100644 --- a/gorgone/gorgone/modules/core/pipeline/hooks.pm +++ b/gorgone/gorgone/modules/core/pipeline/hooks.pm @@ -72,20 +72,20 @@ sub routing { } if (gorgone::class::core::waiting_ready(ready => \$pipeline->{ready}) == 0) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'gorgone-pipeline: still no ready' }, json_encode => 1 - ); + }); return undef; } $options{gorgone}->send_internal_message( identity => 'gorgone-pipeline', action => $options{action}, - data => $options{data}, + raw_data_ref => $options{frame}->getRawData(), token => $options{token} ); } diff --git a/gorgone/gorgone/modules/core/proxy/class.pm b/gorgone/gorgone/modules/core/proxy/class.pm index 0029ea88e18..6ca1ee63a12 100644 --- a/gorgone/gorgone/modules/core/proxy/class.pm +++ b/gorgone/gorgone/modules/core/proxy/class.pm @@ -28,9 +28,8 @@ use gorgone::standard::library; use gorgone::standard::constants qw(:all); use gorgone::class::clientzmq; use gorgone::modules::core::proxy::sshclient; -use ZMQ::LibZMQ4; -use ZMQ::Constants qw(:all); use JSON::XS; +use EV; my %handlers = (TERM => {}, HUP => {}); my ($connector); @@ -43,6 +42,8 @@ sub new { $connector->{pool_id} = $options{pool_id}; $connector->{clients} = {}; $connector->{internal_channels} = {}; + $connector->{watchers} = {}; + $connector->set_signal_handlers(); return $connector; @@ -88,10 +89,10 @@ sub exit_process { $self->close_connections(); foreach (keys %{$self->{internal_channels}}) { $self->{logger}->writeLogInfo("[proxy] Close internal connection for $_"); - zmq_close($self->{internal_channels}->{$_}); + $self->{internal_channels}->{$_}->close(); } $self->{logger}->writeLogInfo("[proxy] Close control connection"); - zmq_close($self->{internal_socket}); + $self->{internal_socket}->close(); exit(0); } @@ -113,25 +114,25 @@ sub read_message_client { # if we get a pong response, we can open the internal com read $connector->{clients}->{ $client_identity }->{com_read_internal} = 1; - $connector->send_internal_action( + $connector->send_internal_action({ action => 'PONG', data => $data, token => $token, target => '' - ); + }); } elsif ($options{data} =~ /^\[(?:REGISTERNODES|UNREGISTERNODES|SYNCLOGS)\]/) { if ($options{data} !~ /^\[(.+?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)/ms) { return undef; } my ($action, $token, $data) = ($1, $2, $3); - $connector->send_internal_action( + $connector->send_internal_action({ action => $action, data => $data, data_noencode => 1, token => $token, target => '' - ); + }); } elsif ($options{data} =~ /^\[ACK\]\s+\[(.*?)\]\s+(.*)/ms) { my ($code, $data) = $connector->json_decode(argument => $2); return undef if ($code == 1); @@ -139,12 +140,12 @@ sub read_message_client { # we set the id (distant node can not have id in configuration) $data->{data}->{id} = $client_identity; if (defined($data->{data}->{action}) && $data->{data}->{action} eq 'getlog') { - $connector->send_internal_action( + $connector->send_internal_action({ action => 'SETLOGS', data => $data, token => $1, target => '' - ); + }); } } } @@ -154,6 +155,8 @@ sub connect { if ($self->{clients}->{$options{id}}->{type} eq 'push_zmq') { $self->{clients}->{$options{id}}->{class} = gorgone::class::clientzmq->new( + context => $self->{zmq_context}, + core_loop => $self->{loop}, identity => 'gorgone-proxy-' . $self->{core_id} . '-' . $options{id}, cipher => $self->{clients}->{ $options{id} }->{cipher}, vector => $self->{clients}->{ $options{id} }->{vector}, @@ -218,28 +221,36 @@ sub action_proxyaddnode { $self->{logger}->writeLogInfo("[proxy] Recreate session for $data->{id}"); # we send a pong reset. because the ping can be lost - $self->send_internal_action( + $self->send_internal_action({ action => 'PONGRESET', data => '{ "data": { "id": ' . $data->{id} . ' } }', data_noencode => 1, token => $self->generate_token(), target => '' - ); + }); $self->{clients}->{ $data->{id} }->{class}->close(); } else { $self->{internal_channels}->{ $data->{id} } = gorgone::standard::library::connect_com( + context => $self->{zmq_context}, zmq_type => 'ZMQ_DEALER', name => 'gorgone-proxy-channel-' . $data->{id}, logger => $self->{logger}, type => $self->get_core_config(name => 'internal_com_type'), path => $self->get_core_config(name => 'internal_com_path') ); - $self->send_internal_action( + $self->send_internal_action({ action => 'PROXYREADY', data => { node_id => $data->{id} } + }); + $self->{watchers}->{ $data->{id} } = $self->{loop}->io( + $self->{internal_channels}->{ $data->{id} }->get_fd(), + EV::READ, + sub { + $connector->event(channel => $data->{id}); + } ); } @@ -286,6 +297,8 @@ sub action_proxystopreadchannel { $self->{logger}->writeLogInfo("[proxy] Stop read channel for $data->{id}"); $self->{clients}->{ $data->{id} }->{com_read_internal} = 0; + + delete $self->{watchers}->{ $data->{id} }; } sub close_connections { @@ -310,12 +323,12 @@ sub proxy_ssh { $self->{clients}->{ $options{target_client} }->{delete} = 1; } else { $self->{clients}->{ $options{target_client} }->{com_read_internal} = 1; - $self->send_internal_action( + $self->send_internal_action({ action => 'PONG', data => { data => { id => $options{target_client} } }, token => $options{token}, target => '' - ); + }); } return ; } @@ -458,109 +471,88 @@ sub proxy { } } -=begin comment - perl binding zmq_poll order fired events (first of the array,...) - the first array element is: the control channel -=cut -sub event_internal { - my (%options) = @_; +sub event { + my ($self, %options) = @_; - return if ( - defined($connector->{clients}->{ $options{channel} }) && - ($connector->{clients}->{ $options{channel} }->{com_read_internal} == 0 || $connector->{clients}->{ $options{channel} }->{delete} == 1) - ); + my $socket; + if (defined($options{channel})) { + return if ( + defined($self->{clients}->{ $options{channel} }) && + ($self->{clients}->{ $options{channel} }->{com_read_internal} == 0 || $self->{clients}->{ $options{channel} }->{delete} == 1) + ); - my $socket = $options{channel} eq 'control' ? $connector->{internal_socket} : $connector->{internal_channels}->{ $options{channel} }; - while (1) { - my $message = $connector->read_message(socket => $socket); - last if (!defined($message)); + $socket = $options{channel} eq 'control' ? $self->{internal_socket} : $self->{internal_channels}->{ $options{channel} }; + } else { + $socket = $options{socket}; + $options{channel} = 'control'; + } + + while ($socket->has_pollin()) { + my ($message) = $self->read_message(socket => $socket); + next if (!defined($message)); proxy(message => $message, channel => $options{channel}); - if ($connector->{stop} == 1 && (time() - $connector->{exit_timeout}) > $connector->{stop_time}) { - $connector->exit_process(); + if ($self->{stop} == 1 && (time() - $self->{exit_timeout}) > $self->{stop_time}) { + $self->exit_process(); } return if ( - defined($connector->{clients}->{ $options{channel} }) && - ($connector->{clients}->{ $options{channel} }->{com_read_internal} == 0 || $connector->{clients}->{ $options{channel} }->{delete} == 1) + defined($self->{clients}->{ $options{channel} }) && + ($self->{clients}->{ $options{channel} }->{com_read_internal} == 0 || $self->{clients}->{ $options{channel} }->{delete} == 1) ); } } -sub generate_internal_cb { - my (%options) = @_; +sub periodic_exec { + foreach (keys %{$connector->{clients}}) { + if (defined($connector->{clients}->{$_}->{delete}) && $connector->{clients}->{$_}->{delete} == 1) { + $connector->send_internal_action({ + action => 'PONGRESET', + data => '{ "data": { "id": ' . $_ . ' } }', + data_noencode => 1, + token => $connector->generate_token(), + target => '' + }); + $connector->{clients}->{$_}->{class}->close() if (defined($connector->{clients}->{$_}->{class})); + $connector->{clients}->{$_}->{class} = undef; + $connector->{clients}->{$_}->{delete} = 0; + $connector->{clients}->{$_}->{com_read_internal} = 0; + next; + } + } - my $channel = $options{channel}; - return sub { - event_internal(channel => $channel); - }; + if ($connector->{stop} == 1) { + $connector->exit_process(); + } } sub run { my ($self, %options) = @_; - # Connect canal internal $self->{internal_socket} = gorgone::standard::library::connect_com( + context => $self->{zmq_context}, zmq_type => 'ZMQ_DEALER', name => 'gorgone-proxy-' . $self->{pool_id}, logger => $self->{logger}, type => $self->get_core_config(name => 'internal_com_type'), path => $self->get_core_config(name => 'internal_com_path') ); - $self->send_internal_action( + $self->send_internal_action({ action => 'PROXYREADY', data => { pool_id => $self->{pool_id} } - ); - my $poll = { - socket => $self->{internal_socket}, - events => ZMQ_POLLIN, - callback => sub { - event_internal(channel => 'control'); - } - }; - while (1) { - my $polls = [$poll]; - foreach (keys %{$self->{clients}}) { - if (defined($self->{clients}->{$_}->{delete}) && $self->{clients}->{$_}->{delete} == 1) { - $self->send_internal_action( - action => 'PONGRESET', - data => '{ "data": { "id": ' . $_ . ' } }', - data_noencode => 1, - token => $self->generate_token(), - target => '' - ); - $self->{clients}->{$_}->{class}->close() if (defined($self->{clients}->{$_}->{class})); - $self->{clients}->{$_}->{class} = undef; - $self->{clients}->{$_}->{delete} = 0; - $self->{clients}->{$_}->{com_read_internal} = 0; - next; - } - - if ($self->{clients}->{$_}->{com_read_internal} == 1) { - push @$polls, { - socket => $self->{internal_channels}->{$_}, - events => ZMQ_POLLIN, - callback => generate_internal_cb(channel => $self->{clients}->{$_}->{id}) - }; - } - if (defined($self->{clients}->{$_}->{class}) && $self->{clients}->{$_}->{type} eq 'push_zmq') { - push @$polls, $self->{clients}->{$_}->{class}->get_poll(); - } - } - - # we try to do all we can - my $rv = scalar(zmq_poll($polls, 5000)); - - # Sometimes (with big message) we have a undef ??!!! - if ($rv == -1) { - $self->{logger}->writeLogError("[proxy] zmq_poll failed: $!"); + }); + + my $watcher_timer = $self->{loop}->timer(5, 5, \&periodic_exec); + my $watcher_io = $self->{loop}->io( + $self->{internal_socket}->get_fd(), + EV::READ, + sub { + $connector->event(channel => 'control'); } + ); - if ($rv == 0 && $self->{stop} == 1) { - $self->exit_process(); - } - } + $self->{loop}->run(); } 1; diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index 374fc3ce72f..06693f74906 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -23,6 +23,7 @@ package gorgone::modules::core::proxy::hooks; use warnings; use strict; use JSON::XS; +use gorgone::class::frame; use gorgone::standard::misc; use gorgone::class::core; use gorgone::standard::library; @@ -33,6 +34,12 @@ use MIME::Base64; use Digest::MD5::File qw(file_md5_hex); use Fcntl; use Time::HiRes; +use Try::Tiny; +use Archive::Tar; +use File::Find; + +$Archive::Tar::SAME_PERMISSIONS = 1; +$Archive::Tar::WARN = 0; =begin comment for each proxy processus, we have: @@ -133,19 +140,16 @@ sub init { sub routing { my (%options) = @_; - my $data; - eval { - $data = JSON::XS->new->decode($options{data}); - }; - if ($@) { - $options{logger}->writeLogError("[proxy] Cannot decode json data: $@"); - gorgone::standard::library::add_history( + my $data = $options{frame}->decodeData(); + if (!defined($data)) { + $options{logger}->writeLogError("[proxy] Cannot decode json data: " . $options{frame}->getLastError()); + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'proxy - cannot decode json' }, json_encode => 1 - ); + }); return undef; } @@ -153,7 +157,7 @@ sub routing { return undef if (!defined($data->{data}->{id}) || $data->{data}->{id} eq ''); $constatus_ping->{ $data->{data}->{id} }->{in_progress_ping} = 0; $constatus_ping->{ $data->{data}->{id} }->{ping_timeout} = 0; - $last_pong->{$data->{data}->{id}} = time(); + $last_pong->{ $data->{data}->{id} } = time(); $constatus_ping->{ $data->{data}->{id} }->{last_ping_recv} = time(); $constatus_ping->{ $data->{data}->{id} }->{nodes} = $data->{data}->{data}; $constatus_ping->{ $data->{data}->{id} }->{ping_ok}++; @@ -192,7 +196,7 @@ sub routing { routing( action => 'PROXYADDNODE', target => $node_id, - data => JSON::XS->new->encode($register_nodes->{$node_id}), + frame => gorgone::class::frame->new(data => $register_nodes->{$node_id}), gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger} @@ -223,51 +227,51 @@ sub routing { # we check if we have all proxy connected if (gorgone::class::core::waiting_ready_pool() == 0) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, - data => { message => 'proxy - still all ready' }, + data => { message => 'proxy - still not ready' }, json_encode => 1 - ); + }); return ; } if ($options{action} eq 'GETLOG') { if (defined($register_nodes->{$target_parent}) && $register_nodes->{$target_parent}->{type} eq 'push_ssh') { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => "proxy - can't get log a ssh target or through a ssh node" }, json_encode => 1 - ); + }); return undef; } if (defined($register_nodes->{$target})) { if ($synctime_nodes->{$target}->{synctime_error} == -1 && get_sync_time(dbh => $options{dbh}, node_id => $target) == -1) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'proxy - problem to getlog' }, json_encode => 1 - ); + }); return undef; } if ($synctime_nodes->{$target}->{in_progress} == 1) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'proxy - getlog already in progress' }, json_encode => 1 - ); + }); return undef; } # We put the good time to get my $ctime = $synctime_nodes->{$target}->{ctime}; - $data = { ctime => $ctime }; + $options{frame}->setData({ ctime => $ctime }); $synctime_nodes->{$target}->{in_progress} = 1; $synctime_nodes->{$target}->{in_progress_time} = time(); } @@ -275,7 +279,7 @@ sub routing { my $action = $options{action}; my $bulk_actions; - push @{$bulk_actions}, $data; + push @{$bulk_actions}, $options{frame}->getRawData(); if ($options{action} eq 'REMOTECOPY' && defined($register_nodes->{$target_parent}) && $register_nodes->{$target_parent}->{type} ne 'push_ssh') { @@ -302,18 +306,18 @@ sub routing { if ($is_ctrl_channel == 0 && $synctime_nodes->{$target_parent}->{channel_ready} == 1) { $identity = 'gorgone-proxy-channel-' . $target_parent; } - if ($register_nodes->{$target_parent}->{type} eq 'wss') { + if ($register_nodes->{$target_parent}->{type} eq 'wss' || $register_nodes->{$target_parent}->{type} eq 'pullwss') { $identity = 'gorgone-proxy-httpserver'; } - foreach my $data (@{$bulk_actions}) { + foreach my $raw_data_ref (@{$bulk_actions}) { # Mode zmq pull if ($register_nodes->{$target_parent}->{type} eq 'pull') { pull_request( gorgone => $options{gorgone}, dbh => $options{dbh}, action => $action, - data => $data, + raw_data_ref => $raw_data_ref, token => $options{token}, target_parent => $target_parent, target => $target, @@ -325,12 +329,14 @@ sub routing { $options{gorgone}->send_internal_message( identity => $identity, action => $action, - data => $data, + raw_data_ref => $raw_data_ref, token => $options{token}, target => $target_complete, - json_encode => 1 + nosync => 1 ); } + + $options{gorgone}->router_internal_event(); } sub gently { @@ -420,14 +426,14 @@ sub check { # We check synclog/ping/ping request timeout foreach (keys %$synctime_nodes) { - if ($register_nodes->{$_}->{type} =~ /^(?:pull|wss)$/ && $constatus_ping->{$_}->{in_progress_ping} == 1) { + if ($register_nodes->{$_}->{type} =~ /^(?:pull|wss|pullwss)$/ && $constatus_ping->{$_}->{in_progress_ping} == 1) { my $ping_timeout = defined($register_nodes->{$_}->{ping_timeout}) ? $register_nodes->{$_}->{ping_timeout} : 30; if ((time() - $constatus_ping->{$_}->{in_progress_ping_pull}) > $ping_timeout) { $constatus_ping->{$_}->{in_progress_ping} = 0; $options{logger}->writeLogInfo("[proxy] Ping timeout from '" . $_ . "'"); } } - if ($register_nodes->{$_}->{type} !~ /^(?:pull|wss)$/ && $constatus_ping->{$_}->{in_progress_ping} == 1) { + if ($register_nodes->{$_}->{type} !~ /^(?:pull|wss|pullwss)$/ && $constatus_ping->{$_}->{in_progress_ping} == 1) { if (time() - $constatus_ping->{ $_ }->{last_ping_sent} > $config->{pong_discard_timeout}) { $options{logger}->writeLogInfo("[proxy] Ping timeout from '" . $_ . "'"); $constatus_ping->{$_}->{in_progress_ping} = 0; @@ -438,7 +444,7 @@ sub check { routing( target => $_, action => 'PROXYCLOSECONNECTION', - data => JSON::XS->new->encode({ id => $_ }), + frame => gorgone::class::frame->new(data => { id => $_ }), gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger} @@ -449,12 +455,12 @@ sub check { if ($synctime_nodes->{$_}->{in_progress} == 1 && time() - $synctime_nodes->{$_}->{in_progress_time} > $synctimeout_option) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, data => { message => "proxy - getlog in timeout for '$_'" }, json_encode => 1 - ); + }); $synctime_nodes->{$_}->{in_progress} = 0; } } @@ -511,23 +517,23 @@ sub pathway { my $target = $options{target}; if (!defined($target)) { $options{logger}->writeLogDebug('[proxy] need a valid node id'); - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'proxy - need a valid node id' }, json_encode => 1 - ); + }); return -1; } if (!defined($register_nodes->{$target}) && !defined($register_subnodes->{$target})) { $options{logger}->writeLogDebug("[proxy] unknown target '$target'"); - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'proxy - unknown target ' . $target }, json_encode => 1 - ); + }); return -1; } @@ -544,7 +550,7 @@ sub pathway { my $first_target; foreach (@targets) { - if ($register_nodes->{$_}->{type} =~ /^(?:pull|wss)$/ && !defined($register_nodes->{$_}->{identity})) { + if ($register_nodes->{$_}->{type} =~ /^(?:pull|wss|pullwss)$/ && !defined($register_nodes->{$_}->{identity})) { $options{logger}->writeLogDebug("[proxy] skip node " . $register_nodes->{$_}->{type} . " target '$_' for node '$target' - never connected"); next; } @@ -564,8 +570,8 @@ sub pathway { $synctime_nodes->{$_}->{channel_read_stop} = 1; routing( target => $_, - action => 'PROXYCLOSEREADCHANNEL', - data => JSON::XS->new->encode({ id => $_ }), + action => 'PROXYSTOPREADCHANNEL', + frame => gorgone::class::frame->new(data => { id => $_ }), gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger} @@ -575,12 +581,12 @@ sub pathway { if (!defined($first_target)) { $options{logger}->writeLogDebug("[proxy] no pathway for target '$target'"); - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'proxy - no pathway for target ' . $target }, json_encode => 1 - ); + }); return -1; } @@ -592,21 +598,21 @@ sub setlogs { my (%options) = @_; if (!defined($options{data}->{data}->{id}) || $options{data}->{data}->{id} eq '') { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'proxy - need a id to setlogs' }, json_encode => 1 - ); + }); return undef; } if ($synctime_nodes->{ $options{data}->{data}->{id} }->{in_progress} == 0) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'proxy - skip setlogs response. Maybe too much time to get response. Retry' }, json_encode => 1 - ); + }); return undef; } @@ -632,14 +638,14 @@ sub setlogs { $options{logger}->writeLogDebug("[proxy] wrong ctime for '$options{data}->{data}->{id}'"); next; } - $status = gorgone::standard::library::add_history( + $status = gorgone::standard::library::add_history({ dbh => $options{dbh}, etime => $_->{etime}, code => $_->{code}, token => $_->{token}, instant => $_->{instant}, data => $_->{data} - ); + }); last if ($status == -1); $ctime_recent = $_->{ctime} if ($ctime_recent < $_->{ctime}); } @@ -648,7 +654,7 @@ sub setlogs { return -1 if ($status == -1); $options{dbh}->transaction_mode(0); - $synctime_nodes->{$options{data}->{data}->{id}}->{ctime} = $ctime_recent if ($ctime_recent != 0); + $synctime_nodes->{ $options{data}->{data}->{id} }->{ctime} = $ctime_recent if ($ctime_recent != 0); } else { $options{dbh}->rollback(); $options{dbh}->transaction_mode(0); @@ -656,7 +662,7 @@ sub setlogs { } # We try to send it to parents - foreach (keys %{$parent_ping}) { + foreach (keys %$parent_ping) { gorgone::class::core::send_message_parent( router_type => $parent_ping->{$_}->{router_type}, identity => $_, @@ -683,11 +689,11 @@ sub ping_send { $constatus_ping->{$id}->{next_ping} = $current_time + $ping_interval; if ($register_nodes->{$id}->{type} eq 'push_zmq' || $register_nodes->{$id}->{type} eq 'push_ssh') { $constatus_ping->{$id}->{in_progress_ping} = 1; - routing(action => 'PING', target => $id, data => '{}', gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger}); - } elsif ($register_nodes->{$id}->{type} =~ /^(?:pull|wss)$/) { + routing(action => 'PING', target => $id, frame => gorgone::class::frame->new(data => {}), gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger}); + } elsif ($register_nodes->{$id}->{type} =~ /^(?:pull|wss|pullwss)$/) { $constatus_ping->{$id}->{in_progress_ping} = 1; $constatus_ping->{$id}->{in_progress_ping_pull} = time(); - routing(action => 'PING', target => $id, data => '{}', gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger}); + routing(action => 'PING', target => $id, frame => gorgone::class::frame->new(data => {}), gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger}); } } } @@ -707,9 +713,9 @@ sub full_sync_history { foreach my $id (keys %{$register_nodes}) { if ($register_nodes->{$id}->{type} eq 'push_zmq') { - routing(action => 'GETLOG', target => $id, data => '{}', gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger}); - } elsif ($register_nodes->{$id}->{type} =~ /^(?:pull|wss)$/) { - routing(action => 'GETLOG', target => $id, data => '{}', gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger}); + routing(action => 'GETLOG', target => $id, frame => gorgone::class::frame->new(data => {}), gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger}); + } elsif ($register_nodes->{$id}->{type} =~ /^(?:pull|wss|pullwss)$/) { + routing(action => 'GETLOG', target => $id, frame => gorgone::class::frame->new(data => {}), gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger}); } } } @@ -720,10 +726,10 @@ sub update_sync_time { # Nothing to update (no insert before) return 0 if ($options{ctime} == 0); - my ($status) = $options{dbh}->query( - "REPLACE INTO gorgone_synchistory (`id`, `ctime`) VALUES (" . - $options{dbh}->quote($options{id}) . ', ' . - $options{dbh}->quote($options{ctime}) . ')' + my ($status) = $options{dbh}->query({ + query => "REPLACE INTO gorgone_synchistory (`id`, `ctime`) VALUES (?, ?)", + bind_values => [$options{id}, $options{ctime}] + } ); return $status; } @@ -731,7 +737,7 @@ sub update_sync_time { sub get_sync_time { my (%options) = @_; - my ($status, $sth) = $options{dbh}->query("SELECT * FROM gorgone_synchistory WHERE id = '" . $options{node_id} . "'"); + my ($status, $sth) = $options{dbh}->query({ query => "SELECT * FROM gorgone_synchistory WHERE id = '" . $options{node_id} . "'" }); if ($status == -1) { $synctime_nodes->{$options{node_id}}->{synctime_error} = -1; return -1; @@ -834,20 +840,19 @@ sub pull_request { my $message = gorgone::standard::library::build_protocol( action => $options{action}, - data => $options{data}, + raw_data_ref => $options{raw_data_ref}, token => $options{token}, - target => $options{target}, - json_encode => 1 + target => $options{target} ); if (!defined($register_nodes->{ $options{target_parent} }->{identity})) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => "proxy - node '" . $options{target_parent} . "' had never been connected" }, json_encode => 1 - ); + }); return undef; } @@ -856,13 +861,13 @@ sub pull_request { identity => $identity ); if ($rv == 0) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => "proxy - node '" . $options{target_parent} . "' had never been connected" }, json_encode => 1 - ); + }); return undef; } @@ -885,11 +890,11 @@ sub unregister_nodes { return if (!defined($options{data}->{nodes})); foreach my $node (@{$options{data}->{nodes}}) { - if (defined($register_nodes->{ $node->{id} }) && $register_nodes->{ $node->{id} }->{type} !~ /^(?:pull|wss)$/) { + if (defined($register_nodes->{ $node->{id} }) && $register_nodes->{ $node->{id} }->{type} !~ /^(?:pull|wss|pullwss)$/) { routing( action => 'PROXYDELNODE', target => $node->{id}, - data => JSON::XS->new->encode($node), + frame => gorgone::class::frame->new(data => $node), gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger} @@ -899,7 +904,7 @@ sub unregister_nodes { my $prevail = 0; $prevail = 1 if (defined($prevails->{ $node->{id} })); - if (defined($register_nodes->{ $node->{id} }) && $register_nodes->{ $node->{id} }->{type} =~ /^(?:pull|wss)$/ && $prevail == 1) { + if (defined($register_nodes->{ $node->{id} }) && $register_nodes->{ $node->{id} }->{type} =~ /^(?:pull|wss|pullwss)$/ && $prevail == 1) { $register_nodes->{ $node->{id} }->{identity} = undef; } @@ -982,12 +987,15 @@ sub register_nodes { if (defined($register_nodes->{ $node->{id} })) { $new_node = 0; - unregister_nodes( - data => { nodes => [ { id => $node->{id} } ] }, - gorgone => $options{gorgone}, - dbh => $options{dbh}, - logger => $options{logger} - ) if ($register_nodes->{ $node->{id} }->{type} !~ /^(?:pull|wss)$/ && $node->{type} =~ /^(?:pull|wss)$/); + if ($register_nodes->{ $node->{id} }->{type} !~ /^(?:pull|wss|pullwss)$/ && $node->{type} =~ /^(?:pull|wss|pullwss)$/) { + unregister_nodes( + data => { nodes => [ { id => $node->{id} } ] }, + gorgone => $options{gorgone}, + dbh => $options{dbh}, + logger => $options{logger} + ); + $new_node = 1; + } } if ($prevail == 0) { @@ -1012,7 +1020,7 @@ sub register_nodes { } # we update identity in all cases (already created or not) - if ($node->{type} =~ /^(?:pull|wss)$/ && defined($node->{identity})) { + if ($node->{type} =~ /^(?:pull|wss|pullwss)$/ && defined($node->{identity})) { $register_nodes->{ $node->{id} }->{identity} = $node->{identity}; $last_pong->{ $node->{id} } = time() if (defined($last_pong->{ $node->{id} })); } @@ -1030,12 +1038,12 @@ sub register_nodes { get_sync_time(node_id => $node->{id}, dbh => $options{dbh}); } - if ($register_nodes->{ $node->{id} }->{type} !~ /^(?:pull|wss)$/) { + if ($register_nodes->{ $node->{id} }->{type} !~ /^(?:pull|wss|pullwss)$/) { if ($prevail == 1) { routing( action => 'PROXYADDNODE', target => $node->{id}, - data => JSON::XS->new->encode($register_nodes->{ $node->{id} }), + frame => gorgone::class::frame->new(data => $register_nodes->{ $node->{id} }), gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger} @@ -1044,7 +1052,7 @@ sub register_nodes { routing( action => 'PROXYADDNODE', target => $node->{id}, - data => JSON::XS->new->encode($node), + frame => gorgone::class::frame->new(data => $node), gorgone => $options{gorgone}, dbh => $options{dbh}, logger => $options{logger} @@ -1071,28 +1079,28 @@ sub register_nodes { sub prepare_remote_copy { my (%options) = @_; - my @actions; + my @actions = (); if (!defined($options{data}->{content}->{source}) || $options{data}->{content}->{source} eq '') { $options{logger}->writeLogError('[proxy] Need source for remote copy'); - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'remote copy failed' }, json_encode => 1 - ); + }); return -1; } if (!defined($options{data}->{content}->{destination}) || $options{data}->{content}->{destination} eq '') { $options{logger}->writeLogError('[proxy] Need destination for remote copy'); - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'remote copy failed' }, json_encode => 1 - ); + }); return -1; } @@ -1112,46 +1120,64 @@ sub prepare_remote_copy { $filename = (defined($options{data}->{content}->{type}) ? $options{data}->{content}->{type} : 'tmp') . '-' . $options{target} . '.tar.gz'; $localsrc = $options{data}->{content}->{cache_dir} . '/' . $filename; - my $tar_cmd = "tar -czf $localsrc -C '" . $src . "' ."; - $tar_cmd .= " --owner=" . $options{data}->{content}->{owner} if (defined($options{data}->{content}->{owner}) && $options{data}->{content}->{owner} ne ''); - $tar_cmd .= " --group=" . $options{data}->{content}->{group} if (defined($options{data}->{content}->{group}) && $options{data}->{content}->{group} ne ''); - my ($error, $stdout, $exit_code) = gorgone::standard::misc::backtick( - command => $tar_cmd, - timeout => (defined($options{timeout})) ? $options{timeout} : 10, - wait_exit => 1, - redirect_stderr => 1, - ); - if ($error <= -1000) { - $options{logger}->writeLogError("[proxy] Tar failed: $stdout"); - gorgone::standard::library::add_history( + my $tar = Archive::Tar->new(); + unless (chdir($options{data}->{content}->{source})) { + $options{logger}->writeLogError("[proxy] cannot chdir: $!"); + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, - data => { message => 'tar failed' }, + data => { message => "cannot chdir: $!" }, json_encode => 1 - ); + }); return -1; } - if ($exit_code != 0) { - $options{logger}->writeLogError("[proxy] Tar failed ($exit_code): $stdout"); - gorgone::standard::library::add_history( + + my @inventory = (); + File::Find::find ({ wanted => sub { push @inventory, $_ }, no_chdir => 1 }, '.'); + my $owner; + $owner = $options{data}->{content}->{owner} if (defined($options{data}->{content}->{owner}) && $options{data}->{content}->{owner} ne ''); + my $group; + $group = $options{data}->{content}->{group} if (defined($options{data}->{content}->{group}) && $options{data}->{content}->{group} ne ''); + foreach my $file (@inventory) { + next if ($file eq '.'); + $tar->add_files($file); + if (defined($owner) || defined($group)) { + $tar->chown($file, $owner, $group); + } + } + + unless (chdir($options{data}->{content}->{cache_dir})) { + $options{logger}->writeLogError("[proxy] cannot chdir: $!"); + gorgone::standard::library::add_history({ + dbh => $options{dbh}, + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + data => { message => "cannot chdir: $!" }, + json_encode => 1 + }); + return -1; + } + unless ($tar->write($filename, COMPRESS_GZIP)) { + $options{logger}->writeLogError("[proxy] Tar failed: " . $tar->error()); + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'tar failed' }, json_encode => 1 - ); + }); return -1; - }; + } } else { $options{logger}->writeLogError('[proxy] Unknown source for remote copy'); - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'unknown source' }, json_encode => 1 - ); + }); return -1; } @@ -1160,7 +1186,7 @@ sub prepare_remote_copy { my $buffer_size = (defined($config->{buffer_size})) ? $config->{buffer_size} : 500_000; my $buffer; while (my $bytes = sysread(FH, $buffer, $buffer_size)) { - push @actions, { + my $action = JSON::XS->new->encode({ logging => $options{data}->{logging}, content => { status => 'inprogress', @@ -1171,14 +1197,15 @@ sub prepare_remote_copy { }, md5 => undef, destination => $dst, - cache_dir => $options{data}->{content}->{cache_dir}, + cache_dir => $options{data}->{content}->{cache_dir} }, parameters => { no_fork => 1 } - }; + }); + push @actions, \$action; } close FH; - push @actions, { + my $action = JSON::XS->new->encode({ logging => $options{data}->{logging}, content => { status => 'end', @@ -1188,10 +1215,11 @@ sub prepare_remote_copy { destination => $dst, cache_dir => $options{data}->{content}->{cache_dir}, owner => $options{data}->{content}->{owner}, - group => $options{data}->{content}->{group}, + group => $options{data}->{content}->{group} }, parameters => { no_fork => 1 } - }; + }); + push @actions, \$action; return (0, \@actions); } diff --git a/gorgone/gorgone/modules/core/proxy/httpserver.pm b/gorgone/gorgone/modules/core/proxy/httpserver.pm index 90ef57aad8e..d0e1d0478f6 100644 --- a/gorgone/gorgone/modules/core/proxy/httpserver.pm +++ b/gorgone/gorgone/modules/core/proxy/httpserver.pm @@ -27,13 +27,13 @@ use warnings; use gorgone::standard::library; use gorgone::standard::constants qw(:all); use gorgone::standard::misc; -use ZMQ::Constants qw(:all); -use ZMQ::LibZMQ4; use Mojolicious::Lite; use Mojo::Server::Daemon; use IO::Socket::SSL; use IO::Handle; use JSON::XS; +use IO::Poll qw(POLLIN POLLPRI); +use EV; my %handlers = (TERM => {}, HUP => {}); my ($connector); @@ -140,28 +140,46 @@ sub run { } } - # Connect internal $self->{internal_socket} = gorgone::standard::library::connect_com( + context => $self->{zmq_context}, zmq_type => 'ZMQ_DEALER', name => 'gorgone-proxy-httpserver', logger => $self->{logger}, type => $self->get_core_config(name => 'internal_com_type'), path => $self->get_core_config(name => 'internal_com_path') ); - $self->send_internal_action( + $self->send_internal_action({ action => 'PROXYREADY', data => { httpserver => 1 } - ); + }); $self->read_zmq_events(); - my $socket_fd = gorgone::standard::library::zmq_getfd(socket => $self->{internal_socket}); - my $socket = IO::Handle->new_from_fd($socket_fd, 'r'); - Mojo::IOLoop->singleton->reactor->io($socket => sub { - $connector->read_zmq_events(); - }); - Mojo::IOLoop->singleton->reactor->watch($socket, 1, 0); + my $type = ref(Mojo::IOLoop->singleton->reactor); + my $watcher_io; + if ($type eq 'Mojo::Reactor::Poll') { + Mojo::IOLoop->singleton->reactor->{io}{ $self->{internal_socket}->get_fd()} = { + cb => sub { $connector->read_zmq_events(); }, + mode => POLLIN | POLLPRI + }; + } else { + # need EV version 4.32 + $watcher_io = EV::io( + $self->{internal_socket}->get_fd(), + EV::READ, + sub { + $connector->read_zmq_events(); + } + ); + } + + #my $socket_fd = $self->{internal_socket}->get_fd(); + #my $socket = IO::Handle->new_from_fd($socket_fd, 'r'); + #Mojo::IOLoop->singleton->reactor->io($socket => sub { + # $connector->read_zmq_events(); + #}); + #Mojo::IOLoop->singleton->reactor->watch($socket, 1, 0); Mojo::IOLoop->singleton->recurring(60 => sub { $connector->{logger}->writeLogDebug('[proxy] httpserver recurring timeout loop'); @@ -187,7 +205,6 @@ sub run { $daemon->run(); - zmq_close($self->{internal_socket}); exit(0); } @@ -201,25 +218,25 @@ sub read_message_client { my ($rv, $data) = $connector->json_decode(argument => $3); return undef if ($rv == 1); - $connector->send_internal_action( + $connector->send_internal_action({ action => 'PONG', data => $data, token => $token, target => '' - ); + }); $connector->read_zmq_events(); } elsif ($options{data} =~ /^\[(?:REGISTERNODES|UNREGISTERNODES|SYNCLOGS|SETLOGS)\]/) { return undef if ($options{data} !~ /^\[(.+?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)/ms); my ($action, $token, $data) = ($1, $2, $3); - $connector->send_internal_action( + $connector->send_internal_action({ action => $action, data => $data, data_noencode => 1, token => $token, target => '' - ); + }); $connector->read_zmq_events(); } } @@ -286,13 +303,9 @@ sub proxy { sub read_zmq_events { my ($self, %options) = @_; - while (my $events = gorgone::standard::library::zmq_events(socket => $self->{internal_socket})) { - if ($events & ZMQ_POLLIN) { - my $message = $connector->read_message(); - proxy(message => $message); - } else { - last; - } + while ($self->{internal_socket}->has_pollin()) { + my ($message) = $connector->read_message(); + proxy(message => $message); } } diff --git a/gorgone/gorgone/modules/core/pull/class.pm b/gorgone/gorgone/modules/core/pull/class.pm index 7baa1bad95d..93f320910cb 100644 --- a/gorgone/gorgone/modules/core/pull/class.pm +++ b/gorgone/gorgone/modules/core/pull/class.pm @@ -28,9 +28,8 @@ use gorgone::class::db; use gorgone::standard::library; use gorgone::standard::constants qw(:all); use gorgone::class::clientzmq; -use ZMQ::LibZMQ4; -use ZMQ::Constants qw(:all); use JSON::XS; +use EV; my %handlers = (TERM => {}, HUP => {}); my ($connector); @@ -90,7 +89,6 @@ sub exit_process { ); $self->{client}->close(); - zmq_close($self->{internal_socket}); exit(0); } @@ -142,38 +140,54 @@ sub read_message_client { } $connector->{logger}->writeLogDebug("[pull] read message from external: $options{data}"); - $connector->send_internal_action(message => $options{data}); + $connector->send_internal_action({ message => $options{data} }); } sub event { - while (1) { - my $message = transmit_back(message => $connector->read_message()); + my ($self, %options) = @_; + + while ($self->{internal_socket}->has_pollin()) { + my ($message) = $self->read_message(); + $message = transmit_back(message => $message); last if (!defined($message)); # Only send back SETLOGS and PONG - $connector->{logger}->writeLogDebug("[pull] read message from internal: $message"); - $connector->{client}->send_message(message => $message); + $self->{logger}->writeLogDebug("[pull] read message from internal: $message"); + $self->{client}->send_message(message => $message); } } +sub periodic_exec { + my ($self, %options) = @_; + + if ($self->{stop} == 1) { + $self->exit_process(); + } + + $self->ping(); +} + sub run { my ($self, %options) = @_; # Connect internal $self->{internal_socket} = gorgone::standard::library::connect_com( + context => $self->{zmq_context}, zmq_type => 'ZMQ_DEALER', name => 'gorgone-pull', logger => $self->{logger}, type => $self->get_core_config(name => 'internal_com_type'), path => $self->get_core_config(name => 'internal_com_path') ); - $self->send_internal_action( + $self->send_internal_action({ action => 'PULLREADY', data => {} - ); + }); $self->{client} = gorgone::class::clientzmq->new( - identity => 'gorgone-' . $self->get_core_config(name => 'id'), + context => $self->{zmq_context}, + core_loop => $self->{loop}, + identity => 'gorgone-' . $self->get_core_config(name => 'id'), cipher => $self->{config}->{cipher}, vector => $self->{config}->{vector}, client_pubkey => @@ -197,23 +211,11 @@ sub run { json_encode => 1 ); - $self->{poll} = [ - { - socket => $self->{internal_socket}, - events => ZMQ_POLLIN, - callback => \&event - }, - $self->{client}->get_poll() - ]; - - while (1) { - my $rv = scalar(zmq_poll($self->{poll}, 5000)); - if ($rv == 0 && $self->{stop} == 1) { - $self->exit_process(); - } + $self->periodic_exec(); - $self->ping(); - } + my $watcher_timer = $self->{loop}->timer(5, 5, sub { $connector->periodic_exec() }); + my $watcher_io = $self->{loop}->io($self->{internal_socket}->get_fd(), EV::READ, sub { $connector->event() } ); + $self->{loop}->run(); } 1; diff --git a/gorgone/gorgone/modules/core/pull/hooks.pm b/gorgone/gorgone/modules/core/pull/hooks.pm index ad31498e149..eb628261a92 100644 --- a/gorgone/gorgone/modules/core/pull/hooks.pm +++ b/gorgone/gorgone/modules/core/pull/hooks.pm @@ -60,20 +60,20 @@ sub routing { } if (gorgone::class::core::waiting_ready(ready => \$pull->{ready}) == 0) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'gorgone-pull: still no ready' }, json_encode => 1 - ); + }); return undef; } $options{gorgone}->send_internal_message( identity => 'gorgone-pull', action => $options{action}, - data => $options{data}, + raw_data_ref => $options{frame}->getRawData(), token => $options{token} ); } diff --git a/gorgone/gorgone/modules/core/pullwss/class.pm b/gorgone/gorgone/modules/core/pullwss/class.pm index 793aee080a6..868704bd6f2 100644 --- a/gorgone/gorgone/modules/core/pullwss/class.pm +++ b/gorgone/gorgone/modules/core/pullwss/class.pm @@ -27,12 +27,11 @@ use warnings; use gorgone::standard::library; use gorgone::standard::constants qw(:all); use gorgone::standard::misc; -use ZMQ::Constants qw(:all); -use ZMQ::LibZMQ4; use Mojo::UserAgent; use IO::Socket::SSL; use IO::Handle; use JSON::XS; +use EV; my %handlers = (TERM => {}, HUP => {}); my ($connector); @@ -175,7 +174,7 @@ sub wss_connect { if ($msg =~ /^\[.*\]/) { $connector->{logger}->writeLogDebug('[pullwss] websocket message: ' . $msg); - $connector->send_internal_action(message => $msg); + $connector->send_internal_action({message => $msg}); $self->read_zmq_events(); } else { $connector->{logger}->writeLogInfo('[pullwss] websocket message: ' . $msg); @@ -195,18 +194,18 @@ sub wss_connect { sub run { my ($self, %options) = @_; - # Connect internal $self->{internal_socket} = gorgone::standard::library::connect_com( + context => $self->{zmq_context}, zmq_type => 'ZMQ_DEALER', name => 'gorgone-pullwss', logger => $self->{logger}, type => $self->get_core_config(name => 'internal_com_type'), path => $self->get_core_config(name => 'internal_com_path') ); - $self->send_internal_action( + $self->send_internal_action({ action => 'PULLWSSREADY', data => {} - ); + }); $self->read_zmq_events(); $self->wss_connect(); @@ -226,7 +225,6 @@ sub run { Mojo::IOLoop->start() unless (Mojo::IOLoop->is_running); - zmq_close($self->{internal_socket}); exit(0); } @@ -257,17 +255,14 @@ sub transmit_back { sub read_zmq_events { my ($self, %options) = @_; - while (my $events = gorgone::standard::library::zmq_events(socket => $self->{internal_socket})) { - if ($events & ZMQ_POLLIN) { - my $message = transmit_back(message => $connector->read_message()); - next if (!defined($message)); + while ($self->{internal_socket}->has_pollin()) { + my ($message) = $connector->read_message(); + $message = transmit_back(message => $message); + next if (!defined($message)); - # Only send back SETLOGS and PONG - $connector->{logger}->writeLogDebug("[pullwss] read message from internal: $message"); - $connector->send_message(message => $message); - } else { - last; - } + # Only send back SETLOGS and PONG + $connector->{logger}->writeLogDebug("[pullwss] read message from internal: $message"); + $connector->send_message(message => $message); } } diff --git a/gorgone/gorgone/modules/core/pullwss/hooks.pm b/gorgone/gorgone/modules/core/pullwss/hooks.pm index 07770466883..62199d5815b 100644 --- a/gorgone/gorgone/modules/core/pullwss/hooks.pm +++ b/gorgone/gorgone/modules/core/pullwss/hooks.pm @@ -75,20 +75,20 @@ sub routing { } if (gorgone::class::core::waiting_ready(ready => \$pullwss->{ready}) == 0) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'gorgone-pullwss: still no ready' }, json_encode => 1 - ); + }); return undef; } $options{gorgone}->send_internal_message( identity => 'gorgone-pullwss', action => $options{action}, - data => $options{data}, + raw_data_ref => $options{frame}->getRawData(), token => $options{token} ); } @@ -139,7 +139,11 @@ sub check { return $count; } -sub broadcast {} +sub broadcast { + my (%options) = @_; + + routing(%options); +} # Specific functions sub create_child { diff --git a/gorgone/gorgone/modules/core/register/class.pm b/gorgone/gorgone/modules/core/register/class.pm index 97afaefd44a..8adab31c01a 100644 --- a/gorgone/gorgone/modules/core/register/class.pm +++ b/gorgone/gorgone/modules/core/register/class.pm @@ -26,9 +26,8 @@ use strict; use warnings; use gorgone::standard::library; use gorgone::standard::constants qw(:all); -use ZMQ::LibZMQ4; -use ZMQ::Constants qw(:all); use JSON::XS; +use EV; my %handlers = (TERM => {}, HUP => {}); my ($connector); @@ -112,19 +111,19 @@ sub action_registerresync { } } - $self->send_internal_action( + $self->send_internal_action({ action => 'REGISTERNODES', data => { nodes => $register_nodes } - ) if (scalar(@$register_nodes) > 0); + }) if (scalar(@$register_nodes) > 0); - $self->send_internal_action( + $self->send_internal_action({ action => 'UNREGISTERNODES', data => { nodes => $unregister_nodes } - ) if (scalar(@$unregister_nodes) > 0); + }) if (scalar(@$unregister_nodes) > 0); $self->{logger}->writeLogDebug("[register] Finish resync"); $self->send_log( @@ -137,22 +136,10 @@ sub action_registerresync { return 0; } -sub event { - while (1) { - my $message = $connector->read_message(); - last if (!defined($message)); - - $connector->{logger}->writeLogDebug("[register] Event: $message"); - if ($message =~ /^\[(.*?)\]/) { - if ((my $method = $connector->can('action_' . lc($1)))) { - $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; - my ($action, $token) = ($1, $2); - my ($rv, $data) = $connector->json_decode(argument => $3, token => $token); - next if ($rv); - - $method->($connector, token => $token, data => $data); - } - } +sub periodic_exec { + if ($connector->{stop} == 1) { + $connector->{logger}->writeLogInfo("[register] $$ has quit"); + exit(0); } } @@ -160,35 +147,24 @@ sub run { my ($self, %options) = @_; # Connect internal - $connector->{internal_socket} = gorgone::standard::library::connect_com( + $self->{internal_socket} = gorgone::standard::library::connect_com( + context => $self->{zmq_context}, zmq_type => 'ZMQ_DEALER', name => 'gorgone-register', logger => $self->{logger}, type => $self->get_core_config(name => 'internal_com_type'), path => $self->get_core_config(name => 'internal_com_path') ); - $connector->send_internal_action( + $self->send_internal_action({ action => 'REGISTERREADY', data => {} - ); - $self->{poll} = [ - { - socket => $connector->{internal_socket}, - events => ZMQ_POLLIN, - callback => \&event, - } - ]; + }); $self->action_registerresync(); - while (1) { - # we try to do all we can - my $rev = scalar(zmq_poll($self->{poll}, 5000)); - if ($rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("[register] $$ has quit"); - zmq_close($connector->{internal_socket}); - exit(0); - } - } + + my $watcher_timer = $self->{loop}->timer(5, 5, \&periodic_exec); + my $watcher_io = $self->{loop}->io($self->{internal_socket}->get_fd(), EV::READ, sub { $connector->event() } ); + $self->{loop}->run(); } 1; diff --git a/gorgone/gorgone/modules/core/register/hooks.pm b/gorgone/gorgone/modules/core/register/hooks.pm index b5b79b90d5f..82d49e26571 100644 --- a/gorgone/gorgone/modules/core/register/hooks.pm +++ b/gorgone/gorgone/modules/core/register/hooks.pm @@ -66,20 +66,20 @@ sub routing { } if (gorgone::class::core::waiting_ready(ready => \$register->{ready}) == 0) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'gorgoneregister: still no ready' }, json_encode => 1 - ); + }); return undef; } $options{gorgone}->send_internal_message( identity => 'gorgone-register', action => $options{action}, - data => $options{data}, + raw_data_ref => $options{frame}->getRawData(), token => $options{token} ); } diff --git a/gorgone/gorgone/modules/plugins/newtest/class.pm b/gorgone/gorgone/modules/plugins/newtest/class.pm index 1fcef6e2ce4..2b45bbf5fe4 100644 --- a/gorgone/gorgone/modules/plugins/newtest/class.pm +++ b/gorgone/gorgone/modules/plugins/newtest/class.pm @@ -28,14 +28,13 @@ use gorgone::standard::misc; use gorgone::standard::library; use gorgone::standard::constants qw(:all); use gorgone::class::sqlquery; -use ZMQ::LibZMQ4; -use ZMQ::Constants qw(:all); use MIME::Base64; use JSON::XS; use Data::Dumper; use gorgone::modules::plugins::newtest::libs::stubs::ManagementConsoleService; use gorgone::modules::plugins::newtest::libs::stubs::errors; use Date::Parse; +use EV; my %handlers = (TERM => {}, HUP => {}); my ($connector); @@ -177,7 +176,8 @@ sub get_poller_id { my ($self, %options) = @_; my ($status, $datas) = $self->{class_object_centreon}->custom_execute( - request => 'SELECT id FROM nagios_server WHERE name = ' . $self->{class_object_centreon}->quote(value => $self->{poller_name}), + request => 'SELECT id FROM nagios_server WHERE name = ?', + bind_values => [$self->{poller_name}], mode => 2 ); if ($status == -1) { @@ -197,17 +197,18 @@ sub get_poller_id { sub get_centreondb_cache { my ($self, %options) = @_; - my $request = ' + my $request = " SELECT host.host_name, service.service_description FROM host LEFT JOIN (host_service_relation, service) ON (host_service_relation.host_host_id = host.host_id AND service.service_id = host_service_relation.service_service_id AND - service.service_description LIKE ' . $self->{class_object_centreon}->quote(value => $self->{service_prefix}) . ') - WHERE host_name LIKE ' . $self->{class_object_centreon}->quote(value => $self->{host_prefix}) . " AND host_register = '1'"; + service.service_description LIKE ?) + WHERE host_name LIKE ? AND host_register = '1'"; $request =~ s/%s/%/g; my ($status, $datas) = $self->{class_object_centreon}->custom_execute( - request => $request, + request => $request, + bind_values => [$self->{service_prefix}, $self->{host_prefix}], mode => 2 ); if ($status == -1) { @@ -229,11 +230,12 @@ sub get_centstoragedb_cache { my ($self, %options) = @_; my $request = 'SELECT hosts.name, services.description, services.last_check - FROM hosts LEFT JOIN services ON (services.host_id = hosts.host_id AND services.description LIKE ' . $self->{class_object_centstorage}->quote(value => $self->{service_prefix}) . ') - WHERE name like ' . $self->{class_object_centstorage}->quote(value => $self->{host_prefix}); + FROM hosts LEFT JOIN services ON (services.host_id = hosts.host_id AND services.description LIKE ? + WHERE name like ?'; $request =~ s/%s/%/g; my ($status, $datas) = $self->{class_object_centstorage}->custom_execute( - request => $request, + request => $request, + bind_values => [$self->{service_prefix}, $self->{host_prefix}], mode => 2 ); if ($status == -1) { @@ -588,7 +590,7 @@ sub action_newtestresync { sub event { while (1) { - my $message = $connector->read_message(); + my ($message) = $connector->read_message(); last if (!defined($message)); $connector->{logger}->writeLogDebug("gorgone-newtest: class: $message"); @@ -603,6 +605,18 @@ sub event { } } +sub periodic_exec { + if ($connector->{stop} == 1) { + $connector->{logger}->writeLogInfo("[newtest] $$ has quit"); + exit(0); + } + + if (time() - $connector->{resync_time} > $connector->{last_resync_time}) { + $connector->{last_resync_time} = time(); + $connector->action_newtestresync(); + } +} + sub run { my ($self, %options) = @_; @@ -627,39 +641,22 @@ sub run { $SOAP::Constants::PREFIX_ENV = 'SOAP-ENV'; $self->{instance} = gorgone::modules::plugins::newtest::libs::stubs::ManagementConsoleService->new(); - # Connect internal - $connector->{internal_socket} = gorgone::standard::library::connect_com( + $self->{internal_socket} = gorgone::standard::library::connect_com( + context => $self->{zmq_context}, zmq_type => 'ZMQ_DEALER', name => 'gorgone-newtest-' . $self->{container_id}, logger => $self->{logger}, type => $self->get_core_config(name => 'internal_com_type'), path => $self->get_core_config(name => 'internal_com_path') ); - $connector->send_internal_action( + $self->send_internal_action({ action => 'NEWTESTREADY', data => { container_id => $self->{container_id} } - ); - $self->{poll} = [ - { - socket => $connector->{internal_socket}, - events => ZMQ_POLLIN, - callback => \&event, - } - ]; - while (1) { - my $rev = scalar(zmq_poll($self->{poll}, 5000)); - if ($rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("[newtest] $$ has quit"); - zmq_close($connector->{internal_socket}); - zmq_close($self->{socket_log}) if (defined($self->{socket_log})); - exit(0); - } + }); - if (time() - $self->{resync_time} > $self->{last_resync_time}) { - $self->{last_resync_time} = time(); - $self->action_newtestresync(); - } - } + my $watcher_timer = $self->{loop}->timer(5, 5, \&periodic_exec); + my $watcher_io = $self->{loop}->io($self->{internal_socket}->get_fd(), EV::READ, sub { $connector->event() } ); + $self->{loop}->run(); } 1; diff --git a/gorgone/gorgone/modules/plugins/newtest/hooks.pm b/gorgone/gorgone/modules/plugins/newtest/hooks.pm index b6ca486a702..79ce99b4583 100644 --- a/gorgone/gorgone/modules/plugins/newtest/hooks.pm +++ b/gorgone/gorgone/modules/plugins/newtest/hooks.pm @@ -72,13 +72,13 @@ sub routing { }; if ($@) { $options{logger}->writeLogError("[newtest] Cannot decode json data: $@"); - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'gorgone-newtest: cannot decode json' }, json_encode => 1 - ); + }); return undef; } @@ -88,24 +88,24 @@ sub routing { } if (!defined($data->{container_id}) || !defined($last_containers->{$data->{container_id}})) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'gorgone-newtest: need a valid container id' }, json_encode => 1 - ); + }); return undef; } if (gorgone::class::core::waiting_ready(ready => \$containers->{$data->{container_id}}->{ready}) == 0) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'gorgone-newtest: still no ready' }, json_encode => 1 - ); + }); return undef; } diff --git a/gorgone/gorgone/modules/plugins/scom/class.pm b/gorgone/gorgone/modules/plugins/scom/class.pm index c59fe3eac6e..96fd1af1398 100644 --- a/gorgone/gorgone/modules/plugins/scom/class.pm +++ b/gorgone/gorgone/modules/plugins/scom/class.pm @@ -28,11 +28,9 @@ use gorgone::standard::library; use gorgone::standard::constants qw(:all); use gorgone::class::sqlquery; use gorgone::class::http::http; -use ZMQ::LibZMQ4; -use ZMQ::Constants qw(:all); use MIME::Base64; use JSON::XS; -use Data::Dumper; +use EV; my %handlers = (TERM => {}, HUP => {}); my ($connector); @@ -257,8 +255,6 @@ sub get_realtime_scom_alerts_1801 { return 1 if ($self->http_check_error(status => $status, method => 'data/alert') == 1); - print Data::Dumper::Dumper($response); - return 0; } @@ -474,20 +470,15 @@ sub action_scomresync { return 0; } -sub event { - while (1) { - my $message = $connector->read_message(); - last if (!defined($message)); - - $connector->{logger}->writeLogDebug("[scom] Event: $message"); - if ($message =~ /^\[(.*?)\]/) { - if ((my $method = $connector->can('action_' . lc($1)))) { - $message =~ /^\[(.*?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)$/m; - my ($action, $token) = ($1, $2); - my $data = JSON::XS->new->decode($3); - $method->($connector, token => $token, data => $data); - } - } +sub periodic_exec { + if ($connector->{stop} == 1) { + $connector->{logger}->writeLogInfo("[scom] $$ has quit"); + exit(0); + } + + if (time() - $self->{resync_time} > $connector->{last_resync_time}) { + $connector->{last_resync_time} = time(); + $connector->action_scomresync(); } } @@ -506,38 +497,22 @@ sub run { $self->{class_object} = gorgone::class::sqlquery->new(logger => $self->{logger}, db_centreon => $self->{db_centstorage}); $self->{http} = gorgone::class::http::http->new(logger => $self->{logger}); - # Connect internal - $connector->{internal_socket} = gorgone::standard::library::connect_com( + $self->{internal_socket} = gorgone::standard::library::connect_com( + context => $self->{zmq_context}, zmq_type => 'ZMQ_DEALER', name => 'gorgone-scom-' . $self->{container_id}, logger => $self->{logger}, type => $self->get_core_config(name => 'internal_com_type'), path => $self->get_core_config(name => 'internal_com_path') ); - $connector->send_internal_action( + $self->send_internal_action({ action => 'SCOMREADY', data => { container_id => $self->{container_id} } - ); - $self->{poll} = [ - { - socket => $connector->{internal_socket}, - events => ZMQ_POLLIN, - callback => \&event, - } - ]; - while (1) { - my $rev = scalar(zmq_poll($self->{poll}, 5000)); - if ($rev == 0 && $self->{stop} == 1) { - $self->{logger}->writeLogInfo("[scom] $$ has quit"); - zmq_close($connector->{internal_socket}); - exit(0); - } + }); - if (time() - $self->{resync_time} > $self->{last_resync_time}) { - $self->{last_resync_time} = time(); - $self->action_scomresync(); - } - } + my $watcher_timer = $self->{loop}->timer(5, 2, \&periodic_exec); + my $watcher_io = $self->{loop}->io($connector->{internal_socket}->get_fd(), EV::READ, sub { $connector->event() } ); + $self->{loop}->run(); } 1; diff --git a/gorgone/gorgone/modules/plugins/scom/hooks.pm b/gorgone/gorgone/modules/plugins/scom/hooks.pm index a8b830677f4..4878d011b13 100644 --- a/gorgone/gorgone/modules/plugins/scom/hooks.pm +++ b/gorgone/gorgone/modules/plugins/scom/hooks.pm @@ -71,13 +71,13 @@ sub routing { }; if ($@) { $options{logger}->writeLogError("[scom] Cannot decode json data: $@"); - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'gorgone-scom: cannot decode json' }, json_encode => 1 - ); + }); return undef; } @@ -87,24 +87,24 @@ sub routing { } if (!defined($data->{container_id}) || !defined($last_containers->{$data->{container_id}})) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'gorgone-scom: need a valid container id' }, json_encode => 1 - ); + }); return undef; } if (gorgone::class::core::waiting_ready(ready => \$containers->{$data->{container_id}}->{ready}) == 0) { - gorgone::standard::library::add_history( + gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'gorgone-scom: still no ready' }, json_encode => 1 - ); + }); return undef; } diff --git a/gorgone/gorgone/standard/api.pm b/gorgone/gorgone/standard/api.pm index 651aaa58b0a..79c1499016d 100644 --- a/gorgone/gorgone/standard/api.pm +++ b/gorgone/gorgone/standard/api.pm @@ -23,8 +23,6 @@ package gorgone::standard::api; use strict; use warnings; use gorgone::standard::library; -use ZMQ::LibZMQ4; -use ZMQ::Constants qw(:all); use Time::HiRes; use JSON::XS; @@ -33,6 +31,10 @@ my $socket; my $results = {}; my $action_token; +sub set_module { + $module = $_[0]; +} + sub root { my (%options) = @_; @@ -89,19 +91,23 @@ sub root { return $response; } +sub stop_ev { + $module->{loop}->break(); +} + sub call_action { my (%options) = @_; $action_token = gorgone::standard::library::generate_token() if (!defined($options{token})); - $options{module}->send_internal_action( + $options{module}->send_internal_action({ socket => $socket, action => $options{action}, target => $options{target}, token => $action_token, data => $options{data}, json_encode => 1 - ); + }); my $response = '{"token":"' . $action_token . '"}'; if (defined($options{log_wait}) && $options{log_wait} ne '') { @@ -121,14 +127,6 @@ sub call_action { sub call_internal { my (%options) = @_; - my $poll = [ - { - socket => $socket, - events => ZMQ_POLLIN, - callback => \&event - } - ]; - $action_token = gorgone::standard::library::generate_token(); if (defined($options{target}) && $options{target} ne '') { return call_action( @@ -143,21 +141,16 @@ sub call_internal { ); } - $options{module}->send_internal_action( + $options{module}->send_internal_action({ socket => $socket, action => $options{action}, token => $action_token, data => $options{data}, json_encode => 1 - ); - - my $timeout = 5000; - while ($timeout > 100) { - my $t1 = Time::HiRes::time(); - my $rev = zmq_poll($poll, $timeout); - last if (defined($results->{ $action_token })); - $timeout -= ((Time::HiRes::time() - $t1) * 1000); - } + }); + + my $watcher_timer = $options{module}->{loop}->timer(5, 0, \&stop_ev); + $options{module}->{loop}->run(); my $response = '{"error":"no_result", "message":"No result found for action \'' . $options{action} . '\'"}'; if (defined($results->{$action_token}->{data})) { @@ -187,28 +180,20 @@ sub call_internal { sub get_log { my (%options) = @_; - my $poll = [ - { - socket => $socket, - events => ZMQ_POLLIN, - callback => \&event - } - ]; - if (defined($options{target}) && $options{target} ne '') { - $options{module}->send_internal_action( + $options{module}->send_internal_action({ socket => $socket, target => $options{target}, action => 'GETLOG', json_encode => 1 - ); + }); my $sync_wait = (defined($options{sync_wait}) && $options{sync_wait} ne '') ? $options{sync_wait} : 10000; Time::HiRes::usleep($sync_wait); } my $token_log = $options{token} . '-log'; - $options{module}->send_internal_action( + $options{module}->send_internal_action({ socket => $socket, action => 'GETLOG', token => $token_log, @@ -217,15 +202,10 @@ sub get_log { %{$options{parameters}} }, json_encode => 1 - ); - - my $timeout = 5000; - while ($timeout > 100) { - my $t1 = Time::HiRes::time(); - my $rev = zmq_poll($poll, $timeout); - last if (defined($results->{ $token_log })); - $timeout -= ($t1 - Time::HiRes::time()); - } + }); + + my $watcher_timer = $options{module}->{loop}->timer(5, 0, \&stop_ev); + $options{module}->{loop}->run(); my $response = '{"error":"no_log","message":"No log found for token","data":[],"token":"' . $options{token} . '"}'; if (defined($results->{ $token_log }) && defined($results->{ $token_log }->{data})) { @@ -258,9 +238,9 @@ sub event { my (%options) = @_; my $httpserver = defined($options{httpserver}) ? $options{httpserver} : $module; - while (1) { - my $message = $httpserver->read_message(); - last if (!defined($message)); + while ($httpserver->{internal_socket}->has_pollin()) { + my ($message) = $httpserver->read_message(); + next if (!defined($message)); if ($message =~ /^\[(.*?)\]\s+\[([a-zA-Z0-9:\-_]*?)\]\s+\[.*?\]\s+(.*)$/m || $message =~ /^\[(.*?)\]\s+\[([a-zA-Z0-9:\-_]*?)\]\s+(.*)$/m) { diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index 622e4897999..e587a70b427 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -23,8 +23,9 @@ package gorgone::standard::library; use strict; use warnings; use gorgone::standard::constants qw(:all); -use ZMQ::LibZMQ4; -use ZMQ::Constants qw(:all); +use ZMQ::FFI qw(ZMQ_DEALER ZMQ_ROUTER ZMQ_ROUTER_HANDOVER ZMQ_IPV6 ZMQ_TCP_KEEPALIVE + ZMQ_CONNECT_TIMEOUT ZMQ_DONTWAIT ZMQ_SNDMORE ZMQ_IDENTITY ZMQ_FD ZMQ_EVENTS + ZMQ_LINGER ZMQ_SNDHWM ZMQ_RCVHWM ZMQ_RECONNECT_IVL); use JSON::XS; use File::Basename; use Crypt::PK::RSA; @@ -35,29 +36,26 @@ use File::Basename; use MIME::Base64; use Errno; use Time::HiRes; +use Try::Tiny; use YAML::XS; +use gorgone::class::frame; $YAML::XS::Boolean = 'JSON::PP'; $YAML::XS::LoadBlessed = 1; our $listener; my %zmq_type = ('ZMQ_ROUTER' => ZMQ_ROUTER, 'ZMQ_DEALER' => ZMQ_DEALER); -my $ZMQ_CONNECT_TIMEOUT = 79; -my $ZMQ_ROUTER_HANDOVER = 56; -my $ZMQ_IPV6 = 42; -my $ZMQ_TCP_KEEPALIVE = 34; sub read_config { my (%options) = @_; my $config; - eval { + try { $config = YAML::XS::LoadFile($options{config_file}); - }; - if ($@) { + } catch { $options{logger}->writeLogError("[core] Parsing config file error:"); $options{logger}->writeLogError($@); exit(1); - } + }; return $config; } @@ -70,16 +68,15 @@ sub generate_keys { my (%options) = @_; my ($privkey, $pubkey); - eval { + try { my $pkrsa = Crypt::PK::RSA->new(); $pkrsa->generate_key(256, 65537); $pubkey = $pkrsa->export_key_pem('public_x509'); $privkey = $pkrsa->export_key_pem('private'); - }; - if ($@) { - $options{logger}->writeLogError("[core] Cannot generate server keys: $@"); + } catch { + $options{logger}->writeLogError("[core] Cannot generate server keys: $_"); return 0; - } + }; return (1, $privkey, $pubkey); } @@ -104,14 +101,13 @@ sub loadpubkey { } my $pubkey; - eval { + try { $pubkey = Crypt::PK::RSA->new(\$string_key); - }; - if ($@) { - $options{logger}->writeLogError("[core] Cannot load pubkey '$options{pubkey}': $@") if (defined($options{logger})); + } catch { + $options{logger}->writeLogError("[core] Cannot load pubkey '$options{pubkey}': $_") if (defined($options{logger})); exit(1) if ($quit); return 0; - } + }; if ($pubkey->is_private()) { $options{logger}->writeLogError("[core] '$options{pubkey}' is not a public key") if (defined($options{logger})); exit(1) if ($quit); @@ -137,14 +133,13 @@ sub loadprivkey { close FILE; my $privkey; - eval { + try { $privkey = Crypt::PK::RSA->new(\$string_key); - }; - if ($@) { - $options{logger}->writeLogError("[core] Cannot load privkey '$options{privkey}': $@"); + } catch { + $options{logger}->writeLogError("[core] Cannot load privkey '$options{privkey}': $_"); exit(1) if ($quit); return 0; - } + }; if (!$privkey->is_private()) { $options{logger}->writeLogError("[core] '$options{privkey}' is not a private key"); exit(1) if ($quit); @@ -158,50 +153,31 @@ sub zmq_core_pubkey_response { my (%options) = @_; if (defined($options{identity})) { - zmq_sendmsg($options{socket}, pack('H*', $options{identity}), ZMQ_DONTWAIT | ZMQ_SNDMORE); + $options{socket}->send(pack('H*', $options{identity}), ZMQ_DONTWAIT | ZMQ_SNDMORE); } my $client_pubkey = $options{pubkey}->export_key_pem('public'); my $msg = '[PUBKEY] [' . MIME::Base64::encode_base64($client_pubkey, '') . ']'; - zmq_sendmsg($options{socket}, $msg, ZMQ_DONTWAIT); - return 0; -} - -sub zmq_core_key_response { - my (%options) = @_; - - if (defined($options{identity})) { - zmq_sendmsg($options{socket}, pack('H*', $options{identity}), ZMQ_DONTWAIT | ZMQ_SNDMORE); - } - my $crypttext; - eval { - $crypttext = $options{client_pubkey}->encrypt("[KEY] [$options{hostname}] [" . $options{symkey} . "]", 'v1.5'); - }; - if ($@) { - $options{logger}->writeLogError("[core] Encoding issue: " . $@); - return -1; - } - - zmq_sendmsg($options{socket}, MIME::Base64::encode_base64($crypttext, ''), ZMQ_DONTWAIT); + $options{socket}->send($msg, ZMQ_DONTWAIT); return 0; } sub zmq_get_routing_id { my (%options) = @_; - return zmq_getsockopt($options{socket}, ZMQ_IDENTITY); + return $options{socket}->get_identity(); } sub zmq_getfd { my (%options) = @_; - return zmq_getsockopt($options{socket}, ZMQ_FD); + return $options{socket}->get_fd(); } sub zmq_events { my (%options) = @_; - return zmq_getsockopt($options{socket}, ZMQ_EVENTS); + return $options{socket}->get(ZMQ_EVENTS, 'int'); } sub generate_token { @@ -224,12 +200,11 @@ sub client_helo_encrypt { my $ciphertext; my $client_pubkey = $options{client_pubkey}->export_key_pem('public'); - eval { + try { $ciphertext = $options{server_pubkey}->encrypt('HELO', 'v1.5'); + } catch { + return (-1, "Encoding issue: $_"); }; - if ($@) { - return (-1, "Encoding issue: $@"); - } return (0, '[' . $options{identity} . '] [' . MIME::Base64::encode_base64($client_pubkey, '') . '] [' . MIME::Base64::encode_base64($ciphertext, '') . ']'); } @@ -244,13 +219,12 @@ sub is_client_can_connect { } my ($client, $client_pubkey_str, $cipher_text) = ($1, $2, $3); - eval { + try { $plaintext = $options{privkey}->decrypt(MIME::Base64::decode_base64($cipher_text), 'v1.5'); - }; - if ($@) { - $options{logger}->writeLogError("[core] Decoding issue: " . $@); + } catch { + $options{logger}->writeLogError("[core] Decoding issue: $_"); return -1; - } + }; if ($plaintext ne 'HELO') { $options{logger}->writeLogError("[core] Encrypted issue for HELO"); return -1; @@ -258,13 +232,12 @@ sub is_client_can_connect { my ($client_pubkey); $client_pubkey_str = MIME::Base64::decode_base64($client_pubkey_str); - eval { + try { $client_pubkey = Crypt::PK::RSA->new(\$client_pubkey_str); - }; - if ($@) { - $options{logger}->writeLogError("[core] Cannot load client pubkey '$client_pubkey': $@"); + } catch { + $options{logger}->writeLogError("[core] Cannot load client pubkey '$client_pubkey': $_"); return -1; - } + }; my $is_authorized = 0; my $thumbprint = $client_pubkey->export_key_jwk_thumbprint('SHA256'); @@ -293,11 +266,8 @@ sub is_client_can_connect { sub addlistener { my (%options) = @_; - my $data; - eval { - $data = JSON::XS->new->decode($options{data}); - }; - if ($@) { + my $data = $options{frame}->decodeData(); + if (!defined($data)) { return (GORGONE_ACTION_FINISH_KO, { message => 'request not well formatted' }); } @@ -311,6 +281,7 @@ sub addlistener { timeout => $_->{timeout} ); } + return (GORGONE_ACTION_FINISH_OK, { action => 'addlistener', message => 'ok', data => $data }); } @@ -338,11 +309,8 @@ sub information { sub unloadmodule { my (%options) = @_; - my $data; - eval { - $data = JSON::XS->new->decode($options{data}); - }; - if ($@) { + my $data = $options{frame}->decodeData(); + if (!defined($data)) { return (GORGONE_ACTION_FINISH_KO, { message => 'request not well formatted' }); } @@ -363,11 +331,8 @@ sub unloadmodule { sub loadmodule { my (%options) = @_; - my $data; - eval { - $data = JSON::XS->new->decode($options{data}); - }; - if ($@) { + my $data = $options{frame}->decodeData(); + if (!defined($data)) { return (GORGONE_ACTION_FINISH_KO, { message => 'request not well formatted' }); } @@ -379,7 +344,7 @@ sub loadmodule { external_socket => $options{gorgone}->{external_socket}, internal_socket => $options{gorgone}->{internal_socket}, dbh => $options{gorgone}->{db_gorgone}, - api_endpoints => $options{gorgone}->{api_endpoints}, + api_endpoints => $options{gorgone}->{api_endpoints} ); return (GORGONE_ACTION_BEGIN, { action => 'loadmodule', message => "module '$data->{content}->{name}' is loaded" }, 'LOADMODULE'); } @@ -390,11 +355,8 @@ sub loadmodule { sub synclogs { my (%options) = @_; - my $data; - eval { - $data = JSON::XS->new->decode($options{data}); - }; - if ($@) { + my $data = $options{frame}->decodeData(); + if (!defined($data)) { return (GORGONE_ACTION_FINISH_KO, { message => 'request not well formatted' }); } @@ -435,11 +397,8 @@ sub constatus { sub setmodulekey { my (%options) = @_; - my $data; - eval { - $data = JSON::XS->new->decode($options{data}); - }; - if ($@) { + my $data = $options{frame}->decodeData(); + if (!defined($data)) { return (GORGONE_ACTION_FINISH_KO, { message => 'request not well formatted' }); } @@ -465,11 +424,8 @@ sub setcoreid { return (GORGONE_ACTION_FINISH_OK, { action => 'setcoreid', message => 'setcoreid unchanged, use config value' }) } - my $data; - eval { - $data = JSON::XS->new->decode($options{data}); - }; - if ($@) { + my $data = $options{frame}->decodeData(); + if (!defined($data)) { return (GORGONE_ACTION_FINISH_KO, { message => 'request not well formatted' }); } @@ -511,22 +467,19 @@ sub ping { sub putlog { my (%options) = @_; - my $data; - eval { - $data = JSON::XS->new->decode($options{data}); - }; - if ($@) { + my $data = $options{frame}->decodeData(); + if (!defined($data)) { return (GORGONE_ACTION_FINISH_KO, { message => 'request not well formatted' }); } - my $status = add_history( + my $status = add_history({ dbh => $options{gorgone}->{db_gorgone}, etime => $data->{etime}, token => $data->{token}, instant => $data->{instant}, data => json_encode(data => $data->{data}, logger => $options{logger}), code => $data->{code} - ); + }); if ($status == -1) { return (GORGONE_ACTION_FINISH_KO, { message => 'database issue' }); } @@ -536,20 +489,19 @@ sub putlog { sub getlog { my (%options) = @_; - my $data; - eval { - $data = JSON::XS->new->decode($options{data}); - }; - if ($@) { + my $data = $options{frame}->decodeData(); + if (!defined($data)) { return (GORGONE_ACTION_FINISH_KO, { message => 'request not well formatted' }); } my %filters = (); my ($filter, $filter_append) = ('', ''); + my @bind_values = (); foreach ((['id', '>'], ['token', '='], ['ctime', '>'], ['etime', '>'], ['code', '='])) { if (defined($data->{$_->[0]}) && $data->{$_->[0]} ne '') { - $filter .= $filter_append . $_->[0] . ' ' . $_->[1] . ' ' . $options{gorgone}->{db_gorgone}->quote($data->{$_->[0]}); + $filter .= $filter_append . $_->[0] . ' ' . $_->[1] . ' ?'; $filter_append = ' AND '; + push @bind_values, $data->{ $_->[0] }; } } @@ -560,7 +512,7 @@ sub getlog { my $query = "SELECT * FROM gorgone_history WHERE " . $filter; $query .= " ORDER BY id DESC LIMIT " . $data->{limit} if (defined($data->{limit}) && $data->{limit} ne ''); - my ($status, $sth) = $options{gorgone}->{db_gorgone}->query($query); + my ($status, $sth) = $options{gorgone}->{db_gorgone}->query({ query => $query, bind_values => \@bind_values }); if ($status == -1) { return (GORGONE_ACTION_FINISH_KO, { message => 'database issue' }); } @@ -577,11 +529,8 @@ sub getlog { sub kill { my (%options) = @_; - my $data; - eval { - $data = JSON::XS->new->decode($options{data}); - }; - if ($@) { + my $data = $options{frame}->decodeData(); + if (!defined($data)) { return (GORGONE_ACTION_FINISH_KO, { message => 'request not well formatted' }); } @@ -606,34 +555,39 @@ sub kill { sub update_identity_attrs { my (%options) = @_; - my $values = []; + my @fields = (); + my @bind_values = (); foreach ('key', 'oldkey', 'iv', 'oldiv', 'ctime') { next if (!defined($options{$_})); if ($options{$_} eq 'NULL') { - push @$values, "`$_` = NULL"; + push @fields, "`$_` = NULL"; } else { - push @$values, "`$_` = " . $options{dbh}->quote($options{$_}); + push @fields, "`$_` = ?"; + push @bind_values, $options{$_}; } } + push @bind_values, $options{identity}, $options{identity}; + + my ($status, $sth) = $options{dbh}->query({ + query => "UPDATE gorgone_identity SET " . join(', ', @fields) . + " WHERE `identity` = ? AND " . + " `id` = (SELECT `id` FROM gorgone_identity WHERE `identity` = ? ORDER BY `id` DESC LIMIT 1)", + bind_values => \@bind_values + }); - my ($status, $sth) = $options{dbh}->query( - "UPDATE gorgone_identity SET " . - join(', ', @$values) . - " WHERE `identity` = " . $options{dbh}->quote($options{identity}) . " AND " . - " `id` = (SELECT `id` FROM gorgone_identity WHERE `identity` = " . $options{dbh}->quote($options{identity}) . " ORDER BY `id` DESC LIMIT 1)" - ); return $status; } sub update_identity_mtime { my (%options) = @_; - my ($status, $sth) = $options{dbh}->query( - "UPDATE gorgone_identity SET `mtime` = " . $options{dbh}->quote(time()) . - " WHERE `identity` = " . $options{dbh}->quote($options{identity}) . " AND " . - " `id` = (SELECT `id` FROM gorgone_identity WHERE `identity` = " . $options{dbh}->quote($options{identity}) . " ORDER BY `id` DESC LIMIT 1)" - ); + my ($status, $sth) = $options{dbh}->query({ + query => "UPDATE gorgone_identity SET `mtime` = ?" . + " WHERE `identity` = ? AND " . + " `id` = (SELECT `id` FROM gorgone_identity WHERE `identity` = ? ORDER BY `id` DESC LIMIT 1)", + bind_values => [time(), $options{identity}, $options{identity}] + }); return $status; } @@ -641,50 +595,53 @@ sub add_identity { my (%options) = @_; my $time = time(); - my ($status, $sth) = $options{dbh}->query( - "INSERT INTO gorgone_identity (`ctime`, `mtime`, `identity`, `key`, `iv`) VALUES (" . - $options{dbh}->quote($time) . ", " . - $options{dbh}->quote($time) . ", " . - $options{dbh}->quote($options{identity}) . ", " . - $options{dbh}->quote(unpack('H*', $options{key})) . ", " . - $options{dbh}->quote(unpack('H*', $options{iv})) . ")", - ); + my ($status, $sth) = $options{dbh}->query({ + query => "INSERT INTO gorgone_identity (`ctime`, `mtime`, `identity`, `key`, `iv`) VALUES (?, ?, ?, ?, ?)", + bind_values => [$time, $time, $options{identity}, unpack('H*', $options{key}), unpack('H*', $options{iv})] + }); return $status; } sub add_history { - my (%options) = @_; + my ($options) = (shift); - if (defined($options{data}) && defined($options{json_encode})) { - return -1 if (!($options{data} = json_encode(data => $options{data}, logger => $options{logger}))); + if (defined($options->{data}) && defined($options->{json_encode})) { + return -1 if (!($options->{data} = json_encode(data => $options->{data}, logger => $options->{logger}))); } - if (!defined($options{ctime})) { - $options{ctime} = Time::HiRes::time(); + if (!defined($options->{ctime})) { + $options->{ctime} = Time::HiRes::time(); } - if (!defined($options{etime})) { - $options{etime} = time(); + if (!defined($options->{etime})) { + $options->{etime} = time(); } - my @names = (); - my @values = (); + my $fields = ''; + my $placeholder = ''; + my $append = ''; + my @bind_values = (); foreach (('data', 'token', 'ctime', 'etime', 'code', 'instant')) { - if (defined($options{$_})) { - push @names, $_; - push @values, $options{dbh}->quote($options{$_}); + if (defined($options->{$_})) { + $fields .= $append . $_; + $placeholder .= $append . '?'; + $append = ', '; + push @bind_values, $options->{$_}; } } - my ($status, $sth) = $options{dbh}->query( - "INSERT INTO gorgone_history (" . join(',', @names) . ") VALUES (" . - join(',', @values) . ")" - ); + my ($status, $sth) = $options->{dbh}->query({ + query => "INSERT INTO gorgone_history ($fields) VALUES ($placeholder)", + bind_values => \@bind_values + }); - if (defined($options{token}) && $options{token} ne '') { + if (defined($options->{token}) && $options->{token} ne '') { $listener->event_log( - token => $options{token}, - code => $options{code}, - data => $options{data} + { + token => $options->{token}, + code => $options->{code}, + data => \$options->{data} + } ); } + return $status; } @@ -695,35 +652,31 @@ sub add_history { sub json_encode { my (%options) = @_; - my $data; - eval { - $data = JSON::XS->new->encode($options{data}); - }; - if ($@) { + try { + $options{data} = JSON::XS->new->encode($options{data}); + } catch { if (defined($options{logger})) { - $options{logger}->writeLogError("[core] Cannot encode json data: $@"); + $options{logger}->writeLogError("[core] Cannot encode json data: $_"); } return undef; - } + }; - return $data; + return $options{data}; } sub json_decode { my (%options) = @_; - my $data; - eval { - $data = JSON::XS->new->decode($options{data}); - }; - if ($@) { + try { + $options{data} = JSON::XS->new->decode($options{data}); + } catch { if (defined($options{logger})) { - $options{logger}->writeLogError("[$options{module}] Cannot decode json data: $@"); + $options{logger}->writeLogError("[$options{module}] Cannot decode json data: $_"); } return undef; - } + }; - return $data; + return $options{data}; } ####################### @@ -733,63 +686,67 @@ sub json_decode { sub connect_com { my (%options) = @_; - my $context = zmq_init(); - my $socket = zmq_socket($context, $zmq_type{$options{zmq_type}}); + my $socket = $options{context}->socket($zmq_type{$options{zmq_type}}); if (!defined($socket)) { $options{logger}->writeLogError("Can't setup server: $!"); exit(1); } + $socket->die_on_error(0); - zmq_setsockopt($socket, ZMQ_IDENTITY, $options{name}); - zmq_setsockopt($socket, ZMQ_LINGER, defined($options{zmq_linger}) ? $options{zmq_linger} : 0); # 0 we discard - zmq_setsockopt($socket, ZMQ_SNDHWM, defined($options{zmq_sndhwm}) ? $options{zmq_sndhwm} : 0); - zmq_setsockopt($socket, ZMQ_RCVHWM, defined($options{zmq_rcvhwm}) ? $options{zmq_rcvhwm} : 0); - zmq_setsockopt($socket, ZMQ_RECONNECT_IVL, 1000); - ZMQ::LibZMQ4::zmq_setsockopt_int($socket, $ZMQ_CONNECT_TIMEOUT, defined($options{zmq_connect_timeout}) ? $options{zmq_connect_timeout} : 30000); - ZMQ::LibZMQ4::zmq_setsockopt_int($socket, $ZMQ_ROUTER_HANDOVER, defined($options{zmq_router_handover}) ? $options{zmq_router_handover} : 1); + $socket->set_identity($options{name}); + $socket->set(ZMQ_LINGER, 'int', defined($options{zmq_linger}) ? $options{zmq_linger} : 0); # 0 we discard + $socket->set(ZMQ_SNDHWM, 'int', defined($options{zmq_sndhwm}) ? $options{zmq_sndhwm} : 0); + $socket->set(ZMQ_RCVHWM, 'int', defined($options{zmq_rcvhwm}) ? $options{zmq_rcvhwm} : 0); + $socket->set(ZMQ_RECONNECT_IVL, 'int', 1000); + $socket->set(ZMQ_CONNECT_TIMEOUT, 'int', defined($options{zmq_connect_timeout}) ? $options{zmq_connect_timeout} : 30000); + if ($options{zmq_type} eq 'ZMQ_ROUTER') { + $socket->set(ZMQ_ROUTER_HANDOVER, 'int', defined($options{zmq_router_handover}) ? $options{zmq_router_handover} : 1); + } if ($options{type} eq 'tcp') { - ZMQ::LibZMQ4::zmq_setsockopt_int($socket, $ZMQ_IPV6, defined($options{zmq_ipv6}) && $options{zmq_ipv6} =~ /true|1/i ? 1 : 0); + $socket->set(ZMQ_TCP_KEEPALIVE, 'int', defined($options{zmq_tcp_keepalive}) ? $options{zmq_tcp_keepalive} : -1); } - zmq_connect($socket, $options{type} . '://' . $options{path}); + + $socket->connect($options{type} . '://' . $options{path}); return $socket; } sub create_com { my (%options) = @_; - my $context = zmq_init(); - my $socket = zmq_socket($context, $zmq_type{$options{zmq_type}}); + my $socket = $options{context}->socket($zmq_type{$options{zmq_type}}); if (!defined($socket)) { $options{logger}->writeLogError("Can't setup server: $!"); exit(1); } + $socket->die_on_error(0); + + $socket->set_identity($options{name}); + $socket->set_linger(0); + $socket->set(ZMQ_ROUTER_HANDOVER, 'int', defined($options{zmq_router_handover}) ? $options{zmq_router_handover} : 1); - zmq_setsockopt($socket, ZMQ_IDENTITY, $options{name}); - zmq_setsockopt($socket, ZMQ_LINGER, 0); # we discard - ZMQ::LibZMQ4::zmq_setsockopt_int($socket, $ZMQ_ROUTER_HANDOVER, defined($options{zmq_router_handover}) ? $options{zmq_router_handover} : 1); if ($options{type} eq 'tcp') { - ZMQ::LibZMQ4::zmq_setsockopt_int($socket, $ZMQ_IPV6, defined($options{zmq_ipv6}) && $options{zmq_ipv6} =~ /true|1/i ? 1 : 0); - ZMQ::LibZMQ4::zmq_setsockopt_int($socket, $ZMQ_TCP_KEEPALIVE, defined($options{zmq_tcp_keepalive}) ? $options{zmq_tcp_keepalive} : -1); - zmq_bind($socket, 'tcp://' . $options{path}); + $socket->set(ZMQ_IPV6, 'int', defined($options{zmq_ipv6}) && $options{zmq_ipv6} =~ /true|1/i ? 1 : 0); + $socket->set(ZMQ_TCP_KEEPALIVE, 'int', defined($options{zmq_tcp_keepalive}) ? $options{zmq_tcp_keepalive} : -1); + + $socket->bind('tcp://' . $options{path}); } elsif ($options{type} eq 'ipc') { - if (zmq_bind($socket, 'ipc://' . $options{path}) == -1) { + $socket->bind('ipc://' . $options{path}); + if ($socket->has_error) { $options{logger}->writeLogDebug("[core] Cannot bind IPC '$options{path}': $!"); # try create dir $options{logger}->writeLogDebug("[core] Maybe directory not exist. We try to create it!!!"); if (!mkdir(dirname($options{path}))) { $options{logger}->writeLogError("[core] Cannot create IPC file directory '$options{path}'"); - zmq_close($socket); exit(1); } - if (zmq_bind($socket, 'ipc://' . $options{path}) == -1) { - $options{logger}->writeLogError("[core] Cannot bind IPC '$options{path}': $!"); - zmq_close($socket); + $socket->bind('ipc://' . $options{path}); + if ($socket->has_error) { + $options{logger}->writeLogError("[core] Cannot bind IPC '$options{path}': " . $socket->last_strerror); exit(1); } } } else { $options{logger}->writeLogError("[core] ZMQ type '$options{type}' not managed"); - zmq_close($socket); exit(1); } @@ -803,7 +760,9 @@ sub build_protocol { my $action = defined($options{action}) ? $options{action} : ''; my $target = defined($options{target}) ? $options{target} : ''; - if (defined($data)) { + if (defined($options{raw_data_ref})) { + return '[' . $action . '] [' . $token . '] [' . $target . '] ' . ${$options{raw_data_ref}}; + } elsif (defined($data)) { if (defined($options{json_encode})) { $data = json_encode(data => $data, logger => $options{logger}); } @@ -817,56 +776,49 @@ sub build_protocol { sub zmq_dealer_read_message { my (%options) = @_; - my $message = zmq_recvmsg($options{socket}, ZMQ_DONTWAIT); - return undef if (!defined($message)); + my $data = $options{socket}->recv(ZMQ_DONTWAIT); + if ($options{socket}->has_error) { + return 1; + } - my $data = zmq_msg_data($message); - return $data; + if (defined($options{frame})) { + $options{frame}->setFrame(\$data); + return 0; + } + + return (0, $data); } sub zmq_read_message { my (%options) = @_; # Process all parts of the message - my $message = zmq_recvmsg($options{socket}, ZMQ_DONTWAIT); - if (!defined($message)) { - return undef if ($! == Errno::EAGAIN); + my $identity = $options{socket}->recv(ZMQ_DONTWAIT); + if ($options{socket}->has_error()) { + return undef if ($options{socket}->last_errno == Errno::EAGAIN); $options{logger}->writeLogError("[core] zmq_recvmsg error: $!"); return undef; } - my $identity = zmq_msg_data($message); + $identity = defined($identity) ? $identity : 'undef'; if ($identity !~ /^gorgone-/) { $options{logger}->writeLogError("[core] unknown identity: $identity"); return undef; } - $message = zmq_recvmsg($options{socket}, ZMQ_DONTWAIT); - if (!defined($message)) { - return undef if ($! == Errno::EAGAIN); + + my $data = $options{socket}->recv(ZMQ_DONTWAIT); + if ($options{socket}->has_error()) { + return undef if ($options{socket}->last_errno == Errno::EAGAIN); $options{logger}->writeLogError("[core] zmq_recvmsg error: $!"); return undef; } - my $data = zmq_msg_data($message); - return (unpack('H*', $identity), $data); -} + my $frame = gorgone::class::frame->new(); + $frame->setFrame(\$data); -sub zmq_still_read { - my (%options) = @_; - - return zmq_getsockopt($options{socket}, ZMQ_RCVMORE); -} - -sub add_zmq_pollin { - my (%options) = @_; - - push @{$options{poll}}, { - socket => $options{socket}, - events => ZMQ_POLLIN, - callback => $options{callback}, - }; + return (unpack('H*', $identity), $frame); } sub create_schema { @@ -966,7 +918,7 @@ sub create_schema { } ]; foreach (@$schema) { - my ($status, $sth) = $options{gorgone}->{db_gorgone}->query($_); + my ($status, $sth) = $options{gorgone}->{db_gorgone}->query({ query => $_ }); if ($status == -1) { $options{logger}->writeLogError("[core] create schema issue"); exit(1); @@ -1000,9 +952,9 @@ sub init_database { return if (!defined($options{autocreate_schema}) || $options{autocreate_schema} != 1); my $db_version = '1.0'; - my ($status, $sth) = $options{gorgone}->{db_gorgone}->query(q{SELECT `value` FROM gorgone_information WHERE `key` = 'version'}); + my ($status, $sth) = $options{gorgone}->{db_gorgone}->query({ query => q{SELECT `value` FROM gorgone_information WHERE `key` = 'version'} }); if ($status == -1) { - ($status, $sth) = $options{gorgone}->{db_gorgone}->query(q{SELECT 1 FROM gorgone_identity LIMIT 1}); + ($status, $sth) = $options{gorgone}->{db_gorgone}->query({ query => q{SELECT 1 FROM gorgone_identity LIMIT 1} }); if ($status == -1) { create_schema(gorgone => $options{gorgone}, logger => $options{logger}, version => $options{version}); return ; @@ -1042,7 +994,7 @@ sub init_database { } ]; foreach (@$schema) { - my ($status, $sth) = $options{gorgone}->{db_gorgone}->query($_); + my ($status, $sth) = $options{gorgone}->{db_gorgone}->query({ query => $_ }); if ($status == -1) { $options{logger}->writeLogError("[core] update schema issue"); exit(1); @@ -1052,7 +1004,7 @@ sub init_database { } if ($db_version ne $options{version}) { - $options{gorgone}->{db_gorgone}->query("UPDATE gorgone_information SET `value` = '$options{version}' WHERE `key` = 'version'"); + $options{gorgone}->{db_gorgone}->query({ query => "UPDATE gorgone_information SET `value` = '$options{version}' WHERE `key` = 'version'" }); } } diff --git a/gorgone/gorgone/standard/misc.pm b/gorgone/gorgone/standard/misc.pm index 689a90733c8..cbc5342b9d7 100644 --- a/gorgone/gorgone/standard/misc.pm +++ b/gorgone/gorgone/standard/misc.pm @@ -26,6 +26,7 @@ use vars qw($centreon_config); use POSIX ":sys_wait_h"; use File::Path; use File::Basename; +use Try::Tiny; sub reload_db_config { my ($logger, $config_file, $cdb, $csdb) = @_; @@ -79,7 +80,10 @@ sub get_all_options_config { my $save_force = $centreon_db_centreon->force(); $centreon_db_centreon->force(0); - my ($status, $stmt) = $centreon_db_centreon->query("SELECT `key`, `value` FROM options WHERE `key` LIKE " . $centreon_db_centreon->quote($prefix . "_%") . " LIMIT 1"); + my ($status, $stmt) = $centreon_db_centreon->query({ + query => 'SELECT `key`, `value` FROM options WHERE `key` LIKE ? LIMIT 1', + bind_values => [$prefix . '_%'] + }); if ($status == -1) { $centreon_db_centreon->force($save_force); return ; @@ -101,7 +105,10 @@ sub get_option_config { my $save_force = $centreon_db_centreon->force(); $centreon_db_centreon->force(0); - my ($status, $stmt) = $centreon_db_centreon->query("SELECT value FROM options WHERE `key` = " . $centreon_db_centreon->quote($prefix . "_" . $key) . " LIMIT 1"); + my ($status, $stmt) = $centreon_db_centreon->query({ + query => 'SELECT value FROM options WHERE `key` = ? LIMIT 1', + bind_values => [$prefix . '_' . $key] + }); if ($status == -1) { $centreon_db_centreon->force($save_force); return ; @@ -116,8 +123,10 @@ sub get_option_config { sub check_debug { my ($logger, $key, $cdb, $name) = @_; - my $request = "SELECT value FROM options WHERE `key` = " . $cdb->quote($key); - my ($status, $sth) = $cdb->query($request); + my ($status, $sth) = $cdb->query({ + query => 'SELECT `value` FROM options WHERE `key` = ?', + bind_values => [$key] + }); return -1 if ($status == -1); my $data = $sth->fetchrow_hashref(); if (defined($data->{'value'}) && $data->{'value'} == 1) { @@ -164,7 +173,7 @@ sub backtick { } if ($pid) { - eval { + try { local $SIG{ALRM} = sub { die "Timeout by signal ALARM\n"; }; alarm( $arg{timeout} ); while (<KID>) { @@ -173,22 +182,20 @@ sub backtick { } alarm(0); - }; - if ($@) { + } catch { if ($pid != -1) { kill -9, $pid; } alarm(0); return (-1000, "Command too long to execute (timeout)...", -1); - } else { - if ($arg{wait_exit} == 1) { - # We're waiting the exit code - waitpid($pid, 0); - $return_code = ($? >> 8); - } - close KID; + }; + if ($arg{wait_exit} == 1) { + # We're waiting the exit code + waitpid($pid, 0); + $return_code = ($? >> 8); } + close KID; } else { # child # set the child process to be a group leader, so that diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index efd957acc7e..e460acdb731 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -16,8 +16,8 @@ Requires: perl-Libssh-Session >= 0.8 Requires: perl-CryptX Requires: perl(Archive::Tar) Requires: perl(Schedule::Cron) -Requires: perl(ZMQ::LibZMQ4) -Requires: perl(ZMQ::Constants) +Requires: perl(ZMQ::FFI) +Requires: perl(EV) Requires: perl(JSON::XS) Requires: perl(JSON::PP) Requires: perl(XML::Simple) diff --git a/gorgone/packaging/debian/control b/gorgone/packaging/debian/control index 62a5e608c55..c220d64daf0 100644 --- a/gorgone/packaging/debian/control +++ b/gorgone/packaging/debian/control @@ -34,9 +34,8 @@ Depends: libauthen-simple-net-perl, libnet-curl-perl, libssh-session-perl, - libzmq-constants-perl, - libzmq5, - libzmq-libzmq4-perl, + libev-perl, + libzmq-ffi-perl, sudo, ${shlibs:Depends}, ${misc:Depends} diff --git a/gorgone/packaging/packages/Digest-MD5-File-0.08-1.el8.noarch.rpm b/gorgone/packaging/packages/Digest-MD5-File-0.08-1.el8.noarch.rpm deleted file mode 100644 index 65b164ab33e91a9b9d046c57f79c765a81e09333..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14344 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 diff --git a/gorgone/packaging/packages/perl-Clone-0.45-1.el8.x86_64.rpm b/gorgone/packaging/packages/perl-Clone-0.45-1.el8.x86_64.rpm deleted file mode 100644 index b517e846c4069acfe32b08f2938ef1ebc1838b2d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19900 zcmeIZWn5L;+b+Clqy(f<K#;D*VgZX%krL?=5Q&v^2#b`K21Ob~q+3Elq(cPh6cABD zQbHsI$upPo-+Mpr`@HXY&ZqNbj~`=R;~Mvfd)#vr_k@j-%})d%AmBNpUF?Mw?VTLa z!e9}ogs_+h+8&0J{Lcd(i0J=W_(|ys-gzDg2t-{Cq^|=Jm(B%{8t9l3Kw==`0#Fu@ z20$F=|HBDZ03-yW{W)I;5U3Aw1|m?O;5&dgc>oZB`UG15;^e`Am;)mwfq_a(i9;pC z;V`fySOSTVhQg&JAP6)HfkMKi!5C?Av=j`400a3@7)(qI31pxpF;FxbDh9!T;nLzL z7z!)~6GI>*#H27tF>x_4R2(LWK}f>jQsNjfgqV~R8t4!NlDlTRq_%Q@pH$NxfBwGX z43Z24@&<JLH~edXe=YE@1^%_bzZUq{0{>dzUkm(efqyOVuLb_Kz`qvw*8=}q;Ezw_ z?Ck6*V2j`Wf`9l;BtRgtX(0aw5COjiS>JgX6o8~a#08w+1Lr5f`AzW8`RX~x`B!lA zZ|9u$oa6i`7l6og&Nt3E&gp|IzX>4DuQGPdaefwD`;+JV2Y@*F!a4s5AWn|c16Th5 zKwSGc#PMSQ0XZJkIX^k)!sq<-oQwbAgt)eWh$jai1(3wG4Y;cC?9Vx_Z5$6i=YX%} zclqb%oE$*FK6p5L;pzhs#|fCuInFLX`vjH%;^Y?r#L02*0LO9f0mu8!IURt24un7+ zZajqa00Q+1`Of8x00J^XV*mlY2yy-3<T(9s$b2qOIOi<qeE*!|+5y@px&$DueU)>L zD~IE%00Qk0fdRzncl(@Mo^v$-f&Pj9qd!g$TzQ;5aQc{>%L~u>{d0LMfVlR|0L0bb zI_KtpII%Z?fNcp800R1v*a3*s2la=OHk@;u3k;Bxww`m$Ie&Z3?a%qUbB+ZN(2sN$ zKwSTBfA|HQ-ErI<KtP`hHvt6ZJt0mH-2Ar%5GZpg9zdWznGb+~K9@QH1o|fnIOhuh z0`<v4&-o&Nxbp9DToLrQzY~P>cY^--J6+I7Hy2kMceK412hz#ei$e!+i*lggSUA?p z8SM%(u(omKaBxDo*`qm}U7Xx)P-s^UI0q8vH|68!a6n_NolqPe);36M4hOg$ApEOE zTzfV^h7*Rv5$%daqd2VLuGT^va2FT2mk@_55^fK7;eb1$fGQZY3)&Hh=J?&I5QmK; z(%uba<7mZ!wE^n6!(D9P2zy-5Kxrq0EgFe+6#<zyGPKw`0nJ!Dx#Elq0=e8%)zRQ^ za^X;L!P>ZDZIB=(G%z$LX9u(+R#d|V;R3V-jLI5|b#|2z6$R9XBc0)nB2F$=qRvhz z(ck`HkiCtgn<vN<CTSrF{oU?g57|Gd3xa>;fRrYIw80;K89<V9g$;#`tTTm;z+7A^ z@HYnL2Il@<=f9?K<M=Nz>ooko@J5E_-#qKI9f!EuQfM#?jFFHOmw<|4#2^T;6c~nq zpe4~T7!(Qxqft;W81Uah04J_ET2dS%g_M>?q9nn9uU8U|0^{a3kS#3+lZGLXa4-ZZ zCItbCK)}+{a5M@96-S~F;s}@o9FD|*5m0F;QVJ$5EeVD~5t5P!DM<;mBnS<GOJUIB zFfc??3?V571H;5Ia5xx+0y=_W5GcU242Ur(Nfbt0QW7aG27w_Y&`1mhAqJL0hy(i` zR01k3jev`zfv#Xsj5JsZ1;fC_#Kj?CG+a^~iWU=xV~{|9QcySw1xEnBYYaj{93u@8 zmxf45AV4q)7(!eMijYD{iJ`ziEsU6$v=kUB36Vy^q|ss!akvy1gOLXGghSEdD3q8u z0s}!Hzz7%`21iH&W)Kk({p-ig)kW0a1|bO*#jO?zQFpW>%E`q7M?~RnSf@Yp<IhvX z)d}|^{tCEd{Ga$&=6{#KomcoSPEJ_R|AoijyTCubdWg8XAVpl99RS6EGx>iTaD)0! zAzXWWzgG?aKX;hFyWs<tk+39`AE*OtT4eu>K#zd2`4pWToL$hau4t5sjXhcm?tpgX z2c64sIZ8HG!1nQXo(|m0-U<G@u<LEOJ6Z?WK5RVys&Xz?b2awn2Z@USd!4Wt2r42e z0{)G-r-TUPcUlZ80s;NCgK&sRz@)`tP?$7u1NoyaPUZijh<mrd*@N46e}5YS_UGT< zW`KS9_uT+@X5-FjV88!=1zZ`@-+S}<x2XT-vlLFBfA;aue*Q~eA_zAddz7#Z3Rof_ zF}NfIj>3SYf&BuBfS}-F5V)8WuzV$v62ONYNpTbgiG+dvG221EU#SQ%C2-UJ4?E8T zradmt)!E(#D`MmM&$q5<4R-|I)p<d2X9p20Z;<Huq7wZ(^_O!0Z5~YG@9e);;$M~j z%K3-3|4sRSN`J51|NjR5vAF*fbhU=NphZzm$n&=U=6_G^{~~gLJBk8PTofd%4dD>B z;t+PWaYl0p8^P`EfhXF<#mPk;<K*H1#|pdR4qY3p7m)8PB&=kiqOE73reb2DuWhKO zs4Ndu|C6t(p>|8r*w{irUtbw0p{=FQA&fzLBGJxR8z)CsfVcwZmz^+hFk^uu(aA;F z73<;zoSMQ`z$xtHf)chuB86Q&Y_Ldc;P4c7bw(py<^Osiz#0>EL8GkU*mDpD28}{H zqEW&~q}czUINZ_E31Q>-r?i74ltb77>*k1-w*uO5u|Wd*!Ck!^k=B4tPHwKkZjK%{ zjwoU5pQHRg$|LQ8Tfu*Z01OMLg~a@WgOJWPP9RTj5aj$s7XG8N@b6Pv*vb)ikNV#Q z+?x6S3b-|e3;!(hpEZojCjufd$_B6~U|}y1NC$T`{O3;O>g48vySJb{u^?p)6%I+L zFv14Qp`m|^<M)*L&viwJ<Ig@Q#Nmzx?pwex#W?u%P4w^b3vr;l9N`W&z&!@Ig8_rG zL!*Q^ZsC@%nvyBdh?to?OyYmGgn?TX*2Tuz8I1xd=qswJ{n5+NKt&kFVd!XsbV8l0 z3QQ9W26s^cc3xoiAc5@{AU2N9Zdf6XzpHZaS$W$y0~fjf?9jh&hC;wi&-1TLU@Sr$ z7#Al8To1rD%yw0|r9O|R_NA?WZYt@i5zd4lQa3C{7zPr@K%l?|3q?voVMt&rl7LH! zNk~HxP&5p<eV`!_;0l98N&zQ03XVczP$&#c94aP>fdO|0DYS&tpSFPK@81p2&W<a9 zH1&y;xA8sTY?(Wv$ifF*#l!u=$Mq&mXx96p@MAL&6Xj<@1mu?+x$VYi$%rK%k*AlX zX8XF6MbrAnXD8;GE$|U1FUW-A#Z`wFH&ft896xp_NV-QKJq*0pKM<kgbz^mc|Kb=) z9{F2(Nv-nlCzEcsmplB`y$DN|4!+6?Gl<f6Bf^fK#Kj_hiIA{#6_)Ux=K0ejk<ZG- z?+UHT>hHS=j7L9z-{sAC7HA_H*S~0YJ-RmYJ<(eDS+(8w{Cq8v6{&@`IdoEzPyUS| zY-PF0-bA@oj=t}GYSI%rd5myFO2K9k5rNIAgMnEy&BvAcP2H*#+SA|yvZ&4n`YoK! zk++>5PvQsuO8;#3Y#&tx{XQuGl~rmreLR5gxsv^!;?W{$vl!I$UIvTZv+feLA$aw; zpoNsuJ7&d1;ZbDKb?glypRLRzwpvKMe6T5s@YxSm247OtSMoJp&n*iLW;ZG|nS0%! z$e+*M0`)U$N{DS>t@>y%{e$dii|!cconsk8*N6ue$)@4+k>SDRhCisu!p_W<GK^@f zJO~eT>-+p*tGeoA*VbMHB`&_SJjU=zYX9o}Mi*okqbFIUdtb~Xo?62UJvhvi2H(ED zuv*2Px>Pjq>dc|dO|@B6dW~f>Qhqq+jxNnjR?ZbBSKT^mYcM(cgEp2pQW&cK^+V|t zBR$vqT3-kxe|p8FT)OgQ68+qfFhyX&V>kK22>I=wwHK`MsH4sz^tcrSFB!B*58i3r z<8^PCE>}0kSW&dk!^pcO3m~y229-aHo(8ynb)<K#9qYEKERo|`x-lr7J;D%!8m&9& zNyCeJMf1h&X$d94E8!lPzhi;d^47!LT9w7t5yGG-k*dTce7~kp4T($3d00^NxMw(v z)J|v%Uqf+fO?Q!mog5>rFERHA9!K{y4JM`wPNqnmmaj?z&E4TKcSnzzRqQ-h_o^%D zwgY}jVx6RoBXSQ?hzzS-(pLNyJG!sU2Ke}+W`&=xbbW{mZ2~jXA)<-Xw`>Y-1a~M8 zHPV=IXAD1cq2*{_K2ergPjJ5e&NZdrdb^p{Y2jRfr!1>U$<J4V3O}?1r=wgIM-tbc zI4gZkR?v_2C+oayA|<hOYR4>(%((cIJw52Fc=GY|b&%+{j|z37@A`dJ1z_;fNJ zr#Ur*b8&gxQ*`Sq&0)pnQIf|iABt=pj=)@>uSQRXZNG_4NaCDi0A*{L|2VT=^yl?) zelAyetP#~>X-f7g`YjC+WSh}bwWB@;iJ9dSddIV10!h@$$j7ta_=U;LOq;ieS&gb} zckA4|{(*;dCdYRsm{;M7Q3N~W(`N(`l}AP=cGjIg<ZdELHQLqehm;Mb=ZNH&bLdF0 zH&eOs6t{IN(cB4TCclLRwW$yHRQmBx`a+&sYV1Q5OAGm@b(ICZ46}4<8F5503soam z3H=j47m}Y%qjbh-YCh#G)q;wAGttY7DydP=_te7G$3C+Ua65T;a}RSdd(+Mvy6138 zLGTZg;suxJl0Eyq9oC48>NOlp`@W1_i&oy|#<qQHE!k4FJKPY+EuhvLTw$W}75ZV6 zLnbHf`(Er5>o1Bf`|E)Rl7<JfThRsrRz6C`!qj}%AicW_#A6dSq6MnTKWB)NO#_&A zH_yr|-Fn_{>e-#X)``~><=o<InUT+pcAR^)|8aj`iz?fw?ETjP@XVV8$0?<U;8!nQ z{DfccpHc{4c7a@YI~>BzaQD;gnH475BUxg**JY3R=e49+lZ!_nIfc)Pi~0QtXD?Pz zOr$g81&_Y-bb9u-&zazCYcFrc+_#u!V$N#}kEWf#G9797y{G#1WolO@zdXi)QmWR+ zqorK(LH+lkd)IHvdkF{jsS!J?=gKu+?N@3n|JcwTsq;-QaW1I*b4y*3aCY#EiTiku zTwg@rQ!~CVSmEY)4Xj%{+Nz~bB<DTIysKuqp+K3L=}&Dugf75b43M}oX;XMNF>rHi zF*8Q&>4VmgLRtYr+cZ`aR<6-Y^%s6=!b5I7cMlf*MoSbjAfJd-(Nl&K44&j!YwycO z9tv$)294ZLCF9-J1TkKLSmz9k$UZ>!SQhH1?~P-hG9`{xnQMF<UcCOupPO4f(`OH! z{NrU*1|nApqJ5=2K>MoYJ$GvFu-%vSwAe?T$3o4pU$>VlmcpMuqZ0O^M#%=+ZVu8k zwx?icTi*BV)1&Ooo<N7yWdc)dqlL}Yp(SrS@WVPbJ#X>|GwjEK0><WVeVl_lozd@h zF^Gq}a&6iehYoJm`NcTcNz<_u?Qf_wt=+V^{i^9M(gA<lkDqRn3*|o`*Y_a%#cSSo z599|Y{L+Lu-sf~h(dS2-+jVTsHxi}ev3|-Hs_F1d>qeCg5N2_V96Y0pNuu1XxUCUp z@i04(`%?#PU*!(-mkXi8_6sJdRuYd_I{Z#$9U7)NBPzdEz;YEVuk8r7D-W`|DzVe< z-;J+Q&`Y7X!NDp10dFI?2AU5s(tgur$o`zA@)4td<TT7!tW01lIt}xzaC%g)`B}-V z^<Mmqb&+`T`&V=!SxF87i*@Dh;ku`rR`G-Woyh_hc7s@WLegr-9J@sOdu#Z78G{yK z76iX`^!k|k%wtR5Tv=J8CVtJFgi62IM0=cASCOcC>4`RZf}^IhC|+}A<Y2#Sg9Td& zdg}XnjRrM~kQsJJ=L!q8knfPYs|_vdMI%4XMkh<Ux39+W>lE1xZ0n4vy`T+@lqzC& zTg_yPPqsoV*2b^ihmMhLhannY96pi>TUIDnwrv`E)j|6!#@~9w?^H&mVW22cDdf>q zAinN+|DpGFU#FC_tf!o5B@5776yN*t+~$5fIvR{r@v$mRefFz6@|_{$K*M2;Mkj-3 zsSU?|Vz-*8E#=XL6C=4-T(-28E@q>=ea_k+2&`Tr(L=FhuZBN}U#*^^SP{bxY~DGw z2;bkY58eA_cQW9${?>rY<7z5*i5SGc^~X1ZvG$T-Ph@?DWCv675Zlo3htN9aXt)8q zyQ|QK>c`UpRj$(Sq?S40cdxuj)7Gn+MFqzQ8Ml`=KBka;S(#wWh^zEpe);1Be)o{! ziageTgq}iNp(w!&Ds%@_T-N7N<vJZoBlh%0DNO>bG4Z_##=vvcC!2oDj;D_-qv$>3 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 XE*|J?z53?s)tSq`|1h7-<lg@RhHjNh diff --git a/gorgone/packaging/packages/perl-Clone-Choose-0.010-1.el7.noarch.rpm b/gorgone/packaging/packages/perl-Clone-Choose-0.010-1.el7.noarch.rpm deleted file mode 100644 index fb1a22d349c73a50b16eb7b67c01cf4c38959b05..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9708 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%0DZdpfl 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 QeVgUL|Dg-IXkq#P08TgF(*OVf diff --git a/gorgone/packaging/packages/perl-CryptX-0.064-1.el7.x86_64.rpm b/gorgone/packaging/packages/perl-CryptX-0.064-1.el7.x86_64.rpm deleted file mode 100644 index 2e2ab421854ea8ffa5173ce85c85d41a96d474c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 720852 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.#$2r>|e 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≥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%0A{fam| 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̎?5#E@}9^28hr%H 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%0Dl9F3`#|C_(wlvQ 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%0A4-PJ<cV_ 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Æ-XL3ykkkf&T%+k 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 fbVE+Mgm!{9Hftas`3)HQGjio6jTnR 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 BfDQlv diff --git a/gorgone/packaging/packages/perl-CryptX-0.068-1.el8.x86_64.rpm b/gorgone/packaging/packages/perl-CryptX-0.068-1.el8.x86_64.rpm deleted file mode 100644 index 589e55bbd388482f36d738a9a971ca0f84d43e4a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 697776 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�#LkZ&)-ceQ5`B&NJ1Kfrq_S-PbBiT{cqNaKk>@zzYo|@-f(Gl zy?GAB&NmJ*JKwB@VrK%RdE*VS`>m&L_is-A=DFkA|8D2oZ;fxi{pP@AuYazWFzxlb z|3q$o-Tzk*_t$*_%l*rim(JwPyW(EA3+(rrb0uKG*KAL~hOgP4fE8c2JqbI$Zr6)s z?KS%nu;t&j3z+%0O#*EG>n0Es$n>?_L7uPO4nBR|b^%ylw_PCEzitP~{(VD*3IN;J zYy{5!>sA4KU$Zp{1^??F0gC^1YZ6BO+YW)0|8*0i^a47+Zs!Xu{knYuT)%GnOSJvE zok{Tfue%bF_={Wl+eu&S{MXC9+>;2{Uw_UpEfK@NVeboA|EAq9kp8RtUxI3Z_%XNy z$X}s?!2C&vl~4LDMS1Dvr<df%YqxpjKR3Vp0mjQOzufW)8}gdtU)`IKWBj9IlG2cW zbnJ^n;GZ3w0Pz3Op^4$}A03L<`)9}D75>qIp&|e5Fzn|a9hj(<|K?DkIsfF4MBVx~ zM~W=-pB$2yswN#7*HMeL%9F+lah8|-8#?^_JNWs>M8sHr{>`+=Nd9Y)E5}y0qhq^h z@&0j^(KR+Az9-vb%J~PitJ$z|c%7Op+J!Z2(l{`<GG9;Jp3vUVym`CoVPU}>(Xc_7 zpTBMOFsvBCM8vTY$G@`M**_*WI^HtkqhtN!;$x$uxORtF%ZQFO{W}<je_Z#7c%!4m z;c+pR5m))8A<+#{KGrfjYVk=6f1bp&_=kr5d7a=tI9!X0iq<2d5|8fUR{i|Dz!zAR zJ8+Iz{uyI`TZ`)%Wpw18qPxcVca7>E5oP+vYkH)(o1~jJBDJ`VFCKx%VuxYBvMw1h z5z$hQUQ{U)|Jh*w#C!Hn_@{*ZJ4A_!-Mhd4$s-`@R`34)b4Nhb9$)<v^Z7av$Gp1z z@9VGb`|tkzfAd=Z^xprg_w=^o-?mTO&)fFDZQqOUzBne~MEvJPo%uyw^4&7AKKuXl zIG7RO3UCHZF`?p@&3;{?&90G_UtDz8Si|y*w|c}&!S!nTxmACNf4_QRHT)9Lk?9w$ zcjBAn{1Q83%lUP)V&g=xSNuwcwFqlkrktPIGfL}1T_iHHryrrcvt^d^t0Bsqb%I)P z5~WRL-uLv6N}c8WfI$&4G2#sI(b0auvYviXK%_Xo(hwqLBtVjF`$fn4(IE<UGH4TJ zB_b-OD|c8uEU-?Ugh%l!-Jw@RjGytclhn`D;<a*|ugA-cJVQA@J2twDUy~@&F8c28 z-3YuJfp;VDZUp{=MnLpgz5DwQ8-d33+aW2DWJnq$Ig$cNjif@-A!!lOV4OLwXZ^xo z`j^<Tr+%@Vb<Z#Rh0STZ#@qh9Mu=D6h<CvAx)<@!Qiy+mhBsF8?*#GAdH(e*-h9p5 zZ+I1&cmW~r?sSNMK8&{>@#<sVX~dg@5(ZB6JXS^0{PD;0ek`;8=IPbECHHvoS;pDP zrT3C$XJfIoAD6qCBk0~AZKS-J&Zg8IPr7F4bmzMzN86W9)uU(o`agWo`+k-Wr-V!^ z?3|MA<_|q9?o6J0XrV0YicTJw=34EI+XBYtTQvFGI&0fr`B-UF<8i9*H!hiWV?pn) z1}{tPe|?&6?HT=XyWejQydHXX{*=~FpK149Gd6$v;CA^NPohWWDK${q-{-^AeJ(cJ zUU%_lHLs@MvZ8+Dw&@EG+x7lEPs=hXyX6?|{3+x@%av*J=lXR}ky__J*;0JlN68w_ z{IWsyIcF;s44QT8+JNqn$F942b}PQS>X1Sm^JWa~{QFNvuHpxpFNjgD<xdk`ba{#? z%fIVdY@F}HK{cSGZ^v&-ewuiHq#5=sW$>P~#moM9y_vl!W4~unpIk56w9l2JxyLN; z*nD%$`34Wdy0`AWYfO_$8MeFEj_!T)+UaX)D&5XC;c4x)`}cp)=G5I)0~)p0&t^;& zRP1){k1Fr@Y+J3VYh0yk{a9t~cKbr6s2|TMi`#tnozkmtiLnbCPWEp*V(PN96E7|) zQR|zFbL%|6>}k=hMONR0{96L^E?UxN;gwU3TAUADHGaaye9GcZd1md~xVyxVsXO+? z51cuu;Q3W&E=0^&RQ<vy){@WixDKE07%}zxGO6b_ectFmc6aZniCur);T+&LgO1Lv z_pQ@;bHS8jaUsPAKQ8*qksJknXp#Q#u$8|Cbel8v=d8a^JX2xv-3Qi!8^_uo9dbI= zv?XIkE$Uiwa&UL&l3T04JyC2*_a$*v((m)%Tk_Y6WhXpyjml+*b=o|>|HEQWdgtDk zBLASH#k%+f_td&Ci~nJKvn83f6uLA1%EVlsm)^5rVa3w%zPrixKYTbn-GzMPt_^rt zGGmiv(W_DhMCPh`wBZxKU1{sNr%xK3A@!dAd$z{9(gdGcGV)-phK)0R6WOz4^n^WM zZLPK8!L0+g>&|&PKU4NXSp)mu@I8ngSWDX3{%ZO;MP?Vf7aP^3!;akHJ)<1gNAB(& zJU4y6yhFe5nmON;`gecqJUeXg#3?84JnPzi>#C*US5MC$n{D9tcZwd0|FrnRL9yLh z{jj9mFVC7U`ZE8|GwiDs7d)Egv#Kubm8RmJ)<1uCbY+vzQl@f8EjedYjNN~2NQNQb zv_Eh=@4PSOhYo36sLt7tTTRn^bL8tH-N!bF_-Vio-TIE7^Yw@6Ke~}=a^9l3hc7!F zf9lrMitDmpZgOd#Qh9XgDqR+=eKhy$i`PQW%*(%~c6{CB&&w?hU6#6X*1julDX{_R zHk%zbR;*obUA<B_#`)K3xjSS)r%e8(Yjk|FE#K|$8|L4b<4pfGnf7JLv}0@UxtVen z8T3bO?+4ktU8%pZYwI-yOYhwB>x5;^K0cG8;HPWCqt}#bd!=yiE?FuK`gF;Htv#ym z-WYmt{-$p~?Ox)uUcSiMU!HYtFQ01v=?zlt9A(osICQM+rXu~%HO{!fZ%L&CXRnSP zTzBI1LVa@7Z?O1$)xiaBS>01So?h8G{z?B*0l%g(`s_X2vh1Oz3m5svs`l_vSH=v} zE*^}26!lBc@-K3C7_odsx&`}J*8g_VflVFHo$OI&Ze)QL{#J@~=i_!ak_WU~U0It} zudG8Wcy`d?F5euL(?zFwf7AR){-1XJt<^>8=9*I9jma^nR-^T0M~<Aid&I&u0ofbf zD_84zZnbEIG4;lWj%aaaT%`gFy_s$mof-I5vF5Ft7aV<IxIDZ2+1|YmpI-PhV)d-@ z&t}PW+m&3k`TEnwbI#`~JJq;f+L_{D>-)RTO{y{D$&FkK^b78snW{A(dF#6o{o;~O z%yTt3%ADGCaR#e*Z2X+vv(~*o>$qc9xeQUcC#3D$dR6d~{>zSTkS1+DTdq<4syk0^ zk``_*m%m-V%Z+cYIzDc6#SgkITeUoE!^7b}6j;4V|L(V`Bg4}lZ!>Y)-6})(Rlhf3 z=K0=Je$QHD&55*=KmTKj`>VBb{Jow-j$ayC`^M={79MkSlZW)F@W;82*G!)KD6Q*< zGKa>D?K`FB6Sa*m#h|5IPkp&JO@U1{E~c|~58GOy*AHhSyPud<;o<JXW`?1q%QhIl zEo6A%N~4wboohAi`|XXJJqlMSTQ6C7-`cfv71f&G>#+ZElPb=iKZrcAyvw#S)z_xm zw!3(<)PX;JdoI@Z`O*pLKe;ug;#BqI`9qz9A3FDkf3kgMma!#TE+4jKW^>=woVWdQ zj2~B|;Cp2<jn?<q>XYec=1Lhmd{_Qpjb67xv&XgFa6KyJy8BOm3;ZUkirl}Id)vyJ zowO$be=La$JAQO~$mcm*J#X@OSpK$ooQ<7ds8tFU+Lvk4so(PKx%Er74#wwY<63<j zK7GTee)aRsskPEMJ7l|3B7E}MEEjtwKdkgFSiN!<v&-mlM{=J3<?x|3&o)g+v;AS; zA?agU-^^sR9P<9EiQ(lZjN12e$l~#P{m+&zx;lH?K~+Y%ON}2K^6j&^5BBX0R<b=D zdm=LT<Lj4O9o~AR;lqW4Iu32Vuhqj8AsH$MEh{i{(ZZFDq=uQ>9Im?FmuFqG<kylN zDE0foDsfHE6nrFQ`8ngR@v-Fw>D~+Fhxjk)_GR^S(E+C}b@*uY=1<#wzkSYw=GQvb z>*@_IwtUpa=$jkPIy)yX@MZNcD&3hn`{exQg?Dy8Z|@wjpz*y~rN;jh+kMRN4Egi7 z8C&yt?h@1HP7SNG&ii<ZU-ZnQL0O9*`M&LhqCf4cV&=NBBK^kqbLMS&ZNZR~3mg08 znbNeKU#HK@<}4gqaMby(M%oGGGPFI>s#H|x`|}Ul8HyFDRW-xVlasREtAGDmyO9-h zhOX&yY-gR{&JVgA`s;)%-`2=gv-M<0<{wYwiCTH4*sS)S`FeYXOr4eAI9BCue43sG zr)Ic0Z$+bSv%iZT(XHasEU7z{jGbA$M4Q0iiyJ!Bx?Li^UQqwVYg<>mQ0n8##ixec z+c4uqK>XF;`|VFRB)Z1qdN+2gSx`GqiXo#HE?Mh$azMSSPam}U#dY6NCi|kAqh}o& zkbG#vj+3|S8hmugx)fIfYrHokS^K5SHtvaC*mcd-_?GkTb{Lht`haXH_AI^E>&IVK zWeFM>UGYG`*XQ%^yg4pM$_46_UUA_Wo42UE@>W34=dJtq@4kJ@(Tl@2&L|o2b@Kd& zKHgN(`$vniSJw2?zxV&8a0UDJ+#DB%_dlGc%_Q@?-y3Z?TlBL6H7-tyPcdWv*)-$k zZT#(%;NMzj`gvB%`PrL%CV#ph^z+7NhbF7Kt3`<u$(Dt<_SnNu`F+@DYHXTot)DOI z{CVy5ms%{D8~t0x>3Jty*wD{4!WwvULaU&ak#!rL>mKlVpMqO&U;gQ8P`$d3#>^<Y zZg`=b2gk2FSlY3Fq}64?hbeDXEkEDUwDymIKKG4@rTy<FUt#|`EZc(9Ip_V9C-l@0 zv*L4qd~bRQM{wEc9{KP`^*;KvWXpR`j;wG0ux{B!^+qi|eRTY#>kn@1xxVeIdqeY7 z?H4xP+ob-mBj?_4IHY#=nptnoO1pfcZ{r%@?@eZw-(oy!*FAf`50<4F+U)L(-y*9V z|2^;BFS4&Im#f{_X7zqtU(xT(hLM?nc>2MXX=lGl7M{Aq=H?dzicaZx;fq4c%iPMa zu2Z8b-_B0`uubbPYdq`GBip*rviZCDw`tjH_@EDzXXRGL%*}sb+@aBRtEH`7;<NTj zx;@EC<vyjiE0I3qPvt&&GI(CsR=*6HH@4G(pT2wVQo66QtXi~uXV00_KS(y9Oxk_B zCn&ROJ`Xvap+lQrT?Yyu?=@^*sY^A&*X$e7IeT2+i17!ePW*D;*xqX#{gluFQ#XJ0 z)24j|b1a=LDF?3H%;B6_>^J{%wQD3lKCbG}FZXVlQM-4Sk$F-Ux?8>UijUh3FE!zV z9+%sn$hBl%IW73mmSUrx6mNSlY(n$<Hx?c0wd0GzOS4o8N`86rhjp&C-2UUiPTzkp zzJ2F%Q!{VhHemJPi??PsxnJb2w6SmHkw5Nqhqh07&+pSKO`FxcvbgG~Pb;17cd^F2 z;?<lte!PDv-`cU6tfJYc|1ecK-uEM`>40h@4jtKj*f?Hp?Cm@oXFq6H<8k-YX>I#f z>8IHrtm<FnLG6NLn(uMl{;gE}*r3}+Y?cD|F0XFeyxgNq6Dwyc)2GjcyesCOD8DK= z<h?6be_uT&Y}1dIJ|DF#ymN+!DfOWJD@)CcTM=|`NkpgT%EiM4=cWw_^54)l^1*`N zX7r9<^qGCp*`a0Y7DWT1ulZ`8n&weYpRK+&wtC@yA$DocRMiNdt2K1iey>Hz!11F7 z_dMBg$kjW6zwIi}V0QMYB^OpKHs|wm73OTcaQ*vT2M1mbYhNL{&$Zi6_ND5O&Q7-b z`&q|^5C5dVdxf$+{I1aO+l5A4>{MVuaQROwH<-NeLADPjbu-l+4}0#oTd7vXV)tii zX+9peYC|=BX1>9tN)L({tnQpqqvoB6$L`!)zwMgme4stF^226DN^I@4Y1<EVyFNO4 z-tTCwS=n|R^%Re~xNv*&q31_+^cLOY|Fqzm(Q_tWesZ*1CD*!c)fR8rS~RTO#6GF7 zlrJ#)al`A^yIx2>=1!j~>Bsn&Om)WBqR*uVA<Yf>{)v_=iuYYMdHL!VRhC{@l|L*^ z@5=o%hp%2#XL*%<-Ew^YORL6<N|<@;RBIHeJ}7#kPPOY>3S2%vd1K$*F#+FayxMv1 zg*GLtHIMyRp8ds^kf%n;<?*o#uD4#iz1*0S%YK>K=)<sQ&1-j?RCviJ$3AMbY0ih2 z22E@EMdk}d^V}TTQ64a@>7Mqj>b+l8%RVvlg4jMcIy9Yr&+JxqVVbn&ln*C<a<1)c zzx64Gr>J(P&qp^7t~uF#VwN2_#x^gk|2XyV*Yiu|X?=LnEbp92Pr_?tNt65f-IB3C zY$`dbP(a}Cr4D!W%hPG+sjt3Tb@uV?zCo4dpATLi(=hx*=&q{m&(2!VzeST`zjs>e z?DXBF_maoodq1$^<`MO3eER6~_7}g{vZD0HqU%B%EV(&vS@P3`V?X&dI&Rp{wfmiD zk$+mUIa#Z87*V}zaK+jUdoP-^a_jMqx1EI_UX-S!sx$S=+)M7?8sE3mr&YEM*s!Qk z?4inKTDQ&Zc3mtsvt{{#0TomK9NM(^l6sw-<x;Nbf6@QLP9br9{V(^oHq1NUe?$*Y z-%iqyhh5hUD>h_Q(LRyYo$1C0-&@c#W#<wR7w(Ko9bf+Y{>H&^*0Zp|Uw5eZ^KYf* zmaTieUHGNxGv|y?9ua=F)vWo+R;Kq~oZ@lei3dyVIy|S)h9iN-z~(0x1YYX5E41LP z^w-y2-tu7fl)0Zg*gi^cwX9Sw)%keA-J!!~9;@mrxq05$jn~$VIe+Wd4Ua~yIZ=2< z<?N#l?jKh^TUegUE3Tb9()ya-aqaz0v(EMS`bwtAY9kM2S@Kcn9~G?HHO{nuT4UwT zYmKYqc7C*fZiQyWz8&SatZv0xy^m?TcHUm}>&EIedX_m`t?cd5!^)hP99egyXY7<O zs_gM|O>OqWvORSlANg=qr(dpiu6A)%wRznKtVvm;)6Yc?<!kxJ&(nPKhEBY8YDvl- zk7{_@1xvBzu00E_Gj(RIIpOEh)W|TY?2K=Q?|9Pg%DKMdZ{DcU&e3^h>0|?nJRErU z+j5ojT&uaS|MYyH&KkS0ebu{D15%F5QKGZGrN`P-tq-N0^>c==8do3MCQHPx57Jhc z`f=&L+v2`RHhSH;6n86r{J?ka;(&*#UESjX${!oH@3DJIqp@k{|2Fw#{yio4hOS<@ zWBc7LL#iJ9V%pGdJ6Dz-T;#Fb(C^4c5l_oFx1T9Bs%5dl<0cO)5wv^F+;Zb9JYFAm z`^fd#TLP<hO0_{5_;H3Wnj9#6?3;QgL*w@4`sq>bb#a%<59wQWdDCyUr`ewB<R8r* zRh;zj`n7aLvxFDDvbO%A4w2iUignw6Gx~T~+B47AXCD%BX38el(u%Vi=WH7i6|;J6 zJ^$Nf4z>*Zx@71#(K*ukZCU)Jz(*O&wmp<=|DDWn6-!MkI3TBSbMVzKqTNru?br1# zG1lXYDA8@poMD6SmT#7P?DGou>is_WV2*O<zh3(5*Q+ngKT>IVWYz*#vR+^D+mIZM z5B5pEDSk!J)i2V|?q9g<jbBzhcrYi&<!dn|`d%u!xq(vk$IQp?teo!&l}`=Sv*pM) z&ZqbI@~q!CrLR}1dU$o)>BY`A_Z}*^GR>K+`lC+OuYWej*&$10ql>9F`tqJydd!{u zT1?4Dq5ea2tqJS)`wyx7D`sl`Ww%xG)Kk5SDRqrbb3gg0{^W1-_L!NvLF=#5ls)j- zkoU7rt^J3zcyCP3%hUDupA9`(b3^pwE8R9-9Y6f%i`UEKU+TZ6zBkX7eCyVC*|I;d z^RPmFhyAjp%>5o;AMOz}EPJ(;hgaQr)UlgOFI(^PKN_oh{l0kLIy-H!--_d(?99{n zX5E<s%=ES85APKyee8lB`Mkq}JN}-zvDcpMn!h-AXzC;5vp=iSuW`9wxBiy<^9wV* zlNUD2ee|34&BD%C3%@?KWQ|N|d$%iCc=6YsCC3gv+M8|E#mrifD{9O63%2-GzxZM0 zcGr{F%~s2tQ~1jUoAdtk{l$9QGKCx)x9rFHt5OAcC)BNT;p~tBg&Zx6hAHy>etzLE z_c~Qub?tbipsdT!_VRX}ae4Qh`R4{H;Ya2j-}3YM??-?9X>_u^J);_Dm>70&{GQp7 zdmfExacOd8hyG|s?c-IGee=QSr$ZMmeezM&y;+j=`sjoG#hUc27FTWZ&)w@z+|syz zyRxOnZyR;SSu3RO(qtX)f3R}>FJljkQt#XzEjK#6aNEgo^^Xl|T{8LVK3&3|6zh_^ z_Qfpc(-$3kv(l3?abucxZdozSPlwu_tW#%NR1shLPnr$vldM+rsG8l%f1Y>Nz+8_e zRUSWR_?#aOA8onst1j*Qzr0`me6RI$L)BDOzkhZ%rEBr(D&9#mJ{!Du=FyZZMy(2Y zzjO5C%`3EkI}a;YzkRW_(|e{#z|PFudUVJ;=<xa-M}A-aq+D;GG)VsL^s{Oa^(#G( zfB&9e-v*u9wX5D{?XLTC=QYk#baSCwpB7$oIsLV!hu`a$XXdJ6`yy&rmVL)(&Pvxi z*SWoo8cy=e7%`${yP&C==I`w)_YCcNVe<01TYgWSp;G8C#o7#=)qhcw<-xuW?@!8E zFZ}qm^|dBvej2i*X51GS_Lfn;N`9y4k>Gsu@3oD;<UG>0>ZO5JWR3N$8$Uf7zUlkl zAGMe&r%Y4r?$NK?6be3;?2~TcbFyXlaZJdkUmm?NER~XKQK20Lz8UN(f4Jc1_ZNL_ zhCN>OylJg(X4DJqawJv11#6;*3|O@A+>rXGN2eWMCq8_#{|~*^{amwc)U7Uer%#(+ zqk&R!(g)YmeKjtyX_Mp^pJZ*c^=^woD;{QkoMM$eGX2r=)34ispWp8F-dDqJ)=EFA zORFmra_3*N=E>IKkw5j$)bQxiEup!O_dh&r{Rnw#)aIsp-1Q&(9qT<JG;=_$b0gAS zNEaSo{QWOtAB<SGW@(va10T(6eWTBofNJMUk1DvYb!g6<CsTe>Bkk!E;|t%OmU`Bt zWc|l1?U&;~`|v8KO0*rWKC3aKQKQhsA=#TBYdUJy$TerGWm}nNcEcMfIu*KmdGXFs zDdH!NeXrWEvyPLE7caZJ_C)ZkVn+L|rNdWF{CLN=2Ty#xaCuDAZ}owb#ub{^Nq4O* zSfoYyBUA1q&-C&3FlW)2+{)o*i)N18*KBu|LAQp6j<4Qe>koayLah54AI8rg*rCB6 z`wO)=*J+lPY}&@9PmX@Cr!ITdl{()k{phW9jk-pkDtdS9tQtAh0+q7v9{uFK+H*gv z`lMf*!6U1@AK-Vpa=ottRu3Aa&-|=FgHH9#zzu$b+h0o`Qqi}!`geWSev;v7&vlpk zS6jNf!=-ZlravpuB89c9_O-RgYX*dt$hG~C47Wcjbv7<n*_h@*<v$+K<9O+(n{urS z-qJcKSLn9eg(q+Ou;{Rfwa)Z#{PD${s4M$+=4$jfW7Z>u*RA^4ar@4`%}-h^+G93$ ze=wr?!*M$+l{k^_!kD}PiwlK^?_RgDd!e8M>)I~($Wbo;SAn^fmtR$9<_Gb)Q@6X9 zbyvm|`;vv0U;InD10y!y<K^)M+g~2Ks9xly{FSdYX_r0EZvO&vg2IO^*;u=6$_%~@ zE2GyGnlSR)_d8~{>)hRYXGYzQqi#Ij`M6WAVkKuExH+;x!NO6;LRNdzj@tUikW42I zXAOJOu+piMF{h4v*tyq!>sg0nQu6lZ?2WzT?z{3-IrK-@_II<~nLY39@aIDpq-^vc zuG-hR9~3XP{q)&d6Z&>M+H7!8?b%xvp8a9uW-TaZ)n}h=-2Alf=feUzT@C6~|MW+L zc4f?Y^7*aj;qMLKJNVR@9uq&<G;@B`vI?I~GgCdko4IcPv3v9$KSgXy9sDeO@YWUM zdd|99f5P3-ZPv~!TWa%~n{$U02p{o#(?@eA?+E$j@PVJ6<><OC$Jg6d&6*aM`}Zv4 zSIoWBx^LbCeho%%U8|1jU-#tUkY<yor~kCn=t+;}Wd6E!!Dhiz9(-aAY>{vB_=WR| ze7Gf9e6mwD8+5pEL5|Nqrpxhbog?k>Asw6le63{R5gjYdOjmVQ<&nFd$6skydFs!R zS9dm>_Uj*`n#+fpq^Y-c!R#u>SKZOq*V?pVc;&SX&(tcKqU!UgS{v5v>AWju#?m2c z`d^w=Y2u2bH?))M(>+}|VrkC8%HR*$$6xy5a<;wc+Ffl^MDqT!x@EST;|}gTSm?m` z&iU6D&A<Qh(Ln*juP?djO0FFnx@c+H(ra#9Z24K2p3`?VIef0q_GhK8tS@-x_Y^Zb ze^)$hU%f+$fl~@z>$Gcbkz<bseUzeZhnzFYg{`kw_4(l<W2eS7+?(%igS(GQkKCWy z_4sNLEp3k)$42Cz);4-dlZg}8Wv$$AO+@p|D{o%9T`$e@URUlfF1-3?NRI3SYh2EL z^sKdQ=I;LY!#1t??cl@W*^-TD)&0v^zisR~E<oOLa^A@Ol`~EYI(&O>);&R|D}R`K zagCNcPV^0SjW6KuxOx7Ap@rv{ZX0v=$CLB6-JMqK%e)a656nEVwrSkGjRyjX=!=|k zic{;Ae5D@FsGHKa#k$zC^s@FPDlRH+ezP_EkB8^nFSjMvg(|yxq-eDI$geFf?^&O- zewUonKQFcG&U@<|otJEAJz&b-g85uSTb?xX&O7Js(Bxv?{C5VFzc%^z1`BgujoL7~ zdPMBLQ4PO5RN=eX#=Q!mRhECauIwDYi6e$hJJ)c0eCYK3xtsURS+3LJgCArqbTRPG zzI@Fq%&6U9SNAm|w?&TXw|MG^PHVdLT;DP*<q`cr&5wVLY2}R>`t(@fh;Ai+K0WEz z_QghMO9~8?I!(G`<O}M0ZqUiB?d!XLo?LXxgg(PRPqutWr>!OSkNLLDl=eHKnth!% zB+xtR*xK(}w~y$#wbyXp-R*q>HW$6Na#OL?i@vEmw&w67MOVjmY58=kd&t?LOY`<B z9A7IS*QVob`VF`><CIZ+-r%0i*FGxMv&J`#L$_~g`XI&o(?gcscQnj+{QG`S8+_iV z_^G>%%YD@OP7V3N+E&M#joDv4;?BNSv(tUJrA(_Y_l&wzI$L~vpGBFv4uAe#i}J}^ zK2Ko<HoG$PNr6v{JKp-6mj_pTIxNe=sn_#nw~9A7X7*1}a{KMfJ*xdWZ2r|breogy zRkbS?Jicv0rryW*o@krp=N4x=lrGw5;O9@aXZbpN{J62{m**<$w<-Ma(_*vMWo)0R zL7HBUZ9P*zc)v`iITv?)^Ual={|hHT*uQtMSI3$l0wdOxBI~a$14ese=iYR<n$A$c z8A!jYKo$s3KCyjeOAY^@3e1FtzIcS3HVTE#N8ZG`>-wX)s{liP@jkopaRXWL)pa7g z<P`(t%?xn8eWr7Ow4X<TmgEiAnerk?T`=i!lijq{q|aQ)3^N%uO_a<!PF66*|8C;D zw{k5cztEC3MFym^h%Ni^BFeZqX%(=V(<yxD>;zfIuY^d)&|yCMu!wW%`B>QvGV}f> z{q(qn0|ba^7w+6ePb(`WmPidd8`Lb6#F@0`ZA=4j=w*B@S_%@l2iP&ZA=8Ws*E?8r z^5+mhzS;ZmI;)S%v^M$L!yF?uCA_nLY7LRKzH1@s8uBMJqkx-nWN~9A(BKjFJQ?q} z>GcxJXgPsU`7YGdzrAg)c`|jp86EbyR!D2cBnY8#8$I~o?O?(NWPC3sy^kDA=@O1i z;!Ba69Ol|S>}sPGS4kM$U?5<(XaNxz0Ua<<<IOXE$mXN<1XQUrKyG#^aEjQ)_vZ%R z+|FQSLOnJwGg<*<!?hTU7_rv&@dqz%W)K`NXmelU!)wdG==c1*j1)(6z^u@9SIa0| zq^LFVC`i<EP5Ui{&_k0AJBBs;N7$IFSI!XlC5ZYAp`T?R1*+G7$ozaaX6T3PRq{NG zi?LaakbvY4N+A8eKGdQ~wrnOw@?O<){^~~$Z6a|{p_rl4O<(&Ks9LXxX`xd4w3W29 z4(<zo3<XIbzxX;@u-SI$vjW*&55+F2_9bV|2-QvjYpkbP54kz94=&B=Z#F`?iXqxD zoztVgdzauO`HjjOp2UqkzZu4BIiF*Ci7pJ`0WS-sl0;q5cno1{CN3GRPq-LI$ON@2 zjR+(~B<D_qh!0&cQWrTLJZPid92u|DNpP<E;$3hYnlQ_iOQU8zjii_FPd6wAqIX7e zLD8kl$5#sqr)4vDWf+Hh7uz7_(RgG_5*gq3rC2|L6bn~eUEl!XZ+exDbc@X=l4{2& zmo}EkrEDTWu5ArH7XgMx2HhtOjb?dG9e1ylC;?`}F`J~BN3Y{==nq18Y6=+yS}=_m zY*d*3<c99gk90ty9I)%6rw%3jAz7=ROPYW?l6^^rN)dzPqp~Zm&A`~7h6-EjdkEbm zBJ9_<k(f4$B(+|9!A^9hGL(HCOexisXFOFR^|a=_&SHdQJt4@=hLnEa8Ja@!Zs+}# zwH0_PkM<N^bHcJO7&UK4|9I9eaii4vW;@kNSq3Ms(^IM&u#mAXpW^EKBgE%(W7Kut zT6EGiWvq_}huH0W0S9h-XIUmg=F7G8fU`V$ZUsAs1|Vd!MSE{MYmXs_EUw`!k>asr zV{ntwrnDnUVqcc~Wcf8mB#5VK=-E2)BI}b;)TaFXqe{@?Ri{VOU=JvYWX>dJ?4v~$ zgu(uQmzq?4%>+#J=E;pmcG2Qj;@l;74*>Q)*8johcEPtWR?I!maZe=8JO$V}KfrG% zF~pl0zGhC}ZyJUEGsD0@4$l7<YZEDFR&b%*Abr};^wsjw+mQukdZBf_rJ<FTow-vt z+2742tmBY4FTBgoc?lA59Dy3pP-4>07*lIuZhJf@$~dqeYfB6;T&n5JzF78myRs|L zj(u*5=waAlI0og;p&bB#pTGW_vVgh}kO26}pr}p1vP-RG=`@E?BP_Swdu32qEg##$ z71kyP^@Y3^=A~X--d{aCcmW7(KxSF$KL^lZLNwRZd&aR*XZMTirfnrT-M~CfwqHTv zS?fnwl3rV#o1)T!Pl6@=w5wUBT()`u)`Zkl<K)6S9ePtczN3pnv84~>Vc1V>I`Hr% zm{^Ul3Rmo-UYokmVM#-_k0poi`*k$Br!c7}=Z!ZUb9iY#ATa|Yu-;UC(6WA<D#5m3 zdPpaW=g^?i+k=3sR{_st^Hl+=mIu9@!FVs;UfW^?OnbNv%;{n28~x~A^)$L%(KVzx zPU+)(!llCm9={BBNMQCzCh3JWc_{BHR^Ndo+MW4>re|I>)G0zd$u~WId#h@|xByTQ zCU&(p;~e<ftazfu;#Aumy1YuFtG&RUF7-Nnk#;z2K$$~*o}MTM5#Xn*WV}FH*m8<K zB$N)|`YC_(W@oeQGIg@HR(wT(ZJ8Mx^^mOc@+8I-97A&mNic1w_g4VDN%RmJZT}s( zWqyXQ7{5x=c0d{^&X*bK#W=pnPh3jjsrtXzP6gr9bh6;JXosz<P#JcZYYqH@2=CjC z)Jjki)dJn~85F}(*k|P>IMclTi+p~@T#X+b(^S=#+N-XFB+n~`vVqPY&AL!ncpSCe zVxWBK_q`42px#iT?}F)%v8C0iRN1<LDeZT6Sro#%=Q#yB?8LG)x2JTNmG1#*FFc;z zVq<!zUtV2gWeV1PkcAC|(*=KVBeTuz9ASam6D6UKet#9Auk?-p&K&90*94E{G4!=1 zYG>V_GzBT_IDOke|NYdReaWRSllB{$7I(wv4tj&x$Y{@l_SatWMsoyk1Ckd1$q86Y zk<PBi_@b0`a6HL*5hL-`*gu9-e~RdphYLHY#giez!*QD?7r6hC{wS+FUEp(WJDOv= zla}$~sr>u;L$j+BE0{}HN|WS^6CRh0NTGFIIEeMrga{ZWB$)(ETY*llw0tH7zp4m| z|DSI9B!dmA^#9_+j?DbLN~70J85{Q+`JAfKI|Azb*c`PBDLEkq&7c%<7qhDEnXBpI z<gfj#|Ddr?Zp*Xmcal5Eu(%S(3#<u*?syEa62d24DoXd+0cypcjIbZs5Y;2<Hu@2F z04C<wdSRC09w^Mt=a1>I?2Ou*_XIsIT~N)cYi0+_lq$Lr;Tk$kcEFzrq_!n%Xi|7s zM?n;~)5HOJI5Ci6O@dU$tvJRqqh2_iE0%8&psvd~z3@N@-2Q$VT9KO$qKi?~8tVz{ zoQRN(5YxAzyL$H<v|0mKX(D=PFx$&l40PGf&Th%TN(wm~D99J@JGxI%Koo2za~NQt zI3;^?_j>mS#$Ce9bvhtnhkbL7JCm%iLwLi<#(qlw#JL-Hy12=yM)t6uY(P9Cyie-e z7P8PVu!C0e{`<#m)5Di6paFrIiw8GGY%%%n3qlw?QoN9F6X7e@?)Nckf)z+%QCk`d z^1eA{IYS;FRF;pE3nh5%M^-K4)(j#=*ki7hF*kA^sz-XL7U`s6f@J;QtHwxRR)x9j zv1YRcvsC7EHnw(xDEBW$9AXMphxO4@I{Ki!xKUnof6aI7Dg;SzB)~r1t)nj>k;ZcE zTc;iW?;JV|$5lvsLIu4<?TZ8p>|Oc_%G5^;RmtAv5gUl_d8|Y-Mh*u()fML&pAGxv z<AtvFU_%A2kzMISL3mg+OoO?i5uxem=z{h<r(~KU=ob`))aU1*8(9D`1=Ofi`woUR zL<6pJf3HA6x{@HdDZVdkyDI|y3AFH^!PvGEnWNb0>##n4@pXZkmo9kvg6ME0PwmkK zQB|1VYucB1aLXx+-L%Q)prq(-ahhT}FQ7g^Hoo(N%IR7n=WSjRE0D>fezn;yTSqVk zZ7f%gwp>W#|2_0uvDtuUVUWdUEmJf<gRxMtx&CD$@FAN{yQ(zkGFe(_MF*9fMki;F zO?C~OU~(n4f0qNqLGVEksX0=X=A>wv0gV&|Rg&1@b-04p_Vtz$Z9+I4N1;{Dnh9Im zJ0h5#*Rf)IGK537$N2h!>Gs6M6=m)29CR6;9z*R&ekkgG7gga{Y}P_no!@Zc57O7X zd^Y3z4zB#}LdH<1QfXUKu_ArRQVOyh&ci|enqYTMcgqAZ-~>U1d6LRu9P_nJJZUbq zLQ*MIdY^0h<ijiIF#Wr-3{j-h62C7SQZJYad_LRWD=dmTSn{v^qL8GUAFqS^CuP6H zvhfYA6+7uDqTS}UY??TL?s-D*c#FOLB>TfEzgupMKlyl+$Ziq=Hzv8B%I%1dCI4hk zlwF+n+jT6IVS04P)cL(RK(IL}{P)5<;gKsIv%tj54)9s2vsXV0P}DjrV4rZjyRE?W zYaAGA-_wwxSDm21#d#D~{E7Jw>dLf=29(*s=MWsHQ$bqVL?`5;C`@FvkE~n!JR;jk zw;RMnV(&^#Rbh@+a$#f&t%+Z?WE75?VK-(F2CmBq6d{(M0Hm@@vkj|Wd-j5^zN~UA zYyB_&BKIsAMp1b$0wxdBRBbB5?AX;+3aGJ_Y>=3><i1kYm!&YqC<(5$u}{k)%RiP6 z$-5ZuZzK8>xde%jAbF*`uM(hw0Dx~uz_@01c`Ve-o11#zV}5FT<p|g^)@QK!ZuTaP z2Z;1L%3f+6&R(`DDMg30ls2t=Py~%>DkY`pbj96F_-RPnb?7;v*r*Lmg?%&N^j)J8 zT+YUX0VN(W8BwfZEV_U!xYlUXJk|$yCN`yC8G5neEubg^Y`YU*6?>7(Tq!op>w&mX zJah@kYRg~`6CbheZO$46vzj2tz9gF5p_KYGWo&~=MNv$=!RH+QcAu%HP>Z&9v>eN$ z&hYsfNv+CqdPwdD>VOiGoj(1fK|Ge-M^;+*0W`3}HU}E$M$5noVn{q3j}qWe5vNmw z+!40|4s>cV#%k##Q<@7OPqV~4!I=wLl@4v1Jgq!WSrM4?cXbf^7&KUc0!5VpG<`K- zGE5zErC~)?qhHvy1aF!N@-7Y|2ca4G|Ec$Ell<&^z*CVpKT2{)1&m^Yw#B+pv5XhN zHWcyMp0HL$w`NM*REBU^!;@v2Wti-K%8pHMs7BgZ!2No}2VgT@Sh?mS%5sBu=x(WP zf_g4~QygtwwX(K)ct8rUik)<c@elcN1Dssv{+q4Gdi$7F*!ykem{Fx|S!VzLS(ciY zX%Irju~#|j^<DYa($vsqQ=s5dm4s^Nt6cc!yh1)KqaJJY91=cb3PPUm^&C$yix0cP zTgyVVmBfx}3MBMhPd-92XLrB2*S`^0n~w*Sb;mWk!#W{LDp9%&=m`g#xsKAhs!z51 znVX>Z#Jqtf{Cy1uj_skFJX-P+%U60J<oPd%nrOi_#XaNCDL_6NIX2k}<?z0oc(xGd z2y?)kpRlQf2oz$nq3o+Gpq19`Tz<)fRnGOru5PWIsQU@yv-XP0-YF7fk&I^*UZQ$h zP~Ur)Mn#4*E{tOq?J$=}4=EqOANR%TMLLLRA`vgl@otZ=Y9DaX2rtMy7vEc@iOP~9 zi>wTkPKT}Er5qj#FdGJ*{~2%YP>{z_*-#cyxU9;UX?d@N<!gq7Th6m@Gt3GD6C$iM zVZu!U<2274@1-eWh!N9y2gV*|v}Br3XAt&Z6Gbhx1ll8FDXoDCNoqRA6Ntqh)tRhz zBs(jQre<=<B)qD38+7CPJ{rJ`@UpAAp8>-j;>rC07T~1{xtBs?zGO#gJ2%+J=Y?tX z)Rj|l=Ub<g)6#6J0ilZc#Z;xY^gUwQ5g<>+d-f#4=V07e;J4f_V+MikhKN>FGg3br z!`MWZ%4|ftP;;NazyVKt3fKmLQKQ~65lEXZp-Ku7TF2WlF4BD|GZ^U4JjQ<`1fxT| zcrb+LVHbnI+nGp&S+YL;=B<Aw01#Rr9K_shUUu2K!p(&RyVAPQf9A7Q$#RcrvX&zc z4F&54<b5=a&uN!TPFc!X&3I+lNFbi7zyx`{Hd5-6lNd#$&N@~FnFVZ5(`O%YfYC|o zxqKYih^nYp!QROOAh1Tv{J-1|{oCAHs~Mm6FuVQo*ko$9eH`6&EyXtpmG)5d)w%)% zM5zD(Mzuz7IPbYu^P4!<W%(c?@2Xm+A`n-(*L5EELangiTHVj87wM+cOHbq1Fbrez zp+|6o9MvOIQ2O`NZ?4)CCreL2a?a}W=tYcF+ePrL<n=BE;gpe&Fu(Cj$u4a;kG-31 zQwkvvQ?}~&8fkpX%{7gI#7c|DD`tAgroBRXn8)r%)RK6XiW8NL2WCB|**enOohDff z8%Jp0{oAWhUlI6Ep`oKbxbkBZMLkJrYVlFRG+GOKw%t)TW{L7Jq}_O=-{75<K|rfm zNm->QRqB#D_bd`$o&i+U8fYH<`sp7uJnBFMPi$%j@tcFKzDQ^Sn59+UPsZDAiigLC zW)$*OD4I1kNH@;;^;gwv_b4;0iRfPwcI<#6f;<WsPxW|Iy+(g(gsCnlGD>c*u@->F znHIMDRb{4lA~LnnXMsGm;OQV8b|KfFff#F~cu&GhWQ_z|7YY|nf#UA&6#5TWS$Aau zvJU^8xRqo0Bk#`wk~}#&xGSb95S6?U?@hH0XK}V`GG5??G9?X6Lfa>ABc>dix$d<r ze1EnX`Nclg=F+IvHWe@2xrWKvY_CQhK#oDjU-!(V`c0)I3g7h#x}>x;FrCr)kEgjk zUw-oX**u;ln59O>O?lNRSJHMb++gRC_jcmu79`6Mga=DEx*DNe+Z3N}Jw+XHTt^<0 zqf6yUn`-e9Y=qbWH8*TK;?eW`+)t7X=)@ZKI|)nx75z~Ayz1?|SI+HRca#O<*X~kM z-S{N{6*T&A%AAYE8NCJSM6EH<Nm_ZwEt7Av{{ht!nyxwpWlQHWoB~Mb(~Sm|OgDb< z!{=?|ii~|TkpEb6?NfB&kP}dyeVR%yarT8EWe%Q11%Y`qGdb@leV(IwatMjxVQYWn zmRkG1si5jq^Uz?j{-NnGG5n#2SUKAZ>wSq1LLGc0)_KZrS>j~Z>$5;4U>@SU8)0-o zB$5rVwlWEAS>Xbg&}*LRPvYUiL5_+HxJ0qMCS(Z`5({crTkH{O@BxnsKh>GY2~pO! z7i4rPjw_#?IHqXPe=t9L{UbgMMVrpu=9c+=sDvn0){!H^VgnF4BC5X*R>~^3B{kC@ z@nS<C)xjy~KYWY>YeV(bBhhR#Hkbf?G#C1iBsxbQH=nv<vHV@(Vhc=~8`;w`I~84( z-O#m3_WQ3m9n7ul0~<!wtvS!FCzOkUV*UcQ3W#Ie1)AmA|7HPZCXHfOuu3GX?+BBM zeC5eb5FE8A(tzh70j?PYCT52B_jRZ-uGcPt(qa_Vix-ihA-U3FRGfN}NxgNu8E?;; zKLu*l(~TJA=s9hq_xM=|QXT@-9&ztXW|^PRfrHOABl$FT5K4x=vlflASq9XFZsoSN zKiNGTOg>$#h4nO>$omK(4-DiU4&n5=vE6J(Wa`14K0)SWDj>E_-d*+H91sD*;78># z41q);e@z&h>8g9hwlx~D`b$AWVUrMkNI<F=kJuY?f+_uG{QOVG&spk$!JSsp(@qfH zI~9|$Ckr$?uHLWbT&@7$Q#-(=r@4$73B}{+Z%)QljDpIDTwFI1Q;Lu?*mXr)%Xn~) zHLRWiu>b2pWtHlx{`hpcCz1y14(TB7T3Fj?DSo2jfzt8@5G>O?cL=LfDdYn{7W1kN zal-0u9(r&PnwhV9O3Ptcm&2fP8Y1gau^~PMY;wWGcg6#-N-n@;!+SktseUWp^=Fo@ z+S5nO>F!qSBGKZFbdHb(k;%N6-kud*lD0%|V#e}CGq(vNs!}E!8X}nn$uR5XZ}q*~ zYuPamq~!okL$VK}VF=+7eB9k@uW@JyP4weXdp_IFZk84MSTbs+c8t0rQMQkX0Ag+A z?90;=1b*}oaWIn6wx<y~tEOc0VBdbRGNV|Z-D$l&FUwdJ5ZgQ~?N&bG^`{6~IH2)< zAvI$1<^6bJUC!yKmLH=oLOFBEZD0vI<7?#37XR&8h@KpU%=;o)Nv(PK-BOlO)JV^q ztBb|C2mDV&ztRJ<X|=TyeT;&q_7xkQ*?0F)!(e*q#m7*U9sI}!OKu^Rc?G$3*uZY+ z)cw`ZpU7B!At)rry4m1G3`$nLa|F6dl8cTMJ>)kss_dsUT#q-e7MQSC)cJ5(?MlOM z&#y@G%I@=k*RG`Nv%nmmX?QklcxASS&8q#fP+8V-7j{gIX=+#J^6zT0qLcGjvCJ7+ z@+)8Bm$&1k2TlU_avBG!I}Ov3-YB%lT`rWJ`#IrP7s$q^rHw(UcOZJyp|6yu8U};L zx7_L_n(*&%Z#-&MQg6gp9Yf>_b;P{~X86eT;}g2*JY0P=U0sO3&PiYt_L8dR{oUup z6jk?#?Y^>GremTJ8kDRBZq%Ly^BSPvWT1uonjOoNlYIZ3`K)?^@;3EC3NI!6G)Ef1 z072Z(dsEuYFUsNl7dyB@ODK{}pluMnm&TvwY|C4eMe*t476>{!t!A*@rHU$LA8}~W z2P6ajKz{!%<~1JNiKakcF*egsQ<U75w3dGxmd#jX;33s&YJzK3qlr^M<xe+MdY`G@ z##YTF_EnK}4*0K)rKR|j><B^Ns%08E03CvP<LOt#!uORVf>{WM^9Ek&FeR7(?Py1~ z;D9Up#6@B`6!;oXQDpMBUzZomI4O@qpMOcv+y`Nokzse|w7XwVE8{aGyyQ59+i+$v zbp<G4cm4}K8Zu`MFNpEJu8Xwu|FrGqi9TK>8Fy#pIo-;1FO!tubXUd%5$%BddoUuK z)Cg66zgEQf;ItJh22Ybgm59i|LoqFR##3T;4c&)%(I}ZcXTfw;KvlM!OCvA!Xe`<! zv3oWC!^gn*6HRs%Sw(-dF~l*nNXdX%%2l&GoA5;P*h$S+U`cH{LMkd;a4P#uAEbqR z0{$#Elff`7z_fW3>-;o@XPNxf$oIc|kGp-7N=mk##Gb&UQgvIcq$2!&jn;Y7uh~JG zsRq8CVZ%InI%n27OQ9u3jw1EJX|W-NyjjQS1qWX>KE=1|mUzqMhR-+3#LB9+|C8IC z?DT~E{yYFz_h-&scQ-uY^GplzP;K@RS4#gC?jql02PwCu-9jGz29u3f)kg^7%hqKM zb{o=MVhE6kiJ*d|{V9)GLAxSNZ#DKG;Bu5-&-Rhb3se3?9t`()HLlS*uw|RLgI(3Z z4u&E8<$b!oiILYmO$`9Z1g&{+ctk-IM>|bM2r9;kS<&7w9v`(?55<LMt*JE9=AC=L z6)2ugp#%~%A;^=Y1sa)e%a!6u<?CPM+qY>b#~~&pSWVqkAnKD9e&5WvOTxF7PWexY z`x@uOo5s#7`yKguh#lWn4aahNHr12~R{v>fe3ym+T<_TBjbFW9gI4JF@B)*#_L!+= zc+S90j`+4H8;rhw&HBiMXOj}e&xccw$10Rd-2jxu=C|ob`Z&GcT6e$Dp*B1!W>CiK zIVNt=L7s%7z4r%8>k4uk&~flAdE1_0ZR0V`7=6k<n+`AX%HKSz4U_JDdnMkxaU2{S z^t6%qAb;~ZvZLD*o2k-T<hN1YLeuhZniB%P&rx0ieaPNOWnpmgF1=^+2qhHN0jA%N zS&P{|*Nk4`&Cmt}BGXCi31g&G8ui@iY*T7XQrT)zJ6M#vEMtp&rmDay-qK5cF_sbI z{d|`t!S~NLZSM$-c1m*<<TLv$(C*^YWQ&+?YB`O+WT?dR%EpWuksb`sp2M?2jOQj9 zi@<j^kTa1~Ctq1f$bedAc>ldK$F9QF_sb{rOGx_=?Fu%gf7dFYvzlIP7Lo{O2|WCB zHXDdP;-p4!JfZaxjsHS)x2VtP8mK)39Z;1$vJM*BTAZePp|{wmjQ-jJ<aL|_`d(a> zj|@)5<tt5l^^dQ+OyKH`Y?I(e`;^9Y&fo0#uuk=VEhLgAk|5td6*+hOm1irUj_+-} zs#)l65H6=ADaF5>{)`;wcNJ)0+4b299(kzb1VMOlTmxh(T#iUwQ)U-upTwv%paSh1 zj#shd#4rUW)>k@1+DVF8xu{hZD@f!#nJaQyFkhI~WraZCn^tvY;5Av-Va9ZL3`!PS zznR_<Fh$drpl;QnL~1sP$3j+OUN#+u`-CD_X;l68<9XCfU*rblv?V~e@4+#<>bbql zG$=$)*JJA;vOWBv5cKrU%J?+&%)vXu0QG>!DdUemgDKn6xI%;=CU>V_bv@^FO?Do& zu5z=`9+E=g6gtQyi&s24Ni2@!4lzV2N=l&BlX)bw1GVdQ3OMaFPJi6$w}|aXvW=^K zY<V#a9|GW0!fk)aBpO=FV_-9v{B1RAQG}Y>Iyfc94S&IdWJmil%~QV{rX<GOq$oR2 zUBu)M!Zu{fh8tS(5KwJ5$kWE+uWAwXXKCoyQx<x>K^<iV#7m@BF3V*DtV_iB*E3J= z=Twj<n5>GDmi-UesmI=}CCruz0AY}v3mepIy9gJ&{4i4J_7(>ST*Pu&4gE@&E?BE0 zo1FWKueBD@7P~@=H6ezCMV_A(KY5UgsWu1GyI_8QW?JKBn7Wm^Xtkv$mhOt9h;!De zFhbNDzM)EnEk|5L{JCjTJE;aIdo17ySpY-i1#5I2Yn7k=Dcw89wwta;6tQ{7n=rc) zDk(&QD8%1LcjUk=Q4Pte)7LwEo)gJI?H3zyP_OX6s>KX{37jHVO$tqq4PT1LUn4%q zf#BDv)G3Zpq*G4>*8$g|l<s2TPR;dRMN^~ZvMx#l5RWv0dHDwK`0AMJKV(q@E%>Z3 zn=qa6J|==ADK)8y1xB`LmzyvzAQfxUrgx0Jx*BFr_5{j|#A-^eu6%K`QMhF_7d=1D zr?AU-?nwSh6;wuwF;l(t?qz{dX2bN#ae7c{Km&W%6X76)BlW1;anF@v%dKV4^mT>w zY2{y6P8XeDkpu`>(#irjE2gbF1WuWN;^`1JWJr}BZ1cRF@ZJq;U1{u{kxwqA$&Ct@ zj@3j>@rxl>e&GAPSGtt{&35=X2M3v5i)Gu5lW7j`_JbnAOMEdmDpd*;K@5~up+LzA z``yfK6s6O~MJ=f<5*fC%mbQXtdXa2@sJ%%JnIqklx&|BFh~JOM02J&kMicmP*jQyC z>&M1>hM-)^Q?257gtTo6sg32GQYJ2eRO0?ddJL_)`XpExW>8>W-zp(NOwH;Ol;oGQ zTFgO-p&wv|`<^ga8`dZb_$6|C>i(=XZ_#!*8zT+XN|A~GEl^md@)|v)cv(&{ysYX` z0I(7C@@BNnsop63{CV|uK8+DdZ;O2KCbHtv58*XAa_L0}EB*M<ObYQ#s>yaOD0OzL zWWyLziQYFfLIAr5lmOB^ene4UlwI1?A4g;!Tra)rNm0;gGUrG|LF0*bC>b`zTpW^Z z*=bBP$M#`tI{0&|{i4_p^(TV}1$w}WwhvVyn*DiNB(WtwYiNL)pb;L8hwE)Lj27=7 zBa}J`X8g2OXzcj$&%t#wqn4$J{5vy9i!0v_dSLW&lgT$SNYN#g-{WTyLtLrrLQ|_; z`0i(MS&_2dL#D}NU6UQ>vMV#>za;ekKP;g)J2eswNYal^`p`PPH}|izfM#DuB_@(v z_|TTx+BGcvO*j~c3(>->H7zJS!YNp(Ut1ZV9%#S`aM*Y;*Y){sB5d*ywTR~jNF-Ky z>+e|RvEe}LB_dV;<+IJ~cjVtJ5-!ptS1DL?zJsx5<77Hul-@GCIY7oth$0fU#>fiq zg*p8X4uYLW+MpXMtBhs_eT48JtYGU53E?1|0J&|TR&+UI>?l=#RmaAGSYGnr(36O8 z?kpC%#WRO)oRd}9yk#M{E^U;EE<2cr!CkZAk~`=fHNPpzyojPmHI;`_B%a3c^Dlji zEaZBsIMPN$KFXbM<`#1XhA76Z$HC-KN9cLHEmW1d*o;n?jijIk*oP__9JwoPu$^|T zFqJ*$8^y?FS>}h2v<he~hmPA@{_(<jW<Q)#_R&&b)K}!IIR==dbJN$17>EUA$RL#V zSy*3{iO`eQg7uCg6M%P}#cw)d?i{}~tVWs}VWK?FaiJXJAdwr^9>GRZepAczc7KIJ z-hYoWaL2o79fh;5sc66CM$uxx3O}pys+HbA?`6E@Cbf^fbdKMm{czF-lYUsgq4_!A z>9&ms7ay=dkMViIJNMFF4jn_5B9CC8SRg%Y#-zk07qo_n7DTSo@&-tU$SI&Gyl!U? zbU)YNptS5spj=d`^nqaEeM>5Ib5>3A?uuQwKWbsJ%=SGX^XnhXk5sizb6KSLuca;$ zR5|mvW9xOBij107b@ePKc=xsp%#O@RVzqqcQ1s%X$DJyqLre=DIu9W6UTA+K406T) zdeMZsWyv8+r&%iRj|KbSv;oR)p0+*=Es(|C1SR-QEi+@i;{=0&+g0I+Oj9585W{3k zcU3b?^Dy~#AIQ8N(^I7p1#Y-9(p?V7N?tn4h&Kk=ISzpegTK{=HL@S#yHk-~?C#>M z@E3=7AXO`!vIDNWiPbdP;`0^1eV>Ji;6gUslnqlKBnt$FfboBZX2A2`+X37~a-Q}K zM=h)$)R+9LU059vZ&DGhh_n6y&1dlFh!LNqLo-KqmGdlt|H7n0z%82ULW91-aX0Du ziEo$%K4on2rn=iFMx5E{uD)0U@A`h`fPpnnf*_zi7ZY~<9D~zpNT8l~XN>${pNFg6 zf3cJdrf4(c`Lvw?eQ(B?96T#t2#;I}Vsj5C2wfOg4nR2&mHonFqEJF%7IoWn?4w+# z7lbXMk94w?6twJG>MLTI&<YVoB6&fj{w0b#rd7XA1c41}uk^ICma8_cw+eVcI-Q#g zWUgo<V;$$|zKF;+pWyjPjyc8s4(+IGF+c~ZQE=lj?-dRuCY0YYg7FOaLdfqjzT<RB z9eCwkQXs3*S{*mB&a*bl!t#dzj5P|zfM&b#L~yBEakPb<&)b4v&p>|aD%1TNzZve* zhO6dEBSHXpysg&p&`g~B#3^h^HJ8VB7R|#Y;6b+)Wph1<IhsP93X`DCB=I$B$TmTN zr}LOP)T}#;HN&zpcn1%4?hMYMlx^%<U1P212EhWw@BJ&idZN*ue=+GABVPCa$LaW{ zr1A};Q)G{8-eODkBvPBb@@MZFrg{F3eWvrV1v}m4D7fA7kIIV!eY*4-a=&S_(0(Cn zb}*h->92_rfRso2WwlR&6jrdiA5o;^w*ET?g4F(T?MwkY1cB`gU?j!)#Tgz|+RC+k z^KKCQ=d6^5n*CPIJr&uw3D!78zf*G>=t?<2nm5uu8$lH`87r;*50Pr)Qj0;@us$SS z$rc$WCamVUoN~VK{J05a-;&XD<E`lQ{spKh#(0FCG{4?G`aI@2_pNLwnZdR8+QEz| zZ8Q8j2I5)_d<cdWBH^l#cpy&t>?@$xFML#qF#8S`>~XuvnUcJT1re-3gYp3&vzoW~ z0|gs8^<wT$Icvz`3lwCSxPQ{?i0|Qy-17o)t_xJamy?g9`2C^>M=au<2khtqj@PNN z^{5S%^P-kU!&FzE(p>sveGijL2YSZ6cJ^teK7r3yP1r&t7s-z6@~;ag%$Ozg;|V<i zJT`cK*Az7Hn^TAT_Z9WLu%<NIcC1)K`T2-rL5AvaCIG8~mx)AfoueO=1U{qL*tFi% zGFEJTww>?*HK#Auue*2ux&bs(KfY?Xn!I}2#glDVMlKj&ya|;dCCsaWeakq)WRQJV zR<Bt)SR1>b%C|kNz`3X*#pl{k=%^zJM|>(`1%Oo*WJh;?&zwLCmDa&oy`OS8=&KmU zhAnO~^x;9QL%|><$GUb-Ffa|#P+z6k<AGivAZ3{UsDm^IRXkx(Yo*;}LV#+tJgIdv zYJ@CmbFa4Kj=gN1$G%L^m{j4n!4|UNcw2liD*eLg%etRc!=5IpX@#7Ysfq2y3y_Fo z97dxMO4}QjS3z5rS6W&D26^N-xcbtzn`V(5?&3A2{OS$F=cYu|p$yZ{YUS$n(3hw* zEfhivw`Zr@j?4SQLqUPnQ7+aT{nv9IiCTF+8{3R5(?`^9!%JShSJy*uk-_42kco;O zYqFtBGE)osf@2ntGsvKo+QQoP+!t6ks28p95Lk~j7{2Ouaz4J^W{ATFFpA7t`Uks* zemtWRW1@;g&}%^Q+La+1ZtIe&?}21NeR61Ld23x+;Bu;EYM&e*AOP7bYqx8{Fy1?M zn9gxa_8Sz!i;Uefw}#ba+%>1IeqOAJWG!kb0>@tWC8wi(*gQ2?(fI%dK~vBOv6;+8 z@}5z(31CcN3m;}C2=%;KGJv@n8py+VIr9W#+Euuw6%y&Bn-kdQo)DYeKTfik`>7Y! zRPsV(XQW53;TO&%E0Z;RKnu%r&%?wOeq)u=SiZ_b#?FL|H_{hOgm>N66;C%J8Y;%j ze9Ll>B3;$O2IZZvxou{CHEN$I*SFTDkLe<C4;F9mE{3|1+zUFSD>uy@U>c0KOSJ$R zT%I+HKp$cnrOZ~o&;+e9#>~jm^wv^VLz$ouAdYBkZ^A#gv{d*<{8Rg+a6fR5Fp~m~ z)etOqrzmKic>0MQcX7cj)Z9zjruOUv8suOY?n)v!5+&P)-~Z$?3vEY%2QAd~IC*`; z@UGGO+JFpKvc4U50Nz{#Mew#YEbh}{ERyCsfV|d^taiD*EZA?v&D@Ih`|ss?&$u@q z;ZeZ=T#|$gz>vZ+DSB=8S;*vA6#*HbJ2K%_NdacEpA1Qo&ajWz>f1?h%VB%P%Oi+J zhG%P-I&<}H(@=pAz37P-+b@hLtYX3ZW`!M5>$kSM|99q6mg>E8i&g8-JPOJfAo=&% zXKNzx>qI|0S;j>%dUtB#iFCleW*X8B3vva(6)rNbM(WfsXV)HsDtju4qu-3K3)<Q^ zy~JK^5wUk3$eWd_V$sOfuq&!?%K+lgKh8rc!Xs=@J7iHR2HS4C<p-;3Y%Hd3Cwc5= zG6mSbzA1;sBZt+Z4pz!r?F0_HFnM`;9!<~++O}+7mD{?Q(;ANK>zA;df}?BMNl>Er zyc_t|-#`m_@~`~`aQv@ImGQ14g*IA%-Jo(J`i{a`G)fVD{_Jm1BW(CWy~(2>&`QI^ zt6YE7P1A?!*suK6D?;g|GFmtmBDg9RWu(KjW$*ESHxYLBM1qzBHYIt98OEI%3HB~J zye{}~BGG;?<psfNk$m2!kDe7uP;)`x6U?;52ooO7<KE2UxngF&il=POV=${1C9`TU zkSI5}rYG>FIgt&pd}MM8Z^e9AJ_@jknn?yh-&MFk$0Vfh-JuzMoYcekro?P{X>uI1 zjDAgkTmt=kcYOh^;QT{#oureu%T#jZD_TrVbELS&iWVa6V(kO%RUQPW8%gNyA4R>L zYzq5yBNa-7mfq<CSA)VG=11O?XR7QNB59A>uY$|#Me=9Zp9;S73jD6MJ0-IQ9{CTT zsjU#jb;p`I<NV(&0KZM|aVq=AHKOJvXo4GP1lmmqBh9$VcU57}Z{C!RtuVmre?rdX z$sLr=F$*#aGm)p3RemgCM=kN|ZFN~^%t>+o4%>FnyM&A3phb}9A->gLguNODa|!uY zI<H_`yO|7$)79YY>nP?eDZpJhJD#{f6MgGlxN_-*low4qhsZ3Ygd$Oao%&|C9)lV2 zrxl_c;TK6Yi0F)x#v|zR5vb*LCu}$6P-jL*zRg8-V=cLu_G!&1N9J*N%2c>lo(M*N zBFL98tgWDi1Fn`$OFfHttw^lN%^1yoKEtM9VBuf8NFi1^E(2zlexHRa@@z`$Tn*7g zyV>-Al6L#GG~NVX{2+>4|GDU<g4{dkqY^v1Cq|RJBn{%XD>OiGt{uae8=g97|Be4E zH&y#hk%@+I#dt-ct&B|gBRa$4sGRTr{dVj}56@)KOqX5z&CD7g9%nygnJWFnUjKfu zCZL(8$pj-C4q2cgCFIf1L}LGHSYPO)0lB0!$jPQo6~@xsRepMu*J`^l+)Rg}(Q|U8 z;%E9R8@hNl4Gn|0H-43T5IDkigVW(WGb?HR`4@xPM}n)rHG8ROfL(|Anq)u*NO05+ zKt5tbbN!RoN#3{L!k79d9Wc0aiaW+SL;taWx0qh_?b=JmtWa@7W-nKiO%~uUZ|wy5 zqKC0eCe;g>6hlBb0L|%L0jSv{zAPD&N$!*;5!p3Ood%;nn)mw|v1?ZJn==(%(rGbo z%<(X$O5{m@u%ezH@p%NJrM-(l3Y87dN*M=oECsrof;2W~x;*2x2|(2uMFtj<kl@Vi zaBOef5Fj1IxcU&fz~p|0eN)XrwP3%s5tOe@pa}7%T*pd?Jb$A^SHSojcpyM<hyC5t z<*wEW)}G$zA4aDT?VWmZeR_EMOIEk>9tl$mrF888!limCY_FjJvi%bTJ>57LtuwZY z<xr7rVY6mxA02~XkD&RC5BjwVU@&@SX=_W)o4~6D3(%V`f-uHEl2|8CA(Ssv$>z?` z*db7WN~zpBYw#v2zkWhpctB;&5o|ZpvqP5_ifeuWBd<{}e}iA=QngExFkNc&YitsI zKq!mxb9_|n(U#Q+?%&pXf_!h_(^JCGM=_+LfOf>BTBN~&z!t`~ml?Y*nwa5$fwOYj z&9ycplgT3UK09s^Vz~6E;Rr>@92G$rrml{v-JzB!mNN$S$egT%mBy2Sg)-D4r$`0` zB_OYkj_lY4w;Vooc#GP|E;tsP_!xWs0{d@G3#iA;MX*JAGNMEWas^y2?&U^$Ar0*W zhwdJD=XE%;;I~r&kz&P&&b-38fnu?RY`X?bXz~-<^$os4ld%Vx_emV>8uwPzIVDO& zh~Q_0Vx_{@t6*gtcuDp@9K1w524RagC1l)$C`;nDOB|yfei`*_qqn$-v0a&To~tu$ zLl2gq3x%XkyU$_l#@v&_qKTS8!+L+1m*<GOCEa0?wb}&9@1wxul$QO?CL+C(w9u#d zg$xPc-r9lAEeq4!ST11g>Y4fSdVg@iNWSzE`dD#M-=mkS_P3EgWV)y#s)x@eu~m!4 znNLUr8)jWm(We8-Z~36#>p@~;Ej99&s*8+s06xa#JQ%&~3Q<V2;FYJb?AU68g0lU< zue!KG4@J@Il|}8Yh<fK81O$tpr-!?r@Bu<}<|_-flTc*dT~FYZ9~Or#AYzKeJItQ1 zz@gbwUNHTly)#P+yQlNzTo^#&h%ov}ETOnyulS;>V_uRzs@zwNo_;VzVq0&laxMtj zad7<Y<PRqcHR!30<-=0V*Qky?$90{D^hVJgR8vHFi)z~Y(W(|=52>f?dLlRNFgzoc z#>l->QhZiBbbt^BT=}&M37TFmVfXS`%zJ<@t+brXdrWNo&JB#bq8&d&A0EhP+~&uM z>;mMm3oW)t6N{y?da$epb>MG^J4(~FLCmtr$ZI%fQ5QcDDu*z0Te@fJ8zm|_xrSG# zq|Px<;8gQ{ITWRGP2xuLs(x_aak&A+T$`5UVKxuHtTbotLcgX^=9aPA?JEJ}=OHK$ zg})T>7C;kVqLBt<Vbmy#k;w&O7nnMe5fk>oYkoFRtf^FJwA6TA(20477;FN{<Yy{> zLt@IGw63q7cc}fg5!~^y>?R_6FVqDk@=F%4X~k`#_@o#ln`?!=2p>mUbr^9mY?x`T zX~xM5Fy1*5`gWnb*TDZZA3kAl?$Gv)%z{#Mb696@eB?9BRJt4H3Dlv7{-JH7t6A=@ ze-WN{ra=^+KfXr((v*%E6SQC=#sA9U1Ld~^K`lweHnoVshE;^BIcXPujs?D}<@h2P zJrRDeNbR07eLP{zQKgWoF@to25*<RN%V0&6{Q;z$P<$Z@4xVv3S*XG-#;--pL(T7} zY<9$f$JrWJW*!`sk0pZS>jl(7!U;SD!YQqpRz9%&Hl~0jYU=qjTw#&!azhPPLGXZE zj!{gS^&GQ3Q<XkTNB`6R2=g<M)}R+#Tlj%%0WSwfvG<JK{;tX3ol#P&B%sWUNxzb? z_{MSiZHk8ZR~9=3LjPT@LWzX4z?%2UtNIK#Zvq*B_y9QWlH<it+nUn#tl-4<wy&S< zM@kiOx{Zm?@eI;fK#S{Tl^_8~YGp%jnzl>6(iVeq3JKcw+DJly8hXYy^{Q+g`@!yW z_0;luG}s5N$lW<b#sgr4fv>-R^t}IOfm>!^fXO+Rcw%Icd!^OrSDM#&ioP3^Qeg9P zLj|9r^~!m=iSg^g<E$XrBZl>)J{!MQ!9*Kz2sa@ZD?J3ZF^?|bhhb&c%tCCZf3lIj zT4-*ze*XXJ+oyWn=utvFTbLIw%*D9oGW`c92|LRb?L$Bjjl49uu)XFUEKIH0R~NA0 zLbRa11eWYC%JaV+pvb4H?kxcwp}3N2ej7u46672nues+!`ZJnh2Am^rMjz(aUVp>> zcpZe}DKV)4b(QG-?s}pnWIuv#-!%ZuAh{w@m6AX6oLW~F%8S{V+J<E7>K!(7UkjzL z|Fr2z)EhY2F$#67n#@`sG1X{foV~=fyb|nhoTX(QP1YI0nxAp;U}-(s6z;^?{ZGmk zI2gkQu_S6VUNdg|<P(*5h((a_sh2cbv%`_6z-5}lK~pPVrkf4Y;O5k_Gpa6mUa3l* z$t>Q!wTBbH1%ycS3Y$=MbtrlD3<wEPIto(hUY~nud@dAK0yZ;H9GOMBnw0Sd%Ak-f za(wY|DPj^PL?4G{$zB7|t3IAVE0p#~qgD3MO{!LnH8+ak^n=Q{H)UAV&J+r@n(0ma z(S%P^p(u~?BViw}DE$ih*@<UcS9^&$KPx<a@;X9J1sAruf^aAu-1}VUx5Ft=d1L>? zUg%-wL_@n%kJhYH8nbbQY9pYRwIs(R?NK2ZB<@lJP_LMbvg$%Z;rQLJFL`4s_eQeP zJ3IGgp}T}X_SfhUEZM!I3oOke<|outh|G7mPC20X5Jn2kwY_8&nF`hBT~^QSUhK#v zRA@B%`3o=BozWV!d>u2aB-^N+qZpr0@RghQr6}GG>CUHw>#R(>;v^=stQa+nCibfs zJt)DE$WGX*V;9JrvnZ5XLS1mGjVtg^N`lA=h1oopSKLi4AHeZm`N(oHEu9xzXns); z7C9k7zsD4UjT22zWlW!5@zIMj0;yGIzlAcdtT9lX8Ud=Xex=#l!Kim<YVDpS{(;<( zBVyQ1Rq*JL<hAe9FRWF0;yT^P3--R>cT+oP`3`wzx~GbK(63SczzU3_ya7cE1j`|4 zoznLx*v+Kj82MDE^QSDhoP?9#>t%^$-WbCJd45Z+FnN<et_uc$o}e+9e<5s*|I6hB z-A}v$H}pvHr@^3B7@)?KN-eg$eE~AiPtS*tx>N{`4t^EDuR<~j`-M?ZyP{YgJCisQ zcX^L$xSjKz^Q-ccSf0!a(BQ4QvBb+xMA7e)XT2QiIDsl+*U*gj>pB>;<)wX%?_KME zRg`%_+f}X*jOAP6bv29}#9|&{H)*u$fprExL#DAdh#ay&l|b<NfEj^cjCM{9;@hZF zi$yTm^?erNYLJBGyRT=<Ika!fe6M?JnMfl10MYmqd|a`=vRf{-JwI~nSX7$5qy$v< zpKRLG*mI(srWUt^u3W%9{&I{fWn)Q*;Z|iTVVdbHSL>PXGrLJ)m~JKbZ(@|A8q>ou z#B?*su)cU>IGufG57SPwjEKmEGIk3(*_J0^wcoQcURMp1cu*-mvr*VKPe`hhIpDt8 zbz4o=60Cr#3mA$J&R6xDQkmFS79;29ea8y>D{q+wPGxpwaAV1`UG)6LRJ=K6tB~lA z&caQN<F6zyOb#KAPI<3d%+LOmnX>GG0{p_YcC<i;sAlvNP=0BCo0>WGS8Hb<u%nX% zjH`3UvQVb<_H;Qrw@?>#koTFs6n(EYU(`Phhu-avRn>4)wY^bRy&#U37`_^?S(fas zXv1K-K;zeVJ%OXgI@~`J9oTX$5MY6ojbg_=__uU4c5$^U7KVVf%-i<J-sGQH71qt+ z;GL0NW{NTsv6=@CN3`IFJ8@DpyfyZ-a`f<fDoX}V9@}<f$vs%Z(YS?M>-x|Z(a+qX z`2{TT30s}MX0ZKNdNU|6K6Nn?nB&XZ%oH|?eyrkSolJ?9z4|eE-%8)cz-;miImD;( zZOJe?;}zRRhFJA2*D4zB83I`jhYC!JLnCACQnY1=y693K;m8pDe-Kf#wovC0N~>BO z_%YMMfT~QsR~F$3VDvKxh<ErlH~gguLGC?Y#J=IoUvfz>z=Bo4=M7_@?&h#p;62(q zUROt1MWJGMk1;%f+Abc-I;4L!AhTt;e-MtvBg6lIG2ZhxK1gD%-~=Y25%LJ}-9y2* z<orQj>N3EvZzy5l2W^I6URIUWOZp6&k{f%Coa_c3jC_as?i?V3aqhEwkx9djaIE;J z@|enYRY++HE8x&Lv|!iCzia=B^sc_p_J`^ZbHfxe$dk>HL=i^dSb+G%PH*!I!=I)J z1p{_|dEmrSN7w;pV#oVL#ep*Jhi0mgS3qW@!|7Kt1fq!gw+wNXmb=W5u%L2dKLilc z>hY%TO*fA3&k{RVy`Mw){UZ6by`GgEF$yXu>yo0WZ9H_l#M}yTT5IeLIk;N{AYbz= zj=g2@<^PTl2e7mHdwveAa}boBfKyAoTJ0%Vqh+-}{~eZpzl=7g7=-v&KgRhVope^% zuvWIkxoUVf=fO7Fm;?|~zrhtHuy#wpHLtavFqIZ36;Z7TaC@ki{<f5Q-FrE4k>Ppa z%k(#lOxetBtT?o^)f)j7U_r9U!pV?)rqt2L_yz3%>F8e#P-gCA7G?I<t5XE`LpZ|* zyg#hd>6*S!NtgJAC+TFy3G>Z}h1s+%HM5%8Pq7@fo)3|nnzr9lGEE`>^>ZD5fZiWp zrCWK}252UKI#@$;=hZ53aS3$I&ir9?Z7|73+G%cXt6M1XEbCq!?^pC~?ryTB!80P` zjY#kpi%dIA{<0KpRqBx5!giI5i<6(m&LeE7kF|EwUx!#@Fg&ARqH3Kg{J#n{FDt}e zPBk%h;BOUI%#hRN6JK4&nzRKfLu&hX%nza}NeA=MKuH~=of}rGXm*Dck2>^E?vT;B zn>rOj{ollYH~Q;aGusJ@ESiQ*crC5H4@DeC;|&Ho@Ri5>qh|>l(@)A#h2!Un<bx<( zltr*Zt5P@@v`mxKl6rptZf+VX4MgUQ_v{!lzfqvNzs8q@<(Tyx^E-b^#Z#d(KIb%A zJGg>Cm^T#|n5l3;iKkXbjCn0J(_h55W*n<(P8H<~WPD1hD>uVM*1wlO9CZv~N)_^y zBl}&k8Icz4b17Wx7M{x!maY3Zr5qm2>B$V&*9O*MhtB_pe@D&8GL1tuN*p*IEYmSX z9(-*}3_{VKU84lJf?yBDJvFOFAUc(AGrJ$Fq-{V%goB!mvd3uRAts1DPoc=m-Td$q z`?MtPW}YNoyRR{qS7=VQAAxr$8Ym6T123T<cVu6=Z-zU44p5%GeRBruQ+~HEFG|2H zA@xtJVA3!^LnQ1gO3NFch#BZHlMN5S{)yjDV|)6Q5BR{l`L8!SIIqJCqKPm5k1{ff z5nF4f$)F8gIboQ{-KG5Ih*xcBL0T`GJsVvfs>hbBJNW*)o!*zC|6!l#eTxU$>5!fa zvI{tG{h4^)A<bv;`-G%Ui%;F%(xg&J%1QeLD5MtHl&GBILe@7ES%(=eW(|9Pp;W5_ zME#&Y*f#lZmqYC47T;kd?18D-_NJ!BG>0U%FVR%<aUTwv{L?@hJNu%&$yQ5%`=%7# z4PC~<s0a~JB1X~aw=~p=?aK|t)P0)!JJdIDb{@Jj-{rwMG8;zR$Ra_QlD6P?wGQ7h zNrzP8b$I+U|7fnff!-8KZ814bB+UW|Oi(`r`;{RWP1Ys^exdD(l8Rx_?h%p`-TKx? zO@7b2^N3G>a)0ZLprE)VIWYGyqqG1tkL@iyOxP*>hD<A?<t|w0SN>CxR2x4;O3Sa^ z)RR?~(tj?t`7#6kSQ{h~*8m?t;J=Eae~|DKWCvXKZZOIZo~Q0WgwPqnsIAW46V-^y zI9W+Nwi0!hkfo*M+u?9W4|fjS{`KNjT*+3(h=(x&n3-52WzS0lz7;2$NXl<)BNQWb zA0&hFr~j%qU;CtQDRx=;bGwE{?gI%t?;y|%Nm^%tRX`fp1><yK&pJ|LyP-1Gjeoxb zr(@p6GfAPQO<SE0vdoW#HQoukNsu)(GDG7AUr&NYk*RmeLsUe7st)O!K$i3!IVii4 zOTUi!W)XXuwBOKE1ybRu54Ae;M4onIk@=Bs01+qu$m(wuXyB_|Gy-v6$8~E5xk{WL zaFspMqX4~ijJNa>5~f)f+SrH|1u&=>d2P7Q__RYdp9MZB6Mg{qyBC+wy2bv({NC9s z)rsTv#F-jb-FwugWQ$>z!19lx&#gUcHE)+t(b>`8V<*OPky9e1=>Yp!q(jYJ_OYlI z(>ab3iKT88(&YUr3vcKKe(cY<Jx7Ec94>emwzQ(X!zHKoF3P~Yq=;Ywi@@h)RboZ) z<A>t@{?0lSDFRE9Ba_Rr)r>|I&9d+&^*tV%eHh9$O(yg?cCjWa&vy*?=ENU7^)RE9 zCpU{SjbHDIpB3u8>Nyxv3a9_mg(%z*L)5dfJe+u%En!tq?zJ&@Pm;hA>nP}0X>1(0 zLZP3Qjo-xaqz9TICu1N4i+6?R!Rqoq)b$BcU3W*4h&p^6HWyBE(l{x~VBnouf!V1_ zmW<i{p*RP)pEGfn+d}Z$07*#k-Lhl)<InLF%U7?Z&gB<#^*HP2^^GBy9u4(4-AgPa zV0U`I-JPoBEE`qB`2z(Fx)s<sralqO`Axtb!e95lt_HQ4PkVY?{51i*ApRGx<o8G` z1Tjq&zdixvaBruaI8bK{S_)%vCYbG<MgyGIJy6vx#yMs9O%Cr|di_5U`f1)9uN=E@ z8Z?Ddti%MhR@6<{p|t$YV+te^YMGAgfQhc%lPjHvG;t2m^#X^*Fn$4u$}L196Mzp3 z4vCD&ZmQRhh^Ub%@!8<TU5&A0l1Z6eAPe5lFbHI&m6nL)N2ih~G>e~R$;O%;Uv<@P z8Hk_Sv7IDW+keAWfO4dXmzdF^M&Ei@_(){J<^Al=*I!_-QoO}LlQ~$3?VMQ(=5+rM zqy7ekQ3nC}8Kt*pN5`bMa+c7zVIEb>6sZl+S|*WfZx>B^S-Gq9tWmX7Ge57ab)}@~ z#ow)p+Js}8l;m>)-?DvF_!fVqtg85BHhr57SOB2?K38RVdv~`WvJ^-MM~kU(EMIiO zyRf^E4EF%9q|L2$@x-`rlZnSNgI*Lv%7nzT5kvivCsA|6f;?_a^A_*q3wF-ABjUMA z!+wqpX$J0@@JnbX*s|W8X#eDyaEag8{M%g_A$oE(d?!ddY++d4`Tf7AAm8-gIu-mo zEH4JgR^lwZuZ%y}IAzp#-R#LcVl=@uoLgJ%#`B}E@xTvqB$R3GW~UuI@*t@sJ88Uo zCFtf>RKI^H(sno`e7lYlN~(EZLn7Mhs`{#_bLA1L+pmBrY^|l*zVLXtY<q=ApHbU! z-l`Sfl`HZ2=hQ^Prsr-N7eC7g?GETUTQ_E!)WYv*g;!sMP2&!zx@8142o0qHysClf zLBQ?QT%cSmewQZ}ECJdG({wuWH6huX`8F~)X1GoYN_jgjs0=)U4u}!fYzY8xIBs8n zXVJ@<jK6Xfe;u~{2q035#1|xpVfrcnJ8BsJ9ZrKTN4Edi&f_V~e=0%=Tw&!8#ul(% z-2y}bpDyFM!6URflFk?rXUUd?%XP)NPrR1mqW?!nJS*Q2Wvl-By)=nhZPZz51~QI= zRfzD7$yfiG#N#=@NO&RRlU_6_B*pqPlpX>yT<yS6l70fcD*)qOVMG<eo$vacD-Sf( z;Jt*s>Lf{^1P_3&b9l#O*yniIzTbYWRI<H&m@Otdw?8oCb3Pf$|8KFvBh=njfO;7< z;W;2`+iuwjREreBZQtWgUfs&@NvTgjZ-?Ubh4^JW-!!BUHtSC&gvi6!i}_jTE=Xkh zH!?+zT88?Xv9Z?;>rTqjN|LJ%su(h?NIl)rF?Xj4Nv?2MiqOomKSZNSxGwJ)by^|; zA~>^;sX{}Z4^AoGa{^B<@Jt0ek_U89vNw#;o?~DGt-*Sx^)X)6@j6v3NQ$7Lx1?cK zJjv{K$PfIMoLxE1{YqSW1QRl@tg5-k-yDEkH@-+7N3{$uYESTg*xRT_MFRJc(|ZzZ zd(yzUJOEYH&k^O;gH@1{ZRN<K|8O6$FQEN_#t)`(dJMWX=t~^uWaeBz3*R0addaS8 zY&64P{eJney|gtU&tRt^H6DpoU5gEl8Lf^+&%{;vRqlR!+v(}k(%teKg&ZFFl$UBg zL$vCPRwt-gyv2oxLN&#GrAsrVLC`49IR-bSnsWt5JoXea6LAK#l_;KLk#M69S;g%| zRf*6Tg=Jj$2vtIjNe%pXB5^%=Ju1X_+K3%YJl~F^Y=QE6oOjPL8h<$dqrHu=BjASB z=f*7H_UZ$_(=rHkmcyTX2?5fBY2ILuuM_g!+LuA=z&uz>DKmR$<}@>HVW;|_47Z%w z-#FV%hztnA>BG$qRZrN@vg_IC#*LOY=J?1}i)R{=8z^eZ4Xri5mR+kp(PEPq13&Zp z7<iSOs^!oiBNL!wp_ef;Xo);P3Z_={L~gIn2CX;QR}3NG;}tdTCq2kbuwb%w(0@Pr zdTVIKy%YeF8uE?%m4CFC^NZqQV73|52vW(AON=#sEPG_Rf&WfWu@Ce!<fd3eZ+3Iv z(2)ph%&Dtbc|V6rgy6zt3zomusFZFeei4Of))>x_Lr~7w4ntx$t<#p&A<1PzQUc)v zHPvs%IZzBO*qYv7!_AeT?<LJhf-Ci@6g1=nA$RCxahxh5$%kko{Jipaddr+<DzEjz zflz-()*RN#amDVsCNk|Nb60W(!3_0^5#)`>r9D$)y=o&z?yyz-uR8?$0l;{|n+PSL z%)I;h3{lZ13fxyb52}Q8z8;MxO86&gp;1>xAya}*=xBn3+Nw~A*)@FTT3hykRZ(~# zf4~WfX4DN~*b5f$>6_4oI%VF~u>1e~EJdIXl@GYo&Zbqm_eGRz*A>RYI)rw5Np;fm zG8qUI7VyDRb=J{ST%C$w&{H~<u$sKkdakyy897>Pl<yUK{Sf}!<0aS9g+zc82mco? zwr5}VG*cB1^ix=Kk3`+@|EVP1Q68R<J@VhIWqBK515>wg&J@YxhLJq`mF*(wSSBeQ zMV+ILzy}=fCLYWK=S>UGT~;%qH(~=%ak2SAtArw%{Day__b)vi6f9YLuV${DsVnFf z86M82GlUk}V38aqX+fo1@-{IUivFkA2?!=cU_Zag!w1oWSs<Cp<P?`(m&IZChsv)9 z&TtRE=OF^?-p7MO@V%yQ(+9&&zkRRrHAm-f1e1D-JJ!b?jQor1_q!Tj$x8}jbn?2i z&kH3jo(Yl6v`C&fID$vbD%nyEpCfp9TNpjYN!ZxnM`&G3eb?fY%gVrBY%TS?GKt#| z2d#`Ec^<xI0##Z>(J%=(*dd<02DjALw4_ce;c@P7E!Dj^oQm2smkegG2avD;`M8#d z)e;QZFqY6df@(C*gWL@-U<Z7USZn%E6^wB-fOJDqq>EBG_ADyE{^)l*j~zg!V<6B9 zSN?j>r}%PCCRK{DXtyDMTo9aD*MtQ=t3kD*ZVmH6Fb>YQli>K6hVK&()yuagud_Eo ze>`0!Whj3!9G9KFU|(3F1!N4OHYTA|{&4-`rV)}*Vb7}l`GRty?p|-{WduvV{`Z6o z>p5x|tfEKM@mq<a%&B6DCkK!mAV+oLQfb;Q^*jW6K#kt*u#Q%MVK|E7jLi#tbeFAF zu;Q!(;tZ`V#f*qVWHl!Uud^6rD~(^r7BGw_g0fxsU>U`i>^LX`Q-$A_a%7VajpZ#b zckW|GEhXB2WJL_Bm7-=b6&c=}d!3dU)05^5t#{>ARgW?By&aKf(d3jVvIi&!Ft%}# zxN!X4wz!hoI>l@uxWn84+|H&{Q3xW!Ug|$t$oo711T4N5jw)h8qxrg3nOTo5dxkUq z3~je&<1+&{Cq+N#s*y#+XE};qDl-E!EC6@k<T}ah{xm&fk754J9*LlH@ZhBae^xh= zzeh5hZ1;qlB3V@U24xWLfxOY7HUX8DLX;Tct`O_Ma5bRYDkVz-zk(A?-)@bLPRY=P zShUVcxEOfE*O+gzPE%Sm^;;aWi^^&u64%a4_U`b#nbLZ|#oOski+P@QC;U0zd?HZ{ zzag<Ya6008oB<!v$tfQs`08VBKL9#Qn2|;84LB;UkXu_|ytzP64ZWqPm|Jc23}~A7 z<k(`v{2eGBWg!72n`!}ApWDJK!bF;FkDGdNw0ZA+AD6CD3hu*DGypFoh<FG-JSnZo zmwpMh!N~Hx0Lq4%b7Qr$=O{sfn;EuT3$A$Zh1QZQg}_@5moK&Ulk)d_GXF42<tg_# zfWhLS1^S+(GN#2UhH(l<C7tg6id_324%`IWm9ff;Y${c>BqfpU-EVUcYF&c3r#z?G zPkS_4Gr=A#Sv)N<EftO~jIht;Rb8oy<!k>bLAh?ybHtH3TVhbUejM{u24t5YjrzXt z5ifH0-<^YF+;->H)V?Q3I9oSoN+;Eo;U300d4?t8^~B>3GrHbT-SNT%VC@&n)wCfN z%v><%5u_{*9j7J)7=O9%xy$7kwB|I9f3Uwi2ejNiQ)kpBK$0w?HC5cpEFFR6Ut`B5 z_U$qSqf~4&aR`?L+dutU-=&S@WVX1!a=hF`LFD**DDUn2^%;T)+HE>77p~g`Q>=lu z^}ojW=GV^CVnP;3dbYH1F+<Z!ZC%21TyIk<(}MUkWb;=bv)KC$9upTU8Bw4GH!bs@ zSq}95<Ny-`sbOtq?8$~3m@8E=!90Agz#a1K23b_WW_ztR-iXG#B3(=8Sbd_3nQoB2 z3Jx3GRec0PY=q^CGFfnXgcMbxOJiMfiIQWzQk9TRozx#8)QnfON(4O&8RmVVyfTKZ z>;>KJW_yga*NaO9J~W{kF@hv}=u^8)j%M-Llah@nkT3GF%{LcN3~|`D&1R2V%4d?X zFmBc%@?N0m89iD&jup=&Dz(sfl#$vOLR!e|E0^0jdp<r)c&BRS9|RJI1Ge<;!<y}@ z$N3BvlIZDT(4)P436Bs;S0q5an_SbMKv>#&nj@<fSR1Yr=ZF*LXx~hQQA$;N+`UjD zm2Rpxy^XHJ+yy>=m^%6(*j9`CUhq{);zk}nLM?`hP`V2JAri?>O#4cCON6=S-ID)4 zm!*J&KjRYfog+S|t`u!vpe~!0F7#@$<Zo>P*czP{5#2Y?cCT=-gQ*lzDC$FL4{00_ z6MBc}L@ifB=LEdeeE)t4-3OwD4FTQ^A()X&1Fbmdh-LbtlA94+QM}JKo3R#Xy^kie z!FN1jc-5?m2kzJaWnsx$Ql}~WcfFsN2|I<_0OX5(9cB6LCpHxeDW$&HqH4~kThmKT z<*t2IxwrxLVR@iGJ9}W7lnM~(xQ{j>MSI3K-?U|_tDvkVjcfLV+^6fo+$&!DjVF~z z44$hPdcKR4iSS+3<eCBY*C|Cq6fS2RLJgSTjUseeZ}~EZQX%x#K?}vYh<d4O)@5yX z#;piA<^OtXU*kpF4xE6Fj*<v{Q}$oqSANBi11Ol3i0qAb+acpM%)E;hlS28vv7jTL z8jB-@0m^bXKP5>RBLs@fsQYoVr-Jv1eH(Y<5s)moPI@AgXtq!!q=K$Uk^+x27y3nu zeRfC>QPB$sd(nwk6Xhs!Ir@mD6W#w_%YW2)niq6yaN|k}n|I{fcfX>bM(+e|w!elz z`V;NU8!X3H?j)fJxvKjm0K}2)U)JdPx*KiEIXq*$`W1+pg$5t$3G4-aG-7GxWCQma z<cHISlv|D!i>3qETN2w*eMyX+(sUX*>YjB8Hkp1vdjc?6-B&R}Mjo4--5-k>)&)Xl z1Y+3tg2|Ybu?}+KWSHgBSN=|Yisgbem{($TO35YZimgX}a?l*SrlYOi?*Pa8=!S-= zL>rwG3J_Ro-p|SuhH|4eCt)PiCOKlGH-o&@o2MsZD7z7Oap;+`zHQ|;V$<n>?OEnD zS&~L66*vh~aE?tH6*=LL3Bh;JQ*as_5|Gs-w4|n{S4IhAPQhnd7SI#vh6NrDQH3~0 z!}3u>9V<@LnXVr}6YQws#y~Y(g;X_&-9xUJaHOG4{mIgkbwng2HijiA)_*X%TMLKK zT>3;30oa*fmSOw~xwDxKqFVCRxy&?%0MzfamdCRb;8YZ4*FNyx?o4Ubs=aFqkMZk9 zDt}2t5WrRofr6qA6)DR$$=Ai$r@FS@Ax|=CvIlby4kE9!R*^5Bkh#~xA92n6h_iTF zvymg|z=B3b9M7UW$pQ*-()H>uXFX-&#EGUS)P8C$3eW^Zx*Td76lyoyE^^BOE>$`6 zZa6dgU6}&r=uC#WvGBp34VV#ydcA5f%i;z0$^5%IFph?9%}BV+FPwV$=@f#Xwo9vS zy$}4K+zRNYiu2PKXZ(TBW<78JHK?}17Gd}qjZvDV8<#-a1nE=8aOq6uf#;c6%{y`i z2m=z&di(v@*mkTn!GmC|K!QbhLk3>MkFjAA4PU|mk~{>PN;?cF%O(TPxTsJkO`NKU zqBRep?FOy#Oz$0B5f{5AugiUG3+7Qb@;P<eMFvDZ|D>MDtXdH%+QDLa?nf_c9bz0k zJwf0eGcE88Hn+xVJ?0V^fBhnDBLgZlu#|j9n|s&>cKNRo1f;O~scVP=53@S`>T6u> zm-0MVLAtGUz^t0=0Al0@@c**{CH{aK7}S8_(VDqQH%UxHvHkmKHcFFkdTrQib1*Z2 z!0~_Lh=6IuMtpurBLY<30bU$^Q5cK^)ZH$YTq%}y7TdBTSp+o$XL-RCaxn9iy0Qe` zvWyx&G}yfbsza+9d4yel3=Mu0A0c}u2ae6nvqdjAY)wTJhW8UkZ8(>cgh+hbDAD!I zF<qC9JC^$-u1phK0dDz0A3EWp=|&PY3Vq=mPA6!KHkR3wadQXyW!+Kz&?y@IM$04G zdZlg-1hpJB%9*=%sxCC1Wi09AKLksI!<-poKE2DO89yI)kD@T5Taz-5A)F81h{9ed zGaGXROP<3RtTYGm;~-AXCQ6)H_!tP!M~Q8Wt6;7;m_1dynzy*fZ939YyJ*0EZ8<fY zYV(GnHnm<91+m$A@t?w?oA8OgS`jC44}7*0p?VdISBxY()VGBknmAWMf0`spR=f20 zFSTP0&i0s4f#|iYt3$wD(rLGs$z_2#5>Upphe1h(^t80W^!Mn~vvvaUpQ0V;en_r^ zO5P*CDpnRpQLi>A0etUjH9GOk(Xt(eJE&HpHjbbZfivB=v*1{MP3DARh^dFXIXc0n zHR=T?VkBR(R$WQ&cMzVC3dUWr?trP94JiK3dep;bqyT77ge9adNY<I3sIo!;RJ0T; zMPPrX(gDhSH3a}C?312vv@tONPEg;Yt9-$;v5h??pOzF8H2&+{Dd*x7@ch$*sO8lW zQ<x#d^+Gp<((-nnSL*7e?1q$w>hw>N<3)@DUsjTqF0Vm|-i`DuK3KTFAVM8meHjYa zfvgZ096VZ?Va(W8f--e0$OK84rXzt0yp;ewWph%Ysr(jCIjMn84%ok5z#|Gg&N+}t z(P<d1g3DdbGsdbygryUJZFLy|4Wt?+48s8#T91!qq%HE@FlQ;KQP}68b<;7b5`zz1 z4Oh;w5HJ2d7KGkD$cs>fn(GAC=IXKLY)hMraMlQ)5r=zFNWCa0XAjW_OQiCnA~`JU zyLVPCxE)`4T~;ZXlC0y!imV1wG}lb!z1J~h5r?0De`X&IEDB{xjtg}UQVyYEU-|h4 z_-Qb<uZ-R{g7KO;9Tx*2V|K+;(50RES0Eq~mpsF3y~!OgOp3@tvmdLR<-@<&j$}4l zQ*SuVS2Hw0GT#0IuzdAzIdy=%>9(<_A(daW!mac%Us)|O^|O?v$?D@qtBG;rImdGy zSrw{@!o&z#L+3g(nxKlIjQ%r@lT#Jm^p?Ou^Zv`;$qsstg`Qi>ee#yVZ<p#Cz1R2P zoKpY0D==LM@@mr$$YBSHzX64dZU*ha9lY}um?T0TzQTUuRcFa}wGU@A&zv>vTz+Ol zF64oLmO<@O3qaagfYlItyMU#%vA9}wlHv)$eO%$bFnU+maARH+3!uDtUyCh04f9_! z%PG5Rf$%<nD80R26_+J#4%9@V<7QB5a{IY<pgGL(+K<K(NFzUUo#j$0Sx#Gi!=#og zT9bx>#68#gE5rwTTZ`!-{SOE|m{$sXvnsU4YN)oH$MAYX?Oq(QINP@#N^RaAgP}wY zS;a~m-PXY6%y~8{=f(;7Z@SRzYcR?1{RG4@Z<JAT*<OufLCWSoMSx5J>^gVt<s>tJ z*Ss+n-Pc%->*WQI7De&}s@zE_{0>*|%XClQd0@Bt;tl2yR`uk=A4!3*1zzw1pVFQG zIe^5<Eq2HVHwfVc%u4%%uQ6XyzQRHt<xKqPB<MSF*#<&=T^1Q_i>!ypd}YpkTG0n{ z>W+%oN;%J@8;!#FIC}Em)B7AOor%HbGETx15N+y?^<|c?-Qi(`>y7l$lK@WZaFmq6 z)Bk>sg1aWRP~F<pE9AcAJu#b7puS;)?mg7mmbMxEfFWNf*9-FC`n0r{bn3mhU`Gx* z1SZEyM-pqcVPSvm86er_=0e(^X;J#k_`!6?5hRYYZL=~?>zZ*Z2g;$V8BrN;(0daP z#)g0<Q=HoJyYMFOh4N;ikzIz1$&0aN9<6#M1%{rYUJ16wbo|4Uhah#GSK8BQr~b&e z4L+zoaZT!0swV$rU6@-fL==wAb@sNHQe)}U-6a%E7~br;pbrNKZM8&mecBTKy2BRM zEOLJmXDfF5uQ9Pj$Imaz?`D_ZYnH4i;I0lG++&eyHP&&1(s!TWQY3k#_aiys#uB;v zL*i1orN_@dz0n-igdCmUh+N5c-gdDKX^diM*_X0E^Z2Al-DU87yDskoLH}FT0d0{Q z*l`S38(OZogyOFX)Ii(_Pvm<A>i|<h)QmZ5Gzj>Of{<?F(dA_qEr-D@i6=v`jO-VZ z$`W{ujm<g7@PjEY)0M~u|2hTDRIZ%-#(wvg^f8k8%O47f0UfTOVAwV+papD<<YR=6 zN*XHA=H7r`PIZj8w&0<J*7zUqWO1c7PDM$yoa?_epDHOiOLZi4xvVv#kSA+ITO09N zEBQn>4A5iR_)atz{dh77+=zIyD9`qLdM~LMXCpqB4ecKwfUzDl+9e$$BgTm`hG+8T z(@<3eEadDEASk}S$dicHQ#eybysrGeSa=66^nhyjFtaZrJg`Hz4WTa#=n+&9<p(<s zLoTWSlj$+lBW+0rD4R6Ik+VCmBfE)ziC$Y!$BAA*#FY{uaH8<Q%Q%5KEaY-UGuM2} zHu~w$NwW!H$O@?n7YDcAM7fx9*cs_85!hTjiWl%JuBzgiiOnYsi8ng!S&%OjVEkG$ zx$r}7y9DI|;wzq=r^0lQJUkURqKs#1p$2|8EO=_Vy8ryC#pJ93zOmTcQ16vH{VWfC zPf=ElS`jL2K;n>;Cz28%v{*@``ZzmJFkOAx%<^@UUH;|Aci0nz%lbgAIOm*ovUF?t z#A#krm4sJl4;I{dIjc>m6pp~tAL*JC_iR?h5IkF56?tyk>iOlr#4+nI67K@s8aE(F zAfGR~#V<0^HjEE<lPDUJ+13{3XP!T~9}4RabxItO$XcT*TNs^eP6=*!36<AVjln#Y zD2W8$^{X_6CccsF$y)|bj4Wjm0$zW{&^L)cz4D|^m;OmPtDhpzNkG>+p<1QxP5<(m zl{Z&EcDcCjgkH3cL`z#PZvr$LUi#W6<VX;B`nKB;(3?ez`3y#Y!#^g_ckjv&&!zSK z*SrM3#?^PGiF76fmGMOnGoDF^@xfGxg>YTW41jNkUxBXSdJ9yDC+OYCIPg}clIMOc zy2I~*JdXJV7t4+e{C!K9!zPvB^Qhk2T}2<#6)AfTuNjYdwEM+3LC<IF<|)0BN~cDz z2$l9{&V$WNt+KB4xtP_Sjv@O$C5yb=D)idw@UB4l>QswK#@zq3vhMHI$yuvTWb|k2 zu?G(;<{{FL)@paUbJ=h+*sy#th5z5w?48VOHT|sthv?eZj-lfFr%j5cTSI_D=l<p8 z8<Hn>9v%O?$L;kD1eI76=CE}TG_1)jNfjjn+D|LuKe*(5;{lp3X2Z!_Z>#&KzSq<2 zdw>YiU1?JKqXR=^5>-lQ`bDA7IyNp|J15Ihsc6og!V`<Z;xHRr^s>-8F*8}x=;hMg zcr?y_aXk!-NoNr=rBK*^VFh#Xw8APyNM3Nr#O&<j-2To^=MDuEXD=?t+p$?2ijjg* z`^Ms@C*~x{h4uIqa>A86=yJDn7X^J%d~jLJrK{}T9uNWN6VgSw9wwXuT{vxU?=pv4 zg&*7TZ|cf@SabWni!8=na4|HUq%@<<&JfU~*YNdu*I=P6SIiU$%m9>xrf^kHgv#V4 z<7IXiuLzLMAx2-{H2kQX@<HQ6uQ;Ixd0tc-^kRPqx|f|*;o(P3W)OTN9j1<G_dzIj zcXr>cABF8(empf-Vp$<<J7L84H^cw+*lO1Xw~)%M!($O<zMq@MZxAS|;M^TZ&mcim zrdgTWs!BAz?ticSnZ9HX<T()`G4<BQkaeHaHJSWdsQwP{S0&Tue6$``tv6=hSt^y0 zcU90M9<|G|S!d3NLcGS9$eaemaCeLAi26^*=_Dm^K1N|!NCKjJyb!H2$u9c+{tX!5 z#`s<*7Q3Xzv~~_+4)2DlQ3%QRMI3-=8Gee;U4$kyOF#a8mi-T&q=J$~MyKDIJk3Wb z>`HTs2|lrziaK%S_qNbEdGz%;Jq5A*gBe`5f2)bSf}QWrbV=PuktxO*PG#fVMm`%D zn<pD@K)#Ku{3MX2_guW9$^O_sdhuhXmk^YX!1}7NoxDHitkF;h!7$N-jiK>6ZK7y> z6cAs%n7@F3pDLUU8XgPP!Vk)_Z<1&r2IMoEsx#}3Qvsah5W1+de71eykiW{1#e7=0 z_l0<WCA$1PGi3VByZ<}e+1CD~PKcVKRM+?TC47sDUwmf8sKQd{oz8v|A%(6ViCn!v zM#U20D~#>=$phhNC6JfsX#Un_a7Y5lDgNKYsLVdRkWT^ih0TKY?96>P7Ay|B$)!-c zu=z61yMpd;79j>$m&=^1U8e&g^P{ymAJ+Zi|E$Ay<znO~(xaZ9f$b{!vg3FqI})9o za2B_bXU<%3EPmLYhoFLN-}^xfL~VQnE4goRp<lU#`7dIZCn0D3$iqX8WuwWa*Yy1- zbGR}=DKC+%YbvAO0S2YqAGC&&pdrB4IO9^X*Sgl4y#ty1KV>1*2V^@zeWf<ArD`!e zYD(VGlpAUMx(mj9=lof?0+Zsv`e_R7vO<%EcUVLv2U{^{ELZyTd+3!Sk4Ir6jf+|Z z-!bV)&c21tqKuJ6{Ub|nd9L!9bfDd_Eb3VsATlOZ9?E_E%hW*1p0j?a$hpI)l!Zy| zhf6U1#iDyyKQ2qytBdp+$>o0JUWanR;O0d=@E>EjPN5ZK^rM~{m3HW2-8^TnGMBe7 zq4*AN2Ezp>eM*9xmAid7DJY7W<-C{#vXE++=E;GBQhm9UUP`wO+BRYCgM4aiRO&J$ z=s#lr1oI{FY{PZuQzCHB%&4qC?>;x8H^v>;{nZO&y76#B>;iR#hpZ4=?5l2E71<#j zzrqH1%iHR3n|oPLS>@0?!vY9QjK4>JAoL5M@u~=eJJ|_LyaHyYHuH`19BQ0p6$kMh zp?W9a<lMQN2f<k$m9-()I1IbgRv>%RUAHakL-+jVsFVf(*#aRIo+nrwOU9|1)U%KT zZs^z>KgqW1zK>*v6kIrVVL$(8L}h+$+c~_=-GZt0cNqeAV(ZrNt!#4&85TOu{`lnh zcKl1y%Ee=`>fTRFj0=r;#4~H#N-Q7zScX6I>-Ouz4+2OyKq;}ajWHDDOR~_S^DbzK zStyS68xV7xNVr%&k+-9eNwkn(pFC{oM{fdl@UdJRlJT537(CVD2S~~v`;)dAC~}QH zNO4fy!`OF&cr`f(iU2h<X!V1VpNQhgWNqbkqwuB{jl4pG8(Y7^2RUB7sOSX{R2;!T zSBNDsF%Zl5Z+R$4InXr|ECNP$xy<g{FXN@@EsXIs=F4=T6^K0eW*P5H398A&>1XZ~ z72Ztk)*;vrr?(gkL^h~{d#>ivEK{Dy4vTN_0qIH}jS{d<S?T~zlG5t{P3)+Q(74;b zfo&&3@7=`ENjaW<vRu)<^?PSn!oWyeb<WMC+|y`OsMSud`W=&Xf7KP=^B8|RQhW+7 znw2qZ860XHYaMZZe^zGzE~LK4BMr%~taG&WYy%<wdt91mWCXHi;7zped<K$Udmu7o zxFyFD<#|HOzd$9I;*mb+AKx=9WMGP50=Ik}bGIvo{GdE|KHercszM%uYtXHRm+Va! z5;_}Y_SstdRi0;>j*?sG8llGI4u>JWN$*Z<Wo<7U3s9LMAQe=>61T6*o8d7VeN0}Z zYDZ6#N)ZIG5g_gs)f^4rptJ9YdbVxvR$*fb{QsK}CCaPG2UTgwp0B&0?)KE%$w&#K zcSI?v$&;x#Q=2gBF1B;Pxc2uMC=8ejOkntHZbRzI>kUUN2(}>C)vJnv|41HIvtNJ$ zDysbZtD_yvv&*R{<n~9IXx?m-`#JK%)mp>!f2hWPZ)B)=vmF!m2&hf&7t5M3mA$X_ zc5t$2tepKb@1=+q#>(mDu@H=UG(;>?apV?lYg$PJYQan)hO{yLMB?bcgF8hvzAHw& zjfkBTyvkeU*^w;>b3Z6=90#~+c)BXpMv9A5pI#TtZR7^oG0}bYvMVSS{9eAo7_m(o z1KkNnT5hyS@6yA1Rg6or11?N#4HE=1#8jZJ*<-a<2e3A;ws_MwL`cR(fj>!h2^CHx zR$$gu;)P#@bvHY530?v=7?CM8^p%|C<WOB9S@mZRRXaDz$u<!uMb1UJ%6R?f_LQ!I z0TXn0NZK_LQt12hgT741|L_Zdd*;h|Y-tfA7*g^e|2}{FNjdfXS|gaG%wQkcVcar# zJ|1b}##;vch;AZh2PB`uoan9_St7K{?S6ygCl`;)^$aafA5s!NYD@d<P8^HfbBGE< zdYO#%x=olMZNz@W0K2s+^;aP52x{cMZvjS+)nvl-@F>MlQYb}K3Y$N-axRu6Pg!24 zw|7-wmhcnFf{ZqLyh~|}(iFU#U_IS+5>oZH+}kmUB4mH-F3p@oonA~Dj2S%P^}!8) z|IByql)e<({oMoD8-RXcOHyCDbT1kV*7!;c%-nICq4o65SBI(q{fkPKI_><4jgK)6 zOg9!BV7(G{V80`0#hQpPOr#RaO9o2rI)qq-!bFP2=za=G-eN8D;AS`0t%0OK&?Ld| zGdYu_u2KBgK)fOrGCxI8%M4@E!vu!pV0a{zy2jjj7UQ_0(n!q^=kIs`R=D`c6kw0` zn&~iMs8;=qzD8cFrkB`?ePP&Y4(ONZNPwsYY2)^+{xh{{?6B-V)NNZf)<M-6l6`f` z&gble09<PCabR}tyeo1vF@qIXg{G6+mQ<5StYUMUhL+2v@M(0$s0euuoQs5=B_k*f zl}DO^EDx`8a8$g~&ksc1zfr3Rk3Bhc7hlO8F1(k53^u2r@#P8!LFB6I+if`$;L|x= zIq3_|-5sWAdRG4Sx%X;K0HEt6rYJI2`1(&-$w1-eN{Y|8i=mGM_&b{EI4>-UIwK}q z0)z^-|Bpvl21qwwpml2=6~7fh7VJDBcPoUlJvVwW=u$V~a1p_OF}LyK9m2on*n2}T z<?|b$oJFDLI7F(GOK7(Pt|bMeRu#e`I0ulbWG#zFv*^9&Ix*>fyr!hIUVi*E;$qE& zzmm^RO<wAfxTw_`^Q^r1BEsN{8{{RXrq|z&NQX~E{}b`=ZiKlUraglZR3qRpV)`p7 z`K!%blqSPbtOxN@CGFSsYDVTH(sX&R;+Y7d-AtLjH$1<5Z$Z9%$ak-LS(^1vp8T*2 zFq<T#H^o^1n+40lecGDRQKNz{rl=7p?=V8pgLN(hH32lx&wn>(uS?rV_hM1ZSWf>7 zDaxRC$NYZb2DmyJ@aIm=YI7{)dl2YZJhwsMT{;Gi)+ggbzGxiJO0MnE3*40w5$7mf zwiQX1r=15BvLmXE!D>#Euv5U991t?gI47ezjb`Znfrba2D%Ae+wM)_IQhhhndCBgV zYgdNG4f`N6rXmRXHyfI$lLw}Jr?%fR)Zlx8PJo0?Na2w6z0BtXp%-mi`p=Co_IsK} z>Uj~;#yfwB5Ja-agDTwstkKC0u3yaB){*dJ;(0ebzcA&_Yp%fmOTulQFmAJJZr}{r z)T>Ewr_!qEPwYWdS>hi7_P|2Q^w-JUJ0h{=_@rTSoq@8WNpgfxpU^6>ljJ-L|AuEX zErC6wpD(l5HBJY%gjGvR2*Owl+_$rGsjg7li%9K7(X#fvVtz=|wo6beH$`U^AIA*G z55_WYQdy8}_}$IYWof5c=Ty9`W<5IIoToBS)rO$Vfffq@5JKc+pgm#ed+JYgG!ZsX z*Gb?LC-g2X(uozDb^)r35a?xYlzuwF1zUJ#!<ivJ2pH#W{csQ_iN=fXVo9-;wTm{z zd2iatPsh`U9Sl}<R~0TYA3j<(qy8!#1*)L9j-CDy8J82i!KPhy3oG50V@DM>;FQ{$ zo0Je1K2c&aJBA^FAM|i_umU8%QEKSsl2P(+_i%mCjd^OQ(95U(w{^1!w|2!ugUYu9 zESw5Q+ln*=VUZe6iqUx7h*KqBN#{w2Az^`+Ywd#>@i1vP`dzxy2?Z_8epQYT<~YA` zoZ!hq<}mNhRl57mYmLXScLb+W+hqW%ae{;)(o=zGY%O}Yqj%n&gALioaGMJ4QYo15 z7E%b}jRLc*I2Gz2ETT&0_}9-OaiZRd5tmx?t;9UyP4H0C8FnB*-wqwj@BgfX8K_ee zCUW}{+f$P!d8@Hy1b9qr-*@;umVvtpmnFg2VEU%-HYI$XFFu&0rOna6xnGQJtFV2- z<m4S;Eu>)XYp6O7^vQcZOYCa;Ir2E>$SI64Wyy}k5(f&^dKa+WSX9;op*-W=oE`Ju zE3^Zndup~VX}Z(9(aK2nid`6Tq8AYm<>mBVz8I*-uopWMR%XTD+>Y+mi504TLl=-k z9i5D9C9L|(sl&QodX<;k$J!iZ4sxlL0KSqy(1}2*TGDI|TUP<`*BcBh7^TrQkW9&f zWf0h@$^c=28Xmt25JvkuCQMXGySh43^xkk(77r8~MVxX~*lwiLi++AJ`+8O`11g;V z#S!eH(B=vQpg2A}M6h5IIp&JBf0ypN5y|#x{ePK~kr=#WI<8+AY<961)<(eGIJ)us zuU)teQn~fT@545c3D692q4|p*f&e%q+ybAm?rVy@`{RyD;0z<CdpH7At*Ov+8dr7O zX^w(pP$}f)-{jCbrSba!1#Ux8tO5Mk$up~F;<9P`R))n!R7Fx*^VX&jjZAvJgqbJZ z<W<?@P2G?o2Bwy#Q7MV8o`c@3HU|1}us5)&Qu~H8K#QmL{pg}2v{S~nuUW|y$zVl+ zros)^%){AVQKM)Y-l|yl{;HM~mUkt=_+xHld%>+R+U&IA2kx)RJVNDSg!VBO`k?i- zUelud7{wLd3s&BcI{n>FKnWxWsC`imF!ZQvt*-Zb;?tXR@JB^IXE7>z+XBjS_2&Cn zGm;Gr?U9S3SA}wn9r^#j_EqOHhDLFG+iqfchg&LSa1j5vAqCsh{ZgvgdjhC{VksW7 zMkuC*LkROkYXC!`1s#j$2W8M%=Xdby)g`q+vgj2JzSG0q60k4ZjLVWN7E6!;frlMM ze){G7XynZp3Yh_j{XaEzURVxrdFU+-!VnQHH#?*B`!3gBuyM8t32#!PM#MByI!?b- zVeJPR;!No#p|ub@(wjL-GPw&!Y2GS<6me1PF`)R$mur|ivrwa3T@A7m9kepP;7Pay ziCq6syG2L7*3U$W_rWwQKQI16L-4NKPNaV2ca0<stJ~fpBNs+5-Z`8>I<N*@^VM^H zN0oM+S6ol790xQo6%nm`8F}Y_fpI>min)e11j?>?Zr4tm3(XWw+B8Y8jW!6iB$=4N z34Ed4(p-L}8QTa`IX$xR<+jYdGMz(91Fg`KvHUd!!8qQAR)4dvv*NHH>p;!a%dV&R z&e&^urL$!lCHw)ZQLOdz<TaJd;%$?Mwy##a1*5?dIO`p27UA0ANC4l1Dt2XyKHgl` z8mL0q!Yz#yyv-SxN5yZmt`VsCT}JATp&_C~{E)IoYw61!H~`aYFRvM{ie&WV+I*1L z{Rj6BUNNh)ySs6rD&Q|vn<hG+5Q_LZS0;Rt{$6rh-_)HS!${)vlkV)jl}#7C5M523 z8W-DdcRaB;t79x*)anq_1FgTSDSzg#_=dFQpT#H4ymXNxQ8&iP!Ycm09@W0K9`=L7 zf;NJ%EUbt3mu?Hv8Ko;tNRY0j;@BQ}W#INCc<dQkfk&ma5VpB#;9ula)T#1;6APVk zxSOqy0<g5VbmXVFm#iUGNKkDfBnnVx&EQ0^EO#zDgsGsQnOIe80Md;+*45B|Q9{oK zsmFODHm2j)(A2aKJ3<5^efmVe{+ak*yLi0B2tM+rmyomcI|I#hW1PJ=L<LjIe%v)z zy&of3yiYPNg{qi4t}6QP2Hv;srN_|`sVg;iwpQrW0guc;f}+|RKU+J<lK;-}N%+Vc zx6LOkVG*Yv8hV_eFnxtuY<zOcMJN2p&#n~15XFgCJM=)}wF%m)iFwqAeKsLPjDsNE zB~~y!G-py~(?+mmyxCnyI$W0yH&x$cb}Cv<$pBYMb@7)R-w{PMdT9UNefAmD$FcN4 z0@bP>$b-m3imsS<lp}ms#Ypzi50NCj@df;HK3(F(Zil7_RIh=&EoY@t?)Yb|;Z9{p zW6XM)kmB&zM?hXXk{fvYUHB}i{Rr=#R_5PYp7S>hPdL%{mNeI-z_wC%W#Xnvp}P7m z`XHypd<W`PnJOF7eDebBTLhH^N8eci3n1x~qn~o<!z#Pf!vRPie`{_&&q~pTBaUIS zOw7ueKC#O?WmGl)crk@k`B+odyi+pXnc(`H=i+7gcb54Z%zDOh&&3B0PJwku<(tjO z)j0C&W+2Bv&a~O44-8h$Nxf3Xr<~!;^Hg+fS&3#4KJEwu&P9vI-<3rHR7J&@&#VS` zMIzB9X8o#od4Z!_@BLZf50_NjyV!P|NsPx;-5%SfFgsF-i~kf3Bz1vG&_}b)2O!}2 zeMgDNWSpA!w%m@iU6f*~*eupN3{EqO>In#S9}_Y`4{QdRl&S&C*a~$A$=MJ2iIW#* z2Uz+me64ZeGAv2xP4cQV>&9NSx_T_C+jk0w{gD|-8?ExCLIKTymZqc{tubPC`H|bj z#*3>lk3gu%5vY=>t}!AgrUzV#=C;ExSAlIxN6~^R&jO(BAQ<k3U!c#im-&On#b&mg zc|a*LN9+qIq<Hd|n5pEu-?ns!37>>A3jU&~2B?Xnbo`1QYtwNem^5E^H3wsLBCLP_ zQ^o@*;YxV1WhRDZafWB809ie2^-j(yo+ut%B?k*08uho^xyf~_Y@@BepKvp_TRHi? zJg1?x2OzR2rQf9K9F;_9F~v62`>s0uRO`<@&E|~}@gHpU&UfD^5K1RTCVkK*TUc-9 zv#egh1JbLmM(v6J*peIVxK%6k!pw@88>TSekfj>fXBrx=kK#~V+6{<_$*so#BO>RN zA<;`MIqvc%qa+c0p?>~FMg?Qcr8|RZ=NyA#!)HozeJJrIy3n95FF2$sS2hgLBnP~G zae1nPo>n=xNnM&tdHqILYSTS1`CT{URxjSS+}{E3^bFKqvK*p^j*3RLkZ1<PC(N*^ zsw7Y{M;R>fI2K^2OtOdr$a4-bkXH1aw0nHOj9f0=Xk&AW2L-<FpD7%&PL*c;_LYqv zh0lYw3J@_ws_<yTrNoIgFpMt=2t9_c-9Ma0n&Tkn4}A1BvviaSACdbLr)YrDtC2rJ z5Pf04Q)*TDLv?EUMa;2nl(m<}#6>>emqQxMgtoi3TsXN8jrbgQyj|?B$^J`)UDs(+ zs4UC^p@O?f5m}1#34yp(5I~X*T&1@AjIAWEI!96y3)3X2b;ph@Tyrw_MZ5(Zin_z? zFAg8)Jh&AG^|?sVbpTR(I%yiYXzlF$1JrqkJ`c&PfU^DP`jsqWn5;nqJXaLoKuP@1 zYSfS-5YZS&ln@Nx&<S)&aUpSB1Xhc({v{OaVG5qJUN1Pf65V&oZ254Gmo$us<A5kD z-LsC%q1iZvZG#FS2%vOz%BQpa3?m#(qO_D5Oy&n+V_wLEzO=_an*0@BIuoovvG7q$ z!k3-*>Xz3lDrfBz#ua|)G=Mqo4&ga>5yXv@%4iozA>G;+Qsq-Ig-af2_%d2Z)9fz& zpjB0=ec3U(jPaFJX%qoqu@61~wQ2OecTv9Qa>n`QWgh(Mi>w6^MLb7MH8w79e`uK> zSmWrLG^JGg?xDJ>Uk)BQ<|B`>nQ^d;#7n?o_ysTFxf}uDPyBH>xS*LjXUhkcZQzZz zB@_#{sO=xVd_;E9&M=R-jOJGK{s9dzBXKa}n#HHHB0u6CoX*&HAnLC#D_aCv)|z$3 zjUOyjf6J`9@RDePA0SR>wS--+KZR`!V@I>4XqD(1T`TG!I2uJbBu9=hvQqHHDH5YJ zakDoRA;A9{Us?IMp%ars-gj+-{A8uR2=8%D{_=Z9wrP`&JqHZ*Eib~L>E>VZc?$v< zeh&g;UWp$4_5ZJn*`W$TXA4JX=%<rv`F%^d3fWDjy!xr2)>`K1Y{Qn4m-C3VKxSSF z4LKu)MD|u$#9d?QK^ItucSJ(Ol;sJ{e5R4T?#TYoC6y9Yh_%ODCiO?gbrbQlZr^yi z`b@mBE+#r^1agm?0!NhsyIcyB58+I91?fBc-ixrCYYozMu+^FwQ`IstW}{KgRB z8cgZ3wu^K?RyKeaQ$o5~(|}`y#5XpZxN0(`*{*g0^q9Y+j=w77((QhK{=+Jf{~S`* zYeC}uTUXwBSOao}iSCSY28QbU&APF(ID(?pxVeA~X>y>qqb}wp6;K{=lFy7<fBt}R zc&c+R3X>())pyfMTv>Y2o2=;yhT5elmGQSMQp`4;d8FY$^}y8XPtAX<*i*;IM9R@k zmX$eoyf%nmq_i2@BS^#O%9Yn)^_l*8r%vAg{%oNI{2MS4@}Q9d8m|ns5au&BpLtsD zW>+4pY0j|7RS*=Qxfq_C=@<$Y<1bzosPL`f?;$xRTD~Vz6c^_?%b|59L73jzRKvl_ zj^Iabs{!pIv+$GN8E+|hlcrgfvKQ)!)<#`|pyfo9hECoHPa{?A6)D!7d<I`8=2f@{ zI?DTil-eT^J+%*=ue-`Rf3dW|1fWn^GA~f^=VcK|=uMM5KmIsv|8H3q%lg3P-^Iym zW-cZPU}9y&;|pbkve}+4Ekt~tKvHQ(BwUCW@>(t!Qlk-v=DS>ahEt1hfo1D-$o5GR zpukN*L07f%9RJ{>???K#*J7?_Pv`ZF=4>UyHR%&|&FDV6ZL2iMd>3O=f{<@`1~z6{ z^l^#w#MV+V6M4d#+9v3U|H(r<t5zqZJ;dKN9DZHCFVE}H?}AW%z;i@HL}7EE*Be)h zYe13_AnN!GE?0lJyLf)rxRKwPu=VUlodTxDL;ZG#y{@r@lPYb;@gbTHW>)c|PZF3v zZOe6<Fs0SCXy85m_tJ0cs&=-YVo?h&e0=`g0=t|8uhy?coG!&eK_bqsmYz=Vqt0-0 z#o1*u-0k%gDc-6>n)v`T`*C{Qz?^g9=IMgffyph^amKSxfPOkJnx6PdH1F!9KBM^X zieiGy&x6;%WlSvWh;h>nD#$m_8;D&HRw80K^H6Bf`RD8BY89fUJF==?Vg~ReEy%VL zLiFaaNRc)Jn8!<U&;ctq3pHr!@C)pL`uxIy1u1824hU-`nV+<vt*SOQ!W4`W(5tcV z3eEvGL-};K0QjflhD8lB3|1VMN?RCx<ifV8V2P!at!ARSWbSSvN^Q6(vGQc;Zk~s= zpCRuPG?z#1oKrVTiyQ5-Tl$RyrcknK>B-Hs{-Lqi{;?HUl|1rrc%*EKC-mi-6mh=$ zN-jr0O)mYm5Dj?nxC-86%*mL2HyNBm75nKlWSI@1=do{-kX~v2^Nt}s+pE0wMREGQ z$?hXl#>9gpS&5PZ{Uvb6Z={QW4(7(lQ@3ox_xKxg*t5M!Qak3=+>ru|4C;zKN!^(W z-MIbDM}mngB-DR|aPycGM#!T;#*>^F-PxO=eG$UF{?E>d9q<|!5gxDs!=5cJF8I*} z#uvh+@|#b$>CH*AOM)y?!%74zz{#Ko<~3*ns-M3SkC+?EN-&^>O(f#)<|$EqZoN^2 zp}0h%$%kOauv@8~No-FRu6t7{*+cy6R|CirRr#DRE%`Pckyzl6Fuz?rwGA#^`Q`E( z>6b+)5pt5Bv*LjyaF`BWNPO}4MT$4n3o0=lZSi|*qlr*!{f&es=14M76nyCVFzhaS zqALm=NZ1nsgT&5pg>Ktv2UD-kSzk_PY|VU8vcdn>;9e9-NCEtY;f3g2ANRG&mtj7X z*Lp%_G#{Ze8S<Up=zLU&Z*&kAQO)jEJlAsJIde`R6M^!-&SLpS3#c;S&n6Js@q^mm z*E*(=SSTncsUCnn8&fHtk$0g?-=7cx+o)#)T`#-c2y|vA!K2ZbDb^e-B!W^eYKhIq zk?z)tO)8xIcnS}*Fc<qIiO28n2pmdp>%8?6H=b8K=$^H8-DZL{{iR0_P@Skc_v=AF z$JzOF9<6(z5OcvS@1@lF{SgA3l$9W^XEnsUK?|5lL;>=&()37~ETo*CCbWwvI~<%* zrlETKCJGZad`hzo<DU_(=CjiSRHvf30uhXohIM)TEs<qD02enw<KH$6))o-!x&-of zr;8$0AD*a7AQDrBjTzuDN=Kz55@SXORoNtOGyGwlGxgSCuW$ACyRi!6TXB>$-9;q0 zW5Q-xrL*Kr&d{XU8MWPC_g3D#aFj3w<FJ0s0{{wP52O07rgI2kQ|h@(+8c}h)meCj zw}9CU*J&Lp5Ex#_O${)w-SGw;FUWF};i<mj74#xgfX>?E4T0sdsRb0^lvlh=$n4dz zG=S#1W@uTN?$pryd1%}~KZFQO-48ViD!n=KkFA`lsXqM4GCsn*B_f^Vr0tb>!vBJ) zU+N-W5QbdbekE2X7d~!{dXidmP!zDvV2%DW<9O~{#CqlFTWOx`PNSfBv|9N>fwgy= zBOv1{prj~?wUmDAr_~CE)w9@&;a2qtihSSj!Z{0Vf5N99g_QLIl^j-KPLm>b=0<Rb z?zJ_ZOUsuxraQnDtFHPsD#e)MG3~tHG-tWE@zGxi0V<XrnT|z&$^-+X-NO{s>Werb z*75&n@Bl$TzQ67nwPQg>>Vc+$-PB1{x7#F!F46qJou=^}RP;8utDVbx6r45?!xvZ4 zZR41)6#89i^+&xChU;HvBhquPP7}_uNjAY9A|J*rlN8ZYiIIEAAQ;1q|B#?sQv~M% zzp9B0nLV+$5`Ut=0|&+2eXezu+9WDX+upE<c3~}c@o9xQNmUASTne>Ubw7H3(k$#y zs7;D+)5*j%$U*!J;LzS~$f9%DHIiphBTLEF_&GqkRC?4LLPRn=KEJ#c3qw79QhCt< zBf}0QCpIT4JWYIrr)U_!0J3@uuiI{s8d;9aZm$fPYiGUDt9+l|_Gc8Q?C0(lPmlNW zZ*vSr-QDQXX)~I_CGIuA7X<J+Ur|Wmc?FI1az9s6H1Hy*4{I-cNG-j%Z4=A$Oj`~~ z#}iQ0aj;yR{D=(=0+CNZ49k+L!LwU`HGQOEE8kVz`g|85KI~iB_L~h(?jfcx>MyID zq*_Dqt^8Mv5<H;Bzx{DwbQT^M{zw+Ja8+86I}<XKe-s4@>+c&U9R6CKFZ1vXA944f zUSO|EjF3=9+b;pN1U|mu`E-h&v>#thKlYe7St8OIS$SNfyPjzv;gNG9`R)c?{xXK3 z7~vcVSA#Frf}^3qVX`kzKJc{$7-gWHI^6m#%#168kcn-+aZ8$r0no{Ci2X$ir7tm0 zbfpKj+`EsU@$(|(s4Iykk`VL>3}Dkr_e3LRMG(TM(C{tcq0<aI1iE^37EC$2hs>Qt zgh|?&Q=w_^Y$2znpH;{*`v+BV80&5g)Vce4?N<mkOpgSX0nf%)Z)3*d@kPFLHxuUF zvnlbEg^H5%tS>wg8gk6NfM8k&a2$1th$5`*KSHkXSfi#$A%es+MJX##K`|z#KHrup zJL;}7Zu9jze)NW(Kf6SY!lvw1+PjOPA)1<&WtWE$vcpKUq5Q!*NsI|9x>B1{X$07j z+y6-WXBxa--4^vH5Go_)6{(ByK%;!%@yQt!wa6|N%5qP7ZF*Q!x3FA10#yk7nU`|C zeh8M3DXB<+zai=zzfW#5$fvE9LwPXIV%=$^dZhpANOezsA>Pgp=^Y6)2MnA}=Ksxc z#VmJ4jo0pzys%pl7ZQugbMCWyxVmfwuW-o0jPNY+YeSD1*t?-Fbnx|tYuzU7yJhJ3 zmZ?Ozta>XbnRb)9%smw8=)T^3FBKy~l*Tvfr%RgsfwLsEaa)+@A>DUB#@kz~&09sX zW>m&>pp`zapu=AWMC0QaUKc;+=7evT$=pE4<ES0EQuUl@iHGMW7t_E+jJQGpK1civ z)+Ns@Iwhk)GR_^%FewEt=Zy!BtWPr{omxs-VbHCcf`L!vSH*v-+_Y#)3r*&@((7br zULgr9qA%BugRqa=jHim&XulU}+?bXCBLZ$6Et3Sv_L+7a-AR19R@vam!-xDzHp$<p zO`i0-pagT)U6&XuxKxx-*1DW$5qSjQ(6fIKCN$N$<0>kW;|k>*i%xDJkF}FK#s6mR zR|2|RJPVTEW8SY#HRkfo`$kui;=~W$19dH{jy#@~Vpu=uI8x#}hX2L?f#~b9BfoY- zQw8X(&#qTRYoryKU;2!}UQ1AafcU^O4(PlB;p<=xDjfVEPg%KHnk)TU#X?lcUSV;L z?4^>xF2GQ%+V{cL-vDgDFbWo|8(aSf$jv9d-Y=hpdI|>N?2;IHKK}WK-S3Bj5_S_p zqc^+?<A-$!fTmS-vMn37I={oo;rmo*m>(0GQ@~S=U?kG*L%A5?sqFY;sUP;Ug|Fg# zhXyN!9J2u$C1H<#)Phe9AwHKmw0&xnd1!cWf^li1lC5V~n&l^UpXDCtW_SH9lguaT ziI_wI(!CWL_2DiL^4rHtQ0P^#E*Hh{0~BZQksy$H;E=1f`4s0@dfa<jew5YgHUhBS z{;t2C@M$ac5@;H0=F(oLC$L`{=&fZ6f1OhlhHX`irYL+m{5Fw|e=bj1sGk2fS!3$O z9N}hl$dZOQa58q6_Fc7VgyU*RsRh8Av&Bz^f8<M?jgtrxxSsi6(}bXMYq%_I%)yXu z`6$eZqH|@8JAYFuZfRagEVJpDMtQ5$J2P?Le0#l1LJCA@&c=c^!FDUMf@TSJ01sE0 zisUlZFm}XEPxX?yHflmQQ3SpCPW^Lf5#%d<kY9P9k$e6%ONa2-L9a;)(k|ufpXfwS zh*BSnZXPq5EwMwMYxV4_SI?uP5F#KR0Xi^S2p8Hu-iT_9LV0JmO|j!`Qc#2Zzi*3= z2bH(Nc_8TCT$D+%!II9a8}ym-1pG^YI+Jl-$+9Q{hHSi`%e$r=`bp(@#GQp@+;0X; zgMyJ5vnIxVQMjlgT$gZe4%+6gTzOj+!?G~rlahjQ07R47yQx)q>`hxts@4+ZLiAw- z&(D1m?l|%QDSOU2I<r_|lX*EH6Ph#Bm~l|b%7~(SzUt6qNaPX(9qCvgcwh(Lh&I&k zUO@J&`_3o1oW3r)TyCy#S@5Bn02t#$q8)F-NzD%H{l?7VU?D@FCei&Qcv-D1-u?BP zGRvXJA*?G$l3Z9B$V2Srv-2S;!)`7G1#@&G=e;XGC7?{>yJ-hklKS~K2ro-rSiXA5 zt2ifeq>uL`_=O58I`%2Rn7>F`D3WA%=z8ojyy=YMZ|9ylAUVQyS&fVEp_$ep3bcky z+qt{T)39T7d<u~+igOXP<E867r&_;byH|oA!B42-@$B+ajQC%V-|Wl3A%L-!0|M&? zf|t%>>!G1@druc8?G(`c%5zgD_crz?oT@=jpNh#3O(i0CADsYI>(ckzmT7o{N3$h< zch|yW+$fj$5G?_S))p(~1vP5~nbk@|rF%dFyhS#^v&r-EzNhJ>+vb8ak4)_zLX#QL z*#0TwrOYT7!pnS(-%XDFl@D_D@LcxQ(y(C3Zi;!{HVfdxcliuc8o^lpvr0g+ae*IM z5DJ$f#W+9)lIhs8?RwLWMQuI^*4aUJS8o=n^O7GS79UZF2pN3sTZGild9{L&`4lS1 zSu<b#GtsUrwx7dq6REuhL{0J&mRn8--jQ@pNR_CKIV(q9V>cbd?}Y}OPQAZI6KJtj zT2SsUPRR2>d>_ZxFE9<d`bl3Ae^T_qFFTx}3XtV9^Gi>d3|OHr@9W}(PZ$A-EM1|P zc4V+hm)?aF%b8ypuUqdGTZL#heBDKuzFPqhrEpu?gp3V_i39IMWfAnVsFOCFS3j4W z;Fbl!FgObUjOSeHVIdHUH7w)n;yjkKcA;%Fre~PlKD_kFp&5C42)!dQ#~Ny8J)lmp zxm?GW?+|O6<H-~%kT|R8sP}4e6NECnef#KybZlZAtb!w4_dMK1(p=`Ta$6xXTY-}x z%4xb5rw{4T2u9CJ9temb-Y*kdA@3Wzo01_{j@!^6`iSAM!YQTohW?lnr~e8NphMJ= z)`Ig+WcsI?Bqu5tn_cMk%d(ngw$o2)mPj<(OkJla?2IVQgTFM31Iawm$zZOyBw!o- z&PndsYgY;0QHS?d*762u_K9b=T_v7e(QOi`-@PJh+T>e1Bl*p+Ak71bT3IGdjall0 zsehC}iP>{KS{r_kuq`w-LbTgjtf-^ZsB3fvjqIl(VPWJ^uTd%n+|$V>UwlrQr50Bv z+=`(;b_OjwukJz-C3BwaPsF+%jm>EHbvGcMJ*?&><e*18g6Mg_Z?5Iodysv+OH<>z zfD#tyb+#Iua@j-sMU)0L$|E}5Zdb>OSb4Q1vcb{3^Mi8<0Z1kH3b2iq!RnU$<OPeR z*l@TQ2ar5k9DaGNkS9()?(e@Zk2H$azD`q1tNim6p7bXtQ!#=r%UyB6;ykmh2fkfo z&6_1wkvrY(l*(vv3^D-wn#7=tKR`#)U4?TwIH~i0EqhDiWjccdsqXsJT%h?MdhHfW zsy#@?{qgGJB5!ebDc*AQm+aHXL#m1wSo=`!{8VA!{MIY7;9d3qNi1uQ%t1#?Y1Z0e zGKx=s+bz^|xVYVjJnP954rg&bLCUE?vQu}$ZYU!j4vEf);rPIuav~xHqHR>dopxMA z8sY%4g4$0|lKOXMfDfl)m0TWJTv|?3ekEiNU6E&sw&mPP{7tz7G5z&|!Py(X0pq)b zZ$O2l_j;HhEPklK(k+Hfx_Er)s^uhKj6HRhffVXaH#eSsA)-tdtssif0SIQ^O(G7P z9V3GyfOXQ+d<u_&Q$eozG?{2u&oe?i?j=AqyC2HD{u<C@0j?*9kl<2qawn8f70*CY zGyt0PU3VAhtVC0zWMLD)*~_7zr9q0Zl*q_8^+bayAWra*$3j_AaWim_O&1eO^~7i> z@wznj<S0AZsA3o7Wa4Xm)z{w3EPE`~=9b-6g-|4-Wv_kaISps!4p4;NXfuk%N`O{} z!cz=&QuFF$r8guikie|(2icXZ0w}X_kbFS3jO=*IqFG*+{Knn7A$aw}o{pI&!9l?9 z_*3uj?fcm7zRSyugICCWClf}#?-bSp5a=V_H4fLU^ET?^SYU@cWUch_BDP_o6{l~Y zk&VD@>E10^TTq<pkDvU=Fsp`2mQ$0@CIq$TU?(eA8De<Y4k%gqOBo7P6Osn@KpBYA zcTA6%99nFA<Xz<H;)GWUzs{v@$xrdhgn>k7=i$9sW^!_FdMW4Kf9}aklr*Il+_zM> zrcyjEE8`lI?g$^nVDh~$GA*$xL`zVd#HW)klXy+$Q{Wzxu`VYTvfFj4$rW$?%p%J% zc)Y5EHauWmVQ?2f<QHx}ltx`$d*75Y_Ww2pgO0wvjNQtcD@?>Xna5~b%omQ*a8Z;< z18-DAs|j+2L1toVYDD|FFjt?`?+2ctqE2E^IJK+fi(OEGkeEQ#-O%Klh^s?Sxm%7% z{JH&p=;*|ULWl<AV30AXz2-8BU4QT0q)kii8=1Aag|yPjLZv%yjV*xF+*%|*aLa#c z#K;G&e)<$-J;0)$QQA(O_~c*!-?goH+@Va26|R*_RVrEJ)c!up4Hk7~kk~F{_VJsG zNXlx~gUOhC?R=>j`0NxQb$f62{c`A5&C?15_Us4WQUoji^wPt_F-(cVsjWSa0og0? z5JB{>TVhMBo)MSEMS~87xw!2JEYw{0p6oT_<b*4SP_?Zm83XGhm&PH4Yv_Y__gXr6 z?P@>QDrFNt?K41~+qavHAb%yD>~?BUydLrvfrL4<M98f{7z=;hSE9!7KTQIXz+tn( z_y#-1u>T@yZ*qSD3L&UKs@41}7O{V+Nt-n1XQUTl@^$ZueNDu4w+4ja0b2sSE)&G~ z7ySa(cV9#1D%JXeRh^?=4)JkGIWo1nyAlsL)|9FYw54987+GU<Rg<Nfed47>9$XBg zLVoIYElT3x?#I9Jy9GbCh#h2j%|BQofwhOH>itgJ(<N1UP{=kv;{+xP%1Oe@WaN1! zVa5+{Vd+GXV~u~*xp_W)&5^#YnI<H)=-dP!Uj<|6woz>J%Xjz<_W)ul^kH5}0DO9^ z4bzmaN|Q>mYKQ5eT|cP!^J1}812Y8PF;vfhUsj`C)MU)ueAOMJ6L;T41arxI%AyVV zYmLcRL_?;&e}^WpaN8cPa|)i@DGtZl;S@)QX`Ad}c~9q0vpB=8s8^R&Gu^?3r%+EY zHS!)=(JRTVNM83M^3{!ADAdgl2%^_&3n3H5GCUfU;eXf=H+1GsvfwD;tgM3@62X+~ z%l?&Iy#qfl!S`#)=PtdnWD6<4Hc%G(*^3kRjI{?g^yJ@x#T`bCeJ=hZAr3tpR)rRs zAkS00^a<?e{CO+H4PWQbdX#xlVPJ!_S*Rx-$mXiZYkk<OOpXa}Xur2*wd?F(c+gzH z?3R<eL30QRb|jZwwMI-hr;8;B;>{Gtv4R%`45~eIwaP&@|1ha2>Up|3xl@;~A;Ye( zDajusU@)d+ww1F4fd7pLYf|Qe%ZLPaf_I5|g|k>j_@kz~g~W*nu}Hd>VQb{DXls1Q zN1heMfZWylRn=f|EUP%WsMq7yjMN1#<|@mW>cAwoSvU`^!l?tzYe~8rmB4BHjvmJ_ zWgH+gWKPBA<s2A?j^rSr@ct~Sr*&v_P<ZH}S)W3!bDO77+{7W4jR-gpQd<sJwT-vE z!1Fj|U=)Ft_yAFou>9nzC#HbJY}ANsj#3ZnM5HCh6*I?Ar3Y_1i6iM@XtI;S#c{A8 z@NK?cMywr9^iO|UUwS7S2|=_JgKjJjG-}I`$RkWPpbLr9kuN?cDzL)&UHQ?UBu5)) zc5Prefu1FBU?ONsh-3i1KYbt@L9-e^9tK<x(dKV6C3qj^b1{BQ<C4L`0~;mxTkDP- zu#e1zIfOW<EFVUxhovsHf@{Aa{jdcQH%s3yBYSF3JBB-e&|IVh#xnEk=|a4XO;9~Y z$d)rWVIO0=J(HlE=Pz>ht#vdFcPzgAN=W-i&65rZ?FW5Ni>6kkhkDq2#@Q!Zr1fAL zuG;w@n*sw5qABNq8C~_0BRsm!<DyRrlClgGg?8u(FO_d3v$b%86z5=dBi0Lo2U;S3 zXK~=PXA{A~j=S4I78I_~p~OLea94lZ5fLj9nQa4A8N2sp4tVnrsN(feGKTcr20YHx zZJ5vGlz^ohrED;=h6=5Cp!JhfVSRlZhtutO{pC9$6A?u&;kA^Fw8g!m=Sxv*&$(65 zLlsksrnEL7*Bcx)ZO+VUL^;?BWCXWYE9miBGo&NrBTn|tfK~^pzwW*qcfTrv|AWj1 zieP5@`C9d*VToaK`<R2|B~Cg*A8YmGON7mEe=?Ms5jCDDKhn0jqu)SehVfgOVWK>V zSlko`2bg{5J!0%ZXs-(=6thMlWcKFbHNit&zbYzUS-^A<ZZcx!vdrIBzeURUEe9po zr*@1UyFleif8MwhSQ!-X1Q%-Zol=pg{LG7DgsqCjpUAZEoIU4ii2(M}i?hq0%0neD zx$*^76EkaExxQH{3MB-*oK@WwR|sgTGNAiNATu~A8UlJZm{R)HTS3C24FL2?@1?Ql z>s}Zu-qK2uk`3iLZKs{_>hlKMJO^-D>FthP*b3MLEV$>6$<Y=X(~oG)bNCkgWbR^f zSx;J!OSsLRlP?pA465aCV;Bi*tHO9uQ#lA(0063qt7A)v*?$RC)Sf;gD@|!>b(ASc z0N*VGBd^yErN>wL?7^X^#&ms+msn`S?ry@(0@a5MyhG8IGFs0;bE^r7E#NG;Cv7!A z+Xs-aY(M0f@Xy&IxfO_*r*Aqj1ZrdF6QQ)_mS;2{NWGbIG0ogSLH~zBUnH9+(@_V! zS4CqHYctZ?tv4a06a?khrkgeai_A(tq=l-7pydOab;P^Y5bN(3BnAr%FGfdkeCinV z7zYLOma}@&!lX5sfzx+XKEa4_B>%^Efy^JeW;(SosWo|Mbb&98=Ic@GpA6EwncEM5 z!jSG+iPUhNbXyH1(WJ5i>8u_TfTve{+wx3VW<3v%%!{{X&;vuUJtE0)MH4x*8xb+U z--&IDDRXGUreW`yiNjO^!1rJvh65lKZ}EMP8G3LC=lR{txFL?uvWhcQbaZLL{1QHd zIoc&(L$F(mxru^PeDeemAZ)I~=~|qMeyU1f>G`G>yqn1}$1{dUap}Ea3i+>7yUgfk z?49<W&tff}4})kGKtl&YPWo_}<rpr!?0e8oN?9qWGzuZ_OGQX9Ww~lMW=DYfE$R8Q zEERK1?F$k0IEGSLK}FrTv_;6%y}i;!33n>IbBPpzp@wS`w|UjB#>q;o%fg<Ehw7Z_ z`ZH|khU<%3+zbjNZ5Be=yQLc39%ltIl{ZHZ1w~99K)7V!?UQN7o~1ThD6K^va`S*U zy2wfk6he?CQZqE%A``A1v@_4)6C!r7-blVEV7q26CtmcE7;icYaSck<cwy;`<IQS% z<>IZ0NWZLI1t8x6ffL!lS}zUGoW7cN?><amE<9t$KDSz|Yf6kk9h-omzheAUx0Xt_ zK$i5tqQuqZ0x}o0Zq0W~J_jl5nl3()eVoA8UGmf~@G+cu;3KbO^U!bqmxBA>z+l@{ z8)4Yw`wAg{e(-nHl=p}kWnH<Zu1W!3!w6AX6lBM7bx-)tteuj;XRXj;D17xjnd!`O z*Q-&%zJJJx-qw<Gb^fw8dW*9@udtiC@*Jd%pW+n^-|d$ToZhk}3o?|0#CI+%845iU z$7=`sv7PdJe!h7yOTbmgkiwFFHcreZnoW~|H!;NLK8tyiaB*A?UC<wAasa|NCFwD@ zd{)E#FG(xPa~Hi|WlSiQV)dLOWd112e1dr99PbADlq|Q2{x!muk4H}Y75K}t_rj#J z)8ILDLwk%_=RZ)}+-?!Oj@3!gh}`!e+`3F=5zikSALFSXo5()RKvG_`QV}%T;0JJl zXQLDvQ~pkjrF*}~?<QZ=N!x*pL|9Y1WH$R}`osw=SWi#AQ()e0hA1slA@bcF)xe)} z7@M69cXHJmEF|ivdUw4CO5%nrn~rpN&h$_=z5<ii-iCJww9Dq_fc3;B!9bQaI#Xa_ z+0{-zv)f*o(4muy`Q^L}dBLO2iMl`vbqeaI;st9WMA#}IF+Q@27|QFcYd<L|Fb;>v zNZK&#ILjk9$k^4R(qyDF0(-(Bwu~NH`aodUrSiVihOv5jwn425kFjoI2U!xIi~(4w zWDU&%QnU~CW-ewLwEY}(co&GtNyy$_j3LV|)_%x~xCo$)Slc{WNdN?&>-{qpUDA+| z3*Nd&P2_(!!_0QN7TYIwvd=g`Yg8n3)77%v5g}}?N3MHPcywwHaYe745p6fOFl$@< zv!fc;9~4RJD}5?IwFxXGC2}i^Br&k>17Ys(I(^lPE%$uo=+dsdkQe}j8Z)T>e0*y@ z_my9hI>A#4Xrr`tAbrAcUsYyOpuoii~Rg4B9B6bC$-G(N+fzDX%m?>Z^w&e84<x zRGyWRxbRC=vzA$1461BYOEVy3bWR(Hc>>W<{7`4Y!)zzULvTSSF0-S&>8~`!fac_M znI?$mi^lT!AWe_FIx*b?twd|1PcU7tp0X-@*Rt$bh^_&*t<E?;99{oEG|Ys$xC(Wr zqv{c%AvL*X*cy<F0m^Y4f(-D(LG7Lxd;9raOFRc?GQihYFE3_GtP`gDDxKj&!?i1T zcVnAYul={wyC&_gmG#$M-d=7r8T2}Rlm)emnT9gp1_~pfQ&*85Spy%(b5;!~WB=Tw z@P^KAGF2A){T#GaHZsz4coQNS%zpZ^F#zUj{TjPX3rk3LOD}-ElTg#)pEqjBMR#WO z&649ttl<yPH?Qqs`Rp-%45u=zn7&Uz_mroibFv8PseN4S%=c($b0L2ZM`a-CugBW} zb=;M`&n8<CxDPE=2Rcm*5LY!<(IE@P{WblLK(D09&+pIL+qNk2R&I=6-gB9<Au9CU zLDZb}F2Z07ucXT_fLw>KE>;C1S1j092s=of=;Z~9Ue?Ii*Y{lBnTXyxX548*GnXN* zk2gIzh_ORwTRHd<l`VRGf7LdF_~ES^09r4{x!+93;ai502b#Z+!<Qd|RvWB$PEq1J z__q|8^YdL~r~qJn_a`1)`@<P{^iP{G_%KxLCtlJt|5UmJM8*z2+v$bVW(Q+WeXvcd zSeWRWd%BaoDAiEt;Wrl#cO>CkEI3BEj~HDrNVlEw0v#SDrs<kzJsgGyAW}#g88u$Q zT5*w*zMPrX&Xziyp1v{YVW_%B6C2ICnkkk0C|h{i6PP(TsAuZv!aB<k#1Sx6%Ib<5 zKsgfN<0&L1(xNzs3D#G!y4sqweg{C}+V9OPNk6tbvMS?ov-%x<_EpN_H=(^}2OBUs z$6hQ~l{v?~hX;k>MeS<SmVj+}Jhq-c<4!m>k`47{{WEZ@eK-I(L0P0)z7ct2g9l)Y z<pr*&g6vbQuFU0@T)GyIGLAWBmHDx?R}GtcZS3A?7KK(w4mzv_xso_pj9nh*qjR`1 z?+LPKsv&G}Eg5>ry<dcQp=TeeVQY534?8iRe-~@Uw6EyC+My0CjP!iRI03KrLGNCD z)41ifhD5sNG4YsL!>vwR@C`*ZuW9U+Xgk?HiC|rY{SC+VK8W*=!qY}MQh#X%`wIi? z5dqE0Ws3dY5o(k{FJ)Kgv7$Ih`RltI14cx#$RcA}<hx*$88RA5qv!zHXR&F;UW+O7 zTtEwTcC^ZkVOMsWm@;1??B3ffJcUzZsveC@9w7gM8Ub(kgGAsdcVjZ=P$eY+PEt@5 z12hz>)<MRu`YG~Yg+sDHx>TQKqh#3Xh4DTp1>8P*CRelQcO{-yi!;?TyJD!!d``-3 z(V|6q<7+=UO-umD9JtyFB(i_tB5KIixMWLZQ7U`iib$zlkzg{btQRFo|FkdZ2&NLi zz53g01<ibQ6O6VF*g_QfV6B`jp`bqgktv<Y6xL6khT+N_H1bYk>^V99P##vQU0RPg zY%Z!7M-{lIGELP8oClrZs-Z+?gK-zSkj43-9#M|EmT^B{D<VmCbPhdtGGav!TW^pa zBHx(M9nSsa)!AA0oRU4ITA=wvaWYG^B8^tg9L#_fk|Lu_TwJurndaM9b1X<Xr#v*| z1T8)Bx0Q_L_@OV&;oIwy3Krx;AmPXok3k~}gU#&BuYxXG%KuJc%_A24Cyl=wK_alD z@6Okmt!6M#%GWcx`y_`7BeSPR0UQ+Ua8ds9KOqToH2bD)(_TxA>MmD1<#DlVftN?_ z_(_gAi@R$wcy)91%0F6I=O>=-5BL;JymPR{Wl-r|dHz4Or00Wx@*aT!4v)J1JoN_7 z8Rd>cb8IMX&%aJ4y;9(VSkpUNH)nPu-~)&Y9<u_ezub81bh;8Ac&-xPt%rXXJl>PR zjaIzVfHamZmjwR-Sj_hW==l4o6<<TM&W%)_t=*p0$<9y3!EPO%g{Fs%@KB@;xZ^Wb zToAg1O<>1K!lNFqzK%lSiUx5yFJf!d&}CDE51w->$Vj<pkbSiICM^eUGyfwhvtK*& z-(N}lVaf(yOl4ZJIB_s+-DzoOdpZHV7q|B^DQN(y(Sp5x>Gm<9TUYNNZPLe$s3}@i zVMGf6*@jVt3}AyB_%8Lph%vKn38l!Etu80mzGHt4($5;FKV%i~XF@D;WW^*FIP0Ax z5U*k38N}9}f4bXo%115WiScStkO^w&G$+x|>T3ZHskHeg6^tfS_gT>BWfbwiID6;y z`!7T7>p6PSUy&{0>9KVDe07MLH{4FC-LJC>Jmu6tGFdD6kD(~+&*4)uA)3o~!;)za z)^G-3wr^p}+4#kX9&X87rxxN~B^TB4Xr(k5M%e>v`!2DwD=}u@2YF0sZu%?+X>Wi| zpnovXeZ~NJcg2m$5dT&=JKZPHSQn3GoH_<(93>$C=QAxsXDw^mI*ez(M8;sUn$4sL zZ+!6|hAV9Lim4fRA?aF?r@ycx)$_YYX9s%u<B)bu^dZ>2OG}`UyNW==rhn~MK2F<( zf**jcOX-gpFXj9e=e=ll<+ptkHGVZ%V(@j+^~;dccjT12bpyYmNMrXwQn~Ob2`{&& zV#nx|XDW6#QzL7uWTdd`Fde_M?=VBakD%SZ3-b1lxeX;;eL&ldoaO+(UZqc{4`YHG z>5Eu!{2~fPzdyF9IDPcllpQ=!gB_C<G216P&ta8POHiF3KD2jmb30ON;7ZVf%vISX zVA!3fLUC%o&qx)2DQJr%=p0gc@8`aK{WPf?rK)bWcUQ1V^;cS^9>{wcu>U}g0Z5Pb zFV6F0xXG7<;2Gk9x{ecL@vF8fKmID+1GWYtuH`u2?xk|VmuSZ8yuLV92r2xQq(Z~f z8*o~AnBxnCI^X(*KUy%sOZJe8sAyWxbok@8>&}MEh47R0r)UEEo}X==P2^*S6`)S5 zsO6N83P5@x<+MYb7c_NXk*dPv7&cfZKw(LG#gU*6x!&Rrg=hP_Upyz_LJCUxDk|8o z5li`%o0)X{tctB7Ufb!Tz>qEeTB|b%4<kM{$S_F2w%AKS+j6L7KT}bj01So1M!Dw8 zt84z(-jN;Lok28u0nYf1g{Go+I%OxWw^fAh{*cVW$#z+7%MJj9wLg5HsN{pafZ68+ zK!&2CyGud(`b<41$G%_xV6}ozwolX<eqQCO6O;7aMi()etA3U)>-0!rlS>oT>!rz3 zTv*dH#u)7tj<$9wQ!fjN%oAL3_|UPeSYtQx!joGMvwO*0$nK26;b6Mrw1_B-3tvE& zhNo`u3L~0|JcS!VE96~{Ml|fr$h7^~BHeh#NBnb&3l#Y-r{hN%9lE(o<0disoWlw= z_aN={ghS?E%x{voHGYppd^nZuABDSFp+0RKy7gK(A39n`HL}_>h1njwJP_*T<z!!L zsDuA1bX~QMsF2E@2*{X~t8;U<lA@NFTc({G5-3n?bWj+zY}Bz@fGZ)6rrUT8grxDV zXYl3+s=jkOA^@Q9i58%a^ucwEh#v2WGoFenEj7z7%~mvATbZ4n>$>!UwY|yQUQla| zuhG#eO;HLt0=7Q5cIS_!)X<Vx{N#8Ni}pT(HpKF7G+E_6Oj4smI*CzL0aOt`c01E@ zGtCC4yF_}=E#Vy~Nvf)rA=#T3doEU&O!AWPrg(hQ%xmL74&kLJyuX*RKVj(4+HElD zV|Ubny>@VMNtaW%f%rQEx!7`@B(=ygyMpmx6U?E5MR~vhd5$2^G8qY7Iu$XbLU@S2 zQ@G!?pMVV|LbDc~#tWgG9YfOXbCMA|7`f<79n@Y<4V-ehcG5_rwl)4iP{R-`SUsb< zLnGphmNhsenVNE}MesknK8YrZ;n*>4$X%QnDYs`csPe3*9<U4XbC-V*;|bvIh;cP4 z;q^V?U`}py<p6?2xA|d2KJbBnjqEJK!g#aU;D%YqJs!t~h~5+7>^U#X`h$Hz`QKOt zTskZ}7m!i^6Bun+YnEVV*^@7AVAzHH0%e{R?LZN;*~PL4*^f1LkGF!MHM`&_#tbFx z12qXoN(d8#%;n2V_~)YlznB=c5wEdIgKT5?jphl?&As(Ge$;CS?Ec=&Yz=&?$s-IJ zA-DlUVF)ugSKFI6*dg>iF#-?88Z^>0&Q)9QBM5xp0p73GFy<!FG@d0~X{+qPQ`Wdf zSbkZ8V)kmRm0V%Dm{`$yh-tLFL<DgX0JL%;X>FhB<s&o(py66GyXkw-kghN4$8gTA zEe_~&b@%RYAo7!);rV_H8(0X|R$vH8_8p?8ss8;{eHPov0Acsaz<ngKv%rU@&x&Cl z*MZwv_A>Q3fGD|;UZN@(;2tMMZupg{R|C<HHm+pUhTBf?0r&PO>eaLI0G9$9zdq=L zHMHwQu>yO!yEl%ZDCXiWeoYL9U$&9pqD)+-sh^u0d$(`0O0*CPk>pt@g)WGH7^0+E zuRmCjJ0hW4Cq<4;mrfbtmvtYDf$Iqo=pD#y**yj=6MFW5JN1U_(cY53w{DL(Cx9pj z?g7<P3!jAppG=V)Tdm`o1~sGr^N~?FaYp7Vp3s`+a8x_vKIGtKF8>v-75;#YsEW+` zFzi3GkoJ!Seip=p&Z{*++k?I*hKp~8<BIHe!=99t7e!i*)M|MX?h`N%;fH~AR)l)h z$-}EeUqjZVEiX<S;hNxI;f2bw2>5ZQ#C;4*yMs;;1Jb0R=t*cPn{aGMsC#I8XLz^Z z$g0}pn1Q8zv!HzLO$BOzL?{b;iZe|9zaT<z4f^$oarR&((}N}+3K`5N&?CLHLzuV| zBa?6t?phSLQH)lXek8rRz6N)+*xZDZ@y;X-(tfNh1!8QJ+0%SR7F=kiTn5#pW$saD z(Jm$jOYyclSGWXl2e>|*z1I~}Ne}xQ-no1GLiLDbk|tw(N>s>5p0MwcXN>XVobauP z0<`Nph1N1vA>TgjM!`TyXW%jbt{1BN-AYeBCn}am%fl7D3?sMI?S-;(J2pK;z|n&f ziPECh8BOnQoPC5fL~<)5Oha?Ksn)>pM(<C84X!RZm7;9DyY%H*J;ySdmn~)fL+Oto zqP#NnF+P<I%oysDifk|d{JdIa{SrHdWAC7;AOOF>q3=OHKhOWuY>h^zDzjZv@GWkU zur~+uL#;rtXFia1hj1R533tZHkj8dbq1VHH5i(`7n6_h}DiY`MR@`V|;4?x5!hg_$ zdnhCdjhW<~;*u;tOx_K*lonS|{>BH%tj8Mw3(Bgb79{5&Huq1}CJ1`aGa^iHKqgdU z$xatcHBa+6xAK=mg4#MYn%U0{P2jE)W4QUGd;Wml|C?a?Df)z&$aVlExO#5iys(@- z)MZ`wU=B7Dek%3!H<I5Oy^8X6X06^{fIf}|xkEvxLF_gCWAm`7!$9_Q*VBXdSf2N+ zD+cO9Rq>~YBFZLNtf1v&C<vz5d&^eU>d1i>l4o96jrghnK8Q67FCaDG9J@Z#@y=`^ zOCYEx4B}$>4%c2hfzShV&j@hSC2vslR{w}1IsITOXT;j{Y4RoPNJoy+YYD4v<k;f# zxtx|e+359L2FCfykcbxP@CE$DW?&TQ+Q@^p$a*NtGEXjXt(~mt8jB2Rls9vw8(%kN z)$N|q;*=j1Rc_XJxox<DS0o&MWG@G`X|P-_@eV#pZ(FXf>L)T}S*j5KvA1wf>I?)J zw)Ga7;M3{HXWM(ngwG>$Rcx_^`iSk7IYa?emI!tYB^7ef3pq5-$rcqBErE40Zy10| zUA^ic8D0_E&-<8Wes_MK!$01xIx~bTw?-=pkdUVZ@}Uvy%KGWRSNJtSDTlk+3aw(W zA^R}nY$7r^au#z0M{qRd!%tNioI;AZRU+J)^E|rt`2(pzxZl`{<(!^t<brU}#>U5Q zEJtlW^ZD~3j007N;+ZR^TJBc>wbz53ABpdTsaKcWtp=g*Ix8hNZhTp&8E<8x48<#2 z!k<z3o!8KEGgSG_1=joO%iJT=@X^pcXSIZ9VtQGG8`xx)i67w`ckn{RRwMZH^gO_k zYeu?-5-L~8v?}!P^GI|gb>N_7CMEQ=zJA1u&*Q&rx29obo&Ax4@2Vk?09^28xKKc7 zX8{zzd<4>{KT@lKA)s<$Of8C8g$4b!;^&>?FYUeG6$jqhKK-T5b20p2Z>zYgtGSjv z|K}ds6{GGyIU?lzG-Q?BP7C)4rC`Cqj44ZL3pM(qtqZuQ?)<n+9zZ+cqHg>Lz1jD? z^=AX<f+&h%{q4nL(kaFw&y&4EX59_ZfFE(^YtayW!=bF0^v?WhbIg?hmTNW}O78`) z#Rtt_HqX^cQx5WlSUL|5)%pedupz~luD<`Z!;Ark2JhdLTLt%HL4~D7u9IatR)V~P z1Qp4-9&!0kcch#DZB5wx`21Bf9OaQkDDdmrbOF-OzD~+n2t=j~Tp|MQm@sBh8`ERT zw;Lzib;P0b{cN9K?mc-;{AS@I|4rar3fqgPWD%4|UQ*%x0QmyT<VEdY7$6%7y2g{? zp58{Auoep}iBLvYO54qy^1-5S8FR?yUmm2MW{ttdA<^357mZ^lBZs3ak$|**ANzM$ z|M+I!rFH_J4{lWb+iDG9yv=$*pTTng#}l5R_fZ}D!=}cD$TXm2bT7L8t6*9@%jzxZ zTkdJzLXS9NPM--s+8I7<qv}#$rN{XpSTOyoe->1UuhgPH()HN&E*9+GO!SLyT`d=p z-`Amb$ngAe*{HQi(hby0�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 z�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�Nw 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�M%xUM`*mkvUgnrqOO=qGq0~pdn!d9G>+JY)zU$mX7r=f@etZ90f)We<^~S( zmi0GroH0>dPkn$yHcQ-@Z)lBb5C)L;TV8pLx~)4Y{k}8JH;;N!42LbfGN;(oT!LHZ zAuQa*?ZS#jMp1PF(Z<@9GN+Y8*=%p5{TKF6x|~l?5qUZ~mAMV;7ww<T+mUvfabeuK zY5>yV^VmS&?dRHM3%};}C!1{RnJe>WjMxkUF}B%wq~UwNKaGOJ9ERw-#pdp@O9jxh zWclkRe!2?!xb*vWK^lEI?YqlG{@l+;$8OYcYbu;fx};CJRkMsFoR!OpiFNd<Vo7yX zS1PrioD%>)RIB}h9m~@)wfRQiFV%iHtXn)2Dn>Q9WDOkt{Q!zc{T*;n^A>WJSa6ym zvK>YCp(8QhyfY*z96&`#JG|YqUtd7`q=$F(39qb_i>NjTY+r%w7hiE6a-}OixJXYa zfE~<SIQDg(;?dW@Y%YbZHE6PRnWXj?c~o$s^??L!m=L8*I<Sm3G6BXMUS~eOU<=qA zE3mg&pw&*u$#v#l2||i9dC^$YO5*u&NO#G9_}fLT4XAJSffgtfi|aQFo^P=7dAE({ z#u%!b`errU%xI3+lqi1da!@9Q395-<bG|DdGDbuCmssO+{t1qOzDvuE3r>TfV>uVl zd9{KWT!M%c3ZUX5zPSVYBu@RyxaZ^Srf;EOL*ExwMUxjS=#2PlZf*q2Eh#GHhRZ0+ zYcEU%{L*gt0a0jKHE+rUU?&ihNjtdFPB9r`3n_MEU{G#ELpz(ggW?-W=WZIsnAF4f zNDO-zo|#$6Hz;U2GiIOFN3P5CsowkPRzHDn#;Tt&goZFzvFUAfQMEKL=iVh9U{T!e z{{Q8kC$S9Ag8$sdfz2&h6_dh0A|1(Tr^_POH}D8@W8^L8z{wy)#qACWkY_F@uf07X z6q|kBe-rw#u1JBdz@2@r-@+?2$hi&@FXfL@vzUv5-Uo3`5%KF`11aQXVZ?77OHobQ zG7G=|6WlEk{zG<0#{o-w5)gDQTlwdf42roslCO*}*V)N75amr0i{x^egLHOzWu_iK zHegYdHN|~389WUsW=WyqaNozy1}&y*yJ*|6fwT^jzYnQctJNe&r%QNt03Z7|_2t!* z1s$mA^rMijM$CTQnn>{O#=UN|1v#}Bo*kwqK`&*M<~2lf&2SS$v-t(LV5M;Fl?Tc- zD9u(t!|JB0)g%5#k>2~&zDZQ?qU@cz>;RFw_39rprr3q2!f2FG2D8j_ykDW>UqSGW z(~p{!x6ghO2lw(P*r3xIM5QM<kWa0EO^{)mP~J|Y`?ZRvF&3%s5R(?Kx_dk=$ow9| z3d7z*)BYS=y4U8vqbp7t1M&_INw=9_<f$3wWq{h+G9~jUb#Xbx+KnThvGQF<bi8%| z*N8f2f_+>e5|4qX6FRe_GtapjyR&u`5k2=a%P`N?tOYz;W1Dz#OoA^eL)Za4hdarW zm#lwvS>Kf&T1NLB;?^Cy=h?DCz_Quqo8@J*xa8YnfopEm&KFe~#MNQx(C~GIE*erv zVM$1i?H_?REid;(k<LLdc0>0og3B_YnEdb+cO+msy$)C6Y>7Lo&|yL)0EnSfw6ymA z{V@5=Nz~CK9IM`JkdxxuA8{%l=pvd%+6cb678K*GQ2&L$*ZNuD|4!YaN<PRXs=z@D z0Ze0t3ff8&4#PO>W!%C>i)0QS77ClQpcyJ{A*?K6JXLi(EGCr{*w!;V)=-hs3hpwn zKk||9R4_&E9Qpg=jAwINY}2ICxlRG0ilw9O-yYjYC%I^5cLKlznBGE8;~qW@(4xEz z4eJm9#!TnPD3aq0Orysk7e~BprW^rf1DYRb`1Vx!Xo0=Rv(z<U1qE9bN%kE90SF4Q zVYkEts0hY}9zcQJR(6(hgBQm@)q~Pavt7G>Z~5L2?4>cXC=pg&e$^Q7bK$i*l*A+N zQW_kwPj}9D4sNx3b_C=O|A7_{oJ62rk`mSbq>q=gP{ow~W(>?Mr&kJ_%JZP6xY{Bg z;$H*PXqEK^Rposu`=(UIY9-SaGuSa4rqbtek9uyM+cKAp{mcA%Vesgpp>0i2OmLn7 zZ)~1Jh*U7M#I06sZsygwfUDbWc~;}E@T1P+0{%75l#(!IOYB9ZfvN==v%Zr*lawe( z`(1~<R`*NIr7+XY2@lENl*j3TpmP;S=?`e$ybr#0B9|(~UR!v7U$#J?g>HTK<D$=1 zA?it(P#Efxe$_AuFj`rVQYAb0<%v3x)q)zN<pNyL9uX><0|go``B)(f+R1~od7^@> zATkw^5Pi|)yFF^F_TXy_mA>^hs+t7<Q{#<jZIqfvo+STl0{-&Yeb%0NajtNu;e>bW z<Nu4!a&6^rRDRvxxUHM+I8`#<GdcK3G9}933Uf!3z5?KBq)yL!*PL1!&?zp;+6boX zGDH4DOd1s_?H8<`{2hMuhbC0rPEw^<oh6MEKWJ8CHy|RTi2p+=-emxl`E$_XuxK*N zHJV_8TGUX7N4E&p_bjFdIaelg#Ilj+x4k>7l;|<IC)o?J1`@nX6*ZkUq{_>F>(Q(+ zY#*Ko^9AK0FqZEwgBxm$i!xMzqz0mGd+g)v1|)4a#idyZzc&*5<A}6Q`k?0<UpYbi zZLSQAEBFj5v^!{$L?_j4P(SJ9(TR4(aJ^$pxw%&4Guy~UoFT)S{;)XWQCyo`34dE& z?QUv%d!@f^$b~-%i;RQzkE~Hx7NV~g_7K#J=^Kkh*ss33rAdK)@R(lm+H=6#o+xxJ z559AEUG>s+6O5&9gTB?k_$L=7rp=ya3riZz^g@93&5M8qle-{c$wHW>Yj~omJ}a)z zR477GMGM%gPclSS?^n0*NQjrn2RJ@Sp<IfPw<sv_yr#YCskkmJwtr@%*<t<(`Ug*U z7)VsGEDs5U)2KNo@7H3j23;O;gxYp~+2^jCSlXKY#LZxrQJvd;(%v@W-Y^Z~Klof{ zQCRKx@L0F(r6ww5VVt344)4^566e81`Pa1~^O`G0_lO#h$xD~sRa7*v9+!e>2lmW; zJ3ys&WOr58v{IlkpfA%2v^j)aabLK~X+u9iZC1KL@ia*jbwrg2*&upnG&z-Ck~a;- zG#Q1+oqjl2*K|L6Qw}}*$J5g}tRx5d|1PUe{o5`?zrlSx*t*ppQUv`EXV$P%|Gu64 zk>^eAK>!g|3}H$C3%b8~&4-RpcyI<A--4%;@EiknR(g<|9?O5TaflZT?u5!@J8bO1 zjM@ai!+iVBR5pF2PXLliYgIt&Zk8*VDp}a;l(+Fad*B}|y)@YM1nu_o0!eG6x{PQD zm<cpoKXeU{-wSiuBuuDr9Jw1yxOdJrV95(GEq*S$%4K2Id5v|e%@xHmH61K`j#r## z#hh+BG|AThEF=lPOHliM<O5oUuMO>243?c_4JxNW3<3OyWH8dD_8`Xm>C5S=i-j=! zVLi%6$cQ7}zJduT2{AASW@_^h)yIGdkdL4sOcb3x8p%pIWF^?A1~7uS?!rl~8WJg8 zz7VJ>OEABU@9GI$umCkc%D<$Y<>cl2X`hg0s1*EuMC_lF<pYf}U;$yFefH3e71EU3 zi<e<o;GFHh%c!}L#d<pgq%NmfzyWs;;5bss=kiekl!|A_4^7=ShsrLB)YlrQI+w#! z54;b{6PkQ&oC8uXER=h;mE6((lu@bPouDlvg2x1=7FytZI0QM2D>M<_?6{KieHJN| zp8T5i$~o$HNr$|~WOn|>RTRP0mY{iBKVf@S91X-2`xv)`!PD6L>qz;f?>Yx24!(;z zCM)mEZ|#?mVO(UjuqRxRv<8-(SM;2*Yv(KtZ7`QTOwXur<g9GsIsLf(`q&wqdkGX0 zwkK~sONRwgr`AJ@Ehw9vYmQB<X94ZOHhTgu^o`^Q*OgFZ!lS+*=&heOi@lQa;6@KJ zM>@)ubE@;8+hHSOLQ2Y{;y*aba>UWpxMXo>=*`<I^MIo=zeBeU`Div9pqmpMdUT4W zxxiw}owC1ojq;J0_LSwJ`dcU(E{$5J8DyxU3=iE{^Y5@6=6)F)c7f0n(#f(%mAE!= zakH!?_?1v{`PJIB>V`m}b|2$@A{cK5c$0vK2x91pld^>KxQwl(G~<B&VD@tJT?yy& z`B`FI8bIyc5stF$al#k)o5(VV|Ct%!@8&f`{kSB+umi1zEaPQUJ>V;1w_mrne9+UV zvr8>m{h;X;3h-msMj8uw4%%7QQGPcaOd*!1kmFtx3>$IMKFP{8NPr@aq2bMv<B98n zsi(e?h$$1(s3GQ11QZpN8syk;*I2!F?Scq!R!`G-C0dG|9%M_+(i6NBw#bNkP}7_0 zd>YPp<h#avjK`=?1Ul1>K7>TlLlA0QjqMBqeOb<BK3`uH7Ven#_45Y);CR*VcoZ^y z_L|=xUQr!-WViFFYMtG>$^VgsBs&mo7!4na+!iOU3SlKvTc4y4vClZuWBYyUe)I^e zVqBi?aD8vr`-J^)$7Do6T-M0m<P4|<Zr5)|@o_o%{iNPp03!88OkLjo4t5bxa=$3} zS9o(LJBsdCLy4ooGNkhOPHdy#i{{jGne#sDccBJz^N~;boso;>@oV(R2qMAq5nGn9 zm{|UiI97zalUHvfBgWS@D-UxubgtSrG#Ast>}(?$=>}H#D(4_Dr`&O0Z3M*Mr;$W} zMvvsb1-moHR??`KmgtXyL)&cHZJQ;dy*choJ2L-w&$XSLB=HB78c<JjkdQB=4%ce{ z2yF1?>G^Uj84$RNY|sXkV&Iwz%+tApzDWR;)^xf?BLal>!Yn?9b`_;8I41d2uY#lV zD&%k_hli9~*XUE>zu4!X;HNz0_d~eSa(m*8Td-kH?7dlon+dbZ6&U*aB)J@}@qq;l z+*3Ctu|kt{*efvD7SCK86+phX!W@r-OWE=$?}C|%EhivwPBHV6;v#PrFp0+e_A$qr zF4LmB07GHpwpI?S;Xz2W^)qqSGw4DABxS9!>oHzP2cGD235xwQk)mjo3ah<bc1c<v z$jLOkA-<gQw;#E74t*mBr7m~cFFKzVx1Vl*MhYu;WY<OoifF3ck0jOtIh*y4uhg3@ zbb7Vt)lQpB7!pnFDW!C$v2Ya_=6D^XyK@Lj?$;UUo^y5PmrZGX!dg@Yyj4urth0yQ zS16nL^O|Ys!>*+HKs@N8UjTsYEcJX%m}x2N+ONFh=XzX8eX#lWYTt=fyPr}ZLy+Rj znoQpP3=sbLFwy8)e6gIUKKE-K1075*J@`X10DpuJk-}pqoeehB0IZ&L2a<~6)*y|F zC8DanMzkXJFpQbBPyk2AeXpgXVVP5mjgGrAJUoQc=aEYby^@tlE{_-hswXz-9O*Ln z$8sq5kUqd_m!XG%{RF#!G!T8oQ<#`E<g5s9n%8z%Xm=y>;YH5zsufTo{<Xgb-EGv3 zw(FZSy@zi~0V++2pml#1IKv6<T>g2ZE{ukm3LshLO2=^fH2F94Gx?os`Yns>1?)RE zl-9+Y3MYw&c_28b**X2S-z?0P{%sd_>ChWRvS;0i4inm<5m0W_C!PzFuCm}rc$RR> zGN9v!DpxM>78=kFPW2SS0xk$WN%G6&Rh@?&#=QOo>j?N02R}4JWz<xuxka>L%=&(% z_}JB`)9*oN?I*&i>dJ*wfVvU^CYPjsg^ljbFj=nWA{<-hPTW6WPs0I2YKUIlvl83e ze=W{h7Z?WPiCnnH1L@zn!zdX4WM%Rv@CKtU2ewS|n{mit4WB~7o025}+6==9O%TN9 zl@MYzRM~s-mZ)14Wrp0E+Z3+^k!ZFu>Ck*Q33{q|RmJV5TFrvi0sXaLOb%TUSxT^D zVEs<pn~5H(9RXKre^fo}XXz3fDWsoG^jj<Dv$ZXz{`38Xc_Pq!)!^(cAal)HK`=o0 z7TyHkpm6Ht$Pv?4i^NtJv>oG9v0#6SGBKXlF`CzHN<|gFs)QGZA@1Gl8Q$Op<TZY0 z&IR%UMvr7M6fLwx_egeBR;xse(1{w}Swy8?j;$i8Ej&ZD&EN%d>)pfTt=$y9r7a>W zLz)^^N1szs4ohL@^mH<zj+cf{rm&&^yzFGbv{7{3G$dX_Yg}@pvvOVDcmo8yS;1YV zoO#msBvTp+kPupTkM<iF7{X;k<;<H)h=&Y8y#tI0$SO7N)=mlg0fzJMR|oE*9!8`{ zjIc;e<0&_XaNdb7BH%wCW%LpU&-^~#n-MIzWyl6e=a=qqdCO_!H<?5#aPZt>@`cT8 zoMLQtKFQ-^*8y=-vUS-jg{sX;9A({hZmoHsb{d9Q@JMU+GbdqS%$`M_&M=eXB|Ne> z>z(67?{MDuPMUtQx2L+>o_*4OJ+G={onlFaNyWE8H`(3h3<q=D-ZsaYoC=ZHILt}K z3*;J`K?E#Y-5%qLD9ESJu(<s~#b?CuZEnI!iQwhIlt$Q>ufAASpc($C>`uGu+>u=O zUL@#K*5vq_g<Uhy0u5|JeUT{}TBO@HkZ%2(s$ZIRLmexBh;_!bqty&nB8XC|3tWwq zju>bPD()?}jpN3rXNmRsa(R|kQ>%Ptpp+nGwoYcc794N?E0^4xHASf({n{}os#iz9 zxi!zhPP3`j!T~3U7{Z@)TdN`&XY7lrMeMmE9r~<kKv{lIjd0tZ(0P#&{1F*TYPR}H zj~nSSE46aUb|K9nC17BpM*oU&#+t@UaKovCsRk*#jb_y~c^u=*ccMZ>#yiN7<l6N1 zzM|Q`3go;W3YEq0TWN`pimC21s*AfAPX#;V*P>wMZ_FO%6eL?kIf818kDBK2pe-zq zTyKsF(O{?&Q8{wUb^iG9u`0qB0=usRzht)6$s5)cunQim8SbCUsLaA&zn+Jt$*7z( z!Zp+Vn50mAXuIGR!Y+VIl?bA*EPyO-$C;vU<u+m>pQq85(`VoT;x79`?&)GeOJv~J zT=3Fs8wiC|6D>i-957JbJK0QKw@5C5sm$>Mzt&;*ep(w&E1{;g&fBb>2m&qg%}0?= z&z4qK9<%)?YkX{~oTx2cH^}r<j*_uPS{ptV7j9L-FIHjRm5YKm=Pe{ssnzopS3>3- z@WXxsv2u#AKRncBM-zC+D<Tg`C_7hE;RRi$l2Z8Kv;M{>$c~W(LowzZE3fssWld{d z?IYEno)BMgj1R=iJizPKe%~*;jLF@XzTkHHzM2x&$qX=U<^5*M>N&H^t_5RPr$plY z>T?Y~W%u{RB`p+|x%fb(nu7R8>x<eqtB;LO3iY^=;Bw1R7u*zZNytI3`pKH8I^lVo z%%|^Q34EybCeAo;J-Pm&a1gSSo;>yk#e}Z!918%GfqdeJ0T7uUN%>5_30XI=7vdp~ zT6xuIPDY7KxFG(gfxk&Z+=wUz$oI_R2$*l#bt8=$7(N;nrVy#a|FCaKdWVCfa=Gal zYMNNsfe_=ka**t<e|m*LL!^$x_)OYN#k`X_pvf7%W?bcfQ%VjtAz1@=l~4M?;2fKD zm&m$9B=8LQ+469!b#uXg9(I@aFIJcyp|yxv9Jx+VNm(VuH*j98gXs8Rt)e>2U0Y`{ zQ#&0nolXl!mHV%02uo*`lu9)}?z>E`JV_x7#AFcE-VZ9dGL3!faBhAkyr`P2A$%cD zABte6FS8B@XZ#&>9c>7eqaV$Bd>)bO7K4|2#>*_^dYniM*T~6z89&U#XHIW8<rI_W zE)y4<^ApL}p<Vt?k@biuj5m0sz0`0hd<6+YCHwi$6Ux?hzEA0sYWQYPXbEd#*)m&7 z2A%e?(LO^7wN-%6x-`TH9$(WWED}-RGYlRZ^0`hQYZ4T6!^`&Suo*9e5G)c_>#|UA zm4E?|i2=5*Um3HwR)s?yw8RMQ1YNI8G|E|32p`i!0s#ZNy#>$YZ257aOqw;QeNMD3 zApJ@U$KRPc>zs0jk)jBkm3y1S|K!DOpz%}HfI3ZRTBcJVqupdEYaEpwffR}M-LWhX z*9Xv)_j*|8yu$4ddR)Hn+vkTUsMC;@QPx7P?T`^OOAI^hGD9N&jxKr?zsUT;TOP7N z{tN#ig*4o1!<u$hH1EH|ov2GY0i>XGhb^DLC3j4<6oHbn8Zv$eL0R}*2+T}S!PL#8 zN$)*@W68B4^+;8}d#<#y_XE5($@qa(xi19@V3S{3KX*To>edu%(kW>I;Rl@@F1x4o zVZJGVmysfOVbOwkjk$Wx97(wqu>6TNt_CK(0HvA8DHA3om|x+Nm&8H2>*E3^ERFg` z-f>qJwfsN`&a^l&Vo<4XYIrF1AL9EX1zJg<1U4e>T^Hkx_**4|=EYQ$Xx^*ZafF(r zauE|@DHQ7exDv4&53eh;#;lzG)vjj9@<<vW#-B6?7Tz}(u-?S*#UJb|b($5BM`k)E zH{S?JaY&reNa9H>N~;Udik11@^U42;jr!+2f|{_9qH%Q<9<_2=sU7*_AkkLJAv!zc z+I2XswlVO#Lr1S9T6{dcd(_bR5VGeWsO>f5jDD8ov6f<hV+_V+-dB~+U)_WW>qFHA zzuLy>L6+sB`$NAcFC&+vlKj|8|2<EKko!*rgAegF8d)+^@@&f1#U#YN-g<?!CcTj{ z?1skaa;Lq?`H!s3dUry5G}MF~SUp++jneTsN&H7fU;QrS1^~%q4JQYtqM9NenZA0j z=u^f#TwI1@@9oWLBnqQ_t^p7E2UhC|f}G&q6}n_g-=1r5OzO~uV!$5dlk`z$k3R!+ zLt9laz}spi#}Xf(tU_Pcs4WRhMxxY<MBG2&Uqa5W)=kyNepBYwr68cnV7nd1ZZY!W zmR9e<|G#^*&#i#mvBPBIVpxe<J^;O(iC^p&qkh7qR%+;bz_gv%yF(CD_%v9t_H)`C z6VGmFeG{1n;lpGK9pJcs_JBQ|aPk7ta?^jBP)Mni+NB*c+wdud@IL64oNU)~J{in; z^y0l|nn7!y4kk_iY07{>|GM+C=ZH1tes3=3WP2u_u6?FKXR=Shqpf|<Kr!M)&QN!o zyd+G_`=M8ln%z@|90c0!Hp%yG<r?lV2jApORT;w_Vr{E~@9)%smPy{e=mlyg@LaKe z)Mh4UggPJ(>etvb^|1PL^40JMKJqCfz{hnGIh||puNjq}Rg1ZVA&bNSNjE4j>+vDk z;5ng?y>7<H7a$lM1bOX!8F46cg(GAYtbI$WD)DmKiTuXpniD7gM3|*yTcx?Ca<vL4 zm6oDcxKgr)GHcIQGUQ%F3^pVK$nU-Zv~D33IP`mEWhvW&$kT`pJrZ0%T}>$5H_hpi z%LT6k$pH2<J{D|Y6!g#crmwmUT8VG9<`qiG4!afM{r7V~T2pImpbsT(P%j6mSi5F_ zz*-{dBf(z=u$Y>crc1ojwN2?GddNLyDvlM^yET3Y*w|{J;?0PUm3jrKIf;o;+(!JH zS9~B%RKAYeA{Kmh(#8tM2q!@%O%s+;T~qHC(6ey!)*gmM9N9)@&sbFa!{jR@_n%s| z+~7{-*ZS_Y5a2OFN!;ZytHaK;i<eFiDYh1ifyAzdz<fqrpZHksRGgFscUQn4IC9H` zGDVaK5;!=c2x!Adx{eK4LZmMRtjn+PSFblOERZQmR&hLH(+(wqf%sbQ7ZTi%Qzo#M zP84w1?8g`C$h_hTAM7YO{C{MlXKOhj^F%36R;J_KDtCR+U_?<pZ1J^hR|?`dX|iQV zDEHH>B~byE4q;fKk~U3)q@Xq%iL2@&gM^>*^_egGX`rX$pgMVV$)<r&PR4&7*@D}W zKmO15uw-0V7<2BJuS(}u$Ue2sM#7AhfLPC;<-E%L6KXfdX-!0J{d>2Y2Pv^Bk8(Le zqDb7#plTBLt-ZJZvrm+gq3wSl_t@g1BbqcfOs{U2UMECYH|C&yswdIrqX+foL|0sU z2+~{Lvp`3G*4;?TAX8?=QX9&8Di?}v(0>x=&>1G>e^`a4lTrY_l*6n;E!Lot_15cE z!^CRQC|vR>={rp+G>E`jw9m!lr}`Up6f#!thy$E<<f;01phyR&&BSN7y{-R^yb)4b zJIlN3Ue@eFQ;h$1vCrnnnM8e-c$!p(JHg_K4bYG%$$0>82wgUnCVpq>#R<IH-oJLO zB<=5ip`Tx9->B%{c4<@4+-4?5@WiGBlEYG$G-3)1Up*4ip}zAozdVDt$M+Cs3U0@9 z{q~T>I0W0T?&E3(a5JZ>Y22C5n;+2+ygav8mxBYD4J$ukzAZR~X3(4j@9>t5`8aT5 zsy-;`%hO1jUO93luV|C}pdUPklyQQngxgoB1pOcfNxqvMNk}~D?D5)j7-u;nqcw{c z>)`|E$oVjC9nS?41f33USFg<*9*D^G@6qH=VjJ@Y?jLRf-ZiAt@a<NvDm;NO`8QO_ z2hzbkgDRKlO%JijSugW`sCcLpU69euHY_i7SZ_(Q@k8Tyt5g2m;IQuRw!4bSk<<Nf zX_hK4Q))^ce%VL>sRLy;r?yL=Cbjy~bXvW%t%}mg3UP8kz1CRquQi({L7$F@K2}Ht zsiW200G}cM0U+4fI(uGWKBOf6f$2B<EX#Z&m<g}cj?^6TTddZrIgI?JE#Mf#TN{~H zI<loxM>;#Xna#=Dwo0`dxNZ6Mb}dc6AX^Ei!V9?b5HpBwv^0NfV;gXxWxY-$#tP07 zi*%OeZF0aP;bk6dNBHJ6RtIikzC0{{lcT9ei>CFT8hq97cAVDY6llA*dbq$wE7gxr z{H(*O%Dx$n=i9-R3p||pr}qFU+wDCn3ku}-E}(rWJ*BfWh|?hBLQc>jGd2ElZ;*r8 zqo=79bW=bq+v-M{T)K0^>KJxq34X^u)*x1TmH*V3p}u(6eaT7UymdMdi=cq-jKHa} z7E*D6ezZdFbZzbB%#8!;^v@g+(C^Ib?fIzi9WvPL&bkUR3#g~7CeN%;Yv882uY5XI z5js?%R@hGDvB5-Fahbfdq%)DmmG$<81D+!7X<Ru|Tf5oO=zPclPO9qyC|&Xt`WnhF z>GWQ3#H&$ll(c2;RRX=9fE^dsF|a5)LkV!^UcNBkF@k7-l#k-F#0zy?-WLk40%nU@ zLhbO_!7OTI(6XbK!5VoEaU1c5IVWl+x#H;FPtkfz22^RNcmn>-3^_z6)RXSP5)ceZ zUuBW_q{kYvF!B=6fI!tHI7~T$H(0!`VNe?B%fZ=yq@E-MkGSZm@7yfv#@Et+$<MjK zer*%;S_YzuTQGWKhTDvKUf4<fO{W!Z5btS}&YCzAu|+I$OB<cu>Q__s8Oj<m>{wxB ze|M`5+*-cJ0Nits)!#>N|4x?=LrlQ7W8ok6Yp8wuZ5eJBk6D?JDWCeWZE)DX=fZAB znY5#0yX4b2HrCrzKyF3ILQLNXNgS|?fb9i73oD;N)n~mV<$H<~j}WB$ek$4W(F}28 z?moh_;W65(>+37!A<B4dhv-_{viLUx{VTb&z)$YJ3+hA@@O9b`U+}X63kMt^LcC&J zRG952gz(6N7`p5>hgKlIBq}I=@S764&Db0TeozK9ZEl1|b!rchv{-X`gT;GQ#@^Z> z$FYco#ClGZ$E@J0eiLz#fx7%&*GJ>d(viAM8qu9|0XuT4!49B>NP^Ep+&TOc-yvLO zWWaG_RsQQ33#Has1*Jy+b$_rkc#IPf&)ZWm2Zj(78OWRlzc#y<eyj}(?324#5r2@O zl<Ql?JW7SZ0>HreIjzy7z!3a6axeQg9_e@xzuxXY2@k_joYBmmHVulGvtn!4`t#x| zNO8&oYQOYXrwD=|g~OLqp=#}UYMwp58Xrb_u!1LszGH&(7n7WvE%+XTq7~X|dAQw{ zE-ibs(VdZVw&<M(TiQu>YS}&1B5I7F`UHi;xrHpXGK*5}7@W}1c54TL`39ygk<;c$ zo>hYrtgdmZ*-Ju1Nc5KnY>uiYQ6b0NZZx5Ob9Wp63@R9+goKzX90gR)G5hLDm<8>a zNzbt-*nB{Dz{4{mlc0NHFX{#a*mF0JA)K8Vh}Q6IByr*Gt?`C+%)qi9^Dt5S#tIXr zczKWc$srG?Dwt7a!4uMlb*`?INfT+tQs)X1XZAFIn32|G8o#*N3~bsZ1dy2svphNv zj7&fo5N9!S7jdW^7h=<;6c!LsGCqYcw?gcjfW7M1>Y8z93<$PGrE)&PEj$1E-%eo~ z?t32%I3(oRumsv~?5F#FA!kl~scZ+!neDxL&1StxI6bwfqmDga4_>YMyq7L7Rif%| zo#Ork`txNjDkE7{oogHLcmf=TGr`blmtqq5nA(wyKO&Y0A3n{5gffGAmj?Ba)~vTB zWKKhAAMD%OmlN|f=jj0rWB$0d^bW@k5FJcLkROzyD4p`0Xwk3QeexatB|ZT3kOaQ^ z-ck~gT-glB&r00nz^{{~Pk<5Zio)OdI9}wZpZJ@!&k?7kWs*=J;$R(8>6mdaHf_cS zbaebY8Ng109GcI3TG5q8Nvf}2CQ;quklE5AMolY`bu=n%eE$KTF@3xvuP*b^J48p9 zps9k)SnN;UC*cz~>tN8FW*iAF5ZU@kjJ3x*TBvmD%|ouZGG0oydZnozD=b-E588M? zFAfj4;nY_fB9-ujBZ9P@Z4XerHbHS|o{d;t78tcdsa`O9Kr}x?`2Yed0Q*f3r-RKB zZb;wcIDQu*Z=hqG0D~Ol$_Bb>q*usA;Na=UFNi{(30(4{FcDxj(<X~J0W$J{{xaR_ zlVf3VA7{ya>nXuGf_n9oy#l`Pi|UpAREy?}VScr9Ta4yZc!3bWOE}YrO5v#f?F%sb z8vyf(kj?L0Hb87YWTrv(5PD;ZVwn3(p22h~aR+5tCI%MwM_Q=xrfMA;u0O3a?u5Hn zT_l5J@iLUm|6~c-ECpd%lkY4wc1s7tbV2iQW!aW#g^3-SufgImt~Q^|6LZKgwiE8& zwlxS!wJ4WqBbnZ+MrV{ODo9fgP0fZj<0yeICV}yR1N{H2k0+eMdxuh3?$8nlQh=BA z6VQ<@@xpO;yu)`$M(Pgf$89{aF$I0c*!gRP_6?Hjb?OM9j1l0Ct$|{hHh=qOZTVzb zM07gCsIuGXl&5oH7X?at-=AwuyYV~1pK;2t#<?Q_+v{LWkUPe)?sqm}+vM^o{Pv=k z7o{VmP;Ou+ki3#dX0Lo2!-ttbXAQX+1WNGFhF!T24;YZR!gt?9j82W}&zb^n7><SP zIYeL}S3%$qf^Q5X3aUwDNH~&XoQe63_{VT9e8)w<>bDQ|H&z(~%KF$Q;4Nws2-#Iz z4mgom495JhTRG35gG0XOmKBqCgvSY$J#7HMvueujW_?<nO!Fxk*348S*ZsO0&Ij6+ zdA|)C%B8&Vf_MS#xks<x4|9A<2Fr<Avyt(!WQwl5Q<i>>q4}AQk$s41)BT`nz@f%( zNtQPio0CjyJO~p53!bEn@zgWw`XO?aTw@{m>3wNYlv)vV{f_{(ak2}E`m<E|S_bP( zI!t+)nj<ta@wQgKN_Z>>oO1AJaq!{3gred@uKWWql|F+<+W#y}k8EUk*1hkB-p2-> zZ^5gea+`b8_JGzwdi3QpD`A>>q{*<epxZ_v9#P(~tn(q-hs1QITX*CLqwc{}<P}S9 zymQ=a_q?=Zdsz2?ueOY4>joVW#1%PRiMzBoUt;5d;#0pA1plZ{UFLzcS2@1^g}-p# z^70fc89#!7cPy4!Pne|zO<xXhowPG*Fsj^3W`ftO7w(&6+hjnhJYNiFF@w}jS^DFo z*@MM(YkY&`P)}N=UK0^T>yj)qLISoy(i6quAMXWom@e(&ZE}yq^6mdkqD)O~R{wxD zF^&<`r+Dp%6%qJt6-SDrrnEO$^I6J?hRAb+P5t=*Jf}6wfr7kYI3=^|YG6F@A_1sX zj8wt9EazNa!>k((v|(mTdCoiOAX0#St%Z6kiM5)_MqKA<b9miv)wvZfz{GF}S7vvj zM^!XuZ{*REn41db8O-vpC!qRg>8!9@D|4l5oU(<%vaty%`JcIO*_vmIOAB>U<+%Pl zaX0b@5BenP&P(qR5Ja$YTFl-I`0%RH#m~{>Ko2@g3(b=#afW&YzA#%&!XMe>R0&8m z*DZ>F+=(o}(ww1){FyD1hEo#tN#&;#D?tSZ^>8cm49Y3POAoPX7mo^qRC0jnhmD3s z3u1aC1~N&nx%pJ^JsT?14JG2G6F=Qv5LOrR?=lD4>Q-10t&}aL)(40CjYNyy8T#X? z*T51<mE2Z(af`^EBWF~kj6AS7t<JRtDTu)WNc*X`)p$*~c*-6(lqd|VYcA-%31a}r zNu<9tU7TihHA61nsuv0x19FZPvPqoR_vTDAuJ_(m_r9B8GZMc{K<|9|saBgkpJyfW z#?yw$>>F;{e-}Stj8J47pOl1#V=4543GK|1w++-S3>2H$lX@<s*wXUz0u{rMK4}`0 z+*H<F_~^zj4mdn$2eDkJ{ZCuIEXw#1Ge<|m`yaZknYqVxmAaKm%6iRUX=I=zzMqsn z?kk9sF7`00KtRp~#8gQiYLzAcdQoB5-_k5)k~1nK|2+K<8_lFED%e@!`@Vy9f$P$w z?wklnxz9=dQUb-}?71I<_YD}Oo`{R3ll}8{jnPj!)v6wCNGBF}d4{N@Z}}|z)Z<Bj zn1X2$9*+{4bg4jnfF#4x4}n1inkg9-Og}Xijk|Eq+A#~8J!_72`DQ_Cm`A|32j+dj z8)OUIV<*nYH2(G1M=;AjSyT;Z<mb<0UvwtCz5Rq*cAL%wcTvy|7~2~A6EO%VnF-9B z`-R%&$7a-#j#@=3&2yji2Ze|8yTZBfj>lzS?j?9Qpq17Uovex;w;P{us3!TMadgL< z3jl6E*<_N@Ii)OJDuKQnwFx_bEqBz$ga>3hcaG6_zG#NrN+W8wVn|m3sUVJcrc&^d z=%5%xWi5rIHgqQ70Uw0*9O-{{;=Id&Rl_u;)WqV2;a*UmkaE(nF=z#Iuof;82l2Cv zNlbMu>ua!iaWGjDS7KUvtT2?#)6Ba2_twx49@G6=pjD_u_xzea?$_Xm<2SkzHc=dp z!^#15sz|l+UDLT0A))SAIX8l5_lGgi++-4tbKQZGeW=W@O#43Czg5<F{DmlP^r_BL zT-lK1-{%~dGbP(THduG_c0c`X*AZ<8-JkN%h6PnyP9odA-@L-hEeDV~ziMj>`0UaW zlNkwyP<%U97>OHZv6*;O>TLWLw=@5uCetMIi`>Mg(F}Np!Bk7h2$8f@LWftb3!#Pi zF~`q7m)ef0BbPwc!~2NQ6aS@HlW?4{XZNTPwfKPinNg-ud(66Km(Cm=4iBg!s}>iH zI_D)B(w~+wzN|hZcs8XguDX0aebd8Hfj1F}MfoOH%9x9M@KSBo&*_8v7WY9_ABr?( z{;9?5F~#|2bQ6w1ARF?EHV8V`bT5(~5jp~YicPIM<;nV%(4Iq~Y3H$u0vSbKeh|c1 zk)s$9p!bOlFNR^KEqQ7yu1x$8s=!b&-N#cv)SrdmO+(psxubfiM<b*Gy|yA-q3|em zusVgt<@R$PsNCyJeg?^e7Ky5JoT0`9>tkipI*B@|y#qoFaH+LT3a}T;`H;yqCb4^# z^TyM1iH0F6pUOCh%OrbV`QTXLK`J^W%~yX0Hz$_>h3lM%<w_w`|D&?qn8hLUM1LU! zp0J0R0e-dHDRa6j9F!y@hIp%QG|a+8L;*SnwxxSCZ+TtBkE<$s`gZQ5BeujjpWgk2 z$PZ)IG_110p5{z~@{ibzVkAqZX?mWJFqlt^1}694aDjkp-+<RB>Cof<ujWn_Midh7 zAML_SG~KS39)VAnz92AZ)OU5xe?|kPKwhc8cKeqW-o}2WS6lhK<Hm`B7mw^Dj3cKu z?*nyN2Ek2^7I>_xgw3WlZ3TXp$k<!(B`E`BJqr1Y-xlR43y`ecoA0BBJ}E36eO2u- zNw0&W889Lzd__m5sSC8zW%Vq^lRK5GPQzulHTrpUAgakqvaI4N5QgTk8YECwL35~a zW~$R9x_|i)o9-=x<LCvQ5n6dwZ>Z*I$R@F;Q|~agnr%<EH7?H7{~FoO4H>*tBLiU| zI@<i3=fSjTgPjSGE1(OO{g;U67K8?^_H%YUQ8&VhS5S~q*^V~c=NNV2<ml{G(FJVY zzPPa{CVuoxZAl{IOMwq$&T}b8byJAmR_Q_n2Z~lL@*Dh$hk3^*9ND{cRUN`rHDWW- zia(H^6-lu_;yu(9gZ<nIK1k;4{&X@e8kMKT5w7kk2Tg>avwO=Pxg|?>@6#asV~hV@ z@uKHqb~ynCg;K{P@H#ABBQ!JQNuh8LXaP4^s~vDDJz=N4jrAL0zzf64^rXQ+vl9dP z`u_oo*QId>mWr_`SyI*@(t{$!0WvM;ZhJH>+m~@w|K5r3!XACU8+P5ob{W5M3*NOZ zW7e~m!^|Lj#SpulaC!PN5PM@;xK!*mSqWqVvtE8b&D6K6)dFsxJ2&$v<G5)C23`#2 zK*OYh=S^SDAT!PiMN9)o&@VQQ0dR+-rT8>&Ab*VQZx;h;*>a4MosO>{Kp@fwo+>3} zNXEDh<tPEiL8vo9d&FOPXUngj_62C)+xCEw-FK|+P#$$Vo{32HF*Y!a_l>JssNIf$ zqDa@k!bHPF$=V0vh+Q<`A)+wPZM6;6z3lu58`M;q273X#2;1dPk1r#JVL0c>j1dCc z#NIe1V`tLLOx-C|aLd%{vIgt5XnY0PJi)32zpx(F)aSEncRNYS>7k)p>svC5uc5Yn zgA$K`SXWn1vb{~%7;HEbV1G2ip|KXD)<o@6`U6S3*phXK73-wR1FBh_Hzt(Y2j(Cd zDyG21$Ng!(!Fv@O*!+>26cS_sI&Lm8(Uvz}KNKaKcjV0>L^$y?7z3xXV}S72K+@<k zdUyYBLZvnYv21%J=mBT!@I03u#an7y@jZAMB^ac4@oQ+%JL@MPvq2pxzxn^Eo<G7l zbfF?4OIwG<u1F!c{%<0c(*4Qj*2nd~q(b4rOfRXULGuFH<LO9;y8%jWrkl9%d%1zW zS`}&PoF)94n{<-u*bgh0*J5rLdMi1ZiMI|{n>~M(_&@*^rXRA%U6)wnF*L@ep+!F` zyn<s?6qcfYMmh|m_vWGlRb(2maCM#(Z6zEk*%IuvZ$j*>mdmaDK}XqcoL4B#UT9Wu zz(dLR@GmM14|@<fM7gEeG<`}5yL)x>=20h)nsnSRwZ`1ePS5i?tVZ!rc@Rj8UTxo( z30=mAG{vZ_W2L$M-w>FNP})%=RzU$c3`mxo487}QJc7oFEkKTm2bodN2g-K^p?q)* z%UjVaDpB<7@*?b*kKu*urppK6CWu`;;9!h$5x!)j5eWDtjgSMIqVr#(Xd2O{v#YI9 z%a|SgY*`3GmJBZ^btS{Y55m~sLD=R>ZK*p#dt~ENYeP24R!*nWZ(-TnLnacAnvuy@ zm27kU43*=Jw%+7}+)LT>Q?i5X$Q+Ov_4sN6#>cRpgi=O1L1x6`(O7g<*q_UFC-%-Z zkTd&k%xS^imfd0;CzbM1fyhHF3={`?W^O2Bd#-kLcczNfNA*%s5N&P`1lL|E(=u)3 z>!K^Xml7z%8%4WEf=A?-ehIZuwF|uFtGMH^bdmewqBajV{Y;2z8s&45buh!MR5uWT za2;`ZgD`hxM=zN|j-Srsa~@Q%p>}eVPXml82`Yv?x*PQ&C!Y1jeLQOb%H(m8uH@h> zgbW1s6UjeI?N4@!{{*dK{07s4^_B0>avo9`>0%GSE!yQyQo20gMRbJr@QA=bco@!` z=R*-0DFrL%>4j5Y=6%7i6PEM!C7=Tl*6a?s>N&KoB|<wdwQnY|_gLb@?^~`AhoR){ zu?ZX!{_}%~8cJ|#G$8C$SSbtz_te+N-jzO=UbN+>>%0XKWMa&xD0@&8(`a;RqAo~J ze6fm^>?B})m1}uXm$bz6KcwP6u<6-r%TSkfG{KmLdRTwUa$(YQ>efjeS2avXU;bJ3 zsWO=h02UYDkwP5s>@Us_x{|@p5GWnCp-tMs=!h&QkN-MP%1<rwr?DVyF<I(p&vH2< zGv|A<DtNgtK_SL53a{kWy&L!_2*G5_Vd^Qof#pn9y2ZkS%0VZCSF8>eg3Dvf>}JMu zvgLr7#YBO78++v9QrS1tK+WJWNDlFjnSiGMbBRN3tUDM9YoZnF=oCj0|MR^u&`p>w zcT>`}M-W@;ImkCTKJXW*o~jH=GJ=T6p_ya7@G@riV+mrD_REInUm9gnvNLi_Jf?zt zLumkcD^vK^V{(j0wyz%n9_Sq_H!c1pW}AJYy+=i;XS`P7jXQ>CDB0xEG9tF+F~o<j zqbGdNihp7$SgrwJQ&wwRXjWZ#BFm%4chArmngkj%$!`r*qYP?FfKQK>%dXz&K803= z!v5?&JN3*XKrMkF{W8cvCA@o<Ag!OV1f&ASDIk8KQM(_<%cpM4_Elq1U=TGW@v3v| zK`N5E|HMOeQE961K)%(OW{odMx{MO(XvHmJC%Tpsif7)Mh%YdZ-2fN<tjE^b%@~lG ztdXn}u&~h*Zij2uC2{42HXb8WFJ{KL#o8p(fzfo+6JZKHWn-(dW5a?wiu^@8^<!+h zC%_6GW@$}9fw4?7GAmbzJOSmkWr^l976ou6PmY8wGgIFiNW!H84oY0b5npAW@pTd> zfLoqj*ys8xgscp5BbRbD;q@wYJ0o$!z98~;ZPd`k79mldm?QiX52u{gzw|+y<eQbM zlzH~G&gaH~;j8!2*(s%((evd==&VT1a65EH<4fsw{V!I~c!Ag`i7+?qCrk8lQg{^W zD%J(ZcUE&*Xu}%~OKgAebIY-!Nh!radT=?mLeW(6xjEm6UiPU!uDXjA$r`;>lJiS8 z3L1i?x?IRCs()X7Te4NzA9IGqjLK&LsC+V#Ra=-C8Xm?m?<JA5&62LSCMCQYlhFt= z1M#ma@@Mq=yO?G|9x9Hk%7pv^ak{q65>pf|bSpLjsh=g8_j$|;9@9cgNkFTkvy2in z6(Ps&&=aD`#Gu|r0S8`JmBP+~uB*oaJUf`Jl3@9g{SaL1Q0FMueZgK+Ua$O?DNyNw z<QW<v+(PRld;(eeZvcRcHvr@C!iD^3UpxAnEsmJU++JLQzqd07CHlV1TW|5}El{TW zIpH;-q8Nw=iMO{Y%;e2xu~j?{Bz+SDU#?*zsHS;{?Jf*h6cmFg-Gd|ftY8emyl2cT zDwq;Aj-gZLOW|11S>!k3=7UtwUXDw5cGB4zB{6#VK%BC)XM7Q%Rwp&I6Fa-qw3g{r z@12Wt<BBO8=|=Mg35I`*ozL}VSe{lEgoAk<d<Vh3$p*H9TIc5;fmc=Y?I$e4WMmdF zu2tW%Z}my^bmGZYY`*;RGA7S-x-I+_H%}XGD=UXUD#x1VI|mCpmS41Y5o;9%t~i1| zhIMy=ALH-<0CN(VN(3GUO_KTte#Azkf7QB`7rzgTqVP#Mb2n)2#yDrm0IWx^qDM$y zXU&6abJWgSKu^zyPp+AtPO0;YD18J9SuBzUG(;AGDW~8xhnl9B`Ksoc#fd{$v0Ar8 zS-{)lu0?>PEmUc#uy9@mU7~7O*>Z17uWr(q=cXg~`<sj^BQmpRh<h=v7YRD^@&|UD zcgK9p6JGk226qfWH*OVBE;y(#u$I7W59#eG%P_Wz8xy&1YP}_luI@c}$~|)CK~J!+ zsRlLt@CatP9K6i0D6P|l2-Z;le{z&VH*f*0Ui~Ab?)Kq~m7cVOcMJK`n{%_h!cbg9 z0-5EOMQuc6=f=eLwG%)Ub^+HwdyGk@8}C8V-Nalf_fcpN;)H>kZs@#|Yze^}b`7V= zSj$6v9ZYRU$*7yH2oop`4G8w%Zyoy3*>4BLz===oTHfT#blkj!A_)ZeptDYZ{V+s4 zoVrmbU1aIkAS$tTx*`P4AU=Z~mo|(g2p)EBj^OXJ*SswPo2nPRi&v2S$kB7p5w_o; z*=h+cX1%l_$^*?o&}rG8+0&&4&9AA(dp1dXP|qd#VnVS(>!s7o+IH`G8{L+DljeI! z)3nhuHX_v?k2&xWncM>#7&Uuf%W<fy<*S<BIc4KL!)*R4Ex&>jzm^3Xh#g62Dj*?S zki``77&;-O1mlm~pR9osk&;kLia<`#0m?wVL5nMts15<d!gkpHnLRN|1M#htTGez} z5UqJWm3p5zHlYK>9!T}fMJ+{Tj71LK0jVP?dIUx8ei15z^;c9L#Xt^{ct=_`ARdOw zlZt_V#sP7}%2Q_iVaoStj+^ePB&WIZB+-N3fSFEz@z-Tsrua=CZS^C7kVgQ6ZU2*D zAK|f%dJ(|TsUNbNaxvjmi)o&?A5|+Lcv2}FK>t^iTy><!Dp@h=rjRvMqK>mr5iq@O z#$D!stmPw}*VmOng6?4Zr-X3D14_(MFgn8Dq?cSXG>ub?5!2??T%cP78EVlZK5z9I z3oSmkiH&%o>~SxYQi+*MIMmK7flb(BlP#@G@2)!-VIzF;NkKHi)yy}q>XfuD&vT1N zW3MzY*S@4++9ly<C|KB(Pb;rZ_5p{Ws2cv}McC#2-~8}qk<UpPFHl%_Q*DNVHFAQD zqv&yk9LU@a1dM<Qk|FPG;g|X&YdN%AVN+njLc(-!v9H9&qE@>e`>-qXg%I!-naqii z1}3Bqy6lAB$<XScs^4vkYM{-*tn{*ok`mrjx3u;_pBn;>!<O3rC(QJWDq{el$8;tO z(@)&=N$P@OcK$W>)d|Qb7+hwBHl3W6=|dRLrl+H>75sRpHZ~aAyMzC|OuLYAxTmH( z30j|#=oXsuz<?i1*L2x}LTyNl{*qt=LR0~gw8Ag)f80xJs>U5srJ$qx<`*`{Hg;lB z^OE1_tKirOt_sw^3V`hii7jHv+_HoFP{R^%*H0`xgjG$f2bn$@8W)o`Eb47jK5th3 zXFBWokvc}6V|`Nj6z)@c&rS5W%?Ndg+ftX?{c-a2_#?s(7EoPbjuYzj4L_3s51AI) zS}yCZ-ooZIB;SI8#m|*yjGcWiU*s~FXT8|jpLVW7wb}q&{r&-{#yZXTcCM%LnDI2& z0gzszBnm!DA8<lY8a%Awd6a|r1~YdVA+p%N&g{c5+RHdDnU%s$=n}Iju?lU(CQ3kA zhw2(tUpoP!<2`-zqX>xuT!d*WG>Td6aT!ccdM`Fq|3`ndJI!PeiM3%6jH5A{aXXPw zUeS{Rz(b8M<a;@9W{VMWGn0b7Z^_F7Y&E{9Gt8bDV9x(@L#B_tbScD#U$YK1cU}om z0aXaLn#Me0m)xg)eLEAZX7c_^OS}x6I2Xg?e&$4qD{VeQq_Mn@XnqX~-80mvH^K)O zMHB^6fGx(JT>lKaFANbUq_`#TFWcGx5MBC*PP)d7PjB9FQy$5F^g$`8ea<u!tu;{F z4~r41$nU!W*<^ZcN{;l8=ez}f-~>ya&#h;DS!){>sPy)v4un9Yq_9%^eO2-rb>w5z zd;*uWxFC1Fh!2m*#xFo+2Xun#gqvB6kygpp<nzvOEz`<*aa<(++ORq5(JT>R+EVHm z5e*6VTRPc-er0=P7w{{tO-aTyHpCOD8%NDqm-f$5CYueMDI?^EUF2`9lWCs5Z87)0 zp)VVprWHCfo|u^1nHi|E_dC)Ck@KD9FaC*ijyX4(MbOa_f`nE<KcR1eV)eT*@g6SD zSo^SX2vVNM2wfV1dlFh#f6*rMT9MC8ACRPJz;6fD+s>GG&VK~kXm0`RqBISwWFrye z=^nm}mhD6C8!tHzWG0`@Hsw@NQOK)`N!ieEj{n#?D+N3ho>;w_K!qRxD;$g((`TST z@u!BqME$X9ANNYO=>{hU{YJ(U9Wejil4O=evsD^R@_NToHsz@lbPXewHn47D62)gv z$FmQf*r2_lYc|y!B5aesu$+d}3mR)IVF!{Q+;g~ooE@Qr{cUyHmVeRPC`73iSx|~- zvO$5N>DR3Uyb_z1p_Mkh-O*irYgr6(`h^poVRCcUKY|5vEeo+g%w+-Fl_sWDH(m$m zi}8{51C8A)3Um4=v=J42q2nnkaJ`6ukyxxhpSlYado#k;(Yj;Jnm^9lhSzPW8H45W zB)8JWC30Z<)W^{<4%dba__~|fU^qCvGb07=O}PUI7DMLm+XO{05_HQ!h1|5!w&KH; zTf|6;XKxU}5JZv|L&hmvWe1p%bs=xEcO^UH&~<7<u)#Ax%-qtI2+R3BP^e~QnrC+W zmn0#fJo!Fha2^)F2N9oQeuuWxOx?jj*!lwR)s$$c$@#u6&W!Wyb;`HH)3F0TX-a~0 zSeE|4JT=@PHqrjta+C3Z2RufS+0#j!@{!3*5o%dnYAA(43?S1dWKvY{7p`~&WxZkk zV_3CKQRpSTx6I?5)wk9&znN-NZ5m-j%`LExE|Hx#fw3|IX<|@S5#a)5-NkU0P;w#T zzmoJ7J}$^3pQEmk=99>{ytL3xctSqA23dPqS`S3Lg53;(TNq<Mr_4M!B7bUHdW36? zjr|Zg3$&Gsn5go)d%VBoz#(v|+_%_D+@H3Sfllu656g7N`h%QitS8>S%LHjLT+-mU zf@WqhV07L$Z9{w+Lh40)$2A8em?uG$H*u+ELMfX!o&Znl1=w&~nFIB3y1Qo8C(7up zIZbM$`OD<s7+zBpwaze>Lgw}joH@b?eirnXt|iSF8HKy<aH*5&ww-LiJKv!W;vx&O z@5?@v>9O`DB>cXSq}f2=SHmXToiFlsqPqC~nd`ClNzN8;!v{?OJgzV!VRaJ14;9KM z`Q%t50;`Jo#HUWeb51Sl=e`=|Y-^QNzH|jbtvJCRh_v{Ue3P=~k`a6;b9k8l3;50( z-eob7DqMV`R+oE(F*uNF<4%#VsxIuDF;OFnLiy3QPkxdAB4Be#{9^Det!v{(C(@$) z)-CdAb}^30lkPCc`_P}res6IpKOu8hKJZ*!t`Ej8N!vbp-L;Mq-sgg&?*u>Q_f41| znDn<*Lo2{5O}&It2KHfgl9zm@ni)tM><~2ab^H1sv@l2=sj9un(=W0j{~CZ)TCg}L zdT+)pN&av|2hk+Xme~QZshuI*bI|l=Qup`3tHGajRC!p-oE@2f#Ju*=ii;e~X;2oW z{EzA)iDLd^=uE#;A>p1U1A6Vk9E<Z#9CarOlzKrhS^Khu-v=zILCdeTQ+De7QB(H1 z(7E|0b7;{L&EVxw2;JwAL#=RIw07H4qBW_wv@WK`Jy1pJUwvz#cYLQzxSSkvy<9ZP zUD%)xtCO$e<Q=eV;3&KcL=P7LA!EVC<nVuy(|u8K?c*2hK_4Bd@NT|ivs*btEH;f; z$1C>L{@q)m*>O8&7T<4XS#Z&EnaW!#n`&?I>q+L}5g;4aHQzq#_l84RlhbXOF$;di z9KLfgXXuskM3J7v(C%5n;rxrdEi6L5-5|~C^szV~<5aX!4=4HXHW_cEs$3s2kkrb9 zI<2~933H-2upYBwF*miMpR6CJFG3Yn6{m3OZTsk^NE#IDr}lXiI^hxX`kp!MOBkOV zNF0=9xBj!I<BdEk^h*sP74J!-0d!f6ToEWsfUTIhzaDWZnEJ{ywFVX&Ycha9D4iX< z;aqLo_z>%hHAI1?Ltwp^Z*d7Nm_}gJ%8`p~RO>Ebvg3HZls0e_PnOr~53b;oI2J4Q z$La<%2M8>jO0HCvA*AVhRY#J&b3rCFMcdw+p*Gq&4)ZVfUV!ClUm<q>o!GLOTvCPg zH@a4LT2|1DLV&-QTvo1-bf(R`+A!!i5?p-@8Op^H^{F#ab90+4BV+Q882>?G`nHwk zwU?LypHX4qz7!DCFG{5<nG(OaB`_i~dE!D<TD#Wo#SU@o_QkWT=rLKv$sp0Re}EwH zh|@S(bNkESIk^g&O}wKQcLzl1HT^Jn$PuOyA!RxjXZg!v8CGe}><b%(I_jv8TZIwN z5P?w*4th*T%GC~gCt6n}BJy-9kXmdIH?o=Z3sF08G2_eh@}i*p8YBWPHZ{}kPEw9M zwyYmb9*@$B5~UFk$%y;XkwIu{WG3;do_T|_(S;{%ubuXL*}_1@CbvJ$F#H9;Xf{!{ zUDMVIow>r}E2rVp`yx3<zQ@)=rC%vL%duyQonPr0($FmHy)TPGoHLYoj1UUo;42%K zq-7zU29VA06Bgp6KH}YtCmyYFIKUlh&#LSl8y$yQSx~Ox#>O4a0Ulc_5vQsgG=l=; z?a_pWo@h_K0>RA0c!BRfHY1G~4Can*(or<b-IO0^-<blW+DT(@S$u*ZkHsO9E^h!@ z*(T1C=I^agufbb(x6gjn<N=3Xv<Q!LQJFxJ5skI@F;!fLf<||Ev7W~_?gu3pjPWR* z+PPc`@f!Hfs0VwJTH7R4{igTvXCvaxuZYN31PT~Uw(Vm=&BDY=YZ!IWSMX(ijH?&v z;~nPi-)H_(jdaJXwb-}H#||S|VeqKH!!XzA_%mXEHpk6jN1QTJa;5i6fSETzG)T}^ zo4HJuZmsY<?7X94$f|uGc~d+ZO}38$)0UYQpnUW4v4{06IWnd)t)nU|os#9A7Z6F` z0*>PZ>?#g?M`A36eXNOZZoj2kmoTWR&c!o$S6%Zc45BgbN`!1_d5;3Nc7&w@X57^Q z1o>DT;3oi*DOgb=@!X(6i<3PEL_g6{ZA#h1E#5+6>&vF{C->c#x`RKg!5AW$fVTf@ zAIt^$;mIM8>Z=_2IwF^Cc%O5VlyMBG0pYpd{Nqv1c|FOo`BU<HJEpbPdmqg6tS*&^ z^AKq?v^mr$G~w3$CWxg(>ugZshDKx_&s=D5oiNDmZxFW(E1zNTg=iotLsrUTqfE)p zW*}#dX2sOrt5E)K^~lHhpyZ@3pk0q8P}AwTfdx5GI2OY=Bpc9D3zfVW{SL6_#ZkXG zBWWndQAzwV@SaV?w<P;~qZjU$Qn;dRgD4F1(*%;a;WKg_r63O&$tj0O<sZNu$R0@~ zJSnRRe(~Y<ROF;sV5|;DEfs8QaaRgAQU$SS60)`@4-rLG{LMR+11{9kWC6Y`T^`V8 z+D<s3evBDgav-5Ikp<w5rfY<}bd!C|ZoT%2#***IOM?5y`_#6wcfc(d1jYGQ0%8ei zD|)LDWt!qfCou(aeALjte8La8oT!zvM7{Z~M$v`i(m=Q*T(6TnsgY*y;NQo;sLx&5 z&h&s?M;5%-)P9}qxa#P}gu6wHagEpjOF*>0F?VsqfBTr8Q|iEbLGQADZ^ysOf&Cpi z`j@df<x0;typo3>FA3J)51w72nH1--mc@^xht`~KUqY3=@uxS%7#(HSes?R$A|N~p znbm&33@=qFk)!g06+EPFi0L;pH$Rz|ZjdM!m-jVuzY;twt&Rd%Er{~;J0VmyJ8NJ! zMo{}+!z6sFu37_Hih-_aP$3*u`E`WsBt|pNda75n1>5p^^>MZZIq6Y#Pqi~okI6O; z2%ZI>H}XZdRk9EB9WDGc`g{!x5`x6dx`K8$lvr4<W7fZB*hpqyuI)4QXP@U=4plae zyVEH17z=X5_su~Kn2dQ(E-d)P)(E_NV5wRaNY6xTQ00_~IaOrq4<lvHRfzL;k_jK2 z8;8A#Az`&Fzt!`Z6c^l9nd@DbjEmm(rq>kcjpu+IM+ft>o)^b75;?KDz(Nrr(FTD= z{B$fBG#4M_fG^twTXQke>yE6FX=YMVjRU#x>X36+kG~XF@c(You+c2y2|#Z9vVWA> zoI=Lh(DyGv=-<2sLk%;S@|{5+FomT-WPEha4N}9;_oCZW@BtkI`Go|x!}d7pbAG(S zjyxsV7WgG?1UiKkTsEju8xYhb`m4ycrR0(yY$et4i7U+G$I)BM54x5K=x2C^_RSFc zKl0<&C129~^d?(JXmGQ2?21FpgnU>o_}wQ!;u`S7k&RcTRs>c;kiiG&#F2y@_D3$Y z&hAq`UZ{B)2ZJ@7eOAMJxRrCy?0q9ky3G700-F2HWIIco$lWgSu;%%{meg|7=A+sy z2bhgh6RlQauB2OUhNE#<wTA6<0$<_}lVkuh1>{}K1n60i#Plk$o%c2pY{n;QX|roH zgGzdpc7?)1(5<BLw)^3OFTL%j&30@2eAi~#7A=~=a|y{fx2eTt5F;o9hDAXYVszMz zV#N+&#*g=pxY6Vq3MIDVRNHZ03GM&wd(5)y-MYR=PbtzW!(k55s{qh4O8KPwY1SSz zCcX)N5h9X=++PknCqx{~D#6b{5WD&}H`z$wnmW6=Qc6DTJ^0EI)#20<VrSoDWz4n@ z&_V?=-^98f1n{DhkSDJ4{7}7j#$+!Q$&ojGz6Z`p&!Ig{8%YekMOZ)VvAy{B+>3jJ ze`<lP&o=hls`Gxs*@D=cyq6OR?by6DbF>;y+zmq1NIDD7A3&SILH)DB9t9l*^3RNL z(Y+2n3|>H8Kf<Z|Nw|!xcp|3=8$jYARrF%?M!H<We`8XeT$eNCBSEi3Zy5^3b>10P zy9e=Xw_c8Z$05AO;9eda2YK-lR=|;U3G{F7rt8St5+q$`{Bd?@1ul0mF!M>71@YlG ze~0~f=LNtX(Dw===q;RMMi>f+mMby%K(CfnS6=SyfBNs*L|s8Jw~0h#?Jd0Ci?7G< z{<PWk7C3lSU&~Z0DbmkKvUU#+(~$WLXMUjRnD@O?6cMcL<Mb!7U2Db8%P0*=J0J1j z!uDy{?Rn9+I=R$o+_k`S@Ji+}W55#h>lcD|Ib{>b%_xtExq<sTGm&B-Z3D$f(qjDq zPOdOu=QVL3Cl8_(>ptN?<d4XaJi|NE<Yj8G93a2>Q4}<qV&Ppbto=7U=Z-L;ZfL`v zWE>ByeEgY|%&g`w!o9H~jJ^mz2yW7NQ+GZ@eM-w46?r)iP_87gm^7y0vw+ZU)~sdR zva927dwvD8XwVxS(cRFwluOv7;XU}QuZ0Crh0)!cE@SAg%SIkHpHjOsN%}y2KwUMr zCyg<d-C(2^v0Z5zQC|8>T@Y+_wx8y%*gi}!E9t*D{NH@5k5C}UQfM5QI~uVTV6_64 zIo+CZ%S#s}PKbtG2t(U|Z|GixCYCyh?Gy2iFr^rXriD1g$JERrk0v>_3j4w20VD;Q zYHC2~1XQ92h{1ztR?vmkxT<F7;h@kZKmwAC={T!KVRo;7h)Kw!kmi&ARkl2!rh4d- zSEts5X#G5G!ty(^2v{y81c~kc1m7B}y!rfaEq>`*EOOU)6gi29Vg;$I(^JpSO9dI` zvrR+mG9sWJtF|y<C-`kzBP0*#Yc}CSPJWVm_P*lg!vkd;J~B7oEhCGo`h6mj{U4^4 ze8KL{SOqk7S_0kSPJDxs0=Vfet?{iIy$x>w6#Uqd;ox7g9dT|amqTZ@*F5wK%BO2A zNzch$lg@zF!7pNdk2Q}w84p*W8*}==mE6Pd(GE;_ho`hlrll*FF<dnz88TndyK6;P z<-uIIM^TAp-!dOr*Q!Txu7Ie(k>0i$VyjW;ZG0r&LFJ`&@}|cIyJZ8+piEwoWz0vT z7*efhGOQ$Y9_ns0KP=ln>n_s~S>7ZBcRXn-5-M))sj|U%x$?l1G8ac1w(6bbKTEZ; zqQBq*Xa8}$4Q?{XIJU}E*(g@GvPgrn&}ren9^=t*E$dTg<@>;>t8BR2w$<-i=fi^s zq!K(cD%|qf+cQu6tmz2Lk4ugIG0*HRktQ8nO<*WCXb<o?0;t>f*?#=g=$((45I-ZQ zKNcMPBsy_X)o1TZel@r=v_~%xjtkv%syv{^Q}inVVZe~@36)wmc#lV}Jxf9a?46`> zgs7%;8#s9$zj=YRe>cCjWPb?(_$vm;ADJNa+s<8b@6t2wLMDhj3S~2hG09JL7@Cbh z#Wk^YXm24+l@OO|b;L4j&u*C52Y=N~mL<2}yqntrlb&cwMZ%fYX)5bWOtUm1O}{kL zi@sp*yaka8Y*KGm3=Ir-n?Yt-<W7~ErEK-@)=7Q$Gd9vs?PJ7BSSwTt8G_dLA@#7Y zQdsE^iim?1C#X?4J&EC(HH2SDb(|i;$!!b+(d-2IFzQfIBzr0rv67{B=YXQp3Q5j3 z7qA`pRPES*3$z^J&_I|UOsQw66AN(MgCn`a{cfyMI+lyuSu-i9BO)CCw}jCGoS1YR z`xU0<*M0-vY2o!?53C#_JM!sIKyB?N`_EPdY{&(-J%$5R#STRkmmMb1kc8G0FWI6{ zV~&r6DaBWPb%1Si#x=A45>xYNw-R-K3?SMjv`i<k%lr=;Ohs|0V~n{Vx5tAg0M_qC zitZ}7SFLE+Vwkp=p;P$>Et*t70Yut@%jz!)_HZ>6I?+H(9&DTu9~^V`Vf8Rl!yJqu zV_3owi+O(u5cUOYifQuk6h*Ur<nROAYK1%v(vDZbq_3bNSfi%M`NpYxGe=&5+1=)s z5bEd1Gb}n#u414bP?+s@GsS!R8ENuMLg@Y9_zp2eS6ucO04K32Clco3LoFyJdfJ9# zV~bECy?9g1?yeAq)SS~Ms5<69@`;Wz5QD>fa|$qD*)i{dz-M$JrZCbAF)aAIV;V{% zv<WIK3mU;ajs;s`Ku!RZ*pq_cY+mX=35%a0^e*Lg@NDQK%nyYe6EbN-Sp;%W3TWNV znz0<tp<}&Ji*zL9bI6X7&8*+YNaJX;F}elD_cDZXe{kGNT!7wIxFJXOa-VMH#)tzh zC_!qpX`iQ~Cod-@BU|Gp5Ea2G9k@<$KDfjRBBhexvaZl`(2cNR=f7GGKbJ)l&eg<V zPio1SM*uoypI)k822w>-Tx+xN;I+Lo$e{@uGcDctKz#G!Q7q8P9w)h_yJ-ncOard; z)c!FmnEVUrTjMn+L!^Fdj%~$RJw3!H>B2$8oZ#ofDfpETdM+v~iZl5_^<aA==FfVg zxY)lq*!6w)%}Zn=a3_?>3V@A<wr5D@|A3r*jy|rP1zLIwIc*+3wJx_PiVC89kIQjm zc~`XW3!&p9#`U}HE>@tA4{^%dbXL^VxI}WcG`}|@`N2z3>P08mFuoYf_G$-CyOoYm z>7znmmTUV%jeBf?gsYnCNQo$kqsE<}6%%4H<zL6r!GDI&m8ml<Y`%&sA%VwC#z-q_ zIggs_f;H;p<;(8=LHr_mlh6QK$$gay4*w|(a$OxlaAz_r)Cjj3zs$Mp4Y+-5Qwepq zWPaQTpD(!?<)wjz5sST-MI(zcE#*geUm9PXCp)x^oJ`co+^sqYi*9WGVRig?`ySqX zR}`GlxR}BI-D$m_|Mfs(G{CKc&(9SMT~p^qZWX0a@cq2Q@ctOKPI%MAxdS95P#xxO zG10Fn#bu1;VtMngK`lJOjzuUjI(C279x9<2+8I1NlW2z7U49rTXZ>u^<y9o9He!#V zK1pCe`A=q|zw0ZmlEYWt7YfPhamELu!nP8LY8)gL;@CMevV9Zxeji;-GcR%7q>;hO zz665-Ea;80^@s8n9&=BSdHzcSSBW$VXn*Unaqb(Nc34EG62!h3N$dNp8JTW$eq@#B zRjLx#_ID0}8nC*7J@Rv|+`w)W1Ott#W?}}#L-!4_*@M{a3qugT>>mms--WpnCl0u) zhwkF)0C$1KTl4$MSaWKUQ!9K(^Widw79GBY5V+kY!OF#AQ!jqZjkuPyb9gCgh%4)W zq!NHn)z1jZG1&~P1u|8>IrSN&=guS4hE{#L#6b5c&J#3{I$F^=(!DfmRqZI<-Lo-g zzP6|dfn3Nt5a2+j9=Rs%Gr~eKp$1=wP7Z5uT>EPS5?rzht;j`{k%ttp_YW4qG}8K# z`S+e*P<oDkM_ELOkb*~KVchCKWw4a1^6yv-o9SsCfVBtp!+bMG)1F=-f(O2>M)y39 zTt^%PNJcSgewneQfL4fj;}C1J005Nvkr}uh%t}KuSThy@m4>01s0p$8ep*`UJzsKc zS1kbM<9f<6UseXG)PLQ^St8^U8wcQU14@+rEZ<lm!xcj3B^9%3#Ki66`J0-}JP9b` zTn%`}+_HfWrx6qjE#aaa1YD>bXFDJv+tm<SsYuzOnO^CUgt65+mbY~Sg`(>C8uYrS z$Ql};7uFs?lYij?ZWi=;gl87BaLc-LY{7)zbW8VqFYU)gDyyrZQ}l5e==nMH0Fu(u zm8og`B596!xLB!a*5DVTagQ%<_jdpob!d_G;adhMmkuJ@_IQu@3EzsyCzcPX^(YDC z2X{*C`ICt;zQlmgGsuT(Q+x_;U;-0K^VBGviW6tpLk*Za8dLpHVC>BtJK6%T9j;fu zb^$z5t|X^ezkQ03Fa2MpBgihcQJM@I(~R$bhtd^x7PvaX{*&PrLeiChLyj0m2|0o= zBMD^Meim$PJ;JbZDiOj<SD<OLt2+0G;SO!CrH-e`kl4puKCUW@4?<|T9MBFqq}ZbY zPCz*@(P=#^@=nycPfP)tt4_~wCl}ux&`1t6mS{?lXU2?b<<$VRiyfg*G~TQ0Dd;>` zCxrHXidwB%MBSTn)JWGsDd_m02``ZZCL}HYAa~5qNK)Uh`R#oXi%p>bC8z4^`A^!| z(F%Tpw+3|qaRw1KmdqP8HZxQM)0L3GG>{t15W^M|`6E>O;`=G14a_(c#|?`mTM&;B zFYZNnT+419cT6<T0U&L?ER|9G84Tz%(?4}m<-|I{n-k`w8c|p$Vn8bB;<G2vF(*{$ z)d|x<?oJ>rF|#N}fYBwGloV&o$#%)e8`tN?20}~j3+{3QM@RlvknDjFTp~?d2))!T zadoj$&(E~*jal4S5bEJd&W_qK+=5gKjAR`*TdsMSjEFbm{zsvJBb+HM?ACH9<!f@B zzHLL39ZZ0MGPSMTecPvLJ&1LD&uA~y*w64-=nKO@0!@1vfM(Q;zPfWLTayRzvt6tt zW?nVCKi7D~v8r2lYhdMTgM*iYVhNJ4pEN=UGpMrs;H3ZegGtwan~X-KUJ=7+Y0;*= zl(bGZg58c)do45a!_-tq0FFIT;)q9Z!>wdP8Bt7y3axb9${Kk&x*|n}z@LAhWy2VC z=^g@zK6rI3{o|FR9|`nc0dYBq9gZVC-7-E<E2kpXotNp#bsWr`D}GsM!Xee=Gjp~5 zBJSEGSJy~kVgEH_zY_Yj_KEqmiIbpyc<V~freilF3^e++D*M}4IIrdgPJh&CI?mhO zKAtfqiNLCswtpF~@~#7FHDXN<h%v{p54UeA_L<YFVqyX;9C`w;ZYPF9xGb)0$b%0} z8DLOfu#t2+%`qZPwkEVd&Oc=EV3s-BqPUhc<gO%j7j`zL^k9X%GvX16Iw82F6NeU1 z3k#u;=VA5#iyw4?I<J&8&6HPp9yc!NeK-j*=Y#rk`2cq$wvL|>9T6x3x^nkdL!M26 z+Fqf00>daw>I`VW3<4wx`{y;kJY+~1oU1~w^=m1|0cYb4H>|y_mPx2Ai*C_aj<BJz zf#)lgR8O$&vhM>m%?nQ769nKleP&;}VVRB`Vv9}7%|U>WkT1$Fwz(xufw&VB8-dU2 zy(=aAYDpHNriCA-t}^_>QvtQ%KSbC_+=KO_#dY*uXXy-Gb6~;(O%?CIt+@{@`+~g1 zz|sQYfHItkGamv?4nf)I2wRH+61#ju_NgIRZk2OGe}hF$Qou&ubFI#Efd$1nIf%-m zb4KpCpa-1~!n4rNv=X7f%W73gi7fi@W#9I|k~<zD^g#e+8f66o9kRr*y%xEm$IJmB zxaPu|Ir^EV>sA8#%3?4wPA18O9tRGa=@;ks5YJW%3Hj-gv$8&C8(-e*#9~+~^A&DC z-3Rh-Q$oG)Hk7k3Ln)QR>Xpa-F0zZ>@{7kK+4|E$m`a;%y}T}HO5PvjMbE#xJ^E|S zMx12O^?hqsOOIvPg*$}d&;3b=6#w^V>Mb;!Oj76CIL`eqe-13^oY@+y)LkD)dVdh0 zqsnI2?L8*L%t(At(LK1AoyO)QsoK1cTJn6TfPl40b2l@W7$*vo;#Gx8Cm13%X|3jq z$B><F{#@I!d28P*rZm~}vlgP4y|Qh-RXkOXlv&^C1$mgKr0Vd6BY2xK1P}k2CbH&C zCy~C$dFtqF-zTH{P#^a48^r8j$Pq@UgD~H1gVrHL^QlK;IAPq%sQ??h5X3&-5glTh zTZ#sfxt+H#Qa8memzd}84hzL&z4?+_e9%HVhE9$ygar==>U5Y6B$LARH52$OKXFh4 zXk`h&lny2tsDxVif03e?!W^##%(`li$c@0I8piD677#tbNw#|gvJ28o;J7J$QRq2) z)z>}~?asFKqeby{<dLi8Cg>PY!e=O*Q_s(_4HREtw3IeE0LK&t@ZE2<Y4mpk{$5n6 zejTz0m>_f(?i}5-c37An=ehHe(N6LIGmCFLsEyHogL>!hjsE6~+ErM;ORp}RkN9`6 zpr8jnG@3@Ngnc}_Q<YW05y@D^hO=ie#iR>diFw@TxHUGRDl9oj86?$9<q9)jLxUie zTzJ$t%!%j&7V5fr$lQiJR7!0j^)Gn!d3|(&WjS1}b5}JMTBsW)>xIOBUOi`k@dGzv zphm5G)B^Mg!6aIM1QND~-b#t^v{^gp5pUjTlM5A--f529z4{<F)+-~7F>(`|VlOX7 zel5%$f)1+2mQzre6TivF-rXQ;$v+*a-2&`#qmAUd9?#86^(oI4SJN~B21Z}_c8u;h zxw;abJOVg#$9dQVJMdPBSsV)Gn0_X)9WzE!`UeyEC9L##MhAhc`N&G)P1yf_jq+Cv zq%#Vkf;*z&N&#K5PB0c`^JW{zV_H2qJmSs~v-k1q`Iu$0k>Ng`#6V)CU3AuAaC6t< zJ74QD+jEb)(K)}#fj0jP3+<(i3jKOhc_af9s3U&mg#?ghxebivCeEsLB{^o^*!ppA z09D;z8NKw7o!83)L3-BK!oysuSz1wxD(b1gmKMbs)GvE?;EF;Pg)E&7g2Z@G9(o2- zyd%qP?+k$VZ*&0nlZsKX?a+AI^hJ|q+sV5w^<YA%tNn4wd?7!+#f47g?AvChaVPb` z{nK&b4bMHrZ4ApSp&+}s?;!}6-jIsOIseb1&@Nk5V4mAVVcsN~jazJ5Elx9F6b%vm z*x`Mg-r(-?2IY$4`Lj|h+zO6Q&bN1gRmer+TNj*PDxRb&4&r+Xd>+lII{KVq$JnUs zG-nXhL*FW-!Fa&GB&cZtirI!c)m8(86^$_Q^py5F$EPS4|FEB1EuGrN>-4bwoB+~c z-21FCABz>wD9~QyP*X8*f!g;4PruCC0Qyb_Pkj%0oQdAU0IfFP?a0ZuYvV$~(+qM8 zVL-FVu_~?WOWk-S(MZ6d>wHrw=c|Am|IX0v?Jf>fqi2p1n+cz1*CIIing0;U3YWy5 z2;N|zv<9h9e$MVvq=TJ|D-Zk45mgLh+&FR<{$=#JhT^nZKvY#)hAvv-+D-O}F1QUv z*PWz&NY&5+vKy%#ryZacw%{5m#0j1I8>spW<#jS$0n;VX(!yQ4#@4}=88N}_3Vh0q z1t?YXo^p)%h$HU;?V2=j3xdK)^`j}8-#l(aji1&fCG;lZ?Oe5`bA447I%JawAx3hG z5>#->OJSpdMJHA|!^5LQ(nS-%VAOW;I8;=38X=&EO_6*ZLQ^_B-~EJ4HF2Lz8>SDA zJRfbEAGde!7EN57a;{-S$KgFZu2h9SVes!ys_Z+y1l;JLN*%MQl}m6daq~MG2=(M3 z)#OF*Eg{a%*Iri1r$kt;AVEXvim1fsYvRS-QmnssFI?V-JmW{Ov*BBFz4)jEmVMI5 z<oUD+)D5(*ui8cS>I7Yb8{X!Gxs?oLcq86WN)O?Od(G)%I`l(3IANBKMKb|sGvcBt ze6q5T`sWSu<N??M@unHSaDPfBwo_I*J9;f9c9<VQSUNTK_kL;QxnG1z4gqhoW~G%4 zEqe+~Dl4%~nlL0~jJ|T)5b^$1W6Ryl$~hw{c2wBkK|^`fwm}@5`IKu1cUZN~MeoF4 zr2fKx(hZK0JpBbMd(oCwrG(T;2BU-8XT8h@Xw0UIGOMiCLd1YNDXzi5+x7;a5V5iu zBSlGE1wLf`)5FLlGYM(cbpRM!L0ZFyua?oWjDV{>u_@jU&R!pT%re?oV>LAv=Kky< zD}+P&V(|$3fs!9c-*EvT1@EK=zWA>`@W`rc$?76IYAZN171&?;gUFF;I}rcw%E^)N zytCSKs$Iz#kA0Ms7i{=YvQ8kcX^{!jGCDER5_oybQ;GIIMMc$SSU=dOHHgp0_a~#z zWRrS+|9D0?*ig5&!&F6E_Vu=;_UZv3TrMN`ToO;@U#jLeG@$0Nz8~K$Bl-qB%3r@^ zMdM<uM2Xkb3iNlUzoMID&Y5d94~dSGAg!XhPBOcE)nfH<yc#^CVKyqZ%Zr7N(%BF- z)mq)TR&*49!k2Z7OH;MYsl4P8Rt_L(k5Ctm#5|IU6q{s!AZ(K@)Fs})6by^6jp$(> zZ+X1>a=kz$h%)JU>oO7i4Sw`GbBUrLyh?)zl5K7ArP*uLw=4T;&fK)kOu&^;-Qn!< z!<=}sDj%hIz676?|LJF_rRLtrd86opn3xHtzZ1LGk>Y(7PBVx0D^ip0{t1VH!iZ-n zD)KfZ9t11SEri%M@7t9}(ej9v=eJlGP-Rv>kZ!?lo}ZUOd0=msLQG~M1Bk-%kvzKt zKjc3IWKkmvdE6nY+)P&tT2_@c*ucmWf|j}oht(=Vo1i(eWF`^}&E25GMGM(;HwIVj z{ZomBAGc>8Ny(<y-wzWm^C7M|dNqr=GA*|Fb%gh9222!`8+%g;AU+1kWuj-Bh`1Y& zfmS~llnW(Xz_f?*54+Bp!|QgvoR8*1=gz7aUe5)YiPQU(O)J3pU+EBs55N|pu6Ycu zl4*2eZ+~1eu_^3!Z%-IbUTuV1)+nk$5Jr%S?kpFDh|^_K_=5zZTVF9i)+_sQP<Tw{ zU9Ds!I~mXCK1G{16y6v!b<tCHI7L#JGUJ8u4nlpx6e^gXD(;p9Flp@o*H}kCHWm=n z0}V-?`A^nM;d*PCTL2vU7gUTm;nTHd8NiniNUPdmTGCu1B)Bbx*Lc~>9;s7ta<?{> zyT>cPz|$)&nSW+earx_n<yH=y7a791TpcuNDhI<SjPjbc%rRBA)<zKtx6b*gs!PLf zfQ|1?tv@@gl>OMmw_98US2kj(&zFxmp~ZiSCaM>S6<!z{{y;Q7b2LSdNXu5&n(uo- zQ<K@%4aqyg@u|Z}&0|OL3ppp_5kRY88k~UGe7`F5k%%yy<D1csOeIr5O*S?;Ow(AB z-*b7+*n$Wz6DO}&Vy|b5C(44>gNB$t4f_OUommB%n9+pcq6d#tA{3mbox!dP2&S4d zgcc-(W4@+-P;Z4MwAlg5SVmIzD}2Eo9RQo$xT$JPGU%=gKg7r2N86|6R8DV=17z=> zc|{OU-!|KV51)AB&}~AF-f`v2(YXH!9>Vv#Hf&91nhcKCK4L6vSN(-`H&mJ0PvA@@ zdJkC$nsuEGryQ>jy6vkq&))xrGs&WmcB-g8-YGtaL2K{*m5=-AUJ8J13hsBbl1FS? zW*f*bs7|<K!9U;I9IWnIL%>4Lyq4-;-Ixti+@++oc3DM(LPUxi1fyjJDv(nlh--G* zM=d*4%E&VMXLIoY4>L_+N~0o5tDzuyy}sUfJs0s?<Ko0<6D&k2M{F9u_U*I7-bs5a z@FMSTs4||CTb2f)uNoRZo_fh8JpM5GFS!;QB(m7D4~FEhs&5Myl9nO`faZ8-)n>e( zjyT!tDRgXVOqSt#bxgiWS>dV0^&u4)#mCTpA<wTY7;y%N>TsU_jTw!eKKqK;o!*|z z)n46Dt|)M}x`o_ZW)P9!(^h~fXm)CD_#)Bg-Dw3)5Hm}tK=B1xDl35gKs&n_L;M&) zQ8lz`HG2K*5SkebyhHBc<N+(*Aonpk9-A9|^X3@@3GF3vS<j&M6=MC-D5T*|lmFVE zPBGyiM0J6~wx;_#qjrJ{shnrsS(3Esk+JaY)lowFD~Uy%0UfS^OLXOY54@*kl@PGW zUn|8*C<Ggw_5d~&rU5hU(Yd5?lICb%qPuE-r&ph!y@=ADHMNF92GvfaN-)jIZB#7% ztwk%<f;ARmWBunCia<e2%?IKv76rGwFP+J_1P|CADmVH*j7M1SVwSm(nl`IuaaIC8 zIR0UCM?hW$723KgPO9``SKH7?rY?@M9Ynka2^^DLEDY}>(<Bw>SZx2+F`JLM&$SSG zBjM8%YQ-p0vXJ}Pt~gkcI)GO#aQ6+r!Z3G5|5R11w2h+qc;jlhtWEEfnh<ZU_(Gia zCwoEBHqt>R*)GV)Xucm7TbX1Z$O$rNrz}0d>xrBa<R}w)xDN_mn9q5MZdmOE5yV*g zPc9KxEproOBd?FoDoiuM-H3=3T)w$aB$5EeUpY?RP~0Cv&Dfx==(*%*+mv2!*E|%Y zZm139Q$oBA&Sk(g>EK9>9IAy(l&p5=$6Z363Cvn*igLRZ29O3TTfoPGN30M9J`jZz zbr&tW4qzL`E*5x6wQj^IN|9&|62iP;Cpd(VIo(Fa6uI9b*FN2ftdq;EA^_Q|k+t77 zJ!UY0`#z3au`2MVG-kU=A6RTiF(^#Geb<($Psf%~mNKJ3TgaZUyPFv?GQtyu88vK$ zt-<!h=T!%F;{q+>p@V{&@RdpJ44yT6n-;vZPBvwx`v&duVe^5zO+rOb;-k5EYM=4! z1?<^5%*Y0l0I+*hm%HH}(%m7%eG-z;qGOBozaOgWJ(_eK)x=WAIdC2OF=U+mQ@p0< zu(X}b;&zHRU|MxIUp~ws`4J$_*#*<_;1k2e5BZ`OWDUdwvfwA9T1f-IbG3M<j36Ub zW-BVD=b1=?c+K$|pOW8V$|)x<2m4kON?bz5*k(2P9GBkf$4IG{=O&R6%^}Y^HuYWd zx*_nFScTL2y)p;UU+<ZfZCmze6C2J8SiZ<>1He<m))U>i;&BRYkw63j4%gnUR>v}w za)xc-X}Fj>7c^@Uvbs_1t<iLCxQ^pRo;^CB{4>~YmBcA3p}JD)0-NgK(W)fB<l)=> z;pCc0p3Gil4O5&f)jnmGj1=O>*|V0p%rfokg;)Wi8!_O77P3&6Os^^~;vVSDj74WY z%KNN#u|wrLNztSkT*0jt2^2YwlOj7&I5iKijq%Z^ECV_zZEfw=qEld^VaFpRa4WFC zWEvNc-Ey3mKD}3}=Z%B<jLrb<@18W!udpY5Xh!0zri%m-0_`jUk9=1ozQ`jd-4}$H zm|S#QxKSMtYLBES<_J<dBNZn+6J4EB*R&_gv6OjJ^9AbeaTpol-UJj*qu1*%3dgKa zL^bC^(M>YRKB5S%)qw@^@0@sx$sJVx*wymG6j(=z{E|5t$|USrWvgkZn3mY2zjtus z8*0KD+7};}%!{Qc8SE0jdinDg-Wlh}#K(wZ5lpDRO?J90dYhreua5)Dpl1#+iljMN zKy4y@x~^r5$a<n?K@e^HfxP!yJ2Z@ma`4QT-K*?rud}G>&VEvOu31vIv<cx9NMTKI zz|sr`vi8734ae+&^q|?W!l5$<T!+tH1I*RNcPD%>gZ3NF>hQ-A=0|}U%Y$?GtVF=` z;G|WEJeidOI+KIOewP1O7zkJNFACXJif_R#gga+`npgavP&mdzowUxnO)FLUUIT$& zh3Jr!XuI<BB`KWmNrI@FJc?;i73m!-P(WAgScWLs-ZomxZ7EMJh%zv6duzF6pAu|h z_O=*MJHewYhBPC#Mc3XRi7jb0jLy^!ld(l`=Z_|TOMnnxxRf_icg;jo`4#M6NjJAI zog}%&=n8;^=pQLruxQwNO2bmjH;xwM)Tnd=|1;Mc_>Vtl7NC-#TJlD^@V`?aR~k}! zL-xA9JSr?-c#Gn=H#o<z1VZGMv7BW~irTQ1kJg+J1_$kSeS6#?+ZZqD7DL&wxgIiq z-f#!<H0Psb9QpLa9_9gS6Z^1&qhx@yny!S%NibLKcj5a>N9CcW9$1x(P&+1RN?1fR z7|z7D1y2VE2W-pF-ox&@pV-t|DdHq;(>eIyI$we{v43XGEks$<i-Hh%QPMV?Z*3r0 zOJx4z=c?6i<Op;^1wZ?n>ml!*MS!`0KzE;Oqg@~{zkmD3jije<fDcmKkEeM^pnx7> z35@K<&!W1&jGScRz4jkzC+7kD2WM}~NerUcu&ac{_++rq!7QcH8{?IZ_TbL}N1_ne z(@9*WG3#E;hVE&Um@$cn9nCjUe4dxp0r-VQ{CPon>O;SbY@U8}p)NS>!iwnoWP}#D zR^T;G_-~8m;%KgVyMz|TK@(QqfuDH!(&0T(QA?5&(&&+yB!<vI!e*y)>dYqq+mHZ~ zuaHn5L>Gc1Ar@3|om<VyS~zO$PJA*Iy-uT5O*I%}1nuzL#zd`vaX`DM_=AB}xzp<K za1g}{k&7jHL9*AmMV3$%`FauXF1^DUbF{ytqHMrH!xzPa!-cZLuZ$@^V|V^3d2#Fh zHhirz?Laex24x>BVTa3pBN)mCXp7vc5U!r|n`K!^!`kV21eB;RZ3{y#u5R)+WdEtZ z^;rpeMQR7beEatMZiv$p+ON6#5y4(oC(+@(S?a#<<P)c^p2zxc5ff#f+ry9AHI-5j zIGdwg<jOarY<O|yA%J@jDMQGgdslZ_>R0qE+apsxp!<3XP&Rw#m%6Ri7QfCQYJl$2 z>9f%uwNkD|z_olw@Q2$Rm?L+ZcI9I&9x7lQ?F?Mk|CM(MkUWM1x%A@0CSiBU>MD_S za(rgazd2q9OS4~)*}cC0;a{4QqFSNJ^1dxU4=|j-3l&<rz@aEo-RoepO8Cb_RqYR; zkH^sxz&Q#Vryf<tb^|Fw7qdlbzOI4T4S?-0()m0pR=f4+KF#cYiG_E}ci(9?&7nVh z(TryxaF8-{&}V~^zIvP8%_n?GICMUSc+1JO3C<ZNPz$K=kj{Ld8@wqwD)gwyLDE(O zdan8Vz}BusnnZSV&smUecqW;9#n{t*V3!ro#<{!TDO8W5a(|HV>E#<_SdQ3rjJ(v9 zTAe>ehnPNZ4){+o*_oJN>WO^-)HgTImX(MIn-CN;tCF@uU!ta$bdC+J#ntUnTjOt) zmLu<h+xbJ3Euw#-3njQ67!Lu-(0r8rl04KJV#^|S0sR{rDiEblQ?4{Gr^eSPQ;66} zH5TR)o0;8Z%73~6CLX{z5z*tPN86Dso*h7$sz@aaL}gAr*P1@4Bl*EP;P;Y5V@a{{ z_>V6JH%MqQf8mD*Ns{a>%O7YciTq5D$Yg`j!*d80XW1bl^<w$(9`ZUn*AMBp?<H0w zyRiXt>VM$LRTy-Rb#o4*p{Diq8JSAlR4#xDpI`gG8v@Kxq8_TmkPQWDCv!&7Q@y;A z_K@b1C<7|*v?&+vYy41VDd57&J#1ApIk^b6r4m4U?a<VP+*JS2H)$Uq-VkN3L5mq1 z+7yK~!G|r5ej+*k0XMt+;ue&7A^A4dtjfIiFnf_@&v5s{>n#k|Um=%T5n)Afc+Nlr zZ;xt2s67zObdStODXyK(Q*i13>D@%u3|HPK6p&b3dW4^K8Vd075n+5Y7TkI|Fd3GU zQhBM`L_s5wztiFAdO_@eDPJyHP+opqQ*i`={1ES689)VHL&A4Mz6%2L06<MFj0$l; z=}b8QbWL$-70V}*IT*Tcvnw$jmk@?k?G>0QaM%!G58UNV-d@Rp-#cSi8&~xj_8y{d zFgJ?4xKDgLHHIuZ)~-KLkczlU>GE;Vi1y@(pnt~MY|?2g_8c0sG!n675pi3hK*N5c zQJaLrjfEJsb!t|tO8R?A9KfIzK;m@pvRTDFJBDX?<#Q_VKJP6*!NbS5VS9s~oK~z& zgXd8a@PvI4dqBNq)VNnp<~XKX$PKJ<ba^ScTgypQd5@Z=!{hybB`A_VLaSeT&+gIr z(9wUH!g9R=r4PXm-^ZgJcrnF>A8Ouy4w9~lQ;wGBK}Nr+U|%tm0Jo3d&v0eAs8Out zK5cYEX9EY%5s|3;!D(R?v;8aztYMO_I)14play{Ak>HNyFO61^w-2v_)?XyclXPiq zL6Q50p5k9myK57Tl<c^LdXAb^JzpmEaJwy{oo1cR^kGq@m2O5ldw)sfEf>$hx*3Oa z>_h2XI3%d4TkwbEC*JQ}-ROyrGT@~63Zs$UJG=RgEudk%W+UyVqL~jxbPW!cLdAzz z1xmo+nB%&t@<Yu~BvuR9P9JU*FXjYnpg+t70`IMYB}5VC$~h_XGw*~KzmHK>5f2XA z{CS^LV}jtih%=Vl*#h5&+p6J!cTm;{!{xXWy;e=i(P@U|+mPB6nsc#LlKNP|BJ!R0 z(o+-LMHG4rMe`r-cnd~GN0qfV#5T02GxuR+2Tkzt)ECC)h~Gh|ru#&)1!G0a>Zmfc z<lKhAewuDXt@&g=F?~K-?h#D@D}co^y}6ouyLb6i4c>9k8A+Z2GFQCEq|d&q_x><f zt-fzP!0piFU*K8gj4;o?z4`fXukkq3k^9-M(BX54C``txRWgrv!_fO&;`c7%jU-`8 zCmA!;YfBNnLy-6k!iahmD;RBU>@X~3)ui1Bap+UHnH!*~AvuqQ`+|?*W$m|FeiVvF z=_MXp<0H6LUS-VF@Y-Qd-r`c{&s6_J(fgyZVCjyC)jU~aZiOA!bkr@V5i_G0ZY2!+ zWMt>WtCq6uW$*<q?+K1nuSbCFCGbtrx@`-@O9cw@vS?wmHklhAmx{GYaeEv`NsGxb z8NhulNR6TZM-|~0sfe`Gs{!(vYS#n!j(T2P<%bJ}6xuW{Y)fDlJ00d3;{}N#dj@%} zs$sDc=0NsN12Nh!9q*-o3O#FPvShXN11w&^EjRz28-P-KWY#$2`N(m6(dEoOBh3+s zSrtH{()w?S@@FgMC?6)dfdkXGkE(n(-<JKgZ;4zBZT^&R1rL>a(uiDD#_8c3_K36O z_?^+pxmiSMCLC@DNl!ls>hZ3~rg`&U^lhZlbeYl|Ns2k^zy*)~%q_ho=K|P~vn5aP zrCZXj++`)wrJD)lzRD>$y;~PHL`z}o{BTQ)xucf;-mdPr?O2n}B@s6-mYE1qH+L%> zJ{(OblH`xNq~{*N*;IHQ{u%H%Z|7<))OViZLLg#@WmMP+_(kDeaRcGGXL%D|{<(@l zOD&y5s_XKJd4Qua4x+li!wJ}&xvzWIO>jh!PLLKF-?^wPV2p;u0YmBXbhSV0o1|?t zl%shbe;&D%%FM!iC#vo0lSrkbuX|@#FOlk(6|0{=)c9RIeNB+#9ZNOMxiKN7`;6}E zUH5X0{q3@jCL43F<~<yzHdXHS0mLFxqlgCr2|)>qWGM#!l+N^<VqTxpu*`>v97T6P zs4!4d1qYrUgSjl&+w^K$_q{|G1u|?Fv>G3+mn6D!;^cD5#GVq~*=iuE;J8ORfJKgZ zb?7hD#;6S0{Nowk?hQ``JG`D7_smv!+Sw6HzyPKy>LUP?7Hx7$h{Nmvzm=PfB;D&b zto)PHC416Yt0}fLNnSgJ)my~_m{9)c^7v~aAKqil#<;|tbsenkE=GTK{GKOX9<Kuf zj*CyO?>RK<p=M{54KBE5h?qwCFgy*4S1~f`E2xHsvm0&c<-Mq5c^{;aRSvCkTqWTo z>=4G2il-qUS=yxqBICMlB+u$8`ev2cT!(@Sqh8%p_Z+M5tC0dyFaG+xCtu0oIqry3 ze+wTy0is(~{RxLZ*F9;3{NSFT&xQ0$h2dN>3eR@gnQ-8{XZp%oKO0Buv1@A;neUNy z<vKy#@nQf_oaS8wtL&!9<!g&La*Dl|dVojYlG;Qu%p^1S#Z`M;H_SBaN_y3}drj$j z%JorC1be#L(Dr!hX%(!volM(T+esuSUml9%VD?6@2Is<XjtwXri<z3x<9Q_Rv=u7D zru^+-#5vwtf(lye&&+`#LLFYeK!ru<=VmmIez~brmiF%n*w0vNCCLeplcxc^U`yn@ zvITS&?~hyN73Nw%EF6wxs&chUS{L%&VEh|9*tSdMb^67IZ~6f;<{hul1c&<TWikB( zNGge^F)EZ_#WU=VcZgw$rOvEp4Q@jXu(>+>NW{ZhX7p7+!B*b)Uy2miB7Ls^-Ms<l zeu5mWszLtB8^FgEsicVLvBStR@J#KaG}>Uihr9k126`%|tff4?S=Kf8H;b>a;r>F> z22vzAgU#a|`EKQek;GW&PP{7HcAnZ1qnc^uO08^XvK?I)X;wf1(9HVV9C}#iR_C2Q z@!X#Rr65o~ccAI3)TNHy@gi|GrUA)2OBq6O1wEPMD)3S#s6GQnY4gD!w`Qbku@0yl zQsoQq&mf*zMSEhWwrIZx<m~JOb-r3C@;W3?AXrW~ll7%-|H*P*Qm(Yr^?x%c(B{iU zzM@JIdvQ>+tIwz?PqKUm8QtxPZ?5=)fl^3{nlRA-Bl|p>&=C`?zU6gBGRZ$no6fb* zsTEoOyi=)ui)YcF=atb+h1Tt`TjxA_e9rhk?!4Ge5bZvT#mza)Xvw=^=~AlEQW%or z7KS0#n)zI=&|jXyzLc3AA1#M!Sbv;=XvAZvF2H-cdcR$yrnvSmZg0Z3TA4lQrDfs~ zE+vNXuxmktHu6KCZAW+(R?*evjrB(C(1%Bj$VP)nT%VQ8Z~ZbXAao=fD-r#bp<1pl z=(t(Z%WOJcP>zZy&1{;`q0DZK2(fGCQybI&dnGs=&9{4}g1?~wZ>Kp!H{|>I@Frfw zE@sjg?7rN08Roq4DGzuwwt6&N8fP&Bbd=ob(7i{AbQ6u1=$Y^|B)ht=lh`<aQszj{ z=Jiy6!RY5s?qTeu`uCq#zN?o_d^{Hps&a~NmlgepvrhZ^C2C;uir06z3o)cR^Czv@ zVED33)If6K<leSQw#Jf(z=i!g+3q9`3gSajsTALMS_B~hzpQS-O;S5YE>SG+0*7o0 zl+3gt1Q~&0KH$jiPJ?mjuN^q2tV|fYF%5PtD_2FNU$jFfGRAH!n;@Ph-467j`O7CR z_~FN7fHb!%m)Lj2(X4n$uD7Z3$_UKE)b7w3CO}Mus?uZ9M2&5~sV|D<RW6W7^Qc6v z`4^iQAH1Cr13by#M)w&Bd`1%1&{A0FzuuBkO83|D2w*5*zZ^MVh&3uk{Ag~?>T-)$ z$f&GgBJL$XmQkr`%jR18jbvYkV!AZq4}Q|$ho~ogjnrl>(mb{Q=2+g36*Xm1?tyj( zh7;oB6+WloIO>+C4&t(=quL)nHM7Brsds-Aw*J<NsFBUhxXYiu_;n%<Su`04pA7s# zMPH@po!wLvoTPvKZ9w$Q*Iz{<<}euFutwpLWX5mAdha+z(5fc0R}0Rwq@N4kcyO@3 zlRZ#3M0mMedd#5XduoI=m0%KAHV%OMKDUq=7VW2U?%!*l8V2FH&leye?(Q>{!*a#A zW_o(zDY0Wqi$+4Zv!v7F^kCM=+AZ&^STnZZ0mHe9(DuJ5`Xrg4*r#SKt3WZyx}-Ve z=kDw4+A7RN8}?ViWF<_YlfK{ki@v<3Gm+EGXM@RuN(sCH<+?nSsmT@jWrN7I&E@Y; z_{}e1qE+>v{2QEYC>7-GXtlgayofr7hI7d}wI$DzGMJ-P4&RdlM49!W<aZsSg*Wa} zl%#kBWAv*GpFcjPhJ@IfTHNR){(t`3=h3YfGH?iWL|cp@r-Y+hT+LA*hvPL_kgF2o zqoGRgTyhl2<C1GAdgD|rgE?FdxGZ<7?y-{?@UoRB9oiM3Zf~XwF4s+&C>7&qP-y5P zkl>kbyj##|2~IX%ia~HClF}<ZZ5!U%F&X~dR%rm$P?9$xmWD}&y%rbs)dbVa!{oMk zDqxk5$$Ws6;kI501QG~CVh>^eJ>HXl*1LY|HtsFJ0I#RSp71J&wXNn5TxDODnxbYg z`w#p#!2P-0Hm>IC4n%SsW=WuqqoX{Dn2G-E#T2_1emMhw2XXjW1v;QtV3j;+3P^aM zJ=>LONI2<vx6I+^JOYjt<)+!2Q~oBTCJk~`Jj+vaTzG4*owZ#@Gg-zC){A)zed@mp zJ<Dp|xeZsN3XeLmerac3x`j2^#XHl<y21b*=pC4?9~e^9v~Hw$5h#z0DijBb6Ow)| zB!hI@W@2I?AoLU&zHa$yG)!#3{G!RHqXFQQ(=pQel^_?og=oAu7=N7!eg++l3Xs;| z9PMff9akA9NZH*jEp2?BB1^Tjc~U{?Sw9S*!Zhvgd;GNqWmz4P83EOZ`}t5!{gb-3 zQKyQk--v^lOVxegpCV=Nqyl5~pF%#m!Y)s+@SD;QAB~cC>nOi4+7ig}TY^3TzU1bO zmmy9_ZJgWJ4rg8L+W~AJgP^!<E`nGYPyAE^U@5?~#r#}wg9EhJEhohFsMwF&itmf% zyK(Ty0g^3k$(sL6Bu6Zw+Z}YpyDqkp`0egPUSM$?DDlihs^<pvh;VGt2;9P@9>`Y# z*e}J5OlX+a)p_@ovf^t}!mVTVW+BrdL?nORH3+M*9-dqLy^D<*Y|;@eKFOma34Mz9 z>0W@pCgG>KtYZkIY(m~avgUC4Z%?;72>A(ea$=g(O~X51!eHM68TC4*qRz_t1fvoL z66b}&Sw=b*Q0~qNw?g+6ez`!4G=cv|g%IY$YH@Ks?-^wbty-VWP>Nhk7Yd0QPC3Jj zf!zP7mX5h+DXrxuogNz2N^_7EDnxn_hKTIlgW6f*$L1RWK;I+~%jISSr<}`omg%bN zfjwp4g?D)Exr;`Bsxq57GU%G?J@W32a>S^Q<5*sE<1gq_zVV_1g?QWyRdZHT63_Ce zGK=Yi0%?F$8T0sZULy9OpPR*3PF+@I*d*O^lY2TV_>PZwkyvQTRr#A-#z{U4%RmKo zDjD{m@@AD2)<42~2+(~)b5p{(^{O}1Aze_!-Bk)Ukp|MW5VrgRYluv|c7Q&XZvsf7 zRp~*nrWeHwQ6uZ=9LKptWTS<@0wwawlX8(V;Cx0F0P<*&Iy4}S+$&W!K;!i41)v*{ z30Z%V+h+$w^J8r9d^QUYU}^Cx<7s+KM#}ni#5b5_>QYD<7`f|_U2qZ{qt)ix#;N(j z2#Pue>V9x}^0o^MRk*H^Z`4kWQD|8J#P8UniVjCJjRK7JgB4btr9g(m+&!jch*h<) z=sQk6SjUaw+cRkl)<<J}6aOo(?9p@lvFzrYpdyembZ$_2^BNKpwA6Bgc-R2_kKdD? zC~eV0AtiiYhdM8E=dyR<n1Em|sNWUX*_zgp6qKZ9NM)UF^XbTO){5xG+9ioa8a1d& zwlCNYeeb59v%IjnhIr!w$9C#~T#}#n-aR*5W{W=kc&+W%?;x_SnkNY4ox254{AVM4 zegE;Pqen-ZuGWrN_}ar8CaD{1%7EjprS|frx#&pXGOib7j$c<gLMfi6IAn6Ps&4cl zun*B?Jp>9M!p+^X!)*3{&ckrh6J?wPFI-t-qBbwtG-Qzc+};s_;;`2}-in|e0(!b= zi{e#}72>M9Y2eKpYt>otcgTRep0m8LXaSOawLLYu8Gm4Yv)7O@>(8kD6+8yPiZop} zqgP7TEZORpk6xOLrV9;}_<$&9AscYP0rT(sgNI@0)@nQ|WF0T}zCQFdZs%NM#5sf+ z&El-Em^{xyfQk?E4hd5|?koLhD*fW_kDzqW1l*qQps?e6*|5MV;{?BlqZlBqdm$8i zUj}2t?MUYX`xiQ)b`dofbf`5?mVGA)bxk|h#Xsa*Dj*qZ@r3Rt*2_s8?H<18Zci;_ z(WIl82E+0@Hsrk^G;pK0^u%d~zk~GxkfF(0>1fkcYTBps5H9DC1i|fK3Zt<!rk{7o z&wIi(bKZ^e`i9}MWPgFhC-$wKyR3pH|Gkv-aH3bQhx^I`D}OjN@x)JD1+(XVE)uW| ztZG{;-}C-yJWriB6C0YAGJ}t!r9Fy_f+qA1k+|#be?T{F2K>3-bag+G0x?tD+Z9#O zUY=Nte}Bx<Bw)L>q+T-MFo5%V4ZB8mKokzmwi+1>WkuPiR)@Z94#k=M^qVhve7DG$ zx1cn?fRG2O*F7LGCiAkwF`mSQ&O+;{^iUl5EYtsx&N+X&+ZfzISU!Um0mR_p%a4hn z@?f$HdFm?MD7z`Ajut=KiEfhLti8XLn|5Id+0KBqwNk85_oU4-0--V3xcFNB1T+;u zBv&wvarOZA8G+s`+Cnq{=BLOOy=AV5c=V>(ihEN}CG_&zaBNz?;zB6`BOE>;IEuRb z*6Xb8sE|^_O9MU_wY~k%41gR3fj*yRzQ?Y=3gTE(U8BrJ#R48=eJeN7!BX_LrzUbq zW{y-iQs)Tgqk8e9eq!|NlD8hubWIg9XUvO)0|-)0EKngIX~&vU%sCp`_f?9pGYiO? z@HCSG(g_WA56U5i?y-!+<(1KFpJtk~ncvGno@K-rqsn;}Gf!7u<=emDyV&qLU_12L z<;k~g3rg`0YzyY6h0KsQ&zOLf<NmSsBXb^3o9)aeSugpe#K7fU8B{>g_XM>|>%kNV z$^Uu2rfvq?8*1kZFv%1yI6C#L&Jjrb+4UkZmgNvQvvlMQXVW;Z3PIzqJ3$7;ms_M3 z#FRjp0nAKIsCX%pMYwJag&w$%pq#~>kJW~gqnuz-Jsp{?J(_tJeZ_<;RKILf#u{&w z1=q&qINC7hS!^fms|Qm2(K+s7EHPW|!T{Px>d3z+BELg5EBt#<15-qqOEvUcAqJ}n zpgOGZ7UQ_bQ=$cT>_^}Q0z)b#3P|>=v^rX(FNzDZEmY)RuvPe*(MJzV;Sl8P{mVbd z%fd&0kEwL&b^X2X7Z3QJa8BMrMl^x%goD3=Q2QJ5l+_qnLhUNPmmHU42tuaIJuY6* zatQHnlYU)Atf~@BIWum6N1#No%UGYW`6#(o!WY=5lpCq#6OKe>>8K2{=1C-qL@V`4 z6{Y6@RY0o0#BFMmySux&$`Z6~PyBXXo2eape^~8ZeNnz1uu2ZJ$B_&pMI-pY(5ACS zTw4HfRO&>jdhk&{*-Td&#AmzH_4o|MS7KiPkMaVYSn>Vl!~Vv$Qj#zfghP)&i^l?{ z{9u+AUV%xoM+=2P?8cm%Y3;q3d@kj~WhDu+jkXK0M!Z9zNe~l3u!DA_d^;#!8C5yY zXStuO3SJiBO&N(RWCOV^x)TMiXcm8&*!p3D)yMznZnP%3H&Z>8fPt(iB&!HlubNO$ z+kDpB+$0x2ro?tp^%o}%s-Z^I;3Ec73x7;Wm~#?06XC?dd@w4%C7ru~Ta+rIhd-b- zG1eZb!jQOg92Qo6Xbg|0HXjtUKL>tL@e^eL(Ogv$w7d;6%p2<mv`%|llZ~SI;^w!H zgyPj*k+Ln`Ht{VtT^l&0(LQcKj!?&>U&s5HCHtrhjS6EtaixJW6n!hM`y$GeAiF1f ze#kjWp*R<vDTkdy!wgHkwC~7Pj-CW$A4+kW&Sye@zs4XV#rn~q0z%Cj&t}zXAYq9T zb!L*(1oapBuH%oScq)9Hxx6ytsuWW1eopZ|L&{Nfu5F(oMo3~fF|k?xq}$~38&3)R zKhJN{$fiBkg}8~LY1&SGCZhv9+g};9Ija6rMRT7J{U6kPr0>qN%1ih&4N@nsXK-IZ zAv(Z#qjaJ8RMD3gH@bbtAXj(_l35ZZefUlKJ7qWNzN5?36by4oOn6Ml-D!X1_3FF# z<DIiY8OKkSCznURP>ASo8yR#$5E&CUi0mGyXwsYi+*l11Ayfy@gsmpSt`y*m1m&mM z?G?nT$>{iO))M$nU=67O7<wiRkS!x&{`)8?Z#aux<rUeh>LSf!cLZ@swT){Oo>O;q zOme$Y-=WD=H5E|3P=x)FX=y4bq9+3!;tCq|nj;B)+$iu=YIda$@{`|cP|iOC!?dC( zq4M?|VsKA<ODo=ey}+d}^7#ezIM>$lceiEfZt!u~oyRKVmOM0NE0T*RQKeN8%qxus zHSSCkoIc2GQS53<pCJv1cOrZ&pwt1r>2|>$jH!{1`>Dg##m>c9or<$Y$!Xv1(>&72 zN%F1qkLf--Q<#U7AmKN9-A-K1n9j(}A%3H=LL6Sdq#!O(a-?myw`N6{7>?R3#6|x2 zZ-D{Qc8B?Td${$G_EL<aPMP*?1rvwt6_#bvqXZEq+fi^DkL3odccV*Xy<AV7f!stF z$~x&{kDS()C%==>iKAQ$Kv{>VZ~nrm4{89tC0%V)xItkMb#^V|4>hkWl!9vqHhG<N z?JZzQPxX;j5{0!OAoZO`TsOc1ySg93GFL3xGgne~eg%7|!{vDG_f~U{L4DZJ<&-Fr zeWgC^%@McGoYKWU5z5WgWJKT9O!i^~v&86FZN}H;A%pN^BD0MJ;Lgo8o-!2gcm1JE zUG<c>x*HVEnAX#;eZzrL_z<r92to(VpIOL7O#nlQa%EIuV0|cZgsEgBaW1>=#)WWI zuW`W`4o_ir2<To-2bFMMQ4)Z!eP;nda0jE;s$Zg+zuOd_Y^h24#00L)b3pu>A-Qa0 z8n)A$6*&Z%B{cdxkSYe5TQwTD(*1PLwL&iE3n{CmylhtEBzLIMK8tq6b~k}%?HVTo zPP$wMQJJXkxPb$(UBX2?kZ4Qdj%~XZm|oKzgL-icA{z?Q2MPVZnG$ouHbs}<(A>>< zA3z?d!^)V&g?YMXbJ2b`1y!S~@sly-YgJ{}j0-Ze8;Z7Jv7?86M_?9YC?mCcSZcT@ z^?q=}y8)@<!M^2oq|uHG;d&GpD5DhLO}o)lZ=wH*t8N4R(rcA4pid&D2<O0}*jE+f zc=`+Wf3XNdpZ~4D6PpA<i>HJ%CP(5c(AvRTk+zyg0#<1pqLLPHI=+RNNHVp9MTHFv z);+r~(0Cf{Jn}_SUkeay+ZrAW%RX>A9DYSR_FXELIjzd;<_<<%;L*{aP#}(?9Z9>1 zl?n3V*La9PMU8x#O&8^(zW4!ZM;0Bw&}~tKujJ*)Tzi?uTG9pqomZFM$hTaSp1(ke z*8m$iCb<o{sbLnYGMM_n*$NKS`^t41_E!1!Vcg+ZczeGFpV9x2{CTQ{UZ_^8)z9>k zSDa=!2Y<To;VRjg;Z`5w0Qt`#LxgKSK`Z4t{$PB_8OfGY5<RRb)&Kn|=nL=N2X7qo zOi-QRW-6?;J$Q5-+TXG@0C~A+!@vC*VZ?u5lnbGba~Hgv4*g{Cue^dKusl>!Y{dC5 z7xfXxw>=Q1pq$d9BMp0o8QONxVD&{t48?Gd;~>%a2aMG-NS18RbyggP2Po3x%|o(p z*n_0>%%`Hd?38#Fk>orhwgDw3L1_~iLmOsedH<%Hb9~)dhNSR6K^PZBCOwboyOf-r zp5q5k(4kdZ6-pHSIW3Vx!X3KIF{rZBXlv`{%Uy?mX`fCa%CwU4=n_FI>C3~W7&GwC z>jC4xED)jBx@m)OJv0W%1t8)Pn%Us8!oGh>3d2P~keOCLov>uyt$`!y5mKHbhcG1) zuO79{rPL8(Y@E05h-e^ql`}26I<<DZ%U#PVogz5)4pEeKw{6QrB-^#wiOG#aoh8;2 zTmYv~%($UdrFa+fV>GIT2A{6fZ=imMo1hg{`u%iN^c6>F3GHp_{cmkX@jl1|SIADd zq3ZIj2r&6(Nq<@1J8ogboCfd4!w-N)oYbY8R9!Y4?i8(t>h|3~5t7SON|o&z)NQ%e z_APaZq1|z0HUHsEHXpVk5|!2pwHFMp@^OlZf{1fx^UV5BaTYWyEUosW)TyAT&)WT5 z^%p6h_`4W@lCsWRU{TD_c%MVqRQ-71t|$BBU1s-~j=kBV<;>KLNfYjFXUfb}%Y?oe z<K&)iqTtL6FAZthK+jiH0=4egyZjQ4NBTVd!5)(Evz5DpT9;wR{kGp-I@sgUR=52J zb&-LwMqR)c>PrSV%z83i6}1ODB(b_3`H$nlc59Mn$gx|BT}hD<5KXg1Hb`b)1fxAs zlSgo|gj5P4lwYwQA3GYbmPn24YhvoR2iS?3QRUXIA)Ff{rF95ZfsrR3G1=Ybf2HY+ z52cJF;dLDUM|<Z8fqnd65ymrdNM_hF1VhM^Q&2RU*zcW~{+t)%%BDOAt772ISF*@s zuI7(n|H5YD^yYDqBy*SHaOuv}5G$f~D;2?mB%mdgyE~S6L}Ag>rWu=S%%)ELhVS_d zxFmQwV#_i>`q(GlVsUhq4BH4qR$%NFbpK{a@WjH&ah@cGtD-Ue#vn`S;6Py-x=N>; zRK}<Oc_qneHmDgzgAoU%UAenqz7&x%*Cy_ruR3ED?l4xYOVL_t-=05XBPL>vob7vA zfOaeL-Om^K`R%-F(bsLF!By*Lsjvo2KqP^>p;H|X85DM8#J{hx#8h=})KTi%o>o>1 zQ38RDD)1^6U1PlZdE7~Xj<XQwg+_c+i>lKtk)oTXotJcobkC(JyF;~8oyIq5nCm2d zH*cVVI#h|Ftb~PARN!*x^By=}*wxL16$ds>K85T~$V&0NdmXV;?mxyizCVHqMJ`-4 zDd`DW08(HgWuQWUxcF0__3;tUCdV|jtXN`U)kxpPq2G#-5wL=_(gtaD1>w4qRfhRR z1`WY6-^W@(1`!1Gc-CAr*HaSCc+O*JfLU!4?AB}=Lew1YZSS=UKDXW8GQo}ZusD$* zL>fH)>c1|<B7-D_&mbpMrfw{>@YO_!Q!Z($GoMi+v%|{wSajq>x8>YpxnC1;9BN0O zKk7^4^5O1AQZo$D-eE4R%tqwJSHo4duXPLW;t*&+8FMcWW7xloueGXl2<i5wtMW@! zt~RHR{rwE<Dl{=|Fx%@w5+~GAuXp8W)g^s#TMO5KW5<o}mB5bLWo{utd^p)R8rtvP z<<b5%KmzGV3$gjpipbN(r*@Y;J7?L8B#a^}Vq^NJPZSn-aYjTklT)PZCf)hoYok^Y z^S*YJCY&Xx>X!kvHY$0({h@q&BBvL-6!zx3$Q@dEEA;Z{kapiP2|M|{=fEK8UI=Ld z_dMT*`ruTMQrHx!*Zq^^X!8u2t;=!!0LV8rhu;WUasqa#&W93vlz~n6PK{ULE21_$ zqmo1EB~LnF%dz25_(#MuB0@PZ1J|E3Ugw%^)Gf(&`iDoarzYhM_G15IGj!guOtt2A ze#`N{VXJ(_<tMDW8wyaBp)fo;@@PyEhsFZq2xA|{VV-Fx7GiZz&^)Wub&N}i4?WqP zcxR*l2Wo`9N%tMPLeoFxv(@+DCO|-4TZDD6l(^sM7fjBAtR(v`A=p=Us_5nEAr7~D zNaH|X8{K~s`#AV0OfMhXKDUqlHv}HN>#*;rf;i3KO}9~3K_`#NeBDqj)ZnYyamPH2 zcG*`{?t&YMlu$vrwk{8d7GzSBzDM-XO%`!da-FGg0%#uQ4G6G3zVLv+SmhqL0@yPQ zXlt)fSn$X%shSGkYdjJDcY8IZjZn>-CFO$h?)7)YHUOZ=A>SeMxt2nO81#{KRQyMr ztp3m$l?;RFEf*~ZP#MG@+9URK`ZsWqXQJgu=b3m2sg&VGK9o5VTlGu%r!Vo|H?Id- z_1pxfa{tFTz_w=nTy`B2o=x{ickJR}kREkC>~j&MjT%zEeY1$#r1(c@?fvx#o*5jM zAMhlCCOqgUhYX7UXLsTetpwBD$(l7UsvH`Xv+|vrTYra34dYG%$eWW&nBsjR`Q1=~ z-0k9b;tHGL)Qi`nhS>t)_GS2`uP;boUP?SC_W^nEg7iC#uvJsrMc@l1rB9hfUP*Br zTbTU&Qpa{!1Jg(TD*A_!Yo(vC@aZHU`J}g&fpW>ZjXqFualdNjA;GooVZd@kuX49Z z&MZzq?arfno9rOMt^%jT7c(>SAB5YF^myT%B`{McisTz9Sew25@FI{rn|5H^cq4@b zf;)D>vImWmk<A`L^H!Vg*8qJV#4D$ag>*RF&#%JL-Giw?`IM@NN;0V|)K!4DQjf`F zxh-^9FAC>5#Ng2!xc0M6;vq1g3FV!!xGKja0erAgu;s=X1vbK<0^E_*d>8L`?tcR@ zv)Rwu?4R-+y~m^yq)1)*S&#sIMGuLAsX^l?N7N~7PVXk{9$@W}gWX)4!0>_llmKD7 zM9}jez(0ANwz@T)ok*_AE>gb;rTfLzg<0;q((n@DS(`5*(}KG6fUP-=%K{hLPMb=+ z1wNw7B~8!)tr-5!aehZ$I6WWtyE88RX}KCGrBd-t42=+)4}YtQi5^QIy4p3@5ErbD zYz`hx8(Vim+;6Q(t~5rFrVl*=1&ppDinPMKYrHPV#~>tZ(;B&OQl77IhQU79e7YV$ zq}o&s%|8j4Q|6rO@k)Dv9NGTGix{Fz`qYEl{MtwdIIR-t(JEg=DlDudQb4_wyN2`h zzON#++wsON+avgSI+b@+wUiWeg&-bupey(9+;)B87<t;V4(pjwJ*xU*DW~bdc?}ie zca{~Jo|{9<XdLS<n$k@U`C;zsI!ADWI;jHZ3D6AhV@B**8j2{c4kv%HYhq%7n;Jm} z=<aEOzy1!h{E}KqpRTF9RnuBllaf}x>{{~%2}k*Ykdo~TLaKy>*pvLzb+Pu3_t_kY zRwibm@2Fb$F4R?6Rmv%K8fqk*Y0yZpa{B5yGXv1)a}+Uw>*$~Us_oQW)H{<!6%5@q z%**VODZ1np+od9yC%>H8b*1E;m!c)LfR1fPrwlJit8AQ8K3fE~ujY_jY9^HJri44N z)X0lyEye&5%sYF955U0++h1#WD;o4~aS3!vWs2yG6RPG}M%@M?x!03J<x<<MKbD`- zwHs|!F!(`@6fj4I_!K-2xna7-Y}VYarbLh0GkZW!xf;w!VRs-vt~;Ns-`+ErQxvIB zp$yG(8libc)Kw3L_Ywh7W^OtlF2+{m_WJIBLLD{|wSm)l|5^jjW_i@F@fm6LdS1ZR z)-k^WtB&@I!M3Y&YG)#aZP7TKCs|<w4VxI8r`5}n_cxiMIHtBEpBfmsAR>;TDEXb6 zg|3LmZ-(IrT{BEp*M89P-kkI#T>Pw6>d&Tv>uT>^oy5YniIIc|Q>1Oi{`E1Q|BpYF zfR+qp{BhC-;u5Hbc$wUh%aRTqs@Y9{JyRuQW}k><$dDAKJu1hG#LGy16_^@@&Ub`z zh(lBp4XfvO7MXp#QZfer^81zqEk6JA7qkrr1_on<96_N)EYkr~XaDwtTp;@0s^c0_ zXaauYuJ4OAX>d{+ZJ7l5$X%;u`b4g~x{~E1Vyt^>&+oy1aa7ln`!nbWrA<6_P=`oA zbzaS&yzk3k21W*Fh!qrJc((*I%J&&IaF%W^m)ffEeR_J$%94diAn7QsCyan50JSV! zEui*2kQ$b6q6DYkvQYx9+@!|so_)kv6e<AzzGVLx5Oq!8zeeO=M>_*m)Zz}!9(EKt zc1>|~W>PHM%JQs7&e7EZPTjuH$VwP?z5k=uta3$XDHVa(!qA4y(*k`=H$YR^4N6ZR zF08+`u!CUWna1!yBmDijS>r1g?-q%ICz2HU(yW(dm$^}(jyuZyPECfp(EztB&m|#U zXo*gz8*7-9A28QMULn%`C2HR8CoLs#ehl({Lg!KUOm|z%Hii%V+lEQ4_T9=HDg+B` z(3Uo|<ncqLlK_K+_@%@S?wf>aPTZOX8r@5TAXQ|zzc20ac0jAq#?c<$^eLUH1&E2K zlBCBJ!{@POh6Lu)3t@|bpE7gs96i)1-6<M#VTm7a@d9<NO9_YMz1|hPYI1&cx^K>7 zy;Bv(b*i+`^_N$G0Noly`t7%;5I2?<^yYU4dy>2V=aX_&F9v&cu{Cw`15E42P3mRO z1BD)e2%$eaQ!=IAC_c39^7Od5LyC?9Dbm}Eh#csT(+`ujo*~UduQl5@4*G{*UqH-l zMC$&4IJMYMCf+0L6&373Rb&OM%iyJ{-tK=UI^KV)2~zyH&*G$c<B1UOGU|oZj{62` zdAX`B;Zl3DoS{|-vknE!-NSk9A0C?ztDO!gABFWTwIwb-v?WN>t72n^2COeF!5a%+ z;xt2j@w;qJ@q7>^L=6vWD$P5o_n$!-y%Yhib@tR++8M{ktnE-U5HalNC4mWq6NBCL zVo~RLqPF2GM)kUi52PgA7-~nwVj0Le&EUUX+E$aI=y`vyoi<&9JpLZO?bMq+g65Jw zcsF#&Zkrf=1jyD4M6JY02a3^~oz34Mx-%8CKAeCi2|@@<lRDrkSrmp<TZlqwG{E^l zh1~W@12v<7er^E)a7{F*Ja#~?avP*%ET-tzcxO~7D@l>W7jN2_feR3n@m+$t2=cf* z;yG{pZY&lB2as|OkzIw0(1|CRIo%tbx1^2P{zF6%{Y>B8N?Xo`nUi4u8b}}OfqmoF zl~Z@p9nFe5$z9#}Fb4Ge)_(zS;i$&V*{<aA2Mh|(LYvV!zXWGnD84s`vj*1gKT16R ztdfLA*wjbg2^d83?)gro6Zo*uM4J4K0~_5(<h=eVI%*Z?k}zumTC9VPYD_~Rj~YHJ z1q<terRQHs`rEqC*qjka9T6No9#6M-mt+z-{%}^oQn(xdX5aA~5uIND<GzNmr^wb0 zv3(yKPXC{RtDQqEbI|1=r}@8u#pbb94};_erLjw3Qypkrf8|_epxeNdD&R4Z^%!61 zdZy!Jhp#oPR8smRk6g8EEYCG>Y|za@&$)n@<EloKlS4^lBCq-1<26E!hx%3N8DG-u zuje!MnU`4FcJ@-~09Il!4$OP(D@%FZIcaNSud+8LpIo3Xi#hU{U+i$WSG@{7IjHrh zCmb(v@_~?d_QaSxq_9!B9w&L3lD^YXo+Q0h1=@GZo*0pNjzqI4@kdPk&aK*Z9$2%F zuI|;Qeb+v_hX3se*JhA{NYfC>3mAC!&r&v|R+eivy@K@fd&oq*DMN^=2ya;)g8g_Q z#m-U7a0>#klg;go_Boafdu?D!6cC2x^6Z)HrJ|i^mH`Rj2$U8LF`AOy{th$U42+*# zFV!>Uc{QO{Ry=ZA9b&Gk=0Y_*0#8(_QCPU^lFRHL6Wg|FdKJNc>PugOeH`F#fsd5W znD-JI0g;WODLk)y3!e{k1b<xP=yiE>9fFatPfsb+n8;vtWvm@%nm`L|fnQ-xufpkp z?|WPGfo7RGR^7R*;A;dgW*j0&evK=@HmGfdoEMSel5>)#TV>)p0>lKpN}m?lU}Me7 z&$Fyc0;Hva0;)a`=DWJdI$cwAMg=PMU_qRFkk7`Ju#h=11GLzL8qd}U#%h8O%_D1% z+kAgIB$g$q$0;zjHf{Oms}{D+mKUa!&bV41(u0sJR+tZZ4o<MjiG>0_E|<odh?=in z>?L^=cq$#Uv_Iyf5-AI`+%17^_AFcY9ty4Z_vIs3E9G00=yhW~g;uf90KjH{55ER% zeW810w=~BLj*Xro_A_)U*dfk>m7x8PWYu}5yDoAzWplVD&ain1tusD|$BqCdmmN|& z6e<IP%ryTEgD)PvjLAjouU{T2lJHI$cGTIdO!|;@xbMi?5R7^~n<$h?H_*%<t&EL; zZ}UUrKsUoha(zW-*>b|+@&-NJoA_mz_^Ww|a1^3VKG+F*41{jGc3-?gU%{c+T>ui# z(3<9OG2cq^O)jXXyIrxGn0TBAouUpOv$m+AVxaX{KdiLpI<)~AQDjW&T8>yZSG9nO zyLgFf{w=IRtYwV*Gbzu9KG3GOBc24a!;lQND}<Dw(adi1<Af)ogrwc~42w%OlK!dO zv8`AB#;OXQSMY!S%>%t4)}8m`Muu)rs8_NO0$5;C954ZR@Fh)%x4WLcH(Ra-5)u}J zHSO!`9PZC|9iA*|b1ewz84;lg0Hsr~x)_mZyP(|_D~qj9)Y~TQqh^d6Z@t{zs;0I7 z*6_r59vxR{@dWrXT0I!TG(Z0lO0w@`7$yKMGz<<+>BAuY2s}IOmGwegzZr`64?P#* zbdb#zwEwi`zpPjQDO#oFC>kt#ySn89tn(geC90JW_V{+`an>!%0_wITdCh+73+{g> z)EGv9fn7HD1hhK!0ls`2&tO3WMmJpJ-((+m2~M+<$#`tQk*VYz$WZkYzJX$y)%BXy zKn#r7Em^H<D8uAsksvx8xFx$xjP(L_#w0d1=;8v8=tPb*0T*cNs)si%ypL`4tA)zI z9oO~l+_z{m9$QAGVw;7Y=|1PjMapRU5P`Ei)t5v)e0WJ2%J&Mh$>kYq`3SusB~?%U z`86`jxsBFxZNvH20)^h%5W=#8@>_)@NR?>Rd_WtKE~TYx0H1q6UjTHy;tIhN0T@ls z6N@sRJ^CXU>ns{?!ka4y7ZrAO3lMQWk)6*RE7A|(gfUdMQ%y+N@ja(Y4stA^KV z@!CA%Pb)^c=Vj_p^q0NKvd<=rP0VI4;MgjE#5khB1HKh-j(>2MDl7ne<KpE`<A%^( zt`(?F@7U=Q!~ZN0lReJ{nhLY!8cGflO@EGja)JBm!#XD~wIGTj%>%2*KPP6SV$)tZ z!+qCN7U-Lq?&AT_VZV9mQ{)49Gt-Kzn?*@P>_La)K3%r^g{j_eP6z2apo8_+eERj~ zxm@wx>lInTq8xQ;Nn@z-+#j3xoOaNJIi-80<q(uR16+N0(W2^gPlf_re6RsCLZ%5s z9u3omI3RPUQSgiA!>r9B&E|W7W;SIHD%<Qn!Y_Or|E1FAI=^Rcy0U8g7RUPsdy_9< z!9%>a(cC&qp4DF;7{n`L@Qo%TJrYV{Ae+rUgx^cS#DAUedd_-FgmX^jNmDy$W&2s8 zBZZHW`hjmJEeQC=oCPSzk7BtM?Q!SD8b=ASz;GZ%Kyrj6Mo%>4PYvhE&Pf$GX3tN| zwWZr;`A(B^Tgs?9jJ!)sYs-5G`HtD#TwC=H-CSV}?rb`G;jJjh|4-_#d`u}7ViPD& z#boW9qxKXp3p-jCa|jOrj71^d?k|TOhjX76D@W*5{8|W&-y!Gx<bNs(^O=9uvM5?> zndD`t?4UE%DmGPTXg=f9_@wtE>)_aurin@6>KE6;ECF!S1>rRLA!o)Nui&{y*P&~W zul+>6_b#Fs`0G(Ow3Matka@iT<xMKvA!1Ja<6I|mH;PF0)PfEw%+4;iQ@tc(2p9A> z`h-5LCZtmzQ<;?^<aQpacNvmmCO>qS72-W}z9VLEdrYx&a@AD0jK*zQUoR4k+yHp$ zLg>L8eWSj}*~5)Ax6V%lyM602xgn&vXI7=63WS>~D<gb$lW}?Rz~h!*yeZ1Pca5wd zuphr3z1HDeA<r@8_FpN}i!a2D@ZJ4{v^W!nJLCuWpl()|k`MxHSBV>-G2<~ljLgvt zWeXh1|7A7$N=w93`D)2!UV9eO>be+_U54>Vi_6F3LCGuD(l@A@_d3dhr9R&Y4v|jz zGO;AJLMMv4-bgw2wPJx|212~4)leHrKD5jaaZk}gJ3dR2VoG||DOh8i5g$yEeIwX2 zZCM`Nr(M_n9Js7*qrdn-|C+La9qxt$iPV$8dnc(JMr~tMYH&bYbSts0GhfQ%J8wF* zMQoQl9%ViUPW)pz!;1D>WF1cs&1cn4^gMu9xu5oY>eJS=;?m~ZhjN~#6?b|e<ZK=k zeER5VXqPSZUM+)eJj;tz0sciZ6WD;sb9ZQdYTdLSz#kBm=mHbuxw+BKXfGjJ67?-S zp$Nl#iMcDg7LKK!Af8}{I!;L{`A2nmCEQ{}`_NMxd|BtjtWW#hX`nrrR8RJ4MrNWc zf8ON$>vuhZY*fs^=t)NpVyM=0ELgizw#Rxfsk{5C9UJ_o5BF<smFP=cAV15PD(t@F z+tgplNicEFY{YMjK0Bq!3wBRS<r$LID+2U(&^BSU)5AHTGNC|qO13cYN{R`+4a~ZM zAxL_wU@bb95nqZl^~RG$uo58X?whQvajNVH9R@KmAuF#>^?wWnX%{<n!Ppt|FNzJx zdcrJ)$s}IvE!mTZHtciPh_!c$tJdT!EEK;e*#CpzEQFu^b8&cvfFT78y9o3sF(#UT z{Q{=qBF+`*h_oXTm1MXb3z`%icfaZ^8ByK&-uF5r)7M3wae%+f!(1I9#^7!p`jTvf zZ;^gJay@9g49Nha!ozDMghl%T30}CkTM^qUYSau{Um74qUmO(B;d%88fzo2i;uXF1 zD(#I*vBgp6N>HrsH{84rmF8HFNDHj2Tg_gpEBXa}Kdvhro83V0y4zS`NlCx6JG@A1 zx$+N}@1jVOtf<-3Y#RW*2XVb!t$I1Sj@)`%nM47;^UlpKg29XSlcx3a$DE7Wc-`}? zl6LVg^D2nW_$c_%Dt|R}Xln`DeU2QyD;IUCv|ZT~r3LMz-ZYd{7x9Br(wZLdmEv+h zgB`vQ>m5WpS^IXd;5=WyT~HZ;@d)5i`9bKLP`nh=&btNe8>4%_+%etI(nkK12r8Wh zUhkTMDhQ5mIdg)cu^H+rbT`@3F??1UQB>PPH6Yyik!rxLW$ORVhR$d8YW{G<RVSpo z<!E)hiZmaj04}8LM&GkyrH%)yV|KU~Iot#r`iEYh#y05{zyHjf(vLJMJx<hwCC>;X zVqMC?=%|p#C`xVf*M<LEWh0Tejd!Tvql}!W!pa8hSbe@-#-f-b47%tXWepZ^k5qaQ z@91u$c<k?F57*_GA?w&*(tl+u#g$pueh+NM*({d$f6y2-!^{!=TBD8UL<8F*hh66W zIOaCtNlpEE@sKafK^|D}_&F?KGNmb^4}ZDhczOk@O?Ty47aiB93DeZ&fHd{RPC|1n zrJDl8ioO<b(jeTkC)A8w(K8Q=rJVJR%*$ILnrMdKBq(?{KncD?a@JlH3~Rq*%zm~~ zj55qW;hW&tg9o!dV?usBf$MiKaGbJTsjGa}XxDy+VOl$E(>U`&fI%f1?FvqST$W-& zhW|^Z6Zfnfvi*wX|Ltn3s{{W}N{DWv_3c(&pvdjiBj)_-r>CF1mw2yrLu{$4m(2D> z4Ol?v&i!mUtYxxiMm}Nt1~R)r?#lz`AYYm+q^nSwk@JN=%zHQ-rA%x1O!F>EG~WOG zHSb4RJoY=J?yyu|fw**J^Q>D%D6#V?kQ4xS@ZMrP*r%}?mL9!4II!bI7`Wr^1h1Tf zmo02$fiR^Y!8lvTI5GwJk5~YAPZZpqFSHbjZ@V{ta%DT{B*+!RuHzpq3^?j6A({O2 zEI26h^H1Xwp?1#?(wrVNN4sW3>rv-ai$6)P*@@b2GjlCHxdeq>c}{a6H?fnG`c@bC zuQsqB>tD!HjQ^QbiDD7_u+JdZ#|%8O(yEI6ezhKn0v8{A4CLLhUF+Gf?1^69?**p{ ztP>HFTi`eVR|Y9sc3)i3+ix%$d)vH~-7$h?98&>aiPXPNhg*4k4Q0r;kaqERlkgYS zF2-r@$8~F=G)Z=oU?<sg-Doc^@oIpM$KkWrT!vz?+<YKd2)-LQf-|n~O>~zJJqGrc ze4uTE1(J49K>CZK<{Ls?!oCC`;uMafYRhlTn!F;4Glfz!vPT>|aE8F5>F(xZh;|S0 zAr-2^imBqJefzFXKib0JQdYu#mkej@gv*u+K|`#8tw~+27oQHd5+fZLs3(SHWO0tz zETz5+g}8w60Kov6Obpr-P7Lm%P`_3T)IBoTJBKZfdtCUgfn@`^_EL4ea>3sQ>S?h_ zg2T;PAKW3XI}AJMgoV0ELnu-?VE1-Gsv~nPz)AOn)4g{?iRZ>pz>TSrw<EyX{lfR~ z%SRihHj(}t;5iu~IknV1Xs{1tBp`qd#s_c#z=h&$EEt@9$+Z0gFWB}{yVRgomduu* z44&OASZF*L3Wi$y%uEwZ{MTANAC<%b@Y(Nx9D)pi4vJ2&4>(oh(Q|0W#s*r_k+g7_ zE$;zz6>xU1*+Y|HKxrFS&qZi80peK;rTOeLi{W+O6%hJMou-fTEbb|!z-EgcL_1zx z$M)HtHev7zQMf^6v!2axw7g>zr~uhlhFF9i)<XRV-adPR%HM+X-?`RQz&hGcQ?VTi zd6II7$~*IF&^~0JG8II<zaY{v(gy1uPy+jAi%$+GXyTq<wHLY>)s{`$ts?q8DKky9 zqp6DCQ=<-#BNWca^awKXs#|tRfUYQDBHaAfpUs|hMPi_$*{pu>r%X>~lVT_~o>-$8 z`sPa3WOlMuW6SJi*F9uN<a~%sQKUlcAguv|W^s=~r#mQg>Y#uCDDT|jbQ+{(II9ww zH?;6S7!KJ|iD7t1P7friRiq#dH7Sl9@3z7U!+pkTW;eMrv{=G3+3pVoC+Phzwa;YN zo4i`(pEXc%uEWs(p+@w6s2`$6L#gB3WD9zgp>==z1-Ss|F?{gMnSIKuF>Y#E#+|nY zYY&%aTFb)y1`DELZ%wsz*FKhq)|!eC>&(5l^1S#4cY~DS^YTG@yf*)7n3&RAO(aL= zq__Rj>YT4{FnfPnRG&J$88#atkpZ)zLvP}hOUMh!qi`kVA-+I~%<UBX1~Dy(+BUlH z*3WPmKEwvP!fjplMhxwQOGq&_rQwx&js0VZFdvjuU^rV&9#H#xR1i#83?6y_3+340 zHL2u+f(f#$ZF(U}k5tf&&!lI_2)X4SWY=`B7e##^gQ;s8eV6MYS4KSFD`%qwBznRd zM6_<a?g`H-cc1R5uOyia+F>xONSjrAEl(jc#>F}}xIZ5?E8Ee|gF++!#PDLZt<sjd zH$M=AwP$|bGvJGBjvhD$5+Z{zj8+WvJ1*7&X4S~U1A*uFU&nv7Q3#w$sM1RE?mpXb z3Vl_ctFK!o+eUzrSvx8M*y1?r^=SdDzS9Fk5K8h(|LcR)mj5z-{wiU#kuGgKaSb=r z)Ux|6zy=S~>2{X@?okUx3aV9|iQvAM0~ZHEA~F3aVO$Bry0f6esFdN`R)_T=Dp5M9 zD!f=zlzY$Q4gng*RLoPBd8<^DEqNs+=9wu@puCRzO>{MGNzk>mnw6^oCdt?X9e`he zz)+;cyaE+L*tv}_nw#wxxw&HxeBj^@dm(qm715s~9H97L&HdGd6Yd_KbSLpU1=J^S zy1n}|9$-q*&2omv3P*AG&C1=lpfklXeG_P6bHVoDop4afQM1|`FWqFgIewh%!UPO3 zw_IeMl{ukrmr1BMoLkq$h;vlm0oILgL(5XWM5V&RlR{kIo1XDK<0%T*a0^->5MK8^ zEHD&OT2iZUI~K@%4hVDIRb{E0Tby6cV8!z!T61M+6-qN>df_o#UsdH$Zkba{(J9ZY zO@P;w*3i8#NblQ~JB3SzHy<#yG}3SRNdRpr?pL54(2}@ROH#9{@*Tw%5}f3L0)Pz= zTxYyW0)qFW6%yKhbaYj(8U|wp>;5!g4fv2%S^l!+#WP?#A=vVrTso;j?ihPegY8Kw zIDQE{CX?Y77rt1qC;TgI(D-1SL2R~ZHtrIzgOg`3ATqD@Pim9=0xY9+r(%mu>wRci z{)K~iu(UGO7rs$9GPs<~qz~#(+J)x)t56+<3pC2^wuDQ|8y!^28ky#_uoGKNT26*G zqCiu;j1{_rcNz!R*msx?RSt+A`y4WX4p+o;mAO-Q4v|6XLvZ+{s-@Xgz!NKmgABTK zg^C_E2xA&pF*->~hOnT0K4faUT&Z2gL$V)EZSw>C1;)Z&E;EbBcA1`CE!slAZfRu} zp2n5|k@VpWlF~DwS}iyvq-;#zLWO(~B2V*x8hGMpKnHRQQuSJXVxFOJ9y7DK!IjUG zyhef`5f7N1(f<xA%Mo2tJiMSWx}Wm-2>87jqi14qleh7-JBwsEgpl;K3g6*qM0VdM zn~|qA?OJbu<)mCBm_&ru@ynoX>nD(MG?y({NsKJ5fObU1V_4`MS9C&VF%CVXYC*MW z30@{3bLFg@woTq(CtA2p;Q7-C3JXhnlGV4Pf!+QRd20r+mWa3;pib=y<8$R{8QhG$ z5p1jNpn`!Z`bKFm#8&;BG0s19oDD5YnIqO0&1kQw6#!e3(lp+cK_MQeh~7GSo}Hr& zd3D@~n<zR$TY!vqx&f6!uA*T}((qe2WHzlEl{nw;5jKC@I<)m&Ifrr42=aCoDWQJ= zIb>naPL>m}(3q!DuyDeOe1Go|SjJ0F!nFJ#U|qInN6#73XvOkDZn_4@?>a4PSU=1j zSnxd~DGjQe+)I9ES3g60t*Pek<fyP7CY*j#6ve_4&x9TLF+gjvs%Y|y-t<+Kns^4T zn%xA0TsYrO=xH+prH4}h{xV=6dWF*F3aJYF@?i7g3D7+=9NJX~x>4i>UJWE41~jd9 zM)q;jpq{=Vm4JdsO{=`%9urBij+s-8P_ozO?AiZe^ex>mi>%~?-0N&T?87Y)M3Vs@ zg7afXj6BrYfV*&K?jzt~bY3)>z%K?X{0c>*T&HrJ!lR79{zlza!&!LUAEgc9%ls0m zZ32tOx*oaLBPsf(0vp;p-#0cKjzuB|mKLfa(3WAX=Ols@L6W(Dk^Bky<wY%CtBH6Q zfF<V&qOkWsb%-VHjPFkK!u~ZevBAGQ6ILC@D=Fgj)b+~#3B#E=K^irgsj49G|KnYR zJ!M{{3Zmxq(pcF2S|0&pHiDAmulwa0<o?#h?b{c;PyOy->I=L3)^T`P3<2(HCT0dj zwWS644GGcq>g2))ofw@yHz~&-m)LX_p6axwf*)optmxb;pjg~m4=QlL->)m|l9#As zERmv#De__hwmkNy#%Si;rRWl}v*jyY=U}p@hsFhu7BpOgTJzC@5m=YEPR31^Wt%I_ z&5?up6=Q=tp-w`;-ZI&E<$ny%vGD@{RhvH>5e%;a`9Qdf4Z5OgGhK5CpqStV+TYo# zTS4Z8is7N{d@g_rPw*kF)*2V`dzboFuTE1N^xsqe=IfS3iw4yzpzLB@>y3bx8gK0r z>T&Nr6B>uS+HV9<VR5|mVo+iJnc3STWnrL8$G{YXQKL)awjH3)b`;-Ds)mIMz(A9U z3-#r7VdK)}B|M9#N&2#(G6&#@d%C5NE+X<BY~(nDP|VxxAU_r&qYAR)(b=hvHed{Y zxcPZVfy_HsTn+CBRyhc2$IH+hSL};evoZdiuuQ3zaBLZdGLNpqQ~4i?JqxuhS)Agu z^9_b}78cXXoyWyhg97TA%B|F=qdb_C_DOitw@v=!(3!z+IR*ymc<G?TsHn#FaDSe~ zIP*Ah$C<+|LNyubH}%mJ++brbeD9?T^zHzCSUo31?@T7w7VpdXGfo)Xg+_}Bl{$)V z909R!7nt}joDo<8*=#D_H{7qW-(_V!5ruw|{QmxwyR2(rtVQ$xc;<3&?vK72VjM%c zAfvC3UsJRI%*xlvao$1JHp03?IuT0>t&A!qDk;|vJGnHqO(K^|+qRrV+kqSu&?yv0 z_YubAQa)_*BwtWCZtwP{Sp#4{Y_4Bq_5;))?rnwi2AY@`8Tc;=xplJI%u9T>>LPtq z+2(*uG}Po|$`4%al7ZnD+`dJQ(cMhkXSM1gN*>LzmQU<Y2#(F}vvHDq@LV#$XdgE~ zF;+PVjSViIy{XZsgnT8Bcse}n<m+9;5L@VJo-ATRrR}Bov_?pAvnfUGggR)kWm&$< zj}8)x!x{?d9P3J(AkBsva%p{p_C5Em$5+V5YaYP-D%KCLKn>?3QIj<FrBdRSatOqc zP!&RnP}-OHQpMD#87t0d*F9B>Q*rm3YJ}oO4^I>p_?X6=%}^ssKPAGq$2<L*C``Fs z>X7R>)1jH5UStqr)26+;50oTW`CVihwGv@hI%PZ=nV<5wze;h9Qh=ce!ng4mkQk0w zjIRBSYKmF!iB0|L5HEkyIp6~#pVU7ybDv~K4w`%&!7r~C^2C#dZoxVqp>7a`CnLF$ z)<DYMN-XM4C=$zBw5(R_({joDK$?qx8bSK=LMba~vjGe>AOctCD;$<O0<zG2y*xX2 z*T{|=W}5(w@Qe5);96-pY%GIjh2PDryFO^C0ZPWu?FWELj#fwHz0OjGD$1QqNxCdT zDO{}EMUo-BuD`Wi0@MNrcyF{<q4&f`v_dFugW|A$P_s6dy!D_CitVmp&{DRmmG}rG z#O-3>5!R^yp~GgjMy80!gL9iOSxAG!0_tWmndviE5Tx(qN&(<Qt-f-knjg2h)Wu#D z2DGitbp<GA6JN2v9|$}`Cts=OK$~))bDynUZhEesGr;?BCd5JGlCKt3nMa~gpXcWd zarlmn4lx4PA0V-j=kOwK5G5+I=;~dn==eXO>7E9rqFNZyC*3$b17pZ(e$i$Q_Tc@j zhzkiIpsPR9K}l7WgH8I%Ed^aYfM@DavK>F94KN_7pLcgsaQF{-j6bX)E2Wf=@pt-; z)|mgql%TFW@)dRV2Q#h4BxV^Jy|ipj!KIWGr|G+WpHb2wg--82(9OczfLpO?rkvxp zMgfOrH{#`nx`ik{yw<TPd5KXsNQ4))Zeq($ms9646_+V$I}yB2yY(@_Yuvo3FfjJ7 ztFvy#CFd*3WOj6@E|N8s_=h9vDUa{7^Z>T`(j$W6+@~T3&n9oiRo?5vuJ9%?5J>Li zo6Ve%0!n!(MGS9UsPA>Jfslj<24!|)o9{6?MXLD_ljb-TR}lWW%71aX4gB@o>o7Yr zHiv6_{}EKgh@@><#$2k#>HZnrC6h^tatiyPXDj&%-LM~y!|8Q;@DbJFu95B|KSG;a zC&{Pbzd&3Qkh1gQj2=^cR7g1My(?Qaaj~t6kKBS-?5@;bN(_lJNExaI(FrtI^$kD_ zQ`mF!ou>@8`&bB@w?GyT(|?5!;0%aA$GGV}QV}PZ#bb_XuZtT(pzG&8&b0Fr8aq%& zUR2(%*r@8jscZtg$>nJ8YOfDSS_tC+jFSXZ{u6Zwy|cj1m(KOifw#-?DU*;7_Paqt zK9`C%jsJ5yr0-l2KW6KtsmWH9Z(vN-c#C8Q_1a<dKzcE#4>5qkp9o74w`T&fr`4md zXw^&sA{@QI!BKqmYlS*_4|zufq0N(1nk`YyC;qr({uDpS6Y~1moxUui3h(n^<+L+w zp!_sm&Axj*unt0sO~^CUK($wnIJ0lg1$5<<PpV(HRu6b%OQ1b#!P##7A%^SJW}oK4 zd~P(08m(oj;($`3XG+IP%_B2FcFxFx_ldgdX5`0uonP-hisKLx3u3zjyN<h(VqH*` zI)zAo<_2TvEO)TqU#WDY+%tgfy9Q#+zYLag#`PdiJm}2@v7D_fCNbU2u1V(*hMU6V znSra{lFEXLdi4_ia0Y*Y&3?^=RT(Gh-nyVvu$kj4+vNjLe{3oFjcfFJyPObC2)K0E z@+G`S{bSmFxWj5k%g)UQZzb;Q>xGt{akJq^m4Pd`&oMjOi3E~WQVmt!$l0C1HUpA3 zb}a71;OQMrB(=+W4&Z?yIu#)M{l8?8+wUFe=Oxk!8WiSUpoZJ>@nOvRJ&Mn{$1KL^ z$IfK1g{%_F9oOc@Hl$r}1UV`OGL^`y(q=!=znGd@)T*Sv<T=pKy&if`0cD9o8rXAx zq8;Ay=t{s}43_p@JbF;S@xje0N<kEm%H_qQd(a25e_e{PqgkJY#n{BFI(FtBAxh6q z*6B2kyVb^(=eM%-j?=Ex?xAx`3_!})aX$_6nTt6gypd}(xqD1XbX0##v+7$t!-OB4 z8X|j*6ZyQR+SG`z%tMixvTqSex8NQcK1J8MgHttT4Pdiw$w!qpalys${v+74VIuea z)6v%vcBD_ndZy(x`K92Zh9ruNj-m|Fa}iwvkWDIUfU(L_3nu>q>p@zlsLy0=tA=w> z+f-bP>7r$Ifk>%HH!<2jvD)_~>?9OGrX&}p;yq)tK0SGO)+qTA(E!0aft_qP0^#0D zn1vD<JUhgWpse52myTi^x*e3_U3weBJ#O&+UWT@F_l;FZQwm6SW}wOj9JKwVVZOs& zUafy1mphk{rRoi32`XvG?m7X<9P%7Sc32q)u?(h(#nXl_zC7jCb2Gq^@M%Yuvprq` zL>=0u0xf=$W5tBReN_l%nwQ@8krncDNwDD`Mx#FC3J06mzYu4UBj;mWeJ^CP#SB-= zVJUdavtS~KJ3tyYMQoo}&(0Ok5HKuooi;+*ahs6s%M#2@IiwhV$M+^YbopK|Y75;5 zW*7<Bz8X)&8!L&_gFbQx@7a*p6@mXhnlTE1onjoykiuzVeL~f0f=s|@027TXpVdgV zzEwnQGC^~c4ZE`2>hE@{IJji3Pl{n1KRu*{tI+yKghke|MgODbTo5o83uqM{D|F5s z;g`+(vxzt}(Mt&?$rYMU?=X+|f7rnt+My*ve{AR&%=ozu(MS_rb!gCYxy$h_Nd%32 zG6d!z8{T^}9cKYqL8Cr_2dqY~X`7;ji#aPvKjp<kxgt=aOJ;RWG&>WQ+rHyVKF0_Y zIZAMlRA%&4Z{r190!tyYc&wYmju?ga_Wbm6Nhf0qtPbd8a|j<@f-@SSkQW;ez0=IX z-9{*2f~TCl;8!>bh7OK`hA)oQvm?#6Yiz#mRinj_<QG)j%Ww;!NT4E+a*~e`JrxMC z(UdM+=ArVO+Wy`-?BIp`7*fOBfc-R<K4DKi*LJq8x?@B2y2w&RtMsAdkgE~>C7a#K zeXi21T8na4q~Q5+^eUed2=?-Km~tSKaB^#4!8MsDwnPx^8D72O=bW4C^+i)M&UHaE z{`bMrulIkF!EHo$gcO3zE*)*mj=2(pXp%zfy!J{s1+0xL6fh>F$-t^b!XH*2l;U=Z zFR%xEu}k#1vjVESA#2bif@*-<sns-U&K1@?1n0BCi-<b=0dln{nM+DX(cV|w%5iFB zU9E)6`tX$NH!)0*SBF=#R{1S7!P)8@4x3n@_&`7#a!$4SR{nV6s&O+Y0cq>S2#oDN zJA45G8m5DIh$k{+oipax6a|Eq)y^%Te6D&)W3G{CwW;Pfhd&9tYz5agsg-;f{=4J` z#Wyr2@jGFH4~S!r!ljn=A|tb9gQb;1-a_EF%OdODQuf;B#EP*Quhz|k-NnCcq1Yd) zg@>?K`>4R+j^PaN@=F-wuahrqN$0&rs#9h>7=iMkCy5S(f3GI1fQ|5KI((iJYi7W& z<hx6=KxbbL`$FZ-L9naAmoi4x)BYa^O$?ZS`C;ix_&|eoS#f})xmJDGPLP`Sxt4Hu z;Cxk1CY6J8k`2kH*osh&7M6nUL}ucfD6P&&Yt=b3(8hhST>choJbYGF_3x(CUPIOh zpo*+0?m^iTb+dZj*<oPu259~_SmxixV1xr1Ff$#i&7~*izbO!1ds@(Zag*$p8CGQv zFQL7)lMG<?3oW=4=4M&iEk&8E-5dRH5l!3At_2%&EG3G0K5Ov3FV|J!e_Gyjx#<Cn z-QfbxWl1ZI26-oo1)xvz#g8py9-?h^yl3BmqF4Bj{n-2z_mwyxre`xjVg`(BMNk}T z9YPwhb$pJBIt<bH(Fm)Z><Z85zE!Oa)m@Ew?rNI$?EyaBKRKs#3dt4#<+KBGu^|5~ zaI#dpe(H?|ZIe<IhU`%KJ~|ddR7nVpa^$(dJcR~&cA*_!@oJT;7F4rusXLGTpq)C| zWRSt3UKd+<>3|z0u8uX%KLpXbBP(SJ7|X+t=2BnX3(`#P9rMvH?OCO`a!UKAxe7xh zFi!up$JYe%e71pKttcVDoX{%sPo|9WuhddSM%i;3PVHffmU=q4Xt0ah?*KJMZWDz+ zy}+!E#8sf}h0VoM@*&ut{Uk#iU_K9R;?~H=KkT+#-SVmD_q?*GPy*h19*;#arTp7> zq}Q5FDne0=PjH0O#QrE+b-`zxPzYR<TpDYwsOG@M(qNeoA^7Y7zWfJY*rRe{on)(? z0aaT0-DB;3TG-sczpLX1WaNeB+TST5c&?E!Jy$1?c@XXkb>m~zRW$#{)(%HI{|3S7 z#FaFcSn(QmUvJtp`!?$l7)}VSa-So)?m)%@ni%ap-EguY7+b2YuDejU|IU`k$tw!^ zk&rl<<VF9T_2#y;2>sl(PMZ6PC~{GbCJV_)=%dVS06J-`;2yriuK|aK(x`m8kA$fL zZl~+w>0+#X%5W!g=ZsCqn|iAJ*!v)`Mm5s_07+}W5~YL$_B&vNMxBU9!TWZqi)06d zAU63$*8QFJKAFE%PJl)leCP-$;$50&Q^Anl#E}|<3q2KGi3SbkWtfQlRdCfz{;rxL zP2|t&I?-EQ9ZKvz&S7G$#CM^|{#?j2FJY}`g$B$!a}mYsvRF`iuri@>sJHTe%x*L- zMyzlXIy_3RrNc`mrnUd;W$C1TFb8Fo^56|*%&L!_eTw5l&@G0#_H>S$v9!)18ZQa# z8yb8OSn^Q1;;<d|Q*wA?@9e)3nA@tR@FK1~`AYCq$uoqupT!%+`x-`Ksr7p+ZKqkd zI(iVUn~l#ys-T@fZ=dm?&26$MA*}DKI?4NG1n!j(6=tiXTt~mGeRY8~biwO~e9GSe zy4#*H80UtqMdp<}0?c{Z>jqPo_kg#$vbSxjQ%Jy~^-j>xP9=i21whcuUZ82g{0g@K z1zAv-I&H~gVdY1vZc6eA|42V-&(S)fsf=RWcN5_Q94w>>pM;Q|W+x~B85I(h6&OJm zWtWQyr~UyZ6#46PzH*7esgMIN2rqpB!t{n5jfKf<k|Dr7BlKLj54{AD^N*f8y4D!G znYJ^}vb?WvY8jok;FA`%bvAn2EE=^?RGudEapEy)tKDi=t3`%qfE6SDSO6(N*1rX) z<$+RhPc`&Sd${O!$+D%UvD|u2&hAKFQ}@)jl%;mfWx;e;$}{jBc8Sn+1b_+`M2Sl{ z?K72DoQ_foEc6cCb=u2kEBaO*U~jtSJf0-f*QAK*|BIbd#3QiiNZ@uq3C%(N)Ol!7 zi+H$_dh)La`=7(gyt?B=JL?lV1Hp;Fxg}yA%nFIHa-r;_#1R2JJjXKebO7DFi-k#= zm?WZ^|Mf<@gTIBWNss+3L!5?9LEd;7d?ey7rl&F`u)1(*H-VH*7ETwA=@G5XVta)l z7AEmCIzuP}R1r9hd=B|=7~hbxq+!#Mg|RD0RK*WP;jwzd5unwdC=o$G+gl!^*vs>J zdko6&>&HD&VRzD+d83|vzdI>VWM?nkxGB!2M?`EeE$$)}BEOvplyYfO`qW5aBd^!k zq9hcI`7*_`*A53UypfVgf#-C&@+Ie_kamEHSH0Ynw(H=YxMPa^e*FFVho%R=`@*na zZVt1wDZY;~hPr47cEb}8@K40c+kOGMtEZ!%vtGljS5jF)33i(uv5Kg+;NTpS76n*F zOP&LuAJU0_KzWFAphuueFIhoZf-_j|zNabg*}?Rdb|~-qJ~p6o+n8X70&sZ=`&LB6 z4Q8g*GqRIopH_rEu?y>C*8HYGcfRzX#1&=?hM+`n7wQ;DuDDwZ{i`}o<*OxJ8#<@i z%j#qbcKiuIQ-!j{p7sU;$EjP!93a88;UGAm@uz%B&)$XGNaCMKm~^XJja1WlkbSE+ z{@hLRmly51Glm5AC@bc<V+7#jTw$R*4{j`BTDIz(%2O>O&A0s!{Pw)@;&GQwQZvS% z$TbKf^9V>E{dc(`HLR83iy|1Gi5Ya_8ikF*uq%`Fpi{Vd)xsLs?vq7A?sX~4`<Z+E z>kk!5DeoOZrS_40b&={j8x};Z2)yr)yD=zkeE^OyTPP?99kvi#c%v?g2fxU0-eOsU zr|G+UQte(S<ajMc<d@OEwt9B>(5m3Sj!nXC%IT%FCAW)Vxq?3*w~KP#M&r}}$1t6{ z=h(oV2Av1ta~=|Q^CM^OW^Rtc%3yFnTR3QDl7uvN;tcXm|HlHk<}AMz8~Z)LLJ*gT z_>RN`7-eqfCY=l^MFteZE`dY*5E~$Xr&2lg6Aw1M@|WJILH-)Z%~m~w;}j4jp;B*p zOD92IGDE9gMBfFyKyqnTY}MVrW_0<TDfDp(AJ#&0W`}O;>&MimE>@5otr~+>)70Au zYe6IeS((yRA4rmL?-P7=*=2hS^9I-dJVs<}NXncD<+_`+SuhPo-uzaS=~KLSMKYm0 z6&|ji65`)kM;k0;`r(t~64`Oj>mYHFiYTS<54EOzJOu-|v~rv4wEmD9o(lyZA4_0K zU8OZueDYnIA37@|^c3amO0r(ytmFt?9gsM5iNnu1urps+%wH_8G4W~aP4dVBHMmi| zq=UsNP>x4S8f4n@^w&umQwFTXHBCHt?EOPgwz0$TKFvZrYISpdy{ohnM)C#Uh%hbU z^Kt47O4#ijm)nk?k)+hj)JQd6vd15f3WXijFVgF%@Y<v}Q?o3_u$|oa8J^-~8$Lx< zI~nHuKc#B7yr_BXfxnz-40V+;VPl+&5?|96sCz|$%LP=)nJ{Y}!p?dG!}EO#RBdAj zXisJG(iTlvM~JKKyZOKf-hljDYQ_3oXD%5Hm&l#VU|6xHSH4<Lp^x&&ICbPbc%2RR zVkjpvyu-c}m$6TjYY^OAZ7cU2$D|VBj0$72e*JT7YW^~{XC}@V)Jx$2S9z#S8*2_b z*}=Rf+-oEWDkoq-FoD>b!TUFQ4fW3NHOvF@8yEf6BqUd<XV{V;mj#}nKQ!QC1epFd ziP!{l#p4s(U$9hMLQKNme3a5VW~uh}K?y{0T!PyRkNagvWBOU;m9s^J{@;078>=6n z&*~>JGl%72$SrUA+c5U0*#bQ|UYM7uD%~`sfl&P~E!;3!^-ipp@zQ>^;UpC@1cG~9 zdmSL2(n?F~S=rU!{mXX@=B!8VTH?$TNny-`cpM{0_i066hK`L!r&+_ZZ^o5OI{Wy$ z%p4@N8r!AiZDve7@{1YTKIcVbYzY`Sv^~fv$4?^u$XHd>NYVAe>GbUy@-KtDe*|ye z8e~+_X#z=62Cim~1!0?VrhJ$D9FbF(bBlJw->^ylBvp&7{O1jKnXw*#8t{c_bTBzo z-f4<>%G_QcNCs3+cDEKavC6h<AMOgg;KGGPx?I|^0+P4FO);4Q#iFNa`}4!2HG?V( zJt!@B?dO!^75IXQV<a%%X)<?T&|bGb$JPeERW<Bm$=U;Aan&hpNL=E>!{xNGfB7Gy z{<N!zDatQptb}-L?uMM5SFmEk99*O-pF7}L3EYT}=ecjl)o;DfG*Wo-9yeW`qAwdX zgN*QB3J2)hcW1Hwpb~K-JDE&NZw+CF>~2Z0ug3Obx(Yqm1CA-@`4K8d=B#e6iemWA z3)M~yL28Qf4aUIk#hqaMQGmkUV2JC{IQ&8@*8PtyA*=`t$hO9>hIWb87i&$$=1{iS zmT2XsJ#pQvTP1Q!&-@?^i;YE&66L>y^@+>WiwUpQWkORC8-sz`0?O_;oOl-*Ew_e- zRZuxO#qod9B_w+QAtv*=IaBUp+}f&oK&L${n3TKoRInU~t%uDz>D6VR-nu}?!}}{R zzRB)m2nYe|g3*IYNFbEsZzQnc7-ZRyDiVX3WT7!RU=Jbbjr{jMWie+-?dOJali`P* zt|f9ELVV>Wwv1|sp7n|qKmTL=X<kP+aFDfGUY#Bi@5X7NFNNk>Y$Q_w`Efs4{Z#<| zMX=si@0C%Wt9%?yo{X$UoOT5WpPWQpr1yl^Tm_ne!;G@#mY|)us8q-pPs(+eRWu0J z1O@_!FF;z+NlryuRbjE)Rtr9=$+9Xbc$R37ENbJRi2n;0^YN8o3&V;GZfEAKCyS4> zSl_khq1Pj}!iw;pNe0&kE$VTNl)84~?3pZ~eIr{G*}*83bw<fr47;icVVg$2B+>t& ztney!t0H@miM)e}kEYC9Px+!&?V2TT_Xv8r+}s7P=}WAB=(#7Qy5=8V-J5GG#w@3< zI%*_EM|dG<1eyTe62bh&*;u=nm9Gsk)s7o3V~yw(g5>+56JEE2o70h<A-1ZwLeau@ zmz~M%`k=Q3vN4$SG5%>7LJ!<k8{h~InXH?q@dZ7w6BGYsPKMt5scZ(ofbf-aqQ)t% zilKyugurKCDN8KL0PUK`M@(2RRF(SOy@x$TJK}BpSPvp5n&#ON>^lZ#rz}xgK0v+* zOy}Vc6m!6yr5+Y=*sFSydYs=jWs+UBQTqTTz$q%Xh^Pj9!NB|keOW*R;ql-h(#yLv z_yaEuHhgwIBnk7>!wBJJfB3Mk^A!B`=R?%hsj%gwmST95_vSBF%Se<JD2v#q!)=;w z-*iVMV0Zl<GRA^xm1FTUG?s<sbBQ)f@W7hV3XEP~JIDTsj3!4yALWUB>t;y;q{s(; zm&Aj^OY|W;YL`~F*k-X39FP5Iif&u#>k)Q+bf|@)NS~72llBvks|u`-tZ}#U$<QE$ z#U;{6Fb{T9R5&BQRYS3}>aETC%KK$gjj$j{bh&2o&gVx!u3ZfO%a9$YtT*YOfgt{6 z8prESIkC6J%uMh^Q_xYGj%0AG5bqpk_waz38qcBJ<CH${sw4suHpyjlNU)|p*VeTR zdS3c{dJ4&&J;=&uQ{e1{z7#HC>QkxQuhp-jSc29v?siU`^|2vcBC5o3qdv2#n=j7s zqg?qNvah8*wMr_g6(7ZdV$ldH0!hnV(v3@Z54&uXesbF>*iB)-D~`L2hzDt33g}HI zw2D6&4K9sKLn0r~QH}kROo-M$Q-^1$ahR#Xz*W)ttEGzIHj1g`DrhhbJi?M{ePO01 zDyG$*MC5!K4hCMAktdaB%3H*G$pOT}SMWCaVqxtT*fO-RN3!zSfA~Zmbt`6lXHHwA z;$XA~*$i`@ZE;rNo{um@nYL2ttNH=gjz<+;b*DeY2Uv$(-b<{O*+xH^BQ;NNA}f%! zz$GEYf<jg~PocDpG&q)TUht7m5eSk4tH8H{W}c=m-tkpU@BS2W)IT!8=8ECFHUH+u zCykdyNyN{pU%rgEYFjci{QUOSCsp+x#Y1Wk@A_zO)e#nW9S??!5?bR0aqB+hf+~94 z{+M^fI98uW=IU<CCBz;Oy*gV57n1~)`7Dy&^^M!JJu&~A(y7#w2uI4l$UnJ-8C7*E zPhac+@=Hy%U?_~n^8z2k+ZZU}z$#=9z44wez%a;d3WwX`yNtuVmQ{Kt3cZ{0F~wy9 z%ImZ+s?_uy74iyLrWgcLM-xTkXlIMjafFxS7_wHR#SI(&rrKO?NMUTlSQ8h1Rq0Rb z`0a}TaIq7d%P4=@)(69hC#m3SOR+U*=h8iCVG6~zjOwg)0uzppm|3t<E@r=)?C12d zt0CFQBI&m6caoF|jAp372v7a{qO|w+OyRK0`&5eBQ}%%Ois_RWA=fwS+W@LGyzF(0 zoi>HWhG!v@)rioL9tU3P?^Ktr97Euyl>BbRU<uVj`MJ32GdySH$}g6dQLi^n?tVI2 zVG~l$ouX3g?Y7or=@#v7c|j}Axr&Pog@JBCj89FhsGy#Zz52?<Wc#-cOaSbi04nDs zH9UD;eLC9Bm*~@h^lvsBO0qWUp<QV<XiUO<wm33MqHz&i`3XAO7_!i_eG7ZRwVz{c zg!4(Vf~?@NuS_BC#_@}x!_6q@p0o`AMRV~PRtTw6U;G9e7a{~4?X{sO2uv9IjXP_o zU9=;RdqC(FA-1vtMNZieju2Y^Mcn|5lu0&PvD$|Q{0zwLLL<)^99l(Fd1)nECbJ@B zJ)RbVH6(R?-NpMp#?36NmTMczR3`@nxlGBs0{Puw!GOO#R&epqWRQC68=Ub1&vlfP zmZG+?Wd74NU^N|@X*TN}m+K>glg!Dc16_YxX0(G#Xnyh4<9(2|^eLKOO`{rMc!U?) z<JG6C9DUFD3OO}(m_hZ^(p`A!rH%m3A)l|+vjkV-#6xt)*EKn>f0(~_jEATZOzR^+ zK_eCDcMcC<wDG$wC)i=H4yV!nMkegVw{$Er=){8xsTDToUMNr`snAq@w)-NsNc`T= zqGy6x^67lPCdiBUegCj4zT5rxgHsAu?0|@}e#p)h_D`+0<2<-tvf2~O@ReofMAkYW z3N@DCG<R2&D4q*|DoB|rL*h6Cw)Xhr{ByVg(NbC@FedHU)ww^k^o1h`OgvleuB4Z1 zUj^et1NgPOQHd|BbZ!VCGXm&=^_onSD{pwpi@lO!xDPr32kXOz8?&I3D&w}rJmqie z1N<Mc5hJBSzh6bCM96umUT}*hpLOIhk5yBM+D^`XFwr+v+w<cWkCLo0UdsoCQ1Xb? z;{-6{tr<w83`bAEF2EwPRlm5|+*Dfp?!e}C0GJI!Wt{H5CPrfia#nWGBWodb<vC0S zUvN~xNRD1#Aha+tz(K~@ro#?blWe1XuPCZEE%uS3yiW)ZW81#B5D8{kCtty*z4%ZD zB>qNg*jpCYNfgr@g7&QvrOp3M&t6ocqX{}<e}hq!I(s3Z)UtD$-DCjNP$@^}KIP)0 zaQs;-P~j$>NBp!GXnYV;9<j)i=e9atXqe#bpklBH8q%WEogE@$;5&8h)HE7Fc;GxY z*hcbpqt{m<v>K(Yl6YhafPj((^)=}e`{)mdisX^_+l-{2;lk^O(?T1t%z$mzLgNF) zcr}mU<Qe=h_jCaN6R3~|iV@AlAH;7hBv<u9=*e?d)?iuhkhfqP?QlzZQ*+DLIFhSO z!%3|!ctu6%q28;kwandcYxa_!2IFaLXzElndsOlBPPLSsa=4s|w8lyn1H7anQH(>- z&g73eAa0Sbk-yBYaiHZmF1rp*r^;8(sWP{f_>^(2T!d|yv0-ej#9$y={Dv%v>LN&9 zawElAzc^dqJ!;??oGj)`Sf66|f@h)Dv6lPyk}}OezhUuBfts)jmvU<Fi6T|8iqf{7 zNCdLyy&O=2PKMzE5N|~f#S<1V4}BLQWid@YQH*f|4onR^8Z3OC`Z07Z{|wymi}X}w zUn9G%x(5ImEg;pNSo^U<S-AaN@kC=#tFBXj@=+~03v`9<+l?lu-+zzoG%XnrQnhK+ zX^KgJEe1`H>vcl4=|fl0LD)>ZDpHisf5RxrhRB&^9oXF=6^Das1LmA}n4RcxPFG;; z;9FGmdM(tV^8WCX3o3>bxe6Cjzo}e3lDxg2@<2aO?)f5is%qbPsojr8p+OPiXaK$U zS(di9z`wFXp8NmH?V$2Vk``OsC9Yy!Feo3mN(AVGBy^kfhGG{8h<YiQm&GOijrH9r zEeGA%FVGt$_zNQkg5&hoLgY@0EJ?4@pDa}0^OWYk`<MuSE9FuEQp*8`(jLWQ*5Ln- z-dk|u-|YgWKb5J!xb=qu8M~HA>Et{rmRB+=cEcaBX;}b`L3`w{Ql{%;(V~F?JoHd= z5&+uKYw`dt9oMPa?G%^pri@!{IR0e2KvcUd{n>N<Kps*QAEl3QmOHP>aQ0>gy;V~t zpGNqHyZ>)u>(dw_Q2>uoVt_P{J=gawWea5>`xgnRN@1H8KY`#24-25p<2)+61CmjN z|MmKJmUt90yi}e|_cZ<y%_B8){_C+ifYhjQOVV>VKmuDlF|Yj;l}epS_U~MRGFqbP z`35z51Bt{J+ZBsO0<&{gu_sXgoD-K5jQd)=>=mA@1Op)6a0*glh&}@Q#5VnU2@?#% z_b~?}egU!<{fn$9*YH72R*FMkc~$0UvT}eblfnp!3LF2ZUS{5SUe$_G&xv<c-`k(i zf%M;S&URKhK$#EXB;bC5w;o_i!{97J`--p_CYG|W)5r;@y2!n5uyO+>lM^kfcUfx8 zo~6SG;|G|2s}aqG#p9BlfCUpjg1|U=mEH=`V0M90VWuTU4Z!HGM>QXbtpBuc`?hEd zMng~LwiCY-zZGX2K%V4#yq}_OUOPFV?-f}lq<>2I`|!*mxBcd87<M@p#(+_V)(25# zIUEcGJ5mLC(5A{=%>^6(L+tb}2#EEhX<sgtM|sD96T8?6E-#Pj$au?ZK-hi-M9(LV z37b@Jke%&)@&Q6<D~Cr^_<&=3ApujCua*bC3CLE+fx9$~`+2b^IHT+olFgZ5G_(ZL z6+8{F1^=w?RI(Aljm0xYZPidRkE)0QR*4)t_r2un#ZANe-m~*-cICqOuA#Ku*K$Nw zjS4ocMhK5fHht@jlcij#n+?YzK^@wrn&WuY)M!%iClvb}j-G-EOZrr5_x(iM@o4}R z7;>NbejebpL$ILe#0#ZQm$ajjyc%XlpLF#s2xr8!eaNJIRBG_>T1yuf62?*&c01JL z&C?>lzE}pQQmTkP$_x;7Wju{|5bXm<j=XAfX3Y2YMAQhmI~l%WNoR)3iBQ7MxhOB* ztXO4y`CVp50t#wgH{6cVWkhhjP-<35$AYA(;N8omH;9{qMQxfg5y95%M`jw%)r+KY zvDwbOlwJWER|7lxKD~8l>UdA^zAUC1p@Iw=fA>Fyj{~&gnzysorhAFz;{qYR%5n)D zc&>~Cnr|CF<7`t4d8S)rmTf2lr^J;y5e5Vy$RC<W<h#u$eGcg9%@Tr@9yE+vy5sre zM+;W|CH!2d?J`&guXS!xADjaU5!4AurTk*X1NHJy&Ey7Hirpfc7i4Pt`y&g}*g&F` zA^1G0hHh&)*vLADG#ip2$YoQznL@3dgQ|0u)47Qz-$XwzX==C4VyJU3ATA3@{2~t| zO8Y2FtyL4@86!6XU#ZOYi1j)Z7h~~PoD#nRg1apB$bNm{=&d}=KenL_{b}EO#-lWa z%J(AJnfIg6T``A2W#ZFmNCBqEo54>e8vm{5_(JFjwzm*8C^7=WS1E1#PtoOcR(z{h zhq7hb$^gFi6ta(sKN>DA(^!3)nl3JL5rPa`d?-vE<PU%sL{*E*FkzAb%f}`jIK#-i z$G!0g17U)(xzTC&*3c3f_eD$F*CX4!H@vk8LW0*&u6!QpkxYAZx_lDq7I;fEwwj;F zrjX?0gK4+VE|K_w9Nvr!NA2g^(_?Z5C2T^m{y{|E(7lNnR4Lq{(?2EvQcn^4L{y+< zr=76HFs7|_C%{>Es>RRbm4kuXlR7p~4y`8>FMBp%@mdz<b!}MI+GAn_g{fxem&?M5 zoakoe206}Xg=V?3WxAVw^`Y$oTYYmGfMVG(eC~MGaoVhi@JVfoWIePNK*?hKE5btc z^T(<KMaSi;Dj)BZ0oag1LJ$QwR!(%`g>^EMEkbDcg|9n^C2vjyN4sO?;{zxbn76|} zDKP@j`JVZdxhAf(4Yz7mWx(i@VD_B^y4@6HeYN>HhpaAxNxQJKKLcA2fkaa?Ui$fI zt57f_-eM`gxAP{qh)AVhIGfmn{WGZWgAojXw+Lgn!L?J76qWxrG{;B);R)Eg!t4X& zqh#z<et%(5GuaXxWxA;}E{WqWY9x^p-+=#NHTTmgGyf{fuXwWb=()Nvy6qpfH$P3} zBYpMLhLf%=*OIy3bB6^&QSq6D5w=%uDi$Hkqw`OkvYLub#T-)hFXrTXa;u`#+pm=) ztX33b8rLI5X%pG^Y0_%>!kUV94OsRNne+#3Qt%RBU!La<rToNjL)<AO6qq-C(X2Yk ziM{IiWG9=OpMep4=toc|5YWzJD#40m%-50*BE-2zj0K@6P0N{V=MC;8+!2ceT;Fv` z1Kd3F-s`@0q6n}Mx0sp2hH`@(?nI+|)L4T0{SM9s8(aTG)We3#XwKJ6%P}5y$Ro{| z-N#oFXx63*4zCk--lv+RrLCY_35E9ox${c(e+v7(DG9lK_k=#D+=;4QDHfO6O!sW9 zd#kO|2>xLy`rPEfLbMg<eW*tQ{l9WUHbOEDpR18ejT0#~PixpGTmDPX-zc|aNmsVi zJ$+i7h-w7g(7Z6%Mx3GxyB0D6?@{dUR94?|W%n0m2Dz&Bv5yVPDhqHnEzO=jEMMrb z=OZy35!NmWT5?PsW#?O4ysH6#qVa}Nyc1*AlLb4$sJWfdUJj9txT#zzqx2=~)hs;n zLCd%j^Z^$@>C($h)1|vn5`7Paj-iq`7%dt<f##NoTW@Xpp)w7vS5~A&-7;m%>C{^Z zNjtU<v~#JiZ5M${ceH0;m*7Z627`Z|QMBvON&vPvpZ$nnT%4EQ_i&e9-b}p-Q>tE* zvFK=>Ik%}1WY=X%`WpfA&>Iu2M*?7J?svbWxAnV%{BK5*IE<9S`~V*zV-Iyou@htR z`;LX`r=C^@dnv-d)7lG>bKh>s2Jr!yV{PN*Q=ufQhXJb%$jCCa1!F)5H*EVko3D;Q z)7oN!ROw#(!>Qv6Vx}NyxZQ0yc9}DSKvlANFv{H(v3b;$mgtrnwFt(4IT67(<=T$O zg!2|A#$B(hV-J}^nUEVX?w>RMmk%;4w2Hr@)PEq}GCUE@JeC!Ee(pmSDywE{LP|DV zuf&@F-(fCjCO<M9vM|9;`XE!Cp`sq1CQ%qxOTpjM=<(ye1xlq!9KF0Ns`&OK>_YV% zLX|?nKc>OW7YfM}csY*Nxexp$wQC-6egwOXXwKo$iqq1}@sskJs6U7ZYDjWKqCgDx zXDQN21fdT>5|Sn?-<aU4b8mvK_3A%h`BSYEc3IAlO54Hqn|!)!^{^h8Y8yPhpylSb z%{(fw<>)*|5_<eE*@$8wPdA5Zt)RmDjpZeCXpXOMRpeS#&KQUVXC$YjsD?E7*dI8S zz9Ev%j~=&cMd6Q|x~`KT@GjqX_^4eG#}oPxFXd*O{l%Zo?*0Hv?2Q8zS@anSZpurV z>QVlE3LGJibTk3F=+Mnh2X4&5#U<+O&(YS|b}ft!fj2HO`R*&a7@1n`Ds;=+Kkbk= zRqiMr9<UPrzob(mWir`D`gf3~d?Auii2PV*`qXi<v3+u+#^jDbD;5k+s5*DoK+CLh zT~kvN+->zK0_X1fpU+LJqc6(Yx$KM7PN0krvJ`;wjS7%StykZeCll-ftT2F#AmKND zE3S`K5R8}zfZuhEKj5Tjrt%`T9MFqsbjmwn|E39uyq28tqn?iChIJd8H|*C7AG3^d zZs99YEc3DPB-gu^OI`F#gfPk8k^tmvkT;NG!C|!3_c-ZBOpV<4e}v|c_|5eT*QK<5 zc`GX3pT-);`>54jrxwbV+dcfg&{LLcQXN)bR1_eR$qo@nPa(v{jTu`~;b!NT_dl^7 z_<xH*`6Oq{=wy}Fc%%I%eCKKm$JIt(*PqKi)WnW#xiGAN`8<n1hcE#1mvxB_+77Gm zB>GAxR-<zs<!QQ(U}ux>a&1=&z6bMwjVF*S{<(hYCwLN?1t7dwVRX7Ynr_Ab)aBp^ z<qlRbUXYS!hv!p0BQ)@0kk3#o#Czqm7;<RKh-XuYUDB2%uUkOSS}{xsR>1^2_G9aL z#Xp=kBsKH65~MCOpLaeQYX9Z(TwIStuiY<%pw3=vLZg&`M22CF?EvWq#rH-yd&1I< zZ*s(liXMT&i%od2Y@xGT8@M2E*=azX5SWYYUGH`q*RGWR7BR|%sajyef7g+mMfe92 zdwnBQ79ntG?CeIXc)FNZ5+Ldw8ePRQ{N6eGS(=_WSjnz$A^rW&)w`acZ$#$A&#ByR z!Y6xNX?K$z895B`9e(b9=m`UApM*7a^oRm2yICTLP03@n!*Pz<R_B#Wmf*PxisEtx z;m(+BL>7oG7{Ix&z7hS7T2m$nZ~HxRcyqVvN`_CWwnbYh{d*EQ)8elmw6c4|Py31W zopARl?-GHiA8t5)K)76e`2~Znqm9Oy?iY(wCw*S*glb_xAYA38gv$eZ&g295`B=cX zUXq;%DVi4L%k<{&g9yXs4RJwaXu<$V<@!B2WoErnvkdWX0$>CgW4C2%UUzQ{mH2+e z2ifY9T02+HCkW2V#4W*QB7l>W^-a1(3Pcj8oI;D{Ro0IgY1WnX?CX+4(a^jA{C`T} zMi^?vE9S}9FiK-s;@UI0+wLN@G>E`+Iy^$~E!iZKT6DG#$oid{m748$DN|H&b|Ac~ zRl<m5XkF28SDYvaCXSk{9XY6fF!!`Zfgz*%X~g`8g?n5B&e8AXn-DI@cr^s~6#4<I zfbt5NT9MKD-)35_z3O$ZRB7*_w*#b~BwhJ}ws<#TaYUk*Khu|3hiSXKe#~E*69iNw z*dKe#q+ZJ@tD4qKNoN>=t7F=*$-P8B&O7S>t7w7*;@Jz~5rnUMkuxa-=%Nv#O7D8V za!hhNW7CeXJQ0y`G}eSv6K7{VcKbQfs}B#h?<=J+GB!dw78MGoVBTyG`KIt#4u<-u zNG?uqUMuh$i=UOTsys+<oD&_#{z=|Qe$#c3o@ml*vX-5pD{9aD$N`R=AwOL`B|yqj zg?@)ga=8bnyvOETT5OG|@NIuV15T|$yc<4Uh!s3lK)Ph-v}BBG&|T&AN4g18tZPE_ zz!##MC>7e8TGFn<;7iK+9=xS!k`ZY6U~*vVDpD2(IX%RUV|3Y04wm>Fu+G)Y7<B8L z60P(ahn)c-9~ZSAQT8~rG!Vtl4g7|M+O7L3P`*dxbM(bIRGU>pB|3V$gBT0tF|Ay$ z*A^7y;V-6k#Cjvx|MO<X&s{L|NV;~cxJ}e_>ZvCK=1;BbM0z|Pma<`I$7JKj^_G5n zxf2h;t<ix$0~A+2PtOCPNl>z7ooR&T7*Sc$QYuGqWP~C@==Fl{9icD7Y!dJuS+MwU z5N<LWIlV-~8MJZnyWC$&Jk}+MgQOgoCFSvFNoeHYKWEW6wW8#$)1h}ww|d?WpSu5& zrYs&9VUZjuvu==N1`DckG-b=j+8qVDO@<QD*>WHQU;f_)ZXs?V>-ZAITjR7&@F;)c zL;h0?P~o#;wn}&K5_<xjT74bu{N@oCz0}gRsU&#N;TsEK>!7$bg#*=-EUQM|FYH8F zoU6by4;YZ81n=4?Fwov;sYRGo9ln_9X)>!>2@{%z%0n3Tv5BJUE7{-s#~NtWYv3vq zpx&f>QU6Bqj(>M77vkW80?vcK&RNC8&mdZ?6wW<I2lhCUv!=x>niW6J>PpEWzw!V8 z6a7>4_8cAH&}eH!8V!D6uqCA<T>MHI0O{6FSZpsHbP8qu>{S3Dh{dG}E)4;-)+_Wa zTLJk}kK^05Ht8WSD}FUrca!Mx3qy@)G6}37w#VX9S*48PCTZlfQr$^ods5bwa5+!< zYaTCG+9?cJ(bVi3jX@t7hth&<FDkku!bxpu#3KThFK25t)3dOvr0fTXYKTa1pfId7 zQ;*mJy%?MFW8OqSNW<6ChZ{!f8zt=lCdTJiQqfP5G+X;ya%N$lxe6=x8(=2(#X10N zn$HQ+nZ@#_Qmifw?^9zGsBL#?S2`R49~ESvT8RoD%VSs9J22b)>l-zJ)mopAa0Mz+ zc(#~#Qb|n?v<ZHLW*pZRcGYrY(drso(SK~`HAgLp$JHPu1}HPEQz;TY(}waX#zs}} z3Ucf(dA7MO_>y1$BLt8YQw2r851X{iqA<KT5XM4aMG^fH3zkcNV%G(C)(g1y;K5^R zxs&V!?C`JNypG6cSAR>kEW(rJ9EOdApl7)LQIZ%}X`kjwm$0lNh#7d+7r|FT8pN75 zVgnmB)v{F6^ANHot?{Yjmb+|ny;J+`4oHO~gD<FeqaNth2puM75xC`-Ib=N!HLLi} zLdP}FP;jFwbQSt~$i<<WrnIz2>yt+<T74Kcnz(0ScF_oOr?3Aq(!;z80vBMrzdR1N zw+a6q*lpW!sgBR63R_A=o#p;z&M`MJQ5ff35egiAIa7B9<|fdTJe=~fb*w|sYqx}T zyGR_^8BGxF9C8b4;7_?17R98lZ{GrhpEeGsQ6Z#t03iz7?mi)6Xt@VZ`NS#j@%ion zT)!-J4jkN;9UICH1RpJE%>M(VuGs<@mO8~mP#&4xqW|+Kxp3F2$Bsc9xP{n)lC`$C zB#u=IX)tpX38U{0MdK*mur+$`eM`1wziivovZg{~=#8>IU<QMLCry8QYsFoQNlrK2 z`#puc`kvHLyytU}8##G07<7wN@qs+i-a~sQ3qGp>6E$KpKdt2OF7ugx-%ZjWD+1>Q zIa0p9UL;13JQ#xT3K<i}@D?ti^cm>%>h|pJr&)eSuh*sFhbOEtK|i$%y<6sAO6n#e z9p;!1$n~^+sf7OjirHsJKC42vupWk|UlvT2Cw-J8N!>Z$5L!I17v_Ks$r;>+cg*uP z8UO2{tOVLMjd?K<tMY9)j6p5Dwoq^V-SkL+`}FApCBV{N%Uf60+(wBcj1EKew#-d3 zW3)tOL)Ue_Smqk*PEb42ovWMWsX=c+^w#g<WbSc?=L+qF)X%)<5u9_K=Hti@qoP^M zW!!=H@gO;~VzNI(j|3ni?lbI-<>Wg{JO!90T5hNfFc`yY7uI}uoxTdRWqa)`q>h~< zCj2KWUX*BT12t<m4(rN1eaA4<bfgj01PLV}J@ZIopKR&p9)48WYze4+MZ_zI1kjp4 z;v7l???28q1$J*D$-hJu_h8qK%N(WCK^!3Zv`xUr$f}iw-QXY!f38rqvho7NXw64e zJM=r>pXOyTkA1Y;Q!iky9lXjZ*G$*sch41{1KIxL2Hg)|AA`DTwuNuwW0^|oa3V4* z+RCd~QV)|;&3NeKJWnEqf@q5lQ~R8g+2JH@(p<mfQll_*6ql?Q70#=Rn@r9nrCx2I zdB_DVXSZ2w0z;E76C~Ntv^I3qh=j;t|5eHOd5Z+GaUrl4C?2zuQcuAwaLD<1{ft~+ zb5E2izV#c4bzXl`B_+Rcd49EFNpM1z@c(pujra7{zF_%{mucL@z~}%z*LB6ZJx&!b z&ilSd$yi*jkM@O}9|y?8esT3<&>;7t9yPkL@RJ0Vi}3ZhFGFG;GGLh6G*vWaU1OmC zSsmL&?%8Whj<8I41OZvw-4tDx`?%Vhq=EE7-0_@U@f8#OqjaWcMRNGzUo5>wJ|Y!S z*q|Fo%%NwVa=yyUsCki%tBJU?4wbYCn6`z3nW-IFOd+5o*HP8ot<tSpi>}j}EN_a* zyBj+N>!>k^Y+*d=++F+aogASRxFU5pJHP)!ms5u@WLTcs@jk8}6@<_aqG#*W)SdkH zw0G@?GGf0d;*amTMg>6Zi1f?PAYDc*tMd)%HC%t1rmKIYueWXFMDrpcNOz@wqKjIV zdj^_}6($WT9w`7|YEkt8pMISD+*zl<ILy+DMvF&TUq@T!_tXwn4+NwUxG%ocSKU?N zJ_~}c0)K`l2C*5g$h3gibH{WvH%Mp=fI|y46@gsHo4k#%`H@#eXPVFKkLQ;JQEDb( zdfFtRF>{kwrn|mOSZB6!p<dxvxh<*yXZ1J;^1J=dSVtL%;@WHxZFLGZ#U6Q2Dp&5I zm)DovO5Q-nbtuPoJC((#*gL?e54Oo^2UZkqF?d_!ut4o=RkA#CUQ9SJrV++Wnb}U8 z6I19CCtfWHisijjZsl=iQ2!S~Qpb$0B|!?tX<QoUt=m|H9B&eXfM(aSUo5;th?BRz zytWOiNB=s3l;5gZ!CmjzF>3Vwf7+HrT}mIh<155goP%y9Z_L=K8C3!gZV{e{i~;9K z9yjokU(reZiWzq=aCF9v)-@&eh&_OK0w~Rm?P=px{48AM=v1@KYmn^boqnN|<K;e! zoDhxy$u*4}2cuusnHzMCbJLrguxVbd<SJ56ns<wjrFV=Ldg2-d3C93!b&~sJHi2T9 z(S*8DGDYX!F;lX<DBT}Cn1h(uk3+w|-makDG7YFgldD3R`E9r13DMWy23p4NA|Kp? z%RxaXL1GB=^R6&s<BHegM(%msmpy*0&qBB@Yg_}^#R-i)K?Fv1b59Xk5H@9k@lE)t z{_vpu!(J4^gK&=c$TC6zd8*EqaALy#SvFv)MwA>{0pRHy%G1XuBcTyzdce5S3MLo2 z9TS7!WJ$}|K_@Js!BB$F=^`ce-`Cl47i-DV6+Ae0l5}2YG9y{Hf)tKAuXN{6KiMK) zE`_ytSHPW)ZSMmJyE!0qj+7k{n9X2L=uRPZpFb9V8~pMeu92_5L@>Ze=Tr_6c{)3V z%zOZ(?Q$|Ke2dl6z{lldmI9<gUYDKD{elbSb`nV2k(tgu{2Ij40>UH|5n0TK2%Yc0 z#44xLwQp|<ERPz?4)8(;Wsck>_)?#PHW|?k3w_C;sl^n>P<&>})9$Q_I2GJ>EzS~| zd=k3#x)pm_@}TFDSA}1${&XY`ub&uJP5vnbtHH#moBE-<3V3rbA=R6G(JRr$c3Z&x zkK4lPyDhvM_K{~_0!W5BA>C+r0~$5<WT-mPMf)&bhY46#%5k*JA`nT(Ap}9ZpULaP z9r?Z#NfWa3mZcV?cH&ku^}Mn?BIz_!-4*;HlZs~xe1gQp9zy`D$oSVQ^ZeXOjm4S@ zJmhQwFok1=eJ?{aBA!H;oAJp9#butR9`DaE;%VHwNN_?F$$$^K-!{7t=5L^FhNPJ% z99S3f({UW;m-<}2`_pNyTpLzZL7oZ_HZ-zadaLZOEC|MfkbM(Xt_e?6AM|8P#OyId zoMR=N)q^{52|j9+tj>DBiJRdOD%1Q>X;?;K@#G-2e50Oh05ef{s0xPUcr&Ylh7L(Q zZYDL5JT_FaHZD$1{Cvn$%)-F<-WU=wUDCiI=~&tR*?o4?%`qWdvPXv7x!DL`G#{qV z#VA}6bbM!(sVUHVhZT>-ZO2Mpl@*Rb^4Sw^{!Us?`bCHp^LjR{{1Ub8c`MG6TTAAL zc)J9o0*hl`Jl_HD^16xW6=CIfx$lpDTI=8r1oCSc>%w<pu1!K&YD46VJfogBt<Z)* z#aW&o=yZ{xhG?_T$}u0bY<iDWh2HOkDh%wgF36;6bB79n0<6)b-lGsm>PA~c&FRF; zA-Zv{ryyTXHg3sf)BiNJprI8V@a_RTub1ghpDSBmyERvY<f{jeI0#Z3URYUJnF3us z$!DDUBAk7Pji9?`eMX2*uqJxqs*%LYr!B+VJwu?X>9dIz&DvbxKlSX6Rb})24#`@$ z0P=RJi4sjlhuZo&3cXZk*jEc#nM;0n?PNbyY$;q060`gjt<lX!cm1rOfpV9%f7M1z z^ryKhqoo<$gv%$(cA~F@>@K6_zm6S57>h2%@W?ez>DWyKdTS*#RaHu`IeX^q02wa` zfx2@9Heg%6o2YfU8jlx$<FDO9hg|Z-qN#AY<L$>VVR*7C^pr#o3>wu1+T&dOO#Zvu z*Hlbshtg3ig!O2fI{^UILFWL4TDLrO)q>FMIcan48AjY|W^EG;6LyG#{GxhsuEp;) z!*s|4{hO0T5dK-pd~Mnq%b5jMIQ>gC`hFu2PE_T>(p)pz0E<})Ghf4h&!rh5?i;V! z0hE-##Ck$R6lz4e46V0ren1Q2b6)rU<fMkOzkSpC@%L22jF?B>ANXV+kBZ+1`<+<= z!mnh6f~>mqvNtxfK*c{E`fm4Kv<sItVBugj42K1pVFZMFNBG=E?`fSt0pxbUwqhRc zSEqeD7_N@U%B7vWS{8*VK0g@HdHHIlAKg=@cg%2VSX(yc%~n|)a{RFc8eO_4?#r;s zWcFJ6c1dMBB*m4kwSyO6r#sw);FSv3+jUqzqak^t<ncBtR{bcaL+z)2`CuE=+rq^h zBtyjc8_q`vFM1Z#SLWU_nUJg(6^Ip&pD0l|QcF5D720X!Q1YG%xdjseF?hy-*a?Jr zo=a3b$hu5?=FgBQj7^II*yHG%HnTsKcTK{82m(Lr%sMHV_yJbx>j6RAQibHDL_<@m zV_CkV%sqJj8)<^Xx3V*<0rqS~@hHcL#ki%ptSs8EQ;Y=^R)Qn<rx$>6Oq`Hs3DU=v zIlr4wO3<@om`-=gQ{iBTqcJ)8uKsNcc0)0`&TWScYqE$_8NX|X!h{NLb49J1Ezykz z77#Fp<YUzDBg2Q}I<zAN4h?2~7cMoxL7Ib>AtE5cC3lD}&s{Q5z=Xh>h3aAghV@uf z&7#GAL?hcOMtWL4^q|YOpi0W(R^i{)8zzvAg<3Q!@r(wly}nBn0(%cMw<--0<mnyn zcnpAPx9&|nQHXpNSd(JiXFw;bOVcE@?bhjIbMb~eqo6J$3y^4OQ#xN072;War%jk$ z`Pu-LUAoZXhp<H9`t9>;`EfA!(b4uMr@l+F{Hdj#jLjP&{;W$dsClG*n~7JXp8_ue zu}<6v81K>Zi~8`bQ8MB!@8;FrK>hT-RV1gMZ%eQ-ls$ONkrtQf!Q`_$plIwqmcPce z#0Y>tuGbwIPcIP0dTU26gEnbSHV{QaiX2by4!`sKSJY9c3f6=DfoPfjbUhzbObSEL zOY_70J(O8oo6X8PssH|&Fd2osd=4UZ_pQz9+_4}WA#X@fo#+l=6_GO+Z(Som1@^x= z^&i7B>b<=4dpr)=VbaIfr%e*1_v5dDcK{;qJm;}T(N2M7DP8>wpa5S6>FY&a(gohH znuO?i-yoJYr&)U}l|ZeZ;LLa5qQDq`{$6%|xLKVj{7a8bXwa+hGl(7jc-i*?{%9#} zj8-2*F8qlfWh7rJ@TogBYHWsMF~qh&Q2K2+L4Lvl^6{W2Jx*v;dcEDr>vBpy3Lau3 zhK3@`>@AO!anT733MK&rlOTkd{s!R4m(Cq+u6bS5vzdO3=cN9xz1H?%!ZNq`{iav; zr@C<}y)zd8DM(qUfM0yzwTwS?0L0%N?g4<-wp9c2+uHm27Nm}ORj^1Atmg~SQVnuW zRzE;P=JeCjx~pG9hHoB0w0`;PXIh4iy84IukSJ;tsdbn--g~!gGSS-lMg_*>=Ix<N z`S}`mTSwAL<$zq0aOQb?)IL2x(m(_3xjnsHW(%RpMRSYsF}b!EMw^@j5XlXs3KywL z?haw8v~AQA(pd@1F|QK4*DB@#gR4K39_tlgI7&zd7Bb(nJ$^_NnucjQ-Yfk5^Ve&f zUo^Ou7Pk~>HK$4$+-4^U(JZ$O5|YNI;oM3M=Ogcrpe`tdvLkZ&Gg+Q<sZb4}%!SRT zk7eLU$vQSfXj8i<67T1sSF;&J6s>gdTiP&fy%Sb!MuO!GTNgbM=POL}Tm+5xD;W3K z^CdgTILtN6OlrleQ$!y!>yk7!E79TD#l^~)QM`*(SK&FB-#R2NcX7ZfE3q<*LxraD z38X$3s>g33bPwE+E1i`@#ph#%7)IDs$%H52w?0dk2JKpyucMvqRrA9i8+8<frnF;_ z?NHK76TGbBdd5Uz$tf{n7YX~_ut|f39W1))JR@vc6BMvST48|EG57J7z8jh1z+~1@ zU@EKj7bO%wneOW?7OL36R?6clrOvlAN%~k)PDh4-yd&B%RJy)?uCW~czi4e%VX<3d zH+F;n3LV?_K<S|(VoKN--c7QCjoY#q>7iOQd*EHPz0-Yhbs#Ev;BIBzeqWo)UF`6% zYkfVam60A({tdkpN8Y(b#g>HMDTXKFK5l%(`Y85<Rd8G3rq$Yeye1C)sY&85_7W!5 z=Q>U+^3{b!ZIu}a^pf(d4;Gz$RMl~~bq*{nE+Z_A>%O;Tfg>JzuHZYZ@g^O;`cbaU zfA?$}*@krI+_Ta}#dl1N(!I{LU_js}T;Gf_?1Y~C&9U)Mgg;AW5v4%A(m_pj=qbg- zi)BT_c?0s8v!&@JB2W%1t7&qUob2U$7(^V)nq-<l&9Mv*Q5tQDWFeI)F4{>#;FV;m zCUuIF=_`T}L0SH*%)&xZK-aLgnbavVIXCMg$Kdpge6II*D-ovTT~y>jg1a#BRgdp; z%*yyxeAx~&n}he@$Cp?P>ajYxP>%ILoh5daC8GZUI9&mcJ)VDQ5Bm$I?PJ6JKWdYZ zo?xZ8n7{N^_Ko4*5r>D}DUKDdu+b}?t;b6a@hgr#$ewVY+8QEb2~b)*==^u|TktvG z4E7D^BfRd^qqV|!5(66gaQ%MwH!q8(_FI6W!I99%nCxBs2hX7-%naZsxYK`kio8@; z3GyJ;q9B|ax)mdME#c}st#v#X<|Mh`)Whnq`lqF!d}Y|4&o!OmG5y{^E0Cni;_uUd zaz*NzQtMsRDW5H(F~qU@Fa9jfW`zKg(!8`-G}idOri1n|cF_8jut{Ieci2A~kv%Z= zsja!b1ACeiLth|J?DbDi6N=-euKfp0A-OOP#@)uO4?_U9Fr=`!BC%hCt8zu5QmIY) zlpHl8Jur@xxCv7wu=Y`;1I)14`~j3JG@$2OIld1dVDZ3KS7iS^a-vH?0s&fvB9NxX zi1L|CfppT$g#b@OKWoMxkeY{jRGGb>UT4X*_pw$e5ph3DmATQ_mpMQF97h&ka-vxB z8GHttLcwmIs&P}e;}GkhS~b$_FTo&vL?0z2Y=?`k^WAG!^1%e=x^4s(7V2PK@h~1! zETBmKkX>w%--n|=-<ZX`kT**pb{KomZ*k_d4Sf~mkUlP&@pjs7@D@Xt!Bn{VV4Kss zgd;dB)R0_zOTq4ho!|Pny3w@~l`r#>kJv`c*JalD@(Vv09BT#I_#o=zB3|DZNiY5R zTyX_`=!g7+*9b?uJ^6K0lRLW%uE#(y>CwCv5_QlYFvG6#H-`Dt2b9(KChTi+)g*5q zJ#~P_22ODt^iYCg#=*FzE*@H9NmmCgA#*n-T5a9Z)6MX4U4@bzkCzGJKCm<?JE7)6 z4DTcl@pA*Ge`RT03)YcKbIOSg-QL#yS}yDPQPZ~X&-DBBJ>4fr8t)uKPP}PaTv`)? zlMi^32A{n-rffvQvikBJUH3;pkuV@@G@E05P_q;*xTg((g<`;hXnEm!A`x=1&kUQJ zlbpi|FKhxRawkh(g{y$<J=S2k`A&)Iq<_fCV<B{tlZv@*OVVP^LlM+#RnwC7t}_D~ zE(@t~)#=EBvo}rS1xH_EI6^2fmBoJ-qT#lEF53zDLpunodUrt$P8-8;Xb?SKj4=2H zxLIX0YNg`r570B_7d9vNQeBzMlh&wR6I6n!=~;{c;<T%Edg`9Meo-4VBJ@qECSxG= zU|JcusA~3W5<&+CmO$zk=E|<A8r1L%laG98ds{y6Bv<nuQ7fwO>H6TZoptX*{QlQL z5vN5!H2-;5X&gM4_!z+a{+7o#;~XQ=N(KhUL3_C43SZSp7jSaQ7Zc2NQX8k=F3Cqv zq91JN)P%3)<A`zu@`kd{m+;~25dX(1_h+b9T?qk+5yYjWWqVV1Z&^dWm-<6pZ9g_a zuy4M@?I?06Kv`A)tweSg6HCtojpGRw)^2Ls9jw#y8ulMSm0!RGg`cBQ0wz_ZOQtkC zPMl>#v9nH%-VpL!<p;cJf8V$}KOQBN1CV&hn42^y1rHao7%Hc)ZppP;oyL=b-jYc4 z{0rISFk5IC=&RLzkI6w%M5qMN_dd0{KqL4<s#r1Osju2Y(L+GSMmgtQn77x;Z#?Fi zfkjq)LdX<<gv*Or=|g>KZBF+S)Awo1T<>&weem`eu$FlHvf(<S^E}8y-~z9!G<4Sa z<NU_Bg&EOV>0Qt1-~l~4N9YHOhi)d$3ILv=lOmcVIP+Q)OB5O3e$##JRx@3;63}Ac z7QBKaM|K?#hrla?G2da0Rckf-!#$Dmg%#XHx|&^k_T{GFcoic-7792vSqEwJuKYcT zmT<1V=0cMhoWMkvG_1ufJiw9*p8FmV3+I7@Ka<s@u5sPyLp8|}(QDL-?9@$AbsUaL z#J-ggjUubfi+2*LZ24;qHE##iOL8h4G2J#IEQXuq3AzHfnBEWVSnk;V8J8z&N!vc= zM8OuH4dS2U7i*Cd8Nii9e#`K$l_W}D+!lGVl_&MEF^=uagYQh%2y6DZ@b2FXFG=Br z7(0J9eh~}(#{0`-;f&X-6ix{FV>N2S^=3P<r`^@}Z7Q)CYCy@1;?-tXl&JW=RAue4 z`99OFZJVUgmrV<sv_i6<+Sm#aF!XV$Y$Mr{bP+FI1lt|FZJ-;<?E@J{bf24v8%~K_ zw8*U@ZSC5I2~`+HDS16@Sk9-+)E8d0@{3r@<I-y2$+@`IU9Beh`-RWvao?<gfFTS8 z?elcPWkdFJVCYW(_d$&b((hvSb)X-#jRLR)+IALp2zTHn!3sX?j}Qt__>1cZS33MB z*{(zZdceYfHo|O@exbi4r<Ap`1aBGz3zg$LxnGD35#KuIEQjce#1rQRax;o@Nq-HO zwPZ;LQ#zKK(omWSpU+}860}G0oo$t}ix<0g4p`Klbblqo^)u%_&z3H`5?lJg+yG$R zeGDoxR4aw(92(F}%GCX4H<wu)WW-rNI19%R{~Rh&HI1aA;G@{yw(ZcPm94QI@lrCD z)psx75OtMUFQ`e90?mW(3<aUJLY9qjiG~M7o{?b_(8*a}j2Ny#cYP(Vb%o*nx>HoZ zhSA$VEH{($7;nsA#KzMfC9r`g2Jxu)U=iz+fI=qw4(zEJbM`-IN(>h|gOIh*9KuX1 z1b;{F?8nbe;3uT%aTHYpIT*mF97{*!zNCG5)br+PBZ<oP<|4}|;P8Xo#|S{#?E(9I z+MD)A7wUVnP&y;Ma-{L2YY_Uhty^4~hGR{i?)nVSpg){hEhjwLfNT2zLqNR0UTXv+ z?;~%~!y4t#>)X}zI^zPkes3()u|WF@jD{u0i0!vObq?FD^Hvid=}^0pCP*ROdyP=& z*KMpQ5`N<L{3t%P2}$W4vl-5^$qC<s+7HsS$NwQ!UU*5pAXrYNdJW1hmvLv7<>O$5 zD4j#ckG(CbeM61qYDjE`_{HRn5ALHHK}?m^)z^sN&j9Tk9<KtMu6R@DrC{H`86<rS zE?g~Vc|c?Fz{wXTvY{I=hG=t0t+3L~_mEH7{(;;64q&fY$?UG^7qVnYbQpQ}+*%D( z&chRzllGzV|C_=CZD+70yc;~ifZzVO@d^cbBZh+^j$7mr-0ixanaC|%QQzP`&fZ<$ zg|EN{N<>lkomA9Sy6;*NUSmOL7*W#u>$OPyKk{Bnu^o(84PAbLBMeTeUmFhILTVUc z6a7;W0D8~`^O>88vCg0u@8z`#KLgr2dM+OL2^T}+0f`Kdy;Jurr-z!lB`oE^VJ`Ce z4?<{$oC)bo9vl)gFwL{&!@tQ+r@#$#Ut0xD5smr?#ys;Zlm`us;DD|!XQ@(9$$&oG zk%`i>-abO@I1Qr0v6eLEl_<+==nNMsC3B~B?-hRk^jP*fNFr~}iH}}SbvDPGSGAMN zFi&1*r#Yn997=ZlLMb7q^*B<fV=QVcUj!6Arl9`-xto$wi%q<G#!4Jd&>Wo7y2kw` zVibpM|NjQoL<Ue9jrfyI>uy1z`Vj$hcZ~Qu`-WzID=4E@y@)#eBQs_S&v-Oy%YT3) zx|Vb1Km{8a3%5H9wX7rg45$vl43)q3VJelaJX?$8T}NuHy5ASxlk4a{LhoNxiV;?x zUtjYAO={$7UumGtXxF0l8g?SGH;Fk=e5*u$+3S5;B2G^x2Yk+@nFHP^E%cC<z2J~B zdb3JR5E0ZvB6DIc$5#SSqy$T3`cPLA>`)^a0)hTxJ!&O8J}Qe>&p{WsMZ!Mlv{q=@ z?GhK>FUCCV44`^bZ3Xr%Qq6Y9g8TMj#6MV?efEsf^MRg2b&;b!$2^*;%)4P6^3y5B zct!;bqeP{Y(gr)z#2+OZZg7ZJEwL^_OfP5glu5YR*$W)sUFldT&ktM1d#Yyu&+{41 z>h<faa;k{|*=G=c^0V4LqmudR4@x{tJ*cKkX)1=TR*?WLX50Q6d<i^u{wia|0c0iM z_I5SE8-+Y!c!l+9UEeouypSH58h&9WfpzOmT$rylL#-$|+arPY6?-MzM)BS^b*c*Y zjdAVJw}}{9i<2*9jR!uNEk4bMWhnhPv&@AU(>^J-bj3=GHvyacHdq`|`M#MUf{otV z#2FH`dfysmeu&LKGkOj>fF%pY9*n-I?!ClD7O0J?PM=*tWOdvbLYF!~sq7nHAJL_S z;lQ<@NGm$*IfE}Nx1y@>JlySA9LbXqJ$?M3=>X8Z*%lxFwmCgXsIJN!ckC`GDvhvG z+}ORDIpv8eF~deIvUrjUEK=F{bUNoj@aE{>75)}9Y`pW^mM@qFT0S6`S?h|_Mrd>D z>3>|XVhLz6OIBz=u8Q#M(=xapHYKPGE~`~7^QIyfi$BUGZ~*S7m_N7`LkkljSDfYa z$huu+r_hcA@-*_8>8mdKau}tM^Y*fXV>(lEr*CZ??W}Sh6!xvJ<GpT%A<>?%mhhV8 zZmIl}5I(KL&h&3{>Dq{;cy3a@u#=s6!8SbhLo>RcM|yLyv>7b%eyF?#(_2E`cKfmk zd$fn6GC)5?>U102J!~skxbMm;aHy;q_Y@or7Qh~!yJM`dNWK77Yjl_>Pz;D7U1-|# zv+}an>^h|R;{6g97f5H-HRIgb1OR1a?Hgp5#0r4I-y2r=J%4*21ZEF|v&{|l>?}rV zC=X4s#h=&NCZS8iAE`Go$ZXtgR-Yhhvz<bBNs6#T+R*WkE9+}Bg?6-{?>QS>I`f_o z=yx_%l*nUf`Li<~oZ2lhpn<U)(UL|}uknAJT|o4Fzpjlgj$)4V$km&U10R1A9ecae zvz-lVQ=NNlK6FuJx`K;`@ITR>RDLSTVr#d39xeN0AyZF0@23d44r-O+UkVSAg>lm5 zD7iiXJzXB#;}I)BBqODNVy-4;)Q)<8IiRa5_O$?%6TcYiHVwwk;dxho3z}nZ*`6$E zrp*wo5<nxs0ACOSfV(b!Z1#m=aysx6at;nz4P!2c!-N_5F=61olC^b<{QB2h$FTIv z$viU9M}{U*_=pG=ur53;<4*cM$GHrx{uslaqA;du40G(vPkG_L&~Ug5G;thjtD9iz zUMfxx;;>yWkYgFEt?v0S@G#WsX^oamku19h7_h5rReqxt_i|$=txy`bHjp}eMxGTu z4|Fdo*f-aKPA0W9Tbw4X*5Ki3JzL&Pwr%wT4cQR<Xyh3yVE;**SWgs6T~OcP>Opz6 zRt!0i`{u}+G~QC%tSM0UBJ<MVMro%cg=&A&ZL}>G&bR75JXn;J3C9{Kgu%wsqlZ-j zwo-*mrE|26=BW}+6?v`n>+imKgk30A8Q+9AsZDDs__|stm96Ju0h!Ky3mc~Yzj8fU zY0zKwDye>M@p%h@Wgb&)@MTvrwH#zc3*sh5PC$Mc2ZZPTac@};8E<9gmsVW_pWY%9 z{P!9La4kYxxm7ez6zNU+#_?0&Hxag1l``R>^I;bj7awDG{O22LWJ^BCG0zRPguyhZ zGroA@>j#Z7EojFkd;Rbh*{}T1y43PZUkudN1mupZalx&n3CZ&$X50N{<~4j^;hEJa z+ViDH+bfi1pw1q!L=*r;4P<127~T-0#Gk4t_UTf4IT^<_@5F0QcOed@$<V3a3F_B* zp(GLxO0`mVKQ921?dTgPe%NG2qb&yN+%Xo;fFAduSQ<4Xf3lsLf@OaTW8SS=>1PBB zGdB_1wb~Shi-h(6jYVlQ^d^w!>2l2O@wLG*-Ji30nyu}yT*@N5#eGISUm%BC&b_PC z-w`erE^5W@$~a*I58CQLVK87IFECo_GT)~aw9l*R+bz*DNcqvF75_8N;l99pBi_<z zg$9;goGyn}PV8Tg(35CX)t^=&jFh9MH$X@hsn3Cl^6Ie{WnD#M(MTIC0Tqc^D%XR# zVq;{PSk^)Y-auyuRVF_BpF@w69CoSo_x01XwsUzS@_NJ`xH`ja09$UsbF6?j5F25n zyw`cwTDpQOCZ9T4>`Go4rvqEjanqN3z)nGO+tYUC<gk|kG|Q#@?LYIE{FiSS&KFBj zw?H=QN{rE<wMj9u6x_bgGno(;o!YBed`)~chO^RD$qCnl^ZI=y#O%($yS!X)Zzv06 z-RyKRy1GyDjMU#IZ<fp1RQTl*_Ajnrs)t=v?sRT-(0N9p!$lc4vdZ}lE@1wevuSsv z4NqIV--|3WwADz`QJDM<9}WK+c6l<!G&SZ8^D<bxG%vNOs+tLN^aQyrP?a|9eS-R@ za1-j^%G|;hohskX<5Ns#MNE7?lwyNF?YfMnY%OIn54?8wg5kFelULTf9Hqou?Ysa1 zOK{N<WpTT_{JDYFfZ|W>xTf4Cteowj7DK#*y^GabH?Er)a5hd_r;a8u%tjz|U>pM< zfASLj<;7X?C|c6+a^XIIRa`2m=^qah95eTqO;glxzqHW^s;a2$179vM=nPCJP*~Bt z>T~}Fo8!xd5o#}r_)p8^Ic=p8dT97Io1Bc-Wh0Nmatn@Da#$}9d~HwkO!P1Pv54!- zkpyT!zWzC(u*2LHR@%e-FBKN_FcPT<!*&1gtLvTF@zMd4gNPYrNCg{HWQj#v884zn zI%^lvd((H{3HyI!%78NcIonLB%uo7a>%2|!4TrISbl;NYn_wTV-rxvnNbZg@uW*r5 zQvyc192c?z#!O`Cq3kY_lOS2W-r%Zq$s@T}#sw<`d1bEj14Ih)K43mYmV{fGPy-<- zhwT1Y^s%n=k~gM<z%ykwHX$>pOZk^Af7~qSA=Afq#-fO=LG9NOs7l0}fptHE0trUM z2^A<TO5}r*zH!HRWx3WcKC8jd+Sq&+{6Ig0MMZNFw?hxVMZq3_D^*ehFqs|_7lXfm zP$@QS*b<eWW7AA9!oz_xy^Y-=gg5}_5h(>K?$J<dMlF;bihVmt+dZ<FIFrb)dH<jD z6a%*JO3}PcroQNl&3z$IOgBoGYGb<;AeKZGX8Ru@csgs&`mLM!!N(s2$jTs;oQ>?~ z9RfLQ6gw()6GK@2lUx;ER|-lD?B8BWD)m<qG*)C()ht$Sxu?PUkD8R$XF4sE(Xndj zVp#%v?$zmB0wxW;8ImfxTq|p20I*^~n<mUDtQjEON+u93(q8W^43M5tQqpq4yV|m) z#4fj?t`my>_8c#9n9~i3aJ~}`VCW&yLnHn47tvx_1$uhBS@2vyA?-36M2&ewxdYm* zZJB-z3-iF9FDmvrZAoJew>lcxqDgsRdiDChU;AM_YUrycqVMg{>cc$t$MQVMWcRz~ zXgo0XkDl|$u*-F#dDF>KuW7d5;c4>%{aYo02$;O2-U6dqUL1bU%$pJiUm>&9n>7N_ zGJQSl;a)^BLvmdJrPc1Hpb67Rwx~6wS4ulmM_l4EravG>U!_7?Hu>l=khS-k*Ku!M zb>$jKq=e5G9f*&@Xg-2Fxx(6;1l`mdkF2JwzZ#?0YTqZ33N)AC%2|vE^-xU2DTg{u zN+$OYgRO-5T~1-L3MA&&zr%n>VI@Vr9T)BqNma41YS2@afwxPGBOO(_%R(XCU)Cu* z$;i9&=X?vTItW}m(B_8xPqxcD1|$J>tfb>NaP$;N%jx=n<FOvzj7<n;*`v4i!e!bL zcWN5ZC%@2e(G*Fk33|?`+>njo`El6xQPdCX{d5$M#yA=~zZi#eM_}2dmy#t@9)MF4 z`BX#-?T`O$wBLw45lclBaTQ$LCNT2nw_j-agOgDIS+XSoFGSKLg+J|x8CRJBho>}U z4o1)p%cexQzUn6q785*(@Q5GRbr7%8+<)I4capQ6`j_4|T8VFD;>w6K{3+zkiJtw+ zU0Wr!;aNP)jJ8dYiE)sv3xH?7%OqD6!!<VLYHn<w3$p>{^J2-;h!#l*{ip?3VT9x* zz{pPAF4-9)Pxux|<u*_`?-m30%-ut$KM@U$>$rvU!}W@qVL9BKXE7u#FcK3@r%xtR zvE<2F42OzRoNqqoYl|L6=4T^PfH9}3<<@{Y4>e5_(QWQ|ceqwqajf6kRJm<@-}2(9 zf83fY*4DW6-oOw<-XVn*X6ojygW7oz1~ltow!q|!*PUqUPYGr$(As6Y_VOE}==^@x zzbIO%?G8et&jHbthP?7yt^8Vkr2Mb8=BC*V5LzcC$zEHUr6=W!UG-j@pePL-EZT*y z7~`M#d4KfP%<gv>rS@2lg|W5IVa3JT(w?ZB6TzSh#a=mk3-tSCI(1MNc4UvyYA*cL z;q|K!jLbGYqB)Q4k~k)<*mF#ej*fjdjbT=bZ-U1O>c{2*R@Gu%W#x=peBFW$0MiMv zg#=)ZMs%#UAW?%kV(7_CG&u$wTP`6@DYV*SiO!9@4VX&9eda5p>TkBsbXO095b;4h zr|yqMZ9Rn9sokV2c2zuUHa-^sJHeX(isz<bI0IEqNg|O5Caw}gOGKURky4AhBm7;p zWxe5!?aX{-r+GxxlB=lLV}?U$1E9^5q@_5=ITD+Q1c{ft)|*!s>{3dCmf-qUBU8w< z4(WxZJtpLIqO{VWv^NNBk~`P_V+5=}0!<5cGG1}Xz9dW`%b9GnmEa&IkA5Xt8~Ssz zF4CY{$bFXE;`ecuV%s^uF7ZHUKxrukkq$*yA*l}%WUxn_lsKPZ9NYv7nZH-oDK~_H zjCh-rl{S)hB4pQ{cI)a5PnByBnqITr!4d<+ll5Lqj(d8f6FuHAA$Jd7FSA*DopwlA z+sbw()%`;zEx5RHudK47hRpsZkg~c+GV~;M+F2YPAzY&*4}cJB5SI@70Z@<>DJwuU zbTk?HeTWau7K)U&$e|iPMa9^;+fU8TH}g{Y>9RNj2P|SG%Dg?GbXokcWCN7xL^gvu zV?RBUCO@k8Xk}69_7Tt6Gq`~uMoEYr`P0++;S+36>8L=%?%$0|_dwUje$tuFP^Nnn zO3MwWjM*rgJo8cfS;>PC3RoVt<<O5xcFIKe{RsKdhe{Ia<<jkZDlp-B2|3_*lXwYM znWdUpB%pqH#~_Y%fUlnNKoD|fn@L|%Kzy9?xMN#Zi}vZeY_)StOtl~@t%H~y^dz5# z#nI^{E&FBb(EWDH?Q|_s`7+PIY03A_FadUa3!0L;@2c@k+7!eL2YhQVW*8^<U+b6^ zN*ti~CTi*c^F8#edXDLz#jfZw125@_5iJr*ylIJ(u|$TjuvXikoJh8R%<`XDT^W`X z`bypCR3=G7$I-HkK=wudT#}jgXoi}X#qOjD#P2<fl3Qg@ca}x2Y&;2X_tosX)-e9| z%#DgsbLJhkvAbxxV|&ptc=N%=CJR6D+42l7GM5D~zVHG^V_&1D)|3sJrPnGEgVESc zOdE!hEy!7o=t1kSdn^I9{G@rZ7Gop%Zy8}77C!2G&ob7I3Yjy@UusuB(s4P4XSiY@ z7F)%^`=zS)zP2=D%gWD5KNj#we6G*LW<}sd0S9g1Hsy<fubvatpT*!{k7>D-&Nk%z zB`H@Pj(!q<Y<X9%W-9i=4PxLQotQMZ4Hsj{Zn6W?8NFWpmE=6MxEGH<<Xu;ubtKkv zSt;hP--)f16t7DpOo<)uJpv@GNS`(^iIfCyx{Sa2JO2_>3K)Z)8O;^Jt9Kc36%a2P zZLcG*GMTn!QfW6g0Lde1H~yKOH0LGQCHxH?JN7rRTVhnVyO>DuWNP7fYLGA5sXiuM zOUARCP-`dE+%9bCZt?dPHB^fv9Z-#xO_YV%SN8`?DhdRuz9<$;Ek!1nkbGja@U5}@ zr!-J-=6^cv^Y2pCXkxv4$9`5hM$ou|uN-Ozj$O1M-^&a5xa~58=sXMaIJDbuncV4X zF*D;}>m8dJ_bq5X0l%4UihfU7Fp~zVJEo@&+g^@Jn->{~gJ_@SazNoY6U2hMwg%Q3 zU-&=@3dc%MJ<0xz#V#}UdCx3jSsSS^rH!jC5QW{*%}@L!P_PQeV4faCFH=J#ksqBO zr!G$io(^-nT)H_Ib>QXDuqEsud}8pb^&@}*>Gi!R%(m*MDmH|3=EUK7)OU@78Q5b7 z0;AV`t32pOU_1<VbyoI)0&AJ0@OT+<ig#ldILuN`?5v+f-J-7f1!gfytX98$3wERo zd>C!bz1z~~T#)GESS$Bzk?<Wy8}P@tDNUn(3_Q&RJ;vrUTiD{V7`3y1d3^x)$1L9f zS8SDEwZf%bLlX+C6I-clmR2yc0fCB7PzVr`baNO&)2GBy))@P1(3o98zTN9P?u8@3 zsF7xnI`#sfj-X4QZM_y1elk@RchNkVHL$Npqo5#%m-!7x)Qk=|FII;N1MlR>pA|=R zOCQv|%WK(*PsTTZ*VJo2b$q(gv0g&sH|5n3zpsu%X@+D9kC%@tc>nHdJC#`cvh-$D zJSp0Qf~tVrDc;jdMTh_-OW-|<Iv8A73dQ)OhOC}Z#&7MEOGNn@z696#?6sdQUBcLK z25<A|2SUCb@*>aRhC+$I&diRJDw=-L>MYQ@_2zSwCJkQ(3x0;3E^StWB~<GBpJ5nb z2h7Akm#Vbp^umNhnXD8jcBYSr%wgzgO%`QVl47EuD<xs>i(N3|nz2mW@|xbKH$<jy z>uvV&!nZ?lGcBIRwmPV1`D(nuk&dGhzR(AJ5dII9sfiKqm`VJ~E`8_@HDRiicrbPt zHe6`Du!?BhTTJCzL&doQm{YPNZrG`^VVOokH%>XcF!26~rplPnDK)GyL4#|F=flVS z)9WGNZs?z#VM@Lq`gL)SZ*W13D;a>jIZq@A7i07yCNo^?425@RSYo&RTr|w%z3y3% z9}K;$5_~>0J1EVnbZut;g3Xe6155aAS+gWQwTiEab`PC~goVrB#VeTGyTy-qP3f0c zgYCX6py=vO!8mNL5m7i;Oe1P34WG6PPz!3JvCP*uhP}*G;YT&}gm8T5-`zMjaN3f2 zVgOP~H>cW|Giko@)ns7SW5i7?qm|j?1iHhDTQ4M#qUqguN_%d{4_A3%vw-<el?M$- zs6oVwyBb{_-hVkU(KVeE`ot3+!Q^Hq?sE<4-=>2FJwP$A94;?4G5p{7&BeYjMB=r< zmXd6<v;*rz8O(=fRZt<@)HE4#Q(2boPbCy^Zeo27%5G$bO*5WUxb`MPKBf!$E++;* zMSG|YXy1Wm<V|QmpbN^GMZpC8%%@6<UVqN(_W4P;z`WIoV0!boEv%|G`-jU{(g=M; zqjKaE&XzDX&3=crP)*|^pobqvfHGoK2MQP<=l1s{FUVIn8n+Hypj@<6mnbIz<}aP` zw^&ScpEpe)q|EC_3Jz0VA55Kh&QMq(fBixFCVb^#<a<hI+oON)h6J9IaB*^TK^b5Z zxT2cSA!TrAg`5YF5#cZPRI6M{wh8~Hg<+`Bs<*{I1$jeT5(*2RVQi1f7|N(c*ZR)- z1IFLhYZ6P6<(Fw{w0fV|OJW@QW`1e?t*kSM&IKt9M(1XiVEU7kM#v<C^?n^LT{2-l zSCppTC?~G@=))~lR(U1j#cv02C>{<LQK;p7u9C<l!ph#kuVH8frr8Qr7}U$QEL-wb zizJQ1Vkr{n)JnWHc(PR~-LQNZM)snZosc`_N7n-u>%~O?(M^%~nCw&}U6~!A5D$nQ z<VS7^@w|-2!!|Ov|CK%+&kYpw&;|f`nu)REmRYG_!}TTAWk65V6eAb(Q~<DtCDFM* zgX)Ybr%ULXzX^-k6vdvQK*NfNg8@o;?$t5~KI2mglszSm`2VN963j+*(Rq=w*4bFu z4?Eu{+a64WSlRmFZ4FHkn%)|a8Xa~XsN!L0+o=FgS3^aqSTNyr?eYhmP|Q;Xj>%b# z@}k0Z5z)E;n4!a=<Bfc$?@HUgSHKi%qD-D!1Ie=?)h$*RxWuLJk<JJbz|TM!x-oW^ z_0~F6+I>1ccxQn#b{l~$IB(-Ch{c$XQnazYuKl|T@b&`i3g5aOn>!7OxzSLCkRZ~o zIi<da+hdxEmIz3>B@o~}ZR5_14onnrA|!d#P*0gdS{Yh+n4~Y(SH)_bQoDxDdagYJ zg?boLIx<_EX%$+nvQdb}kGsu<u9!dkn!k^*IFf<VKUY=*;5pp46<3(t{4W&;*KvAH zmF$>La6RFwDWrf?RtQ`YJm1Kv5)#@#I2XEO=KaL21xFAgUL}A1`>9oWe0+b9z{XXo zb}Iuol!pz52-C@f3#~kg9nzBarkQ>YE$p<Czu#(t0ugtgk_g7xbZ$))cIC<4EMiWP zi*{Y?wq#&VHM2d~Zg&t&eeP#aRx*!=aG#Ii6RTy4ZeIA<iHbx7T^xnY9&y#PCQ87v zfLoT8(4bX>OKh3u+ccuI|6HenS9prb>}QHp*0UCedAcm;$v5Kr6mP;T&#`F~qshNo zv*Mx3V~w^DgNh)Izv7Cc<{bf7iU-IOpLvgZmOD_M1p&ze<CWaoWi5ovD>@tV=G+^G z#UaE;DU{RdNsPwYjijCYDL$1-dda`%X^7=WzOPgb<m`6`(7wM&k6V=1*F(LCQfu)? z8W+w7VFZV|0x`3dcUya8h+*xC$B~uSB5WboMt2T=m=(cK3a+wQJ?Ee~WA%uZp#@nY z$N^1y5{-}S{%G6PQpz62IaL*Cy<C~%%~kzz4`aTEs$qr|$KXxX(hqC#f$ikpmG-ac z@|rA3^x9Q=^J3!{W%|}9*&91~pY(*<IjgUek#AbM_fuVZ=(j>InOJPDbM!(}9J6Vr zGTxcr3O4jI{d1VNoe4FR7fs=wDzpL7htA{!l6>`TK;1Rt>Mi_01i_8zw4J!bP;@00 zo4kzd@m%95I+n8Rk?cNZ%_STKFFIv9sAPQ~U<xo83`T7n?|mz1#}<cbyXphDe4p0~ zUtfsIoZ``$#_FP`A~DyngU-{^v6WhU&lgzsbe;TV&SBLBm1Ucv8?N$L^;GWZ8MHfu zho`!X<=Nxr9_SsxFD!q91)c8r6)ugx-nA%6)O{YCU?U(lB^dHbXB8h=dLXJf@|V4n z(77^|0D(xr@BRQQ$8P_otzecRy3Wof0d<f<n^WIj>vD#9`T|Au0j+*A8$95ns>lPm zxaNZ{xSpMXK$~h_9A;k^!>lPL&-U$82_*H&PwKv-lcNfdf{Ky->xo@o^?y3E<>m;< zb6hC0v9Gt*{C21)RZVwLwYPg?fnvaL7zEyH3$`MwEBRv88aSb#Yi)rKubS_HEFmhL zCgvZw+7Yw|Z(IcZx2_`F+O79ONvEQ0v^F#M^jd*IQ)A;mSZxQ))Umb%XZVw5rXH%k z1%Kd;R+<6omndB}T-51h!QJ>%CcW+_g#QKE@$uT4KfarKU#dB%I~orPxI{G3s?+{1 zksY*tsP3^inL#(nsIvGFss%@$=avxZf2yJ_%TnT|LRG{o#*4}**Oz_<?{U0Tm}_re za4+YDQ>1@g{k&q>jcGKKM1Q~XFa`QHOrWTTg%jQ0b_um+T)v}8mSS_(0vrE8Hi6x| z-NL385|o-}zq5?w$S_Q4hL3%=5K%^W&PUgm`K2c5z4;H1WF0w)q=pOOa1VIXw^>pc z+`hd=!{XuR#;{BH33uI7B2VVmA%JTQz5X$26;xC=;PX8xh<^#2BP5;~=0vk>^S~a} z`<&kx$4MfN0f5M(@?Rn$RDKy)a5v-{rX;qtD0LmJ7b^Y?)4+6ghp!X_sxK(g9<T_N zPe#Amd9w?vaMTl1bNb`PdxECyQg5~Syo`bqcLtUx;Sy7O07{pbBD3RlPcj)AYrbso zJ{o_dR$_DIxDhw?06rCLNrB3=NRJiP(C>kvL6<`aigkei$#5)=%nK8;FCL1^daIRU zb?$l}Lw=2@2q%5bQ4~1rv`h^3xB3O&5=5xu{M1ljtZ{Zlhb^c)+Vqz;%hzaE!4opF zLC$8?eY2Xyau`^9;i4H1;MyqS){4MJ16*BknQ?p1g=}U-!<S~uaK#=%@M99kh}kES ziE=q`iOo)k(QqM0XPBS4!*-|9z0*0RNQDSJE`jM&zvvaSV#81Jj~#HH*Yx48L6x7G z0E0oqOfUc&U>9~KQG<oOYlrPqQy;;0t#m$-P@Vjv$78&*DC-k|&e91oI|c%6=WlUg zL9D0Tc0-;VDI=IT6?T+HXv*13>3q@!nI^%9&9xwmu#VUN`RtNjf|Q*Thp_Ng&IsH9 z>oJuh7%M4bCEu`Z`s2Auv~Hnw^Lm4_x9jV}sU8s_xN3ydZXxX*u-WLfpk8(#X|5U$ zS$lLmQaJH7{$ZASj{xkd?LmTOtI9e+j_2PTEIdK(e#an?VH&9SUwi_G|L0n0t2i|F zucx~@i<GieueT6%$aA=J<hK=tZPtV7Fu9cF`v`fr=ipzxBS5Ug+l$8?R%|^FHBFy| z0_Gfgx|7-gs<OmtjMR6H)Nw$)sCZ0bQ`YF-Q=(2FGcy;QBz<enyVETTq1Q#09o$f{ z|9HqE((5ks<n)s>fcf=`0KPutf}OE<)Cp)8LQxH3$J1juil?m$cDbRC8RDkee5L+< zf2AaS@K!AR96*TOZ*0YLxolC}vy{)vHHh+s2y4u&nSn#On*Nd`T(;PKlKGlHaQ1TQ z(9hUqmeivhk>oY-5G`)@;qw;#jrEDH623}HtpGw<$?^f8?^sYU<6?C^6gr2WdQQL# zkyV7@Ibzl$y0-1pOjwjH8^moG==ss0B<-AXpdW-+gE;WsZ?*9f^MzdoeevDBki8Rc zHt=%Aum_L&6?7K*v|Vu;Y^ou8z@$uS*FP-oqL?psc!Kfq)yIhT&IIBiSFkw}vo&XH zlS|}kcOj>}q!U`S8@VEGSoIxc(;x7hjoO0xpP0|Vu-IK&I@@I{h|b^|>~?4Jc9<&` z@gip#{ECh3Wo%T@w1a6yoW@Us!gl5fe{FltQM{Z;RyjwsR}wPtPK%*n&L<#9V-wU4 z(Fq)sI!#RU-`tPjcd;_7k!oUL+yyHN-Io5;sB4c)+oqZZWyZ^b!XH$!S`rZB>(LcK zu_tf-I~k3^3>$XULwMy!MhQ_NPH+OF2@p2|OACWmDxAlOuc3~Q$BAko^9@GAY46ux z#y_aov1&l{ed9r#6LxEVG_5XxH{t7M{JkjqtigEAi%-0#;nk1l;9PN656L>!3rfYt zrY&ciBe|g*Fl-<HSuyG=E^315u!6N`$|S;>q%B9*BxJ_f?nTgcaDi18%{p=Jb*a!m zB{kw(X0?C>Ae*PovQ2@!WUDXGnn9@oRjPF5>kP(?azVHP<UXeVBi`$9WX#EvP(<hz zNJB~LV~fzJ9A)TP-JkI~@pWzY$p4m{X!HZul^poDEB^rmD^+dJt<cy>xjeJ<7G?pU zK)|SY&$wH(HwoM6K@97@c*#%Ip)ADpg9#}Nf0v{ipH=WF<tfV#m8U)B150*1tr`bN z!0B$Nq;3jDK;XW(yLl2irgsd0LXzi6!9H&+TryMCS}prl--O%#7qcx2K%ADb$)Hkf z;KNEkG{|OQlV76&m(~1S70Q&J;xl#n`2=tVidgat^nIT`-B>gDIurky>psqlL>a-` zl(d;Gv}*;eAl>PJw?v`lhL8S}Y!tK5b-9h#7M2xYy=!Fi58XURSI8XJT?-jx$zIy{ zqEn=N^C)_lAWO>RT>?*M0JzmjwCjmPI~j)hQT)BS2JWeYcZB$o?I%+rS=iFHK(4Fn zr&<l9RJw<7ABDZ<ct-g=E<WV$%_X9rsfr#Yg|e+_E(Ho)aB1GDSa}09eXHwn<@ND9 zh+<`{rV51^RbuHp+@<~>*=}oTqHweZ{LJ2Q*|Ka?sG4bWZw9TtsZv`;L(bWuP<^fG z#Bvk4s2xZ@z|`A_!3Q>gv=8+g4)1Lf#qHGczFCZ^6}Q-~gk6R_ZoXV4bEJ(^Jv()G zSBF=Sk*|eXE1yQf0=%PuIOnKo7VaS-U2$nGM8eqxbsXmR!H)Y7vI9|b^-&Y5m#ep) zM2-ggye<OCK$3+5yNXkm-`&*^!-_%cSP23_x=l~lJ)g$mK*MzBGTp0GpNz2OZ}z8n zla9$cx4F?0ZySchhDq+VzZj#qn8av))Uni!-2^c|0nRe4Yv;%z0MxCnW_>X?9r9Gk z0LgZa4l3w!EOa6I>1g+QidZn)I&@7p#y=XV3qq#r7lYziKPpCoHN`f)hhY}3zTCHZ z3>KeH^*E{u@k^B0%HI%n<CU$jnK8fxO!2@xqek9&Su~gWxfj_o{pWdU7KvY%m{Jaz zG3I{n2UQ7fMDYHzPzWy+pE?nENaW_$8q8uk+wjmDfJ<90UrGX@)d_|@R&c(z&U6(z ztxPWLjUF!%a~JKq(h!eguQfHNcAd6Y9QTmP6AJvd>9Wz_q68&pye3fpyR+Tc647M1 zL^!-iBm$hq2F4-hV*n}?L?d4`W@U7i2LEpv<cEvq2+KS`b_jqLKH4RSYYKpx2iLf` z)p*4YvOIY`d)<>$@0!J&5-r3x^g`&M2Q-Q3&NOf6XOnx9mRdK#lgxEa(ZJ9K!rt%5 z)K`s!6`(}4VIsSePs$%asux;yVVF6uM~YTSwPJyK6UsPaIYIAv;>|L0ikd5G1Xm@E zq^A24a2jkMTM#K7!1e%C<R%*1T<#-nNC3B*q@d^c9+{Z!@7RBoSp*@<$;?Y3|6fJi zCQsqjTEWd^Y%$HE3$`+C_7B2}7uR(%Xb~1<o`<owrr{~;nSdp;s2lvLO9*KF`1Tk# zk>Hz7?urTI9D@X>>zjIg>Q1Ge;ZlSzwUaBKM6fz%6SE~KDWeE<dx1uq89UIv*?-Ne za=EiPfML6+=9pU!wSLsVQ<A1PB6r_{$7t;&aM}g~=$#Ju>24=tXiy<*V}4^s;47n5 ztielzzQ=M`W>Yry_<pW#PMXbmHyOt!&f7VVMyvTG;ROS&Lj&XDtK)1V#$g#%VU0Ul zbi_+UGHzOb^6lv{;wa3cH(D679eWKh7I?~2qccE6y*J5l!dwE74(?FW_FT{^;k&2% z@pc`j5Q-GUq3bY8j4LGy?<(=YlACcI1*{o)B@;FbNpCLxJRpNhQjc?}!?i}kicDFf zLvnqKBVeAG3Z}6MVO|m|>((Y9`%OQ~t099@jHr+khy@r|@)|x69g6qdjits+VyKC? zyHPu>q93ZFVg3u;1`+xqoajI5pF{EtK9*^(Gb2{_$w=Z6Jrlu&4I;78+*}XBZ>C)x zYfV6SgA@QD>B)WGV`|%Bqda<~0-iFY=Nbu~S{p}F3!088itgvQ-)xW})^9#81lbW~ zu`wK@^cMYm+9)#*6H8@HoY*x5ts)Y<OrIF&q20=Xs_OZpMkGBp>abAe0J%}dNJLr} zz2x9L94r@Cq7?C5_3D>C%mqldcOB@-A1}Xv<%+~Wz7KS)$E(#{GBFZr=lw(1Kol2q zdxPTaNMEZo+ZU+}pHT1cgT}ExrGCuzWNdA|(sW7M4#*$LLx0~CXhn6xlBa)6A)`h( zwt$#CD*knRr$PVw)x{DWDTI8V9X3xhZ%c(=X~4$wLA-)r3gN%`5N~^hGs}JXd03D+ z+RF>N(2~wi9|~G(AP=FZdU3i!gKp*$M$Sk0|KTOVxev?QNGw$bvd(EZMz(<GEc@MA z$a^E)1T&%E-uE%yP&;b<nfMo1WQ8qc8GT^|*~JWK0Y9E?4h>mu<DKI{K@w$Ie@r21 z73tLu#6eFO)EUzNtHQBsU86Z{rVC(n2s;(Fv%xF`FY=jHCY;HYo2^6%Y;cCkteTFb z_@V>Y7aj5_p&t-d?>YsJER2l8k?qvblLbiyX0~;<wc2Qk4YhZn?XP?j!F=?3{Hdzs za%d&PoJiM)1c->|gA0-4*SM&Ozk<Aga1A7>=YumMM6fFkgZ-LFQEt~#tHU*6P`?Xf zr%&+E4>@Q|LnGKsL-7m=eH7Ph0LJ+q)mqUkN8@DBA3->u!d13dJ=Ui-k+P>$n>^bl zu=1ok8m(-07Wgto1;9s&3s1^Ou`LJl+g-2Hhw#&}TAUWL%>&Y3oo=9s7@4<MLkW(d z{K4;(a^q<deknTlvVs0Xt8{VfVLnEXThfCF_ud37-~HFMN9E(rS6REDhJ!U!T^w+C zs%4TJl#`&XVfptr!u9YWS}I0mjIFt=gfP`MTr!AAJWU$}L;8Y$NpYeye5UHudws*F z-K!M1zsxZ-cH+)%fP*0Fd$6~eD_~j#EqPts=Jb1J01wKPP7@Intlha%1n2f-5o5a7 z6yu2>?aOkmBH2dOUHm3ExLkAdTtMZ86P91F?O{tl{MAr_N^(no6YKg<%Tl~W#^ zCB6C=l^Gbj-MM#fuwITS>tZSO@tj4QHInXV2S)<I3e8O&)?;+uq{_72^Qm52UAh>U z0PMOqh6$Mxupu+jpBIMR(MdyzP^!4w{dqZOdi#PUUhg94DK7v{GMO&9R{MPnO$8By zWfA`=znJ!OBSR|Y^@uLFnU4s5?F2Djss>Nx$QHLsR<#Y{a%rl?tqtva8}CM&Z~kuO zp{XF*K&)X7*yA9(_@&_CMx&NwH#~$|TW90Ryn->lTgGr8*X&!ok}vc%k)+D}V_H_t z8nXq5$Y1qi<=NqdT#&SPT1PhapMW5?*fl)l@yZyN4Iro0pAmhf>A|!AHlt&#p2<2M zOSu9NE1<Ex*&#YP1&V$YxE{UJJI>+2Xx(2r0|a#vYi$=mBqmTrw@ikVn>_aVn~_%U zOg>rO53F588H$wE;Yudq2L`zge^B~SCaY|-E*w3MwUpl~X3byn6C^1uGxuY+(KlqL zJmuk+=0<<QR;IcSiVI-L!ApsSsg=*5d+3kMYO=rs;!+=u*(tV#rED2s<Yr2cGw5%P zfWbdL^-j~&9{(WFC*P&bYC@6Jta&%^67M~)zppHW21jbq6TPO15oj3!;5##%(W|}% zVkY%-`W`5`#+R$w)7f4hXzXINO&f13fZUfeNmb){Gcqx2TiUnm3#_<_iT>EE^maPl z@cxSn8YVWfVYWzwdX|XexCd0MU?m$sHGavsJdvvvxlU^q;PG-V=^=a5o2ONh>acd0 zo!1_FptkYiSfy71itgz^za8{HWA%`isTA~ij$?n~#LlL;SkJ~J7|~2IS?}P!UAb0D zzHD{p(mF#)LFs+F<lVWPb8UF(GX0ID7>m!TK6j3r=PKZv$f`Q)4yv^pecOVDr}e+5 z*AM!Toi$4Y5!>;^Az%4q_FEYOrFk1GIBmXrCoA>{C%8D-7C|_d8~!poz3B}vk<N#Z zN?diWN-xicKF%KSt?8vZX+A?OxQW9!e$1qrw?%&N+eJg<_meo}MT?i`AT{{=0hWTC zND#Rlk^Iv!xa%>SD5(ycMUS4g7UR-cRs5rqmil=s*NOi)$9(u+Tve%O*c!GyBexSk zGWYt|h>ZXFr=i|g-y<vV9}X%&BD>U;#2@C!)P9|>ME<}Ta^95!oMgM3k>nzlRoLVK zBjN^&M(HFH>-jmiZxb?b)@0wVSTu(9_nN;wSt*>9q+dK?z7%7%&3;|X)?dlfHtioX z{tY~(izh6}w5-*VfS<Q_ArIc5r!%>A{75*r@0+=`6-Xc-MUUyH<t#6MaRdiE+nGSM zN@L!_IMR2T6jEfhev`55VILjQUM4&IQc<&ixb;Gw%HDnj!cL)GPU?p^+6?xemV+`% zovI}M)|xnJvoyqXMqKr29Hc&cE`!fL+s|!uC(Q_{R27&G*FJf5Uv6Si4%fF&IxCHG zR&576;FM~#D6OIkE2tEiL>(z#9w(DheKd*2&d){tx5p-{V)FuuIEyaUv7mU769hNR z42}v3k)W*^Zd$#(FqjQl6D8Xj6)Vst`~6V0ruH+T=w8$ZA>d8&tR<WU9f7$wcBFDd zK4^cTeo>FCEFx44ycF@OfFv4@fG&$m9`w$W@$ZdAauCw@{)HrYd>~`eERq5eDM<@A z1qqoh$5U5h*Wv72bwr$rAj4oeb+8~Ih-Lo@FlwPC$ArBhc_~0Fh*u|B?met%!t_3a z&le5wl>}M}bc*ch|M_0NQfY@J?#b`r#9emaoo(YH#?jho=THbdA;8|a%t8e3uBQT) zJnjPM`gBh2B=$fecc{o78)O{0Fv^jK@euaA(b`6{Al9#Zj!~}k;jd8zvagWFSfJj# zZhU7nC)lp6j{wW*@n|Z(T5v-sao2t%aK~?UElnMO)qeWDk#Q+#9sO#`EooG$R{VNA z0Z6BJ58U{E3)r8`xoWwJ;*#wt=nQY#?zqMEijRxy3DlRLP)eQxpC!i^h^EH&2cx$u zTjP6-jc+Xr$!_jdXwq-~?X%P5tcxZPFtK(CYWhq@XwS?NY9!mW%lp3xlX^w-?D<(k z99@qCQ72?U0L#$;IoRwEW$!DgRrI2Gw18+kkA4uY*Q!TB$0<&^xF+fHWPtC&$@60$ zdM=zCG}^zkWJ*43cTf@8Iz6$jEVxSw5|_E05D#!~+yMNenN2iFkH{+63de>g&wNc4 zjzDY+<t-Hn^VO*uG;b>QuqWOT7lT!L8+us)^jo3vG9M?1iSc~cf|0HO$>0`^y5Ek? zlzp*Sp^l~EcS9?~S8_d#r8}O-_ib)qfM}(_1Y!Ttr&PB)%giAa0hPx|#>pa%d^&yy zI%OpbrXSz0Vo8qChlJZT$MM?63=cdHaPp4@$h>by*5Q&gmI#^b*mcoa;llc31KS-K zrYXnmI)nIfGQ`lTa4KQaPrJWd+OnGIR`wQpBQ~oBcxf{=b23A<1<guIf|dc2&cpz; zD<>TDQNvJ>e9?>GY9}w>qUvYQ%8Wveu(&~3T%>Iwv{W6720o&h;THD%^<*<ksQtx_ zt9tjbi7aLuAuQ58x(4>CP!K*)wQW#MZC=Iew=>br;h@)xk1m)eYA}+KdeHo%H<H!N zV;u`|aWi8pmgzH8+sm-(yiq^I(&y`tQq!_nlx792G2m~hYNLKEauVYKlZh9K*}6Ro z4`)qYs`E$FO|9qBSHvAf!HOJIXCrp(qcyz@mVKk_L$K518l~DhSC%o(-m~RnKQlT{ z;C0%NlG|(Acp5LUEbfPI4x1}bcnsn%N5$=IOVm>q_?PQ9z;$bPUF>4?l|wZBzuAN^ zP|NP`?Y`Ce(<~Ol7ga!}y%csur&}hkS_|YEi|om(lvC#_e+P90VUzHCW?Uby0HuZ} z3rAYUdnZ4W(eH5rB?+}+5J?&B_jqbDp~EbA&<qs)PUp1A%2zw@J~YE%WlmqjE#X8I z^|F5FSVt2(w$dy4hLXvWb?PXabG=Vh#zJk)NIq5o%Ky~nm9ppJO7_JiMy1<4V`rU> zE{>O4d4{e09*vlxZD8%!GaMIn*M0A?B_XRPulcoD?mS%)Lw<Y`hrfL~!BACSj*772 z4UzusZB4XwTm*5XSZuI=ME1%k=uu%5N-(0=Ya+`_M>&2f{-dB``}^o~2Fq<L6?zb8 z&L4}TCT;bMTZ7)`CzNk=bf9wclQ_wAiSL#=q)9?(M$te3_l0D~I`W1CM=eCY;7Kk_ zu`IYG2%=xSUI%F@u2;G~b)%00-SL1an6f3GS=^YsLGs2Q5>yXO4%Nb;LDq~W%l7WZ zM@WhbNprFusMgE?Qr95|Q4-yBt?RDEqR^h_vmswqRpt*0d)7#2cFH&wV#xp<1s6)J zw_#>yqUs9m97~ve`zwLsN&h0!(P3H(lo?0dB^$`lOS9}URn<ilO?(?j8SF{CSslH3 z!Vfn}rDJH3+lU=Xr}hi)omepPhEdY#(0N8=gOVjN8@=O9;^}hbd?$r}UweyPU~&@5 zpwg-cEXgg?qAL_M+3)5oDER?3>x%Itn|gGb_#8^p|H<@kSa)I|*(Db1(x2hUbzyLE zqfa|;Yd0?ArMWh5niWepSMjWXVs}cT4Mam>2Mwb>!RAKqW^YU;C>R~CGK?(YpUr9c zS?3c#;|Ql#^-oI`2cN{d$IY)1*=M~=ymKhmo-=oBS32ye9Rqe~KK)EiJ9qn)$gOy$ zxp>P&X5B6a)E-cp^m=71iu2G!8RJvg^)p5UOUNK9UFe3}$TyVBZnWX_m1{MCHb_-C zPRmp37f<EDyv}m*#^JPDDyGXi*HUDIQzd>idkVu75p@X;dwLtC_+^av$Yf%PEAds> zlzC$NK+jvt8;EriknJKC%XZAzGj)O+mwdChpFF1U3}q45Tae<wqrN1OHbiF-6q7`p zj8KafPb2G_BJMPWTRbeNh8Gh>&QnuA;90L?+RS<QDh0_HpKcv)g5o_%M~5uCZ}Yh# z?8=qIN+u;9@EsD%`|o4LL9Dy2{?|B{qE^Rh>cz4HEeZIFMCOoU3{i;wM`H2Ko}zs{ zSx}nJ4&Ei<s!S>{T2cV}U!srI9<Fdn*K)@_cZ*Re@(w-|dsONe?4o??eUe@vos8Ox zob9-}C#8f#DW`xdFv^#?@Z9B8I@eVcA%4vU8Kcq!Ly=z=(H43y^F~0YmpsAxZCXhu zm8RYAc(in{aTvu63;}8Z1dr94>q(T&wDcjqT9=|-(7<MvJ6heNcjgy%)ST1%NlK-y z{GxrP8HTjm#KRhEn*sOLA^NZFXzILeAlp~Ivw9F#AG2Hp;BjInj{vn4($})xbp2h6 zuxhuO9TBl#S06t>K(K6&)+Z)>?u+NY#`F*K;+T(f#D4l`H{`{^vbv_Vz#uag->(YT za4wnQv)qc!xaa}LM$K1X=hdsO?PP8$k12SUt44Oee{bp-3CSgqB1-m>ZmB~uO3MCG zo9(r4_Te4aC_sgjULztTr4p18i+oIo?O@JHl(k)-(CX1O24$}OP>~jw6K#T~TEZrL zt+$FT^_%sMJt8!!ibB9v4<^w2>j$O93B;N-?LE#0{f$xhtHy1^E!vwYFm?2B8;`Vr zH9DPguoqGs=sX316MN|LvLt)Kc{<asv?*T35Wk>AIcS^kl?Makv|U%IJ*4CqM8A{% zA>zo+857gWp=y}z9FMIG7=VIb3mk36Q)`P<Yc4*s_&Vi&{4GPAO#-3k@)IWyyANx+ z#9X3!3Q=%2jR3y<55~bV|HQs;pj2AICY@W*l9N)6iCP8Fk7t5ET+6eZ(dVfSPMGRY z7N?aSGV&z(t#MSjq~4Bqq3K4{IQr3n?UQC2UMf`y@0P07a_dYd8}-|`^f4$|>#}AG zo=YGtD`r-CgdSa+j?=S{UXH*STqcApoFwsNOWf;@Rg_sv4+vy|I2yM3GPSmrSlPqp zIC^Ga-EW^7&vruQqY(Zct*Ns(q@v&uW%d_MF)albgLFZQI&cX0aW`|ObMCs?lhXVo z7bH)JbP`c$TCJyf7Zy_Bk2?m8+lm&-eyxQozn5p;afFS1h$Xe|)ZO$!+~*aCqdmf_ zn`_;R`5Uciq4OrU*mh+GMklbbb6l{Vn64^!_Qx)anypKLrGR>pfonGX(aKlSru50y zT4og3_5|#~ymB^FRHt@Nrwfnc($V6Xv1kB%>ran2P9-o-y7K+x>sdz~A<=K}pj^aK zUeI(n?<7oeZz6ZQiGuX+K>yNDi(6jHiJAkP%cAVP(X3fWAS=vn(xn*lw_Bczq~Th7 z@r7PDW!N@-1Im532TVnuP4V-Nmj|)Mm8H9MZTOWzw<=(+<hzEwbb;p`MPC>$H9tlY zA$-|ojd)9Zr9Tq{*%pr5#c&bP?gtu0P*1}_$Cot<O}r(mp2RVlC(`i>?$5@@;Au+3 zoqJEu$B{7rwPpq<iHLiheKtzF$>mC!DiyXtj&6{hBlZ}q+#cGf1%#kh5$|JX9v>KX z2uXq_chAuDQ$C+et8N23k*58J+>zzhgnympb&+jATLOTnUlsx^M?5>*PFfK{r$Ndo zJsUT{%+oz;)8J>41`VRYx{J1YG~AH-N5p^{25p(YA)NNDdwT#NE23-9#E2$oA_7f| zG&RWkhY3C$oNJw7W>E-PdFh(=m(R{RT~T@3FUG&GeVqz%cgEdZ7Oq+xPoB_L5wSD3 z^8m|+Qw!8;oZ-(}x#%h31DU%0aGtD7xPEn<IBc@?|MRGK(K+!OxseT>CxC$kZKyyx zyP@RS<>I#`3Se<2gwYv6BS0$?fb#Hh!Hgd9CR!-f4O=)&ACnnpyNQS*c=B8aLEdod z>6h|Nm`{yBI?2^KveqgJ9$Z6V3EH<X36aBAtYVJMm@O-ZZic972cJWw`)zoJU~$zz zZ<xpgl2A0$lZT6qJh~L=P`X8tj{4*!e>Fw(*(M~5X%ss{W&ZCUso<mDeLIdlG7|KW zy&*wyk`(zOH7y&A9hz;+IYJ1~*(kggoCCk6{@wj?>|r@4a?4)`ZO@u{lvJ+;`<(Mq zQb5mxEkt504mW``nD+$zbG8}&RgO!-*ZV|@s{`zk&L+}};+gslE^Z`-_Zdz)<`V99 z6F)=U16Dg<n?yCH@>2i#Ts*}%iOp{LU<T^&qx^~=7{Nl>Nk#U9N#$7(X@*pauNAq9 z1X!s~9dka|TOd@0K)zlx1!n0sw=L18M2InEa0r$3GQ|0mZf9#@7QNuG*^!b-?R4f@ zVdp%e)qtAyUDXUzIj*VzJwU?0KK<OREwqW&1=`$6Xw_T`r)MO7@LV!%xRQq}0rr+x zZ_u-vbF109ot&EF>o7B}sH($k#@XD9Oj7;oU*Y}0yn;N3O6V462{#hRj{y9a=G*!f z9Zpq!RqL=fw3uhaon_;+dLB^R+(~N{3!gxAw8ltw^!T#YS9%%<cq=G|3L&5u`EW&9 z2ym`yhxQCh!ZE8rM#Cz8AbahmYhB@OQ2vnNnJ@J9*-(RfFPgJCUkuEZ%#&)7*Bmdy z0u|nM{zQgqp8OgWN{QDJ6Z<k_)yz+OIf?mdMv49!izHJzmUJ2h_J2HRr#uF;taa35 z?xQ>iE22wNx-p*7$Ddz1`0Zu*VGxK{^?KK|np+340IxLNgPzfE#y`RyJ~0Xoc76vW zNOwk(jJNKHU+6L77Q}w+tM#!4I}cJUn{;<HbBiU`xh%PMd!aaYYdHFx^aY3QRph_( zSauhCw^5P&kA@gm=vOjdx!rfwloIoTZ6RxfPWEn@XYMtz=P0V2>woLu$s7r0C#uWW zh*+G(-HWELv%F!jZXxRH^C5_D4f*s+fVY70B*~%M9TnlWjGmgAObMaliokN~5XR)$ zq!)?d%5h&L{L*>@WM;7*!ERDYccpamSaR1&H{1EkX4iNP(Fkr)^9Blp{ShrnPf$_# ztEn=J%&xqvlbHpaY;pvO^T(Jn(t4sgO$(GD3v4>E{3n*du2jtpm|WA5S&uTyUCjTI zu<tF+X9VQWTSa5|wv<Fg0t(%F6@sggK*?pBGGW&`+TPl`mr{4f=m%T>MKB1RuVFnw zo!nhHEI*4D{jfYSL6(9v^C~8b*3;3JLQeELTxRW7Kcf+cg}d&zVP0hbJzM|ZsBa0- zl%9?ZL<HgKmT6WuyrOU$&wT<{lFvOzp4Im)>O}pM-~M5xxyWp5TW5aGtF`M%m!9-7 zME-&+tO(~rn&3;9YWmTk6O>U11pmr6%rdn3GVoJ&EY6)pdT_NaA=_*r9H1KuuT!6D zEA%rI)6*lONyOv-q5no3d+%$Y6g?c!NX0cpA6i~#1f5VYcJsLpCnf$yfOpTvqwT(( z8BN^NBptIW_l0Ogb{i9F7NP|*Ru8JNI18YD_jiRJJ@D^NoSqCP*`*0g3}%xWG=tU4 z|J2`bKs}Q4h}!u32TAu{1mm&`KxUt1-9EHggMFkWR_Ku$tj*~$|7AO-UcJgs?#3Kd z56y)<yhy{ddjTpHP?Lb||7~{v<6c`&ELZy%NJwRlIox2?bBiLc2wtI)O%POa>howo zKsLW!BKbnDHqXfD=RmQU=hBl=h-g%6%CLn8=Y3waz|lcO*9?Vu^k@un#Oz#R!*El3 zx2~`d%ipQLqz96^A>5?cl8<NC3HsA;LQmw{t5Am*bvtjeD7@=+xT^w6i~#`g!j7<% z98^e{^okQPz~&B16U7dEm#2Pb7I6&h#&33kr2gzn?aQl+=R|i721*eSqCjO;|6H8Y zA8!Y}hk?n@>!_^fyk^^2!=L#d<Hil8(*;mbEOgM?;_arQrO32la1ETLYMM5~dee_P z-YkNS+uGQuEA=PWu<qqnI^!r_2VJ0ykNUbsAHDVTAtn&V+<}77yeaaogb+t7PNE3N znGWb%lqrU#>+K>?H6Cc(5C{fM6%v{S8tnFVm>9VQJ%HgWzg6&w?K7aOVJxBHHyA*y z#Yg0-*`MRA0VNp$ylR*^sWYmVfAUSzHHzle5bRDXy*-$jV?~~JP!s=#S_zcm@SE{H zx@`rjEf~Jvod@V3#E{S@*6`y^Bo57FjshJ*j({f5oe@H($pl5miZxJM=NQg6bODi; zAwz2Se+-6En(k!m!FX%aWSj*4J|2hF#>{Z<{^=lz35TLI>J(!}S~?Lawtt7VR=m|k z$%R=XFx)kHcg!h05&gAe!WkFbcO%ppa1DE4O#l0>Z(aRuCTT&Hs8I!vo_NOnylB2Q zU2&tm!zEOm+3kZSGqPGazvZlHg?AmYjG#2wSg?#+cEHQJ^j^%(Cjkx!!h*=U3dr7{ zVxFX71KvpqWx*Rk>k+jp81M?$e)~j7r*K1j7}jNiNMQ*-A#dq{wAYn3!@H~Z<7RiA zW`PG<wF|Yegjo9P#nv%BQc6O^wt|Jj$=6#K5?n80<vE7fT+d-J<^79#r<}EQ+52E5 zChpJp$>n9cdyx0~sn3MV3r(fwUJfV56lmii{+1xbdw`$ww@p?ByAh>7VlZf=kDSx= zYn#SIdGT!5smS*z?3w?$v@p9U{`UKV=Ea3x_LqB1j8?<`U8)7d?HjB~#RF1&(-i=y zjYJJT^>F0>@E;BXF+QEh<<(gBDtvoPO!+WGWr0-%1dBj45G-4uBw`^?hT5jpubb1o z$#&Xvuvmp8skZN_pP{xOrk9Q99C^OhmBSy*qPT;NDYum!W;Yqx<dSNYQoiK>L9wV9 zrj`k63aX43c!cF?akP{YIh@sbrvRVZO4cFut&mOI59t!wthv5l2990hM=<j3Jkk`8 zdeXHd&6>O7`B6#^uv&q!E->JZXp?$~3DiHUN%WwCG@AH!w5@I32<e;8-<;%Xuu?E3 z>CX;L9D;MslVSD4CJ@$8<uZ@-z<rrZx1;s>W(9onS_J4m4nH16SmzpBf%NEDss1Di zSLL$PB=44|+7Iq^rUt>h@J8xgO#MoMmHaZTKxNZZYWa>!<N+cD7lduI1Kz;+35C1X z=;_Q>88*O!YS=vN*no?DB4RxiuZ5#bZ;)!5PmFg+&Sb>|($UA5|E>7~D|PBnMX=`U z>LKIvluc673*i!cl(6F*jVdLx;4)mHL2L4w?yjM4)<zA1-l2*I8O8kA;QNy?Gbp1( zW8ccBRG152J_EvPjwk$*)$ufSezC;~lXmo{yB#tqVD<Tn;wpQ%L&(lgM~u7!&~a_4 zsi{YK=3YY-6xcjA0oBK_N2C~P-1t`W9-y8!s%^7zPkskpGIprcfO<b1s7B^bq$M9` zDJz3%n1+Kj5dlQl6d^8Z-^Q5?R+tI?q;QUy0DUhncDC|H+0k5hL2MzH<eCr5ND6Gn zO12lOON-F3AO5zhihSI~@l-D081VN;+_eZVw8p|>E}I{YX_Y`9epQ)gvx{cBwbc#V zhOC2Q0Gmx(^$w2;BhWrb%#rw_wBFQ+f7P^J7lGtq%w33RiK@vWhjxQIE<As$2CC6A zK*eV7%;oC*3)4uicT*A?igM7I17X+&)f%M!Fu#%TDA@$1tR6Ay&LZ|tWsbDX#?|c- zG_yj2U!goca?f|E$4rFgfj)9PI5L+HZSJ}Znv$uYPCFJw$kfmcrp^(quW3i~1D$%L zhTw)-cyt4@ZI&4`t)rlJrf$sGPUsb11=3YmgYA!t&7E23t?;O7CuyV%W;&kKo}4g| zNPg1S!naBm4b_cw%Xj~alpxa!75q*sLjX_jNI|;DM`+S<<=Za_82ZkV1zJnkQ+q_< zK60&}7}?@}mVjzu>Am8_p|T+~gcdIbL<TI}4AxhE<3qQ^&)P+pyE*ny56;5*9Omp- z1s3*)dKEq&f*NXK2PKp;UjaKe{86=mUI5A!G$HsogA3Jcy!C|Rb+LBnE$%8aQyYii zf_Lrexq=;6&Ti6(QCA#&kQR~`iM23gm96;lf*!4f3u_&TGZiFUm4TT&AF83&le$tN z#J<Mv7egU23S4WiK!qe_ph$5(la&ty1%RE*#FN?mWa}*O@YTFNyN9g!Q?k1p5E>DJ zsly9li^u_U)()p&c;k5B4{*XD(gQ0(#^iMwgO1|~ttV;Yj$Rv~kjMiHx1>JPS&{WW zVr<xH%v!wAI*~U)=x#^8S~v0ogQ>k_;fP*MX;(xUIKW5&w8RBMf-k#8KooiVXmZ1$ zzv1HcNYYz}=3cfM1b&2c&yHm9ZD61_EOyaxBX};K8K3bx(zl{-jzT17=2GAmuL3l6 zf|8?SQC%N)B!`)-kJx!U`(1Yq<cwdkh#lV5KQk$MH4L*ltPJq{ZqR7G9(j*8rE2<Z z;o!0bSp@R7Qf4V$pSZDt=cQi>|5Hr9WaogkG#5Z4^%e%e!utu$0|!%y_#CT#5S5>) z$#@ZJR4c3ad!fZ9TjySPdE${ZXttL{FHp$|`i0mi`(u0vRw_-?4;BMDGpBU5f%8b6 z7j^OXqO>jGV_kMt*OMXK&86uEW&Sb_;4<B&_UCGa)ZZQT44@g$#z9wfvRq^=mnf^_ zq$lNPS5cBqM3|_h)%t1@CUs$)x@`4;X_&@%$!o<a%Rf&b@rlOL>L%oKWsA3UMcld? z+03;t@I8mHPx?zjzC+%1Ux*I5z&uYU*HWcSzltEa@K|)#>1tO4q=?U|GCR)DdQS&x zI(PB5SYn)n+_s*p{SW}|s4c>1W*OD>z51}~=xAXB$xcsdNNq?wCD<x(;vM93vtTO( zE;bmh`w|noeb`DJt7^7Kr%D=hIsx%OW7gZ*?vnCHRAYOMYE6*_Kj@V6k8oBBpN@^& zch?o6FD2j4VfLznzC;>0+}3{1q$jRij(Sd`mI^`>_XezrzKG|_6)p5ao9CFei;em* z-TYY+ijRAhtBf#}wSe=KuT#z8`nl9g5oJlzq@3^77ZKOszUaD&P~ue)K7nFsF%Kz1 z?pRXheZ)522Patqi}>7wAjq#A^se-e3P^JBKZ!%Rdq=;$2P3o?3Yu#_AgAKA-Ek#@ zn8DDxj~@|Vf2f<wapcv1+x*5u45(Sv*p!O(VS7UorfQ_%JHlJF?uh4->ImS)w#8UJ z(icqArY+3o#*{}Q{|)K3qRVy6=+_0l^4s@gaAXJdx*qR>d41xSg%NijKIdr<JHBCO z_`O)I9gWa%Owbzio#5mr_jO3yVOCtLtg={Aw>}IWA0SP0G#BGy9B)>7!=4DFJxuIQ z%<M+AGoskS4hOBeJ>~l=-$~7wrp4wcU?^Ap1!4908QpN7qo_1VWx)a|*^#BKcYmoe zE}DegI0h=89==||wd|rpWyh16w&E(XYHq7)MX1KR@>u^q=W=r?*&Er#G(yL!{DH+~ z?vj%r6?jZ1+%O?6?Ni7Yu@OQAV{N%=BMMj-?3OdG+8<dOodM>#@~SW2Ex78cjM0n~ z4>)8jPc~&Mk~~Ez3Boxbo{%;c@YB7<x~j?S2}Qo#M5ejibk2eRUvr4`Q7kboVmFdv zIZn5w_q$=J{Qpj%RzrKVX%k!ROpk#2n8Ap*M>c#u`BYjC@LW_Nw}cQ$^n{Zy3Lv$B z`-q0Z_!@}lHO9evjbyw2yOOysBR)W@YUyT^w3j)U;s8v4x)crF_MzigMO&tHg%Mj( z3W<s?W(JJiFR_bBd>S7pF_fD*S3`|uU~y~BMWEI+S>~P2c4xaF|DR|a=^fBXt+YBA zsm?9(&0Mj2nTBRFr2$m5dOA-(3`ngOPw7K!RzOUxXL%`6VxunODd5L1JRsA_lQBux zhr{+taOnJUlCNpmHWgi{02J9_uU_>;G<4rGOw!D?{YAJ?u;8{rD`x4`ARIuSNs3D% zX_<>(Q39+A3|E;U9_UDyi9bTlI!+$Sw5Q^T5$c4CKOTE3{_Y2?q+qHO;<uw(Pt2Of zPGmwe{_Dy|Qna*0qo~ROm>iU{+7Az<g}%j*q~Wt2!f0!@T^4dFc_ioLM$|mW@Sf{9 zr%&UN4zL`ynpO~|oBjNnjuupT^Ni}t3{GI50jVJ^$#xs~*y;IJ*z&=;I4JAsJsz_Q zh35?4#>2v5z}hv(r2cPOt7wzVNnDI>m>RinfC{(Q4%KL<h|6e&#!9_FojP}gx3%%- zeR0(*9dh<y$5G3EsffK9p28=Zh%ACT9#pNEFR_xG9=|^%Ok^ee!J#<{IJQ3;1%D1o z&AX)O146gemjLm-0sbAbt-jUZ(iwxlhjpu|H@K#g^^*|^?m2Lq=A>0IV8uma=6QOS z$c<~5v2RPC(2)fS<aBi62Qy|#+r#tE%q6{G*uyIIIB8HqcQD)JQNCvTG10k8EI$YV z48=j!8z!8m_?F&Rr`%nxD@)yJ;jzh}u7Ow4>OMC`lE-OY{1M#SiYO265%6@cr$zZS zg<t#ZOXd4w-LGB*<1IYLa><D-l`MTyu<n|@qvWDjhn_=^XEOF@;SBSd)Tu0rJZW~} zbn{V*4E^E&OgU#C;?BcO9A`N*4jYNryYO2Kvr{4cT^NrpR86>?nGEy{nmt`)9F<%w zkj8Z@>iVfiWB{o5$jOH#$+Z(-dlYe0|00cPw4ilaiHRv3w(XMobdyOrS-3bq>eD09 z1x7|hcw}&+7TvsuuZIQL>pPu(Ae_j6b?)yEDi(~Z7Nw;~#7o1>`8_yXP>uVKzP<OD z^}DX9l5YA|X96<Ct~Ts;LA`5Sr?`-ntC07*-_qire;d>}WQl<DA&ZE#75iBhMW%ev z%{!@kiLOja8!<!73-;dj|KRfHYBJK0c~b2Ebh<E#EmRPz?JvZ?0#`BQYHkZC)=GzP zu_A=phK{CsVmj#o@!`ZyfFu;~Mb~)usD+0`o6$BNJrF1|x-iQO--+dREQ6)eI%g&q z@K<G6OP$gIWLa>B@f+{>HCaFF4a|#qDJb@Qvm=ec4<{mxEk;CFS*6*YDL;tL<>8a{ zZiSVEH{czJprlMzd-33+3jrpQN%dmR1e+x^=pM96nED|K^UH$gGiV#xctF=V`~ah6 z>zVdZ#~|gLfbQBcWNsug-v+Y<Tn&9ok$Su(0Wgk<(6V~Kyd-RUHTm(RAlJX<tHI{! zG3}CA9>-xmo4vQgH}SR!d0HRz=3-g_9X&G9suH&3aO*_hDuna&-7dhUjZHozUY(L~ zUhjr?LCknm*#iRCaZ_VOd-Ze~=lW#f9ct|N3oh+>wlaF?!OBGrv1HWoJtdUQtZaQJ zKCy?1HNJ^-ck|NX@Q-I-I{hjz=!vu1@_kr^1C+;*WCdmfL9Q2Jef0Xwzfsqt4Y!NY z`z_xMZ9Fmi*HkXdD)lq5KWG&QcI|aZNU@$Vyssb(9N}2I7G_=b%)5i_we9Ew^DFl7 zk_j#);(F#WdUO{zHvH6h9(sy^)~+BORN%;fGSbY6G2%Ge3i<{Z(6g8sY*N;FhzY}{ z#AfUP5_hQgi@e7iPCT-)BF%}WrLVQ)))qX*-5wt|etwbal5c$$s8o2$&tnyv#2-(C zTY2yViq4(HuI=ElK@%x}$tC!f)R!()%JQd(d(`Blyq^%h!Jz6uoloDItwTl$tblWK zYPtFLz6wOC)V<Z980HF<?-!ruX8UHe9_lHhA|)jFn#pGwMcZmDVfGOS63Hz5#c0kx z{%Ft2b<H}lz<ymwTQsR9qIxPh7`_ti%|wVY0Q64fsZB>PMKHbE<_BYxXpKUiM%_@* zV4g0TjD$4)l!+?@Kxs5I^s!@1#?trSt2hts;H!mnK_L)*5y3*U9uEp1$*eWf3E!+q z%MIXq%<xACpF7J>{_R0;gHnbU6kP=wuDIiXO6#v8oS|KP^+cs1BBu`nHF<=ui)E~9 zlYMi?x&{GTKegb0gSnHXm!4|O*C@t)*Y8RyVsF#srA^f1`0f<*m%sL!M$>uU#w;gA zFxl(>>w1pj%s6gkO4qB!U;5|!#UAZ^ZG;tUM>|#l_tLuvWVAlrrU8^~k7Wi%jL-sB zHK4<vd)7nNL$ztGJ>j6sDb)qxdS$MY;0u(Fk0`Gn$?GE+&DxM_Xxc_&>$U?fE$Q=( zrr75(t~0mPeb7Rf%wy60^zcqmGmG^ROsRf{L+qi-?Zg4SdD3!~9}uPR=G`VZ{(;aa z*9Xo#J^=FOypqcSCZpl<bVN}Ow#M*z>3oQ`9{2({0D*`Lztq^oDDgEQ&ZJl--Y^;# zNYwV^yzeA_j5&A(k1^D1*Sk#^AJISi-kueUG~Qmr(S-t^NmQ^%&DcBB2??W#{(N3c z-9UE9OU4Zuc)y@gpM|yrV4$qH+-aai{cm7QwC|N;h~Ii!kmDDQ-T5tIzH;u?<eX&h zK<@&k&Z{fXP#{9<?`aA*5u8{Al1JYQW%&Sq#zGk~zs0jteG&Pv)OR~Q_KeCf9{?rL zahp?PXaSLe=U<_n#*8BW>^cTO5E>t4nr;Rj78o^v!b5r+N}ncDi7f-qyCDmPIGMDu zDQ~Iy=FX8(l9Y5KV5GzCojS;#+N0X{cRV%?a5!ILSOk#j^WA!)jRC;Uh^=^*qR}fF z@o_wT+XrLpiSY2ssWT#yJ0<HyP)6@;WW*Y;3B0L1e2A9Sj}2DC@()U0qGWWl+Ox2` zF8jM+6E$$#OYkEIzwqfLpq?IVVwzq_{%7BvDJnf7)3m+oDvzJ5A0%bjfM#(oCdp@i zxPf?nX=Gnd=2zEZ=_WWU`*mc5fZIZ8<!?&V{f>+_ZYE^tJuYQhIfWwwx+`Ufs%k&0 z47NSI$3|^@agNZVOtne+b=bq_Aq`v;6{xgHdf1*sV~7^@FDrOjQgY-4+MfRVtK*5z zcan~+LzI02Up+;AnsCJ`80eE}2ACXuNt5Qi!+BWL?V9Y>t<{pS*&Ttt#6xM5Bw@*j zVs7x-`W=U_5kU)4d32pEa0;cro`L)j7OxzA@5n&{#;-itM0VqBc=k}r2yVzO_<he~ zqu`hSvJs96tcOjhQbtO1{SVFH^?n6PW+WTI-E$V}n@32Y&it5rBHB$2;y*A({G0nB zfI6~~ntU7uIjF{@?U-pwt3ss&SP3|H1F*`KV=m{SSh@<26YjzabwKr@dTu{~ed}Pf zKHSc(Lzh`4n$xGPkfN+_i#j3Nfs{hw*&K~!>x~*HMD$YsAGbWuD>`Uzmi2&I8cq<7 zH?gtF%R%D6MvEl@)NJ-O!g_g4XCDnM76sRG>}IP)1N#%>$?yeJE&qHoTPiw&JA8GC znBbv=6ISPB!{Py5Wn;kKVdBpoLAQXZ$q)`&i)9MksiYnVJREga#Sl5!EhL==j;B}# zaeDtHOH7oLBE-(|MVuN=T@Zi!M2`O;clX8;6Q@T)yB4-vrI&x@J}EnGsG&%{a7N?+ zX6OEHxBl2$AKs-E0Kpb#dYU)2<c(V})f}w;tvL{iiXfOWJAN%#p{U5~NW78OA@JHE zuL+Qy?Hm>@F3spdiV8RQs*@SL7V>5I0&3Un1Dg)3(?Z>up)#*&!SM&;|3I`^_t-l9 zCtlcFnY+TJ<FjiAG%gB^R=*l<-hp_uppinExbiAUUKHUqh;Zm}Uo_u-k33PX-3^tR zLBUZN-?Q-bH*Nkuk|{mdAlmxXst}53zjwuQuX9Aubiaj8s}NvXyS8G32T+L-X)tOe zVbc-7xQE%mTjXF(V8V%&)#sgT#-7FS=taJu;_&OmemIt?0Mr-jpakTkK5_;S#8pbv zzV;<8nltftFisV4IJs_3%m9D=wKyTDtQ6n!9oXLjO<KU;a8B+<c{}&R|N0!}<yC!T zdXU@Aa)3%D+$FE!azP`9q{47l;Z2`}7L{Q0O3i4sZY9}t*-}3gP%q7|8Q|_+t)tpi zXvWu1Q;6gjfsPf3^;^-3c`j5LWQe~kT~;RFYQh%B2PI56&j_mVhA`+EMd-v}EpVU~ z41Hr!tX2S&VeQ8yHgS6ZdC4{wI)e=+{~C(^T;x$2#2)gxK&HqCDzw5Ewl%(R{%EOk z(z(dMNcpy&S_-bm`E@6PRNe|eps&$6w@lJ$LAE=o4hh<6XxROz>2Pifsge%0tE|t) zh&@^`Jl%JM$dC#1L=l0}$N(^4liTaq89qd}v?CKL_P>j_;~kU6HpQF>lN0bw=UW>v zQgG<I76t^4?mmesjRL6R7bTv#l`6VCjoXr61j=sP0{}?C?W%Lr@<qu`F<*eI*o=cn zke>cjEEU<Zv1D7-d4ot6I+i7~1gahSSt<NUzOE0bSP!t=zH!*sf?CddFT`s<6=+MZ z50%u>3!5QC4_*RX26w?LK<+|uaU<1eTop$445fVxmr=`n^BU#u#El;F`l_FSqICqG zF|;Hr<(8h4W}?tOf8t8=g2SlbU$H@I7mWV8b(}b3;V|YinyQU^76=o9dMKF<2g{?P zkt%$R@NFEX{@e`RaLDjwcH&J2O{@J4wk8x7gk%XAIQ=_*@s3WzRdM9x%elE4moy<! zd5EPQX|N*(A({W}M;8hBziF%3Xo0)vh>bK>vsTrn><U$Hgv#QO@wBGziF(#TR*3m+ z=4r^(dOUprq@kX;w~9!i>WX32FhVqakk~hs4VC~4l}sIUUaxM@*Cg_~e0{y^`lwE( z&0Ha1`Z+c(!E0uDA`Dv%abs6M1TV(|)3#Qt)a4NRqz5lz4V=iw+siRqT3imuTs6(c zJrx!a?P*Lon&NO10kIc_kLKvMYgLhyoiF-U|88h-5<C|T2hVsVx7>?{=GRcO>(lIN zBYk(<<ADA?YxopmVVGm)GkwJhU1lAUO#m+vUOs?hRNn_H0W-2s$CtZji9lKNwd)sl z&=no%ydb>($dMPyXcm3tRR3%wts~8pmfNzx2KQV{uBH7H4q3uI_Ig<Rs$X12FkD9Z zcdK=(*L{!zyaAa1x;xj(GJnZPc+wPncLUBO4MHi)UjMrT#<qe<`8ECz_V!r^Kh>By z)GnX*C*su^=V*c7vVRY?+9Hx*iU@pC*<LE5S<|>+-9zc_c$Q!91Cf*tp)F9H=jCo7 z9Rp`-k%5pvYP<+u;%qoZJ?bW|7pOVPHJlEdg_$({UlOkzA8zW`32=Hm{BS&pjJ~dj zm3+C<F1%fi?msh98|NmN>l^#Kq5)J1&zuy6Q|}<tac&{-)N@U}g*LQ(T2xqrG-50n zx^kcT;@(p3H!o)QravHrEl*23VSNdBj{!F7U#={BMTaN|d4|E_$7^C{W^eKfn2EZZ z#K<wMvr1>eAmuY%ECUB|tnS(HRDsW0lM7YS>W;4EG{b(z{hI_6kVl$B#J{vEgC0_% zluw}9YD^QTcTGZFY|>hb^jwTi?Ui?zKQF&N!WF1ORHH88_v82p*wp<3hyr?A95Vv= zD`BnUMcG)cKnI+E9aWDoH%F!lw!7Tbj-}Af2Sp_I(NyG_*^MBpBlYLU{I;eZM>7Sd z3ZmR2nP)+8#4$eFMOuBwtQ_}aT!=67TWo-R30?<e`+fV1?Ha~Y2+*6%X{Rbx?r8iM z=6l9|%jxOK0Ll#dp>hZb9b>2Ah~bgg4K{~MgWn}0=4}$%hR)A*d}hh%pu-kkokkgP zyHuG1hgcwYjAdKzC)o^0{cAwHS=33h`-HzC_?K7kg)b$4r*`<LQGu!o;3+$IS=|uF zUjpAXL=8$gSK19xTd^7E+02oIt09vQEi8bQ{S5Q_CquvxNGBh429gU*a%hL2ew6iP z#;oUuo3@Bb@IQF6F+*O-21Iv)*xbjal#`dchi|TAa!CC$SnWuLUA#nFtO~L+RUeH@ zQK#k6xv<P+7rJUsSJe0>HImTi>OgrC-eY;!D~9oVO<5tJ#-C&Y)n@E&`e~nfm@W|@ zlWPhN!Tx`%73^<1V5mV=J69j7G~LXxq{24Y>-mjzBF@qm43?e(m{b8MF*q)=IRM!w zwLS{;DFN(h@Pn*DThCLWHOjQ>sb7FMB4|E`W0%c(+opfI0+cli%1{1&x(AJkM1RfK zPpOn<g!AY=i~TgL2);Wqmttfo7izPe*lG(GK4X?f!`A{SZgcXwhqs%@1((86a<@By z89lBb7tX~R@J7~<vH!cuIuD%u6%u>~3BiD-R>jT45XBJVp^cVR6)#BK?%<azPJ5j$ z=m+uBy1VMC-mI<-wOPmRy`UI+nX$dN+SMb9owVV_M-*`OFplE}dYe;BpnP|h&mr*` z&6*9Nn~cCOj|#%esX^qy&Ke)-fE2BSwK(9fA1R>y1uO-HRF(B^qg7roa~>be;cvn8 zH}%-{C&Nlb?HtteIihu4SV=)$Z0K*wS9Dfkmw>llUo(L(s|ie@=wS*u`Mc&{a#}VY zo!>8CGKaiER}7w^5bl(rRhp0B$-Ur&UM8xfDYaz7Ldv6hO*BRMujrR$3mZWBUmr_t z4K8DOiz>vS?MlQ*{B8-q<`X;l;k*1U`{@ccXi>5=1dQC%==m*wz(-$_C@+AVj@`;b zXFuyXclnv&FfD8MjS)lFP;;x9L5=wJ2N~Yeh-YXY4y3`NTE%J)Z348Nc_LE!HKuC` zu-QfJ$aIOq3VPtusP1V|(!6ZjNsbZ<gzPl$$&*5ZsB025)2C+i7P+HJ0#1rlvsPz# z(=bRtMM1@VildP=L15{-qBsXDye6UYq7w9&5`IU5DoG#)Ph{x2M*ND^iy^ULd+^X4 z7(bu*XzIifUM+8cUuwt+>&`cOM^ii4BpD!F?z`&JmA&?Q24fI|dGm<#+s%iNQV5!7 zy>3Q_eYH#ytEOB2I2`~p5_*zqL|~jxtoi}dr;y$R7#G*y(@nYh|A}70tMj#gh=7=B z+E}a=_wuLDv?q_?_vj6<$Ma|>d~gc}O0a+c;wcW6%|CKww*#nuuKxkT5u{)~5%k;s zgcXo#;>Wnm9p2AFO1*mkjhkX`yao2#YV~?D*BRX7RYHJ-Y_QjGTF`mefw$sk7r@CL zs|l-@Enw6g-R=QNKgDup*ZLWrwWmwo6;v|YaH=hkszdeBH5SNo))-%rK-SWuE+l88 z<MwqbOi)J;(PnSbCR>FsQ-KiKlu7P(D#DT=fZDfw{e$9V8^fR?U;Nb}oRyO*Wd!Bb zg&~8Mm7u-q^pvDW-g6L}P=4?cV3w@Eyv&s5_rv%+ARH#zs3s9XDNNyUY%7IG%2Tdy z&xyxGo5+BO!b&bFkYYd3ynqak9U6E1<&w<ZwYR54YRwwpO(fc$llGAWv0;JQ(n%zD z!#1JX77=4R1WJVqg&B8>dWS7$Bjp$<dzw==D3P^7wRP#NAtJU-zu?77#%o^%HYr1x z+g{-LzmxllJviK;>GOktZ0{s)qsiO&t;Upb#fLmt_~)=5e>)s?1?RP@X7|i5`AFzG zCwetITz&<uU{vYP%H&(XY8H6MGzj`MIhm+|eI-%S5ju0S@upMTAncd@Ldc_@Et=?- zJ7QK8NbjQ9d^kee9uylf4_KW`D_Q9ebzZ!;mg~Tma9`?F7Yj<X*mSwFmc2^H6N~@E zw@k{sNCei^5sVovS=^rDef1>jTUhxqNR%?oWn041Idwhywz_(xIt$_tK3~LbQm{n+ zb7AY6{6Rz$LSTnWQrXNEk>+@Jwt#q<N_Q5rW54`8k9s^Hu2ov#a|15&0DTWo4G@{J ztb)z|haKjwGW0K9YW#A0E3^n%;-c^~MvtqKQ=M8RMbWYvG;YCWNR<M4v;bWC=9KL~ z-!kbbc+O?3Eq6XnT}PklVS3iWBxbIcy!$zP9f(FQE{E5h=ADPm_7N}7O8Y_Zy0DOk zt*oAHXO=%crNqbp#w&(3I?Js!+zwl9vc4t;goa8sG0FLwEA3`#Q!v)f+5;~DXKe>S zc1PBOg>QNR^uuB%*BXDQ>305jumM;*xy~w@@^5dt{>4|tKMnCuqnz{dEP4G3P`YAP zW<lAL@&u$#Ka!Ag_BBXe@P^Yi^(>M6ZnC#1xW99}Xs?>?8U_=E|JHlQZp(BR7$o&( zxiXI2%twFBO=5WQ)*Rkj-VH09b~L>0$N89F7Tk(ctfQ{V$?HLK;LUU$Vz}lQLfpD_ zXt&Z^61v_%l{xl6%I=E|2&baVGbvO)-GiL!&igX(r6wE$LdP<-1#Szs!x-CvmLXUW z!Y;xfTVOIrRE5o3*Y2}NSmq-IW-j@*7_l=62qk>wg=>UV!A-YYVPdIOhPaj--K5<8 z6RgM0mE*jx=M?xFGoW!~oDqS$V=l`~<f(+-50c8Acsjp?fCt(hjJxp20y?2m6ENr> zaDGAh3(SK9OyQ=RCI{zX&rsr@uZg+-u}StsaY^JGw%U)VN|QAh`AX#S23<rEXo}m^ z1F;b)i&exC@64bO%k;JA`LqMo;sfbFJ_ItrszfJJF;^g88M<+#C6T_DlZUg^6XB^9 z`;GQPUghHn%<u%J=G<!60UVz_l$y$V`P0t-U$9{&%Df`N$S<)<Ef>{-3{<`=1UVqO za@@H%(#3)hsJ-S>FJCVbt3=L-^uglIf^wZY)qR=!Q$xM$V9PGtJB!jUsmcGb|0oo= zo)BY5gEigcv3^(?01SMmTDfMb`wZv}%AOf}Vquu0VwfRc{FOB&GVLat8ip&QDV@Jp ze&Q)6EI!BwjHp+V6<bvBlkDDcZj7fdSEK?Ip&wzMDU!B>G_b~Pcl#SRILF%C8r=e2 z(maw=kfs-XlXoKtM$<PAvUYBU1$^(>4v6%0{PO%|({$sm+PQkxHj|FnCGlG_u;+r? zi@cmzg2{isiT-v8(ITKhrmI41_!Ut<5re)&)F+MMF}GAwu`B0AX-j@IWH<cA3cnwv zoTe+UXhf<_W!p^yz!>v|688$%OYSoNFtD2?Aex#j<Ju&{W3eURY@Z6|Mq|@;Y&N#V zdLY2MBKAAivG8DTVD2+yIjjs?gw>QQSa9&JFd#ujtoF8my0;p;m;o);AnL43jvD!b z?X`Y_CJ**TGOe>Z2`ch6Q`qWg7R-^Q=~G96CA+#<@qZ@FgZK^QyK<q~sV0@8NRdaI zJ|wRC0cIkSL+jSkEPy7cq|?~w#eG@1dvOy70Vnlf-eHG(GaxXe?t;z3Y{C(d3oNOY zbfe<uyB?>boM`I$4E8}DDFgQsA+JR>TG;OJb$`Sg0n|cHP6gXyq|;y$KOcu1u3=0L z9ijRYqA~O(yrEua@_`eaYoLN0>*xc0iPrO=suilX^%1Ha8(9L<BN}|@a}~9HyP3~< zaTFIRNsF}vdJv(pd?6>WJ{UB#$L`A4(q@*>JXD-8vL3xs#-zSazBdR2F59UX!xL{% zY+Uz7E(cC3qYv;c3(qTmG~=xNpjQka#Nvpx?>f9A*~LO@>L@Z}qrkxdq%zSEM8xqH zu@2|Q)4=}PZiE7%i$q*UT|Kaz{eX08tgp}fNrQ3!<r;U%F=|5a{yrH^DbKVtir5R$ zlbIxfJTu-C?5+bHm6j9R{@klH)Yst<YjCCp@3Ylu(p|a>v^**893;g9>t<B>x@A&o z4S)ctzD&Das7cCjU-W2HG~!dMLq1?(gcKA^bh(z=e@Z}mibk3N-w?ZPLRwCOGVyh_ zQHJ`WW}CDBLFxCTrqc%HkY@TNHtyTi4|m!$q6iZm%woh1)p+$<V*jgY1G3)$>=k4n zKbyo^HkVE0<S|<4@Z0C&Xetzdkm9o;YZY;32fWb8MfykEm1xK4UDtMSQf(hXpJV=n zU0@U15xJg&0zjT6Eu(sw;aHqM((dIf^cDt#TjPkg2YSf-t+v=JRLb&SYSUFZ9th?G zzi6oO6|jS5fZ*k*Ps_8oCy`5eH1I5%`56>e=uQ>s8h4wH6PHwR@SP~WjEP@Ub_?Zf zH-hB&9zV9SCFEJ169S%0s9)K4sd(zd7$B*SM@tU96Vp$S4#EWDANQR{NrA1rQyvXl z7v74L5mNjS-6<ZlH}m`<r7cS`0Y>J(5=F#_7$*mWg^GS*xj1YvJj?E1F>f^ow%E-L zZ<0R!wc9JOUt=-838B}$c`22!+u#=j7oCSN->^lOVhiE+wg5wMU#~cEx_X4e5z*AU z+UB&J#$4=edJ~~V+hhkpz9(QJq%Hf<EfEU@-mRH9{`qs!?uMDcFvb$90W+(MPUBn( z^5IPPbnl#i)KanGDr=X77Fj#LGS_Js^Q#8{h*hcW1yf_%KcY`aSke*@xBckl#}lzI zjcHZOS2J6eKprGJyW`iXH&EkN(tyT?8M#d=@w%dcTNdvb7eC1vfg&fsYMRoIf4N<_ zp`vBrBSzwL8;-vxNnHk8oQ7IgZP1_cq`teAiw=C4i$R+d>))dkpTgM7UXAf$?6U>a zthn&$?t+*}=#jLeiz=D-n6F_q#IPb%e6q^;WQOw2zm)xd^vuHRo?A>7SYJzfL@IY$ zOAaq5kNYb(%4pQ-y-uDxkXzlQ+kXmQnA-LHJc%@-4RfdiS_+Nkx5CAqLMPj4ZI^~_ zb7;WShxP)!YF;qbJ9e8IfoDv!D#R+U8y}@%k+?$soMkJHET=R!?5^2IO)B_ML;+U3 zPJ*c!cchvzaB17zOTyNvz>u&`o~rp`-#Q;dvLT+pSM#Ks4GH@N1@xtyExtd-$x}gY zsqPw=GLaLmdvdA9iBj4r3&~T2<tz#L?XcRDKNW|mRVSf*Me(I65#WYapjlwA#PFzt z?KTj864|%(gVa)ST=w`nCDOTD=~AzRIF79ktKPm5QQe@sqJA&;7n!uBb}1}pY+BI2 z*~H^p<#z1AYjevSA^HCDG%K$>bEbdOu=29XR+jZSw<FnruuTB`NlmvLk7X5y9Rts= zZ)Vh6Ml7KYsai3}4QHG65T+w4>Or#hJ#gGP3H{J`SiCFG4JSMV8wF{e`{kRo@iL#g zIF_GWFp5nTw<{d}jH0H8d6`E_LRYdhTP0cIQmNEjS<wjr(=)T^0S&j3nr2)9D{1^$ z4O_Z&Rnt~<T3MVuU~*HCz*v`U59Fk{l2WL1EU#AvrVuM%7HM`bN#bqB9cQ~8dQMSc zHlZ!$9c@NBERUu~j`Z9ujZ+G@0X<)^nl|4Q9ZB(RXzYs9tycuu!7<z|WJo9T;>8Jh zOVNH%QH7;w%^B3-k)=^VeAfV1wrbM4s$nP=!YB=8s3=dYW@;<PujI}WjwZZ*(ab^+ z#pmC)BmjR?z>u&5kK7nQR0XW|MUG#I(&yuPWZ?8%Ndyx3<*4eZja)13(gbM~?}~H9 z+j*?{F&%ssuwcn5%gXY^P$;89$**Y9%(O|$bZN;qiO7+m$eGL`yBrDP__gDG2e0{1 zV9GZnW|a+`jymG(YxUCfo%@J(s=-93YpBG<8d#l+q81-468D;_v-(aG8kI$@4de?_ z|LXMGiXvJ6<C!#t#Q>-a$#r50_{4;0G8L}5=S)XWy9hHl6W!Pn6cCxgujsTnPb^jE zSV`V|Bl59Mv!PX*$0TxCCfODGSx)2I67SW#sVDyGbGwEQCzuHgA#Fbtw&t*NfD~43 z{LrnCjL?2Jj~c0r#=et#BnyRC9wC83P>I=#t*@9JEJtja{CeE?rO9vx3oR4zmkw@b z{$Ihke&6!HP2tqvg$&WIFCi((8&?$9e$5=K^$G9QtPq5em}LyD6(a5E?p1h~_ovm@ z|F@U?w=p#-{NP?8PeFN%FfuQ(*t&UBxx?KP8-CMkoqFmdtjlr?sL{E9Qde~-vNM>u zRiV|F{PZHl**!PojQGVSP<Y*IR9&zPm4TFgOD<ak39O(_oU*slf=zDT2+5h{hn0Zv z=WCry+3xkPIA5K&&t>Fi<y=EASDXrOrEPC*`Y}(I`l<RS`*_<TYhDPQ<Go>{H|_Cn zS*=@htbo*Bwzh)p3ji-rJ1|dWrv(25Sy?6$xI>UP&_SmrT7ooyQ}*cO_r$(V={;RR zV)wp3Mbp21(YJ6R=`p-+t`vuuN$6TSThh+aT_k?1yEes_%calZ&>$0tp&iE8tcf+` zcIeyDVg>%U<eukFf09p6``BLjL0G3eOb3nh2wE)X$qrqfvU<wL?7pz(zXhbaEEwxC zIYH(SFR;<f;Uajptx|qi1AUk(tR77~lD>L&a^?;P+|f0EEgVqskegjO`Tzv(j#p+m z1f5MPG`+K1G}2sGgPx20xZ19e-0LU?EvD_D{>WUWvod_83h)7v;3>ZZ62yw%L7No* z?fx;zcbiREb#QxFuDh2KFJYiM*x|)mn-`kp_xMGwH6XPjA&bB`w!fIhebc=mN>59a z%(OV7<JD!7ZPGDp5xDCDBnWZOk@@R$leT8oVyJh<7c&*so7gSaj#X_rYkDb(ScPBG za90Mcpdq5jya!CXw|e<ne`i}#NV3J62P#*jgG;Rq-I<|R)Y2cveZM_Ts}i~Kr{pnM zlKc6Y+a#4n00Q{T6T}#mwOY!mB^^dNCPgZMB*mtK6_yAJLO$N`9z5T6MO<i}LbI)@ z%J=d&{O;Z`aqTEtlKUQ%D=4}IsgkK2qotP;uRbOWfeJ{9AYIenjCPE_uI`6XeSE}R z7HR`6mwY=M3`9_=#FI!kYKeV%k3(k)Z6UfE_(v5`iW#bFH0zKLF}WY-po%q)^V=xp ze?oTP#`TTu_jrfok`rhs98k}PS(dkUBd4qzCS}q)IHttu8!rtW+ffR`bVfLz7ZFXw z$7jAXgVlA4jVIT`D(IS_fFFk3_em@J{!by<p-ufDvN=zFsO$_5v}=ARWp6iwO7BDB z>dVJzgC>Ek&}beqb7X<lMNAt|5*^%Z+*wT~?&Fxj5*yLih=ehWigqBz{M7}Chl*>a znEYV~Jj=j||Hpzgb3jgT*fObf1v(|OQdN0y1IIU(x-zb=s-&Mdn)@5K4QZmN-(ORa z_fIm1v{)2D4^)uT2z1lQ{hX~n^ld1%a&;0PCybGgjw&8LBTDCioE80lkuL8Q1UYq? ze>XEX*3SchiL(YOwRj#M<G~QHjU&lw%baL*wyeoN8u<rjDu-ujJV|ry8tVPQZA}qS zyFEPt(2f|8ndeA&w@D&~Ubo<b&_j@gD-CUkBL3o4l7yHyHDPpfuZr<wF{5N2CS!_7 zX9)X}9PU3X0xRe=iqQE<RJdq7vOAX9gMSGteP}LS#?@)*8cj+1w1IXqk9YjYsEaWk z(@})w<X<}fw#G|S2pJRJ(8H6haG`UfCA^Vl0J2h$;s%j#ZG8HzNXg_G&J9F#u3BkN zLW#UcrIGdYjtVP9Aaa_RB~jd&bBsMLeNy<VZo5`9!RL`Qyx$kUl{sN;N4x*5=vf$; zrkikWF@5c493-laIJL?qKcsdGw0N-JiYvggnCthC8Tf+QrezIm=_HnuoS1%s{{da_ zm*at-eDIW7@>7XD==bVtf*{u&IO2=*SE&@Jc2|hPFBw1^mL!nbDK)t7_#h7Y!Ytq$ z2EL(m2E30Vi$=;$8O{Pa=mJ+D;aleLL0UY!Wu@NRjpC<(#15znt~zk|@H_B+2MN1w z;?8s(w%u}*)sfbv)>vgI4MndMhA>eWED;ltL+QmoT$5WfQV%PPLD3mMitrudagCNI z09rYNb4WR-mY{f<U<MDD7e3s|K#?W)l$&dpPDmgS2>1NV$rqE=$!$bk0ATD+oXSyD z#PJyh*7jr%jB`u9x6H|ey`2?IHMPB8z@bq)W^TyFscTU-1kws~G-|RgV8Wep@r)g= zsE@rpar5zh@WU%iR(Lnr%F+IEVnR}@lzHcrcFelzqA7WQsP#wq_2yc*hXp8SWwNNl zYP<+wWH$ylu#1v#CEz>GWuIC&X(qa@!xE%C5g)|z;;;19S3<kL@uO{)0}}=5DJr^b zxo>-k6Hm>Cj=1jBMq0ps$hAb~lw^f|D{D!QRTA!UvbdpPrf6u3m^*41?4MNIpHmJU z&Bt1Z2*Ewjajphwzr5a~qx0sv+5pUGJNks_S_sSd2c{!V&@AbmzNfV)#8f{TSDkG4 zpS&bU2Jns`GPVpY2NxOg8cLp(G4<RlZJmU-2o3~HiAT)Q5@E4vl_H~(9|=~i02-Ki zl5U&-rZWx+JjRMW6`;a=<5DzP1hVL+Iklv&052m7L+zzZDStLTWOrb{hi|h1-9zUI z*isWw)F55S1vu0fe&FM1v4Pw#Ta)l-Or#j8Ta_dQ81Ud}ZjLRShh)`h;5d2@9ic*W zmc4-91i?L-_?`$#*hnXpI0PBI4{%{1E>4~kfWQPrji^GQCYR~b2Boz48>|i`ykiZt zI4;fjiu_xbnyHq?hp|_Teh9>B#D%F-CBZ{Wn$;9Hds$6sRx1Xlv}}Y;cc&i6J100~ zT7`X~DC+=r9&0EIE7mtHC>~NhVVb6Jf6YyLQ5o$)8e)T{(8XBn%%^ZpC%R4_&9%r} z4M0oO>Es>)h}2}h^&F)X7w$!S#9H?h-IQxmuw=;#BO}?kgoM4TJTP#ckn<#h8)ysq zZ@4F|w?|dk`pKYeD?h)Xr*}u2y#QC<i|9BJy6>m2H7h#kr>FLRn43uLHUe54upJ3I z``)LQPi7XOwglu1mj8o#$Do>yT10RKWs_4nW7CxSt9-uqQm`P&6AsbuUvJn1{K9BI zc|oXJsU?xa>&<Grpu7jMS)@NBF`-Tpaonr}6UmY;(Yh9%KgDV$8Zz^>cHrkpyeUvN zB9%k^+Om}JLo@ki34Y>UOwR-F_K7!Rw62E22Mt*~+O1J)U%6z8^#jEYy~A7YA}4fK z7Ra=%Ud_>!{RfkCDaYT-Eh%!@$(GD|*qI2L@?;jp02HM=0TM|SMgjv}CAoICRCJAt z9O8-&YVp<Gj&N39ra_s5S1i7R1$o-0un~Z~kL><WdN%m=uu7>RF-TXHPX43<{HAzP zKY*gXvz<P(gYED^<3g0!+L(vh>rV~ogG>Fopn;e=o%@q@C(|(3=^<A6<72((unHCa zbLvD6seF*0<hLOOiZm*@aGIu5tD=UC+#rFBGG3kjc4QtWc+Qtp+o~cPubvQE)7ckA z^13FXfPq6;rRszFkxC<P<F7;!2A1OGXfCECqKZyko6rr|n2meU<?543qh%@<*p6<g z;g!3GWwEePidftSPNJ+Y;I9t8NTA=2-q+YfCE2${i8)<5mot@6=s**7UoaX<OJ)r} zC-#OewP8Mo%=E)lb%IiWfh5RM5}S+>U2{DIh%aeeyyoz@LnVC-cw*1Ii?1Aw;MZ<r z(~7XT!-!^0Z%}($I0ZCr_4LB0qMKmYP)%H3gTmh^5*6ViC`UR?*`o3*(7s%tAzh{a zm=aFn&OxEATO4AQwQoGLFrIp+ecvJkY9Z7m0FM^sO+>(n$DLGRGdC>z+x_Lhh6!Cz zF%<?_C#$`4dFBaGNB<d-$vSd02J&0c;OLjBeA3~uOc~uOgkX6stYRgz3E1Xi76+;Q zaV=&#R*c3c$2Q@q^E={FIW%A(JF)`K6U*IHF+hN-oVeBvyS`;ugTK{2K2}(!sV6OX z45Gyah@nQQTC^XzZHEw0ShK{1ik<=2PNOfSlWXnqO`wS<s{OlKEORlQFK#;B$XZYD zXN_-0)Gdi;``_sa9&!6DZZ%H>iaL&_s`K483ZLU-6c?Li>c;XXJ8pYVyK;2@7Jb!! z6NSdQuGr#NEGqZNemI|z(HHC2G(XpvLHJhhD5I;SIZS-i0lwEQK~7K^o@B}hWOX0e zqipkP<Ku??8^6SdqR?(}bQ5w#)4ptuz`PHx^l>AL&X;9|iH1ONP09OD%`O#fdhOtc zmM#RBEGV<WV!I|N;?1}uQ+A|}S7S`ZUA*VYeL~LWy<j?`G6!JxVq<^}xoY@3Q18|3 zQfL^`o1P6(i#-176;@UV3vvri_^6|b$0p)d-eZl+CpJaJlS3`k4Jm0fl^2jg;2r*) z|CyvmQV6@;&R3a{@g$-FVmf(9XB?=;-8vG2x0y}M!y~!+5;!zZ5Th0}Pw-fJ@>**# z)(Mz?v$r7yc3s!W$$`5stWIMPmoCCou7-QJjn`ajgnyk1X&%)dQA~q<p4X-0nE*dA zR2uj&Y&hUBG?;v@h`6I@{eKT`hBy$r8d!cm%jC;p=Kw!Iz`s_o)PYR#L!oS_Ot58p zFmnSHo^C$jV_V)J&HN{VW9D&ej))`xnuX%e=}aw+8S7WwlJWoRm$ogRZ3nMck?kc* zKuZMFaDR4UvCdFXo5)9Pdwj-k!is-APe@Q_2NamYOC6KNsB~XaSu`}4u&3n%kU7+r zlRRmzh(F=28#@ie(gZj6*}<_{vw}iOhgSs)PiiD^vx%b7*dDa;j&41X(~tqH+3GrS z#wSahguio8-G3X*(Gw0D1mexZ-#gf;W!_o<ychA;e%$>Z@KEXMX23BStHLNAslu&F zv6|1i|HN7a#w7^xqpj|atE0(N2X*6MagC=qri#W%D2JIYQL}BAFFdygB|sH|W<|5z zpVlB$i0*7K4_FUMOKNu^t#ji{M?uoK`mIhrP0}BTg#N2Fr$u7v)-G}s7y$4&th>kf zA2tlrQi(Zf=At|wRc7^-r4y0Ux}Sv2ELm&IL~2yCHu9!O#I3{gzvM&LO(5k<_S^Wt z`q3}Nh7e2<JeY~lPMwsR$W5YyiCY@_@Ls&F1H@uhlQNu(A;L|!>cWow%G~Rs+T>k> zrux0J64>d4<K~uT$VnG96R!5xVfW49Q*jRt7=8v9>2I1JG+55fL?8A*hZ+hCJ-0CB zDV+_m$Xpl#xzYmyC6iDza=6gvqoYfE&ZK?oi8g%+Ea=}3w(E1{rGFRn$S*bOnqy~- zGoQK%ZAEM?P0sQY1*4A_7e68|h}!xAN}1LKG1U(!%@?wS|KyQ9bQ`xDbhhh4^1b3M zOLp_l-g((H7NHefzcf}>F-R@f3k8F$Ze9U$IX8Ku8MQMK8a^X4uG`b%y9kK-3-z~R z`!XgB(t5AGRf#G{wA8!i#j3cT8CO#Z2XAidOs}^B`kUP&s)4@ft%RH7iocDW1$n)& z)JcWev=py+A90SK2-8MQqsw7o4eWOPY&m7~TZntHebxw~q<wXUtrqKE>-;KTSEt@} z7i0fDvDP4(ElH`Gr||Z=rMIZvvmkSI&asigVpaN%M7gf|!*eNF&>qhh*IL+29D)GO zoUjxR9Pgh~Uw&@7RYvh(Lv4PypIq3aj#bh{i40t^@L)~pq)<DKt%hogPFghr;?je= zuf0zH)0)D$^9?J{be~tIg@-WQv$QkD$QPvo^j+1Hli==BZS*WTomWQv3_a;9Qjkx9 zunFae{c=DIHpJkQb7>3}N)jXdLiKMTym6xF@Cx7?7mPcr;~Mp_#kL(F7B2w>we-pd zyaxA!PFT?9O{P^$z91vX>H)oCJ;FrN+UL>=KZd+aN2on7cX#ISKzxhgkuIGOe?@yO z5m>hPEDE$*Qu)6AlA|;_H3kZxw%5ew$TQ0rH{O+MRXeClK9jG|htJ*{F!J=lX4nsU zPu5b$peBHLx}V6V9>Zh_G9}mtLG)x4*0mNSL0in5x?PtV@09hG^V{**h1>5}pPnko z70XH}9T7YX`W4PAqDMb_lgTl%`BfV^7Bw&zLq8FAZcL{e*|orq?MRXOEVkT*VjJ#D za2im7A?_H14XnMf4XouV%u6ZVp`89J&WYV5mck9AObxLn<G~@1<=vBQJ`>m7^vHYm zJ`<bUP3COE#~O5l(iQTPN=mwu?eZG1S0Cqhy(C2KG{Hu@;xi2yAi|(P6dlK4{!*#r zn>B$Uv}|ur@449PVrVK!T@aml4Kj9HL6!4TKOArCt7|JhR|xDS<|M6W^AQZdLtB<v zbAB+yraD0al2PoAKFjvAQfqS;lPPy3?_UeydYB<lMNw$_$+IhKF}aKDU&H&2X-R-V zmI%;hKJ5QRa8dx4$H;BQb>Z=hgsmjO5j<#<x0-WN8z{Ls`NK?gd|Oi6krac4$SD&_ zVr2*jUhqa2vVJJ685K#57oC}V+z)^z4ik_rQ~DiTXjWIP>aSZ*-+B)t<j$`aRi8UE zIz6szq$|W8JOBxm_3Q2XnI9Y%$MRtaW32tfVl{f7(94&^-25Z1;_5K1*x)%9!c+Sz zquS_cRocZwc=b7NW*?D;8_J5OHjzDh|8eOaBuxrtT~5egGvE`YJlvq|sHXSYNz&+M zLjdNF)S=OfIsWQok%9Dt+DUU;_d&%gvAXk+n6YJio?x)e%_vJp81`R~TjOZE21w#- zC8N)r1wksTx9RZl>KfMe-8fzI_$*amWCzm2iiMb=R@Byomnb2d-w&AcFy_>o7Xb_} zQWF>%|MdVUTC{}eez1x@tL&a@(GGUW^VrvfT!!Q}97&*B^st~q%)7S%6lRl$>iVB) zH{zRT9NE&A7o0z2@<vCO4KuL<yf*-`7xe8etL#OhxO6%cg!E@WWi5+ahL1KzV06nc z4snd;hYG;wU<-P&_>^+0KPZ4RIGb;ONZWPg(7LvZu6*DWWR!slT(<}<%U0IbomUsp z+5HafGNAoHb;Zp+yUE1_XCP<sq3Yi=Y8+XnKCBiP{Qw9tpTnP@jYxUB=#`i^>v8Kf zE_W7XO?e#Z=DCvL?a>S_Ri#w-ekn-_!5K8GIMQf0L8iUdCs>7ywj<h$T+)2QK0ncY zb#@EU%}L+x9gXevgg$#Hc)Pk#<=^V>zXgeEpW-M2$`nmm{6!i|CK=f(O?ORe1t##4 zl)6<_V<SXV+ZX39t#`}+!P9^2U<bhdeD*~1U@kEh7>cPVFmII4Dk&$FAjkVlZLdEQ z0b483;W`{NhcH09`engEG3`5c`Lo`7rDi3Cd0tp*J45Qqq}Y#eCx<5n&WNwm5jq{- zD#D&;$hH_)BfCE17ZWmUN!NLYmkEW*v)=2JSb!SfQHQoz=eo{Cmr#x|r!r({wjI|4 zVh2Thh#INf6bosDlv1t0H6t*~X!C#H_q8HNwEl~@py0O=+sHeI6=)t9X)#G#NEhD2 zQEkGQ4hmW1^aUzg&J5s+(Xnas{ot~KtDpOXK@!p^2_VRb3H)pJQN1-R-(N?~XXjY4 zp5k!&kSZ7-UWt!ZfABvG)Fz#)!?Ry4|B5YaYwsv9XnVs{J_&@UtwMAjmrzL9h>}w6 zZ5%RCA%~_xYw*3@DV=;q*1<kNS%Q!_mPL@wMQtlkUINB_ijxCIv@Us^n+oM%iA!H* zB}oRI3h2KX3au69c~`z3dqpw$6TOFyo2dk9QDShJT~m6%_^M^3t!v5%heW8^2t=D+ zo{64?+RDHD-XB}R{87`AVj%v|kaU{GXYoK2P*7uC6F!;P<Z*1i$NoOhJyPY(jwI<y zI9!o5$5BYKs(%)JJvd~;Yv^7Ex46?Eg%PoR0{3A=_h7tDGe1vshXm5vvPMp2?Co>J z7KFs~%&jXV+;>nnIWZ+qsq1=0k<|u6m&eJSr6a=}<?jC>SbJplF)E8sV!o+p22Ku0 z4UfYNC>aeeg&3#I6c^ej^Ozuo+EoUg{81QBXV0~A3X+(6H3eVrML{Gqp;!x>!cdBZ zb%rTlMo;>c`fDrUR43Re+d$EzP(Swv`<?f16%A!Rj_F$?eTvzCnpA1P3)X_^JW4bk z8!`$a-g$tjZdtU8u2@#U2lrhdgW!F{>p3duk+1_e<&{LLm(l7>q--p8r*MUf)txuP znMh?TB-nEm08fJA68hCO>FrV_&G3)V$LZrGrYlIg;e{SwGXgxeSC_WI=68Wl$Ek$Y z3SiaRO3KI!4~t%Xg}ASGHWu)O$waS4F)$b_BDrwnc_seB9Bf+F+sbu9{sd2PQ8!eB ze(B`$kx`{pq@H!zCly~{*y?f#!_8lS;M=>m;D~DM)_ce#TWmh9u4TE}O}6s)tmbzS zD()r-@&jIDjo8g`84nP+U!(E!z+D@$rTt%=EjDvHj8YD91$}HT5mFN>Z!EUn+XPB? zZC|q3Lvv-Xwmk)1mEwZPYiiG`cHo`&XRzCE-OFW;o(o%v@X$*NoMgSG5@O*d@+-<M zFtvW30U2YA-PG@$3uyJk@p&i7#lI=Zhkx9G8ZjKtFe>xy1yBNbW|!E$BBbs3367Z; zlfF%x>(BF6hoklc&mf4HDr);s;?6Y}1M}`UOlAUNkm`%>PYyGBN6n(u`WZF3xWby- zG(~e-`!8PrMV?#u#r+%~RN^h4^!XzQtD)V5T*k_`K2fg;klo;@jEWE-;nLO&&>Ijz zk}Ao&bt3m8z%)&xTuZ9JxsKN%II@xJj9QEd{PYZ|OZ^$58T$N2ZJ62|WbN^p<_3cl z-=Cyy${ZWin}M(jx3pgOsn(C71A$tGWQZI`@5>kJfZ`#{T%em2-?}MggE*rs#TPL! z$DkbfBp^(3JK{F50^6w{B*Q(Ab`q`Kc#J9IClq+i9)sn?E#dz-pa}e^pHuhI+^+MO z8FlILsRr|Gem`XP1Q)OWM2T;un1e#nhb7OUwLz!GGatF`q*#-!n(!RADrM~Vyxtrk zaAvo%2A5RY@eS7b_Ai*&C9`2;BJQPEfwZLGv(Hi}S)H6;%<(ALd|sQQGQ4zC`bGe1 z!Ver$qR-2_t@Cx=J1&z3j}$68X3+!%l{hoBnbvY(5DE_R#e<zC+8~+qm1c{J$WV&u zJ=s+TPhggK*4wgt)Pz8KLdM$}H-GQ4L9io`Y&eTLF|55U?IA5XSjH;pd}Trv@9svE z<_@V9yMdD7cv_F<lVrZ?Pfm~)HNh(RXjF!+Ao<D|^`^Ju0G_{1ebM3{Tbi~P5X>Z_ zk}#os7Dh2>BN&&Q%vM|?^38T8?GaWXV<`2bN-0MkmZ;{8E_!O?0scN8)^q4(48G*P zO(KeER1+LR?HUw!KgfbFS;EYfoT(bTrDms0B9g=4bO5hfp85*eqU)_!;PuW)X-&Bs z_8{}(tI`jm82qOCRb*^4uX%`tC@5%VLKDAb_d(+p+V6g}&09t7C)%WVo)mNK^CWL$ z0Dj0u2G0F&6R?=Xuw9t}+QjM1ALM{5bT^<@7musnW=BIj6_-sLVueMxwq3?fG0r?x zjCm*wNEqlOOdqAPD~7uWcii^+@lF(I+k@x{2j+CHcL^7&S_R@@oQ-Gq&gA<nA}Hv@ z0_&m5IV~Q0cI<8|J7+8vGFG`4<U*a#%k4ZLu42YX1KQEmAgFDhO#a+iPc>zPKQq7b z2Wg<o^u4YZeK)D-RPJ|Z2y}r(e(r30@5VxEba`5CVO+g1(P<l|*Ka>6l}Pw&l4dF) zHD<jF$so~ui|K|30yzScqg|omE|>dw-`ja2>2FhBGINe~q5=l`(gDGZe*?#+lCAir zJBd5J_~64((=kP0U`@Mv#_a_*YSq`ij(D_bJyB)AJ=~XN$hl%t&L)K$Kh)jMSl9|m zQD82t1S1HLZByD;512r~P@|sn<c`IIJYZbC=g;a}(9lu!es7co7vC<88?t}?usOf7 z5Lz9l@+Z})?|^yS)BR>-ZV>3huKoK;Yr_<nrfdGZ!a1kEMqPIt49IM3JS~nwfOZXh zxPNTM%|%+Yvx|j#DVQ^@FVgm{oo`#9iyd;hXoi#>-OVPVx~Gute88G{*1s=xv*{CL z)EVZpX>=D&e#Q^dw}kBkAGLR{i};9%=+xud<SZfq+UmVW2GB)|d}||&J~I@Vmn`n0 zrvGwiDdztVhexhVcAkCG$1wM85&VeU7}D3C-Do_~iAm{>HWXvW3tfvAgNo_5>}bNA z?+4G#_jqzBcgPx+<e7r@L#`~5YkxN@I%8)9c6t?;yH9!N;}5FO_2@%&{oK-ub7l6b zNX#DYKQFnerl4_sSLO-)<QyH+A;j-^8|q#fa{fl(s8`a13GgL|${4RLH>9O8yh6%0 z{WH3aDSC_j&N}<&xQPC(mHH^QyoSF8wwLy8zVRA|wnfY)nr8)ygo0Q#a-@fNqN)`x zfpnN_Rr>wc&KQZD59wb{xLkF1RQ^pHExj81PpLP8yg#h8UqtDVxfhdSCw{#)Q^^d- zVpZ6^B{aM)PDJF@t^L=3xq8HS0w;-X;-u;coK|O!r*ExdLCB-a@GAui9-!c!_5e*r zPR+g+@Fb+aq*W}XT|S`)s~rJG+!PFa81XjZ?6ln0&Aw!?(SRizV)eq8i^j9k%Nuz! zVVY5<;VK#IW?P{T?SOQ@d(9^Gd$1#%l4$YdV4<d{FA4^91g!B9*M>@NYs5o@oIgci zeZg<zd{j^OAGdz~Qn8TKb3vnDJts!i$nAeoTeY<(RS-Cw;+j!#uK1e>j%Fzen4-vp zLtr+zAYejI%!w<FTJjhR6r~<qv^fBnH?{o_@F&_u`%z&!uf!J@hFbwB&|iQd-_tH| zR<#hjp#mD}KbPGvnV1&v$3vgZpvNWoB!U*AiIIVoDB9>5<GB}SfI1zMqsCAkLfk@d z40WXku&6yfwlRzE8pqrugZ)Y$^PVcGnd-b3cv6ajpePcKy<5Ae@YCYMx5S@!QO8~S z*xg0861xL*8_HLyu&UjY{;1mrnf+$3OlJf64E=29Ma<Je19Jik^Q5ixR;!$IW}aA% z7^b5f8-pxkSMWPh9EKMNVoO~%DBl5Xk_t~GEZTq*P$aKLcyMGZqM%)noQ(>Y^Bs`z z`(O;4MO-nod$uZqsCV!@uryH;E3p05U+`Eub&bubqe#s++<WeJ3Lh06T4K2@><OY? zR(y4i_F6v+r05jzy$(Tven1B|WZtK@wbeera2`A3`(#jYj5m3G<#K?B9Z8VB5fPw7 zYBbG+bvm63Mllb8!R7*!FI@n$%z0P>)1~We{PIGX1z;LKa@G{f<X9sW-X#2^0$zvK zvbEbr#XRdAl9@Bw^WTc@8oqEhH@Wa4alIQQ1_)3_CxsTa#w>S<d9N0L+rdL#dSc<a z;X2f5J(-6E=iR}kdiFiK)?|^7)k~<>+TI3Cpx$mO10cpL!{q`0r#qV;J7mO4U1}Qh zur!mN)HdHOzwE<1j^#A*q-}gHl-jRE%{_jStQjVa{2DF%Fbw`znW%xstg+tW81Buf z`w+hcPhoN5BsnC4oR@3nb1#Vkn+jH0iH88Dg9W8@sa?vw@_Z-6Xi7aF=I)br??-?) z{)oao_h+8n_a1j~AgtwPwhW;>!s5F2tP$`_((HSt)r(qJvV}53b&EvVqrf|xA9nbJ z{BO4k<OZc7WRC?K_8NoR;1=mXLe&?s8BQyL7bGc?M_O<<MZY+j!V+kmAjD#T7Fv^W zC6IGIeK1C8&Wj1+v>8`w1cb>l^Xz?{PpQGCOYRL?f4n4%lLk5TBJ<Jc^1FJ2(q*q8 zG5L>$a)ycj5{c9v{CLaM7k@dB79vUBq+DB35i(BMk?uodD*yka-C<yOB!kh86`AC* zt@kQ%n+`%I!10p8dPDts%0y46K-A;*)GYMe);CzUln<{Wy$0>w<U*Nh@+cqi&IyW4 zT3PA&J#TXbz`Zr^LpV#3*0sD^79a={5uom_8jT*(7M(Z{Kwp_Bu$tiSM}wL+^RfN4 zQws5;LQnik<(Svx8D>ewCgc4Yb)z@^)o5K9^LiSlerExafNfhCM?oPx^hY$0WwfQ^ z&##4=*3{J#>*~r;W_K0cZJ7fY$&`|E?ljUL2PmYaRvv#A`F9C+&tC}vYLWXYxF96o zVFN!*?Cq9uh^cWyvCAqpWKK;kotX9eBCmtOVy(R{-mJSooNKauss>(Fa`-pi!Yj&7 zSj4;-n4<@mMNZVpH?6z5p?Df^Aq}zQx;9r0Pc8p6`OzXyyoI4viw5`{w{<T~?Z=VC zA5q3>*l(${N#Z#btHtJ<U;p7MVc4Nm=QAT*ig?}#Zy=)&GRIgRH#L9P8D8<>LbR;L z*ZRFsVq6b*knC8-rD&ZBBEuNpn=R$_{BA>GUi^sK)%Ul3jL}23zK^|6-Fvrkt@rwK z(;fe6LlOVg;hnGZ1CZ}0fkP1e>_HbB!{>KUs*kh~^DwV9RF-&Mg(y^^$K(ytun>_O zr}T8Zk6yS1k3o@-04)$fy-x3IbWaU|2`NA!Q047SVC?IDeAKAzk4Q|6Jli(&L3#6t zI{Dbp5xfe>s)3-P<Xi5v-XQq6n%AAAUpshvm3bzvR#??Pr1DvYZ`+$P2(H1wga@>> zyZG{R_1t18xiC;@clK<(5tucrm?AuR4&Tla!eG9B3VPuhX84>rVk1yW{0Gx}KBN4g z8*i{Luql-QfM3`q!WT!3UU7F&X1>2*)p_%(2rutrEGTLQ&X2dTk|--kNXd|v>!(}n z?A{zJwjB8l@XfrUy;SH29cTG<bDRb(UQ}75oGu%uk6y#`x#w=j1HR6;%8z2zez&+R zb4`);K+mZs<HhZLBDDoZqnjRtH1oH{3#4sspZV35Qz%6Cyx}YQmO9cmZmkPwwuBbC z6@IVtl!G26^BAT2^c)=Yy1aceg7z(+nbeE(EYjJ=DQaEdqDq1|@GV#y#uxS{sX}@) z{f6LX67?X@Zs%P&SGr+$r=Mt`ni5+PpuFe#rk&NVDbOyM!(*x1pQ@TiCb7FCwClYH zB`cnUpx1|!h%3bQ(-0N$f}W}UsDEpY@n3|Eo}51<IBSKu_Yg%e4Q$(O+KF)}jN=E2 ze5d!CkZwStt&QsR1ZvfxIOA!hV|4?pFDE=Y)=a3G$i<j=&6~6t_3uuvPVpUgBVOs& zxMB8B|14+`4+iwYpab3=M(=68##80B#C!^NFoG8;tRYhC$&QKs)3SUBS~q?2fW>h! zcd6Z-_Dx$57Zfs4k*WbGP`M-=oP=k^u$7cHa#Vi-D}vu2S|MxI`$ZRVs=SBVdfPp9 z-@8UYE*-4`cP3@IEUj`@x(j4-2s0ih{7E2nPC&Yiji*v#qC1nByKtD%fsx|iLC!%o zR^(&w?d#wL4nbnkB`~f%7g13W(=@yWQr^ZrzKC(<cshabLRU5T&njerK9DN=+UtPY zfo1vl#Gy@#FW+GZZloW#B742{NGg=m5;HA8NK@eAAeEhX%eBcEDWNq2Na?9)(PG|V z3NB`!tTXV4C4@G{Esl>E!Q=ZAUY+J(*pR-V|BG+Gkiq@9fw0ibsvi!hkmpm3FD`{d z7XzbR6JUtcp^6LK;y9a!62<ND)>3<`W1B1_r@DY)(BiZ%&}5?)^yuM0oy^>zUktUh zZFUlL859iI%lOy5&#iYcf?uL!V@u4Rdm`%?&L<8RmEC2ww;6A6IZC)l{JACQ_m0Ze zZ0VFq0*`UvYrkUCV&57WouYl%TAJUGL59-l8#4(t@ZYMs1Xv^FVScs8bB(3S!SQV9 zS=5rdtD5PFaWvQWt=b-DKAH-8`L*LTA6{|qFrr*3cbxLZYPt`t80dx`e`9d&@^t7X zS^l$3t8?74(LIC`(QFM+svVPyt;p%h@(IO$1_TFb-@tVBN>l0ZF5-eKj>%O2i%bC^ zPovxNzv`T)BrLbqqVpGN84&DK4v@miVqWOjup@>0gZKm`)pQ=vimSS#=g#&J)`*<F z$x0#HZ<?3^(SAQ0WOZG)dM#Acod~Heir9&+shCcl5wc-t`(re<%VUx20SMoG=kewO zs^Aocn$u)gXvKH|``?lDM#)@Vzwl@<lJrL!1mIuR!I56QkBU=)d8|qE$O&y_QB6iJ z|2ii%)BIJtRgwhVUb)W)kX&^id%^cMA=FXB{BV-@2L-A3L7`eySF48r%}zWPIX?z| zXu-H3xSJULDV282)1-Lp+CVV;lVUw)=+pp@aH4<Y6y|~ir_0Ix14=`15AOyd^gLQW z%MfQ6%I!#l7*Kz`7PZTa^iu#;JR6e<%)F{mUtaO7AcZeIrR|Wd)t<-3pVrCpO|)W~ zn$?iTj-@Xpbn?x*Xpbk*BM$~TWMmd;c_76a;>=7wj;sdsT8DUNTGKGQnF^`Ch*RZ& zaiBU>G9=&2fyZL>LA)vtT7cKSHOtjgA?Z7v7HD}BuHz%FUe0Abs;XU)dC8QG?cv_g zm8G5x^?yp1K5gsw-G|jadTJHq6gtl){BkO43)*2)Hl8tb-m?Znl`m`ZgXE!y=X>;T zpu!TP7ATvNWm2QZC1vfnszW0rztVMk-TV{aD>wp7-dE5sB(@o=4}XM6L$15Tb)$5P zWlSfvnBCvuaP6CM<ai+?;vgCZxcuBI(W!#CX(IfZ8NGSFr=?j&vI0A9d-THI^7sZv zJ{i3$9Y_tXoZS7QjRL?2__70Pmc;mNayN7ymF|&&;_N~CegsHBW9QR_dWt}_LY8(L z7RD>oVdiH^2Sx3CYs>p9-y1)EVBm_Vkv1_rsbzA;@tN802wG^|_7$DuE0WkVly@ZX zuF&i`)=B2z8NWmF%+3|0DNO-^kTB<r5ehTub#QLj+Wu#yCOLf9@CTuT4`{ja0BQ3k zH}7r%*E8;81=CzY6hC)3hrmtZ!PrVo0Tzn>i^{3~_9GPdXRZCV_{-wkgB1(#nMj|4 zkX&<^+K$n$g0mD0t6^piPpp+ayO~UXSnRiPQ#$7FSK}-_-cb~)cLCv!&U%O2!?G=@ zV!e3M7QqU*(EG-K{tpl5){&IdS-wBf48O5_Mco`({@?6%(^{naAS0r@TlvMr3~ngY z8iw37S%n;R!z3C+b#4P8dNgxbq<*28(KC1?%P}SMM*5E9BgCZr%uqhQXrQlhWayeL z0%@-RZ;FGVvf2Uf@@jPvCCV?a7GJ!ge-5T(dxS*lAT_UvY6v-{q9q1?X9KvG^i&P7 zk-r~L?3Y_??$i4K(leG?b~shy_!yDB62*N$A>$e4^ZNKd;6^s(r^_;BObD9L0TiHR zE<@-G2b=py2KvS0%kI6HG8~QNE)h#r%JM5o`n8!Pm%1V66tMS+8G~a3J9L(Lj;dEA zEOJB$g;XsJM?;g`98O-1Y+QWSYOW39SdZJ7)((0MgnC_n%%HBi+!a^($}oe5%UWg& zdUe>PU;hW^;3igme0dN${&5;*;!_bgP>wItPm`uQ78MDdNhU^qUpNC*IIfyf`}&^e zaXX15apc?V%71&YgF#1`F39#a!~6mP7Bl1p8Oa_Lk;Z;-?hM+A7eu)PIHRpXa7xm- z4T5u`thlwp1fl)Z7cvJF<6W;>Qt<ILG}0*`F;Kibn_^W0RelJY07wsvjaUg-!T#lB z#kv8=+T|)hU{2KVC?Z$P)xIPbozZB89_mH|1;((*nT_9rT-MoqE<{ScZsRx|EFpYM zJHsD(_{$gK9uygDFGZU>WSmYbUD{o_2IuTCzdJ8aTdW;+qrB>%v+{=QgvFLrKG6;q zq~Nnz-6nF!N^z*L!35-*fnAfqqp!ndi>xBxWgI2|K9I<R_?2AMcb#4*N10|5mY}WI zAaNg;+)nIF>SM&<IBF!QASa87XKsQ(;-X-f2%yj?paCx9oN*qo#VdXy-^KttbjyR> z;zTk*g4SWEhJS!=^CV=c8e-Gjs)7;8fjwjYp3gI>r=LA35MhggM;xCJSXn;kR%{H2 z>qle91YT%No@yaj9b3Lh4$y@gI7oE5kT}jclW<Xa^Rgc@Q6(IkM6=q5(`GN-E-BY> zBOx2!LrU3x?OF~O%$kQ+-_!lHo3UMgeI1iJjRg@e$K3>y1WgigyRamdtpd(Lrg9Wy zrp&vXCNNc4tV}zWM8|!U8WOlA1fmBE=m6^E+I)*pqG=uas)A+haJ#_3?2GB9qZ2_y z6ICKlrk_ciB?q%}#0{@Rg}BK)efRcYPcJK@<ez(uxD}GUE>rpqT871qI|cEcQM0-@ zc%{f+;xJx<tIs+Rw1TcAPoG^_JvEAo91uz(kOdu0fXC#`%{=C*r8!s|$m=5AfGlRL z&jDPa*d}SoIg67Fn3olicf0q>w?CO**Ww)S%XJD{DdtMK8Lt_BY|d5SRyQW&QP}5> zw(%T>4v4_`kQP0J!kvB`ua@}Iga9>a#zvMYWYUja;Ah1@vM=U?NZ~(TJSkZ=Xkj@D z#4=!&7Z9TVU6lIrlt6h{wlTCL!4CO!dD=VVn^Nfd=Zt$1?gdbD(I_pGjRNoVUzy(< zU)cJsSAUkipv+LKu!EJHsxnl=rCs{B6Q#i^lvL7@wUNsr^?le|6|y2vXoppqh3r%M z2rh|r4dSwo#AtJBTv@!SGZZ+d+9x)KfT?$zMS7}fDs(+@`8t4@o1*>YJcK)X&HCeu z2iO$sP~#eHAh+i(?<j<3?G1m-3G^_`8Gp+)X1v@IlBDzS-2Nu3R`du;rY_>ihoEfg z)hl6h`ordEtO9f`>qG<h2Vu5>Hb6VxBMIf-4**w)0(7l(3o1dk#X<EjCvtmjBWq=S z{z!ry>MtXGk|2mNlIVMroX6ZweQ&Q+FW=&Goqw{0)ffQxO<HmoS9hqZey`1H{9i2k z64rEjOTjfa0t)k)I*YPQR_~uNZ;56&3S|IuK#ph*@6;4K01bbq?8!{tKoGD?wiADF zm&tXGHg(sNsVGfH1bc63Mc%@Elj&mBoyEfsiIi8c>Tkpag}18(;|+92+foico+xPJ z=s36?pjsvZwLKvIlIvrf+Pz=mDWwXdKBqdX(6x1wH;t_6(0scq`dFZQXbxB~-oa<C zr-<KI=sC_9-O%oA%Jk%!Ksnaz(veG`Gp!GXuDc}*s^RV{)F_7;21?&ej8y)C?|g0H z0+*|i>Y-TNu^+XxAcPz!#KV2$%A?alpWQ6t(reIVRKVA31IN(&DUN$KLzbHjA!+hG zXn}e5<@HxmlY6Uw$s$O!v&nE!#uY`gakFZ!sC;^qA<-d&r@WnRi|$(yw?t378kQ#H z8DLZ|=f(~SUH<Ib`@9vbkh~ZW)i8y86a}e6Vg_BR>6L+lW_sc(G8~(o8YfzlJ$JUr zQx?;3EHz*s>W$^0poTiTvvO(n6VS6>01w&IfSFA>7&TQztR)}AOU0-Fkf3EyQu|c{ z=&YD0dhVEb3pp^`c{!|DUmC@e2$oqgBk)HYE;x%>b9vQXm&xO#p}<hI_E;%j;L=Y% z{Acj=6eW#hb0>>#GIo(Ta)~F0Zag9M*w?f4htHqyl#2h`ad3ehboW(CiN6-_@4Ql7 zkl-e>Z8R2IR)h~S^(FWhcNha}=q8`+UcZthm%7*ZOfS8Py7c}Shn7@XcCunN1Ej7% z`6i@_wH#N5kmNb%OVK2YBVC+5=<0$nh%)r~Qnn8J?N<6<EmrdP$^9cZu*+{OMSAqX zbtnJ0P{)&yH|oCd7xn)OUbqc#R7G+@=#$r@hdy$pZW^0y>o&!{MU;fXJ`tSd_U_mC zqlE*MI(tMlWBCv2XOc!H^~Aq=9XF8=+qtf4%`@WlzF70Pd|vj2P}yA-{GCKW@tkkb z=<EY3YTf#DnC9VvoJ8bI5FTVhd{SDqR3xbvK~L2?;tNZ71;6uUX=3j9QX|OE$Ic5t zgQ8gR;XJ9^k1jg1ep*$#3B5&N`jAT4m}N;zDcRBtMmJu0Y9cfN+wNpB=y94kw#6ru z*A;TqlEBIT6me(5nii=1P_hULZ_fj|LfMA*mQ!vF04P)1;L-y1U>eeZz||&T0v*N3 zHBmD@81&-k@n$6#;&e4~tSR6sIP^e%uR%v0W2Hr=*d_xf%)RC{H3gwQEERP25l=MB zeJSi#jT$W{L7rD5P>OuVUr0ekY9w0eM|EBRtt<l!6~u{z^Y8v-wv-i9N9P89ewZ=| zcK-F89_Kaf9dTOYxc7I7=~`|Bo*l0RU>{s`;SK3;rYNuY;oF%Hi8eQHaWcFF@%AFJ z-*7R%A897eYi%Qa_Iq?J``!^Z@2Vdz!CDQ6LeZ3Z#IL4pU2*qIH`iEa?v?W~&Z+>& zguIlWxH!xK$JA4dHrG7NNB6{>EhFa(uzdc&uo<bO(=NOX882u;#FTk;)jwz<s~-lc zEgU)m0iito^<yhfozz}|bVP-59N&%JOTr2T#+Hu&a-}}Y>9b7M1YoSU*HOITBI!*v zzxc8ClLm9@f>^wicMs4Q)Wyh_sU9(?JhGWTJ^DM=!CGeLYplAUOOi@DJRr{<`|#yw z_snT8y@xSD)5{sO;K*NcKhJ)Y;14P*KnzO^SA2V*WCe6N6kAUbzR(r~1-&mE2KBUB zc3NJ5H%cI0Asc;h-qMRX1pApA77lSDN88-CUqwRtNDin=Tdp(qPIAe+PQHWKYu?XU z9zWDa$Bnn^UHP|OmFCsLMniH(HCePKVMapoP4o#0d9EtOO)LsmitHpgPsP%VXhg7g zDas0L?Eu5mk>zrTlFo^|4zAXJ1Rmh?A0&)-U0Os^bJ{W&-TS=%od&Jbc~Q>I!P;*d zTGtQ>Pk<QaRzJVM`wCf4?<M&e@<{DTdhEX=P)R}PM9;$+l*2Jm%qe7=TJdCpb%%+_ zq20v3GSa`eMhQ>`b4MgXcIJI&TnL`}jS}`DU)nXE<95}$B|0;n<q7`>?IDt9Qc$Bt zt0n0z4|PL`Vm#ZCJs<v+g}*q_<<yL<M>TEFrGxizml!d{#uFNppA%>1l4d!si)#Fv z<xHXN9TMu;z*vEbe(JR(J-PxX6S36tw+@F35;EVHi5*%j>U~#%n{+jBUBL~Z%(&Er zkUlkTSD_d1S-9y3z0Fhj-9zr-$H~ZY&`e5#2eRfA9X#quz3`rBM-47EqdH_TN1G{} z`30+l|CrRQ7g?(^ltz_{oC#q`tv!hM`Gik5B-*fe`C`~m!&<Ff=YMX?bd&`x>G|)w z=wMtvzNe;GlvpcpLM@e!eS_+)``zoN4<$C<!Xe5p_XgM?Q<Mj0DcxCUJTO=HzD*5U z)w#B?Qm>G@?Jr}m-H88_dfLM&{t1AvtT;<(rLfleTv;Xnb?Yzl<o8GIxx=H&X!FZa zx2PB~9|d%%!718yMlJ60h(crQ(O?*h>2r%tl8_R!6rw!dmpA`!u2FyJHhjT0|I`SD zORd}dhoa^z7})tV{)RASBZVB+-r_geIQ2CoEd3&jKTaA4O&gRmlo-I11GN-gIJ#k{ z4(9I2SbmVL`q5g=6flkl;HOujMkcs-lM%F=4g~>n->2W|j!qQ2sAm7%^CwTUR6-)@ z3KCzx)N;UE8@*gjAapA)%uSA!4X{jvLZ|>!TuEoYSQM{4#=UtS-;7@<j?2j<=X}?z z6ZwOyY}9;3^V6HEd;tzAbOnby5g1habX2g)PQab^zXV?_8&S^!kved<Z@&Wffi$uR ziSv?*5Esq<ZR`Vv;UJx%`!X$K-rcej!VqP|?gcSnMi7plCgqZlH~F^X0%9BhMq<Y! z4h!i=T}WboDJ_{ShewTU!;RC1mdU%(ah+t<!7){fhPe>ZIl?ziU~}(yA<b2K^!~W^ z2Kcui6;JvLdXw|t!DP4fJNTlULC-8|F{Tp@=50|aIe5;2JxgG&y(6-j6%BTHXlA*{ zGczr$+i|*xB^LEQzYLM@wn9LjfH`os2f`p=N(X{>zYh{(ECdbrR#Q1%@1UVf_@V@7 z@YQi=_F(360HCW<PLY!~0H+#M2Nkjhpw%hw%aK8Mmp#Alk+U~xt8Fy!o3gig-PF+$ zbWp+2v~>C!QCA3HgX``uxOp~IbJYo{jm_Udvb)ZsGYg973X;M1%cJH_u`YguQgV_~ z{A5N6-yz@B=}xf-pYKxM17V#QkwQC^ZN85fEbq0xM;*mWBlhYZc}O@_*IEoV(jM2A zxdPwj0+gK2o+Utap=}y<(36MOn6vs8PihlZ=!{yl)Yxp5L0ZZ@L#%(O9$>S+&^&@X z5=n-TN91uvH(lkMg*x+>%QJSag{-3K=Fsj&+_8<GD}55}S8P<Xho)!f%dd?^#&nOe z7n#v|>KF6)GsWu5w2k3uDT?4G0+yc$Bt1_o8iTQNQ?m5(yI67*i-pO~+}=D2#gnS? zHQyZ4#P$=`XY@nSfWtygFzYq%)>lT|`4?XaGJ)$U{;!XU<mf!7y!5DrCEX^Q-O9uk z>+|Y?Z+|-+sUV>rT>WqWRUs%@LSQlF!4qeV;;eaKUQXkkF^wHd>Om1WYGL8AW=S_F zc*EJmLN9h&j~kXLm6QFRJbrHru+o?6elXv*N<Of!o~hf2f)ELDsACmeJm2n+^FjGV zrGw}I3x4)~pc1xN<Ya-G3xVchA++bVwLMeU?F$egkSV=Na~;t5Bo%(4g8l{>B2`6L z*h);YIO|J!#SkE?J}TfyKOGSAREsWhLuf%CDRF>DmaejA53KBMJob%~inlG+nVdS5 zD|w6CD@O`#I7TaxYo}b@iI5iOI-m$&oGlqRNyTR3WNI`%l+B8uJo5z+uL;~hh9;)V z(R<Mfw&__3Sx9<og}%n!uyyF~`d8BL{JN-ti7Bfu%unwNtY}_1jNL1pj{@;j5C5?Q zWrio}c<em{2($JXg_cFWRJniCWndc2W0fAgdxEf4Ba>=Z_2g_sFNI;+H!?Reme@a% z+&~Cu!AgcXv!3=k#yvZ-zUDZG*h92k&N)|!r<apgD^Q3h&op4?v)%TQm@mKk+8Km+ z10rZ$nGZ3N1a5xIz#jfu6JgwqM%WIpATBu1|B6;Iv5-W8L`DqelV(^GHyP^1BT>hz zCEXGAZVxckP_leGKiBzyY?BFh1Q(z3*aP`~nY+%AiEb`?3?Q8?W|M%SzMJVYn()&; zNby^3PRS9FBRiFGxS&C|%$JUC0t^C*?#>U73UF)kDj#a+R)Rr@8#RSopM<?i`OBU3 zy{x4`-pgpF=gW4%k8=o%ECEN#qT6zIj)l{mz)mMrbA(r$K4|~nYd7>Cx-&3w$q#r( z`v>B6l>N}^e&O<L60szNBn4%LTXQN^jgffl0&^tF53{I_!=r4&kerp=-<-p+my@+T z(lIBGZr|luHJiE=`9l7rX(AM8k3=0?UYQ<#&VhT#_QdUwR!m>W9f~F5qM@LV>-LJK zJ6CqVQ^KW=o+=r?NML+RT^A&)e@BQEq=Y`v=JGq%5>J`o2ARO{>cBmY)dsLec0-M# z@Ee_`sl8rjMR#uZ&<@2<ENL8!|7mzK*Wx5O@>Te2qs4*DXuT|<%X#R||3aqq4>vXr z_to6D+SaLG3pyDS^vDobLZP9aPoIo$3S%KzZqJ=ApS^88zKjie_8tZ=xnlO;b!8AO z(&^BQF_D(HPzHUEMqMY38dbUKoV;1>MTuldye26?(S~8G#j#3A5SaM_GQN!g*rKD? zf47ajIiFDQ>?V#aX|@!UBGZ5vg}7?;_k9l?K`hqIjk>K815j2PL{11ofosyfGZ#%> zN2ikdS85RqDZHNtdsY42H0~NoXMyEMy$E7_pURY>V#-Hc4r4Dc<{R4-(epe(B0EB0 zhwwaBPl~$N;wykoFqZ~1X{da_RxW0)U4F2xFwBl~L|K~#Q?75F*_1N)s#&Ut=DZx< zDJu-SuN17D3?V!ME+Yxy_r~_^)z9d`AHW5ES{}8#vSdMAo}78R6NVV}NPIy9TN)wX z_^RyDajU!(Oht`IrZ;ulvo~{?^?kJ&242yXZ%?LsQ{TJQUleI;VtQXOUQO*sIjyfj z77kQUvJSfB8ThM-vu|DQpC#YgoTw%wqYS^0;b4+<38~f<<*q(u6KbE-Egg`&&?OdP ze&ZDDlyBWt0SN9_0&4egnxF7t{1XD<a*$MaOnxP_FJx@GY>t*>5XXZDSi<L((sJFM zcC2(fR*3Z8QPF3|C6eulL;?f0%$T56dMFe&4*AoEd0#d_C~)lX=h`BJp4D+7zmo*i z-DAp+jrfa2rbM)QmryfWk{!_eZx=OnfOyTS(pMD@lMr=s#!U(N^I7ZCQF!`}p)uTm z>$C}!(@cB1!hH$pZ|qhg_^Bw(j^3b0%mXbNAqUavrpt*aBpCfxW=L$<v$Js^Zx;qD zOmDs`{e<m?(Q5#@!#3wfot^1OqhSSg9mj~OU9pIuE2`>`z)dg6(0X*P%mTF~#xa5A z{Rp`~Bc_?`FvO{rV&@DB@LKMLd7pi#5loMLVmXAG3v^{(#%KyeU#f9rO8RNJ3nH7M z%uw}!2Cu`w#xD>%G$b5#pjeFS@y*LkrIMmdeK1ic{7ts>621@Ly)aqL)x0~HQ{l)7 zsoRm<1ovl{r^flDd@gZBaMwh%*7MbVct&tB<<q6@Y{Q(88}cMcZC-lzc0{*J_Ic0s z_%afu;?u|9HwM`U{5~eH=B^{l5yD|_9(%$mt2n7e;IhE)><FGXYK4o0PNY{e<$wjf zAZqUz_}eE?-Y`0+ZOPkYI1*)6;vxR>enOErdG#3oH|6)4yS8WoqFl%0fF<Ee{P(w5 z(#L@}3*y&<=u$JZBz`yow34mzmg~^SEX=K2hN7${mC9$_+9>eR^(H}CkQU`id=RLb zXe;edt-AAOJU2%`@hC>w^%!F6Q=k%K3I`Om=*^sX)IzyBEp(}!%fO0*wFje>chbOs zrS52VSh(&s<lI|lfMox1!A~?K9GFNRq)HyY7nCjr%@)_P${sX;79z%fhLNCf(R^WB z(sGo%O}C<ph96x`bfXRh;h18%st{)`w=OY3V4Ej!_`jj|wVj=>@-&I~+EBQ~$4V{E zfo@kB>N$3?Xrn`URcY5i^*5$KJI=i3v*pwPF?AjR9W@fZlF0)&PPPHxlW`b4?YP@- zobrSq7ii1u@x^b8^;Drp^l<?%9*z{e;%ShsXY{V6oN(-;6qPZ7(LOkdGM>b{w=w-j zJo}%J!XWl&GG+p@;G`DRQ{%q(1cg2s>>nk2aHvXFu?B8z<wa%b<IASg+At^d5Hr(_ zH@i~26(wk9)uhokW8V&<l+jBN0s}J<H<KzR+jHbYa3U>J_A>Z?`dh~GB^HM(C;YJy zi=>WzX`$`KG2}#O_D>a1z26u9)Rz`{^|58*SJ?IGNjfFl+fd~l?TRy-v30niE?RJ% zhVSqS#^Di_7VSG4Bj;xDRBp}7B_<mhOI`H|z&c4>ZeGMNH4Jf}*eLEB#TqZLNcWbw z^803hLZA}h!l8esM#}JFh{Z~3lP@Ixz5!SB7HfBJNuJs;m=FNH$oZ#;LyfZP)gwKw z6y4Wi=>!CG)Uc}#!?w^kOO9?7w2_9{Nw)m{X)%IItq?ln!*?ioROdh^_BXy8IgO1h z99VYKlf+wf2qbNRaJ-PId%&H*8*6umMiKQWHtN7WXtw(BVAFT_(Tz%#<Kz~%jpX~+ zI~Nwqz*>tv6+8`Wixd`|IFQcG(Yp-Jv!ETNs$MgZ)dG2}7u&{kh)a4443qfeZwB0^ zy^K9tmWo>I3wOcIqoN&JtrPNwpGAaO&YlyJyE-lQzr!d}=&FR8ok@0c*}$Xc_makh zYl)$2vH*`{W*zEfpcP~jHceaCAIt`Ld!b4#M4E8-(zvo^sBJlhRQ0wzLMLM(4rKbR z&(KjqBq&+@1Fj$%xWs(|^c|9Nm6D4D!cag1w?i}?a3(9VS5HTli|-n5mS|Xva9nBu z^Z+ToFoYTSBr0c|l6Uxg7#9n~pJhzxB-EN00Y#a#PDloQVC{8Ye5pc3(sC#1R<o=i zNPx=l(Q?phIJ}+*(I0irPsP&>xK~&R%sTP1{epI`E<IkmktuWqV3`<i#?>kRSJWz3 z)@gT^(HKE5?GW<^^(obD8vo-^;5v_LB%K2;DLE1bd~ZG^`b<kFA^HmbbST5K>&1|2 z4<)IHld#I}>`al{dh+pg7X`%=!c^$_$hZG6{3Mj03J{>PC!;P;!d<NG-NdEIY@nr( zc|#!!b(%wbJ`bA}{gh+K{T0AImo?(Z2pT^GuNp7|@6hbK8*;Bjn45fq(3S6n5|PHI zvWL8Z6Br9!ARcstkK>#KhuU7XlwaLd$IdAMYNG-VBc%vcjs)Ynp2&4*o=ck;H{t-d z8cvuSUv?QF4^ooHdf^!EMN(?OTm9y-@%Z+{r+{+)S>kYNLux5N+7g&NJ-=b$YE>|7 zr}*0Al;Z%;{{~XRWHTuXt(O3~28IF|GH@D8nOuX&hBg>Phf6XU5?qjM8lr@#i~xEW z)Te+oI@mRUG1i-Wp3{a%2@sqXFz|W-#W{yF>&U#Sr^gQs8@XMDK{vv6hexol4Qw7L zVqD~ZOYN4!rV{bFWm1~9LV0y|GRy^O8onS*R<S(miSi=F$RRuDdS}db1W#yFZb@wY zoyyCt%fhh1X9PmOe2n<gUJUXE=|`xYNDYd?{i~`}8;a8jL-s>q@@WGuaf80oq0M?J z^O5fa@W*5zO#?|7gC-1meha1lpQSLMn|Ox#=#j#pU-rk7av2#Gq_X&}QxY^jz%lH% zGSI4irCg$@dNher`T|v5rc3q4#|v2Vs`7)3V5^ukTJ8LadR&=oX4tm~&rb6-xmlLK zYjy3fmF!ZgB!35o;Z%x~YxSWTax@?d90JTtxRozlwLVpSc^n^7e4PE7pn~YaH5L{) zt>S<1eEaQHL2%?}`|zM<ba=Rd-Yz#jPPJAQAgmt0yDPb5gwy|>WnXsACSv4!3K@@X zL9F}NaHjZ_l6=XeEe1_$%rE{Tnr><L%}h#8c~M+1JvLM+sx}YW79PzDo83+pfnPvg z=TjP4#jLW>2~hTt?5acw_{)b3{670H-Zf^A#>tt%iHBUUboRnWeyRqE6R!LSF!gAA z6!m<#ul{@tA>}`&uqGg3*m~2nl|cpD@T3CZb!toWuta?1bi<enP!D2c{8fWmT<~bo zeI_;CaU#L?3+7s*2Q3ONTY=dq78?Pb5q>y0ClNM7IE7{B;@-G!n15g54z{ilbr5cD z_@i*B1o2QIG3W$K+?O%ht@vtY+IT>2_34OL%d#pP;4Iy!f;;KtX>;<=%M+!PigK-y zY1@k}N}>@YM^YUlcqyy^7WUc6uF>hTl3dnxOuRYfW1aG3v@9#j?`%2VZ{7%@X6vwF zbwXwIa@$0WFxjsQXsg{3G#(!nLR10#*6!^G8<w0(rtz+E`E59lBcDpmCm7dKFLKb+ z7;{F#2|Eowy0{=nHymo!KE@I$(kp4jwe{&<rEr4?+%9D)H+ajolcWfQwW~p{tPD>f zOFYy*R<FzRj5<!xtJ=#3@_C?@l#QP!DUmkN|03lEjw!vQh<@2g4WQOU9R8!SX3?v$ zPOz5G{5C_3%8{~T3}dZN>r9LRh;atH2nG#bkBV#FsXTlzssuS4f0p54;l3;hwxtQ! z{1A=c`2^I6*nDxg(ZpSFvcm$gOHc*c7ApygmAalHNHA<PayoxX<qpwxH2G(U7RAVo zVECt6r+ajX5pJY6FnRyGXCtd{02>Kq1fKUcLLCrXP8p7a!03VCMgI~E%>q5M0ZCbe zdiKglE#IBe0zzzU;_>c!&2XannHSXzH|ZjA@`pt9mh*Nb&hj#({d=Jp<uV=Bf@wJM zIFsC$IsA@Dn8`@I{Y#0Eb8+9b4NBNbLuc{PQgY$!#`uChOlah=`R)%UGFiwg*k1Y` z+1FojuY<HT6^x6J>ye-Q6@~43=xsnR5C#1yJ?f)2F`}P%wKRA#3_;iQ1;nbMN_WFJ zWQ^~+KlzW*)FT;j)dNJ-Lun+k_*cAT!xHQj8WnKVlr-{wB>b|J<Wkqw$O8TyaM>{} zogk>xw>&WaVBbYvYB5&~4+cx2Ai=K0p=gNdvfBiH*SB#-&E?|M)BDhO87KB7nN#Mi zxk#1=Q|Sx5!-oN{0hR^gs4G|-Y($%Yf@;%?WAw$Q<{P$>%0m$e<BViXAG~&3uB>gL zw<T;WlUS^__r3J$3qGo7qD$>PM`yHq-yO0R3M#Dva!YW8ygG4B%<_cEX{Zu`-SQ%5 z(*RCDvA@jLPuIdJwbe&YAKfPn*5A8@+Eqn_SV2O>r%pm-2tbujEwz)ukp&(b4{Lml zxq+?k9Jh1xLbJFkc8BbP+DPJ0DNXXVz50QuIFEq_bCt3&+MV)3n$|3Nt34D?%#(x+ z1&AoyrMK*xmMQ>;TK+;a(w9YA#inwSo~YWBAO`#n*XemZ*l{i~wDCT1*MnTZ2o}Qj zQkNl2$L<=dJ%n1@In47tuF4G#Uyq6+|CH(BhJdg*I0a`Du~C1A^yK1Ky~}iBX%O&5 zsvC3p&ZEXE0~1SPVxB;OOH_5$7i6f-n*zUi9_P|$my3*k?>&zkX=8HS%h9<Gz&|zI zIn4oWCl1`d%!Z;Fk!G2_DRRtvcj>eF)}D#r7z%gzIPEpsUJIL?HLqt=s>aiV7d}17 ztjSSNdBd&x?dfN$>F_JjOFA@4Ez4XjpZ<?MB5Ytd91fBLNw=4WAOweX0H+?1hY_uX zu03J@063d-tzx8d!)`|UDp>~+7tN+1q>AZ@ym30N>}exQuzIr<H4%TXE@2D<@FVcz z_)opyLlcN=KZYuP4BjmMw;glbLLHs=CD-ZICfyik8m5#Ek>l#nwoD!Yf6)Fw+9=G8 z2^EM_j;A}geIn8hzGW3!ZqO6_jA}QSs`>oaEMfBuwkkF8n%lmtb|$&mbAY*n`W|DO zhG77~AX^OtwAhhdN;L+CXv8*8a^Y1;G&sRoXM;@9{GC{LIMXjYd>HC*2`MZ6RhTv9 z)orwB;5q^poKx;vyuPyc1zqImaKSr$sNhU0Aj(XocDD~_d!>n^8lP(-@pp5+^&=gx znl`cKf8>>PJ~EK1`KkgGM6>6!3+i#`t`s!r3P(CVC&hxZ)uaU_NCZ9xEYcy+xB^-a z`pUWWR0ZWhbuZIW%D3y9h14-kA^qeNF^>_U)8kSJ;kF3Sl{pf0m1mz&XxC&|H6?X< z-R5SkKUIB;InO~peuhfxX4yQPHI%3nL(=PvTbl|xheC{a&<y2Vk>Y0O|K*m;M!@?P zt-LJ12nrQDx40$!l9x9#deH@-+4~>tp}n_;?skywVfHlmZ0hfE2P(sZer_T!kK?1A z=ii$KzR5&|Suyp+?!5<a=kD%fGz`G8g%@cY5812l-bS0P`hDNNKGSQRfLB)R6>zs) zSco>XJ$z>E`N_nrQs+xQ@-$O~DzjVs9!W5qShDsKmbt9=Kl2&F1{Z(;ru}emgOS;Z zQA0LF+PTdg&$4^5m_#FOs030@b=^8LG@!c9WYplZw!hrLX+`tEHJJ3i(ob@Q9?W9z zZI27Kt4Lctq6a#$r}RNrrP##Hwf5=T2Q5z}VaMqQE{pniXNqLT={|F`*`lp?pS_d# z!$?!4Uz*f1j$ey2Ex(Q?zD`7rL<dZqr6Bw+9Hzh&$<Ln|loV?t0JBK{z}vlws57g+ z(Nz0-)*&a|;taTp3GMalBq;oh5{M^r>-gIMux${F=AH^9jPhVEA3aGQMI-K<r{%tc zggLkpx;}1$VO_Xnb8t5ziqnq*tF&)LxeDOkjd<uc2wL-};0#^+o1F$<mrn(ll)|VC zQCDtT7Lz98QdI;;DHG-~bO%r)*K(28Aa^&ULMMEycEV?-x(w)zu)RS+Y;X|$Pk(Uc z&akIE;Jib>=M~0m-W98+j(Y1nSvsO-sJs?+-N7y4Tl63}#H;YDa2aCesAy)ri!Y>v zLrE5=dz=dhTcHz^wz+3|QpC0HYQQ%tKm(>~3&qtDb^kpS4-La`R*m-Ky8pzy+z3S? z-%SqiQ8<t{R4WVRhKjB+4d^`>4u)drQ}~7s-(3B1HN##~#FSd1|J_vb)}4+^l<iNe z+Z+}jNEZOUOl*%=LenH;{}-wQS7q+*2Ub+9fo?<fjLkFaZhIgKl*=rt0W`|RjMkZ+ zYxK3mQUMm)m!)>vL}|($)>+?>1s+L(L6m;04}dF9@zjP|7xdpDZJ+(VB?IU;0Fk(Z zgd(!_fJ_jI_tE>`rFrK96|eP`?9DP7YcN@?<n6P~z#}y-m;0AMvKGH2s?U6GbII`f z>=lvn)-fT4Gk^uU&ssgS7Z<G$UQAk>=BLaOAw9ha796O-_t6R+<|76Jv{)#_&9@=| zUbH;89bq`oO7Qdru~9j&9{`$JEG(1PFhpKwc2~P%XCca-+|Pmw)7uCS9|)$5fW{yk zMTZoi$+C%2{)~qJ|A;FV(gq$&@JlLKg$qz63<4+*Vx2;?w>GC1JN2$yJtfo2sSEz~ zGUNnMD6y^SAArFjk1D79^(eV(VKG$0OQ`PdQ@Y7<gecY1&SBA5aj`T|#4A`X`K{Yu z<)!s0XE+j2gZ$@)c)u=SR?K>~;J#`MV=gvx81QyU=#dcZ2(0^v%Z$WUpROm02!~6S znym444dR^DzxboJkTALvH`@kV{-(r9O{}=4YaS!0r*^i8P3QnvW+FJtl{Hh!l3*s% z`PKUKUT0r)VJlejUMRSqV>gx{Jy$xL@Y`sjEqS_9HP>Xy7nZqRQ0u)aHwq0?Q1iI* z_-GqL)(f6EvYw9fpwQPf+cz05%DC-U3)ED0&+N?M5J43glOR%4zZ+BE8(5q$m;?y6 ze|LE~Po>p_w|`!FI;Nz{UCy>_upIpZzO|H{3FILq&$h-n1v?L#!bN>tFw^)AbX9_D zlk#_0iWOqy%f}h`^U#EVkgAOS_0F<Gh9-jQaPFswleIO1o}%>3V&tud%OhEHaBj}} zL{Mh>{8uqRXHL^MmwuD(Ra>cIX@<|;c`B8>orC=<z}p%QJ~U11fOZhWRX{COddcy- z<eGOE<7lc<dW}%YGfWCxMT30qF@rz}a)WBx<rQ*iEL6{*OF5}M#{r#k5q|?H?>!7E zsK|J(?VzRy&5BnDP<BSAl7SPVlp}jo@WwV-Os@?N&>f{fFDt{cO>WgqiIz&P7~wof zMCY0IM8v>O!hRo^F5$ou34gEvLsm2D1uXek3FcVCnSHKpiC`r=+;`~b+9^=aLq-j; zsng+n@d$8PM)(ZMI8LM#6ATT*zr8RtyP=b1!V?nAfED_b*P%@zYIHQA4z!;&$~ZS> zIAUqxVU4+WVeX5crL@vrS-)$4zZm4Vv3JLs3^`%lD?TM^kCCM$e=KD?_fLb9(I%z0 z>L-A8lmOGzFcdno@SoadU=mL%Hm{s(4guP0!ZEvqW<}itWU|#1{5i!pf=+9cgpztF zEYS^}qMlgG(m!Sy>yw^Tin+X9*H<Kw`q`Y)ff*uTD}(BAG(;`=3>(>n!N}`ebr7D% zVnz}9S<g^w+|Y?=9KiU&GIewSIIg45o~N45gVE#YV|Wq){UJ(kwU+o3CZ(g@xi8y? zH0hG+`FAA$yH!(fBW?lW`XM@BL7#CQK`v$+xzK6t*KZwZE8T!?bJU~hQ=?}WJF};l zdD(8(QRIgr5ny&4nOsFDrA(xh=4vyIuB78PYeSseD9T5DPLjPd`wx^j@DPwyKEmT8 zO69UF05muT-4<m7Hp;1bk_FoHYlLn<J9LDqRTs8s$0dtJF=st&(KrtPu|(jKor%j( zIl?pef<kXway%`RYGS*QOYNK(cxy)cBN`^Q@}B7oqW`acE9aws6JVLZ$A%}-A0sa3 zvyBqB#EJARyMg(M_)CbuQ|F@Gtq-wLXguqzTREd?mp$W{d-OO9Z>aT0BZFLx3uhZF z9o|XAd|beTz?89PPez2aHyQm7<t&}+G!DJyh5&f2k?rox_gP=as6&OThdlcE^N>y` z^L4%nxPK0Xl<T!_4UEuK+^4|XVj#0dPQ2S=CJMSdaOX91@_^tK>qgkjaIc1wO6$!v zsqs@0btvq@<jg%8j@ERJXTE_^_y=v=V0@m>2REJ$us!q4P)W@BCQtex!2q2xy4_eu z${a36|9<CU*hw|PG4`BWpw#`xr=H9p@*=BP4RI0X{6&sqp5}5E_1W-+hHE}YN~X!P zN+d4a`KJg2AuH48-Gz%~ORv-)s}p$D-M>PQd1POVu-qYlDqoD7u*X_ooKk#)BhqtS zwRf1^DN~rE#kcO4#6Zq_e=sDfan!OMAyD9jlg$L$7|WKbC!+2IRin6EF<CDS7`KG1 zzeJM<4URb@dn9G5LGe#^F2LGr^_>L;F>pIYJZpJV>~<=}QvE%v*@TYzr?w7Ij-*7L z3M%HyON*Je*zd*!eEV7e+i=wc^W5CY=Q>r?Nz1%L2UK&&m4KswPir$MF9g!Z3V7$~ zp`r~nL-E+m--{FtnV(t8Z{o>?-BEJ|D3l|SYCkmk!jFHB9n~FLbholvFkk6_t;iti z|L@(;Qn*Zerek$$NsugU4%U>{d5sp+l~0bbe|2QgRTvas!#Qtwquw?d3L*^OH8Go% zr6thw`m+1b`nJl8-BiM8aAz}22pM!Pe{I2_icp9Qu*Ee}TD@-}aG?BGMn8wr!oUM` ze?V}E&QV<-%JB&@O-UJ}a&o`1-VKQ$t;7G|aAl<;1WX19U1nH7>%jEu#u_!%1yeG> z8qOK(j;8^nVaBSUdrlgV+}TxR*g7)uHqrvl8qGxu5HD2}Td8epeb+&@DCNyvWH72s z!~bG@kiUAxWA8_WE~(GmSLhPf*~<;Uy!U}e2sM~lJd1*PG7}&IXY3nLGm0b{;@SoS zw6wI)v{()!>i(Z?ih79m0_&aXr{(NZrxD40!v-K39F?e#7bJ`4zgN~)`8@j8?Yx5& zGSwU9va}zbq~VM1bYxSg8Og%0cpuR9JVxi_QmDpB*3OmHGUuD^#L@luyrcBz@d2Md z#Skp(R?ea2Itvp%$f^KBZ^+_M30_IdRZ(|wcz;9yC=XgH7&!isLL0D7>Rc*p9!KEy zQV5yE+M?gq{xYx<aMM!Z-S^fvF^m}PTc8lvlIz%$y0s*OAlSv&B#<&CApl;AL|ON| z_C#U6M+=`Lq!${wA?ve9V{u$_=;*TpC!ER0MBmr+qU(#~Le^6U!w=Le>-6T3bgDCd zkJ|0ahDf3aAoPSVE<3L{*xhg;(JZQ>Dz_aZuB#Vw^rl+mA*T<TEM4oND{J0vb5Z}` zE-?n$uBm4V(ybYjgxz%SYGC4p3cGOF!{L{TO*M5`i~g(I+8L=E%6Kt|tk&H~5&4Em zV1?O%)Ky;g$L+$ZpPtvJsdn3+H+W^&-F$_e21vD|0b4D*i`PfH%MwcAKPFP-Rz3)O zAiDq*^%}kbAe?ahGsK+5RfVz*m6_btdg!!;8k{904Hy`c0;CNEN;8^7H>=%%NA&P< z5h!sF#)sq!HLC{(#CXM#$?k#O_$`PrhMfS@q-3(HFzb%%bg$lKP>&w~Kb2W_re-X^ zo_KV2uD91Opt}uh?qP0+wV!86ph>p4c_iq;U6ibp5f(RT`G5uTht^kMlN9SBS0dG9 zN&jXK$3+~ewZpM1C0xV|YbX!m@*`i6@0hgKfKOU}{~j`Joq&5<oP(*Gb?m$WGkiP* zC$aqXC7#?MPFuFL;RU}~15YJ##Xi#NFCCl<%=NGceN{qr1crDo)#5t;T>#KBypTN` zcY7KXov1s`lrdP~9JqBsWI?fgx-7{~1a&P7?9o3{b;@1pjz%QWrcJ;uMX;E-zZ4=T zBt4vI=%aP7Ly_x>o=BFp)Mwh$1%p9nMH=e9Wke3YqwLg^*ED_VVJ{B-CzPnY9tec& z>@UVY35xf~JGh495|f|4UY4tq#Gx39@>aNPk)hv8NxtC66{3;|;A$U5;cHEDr@@1x zsd)Ncqq<pR*L0sd%l<KPd!{@J1jfhcj!T;o^0=cWNvm-e0W3n71uu1wPzK0qX9szN z1XKI|OX5ENqCI=J{B30UQF&wRLYp%^=3bH5X;b{+ZI}!AjKQm{BSiw4<-1&DWjF(C zNEw#x{6<>>Kc6=a{&GG#Ym!?hZE?T7&rO%ei!R<4aW)q^v1F<J5)}0LsG!UGJN~dq zFnUbk>w|}uh}vw<R&MuA_x+oszMCT57C_nL87nZgSg`{2MHJZmh`-asR#Jebbxdun z?+8SVLj93>r?cyB67yz#G60`$nXpGx7#(|e?lxn=Z&d*vJ44469orJj+Xqm-b+Nr= zuZbQ+D<$GfLcYPW-scHnP?e~QC&2ESNOUoU^UeHN)pF?Ae9b<>8H#Q}p;i;CgmoT( zk?n}b%FU1Bu7hqpL+wg3Bi17p_(A@FnyMs=A;K#whI28n?=vw=kGjJu9?AO8c?+q+ zucw#Shc28(P>d}OUwN?{m{lQ-D&v-#+)z7?Pgn6jH<#^cWg>ij5IbP|sw1B7U7b~* zkv7~NAQIV&K`wuiveF9{<2NnjHMM2OcDoPxatFTk?pMcHZ@a~CKW~KcTgbpF?QjMz zq+f$zYK_nziR3M3mGT5NS-Kni$?9C*c=4Td{9fQTN<i;E^=Sshe4tAQ{5{;5WHi}1 z^R~OcXD6#LlI59szT3<33N&UREaU(bj|%}VCmaes?+Bv-+SsA_)^QuCEnVq6UR`6v z_6Z`{GEj;Q=}bl)Yd*9nInYWp^hpIr9pH_|hpaq`q00xU!hMrHXN513J}cpUxK2y7 zh@q0u>xHVx(x!VPG>?*3Fambz-Pa61gUh`*=YnFa^DeoMv05#U-JyntqUz8`+Dw_5 zi&%oNdBRAsw<)LdQeS<nqoi-Y-Ke#?5aL^XhIQU}ElY;UU`7%TimIa-K(|Yln|Bh% zIR_^=zp%Iq3$>}jCqW2qm9mni9c2@usC=&x&^yU4Pquj@3mo>19anEJMIprXFivTS zt$7kbOSZJ!&*i7AY&BcD3@PHvJB^>)MjG2Vo>#^RNz5`JC*bXxux3zLt`qTruUXfP zC>gG4k*|NsioKH#$6fLyGd@R7RO4?%T~m6r^!$+26U<Py*5+3)sIb+&R_o|3ny*&g zto#`$NL-$yX#cP{#*b4;X40k?0W#7ze*q60?LfJqBFTk))hVpCvjg0T-0T5y`5=N; z+%#Ndog;781W&fhUhEb9b<@l1O@%f;&4NWJo0W;AF08MiO+u<XF3Z#TEi>!7jBUis zi@ipAS5}@4tx}ZK!xUh?5ju&RZ%?}6q+Uz%t1}HNn%sLTt~aJ7avs$R&ro+5<uieQ zAItCh@Q$+*C18&rL;71Vp&k(pGWdMYRLzKOJ2|oj#J9ryEWJB_?0NuYl?j3Py_U4~ z|CU#lWsmzlkd;&&+=vP}L~d%QiNRPd6Kkd|i+An4{EbCdl9hj0w&B{*#;^CF{b?B? zz?%3GGr`zC;L~yomeQA=Y3uEKGk#;bf(j2(O|z4fgCO9X6QVb%P1Gjd_4bq^Z3!g) z(#0F)!X8w8GpD$<hdos?$*eXVGcG!Yb-bdt#s)m>w22GA+j2j@mtk^JrX*B$IErVL z)-`UP8_?!6L<C2o-vNNyIVH}hm$Q@1%J0oDrGs>z18<V{yw7~OWP#fs7)RAW`7@XB ztHBgv#!h_%U6@Z$3=1I&CAaLb)n>r0#f8_+g-(oV>nV@kL{w34GjzT>B0;%wyr-j@ z&N?K9GL`-6mc3`<w6{gry1(|c*zGx0XSaGc7_L7?7`?xSz?cJACP44p!-I-GtuQ)~ z#aM!fW73g3&_}k_U44edq;2?CP4>v6;-dLU^NHlXZD8-M-PHLpE~Ldb!JKu?YMMBW z-V<u#A?McI?uV#h#9HsCkA1$-cES42XPY^**_S<05`J`d#~|HjMK=|gS^*RNp@pX| zhKj8K#W4!T{8825%s&fz@&N|o{28N~@b`L1u6jpcF{|~5@At(L9>Q|yQ>dxEMdTiM zaDu2M0sZ5>Slh;_c>p)f4`_hyiyPMYjb{ppzMpRpN85OoROisJB2$UiLbJwzfT?JK ztwx%+j{;c(<%TKtM{p;7KX;K6*`GxfGtjJb@fsp*D8pHX69FmQ#GSl&mZ(s9AyJ*- zkF3qJPbMmOvYGh@#i=j1Z05y0VAtCFrJSg|kC^5r-dZd7p%y;zc%_A4K%$%S_7sNh zPYNU!&G}ruajAzK3B1}I|E^*1KT2i_7A78@c>$Fq<1vdVeO*<jy5~+YC;XKJSn>4c z`Y~5~dz?A>?bZ$*Ir1k@8r$AxCxaTi!E;mB^5ki7toCiD-J*#H^HK6ybguEOcNPBS zN@vCjGq=B6yJA<Nmf&rq$x)$jn&jPu9XO_O$dq05B_Rxx0TBBeA_?L}b)+!JPiy`9 z6knewL-~!&<}m7fxJ|s9vR~(S#m-xK*cg1k6PA?zY*A~yOHNw)#u20~xa~8t)C!?- zL0KD0{2MEVzKTU8Fd+f!LgUbms)YuUa_hhg-h{9_;M$!>p0Fhz>97+QxiqG<cP zZ-7a3b<mO*JD_8Ft~88(2G((%`o~LzXCq0vZ(qQSUeEeW2LDMWb1pR8V2jsc_0XLk z2dHrm@R;G(w7^7uI$vvZiBZ(EaE+O{ax)qw7eG5D_vMgzev~%PPw@|nW4ZR4!3%M= z$k8xQwY+!0m^lYC{hoLvea32>0`=ls2)#yOU;X{fZunojGuAj=Q)B-Lvw_?Aow)5F z)yD%BHuga>9B`lp%63k(Y@20xF#9G)<xSM+M;LWCKf~~X${$0IbyrwD<85VjfFkA* zV5Vh>kxH<3(L%Uud}Kv+1LFm*cOOw<f{&sYeolgb3lc<<%s8`9+_yRvodyyALBsFL z5UBK;lAQzWtABD{*#5u-jCiYZ6~_i2T+)?v1t%rF5zCzhTp>+bDfu8#<h6|pErRS` zEN4KR3&$lxDFw%DC6wvH0XaFJsW2ky0@s&ip8%i+f=V|e?cncHGf}KO5`Iu;VE<BR zypawZG5L5=P+g-v2Z70uf@#|61^QI)BPs&}ndHwVj-uTtj)rt|ID(gH?c9AXLE7Qu zpRHl0S71tOYoDDY6UOIBOwCkx5hTX*ct>i(stSHM!}I5z(q(sWH~usa-kTV@6E!PR z*CtVx;hVSPt~Zj2gya+A;H2{U-T+rM0*6f>Qi2Q`s*--3|H<IVbPfW3;v#9b%_8yk z;|8_<!v3JEj9_kALS^_`v^)M8)U(!W(`2dd;hHMP1QDS!X2LFok1fqL=sc6gB&H8h zlCtBw-N%1tIqU*%eiUfv(a5;=(iZU|!qf;0UC>n%3uzU9A0iE+hXT2d$dL}khmDEY z+ES3rtM4Rj)%H_gR7=>|jbSb8vVkDzc;NFw+QVqW^S0C+3eY_GidEj1vO1P#T&fu; zcO+pZz#BC2|H}&tOEklm&d7fkC4MQW@iNOPP?OM-CYxhP2(aIU!6aOivfAeE3JIGX zuOan8QC$%na6@ctSfIc@X|_aCgFrA*uht6y0Mx^U&I0qNFR<1~7pPdl59vIfEm$(m zIx)LgS;u>k8@0E6v;3By+u`*(4(4*v%aZ|JTGwf}L31Mn^SAA|E`aQ8Z*IZH0v+W( z3iz2V04B)FSvQF4QFgNkpKp&1t%`1BygpTWEy@WOiQ*KY!k6Zvwro8q6&8D;a8EP# zo>^T8nRLTf57y}VWM|+2F{|}Gqe}_QX)~j7^!bP!0<7Xp;@@#Y8l?%sq=SV5W`*KP zBZJz6zd_!<2nRbPXY||`Fixdavjx}FJmi<zgiA>QmhtJXYLEo8^$n^oTAm)=U2Wjk zKDh{J7A6rQT?D!JlvVJYk>^0yS%PN*IflY*g*;YM*l9Y%NHvCUbot^%8VwJ1_TBA# z2943L#fK+ZV|{rmIy7#kY(U9{wI1K(WJVP?ci#df?V%q+EM4CS4Vd^7e>kMd-nY1K zcRFc(L-D{0%D6LU4YZb%WvxgOV<}=oGPBgh9fphK2UPd8-h8a5qGso#xi#^4EpttX zzCH=PMdIl^CCZdCNatmT2R@Emyt+LCzV8KTDYv4t@UqT}<ifn-Cg1a$Ba;P{&anY^ zer@zrG;^HGT=-clBi{(<zrwyVu=TTQgA1}A`CI&IW`k;BpN3Tv;B{O6ME03(LMGfo zF~{+`3O;w6Unv0!^`Ylhr@*0JEdC+8i9=NGU)t^9a|i$ubLe*{l&_dLadQqs<zStY znyzLnA?R$?Sd(0)c3O+EliFx_OVv(uBa#Dw>mYSz)`8^Ik}9y9uf0o0_&Q*Y>?b>z zv<Kq{M`(%%PoLCt%5?LiM6XEJ>n%9;q+gdrBzgy6&T5UrzTOLi=EB50s2V>J%KFpR z?rKGafkM}feGan0^H=vO1X8E`0&%A5eD5nCyRIhLM)}v56Gm{2hAlZbF-2j7hXt(- zT*Ug!nmPrkoe2{bBfL<(f7_Wk2F_yBU2wfe?N27{=Ueybj_*%TQCf@RqSz47NrtB! z*I29=$s7P`KTdjtzhpV?AvYB2z>o-=uQW-lTGz4v@2yvQ#^H|=&k6ZQ)C4HVj1x4; zbcfA)^LlMvry2Oax)Qq{A4gbTp1+s2n}xQZNRkdfJzZ*3OpB=O+j$4soK#An2MzQ0 zX&DtbtM~k0@!({X#u5G8pk<%yCYVqs1PLRu;ck3@O=|S{Z=?Zyg;ouZPE&GZu;tfn zWe@BjsQQ~5dGMEEIc*|E+lhu_Sn~r3IkQ~@KZ?)S*Dke!fvn8>YUno944Fx9S(G-i zwMmDG@a<CZ6{7V+M>l13<jzOr$@~G_LIV!!qd((fX&|eFrQ$GLl8v=NG^<~L8%U83 zfy20Mk>!I<?dBpeGbKj76kepZSD$`K7B7l@F`)O2DJZNehf{&L7_J#4BG9ddF{`)V z+OmlkqXAS5m$chHmZNh=0X87z92@dwRHV(9BbP$_ol1G4P)MFDB`a40m1_?5grltb z%f-XFI|D<=$)Pt*y|3fR`lbLhhk8IlOT>#xE%JCys4F9m()su`4#`YQmd`dO<o8(D zi7P{c|2KjYvgW!*S!k>LA?J}<Fp(;(f1F{)#^#eBOp`cK7l4MB^h{QCYMqOam)}ro z!O_i2zJ3cU+sJqb&y`97)^&*J+XiDUe~RflLDqabL~{UqmwWv)IxQw@^MhQy1`*{N zzb$R6BF!Oun{)y5Tv*f%WXJ+j9R~cS&e91@SrAJ*e~j{;Q8xv3jzr%d5$P1XY;T<Y z6;RLtXpTsd#S1IaTCMeN`GD9t?tSX2LrUZ&!!J~1GEazwWz}kx-sruBNcud(F7gT$ z31j+!_9fc^+KVdQFv;SU^onGFaIo5TVpgT%IViWn*^Or>zn)tqlyq8NXJs&ae>MTO zvmj4G9C3vE=F|ip32p~V6y14|4#~WRRq_m}--7zd@HwWXCbNsbdX4UvyMLnmv_FQT zUw*swx13G{q~07i*}CUbvpL^pZ2m+NRV>T8Ykh7^t&vSpRD_V$lo>n^f{sS`RGAiO z|5*T)MD2G9IpzL$I&@H+zIOEN=)eN0A+wd!zZHpNEo2wBrs22<1ca->*}9;zs-2$2 z38n@M)N(VHS4i7&<j<k2ye__)bL7+=Nt|_o!_3e_6do=<HZT1_wmL_=AM)3gW~<3H zXllYis!yImS4aL~r^#B;q;*y>l5)!h%_Jk0X6X!sCFf_~_(XQE{jzh+j0lCyR4b#} z6MvLLx?~%BwaH#!En2=mJn-xljEDW)O&xC0j2HMxTorWLi)gk5F#b1x;)Gd58pqmR zNMZdm)if(QdCsMc`%OPBeGDN8l^h*N`wqk)kF56X9D7YH_`SVj5yqfI6n9Oz&EhK7 z`JX3nhkbAp>pbI&(m)Gbvid+gUyPjZlZlER^QykEb+R2MP{RG7R0lcZbt`@R?btwa z(kq1F(<EuSa_V9S6H=xKJ0i8U&=~4_a7aLvkJQMX76;+=kC2bb*^5WSb1a_>R*}p~ z_g-a~zHK}TC2p18P2XK`b=PA_>s-fpQ1`YjEnl_{Su)eKg=Kl8&eEXZtr=BUvHW7V zFoCSNrc}UVzAYLDrzLD8D;0rkAx<F%kFL6FjZJgsBdnyU_ozE$Z_wY3`H!6=h3FPW zLJ*-mGg~Wglllb7ihTuJ3;HniKA9zO)%Ui~CTbgQ8)$e@F1{oQtrCX2Mh_NBv{&Pn za)1sW=mh()&lQ$3L_6~&eqOReOS88j#rVz)wMY8<pzHPvn7^hMVECI)o-11x#(+m< zfiWOWr*=$EfV6rq;5Z8Gw6JQ)Ieh+QaG}ava?m9}=2u}@IA`X5ji~I8AL6i|rvq&T zk!gPFs<{GzW&!_mC_a_uqg}|b^(xy>C*T6qZ!yf;Gn2xj;YmZ$p7+g&lDO+pSV9Iq zgD&k=B6P~VTLys`2j=it%u)rsQ{H5ODSK#mV;Vr6qU-c5j{p3N!gom9JBS@OU5S#! zQOw^3J-bTzr=kbdicl1dfMC4#DjM>T!V%1CVEI(T(ZfiLW6^>@`(9wM^o%-mnwr7| z$G*?&Vj<Vme{^J4Xe1`vDg_>@W-cJKDyy}ME3yl=uk0-Ef8gT5#32-|m-4K(%7OAr zXlGu|y%9+B+goJ3snAaqn8EoQ7UvKg#x0PcVt=lL48EBQd3xKXOnm{l&pp+aL4w7% zS0gXcew$V&1Wt{mvUr{bqieyWA^s+{Jovp!=H?<~a{-CT9XPLZnN?K%(Es~L0Ry)J zk`(x#fXVrdpuAY{6}7Xza4?BOQ*YN8QV1{3iM$Nxub~bEo*fTdY~=X<jRtM4be&*` zYL<Dx#@j%h?1KZWrsC-O<Lez?pj;zEoD3m{_*|f@nMy^*A(!kWUqa~4GZJrwogUQD zq9SSkGIoIYXa<Sfr(1MO=v<R*^~%%gz}~rjSyw=ecdh}jmz~$972o2+AOEuCbt2>@ z%xpsZY=80@yGJ{<=2K|Z!{D}X;VASkrGo<AWQ2$!ojN}ByFreyEbpf{vVPAII94n( z^WqiGCe4D~y_$lD+!9n<f$AuDxn<1C6T0gn8b|oh{{J#2ftt7Zlv>$5rv69;SDBNY z+BX#Kvn6B|v8fdp(le8@wXZ1(b~?TO<(MX5^`Em(Ee7i|!|guQ>D+DG4=HNi_mH_x zz$1!j28Ynr*KY+*k70<m<c70Rq#)P@;A7I5NRX<GmK5IAwCb6Xd_dR}z89ov>p9F2 zUzSbOpW5NwWrj{;4u)?ed!!niA&r9V8zdi?d#{7v2oM^6bTE|B^{tp*3WO9pNLT8z zg=gvDe7*|N77I=byckp^OW;M&D6UfAw%$*P(CEuPVv0<8O}M_3S;dLrQMZjrFb2Xy ztWJE9Kr#u&kGPuHq8x#aC-ES<jDp`Zn7nvF@+eB_>cE<z3)R>WasDZVe&bN*1jRQB z%VwP4H$IgSDCldab)rDZkbqyL1Lt7mtDLp+xTvlP%ChyC_C?*5$+J*CAUbIkZVw<E zl%8RrXMB%No5{zs4u>CNm`l6W&It6;W=w&y=HKNQVt>=+N8Uz~X<XASe4>$3nm#9B zBdw=4%a*3&5B{-5%*~jlfKJ=^FTj&Da*02GHzebuv=sy1L+%oRePh9G5y?O$v8%>L zeTp>(WQX+twyq;DC^5J_HlG0X|IN}-*T+lS^)|PT3Ki>wF?>@DsYA{Yj$})Uzw&ZV zf<4Pylp&yA*DnT)cdONuCtWu@#^%041F+M3xFolI2A#8j6)vCK;FRWsI5yyUhP%kt z%OelE>J&7?JLE|R>%d<Ydu61Gf1)PoE&dU#$pM}K#rnL_>#lAjR}J9f@J9+X0_&m` zqqU^j6ypF=_JbWya+ArF8Pc5;^HU8bA?|U>aOx-<S}Wo$&Y1pjBlx;iNV>tggb^a; zQ=V?~A)zAAR{pckyZO6a#vgzylr|2}8)@H?ssHYcfXFJf$hzRjuR1hDsr~AcBq|Dv zSz#wVdd(=rOgMoo%C%52S_=xeI5Oojqs&#Al`T#}X{X3$=d$l6wOG%aQS{&Vnyw!y zf)Wl!f{7u1)<(-tU?;7rOy$Lnsjin$f4&ner`h*Hbm&p^t-`Ud3&M#}e5Z`GZo1pZ zDqURGy?d)NuFJ;7dNe-v{?_NBrt`0EGB$3Q>Ye$}^gcZ_f4XL~_-cI<9|KViO6@$_ zyM9z`)MJS5_;jU)%~>89iu+1;e5xRXevT}<lY_C@>wprb$~J$pJ8iU8HCzau?K{sU zieODM{xXA+^#N2Sgv8}BktcHe4x;>dxHi;U2qH)8hfLVER)e6_5nIS$FD}dD;<Nu% z;8FRjf`1)^CX!s)NDB56&OZ%)&)2#Vhk6Xo6~F6E+M3(jGqxDqA?>7Q>H<<l4ST5) zN#f%m>tjRAtxff>`@dirIYIeBkw6L~*lGzY#Q#}i#e?gHHqhO-gncEeUm+zYI2rHn zqk*2bBv?_r45CuUMSJ0k=byUk+&yltn+6oG^N02pkpi!+!^vX%=eu$WA5(I8?}omj z8ds^Mv?&&aX+b<qu1^|N1?Cma|JpabbzXf@fH^{qE<lbZ$`xwJ*jrxxPES<PQAfjo z6%8>of2aadaLy9>oRgK0)oJy3^_xXV9}<A^FyIQs(B*tfEukC6nks{(59&x>hoOI< zpYE#5ws{<iUUY%)+TNg?@5J^e(_dzwSV0eq+~$6Lb+EVwT;lMO%)lU%5;sz*Ar0K= zR#E^!2%=;2N(A)>xX`QlIUFS<ch`PUS*$e;Z}RGJG~SP_L(~3TTuQf*)Wbn@o~Ne7 z>jdr7O2cd-Dm@%F8>KUdl7+l}p%QQ*xB&2<>d0@1o~kS29UY-UL|dq`7jK+kaIvW( zLyzwPoBogfuq?IS3^LgkB@j|mD@=~?X7y*dqa>F~oiFsDb#6qOR;?c$OBstjQ?zzy zc!faD8KpLkHy!poyI}+5VwRGo_tG4ahOjX*30X<>mTDf}$zz*|b0*uO;8(P}$mlmi z1w~d3D^~?=!)D_%0V~xW>~$DCiOsvS^Q&Czfm`FPwJhzPZ#L#t!jbNa$-=LcM11p| zjgQzm(&SkfQnT)KjW|QKh&ugrwKNnAx?d)pyF*KA{jP=yzW>T$h*#os&$*sAGGt9+ z`3EL2l7@5Af@EDPo5gBJ_YBOn5i<2hd0++je#E-}E2rt;*te$rV8?Ugl|65=fr2}O zN)uX-0xX=l>qCU8pf>0df9#4s;j?n1qdTpc!<?U;gO}d+zLes9sUcQZ3l(-b%nyP> z*2U_wVuFb!Z$tJ*vb5+aSS4Zbapb`GIjHZ4XMLF*D9M!GC)K*IpJ%;WjlTX1?(v~X zmctjeJOa2J#&9pI?};|4YLUEjE3yclJGc-IVDcL$dyR0b(b}m(xs6?Mq~Y(RFWzM4 z2Jb0r62pbb0-KQOq4M`C<)}Bpj7t#{Epic6F<?eVBBybDe_`p%R>RYa;i+gMY7k9y zG&u#odKc~m@E1qLuRANs(dRX@)#Oo-+@1Lbb~6&RlYzQlXKC&(_Y%t<qKx#t{qZwx z;#}bZIi}~VrHqky-;g~kk956=H`e@bJYBNbR@@q>ajC~i;Wjbv6{8d@q-6Y#JQ#$P z2H=}Qz<IXXSy9&u%{cs?T#dsyLC$=uG@VY++!c4IE<f7w>C@hkE(XWBTW^GtFu(#Y z#i%*M*IeF;6JneCJjevPlgULH@@OI>)TaR1uSBARBPoy@D%y<4z*gpPkU*NZzCOrK zW4io`ny9TRE3rTBb9a8q;d>PK#&N`f)&^a^9?W%c-@&`Cyi<s&kjdaghq+-s2C^@s z-{n!tw(SlKCFb`>6+5DI&knsFN^n9i1xTw~vj2?_@C~(f)R7R4tIbuBV{x8xA0Kj} zO=>H?BwJ5b>=*(+MY-}`XpC~w4WtZ%%7R+oyq7QUnjGT~FZ1jkoe?Z!4w(ARv`y85 zZ8AC_6IK2#p>}oCH)t0vQ6hnQ=|RkMH7Ucq@;0)zbna}txhPD<iohpQM|A!!EbQ5o zuv9%RKuNdTuXfN6TBATD_HIoswW2ZO<Vw??f7W;u11lYY?#0p2Lb7#|7AL9J5rTQV z0y@FP=KjK<c!-n(?}hT((@;S?M&?4f1&?xrPu=nC&7+x|-OaZ`U3Q*z*V#?9?IC&5 z$}3#5i7qJ<Eekg`<!d%$9%N%wivQ^9+`fH6I2cYQ9PF4J39^DP5x!4-seNb7Vl{@X z$ts2?=Uq%s%Q*-x-;$2gRI>_T#K$hsIn)+aO~`BoD)ERE%I?DKL{M42s4+G&B))Qp zt$MRICs5H|D!{c;6Z0CUmjycLOip)LQ=Iwz4tyUt&~60uc{z*`4pB+O3ZLLI@aYSk zdmQQ}zAVTzS0L@p%>s+XNh{ZP+V>m19SB|~ZfVdB*k_}yYH71EWEF>V>V&g&m_Y`S z{TLi+pg=}11~*NOI_D;EX|2unwK4PDFE=>i(R)ni*PMRDNq#JDf`e-787;g$>Vb&@ zv48J*`3#<%-}a+<31EWEnWA-el(LZ`R)`BluHZ4@&kvF+{=T$B_fSjMrOHycP<Cf0 zM#SnSjO7Qu%uZM()(@#s5BnyJ*T;i;s~SP9kP$a6puhTZ((!FFXw+Ogbq58^8~tyy zGos&fW&>+}7F>D@Iw6kgY&4h|M_mY?4V!JUy*bX$0IPBk*uih)lx(!$X>Xc;^E_M` zR)@1q(hVlXho6s)#pDwqmZopc!F9%0to$u_zAa&yBFWAsvK89-e^`vgm+G+ID{QB_ zh00?CsZeizRhk_InUJ`lLtP|M*1PTtz*_f;4zm7~z6C%oW<X}Zxnua;*?%j?dLGpS zXOH%8g85B>r(NWP3qnq+(}<;#5lcK7ipC2Yf;!zo)xg=ng<D2A&dYi*Hq`$=G!+F7 zA;il{;=YvXf((viWgc?AhDN6RJ&>|o+(tX>8f{D7ixi+MB#9+pmgt;a(ya>!PDx@f zMZe1PVH-Z{%5Yp*`1=5rq%LwJr=_fH13J`JrLyx+v%5`Nq`kI+TVR24h6hQ9@D_(1 zuaD0_pkjW4nKNcW>}1-*g*;2?$A2MlpFesl+aAwy#ZpKo<>;|LPDuXRg)OsWfKsQ5 zEvWMa&0~v2pRJ$YvWKe%bV@kcdjX=M%3oq4a__O<ApS8+U>dn4&y3IAk)gmLm(r0k zjgaT>=frxl{P+>ps?fOK%>XnKWr5o+EypD+Ow^^ZcqA~cbx|^>Hu3&)5|DLmk0p%+ zTf1i{q2o=uZJxw;2eTiBp068%aZorYG&O!2fum?U!>jyLVkMj04#SsR>~K{>>?6xN zhR6?mSVhck49HynGcMQ}ktK>{cN$mIIDmdOH#+K^@sjk!x@+qXscUV;sc=q=*WCz! z%MltZ0bX@$#Gz4!0MEDuXaH<E6mUmDiVYFSd8h<#5qzrkRG-*w9{10{kE7z09Qr`& ziRGnf3od+#WOzjl;=)}l>x>`~Bx`|>EPb3|=~MNaSAzKQJe+T0cmhl5VWm$MP}?E6 zB3N%Qt8k_tyvGfOIo`{qmh=87oggCOjnTf*SWrP-@Ck)OYxbS47Sr$eRWLhqNGJHr zivLX!ki+5S@*9N*)(E_biw@tG&b6d^np-A9n4}eQPSkQQwb-lG7Jf4Xi7z->l>0zM zWU(33A~P8pk;5#kL}|YeW#ON>Qj55r5G40x-MuVcU>D!>w2u+?YVHKXENkzp33TUT zc5f9RYX3cq?h&GH&Gs-5@!OfJAIy)s#1XKT0s!+Y<{kALO@k3@0`h}|&Vz1_Je>m% zaj}UOTN08*Ch+Xb&72mw@U$d-RBN}D={LGzntoMwmuL>jo-><cHg>TXLKCWUVFyyZ zsS`WNU|wWWxuiptg!Ob6Nz~E#8>dfC*y#VF;>!g`gB7v>haK~-e{dIT{(Dxod7q<B zzbU;t(ER^x^Ch#uDyP>$sc&%2%X>8gX8BD}AYcd2=wxd0rf22e=u8%q9rP|BwbPY` zn%Ii{hg?6-#6C4{%ZY7l52bI8<4=m>`ZPRBXFiyPo(-(BPCY!GIB11Sm6g*As)}(8 z{m&X65Xoitesl7Pm%g4x(C=0&cc2$DDS#THcx!60YeSH~>gC1Sc7L!03tK^cRvuOa zeKj4RIk;VQSga=nj0!&p8=26(z7rB(2h6jWOutoC1H%B)e`EVb-ZvGW*e1=-L#{#p zh8!tvc}WOJnt2#TKq%`B{$ffgQ~wm|@Y$NF^*F&<eM>#N^eNIlR_UYxyUjL|13TJZ z5v+5e*dP&$w?J%G?uc~^W~LQ58eomO|29sPaj;9g72#OM+E@LIB>b?A^+~U>dH3O= zgcd}<|5p6|6#Rtd9*&)bUa-73u=@@uC5M`&@M3KDPFDnlv&Y;5Y%EvFvuHS)zqh<1 zko9XIEo4dgVy*{zDe%IFQsIrCVyfmw;R+|pV}7?0uEVt3M{s&9FWqSS@dz~+(SARo z<quUiDQg>gzOdq3S8>VXq#kl8yFCJ0tWCX$4E>L3%KO~6Gb$91hw?z-aWhx`C<=#8 zm!tFQs%C#V=(D$MdQyTZI#e114KhO9FWScl;D3X(Q*C4pILus{nTyGrU3Vr}QQVW+ z5hlm(oUNU>QdykLLt<3G0<=V@tQV)QklN(Fn~`DV>E}Lb@5Sz2OXZp1#7>vVbHJa> zi1AE{)Bksq15QP?oAqtFrp{S$xe6^#VN9Y!ZmOOtuc%z(ds{w_dx)M%0QhL_>K<`M zA)!~jFK2eL8XV9?rw%Kw$LA0<M|bFiH6OV0Vr9xLhPvygufH_aMJ^cHVNq;+)y_ih z0vXz3in2%eBzyWoAU4+SL*-$ibTg?db_66NkO*;N86*9BqP_Lcnq}LmE+o-lDHuk! za?GvBb<jzI0^9Ec2#u?IgGa;l|0XnPAOMDvyB~7VL<dYSwb^flre@4I4t12`KQj># z=2YDjNL}a;YP}_HXD4YCQi%NA!eAahZKBZef00_X)uoIH59OX$Q7#k`X*Pa1iM?Sc zZ+@2@pFLb(xiZ$V;Ney1Emysy*6^S|!3GPMC==HHMtiw1E_A!2PUUDxsn&pFobWwb z3`oMEqra-zqr&FZCz&0sX?+UpQ(5ZHsVEx}lpjJlPmG=ZmG?5XVVU_t^w<QhYzxTJ zVZqBb9dJp=gemb=Q8+7J9l1_(skF?-m);PAee-Vj`uv*;NW_^er1+eoAX(Gccv9NF z`m8>X>hyQNCCf^^_n@M7H*jhPgbo9yCE*<cxNYW3Uk2AL4+rNkR-3Lb-*c3dN7Tan zhbdZ1aloIr(jNa#bt*<IYN~}vyw|FL1sc0Y5pR}|t^c{DQ0-#oG=h_BV;q}4(pZGR zg0%E4o@=wNB|Q?653gaWcyHR5g1i*HBZx|bu|#xD1q}q%KQ7j%G@LEidG~-T$Ab_8 zUs<rJ+*@B{w$wya#pZGpHPLl8_AajvgPzEv3hWvQMu<;3c`8UQ{lL^~^1DwMXD24G zO~5baX?2v)AHK18O?hp1?m+JhZ2!1O9AT`puCKXo;p-CcQYpdA>BoP9j+&%MQ`AmJ zOX(fXi8v)p$&Zybijh(|S`yDJw4$Zh-t3&=?`wBs9k7GFMIau*M7b5(p_T{9b<G;? zxLW73<EI9s%XS*h9E5Ur+B!>&pOGcx(6`V1^puA3uu!8a;?f%Y&7O5qNj@4f>o3); zVMi-8?#yByg(W-6LuXncSr6WZXB@9A(P%$Qk>Yb|i|R}M{<aGu^A)IXSV3b2hIv#o zY)G$s^~fi8AX+Rs$fl=%3lIsu)85PXxjjgqqv;yrUjA*aANhrW0tNhGERh;NVcsWQ zVc8Z~VlioS)7S?&w7aW}Km|v(3~)F^nuQ?l9%d}(gvqpJ=kkG0u((OKD;)oJHJNB} zr=paZA#xTK09O*<9ypET;2NXyhRxTegbK>>ydt?mC6tGaa4OmZpC}v~PJp6ihBmMr zwPQ`UVx?hFtMOuN!=-Vx1S9&6B28y^R|`U`67Xa7pApTVR1}OE#6d7|xQP!U_u=ex zKSq3!q;nvRr$_~3tul+hXW3hm>3+I1t`4M{Ls%&`=w1Rlipnl7^{~eU<4y;_ETnvU zgEpGYC&@wAYLSA^iL@9i{!Zqy9F?U|?Eyi_K&8OS?EP34^e7)uX-=mIb_|EmJ~bR2 z>a8iW$mYhVU-Z7BJiv(ql3RRJ4M|(GMQBAxEn8wvnSrPEzq3~#;>$!mm_%YmNjeL% zYY$GKt88}UA_B}Ntg?6G&FK|=XI;vxsPaIe!)Q|;J;Zpi$Hcn%jf;>#ESg}mewjQ< zQuM;Fd?UWC>*gL7D|wCe;Sz1`pWH9IFi!W?5nSXS^n;>00Zl42M>_E2CL{6`O~7tC zZ!Ui4=O76Sc%m97#0blfxRhXe&~KW>jHf1hdFL^;>Rgrwf7#Bz#{?S%CmnU7f`>#h zsQ^vG*d6ixpRjybhCpB>@@nZPq*2+LxE0r()>J?Y`S#yJi7}^>kdwuU+z_2*6wT>T zft~mqEa{Wb-PIlyoh%87YM<hHYE^=pm}&l(SlF^p2*9CBKi3WGLl39rpcd6g&C1KT zx!yfpsN3k8>*x+#i&#;}+-cH|7k~kY4R}YNvAqg#*}ytqb^WDk2(j!mtBxE!0^(Y* z{HX%9-YPnbWdUvw8sdM?0GYEZ)L_><o2(!k<X8F-`B#AnQ8)KwuGIF1e2$=WKCeY7 zK3RrFy|fC$aJloQ{zI5y<~a+rRY&b}sMMgCIu`^QvYE^q_wn%ufq4e*;oX`NuzO22 zrLqn`4zIUKc!(2Y(_AhVu|1bpR7hniu3IZM0bxhJk_O|9uU8ibgsZQVWf7@ps28wq z1I?cPgxeaLpmVH=nuk7|L+O+dB~Ir(E~H34igHWqMUh_JcPGaPc1SyYpU5{Lzqvm8 z4Eb021&<0+1+QH_Q5Dk)qdMGm-vM)l%!UvIclMsoiImy4u8GBl|0Pu`E7tmALlJ2H z%WKJE_x5rN)?&S@#0~eiFkHpKS57j~vw}Tn#Gz{)N}~GuwNf*@gPwR`P{}SiC^RmY z>c)czkSv*6^49H}W`7=3|FKmur*Z8Ty6{y<_qZI!Bnum<8#UA4!HBea4HgdfJ91rl z?S@vkf|5MuoEq7OSx{0`fz3m2vZTertNYmkKXe$P?z}hB2Q0hOhdUgq;MNWL#0@|i z6{6=n$tipmd2qbMVBwsRd+kb}A5#}`Q9<7|=121udr#UteZ{E5nh7Zuaz<{zw+j4i z)Ur`b<8OT!&ALltvgI*3;KuAg1X@@Ec$;()!897WEIeKvbYD(Pw}XdY3u6>^OTn7o z>agaTi$k@0eSrpn@cq+@&YDm=G8dj#v<B9^nxv%#@jWWH&Y8MDg$7?bS<%?;0Vqv? z+dp*rLtHEet?fPqg*u|it_cZRQrsAFcIRMZBVh1Nuvov%xU{zrenVrRMzAwwlS$g` z^GA}Yxn3xB(}*L7tiCQko6RY(%OG^bR1QHH{4#_JIgd;net}Hc<wFEe5pCnK{C($u z$bOy4g}q{b1!Je2@+9{~^cK5yMgHvobc^n(j_ElG*8_*@K8`lM`1uh=j_76{m1oQB z`_OnPXe}EK;4)uUe3-U1rb~39;G+yV%}y2M8p$&4ky{rvAvAATd2_#Z<UAlxM}jU4 z<<wEEc49Q9?m{S~QOvD9<#@q&3m@&|Q5S6zm4IEfmrEOuT6CAIIa5-fr__lKAm<0{ z`H>?1o^@-H1w{SamdDKW8CDHr3O6Z-yo7GofR?#bE<56|04G4$zgBhJicf?^XWEpU zv_xFAAJ!cj;JRni@^mhKN4Z003)spd{Wg(X*8*GS7Znuq=for|Qf3U7=#KQ#U@8tn zpeBJxFcE`+7oTV@mWLMyJ31Ngy#rmkOPIeMJJ_4EhbrIk?8ig4b=#6}aC?o}R6S2@ z9ObwO;sb{F(j#&6#QC_p5zrvGu%1|@IF6}~tb8xccrf&sPyt@&gm>g`3VVDm5VF=3 z`Tqfbps3!e^fShoVN;&@qApn{D+G!+BIMj<$=ChvYyvDBJW{N|;su+*g2a$iP%V@= zYc{Kn|HzkGK|ZXK_zJi|lO`2>JZ4T#O4(k5kthMj<u{4bLvpYZ^Na>lOM|{AFq`k% zTud_k2qsTGLD+J6a^w6=hDyT(kJ^5yyq6SSZC&K<?F9e&JBwk-ECx6ylZ|3NloS*8 zztubBNLky2Grcz=?rsOwMPvIKK8k#WnOy{z;YaI!Fd&lZl)`0i-x%UszCJnc0Bsy% z;b>hUz^6=pzhrbk8`}99G~ZasBd+Z|UULDoF<{=`V>^p!@pA$ItQ+1(VE?nPhIR=j zeI6@n-eCebk{pSuPf4vOjn^MLNFZTZ#fK#q07Ble?go+6P0t)s41_O|xEf#lB}&qP zCkg&Sz-;9>G43F;W~7JUhQ0e@k4|HU6h!fN`l`Zh)CVzXbgD#UF~H{zPw$~4`KXzg z#0VK&W4-tuCca~A=Kp9&(4w#~Cf``07sQH1!xDnkiv7su#E&JA|DrR!NH>MU%8&Q) z?Yu`~DrktetE#G&IiQN@{RIX#P@wVj-fcH~rECNnjlTJCsshO%wGZ~KFgc_e?WF7; zJ!X@056X1p(yjL0&8g1+$h7G<Hz}KkI`6&Rhws}?m!9sy85s$0X!G}S3)u@TPD&QJ z0VzL85Wx6rcXGTO5>2*-#+46nmTZ){2h3Ry;P3oC;hNFqx0E0fw6{c14Jy9(y!)@h z{en+JoLD1Kk{uDHoPcBNjx=j;&K!v(QRQ-AOr-&i=o1*Hh4e+iLvr#T8<p7{v~!Pl zn#fP=QAv?IsnmCi5Zt=lHET%<n{@K$Z$d4&b9!-1ry5Y22#>)@vT=jtP}DjD_2_%^ z$z&6j*T0R4685?r$^Cj**tsx9s;T^xYjJ~n!Wi&Q%ku#qSvkOx!cs5#%4#1UB!C*P zRq+-gd8SCn8h65GW`qkkpJO;NS3`GuxOQFTr@vx3tJbRuSx2_5L1I6LJQXK1^~dn* zlBHVsAZHymrL0x{bhND1|F8}p-E(~N(?cdvE2&`SHjyXqhN|zY>AS|QG_UV(-`uAz z)iE%S)D^tsnqMBvy#I`WGl}g{+Zi<F2w%;^)%JKJ;_@W-`KkgwX;Wo?oU$YB&Q?ra zI-VI%Gh}#6$~qRSI$5}$Q1z9XKtG)hm+9{02vme9lsY6kmNki+>5XR(rmuW)Ogf$1 z0n((LD6nM8#|>l*%Jy-ZEBy<dDbJfP-R&yP1u{D9Un=dD#nWM~?*R#WKJ8V9+z>Eb z&m;2Nml>6nd3t+J{(&eU^6Cfu<n-WB<&x6RoHp~3gSa(>*uDGN%^fpdugchydH?5R zYvyp^^@t>XQnqL2Y(|d;1?{s=kOZigf9vbhi<tBD&yV<ic`GA;jCh{DDaiDa3&(!4 z#voz`RPEX^`ukPV7#kpYG<0I<g#<-#mu*j09?Q}R0zbA4`@c^#RTULpvL-d;kQ>P- zUn!0=qWflx=D6<OD+GWP+e?iHiLNmw8U_1DhH?%q7SVdYUs+*pX$kwG(XC<LcmJ~W zN@9~x;st2vd*r&bAA7@Y#Xilr(mf7(se-^PtepoChh+3IQ5vA9g<-2p;|E_-EV4+O z4Z(-7@1hzELb!}OgI|BVp)X~#1M##4EIuvD&D2%C-3Cxd2Yr$#ii=y9HFqy{=2IZa z1j=4{4$0909bdk_#k|1XMkv#*v37io9|BR`aa#H^?<oeVD0g}{VG`K42ad{sBakp^ ze>Z#x=Kws6{)vV!);q1I`;az9_1xo-GDD7vLEQt+1G4u$UHF=k!lfW-7a&&XlH2Oy zPK96&b+#b!{HO^9xeS%*@uUl~xI4Y!F~*C<dtiKfab6!D6Tt?P?(F_<lXI0T?HV~R zF@5*MZGmYV|AS}0<$S<4tTByjgJ0N72l}WXhge@@g}fm^K<}t1^o1nBhCFB3T8!Z> zKE$No4pLZlR5V^qAWu9AbBJU{GE^-(Q{bVJII}0R*rSD#VX59iH8_nexJ#Nv9-BPc zSz<(W8$aJtf|~=xnAC~9n5v#s8=lvfK2;-x%kPF$1XRU$P$ADh*mS#>POXiRqd;t8 z9j(Z#$?ea`dXDM*N$oS#<winZ_!Z!DO+FWT910d{z(`vCBNx?|P!TEqnCErW4FiQB zw2c77bqgdi?Tpp49z>VvGVAeuD8gW?g#iLZt=e%gzAGen@?_>~NcwL~Yj%5V2)AZw zCf%Y)Z+c)O&#sVEn_44Rvz<2GKaGcj2=>eG!q8~~d^vLvo!BXlm!dBqh-hp7aBC4r z<;gaZ=v>%&vSFbHi+k-=B(#F2r!@{j-GNpQOn&VwIJOU&{pyQ+*(fmp#7AvH`N#V4 z=3*1vcB81IqNz)i&?R%Xm_YJHvy3cUF-D;1umr1KjPCW#hxsODE7nX*QG539`1MG; zrwl&|W#>J7#Jw&ny`d4t3x~)Q(g?q@w4Jn#;P~#LF(j?iPt}nQD5WV-4M<f+A5~Z3 zPMR~dX?iRc&|BF@`{3jl9df$=rE<F_pEXO_iKZ}A*lR6IdJJf}?!@S~{3w^YN#NOI zR}=o|U|Ju4<9CrRmZL2z*O_pPp$L(-sl(%gnx5n*zdM(LWawSwt70ZxL3AURfdU~l z1qN+<T7^4Hw#X00BM*?exoazaDw*w0?IeROe3*TZwCinHEOfd^>aPwSoF4c)p3@o6 z`}~^bAq+Oi<Cv`rjrKZSLnjc|xhjIbWo@KXBr^Z{p~Z&t0~Qn`<#Vd6st|5~7jXV* ze=g|UG1a6cb?}fr8~D?Zpc0)ruM@(fnSNIU17X;Qe(#X@NvuH9eMi>@X^nPE>XQ(u zNzl5?`FwKivcW$WNSC*5;$TDxNL~^>=<iw$oaT1yPAR+OW!zrpO1w(tFwb{kB3d<a z28z3?B=4ho(!^eg%fXxWw&4=hnxm^?_zv+SFGL({vWDbhgO)|&O*G}o{l|Abg_!d{ zfhQZJFTL&~KVAg{BJirqo%;mHj&EH<9vSXr$iG}f2dh(gda+_;fHDEv%mnBgbs2s* z#OF0cYaf)-YkK;Mg%X`u*uj5ortN)acy>~aVw*`-hmA_>Ld70=Z9&+2XEPdp-f(k0 zTwQR(6JiyhUoK?%4xu?RJm?-^s88tNYzp_5{-78Vv_Q`doF9vN=Tk#Ckt82Dm|7Py zK|+Oyc|HeOQ`UsXLfh4LvB7Gvh7JNdmEumZ;YvBb6eE9;6U*<W%uhxMh90aKY%n*` zu|6z=!Lx?a?{;XQm~$5p`T*5B7f{JcW_#81yxYb;Cm2>|BliJU++z-K*o(Mn*xau3 zZc${oOnJSnYf$$X8V*R3<P^)Cdc+&-PHnSFpa!3s{cXti``rpjxCF1>tWnl8jIbh$ zjEG#+1}mCoVF0V=wV+Gww4Z^pExS!c+SWINGA<CmW?irpWlK0jutrd}C<%!|N2jh7 z(sxrox&R%l?v~<jz!!Tb@@vD=ghuv^>_l4e{TVFilw_`M*=C-oy|+CwCJBN0hdN|k zXxw^r?{`<j=F@FxyZ<Va{bIUhHC54^4J@gg2h1N9GovZoB6O9!bAHcZZ=5Ov2|ttD z7#t}=QaAebY@K_aOJL*ZWHcIa&}Gq?lQ4$W-50PyOs<#tEY0z^<RRy-Z%f~;Is=Z= z>DJv_myLzXpU3&9`Mh8>c}qBxSap1TOh*Wlava~clppb7Bda^zwc~~Y*b#Jx%5j-o z0O2PXDU+uK1fIiHP@pEJKlED~pQIb<K4-CJ4`aW7E^?o@A00nN$1^6;VX*+8qmk0n zPn}lmnFyMzb{`+zm=eYKHcH?&fl1|DiZCAnzRrZOAB#==-R%s*&IMP;h#&sW2p@9F zrre&KR1Msst%HMA?-tGC;8V434QFM`{4JY@9W9j4oI(*cS;_H1^UGbV7QnSHs_X)C zCzO3`@tke*)vs)kR!>bW@Om}+6|#c$I0(K=(-0!s@I%zTu~m0smcR#$eU92L3Hmg> zIQf$W80pSY==Tf7I&kfJ@vKWf9Eh+^#ZhB*^>K7<-+e2<+VKmWY5)vW62)fT33Xu3 zLk5&AJ&pZ$wj>!;akW56{icK^TXp;$02g@g%=q=ql;l|iOp9LkfwU>m%E1oaaf1b3 z6Qfue0D&YS@BRROl{J6E1lmJ>b;wSGB$L3j`XYvmV`uCyI;9rnNVb%>q+B!bNcLE` zXA3j%dKj?*(p(4ZYo#n%_!dJeA$_C>FwvTc3le4YBR5sY0vQkg9RfKJd64v<NHi$( z(Ynp%9qysNm`%5pFB*(r<3L+uTNTFF(wH)hPx)|&QXzDP0a)4+Z>UVpWLnkBx?E1y zjIOyj7OawSccOK3`c&o)tZ6mt^L9$D+z#&EKfI+h)Af8MqQ0c3^a_ngEo9<!JlDic zU?qG6l{2@}d`Az9fah;hcAa>3f!midMoObhsGRW>MUsk5<Gu;XNAMr2Q+gU2bsc^C zGl@7*VFB9t61Ca@qY*dp4)@=JTlDj*Uw((%hwvSJn`gBHuKhDB7&j19_5&C44;|I@ z8~P;{0b&}IvqZPift?q=_SoauJvxFM2PUz1vnzce0114t>Ft?4{xxRWWPUCmRJH`s zu7@MfKt8lGyO+OrOAZ7UYr)_=-0u%;h+4-gTt8Z4zgiO<)MDu?zB1uHL6^38FNW&8 z!G^vx+_g$@Jfdm-reo?I^Agw4lAIu9O*qb$03!I(=ViqxJdyH9_@_CP@t@9Gkv6N| z{U4!)`K|tYqvKWy%A?;usm5O|Weppj(;^@ar<Md&-TbnLGWWM#Y|9#4N^;dwpPk}O zd06-Hhe|UdvZ!B?2p@ynFQW*P@+k)MQpbYQ5K9k)UNL0Yvr57G(MiiledcIBzp@20 z%JRnv%U<(U6d~el5Dm7xmU?dPu7I|?7MinlI{qi1urUPW`{xt=0L2>IlSh#v2A=F< zPy!lZX5i$4+yH8-wi)^BoGOp8`0hM)ZAtvVSi}WHkn#k7eVvAO5u)?g%YW?$C8-HE z^$Z4KnXs{B04!tYsC&6-dkf`<p%oEQX7-Ych|PZfUZ@T#;a$5a#c$L(^6$!$U9=SL z`dARC1<AkG-EvvN`+FoxbdBX#6!r~OSvjW^)wEh;;IQ#z3n&WV#0>RNNB-(^m*zN4 zGUp_loo>!Gy4ACarC2KXd3Q=U7zEm8I*KEp$YULRw%Kf?x78Ar2%xApqOAfBTN7*- zTzn7X*1hn50He&ONP2uT=7>YWKDWn7&_!{cw~Rn%Dey;5EvU?oltpS1gu7pQ=xQ8m zO`IU)1XBaG{V@15iSD7*b_#e4NQkT_E~IX|aC;R^reaOIkB~2zzgocFB8#*$uwVDW zLA>1zKdI^(=AWJ9!8H?}sfby5hZH$R%fIDNbrI;cbyDtKHvjgI>!1jgcN}{;nxU9D zOIH*p!hEA=;_MX<>f^YlF3ckEy0io=?sZJHR9J2teKqhEG;OmiQ2RqSy`o?0D&B1# z;7}zy*b;0T9|sVJ`-rF@q|Z0)mIfWpivTMF3!ZW48jupZOKqee)F`S)^KQ@j8Zf&u zKF3XxV#5Wz;iJUuyQiT!#YA>NN3jg&t^XnQ<mB{C9-3q=XX<f6+xBCBi<T;G`358& zgA@wOJuec9p0?^Lf}F?+_M=}hbyC1_Ox*WLd<K<9kQ!;yy+u{teD0bm<oa|9ZVbOc z>`yWu4$sgCCiJ!Ity*?e{fOOW<X+WM=O&%V43b`@?$L(V^J~#!BcI19oM@=3hv$P& zT9{$`ZV$#J`-zhIPGQK0>#9ne&kP^z*$%|hAA)c%v4xuMNzRwP4jqfTf<D&by&N1H zIsDGADK1_y@z+uCf{mAu+Z>$nLVmG7rBjNuw4s4Kg0VFTx+bSrxcpBgjE+C1K5V2Y zbOHizGGxGu0h?p5+@?ocA<0<ZAN{hGl59y7hYHHij?(W`pgv9p3tg?S^;p)KTzG;_ z<1t>Ag)4P$x<jxVz-+@E#M|;f#JmO*HzoZ|F&=%E%&pcOVytpq&}J7oCc24Z!!Qtq zRr;68pJt&^_Dr}w@EzEDHTAK>qz$iC#bqc04%R#nqeeVqgDP=qydW?N&bP3^prK|8 zlW4pNv;2hXsK5hJh&`DxL<MHu<Ton%1PwywV!=5Px9e=FYBcWK`9C_S(WkndV_S8j z?b+B>1~E0UhEMI@<`ATp+F7w1%s80?%VH7NqC!(Ss4^wziE}|ox~=nvl>I!vG@^!U zpSkXpd6mQne8L9Q?&fR9=tqIQ+(jhYe7GC)1S!Cue|$@nyc;dsGDbMIRf3I{BK-3= zpa;@<jTXiOC}3Uf-v7Zvf2cKGrLJuH7<!s^7qd))5TIq=;K2t~Hobn$vm%tl8&fAa z?4|`S7tc=K3BB;_qKjv|QMf&qEU$(mLI}Q#l_asb+X|9YrkQ;Vd-m@CD{L!P9vXcI zRV;$hU0Qlp*lFC7iliSe5g=bbc|*mFkZfPPRoK&zOhiHYQnAY3k}sh;>_`9VP=RqE ze|Cvw)A_zrtInuVe?Z->DSpR9<i<uvCN=PaQ7h;Hy76PJMEFQNP=ZKSdP%o+Sm>^g zGcPdj6wU_esouYRl?xk2UL66`jR)M-ch|$0^%MWP6rCg;Z9W88&Oh=O*nkUq#;di; z1TCI|yvd#-3#Qm%aN*h2=&MZvxcPNPu#pt|ku>^1rGq%(7oe<DTH8_>l`GJ^7kLMx zjXzd#2B;+Z7ut9(v3h39OnEH*VYPt8TSDYlx$P#UE+6Fs^`~gLc*hUW-nN~+Ery9M z3G3uRcEZttoY=Hm^t$){F2~*I6KhTu;OaRusA9keUr7g}yE*PD8yzfXFz+>y%2&Nz zJWj$Ws>3SLO?{a0gdF^<6zjY+MzsYV)<9<gqv<474oX6)bme{r)Updg)?lp}Wz&^R z4B}+U-!Y?Nj(2JYQbIROG-*EEqgQS;Cmh0Zyz!|?#<gwzubV2H7tdbf-YDBde+Sk~ zrPv_IjB&^XL*xA{{HaLIQ$;`<MakanX(gC}n{jEI!mC9M0)8<>a5{7{Fc)WYA#{o4 z>^g6{ZZW-3b`s)~$6{#`nm}S6xCoN;8hi*Ub(x~)9B|D9w*pOZVu*2W?qyQL03kR1 zUmSK>Bs^fYesCqbP}P9XU!Wrc43Dka?rEX;ylsTLXeH9f9Mx%Pa0IS?H+zQ&YW|XV zTHv<QfW@o#kw|Gu$sipmxo{QTFzDh>l#Bx+LKbbS(pYYe!*!sFoqfT&pvvUqqBt^U z7aG|sn1DqINt|Ba!7L3#JzbU1w*R-czk111pHah`+$s^vZTPDz2B2Gjb$iSRgaR={ z9cOVTG5l$Z$?O5Io6K4|-|YVROJ>(K0}N!PZ~X&d_oEP%FhBvNJpNtO>2Fw6giK5l z1H^a1#^F?W_69irqkpiv$P#HJCUP|&&opJ%IJfK(ddkQ^<8a3Ty1>&+k7qi4?RXm| z+`zvvrI$^QUdNesXBu^{Wz!FtL>JOChC4lDX5t)&D!DM)$=Jq_=d`-ja*}2TKHCJ1 z)*OGJg_gN2d+|=b)TXeh2F!IvQj*^0N*xvccCHlTE=itDLz+t~^~7^K7usM&J#OAq zm<aqJ8cFERfP=WslY>?-;_?&a%U(;9Or6{g;+j_}8=9eXN5C2Y^YT!HzxfTSc;;R^ z1&q8?Eb&}i=1*-Bhv&|`N$hA4r&{jrFmVOA6wZ{5hK^BL0uB=IWRYjtG?CmtECiEb z+)gZIHv|pt*jcCt!G7RMH)K#$E`gx7QrzMVE{ZW|;1;ZM9dATSycr{N9~8SEQ|u>F zSTRpX&<Vzqxumw`->Gdwb1mU0Wm)&r4O~$Lp_^$qCoS2UXi60^wdiM`aQ=S5G6JHa zt=Qi=f!qt|v&M{=9SsUS)NA@NmI1JxpLy|jg%u7mtJ~?p$08bNy(2`-6sC>FYDPZ) zvo8X;(2d2ad!Xi`iO%xHO#_rqev4d*8w&KNxz8h>=}Vmp(Bzd=kXDcceLQ9b%d%x? zR9>7Z?=Dq6qN4;Vkc60Cg;lgd#v}zmLC<1nKIRBD=G2x{?+c84ncOn7ASq1k{$2*} z-H@}q`QJQQg>7z>BCZ+jN5j?>N2?<?7AK_tVH2}6IfQV)F%V;F-YfBbkk0X9wK6OL zNdQawkiw(;k$5fW1Nh@0fPFZzS1k{Qgy*vmmjTdE%A3w$jopA-2?~^6>x_tMlZo>5 zgwkKXHwt(O*wn33M+ZJwebob^o@YjWz`Fev;@vR^ET)NI?Kua`E9v2JvC0@*PCA(V zO6<23EV&ug&j9!v=c)FWaLd5YJ>q6jXl2ubBrKTN!Lb%HOPy_(%2L8fMFfRxH+hoh z(9tSLKot8AT>>xkD<oLMQtBW@YD`T;&MosU`d+i{)6ohXbLBy`@+(oICg0`bOHawh zQlvm!WUVMoeP+lt9y>J#v;U09Tvt764hB<dX7C{DB2n>p(&abHs=M75i~1Lmbv<lq zshAC-qsK7E281~IqPR1c*oc3S1GBDU#1jVx$+oQ}MLSVpz%|i{TCS<rh)4*vcBJs% zqWE>T4(73km)gJZ2`C+9_{`z}mhWl^2nT~3V<w@$GRXZybJ-F$r<8=H1w14tR;y5C zKNBXGs5g*K?W&myErs0oyqH*#??9D&R;39?XKq?7GIb(g*=y4P0ixUVnCT|S<JAt9 z6m8fGsqKmx8qn0f9ZUAQ6XY_yJ5oZ!I)}e2C#W<~2hGWzB|iKnjHMvO$f;_h?x#B3 zzed+!iW+8#U8$-TWK!1Y)gt^Eas^!4#1-7O)m$)7A&JO+HKA}y_<YJXpsM8&k-Ucu z1Z!IvDAavBO-(*IxNq7Iq+JgVM{|Ej*YQ^-Y=6qs`UfClL1JBSl+_QGXS%T=2=Bte zJoX2G0QluttJCEC?Eg&t7U{7ZGl%8g05p956cB07u8ogwUN8@F2hqJUG9(g%OXli| zASxPsgiTY0kc^A?J4DLxAdu1)=XWH=V*3>46tCpJ_vke@Nm;NKe7SQpJ^|)je`H#0 z(h<X;4OL|LiQnGu2WlZsG35T0R!pfJgRy;%bFA}8FipWRT~?zySl9$KbV9M32Zn`c z#n<v0544SGU>FYV@|@4V$EUNzc*9BdQ=$Ul2Y+={^}Fb36-MZn`7qA7FC?&`7M#vl zH%^omAae7LmQE!7G>BbUQof*D+N495y!^4@gfOCAy*68dolqoKkX5nco$-pz%X4kk zQdv_ria<0}Y|)(dpN%Vufpr#>r;NR?8c@m6Vh0NDx}WyBh1N`F+k)NOwpIDZZAab; z?8bMlAyO(+ggAcOI4xPoOwHd%m>qHPugRvaGxylDkVAg>(7D4&)^0}@5>3vJMG#z} z*TzcnI;mVzV>Ag8>>peG;I{0Kuo}#4TY_RoGqry)7r*!5qGkCD>WfTxV3(zU4mct_ z8Z+5%+}3A;^|5bz${;cKP+`n@PQ?goDj1i))`~V%umt~6sBpgSHupiMaNKAe3g4ye z87ux-*!_uX0#4_;jQ(cohrPmCTY;GC;@LRQpU_)qVBO#&mqf85Z*}~fO|;69nsN4} zb*dtmQT9i@(<uy#$luq^{udh0Wfa$5;1(@u*HhwCp1K-bE(QAMnXaNyq27v;1qrGd zN=deRn>$fj-ptG&>9=2Fu3dnYZ}lk=2ym6z(;p0YHM()j_<-x`*Y{*bJOpvdpcp^c z3kQTTls5%bL~PQg$hTLxT^n;9;I-%faM}_6Ot(`;KfW}Q67d@y271=2?xcM<0mJ!R z+k^zwPpiXEvjs84yjF1!K*mhD<A^o4R%SXVj3N&pLzRvEXj`L0+3PRnMrs%7{!BCt zmDA!Hd>u-79ih>_tL<*IpIPQuvZdXKb-ON7d9#eg#+SKQ!uHt7A&I_zWo|Ivy9;UI z{{>`D4p`r*Zco9>{5N9do3a#K#t!x|hT*TQ#0vGrRLN#g#w@aq<we&akEn-AYgq4( zn_CKH3lI1Iy<d@_>RQ321ybbw$kL8xF9K{;>I}(1`Odj+in&!%vs?j0gw^F)sX)1= zD*NZ(yyZGZ`d_mfSHMNKIHC|)$j}D`+2Kv&`|qD3<--oPU2hm9odKo`?ypRbb0;RU zJ`JudGmFJ;w?zOQ+DoKC14S+V)L5+ye_YJ-0>1Wcsk7b{n7ez?m~du%o2m?5){^fz zBrbdPEJguqQB@&Jcd|DBwvePQ;BK4g{v5OxZ3dsdY+FS_CWa|gw|g{qKV{97O+@tB zH|SU=SNNlnVlp%_N4gM(G`>_m#BFacb~*$~Tx{<p0p<@JIf8mo0x}SzEWuZbuLB1N zm)BcKZ^nocw8iy!tTfyTOF3Ei>1NO5fFE=>0^0pc7F{35Z@_|b=-a_yd;ZY(w>d-D zQVadGfv<9Ny`_$2hhFnIac`~?x;wKF^eS2i)t*)kt2$mU62L}U)ai_401o())DK;G z9*6XZL=I6~GG3EKU05>dHkGEw@_^j~ESfF_jgo?QtAs3;#ODYQ<&nv_F=kW|q|`tu z8q8*MEoLuC>!WwPQUX03w1rMD^fk8Zv&I?XR?n>0YA$&<jp17Jf#$81P9<{LPb&9s zU2eh+(d9D|%t`uUkFQ{llSuWagHJ-~gy0biPQDl!tDhYnD{xI2EG!mN`Hm=*2$Z{* z!r;ONIu&5+u&xcez{`0>5FEOenPZY<YA2McKPq}l>ZiH{&Cd3RuI4H-m^a<YO9N7< z$Q?v(oW@S_VlN;0etW?7Uk0Ph4W9XJd!L9h8U4s1hS$G^6den#K;Gdn*TyraHl=7P zA`6wlatF>WYNv+Xz{|UczP;%}Er3wR-ENFZA=%r!0&1TTPqB5c{s;TgAaA#CZ(6le zI0lJys#c`*4xwhloF}O>yFKJ%7%+m>T2MZH2>q|kQ_ea-JzeAtmoIXrq|<iEBJg5x zOBNyIey7yY9CFuY+gMCSHD$h3a6D)Hl$dq!?(Irf>4^dIVkrd;B}jd*5t{x3mzgyM z%G#BlICn&%nC|Iaa$VLyp7AxFzi6myRRRS9UHyt&h(G+QOpAwXX+hOv=?!zG?2gZy zgU4Y7)2p3sy81f-HY9D$`Lqn_BNh`vPu@w$f<u6^RK<FyQ<oZzoM%gtwi<H6<M>th zDmt2MfWVFPT`Dybn%KZ6fM%_s!~Gtx{EVcv_{z7Vuz?PM7LJB8xEOsFUUU2!<<<G> zlT+n}nhmBrW_%e8Asat_B|Ql_Sf+U0>ssRVEtbS!U>elDW$!Oco*@gLeUiC96`Ll{ zH(TcVMs|*9_FWB+XV=N&FX>_zeOOPG@-VlMqZ8m41IpWt>3u|&=KUMUWq&KcyV5Vz z{iqwD;r`o6YeSr$-b&)lc_LN+M5YT$>lmgW7(OwfEu-GZKS5wr!)JR2WL4U_jT}Pb z&4CCRSO_mfy+#(nLQT^<qSX!2<OB)}9CS{`8)FNU-ZoGEyb4Yv>8#a;1Oo!+?81_Z zX^W!V(6I4^-Trap_e_j~(4T`T%1gUeUQcInec&Hm1HE8;)6RG_jZKv0cRX;ts0XDv z1WLuet}z)hFb+f*%4Q*^Q)px9N|UqUK*-+IFv^Qr5jA3%v2}QaHX{_>t*MZqT@g$h zP^pIk(NEd+`ZcZewD6zMvzLicBI{NWp?F7*7g?!Tncrk$1*&bHY7z($0FB8ra5c08 z&;d>mkqT_FQP~(Hnv_oqQHW=*u`ct8xyBBBZ5=V4xYh)`^unN3)~2$xNRctv^r-4z zn9F^7Z%|=?5Z-yW2Y><$0Kr<oZFP$)!3mKZp!dzoP?INbnzlT2-#%gLuzjxr{5Jp0 zm_F~Qo3oD}0g|_zMAe4EQRaZm=A-Sj9=ebqItDP;;ab(<?b_SY00N4r`Y>^MADub( z4C5`8Qc<x`by1N;pXr<S#SiAP9EX@}ynLZJwI}EeXwbiD%8PGN=T7UAvb<xB&&9^3 z(-Vf}xhAft2wBD|x9PRlanvVUQ6h6$+-qJ+6^5$yixj2@rRQFPd>O3Mmc7XcE?+3e zS*O9*VT|>o#HN{iWd`;@sJkx;YxJ^v^^F&ITRESJ!g0Q}Ek#WO>Xw%y(<h)xnJtk% zif&sjp$y<|RQi4uB^hq-F3@<>2r^-0`@Z=UA+y>%EL(kaHci5`^|RTo#EnVhEQbn; z!XEMyF%lgs2unXdt#lG`BKWlbx;A|$<JHGc?PIK+#MixIujKQ@RN*lw#u#_BchLs+ z8bKd7y(`!4pdvJ1to&f|v<I8IQIlP;tp!b8PT8?-8eNaTZ?px~9$rqir&xYQ$PSf- zo>bCl{>2M=3uN)8%%ot5CmjM;nXIguLghX`Mz#UyL}_ze&wVYSd^hU$zwU6UnbYON zJs?ZDFh{rEdBww=eNRp1I|4PwdNgz-Y$8$Q0mDRU0%rD|?Fru{(~4N*@ly7^j$1?v z?!U1>0h_m)v6``ZtNC7LOApN0<((3=IHjeC|08^?`#g_$pKN2#GBw;lH=Zuk`)d4k zAT-cb6r`sLI0xn6jpG_nTn`Xla|LO=9G$c)prOG-FyJ;)5*fur3LK@!UQlQhw@!Po z5Vd<kXZ=>ClO1=rM%1|tus#E{9IX?w+nXIOJ*<FjXV+GXyrCt?ivXMd=KQt8qgQ(1 z8MMUTQWtIVcsJjVxlzn5*wZgw%iSIK&J<#3Q3m^5+sOBkwtOp=$Z=8XETCCggufid zQ{Sx7#+8)7k^EyJa4XgNqsuDu?3P|1582LuL_yfKcD^PBlg!*%5Ulg67XzyO4ChBq zLyW5tC8K^+coiltv01J*ysYYMWEP=b4|*RV?1MI7afmxoD)js8#bfFAA^Wi+x1ayW zd*0<2=Bkqk{|YK!3?N(e9oZA5xo5ZdQIA#t_HH(}OO6aJ?Hhvs)bz*1v!B11n4@_| zmr`uf_5G8>iebBXimxGd9sad3a0&0t7J%g)&&7SI#Ca54JvXAUg%c^hD%`-ZsqO4s z1UFZb3f0xCV`Q{_?df|d;0x;mC{xhEU`tWVBPH(3c5<e5r?zpjyH&X{&3FEHLOOZH zOMBg3HB%D&{&Z;Csv#kIQq?set5P^SpoVlWS^wlGchu`SE!Za32<gAXdsNW=8r|xG z4ERUE7?JhJY`l7Q8t7y=AigqPt{S-oQg;iq_SmRbH2R{DKz>BPI9AY7LNAF<7T1eC z;a!v#E=C!D6Mz{eVjbSAgKh(>x<Wn2B%XV)<4O*kJ6G#D&5Xj9y5-8?>@5<<r5AxO zr~oM)?m2*r8E;V2BiEs}EKS7X>7pA4G1>ZwsjU-{MX_z)O=gB=$b!GEw1468e?lGc z?bdWkrU!}`$e;4^7$IA=roI^hI<j8SV@VVZGR#KQ0Pv>&bctakKIDmZGOIGqMIn4b z%7J8n;I{cDX8;$6Ekuf~Y6l4&V3(+)B?if<T^nlW@J#n|bZE22ot;qdvUZ)EV57sA zEwKK)bkvFlOIevD!_{2w48a}N1XMttqPxw4f2!R?V}5AV`G>0lx?1;}B-orKMesqO z5@zGC<q$tfuDIy_ffwBR!mD@&F1eB)(5)r8{soy*1<5=j&grj99jrM9v%Gvu3@PG) z`%(Pn_2j;iI(meBEj^=aI}V<lHwa&}EuJ|~bw+crqLX+^6(V+&O4;o^02Ff!YMqg9 zn4pLhhO{ZfcNPBo{FzdJtS$|0G~6;*3o*CoTGK^YwEKC+rk<1nL)^Uxq(%DNHT*%O zmEH>OqiZ}r?po8gix6_msr7175?>%c|6|WaV)d1kWe^{?ZQHKPu2_Oe<7Xuk@3ytY z^p^a>a2Gb>EvL_G<hRwYz5`kmaW*Nw-`^h$YD&5bUW-ODb71UJOKv_r6-dM3=6}rC ze9VEuwJ0G*OSW?Gia)IAr^*&7ANiy5{%h`g>YhU5SALsU{Kr?K^QJBUG=S6#DADu1 zYxWt{Mv}?%w)=46E&=;Ae2wS4JGUMyF$LN2%>3ra6s3V)s7A`;BSixjhDqezr@8@) z*p>Wr(p+C;+yM{&0z!RI=b6y&$|`Sjr_RoJ4Q1JqmscvDo2I<v8pBS|OU&M;_tu<@ zu;w^A3DNYQ=6>>hbO~OO5TpOwqPBTZ0Q+nfv3axlTSM$Yo;da?HDmPga&NL;DCvtj z!|Vk<_C_4PNYQp$sF7NJ6csF;q?)E_wgzF`@-}O@eM^o9msju<Tm>38-1v$`uZM*6 z8|wh$2mfN19j$%}Jb&IDj8%f-ZjX~#^(TxjLg*boyP;HsS59G;)rduFqj5W_i4d8u zG&F$DZUC-DAC#Pdv`ht_>EM<h!Mx=Zl^Cy{MFy{gy0$jg>d>Z!yp1WBjRf&7vgVam z;#QC?_oVm92<o4F!>TkPf`J-POrBPoP)`_42x4igs?rh2Vu`=<3dVE5fVR-sBk8XF z2~NSw@fz?E6Y|;mijK;1LcsnvcDCNbs;BHL6jr=bpV5Ca7yj!Z0)$hF&`B3m<$|ad zsUFp$MmvE<QzBxxdl)KbRU4rl0CQ!q<YNI1E@D1dO-C@C<YPo}OLmI}!?*NBF1Rss z3s)mm2@Wp=L6EL6=wqS32cVu15hzjs(CGS$ZP+@qP{~<ERvJWU${73==)TPRF*@U8 zCKW^p*B-yOxr1T8^z&`uOYHtp?#cGrtFqQXIC>5igwCG^fj3<0rG5-<A)jJN4-MNi z#}0RbIRTnRyLYa<wLS1u)RF<bmoq<!-pt4X)nUpkmRMW%D^`ZPX+99g`jK*yc<R*K zG=7Nka9Ha^4D|w;HVfV$!S+3UKji<EADE4g@9CJFznp7L6i*+Rxnw^McJ}3%dJP-= zQ*R=bQK9yj6x=WK^`2u&LS<CTW{DCT2w4{L!)o#0Zle)AS$;aU^|JK%e(HDT`6B!3 z^homSU2wc7%CpE?qpxhwKTk(6(K<R`<gcJJ-CbqZFhy3T@B+l&jrG8~(+PkChl^x} zC*b)T34h+|9x)+=k`BZnYUB1v&Pv+w<lAd;9-Mfhz*+R`AT@wht^tby`rD{s+&5D` zT9LS^!L@>lO1VH#<p8jnd2E4HUcxOSPkqVUf}m&nfmItCvhHt4ehIh7TCO}YN~=s~ z9mq?q%XAdmPll&_bXzeZQ?0$mOuz-^vLLL(eq(~@W8BRlbtkggS||mkA!N87K04pM zA-^2s&#vse=sb&La<iveYCp|xeU1szV>vFJB!9pZ8+6@7;Y})?_RNonj62t|;Fj9q zh*=?fC^W_+C(xoK8hMFQuOEvi{{^<s7Sl_OLE)$*=W<90Hi13&lkL+`FVZfSE%CSN zQ|c)!(2Q!6k;rdgW!YPwTF&NktDKI-8KAI3!w<PsPxx`$iX!hIu4wGv4pE|?Wrl0| zv+9wW=Epj=_?%z^>rv2CNRUS#?Id{f1TTtT=g0ud;FH%8S2hDd3sjBzDUpbdBC0;4 z&PAo+yCDt{zY*e0Li+53NxiCGkQ34HES0cCuUddy^UoobHJ>Ufv+$eZF8f6b7InCg z6j(@T$e1NiICg|cI7_g^Nk+{+P4_o?J~*J*2KV<}%0^Km+2wu#iX00k6NBFn3Gkjd zIAP4^H?m|O7i0Qq7`I`X)M8;3J^a(XOO;CG<h*dcrjz%<`usMU9-Jhn#Da;_E9{1b zip4TKqFhTBg~qR)e4++(WfP4tnsp29ylc%5AZ@Apzd=Tg*z1!J36S}8+K`Y#l{384 za+PsAZ2IeB@B+kASLe+njnE<j`hW*2(>MX@8qt3GZ&K4f%FGM%*W*sm2i&b%Nf_r9 zSBK=}y5-sh5@uvM^p*l+SD#yt9wpp&U2t`R;%o)F#4n!=r_*B?giq28H}AFVf~}gk zOhp(Rk2A`@&Yob7?rj&PHlTkso4J69Ck@4;6xe$eVX?{z4{svULpcKMLSs~1i_{e# zp;H*;soxxCPu~G_^d*h1v>ukWIz$b3T$8PX*yI!zre`P5gfNH4XDrb6we|!?aTE|- z^14y9%v=v>=2qRZAA=dX^^Y9v7`teI_`BV$Y2Auv1Niez9ss@K7<=wI-A0#*bN*tj z&gx8ziE(#IiA{M1{oOOXfL8XaD~@Ce<d$*s9^?QIha@~S`S4h%&tWUY3p`$!p@wK5 zTtFv)oi;7pQY<bFbKS73h=O9x6M80qu!4DVm#fh}VrpIB;NJGzAagpT*qGEWvp_Jt zW(`d0org#YI&o9m0<hdB^T>`}F_gBE3W%OK??3`7_4aO})%<2}l>B<IC*2oOFbW>> zn8Cos@&N^#_<5qU7t+4Yfs3yoBS+j;kP{3}{WnI<Rhmhc(RAoCKpJKzMniDTcMb81 zT45gIoFwMd<(Wl}hr8L{M$ZC%MS5r;$7(#dS+nr-gcF$a+ILvDGXp+d0tvT1B3oMW z^v8=}dPKXUaG%YdXU76q2|n+Y!bcoocQuq0xLA;%frh;X#0gM?y|7a{C79g=aCF{| zu2CS|X>)@{49^7SN$SARR>|W)FA(jq3^JUw+z8KqHTAEnpDWrWhj{rN!y_>)UiHh+ zcD<zr5fqdtG^RIMT(==+%^pt1tYGi+3iLQ0DMHY$O(YsBWEfa*09bki31tb@g8U1~ z2O6?&e+|GHVDiseJ|9iT+NO{GXx{T7F~rC7bGVe$K^D*RuN?5gsQREdqX_bDL6>;b z-jFDo(da4kl5d(HC!lv`$$#&}fglu$z1je?V2=4wk7Jmv1BA1z!dRFmAcH%aq1~q# zCWYjQm<Wrow0Xh7ht$mlKmJd05iH6-sgFw-Lj}7qryDUyZ77fah804}dT&<@FUrho z(KOf;tysy(^evZ({IoEM9i%=g?aOm0N|OP;bvsL3-4KQ@SbM9~Rpx6!-Asa3t`7mT z1u3^_UXFRU$vkoyV{XSI-UaqErWUBLP#@)6Al6n|wPiB;3}_oHohm}kZ1lDdO8j0( zx&@ny{#i^JgIvolh-l(iSK^P8zbIuEYX$sD)n+$k^6(Mn;p)2tMikV^{)Z3g8TZ~C zCHeC(gxRAFKRh(zS!<4YMn-v^_cDvi(zfoUKU|A4{G~Pse;^{e|Is@sSmDotWeVi_ z(8~xo8E~iLk63t`2dkK0?Wegn4bf*l2NTQ<$QyJoo@4ntKyV#G=N$i56x3xc%do@g zZl!z@q>EouD}~hRVZLZ~mW!Vm?qS_)@WQS!c1+JBwU&BW3hgu${IrHQ1-nrBOjwXj zKk6Se{e|0W+2l)3?L>1$<nqO-wJ`zSZS|d-S7Gz`Kn{CjvF37BJ^ja#5jvSRbq<M+ z`;x244`z31j>E~XN3(BMk{MklTNz`{20?Fvtj@JSx4#&{P;EpvRM|bpBlXf@$Wcy= zk_6E7%&{+eG}{8;gX-yv)dg(LuTs3CYGRT^WosDOU$E<!bSG~Dp>Lp{{p8r1sbGf; zD()UX?XZR1sS%Ua#3Jt9-h@sJRQ*o%?tCV7RGP;77rt|-Ye)EVoyu-WqxJ2yx9s_j z2>5G_xhMj9C96&K80r(XTtY_J=KfY>Pg*`qtXjQZNZQ4ikN0`|!Co@o;A(gCHg9pQ z=vH|Y?kIrx@$anfJo_l3rBX9C0bE>54$!F`co2#^^@C&c%88<SASAogxtuM0{=!F` z%f86cdVyA_s$$D=W2lie^ut6j*SL9jBvw~<$FWCgb8m>%L)wx8r+*id+K)6MtD-IH z#`QD;_iM8B10@|nv}85LX=RNMkkEd+df@t~I82>I?#osL7-*$AP1nf@3covGFg$B> z*GTburiH0&imbNmTc$qLlsq6)jQHR02`ejTgtttK!0r^_anL6N;_Cm~IHqCfZa|zd zX`XMql(h}C@@-_^QHVh98U_<Q<fv;0RdoV%<x)<9lsZ`VMkLu+Y{gPWNWG*+eAi?x z3q3NntNa!wyQ27l{S**^WfJ(4#KLx{XIktfYaY^_Geu3rWDQ~1sMJa_i$hjN(jXS! zp%5Yl^*)AXz<nw2ozzC}erk5`$k{28FbtnMAs|U0BakSl&X)jX=$57lCKwHZRch6@ zGhwWyXLqCaiK&)TWrpo@!XLXa+T-r)C^M5M+FxM8FQx7%1>IaDnQ|1E*pcDYW9`PD zmG{B`lHT7xI6HB-(Yedum>)f)QEgAj5`#b#)yI5&Y?&=ljhFVN&VeCXreol?Q;7#h z`ZTR`qNm5)EksGjIMfWuu!!c90c1~1fXLI1dJhNs-pQV4KYo>J!gIZkeTP?RnHt?w z<y1~$2v|vpONg{gxlg#*8v?<Vm^n%hyVx8sziFah)SO+umz|EEL+MgZ*7_6S34(Uz znrmz&@MXjee-cGlXY3ihXR=5-5}0yPxOE;guGhb9a49bm(dG+hX2MN;O>TPusZC;v ze&(~wl|0AZB`c4wmmF@-Bn#&4an(sn{^Rs>2!tkO$q?g(MZsU=Rw*m$BL|Z?Fnwgm zR?dyx0Tr2ua_Y>emYZ4YdDXg|V6%dMoB$l^Ae^KJI5U0=yNh;g^*`LK2mE%EVeFh4 zIG-t;d>AITEV`Pz(`vbhuW3IMYcG%AH~ZU%^O68;dw=%H&W9zfX;SmfmpnqRL(L60 zvlLGgi)x|GqpXjyopfC$>~nHM`m!^cwrwDij7d-H1<wCK?#Mlbty)+*=~8<H8O<Ms zQ~-E7p^X~lG4V1ZrHWd~5z!pqTBOu`C70q8tN3FNS!^r<B-x~_V6^h6Ig%ZrC(qby zYPI`vb~}a*=KJ%(a71DCMUkk6x6guPx=_1#e-q3GPsR#$4S=|_vSM_dlC!3$>Xb=s zVYkc!r-j+4XB&|xJM*jNAy%{nBBkRq&={bzBp^STpV*C?Nc{$Ht3vU6$%sKcmPMU{ zxke^9S*T(5v@7#CQ2oc|dsPq{ITCek5rOb-oTk?;Wo6Zv5lBOC0QJu2W;vD=+`~oN zqlXcO7>t$UOM&R_O0}1>wAWQTl`ZA>$W;pe^hvTQIlMni>MwgBPMOfBd`3;IE_}ii z5bTq6U4Nf6M*=k&O#1XVsD{qKTH}BLxb}9nEulTEsCt!xhzeK5;pT(m&Ip0Wp*6Lp zzRe~7WfmRpB_GRV{y;PbsnIpeXO?arprX(n;_f9Ga^PDo+&o}|&fS|GZRdouY4&qT z^o9ddTjV=NsM?w+?+0F?1om^1;b7}L=1&tX<ESi+k-G)^Q05MMtI~_{!ia8-J%O_T z^w(xBr;sglEj1Xkz$@{3K`4yNsmmr=T6;d8tx2^bBu1vh6h0dsFxvL~g)Al3*<3y~ zc&nxJ7>Dcwxn+D8omo&=^j~bd`{JZ8FWRCZE)BgYORVwtgmr7T0<|@pPDA=mmS{<h zoDgT4Gq|9lF#fnK4`k50pG$*U=-)RYgjTA7iS-T`j2^(0)iw7hbR*i3txWCY*}1cP z;)9%S!3Bcu8EEu?ZK)R3+3Y>hA%`tev8-E|=2f~xHp{Gkjx>@Qkm<xox2qZJBQ)h9 zk+4(j3?QM&a$hhkNQyU{=Z#!Ym%&_Gv{H>-)LM){Q}Fdhg0!)25dl81n;2AajZ>3e zEV`jO&JUD$_4`_RS{IH~+U(RCqwm4jQ_0f|tIYZT%V`5IC#={caqn>fJDefUiRYmS zOTzi<aWn*;VJ282HUTJppv))Z3`9T!R7i5E4%$h93n7YwUumrw!V)-95XL%<BA(fx z{8A1#5pdrX-A#rImrx^iBnH|?5*e0}uV{>*Td7KpOz#8A8W;^kvA=vi0O$NI644!? z<1{R%bWi)`C6haZYR0HPU0Y6^@Tb%vm+EKiW!g$c#Jn5;4qUs}%u$e>qyb@E>Kw9j ztNEj4)=tGW*12;ooFtd!-HqRYK)^!|^nzv_g(f}>q?`%rGzp_t*23Yk5C!^!B3Sia zv}Y44B>nNU!oiUeWTy2?pnYqPi=N)+4#`|@_Y)4($r9Ga)D(|3PA}m(L5QA?#{F-g z`KT`BN;PpHJD%D_49~F&6^Yz2(a0NrKoAI-Qhuhpjb#T8BBwR6OivBFf$w2^KgU?r zN&XRtIPBwM9Nw#tbcL0vUM4@$cIv>ktt<MO<Ov@!ln%ul@Kx@VKdyyRM2yHr^M1G9 z@xE_d7HE229{%VXW*2cby^@nC7Sn3KpmqLyNMnyXBh)OZd+(2@`=xHSj~IdUgAUOH zc4)&9-(t0ru+=RV^vh(!cU2C_K;D!wN<CI8u94<DMu|<egHuj^>`7(wY<MRrt~gtX zNZ_4Fse_sdd_;Hem}JL>m4=Q!Zkexq3*gz)Ur89M{yjf!A2W>LH`9%y(T?wxfUv15 z-tiAV4;1JTh=J${%~=gE_nKBJtRbc}aQNu8lNjB8s`A6(KZt>E=c_tpwV|xZDFiS@ zBgdAODQ~6@{9vgyY|)b)5Vj~Q#y%O++uM;*S)yRclLsL^Cz9(ppJw!D$q0QN{-N|i z2oVmT^ZI~mAVBW7H+Rt~&7>T7pxtto9dfLsh74bodMVw&lR$acDw5Lh5v~|*=_K+C z8?Y>tXKtsbi=&mV_RbHZmYi~^xBav$*Hk6ZbXAq%fWRj++RVC66=w2D?#=3QqsO}K zS1Z64`-{|_k+V_J5S%AGX5>miBfLHP4!!ukj4;#W!rPi-V{jl|z*7FZFI9(HxlUc` zX{Yu}p9<{dF(f5@LWSMwN4;U!Y0Q!k2Vb&RJ75h^JI7FKSgbNaS;+MjD9H6SjCi{u zn0xxzSr^HOBiow<DByU_vs{F16fFd>D)ANi_oz;A4ZTP_4l^ty?kRY`<;85^IGTO5 zx?~`il0hA!X?g*YGapQx+3s`iFY!p4&q}C_%!Zg-R<e8y`t!PICE??}?z(69OANo6 z{@cYecaESr`Guh@>FZD74WTIP40_<=4!>W<w=~QrmFK9n91FwE8Rq}|wfg=N7~@0} z<N7lK=J&wB`Fyr87Sb!Jg!Z$$7&~)n#y!GqW~XIobwm`hlmR&-MB3;&U?eE5&t%1d z^n7A~sXDHjE^45kvYIp%VgMIB9vYQ%%1SEsg$wnR$pfIv9`1TcZtUy#iV>-6J_rdq zf9Y3qQm7L)>0QvJmkDD~gizh4vze}Y`LO<+wH95BoaF+*S@ss0NwZ=_ijww~|AF7C z9t6Fv`WNA`YVX|RrCKhi_pfoLT=q{5ciC`XLclebO(cpSG6671Q-wR8oWblmCo4CJ z?5BfLTB++eAtGE7+4+tEvZg-K3E6JefcXaWYEU!jCkzy$HCGKqy9#a&IV#SHCudI> z&?%rTP)<X>9u|3e(#>5>XDT#s#R*46L0&AM`~5GZ04+e$zYe%=WtEF8QNO_KTsh@S z*=(SI@~d*0T;(hO_oB9vU%@kCxk_>BUsld~b6<t~nr{aZJnKDoKy^=HXTbaq^DGoK z$8@A<Ws16~P0bClYaZUdXMqmtwJ@M{xC`HR`oXqSUZQUD8I+e$Rf)nwL<<?3=_Jx{ z*}ZYY<cQ599ZIti(KOM*3ve6!n{y@*$qz*CQwjSB9^8pMYdEwB4+f>{sn-#OtxHaQ z88I#FWss6^tL?SnYcix*^{)z-M_b%(jp{`j+|rq^zSod6i#nf|UMe=RHu&JEt?GI1 zTfY_A*3Gxy0h!J(8N8PBO(5Vn{0iD5b=Gh`eHY6Up`5@KQ72pFUo(Dlp0@%EV@aJ~ z!zW%Z{mVgeC;mZ_gX?m-xgx9WeU=xK+h~o@AHE<GDS)#wJG=&~hjEhk7%J;$zhf<o zcEZmQagX&Suz=)dZhR%z8oo>s(zbFQ%n=J@HRFwLk$IfZz-p(M=74&*$lgsF!;Qgs z(P;7#jENV#>jF%ZR`si174oNkTH-*>E-*w&2B~|VIQf}BM}JK+_~jA*Smo0N=73AO zAH)|lw$^evLK$c+bFqu7p!8QwWtQlv`Sv4%WjhMjqNn@6xK=|okm>=;^(vB-KaIWN zcF=W}p!#<{KQ`te^7_)rTyDURU}W9vDmP{1nO7Gz)5G7q{k@9#+}2YYX<m`R;?<v{ z0o!XK=Rk4n^t(b`2Wa4hIF}=Qd5n(_GpqG3o&WnTG;dX@mtlw6<5(2=t=qh2#V+`< zOiWqheM)PANohcvSYT;MNtz^ZK>FMQY?XRCCB>xx=Rr@GWH%U@jS&#scxKpiN~;wJ zY$1R<!fT%p_p&5?MX}-pdCp7xm^eh6S*4i#fRcmUsWb#`?d2Y?8$QOV3M~X(`FgGH zOsTVc?`|%_V(3y|)Fuk9f<Jb;+`LbJk4Qw|MYxwWspjWE2X}H1fh@&M!i35LnJS4D zX>rn#e$`=|;7^oW{CKiZ&uktduDH#zI3E5fZ)8FJ8OFr$MR4WJyB4SLJ2EaG-gnFt z0RnQKznJvJQuHj-dkctadr|-%y7dj9(w}HnmtoM0$<%ABzIr3|CCv}UHf*uaz$B9a z|AyOl#DR^#jq|mR&y=pY9~if=gqe+|Gv%UWyhd~9>vcE%igf~&w{|(a^QWHOlTHG+ zPL!UcP_MoqwYA^|eC9x5nWK@LhP;=K^qrX9jBM`=ku+30Z|UPbc(HLT<msptGCCZ? zk76GT`Gg8wqR>e-5_ogT2TW6xj8?QN1wn(q(p+Vw3uEpJad4K>yJawVChaNAjPqAI zJ|z;+qXA)$Dh&PlN2Mp^5c#@4heUX}J8RXRZz2G?s_vT0LlDj_VW>bgKb_#1GkR(% z1$%Kp&x#M3V<SUwrL{QbGmtO~Dy)YBD<HA(>cdEGLaAXKuxgGhB30qz+u~*998Kg1 zfj4b(uRMNg<NfHKZV@_3khXS3%6M~Hzli5I?%U2tMVjf?ytNce9vIGbLqTH9NPl^Q z!|4S4ZZje@MwimzYobLLdt#FJkHok%PTNMfy)zy}faGil>ZWMBd$h2(_3ZXM^f-+E z`1^5f$Q0xkP(+6Iy-O}$!(!GV?t5sP?9?1iB&HW^a}*HU&TU1vI2S(^w*CJ>P}0fk zJ6yTW6g@G+R_wu7#{c(tW?Q|yz?zS5YXMx8JjBm3t%jZe7CRxN4andL27_HLef1-o zWYX~L7iaHKi8~#-=uYUtlR4;qQe`n=T<)u>mB!*Rh@ZmPy}}u>S%S+92{H<#>k$Le zyh@E6q#T@PZFwMD#qJHQvQGh5e)2z*1CNRmT2r`umOX0FH0T%FJ`xOIC|ShbF^{q2 zx(MxdinH;G*~pXRLcpTq&#U+~2g8W+fPXbQohJs*hFW0~kz!o@G>v(Iz7_yTVSu;G zX%HOU-?_{z-?-`FdzPtLQble6z1z$yz(8m{VHIXKE}=a&AhDTf9VGd=HW@h5a0Q>m z(4Gyy?YczQPM<5XU(%y3!)|idh_{z|P|@!t)E%=9yGobi(qP9Is3VT1huu!mnyY&d zmbPp$^Jowz-FkZJG0i5dTHT|>Pg$^8(jXTE*oZSc4n}sK+)ZNhR$a_tPsls!;?$Fd zt#N|u-Cq_vcBh_&i~xGVqD6g*pcK-0HqezFa8&i|^0|Nhmv_9mu|zmJwR`U>s+fcy zw%>+rOL!bgkJImg1uLefwDXCM12-@}<tB~&<R?c_g}Z_rpRLowyuNl(-hLtVFa+!D zK)INQC?@;Uxx<UF@5br4^Y(&9?^1Z;!Ca7fC6#i4;CuXA8QOuv4{Df(&54^;(4;V5 zR^MQF_k?}m9Owh+j<j8lff%fD1>;%AWUsAQSd}SIoHRejjbx)h@-)%eO)d2)DhnI# zLMyT!TmalfYYhw|Q(a0wVL$>9ae(7qT<*uvIIjz*d-C%zAYsL^y4;>`n-Tz+JUj<8 zX?(;JE)JM)lN~5EC-Xh_JBJXQJd^j!0K*z&vSnS*=e!?mhrXc1ydY#suM5*xaj8c| z4Dt$f=wwO_Kx^9NN)d%SUe*q0=GQgs7zGy_2{$I>#j7jAL56#NE%&S?j+6xvBB-bQ zh>@DmCoe*v&a#e6uzj=gyDxK)2*bBkfDqSn+tVH;vz^g)w-i_0om`_kOp{G6ZmMJD z?C9AP{wS|@i*m{JnI_Pw+qabB7U-eIEpYco6(Dk-XgG9hagV|g@T<fQow2XF@n%bz z#0}K8ea4&4C`w}<4n0)YG!4I+C*2{UlHcq5n@FlK4b&E%GZPoIkv^O-&NppYcU@k( zIx0Z+_Gijqr_7>m=A3kSGccZT%|S<LY!Rl$taNZDUU6-TtQKMBuo)V}*y_9|lzA-9 z;5H$Q6*!(4pbJ*rLS)b}p58fL!be>|fd)r<b#K5^0rCdyZYipk!nW?^553<+?uM`E z{chD@v?TihH<v8W060Kn?^_#H@_Jh=KSmxW(g(~?18jh;wh$!CAgUU@jf2$tfR19Z zhIw|d#J#PTzeMJgO_8dua<m!rColt0qotiNv(0$OnI@&16+4Sra$KAd->owtgkV_< zGb6C<*92Y$0(6x@W+=sgdut6c9HHP6YsN)?lL?vU^sR9i`1lcMr!leugj*J({;9rF zkn{`qSGF_!IU5QD4qm>(9SW`_%aopA)Gu4$=!FDv_->M#RR{6dS_r^TYpFc_4fI5P zp0ox{w8lcC5bd#4Z!;gpR<|VtSXcvF4Z1yAFdi2b#PkgLpDKh<iNt9~RMJ!3>82Su z&g3?HcSNfzslCW$?{CKBvdnE@-*_7zT*4XtqcU1(pxzMRsc{!>cRKi~ywjBN4>gZ> zjc~z;v>i1fw{4TCq<Kou=HRH?mSOE0n&GdSL(fo~4_cpXkJE$geddzinI^;~GUZa1 zG|3MeN7Z{skI&$#KVy4o@&R-8SY+<wK>%yaR#;bpYlyT_)bI09_nMf0)W<HqY3`yD zu%z2>a!TfvgC~`^19o{-MpFDRf#I+JoUB`4kLLnQW|+dE!o1JIQ8F^0W5WJ#ARU+L z?xfbhN;~WCK$1>I$NC073j7fTEt|U)$IAO=>|!BVM<p>h&R;!&G|U%!JyFuN7XA?6 zjgG~D%GZUAu`G(k6{e}-|K82`(8x-)t6QD}GT)Jrot`zVQ94|@%qeCASK{6Sz@MTy z74bdvh{FCy6cqV&q2r)|49~W~L8T)H=nuiNvGYdZLKNx%CJ41v{ipObD{g=8g$Lkr zST6%bB}2_fim%L(0YXnspq;A$0Zt1Qh;87pZJBG4uTZ~}zZVdAFF8%#0cI9x3w)_8 zTv*}BpV=Wg!Ez<Lb^#-HNJn`W`!`a_LPRt<Ztb?v)!r<}MkWb`ykjdxrQm0UaVos^ z)fSDMP0x%axcFNQ_qe!{F$kMxWujx0e$KuLI>)=k?@cuR!)5@|pc$$cJ>bRW|BvWa z7pY1dx1I%jwngAFq%ud8QMB$_4cBe`nC0@=4}`dHTwK{xHSXl(-H6=dvA`(SxX22t zZVj)Lw5SR4NfdtyMj7Lj<#_XCW%R@DU|QsaMg$jq-MgQf90ZC#SsmR=KU}+AibD); zOize?`647vc`CLF1*KP5bfhpD9=Zi;%nVHH;UVU}w)<vFCaPzLepWC+9m%-mYppQ{ z@!Q|^4!<QOq`1V+T;WrH?r2J}gk9TcD>odHgz22}Dr7|YTCiToU_1_xS>VCY;V31z ze7<kH*AeKPb#NhRG!`<y0W_<OiiG(Jlj9l7exRbsg#!*wDEX&*HtlM}w!D;V=MQI0 z62-z-3)#t5U&dCx0a{oD2lQoyqOL-H{ZQzT_AW|zz_69591p!i^p>I5reUD7Ddakn zArt<x_oUF02KbYuR=ug29MybASgmBtocpOVH;BU>>-4%|`ion4{Ok2r2qH){Tq%TN zQNNW|v5F;el2H;Ed+n@sS$ExsWbyZ%fu`hfW;1$N?uRd4FyhiMJ%BBb`;{OR%w_OL zD5$&snWnECrvPjrp@<4tO5j4G<K?W<i@b2ex$gdxSLG`Jb6u(EK2kDZkAN{`IO`2) z*KhAn0dT=SUNKd)@Tr{^{P>$MbAETY?Rsa^3b64|mz9f9<LXv}9aSTYcYy$Iy4&PJ zSB~at8MP})^B|N5BD}CwJ998$*#DLA(>uogqeQp|0t0VK9n$K-U>4@zbWYl`YTNI( zLg+9e5@SMF!2Vl?&$`b^wWr-(%AIzk?WX56x{ArB7S#*W126O~iEP`j{S)!@I)2Pc ze~f&w7fg7r1ZB{EjdY+7K>_uNR1n#J<TS)ka$jdsKH1pEqRfHl{-aC|>sTLoSJDd7 zhFMC^(%C79xq)R950_a+<$S#dD*!L>)b`}1YR);UV80!-Q;gaHdegbF1atHRPybo9 zpjBILT<<liWN5Fqs#gzKjLkT!%mO9qH(u{Sz`+Ihl75sf1Z&iGP#%|4$^fA>WVX#s zV$AURyw952tycI|hzcr6(nEb-@z4I_S67b|ux$O5nUi73>!6E-$4qH6?QDbvjccXo zaHGSuk_wc56ME5NPuxd&>Y2bs6*H%jcofUMUu5-Yfm)1`u?3+l?4v=eB~Rsrnttz4 zx_0c$#D|KU25UbH-czfrD^^G{DNZ;l1o7{NOcN_%Ij`#C)Db2=x9{dn!=a?GO!0-t zB&-^CXgbQei@W2wTNp{i?nNPeO1+Pd_sNtgA)*-D&9p2@94|Td;DdE7a5n~Fe5U#e zDQ?x$JfE!sCg!i#<JVJIKe$!;FxL=MQuspuUVD#&uQ2(@0!}QMeB5UMM+$f&q2U!| z&EM;h%1}W+nvQ5w1^<q97mVvyx!@cun)p%wf*&t{^XheJPB`mT5gxD2<e3E<?V}Mu zl%%?zmwk;Vv6~u7(Xl9)auiqNsjo(TOS9FhVZ=9a-(FfvFPr->Q_y~AaxAD>;A6Wk zh{wI$ANaXEf31Q1Y+L1*%Xa8S$B@<Oe{fJ-)7R2GC!S-i-;Us5nV(vt&+zDVl-X`_ zN$zL-J(k7O+X{9iv*b5T*9cfOVH~}H|4yn0HoeYJp!e)?*R$erFAiyJX_1}b!1I2( zHS(*gO){@LGl&+KmGaCkxXC7rN|vZ3ld^X5<5Z4C{}i_J<&#qHFxZ`acKSx0LpkWs z23w^K>P#n-GikoZffxZI*Vci$Iug%+m!A3Vo;}%pRm*4iEdIfp!&KAeb;i;Hc)Rp@ zqPYv*cmqbv!)rERi5a5+Jer~~X#64n$0g&*bi9??`MamVQjZRz3DO5%hFgl1RYq=; zI0B9LOWBn3U#|QRUKog)uZ4$RH3#$AwJcs7XqMpyzeuYcLdw1g{bFD}T{M||J#w@w zF8VjG;p)2NwZcWX6Wfe)=(bcBZ>*H&sBqmbs^3F$<u}QcwQyQ$i)&pUJb#%dRn%`o zSV_9rFSzsKgy(!VkH?RCAYh}+AS;HGW`@CM+NT4MwIY=r6P&#nl&0*uq3)QPnXz`U zcQ`N(Sn5#hM?Vy}V{OC*0nN`GPZF)1OWrXn3=*R~s|^sz?dSe4pmpYag_JXm|A-BW z+^Wf+4F$xRL*lu_PUuBkDX9N`eG+=667M&O<qq_``58;)TQ~GcJJe&~n62>jb}CJq zrxbv&v;N+-$$VPBcE(&Ic8i(tP^^3b)D*`LzuKtwT=2KfHugPry>y<bN=F7bNDPg9 zS*3o|s|ZZ<4hpaY<Dh4z_AbsFPJ!*lHI1lcyBJ2!!9G54CP_#YC(l~c?Q}%jYI1jS z3w9q6$yrH|c~8*ZbfvLc#oVpVehd71z&6@FL^WX>xk24?-G8;eb>hWEA2KH@c(&3l z2fl7Gzc)PxrymtwES?|7e9RUM(K7)(E>(G?jnjhZ3fmkEer)xeW<wfFw6vWq$EqtD ziixS~u!C3uplm5l-sWabZ`sc5EFe5uh1gk+)(9MJ7ImnE-<d6ts_9R`W5<Y<1RA)D zg|7%Y{Jn*I6Szo|L%43N4Ak}+d4s<0Wr-rb_cwE((%@~MrxBbxJ_yB8aa@H2>j^>* zs!~;K6ggtU@xVu^L^)jBl}kLHZ_h{^5sV$>3x>a>`UQb^cfWSfYp;VSm}@(g^~h4v z1O5j)NtY~wEGLrM+=YM*?ZM~cun=;nsmg_vpXTxh|Gvo{J{$#zT@#M|Kk#3~=vnZc z$hAYTY=G}#EclDAuJssbr9cH>pLk+%?93^wYa&_0HVzh%tt(uZ<`Vx{oJwNw!u-26 z9xbp8)voc(jot%M&*W8xIXshdVmvlgY6PK$m=1nI{mupXsk)YV_(&_n?EZ1Wdxr}P z-$TfXnwY>xWpx#kYn+nwT4#UoI->Z<oIwxzw5OCsf|>{>*Hl~_3o-&P#J~SSa7q)b zC#R|2ext+!L42UZK9#gR=HtmS8~+Z7T9nQHyvlcnuCLuhWyF#sk5RG>_Z%W3va$ed z=K*dUtb5r<&Ww_RQF|`<N|*nVX+?S*-dVb`4n-~Y(zN&6w9j#MBphFiSQ+2R)OCH% zA{JUHq8eL$tKTgm&dxFZp++&9E-M&Jt=!fFKw?0;_GZ;S0w+Et(m$^gP>a3%SiDeH z7cQH!PnySp0`b8meR_7yo!3J<ZY2<(zo|r#c>a7K2fFP+ztb@VhiA2M3@^GgJOoVD z+h~!xUY#~o(9`KmAJC&CCAH4TFs)vsC6T~^ok9mAS>S;5ArJ1S$bq*E5(EkjxnhGq zE!5kAywQ`FvNbaqXQ5yb=a~B#%P#u~m*k}x&X`4~K3GW+9FoW~dL1_Zt__mcPf1Xy zck|%8_W=wTI51Z`rM2-cRbV-Ja`*GSpRM}?wv3!!W|y%}OIxb1;LrC$h8Was78hnj z_xnFpmwb8d?U8HI&&(Wi;%6dnvcD_urn1Ne*aeZ6f{kbu2nmp7$y5TH>%%<x2vG?$ z%Ss?&1yh1T+|6csqOdk-kcSnu7CC^DSmuf|k6Ml*uSx4`8oWNxrq4stOtZ~gWx`R~ zUNQGR-40e@*6=HO(xv{WxJq^SnI#rOj5W;3DQSs{$7RKu<<-yphFGcNiyFyL?vEbf z{^ve5_Y3VmhBwzbKIG&BN5E=#i1g*i=3C>2m#$q%oZq*6)7}c4lcpjt$B3}~9tWU> zA}(ZDq9Vl*<d6P%hqp4H+=w|yybwlRhPHEwN`H=&K-o=k2*gQdwuk0p_gz^_8;<NI z*j5X=x;6c+6;>Lx@p-a^uqn9TpBvlwz;0K3#3fx3xDS!rGCU2y+N7<0^y;JH@+yyA zy0NGQkAah@Fn$ICq(;w2A_1>y88$Y4O?ko--X&m2#qCl^*iPUQAa$9@Z@KGnNQ-}a zTJurj(Bz@<8?C?tbc^stWs?@NnusH4Hqp6kS9z)=HnS9HBw3jf@{d)~1zaDHuq%cL zKpX6e4Yqg2GUk1)|G==4D2jQ3Sk6{a$|Qp)z7w_HF>P+ji!=`+<QxL|5Qqe_(_4%n z>2yLNQ;)ln@G}KSIaJ=DP^^n2`lVJ6)NDhB8o^a;+)uNX5Q=^8;m^e6Oy}}vicR1j z(&sVom8QH1gS3poAQ6D5L+?TVQaQaY2~jc1cKlJ0aXLd$u{V-6!+nRJ9Ewv-{$5|~ z=787oLkz|JAIFXOra!fGmyES50}dUxymgIiFqp?iR9Cfwip`zGw38K%ChPV2#Z|p3 zi?n8P;nE;qORHqdOuh>S{m2}XLF`LnMGC@Zmt?YW<H_RnTf{A{nu?IvZe}{D7AUC2 zA?;A5ROZ26I72jp^<1o=bB831nkWTFRPU15#3HC(rwlf8=J=EvLSS<=IF{;<t64Z- zF=Ss8X-g~os^a&FTxCh3lE7a><s6WmBhf1+48eMCv5>R9-v%oJ@`YU&cXdTX2tc&8 z0H2@sq`Hcd{&idvotAIVTr>p@0%D?INQG3pcjDtc#GsnjD|p2EHnhS1N;P^Kxv!RE zi+2qa09$VIUpM}v!=aFGpB~c{3@VJW!bsebvJF=V{j&V?t??hmRre;2=pP%MIuf0e z%ViwQ=iA#n?g(SK%vGpS+WO(`?D8ZiVvD`qIFRo-!3I5v{)dJs%bh`a*{HF}%HQbn zc1{aG9$(^81|9+42P}4|U;=+PAPYNDGAQNeoFZALHVb;PqADScBLCU=pOiqzmLCY? zb_fpKACdzUYx`6TE)n*6BO<HDa1sVC__?0#s$8K6JODOEygLMed<2BZp3@sZ=u{?f z3zZv^>0ZE`BqOWBJxEKQ&Q2Of=o&U9t)tQX2~s_%*41`G3zL*d`43_&T^^c=j#lSS z+%<SE%9QW#=g+ZJpRxDSMNCaNnhh-?CpvghlNdrTi*x)*-&@vtTXTPi$Niiu5POTP z6pu&cnGw};COXnu-LDw%80EE7%go~O?$g8qs6PCDsn#Rm7A%2(#83cjVcPtldW$@< zQh&y2!ifvs`LvaH?kfcTA0_q|``;S2wh`J0^59&REbfCwJDMH^^%Leb&8yHIHA#^z zhoH2QUmkQY&VLvJ0dJmzy)(G1qjRH!z~B6N2rhho`UG;NTIQ{zk`9YgSe17QDY<={ z1-QYpWJ~^mzH34lCL5<NG!kWK*CWf@{DmG4R+u!YBB}i5zx0MTf$4#V$I3vPBwB~4 z&=`L@*|EHos)giVvnEbQ(&E2RC88t)A_DYuGtc6J9+6l(NC|Oxe16b5rxM6U0oQS1 zTb-H)NLa2dfKna0cE#pcL3naBS;-d#+C~YRjC)6twQ_HDU9T-^S(=grR1UP}EM3{t z$LOLnCrB-yek$%e)ER4?Gptb0-}srSC4)K1MX&zIbWIv%M_S1J&%AD1i*w74Z`Le( z?u|J1&QNim#NElOXi&zqb6!?nH;SR}ow-%RVoHF@mP{6L7i#+s9wau3D9cH1PnFR2 zKpdnp;}7Ut<Yk9c@xxBZTTV%J!QnAdV7~y+ZX3Ts?l7!p|72p*y&2}d>goH85{*8n z+d<$=((gMbt7KR)2Rl&QH|Jq_y`xT|x?Q;E+CkzQM|P9LA9f0}r~suAqeFRY{8Wow zm4iFH?}0{EehQNgMh>dIkf=U1uul?wUFdgn(~~0ny}gZkcsU&8cfjf1j`qgi1;^Q6 zZw*f^kN3m|g{$u4Du#3muvWvZ)nK=K+$h^{2REkbCC5MdXf)`*WPc~#<xz9C6IH9w z-Kitue)aN7smUMNI?1&dV>|V`^%WfKF)1=4rmFC~b9np{9f-O6lX%PY72SFU!Ist7 zt{@*hZ}#o3Jci+oj|a60BRB5d4KdDv|M87qqoHe0V0-~(-JVrZCiOV|QiH9T3L6`{ zb>jDJt={W)KLor5Ev}1MDEk^h-8&8g2ZW?wDOoO@hsp$ed*)0mJ(Qp-!e?u@G80_U zmzO?&5wkLlm~M#{=w0Z(vg&}pKePjLtCk+>IN<G*3P1oebIo(a#7pF(QD-6ZsvbcM z#a6|8kM?GM?mFpZtAhn*W?ns+%VkYRpiW>UGnYpbuj_>I_G7A%Qob*By}>gKBohpi zz|Yx3Y&(9@9>Qql!WVb}fa&J_H9SygdVjIQqnP(CwH6F~9dS|?dvNQT_ov0~K%czU zdJeyUlZ$6VR>dO;n(_!i*L#|^ZA*2j(9LF9Zy6h4Si3Vjx*40Il-u4ozc=QB9&qf! zMForAtdfx>EMb5M9&A`Ax)*<$cBGnuZxwa)yfGP~?C{|suz?}_TVyy}_vI99^vJ9e z-0Hg%rNvSgQZVbqS7-u~(F%NujcZZSPl*w(y1W-Ib}_&$R?R<r--#D*RrP^pebOtZ zCHVu>5Sf6PH*t{csYCe2=R$<3YKIgHLNM+(ajtK$6{m@p6k)f-S(oSH`LxCaJC)0& zZO-mR)V#u!aA|&MjD3|mdp&(kOX^2U>GV|&TnKqcgQfT1{XIfWRMXQhRTJgmFN8#O zs@X1E77LNK3tQTZiAtloxZ}giryW>AW|we+akK(OL=kxo4DJEgM0`t9!imG<KMaLp z6wP0NPk7nRV`|WSgkf(NCB`eS!bcT2633379j|rnGatKMHm*O)<`Dbc$_-76mq#Px z9oYK~S~vta;6y}4zJqh`S7jsQN8W8ktv8DEwpo=??_Vn>s#sNh@;0ggedVv^)@4i^ z=T)f-v7tx9v?VX%;_1Zq1-mbvg|Xx!<2)Cok{!O>HmyEwRez9GXkgpf-jd_mPWkAm z1TAC4$g1-n-N3m?{nEp9h9=9(RZP2txuJ6L3rFGTf|xi&f+$nr%y7te(*W5+(+mRm z?-V`^3V)Nw)zKP&?*rR<kBiCpQefm<(bqRda_@ehUxuimrw$wlEaat`mOPK^h!4>G z4m%(AE*&<Cc7ME3M*37G_vxivJAK3Up?P1$=k8YaoUF+1D9|8aj$!9z-${$(WO8-8 zEy3zUN&X!xJHPs%@wn1-+o|NDi{}P#H#9b*w%7vwV%db^V<+J-i?6j0z(~dUfqbtY zvelB&tpk?~UT)w3(yl+7U98GQx@IeWHoK?$I)~b=<flnYs&l4<6lzDJ&meHP@sfP| zmDEm|0A;aKcfvVP(P43jlTUsBPu4ku(}nI7EyMeJ1=3(%lIe01ZdoHRKEK*~FDlFr z|19Cwg=Ee`uo}W~6ZTssNez1&{=G2(oP<)8w=5*ZV0$#_Q67=>)s84SlZEv=DS5nr zp`zpaQxgl;I)`%)ZU|6n>L`O_NZ-*ui9%!X!HuPq9db*!LyW`eBdZ#;BC+@vECw#8 z)zP!6c!iRcMXMsg{z#T>?T!{<Q%;W=J!e(GWvn-mX*@&INxSkSgg|X#Vs>KdKr@9x zPrb%Jz7g+w(1`^Y@-W%$%-)0Tu=_?Mhf1P_gVU}u9(xVGn(~B;1A%blZBJ9va7-eZ zuh|S}`U~91_WDUH8{jK`z>sC*kq64~m=!IBRP_(f++gX4m9tPWpN*Kr)J9DJwbp9V z$T2rWxJk{f2|_1-p}ZhBfmZukD3akzjfiNKbPG9DODG+wpd~j(V*sQM(lARs;NRE; zLtZV3P~ci<Bfq+UVRM5M1g!#A9~+aN)t}@|h-9O-g>gkeltn{d@rk&ol|967g3BE| zvo*Z$1!DRK7H(-}2nc`g1BuULKq;6GV2YNc85<xA-&u|X2G(QYklSD%V*;b+_NF#7 z@FZnGxk5>sNDmDPVK4J~b^~PA#@U%w_>@wY@cj9jppfMmJ3g9)C+Y>c^I`w(kVEgx z`6Wz>@_<_*TNBNI(V3xi;2&NPim&TncA(yAB5HDo7VOL07*E4tX`Kgn!;_fERPdod zwL{&mAFx2dJfRKTYY8FF(>Vj+_iw~HZ2blo;Rpt##7XDrJ|cyGt`=_G?CO^B`~)Qj z2A_F-mLDpC*2v0!G+Eq*TZ-0^<H8RNxw6UKeo@f8J^JV*#W$o<a=QvW*-T_YavOSH ztVnz~Vhx<IvsGY?XAB>v%6d!NlDu7uoOUxnxluC%py9(^kGA0>+iE`=i<)_z?J|Ue zKF>ZQllT?&`_IOl`$@_Wq6+n|BO+FQu--^DIU*0#60fsxDQ_#xy-1w)`Q23+37t@N zbR_L*FC5i)E|DH(<OK9(Lv3*9Fi@2ixET>K$S*SL(vvRF{mv}7ndVV**u4o&rZ|RO zbAHN6mvOZ{wt?~53fhFyW<QQyMjq|pj=K>K&lAyb+*k%tb5uG=bltp+YlDliP``or z6DggL*;s4y)a$+5M7POm9>i^7lC%ILFuph$ycHS{^Es{6a95zNt4X6g50H=TlL<l6 ziwGxCKSg|3c!WWGK>sh}?FiVzmk+yn_!wl|f1$q9E~?M>6q)aw+H&wsR%}Z4leHCZ z);YBum$<02*Snoe`e7Q+6DIh#KHtjaYe+-1SB8SyRkhcFfo_&`tIc^Q4On=RZz>Td zh23a$v-x+e2~gMlk_WF6xLYk@*#udlhBFFLkJ8V-VEtC7P5PUL;_9V&|7vcUkE`Xs zHLko`>~{rET-a6Pt|o4|;TedJM=nsXpW-`&Ji2QZhdWav3#W?-{+wL>oJ(vo%VX&8 z9MnQFzHg0b-@YC1^N1H#a(yKz*6=oUv@qwPwO}$*+%hKSXe+b)SB4=x3^$yh^Y<a} zq9J|`CPt_x&N@Pzo7^v*JAd*+TstBMC8OBSqTB$E)U!*6Zrv-hQ7^h<xY2FM9V|%Q zW<d?O?9vl|dYpnjwv41390n9_Z5alE^;yBy%IRrKq3nSsClOKT7opD|D2!HN;J_0( zOcRx=(MjJWaE%r8-S~W<6G_vX>>+#{Fs;3E#94@of-C5zTqP>m?%-sMlWSZ&6ix+< z4;vv$WBX6{uX7nXq?0y9>$cHqFnx92H74q$<YEl!S()Ub_RLNpslU-&xVjK?wsQ3F zWdUbK7ySADNkSvcy%RrT|ExDXQPFLl;_u10S%#1a$}XS*cLv4O%*PM;eb`7qG}N_a zW;9TomrpS`@1S0cL$(ju^-Z9mq0_OT&Fth=qa$<J7egJB^X~yUj;_4zMUkb8k;Lm1 zm+?;Bs_Y;j|9GvN7-^dA5eSrOZ`-kI04Iw;SkXZ9T)|E5^&n4_;mBwj%BuUDnj2>r zvSf2=kDM}Lin|i`<;xiKVq+f!Wi{X)*4^;Siw2B25_A6&2)hnT)E_`evCGWkEZ2P$ zZ_JxNvpBzUxSuB+6KzZ61cuZ{@;7uzZI?|VLXFy%tGl_P1F$VxkQUR%YamUtDt)1} zGv2}ULqXc?6?k59xnvjW!-3<d$u?~bGGr$0#RY%xx($$Hzu>SKIZN(JAf)^|*W6(e zx-8UyH_1{u3{u@ME{sG9Lwa$7RAi92skP%qCMa2HEq1(;vC?u|yEUC+YrDhX-Ar$! zxqBnkA_Y)s13kwpJbX)#$DTLkfw4luFCIah_EkQK2qeCP;n#>rO{Q(bjqU_FaHQye zPmpnlkLEM*Q{a{o2j}#Nd%?*Rui^cwiTaoEF%|h{CeSH%8X4eo<ID8FElQ7%9`iS3 zo%K;(^Gz>sNE*URFcn+zLHn@S51crZ=Co(9u~TY)QTjzy#}G_#DE64Ct$dLxE~j?E z!_iifak!?wyp7xXLc@9B_W*eS*=W-|Bb@|>6OTTbdLHC_=YN0+kuM9AoNnhN>H7=V zgj_6QziYVr*Xr<}m)@tBrcL9WRWt;|Fo!l|CksJjbp9*6(^UnPSYO6z9niy`vj!KC z;fD5&@Xqixn7!>%vgd(hA=8<PVoRTWujvX{%c=0DSl-n*`?lles@o&jyWdG;gLK0Q zV!5D!qd5jgyiPKFkqv@^UlR$LQn$e0n}f@|201y06ukYBJ#SfP<*C4^XS|H$@+<`N zCqrRKMkKulY$Iz`p}CbwBl1gx2cH&vJ(@CdbbRbFl~<$NERF`-^@LD9IGD>rg(aD! z!H27dp$E!}sZiD02OJ|WIE6LlZg_Dpml2M=#Z6cHY9X;S?VHSTjYs}q4L%$T5IJ^a zNletDtP1b@=SAiCX~Lcmv1$$h^krz};hV9`&A6U!)OptrcsCy1s`tH+>=$o2X+Jt~ zegBgk-l@N{gs;X*ChXev3$=sB;fTKz!?vm2uSs?(ThZyd3x{;$PLH>w>{$lePi3`5 z2)9z8{XH&F<Kb4=$&xy3&^~k`(5yZ5CwZnTn(FdNqxn^=P5rE``os0n(<O@+2`&hm zFue@m@8=BMz<*u`LFQ+VlB<@-fFO@`$vwvW34@Epr2(L;ZR<_)y^Efd7E>8jFJ-I; z^ZXMHcsA)N@=KN`<_#-rzYN<!CAuPgiuDJTRNa1abxm5~{^EpH{u0#U!sG_xPI6v) zFOj*v6tfroaErAGb(p%nCChw}*|$pW_fVUiqwBzU7&>9k`aqwF4Zh@17<&}~Jl<}z zvrDblB<l2Jyjjy)p`qHDuK58)u`TYLt^94-MGNRvb&WrQ*|rr>D9;~MKT#O8kfp~< z{6>VZcd1z&Hye}PcfB=`^0g9hHwhq1rOW(-=XJvC>2@`t(x_M_nz@7&k7RXJG(m!_ z3v;@E`V1MPpdUa`0@M5>JDghh<gkK5^dBl5^AO<4MXTaV71Xj%JMAnH!j9Kty|;QQ zOTJw!?6P5f<n&QD#*Zgf>@~pgk{=u{ejcaQ+bUWQrs7hbc`Er{C0M5lqJu{$AMDzW z<o==F^+Cn04$E2+xXye%Q`lB=`MBY8#&x+EvQu-oR^g>Lv0()FnfgDOmt`axTHa$h zR)E%W^5F|ZKhElWmjn|#a5^dCB;n-Gka4SQPFAD0N;dEfoS1$9I;CAmCY)SeSekh> zZ(`YV<d!)LoVZZ6```)dcKuM9iy`-lu1m{3z&9IP!vFd=IApVMg+5n)>M}}-aCUXl zoY4wW>2J`6A)~8p!=7e19=6f72-wKknIo|9v?a~_d|&7{aBby-yPqY_FX9<I-`P26 zHNFVSeDYDO>$YzBc<jLswoWiJJlWU`gv)feOY@?(w(PWek@HX*d@s>^jIW0q?7)=_ ziJz0Mbt=(d_cYy&d2NGecK8zIe;eGmD=#fOp%h`&qT>Y-!sLlt9QqWEN3Ewa*pY9r z*MR}+)ISl%l!m-%pTL#wgi%^U;6L`2M@P0TxuL50fR<QPAE=uV$}$f0a~Y@ynfT8J zKli$ILsL-2o<gqSEx9Q+`lxWT*3rT0cqhe^%Q@LEzmt4?!?@b7kB-XC=)ttiVpnJc z>rLHGFaJr1!&D9J6f3Hi_a75{gh*wYvs$txm0^6~*dyu3uj3q}`=ru#o~we=1~Tlr ze2*IX{7!!{rDg3~GK+@8p!h&GaW;Q>a!Q68JdgNmN0OqXj?dUp;&3_3q`mO99plWx zZizfKwTQ0fdz!@;6PQ7dF9xFL>a`aUK6kv*X|JBoE`d{DdrNAfda9(<caSY66)Q+# z_$6njVLla37{g;)w2MJrm3_@ub|5=uqi?w^jsMC&)e?<^w_X(X>7|qCNpyW;KvO5n z467=WlgB{GqOWTbyuUrC?%w~?pCsrgTj?+S#4a%&X)WvJOrT@Kwb4H3f6F}>;Y>>w zrS9s9k5A<Ft6_Uhd@}*f3<%1-sW7=x*^%Y96@Tx{T5nkIAsIKwiBfn~W%|{!(1nPI zF)PS~y^9}XR=V+;oVKCyxsZpAUKV9V9XZ1^OuT|p#j)ow_)yvEnIOb?Z=$C*EnSGJ zv8b&sjob5h`@USR{E9u|-+@R#)%9HS6Rgd6%KvuBt5xH@K_}Y}9A?5uhIUioW80B& z!=9YEV~%RkE>0d7>Zec}lp~l@CgON>W+VGiTA5;XU`xQN(th)<!EuH)*8ui|M0iJ3 zVk(LieBG_Jy$C0BfDcH0g6VSpf!!cNbi6!foTl^5ke|eIf+=#F;I*+_hc*^4%7~|q zQ0LP=N{f1dFY6^pM+5aG2As1)!gy3ivV}@E>FMM2x)RarTj=61FaQe%_$X1O$tjZo zfmovdrlcdFF+XOptHN|WUTc4;(5rvUHCTZ6ms5?4GIqD}p*J>(TP=<azHje0i^E7k zZ2Wcj+R|BvsWbiy&4QuvGX$#N0}9Gb6OR%<Vru<;^Bd^T6?9`*YkKliI|9$}W->T0 z*Jgw;`wJraIXFidEY!$S8_v#%oYsFuIJ5azZuu~e0xV1@lXU}!bIMlS{_uTc>mQtB zN1$2F%t9jih})}#O>>NTA|6db;(ofgBFfUiE*^g@gioZPMV;knj=R}k^58IvmiJc_ z5~0riz5^RRYmX+`qA!BJT{8@+@^PyY?J~M^nBYe@h{U2Xr3`zI$-pNEwEnt&e0MZ| z;no21C>d#&kuxx4#GnA?;S`=Pd6JE-K2j7Y7{z9+e3StA#>DwEK=*eT9WISxtn|2Z zw%%W^8p}fEw>}+rI1O8<qjd0tx}HpUdC61?r|zZfQ-j%K+HW7RQ{TbRPMiPIoq2$- zA0eVS;@%HXslHIMUkv0&C(5byJLk0|Zkl$m47+hXbO|r;V}r(sfd_I;Qy!~{_}kW~ z$*Pjl-`c>qhoM2op`i83M#Oi2!rbaF!nk(<=vo+FaqdV?&VZ~U{2f*!BeZ4{y0%Rm z@u@c;O_K0`XP@E*k|vpnJlL4>M8LLSgUDpgb^$o9@RK#eQ}UZW3A6gJlz?L!6QHYT zx2P&iO2JRe>-as{T`W#6f1C7G!ACUK@{G6kOdyjE=n(cFQQU9(3v?bW>1wrQ#`SnE z*53+CM7k|*X&VQJqfb=z3BliTbD|+#yii@pO@&5bM*QUOwi;=SCi~392bKZb{6hrI zE8bvNINk%f1CnGCCK6sz?Jlf$p1kN-zYn8NK;WZHtK!HGXc?WKLXM_wmI~A!`qg!L z)#%Q7?Ir(<jv`1~McFd-qrHV{cP=2Gkk(D{o3VTLOsnNy{Oz+$yxyG}bF+3y_KM7H z`5Qhc7#KTqKnUT=WP5gmacvi<Ty;88545=mHB}2eojH8K1ktamcn8-C8irk-tA@96 z4K(RDe1gk@30QUv-yK>jkl~<rV}_97aO@UJ&=%F-P$}&q)bk^f7E;%-|AFD9Z>5}p znUA3Q2c>NNzmdGUw{4Pk5CZ6!c5YQH>`qfnj>?<8!YTjaX9}_c73ev!CnT2|zBLiz zbVK?8p&RTOhkZHWXqtYWYU?|XvKQ##`B|kfeWrIDNQq>6_^(4eTl!}rFBnq7c8>^o zDFwMm`L-h55}PC}R5GVH*agwBZnw-#bo&v&yaw%B_f%U!CtByUobULA*<K)f%Ay5I zN)xjqA@8qstBkfomF@ac)6mK5TcJOubAH~&nxzQ_V1DRE#^UuCMwHW_<UF)kb$KPq z(dT?)WxH+q#UI=S`+!^`Pzn-i*0ARGwg)M+AkE2cC{hDE!?(?(j@Ecp;LE;=y(l!v zDB1Uv9-l9H@I_+D3gG{<lXrjzFgvudtAX!7I{_j*>(o)Oe-9j>dI)F4w=QKMX9RT~ z9)RnyjPHcm>LF*UZR)c@w18d>dBok9JQYRux$8oBhfa0xLKC7{XV1VjKyGSuHp1d? z&hN0f(3%d+k_&1^qC5~orRr$xc0h5Mv!{{r>3v8xOAHhkY3WnUl^b{lcZcd8GaxX@ z=7fHVO;RNZGfh!Q`v=i7pM*jP8SN{M!Mo)<mF_u}dO`hBJD6<~Sj7cXv2Tu@Fb{SQ zP1hb2cr9Po%7U7<Gj11}#z+YL0;&^kd-RAnA!hAF#<)rL3vuCKW;UmV9+9IpnVeYu zUYh_13aU9Pfi({|1Dhi9wz|BDHyqbauLl_;69PPD+dg7kK5T2y9ApD2I1o?JWGSf> zx;`m$-Mw7RTA0MvjRRdoLi(F=Pgp?)%Mf+1KAL5>m}IkWqs$W*<>FWJdrcJ(baBxR zmy)*{s4|OFPK;EDZwNN6rN=CatP88f>(KEko*LhU5nRZayRbw-3Re$8)blJsInv>& z;pKdk=Bj<i9C&+S+B3w*X9S3C5qmQGAlEPx^R#0|8GWh|`0(pZPc)UerU^5>T?QBK zzn}D$?e=t_G7%m=SzlK#wL#<ywP8Wb-ynzb+{bs!I{%%k6|z%L&Bio95yXB*Uafn& z{-F>x5f0XOy9oDkD!GSx$a5|6Tu&f2CahdXsY%J+MvEuULJ7N#J>Y5p&^(P~2WyE) zcdT4~M&)*%>E@H+o@j|FZ;2`KAapI9c@Yi&ULdhQ855g9z-$LD>geGc$7lIjJh!?| zX=(LZ*QAgi`!E1fj39G;Pk{0V$J@B!F}?L{TSzO7r8U*}P?v(a1>;rOVh^)MyZR)M zGhRFTNeJK^ijsb_v11EN5k@u43PX&u*#C&8mGMmj-M-f1+~qkv?gMn0(r_5RVc2Kk z;&d7BGC%tyVIc%^$Vg|XwTUYKr%x(KH*rln%gQxOs*8j#_Qs16UzdZfogRrBnm`*b zO@up79h{h#xl(1w>Xd;2ZJT^r*7tQkc<?!to0)D*Cia$IbRClkgzCAvj!4Ym-#kd* zc!YY?ixDBBw#ovI7WboWm~1gTnDdX>V|yHRY`G{F-W>N=0>1YGms04ehZ;JNKx84k z`Jm_W-|85WEubCgAC1a;zDw6qa56hC0vyl4d0&tW+}z;l8(+KM)$z@otVK=YJyBPp z@ElpQBn1M=2q2!2S)7EXYo3#mgYl;{?pwnb;l;L)+FJWS;8-A-|BHN1D({h%u5z%1 zR{If&6L|{ah>kuMscwMG+*)k%rDbVWql1wsW!(xs`_OuOtrzzgHnd<g4||iF<%;Da z(i)=iw(btY+cmmz#kW@{!|$kV-tN$ws6<{?9di?qcH$m{)U3y7u*Gw)ZxgKLVfll6 zdYBS3JNaiF<|%hsQNaYh4*Vi$ZwZ9^gW)3y8rguR(u=S_Ghhq~Z+v2n6V1XH1S!y= zJp#qiDj`I@B-Npb^BM>G29{T`>uI17q4PY3^<okaWq(#q)lpUQz>6_vIVBn`t7B!n zQiyrDWaBR<G)MjxE&bm<A_nOTTDeJm1(wH&6TiMfb*k%@1OCfU3*48Au6H?2EI|@4 z1HIMsj|7M@`*n?ptupZ2sAO$4{Vg7d>O5Nu{g)ee8JM4)k`UaOJq}hef(s668NpNs zl-<}NHsAudY6b?h3Pzejp8q<7;GInt{<NN6T*_<hR(*(h&DaRjo-)%#HqSxh%&F@u z4jt){9)CGjLrv*vCW|OYS6q}1NF4k&#G_j{C+4eu_VstpXR`nuK(Tnj9L0eP?4V)u zYwMmM;3V*{I`m@4CX`DeJ#__Vna(+LKy!kCwzg#GX1L--qqcGKFgK!$k8#Sj8G}~K zCk1ojY+CR~+@F2WhfdeJlk1m0>Cv2~VL_f*tXuSF`xDHbv!)6Q-*ujNUCO(^MTd*0 zgI#=Q)F|6V$^y}&d#M>!#ATp)N7~4&3&mIPkV2qeoA3;quUS|NIC9=XdGSOYpgPn} zhEF{d)=sX}-J$*M^RbQZLog<hzLu(yVI4T9Gl?@E4Wjz$-q9J)r30U8qM)zAo1j}l z1DtkxVSm?m<y*@If+VqwsuHxTrX@*^jpZ%@fPURuN~-6}9p1~f5?o4Ps=u6RJKC+u zo#of!9@wwWxO*6(lXg{FO{O=t<IkOk@YNuT{Gbx)B_#h&hgV@r`fo(ep)1|EQWnFA zI0HRS@9%zp#f|i%3mg{bX*yn`2oAnc6O1|~#0Znr&JZ^ev031)vdZYwy;%vO46f5K z-bfc@wolQ+efD@C;nwV5n(Hs>=xcB6Sl@tKs8L5~8bvVNd6?ftP$q*tv{&C}XXDbp zSm@THLocy850J}w(R5IDA9r1TKVm9P@dezV*Bjom(fy9+A*#VBQh6;2_SjsnhJ)Iy zR+hNztcn!91=+4z+E~UNkJYRPFBtz&)W@FsU|}S=*K-0*5Mo%!>Rz~p6VZd(9^>W9 zK+TT}f4Z#~tG)#=%JT?65P2S`h^CjGf0dIWhq6YRoBujksXe0iZ!*=VGa_B$t?z!J z;E?R6uniw!%U5IcasA^P*gxl^)b2siL#r=HE)(KYva?FeGchS~n)J=Xms&yUb|f>p zElTP{Tg{JYM+Rsv53;;)D^)uTa&5<|f1ijkOVf6Bb%{|p&<FN$+)!^C>W$17L{kM) zi;6HY-cNW=7g?#dhdF;R=NUwrw^F5ywqtV!l4s0nO1jVQk;xhe^Lb2#a+1<Hg2z=Z zT8*0a*3=M>6n!z3uSVmrAU!=avHq(^!6B6q=oIC4CZDElZVVnBXGXDnj)US-Q&pd` zWv#OR>fSsu$d&)6(y82-_9o_rBjjB~-L^6B8jh;S2qwqx_jfxj4TnF&Di$lo{y|qW zDr<q3m^{AClu!LIIVCyIt*h_CB18nx8$(m#m$x~{=?jtU-oY^9E-lHP`g9&tEdXEf z9Q2Q`h8~1Ad%+S>8Z=50*MAWBan{ybs|X07^WVu0>7&R4V?9prdz})2j`e?yQZnHY z7ST+HLWW@Oo?C6t86>K{<~5@4fo0(^3Uhk1vLlP_SaY(EWeo*e8KB{o5J$gNlGfs^ zQ&&x<wc%m!6RjiOw>i$BRzYX<TsVETT1-Kt9Lds7u=5zmp6GUVW1b{<>W{3(vzG^( zu*gA*40mI*39Di-R||z1M(r<?W#K-QiKmFE$hRgYP`Ea@eQ<zSl~QK_xbb^UN7gk4 zA<=NXuiW@rDkIYH4CrT&8klQPzua<UuVlB$Ne=RIn6msq;8PEsUe|R;nVAp;dJRJ_ zivuCw*ugBN2$`jFvXa<BH8i-{M7sAur|zsnd3%VlSZ<x%w1=0BxJ@R{_z?6%Fs@0> z)8hE9x|j8Ow+L0nglieZ2I=*p1zqcM7Gi-9nMI;APPp+(MQ##ni)$$rx|{MCrN7a4 zNW*vQqPt=g{NL~7DbLLt`l7<1Q+`yl?-FOYAFo~_$MW+l$lZQGRJ;XRLvs+JwTSYv zb92nFFPOLC`un+0Js;7G4Fy=(+xFX=Lt5ySslO;`!YHfbN(F|>{s5F6s@8J=_ai%b zMr}?AEkTmDO?Lz72zhf$Oc_M$0LY7bL2V;6G7J4w?vcF<9cdu^naPwnv|<O|L`jt# zd!-MSSBe7Zwc5E3=EC=9Pb9!@_NJ*>92E+DGFQd_m?~m;-zIe6@e0|>QM{Lda-qOi zB}UvY_U!p$`&AiYtheU|?29mx4!OC@1~H}~XH13WT*jkyO+clDpD^Og^WgTBYBB-^ zpwelM)v}#_ccZI~{(OMY!c&S{TDDvB#)BVb((vgWBIy6vv?b+f*hADT=+HLUh317< zM$naZd@6>|gIHbeP%#VzrYzKD-tG3sX@T~H_{?BT;=l%Ob$}j`@?gw7MW38eQU?2_ z>qDR~h2wD9_z+x1huPceEOs(kg_SGu7P3qi@+gvDm>VC%fmh;+ka$|#4zEXs#|maJ zPmp+{Qy!tgSZ#BuAG#1`+xUZFZh8&iH2AOH@r3m@83g;ZqRM9*z^yY@N#o@CP6|Bk zTkQhyEi?p*I|R&6Vcsi+c(~GJa19j4dGfP+X2e6#r4j%=K*GQBt&1KtKPt<Dmbp=p z`@7x392YN-{UyzF->1W{k9(RBIFalV{6CB$Sk8?j)FQx7;o)_pDRBza$-$hrGp{s$ zlyTP_mC;K=9|rOGoPy09K5|Grli>I!dTR*Dx>~h4VHx>D?C~xWDso*CZ1%(~(l^q& z$=RUFL_-Yp{6u+9S|kbM{d~hoBAd9zz@T&VLUD_$yLXSx^s6F59MQcZ+T=24mt3HS z<ki+c5nLX~tfL^4Q=FtchyzZYxfg7pYuxPxM<yB5-FS%21;)qg>{TwaK)c6Zeu`)& z;fJ&^M3)|!FjP4~_<4EPkN<X(xYd3_q1CtVfQUx0F|$xiHSYmb_X%p^cpag=Cf%5q z|3S(0C;r6-CUegRHQ^L)+{Dn4rv^>r2urmW?}LU6NanocFTT=`bb`GgA}5!BZ+U@4 zWY@8UA#5e6-C4QRN!$xON4Fie>K_nUss?D!Prnbzt;5HfuO84<OXv(ImUdBjEORnU zf&9(vS7;)UX+Xk`_4pm{n)FusopWs;#?nErQPVY(ve(w0!<244ZN^)IeS$<M34t_{ zPu0<@3KVida*#w)VBpS#CjxI*qOrBXi6Z;zD(dr8R}svY;Eelrm~*e8Lf$17+E?>3 z^l2!Gnuzj{o4Z*bQ<+X*%R7zz$w9!7dPINNq>P|<%Dvvxkva;Ay7X0@uTL`-s_&3M z33(admxeqh-ZT=`zF`@&K}pSy4aG3@+}ins^@9tn0|aZ_jZHcCH;mTZ#?;WFCXEGp zo?`b3V)V6tgOI`0lvFbQK{1;?g9e@%?>Q{s!4Ec=Cd<$him843qK@>Df3(SSu2mI# zOqrH!_he`TJmoh^faNdcF)c7+W2ierq8cj)9++yZtVk2YgEoQHZt@R|8YFQu*6xbn zrn`MISgGnmTIXBzTs;*e46sW_a5byHf!sJil1b_AR@#gV^w~!4rJ)6CarYjHX>TWA zW)4uFAh(ZExGjlsM_CPTDw_@oUzQ5N(-;9}fEDfaOGUN(%~6j%M9NP5IGe#@9xocK zz#MP7!n5y7w67tl41XR|%F$l5(|=WaA?19-3(1ZH3Ki}|OONBt*>obe^CmdJ*`;=% zYFCcE#xF5BJqld;{Wq$w|92kRm+A=e8i*EmLRIX<yUO~7Lg$BagtM~NQ5qG$Ejf;X z1<cxn*b07kZ3ZcFG@qy07M5#zh0PA9$nj5ISwx&GKBcBPyzg$7BQVZDa#8@bJQ90j zOvRTKt}8%Gkq!Zr8y2!SbRWMlsg?E~Z|19Yaf>T29}g;UBp7Q^$&t(|8B@wj>&Sz7 zk+^n<WksBj>c+M)x9bzSIbqrfQlj?#Zm0PXR7yBKL#bzQch7W-W@}hn^5M4G&JVlX zCc33x4BYQRZ^}u$QU_Y!zVByhM!6T7J4;Kdj0$eY%+uKwiI92x>2SObCV4UIAzovP z*iKh@P@5Y;&oY`sQ$pyrD?3f$B-S~%_IT_L@GXkjN}~rYSH|yv@x&N*{$52^Vx!)T zaha$q^pK@+dFl1C+bVGg+875%=n$tS^YdvWbl7>2n{}py+h-+*?g|<OV%riX1Y&tP zyb_cu9O~1gYMQva8Lc;oLgz;~(Yg<|y1P38^v<<95$7+53|<eV*9DLXM^do??ZtbN zCt58rIbtv9SbxZ+$@c`y9ybciPJ#awfUBLO6qYKLK4g=STeiop%yE!BiQ|3;(%j;G zSA7QI5cBJ}4Y79CnBTw$yu48L6__*yZB_WQfSss*uTh0_zqojJCrTe&7GL=iLw)C3 zy@?4Y)~yo)kS3|q5zOgAzG0~p1T6hhq1+oj3RW1esmwIx6(*n;tYc(88p4(0m)-ND z<ShDerK17FHm?x$m3x?(X(IrHW5%-3L5`B1*yi)(HBgDH-i7MrB-{eAN(}Nonft37 z>1j~(MjGI<r;J2h{j3!`isLce+43!~H+JvH$Sj7n)vVL~DCwma>KLNNGD8CbYIrP2 zZEPP3>pp%D1LTs~>jHozV9SGj7^T8$Gu<!C!<SJ5Rx22lY&^sdr-nA~VXHpD%SGYL zUClPGfCR*N?FgJjXAd&RSXYU-;9SwU@4^n<iQn2y&8jL-C~Y%LHHG4B^Kd{w1W<OO zE>NU35iBPy)t9Z5rRRya7NA~3h=l|DzBAZ(cnJr&JJ`EtU{3xyB}!V#1@03$$^g{> zLioztX2?hvmap9Yx$qyZYjpMq2wKav$)o7syQ!eG#m<5lG}-XW`q+0;h2qsd2;|>E ztBo}5e4k#25OnfEXdC=G<f>Yv)h8#_Pr$TX&~`zwblB`HT{TK0n#RDVzH6za^|M!d zOrGBz8!mtj{_8N@3)bCuKH>RQcd;S$%zOxsZE{4R$e=FQ^9%26UD_WjUkgM}7cSD& zSRd4xQE&or{G2zfHBh*;z-_6ZeQk1>)UD$E-SUCI9jrzi)eQPN1yElzBt;UuQ6y!; z9?juWXo%*WZix^OoDvsnB^jE>B&tzs)>G(adjhZhUR1)l!AGR(R!{KTq*0{Y+bLET zht1v!IY+4HbFT0IxjF;99bOZ$^RMcZqyieN#i+_NjnLG!{w4$U9#;j3$?F}%%8ox) zLMbO(QK@^omtw`q({^e?CylHQH69PD_IVri*~T~4H1%%^U`BHNH9Wnp{jL>fXPt>C zH7#P5LNGc3p$WH+a~Erw&w%i=g&!C)TQru>%+ueMI8Ps^0SE&_DoxK{4}vIB5kdvt zBxU*zf>;KT*K0;Y+Z`NpgDYS~vt-)*Erh4LH=HVk`3Gpb9TZ?p9q9ecq3d!QZclvR zT>qw6eyWf7xEJv01a?Ur(tQev$D?D5H^H1tv#GdpgNoJskt;vNsf~QCe@u~=Z?;B0 zAikEzlN3i~b6`;JDyZt;cw<~)8ik8(`IO&zZ9kJR-z;cWhFL}Xo#TCLZD*kT-e<+6 z^ZPQY(>4zY%NU>xW<C~u)fYb^u28HA&P1{WN5i!0hEY*fa<$i5O|_&;l044an%09N zEP#(|&3x#^uvfzBPeR&gSx4&`>>iD8w4IY)k3~Kj(W5j&`EH1Ey!H$_-y681ClEsY zoHA1jgtAklCMD0$OQ7`b0FWCf`u@7Zrsd_qBLzT__A}&Zr>8J=t$h0IY=K~veRyU_ zP+}Az-D^%_K!CRSUHtV)$Mput0`@`2oH`4n=8w<~%)7~x&+w}7L)wi{A*O;b7;LiE zxuAEYhRg(m@aeYMjalyC`KtGhDz#u;%{SqZn-|wF?7Qv{#Eex6=xGj2<}ex_qvX|W z!SiCD>-0(*)6Fb!Nr#;YP*5{pQoOi5y-~#i@r_`ayF9ov_H7d`WCDB2D7WIu*(0*C zuDw^T{Xl#8F&ZLaz0F}Zi*<u9q5+oD@ouGA?+ssPgxtNP(O3zyFgOA#Uv=2KSSt0I zjgzm6&w%&t!DP~s6MG5aa?bYvK3;I?B=Guw{CT|WFRN~BRgD%n6fTjyG0wCFz>z@5 z<mRa`x?3~Jd2+_zD0}Vr`Wr035YNF1(h^5NSt4uc`0kD_dtJ|yRx?)e-4@DgP4*PW zy~W_6vY-m+hjOo2n+4TcCn1iEh2Vr-D}};u6vYH1Xl=?Z)o|Pmeva9}H1@R|hZ)mG zxVh=g-<Rat9kF<yQN+fM-L<~4?5-Tn_`}=-*4=f{EeiG5jpz%^UMhq#dlo}#b0?h` zvVu>-iMO|$unDdkjb=_3o>?_ghGk+~+WOc>lZQ^#nFa@Vq4=&E<2sR`zi#r3h2@>x zT<diH)X<a2<RLGqnQz(<ln}Liwp-wg&Ar3DCq1nki6Yw^wfhPs18LSFT>eaGLCHM_ z%Fpf8OV$(ODkaT(a>frXgQiEp=1Ssc;q$L!T!@Pc9HN0odw{P5F05j2a(=fVRdF%) z6r!olSl~e8qYNsNeIp_LL;RZ193@^^JhqO&nukH}IV%;M;$^^bfaycsIV;t9>_;PL zF^mR4W6T3dB-r8D#{y9B*G%kM284LXq_h;WN0M!T`r<{ZVimoCX!fyH1WdS5&^ss} zo-X_WBggfU>`>FTFsK-#fv`%@EoOQ3C@*CM_h<`Gp|!=E*8E3>sFO?C(E!4s-ho!? zFGtnT4d7DJUze&#mv<`gKnKc`1b02?_wT2YbCRw({ESt*mvL<=8F&mWj||@WrY|r) z@A!4cjk%cFw4;dCn71q)nk<?h?LhPqgNz&X|LTc^?Uysr5dUV$#}}&EE7WntgaS!T zt6t%SQ>KZ-(OTG)4~wHevFsvFHXfE56|d@Bd?6}I&SpSxI3D;iHelK*L|!qU#m6Ea zghk#7{E>LnQ+Ml54Jzzq8vqD6Z?50L=H9EKXGd*+AUNco<MVD*!mUQ3t{KgAEQy~~ zl&?Xtp!Dd%_aq#e{nNhMs^sH~R2}-8ATtb0E=S!WsVqN(0*;1_GXF%1+_Awczdn4e zO$h>Z&<R?!2$s*K7uQ|6A^H~J-~H=rly5qr2w^RV<JY@<p=D6L2yGxk21Nrwjd!PH zwg&0m&|y0V+QttL_NP0BbYOG@LreOld9<swJzlmPqTu#6-bfQbGDy!deWzYNzoSnw zq7oxfXW#?h!4S%+s4`sFF;{n|Y#=8TwQ(PZfUDS0ve>gW7UAqXEpow%SM{23z22G+ z#vfIu&S6Kf@(b<T41sK)F_e3Tetw1|c$VetHNLg22bX5c7OK+rS~-=3YsbnslN}%? zTLA4Z)7bDq@-o;x)Q##^yFBF~qkT5j!T7TLVtu90%}(FQ2NK^&YOm}iYWU!u3*AJ{ z8)7othA8k~4FlqAZSzuuWaK_?92c5>ks}xCp61=QO02xsp6^mBOJtL6fh4n34IWRh z%Tnk+77&v;*{LG$;0zYiy`e>&n}b-*FBaG@szJNVXxrZCn;&gxr#EtUa_LUktC7If z(H3tA-+~Rm8)#tCxIU9A+z97<ccEs)X*Msb^<GnYp8$CEyXy?!Ai%9Ti&I0E87+Vp zjA2Q40ZACP*g0s&MeCt-ojCvDXl8oh{TrSu$53r;_?T85*VHaNHVhyN4TK(*zJh(u z3Y_-XW|6Dv->DJNOvTCHGnvHTNBBbfq*4H{Qg}j-j<Xy@Jt4-as|wbk&24&_%ng?- zd4|=)7eS(nd7Yx5?>8Ij@K`zW$?f*S#e9;lW`LAfAP>Ovl+Hj0);UV6!+StVsZ#q& z+p_tvD%Q`Sw3eu(<*|({6>(QK_bwq~eVb}L8hs_P&U`-^=YT^^1{=0wn0c?@YoDQT zkV??BWhlHbLD#>xR#hC28qq7v^oTnd*h=E<q{TX^x>CKf)qoAyCET1Qgu&4)d9&c0 zt|dCf(bF@|zbMPR*lQBcORYs1*og@Gf+#Wp;7zq}5BnpocKS2N$!16dlej0x6X4hZ zQU=Hk*kOP+F2ZicRqw2u?CZQ8O$x2CRa$=;Gr_Tu3B9|2lxaE*B0f<>$zhJiPd<*u zGTatgEGsoutRpY7)hS|z&Ei<2?=c<;Th6hw^X%Z5vKegt6?ohnVb&wv6fFC(eW=FM z*T7P5k=1nzTlm|$kWCtrxVnFjcpw_(8A2;ZpcX6V6~cbZKd1S#Y>K_ztQg4pd%K#c z?HEfbj#VmTM*Ybok52znHU1OwKv<gm?2Y(oA~yJ%Nio41jK5aQevt7F5>j?upJ8`O z)B`M@eb-gA`|F=+vF@njt-HIi3&gO-_5-j+NtEC&=F8Gcn$(%gwZC*Hc6o=t)E3Ge z0gIl@57}IF(!&%@`}gN^Tl_XC=<M9p6YLc7l`%B$L^*&E<cqW2&~!D(^v=YE6{b_D zUT1ICyPAos-7J@D>tJJ6$64J2yB^ic5$(BazAgSYn6EVN%*Jp2b@fVU_$%uqLuePr z5~11LZ~Uo0-r6AlxiK^Nj+XxVIPuS$RB>pm!Y__c*hOvQ07U|nEEV42EZP$GUlYQ? zk$KeQbN41CLzac7gw`6#So`!wB-B<E^w25EmjPjo0!-k|P6?3656<`iJVqdu-ASVy z+d~3x^=^V8UBZ}{zG@m!^}4;|nbt8aD4wIH{FE#{*ecp2azUSiRgAA#1s7VQSG4%# z9Dr@&{~Yh3sQ_7Nj_3s}<sTofy{LqOu+}uqtUeOxX&QA7X2cbnct`K8n$jDQ%VnG4 zwy6%IErFsoaeWD?#H@7muV+2jI&r^C#(l%5AkmnxEk!idz^NIkx)1X%S}IZiXMI%f z<!Nt@w%E(XDy!u0Py-VLu&{4Rv)pAt0>As7nee!yiESUk>tP`1=$7~o_~6L}@*Rhv zC751Bot)%DYK5fhR3_EJ!Q_<dXgZk|M>(9C^(Ax*)RLSZZE?On#$eb9A)icS1$^rq zdx}Q}v6l>N|Dvqnq$XtT%tv(aYZrW!Z9WjVD=U+(v02!;5}gnrdN)#Dc(dHFV(H-z z;6=kn1LJB_yUk#CU!WF+S-Oz<&49f7>c^0zrfoR0#8MBC1JPgw8k9G0Quv(DEC_qL zmXR&2z!8^|Z==f_xCPjo?#_V!%qoXx+RE<~tejK%OX-^0O1D1-a!PlC0%Hy8+ZYq8 z*5z;-(`@yv_nbC*Kd*TaWF()s;*d%wQ|+B!1MB%tQai+-QQ6kN*aletX;y`hAt8GM z_p1$po25~2w=t#ho3MLSt81t>ltm_-(f%$eT(wi$ANndyzOV@S$*=m{t!7fZErqvA zd1a`m8Hb%Fh3G9@4LbuGgg5Mu$Q;&q=Oyf9^1-rCHXzvd>E&8tH;6m*=xxSfm9_bn zJ6i~lIa)vk7y?>$M!V=Xj`nRji+Dt>St$Q(1!^VOyP^bO(VB3*ICUO-EHfQ1@A^E} zhEaDrbobMtW6h*Q!PtNB38H$fLA80H`?m^yr?azPQ&{k0_@*{0i}4FD(iB@+rL(6K z;%P<OaO?Y651yjqhR=R&BD76&6cUW9mvUB3O11O#MrTxP4*<K1^*|&Ht@5czA$y_6 zLLF22hi|t}$Hykz&Xndw;OOz7R7)~IAa9e-#Y?FkH|NoOCQ@}+3f1+FCNF=#)FKG~ zZqPrQ)TUuS1OCfq1W)Z?Mwtcu&BVD8P^>AKL3-P{pQ+k-Jvc6}vVNE#fcg&E0a_`b zm<h$!Z#X_=dt&5Ni;FtPow*VaNmb(FcM^#(Yf`JgllEX*lQ%|Ml;m+ah$Z!PKQHV9 zOt>Xr<3lWdfd-IgCksg@w16d)ige$W`Fk(FcF^*NOxwWo1a063Pi3-@V?6qL%1+R) z?5{DJ2MO^F*f1^v-t*!v0$3|(p#LwnhY775TyU3KNiwipT=9;syfOMgEwcYKrVy-J zha9$Joh1BVqc22JG6cHJ1<{bX{iWeD<F{v~FIqiJh*(^*x$VTcny|1^Yv`y8yZPk( zFAcaud@Tr8LJfG$FuXx8ShiDJI5s@=I`c?03F}>9bW>$)3hq}XXNx#Ns#<Ji{XCEb zXG_;{@{J@zF};U;h=we}x!x-*4gcug&>xoDSfj3pxKR=*p?@&i6vqCxEXpu?i{#P5 zumaQxLN5+I+tp8rlMM28F)jbUBu@$jmAZH2IXNsA(1iDmeN1ahii!{GFkO=zK-hnO zV7kCJDxvBoQirr<AXcw8DOMlTQzSgUaA}f^Z>hL>9RhN)e^vYXb}L+@f<Uc!O<K!( z*pWV+Gf;c;*_<6O3dKi-_C7I+xw)e?qBJSOEJxuEvbCs@Et48nzr1RrN7*I*@Y_J| z#(Ckn0glF`7L|8nA(L)y7fpRko@JOraJ8hOs87ZGzj0)DzuDs+`SZJ}NAGv9N*Qad zN-oE7$FUk!?4Uz0SC^P;774Xx#j*8=Rm#C(e_!o9+)?q=q9^F%`jQgc><AKtovzVy zTC+%SZm{k~x$>1UGMt_<kV3;pKmk<DVF8SXXAE45QqhGB)xRaIa|(j8laXt)7ptT4 zzU3e;mlY7(%UzinRnEEObz5l!DW(b*WvgTo>5r6ifyO3IPz*WLdnD0t__NzCVvn6N zy`IA|?cyA=JdZc+hd$|y?`-;9MKQ^2$&ZdQAWT@`w=mZDN;gze=Q;Vq=dfU@Vu?A# z`r|7$&D=$G**8c5`xL!a5f#FWO?ymNl%ps@cq*Ur&3+-u;H_e=NvYs&V*Q*0C;r7V z0QsgWP8dN#yKXCG8mqZvV;HC7qA$GO-CiMJLUkcisuk;2(O4qHtbdwJFKQkP(ldJ% z0|i(FCva5Q<+Tr(JfaY6pwlnHHGzdlOg`<OGYX?`uQi@5e6M3`ZB%GW1X6V~E|xbc zqU^;f)GIo@;AzO)1noyvm4gSzRXQqsK?<>{oCZe)+0fDYTkwnng0h9Yn3bLTs%I!s zD!}Os_N4Y|(uP*E_U|)jMV7Ht{W_?$VZ^J5_OrBkp`BjB?R#D*)UixL4`boi(w^E5 zk+NVT2&NCE;0O@Yl>p(-+D4?pdmsQ%fEED)^{g6&pw8U)3y6OV4G>7y^IIdN$F+Me zsW9QDm6Ii0p>AwwY)--%*O+Vs!0O?a+8W}m;sCFfH7m}ix?(T^!QuXXS2fVJ$T)qp zIyb!S5Xa4XKjU%ZasMsc)!Z>$ao%dqdW}F+O4rq-%U$g*>j-<V|1&>`h7htkn|3#x zb^c3-d;&O&Wyyh=($+)2FY$FeDI#kVc0+JSxJ0Q63}=<bFgmW@%|2VHH}3G*+r<V- z%s^{ysl&9*fxq{lwM58*tT~L9+Jy=MsiPN!#FD@ML|sqm$P6RBg+T=lX1c}Q228&) ztKZ4B-bcn`DD=`6%&;xyIg|H_j}?k_wBW-amQM>E0H2Fb7AeqrC`A58*xhnnIm<7` zr7Cpy_FCFFKJl>q>`=!faM^ZfGJ7Ja_oh5sb3WYuz{*HJ59;e>a>4^=nsP)cYE6sq zR$<$Clxa>*kz@;R7}nN6cQ{<UW*gIi9E$4hPs>gg`~MPQk~zqca?ZVQTlm!>0=Rf2 zc|5H3s-%f5E-X5O&lw)4Z%x|Uw&9rHJ`e@hRl1o-?soPIh@+~d{Z=<9wm8fAvy43$ z8Iq)qv}>?%?!b(ys9l0s?MvS%|5*eZBw|l&kh)#uP!}KRpgY0xOi`ABKD~3NRGv_V z!)J~i52C7H+;l%RGULsZ=30a-W}R*cM-m%c93ff0^c10IwN|PoxR9V0x0Kp|oou;u z$c(aE(Rl@=sMQx_K#Gb-gJyuH6~cIHf+OrZwNVJO#B{TXnXu7Rrj54`DBm5hm_}21 z0SdI%^TgJq{yC3=k~?C>Wa(eB5aHuZSD{5(k_>dALM!Fh#k_`$Cco>2U2WSfr_8dR zb<|+L_XP2z4QslFq!&F&LE{&f4>&vIqy0T9fa>jznTF3|7Q+j3(gQ)y0Dh!N&B&rl zdd=wkcE{X&dEF}yO6tK;(hQA>CyBmCpeiFdsHrr370Uy9CEoB$0nuyHmewV|0#Ixj zNRK023bood!rPlub<fO`9CoCOo2Ut%+Dl*=hP7XKm<mb0t+xz{ysaD8yc=eY3d^?# zR=|oW^dq`!ShZ+GXWvZu36?u4DL)6$mHLI*48g0C6{gcuejqAMk?RviCu|Audz|xq z2g!YmssE2IB4p1Oe0zT<%E`;6ll~QJ=FOULi${DuAxtfO42IK4omx#Lxqabhn%M&{ zr(;w7IF*;N_qXF6guI`-ImkT${M|IpabpRZDBTk%gkXs4=2+sC2oNsx&ZIdx*E2nD zsh+9OY+4^sfdE;Dcwe?LMrWP=ik@nahhGD6W<LBd7uf0%pqk&vMr$<zmSnfxu08DW zKRgl3M1>kKRC|M1iXZxk%61BxwZA=On_8Hg%-GK0fVoC#IsAW330vHV-p3Xo#fN$) zIGYP$Fb0laK^4OeSy0+Gw_;k%#*(kjJ`zb!n>!$n7mgv734dtX;Y!C~oY1<f)2`6+ zS7f%ClDxaa|0GT;#+&|p#5~-E_#U7m_XuF-Lr2|!^ZO%Q@$t!P>##OV!VoC_*z7;P za(<}jMy67ZhV&MuH<e9Hq`Z;&JkS^ICEsaZgQ(jsO%$%vBveiBup&<5elMv)-{t0w zYd_>g^-XkyLnaXH*o0>w-!JgiA>264G3q_Z5AMAxrrQ1=L!SoOOXUyn#P<O3TLq@J zIh{*6^kTz)(Of<1i39Y`kDkydpQWdmiU^baCGK))ZlS2v!cCQFWWA)}Da5+3h)QDK zsk>*Ze1cGr2Ghgey1x*ev&X|~7y*#d+qbUNXawlaEn9&^LU|Yv^Cn}FXW3PbdJRtr zZw!;J|D>piYsO0icBw(3KRwbHBXo4AcZFUnImBS^B&$+Nq30-)R=W8KN|iUf<+?VK zVMS;oTR+UeqUl=j!WI&1Z=!L(K@qP$4O~x75JImyI|atbS4St^>tBQ~_ECH@?byG} z{JR!<?La$<w)Y~`&Qxj`I>AyH^UeDUybNam#g#(2%p#i(m}&HPLERYiYkn=n+1gb2 zXEvZ9<&!IJhO2_(^iuYRClJlzyqVSv9ecsYp-1mP7plDf@!H`-0ld0BM7%)opNAoh zS#-DP$RLJm=4;hS^XD1{i`TS~Km}RJ`uGW5#o~QvAF%e@L!w_3aJh2@0+<VY!w66V zC*&c2l&Kj<FES!t)U~Yzc_+^PzVCb6L0;2A_Kw9MAiM#Tw##tCp<Jl%=l-BXDRV{! z&T)~<YKM*As%#XOIa-LwyQD8SOZ!xJ3d|FXt-;{JeX*48wc&)zC`GULF(O{^YiVnv zFQ`YUS1G4-q)YQa8u%KTbM&7MRaOEG4&}Ch5WNGlR8>A?K?reC0A?_Z|LR>DX&By) zXJEv~@lj=<BRXKwAHf;WkS<9@N@AJgHd4R*EPWy=OmLWmjJ&qCDBP^mbN>vK+rR^9 z%;;FdpORZDCtNymC!yb@w{q@GfX6W4WIpkiJgY9i)>vNO!E=atDJ%!Tk3pOq!dDc& zXDv}p;grrt4o!AG;XvBGe#IY;`MmqC>Ac#M<mEYwEjXq}0}+crwas-?pV{aHSwq7Y z;Z9?yY+{OSF2|xL{4mMeSh9zOXh={T$5IR8Fjn!^Ox~`}`67WNp?XW#9H!<~CKRIf z3OCO7_6bCnY_S<FNCt3J|F%WIfR<?)PfyJ$ah@A70qlO^$Fz_AU9p#YX^)*`=h;uy zfrE1w@(%>N{_`bV1Uq~+9x226gwe^B!-RQOSyjKzNemFNh**868(x(QO8~9_wotqd zlP9u4C6L0U2V=V5p|Asw^-1v+9uqP<%l;IlSbelwYwxEgX_jVF7qXUb*cVy7tpKN3 zDFym|UXf8}AmtjwLe@Ev)1}3A6)t^o0tW?@!0#nsgvA9Um5X=rfNvu6?+WF(`Dv>$ z^hXT%eCpTLM+E5wG}3%3TQJtuhHlv*+KrAPIuO$`eD=P*HD<MKc$linZ-+_Q@9Q5S zzTi6U3C4fl5}x_&vSLpzS<$4w8i*DVv?8mE>bn7X0xI^;r_@i$o4YU73C4T_<AxfG zHsE#j@BaP64kn6P_Kdcg)DT+Qb%t+8-;<1gMedqBDa?Lpg3B4f_FJD&?4mcDPyrVN z+CYZvZpscbi2pmI(7{b$a1gd?rUIdyJzty;G$y)xIga3ua;LP;AVtp&r2@zj_cu5T z>;lTkOTj_n4~ZJMoa$|~(dF+}G(4U)ZL#H_RyR!d@uB}+)td2U!=(tafezb0BRbsm zXBpt*Yt<hsacbE?xivVHu*N>fs0x#uu533bMdE&_tL~SI(R*-TzuoU~6L>BCS0MES zM^YPabK-rX(wi%b(F&s3+*x|p#afDFW<x))=8cmG^tdak2BN?vN2FVAHnogcswtr| zTr}eI@Kl^E4x@AIDGO1g4x9g{iH$40X55;AQGPQRxx!8vEG?}-P6OtJ48!T(c2Iji zXOpA)CP8}I54m^D7i)ap?P8n7a$PEswR$9t)FdKg^+@r-84l2q-dOgPoGZuP%<kSY zjc=w88<5TyCvTte^T|nax?cG`4q+iuczxxZ$w{E3`z8a`k-7@?p=|o$uDK3f=9fLc z_0jR#DWQZ0QL#MWKishyL-Fe2VwiP8fTm8v|BGZa%Fl&AXpW?za&f<UODkZ%t^lzK zxn9N>-QWDgWVK`rvinOIfz2Vbq!R~$9%zAUaPOGg-rGCkDR^A(T~fJ$+l;65*0m19 zm+kYy;ex(@N9TY#^KtMivmN6KyK-^ph=3Kb{^hV?_B7Fo#M8Yr0r~4h?UHmO|02vQ zszWa111ai+E^TU#^5|fGN5ul3npQ<(^JmhPIuU$f7rLXp5v#*$RT$kvC8p*VSmfZE z1)CT_#I~GA=0t*r#z&}%%#W-L<)6m+SzN{9#6JRaMfZJRwj`E)Iv@aUjpbD*=O7tg z<T4kd(%TVrxRj;Kys0uF9|kxmj)5s80GVCbrN^DxQUCMlQ1<uj@sySoVgJHP7Im6Y z>m~ZChf&q9T-2Q}W>P6F?&H^|iNpiQ7DiHk(C;{TLgWf}wRt}7r()O5<muwgH72V` z#^*jm<8=2;J@)n2LjbM`zm+|R*Z7`Y67^r~Z+&{3kCz=>4`MM|&<a8gqs^6;&eg+? z0RlvO0P_Z%xPeLV<PyLw(O0*ujosnsBZQK~>ETcfB@Cw}pt`?FJTm_>+SsRwTaR?< zgF6Lh`8T7sF|R`iRhM|eIJaQ}1R821jxBE4=^&s|myw><ZEO~&N#0?yB(Ot<fA7Y) zNsHDP#01!zVGy)DJi|G@lqeB9PG>#wqTb(zj0pQ))<QTQ`0vzgzGC{Q-RSTs8?>5T zv0v@tQDJR>ib&e6wG}0GOKuan(QDs?t4_XNP!5Xhgd0f~LgRCP*nM#0ei%pzc+6n% z4Q&zCemk=yWNnH|T8;$C(M9>zHt}L&7HMQ!tnhcj8UJY76Fxt0jw88!ileHOm&oHH zRx;=xg(|1L!aQ*786m769=T2qPT-WH+=iZ-mY*^2A8gfYysnR~67*?%Ed=0i-e8hN z5Y(k2Hv<N~d#+m4RToRi$knpM-)0<x$GtCH+vL8$lKB&;x*M}wdPA79ep9jTrCs@u zW`D*1Fv%rY=C49+n`K!3E_;J{(T9tJDJi5}0tl1F+A*Z_y3dO%mdeOT%~x3y*$Ef5 z#GoANA!8c8We%w*Zm0y*I{sFg>Y4d7NXKiUyu5RU*INU~+FMW(TaK`39hWOv%j8-> zNqi%SQsYLIODNNPV(kt?^G+-Rsxa8fI*apK_c?bFO8MC@Dv7qPN0{-j&nJZmnHLB{ z=HIhzUS*ML!A<|Z!v{!RIyg%;E+NHoG^fJlQQfLTpN7^p-tGI?0uF-fQKRtxFT@k9 z0*5gIy-i#!Q16DTFLl{Ch@DUSXNheqk#4$BJlxrpuoM43u{jj=nEhp6%k69Wv1=k- z=X8JV)95}c=u_v9{n0R+A<Ty!A|uaym*@Vr#?Z{OIOzA$pS!bEADudEKvZK`3-}*k zyU5$GIr4t9YbDi5S@xxn4VS^U1R|z3PnSH%F4vpK#^QkCR8~tzMI9<mqiA>NC0mb8 z*aP>f+ack(Aud+n=K=^w-AJ7q7FF9p9DvE^pM;(lAC1AIm1%ad<yx>CG&6JC40UxY z>&lF;0q5~G0^Tn9U~|nTmE&*KnGE`gnj(JoT_k>Y|0s)%{TVmO7Kms$J`(z;hO8h4 zWHluB9qV2)$a39{IRytSq@(ovw8h$MZlS>&+|+$MK_KRGfV%BM@~${73BC`Ej0Y|h z_*`frQhNx~$@>SGfpeAcB^&g{qOv7|ar-4R#mJ3_{pb?+4?%qAYGZ+9pzZJugag!f z;9nj7^&(85mTKsG2}|u0jYSie;EaXN-k8>TLwgkHj<*bByKWCqQ{rO;tt<&szY5lx zblmZJ;dSzDg*}wr25wH0iS&1q(3#msknUrQGk(D9NF{deZ%kWk&De)R25q;+LU&C- z0epIPwFOQYbrIxyWk~;`5G2^|_6U%b)n(0BLw}7&%D^LMM?z07%{L>sL50~Iwl{wX zC-KBd4_ITY;|`%f4Oy5Rs|@C2Oylf+yy>2I(3V-@X+3YC%U{bLsK+vHodt%2keQrB zX^&JS652Y>GX^;S6FWT}av#EF^geG`GH(RunTeBrGf;rJ=H;t(m36;Dw37THty+9C z>9;+yHaKT*AME$4g4xM6Wdoo4fP0f2C(8OKf+@o}XFQsLHQJgocP}l=hSh<#gw&>3 zp*Egn<6?N`#&9dCpZu-f^}js36kBeLCRwi$LFg1<6y#>!LIU~9ha-Id`jjp3{7tUf zscd81#|mMMKz>-lvuz-<7MF3uXFN;fXbi~IuymcR6IO~Ny-a0cU}+lH{eE<F=?aH_ z5?W6>6`Y=H%XU(=AyZ}2*4ftYg*SiD1d&6V`@yGGAr1&<2xk~h*A}!CyH4NC$3$<; zu|5}O619d6@oodhf^szp9L@zHuu`{|1GI#vOf}nL4&fsAD3fMpFz$I-ghXittO>_2 z$>hFWimeedNDC|DpG$~gu&N6m%U{wU^AG%j)~-+UaBb^EWNLF9k1d>0(Ob8|)bdx| zM{k=nQ^s&tASByh_y{pzP9OJJy?8$0yp<W-SrqA<RSsX5m>WdLdU+JTO&J&E7Fk-9 zBwFt409M?m3;>jR_%5l~%M7X6eP;0eu6RY9h-KltEP8rc+)1WQ>F<bnt8<mHcY%#W zuu!O+F3<MZh%Cy)W~;8B8<ynAQbjG=4t=yfleWOJKh6DfX`!T1f9XHDB-(wjo2tUn z6KrY)xX9%Fb*YbhUX{LQ-*@DpZq-e&7-YSB@d@N-L!oBKtk5gaqx}U-`ceS+UFzp2 zZwy4Nzh%YYJg5t&csf!pA4F*`r)tq3r+s2(_m6gC1+#_(QFfLhLcqB6o)p6+B8jN7 z3!>z@ZdDy>+~-&TS#9xo{G4d5d5u-psD(IrQd}cJdzAr3MUbBrKVT&+-Vd2Oy7j=h zz+KQ1V#|q?aq(H@E_+ug&DtQn{ynzM=#M~WYaKuX1zq7+hzVm}W2j>K>DQz=LNA4; zJ4^@wl8K8R9xiTqsfn8>jMq4;Ev?Cf^m&v6Cd<LMWWDXlz=8G`rrI&fX24YXNjEq~ zrBBsr%pxwa2uGcxVE9cPA;lxCsfDYzF?~p~!>j!A!EI`-?^+*5L5>QP<|uT}Z10Ek zo%7Y9Gsj)G3hcx8YrSbsPY=W2X3@RWS^1w4G0ga^>j+!ta%_)(bhwG8PI$14Nh{hL z?4zRMwniM|8ZAJW{k(sJ>=g*(^!{#sO*$d*S*H^$70OMHxuVrXlLA4S=QGy}jUrOz zX9Rt1TP>@v;c#4<ZTxJ8B;z#*;)8G{Pt;HUW_6GiUiv+PkjdpXt!%WvqRI3xCoCz- zAd4HzrIyz$F0$+|v`8%bA}`rNtzy!!m?_Xd%%QCIbtj1b$KF)ILhCDearXLJ>!R^2 z6Yw1gBhM0EZ*GH<?x);2#pdAoDyLGL5%0}>toOYHNonO&Z6pG|92Wl`lD5+&Dc$PL zJk5rbTxM|3+mW)Y$iSkL5RcA{6{ryI!uLe&rn6D>=@pD139g>Go}Yd4abK3>nS=Rb zbq#C_-C#P13>zB1D>){_)eKtnYXy#<T1JYAu~6D}uWCotGq6+eGmRq_?cDqCv{<h3 zA-e!uS$KVl>2u9L+S;Z?4({S)WiU&nZN8TuTzGj;_ka()7P7(Xp0K;vZvTLRmyIOQ zw#7xfD6ADJ^?@tNK$_oYi?5DW74tkttcQgQeZSV{J&^?j=f_*V>Q>IuU+gke5}pN{ zbU%=sv=u3}&2C`mAt(U2v5OrCz%d#%aRK!9oXSkkdnd<m@B6w>G;NL^@3V>&P0orH z!%&`$br3QCe%iE0p#8o^55oYqqy@e|M$hDyohClMt7!heQV2E9$7^fxB}3&#Wv9{z zD5BAxsI(?b7;GzBOj}XFs5p#GrsMcNvrc^FxKhb51DwHSd2N|G*XdM9pmbB72}C6S zfyYa0)UiyS$kwbB2eqsfKGmtUP3#7^R2)2{vFk2Ds0WyWILf_)P&~d3&EQT76tQ_G zGE|1K*6K_|1ab0Fx2<pIxWX1WUfs6W%rcJOV@MzV3GbZn-S1i0bC==-{{hJhFX%s4 z?$QgcM|KbBQ?vG{Na#4xE-fG|JDUpu`FvOAyLA+ef85dQOBSmAJ45e$=rArG>PwY3 zuWl}BdB8KVX7@6K#dF}4CoW(#QMh!%GfLc!WsA~_p&f1C1wSBxPTol#De;rbEcRkf zp9Ws#B`k)r+^wc(6l$asXv0gYlItVTFM(}K<PP7TTg|x*b_V`v4F0CORg|HpMpDIT z62O4YLR=I8f?oeHm?qjB?84<_jSM!p$Fp3K#{0&3UJztP%?a8uif5JLH8i|*Aa(1c zoH5F0UZ3E{_+zX1H}~Q^rSv&ws+MbmFq=Z7(ijL}b3RA{ULvHIwU;lKC3L8=SWD2| zrhEO6GQBuHWK4+g%z*mNin`rF!>@}OTzdUNdKoMNa-afXL&Bk7Ur<6BYfAw3SREQ; z{Ib<304wN2Tiw$H(0}!o{XCzsLNP9nB~J)R1CU3-?c7L&7Y;TlbLyiE{YMo-GS+KZ z>GT7p5HCIH<#qtL6=-$oN{V#6RUx3Mo_qHj%TP6?d)sao>wPTXs27!*Z#s6&BpvpP zA+Qz{P03_zgbnpCte)h(uXb>9h>Q?tNE*DZpSC%b2D}%axpSc)wU4gxc^{N-<@;-W zp<0PP1hLm<@*v<NFU*05&CmU_&2qIi^Kw86kW5*rdx3u%;T(UTBjQaE$4zu(;1-eu zgtKc=CKli7+|CbHRVco)AnKh%mQiut9*E(*o)&UZN_3JtWl1Y#$taYVWn{jSdoNGK zQ9Ukn>Y*cm9;|L6igBoW4VDoHgDyEvDIeYZ=`Xg^AelCT8}SbhK_ey9wk;trJ*m97 zG1@gx>Ut7OX=OcFNp*4Kh{UDtgI~s=yry^5y-Ygl(xw?xBpYfE$(RC!{ek+Ao6nC} z_bshG6<*sbT36<3&Lo(-dvT8_ThG^QWm`{3r*}%y!?5#Y(?25;t+2#lnlA-!q<j8| z6xX`UCY_l^5&*8!?N|s%TK5tv2K!A5wbA2#-^LZwm%$p@3^)<kq94Lc_4=}N>^@Ke z^kGJsJ?^fwHSU&xgu5K6#KTasq}eOP>CEp1kSMjnnpEZAe{qp`-EwfC)21K*g|wZ% zF5F=`1iw65IVNn1`RD(6r>`188vHkRYHZuFJP@iBzGBn}^&EVH5i~64Atj$Noo&4A zsgafOFpH$P@VI!e<LGXiPC@6OSUVgScKHod9KhMTTYNm~H?%5aLZ6C!P?5fv{6X7B z?NDQNkb9UQlD|smqCJS4q9VG<G(z18;o+chthK{z=knWDUcyle*5G|{N%wWfd#Ff3 ze0k2Xj#nni8(C6#P0N3uiVaT7TZwidhDzSm3a&1TcP=UIoZ(hm5-1Vcop(QBf7&qA z6J)6|J)Y2CLXgko`h^g9`I;xm3lPDdne0S=Mp~H)1uG6uQ$Ty2_7~<HJG!tV0XLEq z*3nj8>Dg+9KfMWXclLS#XQ?+os)-rU2vEKnz@`1}b|{kG1M?W*LZ?`JY!?sgB^YaB z2}BQD=zI9L`i%cRXa?ZYR%mv<ny~bK_-O<44}Z8UHx28`)_T`{vcK_c{#L1xS3N1j z_ym^i6R;&-ttxvue&_+X$`=mkMbE)QHCR<o{XLVZBe$d-3X6rJ3|PoZ=YGRL23X37 zWKn+b0WI<wRahbithxC4v2hSDXHM$f*s_wKLmvbrH=WZ9y>gV>l;FZ0>=!1#)i$O< zof}biPXr-yY2$4RVlVMQI~@00#=qshB?mnN#KA>Qryr>%at~keULAO+U;YmnKyNtr z{bbRQ3+6GoTxe~*?-)AvAybs7k^38!-|gPQgfjIL2leDHH{aS9AZY0wgfGW}QVWg` zlk?xW!B|yO*@uscUwRerR_hkCNwhIe-;Qd0$n|vf>T5{Vq#FZ6r$3(&7UK3svlr1z z+)!dt$Ds;S2N{BH9y(zcmmdr|@<a+0jgUw>3CpPi&AS9k79~Y3@)A{5qU+g=n(Wp2 z|EU)h8oHcRSR#t{SlvZnHk3Fn2)Lq?c<P<6;;{aSyyO+RUjb9ueFTn_!PM`0LpjUF zdN(OdFpygvLdChv4@ic*dF-C4oe}5PUqDp0eiIvQb^S{nUp;W%8LlB`6vrr8Lmq3> z!;5}aLgXjZfvI$qK4Ax|y5*jgzlDFJ>{k3#>DC<7ETWwAVD_iB;O#CKSr^(NC;`Ae z$NYWJwu$SNX7}mFf#vqa<00VKehixB4Q#FGnfotp%a!_eC)4)EBF1IN8Eew(S0zL< zi59U6eaR41aRpk(QtvCxXW{@(rBw8TNP+;;7FFm=?^(YD6?>JJF@9HQ8aeA%GVt?F zaNN0Iw{5Davrxn(mwPdk04pgj^fi_CZmqN2So0Qh%-Bl$wctr2*1fbsZz2f8o{MiZ zzq-H#Fs7jC8)8baH1lMj1;hJ}wYRw4RHxYGpqNc3Hk3^qP>;UY5~?Xf3dT5W=>kYZ zo{L-N+)F}IDjxn*ul4HBY?&8WLV7KXS(?$Wn2`%;fq7zb7p@2weJ-rdI&pno&2P9j zRHT-gr+ybAQG7;Kq;#16LcE(LkjpO%xf^dTRJ>+`!|mKZc=#z{1}{0p^8$kY2(28* z&5I;{kv0u*iinlUf#^tp{Ksd0m3ssomDUDS<IvTiVL9O}(tNw%0?s@NBD`wy?yD^# z5ue@S$fYW#1zc==q1YwyAd-pzf3h_zXr`2t_^?GMk^oc^GyDo@wKV&~sPo}8r@;Ec zDl!$vgblsI00fmXmk4fJC?~cPxiC5NvIto6;#jAgKpeQ0%=lrztnqmyFXt}NC<(aO z5!~#C+e3_TN-sgur;U0_p609{YEY?Ln2dW@O@SaGo}Xbd3PT?Tae8HK3?iB;_91!e z1yAah2$Dl9(ifdwNq`jr-m3irA5Ezn8exAFu{Wezxcd|USSQ-4F4-%a3t1~O0xwuk zMIc|SR#N|f&{O&H(H3<FSWGU8Oh0l5K&FNMlokc;)y8jGy<|MwDTwGf*XEdYcqoe6 z#`9~-2d$`2;HR2VWgFrxc!rB_;LLRSf|u>14{iBRG2o<uSfEqaOMDiC?d|b=GQ=SN z6sdsYJ6>8ff7*n^x(Y0(Kbe=+s#6wiv5zKYZ`!xunPb-Ksvf(wlx&uAZ7Jw=sySgB zYwQ^W&A#9RXMZVbG42`OSli<GiA6L&${>%}A=hnt7eg8|<h<X%i>3U?WP;=?h$4FW z5(-q;DoBYt;}EkcY?c-Hk59wfQME#n<nTTnXJ^r{lacksF2p%FrWCHhO7$Es*Bh6r z$X@AHxgZuJq@!Sa%Cb4gLttn68M{r#O#w9ld7tV(UR@;H>v?zz29m2@^fkVgoRXim zbc*W<$3JEJA+H#jEwaV%2*62eEcKaJy+5cKwgnGgKV>Ej;tfj!>#vb^e4W$iz|db& z?TmAQByc$rkV749rZ7$zqcV|oAU4Huxt_QS4QU#JKk;nxVZpG})19qX{0S`eU>dkj zds^%Ig$8_ci6Rxf$R`MXGNy$G1^>b<rC`_xLme6yXyz+ORs!)<t_wl?dNDJ<!K*D9 z9;}IiVUE%=No@|Wp4C>?Gpv4tdM1&ZVSj?|{*mpNjGV)BJ4kj@kpN$M#N7DJO>x7D z)%dT_--=uH&Sp<*z5$YoFk&>l`nma>DBS!%8Ab7K(yQmmQ7d`g1#J(e;`!u2tCttE zm?0x(LcR4YKx0l~g#tvvJl5UwJn4JHA1rz^R+_Sh$1+CU$+NGcZq=BY1pDxMEmo4c z#}Q-5meQV!Ch>Kt+0&ez$&N1_-cw`pBAX6wiCJD1q4<?}G`+xfmakGxfc|U$1yv28 zv3B+;qde-Ndf9H3u&mwKJ{iiT;*;lZf=a}cgZiL|3HWqHCK$6DFv_(sWq=wY&U&fB zKUVCHLCL`k^8)4*f5bQPUu<)z;4IFcryx<pLXmDx<{8J2?*5g*5zy|eSOm;;OZc}C zFQDiPiH0oo(ibC6Gw61p{@g^zxwMi3QNheYu1?nTR6oZe^&p6!wq9NgHr)OI6lZ)n z9S09(N{<w1|H;JyWW#cEtTc62Vq!@?VXs~?ED}i23?z<f{­XStZWtd9sM22twu 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 z b=(PYr^svtXF*{_GWN0?C8-7)$NzFw+-$YvMWX{S5j61o2v_GG~TiHk>d#B4+x zbP4zNdBMIW;~eJvh|T1E^+i3sU4x5QMq71zM4-2E)vcP?1NpE7^Wo;3$}yg+T6vQI zw3c(^&hDC(A1mAlMS^@e@{E+C$xoq7oS=VaQhzdCo0bOB09@eY@%jFnF&uTX?%&A< zNiGnCxT~QSgY<w)65z>MP+e4}I*vNPuimHGt_sW^UCNr6Y>*!#*>%|fU8K-sV4KK- zzs)iYDU0FJZd}4_?f{c>o$?)zz<O+$HtntyDE1S;ws`-mV!tQszc*{R-1ejgM5N*U zSr8-w1-toq4THV~bMIdw67v)>z5_!Ox~kK5Y-^Z<azSaqyV2x<wy1M~Kk4T$%^~4$ znq9uZ*1x5CvTBhApWD3U3@o{kZPQp#)~*}uw1sv~hkG8qb|(8B1$EZT?@;gB7sW<> zby(q?SCdRfytTv`o4}fm)1z!v1as-rH$U!~hqbM5$FV}GkWVc<T%<CODX`+}o8TL` zaeZdPiM4l<(GHI%*YutsD1|L3h4BEGzz914U!Lppdo@6#MdA&oejZ!;m2iO}6#UHh z=go3^YZdvKpZU{g=Wjz&)?h?>fi<b?%6*P4VeM;s$&Gwc4i2K({q;`heruQWZ{w0Y zYOmVHBTcZC;HV(&Qx#S`VrqXZ0A7!?no*H=#o}#D=Fcd$ZprG0x;xZB9934U7>0JD z6NQ1-vpMv`G6<)y6klY5a;LjJ?e*$}@n+DTvF5cUV9BqRN_>9Jbei-@FzX=46&_Kw zMmA18)+Q7el6K-CTZ`h$F#op8dn8M%cP*qs5M-scdi&%IGtKau?OZHP%^SpG>fXgW z-E!nc7mDkpRAL_RzyEV>Yu<seAc$EZSjw)}_3v+`^;Cq=c>bkPU(;edANHX82_e*Z zOA{6dujWk*^zAWI3CLVI%c%V{atRzpXgzjik+m3}mRlc5er^jj&yCNV2~~mS4nK}W z30?0HGd2hY0kqREds<0fKq$r;w;Lu3MyA2<^|c|8RoFRl@!4NfWR9#nJ>oWGrw0$y zj8UM#6N~fA+9L|s?np52rI0|LO3WEoU(S%8Mn&+tELVesVEN#n_%UkA&YK$v`trqv zw%a3m+#($97bIQho=JTzIkox4PXr>mRPdo87pXkjU3HfQLdH7_g-PkH?&tO$1$r-y zS)+ya5qD48w9=GjcX|jTboPK<T}bCdv<WtoOu5p}ldz}%694>#M2li_j^<A9;jY(e z%<QBvnEYMM2csWm!yVL`<kuZOxa!hLS^wTf9#ZgOxs?l73ak{JtORUW>aC{-{Oy&V zjL68sG4Y~<qVdz2Ua8xIF+<$+2S>fLv^!DG(6Y<YOjN5y(B-uIoj<-M9GHS4D<h6+ z(qr<9Kyt}lK@knor5I<&rQKlH&3_4S)1d^#XO$5lB$ca3#4rlzouC_3b04Hk!!aO9 z!LEzuVm6n?azF~louZ~n%&ZgE8q{UwZaU<mJS1d{|GPJeR=l2@S-O`}P}LsoOv*LS zGKPKK#OZO@c>fUZS5|M$l6_KYy3OLG_W5vUWvmkBCWxS$Mk?!M>5Dh;hUN#fXy_Dt zu)$VSEU!59H;Q+EM!}W%#s&>g^hCh1!ADKjCd*|OPh?v&fx@Tzp_oihAT{RM_mN!w zKPPSFOa55atJTH0`a>kcc*+IM%C2rRvts-9V(r7b6Hb4iD|~RN3xcsPg>ho!NhlJK z-z_nJx#kf=w&}KT8u=HY%)02sW{y{r!$BNQEA^~@H9Wc&>T4g41v&+COoV;RZ$}K> z9f5l`uXnH$a9n}ZBgOIaNJ1_91TkCNEA+gjY0J?L3ppPxIWvTVZ_drzd4n0{)g{0k zt58%6fCt_43}IO;)$aotvU#CC$s?Opmj~Fqu|-hRuU296i3?IVFicpe!#@8j>o1Kh zUa4TAifmQMLFauide5m+eCS+jGxEodWfLO&%()kF8tCIl?PCH1o?{}~VtN-y)W=zE zLAjHlI{~N7viXN*TV)#_aNwN6gkT&Pm(T9_oewUjtzX&VVQg$TN4A>mdv8xWbb5$> z({M4Pw_T%J4HA~olI1tqocXBb)`Ex+Z7i@a%beh<0^+7W+zL9+oy0QS$lrxDgiCV6 z*zidcG^VwVdpK2M(cu$s*IC`p^??ai80n9VqA9IY?js$>e%e%L_I$dL0QDTr`51j6 zl2;3;0WJ>AP`rt~FFbj0Pn}02>F`%L$1xfLO5il&l?*(lJWxSqRKaQT`2YVdO@VV8 z?tYycE#%tllGXG&x#wgGR$ys)ffawwH#alMFZbz|=&P@4x_pnBOy>lP{GTHigG(<M zs!3SM%`C>^zFS;uPYfCf<hb;w$F!#2pbz12E`?%c6=}hFVS3evig_1UqRT!Sq=BN~ z=HHNB^JwV;>h|%q0n3apq$wxR%RhwLj$73y7zFUF%V^Z0)h4$Prf$oYl-Uc8+h;E& zX1helA%fSLZwlgdWJeVUb<o+@0a^f(Cw^PF1fNDl-e9@?Db%BhMU_&Ss*cFOZTd4m z3$|rb<T;K1^h{KFC)ubX+D7+OPhaW6+(c-CT0^{)|4xO!Gr{DXUM^1Ug%xXoK7a^* zf{f|Sf89zrwvQrLY?PS(tf(wguLB_$Vc8g?0=#H`?Jq<9D)_d0i8wry`$URw!F&}5 zYpC?w6u<6aM>+2#aG!}?PWCna8=1$2TQZ?RIR^co!O)cJ@i*X|@`6XgS#xM_%vPnv zO<&{;H>WSV9q0ygmYqp8Nus$EepENK+##UJY}Nn;4ArUVV`ZVgJ3>2~$*O|TeB#yu zoz)UrpI%Ws9M|LNNM?5n^Sqjm)Tb0dQg3h|^=ST$2I<u`%!jn=w@fh9&LU0gpj}*$ z%)&F}BrXnlk^=xIp2O;uQjGXu#Iv2t0CMQ<2_L%I?@@7IA8S32!$7CZ#bpNwbfr2) z5HcuEx4=*+l`rU!zMWFml!1Fli+8#Qwzwh-tdPTQX?J}fo3xfqO}q|c(?!Sl!p)cK zSG4a~smV)JIVQPZJSnXUOc2COS?yAV(G@b0pa}M3P8|_}FeAFjTgue1kY3Q?;Eluj zhi9D#h3POqJai+PIcXy~hHB?X@lsmygrppgcz`)>p)Y<s1z)*(Rgh<LgVP*xgnd)F zV=oE8?=2=phaVjl;%up`!llCnn{o4;W%+V_(T7IW?6%)mI|sjuTA_;p|7te-w5R>} zNcIb9R(&l$kNbv{jfiZ++n8*j#dPMZv1yy)OJCg5C9O;p@mX$18_bv5e6sl}yDgG4 zi;3!aCG&zY*DICfXU3QBSHa!UY&P&8UbFN1k~Dq{FdV?kZ3A3tiecP5oG&sKs3^wC z?5z)UP$6-1u@qPeOd~(E^~M5LjaSdlh6%}!hc@${$*mYDDCum!SYdA(F5JDN?H%U# zGQGK%m4(I$QoxjA2s0yU@{}HblW#1}1qhu2G)mBa9}fZrvmaj*(k^Pyzi<#9G|9%y zNC!X1cAvnr5!-d0<}=$pjmO_@Z@{~sSO+#-Af^pXa`?dMVd#Yxxfa31s+vCXTNSBR z(MM`U-EdED@W$OnGX7;d&9He^u%y*Qp+ff(*9?;G9En45w;ZVRb|Z0z9i}A`TU)^# z)Art(claDPHKY#P>hLK%v!mQazqCqnbMFX!`k`ama$@Z7TX^(}ozj}d2BwL5k5iqr zkCmg?x1`&EJWWhFyMpD#3iiv>4Jm+<^h+%Iv+qh+yNA#StFDBzUO1Lafo!s4g=u~b zk-O%K9^wn6hR)lR|3;%E<5e#NO=KwlWZCq$T)E%310Ny2ubE}g)mH?6;v~zPT?;wa zW<#{J)H{pZ>M{L<*<R#hC&2xANVb+&p01Og=VB8-I3K%0S-uplgRu`%Qw|tLci&?S zYL2kAchGx!3(THR14ylA`g5Wy5zBqoqGV(WDbdcZT+N8${VPO@zj-VB1cPU&OV2oC zP6~KTm<gOw9`gytY=AcObEUuJl5p|=(0?Z6R-;?#Sr)zxPj_2dMc0P&*Nv^VhO=}L zQP>8wBzSbdsKvB)DpJ(fJY!#7z9|d*e=QvVJ3t))8Smf3qE<slw`?x5IVeB?WbSuf z%Hv>^uruB2m$7;*uEZVBx$OjaLT+Fp2oc1}@jUR0ax7Ld)zZEs+ZOIneQ1W`OMd;8 z3<{f<n$5&E9(H*TM7#a|TL}QdzhDXS)KY~Wd4$<CKz~IR&bDGHTC`x;k}d%i*eZ<a zT0w=jB6s@!*Kni8h?wd0gpD%<niA|+YxarJL}4xf@MD*^r@S^lL-QPYpP_~kOO9Yq z`U8SWg<Uz_R#}>g!I9vebIhrVCJ;V4MO&4j03Fs*<SLKu9VBk^qJN&*(;Cbm<MO2; z>;1@|^^1cKm;fi(yJZD-TK|Mq*cMPZ<a(1@c7`^gh;?il@MXu9Rz2TkFq`m_xb>mw z(Zrfswc~d2GSL(KNH^T#h$5+f?KXYAJVX)a=dhjyK8L4=AT^`A5Qf@FaD}D@JzE8( zdoGq>ki6gdIN)6q^ew$vjT*fmCfr*Djb{J)Z6dug{g)TL{s;XUkDL+S@I*;3qu17o zHuIbE`W)4TugG&nf6NX#Gz;2C7ey>+C^pN`bL^qL?h8e282<|#-^y|0?`kU6i0%r# zo?e6IE^u61Y`}K$h)Yrmd9L~jF{0sMmrl50Aw)x5s}^i{!VKEnVN2oWcjU9*wT<9G z626!dMVQ$~Hy4eT;DG&OflmUmcnMa6_LYZ@7GYCndco*kxDy%L!6nLPxs8dzJdda> zkz6S7%Op%LiN%Ec%Cb!Cjl1`<`uV>F{RYGs{~?qa+&|g1UO1pE-vt5sAqC!NS5Ngs zfu@sBO;L-CQY5SHl#()26pvQiEsU$nTKs!yd|unN^oV=-+`a4jvFPfdgust`32M#6 z-CUF>`jCrZobqFa)Uf|v7-9cMKWa7OBMw#S)No+PbHy<l>x55-hAx|T{z3qsSl}Ry z#C~Qq#-Fm$AU8tN`+<(Wx}+vvuW$rAs@9K%n+$%Dlc=4FF3bJ=4N<02=23IQONa)P z`QqlXRXvo69WjC;X4l`XJ9M<AS*}?1GhWKOgjS{l{e-WJrcq-D>Mvrhn%N`Ee<;BV zzj`7V$C;1!)#k|d5|{?c_4y3D0HK(Yw$L=dw|kw~axl{!MVr7VMtUE)_N<s?#b5(< z0H|AOL`&Q|jgkBv1(EaN{^3F)^V;jgj%5JB7xam4PSnK{h!nxOr@#AVRyisH5_j3C zT67opJVuC`QuSf*O;+@Y9d@z~HZ@4Pp&?GcanP~@6hWi-N#dx219_2AZ~=f^X+1;r zKP*=2u-X?BK~22<UR`z9IhhV&;(Ne0!^^}b-wX@z7<=<)7}DrL*3)xs;ju4R^zZRz zislDlzC~tkXU$AEXn&oRa{0uK!UJnX=2o*!FI7h&RHR+h1#?+hB%qO{z;5@7=h_0_ z2xC<CW8PbOXYp8>!X+qiTe}{kEC@26ShMvO5G7O;w3}y$VI(#caQ4mo?i{R(o_~Pu zI<qbVA-q6q)fdW`Ez4$uXgnv-G6}1iGoMu}j(VoJQH3cvxA@x?Q&$$Zk3aLid1^#< z`V*dgL$?-MweOOae&fo^q>NTy9DCZ-@WN*+>uzOZd||s-nvIh>c4_{baolg{%a%wa zjJDKUNbZ_%NIXR&jGyslEl|#0z|$m4R}JjDhBI1rFCWkV{<b1W8&NacO`of5u>Si8 z_khlibM~dB*Qi|4Rh^*pkM)-T{@`5u^<KqPDPN>bBF}6u2Y*;pm7TcCifzcmn~~7r z6SSs@5t?{M3#Xp_z_HKeR1Kx-@1mpqe#5iCT~m7FOlz&_`I|>BqEO)LAraz+#6Q{e zhczjcFe>sxOU*Vwo<{TM1S%+SFW;naE>32sqn=F))Se_O^;~RR%PY5XwC8S7#2VbE z8Mm(}o<y6l?~PJ~itkFtN?-gon-thixb)&DR~Z0$6N(m>d<<ZR)Ci~>!IDQX;Ct@~ z0XnhH<^qapfA@F>W|GRkUcxQ<k6Y8xkdGgx^vLJbc=d{wV)kfpTolV-%Ef;XOXtO? zIwlWaviu5-1yu2P@gjG(wNluLzpdM4Rzq}}ZKutq>?-O|zxMt__228#|H0#B_T}CM ztU`@ZSW|@o_{PNmK;a^P1#sz8-_!{17kQ?gLGTxJ!=!hvIS>ChU_Pt4N7tK$noikA zu?owD^zU{kPB5B#bDE9v(q@&s7yW{KRE6~SD45m>%&C}G_tj4VO3RiX5UH+YBR606 ztVkxOJl8U;Th~lrBs_oUv`0WtxkzJJf0v|DSKb0O4TPFcO1Z0HMnwOQH4RI(Qj--l z9BDTTb(Em8+;<c>px0b4ftH9OrvU?gz`A`ibs5@$fed+=mF+-hhxzO%iGRwHKi)S0 z4}XHa;6sWGBJjy|T^xaycj%AEgFY@YW-@pp1lbW47m~Bie0=#@CQ{>Gvs<8P^aN2U z=Qkw<BQ40FzhAzqfO?hBR=z;6x}-0K87+psvsTWh|7{O{9Kjmp%0iDY8iZ8DWJ&!8 z`W4Dgn(^ifU6E=rG{+<=%H@k~xj%~d<)E_(u47|2(KzqcH-!@Q6zRoPXeP7s3QGNt zQ66p`E!1SHduzX9#AAmgX!Zpxma1ptFD7cJ>KYTeZ}f%lt@8w5RFcPv%kP_r-<5Bu z>n1NV&^t5<vFM-_Jmc;SlnxX#J$cj2<kwwSR=`3)_Re;Q2fy#vASqIdzY^K>8Z;W1 zWJ#&8zfhZ8SkO%?uC%Ra!{VDU>?kd3C;iV%eyLbJ6k*zEyTVOy;fWcp&lfjQXPqIS zdC8J=`6U{vIn6IGN>`oZW*NII)b1OcQpeYZP})QNAmV+W-2elR)h&vXI{YP_FkQ9u zL1!vnxj!+8lC+fRjtsyFK(ZJ!rejQ0dAJ=XgyY%BGe8C#Egd;0+RQ;;v1CxVg_x5Z zZWXV&YyZVr)4t{Fp@a|Y;!j1+5G^2N4O+BKi2LHbOKFRa7~`PymQKL+9e<m?w-39+ zqwhLlg<;BDq6g05IlSB`{y@3)^j4V#Qd3dnu``qJiJQ*FVO*2H*Xu+OerHX{C7n^H zb~khJAAS;Qm+UMH8H~OfbXA$jx@3b6v`zeJ_5Y_l)=v5ko&#Hg0+&fu-Q?6Xk@SK1 z+8#XDr0HJzzk#Nhq>&cHEgA^F-#Fm9xiAdc;qK{rVC5J(pPSv*jD;4vBjKmx*}Ku` z8Hc4GyvrdJ7rzsnex8tq8Y?&0w}sifi8er9Y@&OjaNEKXTK-8I(J_|yme(oZ2Dx?d z^DK5#oW_6%)jUyqtgLD3vA0dp(O#m9!e#31nO|(qDGGp38!kjrh|!0HxLm}Y+(qB~ zfIAnFLmOE+LkQe6YI1qHH9`sbn9Fioj<%L(D)Q$xauh$)E=LDOI@GbSn?<BIUV|}I zrKrj^2NDM045$dO7s>AQ-v0oGYZ}0u9kYf%YVv{hILM&0Av3)7Q${wJY@%CB1yZhK zY#Ty!N>M}<@v}r+O&cPTo5vL?Z6WZJk&=<xC_cW_z8#3W0(HD*rmG=>s<zDbGW+&u zr#ik<+;p$|eN_BYn<;y~hIgG0J4C@=$fKVgg0o=W8FjWkX0wBrY1K=kaEDzDd;AYy z7P(hEO2g}1^9u#4Cn+KaTj|^jf^{7(?my3FG{sm>c=Vre`UWUytnn|fX^*nXFYdqz z)d=!w&grFs%`((6p(7R_Mu(m>8WLyB2yvVhp7OS7P2ts@<x}+sWRb&X^N5Gtc2U)2 z;egO8OOJRn2@E*agk0wB5e1B^`SkW5URXRlw6|rAM5+)uV?|1WYIoQ`zNPohrs5!T zL;N&zDWj2g`3Wl`>BN}Fd6A;4AIH}GY7*`E2UE#6t9Qf{>PAmlk}HpTd%EB=E5qlp zPk8=s!wFJ=vcdI|E8>G5PcILAj+}N^hlv|Pem-za+SPk`Q$RipaaU(EAg`ZQz)QOz zr~w5!%=#hJAR)2L-#kmdRa109hCINGP&0gH79^>}|A*3QVsbUWlvupy%)B!%`$M~g z6Y@7ixwEe~ibJKR)2~L6=zJe)_!o8`jo8QSH%kDwjED@Lay+ih*`gJ19HyRJliDOW z-6KO@CSYuN-!?rxpEd8rd&}cXi;SiB&VgdEGX1+ZJvrBwDSKezh6L5Gq8S!wFTvOP z%wK@Y6a#?g{RVi;G!~~YP7oSl#l?1Hj<f(0Lx5&kF~i%QMk%ws2w@t#4oZCgt&L=g zJaKPNw5+R-Jzqx}tMg>{J)zR2?Rekr0lg&Oa(itS%Qwtu`F|8!HUixqh-jWJSXV3U zx5vFciQT#f9`Glomh2`RpBP!$sV$QnZT>W0IhQ|_5iu2W=q}_Bi<6b|WIbbcE?R*2 z$If6Q<iWp4U({36%hbZw!Ze#1X+kRY6n%h-E+3(9)4is`9QZqR8f}gKeN;N7`ftPy zG&2=eEi>KL$~|WB%Y@s4kspWIvs)T|kzihWwAk>e5HR_x!-U}adV78@nluL@=VuZC z!`u42BR!mn?pU8{TM#Y6_D302`rPKZ$lPCPQhpdk^ahWw*+T!(pim}<R;^$LFtnzx z-P_>GdvL#JNb3)zDS$*To2>S$I<pU(*5udy;ooWMzUV1$n}sV=zNQv>aTirh#fz9> z%Ii3(R@==vH}D^%st%=qB@Ti%RFuq48R51(B?}vrac*ki(@V=uPd1E()^1LeS#pdn z1uo-!%^DwC;%J!r8t-Bdb`4Pu<sj?|WRIr}DxAc5%&(ukA<#h=SZSOOKax<k<5Ko) zam<xFu3t||WkewQkq9Fs?GKmiLm0v}H$owWZ}@Bm#MEj*`4b74PH$O}MV&CCenJOs ztJ5cV<5tBiGK*Hos2b@U)xN7wY(!zIL0{xFY27Xwm%jca#3xvmy~S8XWxc&uUJSmY zkFhEeX$S{E0ZmDO^1zc^d5d#DpbYMWHAZg10cR?A^<-|0hJ8UX({C;7`!$?Um^N-_ zaKLQc7u6{TS89-ngB@~c!B91NvF}p^JWd3h>q#RY{ffZKu;O3J^pnbZfMrz>OQipT zQ-%b3FhS*+a`;e(){T2$h4SNX?U1FC<oV1+<Arh~V>H=&Yk7Glcz-bi{@ZG&!{s7V z^d~E;)n>sU9)m#BP5eS}HpM3Z&)W$SS}mJ#zSb!WUu(k!D^Y?^*zL$-IPvgBz|}#; zx}7a|;PES3wbN?la6x6GMt=2t>3#>xm(|W{+>7q4IAC0#m^8<gATPH=Pp}>y#0<k> zd)`$-N>Tu}(6ghZVN+n^7&AVeKYUQqta;}LV)8~&`^i4Jh?2eTq@kEL2t?!uSJ9S_ zRGu(^q*F)Vv%gh}DV{D;m6f;=%9zis%L?5<=;}a_q1@6uvNvWk_45F6&%6H%QRK3z zLaQUF+HSJ2bSe}mKj1D()?)+Xf%1g=U1+6+zBB;`r;ST2V4xt<LDR4gr;_9}`r0y8 zC0+z@GH0wIl86h*@JnvQD-JKvw<?V$*H`o)_2K>IHaHSIu8rtLc#Gd}L0mG_{e6O{ z1x>_A85$*@eYDy*cc|-Eg6FZeb;*)7inoS}l<hRg6z%_z_S}k}RlZG%0A4FfBteM% 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 M*8>0m000VET2s$y?EnA( diff --git a/gorgone/packaging/packages/perl-ZMQ-LibZMQ4.spec b/gorgone/packaging/packages/perl-FFI-CheckLib.spec similarity index 50% rename from gorgone/packaging/packages/perl-ZMQ-LibZMQ4.spec rename to gorgone/packaging/packages/perl-FFI-CheckLib.spec index 96dcda87732..025166f200f 100644 --- a/gorgone/packaging/packages/perl-ZMQ-LibZMQ4.spec +++ b/gorgone/packaging/packages/perl-FFI-CheckLib.spec @@ -1,30 +1,27 @@ -%define cpan_name ZMQ-LibZMQ4 +%define cpan_name FFI-CheckLib -Name: perl-ZMQ-LibZMQ4 -Version: 0.01 +Name: perl-FFI-CheckLib +Version: 0.31 Release: 1%{?dist} -Summary: A libzmq 4.x wrapper for Perl +Summary: Check that a library is available for FFI Group: Development/Libraries License: GPL or Artistic -URL: https://metacpan.org/pod/ZMQ::LibZMQ4 -Source0: https://cpan.metacpan.org/authors/id/M/MO/MOSCONI/%{cpan_name}-%{version}.tar.gz +URL: https://metacpan.org/pod/FFI::CheckLib +Source0: https://cpan.metacpan.org/authors/id/P/PL/PLICEASE/%{cpan_name}-%{version}.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) -BuildRequires: gcc +Provides: perl(FFI::CheckLib) + BuildRequires: make -BuildRequires: perl(Devel::PPPort) BuildRequires: perl(ExtUtils::MakeMaker) -BuildRequires: zeromq-devel -Provides: perl(ZMQ::LibZMQ4) -Requires: perl -Requires: zeromq -Requires: perl(ExtUtils::ParseXS) -Requires: perl(ZMQ::Constants) -AutoReqProv: no +Requires: perl(File::Which) +Requires: perl(List::Util) %description -The ZMQ::LibZMQ4 module is a wrapper of the 0MQ message passing library for Perl. +This module checks whether a particular dynamic library is available for FFI to use. It is modeled heavily on Devel::CheckLib, but will find dynamic libraries even when development packages are not installed. It also provides a find_lib function that will return the full path to the found dynamic library, which can be feed directly into FFI::Platypus or another FFI system. + +%global debug_package %{nil} %prep %setup -q -n %{cpan_name}-%{version} @@ -50,7 +47,7 @@ rm -rf %{buildroot} %files %defattr(-,root,root,-) %doc Changes -%{perl_vendorarch}/ +%{perl_vendorlib}/ %{_mandir}/man3/*.3* %changelog diff --git a/gorgone/packaging/packages/perl-FFI-Platypus.spec b/gorgone/packaging/packages/perl-FFI-Platypus.spec new file mode 100644 index 00000000000..7cc88d10e74 --- /dev/null +++ b/gorgone/packaging/packages/perl-FFI-Platypus.spec @@ -0,0 +1,58 @@ +%define cpan_name FFI-Platypus + +Name: perl-FFI-Platypus +Version: 2.05 +Release: 1%{?dist} +Summary: Write Perl bindings to non-Perl libraries with FFI. No XS required. +Group: Development/Libraries +License: GPL or Artistic +URL: https://metacpan.org/pod/FFI::Platypus +Source0: https://cpan.metacpan.org/authors/id/P/PL/PLICEASE/%{cpan_name}-%{version}.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + +BuildRequires: make +BuildRequires: gcc +BuildRequires: libffi-devel +BuildRequires: perl(ExtUtils::MakeMaker) + +Provides: perl(FFI::Platypus) + +Requires: libffi +Requires: perl(JSON::PP) +Requires: perl(FFI::CheckLib) +Requires: perl(Capture::Tiny) + +%description +Platypus is a library for creating interfaces to machine code libraries written in languages like C, C++, Go, Fortran, Rust, Pascal. Essentially anything that gets compiled into machine code. This implementation uses libffi to accomplish this task. libffi is battle tested by a number of other scripting and virtual machine languages, such as Python and Ruby to serve a similar role. + +%prep +%setup -q -n %{cpan_name}-%{version} + +%build +export ODBCHOME=/usr/ +export PERL_MM_USE_DEFAULT="1" +%{__perl} Makefile.PL INSTALLDIRS=vendor OPTIMIZE="$RPM_OPT_FLAGS" +make %{?_smp_mflags} + +%install +rm -rf %{buildroot} +make pure_install PERL_INSTALL_ROOT=$RPM_BUILD_ROOT +find $RPM_BUILD_ROOT -type f -name .packlist -exec rm -f {} ';' +find $RPM_BUILD_ROOT -type f -name '*.bs' -a -size 0 -exec rm -f {} ';' +find $RPM_BUILD_ROOT -type d -depth -exec rmdir {} 2>/dev/null ';' +%{_fixperms} $RPM_BUILD_ROOT/* + +%check +#make test + +%clean +rm -rf %{buildroot} + +%files +%defattr(-,root,root,-) +%doc Changes +%{perl_vendorarch}/ +%{_mandir}/man3/*.3* + +%changelog + diff --git a/gorgone/packaging/packages/perl-HTTP-Daemon-6.06-1.el8.noarch.rpm b/gorgone/packaging/packages/perl-HTTP-Daemon-6.06-1.el8.noarch.rpm deleted file mode 100644 index a69151fa6fe0b19632b336f3fd09b788cabdcc78..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24608 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 H018=J^ph8( diff --git a/gorgone/packaging/packages/perl-Hash-Merge-0.300-1.el7.noarch.rpm b/gorgone/packaging/packages/perl-Hash-Merge-0.300-1.el7.noarch.rpm deleted file mode 100644 index 5856fc131d0c77893b44478abefef00529000b9e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14768 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 HUA_MW?T(jw diff --git a/gorgone/packaging/packages/perl-JSON-XS-4.02-1.el8.x86_64.rpm b/gorgone/packaging/packages/perl-JSON-XS-4.02-1.el8.x86_64.rpm deleted file mode 100644 index 98340cb4e05bfa065ab4ab6c2e101f493212cf91..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 99448 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]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%0D!gbbZiRea>ZI46ZE}Bi;kL@qQm!l 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 diff --git a/gorgone/packaging/packages/perl-Net-Curl-0.44-1.el8.x86_64.rpm b/gorgone/packaging/packages/perl-Net-Curl-0.44-1.el8.x86_64.rpm deleted file mode 100644 index bb118172ab4ee1f5e3b0691e4c396f59da1f4631..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 98844 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 a0rs^8kiQUYL!NB5>E9dz000003RzmWJ~og5 diff --git a/gorgone/packaging/packages/perl-Schedule-Cron-1.01-1.el7.noarch.rpm b/gorgone/packaging/packages/perl-Schedule-Cron-1.01-1.el7.noarch.rpm deleted file mode 100644 index 97735403aa7ca41c1dd846365a12b943f1ad337d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44950 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�=L`R_52$g?mOTvoL=e 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 Q=(g$K90C9U000VETF~I;WB>pF diff --git a/gorgone/packaging/packages/perl-Time-ParseDate-2015.103-1.el7.noarch.rpm b/gorgone/packaging/packages/perl-Time-ParseDate-2015.103-1.el7.noarch.rpm deleted file mode 100644 index d4010fb5dd85d7362558e6ecdb2fbeb49b2ad81c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40012 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 diff --git a/gorgone/packaging/packages/perl-Types-Serialiser-1.0-1.el8.noarch.rpm b/gorgone/packaging/packages/perl-Types-Serialiser-1.0-1.el8.noarch.rpm deleted file mode 100644 index db0d4a634fd551748c1d531885cb66653e8883d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17420 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 diff --git a/gorgone/packaging/packages/perl-UUID-0.28-1.el8.x86_64.rpm b/gorgone/packaging/packages/perl-UUID-0.28-1.el8.x86_64.rpm deleted file mode 100644 index 4866ebab7b2e527fa045019585fe3ef9f61fb25d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24384 zcmeFXbwHI{_dj^(M!JPVgLHRGNOvO~P5{zf(n<<aA|=w@El5hIG)Rgdf*>V=pfJzz z^4|B(%y(vf^UwS<ck6!E^Vw_dwbxoZ&we<l__mA&0uqWF+{1~-$mqd+9)4ayaUKC) zxRW@N@&6*BfH3}#(g{^xP;SexKp>(<;9VBT$oE2k5&;bp02C9*$OOou5Cas6Isd|F zUjT{@WT#ua4^W^!$PLIqeY8V>BKe^}2I`|70Tju90<ggdTTwAVgn)n;LKG%$D-PpF z2#E^|K*b~wP>84)KSBhCfJndvU?3q8VIhbFL_iEGir^Q915$7a0YNwv4u`{pU<e5j zA-J$0KSWSSR1^Y*Kty072sjKbCMb^JM@WbXLVyNAAgxl`FuuKxM_;VDCbX?GaBFQq zpn?q~=il^i1pbY{zY+L10{=$f-w6C0fqx_LZv_60z`qgrHv<1g;NJ-R|2_hL?nG{G zZW4h=0CyT7&_C`?L_i?CTA)Y{$iTe@UM@h9GB6-x0~wi+_a4Z566C!J>Mh>A#rU^) z?-nEPSCI0^dlVcXQ{Li(Tm0Y_e*-8wpicT0Bkx%N{iyN)Mb<wCD3Y&vi+=zV$=AKb z7XStHquSi!UjRk&eQxpZTby-^uWxbQUl<+G{YQT(K=FWAq;J4g73%CQM%s?VNWYML zTz~>~{@i9EF_4k*LF2yVBjW&Ibg^5EymtcR(I4O9JGcD*w1<o_QhxoGPX<uH1`G;- z0{)|u0~Dx_A$-e6#u1RmumLDw1UeN!k^E?Y0{3F*)Br{Dvu`oYUl<d4SB2zr02I)V z$pTQMewACyb&FL23h2X>2Po1%wOj0Ti;?{X=*LXI#iqCXLVzOeF}vj>#}X1--tvb4 ziqvOy%m03hAO3~0kotkxqT2!#un*e;ph){*e_@=8TMWO&Nc~7Y;ubgDVx%6RK2Fmu z_5vv256*jlBHQ=A#mI4j#K>_1_=C#<P@o=q06>xBA2|<zz9CHkC{Q0y7ob3Uqyqp2 z<nRn{@fJXV`grEIcpIQdd1U-ilt6#qcY=`jouI$&JGI~*c5qKH#KYFX%LC%!;N%a6 z!QpOTdx)n!Q10O3<qC#?Jsn)^oZx>7L23X;Qu0qdu=8KFy?g<o|78ulU_*O|7udm* z8w?PLi$B;2?gMw?20M6xeI1;fzz`S=?BeRe19f#lI5|MQfX2a&aDN^jh?6%Q>;`f0 z@B||~T%EzLE`Qks28{Hwhl5>#hX<h51qMdAIyt%e0*-+_;a*@@1lZFJ4s}2{K*1i~ zPJm;+_HY+u9S=BQIUK1T{FnOwH0lZFfOvvky}g_qT;MRUEgVqCi8TGTNe94&KWzX^ z4>;7@!_xt9!k-)LiL?c93>iisnl5lpFF@oro_{#V1_1%2#hwsn_+Re-<+?5Wub9IA zHW2XF1McGW9|g#|f1(cK1?xJ&A)as`R9^7En)HM_A$UCC2*4{BC^Bk)waN{4_4u!i zz}sH}aAXGn>VW!ypbN|iFw++f#NXB*PzU(u4EM5kh4F$w58o2bJGnw3PWG;z$UzJO zWiP4fY9iGtcz8K@dO1Kr_mO?*>gEjC%BSgIiyTXEPmsNrmz$>~A0MC!0(FD9@Va`~ z@wvIe`2I19KtP1O{Xl-=qSm6qf3)8|B>tkbi2oK^Am<K{C_+J?JIo3P3I};N3I}0@ z$hR<LE(|ODBk+HEAbt2pp0m97EEm9Y|0tWcWGAgdmLV&P3kpJE!s51Yeqk{vTmUL0 zE(V8-@B_E#2wUKvh^?)-ElgZQSVCAF0p;fxlYmJGiP;JWONa^z2?--$fC@2~AVg5q zRumY|f(TJT5itqi@*N=#6Nd@H1tf$71O-L-A#em#6bcm+fg)f45r#wf0U<#NxVSjr zwulIvUlb}T1``($hDbmKMWI4sFi{~GKMZbb%P%Yeg9-@?i6RhCTM<zhzo3wW1XLI* z2;~<;AcO@F;t-GkzkrB@fPgIo0kZ|3z-2!|7~l#3rij7>gc0IU7(@gr1`&`D1?t&~ z!l9z#5HXklTtG|&W-BfNc)`od_xFEqPY*sP2U}5LKICE|!si2bfw_8EBLN@8+sl;? z`8nm!gV)m)8O8r3VEOv9g(K<zWkT-bs2;AaUZDTa9)GWi|M&*P>*)dI^>A|rfq?VL z|7}8g@*e@DCJv<goc~y8|5ncdjBXxLVa~sN2NrMKe`X-0-rf!{5YQ;_%Aw@y?B)UY z^n}Ay9Gu|Vz!Krf`5zv#<h}#2WPAO$Oc&zs<O=yC?5PIvf$IV@+`;efD!1$ho~8jn z`$D|@yuv&JAYoonUj9E6`4r(5{PQj#%qs}`d#Zy4M8qY8#0AC0_>qPKCjY<6$o>TO zUF070=Q|m&SN-`;1?*9O&SA*&3$X9~pAwRf{b!T9{V4Z8e4j(w@=yK$V;j7--VRPM z9w4&(LLdmtRsxtH;(|i_A_(A1m?$v95u*H}wqg)LF$rNY2tOPq3J3jTNQ02w$qNhw z<k0>D{we{6_TL=~QUaDvpgaEJ2)Q})+6934Zs!T#f8YK#<$o;W7y57U-;?X_%72&q z!?yoH{-4}Glkxvv!GGuLUvi%Iz-GV)bA{fj{xAO9+5a8K8REhRWWaJB9YHXU9hk?> z!3_@PF@XSE2#*Qe!^71>2I1=A4DsUeL~f7{Uj9J28#m8=YZV=R!v`v6)&@F8`bx?& zK=r@MRW%<dDw&#ED;O9k0}?vg24Efp+z$$O^Kx)?@dSV;u#Y(MxOo739@NX#gU8d$ z!xh-*c<g{Z*wq8ZV+V!uc=|ecL6N5y9^|I)Df4%C0P~B_0}car=Uc)9_zZ&s`#TR5 zD)7G`4smgDwRHeaCP-;#QDNZW<?RBOu>*8?0A~vx1jN(d1!@o2<m&Co<L%<>-~!|E zg4iOD9RD#M>I9si{^J7R7Qlca{((VIHwRacUjRt(c25MhbfnEZfA&cpI~U}+@&73y z=f?k(kn;wa{>ewqQDpivhhPpsB!S5KgFw2-H6HBj3Ik3|z_G^L0}7lo;C@~pWla^Z zs4x$3WCUv(D1!ft6d2gm_7PB%8~oQ&#|`#@17{+@JpnL>fti6ZCpQ@8?*egl0L~G> zxd?E^5f0-9D<Y@pgZmbM5&<h2exd)hX$l;(ygVG-+~6>ff`QV52Y=ZGj0FS&c`5;x zPhf~Zfi)BW4lZuqfLZ^Q19R8~IJkkKe=oCO7{m+04R(h3{apwc&i&UR)5rz6mP!)R zx_eYhhd}ApM!Z3!$e)HFY;P|Fk2pv|LQq0j0xBVj06vZL+d^%H#KaLWIIy}1!+|A- zUsyy?+!i8k3v9ju0zv{pa8cyPc8E9x$`2eW#Qr+vAlJM<zjtqLe$@i+M8CxXOo4+u z?#C<qJX8=P3JFli+#1EbQLrv6X9c1nSVgxL{OOJN=wPN|+J*ze&)-I?cR`&0q>I%W z<2UW6#6)KsOK)r4y?`sDP0<(kjcrOl?AX~H9WW0V`d@lvkYx(SyVJjR!+er#A0(g? z@k{iFm%OK;@de-N<mh*LIO|L(w{uPWu0@Hav`RySW(8H}r-O(rti11-Xs@N9C<;@> zQ}`#+Uii}ktt|3#nlp3ZtnyKm*G?0K!{<@H0s-JDQ#er!QRlsATpv*^0TZ9}&Y_1y z&k2;RagL5(zLs0XADq56!5IALYt3T$qH{lUTKUcs*`DAh#%k}eDX}!nH6w__uT}#F zY>nYYYg*N8_Rk)!K~67F>)$nPoqw?T`HP|#TnlS^%fNb{+w<Cm;^8b=e7X&~xq|`E z9py^2-RQC7z7Z?}UM(*x9o45;(Mpu&ubd5we|VK@f2aC>^x)hrUd$?sg&>J>UGwpz z)k<mDWF?zah3sr{OsoKhFh=T>{2Bk53(8M61{!thh{v=!)@BBaa8Eo8A_yAcl*Qnn zT254-McmDdBeS#o@#j2eUBMl_hS^lYFv#3QZH#Df0__7r3L8F;;Vglor6;9lO&`2` zUovGi88b!~W5Zl^ITc6ZR1;sgxO4DN!A>LXNA=sdXxa{VbUyZiTlPqdmLWA_=Wg%1 zzrF2UwI-#ebPY+O6py8*EgTw!ImO<LCEQ0*$$2TrIjW1DH~Cr1F~=)$A)D4tXg~9= z{i`2lTxO3o3?~)(6c<uRTv{HeZ}KLU3k3TN5HQE^lxb6O3<^tM!Jn3DC))|$CAapD z-nQyuvw7+>#CgU(QXokjl%2%NKJPKTftnG0_vPWL!Mboftkg1A?YVe)<23G!*b8HV zG@R*zh(LP!r4aLM*{i`yh;0eAXWZLHKH<T7tLG$scIqr?dS;UDzp4o@T)yJxa9yw^ z>5oA;R!jX#9!Y<!6rh^^MU0;-xx&p`INqxzNH1g_eZ^@VB+wQ)3QK&-PpHFA!m?z` z87YDv!x5RqzV-INNo&&j-JH{=<v7b!j}gmpLqaLz`RR&tHHrY%2FS^PLV&x!+WriO zEGg6X$t!3{&ypTrW&OTAQTj*Ly{Ns|aiZV}+xW+)iFuz<2^WPV)d$4{^UNky2LfLt zH?@;X#cs76X&bwOYAMx)If@ui+gEQ)1-|q!J%pi|G(P(k&{aNRi>r9CZyTjC>sCOp zXCdEly!!|NmiI<qkak6!DtMQRXKA!@k<yhGmDc8bV-z2lpp2@@|A7;I8J6N`UR$a4 zwCuMiM#<+LsRhQG$OlgMWUk~?lVvfNM``)Z!15{4(oNm&%b$okGU^@)p})(|;H^9q zFcZPc<lm9DIc)a9V>;_9D=4{(ML_K8B(^gc;{tx^jsu^Mb(xa7#{7|vzZ_xxRI5o~ z?eU#yORu<`Rc5Q%=NN;mp9N1HI2@jkj>Pqk=~b}#HhmKk+qg=rTbCUx@$lDskNvez zNjvEAaQi);O#x}g^B3Q_haWFwoV^V>lnyJSx$Ni)i`D`K)w|erN?T!@nLLboG;lt( z>tH{J7D2)F`|j&Bj8R4@qeg9+yE;qQ8QL-08w;sjbrP{mP+5Iy;xCBho(6H%Qh~~S zvO{8nXtmRz+5!9vtaeRKM8d_a!gDmom0w~??w+WL#~OsQ_IXm$Ll+7`&uTte;jRd` zo0e~5tjzk$WaGzti01ZKe9e}2>2fFkdeZwvH~bTr-Gb!G*o%=$hIoDQ$6RlfxzyCq zOkt#T4~bM6<ID*+jzACozCluWyr`Mz7|uvjrnj?Yxi+)}+%*yM@MAC`yONfHBRVP@ z+VaD*4Z-@^yo0{-T5FQ&dMdY@2#+*&>|F;-A|B_(-sQ*McwqiVU%qy#e4ucqciN^2 zl=WVndP{aUH0Mh2iLB1p#XQM)$X)c67&JWoL*bt|#2wtq{KAet)+dgvdfX&61#%C+ z(pI+x^nnx@&G=`&a88$w*52IcyX(;rCi^)mBx@L5kbVl4V|u8y#L1Xauk!L+eCU+s zmx;DTU#9{wl_6Rkm7Ue=(Q7N?{_~BoL9F=??Y<Zv`pvhptF&vVdvn`@R|vA`iu-5C z%H~*pe$M5-Iic+1FmGA(ll7z?)DE+ry;wNPTAbZDMlq1tqjv(ck5GlW)HrG$KiV>5 zuxW#f&QSI0j8fVU2)$$|6Ol6;_;I5Vi7$0zzPeh!>DGnCrZsClEmTT$&K2A25fG@4 z&7Ma010k5ZMEJOqC-U8<Renx#drYA84}FnQ<A&9y1@e)y{vXnA;p5{!!>fB^n<%WC z&DY-S4c_l$vN_Uj)$pj_3=Y|M+kNkOlGeND{L75$nc=T{#hdZIm=j7QR1=nC53ly` zrIM~L*L1E?)w8{@?qw7?g%ud&-=l?CN&Q%(IQo#*;la<yMX+K$zC-^SyrO%5uTEc~ z7U$e}W@a==G|Ww`;PltP>6CCu8TJPtuBF|+1v&wgj6@?)r+y5&UKNrRpUcV1Ew%2x z(GMAOA+c6NWS0%picNmwy!Bm2IDyg?DHaU^WkrJty+$PK&U1@n%K>S+dph~2_IdZx z9tC_EUKO!}OC-W#DuWegOuy8*D-3Z_c3;I9t+jl}kGIFxp&lygX8h`M6ZN@d)6Kef zl=C-b#Gp$uJ4YcYXo<umv=2+VO!P!fj!}@_T)L%e?!1WSDxeKa3FA!+RybZN;F5Xe zOTs|ezw|pKK!mmKt2DW{LO#>9HV2<ZB#V-J+SZ#$<<Qh{u1o{=vzhUy{+8AsFSm$M zNaW_f{V+{n`XVGxS#Q^e6Kv5<(Elran<w7l9`TccgcL=EX0naFmfsINQ`0y6<R4G7 z>a1CrDlSRlcD}GJb2&TY<kATaRKlT;V#%RRtxtG!89W};e)`aY`emr57e>QKK=tI~ zPxVaXRml|Q{<!aG&rT{;X7gxZ;y08QCt1G_)Y<auSS?@k30Tj^UW&mmNJ_316n19l z=i0-mdoI2UTPM*#qwd;viTUeBKV5KM_3nIe+)9<%UE?Cld#F%6Z;&u?PEB1enDFVf zA;CkRQwh|-mOuxVB1~x`cH?$>5n)L@6h>#CNN_vxicVlor~TCygI}~V`0FCRZ-#P^ zbNG^-jIwF>PwmTJYIb;EtH}fX!pXB>OmA^-BsBwKwpF<yi|C-Wz3plyi7K@!$8D2R z#p{$)Owz@Bvo$wl+>7~&onNRw2{t5~`!$he-<!}5B{jWswrwDta@ly}gF6SI9xnUd z<hp0^1%Ha`i&}+mB_Y}408@6B=U!403d{=QPj`G|f7~d3(%>dmCF8qalx^2LZTa@P z^NqoVBZ2j4=Q#(79xMvcoStxl5)|o9$aCX$?eT6|-Xv1m*4ra?VWz=VT((jroqsds z)u)nvg^;GP)Az4I^lMBNwU0Z(R)07v-4tZ*kP=E^;<*zP2!4zvVS1!x*uVgJ6Lh7z zSk|)Cj}?R4yE~p(mD;CAAUVIc-A_1iqi0O@!+x>SwYW;cYT5ooqw!O?Fh0Ym2%(%i zV)dQDYGr}o@(CFST6V|G57{<HjvwfkuU4!FRLBK#uHQ`#vD5Oy3w&Vuu-!(%H2S=D zTv!S}yL1Hokc+888F})W7h}skBOy=5z9V(#xYZKMZmHnONAHQFObawA3Qm`bv_gpq z80d*yKQ(VNz%f&-r8!J;<r&q;Hf$tNu!ZQu^h&{w^O(%CWjCP1qugOBH7**{a%|rg z4>l~#Ia4}K@{0AE$w;aPM^Cp;)%zCph@!76%~z7EjXu76>w0!qYoU1RV|culmvD#1 zS{d&0dM5r(j9s1-UC*bVdWz-5?Rb5)*j_nDg;?}%cM>vsmCcT-L-nIQbOW(!w+@ZX z39HK2x=}Cvr=B`*Nb?^VI$wTn*bNiS>0HCp_9djb|Ehp*iCZc`+aYeRT)^<f<3!OQ zA7ZhX3DYDTblyc`&4(T{gi4AO2xioC$_eycTv=uXA&g+(`4&b(B5Ad&!`Z0mybtDu z`sLv$^p`E>4inw?_$(jF1kS94#eWZX8|bv|+YJggCX*=fS7YvY<5@B>h5r7`SNtUd z7+?S6LnY;rIKCEXe*?MGbOF_pu~*-$w>BwJAmXX<l0JoeRutLJ%KQ3iigI8Tm9y5D zcP)t3%w?JsNEz+8@n@-<dZbBtgFKDqLuBZdR7kQc=06bRJT4?-P16gfM~LOE1&=tg zOP<nBEbLSh_+eIRI+cnNJ;t86Sc<4>cJ%8JBdILAdQ-K%w`5o*zfJ`H+}r7QN|6bd zQds(|r5{oh-NwEnYbWUwZ!48glzrOpjo5ITv*NoLy6USuJ4OYT@iEPsecJ<C8?$>& zdY301W`blroki5?Dw((tT;_PyN$}tXnYp@()Emqsy{V`{+f;K=`DfoQ(dW?b>q2u0 z_w<*p19WQNzV5~m*TkW&Yf5i?_wA;;Obyk$?=t>@-;)tTrms&qbqM2%#Ohssvq2w( z<cew=HYLud_^RWH5Jchx@y?HCvkxs0p#^^&u5-@Mmx(qi9Aw+k6w)#B!!f_t|9&{u zTgYWUX`H-*Ty{H2drkUW0ypAm{gWW$<7h8w^>BIyUiS4=E`-C0=<uAG)<~mm{KZnj zLu{W9tjVu#_}JG?U7ihM9umKH&B(PFk+Wi3ILfy%)(N-2$)65qdox+F^g-u@3o|E* zxeRYS;i`OWMPg#A|D?f{q3cmcYthdzT*9T(^O`oIZVFaLrucO-)DpS7CI;VB*tzwU zf;#-a?B=m$oXmL#aA}jfFQa=vpZ4CHetG7a)2*3e%kS^&z@aWlphjsjEIXW3V)@d! z%L1pvwq|}K-RrC6mDJf(wR1Y72j;6tAN34E)f}+im#Y_Fbv>q&4tmaxzcpLrEr&@} zOxiHJWcYyc;@@=h+PTNZYZK_k$+0nVYf&+KWGx(^gIdvO_OQp4$`^S>LQZhW2L_yN zt?+6bxUcz|E`2nOnvdz(cCdAqA>0MhY`*#pk7!FW7*K5MO<6Hb-K+dxacfx)<-Rfq z`t3wY-7CI9M3DH?HDC03#)#oI+Gp?Rtvs06#n67Sp=`tF>!dE-QPQL*qd%Ow!=5#u z(fJqZn9j7R@veAuR*`eH1q+(9zK?uxhqq2u`(W0CywUT4NM&!koCu~-lBVxNOxwPv zYiOZob^~!`@%i5qy3x^yeXq2shPpoVvhsI7xHknCf1WoWJ{Hm!F8lq6uIl}GwpxLX z@|15MwsOgg5I8D-N<ckaHs_9b>uZ59oFVmggJ#Q()TV}4%td@ZrH-FO`mH=?d-<wu zO5-if0AV?^w}8`fkSKS0gL__MDyulb=dBKjjdtv>`)?iv1Q}}z(FUl+YUg;&ITlj? zMnj8{%t`+0_@oopZ{6{epHOQoWJ;1#C91pn&Y4cJgn&*j`g?&q(Pg^Yo~RcT?(xDy z2FqJjUYEpIWsBJIgBh9|-ob0c=%X8X&liaLO=lg!dZP!vyXE`W-d++6HTNZyt-iAx zt7Zf<{=nsEeD4~$Z>KmhwV_-PO0h89xK%BP{^V774q<+-cyIH4-=1kYYgwx32Kj#U zpDw>$<t@_IR0`EUDPxuItEvA&>onj?NXGwcmhJiSb-TJ7yfr?3x@i2xyb-?V;&)uA z*Tdf_eNsM(8d@83L{lf6pcj?z^*2>hG2_eOrmCu9_bHp{MDE=e=wXr~#`XREj4Vux zSN}zmtjD{<Mmpn5?!>QFPZ{QU+=6pNO~WR(zYBGJ-YUh=Ry*D1oR2USkUQ+FUJf+d z7x$tCyLdeudDZfoJ}Cy}wY(l$8YcJCd)GA61>%p~EfAs>*vg=vU(Z{WHYbz(t(bHA zvsXPN7kMl`zcVQ`p}=JPWU${s?G=j6&gPBrnN8P!%MH#p%e+nUYO<iZMK19U(*gdu zHT(sRF()ZpeK7V}17&CWVKnU`@Y77NJu!ysh*$2e0JFmnN=@5RlzHdDqWWBym!w|B zV}-;;hf)KSkQ+QJpI@cLC3{pRG9OL4kEgjX8zr9LV4g41V;~k)-Jq1&8;Y!W9_)y8 zE?xeib`fHB*f7|jqCiTi#O)-0#H}6Al)Fv{|J2DRad-54WyM#{4E1Cdx(Q>A<6(>G zq2_P6>2H?2RZNZ)^Df`i89jOT$>cem0$T_qY+<}GAi887nskKmvqAFEm3BI`qL$vN z<+ql1Z)}95qL{5zi>=W5*CHm`p%=dDU1)bYHHvG*N$e!$*O?z)dAyM@z=XJP1*1w0 zWDV!dy9jIlw0Jg)H(1ocLdkKlypW)N*L#E0S$#*!BF<ZpYLB=gbD`zgmdr!px3!nU zg)if|ad~DR8&=+(uFIas2ubXoeZ4Knz{>HH&+YJv@e%nZ{a}=jDExCIjvZlDsHF-f z@z9);=Zr}G0Zpfqv{4wYk|4`+d*x-m4BcJwNBSBCkvvkbO#*CXpMQ)@p*Z)5zF$J# z!*!G?*{*5DS?5$u_fB3zMyK5`^+nnJ$ailEbUrPLv!Rmkj%Gc7&gmIvq`Bl!jQU{? zb-%I0oHtV8cUsN<dPTWp?PGy!bv|f51{yp1G6~zqipN~pxzKmb(e`pL+P_cKIj_<M zMo4*0p;{y&Xp1i+(`??Jc%rAMj<{3sWCkp2x>1_&e@EZeq1z|@fwpS9-an#;uU>60 zaCR<xPyTpLZNdocg||%yWov*Os8hF6yqA@$$H%i%&$3hE_}nLSp!~^MY=UmVg}MoW zAPa7Vfq`}zeIwJrP^GTibyqcSGfP-vd)n2xh8imVdpS1}(a@*loSpYxCJxayy$%2M zde<XzcCmFW)Mam+z9Bh%(>2lEqgb?tmrF@*$$qi8nU8gMS1Wj3l-qg<wS6R3a+1bo za8Mt5!$nQ+5KjJx25(Y@H>SwS?qb;s-D)%HjRKYyfz*d>!w>kBuI`JiggLQJlc~G( z6MJpm_zawdJUw`=ojzQ1g8EsAa3=GI#P#<DTHmF#^^o1*ze*$9M48cR(lb6ODr%^b z3Yz(u_s~&h{OR!bjWLEIdOnkw!j#^|8ljr9?tLMbk&@R0GA_NZUnSXleU2zTALoUG zPl~<V52fTIB85StQ57gZzu+k47T@(TI^1e$-ii@_B`C3^apgCw<+BrdhfUwU;X#_C zREfZW(XR4!)ok4!w-}Fje3n+rk5>8TJ(!M6gjmp1vLr6|j{Dc7hY!Oa)7Yk98*xW` zjA1drrV!GE`wucV_tVH(8TM5~FmJqAUHS#KXETwQQlY~*yzKOgo^WAnf=KgHss+x^ zI;bRPSiQR9(Jp#=?9T8FKX!}$A@!C%Q>E?Gt@-X1?|kk$hh^!d)dIpI!m<aHk2g^b z9SV-7{C%!`YobEwot!kB2SM&{gtc;IdAw|3=x^~@j!ST&@QkLgZGy|N+nFr0;`VjD zG27KXSrF%3mZFZWxOG1o_*y?EskAeZyQCJeZ6rZNvA#0ib@!|||C;I0%lb2_Fh@DV zggyUVO71JgQ<Ii%GV-(+IhYOg7e9Vkr1-@IRu2pw5Gt8Tb0k`j`V>alu#+?^d1p<W zNJm`AdZ}!EOPO)3BcDMvEYE%>9>2VzAx@R9@P+EmitPczFK}lYYJH)O5o$2BUyx*; zK|nopWU|{XW{1d$zADJ|p^j=+&hrL?l2J34juoa+U12-PB}SWStG!0S%d+Z-U6jB^ zH9WP^H-#;l=U>y$@5)U(PbJB`3YsANDF5oA$Z<Q4f8eF??vFcyvlVVZKC=P2`4PUZ z7HWK({Tb6%rNbQv%{%FVjfD?oj*3}GJ5oj6h;rR^)FORnM11u>Qb;U*b6W^@bpGDK zOnAdH<l>E)NZ7gkcIu<lD|=r#mM*Q@-oilE;NI-7hF*dcy0*=mkOlBlU3mJXLs^gv z#EAHD=p?yMvAy~H1IEMF)b~8kBF9YHH{Y=FEaM6yItqHJ3BNpC`N#(O)pB66tzsp~ z879wbyiD31M2uE<k|dIvY!FJ`DrS4ZBkLTgu>OYiVe?Pam-6KqCTD@XDU2^OW(BM( z>TaCiV<gEX?~ar96~28=V?imNadVMpo_6x5XZZ3v;OJgj=aWrlgQYw4)TJ4v+9CD0 zfg*1nhhVY`7C(xKRkBX|Dgmny{F*3tEaS?V+g73?-Mwy4vrrXne;1mfzPn0oWj4Y} z7!aew?c!0K_Oe;}PTQOzD%^Hv-Oyw7gaD1E@vi9Nu*|Z1aImMI5-xV#*?03@=FYNn zyYwUf_nC{itZK^S0wmAwiPXO@nCjW5>urzP7sdRwNajzm`$~s!Xr~aTK3%uYKVwHb zB(lf&dnX~)UKO``1<~x>^p;73&)s3#u_sviM*|fk?Q@Ib-m6>7A8n(4o(qV2-kFy? zaaR8-E>%NQm93~f`<W^PSI&md7ILGcWzRIL!Tjl1{%ONn*we{)S`=Q}kb%`JJDwM5 zUr?KqskfGI1k};gm&l(v5U=HhK9F$ykj9?4He7YK#kRSgJC+ssy&aSFI8`T8g}UMm zgyae9=Qh%>m<>b<rDF7%UZz=S3#?G}3&aYI_|q{bqR4MX;3$ep{X<FEC-3g$4k~*k zW`2*_T7%5$vu=bClZ52GNG`87H3~NYZ8JF&(yYOy7n>b#ztak2CcN_TGRq!;f|<Sr zpR=%;HUxxk5ff&Ui^yutXcEN?6m-#LW!l~k34WqRBV3XrTkLgqe|U(Uc|tj_$rX;J zHR`8XKZ2LwP(^gtO`Gp6^Hy--qj|J-BOSK=_lP-)+}fkVr$j4_PaUun8P40AQdPFl zMLa)72wsSy=2?7!a-@3DRG5hRk5Th})&1f#<-6^6evcS8`qb5)&5E>9D$&n|6Y8#@ z@6*8u4ZY~3-XCWwwi`bj(4>>98Ud^7FV(_+vD6&qb+UXD>W|o!DAt&E4%HJ^wI}*~ zO!BsTspbP^+hCb;=5?Kz?8aW0e14?jC9cA9=t+<s{Wit+O{w83H;4S$Sgq$S!Pk2# z^Y6z{7%p!(6rd=_?f$x|8r^-Q9H%P!{im@k+#Tgo*Ll>9N2PsJ0+hnZRl8rUg++B- zvuI4mpVX(+vM|3j2nc-AZH6vcbgdq4Elz~h6D}pNaT#oDQu;RR+0Wdr08`7ntY^4# z;u#j-@($W{cGj!kIZ)JR=zm!bk6d3}7B)7aQME|A#%_9I(j#xojh@jnB7z=Sz_l*7 z{Qd&XNpUU5;k76fd?)<fD>ih~=IgaKIX-)~`S@H_rL)Di7sP8@<Cn>J`SlkoYhT;D z$)&N41fvZdD6h6>p7OtD@FUy~wY*RTUrrW=ec(P=+#dMM=eSN<=s1CSMmFD(Q&za< z>EB7qja#N4iud{8dx!;-#o@w}@Vx7uS5mY(^(YWpc}l6-6GLUMp?S<{*!teL2?IM8 zP+-*_7iDb)L+_8ZqBCRp*O1ioCmL9CYZuS8U?c(G5-@n>944Pb2@B&jTpLvU?hUQI zw`?__VQ7aOC7elxw=tH~WbrvkSH+c-Yvx+3-qUX?37YzC^7s+CeV$JK7o|nn0{@GV z=l=U6H~OudSN_Tyy0jspM|a7%T8I<6#$s&vr0!FtsK4R7_Bg!r8p}*Gted8A`O&4k zG_K~3!`01;rmAU^qMPjc1xvgo`W)5Z3X$HRxq0jPu{@Oz)}$>dP*Vb%yXF0t!YD5F zNwqo|ZF=}o>o40$`^ORWyw}Gv5ezuxkbYvCj@&e56?}D*+PUJ{iz6Cqnb@gjd<+7S zI_ECqnjL<jl8Su#oAVEugqf}kgRqp?($u82H#*Nlwka~{$P_1B%7`@OzLE$H7fppf z?3@ds$|)@3KiDXmi;kee-jVmuHJ+?Mx4qA~w~sw_h$zlk`-)SQ3W~DEFkqzmoXTnp z?%8@m-z?P_92#ykf>QWp&i@H!>+0<KyVevn?=H>#r|KY^M?d<&71_!ZUE&`O0`vMx z1o>9=SX?ffYkz(s*zMhsy2x*9HZ&pC_g3^e0y))<EaG<WVDQy9wtSj#Qfji+X&e|C zo)Zi_5V)4AKz}yTU)#ADOjC~4Z7^%r^J8Z`GT)C*v4imqqNM`#P?Q2~XGJvSaY>Sp z%CB#k)+BxYBsW67&s1S?8uio6j|oQDfuDnxAR>`Sw1mBQnS*s(HR8{xcz8pdI`7=# zpGADwkp7hYHd&t6Xd%YJTWM7N;FI|4Al)tQjXq|&;Uz0&w%{-1sdZOW^%tP{?$FIi zuEX3IOlH%BhShbErbSA@xriPDH<k+}CN$8|;3P&_twKwJsy)=Icgtzl@*^*07&|jA zDi59iOKQ}VL@Bgr{!<t9(G<JiU6EG>n>3G@U$>1$n%1v*1+e|rwmY%YDR830?+MHf z>fTCdMN^RcsA=!$vc)(W@6DA*FH#)uNq-pn#gEa(+ueJ|mb$I>*K=U?dn>zZtoAhC zM4}DE7ciapuAk(D#TTNW&ycyQ6O&H*(+!`itw`$?=F>Y#T3M*vziB4|3^mplGRJcA z%lp|Rz21tUqE$~6ao!zNGhiy4xLfEH*>@P8Ntdl6>i!mg?TXFlX7_aZw5?|^_2orY zCR<;OtBlMGR>tyv({~6P6~k2%we~t@fyR)f)%#c6kJfcIlDcEj*;0cHY0Q^>MB|33 z;Ex7N5_}t%>o=BsB`XyRKE}mx35)MgjAmb!)iGk^@hCuNrWSfCPJ`~&e}}i0_0&*> z#5Zpy_xZVU5e&@O+!T~19=Vr{Ijbg~8ctbZW}iBaB$9r+Tcq|Ta_Dm@cvLcqVlK)j zkc4(a>TPmrqrmUW&hv{YY2WD=7m1nMtu?t%qKZR^%VFHCbRt+(kA17+p8nDkeUOi7 zs$H;cRYJ45M@+1NTGQssW#esFOzcr8Pu%Y%jWh8mC-dl~+<f{@99@u}#re!cdHBb@ z5amf&Q5n?0=@rk<*COa*PpRVv(Ld;=KJ=7noi+}^#w+c+$JJ78p&dhXrf$|1YUvU4 zQsSVwkQL|3=}P`x`($o&2x8--)t-ztlPr_<vFFB|_s(+-ck&<PnJxQ!9M>yn45^>6 zj;SP;?yhKfbT7YgYeaNAfyav~uR>*u(K-5kDyjw>SQ6CmuELUyPaDiJp1fy}qu6w> z#OMCj@C0t<@AeW+9YgjZS`%;8cX9$%##EBCtNz+h^H96N=LZr6C%(La^R2T(E^X(- z<39}aDK1}|>E)_rQ8Wj6F`$V4!hRy5-I?Qe{lo1UL?%uVWp@)1OqmhE%)a9CYP+<K zv#b0?%VI~gf))0-yeA&FtvHSZZ({7Eb<O+!T%1JTSa`$@9b6`5j3af;HuzAX@KF$l z*K2Z*kz<j<9Zx)WERAVtF~jwYx{%NUMh|p`aXO=#2zTb;56=+E_MxMhv2NeNqly_6 zcss+BVS#k_nYcMs-MDS5iyaJ<P|OF_*|s4&1xp(^QRo>Mql82%0{y7JN7`8FgHr}; z3FdtI0}NP>tE!U)`(nNxt_)^DG;%)$z4#E)^H#AIREP-wIOI|ZQMxP=ccc7%pQPJ- z50ry>$<ISx6o79o^TG0-RFUkD#t$0Rfp1n^`0Pc;UrthNX>6A*pu({DGX~ERAajw6 zJY#YC;k-}l#WiW7{B?t|{ES=n*n`0_mPd@4IKG&Ss%mm4SdmoDg)d-unq_Yi#((6# zhw^=%8rzv``C6RIeAE?xD$&FI`PbuW{#?()dwwkUW9jFI0>RH-GFz1A`p}7ENZ|G{ zNPW3{MbCf;*$l^?jIY`==>Uld5x!|*SHqpX3HBK}`+oM@r1UtusBItyLR(-Fwpy-V zgZa(V7DwOLgqyg--Blig+;u{dM8GmrI#m(4>=8=%lrsR6@!fMY{^me^TI-Itx`($B z@0<0q<Tn+=6W(NJ&LA6W{N-la51di$xNLk5X=k_<TSf09o=_`Drtp#OV+YL%&OC@$ zdi2}e7(ZK9<7IlesE0Qzi)E@ZIZ?=oK~0L#?_NC$G#3#^n)*R|4IC)b`K!emO&o=l zv@+PYZvmHhR`n-q`h<_lE6AJ#w-PK65RE!7-0KLXhUdv7S4VCa&pz_HOpEKk32Qb@ zjA@Ap-G3DoyOCxeN$so<(p%`LTkyb<_PlVLE6|?OTt=d>{C!`t2FunxiWfTS7CHFf zw}ElPpR9edP7k?z-X@{%KTA7sd^#uFbmG$%^^TF3EnSy&L8s<!Sc^R@?V&W8*=}*A z+V3ih76!qPK11)qwaaVYD`-4O;usw9+)JB-tknt2+raT=A#pl2VPi)GtRc1fAf=O% zNUjNA(I!Sa1}$vT3~Qt0u{?{`&<QSWx`qUE!fK+K7|5Zc6X(d|Em!B>5|J&H;x<Kl z(f0(-2iRmM*Jw`P#T-6Glm3iZa_PGJ7{q2_zS)YU#s?BA_9n6!6EwE2@VUG3vIg`d z(;P2z=5w9w$-0xsi|Nddmb#|_9k#g3?WDsKxLemI(U2xt{FhQCS=m1WxtVZZ;O^6Q zAe^p(4#yb8(b2<2P+NF*h2g#-3_TKMQu@rkWjb%h*X9VPKB&JC%+<hK=8arrYfV{1 z&Ee0SHqG*ztLncgHcRzk>H4Gy$(ZP3(W5dL>?98kq~0ByZuk{1^vm;OPOtZvn)DC> zo4{kDWUQfHx6>*)1%{npr-WLxhi8Xk(W$3gcUnm(`RH}03=m5$(ZAJj!?}%r{&a&T z(Es{|z+?XHN9M~oC9rO@CFMS_&Y5;sr6hVWjH>>zU7P@q>9YYyZcmY-W@D<=G4|AA z#rvPJ5=J*{zuC%jWA<!n>4uH3cyuuX7Alf$t19A6*HuY|Kp92U{T+RG6sHb!N_QPQ ziYx0CE!Y)uNwAaSXoYz*m0q{_<+lo)t*FD~R0+5O!;{hvT&f=&kgNAkg#UV4(?3(` z%r_b{HgCf~$fJ$@X{|QAg`{XB;nOS0jfc9LXeKHF7aOr2zw=%Tq>n6N8m};F2yctL z|LxY2!QY*=Q1$i8Mtp~db}xm%FeIEuqrcKCLYcCV`^kH~aO2=r`yeV_fhHjoiI!L6 z1f3l%zs{dresNDgF?MkDq1CX|<&Inbm0sxgjamoofE2^f!nLseMDkbArm&Qr=-W!} zloX0-xfKO?8V<{IbpsQ>mqMLSPifVE#nc3elgwW|I5}++O_1KqeJQe|neWZ8Tl!q_ zdqZz4_IrQl0<k&c3=2N2$Z)Z}3wpB$1JJ0}!iQ`MdS{O=eWQtwZP2(`Kz(Sk^NOvt z8Ts8lZ-jp&VlvU&Vbmpa-U+x?SY^N;v&dN)UGyC4=qwt)%(8G1IceykU`D*e#eWiq zGZ|uym)575B=B7If*QOh@H0#J`&S$8(8;z(&6mdG-Bc4g-(uRWa_(P?H)niYDAKMX zUEO+g=gjwNCY9vP*7zQLA7#n?rL{EIv4Rzs+Nl!tvxqvq4yj5d^h~PLhMH0LO_$%; zteIoy9VJEOh?2tI_OozUp>fSMjndm|v|8J7O!wI>(e{0bFAxWmPKb%+09%b*4+qUR z2RW$-)$ec-RIDWJ;17YUhtXk%W2Gt*0ZhxyMLXX03(fV(T$&2}5kXrL`!IUkU!waj z>jzmT&)tYHJPF?99{PNzigTAuVZMjSSE}u|6XxSq%PsarUkJD81L(JR<8wuuJQ^R@ zFKT0O9GM(pg}m`evvma)Jn6i-Y9huT*<+{IZ_xbtQ?@AKSS6TutytqcaO5+1xs{30 zy!(?%dAZ15T7rcrwP3cjyfUg*7)k?4#5w18M;17j_p;PKlE&cB&ZeBMqjpgdvWws| z(50yJ{~Ac*d!#qYk!k(|)}?p%)Q4ml-aaF7&$h&sKoy-<@s-1HO#b;tFArX$pYH0h znsnq(igW1Iq<8Hf4pnU9V3{@sZS2HkO3&`PH-JV`l}oY=h&I8dhRdS<Ue*$e@7Og2 zM|ep&dtBTrhy2Mg4l^p(?l#v@%xU|t*>uX{Nt9l$E=VL{@mpSdc778Ox+@j?Q%;mb zV!#O}+&Yo7P}O_%dmALLaD%cSFs8%9&h4H_6bF5-_+gA{b&(~dDqLn<Y4!3m^$HhQ zDPv)gO4EDXqFChv`FjNx&q0pkag;;Dz=AhK66Ie|2@L6!<-Ww}lVHLW4%47C96tJ` zWS1SV{x<ySg+SBx_-FMcX!O58X8t3yh@^|$4tZYIEK_3|hLM`0**lFfBwY)9wp6rD z4HA{|&iI|-&oBz;DcIPOGuXIn)|coy%CCLC<jlWI<2u4Si#3gX$>UAj7G+VVRnfC` z-zXEu2hTyBI#9B4(R}khTM+Eig`mtp{^dx?KIeos50L_{bLnu6Pt3jHdgC2!&atTH z_v4jqNDrd(cFC$Z!O<BsPWcLNCm~j)j9F>c8@Y!kJBlqonXu})yFPIJQktLTpNQ2p zh+3DI6Iv@>c#qDq)R(p!i^6AdYU;SoXP*4nRL-Hejf*KCwj5TDq3&Q6gl*uU!4{4l zlgIx-OEXlxDK^0JI)@I8T>1TP%!;pcS4H;s!>KC{9o$Niog@sMn=$7KvWG|g&6A(z zxzVO)c@4a`h}sQv2v=t6VW=MVRh+5nc%vP6eW#})CFCughrFmTwXs$BK@Ky;^MUxc z+IuMBpIv^}yRMmqsNQ>ab>*KHkKwPmaj1MHDN<RD4tJKFCHi<rECToUH;5wql1gzs zfFO5O^<F;2*2)czLQdZI>7899Gea92`7nN-UOU1r!_VvULlWjCw~Q?z|D-a{oRQMF zl{Yk+3SPMuzn$rODUZKqJt9YVGr7n9>Mmy(U7L)<cVPP6y~W4C-155J#+m+0GVxOT zW>kFgj=+-S8}YgIpt>?Aae|*MWcSI-MxGzQ52)5pSY6YkE2~S&ZD`|i-rkQ;d5%wf z=IivK9gk4XB@MUwM{O^Uw2$RE;#H}A`ePUiQ5HQZDs$DtK4!SZ*M7}sT4q-!>qDl; zy^SP{Ichn=S+CdYBZAvS>xELAhJ!Fyp~vxXeg}e<u*=!v_bD(md8Ix}S>rl&%#m-H zZ`xD%xauPAmk(~-Jq#Q&3fo$|L(XZ0+8>%x6d{{TSWBcAmsB_P(RktAhU!fvTW>N* zo@jCYHy5);xqNDOwD#f-3#u3MXVK^~^9DRsItKh|%|$->%x^3vPiS9}u{|8%=qn_@ zUv`K4MFuIG52o<*{hQKZ$F>8H(rlNg*QR#(LdnHOg;};)uO>SqW-_g)=e~UPZO)3* zTu^tE$#sZVGt*i8re^n;1OcH>cSU&%9Jqekx5JupjEH_$7QEZ+`S1ygHf+cFkz#dC zR?@I*MF9Jqkgq?)$!GLTKlvzop5A9ep$k;bo?eZ6kiiiiiWO7PK||DE?UpV7bbG;Q zl&;5KoOrJv-&pqNF~K-fNukDzNm9qm;Eosa#_}GY1VaJ`Od+^2u~evicjhKhSkP-p zP>Dmr*K)6_+vyv(<j{8$g@XEwTEDWS^~?--J1E8{yxDPp1shw%_d0FMIJvlw?TMnd zWBzz2Excvj)c?e-u_JQ_-nP&|E|D&>_u8x=@rkc_<d;0dCarlzZM5JZcv+2%-j@nX zd8jw1Pd6P~TE8MFRryXK5mdYyR#pcgXd_9|@Qex#8`a?nl`1WmUeJv`g>u~NW7I4N z1mI$jY@GEy^hf*JK}~kKqO#Ykr0*fUrt$+#a<K6ypF7uawc(fnzRL`2<m1AY%3%ys z_qJ#PHlb1VZTW2oz4M?>oLnuUmz#y4x~4S!Knvb%`YvDdEO>;HwEpN38TFmQ6Pk|) zM>dP^Us!L^^mc_!XiP6E_@%WfKu5-jcVcz#{ZQ%mILd$Wg`LW1(XYEDQ$SfqbOTc! znoDiR&@|^cO<hr`$IMomm|%b6`FI)<79YVru(D=X-CFx{IP6if-7`U<u()61?}$$n zCN}H1&F}*_c53Lp(Wlr{i+mO3<-u{+Yhl`99K-G2-3YWSI{4Yn8CS1xB8|pU{d++$ z4lm(V`qCZg03D1o(Kce^=aqQxTOzy`28XM_cJs3@iQH<*P#<l6$&fXrGIt-^O^6|h zd+WSv7jSg;`{Hp6bNYz*pf8SJsE7^%7wcQOk5tcarW2{CE~K)x@#SorYbFu#{aVuF zHf+_N=8B6|>2Jo$pXkm;WY8o?#wRs2-tY$5Ub<UZQ%+!82cMmRdRy<St2SgzA5Ldw zOI`cn$?=6`5xG9@D|YY;N64Q_FYbItk7l96%hxV;!IAEfF^~pVQ<LG`y~4gKX5NpS z5Jd1{mzn-pT9knB^*T9pEH8TTw2Yyrso6c+biI^}$xnTjozvVx(Ka(;&9eS1efaf6 zg?4%(eXI4W!QfJDZ{1~>v-vE7d0yvz=(W{Vg+XE~7m1v_t$g!5%6H4&V<@*TT3y() zFKQ?yryt3Jf`jHSe%YbFeTw#mfBBMXb7?#@Y&q*$MVW)j+OtlZ8yfN^bBaB;c^BtY zsFlQ66m9zFn{%`y3jI*xpJQNj)@#<SV#)Wy7r4&HKS+zQ#9O?56$lK3xakLYv?-Vy zN?plo`!pjYn6&96_a&t$d!wt{9hD~b1{>>qPiZcQd@!t-g>?LmqW!OHYYO|=h}@yf zk{l>z6HRyDRfe=~IB~Z(bv>I`qaJQ&i;GXwD9GTCM~zusKQCp!Z=<GLSmRWed8f&F z*QI!~-1+Vd>a!OP_(CA+D9aq7=J2D8k8S1i83-N`UB?H*2<f#~Q_9d+XAv~^Q#*a) z&+U_|3nHzZuJEVS`7Mf#t?)d(c!S<ke)w3y;huP>%n7~jx>u4YG7J)rx0gkBV*Rqa zM=iPx=Q_|J;Pl|Z=ga~H^WH2HoGFFEE+IB83>4~iCv8413MJy2_>lD`sNJ)e1adVL zxXhJw4d>S<12U;AJmYJk`-ds*<1puYEE8kS8+W9uw3>(om+ts?gTGy4dVwvE@46l} zt*}L-<7?o*PQQ$neb#~glGiMjBY5~|TqD<ewG#UQW=As7Blf|EwDajBw5~fTeC$3& zuNf?Us?C0SCZkTBP%-KD;QZ;f#{om=AtU2&<NL`~DJJvB<f}^URTa;3mv`;Y^S2oe zjrKHWvNbCoi-?W|S<o}H)Obf-5kS9aqpQo3vR@C9+Dw&seD-i_rd>mMcZkkdJ-@$d z_RR_9w=-8M`$V`xvmyC`VYqfoUL^O#Jhf|ot}{hh<2tL4eiuEhMccQ*1!Kj_uk~Gr z{;pb~fl)1~WP!f3jxW6zQ1Qy2PF%~#t6jVBV90*-RQk03>e1)O*Tr08W+{R>Ox(j^ zyie+8)4uP1_2eTJ*tDA78_{Oa#M-s@tG5*=D++wRJU+wG8a$+l;c*l_&l$V?Sg1=| zxD!O#gBsNcoxbWLZlq0^KLrPKyx#mC@gmVWxy=XK&&n&lJ2Q^UtK%B*^HV_PsNg9r zekQ~&P%#)4aWL8e{;Vcb7D4BWxou*Qyk>ibdq^98;lQD%dpSa^<o;M|&Q|4h{h`@! z6{#cxO0md~x%MHfUq8A96sYy8>9_h^6StpU&(}TwUMu+m;(_<};7hL+=ldqP^=hVr zX`QAu`WF1xQEJl@XGVb^gf3-Q%mc4gTiYuM?1NJmP*z-}zNhbZi_%lDi<s#LI0VID zJA^DhJrbZf`<R~A^rFk`WFg4H;NDw8ii8!1NtLl7t#jFQkvjD07sT!~_xBBxjL0Y( z2v#@W-1U-m4^6B1G#>Ut1~&6qLfs_sm|#WQ38v+rJpOj(poh3=7k2rQP*77;rLh$X zk(=8ieGs2Tp_TGHe>(9bOqoX4)8tCg@KxdR_lJ);SE{1S+WlROtRLIkcO=nXq#vuY zw0i%vZY!)D3$7eJ-c*QFUCOB8M>l*{J8qKlP1Ephamcd9_ny0+6XlVmWvGH6CAUe2 zs>0YXcZrHE&a^;YNQN)-xo*}uGbd?+&(-qUbqp;@$9=Bw@PRsva}yC$)>)_f+LYok z*)*wOVM{3yM8|SQsq_;0ydkeVH=~m+jsT+k-DAu~H#zsR!{N-k-7h?cW*W~e<u!Kr z$_Dw4Sv4Lkexo{EJ=k+ocKmrbD=fmz%~cyQomlOMzGht~o{YnF5NcRBl#6eX5<KUi zQM_vEDD_nGSHJvtJ}<^|qm4&RR1HRnkOn$QQ`&6yv*NZ>mqumpGMCa3j8olO2C?2@ zD;T^UbZX=}5fN{8qfPDb(F25^Y$e^|fAIZ90qUp>eroQ*V$Q_`CQ~FDQ-f&F!#JAs zUnCW&?z~^io)-({RQNjEH2Fs97IxUMslIvoPTCA#o+z7E&n4GIUv)oQwfCrK9luMG zr~FyG*_R(I1M&|bo8x6OFAl&zu3pm7H=8<K5~X4BJIOqXQ9A4%?5jL~ebZL5ggTi_ zrf_Md0eg;GOq<TCEG}~L#Rx-8Z8i1=H!v;b8!Bv=HbDOlVtXu7W$fdfk(!z}H!$H+ zW5scU8}m4#(_ojPwHso^(LTa+SIS@REi7T#K5p2rq1F`s84gae+PFuHQy`wG5O+q= zUNHL12Lo1=`x+ymWc)$>(defu9Es9g@eh|6^*8ySUKrmI$^1-0U%H^#8ptegRYZhC z_QR#|#Ef#N_}67ndisZk(+b*@_R0@V*p><;W~S8m^6{>WISN5WCjndBZ>V6NhELhE z!rhv_cF{QwzxBA-trRdZiF?#MZPs4gcpbBXC219#-}}rX5*pD3O1tit`v{{XeCq-H z0W-KJB0FUhr>MfVUGMdcbJ8gLJ9RnFVc7%D=h;2bs7P4Mf|s{^znSlxOJmh@K3J)Y z-typ!3MR~S;LmW`3WjS5I`thtRn7CpOf*U4eP|c{$%(bN6ZvYKagNX&=i}?1ZBD`{ zY8@~9K+!db2FcK2VH{<4!Z?-jc373d+!~gV(}5O>&`XC`>twRdWG>7}?n|15MBsO8 z%kbol$QP3d6J5%KKdzl@A_aAio-i3FfNW8z<d{?3&`es}h@G8Jma1RJ8AQG#c(bg+ z^<XDlRzi&QvUqs0@@9BNp6r5*-Egl=WeY7opc#AIvY5YkI<kOD`pWLDb0vM)MgHDF zZqw6(pUy<jIGr!L-Y#M@zcx{E)n<%94f82Kn4NmQ?quIo_X+(`C#Kcv{}ww2#QDUj z_@LZ5Ms4x}2E07M5-F?>fG;UP^G|ws+>q;$4Doi=GC|&9grfC;*HvTi2)Y0*i6qhW z0|N<~b#idB6&QSt3;eg3Cu?Oj=ugHGYAHHFIbN->3BRnO=?8R-mq>aK1aYY~aIQG} z>)_<O<&4@;%RU+;kpB)Uf8|*i^>n{G`}ez21_!MzzA=f-oeWzbTYa%V_7m;a!`mt6 zL7Fnt25?GjXX5y>uD^XE%SqBC4aTKSwokxINfc>&?UvPktbc%(4WVS1@z4fk11iKF z{ocjt6zN;)rJG_{BW8<sj9-*&ok%=HqTE<v3)Yr}IU6o7(W8G5F}w~&ZAR0|7W;s7 z-Z*IVTzj~thTfRN+n$x-H0;S-`*M_isQGlN@2Mpw-=5eT3CkoBdK&uCzM&(eOft-i z1#Swxwq%v=-$JDH*Y}$+7k9+e5Kb|5qOc;%?{K{pooq`bDl$^F$PCSSPBiR%P6Gzo zw~LNT3`DjbQZVlICfx#fkIaP4)@yJfc=+{y`GL<6k-MjK2510m9O44f=cL1sC!WOB z+}O4)mD!~gO6Y7>Y<nS*^tWrQB$G?u_hyY#2DP_1{X^Ju!0}aw<QNxV<pj6e2C1d> zJMsAYB*Kbwa^l?Qjy>ZYHv-LvtM@NDg4E4J4L$RDsLJ9!C8=}Lo{X1AjoR?Q?2v4V zJ`VzN1d{r$9#zerp%jqp5HH4Q2!K+13!ttRnNK<(NF4ce7)|<omDA;WQ<1Sg$CP}H z!lBh?Mgp0*_`A=VecD=lO@-zlIC%!mwk<b0fLd_)5at)Vd<xZ3PEz6b!nT&PBB+EC zECdy$`mO-U9B@Jb2zOAx<u3!TqJkAKD?}upzPEZSkKMZu(EC=#-^R@gW{f)b^J;w@ zR<AEVb6?cKeF6I>$nOvE7=!BRw$q?>2CNd~wkRK^z+tj%{sNA@_DT6*FF5c!uCQ^w z;%wPaWtoPzGVLH2IPWAM<g6T@GMf{FPZx{k{LM=RjeWXshm0i)AGPoCohmICHk6m3 zMfNxqIW%ST`slc8XclU`uDcKzTFdmm?MeR2?B&QQI#{9|q!bHaBjuaj$=PiK_krY* zQ@+Mn=O0yA9hV9&QAkqmGr`7~z8hoqA+1zA@>8Zm_xCCRbZiBKazX+WdB{vW<G0SV zeLP@l8ENAUH*u4Vmk3QVp`KwFR>MBnP8~)+4ZTCq2mZ|JE(Nh8*L4fW*us+54|CPf zezrk9V)-&y7UX1ln_e?)IaC;Jofxe_iSR115q5AZ*PobLF}rK3$4(R6gJt_?0DDcE z^CD=BxX+RJZ!Q-V+81t863+FSr^5-6t(USygt$7Qb7ME-U8y!znirEsn%V(4qeK~J zVs_zfKi$PKS!!)aqs?t3^!V9UWx{k=G}7kD^bIIO*0;lr)~=p}z)DXk0n@daB7)jl zUZS&lTKxCSZ^S%e325efV3Xnp-jXlEZc{fBJG2$iY243H9$;k0^hhr<>iSk!e3fdr z>8MFABH({P8#?#9vNbtE(z0JT^tJgkw-vQ>D}Uki(H)2g{iEx+*iBcoKPsf$-zsaF zeuprzk%|i@gI0yjn@Z)aG3IA`|B#0>2b-jk@EHZw=u^B!d5`=nyn7~)Rg~jvipkYh z4D1eLD4cjpSm9b0_8~--z+-Ov;M_IrNLPzI6qiE7<!CB3$^1<z-_}9c4ZV_GD4zgD zV+dX+JF9d&VBHQ0(U^saWer1Cp5bU`7+HfIeF=o)FK5){^w|mDeISGxRlW3ZXnxls ze|M2U!fd?&2Z3J*W$dvfvcgimaf;VJE_Gk8n3cL-a=ecv^9*jyad>Y~PKXF?Jb@K+ zB)__Cfsk4CXlC&O8_W2$y1>I}vOgN4Pp+?Wn|gb;)-AZzW*Od8C1$!$xq#~oK^z2U z+N$-c?bZ|)<SSb1*mvP9`XJrhAU-g#k9$9*SV`*9i+`07;wB<(hfoR;?{~y2^Eg`? zRE?D-56|#qDI;8;6<RUzI(Ys-<!9db$*-pX01<gzw?Cw?vw;Yf_;k%f!N|v~TPATc km`eA_$@X3l_W%L&eBj3d0NWAR9JcA-90C9U000VETEGzB0ssI2 diff --git a/gorgone/packaging/packages/perl-YAML-LibYAML-0.80-1.el7.x86_64.rpm b/gorgone/packaging/packages/perl-YAML-LibYAML-0.80-1.el7.x86_64.rpm deleted file mode 100644 index 063a3ac87a28cf8c50760ca634e56d95d6de207f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77612 zcmd41bzD{7)-Sy2ZV3gE5b4<DMmnUVLpnA)0O?Ja(jC$%AOa#tODZWyqcnm@NF$(h z%Dc8de&;;TIrpCXyzl+zojTT7-#Nw{bBr<Pn!#spm+j1Afq;VHf<QU&8p~;@^Qzlf z|KS3B5CL8=AHqQlt?)l67$BT~)rv1)$GEOT%k%+~1_%!zMS#eGj!6KH3lO>hx>)M~ z#|DV!8vg(|T3-MlKp$%t;ApulKo~Fq5g1ay8VnVLiVMR8kRswph?uybkeC=$3@n5Y zfWU!Qun@W}pv`xqmv^8)PT3I@zKUf<v-21Q!W@wWB!7Imy1J?Y-hn<qp#S2x2na-m z_EiQTzz;HEfTMNb0O13KE`R%tjsx==ZvY&PZ(QTeYkcz>qvIj~=*~6XzQ*d;cn9D> zoER?G7#%lY55xT$|GJh3UgINxqvfI3_ypj9eT<ZAd~q!=y~dZ<c>EgwzQ!|uVQir9 zKm9KQ9EcYi9Sbm3Fz;StwC`xFc#R1G4z$69Ut=PG1Np%$yvD?TVeErzj2?GD9|xd6 z_R0Q|<0xKZbdG`cIB5UT{oepM-~-Mwz=3>YQv)1mi;K34meT?p(8opR514D%bl38? z00-J)qw@~P@zDLD@vXlwe%>|a061VDe*)lW`%2gN>oryeIM5dV0O08URQ|#Q!q-?8 z;DCJs_iJo)Ee{1a+8^U<dB!z1yOzHPINF~1wH!T8Xl(HpCVUQXAlKN|00;ad8V5Mq zKlopm*!~(LuCdECMqXpLYwU21J+3j@Paq!RX8=d{hptEC&)3)!;6OYifb5U|UVq6+ z6|d#q*EsYV`&{ELfCFQJ?F(@9_re5lzy?(^zybSY0szNQ0R7u~146Gipug4|ZD28C zLN7v0dI*#q)WOad>Tc)k#N^>-=VZfVq{jqxf-^bTS^Gd89Y7E5P<9RuOnP>X&Q8vr z>~4PxnC#q`peP#;sI`L+le;Z~$r|D2{+CrdM;8Z#Bf`o3k6At@eL$*!Zij-Re9-N@ zP<HO_2qz{N6ar@FhCmx|{$t~hQ(!(YlbZ`5LE6EtEvULPc|qNnoKbc*c1}<Spn<is z2jC>oOxwrZ7VsVIGFrs}>W)DDQ>yD>?ZZc?Zwqwl=nVI8K>Tr==}!iLK&|bZ;OLkE zJJ<0tdD+^*Y?+*qNCe95FTMXBoSTP>i!;g{-2@Kw#RudYWWk|L>EH~5I@mh9p$8s> z*>g)-TOIJ1Ne<<1=jLt)11Tar5f08S=(O+ydjOCMJA@m^*4^F3?LI%hBf=dDbAdYX zIiqa&Q3wYF)D6M^U-t`;gPoIyH^>_zYAGs=?gA)`0BHkM1$2m>hrl;U3e05+E8zVE zAWDFMxdB3d%XA>jFkrsna{zNh1DHBD<+kOvg9?KR|9YbNU;l#s)CLs-ZT@2cVx~Zo zxrNUMH2v52zwzHX0H?era)AFK5C~~43<ZmeA;pCt*23a&xUe8Z5GgE%6okXYU;<F2 zh_y8WAr6Hi#F20zh#(9KfeTnekz&?DqT<$Y0dbJ9kT4X2gjfqA1(09}SQIV{M!-d^ z1;quxaHu#K3Kl>h!6Kq?FdW#c5MtJ%zz&9lz#!sqK?EF$1U?lp7}8n{E+i@>EF=I2 zgW=+WV6dowAY23t5wsQoiwFw}iwFt|0KG$ng<w!YBuoq`B7%Tgi$NhmNTeW03?d*d zj1(3Xg$sZo0>E}CYK;^U0Zf9$A#ia~Ye5lf7(@gvCJM0@fC`BSK_Nh7fM<f%U||Sc z5C%db;R2!v0VouS6cvC8!(d35Fa+3C!B9BV8UYc2iU^4V-hxHoNU(q?5-N^BAOr-( zfzZI>)(DvRpD_ZmCxnCwK}EqLFt{iJDkKQE28)TqgoPp2fB-B8Y}ulM5NlB}5kat+ zhzLYXR7?nAjfDS6G8hU1k_81rguzH4aY6_|2vP`!u!aHsT8oQ8L`1Bi0@h+MYrvQY zOdJUYG9n-Vg9;#_0x(gqkeCnzBnTA|5fDPcg+;`Kpdvt~tYP91sHhkij)1_#grUL^ z2uu_P0fGdJ2ndRb2ns-iM8Sd(2*TQ093dtK5*8MQA;f{Ci6O-h;%HBxaBER8&?C@4 zI#_W*xF8bfSrjH@EdoV=MS;@@!WxW#!QkT7g7E)1SN~rK(dUalgmQLv2mSx}@oy_U z?>|qJd~PThAIilM9XD|9`oAjZemKx8(f#|tvW0%({Ik1U*BR;k3;Y+g|90s8_ic#- zSkQPyg*nll%Kq;NID`CeWq;ZL8#dwJgw{pB1M4}A&&`=nl!KE)RM^_iofB9>|E^M2 zSCv<=6yyVg|C3PoKL`c+ME-+Ri0?nO{ih6_+`qE<z#F}*AW+xY)B`pXDC+)w19v-z z>nbBXb!RC29~yrRiILvF<eS49*g39Sz?_}j+@VfD<bMXy%^hV2`x|&cQBJ_l=f(*F z<_VA(4h3gNU^{kmL%@~n91xmNN5ns6=o&>k8(@R~XPq|G#{uvMEpk(VdLpz@2&A1i zV9wnE&Y`SnV5uUfr}7W$*V?LXM!tW>8W=W#`yzY-0uTWKFaZ84LVN;z!n|ORFrO$N za5(}v`Uw)@6Z`|fU_K$xKlWKBu!xwrn1Fzwu;6ubVA#<^|9?g3Zy$YS1I|)^?pwf_ z@X!4S*gOB+C(!3`^w}GIE=HfRNdZEit<n4Jzm!0r%^yP7qub;EIfMOq{5g(YU;qC3 z-ToH?=s3{6{41e-L(BeG{{8906X67RMp^!uS?D<ey8deUfaTyHug-9gksk0LU5S1I z_#ZMy5Y)rn8T6+upmbgTHwQkcKcat3J3b*7M?M?h>q@}bzu<pU<o9qx@jKXAiwg6j zofP2*{aej{iQ)esMSJ-_Q8Ci{KWL$2{-3D)4?6r~>W|+4tk=I)uQPq^g_|uDh2VFD zI-yC3AH=IA$i!>I#Oq?`f?(n`1P;N#(}jmu(NalES6@}h*iui+Kv&^`6rhGgc*77b z=rg?=0Dv_BX3y(_a&||++?`Rpz&hXzoOyY9U7#qaqm-MQ3$L{Y`n=2QX6K8L5(c^f z&gIT1IIj&1#_Q%~=MJ+)xG^y?@j8kELPvKGCxny@!U?!Lz(6n;J7<u$FG%qEXv&L1 zz-^)Kynl|Xyf#kgd(Z!uf}Z#PtMd1EftLOKj^_Vb{{Dv1-w8I{4hRSc-3O$No^gL) z1>BrHP%s3OJHp%jkAO*AOOXlg4D&!A(s`H+^p$uaOa@MNFlYF+)&q4VCQ)HtU^ih> z*OO=ZGe&SGXY0p+c^;;}_A(wOPvGi+zE6RfIP{G59&z$8!F`;dj=*gOeRBaaV~>FI zFv)w^IlxsFO#vfdb15Mh1OkT%h>BW^A(6sx7+gRYH~<QZiy(v$LIS`+P7sC=6Bhv5 zp_g$N7X%yx3?mYWzGDFEHZZy{VDSckos)|P;ESA|f~xAD(3m)EeC=GAVE<U((f1Z8 z4-*pQ?1+xen}^8}>irKn;P~Gw^uOj5jsG4O;GXes<$tjT{?Yln4H}^T)`Tv9^S{tv z&;M4Z4b<rZYb?Mq0FDO`K0t&35dlOD5L$)_a9n`Uasq(RG7^AD0YbOK1_-T#0}vKK zXq%KQkp_G2`B=*-SHTY{e>kG=N9eWl&)=Y~t}d#8ck)XyUn3Kszr}On7nmSs3`(Gq zwK0NcDcfrIw>gN8WDeU}@YI9o@%C8Rs1*nP;0|lAhrahDXwIOAAR+W?lE|sTqy7|$ zZ@nh=oqVdqp9zv&^@)Z2Gd!%_o6cHx<dB1zIPKgc;4n>%*}Q=0IeOCbcvDMITxiLf z-!^HAaeDfz?covb$8~Sb`A4(4zr-_Yq|zEc>^;9rs$7(IS1(6n|L*FPx7(^v?&^re zWMCeWiFIt6Ih_6+>!CKDR`u+c2|)!;Z84~Ms7=V4QOWv-zf+HIy2Qdc=$%BLmAyHo znBVHi=bPbdPJM0`OlHOxKI<SH$Fg5+7)>>k(65t);o#Mua0&lGNOG`Dc2U0x0b3 zt`>e+n9}^Z@~`ZOJGoz|V)i!FSEQ&Bl|S@&MP=sjhfC{f>3+|rwOlO1CMl+!w$W(E zaQ*kWZ?C5LFh&~V-dVcEW9odv;2nRil+5gjZ;L+=c97l?*O-bB`aqte0Sy(xCaXmS zi2YCv%d@@Tks!;QK7+awz`OqDNkT9KPMnQT)Bb}P`KKGoj?cLV)Yx8_>MZf85SAk> z>l3ZZupYIp(@`DYDV@Sem+U>rXNcnT^BV3l%*8CI&D}_V3`WM?uJjIa>NWg{6LVG- zr5kCIqj=+vHK~-B(Ji}d=6U-OWtzf~8J%|zBv4uk9UE1>Eec1i7?onQ39X@`cY}ft zV(9RH*#34V5WN(;)%C6{T9VX0$dmWbOFLN08q=W*E6f;|{$BJ@mFQH<Yn+n<zmS*A z@5G4moQkFkHoBkoKhx<nlC|IRdTK@U)o2&x>c;JusrF@P4tMsDR4#4qGr{4)dqyRT zI0p6l;o*1i_18DVd#_q59!Dh2{U&Hr8zehg$yt^1vv`ZG?~OZ{KJ;9VpRiXHOlsm5 zo^HHtXw7;4B;$(vlkAiXg7D^-{1hzJvFG8m)oXoGa}hmi#**P-KgB|%f{h{%8vQ%8 zCIeZ_FY|ZU>wVojO@F@ad}x)dnKH(y2xr&h2Ct`_Wx2&Vy;6BbU9u-e?(+MKvQF%H zBrBedu`2C+ZeXI&OD8{Wxd<$(AoKmt)jevgZ6z+tij|OC@~g*vh1(MRu1fyh_diz) zTPTlNcZVB4!7|ZFvS}vRKK9+R#()}-<3hd9iij4eW4(>{IGbJ=L4?Q^E04M3jb_do z)pOP_?W=E7-_hw{%Ln}q$o^LGfw;j%nH^$}I|2D1bCSe{(3D!K8Y}T6?2V|<*?t<q z!`BnJ@Y%k9)5l$mE*qNj6h*9jDeyRETpJ_v(NOIxbtV*Dvhct$vhb{>a%M>RS#r1H z!Y7S&R^j#lqQ<3=pmW1=4@UvG+T>X*$-!Xgz^<{<>`UXd+V@3mCgkDK?!r2G0a3h1 z$-h=Q9Kfj2rJ=Zs__|KHd`;KM5GQ@AImVY!d4*Ar23}?DLGfr*n8RloIcd_C(<}nq zo<F=3^03qDm`fMqw&~#pgFMUe$|&2Re%`{NNVK3g!3V~+nM+%(yChmRjBa<&zo>I$ zyp`6mAKw$LjxMGW498q;!1s?CzbSG{QbSpx<;9G)mTYzUK%;#F20hrzB!lgE|Gda% zbvT5foE#}R_+BBtg+Qog&#qJ#to(}7HJq`Ejh#MJuKmG&k7b#16j{-xebBGZzY@f5 zW>GfQB5&_Lk8*i>GAk+QJ%pDDnU$tOFcX`&Dd*W<^p(ZNh9=>dk$?Owe?oncVukuS zW^Kj2T=lx{MXlT|!&lMbG8}gv1rV%-5G_PcO|nv#^T}BT#4TvcjZ{wFZjKc6Zd$&j zZlN(UMZO*mc1)naOkB)Ad3+v*Jv&`+SVZ9+2l<+nm8~zE^y0?G=ec|*r=VlEO9GxC zn0n0TiFTqPE$c{!xAyP~n+Q3<j{De&J;5U`7;;Mn+@7okqNzB>bJia=lII_HX%TAK zbK*E$<hy6#Lxtv_uKX}MjBgXkx`Z-sFbYm0c{Z&W%-04svuaeB52WV5M9MP|YtA|j zF<s_VriqHvVGg7A2?kP1wiISZEY*DH_67n)hcQZA1mZNk3;gO_wX*3I9V(V+Ow1ZW z*KHF0%dsdHjm0K%P(~FunPdi#-8Y&Qe(Nq|9V_Jxc+S%cDQV_Nv=*Cf(A~hqv-qN| z?r~-|#q0g{fhf%3`AyUPB{}fQYZoHKtijEwkx-7xukPvT$y=fG?rl+&{=S+qykSua zI!y*Qg5RZ*h;XH>weu8%@ARv8XK`#uZ9TzkN0r*>9=}1HNl%b|_LP`g4zv<2rwZfa zOi8~@L(AB&)@@f)eE_+iN?f<M0@;RtHN?wT+A)}VO=^~FAe=gyK*+RCPjg^~NAvCp ztxzn2fs}7p=?3dTIf*dL6YI(kf6pXxr%~f3q?=pLDgCR@5Ie~pP0yPf_Ubne`2~w` zDfGVn?#vXT6Z`esK~%!2{nY?{diLrpahgk8CyRs`)-D#_d-3@zjY?+|rap~P8rXLJ zC%RJE2Ju{us1-}@azCe@Q~f)595Xpvj_GAB;r5>LMN;Gx27YN`hb=uMl$l+|)~oSt z`0S5{7w7co8O-!Cr3!d+kHR=ypM-6%5e;;Corp>@SQm8cHPP&!NCz^J)T|$Lv0>7D z9p3E>6LS@P=zi1n{UGPG$?Na#X^8NnG%CDAj_JuBhVA+ZIH=v@Vfe*>U#}}n_><=2 zn}fA`VnqfS9SmNa#gwDZ9VW|gXJ{JTu*_O*w!T>5>D(uXbZ_Qcq1~kR$NZSoQAX*L z=o1-RjkvUnMlM{v9w!aDgKRT5#Ynwp%*d()X_gu?4Qig78PGG49J!}tmT<ll0v>3j zdqa41u4*So9r$eYA@#?IM^ybBgPl9F`212~b_~7zsJaPpR==_;_CkvHlU?aarT#0a zo&;XL2*O+O;J`6yV%6cd6)UfI!<=^_&aNm=?_`q)-O8{xAX{6?{z@wP(c6TIL`70( z^I^83_Q&B}qlyo`7gYIW@|)_B#+4dBIoix-nX&L_l+x%GB$<1XJnr#-4p!LDSDL9i zmR9l@IAcjAD&cstM9<MFAPlN@p6hnLP=uGkQzOd+Pj`kQqt3fC;7mL9$AtZaO}3md zi6L9LVFvbA);Nki-<*GbDts#V8>JR;N^9gr*LVAEktm5;nnyVc>f4Pit~($fsjreR zePwJLd|#1sy);S;b_yYB)kl4~wV|*=sw7jKUI4zRCn)7|T(VzON!YAYqNi!rtK?#R z)4vgKe(W?qx7CaHY0}hD>Hc_n+gXh*Y*1@px#=lXtAfrekKsw1G4n}$c8mDe9NL2( ziMvl1@!~e+=@)NZL<b%GptejGDSeI_kj}k<W&YOq2`&}$tF3K|?$!?$QMYWz$Clcm z@xi9ihRMdwEKE`bThEu;IK`PCQ`4C<<7_eAlOt@uA*H6t+VJMFx=4{TuTw<W`%>8- z^j9zvRr)+{iTgn(?T<=7)Jd<VH!qUh)nK@>nqN#Wyx<TPpQ5mwWyN~1`tWz&6)wM@ z`U2}G%c}t<-k_q5-i~{wMCZ>gXEmMLF>Tqun7#Vq-*9x->7Jl){{qY9ZB56cJ;x*q zf#kjWOfXVV@(ykUhWK>xTqmniuNNUhj}I0unZB~@C{Y&Xpy~#*Jc$tv_BYf5>f|bj zn!%?GV)vam?Z=_gYf-!s^Vi(IE>6C3rw5GB4eb&dTHrb=oMpu2>4#WV)0O({2~K)J z70us>Hy=NEYZN76n9}jEd8*8(vaXqJlifAT>0xxNG3sTO$okeYGQYC8^6P-eLJZ=- zz@fV?;JJoi$*$upujuL8AXk8fsn1W>k%@GHVgGp(gC(=&Vv%Z31PAI1bS0E2`~Ew< z^c&08suHT7kGb)ivIb91sc+~-raiRDOt^cr>R=G@VTZeaw|Pv&X#G<ca~CE4Chbyd zaa>UD8?wFn#t>4h=M>nHv`x|0QBiRD*q$EYiZ>0~paIv{C77MIiM33`Ph4(KWsEZz zzGyv9`Bmqt)F>3AwKJ@4t2Ks=LvGC*RRQi?dEAvJv9ZHCe)z$PuRd3=MK+T0{MmeK zqhr0qkXNJXh2f`;e2M)>O&NPka^5)AMkqef#ZZnPaR@;y&ef<F!nI#9f4H8CwdBdr zC(JGoD{Z(tGgKfyxYX<s-<V&NiI6Oe`KHvC5J}jkU5@$?mcD(X^Fh68DXB*~H&GDN zlfmf}j&i;6@gC1WX1zK{cKq`usd4g4oHD2?XWCxJjqDs4tjke12Jv(=3`*@ifyH)_ zMxS*kGx_8Bi+QO$ZsY#*<?3k4e9DRqSk|p!%z5MWTOXdUw3mpbP^X6u+G|kKnSYp; zv`Z*WG8|4SDo-ydod}U9#(dPI9w^y}^=WtN#|%9Nk<Zg@L|4{1<p=5Q%LOYtKl>VS z70qD}qt8~hey-#jIpB~8n~zOoG83hKCLEo@>&Lnk;Ci3C57unm-p2b7UMPb!Cbj}6 z$y6lXzrVSzi>avQ2iN$VFy5D`6UxLrh<u=1^S=Cwcifiza9FMIeR`h>&8a@m28=SC zX8+3>HKSuE$Io#230yhqF6EG~Z8z;%2F#U`7CJDeRkLG~O~V!+@Kkw)#;PlT{PS&{ zKIRA*PC1v<B&m{~P-}SQAQ^h#irt49KSF3}D0CnW=Oy3tKVh6YyeCHz_hpeU{<d-6 z?ojr)Vx{+X*vq`}m3KmSo~@=T4B~t9Q<r{oFX+Zi%U_T&0db<6GvD)5<7ck0O3u{S zUAeMK32`vBOzzX*o=OPNtbDy`+(M+1-CuZX%2cHy^|zldX<2B{D|WrU!dj~>R`C*Q zaW&(hnDI2u?+}vA0Cv+>l9vW%tmO~F<?n7|l(yimd(td_CZD3Tz`2SS44Ug?!`O{_ zz@(0?lYHm-lO?iy)MWhgpCY9SYw2;KXa}T0U`oQrj4Qi=hrA>@tcwtWN(*1&Zmw5- zlE`OAgDHdG_0NqI61`9dacaNItJv7OpH#dW<*Q%ld;2uD*mlI~PKm0z<fkl&_*`?@ z>6<s+U44eUab>7y*Kbd63fgCTfhWW~3fm~I*y?`Szfr=8<31#nxxP?yWWXR_5F1jn z|4xaZmek*%lM0$Jug!FUs#gAlkP2Q%XFbD#pFl9*y}7FNlx?flf4w{F3e)<HRm`q) zx4R@sz_9$r2i#vpWLr#DnXubtLVXX*wU$6p&Z^I`4`Rvfeq7<B$W<G9MZP%aa1X8Y zBt#$f`y88ER3;}pXrxU3#Eac+t%j#gQ@|XE`O9v3y9-7b|6@{{59dvPyuHZ*&xfo^ zp_1ii-r5)}>BXVS+pf1*)WdPdV6jK!m9ad=iM5ZOt`Kc6`^~fkhAsb2BEIvg1OqP5 zBld!R_zO6$iz%4u0tFG?c_lB0oeb%Q*^bLEVyk7hI1rOa2fmQbRtt~&+<t>Q)4lBZ zMP~Gxrs#T)`A@6iXHR%qJSQi%l2m%%F<MK6+C3o;4mdh}V!3wq&^Rvwd)m6v(y@D< zt{|$_@z)YldGc9$EwO#V@~*A<7~6a%7o*Ebbz-W){YQ~}`pb{-SDZY5yi+NmxkDV< zw>EDy_o8B|lR<al4ra_&$&8&`;j?Xt-&(AWhFD3a=ae<xSAH*^*7l8jgw%jOcz6z2 ziT9Y~9oh?|Mfab*t|A_9;WFoxFRmz!AxjCqmu|+q>_4IxY!B&(`t&`ptLxOJg)8mN zoiC-DltKhkx9n78o7Qrrc!OckK!QG+vb=R=pJP<(@}a%@;)&t?{n&b=vzr~t)TsI$ zg<lqCoaD-2X>4ilM%*^M^qJ9GS0|acjE^Y|!$CU&y3;)|to^^!nN0J?6~dpmthMCh zes<?1<gDdZUV#hyvs(<b__@iQlB>&oi;sUt-1IT+`@0`M^C52FyAd1|T3U{*YDacE zba5}4jEFgIFJbT}+pI3HMh0F;OvzLcH6FxdRU1?J-K?tnmQRjzHRpnBrq}qEa7r*R zTl`VMGI6J!l6n}2Ic^WD?tt&@et4N@YqzT`D2xR3tFRR!C1q26tIt=wznhV^SZ*gz zK$Q8DZ~%|Wj5Bz}!Cm53(X4F1_F>J(2yqtX1tX2;fy9>&aClgX9AD2BPT~=M5hX%U z&<XlH!q5#n;L2Mc|MltDWJRN$U#7jB+a>3S-pk)XvZT8!51LdCM{rx~w;UYgnXeo+ z>l41RG^o{=V*N0Ggw5WYI|ik8y{Hdx>Rc?Q7`U(SI9&KqpPGX_8Rmwt_8Cs|1CgjI z&o*RpEJd@|-J&?_*tTIY2@sdC#c?v*Cd_t*Av!B}nT^x?wI=nQn%mS|o6Z$8f*2lL zv=6`WuuFMw3Cq4~_2+t)HW~hHVQdp&N%%8%RP{pM-^b;_YNZ1Ax(9-^t|>-`=W5eP zF@$o<m$X>^(~TR;J~v`M?U{9lvWGf(;neg0qzSgOS+e~ZU6jV{2`)(P4fZ^dbfQwE ze3Bht^bqD?g>mYHGezPcH=QZvwzl9m)Z2KVe@L_EeL&9o)VwWoZht43M4aSO@#sA6 zsBYo`YgG3}r_3!_o?QsTK#j`C9rNP?vL#Q0cbFf_^EIAYha)8=v)|$JSW_2aRd$i` zElOZJj;uc362X}pG1DRP_sqvDpHqxk!E$WUAf(WaBzI%ZQhv?g@YD-?5jL}#b0D!b zzGK(vv`K<1)-t47!E@vD+-mHgYPe?CB;>Xrb+wjjT+nJj^9^E;8{MVbE#m8WmM#HZ zZjT$C-uSBye(S)GzCWc-OtwVG;gt5h6FG2Wx23r6+;qN**wm5p*vlx<F5V*Fdb`;0 zYNkVI^^`kWS?9ipL+YiaDqZ1ooEHDxZvw}0ECWuT7(+<6`hMd~;I~p073kI48<>8{ zd?o?j17qWElsS^N)`-7^Z5$_xQ3~%1oyJM@hZ&VE4>OW~fD?L4)9Jeul7GQ;yGtI$ zAIr%%dCPNj@qLDo*i9cUU*B(+k3pM(#)SyGc|8MO-C<DrE>XD9`<EMAeEV}k4_}ZK z;R(>s!2O%|Q?yvu6*+f~q@CPHM3@8nXT12`g(w4t7fwIz_IzV5ijllXIZX6@HO_FO zRwXM9=WEhYBkP;ajBh_OFxN;2865H<BOBd!-f70g_!XPf)-8Q%EpFA~t6v03gm+ZS z6Zj#&J*~y+BN}Zqz;aq^%aaKfhUa|cO*BvqVd5Bl_3D?aLncbghD9=ZjGxiD7rM38 z@05ucedp*EQS3koGpMjC(8sy*7F8V<4oRc<x-ucH8yD2E@b0IP|IM!)bW^28p5QU@ zyYFlk-{Dv2)eqgqK=PiiotEpr-%yvN?wqzpEazpp+>z9Bif&vYbPM%MsuLJ+cyAS` z-kh$^#vwI3zenPca(kWb5l1|?H<4c{j(~Uf=X-aGmlF%VXq6xjrDO)DD@8DGpUBNB zra80^q=>IHy!@ouDGv6sV{BuZuzJv#eDPYNJv1llI}4v~^6wO04N#cCDrZ@*;&&Lk zq*w?e<LI`MAfAD#1NP9jkIJ|EJy3%|BM%D8J`dPH+4!IKnSawG3wubFPpju@+_UDT znu<-d@svDqiw3`*(PykEXEWch#_Gv{CTM2*#XE#Dq&u}^&gug$B!orr!Q55<$b%l4 z6X}-XHfEgdm7FfMJ6qogv_16<%ztLRC2e<V=eG5zH1`#FFxIf}O<rWrsv7ut2muF) z<D6)9CC2Jye}~{p>pbq!T;Zi1!mg@&tsb{Z3YS%Dmj!9=*NK}FMR4P&(rGcAbPr-4 zQ?zOrcy^!qzYF6fs6Njo2KVnulHOq7pndeP?gz|a=xX-2MLW^b_mWQr7vYTZQbwK$ zgxEit=;c^=<z{m;RB)c2opwl#g<tWI_k3=a$5U$`qSX58(>bEWQxp4P+#&4BB2ZJe z!&ICjo!PCbP<F~lu$F+v>HTy;JzP2_g>rPZL$j%B{<SVh_2>KV3D4Hcdi?R+_2fce z5x8U-Tt+|L+2URAHN@ajV^s;fc*LA!hjafXZDa<o;utUeiFfZs7O@$>!RcgBE?#I7 zW3-grFHTd3w}I^kuOF#s*^sC<Ao3j~XZWWtob!tHPQ4OtWQNAJIgZ9ftKcgBl+b%x z+FCbkZnkvywVV_VBbTMDD3X&(+CJsg`Q*OX*22CrD6!(bWE)N}>~&aIv1*)w$Sp!! z$O4&G(5`W@7mGcDs$4lfyuM*<<7<+-7^7b+N=vOg4U*ekU|lkA;wPm)mGEZMy<rj- zN8DXuQ9M`x{A>mHry}v9&F}MHt?vvNf3@$bw+($`{JywRcjPz~f%o2J)k}>7jzt31 z4J`+E;;wf_AF{<9aw&*<Hcz7*Mx^AVPwgXkV`=xVMy)WLeh}{~qCD3$<2f_Z5%xW- zcMuF^^K{|;Ofj*bLs6^HmBprx*4`(gh=vB{d%O+z=UkmIK1JOB{(DQXeNreSD?7k( z_hNU6@<9FfsIe@VINOR?YFzLWD0jXgwl_pmI(g})KQ!F%fke037SSc6$ei+X6Y5T5 zgU(kEo$0F@q0<lLPSZ?i(++D~xy%U~RDK|Jz^q+-tPOEof{H<IQx-N;qQ(IwP2w`W z3oD*>&ujIt8LJ(2({wGSg%B9V`|w@iKynos6SGeDd`Thd=a8MdNrzKnb0HELZKQj4 zuPV*oV9DKfrZP*-v^CEp>aqT6uHUZFzrK>4=T3pOI=^$@sy(Zq9S5aeWJdu*a)P(7 z1RSaNCr8(L<d>_sdg=}Gcwr{J%QN4$U(-ur>#XIeeQ>hXjZ#xwV-rp<CpILK{v=>W zby(0(OgR0lK7KB5bFSdB*o^hgS#^^x18q)e-1;MuTh_g)9+b2hO)yRwyz)+;?@#!Q zn@C`Cg(qs%b$4)LC0ay-G`|0MbvJih3bG)d>4$YiP1bOU8)xC1-z{D_C&S?qCjXuK zj91vXhw|>0pS`Bn9Uooi{MOPg<Gx5jLw4t_RfQ`4nin3g?sA^qv~`day+;S@IA4~& zU7@(h`FZxhZsFpdKr=I9$m)c+#1_<~olBK7D`9%^DaSOHu28M)BJ>>2>J|4<JHdS) zOIKr~WMQ-{`EdzZn8&SlFlp2+-cJ#DH@T*^ycX<KYR6!64h_|P52m_fj}F<?Kb#Ij z!oA*HsnK}6{bU(Y@Hv*plCozhA3jYXr}11d+nva-+xI7y_QdCD6&gOHP*2s|-ynuN zrC3Yd0#!Uc-0ZXF#!pi4y2S+@CWl{NeltUf!_Mx<a0RrE`Z7MD)w76F#=?226W`KR zKz2&VZ?m4wolgAZu8$%~IjLrGXqqNBt?&J}T`FhQ&u$#OVK!a~NoLCV`c(GSFZH9s z-F~WYzl`Cep$U?0YQyBNn!%Tvaa|{A#_8E_NF56-DdD)cI`JEyTLl+6#@xmzBkg}F z$}JG<EvBI2S0j4WERsRG6M1Unfu&ljSB+fA=4~Y^9KTeH2qd6C=ia)Nu*Vr2KkZ}h z244bECCJVc@&-HPH>a&!Wv^6;KXZ`TUR7kWA1i(TEb*lXA}KJ@syC}udpygBJ^$Ih zA8x9V*I4AMGLz3TYpoM`sfi}O?aE37jh^OvD~h%;yRjC}DXevF2W8Hj-4f%yq8&d4 zHSTe!m`@}vRq@HLQHo(Rs{7336o`k_Y0yiZfIJPn%Qn|sZyS$H=}_8d#`1AUhjE5Y z`cxW^eZjBEz{9!uq~nc?gDOPzmV?OagilA`N0n0@-;Dp71+5oIWk2(=lLh5C_Z3lC z>Uz_k8`PIWSOV};m)dleeoq(*>2PTH*Mi>cQ|wYaTTK11$8FqLqMvPP8vgyRx@Mt< z;UfMQ0dPcJd!hZ;->x>MGqUfWPdO|Vjn=e@*0dsYe*Hqs#gWy-<w^{P7;6`7m3(7- z&06qiIKcnxJw5(`IXeaUZnQ&9P?q1Bp-c!Pf7615^XskC;h(FDv)vfLPcGfdGp7Bi zYPAMEDf-~DOOqPi%GyFyQ7Ye9x1%n2$|#?x+&ICAV(ZAwOJs^`A)H}rF-hf|7Kb{; z_4$mLWL0Kjr6Nz>xX>teG}}0Z;D|Qt^D9z~k{EoITLRZEjAzGJ<a37KW6aDEyBF73 z`h@V2hwGQR<8d6CODnqPD;WyG!yKsTANaM&uF#mJH|#%i;y(x8D|%w_&Re8$-h!OG z#TLs-IofvSK0_+lFu*qQm1}Hv=1oo+?pr?6Qogh<Y@ApsHGDU+lfz}AnpycXCz5<e zSrPGEbryM%^*uC<L<~L;^W7#Fd(KmXLx^AbJa@`0HvPUO-dHvvHGJ49H>Y>3Uu#fU z%mYca^sRO|%+tD!t>rUJXY}YfeB<6vUp|Z+FwK4Xt};0~^B13c>tY6LU}*?MOX;3s z1@V`SAHO6!cwgc+ptkBVA~7l+vL?P;AC9tkp|;h&hQ;cwp^jI_wpwK!Q7#v4v@V8E z_*`z6e|P5#mO9?*=qBp~(P_M)`M~dzQ`fcI0#7BojLfE6KhjpSVJjuPbF@C2)Y$%B z&Di1Zv`Op3)*d(6Zc#-V{M9~e8`Th?D(F;~>GzdM(zV*+sI)m4F|qam<GT&d=x5nl z=Pfe>?3ow_+26B%$^PJ6S0QjwnD1lE;7)UGg5u2@@OS>-Fyl<BKs_#^FM4kTBT*zI zyGD<tJ4EM0L!xbJWA}0Byu;1?Q~V=ubGu+2k;39AHbo?o1T_>IlM>ksJ~sLOSg!cR zKK_zcCAx<foAQH2fLY#Y;78U--1z%=Z8`<{gwGO+am&rN))GwD=WlFFv{5FEDzZ-t zPTn30CHE7x4%rXdy}JEQJphjVvdNpuCMopN==Qy6BD?wdg<{@4#}DKK`?KeF${UI5 z8G=pdg!mthMAvTTnG&!SCfvG9P4#4u<XzuklBE6T!DW|dW2(Mt(MGU&YRa#1BEp0w zbs2-#k5wN9g2=?;)QS-{ysLdOUPr;oZp}GU@~kgtEi(1#<NR&k@oF~fhJ0(G-nCLb zB9&H?bgF%pN3nqCDHZ)<V}Su~&OX{Act@v^wrI7hm`|`aP2>CUw6`%{0P?dK`Abmp z`Gg02%k=Ykj6tRz7xB9+-B+~zrsOys5+8?HoN7h=q=PS2r9SEzQryx*7{ipZR9RNC zpNqz+=t1^$*uRl`kL`<K({U^vovEj4%Pwk1c(!LsXg=vTY4jg`D%jP8m)X9=S$MJf z#hs(|_{GPp0RKrr-$v0xq=FcWG$+PR_40sS4T7w;N4n3TqLw9<qWR|n2ICO3X|TD8 z#6W0&R`j5P7FIG1tNBIXU5|Y3$Qie#{-q$p^F+5ri%vUFA&p22KZ&Gh+vzc`vxoEL zoA3E8=POhB3y#WCHu~eu9yB!s>L~ON5LXE{j^Ta5rsAlj*;-gEZt&nt6e=vwILkJR zn+nk_0ZZ~2Mli5(jl+$wAe*h98uqAdjS9%sYA)4dl(a?DB4)7|Y+s5v^b2PX&xk%U ziZM2MpE#jO(MTbw#^iTI?aPs$cGPIvogN5vm~UVp?O|{v5z=ajc%3LrEbPuMc4&E} zgCDxpdD_dxG{@<iJart1ZM&Ml<r9uea*Qvq)W7i-&ySs|$y30-AVd1&<69(NN*U%D zN?Vh^e$g?#qsy7wOj&g9ZkCkrlwkH1$GQvt9uXaJ*Bbr|<K{^#^?Q~|TK&W=p&nvh zitr~{sZ$&EzD{=+_{%DuED9jb{A9adi7DtUYJKA$LpFB|aB~&O%P`leS$v-ED{%gp zQ~8BtRXuq1*1)1!-s2B+u)yr>ELUb%js3Fya>^JBLA_?RWHq<R3y+-5AD<tQrEK22 zaIl51Zr!{4w$^zT7pwgV!Y6<!!c<(LHiC1ZF+8#(OVnGk(Uq1eAa5-cWc^~=cZmN$ zN}6BkgFK4DGF9?LAI`eNSw0+UU#P|ZDW)J+@O0vy4bkuVa7%&5diATSDIAqq2{l>{ zE(2nfd(}VNER(JV6?_`{;>_v@FC}Z3OcDY%Le?KNN|B6A<xo!-7gh_*W*$m^vl47I z_Ibo2Ov4+RwZMFt*j*FhzvZxc<*{of=s{amK>5H#w~tJ_yY#VbnBqnHdR;xLUmtqf zoZ!t=o}GzI!!!%g4VcK2tiMXr&lRT*(1jFMt!+uHNhoPaOdo_(_FhWj$OeF?RtEV) zl=PK<C8<(-isO(k73vkZrS@?y);=so&dH!w>^w)2_>Eb{IDX1$6yHv6u*CETU~|Py zgnq>~mNMha>A(C1%F1n7#kxuDqodkcz;bYJRMxJwg<;3AEu?62IyNRSfXyi1fW+ck zvXkfYK4p*R=1~Z|yB?u=3?F-aSJUh5cx@)f>7EUVc^;>)2GS4)nX7*3?f4kyV>Dp= zDaxyPM<f=!SOtFczSMoK>9m;hpxJ-UG<vNa)^w+@(iML!sH|4-;yi4AiD{`dJ1RNR zCDqwhbK<6EMW%wL%3Hzm%kAdNA=;J9QlIbqmMuhaL#_l8UoNGd`cDmOvLy^*4rs4O z9ZRMMClv~H#Vu4ceA-o|>sDE+zz_;ST|PfPd8!ex?a9tuQoebs=1kEzz5T4^sJy<8 zqSefO@7cghoAgzJWvSs(%;&oC_Lp^X#g-3#FDZ61-*qk%eAsGUx}3V$DiMgclUO*` zFn3uLPjl=SUaX@YK3-n>@y+9ELXndU{jco@U)U|cOBw{?n`X67Iv5wN?P(hAQ;&;2 z2MZ~Et7@qtb63aCZF=S>U|kX2?@5puMQ_3qmbLV*h!k!^j?Yk8WvDc;#1e_?)u2S? zT-8|n^1a2=$KU8cRriQENM<)2Lw(qh2c{pa^fy*Z#csVf^Ign?h}4(GTs%6%_&u(T z^4mN5a7YZZV#X%Hb^R=9acV{A>u%1Pvch>(${f*}aNjprCg{YYm;r_&%WLoTIbpXy zeqiJoSSzSKd3uR&>jp_Q3nbj4_b-0R!=<#PN2xi)mNH*6!knp0FU^IWrhG+7FEI4D zra}L<<HdAc%Dm|I{bZJ)#tFJt!_-OJKKwgEBp>Dd^|9&d9q&?&zAO0g-pk|@7HUwG zHnF3<W0ka>qtx9VRnTxZ`k~z3u(FiF#YNm3yy)0zv0=;NR=KrI_?JQ9aonh1<)%^? z69WXwjJ7lhWPN)KeKPgJr#sXygnOJ@o>m=MF1emTUbcT(ZFwe~3)Oxt?EEG9XH3M2 zZtO(W4}|50h2oar1O<}9W=Fu^j8qUc!RYktqbM<Rm*KMVrcXGPXlqTppk?1T-BurK zS!#F9?1l6(<;5QQ`*@meZCvM;HQ^-1bMbD^RhJ>YR5()^u0hL#rqbJ^^4pIQx!rlN zV=}6OE_f#8f?Z_&i^^h9p>|E{TyXwrxm?{N&f;RXwo_YJbGu`I!36yY;V*kK4<$N< zAE%UgVODQmq+WLR>SR|Sc)qX;c#FJE3tMzfNox?4PO&L0H>5YJiClP8{5Hfzl1XpM zVXj4wXfTEChHq39<{O5)O$UBXPEKVL2?di5?*-;8<=(I@7G5<NVxRUFzA!Fxl}#eQ z*OsKXsrXjol?ThB44X^&3+<wn1eZ<HXNyX$DYi+;2=kj4k0*+BgVyq8T5zfk2NyO; zXYw-$t`d~+uvlG9n_uHUG~~;i%xDMiutV^lE7xq!fsIyYzCC6Cp?uRItLoM<X^)P0 z5zR7<(7v~s>}m(Y?VkemZ@q&HB}~$3c-%<Ca2c@tF7H?t*yXhrTsn?>DB^Srm!8wW zmQMG+^O3N08{%qdPDptNpFsCk-Ip+`;j{J@mye|9c@`dyFC%ff5iv_-C<~Pv){h5- zi~~asa4Vvgzc%{cGlo?f9A2I+JV*Gz_vm}sI(|-m4sbTLU_4Of3aDvFR8-`7?}`-m z@9O=8>FU}1$o}`+z~XM;hVWpcNWaH;vL@!_ZlX%g2h}&<ITs$Y@D<@46O?`z_1GMo zF};$aHHwQe${&3p*Ay;$!5f;vNAacf<em?KtIzgD^}PSiOb2PnV=LPu8Tf-MlIde< zRP@S&H!_`cPrI9R=|t?#v%cIZA->78wwdv!O=(en#?By<L&yAaz^@9210rq}zb<-H zOAE0!EvGx2-^M7r*EUV%1EUuMoRdOE>thmPwTfFiu;JCE10)akdMn5EZwb*px0?-O zgG6(U-hW96Zo1L;_5P3GZ<opw<dqh<NBlkt21cIH8hX3ByxqeAV(cT)Kx5q=ZkMM} zYp%Xq7>cnUryj!cP3V^KH4ju?e92BKH&H$i+=(5JCob=DaYRi7Q$z(W?9L4T8fh`2 zO<%ZB%`5jKyp4A^B&FeOW)IpGXb|J8q^{FW4hsdzk9>)Gchm5K!0kEe&a0u%Nmfo4 zdTy*%-w%iHx*`?jTZtWXXj#c?Cc7uk)S9|4b0fR^G+}WgZ71F%Aem?{%G;#`f&2XU z-u_y20@;%1ik_OO*|!cdi=^7Mzl)K(4W#59>mLs$j5rJE?kD|R;A^DRpHZi*$z3JU ztBkfT{ffiVX}If-e|Kz*f+WAOS!|lS=u+lTKBvYO+A?Mft*fy5`u^wD+RcWoBv$&n z4u~6H9NJY%Hh-6t&~tV2!#7tWFr>t_t5kkG`2K=?Wd~z#;k}&moNcZc&I5dB<NbF} z@n5Qv-C@@2@O>kw2J(6=yIqNtqJPF0RXii5F2rZdXEq*4$$FkxyM_Hk^2xOR8reyq z&>ES;H}SGh)7#F|AI5|pG}maiiMiI}7FTlUJC6U3quTO&QvB@2#39GqS=je&ecr^} z$FGXDlb!Sn#2#P$z;ry1`?54bo?xzV-uO$VYmDTZsy6{^ThqG<6Mvd#$W~=8sOUJ) zy_)W$!QI(2{^%Dpl>Kb?a{|uind42OVvI#wrR{sIb9QBocAKQ<C6O5qE!k(>BMaCI z%&W0dEa-Z1G^DS%&3}spuaI8d39bDu-bGPoNtm+{I+3!JD`x>=@_uHO|9e2QkiAZ1 zWV5)O=of({_}n3s?!23G-J|UFi`8}U>Q`VnyYeXfB&_6aW0mm<Tg#Qe9MtA~aX(qH zw6i_O%~rX~t+6~7*=@4>tHWoUCdEl>cx`1VPnB7em)V|(cIu_#v$>NElo)Vd^eZ8H ztaQ_SyN^!B^Is&y${U5oxJi+#&?R5q+uzW)wZa#Zhx)Crc{6mJUKX3z7QfLF*Pci) zyEKG+uU;)CPxJcT<kOcR`BbmwQQo`SewDG8D519s_!EpY5&Npd;6ssE0dg(ARnR>P zTt6;PTIzhK$*Hqvn+e!;#1CCY1(gqaRW(fK6&h&4b9$2}?-lweHIKdBmb}Dt$3@b} zi{`RfH8cW++L!U|X;LcqMK+{-8*Zn_J9$f&hUkVTntrLFg7QbE#9)qi=c67*lsfnF zE{A+WUCggo-4qim&C;(GJ9>T0fG@lpz_7@{Y5m0B?<K74tx9lVvN7`ZE79t@I{7#P zqjsjVxHdA9Z>2}v`cd8ZPrBs%FkT!o*6xjCRfe69rVhm!U~0`OU;aF6G4?OEd(%kb z|ByFgw0dU8nAz|4sc)QUy7bh>bBX)1Z;EJfdb8fR-zgLMev^AdMXF?U`D|QeRbj0s ztnYVS)mhkK?+>JqOinWc8P%=1R<@+wpR+NZ@61)SZHu1KCKZC#uSPg#UQkSz7NU4Q zLb@#s?^{JaC&ie4OW!PSI`rOPd3tzOE8nry!eQ&zncUOurj}~scbM~W@-7i++K;1J zZ+%}N>>TGoW-`pbAsl+UPLyG&;-*aUmL?~|CyVltXcqmW?9*x%;e2l@Vdt_FpOWoQ zu449A39Vxlvtszu;WM3kliveeG_M+SlRYhGD#fzcbKllfI6q9UJ~SuPXFItNdgC77 zU^I$)B)K>n`LN~*4qT}OK7Ciga+8qJCAHiciGi&d_9S;-m;5kFV&vPyz@j{<o6*RZ zk>cA_R$D;@OK<8+jEX2(_m$c8e!mZ(FqFPgJ~3%`64|GHrxt{v5|RQM8yj7+)RU4s zg3{4_!VnC;!ESK=Bl%-z$ds~{oPJ3PR;YrU&iVbpP>QvMQ$}I$j43tYuhrud39@9z z#-%+d-}lcno3lFQ+7>y9b02BCF5NO-7tIxL<`3a~U`M#aJ4S4Q@p!Y<R$^R|fwgnc zfAnGgBFj_i#`(?PZWb&3Rs+&s!}5ggZTqQq9Fw-FP)TG{YxKSvm8BZtJYd~vCS@ak z#T|y-v1!u*hd^50@s`LXneU|+e_H0WKE&pas}ggJh9=H^d*czGR{Jw5(eLwugR1ZW z+xk88a>qmD##a|PN}f@rsxQy!E9__anCJGCZsuNwnQ$6}{BD^1VAV72m>@)kN(NO2 z(H`qGn@IJ&kN&>hHngFvkMvz01nI8x^yqPMFGU)7uu#4=NC4|!C`}w;6n!RLo7CrW zt7x4*aAbOe(|%`8#zA7nWF_|P_{G3OUjC-vdmxOg9TV-|j(+le*%Y<2Fcyn1eEw5P z0dx=bWQO}yowjvfzDuOI+F`<-8E+^%_j3|acXV?2(fRq!R6wX{v=bGs-T+vGsA~Sc zeoCEt*+Kr;vO;MK^r-z^rQ-cAeqkG>29?LJmQ8z;uLP^`hl*C)7UiGHQxx)B(og>4 zP|C4-F@HgH7uSJL$?IcvvVWy((ROqF<6g!xp}9*g>G$TM)s3}uX1y{29$*kjq2%e{ zoqdMSWupRbxYs^yY}w`9fa8fL#@F|~!^FV%ok#p|b(Oq*Fs~_L7^(hx7US)cPKu&b z(tc&QMDj@p>E&&EbJ}FQ8ff3r?P7I?_sWXj%pAGL@|?svdq$5{GG;PvBd0DFFBcmw z%ILF9KQw46)`v+wKJD07BBcAsqw<X1jV0Gvaq4VX1@EUkMtCQ|((<o(v(f4g`+O-G zI$P3OOS79yYvMd6`?<LH+#sg5FKK23_>{PHM^{n4L<i0Uk75-C@=XSFY=)o&1SiWU zJi2XWVq^q3v5Kjqyl|-&y)%36MH;6bn|YH6j|<XA4rf2j=xJZB8GO#(l5ih3FLaBS z=f3<sXHAL2l(}I6O7WZWjBNYmvFK85!C6L-Qj(##964BaTj7nCXl6q+Q?w1w{g+ja zHk;iECbfeIm(4qM6MU1UnX5y%RfjX%M)ce-bZxmJq;{Smj3{r*yoF~_Nf8khS{c>` z*hUY32RqlBKPSDp-#X*b@X@7=<^eSb{H~bmJ=6CadOCZxSFfb{+G0&AUmfE<yCt9@ zfzKV-F!cU0I}P?FSH!%1R`uPV<U4w&r&||hGQk2lo{_t~I{}B^zPdkn+rjgzMR8Ac zt{^sLWr#GOc7-J1JKcw!=8)uoDHCKp&xp+DI{yAp7SozXVwyc9*t2B;tS0r36k<FU z-Y?~F&CeA(FB`_|th?9ib;VFA?FVT*Q|=wBC}vo1)5(EQkc%P~*-CLh41&zhSUOt~ zx-$K3_Xu<1YlH`}KV4pWQ`#(%8<13gk@DYMsC+HQbPM%ymu))PS+Hkbd6?O-e|KC< zmYRpUQv219JCBWz3*1^7+|8yng^h5GC+Ti)nm%zmqUA$8|Ll7~r|}KT!{}azj42+y z>|mPf*lPl1b0pt*WW!c2{hh7(wrN>syff;iM*Sn&y-y@E(uVH6dQMm$dzLSH`bx03 zF4Dc@uL|-#Got2qk`;mz%N}8pn@m$kKYEE+3UHVCG>hYE;-OP<JIBEE;UO4L`m9QC zVXl!r9J{FNr;c9)s4Fq|tCqn0NrM(BEx0&!prUB09FjWw<x3X>v;k8uROE%%$AA%C zduK&v|F}!<*rDN4f)0Jfh8|9N8~co^ng_Z+WrX9UC(3>%eqW&Eou0B7iij8>{OVb0 zu@-mhyHKkULEDD_%gAomC3bt#rl&P-cs#S3X1r7VOy6`oTp}ff*crUu8A>KMC4TXm zdlDbVH*`oc(GgZJGW2%n1))O2uwAkJn`aLsW>Bt8T#&}SGv`9t&R92A@o9HdMz%ld zX*$TVagdO>bh7lQI`f8k=qqvgk@_)rbK|xA2C9t!{t3m&W=m=h{JLk&#`k-k=Ds(x zD^OY?f;+7=-^E!V4elfkA^&i0^69Qfq&$fEml}(~bI#6Y;^)asPOD2-4{g@mad5y3 z=JNf81NqLcbSaq;wHJ6!@7H)dt<LhZVJT40esx#XGRUGp@4CS@3?35+rwiMJXex6& zJoLuJx`*7VNKD2!j_-*c>a@76tGnlk?bEVjrfl!!D*ZKw@H}_+V?%ThRZi(_3}Jv8 zq5LAIaEpu#=Sp_Xd@;fzUjB$j_vi%w?u&cWsO0A3)LJ#NdBnxXrCyl++1Z4f{|6lt zS?-^+!}zZ%XLchW6-|_6x`)J;i^>pOR&4O>oAkLNT_iTz+C;g3S2GgW)e2{D^YS+m zn^TMq$YhSSr9D=ELs?DxyGJ`u&`nKw*j=#aX60jN^^fuVYZVh7xZB=`da_+>8+nv* zER`AVEGG#kzdm>?+nP;gvll*42jky*l{=}&_YBEMO<uM3UXAg<56Z@tpQ86s;nuRZ zwcpxfWF$+0gi-q|_oF=@4x`~kzRkge7?$j3x5M~<PwEj5<zv#Md#k2VCThLLlN@JK z^w-ckE8aSRdZ#vy7kKgvm=Fw_5;q2Wr0%!hgkv&yaK;BkGU5(tEfDhOC?hjpo#4L+ ztGC0~f3Y9<6L!3M(c#g1db8h~#QsS3UI8q-ncozJwVISrSGHU2{^03%oPLkn_`-(^ zF2V6*<u2;9r>JVq=#Urm((7`aud2>`gROC?uP}Jc(|L{GJ>k`*T#(tad%~{77%nd{ z!lmUs7r>oCpEK}%vMVL=q4_<x%yQ~bdX0Q3joj0o?<6@?FIn;lMYrw7r0={-t-lxM zA<#0i3*+T%c=?Npo=?Yif-Y|G7lQtyY9XKZow@t-@T2kn2O&V-zn^9G@G@*6o4c?* zPf3qO)X@$b6y71mC}=u&;NrA0Z4=24c~-)Bsm*!+v<wZ`iN@D5xgUS3*Ac|<$meL< z<!D)=S$iVTlUl!4lK^kTo<Y_%{BHUeb?%r9>d>P@bHYFq3SbI<Fv3f*hNsf)x8REK zk8|Da$>+cD@3LB$pglO1t`L{JI#_U3r!;%FTMmK<;j`MzzFTV5QxB>E_?#-4<f8qe zx<*E~xYGKqjrQE`P!BhMtJYq}swHki2UJ}y6N!1QmT=#@vxj_0M@HB?M}NKN5oqUV z!5&OH1sFvzw~GU(;SD&qL+Uv@SifR<CU3Fh&0^_rS35<kBRs@AdA*9M=rKxRpp~nA z4+V!H*A9FG{kL9`P7qT-#1D3U0NBUHWby~);b;p<j?N6*dM5-)RIx=<Ip>~!L?MvM zZZZ12AUE}Kzg8hG8(LOk6may8nvDR@H{Fh0w|AK+MmZvd_ilXz_5Ba-;#Y6&?&yv6 zK4zTJBUunefmM2j04W}EmXX5I4E25;C9GNTrR3Cia8XRC{sBxv-1MB|bm;R^a-C~v zo3q4JE9^Jp%nwJ-{V{+#4$wT(8%5v+O*k0=qz95{z#c>)oh6v!kJaGTx|oy<hgE5z zWY(kIg!Sc-<Tu3st^=4$u@6A@6rlojwf<jJF~?!D7)IW=QI!J$h}*}GUqGY~e#a_E z$HkdNu`LW5X)7Aa`8Y_ER0WvHv^eK(kH}oC_)!FC^d=A6O{8!>b3R31Hvtd2hi6E{ z9dD9bPcX!thK`4siU*NK^a&y8hvkk-zunxK53iTqWXIT8gUlSu7Ye7IML}|hj0Tw~ z3enHq3i}D&RsBBZCreMH-lep$wk&4LhG~$H;9{1hTHu}173nq64j^=<59`H816ksT z1TlTJC<4{4J5&Y*MmE3f12_T?omK#ocK3<6BWh+VMoE3bU5>B@z8c3_JkHMxX6;6Q zKRYil*IS&`(f3m0oj-k~NDhB3bdgs|erf(}8X5c0^^C+-Aj=lp2-Z)w0&Ej}y*e-9 z)^R=H%iI2MwtwVvF;EpN>z;PS<|27CtFkQ*N!R8RKAu$5sA6rph>4p)lHgktFpz!^ z)m|dXbJZ77W(I@^%civ+kjDkzk(1dyY1Rwc_QBoTcNFWHMx#Ic$T>GDn7`yqiImaX za{!ZzN0_71EGS2?V$CO5<!1E&PRdKWi}UIu(Ed|-n4F+(8m2LVBNeE2c$pH585ay= ztAJXGo&$-lgsjgLjiNG~Cu)tTI8j)&Us6h2qQqfDOYLa4J;C!Bp|lj~f8B%8fsly; zRVg=GXUUh(sO+)G0q$wty)DY_rXS*QW>}H)^Xyap#VVKSqmbyL0KCwbikZv~Z^z!p z7U0V|8<?Qfy|aKflS#Zly7%WaC{%6^qtyv9Rvkgglsn%g9_DIp+<9Dg&zO121maan zv^hoRU7inW!s{CcY?bpAT5LVZ_F9@Je8}W69g9wdoZW)rQgVe<>HTohUU3T83|N2d zf?tC<GlqAyZZZoe?k~kgJGYY*(ESX4UIUZ9-S%LSxEi_4wwx=xNQ{(95aB{YOY3A{ zr_>MwJVaH@3C(cEsNhOy2bQKiduH;yK`n$25ZJ%AWjq$BJjy9w&ZYOc4dXNDnw54# z4k;_MuJ7{-ivd0_kKu9Ec0gr}Q_Tx-N^BkG9rPxMy6@Ye3Vq3Y4CTi}Ab+0LNrn^7 zy3k8`dgZ=(+Qi0GUI#{`{C;8Iw{J=yi@jRwjmzfo9;ms~(rkcOn*trIgnfi^-cC5q zHiv)hM_KH71-iBkqu0XLIcAWN5n=pX61z8X5R`q;`Ps*ukM1Tk3|ZZo!uAY_>f@8Y z`0Oip-O$C~Ht%GTa|`@)D=om>?yvvG6@jet@E7>FQ9&c^{|og`_*cw0nv$D8MXHR% z!#hFw|FQ{Xh!D`Z`J&2&Ln|GKxR`-kKANX!dnnFosACZ5@0G87p-TW+x2YAbR2QaZ z#5zMy1$90ckITvfJUh9ah%Wssx&1IG4dz*t-S9^lA>Gaq?Xau?^kiv0B=vHvAKSLa zIVGapHVBiO<?N98Bm*I)keT-|v75GlU7KtidfOlLJPlpiDcvS{Z56atf>k@mC~#l? zLgt#p6|O_+5Es(Bc_!R^E9w5(Ft;W;=Ut}Y;kGF$>^WYTRBLaNc8Cv^tNC{yd8TuD zYYEiK!o^{ed2ROzzxb}QKuA*3u7_R~Ik3{@>(wcx;ud=NdQdP4dZG50I7N21)Wdg- z?m$${s>xqYkcZ6xjWHn*!mqi*ucv54(d!Q_n+{lM5bID=1RuPYenVV~cYw>y=mHiM z+cBMqVBUQVjUN7NMJP{{6)O3h%WGlp^ZaaJP!}sF8)g!FQTi#p7KFR64DFdqXn_Ll zM29!3G>x14J4>ES*=y%WrzyI*oQ9qeuYx$d$GP`?)`%hGWMXt_PY;?#`Qihb2Iv?| zlZ^5ex4$QN(>Hx`H6}|n=YnoxzohtJSy_yBEeXPj5akZ{CJ$cM^+t>h2w!l+)!;YY zI~9n-lG@5qlZAkGuWr>wwy+PS_jauNfVXh0V~F?m6lV+W<;?z$q8ZUX$x-3=!q0QG z3C4ILtAxF@!|`abJy1eqONVcrC^x3j5vW|DLRje85u8ffKEjzy8b#PIFjXeQ`TmCg zB&kGZ^e`kGIjDyOA}q%NYmf4$E6m}<V;;+oCXdYywq9B_GgmBJ;qlL12ijnpK*3>V z*D$|_?hP8Hhce7;Y~@4;=;aJF4?F@hVQ-$qnVVnC5!$|4UVIbIVaq)Fg)g1X7)j;9 zp<Z2PVY{cNz5ZI#D+>*x!N{K!ax~m`QuqtX&BE1|cnLaHBx`ON#F-*v128;a?Z-9Q zg;hkrO;~8tK++Kso_o=r+(IrKSuF8SX}8DS>J4AnHFK$pg)RNcJCkU3XM`G_k`Pv- zP!lvXwduNT1Or3JdGn8~BqA&&pM<4;zCo5klcT#Zjz1Y*9Vq?=TfTXHb5DMHt&JYM zQispK=+P9*YH+5@X&r#eAYc8PL`)xFa(}XpK<$>}6UOG1yfG@*(1ZacPB#Yi(<GFO z#&q{`pl}c>19AI@s@9m0J-g((E$0iHK0V-5QO>|p(eJyZi1HN2dVxH%6Ggj)f-shH zN=G+FNjp7+iJ9@rxqxm}tP%A!tA=;KIE6Vx)=pX({UMthYj-eT!p--rOT}Rm-Cxj( zBk93|z)6kQuNAreg%oU;390-Il#2T@ZqU+8z-w@OgZS|HIkKzt<ZHm7^ad(6tRT2@ z*(|yyjjkP)gb^coe=@GW+Us47yDV=T91=tI7kuBSS?<k8NqvZ9R?3qZqH(xjW==+H zcx2VRaDk;@?Mdkzthw1%lb8FE3+(L)b9d|SWF;h{*?GbiDZA&O+_-ZW3<io~m3m$} z3ZK>sRy!{+)w_BTbjr~MAf{$xNs!$Bj)2K{lvzbG)Lx=gaUPDNoLrgY!@A70#|PKA zD9Cf0Se#yuSK_UsVnq8TNjoN_fBClYWBp)Zpru68K1>d}w53N*Pr}TD<Hx>nD!O&Y zP~Q$9hp3|7*U<RKb{Jvu&eu}o>Q2;gc%Q_3$bOqu9Xmcg?ur@8T}9z8OzI^BOzYVQ ztZwusw-vYFi}Jt0AmGTqCrlXfx5;NiI0?Cu09m^OS$_}j;YB-J_ivZR>y1D*20$KK z_!;^qtw}TvBj)TDyE*YIKbDNb%%c})0aS%tAf}Ycz9R}kmO3Zc!zF~_v+EV=DVw}k z<Q4F;vizIpHtEAH1R**zF#LMD{7&-LuNiPD>a8H{b=`V;fCm>exT!5gaLTvH#urDf z4(=n*nuRTR_-!Hmpvv(XfN<)w1FP7TmBL$isuG2|Z)K!8H;6ZiDTY*@hrf8+oHKDD zL`26<5YMO@a=TLdN`S}YW+B-ORM&TAg6aL~y7}UO58TU(GR^RUaX)vX%YIT`!YtKL znD@^*KMv3=w2CzK>Hn5;k4gCz+h<obl$YOR&8dX)CYc?tBP|Hvm$=>8%hi12rh=^* zi*d1sg;Xs-(yiEo9#<VHq;N)yp36@Bi75r_YhhG`bD6I6iZiq{usU79Z^+WaA^u|( z9hPKD?^zrCVBlYaBgj(-kn$+zFI<4^&ory9Qd3w1Zn7SUZxT>;Q%d}44E$`RG&29_ zssUn2YM||VqbWrhNleU6ZM{b7)qB?al>yOsNqZ|Ho0e-hj5k#go+cvYOd^8AP|`4< zB1)a}U;3`F4_bBMSlstva=X9K-NBk#lQN|zvq{^y;$^QD?L4ZSA7@dL38%0gs1CD= zO*8c&^#Q$jvK@|=Kd~dCuM@|e7<qy^&|||x<MIdj+s#i#DMdPll4)kX6UNvKT*W=J zD2TZ{LV$@khQt#usqY%ai<(!uP<~~x@-d5;`4tk}_>nP`20vf6^wSwgkgcA@pR<t^ ze%<2C*yPOpu+B>1Kyaa~GI8WO{*|XtL(~&~*rw@ksTERTx)1k~ziQ=MKEG!4C*30L z8zR-MEaVPcA^>9F;v76gx!px17~7O#!~ZKU0Oh>4`>gz!|4-SlNB)~>nBT(B?INBu z0!q*iywwKr_`6TlLlT-U07Lh~uC%k%2%CC>3oJ;lu}tD*vgzAype4dgO?#)+&Ia%L z0H_FHZBF9k6!Lm6X=U-V!O(xd8%uGSiH-S%X9W{Ly(p0A+{ic`g7@qCsM@-00<NT> zttHF$&1wWx`YI0c46<D<0n~#7<|$M)MH`RckFNgWHrN@HI<&>0<Mqy+6g55_JPDl( zFjxp>{tmh(1KLcji<pbe)e6gWG0T;(sso~54S;ee_d{;Qm2bV(^Evq)4;WONuik2; zDVuu?nr!$VF0I<y$-UM@HgffJohM>|qGI(TEe_V2w?sif=vD=hQG&A9(xxd#AnG3S zB*XB@>p+>-Q(qVq6fxm^5IM04c%3U2%qiKtLW3o<>wnor=N3|D{^)-CIIGx<mAAUO zwUsjXxq%dLi(AeJqjnm#4xPsr^42v2*l@5U!iVss<-Iv-4Nf>cppbwPPo<Oci0ksn zvs$GRNKq3Ix&XQ(tM*EJzJo#iEZ2U;s*;&a)tz?TWotQxxg@@`j8?kk;zdEutEI<} zb4K>nAM+3M^EUnq)E*}K(So!Z0+*U7?EY>mSjO!qm-_5`V3U%V%hG*fn;3Q1=Cck& zc?FWAm-h-o=(zp1&9&^nvHY1Xn|*au@Crro$U2erGw!fX)C5!yh;!b(&cxU72_K^} z%=MO1P-R4k8Fob+Csw%1f20`SO~7N=76{JAy;o~6&X>HVt?X>1K<(&e-eAAd$SS;% zj{H7C1Rj0YeoV#-nZmdiwL{K>?+Z*#SQMtt{tW;A!|z?KQH2k5Sxz+-dwBwayT>Ln zS89159^p4(cx0TzKSkuVU|G~Zhuq{A8ooHwJt0fPrQQ@@ybF0&*({sTC%*4g5XciA z6*6NdqV?|r5=)*Y<lmJg44Mx*0|#O)Bm-{HN!jCNe>#Ps((dhFg*}OMjuPE^e1DkA z_7V$@$x^A5!)N61&4#KgP0eH}bQe(c1ull^jHi%U{q@_BeHq;P%hQivya=)#QB{+f z`{F%&UJtNWaE*p2WwfOy+S{auTUn4=&-=7YsnqJe89p{Y+l-=k?MJ#*6zw71k%Akl z7{JNHf3V?^T#J>$S!KjGeodkG`^c|R7!X?UXRQhA(3mq`h<;dCX>^P~wT@6(3Vq@+ zh1@FLsd{c&Cw$zpQALGtChvOrBIChg-!Kc)8S63Xtwm>a0OKD3uwcpEi^S+|bk#Yh zv5;&Cnp@7dQ_5jP5$j{xyJM_Ly;+cM5VN-T2{@n#^rlI~MmGO1;jvhp<m}`L$s1lG z65gP1UVN<;QwUJ8aG1%%u=GD&ig^%$!@X;H>na;|cMLTU*%3GKi-l*&;7TJgNT3_F zj%G|=K{?tiO}~5L2Uz{x{F#eLT?=G=ROdxj+ny7$f@4s{c_(4835+6H<TE$KVO~1j zk^#}Q@t1^29RroFwloQH`=-D*wkXLX=-!E6$0d&xtaKzHbtjKji-ZpC9=?V~Be_+e z`2sf_ZTRoUTsNn{AIH<hwPqqg-R|`O;l={j$%702_xg*~zSzd4p2$;M-Rk7K`*p?E zJJ*!s#5?ySK*pAt(hR|clH(Fy=1S3oM`rxnG855dtD}u+*R5r!llX0$z$|VZgYNTq z3OP5-n|U^dBeBHA%uGqNA9>p8oF}GsRWU6<yC*zj;&`-5lPaEnyZ(hsT(~q8qa866 zv!J=2zZmu;J;zj6rO(V$xXBtWB8o?7L(|jrj$U3kV6uAg)-wnIWl!gx{F;3622GD% zRoksbl*0fRW)fr*(JBs2+1{m8#N7D(mN>wdif>+^kHh%YYc1hGh6>aP6WEqPXIt^i zsBR^k**;ws3@{U)0>jS={ywm?2wcIp&rZH!iu5t8^B~L{&Q@p`eZ5X#2|>8Wf|mig zY+A_99I#aS)@iP{Qla@Og2bVuLFJ5ibOSQZAiQ8NejiiMa;{h91MixEjoQRXgSkTJ zQN4k&l+%8q!np&%IqPyr-N&yUYOXQvlj$i0!zZN;;BuKyjW>7Sd3{`MvC`gR0|ECB z#KP-~^1KTBJPr0@&)sQN-zm;8j@SLNnmUD>@dvHguLDLG>ns$_E$8uRE*OJ^>KvpW z$~75Fxj6bGG&r>^$YDGDhzzU!CubPyCxztE5>d8w!n(krNS7Gbxqr&M$6c`Gk|1ip z%}njN<k*?qmS6X~YzZCC2a~>7mntnwflkJbnsIvv7^9;e-3^OH0cB4#v3$m<A@KgL zZCuEY%Lj660(!xTbym=f7-bMaX)CLvzerEXV!>i^_Jq=yT%G5Z$hwwkHpCs9RkJKo zOt`wvlHx5TmRBDP8~GTSM3mz(ef@1O1C8BN+!-Lai*nZGfPc$mS9K2a)L4BDGrV{7 zV0l1Cf)8`NQl_{0@L!DYprUMX$KOfqJhWOxPpJ2AM4Ll4WF^8R1aDQ^k+W^X7xn4Z zQ7fv9llM{5;OQ>qtVNd9hTjtxxsO%SlX3Pd_ij)0R7m(`eLh<!`Thw`Xt5HZ3!g`Z z)VKOlkY8WvQF4IEqVsGN>hJky7dqt9uJOlO;zLr5%(`e{r#&Kohu>00pBHwp5>6DX zNj>S^4>P1*U}vLi;%6K^It3FRs>XUrf=}n8XU4N1hgf}-fB5b@kqvnqS%UC@IIJu^ zR+OYAL6A&s8ut}K>ydU1+F+12J3789e0>Tv;t>>4WQ2tut60hIhvy8|5TWqkl+@iY zyq1RSsorE$jUs|L1ZeT`aJvk^JzqTt;@&$H=6b8iEWsM+?U_^5WTknmBpm%uU*^T` zlE-AjG2nug4Gsab*Y$|(J5cLVO+TyF$o%-WW~kr+fRs~#J`Z^r)j}c}v0Bc+g(8&{ zcPn=|pw+u>Ru+46=pa-F8Q(1y85Y7|)aNrX`r|zcYA`F2>6Uq_=ptB^2^iBU2<&H( z5n|~u4y3k?>bA3<ItXlXfPaIkvEwqaYy<fL_4>mm4l;0YrE2&kLa2n{4kf<hodCyf z)dEj7c!lpAq+&l>eVTn2Vmj>QUXtijL@dJxE7A%o)P$}Z*&b!a4ba##7b>Yt90^RJ ztD_nuL@v=<n4tg{D)ZZVO(z8Ks3DvuQwG0cKEs}h&mqEg2h{6%_;}0W22i=SQd|iL zs``3{KBAj<3(n#R{{!*9ztD-wym+G<4wv+=p3n}&ZkgdKttuGDT2*()k>}>U_IZ;8 z5YDJnuwbX}CnRrB3+V;!L{g1SL!|T=@&y1Z$~Uh7&d<Uqdo)&W0XT0p2p3Ck7X4Y+ z0f@vgK0w*6-ArRDsOpPMBDrI)8jOhG`}!m~H}Zr}l`ct1PN3IzVl89W0_b~K$@C|8 zLT5p#Fe;EFr;12pT|8fx**X%0rD0&Kbv`M;XE{+!K(8Bf$|kw)sm6(&`|-Oo2F11I zs%?L<g&by|r}@F}&v~$hdB4xI#kn5ip79!QYcMFc4RqlqU=I!Jo&*~B4fJquCV(;L z?VnVM4Lg=(`9!n-`J!-0z;V1@)fLyS?%g*2mIk%LoOSdL%m=S6>noXh^TZzL0%W_s zhr$pd1do-K2cVkyLKK(V6Zcq35#Wn)$jxXd{~<V%(?2zwlDwV9cxRK2V2K(}>;}B^ ze#l_;72qa-=XuxL0cyWhjdrZH!g-NbA#9TvUmzZqGRo~Gv9AboM!g0{NKo70Mp<WQ z4$G{BKE`vIsLjw)Z`dmac*fa!F=nEKv`n(64{$KA?{7XCIPWPwQ@=T8AD%SFu78cj z%BsuvKA7{0)FtcydGCI$9l66a>~)1-j4H3wa$t(i&!E|vZ|UvS{y&ES|7z21m5*C; zenagN<%4vs1<=@>?(4K3I&g-&+q)byhu}JZw1zEKhXq}%bUfY0;AL>2i|t!MgT4)Q z=yR0Nw3~hFzpc|vN#*6)N-_DPJGJ}jC=lu)nQUyMC$F;cfJTibkSv}?v~dZ%`Z{K! zHfwwfHd5}D&EJ>O6KIjXsD3TukfM^6Q+$T43tg_<{HMB{*bteLC&UbY+V34E7(NEy zKzihH>0t$0#N~jfU*Bs>Ifq?53%y_&)=rSN*HC^FiAP<je`rku2WD$|61J8d&!ad= z->1qdTAK|C)%V07L{Bmqay_C>HXK4o0RwPrDIcJqIai46^%F_8^tpNTR`SplR!L97 z8GC;m=kh4nMNCHm9owt8c@#Q&oqYaSb60WW+_wS@ric$xk#HgR7jF=eDO8Mm`ck5Y z5KWk!*qfpMh`&ynD&XxOd=lGxgburOW%RS_1;H~*dch@npsZ|X3=XD3`)p<M(1GZ3 zzQM|vE+OQx+Ewdu$f(?ggOy*MR{kf#s$+ClHaX^i2>{Y1a^o4lg`q<6IS4R*kV+!* zZ{}H$C`>PdwD^@0Sk$0)hk|+*YI#{9i<a0Gy}<-)ldbVhB>eIBH<hrFA1MBEa9p3T zD;=<81peQz5$K?CpdLD0#Qaplj9@HOpd$}Gku0olh}m`j<4}ERT4vZb{im5}BQ$-6 z#fO&0^<f;>SKQs=t6|?BC$~mC=@+kmk{;)J@P(8IBVfenWAH+rE1%Ns;e&eY)3yM# zyW-qxeGe{kW18;&E(HeVG-%yRU7HKSY$_naA{O)rQ{+Zy=l`3ozho#jU_Qz1wM1Xa zycyu`FsSPH9KB&0gn|ONFnAhE+dHy3X~}XC4H~QMt(msw4mrZW`5)^ytWC}_ew9>~ z8!C;9K7LkLGa=EX75j)XErVV{EAL$q<6e;uP3=z`J~bD}e%?gE;nXf`#3!}oDw#Wt z2oyv!(QdaSp&A#1AA$LW3^8AJ?F#46xTpM2RIL3O7%9o&#`5K~r~_Y55HTDFah+Q- z>l>6KUQB``6mX@!=`8Y3bc7t@ZAK@G;{UP6hP(fU39E=9Of1GH^)VBPtm3I_;Uf0I zW4W{&{F(k?dX&;gUjG;1v^`UebUL;io4mI8lJ?!ZX`+}^5qo|j<js@Z1kpFChTn)W z`MmMGElX@rq1YlR*~A+#As28TF9QHGbq1%vUx>yyaN9#s)WZ2!{%I;jfq7MQz8C+Z zHB9v*O)>>RNmcARt@i^mdjRnQEw4`t^>ytbbop<~fg9BA^4UA^|FFN38dl`n#OQtM z?hpe6XGXbjKza(iM*-K_pzja)Xy>W|ntS2!_s!lhG+Zo(;<q+ZlkTu`IZkJQWL#}8 zc;qSh`viY|sl;d{Ws3!|fbz<DbPS8|)DBC&n)SXoM6W+sSV2eZIk^`)V@Hw*#MYMT zsoLd)@3hIb^TF*EPgj;+>OxC9x&q^(4kd6kkwnjkh8+=Ug%QJ0lDq5F=4?%ZC~&FE zH5U!5Q9xMC4SJ^tTvQ!Mz~b|rw)4q1RKgr7u9D%obxGCC^W3mnRo6#SC7`H;Y;SpX zn?kX#UV@vpQi)-(7_@)ipw7R|57wVjsgNlKgS7FaH*l%)ALeu;^?wwP=m`)lAzDMJ z`ZX+r8G&h$lAiLLlwUj5Fcor5rX>X9E%Z8NVNKNgKyx7t25e3YGkcRy+XVqU%41Nq z$}hp;s+izc2Yxw7_JG(J1^kK)I0fYckCh9aex_$3JY7TFp>zUD=2kk1w0_O3h)VaJ zI_uAE2Ai<G;yir|Qd9He!%fhFr<r&t)poj}iMepI{hi^)_htM$2nA2uZ>d4RFln<h z6K1BPn3wfKBA8ue5z&Xk^)SfhX&+(1P_rSllZn7b%q(;N%lib(%XirD-$kEKf&-5) z`|7oPyFSO-_u)lV(`-SBr-1n98U_@-V^mGW3kFKBH3UH7-n9asrabQ<x@5fUaKB3w z8@GCpwUdmI`=zH<-39lbAE9+MS!wPP<m&?bB|FDk0C9~Z&EkN4r;;pOxD<?Y7zihm z343jjM)&umUG|3EsYKa(?^3)`mY<7$??-mUEMO!I>JpsYG$DZd1(}GV0cVs=5T%zQ z&!v4D9F2oHR8gi=D0&z}&J3-_yek_tVaf?3v})~_6yo9JwvBOG-cxY<K|sR@KAzu< zQxDZ~JDFIsB==M0^b6-97@Ja3@YN;4A3J^|<_-&fb3IW{%x)J>;2*AAyk<`1<D>_P zRfgEN77W9fteOeUhdkbl2!?(^`tZEBQ;aIg!RfuCrGM<787Z4@M8!oYdr@3qceb}= z13ptzJ5e9x-Z@ES`P-`N(#P65_NF5$IkNxADj@o9J>VZh8|V~QHY)}<wzquQ4UP%z zY^E*<d%c+#N2O#)g~9GAHmKm;*<qeADlju!`bNz%|K@Rp@6J&?XjW{#E<#;wP<*M1 zBXI9AADrz-poYKD3oAnysC?Ur#dJz_9@t`LYqZ`V=$PsAKvYyB+9d20{m*+*yAnl~ zgKGPr8Ou;qI9mFoN+6^Q{7NY2{0e>k)Wy3!Z+iYy>IVf*A*^dzg}{okB2UBk;viAu zJay69``1#2Gx6PO=;(P+wV);713XXnHg+2h-b3w4y}@6RhdTAgLa5G{r=@~>ZenDJ z?RrYkz0N$T8)FZhdz11C{xEfFbIj|0bu9je!EA^}7>lRQAc7B%Mk#zpK$h^n8SC4a zB%lbpaI!`!D?Dp$_hj{)mL`GWkhIbnT`(69qYTvHh2y3VMEtcrlnaBR-3~Xn@{L6D zi31Z{xs?g4x^!Q`dHm^!_@)`t38Sp!t6x)=N!v^7E+^HyXIaF)KNJ--@|ZZM<VbA! zX4iZAb#0kB@f1RM_JA4!N+s>9Hpm)f#$Z%JFX~uOt-`@}i14Ea5t<Bm86vKcnEwoB z<rN>hrR_H?B|7f|zXdZHn*xduBce$hQhF>2Hi&V{kw0v*Avrq>Of}6}6t}|b(h~JL zQ&IIjLIQ__r#RTpI}I8Byay`vC;jH!CR1KvPhS$eaN@NV@gb|}pASh}?BdirJS_^E zupu_qe?o!QG_DU1Oj8v+XKYtqeolU$Ld`gkkS6XD31WkR*>~$x^*gk`FfNe=1(H-~ zIpaZov&A0?;(lPrEiq_(hW%p*szoh3cwCH;?4qPfB&$Ntd%cCg2A3gkrhMu8HQ%7y z(vxzNYHd@;&}zB6>#~Ii2n?PxO;_8EJXSO*I0n|A0?EZZCm?_)=%R?p22EVogQ!(I zB#Vwz;f+MJ9LCZeRUj$-ow(GAk}za7uy>&dp><aX@W7IQN>nXcWPutmR9@g1m6=8d zOd<ICG(H!%H@}(3Qa#^KK&x{p?Ur9wC>;k|2ir&Nuip0a&D4OwZ~tPoFSeoymt1uq zRbJt|;wSYtjSB0S3{N^Kt1!JQ^_myobdS};wm4d>-X*7W_@(%jtf)GKIw#Ga5LE^{ zq#gqjkv|X)#qwynbNV1XClgh5SRkBKUQJF@aUdR`E}~PXl+&|xTshoc>9+wfLU9Tx z43`RRnvYhmob#WlS@9(}z9$-hLe=O*NF7UjB)a<y)B$oM>if7yeK!ClNyK~MpZ}oo znSY(~oFAj`N=65cXGSDtj(cv~MtG8OZw9_j=$jZgQ&P5$lRN7S>QNIv8bMclTzh7U z!~#L}^w@w2|2mpl=mOwT=&xOV-dWFR#Iy4X>R-<-%snft#yzVx%-f85ItQ^Dx)U!M z$9#2+$9;?;Hwf&zHf-fJbd7JUo%>BnTRx+F&rR$rle^Tb8N|oK444d%Dzz9q{40CP zrM75007u!=tk+m#Xp}6U)ptAq@(#;c%@y-^G!c^1aV#)5U@b|)U<Oav0oep|pr`dU zfOOmduK|XINFtqTA){k+UFpTZkEI}z@=ov!?DD3pu}3~t*k-=+uD~)#s0d(>Tg>Bo z^}H`+VpO$dsCqaP6`INZQTNIUvE?t}!<ypJ;<1(MKuvFYtn{(MZ)Ul=xi8~Ea^FOf zyBkxb0o6_$R~r;e?#e5xDK$GVbBjXu*#to*t>i}FhVU9U$YF>1j9jD(ms^jwql<C- zRG15VOM)_du!rekG;Td@TqzrNd+hf0y;<J_Z*pvxYEkXA08si<kKx04$Xn@(k-Q&& zs1~NRbL-){BL;WhV5FyCF%;F%9cw`6dq1t#rQ;7*tW^0)k!DHpezHdxS^z@|B_+Vt z%4mgOuKNyJWvHaFXR&;+*kM)KfA2<>Q=8)5A<Td66s*vhviECF+{A{VHtm4B>N53o z%iR%tK26`w3&k{|J)rh-D8+C0rS;B{$eKZ0F&O^UU?TR}OmJfSZ6m~s?8m;fl-Ysn zJAQ9f;BfEZD%kh21VJ^z^R*TSO;!xwTJ^PJmea%N*WZ9o-cvgF@&F`$x|)m7)0Vek zp%gP<$Nytj3frba6f}<-V<<Bbx2N1yXIcgFtTu57W^o2BBiwQ}#|-`O4B=q^YQE-- zyi~z$uJEYkaGE}FtxWu0x#}%rhZrVGh^x2k>;oQSH{g$qG&uW|@n`=%2-$)fS;2{Z zC0l4>vs2!uu)<@Jn;<>Sb(I&tDSy$hn53$!B)k&5{J9H4H{15Hw9@^xWP=`_IF3Vq z7Zk#Nr5-liMId<KGJ&c=byY9Ra=qUxW->`^ur&da=}3388?`O+9JNc9<(4<2a2dk) z#~?Xyv+q@MXvtK3-bvAfYT7uq(-QKzsH)yIyUw)>J99O`d9DB!l}yS=j6+(V97U&D zC$A~h<xhaDGBt5E`X4k&X=)mmp2V^yCalfBCeKRc8g(9%v}7>1rfR0LY9%4|;jl!y z@?$(w#OoZl5ZS=vutI})bJ$GO_JFrd;i>o-=qu=5Ox`)uaDEvf@f#b@Fc~YgNG%_{ z+HT`@mTW>!DEK{x*9is#DWSq=&O%R*(>#_Zr$@1EM3rG^!gClS@Z0Y?Zy!d}-=B=Z z5_nPBu<kLn*|ui!qN0W?sb!@Y=yVYw+GC6v5G}^XJ^I#iUW||#gw}7?&V<`fWNo32 zO%@%<0O&^u-dD_|!tB?93c5>9L=vGH_nb%8zIE@|oQF**G7+_`gZRV%V`-aSVnXH< zVYQ{^MLThZbz$9Mn4^9e-cI6h`C+oengOCI!Txc^YEY3j`r)Q&3*L`ru`e_)Obd`~ z^%YqV3<Po%u`6Ny`qbctjSlr{&TpO6-H!tg@-dE<*s4RXO^qrvflS|-RHgqCefK=B zAq1%@Ok?CicAc>}o9y*cTHAz*TD4vR4{RrgY&3jXaqT}S5h|mc5-9^?Xx`02F<S$U z>N>^|hV_?);<xMx`ac5N*dYsnaFght=CpA00D3??>f3ZL+YlS=mgo@-1$tGbSO^Q? z7>~LZ@r{dT()8#g+uOv2G>xp$_LJRnQqtSw0IOWc2huFe<i3~Wo*3vx{IdD^yb$-v zVNjIlLFE{?3*3c}Clr>erJm=RYX1R}F)6>L0voLK&f;P_wEv*5^K`r_U>8zvAASX- zf^^oL3h8ctLfQQd2WYTO;iIW4pi11l519(<Atals1gw|A{F@lg%_gE9>4wQ~QrBbV zCEB33ER9#dtVrIN`s26|pp6cG+-WEsaIDLc*I5E|<Un0ZloI;k)+nGTlb73#iK}gY zs<ameDED#{LG4Eco^P~Oig1}F#zP8RVUijYeaD_K(tOnU^%+b0;J3+<%!Hw3$bhMP zuWTK&Am5?&U?{@ylb(9`z$fj<j{2*4su{nK-A4yQr!%@~$iuNFt5+o{GPsi%&m1Gv zd1oACLTqIRCYFbFw4X{@ilc5P3+$KUy(?vL;v0G5bd4k*cr*bOVyrVIfIA=yLB?TL zL`9!8O#WFVk!soT>%k+T;5O2DN2(NKRW|Dxy}XSlp9AXM?=|xfnVPYt-i&s1nhnXB z+{WH~`=*57QNb5Ih^>)UGuYB<5ZQ<8ya2Gi*9%%m5wUP5NA{<$=s>b7sLc*afXZ5U zI?BVlNSsX{j3J^ouU68zH=~`9jtzsMaCVIOW4WvD#7Pe%T}DQPXltJIJA+TVM#^W` z{lwD^Gsi0nolv8vA5um+>wFc6U$Pma39oU#Uq{2uJ0{B6Q4U*sf`VqvOat|&Q+l)+ zaykYm)LAGv2_^c-036~AXL;2EU_ml|hcW&clP3(2crO;u93c|J$TNVccPRsBH2P0g zj&Ma=b(4TNCze7hu{*^-(I8|~BG2#Cx{SCxa)Fx#pA{Zz49Zbj<i)qx0nB+i_-oM) zJ_f>8sIE<!jZCk_Gx829Xv8R`lW#W-t?nuIf!|c9|GfGeg8piZfpOL6JK%`6g#FFP zq=XEN_modzG<WGbB(WN<w{ni6m38jFuSGG7f#tXZX=TlcT9#sF^0g=sO+sO{b#+vN z?k{s;@SM#IpHrz;V57HtA)Zkx<$HPD&Q0WO@BnkRwn(yO#{dTxP<l2<m7e8p!s`&+ z3N0hZQchco!6cj`>iidJQmlAoTB^jehXHC|Ij_|g9={G`U~zGNI{UBl;^>2HGM;l8 zDa@f@^%!sv<GN@WAu*VjouE7LFE48{l9fl#GVQ0nqi42OfK+=HE@?hG7_v(_<hw6- zJAM@OY?iM!J&&p-G}9!u_?g8xr`1TJ@H0R(uh6fm6Twzd{~<Q;i=}({kn2_mL6DIW ztgS@}hU!|5%zEshv$aYLtC(`ulYL#Fs+@5%7d0{W#|4suqpi*jn#@a{?<ZirBjMqL zI7wic4L%%>xU$sbxMBfv3sU?U@C<8}*U#T@5`L~;(zEq}Io#(j(?!H>IZrDvaRhXo zD>$F3u6dC%6Y@FTi~f|~2GLAA6eET<*mYIMK|a$i2Db_xHt`8jQHbn;zwSYdP&bK; zCaMB0qcC*S?x2iRsx2Jf=VbrH?mQD|0I{0|yBKe==g<dZl0`5a#!577{BWz7K1m!O zopd9vpoQBJFBGCWU~lu}Oa@Ekuzytg%o|y>TzBU-GE^;t7>u)jpKcN9|B$iGr5_6- z1XVb%^*AxtuB$sGSknhv^#<NpbC*CJM0x4!c5*IPlB-s{zSHCPFsSF3xJ@aV5_OW2 zgvw=zG@t2SaEOePN0DNWr}AR#_Yz~9mAL%rDUOzFGB=hOI<{3G8*0vS-z)@KA6Fc) zVxnqHKAIYE<1-#=Rb&dOI_{Mq70{I6$r3rICFZ5luI$?`4#fR|3eDfaFs{CoNsU-f z*#VlKQZuM42}_=)&GPT*KtxZoMjn1h<b+w+$%N7rSNe`l(|l!Yve5_3x0xU8nD_D& z?@jZ@YQI<gHD}!zjV`WA9)ro@C^ij}CT@7(03XV=x#U=@JiFwHsr4W_1{~!7=Wm9m zbdT%%h}&}F^oCO*CiGIpKQUFq&0QTuC1FFf>HcDLyYB>~@1u$`%!%mIp;(;nZM7D# zAYXwNaD+BBUi%=d+>yQ)*SB0qa<_Qi&_Os6l#r9GZD}ISi|;_rz$$(aURT}-*rR_< zZp$ZEX(7UqY6|+J`K(JG0=i3OkA1L61hx<z0|D8ZoODvs<4BCWS%~1yf7!O>s-AZz znIk=)w_Nwj=v5~3D!{=cc&k0)M#r>3pU;#&j^nHg2ZnklG&{HwTQO=JaxxBie*zVL z1%Fd6sL>L}!rh5<J8x`~$mm`WdfnS;QkLgZr*rd~U%IG#nQCYf5O7acoR1{&>~&*0 z1Kw3hz#+=EA)uMA%7p-TwCD6w4~JKy*GV7oJK%!^xPcJnvbD}oW`e6iAZpzj5^S0M z?2ny@*Gh`{Di!KnmRxsN_(2sjHQuI7SWgc0TF+YY(q@E@#&JrXdGenT>jrRpI3KV# zkG9%XM8Bwms^T-4t6nxS_R2h3-k6GA&lz^1O5e0_mIHw{`)co5Z0%UO`?95Ej*+1` zP<^9ClZ=VjqJUUjCXD=Z*IdK>4{4OLdfHgQ>r-ff2A>^jxN5Yd#El>m?<(ioZIPjb zrXRUQ0|t?jwe}f3P=k=B%^MCWe}&wI<si5^H|Nt-5{Bi;G(Rr<LOW4>PSl|XBqw-l zxPmxVEeWvAY#{$#Au7q3ll#^?y8m?w@heP8*}`Ohh1q`0e|JLIrvzzx#cx>;#!U?f zWdWysRIZJ*WX{{l`ob+ber(Al%Sb~uiyt!sBa7!RS@WVKh<DE_V*t24FVURz+#Hn) z2m=1Jtvl+6#oMD~BbQS`D&jOax9-HDx(av$xP!!=H{7kip!OzSLww+y+)TW}JP=Z~ z=dM-ojNdBxLIkA^QO}K(W&dTZ`|dIb4qp*CSXcpWj0|qr6fVvFD2Hm3xzqhMm1_uh z_|5@rCVT?hG)9_q8f~)Ivv;6b$y(`JVsyp%^=kD7KX|tvL%xzqNIg6_PYopix9P8X z-}%5Z3vz-SZHw;9Ta?V{@mz&-d6B|Ma<R^8p2yXISOGV1{^+ADrp_=7zP^rRE1L#e zv`b{n@1_645`_p}ojVB!L6lY;g4_9z{Zq-Z{HOLw8^Ua1o^Y)?i0z_t)mmrQf?AC+ zpZNyw8d32)vJu^vgW2_n39HK%A~MG*tDa}HotSUwWFZHLsV(Lp2RFwNxQdho=;zn2 zukf3?CLDGnnetKI4mg@KAo4f^f;yVXcZWIWf)1sGaIxA7<X+R;4F6KeDf_|$G_`2x zRuM~7`^PQ}7|ESJ`$H_%UPNOgk7n<Z^tNT~QmdLSoGfpuW#bDcbqi~t2)d@eTjmS_ zALCE8-npV{O}--Ehm*pydB?myWS;$-r!v6G%}}62$75*crkb*N6o|m)BjGG0PqX@( zyy;D{cG!`?tVPl$^A+*q+fwL>2xNQ2n9}TOk;>vYjss$Gao+v*rrE<l&nSm-J)1Y3 z9Z4z`IH+l}1!`h$GyX2cRy|R~jaif#hW)?f%qDY(ww<`0@!2I%3H8Vc^crOt)of_u zJrbH+G9t~1hAH(s!A%S%vwQ=qtBNT(B!`sdkQgwdw|?KLH0`L$SaYM+;PI09yXTPu zkzegwPKg#W_$tbnz;2sJECR!JIS2`n4CmP95G$i6vmR~Oh}S61X5SIcLzg=YiKQg5 zuTg<c>i?-@R0<fs7-U3=$P|q*x-0vWySu6HM`s$07;4K&bOgsRSQ<MhulX&w$fO!J zB7~O1_S8Q$mGh8yHF_^~#SkfgTNc?D)k0hwoi#B}xlD>6yA<4+H~=xaZE;@ZI;H~l z)hPJC=dC6uoRVWAU;|;aDyYAeGDZdvy-rk`g@UeGFZdwxX0}2;+F&@l@FD(er*%)x zu-IcC#=1{D%TghbI7~<QVAhi${_N)uvA5bKtV~qpl?}b=dKmB!Z<*ouzC<)z3C6v2 zHr{c?mr(dBqxN(FFvoQ-;`pVR;eNL^ZffYr96H4AZ_$7Lp5O<3oL`^WZZR}*#V-vE zj?2PHIrWAYH-ghoy&UZ`fJk=!$+=WE#buk(xiH#3HB{3mYDm~h<6@(xj2#m0du~4v zEZv>5)|%KR>i2iw6jTmjw7~4axNKVulvJofDQZyG+pbyM8yWQS6lp>5U(Wo3h7LlC zXj)0ctYNu2d3FZUM-(4b>AQ;ec%YPyH)JZRRz~|QC2(?K=S~-{2O71)C)vqGMF)`T zJ}V7ls9jj83_b%B4v-VV_rGE9e05H~BwlHqOlFar)4byogQSA!!-u3weFlUYalr?7 zJt$pe?CFJok)3mBn=}(fOt1*I)#iVg(<|yYlJ1S<Cgd<|#;}X6e$zy4M3SK=jzej| z2Z{fTXOsZzba0e`$}?0*$KXDjJtDV-*)`1*b!}HiRk4uf7#Z*R7XeqAzOBfdP7E=) zVXRb}ee=Om$&o=WnXXiGyzPTZ$CzZ1&+?_M8ZFE7K)Ji!JqvDW;A~24Mm!PPz6>Wa zsx@{r&H{0o?rX_NTb2lzP`{lAhiRs^Dz0(`*CGu{In>YlskiV?hT#zrKZ;PT8QK(Y zd43JnvGKW16BJ9MSZOM=yD)rM9K{B)=-$_v@2>uS!<<i9?4DOO^{%C>+INLXl}AGD zqcTZv_jGKpGPFTL`&qSu2NmuH;#J?DFZ!Pa9ql0rDMwa3r)&R-?MiDx1gJJB@4OoV zz`x@N4AFQ<8Z_p9xMzauFowmOf&kr(x79zRK*0sv6y{HT>_k+yEdRCcgS9rD=1+SL zY0B#6Kl;hNNme(Wxw2lHOyma|;fh~;yB&>`<~4^OT<)P<1_t^EN(H>dZ=qP5l6*py z&VY&rdT_+mJFf6g97LAxu#Hy#ilxi#t-m3K7+Q`8AX*ziFXMMd0yDc^zNKaz$@&L_ z6LG(p;8y5MfCz-2j}rNJX9v;l(H~9L$}?r}*3y|q2xe_*Uht(~$`(V<FUa^973D15 zqen4w^U_!{!9SSW1Lc*-tH`@F0-QZ@WYl9*7Tv`k{XFS~OTo})hm$A7t3<U3Bj~FQ z=6_Fa9{T(?vrE1XN;t_fs`WpUfAO89iC6AxrT(}L?&D>lVO}iBjSU{Jwed;P$8f3` z3#8pa%GAR&T!mmq2k>(mcLyzrD3oCML4JtAfI%+>xjAQ-wVhGo{mnQ>Z~bj_?~70T zU>N+}PH-80`CP9qM+S<63L~x}@@#<oOdqiHUK2&L4xxgCOUjw<Dx-UB-4Bts?xvQc zQS9v7b>5qFt+!Cd+PUcP;K*b-;o~rvYsfX7L1nbOX>HZw?}fd{%CK~PEb@4Sk?Rl; z51Q5VXRc3H?{TsNxhXb!lK-Ls#T=5<PE1oQFT2TYDA2(K-eThu)eoiS*tB1@dulV4 zKO`NUe-U?obAE6vn2n(-v7Y<6T<wrY;pZj|F?b<{^K5a{^!%XW+B=!engX(hU&DQq zA2Y8)Vj8I<z<VIV=+rN{A_%Q7zhNk>Go4VeaN_kpBqJ}J7ei)Ibe=2|>50?#=Z}+F z(2AIcE-}V`MQuB~sEEFCqB(lAx1^_3xAXmtj#Mupb*j~{n?%S(B95N?pkc$5Jb{4u zfq9r8uB@nnuo5=d*4R$zyVCHnV~;DX>2&v(G^qxe9h>#m9d1Rd)(THwppIjw_|Jt2 zD0hnyBLvgP=;;t6IGLz&bZ`qnS2ubqZi+ki%dE@kk=dAIjheym$|;5!t<ozwKe!@c z@ZP7iO0U?YVl;W|(&IZSX(gB&QF?r=18?x{0v1;J@=;AJ+MaxNHgaEyD2?IGF%C`2 zJ)a*#uW+bg0yw;|%NpLBXW$k!SXM)rr{LaJW4nP2aGN3|N*%48;iJ!87%9^?s=1wp zE}hR2q?3vzuC<*|m&(lsxq3czKm4BIiAH-c@%~`*{dJjhRL_|~?LX3=?15|T`x*M( zBc_)aSt2UhBl)R5l3Ep`M_Yy}Hv!1Y^7M3*Z0!pO!6(JTuK0ifUcJxa?h8BWLmwjV z*?8Gut7NgdmVm`>zW%HHi5rxq=v7mW<$ZhTI3BQ(35Y?De|T23`9#}MP=*sqE~o<= z+d+L=6HqE16ERDs$akq69J!jW3f=-4G=c)RAp99olIn}Y*q<+3i`arCA>e`TCsj(Q zalRHr$-Q>}%4yva+QCJ|B}h_cD25Jvq{h%9RkYqQ1jOeKrY?$0L8guENt#gCXm+Q5 z9b7{c1Zdt4HRG*V^Wv*e%LHbx$Z|kLF9V$TC0?aj{*U>3jVxo-!wlclYg@{8wQ2x% zjGcL|BN_x+)2vntA}mL!*X$30ApUrUra_qazX4}gxBntr+|INg^YpO2?4G2VURfab zlAZ08X5v@|W7#f~du@ypLJpuXH4bqaK4F4+iXNvE1MiE;u+WhZR~Tu^x!R|~QkP4) zAYI|td>^D9vHkycjlPo#-7NV7VAK=jw4#|}S$`a^^`O~IFlTBUjqL`R9K0o+U1C^S zyXbanKrHfMTvb!<92)tq^mgSP9ZmH#`WJ6$XmjMwyco7IXb#6uoM>R?)MD=_SNf@4 zh}rHj$uyK`%h(o3B4D*3@Y3O@Jy}itp$;4x0==~&Q|;!%)d20!%09#9S4Z}A3>&!9 zFfoaD&t<n({#`C-mN&R2djqwo$O6zbKGX9MrjVYjqS>%`gWYA7h<YixcIP1cCj)+z zO8I+zZ-Xv#EJ+i-F<^qdY#zWW+zIgcic}p$r;(jJB!R49W(34C`w;Cb>V(AiuHZ%5 znCN<6_H)#%dYZ%HiGMMDkk?NKJixCUsXTmdN+#kO>g_ah<$JXV@Fiot=kc~Ac|-OF zG?W`9{RPV6A$xSf(#B-bS4%r}k?MhNJF+~!v=q<%E;jRb>J61&^fXFfugjvbBZTIX z$@Dt0kV-sa;yEpvu==pRSlkiYlgCh)T3N$P2v@{U!RSFW9V*?Stc`OXXg@mld&}%u z_0!In!8p~Oew!OTOAb_HUkY2aWuS7zuwz2seH%ob;bNFObY)JGHMrR*@`e9=DE~;P zlplKX!K*DqlQDtw_Sj83aOmpzxNHM<lLho+ul1!-U;L-TG2e0qqR6J}e@vaqjiv9} zvM<0SK6g4HMicLR2!Oj1y1Z=!%Sb4=pq3|jS%$btOqbA5z%9Dx0@gqzu67iEzHi8G z46XmD3k8K>c2I`V(I*h{Xd(=}pZN>fN8;smt0ZJG$5^|&n*I!k2rNFPA5XL7V~OuT zPVp@Ci`PrN7G+BOT8Fmn825ESG2Cpbjn0}7n>%`3ysA3;>~8#*7-04{@{xHXH$}Gv ztvrHDQGSyGXIa7&{L@F{Htxwgri<eEFN>Dc#2?)Lfr{;{6t(*zW^KflR(NsFLh$zZ zsOj8;q=1+F)bqTy8NQS5@L~jKjHV?69C(Y$Gh=mNHx4@lMdcR%IG3LCuLyCR|2)fq zoCd7Yn(0YA>K3^Qvy+q7&g=j#OGzRohkt<f>Zds$)1?okhqbXqfnlbduyv|9c@t0D ziCK~73~QMaeC1fmzIir@oPua&_?^vC2X3@n_hY>ReW6=1OGiP@<^y@btt}z8<bhAO z<PG3dO9;N!Ebun%eiLI277Gwc#!8%A(;FW&a@#tqqumBiKEAS~dL6kUNm9S1qgv)& zT4XrHPGKDdjh)V%{dR&*DCYb+OV&rHWB~W|H(*JUdvXXsQVjw?vMHJqa>Z4{2ZVr; zqeD3p!f0EXdk2?qi9~Mfdqiwh*xI?=V;ulZK(fC&$i;*64YXvRvKvpFl0L!9)EmJt z7ep5es^wl8j-Ksuej~A4CaFeaF0cxOaLRNXR|`Y&Sg%<8BJS)}=JR)qGP1T=q(^P) z#>?ZZLixBFxjNIP98u=cRHkVZ%>Urc*`ck6_Q<qNW~Si?Vd6Z}2+XzEEa%Y3wd)oY z$rh&d1({g?wfB8mOxa5wq7I*k5MtiXpH7)m-z>Ajo{~cK_~o6TzZ!RLIoA@TJ^xfl zJSU^?(^Alj=P^#+ng$^i?whGazE&C0V;vuYbcss-zI8X^d0>3DVG?-PcYbY+H~zvJ zO|em!Eb^nFOCfUs0_EGE?+?^E>*5Sk#_o&r`BRzGP|0zAP9`yJH$MpK^6jhyN4Hx3 z@pRLxuChh4o<GSU9l&sK2(}bUEENMrlvI!W%P;b2J!hzGI^=6UK(4t5#p=w$VQQC( zuGiX8hluq+mjuapTb4A<w7)!<`1ss~RbUJl;OBvF6%ht;bJ4+~tK)n%So{qIN}z># z<UHSvwhsnK2Oc!WQDHLEX|&Y4<}W<-R(Vyyv`10C&m44lNpRMGlIL6%&Mic}0;^$H zLn9u)=&Rb92>gnoiWN3R7hh)~f7ZhA{D(C~iVL7mI$FPB7vAP%p>f-!{eE(S{1@I5 zY~^y#Ai3qwhxKJNMaTr=W?CM4<W5#P%|7<7O!FRdC;@Z(G(0H_S@%JIN3uC1i7I^i zZ@#6(<?&)7qhFcY+lm@2jwy$=bMsxI7kpF8qmWH#RZxmKtSYy>$13dMkcr)bDrn<d zSXhrdwR-T{_w;WSc)=N_JZEZ~4C2$)-7;?VZp#tzjM~{JGI+0v^w1l=(!A?+by_Gi zA3ta4u-|rcY6jTiNsMI)bjt!i4cDCNsuQ1u=kfHZCE!3VW}M0>l(GTzoAgYZ#I*sV zKB2%V^2daLeBzfs{S{Z6A8~O&Fm*Cy0j;q$FQyJ>o^euF<!P2^0K>^P&y8W89OOe2 z9lwEbQsxUM<ckXTE!Zur?m1<+t=VwWC9?7kXoJuPoK?sEGZ#5jrqnkeY_>zxeDKlo z-WDXD(VM_S@F=c$7ngN-Zw(i-AY<$rwxi4+l78<?m(bLVzf>mV@BlC1qDFw#=L2X@ z4WQa=F9`UI17`q%*o^TjFgCQ7r-iY|+Tz_Lun6z2uDc&YMjC<xXr5&xk?QV+(yU2E zu4F~p0WBAd)1(O*rzujFPr*z~jzu%D0N3AqMv%iLbXkNvRO$_2b71HX#PksLT`swO zn|OyN_hyFM)Zs3I>)`osg>`ru-F4%7J5W$Tw7Jpm=bOYs_3Y=)+OZ*9TOA=eMJr|) zxYkgSvT(MF_CdwQ0o}cRr2{yMgI(a&ef;D+V&G!UZGLF$WmY^6iGTd(;Pcv3Y<N3T z567GS4di=E;VM1aw6K`k=xzx;mLK9W3^1jIc~So5=3=j-*#7>!k=)VOjdx5vctV1P zt53T}P$iJC_A?29cuBkE9uXk2p}_+NQOpO3>g~0>6btmS>BJ_SFfRE2^q{#i*hIIo z)+O$_7o(VPpG;g*(M#cf0(K&K7mJb87-aj>oc<?;{|nBmgP4g7XAu>?niYD_sPx(c zRcJ76gZ=Va$|NH^pDvCA<`HUYQbtl2Tk<W~^Ka-smi;D)Q4g;|@#J|Mab8)DNi;m` zFJE~xv9ub>>-CZ7nibiX{`<QWQIR(6s(l(>3~|+uru7f13bdTb@e8@WQ<@dK1v$&} zC!mTs5L~z;OaN~l8koWJ$1vP&*`Uz?*ySo{H`^2-<FFIpkAV5RpIYqE!^_8s^20dY zvI<#(`X@*B4$vmWn&c1rgi83ugzl({y#sYw1kZ|CCVk_~*nU>2LJ28kvx;S`LYKF7 zPHLvSt`V`s^Ajy><8RosXN13)Kx8XZGl6T@9DA}LBLaB~?5C*KMWk@csqOK8J3wd5 zD;e*Z+*pg6n-sO@sEKW(IJqUI5F<vU8gTA4{|B6FvM&QGs=|*!4OJ)~t*nm4+wCMz zUC2;lp~yfQrAvS!K@J;50V5TK8$P5vxY|KNmT0HP?oI5MKz4et4F)Q%orN$r@Q_S9 ztK6ws^6b`ko7gC)?ZL9E4EVKhY~;a$F;Y+9o<^)_WcZci-AU0PtwHI^FPWfJ4t0xj z>m{v<X?*L95IZ3+@gX!r@cD@7vPx__F2(~DVAup;8I@kmsL2wl$-#(^*}CYik$)=Y zq7-_GhmwZC$=YoABT4((uR)Y);lv4xVj9F=1eRZy;YX3BNML*@tR_Hg^r=<a;kd)I zXZM0**ie?RhBjMAy)cUyAiYFHk4*-E5_k(~yS~ZJY3kaw9j6(L`I5EmAiJ<2ItiC} zE%Piw!&J3QlQ7s5SKwg$_4?}{j+@k(7=ygUvgzrMM}^HExo4YJsPe6Je=mV!f|am} z&2u(G4a3~be6V4$Z<7ETH!8&ArrU9(4qz~2<>_>ZBnsww^-mz*ZmT3^p?jHT`?n>6 z;bl(vw#`ZJt|Y2gaPm0WCTZ#z*`&wvbkbxeUUIaO0|2tdgVF<x#gJq0qk#QHN&{#- zgZ+&wsPB#Jr8>YHMbBRAEXJl`p7H8kJeyK9y>REX5S~-5#b|M#4^?|8YH<?5jgOmG zk3(z62P-5IKj+vw?h_t+jx<P@A$zA)v>1+m#8Fe;9q-O-rk(G4r~)_K?tku|q&_I? zHs;X1(FX;;3nM}nI_d8s;wLFZlJ9{Cu#9Pe^Qci(+;81qXozSl)_YP>j??wvOKG5a zskI#hfxFH{i~+<Cj4>yA)D*vt(9$lrw7!Ns?4kW<%eu-Tc&Do+6l%@#)utK@Zo3dn z+N(+HG4jLnbpPm~9LWy5G%Na8zvv4z#7#v;so0l`5do9hN6A0m@rE);JcJfIw~J|H zc{X!7Ck#V*@E1WgWtv}ssl}6^MOh=h+)2?O(C&@q39M+y{T+o<XM>2_9SIKV<;v7T zhuDS#=oqeC$4~2bP2)(4UAowfe1r@PYmvUy2%*=EjE}<A6~7ZZnft*t96x)ZB-00v z(UeBgqj`9L)GWo7a<=W1d|T~oZKYoOogoQQ;e*pS){bN4>f_$ihacafB5l|*vv?er zVqcAivsa*li>SyWFPzl#PaO&v!Vs~pLYqUD6Veb>XIwsVe>uE$%Dwe}PfLSy*b8Pu zTi+$FQvSqc4DF1}#8>YPVws9N@XcaKLBkaUThmLYPE0j6$z9i#^3VtC*~MY?iREq5 zHeZaMPH@_8q<+pa5oy|R=7MU`fz86VwA!FcE7tKQU?N2Bp|z(iQocn2#x9U$^y<I= zOw}s0-xy{JOo~MVg;9tSJEkW#NsVi3gjI&kkPl(@sK>y#U;@}T9ds*4%Ha`aKK`y5 zME#=x5Kd|j!Zc(Zyr0Qs5zlMe-09gz8M!YOkHlbcHB&^MgJA5E*Q=k_vyb)=6Tc*K zXU(ZH2x|m}nNR)y5ZNlN!xlCAxvQjSE*i7TK&`e}4!w4U7-QOcg8Z2$OD=w01AXhr zkUjjY;gk?mN*{@x9solq$b!}_k|S6?851I(zODUwE;yaaiiIpsk(oy%Oet)jxd>I& z&@=?7!ObQb-5)ogb~HSs+YR>ebZN<WT%a{f=lDIr0Ri|n-~y!a>xh~E<ubf8`CiDv zmLe|vHj*2*m&R@xG7!UjZHFislo9hh!8rU4j-V$${{eUJ74?^%$q)l1Gb=ha4MDLv z!EpAK7!Yx0N(g2s=#yKR9=FV*8Moeu%bLTvor0(&aAZ?+Gn%6L7A@IeEQ~i$!BjqO zs`*`Se(4lb&sDnpiraCyubO=|jO!pH0HlMVSuju%)oKN?esn@<`F&t<)YCo(ku6Yw zME7@(lt#`eRd^#ucP=Sh6^+Q=S|Yds4_!lo+erIcN50*fMw&iLxpO6?%6d?;vWSl# z<*YZ|nlzoSkyNM-tdR||(~=xpGRAS{>6GYE#IulYkF)Ng7OYN_5G9FsmHpE($?-BL z;=M4j3{xfQhEhMp#elD<eZAF#==V1`u)j#iogGA;S`!TmCbt^74br*Pg$UN+%Q!5H z76CHCmVKy?0X6US2#IpH^xkrJKL$Kg7{lSgbpe5lbrvBGTcf?bDE#+yxX>VRRHM6- z>RI=$TSOD9aW4Hq{F>RUoLBHH#5;pn;35nk87rYxdOiJi<QBCbk-LHcM{n1(iYW#A zlUMbsgcmEL<n_2kov^DqJw|2#i-HGpC)1bBtMIu$((!<=`QAm<-IQKPOAG<*tEpo+ z7-^za<2n;C=Hh14bD(q@V5D1Nqg7y1NVNB7!K3Qn#x=Td3-3RDEs9QaM7@G+k@-fz zInr3JJjn`dMzD@~`_c*W^6XOrCgemPtxpBNj{x$3(J3OOG!B;Syua)hsob@f!&)t0 zc4l-bIA&>G1d|U1z=@d{Z5AR9rB;k!p7BAD0KQYYoVnQi*Xl{a<5T3vjyd_{YA7R@ zkhyu7IpU&DnfF(?_x7CXW@ttsFOZ16x*`x<)Iu+7|2ZnRuPd?wtVn&9Ao>8u$Ef0# z-xHTUxRj6xNAJ5lGq0g+jzq<TWK=bQYxYL2LugR`uvau_xZchb`?s)<ixpA>y4p^k zZ!Ih|E?Kezr{?J^jGYID0d_Ymqu4};ko2Yf{?FOb5``P8N6AEM3ve5@+$_!}(veBZ zMIM(iL)<_rRm?-4IUu4}&aL&zBTp^2V2sn*6R6yE`eYozQK@#9Ac%l~=$VlAWn2P_ zK}d`2v!2ALb>Ex9gQN`tV}(6M%r*mp(4#vt>bd7jNm5TEp%zW_p;}RWqVm?5!S{l^ zgFM+mBdnr$CPMg8I%O>tK<I@xn@e)TRYa(&QX$p%D3Y>l<1W}cc%!(q<1)vtae*jW zE2xgHZS2XVT@gb*_IoI&a9S*<AkS-!olqY|f~xdC>Jvu+P!|;&Zo^L@rrO*5Az&+R zc14ooXj|}tCIA?)Bfob0TC<N_H*(J(2H{Tet{VF#5*;?oMPydR)NJ{RB3?^B`PRT_ z){P6&IdtdDR{}9P(=ev2A#zYxk9V~S;$oUj=$$2vjE|bNhf!_~Ykh<3(6Ru?OW{e* zSQWn7Yz8xjgOn`&Y$vlD9%k45x;#aJ%zh)w#<Ab`vTjW|mp!icu$(D+?oKiO`cS5} zG2yI*Ucu0|*~43_%+^4CdVdEBO&`i$N!TM#`&x<#lcUNmWg`Uxo+~x82m?wlNU+cs zIewP?OR8K?fg^R}N2rs4n1`6NgX8HI4h0cM5$XFiYKwGRi?d*_u^#lHeOyLwj5ooO zIBh8;EkgnW(JMyNg&80BHmYSouip<wwCf*ez(mHuT7{t}YCnpA$mrV^1jslNKTBQ# zL;AH22E>!fd|COmw!z>0Xo~@+;$G#<<)Ov;4&>(LJgi(068oZzR`VX-^E5=Kx8izb zZjjkV8gVGeF7~<m6tDSt$Y}ur;#FspRp%S|B2O#r0!~Mt=~2Mk9Gsz%nqFxz=t?}p z`q>97#(xzBx@hvx5VPMD(I<GSMVm5<tJIZ*@ynQ~(uKt(I(ZDl87a!bIN;BLC9S#! zN$%<ow}g{xHl~oS%oX-LhTmMN8G<lV2MXWXWP-@Lze{<qK*Z@5&sRGYBx8v+Vmn!X zErmW(w6Q$$b`PK{7&x#HdH9v@j7L6W>QagcDtJ9Y{(6z${8C}#>)%y8Gx2?sB7aw= z-|trkO8;W7%WoSi;LF7<>7XY<JFWc<$Xj0LU<zNJR|i|-<q!1A8Tc5*{-dd4Su<9M zr)j2_ePAaZ&UGGREX%^KFgn9eu}6~~{<#a6?iNTBz6@8TICHQQ)med02wy;2=)O{K z`BW`(d+HV)va)$k!sy>bfbsQBO`UqkhZR3uU;1xA3Zq=sLUQAaZ=YEF?WDR;O2$uf zvYa&vWDN}ep@QS?QAC*jXPBFH`dIxqq30;l9s$`I3U6qZyco!~@LN_BX*kcGA0k7z z#>wpW-AgeE?w}&(8X<9Ol#NriY~I90kG_dcr%~a12KLS`C1;oX1a7>_wDGk56$bvs zJx&1s>vMCD`h=D+-9X}x$Jw-3mkjVqF_CaXk+6f7KHz5pl(W_lGDRrZ;)qr!PT(*I z57+zs`$Km|2PEfp%aG@o&y@|l!D7mRemK^kAxt=$B$ytlC;%^|acs2`0<3?Iw5j$@ z-IcD6a5eqy+%JAIY^iz>JUJT?w$tgpM{M9Y1ts&h;B2nj2h9B7@QOr^WBCl=)Plst zz+Mhb(%Zh;+uSWUvCj>&9udv6Xfry?tg}KNV(()x5-um*^k28*&Z3Gu8lg+h@jW=! zTJjl4JGetplN-w{(n`a6fHFtCtLfKoOdhYq&R|1FoCFHLG`%{f-Qg(&pcDCb`V*Ak zimGU>fg2}m!6Rg;;rnVdOvlrchaQU2cb)-iS1-YMY|}@dO?-j6DHc;7gHo7Fn{S{t zS@Lsa94cu*8AyIzaKL7W)-n=vWyIo`3?nMDX7N#F^*KBHw=i4B2_&E+?}~#y5x2|6 zLq`{d=fLIR1@%#?YoCF69ex5M=tW%&wVY0+$Cr|p1)Q`>kU#1HrnC-oo#K!o_T{}} zKcdJM-<!e39sWhMwoI8mC*YQ8*?)cwZjylX)vuu4OE|PF={mZux5C4=!ytkdyu^K> zyGoS%$`m!hI+hoqv2@?lT~gQd_G@pYx*0EExX=)p-1H`O+_h?;B)CY7Ajg<QS3{`B zwmtu6nr-^Etm;M<C_ms2Im6>awHtHi?dBFn08=J|YwI_0T2kbP1zXGnCrzbOk*5_8 zGC<||?Sm)C<+uHN`{wVU=rp$dPFB(#*kp(5jJ-b5A8^VMwbFB}_QhN`G^9ktY1?MW z)VpYzLOz2B;zxjGF7X?|=vj_BwlPqUCOu3>houDNKug}t7_(c8a>PF>-4=xGTnhFU zuYPeS?Gtv5;t^$ZKS_E9v+Us8kLMeTn`-ySwZ9UtgR`GS;PePoA+EH@5+Ibfl-|(; z?~<y7#sW0Qc~U+GejqN2V;$U?8VKY}G!qNBm8&#!MuQ4CP%Om5I`q%!p-Lbi#CbOl zYH$0E2d{PYXhIn7JPLr60ZxQL1n$v5JCzAESP_2r<w`#enhPIhQQPHY!}gN$)hE#d zjO2q14oSpU#3it-73M86FNI05;qZ8A2drR#D}?0i;oHK)6oT&6m<M@8KoCgP$s<Lo zeWkuxJkHvFtK;9Kxct;iGZ&a3x3r%Y&ge(tdV8tO!0aZoXZOmPE3RtjAx=wavV=3l z&>#n=EmHW?GMydJRuq~A0Cl*L$<yN#-;r$@^8aw}Pm?w7BK0g2zMNm)XYukaK%5Fi zb*H^z<gUs6&IvqB2ci9y)yX4{e4|@Goh9Gm_vszWl22N-*UL7~#oe%4fVj0;Zy7S8 z*h&~Y4M=QYwMFd-iea<A8Kvg7RQ$65v8TlKMYN02i;S)EmltX7JS`;2mP+zxZc2-n zy~~RoB^lIn^#?LY3E~~1mDji9L8DnWT{Iplf*OH{mdn41h?nFq&N|JtIKynJ00oeV zBJi4Iuvgztgr10tmXrZ8ahvQQ^BKT@nq<*%QERO}>0tD)WjK)kqp?Cj$6z}Fl0hk| z$mC*q2d<z!(t^h?Qy0`FI#hi3XSU~rz?=hD!zo{(<1AO%M*m$qT9`@7-T{af``Vv1 z9{3a^N$*OQ?F-&(SIs1+?cCX|H;OARHer?~8h$ZN?eq5V+014gfd&*20t9j61P7El zn>6(xGGgtUS!{K%PzTXUGgl=e<rlID$%ba|WZH^iDbJ8Zb5nI1*tX%4AxIhTg>+oa z%dClb1OH;|I%6>~c{;B+bN{Uz$gvV?j#701>bdR>62WcNq?pop4X1mAU8&jWTz?4M zh`PL?WE@4L^wuQe@$v1(wx@R@-FeY-K=`A0oiuf;W-C`5q>4a^s8h6z{EH-72ICi{ z?7wJS(1fCI1ra~h3qJrMdt|`gf9up6=N73fyd|eD94#!x*LxvfWACgf7*$sC!4xB` zf}<iJoTvE5LE}TouCmNO|Io%y0{S5k%(!0m8(2n?>L~w$5?$C%9H)DMY$`&}Jxm}B zieD%Bk6fjgUYmOnr4Pl(3onNX%07)B7it*V%O~xBw#eJGwSo;r=yUeVcCR{MbqK}* zZ7|nPCaekgO$qK|f&NeSxs#-A#!z37UlIc;B~_OOZw_4(vWSnBo}NJCZUR)r|NJ_^ z=$0Sa<z1p`;M|aNV${nz#CsjXp64{RV&aExXe+TDzv%S!?Tqa*!XSt&iV$jTl`lp` zH?pZ1Tv~V*sVgDX$*i_&WwigrBdov|FcM+&gXc<rQf2Vk;M@h-(6_od3))&!4aVd3 zSkvFTv@_&+SM&%&aOv1&qKAuwB^H16N0|r1IKQVJrYxJkIJ!b{a23Jtv{dqL?kV90 zupjavpD!WfeAfpR0SF++<rru#6=4JLS&i%8Yo&ZSBV7oh;rt!HI{IPcNa8_%PW974 z^<Sd^Rc->7CwiQy7r++bu+{>S+X>bm=l@1j4kBb_>CJE`P(APD1Y)F$$27x0ViJ{t z=o%S`>z3gdJqsO$dmD2K(6?hLYGJ)Lv`>uAZGx4$;{vf-18ZTH6{s$RWVgcVycI+I z=K;ipLz{o%^vBN?4hlgs(v}Ja=+=kcK+%8?nYX`?7o2bkQZt0AoqyV5H84K0Z@cW9 z^!*qVcgY7r|I!YTJpmy~sU8*y$ZJjc1ZUARGYq%Fex+|T_TUC)6^M){5d1OWGu-t9 zYk!zK-ZswTg<mUJVl?mCJ4ivPa8%l~h?)g7bid?9%WOL*!Z2$4OoO|f9N5tPrgkM@ z|BZCvX^F(wrN2qgArZ6{jKtqJE{TohOBctMYp*&r^8sYkV98Q@U0vY?n*`5fA1x12 zlPmp{r|hemyV)&aFlT;n;cddQ)n(`}G?r8Aa%M)pq8j=)NhnTy>G3AXpuH(J3&6V! z7kSo5O|9_QRXqe(s)aiXj0UnUCO91H)I%5!*K1s2uCzn@Nm{mjy&tg9_F2^b^4vu| zp6rsNq=B8Da{$9|cBlQx22nBQ2A0cljW!%cL268<4>o_~fJ@d)8BoZLDcHvC1qnO; zLIrnpPN(behVf$0>O3XH>K}C9yMb~DUK*q+Aqo#arq{#!*?0|XLStbTvX`GSt||Gc zwCtlcn->J5<`Xwk6D_cx=ePq``j}&4VZ=gkckY$lX@wSJ)SXCsqSvw2MYVhrq?{nF zg}_|zb*0UC9_mVq*bai<cc?BaMFrijP1gSKzXofVh@_@MlA~+9#Di0UE;adENvg%^ zDk#+3aWh}6S&(5EEO?TT2v9e3`9aAPgocbLFFru-$CKI;>`{HrThSOa@t%dVwa3SP zggJohM1sI&;67^5d*|j9MW<ZUFE+l~FK+1oiQ3x(>Y3L`mw~jBr78s%_RqrlRj`cH z_5+dmAE22f`hA++_ZDapN4Q#b{E5%#10AV@F#sk!G7adx63DHTpl=2dTm*dY`ux`@ z@MIGCQ~Q-o%we@S^l)@f-0{>Gv(pTYoTY+g0pck2veVUk!W}(bi<)$Mo%Ojy@DeK0 z{VVX&`qy~f4Y?W$q~F(k_2h2@f`~vu-|>MYEo_U%5p9U>1F}vPinnC>Z9|zab4!n` zHF$SgZn)c&xrI{n{~N@77WIes<{eMI1{<^t$wDF_>uhCkh<5iFKx?^rR{WhEf|=|A zt(eDJd1d&IYI4X&ez@ALN%VGYrO)Z0`uS?((IQ}8rhA5&#LVkr4R7nTF#SKRqyd-n zwl3oA;TRYs8Y18tBl){+E;__fG8NTa8<^D`Q-SuC<se9uQiE9Th9mmd>WwndbgDQ! z+lvEQ;C5D=qWV6^SG2ckWld%wEl*3(3Rtei{07_UK~4l9oxpIj!5)UN36PI~N+&yn z(Rc)aM8p$z97`fE8^9Tn-@!0ql6krZ8G6R5x{WM7stGFDb%kQHLjkl(VYG_y^6f4Z z3e&sk{hSNu*GFq3Y4Lo$LzbiL1IEliOURIl5EW1f)MvG%dNe;YWst=HH!@WJ5TW_4 zL|8tHmaTnxVd`*uP4*G+WDX90z~Z{8tk^dtLBIT)A|(NMrB-OE4J8TN+lofCB;sMn zkUwe_6eVHnN(%{nXfv}p3=bN7OiRCy?TYBAZ2o6Bj;U96WT6exNY%erE~xmlk}B#^ zQ~!FqU9BG!8|f(l42$Gri?S~q$icVikXDo5VLrV{UKsagvzdqjk9>)|6(_m;@19pt zlj}R5G%cZowkuC)6X3=$pYkfI`G-!#7%2R>lDtzd|N8rrBO0tBbf9y8kKbZ`%(vum zhH8K9FMsF>#poWX(JZuEfM6~ex2li7jgvS?Dt_u{<Kax|P2~uhSvp6OctXokU{<VY zG><+8&7+&44%xj_w`Rft7bQH6674+iqjL=;6mW8|oV3cayxaA&Z#fC;2tf*8K@gJ{ zOwXZYSbkJNyJ}S&PJzwe=$irJnq*4XK?7xo^+``$ktog#=GDIvw+YFg*Z^wgC|QK* z12MalBVMSqd9i{AtxD0EBt+4{CInLX`vh$aqwoj{@=9Yg84{%>v8%aQc+o#UC9lRl za(81Gy_prN*3wisoKXuaXMZ?*JxK}iw{A4T@czFFX(nilSlu2@2@Lg@F@@T;ehid) z`DHA;Vo3OW$}l1pM$ylPnS*gRiaJC=g(1fXVbiGZ&I;rrk_kG!PjG_N^rs`#ZQZLJ zIRPMQWZMcPo>^2pzYd%1^s}68r9EVA*keWygYL?a$V6X9IE?j0sP_b+m4)kN0drq8 z7>PX<H+AU%;_IK-?Pq&xITY^~MBWQVvgI)m@d3X+3Oum#IK2mD$xp`$Nyf_bWgU<R zOe+NuVd@aOJ15opi{lO352vt?PzJ=$UxNQr^evr3r1;G-j0=fa!rTmP%Rj!gE%N0Z zCo*PH2I9A5-6$`FZ5Xi8#yO%yPk%zfxkHP9#7KK6VC%qYOO!^14KmAXM0#g<1TRqq z!=d!%vAlhF^4X{vs(BXuH|P{FH^O{dI8tR<Hf+{|CwI8$26y`kA~DvX10w|7rL*z# za(ZR|%iv!LF=Jl(Q?%ZT2<@L~5hhy;eoOZCf&PJE-Pgl)M<<gna@}-UL#Ru^(5%Hp z*xH}Yv52Ga)3~cD9Fkj?xSDt#(QY3S!-PXUIIp~95akLx1f+*Ut4=qMrw;flg38rU z3d2%v_8Ax|3`AUeFxsbfRtS!&Dlb8rzeR{^;PbG_;f>%Ehl?;mhmL{Ql@O!PGiYCG zGego|Z1HbER7wY}Y%DGpc_l3FZ;EFDMteMPiQd7`$}_l2W?Wmh&37)Ojq8(beT_a2 zb+$Ezwp_1J#?qhxbzrv|u*7M0|6`Sxyb{A6-zI$ZuAK-S^HSEz3CiSDKEelc=87jg zE|-VWertPpPUwB`{f+;P?J~bqOMRX7&8kPZciLW-1%n)<xINQv2pi+xMC=Z3lvvqq z**OogewJmp?11cOa|F8K1;i)&|CtfsnEaRpjqAFJlJXWCuJdlRW14?TnQe&iUX$<@ z1598R@w!343<`PFtTPHiA1I9!i4qsN1IzZt5sGX(o{xakw+C0xOMY=E2&kVc?BCSX z>QJC*+%Lvixu}=N)M2Zc_PJh9AW1GnM%uw)y%Wggq*{FR$#)|S9)QANb8ZLJWZ3cq zC39#m;9=w+RD7{?_9(hC%Y+FdV>P*IaJ{*hRa^h14z|HVT<c01mmIrD9)=l2t@Cvq z@P*Z%`xT}L*O+Zy3>V5CoMcO~s)B3edhM4yjUC6pa<q*xBgjrRMQpt%o+-Cb`U*kk zI2lybR})h>(w7a;@o-Ec^j(8hcMpx)Hi!2TuWap|va?~fcy~$%4!Yq*Jg!A~qee}Q zB&!kao3<8QbRZzK1OQ({YRM@Fd6|_^XlBLW(Gd~)I)!J_iAIu<bFY7dAiCM+uy_h4 zqy6LPk~5IZ>i(4wP9p?a`iMC+60WuhKS|`~f`3kfk5JeeuJvoQ9<~;<FSVr`)kh$N zb6<td?nK+&2Bje<Rs!5_y{8kb!l$#=?~7Y#a6X1!HA}2QqA?0?+p0%SC0(BMAZ`ep zuPC<=gn;1ZxI1-blAaoG`<t8+&eChrMk)z&qcz7ldJb8(49-Iq|C?OzwSBxV=q-GD z1=iyTHHt&lB4;80Ik`6g6aWzC9{%%RMH|>%;TXh5IFar$uxF6fGslNE@a5*;U&cHy z(xh+)WtFu!7Pe?j0g4BT+YRhPFiOy(^<q)nmyI2!@5QEGz0Mokx40L&Kbc0xQ>Dw+ z+#Q)zv#H8o6%qeCJ2>`{pAay05{iVg<E$c~>7m-hayiMq67v<-MtN&dF6Ff!GNuh2 z847Pp_Ml#e&-ze_G2zy`D~+iR<_@b{aFY(wdKY?o{5`Lmd)w#B*7h0mCd_0m3`xTA z0D&7|^4A3ZCF(*boC<$Ey7Jf!d4eT(;rPF;<u+|*Ou@Fx2o)dvCZ>sCdH>Umtjy{@ z9et`jXWZf!{>Fv4ACSu#INgH?Ol1uhmKs#$MS_*|l-=?E_lACy(7HgB^8wSh%*FKU zK&3#e+=%WR#mBC2mDtwoi)71~!eIR~>KVdUZ{8HpLHk*)tM3|zJkt13HkXMbLyPM~ zEN5<+`=W97YhCz0ESf<m=~oD1aAC5n)r{oo@hq$^=2<W6cR)}Zt)cE?MxkH(+yM-H z4SPO*P+iJ5)xO^1ha<7RAYtSZu=w=QTnmfh{)nT1&FLbE*%<yJ9d2-elDr(hpjOE% zExwbEt09zPGOWZQ>=jL!v<K9|=7|-9RAkb|k2%aLvC_jdn!E=q98)tDL;~F4zV_L< zdfJIcUH|sb5AC6<oiAeF&Fo;3gR{zbaAWVtuA2$-thKgj6Mq6I0n8}^q5QF)8$w|i zXAg_We#u*Eo$fMFVW4ow!^t>7a}6<g_Jpe-!#^Q#DNzHMo3XY+Mq52{7#w#uaU9Oz zjDTAziYA_+e!!PaR%pTLxV`hENk|W=$?Hl1=2AUhF0F>H5Dzv4zPDorDtGXLxLL~P z@aUl5r-x}G24{NJ*G!$g$xDDuZ@HKxHlPmf`F}Fl@QkN7&c(;>ey1dy)$>sB{|ZXj z46*XB?aKxmY()}+1wO%__?<E`SRRg3{lFUpvwRV8zwz>2xK6>_Thu78hkl5GJPp6x z{o5EzGc4!?Q)G*f|5X<eueG1-Ce!^he#?c1i?ub9B>>Df0?$qHHH<z|brxH1U_V0I zMMggmx@DFZRc8R#7(eR--Rl)%n>-53Ik@$1FJO2<+`de85J%F}V_^W9gALqn_Jp-N zM57G7mJ~JMN&$mW;}iUpYPQF%!?#ywx$@_VZWUn;vHjO%828@cp;vLN)5$_co}_n; zC9a|~bqjtg%WR+o)ie~I1LKO@>gJgZjOgQPYKrRInGL9BJ&_Y#1V<Gbfk0}<pTj*L z>zM^{c*)03*w_LDy?-GI_igW7EWx>;JuU$GsE(_U6`O#mn5x8MnDYhP-GF)*uNr;( z^ebnOdA#s?uT+XmV!+`^rB}kpk`B3zu#$^DvUc#hLN|LqFRp=ve_I{DU6%Qi9u$k* z`05p&{d~V$FXJkEX1S+V&HdI6ziN2%^72KOV7*rfDQw793~BrDd;$~*CvYtkk5ItQ zb2AadnoCht%G<gZ7;1s%43VA>IESNjve>j^^3Gbxv-dV#(_M-#Ld^eBSY*w5bJWoM zhl7qNO-1%LDnkz#;(Hy|OHEHAQ^_x1#!Ejk?XL2qGZg~7HL*>*%aB<@F120IHv)4K zWb;HR4%9=k5r8LgM9Z~RTrZh0n?@_-78KNo2N3`8Hus0(cLy=0^mjUFUT!8q&USCL zZ_=G<hr@q7{?Vjms(M2ED0{KGmJ-<*=-NdrHk%I1eOFu#(DnmqN6X`enu?B}e085F zOTY9LV0QRI(XazCi@)LgTsc;Y@VV8roMRJ)CuIDqsXfPkYf$^oP3~O(0`ZZV1W5J$ zD@^Nv*%aicz2oldJ6E}f0%J91Q$?GENO)^GYbv^>@nl!@v@o4)(v0PZ`t%|I$#~yP zK3tG=b8t?L!#)H&-8a>m;8L^?-MxV{-$3dG1hLk2Z;_gSU^+#GS$G0cC!>2#kX7?> z*yC3UYwGa%=CuEVkP@%PR0Z|lv=sQcGfu?c`Sx7Q?s(xFu}BW7hb>}`+WUP<_mbCj z%pP={Nx6?*WVuIiRYR7|Fv6Whcr)ehhb$Y{qgh1?&}1az;$L3^_$cw`%(O;*(k}F6 z7~63;A)`b_TXhz=F}bRwJn)(efZFDnU-bJb4IriHl0onKdU$t3qM>Pw2JQLFce>6@ zn`-#lb!`3BxSDii@k{>fusF9Ozg*uv3!e`Ae<>m*G<Ok@F@OJd?{Bzfw+h%GAK*0& zTe0(m=t2kd1wPBl%~Ws;?6tce+B?9VJu=@QE5^qnv%S=b(Rqg@iymAy7}X^EN+<|i z;{6V%%oF?=u+HPQu5ZuT6}ZJw^p!L=9peSn=A8qGtN*mQ0lVJS=@HI9h*cSu6k$1Q z!qM7gYt06P_Y8d>{=;)a7dp67t3E>RsLb|?cNDew$@P}uFXj39rV!c%u1ulb5-Lcj zR2K&{=6vwF0D}Y_?!_8JI%=QV6uO1lcw#5PrJyrH-ack+nyJ2)!MFUf&Kf-wA~Il? zR6{P;h%*RBEElUZo)sP}8v=>{q@f?l+|j97lf>o@{N0Edf}Er$BH;cmr%K>tEW<wq zAi+|{F93_Ew+K&@OMjRhB%UF4p#IKbs$ZmUM=$j63&ke%lS{y`OMIfD$W)v(T<796 z9*<$`fobJ1t@C?c2Uj?eT)w9N=oxEq_BCv#j40je2!NY@oW$dNO`W>ideRtQ&%5^| zR3r!t%Nd?hR5elB5U%)N5FneNXaHNzrPA2vFOyH44w>>;jbg9i9xuQ#sEZ%iiyH)- zWJhJL+?=%1v4Lw)pLmtpeY7PZ!8xldmhUr%Tq1v9z;F}IwV2R(kSZX|0D;vL(_l() znGW39u(EPD4(s_!)l=@@nO>p0Ldzzp77;1we~vW#Y$?T4m)_CS_2F*|=cve@f%kk= zy{%{=)*o%QSoQDi(eb_l^@0AONHK!QdH%cquf4VJpbCmN5!k=mQLh1$EoL*Y3M+&h z_b2w>_E+w<Mda44?-{X?8aGESyOr`2{k9XY4QU?(q8Re-rw*QtHvxapg^t=acXDgy zf*!TZ{`P*Lt?iHQ4u^B3CR9R8%6MkgJzeKn?T~WoHxEO*e4RbGbMAuIH5~!5qb+s0 z=R+Ouz7Oc)v@K5Kt(jS5c+7hJ>HJ8Niv0rz_-8(!cb@7g5;}XyXS#nXM*j)v>1VZb z5zH$4RxIDX2=@J)LrEIPGWtNjESq}T8NhDDu@AbqbqA5o46(%Aal8rB+@E-M4s1{R zGLie@5A{)M{-Fi@m$EfIx*~Re*3MC`DtQA=)#l~1Q-zsS7R5<TO$J*)t@qkJl=_R< z)L4oY2A&bg@YBX{H4KFgDBe>O>{4l?ig~0mPRhgL7H^_A|1qWUshB6ZdQThAwMNd& z77wV0>v%5gnDLch$aWov$xI#aafHK=-vv2AJzU3EnRD8t@$et?%09bbBQU+NWNQAq zpxU!E^>?zs|EC=^#Fpo`ayJbu1p~%GnltOCSsJ$z@cCzlfYHNywyP`#QMv=w_`qB- zoRf-~t1>(G0_`h94U?gAFR#drQxQxb+H3ED60u?N!oY&7+Po`~aFMH-KKB{rqdIaJ zQCL{z?s19_#7yfGYqaSL*HX^mO=V5=m|+c`(Lm}HHhWdQpl$LXg>Oh>n07wkpa{6V zG}(n0jlhk|xGO5hAz5pdh@q04lZt;_(uCqMjHrd+abr6qMz<D*>B$Awvb{Udwq3Z_ zNilk*9F*a!ck+rWqOOgpLhT<KJVchcONOSBzdQt<%**^tv)(h&LZ=I#w?vp8Dd?@( zB702SD#kA}Ua^}O;^`nS99G6(JX>7LFR&jSW(~d==%v;;gWZ&j&q@~+Mes%8ol#j+ zRXWt^6<Pat(44h9Z6;ex6zNyE6MOw7+KLBm?%{dlFc3fLI-7pqXs&5b+U+@g&D7Bb zSiH1HCYA2CT#uwfsnkJ<=odQ<j>=E+d$x9pz_?QwNL?ijti&L<J8i@mN%h{iAn-8F zm}_WRj8583dS;`lbgNp=u#tQ{4-g~uR=&F72@LOjoy3(Reoz>?*~vUbwD)LW*?iop z*^skWahdN$OMoFS*ZkEPhVp4ccY7t^ty48mGTKryqXuekxbK{jvuj&ffxN~-hCGHy zT4YxBrSlbLh)dE;CDqF#=mUPI2fiSrCeWnnJ!BeN6g|WLtHu38xnPWf(os9wZu@Xw zq}zD7-#V~43QnaFR1;6`M)PfCU8xdc<8G{^w%6(0qh9=DS*>3kIuI+slYk>yJ0RGn z&y_*&^Z>9#n{QJypQEF*r~SdDlG?F7xmkEur;o*)0iN?3-aNKli(r%V?Yr)#W-7Bi zpyvkElv3L{oJ^2GxR#$;kPG*%0<|0-^;5*1Z&o1U>7IUrK({S%(egxZi4i*=JPo4b zbyv5BRcg%8?H^Tdw~(7;#}7i!Y85;EZ2)YMJ2fGxJ1YG!s1Ry**r?VEcrHV1@j-o7 zt)6rvP)4+Q3ntfhRdip}BC@ac;!@ryD0y<z5@Q9btuvVpmaWQ~fu}1TWT+^IPsy;_ zx)pN73+Yh_9;t;K<y(JV8*+S2)k_eYwt2~W8w?Q};<taLt3IDs|L#8ze)~BS|0K<p zvGdYEf*-P(K#smi@Uxd|Q9idQp#1&e;-BvhvA|<@r{OWRvU18efKzrElMp^QQduyQ zu;h%j5cCJj#wFIIe7efT2(RWj@YA<KH|57@UGj!C#=l+aAw4$IYIpV{Iw&O1SJ(&P z2s%?Lxovttt#!_^0&`Q2j8dz!p%BsJPOTr`WB1iS`0k3C2%Oivw7YS1kYcKe{g=op z8}b%hu5KF?4PBpp{dv-3T!<dlKG((%m)3}gT}o=*;UXPN(^19!G(9MnzUW&;(^ki_ z?5g{TGpra&%&>#y6)PJ+&3jDrOm)!ywhA#dun9qT1REafFR==*nWlu^;=VO1YV4!p zLUz1BbDg|jvS`X%x*|00tx3?zR!7l_3AKaNxX0Ai;<%eX4VkxBw48)?laGdQXl*Y6 z9__$i9m{_!Znngj#DZvrO-{!%{pb39T8mA<Ym0`MD<``8vi8vnj($Nx-b;#V_W9a# zCrk=lY2cpbS8Goj5A?p5azvUEr3#mBJdB{Ek^oldCUGoV#LPwKf?f4I3?!R(awy%I z=*>^c3g`&59-#}DeNn!FLeGu&@Ykx0$%$-Y6a7zSdw|}GgPP@D_K~fjcb)HPFT;`& z2E>y&@(N<Txaws{eeCiX49i*)&4=2Nk9qP*r1*VmZU5b;L>>DEVzo!_TGCYkxRYcc z?nx}P62Of}aR-?wql2s{V9I+1MFm{vCQ-aYG715AyF;81G@*R3%q6|q4TALHk`aL& zh);dWofSw)?12m@0byfg#IQ{tr<cDE$KSOEF<E8t4e;uoDD;j=oW;)0B>rwrC;3E? z+~2{0!^@y<lXN#WCk*7gRKxH>hb67$@t0tMbBr^BT^jY@2uWZm^}B7&Rcs##z9SbF z2t8qaDXiK;y~zi=l{TM1j@3RsNx;KRqs6oV6hpHF>Q{u<2{$hdBC!U0_QXhETH&(q zN5>68=iMk(d%uj#Yi85?UQ_>VLaBBR#>D0sHh&fA_ZCIlC3u|2zi0b_H}LZO04s+5 ze2DX>t-Cl`FHkOjc{!+Z^}LUE*E=#Lx(=uY@bh2CIWtqJ?og^)eQve;a_0Ns71W|6 z4={vwE<mCjp$xgW!I{+NH+uu1YrBl~(8X*92;zuE*ofQOQWPZugT>NB4w!ht?jQZ& zD$)+D@=k?gd!4n<Va!4lk3|OH0WUGP1tvVxqTjKt+7Z@PdsBn5up3o}&3}trl(Ury zRIZ0yA@D52X@YGH%->;a9+IwfuP+%k<3kv{84143W4Mw7U3?|f#Y5QI)tR-xbsnaw zOIM!(;l^g_^mLOfm131wnq(9W%ZdVT-fnLyCvK&>NeTP)qs}9s?qG~bdIl7iV)IXP z#f&M^xm6$?MoE@u%Tf9;m!?NQ2m+5OBHinv&HNqPB0^DPH2+~o52b*ToT69p9vpV3 zGwAD_Q;<}u={RC&wYoT2FpC#aV2<;7ZO0gU98N}fxbU8anGL@Yv0j3-S0qljbvW0a zPucbv$g%OCk02t)!7Y<4<h8h|U~Sj?@Y7;eymg0xdS~CDqmy%Qp#~zYRG}}gv^}J& z=ObXwsn5!7|107sC`w7NPU5+85m0=4%2HWZ$B^q}vp!_pH|8X@ok<NIneUTM)bV(8 zWM4L~P+dT_gCXZi>wkAY{YpQhb;5RTA6f7BH3Mdb-ZXJ1yite-zP!BK##Rdr0FW<# z0an;M!EP9N-bc5@qW^F|HQo^PKLB9<tF1<Xu%oX#6L3}pfw+b{TM>NO1$k0J%O<P6 z_c9@+On+Jtbiq;r3Q0iM)0QKqqrwp3szgy&dj9r$LvuF9kwJgWqs}vCl@naGH}2DK zcAic`KbF}?U|Y11j0S%mq*uN|HwCuJdoHTKoo&(Tpy!XZoSf<5qgj{7bXP(dI1a}U zpf?NY8Ir(=oXaHkCvlXCCM-=1kAkrd1v{EPU;74gl8D$x>ML`O?cu=braJObY7-rJ zp~X@xk*OW*CjHneuJXiW^uR25@ZmcFqGky0rrClV!uE`H*p_W_sT1%7@qT=#Ce1h! z35x&DBgbfR<;tr?(oM&Zv2M6>t=C&~CJVRWa1=+UpAEh-gylJmz%~h}tTzk#Z>(tK zL7Ocj$@D8Uu0Q*Ky+eXBnC7lHE%13d$ft0TKP8tN%r0D}5ZgsPzV>!r$S%?I01mAN zEp0{BtE)<vpe-?B>GS(oa9*Opo=s-Y^HVI`DJK`-Y=P5vrJGmsbAo)TndfW9PI8pU zS0__VYvz^&>a_yu-*R)lC|*DUpa_XHrI(5nXh-#xd#RNw0a|w$ej)^j1AnFh^Zd4- zFHnDs79DDTbFc$cZ*jM8Pgv>9%pIH3nvk(>#m$|VEMOr?_9ysPN#a`bjWov7_xn4Z zkoIaGv@G6a$|YYQD0v69r{&*)_qDEMx1WFcoX_$J`)Zs5?_!$skJIUO&DByeZxNzA zvxnT^A1JvUK)@2{w{B@0p#io@>NCF1UYnnDePlrlv|4l!wT$Ge89YjjH`Q&&B(lVa zZz3yrxK#4sP}|ZdG6UIw00?Q(^!d)gg;8vA;*P&4)h+2*I}q@Y|4o6zU%mbC3KZEv zn@^m7kIOGdk0bSm1FyM_H1hjP7xdQGmy$8VE@jljzsFpkXn1)F3#AH~BH9sh9zxTH zysvY957zyEMIVI3`;BnO+5paz-C(kI9sGtmxZ7vq2dB%g11(e2mqH|8O}@brt&j2$ z)5{v(f%NDa#3y&oSC;L4&YVG!dxng7xc%<)L)nlHj&y>+p}UYi4NS{ArCIh-9WOzg zc%Gm&vRt2a9tOe7)QkTQ92HnNHD7zi$#>6iYQK&Fpcmjg*E>s^kx=62%yoHqyQ>rv zZ?}P_`BE*7Lge<0Q{6eOo+cBrUdC|gI%kMa_QPC+&@bA=uok*O=opSm?dgv`4~wuH zWdX5gVZvd<?s}r@36JscPpR@HeA_9NXtX287N=(1vPj$ycDtI%I<{j)Ys*K9XMG`S z$%d_q3^pkJOjzZWacMUR*nlS9&Dbfj)E$3<iAA%*5r;w1or}|&ufEhh9oyDc+KnwP zOufnniPln3CJHkE?m^71>BDu(;DU^)lgDQA)rJMo)r;1(K3tRj4v&sbm!G_Q2FcDr z>-<d&vIy=c4`RcVJXGEteHY-S=Q+t#u#9<A!_Bo}tf4_dInS>)JB*|K!Dw+pro+BJ zELY+>FFX`5FT06&vdOB6c@uk0yuVk{(tYGG5Nb%gL_UC%{2iBed7a@VnYQGAj`xCo z4+UhqCsiiQ1639vbLwRRgWi=Bi+}hc0{^3>Jyj;}6B!@g$g-w{BlUC)Te-a13N!(F zj~hw~GsoTmDnX(uJ2;^sz}`%7dQXM&u)nXG<#kV!|Ggk}#w2ZM<+rl+gv}d3WpzD3 zi>|Z-#N27ms0xP?9d%iNhi$ABN;*f3p#J!@w1cGtFXe0}!@_Qo{{u63tzKwnmQLH0 z@RL+)A0?bO_R$;LtX|b3N2)7DJ@Gx23Kf?`Bk|~RXA=IinG!0n3=wam{0w)6wGU(a zK1jCcRmcWf)|=3Dx)0fU1QH>g2k6H{g&tQVA>4DkdYX&hfUd)v1vIg)A`wM9Z*E|s zt5R(tQd!A5WSk=G_hB$1zx&-e>EK(qx*sU5&pXZSwP|Zw7@oO6f?zY2q<Is>m80<3 zE-5B*@9+=?N-*xjuqy7%?dD(^IEwN_X?OI_yXgI7Ef}Y5L36E-(A2~sp=<tH@WMVx zPtlUVGqPFN;}8L!5+oYM#E+-|tOgpSC*AHxxFeQ$2@(}F-~-Tmxr(I^BDK3)$vI!| zJ875h_bncYW7F;vgzQoylRmDkT<7DR2&;AX2@VtoaHoGya6xse*faC!44(rzd5>YH zp}Ffu*aq_Y+|A8@g9Oe39g+8~-dghIwq{UDK8!1f_+lmU)G1$ehKLU1+>ABowo?lC zWn0XD_6sjFUpBUzv?(<G#khju$7Zvq9WVNdv6$OOiMpXHJ^>JAiqgWK-fCw-U;0d4 zgYQWAav6=$Up=L<>X#La2I1hI4T!-?0h&O-?~&RiAg1`;!{(ipcM|%lztt_iz_Trf zmXmj&gHE@vRBCn-7HUR*$%N-4oyh>T7_6zJ-}hvYm>^+rUa*#I-|*pke;))EDm%U( zo_nt+zp9|OlRTjz)51v^dp2GNI_~}|f9L>1U>!ZARatk4-%xF*Pi}12DTZb@MuZ;V zV(=(b>e|1drdptzX=Y72nP^XY^_3DHgjulr9P^X_2%7m!%Cu;yRH5lk)`|Ayi($(@ z%*9W}mO0+VaQ+&x;k8;<dIrDBNrmOsJO{Nd<MstNA%vUO$uJm2i-9JNM6?H`3F`{X zHD9Q)hKmAOCh(|G1Lzs#h8rSK@A6!3AE2jC-*o*6ow<2(*!S^Ig*_nOT%dulDh;3j z5o~Z-{J4eLjfBC-+oxxC-LW-gc`X9_NzkwC3t3?O66J!3+%NET67Vyct!I1-yDpOz zLI!e>>)P~?$=Z^7x0TR=uO-!U!R5BQami36WlFPvQ1yV+S+br~bWB*x1(HJh+1c^x z?BVkg2Weo7mZy$Ic`An@O(2LYjPfXa+e??P5s#QMMam9`3?w%BC$1^FBqa7?*dw{i zys19p?)Q%Y8!!tNpn$7dUTqdIZHazFGXTxp2>e8&s48Z3@Oeu0=G<fM=FH|$Pd6FT z9MkB{K{fA2bhHW(qn+^CdE?0}zkc$m@g4J8`0{8wj98_6&?eRDj;D;-1_{T~%J7*c z6n)0+86M&106{>$zaT<<Ur?GpVYQ)VRWDpf+OmmAa!DHf9aqzZxhMcrwZ-zmLUM*R z|J7>!v_%A4Rv>3PlpprxxFijQy;-zn4o?QT`yE>+_UT+4o-9~p8tI}@?H=+Ik?eIL z@WY*{I5hiW4owx<OezzwO0|kXIDXNBh+e}1Q`?3@OezcZe8ocYaOfV7cbDx&z$8J1 z#@VH$nvEtD#X?uCz52BEtgejjJd}O+#y}yd^N4qmrgBToq1Vv~<!E^6pWwW0e5!KJ z7mWvvlD<DDPx#nHw&iO%-&!r<YqwLxqB@-piGh|+3u)t%u)!)@hMa%jy`|72hByt7 zQ=61FRcG>DMB)d%@zkk;ifd%)QX8}dOr$FeR9C2|rXD6gczib!EOBjDMaVN%B0UTk zA|ot*#_f>~Td?kwbck}$Iv$-+BR7UtQC($qli&E7TE;9nB8+-Yhq!XAZ3We0ih7nG z1u#Valb{0%h!px-t2STgz0%7r!&}-ZC|>7ORAvj+C~De$xTlLEE-tY+6M8*T8YnlJ z>l+h3epx_w|7PCa4K?x+n5YGVjCryuNFA0i?>jbNM$e%VSW&JG0vpL`)vKT(#4HP3 z^d5&WsshVC!a%i&eXNrd8S@ooeS72W1y*B6G-M1$Z!ZvTAwqZ#V-F@vq~`d8=`bmB zoI>I8Dc6q4(lMmT3w6Q}%}2^McQDiqEUHinm$S}~H*GG1DGL?yFZHft_DY;a3#4Lc z^%vOqLTZqPY?#SfdM3hmTLwPFfH9CAH`c%_7}p7Ghc}iOEPKs=K!uOPSXdcwXs+st zo$Suo|8R$!hKfY?bPrF)L^g|3R`7;cCd)))stCv;psRh2Nj>Hrf5HPFp7-HY0WPs} ztCxES1J%1I256ZX5cSLIw2$=~!^l|6@YW^)8%1DdC7qhK5mTm3oJtnFw1vK)Ld<7e zl_javWIQO9Hxxiu%YsZlFnd6*6!2dD96UCo%bgWkq5vSk>N69i1y?<BE7bYxH~Gdi zNj+75>4Zkb-#U@7$ofl%{n|84Kz6Gd$Am?;jrn?aE&PY~4<f<^Xu10m2d%ykjaivr z>OzrE{m%!dYf=iMCO+}g7X6WmS*13@KfPIm6FfiICZ9~nOYo;wn@JI~xFPr6DQ)}W z3vRC5YR01maWgRvV{#u%x26y1|Gk-lfF*>jTv5J~xs7l7A_!(P4?0o(h35Fa-G=J{ zVW1cZ#wnXu<L32`U2{g(cg(+syz{8GldY8AGe|oBf?{hx%jJDaIiC*}U0@*^{ZA3j zv>L5=>H#3O1KlQ2YkpVB#s%7Oe~BA>Ym?HW!7*p6#9YU=G!w1qO^&%>7&J~!SW0Rr zEwF{4Chw~7=kXEAe?ns@mGHo~UN_df1e_E@N^KgprRgNinBa@vGtr^Am7y~byRrk( zfVh{}*bZjs*RsHCASe?N^DO4N2-Gzg0MTehiVeWcK2=pHZwj!~EX2){-kO1`?MD-x zFHrU-zzpwaWb&JvERQfN;Qa5|s5KJcbZwk^n?Yav$J|%~mX*R!({1-GRac8`TH6Y# z8HchVfPBE4JOn-BEce<8QzKhRS)C^c8NWUMF0D!tCkUtB-yL(r5!eo`fX|K~u8tU5 zD`$k0hCVlC-dMJX@Q-E3tx1+L38*T!=pihCxyEqVJA2NBXRFiq*kvQn%P+Hhzw!$M zE~B{R*EXM{S_JWD=U%YDK8xhLB#5}20^o{q$$AX559L@ecabEpQ7j-Pzt%d|w>}W< z_sPl0QBp(Bl=TEi?c^oe$n&%2E|Cyt<^<|+2q^1##ylGuhVzX@M>4a|d!r|uZiGev zhP)(ocdOw?xcNE}{7as(IDNc&PAN8cUx-Uf8n|dku;_j-X%>}qwP2B`i#yM8Zi20z zg?)^}E&<wTlzYgk7>a~S!c}Jra)sYAnvzYwRql0Sv~|vFGZ!d&<*y04!Rf6~pUgwm zDB4sxiQvDeP8%7W#>`VZ`6~3FB7RzQ?L!q=1KA3<UR_TKhy|fkH0VmxSf>S`e_)cR z?&MV)>Qi#@<u&ox-H$2^7W|7K{rnt4dy8kS{1_FBjLam>(@ItGz$<TbO;&DQ49r5i zr$O-k!UDMYcfhyGrth)`>RGoxopnwxf{46MBDb>m-`K{0m6$qALVD{X#3#=cFZ!i@ z09WUa=bV{QrSC}+zF&Ux*k4{Ke*3&zy{IFEaX8{KT5}EE{bqJ0$mOmT*1fKd{WlX= zNJkL~0`4a2Q3gh8=6inEZLK$2{aa#4fMO>0M<Ko>kHa<CVmt`Qyk%fJsLNJQ4;ssv znRgyiFOpIYUT|GBcGtN&w{v+1dd`r8cqrL12)|xg-kjG>JzUeHO6GTm4y;ZRWjY}g zQ`0~bdA=08k?YI5k`cJmZ;WjWl}+^f!m(42bskpGcH}5EmhZJYxWNnD+q3Ya_PeyG z@l&f=rIWi3vf|6u>zy#g4;Lh<(-p%x<Gu_V-vg{*h(rY5t&?dqM8m2A|IjkJl9qRr zp{c0-dP>;!5y9|kf-)c;wegaVBS&T4{!8xwHZuU<av@Lk(Vrvx=uvGUkjkR{UWu3m z)TrZTIss8z%hVS1{*52sUl8HlZ?l+a@Fp>elix+Vky7qW<G_{1RE|@#1#27>Iv0nD zu+!|&0GxxcaMkvF_bJogu63}pijJpeU;+`MhrKFTfI$Y2*vbiNDO32TA&uM6RTH#` zZe{lAPmS6b<nhXq;V)*u5*Mr8nYi*)3+3nS!eIS5(&;CVt+88WceU?C2qOgVTa0o; zP%{xHZz@A24qf;v3qW8AE=91p98cKob0-=~EHG2{65u}S3s40C^YzCqqlIadt_TQ1 z0aD%kLCIW|hgz;T){CR2?=Cm`Q==S&@|kA|v0a%HCQPPh1<v5n^e!S?C`RQ{p19C- z%J{A?q1s9CTFHqlD@KA{{YhKkp!%4sxF?1X0$^>hui+ho<7Zqy-qCwy0P+P7kts{i z&)}|Z)%v<%2Orb?0sCe{ip+Oy1%@x%W~#8BGD9yUo-j1mIe~8#QM1nyj)D1`gz}Fy zORVN3p_8t|;q}Y-0dXX2d{F-WzwJ>QrN;0&==@w)xf>df#*yP#*Nuh4NLH{D5KNi; zvKOd#0kxh_tKzeYA7|Syso?Y#h>r_4lG4F1`Q3ygpa<|NG?}A7cj2skjr6b=?+Zlj z6gxw#NtL!+jvAzif+r~zr!Jt1V$WJM$~sy6eC?DH!qmJx%77X-0{qPT_VcwcY5xjQ zr2}24qb(XL=plTc89lF1;(Zq9dfU{A!VaN<QP@IatDI}G8<ZYAF}w#aF75ntwrOxz zKodmCk(1f)5-6IT+dZ!J|7o?T<3FNCk8=g1Cs$(SqnIfF8y4(I=Xq=o0`n~vco<~i zFLIRk?t^|+HU~X^OU`=H+h<)kE2m@{KY}y}%%*1P);#;{ebp_UxB&tbp<qoOgG#hs zeA7M-eq*wM0R&e1d$K{Sq%$K&!Xs1#;S*kA$?@$rli6kg$xr6+VJgq2tZ*Ve5su}M z!kwaY&5ljc#*eIcamjXpI(HyK(qeWO11UWh{0L!+jluQRDh7THqadwY(}dQg!(lm? zD*mcBExAQQOADMfXl@@FWA{(IuH(#YWT^`bw1JQ<mWmZx=XYv2nYT(I><c?(*}ZK$ zb|fSk(%JH#U7)(zF0e`mS+CEF@s9@Q(l1wdm(KqrgiUe3wwS|TjO<C_T{p=in6BCM zN$HZ{Ax2ZteevGXcnuH+iz9Hb`?Y;D>#tIvsUFnacmJncvA=Z=9V_{nT<ymSHe4F5 zu)*s73*3CuO$N)>Iop6^w;H_UjWl>DDc#0;982F8`%9kme~mlM_SI9<I3oZJOePN} ze^`ntAA$P#(L5Bt(_4vTe|Mz}VA<Q(#%qewb&<DXba*<O;+xM|Wam$QueKNXfj#i0 z19Fx~+g0?^QFt5JZZKN7T1Z;e!c7{ZQ2CJ$e+kD(k};ujC5^9TYh&*ZVm>{ZRW51g zIB^Csjp8;(3qYUwEZ+|?t!oX{$M49Y=fn?k+D{7_SI7fQO|kT%rd}xjlTbmp5r_MX zS$J_C#ixqp;~0u4W>49rK1h7v{;PG|bh`u|GMn92CG1PFU$=)f<l+Cqbr8V55#^jw zT1TQ=Dd@h-uI_O^xJ;C0^^obh^0T<8hBVn`^_YGY&Rr@(dapeV^7(o=wP^~5#5|Fm zi}!wK%Vfn(_};7ydjp%;440=RlK%0|@Zk1V1FyV)TIWP^?t0~rFWUapfty^D&f>dZ zLQKg}yr#$EDBc@-cIf;h`{TGkAO=%G-4ZvWLp?>H<(FnLc$l`pL1}7ICz`uihBOL| zxAZX{3%S9O9MebVx}x4y&c>EzPRu{3Kr8*W`vVHROFw@;9OX*)(cZ`ExLk6eTOnnO zk;k^T!k$GU8h;iB-xp$ZDSQ)dNITS|%}gy1H00QjsOPC#Q^Wir*4eNEdsdT&o>5YF zSWFuxFAq&0-aoBdEUM}6I}ZX2Y1I{w^M*y#v+}gQlpAe;cu#!;`Ud=Nx<b3{h8M$R z+*BoQMM4Vuyfo7U_7Y9y#}FYonl$s|mVRO<SH|<0n#wKE=h4{qz+p!-?ZnAIvI3LD z%MHjq5U35JmVj5ONQ7?lQj=ZMF!Ss;s^r|;m4F${Y*Y))OyP(8PZ&RvMMIklOe6<h z`84s^Ds@#{JxZdo*ser-Sx4fnCm-MGSJrjV7%?8;7jUiB(x_gqauL~txuSBMGD~G3 zhOoAPvJc81!J2_Fy58~`RP`x|0nD|3MElN~mf$Jc{u~2ona28_n}uLj=-J3|s!*-$ z@JMe849TCKxmP(2{B_ArzC^~zdmloi!u;{Z2fJW7uo$NsXtZo?p!QfVZWA~8x->Xq zy@0$&2gMoQ=nq}=UDqgnlH0Q8dh#12x&d5kt&P8PZeItwqbIUie5-Fz!!8h2*JJ`K zzXonuP_sByt3uyP@q{<X?QERp7mNV?0sC4I5Q?reb05yE>5;j~Kr0`J0SlSua2ecK zLFtp6VSHZCpeGn(Xa@0or&WfJ(YXO2Wkl%MumF(cvB6m+-g9M)vtshWq-u{P<4}Pt z5V@oTF|D-#O-RD_{k=VQ^u<fme+k$Tvbx&dZL@msam+)?d)-P>GEJe_bN|l)YuBay zT8%vS$c|;`8tXuTK!w>*%|8XeEL@g-B$Eed%EM}{OCP=vcdf!jTBh}KkS98^5`@V} zqnd!yIWu0mN#@;Cm$&qTio^!zCpgFXg=RNa2eh3fgvVg>eA+G>ARxTOvyq4~miaX9 z)Y`>ZannHs--w`ZEn))u4BM^~Ar34(_@;cm<r{?2(q{zhF<m(i%lEc>hJWoL_{^o@ zpoIWVY+zg&+NOgA;vu4CS|@w$kL&Cr=+bc|=1(W#?P%SZBH!cV%oAkb4_unID8gP8 zR-+%^dXNc@6*qn?^t<tZH;y@qT3w1mD+hw}g^&<t1^GL&OhE8#@{_Xqi2WQf%Nd+N zxtb)dU7o22R!|98z3$pu1#JcO2vvC39~*)?2?|BqPX^iygx{S%@B}?3c_4?0PPFv= zf%)d&>vmlA(Tlxa7N;X}lM*qWQTYC)RPa9kVd~nXZsmrJad}ti&<b9{OAcAd&zwk6 z=2M|eMX@0M8b>eRv3T-5l`xX?6O)qK1aH3p_V84Y-C7w0g(Kdk710aa^^=H1uy}Yc zwK0k(l<>+L4(~aPIi9iSD&^$bGL$%*#?}vKNtdNyA75Xo)b8J`O&OcYBR!M#Woc;y zJtn?Wi_!#5%9BcYsLxMx3YMc(Ifefss$LKBxQUa<)@48-*Q(6B3F8KO@Y|%83EXPB zpG4qEpEfu3?Kz%D#ukI0WCRoE!H4$BIwz~x8V05vTuc?jV(ThO&;0N6QVi*?LKecF zLOX$j|1im%pUlHE5P$l#lAl|nZCDu8eR8GqcAawE<j7pSt4HO}t?;ni3~&V^`19*1 z76{Ng10N-;H0OLM*KIi1#6e&^EYW{|BzMz6FI7%g2D%)fk4=CA28+ZXw0jDCtk+fm z*Q`7=4Jv6vS(%y8*-x~GRl0(bA<|i2(+6hCxUCX(2;}c^(BsxE4)L@zk3KtB96Mn) z+@H5+$S@r@T9$&$(vmmh+_h2We0S2<ofmsksg;|(HwC<fi+i$$J%KBk?4|7Aruk-f z6u7bGXw^x!><frgzib2&-r8OM1urn8tdU)4uaUat;7@jQkZBbW{R}EMXeXF<-<Da5 zH0Gi2O@a38rMTry!GYMWF(pkr?}dgOp8WzHMW#=t+5^)(v8*50o4ZI|+<|5`_q3Gb zcjqH{Xr`fqJF;g*i}2`k5^RkSktSH!(dYHDjAq^Gh3$Jiim-F=4PNks#bK0oA+^@q zYa8c>4R~VW6}6DI2guvaP^kNSA30PLJrc`)d$MbY1YD!3mGiALG+ftX!BO=OXjdwy z^wRLWNPwm~U108X4Z`?9o1hGZNl}B>Tj`Ejx&&|bz>N|t3~8|-mB7#8ubC?M$`9cF z)h$e2p(9pB0!FVIM4B$J5##;@)^f&Bm6Dn9O;N$u9QAyaMamvD;{0Gn$3xhgUxcjY zykN`wL4wRd6L&bgg_sM+Y84GdVMOIOjcsgm1PrcV;QYhY96e%!lxU$b)d8Pu(JUBQ z?gW(DG%vnCgYlUt$p8El`XF+KL1j!~PWVsd34$jD`rC|E9bmjl+rm^b&GD(N_ckh3 zlsx5j92sNiD49Yy;&X!t7&}~kuYtBd9;_JZC;Ekc+^fzX9!XS^Bvu2{!@yg$9knl} zDlG(inhBl6rzm3Tbs~i6gFHuirMFNJP`7NXeM`17Tyh3DF|$BciHw5VX`D7WPupV1 z%067VD(eNa3%o|RIl!zCM-mtC9hTOP*d_7&fT+Mt@`;Z+033V1DrxfH)w14%nS#^Y zPUZ?|Q*g>0ghhKC*_}P(+aOh34yxZS>Es^MCjuKyN~D4jGnm?y1GBs3<sei@z$=Z$ zRNv;Gz?Aoap1TXADZBJ^*=#owgiXzeD#LGBX;+-XpgTr5k7!)X6;(mVLsi$BO4DCG z(7tBwnttQ&ITJ_SZ^rdHaSqSY#4t%k0tFc&?G#<WlP-G(vq<-Ryq`(fyQ|evg8=g) zjQu`r!=}nP^n9^FfP|5OGS>s85&d&Nn(X;yS#kH5gJ+BWp@B>->sDn|Vd0Jn8G-3o zmTur|<7tSRU<L-h@y~4{5(H(aXi31#Y+vUGN@~yj2NAvg|0=sD>li@haMGN|ryFFr zOec-X)G|)bTc*bm1S-5oFP=Hvb7)VU3$An#>Y3d=wrs>Hcr=Z%CR5Cg^@(5_;Nih| zo9BkMm-yRW_gf+}IGzX@<uEOh>x5go?=8=zKo>Ly5>@nA;R^b=K5$2~Q3cIsfU3Zg zARV4ZGTcPV&9iBH5m@NVmt1PLI6Ua28GkHGiI})`61Rd9B%8(r+fRQMpSQEE@27{l zm~Du>USUPT7ahc=UO1(P{GFvevfd-Fm9kw%j^;?VDy<oI%?hysg1g}$BvI>N=5@po z_seCAlOc%H#Tb=oKTkJ94SXrjgd$}W-Q)v(bVeECp$Vcex)88&jz=<R326{qWirl} zkgZ+920Z|<ybL$xAPywXS6z#u6WE(Mgd{cUO4>4;qY-gX0vX(3DsSF9UHb3u`T42y zfpM)?5bu7zTjaC62>vJJg}aWAc)lW^b^Jf|qxSK{iR62qe6MTY4sXcounA1RSEtjR zvyGmWzx2Dr@>ZbwK{_A%7JGz-I4uRFURXL%1PK{wVnyy4u0UsS!Ye+=Pl}stLqPVH z)P5H~^J{C2RU3%MB`$`!kVk_yQt0m!)qFMtM2gv<j?WeGkNB=gqF6K;btU_6rx*1m zajZ9~<&aU({Y;=nFdS{LXqQ~#Ke*HS9E%+ziPyKD+qVfg;Tv|oMxvz5ZSVi6%Nv5Q zoRD8!Q+Y#`W$4C+)hNy|14P|~_Jh;0t7Y;B%{gIebi&;+@}{*M1!(qtD#o5+I5*e} zL5Rkk*$JM_(bj@GX(Y=cS~u2FF|d?XtR<VF8;7;ra^B{H?pH(Gj_`l!$-UVNRDTAG zC@!zTm)tioZuZovf+`pi=hkv!1+Guh^ugjPFs16gxO-u&V`YTp_?IP-SKIws$F~l) z*AXQxYnCG24NxE30&ZfhhN%_SWzfHVmTNh?tqX|!<{<7d4c34RC?bFNBAS~O4Rq#R z{p3ULZr2>3X@n^bi1ngRu>sQ45NZq`FUev`dm1kZCjn4Ldm~KFo7GR965y9c*L7h% z8$QqWXF$Ovcz@38{H-lw_g@K9?2=cje<m4K_0X&sAsFEp;KrD0Awa$63)C7D0?x2J z->Fhy^mx~=o8=*@^-37(ei&^~L4Qd=0|OfkbA)w|kF40O%2wnkeJ+lL7`z-3`&7DT ziXEhCENYsUw8(5;MH>TJb0-MfKKqD8Wi`Xdx#BK5Eb<hoyn7}0u26xgfC8s1$r!Fm zYb+)GxA*cTEcR!98)U3f9st}J?Dp7sJIisP+3$5K(=D^?&)zI?Z<?($rOw{kRa4jN zY4xuRo;nn@$pvUT1eVUMl2of12)(nQJbzJArYBNK=W~4~reBgOOK+-_FS}7`qhG~d zaBdrsJzadw5Xa(fdH+k##)k=hy45v}c_MpF4Q~qzZrLjP%#}FPx*93;6>ggDs%)%o zdDCF<-}Zx$WN+VhxCIvNwK8mT*Px*?B}|^UxYR|!{vMscQ>I{Vqy>~$X_Q3$D^-W$ zc-Di;b$%5vi>(hE5+4h_*^&&5m+c%^W@V~@8wE}Yt9nNZNM2*Q4-5#Ew$p^$FM%yn zFoDH-*$fMjn!S>HQ%CMGXKXq<c}dBlud|VD7%Xz|a}R0}#<Zsp*S1l>*$}SE9iGL1 z%hW2+g_fOmx9n|rB4uQ^+Uv9>67N*8h_j&BS}mHfB8m4p7dpc&jgcy?t%X6*vqxi^ zpDjfiKC7pmv;BvWA6%5a$-o!o)JD8ktLu2Ewsc>fmx*0JH0P#gN!+hx@VGW0ur=$c zR+#&2obu=33Ylua{;cP?JS;uMcm>J>smXn3H#4eVEb@91Di}J0=}UxT*<?Voq611N zCiH}UG0lW_SbcA&z_frLx2yUx#mBusUxKRR{5#YORT-hH(dZOX=|C$W!tC)0{3eE3 zzl1|b32H-UJ^y6NkfE{cw%*0+;^E9YYKBAV2S$&&_8!%MC*Z5OVjgc>W%yU}RpM=_ zW=wY>Y8Pi+X&c!>xh$epFbB5oU$vrQ%$?Aaj*8s$&9cN3XIIP^@3kYm-X7Hyo89?6 zzl;U5*y?eDF@Iaa48Q_<m~#5vTHIXuTO|GmWS&u&K4fb*yrp)qQ$H0)^BU<0@Veu4 zegl*RijLpC9BN-To##lyognr6*i1>oFNa7`Ov2C@EX^g1l=M@F_T8`0JirPr@v|~a zCvD=de$BgP>lq72$>3okG6h9ZwSKaCS!7g@{xk|=*Tb9XYDyy+5Yd?(BOQl9%KUK` ztyoxy{+_-8@e$M1P1!rj-<rl;tcy?-Kb`jU<OBW8)1dxw!WKbCox(bZx9)Ox30Zt- z+PF)p2RG7<tUM!d04P5BQLDgt<A^lw;6!afJQnB?g>OlsbJ)NM$(?FFF>E8Pi^0_P zSw#YB?sFya<R7-E0f^hk&83ted7NT-*Xr4)H20_-R#qVwvwoHZUa(wYsAA3!e7T_0 z%xc9~5D8Mu#!9&nZP8jAn;G+@-$Ylh2k{Agjp=-@ZEg|s7Oe|Hl%w`u-23gCw}Ps% z6c`R@!&RIZwSDF-k)d;usYzt(po&vV2gL~s&YxkbcBWeDU}okOO(hqQRtTea=7{hm zw81vor8*plFaeDlTiaQ?iZiN#WuH%n2_q$|q0cyc<M?4O2%?+1nNE%Nb5PWqu5+Zx zn?yot`gEOj8hx0t+Y}v#eCstQD7RTd8M5s%1_X>8^xI=hE<<3$n0hhuZ_b}m^~?O# zZmSxcUHWxR%-=vD9mYFVYKnzKfs8Eky1~+?asW;%v{oj!6*qoy_0f$x47@*!s_)<^ z_wqHEA?r={k-NSzKPlua>_857DU1Z8Jd<wbM*i?N4LyD^gKP}r=XfTl)W1bv>W~IR zv%{4SPt~=0_dT0HVoWHanHc-8YfJ-B{trsTe?)ESNUcghv#h($CkdT7K!)93F)PoK z8inlg4rgf>I(B(Z(gl^$sC2%vKCM#?W=r%2d7wT}X!YMr-}2?=71I_g+mIGpjzF+5 zaVITR#_y2Dsw0xSKa?j`JKQJxZcU^NQs^))n#sO09fpXL3>j~mD|ix8g+N4@o)G&L z+NM!ejXcsdlrRG9LI47EE=C?^eT@%k>vDqAaSA~o=a`f&nzwy~3;%UFtlze(cD7@r zjY`pGCQ|p8AZ2bj{`lhv-c;1}NSlcz5vjQFkUDTpw-}kt?rz0yYp4~pTv2XiBF*FG z&~6_-BTxHqW4#%d(|>~s*w{qncsbQUNaMxjtoY!o*FPM{4!3(*y@eqq3L(<^x>NVS zL45)E!sk%W@Yo_OJkqMeJ?Dk#3f}BWje?kGKbn98Rj)KS&I!t<A-TlI3dy7~DeA29 znQ`PId|pb;8N`^XjsBGe8tK^U>@qlJpuV7YNuF#%$jBCI``#2?xLI-BaOP!ls#|;i z7LbwLGzdX2h(NtOX75L7smVA$gcgu8#sZe~jKxf(`s7Q_H9v)xpdRO1otJ;G-TX=f z`k45jfA6w^p1JioFqxAYWFm}ju8T3NP_5@m4A9ro(qVXf{cdoML0_=}z7y&s2NYk% zDFi<Z2rXrk3SmuVYbh;3ab)ex`M{?rHRuUo(Kr>C;vzmpinlg$^v#2Rb@Mo&*LKy{ zRmmi87er_`XUu>jfthBD(ZQ4RNnQ3GhZd*GwX%|~+zWt%J5V+W0-2Sb*tqScm5Zr9 zkK1)t6>X5<#4S;?nE`u;w_;K%3jKQxXD@wHQf^anA`)4Lb^d<rp~!pdsJ%4Lezjof zPO7y_EJIuJbkoqCLOCH;;wdlW$-r21Ey)@I*9$7(;sv%>M>TV5@z>vrt|<df6BE=2 zMGu;g_ASh0i7Iu4e1NH*>&rZan(!TYa{8`&d9IvuQcp=^A9th)!-<5BI(&fWs-9Fo zCDi@nku>?7{P?aI>TcU1^h^d|wnIDlKx_s>XpwGG?$*f<o061+=|l0}RDH};jiB~( zQxW(TcF?p{y_T@pE7`P4edu~yf-uGdC5iCDDh|{E9n|Qd$%b}P5vcfqv&&zUlmCyY zfb8EOvt9}hSDuTz&#ZDc1H?LV3`~2*dVYJ|V&N%*(Jd0h<_>D=9jHNX`IQGS!zM!@ zi6P*AdIxT;MHi^0_1(utai+sHoTkK={248tmFGof<Xjufowxcnxd6@D!XlRro_if? zgw>d0<MZG0_&rj~&~>HdwB2RQ`Rn{R^tJz{xjk-;hR4I>QgV~ms1x{cnFZOEx<E3) zIMFRV#pCQrJm<4)G0tlcC<z^?8*Xd9G*~MeFcS{QMut}+LZ$FvZ^z~MmAltlj!6>6 zpfaif*flK(;E8I+|H&)UGrMZG1J3eQ&hi?5?vGXTQN!KY{qKCSGCxlF9k8jH9;Ez7 z{%n1jJ?uYiopPXTS%Ui|xH*flg$Y{C+iOUr6K43$cP=^G{Ix=BC6I6t5jk^_Jg750 z;nTL8*32gu>QADbokm?}J+5MWX<zG*$(Px`9H9DZpZP41<n6mLaEl}+pnP!h{kZqA z2jKROnZ#{(-SB(4c5VWgSc2a)?e45^mn*8Y)wPYJdYD86zxPoSn=|q=Gvb#rme~-m zu2B-4mtf|Ltg3GFf5`!y8hL3^xJ&`3J99*5tPI%+F$LXX67g!FYZYh81Iz|FP%bid zXxnpYSjTL~2CiR2xn%z@U6^OuPhON?lQ<{Y$81}oGl@2XC{9ntBfE@pos(LwS(6LW z*<ZatBKg^|<$>q`enhv!&PXM`CQo<c%o(6UuY!@6CXC4nM8}^HJv}J11{O0VvhDB% z==gp#R=A<E)I7@tWk&H}b}8obvxjxcAK#=B;1)Q54CN=$R@0x$_TN8ZeFgG3O=ode zyq07M%K+b&<%tkCY|!Y6bJfhxnVIb#xKRp7O3E<O&akn0cdpJPL(kqXd2P^A{9w;6 z$gC{WRDEMlK`)auy-5RiEBIP-4sZRpjki;(TFL{F45D8}oK|@-0TJT8OM^)QXfp@u z9L$D;H6#weCN?E<VNLQ1u{_Lg3wCN+Ce1<^XfPEqIN4&1ohiYetzt;U81CXUNOsFh zXpY`J)eRfUG^Tv8s{+=4gSPmBx|LmMZU*zaSJ4HM=H1ZF218%I^`MQxzMf{)4y=cD z<LzDRk$<hX{LSc|6U~n*i`M`!V50kGexG-8si-wDz6G-!fYKJrk&=7!5yC#f<4VVQ z4`ebIkIrM>BsmYArYW`Ws+lZj%?Sq^Om1sr*_@YEAS9pDk>6Q=8Njf-_i~FWdx%C3 zzw2*GT}uT4XUhe>6(KjJ(QDl*I%EqW#^)ufCVbLz2!*N#E<7g)XP~^NUb{r?Q(`6e zGEQ`%+`xn{LGpdPMK7GM@Mf5U3{=-ok~2EwfJb=lPf$*v&y;-n&-sGm`o+av{5BiX z^PU=>KthRM8^6NOv7Y=3ZokJ!p0Yw`Rx<JskO%lC30mF>?d-`@=88@1OAU?eT#oB$ z&-cW!V7y48!g04nEZlLh1?*j9#n43)XKh(b3P`MiwI}8OIF`CZy-F^+(z)vi#Npu$ zyeQ8lj^0n}m&8nzY5nP89R>M3aw9rkk}aMLgbyoyy3P|RlXNmCw*M*N8DJ;*TRB(d zB<@Cz+idSv`CNc==$P7ArHTJlK27OBgy*&%4ra=RkfH;l@3w|IQtgPffb?!5&^wW% z19jdsanCKU2n!^}N^+F{FQtKg62ly#6mWIKI@|)eza^<@4Et?5Jj$J(p56_3mb$hH z5GXr1GeT0l<=?)DOqCyz)9+e&*DMA03%mgxrF_AZy|)!FI$cX*3|>s_HMyiZj(XEJ zdiFtKX7uu{pz$+7uSl~Gf$;;&OmC9|UNIAtT=k!p0&jhY*M++nT<~j?*nd!?Ca6TH z!_lyd6|h9;oNq)s;(d&aQ%5?kw3RJ(XHAs-V<E)P5rWqRMrjtE3~@dyhdpj^vqMR_ zoqchfMMtsXw~Es&MeCk)!D6Jg<RYFM;>V+bV!1==l(P0oaG+IkZL`r5v+@fITWeUp z4$mrYHDBrn9YIID!q{K(#p37B!9h(h17!bwf%Y}Jgknwd3-T`UvEVy;V|a%bRsS*> zLs2Tbz|JB`$=r1kM;r<9q)4GZ7N6qC|2t(^H}s>qyg0KbEzf1m=qNHD2s@{ST056_ zz(Pu=-hV^$WD$6cKqtjBCTfrMrL?Lj$#?9}_W(Ot4$TO#?G-{X>y$L^sZ&aT^dIfb z0a5sE9&3-o0t0t^Yc;LZg*DshYZhZgOu_Dh92gHcDVNG1Y@RAPE$U}vJxf~l$zjJO zyWDs9mt0Q|6kw6eh9aFq^Q{@S`iyIJfVF9Wy*f1!MTt2(wVky)!*lt5BvRAs+>Z1d z{k~JW4)DQH5_42d!CU1iuq>_3N$i0C`pPA9V)wtq41rxKY<mBqGVPs3%MsY`a4IFS z(&35UR>*lIM+9wzunW`y&1B<-L#IgybYQ-u323JkUagG(mRmM>HOlm!9DX+`*{Nu6 zwBHE|bKvy4^NL0WrMHKFZMSkN=H*jIBG0uI&;@z+8Yx+|y>v=P?m^aX`AwoHsnN3b zgP}7;s)!dsDza~Jz0oAY8tNin!lFv9j>Hhwtb$c1HA%|!i+m0fN@__{H5@DpE+JvU zEDm5&ArA4;(f;x_k_ku1jVFRx^{D4g^fpH&@tnxE@s-rlm@|bFJ2BRt&0Mr|Cct!q zL_j)=N%zxSI9ek=;F4vv?6h0ja=%^j#t@bF(Ca!LpikPBU#FlV<?iNKoI$6T?O_#2 zsTIEGgflY9Df;!Dl%b8ay^rXR?<O<G?$P8tbLD?Eq_C&LpXo6in6#i>5LM0|pL8Cp z97AFl!0OXTuV0P5T@r^pto#>#Z2*$Mu0>x4W*A+TgU@=)57;bDJqTL7Q}1>YjICJN z{0*l~AMw$u{B7UE>aELIyW}hPg{ZKqyEK7;$Iwl`sgh?{NkRKV8jN`S8BUP0COB11 zt!9Qdcs_gW2m?lEZ~oqL2m9P&Fl$e#RIzFt6Xm+$dK_0GiOGZbm2j&1?`m}Y<90i4 zRzCRoNN{m<TOw7OV;T{>vnpeqIj`0#C+$vtr%P=m6xK4q6n-F_>)8w^WXmvAe;My+ zY%sh9773wqQ5m*0|1<>`b^SDCP5P^>Ez55|O_;@<T<#H_Knd$!BXzxh-0pBQAx?vs zO~Orgg`5d&=@yix3lMg*V@2@tIQrbp;1K!1eB!l&dvnoSHY_gX+l+`h0wG&-&Kq9w z#xu%Le&XHlU5_{Apa&^`S3f>aV{H8Ivko=<{Y@R*Xi%CdTkiYd^SKlZieCojnezkx zV17k!hnGV?;(j}SDkskfdkYqJX|ox4N9j#;F1EF1S>Pomr+)0RjA1J7_5OphW(q;- zImE?r1T|$Czuj}YXf)WPMEHSdSkpS(E`M#+;STu@9TTwIm7ImKu3ylo2OJqX4u#e} zTi4|h@%@XMXOGL^{|z%gVB9@_Rh&_>k7r$N8gF0=8T}Fx4{S{vLpG1=Su!e?^q1A+ z`&;mB7fVD@3?9IQC4W~VCdh#4=C_`vigU*d={d#4!QGnrw%=xDQfh1JW#aK){>~qI zh9V4XNluz^Zjs(%-pO*IIGC}l*S1V<5kS_Q(2-IPgI!>@nLeLIU@Cd-^4Kb8*bq`b zIEC#F1TN^|=9k7-*`ZZ%B&{F28&~O6ckMX1w;wkEBjK&N(J!Pw2tk_vRCBd#l2p`; z_VYi<T^lRu3Ux?FDdh&Oo6%#);1)pq`ts+O0UnAFLiQUOib}1_lSMvb^L&j6$7l$H z>?g;hqQ?%Aam2cuBRBkvbynsRpf)QM0n|jYeW~JE+P*ai<YoDj;D!smckb;i#2SC$ za7XKr$$evL(8<7|G{j^SXiYYN51<^t9_fjiS~dIsmKx+avbl4@cYqVWeUM=5Q6Yu` zMQ%;fh%YHB#+UpRcjft93jdJ-<DkH<xSM7lYK*`GNR!X-C#6uQ*Q0~;V%6$G&)f@R zQ#q#tJo3|;W+elV-7Uq_{ERd+$9YgMz;%na8OyYR7F)URd(Ao6ip8t*eZ>(!#|&9; z7|LWm+es|PvcU&zL`RTZfS}3lP%;-Gh}ySCWBo@iP2xN}(QC6gA{gq+yGygGjEoui zv+gC{oT$L3`~7U3@IbP92%@E#fy4ZM|4WaNl&+8-vs3KP5O?E7{{(%(wZ8|!1(um` zVONTY3$}_Sn{edP#he^5B{R}nM9qH$S!V21bu?vdXsH?BMc;$jej)r#G{4>om)HHA zNYJcLtx1?9hxkX36r&!vB`z$UoJRh88uA!%<BQx1NGmHzU6+@!qgXU`ulh7Pf$SkO zQTqLU34$*1p@>3~Z8Eh->X+LDmsannCf4()?&$-Q*MGfF<1Y@-8*-bjt^p02Difma zDwftlPb)glKF#gWV%vDMU>X;ow8isN9bJc7zcTaVPR6MpX_=jh3HkuJ%vhile91ux zHPX#0n5Fbv*@i-aPal|-hHjl^Q}u#yR@cYB>k?TL(~%+vFZO|C#o6*u+-B!0lXWNY zGHePY*hf;L->~e7HLdFX=|K^MApN^rnT%7g(sUDs4u~4c_;N*6?llOC#4TB=L;f4I zm0IU*ad!|tq;nOER69r*7Zs4qpn@2ofA~Rh@G|ze94w#?Yd<d|MWvi8M-q1N0PsTA z6Q*X^Ccky|Tpjm~NDH>YGYtGpud>k)ak)W3(!M}{v(*?dSE48|QtaAktoV$7=Ijy% z>oATVviL{cnPOx0>_T!G%mpP$h7Rddo3LoeUXo*Le4gH{9IjYCG9YZ9N`rV0cS=g| zU&OagZTAe!!bkZAYXp+=Ax3V;Va|&*sd0@f^q=+m(mt$@{v4ZZ=Qy?bfS;PBa77jM zgp~CUH@e`We|NmPEi&CRr#xIOk?!IQN)ZC5+Pm$&+qB`lg-+q4XJV!BAE^FF^Nk?! z&S%W0)oII07VqF)$8sJBZXe4U+AB)aQph#$f>wkg4VlAbqOo7B2LUw`U8PVT2GxU0 z76R+a=}N_wQrMReI-=Js*%u34w|JK+m2_*CUR!%jv^_b492mm*u37u<Icv_G#xHp8 z*_L7uZtW!gqUREp^6o@d%S@ebVt<ys9XyQ{bDE3)%CgJC+7yjz>68pY5mf=T2v*6Q zd%`MeWaZr%VAkQCnX|1<Bl9s!@eOktW8YmuRcMMZJ7WLJ5pX5yAK>NTH~4ik`6n1C zjoEyH_LZzdFE~Lxu*_Rv8sMjO$Qb8#N$c-s$a7-AVh-Fwi-;#wKC((cn)TQEyscO9 zaMdxNtZTC@{?gLLwqF2xxzRE{wf6(tZ;c|tH5bA^NMWimSQMSi4vUmm!XWv->1gk% z-LH6S5|@zPR3?cq`(Vj8w8Ppq1vHMi@U2KOE@E!qzOmW^iZ74R*~{N7n<5AWg<OlB z@ZSWG<kFB&+bKOkjyDknB_|Bi5ViI}2;OpUklfutiH}=5@Rb0U8Ndz+%j5;PswR^( zLLV~l@WrR>$K9n{R#cz=)@~$%21X2ANdTrGKRPA9DV@b%ORfuyw9OXG#4Z8Z5tyPh z&Ca5BT4kkCJ{+c#wf>GuiE#fX0V;RXK2%<qDc@4>oRa(l$5_>Aie5+*%F<x*T^}(e zXnxpQ8W`dL$qmKo5apd9%Uh)Z3eW^_qUW`eJ@!9$Y&W6>Vp2tSoY0It4X$GnV5(qX zt_Skv1Q$hPz)jqfiMgPa5%McW|Cid2Xh?6mj=5BPT4;cno#{EZ3AjQeb)2JsGVguj z5U3Rt({j28!>b4BIj{ZScSSwWE>Xh9(8yt(P_1e((5PFWkL$4W#$~=MM!{H9n4#@I z{^n<!9}FS?Z{`2U4xQs@so(_2#m=<1FW7L%auk)e2ZkG}X+v1Nnf=$Pr%7Jm=Ka@c zI36fZk1;pXFNhs?H+T3;);OJgVT?Y`cI)z!A?FNfc*r#zy=9vWa(~gDa&I$Gv&HuD zEH^q@b{@0QoLq{jBftO=(;~O*H5{Y0fqnrYA|b;I%!}h1`I^fQW&AuHn9&k^Kxv{Q zIeRtCD?R6|;J0Rgq6|v0sWXi%jA+rqxx@3N3l6}x_U)HmmZy0<Ko%~0GVLhHaCkC1 zaIdm}Z>@s{9c;@7z%f+>n;7kIm_%FzL5U7-x3lmDhBw`VIi}x;<z_sdJ{5FtaR*0# z@0R?vePIn$U~Uq`i0e+E*D0L&i-pw*$XDbtDFp5qRLOat!m3Y5>ITW?#xHGC?ylrw z^h0?i{pRv9waoo^b!m_}>N$!U8Uf#g@L-R|_w|Q1=>#9HPW_gj&oacKA!H0VVPX>u zxyH568N{v8`;-F6sx>=b*KXSl<p%7`xn9u9z<pOCZ((0;p4kL6h`wG7fWR>i^8Weg z3iMOZFquFTic|nqFozFRUfbpOWJ8hnqRXW2^LfmIcn+Gm>Kn!esqE5_ft5~DW|Nt* z8fx9PAp!6mjsc4SHKXr8DiYnK@>-<A>4HWs5v-+<p%(+!mq`qSJ4_shu|x;K`cRKO zFOyY?mjk?q@XSy!S`T!!Qte<rIleN`YB`;9FhxrBdZ*km2TIYEtPyz>@~S>&{Q_hj zL*<>jxJI>qs0<oMC!nL+GL0Ku;H(ZK_y*23i9qb5@Il^#95kB=6HPBcJ2l*U2EW{Y zGdkQ>rR76RSnUPk6yvURlElz63+`}<r^Nouvvkz@$owJ`np!B;>OsKeFPFaQUVw(E zk}NQ7)ML1SFRaLnF2^)P2B9>&DZ29LB0x1&kA#Tqf9(FTL*-YuL+N{W#U<Rap~_}Q zuQLwNWwa{21qZ2)K?a_Gal{c7?E{p`Lc{k!or{xro6C!|KZNI#WRyE(c=#;4C>kOH zgLWQ*U<O1TaPB%eDQ<<##vaUr_Bv=eBl~?^<!jxQV>psKJB@apcull3j>sH*Y0)_y zLc;RigMA7NuSx(I%RDT_2Zbk(wOrZ6V8W{#<Bw2Qd)e7bB?=J<kmb|^-<JvvR47!~ z+R9Q(1S^w3IeEjF;_btQs!Oxb9Ir~Tq2JS|1<=tveNTI`aqQ*CaitY8qaB(Rd!=#3 zy#Dq<qcSc#S?_i#L6s?p`O#{RKFz^DU<@v%C~;rNloqGVz3@U6N`^J=de@3eBvyCP zKE``Kv;=-<c`rI$*z(2kF>64bxoDLuuBg^dg{$bV^AzmA+2y>R?}0fyz=?9Eo0VjZ zPfwvt4P52~PSRcTLK_d2s?A>2pH#U*{~vm4T;8bM0S#tUNocuyCb?A7x8xa(>No3Q zdLXRO;IozNQHZuRW*=TcZF}|%6O6lfs4;7u&zkx620=~(f+Chu-<Rw6M9dqwVIfSj zIv?*hG}^4XF79_3wqTnlQhpyel3arI%~eDDtc3Hq(*_ZiF`=JavamMpovL!mva65< zLKi>}!j0$7EMH`?5rM5UrCi7jY_YZ(i{<rtC0{M;IR!=93o3MSNOa#4m?ai>lbt@2 z^FAw>P9l6<QJ#KfFtmoT;`PVIXO8$(>mW3@?hzGZsG7y>`4zArX#eD+Yx=J^PzTCh zThUK5OxpT%SOpX<6zhz2w~As0`QZ9jO`sgZHW+W>S^$7G-YXOUehn$~;v*Dp58|Aa z2F2XPwH&xzlJ@^)Uvl#?TNbCv=p_N(vH)iKEoTc~p>5f&+`Fxr>x2EIVZ`*>tQH$b z1!{U<t;ZdzEvyX*-#-cZp}@M1m-V$F5%&~`oGj;3@&ZyL>1o$uYPCMx+$xzQ01Z*M zwAAQz-~}Y3N4Z3y%C!rF2&u{vcb5BgmL2{Xf&ysWpAkK)?MPshCT<~axm0SI67re` z*A`H2qn?efuLbm~b~3(2XqUeSlK%cz)s^@)RSZcX#jKGgzJP>f&0<^vY$ab2$d$(6 zA)8JUOUpenIb$+Sy>2LSNuo#*(8(~Z>DJBP>piZR<{W8@IXSIr&nGOrWce}w+CEE% z1mBl|tg3ZFG#!ZNg~K9^Mq!(8Su%jtb;LLLts*UdEWKgTGOf3QW<L<SXz)@=-KS{G zsOg;9O)m0$zDb=oQCrbk93PB?JsWThcr_k+y*C^c(bZfE$~ra+Ro?6NCyMV|9l!8Z zEyUtb=WYs8cR+3#ja5D#J1j~6I+HKWEjr&4jt|<vGs>goW!QQlj+aznx1v#LQ70N6 zSjt<vkeb_=1fqOdB0tte5s6YtCHH6XWjs54Au}5m9_#kwvlDK-UK~(nIN>c}5?Kij z>621-4V4TuU9g$ZMJygD2ol)sU{Cese;AyDn<AXB>AKsL>AIh-lJ?f>X|X>RfTB26 zar+vbjtXQuaiO#dJY-wSh=>n5N#a@m?%p%pZ_5`ZMu_DHO_!<)a~!&0S&s6*OR!U> zVyLj=4daxzw8>17wAJY<S3~Z<KiscQXW1Kx6R|h$I^r2mc$J3tnn?T*SqmSJf-R8s zgP~@-TN6pazIvj1N(|7JV!{%ak}Z@o963rk93L|(JW}|bW0oZT;dRK@XS$jE9B1cT zw~E39vjAeoTaXEeF)sKx50UC^0BY8P7-!t$4V$|!Tx<-%R4q?V42~R?9tE0D;XE*` z5iFx3V1Pt__^t3}XvNVZ4XTfyz!^c>!}umnDt?qd$TlX>z~Mv`Fa}XHRpvqT9@pJs z!PLe>M(woawtEB&f3-drG(kD~3y3Oc(Q@Q(HT-IQcGI8@u4NjIjjl7u)rYQ9e%J#u zEm{{>$iO2g1xUltF3gqc6cYe6Y}9oy3{BId7$(E+SpGV3WCp<_nT0pekh*;6g4^a@ z>N=3;R@Fe(gB<376<=>kAHWGcE)HSKf&g(93C-Ig*99_>3F@@%4DqO3Vi;Sa&2P_B z(1jl{LC7HNL2+6y6@QR6aYHqe#OgWUZYj?>zcTr6#B&oJ^~4E)NiO;F?CF#5t@lQd z0PoANgjP!(7BdYRDh8<9L-jEiMcnhdf4`vna|dQxsUSn`Om&&HdBVL<P;@=`m_NR) zF32rvDGYuBCi5i1E*>VnD#=MeSN%+b+(6`PDrs-kCYGSQtn~yt{1I?PBrNIRW;eXO zQ%zU54Dc`l7&FU@9G*_-Syev4MRJ@9s(b?$nol>wrR+|>l{;Ht`$7N!g$tJ1BVo+i z=IMNj(smnkObpYAdCyEJtG~z8YhZp4uPl7Km~=(^HV=iB2t-u6gxlS9A(T%}Jb@xF z?D;IQdihuc3UKwl12O=r?*9JlUHu)d-B-gO+#Y5O4IJyk3(kJPKi0Tnjv`df$j47I z{#gNAB2!CNApZa*o|*8WF9wMw;Ig1b>AX*>Sn{9AJjO3R`ZReR*`M=xf)r{4RaHc} z&;wXSP1!b*bNA!sEYFx@kXP>Lh*2k?&{cKia@GCFc5=MmTyG^)l?9_2QpIxkuYNmj zBu6zV3uv<H$YUhf#nVgAoEhHv(7??=d0%Oj)&0wS5{}xX{%Z|hO@?D4=&DXiJu{>1 zb)DQ?mA|qpM$X_)ch0AR{9br~I3g<M;U>)fB3CXlUHhhUtT`@L5AjyR807?A)mjST zq#~Ngz9u0j)PIc1wFNrT9$%cgR~xPDSK5+%zxZ+6T(_kNp<m(1WL8gF+b6MC^M@0l zanorLKF?SE9XuAFwd=>`OEWAP<e-tElb&!=F2FIt)~-<aY-w*p?g$*5rwv>BGC5^` z`lNS1dGX4V<IA552dIbzUT~iuLR9q3BNH00LxVIYCR7S+bC_D4K;d5bxv*cvXbQqF z)otCaRQ$H)2P;ReFw9l}?YYNAQ_}FfvXLh>QB{P_3kd`!Sc#uf$y3GIGR0^>(sqhD z_F!RYZ5<ay>1MO&V*smJ5lI?>Gq9<>3xj}Rs>aW(K@!km#11YsO;XigKf%1n@55!A z3`-QR(9%Nm)N=>WI*D|~01p+3CN(z2^vEU26@6B)PD{3ibj*VFIV03NZc9PdHDL0l znZfo^+C8RF8XWUl+sjGCN@R3GaqN7}rG<M2YyzAEOm*OpigY?SyD_A(P5nVL?vfbZ zb!CY-Jc`_$i76o*KXO53c%Q~4<#B38uI;9MJ7w~NtlYd|N!7z#Mm;HMB{fJWFjy|N z+rqR$0=!70TupM{Ttf2);OBDLfQ}kFfdn=;6saC0d4dZHN<W_&lVB7Z2-5i$>cvNs zLV!HRV6Y7W@>h~<iV6A;KhDtlt+)PG5SYzhsTXYWp3o>=3C=hSUAxCA;WK>S((V$Q zjQTBcNHfL)M#G;bEo?xj@X%aEu~~%g#NV6jn>ln-_2Ne$RAMm7>()f91?%G$MsFAs zO;<_fcr#BZ386a$*Z+|_`J9+M&VQBxQ{oQLI;{aWBgowJU5+(~?qQ9&#r4$^to}G6 zuTymrZUh>fD)dk%*bxwK<%{RFBZE;acOB`HIQAJvrnz`??4A2>hQ!vtWyPeE^)DpW zn|jJ7((81}B08tD9``Gik*bPDN$MFJ>FU^xuI_HFys-wwIflkv1hBNJ1Ev?~N@D+^ z^VB%=zAP2stpXuu=Rk&`_<J)pl~f|EQ>sk|E5zn&(2?8!lvZ!I3=;|g5wpO1(2opf zhSZtLkDfnWX%hY)HRCFFot@nO;A6Thm_hT!jO1tOv6)Is1DNT!FZ3OaNW`INbI8K{ z$XZm$jx#W1VO~D#8eUVmKf1)Z&f2#c8OPRrStv>H<_r)tw4f9x!v#+VR)74mc{1`J z2tqEsjy=7yT5zPJDjR;0y(xVw2DU|mIOP=Sx2?^TT{u3)<+f=s>`k*0C%=Ep#a<Vm zOL+THAVZ;Bvw4PA-3k6YLBug3l)?j&C$Kn-a+780x}8gJ?V?0RQ6W|pm9uMNyRl!h zh-mJY-e~?U>6MZ$KjsZjr|n+*iww2JD%^g&c65QIZnlr{t1bHiiN?7mG{xa;TDj`i zej0k-V>ui}r_CNl@f{8zb(v3k@bVADLbF@)BcnnaFnZW}NEZlTjvejCTAZx2Z|Q%n zqJAfPRO99tb>0<?j6FBjOYm^Q#`ZG4{%9vXLR!VyvLtod^rc+(P*amv3=>OMS4o_5 z5~&bJCx)>p-4;wdJ79^rKEA;+Q)g))#TaJ@UXsM>FCpVBe%UT!`aD$q5nsP9Vi9(- z0_R&f#bSVPqBzm9Jt={$V80){fLN%O941ap*~5d>GE7SD0)?*B9fh*z0)%b02)ri? z*{yB95PBoF#T+SYIi<*kbXo-B>CF_y3m2Jcbz!p+a?iWeo>9^~wNoZgvWud0mkFx_ zc;_r&x0TD?PukoK%(Z-b4H0@SUSsl7kAUJKZojF7<dw2SG0jE#9mCnf3zy`gs-uVs zUj3e}g(B!t*W>rcmti9z8E3JIJOnaLvgWyaWczMs@>S@}bmBLyczGQPjAVWRwh3bV zfTx5)E`J-P0Uj&Plw@b|lr?ni&SliK6}K`<S}79Sz#jME%a8ydsq-1_QmS|^9j<Ql zI4bqBYag(J<IDqf27lJ)J4lmB=<*$(|HKHqk8qU5Zm>|k5%4Y%HLIoIH9mmCI!FZr z7~pE56L7?tTGkEE^x+Cjf7Zs@nJYHK<o+;v2J8$YSWBKf9oveOKh2xbJ~Ty!8#{R9 z#6ciHzWGVE@JydFWKq9^ux0eGiGTitTK>EhEeJcpTLv$E26QX_Y`??1+c9weygP>~ zBbP4>QN_Emn)+_HERrqW*z%o}o3jez&a$xjp!V4FFhrr2!uXWE^A<Z-GHE^NNR|){ zTo_g)8WV^Rnf(@D)(e@1U8xrmO-YIaJ2H~&i$8C2odt#%5Ks{<gV5zlyBQ(uT{G|N z$$$aZQuR4Tcc;Tdc)4SUdq$Z&{FU9+uEV}Cdbh#)-|=`wfb;ezK!URYGi1^Cm?Es{ ziBc<%(R-u1U*#7pmF&eY`k!QiTj4badjUii$uKww&=eMWCPQ#|PeatXO%7UK=ucJX z3n&fuKJJ4gOrqj)sMzk_9}AHfGihpN8}>%DEkWAF5`yE4>jT+n`M++5@1FG&>WV7I zTqWPJHLzx;bAGTm@#@ytoFuHrRW5dg%SQl()mW+0>8O57sby>Yk)$_?XWUK4<Lv7e zX5`zK0b_R3KeW)1fl8rD(ycu89#7RSj-!LzPqDZ!iz@lfP}mwGdsrnQzFH%IV6Fv~ zYk_5?^Ps3HN(%uB@-tb?JtDzP220v_gW2dqHg=lJdGR_AdGHv}w81fC|28&IB|HsS z5a1g-5uFUsQ^%JvL4T`1ucLd1)5AVEkI*dmVAiSB!eu+dZIfZ{PU*uxG3G3;!+Zg- zTo~Xi#AX#uDBt1})rUnRWm{J=ZmZ3m<=CJAdzWDO=}b*4`D);C5dyS!J9NgPBGB!T z0RCKgiOS*|Hi)TlZ|AV0cK1m=@nz}VDud6#MRbJ#(cc@KmU}>^y`+$3K(aUxc*a$Y z8%oBG(njVVO>bWBwI@ms?gO8qSE4AeLBgn-U|GeYwg`C1{ep<{Zs!GbFC^S%Sgly! zA#Pkom)Aoiwhu-_<1b<tEw+RS|M$3yxM2s@K8i;oYmiVMaus|T!4VIcl$)YzH<R(a zpQX67`DnmC{t_NRb~_(|F4W}XpZV^zg{aCS8%`;{YmUkJneI9W7UTy#>Yw&&9m#}- zhf1Mg$V`x?CsaU1^?A#-Gbv1dDhLc*>kY?F8ma+j#p6i9FT%r(mj4Qz<n&1=QaZz^ zPk1TNHZvmRQm8w3=Mm=r&{s40Mj-zqQDsrx&cW2r@HQx5Q?~S2v4FHW>5V6BVD%;e z#6cH7{n0Bf4~~HtLnN(CZof&39xl!iVDZj<gmj?Yo=#Px82~b^>Mb)ad0M(3`apO9 z2o3kIYZ=$PK}2#Yb?d~4UdXPq%Tx@|Engqv$dbEQxF0>-2OW-i3CVNjp6X66Jd<az z%6}=*VC;;&ZVo(YsZT2`m{NDOeF-oiCiESXjGKz!z46wj`Bw9`BT@9_1yU$hjvvq{ zqeX=d2ltCBU;gva6>g>5DtDUY{oqk3tGFs7lY&43@}WhOVSBuhev2;?sWT(@u_}Bk z9Z%;kox4*e1)~(c4?`D^`)$9U_BEv)g4-fr7k~GLRCEB=whdb~;oR9qa%3x+O@}?F zjAZ2eB2^fc<TQQ5W_LENDxfxp392OzUUTr32XgJTAP4dc;=Wo|Ow<M8+jxx!^hTBr zZ$KT&upqSE0X|0F_$T#Q+z=EZN)a6bU3Xdf53t~xMJ$ItSBfNJv6W$s?K1ExER?$7 zN#RB&&vIvX#q-Y)Q%elQ9BKbEj}%H8ncT~3fmZ9+=kbf_$Qlj>$5y`$t#5_+$;hUN ziC)&<9rPXYt;h!p$4dpDmCE`R15$Qf!+*#yNKU5WL~O`{n}Z{=<P^oNhB|eg)=8f< zgAi_~wuSte21V0ilB<*6;yWE<K0&`?FXA-D-nAqmpA0?Cx7(Ev_z2{db29Eab!S>3 zRa-dG7Eo>VFK1k9&prYH-O2W3??CXwCrC+f9+-SS^w1*qLXdq|1a~v10yqABIPjYS zG=^|{YH_Pco@HDr=yEwqFXTx_1>G{L_)OTXeOe3DvoB-|>z-cC^^Lte3L2e08xsHH z^Qy}L=9F>#aWAM>i0OY;?n65*nBq0*3LJi@_Iyqrp=i#Rhu~N~KIT5x;6-Uu=7Mnz zSPY}VC1-JN?VCP&u;(KR4$yRP(2M+N*+qAN;DA%edTO#hQW0e}@q_qdAP~E4H_yq7 zCSI*TYYd$w{t<xojz2H|yp|zoI*Xbamog_nmsM%ptqGs#VHG1%t8)^L8LBlFfjaSK z-)Q;x{1-p=#&0iq;y4q_V@5}2psBhw%^G#|1Q-}SDIVB|Qe!fcblD^(EvCRCj<R`F z`rF6lcajQBr;4K%UWQ$Bb=2&eOe2{A*(nUAJCm*mjkUdz-&d2(fs|=4%azod!lz4& z_(bK?ZJ7@Hm2j?Vi%D(X{Nsg2u~jRNX#8)Rmz9FFR@O2~G7FKlj!<dYKiwzvZ4c`I zrP6S9YzEho4W!U}OX`|9IeLy9M|6&S7S!_-=C~5w?)V;r3;9<Z96bCkE0Mn6yX%qy zvWgnk8EC47Ul%>zWstJLmU}vFc2i$#uoApUnE*z}$|7pLHg(;aey_;4t;ez95|?_{ zpXS*!{lwh%s!i#Q8DAbWI<r(+Zs+(zg8kGqJM!3cFaiiY`KF%4)ykJjN0h`rR8;xW z%%7Q0Ya%4mGdESJ%VApaduAs3GAg;(F-xh=#<T>NJH7(37URTzoat(c=nW{LZeE!N zJaY_}q;FF~yO6BG<U=kMBOj4De7P)#_*3;WPq+S(a_;>PwyeC=VNj7e_N5|$4&)zD zMZDK3Ne^kL2RzzD#+|(}di*q;=k`XSP_q?DJFpLPQwxi!=bN#W?3g{^lA~zmo3;=v z8FS*3xF<@s6=yqgvXAp`(w7V2hKF<ZdlcboD>O>8p<(PoPoJ<9dICr8?RtC+K0TJ+ zIaVj&rI{Wh1W}=%zq8@0`{w&&o=WcnSdA*X@r_gPT;^_;=qFBIeh?A5HjKM2I|RG| zZ)OBtZ#_#`gtJ55N)92IeJA?GvG0D2sK<k7k0P1X?Yt2yr48Q`?Wf=u+`n05B0U{Z zlD>VMf+^b!vu_c}P9%<VnrdM~SGs_57C$K+u*&Mh|1||_I2P9G3$>Rj(S>5g4jzHw zo5k^FL*3yfXJCjjG}UVH#{?Z5SPsut#RuIPz-KAL#`%3`3VT-BW;tO}C~oVqW#e5h zq8klDVn3aez?z;Tm`GTzGbELp85ta|>voifdYtx?&F423r<2^xb~uKA1&^1T-cAaT zflrilbRxMRTie-8k8qKL65Qz=8Hu7EF>RnwnDg8^N5v!)R?HgQt(tAz!}V`!-zDTV z841%v)8nKyz&W5tCNyU(4*PzwktYz~`?*YTD*8~oqqc~_{{^v_(U8c2=UswKI7f>I zGO*af2qV24+IUdx)Ac3H%%^jWV(V7<f&qep)8d@rhN=CBAr9FMy9}f&$0wyO{qw|( zKZ_w~=esg3Sz%B|=VF;+2>-2z$N{Kv1VvPpfQN}GQZ4H*lDKg*2kPv=Z9t;tX30uv zX{h*OTh!H*jh>#sXcbEE^NuLE4*LB<2`2!zp=)|8ER#A8KwJ7!(C)E7NFK>IAEbQE z#Q7=>n+$zAK1He+PF%t^75wVvIM>Xd=nfJtVl$@_&TGP+NCPY2-k=Cjl)9gMMM(Q$ z))|Y1NIE1LB4<G#hQebvqW%eo@1HS3{yz{FEd1>8{)HF|_z++E91bATYta+P8<Npg z-tROJRefIZs{*r^e3(Jv=5Q@Ga%_)U^d50G=~+(%pdhCgF?G6t;>jfB{ADDw$<jIi z9=GKw=8{oohMx7rDBApQ<SyA(@{ih~ptvJY6RD3kH14u$wf`mxMGVtt#XeCK9O4i6 z5#(2b-3<PvW=W@9z)S6xTIZl5+!r2)i&uoW$Q4!cz+DFsZM^pDkL>91HOq`WOA03c zPM%e|M=o1&5)r4hOD4g2Y{+6lkff?w0}SyHI69y}fwWvg4N&@i-T%|6|0^F!#ldS^ z^27Cvf~D3&EjF~8e#B<=#ZaD$JQ%xRk#7mMf;N%EHm^VB+s8A$qPB>Ih`IjeS85*B z5C)L8m3rP5h=bu);04G23~hr`B}SNVH~IL>UEQ$8)EbB2Q151cA(+}!K9t0lFgsMn zHP#`u!=t{v;-v433%nt|wwEHb4Oe#ygTJOkKHJ5=IbFIncwOygdGWfV@#hP+IMY(D z?uAwMp@i>FC>~5yETK?Tm_)pz4~2}0PwwIpGM$3~xC~QmaG0cD6sut(F2@tlriQ$U zV?5apAhPhYEqAl&ZIw2`Dn;VcOtC8Fs&z839%QXwwebNG`CD6!Q1zm>E|y<z*4EPO z23D5AiA#t4yfD<gp&Zj?x<0_aPRawut#WF|kM|>>u-~rSQtA126DSe2cvglc`AzJn zm&B-uy&!GF8N|=6T?^|7iwI0&<7y>Yy07_>HYIEz-YWJ4^FAN@P<yhi!h!j~Y9AP8 zm~{--fT@R9*q@hFJ<1&iO&JklKh@suTFArQSDE9++{6wY=aYHF3)tfWB>@(w#>Y8N zAy`QLg>AKCx)kdYI(#mM-c*|ITehh-@h@?*?QiY#-)+AT^v~EkktUHC2#m4ieJ8TB zBnpIV9xrY3uazSz5sT(xpHZ~48oD@!L>qNBz58M~M#nlWQWq3JV2pgLCY(W0I?oO~ z_`e!|0J__Skp63a%BikA#Xko`=oV@v#lnIf;MtyLpK|?MS05<qUw}ghs+lzCRVIWO zwhJextm}yC=yicRs-$dwscmXV|8NJ_jyhhvdg!{y>~S4cxllK*^)IAr3iwr=Py9M5 zvx({STb}?XVD{@EwGzO_&6#`cb<qv{e#|+o#BiK(7CX{yzn9;tE9JM=Dp`C+>?OF{ zdJAZzL_z-^W-eonfRutcUhiL2WH{tR4PXIsNZ=HKo1$Niq<E*9AULSxGUMRi-_YCR zy_a_ian4VrlihP~8n0?6qmj+|M^|SAExrgY@P6LE>s9H}kM%`>pxZR`iB)=|C;ZGJ zZ~q=Y=Z0xEMq;gA;vPY}V0=f%B1{zrrS9{nYvdJAjF8lAA-5s3Tpmv}q)px(WL4|{ zz>z)k*8FLAdkB+#7w^Ic)#)$57wKXsuU^>R0BQqA#&LUukY7n#zrsjEf71^RK(Heb z`H`Xn7n#HVaI;>5W>}p`$8Q)FL_mF_Bt)uXtw0bJRZTzDCsZ5A$i8Oy<Hk1YPrG<D z!ViRUOK`}D`Zu~N&<{J+m0XsiZa!5zI;!j|aNcvwNNZGklWsf@1u+h)fKMSGK8+0& zSzIGldGXh#KYR~FrS1nGFFMRu)(zyr7j~Z6jp%!wX=>%K!na*%RM_6{MM&|J2H4B) zcQup*8ZcVRz#j}W@apggY3ef`c<(@Ud9ER|npk#Lkx}@0;1I7!lwuot;j0J!?JKvf z>kAxH#7N`OepvvCo{_jB%KbT@d;b0P)CX`$(qnE-fV<-8q6_2;7vB1lGpPZSq0-*@ zT>qofMJU*j)rkAdZ8Yo@vye}bs<TxP7L)BUXwv>3%(1XGJDo8o{dt)SU1;FRx}OB# zIA|HbU%`lu4sj(;3u<nlG6|&;@-Lt?<>46ELV<-zE=rEd9Igs3UL%(%XHgA-G{m4Z zG2Jsb9E*7972nBq7}b#yJxtmi!&q}{o*~~>GzhNBIg$thXs&WzyPGBgmAwu{c?Fhx zVAKahN)yu;c^oT6%1t|vLic<r(4#0c$@brc)EOsMuk(y-8fxlMzSOg!ob|q-j^W(g zqMY53fVYbHfPcOTGp3O@F=5)$bw-B8Pmq~Sl)tpOxTNZh)OjP{rL!(eH}LD|DDR?F zQ*1;7!A$o}+qiQtc?U3$Cn&116Zl!cmhMhdv6<>5M`w+*(0=y16kBgtRsQzJg2thQ zv73?r%a8uRObWd2kwPAcoO6t?fG?75w^bb2gVWb+!jTIQFReMAtk05y^3?9d1p=(9 zYW@oxvsIUseK;^wL<If9ZBl(CB~jFp-$yP%x_TuCUmBwEu^Gb5Zvg=|H5NqkSHNZI z)HE|H{)1^x6KvL7;>kJC2XjM)?8M{-(R<+N9MAVbPi{1s66GVOe&idgL?v!+`9Bvd za>o=>g_l@X9O6k7T$k0H#c`1UnytSpXj6#uTB()40XSjO4oh*j0#kc9DG(sa!ROCq zjBX^f?TK=n=<qiSB%)aYPc%fA!U`-x^9EqDTLsMR{*wRTm9PyzA@n<kwxulnhBob7 z_-I=OXj>BZJ*CM8S@vFH=CM-l`;2R#UEf@*7EiXS3TIP4FV!>nxEk!EWvKLN!q-4U zC@)_%Q$x<5%)+~~R#2-lL{;=cF)+ofI-mtp58r>?7z{bS4!jS9by=+&=1F?O!evDT zNXZ6~3k})jY%I`>PW5P2jpLES+kNef{y*|g#8v4JVJM`|bsx|dK?Lz`3W=ApOAb~V z5Y^$BojmaDzCD~3)P)WztHPV~CeYYdhY7)XQpXXtb{24$g$nI#Zh*W?^PK3gE-*n$ zIF7>OJS^UgKn-J{b}P;!Tyt>UW<iHM4>?FWufxu@33g$}Df^cj*Z(16vxDpS*p;8> zLnx|>+XL8kSU+f(CnE5rOR&rz-=@W?f^)R^iD9?DPvI$8@$dq^yeg>#H{_X4qC8Zm zY;nY%AyG;w|A?q&UXHVI-jc2CZjSU1dP^ps(=iv&W4mV)6L8U1YFrT7`T&n{eqqh{ zVqF^-_lzgdLvyAQ_iY5uxvq=<;PEfN*-TV5=Nz6OO2`Tt34U#^&uw)>ANfAl#)`|N z${pFTurJy+SNf9hH;5BwK)_)wERkAJyjT|ftZ3s}Imr7>nY!3`PI9vBNQFH0M2KsF z#rot!BNk`?w4X!F%lKBr<{~ARt*9EIvI=*Wp~t3ec}U7x_4xdlPCV0fT2P@s^L%B2 zU27rcldNK*c5a>2x`p*wgUlrMgK9^d2bwrnE3#d)I;R-IC*cv+Cz1?(KWh`Y$1~Q! zLYA{aZq#>n-imqnn{SyUSzZ}hwpLQyX^cyMZ^-M80wX_GGU0$B_W<52@#i_PLSu6{ znICY_;*Q=h=TJ7+nrc)`LDB3xpkl`#J%$XHr$5`+(j=noIN1l%qA$P})Y#<1KEpr< z(g3YR;a0>RMT>c&@sOo7W!!&-r%?8}n|LkXThLFUfjCMypykcc)A};@faf%`wb>J) zAufwtd&UQyVLdjeDd9p*jGx0L>+Sq}M=kMp$;W#G^Q1kZk)w4Ad6eKt-<T?T4!QGT zFsvZSYJ=v~1a-p^9}w0bxyFYl0<uKe2K}}?i?_ba9noWPJPrV$e1${Xd0m_j<Fe=F zHzGpX5V_?=H*$7eBsZ6Pa%R4{s0_KV!l&uJC*_cfe<Ldk+ZSfTH7#L&pmT!oMZk$| zCBopZD>N)cFyJg$@T>QI(V7;uR@Dqik_xbcyKRvn77}p;XJ8I!FFW!UsC}Ap&c^hx zRSs8$Gvw<i0{qh7b3;dg4Kj-%s{89)T<B-|gaj-_VtM$kD}1d+5IfcPTFj?0&@NN3 zM}bpW<rJ<=E-Pdcgs&Qu8d$xJn^F72mg%Canq=;!<mSW(v-sj<??WuLR>q@f*jra4 zJXZ>Wrlz~_b0vp9`i@m?c_pQ55h{zi3825pr{HEu*^hLZI^Mk^`v(z>fIdv)UMXUQ z-ei7at8=j=h;1*na&6Pl2?k!7h$r?FsqM;SxW#e4AHOckpKUmeeLu}vgAI^<SlAv# z(<6NUQVrUj`!&0C@3~rZ*PQdPcng<9n;z<^>sLx$ZKXw7fOE*`Wq@o*L4>lJAMGjC znofV}ucT~gkQj19o$&R_9`hW6XxyA*?k)RxtQ1Z`090DB>RB}P==h+GfN6Hp1V~^} zEd3TK_w}gUf6CuXkcE?FTk?#t-QYnf53tyz&<KQD!7pTdVrpL~oBB~KCV3T+79alQ zQ&vh;1$5&K#oj~|{dPe{H~W|rn&bWx9I5o$>gwLkw0UUO;=hT}U=#q!1oBGKSOg!4 zm)FzVo{xhF8HW!wLiG=Y>J8wK=)YIYrpQ3a<DL}{wR2vB-9_EKp%c&o3}ndN>gCb> zN@{aW>xw{i*lXMaZF$`cxZC1%lyAQZP4sG8&`THOHt5a$d$@=FnkZ&-x(6obAp*JC zLq@uEbOKP)FW9<NFoASm3eD9>AOg5nZSj+w{1WrA-`q(qv4a!{wq|kW{2f^mG;@4F z`->|A1rzFCYVdQ}{B5ay#MfD27k;D+IZX5#9kDJdjXKqAFeZrpJC#|H^hKGWEZ27M zCP}OYzIzI*94kjBmP3g*0F1sGev1b?_AdMTL2lJT(U!7I-n^8+yZ?+5GBrfIw_(O( zBYEGG!X+{{a2^R#Bb-Y%nf0Ib^)4rFpxE<E%+A6Rz8vyZf)EhJP+H}9VEh(nhSMhn z#A)<F3LN)a9C&$`XP=fxjDJg`9BTdX`XLmLM)%EXVzN8@!8rU{`C?|9FfHhky@BPO zH`du^nK&0birAT>scQ5eqJQsN$i!edO**)6Dy|dm!irKY5yYEw3km@guMx2B#$#7- zg})8*naqo=*2}%4#<RK0=U*4JtnuyKX8C6@K^Ng~lF4PjYsz^HSwi%iGv^PfGFAUP zqicA(zD0_Hd^l<YaK9>h68SaIvt!?SZav7dU+y(mh;Fx8yb)dEA!OS#qvu(WQ>XDl zuSgI36FJKWN5JSZUW=`3t>=kSbT`^uCb+^S+e|VG$~hI0R3qXH>IZYAc(%1kL(ZA) z;0tfmY9JiSbr-m^5Y78Z${+nD#E-OF00r0m*p<N0_jN8xQf$NSvf=efkxu+irx0LD zdvRK3rH{U+&OWtz0S7QLX3^yo%beP21lr717h5A!)0BP=dh|=laMIV{*2yRf;I1gX z`S$b|EtE`d2NssG=Y0EA9FRO`t!!CM8Ow3k3SNc%(I%R|E;{8-!K@Cev>(7_*c&DR z7weJR=8KttVTpNzR8NzPl@h$BuA80xe&8%FBi1|$i@%F8!l%tOaQs0Z(j83b>hN|4 zzL5cg79P=jo#8$mH4S#^131ZwFG<N89$QXSNcF-IxRN3Z8i|-CS^n<O2=jFq{mX-% z%k2w~MI7vOMDn#Fqmzt@gtiRX@Ih8otgY-Sk|w$bb)|R)jUeiS>+@jv%Aru!iJVSJ zDQ16HVJH^bAbq=O5A&RJ%fH@`m*Fk4Wq$5>zJyl{58(S44)J+NO1fEfyG}dNGizS% zrSXz9MK*~mrz?(=xknT$j9kXjHU~_cIJ<#_Eu1oWaqdg4<9PYslp&EUMGO~!>wtWH z)G>vUgz_D4NhJ`buMbsp{k`Jfs4U<a@%{+c?*ZZGxpwYilENL-Umg9n^b45@BMOHj zFLNKhPGSGaOPC22LQJ)+`JI?m#A~I9a;B{nfPipExln@o8HZ((j*6@$%3J=g*lGio zF;>=1*y@6P?MmU&d0oj&3fZ<6Cdn`$8~70ju#Y4Sbk^S&RntJwrGd8VM6ZjQO!iNI zsQZ8Kvy8#(VUIi||NP>sch(`*(CEfI{NgVIERF3S^OD5r9X9Vkr*fawq^x3oEI!JW zm*|r!F}Rl)P{nDPb9&*m(KXKr3Zy}EtHh*TG}pxON|scKmh96-z@1c;t=(0>;va}0 zhR*o&$h^PM3SEaTPv*6oRF%7v)S(*#MF#H#bR1-#EUmww4T3|i!SK9Qj%ZA+3JJNE zuus5uiJ5lrgcf*eI%sQX`=t`3fpy~eoqcHp<knm3gr1l;=VvJvL$*-BRfi&QNA68n zV@J7+OBt?Qj?g_*LHh7{rpoD?@0LO+S==d}PfQu8+tv~$gPpuUl&82OduO=U<3}ZO z)UY2lxyxMPlL{wLh{;(`H)o%Ah0vu2h(N7B1jbsOBEMc=4RV2}i|f+p>hN`U71mTj zK;~Pq`WTDNOFw(AL6X>70=z7>lJHTC^SS*u&dNR$0hkLk>zEVB1B%nAYkG&s=dQOO zI^f`z--Cd^{VkahiPbL_MALgE!8$gj9XNyT<7Bjasz`^b`ddX!(Y%dA7ERLzMhXcn zARC>K1YbLlHJ?AN7#_!dBINa3C9f9!a{3^+Es*R5Zz}f#2mQkCUcP`szR~N8V2Ho; zM&`ArO?jl$84kRiLE1?&N}x^q9HxZ560}GS(LoE$A?}8nHczguOr%9Jgl8oBz`U?L zPW>wtWS;%#<#n<rwF??kGan^=rLzO-69t9zCE3BGrl{lFK?Il{_BN|PF~yBL*{%sa zg>gj&V<wsUHFXqvo7vMR-@~N3><ym}LbFdB9YHyK>xGiybrZr)S%yf5qKW>v)9PBV zUi{Iqw8dZ1W{TNwaPiUkjs@+yrK)Qph4eNJQnho#vDIF=ic|V`Z^XfJn5DOy0Dn3Z zLFN$>OWY4d@mdiV5~y%G{B+`0y5{JC@k2a*|3T`mrxw`pf(P(*gO2hpzQdIaRmS%8 zmi}82=Wy*-U{?zQ^~TJ1&~;4O&v0e#H2YA0ZX;>DyI*kq7n1lPkwv{br;8E#g8+JO zr%5jh0-h0c`5)mKT#GSk?JYwD=0N<Dhs8su9GCzB%(eu;q6<>-)-Sf{-y8w}0000A FSz6^gi4*_; diff --git a/gorgone/packaging/packages/perl-YAML-LibYAML-0.80-1.el8.x86_64.rpm b/gorgone/packaging/packages/perl-YAML-LibYAML-0.80-1.el8.x86_64.rpm deleted file mode 100644 index 3e2df93207b6e6e83a024a3622702ea3b641c33f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 81484 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 diff --git a/gorgone/packaging/packages/perl-ZMQ-Constants-1.04-1.el8.noarch.rpm b/gorgone/packaging/packages/perl-ZMQ-Constants-1.04-1.el8.noarch.rpm deleted file mode 100644 index 0f0845812689b72b5e0cbb7cc848f3beb7ea6e7c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15508 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 diff --git a/gorgone/packaging/packages/perl-ZMQ-Constants.spec b/gorgone/packaging/packages/perl-ZMQ-Constants.spec deleted file mode 100644 index aeeb5ac5b50..00000000000 --- a/gorgone/packaging/packages/perl-ZMQ-Constants.spec +++ /dev/null @@ -1,56 +0,0 @@ -%define cpan_name ZMQ-Constants - -Name: perl-ZMQ-Constants -Version: 1.04 -Release: 1%{?dist} -Summary: Constants for libzmq -Group: Development/Libraries -License: GPL or Artistic -URL: http://search.cpan.org/~dmaki/ZMQ-Constants-1.04/lib/ZMQ/Constants.pm -Source0: http://search.cpan.org/CPAN/authors/id/D/DM/DMAKI/%{cpan_name}-%{version}.tar.gz -BuildArch: noarch -BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) - -BuildRequires: make -BuildRequires: perl(ExtUtils::MakeMaker) - -Provides: perl(ZMQ::Constants) -Requires: perl -AutoReqProv: no - -%description -libzmq is a fast-chanding beast and constants get renamed, new one gest removed, etc... - -We used to auto-generate constants from the libzmq source code, but then adpating the binding code to this change got very tedious, and controling which version contains which constants got very hard to manage. - -This module is now separate from ZMQ main code, and lists the constants statically. You can also specify which set of constants to pull in depending on the zmq version. - -%prep -%setup -q -n %{cpan_name}-%{version} - -%build -%{__perl} Makefile.PL INSTALLDIRS=vendor OPTIMIZE="$RPM_OPT_FLAGS" -make %{?_smp_mflags} - -%install -rm -rf %{buildroot} -make pure_install PERL_INSTALL_ROOT=$RPM_BUILD_ROOT -find $RPM_BUILD_ROOT -type f -name .packlist -exec rm -f {} ';' -find $RPM_BUILD_ROOT -type f -name '*.bs' -a -size 0 -exec rm -f {} ';' -find $RPM_BUILD_ROOT -type d -depth -exec rmdir {} 2>/dev/null ';' -%{_fixperms} $RPM_BUILD_ROOT/* - -%check -#make test - -%clean -rm -rf %{buildroot} - -%files -%defattr(-,root,root,-) -%doc Changes -%{perl_vendorlib}/ZMQ/ -%{_mandir}/man3/*.3* - -%changelog - diff --git a/gorgone/packaging/packages/perl-ZMQ-FFI.spec b/gorgone/packaging/packages/perl-ZMQ-FFI.spec new file mode 100644 index 00000000000..ca4ef00bc76 --- /dev/null +++ b/gorgone/packaging/packages/perl-ZMQ-FFI.spec @@ -0,0 +1,62 @@ +%define cpan_name ZMQ-FFI + +Name: perl-ZMQ-FFI +Version: 1.18 +Release: 1%{?dist} +Summary: version agnostic Perl bindings for zeromq using ffi +Group: Development/Libraries +License: GPL or Artistic +URL: https://metacpan.org/pod/ZMQ::FFI +Source0: https://cpan.metacpan.org/authors/id/G/GH/GHENRY/%{cpan_name}-%{version}.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) + +Provides: perl(ZMQ::FFI) + +BuildRequires: make +BuildRequires: perl(ExtUtils::MakeMaker) +BuildRequires: zeromq-devel + +Requires: zeromq +Requires: perl(FFI::CheckLib) +Requires: perl(FFI::Platypus) +Requires: perl(Moo) +Requires: perl(Moo::Role) +Requires: perl(Scalar::Util) +Requires: perl(Try::Tiny) +Requires: perl(namespace::clean) +Requires: perl(Import::Into) + +%description +ZMQ::FFI exposes a high level, transparent, OO interface to zeromq independent of the underlying libzmq version. Where semantics differ, it will dispatch to the appropriate backend for you. As it uses ffi, there is no dependency on XS or compilation. + +%global debug_package %{nil} + +%prep +%setup -q -n %{cpan_name}-%{version} + +%build +%{__perl} Makefile.PL INSTALLDIRS=vendor OPTIMIZE="$RPM_OPT_FLAGS" +make %{?_smp_mflags} + +%install +rm -rf %{buildroot} +make pure_install PERL_INSTALL_ROOT=$RPM_BUILD_ROOT +find $RPM_BUILD_ROOT -type f -name '*.bs' -a -size 0 -exec rm -f {} ';' +find $RPM_BUILD_ROOT -type f -name .packlist -exec rm -f {} ';' +find $RPM_BUILD_ROOT -type d -depth -exec rmdir {} 2>/dev/null ';' +%{_fixperms} $RPM_BUILD_ROOT/* + +%check +#make test + +%clean +rm -rf %{buildroot} + +%files +%defattr(-,root,root,-) +%doc Changes +%{perl_vendorlib}/ +%{_mandir}/man3/*.3* + +%changelog + diff --git a/gorgone/packaging/packages/perl-ZMQ-LibZMQ4-0.01-1.el8.x86_64.rpm b/gorgone/packaging/packages/perl-ZMQ-LibZMQ4-0.01-1.el8.x86_64.rpm deleted file mode 100644 index f0a24dec8330053a7cab1a1bbcd5c67ca474c52a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46812 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%0D<#TSmfM+%`9RIqb{T&4{^vj2}Wu_Wd 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 L0ssI2018=JmiEPx diff --git a/gorgone/packaging/packages/perl-common-sense-3.75-1.el8.noarch.rpm b/gorgone/packaging/packages/perl-common-sense-3.75-1.el8.noarch.rpm deleted file mode 100644 index 8f5a24cef993f7156e96378d24b2e49122354100..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15072 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 brB$;mng!Hot)5u3Fe%3P#{>|M)0zJP67QV; From bcb84cd1ca66bcc2b0aceb98dea81ef465b496dd Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Fri, 10 Mar 2023 16:40:48 +0100 Subject: [PATCH 719/948] fix(ci): retrieve zmq dependency and fix gorgone engine module (#1088) --- gorgone/gorgone/modules/centreon/engine/class.pm | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/gorgone/gorgone/modules/centreon/engine/class.pm b/gorgone/gorgone/modules/centreon/engine/class.pm index 85dea2b85ca..178092a33ee 100644 --- a/gorgone/gorgone/modules/centreon/engine/class.pm +++ b/gorgone/gorgone/modules/centreon/engine/class.pm @@ -204,8 +204,15 @@ sub action_enginecommand { sub action_run { my ($self, %options) = @_; - + + my $context; + { + local $SIG{__DIE__}; + $context = ZMQ::FFI->new(); + } + my $socket_log = gorgone::standard::library::connect_com( + context => $context, zmq_type => 'ZMQ_DEALER', name => 'gorgone-engine-'. $$, logger => $self->{logger}, From 6ef6f00ec3ced97ddaa27b559a3081053e026777 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Fri, 17 Mar 2023 11:22:08 +0100 Subject: [PATCH 720/948] fix(mbi): add option --no-purge in script (develop) (#594) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: garnier-quentin <garnier.quentin@gmail.com> Co-authored-by: Stéphane Chapron <34628915+sc979@users.noreply.github.com> Co-authored-by: Laurent Pinsivy <lpinsivy@centreon.com> --- gorgone/contrib/mbi/eventStatisticsBuilder.pl | 4 +++- gorgone/contrib/mbi/perfdataStatisticsBuilder.pl | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/gorgone/contrib/mbi/eventStatisticsBuilder.pl b/gorgone/contrib/mbi/eventStatisticsBuilder.pl index 992d454af6e..6f993f5a6e3 100644 --- a/gorgone/contrib/mbi/eventStatisticsBuilder.pl +++ b/gorgone/contrib/mbi/eventStatisticsBuilder.pl @@ -55,8 +55,10 @@ sub new { 'host-only' => \$self->{moptions}->{host_only}, 'service-only' => \$self->{moptions}->{service_only}, 'availability-only' => \$self->{moptions}->{availability_only}, - 'events-only' => \$self->{moptions}->{events_only} + 'events-only' => \$self->{moptions}->{events_only}, + 'no-purge' => \$self->{moptions}->{nopurge} ); + return $self; } diff --git a/gorgone/contrib/mbi/perfdataStatisticsBuilder.pl b/gorgone/contrib/mbi/perfdataStatisticsBuilder.pl index 801e004b18c..da32dd6fd6f 100644 --- a/gorgone/contrib/mbi/perfdataStatisticsBuilder.pl +++ b/gorgone/contrib/mbi/perfdataStatisticsBuilder.pl @@ -53,7 +53,8 @@ sub new { 'e:s' => \$self->{moptions}->{end}, 'month-only' => \$self->{moptions}->{month_only}, 'centile-only' => \$self->{moptions}->{centile_only}, - 'no-centile' => \$self->{moptions}->{no_centile} + 'no-centile' => \$self->{moptions}->{no_centile}, + 'no-purge' => \$self->{moptions}->{nopurge} ); return $self; } From 9d41fbe61aa65c847e2e8098baf8db38c44d7be7 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Sun, 19 Mar 2023 10:15:44 +0100 Subject: [PATCH 721/948] chore(ci): move to new repositories (#1087) Refs: MON-16173 --- .github/workflows/gorgone.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 34ddcc8addd..dd7d1df8dea 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -77,14 +77,8 @@ jobs: - name: Delivery uses: ./.github/actions/rpm-delivery with: - module_name: gorgone - repository_name: standard distrib: ${{ matrix.distrib }} version: ${{ needs.get-version.outputs.major_version }} - minor_version: ${{ needs.get-version.outputs.minor_version }} - release: ${{ needs.get-version.outputs.release }} - nexus_username: ${{ secrets.NEXUS_USERNAME }} - nexus_password: ${{ secrets.NEXUS_PASSWORD }} artifactory_token: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }} cache_key: ${{ github.sha }}-${{ github.run_id }}-rpm-${{ matrix.distrib }} update_repo_path: ${{ secrets.UPDATE_REPO_PATH }} @@ -108,7 +102,6 @@ jobs: - name: Delivery uses: ./.github/actions/deb-delivery with: - module_name: centreon-gorgone distrib: ${{ matrix.distrib }} version: ${{ needs.get-version.outputs.major_version }} nexus_username: ${{ secrets.NEXUS_USERNAME }} From baee2e528fb985ce16c6405c9fd4982138542d3a Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Wed, 22 Mar 2023 16:51:22 +0100 Subject: [PATCH 722/948] fix(delivery): restore release with timestamp/hash on unstable (#1134) --- .github/workflows/gorgone.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index dd7d1df8dea..d614b2e0548 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -77,14 +77,11 @@ jobs: - name: Delivery uses: ./.github/actions/rpm-delivery with: + module_name: gorgone distrib: ${{ matrix.distrib }} version: ${{ needs.get-version.outputs.major_version }} artifactory_token: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }} cache_key: ${{ github.sha }}-${{ github.run_id }}-rpm-${{ matrix.distrib }} - update_repo_path: ${{ secrets.UPDATE_REPO_PATH }} - cloudfront_id: ${{ secrets.CLOUDFRONT_ID }} - yum_repo_address: ${{ secrets.YUM_REPO_ADDRESS }} - yum_repo_key: ${{ secrets.YUM_REPO_KEY }} stability: ${{ needs.get-version.outputs.stability }} deliver-deb: @@ -104,8 +101,6 @@ jobs: with: distrib: ${{ matrix.distrib }} version: ${{ needs.get-version.outputs.major_version }} - nexus_username: ${{ secrets.NEXUS_USERNAME }} - nexus_password: ${{ secrets.NEXUS_PASSWORD }} artifactory_token: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }} cache_key: ${{ github.sha }}-${{ github.run_id }}-deb-${{ matrix.distrib }} stability: ${{ needs.get-version.outputs.stability }} From df543e3c46fa92f3d70277836d4904aad1e3b5d3 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Fri, 24 Mar 2023 11:37:29 +0100 Subject: [PATCH 723/948] enh(gorgone-hostdisco): install missing plugins (#1092) --- .../modules/centreon/autodiscovery/class.pm | 25 ++++------ gorgone/gorgone/modules/core/action/class.pm | 48 ++++++++++++++----- 2 files changed, 45 insertions(+), 28 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm index 6d4e7bdad2d..14e860d41a6 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm @@ -478,6 +478,12 @@ sub launchhostdiscovery { ] }); + # plugins attribute format: + # "plugins": { + # "centreon-plugin-Cloud-Aws-Ec2-Api": 20220727, + # ... + # } + $self->send_internal_action({ action => 'COMMAND', target => $self->{hdisco_jobs_ids}->{$job_id}->{target}, @@ -490,7 +496,8 @@ sub launchhostdiscovery { timeout => $timeout, metadata => { job_id => $job_id, - source => 'autodiscovery-host-job-discovery' + source => 'autodiscovery-host-job-discovery', + pkg_install => $self->{hdisco_jobs_ids}->{$job_id}->{plugins} } } ] @@ -678,22 +685,6 @@ sub discovery_add_host_result { sub discovery_command_result { my ($self, %options) = @_; -=pod - use Devel::Size; - print "frame = " . (Devel::Size::total_size($options{frame}) / 1024 / 1024) . "==\n"; - - my $data = $options{frame}->getData(); - print "data = " . (Devel::Size::total_size($data) / 1024 / 1024) . "==\n"; - - my $frame = $options{frame}->getFrame(); - print "frame data = " . (Devel::Size::total_size($frame) / 1024 / 1024) . "==\n"; - - my $raw = $options{frame}->getRawData(); - print "raw data = " . (Devel::Size::total_size($raw) / 1024 / 1024) . "==\n"; - - return 1; -=cut - my $data = $options{frame}->getData(); return 1 if (!defined($data->{data}->{metadata}->{job_id})); diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index 3c386d16cdf..dc7f30d27f3 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -314,15 +314,18 @@ sub validate_plugins_deb { sub validate_plugins { my ($self, %options) = @_; - my ($rv, $message, $content) = gorgone::standard::misc::slurp(file => $options{file}); - return (1, $message) if (!$rv); + my ($rv, $message, $content); + my $plugins = $options{plugins}; + if (!defined($plugins)) { + ($rv, $message, $content) = gorgone::standard::misc::slurp(file => $options{file}); + return (1, $message) if (!$rv); - my $plugins; - try { - $plugins = JSON::XS->new->decode($content); - } catch { - return (1, 'cannot decode json'); - }; + try { + $plugins = JSON::XS->new->decode($content); + } catch { + return (1, 'cannot decode json'); + }; + } # nothing to validate. so it's ok, show must go on!! :) if (ref($plugins) ne 'HASH' || scalar(keys %$plugins) <= 0) { @@ -412,7 +415,6 @@ sub action_command { ); my $errors = 0; - foreach my $command (@{$options{data}->{content}}) { $self->send_log( socket => $options{socket_log}, @@ -425,7 +427,31 @@ sub action_command { metadata => $command->{metadata} } ); - + + # check install pkg + if (defined($command->{metadata}) && defined($command->{metadata}->{pkg_install})) { + my ($rv, $message) = $self->validate_plugins(plugins => $command->{metadata}->{pkg_install}); + if ($rv && $self->{paranoid_plugins} == 1) { + $self->{logger}->writeLogError("[action] $message"); + $self->send_log( + socket => $options{socket_log}, + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + logging => $options{data}->{logging}, + data => { + message => "command execution issue", + command => $command->{command}, + metadata => $command->{metadata}, + result => { + exit_code => $rv, + stdout => $message + } + } + ); + next; + } + } + my $start = time(); my ($error, $stdout, $return_code) = gorgone::standard::misc::backtick( command => $command->{command}, @@ -495,7 +521,7 @@ sub action_command { ); } } - + if ($errors) { $self->send_log( socket => $options{socket_log}, From 3d6694c02b6ac0e35bb768a791b48edbe72620aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Duret?= <sduret@centreon.com> Date: Mon, 27 Mar 2023 11:12:36 +0200 Subject: [PATCH 724/948] fix(mbi) change the type of metric_unit column (#1117) --- gorgone/gorgone/modules/centreon/mbi/libs/bi/BIMetric.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIMetric.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIMetric.pm index caf4fb8503e..55269e07ba9 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIMetric.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIMetric.pm @@ -94,7 +94,7 @@ sub createTempTable { my $db = $self->{"centstorage"}; $db->query({ query => "DROP TABLE IF EXISTS `".$self->{"tmpTable"}."`" }); my $query = "CREATE TABLE `".$self->{"tmpTable"}."` ("; - $query .= "`metric_id` int(11) NOT NULL,`metric_name` varchar(255) NOT NULL,`metric_unit` char(10) DEFAULT NULL,"; + $query .= "`metric_id` int(11) NOT NULL,`metric_name` varchar(255) NOT NULL,`metric_unit` char(32) DEFAULT NULL,"; $query .= "`service_id` int(11) NOT NULL,`service_description` varchar(255) DEFAULT NULL,"; $query .= "`sc_id` int(11) DEFAULT NULL,`sc_name` varchar(255) DEFAULT NULL,"; $query .= "`host_id` int(11) DEFAULT NULL,`host_name` varchar(255) DEFAULT NULL,"; From 46de46a2229b61b8894849e0527ecc7f977cc581 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Chapron?= <34628915+sc979@users.noreply.github.com> Date: Tue, 28 Mar 2023 14:26:50 +0200 Subject: [PATCH 725/948] chore(debian): manage version threshold (#1172) --- gorgone/packaging/debian/control | 1 + gorgone/packaging/debian/substvars | 1 + 2 files changed, 2 insertions(+) diff --git a/gorgone/packaging/debian/control b/gorgone/packaging/debian/control index c220d64daf0..efe34b637b6 100644 --- a/gorgone/packaging/debian/control +++ b/gorgone/packaging/debian/control @@ -12,6 +12,7 @@ Package: centreon-gorgone Architecture: any Depends: centreon-common (>= ${centreon:version}~), + centreon-common (<< ${centreon:versionThreshold}~), libdatetime-perl, libtry-tiny-perl, libxml-simple-perl, diff --git a/gorgone/packaging/debian/substvars b/gorgone/packaging/debian/substvars index 929c65b6607..0e75aa07254 100644 --- a/gorgone/packaging/debian/substvars +++ b/gorgone/packaging/debian/substvars @@ -1 +1,2 @@ centreon:version= +centreon:versionThreshold= From 8a6f21297b75bd55a74b918febbcd510822b0389 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Fri, 31 Mar 2023 09:47:50 +0200 Subject: [PATCH 726/948] fix(gorgone): mbi unable to rebuild partial data with new scripts (#1190) --- .../modules/centreon/mbi/etl/event/main.pm | 19 ++++++++++++++----- .../modules/centreon/mbi/etl/perfdata/main.pm | 19 ++++++++++++++----- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/mbi/etl/event/main.pm b/gorgone/gorgone/modules/centreon/mbi/etl/event/main.pm index bc59ac696b7..6ccbcc447f5 100644 --- a/gorgone/gorgone/modules/centreon/mbi/etl/event/main.pm +++ b/gorgone/gorgone/modules/centreon/mbi/etl/event/main.pm @@ -83,13 +83,22 @@ sub deleteEntriesForRebuild { "DELETE FROM $options{name} WHERE time_id >= " . $utils->getDateEpoch($options{start}) . " AND time_id < " . $utils->getDateEpoch($options{end}) ]; } else { + my $structure = $biTables->dumpTableStructure($options{name}); my $partitionsPerf = $utils->getRangePartitionDate($options{start}, $options{end}); foreach (@$partitionsPerf) { - push @$sql, - [ - "[PURGE] Truncate partition $_->{name} on table [$options{name}]", - "ALTER TABLE $options{name} TRUNCATE PARTITION p$_->{name}" - ]; + if ($structure =~ /p$_->{name}/m) { + push @$sql, + [ + "[PURGE] Truncate partition $_->{name} on table [$options{name}]", + "ALTER TABLE $options{name} TRUNCATE PARTITION p$_->{name}" + ]; + } else { + push @$sql, + [ + '[PARTITIONS] Add partition [p' . $_->{name} . '] on table [' . $options{name} . ']', + "ALTER TABLE `$options{name}` ADD PARTITION (PARTITION `p$_->{name}` VALUES LESS THAN(" . $_->{epoch} . "))" + ]; + } } } diff --git a/gorgone/gorgone/modules/centreon/mbi/etl/perfdata/main.pm b/gorgone/gorgone/modules/centreon/mbi/etl/perfdata/main.pm index c5fbd0ab3a8..170903907c0 100644 --- a/gorgone/gorgone/modules/centreon/mbi/etl/perfdata/main.pm +++ b/gorgone/gorgone/modules/centreon/mbi/etl/perfdata/main.pm @@ -83,13 +83,22 @@ sub deleteEntriesForRebuild { "DELETE FROM $options{name} WHERE time_id >= " . $utils->getDateEpoch($options{start}) . " AND time_id < " . $utils->getDateEpoch($options{end}) ]; } else { + my $structure = $biTables->dumpTableStructure($options{name}); my $partitionsPerf = $utils->getRangePartitionDate($options{start}, $options{end}); foreach (@$partitionsPerf) { - push @$sql, - [ - "[PURGE] Truncate partition $_->{name} on table [$options{name}]", - "ALTER TABLE $options{name} TRUNCATE PARTITION p$_->{name}" - ]; + if ($structure =~ /p$_->{name}/m) { + push @$sql, + [ + "[PURGE] Truncate partition $_->{name} on table [$options{name}]", + "ALTER TABLE $options{name} TRUNCATE PARTITION p$_->{name}" + ]; + } else { + push @$sql, + [ + '[PARTITIONS] Add partition [p' . $_->{name} . '] on table [' . $options{name} . ']', + "ALTER TABLE `$options{name}` ADD PARTITION (PARTITION `p$_->{name}` VALUES LESS THAN(" . $_->{epoch} . "))" + ]; + } } } From 3b45658a16918963a542c556ff4d61b85bace814 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Fri, 31 Mar 2023 10:45:33 +0200 Subject: [PATCH 727/948] =?UTF-8?q?enh(gorgone-svc-disco):=20can=20change?= =?UTF-8?q?=20macro=20before=20the=20service=20name=20crea=E2=80=A6=20(#12?= =?UTF-8?q?23)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../centreon/autodiscovery/services/discovery.pm | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm index 2077b9800e6..948f090745e 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm @@ -578,6 +578,13 @@ sub service_response_parsing { foreach my $attributes (@{$xml->{label}}) { $discovery_svc->{service_name} = ''; $discovery_svc->{attributes} = $attributes; + + $self->custom_variables( + discovery_svc => $discovery_svc, + rule => $self->{discovery}->{rules}->{ $options{rule_id} }, + logger_pre_message => $logger_pre_message + ); + gorgone::modules::centreon::autodiscovery::services::resources::change_vars( discovery_svc => $discovery_svc, rule => $self->{discovery}->{rules}->{ $options{rule_id} }, @@ -604,11 +611,7 @@ sub service_response_parsing { logger_pre_message => $logger_pre_message ) ); - $self->custom_variables( - discovery_svc => $discovery_svc, - rule => $self->{discovery}->{rules}->{ $options{rule_id} }, - logger_pre_message => $logger_pre_message - ); + my $macros = gorgone::modules::centreon::autodiscovery::services::resources::get_macros( discovery_svc => $discovery_svc, rule => $self->{discovery}->{rules}->{ $options{rule_id} } From f51eb087337e2e2c81fa1d1ad0b6013cdd197fba Mon Sep 17 00:00:00 2001 From: tuntoja <58987095+tuntoja@users.noreply.github.com> Date: Mon, 3 Apr 2023 16:18:26 +0200 Subject: [PATCH 728/948] enh(packaging): add commit_hash (#1231) --- .github/workflows/gorgone.yml | 1 + gorgone/packaging/centreon-gorgone.spectemplate | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index d614b2e0548..06ef512a2b8 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -57,6 +57,7 @@ jobs: major_version: ${{ needs.get-version.outputs.major_version }} minor_version: ${{ needs.get-version.outputs.minor_version }} release: ${{ needs.get-version.outputs.release }} + commit_hash: ${{ github.sha }} cache_key: ${{ github.sha }}-${{ github.run_id }}-${{ matrix.package_extension }}-${{ matrix.distrib }} secrets: registry_username: ${{ secrets.DOCKER_REGISTRY_ID }} diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index e460acdb731..81cbdd83d00 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -44,6 +44,7 @@ Requires: tar AutoReqProv: no %description +%{COMMIT_HASH} %package centreon-config Summary: Configure Centreon Gorgone for use with Centreon Web @@ -53,6 +54,7 @@ Requires: centreon-common Requires: centreon-gorgone %description centreon-config +%{COMMIT_HASH} %prep %setup -q -n %{name}-%{version} From 15d20134c5f8609a40b351513c52bbd033d35a78 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Wed, 5 Apr 2023 16:54:06 +0200 Subject: [PATCH 729/948] fix(gorgone): getlog ctime filter (#1273) --- gorgone/gorgone/modules/core/proxy/hooks.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index 06693f74906..832ced26df0 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -272,6 +272,7 @@ sub routing { # We put the good time to get my $ctime = $synctime_nodes->{$target}->{ctime}; $options{frame}->setData({ ctime => $ctime }); + $options{frame}->setRawData(); $synctime_nodes->{$target}->{in_progress} = 1; $synctime_nodes->{$target}->{in_progress_time} = time(); } From f8ec8bde9fbc6dcda27b36d36eaf89b3da5b5ab4 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Wed, 5 Apr 2023 17:02:11 +0200 Subject: [PATCH 730/948] fix(packaging): fix systemd configuration of centreontrapd and gorgone (#1272) Refs: MON-18006 --- gorgone/packaging/debian/rules | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gorgone/packaging/debian/rules b/gorgone/packaging/debian/rules index 95af63e770c..d135e45c060 100755 --- a/gorgone/packaging/debian/rules +++ b/gorgone/packaging/debian/rules @@ -9,3 +9,7 @@ override_dh_gencontrol: dh_gencontrol -- -Tdebian/substvars override_dh_usrlocal: + +override_dh_auto_build: + sed -i "s#/etc/sysconfig#/etc/default#g" \ + config/systemd/gorgoned-service From ec0e07c4820acd525dd6eabd41cdc48ea87b57dd Mon Sep 17 00:00:00 2001 From: sdepassio <114986849+sdepassio@users.noreply.github.com> Date: Wed, 12 Apr 2023 10:07:12 +0200 Subject: [PATCH 731/948] fix(gorgone): add missing perl library for deb (#18238) (#1293) --- gorgone/packaging/debian/control | 1 + 1 file changed, 1 insertion(+) diff --git a/gorgone/packaging/debian/control b/gorgone/packaging/debian/control index efe34b637b6..2fa16f17111 100644 --- a/gorgone/packaging/debian/control +++ b/gorgone/packaging/debian/control @@ -14,6 +14,7 @@ Depends: centreon-common (>= ${centreon:version}~), centreon-common (<< ${centreon:versionThreshold}~), libdatetime-perl, + libtime-parsedate-perl, libtry-tiny-perl, libxml-simple-perl, libxml-libxml-simple-perl, From 09dfdfc5d436c541d26047e7a4550222bcb7c79c Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Wed, 12 Apr 2023 12:01:41 +0200 Subject: [PATCH 732/948] chore(ci): publish releases on download.centreon.com (#1298) Refs: MON-15793 --- .github/workflows/gorgone.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 06ef512a2b8..0f692d9026b 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -63,6 +63,25 @@ jobs: registry_username: ${{ secrets.DOCKER_REGISTRY_ID }} registry_password: ${{ secrets.DOCKER_REGISTRY_PASSWD }} + deliver-sources: + runs-on: [self-hosted, common] + needs: [get-version, package] + if: ${{ contains(fromJson('["stable"]'), needs.get-version.outputs.stability) }} + + steps: + - name: Checkout sources + uses: actions/checkout@v3 + + - name: Deliver sources + uses: ./.github/actions/release-sources + with: + bucket_directory: centreon-gorgone + module_directory: centreon-gorgone + module_name: centreon-gorgone + major_version: ${{ needs.get-version.outputs.major_version }} + minor_version: ${{ needs.get-version.outputs.minor_version }} + token_download_centreon_com: ${{ secrets.TOKEN_DOWNLOAD_CENTREON_COM }} + deliver-rpm: runs-on: [self-hosted, common] needs: [get-version, package] From 3cd227faa5d82ee59476f530955bf575347d8e48 Mon Sep 17 00:00:00 2001 From: tuntoja <58987095+tuntoja@users.noreply.github.com> Date: Thu, 13 Apr 2023 15:00:01 +0200 Subject: [PATCH 733/948] enh(ci): update debian delivery (#1318) --- .github/workflows/gorgone.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 0f692d9026b..744534ba093 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -119,6 +119,7 @@ jobs: - name: Delivery uses: ./.github/actions/deb-delivery with: + module_name: gorgone distrib: ${{ matrix.distrib }} version: ${{ needs.get-version.outputs.major_version }} artifactory_token: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }} From ccde93e8b026056e6f6cd1bd8430c62b00711b83 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Wed, 26 Apr 2023 16:05:22 +0200 Subject: [PATCH 734/948] fix(gorgone-httpserver): no response from the server (#1387) --- gorgone/gorgone/modules/core/cron/class.pm | 19 ++++-- .../gorgone/modules/core/httpserver/class.pm | 36 ++++++++++- gorgone/gorgone/standard/api.pm | 63 ++++++++----------- 3 files changed, 75 insertions(+), 43 deletions(-) diff --git a/gorgone/gorgone/modules/core/cron/class.pm b/gorgone/gorgone/modules/core/cron/class.pm index 21c7078faac..275760b88f7 100644 --- a/gorgone/gorgone/modules/core/cron/class.pm +++ b/gorgone/gorgone/modules/core/cron/class.pm @@ -96,8 +96,14 @@ sub action_getcron { code => $options{data}->{parameters}->{code} } }); - my $watcher_timer = $self->{loop}->timer(5, 0, \&stop_ev); - $self->{loop}->run(); + + my $timeout = 5; + my $ctime = time(); + while (1) { + my $watcher_timer = $self->{loop}->timer(1, 0, \&stop_ev); + $self->{loop}->run(); + last if (time() > ($ctime + $timeout)); + } $data = $connector->{ack}->{data}->{data}->{result}; } else { @@ -436,8 +442,13 @@ sub dispatcher { json_encode => 1 }); - my $watcher_timer = $options->{connector}->{loop}->timer(5, 0, \&stop_ev); - $options->{connector}->{loop}->run(); + my $timeout = 5; + my $ctime = time(); + while (1) { + my $watcher_timer = $options->{connector}->{loop}->timer(1, 0, \&stop_ev); + $options->{connector}->{loop}->run(); + last if (time() > ($ctime + $timeout)); + } } sub run { diff --git a/gorgone/gorgone/modules/core/httpserver/class.pm b/gorgone/gorgone/modules/core/httpserver/class.pm index 74461ead10a..132218339a4 100644 --- a/gorgone/gorgone/modules/core/httpserver/class.pm +++ b/gorgone/gorgone/modules/core/httpserver/class.pm @@ -67,7 +67,9 @@ sub new { $connector->{allowed_hosts_enabled} = 0; } - $connector->set_signal_handlers; + $connector->{tokens} = {}; + + $connector->set_signal_handlers(); return $connector; } @@ -144,6 +146,34 @@ sub stop_ev { $connector->{loop}->break(); } +sub event { + my ($self, %options) = @_; + + while ($self->{internal_socket}->has_pollin()) { + my ($message) = $self->read_message(); + next if (!defined($message)); + + if ($message =~ /^\[(.*?)\]\s+\[([a-zA-Z0-9:\-_]*?)\]\s+\[.*?\]\s+(.*)$/m || + $message =~ /^\[(.*?)\]\s+\[([a-zA-Z0-9:\-_]*?)\]\s+(.*)$/m) { + my ($action, $token, $data) = ($1, $2, $3); + $self->{tokens}->{$token} = { + action => $action, + token => $token, + data => $data + }; + if ((my $method = $self->can('action_' . lc($action)))) { + my ($rv, $decoded) = $self->json_decode(argument => $data, token => $token); + next if ($rv); + $method->($self, token => $token, data => $decoded); + } + } + } + + if (defined($self->{break_token}) && defined($self->{tokens}->{ $self->{break_token} })) { + $self->{loop}->break(); + } +} + sub run { my ($self, %options) = @_; @@ -166,7 +196,7 @@ sub run { gorgone::standard::api::set_module($self); my $watcher_timer = $self->{loop}->timer(4, 0, \&stop_ev); - my $watcher_io = $self->{loop}->io($connector->{internal_socket}->get_fd(), EV::READ, \&gorgone::standard::api::event); + my $watcher_io = $self->{loop}->io($connector->{internal_socket}->get_fd(), EV::READ, sub { $connector->event() }); $self->{loop}->run(); $self->init_dispatch(); @@ -208,7 +238,7 @@ sub run { } if (!defined($connection)) { - gorgone::standard::api::event(httpserver => $self); + $self->event(); next; } diff --git a/gorgone/gorgone/standard/api.pm b/gorgone/gorgone/standard/api.pm index 79c1499016d..a8ec6166271 100644 --- a/gorgone/gorgone/standard/api.pm +++ b/gorgone/gorgone/standard/api.pm @@ -28,7 +28,6 @@ use JSON::XS; my $module; my $socket; -my $results = {}; my $action_token; sub set_module { @@ -40,10 +39,9 @@ sub root { $options{logger}->writeLogInfo("[api] Requesting '" . $options{uri} . "' [" . $options{method} . "]"); - $action_token = undef; + $options{module}->{tokens} = {}; $socket = $options{socket}; $module = $options{module}; - $results = {}; my $response; if ($options{method} eq 'GET' && $options{uri} =~ /^\/api\/(nodes\/(\w*)\/)?log\/(.*)$/) { @@ -149,14 +147,23 @@ sub call_internal { json_encode => 1 }); - my $watcher_timer = $options{module}->{loop}->timer(5, 0, \&stop_ev); - $options{module}->{loop}->run(); + $options{module}->{break_token} = $action_token; + + my $timeout = 5; + my $ctime = time(); + while (1) { + my $watcher_timer = $options{module}->{loop}->timer(1, 0, \&stop_ev); + $options{module}->{loop}->run(); + last if (time() > ($ctime + $timeout) || defined($options{module}->{tokens}->{$action_token})); + } + + $options{module}->{break_token} = undef; my $response = '{"error":"no_result", "message":"No result found for action \'' . $options{action} . '\'"}'; - if (defined($results->{$action_token}->{data})) { + if (defined($options{module}->{tokens}->{$action_token}->{data})) { my $content; eval { - $content = JSON::XS->new->decode($results->{$action_token}->{data}); + $content = JSON::XS->new->decode($options{module}->{tokens}->{$action_token}->{data}); }; if ($@) { $response = '{"error":"decode_error","message":"Cannot decode response"}'; @@ -204,14 +211,23 @@ sub get_log { json_encode => 1 }); - my $watcher_timer = $options{module}->{loop}->timer(5, 0, \&stop_ev); - $options{module}->{loop}->run(); + $options{module}->{break_token} = $token_log; + + my $timeout = 5; + my $ctime = time(); + while (1) { + my $watcher_timer = $options{module}->{loop}->timer(1, 0, \&stop_ev); + $options{module}->{loop}->run(); + last if (time() > ($ctime + $timeout) || defined($options{module}->{tokens}->{$token_log})); + } + + $options{module}->{break_token} = undef; my $response = '{"error":"no_log","message":"No log found for token","data":[],"token":"' . $options{token} . '"}'; - if (defined($results->{ $token_log }) && defined($results->{ $token_log }->{data})) { + if (defined($options{module}->{tokens}->{$token_log}) && defined($options{module}->{tokens}->{ $token_log }->{data})) { my $content; eval { - $content = JSON::XS->new->decode($results->{ $token_log }->{data}); + $content = JSON::XS->new->decode($options{module}->{tokens}->{ $token_log }->{data}); }; if ($@) { $response = '{"error":"decode_error","message":"Cannot decode response"}'; @@ -234,29 +250,4 @@ sub get_log { return $response; } -sub event { - my (%options) = @_; - - my $httpserver = defined($options{httpserver}) ? $options{httpserver} : $module; - while ($httpserver->{internal_socket}->has_pollin()) { - my ($message) = $httpserver->read_message(); - next if (!defined($message)); - - if ($message =~ /^\[(.*?)\]\s+\[([a-zA-Z0-9:\-_]*?)\]\s+\[.*?\]\s+(.*)$/m || - $message =~ /^\[(.*?)\]\s+\[([a-zA-Z0-9:\-_]*?)\]\s+(.*)$/m) { - my ($action, $token, $data) = ($1, $2, $3); - $results->{$token} = { - action => $action, - token => $token, - data => $data - }; - if ((my $method = $httpserver->can('action_' . lc($action)))) { - my ($rv, $decoded) = $httpserver->json_decode(argument => $data, token => $token); - next if ($rv); - $method->($httpserver, token => $token, data => $decoded); - } - } - } -} - 1; From bf1975eaab0681ac35143d4668d9e49047fba9b9 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Fri, 28 Apr 2023 16:53:49 +0200 Subject: [PATCH 735/948] chore(version): prepare version 23.10 (#1396) --- gorgone/packaging/centreon-gorgone.spectemplate | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 81cbdd83d00..943b331e249 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -1,5 +1,5 @@ Name: centreon-gorgone -Version: 23.04.0 +Version: 23.10.0 Release: %{PACKAGE_RELEASE}%{?dist} Summary: Perl daemon task handlers Group: Applications/System From e0061f373ef57389161548ef831ba77a0ed134dd Mon Sep 17 00:00:00 2001 From: Paul LOUIS THERESE <53221698+paloth@users.noreply.github.com> Date: Fri, 28 Apr 2023 17:36:04 +0200 Subject: [PATCH 736/948] env config setup (#1352) --- .github/workflows/gorgone.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 744534ba093..a0d6929debb 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -86,6 +86,8 @@ jobs: runs-on: [self-hosted, common] needs: [get-version, package] if: ${{ contains(fromJson('["stable", "testing", "unstable"]'), needs.get-version.outputs.stability) }} + environment: ${{ needs.get-version.outputs.environment }} + strategy: matrix: distrib: [el8, el9] @@ -108,6 +110,8 @@ jobs: runs-on: [self-hosted, common] needs: [get-version,package] if: ${{ contains(fromJson('["stable", "testing", "unstable"]'), needs.get-version.outputs.stability) }} + environment: ${{ needs.get-version.outputs.environment }} + strategy: matrix: distrib: [bullseye] From 78aa48cd2972067f7dc9d68d444ba98fbe868f6b Mon Sep 17 00:00:00 2001 From: tuntoja <58987095+tuntoja@users.noreply.github.com> Date: Wed, 10 May 2023 09:25:13 +0200 Subject: [PATCH 737/948] enh(ci): promote testing package (#1080) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kevin Duret <kduret@centreon.com> Co-authored-by: Stéphane Chapron <34628915+sc979@users.noreply.github.com> --- .github/workflows/gorgone.yml | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index a0d6929debb..00ef3f61a85 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -31,6 +31,7 @@ jobs: package: needs: [get-version] + if: ${{ needs.get-version.outputs.stability != 'stable' }} strategy: fail-fast: false @@ -85,8 +86,7 @@ jobs: deliver-rpm: runs-on: [self-hosted, common] needs: [get-version, package] - if: ${{ contains(fromJson('["stable", "testing", "unstable"]'), needs.get-version.outputs.stability) }} - environment: ${{ needs.get-version.outputs.environment }} + if: ${{ contains(fromJson('["testing", "unstable"]'), needs.get-version.outputs.stability) }} strategy: matrix: @@ -109,8 +109,7 @@ jobs: deliver-deb: runs-on: [self-hosted, common] needs: [get-version,package] - if: ${{ contains(fromJson('["stable", "testing", "unstable"]'), needs.get-version.outputs.stability) }} - environment: ${{ needs.get-version.outputs.environment }} + if: ${{ contains(fromJson('["testing", "unstable"]'), needs.get-version.outputs.stability) }} strategy: matrix: @@ -129,3 +128,25 @@ jobs: artifactory_token: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }} cache_key: ${{ github.sha }}-${{ github.run_id }}-deb-${{ matrix.distrib }} stability: ${{ needs.get-version.outputs.stability }} + + promote: + needs: [get-version] + if: ${{ contains(fromJson('["stable"]'), needs.get-version.outputs.stability) }} + runs-on: [self-hosted, common] + strategy: + matrix: + distrib: [el8, el9, bullseye] + + steps: + - name: Checkout sources + uses: actions/checkout@v3 + + - name: Promote ${{ matrix.distrib }} to stable + uses: ./.github/actions/promote-to-stable + with: + artifactory_token: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }} + module: gorgone + distrib: ${{ matrix.distrib }} + major_version: ${{ needs.get-version.outputs.major_version }} + minor_version: ${{ needs.get-version.outputs.minor_version }} + stability: ${{ needs.get-version.outputs.stability }} \ No newline at end of file From bebfe14d8a66e3281d4257e3be92bcd31a6e266f Mon Sep 17 00:00:00 2001 From: sdepassio <114986849+sdepassio@users.noreply.github.com> Date: Wed, 10 May 2023 11:06:42 +0200 Subject: [PATCH 738/948] enh(gorgone-servicediscovery): use credentials from centreon vault for manual scan (#1108) --- .../autodiscovery/services/discovery.pm | 19 ++++++++-- .../autodiscovery/services/resources.pm | 36 +++++++++++++++++-- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm index 948f090745e..d1e00408da0 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm @@ -750,7 +750,8 @@ sub service_execute_commands { my $command = gorgone::modules::centreon::autodiscovery::services::resources::substitute_service_discovery_command( command_line => $self->{discovery}->{rules}->{$rule_id}->{command_line}, host => $host, - poller => $self->{service_pollers}->{$poller_id} + poller => $self->{service_pollers}->{$poller_id}, + vault_count => $options{vault_count} ); $self->{logger}->writeLogInfo("[autodiscovery] -servicediscovery- $self->{uuid} [" . @@ -846,6 +847,17 @@ sub launchdiscovery { } $self->{audit_user_id} = $user_id; + ################## + # get vault config + ################## + ($status, $message, my $vault_count) = gorgone::modules::centreon::autodiscovery::services::resources::get_vault_configured( + class_object_centreon => $self->{class_object_centreon} + ); + if ($status < 0) { + $self->send_log_msg_error(token => $options{token}, subname => 'servicediscovery', number => $self->{uuid}, message => $message); + return -1; + } + ################ # get rules ################ @@ -874,7 +886,8 @@ sub launchdiscovery { class_object_centreon => $self->{class_object_centreon}, with_macro => 1, host_lookup => $data->{content}->{filter_hosts}, - poller_lookup => $data->{content}->{filter_pollers} + poller_lookup => $data->{content}->{filter_pollers}, + vault_count => $vault_count ); if ($status < 0) { $self->send_log_msg_error(token => $options{token}, subname => 'servicediscovery', number => $self->{uuid}, message => $message); @@ -921,7 +934,7 @@ sub launchdiscovery { pollers_reload => {} }; - $self->service_execute_commands(); + $self->service_execute_commands(vault_count => $vault_count); return 0; } diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm b/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm index 1e76f54f1eb..24a1ed96383 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm @@ -99,6 +99,20 @@ sub get_audit_user_id { return (0, '', $user_id); } +sub get_vault_configured { + my (%options) = @_; + + my ($status, $datas) = $options{class_object_centreon}->custom_execute( + request => "SELECT count(id) FROM vault_configuration", + mode => 2 + ); + if ($status == -1 || !defined($datas->[0])) { + return (-1, 'cannot get number of vault configured'); + } + + return (0, '', $datas->[0]->[0]); +} + sub get_rules { my (%options) = @_; @@ -374,7 +388,11 @@ sub get_hosts { if (defined($done_macro_host->{ $host_id })) { $datas->{$host_id}->{macros} = $done_macro_host->{ $host_id }; } else { - ($status, my $message, my $macros) = get_macros_host(host_id => $host_id, class_object_centreon => $options{class_object_centreon}); + ($status, my $message, my $macros) = get_macros_host( + host_id => $host_id, + class_object_centreon => $options{class_object_centreon}, + vault_count => $options{vault_count} + ); if ($status == -1) { return (-1, $message); } @@ -429,14 +447,22 @@ sub get_macros_host { } ($status, $datas) = $options{class_object_centreon}->custom_execute( - request => "SELECT host_macro_name, host_macro_value FROM on_demand_macro_host WHERE host_host_id = " . $lhost_id, + request => "SELECT host_macro_name, host_macro_value, is_password FROM on_demand_macro_host WHERE host_host_id = " . $lhost_id, mode => 2 ); if ($status == -1) { return (-1, 'get macro: cannot get on_demand_macro_host'); } foreach (@$datas) { - set_macro(\%macros, $_->[0], $_->[1]); + my $macro_name = $_->[0]; + my $macro_value = $_->[1]; + my $is_password = $_->[2]; + # Replace macro value if a vault is used + if ($options{vault_count} > 0 && defined($is_password) && $is_password == 1) { + set_macro(\%macros, $macro_name, "{" . $macro_name . "::secret::" . $macro_value . "}"); + } else { + set_macro(\%macros, $macro_name, $macro_value); + } } ($status, $datas) = $options{class_object_centreon}->custom_execute( @@ -471,6 +497,10 @@ sub substitute_service_discovery_command { $command =~ s/\$HOSTADDRESS\$/$options{host}->{host_address}/g; $command =~ s/\$HOSTNAME\$/$options{host}->{host_name}/g; + + if ($options{vault_count} > 0) { + $command .= ' --pass-manager="centreonvault"'; + } return $command; } From 462e23926ef1bb74a4589f56ff644bf06b5bf5dc Mon Sep 17 00:00:00 2001 From: jeremyjaouen <61694165+jeremyjaouen@users.noreply.github.com> Date: Tue, 16 May 2023 13:20:34 +0200 Subject: [PATCH 739/948] fix(gorgone): Fix query to update mod_anomaly_service (#1470) --- gorgone/gorgone/modules/centreon/anomalydetection/class.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm index c29fc615f41..c1ea2649b16 100644 --- a/gorgone/gorgone/modules/centreon/anomalydetection/class.pm +++ b/gorgone/gorgone/modules/centreon/anomalydetection/class.pm @@ -260,7 +260,7 @@ sub save_centreon_previous_register { ' saas_model_id = ?,' . ' saas_metric_id = ?,' . ' saas_creation_date = ?, ' . - ' saas_update_date = ?' + ' saas_update_date = ?' . ' WHERE `id` = ?'; $query_append = ';'; push @bind_values, $self->{unregister_metrics_centreon}->{$_}->{saas_model_id}, $self->{unregister_metrics_centreon}->{$_}->{saas_metric_id}, @@ -605,7 +605,7 @@ sub action_saasregister { } if ($self->save_centreon_previous_register()) { - $self->send_log(code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot save previsous register' }); + $self->send_log(code => GORGONE_ACTION_FINISH_KO, token => $options{token}, data => { message => 'cannot save previous register' }); return 1; } From 39c7bf4ffbfd3c30b4a5544053e3bbf4ad8ee995 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Tue, 23 May 2023 09:47:34 +0200 Subject: [PATCH 740/948] fix(gorgone-mbi): etl dimension process time increase (#1325) REFS: MON-18443 --- .../centreon/mbi/libs/centreon/Host.pm | 266 +++++++----------- 1 file changed, 101 insertions(+), 165 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Host.pm b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Host.pm index 89a03a14384..e175076410d 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Host.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Host.pm @@ -32,13 +32,12 @@ use Data::Dumper; sub new { my $class = shift; my $self = {}; - $self->{"logger"} = shift; - $self->{"centreon"} = shift; - $self->{'etlProperties'} = undef; - #Hash that will contains all relation between host and hostcategories after calling the function getHostCategoriesWithTemplate - $self->{"hostCategoriesWithTemplates"} = undef; + $self->{logger} = shift; + $self->{centreon} = shift; + $self->{etlProperties} = undef; + if (@_) { - $self->{"centstorage"} = shift; + $self->{centstorage} = shift; } bless $self, $class; return $self; @@ -47,13 +46,13 @@ sub new { #Set the etl properties as a variable of the class sub setEtlProperties{ my $self = shift; - $self->{'etlProperties'} = shift; + $self->{etlProperties} = shift; } # returns two references to two hash tables => hosts indexed by id and hosts indexed by name sub getAllHosts { my $self = shift; - my $centreon = $self->{"centreon"}; + my $centreon = $self->{centreon}; my $activated = 1; if (@_) { $activated = 0; @@ -61,18 +60,17 @@ sub getAllHosts { my (%host_ids, %host_names); my $query = "SELECT `host_id`, `host_name`". - " FROM `host`". - " WHERE `host_register`='1'"; - if ($activated == 1) { - $query .= " AND `host_activate` ='1'"; - } + " FROM `host`". + " WHERE `host_register`='1'"; + if ($activated == 1) { + $query .= " AND `host_activate` ='1'"; + } my $sth = $centreon->query({ query => $query }); while (my $row = $sth->fetchrow_hashref()) { - $host_ids{$row->{"host_name"}} = $row->{"host_id"}; - $host_names{$row->{"host_id"}} = $row->{"host_name"}; + $host_ids{ $row->{host_name} } = $row->{host_id}; + $host_names{ $row->{host_id} } = $row->{host_name}; } - $sth->finish(); - return (\%host_ids,\%host_names); + return (\%host_ids, \%host_names); } # Get all hosts, keys are IDs @@ -89,6 +87,37 @@ sub getAllHostsByName { return ($host_names); } +sub loadAllCategories { + my $self = shift; + + $self->{hc} = {}; + $self->{host_hc_relations} = {}; + my $query = "SELECT hc.hc_id as category_id, hc.hc_name as category_name, host_host_id + FROM hostcategories hc, hostcategories_relation hr + WHERE hc.hc_activate = '1' AND hc.hc_id = hr.hostcategories_hc_id"; + my $sth = $self->{centreon}->query({ query => $query }); + while (my $row = $sth->fetchrow_hashref()) { + $self->{hc}->{ $row->{category_id} } = $row->{category_name} if (!defined($self->{hc}->{ $row->{category_id} })); + $self->{host_hc_relations}->{ $row->{host_host_id} } = [] if (!defined($self->{host_hc_relations}->{ $row->{host_host_id} })); + push @{$self->{host_hc_relations}->{ $row->{host_host_id} }}, $row->{category_id}; + } +} + +sub loadAllHosts { + my $self = shift; + + $self->{hosts} = {}; + $self->{host_htpl_relations} = {}; + my $query = "SELECT h.host_id, h.host_name, host_tpl_id + FROM host h, host_template_relation htr + WHERE h.host_activate = '1' AND h.host_id = htr.host_host_id"; + my $sth = $self->{centreon}->query({ query => $query }); + while (my $row = $sth->fetchrow_hashref()) { + $self->{hosts}->{ $row->{host_id} } = $row->{host_name} if (!defined($self->{hosts}->{ $row->{host_id} })); + $self->{host_htpl_relations}->{ $row->{host_id} } = [] if (!defined($self->{host_htpl_relations}->{ $row->{host_id} })); + push @{$self->{host_htpl_relations}->{ $row->{host_id} }}, $row->{host_tpl_id}; + } +} # returns host groups linked to hosts # all hosts will be stored in a hash table @@ -141,165 +170,72 @@ sub getHostGroups { return (\%result); } -#Get the link between host and categories using templates -sub getRecursiveCategoriesForOneHost{ - my $self = shift; - my $host_id = shift; - my $ref_hostCat = shift; - my $centreon = $self->{"centreon"}; - my $etlProperties = $self->{"etlProperties"}; - - - #Get all categories linked to the templates associated with the host or just template associated with host to be able to call the method recursively - - my $query = "SELECT host_id, host_name, template_id,template_name, categories.hc_id as category_id, categories.hc_activate as hc_activate,". - " categories.hc_name as category_name ". - " FROM ( SELECT t1.host_id,t1.host_name,templates.host_id as template_id,templates.host_name as template_name ". - " FROM host t1, host_template_relation t2, host templates ". - " WHERE t1.host_id = t2.host_host_id AND t2.host_tpl_id = templates.host_id AND t1.host_activate ='1' AND t1.host_id = ".$host_id." ) r1 ". - " LEFT JOIN hostcategories_relation t3 ON t3.host_host_id = r1.template_id LEFT JOIN hostcategories categories ON t3.hostcategories_hc_id = categories.hc_id "; - - my @hostCategoriesAllowed = split /,/, $etlProperties->{'dimension.hostcategories'}; - - my $sth = $centreon->query({ query => $query }); - while (my $row = $sth->fetchrow_hashref()) { - my @tab = (); - my $new_entry; - my $categoryId = $row->{"category_id"}; - my $categoryName = $row->{"category_name"}; - my $categoryActivate = $row->{"hc_activate"}; - - #If current category is in allowed categories in ETL configuration - #add it to the categories link to the host, - #Then check for templates categories recursively - if(defined($categoryId) && defined($categoryName) && $categoryActivate=='1'){ - if ((grep {$_ eq $categoryId} @hostCategoriesAllowed) || (defined($etlProperties->{'dimension.all.hostcategories'}) && $etlProperties->{'dimension.all.hostcategories'} ne '')){ - $new_entry = $categoryId.";".$categoryName; - #If no hostcat has been found for the host, create the line - if (!scalar(@$ref_hostCat)){ - @$ref_hostCat = ($new_entry); - }else { #If the tab is not empty, check wether the combination already exists in the tab - @tab = @$ref_hostCat; - my $exists = 0; - foreach(@$ref_hostCat) { - if ($_ eq $new_entry) { - $exists = 1; - last; - } - } - #If the host category did not exist, add it to the table @$ref_hostCat - if (!$exists) { - push @$ref_hostCat, $new_entry; - } - } - } - } - $self->getRecursiveCategoriesForOneHost($row->{"template_id"},$ref_hostCat); - } - $sth->finish(); -} - -#Get the link between host and categories using direct link hc <> host -sub getDirectLinkedCategories{ - my $self = shift; - my $host_id = shift; - my $ref_hostCat = shift; - my $centreon = $self->{"centreon"}; - my $etlProperties = $self->{"etlProperties"}; - my @tab = (); - - my $query = "SELECT `host_id`, `host_name`, `hc_id`, `hc_name`". - " FROM `host`, `hostcategories_relation`, `hostcategories`". - " WHERE `host_register`='1'". - " AND `hostcategories_hc_id` = `hc_id`". - " AND `host_id`= `host_host_id`". - " AND `host_id`= ".$host_id." ". - " AND `host_activate` ='1' AND hostcategories.hc_activate = '1' "; - - if(!defined($etlProperties->{'dimension.all.hostcategories'}) && $etlProperties->{'dimension.hostcategories'} ne ''){ - $query .= " AND `hc_id` IN (".$etlProperties->{'dimension.hostcategories'}.")"; - } - - my $sth = $centreon->query({ query => $query }); - while (my $row = $sth->fetchrow_hashref()) { - my $new_entry = $row->{"hc_id"}.";".$row->{"hc_name"}; - if (!scalar(@$ref_hostCat)){ - @$ref_hostCat = ($new_entry); - }else { - @tab = @$ref_hostCat; - my $exists = 0; - foreach(@$ref_hostCat) { - if ($_ eq $new_entry) { - $exists = 1; - last; - } - } - if (!$exists) { - push @$ref_hostCat, $new_entry; - } - } - } - $sth->finish(); -} - #Fill a class Hash table that contains the relation between host_id and table[hc_id,hc_name] -sub getHostCategoriesWithTemplate{ +sub getHostCategoriesWithTemplate { my $self = shift; - my $centreon = $self->{"centreon"}; - my $activated = 1; - - #Hash : each key of the hash table is a host id - #each key is linked to a table containing entries like : "hc_id,hc_name" - my $hostCategoriesWithTemplate = $self->{'hostCategoriesWithTemplates'}; - if (@_) { - $activated = 0; - } - my $query = "SELECT `host_id`". - " FROM `host`". - " WHERE `host_activate` ='1'"; + my @hostCategoriesAllowed = split(/,/, $self->{etlProperties}->{'dimension.hostcategories'}); + + my %loop = (); + my $hcResult = {}; + foreach my $host_id (keys %{$self->{hosts}}) { + my $stack = [$host_id]; + my $hcAdd = {}; + my $hc = []; + foreach (my $id = shift(@$stack)) { + next if (defined($loop{$id})); + $loop{$id} = 1; + + if (defined($self->{host_hc_relations}->{$id})) { + foreach my $category_id (@{$self->{host_hc_relations}->{$id}}) { + next if (defined($hcAdd->{$category_id})); + if ((grep {$_ eq $category_id} @hostCategoriesAllowed) || + (defined($self->{etlProperties}->{'dimension.all.hostcategories'}) && $self->{etlProperties}->{'dimension.all.hostcategories'} ne '')) { + $hcAdd->{$category_id} = 1; + push @$hc, $category_id . ';' . $self->{hc}->{$category_id}; + } + } + } + + unshift(@$stack, @{$self->{host_htpl_relations}->{id}}) if (defined($self->{host_htpl_relations}->{id})); + } + + $hcResult->{$host_id} = $hc; + } - my $sth = $centreon->query({ query => $query }); - while (my $row = $sth->fetchrow_hashref()) { - my @tab = (); - my $host_id = $row->{"host_id"}; - $self->getRecursiveCategoriesForOneHost($host_id,\@tab); - $self->getDirectLinkedCategories($host_id,\@tab); - $hostCategoriesWithTemplate->{$row->{"host_id"}} = [@tab]; - undef @tab; - } - $self->{'hostCategoriesWithTemplates'} = $hostCategoriesWithTemplate; - $sth->finish(); + return $hcResult; } sub getHostGroupAndCategories { - my $self = shift; + my $self = shift; - my $hostGroups = $self->getHostGroups(); - $self->getHostCategoriesWithTemplate(); - my $hostCategories = $self->{"hostCategoriesWithTemplates"}; - my $hosts = $self->getAllHostsByName; + my $hostGroups = $self->getHostGroups(); + + $self->loadAllCategories(); + $self->loadAllHosts(); + my $hostCategories = $self->getHostCategoriesWithTemplate(); my @results; - + while (my ($hostId, $groups) = each (%$hostGroups)) { - my $categories_ref = $hostCategories->{$hostId}; - my @categoriesTab = (); - if (defined($categories_ref) && scalar(@$categories_ref)) { - @categoriesTab = @$categories_ref; - } - my $hostName = $hosts->{$hostId}; - foreach(@$groups) { - my $group = $_; - if (scalar(@categoriesTab)) { - foreach(@categoriesTab) { - push @results, $hostId.";".$hostName.";".$group.";".$_; - } - }else { - #If there is no category - push @results, $hostId.";".$hostName.";".$group.";0;NoCategory"; - } - } + my $categories_ref = $hostCategories->{$hostId}; + my @categoriesTab = (); + if (defined($categories_ref) && scalar(@$categories_ref)) { + @categoriesTab = @$categories_ref; + } + my $hostName = $self->{hosts}->{$hostId}; + foreach (@$groups) { + my $group = $_; + if (scalar(@categoriesTab)) { + foreach(@categoriesTab) { + push @results, $hostId . ';' .$hostName . ';' . $group . ';' . $_; + } + } else { + #If there is no category + push @results, $hostId . ";" . $hostName . ";" . $group . ";0;NoCategory"; + } + } } + return \@results; } From fe82bd55ddb074f2b4cf31aec04993ab17b81010 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Chapron?= <34628915+sc979@users.noreply.github.com> Date: Tue, 23 May 2023 20:25:33 +0200 Subject: [PATCH 741/948] chore(ci): add pipeline scans (#1477) --- .github/workflows/gorgone.yml | 13 +++++++++++++ gorgone/.veracode | 1 + 2 files changed, 14 insertions(+) create mode 100644 gorgone/.veracode diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 00ef3f61a85..ed3243d821e 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -29,6 +29,19 @@ jobs: with: version_file: centreon-gorgone/packaging/centreon-gorgone.spectemplate + veracode-analysis: + needs: [get-version] + uses: ./.github/workflows/veracode-analysis.yml + with: + module_directory: centreon-gorgone + module_name: centreon-gorgone + major_version: ${{ needs.get-version.outputs.major_version }} + minor_version: ${{ needs.get-version.outputs.minor_version }} + stability: ${{ needs.get-version.outputs.stability }} + secrets: + veracode_api_id: ${{ secrets.VERACODE_API_ID }} + veracode_api_key: ${{ secrets.VERACODE_API_KEY }} + package: needs: [get-version] if: ${{ needs.get-version.outputs.stability != 'stable' }} diff --git a/gorgone/.veracode b/gorgone/.veracode new file mode 100644 index 00000000000..5c457d797a6 --- /dev/null +++ b/gorgone/.veracode @@ -0,0 +1 @@ +docs \ No newline at end of file From 8295f22051401b01cb2e2fe6abd1770d858bcb44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Chapron?= <34628915+sc979@users.noreply.github.com> Date: Tue, 6 Jun 2023 14:31:15 +0200 Subject: [PATCH 742/948] enh(scan): widget removal and minor updates (#1552) --- gorgone/.veracode | 1 - 1 file changed, 1 deletion(-) diff --git a/gorgone/.veracode b/gorgone/.veracode index 5c457d797a6..e69de29bb2d 100644 --- a/gorgone/.veracode +++ b/gorgone/.veracode @@ -1 +0,0 @@ -docs \ No newline at end of file From 15aa9d4287c3515fc8d74528e53ab9ce245a8258 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Tue, 6 Jun 2023 15:52:54 +0200 Subject: [PATCH 743/948] fix(gorgone): proxy module is stuck after internal key rotate (MON-19742) (#1584) --- gorgone/gorgone/class/core.pm | 3 +-- gorgone/gorgone/modules/core/proxy/hooks.pm | 4 ++-- gorgone/gorgone/modules/plugins/newtest/hooks.pm | 2 +- gorgone/gorgone/modules/plugins/scom/hooks.pm | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 0aba0051240..45d58480d08 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -620,7 +620,6 @@ sub broadcast_run { action => $options{action}, logger => $self->{logger}, frame => $options{frame}, - data => $options{data}, token => $options{token} ); } @@ -805,7 +804,7 @@ sub check_external_rotate_keys { } next if ($self->{identity_infos}->{$id}->{ctime} > ($time - $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_rotation})); - $self->{logger}->writeLogDebug('[core] rotate external key for ' . $id); + $self->{logger}->writeLogDebug('[core] rotate external key for ' . pack('H*', $id)); ($rv, $key) = gorgone::standard::library::generate_symkey( keysize => $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{external_com_keysize} diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index 832ced26df0..6bc26e4ed74 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -496,7 +496,7 @@ sub broadcast { $options{gorgone}->send_internal_message( identity => 'gorgone-proxy-' . $pool_id, action => $options{action}, - data => $options{data}, + raw_data_ref => $options{frame}->getRawData(), token => $options{token} ); } @@ -505,7 +505,7 @@ sub broadcast { $options{gorgone}->send_internal_message( identity => 'gorgone-proxy-httpserver', action => $options{action}, - data => $options{data}, + raw_data_ref => $options{frame}->getRawData(), token => $options{token} ); } diff --git a/gorgone/gorgone/modules/plugins/newtest/hooks.pm b/gorgone/gorgone/modules/plugins/newtest/hooks.pm index 79ce99b4583..01e4b06e3fe 100644 --- a/gorgone/gorgone/modules/plugins/newtest/hooks.pm +++ b/gorgone/gorgone/modules/plugins/newtest/hooks.pm @@ -180,7 +180,7 @@ sub broadcast { $options{gorgone}->send_internal_message( identity => 'gorgone-newtest-' . $container_id, action => $options{action}, - data => $options{data}, + raw_data_ref => $options{frame}->getRawData(), token => $options{token} ); } diff --git a/gorgone/gorgone/modules/plugins/scom/hooks.pm b/gorgone/gorgone/modules/plugins/scom/hooks.pm index 4878d011b13..86bf33fe972 100644 --- a/gorgone/gorgone/modules/plugins/scom/hooks.pm +++ b/gorgone/gorgone/modules/plugins/scom/hooks.pm @@ -179,7 +179,7 @@ sub broadcast { $options{gorgone}->send_internal_message( identity => 'gorgone-scom-' . $container_id, action => $options{action}, - data => $options{data}, + raw_data_ref => $options{frame}->getRawData(), token => $options{token} ); } From 29453eeff35e20a47178fb42efc2b0c8fd4de31b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Chapron?= <34628915+sc979@users.noreply.github.com> Date: Wed, 7 Jun 2023 15:55:12 +0200 Subject: [PATCH 744/948] chore(ci): add sandbox scans (#1507) --- gorgone/{.veracode => .veracode-exclusions} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename gorgone/{.veracode => .veracode-exclusions} (100%) diff --git a/gorgone/.veracode b/gorgone/.veracode-exclusions similarity index 100% rename from gorgone/.veracode rename to gorgone/.veracode-exclusions From 0be839ca05daaf043eee54dfa84a57f57c545b4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Chapron?= <34628915+sc979@users.noreply.github.com> Date: Mon, 12 Jun 2023 16:09:55 +0200 Subject: [PATCH 745/948] chore(ci): split scan api accounts and activate web scan (#1629) --- .github/workflows/gorgone.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index ed3243d821e..360736ff4db 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -39,8 +39,8 @@ jobs: minor_version: ${{ needs.get-version.outputs.minor_version }} stability: ${{ needs.get-version.outputs.stability }} secrets: - veracode_api_id: ${{ secrets.VERACODE_API_ID }} - veracode_api_key: ${{ secrets.VERACODE_API_KEY }} + veracode_api_id: ${{ secrets.VERACODE_API_ID_GORG }} + veracode_api_key: ${{ secrets.VERACODE_API_KEY_GORG }} package: needs: [get-version] From b320fb01f062bb358ecf5ae079fba82bdb77f64b Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Tue, 13 Jun 2023 17:00:09 +0200 Subject: [PATCH 746/948] enh(ci): replace init scripts by fake systemd (#1602) --- gorgone/gorgone/class/core.pm | 4 ++++ gorgone/gorgone/class/module.pm | 10 ++++++++-- gorgone/gorgone/standard/library.pm | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 45d58480d08..fbc6f264355 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -1217,6 +1217,10 @@ sub check_exit_modules { sub periodic_exec { $gorgone->check_exit_modules(); $gorgone->{listener}->check(); + $gorgone->router_internal_event(); + if (defined($gorgone->{external_socket})) { + $gorgone->router_external_event(); + } } sub run { diff --git a/gorgone/gorgone/class/module.pm b/gorgone/gorgone/class/module.pm index 042682cbc79..848be128f3e 100644 --- a/gorgone/gorgone/class/module.pm +++ b/gorgone/gorgone/class/module.pm @@ -1,4 +1,4 @@ -# +# # Copyright 2019 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets @@ -220,6 +220,7 @@ sub send_internal_action { json_encode => defined($options->{data_noencode}) ? undef : 1 ); } + $self->{logger}->writeLogDebug("[$self->{module_id}]$self->{container} internal message: $options->{message}"); my $socket = defined($options->{socket}) ? $options->{socket} : $self->{internal_socket}; if ($self->{internal_crypt}->{enabled} == 1) { @@ -227,7 +228,7 @@ sub send_internal_action { my $key = $self->{internal_crypt}->{core_keys}->[0]; if ($self->{fork} == 0) { - if (!defined($self->{internal_crypt}->{identity_keys}->{$identity}) || + if (!defined($self->{internal_crypt}->{identity_keys}->{$identity}) || (time() - $self->{internal_crypt}->{identity_keys}->{$identity}->{ctime}) > ($self->{internal_crypt}->{rotation})) { my ($rv, $genkey) = gorgone::standard::library::generate_symkey( keysize => $self->get_core_config(name => 'internal_com_keysize') @@ -256,6 +257,11 @@ sub send_internal_action { } $socket->send($options->{message}, ZMQ_DONTWAIT); + if ($socket->has_error) { + $self->{logger}->writeLogError( + "[$self->{module_id}]$self->{container} Cannot send message: " . $socket->last_strerror + ); + } $self->event(socket => $socket); } diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index e587a70b427..b0ea3c8b9d8 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -734,7 +734,7 @@ sub create_com { if ($socket->has_error) { $options{logger}->writeLogDebug("[core] Cannot bind IPC '$options{path}': $!"); # try create dir - $options{logger}->writeLogDebug("[core] Maybe directory not exist. We try to create it!!!"); + $options{logger}->writeLogDebug("[core] Maybe directory not exist. We try to create it"); if (!mkdir(dirname($options{path}))) { $options{logger}->writeLogError("[core] Cannot create IPC file directory '$options{path}'"); exit(1); From 6fce9bb5a2157c523030329cfe3da3d30bcb2e02 Mon Sep 17 00:00:00 2001 From: tuntoja <58987095+tuntoja@users.noreply.github.com> Date: Fri, 23 Jun 2023 10:38:04 +0200 Subject: [PATCH 747/948] fix(gorgone): external communication crash every 24h (#1727) Co-authored-by: garnier-quentin <garnier.quentin@gmail.com> --- gorgone/gorgone/class/core.pm | 1 + gorgone/gorgone/modules/core/proxy/class.pm | 26 +++++++-------------- gorgone/gorgone/modules/core/proxy/hooks.pm | 12 ---------- 3 files changed, 9 insertions(+), 30 deletions(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index fbc6f264355..3eeddfd209b 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -962,6 +962,7 @@ sub handshake { my $message = $options{frame}->getFrame(); if ($rv == 0 && $$message =~ /^(?:[\[a-zA-Z-_]+?\]\s+\[.*?\]|[\[a-zA-Z-_]+?\]\s*$)/) { + $self->{identity_infos}->{ $options{identity} }->{mtime} = time(); gorgone::standard::library::update_identity_mtime(dbh => $self->{db_gorgone}, identity => $options{identity}); return (0, $cipher_infos); } diff --git a/gorgone/gorgone/modules/core/proxy/class.pm b/gorgone/gorgone/modules/core/proxy/class.pm index 6ca1ee63a12..de525cd98e5 100644 --- a/gorgone/gorgone/modules/core/proxy/class.pm +++ b/gorgone/gorgone/modules/core/proxy/class.pm @@ -286,21 +286,6 @@ sub action_proxycloseconnection { $self->{clients}->{ $data->{id} }->{class} = undef; } -sub action_proxystopreadchannel { - my ($self, %options) = @_; - - my ($code, $data) = $self->json_decode(argument => $options{data}); - return if ($code == 1); - - return if (!defined($self->{clients}->{ $data->{id} })); - - $self->{logger}->writeLogInfo("[proxy] Stop read channel for $data->{id}"); - - $self->{clients}->{ $data->{id} }->{com_read_internal} = 0; - - delete $self->{watchers}->{ $data->{id} }; -} - sub close_connections { my ($self, %options) = @_; @@ -408,9 +393,6 @@ sub proxy { } elsif ($action eq 'PROXYCLOSECONNECTION') { $connector->action_proxycloseconnection(data => $data); return ; - } elsif ($action eq 'PROXYSTOPREADCHANNEL') { - $connector->action_proxystopreadchannel(data => $data); - return ; } if ($target_complete !~ /^(.+)~~(.+)$/) { @@ -429,6 +411,7 @@ sub proxy { $target_direct = 0; } if (!defined($connector->{clients}->{$target_client}->{class})) { + $connector->{logger}->writeLogInfo("[proxy] connect for $target_client"); if ($connector->connect(id => $target_client) != 0) { $connector->send_log( code => GORGONE_ACTION_FINISH_KO, @@ -476,6 +459,8 @@ sub event { my $socket; if (defined($options{channel})) { + #$self->{logger}->writeLogDebug("[proxy] event channel $options{channel} delete: $self->{clients}->{ $options{channel} }->{delete} com_read_internal: $self->{clients}->{ $options{channel} }->{com_read_internal}") + # if (defined($self->{clients}->{ $options{channel} })); return if ( defined($self->{clients}->{ $options{channel} }) && ($self->{clients}->{ $options{channel} }->{com_read_internal} == 0 || $self->{clients}->{ $options{channel} }->{delete} == 1) @@ -516,10 +501,15 @@ sub periodic_exec { $connector->{clients}->{$_}->{class} = undef; $connector->{clients}->{$_}->{delete} = 0; $connector->{clients}->{$_}->{com_read_internal} = 0; + $connector->{logger}->writeLogInfo("[proxy] periodic close connection for $_"); next; } } + foreach (keys %{$connector->{clients}}) { + $connector->event(channel => $_); + } + if ($connector->{stop} == 1) { $connector->exit_process(); } diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index 6bc26e4ed74..15bd57869b6 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -567,17 +567,6 @@ sub pathway { } $first_target = $_ if (!defined($first_target)); - if ($synctime_nodes->{$_}->{channel_read_stop} == 0) { - $synctime_nodes->{$_}->{channel_read_stop} = 1; - routing( - target => $_, - action => 'PROXYSTOPREADCHANNEL', - frame => gorgone::class::frame->new(data => { id => $_ }), - gorgone => $options{gorgone}, - dbh => $options{dbh}, - logger => $options{logger} - ); - } } if (!defined($first_target)) { @@ -1033,7 +1022,6 @@ sub register_nodes { in_progress => 0, in_progress_time => -1, synctime_error => 0, - channel_read_stop => 0, channel_ready => 0 }; get_sync_time(node_id => $node->{id}, dbh => $options{dbh}); From 408f0668191c0fca2c99dac9665b947801a3b8f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Chapron?= <34628915+sc979@users.noreply.github.com> Date: Mon, 26 Jun 2023 13:48:58 +0200 Subject: [PATCH 748/948] feat(chore): add SCA scans (#1660) --- .github/workflows/gorgone.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 360736ff4db..057d6a93ad3 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -41,6 +41,7 @@ jobs: secrets: veracode_api_id: ${{ secrets.VERACODE_API_ID_GORG }} veracode_api_key: ${{ secrets.VERACODE_API_KEY_GORG }} + veracode_srcclr_token: ${{ secrets.VERACODE_SRCCLR_TOKEN }} package: needs: [get-version] From edb0570121b0980cba8a6ce9f2fdd0d220d06da1 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Thu, 20 Jul 2023 11:50:31 +0200 Subject: [PATCH 749/948] fix(packaging): set gorgone deb package to all arch (#1906) Refs: MON-20680 --- gorgone/packaging/debian/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/packaging/debian/control b/gorgone/packaging/debian/control index 2fa16f17111..5aa5025d5ef 100644 --- a/gorgone/packaging/debian/control +++ b/gorgone/packaging/debian/control @@ -9,7 +9,7 @@ Standards-Version: 4.5.0 Homepage: https://wwww.centreon.com Package: centreon-gorgone -Architecture: any +Architecture: all Depends: centreon-common (>= ${centreon:version}~), centreon-common (<< ${centreon:versionThreshold}~), From 49cf4e7baac3eae5927a4f1b0ecac87ecd97ebc3 Mon Sep 17 00:00:00 2001 From: Adrien Morais-Mestre <31647811+adr-mo@users.noreply.github.com> Date: Mon, 24 Jul 2023 14:59:58 +0200 Subject: [PATCH 750/948] enh(packaging): add perl dependency and ship perl script (#1918) --- gorgone/packaging/centreon-gorgone.spectemplate | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 943b331e249..01b73aa79f1 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -14,6 +14,7 @@ BuildRequires: perl-devel Requires: bzip2 Requires: perl-Libssh-Session >= 0.8 Requires: perl-CryptX +Requires: perl-Mojolicious Requires: perl(Archive::Tar) Requires: perl(Schedule::Cron) Requires: perl(ZMQ::FFI) @@ -81,6 +82,7 @@ mkdir -p %{buildroot}/%{perl_vendorlib}/gorgone %{__cp} centreon-gorgone/contrib/gorgone_config_init.pl %{buildroot}%{_usr}/local/bin/ %{__cp} centreon-gorgone/contrib/gorgone_audit.pl %{buildroot}%{_usr}/local/bin/ %{__cp} centreon-gorgone/contrib/gorgone_install_plugins.pl %{buildroot}%{_usr}/local/bin/ +%{__cp} centreon-gorgone/contrib/gorgone_key_thumbprint.pl %{buildroot}%{_usr}/local/bin/ %{__install} -d %{buildroot}%{_sysconfdir}/centreon-gorgone %{__install} -d %{buildroot}%{_sysconfdir}/centreon-gorgone/config.d/ @@ -108,6 +110,7 @@ rm -rf %{buildroot} %attr(755, root, root) %{_usr}/local/bin/gorgone_config_init.pl %attr(755, root, root) %{_usr}/local/bin/gorgone_audit.pl %attr(750, root, root) %{_usr}/local/bin/gorgone_install_plugins.pl +%attr(750, root, root) %{_usr}/local/bin/gorgone_key_thumbprint.pl %attr(755, root, root) %{_sysconfdir}/systemd/system/gorgoned.service %config(noreplace) %{_sysconfdir}/sysconfig/gorgoned %config(noreplace) %{_sysconfdir}/logrotate.d/gorgoned From 408e2c9d1f77c6919c8051a2b314f8924aab5328 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Mon, 24 Jul 2023 15:01:57 +0200 Subject: [PATCH 751/948] fix(gorgone): mbi module is stuck after internal key rotate (#1882) --- .../modules/centreon/mbi/etlworkers/hooks.pm | 2 +- .../gorgone/modules/plugins/newtest/hooks.pm | 19 ++++++++----------- gorgone/gorgone/modules/plugins/scom/hooks.pm | 19 ++++++++----------- 3 files changed, 17 insertions(+), 23 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/mbi/etlworkers/hooks.pm b/gorgone/gorgone/modules/centreon/mbi/etlworkers/hooks.pm index 21a861e5aa2..b286e7192a9 100644 --- a/gorgone/gorgone/modules/centreon/mbi/etlworkers/hooks.pm +++ b/gorgone/gorgone/modules/centreon/mbi/etlworkers/hooks.pm @@ -186,7 +186,7 @@ sub broadcast { $options{gorgone}->send_internal_message( identity => 'gorgone-' . NAME . '-' . $pool_id, action => $options{action}, - data => $options{data}, + raw_data_ref => $options{frame}->getRawData(), token => $options{token} ); } diff --git a/gorgone/gorgone/modules/plugins/newtest/hooks.pm b/gorgone/gorgone/modules/plugins/newtest/hooks.pm index 01e4b06e3fe..3265f864488 100644 --- a/gorgone/gorgone/modules/plugins/newtest/hooks.pm +++ b/gorgone/gorgone/modules/plugins/newtest/hooks.pm @@ -65,12 +65,9 @@ sub init { sub routing { my (%options) = @_; - - my $data; - eval { - $data = JSON::XS->new->decode($options{data}); - }; - if ($@) { + + my $data = $options{frame}->decodeData(); + if (!defined($data)) { $options{logger}->writeLogError("[newtest] Cannot decode json data: $@"); gorgone::standard::library::add_history({ dbh => $options{dbh}, @@ -83,11 +80,11 @@ sub routing { } if ($options{action} eq 'NEWTESTREADY') { - $containers->{$data->{container_id}}->{ready} = 1; + $containers->{ $data->{container_id} }->{ready} = 1; return undef; } - if (!defined($data->{container_id}) || !defined($last_containers->{$data->{container_id}})) { + if (!defined($data->{container_id}) || !defined($last_containers->{ $data->{container_id} })) { gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, @@ -98,7 +95,7 @@ sub routing { return undef; } - if (gorgone::class::core::waiting_ready(ready => \$containers->{$data->{container_id}}->{ready}) == 0) { + if (gorgone::class::core::waiting_ready(ready => \$containers->{ $data->{container_id} }->{ready}) == 0) { gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, @@ -112,7 +109,7 @@ sub routing { $options{gorgone}->send_internal_message( identity => 'gorgone-newtest-' . $data->{container_id}, action => $options{action}, - data => $options{data}, + raw_data_ref => $options{frame}->getRawData(), token => $options{token} ); } @@ -180,7 +177,7 @@ sub broadcast { $options{gorgone}->send_internal_message( identity => 'gorgone-newtest-' . $container_id, action => $options{action}, - raw_data_ref => $options{frame}->getRawData(), + frame => $options{frame}, token => $options{token} ); } diff --git a/gorgone/gorgone/modules/plugins/scom/hooks.pm b/gorgone/gorgone/modules/plugins/scom/hooks.pm index 86bf33fe972..3c2d7414fc0 100644 --- a/gorgone/gorgone/modules/plugins/scom/hooks.pm +++ b/gorgone/gorgone/modules/plugins/scom/hooks.pm @@ -64,12 +64,9 @@ sub init { sub routing { my (%options) = @_; - - my $data; - eval { - $data = JSON::XS->new->decode($options{data}); - }; - if ($@) { + + my $data = $options{frame}->decodeData(); + if (!defined($data)) { $options{logger}->writeLogError("[scom] Cannot decode json data: $@"); gorgone::standard::library::add_history({ dbh => $options{dbh}, @@ -82,11 +79,11 @@ sub routing { } if ($options{action} eq 'SCOMREADY') { - $containers->{$data->{container_id}}->{ready} = 1; + $containers->{ $data->{container_id} }->{ready} = 1; return undef; } - if (!defined($data->{container_id}) || !defined($last_containers->{$data->{container_id}})) { + if (!defined($data->{container_id}) || !defined($last_containers->{ $data->{container_id} })) { gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, @@ -97,7 +94,7 @@ sub routing { return undef; } - if (gorgone::class::core::waiting_ready(ready => \$containers->{$data->{container_id}}->{ready}) == 0) { + if (gorgone::class::core::waiting_ready(ready => \$containers->{ $data->{container_id} }->{ready}) == 0) { gorgone::standard::library::add_history({ dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, @@ -111,7 +108,7 @@ sub routing { $options{gorgone}->send_internal_message( identity => 'gorgone-scom-' . $data->{container_id}, action => $options{action}, - data => $options{data}, + raw_data_ref => $options{frame}->getRawData(), token => $options{token} ); } @@ -179,7 +176,7 @@ sub broadcast { $options{gorgone}->send_internal_message( identity => 'gorgone-scom-' . $container_id, action => $options{action}, - raw_data_ref => $options{frame}->getRawData(), + frame => $options{frame}, token => $options{token} ); } From 4d9251a38b5a6c0464a18b8e63fbe4cb176e5f56 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Mon, 24 Jul 2023 18:16:12 +0200 Subject: [PATCH 752/948] fix(gorgone-mbi): cleaning configuration (#1680) Co-authored-by: Laurent Pinsivy <lpinsivy@centreon.com> --- .../modules/centreon/mbi/etl/import/main.pm | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/mbi/etl/import/main.pm b/gorgone/gorgone/modules/centreon/mbi/etl/import/main.pm index 39551763b6d..54bf7b0c6f5 100644 --- a/gorgone/gorgone/modules/centreon/mbi/etl/import/main.pm +++ b/gorgone/gorgone/modules/centreon/mbi/etl/import/main.pm @@ -78,7 +78,13 @@ sub createTables { type => 1, db => 'centstorage', sql => [ ["[CREATE] add table [centreon_acl]", $structure] ], actions => defined($action) ? [$action] : [] }; } elsif (defined($action)) { - push @{$etl->{run}->{schedule}->{import}->{actions}}, $action; + if ($options->{rebuild} == 1 && $options->{nopurge} == 0) { + push @{$etl->{run}->{schedule}->{import}->{actions}}, { + type => 1, db => 'centstorage', sql => [ ["[TRUNCATE] table [centreon_acl]", 'TRUNCATE table centreon_acl'] ], actions => defined($action) ? [$action] : [] + }; + } else { + push @{$etl->{run}->{schedule}->{import}->{actions}}, $action; + } } my $tables = join('|', @$notTimedTables); @@ -180,7 +186,7 @@ sub extractCentreonDB { my $bi = $utils->buildCliMysqlArgs($etl->{run}->{dbbi}->{centreon}); my $cmd = sprintf( - "mysqldump --replace --skip-add-drop-table --skip-add-locks --skip-comments %s '%s' %s | mysql --force %s '%s'", + "mysqldump --skip-add-drop-table --skip-add-locks --skip-comments %s '%s' %s | mysql --force %s '%s'", $mon, $etl->{run}->{dbmon}->{centreon}->{db}, $tables, @@ -188,8 +194,17 @@ sub extractCentreonDB { $etl->{run}->{dbbi}->{centreon}->{db} ); - push @{$etl->{run}->{schedule}->{import}->{actions}}, - { type => 2, message => '[LOAD] import table [' . $tables . ']', command => $cmd }; + push @{$etl->{run}->{schedule}->{import}->{actions}}, { + type => 1, + db => 'centreon', + sql => [ + [ '[DROPDB] database ' . $etl->{run}->{dbbi}->{centreon}->{db}, "DROP DATABASE `$etl->{run}->{dbbi}->{centreon}->{db}`" ], + [ '[CREATEDB] database ' . $etl->{run}->{dbbi}->{centreon}->{db}, "CREATE DATABASE `$etl->{run}->{dbbi}->{centreon}->{db}`" ], + ], + actions => [ + { type => 2, message => '[LOAD] import table [' . $tables . ']', command => $cmd } + ] + }; } sub dataBin { From a78b28c5a9cb29542fa9c26a578c390489495458 Mon Sep 17 00:00:00 2001 From: David Boucher <boudav@gmail.com> Date: Tue, 1 Aug 2023 13:29:44 +0200 Subject: [PATCH 753/948] fix(gorgone): mbi module is stuck after internal key rotate (again) (#1977) REFS: MON-20891 Co-authored-by: garnier-quentin <garnier.quentin@gmail.com> --- gorgone/gorgone/class/core.pm | 4 ++++ gorgone/gorgone/class/frame.pm | 3 ++- gorgone/gorgone/class/module.pm | 19 ++++++++++++------- .../modules/centreon/mbi/etlworkers/class.pm | 2 ++ 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 3eeddfd209b..1e6ba79b15c 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -564,6 +564,8 @@ sub send_internal_response { $self->{logger}->writeLogError("[core] encrypt issue: $_"); return undef; }; + + $message = MIME::Base64::encode_base64($message, ''); } $self->{internal_socket}->send(pack('H*', $options{identity}), ZMQ_DONTWAIT | ZMQ_SNDMORE); @@ -590,6 +592,8 @@ sub send_internal_message { $self->{logger}->writeLogError("[core] encrypt issue: $_"); return undef; }; + + $message = MIME::Base64::encode_base64($message, ''); } $self->{internal_socket}->send($options{identity}, ZMQ_DONTWAIT | ZMQ_SNDMORE); diff --git a/gorgone/gorgone/class/frame.pm b/gorgone/gorgone/class/frame.pm index f96e18dd5a5..14688e2da23 100644 --- a/gorgone/gorgone/class/frame.pm +++ b/gorgone/gorgone/class/frame.pm @@ -25,6 +25,7 @@ use warnings; use JSON::XS; use Try::Tiny; +use MIME::Base64; sub new { my ($class, %options) = @_; @@ -76,7 +77,7 @@ sub decrypt { my $plaintext; try { - $plaintext = $options->{cipher}->decrypt(${$self->{frame}}, $options->{key}, $options->{iv}); + $plaintext = $options->{cipher}->decrypt(MIME::Base64::decode_base64(${$self->{frame}}), $options->{key}, $options->{iv}); }; if (defined($plaintext) && $plaintext =~ /^\[[A-Za-z0-9_\-]+?\]/) { $self->{frame} = \$plaintext; diff --git a/gorgone/gorgone/class/module.pm b/gorgone/gorgone/class/module.pm index 848be128f3e..f80b525dae1 100644 --- a/gorgone/gorgone/class/module.pm +++ b/gorgone/gorgone/class/module.pm @@ -32,6 +32,7 @@ use JSON::XS; use Crypt::Mode::CBC; use Try::Tiny; use EV; +use MIME::Base64; my %handlers = (DIE => {}); @@ -171,7 +172,7 @@ sub read_message { } else { my $plaintext; try { - $plaintext = $self->{cipher}->decrypt($message, $key, $self->{internal_crypt}->{iv}); + $plaintext = $self->{cipher}->decrypt(MIME::Base64::decode_base64($message), $key, $self->{internal_crypt}->{iv}); }; if (defined($plaintext) && $plaintext =~ /^\[[A-Za-z_\-]+?\]/) { $message = undef; @@ -188,7 +189,7 @@ sub read_message { return (undef, 1); } -sub send_internal_key { +sub renew_internal_key { my ($self, %options) = @_; my $message = gorgone::standard::library::build_protocol( @@ -203,9 +204,7 @@ sub send_internal_key { return -1; }; - $options{socket}->send($message, ZMQ_DONTWAIT); - $self->event(socket => $options{socket}); - return 0; + return (0, $message); } sub send_internal_action { @@ -223,6 +222,7 @@ sub send_internal_action { $self->{logger}->writeLogDebug("[$self->{module_id}]$self->{container} internal message: $options->{message}"); my $socket = defined($options->{socket}) ? $options->{socket} : $self->{internal_socket}; + my $message_key; if ($self->{internal_crypt}->{enabled} == 1) { my $identity = gorgone::standard::library::zmq_get_routing_id(socket => $socket); @@ -233,18 +233,20 @@ sub send_internal_action { my ($rv, $genkey) = gorgone::standard::library::generate_symkey( keysize => $self->get_core_config(name => 'internal_com_keysize') ); - ($rv) = $self->send_internal_key( - socket => $socket, + + ($rv, $message_key) = $self->renew_internal_key( key => $genkey, encrypt_key => defined($self->{internal_crypt}->{identity_keys}->{$identity}) ? $self->{internal_crypt}->{identity_keys}->{$identity}->{key} : $self->{internal_crypt}->{core_keys}->[0] ); return undef if ($rv == -1); + $self->{internal_crypt}->{identity_keys}->{$identity} = { key => $genkey, ctime => time() }; } + $key = $self->{internal_crypt}->{identity_keys}->{$identity}->{key}; } @@ -254,8 +256,11 @@ sub send_internal_action { $self->{logger}->writeLogError("[$self->{module_id}]$self->{container} encrypt issue: $_"); return undef; }; + + $options->{message} = MIME::Base64::encode_base64($options->{message}, ''); } + $socket->send(MIME::Base64::encode_base64($message_key, ''), ZMQ_DONTWAIT) if (defined($message_key)); $socket->send($options->{message}, ZMQ_DONTWAIT); if ($socket->has_error) { $self->{logger}->writeLogError( diff --git a/gorgone/gorgone/modules/centreon/mbi/etlworkers/class.pm b/gorgone/gorgone/modules/centreon/mbi/etlworkers/class.pm index e7368c5a79e..9ba89780dbc 100644 --- a/gorgone/gorgone/modules/centreon/mbi/etlworkers/class.pm +++ b/gorgone/gorgone/modules/centreon/mbi/etlworkers/class.pm @@ -296,6 +296,8 @@ sub periodic_exec { $connector->{logger}->writeLogInfo("[" . $connector->{module_id} . "] $$ has quit"); exit(0); } + + $connector->event(); } sub run { From b236c6d67cb755aac5b34076943204f235d00cf9 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Wed, 30 Aug 2023 16:49:59 +0200 Subject: [PATCH 754/948] fix(packaging): fix user/group of gorgone on debian (#2090) Refs: MON-20896 --- .../packaging/debian/centreon-gorgone.install | 6 ++-- .../debian/centreon-gorgone.postinst | 33 +++---------------- .../packaging/debian/centreon-gorgone.preinst | 24 ++++++++++++++ 3 files changed, 32 insertions(+), 31 deletions(-) create mode 100755 gorgone/packaging/debian/centreon-gorgone.preinst diff --git a/gorgone/packaging/debian/centreon-gorgone.install b/gorgone/packaging/debian/centreon-gorgone.install index 5ee819ef90e..ec8e2f1eb06 100755 --- a/gorgone/packaging/debian/centreon-gorgone.install +++ b/gorgone/packaging/debian/centreon-gorgone.install @@ -3,13 +3,13 @@ gorgoned usr/bin contrib/* usr/local/bin packaging/config.yaml etc/centreon-gorgone -packaging/centreon.yaml etc/centreon-gorgone/config.d -packaging/centreon-api.yaml etc/centreon-gorgone/config.d -packaging/centreon-audit.yaml etc/centreon-gorgone/config.d packaging/sudoers.d/centreon-gorgone etc/sudoers.d gorgone/class/* usr/share/perl5/gorgone/class gorgone/modules/* usr/share/perl5/gorgone/modules gorgone/standard/* usr/share/perl5/gorgone/standard +packaging/centreon.yaml => etc/centreon-gorgone/config.d/30-centreon.yaml +packaging/centreon-api.yaml => etc/centreon-gorgone/config.d/31-centreon-api.yaml +packaging/centreon-audit.yaml => etc/centreon-gorgone/config.d/50-centreon-audit.yaml config/systemd/gorgoned-service => lib/systemd/system/gorgoned.service config/systemd/gorgoned-sysconfig => etc/default/gorgoned diff --git a/gorgone/packaging/debian/centreon-gorgone.postinst b/gorgone/packaging/debian/centreon-gorgone.postinst index a61a8ff3960..7a9180fb034 100755 --- a/gorgone/packaging/debian/centreon-gorgone.postinst +++ b/gorgone/packaging/debian/centreon-gorgone.postinst @@ -2,33 +2,14 @@ if [ "$1" = "configure" ] ; then - if [ ! "$(getent passwd centreon-gorgone)" ]; then - adduser --system --group --home /var/lib/centreon-gorgone --no-create-home centreon-gorgone - fi - - if [ "$(getent passwd centreon)" ]; then - usermod -a -G centreon-gorgone centreon - usermod -a -G centreon centreon-gorgone - fi - - if [ "$(getent passwd centreon-engine)" ]; then - usermod -a -G centreon-gorgone centreon-engine - fi - - if [ "$(getent passwd centreon-broker)" ]; then - usermod -a -G centreon-gorgone centreon-broker - fi - - chown -vR centreon-gorgone:centreon-gorgone \ + chown -R centreon-gorgone:centreon-gorgone \ /etc/centreon-gorgone \ /var/cache/centreon-gorgone \ - /var/cache/centreon-gorgone/autodiscovery \ /var/lib/centreon-gorgone \ /var/log/centreon-gorgone - chmod -vR g+w \ + chmod -R g+w \ /etc/centreon-gorgone \ /var/cache/centreon-gorgone \ - /var/cache/centreon-gorgone/autodiscovery \ /var/lib/centreon-gorgone \ /var/log/centreon-gorgone @@ -50,12 +31,8 @@ if [ "$1" = "configure" ] ; then /usr/bin/chmod 600 /var/lib/centreon-gorgone/.ssh/id_rsa fi - # rename files to priority - mv /etc/centreon-gorgone/config.d/centreon.yaml /etc/centreon-gorgone/config.d/30-centreon.yaml - mv /etc/centreon-gorgone/config.d/centreon-api.yaml /etc/centreon-gorgone/config.d/31-centreon-api.yaml - mv /etc/centreon-gorgone/config.d/centreon-audit.yaml /etc/centreon-gorgone/config.d/50-centreon-audit.yaml - - systemctl preset gorgoned.service || : >/dev/null 2>&1 || : + systemctl daemon-reload ||: + systemctl unmask gorgoned.service ||: + systemctl preset gorgoned.service ||: fi -exit 0 diff --git a/gorgone/packaging/debian/centreon-gorgone.preinst b/gorgone/packaging/debian/centreon-gorgone.preinst new file mode 100755 index 00000000000..4e8561eedf3 --- /dev/null +++ b/gorgone/packaging/debian/centreon-gorgone.preinst @@ -0,0 +1,24 @@ +#!/bin/sh + +if [ "$1" = "configure" ] ; then + + if [ ! "$(getent passwd centreon-gorgone)" ]; then + adduser --system --group --home /var/lib/centreon-gorgone --no-create-home centreon-gorgone + fi + + if [ "$(getent passwd centreon)" ]; then + usermod centreon -a -G centreon-gorgone + usermod centreon-gorgone -a -G centreon + fi + + if [ "$(getent passwd centreon-engine)" ]; then + usermod centreon-engine -a -G centreon-gorgone + usermod centreon-gorgone -a -G centreon-engine + fi + + if [ "$(getent passwd centreon-broker)" ]; then + usermod centreon-broker -a -G centreon-gorgone + usermod centreon-gorgone -a -G centreon-broker + fi + +fi From 2ae8a5ac95eef5c38eec42f3cc3a3e0d4d32f008 Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Tue, 5 Sep 2023 14:51:12 +0200 Subject: [PATCH 755/948] fix(gorgone): pull modules stop responding (#2124) --- gorgone/gorgone/modules/core/pull/class.pm | 14 +++++++++++++- gorgone/gorgone/modules/core/pullwss/class.pm | 12 ++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/gorgone/gorgone/modules/core/pull/class.pm b/gorgone/gorgone/modules/core/pull/class.pm index 93f320910cb..230cf96dd96 100644 --- a/gorgone/gorgone/modules/core/pull/class.pm +++ b/gorgone/gorgone/modules/core/pull/class.pm @@ -125,6 +125,18 @@ sub transmit_back { return '[SETLOGS] [' . $1 . '] [] ' . $2; } return undef; + } elsif ($options{message} =~ /^\[BCASTCOREKEY\]\s+\[.*?\]\s+\[.*?\]\s+(.*)/m) { + my $data; + eval { + $data = JSON::XS->new->decode($1); + }; + if ($@) { + $connector->{logger}->writeLogDebug("[pull] cannot decode BCASTCOREKEY: $@"); + return undef; + } + + $connector->action_bcastcorekey(data => $data); + return undef; } elsif ($options{message} =~ /^\[(PONG|SYNCLOGS)\]/) { return $options{message}; } @@ -149,7 +161,7 @@ sub event { while ($self->{internal_socket}->has_pollin()) { my ($message) = $self->read_message(); $message = transmit_back(message => $message); - last if (!defined($message)); + next if (!defined($message)); # Only send back SETLOGS and PONG $self->{logger}->writeLogDebug("[pull] read message from internal: $message"); diff --git a/gorgone/gorgone/modules/core/pullwss/class.pm b/gorgone/gorgone/modules/core/pullwss/class.pm index 868704bd6f2..d74cf784f1f 100644 --- a/gorgone/gorgone/modules/core/pullwss/class.pm +++ b/gorgone/gorgone/modules/core/pullwss/class.pm @@ -246,6 +246,18 @@ sub transmit_back { return '[SETLOGS] [' . $1 . '] [] ' . $2; } return undef; + } elsif ($options{message} =~ /^\[BCASTCOREKEY\]\s+\[.*?\]\s+\[.*?\]\s+(.*)/m) { + my $data; + eval { + $data = JSON::XS->new->decode($1); + }; + if ($@) { + $connector->{logger}->writeLogDebug("[pull] cannot decode BCASTCOREKEY: $@"); + return undef; + } + + $connector->action_bcastcorekey(data => $data); + return undef; } elsif ($options{message} =~ /^\[(PONG|SYNCLOGS)\]/) { return $options{message}; } From 6010945fae2233db89a33f787cd1d0803d8512ac Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Tue, 5 Sep 2023 16:25:25 +0200 Subject: [PATCH 756/948] enh(e2e): upgrade keycloak container + try fix flaky oidc tests (#2120) --- .github/workflows/gorgone.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 057d6a93ad3..d01b8bf51c7 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -85,7 +85,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Deliver sources uses: ./.github/actions/release-sources @@ -108,7 +108,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Delivery uses: ./.github/actions/rpm-delivery @@ -131,7 +131,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Delivery uses: ./.github/actions/deb-delivery @@ -153,7 +153,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Promote ${{ matrix.distrib }} to stable uses: ./.github/actions/promote-to-stable From 2ebf4beb52651caafa55a4d47ec0a9edb636b5ce Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Mon, 25 Sep 2023 13:30:25 +0200 Subject: [PATCH 757/948] feat(ci): build centreon-gorgone selinux package (#2142) Refs: MON-20157 --- .../packaging/centreon-gorgone.spectemplate | 31 +++++ gorgone/selinux/centreon-gorgoned.fc | 3 + gorgone/selinux/centreon-gorgoned.if | 1 + gorgone/selinux/centreon-gorgoned.te | 119 ++++++++++++++++++ 4 files changed, 154 insertions(+) create mode 100644 gorgone/selinux/centreon-gorgoned.fc create mode 100644 gorgone/selinux/centreon-gorgoned.if create mode 100644 gorgone/selinux/centreon-gorgoned.te diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index 01b73aa79f1..b711e2e816b 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -1,3 +1,5 @@ +%define selinuxdevel %{_datadir}/selinux/devel + Name: centreon-gorgone Version: 23.10.0 Release: %{PACKAGE_RELEASE}%{?dist} @@ -57,11 +59,25 @@ Requires: centreon-gorgone %description centreon-config %{COMMIT_HASH} +%package -n centreon-gorgoned-selinux +Summary: SELinux context for centreon-gorgoned +Group: Networking/Other +License: Apache-2.0 +Requires: policycoreutils +Requires: centreon-common-selinux + +%description -n centreon-gorgoned-selinux +%{COMMIT_HASH} +SElinux context for centreon-gorgoned. + %prep %setup -q -n %{name}-%{version} %build +sed -i 's#@VERSION@#%{version}#' centreon-gorgone/selinux/* +(cd centreon-gorgone/selinux && %{__make} -f %{selinuxdevel}/Makefile) + %install rm -rf %{buildroot} mkdir -p %{buildroot}/%{perl_vendorlib}/gorgone @@ -96,6 +112,9 @@ mkdir -p %{buildroot}/%{perl_vendorlib}/gorgone %{__install} -d %buildroot%{_sysconfdir}/sudoers.d/ %{__cp} centreon-gorgone/packaging/sudoers.d/centreon-gorgone %buildroot%{_sysconfdir}/sudoers.d/centreon-gorgone +%{__mkdir} -p %buildroot%{_datadir}/selinux/packages/centreon +%{__install} -m 655 centreon-gorgone/selinux/centreon-gorgoned.pp %buildroot%{_datadir}/selinux/packages/centreon/centreon-gorgoned.pp + %{_fixperms} $RPM_BUILD_ROOT/* %check @@ -160,4 +179,16 @@ fi %postun %systemd_postun_with_restart gorgoned.service || : +%files -n centreon-gorgoned-selinux +%defattr(-, root, root, -) +%{_datadir}/selinux/packages/centreon/centreon-gorgoned.pp + +%post -n centreon-gorgoned-selinux +semodule -i %{_datadir}/selinux/packages/centreon/centreon-gorgoned.pp > /dev/null 2>&1 || : + +%preun -n centreon-gorgoned-selinux +if [ "$1" -lt "1" ]; then # Final removal + semodule -r centreon-gorgoned > /dev/null 2>&1 || : +fi + %changelog diff --git a/gorgone/selinux/centreon-gorgoned.fc b/gorgone/selinux/centreon-gorgoned.fc new file mode 100644 index 00000000000..5e782b3c860 --- /dev/null +++ b/gorgone/selinux/centreon-gorgoned.fc @@ -0,0 +1,3 @@ +/usr/bin/gorgoned -- gen_context(system_u:object_r:centreon_gorgoned_exec_t,s0) +/etc/centreon-gorgone(/.*)? gen_context(system_u:object_r:centreon_etc_t,s0) +/var/lib/centreon-gorgone(/.*)? gen_context(system_u:object_r:centreon_gorgoned_t,s0) diff --git a/gorgone/selinux/centreon-gorgoned.if b/gorgone/selinux/centreon-gorgoned.if new file mode 100644 index 00000000000..ba267cf4710 --- /dev/null +++ b/gorgone/selinux/centreon-gorgoned.if @@ -0,0 +1 @@ +## <summary>Centreon Gorgoned Network monitoring server.</summary> diff --git a/gorgone/selinux/centreon-gorgoned.te b/gorgone/selinux/centreon-gorgoned.te new file mode 100644 index 00000000000..38cc2726970 --- /dev/null +++ b/gorgone/selinux/centreon-gorgoned.te @@ -0,0 +1,119 @@ +policy_module(centreon-gorgoned, @VERSION@) + +######################################## +# +# Declarations +# +require { + type unconfined_t; + type unconfined_service_t; + type useradd_t; + type fs_t; + type kernel_t; + type setroubleshootd_t; + type rpm_script_t; + type setfiles_t; + type unconfined_domain_type; +} + +type centreon_gorgoned_t; +type centreon_gorgoned_exec_t; +init_daemon_domain(centreon_gorgoned_t, centreon_gorgoned_exec_t) + +######################################## +# +# Centreon local policy +# + +allow centreon_gorgoned_t self:process { setpgid signal_perms }; +allow centreon_gorgoned_t self:tcp_socket { accept listen }; +allow centreon_gorgoned_t self:file { read open write getattr read_file_perms relabelto }; +allow centreon_gorgoned_t fs_t:filesystem associate; +allow rpm_script_t centreon_gorgoned_t:dir { getattr search }; + +#============= setroubleshootd_t ============== +allow setroubleshootd_t centreon_gorgoned_t:dir { getattr search }; +allow setroubleshootd_t centreon_gorgoned_t:file getattr; + +#============= unconfined_t ============== +allow unconfined_t centreon_gorgoned_t:dir { getattr setattr relabelfrom relabelto }; +allow unconfined_t centreon_gorgoned_t:file { getattr setattr relabelto rename }; + +#============= unconfined_service_t ============== +allow unconfined_service_t centreon_gorgoned_t:file { create read open write rename getattr setattr ioctl lock unlink }; +allow unconfined_service_t centreon_gorgoned_t:dir { getattr setattr search create write add_name remove_name }; + +#============= useradd_t ============== +allow useradd_t centreon_gorgoned_t:dir { getattr search setattr create write add_name remove_name }; +allow useradd_t centreon_gorgoned_t:file { open write read unlink create setattr getattr ioctl lock }; + +#============= setfiles_t ============== +allow setfiles_t centreon_gorgoned_t:dir relabelto; +allow setfiles_t centreon_gorgoned_t:file relabelto; + +#============= kernel_t ============== +allow kernel_t centreon_gorgoned_t:dir { getattr search setattr create write add_name remove_name }; +allow kernel_t centreon_gorgoned_t:file { open write read unlink create setattr getattr ioctl lock }; + +#============= cluster =============== +allow daemon initrc_transition_domain:fifo_file { ioctl read write getattr lock append }; +allow domain unconfined_domain_type:association recvfrom; +allow domain domain:key { search link }; +allow domain unconfined_domain_type:tcp_socket recvfrom; +allow centreon_gorgoned_t domain:lnk_file { read getattr }; +allow daemon initrc_domain:fd use; +allow centreon_gorgoned_t domain:file { ioctl read getattr lock open }; +allow daemon initrc_domain:process sigchld; +allow domain unconfined_domain_type:peer recv; +allow centreon_gorgoned_t domain:dir { ioctl read getattr lock search open }; +allow daemon initrc_transition_domain:fd use; +allow daemon initrc_domain:fifo_file { ioctl read write getattr lock append }; + +mysql_stream_connect(centreon_gorgoned_t) +mysql_tcp_connect(centreon_gorgoned_t) + +kernel_read_kernel_sysctls(centreon_gorgoned_t) +kernel_read_net_sysctls(centreon_gorgoned_t) +kernel_read_network_state(centreon_gorgoned_t) +kernel_read_system_state(centreon_gorgoned_t) +kernel_request_load_module(centreon_gorgoned_t) + +corecmd_exec_bin(centreon_gorgoned_t) +corecmd_exec_shell(centreon_gorgoned_t) + +corenet_port(centreon_gorgoned_t) +corenet_all_recvfrom_unlabeled(centreon_gorgoned_t) +corenet_all_recvfrom_netlabel(centreon_gorgoned_t) +corenet_tcp_sendrecv_generic_if(centreon_gorgoned_t) +corenet_udp_sendrecv_generic_if(centreon_gorgoned_t) +corenet_tcp_sendrecv_generic_node(centreon_gorgoned_t) +corenet_udp_sendrecv_generic_node(centreon_gorgoned_t) +corenet_tcp_bind_generic_node(centreon_gorgoned_t) +corenet_udp_bind_generic_node(centreon_gorgoned_t) +corenet_sendrecv_all_client_packets(centreon_gorgoned_t) +corenet_tcp_connect_all_ports(centreon_gorgoned_t) +corenet_tcp_sendrecv_all_ports(centreon_gorgoned_t) + +corenet_sendrecv_inetd_child_server_packets(centreon_gorgoned_t) +corenet_tcp_bind_inetd_child_port(centreon_gorgoned_t) +corenet_tcp_sendrecv_inetd_child_port(centreon_gorgoned_t) + +dev_read_sysfs(centreon_gorgoned_t) +dev_read_urand(centreon_gorgoned_t) + +domain_use_interactive_fds(centreon_gorgoned_t) +domain_read_all_domains_state(centreon_gorgoned_t) + +files_read_etc_runtime_files(centreon_gorgoned_t) +files_read_usr_files(centreon_gorgoned_t) + +fs_getattr_all_fs(centreon_gorgoned_t) +fs_search_auto_mountpoints(centreon_gorgoned_t) + +auth_use_nsswitch(centreon_gorgoned_t) + +logging_send_syslog_msg(centreon_gorgoned_t) + +miscfiles_read_localization(centreon_gorgoned_t) + +userdom_dontaudit_use_unpriv_user_fds(centreon_gorgoned_t) \ No newline at end of file From 1c432caf960c58d2beea511976cff671e6a76c9a Mon Sep 17 00:00:00 2001 From: qgarnier <garnier.quentin@gmail.com> Date: Mon, 9 Oct 2023 10:35:51 +0200 Subject: [PATCH 758/948] fix(gorgone): deep recursion core (#2255) Co-authored-by: root <root@CNTR-P-PC1KSC32> --- gorgone/gorgone/class/core.pm | 56 ++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 1e6ba79b15c..55ce25ae819 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -41,7 +41,7 @@ my ($gorgone); use base qw(gorgone::class::script); -my $VERSION = '22.04.0'; +my $VERSION = '23.04.0'; my %handlers = (TERM => {}, HUP => {}, CHLD => {}, DIE => {}); sub new { @@ -74,7 +74,9 @@ sub new { 'GET_/internal/information' => 'INFORMATION', 'POST_/internal/logger' => 'BCASTLOGGER', }; - $self->{config} = + + $self->{ievents} = []; + $self->{recursion_ievents} = 0; return $self; } @@ -513,17 +515,11 @@ sub broadcast_core_key { ); } -sub read_internal_message { +sub decrypt_internal_message { my ($self, %options) = @_; - my ($identity, $frame) = gorgone::standard::library::zmq_read_message( - socket => $self->{internal_socket}, - logger => $self->{logger} - ); - return undef if (!defined($identity)); - if ($self->{internal_crypt}->{enabled} == 1) { - my $id = pack('H*', $identity); + my $id = pack('H*', $options{identity}); my $keys; if (defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_identity_keys}->{$id})) { $keys = [ $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_identity_keys}->{$id}->{key} ]; @@ -533,16 +529,16 @@ sub read_internal_message { if (defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_core_oldkey})); } foreach my $key (@$keys) { - if ($frame->decrypt({ cipher => $self->{cipher}, key => $key, iv => $self->{internal_crypt}->{iv} }) == 0) { - return ($identity, $frame); + if ($options{frame}->decrypt({ cipher => $self->{cipher}, key => $key, iv => $self->{internal_crypt}->{iv} }) == 0) { + return 0; } } - $self->{logger}->writeLogError("[core] decrypt issue ($id): " . $frame->getLastError()); - return undef; + $self->{logger}->writeLogError("[core] decrypt issue ($id): " . $options{frame}->getLastError()); + return 1; } - return ($identity, $frame); + return 1; } sub send_internal_response { @@ -640,7 +636,7 @@ sub message_run { if ($self->{logger}->is_debug()) { my $frame_ref = $options->{frame}->getFrame(); - $self->{logger}->writeLogDebug('[core] Message received - ' . $$frame_ref); + $self->{logger}->writeLogDebug('[core] Message received ' . $options->{router_type} . ' - ' . $$frame_ref); } if ($options->{frame}->parse({ releaseFrame => 1 }) != 0) { return (undef, 1, { message => 'request not well formatted' }); @@ -761,28 +757,47 @@ sub message_run { return ($token, 0); } + sub router_internal_event { my ($self, %options) = @_; + $self->{recursion_ievents}++; + while ($self->{internal_socket}->has_pollin()) { - my ($identity, $frame) = $self->read_internal_message(); + my ($identity, $frame) = gorgone::standard::library::zmq_read_message( + socket => $self->{internal_socket}, + logger => $self->{logger} + ); next if (!defined($identity)); + push @{$self->{ievents}}, [$identity, $frame]; + } + + + if ($self->{recursion_ievents} > 1) { + $self->{recursion_ievents}--; + return; + } + + while (my $event = shift(@{$self->{ievents}})) { + next if ($self->decrypt_internal_message(identity => $event->[0], frame => $event->[1])); my ($token, $code, $response, $response_type) = $self->message_run( { - frame => $frame, - identity => $identity, + frame => $event->[1], + identity => $event->[0], router_type => 'internal' } ); $self->send_internal_response( - identity => $identity, + identity => $event->[0], response_type => $response_type, data => $response, code => $code, token => $token ); } + + $self->{recursion_ievents}--; } sub is_handshake_done { @@ -1319,3 +1334,4 @@ sub run { 1; __END__ + From 3d833de1b10010355cfe1faa2617d9fa5f744a36 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Mon, 9 Oct 2023 13:30:21 +0200 Subject: [PATCH 759/948] fix(gorgone): fix external commands handling when recursion happens (#2317) Refs: MON-21972 --- gorgone/gorgone/class/core.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 55ce25ae819..c86d1fa6221 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -772,8 +772,8 @@ sub router_internal_event { push @{$self->{ievents}}, [$identity, $frame]; } - - if ($self->{recursion_ievents} > 1) { + # @todo check why recursion happens and avoid this workaround + if ($self->{recursion_ievents} > 10) { $self->{recursion_ievents}--; return; } From 803427ca36b4446be93d65b75e9d67e1cd9f0e48 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Mon, 9 Oct 2023 17:37:08 +0200 Subject: [PATCH 760/948] fix(gorgone): revert gorgone changes on deep recursion (#2323) --- gorgone/gorgone/class/core.pm | 56 +++++++++++++---------------------- 1 file changed, 20 insertions(+), 36 deletions(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index c86d1fa6221..3ac9e36f349 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -41,7 +41,7 @@ my ($gorgone); use base qw(gorgone::class::script); -my $VERSION = '23.04.0'; +my $VERSION = '23.10.0'; my %handlers = (TERM => {}, HUP => {}, CHLD => {}, DIE => {}); sub new { @@ -74,9 +74,7 @@ sub new { 'GET_/internal/information' => 'INFORMATION', 'POST_/internal/logger' => 'BCASTLOGGER', }; - - $self->{ievents} = []; - $self->{recursion_ievents} = 0; + $self->{config} = return $self; } @@ -515,11 +513,17 @@ sub broadcast_core_key { ); } -sub decrypt_internal_message { +sub read_internal_message { my ($self, %options) = @_; + my ($identity, $frame) = gorgone::standard::library::zmq_read_message( + socket => $self->{internal_socket}, + logger => $self->{logger} + ); + return undef if (!defined($identity)); + if ($self->{internal_crypt}->{enabled} == 1) { - my $id = pack('H*', $options{identity}); + my $id = pack('H*', $identity); my $keys; if (defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_identity_keys}->{$id})) { $keys = [ $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_identity_keys}->{$id}->{key} ]; @@ -529,16 +533,16 @@ sub decrypt_internal_message { if (defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_core_oldkey})); } foreach my $key (@$keys) { - if ($options{frame}->decrypt({ cipher => $self->{cipher}, key => $key, iv => $self->{internal_crypt}->{iv} }) == 0) { - return 0; + if ($frame->decrypt({ cipher => $self->{cipher}, key => $key, iv => $self->{internal_crypt}->{iv} }) == 0) { + return ($identity, $frame); } } - $self->{logger}->writeLogError("[core] decrypt issue ($id): " . $options{frame}->getLastError()); - return 1; + $self->{logger}->writeLogError("[core] decrypt issue ($id): " . $frame->getLastError()); + return undef; } - return 1; + return ($identity, $frame); } sub send_internal_response { @@ -636,7 +640,7 @@ sub message_run { if ($self->{logger}->is_debug()) { my $frame_ref = $options->{frame}->getFrame(); - $self->{logger}->writeLogDebug('[core] Message received ' . $options->{router_type} . ' - ' . $$frame_ref); + $self->{logger}->writeLogDebug('[core] Message received - ' . $$frame_ref); } if ($options->{frame}->parse({ releaseFrame => 1 }) != 0) { return (undef, 1, { message => 'request not well formatted' }); @@ -757,47 +761,28 @@ sub message_run { return ($token, 0); } - sub router_internal_event { my ($self, %options) = @_; - $self->{recursion_ievents}++; - while ($self->{internal_socket}->has_pollin()) { - my ($identity, $frame) = gorgone::standard::library::zmq_read_message( - socket => $self->{internal_socket}, - logger => $self->{logger} - ); + my ($identity, $frame) = $self->read_internal_message(); next if (!defined($identity)); - push @{$self->{ievents}}, [$identity, $frame]; - } - - # @todo check why recursion happens and avoid this workaround - if ($self->{recursion_ievents} > 10) { - $self->{recursion_ievents}--; - return; - } - - while (my $event = shift(@{$self->{ievents}})) { - next if ($self->decrypt_internal_message(identity => $event->[0], frame => $event->[1])); my ($token, $code, $response, $response_type) = $self->message_run( { - frame => $event->[1], - identity => $event->[0], + frame => $frame, + identity => $identity, router_type => 'internal' } ); $self->send_internal_response( - identity => $event->[0], + identity => $identity, response_type => $response_type, data => $response, code => $code, token => $token ); } - - $self->{recursion_ievents}--; } sub is_handshake_done { @@ -1334,4 +1319,3 @@ sub run { 1; __END__ - From 3e41bad65841c0ddc20c474113375f497b29e77b Mon Sep 17 00:00:00 2001 From: David Boucher <boudav@gmail.com> Date: Tue, 24 Oct 2023 14:40:20 +0200 Subject: [PATCH 761/948] fix(gorgone): missing dependency on debian (#2444) --- gorgone/packaging/debian/control | 1 + 1 file changed, 1 insertion(+) diff --git a/gorgone/packaging/debian/control b/gorgone/packaging/debian/control index 5aa5025d5ef..f117516bb03 100644 --- a/gorgone/packaging/debian/control +++ b/gorgone/packaging/debian/control @@ -36,6 +36,7 @@ Depends: libauthen-simple-net-perl, libnet-curl-perl, libssh-session-perl, + libssh-4, libev-perl, libzmq-ffi-perl, sudo, From 1177b8d7dd96199edd1d458ec52b310cf2b19c94 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Wed, 25 Oct 2023 13:38:54 +0200 Subject: [PATCH 762/948] fix(packaging): remove condition in gorgone debian preinst script (#2458) --- .../packaging/debian/centreon-gorgone.preinst | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/gorgone/packaging/debian/centreon-gorgone.preinst b/gorgone/packaging/debian/centreon-gorgone.preinst index 4e8561eedf3..774d2a26c24 100755 --- a/gorgone/packaging/debian/centreon-gorgone.preinst +++ b/gorgone/packaging/debian/centreon-gorgone.preinst @@ -1,24 +1,20 @@ #!/bin/sh -if [ "$1" = "configure" ] ; then - - if [ ! "$(getent passwd centreon-gorgone)" ]; then - adduser --system --group --home /var/lib/centreon-gorgone --no-create-home centreon-gorgone - fi - - if [ "$(getent passwd centreon)" ]; then - usermod centreon -a -G centreon-gorgone - usermod centreon-gorgone -a -G centreon - fi +if [ ! "$(getent passwd centreon-gorgone)" ]; then + adduser --system --group --home /var/lib/centreon-gorgone --no-create-home centreon-gorgone +fi - if [ "$(getent passwd centreon-engine)" ]; then - usermod centreon-engine -a -G centreon-gorgone - usermod centreon-gorgone -a -G centreon-engine - fi +if [ "$(getent passwd centreon)" ]; then + usermod centreon -a -G centreon-gorgone + usermod centreon-gorgone -a -G centreon +fi - if [ "$(getent passwd centreon-broker)" ]; then - usermod centreon-broker -a -G centreon-gorgone - usermod centreon-gorgone -a -G centreon-broker - fi +if [ "$(getent passwd centreon-engine)" ]; then + usermod centreon-engine -a -G centreon-gorgone + usermod centreon-gorgone -a -G centreon-engine +fi +if [ "$(getent passwd centreon-broker)" ]; then + usermod centreon-broker -a -G centreon-gorgone + usermod centreon-gorgone -a -G centreon-broker fi From dba735e79ec8ba07a50cb360e603e5b9f7b7b3b8 Mon Sep 17 00:00:00 2001 From: hamzabessa <hbessa@ext.centreon.com> Date: Thu, 26 Oct 2023 11:56:06 +0100 Subject: [PATCH 763/948] fix: fix errors related to lenting --- .github/workflows/gorgone.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index d01b8bf51c7..75b9310feb3 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -122,7 +122,7 @@ jobs: deliver-deb: runs-on: [self-hosted, common] - needs: [get-version,package] + needs: [get-version, package] if: ${{ contains(fromJson('["testing", "unstable"]'), needs.get-version.outputs.stability) }} strategy: @@ -163,4 +163,4 @@ jobs: distrib: ${{ matrix.distrib }} major_version: ${{ needs.get-version.outputs.major_version }} minor_version: ${{ needs.get-version.outputs.minor_version }} - stability: ${{ needs.get-version.outputs.stability }} \ No newline at end of file + stability: ${{ needs.get-version.outputs.stability }} From 9d74e87b96fa6114bd34a01e9240e0712226b918 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Tue, 31 Oct 2023 10:48:13 +0100 Subject: [PATCH 764/948] fix(ci): remove workflow triggers on tags --- .github/workflows/gorgone.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index d01b8bf51c7..ff5f6d1c5e7 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -17,8 +17,6 @@ on: - "[2-9][0-9].[0-9][0-9].x" paths: - "centreon-gorgone/**" - tags: - - centreon-gorgone-* env: base_directory: centreon-gorgone From 26380a8b29fe5e0558764b9c46ec968512978394 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Thu, 2 Nov 2023 08:53:14 +0100 Subject: [PATCH 765/948] chore(release): bump major version to 24.04.0 (#2489) --- gorgone/.version | 1 + gorgone/packaging/centreon-gorgone.spectemplate | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 gorgone/.version diff --git a/gorgone/.version b/gorgone/.version new file mode 100644 index 00000000000..be51a9cda2e --- /dev/null +++ b/gorgone/.version @@ -0,0 +1 @@ +MINOR=0 diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate index b711e2e816b..4dad080dda5 100644 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ b/gorgone/packaging/centreon-gorgone.spectemplate @@ -1,7 +1,7 @@ %define selinuxdevel %{_datadir}/selinux/devel Name: centreon-gorgone -Version: 23.10.0 +Version: 24.04.0 Release: %{PACKAGE_RELEASE}%{?dist} Summary: Perl daemon task handlers Group: Applications/System From f72b6a52ae0c99c8a70a049f0ff3b5f1ed114ba7 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Thu, 2 Nov 2023 11:03:29 +0100 Subject: [PATCH 766/948] fix(packaging): update rights of engine/broker configuration files (#2455) Refs: MON-14480 --- gorgone/packaging/debian/centreon-gorgone.install | 2 +- gorgone/packaging/debian/centreon-gorgone.postinst | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/gorgone/packaging/debian/centreon-gorgone.install b/gorgone/packaging/debian/centreon-gorgone.install index ec8e2f1eb06..70abee6422f 100755 --- a/gorgone/packaging/debian/centreon-gorgone.install +++ b/gorgone/packaging/debian/centreon-gorgone.install @@ -3,13 +3,13 @@ gorgoned usr/bin contrib/* usr/local/bin packaging/config.yaml etc/centreon-gorgone +packaging/centreon-api.yaml etc/centreon-gorgone/config.d packaging/sudoers.d/centreon-gorgone etc/sudoers.d gorgone/class/* usr/share/perl5/gorgone/class gorgone/modules/* usr/share/perl5/gorgone/modules gorgone/standard/* usr/share/perl5/gorgone/standard packaging/centreon.yaml => etc/centreon-gorgone/config.d/30-centreon.yaml -packaging/centreon-api.yaml => etc/centreon-gorgone/config.d/31-centreon-api.yaml packaging/centreon-audit.yaml => etc/centreon-gorgone/config.d/50-centreon-audit.yaml config/systemd/gorgoned-service => lib/systemd/system/gorgoned.service config/systemd/gorgoned-sysconfig => etc/default/gorgoned diff --git a/gorgone/packaging/debian/centreon-gorgone.postinst b/gorgone/packaging/debian/centreon-gorgone.postinst index 7a9180fb034..9dea7fbeabf 100755 --- a/gorgone/packaging/debian/centreon-gorgone.postinst +++ b/gorgone/packaging/debian/centreon-gorgone.postinst @@ -31,6 +31,12 @@ if [ "$1" = "configure" ] ; then /usr/bin/chmod 600 /var/lib/centreon-gorgone/.ssh/id_rsa fi + if [ ! -f /etc/centreon-gorgone/config.d/31-centreon-api.yaml ] || grep -q "@GORGONE_USER@" "/etc/centreon-gorgone/config.d/31-centreon-api.yaml"; then + mv /etc/centreon-gorgone/config.d/centreon-api.yaml /etc/centreon-gorgone/config.d/31-centreon-api.yaml + else + mv /etc/centreon-gorgone/config.d/centreon-api.yaml /etc/centreon-gorgone/config.d/centreon-api.yaml.new + fi + systemctl daemon-reload ||: systemctl unmask gorgoned.service ||: systemctl preset gorgoned.service ||: From eda5a2a0a3f8dfe2078b9371d22cb4fe26cd5638 Mon Sep 17 00:00:00 2001 From: sdepassio <114986849+sdepassio@users.noreply.github.com> Date: Fri, 10 Nov 2023 10:44:02 +0100 Subject: [PATCH 767/948] fix(gorgone): Process killed after a OOM - Deep recursion on subroutine (#2546) --- gorgone/gorgone/class/core.pm | 109 ++++++++++-------- .../modules/centreon/autodiscovery/hooks.pm | 4 +- .../modules/centreon/legacycmd/hooks.pm | 4 +- gorgone/gorgone/modules/core/cron/hooks.pm | 4 +- 4 files changed, 68 insertions(+), 53 deletions(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 3ac9e36f349..86f33867979 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -23,7 +23,6 @@ package gorgone::class::core; use strict; use warnings; use POSIX ":sys_wait_h"; -use Sys::Hostname; use MIME::Base64; use Crypt::Mode::CBC; use ZMQ::FFI qw(ZMQ_DONTWAIT ZMQ_SNDMORE); @@ -74,7 +73,9 @@ sub new { 'GET_/internal/information' => 'INFORMATION', 'POST_/internal/logger' => 'BCASTLOGGER', }; - $self->{config} = + + $self->{ievents} = []; + $self->{recursion_ievents} = 0; return $self; } @@ -269,7 +270,8 @@ sub init { $self->{hostname} = $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{hostname}; if (!defined($self->{hostname}) || $self->{hostname} eq '') { - $self->{hostname} = hostname(); + my ($sysname, $nodename, $release, $version, $machine) = POSIX::uname(); + $self->{hostname} = $sysname; } $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{proxy_name} = @@ -513,17 +515,11 @@ sub broadcast_core_key { ); } -sub read_internal_message { +sub decrypt_internal_message { my ($self, %options) = @_; - my ($identity, $frame) = gorgone::standard::library::zmq_read_message( - socket => $self->{internal_socket}, - logger => $self->{logger} - ); - return undef if (!defined($identity)); - if ($self->{internal_crypt}->{enabled} == 1) { - my $id = pack('H*', $identity); + my $id = pack('H*', $options{identity}); my $keys; if (defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_identity_keys}->{$id})) { $keys = [ $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_identity_keys}->{$id}->{key} ]; @@ -533,16 +529,16 @@ sub read_internal_message { if (defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_core_oldkey})); } foreach my $key (@$keys) { - if ($frame->decrypt({ cipher => $self->{cipher}, key => $key, iv => $self->{internal_crypt}->{iv} }) == 0) { - return ($identity, $frame); + if ($options{frame}->decrypt({ cipher => $self->{cipher}, key => $key, iv => $self->{internal_crypt}->{iv} }) == 0) { + return 0; } } - $self->{logger}->writeLogError("[core] decrypt issue ($id): " . $frame->getLastError()); - return undef; + $self->{logger}->writeLogError("[core] decrypt issue ($id): " . $options{frame}->getLastError()); + return 1; } - return ($identity, $frame); + return 0; } sub send_internal_response { @@ -640,7 +636,7 @@ sub message_run { if ($self->{logger}->is_debug()) { my $frame_ref = $options->{frame}->getFrame(); - $self->{logger}->writeLogDebug('[core] Message received - ' . $$frame_ref); + $self->{logger}->writeLogDebug('[core] Message received ' . $options->{router_type} . ' - ' . $$frame_ref); } if ($options->{frame}->parse({ releaseFrame => 1 }) != 0) { return (undef, 1, { message => 'request not well formatted' }); @@ -714,46 +710,46 @@ sub message_run { ); return ($token, 0); } - + if ($action =~ /^(?:ADDLISTENER|PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID|SETMODULEKEY|SYNCLOGS|LOADMODULE|UNLOADMODULE|INFORMATION|GETTHUMBPRINT)$/) { my ($code, $response, $response_type) = $self->{internal_register}->{lc($action)}->( - gorgone => $self, + gorgone => $self, gorgone_config => $self->{config}->{configuration}->{gorgone}, - identity => $options->{identity}, - router_type => $options->{router_type}, - id => $self->{id}, - frame => $options->{frame}, - token => $token, - logger => $self->{logger} + identity => $options->{identity}, + router_type => $options->{router_type}, + id => $self->{id}, + frame => $options->{frame}, + token => $token, + logger => $self->{logger} ); if ($action =~ /^(?:CONSTATUS|INFORMATION|GETTHUMBPRINT)$/) { gorgone::standard::library::add_history({ - dbh => $self->{db_gorgone}, - code => $code, - token => $token, - data => $response, + dbh => $self->{db_gorgone}, + code => $code, + token => $token, + data => $response, json_encode => 1 }); } - + return ($token, $code, $response, $response_type); } elsif ($action =~ /^BCAST(.*)$/) { return (undef, 1, { message => "action '$action' is not known" }) if ($1 !~ /^(?:LOGGER|COREKEY)$/); $self->broadcast_run( action => $action, - frame => $options->{frame}, - token => $token + frame => $options->{frame}, + token => $token ); } else { $self->{modules_register}->{ $self->{modules_events}->{$action}->{module}->{package} }->{routing}->( - gorgone => $self, - dbh => $self->{db_gorgone}, - logger => $self->{logger}, - action => $action, - token => $token, - target => $target, - frame => $options->{frame}, + gorgone => $self, + dbh => $self->{db_gorgone}, + logger => $self->{logger}, + action => $action, + token => $token, + target => $target, + frame => $options->{frame}, hostname => $self->{hostname} ); } @@ -764,25 +760,39 @@ sub message_run { sub router_internal_event { my ($self, %options) = @_; + $self->{recursion_ievents}++; while ($self->{internal_socket}->has_pollin()) { - my ($identity, $frame) = $self->read_internal_message(); + my ($identity, $frame) = gorgone::standard::library::zmq_read_message( + socket => $self->{internal_socket}, + logger => $self->{logger} + ); next if (!defined($identity)); + push @{$self->{ievents}}, [ $identity, $frame ]; + } + + if ($self->{recursion_ievents} > 1) { + $self->{recursion_ievents}--; + return; + } + while (my $event = shift(@{$self->{ievents}})) { + next if ($self->decrypt_internal_message(identity => $event->[0], frame => $event->[1])); my ($token, $code, $response, $response_type) = $self->message_run( { - frame => $frame, - identity => $identity, + frame => $event->[1], + identity => $event->[0], router_type => 'internal' } ); $self->send_internal_response( - identity => $identity, + identity => $event->[0], response_type => $response_type, - data => $response, - code => $code, - token => $token + data => $response, + code => $code, + token => $token ); } + $self->{recursion_ievents}--; } sub is_handshake_done { @@ -1124,15 +1134,20 @@ sub stop_ev { sub waiting_ready { my (%options) = @_; - return 1 if (${$options{ready}} == 1); + if (${$options{ready}} == 1) { + return 1 ; + } my $iteration = 10; while ($iteration > 0) { my $watcher_timer = $gorgone->{loop}->timer(1, 0, \&stop_ev); + my $count_iteration = $gorgone->{loop}->iteration; + my $count_pending = $gorgone->{loop}->pending_count; $gorgone->{loop}->run(); if (${$options{ready}} == 1) { return 1; } + $iteration --; } return 0; diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm b/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm index 24a4b8007c3..f20befe507a 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm @@ -68,7 +68,7 @@ sub routing { $autodiscovery->{ready} = 1; return undef; } - + if (gorgone::class::core::waiting_ready(ready => \$autodiscovery->{ready}) == 0) { gorgone::standard::library::add_history({ dbh => $options{dbh}, @@ -79,7 +79,7 @@ sub routing { }); return undef; } - + $options{gorgone}->send_internal_message( identity => 'gorgone-autodiscovery', action => $options{action}, diff --git a/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm b/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm index aac8608661a..25636686c3a 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm @@ -66,7 +66,7 @@ sub routing { $legacycmd->{ready} = 1; return undef; } - + if (gorgone::class::core::waiting_ready(ready => \$legacycmd->{ready}) == 0) { gorgone::standard::library::add_history({ dbh => $options{dbh}, @@ -77,7 +77,7 @@ sub routing { }); return undef; } - + $options{gorgone}->send_internal_message( identity => 'gorgone-legacycmd', action => $options{action}, diff --git a/gorgone/gorgone/modules/core/cron/hooks.pm b/gorgone/gorgone/modules/core/cron/hooks.pm index da332ef42d2..f2aaa00711c 100644 --- a/gorgone/gorgone/modules/core/cron/hooks.pm +++ b/gorgone/gorgone/modules/core/cron/hooks.pm @@ -62,7 +62,7 @@ sub routing { $cron->{ready} = 1; return undef; } - + if (gorgone::class::core::waiting_ready(ready => \$cron->{ready}) == 0) { gorgone::standard::library::add_history({ dbh => $options{dbh}, @@ -73,7 +73,7 @@ sub routing { }); return undef; } - + $options{gorgone}->send_internal_message( identity => 'gorgone-cron', action => $options{action}, From 12196f4fabb7cb7eaaf2444ff3bd1bb5c5e69a21 Mon Sep 17 00:00:00 2001 From: sdepassio <114986849+sdepassio@users.noreply.github.com> Date: Fri, 10 Nov 2023 13:49:16 +0100 Subject: [PATCH 768/948] Revert "fix(gorgone): Process killed after a OOM - Deep recursion on subroutine" (#2587) --- gorgone/gorgone/class/core.pm | 109 ++++++++---------- .../modules/centreon/autodiscovery/hooks.pm | 4 +- .../modules/centreon/legacycmd/hooks.pm | 4 +- gorgone/gorgone/modules/core/cron/hooks.pm | 4 +- 4 files changed, 53 insertions(+), 68 deletions(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 86f33867979..3ac9e36f349 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -23,6 +23,7 @@ package gorgone::class::core; use strict; use warnings; use POSIX ":sys_wait_h"; +use Sys::Hostname; use MIME::Base64; use Crypt::Mode::CBC; use ZMQ::FFI qw(ZMQ_DONTWAIT ZMQ_SNDMORE); @@ -73,9 +74,7 @@ sub new { 'GET_/internal/information' => 'INFORMATION', 'POST_/internal/logger' => 'BCASTLOGGER', }; - - $self->{ievents} = []; - $self->{recursion_ievents} = 0; + $self->{config} = return $self; } @@ -270,8 +269,7 @@ sub init { $self->{hostname} = $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{hostname}; if (!defined($self->{hostname}) || $self->{hostname} eq '') { - my ($sysname, $nodename, $release, $version, $machine) = POSIX::uname(); - $self->{hostname} = $sysname; + $self->{hostname} = hostname(); } $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{proxy_name} = @@ -515,11 +513,17 @@ sub broadcast_core_key { ); } -sub decrypt_internal_message { +sub read_internal_message { my ($self, %options) = @_; + my ($identity, $frame) = gorgone::standard::library::zmq_read_message( + socket => $self->{internal_socket}, + logger => $self->{logger} + ); + return undef if (!defined($identity)); + if ($self->{internal_crypt}->{enabled} == 1) { - my $id = pack('H*', $options{identity}); + my $id = pack('H*', $identity); my $keys; if (defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_identity_keys}->{$id})) { $keys = [ $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_identity_keys}->{$id}->{key} ]; @@ -529,16 +533,16 @@ sub decrypt_internal_message { if (defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_core_oldkey})); } foreach my $key (@$keys) { - if ($options{frame}->decrypt({ cipher => $self->{cipher}, key => $key, iv => $self->{internal_crypt}->{iv} }) == 0) { - return 0; + if ($frame->decrypt({ cipher => $self->{cipher}, key => $key, iv => $self->{internal_crypt}->{iv} }) == 0) { + return ($identity, $frame); } } - $self->{logger}->writeLogError("[core] decrypt issue ($id): " . $options{frame}->getLastError()); - return 1; + $self->{logger}->writeLogError("[core] decrypt issue ($id): " . $frame->getLastError()); + return undef; } - return 0; + return ($identity, $frame); } sub send_internal_response { @@ -636,7 +640,7 @@ sub message_run { if ($self->{logger}->is_debug()) { my $frame_ref = $options->{frame}->getFrame(); - $self->{logger}->writeLogDebug('[core] Message received ' . $options->{router_type} . ' - ' . $$frame_ref); + $self->{logger}->writeLogDebug('[core] Message received - ' . $$frame_ref); } if ($options->{frame}->parse({ releaseFrame => 1 }) != 0) { return (undef, 1, { message => 'request not well formatted' }); @@ -710,46 +714,46 @@ sub message_run { ); return ($token, 0); } - + if ($action =~ /^(?:ADDLISTENER|PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID|SETMODULEKEY|SYNCLOGS|LOADMODULE|UNLOADMODULE|INFORMATION|GETTHUMBPRINT)$/) { my ($code, $response, $response_type) = $self->{internal_register}->{lc($action)}->( - gorgone => $self, + gorgone => $self, gorgone_config => $self->{config}->{configuration}->{gorgone}, - identity => $options->{identity}, - router_type => $options->{router_type}, - id => $self->{id}, - frame => $options->{frame}, - token => $token, - logger => $self->{logger} + identity => $options->{identity}, + router_type => $options->{router_type}, + id => $self->{id}, + frame => $options->{frame}, + token => $token, + logger => $self->{logger} ); if ($action =~ /^(?:CONSTATUS|INFORMATION|GETTHUMBPRINT)$/) { gorgone::standard::library::add_history({ - dbh => $self->{db_gorgone}, - code => $code, - token => $token, - data => $response, + dbh => $self->{db_gorgone}, + code => $code, + token => $token, + data => $response, json_encode => 1 }); } - + return ($token, $code, $response, $response_type); } elsif ($action =~ /^BCAST(.*)$/) { return (undef, 1, { message => "action '$action' is not known" }) if ($1 !~ /^(?:LOGGER|COREKEY)$/); $self->broadcast_run( action => $action, - frame => $options->{frame}, - token => $token + frame => $options->{frame}, + token => $token ); } else { $self->{modules_register}->{ $self->{modules_events}->{$action}->{module}->{package} }->{routing}->( - gorgone => $self, - dbh => $self->{db_gorgone}, - logger => $self->{logger}, - action => $action, - token => $token, - target => $target, - frame => $options->{frame}, + gorgone => $self, + dbh => $self->{db_gorgone}, + logger => $self->{logger}, + action => $action, + token => $token, + target => $target, + frame => $options->{frame}, hostname => $self->{hostname} ); } @@ -760,39 +764,25 @@ sub message_run { sub router_internal_event { my ($self, %options) = @_; - $self->{recursion_ievents}++; while ($self->{internal_socket}->has_pollin()) { - my ($identity, $frame) = gorgone::standard::library::zmq_read_message( - socket => $self->{internal_socket}, - logger => $self->{logger} - ); + my ($identity, $frame) = $self->read_internal_message(); next if (!defined($identity)); - push @{$self->{ievents}}, [ $identity, $frame ]; - } - - if ($self->{recursion_ievents} > 1) { - $self->{recursion_ievents}--; - return; - } - while (my $event = shift(@{$self->{ievents}})) { - next if ($self->decrypt_internal_message(identity => $event->[0], frame => $event->[1])); my ($token, $code, $response, $response_type) = $self->message_run( { - frame => $event->[1], - identity => $event->[0], + frame => $frame, + identity => $identity, router_type => 'internal' } ); $self->send_internal_response( - identity => $event->[0], + identity => $identity, response_type => $response_type, - data => $response, - code => $code, - token => $token + data => $response, + code => $code, + token => $token ); } - $self->{recursion_ievents}--; } sub is_handshake_done { @@ -1134,20 +1124,15 @@ sub stop_ev { sub waiting_ready { my (%options) = @_; - if (${$options{ready}} == 1) { - return 1 ; - } + return 1 if (${$options{ready}} == 1); my $iteration = 10; while ($iteration > 0) { my $watcher_timer = $gorgone->{loop}->timer(1, 0, \&stop_ev); - my $count_iteration = $gorgone->{loop}->iteration; - my $count_pending = $gorgone->{loop}->pending_count; $gorgone->{loop}->run(); if (${$options{ready}} == 1) { return 1; } - $iteration --; } return 0; diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm b/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm index f20befe507a..24a4b8007c3 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm @@ -68,7 +68,7 @@ sub routing { $autodiscovery->{ready} = 1; return undef; } - + if (gorgone::class::core::waiting_ready(ready => \$autodiscovery->{ready}) == 0) { gorgone::standard::library::add_history({ dbh => $options{dbh}, @@ -79,7 +79,7 @@ sub routing { }); return undef; } - + $options{gorgone}->send_internal_message( identity => 'gorgone-autodiscovery', action => $options{action}, diff --git a/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm b/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm index 25636686c3a..aac8608661a 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm @@ -66,7 +66,7 @@ sub routing { $legacycmd->{ready} = 1; return undef; } - + if (gorgone::class::core::waiting_ready(ready => \$legacycmd->{ready}) == 0) { gorgone::standard::library::add_history({ dbh => $options{dbh}, @@ -77,7 +77,7 @@ sub routing { }); return undef; } - + $options{gorgone}->send_internal_message( identity => 'gorgone-legacycmd', action => $options{action}, diff --git a/gorgone/gorgone/modules/core/cron/hooks.pm b/gorgone/gorgone/modules/core/cron/hooks.pm index f2aaa00711c..da332ef42d2 100644 --- a/gorgone/gorgone/modules/core/cron/hooks.pm +++ b/gorgone/gorgone/modules/core/cron/hooks.pm @@ -62,7 +62,7 @@ sub routing { $cron->{ready} = 1; return undef; } - + if (gorgone::class::core::waiting_ready(ready => \$cron->{ready}) == 0) { gorgone::standard::library::add_history({ dbh => $options{dbh}, @@ -73,7 +73,7 @@ sub routing { }); return undef; } - + $options{gorgone}->send_internal_message( identity => 'gorgone-cron', action => $options{action}, From 659aec0a11f31f778e8ae968f7b81ed77a104030 Mon Sep 17 00:00:00 2001 From: hamzabessa <148857497+hamzabessa@users.noreply.github.com> Date: Fri, 17 Nov 2023 15:26:09 +0100 Subject: [PATCH 769/948] enh(ci): package centreon-open-tickets using nfpm (#2607) Co-authored-by: Kevin Duret <kduret@centreon.com> --- gorgone/packaging/centreon-audit.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/gorgone/packaging/centreon-audit.yaml b/gorgone/packaging/centreon-audit.yaml index 852f0f85fda..ae0f8c96c62 100644 --- a/gorgone/packaging/centreon-audit.yaml +++ b/gorgone/packaging/centreon-audit.yaml @@ -3,4 +3,3 @@ gorgone: - name: audit package: "gorgone::modules::centreon::audit::hooks" enable: true - From a33384dccccdad606b076c560b7a9c6be5879ef2 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Mon, 20 Nov 2023 08:49:04 +0100 Subject: [PATCH 770/948] fix(gorgone): Process killed after a OOM - Deep recursion (#2592) Refs: MON-21972 --- gorgone/gorgone/class/core.pm | 115 +++++++++--------- .../modules/centreon/autodiscovery/hooks.pm | 4 +- .../modules/centreon/legacycmd/hooks.pm | 4 +- gorgone/gorgone/modules/core/cron/hooks.pm | 4 +- gorgone/gorgone/modules/core/proxy/hooks.pm | 5 +- 5 files changed, 67 insertions(+), 65 deletions(-) diff --git a/gorgone/gorgone/class/core.pm b/gorgone/gorgone/class/core.pm index 3ac9e36f349..b432de30721 100644 --- a/gorgone/gorgone/class/core.pm +++ b/gorgone/gorgone/class/core.pm @@ -1,5 +1,5 @@ -# -# Copyright 2019 Centreon (http://www.centreon.com/) +# +# Copyright 2023 Centreon (http://www.centreon.com/) # # Centreon is a full-fledged industry-strength solution that meets # the needs in IT infrastructure and application monitoring for @@ -23,7 +23,6 @@ package gorgone::class::core; use strict; use warnings; use POSIX ":sys_wait_h"; -use Sys::Hostname; use MIME::Base64; use Crypt::Mode::CBC; use ZMQ::FFI qw(ZMQ_DONTWAIT ZMQ_SNDMORE); @@ -74,7 +73,6 @@ sub new { 'GET_/internal/information' => 'INFORMATION', 'POST_/internal/logger' => 'BCASTLOGGER', }; - $self->{config} = return $self; } @@ -269,7 +267,8 @@ sub init { $self->{hostname} = $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{hostname}; if (!defined($self->{hostname}) || $self->{hostname} eq '') { - $self->{hostname} = hostname(); + my ($sysname, $nodename, $release, $version, $machine) = POSIX::uname(); + $self->{hostname} = $sysname; } $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{proxy_name} = @@ -513,17 +512,11 @@ sub broadcast_core_key { ); } -sub read_internal_message { +sub decrypt_internal_message { my ($self, %options) = @_; - my ($identity, $frame) = gorgone::standard::library::zmq_read_message( - socket => $self->{internal_socket}, - logger => $self->{logger} - ); - return undef if (!defined($identity)); - if ($self->{internal_crypt}->{enabled} == 1) { - my $id = pack('H*', $identity); + my $id = pack('H*', $options{identity}); my $keys; if (defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_identity_keys}->{$id})) { $keys = [ $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_identity_keys}->{$id}->{key} ]; @@ -533,16 +526,16 @@ sub read_internal_message { if (defined($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_core_oldkey})); } foreach my $key (@$keys) { - if ($frame->decrypt({ cipher => $self->{cipher}, key => $key, iv => $self->{internal_crypt}->{iv} }) == 0) { - return ($identity, $frame); + if ($options{frame}->decrypt({ cipher => $self->{cipher}, key => $key, iv => $self->{internal_crypt}->{iv} }) == 0) { + return 0; } } - $self->{logger}->writeLogError("[core] decrypt issue ($id): " . $frame->getLastError()); - return undef; + $self->{logger}->writeLogError("[core] decrypt issue ($id): " . $options{frame}->getLastError()); + return 1; } - return ($identity, $frame); + return 0; } sub send_internal_response { @@ -570,7 +563,6 @@ sub send_internal_response { $self->{internal_socket}->send(pack('H*', $options{identity}), ZMQ_DONTWAIT | ZMQ_SNDMORE); $self->{internal_socket}->send($message, ZMQ_DONTWAIT); - $self->router_internal_event(); } sub send_internal_message { @@ -598,7 +590,6 @@ sub send_internal_message { $self->{internal_socket}->send($options{identity}, ZMQ_DONTWAIT | ZMQ_SNDMORE); $self->{internal_socket}->send($message, ZMQ_DONTWAIT); - $self->router_internal_event() if (!defined($options{nosync})); } sub broadcast_run { @@ -640,7 +631,7 @@ sub message_run { if ($self->{logger}->is_debug()) { my $frame_ref = $options->{frame}->getFrame(); - $self->{logger}->writeLogDebug('[core] Message received - ' . $$frame_ref); + $self->{logger}->writeLogDebug('[core] Message received ' . $options->{router_type} . ' - ' . $$frame_ref); } if ($options->{frame}->parse({ releaseFrame => 1 }) != 0) { return (undef, 1, { message => 'request not well formatted' }); @@ -683,7 +674,7 @@ sub message_run { }); return ($token, 1, { message => 'gorgone is stopping/restarting. Cannot proceed request.' }); } - + # Check Routing if (defined($target)) { if (!defined($self->{modules_id}->{ $self->{config}->{configuration}->{gorgone}->{gorgonecore}->{proxy_name} }) || @@ -714,46 +705,46 @@ sub message_run { ); return ($token, 0); } - + if ($action =~ /^(?:ADDLISTENER|PUTLOG|GETLOG|KILL|PING|CONSTATUS|SETCOREID|SETMODULEKEY|SYNCLOGS|LOADMODULE|UNLOADMODULE|INFORMATION|GETTHUMBPRINT)$/) { my ($code, $response, $response_type) = $self->{internal_register}->{lc($action)}->( - gorgone => $self, + gorgone => $self, gorgone_config => $self->{config}->{configuration}->{gorgone}, - identity => $options->{identity}, - router_type => $options->{router_type}, - id => $self->{id}, - frame => $options->{frame}, - token => $token, - logger => $self->{logger} + identity => $options->{identity}, + router_type => $options->{router_type}, + id => $self->{id}, + frame => $options->{frame}, + token => $token, + logger => $self->{logger} ); if ($action =~ /^(?:CONSTATUS|INFORMATION|GETTHUMBPRINT)$/) { gorgone::standard::library::add_history({ - dbh => $self->{db_gorgone}, - code => $code, - token => $token, - data => $response, + dbh => $self->{db_gorgone}, + code => $code, + token => $token, + data => $response, json_encode => 1 }); } - + return ($token, $code, $response, $response_type); } elsif ($action =~ /^BCAST(.*)$/) { return (undef, 1, { message => "action '$action' is not known" }) if ($1 !~ /^(?:LOGGER|COREKEY)$/); $self->broadcast_run( action => $action, - frame => $options->{frame}, - token => $token + frame => $options->{frame}, + token => $token ); } else { $self->{modules_register}->{ $self->{modules_events}->{$action}->{module}->{package} }->{routing}->( - gorgone => $self, - dbh => $self->{db_gorgone}, - logger => $self->{logger}, - action => $action, - token => $token, - target => $target, - frame => $options->{frame}, + gorgone => $self, + dbh => $self->{db_gorgone}, + logger => $self->{logger}, + action => $action, + token => $token, + target => $target, + frame => $options->{frame}, hostname => $self->{hostname} ); } @@ -765,22 +756,29 @@ sub router_internal_event { my ($self, %options) = @_; while ($self->{internal_socket}->has_pollin()) { - my ($identity, $frame) = $self->read_internal_message(); + my ($identity, $frame) = gorgone::standard::library::zmq_read_message( + socket => $self->{internal_socket}, + logger => $self->{logger} + ); + next if (!defined($identity)); + next if ($self->decrypt_internal_message(identity => $identity, frame => $frame)); + my ($token, $code, $response, $response_type) = $self->message_run( { - frame => $frame, - identity => $identity, + frame => $frame, + identity => $identity, router_type => 'internal' } ); + $self->send_internal_response( - identity => $identity, + identity => $identity, response_type => $response_type, - data => $response, - code => $code, - token => $token + data => $response, + code => $code, + token => $token ); } } @@ -972,7 +970,7 @@ sub handshake { } # Maybe he want to redo a handshake - $rv = 0; + $rv = 0; } if ($rv == 0) { @@ -1085,7 +1083,7 @@ sub router_external_event { identity => $identity, cipher_infos => $cipher_infos, response_type => $response_type, - token => $token, + token => $token, code => $code, data => $response ); @@ -1124,7 +1122,9 @@ sub stop_ev { sub waiting_ready { my (%options) = @_; - return 1 if (${$options{ready}} == 1); + if (${$options{ready}} == 1) { + return 1; + } my $iteration = 10; while ($iteration > 0) { @@ -1133,6 +1133,7 @@ sub waiting_ready { if (${$options{ready}} == 1) { return 1; } + $iteration--; } return 0; @@ -1140,7 +1141,7 @@ sub waiting_ready { sub quit { my ($self, %options) = @_; - + $self->{logger}->writeLogInfo("[core] Quit main process"); if ($self->{config}->{configuration}->{gorgone}->{gorgonecore}->{internal_com_type} eq 'ipc') { @@ -1222,7 +1223,9 @@ sub check_exit_modules { sub periodic_exec { $gorgone->check_exit_modules(); $gorgone->{listener}->check(); + $gorgone->router_internal_event(); + if (defined($gorgone->{external_socket})) { $gorgone->router_external_event(); } @@ -1308,7 +1311,9 @@ sub run { $gorgone->{loop} = new EV::Loop(); $gorgone->{watcher_timer} = $gorgone->{loop}->timer(5, 5, \&periodic_exec); + $gorgone->{watcher_io_internal} = $gorgone->{loop}->io($gorgone->{internal_socket}->get_fd(), EV::READ, sub { $gorgone->router_internal_event() }); + if (defined($gorgone->{external_socket})) { $gorgone->{watcher_io_external} = $gorgone->{loop}->io($gorgone->{external_socket}->get_fd(), EV::READ, sub { $gorgone->router_external_event() }); } diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm b/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm index 24a4b8007c3..f20befe507a 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm @@ -68,7 +68,7 @@ sub routing { $autodiscovery->{ready} = 1; return undef; } - + if (gorgone::class::core::waiting_ready(ready => \$autodiscovery->{ready}) == 0) { gorgone::standard::library::add_history({ dbh => $options{dbh}, @@ -79,7 +79,7 @@ sub routing { }); return undef; } - + $options{gorgone}->send_internal_message( identity => 'gorgone-autodiscovery', action => $options{action}, diff --git a/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm b/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm index aac8608661a..25636686c3a 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/hooks.pm @@ -66,7 +66,7 @@ sub routing { $legacycmd->{ready} = 1; return undef; } - + if (gorgone::class::core::waiting_ready(ready => \$legacycmd->{ready}) == 0) { gorgone::standard::library::add_history({ dbh => $options{dbh}, @@ -77,7 +77,7 @@ sub routing { }); return undef; } - + $options{gorgone}->send_internal_message( identity => 'gorgone-legacycmd', action => $options{action}, diff --git a/gorgone/gorgone/modules/core/cron/hooks.pm b/gorgone/gorgone/modules/core/cron/hooks.pm index da332ef42d2..f2aaa00711c 100644 --- a/gorgone/gorgone/modules/core/cron/hooks.pm +++ b/gorgone/gorgone/modules/core/cron/hooks.pm @@ -62,7 +62,7 @@ sub routing { $cron->{ready} = 1; return undef; } - + if (gorgone::class::core::waiting_ready(ready => \$cron->{ready}) == 0) { gorgone::standard::library::add_history({ dbh => $options{dbh}, @@ -73,7 +73,7 @@ sub routing { }); return undef; } - + $options{gorgone}->send_internal_message( identity => 'gorgone-cron', action => $options{action}, diff --git a/gorgone/gorgone/modules/core/proxy/hooks.pm b/gorgone/gorgone/modules/core/proxy/hooks.pm index 15bd57869b6..1319abad40e 100644 --- a/gorgone/gorgone/modules/core/proxy/hooks.pm +++ b/gorgone/gorgone/modules/core/proxy/hooks.pm @@ -332,12 +332,9 @@ sub routing { action => $action, raw_data_ref => $raw_data_ref, token => $options{token}, - target => $target_complete, - nosync => 1 + target => $target_complete ); } - - $options{gorgone}->router_internal_event(); } sub gently { From 3468f4052ee0d97a36296b046350d19cc0630ef0 Mon Sep 17 00:00:00 2001 From: hamzabessa <148857497+hamzabessa@users.noreply.github.com> Date: Wed, 22 Nov 2023 08:56:35 +0100 Subject: [PATCH 771/948] enh(ci): Move Centreon Gorgone packaging to nfpm (#2492) Co-authored-by: Kevin Duret <kduret@centreon.com> --- .github/workflows/gorgone.yml | 69 ++++-- gorgone/config/systemd/gorgoned.deb.service | 33 +++ ...{gorgoned-service => gorgoned.rpm.service} | 0 .../centreon-gorgone-centreon-config.yaml | 61 ++++++ .../packaging/centreon-gorgone-selinux.yaml | 41 ++++ .../packaging/centreon-gorgone.spectemplate | 194 ----------------- gorgone/packaging/centreon-gorgone.yaml | 198 ++++++++++++++++++ .../{ => configuration}/centreon-api.yaml | 0 .../{ => configuration}/centreon-audit.yaml | 0 .../{ => configuration}/centreon.yaml | 0 .../packaging/{ => configuration}/config.yaml | 0 .../packaging/debian/centreon-gorgone.dirs | 6 - .../packaging/debian/centreon-gorgone.install | 15 -- .../debian/centreon-gorgone.logrotate | 10 - .../debian/centreon-gorgone.postinst | 44 ---- .../packaging/debian/centreon-gorgone.preinst | 20 -- gorgone/packaging/debian/control | 45 ---- gorgone/packaging/debian/copyright | 29 --- gorgone/packaging/debian/rules | 15 -- gorgone/packaging/debian/source/format | 1 - gorgone/packaging/debian/substvars | 2 - ...eon-gorgone-centreon-config-postinstall.sh | 63 ++++++ .../scripts/centreon-gorgone-postinstall.sh | 31 +++ .../scripts/centreon-gorgone-preinstall.sh | 10 + .../scripts/centreon-gorgone-preremove.sh | 3 + .../centreon-gorgone-selinux-postinstall.sh | 25 +++ .../centreon-gorgone-selinux-preremove.sh | 5 + 27 files changed, 523 insertions(+), 397 deletions(-) create mode 100644 gorgone/config/systemd/gorgoned.deb.service rename gorgone/config/systemd/{gorgoned-service => gorgoned.rpm.service} (100%) create mode 100644 gorgone/packaging/centreon-gorgone-centreon-config.yaml create mode 100644 gorgone/packaging/centreon-gorgone-selinux.yaml delete mode 100644 gorgone/packaging/centreon-gorgone.spectemplate create mode 100644 gorgone/packaging/centreon-gorgone.yaml rename gorgone/packaging/{ => configuration}/centreon-api.yaml (100%) rename gorgone/packaging/{ => configuration}/centreon-audit.yaml (100%) rename gorgone/packaging/{ => configuration}/centreon.yaml (100%) rename gorgone/packaging/{ => configuration}/config.yaml (100%) delete mode 100644 gorgone/packaging/debian/centreon-gorgone.dirs delete mode 100755 gorgone/packaging/debian/centreon-gorgone.install delete mode 100644 gorgone/packaging/debian/centreon-gorgone.logrotate delete mode 100755 gorgone/packaging/debian/centreon-gorgone.postinst delete mode 100755 gorgone/packaging/debian/centreon-gorgone.preinst delete mode 100644 gorgone/packaging/debian/control delete mode 100644 gorgone/packaging/debian/copyright delete mode 100755 gorgone/packaging/debian/rules delete mode 100644 gorgone/packaging/debian/source/format delete mode 100644 gorgone/packaging/debian/substvars create mode 100644 gorgone/packaging/scripts/centreon-gorgone-centreon-config-postinstall.sh create mode 100644 gorgone/packaging/scripts/centreon-gorgone-postinstall.sh create mode 100644 gorgone/packaging/scripts/centreon-gorgone-preinstall.sh create mode 100644 gorgone/packaging/scripts/centreon-gorgone-preremove.sh create mode 100644 gorgone/packaging/scripts/centreon-gorgone-selinux-postinstall.sh create mode 100644 gorgone/packaging/scripts/centreon-gorgone-selinux-preremove.sh diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index c89c7c8ba86..13770deda95 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -25,7 +25,7 @@ jobs: get-version: uses: ./.github/workflows/get-version.yml with: - version_file: centreon-gorgone/packaging/centreon-gorgone.spectemplate + version_file: centreon-gorgone/.version veracode-analysis: needs: [get-version] @@ -60,21 +60,58 @@ jobs: image: packaging-bullseye distrib: bullseye - uses: ./.github/workflows/package.yml - with: - base_directory: centreon-gorgone - spec_file: centreon-gorgone/packaging/centreon-gorgone.spectemplate - package_extension: ${{ matrix.package_extension }} - image_name: ${{ matrix.image }} - module_name: centreon-gorgone - major_version: ${{ needs.get-version.outputs.major_version }} - minor_version: ${{ needs.get-version.outputs.minor_version }} - release: ${{ needs.get-version.outputs.release }} - commit_hash: ${{ github.sha }} - cache_key: ${{ github.sha }}-${{ github.run_id }}-${{ matrix.package_extension }}-${{ matrix.distrib }} - secrets: - registry_username: ${{ secrets.DOCKER_REGISTRY_ID }} - registry_password: ${{ secrets.DOCKER_REGISTRY_PASSWD }} + runs-on: ubuntu-22.04 + + container: + image: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }}/${{ matrix.image }}:${{ needs.get-version.outputs.major_version }} + credentials: + username: ${{ secrets.DOCKER_REGISTRY_ID }} + password: ${{ secrets.DOCKER_REGISTRY_PASSWD }} + + name: package ${{ matrix.distrib }} + + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: Set package version and paths according to distrib + run: | + if [ "${{ matrix.distrib }}" = "bullseye" ]; then + PERL_VENDORLIB="/usr/share/perl5" + else + PERL_VENDORLIB="/usr/share/perl5/vendor_perl" + fi + echo "PERL_VENDORLIB=$PERL_VENDORLIB" >> $GITHUB_ENV + shell: bash + + - name: Generate selinux binaries + if: ${{ matrix.package_extension == 'rpm' }} + run: | + cd centreon-gorgone/selinux + sed -i "s/@VERSION@/${{ needs.get-version.outputs.major_version }}.${{ needs.get-version.outputs.minor_version }}/g" centreon-gorgoned.te + make -f /usr/share/selinux/devel/Makefile + shell: bash + + - name: Remove selinux packaging files on debian + if: ${{ matrix.package_extension == 'deb' }} + run: rm -f centreon-gorgone/packaging/*-selinux.yaml + shell: bash + + - name: Package + uses: ./.github/actions/package-nfpm + with: + nfpm_file_pattern: "centreon-gorgone/packaging/*.yaml" + distrib: ${{ matrix.distrib }} + package_extension: ${{ matrix.package_extension }} + major_version: ${{ needs.get-version.outputs.major_version }} + minor_version: ${{ needs.get-version.outputs.minor_version }} + release: ${{ needs.get-version.outputs.release }} + arch: all + commit_hash: ${{ github.sha }} + cache_key: cache-${{ github.sha }}-${{ matrix.package_extension}}-centreon-gorgone-${{ matrix.distrib }}-${{ github.head_ref || github.ref_name }} + rpm_gpg_key: ${{ secrets.RPM_GPG_SIGNING_KEY }} + rpm_gpg_signing_key_id: ${{ secrets.RPM_GPG_SIGNING_KEY_ID }} + rpm_gpg_signing_passphrase: ${{ secrets.RPM_GPG_SIGNING_PASSPHRASE }} deliver-sources: runs-on: [self-hosted, common] diff --git a/gorgone/config/systemd/gorgoned.deb.service b/gorgone/config/systemd/gorgoned.deb.service new file mode 100644 index 00000000000..46aef41c175 --- /dev/null +++ b/gorgone/config/systemd/gorgoned.deb.service @@ -0,0 +1,33 @@ +## +## Copyright 2019-2020 Centreon +## +## Licensed under the Apache License, Version 2.0 (the "License"); +## you may not use this file except in compliance with the License. +## You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. +## +## For more information : contact@centreon.com +## + +[Unit] +Description=Centreon Gorgone +PartOf=centreon.service +After=centreon.service +ReloadPropagatedFrom=centreon.service + +[Service] +EnvironmentFile=/etc/default/gorgoned +ExecStart=/usr/bin/perl /usr/bin/gorgoned $OPTIONS +Type=simple +User=centreon-gorgone + +[Install] +WantedBy=multi-user.target +WantedBy=centreon.service diff --git a/gorgone/config/systemd/gorgoned-service b/gorgone/config/systemd/gorgoned.rpm.service similarity index 100% rename from gorgone/config/systemd/gorgoned-service rename to gorgone/config/systemd/gorgoned.rpm.service diff --git a/gorgone/packaging/centreon-gorgone-centreon-config.yaml b/gorgone/packaging/centreon-gorgone-centreon-config.yaml new file mode 100644 index 00000000000..f48e6d2f276 --- /dev/null +++ b/gorgone/packaging/centreon-gorgone-centreon-config.yaml @@ -0,0 +1,61 @@ +name: "centreon-gorgone-centreon-config" +arch: "${ARCH}" +platform: "linux" +version_schema: "none" +version: "${VERSION}" +release: "${RELEASE}${DIST}" +section: "default" +priority: "optional" +maintainer: "Centreon <contact@centreon.com>" +description: | + Configure Centreon Gorgone for use with Centreon Web + Commit: @COMMIT_HASH@ +vendor: "Centreon" +homepage: "https://www.centreon.com" +license: "Apache-2.0" + +contents: + - src: "./configuration/centreon.yaml" + dst: "/etc/centreon-gorgone/config.d/30-centreon.yaml" + type: config|noreplace + file_info: + owner: centreon-gorgone + group: centreon-gorgone + mode: 0664 + + - src: "./configuration/centreon-api.yaml" + dst: "/etc/centreon-gorgone/config.d/centreon-api.yaml" + file_info: + owner: centreon-gorgone + group: centreon-gorgone + mode: 0664 + + - src: "./configuration/centreon-audit.yaml" + dst: "/etc/centreon-gorgone/config.d/50-centreon-audit.yaml" + file_info: + owner: centreon-gorgone + group: centreon-gorgone + mode: 0664 + + - dst: "/var/cache/centreon-gorgone/autodiscovery" + type: dir + file_info: + owner: centreon-gorgone + group: centreon-gorgone + +scripts: + postinstall: ./scripts/centreon-gorgone-centreon-config-postinstall.sh + +overrides: + rpm: + depends: + - centreon-gorgone = ${VERSION}-${RELEASE}${DIST} + deb: + depends: + - centreon-gorgone (= ${VERSION}-${RELEASE}${DIST}) + +rpm: + summary: Configure Centreon Gorgone for use with Centreon Web + signature: + key_file: ${RPM_SIGNING_KEY_FILE} + key_id: ${RPM_SIGNING_KEY_ID} diff --git a/gorgone/packaging/centreon-gorgone-selinux.yaml b/gorgone/packaging/centreon-gorgone-selinux.yaml new file mode 100644 index 00000000000..98d1975e1e8 --- /dev/null +++ b/gorgone/packaging/centreon-gorgone-selinux.yaml @@ -0,0 +1,41 @@ +name: "centreon-gorgone-selinux" +arch: "${ARCH}" +platform: "linux" +version_schema: "none" +version: "${VERSION}" +release: "${RELEASE}${DIST}" +section: "default" +priority: "optional" +maintainer: "Centreon <contact@centreon.com>" +description: | + Selinux for centreon-gorgone + Commit: @COMMIT_HASH@ +vendor: "Centreon" +homepage: "https://www.centreon.com" +license: "Apache-2.0" + +depends: + - policycoreutils + - centreon-common-selinux +replaces: + - centreon-gorgone-selinux-debuginfo +conflicts: + - centreon-gorgone-selinux-debuginfo +provides: + - centreon-gorgone-selinux-debuginfo + +contents: + - src: "../selinux/centreon-gorgoned.pp" + dst: "/usr/share/selinux/packages/centreon/centreon-gorgoned.pp" + file_info: + mode: 0655 + +scripts: + postinstall: ./scripts/centreon-gorgone-selinux-postinstall.sh + preremove: ./scripts/centreon-gorgone-selinux-preremove.sh + +rpm: + summary: Selinux for centreon-gorgone + signature: + key_file: ${RPM_SIGNING_KEY_FILE} + key_id: ${RPM_SIGNING_KEY_ID} diff --git a/gorgone/packaging/centreon-gorgone.spectemplate b/gorgone/packaging/centreon-gorgone.spectemplate deleted file mode 100644 index 4dad080dda5..00000000000 --- a/gorgone/packaging/centreon-gorgone.spectemplate +++ /dev/null @@ -1,194 +0,0 @@ -%define selinuxdevel %{_datadir}/selinux/devel - -Name: centreon-gorgone -Version: 24.04.0 -Release: %{PACKAGE_RELEASE}%{?dist} -Summary: Perl daemon task handlers -Group: Applications/System -License: Apache-2.0 -URL: http://www.centreon.com -Source0: %{name}-%{version}.tar.gz -BuildArch: noarch -BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) - -BuildRequires: perl-devel - -Requires: bzip2 -Requires: perl-Libssh-Session >= 0.8 -Requires: perl-CryptX -Requires: perl-Mojolicious -Requires: perl(Archive::Tar) -Requires: perl(Schedule::Cron) -Requires: perl(ZMQ::FFI) -Requires: perl(EV) -Requires: perl(JSON::XS) -Requires: perl(JSON::PP) -Requires: perl(XML::Simple) -Requires: perl(XML::LibXML::Simple) -Requires: perl(Net::SMTP) -Requires: perl(YAML::XS) -Requires: perl(DBD::SQLite) -Requires: perl(DBD::mysql) -Requires: perl(DBI) -Requires: perl(UUID) -Requires: perl(HTTP::Daemon) -Requires: perl(HTTP::Status) -Requires: perl(MIME::Base64) -Requires: perl(Digest::MD5::File) -Requires: perl(Net::Curl::Easy) -Requires: perl(HTTP::Daemon::SSL) -Requires: perl(NetAddr::IP) -Requires: perl(Hash::Merge) -Requires: perl(Clone) -Requires: perl(Sys::Syslog) -Requires: perl(DateTime) -Requires: perl(Try::Tiny) -Requires: tar -AutoReqProv: no - -%description -%{COMMIT_HASH} - -%package centreon-config -Summary: Configure Centreon Gorgone for use with Centreon Web -Group: Networking/Other -License: Apache-2.0 -Requires: centreon-common -Requires: centreon-gorgone - -%description centreon-config -%{COMMIT_HASH} - -%package -n centreon-gorgoned-selinux -Summary: SELinux context for centreon-gorgoned -Group: Networking/Other -License: Apache-2.0 -Requires: policycoreutils -Requires: centreon-common-selinux - -%description -n centreon-gorgoned-selinux -%{COMMIT_HASH} -SElinux context for centreon-gorgoned. - -%prep -%setup -q -n %{name}-%{version} - -%build - -sed -i 's#@VERSION@#%{version}#' centreon-gorgone/selinux/* -(cd centreon-gorgone/selinux && %{__make} -f %{selinuxdevel}/Makefile) - -%install -rm -rf %{buildroot} -mkdir -p %{buildroot}/%{perl_vendorlib}/gorgone -%{__install} -d %{buildroot}%{_bindir} -%{__install} -d %{buildroot}%{_usr}/local/bin/ -%{__install} -d %{buildroot}%{_sysconfdir}/systemd/system/ -%{__install} -d %buildroot%{_localstatedir}/lib/centreon-gorgone -%{__install} -d %buildroot%{_localstatedir}/log/centreon-gorgone -%{__install} -d %buildroot%{_localstatedir}/cache/centreon-gorgone -%{__cp} centreon-gorgone/config/systemd/gorgoned-service %{buildroot}%{_sysconfdir}/systemd/system/gorgoned.service -%{__install} -d %{buildroot}%{_sysconfdir}/sysconfig -%{__cp} centreon-gorgone/config/systemd/gorgoned-sysconfig %{buildroot}%{_sysconfdir}/sysconfig/gorgoned -%{__install} -d %{buildroot}%{_sysconfdir}/logrotate.d -%{__cp} centreon-gorgone/config/logrotate/gorgoned %{buildroot}%{_sysconfdir}/logrotate.d/gorgoned - -%{__cp} -R centreon-gorgone/gorgone/* %{buildroot}/%{perl_vendorlib}/gorgone/ -%{__cp} centreon-gorgone/gorgoned %{buildroot}%{_bindir}/ -%{__cp} centreon-gorgone/contrib/gorgone_config_init.pl %{buildroot}%{_usr}/local/bin/ -%{__cp} centreon-gorgone/contrib/gorgone_audit.pl %{buildroot}%{_usr}/local/bin/ -%{__cp} centreon-gorgone/contrib/gorgone_install_plugins.pl %{buildroot}%{_usr}/local/bin/ -%{__cp} centreon-gorgone/contrib/gorgone_key_thumbprint.pl %{buildroot}%{_usr}/local/bin/ - -%{__install} -d %{buildroot}%{_sysconfdir}/centreon-gorgone -%{__install} -d %{buildroot}%{_sysconfdir}/centreon-gorgone/config.d/ -%{__install} -d %{buildroot}%{_sysconfdir}/centreon-gorgone/config.d/cron.d/ -%{__install} -d %buildroot%{_localstatedir}/cache/centreon-gorgone/autodiscovery -%{__cp} centreon-gorgone/packaging/config.yaml %{buildroot}%{_sysconfdir}/centreon-gorgone/ -%{__cp} centreon-gorgone/packaging/centreon.yaml %{buildroot}%{_sysconfdir}/centreon-gorgone/config.d/30-centreon.yaml -%{__cp} centreon-gorgone/packaging/centreon-api.yaml %{buildroot}%{_sysconfdir}/centreon-gorgone/config.d/31-centreon-api.yaml -%{__cp} centreon-gorgone/packaging/centreon-audit.yaml %{buildroot}%{_sysconfdir}/centreon-gorgone/config.d/50-centreon-audit.yaml - -%{__install} -d %buildroot%{_sysconfdir}/sudoers.d/ -%{__cp} centreon-gorgone/packaging/sudoers.d/centreon-gorgone %buildroot%{_sysconfdir}/sudoers.d/centreon-gorgone - -%{__mkdir} -p %buildroot%{_datadir}/selinux/packages/centreon -%{__install} -m 655 centreon-gorgone/selinux/centreon-gorgoned.pp %buildroot%{_datadir}/selinux/packages/centreon/centreon-gorgoned.pp - -%{_fixperms} $RPM_BUILD_ROOT/* - -%check - -%clean -rm -rf %{buildroot} - -%files -%defattr(-, root, root, -) -%{perl_vendorlib}/gorgone/ -%attr(755, root, root) %{_bindir}/gorgoned -%attr(755, root, root) %{_usr}/local/bin/gorgone_config_init.pl -%attr(755, root, root) %{_usr}/local/bin/gorgone_audit.pl -%attr(750, root, root) %{_usr}/local/bin/gorgone_install_plugins.pl -%attr(750, root, root) %{_usr}/local/bin/gorgone_key_thumbprint.pl -%attr(755, root, root) %{_sysconfdir}/systemd/system/gorgoned.service -%config(noreplace) %{_sysconfdir}/sysconfig/gorgoned -%config(noreplace) %{_sysconfdir}/logrotate.d/gorgoned - -%defattr(0664, centreon-gorgone, centreon-gorgone, 0775) -%dir %{_sysconfdir}/centreon-gorgone -%dir %{_sysconfdir}/centreon-gorgone/config.d -%dir %{_sysconfdir}/centreon-gorgone/config.d/cron.d -%{_sysconfdir}/centreon-gorgone/config.yaml -%defattr(-, centreon-gorgone, centreon-gorgone, -) -%{_localstatedir}/lib/centreon-gorgone -%{_localstatedir}/log/centreon-gorgone -%{_localstatedir}/cache/centreon-gorgone - -%pre -%{_bindir}/getent group centreon-gorgone &>/dev/null || %{_sbindir}/groupadd -r centreon-gorgone -%{_bindir}/getent passwd centreon-gorgone &>/dev/null || %{_sbindir}/useradd -g centreon-gorgone -m -d %{_localstatedir}/lib/centreon-gorgone -r centreon-gorgone 2> /dev/null - -%files centreon-config -%defattr(0664, centreon-gorgone, centreon-gorgone, 0775) -%config(noreplace) %{_sysconfdir}/centreon-gorgone/config.d/30-centreon.yaml -%config(noreplace) %{_sysconfdir}/centreon-gorgone/config.d/31-centreon-api.yaml -%config(noreplace) %{_sysconfdir}/centreon-gorgone/config.d/50-centreon-audit.yaml - -%attr(0600, root, root) %{_sysconfdir}/sudoers.d/centreon-gorgone - -%defattr(-, centreon-gorgone, centreon-gorgone, -) -%{_localstatedir}/cache/centreon-gorgone/autodiscovery - -%post centreon-config -%{_bindir}/getent passwd centreon &>/dev/null && %{_sbindir}/usermod -a -G centreon-gorgone centreon 2> /dev/null -%{_bindir}/getent passwd centreon-engine &>/dev/null && %{_sbindir}/usermod -a -G centreon-gorgone centreon-engine 2> /dev/null -%{_bindir}/getent passwd centreon-broker &>/dev/null && %{_sbindir}/usermod -a -G centreon-gorgone centreon-broker 2> /dev/null -%{_bindir}/getent passwd centreon-gorgone &>/dev/null && %{_sbindir}/usermod -a -G centreon centreon-gorgone 2> /dev/null -if [ \( \! -d %{_localstatedir}/lib/centreon-gorgone/.ssh \) -a \( -d %{_localstatedir}/spool/centreon/.ssh \) ] ; then - %{__cp} -r %{_localstatedir}/spool/centreon/.ssh %{_localstatedir}/lib/centreon-gorgone/.ssh - %{__chown} -R centreon-gorgone:centreon-gorgone %{_localstatedir}/lib/centreon-gorgone/.ssh - %{__chmod} 600 %{_localstatedir}/lib/centreon-gorgone/.ssh/id_rsa -fi - -%post -%systemd_post gorgoned.service || : - -%preun -%systemd_preun gorgoned.service || : - -%postun -%systemd_postun_with_restart gorgoned.service || : - -%files -n centreon-gorgoned-selinux -%defattr(-, root, root, -) -%{_datadir}/selinux/packages/centreon/centreon-gorgoned.pp - -%post -n centreon-gorgoned-selinux -semodule -i %{_datadir}/selinux/packages/centreon/centreon-gorgoned.pp > /dev/null 2>&1 || : - -%preun -n centreon-gorgoned-selinux -if [ "$1" -lt "1" ]; then # Final removal - semodule -r centreon-gorgoned > /dev/null 2>&1 || : -fi - -%changelog diff --git a/gorgone/packaging/centreon-gorgone.yaml b/gorgone/packaging/centreon-gorgone.yaml new file mode 100644 index 00000000000..7870fc3917e --- /dev/null +++ b/gorgone/packaging/centreon-gorgone.yaml @@ -0,0 +1,198 @@ +name: "centreon-gorgone" +arch: "${ARCH}" +platform: "linux" +version_schema: "none" +version: "${VERSION}" +release: "${RELEASE}${DIST}" +section: "default" +priority: "optional" +maintainer: "Centreon <contact@centreon.com>" +description: | + Centreon gorgone daemon + Commit: @COMMIT_HASH@ +vendor: "Centreon" +homepage: "https://www.centreon.com" +license: "Apache-2.0" + +contents: + - dst: "/etc/centreon-gorgone" + type: dir + file_info: + owner: centreon-gorgone + group: centreon-gorgone + mode: 0775 + + - dst: "/etc/centreon-gorgone/config.d" + type: dir + file_info: + owner: centreon-gorgone + group: centreon-gorgone + mode: 0775 + + - dst: "/etc/centreon-gorgone/config.d/cron.d" + type: dir + file_info: + owner: centreon-gorgone + group: centreon-gorgone + mode: 0775 + + - src: "./configuration/config.yaml" + dst: "/etc/centreon-gorgone/" + file_info: + owner: centreon-gorgone + group: centreon-gorgone + mode: 0664 + + - dst: "/var/lib/centreon-gorgone" + type: dir + file_info: + owner: centreon-gorgone + group: centreon-gorgone + mode: 0775 + + - dst: "/var/log/centreon-gorgone" + type: dir + file_info: + owner: centreon-gorgone + group: centreon-gorgone + mode: 0775 + + - dst: "/var/cache/centreon-gorgone" + type: dir + file_info: + owner: centreon-gorgone + group: centreon-gorgone + mode: 0775 + + - src: "./sudoers.d/centreon-gorgone" + dst: "/etc/sudoers.d/centreon-gorgone" + file_info: + mode: 0600 + + - src: "../config/systemd/gorgoned.rpm.service" + dst: "/etc/systemd/system/gorgoned.service" + file_info: + mode: 0755 + packager: rpm + - src: "../config/systemd/gorgoned.deb.service" + dst: "/lib/systemd/system/gorgoned.service" + file_info: + mode: 0755 + packager: deb + + - src: "../config/systemd/gorgoned-sysconfig" + dst: "/etc/sysconfig/gorgoned" + type: config|noreplace + packager: rpm + - src: "../config/systemd/gorgoned-sysconfig" + dst: "/etc/default/gorgoned" + type: config|noreplace + packager: deb + + - src: "../config/logrotate/gorgoned" + dst: "/etc/logrotate.d/gorgoned" + type: config|noreplace + + - src: "../gorgoned" + dst: "/usr/bin/gorgoned" + file_info: + mode: 0755 + + - src: "../gorgone" + dst: "${PERL_VENDORLIB}/gorgone" + expand: true + + - src: "../contrib/gorgone_config_init.pl" + dst: "/usr/local/bin/" + file_info: + mode: 0755 + + - src: "../contrib/gorgone_audit.pl" + dst: "/usr/local/bin/" + file_info: + mode: 0755 + + - src: "../contrib/gorgone_install_plugins.pl" + dst: "/usr/local/bin/" + file_info: + mode: 0750 + + - src: "../contrib/gorgone_key_thumbprint.pl" + dst: "/usr/local/bin/" + file_info: + mode: 0750 + +scripts: + preinstall: ./scripts/centreon-gorgone-preinstall.sh + postinstall: ./scripts/centreon-gorgone-postinstall.sh + preremove: ./scripts/centreon-gorgone-preremove.sh + +overrides: + rpm: + depends: + - centreon-common + - bzip2 + - perl-Libssh-Session >= 0.8 + - perl-CryptX + - perl-Mojolicious + - perl(Archive::Tar) + - perl(Schedule::Cron) + - perl(ZMQ::FFI) + - perl(EV) + - perl(JSON::XS) + - perl(JSON::PP) + - perl(XML::Simple) + - perl(XML::LibXML::Simple) + - perl(Net::SMTP) + - perl(YAML::XS) + - perl(DBD::SQLite) + - perl(DBD::mysql) + - perl(DBI) + - perl(UUID) + - perl(HTTP::Daemon) + - perl(HTTP::Status) + - perl(MIME::Base64) + - perl(Digest::MD5::File) + - perl(Net::Curl::Easy) + - perl(HTTP::Daemon::SSL) + - perl(NetAddr::IP) + - perl(Hash::Merge) + - perl(Clone) + - perl(Sys::Syslog) + - perl(DateTime) + - perl(Try::Tiny) + - tar + deb: + depends: # those dependencies are taken from centreon-gorgone/packaging/debian/control + - centreon-common + - libdatetime-perl + - libtime-parsedate-perl + - libtry-tiny-perl + - libxml-simple-perl + - libxml-libxml-simple-perl + - libdigest-md5-file-perl + - libjson-pp-perl + - libjson-xs-perl + - libyaml-libyaml-perl + - libdbi-perl + - libdbd-sqlite3-perl + - libdbd-mysql-perl + - libhttp-daemon-perl + - libhttp-daemon-ssl-perl + - libnetaddr-ip-perl + - libschedule-cron-perl + - libhash-merge-perl + - libcryptx-perl + - libmojolicious-perl + - libauthen-simple-perl + - libauthen-simple-net-perl + - libnet-curl-perl + - libssh-session-perl + - libev-perl + - libzmq-ffi-perl + +rpm: + summary: Centreon gorgone daemon + signature: + key_file: ${RPM_SIGNING_KEY_FILE} + key_id: ${RPM_SIGNING_KEY_ID} diff --git a/gorgone/packaging/centreon-api.yaml b/gorgone/packaging/configuration/centreon-api.yaml similarity index 100% rename from gorgone/packaging/centreon-api.yaml rename to gorgone/packaging/configuration/centreon-api.yaml diff --git a/gorgone/packaging/centreon-audit.yaml b/gorgone/packaging/configuration/centreon-audit.yaml similarity index 100% rename from gorgone/packaging/centreon-audit.yaml rename to gorgone/packaging/configuration/centreon-audit.yaml diff --git a/gorgone/packaging/centreon.yaml b/gorgone/packaging/configuration/centreon.yaml similarity index 100% rename from gorgone/packaging/centreon.yaml rename to gorgone/packaging/configuration/centreon.yaml diff --git a/gorgone/packaging/config.yaml b/gorgone/packaging/configuration/config.yaml similarity index 100% rename from gorgone/packaging/config.yaml rename to gorgone/packaging/configuration/config.yaml diff --git a/gorgone/packaging/debian/centreon-gorgone.dirs b/gorgone/packaging/debian/centreon-gorgone.dirs deleted file mode 100644 index 326d08ce2ea..00000000000 --- a/gorgone/packaging/debian/centreon-gorgone.dirs +++ /dev/null @@ -1,6 +0,0 @@ -etc/centreon-gorgone/config.d -etc/centreon-gorgone/config.d/cron.d -var/cache/centreon-gorgone -var/cache/centreon-gorgone/autodiscovery -var/lib/centreon-gorgone -var/log/centreon-gorgone diff --git a/gorgone/packaging/debian/centreon-gorgone.install b/gorgone/packaging/debian/centreon-gorgone.install deleted file mode 100755 index 70abee6422f..00000000000 --- a/gorgone/packaging/debian/centreon-gorgone.install +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/dh-exec - -gorgoned usr/bin -contrib/* usr/local/bin -packaging/config.yaml etc/centreon-gorgone -packaging/centreon-api.yaml etc/centreon-gorgone/config.d -packaging/sudoers.d/centreon-gorgone etc/sudoers.d -gorgone/class/* usr/share/perl5/gorgone/class -gorgone/modules/* usr/share/perl5/gorgone/modules -gorgone/standard/* usr/share/perl5/gorgone/standard - -packaging/centreon.yaml => etc/centreon-gorgone/config.d/30-centreon.yaml -packaging/centreon-audit.yaml => etc/centreon-gorgone/config.d/50-centreon-audit.yaml -config/systemd/gorgoned-service => lib/systemd/system/gorgoned.service -config/systemd/gorgoned-sysconfig => etc/default/gorgoned diff --git a/gorgone/packaging/debian/centreon-gorgone.logrotate b/gorgone/packaging/debian/centreon-gorgone.logrotate deleted file mode 100644 index e6f56b7475f..00000000000 --- a/gorgone/packaging/debian/centreon-gorgone.logrotate +++ /dev/null @@ -1,10 +0,0 @@ -/var/log/centreon-gorgone/gorgoned.log { - copytruncate - weekly - rotate 52 - compress - delaycompress - notifempty - missingok - su root root -} diff --git a/gorgone/packaging/debian/centreon-gorgone.postinst b/gorgone/packaging/debian/centreon-gorgone.postinst deleted file mode 100755 index 9dea7fbeabf..00000000000 --- a/gorgone/packaging/debian/centreon-gorgone.postinst +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/sh - -if [ "$1" = "configure" ] ; then - - chown -R centreon-gorgone:centreon-gorgone \ - /etc/centreon-gorgone \ - /var/cache/centreon-gorgone \ - /var/lib/centreon-gorgone \ - /var/log/centreon-gorgone - chmod -R g+w \ - /etc/centreon-gorgone \ - /var/cache/centreon-gorgone \ - /var/lib/centreon-gorgone \ - /var/log/centreon-gorgone - - chown root:root \ - /usr/local/bin/gorgone_config_init.pl \ - /usr/local/bin/gorgone_audit.pl \ - /usr/local/bin/gorgone_install_plugins.pl - - chmod 0755 \ - /usr/local/bin/gorgone_config_init.pl \ - /usr/local/bin/gorgone_audit.pl - - chmod 0750 \ - /usr/local/bin/gorgone_install_plugins.pl - - if [ ! -d /var/lib/centreon-gorgone/.ssh -a -d /var/spool/centreon/.ssh ] ; then - /usr/bin/cp -r /var/spool/centreon/.ssh /var/lib/centreon-gorgone/.ssh - /usr/bin/chown -R centreon-gorgone:centreon-gorgone /var/lib/centreon-gorgone/.ssh - /usr/bin/chmod 600 /var/lib/centreon-gorgone/.ssh/id_rsa - fi - - if [ ! -f /etc/centreon-gorgone/config.d/31-centreon-api.yaml ] || grep -q "@GORGONE_USER@" "/etc/centreon-gorgone/config.d/31-centreon-api.yaml"; then - mv /etc/centreon-gorgone/config.d/centreon-api.yaml /etc/centreon-gorgone/config.d/31-centreon-api.yaml - else - mv /etc/centreon-gorgone/config.d/centreon-api.yaml /etc/centreon-gorgone/config.d/centreon-api.yaml.new - fi - - systemctl daemon-reload ||: - systemctl unmask gorgoned.service ||: - systemctl preset gorgoned.service ||: - -fi diff --git a/gorgone/packaging/debian/centreon-gorgone.preinst b/gorgone/packaging/debian/centreon-gorgone.preinst deleted file mode 100755 index 774d2a26c24..00000000000 --- a/gorgone/packaging/debian/centreon-gorgone.preinst +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh - -if [ ! "$(getent passwd centreon-gorgone)" ]; then - adduser --system --group --home /var/lib/centreon-gorgone --no-create-home centreon-gorgone -fi - -if [ "$(getent passwd centreon)" ]; then - usermod centreon -a -G centreon-gorgone - usermod centreon-gorgone -a -G centreon -fi - -if [ "$(getent passwd centreon-engine)" ]; then - usermod centreon-engine -a -G centreon-gorgone - usermod centreon-gorgone -a -G centreon-engine -fi - -if [ "$(getent passwd centreon-broker)" ]; then - usermod centreon-broker -a -G centreon-gorgone - usermod centreon-gorgone -a -G centreon-broker -fi diff --git a/gorgone/packaging/debian/control b/gorgone/packaging/debian/control deleted file mode 100644 index f117516bb03..00000000000 --- a/gorgone/packaging/debian/control +++ /dev/null @@ -1,45 +0,0 @@ -Source: centreon-gorgone -Section: net -Priority: optional -Maintainer: Centreon <contact@centreon.com> -Build-Depends: - debhelper-compat (=12), - lsb-base -Standards-Version: 4.5.0 -Homepage: https://wwww.centreon.com - -Package: centreon-gorgone -Architecture: all -Depends: - centreon-common (>= ${centreon:version}~), - centreon-common (<< ${centreon:versionThreshold}~), - libdatetime-perl, - libtime-parsedate-perl, - libtry-tiny-perl, - libxml-simple-perl, - libxml-libxml-simple-perl, - libdigest-md5-file-perl, - libjson-pp-perl, - libjson-xs-perl, - libyaml-libyaml-perl, - libdbi-perl, - libdbd-sqlite3-perl, - libdbd-mysql-perl, - libhttp-daemon-perl, - libhttp-daemon-ssl-perl, - libnetaddr-ip-perl, - libschedule-cron-perl, - libhash-merge-perl, - libcryptx-perl, - libmojolicious-perl, - libauthen-simple-perl, - libauthen-simple-net-perl, - libnet-curl-perl, - libssh-session-perl, - libssh-4, - libev-perl, - libzmq-ffi-perl, - sudo, - ${shlibs:Depends}, - ${misc:Depends} -Description: Centreon Gorgone. diff --git a/gorgone/packaging/debian/copyright b/gorgone/packaging/debian/copyright deleted file mode 100644 index 9dcff2f907b..00000000000 --- a/gorgone/packaging/debian/copyright +++ /dev/null @@ -1,29 +0,0 @@ -Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: centreon-gorgone -Upstream-Contact: Centreon <contact@centreon.com> -Source: https://www.centreon.com - -Files: * -Copyright: 2022 Centreon -License: Apache-2.0 - -Files: debian/* -Copyright: 2022 Centreon -License: Apache-2.0 - -License: Apache-2.0 - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - . - https://www.apache.org/licenses/LICENSE-2.0 - . - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - . - On Debian systems, the complete text of the Apache version 2.0 license - can be found in "/usr/share/common-licenses/Apache-2.0". - diff --git a/gorgone/packaging/debian/rules b/gorgone/packaging/debian/rules deleted file mode 100755 index d135e45c060..00000000000 --- a/gorgone/packaging/debian/rules +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/make -f - -export DEB_BUILD_MAINT_OPTIONS = hardening=+all - -%: - dh $@ - -override_dh_gencontrol: - dh_gencontrol -- -Tdebian/substvars - -override_dh_usrlocal: - -override_dh_auto_build: - sed -i "s#/etc/sysconfig#/etc/default#g" \ - config/systemd/gorgoned-service diff --git a/gorgone/packaging/debian/source/format b/gorgone/packaging/debian/source/format deleted file mode 100644 index 163aaf8d82b..00000000000 --- a/gorgone/packaging/debian/source/format +++ /dev/null @@ -1 +0,0 @@ -3.0 (quilt) diff --git a/gorgone/packaging/debian/substvars b/gorgone/packaging/debian/substvars deleted file mode 100644 index 0e75aa07254..00000000000 --- a/gorgone/packaging/debian/substvars +++ /dev/null @@ -1,2 +0,0 @@ -centreon:version= -centreon:versionThreshold= diff --git a/gorgone/packaging/scripts/centreon-gorgone-centreon-config-postinstall.sh b/gorgone/packaging/scripts/centreon-gorgone-centreon-config-postinstall.sh new file mode 100644 index 00000000000..dc05e3da774 --- /dev/null +++ b/gorgone/packaging/scripts/centreon-gorgone-centreon-config-postinstall.sh @@ -0,0 +1,63 @@ +#!/bin/bash + +installConfigurationFile() { + if [ ! -f /etc/centreon-gorgone/config.d/31-centreon-api.yaml ] || grep -q "@GORGONE_USER@" "/etc/centreon-gorgone/config.d/31-centreon-api.yaml"; then + mv /etc/centreon-gorgone/config.d/centreon-api.yaml /etc/centreon-gorgone/config.d/31-centreon-api.yaml + else + mv /etc/centreon-gorgone/config.d/centreon-api.yaml /etc/centreon-gorgone/config.d/centreon-api.yaml.new + fi +} + +manageUserGroups() { + if getent passwd centreon > /dev/null 2>&1; then + usermod -a -G centreon-gorgone centreon 2> /dev/null + fi + + if getent passwd centreon-engine > /dev/null 2>&1; then + usermod -a -G centreon-gorgone centreon-engine 2> /dev/null + fi + + if getent passwd centreon-broker > /dev/null 2>&1; then + usermod -a -G centreon-gorgone centreon-broker 2> /dev/null + fi + + if getent passwd centreon-gorgone > /dev/null 2>&1; then + usermod -a -G centreon centreon-gorgone 2> /dev/null + fi +} + +addGorgoneSshKeys() { + if [ ! -d /var/lib/centreon-gorgone/.ssh ] && [ -d /var/spool/centreon/.ssh ]; then + cp -r /var/spool/centreon/.ssh /var/lib/centreon-gorgone/.ssh + chown -R centreon-gorgone:centreon-gorgone /var/lib/centreon-gorgone/.ssh + chmod 600 /var/lib/centreon-gorgone/.ssh/id_rsa + fi +} + +action="$1" +if [ "$1" = "configure" ] && [ -z "$2" ]; then + # Alpine linux does not pass args, and deb passes $1=configure + action="install" +elif [ "$1" = "configure" ] && [ -n "$2" ]; then + # deb passes $1=configure $2=<current version> + action="upgrade" +fi + +case "$action" in + "1" | "install") + manageUserGroups + installConfigurationFile + addGorgoneSshKeys + ;; + "2" | "upgrade") + manageUserGroups + installConfigurationFile + addGorgoneSshKeys + ;; + *) + # $1 == version being installed + manageUserGroups + installConfigurationFile + addGorgoneSshKeys + ;; +esac diff --git a/gorgone/packaging/scripts/centreon-gorgone-postinstall.sh b/gorgone/packaging/scripts/centreon-gorgone-postinstall.sh new file mode 100644 index 00000000000..0ff1468729e --- /dev/null +++ b/gorgone/packaging/scripts/centreon-gorgone-postinstall.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +startGorgoned() { + systemctl daemon-reload ||: + systemctl unmask gorgoned.service ||: + systemctl preset gorgoned.service ||: + systemctl enable gorgoned.service ||: + systemctl restart gorgoned.service ||: +} + +action="$1" +if [ "$1" = "configure" ] && [ -z "$2" ]; then + # Alpine linux does not pass args, and deb passes $1=configure + action="install" +elif [ "$1" = "configure" ] && [ -n "$2" ]; then + # deb passes $1=configure $2=<current version> + action="upgrade" +fi + +case "$action" in + "1" | "install") + startGorgoned + ;; + "2" | "upgrade") + startGorgoned + ;; + *) + # $1 == version being installed + startGorgoned + ;; +esac diff --git a/gorgone/packaging/scripts/centreon-gorgone-preinstall.sh b/gorgone/packaging/scripts/centreon-gorgone-preinstall.sh new file mode 100644 index 00000000000..f4d22b0a160 --- /dev/null +++ b/gorgone/packaging/scripts/centreon-gorgone-preinstall.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +if ! getent group centreon-gorgone > /dev/null 2>&1; then + groupadd -r centreon-gorgone +fi + +# Check if the centreon-gorgone user exists, and create it if not +if ! getent passwd centreon-gorgone > /dev/null 2>&1; then + useradd -g centreon-gorgone -m -d /var/lib/centreon-gorgone -r centreon-gorgone 2> /dev/null +fi diff --git a/gorgone/packaging/scripts/centreon-gorgone-preremove.sh b/gorgone/packaging/scripts/centreon-gorgone-preremove.sh new file mode 100644 index 00000000000..3498c040c1f --- /dev/null +++ b/gorgone/packaging/scripts/centreon-gorgone-preremove.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +systemctl stop gorgoned.service ||: diff --git a/gorgone/packaging/scripts/centreon-gorgone-selinux-postinstall.sh b/gorgone/packaging/scripts/centreon-gorgone-selinux-postinstall.sh new file mode 100644 index 00000000000..c7a5de1a198 --- /dev/null +++ b/gorgone/packaging/scripts/centreon-gorgone-selinux-postinstall.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +install() { + semodule -i /usr/share/selinux/packages/centreon/centreon-gorgoned.pp > /dev/null 2>&1 || : +} + +upgrade() { + semodule -i /usr/share/selinux/packages/centreon/centreon-gorgoned.pp > /dev/null 2>&1 || : +} + +action="$1" +if [ "$1" = "configure" ] && [ -z "$2" ]; then + action="install" +elif [ "$1" = "configure" ] && [ -n "$2" ]; then + action="upgrade" +fi + +case "$action" in + "1" | "install") + install + ;; + "2" | "upgrade") + upgrade + ;; +esac diff --git a/gorgone/packaging/scripts/centreon-gorgone-selinux-preremove.sh b/gorgone/packaging/scripts/centreon-gorgone-selinux-preremove.sh new file mode 100644 index 00000000000..d3d21a909ce --- /dev/null +++ b/gorgone/packaging/scripts/centreon-gorgone-selinux-preremove.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +if [ "$1" -lt "1" ]; then + semodule -r centreon-gorgoned > /dev/null 2>&1 || : +fi From b97f4ad885cb73a9af093df52d44d83de8a55d21 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Wed, 22 Nov 2023 09:45:37 +0100 Subject: [PATCH 772/948] fix(ci): fix gorgone package cache key used by delivery (#2684) --- .github/workflows/gorgone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 13770deda95..b448ef763f3 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -108,7 +108,7 @@ jobs: release: ${{ needs.get-version.outputs.release }} arch: all commit_hash: ${{ github.sha }} - cache_key: cache-${{ github.sha }}-${{ matrix.package_extension}}-centreon-gorgone-${{ matrix.distrib }}-${{ github.head_ref || github.ref_name }} + cache_key: ${{ github.sha }}-${{ github.run_id }}-${{ matrix.package_extension }}-${{ matrix.distrib }} rpm_gpg_key: ${{ secrets.RPM_GPG_SIGNING_KEY }} rpm_gpg_signing_key_id: ${{ secrets.RPM_GPG_SIGNING_KEY_ID }} rpm_gpg_signing_passphrase: ${{ secrets.RPM_GPG_SIGNING_PASSPHRASE }} From f44d5a793a421a21f128e6064eb31300b2112f56 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Mon, 27 Nov 2023 16:18:57 +0100 Subject: [PATCH 773/948] fix(packaging): fix conflicts of gorgone packages on upgrade (#2711) --- gorgone/packaging/centreon-gorgone-centreon-config.yaml | 7 +++++++ gorgone/packaging/centreon-gorgone.yaml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/gorgone/packaging/centreon-gorgone-centreon-config.yaml b/gorgone/packaging/centreon-gorgone-centreon-config.yaml index f48e6d2f276..6024853a9b2 100644 --- a/gorgone/packaging/centreon-gorgone-centreon-config.yaml +++ b/gorgone/packaging/centreon-gorgone-centreon-config.yaml @@ -32,6 +32,7 @@ contents: - src: "./configuration/centreon-audit.yaml" dst: "/etc/centreon-gorgone/config.d/50-centreon-audit.yaml" + type: config|noreplace file_info: owner: centreon-gorgone group: centreon-gorgone @@ -53,6 +54,12 @@ overrides: deb: depends: - centreon-gorgone (= ${VERSION}-${RELEASE}${DIST}) + replaces: + - centreon-gorgone (<< 24.04.0) + +deb: + breaks: + - centreon-gorgone (<< 24.04.0) rpm: summary: Configure Centreon Gorgone for use with Centreon Web diff --git a/gorgone/packaging/centreon-gorgone.yaml b/gorgone/packaging/centreon-gorgone.yaml index 7870fc3917e..5675db5a1b6 100644 --- a/gorgone/packaging/centreon-gorgone.yaml +++ b/gorgone/packaging/centreon-gorgone.yaml @@ -37,7 +37,7 @@ contents: mode: 0775 - src: "./configuration/config.yaml" - dst: "/etc/centreon-gorgone/" + dst: "/etc/centreon-gorgone/config.yaml" file_info: owner: centreon-gorgone group: centreon-gorgone From c7b62c9d5a3cd2a7dec245b7c3a262d58ea499e0 Mon Sep 17 00:00:00 2001 From: tuntoja <58987095+tuntoja@users.noreply.github.com> Date: Wed, 13 Dec 2023 08:55:30 +0100 Subject: [PATCH 774/948] enh(delivery): no deliver source and promote on dispatch (#2876) --- .github/workflows/gorgone.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index b448ef763f3..435afce8e61 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -116,7 +116,7 @@ jobs: deliver-sources: runs-on: [self-hosted, common] needs: [get-version, package] - if: ${{ contains(fromJson('["stable"]'), needs.get-version.outputs.stability) }} + if: ${{ contains(fromJson('["stable"]'), needs.get-version.outputs.stability) && github.event_name != 'workflow_dispatch' }} steps: - name: Checkout sources @@ -180,7 +180,7 @@ jobs: promote: needs: [get-version] - if: ${{ contains(fromJson('["stable"]'), needs.get-version.outputs.stability) }} + if: ${{ contains(fromJson('["stable"]'), needs.get-version.outputs.stability) && github.event_name != 'workflow_dispatch' }} runs-on: [self-hosted, common] strategy: matrix: From 15ecc235140b87060f3374fa0b06e46664255f56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Chapron?= <34628915+sc979@users.noreply.github.com> Date: Thu, 14 Dec 2023 10:04:06 +0100 Subject: [PATCH 775/948] fix(chore): disable gorgone pipeline scan (#2884) --- .github/workflows/gorgone.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 435afce8e61..a2cea2af776 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -36,6 +36,7 @@ jobs: major_version: ${{ needs.get-version.outputs.major_version }} minor_version: ${{ needs.get-version.outputs.minor_version }} stability: ${{ needs.get-version.outputs.stability }} + is_perl_project: true secrets: veracode_api_id: ${{ secrets.VERACODE_API_ID_GORG }} veracode_api_key: ${{ secrets.VERACODE_API_KEY_GORG }} From 0f34ebb6cc5b9d2c9df4f3f814b8b7e5f9fe75d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Chapron?= <34628915+sc979@users.noreply.github.com> Date: Wed, 20 Dec 2023 17:45:13 +0100 Subject: [PATCH 776/948] enh(chore): github actions hardening (#2932) --- .github/workflows/gorgone.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index a2cea2af776..7c15c6c37a1 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -73,7 +73,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Set package version and paths according to distrib run: | @@ -121,7 +121,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Deliver sources uses: ./.github/actions/release-sources @@ -144,7 +144,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Delivery uses: ./.github/actions/rpm-delivery @@ -167,7 +167,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Delivery uses: ./.github/actions/deb-delivery @@ -189,7 +189,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Promote ${{ matrix.distrib }} to stable uses: ./.github/actions/promote-to-stable From 79245df1e995cc2db012eeeb443db5d432d66ba7 Mon Sep 17 00:00:00 2001 From: tuntoja <tuntoja@centreon.com> Date: Fri, 26 Jan 2024 11:00:25 +0100 Subject: [PATCH 777/948] Empty-Commit From 9974276bb8d601b04448d36d8f91d5cd61ce1101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Chapron?= <34628915+sc979@users.noreply.github.com> Date: Fri, 26 Jan 2024 11:30:49 +0100 Subject: [PATCH 778/948] feat(ci): create jira ticket on QG failure (#3053) --- .github/workflows/gorgone.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 7c15c6c37a1..7d708b9be8b 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -41,6 +41,9 @@ jobs: veracode_api_id: ${{ secrets.VERACODE_API_ID_GORG }} veracode_api_key: ${{ secrets.VERACODE_API_KEY_GORG }} veracode_srcclr_token: ${{ secrets.VERACODE_SRCCLR_TOKEN }} + jira_base_url: ${{ secrets.JIRA_BASE_URL }} + jira_user_email: ${{ secrets.XRAY_JIRA_USER_EMAIL }} + jira_api_token: ${{ secrets.XRAY_JIRA_TOKEN }} package: needs: [get-version] From c4aae702f156b26de21b465599941c49910bef33 Mon Sep 17 00:00:00 2001 From: tuntoja <58987095+tuntoja@users.noreply.github.com> Date: Fri, 26 Jan 2024 11:34:26 +0100 Subject: [PATCH 779/948] fix(promote): add github_base_ref to promote job inputs (#3182) --- .github/workflows/gorgone.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 7d708b9be8b..551ce146abc 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -203,3 +203,4 @@ jobs: major_version: ${{ needs.get-version.outputs.major_version }} minor_version: ${{ needs.get-version.outputs.minor_version }} stability: ${{ needs.get-version.outputs.stability }} + github_base_ref: ${{ github.base_ref }} From 56cca8330db01dea05561979222cca819a50e125 Mon Sep 17 00:00:00 2001 From: "Gabriel M. Aguirre" <46610157+gabrielmagit@users.noreply.github.com> Date: Fri, 2 Feb 2024 14:20:32 +0100 Subject: [PATCH 780/948] fix(conf gorgone) YAML templates need actions enabled by default for gorgone::modules::core::action::hooks (#3222) --- gorgone/config/gorgoned-central-ssh.yml | 7 +++++++ gorgone/config/gorgoned-central-zmq.yml | 7 +++++++ gorgone/config/gorgoned-poller.yml | 7 +++++++ gorgone/config/gorgoned-remote-ssh.yml | 7 +++++++ gorgone/config/gorgoned-remote-zmq.yml | 7 +++++++ gorgone/contrib/gorgone_config_init.pl | 9 ++++++++- gorgone/docs/migration.md | 7 +++++++ gorgone/docs/modules/core/action.md | 4 ++-- gorgone/docs/poller_pull_configuration.md | 7 +++++++ gorgone/docs/rebound_configuration.md | 7 +++++++ gorgone/gorgone/modules/core/action/hooks.pm | 2 +- 11 files changed, 67 insertions(+), 4 deletions(-) diff --git a/gorgone/config/gorgoned-central-ssh.yml b/gorgone/config/gorgoned-central-ssh.yml index b9b87a3e97c..4f381f0a5f0 100644 --- a/gorgone/config/gorgoned-central-ssh.yml +++ b/gorgone/config/gorgoned-central-ssh.yml @@ -31,6 +31,13 @@ configuration: - name: action package: gorgone::modules::core::action::hooks enable: true + command_timeout: 30 + whitelist_cmds: true + allowed_cmds: + - ^sudo\s+(/bin/)?systemctl\s+(reload|restart)\s+(centengine|centreontrapd|cbd)\s*$ + - ^sudo\s+(/usr/bin/)?service\s+(centengine|centreontrapd|cbd)\s+(reload|restart)\s*$ + - ^/usr/sbin/centenginestats\s+-c\s+/etc/centreon-engine/centengine.cfg\s*$ + - ^cat\s+/var/lib/centreon-engine/[a-zA-Z0-9\-]+-stats.json\s*$ - name: proxy package: gorgone::modules::core::proxy::hooks diff --git a/gorgone/config/gorgoned-central-zmq.yml b/gorgone/config/gorgoned-central-zmq.yml index 9bd9dc4cecb..1d28e94fb91 100644 --- a/gorgone/config/gorgoned-central-zmq.yml +++ b/gorgone/config/gorgoned-central-zmq.yml @@ -55,6 +55,13 @@ configuration: - name: action package: gorgone::modules::core::action::hooks enable: true + command_timeout: 30 + whitelist_cmds: true + allowed_cmds: + - ^sudo\s+(/bin/)?systemctl\s+(reload|restart)\s+(centengine|centreontrapd|cbd)\s*$ + - ^sudo\s+(/usr/bin/)?service\s+(centengine|centreontrapd|cbd)\s+(reload|restart)\s*$ + - ^/usr/sbin/centenginestats\s+-c\s+/etc/centreon-engine/centengine.cfg\s*$ + - ^cat\s+/var/lib/centreon-engine/[a-zA-Z0-9\-]+-stats.json\s*$ - name: proxy package: gorgone::modules::core::proxy::hooks diff --git a/gorgone/config/gorgoned-poller.yml b/gorgone/config/gorgoned-poller.yml index 39dae92f89d..a243f0834a5 100644 --- a/gorgone/config/gorgoned-poller.yml +++ b/gorgone/config/gorgoned-poller.yml @@ -13,6 +13,13 @@ configuration: - name: action package: gorgone::modules::core::action::hooks enable: true + command_timeout: 30 + whitelist_cmds: true + allowed_cmds: + - ^sudo\s+(/bin/)?systemctl\s+(reload|restart)\s+(centengine|centreontrapd|cbd)\s*$ + - ^sudo\s+(/usr/bin/)?service\s+(centengine|centreontrapd|cbd)\s+(reload|restart)\s*$ + - ^/usr/sbin/centenginestats\s+-c\s+/etc/centreon-engine/centengine.cfg\s*$ + - ^cat\s+/var/lib/centreon-engine/[a-zA-Z0-9\-]+-stats.json\s*$ - name: engine package: gorgone::modules::centreon::engine::hooks diff --git a/gorgone/config/gorgoned-remote-ssh.yml b/gorgone/config/gorgoned-remote-ssh.yml index bbdf5e7f2ad..ffa3b5c71d5 100644 --- a/gorgone/config/gorgoned-remote-ssh.yml +++ b/gorgone/config/gorgoned-remote-ssh.yml @@ -18,6 +18,13 @@ configuration: - name: action package: gorgone::modules::core::action::hooks enable: true + command_timeout: 30 + whitelist_cmds: true + allowed_cmds: + - ^sudo\s+(/bin/)?systemctl\s+(reload|restart)\s+(centengine|centreontrapd|cbd)\s*$ + - ^sudo\s+(/usr/bin/)?service\s+(centengine|centreontrapd|cbd)\s+(reload|restart)\s*$ + - ^/usr/sbin/centenginestats\s+-c\s+/etc/centreon-engine/centengine.cfg\s*$ + - ^cat\s+/var/lib/centreon-engine/[a-zA-Z0-9\-]+-stats.json\s*$ - name: proxy package: gorgone::modules::core::proxy::hooks diff --git a/gorgone/config/gorgoned-remote-zmq.yml b/gorgone/config/gorgoned-remote-zmq.yml index 6b5a96e9f18..4b243bad795 100644 --- a/gorgone/config/gorgoned-remote-zmq.yml +++ b/gorgone/config/gorgoned-remote-zmq.yml @@ -23,6 +23,13 @@ configuration: - name: action package: gorgone::modules::core::action::hooks enable: true + command_timeout: 30 + whitelist_cmds: true + allowed_cmds: + - ^sudo\s+(/bin/)?systemctl\s+(reload|restart)\s+(centengine|centreontrapd|cbd)\s*$ + - ^sudo\s+(/usr/bin/)?service\s+(centengine|centreontrapd|cbd)\s+(reload|restart)\s*$ + - ^/usr/sbin/centenginestats\s+-c\s+/etc/centreon-engine/centengine.cfg\s*$ + - ^cat\s+/var/lib/centreon-engine/[a-zA-Z0-9\-]+-stats.json\s*$ - name: proxy package: gorgone::modules::core::proxy::hooks diff --git a/gorgone/contrib/gorgone_config_init.pl b/gorgone/contrib/gorgone_config_init.pl index 0e3e8f89bc3..d30b646bc37 100644 --- a/gorgone/contrib/gorgone_config_init.pl +++ b/gorgone/contrib/gorgone_config_init.pl @@ -94,7 +94,7 @@ sub write_gorgone_config { dsn: "mysql:host=$centreon_config->{db_host}${db_port};dbname=$centreon_config->{centstorage_db}" username: "$centreon_config->{db_user}" password: "$centreon_config->{db_passwd}" - gorgone: + gorgone: gorgonecore: hostname: id: @@ -121,6 +121,13 @@ sub write_gorgone_config { - name: action package: gorgone::modules::core::action::hooks enable: true + command_timeout: 30 + whitelist_cmds: true + allowed_cmds: + - ^sudo\s+(/bin/)?systemctl\s+(reload|restart)\s+(centengine|centreontrapd|cbd)\s*$ + - ^sudo\s+(/usr/bin/)?service\s+(centengine|centreontrapd|cbd)\s+(reload|restart)\s*$ + - ^/usr/sbin/centenginestats\s+-c\s+/etc/centreon-engine/centengine.cfg\s*$ + - ^cat\s+/var/lib/centreon-engine/[a-zA-Z0-9\-]+-stats.json\s*$ - name: proxy package: gorgone::modules::core::proxy::hooks diff --git a/gorgone/docs/migration.md b/gorgone/docs/migration.md index 81d3d47592a..a049a253853 100644 --- a/gorgone/docs/migration.md +++ b/gorgone/docs/migration.md @@ -53,6 +53,13 @@ configuration: - name: action package: gorgone::modules::core::action::hooks enable: true + command_timeout: 30 + whitelist_cmds: true + allowed_cmds: + - ^sudo\s+(/bin/)?systemctl\s+(reload|restart)\s+(centengine|centreontrapd|cbd)\s*$ + - ^sudo\s+(/usr/bin/)?service\s+(centengine|centreontrapd|cbd)\s+(reload|restart)\s*$ + - ^/usr/sbin/centenginestats\s+-c\s+/etc/centreon-engine/centengine.cfg\s*$ + - ^cat\s+/var/lib/centreon-engine/[a-zA-Z0-9\-]+-stats.json\s*$ - name: cron package: gorgone::modules::core::cron::hooks diff --git a/gorgone/docs/modules/core/action.md b/gorgone/docs/modules/core/action.md index fc1e298f8d2..9a86c966d85 100644 --- a/gorgone/docs/modules/core/action.md +++ b/gorgone/docs/modules/core/action.md @@ -20,12 +20,12 @@ name: action package: "gorgone::modules::core::action::hooks" enable: true command_timeout: 30 -whitelist_cmds: false +whitelist_cmds: true allowed_cmds: - ^sudo\s+(/bin/)?systemctl\s+(reload|restart)\s+(centengine|centreontrapd|cbd)\s*$ - ^sudo\s+(/usr/bin/)?service\s+(centengine|centreontrapd|cbd)\s+(reload|restart)\s*$ - ^/usr/sbin/centenginestats\s+-c\s+/etc/centreon-engine/centengine.cfg\s*$ - - ^cat\s+/var/lib/centreon-engine/[a-zA-Z0-9\-]+-stats.json\s*$ + - ^cat\s+/var/lib/centreon-engine/[a-zA-Z0-9\-]+-stats.json\s*$ ``` ## Events diff --git a/gorgone/docs/poller_pull_configuration.md b/gorgone/docs/poller_pull_configuration.md index 43e9c709cfe..e88c2cbeabb 100644 --- a/gorgone/docs/poller_pull_configuration.md +++ b/gorgone/docs/poller_pull_configuration.md @@ -38,6 +38,13 @@ gorgone: - name: action package: gorgone::modules::core::action::hooks enable: true + command_timeout: 30 + whitelist_cmds: true + allowed_cmds: + - ^sudo\s+(/bin/)?systemctl\s+(reload|restart)\s+(centengine|centreontrapd|cbd)\s*$ + - ^sudo\s+(/usr/bin/)?service\s+(centengine|centreontrapd|cbd)\s+(reload|restart)\s*$ + - ^/usr/sbin/centenginestats\s+-c\s+/etc/centreon-engine/centengine.cfg\s*$ + - ^cat\s+/var/lib/centreon-engine/[a-zA-Z0-9\-]+-stats.json\s*$ - name: engine package: gorgone::modules::centreon::engine::hooks diff --git a/gorgone/docs/rebound_configuration.md b/gorgone/docs/rebound_configuration.md index 16bac4cdadd..1ff5332110a 100644 --- a/gorgone/docs/rebound_configuration.md +++ b/gorgone/docs/rebound_configuration.md @@ -42,6 +42,13 @@ gorgone: - name: action package: gorgone::modules::core::action::hooks enable: true + command_timeout: 30 + whitelist_cmds: true + allowed_cmds: + - ^sudo\s+(/bin/)?systemctl\s+(reload|restart)\s+(centengine|centreontrapd|cbd)\s*$ + - ^sudo\s+(/usr/bin/)?service\s+(centengine|centreontrapd|cbd)\s+(reload|restart)\s*$ + - ^/usr/sbin/centenginestats\s+-c\s+/etc/centreon-engine/centengine.cfg\s*$ + - ^cat\s+/var/lib/centreon-engine/[a-zA-Z0-9\-]+-stats.json\s*$ - name: engine package: gorgone::modules::centreon::engine::hooks diff --git a/gorgone/gorgone/modules/core/action/hooks.pm b/gorgone/gorgone/modules/core/action/hooks.pm index b803880f1e4..4adaf195c7f 100644 --- a/gorgone/gorgone/modules/core/action/hooks.pm +++ b/gorgone/gorgone/modules/core/action/hooks.pm @@ -67,7 +67,7 @@ sub routing { dbh => $options{dbh}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, - data => { msg => 'gorgoneaction: still no ready' }, + data => { msg => 'gorgoneaction: still not ready' }, json_encode => 1 }); return undef; From 5cf59ee45b1fb3624d104e7f5194e590fd6feb16 Mon Sep 17 00:00:00 2001 From: May <110405507+paul-oureib@users.noreply.github.com> Date: Fri, 2 Feb 2024 15:40:03 +0100 Subject: [PATCH 781/948] feat(packaging): package web for Debian 12 (#3174) Co-authored-by: Kevin Duret <kduret@centreon.com> --- .github/workflows/gorgone.yml | 18 +++++++++++------- gorgone/packaging/centreon-gorgone.yaml | 1 + 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 551ce146abc..ff3183f7f29 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -52,17 +52,20 @@ jobs: strategy: fail-fast: false matrix: - distrib: [el8, el9, bullseye] + distrib: [el8, el9, bullseye, bookworm] include: - package_extension: rpm - image: packaging-alma8 + image: packaging-nfpm-alma8 distrib: el8 - package_extension: rpm - image: packaging-alma9 + image: packaging-nfpm-alma9 distrib: el9 - package_extension: deb - image: packaging-bullseye + image: packaging-nfpm-bullseye distrib: bullseye + - package_extension: deb + image: packaging-nfpm-bookworm + distrib: bookworm runs-on: ubuntu-22.04 @@ -80,7 +83,7 @@ jobs: - name: Set package version and paths according to distrib run: | - if [ "${{ matrix.distrib }}" = "bullseye" ]; then + if [[ "${{ matrix.package_extension }}" == "deb" ]]; then PERL_VENDORLIB="/usr/share/perl5" else PERL_VENDORLIB="/usr/share/perl5/vendor_perl" @@ -116,6 +119,7 @@ jobs: rpm_gpg_key: ${{ secrets.RPM_GPG_SIGNING_KEY }} rpm_gpg_signing_key_id: ${{ secrets.RPM_GPG_SIGNING_KEY_ID }} rpm_gpg_signing_passphrase: ${{ secrets.RPM_GPG_SIGNING_PASSPHRASE }} + stability: ${{ needs.get-version.outputs.stability }} deliver-sources: runs-on: [self-hosted, common] @@ -166,7 +170,7 @@ jobs: strategy: matrix: - distrib: [bullseye] + distrib: [bullseye, bookworm] steps: - name: Checkout sources @@ -188,7 +192,7 @@ jobs: runs-on: [self-hosted, common] strategy: matrix: - distrib: [el8, el9, bullseye] + distrib: [el8, el9, bullseye, bookworm] steps: - name: Checkout sources diff --git a/gorgone/packaging/centreon-gorgone.yaml b/gorgone/packaging/centreon-gorgone.yaml index 5675db5a1b6..597195e6ac7 100644 --- a/gorgone/packaging/centreon-gorgone.yaml +++ b/gorgone/packaging/centreon-gorgone.yaml @@ -190,6 +190,7 @@ overrides: - libssh-session-perl - libev-perl - libzmq-ffi-perl + - libclone-choose-perl rpm: summary: Centreon gorgone daemon From f716b187801d5426ab905abb9deab2ca0f3180f8 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Mon, 5 Feb 2024 14:14:22 +0100 Subject: [PATCH 782/948] fix(ci): deliver ubuntu packages (unstable/testing) (#3273) --- .github/workflows/gorgone.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index ff3183f7f29..c0d527c3230 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -52,7 +52,7 @@ jobs: strategy: fail-fast: false matrix: - distrib: [el8, el9, bullseye, bookworm] + distrib: [el8, el9, bullseye, bookworm, jammy] include: - package_extension: rpm image: packaging-nfpm-alma8 @@ -66,6 +66,9 @@ jobs: - package_extension: deb image: packaging-nfpm-bookworm distrib: bookworm + - package_extension: deb + image: packaging-nfpm-jammy + distrib: jammy runs-on: ubuntu-22.04 @@ -170,7 +173,7 @@ jobs: strategy: matrix: - distrib: [bullseye, bookworm] + distrib: [bullseye, bookworm, jammy] steps: - name: Checkout sources From 4eb78a15e9df376743fbfdc606b847e1b6beea9d Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Mon, 26 Feb 2024 15:03:52 +0100 Subject: [PATCH 783/948] MON-35517 Centreon next 24.04 (Jira release #19702#) (#3356) Co-authored-by: Dmytro Iosypenko <108675430+dmyios@users.noreply.github.com> Co-authored-by: David Boucher <boudav@gmail.com> Co-authored-by: Evan Adam <eadam@centreon.com> --- gorgone/config/gorgoned-central-ssh.yml | 13 +++++++++++++ gorgone/config/gorgoned-central-zmq.yml | 13 +++++++++++++ gorgone/config/gorgoned-poller.yml | 13 +++++++++++++ gorgone/config/gorgoned-remote-ssh.yml | 13 +++++++++++++ gorgone/config/gorgoned-remote-zmq.yml | 13 +++++++++++++ gorgone/contrib/gorgone_config_init.pl | 15 ++++++++++++++- gorgone/docs/migration.md | 13 +++++++++++++ gorgone/docs/modules/core/action.md | 14 ++++++++++---- gorgone/docs/poller_pull_configuration.md | 13 +++++++++++++ gorgone/docs/rebound_configuration.md | 13 +++++++++++++ 10 files changed, 128 insertions(+), 5 deletions(-) diff --git a/gorgone/config/gorgoned-central-ssh.yml b/gorgone/config/gorgoned-central-ssh.yml index b9b87a3e97c..069d53679df 100644 --- a/gorgone/config/gorgoned-central-ssh.yml +++ b/gorgone/config/gorgoned-central-ssh.yml @@ -31,6 +31,19 @@ configuration: - name: action package: gorgone::modules::core::action::hooks enable: true + command_timeout: 30 + whitelist_cmds: true + allowed_cmds: + - ^sudo\s+(/bin/)?systemctl\s+(reload|restart)\s+(centengine|centreontrapd|cbd)\s*$ + - ^(sudo\s+)?(/usr/bin/)?service\s+(centengine|centreontrapd|cbd|cbd-sql)\s+(reload|restart)\s*$ + - ^/usr/sbin/centenginestats\s+-c\s+/etc/centreon-engine/centengine\.cfg\s*$ + - ^cat\s+/var/lib/centreon-engine/[a-zA-Z0-9\-]+-stats\.json\s*$ + - ^/usr/lib/centreon/plugins/.*$ + - ^/bin/perl /usr/share/centreon/bin/anomaly_detection --seasonality >> /var/log/centreon/anomaly_detection\.log 2>&1\s*$ + - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ + - ^centreon + - ^mkdir + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ - name: proxy package: gorgone::modules::core::proxy::hooks diff --git a/gorgone/config/gorgoned-central-zmq.yml b/gorgone/config/gorgoned-central-zmq.yml index 9bd9dc4cecb..3fe322d1654 100644 --- a/gorgone/config/gorgoned-central-zmq.yml +++ b/gorgone/config/gorgoned-central-zmq.yml @@ -55,6 +55,19 @@ configuration: - name: action package: gorgone::modules::core::action::hooks enable: true + command_timeout: 30 + whitelist_cmds: true + allowed_cmds: + - ^sudo\s+(/bin/)?systemctl\s+(reload|restart)\s+(centengine|centreontrapd|cbd)\s*$ + - ^(sudo\s+)?(/usr/bin/)?service\s+(centengine|centreontrapd|cbd|cbd-sql)\s+(reload|restart)\s*$ + - ^/usr/sbin/centenginestats\s+-c\s+/etc/centreon-engine/centengine\.cfg\s*$ + - ^cat\s+/var/lib/centreon-engine/[a-zA-Z0-9\-]+-stats\.json\s*$ + - ^/usr/lib/centreon/plugins/.*$ + - ^/bin/perl /usr/share/centreon/bin/anomaly_detection --seasonality >> /var/log/centreon/anomaly_detection\.log 2>&1\s*$ + - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ + - ^centreon + - ^mkdir + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ - name: proxy package: gorgone::modules::core::proxy::hooks diff --git a/gorgone/config/gorgoned-poller.yml b/gorgone/config/gorgoned-poller.yml index 39dae92f89d..72e0a499ebf 100644 --- a/gorgone/config/gorgoned-poller.yml +++ b/gorgone/config/gorgoned-poller.yml @@ -13,6 +13,19 @@ configuration: - name: action package: gorgone::modules::core::action::hooks enable: true + command_timeout: 30 + whitelist_cmds: true + allowed_cmds: + - ^sudo\s+(/bin/)?systemctl\s+(reload|restart)\s+(centengine|centreontrapd|cbd)\s*$ + - ^(sudo\s+)?(/usr/bin/)?service\s+(centengine|centreontrapd|cbd|cbd-sql)\s+(reload|restart)\s*$ + - ^/usr/sbin/centenginestats\s+-c\s+/etc/centreon-engine/centengine\.cfg\s*$ + - ^cat\s+/var/lib/centreon-engine/[a-zA-Z0-9\-]+-stats\.json\s*$ + - ^/usr/lib/centreon/plugins/.*$ + - ^/bin/perl /usr/share/centreon/bin/anomaly_detection --seasonality >> /var/log/centreon/anomaly_detection\.log 2>&1\s*$ + - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ + - ^centreon + - ^mkdir + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ - name: engine package: gorgone::modules::centreon::engine::hooks diff --git a/gorgone/config/gorgoned-remote-ssh.yml b/gorgone/config/gorgoned-remote-ssh.yml index bbdf5e7f2ad..e0ceacdf18e 100644 --- a/gorgone/config/gorgoned-remote-ssh.yml +++ b/gorgone/config/gorgoned-remote-ssh.yml @@ -18,6 +18,19 @@ configuration: - name: action package: gorgone::modules::core::action::hooks enable: true + command_timeout: 30 + whitelist_cmds: true + allowed_cmds: + - ^sudo\s+(/bin/)?systemctl\s+(reload|restart)\s+(centengine|centreontrapd|cbd)\s*$ + - ^(sudo\s+)?(/usr/bin/)?service\s+(centengine|centreontrapd|cbd|cbd-sql)\s+(reload|restart)\s*$ + - ^/usr/sbin/centenginestats\s+-c\s+/etc/centreon-engine/centengine\.cfg\s*$ + - ^cat\s+/var/lib/centreon-engine/[a-zA-Z0-9\-]+-stats\.json\s*$ + - ^/usr/lib/centreon/plugins/.*$ + - ^/bin/perl /usr/share/centreon/bin/anomaly_detection --seasonality >> /var/log/centreon/anomaly_detection\.log 2>&1\s*$ + - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ + - ^centreon + - ^mkdir + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ - name: proxy package: gorgone::modules::core::proxy::hooks diff --git a/gorgone/config/gorgoned-remote-zmq.yml b/gorgone/config/gorgoned-remote-zmq.yml index 6b5a96e9f18..ecb0dd10474 100644 --- a/gorgone/config/gorgoned-remote-zmq.yml +++ b/gorgone/config/gorgoned-remote-zmq.yml @@ -23,6 +23,19 @@ configuration: - name: action package: gorgone::modules::core::action::hooks enable: true + command_timeout: 30 + whitelist_cmds: true + allowed_cmds: + - ^sudo\s+(/bin/)?systemctl\s+(reload|restart)\s+(centengine|centreontrapd|cbd)\s*$ + - ^(sudo\s+)?(/usr/bin/)?service\s+(centengine|centreontrapd|cbd|cbd-sql)\s+(reload|restart)\s*$ + - ^/usr/sbin/centenginestats\s+-c\s+/etc/centreon-engine/centengine\.cfg\s*$ + - ^cat\s+/var/lib/centreon-engine/[a-zA-Z0-9\-]+-stats\.json\s*$ + - ^/usr/lib/centreon/plugins/.*$ + - ^/bin/perl /usr/share/centreon/bin/anomaly_detection --seasonality >> /var/log/centreon/anomaly_detection\.log 2>&1\s*$ + - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ + - ^centreon + - ^mkdir + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ - name: proxy package: gorgone::modules::core::proxy::hooks diff --git a/gorgone/contrib/gorgone_config_init.pl b/gorgone/contrib/gorgone_config_init.pl index 0e3e8f89bc3..1f58077c8ec 100644 --- a/gorgone/contrib/gorgone_config_init.pl +++ b/gorgone/contrib/gorgone_config_init.pl @@ -94,7 +94,7 @@ sub write_gorgone_config { dsn: "mysql:host=$centreon_config->{db_host}${db_port};dbname=$centreon_config->{centstorage_db}" username: "$centreon_config->{db_user}" password: "$centreon_config->{db_passwd}" - gorgone: + gorgone: gorgonecore: hostname: id: @@ -121,6 +121,19 @@ sub write_gorgone_config { - name: action package: gorgone::modules::core::action::hooks enable: true + command_timeout: 30 + whitelist_cmds: true + allowed_cmds: + - ^sudo\s+(/bin/)?systemctl\s+(reload|restart)\s+(centengine|centreontrapd|cbd)\s*$ + - ^(sudo\s+)?(/usr/bin/)?service\s+(centengine|centreontrapd|cbd|cbd-sql)\s+(reload|restart)\s*$ + - ^/usr/sbin/centenginestats\s+-c\s+/etc/centreon-engine/centengine\.cfg\s*$ + - ^cat\s+/var/lib/centreon-engine/[a-zA-Z0-9\-]+-stats\.json\s*$ + - ^/usr/lib/centreon/plugins/.*$ + - ^/bin/perl /usr/share/centreon/bin/anomaly_detection --seasonality >> /var/log/centreon/anomaly_detection\.log 2>&1\s*$ + - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ + - ^centreon + - ^mkdir + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ - name: proxy package: gorgone::modules::core::proxy::hooks diff --git a/gorgone/docs/migration.md b/gorgone/docs/migration.md index 81d3d47592a..ac99d073018 100644 --- a/gorgone/docs/migration.md +++ b/gorgone/docs/migration.md @@ -53,6 +53,19 @@ configuration: - name: action package: gorgone::modules::core::action::hooks enable: true + command_timeout: 30 + whitelist_cmds: true + allowed_cmds: + - ^sudo\s+(/bin/)?systemctl\s+(reload|restart)\s+(centengine|centreontrapd|cbd)\s*$ + - ^(sudo\s+)?(/usr/bin/)?service\s+(centengine|centreontrapd|cbd|cbd-sql)\s+(reload|restart)\s*$ + - ^/usr/sbin/centenginestats\s+-c\s+/etc/centreon-engine/centengine\.cfg\s*$ + - ^cat\s+/var/lib/centreon-engine/[a-zA-Z0-9\-]+-stats\.json\s*$ + - ^/usr/lib/centreon/plugins/.*$ + - ^/bin/perl /usr/share/centreon/bin/anomaly_detection --seasonality >> /var/log/centreon/anomaly_detection\.log 2>&1\s*$ + - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ + - ^centreon + - ^mkdir + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ - name: cron package: gorgone::modules::core::cron::hooks diff --git a/gorgone/docs/modules/core/action.md b/gorgone/docs/modules/core/action.md index fc1e298f8d2..aceaea4ffc5 100644 --- a/gorgone/docs/modules/core/action.md +++ b/gorgone/docs/modules/core/action.md @@ -20,12 +20,18 @@ name: action package: "gorgone::modules::core::action::hooks" enable: true command_timeout: 30 -whitelist_cmds: false +whitelist_cmds: true allowed_cmds: - ^sudo\s+(/bin/)?systemctl\s+(reload|restart)\s+(centengine|centreontrapd|cbd)\s*$ - - ^sudo\s+(/usr/bin/)?service\s+(centengine|centreontrapd|cbd)\s+(reload|restart)\s*$ - - ^/usr/sbin/centenginestats\s+-c\s+/etc/centreon-engine/centengine.cfg\s*$ - - ^cat\s+/var/lib/centreon-engine/[a-zA-Z0-9\-]+-stats.json\s*$ + - ^(sudo\s+)?(/usr/bin/)?service\s+(centengine|centreontrapd|cbd|cbd-sql)\s+(reload|restart)\s*$ + - ^/usr/sbin/centenginestats\s+-c\s+/etc/centreon-engine/centengine\.cfg\s*$ + - ^cat\s+/var/lib/centreon-engine/[a-zA-Z0-9\-]+-stats\.json\s*$ + - ^/usr/lib/centreon/plugins/.*$ + - ^/bin/perl /usr/share/centreon/bin/anomaly_detection --seasonality >> /var/log/centreon/anomaly_detection\.log 2>&1\s*$ + - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ + - ^centreon + - ^mkdir + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ ``` ## Events diff --git a/gorgone/docs/poller_pull_configuration.md b/gorgone/docs/poller_pull_configuration.md index 43e9c709cfe..4333a3a1f8a 100644 --- a/gorgone/docs/poller_pull_configuration.md +++ b/gorgone/docs/poller_pull_configuration.md @@ -38,6 +38,19 @@ gorgone: - name: action package: gorgone::modules::core::action::hooks enable: true + command_timeout: 30 + whitelist_cmds: true + allowed_cmds: + - ^sudo\s+(/bin/)?systemctl\s+(reload|restart)\s+(centengine|centreontrapd|cbd)\s*$ + - ^(sudo\s+)?(/usr/bin/)?service\s+(centengine|centreontrapd|cbd|cbd-sql)\s+(reload|restart)\s*$ + - ^/usr/sbin/centenginestats\s+-c\s+/etc/centreon-engine/centengine\.cfg\s*$ + - ^cat\s+/var/lib/centreon-engine/[a-zA-Z0-9\-]+-stats\.json\s*$ + - ^/usr/lib/centreon/plugins/.*$ + - ^/bin/perl /usr/share/centreon/bin/anomaly_detection --seasonality >> /var/log/centreon/anomaly_detection\.log 2>&1\s*$ + - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ + - ^centreon + - ^mkdir + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ - name: engine package: gorgone::modules::centreon::engine::hooks diff --git a/gorgone/docs/rebound_configuration.md b/gorgone/docs/rebound_configuration.md index 16bac4cdadd..5a7a5fe84df 100644 --- a/gorgone/docs/rebound_configuration.md +++ b/gorgone/docs/rebound_configuration.md @@ -42,6 +42,19 @@ gorgone: - name: action package: gorgone::modules::core::action::hooks enable: true + command_timeout: 30 + whitelist_cmds: true + allowed_cmds: + - ^sudo\s+(/bin/)?systemctl\s+(reload|restart)\s+(centengine|centreontrapd|cbd)\s*$ + - ^(sudo\s+)?(/usr/bin/)?service\s+(centengine|centreontrapd|cbd|cbd-sql)\s+(reload|restart)\s*$ + - ^/usr/sbin/centenginestats\s+-c\s+/etc/centreon-engine/centengine\.cfg\s*$ + - ^cat\s+/var/lib/centreon-engine/[a-zA-Z0-9\-]+-stats\.json\s*$ + - ^/usr/lib/centreon/plugins/.*$ + - ^/bin/perl /usr/share/centreon/bin/anomaly_detection --seasonality >> /var/log/centreon/anomaly_detection\.log 2>&1\s*$ + - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ + - ^centreon + - ^mkdir + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ - name: engine package: gorgone::modules::centreon::engine::hooks From 3fce577803db298a2f337c99376f4c622bd1890e Mon Sep 17 00:00:00 2001 From: tuntoja <58987095+tuntoja@users.noreply.github.com> Date: Wed, 28 Feb 2024 20:08:02 +0100 Subject: [PATCH 784/948] fix(packaging): add missing debian lib dependency to gorgone (#3460) --- gorgone/packaging/centreon-gorgone.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/gorgone/packaging/centreon-gorgone.yaml b/gorgone/packaging/centreon-gorgone.yaml index 597195e6ac7..0bea8be1260 100644 --- a/gorgone/packaging/centreon-gorgone.yaml +++ b/gorgone/packaging/centreon-gorgone.yaml @@ -188,6 +188,7 @@ overrides: - libauthen-simple-net-perl - libnet-curl-perl - libssh-session-perl + - libssh-4 - libev-perl - libzmq-ffi-perl - libclone-choose-perl From 70aa54d441b4b51b8508ac37b0500e57d1a4c971 Mon Sep 17 00:00:00 2001 From: Evan-Adam <152897682+Evan-Adam@users.noreply.github.com> Date: Thu, 29 Feb 2024 16:40:21 +0100 Subject: [PATCH 785/948] Mon-32851 [gorgone] service auto-discovery only the services associated to one host are created (#3160) Co-authored-by: smau <102975713+smau-centreon@users.noreply.github.com> --- gorgone/docs/modules.md | 68 +++++ .../docs/modules/centreon/autodiscovery.md | 30 +++ .../centreon-gorgone-autodiscovery-archi.png | Bin 0 -> 73606 bytes gorgone/gorgone/class/module.pm | 5 + .../modules/centreon/autodiscovery/class.pm | 235 +++++++++--------- .../autodiscovery/services/discovery.pm | 104 ++++---- gorgone/tests/gorgone-api.py | 12 + gorgone/tests/gorgone-auto-discovery.robot | 150 +++++++++++ 8 files changed, 444 insertions(+), 160 deletions(-) create mode 100755 gorgone/docs/modules/centreon/centreon-gorgone-autodiscovery-archi.png create mode 100644 gorgone/tests/gorgone-api.py create mode 100644 gorgone/tests/gorgone-auto-discovery.robot diff --git a/gorgone/docs/modules.md b/gorgone/docs/modules.md index fb1f1ee940a..f0587dd6e01 100644 --- a/gorgone/docs/modules.md +++ b/gorgone/docs/modules.md @@ -19,3 +19,71 @@ List of the available modules: * Plugins * [Newtest](../docs/modules/plugins/newtest.md) * [Scom](../docs/modules/plugins/scom.md) + +# Module implementation +Work in progress, may not be complete nor true. + + +Each module should have a hook.pm and a class.pm file with some mandatory function implemented. + +## hook.pm +Mainly used for creating the module process(es) + +and route event to it each time a new message is received by gorgone. + + +### const EVENTS [] +Array defining all events this module can process. Optionally add API endpoint for events. + +### const NAME +### const NAMESPACE + + +### gently() +Called by gorgone-core when stopping the module. +### register() +### init() +Called by library::loadmodule to initialize the module. Should create a child process as it's not made by gorgone-core. + +### routing() +### kill() +### check() +### broadcast() +### create_child() +Not strictly required, but present every time, used to instantiate a new child process by the init() function.\ +Inside the child process a class.pm object is created and the class->run method is started. + +## class.pm +This class must inherit module.pm package.\ + +This object is most of the time (maybe all ?) a singleton.\ + +It will be created by hook.pm when starting the module. +This is the workhorse that will process all events. + + +It seems like none of these methods will be called by gorgone-core, so naming is not required to follow this convention. + +(please keep the code base consistent if you make a new module). +### new() +Class constructor + +### run() +Will be called by hook.pm. This method should wait for event and dispatch them accordingly. + +Uses EV library to wait for new thing to do, either by waiting on the ZMQ file descriptor (fd) + +or with a periodic timer.\ +Generally waits for new data on ZMQ socket with EV::io(), and call event() when there is. + + +### event() +Reads data from ZMQ socket, and acts on it, generally by launching an action_* method to process the event. + +module.pm parent class has an event() method, so it's not mandatory to implement it. + +### action_*() +Method called by event() when a ZMQ message is found.\ + +Method name is in the form `action_eventname` where eventname is the name of the event in lowercase, as defined by the constant in hook.pm + diff --git a/gorgone/docs/modules/centreon/autodiscovery.md b/gorgone/docs/modules/centreon/autodiscovery.md index e9446458fa3..6f75b1d2647 100644 --- a/gorgone/docs/modules/centreon/autodiscovery.md +++ b/gorgone/docs/modules/centreon/autodiscovery.md @@ -316,3 +316,33 @@ curl --request POST "https://hostname:8443/api/centreon/autodiscovery/services" \"dry_run\": 1 }" ``` + +### Developer manual + +This module heavily uses the gorgone-action module to work. + +Here is a diagram of how these modules interact: + + +![image](./centreon-gorgone-autodiscovery-archi.png) + + +Dotted lines mean a ZMQ message is sent. Direct lines mean the function is called normally. + + +Each column represents a Linux thread, as Gorgone is multi-threaded. + + +For each ZMQ message, names are described in the [events section](#events) of each module, +and for putlog the second part is the 'code' used by gorgone-autodiscovery +and defined as constant in the [class.pm](../../../gorgone/modules/centreon/autodiscovery/class.pm) file. + + +The gorgone-action module does not send the result directly to the calling module. It sends a putlog message instead, processed by core.\ +Core keeps track of every module waiting for a particular event (use library.pm::addlistener to show interest in an event)\ + +and dispatch another message to the waiting module. + +gorgone-core also stores the log in a local sqlite database. + + diff --git a/gorgone/docs/modules/centreon/centreon-gorgone-autodiscovery-archi.png b/gorgone/docs/modules/centreon/centreon-gorgone-autodiscovery-archi.png new file mode 100755 index 0000000000000000000000000000000000000000..50784fdaeb0a59b30c8857b035c9775561ca177b GIT binary patch literal 73606 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 CL4WfA literal 0 HcmV?d00001 diff --git a/gorgone/gorgone/class/module.pm b/gorgone/gorgone/class/module.pm index f80b525dae1..b55e7178aa9 100644 --- a/gorgone/gorgone/class/module.pm +++ b/gorgone/gorgone/class/module.pm @@ -267,6 +267,10 @@ sub send_internal_action { "[$self->{module_id}]$self->{container} Cannot send message: " . $socket->last_strerror ); } + #For now we don't know why this call is needed. + # If we remove it, Gorgone start without error, but any discovery launched by the api won't work. + # if we always run it, the autodiscovery::service::discovery will launch this function, + # and eat some message that are destined to the autodiscovery::class object. $self->event(socket => $socket); } @@ -298,6 +302,7 @@ sub send_log { token => $options{token}, data => { code => $options{code}, etime => time(), instant => $options{instant}, token => $options{token}, data => $options{data} }, json_encode => 1 + }); } diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm index 14e860d41a6..b5d73fc4c57 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm @@ -58,35 +58,34 @@ use constant EXECUTION_MODE_PAUSE => 2; use constant MAX_INSERT_BY_QUERY => 100; my %handlers = (TERM => {}, HUP => {}); -my ($connector); sub new { my ($class, %options) = @_; - $connector = $class->SUPER::new(%options); - bless $connector, $class; + my $self = $class->SUPER::new(%options); + bless $self, $class; - $connector->{global_timeout} = (defined($options{config}->{global_timeout}) && + $self->{global_timeout} = (defined($options{config}->{global_timeout}) && $options{config}->{global_timeout} =~ /(\d+)/) ? $1 : 300; - $connector->{check_interval} = (defined($options{config}->{check_interval}) && + $self->{check_interval} = (defined($options{config}->{check_interval}) && $options{config}->{check_interval} =~ /(\d+)/) ? $1 : 15; - $connector->{tpapi_clapi_name} = defined($options{config}->{tpapi_clapi}) && $options{config}->{tpapi_clapi} ne '' ? $options{config}->{tpapi_clapi} : 'clapi'; - $connector->{tpapi_centreonv2_name} = defined($options{config}->{tpapi_centreonv2}) && $options{config}->{tpapi_centreonv2} ne '' ? + $self->{tpapi_clapi_name} = defined($options{config}->{tpapi_clapi}) && $options{config}->{tpapi_clapi} ne '' ? $options{config}->{tpapi_clapi} : 'clapi'; + $self->{tpapi_centreonv2_name} = defined($options{config}->{tpapi_centreonv2}) && $options{config}->{tpapi_centreonv2} ne '' ? $options{config}->{tpapi_centreonv2} : 'centreonv2'; - $connector->{is_module_installed} = 0; - $connector->{is_module_installed_check_interval} = 60; - $connector->{is_module_installed_last_check} = -1; + $self->{is_module_installed} = 0; + $self->{is_module_installed_check_interval} = 60; + $self->{is_module_installed_last_check} = -1; - $connector->{hdisco_synced} = 0; - $connector->{hdisco_synced_failed_time} = -1; - $connector->{hdisco_synced_ok_time} = -1; - $connector->{hdisco_jobs_tokens} = {}; - $connector->{hdisco_jobs_ids} = {}; + $self->{hdisco_synced} = 0; + $self->{hdisco_synced_failed_time} = -1; + $self->{hdisco_synced_ok_time} = -1; + $self->{hdisco_jobs_tokens} = {}; + $self->{hdisco_jobs_ids} = {}; - $connector->{service_discoveries} = {}; + $self->{service_discoveries} = {}; - $connector->set_signal_handlers(); - return $connector; + $self->set_signal_handlers(); + return $self; } sub set_signal_handlers { @@ -596,7 +595,7 @@ sub action_launchhostdiscovery { code => GORGONE_ACTION_FINISH_OK, token => $options{token}, instant => 1, - data => { + data => { message => $message } ); @@ -622,8 +621,8 @@ sub discovery_postcommand_result { if ($exit_code != 0) { $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - execute discovery postcommand failed job '$job_id'"); $self->update_job_status( - job_id => $job_id, - status => SAVE_FAILED, + job_id => $job_id, + status => SAVE_FAILED, message => $output ); return 1; @@ -631,8 +630,8 @@ sub discovery_postcommand_result { $self->{logger}->writeLogDebug("[autodiscovery] -class- host discovery - finished discovery postcommand job '$job_id'"); $self->update_job_status( - job_id => $job_id, - status => SAVE_FINISH, + job_id => $job_id, + status => SAVE_FINISH, message => 'Finished' ); } @@ -642,14 +641,14 @@ sub discovery_add_host_result { if ($options{builder}->{num_lines} == MAX_INSERT_BY_QUERY) { my ($status) = $self->{class_object_centreon}->custom_execute( - request => $options{builder}->{query} . $options{builder}->{values}, + request => $options{builder}->{query} . $options{builder}->{values}, bind_values => $options{builder}->{bind_values} ); if ($status == -1) { $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - failed to insert job '$options{job_id}' results"); $self->update_job_status( - job_id => $options{job_id}, - status => JOB_FAILED, + job_id => $options{job_id}, + status => JOB_FAILED, message => 'Failed to insert job results' ); return 1; @@ -702,8 +701,8 @@ sub discovery_command_result { if ($exit_code != 0) { $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - execute discovery plugin failed job '$job_id'"); $self->update_job_status( - job_id => $job_id, - status => JOB_FAILED, + job_id => $job_id, + status => JOB_FAILED, message => (defined($data->{data}->{result}->{stderr}) && $data->{data}->{result}->{stderr} ne '') ? $data->{data}->{result}->{stderr} : $data->{data}->{result}->{stdout} ); @@ -712,12 +711,12 @@ sub discovery_command_result { # Delete previous results my $query = "DELETE FROM mod_host_disco_host WHERE job_id = ?"; - my ($status) = $self->{class_object_centreon}->custom_execute(request => $query, bind_values => [$job_id]); + my ($status) = $self->{class_object_centreon}->custom_execute(request => $query, bind_values => [ $job_id ]); if ($status == -1) { $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - failed to delete previous job '$job_id' results"); $self->update_job_status( - job_id => $job_id, - status => JOB_FAILED, + job_id => $job_id, + status => JOB_FAILED, message => 'Failed to delete previous job results' ); return 1; @@ -725,11 +724,11 @@ sub discovery_command_result { # Add new results my $builder = { - query => "INSERT INTO mod_host_disco_host (job_id, discovery_result, uuid) VALUES ", - num_lines => 0, + query => "INSERT INTO mod_host_disco_host (job_id, discovery_result, uuid) VALUES ", + num_lines => 0, total_lines => 0, - values => '', - append => '', + values => '', + append => '', bind_values => [] }; my $duration = 0; @@ -754,8 +753,8 @@ sub discovery_command_result { } catch { $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - failed to decode discovery plugin response job '$job_id'"); $self->update_job_status( - job_id => $job_id, - status => JOB_FAILED, + job_id => $job_id, + status => JOB_FAILED, message => 'Failed to decode discovery plugin response' ); return 1; @@ -766,8 +765,8 @@ sub discovery_command_result { if ($status == -1) { $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - failed to insert job '$job_id' results"); $self->update_job_status( - job_id => $job_id, - status => JOB_FAILED, + job_id => $job_id, + status => JOB_FAILED, message => 'Failed to insert job results' ); return 1; @@ -781,12 +780,12 @@ sub discovery_command_result { $self->send_internal_action({ action => $post_command->{action}, - token => $self->{hdisco_jobs_ids}->{$job_id}->{token}, - data => { + token => $self->{hdisco_jobs_ids}->{$job_id}->{token}, + data => { instant => 1, content => [ { - command => $post_command->{command_line} . ' --token=' . $self->{tpapi_centreonv2}->get_token(), + command => $post_command->{command_line} . ' --token=' . $self->{tpapi_centreonv2}->get_token(), metadata => { job_id => $job_id, source => 'autodiscovery-host-job-postcommand' @@ -796,13 +795,13 @@ sub discovery_command_result { } }); } - + $self->{logger}->writeLogDebug("[autodiscovery] -class- host discovery - finished discovery command job '$job_id'"); $self->update_job_status( - job_id => $job_id, - status => JOB_FINISH, - message => 'Finished', - duration => $duration, + job_id => $job_id, + status => JOB_FINISH, + message => 'Finished', + duration => $duration, discovered_items => $builder->{total_lines} ); @@ -817,26 +816,26 @@ sub action_deletehostdiscoveryjob { $options{token} = $self->generate_token() if (!defined($options{token})); if (!$self->is_hdisco_synced()) { $self->send_log( - code => GORGONE_ACTION_FINISH_KO, + code => GORGONE_ACTION_FINISH_KO, token => $options{token}, - data => { + data => { message => 'host discovery synchronization issue' } ); - return ; + return; } my $data = $options{frame}->getData(); my $discovery_token = $data->{variables}->[0]; - my $job_id = (defined($discovery_token) && defined($self->{hdisco_jobs_tokens}->{$discovery_token})) ? + my $job_id = (defined($discovery_token) && defined($self->{hdisco_jobs_tokens}->{$discovery_token})) ? $self->{hdisco_jobs_tokens}->{$discovery_token} : undef; if (!defined($discovery_token) || $discovery_token eq '') { $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - missing ':token' variable to delete discovery"); $self->send_log( - code => GORGONE_ACTION_FINISH_KO, + code => GORGONE_ACTION_FINISH_KO, token => $options{token}, - data => { message => 'missing discovery token' } + data => { message => 'missing discovery token' } ); return 1; } @@ -846,9 +845,9 @@ sub action_deletehostdiscoveryjob { if ($status != 0) { $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - cannot get host discovery job '$job_id' - " . $self->{tpapi_centreonv2}->error()); $self->send_log( - code => GORGONE_ACTION_FINISH_KO, + code => GORGONE_ACTION_FINISH_KO, token => $options{token}, - data => { + data => { message => "cannot get job '$job_id'" } ); @@ -867,11 +866,11 @@ sub action_deletehostdiscoveryjob { } $self->send_log( - code => GORGONE_ACTION_FINISH_OK, + code => GORGONE_ACTION_FINISH_OK, token => $options{token}, - data => { message => 'job ' . $discovery_token . ' deleted' } + data => { message => 'job ' . $discovery_token . ' deleted' } ); - + return 0; } @@ -882,7 +881,7 @@ sub update_job_status { $values->{duration} = $options{duration} if (defined($options{duration})); $values->{discovered_items} = $options{discovered_items} if (defined($options{discovered_items})); $self->update_job_information( - values => $values, + values => $values, where_clause => [ { id => $options{job_id} @@ -897,7 +896,7 @@ sub update_job_information { return 1 if (!defined($options{where_clause}) || ref($options{where_clause}) ne 'ARRAY' || scalar($options{where_clause}) < 1); return 1 if (!defined($options{values}) || ref($options{values}) ne 'HASH' || !keys %{$options{values}}); - + my $query = "UPDATE mod_host_disco_job SET "; my @bind_values = (); my $append = ''; @@ -935,12 +934,12 @@ sub action_hostdiscoveryjoblistener { my $data = $options{frame}->getData(); my $job_id = $self->{hdisco_jobs_tokens}->{ $options{token} }; - if ($data->{code} == GORGONE_MODULE_ACTION_COMMAND_RESULT && + if ($data->{code} == GORGONE_MODULE_ACTION_COMMAND_RESULT && $data->{data}->{metadata}->{source} eq 'autodiscovery-host-job-discovery') { $self->discovery_command_result(%options); return 1; } - #if ($data->{code} == GORGONE_MODULE_ACTION_COMMAND_RESULT && + #if ($data->{code} == GORGONE_MODULE_ACTION_COMMAND_RESULT && # $data->{data}->{metadata}->{source} eq 'autodiscovery-host-job-postcommand') { # $self->discovery_postcommand_result(%options); # return 1; @@ -952,10 +951,10 @@ sub action_hostdiscoveryjoblistener { if ($data->{code} == GORGONE_ACTION_FINISH_KO) { $self->{hdisco_jobs_ids}->{$job_id}->{status} = JOB_FAILED; $self->update_job_information( - values => { - status => JOB_FAILED, - message => $message, - duration => 0, + values => { + status => JOB_FAILED, + message => $message, + duration => 0, discovered_items => 0 }, where_clause => [ @@ -1000,12 +999,12 @@ sub hdisco_add_joblistener { $self->send_internal_action({ action => 'ADDLISTENER', - data => [ + data => [ { identity => 'gorgoneautodiscovery', - event => 'HOSTDISCOVERYJOBLISTENER', - target => $_->{target}, - token => $_->{token}, + event => 'HOSTDISCOVERYJOBLISTENER', + target => $_->{target}, + token => $_->{token}, log_pace => $self->{check_interval} } ] @@ -1022,7 +1021,11 @@ Service Discovery part ********************** =cut - +sub getDiscoveryListener { + my ($self, $uuid) = @_; + return undef if (!defined($self->{service_discoveries}->{ $uuid })); + return $self->{service_discoveries}->{$uuid}->can("discoverylistener"), $self->{service_discoveries}->{$uuid} ; +} sub action_servicediscoverylistener { my ($self, %options) = @_; @@ -1032,19 +1035,27 @@ sub action_servicediscoverylistener { return 0 if ($options{token} !~ /^svc-disco-(.*?)-(\d+)-(\d+)/); my ($uuid, $rule_id, $host_id) = ($1, $2, $3); - return 0 if (!defined($self->{service_discoveries}->{ $uuid })); - $self->{service_discoveries}->{ $uuid }->discoverylistener( + my ($discoverylistener_method, $local_self) = $self->getDiscoveryListener($uuid); + return 0 if (!defined($discoverylistener_method)); + + $discoverylistener_method->( + $local_self, rule_id => $rule_id, host_id => $host_id, %options ); - if ($self->{service_discoveries}->{ $uuid }->is_finished()) { + if ($self->is_finished($uuid)) { delete $self->{service_discoveries}->{ $uuid }; } } +sub is_finished { + my ($self, $uuid) = @_; + return $self->{service_discoveries}->{ $uuid }->is_finished(); +} + sub action_launchservicediscovery { my ($self, %options) = @_; @@ -1052,28 +1063,28 @@ sub action_launchservicediscovery { $self->{service_number}++; my $svc_discovery = gorgone::modules::centreon::autodiscovery::services::discovery->new( - module_id => $self->{module_id}, - logger => $self->{logger}, - tpapi_clapi => $self->{tpapi_clapi}, - internal_socket => $self->{internal_socket}, - config => $self->{config}, - config_core => $self->{config_core}, - service_number => $self->{service_number}, - class_object_centreon => $self->{class_object_centreon}, + module_id => $self->{module_id}, + logger => $self->{logger}, + tpapi_clapi => $self->{tpapi_clapi}, + internal_socket => $self->{internal_socket}, + config => $self->{config}, + config_core => $self->{config_core}, + service_number => $self->{service_number}, + class_object_centreon => $self->{class_object_centreon}, class_object_centstorage => $self->{class_object_centstorage} ); + $self->{service_discoveries}->{ $svc_discovery->get_uuid() } = $svc_discovery; + my $status = $svc_discovery->launchdiscovery( token => $options{token}, frame => $options{frame} ); if ($status == -1) { $self->send_log( - code => GORGONE_ACTION_FINISH_KO, + code => GORGONE_ACTION_FINISH_KO, token => $options{token}, - data => { message => 'cannot launch discovery' } + data => { message => 'cannot launch discovery' } ); - } elsif ($status == 0) { - $self->{service_discoveries}->{ $svc_discovery->get_uuid() } = $svc_discovery; } } @@ -1116,9 +1127,9 @@ sub event { next if ($rv); my $raw = $frame->getFrame(); - $self->{logger}->writeLogDebug("[autodiscovery] Event: " . $$raw) if ($connector->{logger}->is_debug()); + $self->{logger}->writeLogDebug("[autodiscovery] Event: " . $$raw) if ($self->{logger}->is_debug()); if ($$raw =~ /^\[(.*?)\]/) { - if ((my $method = $connector->can('action_' . lc($1)))) { + if ((my $method = $self->can('action_' . lc($1)))) { next if ($frame->parse({ releaseFrame => 1, decode => 1 })); $method->($self, token => $frame->getToken(), frame => $frame); @@ -1128,11 +1139,12 @@ sub event { } sub periodic_exec { - $connector->is_module_installed(); - $connector->hdisco_sync(); + my $self = shift; + $self->is_module_installed(); + $self->hdisco_sync(); - if ($connector->{stop} == 1) { - $connector->{logger}->writeLogInfo("[autodiscovery] $$ has quit"); + if ($self->{stop} == 1) { + $self->{logger}->writeLogInfo("[autodiscovery] $$ has quit"); exit(0); } } @@ -1154,48 +1166,49 @@ sub run { } $self->{db_centreon} = gorgone::class::db->new( - dsn => $self->{config_db_centreon}->{dsn}, - user => $self->{config_db_centreon}->{username}, + dsn => $self->{config_db_centreon}->{dsn}, + user => $self->{config_db_centreon}->{username}, password => $self->{config_db_centreon}->{password}, - force => 2, - logger => $self->{logger} + force => 2, + logger => $self->{logger} ); $self->{db_centstorage} = gorgone::class::db->new( - dsn => $self->{config_db_centstorage}->{dsn}, - user => $self->{config_db_centstorage}->{username}, + dsn => $self->{config_db_centstorage}->{dsn}, + user => $self->{config_db_centstorage}->{username}, password => $self->{config_db_centstorage}->{password}, - force => 2, - logger => $self->{logger} + force => 2, + logger => $self->{logger} ); - + $self->{class_object_centreon} = gorgone::class::sqlquery->new( - logger => $self->{logger}, + logger => $self->{logger}, db_centreon => $self->{db_centreon} ); $self->{class_object_centstorage} = gorgone::class::sqlquery->new( - logger => $self->{logger}, + logger => $self->{logger}, db_centreon => $self->{db_centstorage} ); $self->{internal_socket} = gorgone::standard::library::connect_com( - context => $self->{zmq_context}, + context => $self->{zmq_context}, zmq_type => 'ZMQ_DEALER', - name => 'gorgone-autodiscovery', - logger => $self->{logger}, - type => $self->get_core_config(name => 'internal_com_type'), - path => $self->get_core_config(name => 'internal_com_path') + name => 'gorgone-autodiscovery', + logger => $self->{logger}, + type => $self->get_core_config(name => 'internal_com_type'), + path => $self->get_core_config(name => 'internal_com_path') ); $self->send_internal_action({ action => 'AUTODISCOVERYREADY', - data => {} + data => {} }); $self->is_module_installed(); $self->hdisco_sync(); - my $watcher_timer = $self->{loop}->timer(5, 5, \&periodic_exec); - my $watcher_io = $self->{loop}->io($self->{internal_socket}->get_fd(), EV::READ, sub { $connector->event() } ); + my $watcher_timer = $self->{loop}->timer(5, 5, sub {$self->periodic_exec()}); + my $watcher_io = $self->{loop}->io($self->{internal_socket}->get_fd(), EV::READ, sub {$self->event()}); $self->{loop}->run(); } 1; + diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm index d1e00408da0..c0f298afe87 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm @@ -20,7 +20,7 @@ package gorgone::modules::centreon::autodiscovery::services::discovery; -use base qw(gorgone::class::module); +use base qw(gorgone::modules::centreon::autodiscovery::class); use strict; use warnings; @@ -67,6 +67,11 @@ sub new { return $connector; } +sub getDiscoveryListener { + my ($self,$uuid) = @_; + return $self->can("discoverylistener"), $self; +} + sub database_init_transaction { my ($self, %options) = @_; @@ -80,7 +85,7 @@ sub database_init_transaction { sub database_commit_transaction { my ($self, %options) = @_; - + my $status = $self->{class_object_centreon}->commit(); if ($status == -1) { $self->{logger}->writeLogError("$@"); @@ -112,7 +117,7 @@ sub get_uuid { } sub is_finished { - my ($self, %options) = @_; + my $self = shift; return $self->{finished}; } @@ -196,7 +201,7 @@ sub restart_pollers { sub audit_update { my ($self, %options) = @_; - + return if ($self->{discovery}->{audit_enable} != 1); my $query = 'INSERT INTO log_action (action_log_date, object_type, object_id, object_name, action_type, log_contact_id) VALUES (?, ?, ?, ?, ?, ?)'; @@ -238,7 +243,7 @@ sub custom_variables { sub get_description { my ($self, %options) = @_; - + my $desc = $options{discovery_svc}->{service_name}; if (defined($self->{discovery}->{rules}->{ $options{rule_id} }->{rule_scan_display_custom}) && $self->{discovery}->{rules}->{ $options{rule_id} }->{rule_scan_display_custom} ne '') { local $SIG{__DIE__} = 'IGNORE'; @@ -258,13 +263,13 @@ sub get_description { sub link_service_autodisco { my ($self, %options) = @_; - + my $query = 'INSERT IGNORE INTO mod_auto_disco_rule_service_relation (rule_rule_id, service_service_id) VALUES (' . $options{rule_id} . ', ' . $options{service_id} . ')'; my ($status, $sth) = $self->{class_object_centreon}->custom_execute(request => $query); if ($status == -1) { return -1; } - + return 0; } @@ -274,9 +279,9 @@ sub update_service { my @journal = (); my @update_macros = (); my @insert_macros = (); - + if ($self->{discovery}->{is_manual} == 1) { - $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{discovery}->{ $options{discovery_svc}->{service_name} } = { + $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{discovery}->{ $options{discovery_svc}->{service_name} } = { type => 0, macros => {}, description => $self->get_description(%options) @@ -293,7 +298,7 @@ sub update_service { type => 'update', msg => 'template', rule_id => $options{rule_id} - }; + }; $self->{logger}->writeLogInfo("$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> service update template"); if ($self->{discovery}->{is_manual} == 1) { $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{discovery}->{ $options{discovery_svc}->{service_name} }->{service_template_model_stm_id} = $self->{discovery}->{rules}->{ $options{rule_id} }->{service_template_model_id}; @@ -358,7 +363,7 @@ sub update_service { return $self->database_error_rollback(message => "$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> cannot update service"); } } - + foreach (@update_macros) { my $query = 'UPDATE on_demand_macro_service SET svc_macro_value = ? WHERE svc_svc_id = ' . $options{service}->{id} . ' AND svc_macro_name = ?'; my ($status) = $self->{class_object_centreon}->custom_execute( @@ -379,7 +384,7 @@ sub update_service { return $self->database_error_rollback(message => "$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> cannot insert macro"); } } - + if ($self->link_service_autodisco(%options, service_id => $options{service}->{id}) == -1) { return $self->database_error_rollback(message => "$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> cannot link service to autodisco"); } @@ -391,19 +396,19 @@ sub update_service { if (defined($query_update{service_activate})) { $self->audit_update( - object_type => 'service', - action_type => 'enable', - object_id => $options{service}->{id}, - object_name => $options{discovery_svc}->{service_name}, + object_type => 'service', + action_type => 'enable', + object_id => $options{service}->{id}, + object_name => $options{discovery_svc}->{service_name}, contact_id => $self->{audit_user_id} ); } if (defined($query_update{service_template_model_stm_id})) { $self->audit_update( - object_type => 'service', - action_type => 'c', - object_id => $options{service}->{id}, - object_name => $options{discovery_svc}->{service_name}, + object_type => 'service', + action_type => 'c', + object_id => $options{service}->{id}, + object_name => $options{discovery_svc}->{service_name}, contact_id => $self->{audit_user_id}, fields => { service_template_model_stm_id => $query_update{service_template_model_stm_id} } ); @@ -414,10 +419,10 @@ sub update_service { sub create_service { my ($self, %options) = @_; - + if ($self->{discovery}->{is_manual} == 1) { - $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{discovery}->{ $options{discovery_svc}->{service_name} } = { - type => 1, + $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{discovery}->{ $options{discovery_svc}->{service_name} } = { + type => 1, service_template_model_stm_id => $self->{discovery}->{rules}->{ $options{rule_id} }->{service_template_model_id}, macros => {}, description => $self->get_description(%options) @@ -444,19 +449,19 @@ sub create_service { return $self->database_error_rollback(message => "$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> cannot create service"); } my $service_id = $self->{class_object_centreon}->{db_centreon}->last_insert_id(); - + $query = 'INSERT INTO host_service_relation (host_host_id, service_service_id) VALUES (' . $options{host_id} . ', ' . $service_id . ')'; ($status) = $self->{class_object_centreon}->custom_execute(request => $query); if ($status == -1) { return $self->database_error_rollback(message => "$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> cannot link service to host"); } - + $query = 'INSERT INTO extended_service_information (service_service_id) VALUES (' . $service_id . ')'; ($status) = $self->{class_object_centreon}->custom_execute(request => $query); if ($status == -1) { return $self->database_error_rollback(message => "$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> cannot service extended information"); } - + foreach (keys %{$options{macros}}) { $query = 'INSERT INTO on_demand_macro_service (svc_svc_id, svc_macro_name, svc_macro_value) VALUES (' . $service_id . ', ?, ?)'; ($status) = $self->{class_object_centreon}->custom_execute( @@ -471,21 +476,21 @@ sub create_service { if ($self->link_service_autodisco(%options, service_id => $service_id) == -1) { return $self->database_error_rollback(message => "$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> cannot link service to autodisco"); } - + return -1 if ($self->database_commit_transaction() == -1); $self->{discovery}->{pollers_reload}->{ $options{poller_id} } = 1; $self->audit_update( - object_type => 'service', - action_type => 'a', - object_id => $service_id, + object_type => 'service', + action_type => 'a', + object_id => $service_id, object_name => $options{discovery_svc}->{service_name}, contact_id => $self->{audit_user_id}, fields => { - service_template_model_id => $self->{discovery}->{rules}->{ $options{rule_id} }->{service_template_model_id}, - service_description => $options{discovery_svc}->{service_name}, - service_register => '1', + service_template_model_id => $self->{discovery}->{rules}->{ $options{rule_id} }->{service_template_model_id}, + service_description => $options{discovery_svc}->{service_name}, + service_register => '1', service_hPars => $options{host_id} } ); @@ -495,7 +500,7 @@ sub create_service { sub crud_service { my ($self, %options) = @_; - + my $service_id; if (!defined($options{service})) { $service_id = $self->create_service(%options); @@ -511,18 +516,18 @@ sub crud_service { } else { $service_id = $self->update_service(%options); } - + return 0; } sub disable_services { my ($self, %options) = @_; - + return if ($self->{discovery}->{rules}->{ $options{rule_id} }->{rule_disable} != 1 || !defined($self->{discovery}->{rules}->{ $options{rule_id} }->{linked_services}->{ $options{host_id} })); foreach my $service (keys %{$self->{discovery}->{rules}->{ $options{rule_id} }->{linked_services}->{ $options{host_id} }}) { my $service_description = $self->{discovery}->{rules}->{ $options{rule_id} }->{linked_services}->{ $options{host_id} }->{$service}->{service_description}; - if (!defined($options{discovery_svc}->{discovered_services}->{$service_description}) && + if (!defined($options{discovery_svc}->{discovered_services}->{$service_description}) && $self->{discovery}->{rules}->{ $options{rule_id} }->{linked_services}->{ $options{host_id} }->{$service}->{service_activate} == 1) { $self->{logger}->writeLogInfo("$options{logger_pre_message} -> disable service '" . $service_description . "'"); next if ($self->{discovery}->{dry_run} == 1); @@ -533,18 +538,18 @@ sub disable_services { $self->{logger}->writeLogInfo("$options{logger_pre_message} -> cannot disable service '" . $service_description . "'"); next; } - + push @{$self->{discovery}->{journal}}, { host_name => $self->{discovery}->{hosts}->{ $options{host_id} }->{host_name}, service_name => $service_description, type => 'disable', rule_id => $options{rule_id} - }; + }; $self->{discovery}->{pollers_reload}->{ $options{poller_id} } = 1; $self->audit_update( - object_type => 'service', - action_type => 'disable', - object_id => $service, + object_type => 'service', + action_type => 'disable', + object_id => $service, object_name => $service_description, contact_id => $self->{audit_user_id} ); @@ -616,7 +621,7 @@ sub service_response_parsing { discovery_svc => $discovery_svc, rule => $self->{discovery}->{rules}->{ $options{rule_id} } ); - + my ($status, $service) = gorgone::modules::centreon::autodiscovery::services::resources::get_service( class_object_centreon => $self->{class_object_centreon}, host_id => $options{host_id}, @@ -676,19 +681,20 @@ sub discoverylistener { $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{data} = $data->{data}; } } + } elsif ($data->{code} == GORGONE_ACTION_FINISH_KO) { if ($self->{discovery}->{is_manual} == 1) { $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{failed} = 1; $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{message} = $data->{data}->{message}; } $self->{discovery}->{failed_discoveries}++; + } else { return 0; } $self->{service_current_commands_poller}->{ $self->{discovery}->{hosts}->{ $options{host_id} }->{poller_id} }--; $self->service_execute_commands(); - $self->{discovery}->{done_discoveries}++; my $progress = $self->{discovery}->{done_discoveries} * 100 / $self->{discovery}->{count_discoveries}; my $div = int(int($progress) / 5); @@ -700,7 +706,7 @@ sub discoverylistener { instant => 1, data => { message => 'current progress', - complete => sprintf('%.2f', $progress) + complete => sprintf('%.2f', $progress) } ); } @@ -738,7 +744,7 @@ sub service_execute_commands { foreach my $poller_id (keys %{$self->{discovery}->{rules}->{$rule_id}->{hosts}}) { next if (scalar(@{$self->{discovery}->{rules}->{$rule_id}->{hosts}->{$poller_id}}) <= 0); $self->{service_current_commands_poller}->{$poller_id} = 0 if (!defined($self->{service_current_commands_poller}->{$poller_id})); - + while (1) { last if ($self->{service_current_commands_poller}->{$poller_id} >= $self->{service_parrallel_commands_poller}); my $host_id = shift @{$self->{discovery}->{rules}->{$rule_id}->{hosts}->{$poller_id}}; @@ -755,7 +761,7 @@ sub service_execute_commands { ); $self->{logger}->writeLogInfo("[autodiscovery] -servicediscovery- $self->{uuid} [" . - $self->{discovery}->{rules}->{$rule_id}->{rule_alias} . "] [" . + $self->{discovery}->{rules}->{$rule_id}->{rule_alias} . "] [" . $self->{service_pollers}->{$poller_id}->{name} . "] [" . $host->{host_name} . "] -> substitute string: " . $command ); @@ -862,7 +868,7 @@ sub launchdiscovery { # get rules ################ $self->{logger}->writeLogInfo("[autodiscovery] -servicediscovery- $self->{uuid} load rules configuration"); - + ($status, $message, my $rules) = gorgone::modules::centreon::autodiscovery::services::resources::get_rules( class_object_centreon => $self->{class_object_centreon}, filter_rules => $data->{content}->{filter_rules}, @@ -893,7 +899,7 @@ sub launchdiscovery { $self->send_log_msg_error(token => $options{token}, subname => 'servicediscovery', number => $self->{uuid}, message => $message); return -1; } - + if (!defined($hosts) || scalar(keys %$hosts) == 0) { $self->{logger}->writeLogInfo("[autodiscovery] -servicediscovery- $self->{uuid} no hosts found for rule '" . $options{rule}->{rule_alias} . "'"); next; diff --git a/gorgone/tests/gorgone-api.py b/gorgone/tests/gorgone-api.py new file mode 100644 index 00000000000..54856e798d4 --- /dev/null +++ b/gorgone/tests/gorgone-api.py @@ -0,0 +1,12 @@ +def is_gorgone_finished(http_data): + + if 'error' in http_data.keys() and http_data["error"]: + raise Exception("Found an error in gorgone response : " + http_data["error"]) + for elem in http_data["data"]: + + if 'data' in elem.keys(): + if 'cannot launch discovery' in elem['data']: + raise Exception("gorgone can't launch discovery : " + http_data["data"]) + if elem["data"] and "discovery finished" in elem["data"]: + return 1 + return 0 diff --git a/gorgone/tests/gorgone-auto-discovery.robot b/gorgone/tests/gorgone-auto-discovery.robot new file mode 100644 index 00000000000..2ef0925fab5 --- /dev/null +++ b/gorgone/tests/gorgone-auto-discovery.robot @@ -0,0 +1,150 @@ +*** Settings *** +Documentation centreon gorgone auto discovery tests + +Library OperatingSystem +Library RequestsLibrary +Library gorgone-api.py +Library DatabaseLibrary + +Suite Setup Connect To Database pymysql ${DBName} ${DBUser} ${DBPass} ${DBHost} ${DBPort} +Suite Teardown Disconnect From Database +Test Setup Prepare Db ${nb_hosts} +Test Teardown Remove Services And Host +Test Template Gorgone Test + + +*** Variables *** +${nb_hosts} 50 +${DBName} centreon +${DBHost} localhost +${DBUser} robotTest +${DBPass} password +${DBPort} 3306 +${sqlRequest} +... select * from service JOIN host_service_relation ON service_service_id = service_id JOIN host ON host_service_relation.host_host_id = host.host_id WHERE service_description like 'Disk-/' +... AND host.host_alias LIKE 'gorgone_auto_discovery_test_%'; +# To work this test need : +# access to the centreon database with write permission +# Snmp must answer all disk info on 127.0.0.1 +# Centreon must have "generic snmp" plugin pack installed and "OS-Linux-SNMP-Disk-Name" discovery rule enabled. + + +*** Test Cases *** nb_host +Test Gorgone 1 50 +Test Gorgone 2 50 +Test Gorgone 3 50 +Test Gorgone 4 50 +Test Gorgone 5 50 +Test Gorgone 6 50 +Test Gorgone 7 50 +Test Gorgone 8 50 +Test Gorgone 9 50 +Test Gorgone 10 50 +Test Gorgone 11 50 +Test Gorgone 12 50 +Test Gorgone 13 50 +Test Gorgone 14 50 +Test Gorgone 15 50 +Test Gorgone 16 50 +Test Gorgone 17 50 +Test Gorgone 18 50 +Test Gorgone 19 50 +Test Gorgone 20 50 +Test Gorgone 21 50 +Test Gorgone 22 50 +Test Gorgone 23 50 +Test Gorgone 24 50 +Test Gorgone 25 50 +Test Gorgone 26 50 +Test Gorgone 27 50 +Test Gorgone 28 50 +Test Gorgone 29 50 +Test Gorgone 30 50 +Test Gorgone 31 50 +Test Gorgone 32 50 +Test Gorgone 33 50 +Test Gorgone 34 50 +Test Gorgone 35 50 +Test Gorgone 36 50 +Test Gorgone 37 50 +Test Gorgone 38 50 +Test Gorgone 39 50 +Test Gorgone 40 50 +Test Gorgone 41 50 +Test Gorgone 42 50 +Test Gorgone 43 50 +Test Gorgone 44 50 +Test Gorgone 45 50 +Test Gorgone 46 50 +Test Gorgone 47 50 +Test Gorgone 48 50 +Test Gorgone 49 50 +Test Gorgone 50 50 + + +*** Keywords *** +gorgone test + [Arguments] ${nb_hosts}=50 + Run Test + # Remove Services And Host + +prepare db + [Arguments] ${nb_hosts}=50 + + FOR ${id} IN RANGE 0 ${nb_hosts} + ${insert_host_req}= Catenate + ... SEPARATOR= + ... INSERT IGNORE INTO host (host_name, host_alias, host_address, host_active_checks_enabled, + ... host_passive_checks_enabled, host_obsess_over_host, host_check_freshness, host_event_handler_enabled, + ... host_flap_detection_enabled, host_retain_status_information, host_retain_nonstatus_information, + ... host_notifications_enabled, contact_additive_inheritance, cg_additive_inheritance, host_locked, host_register, host_activate) + ... VALUES('gorgone_auto_discovery_test_${id}', 'gorgone_auto_discovery_test_${id}', '127.0.0.1', '2', '2', '2', '2', '2', '2', '2', '2', '2', + ... '0', '0', '0', '1', '1'); + + ${output}= Execute SQL String ${insert_host_req} + + ${output2}= Execute SQL String + ... INSERT INTO host_template_relation (`host_host_id`, `host_tpl_id`, `order`) VALUES((select LAST_INSERT_ID()), (select host_id from host where host_name = 'OS-Linux-SNMP-custom'), 1); + ${output3}= Execute SQL String + ... INSERT INTO `ns_host_relation` (`host_host_id`, `nagios_server_id`) VALUES ((select LAST_INSERT_ID()), '1'); + END + +run test + &{data}= Create dictionary + ${launchDiscoveryResponse}= POST + ... http://127.0.0.1:8085/api/centreon/autodiscovery/services + ... json=${data} + ... expected_status=200 + + Log ${launchDiscoveryResponse.json()}[token] + Sleep 1 + ${getDiscoveryResultResponse}= GET + ... http://127.0.0.1:8085/api/log/${launchDiscoveryResponse.json()}[token] + ... expected_status=200 + ${IsGorgoneDone}= Is Gorgone Finished ${getDiscoveryResultResponse.json()} + WHILE ${IsGorgoneDone} == 0 + ${getDiscoveryResultResponse}= GET + ... http://127.0.0.1:8085/api/log/${launchDiscoveryResponse.json()}[token] + ... expected_status=200 + ${IsGorgoneDone}= Is Gorgone Finished ${getDiscoveryResultResponse.json()} + Sleep 2 + END + DatabaseLibrary.Row Count is equal To X ${sqlRequest} ${nb_hosts} + +remove services and host + ${delete_service}= Catenate + ... DELETE service FROM service + ... JOIN host_service_relation + ... ON service_service_id = service_id + ... JOIN host + ... ON host.host_id = host_service_relation.host_host_id + ... WHERE service_description like 'Disk-/' + ... AND host.host_alias LIKE 'gorgone_auto_discovery_test_%' + + ${output}= Execute SQL String ${delete_service} + ${output}= Execute SQL String + ... DELETE host FROM host where host.host_alias LIKE 'gorgone_auto_discovery_test_%' + ${output}= Execute SQL String + ... DELETE host_template_relation FROM host_template_relation WHERE host_host_id NOT IN (SELECT host_id FROM host) + ${output}= Execute SQL String + ... DELETE FROM ns_host_relation WHERE host_host_id NOT IN (SELECT host_id FROM host) From 90294f47f12e64fcce8da3d5c9de6e74d44cb3f7 Mon Sep 17 00:00:00 2001 From: "Gabriel M. Aguirre" <46610157+gabrielmagit@users.noreply.github.com> Date: Fri, 1 Mar 2024 15:34:07 +0100 Subject: [PATCH 786/948] fix (gorgone): add cmd to whitelist, to fix inability to link Remote Server to Central (#3473) --- gorgone/config/gorgoned-central-ssh.yml | 1 + gorgone/config/gorgoned-central-zmq.yml | 1 + gorgone/config/gorgoned-poller.yml | 1 + gorgone/config/gorgoned-remote-ssh.yml | 5 +++-- gorgone/config/gorgoned-remote-zmq.yml | 1 + gorgone/contrib/gorgone_config_init.pl | 1 + gorgone/docs/client_server_zmq.md | 2 +- gorgone/docs/migration.md | 1 + gorgone/docs/modules/core/action.md | 1 + gorgone/docs/poller_pull_configuration.md | 1 + gorgone/docs/rebound_configuration.md | 1 + 11 files changed, 13 insertions(+), 3 deletions(-) diff --git a/gorgone/config/gorgoned-central-ssh.yml b/gorgone/config/gorgoned-central-ssh.yml index 069d53679df..04103c968ab 100644 --- a/gorgone/config/gorgoned-central-ssh.yml +++ b/gorgone/config/gorgoned-central-ssh.yml @@ -44,6 +44,7 @@ configuration: - ^centreon - ^mkdir - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ + - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ - name: proxy package: gorgone::modules::core::proxy::hooks diff --git a/gorgone/config/gorgoned-central-zmq.yml b/gorgone/config/gorgoned-central-zmq.yml index 3fe322d1654..965850c66bf 100644 --- a/gorgone/config/gorgoned-central-zmq.yml +++ b/gorgone/config/gorgoned-central-zmq.yml @@ -68,6 +68,7 @@ configuration: - ^centreon - ^mkdir - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ + - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ - name: proxy package: gorgone::modules::core::proxy::hooks diff --git a/gorgone/config/gorgoned-poller.yml b/gorgone/config/gorgoned-poller.yml index 72e0a499ebf..d7a2092abb0 100644 --- a/gorgone/config/gorgoned-poller.yml +++ b/gorgone/config/gorgoned-poller.yml @@ -26,6 +26,7 @@ configuration: - ^centreon - ^mkdir - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ + - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ - name: engine package: gorgone::modules::centreon::engine::hooks diff --git a/gorgone/config/gorgoned-remote-ssh.yml b/gorgone/config/gorgoned-remote-ssh.yml index e0ceacdf18e..3e710caafff 100644 --- a/gorgone/config/gorgoned-remote-ssh.yml +++ b/gorgone/config/gorgoned-remote-ssh.yml @@ -3,11 +3,11 @@ description: Configuration example in a SSH environment for Remote server configuration: centreon: database: - db_configuration: + db_configuration: dsn: "mysql:host=localhost;dbname=centreon" username: centreon password: centreon - db_realtime: + db_realtime: dsn: "mysql:host=localhost;dbname=centreon_storage" username: centreon password: centreon @@ -31,6 +31,7 @@ configuration: - ^centreon - ^mkdir - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ + - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ - name: proxy package: gorgone::modules::core::proxy::hooks diff --git a/gorgone/config/gorgoned-remote-zmq.yml b/gorgone/config/gorgoned-remote-zmq.yml index ecb0dd10474..5d75c5371ac 100644 --- a/gorgone/config/gorgoned-remote-zmq.yml +++ b/gorgone/config/gorgoned-remote-zmq.yml @@ -36,6 +36,7 @@ configuration: - ^centreon - ^mkdir - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ + - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ - name: proxy package: gorgone::modules::core::proxy::hooks diff --git a/gorgone/contrib/gorgone_config_init.pl b/gorgone/contrib/gorgone_config_init.pl index 1f58077c8ec..51250f6b805 100644 --- a/gorgone/contrib/gorgone_config_init.pl +++ b/gorgone/contrib/gorgone_config_init.pl @@ -134,6 +134,7 @@ sub write_gorgone_config { - ^centreon - ^mkdir - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ + - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ - name: proxy package: gorgone::modules::core::proxy::hooks diff --git a/gorgone/docs/client_server_zmq.md b/gorgone/docs/client_server_zmq.md index 0ef1d30e1d5..079e12087e1 100644 --- a/gorgone/docs/client_server_zmq.md +++ b/gorgone/docs/client_server_zmq.md @@ -39,7 +39,7 @@ $ perl /usr/local/bin/gorgone_key_thumbprint.pl --key-path='/var/spool/centreon/ #### Server -In the */etc/centreon/confid.d/20-gorgoned.yaml* configuration file, add the following directives under the +In the */etc/centreon/config.d/20-gorgoned.yaml* configuration file, add the following directives under the *gorgonecore* section: diff --git a/gorgone/docs/migration.md b/gorgone/docs/migration.md index ac99d073018..71d5048a704 100644 --- a/gorgone/docs/migration.md +++ b/gorgone/docs/migration.md @@ -66,6 +66,7 @@ configuration: - ^centreon - ^mkdir - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ + - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ - name: cron package: gorgone::modules::core::cron::hooks diff --git a/gorgone/docs/modules/core/action.md b/gorgone/docs/modules/core/action.md index aceaea4ffc5..13c57fd958d 100644 --- a/gorgone/docs/modules/core/action.md +++ b/gorgone/docs/modules/core/action.md @@ -32,6 +32,7 @@ allowed_cmds: - ^centreon - ^mkdir - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ + - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ ``` ## Events diff --git a/gorgone/docs/poller_pull_configuration.md b/gorgone/docs/poller_pull_configuration.md index 4333a3a1f8a..91c7a23f162 100644 --- a/gorgone/docs/poller_pull_configuration.md +++ b/gorgone/docs/poller_pull_configuration.md @@ -51,6 +51,7 @@ gorgone: - ^centreon - ^mkdir - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ + - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ - name: engine package: gorgone::modules::centreon::engine::hooks diff --git a/gorgone/docs/rebound_configuration.md b/gorgone/docs/rebound_configuration.md index 5a7a5fe84df..26de145ec8b 100644 --- a/gorgone/docs/rebound_configuration.md +++ b/gorgone/docs/rebound_configuration.md @@ -55,6 +55,7 @@ gorgone: - ^centreon - ^mkdir - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ + - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ - name: engine package: gorgone::modules::centreon::engine::hooks From 57a110c55435226c985dcdf1a94b445878241e95 Mon Sep 17 00:00:00 2001 From: tuntoja <58987095+tuntoja@users.noreply.github.com> Date: Wed, 6 Mar 2024 08:51:37 +0100 Subject: [PATCH 787/948] fix(ci): handle concurrent hotfix and release (#3329) Co-authored-by: Kevin Duret <kduret@centreon.com> Co-authored-by: jeremyjaouen <61694165+jeremyjaouen@users.noreply.github.com> Co-authored-by: Adrien Morais-Mestre <31647811+adr-mo@users.noreply.github.com> Co-authored-by: Dmytro Iosypenko <108675430+dmyios@users.noreply.github.com> --- .github/workflows/gorgone.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index c0d527c3230..e520feb400b 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -165,6 +165,8 @@ jobs: artifactory_token: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }} cache_key: ${{ github.sha }}-${{ github.run_id }}-rpm-${{ matrix.distrib }} stability: ${{ needs.get-version.outputs.stability }} + release_type: ${{ needs.get-version.outputs.release_type }} + release_cloud: ${{ needs.get-version.outputs.release_cloud }} deliver-deb: runs-on: [self-hosted, common] @@ -188,6 +190,8 @@ jobs: artifactory_token: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }} cache_key: ${{ github.sha }}-${{ github.run_id }}-deb-${{ matrix.distrib }} stability: ${{ needs.get-version.outputs.stability }} + release_type: ${{ needs.get-version.outputs.release_type }} + release_cloud: ${{ needs.get-version.outputs.release_cloud }} promote: needs: [get-version] @@ -211,3 +215,5 @@ jobs: minor_version: ${{ needs.get-version.outputs.minor_version }} stability: ${{ needs.get-version.outputs.stability }} github_base_ref: ${{ github.base_ref }} + release_type: ${{ needs.get-version.outputs.release_type }} + release_cloud: ${{ needs.get-version.outputs.release_cloud }} From 2fc92a66f0b7571bc7f9cb5fe42f1ce565aec37a Mon Sep 17 00:00:00 2001 From: tuntoja <58987095+tuntoja@users.noreply.github.com> Date: Wed, 6 Mar 2024 08:51:37 +0100 Subject: [PATCH 788/948] fix(ci): handle concurrent hotfix and release (#3329) Co-authored-by: Kevin Duret <kduret@centreon.com> Co-authored-by: jeremyjaouen <61694165+jeremyjaouen@users.noreply.github.com> Co-authored-by: Adrien Morais-Mestre <31647811+adr-mo@users.noreply.github.com> Co-authored-by: Dmytro Iosypenko <108675430+dmyios@users.noreply.github.com> --- .github/workflows/gorgone.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index c0d527c3230..e520feb400b 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -165,6 +165,8 @@ jobs: artifactory_token: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }} cache_key: ${{ github.sha }}-${{ github.run_id }}-rpm-${{ matrix.distrib }} stability: ${{ needs.get-version.outputs.stability }} + release_type: ${{ needs.get-version.outputs.release_type }} + release_cloud: ${{ needs.get-version.outputs.release_cloud }} deliver-deb: runs-on: [self-hosted, common] @@ -188,6 +190,8 @@ jobs: artifactory_token: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }} cache_key: ${{ github.sha }}-${{ github.run_id }}-deb-${{ matrix.distrib }} stability: ${{ needs.get-version.outputs.stability }} + release_type: ${{ needs.get-version.outputs.release_type }} + release_cloud: ${{ needs.get-version.outputs.release_cloud }} promote: needs: [get-version] @@ -211,3 +215,5 @@ jobs: minor_version: ${{ needs.get-version.outputs.minor_version }} stability: ${{ needs.get-version.outputs.stability }} github_base_ref: ${{ github.base_ref }} + release_type: ${{ needs.get-version.outputs.release_type }} + release_cloud: ${{ needs.get-version.outputs.release_cloud }} From 5c09643ce4d1b6bc948431e0add0e3292dca774e Mon Sep 17 00:00:00 2001 From: tuntoja <58987095+tuntoja@users.noreply.github.com> Date: Wed, 6 Mar 2024 08:51:37 +0100 Subject: [PATCH 789/948] fix(ci): handle concurrent hotfix and release (#3329) Co-authored-by: Kevin Duret <kduret@centreon.com> Co-authored-by: jeremyjaouen <61694165+jeremyjaouen@users.noreply.github.com> Co-authored-by: Adrien Morais-Mestre <31647811+adr-mo@users.noreply.github.com> Co-authored-by: Dmytro Iosypenko <108675430+dmyios@users.noreply.github.com> From 984fc32467de283cd46743762d968b52473db591 Mon Sep 17 00:00:00 2001 From: Laurent Calvet <lcalvet@centreon.com> Date: Mon, 11 Mar 2024 16:43:40 +0100 Subject: [PATCH 790/948] fix(autodiscovery): fix regex for gorgone to execute run_save_discovered_host script for discovered hosts (#3572) --- gorgone/config/gorgoned-central-ssh.yml | 2 +- gorgone/config/gorgoned-central-zmq.yml | 2 +- gorgone/config/gorgoned-poller.yml | 2 +- gorgone/config/gorgoned-remote-ssh.yml | 2 +- gorgone/config/gorgoned-remote-zmq.yml | 2 +- gorgone/contrib/gorgone_config_init.pl | 2 +- gorgone/docs/migration.md | 2 +- gorgone/docs/modules/core/action.md | 2 +- gorgone/docs/poller_pull_configuration.md | 2 +- gorgone/docs/rebound_configuration.md | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/gorgone/config/gorgoned-central-ssh.yml b/gorgone/config/gorgoned-central-ssh.yml index 04103c968ab..144c3f47562 100644 --- a/gorgone/config/gorgoned-central-ssh.yml +++ b/gorgone/config/gorgoned-central-ssh.yml @@ -43,7 +43,7 @@ configuration: - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ - ^centreon - ^mkdir - - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ - name: proxy diff --git a/gorgone/config/gorgoned-central-zmq.yml b/gorgone/config/gorgoned-central-zmq.yml index 965850c66bf..a7a0c1d12e0 100644 --- a/gorgone/config/gorgoned-central-zmq.yml +++ b/gorgone/config/gorgoned-central-zmq.yml @@ -67,7 +67,7 @@ configuration: - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ - ^centreon - ^mkdir - - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ - name: proxy diff --git a/gorgone/config/gorgoned-poller.yml b/gorgone/config/gorgoned-poller.yml index d7a2092abb0..735e864311d 100644 --- a/gorgone/config/gorgoned-poller.yml +++ b/gorgone/config/gorgoned-poller.yml @@ -25,7 +25,7 @@ configuration: - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ - ^centreon - ^mkdir - - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ - name: engine diff --git a/gorgone/config/gorgoned-remote-ssh.yml b/gorgone/config/gorgoned-remote-ssh.yml index 3e710caafff..fea645f45af 100644 --- a/gorgone/config/gorgoned-remote-ssh.yml +++ b/gorgone/config/gorgoned-remote-ssh.yml @@ -30,7 +30,7 @@ configuration: - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ - ^centreon - ^mkdir - - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ - name: proxy diff --git a/gorgone/config/gorgoned-remote-zmq.yml b/gorgone/config/gorgoned-remote-zmq.yml index 5d75c5371ac..2eb9872d8f0 100644 --- a/gorgone/config/gorgoned-remote-zmq.yml +++ b/gorgone/config/gorgoned-remote-zmq.yml @@ -35,7 +35,7 @@ configuration: - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ - ^centreon - ^mkdir - - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ - name: proxy diff --git a/gorgone/contrib/gorgone_config_init.pl b/gorgone/contrib/gorgone_config_init.pl index 51250f6b805..b5702888331 100644 --- a/gorgone/contrib/gorgone_config_init.pl +++ b/gorgone/contrib/gorgone_config_init.pl @@ -133,7 +133,7 @@ sub write_gorgone_config { - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ - ^centreon - ^mkdir - - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ - name: proxy diff --git a/gorgone/docs/migration.md b/gorgone/docs/migration.md index 71d5048a704..ce115818a1b 100644 --- a/gorgone/docs/migration.md +++ b/gorgone/docs/migration.md @@ -65,7 +65,7 @@ configuration: - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ - ^centreon - ^mkdir - - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ - name: cron diff --git a/gorgone/docs/modules/core/action.md b/gorgone/docs/modules/core/action.md index 13c57fd958d..c885dcd0cac 100644 --- a/gorgone/docs/modules/core/action.md +++ b/gorgone/docs/modules/core/action.md @@ -31,7 +31,7 @@ allowed_cmds: - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ - ^centreon - ^mkdir - - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ ``` diff --git a/gorgone/docs/poller_pull_configuration.md b/gorgone/docs/poller_pull_configuration.md index 91c7a23f162..852c145b1c3 100644 --- a/gorgone/docs/poller_pull_configuration.md +++ b/gorgone/docs/poller_pull_configuration.md @@ -50,7 +50,7 @@ gorgone: - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ - ^centreon - ^mkdir - - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ - name: engine diff --git a/gorgone/docs/rebound_configuration.md b/gorgone/docs/rebound_configuration.md index 26de145ec8b..4a83f5af2ce 100644 --- a/gorgone/docs/rebound_configuration.md +++ b/gorgone/docs/rebound_configuration.md @@ -54,7 +54,7 @@ gorgone: - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ - ^centreon - ^mkdir - - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ - name: engine From f49d480c2788f27d0bb21c645a1c2b30bdd163cb Mon Sep 17 00:00:00 2001 From: Laurent Calvet <lcalvet@centreon.com> Date: Thu, 14 Mar 2024 16:41:36 +0100 Subject: [PATCH 791/948] fix(autodiscovery): fix regex for gorgone to execute run_save_discovered_host script for discovered hosts (#3559) --- gorgone/config/gorgoned-central-ssh.yml | 2 +- gorgone/config/gorgoned-central-zmq.yml | 2 +- gorgone/config/gorgoned-poller.yml | 2 +- gorgone/config/gorgoned-remote-ssh.yml | 2 +- gorgone/config/gorgoned-remote-zmq.yml | 2 +- gorgone/contrib/gorgone_config_init.pl | 2 +- gorgone/docs/migration.md | 2 +- gorgone/docs/modules/core/action.md | 2 +- gorgone/docs/poller_pull_configuration.md | 2 +- gorgone/docs/rebound_configuration.md | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/gorgone/config/gorgoned-central-ssh.yml b/gorgone/config/gorgoned-central-ssh.yml index 04103c968ab..144c3f47562 100644 --- a/gorgone/config/gorgoned-central-ssh.yml +++ b/gorgone/config/gorgoned-central-ssh.yml @@ -43,7 +43,7 @@ configuration: - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ - ^centreon - ^mkdir - - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ - name: proxy diff --git a/gorgone/config/gorgoned-central-zmq.yml b/gorgone/config/gorgoned-central-zmq.yml index 965850c66bf..a7a0c1d12e0 100644 --- a/gorgone/config/gorgoned-central-zmq.yml +++ b/gorgone/config/gorgoned-central-zmq.yml @@ -67,7 +67,7 @@ configuration: - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ - ^centreon - ^mkdir - - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ - name: proxy diff --git a/gorgone/config/gorgoned-poller.yml b/gorgone/config/gorgoned-poller.yml index d7a2092abb0..735e864311d 100644 --- a/gorgone/config/gorgoned-poller.yml +++ b/gorgone/config/gorgoned-poller.yml @@ -25,7 +25,7 @@ configuration: - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ - ^centreon - ^mkdir - - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ - name: engine diff --git a/gorgone/config/gorgoned-remote-ssh.yml b/gorgone/config/gorgoned-remote-ssh.yml index 3e710caafff..fea645f45af 100644 --- a/gorgone/config/gorgoned-remote-ssh.yml +++ b/gorgone/config/gorgoned-remote-ssh.yml @@ -30,7 +30,7 @@ configuration: - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ - ^centreon - ^mkdir - - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ - name: proxy diff --git a/gorgone/config/gorgoned-remote-zmq.yml b/gorgone/config/gorgoned-remote-zmq.yml index 5d75c5371ac..2eb9872d8f0 100644 --- a/gorgone/config/gorgoned-remote-zmq.yml +++ b/gorgone/config/gorgoned-remote-zmq.yml @@ -35,7 +35,7 @@ configuration: - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ - ^centreon - ^mkdir - - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ - name: proxy diff --git a/gorgone/contrib/gorgone_config_init.pl b/gorgone/contrib/gorgone_config_init.pl index 51250f6b805..b5702888331 100644 --- a/gorgone/contrib/gorgone_config_init.pl +++ b/gorgone/contrib/gorgone_config_init.pl @@ -133,7 +133,7 @@ sub write_gorgone_config { - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ - ^centreon - ^mkdir - - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ - name: proxy diff --git a/gorgone/docs/migration.md b/gorgone/docs/migration.md index 71d5048a704..ce115818a1b 100644 --- a/gorgone/docs/migration.md +++ b/gorgone/docs/migration.md @@ -65,7 +65,7 @@ configuration: - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ - ^centreon - ^mkdir - - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ - name: cron diff --git a/gorgone/docs/modules/core/action.md b/gorgone/docs/modules/core/action.md index 13c57fd958d..c885dcd0cac 100644 --- a/gorgone/docs/modules/core/action.md +++ b/gorgone/docs/modules/core/action.md @@ -31,7 +31,7 @@ allowed_cmds: - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ - ^centreon - ^mkdir - - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ ``` diff --git a/gorgone/docs/poller_pull_configuration.md b/gorgone/docs/poller_pull_configuration.md index 91c7a23f162..852c145b1c3 100644 --- a/gorgone/docs/poller_pull_configuration.md +++ b/gorgone/docs/poller_pull_configuration.md @@ -50,7 +50,7 @@ gorgone: - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ - ^centreon - ^mkdir - - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ - name: engine diff --git a/gorgone/docs/rebound_configuration.md b/gorgone/docs/rebound_configuration.md index 26de145ec8b..4a83f5af2ce 100644 --- a/gorgone/docs/rebound_configuration.md +++ b/gorgone/docs/rebound_configuration.md @@ -54,7 +54,7 @@ gorgone: - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ - ^centreon - ^mkdir - - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host --all --job-id=\d+ --export-conf --token=\S+$ + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ - name: engine From f518403d138846843673b2697a30b59e6edb75da Mon Sep 17 00:00:00 2001 From: Sophie Depassio <sdepassio@centreon.com> Date: Fri, 16 Feb 2024 11:57:59 +0100 Subject: [PATCH 792/948] commit only if autocommit is not enabled --- gorgone/gorgone/class/db.pm | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/gorgone/gorgone/class/db.pm b/gorgone/gorgone/class/db.pm index 53ff07d37a3..847678411fd 100644 --- a/gorgone/gorgone/class/db.pm +++ b/gorgone/gorgone/class/db.pm @@ -185,17 +185,20 @@ sub transaction_mode { sub commit { my ($self) = @_; - if (!defined($self->{instance})) { - $self->{transaction_begin} = 0; - return -1; - } + # Commit only if autocommit isn't enabled + if ($self->{instance}->{AutoCommit} != 1) { + if (!defined($self->{instance})) { + $self->{transaction_begin} = 0; + return -1; + } - my $status = $self->{instance}->commit(); - $self->{transaction_begin} = 0; + my $status = $self->{instance}->commit(); + $self->{transaction_begin} = 0; - if (!$status) { - $self->error($self->{instance}->errstr, 'commit'); - return -1; + if (!$status) { + $self->error($self->{instance}->errstr, 'commit'); + return -1; + } } return 0; From 687488091fd8f81855ada8cc0439ad59dacb0547 Mon Sep 17 00:00:00 2001 From: David Boucher <dboucher@centreon.com> Date: Tue, 19 Mar 2024 10:03:17 +0100 Subject: [PATCH 793/948] Revert "Mon-32851 [gorgone] service auto-discovery only the services associated to one host are created (#3160)" This reverts commit 70aa54d441b4b51b8508ac37b0500e57d1a4c971. --- gorgone/docs/modules.md | 68 ----- .../docs/modules/centreon/autodiscovery.md | 30 --- .../centreon-gorgone-autodiscovery-archi.png | Bin 73606 -> 0 bytes gorgone/gorgone/class/module.pm | 5 - .../modules/centreon/autodiscovery/class.pm | 235 +++++++++--------- .../autodiscovery/services/discovery.pm | 104 ++++---- gorgone/tests/gorgone-api.py | 12 - gorgone/tests/gorgone-auto-discovery.robot | 150 ----------- 8 files changed, 160 insertions(+), 444 deletions(-) delete mode 100755 gorgone/docs/modules/centreon/centreon-gorgone-autodiscovery-archi.png delete mode 100644 gorgone/tests/gorgone-api.py delete mode 100644 gorgone/tests/gorgone-auto-discovery.robot diff --git a/gorgone/docs/modules.md b/gorgone/docs/modules.md index f0587dd6e01..fb1f1ee940a 100644 --- a/gorgone/docs/modules.md +++ b/gorgone/docs/modules.md @@ -19,71 +19,3 @@ List of the available modules: * Plugins * [Newtest](../docs/modules/plugins/newtest.md) * [Scom](../docs/modules/plugins/scom.md) - -# Module implementation -Work in progress, may not be complete nor true. - - -Each module should have a hook.pm and a class.pm file with some mandatory function implemented. - -## hook.pm -Mainly used for creating the module process(es) - -and route event to it each time a new message is received by gorgone. - - -### const EVENTS [] -Array defining all events this module can process. Optionally add API endpoint for events. - -### const NAME -### const NAMESPACE - - -### gently() -Called by gorgone-core when stopping the module. -### register() -### init() -Called by library::loadmodule to initialize the module. Should create a child process as it's not made by gorgone-core. - -### routing() -### kill() -### check() -### broadcast() -### create_child() -Not strictly required, but present every time, used to instantiate a new child process by the init() function.\ -Inside the child process a class.pm object is created and the class->run method is started. - -## class.pm -This class must inherit module.pm package.\ - -This object is most of the time (maybe all ?) a singleton.\ - -It will be created by hook.pm when starting the module. -This is the workhorse that will process all events. - - -It seems like none of these methods will be called by gorgone-core, so naming is not required to follow this convention. - -(please keep the code base consistent if you make a new module). -### new() -Class constructor - -### run() -Will be called by hook.pm. This method should wait for event and dispatch them accordingly. - -Uses EV library to wait for new thing to do, either by waiting on the ZMQ file descriptor (fd) - -or with a periodic timer.\ -Generally waits for new data on ZMQ socket with EV::io(), and call event() when there is. - - -### event() -Reads data from ZMQ socket, and acts on it, generally by launching an action_* method to process the event. - -module.pm parent class has an event() method, so it's not mandatory to implement it. - -### action_*() -Method called by event() when a ZMQ message is found.\ - -Method name is in the form `action_eventname` where eventname is the name of the event in lowercase, as defined by the constant in hook.pm - diff --git a/gorgone/docs/modules/centreon/autodiscovery.md b/gorgone/docs/modules/centreon/autodiscovery.md index 6f75b1d2647..e9446458fa3 100644 --- a/gorgone/docs/modules/centreon/autodiscovery.md +++ b/gorgone/docs/modules/centreon/autodiscovery.md @@ -316,33 +316,3 @@ curl --request POST "https://hostname:8443/api/centreon/autodiscovery/services" \"dry_run\": 1 }" ``` - -### Developer manual - -This module heavily uses the gorgone-action module to work. - -Here is a diagram of how these modules interact: - - -![image](./centreon-gorgone-autodiscovery-archi.png) - - -Dotted lines mean a ZMQ message is sent. Direct lines mean the function is called normally. - - -Each column represents a Linux thread, as Gorgone is multi-threaded. - - -For each ZMQ message, names are described in the [events section](#events) of each module, -and for putlog the second part is the 'code' used by gorgone-autodiscovery -and defined as constant in the [class.pm](../../../gorgone/modules/centreon/autodiscovery/class.pm) file. - - -The gorgone-action module does not send the result directly to the calling module. It sends a putlog message instead, processed by core.\ -Core keeps track of every module waiting for a particular event (use library.pm::addlistener to show interest in an event)\ - -and dispatch another message to the waiting module. - -gorgone-core also stores the log in a local sqlite database. - - diff --git a/gorgone/docs/modules/centreon/centreon-gorgone-autodiscovery-archi.png b/gorgone/docs/modules/centreon/centreon-gorgone-autodiscovery-archi.png deleted file mode 100755 index 50784fdaeb0a59b30c8857b035c9775561ca177b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 73606 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 CL4WfA diff --git a/gorgone/gorgone/class/module.pm b/gorgone/gorgone/class/module.pm index b55e7178aa9..f80b525dae1 100644 --- a/gorgone/gorgone/class/module.pm +++ b/gorgone/gorgone/class/module.pm @@ -267,10 +267,6 @@ sub send_internal_action { "[$self->{module_id}]$self->{container} Cannot send message: " . $socket->last_strerror ); } - #For now we don't know why this call is needed. - # If we remove it, Gorgone start without error, but any discovery launched by the api won't work. - # if we always run it, the autodiscovery::service::discovery will launch this function, - # and eat some message that are destined to the autodiscovery::class object. $self->event(socket => $socket); } @@ -302,7 +298,6 @@ sub send_log { token => $options{token}, data => { code => $options{code}, etime => time(), instant => $options{instant}, token => $options{token}, data => $options{data} }, json_encode => 1 - }); } diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm index b5d73fc4c57..14e860d41a6 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm @@ -58,34 +58,35 @@ use constant EXECUTION_MODE_PAUSE => 2; use constant MAX_INSERT_BY_QUERY => 100; my %handlers = (TERM => {}, HUP => {}); +my ($connector); sub new { my ($class, %options) = @_; - my $self = $class->SUPER::new(%options); - bless $self, $class; + $connector = $class->SUPER::new(%options); + bless $connector, $class; - $self->{global_timeout} = (defined($options{config}->{global_timeout}) && + $connector->{global_timeout} = (defined($options{config}->{global_timeout}) && $options{config}->{global_timeout} =~ /(\d+)/) ? $1 : 300; - $self->{check_interval} = (defined($options{config}->{check_interval}) && + $connector->{check_interval} = (defined($options{config}->{check_interval}) && $options{config}->{check_interval} =~ /(\d+)/) ? $1 : 15; - $self->{tpapi_clapi_name} = defined($options{config}->{tpapi_clapi}) && $options{config}->{tpapi_clapi} ne '' ? $options{config}->{tpapi_clapi} : 'clapi'; - $self->{tpapi_centreonv2_name} = defined($options{config}->{tpapi_centreonv2}) && $options{config}->{tpapi_centreonv2} ne '' ? + $connector->{tpapi_clapi_name} = defined($options{config}->{tpapi_clapi}) && $options{config}->{tpapi_clapi} ne '' ? $options{config}->{tpapi_clapi} : 'clapi'; + $connector->{tpapi_centreonv2_name} = defined($options{config}->{tpapi_centreonv2}) && $options{config}->{tpapi_centreonv2} ne '' ? $options{config}->{tpapi_centreonv2} : 'centreonv2'; - $self->{is_module_installed} = 0; - $self->{is_module_installed_check_interval} = 60; - $self->{is_module_installed_last_check} = -1; + $connector->{is_module_installed} = 0; + $connector->{is_module_installed_check_interval} = 60; + $connector->{is_module_installed_last_check} = -1; - $self->{hdisco_synced} = 0; - $self->{hdisco_synced_failed_time} = -1; - $self->{hdisco_synced_ok_time} = -1; - $self->{hdisco_jobs_tokens} = {}; - $self->{hdisco_jobs_ids} = {}; + $connector->{hdisco_synced} = 0; + $connector->{hdisco_synced_failed_time} = -1; + $connector->{hdisco_synced_ok_time} = -1; + $connector->{hdisco_jobs_tokens} = {}; + $connector->{hdisco_jobs_ids} = {}; - $self->{service_discoveries} = {}; + $connector->{service_discoveries} = {}; - $self->set_signal_handlers(); - return $self; + $connector->set_signal_handlers(); + return $connector; } sub set_signal_handlers { @@ -595,7 +596,7 @@ sub action_launchhostdiscovery { code => GORGONE_ACTION_FINISH_OK, token => $options{token}, instant => 1, - data => { + data => { message => $message } ); @@ -621,8 +622,8 @@ sub discovery_postcommand_result { if ($exit_code != 0) { $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - execute discovery postcommand failed job '$job_id'"); $self->update_job_status( - job_id => $job_id, - status => SAVE_FAILED, + job_id => $job_id, + status => SAVE_FAILED, message => $output ); return 1; @@ -630,8 +631,8 @@ sub discovery_postcommand_result { $self->{logger}->writeLogDebug("[autodiscovery] -class- host discovery - finished discovery postcommand job '$job_id'"); $self->update_job_status( - job_id => $job_id, - status => SAVE_FINISH, + job_id => $job_id, + status => SAVE_FINISH, message => 'Finished' ); } @@ -641,14 +642,14 @@ sub discovery_add_host_result { if ($options{builder}->{num_lines} == MAX_INSERT_BY_QUERY) { my ($status) = $self->{class_object_centreon}->custom_execute( - request => $options{builder}->{query} . $options{builder}->{values}, + request => $options{builder}->{query} . $options{builder}->{values}, bind_values => $options{builder}->{bind_values} ); if ($status == -1) { $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - failed to insert job '$options{job_id}' results"); $self->update_job_status( - job_id => $options{job_id}, - status => JOB_FAILED, + job_id => $options{job_id}, + status => JOB_FAILED, message => 'Failed to insert job results' ); return 1; @@ -701,8 +702,8 @@ sub discovery_command_result { if ($exit_code != 0) { $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - execute discovery plugin failed job '$job_id'"); $self->update_job_status( - job_id => $job_id, - status => JOB_FAILED, + job_id => $job_id, + status => JOB_FAILED, message => (defined($data->{data}->{result}->{stderr}) && $data->{data}->{result}->{stderr} ne '') ? $data->{data}->{result}->{stderr} : $data->{data}->{result}->{stdout} ); @@ -711,12 +712,12 @@ sub discovery_command_result { # Delete previous results my $query = "DELETE FROM mod_host_disco_host WHERE job_id = ?"; - my ($status) = $self->{class_object_centreon}->custom_execute(request => $query, bind_values => [ $job_id ]); + my ($status) = $self->{class_object_centreon}->custom_execute(request => $query, bind_values => [$job_id]); if ($status == -1) { $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - failed to delete previous job '$job_id' results"); $self->update_job_status( - job_id => $job_id, - status => JOB_FAILED, + job_id => $job_id, + status => JOB_FAILED, message => 'Failed to delete previous job results' ); return 1; @@ -724,11 +725,11 @@ sub discovery_command_result { # Add new results my $builder = { - query => "INSERT INTO mod_host_disco_host (job_id, discovery_result, uuid) VALUES ", - num_lines => 0, + query => "INSERT INTO mod_host_disco_host (job_id, discovery_result, uuid) VALUES ", + num_lines => 0, total_lines => 0, - values => '', - append => '', + values => '', + append => '', bind_values => [] }; my $duration = 0; @@ -753,8 +754,8 @@ sub discovery_command_result { } catch { $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - failed to decode discovery plugin response job '$job_id'"); $self->update_job_status( - job_id => $job_id, - status => JOB_FAILED, + job_id => $job_id, + status => JOB_FAILED, message => 'Failed to decode discovery plugin response' ); return 1; @@ -765,8 +766,8 @@ sub discovery_command_result { if ($status == -1) { $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - failed to insert job '$job_id' results"); $self->update_job_status( - job_id => $job_id, - status => JOB_FAILED, + job_id => $job_id, + status => JOB_FAILED, message => 'Failed to insert job results' ); return 1; @@ -780,12 +781,12 @@ sub discovery_command_result { $self->send_internal_action({ action => $post_command->{action}, - token => $self->{hdisco_jobs_ids}->{$job_id}->{token}, - data => { + token => $self->{hdisco_jobs_ids}->{$job_id}->{token}, + data => { instant => 1, content => [ { - command => $post_command->{command_line} . ' --token=' . $self->{tpapi_centreonv2}->get_token(), + command => $post_command->{command_line} . ' --token=' . $self->{tpapi_centreonv2}->get_token(), metadata => { job_id => $job_id, source => 'autodiscovery-host-job-postcommand' @@ -795,13 +796,13 @@ sub discovery_command_result { } }); } - + $self->{logger}->writeLogDebug("[autodiscovery] -class- host discovery - finished discovery command job '$job_id'"); $self->update_job_status( - job_id => $job_id, - status => JOB_FINISH, - message => 'Finished', - duration => $duration, + job_id => $job_id, + status => JOB_FINISH, + message => 'Finished', + duration => $duration, discovered_items => $builder->{total_lines} ); @@ -816,26 +817,26 @@ sub action_deletehostdiscoveryjob { $options{token} = $self->generate_token() if (!defined($options{token})); if (!$self->is_hdisco_synced()) { $self->send_log( - code => GORGONE_ACTION_FINISH_KO, + code => GORGONE_ACTION_FINISH_KO, token => $options{token}, - data => { + data => { message => 'host discovery synchronization issue' } ); - return; + return ; } my $data = $options{frame}->getData(); my $discovery_token = $data->{variables}->[0]; - my $job_id = (defined($discovery_token) && defined($self->{hdisco_jobs_tokens}->{$discovery_token})) ? + my $job_id = (defined($discovery_token) && defined($self->{hdisco_jobs_tokens}->{$discovery_token})) ? $self->{hdisco_jobs_tokens}->{$discovery_token} : undef; if (!defined($discovery_token) || $discovery_token eq '') { $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - missing ':token' variable to delete discovery"); $self->send_log( - code => GORGONE_ACTION_FINISH_KO, + code => GORGONE_ACTION_FINISH_KO, token => $options{token}, - data => { message => 'missing discovery token' } + data => { message => 'missing discovery token' } ); return 1; } @@ -845,9 +846,9 @@ sub action_deletehostdiscoveryjob { if ($status != 0) { $self->{logger}->writeLogError("[autodiscovery] -class- host discovery - cannot get host discovery job '$job_id' - " . $self->{tpapi_centreonv2}->error()); $self->send_log( - code => GORGONE_ACTION_FINISH_KO, + code => GORGONE_ACTION_FINISH_KO, token => $options{token}, - data => { + data => { message => "cannot get job '$job_id'" } ); @@ -866,11 +867,11 @@ sub action_deletehostdiscoveryjob { } $self->send_log( - code => GORGONE_ACTION_FINISH_OK, + code => GORGONE_ACTION_FINISH_OK, token => $options{token}, - data => { message => 'job ' . $discovery_token . ' deleted' } + data => { message => 'job ' . $discovery_token . ' deleted' } ); - + return 0; } @@ -881,7 +882,7 @@ sub update_job_status { $values->{duration} = $options{duration} if (defined($options{duration})); $values->{discovered_items} = $options{discovered_items} if (defined($options{discovered_items})); $self->update_job_information( - values => $values, + values => $values, where_clause => [ { id => $options{job_id} @@ -896,7 +897,7 @@ sub update_job_information { return 1 if (!defined($options{where_clause}) || ref($options{where_clause}) ne 'ARRAY' || scalar($options{where_clause}) < 1); return 1 if (!defined($options{values}) || ref($options{values}) ne 'HASH' || !keys %{$options{values}}); - + my $query = "UPDATE mod_host_disco_job SET "; my @bind_values = (); my $append = ''; @@ -934,12 +935,12 @@ sub action_hostdiscoveryjoblistener { my $data = $options{frame}->getData(); my $job_id = $self->{hdisco_jobs_tokens}->{ $options{token} }; - if ($data->{code} == GORGONE_MODULE_ACTION_COMMAND_RESULT && + if ($data->{code} == GORGONE_MODULE_ACTION_COMMAND_RESULT && $data->{data}->{metadata}->{source} eq 'autodiscovery-host-job-discovery') { $self->discovery_command_result(%options); return 1; } - #if ($data->{code} == GORGONE_MODULE_ACTION_COMMAND_RESULT && + #if ($data->{code} == GORGONE_MODULE_ACTION_COMMAND_RESULT && # $data->{data}->{metadata}->{source} eq 'autodiscovery-host-job-postcommand') { # $self->discovery_postcommand_result(%options); # return 1; @@ -951,10 +952,10 @@ sub action_hostdiscoveryjoblistener { if ($data->{code} == GORGONE_ACTION_FINISH_KO) { $self->{hdisco_jobs_ids}->{$job_id}->{status} = JOB_FAILED; $self->update_job_information( - values => { - status => JOB_FAILED, - message => $message, - duration => 0, + values => { + status => JOB_FAILED, + message => $message, + duration => 0, discovered_items => 0 }, where_clause => [ @@ -999,12 +1000,12 @@ sub hdisco_add_joblistener { $self->send_internal_action({ action => 'ADDLISTENER', - data => [ + data => [ { identity => 'gorgoneautodiscovery', - event => 'HOSTDISCOVERYJOBLISTENER', - target => $_->{target}, - token => $_->{token}, + event => 'HOSTDISCOVERYJOBLISTENER', + target => $_->{target}, + token => $_->{token}, log_pace => $self->{check_interval} } ] @@ -1021,11 +1022,7 @@ Service Discovery part ********************** =cut -sub getDiscoveryListener { - my ($self, $uuid) = @_; - return undef if (!defined($self->{service_discoveries}->{ $uuid })); - return $self->{service_discoveries}->{$uuid}->can("discoverylistener"), $self->{service_discoveries}->{$uuid} ; -} + sub action_servicediscoverylistener { my ($self, %options) = @_; @@ -1035,27 +1032,19 @@ sub action_servicediscoverylistener { return 0 if ($options{token} !~ /^svc-disco-(.*?)-(\d+)-(\d+)/); my ($uuid, $rule_id, $host_id) = ($1, $2, $3); + return 0 if (!defined($self->{service_discoveries}->{ $uuid })); - my ($discoverylistener_method, $local_self) = $self->getDiscoveryListener($uuid); - return 0 if (!defined($discoverylistener_method)); - - $discoverylistener_method->( - $local_self, + $self->{service_discoveries}->{ $uuid }->discoverylistener( rule_id => $rule_id, host_id => $host_id, %options ); - if ($self->is_finished($uuid)) { + if ($self->{service_discoveries}->{ $uuid }->is_finished()) { delete $self->{service_discoveries}->{ $uuid }; } } -sub is_finished { - my ($self, $uuid) = @_; - return $self->{service_discoveries}->{ $uuid }->is_finished(); -} - sub action_launchservicediscovery { my ($self, %options) = @_; @@ -1063,28 +1052,28 @@ sub action_launchservicediscovery { $self->{service_number}++; my $svc_discovery = gorgone::modules::centreon::autodiscovery::services::discovery->new( - module_id => $self->{module_id}, - logger => $self->{logger}, - tpapi_clapi => $self->{tpapi_clapi}, - internal_socket => $self->{internal_socket}, - config => $self->{config}, - config_core => $self->{config_core}, - service_number => $self->{service_number}, - class_object_centreon => $self->{class_object_centreon}, + module_id => $self->{module_id}, + logger => $self->{logger}, + tpapi_clapi => $self->{tpapi_clapi}, + internal_socket => $self->{internal_socket}, + config => $self->{config}, + config_core => $self->{config_core}, + service_number => $self->{service_number}, + class_object_centreon => $self->{class_object_centreon}, class_object_centstorage => $self->{class_object_centstorage} ); - $self->{service_discoveries}->{ $svc_discovery->get_uuid() } = $svc_discovery; - my $status = $svc_discovery->launchdiscovery( token => $options{token}, frame => $options{frame} ); if ($status == -1) { $self->send_log( - code => GORGONE_ACTION_FINISH_KO, + code => GORGONE_ACTION_FINISH_KO, token => $options{token}, - data => { message => 'cannot launch discovery' } + data => { message => 'cannot launch discovery' } ); + } elsif ($status == 0) { + $self->{service_discoveries}->{ $svc_discovery->get_uuid() } = $svc_discovery; } } @@ -1127,9 +1116,9 @@ sub event { next if ($rv); my $raw = $frame->getFrame(); - $self->{logger}->writeLogDebug("[autodiscovery] Event: " . $$raw) if ($self->{logger}->is_debug()); + $self->{logger}->writeLogDebug("[autodiscovery] Event: " . $$raw) if ($connector->{logger}->is_debug()); if ($$raw =~ /^\[(.*?)\]/) { - if ((my $method = $self->can('action_' . lc($1)))) { + if ((my $method = $connector->can('action_' . lc($1)))) { next if ($frame->parse({ releaseFrame => 1, decode => 1 })); $method->($self, token => $frame->getToken(), frame => $frame); @@ -1139,12 +1128,11 @@ sub event { } sub periodic_exec { - my $self = shift; - $self->is_module_installed(); - $self->hdisco_sync(); + $connector->is_module_installed(); + $connector->hdisco_sync(); - if ($self->{stop} == 1) { - $self->{logger}->writeLogInfo("[autodiscovery] $$ has quit"); + if ($connector->{stop} == 1) { + $connector->{logger}->writeLogInfo("[autodiscovery] $$ has quit"); exit(0); } } @@ -1166,49 +1154,48 @@ sub run { } $self->{db_centreon} = gorgone::class::db->new( - dsn => $self->{config_db_centreon}->{dsn}, - user => $self->{config_db_centreon}->{username}, + dsn => $self->{config_db_centreon}->{dsn}, + user => $self->{config_db_centreon}->{username}, password => $self->{config_db_centreon}->{password}, - force => 2, - logger => $self->{logger} + force => 2, + logger => $self->{logger} ); $self->{db_centstorage} = gorgone::class::db->new( - dsn => $self->{config_db_centstorage}->{dsn}, - user => $self->{config_db_centstorage}->{username}, + dsn => $self->{config_db_centstorage}->{dsn}, + user => $self->{config_db_centstorage}->{username}, password => $self->{config_db_centstorage}->{password}, - force => 2, - logger => $self->{logger} + force => 2, + logger => $self->{logger} ); - + $self->{class_object_centreon} = gorgone::class::sqlquery->new( - logger => $self->{logger}, + logger => $self->{logger}, db_centreon => $self->{db_centreon} ); $self->{class_object_centstorage} = gorgone::class::sqlquery->new( - logger => $self->{logger}, + logger => $self->{logger}, db_centreon => $self->{db_centstorage} ); $self->{internal_socket} = gorgone::standard::library::connect_com( - context => $self->{zmq_context}, + context => $self->{zmq_context}, zmq_type => 'ZMQ_DEALER', - name => 'gorgone-autodiscovery', - logger => $self->{logger}, - type => $self->get_core_config(name => 'internal_com_type'), - path => $self->get_core_config(name => 'internal_com_path') + name => 'gorgone-autodiscovery', + logger => $self->{logger}, + type => $self->get_core_config(name => 'internal_com_type'), + path => $self->get_core_config(name => 'internal_com_path') ); $self->send_internal_action({ action => 'AUTODISCOVERYREADY', - data => {} + data => {} }); $self->is_module_installed(); $self->hdisco_sync(); - my $watcher_timer = $self->{loop}->timer(5, 5, sub {$self->periodic_exec()}); - my $watcher_io = $self->{loop}->io($self->{internal_socket}->get_fd(), EV::READ, sub {$self->event()}); + my $watcher_timer = $self->{loop}->timer(5, 5, \&periodic_exec); + my $watcher_io = $self->{loop}->io($self->{internal_socket}->get_fd(), EV::READ, sub { $connector->event() } ); $self->{loop}->run(); } 1; - diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm index c0f298afe87..d1e00408da0 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm @@ -20,7 +20,7 @@ package gorgone::modules::centreon::autodiscovery::services::discovery; -use base qw(gorgone::modules::centreon::autodiscovery::class); +use base qw(gorgone::class::module); use strict; use warnings; @@ -67,11 +67,6 @@ sub new { return $connector; } -sub getDiscoveryListener { - my ($self,$uuid) = @_; - return $self->can("discoverylistener"), $self; -} - sub database_init_transaction { my ($self, %options) = @_; @@ -85,7 +80,7 @@ sub database_init_transaction { sub database_commit_transaction { my ($self, %options) = @_; - + my $status = $self->{class_object_centreon}->commit(); if ($status == -1) { $self->{logger}->writeLogError("$@"); @@ -117,7 +112,7 @@ sub get_uuid { } sub is_finished { - my $self = shift; + my ($self, %options) = @_; return $self->{finished}; } @@ -201,7 +196,7 @@ sub restart_pollers { sub audit_update { my ($self, %options) = @_; - + return if ($self->{discovery}->{audit_enable} != 1); my $query = 'INSERT INTO log_action (action_log_date, object_type, object_id, object_name, action_type, log_contact_id) VALUES (?, ?, ?, ?, ?, ?)'; @@ -243,7 +238,7 @@ sub custom_variables { sub get_description { my ($self, %options) = @_; - + my $desc = $options{discovery_svc}->{service_name}; if (defined($self->{discovery}->{rules}->{ $options{rule_id} }->{rule_scan_display_custom}) && $self->{discovery}->{rules}->{ $options{rule_id} }->{rule_scan_display_custom} ne '') { local $SIG{__DIE__} = 'IGNORE'; @@ -263,13 +258,13 @@ sub get_description { sub link_service_autodisco { my ($self, %options) = @_; - + my $query = 'INSERT IGNORE INTO mod_auto_disco_rule_service_relation (rule_rule_id, service_service_id) VALUES (' . $options{rule_id} . ', ' . $options{service_id} . ')'; my ($status, $sth) = $self->{class_object_centreon}->custom_execute(request => $query); if ($status == -1) { return -1; } - + return 0; } @@ -279,9 +274,9 @@ sub update_service { my @journal = (); my @update_macros = (); my @insert_macros = (); - + if ($self->{discovery}->{is_manual} == 1) { - $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{discovery}->{ $options{discovery_svc}->{service_name} } = { + $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{discovery}->{ $options{discovery_svc}->{service_name} } = { type => 0, macros => {}, description => $self->get_description(%options) @@ -298,7 +293,7 @@ sub update_service { type => 'update', msg => 'template', rule_id => $options{rule_id} - }; + }; $self->{logger}->writeLogInfo("$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> service update template"); if ($self->{discovery}->{is_manual} == 1) { $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{discovery}->{ $options{discovery_svc}->{service_name} }->{service_template_model_stm_id} = $self->{discovery}->{rules}->{ $options{rule_id} }->{service_template_model_id}; @@ -363,7 +358,7 @@ sub update_service { return $self->database_error_rollback(message => "$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> cannot update service"); } } - + foreach (@update_macros) { my $query = 'UPDATE on_demand_macro_service SET svc_macro_value = ? WHERE svc_svc_id = ' . $options{service}->{id} . ' AND svc_macro_name = ?'; my ($status) = $self->{class_object_centreon}->custom_execute( @@ -384,7 +379,7 @@ sub update_service { return $self->database_error_rollback(message => "$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> cannot insert macro"); } } - + if ($self->link_service_autodisco(%options, service_id => $options{service}->{id}) == -1) { return $self->database_error_rollback(message => "$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> cannot link service to autodisco"); } @@ -396,19 +391,19 @@ sub update_service { if (defined($query_update{service_activate})) { $self->audit_update( - object_type => 'service', - action_type => 'enable', - object_id => $options{service}->{id}, - object_name => $options{discovery_svc}->{service_name}, + object_type => 'service', + action_type => 'enable', + object_id => $options{service}->{id}, + object_name => $options{discovery_svc}->{service_name}, contact_id => $self->{audit_user_id} ); } if (defined($query_update{service_template_model_stm_id})) { $self->audit_update( - object_type => 'service', - action_type => 'c', - object_id => $options{service}->{id}, - object_name => $options{discovery_svc}->{service_name}, + object_type => 'service', + action_type => 'c', + object_id => $options{service}->{id}, + object_name => $options{discovery_svc}->{service_name}, contact_id => $self->{audit_user_id}, fields => { service_template_model_stm_id => $query_update{service_template_model_stm_id} } ); @@ -419,10 +414,10 @@ sub update_service { sub create_service { my ($self, %options) = @_; - + if ($self->{discovery}->{is_manual} == 1) { - $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{discovery}->{ $options{discovery_svc}->{service_name} } = { - type => 1, + $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{discovery}->{ $options{discovery_svc}->{service_name} } = { + type => 1, service_template_model_stm_id => $self->{discovery}->{rules}->{ $options{rule_id} }->{service_template_model_id}, macros => {}, description => $self->get_description(%options) @@ -449,19 +444,19 @@ sub create_service { return $self->database_error_rollback(message => "$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> cannot create service"); } my $service_id = $self->{class_object_centreon}->{db_centreon}->last_insert_id(); - + $query = 'INSERT INTO host_service_relation (host_host_id, service_service_id) VALUES (' . $options{host_id} . ', ' . $service_id . ')'; ($status) = $self->{class_object_centreon}->custom_execute(request => $query); if ($status == -1) { return $self->database_error_rollback(message => "$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> cannot link service to host"); } - + $query = 'INSERT INTO extended_service_information (service_service_id) VALUES (' . $service_id . ')'; ($status) = $self->{class_object_centreon}->custom_execute(request => $query); if ($status == -1) { return $self->database_error_rollback(message => "$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> cannot service extended information"); } - + foreach (keys %{$options{macros}}) { $query = 'INSERT INTO on_demand_macro_service (svc_svc_id, svc_macro_name, svc_macro_value) VALUES (' . $service_id . ', ?, ?)'; ($status) = $self->{class_object_centreon}->custom_execute( @@ -476,21 +471,21 @@ sub create_service { if ($self->link_service_autodisco(%options, service_id => $service_id) == -1) { return $self->database_error_rollback(message => "$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> cannot link service to autodisco"); } - + return -1 if ($self->database_commit_transaction() == -1); $self->{discovery}->{pollers_reload}->{ $options{poller_id} } = 1; $self->audit_update( - object_type => 'service', - action_type => 'a', - object_id => $service_id, + object_type => 'service', + action_type => 'a', + object_id => $service_id, object_name => $options{discovery_svc}->{service_name}, contact_id => $self->{audit_user_id}, fields => { - service_template_model_id => $self->{discovery}->{rules}->{ $options{rule_id} }->{service_template_model_id}, - service_description => $options{discovery_svc}->{service_name}, - service_register => '1', + service_template_model_id => $self->{discovery}->{rules}->{ $options{rule_id} }->{service_template_model_id}, + service_description => $options{discovery_svc}->{service_name}, + service_register => '1', service_hPars => $options{host_id} } ); @@ -500,7 +495,7 @@ sub create_service { sub crud_service { my ($self, %options) = @_; - + my $service_id; if (!defined($options{service})) { $service_id = $self->create_service(%options); @@ -516,18 +511,18 @@ sub crud_service { } else { $service_id = $self->update_service(%options); } - + return 0; } sub disable_services { my ($self, %options) = @_; - + return if ($self->{discovery}->{rules}->{ $options{rule_id} }->{rule_disable} != 1 || !defined($self->{discovery}->{rules}->{ $options{rule_id} }->{linked_services}->{ $options{host_id} })); foreach my $service (keys %{$self->{discovery}->{rules}->{ $options{rule_id} }->{linked_services}->{ $options{host_id} }}) { my $service_description = $self->{discovery}->{rules}->{ $options{rule_id} }->{linked_services}->{ $options{host_id} }->{$service}->{service_description}; - if (!defined($options{discovery_svc}->{discovered_services}->{$service_description}) && + if (!defined($options{discovery_svc}->{discovered_services}->{$service_description}) && $self->{discovery}->{rules}->{ $options{rule_id} }->{linked_services}->{ $options{host_id} }->{$service}->{service_activate} == 1) { $self->{logger}->writeLogInfo("$options{logger_pre_message} -> disable service '" . $service_description . "'"); next if ($self->{discovery}->{dry_run} == 1); @@ -538,18 +533,18 @@ sub disable_services { $self->{logger}->writeLogInfo("$options{logger_pre_message} -> cannot disable service '" . $service_description . "'"); next; } - + push @{$self->{discovery}->{journal}}, { host_name => $self->{discovery}->{hosts}->{ $options{host_id} }->{host_name}, service_name => $service_description, type => 'disable', rule_id => $options{rule_id} - }; + }; $self->{discovery}->{pollers_reload}->{ $options{poller_id} } = 1; $self->audit_update( - object_type => 'service', - action_type => 'disable', - object_id => $service, + object_type => 'service', + action_type => 'disable', + object_id => $service, object_name => $service_description, contact_id => $self->{audit_user_id} ); @@ -621,7 +616,7 @@ sub service_response_parsing { discovery_svc => $discovery_svc, rule => $self->{discovery}->{rules}->{ $options{rule_id} } ); - + my ($status, $service) = gorgone::modules::centreon::autodiscovery::services::resources::get_service( class_object_centreon => $self->{class_object_centreon}, host_id => $options{host_id}, @@ -681,20 +676,19 @@ sub discoverylistener { $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{data} = $data->{data}; } } - } elsif ($data->{code} == GORGONE_ACTION_FINISH_KO) { if ($self->{discovery}->{is_manual} == 1) { $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{failed} = 1; $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{message} = $data->{data}->{message}; } $self->{discovery}->{failed_discoveries}++; - } else { return 0; } $self->{service_current_commands_poller}->{ $self->{discovery}->{hosts}->{ $options{host_id} }->{poller_id} }--; $self->service_execute_commands(); + $self->{discovery}->{done_discoveries}++; my $progress = $self->{discovery}->{done_discoveries} * 100 / $self->{discovery}->{count_discoveries}; my $div = int(int($progress) / 5); @@ -706,7 +700,7 @@ sub discoverylistener { instant => 1, data => { message => 'current progress', - complete => sprintf('%.2f', $progress) + complete => sprintf('%.2f', $progress) } ); } @@ -744,7 +738,7 @@ sub service_execute_commands { foreach my $poller_id (keys %{$self->{discovery}->{rules}->{$rule_id}->{hosts}}) { next if (scalar(@{$self->{discovery}->{rules}->{$rule_id}->{hosts}->{$poller_id}}) <= 0); $self->{service_current_commands_poller}->{$poller_id} = 0 if (!defined($self->{service_current_commands_poller}->{$poller_id})); - + while (1) { last if ($self->{service_current_commands_poller}->{$poller_id} >= $self->{service_parrallel_commands_poller}); my $host_id = shift @{$self->{discovery}->{rules}->{$rule_id}->{hosts}->{$poller_id}}; @@ -761,7 +755,7 @@ sub service_execute_commands { ); $self->{logger}->writeLogInfo("[autodiscovery] -servicediscovery- $self->{uuid} [" . - $self->{discovery}->{rules}->{$rule_id}->{rule_alias} . "] [" . + $self->{discovery}->{rules}->{$rule_id}->{rule_alias} . "] [" . $self->{service_pollers}->{$poller_id}->{name} . "] [" . $host->{host_name} . "] -> substitute string: " . $command ); @@ -868,7 +862,7 @@ sub launchdiscovery { # get rules ################ $self->{logger}->writeLogInfo("[autodiscovery] -servicediscovery- $self->{uuid} load rules configuration"); - + ($status, $message, my $rules) = gorgone::modules::centreon::autodiscovery::services::resources::get_rules( class_object_centreon => $self->{class_object_centreon}, filter_rules => $data->{content}->{filter_rules}, @@ -899,7 +893,7 @@ sub launchdiscovery { $self->send_log_msg_error(token => $options{token}, subname => 'servicediscovery', number => $self->{uuid}, message => $message); return -1; } - + if (!defined($hosts) || scalar(keys %$hosts) == 0) { $self->{logger}->writeLogInfo("[autodiscovery] -servicediscovery- $self->{uuid} no hosts found for rule '" . $options{rule}->{rule_alias} . "'"); next; diff --git a/gorgone/tests/gorgone-api.py b/gorgone/tests/gorgone-api.py deleted file mode 100644 index 54856e798d4..00000000000 --- a/gorgone/tests/gorgone-api.py +++ /dev/null @@ -1,12 +0,0 @@ -def is_gorgone_finished(http_data): - - if 'error' in http_data.keys() and http_data["error"]: - raise Exception("Found an error in gorgone response : " + http_data["error"]) - for elem in http_data["data"]: - - if 'data' in elem.keys(): - if 'cannot launch discovery' in elem['data']: - raise Exception("gorgone can't launch discovery : " + http_data["data"]) - if elem["data"] and "discovery finished" in elem["data"]: - return 1 - return 0 diff --git a/gorgone/tests/gorgone-auto-discovery.robot b/gorgone/tests/gorgone-auto-discovery.robot deleted file mode 100644 index 2ef0925fab5..00000000000 --- a/gorgone/tests/gorgone-auto-discovery.robot +++ /dev/null @@ -1,150 +0,0 @@ -*** Settings *** -Documentation centreon gorgone auto discovery tests - -Library OperatingSystem -Library RequestsLibrary -Library gorgone-api.py -Library DatabaseLibrary - -Suite Setup Connect To Database pymysql ${DBName} ${DBUser} ${DBPass} ${DBHost} ${DBPort} -Suite Teardown Disconnect From Database -Test Setup Prepare Db ${nb_hosts} -Test Teardown Remove Services And Host -Test Template Gorgone Test - - -*** Variables *** -${nb_hosts} 50 -${DBName} centreon -${DBHost} localhost -${DBUser} robotTest -${DBPass} password -${DBPort} 3306 -${sqlRequest} -... select * from service JOIN host_service_relation ON service_service_id = service_id JOIN host ON host_service_relation.host_host_id = host.host_id WHERE service_description like 'Disk-/' -... AND host.host_alias LIKE 'gorgone_auto_discovery_test_%'; -# To work this test need : -# access to the centreon database with write permission -# Snmp must answer all disk info on 127.0.0.1 -# Centreon must have "generic snmp" plugin pack installed and "OS-Linux-SNMP-Disk-Name" discovery rule enabled. - - -*** Test Cases *** nb_host -Test Gorgone 1 50 -Test Gorgone 2 50 -Test Gorgone 3 50 -Test Gorgone 4 50 -Test Gorgone 5 50 -Test Gorgone 6 50 -Test Gorgone 7 50 -Test Gorgone 8 50 -Test Gorgone 9 50 -Test Gorgone 10 50 -Test Gorgone 11 50 -Test Gorgone 12 50 -Test Gorgone 13 50 -Test Gorgone 14 50 -Test Gorgone 15 50 -Test Gorgone 16 50 -Test Gorgone 17 50 -Test Gorgone 18 50 -Test Gorgone 19 50 -Test Gorgone 20 50 -Test Gorgone 21 50 -Test Gorgone 22 50 -Test Gorgone 23 50 -Test Gorgone 24 50 -Test Gorgone 25 50 -Test Gorgone 26 50 -Test Gorgone 27 50 -Test Gorgone 28 50 -Test Gorgone 29 50 -Test Gorgone 30 50 -Test Gorgone 31 50 -Test Gorgone 32 50 -Test Gorgone 33 50 -Test Gorgone 34 50 -Test Gorgone 35 50 -Test Gorgone 36 50 -Test Gorgone 37 50 -Test Gorgone 38 50 -Test Gorgone 39 50 -Test Gorgone 40 50 -Test Gorgone 41 50 -Test Gorgone 42 50 -Test Gorgone 43 50 -Test Gorgone 44 50 -Test Gorgone 45 50 -Test Gorgone 46 50 -Test Gorgone 47 50 -Test Gorgone 48 50 -Test Gorgone 49 50 -Test Gorgone 50 50 - - -*** Keywords *** -gorgone test - [Arguments] ${nb_hosts}=50 - Run Test - # Remove Services And Host - -prepare db - [Arguments] ${nb_hosts}=50 - - FOR ${id} IN RANGE 0 ${nb_hosts} - ${insert_host_req}= Catenate - ... SEPARATOR= - ... INSERT IGNORE INTO host (host_name, host_alias, host_address, host_active_checks_enabled, - ... host_passive_checks_enabled, host_obsess_over_host, host_check_freshness, host_event_handler_enabled, - ... host_flap_detection_enabled, host_retain_status_information, host_retain_nonstatus_information, - ... host_notifications_enabled, contact_additive_inheritance, cg_additive_inheritance, host_locked, host_register, host_activate) - ... VALUES('gorgone_auto_discovery_test_${id}', 'gorgone_auto_discovery_test_${id}', '127.0.0.1', '2', '2', '2', '2', '2', '2', '2', '2', '2', - ... '0', '0', '0', '1', '1'); - - ${output}= Execute SQL String ${insert_host_req} - - ${output2}= Execute SQL String - ... INSERT INTO host_template_relation (`host_host_id`, `host_tpl_id`, `order`) VALUES((select LAST_INSERT_ID()), (select host_id from host where host_name = 'OS-Linux-SNMP-custom'), 1); - ${output3}= Execute SQL String - ... INSERT INTO `ns_host_relation` (`host_host_id`, `nagios_server_id`) VALUES ((select LAST_INSERT_ID()), '1'); - END - -run test - &{data}= Create dictionary - ${launchDiscoveryResponse}= POST - ... http://127.0.0.1:8085/api/centreon/autodiscovery/services - ... json=${data} - ... expected_status=200 - - Log ${launchDiscoveryResponse.json()}[token] - Sleep 1 - ${getDiscoveryResultResponse}= GET - ... http://127.0.0.1:8085/api/log/${launchDiscoveryResponse.json()}[token] - ... expected_status=200 - ${IsGorgoneDone}= Is Gorgone Finished ${getDiscoveryResultResponse.json()} - WHILE ${IsGorgoneDone} == 0 - ${getDiscoveryResultResponse}= GET - ... http://127.0.0.1:8085/api/log/${launchDiscoveryResponse.json()}[token] - ... expected_status=200 - ${IsGorgoneDone}= Is Gorgone Finished ${getDiscoveryResultResponse.json()} - Sleep 2 - END - DatabaseLibrary.Row Count is equal To X ${sqlRequest} ${nb_hosts} - -remove services and host - ${delete_service}= Catenate - ... DELETE service FROM service - ... JOIN host_service_relation - ... ON service_service_id = service_id - ... JOIN host - ... ON host.host_id = host_service_relation.host_host_id - ... WHERE service_description like 'Disk-/' - ... AND host.host_alias LIKE 'gorgone_auto_discovery_test_%' - - ${output}= Execute SQL String ${delete_service} - ${output}= Execute SQL String - ... DELETE host FROM host where host.host_alias LIKE 'gorgone_auto_discovery_test_%' - ${output}= Execute SQL String - ... DELETE host_template_relation FROM host_template_relation WHERE host_host_id NOT IN (SELECT host_id FROM host) - ${output}= Execute SQL String - ... DELETE FROM ns_host_relation WHERE host_host_id NOT IN (SELECT host_id FROM host) From 6d201fe9e206cb3fedcd3d0ae5f3513f419c132c Mon Sep 17 00:00:00 2001 From: Colin Gagnaire <gagnaire.colin@gmail.com> Date: Fri, 1 Mar 2024 21:29:57 +0100 Subject: [PATCH 794/948] fix(gorgone/servicediscovery): lost discoveries are recovered! Co-authored-by: garnier-quentin <garnier.quentin@gmail.com> --- .../gorgone/modules/centreon/autodiscovery/class.pm | 10 ++++++---- .../centreon/autodiscovery/services/discovery.pm | 7 +++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm index 14e860d41a6..289729f3c6d 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm @@ -1040,7 +1040,7 @@ sub action_servicediscoverylistener { %options ); - if ($self->{service_discoveries}->{ $uuid }->is_finished()) { + if (defined($self->{service_discoveries}->{ $uuid }) && $self->{service_discoveries}->{ $uuid }->is_finished()) { delete $self->{service_discoveries}->{ $uuid }; } } @@ -1060,8 +1060,11 @@ sub action_launchservicediscovery { config_core => $self->{config_core}, service_number => $self->{service_number}, class_object_centreon => $self->{class_object_centreon}, - class_object_centstorage => $self->{class_object_centstorage} + class_object_centstorage => $self->{class_object_centstorage}, + class_autodiscovery => $self ); + + $self->{service_discoveries}->{ $svc_discovery->get_uuid() } = $svc_discovery; my $status = $svc_discovery->launchdiscovery( token => $options{token}, frame => $options{frame} @@ -1072,8 +1075,7 @@ sub action_launchservicediscovery { token => $options{token}, data => { message => 'cannot launch discovery' } ); - } elsif ($status == 0) { - $self->{service_discoveries}->{ $svc_discovery->get_uuid() } = $svc_discovery; + delete $self->{service_discoveries}->{ $svc_discovery->get_uuid() }; } } diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm index d1e00408da0..eac6102999a 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm @@ -40,6 +40,7 @@ sub new { $connector->{internal_socket} = $options{internal_socket}; $connector->{class_object_centreon} = $options{class_object_centreon}; $connector->{class_object_centstorage} = $options{class_object_centstorage}; + $connector->{class_autodiscovery} = $options{class_autodiscovery}; $connector->{tpapi_clapi} = $options{tpapi_clapi}; $connector->{mail_subject} = defined($connector->{config}->{mail_subject}) ? $connector->{config}->{mail_subject} : 'Centreon Auto Discovery'; $connector->{mail_from} = defined($connector->{config}->{mail_from}) ? $connector->{config}->{mail_from} : 'centreon-autodisco'; @@ -939,4 +940,10 @@ sub launchdiscovery { return 0; } +sub event { + my ($self, %options) = @_; + + $self->{class_autodiscovery}->event(); +} + 1; From f299254ca2be4c71faf1740d653c1fecc2c9d560 Mon Sep 17 00:00:00 2001 From: Colin Gagnaire <gagnaire.colin@gmail.com> Date: Tue, 5 Mar 2024 09:16:27 +0100 Subject: [PATCH 795/948] fix(gorgone/servicediscovery): pollers are reload multiple times --- .../modules/centreon/autodiscovery/class.pm | 2 ++ .../autodiscovery/services/discovery.pm | 25 +++++++++++++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm index 289729f3c6d..26b7a5585ca 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/class.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/class.pm @@ -1041,6 +1041,8 @@ sub action_servicediscoverylistener { ); if (defined($self->{service_discoveries}->{ $uuid }) && $self->{service_discoveries}->{ $uuid }->is_finished()) { + return 0 if ($self->{service_discoveries}->{ $uuid }->is_post_execution()); + $self->{service_discoveries}->{ $uuid }->service_discovery_post_exec(); delete $self->{service_discoveries}->{ $uuid }; } } diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm index eac6102999a..ee4c61beba0 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm @@ -50,6 +50,7 @@ sub new { $connector->{service_parrallel_commands_poller} = 8; $connector->{service_current_commands_poller} = {}; $connector->{finished} = 0; + $connector->{post_execution} = 0; $connector->{safe_display} = Safe->new(); $connector->{safe_display}->share('$values'); @@ -118,6 +119,12 @@ sub is_finished { return $self->{finished}; } +sub is_post_execution { + my ($self, %options) = @_; + + return $self->{post_execution}; +} + sub send_email { my ($self, %options) = @_; @@ -722,16 +729,24 @@ sub discoverylistener { manual => $self->{discovery}->{manual} } ); - - if ($self->{discovery}->{is_manual} == 0) { - $self->restart_pollers(); - $self->send_email(); - } } return 0; } +sub service_discovery_post_exec { + my ($self, %options) = @_; + + $self->{post_execution} = 1; + + if ($self->{discovery}->{is_manual} == 0) { + $self->restart_pollers(); + $self->send_email(); + } + + return 0; +} + sub service_execute_commands { my ($self, %options) = @_; From 6bccd39578494a884a900b390669b5b197891ad3 Mon Sep 17 00:00:00 2001 From: hamzabessa <148857497+hamzabessa@users.noreply.github.com> Date: Wed, 20 Mar 2024 09:46:34 +0000 Subject: [PATCH 796/948] fix(packaging): set correct rights for centreon gorgone config files (#3527) Co-authored-by: Kevin Duret <kduret@centreon.com> --- .../packaging/centreon-gorgone-centreon-config.yaml | 7 ++++--- gorgone/packaging/centreon-gorgone.yaml | 8 ++++---- .../centreon-gorgone-centreon-config-postinstall.sh | 10 ++++++++++ 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/gorgone/packaging/centreon-gorgone-centreon-config.yaml b/gorgone/packaging/centreon-gorgone-centreon-config.yaml index 6024853a9b2..3f46b2ba7db 100644 --- a/gorgone/packaging/centreon-gorgone-centreon-config.yaml +++ b/gorgone/packaging/centreon-gorgone-centreon-config.yaml @@ -21,14 +21,14 @@ contents: file_info: owner: centreon-gorgone group: centreon-gorgone - mode: 0664 + mode: 0640 - src: "./configuration/centreon-api.yaml" dst: "/etc/centreon-gorgone/config.d/centreon-api.yaml" file_info: owner: centreon-gorgone group: centreon-gorgone - mode: 0664 + mode: 0640 - src: "./configuration/centreon-audit.yaml" dst: "/etc/centreon-gorgone/config.d/50-centreon-audit.yaml" @@ -36,13 +36,14 @@ contents: file_info: owner: centreon-gorgone group: centreon-gorgone - mode: 0664 + mode: 0640 - dst: "/var/cache/centreon-gorgone/autodiscovery" type: dir file_info: owner: centreon-gorgone group: centreon-gorgone + mode: 0770 scripts: postinstall: ./scripts/centreon-gorgone-centreon-config-postinstall.sh diff --git a/gorgone/packaging/centreon-gorgone.yaml b/gorgone/packaging/centreon-gorgone.yaml index 0bea8be1260..94e0423b58b 100644 --- a/gorgone/packaging/centreon-gorgone.yaml +++ b/gorgone/packaging/centreon-gorgone.yaml @@ -20,28 +20,28 @@ contents: file_info: owner: centreon-gorgone group: centreon-gorgone - mode: 0775 + mode: 0755 - dst: "/etc/centreon-gorgone/config.d" type: dir file_info: owner: centreon-gorgone group: centreon-gorgone - mode: 0775 + mode: 0770 - dst: "/etc/centreon-gorgone/config.d/cron.d" type: dir file_info: owner: centreon-gorgone group: centreon-gorgone - mode: 0775 + mode: 0770 - src: "./configuration/config.yaml" dst: "/etc/centreon-gorgone/config.yaml" file_info: owner: centreon-gorgone group: centreon-gorgone - mode: 0664 + mode: 0640 - dst: "/var/lib/centreon-gorgone" type: dir diff --git a/gorgone/packaging/scripts/centreon-gorgone-centreon-config-postinstall.sh b/gorgone/packaging/scripts/centreon-gorgone-centreon-config-postinstall.sh index dc05e3da774..f4bd78d02c8 100644 --- a/gorgone/packaging/scripts/centreon-gorgone-centreon-config-postinstall.sh +++ b/gorgone/packaging/scripts/centreon-gorgone-centreon-config-postinstall.sh @@ -8,6 +8,15 @@ installConfigurationFile() { fi } +fixConfigurationFileRights() { + # force update of configuration file rights since they are not updated automatically by nfpm + chmod 0640 /etc/centreon-gorgone/config.d/30-centreon.yaml + chmod 0640 /etc/centreon-gorgone/config.d/31-centreon-api.yaml + chmod 0640 /etc/centreon-gorgone/config.d/50-centreon-audit.yaml + chmod 0770 /etc/centreon-gorgone/config.d + chmod 0770 /etc/centreon-gorgone/config.d/cron.d +} + manageUserGroups() { if getent passwd centreon > /dev/null 2>&1; then usermod -a -G centreon-gorgone centreon 2> /dev/null @@ -52,6 +61,7 @@ case "$action" in "2" | "upgrade") manageUserGroups installConfigurationFile + fixConfigurationFileRights addGorgoneSshKeys ;; *) From 3f3c3c0a1df0ae685a6ccd8d1c2dfcede84b103a Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Wed, 20 Mar 2024 16:33:36 +0100 Subject: [PATCH 797/948] fix(packaging): update rights of centreon-api.yaml file (#3661) --- gorgone/packaging/centreon-gorgone-centreon-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/packaging/centreon-gorgone-centreon-config.yaml b/gorgone/packaging/centreon-gorgone-centreon-config.yaml index 3f46b2ba7db..327698ffbd8 100644 --- a/gorgone/packaging/centreon-gorgone-centreon-config.yaml +++ b/gorgone/packaging/centreon-gorgone-centreon-config.yaml @@ -28,7 +28,7 @@ contents: file_info: owner: centreon-gorgone group: centreon-gorgone - mode: 0640 + mode: 0660 - src: "./configuration/centreon-audit.yaml" dst: "/etc/centreon-gorgone/config.d/50-centreon-audit.yaml" From 7ab743c652172986ef83e529cd737a9367176726 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Mon, 25 Mar 2024 13:26:38 +0100 Subject: [PATCH 798/948] fix(gorgone): install default whitelists in a separate file (#3671) Refs: MON-38302 --- gorgone/packaging/centreon-gorgone.yaml | 21 +++++++++++++++++++ gorgone/packaging/configuration/action.yaml | 8 +++++++ .../whitelist.conf.d/centreon.yaml | 13 ++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 gorgone/packaging/configuration/action.yaml create mode 100644 gorgone/packaging/configuration/whitelist.conf.d/centreon.yaml diff --git a/gorgone/packaging/centreon-gorgone.yaml b/gorgone/packaging/centreon-gorgone.yaml index 0bea8be1260..2b9afc30bb2 100644 --- a/gorgone/packaging/centreon-gorgone.yaml +++ b/gorgone/packaging/centreon-gorgone.yaml @@ -36,6 +36,13 @@ contents: group: centreon-gorgone mode: 0775 + - dst: "/etc/centreon-gorgone/config.d/whitelist.conf.d" + type: dir + file_info: + owner: centreon-gorgone + group: centreon-gorgone + mode: 0770 + - src: "./configuration/config.yaml" dst: "/etc/centreon-gorgone/config.yaml" file_info: @@ -43,6 +50,20 @@ contents: group: centreon-gorgone mode: 0664 + - src: "./configuration/action.yaml" + dst: "/etc/centreon-gorgone/config.d/41-action.yaml" + file_info: + owner: centreon-gorgone + group: centreon-gorgone + mode: 0640 + + - src: "./configuration/whitelist.conf.d/centreon.yaml" + dst: "/etc/centreon-gorgone/config.d/whitelist.conf.d/centreon.yaml" + file_info: + owner: centreon-gorgone + group: centreon-gorgone + mode: 0640 + - dst: "/var/lib/centreon-gorgone" type: dir file_info: diff --git a/gorgone/packaging/configuration/action.yaml b/gorgone/packaging/configuration/action.yaml new file mode 100644 index 00000000000..8dcebf2cd2f --- /dev/null +++ b/gorgone/packaging/configuration/action.yaml @@ -0,0 +1,8 @@ +gorgone: + modules: + - name: action + package: "gorgone::modules::core::action::hooks" + enable: true + command_timeout: 30 + whitelist_cmds: true + allowed_cmds: !include /etc/centreon-gorgone/config.d/whitelist.conf.d/*.yaml diff --git a/gorgone/packaging/configuration/whitelist.conf.d/centreon.yaml b/gorgone/packaging/configuration/whitelist.conf.d/centreon.yaml new file mode 100644 index 00000000000..7b7e5c67bd8 --- /dev/null +++ b/gorgone/packaging/configuration/whitelist.conf.d/centreon.yaml @@ -0,0 +1,13 @@ +# Configuration brought by Centreon Gorgone package. +# SHOULD NOT BE EDITED! CREATE YOUR OWN FILE IN WHITELIST.CONF.D DIRECTORY! +- ^sudo\s+(/bin/)?systemctl\s+(reload|restart)\s+(centengine|centreontrapd|cbd)\s*$ +- ^(sudo\s+)?(/usr/bin/)?service\s+(centengine|centreontrapd|cbd|cbd-sql)\s+(reload|restart)\s*$ +- ^/usr/sbin/centenginestats\s+-c\s+/etc/centreon-engine/+centengine\.cfg\s*$ +- ^cat\s+/var/lib/centreon-engine/[a-zA-Z0-9\-]+-stats\.json\s*$ +- ^/usr/lib/centreon/plugins/.*$ +- ^/bin/perl /usr/share/centreon/bin/anomaly_detection --seasonality >> /var/log/centreon/anomaly_detection\.log 2>&1\s*$ +- ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ +- ^centreon +- ^mkdir +- ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host +- ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ From 9b38fdda97db23ea6dbc5268d81f5bf1f0952a8b Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Mon, 25 Mar 2024 13:44:37 +0100 Subject: [PATCH 799/948] fix(gorgone): install default whitelists in a separate file (#3671) (#3681) --- gorgone/packaging/centreon-gorgone.yaml | 21 +++++++++++++++++++ gorgone/packaging/configuration/action.yaml | 8 +++++++ .../whitelist.conf.d/centreon.yaml | 13 ++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 gorgone/packaging/configuration/action.yaml create mode 100644 gorgone/packaging/configuration/whitelist.conf.d/centreon.yaml diff --git a/gorgone/packaging/centreon-gorgone.yaml b/gorgone/packaging/centreon-gorgone.yaml index 94e0423b58b..f58b701970a 100644 --- a/gorgone/packaging/centreon-gorgone.yaml +++ b/gorgone/packaging/centreon-gorgone.yaml @@ -36,6 +36,13 @@ contents: group: centreon-gorgone mode: 0770 + - dst: "/etc/centreon-gorgone/config.d/whitelist.conf.d" + type: dir + file_info: + owner: centreon-gorgone + group: centreon-gorgone + mode: 0770 + - src: "./configuration/config.yaml" dst: "/etc/centreon-gorgone/config.yaml" file_info: @@ -43,6 +50,20 @@ contents: group: centreon-gorgone mode: 0640 + - src: "./configuration/action.yaml" + dst: "/etc/centreon-gorgone/config.d/41-action.yaml" + file_info: + owner: centreon-gorgone + group: centreon-gorgone + mode: 0640 + + - src: "./configuration/whitelist.conf.d/centreon.yaml" + dst: "/etc/centreon-gorgone/config.d/whitelist.conf.d/centreon.yaml" + file_info: + owner: centreon-gorgone + group: centreon-gorgone + mode: 0640 + - dst: "/var/lib/centreon-gorgone" type: dir file_info: diff --git a/gorgone/packaging/configuration/action.yaml b/gorgone/packaging/configuration/action.yaml new file mode 100644 index 00000000000..8dcebf2cd2f --- /dev/null +++ b/gorgone/packaging/configuration/action.yaml @@ -0,0 +1,8 @@ +gorgone: + modules: + - name: action + package: "gorgone::modules::core::action::hooks" + enable: true + command_timeout: 30 + whitelist_cmds: true + allowed_cmds: !include /etc/centreon-gorgone/config.d/whitelist.conf.d/*.yaml diff --git a/gorgone/packaging/configuration/whitelist.conf.d/centreon.yaml b/gorgone/packaging/configuration/whitelist.conf.d/centreon.yaml new file mode 100644 index 00000000000..7b7e5c67bd8 --- /dev/null +++ b/gorgone/packaging/configuration/whitelist.conf.d/centreon.yaml @@ -0,0 +1,13 @@ +# Configuration brought by Centreon Gorgone package. +# SHOULD NOT BE EDITED! CREATE YOUR OWN FILE IN WHITELIST.CONF.D DIRECTORY! +- ^sudo\s+(/bin/)?systemctl\s+(reload|restart)\s+(centengine|centreontrapd|cbd)\s*$ +- ^(sudo\s+)?(/usr/bin/)?service\s+(centengine|centreontrapd|cbd|cbd-sql)\s+(reload|restart)\s*$ +- ^/usr/sbin/centenginestats\s+-c\s+/etc/centreon-engine/+centengine\.cfg\s*$ +- ^cat\s+/var/lib/centreon-engine/[a-zA-Z0-9\-]+-stats\.json\s*$ +- ^/usr/lib/centreon/plugins/.*$ +- ^/bin/perl /usr/share/centreon/bin/anomaly_detection --seasonality >> /var/log/centreon/anomaly_detection\.log 2>&1\s*$ +- ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ +- ^centreon +- ^mkdir +- ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host +- ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ From 83e581846526c9eeceaceec156a5281733cf6ef4 Mon Sep 17 00:00:00 2001 From: Sophie Depassio <sdepassio@centreon.com> Date: Fri, 15 Mar 2024 11:51:11 +0100 Subject: [PATCH 800/948] fix(gorgone/pullwss) escape character before sending http message, as wide space was not correctly interpreted. REF:MON-37293 --- gorgone/gorgone/modules/core/proxy/httpserver.pm | 3 +++ gorgone/gorgone/modules/core/pullwss/class.pm | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/modules/core/proxy/httpserver.pm b/gorgone/gorgone/modules/core/proxy/httpserver.pm index d0e1d0478f6..e2ba6525a9f 100644 --- a/gorgone/gorgone/modules/core/proxy/httpserver.pm +++ b/gorgone/gorgone/modules/core/proxy/httpserver.pm @@ -34,6 +34,7 @@ use IO::Handle; use JSON::XS; use IO::Poll qw(POLLIN POLLPRI); use EV; +use HTML::Entities; my %handlers = (TERM => {}, HUP => {}); my ($connector); @@ -53,6 +54,8 @@ websocket '/' => sub { $mojo->on(message => sub { my ($mojo, $msg) = @_; + $msg = HTML::Entities::decode_entities($msg); + $connector->{ws_clients}->{ $mojo->tx->connection }->{last_update} = time(); $connector->{logger}->writeLogDebug("[proxy] httpserver receiving message: " . $msg); diff --git a/gorgone/gorgone/modules/core/pullwss/class.pm b/gorgone/gorgone/modules/core/pullwss/class.pm index d74cf784f1f..5745dd21d5b 100644 --- a/gorgone/gorgone/modules/core/pullwss/class.pm +++ b/gorgone/gorgone/modules/core/pullwss/class.pm @@ -32,6 +32,7 @@ use IO::Socket::SSL; use IO::Handle; use JSON::XS; use EV; +use HTML::Entities; my %handlers = (TERM => {}, HUP => {}); my ($connector); @@ -103,8 +104,8 @@ sub class_handle_HUP { sub send_message { my ($self, %options) = @_; - - $self->{tx}->send({text => $options{message} }); + my $message = HTML::Entities::encode_entities($options{message}); + $self->{tx}->send({text => $message }); } sub ping { From d9d66a550fb586e20b97ffedde3eb73b044c6aae Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Tue, 26 Mar 2024 11:46:35 +0100 Subject: [PATCH 801/948] fix(gorgone): prioritized gorgone action module (#3696) Refs: MON-38399 --- gorgone/packaging/centreon-gorgone.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/packaging/centreon-gorgone.yaml b/gorgone/packaging/centreon-gorgone.yaml index 2b9afc30bb2..9db4c819dc0 100644 --- a/gorgone/packaging/centreon-gorgone.yaml +++ b/gorgone/packaging/centreon-gorgone.yaml @@ -51,7 +51,7 @@ contents: mode: 0664 - src: "./configuration/action.yaml" - dst: "/etc/centreon-gorgone/config.d/41-action.yaml" + dst: "/etc/centreon-gorgone/config.d/39-action.yaml" file_info: owner: centreon-gorgone group: centreon-gorgone From aac35a9f915fa96554ee956c94d7b885f0884d4f Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Tue, 26 Mar 2024 11:56:42 +0100 Subject: [PATCH 802/948] fix(gorgone): prioritized gorgone action module (#3697) Refs: MON-38402 --- gorgone/packaging/centreon-gorgone.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/packaging/centreon-gorgone.yaml b/gorgone/packaging/centreon-gorgone.yaml index f58b701970a..81211edbffc 100644 --- a/gorgone/packaging/centreon-gorgone.yaml +++ b/gorgone/packaging/centreon-gorgone.yaml @@ -51,7 +51,7 @@ contents: mode: 0640 - src: "./configuration/action.yaml" - dst: "/etc/centreon-gorgone/config.d/41-action.yaml" + dst: "/etc/centreon-gorgone/config.d/39-action.yaml" file_info: owner: centreon-gorgone group: centreon-gorgone From 3aa51685027fc87dfc1a73562232263f242eeaa5 Mon Sep 17 00:00:00 2001 From: Evan-Adam <152897682+Evan-Adam@users.noreply.github.com> Date: Tue, 26 Mar 2024 16:24:42 +0100 Subject: [PATCH 803/948] fix(gorgone/pullwss) escape character before sending http message (#3706) Co-authored-by: Sophie Depassio <sdepassio@centreon.com> --- gorgone/gorgone/modules/core/proxy/httpserver.pm | 3 +++ gorgone/gorgone/modules/core/pullwss/class.pm | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/modules/core/proxy/httpserver.pm b/gorgone/gorgone/modules/core/proxy/httpserver.pm index d0e1d0478f6..e2ba6525a9f 100644 --- a/gorgone/gorgone/modules/core/proxy/httpserver.pm +++ b/gorgone/gorgone/modules/core/proxy/httpserver.pm @@ -34,6 +34,7 @@ use IO::Handle; use JSON::XS; use IO::Poll qw(POLLIN POLLPRI); use EV; +use HTML::Entities; my %handlers = (TERM => {}, HUP => {}); my ($connector); @@ -53,6 +54,8 @@ websocket '/' => sub { $mojo->on(message => sub { my ($mojo, $msg) = @_; + $msg = HTML::Entities::decode_entities($msg); + $connector->{ws_clients}->{ $mojo->tx->connection }->{last_update} = time(); $connector->{logger}->writeLogDebug("[proxy] httpserver receiving message: " . $msg); diff --git a/gorgone/gorgone/modules/core/pullwss/class.pm b/gorgone/gorgone/modules/core/pullwss/class.pm index d74cf784f1f..5745dd21d5b 100644 --- a/gorgone/gorgone/modules/core/pullwss/class.pm +++ b/gorgone/gorgone/modules/core/pullwss/class.pm @@ -32,6 +32,7 @@ use IO::Socket::SSL; use IO::Handle; use JSON::XS; use EV; +use HTML::Entities; my %handlers = (TERM => {}, HUP => {}); my ($connector); @@ -103,8 +104,8 @@ sub class_handle_HUP { sub send_message { my ($self, %options) = @_; - - $self->{tx}->send({text => $options{message} }); + my $message = HTML::Entities::encode_entities($options{message}); + $self->{tx}->send({text => $message }); } sub ping { From 0ffe5050d08205ea22b473ba1f452b373de0b7d6 Mon Sep 17 00:00:00 2001 From: Laurent Pinsivy <lpinsivy@centreon.com> Date: Thu, 28 Mar 2024 17:20:40 +0100 Subject: [PATCH 804/948] fix(configuration): Fix Export/Import Remote Server configuration with MariaDB 10.11 ( (#3615) Co-authored-by: alaunois <alaunois@centreon.com> --- gorgone/gorgone/modules/centreon/legacycmd/class.pm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/gorgone/gorgone/modules/centreon/legacycmd/class.pm b/gorgone/gorgone/modules/centreon/legacycmd/class.pm index c1392662097..67d2a2121ad 100644 --- a/gorgone/gorgone/modules/centreon/legacycmd/class.pm +++ b/gorgone/gorgone/modules/centreon/legacycmd/class.pm @@ -554,8 +554,11 @@ sub action_addimporttaskwithparent { return -1; } + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time()); + my $datetime = sprintf('%04d-%02d-%02d %02d:%02d:%02d', $year+1900, $mon+1, $mday, $hour, $min, $sec); + my ($status, $datas) = $self->{class_object_centreon}->custom_execute( - request => "INSERT INTO task (`type`, `status`, `parent_id`) VALUES ('import', 'pending', '" . $options{data}->{content}->{parent_id} . "')" + request => "INSERT INTO task (`type`, `status`, `parent_id`, `created_at`) VALUES ('import', 'pending', '" . $options{data}->{content}->{parent_id} . "', '" . $datetime . "')" ); if ($status == -1) { $self->send_log( From 74210a654713db4d6853bae1b9cbb9508cd17d81 Mon Sep 17 00:00:00 2001 From: tuntoja <58987095+tuntoja@users.noreply.github.com> Date: Tue, 2 Apr 2024 09:07:06 +0200 Subject: [PATCH 805/948] fix(ci): fix release pipelines issues (#3745) --- .github/workflows/gorgone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index e520feb400b..fdd94fb9797 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -214,6 +214,6 @@ jobs: major_version: ${{ needs.get-version.outputs.major_version }} minor_version: ${{ needs.get-version.outputs.minor_version }} stability: ${{ needs.get-version.outputs.stability }} - github_base_ref: ${{ github.base_ref }} + github_ref_name: ${{ github.ref_name }} release_type: ${{ needs.get-version.outputs.release_type }} release_cloud: ${{ needs.get-version.outputs.release_cloud }} From 9656f2721db73d948b757a47f8e4a748dae1f4b7 Mon Sep 17 00:00:00 2001 From: tuntoja <58987095+tuntoja@users.noreply.github.com> Date: Tue, 2 Apr 2024 09:07:06 +0200 Subject: [PATCH 806/948] fix(ci): fix release pipelines issues (#3745) --- .github/workflows/gorgone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index e520feb400b..fdd94fb9797 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -214,6 +214,6 @@ jobs: major_version: ${{ needs.get-version.outputs.major_version }} minor_version: ${{ needs.get-version.outputs.minor_version }} stability: ${{ needs.get-version.outputs.stability }} - github_base_ref: ${{ github.base_ref }} + github_ref_name: ${{ github.ref_name }} release_type: ${{ needs.get-version.outputs.release_type }} release_cloud: ${{ needs.get-version.outputs.release_cloud }} From c6632380752135869a961176432d1dfceeb2abae Mon Sep 17 00:00:00 2001 From: sdepassio <114986849+sdepassio@users.noreply.github.com> Date: Thu, 4 Apr 2024 10:49:38 +0200 Subject: [PATCH 807/948] fix(gorgone): systemctl path in whitelist (#3780) Co-authored-by: Benoit Poulet <benoit.poulet@businessdecision.com> --- gorgone/packaging/configuration/whitelist.conf.d/centreon.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/packaging/configuration/whitelist.conf.d/centreon.yaml b/gorgone/packaging/configuration/whitelist.conf.d/centreon.yaml index 7b7e5c67bd8..7b9fd8515db 100644 --- a/gorgone/packaging/configuration/whitelist.conf.d/centreon.yaml +++ b/gorgone/packaging/configuration/whitelist.conf.d/centreon.yaml @@ -1,6 +1,6 @@ # Configuration brought by Centreon Gorgone package. # SHOULD NOT BE EDITED! CREATE YOUR OWN FILE IN WHITELIST.CONF.D DIRECTORY! -- ^sudo\s+(/bin/)?systemctl\s+(reload|restart)\s+(centengine|centreontrapd|cbd)\s*$ +- ^sudo\s+(/bin/|/usr/bin/)?systemctl\s+(reload|restart)\s+(centengine|centreontrapd|cbd)\s*$ - ^(sudo\s+)?(/usr/bin/)?service\s+(centengine|centreontrapd|cbd|cbd-sql)\s+(reload|restart)\s*$ - ^/usr/sbin/centenginestats\s+-c\s+/etc/centreon-engine/+centengine\.cfg\s*$ - ^cat\s+/var/lib/centreon-engine/[a-zA-Z0-9\-]+-stats\.json\s*$ From c326cf983bc150faf38a625e79e96f5f96958384 Mon Sep 17 00:00:00 2001 From: tuntoja <tuntoja@centreon.com> Date: Wed, 10 Apr 2024 09:35:22 +0200 Subject: [PATCH 808/948] fix(packaging): fix conflict in gorgone packaging file --- gorgone/packaging/centreon-gorgone.yaml | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/gorgone/packaging/centreon-gorgone.yaml b/gorgone/packaging/centreon-gorgone.yaml index a69e6af845d..f58b701970a 100644 --- a/gorgone/packaging/centreon-gorgone.yaml +++ b/gorgone/packaging/centreon-gorgone.yaml @@ -43,13 +43,6 @@ contents: group: centreon-gorgone mode: 0770 - - dst: "/etc/centreon-gorgone/config.d/whitelist.conf.d" - type: dir - file_info: - owner: centreon-gorgone - group: centreon-gorgone - mode: 0770 - - src: "./configuration/config.yaml" dst: "/etc/centreon-gorgone/config.yaml" file_info: @@ -58,21 +51,7 @@ contents: mode: 0640 - src: "./configuration/action.yaml" - dst: "/etc/centreon-gorgone/config.d/39-action.yaml" - file_info: - owner: centreon-gorgone - group: centreon-gorgone - mode: 0640 - - - src: "./configuration/whitelist.conf.d/centreon.yaml" - dst: "/etc/centreon-gorgone/config.d/whitelist.conf.d/centreon.yaml" - file_info: - owner: centreon-gorgone - group: centreon-gorgone - mode: 0640 - - - src: "./configuration/action.yaml" - dst: "/etc/centreon-gorgone/config.d/39-action.yaml" + dst: "/etc/centreon-gorgone/config.d/41-action.yaml" file_info: owner: centreon-gorgone group: centreon-gorgone From 8f950b806a825f6825511a7254b2d55b4ed47082 Mon Sep 17 00:00:00 2001 From: Evan-Adam <152897682+Evan-Adam@users.noreply.github.com> Date: Mon, 15 Apr 2024 10:33:54 +0200 Subject: [PATCH 809/948] MON-37899-community-pr-change-level-of-some-gorgone-logs (#3648) Co-authored-by: Colin Gagnaire <gagnaire.colin@gmail.com> --- .../autodiscovery/services/discovery.pm | 26 ++++++++++--------- .../autodiscovery/services/resources.pm | 4 +-- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm index ee4c61beba0..7024085e43c 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm @@ -151,7 +151,7 @@ sub send_email { } if (scalar(@$body) > 0) { - $self->{logger}->writeLogInfo("[autodiscovery] -servicediscovery- $self->{uuid} send email to '" . $contact_id . "' (" . $self->{discovery}->{rules}->{$rule_id}->{contact}->{$contact_id}->{contact_email} . ")"); + $self->{logger}->writeLogDebug("[autodiscovery] -servicediscovery- $self->{uuid} send email to '" . $contact_id . "' (" . $self->{discovery}->{rules}->{$rule_id}->{contact}->{$contact_id}->{contact_email} . ")"); my $smtp = Net::SMTP->new('localhost', Timeout => 15); if (!defined($smtp)) { @@ -302,7 +302,8 @@ sub update_service { msg => 'template', rule_id => $options{rule_id} }; - $self->{logger}->writeLogInfo("$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> service update template"); + + $self->{logger}->writeLogDebug("$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> service update template"); if ($self->{discovery}->{is_manual} == 1) { $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{discovery}->{ $options{discovery_svc}->{service_name} }->{service_template_model_stm_id} = $self->{discovery}->{rules}->{ $options{rule_id} }->{service_template_model_id}; } @@ -315,7 +316,7 @@ sub update_service { type => 'enable', rule_id => $options{rule_id} }; - $self->{logger}->writeLogInfo("$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> service enable"); + $self->{logger}->writeLogDebug("$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> service enable"); } foreach my $macro_name (keys %{$options{macros}}) { @@ -346,7 +347,7 @@ sub update_service { msg => 'macros', rule_id => $options{rule_id} }; - $self->{logger}->writeLogInfo("$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> service update/insert macros"); + $self->{logger}->writeLogDebug("$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> service update/insert macros"); } return $options{service}->{id} if ($self->{discovery}->{dry_run} == 1 || scalar(@journal) == 0); @@ -507,7 +508,7 @@ sub crud_service { my $service_id; if (!defined($options{service})) { $service_id = $self->create_service(%options); - $self->{logger}->writeLogInfo("$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> service created"); + $self->{logger}->writeLogDebug("$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> service created"); if ($service_id != -1) { push @{$self->{discovery}->{journal}}, { host_name => $self->{discovery}->{hosts}->{ $options{host_id} }->{host_name}, @@ -532,13 +533,13 @@ sub disable_services { if (!defined($options{discovery_svc}->{discovered_services}->{$service_description}) && $self->{discovery}->{rules}->{ $options{rule_id} }->{linked_services}->{ $options{host_id} }->{$service}->{service_activate} == 1) { - $self->{logger}->writeLogInfo("$options{logger_pre_message} -> disable service '" . $service_description . "'"); + $self->{logger}->writeLogDebug("$options{logger_pre_message} -> disable service '" . $service_description . "'"); next if ($self->{discovery}->{dry_run} == 1); my $query = "UPDATE service SET service_activate = '0' WHERE service_id = " . $service; my ($status) = $self->{class_object_centreon}->custom_execute(request => $query); if ($status == -1) { - $self->{logger}->writeLogInfo("$options{logger_pre_message} -> cannot disable service '" . $service_description . "'"); + $self->{logger}->writeLogError("$options{logger_pre_message} -> cannot disable service '" . $service_description . "'"); next; } @@ -715,7 +716,7 @@ sub discoverylistener { $self->{logger}->writeLogDebug("[autodiscovery] -servicediscovery- $self->{uuid} current count $self->{discovery}->{done_discoveries}/$self->{discovery}->{count_discoveries}"); if ($self->{discovery}->{done_discoveries} == $self->{discovery}->{count_discoveries}) { - $self->{logger}->writeLogDebug("[autodiscovery] -servicediscovery- $self->{uuid} discovery finished"); + $self->{logger}->writeLogInfo("[autodiscovery] -servicediscovery- $self->{uuid} discovery finished"); $self->{finished} = 1; $self->send_log( @@ -770,7 +771,7 @@ sub service_execute_commands { vault_count => $options{vault_count} ); - $self->{logger}->writeLogInfo("[autodiscovery] -servicediscovery- $self->{uuid} [" . + $self->{logger}->writeLogDebug("[autodiscovery] -servicediscovery- $self->{uuid} [" . $self->{discovery}->{rules}->{$rule_id}->{rule_alias} . "] [" . $self->{service_pollers}->{$poller_id}->{name} . "] [" . $host->{host_name} . "] -> substitute string: " . $command @@ -826,7 +827,7 @@ sub launchdiscovery { ################ # get pollers ################ - $self->{logger}->writeLogInfo("[autodiscovery] -servicediscovery- $self->{uuid} load pollers configuration"); + $self->{logger}->writeLogDebug("[autodiscovery] -servicediscovery- $self->{uuid} load pollers configuration"); my ($status, $message, $pollers) = gorgone::modules::centreon::autodiscovery::services::resources::get_pollers( class_object_centreon => $self->{class_object_centreon} ); @@ -839,7 +840,7 @@ sub launchdiscovery { ################ # get audit user ################ - $self->{logger}->writeLogInfo("[autodiscovery] -servicediscovery- $self->{uuid} load audit configuration"); + $self->{logger}->writeLogDebug("[autodiscovery] -servicediscovery- $self->{uuid} load audit configuration"); ($status, $message, my $audit_enable) = gorgone::modules::centreon::autodiscovery::services::resources::get_audit( class_object_centstorage => $self->{class_object_centstorage} @@ -877,7 +878,8 @@ sub launchdiscovery { ################ # get rules ################ - $self->{logger}->writeLogInfo("[autodiscovery] -servicediscovery- $self->{uuid} load rules configuration"); + + $self->{logger}->writeLogDebug("[autodiscovery] -servicediscovery- $self->{uuid} load rules configuration"); ($status, $message, my $rules) = gorgone::modules::centreon::autodiscovery::services::resources::get_rules( class_object_centreon => $self->{class_object_centreon}, diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm b/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm index 24a1ed96383..605ff9900d1 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm @@ -586,10 +586,10 @@ sub check_exinc { attributes => $options{discovery_svc}->{attributes} ); if ($exinc->{exinc_type} == 1 && $value =~ /$exinc->{exinc_regexp}/) { - $options{logger}->writeLogInfo("$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> inclusion '$exinc->{exinc_regexp}'"); + $options{logger}->writeLogDebug("$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> inclusion '$exinc->{exinc_regexp}'"); return 0; } elsif ($exinc->{exinc_type} == 0 && $value =~ /$exinc->{exinc_regexp}/) { - $options{logger}->writeLogInfo("$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> exclusion '$exinc->{exinc_regexp}'"); + $options{logger}->writeLogDebug("$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> exclusion '$exinc->{exinc_regexp}'"); return 1; } } From e4d7a617acedeabd854ca74226c9112496ae6b7a Mon Sep 17 00:00:00 2001 From: May <110405507+paul-oureib@users.noreply.github.com> Date: Wed, 17 Apr 2024 08:48:47 +0200 Subject: [PATCH 810/948] fix(packaging): fix permission rights on systemd files packaged on centreon (#3878) --- gorgone/packaging/centreon-gorgone.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/packaging/centreon-gorgone.yaml b/gorgone/packaging/centreon-gorgone.yaml index f58b701970a..da89e288d41 100644 --- a/gorgone/packaging/centreon-gorgone.yaml +++ b/gorgone/packaging/centreon-gorgone.yaml @@ -93,12 +93,12 @@ contents: - src: "../config/systemd/gorgoned.rpm.service" dst: "/etc/systemd/system/gorgoned.service" file_info: - mode: 0755 + mode: 0644 packager: rpm - src: "../config/systemd/gorgoned.deb.service" dst: "/lib/systemd/system/gorgoned.service" file_info: - mode: 0755 + mode: 0644 packager: deb - src: "../config/systemd/gorgoned-sysconfig" From bdbfa39a608c9c43d9fbd224fc6fe2ae2b116758 Mon Sep 17 00:00:00 2001 From: sdepassio <114986849+sdepassio@users.noreply.github.com> Date: Tue, 23 Apr 2024 15:25:10 +0200 Subject: [PATCH 811/948] ci(gorgone-tests) Add gorgone testing with alma8 docker image environnement REF:MON-38234 --- .github/workflows/gorgone.yml | 59 +++++++++++++++++++ gorgone/tests/robot/resources/import.resource | 6 ++ .../tests/robot/resources/resources.resource | 27 +++++++++ .../tests/robot/start_stop/start_stop.robot | 15 +++++ 4 files changed, 107 insertions(+) create mode 100644 gorgone/tests/robot/resources/import.resource create mode 100644 gorgone/tests/robot/resources/resources.resource create mode 100644 gorgone/tests/robot/start_stop/start_stop.robot diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index fdd94fb9797..a9277b1bf03 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -124,6 +124,65 @@ jobs: rpm_gpg_signing_passphrase: ${{ secrets.RPM_GPG_SIGNING_PASSPHRASE }} stability: ${{ needs.get-version.outputs.stability }} + test-gorgone: + needs: [get-version, package] + + strategy: + fail-fast: false + matrix: + distrib: [el8] + include: + - package_extension: rpm + image: gorgone-testing-alma8 + distrib: el8 + runs-on: ubuntu-22.04 + container: + image: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }}/${{ matrix.image }}:${{ needs.get-version.outputs.major_version }} + credentials: + username: ${{ secrets.DOCKER_REGISTRY_ID }} + password: ${{ secrets.DOCKER_REGISTRY_PASSWD }} + + services: + mariadb: + image: mariadb:latest + ports: + - 3306 + env: + MYSQL_USER: user + MYSQL_PASSWORD: password + MYSQL_ROOT_PASSWORD: password + + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: get cached gorgone package + uses: actions/cache/restore@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 + with: + path: ./*.${{ matrix.package_extension }} + key: ${{ github.sha }}-${{ github.run_id }}-${{ matrix.package_extension }}-${{ matrix.distrib }} + fail-on-cache-miss: true + + - name: Install gorgogne + run: | + if [[ "${{ matrix.package_extension }}" == "deb" ]]; then + apt install -y ./centreon-gorgone*${{ matrix.distrib }}* + else + dnf install -y ./centreon-gorgone*${{ matrix.distrib }}* ./centreon-gorgone-centreon-config*${{ matrix.distrib }}* + # in el8 at least, there is a package for the configuration and a package for the actual code. + # this is not the case for debian, and for now I don't know why it was made any different between the 2 Os. + fi + + - name: Create database + run: | + mysql -h mariadb -u root -ppassword -e "CREATE DATABASE \`centreon\`" + mysql -h mariadb -u root -ppassword -e "CREATE DATABASE \`centreon-storage\`" + mysql -h mariadb -u root -ppassword 'centreon' < centreon/www/install/createTables.sql + mysql -h mariadb -u root -ppassword 'centreon-storage' < centreon/www/install/createTablesCentstorage.sql + + - name: Run tests + run: robot centreon-gorgone/tests + deliver-sources: runs-on: [self-hosted, common] needs: [get-version, package] diff --git a/gorgone/tests/robot/resources/import.resource b/gorgone/tests/robot/resources/import.resource new file mode 100644 index 00000000000..461a09f32c8 --- /dev/null +++ b/gorgone/tests/robot/resources/import.resource @@ -0,0 +1,6 @@ +*** Settings *** +Documentation This is the documentation for the import resource file. +Library Examples +Library OperatingSystem +Library String +Resource resources.resource diff --git a/gorgone/tests/robot/resources/resources.resource b/gorgone/tests/robot/resources/resources.resource new file mode 100644 index 00000000000..3e7e6ab6953 --- /dev/null +++ b/gorgone/tests/robot/resources/resources.resource @@ -0,0 +1,27 @@ +*** Settings *** +Documentation Centreon Gorgone for Robot Framework +Library Process + +*** Variables *** +${PERCENT} % + +*** Keywords *** +Start Gorgone + [Arguments] ${CONFIG_FILE} ${LOG_FILE} ${SEVERITY} ${ALIAS} + ${process} Start Process + ... /usr/bin/perl + ... /usr/bin/gorgoned + ... --config + ... ${CONFIG_FILE} + ... --logfile + ... ${LOG_FILE} + ... --severity + ... ${SEVERITY} + ... alias=${ALIAS} + +Stop Gorgone + [Arguments] ${process_alias} + ${result} Terminate Process ${process_alias} + Should Be True + ... ${result.rc} == -15 or ${result.rc} == 0 + ... Engine badly stopped alias = ${process_alias} - code returned ${result.rc}. \ No newline at end of file diff --git a/gorgone/tests/robot/start_stop/start_stop.robot b/gorgone/tests/robot/start_stop/start_stop.robot new file mode 100644 index 00000000000..66b0468652a --- /dev/null +++ b/gorgone/tests/robot/start_stop/start_stop.robot @@ -0,0 +1,15 @@ +*** Settings *** +Documentation Start and stop gorgone + +Resource ${CURDIR}${/}..${/}resources${/}import.resource + +Test Timeout 120s + +*** Test Cases *** +Start and stop gorgone + FOR ${i} IN RANGE 5 + Start Gorgone /etc/centreon-gorgone/config.yaml /var/log/centreon-gorgone/gorgoned.log info gorgone${i} + Sleep 5s + Stop Gorgone gorgone${i} + sleep 2s + END \ No newline at end of file From 7d1d43b2937551faa0a8ef43688d35d3203b7469 Mon Sep 17 00:00:00 2001 From: sdepassio <114986849+sdepassio@users.noreply.github.com> Date: Tue, 23 Apr 2024 15:25:10 +0200 Subject: [PATCH 812/948] ci(gorgone-tests) Add gorgone testing with alma8 docker image environnement REF:MON-38234 --- .../docker/Dockerfile.gorgone-testing-alma8 | 19 ++++++ .github/workflows/docker-gorgone-testing.yml | 51 ++++++++++++++++ .github/workflows/gorgone.yml | 59 +++++++++++++++++++ gorgone/tests/robot/resources/import.resource | 6 ++ .../tests/robot/resources/resources.resource | 27 +++++++++ .../tests/robot/start_stop/start_stop.robot | 15 +++++ 6 files changed, 177 insertions(+) create mode 100644 .github/docker/Dockerfile.gorgone-testing-alma8 create mode 100644 .github/workflows/docker-gorgone-testing.yml create mode 100644 gorgone/tests/robot/resources/import.resource create mode 100644 gorgone/tests/robot/resources/resources.resource create mode 100644 gorgone/tests/robot/start_stop/start_stop.robot diff --git a/.github/docker/Dockerfile.gorgone-testing-alma8 b/.github/docker/Dockerfile.gorgone-testing-alma8 new file mode 100644 index 00000000000..b38ede80796 --- /dev/null +++ b/.github/docker/Dockerfile.gorgone-testing-alma8 @@ -0,0 +1,19 @@ +FROM almalinux:8 + +RUN bash -e <<EOF + +dnf install -y dnf-plugins-core zstd curl mariadb +dnf install -y https://rpms.remirepo.net/enterprise/remi-release-8.rpm +dnf config-manager --set-enabled 'powertools' +dnf -y config-manager --add-repo https://packages.centreon.com/rpm-standard/23.10/el8/centreon-23.10.repo +dnf -y clean all --enablerepo=* + +dnf install -y python3.11 python3.11-pip +pip3.11 install robotframework robotframework-examples + + + +dnf clean all + + +EOF diff --git a/.github/workflows/docker-gorgone-testing.yml b/.github/workflows/docker-gorgone-testing.yml new file mode 100644 index 00000000000..492315e7d33 --- /dev/null +++ b/.github/workflows/docker-gorgone-testing.yml @@ -0,0 +1,51 @@ +name: docker-gorgone-packaging + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +on: + workflow_dispatch: + push: + branches: + - develop + - dev-[2-9][0-9].[0-9][0-9].x + paths: + - ".github/docker/Dockerfile.gorgone-testing-*" + pull_request: + paths: + - ".github/docker/Dockerfile.gorgone-testing-*" + +jobs: + get-version: + uses: ./.github/workflows/get-version.yml + with: + version_file: centreon/www/install/insertBaseConf.sql + + dockerize: + needs: [get-version] + runs-on: ubuntu-22.04 + + strategy: + matrix: + distrib: [alma8] + steps: + - name: Checkout sources + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Login to registry + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + with: + registry: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }} + username: ${{ secrets.DOCKER_REGISTRY_ID }} + password: ${{ secrets.DOCKER_REGISTRY_PASSWD }} + + - uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + + - uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0 + with: + file: .github/docker/Dockerfile.gorgone-testing-${{ matrix.distrib }} + context: . + pull: true + push: true + tags: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }}/gorgone-testing-${{ matrix.distrib }}:${{ needs.get-version.outputs.major_version }} diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index fdd94fb9797..a9277b1bf03 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -124,6 +124,65 @@ jobs: rpm_gpg_signing_passphrase: ${{ secrets.RPM_GPG_SIGNING_PASSPHRASE }} stability: ${{ needs.get-version.outputs.stability }} + test-gorgone: + needs: [get-version, package] + + strategy: + fail-fast: false + matrix: + distrib: [el8] + include: + - package_extension: rpm + image: gorgone-testing-alma8 + distrib: el8 + runs-on: ubuntu-22.04 + container: + image: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }}/${{ matrix.image }}:${{ needs.get-version.outputs.major_version }} + credentials: + username: ${{ secrets.DOCKER_REGISTRY_ID }} + password: ${{ secrets.DOCKER_REGISTRY_PASSWD }} + + services: + mariadb: + image: mariadb:latest + ports: + - 3306 + env: + MYSQL_USER: user + MYSQL_PASSWORD: password + MYSQL_ROOT_PASSWORD: password + + steps: + - name: Checkout sources + uses: actions/checkout@v4 + + - name: get cached gorgone package + uses: actions/cache/restore@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 + with: + path: ./*.${{ matrix.package_extension }} + key: ${{ github.sha }}-${{ github.run_id }}-${{ matrix.package_extension }}-${{ matrix.distrib }} + fail-on-cache-miss: true + + - name: Install gorgogne + run: | + if [[ "${{ matrix.package_extension }}" == "deb" ]]; then + apt install -y ./centreon-gorgone*${{ matrix.distrib }}* + else + dnf install -y ./centreon-gorgone*${{ matrix.distrib }}* ./centreon-gorgone-centreon-config*${{ matrix.distrib }}* + # in el8 at least, there is a package for the configuration and a package for the actual code. + # this is not the case for debian, and for now I don't know why it was made any different between the 2 Os. + fi + + - name: Create database + run: | + mysql -h mariadb -u root -ppassword -e "CREATE DATABASE \`centreon\`" + mysql -h mariadb -u root -ppassword -e "CREATE DATABASE \`centreon-storage\`" + mysql -h mariadb -u root -ppassword 'centreon' < centreon/www/install/createTables.sql + mysql -h mariadb -u root -ppassword 'centreon-storage' < centreon/www/install/createTablesCentstorage.sql + + - name: Run tests + run: robot centreon-gorgone/tests + deliver-sources: runs-on: [self-hosted, common] needs: [get-version, package] diff --git a/gorgone/tests/robot/resources/import.resource b/gorgone/tests/robot/resources/import.resource new file mode 100644 index 00000000000..461a09f32c8 --- /dev/null +++ b/gorgone/tests/robot/resources/import.resource @@ -0,0 +1,6 @@ +*** Settings *** +Documentation This is the documentation for the import resource file. +Library Examples +Library OperatingSystem +Library String +Resource resources.resource diff --git a/gorgone/tests/robot/resources/resources.resource b/gorgone/tests/robot/resources/resources.resource new file mode 100644 index 00000000000..3e7e6ab6953 --- /dev/null +++ b/gorgone/tests/robot/resources/resources.resource @@ -0,0 +1,27 @@ +*** Settings *** +Documentation Centreon Gorgone for Robot Framework +Library Process + +*** Variables *** +${PERCENT} % + +*** Keywords *** +Start Gorgone + [Arguments] ${CONFIG_FILE} ${LOG_FILE} ${SEVERITY} ${ALIAS} + ${process} Start Process + ... /usr/bin/perl + ... /usr/bin/gorgoned + ... --config + ... ${CONFIG_FILE} + ... --logfile + ... ${LOG_FILE} + ... --severity + ... ${SEVERITY} + ... alias=${ALIAS} + +Stop Gorgone + [Arguments] ${process_alias} + ${result} Terminate Process ${process_alias} + Should Be True + ... ${result.rc} == -15 or ${result.rc} == 0 + ... Engine badly stopped alias = ${process_alias} - code returned ${result.rc}. \ No newline at end of file diff --git a/gorgone/tests/robot/start_stop/start_stop.robot b/gorgone/tests/robot/start_stop/start_stop.robot new file mode 100644 index 00000000000..66b0468652a --- /dev/null +++ b/gorgone/tests/robot/start_stop/start_stop.robot @@ -0,0 +1,15 @@ +*** Settings *** +Documentation Start and stop gorgone + +Resource ${CURDIR}${/}..${/}resources${/}import.resource + +Test Timeout 120s + +*** Test Cases *** +Start and stop gorgone + FOR ${i} IN RANGE 5 + Start Gorgone /etc/centreon-gorgone/config.yaml /var/log/centreon-gorgone/gorgoned.log info gorgone${i} + Sleep 5s + Stop Gorgone gorgone${i} + sleep 2s + END \ No newline at end of file From 5099c6671c26bdd183e871e75bc71f47d8535732 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Thu, 25 Apr 2024 15:14:40 +0200 Subject: [PATCH 813/948] fix(gorgone): restore gorgone packaging file (conflict resolution) (#3940) --- gorgone/packaging/centreon-gorgone.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/packaging/centreon-gorgone.yaml b/gorgone/packaging/centreon-gorgone.yaml index da89e288d41..fc94711c0a1 100644 --- a/gorgone/packaging/centreon-gorgone.yaml +++ b/gorgone/packaging/centreon-gorgone.yaml @@ -51,7 +51,7 @@ contents: mode: 0640 - src: "./configuration/action.yaml" - dst: "/etc/centreon-gorgone/config.d/41-action.yaml" + dst: "/etc/centreon-gorgone/config.d/39-action.yaml" file_info: owner: centreon-gorgone group: centreon-gorgone From 27cc629a33c73fb6e1c69dcdbeccb72906684b88 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Thu, 25 Apr 2024 15:14:40 +0200 Subject: [PATCH 814/948] fix(gorgone): restore gorgone packaging file (conflict resolution) (#3940) --- gorgone/packaging/centreon-gorgone.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/packaging/centreon-gorgone.yaml b/gorgone/packaging/centreon-gorgone.yaml index da89e288d41..fc94711c0a1 100644 --- a/gorgone/packaging/centreon-gorgone.yaml +++ b/gorgone/packaging/centreon-gorgone.yaml @@ -51,7 +51,7 @@ contents: mode: 0640 - src: "./configuration/action.yaml" - dst: "/etc/centreon-gorgone/config.d/41-action.yaml" + dst: "/etc/centreon-gorgone/config.d/39-action.yaml" file_info: owner: centreon-gorgone group: centreon-gorgone From b65deff92e566e20508af5e3adf5c252364b2177 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Chapron?= <34628915+sc979@users.noreply.github.com> Date: Thu, 2 May 2024 17:04:26 +0200 Subject: [PATCH 815/948] chore(ci): deploy veracode 3rd party override in components (#4002) --- gorgone/veracode.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 gorgone/veracode.json diff --git a/gorgone/veracode.json b/gorgone/veracode.json new file mode 100644 index 00000000000..329f76f89be --- /dev/null +++ b/gorgone/veracode.json @@ -0,0 +1,3 @@ +{ + "ignorethirdparty": "false" +} \ No newline at end of file From 87d5bb5c3cb172eac01545818367e16265a32b78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Chapron?= <34628915+sc979@users.noreply.github.com> Date: Thu, 2 May 2024 17:04:26 +0200 Subject: [PATCH 816/948] chore(ci): deploy veracode 3rd party override in components (#4002) --- gorgone/veracode.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 gorgone/veracode.json diff --git a/gorgone/veracode.json b/gorgone/veracode.json new file mode 100644 index 00000000000..329f76f89be --- /dev/null +++ b/gorgone/veracode.json @@ -0,0 +1,3 @@ +{ + "ignorethirdparty": "false" +} \ No newline at end of file From ec95c56fa7ff1e338efef61b5366350a7d04536d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Chapron?= <34628915+sc979@users.noreply.github.com> Date: Fri, 3 May 2024 14:35:27 +0200 Subject: [PATCH 817/948] chore(ci): prevent override file from triggering a build (#4008) --- .github/workflows/gorgone.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index a9277b1bf03..f3985cfff0b 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -9,6 +9,8 @@ on: pull_request: paths: - "centreon-gorgone/**" + - "!centreon-gorgone/veracode.json" + - "!centreon-gorgone/.veracode-exclusions" push: branches: - develop @@ -17,6 +19,8 @@ on: - "[2-9][0-9].[0-9][0-9].x" paths: - "centreon-gorgone/**" + - "!centreon-gorgone/veracode.json" + - "!centreon-gorgone/.veracode-exclusions" env: base_directory: centreon-gorgone From a044f216c79216041a44a495fe4f33e6c3761f89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Chapron?= <34628915+sc979@users.noreply.github.com> Date: Fri, 3 May 2024 14:35:27 +0200 Subject: [PATCH 818/948] chore(ci): prevent override file from triggering a build (#4008) --- .github/workflows/gorgone.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index a9277b1bf03..f3985cfff0b 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -9,6 +9,8 @@ on: pull_request: paths: - "centreon-gorgone/**" + - "!centreon-gorgone/veracode.json" + - "!centreon-gorgone/.veracode-exclusions" push: branches: - develop @@ -17,6 +19,8 @@ on: - "[2-9][0-9].[0-9][0-9].x" paths: - "centreon-gorgone/**" + - "!centreon-gorgone/veracode.json" + - "!centreon-gorgone/.veracode-exclusions" env: base_directory: centreon-gorgone From fc5e041e873bf9b969890ae2c447e1379f358731 Mon Sep 17 00:00:00 2001 From: tuntoja <58987095+tuntoja@users.noreply.github.com> Date: Tue, 7 May 2024 10:10:03 +0200 Subject: [PATCH 819/948] chore(release): prepare 24.05.0 (#3855) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kevin Duret <kduret@centreon.com> Co-authored-by: Stéphane Chapron <34628915+sc979@users.noreply.github.com> Co-authored-by: May <110405507+mushroomempires@users.noreply.github.com> --- gorgone/packaging/centreon-gorgone-centreon-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/packaging/centreon-gorgone-centreon-config.yaml b/gorgone/packaging/centreon-gorgone-centreon-config.yaml index 327698ffbd8..c57d0cc9c8e 100644 --- a/gorgone/packaging/centreon-gorgone-centreon-config.yaml +++ b/gorgone/packaging/centreon-gorgone-centreon-config.yaml @@ -56,11 +56,11 @@ overrides: depends: - centreon-gorgone (= ${VERSION}-${RELEASE}${DIST}) replaces: - - centreon-gorgone (<< 24.04.0) + - centreon-gorgone (<< 24.05.0) deb: breaks: - - centreon-gorgone (<< 24.04.0) + - centreon-gorgone (<< 24.05.0) rpm: summary: Configure Centreon Gorgone for use with Centreon Web From 5651576350139fc8631982ca897548fb1accf56f Mon Sep 17 00:00:00 2001 From: tuntoja <58987095+tuntoja@users.noreply.github.com> Date: Tue, 7 May 2024 10:10:03 +0200 Subject: [PATCH 820/948] chore(release): prepare 24.05.0 (#3855) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kevin Duret <kduret@centreon.com> Co-authored-by: Stéphane Chapron <34628915+sc979@users.noreply.github.com> Co-authored-by: May <110405507+mushroomempires@users.noreply.github.com> --- gorgone/packaging/centreon-gorgone-centreon-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/packaging/centreon-gorgone-centreon-config.yaml b/gorgone/packaging/centreon-gorgone-centreon-config.yaml index 327698ffbd8..c57d0cc9c8e 100644 --- a/gorgone/packaging/centreon-gorgone-centreon-config.yaml +++ b/gorgone/packaging/centreon-gorgone-centreon-config.yaml @@ -56,11 +56,11 @@ overrides: depends: - centreon-gorgone (= ${VERSION}-${RELEASE}${DIST}) replaces: - - centreon-gorgone (<< 24.04.0) + - centreon-gorgone (<< 24.05.0) deb: breaks: - - centreon-gorgone (<< 24.04.0) + - centreon-gorgone (<< 24.05.0) rpm: summary: Configure Centreon Gorgone for use with Centreon Web From d11cdf6e5dbc364ee97c33066f645ee3a5f6499b Mon Sep 17 00:00:00 2001 From: Evan-Adam <152897682+Evan-Adam@users.noreply.github.com> Date: Wed, 15 May 2024 10:07:45 +0200 Subject: [PATCH 821/948] fix(gorgone-discovery) during service discovery gorgone display vault error messages (#3883) Co-authored-by: Colin Gagnaire <gagnaire.colin@gmail.com> REF:MON-23408 --- .../modules/centreon/autodiscovery/services/resources.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm b/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm index 605ff9900d1..7185326ba4d 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm @@ -458,7 +458,7 @@ sub get_macros_host { my $macro_value = $_->[1]; my $is_password = $_->[2]; # Replace macro value if a vault is used - if ($options{vault_count} > 0 && defined($is_password) && $is_password == 1) { + if (defined($options{vault_count}) && $options{vault_count} > 0 && defined($is_password) && $is_password == 1) { set_macro(\%macros, $macro_name, "{" . $macro_name . "::secret::" . $macro_value . "}"); } else { set_macro(\%macros, $macro_name, $macro_value); @@ -498,7 +498,7 @@ sub substitute_service_discovery_command { $command =~ s/\$HOSTADDRESS\$/$options{host}->{host_address}/g; $command =~ s/\$HOSTNAME\$/$options{host}->{host_name}/g; - if ($options{vault_count} > 0) { + if (defined($options{vault_count}) && $options{vault_count} > 0) { $command .= ' --pass-manager="centreonvault"'; } From f8a80fa3bf254c69cc8f2c886fcb14f7e905cf98 Mon Sep 17 00:00:00 2001 From: Evan-Adam <152897682+Evan-Adam@users.noreply.github.com> Date: Wed, 15 May 2024 10:07:45 +0200 Subject: [PATCH 822/948] fix(gorgone-discovery) during service discovery gorgone display vault error messages (#3883) Co-authored-by: Colin Gagnaire <gagnaire.colin@gmail.com> REF:MON-23408 --- .../modules/centreon/autodiscovery/services/resources.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm b/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm index 605ff9900d1..7185326ba4d 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm @@ -458,7 +458,7 @@ sub get_macros_host { my $macro_value = $_->[1]; my $is_password = $_->[2]; # Replace macro value if a vault is used - if ($options{vault_count} > 0 && defined($is_password) && $is_password == 1) { + if (defined($options{vault_count}) && $options{vault_count} > 0 && defined($is_password) && $is_password == 1) { set_macro(\%macros, $macro_name, "{" . $macro_name . "::secret::" . $macro_value . "}"); } else { set_macro(\%macros, $macro_name, $macro_value); @@ -498,7 +498,7 @@ sub substitute_service_discovery_command { $command =~ s/\$HOSTADDRESS\$/$options{host}->{host_address}/g; $command =~ s/\$HOSTNAME\$/$options{host}->{host_name}/g; - if ($options{vault_count} > 0) { + if (defined($options{vault_count}) && $options{vault_count} > 0) { $command .= ' --pass-manager="centreonvault"'; } From 9143b1e14f23616adb871f41da83cdf84c73fb6d Mon Sep 17 00:00:00 2001 From: Evan-Adam <152897682+Evan-Adam@users.noreply.github.com> Date: Thu, 16 May 2024 14:14:00 +0200 Subject: [PATCH 823/948] ci(gorgone-tests) initialize test environment for gorgone on debian 11 (#3876) REF:MON-46504 --- .github/workflows/gorgone.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index f3985cfff0b..36209e7627a 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -134,11 +134,14 @@ jobs: strategy: fail-fast: false matrix: - distrib: [el8] + distrib: [el8, bullseye] include: - package_extension: rpm image: gorgone-testing-alma8 distrib: el8 + - package_extension: deb + image: gorgone-testing-bullseye + distrib: bullseye runs-on: ubuntu-22.04 container: image: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }}/${{ matrix.image }}:${{ needs.get-version.outputs.major_version }} @@ -168,6 +171,7 @@ jobs: fail-on-cache-miss: true - name: Install gorgogne + shell: bash run: | if [[ "${{ matrix.package_extension }}" == "deb" ]]; then apt install -y ./centreon-gorgone*${{ matrix.distrib }}* From d6be01b0ab0da9e23525cd369848b3690b3e3c0d Mon Sep 17 00:00:00 2001 From: Evan-Adam <152897682+Evan-Adam@users.noreply.github.com> Date: Thu, 16 May 2024 14:14:00 +0200 Subject: [PATCH 824/948] ci(gorgone-tests) initialize test environment for gorgone on debian 11 (#3876) REF:MON-46504 --- .../docker/Dockerfile.gorgone-testing-alma8 | 4 --- .../Dockerfile.gorgone-testing-bullseye | 30 +++++++++++++++++++ .github/workflows/docker-gorgone-testing.yml | 4 +-- .github/workflows/gorgone.yml | 6 +++- 4 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 .github/docker/Dockerfile.gorgone-testing-bullseye diff --git a/.github/docker/Dockerfile.gorgone-testing-alma8 b/.github/docker/Dockerfile.gorgone-testing-alma8 index b38ede80796..7964dde694a 100644 --- a/.github/docker/Dockerfile.gorgone-testing-alma8 +++ b/.github/docker/Dockerfile.gorgone-testing-alma8 @@ -7,13 +7,9 @@ dnf install -y https://rpms.remirepo.net/enterprise/remi-release-8.rpm dnf config-manager --set-enabled 'powertools' dnf -y config-manager --add-repo https://packages.centreon.com/rpm-standard/23.10/el8/centreon-23.10.repo dnf -y clean all --enablerepo=* - dnf install -y python3.11 python3.11-pip pip3.11 install robotframework robotframework-examples - - dnf clean all - EOF diff --git a/.github/docker/Dockerfile.gorgone-testing-bullseye b/.github/docker/Dockerfile.gorgone-testing-bullseye new file mode 100644 index 00000000000..15004724967 --- /dev/null +++ b/.github/docker/Dockerfile.gorgone-testing-bullseye @@ -0,0 +1,30 @@ +FROM debian:bullseye + +ENV DEBIAN_FRONTEND noninteractive + +# fix locale +RUN bash -e <<EOF + +apt-get update +apt-get install -y locales libcurl4-openssl-dev curl wget zstd jq lsb-release mariadb-client +rm -rf /var/lib/apt/lists/* +localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 +apt-get clean + +EOF + +ENV LANG en_US.utf8 + +RUN bash -e <<EOF +apt-get update +# Install Robotframework +apt-get install -y python3 python3-dev python3-pip ca-certificates apt-transport-https software-properties-common wget gnupg2 +pip3 install robotframework robotframework-examples + +echo "deb https://packages.centreon.com/apt-standard-23.10-stable/ $(lsb_release -sc) main" | tee /etc/apt/sources.list.d/centreon.list +echo "deb https://packages.centreon.com/apt-plugins-stable/ $(lsb_release -sc) main" | tee /etc/apt/sources.list.d/centreon-plugins.list + +wget -O- https://apt-key.centreon.com | gpg --dearmor | tee /etc/apt/trusted.gpg.d/centreon.gpg > /dev/null 2>&1 +apt-get update + +EOF diff --git a/.github/workflows/docker-gorgone-testing.yml b/.github/workflows/docker-gorgone-testing.yml index 492315e7d33..8f1c0bc5029 100644 --- a/.github/workflows/docker-gorgone-testing.yml +++ b/.github/workflows/docker-gorgone-testing.yml @@ -1,4 +1,4 @@ -name: docker-gorgone-packaging +name: docker-gorgone-testing concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} @@ -28,7 +28,7 @@ jobs: strategy: matrix: - distrib: [alma8] + distrib: [alma8, bullseye] steps: - name: Checkout sources uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index f3985cfff0b..36209e7627a 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -134,11 +134,14 @@ jobs: strategy: fail-fast: false matrix: - distrib: [el8] + distrib: [el8, bullseye] include: - package_extension: rpm image: gorgone-testing-alma8 distrib: el8 + - package_extension: deb + image: gorgone-testing-bullseye + distrib: bullseye runs-on: ubuntu-22.04 container: image: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }}/${{ matrix.image }}:${{ needs.get-version.outputs.major_version }} @@ -168,6 +171,7 @@ jobs: fail-on-cache-miss: true - name: Install gorgogne + shell: bash run: | if [[ "${{ matrix.package_extension }}" == "deb" ]]; then apt install -y ./centreon-gorgone*${{ matrix.distrib }}* From 90014e5bbc57f23a92eb7dbb6f966468da314dd7 Mon Sep 17 00:00:00 2001 From: May <110405507+mushroomempires@users.noreply.github.com> Date: Thu, 16 May 2024 15:34:10 +0200 Subject: [PATCH 825/948] chore(deps): absorb May-24 dependabot dependencies (#4076) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/gorgone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 36209e7627a..5aecc0ad643 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -164,7 +164,7 @@ jobs: uses: actions/checkout@v4 - name: get cached gorgone package - uses: actions/cache/restore@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 + uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 with: path: ./*.${{ matrix.package_extension }} key: ${{ github.sha }}-${{ github.run_id }}-${{ matrix.package_extension }}-${{ matrix.distrib }} From ae496ed800816f3b87962296273b7f2f70aa7d94 Mon Sep 17 00:00:00 2001 From: May <110405507+mushroomempires@users.noreply.github.com> Date: Thu, 16 May 2024 15:34:10 +0200 Subject: [PATCH 826/948] chore(deps): absorb May-24 dependabot dependencies (#4076) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker-gorgone-testing.yml | 6 +++--- .github/workflows/gorgone.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker-gorgone-testing.yml b/.github/workflows/docker-gorgone-testing.yml index 8f1c0bc5029..05bfeaf0632 100644 --- a/.github/workflows/docker-gorgone-testing.yml +++ b/.github/workflows/docker-gorgone-testing.yml @@ -34,15 +34,15 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Login to registry - uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 with: registry: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }} username: ${{ secrets.DOCKER_REGISTRY_ID }} password: ${{ secrets.DOCKER_REGISTRY_PASSWD }} - - uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + - uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 - - uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0 + - uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0 with: file: .github/docker/Dockerfile.gorgone-testing-${{ matrix.distrib }} context: . diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 36209e7627a..5aecc0ad643 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -164,7 +164,7 @@ jobs: uses: actions/checkout@v4 - name: get cached gorgone package - uses: actions/cache/restore@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 + uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 with: path: ./*.${{ matrix.package_extension }} key: ${{ github.sha }}-${{ github.run_id }}-${{ matrix.package_extension }}-${{ matrix.distrib }} From 72bbe28b258e570e101ecd0b965c87c0e701d1d0 Mon Sep 17 00:00:00 2001 From: Evan-Adam <152897682+Evan-Adam@users.noreply.github.com> Date: Thu, 23 May 2024 10:46:32 +0200 Subject: [PATCH 827/948] fix(gorgone-logs) change whitelist error logs from info to error (#4063) REF:MON-51684 --- gorgone/gorgone/modules/core/action/class.pm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index dc7f30d27f3..aa2a0a84aec 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -387,14 +387,14 @@ sub action_command { } if ($self->is_command_authorized(command => $command->{command})) { - $self->{logger}->writeLogInfo("[action] command not allowed (whitelist): " . $command->{command}); + $self->{logger}->writeLogError("[action] command not allowed (whitelist): " . $command->{command}); $self->send_log( socket => $options{socket_log}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, logging => $options{data}->{logging}, data => { - message => "command not allowed (whitelist) at array index '" . $index . "'" + message => "command not allowed (whitelist) at array index '$index' : $command->{command}" } ); return -1; @@ -679,14 +679,14 @@ sub action_actionengine { } if ($self->is_command_authorized(command => $options{data}->{content}->{command})) { - $self->{logger}->writeLogInfo("[action] command not allowed (whitelist): " . $options{data}->{content}->{command}); + $self->{logger}->writeLogError("[action] command not allowed (whitelist): " . $options{data}->{content}->{command}); $self->send_log( socket => $options{socket_log}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, logging => $options{data}->{logging}, data => { - message => 'command not allowed (whitelist)' + message => 'command not allowed (whitelist)' . $options{data}->{content}->{command} } ); return -1; From 52f3e7e81af8726ef858ffbd59f645ca81b9e2ca Mon Sep 17 00:00:00 2001 From: Evan-Adam <152897682+Evan-Adam@users.noreply.github.com> Date: Thu, 23 May 2024 10:46:32 +0200 Subject: [PATCH 828/948] fix(gorgone-logs) change whitelist error logs from info to error (#4063) REF:MON-51684 --- gorgone/gorgone/modules/core/action/class.pm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gorgone/gorgone/modules/core/action/class.pm b/gorgone/gorgone/modules/core/action/class.pm index dc7f30d27f3..aa2a0a84aec 100644 --- a/gorgone/gorgone/modules/core/action/class.pm +++ b/gorgone/gorgone/modules/core/action/class.pm @@ -387,14 +387,14 @@ sub action_command { } if ($self->is_command_authorized(command => $command->{command})) { - $self->{logger}->writeLogInfo("[action] command not allowed (whitelist): " . $command->{command}); + $self->{logger}->writeLogError("[action] command not allowed (whitelist): " . $command->{command}); $self->send_log( socket => $options{socket_log}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, logging => $options{data}->{logging}, data => { - message => "command not allowed (whitelist) at array index '" . $index . "'" + message => "command not allowed (whitelist) at array index '$index' : $command->{command}" } ); return -1; @@ -679,14 +679,14 @@ sub action_actionengine { } if ($self->is_command_authorized(command => $options{data}->{content}->{command})) { - $self->{logger}->writeLogInfo("[action] command not allowed (whitelist): " . $options{data}->{content}->{command}); + $self->{logger}->writeLogError("[action] command not allowed (whitelist): " . $options{data}->{content}->{command}); $self->send_log( socket => $options{socket_log}, code => GORGONE_ACTION_FINISH_KO, token => $options{token}, logging => $options{data}->{logging}, data => { - message => 'command not allowed (whitelist)' + message => 'command not allowed (whitelist)' . $options{data}->{content}->{command} } ); return -1; From 367f196c4288a9a5414f655167bd10011f1599cf Mon Sep 17 00:00:00 2001 From: Evan-Adam <152897682+Evan-Adam@users.noreply.github.com> Date: Fri, 24 May 2024 16:57:18 +0200 Subject: [PATCH 829/948] ci(gorgone-tests) test zmq standard configuration on debian 11 and alma 8 (#3907) Co-authored-by: Sophie Depassio <sdepassio@centreon.com> Co-authored-by: May <110405507+mushroomempires@users.noreply.github.com> Co-authored-by: omercier <32134301+omercier@users.noreply.github.com> Refs:MON-38012 --- .github/workflows/gorgone.yml | 22 +++- gorgone/contrib/gorgone_key_generation.pl | 41 ++++++ gorgone/packaging/centreon-gorgone.yaml | 7 + gorgone/tests/robot/config/includer.yaml | 3 + .../robot/config/push_central_config.yaml | 43 ++++++ .../tests/robot/config/push_db_1_poller.sql | 68 ++++++++++ .../robot/config/push_db_1_poller_delete.sql | 2 + .../robot/config/push_poller_config.yaml | 21 +++ gorgone/tests/robot/resources/import.resource | 1 + .../tests/robot/resources/resources.resource | 123 ++++++++++++++++-- .../tests/robot/start_stop/start_stop.robot | 15 --- gorgone/tests/robot/tests/core/push.robot | 16 +++ .../tests/robot/tests/start_stop/config.yaml | 34 +++++ .../robot/tests/start_stop/start_stop.robot | 19 +++ 14 files changed, 385 insertions(+), 30 deletions(-) create mode 100644 gorgone/contrib/gorgone_key_generation.pl create mode 100644 gorgone/tests/robot/config/includer.yaml create mode 100644 gorgone/tests/robot/config/push_central_config.yaml create mode 100644 gorgone/tests/robot/config/push_db_1_poller.sql create mode 100644 gorgone/tests/robot/config/push_db_1_poller_delete.sql create mode 100644 gorgone/tests/robot/config/push_poller_config.yaml delete mode 100644 gorgone/tests/robot/start_stop/start_stop.robot create mode 100644 gorgone/tests/robot/tests/core/push.robot create mode 100644 gorgone/tests/robot/tests/start_stop/config.yaml create mode 100644 gorgone/tests/robot/tests/start_stop/start_stop.robot diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 5aecc0ad643..82a64588afa 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -155,7 +155,7 @@ jobs: ports: - 3306 env: - MYSQL_USER: user + MYSQL_USER: centreon MYSQL_PASSWORD: password MYSQL_ROOT_PASSWORD: password @@ -170,7 +170,7 @@ jobs: key: ${{ github.sha }}-${{ github.run_id }}-${{ matrix.package_extension }}-${{ matrix.distrib }} fail-on-cache-miss: true - - name: Install gorgogne + - name: Install gorgogne from just built package shell: bash run: | if [[ "${{ matrix.package_extension }}" == "deb" ]]; then @@ -181,15 +181,29 @@ jobs: # this is not the case for debian, and for now I don't know why it was made any different between the 2 Os. fi - - name: Create database + - name: Create databases run: | mysql -h mariadb -u root -ppassword -e "CREATE DATABASE \`centreon\`" mysql -h mariadb -u root -ppassword -e "CREATE DATABASE \`centreon-storage\`" + mysql -h mariadb -u root -ppassword -e "GRANT ALL PRIVILEGES ON centreon.* TO 'centreon'@'%'" + mysql -h mariadb -u root -ppassword -e "GRANT ALL PRIVILEGES ON \`centreon-storage\`.* TO 'centreon'@'%'" mysql -h mariadb -u root -ppassword 'centreon' < centreon/www/install/createTables.sql mysql -h mariadb -u root -ppassword 'centreon-storage' < centreon/www/install/createTablesCentstorage.sql - name: Run tests - run: robot centreon-gorgone/tests + run: robot -v 'DBHOST:mariadb' -v 'DBNAME:centreon' -v 'DBUSER:centreon' centreon-gorgone/tests + + + - name: Upload gorgone and robot debug artifacts + if: failure() + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: gorgone-debug-${{ matrix.distrib }} + path: | + log.html + /var/log/centreon-gorgone + /etc/centreon-gorgone + retention-days: 1 deliver-sources: runs-on: [self-hosted, common] diff --git a/gorgone/contrib/gorgone_key_generation.pl b/gorgone/contrib/gorgone_key_generation.pl new file mode 100644 index 00000000000..a7ec62bbad3 --- /dev/null +++ b/gorgone/contrib/gorgone_key_generation.pl @@ -0,0 +1,41 @@ +#!/usr/bin/perl +use strict; +use warnings FATAL => 'all'; +use Try::Tiny; +use Crypt::PK::RSA; +use File::Basename qw( fileparse ); + +# generate key if there is none. +# Gorgone can generate it's own key, but as we need the thumbprint in the configuration we need to generate them before launching gorgone. +# this script only create key if the files don't exists, and silently finish if the files already exists. + +my ($privkey, $pubkey); + +my $priv_dest = '/var/lib/centreon-gorgone/.keys/rsakey.priv.pem'; +my $pub_dest = '/var/lib/centreon-gorgone/.keys/rsakey.pub.pem'; +$ARGV[0] and $priv_dest = $ARGV[0]; +$ARGV[1] and $pub_dest = $ARGV[1]; +if (-f $priv_dest or -f $pub_dest){ + print("files already exist, no overriding is done.\n"); + exit 0; +} +try { + my $pkrsa = Crypt::PK::RSA->new(); + $pkrsa->generate_key(256, 65537); + $pubkey = $pkrsa->export_key_pem('public_x509'); + $privkey = $pkrsa->export_key_pem('private'); +} catch { + die("Cannot generate server keys: $_\n"); +}; + +my ( $priv_key_name, $priv_folder_name ) = fileparse $priv_dest; +`mkdir -p $priv_folder_name`; +open(my $priv_fh, '>', $priv_dest) or die("failed opening $priv_dest : $!"); +print $priv_fh $privkey; +print "private key saved to file.\n"; + +my ( $pub_key_name, $pub_folder_name ) = fileparse $pub_dest; +`mkdir -p $pub_folder_name`; +open(my $pub_fh, '>', $pub_dest) or die("failed opening $pub_dest : $!"); +print $pub_fh $pubkey; +print "pub key saved to file.\n"; diff --git a/gorgone/packaging/centreon-gorgone.yaml b/gorgone/packaging/centreon-gorgone.yaml index fc94711c0a1..c24f1d8ffec 100644 --- a/gorgone/packaging/centreon-gorgone.yaml +++ b/gorgone/packaging/centreon-gorgone.yaml @@ -143,6 +143,11 @@ contents: file_info: mode: 0750 + - src: "../contrib/gorgone_key_generation.pl" + dst: "/usr/local/bin/" + file_info: + mode: 0750 + scripts: preinstall: ./scripts/centreon-gorgone-preinstall.sh postinstall: ./scripts/centreon-gorgone-postinstall.sh @@ -183,6 +188,7 @@ overrides: - perl(DateTime) - perl(Try::Tiny) - tar + - perl(JSON) # gorgone_key_thumbprint.pl needs the json module, even when json::xs is already installed deb: depends: # those dependencies are taken from centreon-gorgone/packaging/debian/control - centreon-common @@ -213,6 +219,7 @@ overrides: - libev-perl - libzmq-ffi-perl - libclone-choose-perl + - libjson-perl # gorgone_key_thumbprint.pl needs the json module, even when json::xs is already installed rpm: summary: Centreon gorgone daemon diff --git a/gorgone/tests/robot/config/includer.yaml b/gorgone/tests/robot/config/includer.yaml new file mode 100644 index 00000000000..f1a5cad34bd --- /dev/null +++ b/gorgone/tests/robot/config/includer.yaml @@ -0,0 +1,3 @@ +name: config.yaml +description: Configuration of centreon-gorgone. Use config.d directory to change configuration +configuration: !include /etc/centreon-gorgone/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/config.d/*.yaml diff --git a/gorgone/tests/robot/config/push_central_config.yaml b/gorgone/tests/robot/config/push_central_config.yaml new file mode 100644 index 00000000000..d866027ade3 --- /dev/null +++ b/gorgone/tests/robot/config/push_central_config.yaml @@ -0,0 +1,43 @@ +gorgone: + gorgonecore: + internal_com_type: ipc + internal_com_path: /etc/centreon-gorgone/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/routing.ipc + external_com_type: tcp + + gorgone_db_type: SQLite + gorgone_db_name: dbname=/etc/centreon-gorgone/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/history.sdb + id: 1 + privkey: "/var/lib/centreon-gorgone/.keys/rsakey.priv.pem" + pubkey: "/var/lib/centreon-gorgone/.keys/rsakey.pub.pem" + modules: + - name: proxy + package: "gorgone::modules::core::proxy::hooks" + enable: true + + - name: nodes + package: "gorgone::modules::centreon::nodes::hooks" + enable: true + + - name: httpserver + package: "gorgone::modules::core::httpserver::hooks" + enable: true + address: "0.0.0.0" + port: "8085" + ssl: false + auth: + enabled: false + allowed_hosts: + enabled: true + subnets: + - 127.0.0.1/32 + +centreon: + database: + db_configuration: + dsn: "mysql:host=@DBHOST@:port=3306;dbname=@DBNAME@" + username: "@DBUSER@" + password: "@DBPASSWORD@" + db_realtime: + dsn: "mysql:host=@DBHOST@:port=3306;dbname=centreon_storage" + username: "@DBUSER@" + password: "@DBPASSWORD@" diff --git a/gorgone/tests/robot/config/push_db_1_poller.sql b/gorgone/tests/robot/config/push_db_1_poller.sql new file mode 100644 index 00000000000..e8e3edf6991 --- /dev/null +++ b/gorgone/tests/robot/config/push_db_1_poller.sql @@ -0,0 +1,68 @@ +INSERT IGNORE INTO `nagios_server` + VALUES + ( + 1, 'Central', '1', 1, 1711560733, '127.0.0.1', + '1', '0', 'service centengine start', + 'service centengine stop', 'service centengine restart', + 'service centengine reload', '/usr/sbin/centengine', + '/usr/sbin/centenginestats', '/var/log/centreon-engine/service-perfdata', + 'service cbd reload', '/etc/centreon-broker', + '/usr/share/centreon/lib/centreon-broker', + '/usr/lib64/centreon-connector', + 22, '1', 5556, 'centreontrapd', '/etc/snmp/centreon_traps/', + NULL, NULL, NULL, NULL, '1', '0' + ), + ( + 2, 'pushpoller', '0', 0, NULL, '127.0.0.1', + '1', '0', 'service centengine start', + 'service centengine stop', 'service centengine restart', + 'service centengine reload', '/usr/sbin/centengine', + '/usr/sbin/centenginestats', '/var/log/centreon-engine/service-perfdata', + 'service cbd reload', '/etc/centreon-broker', + '/usr/share/centreon/lib/centreon-broker', + '/usr/lib64/centreon-connector', + 22, '1', 5556, 'centreontrapd', '/etc/snmp/centreon_traps/', + NULL, NULL, '/var/log/centreon-broker/', + NULL, '1', '0' + ); +INSERT IGNORE INTO `cfg_nagios` + VALUES + ( + 1, 'Centreon Engine Central', NULL, + '/var/log/centreon-engine/centengine.log', + '/etc/centreon-engine', '/var/log/centreon-engine/status.dat', + 60, '1', '1', '1', '1', '1', '1', '1', + 4096, '1s', '/var/lib/centreon-engine/rw/centengine.cmd', + '1', '/var/log/centreon-engine/retention.dat', + 60, '1', '1', '0', '1', '1', '1', '1', + NULL, '1', '1', NULL, NULL, NULL, 's', + 's', 's', 0, 15, 15, 5, '0', NULL, NULL, + '0', '25.0', '50.0', '25.0', '50.0', + '0', 60, 12, 30, 30, '1', '1', '0', NULL, + NULL, '0', NULL, 'euro', 30, '~!$%^&*\"|\'<>?,()=', + '`~$^&\"|\'<>', '0', '0', 'admin@localhost', + 'admin@localhost', 'Centreon Engine configuration file for a central instance', + '1', '-1', 1, '1', '1', 15, 15, NULL, + '0', 15, '/var/log/centreon-engine/centengine.debug', + 0, '0', '1', 1000000000, 'centengine.cfg', + '1', '0', '', 'log_v2_enabled' + ), + ( + 15, 'pushpoller', NULL, '/var/log/centreon-engine/centengine.log', + '/etc/centreon-engine/', '/var/log/centreon-engine/status.dat', + 60, '1', '1', '1', '1', '1', '1', '1', + 4096, '1s', '/var/lib/centreon-engine/rw/centengine.cmd', + '1', '/var/log/centreon-engine/retention.dat', + 60, '1', '0', '0', '1', '1', '1', '1', + '1', '1', '1', NULL, NULL, '0.5', 's', + 's', 's', 0, 15, 15, 5, '0', 30, 180, '0', + '25.0', '50.0', '25.0', '50.0', '0', + 60, 30, 30, 30, '1', '1', '0', NULL, NULL, + '0', NULL, 'euro', 30, '~!$%^&*\"|\'<>?,()=', + '`~$^&\"|\'<>', '0', '0', 'admin@localhost', + 'admin@localhost', 'Centreon Engine config file for a polling instance', + '1', '-1', 2, '1', '1', 15, 15, NULL, + '0', 15, '/var/log/centreon-engine/centengine.debug', + 0, '0', '1', 1000000000, 'centengine.cfg', + '1', '0', '', 'log_v2_enabled' + ); diff --git a/gorgone/tests/robot/config/push_db_1_poller_delete.sql b/gorgone/tests/robot/config/push_db_1_poller_delete.sql new file mode 100644 index 00000000000..69f3b03943a --- /dev/null +++ b/gorgone/tests/robot/config/push_db_1_poller_delete.sql @@ -0,0 +1,2 @@ +delete from cfg_nagios; +delete from nagios_server; \ No newline at end of file diff --git a/gorgone/tests/robot/config/push_poller_config.yaml b/gorgone/tests/robot/config/push_poller_config.yaml new file mode 100644 index 00000000000..a3487f22dbb --- /dev/null +++ b/gorgone/tests/robot/config/push_poller_config.yaml @@ -0,0 +1,21 @@ +name: gorgoned-pushpoller +description: Configuration for poller pushpoller +gorgone: + gorgonecore: + internal_com_type: ipc + internal_com_path: /etc/centreon-gorgone/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/routing.ipc + gorgone_db_type: SQLite + gorgone_db_name: dbname=/etc/centreon-gorgone/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/history.sdb + id: 2 + external_com_type: tcp + external_com_path: "*:5556" + authorized_clients: + - key:@KEYTHUMBPRINT@ + + privkey: "/var/lib/centreon-gorgone/.keys/rsakey.priv.pem" + pubkey: "/var/lib/centreon-gorgone/.keys/rsakey.pub.pem" + + modules: + - name: action + package: gorgone::modules::core::action::hooks + enable: true diff --git a/gorgone/tests/robot/resources/import.resource b/gorgone/tests/robot/resources/import.resource index 461a09f32c8..b072755cce0 100644 --- a/gorgone/tests/robot/resources/import.resource +++ b/gorgone/tests/robot/resources/import.resource @@ -4,3 +4,4 @@ Library Examples Library OperatingSystem Library String Resource resources.resource +Library DatabaseLibrary diff --git a/gorgone/tests/robot/resources/resources.resource b/gorgone/tests/robot/resources/resources.resource index 3e7e6ab6953..95102feb845 100644 --- a/gorgone/tests/robot/resources/resources.resource +++ b/gorgone/tests/robot/resources/resources.resource @@ -1,27 +1,128 @@ *** Settings *** -Documentation Centreon Gorgone for Robot Framework -Library Process +Documentation Centreon Gorgone library for Robot Framework + +Library Process +Library RequestsLibrary *** Variables *** -${PERCENT} % +${ROOT_CONFIG} ${CURDIR}${/}..${/}config${/} +${pull_central_config} ${ROOT_CONFIG}pull_central_config.yaml +${pull_poller_config} ${ROOT_CONFIG}pull_poller_config.yaml +${push_central_config} ${ROOT_CONFIG}push_central_config.yaml +${push_poller_config} ${ROOT_CONFIG}push_poller_config.yaml +${DBHOST} 127.0.0.1 +${DBPORT} 3306 +${DBNAME} centreon_gorgone_test +${DBUSER} centreon +${DBPASSWORD} password *** Keywords *** Start Gorgone - [Arguments] ${CONFIG_FILE} ${LOG_FILE} ${SEVERITY} ${ALIAS} + [Arguments] ${CONFIG_FILE} ${SEVERITY} ${ALIAS} ${process} Start Process ... /usr/bin/perl ... /usr/bin/gorgoned ... --config ... ${CONFIG_FILE} ... --logfile - ... ${LOG_FILE} + ... /var/log/centreon-gorgone/${ALIAS}/gorgoned.log ... --severity ... ${SEVERITY} ... alias=${ALIAS} -Stop Gorgone - [Arguments] ${process_alias} - ${result} Terminate Process ${process_alias} - Should Be True - ... ${result.rc} == -15 or ${result.rc} == 0 - ... Engine badly stopped alias = ${process_alias} - code returned ${result.rc}. \ No newline at end of file +Stop Gorgone And Remove Gorgone Config + [Documentation] This keyword stops the gorgone process and removes the configuration in the database. Configuration files are not modified as we want them in case something failed to analyse the problem. + [Arguments] @{process_alias} ${sql_file}= + Gorgone Execute Sql ${sql_file} + # remove configuration in db if needed. + + FOR ${process} IN @{process_alias} + ${result} Terminate Process ${process} + BuiltIn.Run Keyword And Continue On Failure Should Be True ${result.rc} == -15 or ${result.rc} == 0 Engine badly stopped alias = ${process_alias} - code returned ${result.rc}. + END + +Gorgone Execute Sql + [Arguments] ${sql_file} + ${length} Get Length ${sql_file} + IF ${length} > 0 + Connect To Database pymysql ${DBNAME} ${DBUSER} ${DBPASSWORD} ${DBHOST} ${DBPORT} + Log To Console Executing sql file ${sql_file} + Execute SQL Script ${sql_file} + END + +Setup Gorgone Config + [Arguments] ${gorgone_name} @{file_list} ${sql_file}= + Gorgone Execute Sql ${sql_file} + Create Directory /var/log/centreon-gorgone/${gorgone_name}/ + Copy File ${CURDIR}${/}..${/}config${/}includer.yaml /etc/centreon-gorgone/${gorgone_name}/includer.yaml + + FOR ${file} IN @{file_list} + Copy File ${file} /etc/centreon-gorgone/${gorgone_name}/config.d/ + END + ${key_thumbprint} Run perl /usr/local/bin/gorgone_key_thumbprint.pl --key-path=/var/lib/centreon-gorgone/.keys/rsakey.priv.pem | cut -d: -f4 + + ${result} Run sed -i -e 's/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/${gorgone_name}/g' /etc/centreon-gorgone/${gorgone_name}/includer.yaml + + ${CMD} Catenate + ... sed -i -e 's/@KEYTHUMBPRINT@/${key_thumbprint}/g' + ... -e 's/@DBNAME@/${DBNAME}/g' + ... -e 's/@DBHOST@/${DBHOST}/g' + ... -e 's/@DBPASSWORD@/${DBPASSWORD}/g' + ... -e 's/@DBUSER@/${DBUSER}/g' + ... -e 's/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/${gorgone_name}/g' + ... /etc/centreon-gorgone/${gorgone_name}/config.d/*.yaml + + ${result2} Run ${CMD} + +Check Poller Is Connected + [Arguments] ${port}= ${expected_nb}= + Log To Console checking TCP connection is established... + FOR ${i} IN RANGE 40 + Sleep 4 + ${nb_socket_connexion} Run ss -tnp | grep ':${port}' | grep ESTAB | wc -l + IF ${expected_nb} == ${nb_socket_connexion} + BREAK + END + END + Log To Console TCP connection establishing after ${i} attempt + Should Be True ${i} < 39 Gorgone did not establish tcp connection in 160 seconds. + Log To Console TCP connection established after ${i} attempt (4 seconds each) + +Check Poller Communicate + [Documentation] Ask the central Gorgone rest api if it have communicated with the poller using a given ID. + [Arguments] ${poller_id} + ${response} Set Variable ${EMPTY} + Log To Console checking Gorgone see poller in rest api response... + FOR ${i} IN RANGE 10 + Sleep 5 + ${response}= GET http://127.0.0.1:8085/api/internal/constatus + Log ${response.json()} + IF ${response.json()}[data][${poller_id}][ping_failed] > 0 or ${response.json()}[data][${poller_id}][ping_ok] > 0 + BREAK + END + END + Log To Console json response : ${response.json()} + Should Be True ${i} < 9 timeout waiting for poller status in gorgone rest api (/api/internal/constatus) : ${response.json()} + Should Be True 0 == ${response.json()}[data][${poller_id}][ping_failed] there was failed ping between the central and the poller ${poller_id} + Should Be True 0 < ${response.json()}[data][${poller_id}][ping_ok] there was no successful ping between the central and the poller ${poller_id} + +Setup Two Gorgone Instances + [Arguments] ${communication_mode}=push_zmq ${central_name}=gorgone_central ${poller_name}=gorgone_poller_2 + + ${result} Run perl /usr/local/bin/gorgone_key_generation.pl + # generate key if there is none. + # gorgone can generate it's own key, but as we need the thumbprint in the configuration we need to generate them before launching gorgone. + # this script only create key if the files don't exists, and silently finish if the files already exists. + IF '${communication_mode}' == 'push_zmq' + Setup Gorgone Config ${central_name} ${push_central_config} sql_file=${ROOT_CONFIG}push_db_1_poller.sql + Setup Gorgone Config ${poller_name} ${push_poller_config} + + Start Gorgone /etc/centreon-gorgone/gorgone_central/includer.yaml debug ${central_name} + Start Gorgone /etc/centreon-gorgone/gorgone_poller_2/includer.yaml debug ${poller_name} + + Check Poller Is Connected port=5556 expected_nb=2 + Check Poller Communicate 2 + ELSE + Fail pull and pullwss mode are not yet implemented. + END + diff --git a/gorgone/tests/robot/start_stop/start_stop.robot b/gorgone/tests/robot/start_stop/start_stop.robot deleted file mode 100644 index 66b0468652a..00000000000 --- a/gorgone/tests/robot/start_stop/start_stop.robot +++ /dev/null @@ -1,15 +0,0 @@ -*** Settings *** -Documentation Start and stop gorgone - -Resource ${CURDIR}${/}..${/}resources${/}import.resource - -Test Timeout 120s - -*** Test Cases *** -Start and stop gorgone - FOR ${i} IN RANGE 5 - Start Gorgone /etc/centreon-gorgone/config.yaml /var/log/centreon-gorgone/gorgoned.log info gorgone${i} - Sleep 5s - Stop Gorgone gorgone${i} - sleep 2s - END \ No newline at end of file diff --git a/gorgone/tests/robot/tests/core/push.robot b/gorgone/tests/robot/tests/core/push.robot new file mode 100644 index 00000000000..1a0032b1b16 --- /dev/null +++ b/gorgone/tests/robot/tests/core/push.robot @@ -0,0 +1,16 @@ +*** Settings *** +Documentation Start and stop gorgone + +Resource ${CURDIR}${/}..${/}..${/}resources${/}import.resource +Test Timeout 220s + +*** Variables *** +@{process_list} gorgone_central gorgone_poller_2 + +*** Test Cases *** +test Evan + [Teardown] Stop Gorgone And Remove Gorgone Config @{process_list} sql_file=${ROOT_CONFIG}push_db_1_poller_delete.sql + + Log To Console \nStarting the gorgone setup + Setup Two Gorgone Instances push_zmq + Log To Console End of tests. diff --git a/gorgone/tests/robot/tests/start_stop/config.yaml b/gorgone/tests/robot/tests/start_stop/config.yaml new file mode 100644 index 00000000000..c35b99ed705 --- /dev/null +++ b/gorgone/tests/robot/tests/start_stop/config.yaml @@ -0,0 +1,34 @@ +gorgone: + gorgonecore: + internal_com_type: ipc + internal_com_path: /etc/centreon-gorgone/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/routing.ipc + external_com_type: tcp + + gorgone_db_type: SQLite + gorgone_db_name: dbname=/etc/centreon-gorgone/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/history.sdb + id: 1 + privkey: "/var/lib/centreon-gorgone/.keys/rsakey.priv.pem" + pubkey: "/var/lib/centreon-gorgone/.keys/rsakey.pub.pem" + modules: + - name: httpserver + package: "gorgone::modules::core::httpserver::hooks" + enable: true + address: "0.0.0.0" + port: "8085" + ssl: false + auth: + enabled: false + allowed_hosts: + enabled: true + subnets: + - 127.0.0.1/32 +centreon: + database: + db_configuration: + dsn: "mysql:host=@DBHOST@:port=3306;dbname=@DBNAME@" + username: "@DBUSER@" + password: "@DBPASSWORD@" + db_realtime: + dsn: "mysql:host=@DBHOST@:port=3306;dbname=centreon_storage" + username: "@DBUSER@" + password: "@DBPASSWORD@" diff --git a/gorgone/tests/robot/tests/start_stop/start_stop.robot b/gorgone/tests/robot/tests/start_stop/start_stop.robot new file mode 100644 index 00000000000..08ddb9b6f04 --- /dev/null +++ b/gorgone/tests/robot/tests/start_stop/start_stop.robot @@ -0,0 +1,19 @@ +*** Settings *** +Documentation Start and stop gorgone + +Resource ${CURDIR}${/}..${/}..${/}resources${/}import.resource +Test Timeout 120s + +*** Test Cases *** +Start and stop gorgone + # fichier de conf : pull_central + autodiscovery + # start gorgone 2 + FOR ${i} IN RANGE 5 + Setup Gorgone Config gorgone_start_stop${i} ${CURDIR}${/}config.yaml + Log To Console Starting Gorgone... + Start Gorgone /etc/centreon-gorgone/gorgone_start_stop${i}/includer.yaml debug gorgone_start_stop${i} + Sleep 5s + Log To Console Stopping Gorgone... + Stop Gorgone And Remove Gorgone Config gorgone_start_stop${i} + sleep 2s + END \ No newline at end of file From 8ffae1f691b0690f8080ab265d81cdbc4129380a Mon Sep 17 00:00:00 2001 From: Evan-Adam <152897682+Evan-Adam@users.noreply.github.com> Date: Fri, 24 May 2024 16:57:18 +0200 Subject: [PATCH 830/948] ci(gorgone-tests) test zmq standard configuration on debian 11 and alma 8 (#3907) Co-authored-by: Sophie Depassio <sdepassio@centreon.com> Co-authored-by: May <110405507+mushroomempires@users.noreply.github.com> Co-authored-by: omercier <32134301+omercier@users.noreply.github.com> Refs:MON-38012 --- .../docker/Dockerfile.gorgone-testing-alma8 | 4 +- .../Dockerfile.gorgone-testing-bullseye | 4 +- .github/workflows/gorgone.yml | 22 +++- gorgone/contrib/gorgone_key_generation.pl | 41 ++++++ gorgone/packaging/centreon-gorgone.yaml | 7 + gorgone/tests/robot/config/includer.yaml | 3 + .../robot/config/push_central_config.yaml | 43 ++++++ .../tests/robot/config/push_db_1_poller.sql | 68 ++++++++++ .../robot/config/push_db_1_poller_delete.sql | 2 + .../robot/config/push_poller_config.yaml | 21 +++ gorgone/tests/robot/resources/import.resource | 1 + .../tests/robot/resources/resources.resource | 123 ++++++++++++++++-- .../tests/robot/start_stop/start_stop.robot | 15 --- gorgone/tests/robot/tests/core/push.robot | 16 +++ .../tests/robot/tests/start_stop/config.yaml | 34 +++++ .../robot/tests/start_stop/start_stop.robot | 19 +++ 16 files changed, 389 insertions(+), 34 deletions(-) create mode 100644 gorgone/contrib/gorgone_key_generation.pl create mode 100644 gorgone/tests/robot/config/includer.yaml create mode 100644 gorgone/tests/robot/config/push_central_config.yaml create mode 100644 gorgone/tests/robot/config/push_db_1_poller.sql create mode 100644 gorgone/tests/robot/config/push_db_1_poller_delete.sql create mode 100644 gorgone/tests/robot/config/push_poller_config.yaml delete mode 100644 gorgone/tests/robot/start_stop/start_stop.robot create mode 100644 gorgone/tests/robot/tests/core/push.robot create mode 100644 gorgone/tests/robot/tests/start_stop/config.yaml create mode 100644 gorgone/tests/robot/tests/start_stop/start_stop.robot diff --git a/.github/docker/Dockerfile.gorgone-testing-alma8 b/.github/docker/Dockerfile.gorgone-testing-alma8 index 7964dde694a..618bb6cd582 100644 --- a/.github/docker/Dockerfile.gorgone-testing-alma8 +++ b/.github/docker/Dockerfile.gorgone-testing-alma8 @@ -2,13 +2,13 @@ FROM almalinux:8 RUN bash -e <<EOF -dnf install -y dnf-plugins-core zstd curl mariadb +dnf install -y dnf-plugins-core zstd curl mariadb iproute dnf install -y https://rpms.remirepo.net/enterprise/remi-release-8.rpm dnf config-manager --set-enabled 'powertools' dnf -y config-manager --add-repo https://packages.centreon.com/rpm-standard/23.10/el8/centreon-23.10.repo dnf -y clean all --enablerepo=* dnf install -y python3.11 python3.11-pip -pip3.11 install robotframework robotframework-examples +pip3.11 install robotframework robotframework-examples robotframework-databaselibrary pymysql robotframework-requests dnf clean all diff --git a/.github/docker/Dockerfile.gorgone-testing-bullseye b/.github/docker/Dockerfile.gorgone-testing-bullseye index 15004724967..5799ac451bd 100644 --- a/.github/docker/Dockerfile.gorgone-testing-bullseye +++ b/.github/docker/Dockerfile.gorgone-testing-bullseye @@ -6,7 +6,7 @@ ENV DEBIAN_FRONTEND noninteractive RUN bash -e <<EOF apt-get update -apt-get install -y locales libcurl4-openssl-dev curl wget zstd jq lsb-release mariadb-client +apt-get install -y locales libcurl4-openssl-dev curl wget zstd jq lsb-release mariadb-client iproute2 rm -rf /var/lib/apt/lists/* localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 apt-get clean @@ -19,7 +19,7 @@ RUN bash -e <<EOF apt-get update # Install Robotframework apt-get install -y python3 python3-dev python3-pip ca-certificates apt-transport-https software-properties-common wget gnupg2 -pip3 install robotframework robotframework-examples +pip3 install robotframework robotframework-examples robotframework-databaselibrary pymysql robotframework-requests echo "deb https://packages.centreon.com/apt-standard-23.10-stable/ $(lsb_release -sc) main" | tee /etc/apt/sources.list.d/centreon.list echo "deb https://packages.centreon.com/apt-plugins-stable/ $(lsb_release -sc) main" | tee /etc/apt/sources.list.d/centreon-plugins.list diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 5aecc0ad643..82a64588afa 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -155,7 +155,7 @@ jobs: ports: - 3306 env: - MYSQL_USER: user + MYSQL_USER: centreon MYSQL_PASSWORD: password MYSQL_ROOT_PASSWORD: password @@ -170,7 +170,7 @@ jobs: key: ${{ github.sha }}-${{ github.run_id }}-${{ matrix.package_extension }}-${{ matrix.distrib }} fail-on-cache-miss: true - - name: Install gorgogne + - name: Install gorgogne from just built package shell: bash run: | if [[ "${{ matrix.package_extension }}" == "deb" ]]; then @@ -181,15 +181,29 @@ jobs: # this is not the case for debian, and for now I don't know why it was made any different between the 2 Os. fi - - name: Create database + - name: Create databases run: | mysql -h mariadb -u root -ppassword -e "CREATE DATABASE \`centreon\`" mysql -h mariadb -u root -ppassword -e "CREATE DATABASE \`centreon-storage\`" + mysql -h mariadb -u root -ppassword -e "GRANT ALL PRIVILEGES ON centreon.* TO 'centreon'@'%'" + mysql -h mariadb -u root -ppassword -e "GRANT ALL PRIVILEGES ON \`centreon-storage\`.* TO 'centreon'@'%'" mysql -h mariadb -u root -ppassword 'centreon' < centreon/www/install/createTables.sql mysql -h mariadb -u root -ppassword 'centreon-storage' < centreon/www/install/createTablesCentstorage.sql - name: Run tests - run: robot centreon-gorgone/tests + run: robot -v 'DBHOST:mariadb' -v 'DBNAME:centreon' -v 'DBUSER:centreon' centreon-gorgone/tests + + + - name: Upload gorgone and robot debug artifacts + if: failure() + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: gorgone-debug-${{ matrix.distrib }} + path: | + log.html + /var/log/centreon-gorgone + /etc/centreon-gorgone + retention-days: 1 deliver-sources: runs-on: [self-hosted, common] diff --git a/gorgone/contrib/gorgone_key_generation.pl b/gorgone/contrib/gorgone_key_generation.pl new file mode 100644 index 00000000000..a7ec62bbad3 --- /dev/null +++ b/gorgone/contrib/gorgone_key_generation.pl @@ -0,0 +1,41 @@ +#!/usr/bin/perl +use strict; +use warnings FATAL => 'all'; +use Try::Tiny; +use Crypt::PK::RSA; +use File::Basename qw( fileparse ); + +# generate key if there is none. +# Gorgone can generate it's own key, but as we need the thumbprint in the configuration we need to generate them before launching gorgone. +# this script only create key if the files don't exists, and silently finish if the files already exists. + +my ($privkey, $pubkey); + +my $priv_dest = '/var/lib/centreon-gorgone/.keys/rsakey.priv.pem'; +my $pub_dest = '/var/lib/centreon-gorgone/.keys/rsakey.pub.pem'; +$ARGV[0] and $priv_dest = $ARGV[0]; +$ARGV[1] and $pub_dest = $ARGV[1]; +if (-f $priv_dest or -f $pub_dest){ + print("files already exist, no overriding is done.\n"); + exit 0; +} +try { + my $pkrsa = Crypt::PK::RSA->new(); + $pkrsa->generate_key(256, 65537); + $pubkey = $pkrsa->export_key_pem('public_x509'); + $privkey = $pkrsa->export_key_pem('private'); +} catch { + die("Cannot generate server keys: $_\n"); +}; + +my ( $priv_key_name, $priv_folder_name ) = fileparse $priv_dest; +`mkdir -p $priv_folder_name`; +open(my $priv_fh, '>', $priv_dest) or die("failed opening $priv_dest : $!"); +print $priv_fh $privkey; +print "private key saved to file.\n"; + +my ( $pub_key_name, $pub_folder_name ) = fileparse $pub_dest; +`mkdir -p $pub_folder_name`; +open(my $pub_fh, '>', $pub_dest) or die("failed opening $pub_dest : $!"); +print $pub_fh $pubkey; +print "pub key saved to file.\n"; diff --git a/gorgone/packaging/centreon-gorgone.yaml b/gorgone/packaging/centreon-gorgone.yaml index fc94711c0a1..c24f1d8ffec 100644 --- a/gorgone/packaging/centreon-gorgone.yaml +++ b/gorgone/packaging/centreon-gorgone.yaml @@ -143,6 +143,11 @@ contents: file_info: mode: 0750 + - src: "../contrib/gorgone_key_generation.pl" + dst: "/usr/local/bin/" + file_info: + mode: 0750 + scripts: preinstall: ./scripts/centreon-gorgone-preinstall.sh postinstall: ./scripts/centreon-gorgone-postinstall.sh @@ -183,6 +188,7 @@ overrides: - perl(DateTime) - perl(Try::Tiny) - tar + - perl(JSON) # gorgone_key_thumbprint.pl needs the json module, even when json::xs is already installed deb: depends: # those dependencies are taken from centreon-gorgone/packaging/debian/control - centreon-common @@ -213,6 +219,7 @@ overrides: - libev-perl - libzmq-ffi-perl - libclone-choose-perl + - libjson-perl # gorgone_key_thumbprint.pl needs the json module, even when json::xs is already installed rpm: summary: Centreon gorgone daemon diff --git a/gorgone/tests/robot/config/includer.yaml b/gorgone/tests/robot/config/includer.yaml new file mode 100644 index 00000000000..f1a5cad34bd --- /dev/null +++ b/gorgone/tests/robot/config/includer.yaml @@ -0,0 +1,3 @@ +name: config.yaml +description: Configuration of centreon-gorgone. Use config.d directory to change configuration +configuration: !include /etc/centreon-gorgone/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/config.d/*.yaml diff --git a/gorgone/tests/robot/config/push_central_config.yaml b/gorgone/tests/robot/config/push_central_config.yaml new file mode 100644 index 00000000000..d866027ade3 --- /dev/null +++ b/gorgone/tests/robot/config/push_central_config.yaml @@ -0,0 +1,43 @@ +gorgone: + gorgonecore: + internal_com_type: ipc + internal_com_path: /etc/centreon-gorgone/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/routing.ipc + external_com_type: tcp + + gorgone_db_type: SQLite + gorgone_db_name: dbname=/etc/centreon-gorgone/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/history.sdb + id: 1 + privkey: "/var/lib/centreon-gorgone/.keys/rsakey.priv.pem" + pubkey: "/var/lib/centreon-gorgone/.keys/rsakey.pub.pem" + modules: + - name: proxy + package: "gorgone::modules::core::proxy::hooks" + enable: true + + - name: nodes + package: "gorgone::modules::centreon::nodes::hooks" + enable: true + + - name: httpserver + package: "gorgone::modules::core::httpserver::hooks" + enable: true + address: "0.0.0.0" + port: "8085" + ssl: false + auth: + enabled: false + allowed_hosts: + enabled: true + subnets: + - 127.0.0.1/32 + +centreon: + database: + db_configuration: + dsn: "mysql:host=@DBHOST@:port=3306;dbname=@DBNAME@" + username: "@DBUSER@" + password: "@DBPASSWORD@" + db_realtime: + dsn: "mysql:host=@DBHOST@:port=3306;dbname=centreon_storage" + username: "@DBUSER@" + password: "@DBPASSWORD@" diff --git a/gorgone/tests/robot/config/push_db_1_poller.sql b/gorgone/tests/robot/config/push_db_1_poller.sql new file mode 100644 index 00000000000..e8e3edf6991 --- /dev/null +++ b/gorgone/tests/robot/config/push_db_1_poller.sql @@ -0,0 +1,68 @@ +INSERT IGNORE INTO `nagios_server` + VALUES + ( + 1, 'Central', '1', 1, 1711560733, '127.0.0.1', + '1', '0', 'service centengine start', + 'service centengine stop', 'service centengine restart', + 'service centengine reload', '/usr/sbin/centengine', + '/usr/sbin/centenginestats', '/var/log/centreon-engine/service-perfdata', + 'service cbd reload', '/etc/centreon-broker', + '/usr/share/centreon/lib/centreon-broker', + '/usr/lib64/centreon-connector', + 22, '1', 5556, 'centreontrapd', '/etc/snmp/centreon_traps/', + NULL, NULL, NULL, NULL, '1', '0' + ), + ( + 2, 'pushpoller', '0', 0, NULL, '127.0.0.1', + '1', '0', 'service centengine start', + 'service centengine stop', 'service centengine restart', + 'service centengine reload', '/usr/sbin/centengine', + '/usr/sbin/centenginestats', '/var/log/centreon-engine/service-perfdata', + 'service cbd reload', '/etc/centreon-broker', + '/usr/share/centreon/lib/centreon-broker', + '/usr/lib64/centreon-connector', + 22, '1', 5556, 'centreontrapd', '/etc/snmp/centreon_traps/', + NULL, NULL, '/var/log/centreon-broker/', + NULL, '1', '0' + ); +INSERT IGNORE INTO `cfg_nagios` + VALUES + ( + 1, 'Centreon Engine Central', NULL, + '/var/log/centreon-engine/centengine.log', + '/etc/centreon-engine', '/var/log/centreon-engine/status.dat', + 60, '1', '1', '1', '1', '1', '1', '1', + 4096, '1s', '/var/lib/centreon-engine/rw/centengine.cmd', + '1', '/var/log/centreon-engine/retention.dat', + 60, '1', '1', '0', '1', '1', '1', '1', + NULL, '1', '1', NULL, NULL, NULL, 's', + 's', 's', 0, 15, 15, 5, '0', NULL, NULL, + '0', '25.0', '50.0', '25.0', '50.0', + '0', 60, 12, 30, 30, '1', '1', '0', NULL, + NULL, '0', NULL, 'euro', 30, '~!$%^&*\"|\'<>?,()=', + '`~$^&\"|\'<>', '0', '0', 'admin@localhost', + 'admin@localhost', 'Centreon Engine configuration file for a central instance', + '1', '-1', 1, '1', '1', 15, 15, NULL, + '0', 15, '/var/log/centreon-engine/centengine.debug', + 0, '0', '1', 1000000000, 'centengine.cfg', + '1', '0', '', 'log_v2_enabled' + ), + ( + 15, 'pushpoller', NULL, '/var/log/centreon-engine/centengine.log', + '/etc/centreon-engine/', '/var/log/centreon-engine/status.dat', + 60, '1', '1', '1', '1', '1', '1', '1', + 4096, '1s', '/var/lib/centreon-engine/rw/centengine.cmd', + '1', '/var/log/centreon-engine/retention.dat', + 60, '1', '0', '0', '1', '1', '1', '1', + '1', '1', '1', NULL, NULL, '0.5', 's', + 's', 's', 0, 15, 15, 5, '0', 30, 180, '0', + '25.0', '50.0', '25.0', '50.0', '0', + 60, 30, 30, 30, '1', '1', '0', NULL, NULL, + '0', NULL, 'euro', 30, '~!$%^&*\"|\'<>?,()=', + '`~$^&\"|\'<>', '0', '0', 'admin@localhost', + 'admin@localhost', 'Centreon Engine config file for a polling instance', + '1', '-1', 2, '1', '1', 15, 15, NULL, + '0', 15, '/var/log/centreon-engine/centengine.debug', + 0, '0', '1', 1000000000, 'centengine.cfg', + '1', '0', '', 'log_v2_enabled' + ); diff --git a/gorgone/tests/robot/config/push_db_1_poller_delete.sql b/gorgone/tests/robot/config/push_db_1_poller_delete.sql new file mode 100644 index 00000000000..69f3b03943a --- /dev/null +++ b/gorgone/tests/robot/config/push_db_1_poller_delete.sql @@ -0,0 +1,2 @@ +delete from cfg_nagios; +delete from nagios_server; \ No newline at end of file diff --git a/gorgone/tests/robot/config/push_poller_config.yaml b/gorgone/tests/robot/config/push_poller_config.yaml new file mode 100644 index 00000000000..a3487f22dbb --- /dev/null +++ b/gorgone/tests/robot/config/push_poller_config.yaml @@ -0,0 +1,21 @@ +name: gorgoned-pushpoller +description: Configuration for poller pushpoller +gorgone: + gorgonecore: + internal_com_type: ipc + internal_com_path: /etc/centreon-gorgone/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/routing.ipc + gorgone_db_type: SQLite + gorgone_db_name: dbname=/etc/centreon-gorgone/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/history.sdb + id: 2 + external_com_type: tcp + external_com_path: "*:5556" + authorized_clients: + - key:@KEYTHUMBPRINT@ + + privkey: "/var/lib/centreon-gorgone/.keys/rsakey.priv.pem" + pubkey: "/var/lib/centreon-gorgone/.keys/rsakey.pub.pem" + + modules: + - name: action + package: gorgone::modules::core::action::hooks + enable: true diff --git a/gorgone/tests/robot/resources/import.resource b/gorgone/tests/robot/resources/import.resource index 461a09f32c8..b072755cce0 100644 --- a/gorgone/tests/robot/resources/import.resource +++ b/gorgone/tests/robot/resources/import.resource @@ -4,3 +4,4 @@ Library Examples Library OperatingSystem Library String Resource resources.resource +Library DatabaseLibrary diff --git a/gorgone/tests/robot/resources/resources.resource b/gorgone/tests/robot/resources/resources.resource index 3e7e6ab6953..95102feb845 100644 --- a/gorgone/tests/robot/resources/resources.resource +++ b/gorgone/tests/robot/resources/resources.resource @@ -1,27 +1,128 @@ *** Settings *** -Documentation Centreon Gorgone for Robot Framework -Library Process +Documentation Centreon Gorgone library for Robot Framework + +Library Process +Library RequestsLibrary *** Variables *** -${PERCENT} % +${ROOT_CONFIG} ${CURDIR}${/}..${/}config${/} +${pull_central_config} ${ROOT_CONFIG}pull_central_config.yaml +${pull_poller_config} ${ROOT_CONFIG}pull_poller_config.yaml +${push_central_config} ${ROOT_CONFIG}push_central_config.yaml +${push_poller_config} ${ROOT_CONFIG}push_poller_config.yaml +${DBHOST} 127.0.0.1 +${DBPORT} 3306 +${DBNAME} centreon_gorgone_test +${DBUSER} centreon +${DBPASSWORD} password *** Keywords *** Start Gorgone - [Arguments] ${CONFIG_FILE} ${LOG_FILE} ${SEVERITY} ${ALIAS} + [Arguments] ${CONFIG_FILE} ${SEVERITY} ${ALIAS} ${process} Start Process ... /usr/bin/perl ... /usr/bin/gorgoned ... --config ... ${CONFIG_FILE} ... --logfile - ... ${LOG_FILE} + ... /var/log/centreon-gorgone/${ALIAS}/gorgoned.log ... --severity ... ${SEVERITY} ... alias=${ALIAS} -Stop Gorgone - [Arguments] ${process_alias} - ${result} Terminate Process ${process_alias} - Should Be True - ... ${result.rc} == -15 or ${result.rc} == 0 - ... Engine badly stopped alias = ${process_alias} - code returned ${result.rc}. \ No newline at end of file +Stop Gorgone And Remove Gorgone Config + [Documentation] This keyword stops the gorgone process and removes the configuration in the database. Configuration files are not modified as we want them in case something failed to analyse the problem. + [Arguments] @{process_alias} ${sql_file}= + Gorgone Execute Sql ${sql_file} + # remove configuration in db if needed. + + FOR ${process} IN @{process_alias} + ${result} Terminate Process ${process} + BuiltIn.Run Keyword And Continue On Failure Should Be True ${result.rc} == -15 or ${result.rc} == 0 Engine badly stopped alias = ${process_alias} - code returned ${result.rc}. + END + +Gorgone Execute Sql + [Arguments] ${sql_file} + ${length} Get Length ${sql_file} + IF ${length} > 0 + Connect To Database pymysql ${DBNAME} ${DBUSER} ${DBPASSWORD} ${DBHOST} ${DBPORT} + Log To Console Executing sql file ${sql_file} + Execute SQL Script ${sql_file} + END + +Setup Gorgone Config + [Arguments] ${gorgone_name} @{file_list} ${sql_file}= + Gorgone Execute Sql ${sql_file} + Create Directory /var/log/centreon-gorgone/${gorgone_name}/ + Copy File ${CURDIR}${/}..${/}config${/}includer.yaml /etc/centreon-gorgone/${gorgone_name}/includer.yaml + + FOR ${file} IN @{file_list} + Copy File ${file} /etc/centreon-gorgone/${gorgone_name}/config.d/ + END + ${key_thumbprint} Run perl /usr/local/bin/gorgone_key_thumbprint.pl --key-path=/var/lib/centreon-gorgone/.keys/rsakey.priv.pem | cut -d: -f4 + + ${result} Run sed -i -e 's/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/${gorgone_name}/g' /etc/centreon-gorgone/${gorgone_name}/includer.yaml + + ${CMD} Catenate + ... sed -i -e 's/@KEYTHUMBPRINT@/${key_thumbprint}/g' + ... -e 's/@DBNAME@/${DBNAME}/g' + ... -e 's/@DBHOST@/${DBHOST}/g' + ... -e 's/@DBPASSWORD@/${DBPASSWORD}/g' + ... -e 's/@DBUSER@/${DBUSER}/g' + ... -e 's/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/${gorgone_name}/g' + ... /etc/centreon-gorgone/${gorgone_name}/config.d/*.yaml + + ${result2} Run ${CMD} + +Check Poller Is Connected + [Arguments] ${port}= ${expected_nb}= + Log To Console checking TCP connection is established... + FOR ${i} IN RANGE 40 + Sleep 4 + ${nb_socket_connexion} Run ss -tnp | grep ':${port}' | grep ESTAB | wc -l + IF ${expected_nb} == ${nb_socket_connexion} + BREAK + END + END + Log To Console TCP connection establishing after ${i} attempt + Should Be True ${i} < 39 Gorgone did not establish tcp connection in 160 seconds. + Log To Console TCP connection established after ${i} attempt (4 seconds each) + +Check Poller Communicate + [Documentation] Ask the central Gorgone rest api if it have communicated with the poller using a given ID. + [Arguments] ${poller_id} + ${response} Set Variable ${EMPTY} + Log To Console checking Gorgone see poller in rest api response... + FOR ${i} IN RANGE 10 + Sleep 5 + ${response}= GET http://127.0.0.1:8085/api/internal/constatus + Log ${response.json()} + IF ${response.json()}[data][${poller_id}][ping_failed] > 0 or ${response.json()}[data][${poller_id}][ping_ok] > 0 + BREAK + END + END + Log To Console json response : ${response.json()} + Should Be True ${i} < 9 timeout waiting for poller status in gorgone rest api (/api/internal/constatus) : ${response.json()} + Should Be True 0 == ${response.json()}[data][${poller_id}][ping_failed] there was failed ping between the central and the poller ${poller_id} + Should Be True 0 < ${response.json()}[data][${poller_id}][ping_ok] there was no successful ping between the central and the poller ${poller_id} + +Setup Two Gorgone Instances + [Arguments] ${communication_mode}=push_zmq ${central_name}=gorgone_central ${poller_name}=gorgone_poller_2 + + ${result} Run perl /usr/local/bin/gorgone_key_generation.pl + # generate key if there is none. + # gorgone can generate it's own key, but as we need the thumbprint in the configuration we need to generate them before launching gorgone. + # this script only create key if the files don't exists, and silently finish if the files already exists. + IF '${communication_mode}' == 'push_zmq' + Setup Gorgone Config ${central_name} ${push_central_config} sql_file=${ROOT_CONFIG}push_db_1_poller.sql + Setup Gorgone Config ${poller_name} ${push_poller_config} + + Start Gorgone /etc/centreon-gorgone/gorgone_central/includer.yaml debug ${central_name} + Start Gorgone /etc/centreon-gorgone/gorgone_poller_2/includer.yaml debug ${poller_name} + + Check Poller Is Connected port=5556 expected_nb=2 + Check Poller Communicate 2 + ELSE + Fail pull and pullwss mode are not yet implemented. + END + diff --git a/gorgone/tests/robot/start_stop/start_stop.robot b/gorgone/tests/robot/start_stop/start_stop.robot deleted file mode 100644 index 66b0468652a..00000000000 --- a/gorgone/tests/robot/start_stop/start_stop.robot +++ /dev/null @@ -1,15 +0,0 @@ -*** Settings *** -Documentation Start and stop gorgone - -Resource ${CURDIR}${/}..${/}resources${/}import.resource - -Test Timeout 120s - -*** Test Cases *** -Start and stop gorgone - FOR ${i} IN RANGE 5 - Start Gorgone /etc/centreon-gorgone/config.yaml /var/log/centreon-gorgone/gorgoned.log info gorgone${i} - Sleep 5s - Stop Gorgone gorgone${i} - sleep 2s - END \ No newline at end of file diff --git a/gorgone/tests/robot/tests/core/push.robot b/gorgone/tests/robot/tests/core/push.robot new file mode 100644 index 00000000000..1a0032b1b16 --- /dev/null +++ b/gorgone/tests/robot/tests/core/push.robot @@ -0,0 +1,16 @@ +*** Settings *** +Documentation Start and stop gorgone + +Resource ${CURDIR}${/}..${/}..${/}resources${/}import.resource +Test Timeout 220s + +*** Variables *** +@{process_list} gorgone_central gorgone_poller_2 + +*** Test Cases *** +test Evan + [Teardown] Stop Gorgone And Remove Gorgone Config @{process_list} sql_file=${ROOT_CONFIG}push_db_1_poller_delete.sql + + Log To Console \nStarting the gorgone setup + Setup Two Gorgone Instances push_zmq + Log To Console End of tests. diff --git a/gorgone/tests/robot/tests/start_stop/config.yaml b/gorgone/tests/robot/tests/start_stop/config.yaml new file mode 100644 index 00000000000..c35b99ed705 --- /dev/null +++ b/gorgone/tests/robot/tests/start_stop/config.yaml @@ -0,0 +1,34 @@ +gorgone: + gorgonecore: + internal_com_type: ipc + internal_com_path: /etc/centreon-gorgone/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/routing.ipc + external_com_type: tcp + + gorgone_db_type: SQLite + gorgone_db_name: dbname=/etc/centreon-gorgone/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/history.sdb + id: 1 + privkey: "/var/lib/centreon-gorgone/.keys/rsakey.priv.pem" + pubkey: "/var/lib/centreon-gorgone/.keys/rsakey.pub.pem" + modules: + - name: httpserver + package: "gorgone::modules::core::httpserver::hooks" + enable: true + address: "0.0.0.0" + port: "8085" + ssl: false + auth: + enabled: false + allowed_hosts: + enabled: true + subnets: + - 127.0.0.1/32 +centreon: + database: + db_configuration: + dsn: "mysql:host=@DBHOST@:port=3306;dbname=@DBNAME@" + username: "@DBUSER@" + password: "@DBPASSWORD@" + db_realtime: + dsn: "mysql:host=@DBHOST@:port=3306;dbname=centreon_storage" + username: "@DBUSER@" + password: "@DBPASSWORD@" diff --git a/gorgone/tests/robot/tests/start_stop/start_stop.robot b/gorgone/tests/robot/tests/start_stop/start_stop.robot new file mode 100644 index 00000000000..08ddb9b6f04 --- /dev/null +++ b/gorgone/tests/robot/tests/start_stop/start_stop.robot @@ -0,0 +1,19 @@ +*** Settings *** +Documentation Start and stop gorgone + +Resource ${CURDIR}${/}..${/}..${/}resources${/}import.resource +Test Timeout 120s + +*** Test Cases *** +Start and stop gorgone + # fichier de conf : pull_central + autodiscovery + # start gorgone 2 + FOR ${i} IN RANGE 5 + Setup Gorgone Config gorgone_start_stop${i} ${CURDIR}${/}config.yaml + Log To Console Starting Gorgone... + Start Gorgone /etc/centreon-gorgone/gorgone_start_stop${i}/includer.yaml debug gorgone_start_stop${i} + Sleep 5s + Log To Console Stopping Gorgone... + Stop Gorgone And Remove Gorgone Config gorgone_start_stop${i} + sleep 2s + END \ No newline at end of file From 44e25d6c6921bda5df3c6906c88ab32e45d0b1a6 Mon Sep 17 00:00:00 2001 From: Evan-Adam <152897682+Evan-Adam@users.noreply.github.com> Date: Mon, 27 May 2024 10:14:28 +0200 Subject: [PATCH 831/948] fix(gorgone): Add mbi legacy function to manage host template recursivity (#4130) Refs:MON-37036 --- .../centreon/mbi/libs/centreon/Host.pm | 389 ++++++++++++------ 1 file changed, 264 insertions(+), 125 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Host.pm b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Host.pm index e175076410d..6211e306f28 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Host.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Host.pm @@ -30,75 +30,77 @@ use Data::Dumper; # $centreon: Instance of centreonDB class for connection to Centreon database # $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database sub new { - my $class = shift; - my $self = {}; - $self->{logger} = shift; - $self->{centreon} = shift; - $self->{etlProperties} = undef; - - if (@_) { - $self->{centstorage} = shift; - } - bless $self, $class; - return $self; + my $class = shift; + my $self = {}; + $self->{logger} = shift; + $self->{centreon} = shift; + $self->{etlProperties} = undef; + + if (@_) { + $self->{centstorage} = shift; + } + bless $self, $class; + return $self; } #Set the etl properties as a variable of the class sub setEtlProperties{ - my $self = shift; - $self->{etlProperties} = shift; + my $self = shift; + $self->{etlProperties} = shift; } # returns two references to two hash tables => hosts indexed by id and hosts indexed by name sub getAllHosts { - my $self = shift; - my $centreon = $self->{centreon}; - my $activated = 1; - if (@_) { - $activated = 0; - } - my (%host_ids, %host_names); - - my $query = "SELECT `host_id`, `host_name`". - " FROM `host`". - " WHERE `host_register`='1'"; + my $self = shift; + my $centreon = $self->{centreon}; + my $activated = 1; + if (@_) { + $activated = 0; + } + my (%host_ids, %host_names); + + my $query = "SELECT `host_id`, `host_name`" . + " FROM `host`" . + " WHERE `host_register`='1'"; if ($activated == 1) { $query .= " AND `host_activate` ='1'"; } - my $sth = $centreon->query({ query => $query }); - while (my $row = $sth->fetchrow_hashref()) { - $host_ids{ $row->{host_name} } = $row->{host_id}; - $host_names{ $row->{host_id} } = $row->{host_name}; - } - return (\%host_ids, \%host_names); + my $sth = $centreon->query({ query => $query }); + while (my $row = $sth->fetchrow_hashref()) { + $host_ids{ $row->{host_name} } = $row->{host_id}; + $host_names{ $row->{host_id} } = $row->{host_name}; + } + return (\%host_ids, \%host_names); } # Get all hosts, keys are IDs sub getAllHostsByID { - my $self = shift; - my ($host_ids, $host_names) = $self->getAllHosts(); - return ($host_ids); + my $self = shift; + my ($host_ids, $host_names) = $self->getAllHosts(); + return ($host_ids); } # Get all hosts, keys are names sub getAllHostsByName { - my $self = shift; - my ($host_ids, $host_names) = $self->getAllHosts(); - return ($host_names); + my $self = shift; + my ($host_ids, $host_names) = $self->getAllHosts(); + return ($host_names); } sub loadAllCategories { my $self = shift; - $self->{hc} = {}; + $self->{hc} = {}; $self->{host_hc_relations} = {}; - my $query = "SELECT hc.hc_id as category_id, hc.hc_name as category_name, host_host_id + my $query = "SELECT hc.hc_id as category_id, hc.hc_name as category_name, host_host_id FROM hostcategories hc, hostcategories_relation hr WHERE hc.hc_activate = '1' AND hc.hc_id = hr.hostcategories_hc_id"; - my $sth = $self->{centreon}->query({ query => $query }); - while (my $row = $sth->fetchrow_hashref()) { - $self->{hc}->{ $row->{category_id} } = $row->{category_name} if (!defined($self->{hc}->{ $row->{category_id} })); - $self->{host_hc_relations}->{ $row->{host_host_id} } = [] if (!defined($self->{host_hc_relations}->{ $row->{host_host_id} })); + my $sth = $self->{centreon}->query({ query => $query }); + while (my $row = $sth->fetchrow_hashref()) { + $self->{hc}->{ $row->{category_id} } = $row->{category_name} if (!defined($self->{hc}->{ $row + ->{category_id} })); + $self->{host_hc_relations}->{ $row->{host_host_id} } = [] if (!defined($self->{host_hc_relations}->{ $row + ->{host_host_id} })); push @{$self->{host_hc_relations}->{ $row->{host_host_id} }}, $row->{category_id}; } } @@ -106,15 +108,17 @@ sub loadAllCategories { sub loadAllHosts { my $self = shift; - $self->{hosts} = {}; + $self->{hosts} = {}; $self->{host_htpl_relations} = {}; - my $query = "SELECT h.host_id, h.host_name, host_tpl_id + my $query = "SELECT h.host_id, h.host_name, host_tpl_id FROM host h, host_template_relation htr WHERE h.host_activate = '1' AND h.host_id = htr.host_host_id"; - my $sth = $self->{centreon}->query({ query => $query }); - while (my $row = $sth->fetchrow_hashref()) { - $self->{hosts}->{ $row->{host_id} } = $row->{host_name} if (!defined($self->{hosts}->{ $row->{host_id} })); - $self->{host_htpl_relations}->{ $row->{host_id} } = [] if (!defined($self->{host_htpl_relations}->{ $row->{host_id} })); + my $sth = $self->{centreon}->query({ query => $query }); + while (my $row = $sth->fetchrow_hashref()) { + $self->{hosts}->{ $row->{host_id} } = $row->{host_name} if (!defined($self->{hosts}->{ $row + ->{host_id} })); + $self->{host_htpl_relations}->{ $row->{host_id} } = [] if (!defined($self->{host_htpl_relations}->{ $row + ->{host_id} })); push @{$self->{host_htpl_relations}->{ $row->{host_id} }}, $row->{host_tpl_id}; } } @@ -124,101 +128,236 @@ sub loadAllHosts { # each key of the hash table is a host id # each key is linked to a table containing entries like : "hostgroup_id;hostgroup_name" sub getHostGroups { - my $self = shift; - my $centreon = $self->{"centreon"}; - my $activated = 1; - my $etlProperties = $self->{'etlProperties'}; - if (@_) { - $activated = 0; - } - my %result = (); - - my $query = "SELECT `host_id`, `host_name`, `hg_id`, `hg_name`". - " FROM `host`, `hostgroup_relation`, `hostgroup`". - " WHERE `host_register`='1'". - " AND `hostgroup_hg_id` = `hg_id`". - " AND `host_id`= `host_host_id`"; + my $self = shift; + my $centreon = $self->{"centreon"}; + my $activated = 1; + my $etlProperties = $self->{'etlProperties'}; + if (@_) { + $activated = 0; + } + my %result = (); + + my $query = "SELECT `host_id`, `host_name`, `hg_id`, `hg_name`" . + " FROM `host`, `hostgroup_relation`, `hostgroup`" . + " WHERE `host_register`='1'" . + " AND `hostgroup_hg_id` = `hg_id`" . + " AND `host_id`= `host_host_id`"; if ($activated == 1) { $query .= " AND `host_activate` ='1'"; } if (!defined($etlProperties->{'dimension.all.hostgroups'}) && $etlProperties->{'dimension.hostgroups'} ne '') { - $query .= " AND `hg_id` IN (".$etlProperties->{'dimension.hostgroups'}.")"; - } - my $sth = $centreon->query({ query => $query }); - while (my $row = $sth->fetchrow_hashref()) { - my $new_entry = $row->{"hg_id"}.";".$row->{"hg_name"}; - if (defined($result{$row->{"host_id"}})) { - my $tab_ref = $result{$row->{"host_id"}}; - my @tab = @$tab_ref; - my $exists = 0; - foreach(@tab) { - if ($_ eq $new_entry) { - $exists = 1; - last; - } - } - if (!$exists) { - push @tab, $new_entry; - } - $result{$row->{"host_id"}} = \@tab; - }else { - my @tab = ($new_entry); - $result{$row->{"host_id"}} = \@tab; - } - } - $sth->finish(); - return (\%result); + $query .= " AND `hg_id` IN (" . $etlProperties->{'dimension.hostgroups'} . ")"; + } + my $sth = $centreon->query({ query => $query }); + while (my $row = $sth->fetchrow_hashref()) { + my $new_entry = $row->{"hg_id"} . ";" . $row->{"hg_name"}; + if (defined($result{$row->{"host_id"}})) { + my $tab_ref = $result{$row->{"host_id"}}; + my @tab = @$tab_ref; + my $exists = 0; + foreach (@tab) { + if ($_ eq $new_entry) { + $exists = 1; + last; + } + } + if (!$exists) { + push @tab, $new_entry; + } + $result{$row->{"host_id"}} = \@tab; + } else { + my @tab = ($new_entry); + $result{$row->{"host_id"}} = \@tab; + } + } + $sth->finish(); + return (\%result); } #Fill a class Hash table that contains the relation between host_id and table[hc_id,hc_name] sub getHostCategoriesWithTemplate { - my $self = shift; - - my @hostCategoriesAllowed = split(/,/, $self->{etlProperties}->{'dimension.hostcategories'}); - - my %loop = (); - my $hcResult = {}; - foreach my $host_id (keys %{$self->{hosts}}) { - my $stack = [$host_id]; - my $hcAdd = {}; - my $hc = []; - foreach (my $id = shift(@$stack)) { - next if (defined($loop{$id})); - $loop{$id} = 1; - - if (defined($self->{host_hc_relations}->{$id})) { - foreach my $category_id (@{$self->{host_hc_relations}->{$id}}) { - next if (defined($hcAdd->{$category_id})); - if ((grep {$_ eq $category_id} @hostCategoriesAllowed) || - (defined($self->{etlProperties}->{'dimension.all.hostcategories'}) && $self->{etlProperties}->{'dimension.all.hostcategories'} ne '')) { - $hcAdd->{$category_id} = 1; - push @$hc, $category_id . ';' . $self->{hc}->{$category_id}; - } + my $self = shift; + my $centreon = $self->{"centreon"}; + my $activated = 1; + + #Hash : each key of the hash table is a host id + #each key is linked to a table containing entries like : "hc_id,hc_name" + my $hostCategoriesWithTemplate = $self->{'hostCategoriesWithTemplates'}; + if (@_) { + $activated = 0; + } + + my $query = "SELECT `host_id` FROM `host` WHERE `host_activate` ='1' AND `host_register` ='1'"; + + my $sth = $centreon->query({ query => $query }); + while (my $row = $sth->fetchrow_hashref()) { + my @tab = (); + my $host_id = $row->{"host_id"}; + $self->getRecursiveCategoriesForOneHost($host_id, \@tab); + $self->getDirectLinkedCategories($host_id, \@tab); + $hostCategoriesWithTemplate->{$row->{"host_id"}} = [@tab]; + undef @tab; + } + $self->{'hostCategoriesWithTemplates'} = $hostCategoriesWithTemplate; + $sth->finish(); +} + +#Get the link between host and categories using direct link hc <> host +sub getDirectLinkedCategories { + my $self = shift; + my $host_id = shift; + my $ref_hostCat = shift; + my $centreon = $self->{"centreon"}; + my $etlProperties = $self->{"etlProperties"}; + my @tab = (); + + my $query = "SELECT `host_id`, `host_name`, `hc_id`, `hc_name`" . + " FROM `host`, `hostcategories_relation`, `hostcategories`" . + " WHERE `host_register`='1'" . + " AND `hostcategories_hc_id` = `hc_id`" . + " AND `host_id`= `host_host_id`" . + " AND `host_id`= " . $host_id . " " . + " AND `host_activate` ='1' AND hostcategories.hc_activate = '1' "; + + if (!defined($etlProperties->{'dimension.all.hostcategories'}) && $etlProperties->{'dimension.hostcategories'} + ne '') { + $query .= " AND `hc_id` IN (" . $etlProperties->{'dimension.hostcategories'} . ")"; + } + + my $sth = $centreon->query({ query => $query }); + while (my $row = $sth->fetchrow_hashref()) { + my $new_entry = $row->{"hc_id"} . ";" . $row->{"hc_name"}; + if (!scalar(@$ref_hostCat)) { + @$ref_hostCat = ($new_entry); + } else { + @tab = @$ref_hostCat; + my $exists = 0; + foreach (@$ref_hostCat) { + if ($_ eq $new_entry) { + $exists = 1; + last; } } - - unshift(@$stack, @{$self->{host_htpl_relations}->{id}}) if (defined($self->{host_htpl_relations}->{id})); + if (!$exists) { + push @$ref_hostCat, $new_entry; + } } - - $hcResult->{$host_id} = $hc; } + $sth->finish(); +} + +sub GetHostTemplateAndCategoryForOneHost { + my $self = shift; + my $host_id = shift; + + my $query = << "EOQ"; +SELECT + hhtemplates.host_id, + hhtemplates.host_name, + hhtemplates.template_id, + hhtemplates.template_name, + categories.hc_id as category_id, + categories.hc_activate as hc_activate, + categories.hc_name as category_name +FROM ( + SELECT + hst.host_id, + hst.host_name, + htpls.host_id as template_id, + htpls.host_name as template_name + FROM + host hst + JOIN + host_template_relation hst_htpl_rel + ON + hst.host_id = hst_htpl_rel.host_host_id + JOIN + host htpls + ON + hst_htpl_rel.host_tpl_id = htpls.host_id + WHERE + hst.host_activate ='1' + AND hst.host_id = $host_id +) hhtemplates +LEFT JOIN + hostcategories_relation hcs_rel + ON + hcs_rel.host_host_id = hhtemplates.template_id +LEFT JOIN + hostcategories categories + ON + hcs_rel.hostcategories_hc_id = categories.hc_id +EOQ + + return $self->{centreon}->query({ query => $query }); - return $hcResult; +} + +#Get the link between host and categories using templates +sub getRecursiveCategoriesForOneHost { + my $self = shift; + my $host_id = shift; + my $ref_hostCat = shift; + my $etlProperties = $self->{"etlProperties"}; + + #Get all categories linked to the templates associated with the host or just template associated with host to be able to call the method recursively + my $sth = $self->GetHostTemplateAndCategoryForOneHost($host_id); + + my @hostCategoriesAllowed = split /,/, $etlProperties->{'dimension.hostcategories'}; + while (my $row = $sth->fetchrow_hashref()) { + my $new_entry; + my @tab = (); + my $categoryId = $row->{"category_id"}; + my $categoryName = $row->{"category_name"}; + my $categoryActivate = $row->{"hc_activate"}; + + #If current category is in allowed categories in ETL configuration + #add it to the categories link to the host, + #Then check for templates categories recursively + if (defined($categoryId) && defined($categoryName) && $categoryActivate == '1') { + if ((grep {$_ eq $categoryId} @hostCategoriesAllowed) + || (defined($etlProperties->{'dimension.all.hostcategories'}) + && $etlProperties->{'dimension.all.hostcategories'} ne '')) { + $new_entry = $categoryId . ";" . $categoryName; + #If no hostcat has been found for the host, create the line + if (!scalar(@$ref_hostCat)) { + @$ref_hostCat = ($new_entry); + } else { + #If the tab is not empty, check wether the combination already exists in the tab + @tab = @$ref_hostCat; + my $exists = 0; + foreach (@$ref_hostCat) { + if ($_ eq $new_entry) { + $exists = 1; + last; + } + } + #If the host category did not exist, add it to the table @$ref_hostCat + if (!$exists) { + push @$ref_hostCat, $new_entry; + } + } + } + } + $self->getRecursiveCategoriesForOneHost($row->{"template_id"}, $ref_hostCat); + } + $sth->finish(); } sub getHostGroupAndCategories { my $self = shift; - + my $hostGroups = $self->getHostGroups(); $self->loadAllCategories(); $self->loadAllHosts(); - my $hostCategories = $self->getHostCategoriesWithTemplate(); + $self->getHostCategoriesWithTemplate(); + my $hostCategories = $self->{"hostCategoriesWithTemplates"}; my @results; - while (my ($hostId, $groups) = each (%$hostGroups)) { - my $categories_ref = $hostCategories->{$hostId}; - my @categoriesTab = (); + while (my ($hostId, $groups) = each(%$hostGroups)) { + my $categories_ref = $hostCategories->{$hostId}; + my @categoriesTab = (); if (defined($categories_ref) && scalar(@$categories_ref)) { @categoriesTab = @$categories_ref; } @@ -226,12 +365,12 @@ sub getHostGroupAndCategories { foreach (@$groups) { my $group = $_; if (scalar(@categoriesTab)) { - foreach(@categoriesTab) { - push @results, $hostId . ';' .$hostName . ';' . $group . ';' . $_; + foreach (@categoriesTab) { + push @results, $hostId . ';' . $hostName . ';' . $group . ';' . $_; } - } else { + } else { #If there is no category - push @results, $hostId . ";" . $hostName . ";" . $group . ";0;NoCategory"; + push @results, $hostId . ";" . $hostName . ";" . $group . ";0;NoCategory"; } } } @@ -239,4 +378,4 @@ sub getHostGroupAndCategories { return \@results; } -1; +1; \ No newline at end of file From ddaf4cbe76be140159ebd89880dded795460364e Mon Sep 17 00:00:00 2001 From: Evan-Adam <152897682+Evan-Adam@users.noreply.github.com> Date: Mon, 27 May 2024 10:14:28 +0200 Subject: [PATCH 832/948] fix(gorgone): Add mbi legacy function to manage host template recursivity (#4130) Refs:MON-37036 --- .../centreon/mbi/libs/centreon/Host.pm | 389 ++++++++++++------ 1 file changed, 264 insertions(+), 125 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Host.pm b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Host.pm index e175076410d..6211e306f28 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Host.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/centreon/Host.pm @@ -30,75 +30,77 @@ use Data::Dumper; # $centreon: Instance of centreonDB class for connection to Centreon database # $centstorage: (optionnal) Instance of centreonDB class for connection to Centstorage database sub new { - my $class = shift; - my $self = {}; - $self->{logger} = shift; - $self->{centreon} = shift; - $self->{etlProperties} = undef; - - if (@_) { - $self->{centstorage} = shift; - } - bless $self, $class; - return $self; + my $class = shift; + my $self = {}; + $self->{logger} = shift; + $self->{centreon} = shift; + $self->{etlProperties} = undef; + + if (@_) { + $self->{centstorage} = shift; + } + bless $self, $class; + return $self; } #Set the etl properties as a variable of the class sub setEtlProperties{ - my $self = shift; - $self->{etlProperties} = shift; + my $self = shift; + $self->{etlProperties} = shift; } # returns two references to two hash tables => hosts indexed by id and hosts indexed by name sub getAllHosts { - my $self = shift; - my $centreon = $self->{centreon}; - my $activated = 1; - if (@_) { - $activated = 0; - } - my (%host_ids, %host_names); - - my $query = "SELECT `host_id`, `host_name`". - " FROM `host`". - " WHERE `host_register`='1'"; + my $self = shift; + my $centreon = $self->{centreon}; + my $activated = 1; + if (@_) { + $activated = 0; + } + my (%host_ids, %host_names); + + my $query = "SELECT `host_id`, `host_name`" . + " FROM `host`" . + " WHERE `host_register`='1'"; if ($activated == 1) { $query .= " AND `host_activate` ='1'"; } - my $sth = $centreon->query({ query => $query }); - while (my $row = $sth->fetchrow_hashref()) { - $host_ids{ $row->{host_name} } = $row->{host_id}; - $host_names{ $row->{host_id} } = $row->{host_name}; - } - return (\%host_ids, \%host_names); + my $sth = $centreon->query({ query => $query }); + while (my $row = $sth->fetchrow_hashref()) { + $host_ids{ $row->{host_name} } = $row->{host_id}; + $host_names{ $row->{host_id} } = $row->{host_name}; + } + return (\%host_ids, \%host_names); } # Get all hosts, keys are IDs sub getAllHostsByID { - my $self = shift; - my ($host_ids, $host_names) = $self->getAllHosts(); - return ($host_ids); + my $self = shift; + my ($host_ids, $host_names) = $self->getAllHosts(); + return ($host_ids); } # Get all hosts, keys are names sub getAllHostsByName { - my $self = shift; - my ($host_ids, $host_names) = $self->getAllHosts(); - return ($host_names); + my $self = shift; + my ($host_ids, $host_names) = $self->getAllHosts(); + return ($host_names); } sub loadAllCategories { my $self = shift; - $self->{hc} = {}; + $self->{hc} = {}; $self->{host_hc_relations} = {}; - my $query = "SELECT hc.hc_id as category_id, hc.hc_name as category_name, host_host_id + my $query = "SELECT hc.hc_id as category_id, hc.hc_name as category_name, host_host_id FROM hostcategories hc, hostcategories_relation hr WHERE hc.hc_activate = '1' AND hc.hc_id = hr.hostcategories_hc_id"; - my $sth = $self->{centreon}->query({ query => $query }); - while (my $row = $sth->fetchrow_hashref()) { - $self->{hc}->{ $row->{category_id} } = $row->{category_name} if (!defined($self->{hc}->{ $row->{category_id} })); - $self->{host_hc_relations}->{ $row->{host_host_id} } = [] if (!defined($self->{host_hc_relations}->{ $row->{host_host_id} })); + my $sth = $self->{centreon}->query({ query => $query }); + while (my $row = $sth->fetchrow_hashref()) { + $self->{hc}->{ $row->{category_id} } = $row->{category_name} if (!defined($self->{hc}->{ $row + ->{category_id} })); + $self->{host_hc_relations}->{ $row->{host_host_id} } = [] if (!defined($self->{host_hc_relations}->{ $row + ->{host_host_id} })); push @{$self->{host_hc_relations}->{ $row->{host_host_id} }}, $row->{category_id}; } } @@ -106,15 +108,17 @@ sub loadAllCategories { sub loadAllHosts { my $self = shift; - $self->{hosts} = {}; + $self->{hosts} = {}; $self->{host_htpl_relations} = {}; - my $query = "SELECT h.host_id, h.host_name, host_tpl_id + my $query = "SELECT h.host_id, h.host_name, host_tpl_id FROM host h, host_template_relation htr WHERE h.host_activate = '1' AND h.host_id = htr.host_host_id"; - my $sth = $self->{centreon}->query({ query => $query }); - while (my $row = $sth->fetchrow_hashref()) { - $self->{hosts}->{ $row->{host_id} } = $row->{host_name} if (!defined($self->{hosts}->{ $row->{host_id} })); - $self->{host_htpl_relations}->{ $row->{host_id} } = [] if (!defined($self->{host_htpl_relations}->{ $row->{host_id} })); + my $sth = $self->{centreon}->query({ query => $query }); + while (my $row = $sth->fetchrow_hashref()) { + $self->{hosts}->{ $row->{host_id} } = $row->{host_name} if (!defined($self->{hosts}->{ $row + ->{host_id} })); + $self->{host_htpl_relations}->{ $row->{host_id} } = [] if (!defined($self->{host_htpl_relations}->{ $row + ->{host_id} })); push @{$self->{host_htpl_relations}->{ $row->{host_id} }}, $row->{host_tpl_id}; } } @@ -124,101 +128,236 @@ sub loadAllHosts { # each key of the hash table is a host id # each key is linked to a table containing entries like : "hostgroup_id;hostgroup_name" sub getHostGroups { - my $self = shift; - my $centreon = $self->{"centreon"}; - my $activated = 1; - my $etlProperties = $self->{'etlProperties'}; - if (@_) { - $activated = 0; - } - my %result = (); - - my $query = "SELECT `host_id`, `host_name`, `hg_id`, `hg_name`". - " FROM `host`, `hostgroup_relation`, `hostgroup`". - " WHERE `host_register`='1'". - " AND `hostgroup_hg_id` = `hg_id`". - " AND `host_id`= `host_host_id`"; + my $self = shift; + my $centreon = $self->{"centreon"}; + my $activated = 1; + my $etlProperties = $self->{'etlProperties'}; + if (@_) { + $activated = 0; + } + my %result = (); + + my $query = "SELECT `host_id`, `host_name`, `hg_id`, `hg_name`" . + " FROM `host`, `hostgroup_relation`, `hostgroup`" . + " WHERE `host_register`='1'" . + " AND `hostgroup_hg_id` = `hg_id`" . + " AND `host_id`= `host_host_id`"; if ($activated == 1) { $query .= " AND `host_activate` ='1'"; } if (!defined($etlProperties->{'dimension.all.hostgroups'}) && $etlProperties->{'dimension.hostgroups'} ne '') { - $query .= " AND `hg_id` IN (".$etlProperties->{'dimension.hostgroups'}.")"; - } - my $sth = $centreon->query({ query => $query }); - while (my $row = $sth->fetchrow_hashref()) { - my $new_entry = $row->{"hg_id"}.";".$row->{"hg_name"}; - if (defined($result{$row->{"host_id"}})) { - my $tab_ref = $result{$row->{"host_id"}}; - my @tab = @$tab_ref; - my $exists = 0; - foreach(@tab) { - if ($_ eq $new_entry) { - $exists = 1; - last; - } - } - if (!$exists) { - push @tab, $new_entry; - } - $result{$row->{"host_id"}} = \@tab; - }else { - my @tab = ($new_entry); - $result{$row->{"host_id"}} = \@tab; - } - } - $sth->finish(); - return (\%result); + $query .= " AND `hg_id` IN (" . $etlProperties->{'dimension.hostgroups'} . ")"; + } + my $sth = $centreon->query({ query => $query }); + while (my $row = $sth->fetchrow_hashref()) { + my $new_entry = $row->{"hg_id"} . ";" . $row->{"hg_name"}; + if (defined($result{$row->{"host_id"}})) { + my $tab_ref = $result{$row->{"host_id"}}; + my @tab = @$tab_ref; + my $exists = 0; + foreach (@tab) { + if ($_ eq $new_entry) { + $exists = 1; + last; + } + } + if (!$exists) { + push @tab, $new_entry; + } + $result{$row->{"host_id"}} = \@tab; + } else { + my @tab = ($new_entry); + $result{$row->{"host_id"}} = \@tab; + } + } + $sth->finish(); + return (\%result); } #Fill a class Hash table that contains the relation between host_id and table[hc_id,hc_name] sub getHostCategoriesWithTemplate { - my $self = shift; - - my @hostCategoriesAllowed = split(/,/, $self->{etlProperties}->{'dimension.hostcategories'}); - - my %loop = (); - my $hcResult = {}; - foreach my $host_id (keys %{$self->{hosts}}) { - my $stack = [$host_id]; - my $hcAdd = {}; - my $hc = []; - foreach (my $id = shift(@$stack)) { - next if (defined($loop{$id})); - $loop{$id} = 1; - - if (defined($self->{host_hc_relations}->{$id})) { - foreach my $category_id (@{$self->{host_hc_relations}->{$id}}) { - next if (defined($hcAdd->{$category_id})); - if ((grep {$_ eq $category_id} @hostCategoriesAllowed) || - (defined($self->{etlProperties}->{'dimension.all.hostcategories'}) && $self->{etlProperties}->{'dimension.all.hostcategories'} ne '')) { - $hcAdd->{$category_id} = 1; - push @$hc, $category_id . ';' . $self->{hc}->{$category_id}; - } + my $self = shift; + my $centreon = $self->{"centreon"}; + my $activated = 1; + + #Hash : each key of the hash table is a host id + #each key is linked to a table containing entries like : "hc_id,hc_name" + my $hostCategoriesWithTemplate = $self->{'hostCategoriesWithTemplates'}; + if (@_) { + $activated = 0; + } + + my $query = "SELECT `host_id` FROM `host` WHERE `host_activate` ='1' AND `host_register` ='1'"; + + my $sth = $centreon->query({ query => $query }); + while (my $row = $sth->fetchrow_hashref()) { + my @tab = (); + my $host_id = $row->{"host_id"}; + $self->getRecursiveCategoriesForOneHost($host_id, \@tab); + $self->getDirectLinkedCategories($host_id, \@tab); + $hostCategoriesWithTemplate->{$row->{"host_id"}} = [@tab]; + undef @tab; + } + $self->{'hostCategoriesWithTemplates'} = $hostCategoriesWithTemplate; + $sth->finish(); +} + +#Get the link between host and categories using direct link hc <> host +sub getDirectLinkedCategories { + my $self = shift; + my $host_id = shift; + my $ref_hostCat = shift; + my $centreon = $self->{"centreon"}; + my $etlProperties = $self->{"etlProperties"}; + my @tab = (); + + my $query = "SELECT `host_id`, `host_name`, `hc_id`, `hc_name`" . + " FROM `host`, `hostcategories_relation`, `hostcategories`" . + " WHERE `host_register`='1'" . + " AND `hostcategories_hc_id` = `hc_id`" . + " AND `host_id`= `host_host_id`" . + " AND `host_id`= " . $host_id . " " . + " AND `host_activate` ='1' AND hostcategories.hc_activate = '1' "; + + if (!defined($etlProperties->{'dimension.all.hostcategories'}) && $etlProperties->{'dimension.hostcategories'} + ne '') { + $query .= " AND `hc_id` IN (" . $etlProperties->{'dimension.hostcategories'} . ")"; + } + + my $sth = $centreon->query({ query => $query }); + while (my $row = $sth->fetchrow_hashref()) { + my $new_entry = $row->{"hc_id"} . ";" . $row->{"hc_name"}; + if (!scalar(@$ref_hostCat)) { + @$ref_hostCat = ($new_entry); + } else { + @tab = @$ref_hostCat; + my $exists = 0; + foreach (@$ref_hostCat) { + if ($_ eq $new_entry) { + $exists = 1; + last; } } - - unshift(@$stack, @{$self->{host_htpl_relations}->{id}}) if (defined($self->{host_htpl_relations}->{id})); + if (!$exists) { + push @$ref_hostCat, $new_entry; + } } - - $hcResult->{$host_id} = $hc; } + $sth->finish(); +} + +sub GetHostTemplateAndCategoryForOneHost { + my $self = shift; + my $host_id = shift; + + my $query = << "EOQ"; +SELECT + hhtemplates.host_id, + hhtemplates.host_name, + hhtemplates.template_id, + hhtemplates.template_name, + categories.hc_id as category_id, + categories.hc_activate as hc_activate, + categories.hc_name as category_name +FROM ( + SELECT + hst.host_id, + hst.host_name, + htpls.host_id as template_id, + htpls.host_name as template_name + FROM + host hst + JOIN + host_template_relation hst_htpl_rel + ON + hst.host_id = hst_htpl_rel.host_host_id + JOIN + host htpls + ON + hst_htpl_rel.host_tpl_id = htpls.host_id + WHERE + hst.host_activate ='1' + AND hst.host_id = $host_id +) hhtemplates +LEFT JOIN + hostcategories_relation hcs_rel + ON + hcs_rel.host_host_id = hhtemplates.template_id +LEFT JOIN + hostcategories categories + ON + hcs_rel.hostcategories_hc_id = categories.hc_id +EOQ + + return $self->{centreon}->query({ query => $query }); - return $hcResult; +} + +#Get the link between host and categories using templates +sub getRecursiveCategoriesForOneHost { + my $self = shift; + my $host_id = shift; + my $ref_hostCat = shift; + my $etlProperties = $self->{"etlProperties"}; + + #Get all categories linked to the templates associated with the host or just template associated with host to be able to call the method recursively + my $sth = $self->GetHostTemplateAndCategoryForOneHost($host_id); + + my @hostCategoriesAllowed = split /,/, $etlProperties->{'dimension.hostcategories'}; + while (my $row = $sth->fetchrow_hashref()) { + my $new_entry; + my @tab = (); + my $categoryId = $row->{"category_id"}; + my $categoryName = $row->{"category_name"}; + my $categoryActivate = $row->{"hc_activate"}; + + #If current category is in allowed categories in ETL configuration + #add it to the categories link to the host, + #Then check for templates categories recursively + if (defined($categoryId) && defined($categoryName) && $categoryActivate == '1') { + if ((grep {$_ eq $categoryId} @hostCategoriesAllowed) + || (defined($etlProperties->{'dimension.all.hostcategories'}) + && $etlProperties->{'dimension.all.hostcategories'} ne '')) { + $new_entry = $categoryId . ";" . $categoryName; + #If no hostcat has been found for the host, create the line + if (!scalar(@$ref_hostCat)) { + @$ref_hostCat = ($new_entry); + } else { + #If the tab is not empty, check wether the combination already exists in the tab + @tab = @$ref_hostCat; + my $exists = 0; + foreach (@$ref_hostCat) { + if ($_ eq $new_entry) { + $exists = 1; + last; + } + } + #If the host category did not exist, add it to the table @$ref_hostCat + if (!$exists) { + push @$ref_hostCat, $new_entry; + } + } + } + } + $self->getRecursiveCategoriesForOneHost($row->{"template_id"}, $ref_hostCat); + } + $sth->finish(); } sub getHostGroupAndCategories { my $self = shift; - + my $hostGroups = $self->getHostGroups(); $self->loadAllCategories(); $self->loadAllHosts(); - my $hostCategories = $self->getHostCategoriesWithTemplate(); + $self->getHostCategoriesWithTemplate(); + my $hostCategories = $self->{"hostCategoriesWithTemplates"}; my @results; - while (my ($hostId, $groups) = each (%$hostGroups)) { - my $categories_ref = $hostCategories->{$hostId}; - my @categoriesTab = (); + while (my ($hostId, $groups) = each(%$hostGroups)) { + my $categories_ref = $hostCategories->{$hostId}; + my @categoriesTab = (); if (defined($categories_ref) && scalar(@$categories_ref)) { @categoriesTab = @$categories_ref; } @@ -226,12 +365,12 @@ sub getHostGroupAndCategories { foreach (@$groups) { my $group = $_; if (scalar(@categoriesTab)) { - foreach(@categoriesTab) { - push @results, $hostId . ';' .$hostName . ';' . $group . ';' . $_; + foreach (@categoriesTab) { + push @results, $hostId . ';' . $hostName . ';' . $group . ';' . $_; } - } else { + } else { #If there is no category - push @results, $hostId . ";" . $hostName . ";" . $group . ";0;NoCategory"; + push @results, $hostId . ";" . $hostName . ";" . $group . ";0;NoCategory"; } } } @@ -239,4 +378,4 @@ sub getHostGroupAndCategories { return \@results; } -1; +1; \ No newline at end of file From c41ac5570a261c6f5ec39a93ecb76c60441d06f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Chapron?= <34628915+sc979@users.noreply.github.com> Date: Fri, 31 May 2024 16:47:31 +0200 Subject: [PATCH 833/948] enh(ci): deploy the analysis trigger on ready to review state (#4290) --- .github/workflows/gorgone.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 82a64588afa..9d90dc85662 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -7,6 +7,11 @@ concurrency: on: workflow_dispatch: pull_request: + types: + - opened + - synchronize + - reopened + - ready_for_review paths: - "centreon-gorgone/**" - "!centreon-gorgone/veracode.json" From 7871d56479cde432cd817c2d934a98eaf1e68659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Chapron?= <34628915+sc979@users.noreply.github.com> Date: Fri, 31 May 2024 16:47:31 +0200 Subject: [PATCH 834/948] enh(ci): deploy the analysis trigger on ready to review state (#4290) --- .github/workflows/gorgone.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 82a64588afa..9d90dc85662 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -7,6 +7,11 @@ concurrency: on: workflow_dispatch: pull_request: + types: + - opened + - synchronize + - reopened + - ready_for_review paths: - "centreon-gorgone/**" - "!centreon-gorgone/veracode.json" From 10638a3f6309a6e758242950f934aa9b8f16a1ab Mon Sep 17 00:00:00 2001 From: Evan-Adam <152897682+Evan-Adam@users.noreply.github.com> Date: Thu, 6 Jun 2024 17:39:20 +0200 Subject: [PATCH 835/948] fix(gorgone-api) http response is now 400 in case of error (#4099) --- .../gorgone/modules/core/httpserver/class.pm | 20 ++++++- gorgone/gorgone/standard/api.pm | 6 +-- gorgone/tests/robot/resources/import.resource | 1 + .../tests/robot/resources/resources.resource | 3 +- .../tests/robot/tests/core/httpserver.robot | 52 +++++++++++++++++++ gorgone/tests/robot/tests/core/push.robot | 2 +- 6 files changed, 77 insertions(+), 7 deletions(-) create mode 100644 gorgone/tests/robot/tests/core/httpserver.robot diff --git a/gorgone/gorgone/modules/core/httpserver/class.pm b/gorgone/gorgone/modules/core/httpserver/class.pm index 132218339a4..10b41e56d2b 100644 --- a/gorgone/gorgone/modules/core/httpserver/class.pm +++ b/gorgone/gorgone/modules/core/httpserver/class.pm @@ -324,7 +324,23 @@ sub send_response { my ($self, %options) = @_; if (defined($options{response}) && $options{response} ne '') { - my $response = HTTP::Response->new(200); + my $http_code = 200; + eval { + # we don't want to raise an error if we can't find an http code or if we don't send back + # something else than a json, so we don't check $@ variable + my $content = JSON::XS->new->decode($options{response}); + if ($content->{http_response_code}){ + $http_code = $content->{http_response_code}; + delete($content->{http_response_code}); + $options{response} = JSON::XS->new->encode($content); + } + elsif ($content->{error}){ + $http_code = 400; + } + }; + + + my $response = HTTP::Response->new($http_code); $response->header('Content-Type' => 'application/json'); $response->content($options{response} . "\n"); $options{connection}->send_response($response); @@ -352,7 +368,7 @@ sub api_call { if ($request->method =~ /POST|PATCH/ && defined($request->content)); }; if ($@) { - return '{"error":"decode_error","message":"POST content must be JSON-formated"}';; + return '{"error":"decode_error","message":"POST content must be JSON-formated","http_response_code":"400"}'; } my %parameters = $request->uri->query_form; diff --git a/gorgone/gorgone/standard/api.pm b/gorgone/gorgone/standard/api.pm index a8ec6166271..52f810a6476 100644 --- a/gorgone/gorgone/standard/api.pm +++ b/gorgone/gorgone/standard/api.pm @@ -83,7 +83,7 @@ sub root { module => $options{module} ); } else { - $response = '{"error":"method_unknown","message":"Method not implemented"}'; + $response = '{"error":"method_unknown","message":"Method not implemented","http_response_code":"404"}'; } return $response; @@ -166,14 +166,14 @@ sub call_internal { $content = JSON::XS->new->decode($options{module}->{tokens}->{$action_token}->{data}); }; if ($@) { - $response = '{"error":"decode_error","message":"Cannot decode response"}'; + $response = '{"error":"decode_error","message":"Cannot decode response","http_response_code":"400"}'; } else { if (defined($content->{data})) { eval { $response = JSON::XS->new->encode($content->{data}); }; if ($@) { - $response = '{"error":"encode_error","message":"Cannot encode response"}'; + $response = '{"error":"encode_error","message":"Cannot encode response","http_response_code":"400"}'; } } else { $response = ''; diff --git a/gorgone/tests/robot/resources/import.resource b/gorgone/tests/robot/resources/import.resource index b072755cce0..82132fb8744 100644 --- a/gorgone/tests/robot/resources/import.resource +++ b/gorgone/tests/robot/resources/import.resource @@ -3,5 +3,6 @@ Documentation This is the documentation for the import resource file. Library Examples Library OperatingSystem Library String +Library Collections Resource resources.resource Library DatabaseLibrary diff --git a/gorgone/tests/robot/resources/resources.resource b/gorgone/tests/robot/resources/resources.resource index 95102feb845..91ae77e0775 100644 --- a/gorgone/tests/robot/resources/resources.resource +++ b/gorgone/tests/robot/resources/resources.resource @@ -5,6 +5,7 @@ Library Process Library RequestsLibrary *** Variables *** +${gorgone_binary} /usr/bin/gorgoned ${ROOT_CONFIG} ${CURDIR}${/}..${/}config${/} ${pull_central_config} ${ROOT_CONFIG}pull_central_config.yaml ${pull_poller_config} ${ROOT_CONFIG}pull_poller_config.yaml @@ -21,7 +22,7 @@ Start Gorgone [Arguments] ${CONFIG_FILE} ${SEVERITY} ${ALIAS} ${process} Start Process ... /usr/bin/perl - ... /usr/bin/gorgoned + ... ${gorgone_binary} ... --config ... ${CONFIG_FILE} ... --logfile diff --git a/gorgone/tests/robot/tests/core/httpserver.robot b/gorgone/tests/robot/tests/core/httpserver.robot new file mode 100644 index 00000000000..53d2e61d168 --- /dev/null +++ b/gorgone/tests/robot/tests/core/httpserver.robot @@ -0,0 +1,52 @@ +*** Settings *** +Documentation check gorgone api response +Suite Setup Setup Gorgone +Suite Teardown Stop Gorgone And Remove Gorgone Config httpserver_api_statuscode +Resource ${CURDIR}${/}..${/}..${/}resources${/}import.resource +Test Timeout 220s + +*** Variables *** + + +*** Test Cases *** +check http api get status code ${tc} + ${expected_code}= Convert To Integer ${http_status_code} + ${api_response}= GET http://127.0.0.1:8085${endpoint} expected_status=anything + + Log To Console \nendpoint code is : ${api_response.status_code} output is : ${api_response.text} + + Should Be Equal ${api_response.status_code} ${expected_code} + ${expected_json}= evaluate json.loads('''${expected_response}''') json + Dictionaries Should Be Equal ${api_response.json()} ${expected_json} + + Examples: tc http_status_code endpoint expected_response -- + ... forbidden 403 /bad/endpoint {"error":"http_error_403","message":"forbidden"} + ... constatus Ok 200 /api/internal/constatus {"data":{},"action":"constatus","message":"ok"} + ... method not found 404 /api/internal/wrongendpoint {"error":"method_unknown","message":"Method not implemented"} + +check http api post api ${tc} + ${expected_code}= Convert To Integer ${http_status_code} + ${api_response}= POST http://127.0.0.1:8085${endpoint} expected_status=anything data=${body} + + Log To Console \nendpoint code is : ${api_response.status_code} output is : ${api_response.text} + + Should Be Equal ${api_response.status_code} ${expected_code} + IF len("""${expected_response}""") > 0 + ${expected}= evaluate json.loads('''${expected_response}''') json + Dictionaries Should Be Equal ${api_response.json()} ${expected} + END + + Examples: tc http_status_code endpoint body expected_response -- + ... body is not json 400 /api/centreon/nodes/sync { {"error":"decode_error","message":"POST content must be JSON-formated"} + ... body is valid json 200 /api/centreon/nodes/sync {} ${EMPTY} # api send back a random token. + + +*** Keywords *** + +Setup Gorgone + Setup Gorgone Config httpserver_api_statuscode ${push_central_config} + Start Gorgone /etc/centreon-gorgone/httpserver_api_statuscode/includer.yaml debug httpserver_api_statuscode + + Log To Console \nGorgone Started. We have to wait for it to be ready to respond. + Sleep 10 + Log To Console Gorgone should be ready. \n \ No newline at end of file diff --git a/gorgone/tests/robot/tests/core/push.robot b/gorgone/tests/robot/tests/core/push.robot index 1a0032b1b16..7ddb758c907 100644 --- a/gorgone/tests/robot/tests/core/push.robot +++ b/gorgone/tests/robot/tests/core/push.robot @@ -8,7 +8,7 @@ Test Timeout 220s @{process_list} gorgone_central gorgone_poller_2 *** Test Cases *** -test Evan +connect 1 poller to a central [Teardown] Stop Gorgone And Remove Gorgone Config @{process_list} sql_file=${ROOT_CONFIG}push_db_1_poller_delete.sql Log To Console \nStarting the gorgone setup From 3aae7351bb54acc07965311e83c983a3a5d5c4d0 Mon Sep 17 00:00:00 2001 From: Evan-Adam <152897682+Evan-Adam@users.noreply.github.com> Date: Thu, 6 Jun 2024 17:39:20 +0200 Subject: [PATCH 836/948] fix(gorgone-api) http response is now 400 in case of error (#4099) --- .../docker/Dockerfile.gorgone-testing-alma8 | 1 + .../gorgone/modules/core/httpserver/class.pm | 20 ++++++- gorgone/gorgone/standard/api.pm | 6 +-- gorgone/tests/robot/resources/import.resource | 1 + .../tests/robot/resources/resources.resource | 3 +- .../tests/robot/tests/core/httpserver.robot | 52 +++++++++++++++++++ gorgone/tests/robot/tests/core/push.robot | 2 +- 7 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 gorgone/tests/robot/tests/core/httpserver.robot diff --git a/.github/docker/Dockerfile.gorgone-testing-alma8 b/.github/docker/Dockerfile.gorgone-testing-alma8 index 618bb6cd582..c3a7b9cb7aa 100644 --- a/.github/docker/Dockerfile.gorgone-testing-alma8 +++ b/.github/docker/Dockerfile.gorgone-testing-alma8 @@ -10,6 +10,7 @@ dnf -y clean all --enablerepo=* dnf install -y python3.11 python3.11-pip pip3.11 install robotframework robotframework-examples robotframework-databaselibrary pymysql robotframework-requests + dnf clean all EOF diff --git a/gorgone/gorgone/modules/core/httpserver/class.pm b/gorgone/gorgone/modules/core/httpserver/class.pm index 132218339a4..10b41e56d2b 100644 --- a/gorgone/gorgone/modules/core/httpserver/class.pm +++ b/gorgone/gorgone/modules/core/httpserver/class.pm @@ -324,7 +324,23 @@ sub send_response { my ($self, %options) = @_; if (defined($options{response}) && $options{response} ne '') { - my $response = HTTP::Response->new(200); + my $http_code = 200; + eval { + # we don't want to raise an error if we can't find an http code or if we don't send back + # something else than a json, so we don't check $@ variable + my $content = JSON::XS->new->decode($options{response}); + if ($content->{http_response_code}){ + $http_code = $content->{http_response_code}; + delete($content->{http_response_code}); + $options{response} = JSON::XS->new->encode($content); + } + elsif ($content->{error}){ + $http_code = 400; + } + }; + + + my $response = HTTP::Response->new($http_code); $response->header('Content-Type' => 'application/json'); $response->content($options{response} . "\n"); $options{connection}->send_response($response); @@ -352,7 +368,7 @@ sub api_call { if ($request->method =~ /POST|PATCH/ && defined($request->content)); }; if ($@) { - return '{"error":"decode_error","message":"POST content must be JSON-formated"}';; + return '{"error":"decode_error","message":"POST content must be JSON-formated","http_response_code":"400"}'; } my %parameters = $request->uri->query_form; diff --git a/gorgone/gorgone/standard/api.pm b/gorgone/gorgone/standard/api.pm index a8ec6166271..52f810a6476 100644 --- a/gorgone/gorgone/standard/api.pm +++ b/gorgone/gorgone/standard/api.pm @@ -83,7 +83,7 @@ sub root { module => $options{module} ); } else { - $response = '{"error":"method_unknown","message":"Method not implemented"}'; + $response = '{"error":"method_unknown","message":"Method not implemented","http_response_code":"404"}'; } return $response; @@ -166,14 +166,14 @@ sub call_internal { $content = JSON::XS->new->decode($options{module}->{tokens}->{$action_token}->{data}); }; if ($@) { - $response = '{"error":"decode_error","message":"Cannot decode response"}'; + $response = '{"error":"decode_error","message":"Cannot decode response","http_response_code":"400"}'; } else { if (defined($content->{data})) { eval { $response = JSON::XS->new->encode($content->{data}); }; if ($@) { - $response = '{"error":"encode_error","message":"Cannot encode response"}'; + $response = '{"error":"encode_error","message":"Cannot encode response","http_response_code":"400"}'; } } else { $response = ''; diff --git a/gorgone/tests/robot/resources/import.resource b/gorgone/tests/robot/resources/import.resource index b072755cce0..82132fb8744 100644 --- a/gorgone/tests/robot/resources/import.resource +++ b/gorgone/tests/robot/resources/import.resource @@ -3,5 +3,6 @@ Documentation This is the documentation for the import resource file. Library Examples Library OperatingSystem Library String +Library Collections Resource resources.resource Library DatabaseLibrary diff --git a/gorgone/tests/robot/resources/resources.resource b/gorgone/tests/robot/resources/resources.resource index 95102feb845..91ae77e0775 100644 --- a/gorgone/tests/robot/resources/resources.resource +++ b/gorgone/tests/robot/resources/resources.resource @@ -5,6 +5,7 @@ Library Process Library RequestsLibrary *** Variables *** +${gorgone_binary} /usr/bin/gorgoned ${ROOT_CONFIG} ${CURDIR}${/}..${/}config${/} ${pull_central_config} ${ROOT_CONFIG}pull_central_config.yaml ${pull_poller_config} ${ROOT_CONFIG}pull_poller_config.yaml @@ -21,7 +22,7 @@ Start Gorgone [Arguments] ${CONFIG_FILE} ${SEVERITY} ${ALIAS} ${process} Start Process ... /usr/bin/perl - ... /usr/bin/gorgoned + ... ${gorgone_binary} ... --config ... ${CONFIG_FILE} ... --logfile diff --git a/gorgone/tests/robot/tests/core/httpserver.robot b/gorgone/tests/robot/tests/core/httpserver.robot new file mode 100644 index 00000000000..53d2e61d168 --- /dev/null +++ b/gorgone/tests/robot/tests/core/httpserver.robot @@ -0,0 +1,52 @@ +*** Settings *** +Documentation check gorgone api response +Suite Setup Setup Gorgone +Suite Teardown Stop Gorgone And Remove Gorgone Config httpserver_api_statuscode +Resource ${CURDIR}${/}..${/}..${/}resources${/}import.resource +Test Timeout 220s + +*** Variables *** + + +*** Test Cases *** +check http api get status code ${tc} + ${expected_code}= Convert To Integer ${http_status_code} + ${api_response}= GET http://127.0.0.1:8085${endpoint} expected_status=anything + + Log To Console \nendpoint code is : ${api_response.status_code} output is : ${api_response.text} + + Should Be Equal ${api_response.status_code} ${expected_code} + ${expected_json}= evaluate json.loads('''${expected_response}''') json + Dictionaries Should Be Equal ${api_response.json()} ${expected_json} + + Examples: tc http_status_code endpoint expected_response -- + ... forbidden 403 /bad/endpoint {"error":"http_error_403","message":"forbidden"} + ... constatus Ok 200 /api/internal/constatus {"data":{},"action":"constatus","message":"ok"} + ... method not found 404 /api/internal/wrongendpoint {"error":"method_unknown","message":"Method not implemented"} + +check http api post api ${tc} + ${expected_code}= Convert To Integer ${http_status_code} + ${api_response}= POST http://127.0.0.1:8085${endpoint} expected_status=anything data=${body} + + Log To Console \nendpoint code is : ${api_response.status_code} output is : ${api_response.text} + + Should Be Equal ${api_response.status_code} ${expected_code} + IF len("""${expected_response}""") > 0 + ${expected}= evaluate json.loads('''${expected_response}''') json + Dictionaries Should Be Equal ${api_response.json()} ${expected} + END + + Examples: tc http_status_code endpoint body expected_response -- + ... body is not json 400 /api/centreon/nodes/sync { {"error":"decode_error","message":"POST content must be JSON-formated"} + ... body is valid json 200 /api/centreon/nodes/sync {} ${EMPTY} # api send back a random token. + + +*** Keywords *** + +Setup Gorgone + Setup Gorgone Config httpserver_api_statuscode ${push_central_config} + Start Gorgone /etc/centreon-gorgone/httpserver_api_statuscode/includer.yaml debug httpserver_api_statuscode + + Log To Console \nGorgone Started. We have to wait for it to be ready to respond. + Sleep 10 + Log To Console Gorgone should be ready. \n \ No newline at end of file diff --git a/gorgone/tests/robot/tests/core/push.robot b/gorgone/tests/robot/tests/core/push.robot index 1a0032b1b16..7ddb758c907 100644 --- a/gorgone/tests/robot/tests/core/push.robot +++ b/gorgone/tests/robot/tests/core/push.robot @@ -8,7 +8,7 @@ Test Timeout 220s @{process_list} gorgone_central gorgone_poller_2 *** Test Cases *** -test Evan +connect 1 poller to a central [Teardown] Stop Gorgone And Remove Gorgone Config @{process_list} sql_file=${ROOT_CONFIG}push_db_1_poller_delete.sql Log To Console \nStarting the gorgone setup From 7f6985806fc47870b8d711d0f685912039205368 Mon Sep 17 00:00:00 2001 From: Evan-Adam <152897682+Evan-Adam@users.noreply.github.com> Date: Thu, 6 Jun 2024 17:42:50 +0200 Subject: [PATCH 837/948] fix(gorgone-whitelist) add whitelist in gorgone action module for centreon ha (#4108) --- .../configuration/whitelist.conf.d/centreon.yaml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/gorgone/packaging/configuration/whitelist.conf.d/centreon.yaml b/gorgone/packaging/configuration/whitelist.conf.d/centreon.yaml index 7b9fd8515db..5a7e2cca4e8 100644 --- a/gorgone/packaging/configuration/whitelist.conf.d/centreon.yaml +++ b/gorgone/packaging/configuration/whitelist.conf.d/centreon.yaml @@ -3,7 +3,7 @@ - ^sudo\s+(/bin/|/usr/bin/)?systemctl\s+(reload|restart)\s+(centengine|centreontrapd|cbd)\s*$ - ^(sudo\s+)?(/usr/bin/)?service\s+(centengine|centreontrapd|cbd|cbd-sql)\s+(reload|restart)\s*$ - ^/usr/sbin/centenginestats\s+-c\s+/etc/centreon-engine/+centengine\.cfg\s*$ -- ^cat\s+/var/lib/centreon-engine/[a-zA-Z0-9\-]+-stats\.json\s*$ +- ^cat\s+/var/lib/centreon-engine/+[a-zA-Z0-9\-]+-stats\.json\s*$ - ^/usr/lib/centreon/plugins/.*$ - ^/bin/perl /usr/share/centreon/bin/anomaly_detection --seasonality >> /var/log/centreon/anomaly_detection\.log 2>&1\s*$ - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ @@ -11,3 +11,10 @@ - ^mkdir - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ +- ^/usr/bin/php (-q )?/usr/share/centreon/cron/[\w,\s.-]+ >> /var/log/centreon-gorgone/[\w,\s.-]+\s+2>&1$ +- ^/usr/bin/php -q /usr/share/centreon/www/modules/centreon-bi-server/tools/purgeArchivesFiles\.php >> /var/log/centreon-gorgone/centreon-bi-archive-retention\.log 2>&1$ +- ^/usr/share/centreon/cron/eventReportBuilder --config=/etc/centreon/conf\.pm >> /var/log/centreon-gorgone/eventReportBuilder\.log 2>&1$ +- ^/usr/share/centreon/cron/dashboardBuilder --config=/etc/centreon/conf\.pm >> /var/log/centreon-gorgone/dashboardBuilder\.log 2>&1$ +- ^/usr/share/centreon/www/modules/centreon-dsm/+cron/centreon_dsm_purge\.pl --config=\"/etc/centreon/conf.pm\" --severity=\S+ >> /var/log/centreon-gorgone/centreon_dsm_purge\.log 2>&1\s*$ +- ^/usr/share/centreon-bi-backup/centreon-bi-backup-web\.sh >> /var/log/centreon-gorgone/centreon-bi-backup-web\.log 2>&1$ +- ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/+cron/centreon_autodisco.pl --config='/etc/centreon/conf.pm' --config-extra='/etc/centreon/centreon_autodisco.pm' --severity=\S+ >> /var/log/centreon-gorgone/centreon_service_discovery.log 2>&1$ From e31baee161981cc79435955cb184e917f40abce4 Mon Sep 17 00:00:00 2001 From: Evan-Adam <152897682+Evan-Adam@users.noreply.github.com> Date: Thu, 6 Jun 2024 17:42:50 +0200 Subject: [PATCH 838/948] fix(gorgone-whitelist) add whitelist in gorgone action module for centreon ha (#4108) --- .../configuration/whitelist.conf.d/centreon.yaml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/gorgone/packaging/configuration/whitelist.conf.d/centreon.yaml b/gorgone/packaging/configuration/whitelist.conf.d/centreon.yaml index 7b9fd8515db..5a7e2cca4e8 100644 --- a/gorgone/packaging/configuration/whitelist.conf.d/centreon.yaml +++ b/gorgone/packaging/configuration/whitelist.conf.d/centreon.yaml @@ -3,7 +3,7 @@ - ^sudo\s+(/bin/|/usr/bin/)?systemctl\s+(reload|restart)\s+(centengine|centreontrapd|cbd)\s*$ - ^(sudo\s+)?(/usr/bin/)?service\s+(centengine|centreontrapd|cbd|cbd-sql)\s+(reload|restart)\s*$ - ^/usr/sbin/centenginestats\s+-c\s+/etc/centreon-engine/+centengine\.cfg\s*$ -- ^cat\s+/var/lib/centreon-engine/[a-zA-Z0-9\-]+-stats\.json\s*$ +- ^cat\s+/var/lib/centreon-engine/+[a-zA-Z0-9\-]+-stats\.json\s*$ - ^/usr/lib/centreon/plugins/.*$ - ^/bin/perl /usr/share/centreon/bin/anomaly_detection --seasonality >> /var/log/centreon/anomaly_detection\.log 2>&1\s*$ - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ @@ -11,3 +11,10 @@ - ^mkdir - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ +- ^/usr/bin/php (-q )?/usr/share/centreon/cron/[\w,\s.-]+ >> /var/log/centreon-gorgone/[\w,\s.-]+\s+2>&1$ +- ^/usr/bin/php -q /usr/share/centreon/www/modules/centreon-bi-server/tools/purgeArchivesFiles\.php >> /var/log/centreon-gorgone/centreon-bi-archive-retention\.log 2>&1$ +- ^/usr/share/centreon/cron/eventReportBuilder --config=/etc/centreon/conf\.pm >> /var/log/centreon-gorgone/eventReportBuilder\.log 2>&1$ +- ^/usr/share/centreon/cron/dashboardBuilder --config=/etc/centreon/conf\.pm >> /var/log/centreon-gorgone/dashboardBuilder\.log 2>&1$ +- ^/usr/share/centreon/www/modules/centreon-dsm/+cron/centreon_dsm_purge\.pl --config=\"/etc/centreon/conf.pm\" --severity=\S+ >> /var/log/centreon-gorgone/centreon_dsm_purge\.log 2>&1\s*$ +- ^/usr/share/centreon-bi-backup/centreon-bi-backup-web\.sh >> /var/log/centreon-gorgone/centreon-bi-backup-web\.log 2>&1$ +- ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/+cron/centreon_autodisco.pl --config='/etc/centreon/conf.pm' --config-extra='/etc/centreon/centreon_autodisco.pm' --severity=\S+ >> /var/log/centreon-gorgone/centreon_service_discovery.log 2>&1$ From 3662087e34e9b9eb8a23ae36bc23cd0375c2bfc1 Mon Sep 17 00:00:00 2001 From: Evan-Adam <152897682+Evan-Adam@users.noreply.github.com> Date: Thu, 13 Jun 2024 16:32:55 +0200 Subject: [PATCH 839/948] test(gorgone) add test for pullwss gorgone communication mode (#4121) Co-authored-by: Sophie Depassio <sdepassio@centreon.com> Co-authored-by: May <110405507+mushroomempires@users.noreply.github.com> --- .../robot/config/gorgone_core_central.yaml | 11 ++ .../robot/config/pullwss_central_config.yaml | 33 ++++++ .../pullwss_node_register_one_node.yaml | 4 + .../robot/config/pullwss_poller_config.yaml | 12 ++ .../robot/config/push_central_config.yaml | 8 -- .../tests/robot/config/push_db_1_poller.sql | 106 +++++++++--------- .../tests/robot/resources/resources.resource | 74 +++++++----- .../tests/robot/tests/core/httpserver.robot | 4 +- gorgone/tests/robot/tests/core/pullwss.robot | 16 +++ gorgone/tests/robot/tests/core/push.robot | 4 +- .../robot/tests/start_stop/start_stop.robot | 7 +- 11 files changed, 187 insertions(+), 92 deletions(-) create mode 100644 gorgone/tests/robot/config/gorgone_core_central.yaml create mode 100644 gorgone/tests/robot/config/pullwss_central_config.yaml create mode 100644 gorgone/tests/robot/config/pullwss_node_register_one_node.yaml create mode 100644 gorgone/tests/robot/config/pullwss_poller_config.yaml create mode 100644 gorgone/tests/robot/tests/core/pullwss.robot diff --git a/gorgone/tests/robot/config/gorgone_core_central.yaml b/gorgone/tests/robot/config/gorgone_core_central.yaml new file mode 100644 index 00000000000..2121670c96a --- /dev/null +++ b/gorgone/tests/robot/config/gorgone_core_central.yaml @@ -0,0 +1,11 @@ +gorgone: + gorgonecore: + internal_com_type: ipc + internal_com_path: /etc/centreon-gorgone/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/routing.ipc + + gorgone_db_type: SQLite + gorgone_db_name: dbname=/etc/centreon-gorgone/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/history.sdb + + privkey: "/var/lib/centreon-gorgone/.keys/rsakey.priv.pem" + pubkey: "/var/lib/centreon-gorgone/.keys/rsakey.pub.pem" + diff --git a/gorgone/tests/robot/config/pullwss_central_config.yaml b/gorgone/tests/robot/config/pullwss_central_config.yaml new file mode 100644 index 00000000000..7e984f386d1 --- /dev/null +++ b/gorgone/tests/robot/config/pullwss_central_config.yaml @@ -0,0 +1,33 @@ +gorgone: + gorgonecore: + id: 1 + modules: + - name: proxy + package: "gorgone::modules::core::proxy::hooks" + enable: true + httpserver: + enable: true + ssl: false + #ssl_cert_file: /etc/centreon-gorgone/keys/certificate.crt + #ssl_key_file: /etc/centreon-gorgone/keys/private.key + token: "secret_token" + address: "0.0.0.0" + port: 8086 + + - name: register + package: "gorgone::modules::core::register::hooks" + enable: true + config_file: /etc/centreon-gorgone/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/config.d/pullwss_node_register_one_node.yaml + + - name: httpserver + package: "gorgone::modules::core::httpserver::hooks" + enable: true + address: "0.0.0.0" + port: "8085" + ssl: false + auth: + enabled: false + allowed_hosts: + enabled: true + subnets: + - 127.0.0.1/32 diff --git a/gorgone/tests/robot/config/pullwss_node_register_one_node.yaml b/gorgone/tests/robot/config/pullwss_node_register_one_node.yaml new file mode 100644 index 00000000000..77811a9f32a --- /dev/null +++ b/gorgone/tests/robot/config/pullwss_node_register_one_node.yaml @@ -0,0 +1,4 @@ +nodes: + - id: 2 + type: wss + prevail: 1 diff --git a/gorgone/tests/robot/config/pullwss_poller_config.yaml b/gorgone/tests/robot/config/pullwss_poller_config.yaml new file mode 100644 index 00000000000..91785c34bc1 --- /dev/null +++ b/gorgone/tests/robot/config/pullwss_poller_config.yaml @@ -0,0 +1,12 @@ +gorgone: + gorgonecore: + id: 2 + modules: + - name: pullwss + package: "gorgone::modules::core::pullwss::hooks" + enable: true + ssl: false + port: 8086 + token: "secret_token" + address: 127.0.0.1 + ping: 1 diff --git a/gorgone/tests/robot/config/push_central_config.yaml b/gorgone/tests/robot/config/push_central_config.yaml index d866027ade3..fc71b00d658 100644 --- a/gorgone/tests/robot/config/push_central_config.yaml +++ b/gorgone/tests/robot/config/push_central_config.yaml @@ -1,14 +1,6 @@ gorgone: gorgonecore: - internal_com_type: ipc - internal_com_path: /etc/centreon-gorgone/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/routing.ipc - external_com_type: tcp - - gorgone_db_type: SQLite - gorgone_db_name: dbname=/etc/centreon-gorgone/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/history.sdb id: 1 - privkey: "/var/lib/centreon-gorgone/.keys/rsakey.priv.pem" - pubkey: "/var/lib/centreon-gorgone/.keys/rsakey.pub.pem" modules: - name: proxy package: "gorgone::modules::core::proxy::hooks" diff --git a/gorgone/tests/robot/config/push_db_1_poller.sql b/gorgone/tests/robot/config/push_db_1_poller.sql index e8e3edf6991..9cb398f7e11 100644 --- a/gorgone/tests/robot/config/push_db_1_poller.sql +++ b/gorgone/tests/robot/config/push_db_1_poller.sql @@ -1,68 +1,68 @@ INSERT IGNORE INTO `nagios_server` VALUES ( - 1, 'Central', '1', 1, 1711560733, '127.0.0.1', - '1', '0', 'service centengine start', - 'service centengine stop', 'service centengine restart', - 'service centengine reload', '/usr/sbin/centengine', - '/usr/sbin/centenginestats', '/var/log/centreon-engine/service-perfdata', - 'service cbd reload', '/etc/centreon-broker', - '/usr/share/centreon/lib/centreon-broker', - '/usr/lib64/centreon-connector', - 22, '1', 5556, 'centreontrapd', '/etc/snmp/centreon_traps/', + 1, 'Central', '1', 1, 1711560733, '127.0.0.1', + '1', '0', 'service centengine start', + 'service centengine stop', 'service centengine restart', + 'service centengine reload', '/usr/sbin/centengine', + '/usr/sbin/centenginestats', '/var/log/centreon-engine/service-perfdata', + 'service cbd reload', '/etc/centreon-broker', + '/usr/share/centreon/lib/centreon-broker', + '/usr/lib64/centreon-connector', + 22, '1', 5556, 'centreontrapd', '/etc/snmp/centreon_traps/', NULL, NULL, NULL, NULL, '1', '0' - ), + ), ( 2, 'pushpoller', '0', 0, NULL, '127.0.0.1', - '1', '0', 'service centengine start', - 'service centengine stop', 'service centengine restart', - 'service centengine reload', '/usr/sbin/centengine', - '/usr/sbin/centenginestats', '/var/log/centreon-engine/service-perfdata', - 'service cbd reload', '/etc/centreon-broker', - '/usr/share/centreon/lib/centreon-broker', - '/usr/lib64/centreon-connector', - 22, '1', 5556, 'centreontrapd', '/etc/snmp/centreon_traps/', - NULL, NULL, '/var/log/centreon-broker/', + '1', '0', 'service centengine start', + 'service centengine stop', 'service centengine restart', + 'service centengine reload', '/usr/sbin/centengine', + '/usr/sbin/centenginestats', '/var/log/centreon-engine/service-perfdata', + 'service cbd reload', '/etc/centreon-broker', + '/usr/share/centreon/lib/centreon-broker', + '/usr/lib64/centreon-connector', + 22, '1', 5556, 'centreontrapd', '/etc/snmp/centreon_traps/', + NULL, NULL, '/var/log/centreon-broker/', NULL, '1', '0' ); INSERT IGNORE INTO `cfg_nagios` VALUES ( - 1, 'Centreon Engine Central', NULL, - '/var/log/centreon-engine/centengine.log', - '/etc/centreon-engine', '/var/log/centreon-engine/status.dat', - 60, '1', '1', '1', '1', '1', '1', '1', - 4096, '1s', '/var/lib/centreon-engine/rw/centengine.cmd', - '1', '/var/log/centreon-engine/retention.dat', - 60, '1', '1', '0', '1', '1', '1', '1', - NULL, '1', '1', NULL, NULL, NULL, 's', - 's', 's', 0, 15, 15, 5, '0', NULL, NULL, - '0', '25.0', '50.0', '25.0', '50.0', - '0', 60, 12, 30, 30, '1', '1', '0', NULL, - NULL, '0', NULL, 'euro', 30, '~!$%^&*\"|\'<>?,()=', - '`~$^&\"|\'<>', '0', '0', 'admin@localhost', - 'admin@localhost', 'Centreon Engine configuration file for a central instance', - '1', '-1', 1, '1', '1', 15, 15, NULL, - '0', 15, '/var/log/centreon-engine/centengine.debug', - 0, '0', '1', 1000000000, 'centengine.cfg', + 1, 'Centreon Engine Central', NULL, + '/var/log/centreon-engine/centengine.log', + '/etc/centreon-engine', '/var/log/centreon-engine/status.dat', + 60, '1', '1', '1', '1', '1', '1', '1', + 4096, '1s', '/var/lib/centreon-engine/rw/centengine.cmd', + '1', '/var/log/centreon-engine/retention.dat', + 60, '1', '1', '0', '1', '1', '1', '1', + NULL, '1', '1', NULL, NULL, NULL, 's', + 's', 's', 0, 15, 15, 5, '0', NULL, NULL, + '0', '25.0', '50.0', '25.0', '50.0', + '0', 60, 12, 30, 30, '1', '1', '0', NULL, + NULL, '0', NULL, 'euro', 30, '~!$%^&*\"|\'<>?,()=', + '`~$^&\"|\'<>', '0', '0', 'admin@localhost', + 'admin@localhost', 'Centreon Engine configuration file for a central instance', + '1', '-1', 1, '1', '1', 15, 15, NULL, + '0', 15, '/var/log/centreon-engine/centengine.debug', + 0, '0', '1', 1000000000, 'centengine.cfg', '1', '0', '', 'log_v2_enabled' - ), + ), ( - 15, 'pushpoller', NULL, '/var/log/centreon-engine/centengine.log', - '/etc/centreon-engine/', '/var/log/centreon-engine/status.dat', - 60, '1', '1', '1', '1', '1', '1', '1', - 4096, '1s', '/var/lib/centreon-engine/rw/centengine.cmd', - '1', '/var/log/centreon-engine/retention.dat', - 60, '1', '0', '0', '1', '1', '1', '1', - '1', '1', '1', NULL, NULL, '0.5', 's', - 's', 's', 0, 15, 15, 5, '0', 30, 180, '0', - '25.0', '50.0', '25.0', '50.0', '0', - 60, 30, 30, 30, '1', '1', '0', NULL, NULL, - '0', NULL, 'euro', 30, '~!$%^&*\"|\'<>?,()=', - '`~$^&\"|\'<>', '0', '0', 'admin@localhost', - 'admin@localhost', 'Centreon Engine config file for a polling instance', - '1', '-1', 2, '1', '1', 15, 15, NULL, - '0', 15, '/var/log/centreon-engine/centengine.debug', - 0, '0', '1', 1000000000, 'centengine.cfg', + 15, 'pushpoller', NULL, '/var/log/centreon-engine/centengine.log', + '/etc/centreon-engine/', '/var/log/centreon-engine/status.dat', + 60, '1', '1', '1', '1', '1', '1', '1', + 4096, '1s', '/var/lib/centreon-engine/rw/centengine.cmd', + '1', '/var/log/centreon-engine/retention.dat', + 60, '1', '0', '0', '1', '1', '1', '1', + '1', '1', '1', NULL, NULL, '0.5', 's', + 's', 's', 0, 15, 15, 5, '0', 30, 180, '0', + '25.0', '50.0', '25.0', '50.0', '0', + 60, 30, 30, 30, '1', '1', '0', NULL, NULL, + '0', NULL, 'euro', 30, '~!$%^&*\"|\'<>?,()=', + '`~$^&\"|\'<>', '0', '0', 'admin@localhost', + 'admin@localhost', 'Centreon Engine config file for a polling instance', + '1', '-1', 2, '1', '1', 15, 15, NULL, + '0', 15, '/var/log/centreon-engine/centengine.debug', + 0, '0', '1', 1000000000, 'centengine.cfg', '1', '0', '', 'log_v2_enabled' ); diff --git a/gorgone/tests/robot/resources/resources.resource b/gorgone/tests/robot/resources/resources.resource index 91ae77e0775..3d304ea9292 100644 --- a/gorgone/tests/robot/resources/resources.resource +++ b/gorgone/tests/robot/resources/resources.resource @@ -5,12 +5,16 @@ Library Process Library RequestsLibrary *** Variables *** -${gorgone_binary} /usr/bin/gorgoned -${ROOT_CONFIG} ${CURDIR}${/}..${/}config${/} -${pull_central_config} ${ROOT_CONFIG}pull_central_config.yaml -${pull_poller_config} ${ROOT_CONFIG}pull_poller_config.yaml -${push_central_config} ${ROOT_CONFIG}push_central_config.yaml -${push_poller_config} ${ROOT_CONFIG}push_poller_config.yaml +${gorgone_binary} /usr/bin/gorgoned +${ROOT_CONFIG} ${CURDIR}${/}..${/}config${/} +${pull_central_config} ${ROOT_CONFIG}pull_central_config.yaml +${pull_poller_config} ${ROOT_CONFIG}pull_poller_config.yaml +${pullwss_central_config} ${ROOT_CONFIG}pullwss_central_config.yaml +${pullwss_poller_config} ${ROOT_CONFIG}pullwss_poller_config.yaml +${push_central_config} ${ROOT_CONFIG}push_central_config.yaml +${push_poller_config} ${ROOT_CONFIG}push_poller_config.yaml +${gorgone_core_config} ${ROOT_CONFIG}gorgone_core_central.yaml + ${DBHOST} 127.0.0.1 ${DBPORT} 3306 ${DBNAME} centreon_gorgone_test @@ -19,12 +23,12 @@ ${DBPASSWORD} password *** Keywords *** Start Gorgone - [Arguments] ${CONFIG_FILE} ${SEVERITY} ${ALIAS} + [Arguments] ${SEVERITY} ${ALIAS} ${process} Start Process ... /usr/bin/perl ... ${gorgone_binary} ... --config - ... ${CONFIG_FILE} + ... /etc/centreon-gorgone/${ALIAS}/includer.yaml ... --logfile ... /var/log/centreon-gorgone/${ALIAS}/gorgoned.log ... --severity @@ -39,7 +43,7 @@ Stop Gorgone And Remove Gorgone Config FOR ${process} IN @{process_alias} ${result} Terminate Process ${process} - BuiltIn.Run Keyword And Continue On Failure Should Be True ${result.rc} == -15 or ${result.rc} == 0 Engine badly stopped alias = ${process_alias} - code returned ${result.rc}. + BuiltIn.Run Keyword And Continue On Failure Should Be True ${result.rc} == -15 or ${result.rc} == -9 or ${result.rc} == 0 Engine badly stopped alias = ${process} - code returned ${result.rc}. END Gorgone Execute Sql @@ -52,7 +56,7 @@ Gorgone Execute Sql END Setup Gorgone Config - [Arguments] ${gorgone_name} @{file_list} ${sql_file}= + [Arguments] @{file_list} ${gorgone_name}=gorgone_process_name ${sql_file}= Gorgone Execute Sql ${sql_file} Create Directory /var/log/centreon-gorgone/${gorgone_name}/ Copy File ${CURDIR}${/}..${/}config${/}includer.yaml /etc/centreon-gorgone/${gorgone_name}/includer.yaml @@ -63,13 +67,13 @@ Setup Gorgone Config ${key_thumbprint} Run perl /usr/local/bin/gorgone_key_thumbprint.pl --key-path=/var/lib/centreon-gorgone/.keys/rsakey.priv.pem | cut -d: -f4 ${result} Run sed -i -e 's/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/${gorgone_name}/g' /etc/centreon-gorgone/${gorgone_name}/includer.yaml - + ${CMD} Catenate - ... sed -i -e 's/@KEYTHUMBPRINT@/${key_thumbprint}/g' - ... -e 's/@DBNAME@/${DBNAME}/g' - ... -e 's/@DBHOST@/${DBHOST}/g' - ... -e 's/@DBPASSWORD@/${DBPASSWORD}/g' - ... -e 's/@DBUSER@/${DBUSER}/g' + ... sed -i -e 's/@KEYTHUMBPRINT@/${key_thumbprint}/g' + ... -e 's/@DBNAME@/${DBNAME}/g' + ... -e 's/@DBHOST@/${DBHOST}/g' + ... -e 's/@DBPASSWORD@/${DBPASSWORD}/g' + ... -e 's/@DBUSER@/${DBUSER}/g' ... -e 's/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/${gorgone_name}/g' ... /etc/centreon-gorgone/${gorgone_name}/config.d/*.yaml @@ -94,7 +98,7 @@ Check Poller Communicate [Arguments] ${poller_id} ${response} Set Variable ${EMPTY} Log To Console checking Gorgone see poller in rest api response... - FOR ${i} IN RANGE 10 + FOR ${i} IN RANGE 20 Sleep 5 ${response}= GET http://127.0.0.1:8085/api/internal/constatus Log ${response.json()} @@ -103,27 +107,47 @@ Check Poller Communicate END END Log To Console json response : ${response.json()} - Should Be True ${i} < 9 timeout waiting for poller status in gorgone rest api (/api/internal/constatus) : ${response.json()} + Should Be True ${i} < 20 timeout after ${i} time waiting for poller status in gorgone rest api (/api/internal/constatus) : ${response.json()} Should Be True 0 == ${response.json()}[data][${poller_id}][ping_failed] there was failed ping between the central and the poller ${poller_id} Should Be True 0 < ${response.json()}[data][${poller_id}][ping_ok] there was no successful ping between the central and the poller ${poller_id} Setup Two Gorgone Instances [Arguments] ${communication_mode}=push_zmq ${central_name}=gorgone_central ${poller_name}=gorgone_poller_2 - ${result} Run perl /usr/local/bin/gorgone_key_generation.pl # generate key if there is none. # gorgone can generate it's own key, but as we need the thumbprint in the configuration we need to generate them before launching gorgone. # this script only create key if the files don't exists, and silently finish if the files already exists. IF '${communication_mode}' == 'push_zmq' - Setup Gorgone Config ${central_name} ${push_central_config} sql_file=${ROOT_CONFIG}push_db_1_poller.sql - Setup Gorgone Config ${poller_name} ${push_poller_config} + Setup Gorgone Config ${push_central_config} ${gorgone_core_config} gorgone_name=${central_name} sql_file=${ROOT_CONFIG}push_db_1_poller.sql + Setup Gorgone Config ${push_poller_config} gorgone_name=${poller_name} + + Start Gorgone debug ${central_name} + Start Gorgone debug ${poller_name} - Start Gorgone /etc/centreon-gorgone/gorgone_central/includer.yaml debug ${central_name} - Start Gorgone /etc/centreon-gorgone/gorgone_poller_2/includer.yaml debug ${poller_name} - Check Poller Is Connected port=5556 expected_nb=2 Check Poller Communicate 2 + ELSE IF '${communication_mode}' == 'pullwss' + Setup Gorgone Config ${pullwss_central_config} ${ROOT_CONFIG}pullwss_node_register_one_node.yaml ${gorgone_core_config} gorgone_name=${central_name} sql_file=${ROOT_CONFIG}push_db_1_poller.sql + Setup Gorgone Config ${gorgone_core_config} ${pullwss_poller_config} gorgone_name=${poller_name} + + Start Gorgone debug ${central_name} + Wait Until Port Is Bind 8086 + Start Gorgone debug ${poller_name} + Check Poller Is Connected port=8086 expected_nb=2 + Check Poller Communicate 2 + ELSE - Fail pull and pullwss mode are not yet implemented. + Fail pull mode is not yet implemented. END +Wait Until Port Is Bind + [Arguments] ${port} + FOR ${i} IN RANGE 10 + Sleep 0.5 + ${nb_port_listening} Run ss -tlnp | grep ':${port}' | grep LIST | wc -l + IF ${nb_port_listening} == 1 + BREAK + END + END + Should Be True ${i} < 10 Gorgone did not listen on port ${port} on time. + Log To Console tcp port ${port} bind after ${i} attempt (0.5 seconds each) \ No newline at end of file diff --git a/gorgone/tests/robot/tests/core/httpserver.robot b/gorgone/tests/robot/tests/core/httpserver.robot index 53d2e61d168..7e270a81c33 100644 --- a/gorgone/tests/robot/tests/core/httpserver.robot +++ b/gorgone/tests/robot/tests/core/httpserver.robot @@ -44,8 +44,8 @@ check http api post api ${tc} *** Keywords *** Setup Gorgone - Setup Gorgone Config httpserver_api_statuscode ${push_central_config} - Start Gorgone /etc/centreon-gorgone/httpserver_api_statuscode/includer.yaml debug httpserver_api_statuscode + Setup Gorgone Config ${push_central_config} ${gorgone_core_config} gorgone_name=httpserver_api_statuscode + Start Gorgone debug httpserver_api_statuscode Log To Console \nGorgone Started. We have to wait for it to be ready to respond. Sleep 10 diff --git a/gorgone/tests/robot/tests/core/pullwss.robot b/gorgone/tests/robot/tests/core/pullwss.robot new file mode 100644 index 00000000000..f678703e770 --- /dev/null +++ b/gorgone/tests/robot/tests/core/pullwss.robot @@ -0,0 +1,16 @@ +*** Settings *** +Documentation Start and stop gorgone in pullwss mode + +Resource ${CURDIR}${/}..${/}..${/}resources${/}import.resource +Test Timeout 220s + +*** Variables *** +@{process_list} pullwss_gorgone_poller_2 pullwss_gorgone_central + +*** Test Cases *** +check one poller can connect to a central gorgone + [Teardown] Stop Gorgone And Remove Gorgone Config @{process_list} #sql_file=${ROOT_CONFIG}push_db_1_poller_delete.sql + + Log To Console \nStarting the gorgone setup + Setup Two Gorgone Instances communication_mode=pullwss central_name=pullwss_gorgone_central poller_name=pullwss_gorgone_poller_2 + Log To Console End of tests. diff --git a/gorgone/tests/robot/tests/core/push.robot b/gorgone/tests/robot/tests/core/push.robot index 7ddb758c907..5ad63f731d9 100644 --- a/gorgone/tests/robot/tests/core/push.robot +++ b/gorgone/tests/robot/tests/core/push.robot @@ -5,12 +5,12 @@ Resource ${CURDIR}${/}..${/}..${/}resources${/}import.resource Test Timeout 220s *** Variables *** -@{process_list} gorgone_central gorgone_poller_2 +@{process_list} push_zmq_gorgone_central push_zmq_gorgone_poller_2 *** Test Cases *** connect 1 poller to a central [Teardown] Stop Gorgone And Remove Gorgone Config @{process_list} sql_file=${ROOT_CONFIG}push_db_1_poller_delete.sql Log To Console \nStarting the gorgone setup - Setup Two Gorgone Instances push_zmq + Setup Two Gorgone Instances communication_mode=push_zmq central_name=push_zmq_gorgone_central poller_name=push_zmq_gorgone_poller_2 Log To Console End of tests. diff --git a/gorgone/tests/robot/tests/start_stop/start_stop.robot b/gorgone/tests/robot/tests/start_stop/start_stop.robot index 08ddb9b6f04..e00d4889a6d 100644 --- a/gorgone/tests/robot/tests/start_stop/start_stop.robot +++ b/gorgone/tests/robot/tests/start_stop/start_stop.robot @@ -3,15 +3,18 @@ Documentation Start and stop gorgone Resource ${CURDIR}${/}..${/}..${/}resources${/}import.resource Test Timeout 120s +*** Variables *** +${configfile} ${CURDIR}${/}config.yaml *** Test Cases *** Start and stop gorgone # fichier de conf : pull_central + autodiscovery # start gorgone 2 FOR ${i} IN RANGE 5 - Setup Gorgone Config gorgone_start_stop${i} ${CURDIR}${/}config.yaml + ${gorgone_name}= Set Variable gorgone_start_stop${i} + Setup Gorgone Config ${configfile} gorgone_name=${gorgone_name} Log To Console Starting Gorgone... - Start Gorgone /etc/centreon-gorgone/gorgone_start_stop${i}/includer.yaml debug gorgone_start_stop${i} + Start Gorgone debug ${gorgone_name} Sleep 5s Log To Console Stopping Gorgone... Stop Gorgone And Remove Gorgone Config gorgone_start_stop${i} From b3bbaac80bdbf83b244a6cb242b7458e2067a897 Mon Sep 17 00:00:00 2001 From: Evan-Adam <152897682+Evan-Adam@users.noreply.github.com> Date: Thu, 13 Jun 2024 16:32:55 +0200 Subject: [PATCH 840/948] test(gorgone) add test for pullwss gorgone communication mode (#4121) Co-authored-by: Sophie Depassio <sdepassio@centreon.com> Co-authored-by: May <110405507+mushroomempires@users.noreply.github.com> --- .../docker/Dockerfile.gorgone-testing-alma8 | 1 - .../robot/config/gorgone_core_central.yaml | 11 ++ .../robot/config/pullwss_central_config.yaml | 33 ++++++ .../pullwss_node_register_one_node.yaml | 4 + .../robot/config/pullwss_poller_config.yaml | 12 ++ .../robot/config/push_central_config.yaml | 8 -- .../tests/robot/config/push_db_1_poller.sql | 106 +++++++++--------- .../tests/robot/resources/resources.resource | 74 +++++++----- .../tests/robot/tests/core/httpserver.robot | 4 +- gorgone/tests/robot/tests/core/pullwss.robot | 16 +++ gorgone/tests/robot/tests/core/push.robot | 4 +- .../robot/tests/start_stop/start_stop.robot | 7 +- 12 files changed, 187 insertions(+), 93 deletions(-) create mode 100644 gorgone/tests/robot/config/gorgone_core_central.yaml create mode 100644 gorgone/tests/robot/config/pullwss_central_config.yaml create mode 100644 gorgone/tests/robot/config/pullwss_node_register_one_node.yaml create mode 100644 gorgone/tests/robot/config/pullwss_poller_config.yaml create mode 100644 gorgone/tests/robot/tests/core/pullwss.robot diff --git a/.github/docker/Dockerfile.gorgone-testing-alma8 b/.github/docker/Dockerfile.gorgone-testing-alma8 index c3a7b9cb7aa..618bb6cd582 100644 --- a/.github/docker/Dockerfile.gorgone-testing-alma8 +++ b/.github/docker/Dockerfile.gorgone-testing-alma8 @@ -10,7 +10,6 @@ dnf -y clean all --enablerepo=* dnf install -y python3.11 python3.11-pip pip3.11 install robotframework robotframework-examples robotframework-databaselibrary pymysql robotframework-requests - dnf clean all EOF diff --git a/gorgone/tests/robot/config/gorgone_core_central.yaml b/gorgone/tests/robot/config/gorgone_core_central.yaml new file mode 100644 index 00000000000..2121670c96a --- /dev/null +++ b/gorgone/tests/robot/config/gorgone_core_central.yaml @@ -0,0 +1,11 @@ +gorgone: + gorgonecore: + internal_com_type: ipc + internal_com_path: /etc/centreon-gorgone/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/routing.ipc + + gorgone_db_type: SQLite + gorgone_db_name: dbname=/etc/centreon-gorgone/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/history.sdb + + privkey: "/var/lib/centreon-gorgone/.keys/rsakey.priv.pem" + pubkey: "/var/lib/centreon-gorgone/.keys/rsakey.pub.pem" + diff --git a/gorgone/tests/robot/config/pullwss_central_config.yaml b/gorgone/tests/robot/config/pullwss_central_config.yaml new file mode 100644 index 00000000000..7e984f386d1 --- /dev/null +++ b/gorgone/tests/robot/config/pullwss_central_config.yaml @@ -0,0 +1,33 @@ +gorgone: + gorgonecore: + id: 1 + modules: + - name: proxy + package: "gorgone::modules::core::proxy::hooks" + enable: true + httpserver: + enable: true + ssl: false + #ssl_cert_file: /etc/centreon-gorgone/keys/certificate.crt + #ssl_key_file: /etc/centreon-gorgone/keys/private.key + token: "secret_token" + address: "0.0.0.0" + port: 8086 + + - name: register + package: "gorgone::modules::core::register::hooks" + enable: true + config_file: /etc/centreon-gorgone/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/config.d/pullwss_node_register_one_node.yaml + + - name: httpserver + package: "gorgone::modules::core::httpserver::hooks" + enable: true + address: "0.0.0.0" + port: "8085" + ssl: false + auth: + enabled: false + allowed_hosts: + enabled: true + subnets: + - 127.0.0.1/32 diff --git a/gorgone/tests/robot/config/pullwss_node_register_one_node.yaml b/gorgone/tests/robot/config/pullwss_node_register_one_node.yaml new file mode 100644 index 00000000000..77811a9f32a --- /dev/null +++ b/gorgone/tests/robot/config/pullwss_node_register_one_node.yaml @@ -0,0 +1,4 @@ +nodes: + - id: 2 + type: wss + prevail: 1 diff --git a/gorgone/tests/robot/config/pullwss_poller_config.yaml b/gorgone/tests/robot/config/pullwss_poller_config.yaml new file mode 100644 index 00000000000..91785c34bc1 --- /dev/null +++ b/gorgone/tests/robot/config/pullwss_poller_config.yaml @@ -0,0 +1,12 @@ +gorgone: + gorgonecore: + id: 2 + modules: + - name: pullwss + package: "gorgone::modules::core::pullwss::hooks" + enable: true + ssl: false + port: 8086 + token: "secret_token" + address: 127.0.0.1 + ping: 1 diff --git a/gorgone/tests/robot/config/push_central_config.yaml b/gorgone/tests/robot/config/push_central_config.yaml index d866027ade3..fc71b00d658 100644 --- a/gorgone/tests/robot/config/push_central_config.yaml +++ b/gorgone/tests/robot/config/push_central_config.yaml @@ -1,14 +1,6 @@ gorgone: gorgonecore: - internal_com_type: ipc - internal_com_path: /etc/centreon-gorgone/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/routing.ipc - external_com_type: tcp - - gorgone_db_type: SQLite - gorgone_db_name: dbname=/etc/centreon-gorgone/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/history.sdb id: 1 - privkey: "/var/lib/centreon-gorgone/.keys/rsakey.priv.pem" - pubkey: "/var/lib/centreon-gorgone/.keys/rsakey.pub.pem" modules: - name: proxy package: "gorgone::modules::core::proxy::hooks" diff --git a/gorgone/tests/robot/config/push_db_1_poller.sql b/gorgone/tests/robot/config/push_db_1_poller.sql index e8e3edf6991..9cb398f7e11 100644 --- a/gorgone/tests/robot/config/push_db_1_poller.sql +++ b/gorgone/tests/robot/config/push_db_1_poller.sql @@ -1,68 +1,68 @@ INSERT IGNORE INTO `nagios_server` VALUES ( - 1, 'Central', '1', 1, 1711560733, '127.0.0.1', - '1', '0', 'service centengine start', - 'service centengine stop', 'service centengine restart', - 'service centengine reload', '/usr/sbin/centengine', - '/usr/sbin/centenginestats', '/var/log/centreon-engine/service-perfdata', - 'service cbd reload', '/etc/centreon-broker', - '/usr/share/centreon/lib/centreon-broker', - '/usr/lib64/centreon-connector', - 22, '1', 5556, 'centreontrapd', '/etc/snmp/centreon_traps/', + 1, 'Central', '1', 1, 1711560733, '127.0.0.1', + '1', '0', 'service centengine start', + 'service centengine stop', 'service centengine restart', + 'service centengine reload', '/usr/sbin/centengine', + '/usr/sbin/centenginestats', '/var/log/centreon-engine/service-perfdata', + 'service cbd reload', '/etc/centreon-broker', + '/usr/share/centreon/lib/centreon-broker', + '/usr/lib64/centreon-connector', + 22, '1', 5556, 'centreontrapd', '/etc/snmp/centreon_traps/', NULL, NULL, NULL, NULL, '1', '0' - ), + ), ( 2, 'pushpoller', '0', 0, NULL, '127.0.0.1', - '1', '0', 'service centengine start', - 'service centengine stop', 'service centengine restart', - 'service centengine reload', '/usr/sbin/centengine', - '/usr/sbin/centenginestats', '/var/log/centreon-engine/service-perfdata', - 'service cbd reload', '/etc/centreon-broker', - '/usr/share/centreon/lib/centreon-broker', - '/usr/lib64/centreon-connector', - 22, '1', 5556, 'centreontrapd', '/etc/snmp/centreon_traps/', - NULL, NULL, '/var/log/centreon-broker/', + '1', '0', 'service centengine start', + 'service centengine stop', 'service centengine restart', + 'service centengine reload', '/usr/sbin/centengine', + '/usr/sbin/centenginestats', '/var/log/centreon-engine/service-perfdata', + 'service cbd reload', '/etc/centreon-broker', + '/usr/share/centreon/lib/centreon-broker', + '/usr/lib64/centreon-connector', + 22, '1', 5556, 'centreontrapd', '/etc/snmp/centreon_traps/', + NULL, NULL, '/var/log/centreon-broker/', NULL, '1', '0' ); INSERT IGNORE INTO `cfg_nagios` VALUES ( - 1, 'Centreon Engine Central', NULL, - '/var/log/centreon-engine/centengine.log', - '/etc/centreon-engine', '/var/log/centreon-engine/status.dat', - 60, '1', '1', '1', '1', '1', '1', '1', - 4096, '1s', '/var/lib/centreon-engine/rw/centengine.cmd', - '1', '/var/log/centreon-engine/retention.dat', - 60, '1', '1', '0', '1', '1', '1', '1', - NULL, '1', '1', NULL, NULL, NULL, 's', - 's', 's', 0, 15, 15, 5, '0', NULL, NULL, - '0', '25.0', '50.0', '25.0', '50.0', - '0', 60, 12, 30, 30, '1', '1', '0', NULL, - NULL, '0', NULL, 'euro', 30, '~!$%^&*\"|\'<>?,()=', - '`~$^&\"|\'<>', '0', '0', 'admin@localhost', - 'admin@localhost', 'Centreon Engine configuration file for a central instance', - '1', '-1', 1, '1', '1', 15, 15, NULL, - '0', 15, '/var/log/centreon-engine/centengine.debug', - 0, '0', '1', 1000000000, 'centengine.cfg', + 1, 'Centreon Engine Central', NULL, + '/var/log/centreon-engine/centengine.log', + '/etc/centreon-engine', '/var/log/centreon-engine/status.dat', + 60, '1', '1', '1', '1', '1', '1', '1', + 4096, '1s', '/var/lib/centreon-engine/rw/centengine.cmd', + '1', '/var/log/centreon-engine/retention.dat', + 60, '1', '1', '0', '1', '1', '1', '1', + NULL, '1', '1', NULL, NULL, NULL, 's', + 's', 's', 0, 15, 15, 5, '0', NULL, NULL, + '0', '25.0', '50.0', '25.0', '50.0', + '0', 60, 12, 30, 30, '1', '1', '0', NULL, + NULL, '0', NULL, 'euro', 30, '~!$%^&*\"|\'<>?,()=', + '`~$^&\"|\'<>', '0', '0', 'admin@localhost', + 'admin@localhost', 'Centreon Engine configuration file for a central instance', + '1', '-1', 1, '1', '1', 15, 15, NULL, + '0', 15, '/var/log/centreon-engine/centengine.debug', + 0, '0', '1', 1000000000, 'centengine.cfg', '1', '0', '', 'log_v2_enabled' - ), + ), ( - 15, 'pushpoller', NULL, '/var/log/centreon-engine/centengine.log', - '/etc/centreon-engine/', '/var/log/centreon-engine/status.dat', - 60, '1', '1', '1', '1', '1', '1', '1', - 4096, '1s', '/var/lib/centreon-engine/rw/centengine.cmd', - '1', '/var/log/centreon-engine/retention.dat', - 60, '1', '0', '0', '1', '1', '1', '1', - '1', '1', '1', NULL, NULL, '0.5', 's', - 's', 's', 0, 15, 15, 5, '0', 30, 180, '0', - '25.0', '50.0', '25.0', '50.0', '0', - 60, 30, 30, 30, '1', '1', '0', NULL, NULL, - '0', NULL, 'euro', 30, '~!$%^&*\"|\'<>?,()=', - '`~$^&\"|\'<>', '0', '0', 'admin@localhost', - 'admin@localhost', 'Centreon Engine config file for a polling instance', - '1', '-1', 2, '1', '1', 15, 15, NULL, - '0', 15, '/var/log/centreon-engine/centengine.debug', - 0, '0', '1', 1000000000, 'centengine.cfg', + 15, 'pushpoller', NULL, '/var/log/centreon-engine/centengine.log', + '/etc/centreon-engine/', '/var/log/centreon-engine/status.dat', + 60, '1', '1', '1', '1', '1', '1', '1', + 4096, '1s', '/var/lib/centreon-engine/rw/centengine.cmd', + '1', '/var/log/centreon-engine/retention.dat', + 60, '1', '0', '0', '1', '1', '1', '1', + '1', '1', '1', NULL, NULL, '0.5', 's', + 's', 's', 0, 15, 15, 5, '0', 30, 180, '0', + '25.0', '50.0', '25.0', '50.0', '0', + 60, 30, 30, 30, '1', '1', '0', NULL, NULL, + '0', NULL, 'euro', 30, '~!$%^&*\"|\'<>?,()=', + '`~$^&\"|\'<>', '0', '0', 'admin@localhost', + 'admin@localhost', 'Centreon Engine config file for a polling instance', + '1', '-1', 2, '1', '1', 15, 15, NULL, + '0', 15, '/var/log/centreon-engine/centengine.debug', + 0, '0', '1', 1000000000, 'centengine.cfg', '1', '0', '', 'log_v2_enabled' ); diff --git a/gorgone/tests/robot/resources/resources.resource b/gorgone/tests/robot/resources/resources.resource index 91ae77e0775..3d304ea9292 100644 --- a/gorgone/tests/robot/resources/resources.resource +++ b/gorgone/tests/robot/resources/resources.resource @@ -5,12 +5,16 @@ Library Process Library RequestsLibrary *** Variables *** -${gorgone_binary} /usr/bin/gorgoned -${ROOT_CONFIG} ${CURDIR}${/}..${/}config${/} -${pull_central_config} ${ROOT_CONFIG}pull_central_config.yaml -${pull_poller_config} ${ROOT_CONFIG}pull_poller_config.yaml -${push_central_config} ${ROOT_CONFIG}push_central_config.yaml -${push_poller_config} ${ROOT_CONFIG}push_poller_config.yaml +${gorgone_binary} /usr/bin/gorgoned +${ROOT_CONFIG} ${CURDIR}${/}..${/}config${/} +${pull_central_config} ${ROOT_CONFIG}pull_central_config.yaml +${pull_poller_config} ${ROOT_CONFIG}pull_poller_config.yaml +${pullwss_central_config} ${ROOT_CONFIG}pullwss_central_config.yaml +${pullwss_poller_config} ${ROOT_CONFIG}pullwss_poller_config.yaml +${push_central_config} ${ROOT_CONFIG}push_central_config.yaml +${push_poller_config} ${ROOT_CONFIG}push_poller_config.yaml +${gorgone_core_config} ${ROOT_CONFIG}gorgone_core_central.yaml + ${DBHOST} 127.0.0.1 ${DBPORT} 3306 ${DBNAME} centreon_gorgone_test @@ -19,12 +23,12 @@ ${DBPASSWORD} password *** Keywords *** Start Gorgone - [Arguments] ${CONFIG_FILE} ${SEVERITY} ${ALIAS} + [Arguments] ${SEVERITY} ${ALIAS} ${process} Start Process ... /usr/bin/perl ... ${gorgone_binary} ... --config - ... ${CONFIG_FILE} + ... /etc/centreon-gorgone/${ALIAS}/includer.yaml ... --logfile ... /var/log/centreon-gorgone/${ALIAS}/gorgoned.log ... --severity @@ -39,7 +43,7 @@ Stop Gorgone And Remove Gorgone Config FOR ${process} IN @{process_alias} ${result} Terminate Process ${process} - BuiltIn.Run Keyword And Continue On Failure Should Be True ${result.rc} == -15 or ${result.rc} == 0 Engine badly stopped alias = ${process_alias} - code returned ${result.rc}. + BuiltIn.Run Keyword And Continue On Failure Should Be True ${result.rc} == -15 or ${result.rc} == -9 or ${result.rc} == 0 Engine badly stopped alias = ${process} - code returned ${result.rc}. END Gorgone Execute Sql @@ -52,7 +56,7 @@ Gorgone Execute Sql END Setup Gorgone Config - [Arguments] ${gorgone_name} @{file_list} ${sql_file}= + [Arguments] @{file_list} ${gorgone_name}=gorgone_process_name ${sql_file}= Gorgone Execute Sql ${sql_file} Create Directory /var/log/centreon-gorgone/${gorgone_name}/ Copy File ${CURDIR}${/}..${/}config${/}includer.yaml /etc/centreon-gorgone/${gorgone_name}/includer.yaml @@ -63,13 +67,13 @@ Setup Gorgone Config ${key_thumbprint} Run perl /usr/local/bin/gorgone_key_thumbprint.pl --key-path=/var/lib/centreon-gorgone/.keys/rsakey.priv.pem | cut -d: -f4 ${result} Run sed -i -e 's/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/${gorgone_name}/g' /etc/centreon-gorgone/${gorgone_name}/includer.yaml - + ${CMD} Catenate - ... sed -i -e 's/@KEYTHUMBPRINT@/${key_thumbprint}/g' - ... -e 's/@DBNAME@/${DBNAME}/g' - ... -e 's/@DBHOST@/${DBHOST}/g' - ... -e 's/@DBPASSWORD@/${DBPASSWORD}/g' - ... -e 's/@DBUSER@/${DBUSER}/g' + ... sed -i -e 's/@KEYTHUMBPRINT@/${key_thumbprint}/g' + ... -e 's/@DBNAME@/${DBNAME}/g' + ... -e 's/@DBHOST@/${DBHOST}/g' + ... -e 's/@DBPASSWORD@/${DBPASSWORD}/g' + ... -e 's/@DBUSER@/${DBUSER}/g' ... -e 's/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/${gorgone_name}/g' ... /etc/centreon-gorgone/${gorgone_name}/config.d/*.yaml @@ -94,7 +98,7 @@ Check Poller Communicate [Arguments] ${poller_id} ${response} Set Variable ${EMPTY} Log To Console checking Gorgone see poller in rest api response... - FOR ${i} IN RANGE 10 + FOR ${i} IN RANGE 20 Sleep 5 ${response}= GET http://127.0.0.1:8085/api/internal/constatus Log ${response.json()} @@ -103,27 +107,47 @@ Check Poller Communicate END END Log To Console json response : ${response.json()} - Should Be True ${i} < 9 timeout waiting for poller status in gorgone rest api (/api/internal/constatus) : ${response.json()} + Should Be True ${i} < 20 timeout after ${i} time waiting for poller status in gorgone rest api (/api/internal/constatus) : ${response.json()} Should Be True 0 == ${response.json()}[data][${poller_id}][ping_failed] there was failed ping between the central and the poller ${poller_id} Should Be True 0 < ${response.json()}[data][${poller_id}][ping_ok] there was no successful ping between the central and the poller ${poller_id} Setup Two Gorgone Instances [Arguments] ${communication_mode}=push_zmq ${central_name}=gorgone_central ${poller_name}=gorgone_poller_2 - ${result} Run perl /usr/local/bin/gorgone_key_generation.pl # generate key if there is none. # gorgone can generate it's own key, but as we need the thumbprint in the configuration we need to generate them before launching gorgone. # this script only create key if the files don't exists, and silently finish if the files already exists. IF '${communication_mode}' == 'push_zmq' - Setup Gorgone Config ${central_name} ${push_central_config} sql_file=${ROOT_CONFIG}push_db_1_poller.sql - Setup Gorgone Config ${poller_name} ${push_poller_config} + Setup Gorgone Config ${push_central_config} ${gorgone_core_config} gorgone_name=${central_name} sql_file=${ROOT_CONFIG}push_db_1_poller.sql + Setup Gorgone Config ${push_poller_config} gorgone_name=${poller_name} + + Start Gorgone debug ${central_name} + Start Gorgone debug ${poller_name} - Start Gorgone /etc/centreon-gorgone/gorgone_central/includer.yaml debug ${central_name} - Start Gorgone /etc/centreon-gorgone/gorgone_poller_2/includer.yaml debug ${poller_name} - Check Poller Is Connected port=5556 expected_nb=2 Check Poller Communicate 2 + ELSE IF '${communication_mode}' == 'pullwss' + Setup Gorgone Config ${pullwss_central_config} ${ROOT_CONFIG}pullwss_node_register_one_node.yaml ${gorgone_core_config} gorgone_name=${central_name} sql_file=${ROOT_CONFIG}push_db_1_poller.sql + Setup Gorgone Config ${gorgone_core_config} ${pullwss_poller_config} gorgone_name=${poller_name} + + Start Gorgone debug ${central_name} + Wait Until Port Is Bind 8086 + Start Gorgone debug ${poller_name} + Check Poller Is Connected port=8086 expected_nb=2 + Check Poller Communicate 2 + ELSE - Fail pull and pullwss mode are not yet implemented. + Fail pull mode is not yet implemented. END +Wait Until Port Is Bind + [Arguments] ${port} + FOR ${i} IN RANGE 10 + Sleep 0.5 + ${nb_port_listening} Run ss -tlnp | grep ':${port}' | grep LIST | wc -l + IF ${nb_port_listening} == 1 + BREAK + END + END + Should Be True ${i} < 10 Gorgone did not listen on port ${port} on time. + Log To Console tcp port ${port} bind after ${i} attempt (0.5 seconds each) \ No newline at end of file diff --git a/gorgone/tests/robot/tests/core/httpserver.robot b/gorgone/tests/robot/tests/core/httpserver.robot index 53d2e61d168..7e270a81c33 100644 --- a/gorgone/tests/robot/tests/core/httpserver.robot +++ b/gorgone/tests/robot/tests/core/httpserver.robot @@ -44,8 +44,8 @@ check http api post api ${tc} *** Keywords *** Setup Gorgone - Setup Gorgone Config httpserver_api_statuscode ${push_central_config} - Start Gorgone /etc/centreon-gorgone/httpserver_api_statuscode/includer.yaml debug httpserver_api_statuscode + Setup Gorgone Config ${push_central_config} ${gorgone_core_config} gorgone_name=httpserver_api_statuscode + Start Gorgone debug httpserver_api_statuscode Log To Console \nGorgone Started. We have to wait for it to be ready to respond. Sleep 10 diff --git a/gorgone/tests/robot/tests/core/pullwss.robot b/gorgone/tests/robot/tests/core/pullwss.robot new file mode 100644 index 00000000000..f678703e770 --- /dev/null +++ b/gorgone/tests/robot/tests/core/pullwss.robot @@ -0,0 +1,16 @@ +*** Settings *** +Documentation Start and stop gorgone in pullwss mode + +Resource ${CURDIR}${/}..${/}..${/}resources${/}import.resource +Test Timeout 220s + +*** Variables *** +@{process_list} pullwss_gorgone_poller_2 pullwss_gorgone_central + +*** Test Cases *** +check one poller can connect to a central gorgone + [Teardown] Stop Gorgone And Remove Gorgone Config @{process_list} #sql_file=${ROOT_CONFIG}push_db_1_poller_delete.sql + + Log To Console \nStarting the gorgone setup + Setup Two Gorgone Instances communication_mode=pullwss central_name=pullwss_gorgone_central poller_name=pullwss_gorgone_poller_2 + Log To Console End of tests. diff --git a/gorgone/tests/robot/tests/core/push.robot b/gorgone/tests/robot/tests/core/push.robot index 7ddb758c907..5ad63f731d9 100644 --- a/gorgone/tests/robot/tests/core/push.robot +++ b/gorgone/tests/robot/tests/core/push.robot @@ -5,12 +5,12 @@ Resource ${CURDIR}${/}..${/}..${/}resources${/}import.resource Test Timeout 220s *** Variables *** -@{process_list} gorgone_central gorgone_poller_2 +@{process_list} push_zmq_gorgone_central push_zmq_gorgone_poller_2 *** Test Cases *** connect 1 poller to a central [Teardown] Stop Gorgone And Remove Gorgone Config @{process_list} sql_file=${ROOT_CONFIG}push_db_1_poller_delete.sql Log To Console \nStarting the gorgone setup - Setup Two Gorgone Instances push_zmq + Setup Two Gorgone Instances communication_mode=push_zmq central_name=push_zmq_gorgone_central poller_name=push_zmq_gorgone_poller_2 Log To Console End of tests. diff --git a/gorgone/tests/robot/tests/start_stop/start_stop.robot b/gorgone/tests/robot/tests/start_stop/start_stop.robot index 08ddb9b6f04..e00d4889a6d 100644 --- a/gorgone/tests/robot/tests/start_stop/start_stop.robot +++ b/gorgone/tests/robot/tests/start_stop/start_stop.robot @@ -3,15 +3,18 @@ Documentation Start and stop gorgone Resource ${CURDIR}${/}..${/}..${/}resources${/}import.resource Test Timeout 120s +*** Variables *** +${configfile} ${CURDIR}${/}config.yaml *** Test Cases *** Start and stop gorgone # fichier de conf : pull_central + autodiscovery # start gorgone 2 FOR ${i} IN RANGE 5 - Setup Gorgone Config gorgone_start_stop${i} ${CURDIR}${/}config.yaml + ${gorgone_name}= Set Variable gorgone_start_stop${i} + Setup Gorgone Config ${configfile} gorgone_name=${gorgone_name} Log To Console Starting Gorgone... - Start Gorgone /etc/centreon-gorgone/gorgone_start_stop${i}/includer.yaml debug gorgone_start_stop${i} + Start Gorgone debug ${gorgone_name} Sleep 5s Log To Console Stopping Gorgone... Stop Gorgone And Remove Gorgone Config gorgone_start_stop${i} From 500b2c155aede3ebb1f4beeff78449fa2a2d804a Mon Sep 17 00:00:00 2001 From: Evan-Adam <152897682+Evan-Adam@users.noreply.github.com> Date: Mon, 24 Jun 2024 11:50:07 +0200 Subject: [PATCH 841/948] doc(gorgone) add documentation for developper and pullwss connexion mode (#4113) Co-authored-by: omercier <32134301+omercier@users.noreply.github.com> Co-authored-by: cg-tw <83637804+cg-tw@users.noreply.github.com> --- gorgone/docs/modules.md | 84 +++++++++++++ .../docs/modules/centreon/autodiscovery.md | 61 +++++++--- .../centreon-gorgone-autodiscovery-archi.jpg | Bin 0 -> 136498 bytes gorgone/docs/modules/core/proxy.md | 45 +++++-- gorgone/docs/modules/core/pull.md | 3 + gorgone/docs/modules/core/pullwss.md | 41 +++++++ gorgone/docs/modules/core/register.md | 8 +- gorgone/docs/poller_pullwss_configuration.md | 114 ++++++++++++++++++ 8 files changed, 325 insertions(+), 31 deletions(-) create mode 100644 gorgone/docs/modules/centreon/centreon-gorgone-autodiscovery-archi.jpg create mode 100644 gorgone/docs/modules/core/pullwss.md create mode 100644 gorgone/docs/poller_pullwss_configuration.md diff --git a/gorgone/docs/modules.md b/gorgone/docs/modules.md index fb1f1ee940a..a24e4495499 100644 --- a/gorgone/docs/modules.md +++ b/gorgone/docs/modules.md @@ -19,3 +19,87 @@ List of the available modules: * Plugins * [Newtest](../docs/modules/plugins/newtest.md) * [Scom](../docs/modules/plugins/scom.md) + +# Module implementation + +Each module should have a hook.pm and a class.pm file with some mandatory functions implemented. + + +## hook.pm + +Mainly used for creating the module process(es) +and route events to it each time a new message is received by gorgone. + +### const EVENTS [] + +Array defining all events this module can process. Optionally add API endpoint for events. + +### const NAME + +### const NAMESPACE + +### gently() + +Called by gorgone-core when stopping the module. + +### register() + +### init() + +Called by library::loadmodule to initialize the module, it should create a child process as it's not done by gorgone-core. + +### routing() + +### kill() + +### check() + +### broadcast() + +### create_child() + +Not strictly required, but present every time, used to instantiate a new child process by the init() function.\ +Inside the child process, a class.pm object is created and the class->run method is started. + +## class.pm + +This class must inherit the module.pm package. + + +This object is most of the time a singleton (maybe every time). + + +It will be created by hook.pm when starting the module. +This is the workhorse that will process all events. + +It seems like none of these methods will be called by gorgone-core, so naming is not required to follow this convention. + +(Please keep the code base consistent if you make a new module). + + +### new() + +Class constructor + +### run() + +Will be called by hook.pm. This method should wait for events and dispatch them accordingly. + + +Uses the EV library to wait for new things to do, either by waiting on the ZMQ file descriptor (fd) + +or with a periodic timer.\ +Generally waits for new data on ZMQ socket with EV::io(), and call event() when there is. + +### event() + +Reads data from ZMQ socket, and acts on it, generally by launching an action_* method to process the event. + +module.pm parent class has an event() method, so it's not mandatory to implement it. + +### action_*() + +Method called by event() when a ZMQ message is found. + +Method name is in the `action_eventname` form where eventname is the name of the event in lowercase, as defined by the constant in hook.pm + diff --git a/gorgone/docs/modules/centreon/autodiscovery.md b/gorgone/docs/modules/centreon/autodiscovery.md index e9446458fa3..95a886f8f7b 100644 --- a/gorgone/docs/modules/centreon/autodiscovery.md +++ b/gorgone/docs/modules/centreon/autodiscovery.md @@ -7,7 +7,7 @@ This module aims to extend Centreon Autodiscovery server functionalities. ## Configuration | Directive | Description | Default value | -| :-------------- | :--------------------------------------------------------------------- | :------------ | +|:----------------|:-----------------------------------------------------------------------|:--------------| | global\_timeout | Time in seconds before a discovery command is considered timed out | `300` | | check\_interval | Time in seconds defining frequency at which results will be search for | `15` | @@ -24,7 +24,7 @@ check_interval: 10 ## Events | Event | Description | -| :----------------------- | :---------------------------------------------- | +|:-------------------------|:------------------------------------------------| | AUTODISCOVERYREADY | Internal event to notify the core | | HOSTDISCOVERYLISTENER | Internal event to get host discovery results | | SERVICEDISCOVERYLISTENER | Internal event to get service discovery results | @@ -38,20 +38,20 @@ check_interval: 10 ### Add a host discovery job | Endpoint | Method | -| :---------------------------- | :----- | +|:------------------------------|:-------| | /centreon/autodiscovery/hosts | `POST` | #### Headers | Header | Value | -| :----------- | :--------------- | +|:-------------|:-----------------| | Accept | application/json | | Content-Type | application/json | #### Body | Key | Value | -| :-------------- | :--------------------------------------------------------- | +|:----------------|:-----------------------------------------------------------| | job\_id | ID of the Host Discovery job | | target | Identifier of the target on which to execute the command | | command_line | Command line to execute to perform the discovery | @@ -62,14 +62,14 @@ check_interval: 10 With the following keys for the `execution` entry: | Key | Value | -| :--------- | :---------------------------------------------- | +|:-----------|:------------------------------------------------| | mode | Execution mode ('0': immediate, '1': scheduled) | | parameters | Parameters needed by execution mode | With the following keys for the `post_execution` entry: | Key | Value | -| :------- | :------------------------------- | +|:---------|:---------------------------------| | commands | Array of commands to be executed | ```json @@ -187,19 +187,19 @@ curl --request POST "https://hostname:8443/api/centreon/autodiscovery/hosts" \ ### Launch a host discovery job | Endpoint | Method | -| :----------------------------------------- | :----- | +|:-------------------------------------------|:-------| | /centreon/autodiscovery/hosts/:id/schedule | `GET` | #### Headers -| Header | Value | -| :----------- | :--------------- | -| Accept | application/json | +| Header | Value | +|:-------|:-----------------| +| Accept | application/json | #### Path variables | Variable | Description | -| :------- | :-------------------- | +|:---------|:----------------------| | id | Identifier of the job | #### Example @@ -212,19 +212,19 @@ curl --request GET "https://hostname:8443/api/centreon/autodiscovery/hosts/:id/s ### Delete a host discovery job | Endpoint | Method | -| :----------------------------------- | :------- | +|:-------------------------------------|:---------| | /centreon/autodiscovery/hosts/:token | `DELETE` | #### Headers | Header | Value | -| :----- | :--------------- | +|:-------|:-----------------| | Accept | application/json | #### Path variables | Variable | Description | -| :------- | :------------------------- | +|:---------|:---------------------------| | token | Token of the scheduled job | #### Example @@ -237,20 +237,20 @@ curl --request DELETE "https://hostname:8443/api/centreon/autodiscovery/hosts/di ### Execute a service discovery job | Endpoint | Method | -| :------------------------------- | :----- | +|:---------------------------------|:-------| | /centreon/autodiscovery/services | `POST` | #### Headers | Header | Value | -| :----------- | :--------------- | +|:-------------|:-----------------| | Accept | application/json | | Content-Type | application/json | #### Body | Key | Value | -| :------------------- | :------------------------------------------------------------------------------------------------ | +|:---------------------|:--------------------------------------------------------------------------------------------------| | filter\_rules | Array of rules to use for discovery (empty means all) | | force\_rule | Run disabled rules ('0': not forced, '1': forced) | | filter\_hosts | Array of hosts against which run the discovery (empty means all) | @@ -316,3 +316,28 @@ curl --request POST "https://hostname:8443/api/centreon/autodiscovery/services" \"dry_run\": 1 }" ``` + +### Developer manual + +This module heavily uses the gorgone-action module to work. + +Here is a diagram of how these modules interact: + +![image](./centreon-gorgone-autodiscovery-archi.jpg) + + +Dotted lines mean a ZMQ message is sent. Direct lines mean the function is called normally. + +Each column represents a Linux thread, as Gorgone is multiprocess. + +For each ZMQ message, names are described in the [events section](#events) of each module, +and for putlog the second part is the 'code' used by gorgone-autodiscovery +and defined as constant in the [class.pm](../../../gorgone/modules/centreon/autodiscovery/class.pm) file. + +The gorgone-action module does not send the result directly to the calling module. It sends a putlog message instead, processed by core. + +Core keeps track of every module waiting for a particular event (use library.pm::addlistener to show interest in an event) +and dispatch another message to the waiting module. + + +gorgone-core also stores the log in a local sqlite database. diff --git a/gorgone/docs/modules/centreon/centreon-gorgone-autodiscovery-archi.jpg b/gorgone/docs/modules/centreon/centreon-gorgone-autodiscovery-archi.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4ee3f7e322d8e163dc62cd53af7ecc1f3b6dfe8b GIT binary patch literal 136498 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%0D(1UaU|fzF%Hc9JFrBGi zXS@Rwew1s|qPw3euQ#sQrdXX?U4M^zSsw7>W8!<#dccEF2aQt3c%S&7E12yw;@?#0 zVt0KWPQ_;@ZyM6i6;AYvr+IGjKj#1XMH(t*^0e=%p(msZ$N^oyh}R|rXF;xLh%)`Y zh;++kaXQCidH=%DuRruE6+YIdl+WC@jTM&ab(l=igYC&nK~z^^7(cD?eWYq3pa9<7 zv5E$*uvpB}Ht6Zbu$gL7GMD*T#8zC<!`0gH#e|3rW!%(V<Cnh&AZ{>PWoNAlqJT9n zAu8j}QVs&V>J_DM#hzx$8X6gUe}Nce!x6mI>8^zgROzV_)RLNPan@?m?di-{X&VoZ z(D<xm%#SLfI1s!fT2IizBU`VS!byuw9cZkME7>@91FaAEQ99d_567`p-FF-Gpbr3D zg0dRWvEpaWWyc;+SfqMqc<tSc{0H}5A6qc)6sW-uRW{{%rLw?;zw%vVpp+&FS<Oj! z4Gkt*AsOvg_L+fCM?Dj>k7mE)f5jSV=Ndm7u?WU(nmkEFzu$1@YSfh-^nKPtKGDu5 zZ@#O&;%wMk*Ge0M4!9NrpF*noVs+%YUtv_F9nWm0IY(_dKry|wT{$0={}N+M^BXRF zVbHBzs-t!MXp|FD!9NcPLUlY_Z;tID)v!~{@-OR(t)1u=rTr>`;_?^-@AcgIZ7w{t zlo=$x{TE2xD0)#yC&F|>k!-bs!9EB7<{MJ0xp~5AXULnKY5c>bPhjV>GvO?2u<OkN z?GxGAZzQPvfT<JmT8gOr5UfwvwO<Le7<>6pdHTsRt0@^j9qsS%+q-<x*jY=~0`U!L z4JHlyPz$3V>S7SqOx49RX^sWc34nGXAq2tIEM3>=VcuIz1w~!AWeK0}x4YT-==P%z zb<<=)Wb?|H<qk|w4Aya(2B}QwL6ri9zru8B=1feu9(G6<54V0480dfXH8=W2C04*E zu|So*KN}+7HiyP(G!Q~oG~cvdL!{31%s(Z_#<+f7L|bwcHo|$oM^4Xu6VDaVZGpU2 z|3ASt{uF5bOMv=+t*!ar3-kY0`R(6!;r`p-^dFKBldg+n)Q&_FjzS6IkzP#|tai*} zKm<DV1dIZl5!x~(!}K5h!ViBI7u4UYwtw(L;B2yq@{MNP*8!5L&9>^c*cx%VZ9a4$ zOR_dTC*~KWR|4av8{+kCahH7--RJ@9y5<Z+xT9|>W-(jpV%2ryiyoTIS0Gqv-ETJM zqbjJX>2uL;rOE<8x$~T3+)Ee=&9t!g!Jf{Ed2X*q!_$#N>tv~-gKbuY&y^+az2JHt zb1Ll6PauT@m0*w%r3aQd6MEFTGfIy#=t0h_*3YP)gO0)oovt{S#O|B^`}{*g9Jdx^ zgS=rh=M2Cg);!EhnHpi+Ya%`9{}~Zs91Vt6&_SN)(??U!_bzmZO(Jhb6~?WSndn!B zzTLgi&en#{Bia`Lhh48ng9AhaL$JDRj@mfUK#lYE_^l(qaci#9M6{nsAx~Y3b6Li{ zQ-%J6KE4dmm0XI5?Pa{qk6)t~<`4|qIGZ<tG;P5mNY2La+Ld=xuF^AgaumC-^|gJf zIzCAiWyorMzjs-i!{Qz3q|E-D=2L=Zmnf{~HF&f(3wTP$>B6USr}W#$RdJ?vJH(<> zvbqYU&KmDm+8leD;l_$3QTdV|!u6=+VLMI5--uR~Ktl<D>hzIoeE#vXdGBZY^Gh)g zW@{xBrPP>Oi$BjVTx<^JPj?Gm;@x#6l^CeQ)0+0QRrmJ%1riD7I9+{&5~N3=fKLyM zRyvT<F{6N;cck${u<1U^Kc2k#P{jJl2VYll483{>Yf7>kWXsEZ?ndYKJ;FvrO$8eV zl{lN@Ipd7;+s!4{BTo}v`^ePahnkt|<?odOlWjAP&0U8`<^ehE!=@Eic3{5SL^H-o z07V#$GJFo|+9u4hS!dzF!&jr#>-9K>>*DqKc*v-F=okd_puE7ZQ^qYAtd>jvDM2{_ zdE+?aI8217?Bx--^5*kk;WP9`P1<XTY<%3U2ZvAz$_$;#^0}1uw-E2wt~>jyqEub- zefZ_>?-NKN+}T|&%dV4W+}!c-`KInkTvfVrg<#4AX34q^WgFWe5^bQ$IlfN@xuwH+ z#MV|lCc0A_7~`&6YOe&vgGN4P_tEI}Z^mXoz!5x4{&!x|y1CTtH`Xz)_UEn}CJa4d zr7+ApAHY;0RVL0w5N)_8?5G$%!drq($(>K3PP<}{@9_FdVg{BN-T6!l+#kUF`lY1E zN91U7*`HH?CAjV!7n%VaYM?zyU}W7fW~2hgSMp;x&v?EI6aLZN)A|1MEw=#^ap~)B zO-3(b8J?=Jg3as!e2D5Exj+^qBTh#jEA|n$sH{7os9WeLFB4w&nZsXc-6z;o+oCCT z0^=ROj+MLBnBdscBjEX>{mRe4Ig`IY-~{j3Dx$XWIXA+#hvi&;94RUNBF)XgN5lRy zJaB_vpvGfyagY;!;npFYVv^G!L}vv$XL(NNtc(?nAPNQk!5lMPIkWtutEr0EwhKdv zzCYDaTtD#kPuN4AHjg$p`9jd+ftKPs2_%l(lz$+~HUb@y>@|S)QN@mbt6>r<|5VL3 z+sAfyc}r3Pg!`5%wCYirgVv)~EQINv2G1Ni9@P1_F`{sOZQ)F;i_<4F_9Mqsc%K1I z%ES4UkVHSk4xhhY=LV5dVhiZz1A7-dN9oj7{V(4FxyW2?@qtx8bt!ub<}~?!OxRc0 zUzAvoL{lDK1o^xi_Zt%552~=gHs1mJ*(jcEf>xRy1Q!~P?$<8)xHSK}`{=xbnGKzp zeas2mu<rD(Tsrlj%lMGM{QFGg1DHe5YgC7LcnsbZD;}oAELeBa30OXedC0O)ku*y} zF86L>Ui;^Q9VzNRqs9KOoIfDZH=J4-_sm>;xyg@k68+wgXbGu&^0*74#n!&cuSf9) z;Rm{oxtDX13xo1l#D;24_jT+%nBjdqGW`5}(71)!dp2tASsychdfJeo@Qspu5f?UX z$YDjiIVvKmS>TJgG58!rx`_NNfif1ZoujC2#CN&)3~MJBc)Pu;eA$v7KU^H%n5VB1 z=}@`DX8_{58SUuJw_Y!W;0Eu|-l|O-|CMj+Do_+DFtNwHv_-|kxqB^n#4T#0_p+Ai zwQf~u$gtm0U2S~N!N_cf$#R(2F0A_`F#MOJ?iG`E+z4~c`D9By>}5lumC?^W7i$A; z%7rg(1C&?Eb55NA=B+tUEP_RK(cCy%)ft!3f$6r?d}U#udfn2Gnw*#&Y_eMBEAC5K zJ!n_5&?$wGeO7iFg^YgL%wQp2J(o$)t9N#Cq|<h_YQx8WN>qI0!0@~cQ+FluVG=pY zo(Luw4O^m1AWtX3+dAjMqN$WQQ5JYp_<X@0G(yYYJg!T<uxgTE*dTlI+T^#7Q|5QC z+YHSCMifKWYl9#~<>!kz(4PvXAQmgkg1~%os`Yi&OEmCdP1RR9c20T4O`6{g;)qIF zG`|xy^hRUjJJAn%<ShxiD7>;hfOf>&pdV*D-#^sXVHO_udRfV_UVD1C%h}|+dDxA+ zzRT7JNc7t8%=%oY83DY<EPz0eU)*CTFLPc@o<f&l>Udpr^<8g0x&N_b_eHy)G@qn< z_tb4{z`8@U9SpQWK<~5JVZ1-CfQW5*f_J{3W2yOq9p;L>*V<y%r%bOS;|*KW(dq?0 zZB##V^}|VWZj;JB2q&@)^@JTn+%ws?-WlIhRK|)!w_k@9W_3(3`|j_Qd9und4B>t% zGE{4~T$z`!%BGC&1>IIi8=KCm6y<D7RI!%=phPVt=^SG-#&i06wUefL+NKZXgSDPL zKYQ0T)3@7TEAYUj^h&HAMHx~8hybIX7-guU0zs-XZ__h@(f+H0cZ?TnyjA=YY6kU- zLE9fV86;^hvGS8{>|Z)xT)OfVU%OOGNJg9VM)09r8D&0IGJQ~cFx`{*dTfv^_f&1f zhW>qrWPy8g%uNj*H;61!>eP#PgYZSQ2#`WQ=OaL)#ANI}3-CvAUIh*;I%~%MRq(KS zRI49Ln8nIYgYEL^*RXRFNEd#2cN4430EA=E=yP%cDiZ8*v=_&ou{<0X!M5ppS$Mr( z@|<+wy)MC28XI5~>gS&B(V(If0$38DAb*58fE(e1vkg<9*yI+Rd@;4lyd}Ze&-s~^ zKl&2M?5$h;R-6nswJ3D|#2os+&@K3X6cGIR(f^P7+9&N-c7|dyS4P_duMkD1z|;=H z<rkC+sM;`U8>?yH%Xw^Si$AY$inkDJBGvJ_5WJ?kpmj?>2>P-03UIKDOG7JDA@A2r zey>D<RZhnew4*RNf-hSp@loW;S85Wz*bo)2r~euqk)-4NCH)=Iuj>*V2nDl8Qu7)U z3|FLj1}<G?jLmkVfgl%GbiX-j)B+yZGI5-8mn#zpNF7Js!L|s_n^ZT|lHN_$P8Z8h zct}1@k7I$?%S`EO^;L!|UxQpYsyHP>-kkYq<ms%|yL9L6#&fw@n}hCh8PMFZPh70| zU?xnJg?YR$TqZW*y@d6xTrxt%j_!(?sp?GWj?1s%SB#EjP+ElRqnW(PpTo=>PwL?@ z#brgoBk)ky+j1Y@KP$C*8+j#B<;pozKz}P=^|YcsffRfa!MdLd&hFJ-N`ByPVOxpV z^Bi=fe)de$(!rQm;!AHLY?mtCka+ht$!Ah1HetsM*V`~%-g*<!bHw9Xfbh`77ah5@ zF&F#g(QvPuW|~Is<vgfPc%_g_c{Jg2dEGE==qlv3cg(2}Q5nCsx{W(FC2&`WW#JUy zhowthiQA7I_0?p)r)f}L(J(GsRX?C-knW?;zDcsKOda@gpZU&6nHM3XN3N2}pAVg( z9Hw0D1=PY9>Fkar7cdK&o+s8H{1O)-nsv!;hLX*#77)_0+_TtocJKyb*zZ~rZD}Q# zhZEu07RN`~Tl2dtCFQzTW>HomRO}DgS!X2JMXu#@KU#&bc(%<|&0^21{c#oHOHu^4 zWu(_H{5#BvkzGc3dEMP9xR<nSg*TsD<S46cP-Daa>*p(wRx!YjAimlgv5u$c2_#3D zcb>p7BER7Na5VSLWP|4TlZh?iNRqmi@zuH{(Ac2UTtaK5$0G&O2i`E2u**N}j$7G) z27<UcfvjVC;;_rWBrI~Od}V6GH0HC}(s;g}t+@>UJ?(rO1^!3vkc6(~ucIA*fv^CX zMg)pTnKqW^rjDYD$@cmV5`!*D!v&g!gV)k=ayopf)Z&fW3sIJ|5~<WAV+h+LBYgOq zRd7896t9}I0Lk4M-c=`48Rcc}_Uq64Jd36KaH$G@h&*~tSK$<TekUt|#Sh7EDVw>l z;<0UHNTGb<H2|y*c4X{_Hj7%4XBIjr@eqDZXXm?LzWTc;oQ7R11?2+cd#geby9?nQ zI0wJvWiOf=`}YWj(anzK_8#ixfwr5djFkEi_4?_(i^s`xHL?Wlwjom6jENg@U1DZ~ z5TUMwxsu*}$NcT=i$Dl2kP%pYFtsQCf&hzBv|!G3x1g?5w)FEu<bu5ai@L02)1oh5 ztqva|M>B#LR|0p`z=osaY;r=tM_h~RV}f~TsWkA(ZY@egsJ?EZyyl=&ivBH6{E<yZ zvO=oVThi;0qlz$PnxD!ce}T{t6+mvCq2a8&VYnwyv#niGpjnT|XC>EA2fcOfzS&jb zGW*v9;QvA)|6lSef0G>HA4TI;gwB=Q)gjOrUHId^BM*k2g(5XoU^fFWFubno8`u@3 z;R+PECk=N672dtvLJi~lwPu3z$J9}ITRt62Ly}!1HdP58Vm&WLQ-v-<{6+EeM=pSG zF7X0h6Rb5y29V+-qMeWemM#gdzIh$))`Gc_gQ7UK8%)OhzJjlSr3a%)a9I_F)8&X( zk}0IM1%X2k6qoEZ8yC)J!ns~MOR`Kkzu;AQ8U4K?Bl6xv@R#-g@FCUw3S`=0l{<%} zK!()La(MO?kPH_(`v<luvv0gC|9t*XaTt6Kc0z0R4nK{iHz_%8>~jV^Z2@@j=8y2s z5$G&6EHt>hS*UP<qlt^$`NH_mZR7s8=`ZUfertUnzAo|wx(_?;y~>VigCa>kgRpg+ z?8lWr=pv#Tj*?B4|B0~@|BPwB7u3zvZ!SX@RY>+U=xp^b5XAQjg$bR3VHvi6(Dvrq zXcVu541-G*AmQ$kTw=bd<{4_;H}iZx6lTA;p_3!!jhr1_x((ve@M<fq`786#2>FOk z7?~MD7hW&tEl-Uh%io--PUey9cCtK3?9X^?`~<e7d>pSL;~KyXP&^`}M07c;wV%Tp z2{vgi&LUo$HGSrfYJ;LkqJ@)M9e5b;UoIgz?L8;-k-=f0HknF#^be|BbpSi!@#Hz9 zIXHf<d>VBLr>;YXa%U(?rOBp|OeLG0-xWFF7#kd~M0EITcGSUWy`Z5=mmBffX>Kk% zo@_N#nv-R=f_JT>zxA&1B}*4neO7J`1V`9miFgN3o4+ayjl@K}0kaagVX*~_(e|YM z+tz$X5(d&Mh62c`yMphS3`ma?&Q{-EPF22i&J5;lLHL#pQ5s)Z1a{DXgSVs+=L|bq zPG5IDb)oYw^2w4#4$mE$D}w`Ew%-8_K1m31QsUf#Q6ibVuy@2(7Qg+FvSUstG8uUg zI>=O+FrX=DuWhXt2<Fzo@ttk~{8qR$dkkRowNO$ZV7oiRtf8iI_R}PfzsAf3e)4PO z_kVv5)wy<~l;W@R&Lk4RNEDg;gHbpP*kj?kfDRHIx#l=_jPh=q)X?(L!qwA<`|?h| zGppBI1Hj=mT@Hqk{eqEyL*52y>%fie&evugO_gIdW1_H!wI9+ir3zcZ-o-zdNE*mJ z|8Gkeob}^&`GnX~kPFXEfj^s#k#6#^x2(|lboV5#^r9B^qh~3Hk0>+ro*F^_I_Woz zh(dtzr$0D+nfS@h)Es=qZ*b&%*=1@1=L2lh+ji?!kFu6L=MLp;hmn7bE~@RK5@^s! z^A+&;jO4*Toellj6yjr<JDh8xl;wC^l2OE<NBKHudZ9py>VI3mqz1>>{U9$Gn-NlD zN)d`Z1q&(O>l3}tGarQg;xM9o%bM39yq-rdo%`i0?5}hFq!Q4@S3meGu1pSF6eg;S zl>q^hf(O!$c3^^@I4Tu5?-sJF(7ai_?lPM38{Ae*t^t%tmMJ8M1%5*hP+KXfxU}g* zzSpaosZ!%FKl92;(5Ngn;PGv#`|ukfe_g)hW&7x5$d;1iS0L^)0){?*E;6w@To~UX zn@+VQ`<4Exg!(JP;d{LHs!v}i|F=mmw#4UPZT{GK%15qf-ODC{osJ+!QRlne%s6;9 z{LZ!kza5qW<KsJNJG7W1a|mv$8%snwK>RZxi^h(8#7A@(K4+ZRqgjj(k$q!#;#K;+ z??I(0E3x2}&AZg66n|a7G^YPNy8Z!=uJ7!ce?297b=m*NhBSXe<oX|rf2#T6znM+i z+5fG;nY6#n4i=fMgcn9CjGuGCpuNS2)gCv%#x%=i#PPG}Rez<Em7z*C=?8md4(}hY z++1jQ+%ZMkr$}?EgLLEO#iNOuD7@kcbJ1!7BZ5>fLT#eLviZ~?ndUI&$8*!7N1SCZ z2ajIG4zS)#iNfTv6&(!$`78TmKiaOGUj-aHG4XF!mm^M=<B7LF*(EM$`hMcL_Oc;* z;J_#KUIUbD=*ID-D+>IY<RbJz)Mq}ca=TD`)2iS{@%Uy<teL~sHhPI9Sb^)t(g6)G z)3l<xyRDa`>7A+f^_Ta8-2ks>217^>M7mW78AzxmT8;lSu2`D{6W_=e?9`R^lEYM6 zCT{qW<r*KA{wm9j`&y5gl(c|>y{lZxJFBNU#K%A_E(e;x2r&OZc=TjYW7e{U(}HV< z<;9^&kF`YhYN@_6Hzs7dinGWrnXvJ^sI1b8hg<_q0N(?n*orGey;+`0{F7D+z<iYp zik_7vAK0w*zmGXrfJv;re@2;?D%V0LMJ1B|{cKKWk3^@<E)b*kXy)g}1V7;HTmTj@ zm_hFuW{GD%PzMVb(GO|oI>d5RbTv$@J4B^~d%c4p*S|98bwvjZMtll1;KzXY{ipcs zl(M-6AOt-de1~D(Ph(od>$)gUQ=AU29yLc=J)gSNqu$j=w+~q-ni9~sim-D@IJV67 z6pDV%3?6nd@QIq$5`>yj28~J2n)CD?%X3s>sk0dM!RQt5>P{-fN5x)@C7&s-W5qG6 zxWH(l7uQVI3RTlGxdS)PjG|KNOzWjtnw}Jz^bFQog}4_LM@|H7cZW_bUd&ioS738u zUZNI-GB#&kcvUEZNLB!9!prgza{5&Sp-vq`oJNiXcAlz9+qqmMu3Xe6)iEkGu`kV` zd-_U`D(hRC@1*0cwA(rC1!td#YJcQsvPH*bc$GNp*t1%SA}kx~CV5j$&s446nhv|) zI((IF{9bOKFPbl`XBsknR-|ZN259!=kba+(X?ff;aN7Gy?Tf0VVx+jUp?2FlG%wGE zr;F@2HTo95xWBzdI^Y`UtQ;Lcf#;4P81}Yu;6LT(fk$w=NpD=U#3#G)1$D2&en(EJ z4qJbPflaqEnkkY!zIdAWq=0QQH<F#UOG;OfHVr^8t}Qf>MNE;&A1pCtF`Cu2;uph) zb5r(hTH>E1-QE+tL0=|YaoghXgDYha_uQY7E`QUL`_EaCe}QQ1fDs-4wjr3Qf3w<l zcE9#MMDg}tAVRtX=y^XV1ql4Pij#-@GWq+2Z~wl^863X9)q>~$@Pq!htv>(3P7YG4 zt|dx*TdWIF*&laDYS#lWH!$<i4wTnAAAbE+2a-nFuDn++Xjah>fLy&wM-pa>SH^nh z_<UM6L0AsvXc5Fdu2RXKI(Hn@`eAQ1)^{Y+G@I!jaP~BvKYb<!kwl7~N)oB@F8Rd{ zCPG2Z{nH|1HR+<R<$JEBWj{~T+2Jt?j_@dMD!4sYl-*UWRX#}h6$nX6Q2;2ztX@yg zcVxe!MAcSbJ6kGJ)3n|~;4b!2Fqs<=sE)tAUT<$fCtq^U()!v{@5DDM>;bT|bfwFP zmq(NO6U{>2u1?Kr;CtYikI(DHvxVIf|Ky$UEp4GJjNwg&BHtHXX;&K-RW9Y84+WAn zi-X-;t=#yL{Gvs(Z;SSCo4Kz}rJagR;B+PJJjXXyA80sn)xD&X{rrqf4|206XHFc8 zA?gzXyIq-OMp^=Ky7Y61Xv}!I%kIuDe{Bsac+sX#`+O8J-cPE-nbh^bS!jTSBa*_| z6|ad6tN5lgij8ZV3lMH5v=p6yWB041qQ>|5S^vm>-Lrt5b%}Mie1{d16Wxvq5<7OY z^|)M9NBO5IphI%LE>lFL7l)hGluN**kL3FeGC}*Q{u_z>s#!1Xrqi{^{g*+kMaFT@ zW5kv-sUCaiNh$lz(YRw{=xeK!cf86s4$N)~F~55FiPh--SlS|ktk1QpBq2xcfE+kd zT{(24p;pdj`(iSnaUL9PL9;{f)nDv>VSUOk$8g>Gu$Q54Xs<g)fq}&K0YHWi(W=3p z=Qyrp8jBDmuuVCX_NlY_!yeSH@3e>N*z><?68V8!qI>Z1TNvLhn?4GO{PSUqVdsp$ z>5*UKq@q-X*}SQJT4O@Ap#kAmnhwg~K=hIIR34(sI($g$=@TZZU`hyPvXuo<()GGE zu?*=t3sRCw5~v)mmjKQ`m7&XapE{5GGgwk}PErO&s68aZu3qcsm*^Rz?hCXI3}q)v zh#U<@!amJyu^+|xf9#TpXqomL6jrFS&6uHL6Ejfo)ASM8-8H%R0`gns8N?=F#Jrl? z{DK#V^Flo$m^e0$MwR{Cy^~E@u^NmS>F1~GX>)CDsrNqH4j#U(7sJ4NRrfvV;(u1O z@IOBHUs*xO2^vVt5yc3z(Vy^!VTzAYCqwiOj4h3rXIBq)Tr)a1L*w62!c*)3%O;Mr zSHvljMLlwLRP;}(Q6>lO%eAeN;JS`0&-ZoVNv4}$Xzr(ClieAVsad}2+%BjEDZa}7 z^Iwm9a^+Y>Q|vwj&^IFV#?%T-{?xh9;}3t-Xt&rUi~r~gdwF5!ZgJ;l5nTv_O710T z7|7G&4_{w}#}hRj-DXN>Uwd`uCI*+d#EjfKwB+d#(CK@q@=_H-`Z!3U`Ozwr7LZ)( z-0sRq_=cPMrJ%z--JH&UD7I%!ugXoY&5`xa<<_GQNfcnimI|G@S$EOgGxL$;W`l9X zk*_L;T2EE^`Q3x}zH#0N?!uD@U4bmhp3@jUtOd3C4O*^O;gK`z7`ShWnHq#}zl7g8 zWI(>;df!z#b%^nwtE2xRA<O^v2Kdk39RKs@{wuZQFjwM01Iqw^rAbpT(Oxd&m{RpM z+&;ysDXUSF!OOcx`!ekn=39`rBt?IENst^uccVVpn~Zir->@5?@SVDm-iF*arWN;T zisqp6!9UU}f>BA`F4)Vz!vwtat2sYhAi=SV@64XU0mF`s_Ty!!4S{+|U_yxtEnDbX zF;^phbzG)@U$Ex=!h@tC%eo}i@jOF=gVEtJI_fOtBL$#04~Wq>6Iz+BMTI-n(r7Jj z8lW<)ZX${vxU#5y7X4sX9mL>#oB1Z{f<PDdgG{<u&?1}@Ng{-X(g4A*Xl}}}9Pqgk zp=b{U<Kt(D7;QLsY@0pbq7rzQS{L_<#9OsSFl(dgCf(;7-^VCE5g*~71(1QyS07v& zc%RZDUiq`1cC)l$xantz>V<P>brbV<%@pouDJ{A)HOFzFMMv7#?cz=)KYCI^ZnnY# zMwVHKYWTKx9|BRbe#%Qnt>gSsLTAQV_gZaDD{1z`S-A{_tGs$+<RDegd|7_t+WeXp z6KJiIziJ*Dzy=8ABYb*t*pM_U8(*iCcz{7r@EY-DJLAqf4AGFmfkLxEa_N~SHJA*7 z$8E#jTSx4QY{K5N>XG}P_vhw8y;EcW3qX6W9B#-SU;G)N+#vzzo7Xqj)upw2V9aZK za(6ehP><Lw5c6I&uN0e*#TP!42g%6jep_9=lMss8$Igb75fxVN1gZlG*0?(lm-Xv{ z{r2m&^>hn5r2BTBY1?x~e~|1l-8dMrBmEX=+aK@;NY^mZ>HG#FcoM6V7A<qHWxOVV z1CfT7WzbzS>EP<Z8*e^RwRS2zDyCN~J!RHBz}zh$jRFaN&{bUwA5QiSfn#Mh%oD~A zcxcO$fhBh^dHwu{53OzW%!A{xet!>%Z0L2~GmKFru{XVIk||bQg%z{BQ?}Y-iLFRz zrU^Wi$80IxnU;2CDO>Jh`*nJ4mB4>&NWQ8QGr%B?l5}P64#2z>$Tuauk|hUu0YAM? zG?NVwPBS>m8A0W3KSJceXn+@6^7m}fn3Ua585se^!=uS8??j6tkhF)YC{L}9Llv&_ zkkeZL*ohJ+5+IA%)|zmx9fsm!U$>%G;bjIGA#lk&QWQQiP5)k>q;8FJ++E9ex;=8F z(<TG)X{(8dIp++RXoEVPa}$|CW5NL8t)o`HahWoB=ZSbs%}n;Oc3oT8{*~lgm%~Lq zf~iqf&LlDjLNi*~$~;cK%kk_Ze(+glhYt*&svLo&&vQ`Xh<Cl*vitMZoUrUiuI{po z_tX8Ex{^9hMv*A$aL5MHePymzJXRkm8lgFXv<1l&J&jr0K>TP|hVyxDqbh!AOx@DE zgx9RAQm67#_7W>oog|&jyb5;;Ui?S{h+FZ8w+Gys31FYMKNlZA=drIg--`0Lo7Dpx zlqm(?5q=k67ZgYBt6feS3h*zyIYn|@_V*SOt}s3y`#vjx?|MBP<H&rJxI9=y6)Gn& zueFyrG;WKC%f5Z(Hmd+DY052#OEFFy`oqV>E48+K^TfZW(&I=}o};87YF|uN>bG7A zot1y&ZtW|#QuiP`QjI_H!7c<lVmLFtx3VS}S3193no#)diFFHKSFq=rxSb)gPhZ{3 z8^L!mAM&aQLIWlNuxGT}536QsL6KDbj95T-k(!rh;+?ggp1>PSFR2qMx$4RvGR5Dd zf!O^oyzTx&1&_Z*U;nVhXz0<0iDvz>Z5TyO&PAYHnR*))AlDj?HuS$Eo;sk9HS=Te zr5&iUJS}*m0wetqSe3JzzXF?2z#ex6#s=`W5K=Qv4)PG4EPkh2d=`S=3c{jmb6<4c zyj*JW{c>zCBq*Q&T>x||S}Rq`#fq-scT#b;YFu|}jd|0kPDg9@2dTpq?gz3wzeXxK zhz?}5z>k!CS|aLHH_W)$EEjFXV1LFrtqu_5C^GJ~aAXZ0w|u@}x=4x>nR(6~-HQ{* zsvXH5@QTX3Ys~V5)Mcf3J)wGorcX3(X^P}Emr&33)$6~ank`(#Bdk#L-T8smU;3}q zn=knn1sGXLVDy1Tf(l#ea}FRA8t!E#dyI3=8mab>JyU*S4lQuB<u?3or#K<-V`0y{ zWmkn^=?9G7rV9VlK$%`-x7_nczw`lyp|3HQ!-q^)+CS%(i>i~$Q7+Zkrjmp%n{KJj zDwQu%L7+Zfl;c#?K&!Cx{BT#{0L>FWk<&dJ4H>G!FL!-IN`2WxwJ|#Fd1a=fHc#>Z ze?HS&2W@<+>xtqT8?Vmx9U!U4DnN`(T0~wY5M}G^QB;-n(q=4GeHt=ff5SI_=QsKl zm8DND<avZPAsR8@|I(G!bPziy+U0JyJm*lQF2-`4W51lrM(zFXT&!7-+&V4%To8@T zGSq1SGHpFd7lzw(r&-XO>~p;-zuBSEQLp6`()RuIE}|MKx{OPk?=U5F!4({{?!4P( zlnt#C`%J#rp+5|>9pQLu$^p*CPkG%~{T}^oMnvb5Ic>iSUyR22;M1h*Zf(t{4~bv< zENhAgUbrABk!Cr9ER1@q(RsgXULnh7-T5Tob1O`1W<;@~Z_Kv<KHjj<=7$LqXc<%D z9k#~)C=K8>o`1HvQ)HB0BarB&BD;4v+wN4!Z6WyE;;stI#|h-iY{?v>%pT?gz#Hz) zX(D%IQkn=4JKMnctc9y_6)MM6Tp>xf#<oR$1#F*Ax&{c&Eo{q4xlaC4$De4_xvvFi zgbedOj~iJ#1(%EH-Wvk5qc~LElE5&6+0eC??;8Fn67z)w<@V%IbM7$wJ97^>8!fcS zt5*o`PP&$EqSDY+rjfYuwyJ5X=x5^lN|S9OlB~Pn6@<v89u%h`>O`RB%QO85vLhSD z4VeKsU&JFOWU3Q{52U&68GgF>h%X)GUUKz>MphdmUJJt_Q@v;9{jSdlI$yFf)1zq$ zF3M0E{UCR>PR>t{?1yI#Yf)^`j36Pbxs@wvL6bS|EVYFHF*eZmDC@Vy+g^KJ+;%s_ z#u(v>b5dfP_MBgis`HT_9-9Ao_6VtzSoXo!JTdoBD=punA>g<c0{6>Bs%H$M$Bl^h zs_Pmi=N?u{*}-|Yf}9ta6vvN{*aou7CQOjH+CMk%-?MoS&V<0-E4M3yyY}DL^EnCL z_+O@2vrFO6lT04b-6Gtbi1Y%kmn;Bvmxy=7<H~WU@t%xgent=vAzHSs@6jfvWcd3s zv~70nJL$rY{~q^{b_P0lPmApb&;Sm-UFTQForszQ-evf->AmT4>CL^;_{8GAqg`q0 zs*5&JmoV+YUk0A07(u{lQpr9SVBXzIK3%gP#eHkmRRCyObtO4Vct%j)UCf$Eu_pf_ zg3Y+jFVP%%^cIC-d(gGvQH52Z5XS~aD>E13*J_tL5+buD2svG+qv6~FQvrN-hO@IQ z8>$3FRhmYD(9GnAjiz-?;lj6)WD<EFt<mL<k*og&5~m2Zo1?n|?f`s*if$!=G3hpH zqFnt9Qqu!xI;`R7Xo1t_(9;$xNGvewZZ6yiaW^8)y$jRp%jMFjCEPg13H8Lunc)+n zL3yR{WNE9Sh!VRca+dkI5#HVl?k)ONWm4+vwY#!MZVz|6NDYtlf!b9j!bo}v;uv=n zeF34bH>D(Ke^L=Iqx0BV?os>`n`|7|VdX@;&CkdYX)^X8@%;#s)xpJgB%tpFeXXf$ z>@tgpr+I9E=pVA>0^~jXaM~N>Rj~D+IZd|ObhGC`W->JU=@9vCF>e39PP#mmm$oA9 zELW=tN-bqTpjiu0Nf5lP2wI>3B^`1{M@te@SL@_CUpee^eb6iT$V2J0N+n`pqb^{e zPI3YAdUWR)2fGXwdBvSuZxEkMC2ikpAe6_u$|$p&Va&2FkD4ZtH)T9>i+z3(Zgh@u z=T{A45YL!zzg58j;KABsP0R3y9EJ*+vb(OFV58x4*km&ZJUAK2BrVL-c53BJWwb8- z#@X*Ky7%O_)H5cxF<}U;W~VuWlK2Ah6*FeE3r+=PQ;zXE)$u{gaZ65@+n%HEeK6I? z=8o>dZ2m~yw05WccoZbSUp=&reG9PyIJ(T)o3G=SJLEUe%Ur-=cfd`;ayyM*m#cHB zV_HT>rfJCOo!xS93ESRFS09QjW`|0x(Q@^_B-vc9O3lZQ8l4q1>l7oe0Uyf{$LfLM zN}R@CW`<_<(`%*`b>@nR95E3-2IztW+MRm`jo1KOkWX7}$Wrg>{@js{EbDrcd1R7I z^DEhDU-G!myN~@YxLwMHrh6`f>w{?(|8mQm0_5HGcIPm-fKgPkK9HHe?7+fOgwE!P z8I;(meEa_A{O3KBqidx&qv;8<v+>K9AjhtA=H2JCGp%>Pl)frpO33eOqeC)Ttn5=S zU<SVcN!!NJRC6{{iicMwo03L~skBF)9;=7ZQ;v~to&4R2^Z);wbX@s+6r&^|*kw|L zY3FNHWbH61{8aQf`q#y28DHdY*sdJhm8r2m;w9rT$S+hWIoM$1e@nCVFEg@%bY8gw z#gS)`r<E@wqGvdK!#ZW7aCbB{-q%gk+dJ8YYd(|s%@(~u!3GJsoJ|f`VA~>+p2Kq; zo{Kih4a8iVRWvJcRK07<ZPeEA{$7A(QYPH}!^#WN+?kq8Ge527ao56@=%o`DRLV|; zO?CZKi(t!aa_yHc2v6%5z8Bdc>*p{cKVA;aR_i!=*?7y@zZ}fCZ?V^*{7arML_R6i zI+m-3OT>}NemmZIyr=R92)XA^fzE$2)%9N~f%WHr^WPS){=MkyZ)eK<dq3s>o&WPS z{AZEvf8m`&%F*2_jrR$XHdchMwlUi+aZd1f#_OmVwndN3vBfhzsH*zRk0(*gfOJot z%OE+%k)jmwG6^wYXB2*IdV6!skwyMjse$C;N4GJN-K%kh<kwYIe4wJh?}rdkU4kjr zt0Hoj$mK=g>m1vj^<H*HO%%+C&Kx)fUSB(W%y2rVSYPPhZt3JQ*O>&l@k5!tl?Et* zPyHIDMw;MlDnI?G(|j~lAtJMF8Yxmnk)FPu$S=&P)}JZXCBC<u^KRwBbtQQueN5*} z>+Q5v*qceT&t$)&W2s!EGg<7RM(Kn`^6zX@`BInr;rth0zGjEyJOPIBf%ov?IRpw5 zploGVxf4^9N2C0#an||e<lc9lKCN=Lu2&NDeQu=>>04+Z4vUD54q3T`q9p)*57ih~ z`gA2tFN|N`PgvdRa%!K{Kmw=v;rP2R--EA_W=$(8A(<vBYR}kRqnGH?jPczmi{~W; zYULQ2Uj?$`=|c8b4@rCIRYjgbTKcY##9@~)IV<E%D>)Rv-Z8E>lz7K}HBMGkSG#uR z*Ss*im<i9#8~&2Ivj2U-6PtNnNn73^dA}SGe&y6`21B2YMz=ykx)~N?q{jtjMGemO zE(=G7H3Z&lFs85D9pQR-QyuaW$Ulk%bg+TkcFNSIpmMhuSLX37M%5Xn(^i*#_%H*m zy_aNKuQ<*x!MfYGSo5JD#_AMAbp+!+C)<iE{gh6~Z%=c^_VG8)+~ek}e!KZK-^o`@ zwhx(Nzynu;2vKA}`UuOrckp~*aX+XCh$;p%yDfVkJQv~mlNMiRcE0Brn=*O0Gey^Y zK}nWqjPDv?@ug43C`|K+nt0jfy6cv>GNIkqVV!g8R>xy!1!kG52<HbDQHL{gp*<3* z^h98O7~&C#2l=<vQNim!0>)r8#LQcH+3UK%{T*2}XA`o>V;%eSxO@Vk5H6Ld!p>sO zb`G;YDKEA!L8NJdT6Q7z>e)S+k}_9aIFGFdU%_M8&xG4_?{LO^MwxDudun8mYG{vg zs3A9H4$iu*TbE*R<<$71KJZ3n&|vbV2X7BkdC2ekK8=D@wjS5E)FIDMKpGymcTK1k z)X#;lM{kMBBDn7ukQ-Z0arPE9zFn4FXsqhgWcbP>N3X)!h8D-jVPW0Q_xI6DTm<hd zSDf%2HT}u&__MXMDu;+2xySSHTeaFIy8O)|1HhUV|MeA!E1-|t?+oO2G+g4q%XNXN zbVQ87BvZx-T6?Cfe9!rAYma0vWA#<qyIJioK6vHQx}LKeV#&_I1ufOdEiy^8fX3Ah zCeY3~aC5ZPPKoSRyr}Z)g-2PYT{<nGxXf0y`IclD@?!q{+wz59=1f3H$2l{>I|WCz zO#3NO<iN-gqlc<DyZd8aJXr)~S8kC!$WX3Es(3kAGf^+dGlwD2t|BytTDpRD(V<mr z((8_SdxpX`X!avra%x}uUX;nwd40UlK=0l-ow-Af#O>eVDhi^EJxVMDreAxB`P(P{ zGew`&on`Zjy1h6SjYPWwthkZ)yYrTUs`g0TF2=r5;gJ5<>e}Ca=lr9nng5Z9m47@? zlzhQzNS0jtF}RM-g&osQL>kRQ6wL+JM&gRM8v{~~4rMcaos#C^pLJ=P7TPB-J>^y( zh`Rjsg(L|!2SQ|9ox(?evh4V5Zh*V>#<*GQRa5lJyzn-wPF8DW2}h<vlTA~Su&-za zM<7pTzymq@W_#Y$b~1k|Fwo0d(G6{5s)4a>Wypr7{RKkinL{;?2;Y7ZuE1LU!t}VY z#@9Fm?GPhEFJ1oSlahw_TgbIdI)|KESQO}R*^32nUAz;$4<`d)AqDe}ubtOY+uREr zq?VuAm-$+Y)MZ<$@P?_RU2d~JM85rgjUs<D=Yo<DkhyA;M({HNUMMQ(Zz(hJ5T2Zn zt?h<R+Sko2ab<VAZqVJwr>{32m^+CJi|>8=5PpMX1UiRa4Gh;uF9}tRA$54|P9hRK ze~mTEcwuLoP_@&w5lyxAabBURU8aK<DA%4`cMG<HNT%yS{DttE_XsSVIiX7ofTPEX zE91i@u8E%|GzX5d>=CI|O&SEMVCUEvTlV5tqO0V0g2~d~sF18;SUO!}g;(E8iwd`& zEQMg-*CsGJu-x39ZKN&Boy+`D5!5ig$E#YZ?yh*O`t9MDD6Z}W#0$Ec6iJBc73EzW z9P|y?m`79E%Cc?ZJe5P~sY@=xEXU2#b$sML$~44YxHhQkeuOO0Hz`ae$qbj2X^!K& zO7Uq6R=v?RKPb+<<$Zo3p3C$ZP`m<m${olozOR?}{*a-I5jeaUVISIoyuiE=;Qs~{ z*M4OM5FiviB!E7VJkI#krSAxHTx-i}1YsegEMp$bYtoxH-|ML=TX`?Db8{$9;L(w~ zL`{g}HHuVF5La!xtBKpE9<~p}&l|C*VFhckWL5P!oiog`uglPL19ccrd3T*uws7^R zJ$~O?zf;9bRG?PCFT5%4-6U|LOo`-)=w)7}S7I~;Saj*PWM$j%aZB?YrI(o3wR-2i z%Owt!QM|{Qs$9;!_LJmiT1YKTpw9SDUD_>fvY8i-k7(0m#<Tl#)#JOZ<jh4oJTATC zaJ#^U98YGAIj$i&)a=v+`$o7qCX{VUT<MSsUxQsQB5x67!*F!g)yX?)$1yK?BU3!q z_l12^bq3^=aB{?U&cG0`E?5zJA{bSOifU!unmw@3p+S=5-`IT4Ke<~gtYfO(mg>tr zylyZ0J$vdmL!NHkl35e=`@US-LtVh0FwTJvWI(E}`0IB`B5ZLwqfw^l*^*onAi|(B z?VhueLQ`*blBk&JH=$&t?dzOsHP%DL?n^4%yZCKA0(lgcMnNZMMW;7(`HbCmLOM=O zH+y1e$?U$39!~3xqZhAZQ049r$m;$PucBmw*KN;<i}5Q!JUVC4#HLFt#{}>Jp-5BN z605Q2#v`*Ou9VD^wcj$l>fdl)Q8(ga^ZyH^3BqaJwljFD11ybuwm70QiXyBsvD_^y z)dJtMIjbf;hYhxO%IjuiPi`@M${4<qm_INvK%$VJkRwgn2z29FaLT)|5<s&LfO+AA zdzPR2ndv`8P5&&GQMHp+X!1<ea?Zl~A=c{Qo~j~A+HCKn-Etqdvw{0o^z<&G`&aS4 z5@ko;0ubDb?U%(oFD{~y?KC4bt(SYN@o7cA$N4+_J>;%pPGa-}#XJ&^UUO2Vn_IAp zNI$&T=n7X|TiLL9>@QJ;UOAJIEV}08Cyg17?oOq<9!KeX4O)UM7bvHlzeN>Zka+?1 zJIiQ=0c}{>bewL4($#T{v+S(Cd|evO*yc@nZRKm~>arD$rCEc6z0iSyi*L&+FMf5) z<sSZ1q5f~arT&5A58YTRxh~x|)CF|NK!hsn=*IRG$=1~0{FnVt@!8*#NBGbF&VTQ7 z{}{!-_&|qI4D;lXEkUTqHL_n3Ue=G_?`SlSz#(CBk2CcM*CKZnZ^lxjr!{t=L8kB8 zawWws3$#}}Jg|B+T*kB}kP&&Cu!A8xVLa@&Yh|1bdk1{S=BcsquVr6?x~stQenRxf z!DctFl>~Aput<*Xk>?IuHy!lO!I*@~Hswa)w5_=<sq>`q!gA`8J+xF}DstB@WS5lM zv(w}p#W#GJ+nj0jls)%f*rpC}suFo5xPH~emy2H0z~(6%|7A$SpB6odzQJ@&8R`9& zS+B*1lMg37w|&L-#l_Z*UepE4i1Jryb=~0oEMoK)axnuUIPKZwFZydR?*5FZys(G& zae`zi6~wIP@%LRymi5ICmKhN5%wl`YNS0%%-SN@v49l3naM^TCiHKC^78}3j<EJU_ zf>{|TuSP?v!LMKeX5PJ4LU1Hl9#Elu>b4-0JE>g5b84LBJh7H{;N!Y<HR*QrDzBX3 zX#qQA;m;DLj~*3v-mkk8BFcYdvdWk}yuMLYnS^D=ZC&&B9lc_;=cOwv+O0~pF6drw zb>ZxfDmyu6t33W5`zF3-Nn&huCag;~;-T|hdasRP>S}eCccuZy(U-nI?J?#}48L0m z;duAQGW@59>c7d3;kj(F=4jn$^P2s55is#fr9#kYi3lRXL`&scbC@u9cRwRynbqIY z6}*X!cUR#w3~jxLyb35TS2GgjCXk}OK7tK2DYMZ(os}p#x+IE1YD=F>Co&&0zf@fD zssDYzR-Huhb2P48s0MG-=H;mPc9=HZi~p5Iw=wIzuA6*sRln1#z;a-;ifV+J!;U3N zFlu{K3ys6fhFOlCb8iRS%+Wvp*4WrGrBRyXps5>;9QGppR{gu!roxrqMvM)W0a*`_ z1fZZ?rpw5s+5R*5&zT~hQZ(Vhs8;Z{?u>w+pCN%Pt0(JX-8W&E@S!0~W2UFg@26bs zUQ&`QBdX8Y2X`8ei9F``I%VvwQ=!;4?9Jt)zd&|!s{C7(cPe&VmCqP=VU~;E{X>o$ zme+|uE>$*5Jz=9OkR2FoH>a`&l0W|dt(nqirK6gyPGZA5O`)_MGFcyni`VT!wou+_ zmpB1$EzZOaYsdyt3I~lomh(Ah#gEhy2A)*n(}F4#y(~(eIocY%DV$n2b1{>@J&G~V zj27f5)8yelRVKMcT940(2((L}M^tE=3hzhPoiJzGuMTT=su@msDJb90Yps^Q?C{CJ z*m}hFI@51S#j8<I(_X)&g`t(M0u>%Uf!GA<ZF9~-IOw{4`LNf3y|$;$E0W#pq4mt# zWd4V<LMud`MdQ>f2Hk2rLy)wLD|NtlfY3zq(<2@I)s)nTd_W4vT(M%oU06D|yzorT z-}NklX?6%DHS(yOTF=bJCsyp26TJ#oLJ(MCD)<G%++BW5sB0sTcc*pJ`<i%ivrhAm zZ;a8tE2x>(I2Yx;a!9rJ$l-X<9mSD+BbWR4sjj_}^lbBW>>b7qpVn*Da_6_k%6|D0 z72G{sJu~zd-gujszT%G-zgD-=cUj3PmQ)4C3)t$Ol}MLKPM+T1Se3>Y<mcPJLR5_8 z7-XM`IurHXUEM@pJ@~qqCBP#hdNe8$*!p|joeu&!ffgcP&4QXME{g6(!(JVDdt+KS z($yY+KGDNv<9r{t4xffo*4nKOPxj0VbqY+qA?Lg5y2O-A8`8r9gY_hSA&MrTbC^EW z=kz3PQNEb;V)I<ZqVN`Bq5)%HEF`j24jr4;-dVquV1u~s%;pD7nD`_hsVrB`;5gYx z%+sD^TjuCimg$`PN-!OA0bzF!0iwy1%lx!l0RzX|uUI(#ANJlms;M{M7Y;>G>0Lkw zQk5!QDFF*jM67g(fCvFGDgpsQ5tSYg5KxdJRl1ZA=_Pa!q((wbB1k6zgeW1td*;5U ztar}3cYbHqoIA7L`HMejS$ps2+27|=)8uo&wjtvp+0nbL&vlxzc*ykTQs|7MjorqG zGMvzWjxqm~xEV@x;8^S8P1^49iq4Wryzwe?m0{c91(XEc6GLIT0rRGZt)7^xH<9|Z zuSvnZa(XSHxGq<E%<eX?1!Ck7H9SS1R-9bL-rLr~gSsj1faw!9&qljOjnwW?5+$aj zqJi4migFDcBc)uqY5th~VJ5B1eQeIHNk}!fb3O6%N!2*Xi#bd!Uua4c%kEZw4NJCV zd{IxZ?!w`IoN3JWTjM`wb#I&BBM(+zldZ3JYmNE3dggfX+i+p?G832`=ofU);S*&M zx4uSRg-5TIsr-qY6t8h~@ZvEO+s6x@E#G@4!L>x$5fY8=J1?CM`k4-<LMaH7?qj)J zsLz!0-Fd;o)*&G!!rfMwY}-^uwPI(BWs*{8&Gt_<wr8j=!?Qn{S%Clq+NGdE#PQ!C z(rgjdvToI9h2w+M+GKiZpHPH|MUwfWfe(suH*K1#)Q`ZBow29CN}TWu5k4&b;do7n z>&^sSyQg3Vmgr@Dvw&X7j4C!;AnA=98%ZHrZn!~rpSq!BWTh{&-we|%TRO~pTFK_L z44g3_XXh`JuR{2ngRNjl6ODAoLAsdwD*W5Zs8O(@QeZiF>TcHDoM^NZ=a=q+qs$QQ zkycNoTOC?+XK_EkC{;vSM}r*pT<w8%{e(o!>dKFRQ<Le$F-hGXdz&vqZnkGpOAvon z39;-pM>eA7)&|dl0T1%R2;{UmF;o2-rMWd0j&2M#tJU?2MvI14%e+Wt@<{AszH;4; zN7Oj+8{E2$wVpoGB1Z9#)iWn2VDSR`apfAi!$SQnvT0a5)W&lTJ(z!4E|EBrNPIE$ zQQ2H&@H*p(auD(}0T83+j^C#=0a4~fU5DD8LOLd~5)<XMl}A@^U2vzrJ`y)+EtH=7 z_4Dai7{*cl=r<4AgrBXJsi9`eNklR!`}q$zJ0jNM3th=GgyTz#geUjB#gW#iO3~Y6 zi(ek{&Rrh;aeCS+V#XoEWyMEn{Km{r)X9#spFlg7&kfiisxwG?sx5_!nEkv(&MXL4 zf;jK%XuLUpM@_i(qpwXt>Ka^`_uI6IVs&^`w5_z8*}HSBE3<!^75->I|0Ad%U1cTo zXnB*y{+tjH$e^NUU(rMOERVe$Gy6UD$3}%Rw`!J0-em>Pg~oHoucYD|-x{8?WD5Bz z&H)6-?CLW@BeQhK>w$ZfB?vzy6MJ-Z#3+ZWWjsuIT}E|QH=4(L5&Oc!rufdQk9R?$ zxgk@n$A5?h_@cawDV7mU1_qI6u)uQqQl%@e?b<J+=-(jI`WhZ{Y3$LFUdgO;_wS{g zIMoMYO0PMrd3cwa7z*eE2=38<nS#1&$p#cOc>4Kat%7FV@1*yiInm`W4ZhE`{w)kS z?tJ#H?L`pB{K=5<#1$xSlfi-|`wGQ~AIgnuhsf4X<l>`+N5Hps${l4uAa;q`*k{o! zb0|YGpokl9-~JoKM>8t&5Cqx?jU&NPyL~@ekFV-X<e#;prd^9LGO4egQs`xwy8GJX z(W`!!w<k|Ib{VQ^R^r#%esQ6i%m_ei6*J-sjLwOBy2a&1m$o;Du4n2j{XFI6p7^=# zZJz66ks4`>#jJZr%i`LE_n~#}5t5TQlWOV(irn>hQddLvvj-|0WxD`w0bNVIc`eDV z(~07Ei47*^mUvSvXY;8%0i;{Wi1dC=v<%Os^t^zGA*wfL7bcifZ;C<KKB=rfM3tpK zjg|Rw=M#+AG4gRFk1Fie6FBI@Jb2xmvRAYa%tt6fp&Px~9BRp%l@%*>lTW5#`#7Ep z@`_cWi3r!*4Pi}v4@V^KyAW<(%n`1Gq7A~fnry{i0Lw|wPG+w>lyIofm4t6=&?+sN zGTwOX4wuXP4rgI|Gx6=EWRf||>io%#8HVQzmDBEoqL|&+6!99M3Pl8=YgA8m`=0PX z6U=Rk&$s?%RBDXj;+>wcklj@`_R~4zd$+II)P*6BoW1v)qTLC!a1V!V(M8w9xknZC zg!8HKbMsJ4g#I<Nip`2IRbD}F-=F>YsOu$R@`8NJQGAXl@IN35YuOx4w&cpoqZ_-p z8cJ*IwU@;Mh<H0TXJ_9>PLdVxp}r05ADwG4!tJ1w6&VMf$TOcle!`(=g}R(X$+Q;n zaisP8H9K_N884!kd~bpdvDZX>AhVo5mY;%EmmYcb?j6Xtm90g8Wu6b|Tux;n5pZ!d zaHR*(23O`dMU9`F)H={#yDIT@a-UB@`<HI2@J}=Rqt)dc@jsPv0?YKT2b2HuC;x$u z@H>F80w%6S2CM?mb~_UQtUV43RC%fA3`tvv_0YQ$;JepoA9|D;u8Y`mvX|J)miAjn z(SD$Pe&98WA(z91O3T^d1$*05RNn=!dEVJ8<kdiZT<pW-tMOldK7ZoGq5AD@OjM1i zT{pyEG``J&@kco(rb)MAIg6j1_KbE0wjipcuar(yvNJa?HyALnDc`7FUdIc*yl9wn zSg2p}`NdEEa}2J<$Xm%kP{xJ|3-u;fbwD;b?x(m+ecFM!`2FF!rFIDR-KA`m4SW9< z?FZs_SyW<`Akx%4LVlnXFvAiiTT4BkXtvZ4X6-w1TzR5bTaMOyE8!P1KPHRfa~J2A zbQvBUdm4HK%sh`rFac~-hYAR0z@hBD<ZWs@8ysQt{gqj>)R<(o){YcWVhG2RB(`02 zmy;ozyw>I)%2BT8?7WchESrrc{9sfM&ks2{&QTjU7$eP7ms15BVY_Jff;Zb!UX0Rs zcL${D8`K($3t_68CeOEF=u*u|=E1j&nka6gD=^QwyZw#;KB$+R3RO@=mwD}cn(^aU z5I~+G({X@K>(rMPKI93iFQM{?r|MyzP4nn|5w|UTM=!t93qi<LQdH!d{nKr_v_E|| z^dG!rCee97wF*~6Uy-N5@fPv5!0wV!J1-~kahl~L{P}A5S#ITPZ?+!UUbaPT+z0_~ zWqT8o0@i>9DMiK+Ewb{+HG2r^4Z?<akkIoR6wbFN?H*R~c=z<i<JmZpFAu)DhaL~M zS%=tdfz}j#Rbrm}DFLEjVgC4qJxg^uPqtJiMhzJATnDH2X1>aO^@;LXJS=^<-@WmC z5Qr8jMS57We1*d3Ao6XxV$wqSUYd67CUGq1xtcSvS;Wg+VZrFNmq**&Z;<L&HXy=d zj`~y2&Dphx>W<x^UNe7!!{_6_jd-55j6R!a%Vr#NyEj%Dr<&MIzx9z7)Z0`?!A0*T z2z!XDQ{fA-v&tUsbt4Z7afMBoNrJNk&#G!)Q3+n*8MoU7KTwio^T&X&la>)7JSJq^ zr5XynV|XfbAv>}uR9C++kL7LfVTQLrvieDr5AFFscLb*xRMAadN%N}P<DXay|JWxh z{$n@si2bZR=^{d_<G_;48eT-Xmv#hRTi*or7tCZ9PUaF=9<#HBGAicT8?)^H1lA%` z641nH5SewBnkiUA<eExb*oxL@z5-Uap~%+2m>Y0-0kIoav%5b936EYW?>Agz(&GQ= zd6EeYXmI7qLZl;h+9AB?_%27Ja^0Ntr&pe!fGnFPmUc)7=U1P_;Qw|15J!Uk;HR?M z+wbWkDX@tdU?Je_jwkl1Efcah+1MN5NV69)y>=Qm3oqOH)yJW>wll-d9WfU(C%*f0 z`U(u8-SH|oc@V&`+<PfXgROX*9j#!D*0H^l#FIZmJ*S)hyaeUvR_8_d+Utbyp<#Lg z2C>qg6oLyr6AwPsE=vr)K~x&2kM>h}+(htawOa@M7#BDwJ&~H=ldF4lHi`+Biy4te zm|d5&xbtIRDV^|;yV1C~u=(|?Ywt%H#VWYN2QMxPAy^H_-wFAl9MY86b1aA1E&K>& zv)2_db0-@Q&P;ZO_$O)d31$dQv8o;pqt|!bx;OF$LCclCaMZDdX|@f6Hu2-xT}ciC zEu%}6;2FE8-1o2QS?5{6+>l4BQQjv+|GZ;y*iX-c<>JZ|p8}z=)R53)eGlypZf7{8 zD;Xc7$nq+j@))`Dmd6dhq^8~4?+02C_(xlJfAHyMeFYQ2bibgkkj($>3L(Jl1OI;O z=%W;fxWY(bZFLK?t#Mr($JUNm*e6ymN;3V*f7x5Y&_TOObp;ZIgsjLc#fO-~?BAfH z+A$=GLE0W%dm}>;u7r%+f~puim~Z8vSBi!_2;(xSK1?akY4}pmL`j;pUjg&bOh_wt zz12PHEo@aL%A$8dpo-A!xt+#=gV@GJ(9(lem(#x9yghC6d?9i(h~#zvTZu1T$Ubg^ zUpeC%RJ5{Rk|_M};nnK$iE%%2JFjWgr4wImI?cI3d^U%#=#tbS3wKBAOUlx)6-9q3 znJ}w+b=+G|ZqM{V;Ru>96#iJ6W!qA(Pc>B2Kj+lYXxQoNY+Bh>OO+oG@AVu>l`&fB zpLgjHU_pGp5kH3AaARIrZk_UW6FDp~j@F_PFAlYViXIQX08tGNM7hG#8&>B4ous(^ z=iKFe9m~iHXR5m!JTy($<<kV*CB?lZMe2C`-5}L7%0p^c4;E{hI=Hh{6zKj%3&qgx zutbyBx}CH~e&9yCGsQknmcw1$@Af;_+dtd8925&zzLUi2XKIWU2Xwp*Kx)B#gO8zi z0k3)M0T;2ZlA=q$@M))I_V7LZIUTb&1$=GHoU@*BoIefJSz<$9w5U<7367Rnabe0N zN^Sm62^20sCM$Nk*QI6LSEw=Uqj~K-yClpN(Qfs6-C0`q%172SB<K_nzTIOWs1$N) zm99W8ZFjAJ{pu<pkZ+$M2A%VEI?s~Lf@Y_r)cT{a3T@jbzNf37nkmaU0l4m?nzRYo zQNzS?$+dP>*s;=8k;PA;k$ZfI$l=Dhr8B;7ZSy@`({07|6HRn-;?>oeWx^Cq4g{8k z<~YgjVYC}0bJQg>at~I?oPeI6Dm&7ItC~|v&*+`Xf6P?$b^C+Wg*4`O$|o4NB3s01 zj#LYheQRu%%J>g>T>4&d4y=VwE9At8#v%W-y`9<Pvkl1`ztZz+nzmZKzWbm)d^ih4 zOiPhNFb5ac=<LAkOWx@U<=SXss6_eq;v$m$8b<rvd_DJ;Cq-T?DYBuuW&%N+X4kn4 zCR<%fODh?*U(mcs<(A!Z;$(lIUic_g&UmyoBS`GpBz}d5XQ@7G`I4{y$+PxdqKV#| z*J~u^9x;rDd=@`W{Q<0(OTYyU(J8Cj7paTQMfhJ0CwBJ_xySd;nkyEv>8^dvGY+aY zeklBQu;`vyl?T(x|JQ^2A1?8~jJ^M_zxOW@ZS}K&-=Gf_&4%KofBSLduQ|>9V~}6+ z<nOdhgHD43IXcI0&{#s$GKdP~n#c$(SRTG+8$LAhUH#ALCXBkVQA!d?tE&h?1izY@ zbezBq!#)>F5JR7A*ttVCl6!kL|0SiKh!$mpidBqgqXooF(f7b?Lb)QtyNvzjMdoDf z0TJ-q;p2el973%*@IbOrFO~iV-SwP1B{G-a;FtHI&nwpf?=T?rwIUat&T94cX!hr% z>r$(bKA%Qd0yEQSF13bS5e#)C(+v3Z&d3%Mf&x@D2LvX)QQc|GcZE_{&mQC$G4l?A zgui`ZQR>-XAg6n?;5s4vl)^+3o7XJ-8kq(Jj;56FXfA&1J87Phb{AhQoS~G+s|$L> z0Kyc~{Hfq_7`~z^kLE_!{1Nez3-4=Q3g@vM;CNvl6Oi1{Yb7&teA9*Hf|v*6Q|cJ` zi7CxM5vY<C=_BH{X^??RyTnpV`p1o0-QS>5uJjdgso9<C%9}?(7yj7a);HOEM&-y` zv2&^~rFoM%;xj6Y$6)?x5b5d|w&jUE^>!&!!8=^rN%>)jHA7JoqBXXFQbN;&gQ<@Y z^|?DZL!B}(-dHJPKh}Hh;cQr)AL-<74hNVQk%0~298qqv0C*E_Sc7<i%ll0<$CXW8 zFAx5Y*PU#S-Z>H_Ed9hVhtp+?)YG<MfNijh-J+`_1g9_J7E{IKZfA5_!C)WEL|-RL zyy>g9vN~dON}Bt^QrP)fzP(&PAg2=bYYVHSU>$C832WEfjVf_4D}hmrn7`PR&s6Yi zUfz~`E`9Z;>=~J+gN|IG9FEDuoEAp8i1(BX8l22X4I!?q=T0O<KX~~r?dba~fsDbg zj<+M^ISnNe0T*&w2`qi-Ev($50afw7$dlW|7v}G!Ho(!TcKTkD_RTvZ|HevER5(JW z=2?)pNEV?Tsdn!KCn2Kan@>x(xSiJUnl0Ji?Wb$U)TEpai%HiDu7-2e_YIBvUFYmJ z!1ED~P{tnaf}<cpleIxVHIEifKF~8O)K7`7t~;>kN&oyvtnTuqv(fidi@78&|6FF0 zb2==AFnXLJIGVM{Q2h=JDff&0^Dhw<l<xOC%JviIhRlnEteqa2ZYO$!9_oIJ<7T=C z3e5T|>D8U=D1+KG0D_Bb5Q@|EN;t8SJ_|5qil6uD-+MH?EFUWM;(mIm<!gUg<kJ|) zlf1-X$uQknq0THZirh4St&8DyH*Os$?>TB6tXtn0-n0Ki0r=;SnMoCddQo;YH5L!1 zEC0a_{wq-8FZS>M3PNVcJh(`W#nO2x&~{5+M5)8ak=+a~aVK;C;Ls<jdt^D9B%VC8 zeg1f!G<)(~m_9IC$$X)?5oWc!vV_N_T1Pd5JhmP@Bp#%3n>HpH4U?;1nN;!rN7lrN z7A;KHR(twkc^R*T2HtJ_agRu<yDJ|khto_h><9>Xi40`>+@dZLTR})|%8$)t0@rb% zQiL4&Oi3ew%fcssyUAPOM#9as0}E71^Xr~BwETF|J(Q}V|6PzRC34-ILh8~H=zx?i zNKa_?Q1Vt|G<CZ$wK_vJ?!|_8;njj~&Y%(R&L)VaJPWZ36Cy^g`%uL9+c4>ZmF3IV zJ)F3ockMSoii@UKMJg)GEy_H3=A^Y#t@a8=_>1Fj0VryY0<&YI2O};F!i%(^;)n)d z+9k?-L=G8f>p}(Hm>9)#7~HU*QjUxuj&5w0Z&Z{kRk$77i`|zsb@5L>_m=0GHRxmy z)-rtl1myIpWnc@(m<AX1g5LQBG3_g<zd_d~fHDK_Sr5Ax7$yF-P-pnl56e;mN=?N) z9^tj!TKo*FI$Y~%Bri+eRq=C__^S6dC|3*0Zo_L4IXoPwF+i`y!XyQ4Uy6L|;pg2- zesFEu)URjfBq+fDZ}^|H<_ChvD-*2*$fRcM{1G4m@OuZCTjNJfOi;A<ZnHhK5tBMS zkWxO8AEWV-Hbtv_vaM<ZWM<iszpq!3z!CF2vH%MneJ!&B;d70mL!GL0#v#pogCC5n zcs-olYv3qUa(}1uz&v^B{L8retTGqF0^K{ag;(drkOH9#D&f`C%8Qhf$+mN%2|Pe? zEcr)M-DAfNoT||WC)i*rhC`6R-<xDEy7Bb-t-!DVm1m*%vg4tgGM^|~+0Nv#&R-RI zfb3iRyAeeeQ|!3X=UqBqzB;(CZg2kBuPq`1u?QLpQ+;bD8Kxd0719Cbr}+~Tc)NsR z^iCBn>(t`*wQzn3zWnVKNOgF@3DjFu>N%^R>wbrY7r*qjy&8xBZE1j~B8gU|6r35A zK$?a#ef{3WR_1BPndbjE2rkeB5zR1vmYZtt(X^5d<LUJv@pMNqoxkylGjgFaRs%jY zOptp4VMVN6qh!6Wq-vX>$5b|(^vMUEfrXihr6;7@eUChgzw_-$(eqb^5@8JAfK=_@ zAQzRGd0s`mtLfk<y)z8BCJU0-lj)5#?HjM3TaP-|*4ctEGLH99anG6M6T^GtL7Mc_ z^e%B#z;FrVFk&rY+maD*>(=Im=O*KBO(^LYDEl-lnm_izhmj>Biwot7Fi)z{3~3;T z!!i<Doz|tKOB9Mg0wnz{EG94{kz^0NW?M3RO3cdVy4;nc^<S&D6l=Z~5_ugBB%)We zBgzTU%%lAL43jhg%6-(qdBF7)*|sGHn^Pax1R|H0_Zr*|agi-xM8fT{f)rQgH_AC5 zRr{ObZ<C)KW#$Iy#r8m}z+GFS2pu>hehtEj8LJHnJ)sp@Y8l|C^21hhdU?~ZC1D-A z-f}a7vv=pMu%xbQJBVo&=<DS|ngbE2&6QZkkndL;(SUKJD>XXczG>>(6;8XUP27N? z@?Mp#t$(yIdjZC@KSw;_Kc?e9<RM~B4*6ohqZhnjMfK04=|I2<Hz_@F1o9w8XZ9WL zx}D*#;2-U8T_QZ*&e~0!EogJDe^T)L1M4e<=w3Q?0mz|<q$a=@0(O-wd&FhQEbloZ z3!Hia_R3{NzYBe7!d%NeZ`YN!z9{_qtg`|i+ECqc<1NLceU5D;<P7Zs8TS$`#?;fw z^`ljc>T`8O>QnsARjun?J_0G<wiKhEh_k)9v&nnf&orLhMhYbSYpWZM2NV3eEiOXE z5D6U(6BD?|CMIS-ojc9nL@q0oAYzC^tM}@Hid7#8%rX<mYy8p`3~?YB@){K&o875> zdCN7>fkF>DH?>Ve*k#l$U1+?PK(KE0*nF0JuV?7>t9*H6A|++UP$zV%XD@?VK=JB= zfW_s=N?*v|Q5wSN0o@FhBVOrGoy3v(uWV;Mg?g2I!CG@p%HB=2Y8TsG?<JlDU8h8D z0DfYu(;4dfic78w#YmuYsappeS@D1)Wz6$e3Yz8zaqT(#iN{D09(eq|psh*YL3PFq zV@Ajq!1)@HSH3cwZ$`#Xo8st-Ui0jVF7+eu#rj(N^D4BApN?>W`w+)imS<M_pTlJg z4_rY%py3b$YAMBs@SY+zS4z-Vu&=47utrug2g(fYp-OC4Z`xh*7s=>)46kN&$0#+& z#YVq6HOKIoiXlaILD^{c0ET&f4;;}1c!Q4#rWL??tjG~n8X{;^|8(h$ax+}Y2Jaxe zn00!?{e>VR@8+tU^>xl$9Z+-+V6zaQuIiTSwh8&Su#f9qDnkS-l_U8oL{ajh>Kdw^ z{vz$r^(kT!apJ`*BlLUwsNbLfDI>{OoS|5QI_qvrE-ITDHGyJpOnzA8Mznm9UfGF) zV6k;Veo4~~wxSMppA~U@$D|UKCt^#^#D%L$i1xDPA;oYIO*24i`zMLLke+?6=VheU z7fszDOU@o2??NL*%&+i12~0Zi@zSDR%~|-I3BL&&s)V8R;2NUUo}7S!u=W*t1s0%E z_T#@r-VW~$;C8}SB!`}fxO|bhv_a!8DzW}HN#Z)|>p40eD7oNT&=(?BCLpy57JzIW zFzuj$5FyvABYB@<*36S8&$)*W{qj#4CD@66V45*GDJmv?IC*@5X)1&lX-NI-LrLfg zJxW)0K*;~HV8!alHhKpW8rd=~D9n8hI$133GjsK}l%2QJ6YTeMb37pZ>i?Md{%cU+ z|7@WA$M8zNrapDwE2Ks+NEvX^M0+;?rW(xig|%azd}H`4hBV&^<)=bz$wl4eK|PV3 zzd9SPB@^@?ng`jeZ%U0-s@UcrURbqhXx%Kd({Q=+(7#WDX+3*tix3&Vn=s`HuvKAb zXjF>|c{ob3R$pKFp)-0EwVB>`oVDss`j9F6<5#y-L<e3j85^jAeu&N=Tf^B6MVDLv zc-lHY{B39D-DRcSF?cBt&0ld3C7=8obXEFA&~cDrlU$|Un$BFYLFYX0oV{n&>)Np* z%$QM2k|kwY%}%k*<WM`wdEJA_fX>Y&08nNCi+i+{ro1Hr&^W<)LjdHMu%5267eDyD zZ|X36&myyC5738ixTk}@G?TNMean{9^TB*bb9e6oW8ST6;qw++zpB7JPli24iw$-= zOie!KFb;GC{-~%aUrnZ2P?Q$yN`=iWbb^94>{q0L7O)zb-2Tqcu>e`|?l5)W5CNh< zX{TDrvA}29q*U&u;q|ttru4pCKyI`?gv`vI0@}x>rVZTZ>5~CD9Blu(692jq|9U3= zwL1Ri_lfSN^9z68GIxRAu~4exoF*NxsJ;NV*MxSM>{$pMn7HqyMGYs{92l%;=(?{T z{08BNH5hO-+X!>G7>x;&zG+`9Nm(4#+hOK9BhHU|=6AFtC#%TwS#ldw4CqJ0za=De zP_)DKR7j$&T=R&8s|OBrp@}qyQRq;TL5))xpP@|0S+0Z9=rpfQX!f~YW{|Tjjf?77 zG`oVC%sR3CeN@l<1ES+|%k(2;xfAu8a>dzuSh&zFTBK^lZ%~RgTNrybTgYH5lb+~@ zRlC&a1<@zI=4|D%=Jsoi8EXnaO{H-XaR@(3GzlNYo4D8=<vrKirtlI3a)ZrR0Se}y zg&z3smt5gnn@YZGY)Z;ME0HE8jzg1mPof`?;sYu~Y%X(_jGUOdBH);R_c}{<B)}M1 zhG(5<t{va4JT{v#tpe0Zc36we&S66Rbv@_r9h_=NGW+hAGRRy!XQL|9tL%TB(Ziqy z+zw}36>6xAY*PQQvTyCDZFf%v=rEF@57d529!wY02%wJfWUzX_dBIU4#BdwLt|Yf- zPW5Z$r$H(9T_~SQC^fm$&q=bb)+?hiNY_lsTWJAfTb>kJGpF&yaLh!yici#RJIt8K zFl|rcp*nl*an(>=;d{`SEj>7lp799T?76lf9*k4-Y1~KyH=d6Rcy}8A2^5k{_G+_Z zNejsH5X29qxP=~m80&H-iW$!cYl>#q&JGryZ#PSSP<<uejN9ENUGx4;hHU=>J(_GY z<#m=$hT%BNdq^ouqkgU3=6mtCkb<KL{Y`1t%(~ts7e@&4*2?T(MrHON1>HS-7Xl#X z9gpQY)d|9))<FX(Ggt4M<gb^4^qg+*KeiDL>Eq~(u}4xmtJQU@T!uQsY{wuBNQqG; zJ%b`VRd&)GsM>w_y+gzeJ-*|B6;F6{iqFpMWW|8aIm`>b(|)H+hlHyc27#=a8ov}W zI@{_nYqNHk9dPVV;@vj$h$$0I*JQ@LCz@h!+xni)QWbWt;T5=Z<l;g&NUL`1<(xdw z%9Za075%Yw3{gVrz{hyfMT5Dp`}i{-o44=3TucrrcvAEH<luYL<7#H?k87FKz*A5H z%QIJ^I$(ZZsSAu~&Vzb9=5XHfbyVvg+IYK!4(~QkiJu#p?F$OBdUgT?xh@0}&aO#Y z#|pJ&`gcK;#*tR(n>9qQU|mc1a0Ti@T}`zavufq^R^>yn*1(0DYqupkqoVkhK>Jtz zZ7KbCCr}_8z4d<fPdU5R!`ED5zd@^aO0F{yhJWgWMuqNK(tx3<=ufj?$(O&>jQs`3 z_uv1Kzpt}|fkc-k&v&DkNI>bgC}H>|Qh>;z-6<|KqG9GTNxR{nKkTjla;{iitxU&v zv^TyX24sGQMQh=k2&mX62N_HOqG}`UT2V+I05HV<QhBa-!KY&8a{;w`|8_%mNWzD& z`DX0tj@7{jKb~|u32-t1b))FN?*;pJ!QVfya1Wvu+Gj-N`wenq-1!Z<;TRaEZ}qny znf}`k`v3Vf_W%Cre~d#}3c`neEt?2F1Wr7Fz+k1P^C<{<?>8tZmMll_Kk}WP+wcSA z^BaWLj+Xh-N^r8JMs{IZ_^AG+)OUF5m{ZNfz0s`Ei3bneYEfInr%6M?a91>H(d44P zqTBeDJ7EwwHJc*aKYXPXX5gU`RhMp*fHg&PJr9iav`!Dk#r()dI}|Iwcky<4ytr~h z@s{*}q7WkzT&pnzaztd1?5XM`Ou{{idyFKmwpD0(4m=?Vk!cC<!QxL&rr*ZbrY?uy zt3F+Q&ElKiwmDol=`s_$DeFi2Aeeh|6RAY<60;;mMxrw%N%o)6WG_2J>valyxY5U* z%?CLXTjk~RH+kMHy|q(UMX^VXx=8RdEkPM+HWZEDAcl4HRwVRflNT|8dzh@-)qv>~ zR?)Kv2$GHdaKWX#aOOw=*Iw{&#*(51ICZo;;-*+@fT37gmxW4~6CawIAsb6cCcg6~ z%!da;JH-q_QD~v<AGq|xd!Npyz4>Y<>z(k+VCZX<vlb}Wt9;FgIT`yaqR5NX9ouQ4 zG}8y;irG|4Z{1V%N85)QFu4^fAE}rv{tW`3VxsSbL7b?T6p=MTWF&+EDBBR~8s_WD zM|>wlphJtH)Ywj3S|XGhZVEINXzncDmEE3%OG<;1+`bmfU2HqaksTc(3G~s-N6~yj zDUn1CkxIaf?ViAnsI>4h5SX{`#$zjTwgPmd!yI&jGX-8WrnKu$L|x1Q7X;oJPXp50 z*86kKV-bsY^TKQ|oZDLpQ~Dp;g#N&r;Gd1PKf38n00@Ukf_9gh#>09kdE=wK-wS{o zI`#Islhh!B61c5}YpkXH{j%Wo6})d{rgV!%(-i?eWZcl#>nyg>`h7VZC4civ`mf%* z{_yVjPqf(u{`b?m{=ftJx9R8scQdrcl9pT!rT4SO{|5b#pEwQr3yBERgva0SjslMW z*I%njVT!4y&i}ct5io#y8xZ&ZS6?HW`9`2v$e;Tf<Bk^8^9BHYjoilvxwiNYuoAqj z-Igy@TO&!y#LDL*O*T^A4E7FaR%<TF?ufDPfY*k7Tart~&vu;CG=~jXBMIc4ZjqrX z!u(O{4z^*goG^u|t8y;#eC03$8)LnvI%6U#_!48hhHg7=EOsI1xDO3VweTW-r7`(X zE_IxxW_e{-_2l-IPb(b^SKZtGQwUT`pOkl_4|<%$d>yFu-e4k&UY$_UurMdV!@M=c z&N*l<=F_FC<Aa<ZZYICw$!8DOjyFO+tIcBy=K#TaD6WKics<CeNFwwl^vb6(Wz7$j zX|{D*+px*?XnlNND=b_qNi*QW*Mf20WBi~gh%)kYto0<#K&5l*SYa`t^gNowvkzVB z{J7Ar<HMMxkNDx2?pSHJ%(gq6`H6{!yvw2d^b^!9?3D?mCPlR47do}sUnU~O$F+2N zSm1EbW0Tyr{LNgl?1`$8bXc5en+C&>J$=OB7`#Pg1<G7IFSvr6lbn2-dC3Z`h1nbJ z=otEDKkU6d&@{tpJDkeBVCV4dIp1;6Xf<_<Y;cp@JdE1iipyeYAa7*iZ5&p~h?vjo z@9TpbPX%;l+4cGta=**O#v6fr@z=@=ynfwDZG7+PmmZ(-VY<iPJpd>kJ2J?7+G8=^ zNXAjv^lsa$3(VJ9vlH;@3$qa!vPC4IkWGLb=M@_2EV33NJ4#X)HBXRqH;!K%W9Vlr zcoF4jbs07E1vD$S7Y|600RE=yH;C&LCH^hPR=5Tmn9noKXH~G&Lu2~Q9JE&9Xa3K} z##Ma^)g_V+({)5?pgBMpqD$kdB{wIaHtA{z5iU;8q=6&xm2y#Kll`kU`vKAXOO{Gg zevpOyM0CxUFGn)LB)JYS52B!n3jmjaoUn14afF+<_^<;y{pVH1fwO0&-m-L?SdkXx z+ej>7=ZASh##)(np~tBMT_Pr(a4u>w?O1`Mm&i~GVb*Er;CckxBV?Pit??wY_s@P* zC!-N4&^WQUy&R@x*HvQrYJR%MN2C{Yx8i-61h4w@p<_9mfosJ1jtnsODxC)iw<hQY z`(yW1idTQ#YnZ5ZT-oyy>BR|gr_Hlp)HYFGIBk9F9k&OAuVpNlO~ahS^7bTP6u0Ua zFv8R{j&D9hXL#gRhi}B-&DCq3iCk@qnu_8y7Sdp_bfA*R)>sj%v<hl6i8cIbrnWu$ z!0&B9Fp<qvqNT2%OXrxlVRg@2DL_TEQ)zIv_lV^&8iMLgg2w<kC<cVjf%q*bc`~tZ z&7V=XG(hT8ZL^ifoLG|3qnK0fGK?=9FcOIpIb0Y2bCAFvet-OpzwpoX`U%+DU|Rrl zBX3cKfu2BdfpOBuNj)>d!G>0UfMb%X#>eWCMiu$EYm*Wu6~sl~gD7x*gf0<Cw@o-k zyEE46HbGuPfV=D-zJE+8oY+k+p@!p?)K<S-2^S9Oj_BLEQ{IX3baYNr;NejWJBe#g zm}(?;zoht>QMy|R9!Sk1=nCX~y{Y$>cOd}BIgzZ#H@?@_Uf@HGDqgg@AE_Fl{GNRK z6$^c6o+(6;ra)nRsj0$8Rp?2pMxG^m1LCLm_a>WE1>(yFNAAyCM&Rc}GotPz1PRca zpPrGP6zn&ejx!9ehOkgDot1!TvQ`LS?O18r>RR;_^$r{_wb#kcNIWi8Fwkx6t-v=S z*~^A=Cds0-otC9IMpIVb0{SrId`u|Nj6&rd{9H)?l68tQ`wgX;r{=LQcSmdg#nynJ zx=rSDwF*9Yy_#C4kr0rFlP>iVnRJU>hbE8N)Tb-?bTurdv}F1h@q5Tlx@V0}>hGT} zmZmry;})y>V9ncPfwp1#++=>BkGwj*3yr^JkGM8U{N4%XdkQ_VqVshBl{_NZVl2(# z?fNX1M<{&~!;|wShx2yTWtUxsx7r;laaf{4$QP^7qbeIIbh&9n2Avz>YjM`|V!Ot% z+WLyYY!5*wEXkJFD$e0#Zd?m32K24W!0k6Ep<yoN1579KMK%^Eu0gKstmGPg(ts|j z^1z7H)eFAAhf_M)ICm@;m8-;fb=3imGWKA!xU~0x+C$>~bvPWR2RkyDUi56E;z5&T zIl}Ssp2+<|WmyGS-Zb{1Yh84K>9-`Sl#wZBdBzyfugaT;MF5zXDSD$}R5KJH(lb$q zal_ueUOvQG$eEnCCrZ1QM<cAPm<1%bFR+9`0;!h{&Ld4m>C!N|GzI#s9DL;k@=DVA zZ;e1GtWHZQTq*Xn?Fss?uNJ*v`BdxaMrNki5#U&!kWfX_N$c>Z3$$Pw;0#D@KdoO` zZzVqm5vJ?EHI7UV2e_y=n2TN<#VRH9Gg{y10{POp53?Zh<`nDpR-Qkpvdg_&3HN3Y z8Fo;9FCBi|jqV2iy2ta)b!qHcAN!<pk0`J?=mx-~sx!ZWCdsYqKPe^`BMFC#3C|VR z(cWO<a2)N%hZ)gN+&#`8j7m+sA4_$~?(-_{UFm<(E94=sC6*&@7JP6%WQ;EN<>B24 z$Vsnbk3u*Rk?qB9kF{T}D?a!UR8aR6J(e@(_u!)nUzz1&sa^w=voH(<sRt-}7<g*9 z!%jAv0Obq$0%hO*>}rgTwH#NhKthY8Cvj(U%v^lc-E19gP1i5va50dF*D(~vwH_^T z0ZM8%Neqg$%2K1SMnj#R+ma9KP>;Qe%SY~8q$ZSEYJ8j%h2^9hMQym$Xu!C0*jw4? z1EJ#U5D}_+4@9nd+=cq=J)#<58Lh{V`o8&nA1s`m^TkBE&8^0n8+@+D6z)oY#Vg-G zzM}RahqIEPy^bSgvUNkaaK66*Vac&V{Id9YuPDvW_U5hG7A+H7-qP_Bb&uNNJSl=3 zcwGekG;NA+@Ge$D47?shP9_$0I6hGbjS}asM~5E};NiQGE#?Pi#M<$vKtwRH+b~&} zjkv01f)eK~!49%BzTzrg{<}_~|9joMUg;hTKr(y*;qsIrWu9JOR{#<}`5Tm6L<LZE z0OV+I{q>k42KqZs{QqciPJi3c<6lVN|Nf!-Z<sv(xuz4~kjsjy+^|>xjFFmb8%$kR zS?phut#<St{d!{le#X3zX>U2tBR?q2)nk*#alv#k2xcYsYMqne^D4BiC%l$Jc@@r2 zQ>Mn|dOgyL`hxJ=?(SUGPW@FzH_GgUI8Gb(-scc4er+0;uf`M?28V`BIB}2CE*vJ5 zQ02Pz>I8-GPAH<1Z%$3HIySxeSNE5d{px}NKKZvBsb)%(9#URWn0^KsLoLV|WXLF8 z7=gpia}uaQB(M-7vJ>z3zQhJ-Cz%`Ri|Fn8z7>=ab%Rs(X8pMb0<1xjXIXj5LuSAn z4>Yzmnwi=m=P1|GxWYG@LPLY{ZS4XALr{tsRlQ`k*f;OY`0(aSmt%%hQ#jCjAU^{x zKwjafO;oNa1%zu<I7#2YYE)`u%W2?de21|6n5JCf-Er5h5f%X(w*a1)q4&ps8ef!> z;F_cxd|d|f>Zy}U_6e=A(~cB9A>6VQedw$WY%dMQj7d|=_*lwgEA@tXUM{tbpO?NG zc5a>uOj!|Um$m8g<47@5uz``d$as@u4s_PWUJ=LyDl^?F{K&ZQ(Hq4dE7m{Vd-Jw; zjys5l_V}<&IYfNT&i@7S*n+pE6VBe690<KknLZkySq>d|z^o)QZm<Gg-j>|d6*O~J zdc7kI(qTR<(*w;(daE{_Cr0fG|4=fyJf>%}S4T3LK%1Y)56V#R-)Y)dX!jLqN|aVJ z^Oo<C%4`hN<qFRhENeN3)Ef(tg^6<my>`2qo*MOIt~lNaxrRE#$SC3FTtIl<LwTkY zWS&^#6?M=f0|3PL^kyS~JGpofL7h5&=u*4obnD2?WXqA=%SoGNKP-&xPuE@DziaVU zz(?rSOG9bBg7DKU9P=BYp}`cPwk*yOh_F|n3s5dUp(jUTv+!9z=AWlo@yhqeYd!yN z-U#h@AIaJh=a@%%pSLH%G?ZJtw<7HPBYag(0CG%9%iBD;NFYQc$8pZa-niw=sCTB7 zIZ8idq*2!`{DxR>_$~fkg4U)yXqcA{Ots|1acg_35(Ogl9`SI#3%@j=uL|@JKP~>A zb^80eatbqXHec1G<$4_F=RWyvh6x02FJ(lJjfkiXzD2;q7k$aL9o=Q6xS>;uM@F{~ zNv4c7Gm1MB0j?&gC+=N-a=mWR4J6d+4v%q?pw=%O`gDp**NkmcXbcN^;h7a&MRPTq zQM5XWI;)>dzcCxPq4c{WC$Ap?YD{X=UW5p)rTr4ay==Ne<Rcu)0P<9RM-Qt<!(5RX zJ&B3Rm3n=ye(aK;9*Z@6)=u+Qc++;=hDmU1uaBx<I5>|lvZ<+x0H7KzOyKi&{?y63 zU-}J}j{}#^3#ZRrPM_~nEp0@1Ub;KmcEsY=ULV;%QbWx81|<{=jtT)!))tO9P+blz zYjn^PEu~MC%9SPJ6^ddms|%uVgS_3ncSdY-?y_SJ%YS&g65WB>pAq3jxZmm~TS;=X zY^N)JoD}SY!qfm$Dcr<3=0~pE8R=Va<2M?r*Glx}l}<9P==@*9IseH>OfaNqy}V?Y zFwaW^dTiv)fk=xCD3qIrFs;vf`Uiio9ZdF>`G@}Pt@vXV%F-{SdCtk6l1?g7>t%>6 z3+ZW5rVJGc#fF?ldY~t1x4aA@Trn-x<i2W5Jl3>TsF07LAtl`_8Y3*L<}K`474}I& zkOn;vq{2I)f<IKECe;zLx{jgN)i_PJNj(PVvNg8HjM7g`+&;_-3Vylr3j5ka=3b~~ zE@B`oOb{YVJ54<xS|*4yQih)+q3>ZF?-Bl;dqRQV(!ux#V;lZf_eZDJ?S9Dmb9dVG zSFfv0Z_1YtML?C#2ZGDLz%P*I3(ZVD)@P@@0Lf}HA<)d{u579x*9YBI<;yYB?!gz{ zbTYCRaqS>;#J)#{uz5lFsHjj1-z7>)V#W>fRwPnevwZH=_G!$R`=@|R+lSnd>AFRi zrFr;HKamx_0t&{pJ6>|2w&v38DAq9#;Lt1NB{ZS<>9nKFLd+8W*QT(m%V)ipcIR?T z=RYLIp4)!L6UL=t`89@QMIMJRQE*+w=vEGdPkUhNvnDY(OH3Wc&JDHe#hUGJ@NO*t z=;yqCxvZ*P_-JQO+BuNpr9DIH%gWWj$XGz|*J|2K0d8H#DcMXPuIG3*QP@+Ci{`y? zb@LC^t|kY(bi6$Bs<Zl|gK-X9Asq_=`UQOaD`7${$1tNZ<7j=SnO5xDG1|qNx#$yJ z$~6ia3GBBlq<X4Pe?KY}H`H!+my^`~n2Om8qy{X^KTijDSc=k&Fl0>RdhJ-H$8fIg z^w5{0R3DDbBK8mS5-uP&hA_^abD%|}`N6r>P!Sq5h~j14I6>p^EiHOU2xhYSSlduv zCn8(DPe`{n?{jv3bo2tc&+w91&uNf`FLdg{R*URsiu`=fBSnn3PN<-Tt53d0qW<e& zM@Kfx`~fXaV(zS@15?!3Fqc=HhI)HkR5?GC*TNPzLJ-?IcWmqd9H|~aK*?Pn3lUk_ zYiKZ1J)Wk6+U{GL8%hMleaX1<I?ey4DTrx|7JQ)cC&33`)K#Y|BvgfVbWuay7N9>_ z0<!NO7)%?CwoWE!`%fE8(B5p*f6lhCvXS6lZ!|$k)0HCG;)tgfa?u)aUOZs2X1Cw) z9I5?*X6#V9u=y;e3sq{6%>VckEdHL`OgzIhRmg+_UtcL&%{u;tE<l|pYPOVDdd|t} z)WtnD*G1T0Z%$vpziu8UrljjH2pvN|W4^m+$f`;K@7X^m?MEea#U`+OgmB_Co0auN z?~Ks`7Rsf%v{MtOEl!0KyHZ!}z0CT}PrKL=<!&8AJU8|SJpjBbRH>U3>&^{p2a3S@ zFS$-A=Ox0|r?KW9<tz!pwRM=rbQXthCuirPx)I*oj0fJ2IzgPFmk)u~z0nHqiYB84 z4z)dRODv{O4(M|Iy3GAE(2=GcFU@mhXFz)6sFmzv-bXjNFZ8n7Yp*v4b9Hr68}pHd z;cVY1zd<zF(0lbGaU-HI4Vh78#cIq^WOtk}Z%9-pON`-p(Eh*cuKq^Q?vMZaKiU5L z@9K*GIo{}xoc;cP(gpB82vGdfmBBL*TjZ$86n{r5wgRa|y#yniCXuyL^1Vdx)CkeY zVCcccfDSwS0DFvLKi2Kh)oqh=DvFo-kH-x%d=~<8*Sfr{KT;!pnY@^8gPfw&p%)z0 z%>{7=(^|<>O5Ywk4I5-GnpRsD4cX0H5H&ejm95%eC2?{y<-iW<JKn@W#ty||d8m0r zl?Y|M2m68WFyD=b1C}36H<CnEs$e?<Pqv%(Po-~Uoy#+tTI^jk2L<|fTH^VL#<2&g z2p^L8%Y_qq2E@a_eZ!9#h7<=O?gCWT22HT0=k(!B34i~hW7s)-mrgn8Nnq=SFyM5f z+@<PfGg=U-5UG_Gu1`2)6r#cPE7ZeZ=e0kZ`yN()#`x|XkIz^IcIUK^Zy>-$R_oqV zZr;_x^N})&II;^E7qX<OK2OuHr38s}%&jCs)u7$mvuaWKL6U_t@7->mxYD_Co$D%f zjRY`C0P5Ys3q|0u*U4EmWtX%go|YmCN0q(H)3>*7V``c*DNolWee11a&YEAoz8SE8 z7Zh*@NRm7-LYQ|sNsS@z7+_H*SIOE5G<5=p?d~DxFMRN|X&b?-HEq((iQe~K!jt8f zoKJBKB1FTKsG@sS)I5@9)MV&XHs_Wvm}PZNb^@s_mXiHk6J0+hSqsaFz42)>GqgrF z-aPicb%cJQz^8=$B#oPAKxyp;wJJTIZ({AMCN-1*=u(xceBkhre3i~Ssi%)jZ(@-- zIH|3j)aO-dU)q0T(>v=dSJK6lH~gTd;94ANT~8Qjo`!bM9oJcU_4M*sC|ZvdXhn8t z+sAS7@GavwlosdwTxRgSbI3*_<ocyEpe{0W!<LfJ-S8U9Ub~D`<%+GOpPE5j8+I6P zk}8Fz-dG`f<lRXnddsd7lt&i|U2M|b*zH3$N_RjvBKO*<pFgbp5_&-zq3OGlL>q?% zGtIA$(N0w$(yqmDfNyz2PP^2M*;bgP8%L)v&bk;Gcc`02fgt%n_=rA~2*yG=sMDR| z0O5D0Wc3%)S_rRP!2D{)#=(`hb3n3m;hc9tin;RW9iK3Uou&E%bEMlC@)AXGdk%aA z7&vr+tiY&WtYKy4VDkpAsYyENs_jGRcaF?QB%9yJmpN1!OEW7-Fw^A^^XO`yDHp#J zBEyxMgxz&%*Av?IaQKQ$-zTrWW^*ck*lBv~(0%WXSrbTD;Pnw*Gc^!rr1@d49zYbZ zCzC8uI!h^qX2fCEE~5Q;m$95gMfa95<xOwb6kd}6L7}r!AC(7lJ`UWy$CNm>dvC1e zJYtwo*$Iz<=roawUO<GFMLy3G5tut4U(Xaw=02>9OT`?MuYIQ0Uydq?Vi`9Co!zaB zfQtFfKZCH1H?fA294~J5%&B20PUX%eNQ((2Q6uu$^sr1_#$o=bMZk&aJ<T_5Vt8BG zb4U3lSWrDtKruk<LL3l06OFt;fn(FtBH@~qNAOf_BWh&vjHmycta*`f4F0FIP~hvv zFF%#kYk4{bw~a)MyMTm9CQm3I9ZV%H0FFu-YW&XaNuVgacz2lJ{_SAu$Xa@Ea}#k> zrq6UhVXjBP1nuU^<ShzF65jvc0-XQpW{-gVPOh-*<B-#9b#s!Go|p62n=s*`W*0rR zt-e5~jSRBI(Cgg&iBQAp&Bq-5eJ51p26kE&j=S%9QRR_d{uB^ZGrlrFe|GxJ_!h@& zYwPP#4<Ej--U<19q_Zkq96_{os7ZBkj0QR0+JjT|OA*>wai%n^bzQpkb0yB~_gWbh z(63Imc5g1LJjA5#6bxLZyyOx!wX%qEP&JZZ;v<Hx*DpZ9NVVq#<G5{v|AiQmA=vrl z_tejdwT%;>>^;1j7EMKrxI`_)ak`1``WACQVw}+cV<$zX>A2V+?k}0uX7hx0*dAHj ztPBbWkS*VXDYZ=K5ANvU6eUtfjPAZ{wID5Jkdi%(ooWx$fB6{`VR!y%E3YR+xz@L} zo<-@;YX1v6=arQ=A(yt5|D^W!Twv_3B8RqRNq(`%il0JH{BTsw8<jc>_4@U)P0r$z zeXN<bnJQf4YSgxQwwg<fEQ~P>bZUamN>j!?L{HCJW5tnkGLyBfJf5h%>6Zt_?~B+T zyK4y=@(l<API-BPsQaL(&poE|t@pRWM-g}^czB`eaq)ov;}5}E!53E@HC2=)w=0yB zuLW4V^XY$y>Hiq{WOJ}9Ga6J-5HbK}dWr(_1C5}^pq?Tj?{f_zIVvhES9RW5HQ2va zf_Y+Y^(qo&Phb10SM|n4>d?*`^d@9*p4HxX;;GkXQHODZx(bbh((+3;DA!5@ilZO6 zsoQGUy}&8?vnDI(c1xx(s`nbR)ft{wIT6weVaw>)SvFuB#bPo3^i$6}_wn(STA1RQ zmOOrrqfIeHj+>LKqII_-Gu>w;)L1zk&>$PK_PReAgM}Vz(j;R$nXXT`k-y<z&IER6 zewbUvh4|8v%n*s@utzmL-lx)F7<HYLc%>y{!(E0imDu?>h(H7v2U3cH!CJCWb<Hs` zV<sb;O-F(Pl-?bUc`xGe10nE}`P=0tZcbB0L&dHG1Ej%(-YFuK12J~7dVGm8yVz1m ztS@ug?pHXxCLN|Ue)OSq>X%~lvsWg{*SqsoA5?+h5ux`hpNsR1EBSi!b>Lj+!%#TC z%<LRGG(dN?{N97~RWrG_cxx}ed%6RRW>45?;|z^(@^ivQb{N2@MQ8<rVzD8zI>i%; zpF69M4drh4A^dgKxm3TuUO1_|ei{S<oqd`eCeE8}G@eThx9<X6f>AU%aCnnR+1rQS z_a=5fb@rD~$_Axgm3J28$Cak~-M#I72egcIBWJg@=#K89<e+14R}01)S}8sStC&Ey z#n`BHUgw6|)bk`3xu<t4m=w6D_!+tf4rGwWdjJrxhNfS!yhRX$kgkp_j_5g-!JbUp z536K8PD3}K@&gKfE+$<Zmb%-h${41-Yx%+oZ<4S(d}5f$wsCP78_3=Oj$DV|&)N*K ztQ|O+mc}>dFrbLL*|!&T+v*8B?BsK$r8<!Q@&8+T>whtPf8;>-R~je&Nf|zMO&|mU zj;*n=r#|o0dH7bj^pUqB@Al?7yUj<aQ!x_XguyPl-VT``dU7-vtg%^oWbHUHS8`h1 zu9A0FKZlw1wQ=Wq*-;mw=(EMD!?8EtfIxcR7_DRNIUM-^gg74;X=#2>4G|4O1=&`O zximJ0cg$`n^9rOdx$|I;3rT3iua|r{e_sM*mcz1~B?(wLqRa0$ljj<XR_pfm)om>_ zQKQ!qXD%+vM87@8Ffv|xX3<n4%n0Q1k0PQLzjFY{-}`hX0CQRnV(T~PX8Fx9F3!-t zG1?`7-UwZoKRic`|GS$k{$RQhS4ODUlyqdb0mJ<WV>7;<9kuFr0#cX9A6)>v*(C6B z>h@%?JoE&WM6!;1ntjp{$w2<G6)A(J$V@aZ)mO%%E>BB&2jgEXT`Z{^JtlZTF+b0H zCP%y#jB0XoBdDP0y3_;`muOEZo;rMyoY4*gPBc;$Ba#i6Q77b;ZqujJp%5$H*)j{o z#~Qg>#!q$`259Fftue?@(l8hDTn4cyhBZ=|es&H<cZ#M6S&!?@o~F8Qn6cY6Wf?7g z&bt;*>3zf{tK}Shl(mV*MA44f0HPCtSJ!?|;P;FL1n{2AQ!Xv}q%*tg1NCHCv@)Lc zd7@Fdj#1XudG^RmAvK5V9g>aW+BrsJt;V&8A)inxWArW=v~3Bl&U4>uqG&Fzr^~)S z5>uD)NZ^~n)IJ3Mrb;8k-dsf3)bDT+<OdbdXca(Q!x0~(t8`n&JjO(Z99s$HetJmP z8ix+xoV<P{Ei5^?I(xjWGg$r&(seoY;w!@*+bW%*zAlK`<b#2x{t7tqIz;{x$x6p~ zzt$eES2+NSeddL60g@+s4S$20bGV+-geh`8cJpnSvik(q?rd=?whIRsPmm_X2#Oov zZ9nWz{%{G-2OG`Rc=2^sSZ_Rb;OkCz#6@ZN95-h&$+`<@vx8FJ{mR621(n|->_@^w z_*Sj`_x>OD-aD+Rc3t-kMS7QxfOHi?6e&tqq=`sT=|!az0ShHar1vTypdbW9I-w__ zgkA-a4iahtqVxn6qJ(^Ve0!~P=ALJ-wf8D>owe6F^DprNu7o%57~^@L=e~b80KrC0 zHGR!=XiYr)QWksH=(SW~hGW<J^Z{dA3CnOEvV6a=Q<{(0d&*sMP7Pd#Y#Y1=Nx%p^ zpUtXq@aza|<F5=I^a1a-NymqNI-2ZfIB7KIJTH+9qKR3nP3q*D!ZTp1DKE(FYi2aC zo~hN@{7M*)2)p}Nqa52AY3M}26KSpnaQ`e%ziY~L9_))!Ws$5t=;YkEYlaW!?OZRR z^$;W5H>oB(QPKq);gJ6muJ!*~67s+5(tXvGCm!`Uu}-Rpa`xgL)h&1*<*3ITn+0oq zoU>MARQe!2rD3#AECwG`R@U&{1+i+g0Yge{F|vsv5fb%dzs~ddsaNWO`OvS%kW4?G zm>Vlnm(jd5*$>U0deV7^OSNEP_m=JLqgQx`CR*2%D+Hya3SQA<u8uLnp5gbSo5E<x zI=7D*;tFl`%DjNguZ&-+q;q#o4=!sw&=qo*K9UVrEeG&ljDst59b$0vY5wCuE?AUB zw6ujuDI`(F<i&+AQZKd|H%SiQk}mL3biFE(7QG7m#_#{ELh7$oD}OJm{$5u7WhWkl zk-Z@FYRRZhdc`*t;KK;QIf_bv^pPt62bq!0Vsm!d(We;>kXP5UQiL7nwVnDsK%8Ws zV{?ERTD%IQ1Q$_^iMu7$cACN)6&_*Lwu3?MaKjPPD+3L6=(88;8+{mC3fb*fa006w zdC-y-Z=Ext;<yE9fybf46fE}x&+*8sUb&Y8w@lFYJQsW)Z{0n(q${jLZ0_I&x@`}D zq4swwKO$NOf8l!y=1~0bOYLFYTi&Z_JI9pKZd-fb#nq_sar)fOeniZ3hA1c@KLjvj zC@=9J+7_axfgn3iACGJai7Xzg$H_wyuj=TtxI3OSt<shvNrS63QAL{ta&y`jVRE-8 zP~~FVx2An5*Sy$KJLiVdq?MW?r1iZXS`HgA++fTW0x5*5Q3R$xRV%L~Ewi*yyBdVE zk?YXIj4%%zANyefN#X$b!-F~m=+AuD75E8sfOXig=S#Qj#uBw9$u2<7HgtMQMWpS- z2?!q27)X(}56)jyc>4NFO3x)Ws~cB(X>~6+!9B=YWiXGMq?#gtX9zH==iK>mPDs@@ z?`-SuqP-)LPmg8%(y8~PDadc0baAaGwH-{Gh*Qe7slrDI5);)bD;y5wJmU!!`4N*B zE*6-`)AGH3zTZ#fJ_5B_3@r|`vTcNGg&9q+&Nk(NAI?Y{`R%T?q_~1ETAO!qF$;Bn zr5|T}OVK_l(dMEUwPGil%=kCa9cLTbXCCGC&f!wt+f^m8Rv<Ut_(YdrGiDEzTDle@ z7lJ{w{b{?WiEd3{Y+Hb9ARVv6wg_vTlpmEho57ltvL?MsN7ud@Uo__Z<@ZWi$tapA zIDRUOGZX+qFa)fTn7@(+(%vn=FV4lqVfBNmyaThx?H%}%Tw^}Imo`;Mp>4`z&J}ve zR(4kScjsVQ#Io4b52Uj*d34oLb<hEQ?4A=4Opw3wumiJLwb8YImZ#L#;ES|dIKbaj z&)4X45}$=A`tQm)PSyHW6C$Cn!8IAi5~*%r*47G{dowl_niuJl3E|3~fAtjl*Uu;a zeZT%+PA2%@&zm%)AVB2rfk+lk8+>tRATX5cyK+cc;w)7r@$2#RY2PX9p0S5hkCglb zTO_rj{ixu!P;bz8k+k`c!w8b$&M$meoCTamn`aizNW?f2KXz>kyKr)CsmG;%>1OSH z++`qqUM{PK4f^%s=QD2K=zy3mv&haS#uTKcBOVgB%-WuFYnCeZ*;Cl%TPHE?8ru<f zUZzTE#glS*K%KQAi%A{2;Fo>fN!C3}_dF2L^am)3DnPkJ?k6!iPAL2^<5`AZABW#r zb!%&lG!D>MuzztCyvuui&tz1=+UlM5`<q{265r@N077r<Pw_ZAkDU*z_MEYExfCiR zX{IEZ{~`3;f_;b&?UpgbC+TCk_{Z;}q2uF?rmXxu4MSbR&dfR<o$+>N062}7ywJbQ z)YLfHF-H=xX`d3?nDr-;XKSjRCP%#MtpgL~(=K)iUmQKRs7KqyMPVSHCmG^-o#}s8 z?Z;x+)gnWAx9vDOT}qcX3HYDqYpUfECroaq4}BV6y!e!A{wO;@ff@9p7AJn2Tu%ri z>wh5%M4~$7dOI$7`PKM;YZCgI))?k7@@2(K?{0eanl}91((>cXN})r=S+-R%abn(T z&>$pcwt$i3m!Qo)vL>IxZzk$x7Z<04`hifFsP<+J@+%+sQq%-nl2rW;k_xar$X=%n zr$aQJ<Y{ZdG*$r;Zq&+ZpVOsjiVM+l3QtVbaxpU_8=y2i+N6!9K2{zchzxC5N0dAP zm$Py4JqP_x(>r+|=ApyvnHv@ZH$A=vzdWUqB0LM$F>h!gg}%+Scj$?bNthnZatm;i zs%Hq-eAcT2M05LSm=RBXsf_{`VW&DSkf-{E^ZNt>^MF>EieP}}ZVhWS0{h*P@Zn~p z!+G;zTG7HY)^Qy)@E63e9xcWmJ4nwy7Y<hX6~CN`&o@S*%s9NA#HZacY8pN&BU(2u zBeCEovd(5o%JmLLN<!S8bgk1J9B}7JZHevBOJ;FuVp0v`9hdY-47h=1>VWjzjxSt` zKX=qqw%J*6>r;Ri+Tpc%%*kTL85Yn-^0nhD<b~c9KIMHeiYd_`znE<B-GDSRTU_>` z+zu-yX6kGi39Du{9MEt5<O+*OFfpQAjnvhWKjbGaSCqj8#V*YG`JhC`xR>57cwag{ zx{*`1pjT>R)*No@9@(#GpzW}9nax+3=EuE%Z*<eYOQY~#v#0Pk7kd8<pBR5H(f?ke z|D{9+z$Z9H0z)IQY&9%R8vvW*hS%;AJLC3I&?~hT%S#)v`~kLnLN*m=T^Sff`$55_ zQO9=n+7%U^0v+NrSi>3wmkkhLG126xI{V4iw{*h#PP!H0yE)fz6;~Ff1*6F{BYj`A zOQa%nHb+>zUsUQQ$s;_VmIWq3p{pUcId;q`5MqxEb8dGeF7?G@6r9eoaxNOG<@)-F z@xwkQ7Yd-v7UTeu!FYh9A>i9(3zZ;;*-hr@Kf@>OYJc3Cv-*u3LN_%>Ua2WK&-Kwx z&H18<h=jHN!-#NYpH6R}#CO;XIu|NVo`mps6KctNE<}61hq(TM_h8H2Xg9@_vJ7;p zuW(?qO^+0Oq3%6Rf%pj$D?k|Ihv{{T2@!RYQS8uui!IJ4p6J;p+TpVTUS9Cr`0Oum zOr)*$1M$3e1DEe=hFF@`x!r)(7mNeT(!{JkK#`iBKqr(D+8Pvx`UAuRE~!1do&OB} z64f}gv3hHwzB$D<L-t@yiQdZKKHG8a!sT9WD71JU2wu)CH+(`^GV4MM9L_!uo3H&j za$e3D{15`GDT--IZ5XQH7!OkCdmJu07on}%O_J-jfUWh2b6{<Qe2LzXCg(X2rxpbc z)eL4$)%7#JU0763Q0#5)jep%#(V`aaO3pmY2iV+juV8!#f|H!RZ8U*`veP+`98pb- z`V%OVr2_quk10x{ZAY_h%U9kfLQvPG{bkVM&n7}~E37h;yDH*jL#%^L$M|Wx$RV5S zM-a`{H&bB$u7pdYyZyKiXFNG`jw^)L^({SV`;T@*@Fu6~C)ddyYxA16!Gz4!O}LCG zDq;q#a@zf<UdXuZSS?+qHG$thzql9vvCZ`}kO+5lSlK}?B31Qhvk(Wv0f-43EUG8> zuqsTuNVF*k=TuU<mHv5zehXt7W5^>FZOp2C_%Yf1))37?=%hF=_8Kt+-zRpFI9h;s z77U4R(mGr6!<Q7S)|$*eiRK>PW4_FHwd+1Jy(<@6l@6{Je=?Wykl5EMkN{RTX%9T= zIn=(`g29;be79~igeKj{{PcagA{I}daXnpQVVHY?#fApKrsJK)0=FauM&Q93UD|3B zaY@P|wY;9f*|jb{d<A7Cz0&6tr7kUMCQ2XQc$6S1oyX*Hmn|<`tMV_`-QVzr{?{Ft zfBt{}<uqh}-lxxa41~ow6EUGYK#A>lI0zuWWS)bU(+~F>e*qTM?*!NHn(ZT<zB{{% zErS-iVj4O-5taJli7pKQhh9bAj}MwHjM_^lAn1ClHwCyCydKXh+*xSvG1?KMZ#I_7 zmP8)Zr9XSg@-nm+XX5!=E%qnmOhQu(5s?6wsI{}-G~mmfvcjg4401IO9kqN)V^tF6 z>|Y(+bCEhbq$~doQTM}2{oW{QE*?u$>NHwG4*VqzgzjpH(uzY&I%7$Fk>{O)UGs}r z`aVhCX1UIE74(_3zvJ-^_Vq4FExs1j4vdN{XRuJt<fereVZE2OTJN<f<}l=gkxOqg z&$Xox5*C%PkTbEWca9s*(3%0}!v?1!;#{AdKr#ig=FEnJ0yJoqb_t0_q81uaf?6(m z=BNHiVW%`&!Q#c6(<6$Lt*cGPu||wqz`b*j1Q}T2?f~l(I{B`>gR7HrQhvM$_)%YG z_$;8x{`352rPsbzt1r^U2mrp`HDW1)yKb=U323hga6zW+h<_F*+3!<($;c|o?LR;- zeKq$nVV+@ye}Is`0)a1Tz6NC-2?Pz(bP`wakmL?Nvh^L}U@SbK_!b5i7a#Dtw3A3_ z5UfRs>hG;8@a(GfGUeJVU%XeMA!y7oSxwUg>pTP#SK>RsTh83o?!;Sxt<+k#8t;*6 z7;UfZEI)zXXgzhrqE!K0m?aT+Zv5sD6?%h|Lj#h?I}|6P_G3fkJnb-volT{rl|+HQ zRu`vDi!ra_ct7b*bVJ6hq31q0dvAlZo`PnUYVjAdrRh4=Nk<(#T;`dffC)4;<m)}# zy~~iyekB~tzAHPIJ$fQvbz@k=h7fD;(&yO3$gRLWPVRzg1&yvG@o48Kfir@7VM_Y~ z%<2yS)Lrr^7k<w#tkA|Qu-?&|zje7*?A^O%`>Jme5xXTRbehQoo8Fik51BwXa#S%O znolQg!PV!q0nInQ9V=X#Rg>ajr!KSGhKivGNPaeSs~9=wrTMNXan~Zl_^dq09kP=y zG5hF8zV%?SCE4vAtoRP8;0>O;IDT)S=y_vfugo7H1Zp<}I?^JQ@*pMdb;Oq!Z|-ww z0FoOZkimcqu)IwM4|Ch&B0Gf0{QEuHqDl6RQ{eql+pkRr7~qt>B~YQ9aOxFQdgnfy z6}WDFXqx8d?%yka7SO+Pn)vt1|9`6$_`9q5Z@)?ChOt-w0Lht=IH@0iZb{gWHG(?m zf6F)Id_A};L9$!GuO#buF>7k!L-u2H*+OOQeHc_)q8w71-fW-~y%dj(>60`MGxt*N zo5{!~U_W3n95f%`*H6kiJ%P@66kLPEtHYt<Ea1(IH`!IYQ#S>9I3<Od3AkJ~#x^d3 zNiR6h+i<h-RQm0}^Oj5u*;U7PDfg#3q_zq~C$VR!(o<N0S}!MoIk0jV2s)>x-Z!a7 zx3a~L85er=NGfjEbJOGT!O#c#yizlz%erDIq%eZn09a?uIWm-kat_g*4JL$fZa?pz zEh2!mH@**r$f0azuM{?iH$QuPFLtL~V<3?CF{eP@;UM6z>15o}zDndzUwv_5tapV8 zOqKOk&Fzo*vYult|E#?KnHPP!Lm!W<qmI92L%onlI)=JKHwT2<+yP*D07DPJ4X-5a z!-d#45XCTPe_)WEZ(6_?R`%=m>CfCGKLncGs`CCII&`7s@h;=Y7cN$v)9_%@*bQ>d zs(5q<)2t>_Yg*EeeO~Vz;nJ!iCu4f%2jDkN319-&N_sH(%g~0|o1D>mx|$g@YaPNT z=+N_?%gmG7a+I4uyv0vZASseHz${)pFspE3alz4k@$-l$!Zn-V_1>1p!e8WhU#Swy zZjQ)ny5fm2&oP|pnl4E{8o^OEi}F!gFzjraLxu{IlWx|f3hiZ!-h}MzUd!0}4K+G) zkhC8b#tWXa$v3*sE)DRJNU=v<E|%kp%39usBmMjDpwDaaiQk<wUI_Q@Xv3c_!Mwhh z>U(%CpF_W|d3WBW`Ge(9p%7K#q#XXdgM}C~7)$Y5y^1=qTX1adPz_}!iJ@e=>zfZO zTqb=2mu(-^VPrQ+e&@L%1y8%^a$8R7s8@jJlNE*}hu=t9b-aq@BbFJFsBK<IoZ}}* zEo8OjwLK0S#Qn>{AUi|#E01#W2-kd+49H&?v-^akZI&R6jEknw09?tip5KQ#_+1l| zI^yk?@|7)Z|BEIe!3<@ON_)n2G3gBpW-aUX!K|82k6=KLu<!TV-;mg!J`ZQ4f~yrg zfLR57g=@-dS{g>7GpmW9UsbDRyP~9|{;&8X*dByoSX&ZM1!5nMy!Q&ST6uUe%!4)L z@#K=pM%duu&Th;n7juRIFVGe4<A<RW!h_)|obl@HalLs^KQ@ZwI90}bb}rZPM}yO% z<4HwnQ$)t@4d#ZK#I^*Mx3o1P=V*>jWlaaoK6dJ<mTFFLGw*#VNPBsI6lD6BFqVH; z8|`0h82T^VccUA9c#=nDDN73A6gFM;^n;)-l?(0G)+#DC&4m19eIacJZ%}!!<e-RG zPVcKdn_>PXI>Q(AaZ(51dW5&izWwckiSpg!F<H8BQZ}u!Yw&yf<CKF|Bx_Q_@&h)7 z!uJxCqRa#M;8|g++(|(w7i@3<y0*d%vot|kk2SXC3*2c-95^5VbeCeY0Jk$$&;VuU zbH?roOV<r(dd`}2cnktXJSHks3Vxk9n5=oN20B_shMLSNSPg@r7O@Am0rK%~!2;%q zn~GBU+#_7kJOvKRHVeY^g&d{M^T(?{(88s!I+)OrBR0h7P`WlWx;d9A?^@c}ynBL3 zgrK-L;;U{0{0~q_Ocz744iokil)wzX<5IFbztuRuX$Qwx<s#SFWoq&-k9x5_dPSRf z@S)wPukwq`a-0vyCJCPt^^hc%MA%gD^F<*z1}pVSmz}(Gtd*{b6)QYlDs@Z8$SYqu zWi^ywVR<oLb(}#l<yea%GXBV)9b!h*s<MT7^kPC!Px<(k<!<p#DkeL7qB5@LXS;?; zp1oDHz+&+DBNGESZYQ58ZZ^5!)J%~pUli5-EYR6b5-SU;xOOP8;tt;5XogljV1-Y_ zyXn4v>mqknK;+PatPVu_r7AmmNuy@m=A>qdNd}d<Lme43e$s<??bDxs96uEub%{aw zhLm?Xh^_M^CzNSR-96>~7~bR?@`r}WHWPbId-=l1GoKKpv0(P~`r8f8hML3DS&DY| z(nl`GD)fa1`}L1~Ht9uhE1y8RmxX74bh41I_x2uA`9okx75fy^AI)FALPrmaDh=t& z+Y(}Yk-qebCgsxMAT7VXu*SG<z%36efCzba@OlLp6%0QUKg=T7I6BxS+nEKqoEh;6 zQqc3Xl}%YxcoN<v=>w|i;65pae6}Iy5o&uicEwhI*~K6+y-Nl-uid-^7vtM#f~u<m z-Hy-usHQb~n%`wRqeN?K;@4=n+%vpMbJGg#XX6sj^Ipgm+z8Z7+KU%Gs(iV+QBP7| z82hxbDq1A!VL1QTp&MD^0Ul}A!(YIB@FT^e2VKC@j-gIswSAKw$=ZN1LP3fXPt^8# z6F8_gtM}=qz({-(CUJI2Ri8z{vqg@D0rUYtiW>ia>0Tu7ZI_*2>@{aMP#@;w^1y{| zammqPxL%L`TuFgppv1j<*JKhvfD)5KRVOWs_6u=zEaf42V%3oN^DPOtxHUr1xa`I@ z37Q!A{wt5~GuQRZ3{GAe@9%4n9(Emb^rRp40$CgP`{*|HGa8-eh%$DsJ8;e0HHtYH z|7AWZ_nKiTUBiRL2vj9L*ctJZ?EZD29_~q$B0v8G+w~1_BZ<Cgi1h+_t_4#@6L**- zj6X0Yg6f}<s#ce>F(okX?}WLivok-xpkK(F<v5}2^L{o12(V}L2Bwn<N~(N}fuRoL zr%ptGywF;1GU5)=EDjrnAHCCOcTUc7QUPTn*WN%AnW@Y)U_|`L_>obX;|Hii`A-lg zzWuFwT%Va3EU0G|qws<4_iV+f<flr>FOiqeNz-0VvjcrZtc7($9a$|%`eXh3r}T<F ze@9FPh#HuNPWU|9kG<@pTxyy=I&`wYwfLiu%`YJgh_zgaY4yzuu7S<n#A0U`Onu~U zPW_a(h5TfK)DTdy8_WDzx9S*PYZ$eNa1SjSFwr9beTHXRj1Idda{!6XoTQaq>*mMz zaUA#U2t6lY{SNm9T+{I>_8yO*Y&wsF+TLe;-D4(3vtsy7asWZ+^j0V{@hFXQwGx)3 zPrqfSpIt?Ngx0-i=^i2YK};z8SNM9wa=08RCry)`xNRhMmK?RF#o8-ue+x}Gww->^ zfKjTGx$`kAD~PLF_tS085|H~M{2|#8`U1bJxJ^JrOJGU<7Z=H)%cf6f+-C53Vz;v6 zoz+-=iP&B2Zn%^S!c%CD9pJp=B(aL}Z_5{^kp7NB0aqa^NxRTniMVq+wH-qT4M~|w z{SxODo+&@Pe5O9z&ExJ_(c8txx)g?qA4rJ93{{oPFlag_<<-<Sg5a^$KlQ`=SZFo< zJ?n+IU&fZYngM@GzOEoO*8IBjU2e{)EXfhRM(+Esu-%6SW#dlX*LejiFydToS-LZ- zwV!Hl{!pelgAfg6>h~*l93iE3Z8FA_PEzDyvE#BOZ`ky9zJBbRf64pn@^@Nwk}kI) zVAfvMMB_}?okcY14}bqCu*$kX&iGhz{HqjPr}ien>@~{+;Ah)mh4k$wg;ZLv4$&#N zb2*Uhda5R-`h&8aZ-q|6qm0R)1PwPEccn)!i|~;dcZpAQg&$Mw$oHPDIg_nsffy(F zc}NmQJ?y?L>Jct`J$Yztr<<}^NxSZYH!$O+NmvcTvEn`YfH`+<Z)e=HTs=u<Af<Q# zs%Dp+8R8RMRyp=Wla^EUykNo%@^u5BptW9R28hv&0N_MG#O&Jl<gotGD@1BiC3G@g zxk^;;P9%;%)sr;fVOqaGSs(0j>#UrAyn5XbgBE7cEYdnx5k;^!OLk&J7!eoid#ks& z$*~j2rg_mrNrz$S1=h=y!mIQJjLI}mrwDUBRVQGTHt~c4-sY*2$r`|Rnq-HDiI}4A z=Lkgf?y8b5U%<9}jt=iTt$T(uDh!H<0_xm$xo%P6-<$ATIl`z`!^;ayU-)U~3vMi2 z3N*I*k}D~&!9OP*!%gWDoB8u*`~TaMGHq;9t!Jpr)BqAc>OOd{-pf7QVctQSB=j)- zU6xvr)KjC><jYyEZdcAGf-<dUnviri+vft7Zcp8w^4|_t+=nP`*xW})s(_^%jQTD} zmMhr21kvY$kj1d7UNNbH-*dgoQj<T8n?6V-{??Em`U<s%&EB=W&ST!>n@V>-%S{U4 z@OlH?=YK4lg>ZiHSe@z>Z#$>-n$4rRT;*+_3i|~|>4y1RLk$YgE@ZeoOikqrm$4Cx zHfKortF{@h%2J3Pu_G;rAkMR`s(w4GtZ!;*#o0loy&bDp^+D=od2x$03#b!#Emvd0 zKC3<f2IrwlPQ}G5g2kv>klk~>xOt&_r3Z~|BW&MFNXd0V0I~Zy(3ip?x-;O{P7s=Y zctMCS*u7(_k+r*Y$@|p%)Enti(2%<s!4~thS5idLm${J1=8T^Z?Ak(<N5I|g1bP^c zBJ#%-Z3Q}W4YZY#Lu@lGC#wTYp5ebtx#hj+k8~WAY->|!uvI)XzF6ew6fT)_s7SJo z)aD?s=+9~zmfZFW753~9LFe6^to3uX-_|%DnJ?ncCVtScZFA7$;%0m~kbKL<lb+T& z7QseU1zem|)bR~FGOOPBPYp=Ciz5IXto~5pWu;!yLOj@P80{c*E3TzGE@j&)<b+7S zHm|AGJx#Vh<Rg8J#j;nC-F}WSJFYl+Ez_WzT4+5pmQBU&^312X8D9-tc_U+k@0LNc z6E2N{eBg2?2&~?*I{DqgV5nA%3R0wJa(l&P#vf_eFLrjOmZ1XPAld)!x-hQqLBiW! z(ZSb4X~)Rwb-FTg5&m<hx0YXTPADU4=8-`6ERNVBra&x3742nOOe#J?;^=}s1YcKY zFuZvG%IUGOx$c#)Q=x-l0->TXz+}eNWKw&|J2x#RuNMg|cG$AELR%wTh`pY3G8^{B z!UEPV(u$Iq=a}xtbeOZ5vL2oxe^{+$!9@JfvhETdO>Nc`3aE5;9t(gfW<4o(M)i$< z?m-_UNcj8IUpD4;7-D((%bWo=awtRoNl0O&az)Q-(($Lg(_3RwhEA0)C*^e`w*!AC zqWYZO<QcQ;UJn<4$c)M?0I35cRzSdV6d&f{%xe$ZAFL}y6~pWtLw26mWR;VP`-D-e zGmz{XHcdu)_BP3vF28nZ{w@SOfCEr(!vleO7R4zLI-Ywwy%c^L;XD~u_Y<}sFyhDk z?e|fXYey3_B>Dzwx};8k`{Jts*JH(bF^JM-h3mKK81vy5PBOHWDCfsli9b}GStw=< zEr3Xh-BW#G`@qRTuO!TM(Fnd^@&u$7k#X&U!NG#y9+&Fjwyxmdp#*ujLn*J{xSD)v zZ6Y9P*VzF5!eD!3Mh#Y@^_0oaa6arYQV@pl$Zyb3>3Zo3Vz&)@IF?|Cp~&F|>O)qW zfguiAkHAoXeq#0n+>uiSJqkQn2!4ucZAv$I*_o%4o$6#D&ksb@yJr&2qV}=pRv2oK z+ERAj*22NWzTB8v;0PdMQ|3wi<es|oIbO4F@bT;BG*9+sh%(I=V(3R5p?yteJc4b@ zPOS&Zx)P@vDu5o}Q*gxw!}U(m;5lOVC>u-KAE0OB`68rRFOA9%NuT9?%wg6SR#)3E z&Wj^&-<ek7r0cDMeKnbzLGo;Dm^iBMTh4*~DDPh_#AZvdH!{8+NItlF@);n~I17aK zi1PqW2l$;1Q|s<(O&@{y;wkzg0in2KL%W6!Gvya=V@_t85|4x6A3=-E_i3IHbap~W z$9;A>$=FNyPCl}e*69Jeu@n^{Bm?F}NkbSEtK*vOg+%5;U)?zU3$3vhX?S2~@aB#i z2<i~pzpOs1?A3H)Xh&Q|1eL>YXrF88Xw3DJi!$|N$v-<NB52+;@^n<P2p6O6)18U~ z&5%u35of4lp|Tj#%7Ex;Bl~k$X5h01+4irB%Gr|GNBdfzG`j>mVMoQ1EoQnLV(<10 zIL2Gu=cuwJdQ-=Bcz$#yIEf(cPW*Tw)gvZNOdbQWk?&0WJO@z`uNSNV7j|G`^0_5n z3DMDH@Sgte13Ktr2<=C3DLXR^mcgQWLz$-oUWgbI_p%-yPyk-T<cg^3w~y|LKZcO+ zVtzR>A@9Gwv2m>>{Sd^0LDbLf3iz$I>?mlOuKL*9&rt-{#7}wGRxW*DMn&1QMB3D( z%pfC0Z+>H9&JDgF@g>6`J?YFk9f~Y>tN^53JCbTf2&yF&u!lz`A)TjRG+crBp6a{k z&9$lH0h?afP4CNnl=#h-wmNb6)6-WpKLh@J9Q;%9;{S76%0Gn@J;g5IG)^%oOR?|G z9ZjuzI5`SxX=pYkDD_L}U+}wbu%0d|=y73a`0+Jf8d+qOmmNb^NX>R@erbN`=Invv zvXkS-x^5qOZ#!ex^!U5_<~H?bLCnfD_68*DSWQE%pFpgaVaZmeSB4?qk3j46PqQ_o z>hgwY=_1diGYs7qh7IY3LDT<0;HIsqsPxJedlQ6&+NIPW%Djli0YO0lO8xBEOBEg& zh7b9;@ACY^lQ!x)Ng$45@i4%QZM!wJ*1d7`k*|Vp20=pkwoA6wkkMjlb#lva!^_9l z3fYB0pJplgz;<E7^@b2kgUlw8dx{SlVximbBcz+(^yu?6zGQ8yyJs=N^XEf+;x9Px zi^^m1Vsx{$3QfD>1GrNsv+prJZl};CYz+=c#_+si;VK@2P*{-c;!apr9juW3_y6F= z``h>6|0#{`zf5_1aJ0Kka=u6WnxN@X^36$c&R;aBw}TNq-4x&)pVJ|3Tb5y;ecB7> z_LfJrUP+J%F%}SeWrl`3P014U2S|t#G~=IgXHc87gN!V<Bj?;Dm#u2#)0vst=@&S% zZ2J3YAuU9b&I>Ln<Xz$p^7nf~)V(Sm`+O`~Q+#!w1$LRh*uQ@oCXVk%cakI_T;5h4 zmpU5@7Wxi<HztW{ZxKzu+8ZAV6l{V`BA!6@ovu12U1V_X11wmbeLAO5CeFAZf<Tg{ ze1*3-Ep-Yn!aJ>~+(eSChv3$*r@OgtU|TN%llm&0Q4Nnq&g1kwMUZ+nX|Gig?pOwk zTa)@`r<k>t!jEIs^6URy7U$oVcL#4Httw~RXypTj&udQ>`q;it8j(nj4Oas~O5MHG zb7Dfz43eM(M2aRIvGV(MlFu+XiF!IIy19MU2SuMatUO!9Z5GTpFSZ9&N!N2U<h4{^ zRB{i$0NW>+C4ni>nx+*#iVtJIh#3#eU;#fDE8E;Mb=qm%BXNAuVYE0V@oFDZw$j%% zgMD_)_q4y$%|g)Ev7`=dwTKQ0CoiX~q-QwF`IaO+8ucQ$dg+Et^!CAriY??*elN)k zExBXOlO{a{G-=UHGTi(njZ3eX45=&yZVwnbyXn?W{XB|LK_twf`6pp!1df)9t4)q9 z!5eixfkr-VoiV;HJW|EOF5lVQ9R`YYwUSo(Hrj#-wY;0+{F=c8PydzJ)_g?7j$(i~ z*CI~8%G<-LU}M%+eDuyUWpu{3C%f)`7e^FsN!v2rXMp>Y!XlJc_{h!%q_}~0Xpb{v zjei>N@(96Hejq5Y!Ia<$k-X-Ld-Y5x=|;nrtUUObb>Y`THU4cL5DZ<s_8G207>ghG z4waqVMZ)z-m-S--qC4C|Eqr(VJl;qS@Tb-k@ru2O{O~1n9av-Ko+}55Vyd7jyjfyv zK@~BxTa9{^ydv6Q>i`=bZ5rc&o6c#TKkVkH<e$A(+Yid!+F0hQM<Rz6G7O(=G$?)q zaR8j?e&*^QyNrEym_7vWf}=6<t1h;Ih%O`10+x*e`P9v94>kTkqP<F5Jmjkk_iBb# zf-<v+By*xUBa%9Y;Kpisl~d#z+Q^MzcvBjxL`}+?NQ!tLq^!Nk(5R(ZHK%c5xOB-V zB<QK*Ezj)>4R{AoFrv4D;z%+X2xG%?R>RH6k;}}}>*SaI_Q&F<S3WO8pR1JYy=`@z zo_k`H>EL7Qdg<0^PMX)lZK-`w(jknb`mP>+9*9hUJCXcxhJDUaxt~yk<93e$wYr5~ z71Tfh&w-HUx;>*&Sr%KJV5+ve%)vU{Gyp6GBCof^#3q{}R%-?I{qQ55X9j&pEWiCk zoxTw_Z+PK%k#f_k_6bckZm(>7xdsJYl+Jrnr}tLH_)1}zwcE9v00NVtn*?>70aF!A zi|}Ah>+t~O?N!_P&(Odl(?G?&`>2qln8}i9Nr4Z-zqUO@_Ma}qGYm#|NW-0*$?ISg zB0ig6Oeu_i`!IJu)>qT5%3<szHsK07MN^9}0W~!No*X8K>g|GV8hgAM?L7<n&M7=d z*bH-b6opVFU;yVcjv%meR82-WC0UWQi{K5;^P{C^qJ5qR=^WF!cOQo<q`Te}SNurx zI{<I%f1Uy<9>c$I@NQMk=HD-KPPw+<zf_TO>&}acCEVI`A3v4+q_0oh<)rB{$lI%7 z3C>&{qAO%5-mu?`Dn^ck3!-4vQf(R+gKp@p4z@*XNO0u;*(CsUK*wCv4>~AF7h*x@ z?i}@^<Fc7gxu-x1B=KS9@$ET#?HBbIQGEz(q9Kb-aD<E`|5?5dg`5*35Ki&{yN7*y z8;OFkdqHvY-#%Tci<qx|t~A83`Q+QLLKbmi0^9(opw*7qD0YbBpj|P~Ifevi+wj=$ zV!t#<nMmvSspBQOfy>+KGACte?@{%2$%-ZSm4`$FWcT{V#yDPZ5yDyj{yKkOx4-X$ z|KqQptN$~rfHN6!U=d3HXAA)L574x=Qz7TQzto@qU2Dl18RqAl_`5bv(|An_a-9^| zR=C|E3dr$FiYqBM+KInMH8U3L@@`Kltfr$9UUuD}hE!h~C}&Cz&Und49$V|EdhcCu zmH6VQ-{<;VUu#*v4$a~>;pg6y3J2e;O^Snez2g^cjRrPd=i`&wis56CG((ijvv3Ij z7RCs>(~aQLhQRW={s3`+0jY5UH!R8$MM^n&d$=rznR#mi1olSyspHJ*ut&z;4y*}b z&q>2Ow&6-tY4TA&Re}tzw4+XSa$#-pjwa@0fkOn-=hC7lC1nXJ388bMs|8GnGG8Xr zj#Zo{^&h7%wne8sc}z2nNeh^GM%dNp(-9KCKH*<`#uGhmyB!Ld(bOne#35L^CF<2j za~r*&8y^m{=)6azoov}$m1#Qv1gaWOW95k(I!VsFfJ#6-#yVFZ$=ABinfTU0rhl)| zr(D{~q7=oH7&4?ibo9)&I#GD*%DePge5NwZoxw03xUJusV?ZgncFj}!91fP*qq@R@ zy`<9r>2>a5l%`cIVZ-N)g2g8pFO@sbq+j}?Lmub7JGcj8)P_5fNLY3f%lDh4v7~}I zyy-`0c2w2&sQZ?Lf}C@<so6c~%SL0~^i7@lue<Mr+%TzxU|!nja8@xv5+p?vN?3`b zGKmX9YO&1Kl-uof=**rlu7FYF(RbhOjHyIH8}y)Hiu^OdbWyQ8;;&tlHdI(>8V}Rr z#m+a91iCu`f_#4vv6tX@8BgyQ60DrQb?HOp;+DYNQ%1Q@ufCmkJ06gU2)?EuqD;ft za|Nrn+79i~1{2E?;RZ(q7XVKgogQ|gY<5qlK@r>wwd@!F+#AmqeK_%9W9PftW!nD0 z%J7FkIa$^}w!><ww~Fc05g?BdQpS2AJl<j4{-W0pUKk!T_L1mShg#unYg2nxyEUt+ z!^$bHMy~|p&5als`w;wpfF9Y3imdANJK7V(BOgw8oLTM%W++Z=vp&0UGugVr9^aB+ zX-8r;&zNuOja#t0V05_FpauEutayD;ze*b6b_-{JzPv6;W-R^zc5WVJs^QR-!cVH6 zu?gl+HwM>7?QEqpm7yQgnP!8&vxg8E8mRmvx6ba`6spP`R$exXNZERR49nO$_<Ebf zw-S#m#A0H<ETuFx*lBw-UFXr->Tr0F$i)%Wmra!+hpm~3FMf08rAm!*hKDMUMT{mC zesT(KS-%9wmbTtmUbx+OYhdrH_WU`q;PjWLKHu3;pxItQ2_#_U@tyO71ldmF0wLx{ zsB|5%Hoiu=<q_O1K&yurYZ0OVJ&ct>?xC-X(=YfQaP?KYoE0-BO*tU&i#R~tqDM*_ zqzVZe_S&eJ#SG~1I_ITv!wT$^;##c(U!p4>%Thlj+nQ_)yj+B*52U@MVN9T2ferQ^ zX)6)cQnV$vgdu5uR5>yOl$|@k*3y4+?~Z{0^if2E0?rw2D7&(>?_u~0!A7%g4r_BF zKO^=W^@ed$rC<nCgb8tMz!qhH1#T81jaEmMRwLY%sy+hTw8vP>n!pYZuh1W#_Gq@L z>a%b!V%uHPN~Hf-!m@@ROPDJ~eFm;qwL(rkI5MATIv%y$Sg~nG6&QM;IsLdi4(jUJ z4-D#KR_W&d0J+ridKE~|XkY5a`z5Haq`>9hlW`hgpaQ=$>oKF*X0}r$;91!GX6b$O z&4ZoHXTecNF>hNQ()@_|tIqd7HqZaV{`t33X4yCy$O1i7P#ty(3w{8`K94yWs0fxm zdgf^QEa0+lhOO+IWkWd-TRlr<W%E6AklMF@IN5iT1R}6t!l-?3>qckEN#$`*Zq!!p zjShIiA+!{ez4ZRiH28`y4p-cMH71AaGW`4-tj52+Z}fi;FM#!@iCA5sB|CTV3*-jH zRLfw}%sZQBAkqbU(25%0QlCi^fO;{szDkn~%)PByDnSe<f4+4FNHRqfKNjx<pgLLC z*fn;4i*>rG=9lAsP3nKOA1-lJ8yMcsl6JG<{+tM!MPp(om*wsK=XuZ7zgvHMPLNIx zAYaJ*Uu)n(uI<5jB>%Wd(W+YVK0L&*B_DXH_b=x>WvP3B-k7v0z4lUxuS@FeI?p-U zG~i$pOXVa#C|>joWuK}6ueP`6SYUJas08ZX!PYIt+>pFC(z#wtcO54+xXKeRR)RpA z<^NRw<v+}3@pl*9f2xbFj}(?d5g;BL_;CYu3?SocdX@eVdg5%J_-V;8mG4$YL%Cet zCsUC1M29p*opi?T(XtrnC{D%bXVTT5>{oXH355NKji@DnB_8~ks^)JUFkAtcf)YV% z6pdq33LnWZ1#Z2XFE^MgKeD>d`I%g7K7G5w^O60nmXvh??%{^FgS^l)8EvQZP4WU4 zZ-kFC-slw4=F*9@C)mO75kp|tqz~--=p0b$-XE}rb&LB4QGOCn+B8zt#~H!ERND_< z*2h|xD>IgL-&cjK(^P{3tDh56PD(%3-vhzjnp~*YnDkt8WsZ`FZ-@nLVYNtUc!4CW zP8F=NCyY;19O;?M=BHRyv5EVF94(2I6cR1VRXfkc#LBY{Dg>@OzWo8fh4wor>XEjT zx3~b74~o~aT*cXJPeBh8${cUAh4=G)$Q%x05W05nVHhyxS-Jw6%p@504wZ&@!9(!V zxf&x{SJ!+jK_fx1%GsCPZIZTG;{%A`-U{=l=V+64fIrp&!NVE`q+7lsD>smnWm~*g zKKI*Z#cYV5hyyV^-&I$OISR*VzjX>yxnY^NeVltcfj3@hQ*z*y65v*2H^#E8vxrel zy?b*hQzPwxu0SCmKsoq&t8I1{8;qa#-_<dWa&BugwPc80x%EamLuCGp>NLhb*2>nH zX+-3u(VcJ(D{-70-Vrd~YhJ?Vv-SnH&eSIPzp!_xn`;^wf#yrwdrj(#ehA6R5#m(T zHt!N#XS-XXk<T3C&X*ZgxauK`0i9}~Ut>!Q|onn>(#XR~Rj_yV_=@9pcJV3Q9E z88L(6kyWc-$cZb0nmn54j&xic{mGG2$C`^_V-n9cf<wqUiaVsqaMcH-m54n}u~3O? zYhVsDR#t;fb%OWBd`<gvGNs45evlCmk8>acr!q~}15)woUPbX%sKk`E0!;P$%?aOk z_0zS5b4XWALraE!>J|5Nhw7)78{SoMnOp?9M(FbX2*WY1s>S>MXk(`XGGqfZkiLQF zsk4nFnOBp}x0>0{uw<N;XCKLONKfC^6**kiA&Ra&Y9gT{G=c@@Y|<=a(lXsHMjqT2 z*}Ha$`{t6=Q$C;_&NjwD@g!@kv7fdfL83bp$ZGbJnp(`wGe6lioKP+3sdr4DoCr3H z)fc!$8`jz6#eiMTUK9FRoVE!WI@jCkk)r(KxsPE_ZcfsV<9s#8Deb{vFBi2e1&hVe zfHO{BZp~*K6(0B~niv(_xlcDPO-2%D=laxjq5^cpVSZ*L#ZE8Ly}TkvD1TMCV-FsU zHD_kM+1d8eH|=SDO5|ftTJXOvM^!y<dmF4Yyk<FFv)a_ul<>8=x!~HFCkBPtK`!By zAnD95+VHD>oNyPiK4G78YgvJa=r?2Bwv&w#8L8ZQ9@qkX=5;?l*3fGsW%vdyZC?xM zoLmXWsu>8e)t&-;fog5QX7SX#T4|!duZ}H{Y_ylJa9P@rzVk*C<M#PDGQ(hYOiz~! z$Pp0@74*be<yYFm26bl*YS7<5>$%i!mLFL)EcHVV%X0~YbL%ngxhgU?86V3d*4+$1 z-xN9|V2l<dz&qNTuQMnn-b!-M@m1V!5)8QFY=H`oAIi4Bzw9k>`i7oT#Bm>$ICP4g zb&a#fj2Gr|htwI{aUP~;Mchm3JT|eepWQ8MX!snl@+tq!pxCrTD&ZoN9FP;KdHL9a zq5#0%6g5>=mkhML&uLzR;-_bPR7{mWFPUlQVzy<PQv8KC>3!<6`GWPTs5|c(!#PHJ z#QCXw@N3hxO+W;rDd3C?U{`SeJ!LqHtd&KHps7D416_Ybh+jODY<>y~N%;eG>Zdrv z3dLZG3V~hN?s@Lm)zmRpG`A~$8};^eSE7Bo+^f`E6QJrYDoF>cM01}4&~lYAvtt0s zf|*@iOHu2|z7Bd98#CoN&qM4udB+go#`E=o+u1Wwx~FUV_G4=aOL|@BNvyqK!VAfT z-PYJd73i}tFTZ;6(d?D5H^PrHR2PRCL~lRV2f2Ixsd*yjj#*5@)d1DgZ&Ya-Z;){E zp|(U*fyR}$;c1tpqYbK!x*yS{CxX6G_>ON<bV&WArpDN9(?ksfm)+=-N~g`k9>`8e zx?g}q{Nk^D{<UVK^o9$`!w(gT!sW?Ths`GiSO^{p5!NT&=@s5f3MPOxdO1Uiq1kHY zxTR%d6x(EQH#axksKlo)iC52@XYr(K2cn&OlhOp8;hGpgY8Cs1WR>H0*tW~AVk2UX zit^UU12_9!I{hdey<vWF>-oh(mJW7uH!+6*J{ia&9uv}{ffIlit)rPwF*I<lp`Uf_ zQEIvdpI*!3zWeLX$4{9+fM{>JpAgnX^2%*s*FtMK=9fxhKGibDS1%8X<SaA@yfyw( z9ryeJ^M}PN>*kD(@3(MPoh7!=R{k=t^3B#1Y*1iOcBaC(L$_pXs?ECPy@^+`Y|Qjr z`XKxNNB`p=2WkJ}Nbmnw%d~&q4g1f}|Nf;sZpI`aB~F~2hrG6$)ENi#e}8*vOF&00 z9fem6^XwX;Z5I`uZLZnqJT!*S87&)}5c(C0w7=8Rd_U?*i$pYW_A~=VbCdn(x!)Vb z4>;4BUKsSO*iBm6grR-{SqQakdH4vtq^vEyMRDoVIu|C85*&Pd5gs@}75{**0>W}5 zH=eBe#bP);WGmbbO^#PMoJY0x;x02MF}oWJjJ!OLmVU628m?j7!47}eLRwlGBvVhD zvC$C%a*_n_3tP`&9R;&sp&yuz@BDuCtShP(fn1BH<OP(1qtAoZe$>uq5_WgH+ewUF zZDBh|z1!q$MT)AXiI-=E7^?i5R({5!?qu*IVs)Y@*E=Q90u4jB1`swBjS!<eC?ppg zTN97sRRoa%&WzsKfnrL|9EP>zSW!7kXj{8BH2-Xp0s7J||Mt_qUz4WTfs~GLHI-UM z%1s?EW%1NmM8o>C>vgbV6D;wr`lnX`r)9`Z2PkOdfy~Pcw|bbzjpqF9^Mw1ZE*Ahv zJ>|klnzlUI8lVGkQnYH388BxPVnJTzw%uC+s9ZWBUz_z<!{~wIi2QpCZ!UFh+O(bZ zb$;6LD^-Yj@io*~PslzEFk6yZWB!R?rKrqch04qVYGK|hH>XSU-#N>E($KKkE5l^1 z+Q0mrJpMuCejOkPD5qOwjChAQ>>?iKzYB98E%OZJouU{MOfQJm9OjlJ3uFsFb`zIW zuJ^W7VvwR<D<4T|IV%d_EK}$P#rZl!7QAqBy}MTO?Q(aCgGsUzKSkP3IX(cRnD+LX zo_=^tN|*s3X3G7(tkK7B?YJ+<m;%2=wjgy5hRXZJFSDK$`~WWPMVQ&Zp@;Ddid}ox z4^kW2lZ~v<J1r<|U*x0sZDvmb$7Krn^sP{^)uU@A1rsVu+E3IpBQ6ZK_SaOEk##-4 zFK-tJOrgk%%E;?p*l#@D3<a_nk3`Q02!BiI2d=L@hRr$JL(fp4h~Ag5G$*25ilR!D zvzS@Urd?`QFdoV&IIoK{YCLvZ^hSKfOm=vnC0;Axn%J|`Rm<rSr>jHH!k|D1QyLx- zFUA{UdNYg>)@a$%iitACq;e_+zmL>tYJ4K%Y|2aZAM@c3AI31kWHh@t^!gAq9rAF? z36yEz*y<L{YSNj5%-VYq7U-jA_N1aTul8C<K)`WDE~K&lk&pyWUDm5B2jf{*pdHPi zV{O2`WI;0P*Lv1VWkP?~)FYX70l9|0F3qS!<Lag_Q`+;7nI8|ic>3L*#qF46v15Hf zht5ggfu2rSPnZB1+Kpfr{ke2UcUGb<s*nAFDng~tXT8_&iSXi;fK~$~$%ZBaP2}J! z<M?oRi}=p3V_m3|3<;6=(~JpLK%Mj_s{G8#NvPGl%D#c&PpE7koqeNVXEkyqs82WA zc*Ed{V20~)JqQZaAX=nM0(d16YX<b9=BV5Q0Gqs11m<$A%?>qDv)EFzv@!MyD&Kwc zz*sRRv+TEKj=k9{w}3^e&8al*(6K*2f<Tr-bQ%7nwl{j3GnHb77w1_ZFK$_R7l<X@ zjvTmJn5+8IK?tu7f5$0#l{;p2Ns>y^WkAG*vcnqjqqX6h4+xH|w}>a07;P|YWWY&! zMo~rN3eT89dzzI^&Bp5?8tc@Mi@h>gfpH)S9L?qm7xfZ_hCH^)$wQGR0^|QUXKoe5 zX)l3n{$>6tN`QyovnqxdOgC@6a+(M)XDpNMz-<O2(195b{PdrmGVyMwD{&T0r0b`j z;Gw%J@O$DhQ+>HZll$*mn;Sz?RK(3-s;`cfmft6R#V&i=ysihu^@ukP_8W&@Agd8n zk$7=FB!;4pmv$OzLP+trp1XBr?t1nojU)6VK6X|}`ju|U18AHdSUT`d8%QunyuqV? z3coLOeVG%L@j`9jxoXyGxl{*+^A8YHNo_)&bx7oZZuw2eV0TqXy~9&Ilwl@D>mcH3 zLO~mGYCyd4#|mrpyJ<)%3Hb%lw|!zY^Wa&P*%N88n>XA#E}QU8+B-12r0`sEz3`oI zWs&Y}&fzn_A((%<KU6leTN@I0$HbZW*K@|CfHC%df%u|{pM34=kE3p&QgO;m4cFMd zk7jPfxF%j|208gfiVIQhkisdqbWV-M`S0oxN;(t8P1|Q5@f+=&?p3nUJYt6`SJujW zQe(mnh<QEIX60h|415yXgtWM2fc{tJDR!2aoe?Uq<-|RCdO*#N{rhH)(n-G0kCV&h zlY<K)X7jA?$AXUsZFcQP6wZJ+ruxM=$Z3Qn7vLo6zds)US6^l%S>=^Gz`Rf|q_;ZG zqLXg5Ht`h3I@cF9gv>ynfN?oCZo_>yOwKaqZ#n}Do3|9wBcQg-GS!4+9B`7HgJ?c^ zFY-;=lvV$Q?}BYzotVAPt2f}%h(3{*BJ)fd0s$b~DIIsSWPWXkDWk;{+@pNKld4FP zOGK)CMj<%uyiSW=Q+#9l#2eQ)bu%Z-J(qdisKY=qFh!CVG%CM3TU%s!tVofb_VI7m z=}W*@y|_H^Tg2qd0lS)6?#LZ4hZ~N<mqH$blh%v#uMOA-fp`^w@bJ#0(TXLtcx_%o z<fa;bC-2$Tsg*vfL!C!SHq%A9!xEZ4Qa7f0QuQ85FwvU6DdZ@&B0C>n+dfo}i?4-? z5RlMg`F`zFvkFM88L(&iw9ii$O`@!tu}Qn?uIcldm-_I9CJFZ_gRQ?_DeZ&G@M-f^ zgpG6(ORw?Fj;2SLTM=xoPYjHiBLG1+(DyeX{ZsNa3x>0@xkh``QRUM^_3-2yyTUil z(Pnk-q?0@>$iNd5Per`L!K{9-tU9%=zVIgbuG;JdK+#i6XJ2=HoRrSc_jfXy)fWoX zNCI{7s&LLXu8Aj`Y#(WZNbd!dybBo7Ee#}X6h7eeXRqAX&99NAX@2Z2sW{mzsSIN~ zYeQGAa+Y&+`-%UI4C>!zBL3$KssGeU?_Z|8+G_d_Xk-1wsrk2Q-~NZb#s9HS{J(mk z|0AH!9P$I5^xCYHn==pR00X52(K-=z4m=CcK>c)^Its4vy~&oz@SKli2)bXmra0JA z$Wp+6QqVyI6LCb5%pyA&DLO<d%B^z;mzsmmlQ$_>E0)^HzA`iSRO5X27n`VVWtwk) z`Skv!OY^^4w*Q##{cA=am*K`W3uKpGTu%Jn&VCJH3hdSjg|fOgw>Ez+!InR`)znZg zV!_MrE@3Eb&X_!S>_oQww0(CH>r5OQ?BJdGkyp#HFjDG`o@EG1v%{)pKP$Fh8WG5r zVUs@9TRQuTS+I~rOyQ)m<`MNO><(Tn8ZHp_0;-l=3zs7#EySSp(FaYJD&EiT%o!iV zWQoqFr{$dLsocMq08;#hK>PKy8!S7z*iB&X;A=U@@Vkcn&t+SxKKErmr=~v)17!Do zmLD$mC_iN?OBH$}W5D!O7laS!T+ry7Ex0(NybYX#a6mM8lBsx)^(=Awx3t>xx0&9* zv1r#<m}x6<=>bX5uRKELsA2~1fhXOZrp&RPmy^=mX!aZ8_J9T1f9rY8%K~sVtL$Xx zR2g>jsm#Hi-NroK!mTQqK5nUt<|0alSUR4S!#1J@8KB+_sx8x#zQjg8LZUc1oE^L_ zy{1^Rj{*cT;h-FOzKmd3%sk&XCe!DmrBWrc5a_J)f*gLPv?$~q4ABz`AuiXxm#xDM zAS!UUU#$<Ob}Ub#k?eixI{8q!Jk}crQqk}RzJ8Di^w^oqvavB>K=epnoe*{u8?>9- zXh1va^tNV~@LJk$9*rKRUA*;<FyrRwk}a6KaY?!g5JeXkhvGUp1B~{5%CFGD77WK% z{kZfQFy;22ktOT63m=c_Z%DNU2hn4)USU0}xjP<JfoQgct^Zqf=NZ)G`>**RP3av0 zDN?0`CPnEsiijX8y+|j75R?`q^bP_70ty03M@lFG1R}kNi1Zd}f*>uBAci32x6j$x z^XlyEod3@5%sH>}A}=zz^W>gyxvtNZsw#MUVDd4B5(K28&8!dRNgOv=prd!4*G98d zK0_|VOU@Nl-Ip{+cFJ<Hu`u>@HmS$_0my15Bu)n)2?HaxT92#LvWMYe&e#bPjr|9= z1b8hPKj*K#DXAESD;r!-={9T7sGyUtIq^Vf_S)Pd3?_H*BJYj`>=+$BF=cYJwo0+s z7PJT#{ekB;ZjLR!PM^~K#z^3!<voczAa}|vd4^aUxjORLOo*N2lZdh|PrRD@;~&s{ zbVsN>jDOrm-nBJ}@i5h@GvtC)majT`JY};S0V1f3VjuU~#3$g0e{y0mk3Y@X40$-! zB8mo06E^?qe0=UDZ;t(SwBav$Q$(ILD5rQ<c2)dSP!^~ceb(4<xj8KyZx8P+P#9BD zB~JeXdZhw3emsdY#26RPr~k5>lX)-^Iv95^2`7?OT%bPR^4yGNzsHd~0L7K}RX&Un z@%vVLL!K!evyp#Krmel<5c#bSY+s@|OZ4Kt98~(<6M0b=@mpQj5VLFj<xv5G^u3u# zRZ=zz@5q+_FvOnze$qflUtPU#qobk8M9<Z$tFDi(zUvt0Pr!_9MhIOeqR303(4LnD z2|6N>vQT3)VtAZtafzqR?H<V~)#PLE4@rzwd4#u+r@Y?790STDn$$6%Kf|L~p;6;8 zf{$uitb_LCsc|KAus7E+#s};jS6OMFKs(%oiQ1eux?p>p0YIN1?AMa+%85J5M<`+p zfJaiHH(SZUh`592UYkcC5rus-br5!m!C3$On>b}J_7P>JXT&9B_KK%-^TgK=11>=z zYd}&k8r}Rr0+2g4S67RDA<;I?d<0j+XlpvseR6ntQ4RWja8VN<(YYEiAQ;>^d-X!W z|J7sS&(BK$7gJLAwCPPKkTna^%|h#@!rl>~0tK-9q;uqkwJUG9yV*Y=DB5cAfmu^u zup7MybvRyl0ICp2w5hFJ4d>YA&#p)H<5pO=Y<wFscI@&Ar@z4+C$XtF9&x-N_!6^D zwBj!-y#r}o@6l~0@1u8gIsE7S!8%_N2Y*9#z_hu0n#^>^S8vJ15sxiJ)v4C3N-ST$ z*ShN4CDf!#;4R+-9OalhYw*|(O=5MMErd7^`5W7m)rVd0KP(f_gz4C7Rual{jUv*V z-aT*|FFja$c1UGlepaC}qQR{2P?-K)+>X-wIPb9|<xbJ$?m!g{50y#(q~;R%{cC4i zSQ>X<fm3UW3scWMJ*G2Z@)%%&xx8DeV(iFfzu;w47F{~O$Y@n;6j$Yaq_l`UsFG9c zXYPLg+2RiT8pDacp0s_~!U_|5pK04cX$C2{7J6OZl*IwjqSub8YfeEvE?Lm1&f$M~ zRvZ{OOjD$pPE#hG-UC~FLS01MC+On7_&seQt{?U8^LxG|%%I93N)VgaraZfb6<bq3 z=Gi@Y*IS^dnGFMpmJhMr4=TB+3~bJ7F{~eez{CQMPNG2yUEwMr=pm8ZiNA+#Iv<*> zg=g?I2VYsdl&hxBUZs3@*?L1S>Fal@!hjQZgfkfCAEFQ=%n@*LPs_HF!qan(9W^~s zJ#Tdr-;$`lx6eJL*qlCN&3zTi^6Pdj8+98|YG6)BoFb!e1#t&An=DUQW;Qc&yE?hr z9(6vji5)68Y_O-bjZ2r93%dNAlP61ElE|t}SwFEFU6pPcp~oFX288;Y^oEHL_hPGd zUSRM3l`ni=v20Uid%p*-8TZy^F`oGfjKfGyhLB+%AwldUxInA2#?dRZh`fPi(bj3C zF%5rUY`GO=V$h?+v9x4D@yk@q3s~?+Gjm7f2RUp5R8K+M0cpUc)fQM@wxt2@-4}ue zDR1$IeW|@uzPa;@;@(3x$v*H>JNTMemEV|2!#<)>gYGW|$*(ej0X7~b^rIJl(#Yxx zzny1b&fnT(H@s|!fcs<<%+@r@$jAT0*@tF1@Y&}?A*<pzBar~3%g<h4$|HADLi}E0 zb>jgF+ES2Z{-JHCg2^NS_Udobk5bp!9olvy4gDJ0+_R#fclcBlx8C|^CDa<f$kW8@ z#wAvs!px`PsBLU^Ue&hc%crrWN%mX9^M2L5L<eyHqF3fUF&D4v@m)$eEO`;q0Qc;3 zoGB-Q7w~D93glmRBr&JvRBmg;zVm#=`;zN#HPp-8?fdZMCJz?1*nGVogl|+*k>TC~ z!dwXBJ5y*>yn`C4>2#TU5@T6Cgj?eOu{dwzptZw!76-faE6@dc^8jW;7#LHwexrDS z5=2H#<v3EJKOm}b1C0R{o-6FOUFU3*Dpmxc-wypT>ZWL;pn97duV$5vuiN(3sfg)# zBD!c73KtZ*Gs1gxJ#eFOooreL)jQ2Er>EM~^4(lp+MeV-7-BwaS}oWXPxAcv`Wn@| zBejbT<tW1RQ`j6}ZiD%2YxZGz<1qGkdr3!zdBm(u`}nR?me$RpC8l0KBuZKBe)ay( zg~T#O*mbTh^DaSnZ(-_R`hEpLN=tVbH?cE8XtukB(JcheM_W#|J7Q+y6t~#TVOEJP z`fSO(PD5oYcS&zmDCgg;ssG>9HUH0x-+v(-j5?FjxN4+x4XI2r?X5r>gnR%dtCGJS zm^>Ul3;39=TyzhVGB+xl{@K;lLxlPlSe<<7e8iM@5mKN^439@?8S}=MqN97xsZC@} zCKV20dds$U;JWs}d^?<*wVQ``hWM>|HtG@}BEU7A^WK_am?$M4U;C~K+JM+L<@I9F zQY2d2*^Cy2yV_eBT*N5wDQ1j6a1D*8n(*f6;+E@lz7_!E80=c%43k<!WOkolCZ<-d z7BX02x1?P{%D0X_rStjtJZPwyo(d#(a~Rg&xjbjpXUql?oz-;#^fa1ZFf-?p$l%7A zrsOJ9#yN0*Va&*TC`(G^8HVT8vZ0O9QVGjK1nof;n&t!ZnI_pR+cen%;K*Vu@s14J zblBhKpELF!5M6EbJFD&l(2PmcmRBzP;Mu!a^cGYFXLoOje4CMix$p(GTeTKXfxw7W z)|AcPo*kU^6R=YTxl>hIOxx2RIeu5jye+j8UuP$!<_UNw_NJ6IPGsayBT>*WkV<d* zoW%3(@5x{%*ZDLa7CvARCO+E)R=;~9_yuqhCvT0Gbo=;-dpc!&vv&3fc7sh^f4z7O z)Cpj+=307z6K$zP>euIWD`9v3e#vgFlCNK7WcIMKO<)(x&;;vm)-^{xiMvvKOIGZ- zhI7D<1~dqKW^v^p5fC`g?z#b$0;Z6?_SCcz>Kn|%zJ(24nqHi(YturB7r51|_rE>p z<+<+jEIAkSS9$Frsi}XeqC<LuP+f|76G`_PFjpjv)C7t{Wq*tx@A#dps1~^@;T5EG zJ4A$aH#_Tvass3hE%B@gTGzE4uKgQ4QdNo=6un0L{`Xy5hE%566qDZw_YxkhSqzuC zF<n-8<a*;NXmnE7$?8KL`w}9ji~s31R7hwl{Zx$jZSYoX@uULB*32!gQLT)J;}PFI z4l+R{BGIo|G6z=bm(pG#jY-9Q<%5k_AWMX~ITGUI{|`tRd}#w6wS2H{kgN4uv7z3% zv%R}zJ17IBda8YLlTzNJb9Ksz*xBzodCH5ozrA2Vl21O1%2-}m94zm3Q}JEK^uq|{ z#<qd!TJdk3Z*h&o%_FYBdr@;?!g1kVX+0fsME6whLxC@hw}7iN&gKC>#@RGmGv)rl zBk9g8=L_m{*+Xhud{d1VJ_w?^3+HE_P;OQtqV2~1q+Qr*aS+YCp9ItF)nN?fy_+;M zW5n-n%=IPnOxbh8UtRJxmjRu#lYndl1hK!ml7=zHr?CK(vd%t<`cE80-&BaK96G z1==p~MTq?Lwe6QGd)Y)hG(_DxW`QZ)@PULY`v>|wge6&g3d2{dlUIEDw65Kq=n%g; zFFK<oLHE%ml&&y9Yl3kY;x6jx!+|SCj28zCtiR!mphDqB=UI+=_;uce;&k~bA$Ljc z@%ZCY+meoaoMj7<5N%qXuM={^KNj=d*NEDX(6H`EV43jgRsz)4jbtlIH2#L_&1Vz} zal1gB_&$uC^yh3eP9(hv>YwdyU0s}Hl`-%%_27U;US2N+zbIsgwFX`3tXGfN(=s5* z{C-NJD^`y`4aZezU56Dfd`Q?F56!@0w>?WGVlo~y^M@eEW0_&UFHxuKUD<ed!q86} zk-F2@0h&NPwjz>C(s06Y78*=op?5%{bi>86;GY?RujpKK-@K*0N^>P5NQZ}FjQ<*@ zjkKhEadAg#Kd;^*zch_6okq6rKzuAN&g?u=oB1tvY|dz}22PPPuWPtD$G{KT3cGeH zK{D-=M#Dl(NIwccQgXUG#O;H6+M+hoo-<>>Ua(rQf|I(8Pqx)YJ8e<MVw&<5roI6) zRz<*-whP6Faz1V*NmEt{_{*?U#|<wfiaV3`#+M-G;qM~3MxqCJMAGqtYC%W72ksH# z<DJe6p_=+6{yTVuOPl=7<>4Q;|K3Jub_0L?HpzG4CHAV*O_PvL=o>pD@mCyy${!KM zDZIn79T6HPma7W=Cj?7<N-~VJ-J_7m?`qTW{%>^$tHH#>EabTQ9KO!@O&0PU7*?8U z1ZWfSu#@p|QP5W)|J8JLpBnM2`y7bgv-lWXOfl_t>G@@t%T{(y6e#6?(<sJd^mAJy zCWM>*6zQrs#})BhF}zz74RxePM0Yg{NfICCxrE^IMwuWQae2g9)28VO>4Yb(Q}Aq4 zHL`yKje88Xx}?1=Lk}dBR*mE*xuZ_>A^(8bRiJ(){Wwz^LCIdrDIU!{fio*~B~-dG z$v=MvD^R1S7!)70;QPvVMDI%c>2-k8CxFzaH1Q^#KBY?ph71ns<9}u+2J!_*W@_Lr z9?!lxwc19K?}jA9<)3EI2O8@43K9CK+a>{zq;6gKv+#a+=Lsvm8AZ}OzxDZd$I{uy z^XT)I^?Bg6J%8D9(SGF`9iS2kkLggRSmNwWdxiK&Gbzd|UP4!AoO6M94!$~fOa<_^ zN7NakSC~X0E{JZ6H@i;an)mDIc}c9~rzGK|CV;(?#)z~@(<2t9VD1!AQ1;{QW3)d< zE4L18IcAP-<8>vavj-->>!<23vAg-!nnx(@>$;dc!l)|5P7Z|8&!TryLi+f3+rnR+ z3-;E`4eFF4W-rNIB3G2f;kkoFf7A2}+GTRQXQLX2gg5FYpR!j#W(yg0RF^hQfxLQQ z21@FY`FN(qlqzGWbjg7aY$NxPVAJH_^B1WTcb@b9h`Gxk*u@SBZ^&9oOF}1ybSBg< zo!%t=O5Q(xUQDst{OIF6ReSo&bG_qt(lGSOQP7J)j;>c)eU=#}0Qc6`=qyT0wG_!u z-0w|Bac&ie&0v)9bO57Wfs^8+{n(`aACSQbSK#bR?@THcqw*3qX-=_3@LHDDvjIJg z@HpWHwBseDBOrcNd%2Ml?1$?F2$SAei)fs&AP1HV-N_3{8M{YaViv<Q%u$`U6iH{4 zNb{P-76;#r4EG>T;`*~2D;E)P0DzpVpk?b<ZYLLXAZKp9Xf?mJVO3%^wJ@(;a_{xm z*5<1(N)$n;;1dQf^+Fac1!B){yh6+$ZS~#)fhOW9e`*c!oH5_g7%?2&lB>PFSdz_Y z$mGpb2)QI~xD%NrBCve@EO{4bfDn0Rtt8?2ri*`s`ERX-@(lMmaP#+<)+|8kAxF(e zV?!^h!fEPnqlCt3>b_D3XSmQ%17B}9AGXTu*K2dCBQ<Ve&v~0fm+l8Grre*@Z!EAk z$Im>LGqcGuG?zd>VOaJ~y{I59CQcm-oLy9?XYn24m$*W_0-eLQhH3QS3jOxWw8V3v z`JEB*z?*DKTcpKr76UID9@GjG*cA^N`IItNg;t2r)lyV9!FW(TPy_F<KpC`8vx=LS z<CyklmxI;tfY^4OlAp~#Nzo^K1YL=E>GtpH;r|XZ+5cHE|CgAO{}<iJ{0A-ezXqhI z|5|xsgS;(#`OuWv)&XH@O&m<nGO0y`snik6S}r%*YTC9s!wIc#tsG1{#!ANY;$3*3 z23>Uhz{0zhLogzRJDj~c1RLM^GI4sd3bD32P`;KoVqlWGExn6^t(bhXZd;VS%i!>k znkUfWU_p*Mt4sTOe<wPAK!I@?(bAW`KvDbM6V9Y%@PT5`mpxK$f2R`4YnkZogYq85 zT7$*(r(ma5!5n@fNv10oh9g)U`*rzsR4B8yfKdf%57i$>G6s|2mc)_ftn%dcP;-R; ziM9>v;nvLBaQ00%bjD*)EK`VxW{F50=p#-63txL0-@QUj=~~$A5FC@oFqT83EoP9a zcm;N=<z>wga)o?=p}c#Jagw<43m-V`z}@f1PMjC02ro_hJCh2O0a;lx9K#32nNsvR zgb|TiH>>BDZ~6XhE>6Dav!?*_amgG?F>|nuiTwEbn`n-5pZ)C!#0Ki*88Vo76bY#1 zgddL~FZq=Nl*-VfRJ5k1v2CMTqKhkeR$c9=A@oDdLb!)r{qJI{3!{8=q9qKVabQRa z9ZmY1TF(=S(^k0GVh=YbsgmQX`d81Oef2u=rHg}3^zS@M8y~uTZifgH#0QytKQU)$ zCBMEHj;jv$w0~*J1%0U=aj3vFPZ8+SP)}*<WI;sV=?HZyaBn?<6{%yd9(<E1sS!|N z8aZ&+EnEj3!Q+;dC}}{KXw&k_-#(-VfbmbCw>sfw&cA-f{1$7$oT%pSewSb4&|kjS zm%<PG&Ed%~#IDam)ektR<NF;MPhE3*ZC1#_aTqWlKjg_Ng$!B{<LXuTn^L|lR4vMX zfs0vT?fBLUt`R(9co4<r+ftThEDCqe>U5-`=Q24@COU+d91%J@wljw}YG9}AwS0#a zyBJE*RBb?1=X?vhk)KzOp(u8&v@iTideLh(CT(7F(^(O3w+_>Ez_4z7kj8$0_$3H- zFdAIrU$x-*`0?kdYM6L)Vd&bsiEDo@mOGb;aw-0l$5Yi4d)Gz*m6M<s$0?ZNUT}F* z%R~*emSS#V+}DYH*_1K2^|EdLj!%8lulEhc;P2vfZ|*SY<8K(c-37H*ozH~xa^fFW zm3nqE`zWZOF75nvj91NPeLzfYtzLF8R#7wx4}e!K=*xj;=IlROd6*p3U|x;T=UoN~ zn#OdfpIWz5dSR<+g3Zym3RN)Na)RIwyVH&BuSU@@`jQ{pK=lnKoA}(4u9ZRkt*jqy z{PL%@qpqP0lj!l5J-YD*4yi}I8Z+&cc8Kuamp08=+WGHa_W!}#7zg==O^StP`jfua z#aZ6sy!!5Y<ZJ3*3&M$j$mLY^L~jMqIbjK?1e8x07?LY9SiZb%!Nazo9@8;Zv^?dY zJRmr2kIOGl{+_-9RT~1)o0Oi<>at@5ac~Yw)f#PFswy*mw|a+EVGniB%g*?K%L+BQ z+#y!-`wme1hf6l*8TJcoyhjhk0^ZPq2uLFmCpnX-Qouu`Pn4v<gYccgh*V~&d$YTT z3!CWS%RD_H!z*a3p%<>%cX={OY<<=^j!E_G#}6_<!I|gt!u&1;``rhKpl+A`jFDiR z{gnitj=>5_#%Lc8*6n)&Q+LPEPe+@@VB46)W;54ktswq3DMBR4*pc+4tt^XpzJ1bt zWV77zu^$RwP4OKqHE{dY&r|AG&3!)dImtNBg5ws?Osu@v*=`C~M8s2K8o`uAhSQ(w zOrKiAiRUn5{C>Rul5tO^Td9NXEoit-u@g-D%fLyiO@N^)ZR@vS5AW+B0Vijw{X%YJ z5YVhg1EM!e(qsGf3DtJfOPd-URg#4sr6W*@#rpb;k-KI1NBq??&Trnp_LjYlWy(aU z4o3S%lXQiF$Y=p;VRq!BBClCy+<yE)w;OID-_m$eRDD^zU^$rPkt3hhqR$<X9M|!F zHh<|h8PK04UUD=gzx>n`#)!A^JQtoUd@@6D8Qa4MS7Gf&AW*^Y%^CAhnAld^tBGEk z$Et1Ba+Ttky@;v^k8%Pn>0o&EM;0m1d>ly&ATmW~^>kR!k@BlBmfzNxI036BDF`22 zG*$MqYldgXaO-M@Vv>Xr8})lqI3b$kh%-$<8sXpx{P5`gpTyW7mF4!^F9TAh(fs)f zn^G1NVsU)(;)+HhaT|Q15Q)Ve<<#~%Jt;?Q@7fz3r9%B29C4aHCcs{xH%xNEUzY~j z7K4rb*-Sq03d+uXBe3g@k65ZQfcZ`sZuzX2rj4XVNLz=^I`SW;(dhQ0E?ZF~;Y^ND z9eLtLzME5-eQyx=`P-I}XA=2)OX4TSR$BKg#E+L11?0IvFVp%Q6NQBi_mdo10M)Pj z4(_ZB$lYV~0?<~KUb(RKX|#Hvql<m>*q|RH8ZOZ*&vzvA`WO~_g?dQxto~e`g6jU8 zmWre;K?vV(1k#Qf!xM8;%IckhgC(ZFEp98>VQ-0BD$yk7m@aWuSL^Hlg#80*2mKk1 zDdaj=qI3=duHYd2$qL<_;D6g#knHnk@`{&&Z}*yI|FoM2hU6KOGS~)#7Ywy_nzs&) z?_LN`abyouLu$5;)u<GgrKlYUTC6=H33m1p9Q(``zGdordoP$y)^?2ZYzmnlx#cR8 zfBj4wd1A(TorD5@nH%;CR;NnDy-d77zErn{LDC<Z2`w-Rk+jff8PvTb=YNuSvA%r3 z91F{_Ojy8r{sVeJ^CA~iz=|!$9~Ov{6p~N`q(lkwCK1)gEYM?<(p+4;)LXS+z0GE$ zj`euE6#U%i%51&<(9iVWR|xaJK`%N4&#HC6%y7cr{Wujvq%rU>U_@x6Z_v&Zshnu0 zgxIt-)n{zX*A|G>Ei_>=gQc%bw6*bZ-w)OkiX_RdjShCk=Waw2%gz^i_VG*lvz9Y% zB)Xi{FCq4Ig%6t%F3pchA^Q^3=mw(z{;&I5>32n5Fr}zG|3HsHN|FjtBvu?M?i7Y+ zrQZ(YnhprnkxeG>nnxM>$jK`uI=iJmC0<dR_*R+&k!kG$9WV5UHyQM!n3s@tQ<)~j zkst4H!k4$ssfbH&_$AIpe&)s=%)Q;0f5oXe%H-(vIu9BWuHh{srXT!_QLPhgN}vkG zg`^8dpqxHWYCXwy`<0Z33r`3eR!awGj&SetGi4Z5e8sOylvHGVt<U`B<Er_D%3Ie{ zAyr6?#M>KAjntm)xIA2eXt_tyJ}<Af_iX#v5(kq%xGZ+BJ{ut~l8#x*wEfn<`nq#O zB$*@PnbrB+3a^d`h2v;Ymj$VbW0uz9BW^4VE=!<iom<~;zc7**gKF}LW$QK$a6W8v z@)P}WDVNQ~zS`=-{8^Qbvct~H*<|#BFdaY+;~aG0A~r&f_}vzuygY4v8h)4#M5~oc z2G0~%g!8jiwm|O1RY8m7lUt_`C(ostV@W`S0PEK1Cum^(W{?BxG(p}S-INN@_q>~$ z(+`*QAx=@!xl0y2Pcj^W^qzLfg#FM(uk<S<J4h2LB;81#_;@YIhml(A#d(CR-DDB* z_qur0j~%m)ThNN*<^z%+Z2e;f52Jv1!YcJdIAkSBNQrWX*m%ydLS?nt@^TTomt4Hi zUmaZNstcPEX?EUFv#^uviyHz(7(P?f(<-(-E7Oric$fhPtIGy0xY$BUi^tfy6f--_ z_Ox{r!z<?PA)P*s4W7+@#=)vs6zk=};U~<}ub&Hm+>pX&pTcgOT9bkRZzO{)<ea%4 zW-;7%ggwpl%pn>1w&~Pm-`2E}{G4j6{!Sp>SKRacdRFsWz$W5H&Zb}r88G4MeY|6b zD{NNCo72eKNOh?=tvk4s+db#=E*!ULckH~U2y4p9+Dos94e5*`eO&`oA6W_Nq@LL) z(@n9>M1#|-VZ5Zym{Y;$?quGH?H04`I>>-^_IfP_jVL(fUJ^KtVC%oX=gYjp7&czW zhcUo+f)LTW1q^9jx@?%xT8jR}*Li#SMh{qdSy81*MqZlq&uo*M^Kb4Jam&Pf9c=~a zl>P6m1^>1BnSa&!DfGDfe7KNC1;qndR;wr09KI||sj`RWIs}~EBcK6gO;)YL%yhY; zZS{ysoFV?6(Iwh91Ur!1-1$IfV`?ER*oD6L_-E6Xv3-on%&f*<>2oIjr)#pN5criG zrsi>;du~>@gJGFMJw_q|>V=KE-Ju43e}i7BDlj@Yc;&*$A(!L>Ms8c7B<szagVgKL z8?rlZ-$b;i%gU|D0GN20RjSPM@50o3S}(#xJEPt0J-0J<yu_yWiyPC-vV|p~=mzW9 z1{0a*;>m1~OLFUPGxhj(POgTHjzgMXg3F@=7`4?V?>CM8tZyU<Rl;E5PCkx9Egf;1 zvBrmw#FLZL<)6+!e3GswC{#t{#Ob$?=!BE_%VU>AUkbE`-Yp~uls4p&m&LfhEY65N zf~tNuygjIuu9DcGU9E-2QssOA&h7$IRi|Dpebp?vW-BLGZp@@|*%S^fx7XC}FMY3k ze|)ZM_vhQ1Y;l|~=xG;i{$sK)1?ce9f&EW7qrKIZ4iox(bSyQidId5YXi>(_*d<RW zOT`tZUpP*f;C@r268Y{)#Kk;v7|}HDP?zl%vCv>bOR9P&OrV*F33@R<9O6Nn)$px( z)BALDyyw#T=97B(`(_|Xe3_SW9Vg6yV7*NVDO-e^LE7l!2IIiz{nblzJk9eQ=+Dg= ziY@d*=JH&Z%+Ps)cRE%Xg2Ed|d!Q?PMr)O5W<4*??K)`V!oAvU_N~lqvxL*CA^)H6 zt%o$;IyuSScJw!#U;GepQSvPJoY9Ul5H4uCdMrM*C+`D4Et`g$#8~FA2V3$!w>s{0 zi^JaqcOdZAeED<r9?p95R;M7!opRRrqS<a>8`A$N&QR;>hP|m2*;)H@%QN+xzr+1O z-`l^+Ty~0hMwM9=Hmpk>kbl|JQi$VAbBpbn<s*)*zvN;Uzm07lhclgVxQ5BkAAP*h z;cpfm|2*ae@u=#~=dAiv;TeV57ZEI%-GK3B<M%+GARt}9F2z}GUJL853@+Kf5M#A$ zm7ngCc#Dyr7E}n_U5zb0ri_SOVeZYBET!-uh+jyEi+8}8zKO2s=l0*UeKXk#H=EGu zeZQlKeJP10pa{ZiW_u_AJBdJ=0F07uooka#g7CZ3=qh`my~!sta5!~0>~pUux}j0x ztNXIgo8&nE6s*4Inj2^XT{v&Ks*|Q==}VCbX;1i5jF&1jpF#b(<fF>zMUD$z#s@)$ zq6}UoGD_2nb5MbJFbXHCz|ioOdSDwoc3%MoS204WzyfOg%%%ye6K)I=^-;3(>&78L z>;wGQve;vuM_s%Jx=`A(TSHWv)x!|~QY;_6aafu6f&*<%^5a6wq>T|Z8;r*L96B<q zw0aSM3jQ>#96{4S2O6Ky^-zdv<D^L1U+>BuHzIE1$@{$-s^1T^OZ&0Pi{E$mO!_i> z+&%I_c#nT$=ssL5`_~@>8Px6{T%Il)QvQ5E%|eHhVgl3Hw7J?Ck~o8_v6+!l&-ga7 zK1e-uz$G_Y|GiLzhPp|Y6{8@>F~7~`#h;%)QgfCvl3n>}3Fuh5Llp9!Ul}tM8<fdB zko*Ie5gj*Zki4d+wQqcu7Vc3aiVd+1r*&Kef7^Qg^=MgaT0uf-tFfA_lql62WQ9xT z`gDPds)aVp2DGWAexh|MNlZ(s0lY$<{4u=Q>Xtb(JPrR=R0kIN1#E;CL2WNdL5(e+ zYptS{uJ%nt=mpYH%K+(rXN8Annzj?pl(0r?C>Ajx8MXfy(`u~Au9Od}rfF0s`x~px zKLu-L86@$T@n8EEVVz8VM&WN`Zi(B|VuKUF7!&xK@byR23JUVIs&5UI>;dARtch<I z=yTT6eNYUyfaisMp!4)7j1f{I<x-CN9Jn%ep8nq5`*m+>4_}Y@jMm+pT(UNh!7iEb ze&et%3NlNN@J-;N7U&ah^oZU+WMxJO^rp{4gg7U(96FSy{O4<2JF@>0e6WWyCMxA$ znf(`5)_ouQd$FnM(JBa^e_LjzqyTP4Aw?2hD@v^p2jJi9ZLc-e=nD+FP1Rn_`e}b# zw&ZgPmx0`Ew)??;!7kpO91$At!aeq*C+56z%_*W-MzBi{JB4Kri^Y42!sv8$-Ezf! zXr6_B3eUeV^!sd&7sRi4DoW%=B}kA=QgcE)K8<>mLJpQ}yjotXWViW`s+NEo%ot>c zCI5(VRK3Z0DTt<ehfj+TW;%7!&fjZ-<CVf)1b5lJ)O6>KlPG7SEA0qXF<2nMOXY z3>Nf(0D__7)29!h$0WzyA>y5Z?3WMoYA8UKLQ07Njso^4JpBDI4kn{qrYZ22;FjV; zc#Rrn!W+)|;q;Fdsa&ki-ghwWEOV0zqf||)d*$OvbbFfn?AGA*K1i_E^Im@CH?J=T z)}TQblc?&hxMt|dQIc1sdQ7=jpf=g<tDEJYXAkqhEPp1p5#eLI8xR7|Re_2~+`I4T z!)!*}QLnp-R;h);=RH<q2jdAyAPe#0Kq&*((P8_Jc{30@LWffA#}e8ge{SsYke|!M zSX#Tgxy5mdBos4%)+r&R_@26M94aPEjRL`i(?bGDHlBaAXCqK>iN;9wCt@Z-s0*DU z?-O#OsD@}0XwP(4O(Q*ssKi57o=uy;^5<*wlaENQ%~gId+Vi5ieqro~ls$=zk(^y3 zI_yb%vFG^oQOY4zp73rX@xGv{Hw>6h-g%K*ox4K*?jSkMX`_y!id=_1^0~Le6LWl* zFX>Is8i)b(3v=(}%BeDmJ_*BDk=UfkOx@k4)>L+@%xb$WwrTPAwk6w$>ZF(AV(Czz z(&)yX$8~{sjP<{K=}quy^FgZyufXUKL60tWiL&_8!H>2!TSrG0Y{q3`#ic3VEhYwX z0>5qYJ$r9-S&s7P%Ml2=e9Az1Twze%8-Tm#`R6Rel%(^aD%dv)c7Jdea9g~keeHdT ztO-w7j2}LJv*!(rCRTQT6Z3j>WLijZksrL+9ITcc9Ib4IYPSKBY3Ru8J91);w|IUH z*dDm+$NJ(q?^99Du`wm<&i8gN)3$#m8FYs*Uk6g=*@hrxOFJ%RGlvA38AxB_!u*`- z?xXp>pW#)_o@P!u=AU>X6m=w!)Rb0IV-I{)I9Zn)$sUZ`Y7)F|mdniB&D+fR4W{s+ zRq37UbQzVap~Ll9rZT;*({?NBa=?7+K#@~PP<eaE?92yiN>n|Z{z%fP5^4yzBmxb} zZ@SmYTRTGiHgzjO38&Pg2jWC10C_3`MDoGJQIF1rsG(}PKVY3@sV}Vs7YPBEGD5!I zRsK4@n_)PeMCke{Ppv4zEvM_I8`&0?{0ZTYaCwTE41m$W8HZpMl)NV$wlGoq0=KO4 z!WSDqcn<L2Y>!=z9{mFXC|h-5Afy<S+4zAD$3Gzb8Pyu~KFdn-32J$phtaz^)8qTM zU7o-d8JxiIakP(jL~uduiSQtVv^ThB(S*+zS=i#Asl+of3mZ%Z2;4mh-8JPLQNXIo zCO_*JcYm|6VEe9=Vb52MjkWlH;cfoIc>cdq+5NAs3N%=gk#4Hx@#2%J?@pyrrw~dF z-h|VEBo|1Y<$d|IIpc4xjn%?gkyLbWvQg3!a>|z}N4|a^SwXKEKw4YVjc+PUdbylQ zDj38Iy(8w~zSOq5(dCrQ$>*cC<pM4Xg|uEZ$hs}?B-o`TSAwH&HQ1<40|f5SMEVjt zGVyQ*O4Wdl^mPX;0(wGi`K6hd^(&F?V}%2wdrQmY;czor(e5-38&Z=fpjEo;n^Z!b zLfr2&O>)qp%&uA?AK~|T9PCfEDJZ<Q@;5Ab<@Whx!HrW1iAn3F=6tT^8p(e^I*V@= zyQMNU-Jd9dUdHdXkOtSvQ!y^9RilD-4$GUJ>{{Re)F`}ik>Y~|Ypgrm6gwY`8`w5J z-krKnK43&c9%b+Z=_?!P350<u???uBDZQZ;4wNIuv~G)Y^>K{0=eb}r@w~D~uSM7; z_K@(97f3qk%)kc;;J{a)&%M|AEbfV15z0K#4;#bAC$lE4oaiqYOd&1tx=cgGUci=h z*0`ai!S?M(sOXU{c5<8jOSRt!SG}^v@D0P_dx@LWoVO_pCk9xG6z+j~I6tb%lpWYS z#1+a%Kll3xNouobm?d90Fw78m%9K90Y^-Q~_nq270UfuVY6th}eM%z!%YDb#zm9#j zy0K(S{+7PbFgV9nE=!iceXM(<AlhQ%<_ER@r#>V7eX&dj!S^2|wk}0TP3uBf({j!= zXH@rDyo>-<zGYvS;MiJAeH(GTFiy3~xE*R;)|$O>PV|yh{kGh0BM>@<7YO(VWC)i2 zz>-X!pyaQy#uaEa&LDXM%ICIif_e%Cc6fp&AUr|9TEt@5bzH^IgtJZFD*F{%TI_O7 zlP}e#?gh*vlKku%@2fCg#0eh!>lI-aBaIv7uwHh6%vijzHAT#hFZNVHxtu<Ny<bHh zX++%$I+>z6=<HTtKmdYp<c&t+{(vx>=iev*1MHJy!5{ce`@1o3qLZmKI|1_=rF`i< z#%Dg`=^g&JeR_ftvjpQ4eM)D~OSKl<aMhXQz5(UY2g*z^fwy-I(pFadQe1iXlTp>8 z9&rm-gX}!KqQSQgz9!4T##$N%2Jq>aL}FS`E?t#WR2c=hpW}*auxHtSVG@rYw053o zw|~#D!8B@DM7sK?2cl!ynrF;dsVUK<-)rk-rJp>7s3{o*SChMRm|A8s``TnE?kh$l z<~0ZV+mqW9UY5@<!r0~#B+f@=%ppETp5)$(pw0@U7iD)@l;qJzMUvbx9mm`xUGHAz zOYUKxgXHj}xK!RLJ!_CwpkY>d#eeqw#h_@!lfr!K+n^Ufx0}!*>uJ*&sUx(x!gXq( z6D#M)uJtsuTt2@PjeS2OB!w^^Ga@UG4=O>d$;>CU;_MAnYzY?zfk*_eeXOPdQEr7> zHAFTgOfU#n+qh<Rxa1zrvVfg;v>j;dJ<FjhL|{|;qb><uik)~jJDiwnxRig5`te{V zUzi4kW#60v$E~E*;D?ik6)t#LEX(EGo#eMyHnFufmR?o5MdNG4RkwkN9*}tIemwfE zi+cI|0%dsc#p0<QF)jJjxB@Y;17OdS79LJ2?A5@)D0zaTabtTbAJ5FSkIJ%(h975C zzb>Q?iU`a0lS2C0mVrctkC^AA3GMqw78K((T~74#&F02CxZslWA9Am47fN2(8E{6j zrQ-DiGp4)W(m6)}ki|9BE1l~kyOm2w=-NLZA?q|2${H^4qYsvl6ZhDvq4mpjNvdA& z1EZST$T~ay4Ak4|gexlEcRvXJJ|A=BM4c-l9tsExP^umg!&8xu@%gmRru;)0CiM$u z24)Vq>e#<x8?`ayZ)sXMm)AcDuQc@aZgfT!@?+pcTcAX%N-lg=Zb3Z%Ld>z5c27hb z+r|)!A?bSAM{EWc3{<-;a)uDK5~4<yiK2WU>bkKG)v&Q%dw>KqVJe7-67(=^A^Nu= z9TWhwv-z;S5o&MUw%zy+V7Nc~6oQDKcWxiPB&eQJavcQS1~y|IigTX)juD427rfx! zmrA5jID=PE5Y`~>14$^I0U}#b^Ts^5_wMJ<Z)q2lL@s(r|HN!w0!<P-Q%?gvZ31Os zm^h_@F&N|KLE_o(vQ(d$#rjV|`Q>`H)ohz4J+iSkGrzTb&gFU`Rxp$lbK5*Zr{llF z$m%~&!T48)TURbqB!JFUn0w1+KYsVm8T9~vE>!(5UA-GTxU6*Z39Fnh#^arkN>((F z&@=gu)^A_4#NQ(7Mqy-cv=IlR4^4!eoS>zNaw?RXg<XrTnlH_dZXFacb&H);TqaPf zP(_ykW#`pm0CGcom`3#LBI^jwEQwDYCc{~6?zthq-qO#deI;Iczmhhxn_BSN1zX{$ za6135V6gv6Oc*0N-n0`v4HJvjL#M#@VULHKTVGx5GAc67zDdW&8!KW)3t&@El_@YB zbGQY`G3Hzt0lhc#XG;iXYd(q23xU?$*7p-rIT(msmzb-UH<B~j_PoG|is@uPVPL#{ zRV#5}T%Sou+6OkLZQ#c3M_DGx%|FX?6$`KR`rg>mW7TEo;B86X*SV_R8_p=qwXG;% zJL0=Z{s)BRWqUg#b|ptZYtR(Tt(Gn36*)$w$3xJfUbUsd{i!EfMEht-rtw6GPCA+H zzVUn|*ZkITk(ym8U1;&6>|_$?+srv5@(M{lNlO*48?($j?H@ZOIUQFR@&P;QtxziG z)_Rc7aQwsd#e|C=uXkpq9&Zyo4j`WH^8$)CHr3JiWqyF<UNTU(^>=AG6Rvtz;`QTO zy`3_j7*)yZh-%Qkc_$f#M2W(Y7)OOd{3xBL%~YrJ=gFXL4S#ho**K*mw8QoBNu`?P z;Ym%?3l>_CT@&$Fe04zZpA#_s!<K3J8mEQ=vg{M%L>NxqIktq_Pn1twrUYl)3>x~t zR3nRv?=@kxY@a>UjW6JvY%)GZ$@`#SDtQPWsodQGpOTDwdfz86J1HxPw<fB4cy`fh zTdodv69Pwj|Aw^?3%lE5Q8Babk+d_hP`Q`)Pv6_fUgcRkdh}dq<qza+Jc1d;P0{Vq z(Hi?j985)mOAyHht*(&6Gn)a~cF282S1{M7uYHO%uD*Jr5lVm3q6;r#?m=-U!fZ%c zVpus0cWp+2wKw=9_IvK_Yp-OmvLAU?FFJz^&{X{4{?DlIuXQ=lVorjvr@S9c&&n%o zw`V)t^BF@w{bg17+L|N#dqg;sWk^7-!~9uFF^K!nF;15msgAOvl=pVBBCnV3ST-)% z0jvfa6AbU;kr!WQm9;7&=Sw|{l$CD!@$>u`kDvvFYYK78Y_osh+!RyXQ5@PH?vTDQ z-2~P&YH4np68BKPZYt*~WNx3#@iU{Zpe*9rrY>y<3&K`Er?=xWkjJtlcE5nkeBV}p znQmrzX%FXy@Znd{D|_OqZz=ZTjrm0pGkT+PPkx@Xpo*0(4RCDM49Zvj4_^%-<EDZX zd$qImJ3ZwasW&G5TnYCC0fgkp0+O4e?(xa%?|Na6Bi*e^M1(!F&4D)Ew8s?fkFW`O zarV8Ok)ah#{;_7G9v1-^p{zt?=Ym(_!2p5jf}rF%O;dYaTCasM>^Xz!uaXz~V%W{b zutRAY`s4VK5s+>2YGL<Vty_NhIURP2y!MNO-zWON9-CYkvmXiieSWsVz7Nq2k^1p) zKfm=jRgaYwz)D=lT~;-AWI&1$9g<mE-Br%awRQE-&9*ycr8JxgYZt7@8SMhHQTj@H z+`AoArwo(+$)l`WE3_DTXz4aj(YL(;Ru=2hFa4k2e7~BZBs6<VS{Wvi^0WE+2*^GS z7&-PBB3TuBFNg6^?r8~XCQ1+wHnV#lq+`0}H=5?xIX7fF?w_sGSB!w}u=WVkS??5Z zOdYl>?2c%55f{s1KWvfiz)I&wRoum1)agfaR4bb@AQ!mhC68Y$M9@S3`#CuNPbxeB zdbm&ibC%h`XhT8a3i~>ffaC=6@eUEhETP$P&L(RxYlD*cC|7s+jt{BXB}-u4<<;tH z6o~!<u!Ivj=R6<R6%LaG+Mi!ZK*)#4xHm=^$1ERGShtAvZZlpA(@%5jd*0e(!uM4m Y%-cgGg6gp5znwVnKj8(Z`e*)s0Q?p$$N&HU literal 0 HcmV?d00001 diff --git a/gorgone/docs/modules/core/proxy.md b/gorgone/docs/modules/core/proxy.md index 5891cee4fb3..5c1a672ab00 100644 --- a/gorgone/docs/modules/core/proxy.md +++ b/gorgone/docs/modules/core/proxy.md @@ -12,14 +12,34 @@ A SSH client library make routing to non-gorgoned nodes possible. ## Configuration -| Directive | Description | Default value | -| :------------------- | :------------------------------------------------------------------ | :------------ | -| pool | Number of childs to instantiate to process events | `5` | -| synchistory_time | Time in seconds between two logs synchronisation | `60` | -| synchistory_timeout | Time in seconds before logs synchronisation is considered timed out | `30` | +| Directive | Description | Default value | +|:----------|:----------------------------------------------------|:--------------| +| pool | Number of children to instantiate to process events | `5` | + +| synchistory_time | Time in seconds between two log synchronisations | `60` | + +| synchistory_timeout | Time in seconds before log synchronisation is considered timed out | `30` | + | ping | Time in seconds between two node pings | `60` | | pong_discard_timeout | Time in seconds before a node is considered dead | `300` | +This part of the configuration is only used if some poller must connect with the pullwss module. + + + +| Directive | Description | Default value | +|:--------------|:-----------------------------------------------------------------------------------------------|:--------------| +| httpserver | Array containing all the configuration below for a pullwss connection | no value. | +| enable | Boolean if HTTP server should be enabled | `false` | +| ssl | Should connection be made over TLS/SSL or not | `false` | +| ssl_cert_file | Path to a SSL certificate file. required if ssl: true | | +| ssl_key_file | Path to a SSL key file associated to the certificate already configured. required if ssl: true | | +| passphrase | May be an optional passphrase for the SSL key. | | +| token | Allow to authenticate node. It is required to enable the HTTP server. | | +| address | Address to listen to. It can be 0.0.0.0 to listen on all IPv4 addresses. | | +| port | TCP port to listen to. | | + + #### Example ```yaml @@ -31,12 +51,19 @@ synchistory_time: 60 synchistory_timeout: 30 ping: 60 pong_discard_timeout: 300 +httpserver: # this is used only if you want to configure pullwss nodes. to make it work you have to add the register module and configure a configuration file for it. + enable: true + ssl: true + ssl_cert_file: /etc/centreon-gorgone/keys/public.pem + ssl_key_file: /etc/centreon-gorgone/keys/private.pem + token: secure_token + address: "0.0.0.0" ``` ## Events | Event | Description | -| :-------------- | :----------------------------------------------------------------------------- | +|:----------------|:-------------------------------------------------------------------------------| | PROXYREADY | Internal event to notify the core | | REMOTECOPY | Copy files or directories from the server running the daemon to another server | | SETLOGS | Internal event to insert logs into the database | @@ -53,20 +80,20 @@ pong_discard_timeout: 300 ### Copy files or directory to remote server | Endpoint | Method | -| :------------------------- | :----- | +|:---------------------------|:-------| | /api/core/proxy/remotecopy | `POST` | #### Headers | Header | Value | -| :----------- | :--------------- | +|:-------------|:-----------------| | Accept | application/json | | Content-Type | application/json | #### Body | Key | Value | -| :---------- | :------------------------------------------------ | +|:------------|:--------------------------------------------------| | source | Path of the source file or directory | | destination | Path of the destination file or directory | | cache_dir | Path to the cache directory for archiving purpose | diff --git a/gorgone/docs/modules/core/pull.md b/gorgone/docs/modules/core/pull.md index d62357089d1..8ec62829d27 100644 --- a/gorgone/docs/modules/core/pull.md +++ b/gorgone/docs/modules/core/pull.md @@ -14,6 +14,9 @@ No specific configuration. name: pull package: "gorgone::modules::core::pull::hooks" enable: true +target_type: tcp +target_path: 10.30.2.203:5556 +ping: 1 ``` ## Events diff --git a/gorgone/docs/modules/core/pullwss.md b/gorgone/docs/modules/core/pullwss.md new file mode 100644 index 00000000000..5bcdf3f1900 --- /dev/null +++ b/gorgone/docs/modules/core/pullwss.md @@ -0,0 +1,41 @@ +# Pullwss + +## Description + +This module should be used on remote nodes where the connection has to be HTTP/HTTPS and must be opened from the node to the Central Gorgone. + +This module requires proxy and register module to be configured on the central Gorgone. +The register Module will allow Gorgone to keep the state of every poller, and find out the connection mode. +The proxy module has to bind to a tcp port for the pullwss module to connect to. + +## Configuration + +| Directive | Description | Default value | +|:----------|:--------------------------------------------------|:--------------| +| ssl | should the connection be made over TLS/SSL or not | `false` | +| address | IP address to connect to | | +| port | TCP port to connect to | | +| token | token to authenticate to the central gorgone | | +| proxy | HTTP(S) proxy to access central gorgone | | + +### Example + +```yaml +name: pullwss +package: "gorgone::modules::core::pullwss::hooks" +enable: true +ssl: true +port: 8086 +token: "1234" +address: 192.168.56.105 +``` + +## Events + +| Event | Description | +|:---------------|:--------------------------------------------------------| +| PULLWSSREADY | Internal event to notify the core this module is ready. | + +## API + +No API endpoints. diff --git a/gorgone/docs/modules/core/register.md b/gorgone/docs/modules/core/register.md index e1db2ebe8e0..6cc3bac81fe 100644 --- a/gorgone/docs/modules/core/register.md +++ b/gorgone/docs/modules/core/register.md @@ -11,7 +11,7 @@ Nodes are either servers running Gorgone daemon or simple equipment with SSH ser There is no specific configuration in the Gorgone daemon configuration file, only a directive to set a path to a dedicated configuration file. | Directive | Description | Default value | -| :----------- | :------------------------------------------- | :------------ | +|:-------------|:---------------------------------------------|:--------------| | config\_file | Path to the configuration file listing nodes | | #### Example @@ -28,9 +28,9 @@ Nodes are listed in a separate configuration file in a `nodes` table as below: ##### Using ZMQ (Gorgone running on node) | Directive | Description | -| :-------------- | :------------------------------------------------------------------------- | +|:----------------|:---------------------------------------------------------------------------| | id | Unique identifier of the node (can be Poller’s ID if using prevail option) | -| type | Way for the daemon to connect to the node (push\_zmq) | +| type | Way for the daemon to connect to the node (push\_zmq, pull, wss) | | address | IP address of the node | | port | Port to connect to on the node | | server\_pubkey | Server public key (Default: ask the server pubkey when it connects) | @@ -59,7 +59,7 @@ nodes: ##### Using SSH | Directive | Description | -| :----------------------- | :------------------------------------------------------------------------------------------------ | +|:-------------------------|:--------------------------------------------------------------------------------------------------| | id | Unique identifier of the node (can be Poller’s ID if using prevail option) | | type | Way for the daemon to connect to the node (push\_ssh) | | address | IP address of the node | diff --git a/gorgone/docs/poller_pullwss_configuration.md b/gorgone/docs/poller_pullwss_configuration.md new file mode 100644 index 00000000000..da4d1cbeca6 --- /dev/null +++ b/gorgone/docs/poller_pullwss_configuration.md @@ -0,0 +1,114 @@ +# Architecture + +We are showing how to configure gorgone to manage that architecture: + +```text + +Central server <------- Distant Poller +``` +unlike for the pull module, the communication is entirely done on the HTTP(S) websocket. +In our case, we have the following configuration (you need to adapt it to your configuration). + +* Central server: + * address: 10.30.2.203 +* Distant Poller: + * id: 6 (configured in the Centreon interface as **zmq**. You get it in the Centreon interface) + * address: 10.30.2.179 + * rsa public key thumbprint: nJSH9nZN2ugQeksHif7Jtv19RQA58yjxfX-Cpnhx09s + +# Distant Poller + +## Installation + +The Distant Poller is already installed with Gorgone. + +## Configuration + +We configure the file **/etc/centreon-gorgone/config.d/40-gorgoned.yaml**: + +```yaml +name: distant-server +description: Configuration for distant server +gorgone: + gorgonecore: + id: 6 + privkey: "/var/lib/centreon-gorgone/.keys/rsakey.priv.pem" + pubkey: "/var/lib/centreon-gorgone/.keys/rsakey.pub.pem" + + modules: + - name: action + package: gorgone::modules::core::action::hooks + enable: true + command_timeout: 30 + whitelist_cmds: true + allowed_cmds: + - ^sudo\s+(/bin/)?systemctl\s+(reload|restart)\s+(centengine|centreontrapd|cbd)\s*$ + - ^(sudo\s+)?(/usr/bin/)?service\s+(centengine|centreontrapd|cbd|cbd-sql)\s+(reload|restart)\s*$ + - ^/usr/sbin/centenginestats\s+-c\s+/etc/centreon-engine/centengine\.cfg\s*$ + - ^cat\s+/var/lib/centreon-engine/[a-zA-Z0-9\-]+-stats\.json\s*$ + - ^/usr/lib/centreon/plugins/.*$ + - ^/bin/perl /usr/share/centreon/bin/anomaly_detection --seasonality >> /var/log/centreon/anomaly_detection\.log 2>&1\s*$ + - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ + - ^centreon + - ^mkdir + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host + - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ + + - name: engine + package: gorgone::modules::centreon::engine::hooks + enable: true + command_file: "/var/lib/centreon-engine/rw/centengine.cmd" + + - name: pullwss + package: "gorgone::modules::core::pullwss::hooks" + enable: true + ssl: true + port: 443 + token: "1234" + address: 10.30.2.203 + ping: 1 +``` + +# Central server + +## Installation + +The Central server is already installed and Gorgone too. + +## Configuration + +We configure the file **/etc/centreon-gorgone/config.d/40-gorgoned.yaml**: + +```yaml + +... +gorgone: + ... + modules: + ... + - name: proxy + package: "gorgone::modules::core::proxy::hooks" + enable: true + httpserver: + enable: true + ssl: true + ssl_cert_file: /etc/centreon-gorgone/keys/certificate.crt + ssl_key_file: /etc/centreon-gorgone/keys/private.key + token: "1234" + address: "0.0.0.0" + port: 443 + - name: register + package: "gorgone::modules::core::register::hooks" + enable: true + config_file: /etc/centreon-gorgone/nodes-register-override.yml + ... +``` + +We create the file **/etc/centreon-gorgone/nodes-register-override.yml**: + +```yaml +nodes: + - id: 6 + type: pullwss + prevail: 1 +``` From 2c64097ec48ddd861cf5a17601e3ac0e18f28951 Mon Sep 17 00:00:00 2001 From: Evan-Adam <152897682+Evan-Adam@users.noreply.github.com> Date: Mon, 24 Jun 2024 11:50:07 +0200 Subject: [PATCH 842/948] doc(gorgone) add documentation for developper and pullwss connexion mode (#4113) Co-authored-by: omercier <32134301+omercier@users.noreply.github.com> Co-authored-by: cg-tw <83637804+cg-tw@users.noreply.github.com> --- gorgone/docs/modules.md | 84 +++++++++++++ .../docs/modules/centreon/autodiscovery.md | 61 +++++++--- .../centreon-gorgone-autodiscovery-archi.jpg | Bin 0 -> 136498 bytes gorgone/docs/modules/core/proxy.md | 45 +++++-- gorgone/docs/modules/core/pull.md | 3 + gorgone/docs/modules/core/pullwss.md | 41 +++++++ gorgone/docs/modules/core/register.md | 8 +- gorgone/docs/poller_pullwss_configuration.md | 114 ++++++++++++++++++ 8 files changed, 325 insertions(+), 31 deletions(-) create mode 100644 gorgone/docs/modules/centreon/centreon-gorgone-autodiscovery-archi.jpg create mode 100644 gorgone/docs/modules/core/pullwss.md create mode 100644 gorgone/docs/poller_pullwss_configuration.md diff --git a/gorgone/docs/modules.md b/gorgone/docs/modules.md index fb1f1ee940a..a24e4495499 100644 --- a/gorgone/docs/modules.md +++ b/gorgone/docs/modules.md @@ -19,3 +19,87 @@ List of the available modules: * Plugins * [Newtest](../docs/modules/plugins/newtest.md) * [Scom](../docs/modules/plugins/scom.md) + +# Module implementation + +Each module should have a hook.pm and a class.pm file with some mandatory functions implemented. + + +## hook.pm + +Mainly used for creating the module process(es) +and route events to it each time a new message is received by gorgone. + +### const EVENTS [] + +Array defining all events this module can process. Optionally add API endpoint for events. + +### const NAME + +### const NAMESPACE + +### gently() + +Called by gorgone-core when stopping the module. + +### register() + +### init() + +Called by library::loadmodule to initialize the module, it should create a child process as it's not done by gorgone-core. + +### routing() + +### kill() + +### check() + +### broadcast() + +### create_child() + +Not strictly required, but present every time, used to instantiate a new child process by the init() function.\ +Inside the child process, a class.pm object is created and the class->run method is started. + +## class.pm + +This class must inherit the module.pm package. + + +This object is most of the time a singleton (maybe every time). + + +It will be created by hook.pm when starting the module. +This is the workhorse that will process all events. + +It seems like none of these methods will be called by gorgone-core, so naming is not required to follow this convention. + +(Please keep the code base consistent if you make a new module). + + +### new() + +Class constructor + +### run() + +Will be called by hook.pm. This method should wait for events and dispatch them accordingly. + + +Uses the EV library to wait for new things to do, either by waiting on the ZMQ file descriptor (fd) + +or with a periodic timer.\ +Generally waits for new data on ZMQ socket with EV::io(), and call event() when there is. + +### event() + +Reads data from ZMQ socket, and acts on it, generally by launching an action_* method to process the event. + +module.pm parent class has an event() method, so it's not mandatory to implement it. + +### action_*() + +Method called by event() when a ZMQ message is found. + +Method name is in the `action_eventname` form where eventname is the name of the event in lowercase, as defined by the constant in hook.pm + diff --git a/gorgone/docs/modules/centreon/autodiscovery.md b/gorgone/docs/modules/centreon/autodiscovery.md index e9446458fa3..95a886f8f7b 100644 --- a/gorgone/docs/modules/centreon/autodiscovery.md +++ b/gorgone/docs/modules/centreon/autodiscovery.md @@ -7,7 +7,7 @@ This module aims to extend Centreon Autodiscovery server functionalities. ## Configuration | Directive | Description | Default value | -| :-------------- | :--------------------------------------------------------------------- | :------------ | +|:----------------|:-----------------------------------------------------------------------|:--------------| | global\_timeout | Time in seconds before a discovery command is considered timed out | `300` | | check\_interval | Time in seconds defining frequency at which results will be search for | `15` | @@ -24,7 +24,7 @@ check_interval: 10 ## Events | Event | Description | -| :----------------------- | :---------------------------------------------- | +|:-------------------------|:------------------------------------------------| | AUTODISCOVERYREADY | Internal event to notify the core | | HOSTDISCOVERYLISTENER | Internal event to get host discovery results | | SERVICEDISCOVERYLISTENER | Internal event to get service discovery results | @@ -38,20 +38,20 @@ check_interval: 10 ### Add a host discovery job | Endpoint | Method | -| :---------------------------- | :----- | +|:------------------------------|:-------| | /centreon/autodiscovery/hosts | `POST` | #### Headers | Header | Value | -| :----------- | :--------------- | +|:-------------|:-----------------| | Accept | application/json | | Content-Type | application/json | #### Body | Key | Value | -| :-------------- | :--------------------------------------------------------- | +|:----------------|:-----------------------------------------------------------| | job\_id | ID of the Host Discovery job | | target | Identifier of the target on which to execute the command | | command_line | Command line to execute to perform the discovery | @@ -62,14 +62,14 @@ check_interval: 10 With the following keys for the `execution` entry: | Key | Value | -| :--------- | :---------------------------------------------- | +|:-----------|:------------------------------------------------| | mode | Execution mode ('0': immediate, '1': scheduled) | | parameters | Parameters needed by execution mode | With the following keys for the `post_execution` entry: | Key | Value | -| :------- | :------------------------------- | +|:---------|:---------------------------------| | commands | Array of commands to be executed | ```json @@ -187,19 +187,19 @@ curl --request POST "https://hostname:8443/api/centreon/autodiscovery/hosts" \ ### Launch a host discovery job | Endpoint | Method | -| :----------------------------------------- | :----- | +|:-------------------------------------------|:-------| | /centreon/autodiscovery/hosts/:id/schedule | `GET` | #### Headers -| Header | Value | -| :----------- | :--------------- | -| Accept | application/json | +| Header | Value | +|:-------|:-----------------| +| Accept | application/json | #### Path variables | Variable | Description | -| :------- | :-------------------- | +|:---------|:----------------------| | id | Identifier of the job | #### Example @@ -212,19 +212,19 @@ curl --request GET "https://hostname:8443/api/centreon/autodiscovery/hosts/:id/s ### Delete a host discovery job | Endpoint | Method | -| :----------------------------------- | :------- | +|:-------------------------------------|:---------| | /centreon/autodiscovery/hosts/:token | `DELETE` | #### Headers | Header | Value | -| :----- | :--------------- | +|:-------|:-----------------| | Accept | application/json | #### Path variables | Variable | Description | -| :------- | :------------------------- | +|:---------|:---------------------------| | token | Token of the scheduled job | #### Example @@ -237,20 +237,20 @@ curl --request DELETE "https://hostname:8443/api/centreon/autodiscovery/hosts/di ### Execute a service discovery job | Endpoint | Method | -| :------------------------------- | :----- | +|:---------------------------------|:-------| | /centreon/autodiscovery/services | `POST` | #### Headers | Header | Value | -| :----------- | :--------------- | +|:-------------|:-----------------| | Accept | application/json | | Content-Type | application/json | #### Body | Key | Value | -| :------------------- | :------------------------------------------------------------------------------------------------ | +|:---------------------|:--------------------------------------------------------------------------------------------------| | filter\_rules | Array of rules to use for discovery (empty means all) | | force\_rule | Run disabled rules ('0': not forced, '1': forced) | | filter\_hosts | Array of hosts against which run the discovery (empty means all) | @@ -316,3 +316,28 @@ curl --request POST "https://hostname:8443/api/centreon/autodiscovery/services" \"dry_run\": 1 }" ``` + +### Developer manual + +This module heavily uses the gorgone-action module to work. + +Here is a diagram of how these modules interact: + +![image](./centreon-gorgone-autodiscovery-archi.jpg) + + +Dotted lines mean a ZMQ message is sent. Direct lines mean the function is called normally. + +Each column represents a Linux thread, as Gorgone is multiprocess. + +For each ZMQ message, names are described in the [events section](#events) of each module, +and for putlog the second part is the 'code' used by gorgone-autodiscovery +and defined as constant in the [class.pm](../../../gorgone/modules/centreon/autodiscovery/class.pm) file. + +The gorgone-action module does not send the result directly to the calling module. It sends a putlog message instead, processed by core. + +Core keeps track of every module waiting for a particular event (use library.pm::addlistener to show interest in an event) +and dispatch another message to the waiting module. + + +gorgone-core also stores the log in a local sqlite database. diff --git a/gorgone/docs/modules/centreon/centreon-gorgone-autodiscovery-archi.jpg b/gorgone/docs/modules/centreon/centreon-gorgone-autodiscovery-archi.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4ee3f7e322d8e163dc62cd53af7ecc1f3b6dfe8b GIT binary patch literal 136498 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%0D(1UaU|fzF%Hc9JFrBGi zXS@Rwew1s|qPw3euQ#sQrdXX?U4M^zSsw7>W8!<#dccEF2aQt3c%S&7E12yw;@?#0 zVt0KWPQ_;@ZyM6i6;AYvr+IGjKj#1XMH(t*^0e=%p(msZ$N^oyh}R|rXF;xLh%)`Y zh;++kaXQCidH=%DuRruE6+YIdl+WC@jTM&ab(l=igYC&nK~z^^7(cD?eWYq3pa9<7 zv5E$*uvpB}Ht6Zbu$gL7GMD*T#8zC<!`0gH#e|3rW!%(V<Cnh&AZ{>PWoNAlqJT9n zAu8j}QVs&V>J_DM#hzx$8X6gUe}Nce!x6mI>8^zgROzV_)RLNPan@?m?di-{X&VoZ z(D<xm%#SLfI1s!fT2IizBU`VS!byuw9cZkME7>@91FaAEQ99d_567`p-FF-Gpbr3D zg0dRWvEpaWWyc;+SfqMqc<tSc{0H}5A6qc)6sW-uRW{{%rLw?;zw%vVpp+&FS<Oj! z4Gkt*AsOvg_L+fCM?Dj>k7mE)f5jSV=Ndm7u?WU(nmkEFzu$1@YSfh-^nKPtKGDu5 zZ@#O&;%wMk*Ge0M4!9NrpF*noVs+%YUtv_F9nWm0IY(_dKry|wT{$0={}N+M^BXRF zVbHBzs-t!MXp|FD!9NcPLUlY_Z;tID)v!~{@-OR(t)1u=rTr>`;_?^-@AcgIZ7w{t zlo=$x{TE2xD0)#yC&F|>k!-bs!9EB7<{MJ0xp~5AXULnKY5c>bPhjV>GvO?2u<OkN z?GxGAZzQPvfT<JmT8gOr5UfwvwO<Le7<>6pdHTsRt0@^j9qsS%+q-<x*jY=~0`U!L z4JHlyPz$3V>S7SqOx49RX^sWc34nGXAq2tIEM3>=VcuIz1w~!AWeK0}x4YT-==P%z zb<<=)Wb?|H<qk|w4Aya(2B}QwL6ri9zru8B=1feu9(G6<54V0480dfXH8=W2C04*E zu|So*KN}+7HiyP(G!Q~oG~cvdL!{31%s(Z_#<+f7L|bwcHo|$oM^4Xu6VDaVZGpU2 z|3ASt{uF5bOMv=+t*!ar3-kY0`R(6!;r`p-^dFKBldg+n)Q&_FjzS6IkzP#|tai*} zKm<DV1dIZl5!x~(!}K5h!ViBI7u4UYwtw(L;B2yq@{MNP*8!5L&9>^c*cx%VZ9a4$ zOR_dTC*~KWR|4av8{+kCahH7--RJ@9y5<Z+xT9|>W-(jpV%2ryiyoTIS0Gqv-ETJM zqbjJX>2uL;rOE<8x$~T3+)Ee=&9t!g!Jf{Ed2X*q!_$#N>tv~-gKbuY&y^+az2JHt zb1Ll6PauT@m0*w%r3aQd6MEFTGfIy#=t0h_*3YP)gO0)oovt{S#O|B^`}{*g9Jdx^ zgS=rh=M2Cg);!EhnHpi+Ya%`9{}~Zs91Vt6&_SN)(??U!_bzmZO(Jhb6~?WSndn!B zzTLgi&en#{Bia`Lhh48ng9AhaL$JDRj@mfUK#lYE_^l(qaci#9M6{nsAx~Y3b6Li{ zQ-%J6KE4dmm0XI5?Pa{qk6)t~<`4|qIGZ<tG;P5mNY2La+Ld=xuF^AgaumC-^|gJf zIzCAiWyorMzjs-i!{Qz3q|E-D=2L=Zmnf{~HF&f(3wTP$>B6USr}W#$RdJ?vJH(<> zvbqYU&KmDm+8leD;l_$3QTdV|!u6=+VLMI5--uR~Ktl<D>hzIoeE#vXdGBZY^Gh)g zW@{xBrPP>Oi$BjVTx<^JPj?Gm;@x#6l^CeQ)0+0QRrmJ%1riD7I9+{&5~N3=fKLyM zRyvT<F{6N;cck${u<1U^Kc2k#P{jJl2VYll483{>Yf7>kWXsEZ?ndYKJ;FvrO$8eV zl{lN@Ipd7;+s!4{BTo}v`^ePahnkt|<?odOlWjAP&0U8`<^ehE!=@Eic3{5SL^H-o z07V#$GJFo|+9u4hS!dzF!&jr#>-9K>>*DqKc*v-F=okd_puE7ZQ^qYAtd>jvDM2{_ zdE+?aI8217?Bx--^5*kk;WP9`P1<XTY<%3U2ZvAz$_$;#^0}1uw-E2wt~>jyqEub- zefZ_>?-NKN+}T|&%dV4W+}!c-`KInkTvfVrg<#4AX34q^WgFWe5^bQ$IlfN@xuwH+ z#MV|lCc0A_7~`&6YOe&vgGN4P_tEI}Z^mXoz!5x4{&!x|y1CTtH`Xz)_UEn}CJa4d zr7+ApAHY;0RVL0w5N)_8?5G$%!drq($(>K3PP<}{@9_FdVg{BN-T6!l+#kUF`lY1E zN91U7*`HH?CAjV!7n%VaYM?zyU}W7fW~2hgSMp;x&v?EI6aLZN)A|1MEw=#^ap~)B zO-3(b8J?=Jg3as!e2D5Exj+^qBTh#jEA|n$sH{7os9WeLFB4w&nZsXc-6z;o+oCCT z0^=ROj+MLBnBdscBjEX>{mRe4Ig`IY-~{j3Dx$XWIXA+#hvi&;94RUNBF)XgN5lRy zJaB_vpvGfyagY;!;npFYVv^G!L}vv$XL(NNtc(?nAPNQk!5lMPIkWtutEr0EwhKdv zzCYDaTtD#kPuN4AHjg$p`9jd+ftKPs2_%l(lz$+~HUb@y>@|S)QN@mbt6>r<|5VL3 z+sAfyc}r3Pg!`5%wCYirgVv)~EQINv2G1Ni9@P1_F`{sOZQ)F;i_<4F_9Mqsc%K1I z%ES4UkVHSk4xhhY=LV5dVhiZz1A7-dN9oj7{V(4FxyW2?@qtx8bt!ub<}~?!OxRc0 zUzAvoL{lDK1o^xi_Zt%552~=gHs1mJ*(jcEf>xRy1Q!~P?$<8)xHSK}`{=xbnGKzp zeas2mu<rD(Tsrlj%lMGM{QFGg1DHe5YgC7LcnsbZD;}oAELeBa30OXedC0O)ku*y} zF86L>Ui;^Q9VzNRqs9KOoIfDZH=J4-_sm>;xyg@k68+wgXbGu&^0*74#n!&cuSf9) z;Rm{oxtDX13xo1l#D;24_jT+%nBjdqGW`5}(71)!dp2tASsychdfJeo@Qspu5f?UX z$YDjiIVvKmS>TJgG58!rx`_NNfif1ZoujC2#CN&)3~MJBc)Pu;eA$v7KU^H%n5VB1 z=}@`DX8_{58SUuJw_Y!W;0Eu|-l|O-|CMj+Do_+DFtNwHv_-|kxqB^n#4T#0_p+Ai zwQf~u$gtm0U2S~N!N_cf$#R(2F0A_`F#MOJ?iG`E+z4~c`D9By>}5lumC?^W7i$A; z%7rg(1C&?Eb55NA=B+tUEP_RK(cCy%)ft!3f$6r?d}U#udfn2Gnw*#&Y_eMBEAC5K zJ!n_5&?$wGeO7iFg^YgL%wQp2J(o$)t9N#Cq|<h_YQx8WN>qI0!0@~cQ+FluVG=pY zo(Luw4O^m1AWtX3+dAjMqN$WQQ5JYp_<X@0G(yYYJg!T<uxgTE*dTlI+T^#7Q|5QC z+YHSCMifKWYl9#~<>!kz(4PvXAQmgkg1~%os`Yi&OEmCdP1RR9c20T4O`6{g;)qIF zG`|xy^hRUjJJAn%<ShxiD7>;hfOf>&pdV*D-#^sXVHO_udRfV_UVD1C%h}|+dDxA+ zzRT7JNc7t8%=%oY83DY<EPz0eU)*CTFLPc@o<f&l>Udpr^<8g0x&N_b_eHy)G@qn< z_tb4{z`8@U9SpQWK<~5JVZ1-CfQW5*f_J{3W2yOq9p;L>*V<y%r%bOS;|*KW(dq?0 zZB##V^}|VWZj;JB2q&@)^@JTn+%ws?-WlIhRK|)!w_k@9W_3(3`|j_Qd9und4B>t% zGE{4~T$z`!%BGC&1>IIi8=KCm6y<D7RI!%=phPVt=^SG-#&i06wUefL+NKZXgSDPL zKYQ0T)3@7TEAYUj^h&HAMHx~8hybIX7-guU0zs-XZ__h@(f+H0cZ?TnyjA=YY6kU- zLE9fV86;^hvGS8{>|Z)xT)OfVU%OOGNJg9VM)09r8D&0IGJQ~cFx`{*dTfv^_f&1f zhW>qrWPy8g%uNj*H;61!>eP#PgYZSQ2#`WQ=OaL)#ANI}3-CvAUIh*;I%~%MRq(KS zRI49Ln8nIYgYEL^*RXRFNEd#2cN4430EA=E=yP%cDiZ8*v=_&ou{<0X!M5ppS$Mr( z@|<+wy)MC28XI5~>gS&B(V(If0$38DAb*58fE(e1vkg<9*yI+Rd@;4lyd}Ze&-s~^ zKl&2M?5$h;R-6nswJ3D|#2os+&@K3X6cGIR(f^P7+9&N-c7|dyS4P_duMkD1z|;=H z<rkC+sM;`U8>?yH%Xw^Si$AY$inkDJBGvJ_5WJ?kpmj?>2>P-03UIKDOG7JDA@A2r zey>D<RZhnew4*RNf-hSp@loW;S85Wz*bo)2r~euqk)-4NCH)=Iuj>*V2nDl8Qu7)U z3|FLj1}<G?jLmkVfgl%GbiX-j)B+yZGI5-8mn#zpNF7Js!L|s_n^ZT|lHN_$P8Z8h zct}1@k7I$?%S`EO^;L!|UxQpYsyHP>-kkYq<ms%|yL9L6#&fw@n}hCh8PMFZPh70| zU?xnJg?YR$TqZW*y@d6xTrxt%j_!(?sp?GWj?1s%SB#EjP+ElRqnW(PpTo=>PwL?@ z#brgoBk)ky+j1Y@KP$C*8+j#B<;pozKz}P=^|YcsffRfa!MdLd&hFJ-N`ByPVOxpV z^Bi=fe)de$(!rQm;!AHLY?mtCka+ht$!Ah1HetsM*V`~%-g*<!bHw9Xfbh`77ah5@ zF&F#g(QvPuW|~Is<vgfPc%_g_c{Jg2dEGE==qlv3cg(2}Q5nCsx{W(FC2&`WW#JUy zhowthiQA7I_0?p)r)f}L(J(GsRX?C-knW?;zDcsKOda@gpZU&6nHM3XN3N2}pAVg( z9Hw0D1=PY9>Fkar7cdK&o+s8H{1O)-nsv!;hLX*#77)_0+_TtocJKyb*zZ~rZD}Q# zhZEu07RN`~Tl2dtCFQzTW>HomRO}DgS!X2JMXu#@KU#&bc(%<|&0^21{c#oHOHu^4 zWu(_H{5#BvkzGc3dEMP9xR<nSg*TsD<S46cP-Daa>*p(wRx!YjAimlgv5u$c2_#3D zcb>p7BER7Na5VSLWP|4TlZh?iNRqmi@zuH{(Ac2UTtaK5$0G&O2i`E2u**N}j$7G) z27<UcfvjVC;;_rWBrI~Od}V6GH0HC}(s;g}t+@>UJ?(rO1^!3vkc6(~ucIA*fv^CX zMg)pTnKqW^rjDYD$@cmV5`!*D!v&g!gV)k=ayopf)Z&fW3sIJ|5~<WAV+h+LBYgOq zRd7896t9}I0Lk4M-c=`48Rcc}_Uq64Jd36KaH$G@h&*~tSK$<TekUt|#Sh7EDVw>l z;<0UHNTGb<H2|y*c4X{_Hj7%4XBIjr@eqDZXXm?LzWTc;oQ7R11?2+cd#geby9?nQ zI0wJvWiOf=`}YWj(anzK_8#ixfwr5djFkEi_4?_(i^s`xHL?Wlwjom6jENg@U1DZ~ z5TUMwxsu*}$NcT=i$Dl2kP%pYFtsQCf&hzBv|!G3x1g?5w)FEu<bu5ai@L02)1oh5 ztqva|M>B#LR|0p`z=osaY;r=tM_h~RV}f~TsWkA(ZY@egsJ?EZyyl=&ivBH6{E<yZ zvO=oVThi;0qlz$PnxD!ce}T{t6+mvCq2a8&VYnwyv#niGpjnT|XC>EA2fcOfzS&jb zGW*v9;QvA)|6lSef0G>HA4TI;gwB=Q)gjOrUHId^BM*k2g(5XoU^fFWFubno8`u@3 z;R+PECk=N672dtvLJi~lwPu3z$J9}ITRt62Ly}!1HdP58Vm&WLQ-v-<{6+EeM=pSG zF7X0h6Rb5y29V+-qMeWemM#gdzIh$))`Gc_gQ7UK8%)OhzJjlSr3a%)a9I_F)8&X( zk}0IM1%X2k6qoEZ8yC)J!ns~MOR`Kkzu;AQ8U4K?Bl6xv@R#-g@FCUw3S`=0l{<%} zK!()La(MO?kPH_(`v<luvv0gC|9t*XaTt6Kc0z0R4nK{iHz_%8>~jV^Z2@@j=8y2s z5$G&6EHt>hS*UP<qlt^$`NH_mZR7s8=`ZUfertUnzAo|wx(_?;y~>VigCa>kgRpg+ z?8lWr=pv#Tj*?B4|B0~@|BPwB7u3zvZ!SX@RY>+U=xp^b5XAQjg$bR3VHvi6(Dvrq zXcVu541-G*AmQ$kTw=bd<{4_;H}iZx6lTA;p_3!!jhr1_x((ve@M<fq`786#2>FOk z7?~MD7hW&tEl-Uh%io--PUey9cCtK3?9X^?`~<e7d>pSL;~KyXP&^`}M07c;wV%Tp z2{vgi&LUo$HGSrfYJ;LkqJ@)M9e5b;UoIgz?L8;-k-=f0HknF#^be|BbpSi!@#Hz9 zIXHf<d>VBLr>;YXa%U(?rOBp|OeLG0-xWFF7#kd~M0EITcGSUWy`Z5=mmBffX>Kk% zo@_N#nv-R=f_JT>zxA&1B}*4neO7J`1V`9miFgN3o4+ayjl@K}0kaagVX*~_(e|YM z+tz$X5(d&Mh62c`yMphS3`ma?&Q{-EPF22i&J5;lLHL#pQ5s)Z1a{DXgSVs+=L|bq zPG5IDb)oYw^2w4#4$mE$D}w`Ew%-8_K1m31QsUf#Q6ibVuy@2(7Qg+FvSUstG8uUg zI>=O+FrX=DuWhXt2<Fzo@ttk~{8qR$dkkRowNO$ZV7oiRtf8iI_R}PfzsAf3e)4PO z_kVv5)wy<~l;W@R&Lk4RNEDg;gHbpP*kj?kfDRHIx#l=_jPh=q)X?(L!qwA<`|?h| zGppBI1Hj=mT@Hqk{eqEyL*52y>%fie&evugO_gIdW1_H!wI9+ir3zcZ-o-zdNE*mJ z|8Gkeob}^&`GnX~kPFXEfj^s#k#6#^x2(|lboV5#^r9B^qh~3Hk0>+ro*F^_I_Woz zh(dtzr$0D+nfS@h)Es=qZ*b&%*=1@1=L2lh+ji?!kFu6L=MLp;hmn7bE~@RK5@^s! z^A+&;jO4*Toellj6yjr<JDh8xl;wC^l2OE<NBKHudZ9py>VI3mqz1>>{U9$Gn-NlD zN)d`Z1q&(O>l3}tGarQg;xM9o%bM39yq-rdo%`i0?5}hFq!Q4@S3meGu1pSF6eg;S zl>q^hf(O!$c3^^@I4Tu5?-sJF(7ai_?lPM38{Ae*t^t%tmMJ8M1%5*hP+KXfxU}g* zzSpaosZ!%FKl92;(5Ngn;PGv#`|ukfe_g)hW&7x5$d;1iS0L^)0){?*E;6w@To~UX zn@+VQ`<4Exg!(JP;d{LHs!v}i|F=mmw#4UPZT{GK%15qf-ODC{osJ+!QRlne%s6;9 z{LZ!kza5qW<KsJNJG7W1a|mv$8%snwK>RZxi^h(8#7A@(K4+ZRqgjj(k$q!#;#K;+ z??I(0E3x2}&AZg66n|a7G^YPNy8Z!=uJ7!ce?297b=m*NhBSXe<oX|rf2#T6znM+i z+5fG;nY6#n4i=fMgcn9CjGuGCpuNS2)gCv%#x%=i#PPG}Rez<Em7z*C=?8md4(}hY z++1jQ+%ZMkr$}?EgLLEO#iNOuD7@kcbJ1!7BZ5>fLT#eLviZ~?ndUI&$8*!7N1SCZ z2ajIG4zS)#iNfTv6&(!$`78TmKiaOGUj-aHG4XF!mm^M=<B7LF*(EM$`hMcL_Oc;* z;J_#KUIUbD=*ID-D+>IY<RbJz)Mq}ca=TD`)2iS{@%Uy<teL~sHhPI9Sb^)t(g6)G z)3l<xyRDa`>7A+f^_Ta8-2ks>217^>M7mW78AzxmT8;lSu2`D{6W_=e?9`R^lEYM6 zCT{qW<r*KA{wm9j`&y5gl(c|>y{lZxJFBNU#K%A_E(e;x2r&OZc=TjYW7e{U(}HV< z<;9^&kF`YhYN@_6Hzs7dinGWrnXvJ^sI1b8hg<_q0N(?n*orGey;+`0{F7D+z<iYp zik_7vAK0w*zmGXrfJv;re@2;?D%V0LMJ1B|{cKKWk3^@<E)b*kXy)g}1V7;HTmTj@ zm_hFuW{GD%PzMVb(GO|oI>d5RbTv$@J4B^~d%c4p*S|98bwvjZMtll1;KzXY{ipcs zl(M-6AOt-de1~D(Ph(od>$)gUQ=AU29yLc=J)gSNqu$j=w+~q-ni9~sim-D@IJV67 z6pDV%3?6nd@QIq$5`>yj28~J2n)CD?%X3s>sk0dM!RQt5>P{-fN5x)@C7&s-W5qG6 zxWH(l7uQVI3RTlGxdS)PjG|KNOzWjtnw}Jz^bFQog}4_LM@|H7cZW_bUd&ioS738u zUZNI-GB#&kcvUEZNLB!9!prgza{5&Sp-vq`oJNiXcAlz9+qqmMu3Xe6)iEkGu`kV` zd-_U`D(hRC@1*0cwA(rC1!td#YJcQsvPH*bc$GNp*t1%SA}kx~CV5j$&s446nhv|) zI((IF{9bOKFPbl`XBsknR-|ZN259!=kba+(X?ff;aN7Gy?Tf0VVx+jUp?2FlG%wGE zr;F@2HTo95xWBzdI^Y`UtQ;Lcf#;4P81}Yu;6LT(fk$w=NpD=U#3#G)1$D2&en(EJ z4qJbPflaqEnkkY!zIdAWq=0QQH<F#UOG;OfHVr^8t}Qf>MNE;&A1pCtF`Cu2;uph) zb5r(hTH>E1-QE+tL0=|YaoghXgDYha_uQY7E`QUL`_EaCe}QQ1fDs-4wjr3Qf3w<l zcE9#MMDg}tAVRtX=y^XV1ql4Pij#-@GWq+2Z~wl^863X9)q>~$@Pq!htv>(3P7YG4 zt|dx*TdWIF*&laDYS#lWH!$<i4wTnAAAbE+2a-nFuDn++Xjah>fLy&wM-pa>SH^nh z_<UM6L0AsvXc5Fdu2RXKI(Hn@`eAQ1)^{Y+G@I!jaP~BvKYb<!kwl7~N)oB@F8Rd{ zCPG2Z{nH|1HR+<R<$JEBWj{~T+2Jt?j_@dMD!4sYl-*UWRX#}h6$nX6Q2;2ztX@yg zcVxe!MAcSbJ6kGJ)3n|~;4b!2Fqs<=sE)tAUT<$fCtq^U()!v{@5DDM>;bT|bfwFP zmq(NO6U{>2u1?Kr;CtYikI(DHvxVIf|Ky$UEp4GJjNwg&BHtHXX;&K-RW9Y84+WAn zi-X-;t=#yL{Gvs(Z;SSCo4Kz}rJagR;B+PJJjXXyA80sn)xD&X{rrqf4|206XHFc8 zA?gzXyIq-OMp^=Ky7Y61Xv}!I%kIuDe{Bsac+sX#`+O8J-cPE-nbh^bS!jTSBa*_| z6|ad6tN5lgij8ZV3lMH5v=p6yWB041qQ>|5S^vm>-Lrt5b%}Mie1{d16Wxvq5<7OY z^|)M9NBO5IphI%LE>lFL7l)hGluN**kL3FeGC}*Q{u_z>s#!1Xrqi{^{g*+kMaFT@ zW5kv-sUCaiNh$lz(YRw{=xeK!cf86s4$N)~F~55FiPh--SlS|ktk1QpBq2xcfE+kd zT{(24p;pdj`(iSnaUL9PL9;{f)nDv>VSUOk$8g>Gu$Q54Xs<g)fq}&K0YHWi(W=3p z=Qyrp8jBDmuuVCX_NlY_!yeSH@3e>N*z><?68V8!qI>Z1TNvLhn?4GO{PSUqVdsp$ z>5*UKq@q-X*}SQJT4O@Ap#kAmnhwg~K=hIIR34(sI($g$=@TZZU`hyPvXuo<()GGE zu?*=t3sRCw5~v)mmjKQ`m7&XapE{5GGgwk}PErO&s68aZu3qcsm*^Rz?hCXI3}q)v zh#U<@!amJyu^+|xf9#TpXqomL6jrFS&6uHL6Ejfo)ASM8-8H%R0`gns8N?=F#Jrl? z{DK#V^Flo$m^e0$MwR{Cy^~E@u^NmS>F1~GX>)CDsrNqH4j#U(7sJ4NRrfvV;(u1O z@IOBHUs*xO2^vVt5yc3z(Vy^!VTzAYCqwiOj4h3rXIBq)Tr)a1L*w62!c*)3%O;Mr zSHvljMLlwLRP;}(Q6>lO%eAeN;JS`0&-ZoVNv4}$Xzr(ClieAVsad}2+%BjEDZa}7 z^Iwm9a^+Y>Q|vwj&^IFV#?%T-{?xh9;}3t-Xt&rUi~r~gdwF5!ZgJ;l5nTv_O710T z7|7G&4_{w}#}hRj-DXN>Uwd`uCI*+d#EjfKwB+d#(CK@q@=_H-`Z!3U`Ozwr7LZ)( z-0sRq_=cPMrJ%z--JH&UD7I%!ugXoY&5`xa<<_GQNfcnimI|G@S$EOgGxL$;W`l9X zk*_L;T2EE^`Q3x}zH#0N?!uD@U4bmhp3@jUtOd3C4O*^O;gK`z7`ShWnHq#}zl7g8 zWI(>;df!z#b%^nwtE2xRA<O^v2Kdk39RKs@{wuZQFjwM01Iqw^rAbpT(Oxd&m{RpM z+&;ysDXUSF!OOcx`!ekn=39`rBt?IENst^uccVVpn~Zir->@5?@SVDm-iF*arWN;T zisqp6!9UU}f>BA`F4)Vz!vwtat2sYhAi=SV@64XU0mF`s_Ty!!4S{+|U_yxtEnDbX zF;^phbzG)@U$Ex=!h@tC%eo}i@jOF=gVEtJI_fOtBL$#04~Wq>6Iz+BMTI-n(r7Jj z8lW<)ZX${vxU#5y7X4sX9mL>#oB1Z{f<PDdgG{<u&?1}@Ng{-X(g4A*Xl}}}9Pqgk zp=b{U<Kt(D7;QLsY@0pbq7rzQS{L_<#9OsSFl(dgCf(;7-^VCE5g*~71(1QyS07v& zc%RZDUiq`1cC)l$xantz>V<P>brbV<%@pouDJ{A)HOFzFMMv7#?cz=)KYCI^ZnnY# zMwVHKYWTKx9|BRbe#%Qnt>gSsLTAQV_gZaDD{1z`S-A{_tGs$+<RDegd|7_t+WeXp z6KJiIziJ*Dzy=8ABYb*t*pM_U8(*iCcz{7r@EY-DJLAqf4AGFmfkLxEa_N~SHJA*7 z$8E#jTSx4QY{K5N>XG}P_vhw8y;EcW3qX6W9B#-SU;G)N+#vzzo7Xqj)upw2V9aZK za(6ehP><Lw5c6I&uN0e*#TP!42g%6jep_9=lMss8$Igb75fxVN1gZlG*0?(lm-Xv{ z{r2m&^>hn5r2BTBY1?x~e~|1l-8dMrBmEX=+aK@;NY^mZ>HG#FcoM6V7A<qHWxOVV z1CfT7WzbzS>EP<Z8*e^RwRS2zDyCN~J!RHBz}zh$jRFaN&{bUwA5QiSfn#Mh%oD~A zcxcO$fhBh^dHwu{53OzW%!A{xet!>%Z0L2~GmKFru{XVIk||bQg%z{BQ?}Y-iLFRz zrU^Wi$80IxnU;2CDO>Jh`*nJ4mB4>&NWQ8QGr%B?l5}P64#2z>$Tuauk|hUu0YAM? zG?NVwPBS>m8A0W3KSJceXn+@6^7m}fn3Ua585se^!=uS8??j6tkhF)YC{L}9Llv&_ zkkeZL*ohJ+5+IA%)|zmx9fsm!U$>%G;bjIGA#lk&QWQQiP5)k>q;8FJ++E9ex;=8F z(<TG)X{(8dIp++RXoEVPa}$|CW5NL8t)o`HahWoB=ZSbs%}n;Oc3oT8{*~lgm%~Lq zf~iqf&LlDjLNi*~$~;cK%kk_Ze(+glhYt*&svLo&&vQ`Xh<Cl*vitMZoUrUiuI{po z_tX8Ex{^9hMv*A$aL5MHePymzJXRkm8lgFXv<1l&J&jr0K>TP|hVyxDqbh!AOx@DE zgx9RAQm67#_7W>oog|&jyb5;;Ui?S{h+FZ8w+Gys31FYMKNlZA=drIg--`0Lo7Dpx zlqm(?5q=k67ZgYBt6feS3h*zyIYn|@_V*SOt}s3y`#vjx?|MBP<H&rJxI9=y6)Gn& zueFyrG;WKC%f5Z(Hmd+DY052#OEFFy`oqV>E48+K^TfZW(&I=}o};87YF|uN>bG7A zot1y&ZtW|#QuiP`QjI_H!7c<lVmLFtx3VS}S3193no#)diFFHKSFq=rxSb)gPhZ{3 z8^L!mAM&aQLIWlNuxGT}536QsL6KDbj95T-k(!rh;+?ggp1>PSFR2qMx$4RvGR5Dd zf!O^oyzTx&1&_Z*U;nVhXz0<0iDvz>Z5TyO&PAYHnR*))AlDj?HuS$Eo;sk9HS=Te zr5&iUJS}*m0wetqSe3JzzXF?2z#ex6#s=`W5K=Qv4)PG4EPkh2d=`S=3c{jmb6<4c zyj*JW{c>zCBq*Q&T>x||S}Rq`#fq-scT#b;YFu|}jd|0kPDg9@2dTpq?gz3wzeXxK zhz?}5z>k!CS|aLHH_W)$EEjFXV1LFrtqu_5C^GJ~aAXZ0w|u@}x=4x>nR(6~-HQ{* zsvXH5@QTX3Ys~V5)Mcf3J)wGorcX3(X^P}Emr&33)$6~ank`(#Bdk#L-T8smU;3}q zn=knn1sGXLVDy1Tf(l#ea}FRA8t!E#dyI3=8mab>JyU*S4lQuB<u?3or#K<-V`0y{ zWmkn^=?9G7rV9VlK$%`-x7_nczw`lyp|3HQ!-q^)+CS%(i>i~$Q7+Zkrjmp%n{KJj zDwQu%L7+Zfl;c#?K&!Cx{BT#{0L>FWk<&dJ4H>G!FL!-IN`2WxwJ|#Fd1a=fHc#>Z ze?HS&2W@<+>xtqT8?Vmx9U!U4DnN`(T0~wY5M}G^QB;-n(q=4GeHt=ff5SI_=QsKl zm8DND<avZPAsR8@|I(G!bPziy+U0JyJm*lQF2-`4W51lrM(zFXT&!7-+&V4%To8@T zGSq1SGHpFd7lzw(r&-XO>~p;-zuBSEQLp6`()RuIE}|MKx{OPk?=U5F!4({{?!4P( zlnt#C`%J#rp+5|>9pQLu$^p*CPkG%~{T}^oMnvb5Ic>iSUyR22;M1h*Zf(t{4~bv< zENhAgUbrABk!Cr9ER1@q(RsgXULnh7-T5Tob1O`1W<;@~Z_Kv<KHjj<=7$LqXc<%D z9k#~)C=K8>o`1HvQ)HB0BarB&BD;4v+wN4!Z6WyE;;stI#|h-iY{?v>%pT?gz#Hz) zX(D%IQkn=4JKMnctc9y_6)MM6Tp>xf#<oR$1#F*Ax&{c&Eo{q4xlaC4$De4_xvvFi zgbedOj~iJ#1(%EH-Wvk5qc~LElE5&6+0eC??;8Fn67z)w<@V%IbM7$wJ97^>8!fcS zt5*o`PP&$EqSDY+rjfYuwyJ5X=x5^lN|S9OlB~Pn6@<v89u%h`>O`RB%QO85vLhSD z4VeKsU&JFOWU3Q{52U&68GgF>h%X)GUUKz>MphdmUJJt_Q@v;9{jSdlI$yFf)1zq$ zF3M0E{UCR>PR>t{?1yI#Yf)^`j36Pbxs@wvL6bS|EVYFHF*eZmDC@Vy+g^KJ+;%s_ z#u(v>b5dfP_MBgis`HT_9-9Ao_6VtzSoXo!JTdoBD=punA>g<c0{6>Bs%H$M$Bl^h zs_Pmi=N?u{*}-|Yf}9ta6vvN{*aou7CQOjH+CMk%-?MoS&V<0-E4M3yyY}DL^EnCL z_+O@2vrFO6lT04b-6Gtbi1Y%kmn;Bvmxy=7<H~WU@t%xgent=vAzHSs@6jfvWcd3s zv~70nJL$rY{~q^{b_P0lPmApb&;Sm-UFTQForszQ-evf->AmT4>CL^;_{8GAqg`q0 zs*5&JmoV+YUk0A07(u{lQpr9SVBXzIK3%gP#eHkmRRCyObtO4Vct%j)UCf$Eu_pf_ zg3Y+jFVP%%^cIC-d(gGvQH52Z5XS~aD>E13*J_tL5+buD2svG+qv6~FQvrN-hO@IQ z8>$3FRhmYD(9GnAjiz-?;lj6)WD<EFt<mL<k*og&5~m2Zo1?n|?f`s*if$!=G3hpH zqFnt9Qqu!xI;`R7Xo1t_(9;$xNGvewZZ6yiaW^8)y$jRp%jMFjCEPg13H8Lunc)+n zL3yR{WNE9Sh!VRca+dkI5#HVl?k)ONWm4+vwY#!MZVz|6NDYtlf!b9j!bo}v;uv=n zeF34bH>D(Ke^L=Iqx0BV?os>`n`|7|VdX@;&CkdYX)^X8@%;#s)xpJgB%tpFeXXf$ z>@tgpr+I9E=pVA>0^~jXaM~N>Rj~D+IZd|ObhGC`W->JU=@9vCF>e39PP#mmm$oA9 zELW=tN-bqTpjiu0Nf5lP2wI>3B^`1{M@te@SL@_CUpee^eb6iT$V2J0N+n`pqb^{e zPI3YAdUWR)2fGXwdBvSuZxEkMC2ikpAe6_u$|$p&Va&2FkD4ZtH)T9>i+z3(Zgh@u z=T{A45YL!zzg58j;KABsP0R3y9EJ*+vb(OFV58x4*km&ZJUAK2BrVL-c53BJWwb8- z#@X*Ky7%O_)H5cxF<}U;W~VuWlK2Ah6*FeE3r+=PQ;zXE)$u{gaZ65@+n%HEeK6I? z=8o>dZ2m~yw05WccoZbSUp=&reG9PyIJ(T)o3G=SJLEUe%Ur-=cfd`;ayyM*m#cHB zV_HT>rfJCOo!xS93ESRFS09QjW`|0x(Q@^_B-vc9O3lZQ8l4q1>l7oe0Uyf{$LfLM zN}R@CW`<_<(`%*`b>@nR95E3-2IztW+MRm`jo1KOkWX7}$Wrg>{@js{EbDrcd1R7I z^DEhDU-G!myN~@YxLwMHrh6`f>w{?(|8mQm0_5HGcIPm-fKgPkK9HHe?7+fOgwE!P z8I;(meEa_A{O3KBqidx&qv;8<v+>K9AjhtA=H2JCGp%>Pl)frpO33eOqeC)Ttn5=S zU<SVcN!!NJRC6{{iicMwo03L~skBF)9;=7ZQ;v~to&4R2^Z);wbX@s+6r&^|*kw|L zY3FNHWbH61{8aQf`q#y28DHdY*sdJhm8r2m;w9rT$S+hWIoM$1e@nCVFEg@%bY8gw z#gS)`r<E@wqGvdK!#ZW7aCbB{-q%gk+dJ8YYd(|s%@(~u!3GJsoJ|f`VA~>+p2Kq; zo{Kih4a8iVRWvJcRK07<ZPeEA{$7A(QYPH}!^#WN+?kq8Ge527ao56@=%o`DRLV|; zO?CZKi(t!aa_yHc2v6%5z8Bdc>*p{cKVA;aR_i!=*?7y@zZ}fCZ?V^*{7arML_R6i zI+m-3OT>}NemmZIyr=R92)XA^fzE$2)%9N~f%WHr^WPS){=MkyZ)eK<dq3s>o&WPS z{AZEvf8m`&%F*2_jrR$XHdchMwlUi+aZd1f#_OmVwndN3vBfhzsH*zRk0(*gfOJot z%OE+%k)jmwG6^wYXB2*IdV6!skwyMjse$C;N4GJN-K%kh<kwYIe4wJh?}rdkU4kjr zt0Hoj$mK=g>m1vj^<H*HO%%+C&Kx)fUSB(W%y2rVSYPPhZt3JQ*O>&l@k5!tl?Et* zPyHIDMw;MlDnI?G(|j~lAtJMF8Yxmnk)FPu$S=&P)}JZXCBC<u^KRwBbtQQueN5*} z>+Q5v*qceT&t$)&W2s!EGg<7RM(Kn`^6zX@`BInr;rth0zGjEyJOPIBf%ov?IRpw5 zploGVxf4^9N2C0#an||e<lc9lKCN=Lu2&NDeQu=>>04+Z4vUD54q3T`q9p)*57ih~ z`gA2tFN|N`PgvdRa%!K{Kmw=v;rP2R--EA_W=$(8A(<vBYR}kRqnGH?jPczmi{~W; zYULQ2Uj?$`=|c8b4@rCIRYjgbTKcY##9@~)IV<E%D>)Rv-Z8E>lz7K}HBMGkSG#uR z*Ss*im<i9#8~&2Ivj2U-6PtNnNn73^dA}SGe&y6`21B2YMz=ykx)~N?q{jtjMGemO zE(=G7H3Z&lFs85D9pQR-QyuaW$Ulk%bg+TkcFNSIpmMhuSLX37M%5Xn(^i*#_%H*m zy_aNKuQ<*x!MfYGSo5JD#_AMAbp+!+C)<iE{gh6~Z%=c^_VG8)+~ek}e!KZK-^o`@ zwhx(Nzynu;2vKA}`UuOrckp~*aX+XCh$;p%yDfVkJQv~mlNMiRcE0Brn=*O0Gey^Y zK}nWqjPDv?@ug43C`|K+nt0jfy6cv>GNIkqVV!g8R>xy!1!kG52<HbDQHL{gp*<3* z^h98O7~&C#2l=<vQNim!0>)r8#LQcH+3UK%{T*2}XA`o>V;%eSxO@Vk5H6Ld!p>sO zb`G;YDKEA!L8NJdT6Q7z>e)S+k}_9aIFGFdU%_M8&xG4_?{LO^MwxDudun8mYG{vg zs3A9H4$iu*TbE*R<<$71KJZ3n&|vbV2X7BkdC2ekK8=D@wjS5E)FIDMKpGymcTK1k z)X#;lM{kMBBDn7ukQ-Z0arPE9zFn4FXsqhgWcbP>N3X)!h8D-jVPW0Q_xI6DTm<hd zSDf%2HT}u&__MXMDu;+2xySSHTeaFIy8O)|1HhUV|MeA!E1-|t?+oO2G+g4q%XNXN zbVQ87BvZx-T6?Cfe9!rAYma0vWA#<qyIJioK6vHQx}LKeV#&_I1ufOdEiy^8fX3Ah zCeY3~aC5ZPPKoSRyr}Z)g-2PYT{<nGxXf0y`IclD@?!q{+wz59=1f3H$2l{>I|WCz zO#3NO<iN-gqlc<DyZd8aJXr)~S8kC!$WX3Es(3kAGf^+dGlwD2t|BytTDpRD(V<mr z((8_SdxpX`X!avra%x}uUX;nwd40UlK=0l-ow-Af#O>eVDhi^EJxVMDreAxB`P(P{ zGew`&on`Zjy1h6SjYPWwthkZ)yYrTUs`g0TF2=r5;gJ5<>e}Ca=lr9nng5Z9m47@? zlzhQzNS0jtF}RM-g&osQL>kRQ6wL+JM&gRM8v{~~4rMcaos#C^pLJ=P7TPB-J>^y( zh`Rjsg(L|!2SQ|9ox(?evh4V5Zh*V>#<*GQRa5lJyzn-wPF8DW2}h<vlTA~Su&-za zM<7pTzymq@W_#Y$b~1k|Fwo0d(G6{5s)4a>Wypr7{RKkinL{;?2;Y7ZuE1LU!t}VY z#@9Fm?GPhEFJ1oSlahw_TgbIdI)|KESQO}R*^32nUAz;$4<`d)AqDe}ubtOY+uREr zq?VuAm-$+Y)MZ<$@P?_RU2d~JM85rgjUs<D=Yo<DkhyA;M({HNUMMQ(Zz(hJ5T2Zn zt?h<R+Sko2ab<VAZqVJwr>{32m^+CJi|>8=5PpMX1UiRa4Gh;uF9}tRA$54|P9hRK ze~mTEcwuLoP_@&w5lyxAabBURU8aK<DA%4`cMG<HNT%yS{DttE_XsSVIiX7ofTPEX zE91i@u8E%|GzX5d>=CI|O&SEMVCUEvTlV5tqO0V0g2~d~sF18;SUO!}g;(E8iwd`& zEQMg-*CsGJu-x39ZKN&Boy+`D5!5ig$E#YZ?yh*O`t9MDD6Z}W#0$Ec6iJBc73EzW z9P|y?m`79E%Cc?ZJe5P~sY@=xEXU2#b$sML$~44YxHhQkeuOO0Hz`ae$qbj2X^!K& zO7Uq6R=v?RKPb+<<$Zo3p3C$ZP`m<m${olozOR?}{*a-I5jeaUVISIoyuiE=;Qs~{ z*M4OM5FiviB!E7VJkI#krSAxHTx-i}1YsegEMp$bYtoxH-|ML=TX`?Db8{$9;L(w~ zL`{g}HHuVF5La!xtBKpE9<~p}&l|C*VFhckWL5P!oiog`uglPL19ccrd3T*uws7^R zJ$~O?zf;9bRG?PCFT5%4-6U|LOo`-)=w)7}S7I~;Saj*PWM$j%aZB?YrI(o3wR-2i z%Owt!QM|{Qs$9;!_LJmiT1YKTpw9SDUD_>fvY8i-k7(0m#<Tl#)#JOZ<jh4oJTATC zaJ#^U98YGAIj$i&)a=v+`$o7qCX{VUT<MSsUxQsQB5x67!*F!g)yX?)$1yK?BU3!q z_l12^bq3^=aB{?U&cG0`E?5zJA{bSOifU!unmw@3p+S=5-`IT4Ke<~gtYfO(mg>tr zylyZ0J$vdmL!NHkl35e=`@US-LtVh0FwTJvWI(E}`0IB`B5ZLwqfw^l*^*onAi|(B z?VhueLQ`*blBk&JH=$&t?dzOsHP%DL?n^4%yZCKA0(lgcMnNZMMW;7(`HbCmLOM=O zH+y1e$?U$39!~3xqZhAZQ049r$m;$PucBmw*KN;<i}5Q!JUVC4#HLFt#{}>Jp-5BN z605Q2#v`*Ou9VD^wcj$l>fdl)Q8(ga^ZyH^3BqaJwljFD11ybuwm70QiXyBsvD_^y z)dJtMIjbf;hYhxO%IjuiPi`@M${4<qm_INvK%$VJkRwgn2z29FaLT)|5<s&LfO+AA zdzPR2ndv`8P5&&GQMHp+X!1<ea?Zl~A=c{Qo~j~A+HCKn-Etqdvw{0o^z<&G`&aS4 z5@ko;0ubDb?U%(oFD{~y?KC4bt(SYN@o7cA$N4+_J>;%pPGa-}#XJ&^UUO2Vn_IAp zNI$&T=n7X|TiLL9>@QJ;UOAJIEV}08Cyg17?oOq<9!KeX4O)UM7bvHlzeN>Zka+?1 zJIiQ=0c}{>bewL4($#T{v+S(Cd|evO*yc@nZRKm~>arD$rCEc6z0iSyi*L&+FMf5) z<sSZ1q5f~arT&5A58YTRxh~x|)CF|NK!hsn=*IRG$=1~0{FnVt@!8*#NBGbF&VTQ7 z{}{!-_&|qI4D;lXEkUTqHL_n3Ue=G_?`SlSz#(CBk2CcM*CKZnZ^lxjr!{t=L8kB8 zawWws3$#}}Jg|B+T*kB}kP&&Cu!A8xVLa@&Yh|1bdk1{S=BcsquVr6?x~stQenRxf z!DctFl>~Aput<*Xk>?IuHy!lO!I*@~Hswa)w5_=<sq>`q!gA`8J+xF}DstB@WS5lM zv(w}p#W#GJ+nj0jls)%f*rpC}suFo5xPH~emy2H0z~(6%|7A$SpB6odzQJ@&8R`9& zS+B*1lMg37w|&L-#l_Z*UepE4i1Jryb=~0oEMoK)axnuUIPKZwFZydR?*5FZys(G& zae`zi6~wIP@%LRymi5ICmKhN5%wl`YNS0%%-SN@v49l3naM^TCiHKC^78}3j<EJU_ zf>{|TuSP?v!LMKeX5PJ4LU1Hl9#Elu>b4-0JE>g5b84LBJh7H{;N!Y<HR*QrDzBX3 zX#qQA;m;DLj~*3v-mkk8BFcYdvdWk}yuMLYnS^D=ZC&&B9lc_;=cOwv+O0~pF6drw zb>ZxfDmyu6t33W5`zF3-Nn&huCag;~;-T|hdasRP>S}eCccuZy(U-nI?J?#}48L0m z;duAQGW@59>c7d3;kj(F=4jn$^P2s55is#fr9#kYi3lRXL`&scbC@u9cRwRynbqIY z6}*X!cUR#w3~jxLyb35TS2GgjCXk}OK7tK2DYMZ(os}p#x+IE1YD=F>Co&&0zf@fD zssDYzR-Huhb2P48s0MG-=H;mPc9=HZi~p5Iw=wIzuA6*sRln1#z;a-;ifV+J!;U3N zFlu{K3ys6fhFOlCb8iRS%+Wvp*4WrGrBRyXps5>;9QGppR{gu!roxrqMvM)W0a*`_ z1fZZ?rpw5s+5R*5&zT~hQZ(Vhs8;Z{?u>w+pCN%Pt0(JX-8W&E@S!0~W2UFg@26bs zUQ&`QBdX8Y2X`8ei9F``I%VvwQ=!;4?9Jt)zd&|!s{C7(cPe&VmCqP=VU~;E{X>o$ zme+|uE>$*5Jz=9OkR2FoH>a`&l0W|dt(nqirK6gyPGZA5O`)_MGFcyni`VT!wou+_ zmpB1$EzZOaYsdyt3I~lomh(Ah#gEhy2A)*n(}F4#y(~(eIocY%DV$n2b1{>@J&G~V zj27f5)8yelRVKMcT940(2((L}M^tE=3hzhPoiJzGuMTT=su@msDJb90Yps^Q?C{CJ z*m}hFI@51S#j8<I(_X)&g`t(M0u>%Uf!GA<ZF9~-IOw{4`LNf3y|$;$E0W#pq4mt# zWd4V<LMud`MdQ>f2Hk2rLy)wLD|NtlfY3zq(<2@I)s)nTd_W4vT(M%oU06D|yzorT z-}NklX?6%DHS(yOTF=bJCsyp26TJ#oLJ(MCD)<G%++BW5sB0sTcc*pJ`<i%ivrhAm zZ;a8tE2x>(I2Yx;a!9rJ$l-X<9mSD+BbWR4sjj_}^lbBW>>b7qpVn*Da_6_k%6|D0 z72G{sJu~zd-gujszT%G-zgD-=cUj3PmQ)4C3)t$Ol}MLKPM+T1Se3>Y<mcPJLR5_8 z7-XM`IurHXUEM@pJ@~qqCBP#hdNe8$*!p|joeu&!ffgcP&4QXME{g6(!(JVDdt+KS z($yY+KGDNv<9r{t4xffo*4nKOPxj0VbqY+qA?Lg5y2O-A8`8r9gY_hSA&MrTbC^EW z=kz3PQNEb;V)I<ZqVN`Bq5)%HEF`j24jr4;-dVquV1u~s%;pD7nD`_hsVrB`;5gYx z%+sD^TjuCimg$`PN-!OA0bzF!0iwy1%lx!l0RzX|uUI(#ANJlms;M{M7Y;>G>0Lkw zQk5!QDFF*jM67g(fCvFGDgpsQ5tSYg5KxdJRl1ZA=_Pa!q((wbB1k6zgeW1td*;5U ztar}3cYbHqoIA7L`HMejS$ps2+27|=)8uo&wjtvp+0nbL&vlxzc*ykTQs|7MjorqG zGMvzWjxqm~xEV@x;8^S8P1^49iq4Wryzwe?m0{c91(XEc6GLIT0rRGZt)7^xH<9|Z zuSvnZa(XSHxGq<E%<eX?1!Ck7H9SS1R-9bL-rLr~gSsj1faw!9&qljOjnwW?5+$aj zqJi4migFDcBc)uqY5th~VJ5B1eQeIHNk}!fb3O6%N!2*Xi#bd!Uua4c%kEZw4NJCV zd{IxZ?!w`IoN3JWTjM`wb#I&BBM(+zldZ3JYmNE3dggfX+i+p?G832`=ofU);S*&M zx4uSRg-5TIsr-qY6t8h~@ZvEO+s6x@E#G@4!L>x$5fY8=J1?CM`k4-<LMaH7?qj)J zsLz!0-Fd;o)*&G!!rfMwY}-^uwPI(BWs*{8&Gt_<wr8j=!?Qn{S%Clq+NGdE#PQ!C z(rgjdvToI9h2w+M+GKiZpHPH|MUwfWfe(suH*K1#)Q`ZBow29CN}TWu5k4&b;do7n z>&^sSyQg3Vmgr@Dvw&X7j4C!;AnA=98%ZHrZn!~rpSq!BWTh{&-we|%TRO~pTFK_L z44g3_XXh`JuR{2ngRNjl6ODAoLAsdwD*W5Zs8O(@QeZiF>TcHDoM^NZ=a=q+qs$QQ zkycNoTOC?+XK_EkC{;vSM}r*pT<w8%{e(o!>dKFRQ<Le$F-hGXdz&vqZnkGpOAvon z39;-pM>eA7)&|dl0T1%R2;{UmF;o2-rMWd0j&2M#tJU?2MvI14%e+Wt@<{AszH;4; zN7Oj+8{E2$wVpoGB1Z9#)iWn2VDSR`apfAi!$SQnvT0a5)W&lTJ(z!4E|EBrNPIE$ zQQ2H&@H*p(auD(}0T83+j^C#=0a4~fU5DD8LOLd~5)<XMl}A@^U2vzrJ`y)+EtH=7 z_4Dai7{*cl=r<4AgrBXJsi9`eNklR!`}q$zJ0jNM3th=GgyTz#geUjB#gW#iO3~Y6 zi(ek{&Rrh;aeCS+V#XoEWyMEn{Km{r)X9#spFlg7&kfiisxwG?sx5_!nEkv(&MXL4 zf;jK%XuLUpM@_i(qpwXt>Ka^`_uI6IVs&^`w5_z8*}HSBE3<!^75->I|0Ad%U1cTo zXnB*y{+tjH$e^NUU(rMOERVe$Gy6UD$3}%Rw`!J0-em>Pg~oHoucYD|-x{8?WD5Bz z&H)6-?CLW@BeQhK>w$ZfB?vzy6MJ-Z#3+ZWWjsuIT}E|QH=4(L5&Oc!rufdQk9R?$ zxgk@n$A5?h_@cawDV7mU1_qI6u)uQqQl%@e?b<J+=-(jI`WhZ{Y3$LFUdgO;_wS{g zIMoMYO0PMrd3cwa7z*eE2=38<nS#1&$p#cOc>4Kat%7FV@1*yiInm`W4ZhE`{w)kS z?tJ#H?L`pB{K=5<#1$xSlfi-|`wGQ~AIgnuhsf4X<l>`+N5Hps${l4uAa;q`*k{o! zb0|YGpokl9-~JoKM>8t&5Cqx?jU&NPyL~@ekFV-X<e#;prd^9LGO4egQs`xwy8GJX z(W`!!w<k|Ib{VQ^R^r#%esQ6i%m_ei6*J-sjLwOBy2a&1m$o;Du4n2j{XFI6p7^=# zZJz66ks4`>#jJZr%i`LE_n~#}5t5TQlWOV(irn>hQddLvvj-|0WxD`w0bNVIc`eDV z(~07Ei47*^mUvSvXY;8%0i;{Wi1dC=v<%Os^t^zGA*wfL7bcifZ;C<KKB=rfM3tpK zjg|Rw=M#+AG4gRFk1Fie6FBI@Jb2xmvRAYa%tt6fp&Px~9BRp%l@%*>lTW5#`#7Ep z@`_cWi3r!*4Pi}v4@V^KyAW<(%n`1Gq7A~fnry{i0Lw|wPG+w>lyIofm4t6=&?+sN zGTwOX4wuXP4rgI|Gx6=EWRf||>io%#8HVQzmDBEoqL|&+6!99M3Pl8=YgA8m`=0PX z6U=Rk&$s?%RBDXj;+>wcklj@`_R~4zd$+II)P*6BoW1v)qTLC!a1V!V(M8w9xknZC zg!8HKbMsJ4g#I<Nip`2IRbD}F-=F>YsOu$R@`8NJQGAXl@IN35YuOx4w&cpoqZ_-p z8cJ*IwU@;Mh<H0TXJ_9>PLdVxp}r05ADwG4!tJ1w6&VMf$TOcle!`(=g}R(X$+Q;n zaisP8H9K_N884!kd~bpdvDZX>AhVo5mY;%EmmYcb?j6Xtm90g8Wu6b|Tux;n5pZ!d zaHR*(23O`dMU9`F)H={#yDIT@a-UB@`<HI2@J}=Rqt)dc@jsPv0?YKT2b2HuC;x$u z@H>F80w%6S2CM?mb~_UQtUV43RC%fA3`tvv_0YQ$;JepoA9|D;u8Y`mvX|J)miAjn z(SD$Pe&98WA(z91O3T^d1$*05RNn=!dEVJ8<kdiZT<pW-tMOldK7ZoGq5AD@OjM1i zT{pyEG``J&@kco(rb)MAIg6j1_KbE0wjipcuar(yvNJa?HyALnDc`7FUdIc*yl9wn zSg2p}`NdEEa}2J<$Xm%kP{xJ|3-u;fbwD;b?x(m+ecFM!`2FF!rFIDR-KA`m4SW9< z?FZs_SyW<`Akx%4LVlnXFvAiiTT4BkXtvZ4X6-w1TzR5bTaMOyE8!P1KPHRfa~J2A zbQvBUdm4HK%sh`rFac~-hYAR0z@hBD<ZWs@8ysQt{gqj>)R<(o){YcWVhG2RB(`02 zmy;ozyw>I)%2BT8?7WchESrrc{9sfM&ks2{&QTjU7$eP7ms15BVY_Jff;Zb!UX0Rs zcL${D8`K($3t_68CeOEF=u*u|=E1j&nka6gD=^QwyZw#;KB$+R3RO@=mwD}cn(^aU z5I~+G({X@K>(rMPKI93iFQM{?r|MyzP4nn|5w|UTM=!t93qi<LQdH!d{nKr_v_E|| z^dG!rCee97wF*~6Uy-N5@fPv5!0wV!J1-~kahl~L{P}A5S#ITPZ?+!UUbaPT+z0_~ zWqT8o0@i>9DMiK+Ewb{+HG2r^4Z?<akkIoR6wbFN?H*R~c=z<i<JmZpFAu)DhaL~M zS%=tdfz}j#Rbrm}DFLEjVgC4qJxg^uPqtJiMhzJATnDH2X1>aO^@;LXJS=^<-@WmC z5Qr8jMS57We1*d3Ao6XxV$wqSUYd67CUGq1xtcSvS;Wg+VZrFNmq**&Z;<L&HXy=d zj`~y2&Dphx>W<x^UNe7!!{_6_jd-55j6R!a%Vr#NyEj%Dr<&MIzx9z7)Z0`?!A0*T z2z!XDQ{fA-v&tUsbt4Z7afMBoNrJNk&#G!)Q3+n*8MoU7KTwio^T&X&la>)7JSJq^ zr5XynV|XfbAv>}uR9C++kL7LfVTQLrvieDr5AFFscLb*xRMAadN%N}P<DXay|JWxh z{$n@si2bZR=^{d_<G_;48eT-Xmv#hRTi*or7tCZ9PUaF=9<#HBGAicT8?)^H1lA%` z641nH5SewBnkiUA<eExb*oxL@z5-Uap~%+2m>Y0-0kIoav%5b936EYW?>Agz(&GQ= zd6EeYXmI7qLZl;h+9AB?_%27Ja^0Ntr&pe!fGnFPmUc)7=U1P_;Qw|15J!Uk;HR?M z+wbWkDX@tdU?Je_jwkl1Efcah+1MN5NV69)y>=Qm3oqOH)yJW>wll-d9WfU(C%*f0 z`U(u8-SH|oc@V&`+<PfXgROX*9j#!D*0H^l#FIZmJ*S)hyaeUvR_8_d+Utbyp<#Lg z2C>qg6oLyr6AwPsE=vr)K~x&2kM>h}+(htawOa@M7#BDwJ&~H=ldF4lHi`+Biy4te zm|d5&xbtIRDV^|;yV1C~u=(|?Ywt%H#VWYN2QMxPAy^H_-wFAl9MY86b1aA1E&K>& zv)2_db0-@Q&P;ZO_$O)d31$dQv8o;pqt|!bx;OF$LCclCaMZDdX|@f6Hu2-xT}ciC zEu%}6;2FE8-1o2QS?5{6+>l4BQQjv+|GZ;y*iX-c<>JZ|p8}z=)R53)eGlypZf7{8 zD;Xc7$nq+j@))`Dmd6dhq^8~4?+02C_(xlJfAHyMeFYQ2bibgkkj($>3L(Jl1OI;O z=%W;fxWY(bZFLK?t#Mr($JUNm*e6ymN;3V*f7x5Y&_TOObp;ZIgsjLc#fO-~?BAfH z+A$=GLE0W%dm}>;u7r%+f~puim~Z8vSBi!_2;(xSK1?akY4}pmL`j;pUjg&bOh_wt zz12PHEo@aL%A$8dpo-A!xt+#=gV@GJ(9(lem(#x9yghC6d?9i(h~#zvTZu1T$Ubg^ zUpeC%RJ5{Rk|_M};nnK$iE%%2JFjWgr4wImI?cI3d^U%#=#tbS3wKBAOUlx)6-9q3 znJ}w+b=+G|ZqM{V;Ru>96#iJ6W!qA(Pc>B2Kj+lYXxQoNY+Bh>OO+oG@AVu>l`&fB zpLgjHU_pGp5kH3AaARIrZk_UW6FDp~j@F_PFAlYViXIQX08tGNM7hG#8&>B4ous(^ z=iKFe9m~iHXR5m!JTy($<<kV*CB?lZMe2C`-5}L7%0p^c4;E{hI=Hh{6zKj%3&qgx zutbyBx}CH~e&9yCGsQknmcw1$@Af;_+dtd8925&zzLUi2XKIWU2Xwp*Kx)B#gO8zi z0k3)M0T;2ZlA=q$@M))I_V7LZIUTb&1$=GHoU@*BoIefJSz<$9w5U<7367Rnabe0N zN^Sm62^20sCM$Nk*QI6LSEw=Uqj~K-yClpN(Qfs6-C0`q%172SB<K_nzTIOWs1$N) zm99W8ZFjAJ{pu<pkZ+$M2A%VEI?s~Lf@Y_r)cT{a3T@jbzNf37nkmaU0l4m?nzRYo zQNzS?$+dP>*s;=8k;PA;k$ZfI$l=Dhr8B;7ZSy@`({07|6HRn-;?>oeWx^Cq4g{8k z<~YgjVYC}0bJQg>at~I?oPeI6Dm&7ItC~|v&*+`Xf6P?$b^C+Wg*4`O$|o4NB3s01 zj#LYheQRu%%J>g>T>4&d4y=VwE9At8#v%W-y`9<Pvkl1`ztZz+nzmZKzWbm)d^ih4 zOiPhNFb5ac=<LAkOWx@U<=SXss6_eq;v$m$8b<rvd_DJ;Cq-T?DYBuuW&%N+X4kn4 zCR<%fODh?*U(mcs<(A!Z;$(lIUic_g&UmyoBS`GpBz}d5XQ@7G`I4{y$+PxdqKV#| z*J~u^9x;rDd=@`W{Q<0(OTYyU(J8Cj7paTQMfhJ0CwBJ_xySd;nkyEv>8^dvGY+aY zeklBQu;`vyl?T(x|JQ^2A1?8~jJ^M_zxOW@ZS}K&-=Gf_&4%KofBSLduQ|>9V~}6+ z<nOdhgHD43IXcI0&{#s$GKdP~n#c$(SRTG+8$LAhUH#ALCXBkVQA!d?tE&h?1izY@ zbezBq!#)>F5JR7A*ttVCl6!kL|0SiKh!$mpidBqgqXooF(f7b?Lb)QtyNvzjMdoDf z0TJ-q;p2el973%*@IbOrFO~iV-SwP1B{G-a;FtHI&nwpf?=T?rwIUat&T94cX!hr% z>r$(bKA%Qd0yEQSF13bS5e#)C(+v3Z&d3%Mf&x@D2LvX)QQc|GcZE_{&mQC$G4l?A zgui`ZQR>-XAg6n?;5s4vl)^+3o7XJ-8kq(Jj;56FXfA&1J87Phb{AhQoS~G+s|$L> z0Kyc~{Hfq_7`~z^kLE_!{1Nez3-4=Q3g@vM;CNvl6Oi1{Yb7&teA9*Hf|v*6Q|cJ` zi7CxM5vY<C=_BH{X^??RyTnpV`p1o0-QS>5uJjdgso9<C%9}?(7yj7a);HOEM&-y` zv2&^~rFoM%;xj6Y$6)?x5b5d|w&jUE^>!&!!8=^rN%>)jHA7JoqBXXFQbN;&gQ<@Y z^|?DZL!B}(-dHJPKh}Hh;cQr)AL-<74hNVQk%0~298qqv0C*E_Sc7<i%ll0<$CXW8 zFAx5Y*PU#S-Z>H_Ed9hVhtp+?)YG<MfNijh-J+`_1g9_J7E{IKZfA5_!C)WEL|-RL zyy>g9vN~dON}Bt^QrP)fzP(&PAg2=bYYVHSU>$C832WEfjVf_4D}hmrn7`PR&s6Yi zUfz~`E`9Z;>=~J+gN|IG9FEDuoEAp8i1(BX8l22X4I!?q=T0O<KX~~r?dba~fsDbg zj<+M^ISnNe0T*&w2`qi-Ev($50afw7$dlW|7v}G!Ho(!TcKTkD_RTvZ|HevER5(JW z=2?)pNEV?Tsdn!KCn2Kan@>x(xSiJUnl0Ji?Wb$U)TEpai%HiDu7-2e_YIBvUFYmJ z!1ED~P{tnaf}<cpleIxVHIEifKF~8O)K7`7t~;>kN&oyvtnTuqv(fidi@78&|6FF0 zb2==AFnXLJIGVM{Q2h=JDff&0^Dhw<l<xOC%JviIhRlnEteqa2ZYO$!9_oIJ<7T=C z3e5T|>D8U=D1+KG0D_Bb5Q@|EN;t8SJ_|5qil6uD-+MH?EFUWM;(mIm<!gUg<kJ|) zlf1-X$uQknq0THZirh4St&8DyH*Os$?>TB6tXtn0-n0Ki0r=;SnMoCddQo;YH5L!1 zEC0a_{wq-8FZS>M3PNVcJh(`W#nO2x&~{5+M5)8ak=+a~aVK;C;Ls<jdt^D9B%VC8 zeg1f!G<)(~m_9IC$$X)?5oWc!vV_N_T1Pd5JhmP@Bp#%3n>HpH4U?;1nN;!rN7lrN z7A;KHR(twkc^R*T2HtJ_agRu<yDJ|khto_h><9>Xi40`>+@dZLTR})|%8$)t0@rb% zQiL4&Oi3ew%fcssyUAPOM#9as0}E71^Xr~BwETF|J(Q}V|6PzRC34-ILh8~H=zx?i zNKa_?Q1Vt|G<CZ$wK_vJ?!|_8;njj~&Y%(R&L)VaJPWZ36Cy^g`%uL9+c4>ZmF3IV zJ)F3ockMSoii@UKMJg)GEy_H3=A^Y#t@a8=_>1Fj0VryY0<&YI2O};F!i%(^;)n)d z+9k?-L=G8f>p}(Hm>9)#7~HU*QjUxuj&5w0Z&Z{kRk$77i`|zsb@5L>_m=0GHRxmy z)-rtl1myIpWnc@(m<AX1g5LQBG3_g<zd_d~fHDK_Sr5Ax7$yF-P-pnl56e;mN=?N) z9^tj!TKo*FI$Y~%Bri+eRq=C__^S6dC|3*0Zo_L4IXoPwF+i`y!XyQ4Uy6L|;pg2- zesFEu)URjfBq+fDZ}^|H<_ChvD-*2*$fRcM{1G4m@OuZCTjNJfOi;A<ZnHhK5tBMS zkWxO8AEWV-Hbtv_vaM<ZWM<iszpq!3z!CF2vH%MneJ!&B;d70mL!GL0#v#pogCC5n zcs-olYv3qUa(}1uz&v^B{L8retTGqF0^K{ag;(drkOH9#D&f`C%8Qhf$+mN%2|Pe? zEcr)M-DAfNoT||WC)i*rhC`6R-<xDEy7Bb-t-!DVm1m*%vg4tgGM^|~+0Nv#&R-RI zfb3iRyAeeeQ|!3X=UqBqzB;(CZg2kBuPq`1u?QLpQ+;bD8Kxd0719Cbr}+~Tc)NsR z^iCBn>(t`*wQzn3zWnVKNOgF@3DjFu>N%^R>wbrY7r*qjy&8xBZE1j~B8gU|6r35A zK$?a#ef{3WR_1BPndbjE2rkeB5zR1vmYZtt(X^5d<LUJv@pMNqoxkylGjgFaRs%jY zOptp4VMVN6qh!6Wq-vX>$5b|(^vMUEfrXihr6;7@eUChgzw_-$(eqb^5@8JAfK=_@ zAQzRGd0s`mtLfk<y)z8BCJU0-lj)5#?HjM3TaP-|*4ctEGLH99anG6M6T^GtL7Mc_ z^e%B#z;FrVFk&rY+maD*>(=Im=O*KBO(^LYDEl-lnm_izhmj>Biwot7Fi)z{3~3;T z!!i<Doz|tKOB9Mg0wnz{EG94{kz^0NW?M3RO3cdVy4;nc^<S&D6l=Z~5_ugBB%)We zBgzTU%%lAL43jhg%6-(qdBF7)*|sGHn^Pax1R|H0_Zr*|agi-xM8fT{f)rQgH_AC5 zRr{ObZ<C)KW#$Iy#r8m}z+GFS2pu>hehtEj8LJHnJ)sp@Y8l|C^21hhdU?~ZC1D-A z-f}a7vv=pMu%xbQJBVo&=<DS|ngbE2&6QZkkndL;(SUKJD>XXczG>>(6;8XUP27N? z@?Mp#t$(yIdjZC@KSw;_Kc?e9<RM~B4*6ohqZhnjMfK04=|I2<Hz_@F1o9w8XZ9WL zx}D*#;2-U8T_QZ*&e~0!EogJDe^T)L1M4e<=w3Q?0mz|<q$a=@0(O-wd&FhQEbloZ z3!Hia_R3{NzYBe7!d%NeZ`YN!z9{_qtg`|i+ECqc<1NLceU5D;<P7Zs8TS$`#?;fw z^`ljc>T`8O>QnsARjun?J_0G<wiKhEh_k)9v&nnf&orLhMhYbSYpWZM2NV3eEiOXE z5D6U(6BD?|CMIS-ojc9nL@q0oAYzC^tM}@Hid7#8%rX<mYy8p`3~?YB@){K&o875> zdCN7>fkF>DH?>Ve*k#l$U1+?PK(KE0*nF0JuV?7>t9*H6A|++UP$zV%XD@?VK=JB= zfW_s=N?*v|Q5wSN0o@FhBVOrGoy3v(uWV;Mg?g2I!CG@p%HB=2Y8TsG?<JlDU8h8D z0DfYu(;4dfic78w#YmuYsappeS@D1)Wz6$e3Yz8zaqT(#iN{D09(eq|psh*YL3PFq zV@Ajq!1)@HSH3cwZ$`#Xo8st-Ui0jVF7+eu#rj(N^D4BApN?>W`w+)imS<M_pTlJg z4_rY%py3b$YAMBs@SY+zS4z-Vu&=47utrug2g(fYp-OC4Z`xh*7s=>)46kN&$0#+& z#YVq6HOKIoiXlaILD^{c0ET&f4;;}1c!Q4#rWL??tjG~n8X{;^|8(h$ax+}Y2Jaxe zn00!?{e>VR@8+tU^>xl$9Z+-+V6zaQuIiTSwh8&Su#f9qDnkS-l_U8oL{ajh>Kdw^ z{vz$r^(kT!apJ`*BlLUwsNbLfDI>{OoS|5QI_qvrE-ITDHGyJpOnzA8Mznm9UfGF) zV6k;Veo4~~wxSMppA~U@$D|UKCt^#^#D%L$i1xDPA;oYIO*24i`zMLLke+?6=VheU z7fszDOU@o2??NL*%&+i12~0Zi@zSDR%~|-I3BL&&s)V8R;2NUUo}7S!u=W*t1s0%E z_T#@r-VW~$;C8}SB!`}fxO|bhv_a!8DzW}HN#Z)|>p40eD7oNT&=(?BCLpy57JzIW zFzuj$5FyvABYB@<*36S8&$)*W{qj#4CD@66V45*GDJmv?IC*@5X)1&lX-NI-LrLfg zJxW)0K*;~HV8!alHhKpW8rd=~D9n8hI$133GjsK}l%2QJ6YTeMb37pZ>i?Md{%cU+ z|7@WA$M8zNrapDwE2Ks+NEvX^M0+;?rW(xig|%azd}H`4hBV&^<)=bz$wl4eK|PV3 zzd9SPB@^@?ng`jeZ%U0-s@UcrURbqhXx%Kd({Q=+(7#WDX+3*tix3&Vn=s`HuvKAb zXjF>|c{ob3R$pKFp)-0EwVB>`oVDss`j9F6<5#y-L<e3j85^jAeu&N=Tf^B6MVDLv zc-lHY{B39D-DRcSF?cBt&0ld3C7=8obXEFA&~cDrlU$|Un$BFYLFYX0oV{n&>)Np* z%$QM2k|kwY%}%k*<WM`wdEJA_fX>Y&08nNCi+i+{ro1Hr&^W<)LjdHMu%5267eDyD zZ|X36&myyC5738ixTk}@G?TNMean{9^TB*bb9e6oW8ST6;qw++zpB7JPli24iw$-= zOie!KFb;GC{-~%aUrnZ2P?Q$yN`=iWbb^94>{q0L7O)zb-2Tqcu>e`|?l5)W5CNh< zX{TDrvA}29q*U&u;q|ttru4pCKyI`?gv`vI0@}x>rVZTZ>5~CD9Blu(692jq|9U3= zwL1Ri_lfSN^9z68GIxRAu~4exoF*NxsJ;NV*MxSM>{$pMn7HqyMGYs{92l%;=(?{T z{08BNH5hO-+X!>G7>x;&zG+`9Nm(4#+hOK9BhHU|=6AFtC#%TwS#ldw4CqJ0za=De zP_)DKR7j$&T=R&8s|OBrp@}qyQRq;TL5))xpP@|0S+0Z9=rpfQX!f~YW{|Tjjf?77 zG`oVC%sR3CeN@l<1ES+|%k(2;xfAu8a>dzuSh&zFTBK^lZ%~RgTNrybTgYH5lb+~@ zRlC&a1<@zI=4|D%=Jsoi8EXnaO{H-XaR@(3GzlNYo4D8=<vrKirtlI3a)ZrR0Se}y zg&z3smt5gnn@YZGY)Z;ME0HE8jzg1mPof`?;sYu~Y%X(_jGUOdBH);R_c}{<B)}M1 zhG(5<t{va4JT{v#tpe0Zc36we&S66Rbv@_r9h_=NGW+hAGRRy!XQL|9tL%TB(Ziqy z+zw}36>6xAY*PQQvTyCDZFf%v=rEF@57d529!wY02%wJfWUzX_dBIU4#BdwLt|Yf- zPW5Z$r$H(9T_~SQC^fm$&q=bb)+?hiNY_lsTWJAfTb>kJGpF&yaLh!yici#RJIt8K zFl|rcp*nl*an(>=;d{`SEj>7lp799T?76lf9*k4-Y1~KyH=d6Rcy}8A2^5k{_G+_Z zNejsH5X29qxP=~m80&H-iW$!cYl>#q&JGryZ#PSSP<<uejN9ENUGx4;hHU=>J(_GY z<#m=$hT%BNdq^ouqkgU3=6mtCkb<KL{Y`1t%(~ts7e@&4*2?T(MrHON1>HS-7Xl#X z9gpQY)d|9))<FX(Ggt4M<gb^4^qg+*KeiDL>Eq~(u}4xmtJQU@T!uQsY{wuBNQqG; zJ%b`VRd&)GsM>w_y+gzeJ-*|B6;F6{iqFpMWW|8aIm`>b(|)H+hlHyc27#=a8ov}W zI@{_nYqNHk9dPVV;@vj$h$$0I*JQ@LCz@h!+xni)QWbWt;T5=Z<l;g&NUL`1<(xdw z%9Za075%Yw3{gVrz{hyfMT5Dp`}i{-o44=3TucrrcvAEH<luYL<7#H?k87FKz*A5H z%QIJ^I$(ZZsSAu~&Vzb9=5XHfbyVvg+IYK!4(~QkiJu#p?F$OBdUgT?xh@0}&aO#Y z#|pJ&`gcK;#*tR(n>9qQU|mc1a0Ti@T}`zavufq^R^>yn*1(0DYqupkqoVkhK>Jtz zZ7KbCCr}_8z4d<fPdU5R!`ED5zd@^aO0F{yhJWgWMuqNK(tx3<=ufj?$(O&>jQs`3 z_uv1Kzpt}|fkc-k&v&DkNI>bgC}H>|Qh>;z-6<|KqG9GTNxR{nKkTjla;{iitxU&v zv^TyX24sGQMQh=k2&mX62N_HOqG}`UT2V+I05HV<QhBa-!KY&8a{;w`|8_%mNWzD& z`DX0tj@7{jKb~|u32-t1b))FN?*;pJ!QVfya1Wvu+Gj-N`wenq-1!Z<;TRaEZ}qny znf}`k`v3Vf_W%Cre~d#}3c`neEt?2F1Wr7Fz+k1P^C<{<?>8tZmMll_Kk}WP+wcSA z^BaWLj+Xh-N^r8JMs{IZ_^AG+)OUF5m{ZNfz0s`Ei3bneYEfInr%6M?a91>H(d44P zqTBeDJ7EwwHJc*aKYXPXX5gU`RhMp*fHg&PJr9iav`!Dk#r()dI}|Iwcky<4ytr~h z@s{*}q7WkzT&pnzaztd1?5XM`Ou{{idyFKmwpD0(4m=?Vk!cC<!QxL&rr*ZbrY?uy zt3F+Q&ElKiwmDol=`s_$DeFi2Aeeh|6RAY<60;;mMxrw%N%o)6WG_2J>valyxY5U* z%?CLXTjk~RH+kMHy|q(UMX^VXx=8RdEkPM+HWZEDAcl4HRwVRflNT|8dzh@-)qv>~ zR?)Kv2$GHdaKWX#aOOw=*Iw{&#*(51ICZo;;-*+@fT37gmxW4~6CawIAsb6cCcg6~ z%!da;JH-q_QD~v<AGq|xd!Npyz4>Y<>z(k+VCZX<vlb}Wt9;FgIT`yaqR5NX9ouQ4 zG}8y;irG|4Z{1V%N85)QFu4^fAE}rv{tW`3VxsSbL7b?T6p=MTWF&+EDBBR~8s_WD zM|>wlphJtH)Ywj3S|XGhZVEINXzncDmEE3%OG<;1+`bmfU2HqaksTc(3G~s-N6~yj zDUn1CkxIaf?ViAnsI>4h5SX{`#$zjTwgPmd!yI&jGX-8WrnKu$L|x1Q7X;oJPXp50 z*86kKV-bsY^TKQ|oZDLpQ~Dp;g#N&r;Gd1PKf38n00@Ukf_9gh#>09kdE=wK-wS{o zI`#Islhh!B61c5}YpkXH{j%Wo6})d{rgV!%(-i?eWZcl#>nyg>`h7VZC4civ`mf%* z{_yVjPqf(u{`b?m{=ftJx9R8scQdrcl9pT!rT4SO{|5b#pEwQr3yBERgva0SjslMW z*I%njVT!4y&i}ct5io#y8xZ&ZS6?HW`9`2v$e;Tf<Bk^8^9BHYjoilvxwiNYuoAqj z-Igy@TO&!y#LDL*O*T^A4E7FaR%<TF?ufDPfY*k7Tart~&vu;CG=~jXBMIc4ZjqrX z!u(O{4z^*goG^u|t8y;#eC03$8)LnvI%6U#_!48hhHg7=EOsI1xDO3VweTW-r7`(X zE_IxxW_e{-_2l-IPb(b^SKZtGQwUT`pOkl_4|<%$d>yFu-e4k&UY$_UurMdV!@M=c z&N*l<=F_FC<Aa<ZZYICw$!8DOjyFO+tIcBy=K#TaD6WKics<CeNFwwl^vb6(Wz7$j zX|{D*+px*?XnlNND=b_qNi*QW*Mf20WBi~gh%)kYto0<#K&5l*SYa`t^gNowvkzVB z{J7Ar<HMMxkNDx2?pSHJ%(gq6`H6{!yvw2d^b^!9?3D?mCPlR47do}sUnU~O$F+2N zSm1EbW0Tyr{LNgl?1`$8bXc5en+C&>J$=OB7`#Pg1<G7IFSvr6lbn2-dC3Z`h1nbJ z=otEDKkU6d&@{tpJDkeBVCV4dIp1;6Xf<_<Y;cp@JdE1iipyeYAa7*iZ5&p~h?vjo z@9TpbPX%;l+4cGta=**O#v6fr@z=@=ynfwDZG7+PmmZ(-VY<iPJpd>kJ2J?7+G8=^ zNXAjv^lsa$3(VJ9vlH;@3$qa!vPC4IkWGLb=M@_2EV33NJ4#X)HBXRqH;!K%W9Vlr zcoF4jbs07E1vD$S7Y|600RE=yH;C&LCH^hPR=5Tmn9noKXH~G&Lu2~Q9JE&9Xa3K} z##Ma^)g_V+({)5?pgBMpqD$kdB{wIaHtA{z5iU;8q=6&xm2y#Kll`kU`vKAXOO{Gg zevpOyM0CxUFGn)LB)JYS52B!n3jmjaoUn14afF+<_^<;y{pVH1fwO0&-m-L?SdkXx z+ej>7=ZASh##)(np~tBMT_Pr(a4u>w?O1`Mm&i~GVb*Er;CckxBV?Pit??wY_s@P* zC!-N4&^WQUy&R@x*HvQrYJR%MN2C{Yx8i-61h4w@p<_9mfosJ1jtnsODxC)iw<hQY z`(yW1idTQ#YnZ5ZT-oyy>BR|gr_Hlp)HYFGIBk9F9k&OAuVpNlO~ahS^7bTP6u0Ua zFv8R{j&D9hXL#gRhi}B-&DCq3iCk@qnu_8y7Sdp_bfA*R)>sj%v<hl6i8cIbrnWu$ z!0&B9Fp<qvqNT2%OXrxlVRg@2DL_TEQ)zIv_lV^&8iMLgg2w<kC<cVjf%q*bc`~tZ z&7V=XG(hT8ZL^ifoLG|3qnK0fGK?=9FcOIpIb0Y2bCAFvet-OpzwpoX`U%+DU|Rrl zBX3cKfu2BdfpOBuNj)>d!G>0UfMb%X#>eWCMiu$EYm*Wu6~sl~gD7x*gf0<Cw@o-k zyEE46HbGuPfV=D-zJE+8oY+k+p@!p?)K<S-2^S9Oj_BLEQ{IX3baYNr;NejWJBe#g zm}(?;zoht>QMy|R9!Sk1=nCX~y{Y$>cOd}BIgzZ#H@?@_Uf@HGDqgg@AE_Fl{GNRK z6$^c6o+(6;ra)nRsj0$8Rp?2pMxG^m1LCLm_a>WE1>(yFNAAyCM&Rc}GotPz1PRca zpPrGP6zn&ejx!9ehOkgDot1!TvQ`LS?O18r>RR;_^$r{_wb#kcNIWi8Fwkx6t-v=S z*~^A=Cds0-otC9IMpIVb0{SrId`u|Nj6&rd{9H)?l68tQ`wgX;r{=LQcSmdg#nynJ zx=rSDwF*9Yy_#C4kr0rFlP>iVnRJU>hbE8N)Tb-?bTurdv}F1h@q5Tlx@V0}>hGT} zmZmry;})y>V9ncPfwp1#++=>BkGwj*3yr^JkGM8U{N4%XdkQ_VqVshBl{_NZVl2(# z?fNX1M<{&~!;|wShx2yTWtUxsx7r;laaf{4$QP^7qbeIIbh&9n2Avz>YjM`|V!Ot% z+WLyYY!5*wEXkJFD$e0#Zd?m32K24W!0k6Ep<yoN1579KMK%^Eu0gKstmGPg(ts|j z^1z7H)eFAAhf_M)ICm@;m8-;fb=3imGWKA!xU~0x+C$>~bvPWR2RkyDUi56E;z5&T zIl}Ssp2+<|WmyGS-Zb{1Yh84K>9-`Sl#wZBdBzyfugaT;MF5zXDSD$}R5KJH(lb$q zal_ueUOvQG$eEnCCrZ1QM<cAPm<1%bFR+9`0;!h{&Ld4m>C!N|GzI#s9DL;k@=DVA zZ;e1GtWHZQTq*Xn?Fss?uNJ*v`BdxaMrNki5#U&!kWfX_N$c>Z3$$Pw;0#D@KdoO` zZzVqm5vJ?EHI7UV2e_y=n2TN<#VRH9Gg{y10{POp53?Zh<`nDpR-Qkpvdg_&3HN3Y z8Fo;9FCBi|jqV2iy2ta)b!qHcAN!<pk0`J?=mx-~sx!ZWCdsYqKPe^`BMFC#3C|VR z(cWO<a2)N%hZ)gN+&#`8j7m+sA4_$~?(-_{UFm<(E94=sC6*&@7JP6%WQ;EN<>B24 z$Vsnbk3u*Rk?qB9kF{T}D?a!UR8aR6J(e@(_u!)nUzz1&sa^w=voH(<sRt-}7<g*9 z!%jAv0Obq$0%hO*>}rgTwH#NhKthY8Cvj(U%v^lc-E19gP1i5va50dF*D(~vwH_^T z0ZM8%Neqg$%2K1SMnj#R+ma9KP>;Qe%SY~8q$ZSEYJ8j%h2^9hMQym$Xu!C0*jw4? z1EJ#U5D}_+4@9nd+=cq=J)#<58Lh{V`o8&nA1s`m^TkBE&8^0n8+@+D6z)oY#Vg-G zzM}RahqIEPy^bSgvUNkaaK66*Vac&V{Id9YuPDvW_U5hG7A+H7-qP_Bb&uNNJSl=3 zcwGekG;NA+@Ge$D47?shP9_$0I6hGbjS}asM~5E};NiQGE#?Pi#M<$vKtwRH+b~&} zjkv01f)eK~!49%BzTzrg{<}_~|9joMUg;hTKr(y*;qsIrWu9JOR{#<}`5Tm6L<LZE z0OV+I{q>k42KqZs{QqciPJi3c<6lVN|Nf!-Z<sv(xuz4~kjsjy+^|>xjFFmb8%$kR zS?phut#<St{d!{le#X3zX>U2tBR?q2)nk*#alv#k2xcYsYMqne^D4BiC%l$Jc@@r2 zQ>Mn|dOgyL`hxJ=?(SUGPW@FzH_GgUI8Gb(-scc4er+0;uf`M?28V`BIB}2CE*vJ5 zQ02Pz>I8-GPAH<1Z%$3HIySxeSNE5d{px}NKKZvBsb)%(9#URWn0^KsLoLV|WXLF8 z7=gpia}uaQB(M-7vJ>z3zQhJ-Cz%`Ri|Fn8z7>=ab%Rs(X8pMb0<1xjXIXj5LuSAn z4>Yzmnwi=m=P1|GxWYG@LPLY{ZS4XALr{tsRlQ`k*f;OY`0(aSmt%%hQ#jCjAU^{x zKwjafO;oNa1%zu<I7#2YYE)`u%W2?de21|6n5JCf-Er5h5f%X(w*a1)q4&ps8ef!> z;F_cxd|d|f>Zy}U_6e=A(~cB9A>6VQedw$WY%dMQj7d|=_*lwgEA@tXUM{tbpO?NG zc5a>uOj!|Um$m8g<47@5uz``d$as@u4s_PWUJ=LyDl^?F{K&ZQ(Hq4dE7m{Vd-Jw; zjys5l_V}<&IYfNT&i@7S*n+pE6VBe690<KknLZkySq>d|z^o)QZm<Gg-j>|d6*O~J zdc7kI(qTR<(*w;(daE{_Cr0fG|4=fyJf>%}S4T3LK%1Y)56V#R-)Y)dX!jLqN|aVJ z^Oo<C%4`hN<qFRhENeN3)Ef(tg^6<my>`2qo*MOIt~lNaxrRE#$SC3FTtIl<LwTkY zWS&^#6?M=f0|3PL^kyS~JGpofL7h5&=u*4obnD2?WXqA=%SoGNKP-&xPuE@DziaVU zz(?rSOG9bBg7DKU9P=BYp}`cPwk*yOh_F|n3s5dUp(jUTv+!9z=AWlo@yhqeYd!yN z-U#h@AIaJh=a@%%pSLH%G?ZJtw<7HPBYag(0CG%9%iBD;NFYQc$8pZa-niw=sCTB7 zIZ8idq*2!`{DxR>_$~fkg4U)yXqcA{Ots|1acg_35(Ogl9`SI#3%@j=uL|@JKP~>A zb^80eatbqXHec1G<$4_F=RWyvh6x02FJ(lJjfkiXzD2;q7k$aL9o=Q6xS>;uM@F{~ zNv4c7Gm1MB0j?&gC+=N-a=mWR4J6d+4v%q?pw=%O`gDp**NkmcXbcN^;h7a&MRPTq zQM5XWI;)>dzcCxPq4c{WC$Ap?YD{X=UW5p)rTr4ay==Ne<Rcu)0P<9RM-Qt<!(5RX zJ&B3Rm3n=ye(aK;9*Z@6)=u+Qc++;=hDmU1uaBx<I5>|lvZ<+x0H7KzOyKi&{?y63 zU-}J}j{}#^3#ZRrPM_~nEp0@1Ub;KmcEsY=ULV;%QbWx81|<{=jtT)!))tO9P+blz zYjn^PEu~MC%9SPJ6^ddms|%uVgS_3ncSdY-?y_SJ%YS&g65WB>pAq3jxZmm~TS;=X zY^N)JoD}SY!qfm$Dcr<3=0~pE8R=Va<2M?r*Glx}l}<9P==@*9IseH>OfaNqy}V?Y zFwaW^dTiv)fk=xCD3qIrFs;vf`Uiio9ZdF>`G@}Pt@vXV%F-{SdCtk6l1?g7>t%>6 z3+ZW5rVJGc#fF?ldY~t1x4aA@Trn-x<i2W5Jl3>TsF07LAtl`_8Y3*L<}K`474}I& zkOn;vq{2I)f<IKECe;zLx{jgN)i_PJNj(PVvNg8HjM7g`+&;_-3Vylr3j5ka=3b~~ zE@B`oOb{YVJ54<xS|*4yQih)+q3>ZF?-Bl;dqRQV(!ux#V;lZf_eZDJ?S9Dmb9dVG zSFfv0Z_1YtML?C#2ZGDLz%P*I3(ZVD)@P@@0Lf}HA<)d{u579x*9YBI<;yYB?!gz{ zbTYCRaqS>;#J)#{uz5lFsHjj1-z7>)V#W>fRwPnevwZH=_G!$R`=@|R+lSnd>AFRi zrFr;HKamx_0t&{pJ6>|2w&v38DAq9#;Lt1NB{ZS<>9nKFLd+8W*QT(m%V)ipcIR?T z=RYLIp4)!L6UL=t`89@QMIMJRQE*+w=vEGdPkUhNvnDY(OH3Wc&JDHe#hUGJ@NO*t z=;yqCxvZ*P_-JQO+BuNpr9DIH%gWWj$XGz|*J|2K0d8H#DcMXPuIG3*QP@+Ci{`y? zb@LC^t|kY(bi6$Bs<Zl|gK-X9Asq_=`UQOaD`7${$1tNZ<7j=SnO5xDG1|qNx#$yJ z$~6ia3GBBlq<X4Pe?KY}H`H!+my^`~n2Om8qy{X^KTijDSc=k&Fl0>RdhJ-H$8fIg z^w5{0R3DDbBK8mS5-uP&hA_^abD%|}`N6r>P!Sq5h~j14I6>p^EiHOU2xhYSSlduv zCn8(DPe`{n?{jv3bo2tc&+w91&uNf`FLdg{R*URsiu`=fBSnn3PN<-Tt53d0qW<e& zM@Kfx`~fXaV(zS@15?!3Fqc=HhI)HkR5?GC*TNPzLJ-?IcWmqd9H|~aK*?Pn3lUk_ zYiKZ1J)Wk6+U{GL8%hMleaX1<I?ey4DTrx|7JQ)cC&33`)K#Y|BvgfVbWuay7N9>_ z0<!NO7)%?CwoWE!`%fE8(B5p*f6lhCvXS6lZ!|$k)0HCG;)tgfa?u)aUOZs2X1Cw) z9I5?*X6#V9u=y;e3sq{6%>VckEdHL`OgzIhRmg+_UtcL&%{u;tE<l|pYPOVDdd|t} z)WtnD*G1T0Z%$vpziu8UrljjH2pvN|W4^m+$f`;K@7X^m?MEea#U`+OgmB_Co0auN z?~Ks`7Rsf%v{MtOEl!0KyHZ!}z0CT}PrKL=<!&8AJU8|SJpjBbRH>U3>&^{p2a3S@ zFS$-A=Ox0|r?KW9<tz!pwRM=rbQXthCuirPx)I*oj0fJ2IzgPFmk)u~z0nHqiYB84 z4z)dRODv{O4(M|Iy3GAE(2=GcFU@mhXFz)6sFmzv-bXjNFZ8n7Yp*v4b9Hr68}pHd z;cVY1zd<zF(0lbGaU-HI4Vh78#cIq^WOtk}Z%9-pON`-p(Eh*cuKq^Q?vMZaKiU5L z@9K*GIo{}xoc;cP(gpB82vGdfmBBL*TjZ$86n{r5wgRa|y#yniCXuyL^1Vdx)CkeY zVCcccfDSwS0DFvLKi2Kh)oqh=DvFo-kH-x%d=~<8*Sfr{KT;!pnY@^8gPfw&p%)z0 z%>{7=(^|<>O5Ywk4I5-GnpRsD4cX0H5H&ejm95%eC2?{y<-iW<JKn@W#ty||d8m0r zl?Y|M2m68WFyD=b1C}36H<CnEs$e?<Pqv%(Po-~Uoy#+tTI^jk2L<|fTH^VL#<2&g z2p^L8%Y_qq2E@a_eZ!9#h7<=O?gCWT22HT0=k(!B34i~hW7s)-mrgn8Nnq=SFyM5f z+@<PfGg=U-5UG_Gu1`2)6r#cPE7ZeZ=e0kZ`yN()#`x|XkIz^IcIUK^Zy>-$R_oqV zZr;_x^N})&II;^E7qX<OK2OuHr38s}%&jCs)u7$mvuaWKL6U_t@7->mxYD_Co$D%f zjRY`C0P5Ys3q|0u*U4EmWtX%go|YmCN0q(H)3>*7V``c*DNolWee11a&YEAoz8SE8 z7Zh*@NRm7-LYQ|sNsS@z7+_H*SIOE5G<5=p?d~DxFMRN|X&b?-HEq((iQe~K!jt8f zoKJBKB1FTKsG@sS)I5@9)MV&XHs_Wvm}PZNb^@s_mXiHk6J0+hSqsaFz42)>GqgrF z-aPicb%cJQz^8=$B#oPAKxyp;wJJTIZ({AMCN-1*=u(xceBkhre3i~Ssi%)jZ(@-- zIH|3j)aO-dU)q0T(>v=dSJK6lH~gTd;94ANT~8Qjo`!bM9oJcU_4M*sC|ZvdXhn8t z+sAS7@GavwlosdwTxRgSbI3*_<ocyEpe{0W!<LfJ-S8U9Ub~D`<%+GOpPE5j8+I6P zk}8Fz-dG`f<lRXnddsd7lt&i|U2M|b*zH3$N_RjvBKO*<pFgbp5_&-zq3OGlL>q?% zGtIA$(N0w$(yqmDfNyz2PP^2M*;bgP8%L)v&bk;Gcc`02fgt%n_=rA~2*yG=sMDR| z0O5D0Wc3%)S_rRP!2D{)#=(`hb3n3m;hc9tin;RW9iK3Uou&E%bEMlC@)AXGdk%aA z7&vr+tiY&WtYKy4VDkpAsYyENs_jGRcaF?QB%9yJmpN1!OEW7-Fw^A^^XO`yDHp#J zBEyxMgxz&%*Av?IaQKQ$-zTrWW^*ck*lBv~(0%WXSrbTD;Pnw*Gc^!rr1@d49zYbZ zCzC8uI!h^qX2fCEE~5Q;m$95gMfa95<xOwb6kd}6L7}r!AC(7lJ`UWy$CNm>dvC1e zJYtwo*$Iz<=roawUO<GFMLy3G5tut4U(Xaw=02>9OT`?MuYIQ0Uydq?Vi`9Co!zaB zfQtFfKZCH1H?fA294~J5%&B20PUX%eNQ((2Q6uu$^sr1_#$o=bMZk&aJ<T_5Vt8BG zb4U3lSWrDtKruk<LL3l06OFt;fn(FtBH@~qNAOf_BWh&vjHmycta*`f4F0FIP~hvv zFF%#kYk4{bw~a)MyMTm9CQm3I9ZV%H0FFu-YW&XaNuVgacz2lJ{_SAu$Xa@Ea}#k> zrq6UhVXjBP1nuU^<ShzF65jvc0-XQpW{-gVPOh-*<B-#9b#s!Go|p62n=s*`W*0rR zt-e5~jSRBI(Cgg&iBQAp&Bq-5eJ51p26kE&j=S%9QRR_d{uB^ZGrlrFe|GxJ_!h@& zYwPP#4<Ej--U<19q_Zkq96_{os7ZBkj0QR0+JjT|OA*>wai%n^bzQpkb0yB~_gWbh z(63Imc5g1LJjA5#6bxLZyyOx!wX%qEP&JZZ;v<Hx*DpZ9NVVq#<G5{v|AiQmA=vrl z_tejdwT%;>>^;1j7EMKrxI`_)ak`1``WACQVw}+cV<$zX>A2V+?k}0uX7hx0*dAHj ztPBbWkS*VXDYZ=K5ANvU6eUtfjPAZ{wID5Jkdi%(ooWx$fB6{`VR!y%E3YR+xz@L} zo<-@;YX1v6=arQ=A(yt5|D^W!Twv_3B8RqRNq(`%il0JH{BTsw8<jc>_4@U)P0r$z zeXN<bnJQf4YSgxQwwg<fEQ~P>bZUamN>j!?L{HCJW5tnkGLyBfJf5h%>6Zt_?~B+T zyK4y=@(l<API-BPsQaL(&poE|t@pRWM-g}^czB`eaq)ov;}5}E!53E@HC2=)w=0yB zuLW4V^XY$y>Hiq{WOJ}9Ga6J-5HbK}dWr(_1C5}^pq?Tj?{f_zIVvhES9RW5HQ2va zf_Y+Y^(qo&Phb10SM|n4>d?*`^d@9*p4HxX;;GkXQHODZx(bbh((+3;DA!5@ilZO6 zsoQGUy}&8?vnDI(c1xx(s`nbR)ft{wIT6weVaw>)SvFuB#bPo3^i$6}_wn(STA1RQ zmOOrrqfIeHj+>LKqII_-Gu>w;)L1zk&>$PK_PReAgM}Vz(j;R$nXXT`k-y<z&IER6 zewbUvh4|8v%n*s@utzmL-lx)F7<HYLc%>y{!(E0imDu?>h(H7v2U3cH!CJCWb<Hs` zV<sb;O-F(Pl-?bUc`xGe10nE}`P=0tZcbB0L&dHG1Ej%(-YFuK12J~7dVGm8yVz1m ztS@ug?pHXxCLN|Ue)OSq>X%~lvsWg{*SqsoA5?+h5ux`hpNsR1EBSi!b>Lj+!%#TC z%<LRGG(dN?{N97~RWrG_cxx}ed%6RRW>45?;|z^(@^ivQb{N2@MQ8<rVzD8zI>i%; zpF69M4drh4A^dgKxm3TuUO1_|ei{S<oqd`eCeE8}G@eThx9<X6f>AU%aCnnR+1rQS z_a=5fb@rD~$_Axgm3J28$Cak~-M#I72egcIBWJg@=#K89<e+14R}01)S}8sStC&Ey z#n`BHUgw6|)bk`3xu<t4m=w6D_!+tf4rGwWdjJrxhNfS!yhRX$kgkp_j_5g-!JbUp z536K8PD3}K@&gKfE+$<Zmb%-h${41-Yx%+oZ<4S(d}5f$wsCP78_3=Oj$DV|&)N*K ztQ|O+mc}>dFrbLL*|!&T+v*8B?BsK$r8<!Q@&8+T>whtPf8;>-R~je&Nf|zMO&|mU zj;*n=r#|o0dH7bj^pUqB@Al?7yUj<aQ!x_XguyPl-VT``dU7-vtg%^oWbHUHS8`h1 zu9A0FKZlw1wQ=Wq*-;mw=(EMD!?8EtfIxcR7_DRNIUM-^gg74;X=#2>4G|4O1=&`O zximJ0cg$`n^9rOdx$|I;3rT3iua|r{e_sM*mcz1~B?(wLqRa0$ljj<XR_pfm)om>_ zQKQ!qXD%+vM87@8Ffv|xX3<n4%n0Q1k0PQLzjFY{-}`hX0CQRnV(T~PX8Fx9F3!-t zG1?`7-UwZoKRic`|GS$k{$RQhS4ODUlyqdb0mJ<WV>7;<9kuFr0#cX9A6)>v*(C6B z>h@%?JoE&WM6!;1ntjp{$w2<G6)A(J$V@aZ)mO%%E>BB&2jgEXT`Z{^JtlZTF+b0H zCP%y#jB0XoBdDP0y3_;`muOEZo;rMyoY4*gPBc;$Ba#i6Q77b;ZqujJp%5$H*)j{o z#~Qg>#!q$`259Fftue?@(l8hDTn4cyhBZ=|es&H<cZ#M6S&!?@o~F8Qn6cY6Wf?7g z&bt;*>3zf{tK}Shl(mV*MA44f0HPCtSJ!?|;P;FL1n{2AQ!Xv}q%*tg1NCHCv@)Lc zd7@Fdj#1XudG^RmAvK5V9g>aW+BrsJt;V&8A)inxWArW=v~3Bl&U4>uqG&Fzr^~)S z5>uD)NZ^~n)IJ3Mrb;8k-dsf3)bDT+<OdbdXca(Q!x0~(t8`n&JjO(Z99s$HetJmP z8ix+xoV<P{Ei5^?I(xjWGg$r&(seoY;w!@*+bW%*zAlK`<b#2x{t7tqIz;{x$x6p~ zzt$eES2+NSeddL60g@+s4S$20bGV+-geh`8cJpnSvik(q?rd=?whIRsPmm_X2#Oov zZ9nWz{%{G-2OG`Rc=2^sSZ_Rb;OkCz#6@ZN95-h&$+`<@vx8FJ{mR621(n|->_@^w z_*Sj`_x>OD-aD+Rc3t-kMS7QxfOHi?6e&tqq=`sT=|!az0ShHar1vTypdbW9I-w__ zgkA-a4iahtqVxn6qJ(^Ve0!~P=ALJ-wf8D>owe6F^DprNu7o%57~^@L=e~b80KrC0 zHGR!=XiYr)QWksH=(SW~hGW<J^Z{dA3CnOEvV6a=Q<{(0d&*sMP7Pd#Y#Y1=Nx%p^ zpUtXq@aza|<F5=I^a1a-NymqNI-2ZfIB7KIJTH+9qKR3nP3q*D!ZTp1DKE(FYi2aC zo~hN@{7M*)2)p}Nqa52AY3M}26KSpnaQ`e%ziY~L9_))!Ws$5t=;YkEYlaW!?OZRR z^$;W5H>oB(QPKq);gJ6muJ!*~67s+5(tXvGCm!`Uu}-Rpa`xgL)h&1*<*3ITn+0oq zoU>MARQe!2rD3#AECwG`R@U&{1+i+g0Yge{F|vsv5fb%dzs~ddsaNWO`OvS%kW4?G zm>Vlnm(jd5*$>U0deV7^OSNEP_m=JLqgQx`CR*2%D+Hya3SQA<u8uLnp5gbSo5E<x zI=7D*;tFl`%DjNguZ&-+q;q#o4=!sw&=qo*K9UVrEeG&ljDst59b$0vY5wCuE?AUB zw6ujuDI`(F<i&+AQZKd|H%SiQk}mL3biFE(7QG7m#_#{ELh7$oD}OJm{$5u7WhWkl zk-Z@FYRRZhdc`*t;KK;QIf_bv^pPt62bq!0Vsm!d(We;>kXP5UQiL7nwVnDsK%8Ws zV{?ERTD%IQ1Q$_^iMu7$cACN)6&_*Lwu3?MaKjPPD+3L6=(88;8+{mC3fb*fa006w zdC-y-Z=Ext;<yE9fybf46fE}x&+*8sUb&Y8w@lFYJQsW)Z{0n(q${jLZ0_I&x@`}D zq4swwKO$NOf8l!y=1~0bOYLFYTi&Z_JI9pKZd-fb#nq_sar)fOeniZ3hA1c@KLjvj zC@=9J+7_axfgn3iACGJai7Xzg$H_wyuj=TtxI3OSt<shvNrS63QAL{ta&y`jVRE-8 zP~~FVx2An5*Sy$KJLiVdq?MW?r1iZXS`HgA++fTW0x5*5Q3R$xRV%L~Ewi*yyBdVE zk?YXIj4%%zANyefN#X$b!-F~m=+AuD75E8sfOXig=S#Qj#uBw9$u2<7HgtMQMWpS- z2?!q27)X(}56)jyc>4NFO3x)Ws~cB(X>~6+!9B=YWiXGMq?#gtX9zH==iK>mPDs@@ z?`-SuqP-)LPmg8%(y8~PDadc0baAaGwH-{Gh*Qe7slrDI5);)bD;y5wJmU!!`4N*B zE*6-`)AGH3zTZ#fJ_5B_3@r|`vTcNGg&9q+&Nk(NAI?Y{`R%T?q_~1ETAO!qF$;Bn zr5|T}OVK_l(dMEUwPGil%=kCa9cLTbXCCGC&f!wt+f^m8Rv<Ut_(YdrGiDEzTDle@ z7lJ{w{b{?WiEd3{Y+Hb9ARVv6wg_vTlpmEho57ltvL?MsN7ud@Uo__Z<@ZWi$tapA zIDRUOGZX+qFa)fTn7@(+(%vn=FV4lqVfBNmyaThx?H%}%Tw^}Imo`;Mp>4`z&J}ve zR(4kScjsVQ#Io4b52Uj*d34oLb<hEQ?4A=4Opw3wumiJLwb8YImZ#L#;ES|dIKbaj z&)4X45}$=A`tQm)PSyHW6C$Cn!8IAi5~*%r*47G{dowl_niuJl3E|3~fAtjl*Uu;a zeZT%+PA2%@&zm%)AVB2rfk+lk8+>tRATX5cyK+cc;w)7r@$2#RY2PX9p0S5hkCglb zTO_rj{ixu!P;bz8k+k`c!w8b$&M$meoCTamn`aizNW?f2KXz>kyKr)CsmG;%>1OSH z++`qqUM{PK4f^%s=QD2K=zy3mv&haS#uTKcBOVgB%-WuFYnCeZ*;Cl%TPHE?8ru<f zUZzTE#glS*K%KQAi%A{2;Fo>fN!C3}_dF2L^am)3DnPkJ?k6!iPAL2^<5`AZABW#r zb!%&lG!D>MuzztCyvuui&tz1=+UlM5`<q{265r@N077r<Pw_ZAkDU*z_MEYExfCiR zX{IEZ{~`3;f_;b&?UpgbC+TCk_{Z;}q2uF?rmXxu4MSbR&dfR<o$+>N062}7ywJbQ z)YLfHF-H=xX`d3?nDr-;XKSjRCP%#MtpgL~(=K)iUmQKRs7KqyMPVSHCmG^-o#}s8 z?Z;x+)gnWAx9vDOT}qcX3HYDqYpUfECroaq4}BV6y!e!A{wO;@ff@9p7AJn2Tu%ri z>wh5%M4~$7dOI$7`PKM;YZCgI))?k7@@2(K?{0eanl}91((>cXN})r=S+-R%abn(T z&>$pcwt$i3m!Qo)vL>IxZzk$x7Z<04`hifFsP<+J@+%+sQq%-nl2rW;k_xar$X=%n zr$aQJ<Y{ZdG*$r;Zq&+ZpVOsjiVM+l3QtVbaxpU_8=y2i+N6!9K2{zchzxC5N0dAP zm$Py4JqP_x(>r+|=ApyvnHv@ZH$A=vzdWUqB0LM$F>h!gg}%+Scj$?bNthnZatm;i zs%Hq-eAcT2M05LSm=RBXsf_{`VW&DSkf-{E^ZNt>^MF>EieP}}ZVhWS0{h*P@Zn~p z!+G;zTG7HY)^Qy)@E63e9xcWmJ4nwy7Y<hX6~CN`&o@S*%s9NA#HZacY8pN&BU(2u zBeCEovd(5o%JmLLN<!S8bgk1J9B}7JZHevBOJ;FuVp0v`9hdY-47h=1>VWjzjxSt` zKX=qqw%J*6>r;Ri+Tpc%%*kTL85Yn-^0nhD<b~c9KIMHeiYd_`znE<B-GDSRTU_>` z+zu-yX6kGi39Du{9MEt5<O+*OFfpQAjnvhWKjbGaSCqj8#V*YG`JhC`xR>57cwag{ zx{*`1pjT>R)*No@9@(#GpzW}9nax+3=EuE%Z*<eYOQY~#v#0Pk7kd8<pBR5H(f?ke z|D{9+z$Z9H0z)IQY&9%R8vvW*hS%;AJLC3I&?~hT%S#)v`~kLnLN*m=T^Sff`$55_ zQO9=n+7%U^0v+NrSi>3wmkkhLG126xI{V4iw{*h#PP!H0yE)fz6;~Ff1*6F{BYj`A zOQa%nHb+>zUsUQQ$s;_VmIWq3p{pUcId;q`5MqxEb8dGeF7?G@6r9eoaxNOG<@)-F z@xwkQ7Yd-v7UTeu!FYh9A>i9(3zZ;;*-hr@Kf@>OYJc3Cv-*u3LN_%>Ua2WK&-Kwx z&H18<h=jHN!-#NYpH6R}#CO;XIu|NVo`mps6KctNE<}61hq(TM_h8H2Xg9@_vJ7;p zuW(?qO^+0Oq3%6Rf%pj$D?k|Ihv{{T2@!RYQS8uui!IJ4p6J;p+TpVTUS9Cr`0Oum zOr)*$1M$3e1DEe=hFF@`x!r)(7mNeT(!{JkK#`iBKqr(D+8Pvx`UAuRE~!1do&OB} z64f}gv3hHwzB$D<L-t@yiQdZKKHG8a!sT9WD71JU2wu)CH+(`^GV4MM9L_!uo3H&j za$e3D{15`GDT--IZ5XQH7!OkCdmJu07on}%O_J-jfUWh2b6{<Qe2LzXCg(X2rxpbc z)eL4$)%7#JU0763Q0#5)jep%#(V`aaO3pmY2iV+juV8!#f|H!RZ8U*`veP+`98pb- z`V%OVr2_quk10x{ZAY_h%U9kfLQvPG{bkVM&n7}~E37h;yDH*jL#%^L$M|Wx$RV5S zM-a`{H&bB$u7pdYyZyKiXFNG`jw^)L^({SV`;T@*@Fu6~C)ddyYxA16!Gz4!O}LCG zDq;q#a@zf<UdXuZSS?+qHG$thzql9vvCZ`}kO+5lSlK}?B31Qhvk(Wv0f-43EUG8> zuqsTuNVF*k=TuU<mHv5zehXt7W5^>FZOp2C_%Yf1))37?=%hF=_8Kt+-zRpFI9h;s z77U4R(mGr6!<Q7S)|$*eiRK>PW4_FHwd+1Jy(<@6l@6{Je=?Wykl5EMkN{RTX%9T= zIn=(`g29;be79~igeKj{{PcagA{I}daXnpQVVHY?#fApKrsJK)0=FauM&Q93UD|3B zaY@P|wY;9f*|jb{d<A7Cz0&6tr7kUMCQ2XQc$6S1oyX*Hmn|<`tMV_`-QVzr{?{Ft zfBt{}<uqh}-lxxa41~ow6EUGYK#A>lI0zuWWS)bU(+~F>e*qTM?*!NHn(ZT<zB{{% zErS-iVj4O-5taJli7pKQhh9bAj}MwHjM_^lAn1ClHwCyCydKXh+*xSvG1?KMZ#I_7 zmP8)Zr9XSg@-nm+XX5!=E%qnmOhQu(5s?6wsI{}-G~mmfvcjg4401IO9kqN)V^tF6 z>|Y(+bCEhbq$~doQTM}2{oW{QE*?u$>NHwG4*VqzgzjpH(uzY&I%7$Fk>{O)UGs}r z`aVhCX1UIE74(_3zvJ-^_Vq4FExs1j4vdN{XRuJt<fereVZE2OTJN<f<}l=gkxOqg z&$Xox5*C%PkTbEWca9s*(3%0}!v?1!;#{AdKr#ig=FEnJ0yJoqb_t0_q81uaf?6(m z=BNHiVW%`&!Q#c6(<6$Lt*cGPu||wqz`b*j1Q}T2?f~l(I{B`>gR7HrQhvM$_)%YG z_$;8x{`352rPsbzt1r^U2mrp`HDW1)yKb=U323hga6zW+h<_F*+3!<($;c|o?LR;- zeKq$nVV+@ye}Is`0)a1Tz6NC-2?Pz(bP`wakmL?Nvh^L}U@SbK_!b5i7a#Dtw3A3_ z5UfRs>hG;8@a(GfGUeJVU%XeMA!y7oSxwUg>pTP#SK>RsTh83o?!;Sxt<+k#8t;*6 z7;UfZEI)zXXgzhrqE!K0m?aT+Zv5sD6?%h|Lj#h?I}|6P_G3fkJnb-volT{rl|+HQ zRu`vDi!ra_ct7b*bVJ6hq31q0dvAlZo`PnUYVjAdrRh4=Nk<(#T;`dffC)4;<m)}# zy~~iyekB~tzAHPIJ$fQvbz@k=h7fD;(&yO3$gRLWPVRzg1&yvG@o48Kfir@7VM_Y~ z%<2yS)Lrr^7k<w#tkA|Qu-?&|zje7*?A^O%`>Jme5xXTRbehQoo8Fik51BwXa#S%O znolQg!PV!q0nInQ9V=X#Rg>ajr!KSGhKivGNPaeSs~9=wrTMNXan~Zl_^dq09kP=y zG5hF8zV%?SCE4vAtoRP8;0>O;IDT)S=y_vfugo7H1Zp<}I?^JQ@*pMdb;Oq!Z|-ww z0FoOZkimcqu)IwM4|Ch&B0Gf0{QEuHqDl6RQ{eql+pkRr7~qt>B~YQ9aOxFQdgnfy z6}WDFXqx8d?%yka7SO+Pn)vt1|9`6$_`9q5Z@)?ChOt-w0Lht=IH@0iZb{gWHG(?m zf6F)Id_A};L9$!GuO#buF>7k!L-u2H*+OOQeHc_)q8w71-fW-~y%dj(>60`MGxt*N zo5{!~U_W3n95f%`*H6kiJ%P@66kLPEtHYt<Ea1(IH`!IYQ#S>9I3<Od3AkJ~#x^d3 zNiR6h+i<h-RQm0}^Oj5u*;U7PDfg#3q_zq~C$VR!(o<N0S}!MoIk0jV2s)>x-Z!a7 zx3a~L85er=NGfjEbJOGT!O#c#yizlz%erDIq%eZn09a?uIWm-kat_g*4JL$fZa?pz zEh2!mH@**r$f0azuM{?iH$QuPFLtL~V<3?CF{eP@;UM6z>15o}zDndzUwv_5tapV8 zOqKOk&Fzo*vYult|E#?KnHPP!Lm!W<qmI92L%onlI)=JKHwT2<+yP*D07DPJ4X-5a z!-d#45XCTPe_)WEZ(6_?R`%=m>CfCGKLncGs`CCII&`7s@h;=Y7cN$v)9_%@*bQ>d zs(5q<)2t>_Yg*EeeO~Vz;nJ!iCu4f%2jDkN319-&N_sH(%g~0|o1D>mx|$g@YaPNT z=+N_?%gmG7a+I4uyv0vZASseHz${)pFspE3alz4k@$-l$!Zn-V_1>1p!e8WhU#Swy zZjQ)ny5fm2&oP|pnl4E{8o^OEi}F!gFzjraLxu{IlWx|f3hiZ!-h}MzUd!0}4K+G) zkhC8b#tWXa$v3*sE)DRJNU=v<E|%kp%39usBmMjDpwDaaiQk<wUI_Q@Xv3c_!Mwhh z>U(%CpF_W|d3WBW`Ge(9p%7K#q#XXdgM}C~7)$Y5y^1=qTX1adPz_}!iJ@e=>zfZO zTqb=2mu(-^VPrQ+e&@L%1y8%^a$8R7s8@jJlNE*}hu=t9b-aq@BbFJFsBK<IoZ}}* zEo8OjwLK0S#Qn>{AUi|#E01#W2-kd+49H&?v-^akZI&R6jEknw09?tip5KQ#_+1l| zI^yk?@|7)Z|BEIe!3<@ON_)n2G3gBpW-aUX!K|82k6=KLu<!TV-;mg!J`ZQ4f~yrg zfLR57g=@-dS{g>7GpmW9UsbDRyP~9|{;&8X*dByoSX&ZM1!5nMy!Q&ST6uUe%!4)L z@#K=pM%duu&Th;n7juRIFVGe4<A<RW!h_)|obl@HalLs^KQ@ZwI90}bb}rZPM}yO% z<4HwnQ$)t@4d#ZK#I^*Mx3o1P=V*>jWlaaoK6dJ<mTFFLGw*#VNPBsI6lD6BFqVH; z8|`0h82T^VccUA9c#=nDDN73A6gFM;^n;)-l?(0G)+#DC&4m19eIacJZ%}!!<e-RG zPVcKdn_>PXI>Q(AaZ(51dW5&izWwckiSpg!F<H8BQZ}u!Yw&yf<CKF|Bx_Q_@&h)7 z!uJxCqRa#M;8|g++(|(w7i@3<y0*d%vot|kk2SXC3*2c-95^5VbeCeY0Jk$$&;VuU zbH?roOV<r(dd`}2cnktXJSHks3Vxk9n5=oN20B_shMLSNSPg@r7O@Am0rK%~!2;%q zn~GBU+#_7kJOvKRHVeY^g&d{M^T(?{(88s!I+)OrBR0h7P`WlWx;d9A?^@c}ynBL3 zgrK-L;;U{0{0~q_Ocz744iokil)wzX<5IFbztuRuX$Qwx<s#SFWoq&-k9x5_dPSRf z@S)wPukwq`a-0vyCJCPt^^hc%MA%gD^F<*z1}pVSmz}(Gtd*{b6)QYlDs@Z8$SYqu zWi^ywVR<oLb(}#l<yea%GXBV)9b!h*s<MT7^kPC!Px<(k<!<p#DkeL7qB5@LXS;?; zp1oDHz+&+DBNGESZYQ58ZZ^5!)J%~pUli5-EYR6b5-SU;xOOP8;tt;5XogljV1-Y_ zyXn4v>mqknK;+PatPVu_r7AmmNuy@m=A>qdNd}d<Lme43e$s<??bDxs96uEub%{aw zhLm?Xh^_M^CzNSR-96>~7~bR?@`r}WHWPbId-=l1GoKKpv0(P~`r8f8hML3DS&DY| z(nl`GD)fa1`}L1~Ht9uhE1y8RmxX74bh41I_x2uA`9okx75fy^AI)FALPrmaDh=t& z+Y(}Yk-qebCgsxMAT7VXu*SG<z%36efCzba@OlLp6%0QUKg=T7I6BxS+nEKqoEh;6 zQqc3Xl}%YxcoN<v=>w|i;65pae6}Iy5o&uicEwhI*~K6+y-Nl-uid-^7vtM#f~u<m z-Hy-usHQb~n%`wRqeN?K;@4=n+%vpMbJGg#XX6sj^Ipgm+z8Z7+KU%Gs(iV+QBP7| z82hxbDq1A!VL1QTp&MD^0Ul}A!(YIB@FT^e2VKC@j-gIswSAKw$=ZN1LP3fXPt^8# z6F8_gtM}=qz({-(CUJI2Ri8z{vqg@D0rUYtiW>ia>0Tu7ZI_*2>@{aMP#@;w^1y{| zammqPxL%L`TuFgppv1j<*JKhvfD)5KRVOWs_6u=zEaf42V%3oN^DPOtxHUr1xa`I@ z37Q!A{wt5~GuQRZ3{GAe@9%4n9(Emb^rRp40$CgP`{*|HGa8-eh%$DsJ8;e0HHtYH z|7AWZ_nKiTUBiRL2vj9L*ctJZ?EZD29_~q$B0v8G+w~1_BZ<Cgi1h+_t_4#@6L**- zj6X0Yg6f}<s#ce>F(okX?}WLivok-xpkK(F<v5}2^L{o12(V}L2Bwn<N~(N}fuRoL zr%ptGywF;1GU5)=EDjrnAHCCOcTUc7QUPTn*WN%AnW@Y)U_|`L_>obX;|Hii`A-lg zzWuFwT%Va3EU0G|qws<4_iV+f<flr>FOiqeNz-0VvjcrZtc7($9a$|%`eXh3r}T<F ze@9FPh#HuNPWU|9kG<@pTxyy=I&`wYwfLiu%`YJgh_zgaY4yzuu7S<n#A0U`Onu~U zPW_a(h5TfK)DTdy8_WDzx9S*PYZ$eNa1SjSFwr9beTHXRj1Idda{!6XoTQaq>*mMz zaUA#U2t6lY{SNm9T+{I>_8yO*Y&wsF+TLe;-D4(3vtsy7asWZ+^j0V{@hFXQwGx)3 zPrqfSpIt?Ngx0-i=^i2YK};z8SNM9wa=08RCry)`xNRhMmK?RF#o8-ue+x}Gww->^ zfKjTGx$`kAD~PLF_tS085|H~M{2|#8`U1bJxJ^JrOJGU<7Z=H)%cf6f+-C53Vz;v6 zoz+-=iP&B2Zn%^S!c%CD9pJp=B(aL}Z_5{^kp7NB0aqa^NxRTniMVq+wH-qT4M~|w z{SxODo+&@Pe5O9z&ExJ_(c8txx)g?qA4rJ93{{oPFlag_<<-<Sg5a^$KlQ`=SZFo< zJ?n+IU&fZYngM@GzOEoO*8IBjU2e{)EXfhRM(+Esu-%6SW#dlX*LejiFydToS-LZ- zwV!Hl{!pelgAfg6>h~*l93iE3Z8FA_PEzDyvE#BOZ`ky9zJBbRf64pn@^@Nwk}kI) zVAfvMMB_}?okcY14}bqCu*$kX&iGhz{HqjPr}ien>@~{+;Ah)mh4k$wg;ZLv4$&#N zb2*Uhda5R-`h&8aZ-q|6qm0R)1PwPEccn)!i|~;dcZpAQg&$Mw$oHPDIg_nsffy(F zc}NmQJ?y?L>Jct`J$Yztr<<}^NxSZYH!$O+NmvcTvEn`YfH`+<Z)e=HTs=u<Af<Q# zs%Dp+8R8RMRyp=Wla^EUykNo%@^u5BptW9R28hv&0N_MG#O&Jl<gotGD@1BiC3G@g zxk^;;P9%;%)sr;fVOqaGSs(0j>#UrAyn5XbgBE7cEYdnx5k;^!OLk&J7!eoid#ks& z$*~j2rg_mrNrz$S1=h=y!mIQJjLI}mrwDUBRVQGTHt~c4-sY*2$r`|Rnq-HDiI}4A z=Lkgf?y8b5U%<9}jt=iTt$T(uDh!H<0_xm$xo%P6-<$ATIl`z`!^;ayU-)U~3vMi2 z3N*I*k}D~&!9OP*!%gWDoB8u*`~TaMGHq;9t!Jpr)BqAc>OOd{-pf7QVctQSB=j)- zU6xvr)KjC><jYyEZdcAGf-<dUnviri+vft7Zcp8w^4|_t+=nP`*xW})s(_^%jQTD} zmMhr21kvY$kj1d7UNNbH-*dgoQj<T8n?6V-{??Em`U<s%&EB=W&ST!>n@V>-%S{U4 z@OlH?=YK4lg>ZiHSe@z>Z#$>-n$4rRT;*+_3i|~|>4y1RLk$YgE@ZeoOikqrm$4Cx zHfKortF{@h%2J3Pu_G;rAkMR`s(w4GtZ!;*#o0loy&bDp^+D=od2x$03#b!#Emvd0 zKC3<f2IrwlPQ}G5g2kv>klk~>xOt&_r3Z~|BW&MFNXd0V0I~Zy(3ip?x-;O{P7s=Y zctMCS*u7(_k+r*Y$@|p%)Enti(2%<s!4~thS5idLm${J1=8T^Z?Ak(<N5I|g1bP^c zBJ#%-Z3Q}W4YZY#Lu@lGC#wTYp5ebtx#hj+k8~WAY->|!uvI)XzF6ew6fT)_s7SJo z)aD?s=+9~zmfZFW753~9LFe6^to3uX-_|%DnJ?ncCVtScZFA7$;%0m~kbKL<lb+T& z7QseU1zem|)bR~FGOOPBPYp=Ciz5IXto~5pWu;!yLOj@P80{c*E3TzGE@j&)<b+7S zHm|AGJx#Vh<Rg8J#j;nC-F}WSJFYl+Ez_WzT4+5pmQBU&^312X8D9-tc_U+k@0LNc z6E2N{eBg2?2&~?*I{DqgV5nA%3R0wJa(l&P#vf_eFLrjOmZ1XPAld)!x-hQqLBiW! z(ZSb4X~)Rwb-FTg5&m<hx0YXTPADU4=8-`6ERNVBra&x3742nOOe#J?;^=}s1YcKY zFuZvG%IUGOx$c#)Q=x-l0->TXz+}eNWKw&|J2x#RuNMg|cG$AELR%wTh`pY3G8^{B z!UEPV(u$Iq=a}xtbeOZ5vL2oxe^{+$!9@JfvhETdO>Nc`3aE5;9t(gfW<4o(M)i$< z?m-_UNcj8IUpD4;7-D((%bWo=awtRoNl0O&az)Q-(($Lg(_3RwhEA0)C*^e`w*!AC zqWYZO<QcQ;UJn<4$c)M?0I35cRzSdV6d&f{%xe$ZAFL}y6~pWtLw26mWR;VP`-D-e zGmz{XHcdu)_BP3vF28nZ{w@SOfCEr(!vleO7R4zLI-Ywwy%c^L;XD~u_Y<}sFyhDk z?e|fXYey3_B>Dzwx};8k`{Jts*JH(bF^JM-h3mKK81vy5PBOHWDCfsli9b}GStw=< zEr3Xh-BW#G`@qRTuO!TM(Fnd^@&u$7k#X&U!NG#y9+&Fjwyxmdp#*ujLn*J{xSD)v zZ6Y9P*VzF5!eD!3Mh#Y@^_0oaa6arYQV@pl$Zyb3>3Zo3Vz&)@IF?|Cp~&F|>O)qW zfguiAkHAoXeq#0n+>uiSJqkQn2!4ucZAv$I*_o%4o$6#D&ksb@yJr&2qV}=pRv2oK z+ERAj*22NWzTB8v;0PdMQ|3wi<es|oIbO4F@bT;BG*9+sh%(I=V(3R5p?yteJc4b@ zPOS&Zx)P@vDu5o}Q*gxw!}U(m;5lOVC>u-KAE0OB`68rRFOA9%NuT9?%wg6SR#)3E z&Wj^&-<ek7r0cDMeKnbzLGo;Dm^iBMTh4*~DDPh_#AZvdH!{8+NItlF@);n~I17aK zi1PqW2l$;1Q|s<(O&@{y;wkzg0in2KL%W6!Gvya=V@_t85|4x6A3=-E_i3IHbap~W z$9;A>$=FNyPCl}e*69Jeu@n^{Bm?F}NkbSEtK*vOg+%5;U)?zU3$3vhX?S2~@aB#i z2<i~pzpOs1?A3H)Xh&Q|1eL>YXrF88Xw3DJi!$|N$v-<NB52+;@^n<P2p6O6)18U~ z&5%u35of4lp|Tj#%7Ex;Bl~k$X5h01+4irB%Gr|GNBdfzG`j>mVMoQ1EoQnLV(<10 zIL2Gu=cuwJdQ-=Bcz$#yIEf(cPW*Tw)gvZNOdbQWk?&0WJO@z`uNSNV7j|G`^0_5n z3DMDH@Sgte13Ktr2<=C3DLXR^mcgQWLz$-oUWgbI_p%-yPyk-T<cg^3w~y|LKZcO+ zVtzR>A@9Gwv2m>>{Sd^0LDbLf3iz$I>?mlOuKL*9&rt-{#7}wGRxW*DMn&1QMB3D( z%pfC0Z+>H9&JDgF@g>6`J?YFk9f~Y>tN^53JCbTf2&yF&u!lz`A)TjRG+crBp6a{k z&9$lH0h?afP4CNnl=#h-wmNb6)6-WpKLh@J9Q;%9;{S76%0Gn@J;g5IG)^%oOR?|G z9ZjuzI5`SxX=pYkDD_L}U+}wbu%0d|=y73a`0+Jf8d+qOmmNb^NX>R@erbN`=Invv zvXkS-x^5qOZ#!ex^!U5_<~H?bLCnfD_68*DSWQE%pFpgaVaZmeSB4?qk3j46PqQ_o z>hgwY=_1diGYs7qh7IY3LDT<0;HIsqsPxJedlQ6&+NIPW%Djli0YO0lO8xBEOBEg& zh7b9;@ACY^lQ!x)Ng$45@i4%QZM!wJ*1d7`k*|Vp20=pkwoA6wkkMjlb#lva!^_9l z3fYB0pJplgz;<E7^@b2kgUlw8dx{SlVximbBcz+(^yu?6zGQ8yyJs=N^XEf+;x9Px zi^^m1Vsx{$3QfD>1GrNsv+prJZl};CYz+=c#_+si;VK@2P*{-c;!apr9juW3_y6F= z``h>6|0#{`zf5_1aJ0Kka=u6WnxN@X^36$c&R;aBw}TNq-4x&)pVJ|3Tb5y;ecB7> z_LfJrUP+J%F%}SeWrl`3P014U2S|t#G~=IgXHc87gN!V<Bj?;Dm#u2#)0vst=@&S% zZ2J3YAuU9b&I>Ln<Xz$p^7nf~)V(Sm`+O`~Q+#!w1$LRh*uQ@oCXVk%cakI_T;5h4 zmpU5@7Wxi<HztW{ZxKzu+8ZAV6l{V`BA!6@ovu12U1V_X11wmbeLAO5CeFAZf<Tg{ ze1*3-Ep-Yn!aJ>~+(eSChv3$*r@OgtU|TN%llm&0Q4Nnq&g1kwMUZ+nX|Gig?pOwk zTa)@`r<k>t!jEIs^6URy7U$oVcL#4Httw~RXypTj&udQ>`q;it8j(nj4Oas~O5MHG zb7Dfz43eM(M2aRIvGV(MlFu+XiF!IIy19MU2SuMatUO!9Z5GTpFSZ9&N!N2U<h4{^ zRB{i$0NW>+C4ni>nx+*#iVtJIh#3#eU;#fDE8E;Mb=qm%BXNAuVYE0V@oFDZw$j%% zgMD_)_q4y$%|g)Ev7`=dwTKQ0CoiX~q-QwF`IaO+8ucQ$dg+Et^!CAriY??*elN)k zExBXOlO{a{G-=UHGTi(njZ3eX45=&yZVwnbyXn?W{XB|LK_twf`6pp!1df)9t4)q9 z!5eixfkr-VoiV;HJW|EOF5lVQ9R`YYwUSo(Hrj#-wY;0+{F=c8PydzJ)_g?7j$(i~ z*CI~8%G<-LU}M%+eDuyUWpu{3C%f)`7e^FsN!v2rXMp>Y!XlJc_{h!%q_}~0Xpb{v zjei>N@(96Hejq5Y!Ia<$k-X-Ld-Y5x=|;nrtUUObb>Y`THU4cL5DZ<s_8G207>ghG z4waqVMZ)z-m-S--qC4C|Eqr(VJl;qS@Tb-k@ru2O{O~1n9av-Ko+}55Vyd7jyjfyv zK@~BxTa9{^ydv6Q>i`=bZ5rc&o6c#TKkVkH<e$A(+Yid!+F0hQM<Rz6G7O(=G$?)q zaR8j?e&*^QyNrEym_7vWf}=6<t1h;Ih%O`10+x*e`P9v94>kTkqP<F5Jmjkk_iBb# zf-<v+By*xUBa%9Y;Kpisl~d#z+Q^MzcvBjxL`}+?NQ!tLq^!Nk(5R(ZHK%c5xOB-V zB<QK*Ezj)>4R{AoFrv4D;z%+X2xG%?R>RH6k;}}}>*SaI_Q&F<S3WO8pR1JYy=`@z zo_k`H>EL7Qdg<0^PMX)lZK-`w(jknb`mP>+9*9hUJCXcxhJDUaxt~yk<93e$wYr5~ z71Tfh&w-HUx;>*&Sr%KJV5+ve%)vU{Gyp6GBCof^#3q{}R%-?I{qQ55X9j&pEWiCk zoxTw_Z+PK%k#f_k_6bckZm(>7xdsJYl+Jrnr}tLH_)1}zwcE9v00NVtn*?>70aF!A zi|}Ah>+t~O?N!_P&(Odl(?G?&`>2qln8}i9Nr4Z-zqUO@_Ma}qGYm#|NW-0*$?ISg zB0ig6Oeu_i`!IJu)>qT5%3<szHsK07MN^9}0W~!No*X8K>g|GV8hgAM?L7<n&M7=d z*bH-b6opVFU;yVcjv%meR82-WC0UWQi{K5;^P{C^qJ5qR=^WF!cOQo<q`Te}SNurx zI{<I%f1Uy<9>c$I@NQMk=HD-KPPw+<zf_TO>&}acCEVI`A3v4+q_0oh<)rB{$lI%7 z3C>&{qAO%5-mu?`Dn^ck3!-4vQf(R+gKp@p4z@*XNO0u;*(CsUK*wCv4>~AF7h*x@ z?i}@^<Fc7gxu-x1B=KS9@$ET#?HBbIQGEz(q9Kb-aD<E`|5?5dg`5*35Ki&{yN7*y z8;OFkdqHvY-#%Tci<qx|t~A83`Q+QLLKbmi0^9(opw*7qD0YbBpj|P~Ifevi+wj=$ zV!t#<nMmvSspBQOfy>+KGACte?@{%2$%-ZSm4`$FWcT{V#yDPZ5yDyj{yKkOx4-X$ z|KqQptN$~rfHN6!U=d3HXAA)L574x=Qz7TQzto@qU2Dl18RqAl_`5bv(|An_a-9^| zR=C|E3dr$FiYqBM+KInMH8U3L@@`Kltfr$9UUuD}hE!h~C}&Cz&Und49$V|EdhcCu zmH6VQ-{<;VUu#*v4$a~>;pg6y3J2e;O^Snez2g^cjRrPd=i`&wis56CG((ijvv3Ij z7RCs>(~aQLhQRW={s3`+0jY5UH!R8$MM^n&d$=rznR#mi1olSyspHJ*ut&z;4y*}b z&q>2Ow&6-tY4TA&Re}tzw4+XSa$#-pjwa@0fkOn-=hC7lC1nXJ388bMs|8GnGG8Xr zj#Zo{^&h7%wne8sc}z2nNeh^GM%dNp(-9KCKH*<`#uGhmyB!Ld(bOne#35L^CF<2j za~r*&8y^m{=)6azoov}$m1#Qv1gaWOW95k(I!VsFfJ#6-#yVFZ$=ABinfTU0rhl)| zr(D{~q7=oH7&4?ibo9)&I#GD*%DePge5NwZoxw03xUJusV?ZgncFj}!91fP*qq@R@ zy`<9r>2>a5l%`cIVZ-N)g2g8pFO@sbq+j}?Lmub7JGcj8)P_5fNLY3f%lDh4v7~}I zyy-`0c2w2&sQZ?Lf}C@<so6c~%SL0~^i7@lue<Mr+%TzxU|!nja8@xv5+p?vN?3`b zGKmX9YO&1Kl-uof=**rlu7FYF(RbhOjHyIH8}y)Hiu^OdbWyQ8;;&tlHdI(>8V}Rr z#m+a91iCu`f_#4vv6tX@8BgyQ60DrQb?HOp;+DYNQ%1Q@ufCmkJ06gU2)?EuqD;ft za|Nrn+79i~1{2E?;RZ(q7XVKgogQ|gY<5qlK@r>wwd@!F+#AmqeK_%9W9PftW!nD0 z%J7FkIa$^}w!><ww~Fc05g?BdQpS2AJl<j4{-W0pUKk!T_L1mShg#unYg2nxyEUt+ z!^$bHMy~|p&5als`w;wpfF9Y3imdANJK7V(BOgw8oLTM%W++Z=vp&0UGugVr9^aB+ zX-8r;&zNuOja#t0V05_FpauEutayD;ze*b6b_-{JzPv6;W-R^zc5WVJs^QR-!cVH6 zu?gl+HwM>7?QEqpm7yQgnP!8&vxg8E8mRmvx6ba`6spP`R$exXNZERR49nO$_<Ebf zw-S#m#A0H<ETuFx*lBw-UFXr->Tr0F$i)%Wmra!+hpm~3FMf08rAm!*hKDMUMT{mC zesT(KS-%9wmbTtmUbx+OYhdrH_WU`q;PjWLKHu3;pxItQ2_#_U@tyO71ldmF0wLx{ zsB|5%Hoiu=<q_O1K&yurYZ0OVJ&ct>?xC-X(=YfQaP?KYoE0-BO*tU&i#R~tqDM*_ zqzVZe_S&eJ#SG~1I_ITv!wT$^;##c(U!p4>%Thlj+nQ_)yj+B*52U@MVN9T2ferQ^ zX)6)cQnV$vgdu5uR5>yOl$|@k*3y4+?~Z{0^if2E0?rw2D7&(>?_u~0!A7%g4r_BF zKO^=W^@ed$rC<nCgb8tMz!qhH1#T81jaEmMRwLY%sy+hTw8vP>n!pYZuh1W#_Gq@L z>a%b!V%uHPN~Hf-!m@@ROPDJ~eFm;qwL(rkI5MATIv%y$Sg~nG6&QM;IsLdi4(jUJ z4-D#KR_W&d0J+ridKE~|XkY5a`z5Haq`>9hlW`hgpaQ=$>oKF*X0}r$;91!GX6b$O z&4ZoHXTecNF>hNQ()@_|tIqd7HqZaV{`t33X4yCy$O1i7P#ty(3w{8`K94yWs0fxm zdgf^QEa0+lhOO+IWkWd-TRlr<W%E6AklMF@IN5iT1R}6t!l-?3>qckEN#$`*Zq!!p zjShIiA+!{ez4ZRiH28`y4p-cMH71AaGW`4-tj52+Z}fi;FM#!@iCA5sB|CTV3*-jH zRLfw}%sZQBAkqbU(25%0QlCi^fO;{szDkn~%)PByDnSe<f4+4FNHRqfKNjx<pgLLC z*fn;4i*>rG=9lAsP3nKOA1-lJ8yMcsl6JG<{+tM!MPp(om*wsK=XuZ7zgvHMPLNIx zAYaJ*Uu)n(uI<5jB>%Wd(W+YVK0L&*B_DXH_b=x>WvP3B-k7v0z4lUxuS@FeI?p-U zG~i$pOXVa#C|>joWuK}6ueP`6SYUJas08ZX!PYIt+>pFC(z#wtcO54+xXKeRR)RpA z<^NRw<v+}3@pl*9f2xbFj}(?d5g;BL_;CYu3?SocdX@eVdg5%J_-V;8mG4$YL%Cet zCsUC1M29p*opi?T(XtrnC{D%bXVTT5>{oXH355NKji@DnB_8~ks^)JUFkAtcf)YV% z6pdq33LnWZ1#Z2XFE^MgKeD>d`I%g7K7G5w^O60nmXvh??%{^FgS^l)8EvQZP4WU4 zZ-kFC-slw4=F*9@C)mO75kp|tqz~--=p0b$-XE}rb&LB4QGOCn+B8zt#~H!ERND_< z*2h|xD>IgL-&cjK(^P{3tDh56PD(%3-vhzjnp~*YnDkt8WsZ`FZ-@nLVYNtUc!4CW zP8F=NCyY;19O;?M=BHRyv5EVF94(2I6cR1VRXfkc#LBY{Dg>@OzWo8fh4wor>XEjT zx3~b74~o~aT*cXJPeBh8${cUAh4=G)$Q%x05W05nVHhyxS-Jw6%p@504wZ&@!9(!V zxf&x{SJ!+jK_fx1%GsCPZIZTG;{%A`-U{=l=V+64fIrp&!NVE`q+7lsD>smnWm~*g zKKI*Z#cYV5hyyV^-&I$OISR*VzjX>yxnY^NeVltcfj3@hQ*z*y65v*2H^#E8vxrel zy?b*hQzPwxu0SCmKsoq&t8I1{8;qa#-_<dWa&BugwPc80x%EamLuCGp>NLhb*2>nH zX+-3u(VcJ(D{-70-Vrd~YhJ?Vv-SnH&eSIPzp!_xn`;^wf#yrwdrj(#ehA6R5#m(T zHt!N#XS-XXk<T3C&X*ZgxauK`0i9}~Ut>!Q|onn>(#XR~Rj_yV_=@9pcJV3Q9E z88L(6kyWc-$cZb0nmn54j&xic{mGG2$C`^_V-n9cf<wqUiaVsqaMcH-m54n}u~3O? zYhVsDR#t;fb%OWBd`<gvGNs45evlCmk8>acr!q~}15)woUPbX%sKk`E0!;P$%?aOk z_0zS5b4XWALraE!>J|5Nhw7)78{SoMnOp?9M(FbX2*WY1s>S>MXk(`XGGqfZkiLQF zsk4nFnOBp}x0>0{uw<N;XCKLONKfC^6**kiA&Ra&Y9gT{G=c@@Y|<=a(lXsHMjqT2 z*}Ha$`{t6=Q$C;_&NjwD@g!@kv7fdfL83bp$ZGbJnp(`wGe6lioKP+3sdr4DoCr3H z)fc!$8`jz6#eiMTUK9FRoVE!WI@jCkk)r(KxsPE_ZcfsV<9s#8Deb{vFBi2e1&hVe zfHO{BZp~*K6(0B~niv(_xlcDPO-2%D=laxjq5^cpVSZ*L#ZE8Ly}TkvD1TMCV-FsU zHD_kM+1d8eH|=SDO5|ftTJXOvM^!y<dmF4Yyk<FFv)a_ul<>8=x!~HFCkBPtK`!By zAnD95+VHD>oNyPiK4G78YgvJa=r?2Bwv&w#8L8ZQ9@qkX=5;?l*3fGsW%vdyZC?xM zoLmXWsu>8e)t&-;fog5QX7SX#T4|!duZ}H{Y_ylJa9P@rzVk*C<M#PDGQ(hYOiz~! z$Pp0@74*be<yYFm26bl*YS7<5>$%i!mLFL)EcHVV%X0~YbL%ngxhgU?86V3d*4+$1 z-xN9|V2l<dz&qNTuQMnn-b!-M@m1V!5)8QFY=H`oAIi4Bzw9k>`i7oT#Bm>$ICP4g zb&a#fj2Gr|htwI{aUP~;Mchm3JT|eepWQ8MX!snl@+tq!pxCrTD&ZoN9FP;KdHL9a zq5#0%6g5>=mkhML&uLzR;-_bPR7{mWFPUlQVzy<PQv8KC>3!<6`GWPTs5|c(!#PHJ z#QCXw@N3hxO+W;rDd3C?U{`SeJ!LqHtd&KHps7D416_Ybh+jODY<>y~N%;eG>Zdrv z3dLZG3V~hN?s@Lm)zmRpG`A~$8};^eSE7Bo+^f`E6QJrYDoF>cM01}4&~lYAvtt0s zf|*@iOHu2|z7Bd98#CoN&qM4udB+go#`E=o+u1Wwx~FUV_G4=aOL|@BNvyqK!VAfT z-PYJd73i}tFTZ;6(d?D5H^PrHR2PRCL~lRV2f2Ixsd*yjj#*5@)d1DgZ&Ya-Z;){E zp|(U*fyR}$;c1tpqYbK!x*yS{CxX6G_>ON<bV&WArpDN9(?ksfm)+=-N~g`k9>`8e zx?g}q{Nk^D{<UVK^o9$`!w(gT!sW?Ths`GiSO^{p5!NT&=@s5f3MPOxdO1Uiq1kHY zxTR%d6x(EQH#axksKlo)iC52@XYr(K2cn&OlhOp8;hGpgY8Cs1WR>H0*tW~AVk2UX zit^UU12_9!I{hdey<vWF>-oh(mJW7uH!+6*J{ia&9uv}{ffIlit)rPwF*I<lp`Uf_ zQEIvdpI*!3zWeLX$4{9+fM{>JpAgnX^2%*s*FtMK=9fxhKGibDS1%8X<SaA@yfyw( z9ryeJ^M}PN>*kD(@3(MPoh7!=R{k=t^3B#1Y*1iOcBaC(L$_pXs?ECPy@^+`Y|Qjr z`XKxNNB`p=2WkJ}Nbmnw%d~&q4g1f}|Nf;sZpI`aB~F~2hrG6$)ENi#e}8*vOF&00 z9fem6^XwX;Z5I`uZLZnqJT!*S87&)}5c(C0w7=8Rd_U?*i$pYW_A~=VbCdn(x!)Vb z4>;4BUKsSO*iBm6grR-{SqQakdH4vtq^vEyMRDoVIu|C85*&Pd5gs@}75{**0>W}5 zH=eBe#bP);WGmbbO^#PMoJY0x;x02MF}oWJjJ!OLmVU628m?j7!47}eLRwlGBvVhD zvC$C%a*_n_3tP`&9R;&sp&yuz@BDuCtShP(fn1BH<OP(1qtAoZe$>uq5_WgH+ewUF zZDBh|z1!q$MT)AXiI-=E7^?i5R({5!?qu*IVs)Y@*E=Q90u4jB1`swBjS!<eC?ppg zTN97sRRoa%&WzsKfnrL|9EP>zSW!7kXj{8BH2-Xp0s7J||Mt_qUz4WTfs~GLHI-UM z%1s?EW%1NmM8o>C>vgbV6D;wr`lnX`r)9`Z2PkOdfy~Pcw|bbzjpqF9^Mw1ZE*Ahv zJ>|klnzlUI8lVGkQnYH388BxPVnJTzw%uC+s9ZWBUz_z<!{~wIi2QpCZ!UFh+O(bZ zb$;6LD^-Yj@io*~PslzEFk6yZWB!R?rKrqch04qVYGK|hH>XSU-#N>E($KKkE5l^1 z+Q0mrJpMuCejOkPD5qOwjChAQ>>?iKzYB98E%OZJouU{MOfQJm9OjlJ3uFsFb`zIW zuJ^W7VvwR<D<4T|IV%d_EK}$P#rZl!7QAqBy}MTO?Q(aCgGsUzKSkP3IX(cRnD+LX zo_=^tN|*s3X3G7(tkK7B?YJ+<m;%2=wjgy5hRXZJFSDK$`~WWPMVQ&Zp@;Ddid}ox z4^kW2lZ~v<J1r<|U*x0sZDvmb$7Krn^sP{^)uU@A1rsVu+E3IpBQ6ZK_SaOEk##-4 zFK-tJOrgk%%E;?p*l#@D3<a_nk3`Q02!BiI2d=L@hRr$JL(fp4h~Ag5G$*25ilR!D zvzS@Urd?`QFdoV&IIoK{YCLvZ^hSKfOm=vnC0;Axn%J|`Rm<rSr>jHH!k|D1QyLx- zFUA{UdNYg>)@a$%iitACq;e_+zmL>tYJ4K%Y|2aZAM@c3AI31kWHh@t^!gAq9rAF? z36yEz*y<L{YSNj5%-VYq7U-jA_N1aTul8C<K)`WDE~K&lk&pyWUDm5B2jf{*pdHPi zV{O2`WI;0P*Lv1VWkP?~)FYX70l9|0F3qS!<Lag_Q`+;7nI8|ic>3L*#qF46v15Hf zht5ggfu2rSPnZB1+Kpfr{ke2UcUGb<s*nAFDng~tXT8_&iSXi;fK~$~$%ZBaP2}J! z<M?oRi}=p3V_m3|3<;6=(~JpLK%Mj_s{G8#NvPGl%D#c&PpE7koqeNVXEkyqs82WA zc*Ed{V20~)JqQZaAX=nM0(d16YX<b9=BV5Q0Gqs11m<$A%?>qDv)EFzv@!MyD&Kwc zz*sRRv+TEKj=k9{w}3^e&8al*(6K*2f<Tr-bQ%7nwl{j3GnHb77w1_ZFK$_R7l<X@ zjvTmJn5+8IK?tu7f5$0#l{;p2Ns>y^WkAG*vcnqjqqX6h4+xH|w}>a07;P|YWWY&! zMo~rN3eT89dzzI^&Bp5?8tc@Mi@h>gfpH)S9L?qm7xfZ_hCH^)$wQGR0^|QUXKoe5 zX)l3n{$>6tN`QyovnqxdOgC@6a+(M)XDpNMz-<O2(195b{PdrmGVyMwD{&T0r0b`j z;Gw%J@O$DhQ+>HZll$*mn;Sz?RK(3-s;`cfmft6R#V&i=ysihu^@ukP_8W&@Agd8n zk$7=FB!;4pmv$OzLP+trp1XBr?t1nojU)6VK6X|}`ju|U18AHdSUT`d8%QunyuqV? z3coLOeVG%L@j`9jxoXyGxl{*+^A8YHNo_)&bx7oZZuw2eV0TqXy~9&Ilwl@D>mcH3 zLO~mGYCyd4#|mrpyJ<)%3Hb%lw|!zY^Wa&P*%N88n>XA#E}QU8+B-12r0`sEz3`oI zWs&Y}&fzn_A((%<KU6leTN@I0$HbZW*K@|CfHC%df%u|{pM34=kE3p&QgO;m4cFMd zk7jPfxF%j|208gfiVIQhkisdqbWV-M`S0oxN;(t8P1|Q5@f+=&?p3nUJYt6`SJujW zQe(mnh<QEIX60h|415yXgtWM2fc{tJDR!2aoe?Uq<-|RCdO*#N{rhH)(n-G0kCV&h zlY<K)X7jA?$AXUsZFcQP6wZJ+ruxM=$Z3Qn7vLo6zds)US6^l%S>=^Gz`Rf|q_;ZG zqLXg5Ht`h3I@cF9gv>ynfN?oCZo_>yOwKaqZ#n}Do3|9wBcQg-GS!4+9B`7HgJ?c^ zFY-;=lvV$Q?}BYzotVAPt2f}%h(3{*BJ)fd0s$b~DIIsSWPWXkDWk;{+@pNKld4FP zOGK)CMj<%uyiSW=Q+#9l#2eQ)bu%Z-J(qdisKY=qFh!CVG%CM3TU%s!tVofb_VI7m z=}W*@y|_H^Tg2qd0lS)6?#LZ4hZ~N<mqH$blh%v#uMOA-fp`^w@bJ#0(TXLtcx_%o z<fa;bC-2$Tsg*vfL!C!SHq%A9!xEZ4Qa7f0QuQ85FwvU6DdZ@&B0C>n+dfo}i?4-? z5RlMg`F`zFvkFM88L(&iw9ii$O`@!tu}Qn?uIcldm-_I9CJFZ_gRQ?_DeZ&G@M-f^ zgpG6(ORw?Fj;2SLTM=xoPYjHiBLG1+(DyeX{ZsNa3x>0@xkh``QRUM^_3-2yyTUil z(Pnk-q?0@>$iNd5Per`L!K{9-tU9%=zVIgbuG;JdK+#i6XJ2=HoRrSc_jfXy)fWoX zNCI{7s&LLXu8Aj`Y#(WZNbd!dybBo7Ee#}X6h7eeXRqAX&99NAX@2Z2sW{mzsSIN~ zYeQGAa+Y&+`-%UI4C>!zBL3$KssGeU?_Z|8+G_d_Xk-1wsrk2Q-~NZb#s9HS{J(mk z|0AH!9P$I5^xCYHn==pR00X52(K-=z4m=CcK>c)^Its4vy~&oz@SKli2)bXmra0JA z$Wp+6QqVyI6LCb5%pyA&DLO<d%B^z;mzsmmlQ$_>E0)^HzA`iSRO5X27n`VVWtwk) z`Skv!OY^^4w*Q##{cA=am*K`W3uKpGTu%Jn&VCJH3hdSjg|fOgw>Ez+!InR`)znZg zV!_MrE@3Eb&X_!S>_oQww0(CH>r5OQ?BJdGkyp#HFjDG`o@EG1v%{)pKP$Fh8WG5r zVUs@9TRQuTS+I~rOyQ)m<`MNO><(Tn8ZHp_0;-l=3zs7#EySSp(FaYJD&EiT%o!iV zWQoqFr{$dLsocMq08;#hK>PKy8!S7z*iB&X;A=U@@Vkcn&t+SxKKErmr=~v)17!Do zmLD$mC_iN?OBH$}W5D!O7laS!T+ry7Ex0(NybYX#a6mM8lBsx)^(=Awx3t>xx0&9* zv1r#<m}x6<=>bX5uRKELsA2~1fhXOZrp&RPmy^=mX!aZ8_J9T1f9rY8%K~sVtL$Xx zR2g>jsm#Hi-NroK!mTQqK5nUt<|0alSUR4S!#1J@8KB+_sx8x#zQjg8LZUc1oE^L_ zy{1^Rj{*cT;h-FOzKmd3%sk&XCe!DmrBWrc5a_J)f*gLPv?$~q4ABz`AuiXxm#xDM zAS!UUU#$<Ob}Ub#k?eixI{8q!Jk}crQqk}RzJ8Di^w^oqvavB>K=epnoe*{u8?>9- zXh1va^tNV~@LJk$9*rKRUA*;<FyrRwk}a6KaY?!g5JeXkhvGUp1B~{5%CFGD77WK% z{kZfQFy;22ktOT63m=c_Z%DNU2hn4)USU0}xjP<JfoQgct^Zqf=NZ)G`>**RP3av0 zDN?0`CPnEsiijX8y+|j75R?`q^bP_70ty03M@lFG1R}kNi1Zd}f*>uBAci32x6j$x z^XlyEod3@5%sH>}A}=zz^W>gyxvtNZsw#MUVDd4B5(K28&8!dRNgOv=prd!4*G98d zK0_|VOU@Nl-Ip{+cFJ<Hu`u>@HmS$_0my15Bu)n)2?HaxT92#LvWMYe&e#bPjr|9= z1b8hPKj*K#DXAESD;r!-={9T7sGyUtIq^Vf_S)Pd3?_H*BJYj`>=+$BF=cYJwo0+s z7PJT#{ekB;ZjLR!PM^~K#z^3!<voczAa}|vd4^aUxjORLOo*N2lZdh|PrRD@;~&s{ zbVsN>jDOrm-nBJ}@i5h@GvtC)majT`JY};S0V1f3VjuU~#3$g0e{y0mk3Y@X40$-! zB8mo06E^?qe0=UDZ;t(SwBav$Q$(ILD5rQ<c2)dSP!^~ceb(4<xj8KyZx8P+P#9BD zB~JeXdZhw3emsdY#26RPr~k5>lX)-^Iv95^2`7?OT%bPR^4yGNzsHd~0L7K}RX&Un z@%vVLL!K!evyp#Krmel<5c#bSY+s@|OZ4Kt98~(<6M0b=@mpQj5VLFj<xv5G^u3u# zRZ=zz@5q+_FvOnze$qflUtPU#qobk8M9<Z$tFDi(zUvt0Pr!_9MhIOeqR303(4LnD z2|6N>vQT3)VtAZtafzqR?H<V~)#PLE4@rzwd4#u+r@Y?790STDn$$6%Kf|L~p;6;8 zf{$uitb_LCsc|KAus7E+#s};jS6OMFKs(%oiQ1eux?p>p0YIN1?AMa+%85J5M<`+p zfJaiHH(SZUh`592UYkcC5rus-br5!m!C3$On>b}J_7P>JXT&9B_KK%-^TgK=11>=z zYd}&k8r}Rr0+2g4S67RDA<;I?d<0j+XlpvseR6ntQ4RWja8VN<(YYEiAQ;>^d-X!W z|J7sS&(BK$7gJLAwCPPKkTna^%|h#@!rl>~0tK-9q;uqkwJUG9yV*Y=DB5cAfmu^u zup7MybvRyl0ICp2w5hFJ4d>YA&#p)H<5pO=Y<wFscI@&Ar@z4+C$XtF9&x-N_!6^D zwBj!-y#r}o@6l~0@1u8gIsE7S!8%_N2Y*9#z_hu0n#^>^S8vJ15sxiJ)v4C3N-ST$ z*ShN4CDf!#;4R+-9OalhYw*|(O=5MMErd7^`5W7m)rVd0KP(f_gz4C7Rual{jUv*V z-aT*|FFja$c1UGlepaC}qQR{2P?-K)+>X-wIPb9|<xbJ$?m!g{50y#(q~;R%{cC4i zSQ>X<fm3UW3scWMJ*G2Z@)%%&xx8DeV(iFfzu;w47F{~O$Y@n;6j$Yaq_l`UsFG9c zXYPLg+2RiT8pDacp0s_~!U_|5pK04cX$C2{7J6OZl*IwjqSub8YfeEvE?Lm1&f$M~ zRvZ{OOjD$pPE#hG-UC~FLS01MC+On7_&seQt{?U8^LxG|%%I93N)VgaraZfb6<bq3 z=Gi@Y*IS^dnGFMpmJhMr4=TB+3~bJ7F{~eez{CQMPNG2yUEwMr=pm8ZiNA+#Iv<*> zg=g?I2VYsdl&hxBUZs3@*?L1S>Fal@!hjQZgfkfCAEFQ=%n@*LPs_HF!qan(9W^~s zJ#Tdr-;$`lx6eJL*qlCN&3zTi^6Pdj8+98|YG6)BoFb!e1#t&An=DUQW;Qc&yE?hr z9(6vji5)68Y_O-bjZ2r93%dNAlP61ElE|t}SwFEFU6pPcp~oFX288;Y^oEHL_hPGd zUSRM3l`ni=v20Uid%p*-8TZy^F`oGfjKfGyhLB+%AwldUxInA2#?dRZh`fPi(bj3C zF%5rUY`GO=V$h?+v9x4D@yk@q3s~?+Gjm7f2RUp5R8K+M0cpUc)fQM@wxt2@-4}ue zDR1$IeW|@uzPa;@;@(3x$v*H>JNTMemEV|2!#<)>gYGW|$*(ej0X7~b^rIJl(#Yxx zzny1b&fnT(H@s|!fcs<<%+@r@$jAT0*@tF1@Y&}?A*<pzBar~3%g<h4$|HADLi}E0 zb>jgF+ES2Z{-JHCg2^NS_Udobk5bp!9olvy4gDJ0+_R#fclcBlx8C|^CDa<f$kW8@ z#wAvs!px`PsBLU^Ue&hc%crrWN%mX9^M2L5L<eyHqF3fUF&D4v@m)$eEO`;q0Qc;3 zoGB-Q7w~D93glmRBr&JvRBmg;zVm#=`;zN#HPp-8?fdZMCJz?1*nGVogl|+*k>TC~ z!dwXBJ5y*>yn`C4>2#TU5@T6Cgj?eOu{dwzptZw!76-faE6@dc^8jW;7#LHwexrDS z5=2H#<v3EJKOm}b1C0R{o-6FOUFU3*Dpmxc-wypT>ZWL;pn97duV$5vuiN(3sfg)# zBD!c73KtZ*Gs1gxJ#eFOooreL)jQ2Er>EM~^4(lp+MeV-7-BwaS}oWXPxAcv`Wn@| zBejbT<tW1RQ`j6}ZiD%2YxZGz<1qGkdr3!zdBm(u`}nR?me$RpC8l0KBuZKBe)ay( zg~T#O*mbTh^DaSnZ(-_R`hEpLN=tVbH?cE8XtukB(JcheM_W#|J7Q+y6t~#TVOEJP z`fSO(PD5oYcS&zmDCgg;ssG>9HUH0x-+v(-j5?FjxN4+x4XI2r?X5r>gnR%dtCGJS zm^>Ul3;39=TyzhVGB+xl{@K;lLxlPlSe<<7e8iM@5mKN^439@?8S}=MqN97xsZC@} zCKV20dds$U;JWs}d^?<*wVQ``hWM>|HtG@}BEU7A^WK_am?$M4U;C~K+JM+L<@I9F zQY2d2*^Cy2yV_eBT*N5wDQ1j6a1D*8n(*f6;+E@lz7_!E80=c%43k<!WOkolCZ<-d z7BX02x1?P{%D0X_rStjtJZPwyo(d#(a~Rg&xjbjpXUql?oz-;#^fa1ZFf-?p$l%7A zrsOJ9#yN0*Va&*TC`(G^8HVT8vZ0O9QVGjK1nof;n&t!ZnI_pR+cen%;K*Vu@s14J zblBhKpELF!5M6EbJFD&l(2PmcmRBzP;Mu!a^cGYFXLoOje4CMix$p(GTeTKXfxw7W z)|AcPo*kU^6R=YTxl>hIOxx2RIeu5jye+j8UuP$!<_UNw_NJ6IPGsayBT>*WkV<d* zoW%3(@5x{%*ZDLa7CvARCO+E)R=;~9_yuqhCvT0Gbo=;-dpc!&vv&3fc7sh^f4z7O z)Cpj+=307z6K$zP>euIWD`9v3e#vgFlCNK7WcIMKO<)(x&;;vm)-^{xiMvvKOIGZ- zhI7D<1~dqKW^v^p5fC`g?z#b$0;Z6?_SCcz>Kn|%zJ(24nqHi(YturB7r51|_rE>p z<+<+jEIAkSS9$Frsi}XeqC<LuP+f|76G`_PFjpjv)C7t{Wq*tx@A#dps1~^@;T5EG zJ4A$aH#_Tvass3hE%B@gTGzE4uKgQ4QdNo=6un0L{`Xy5hE%566qDZw_YxkhSqzuC zF<n-8<a*;NXmnE7$?8KL`w}9ji~s31R7hwl{Zx$jZSYoX@uULB*32!gQLT)J;}PFI z4l+R{BGIo|G6z=bm(pG#jY-9Q<%5k_AWMX~ITGUI{|`tRd}#w6wS2H{kgN4uv7z3% zv%R}zJ17IBda8YLlTzNJb9Ksz*xBzodCH5ozrA2Vl21O1%2-}m94zm3Q}JEK^uq|{ z#<qd!TJdk3Z*h&o%_FYBdr@;?!g1kVX+0fsME6whLxC@hw}7iN&gKC>#@RGmGv)rl zBk9g8=L_m{*+Xhud{d1VJ_w?^3+HE_P;OQtqV2~1q+Qr*aS+YCp9ItF)nN?fy_+;M zW5n-n%=IPnOxbh8UtRJxmjRu#lYndl1hK!ml7=zHr?CK(vd%t<`cE80-&BaK96G z1==p~MTq?Lwe6QGd)Y)hG(_DxW`QZ)@PULY`v>|wge6&g3d2{dlUIEDw65Kq=n%g; zFFK<oLHE%ml&&y9Yl3kY;x6jx!+|SCj28zCtiR!mphDqB=UI+=_;uce;&k~bA$Ljc z@%ZCY+meoaoMj7<5N%qXuM={^KNj=d*NEDX(6H`EV43jgRsz)4jbtlIH2#L_&1Vz} zal1gB_&$uC^yh3eP9(hv>YwdyU0s}Hl`-%%_27U;US2N+zbIsgwFX`3tXGfN(=s5* z{C-NJD^`y`4aZezU56Dfd`Q?F56!@0w>?WGVlo~y^M@eEW0_&UFHxuKUD<ed!q86} zk-F2@0h&NPwjz>C(s06Y78*=op?5%{bi>86;GY?RujpKK-@K*0N^>P5NQZ}FjQ<*@ zjkKhEadAg#Kd;^*zch_6okq6rKzuAN&g?u=oB1tvY|dz}22PPPuWPtD$G{KT3cGeH zK{D-=M#Dl(NIwccQgXUG#O;H6+M+hoo-<>>Ua(rQf|I(8Pqx)YJ8e<MVw&<5roI6) zRz<*-whP6Faz1V*NmEt{_{*?U#|<wfiaV3`#+M-G;qM~3MxqCJMAGqtYC%W72ksH# z<DJe6p_=+6{yTVuOPl=7<>4Q;|K3Jub_0L?HpzG4CHAV*O_PvL=o>pD@mCyy${!KM zDZIn79T6HPma7W=Cj?7<N-~VJ-J_7m?`qTW{%>^$tHH#>EabTQ9KO!@O&0PU7*?8U z1ZWfSu#@p|QP5W)|J8JLpBnM2`y7bgv-lWXOfl_t>G@@t%T{(y6e#6?(<sJd^mAJy zCWM>*6zQrs#})BhF}zz74RxePM0Yg{NfICCxrE^IMwuWQae2g9)28VO>4Yb(Q}Aq4 zHL`yKje88Xx}?1=Lk}dBR*mE*xuZ_>A^(8bRiJ(){Wwz^LCIdrDIU!{fio*~B~-dG z$v=MvD^R1S7!)70;QPvVMDI%c>2-k8CxFzaH1Q^#KBY?ph71ns<9}u+2J!_*W@_Lr z9?!lxwc19K?}jA9<)3EI2O8@43K9CK+a>{zq;6gKv+#a+=Lsvm8AZ}OzxDZd$I{uy z^XT)I^?Bg6J%8D9(SGF`9iS2kkLggRSmNwWdxiK&Gbzd|UP4!AoO6M94!$~fOa<_^ zN7NakSC~X0E{JZ6H@i;an)mDIc}c9~rzGK|CV;(?#)z~@(<2t9VD1!AQ1;{QW3)d< zE4L18IcAP-<8>vavj-->>!<23vAg-!nnx(@>$;dc!l)|5P7Z|8&!TryLi+f3+rnR+ z3-;E`4eFF4W-rNIB3G2f;kkoFf7A2}+GTRQXQLX2gg5FYpR!j#W(yg0RF^hQfxLQQ z21@FY`FN(qlqzGWbjg7aY$NxPVAJH_^B1WTcb@b9h`Gxk*u@SBZ^&9oOF}1ybSBg< zo!%t=O5Q(xUQDst{OIF6ReSo&bG_qt(lGSOQP7J)j;>c)eU=#}0Qc6`=qyT0wG_!u z-0w|Bac&ie&0v)9bO57Wfs^8+{n(`aACSQbSK#bR?@THcqw*3qX-=_3@LHDDvjIJg z@HpWHwBseDBOrcNd%2Ml?1$?F2$SAei)fs&AP1HV-N_3{8M{YaViv<Q%u$`U6iH{4 zNb{P-76;#r4EG>T;`*~2D;E)P0DzpVpk?b<ZYLLXAZKp9Xf?mJVO3%^wJ@(;a_{xm z*5<1(N)$n;;1dQf^+Fac1!B){yh6+$ZS~#)fhOW9e`*c!oH5_g7%?2&lB>PFSdz_Y z$mGpb2)QI~xD%NrBCve@EO{4bfDn0Rtt8?2ri*`s`ERX-@(lMmaP#+<)+|8kAxF(e zV?!^h!fEPnqlCt3>b_D3XSmQ%17B}9AGXTu*K2dCBQ<Ve&v~0fm+l8Grre*@Z!EAk z$Im>LGqcGuG?zd>VOaJ~y{I59CQcm-oLy9?XYn24m$*W_0-eLQhH3QS3jOxWw8V3v z`JEB*z?*DKTcpKr76UID9@GjG*cA^N`IItNg;t2r)lyV9!FW(TPy_F<KpC`8vx=LS z<CyklmxI;tfY^4OlAp~#Nzo^K1YL=E>GtpH;r|XZ+5cHE|CgAO{}<iJ{0A-ezXqhI z|5|xsgS;(#`OuWv)&XH@O&m<nGO0y`snik6S}r%*YTC9s!wIc#tsG1{#!ANY;$3*3 z23>Uhz{0zhLogzRJDj~c1RLM^GI4sd3bD32P`;KoVqlWGExn6^t(bhXZd;VS%i!>k znkUfWU_p*Mt4sTOe<wPAK!I@?(bAW`KvDbM6V9Y%@PT5`mpxK$f2R`4YnkZogYq85 zT7$*(r(ma5!5n@fNv10oh9g)U`*rzsR4B8yfKdf%57i$>G6s|2mc)_ftn%dcP;-R; ziM9>v;nvLBaQ00%bjD*)EK`VxW{F50=p#-63txL0-@QUj=~~$A5FC@oFqT83EoP9a zcm;N=<z>wga)o?=p}c#Jagw<43m-V`z}@f1PMjC02ro_hJCh2O0a;lx9K#32nNsvR zgb|TiH>>BDZ~6XhE>6Dav!?*_amgG?F>|nuiTwEbn`n-5pZ)C!#0Ki*88Vo76bY#1 zgddL~FZq=Nl*-VfRJ5k1v2CMTqKhkeR$c9=A@oDdLb!)r{qJI{3!{8=q9qKVabQRa z9ZmY1TF(=S(^k0GVh=YbsgmQX`d81Oef2u=rHg}3^zS@M8y~uTZifgH#0QytKQU)$ zCBMEHj;jv$w0~*J1%0U=aj3vFPZ8+SP)}*<WI;sV=?HZyaBn?<6{%yd9(<E1sS!|N z8aZ&+EnEj3!Q+;dC}}{KXw&k_-#(-VfbmbCw>sfw&cA-f{1$7$oT%pSewSb4&|kjS zm%<PG&Ed%~#IDam)ektR<NF;MPhE3*ZC1#_aTqWlKjg_Ng$!B{<LXuTn^L|lR4vMX zfs0vT?fBLUt`R(9co4<r+ftThEDCqe>U5-`=Q24@COU+d91%J@wljw}YG9}AwS0#a zyBJE*RBb?1=X?vhk)KzOp(u8&v@iTideLh(CT(7F(^(O3w+_>Ez_4z7kj8$0_$3H- zFdAIrU$x-*`0?kdYM6L)Vd&bsiEDo@mOGb;aw-0l$5Yi4d)Gz*m6M<s$0?ZNUT}F* z%R~*emSS#V+}DYH*_1K2^|EdLj!%8lulEhc;P2vfZ|*SY<8K(c-37H*ozH~xa^fFW zm3nqE`zWZOF75nvj91NPeLzfYtzLF8R#7wx4}e!K=*xj;=IlROd6*p3U|x;T=UoN~ zn#OdfpIWz5dSR<+g3Zym3RN)Na)RIwyVH&BuSU@@`jQ{pK=lnKoA}(4u9ZRkt*jqy z{PL%@qpqP0lj!l5J-YD*4yi}I8Z+&cc8Kuamp08=+WGHa_W!}#7zg==O^StP`jfua z#aZ6sy!!5Y<ZJ3*3&M$j$mLY^L~jMqIbjK?1e8x07?LY9SiZb%!Nazo9@8;Zv^?dY zJRmr2kIOGl{+_-9RT~1)o0Oi<>at@5ac~Yw)f#PFswy*mw|a+EVGniB%g*?K%L+BQ z+#y!-`wme1hf6l*8TJcoyhjhk0^ZPq2uLFmCpnX-Qouu`Pn4v<gYccgh*V~&d$YTT z3!CWS%RD_H!z*a3p%<>%cX={OY<<=^j!E_G#}6_<!I|gt!u&1;``rhKpl+A`jFDiR z{gnitj=>5_#%Lc8*6n)&Q+LPEPe+@@VB46)W;54ktswq3DMBR4*pc+4tt^XpzJ1bt zWV77zu^$RwP4OKqHE{dY&r|AG&3!)dImtNBg5ws?Osu@v*=`C~M8s2K8o`uAhSQ(w zOrKiAiRUn5{C>Rul5tO^Td9NXEoit-u@g-D%fLyiO@N^)ZR@vS5AW+B0Vijw{X%YJ z5YVhg1EM!e(qsGf3DtJfOPd-URg#4sr6W*@#rpb;k-KI1NBq??&Trnp_LjYlWy(aU z4o3S%lXQiF$Y=p;VRq!BBClCy+<yE)w;OID-_m$eRDD^zU^$rPkt3hhqR$<X9M|!F zHh<|h8PK04UUD=gzx>n`#)!A^JQtoUd@@6D8Qa4MS7Gf&AW*^Y%^CAhnAld^tBGEk z$Et1Ba+Ttky@;v^k8%Pn>0o&EM;0m1d>ly&ATmW~^>kR!k@BlBmfzNxI036BDF`22 zG*$MqYldgXaO-M@Vv>Xr8})lqI3b$kh%-$<8sXpx{P5`gpTyW7mF4!^F9TAh(fs)f zn^G1NVsU)(;)+HhaT|Q15Q)Ve<<#~%Jt;?Q@7fz3r9%B29C4aHCcs{xH%xNEUzY~j z7K4rb*-Sq03d+uXBe3g@k65ZQfcZ`sZuzX2rj4XVNLz=^I`SW;(dhQ0E?ZF~;Y^ND z9eLtLzME5-eQyx=`P-I}XA=2)OX4TSR$BKg#E+L11?0IvFVp%Q6NQBi_mdo10M)Pj z4(_ZB$lYV~0?<~KUb(RKX|#Hvql<m>*q|RH8ZOZ*&vzvA`WO~_g?dQxto~e`g6jU8 zmWre;K?vV(1k#Qf!xM8;%IckhgC(ZFEp98>VQ-0BD$yk7m@aWuSL^Hlg#80*2mKk1 zDdaj=qI3=duHYd2$qL<_;D6g#knHnk@`{&&Z}*yI|FoM2hU6KOGS~)#7Ywy_nzs&) z?_LN`abyouLu$5;)u<GgrKlYUTC6=H33m1p9Q(``zGdordoP$y)^?2ZYzmnlx#cR8 zfBj4wd1A(TorD5@nH%;CR;NnDy-d77zErn{LDC<Z2`w-Rk+jff8PvTb=YNuSvA%r3 z91F{_Ojy8r{sVeJ^CA~iz=|!$9~Ov{6p~N`q(lkwCK1)gEYM?<(p+4;)LXS+z0GE$ zj`euE6#U%i%51&<(9iVWR|xaJK`%N4&#HC6%y7cr{Wujvq%rU>U_@x6Z_v&Zshnu0 zgxIt-)n{zX*A|G>Ei_>=gQc%bw6*bZ-w)OkiX_RdjShCk=Waw2%gz^i_VG*lvz9Y% zB)Xi{FCq4Ig%6t%F3pchA^Q^3=mw(z{;&I5>32n5Fr}zG|3HsHN|FjtBvu?M?i7Y+ zrQZ(YnhprnkxeG>nnxM>$jK`uI=iJmC0<dR_*R+&k!kG$9WV5UHyQM!n3s@tQ<)~j zkst4H!k4$ssfbH&_$AIpe&)s=%)Q;0f5oXe%H-(vIu9BWuHh{srXT!_QLPhgN}vkG zg`^8dpqxHWYCXwy`<0Z33r`3eR!awGj&SetGi4Z5e8sOylvHGVt<U`B<Er_D%3Ie{ zAyr6?#M>KAjntm)xIA2eXt_tyJ}<Af_iX#v5(kq%xGZ+BJ{ut~l8#x*wEfn<`nq#O zB$*@PnbrB+3a^d`h2v;Ymj$VbW0uz9BW^4VE=!<iom<~;zc7**gKF}LW$QK$a6W8v z@)P}WDVNQ~zS`=-{8^Qbvct~H*<|#BFdaY+;~aG0A~r&f_}vzuygY4v8h)4#M5~oc z2G0~%g!8jiwm|O1RY8m7lUt_`C(ostV@W`S0PEK1Cum^(W{?BxG(p}S-INN@_q>~$ z(+`*QAx=@!xl0y2Pcj^W^qzLfg#FM(uk<S<J4h2LB;81#_;@YIhml(A#d(CR-DDB* z_qur0j~%m)ThNN*<^z%+Z2e;f52Jv1!YcJdIAkSBNQrWX*m%ydLS?nt@^TTomt4Hi zUmaZNstcPEX?EUFv#^uviyHz(7(P?f(<-(-E7Oric$fhPtIGy0xY$BUi^tfy6f--_ z_Ox{r!z<?PA)P*s4W7+@#=)vs6zk=};U~<}ub&Hm+>pX&pTcgOT9bkRZzO{)<ea%4 zW-;7%ggwpl%pn>1w&~Pm-`2E}{G4j6{!Sp>SKRacdRFsWz$W5H&Zb}r88G4MeY|6b zD{NNCo72eKNOh?=tvk4s+db#=E*!ULckH~U2y4p9+Dos94e5*`eO&`oA6W_Nq@LL) z(@n9>M1#|-VZ5Zym{Y;$?quGH?H04`I>>-^_IfP_jVL(fUJ^KtVC%oX=gYjp7&czW zhcUo+f)LTW1q^9jx@?%xT8jR}*Li#SMh{qdSy81*MqZlq&uo*M^Kb4Jam&Pf9c=~a zl>P6m1^>1BnSa&!DfGDfe7KNC1;qndR;wr09KI||sj`RWIs}~EBcK6gO;)YL%yhY; zZS{ysoFV?6(Iwh91Ur!1-1$IfV`?ER*oD6L_-E6Xv3-on%&f*<>2oIjr)#pN5criG zrsi>;du~>@gJGFMJw_q|>V=KE-Ju43e}i7BDlj@Yc;&*$A(!L>Ms8c7B<szagVgKL z8?rlZ-$b;i%gU|D0GN20RjSPM@50o3S}(#xJEPt0J-0J<yu_yWiyPC-vV|p~=mzW9 z1{0a*;>m1~OLFUPGxhj(POgTHjzgMXg3F@=7`4?V?>CM8tZyU<Rl;E5PCkx9Egf;1 zvBrmw#FLZL<)6+!e3GswC{#t{#Ob$?=!BE_%VU>AUkbE`-Yp~uls4p&m&LfhEY65N zf~tNuygjIuu9DcGU9E-2QssOA&h7$IRi|Dpebp?vW-BLGZp@@|*%S^fx7XC}FMY3k ze|)ZM_vhQ1Y;l|~=xG;i{$sK)1?ce9f&EW7qrKIZ4iox(bSyQidId5YXi>(_*d<RW zOT`tZUpP*f;C@r268Y{)#Kk;v7|}HDP?zl%vCv>bOR9P&OrV*F33@R<9O6Nn)$px( z)BALDyyw#T=97B(`(_|Xe3_SW9Vg6yV7*NVDO-e^LE7l!2IIiz{nblzJk9eQ=+Dg= ziY@d*=JH&Z%+Ps)cRE%Xg2Ed|d!Q?PMr)O5W<4*??K)`V!oAvU_N~lqvxL*CA^)H6 zt%o$;IyuSScJw!#U;GepQSvPJoY9Ul5H4uCdMrM*C+`D4Et`g$#8~FA2V3$!w>s{0 zi^JaqcOdZAeED<r9?p95R;M7!opRRrqS<a>8`A$N&QR;>hP|m2*;)H@%QN+xzr+1O z-`l^+Ty~0hMwM9=Hmpk>kbl|JQi$VAbBpbn<s*)*zvN;Uzm07lhclgVxQ5BkAAP*h z;cpfm|2*ae@u=#~=dAiv;TeV57ZEI%-GK3B<M%+GARt}9F2z}GUJL853@+Kf5M#A$ zm7ngCc#Dyr7E}n_U5zb0ri_SOVeZYBET!-uh+jyEi+8}8zKO2s=l0*UeKXk#H=EGu zeZQlKeJP10pa{ZiW_u_AJBdJ=0F07uooka#g7CZ3=qh`my~!sta5!~0>~pUux}j0x ztNXIgo8&nE6s*4Inj2^XT{v&Ks*|Q==}VCbX;1i5jF&1jpF#b(<fF>zMUD$z#s@)$ zq6}UoGD_2nb5MbJFbXHCz|ioOdSDwoc3%MoS204WzyfOg%%%ye6K)I=^-;3(>&78L z>;wGQve;vuM_s%Jx=`A(TSHWv)x!|~QY;_6aafu6f&*<%^5a6wq>T|Z8;r*L96B<q zw0aSM3jQ>#96{4S2O6Ky^-zdv<D^L1U+>BuHzIE1$@{$-s^1T^OZ&0Pi{E$mO!_i> z+&%I_c#nT$=ssL5`_~@>8Px6{T%Il)QvQ5E%|eHhVgl3Hw7J?Ck~o8_v6+!l&-ga7 zK1e-uz$G_Y|GiLzhPp|Y6{8@>F~7~`#h;%)QgfCvl3n>}3Fuh5Llp9!Ul}tM8<fdB zko*Ie5gj*Zki4d+wQqcu7Vc3aiVd+1r*&Kef7^Qg^=MgaT0uf-tFfA_lql62WQ9xT z`gDPds)aVp2DGWAexh|MNlZ(s0lY$<{4u=Q>Xtb(JPrR=R0kIN1#E;CL2WNdL5(e+ zYptS{uJ%nt=mpYH%K+(rXN8Annzj?pl(0r?C>Ajx8MXfy(`u~Au9Od}rfF0s`x~px zKLu-L86@$T@n8EEVVz8VM&WN`Zi(B|VuKUF7!&xK@byR23JUVIs&5UI>;dARtch<I z=yTT6eNYUyfaisMp!4)7j1f{I<x-CN9Jn%ep8nq5`*m+>4_}Y@jMm+pT(UNh!7iEb ze&et%3NlNN@J-;N7U&ah^oZU+WMxJO^rp{4gg7U(96FSy{O4<2JF@>0e6WWyCMxA$ znf(`5)_ouQd$FnM(JBa^e_LjzqyTP4Aw?2hD@v^p2jJi9ZLc-e=nD+FP1Rn_`e}b# zw&ZgPmx0`Ew)??;!7kpO91$At!aeq*C+56z%_*W-MzBi{JB4Kri^Y42!sv8$-Ezf! zXr6_B3eUeV^!sd&7sRi4DoW%=B}kA=QgcE)K8<>mLJpQ}yjotXWViW`s+NEo%ot>c zCI5(VRK3Z0DTt<ehfj+TW;%7!&fjZ-<CVf)1b5lJ)O6>KlPG7SEA0qXF<2nMOXY z3>Nf(0D__7)29!h$0WzyA>y5Z?3WMoYA8UKLQ07Njso^4JpBDI4kn{qrYZ22;FjV; zc#Rrn!W+)|;q;Fdsa&ki-ghwWEOV0zqf||)d*$OvbbFfn?AGA*K1i_E^Im@CH?J=T z)}TQblc?&hxMt|dQIc1sdQ7=jpf=g<tDEJYXAkqhEPp1p5#eLI8xR7|Re_2~+`I4T z!)!*}QLnp-R;h);=RH<q2jdAyAPe#0Kq&*((P8_Jc{30@LWffA#}e8ge{SsYke|!M zSX#Tgxy5mdBos4%)+r&R_@26M94aPEjRL`i(?bGDHlBaAXCqK>iN;9wCt@Z-s0*DU z?-O#OsD@}0XwP(4O(Q*ssKi57o=uy;^5<*wlaENQ%~gId+Vi5ieqro~ls$=zk(^y3 zI_yb%vFG^oQOY4zp73rX@xGv{Hw>6h-g%K*ox4K*?jSkMX`_y!id=_1^0~Le6LWl* zFX>Is8i)b(3v=(}%BeDmJ_*BDk=UfkOx@k4)>L+@%xb$WwrTPAwk6w$>ZF(AV(Czz z(&)yX$8~{sjP<{K=}quy^FgZyufXUKL60tWiL&_8!H>2!TSrG0Y{q3`#ic3VEhYwX z0>5qYJ$r9-S&s7P%Ml2=e9Az1Twze%8-Tm#`R6Rel%(^aD%dv)c7Jdea9g~keeHdT ztO-w7j2}LJv*!(rCRTQT6Z3j>WLijZksrL+9ITcc9Ib4IYPSKBY3Ru8J91);w|IUH z*dDm+$NJ(q?^99Du`wm<&i8gN)3$#m8FYs*Uk6g=*@hrxOFJ%RGlvA38AxB_!u*`- z?xXp>pW#)_o@P!u=AU>X6m=w!)Rb0IV-I{)I9Zn)$sUZ`Y7)F|mdniB&D+fR4W{s+ zRq37UbQzVap~Ll9rZT;*({?NBa=?7+K#@~PP<eaE?92yiN>n|Z{z%fP5^4yzBmxb} zZ@SmYTRTGiHgzjO38&Pg2jWC10C_3`MDoGJQIF1rsG(}PKVY3@sV}Vs7YPBEGD5!I zRsK4@n_)PeMCke{Ppv4zEvM_I8`&0?{0ZTYaCwTE41m$W8HZpMl)NV$wlGoq0=KO4 z!WSDqcn<L2Y>!=z9{mFXC|h-5Afy<S+4zAD$3Gzb8Pyu~KFdn-32J$phtaz^)8qTM zU7o-d8JxiIakP(jL~uduiSQtVv^ThB(S*+zS=i#Asl+of3mZ%Z2;4mh-8JPLQNXIo zCO_*JcYm|6VEe9=Vb52MjkWlH;cfoIc>cdq+5NAs3N%=gk#4Hx@#2%J?@pyrrw~dF z-h|VEBo|1Y<$d|IIpc4xjn%?gkyLbWvQg3!a>|z}N4|a^SwXKEKw4YVjc+PUdbylQ zDj38Iy(8w~zSOq5(dCrQ$>*cC<pM4Xg|uEZ$hs}?B-o`TSAwH&HQ1<40|f5SMEVjt zGVyQ*O4Wdl^mPX;0(wGi`K6hd^(&F?V}%2wdrQmY;czor(e5-38&Z=fpjEo;n^Z!b zLfr2&O>)qp%&uA?AK~|T9PCfEDJZ<Q@;5Ab<@Whx!HrW1iAn3F=6tT^8p(e^I*V@= zyQMNU-Jd9dUdHdXkOtSvQ!y^9RilD-4$GUJ>{{Re)F`}ik>Y~|Ypgrm6gwY`8`w5J z-krKnK43&c9%b+Z=_?!P350<u???uBDZQZ;4wNIuv~G)Y^>K{0=eb}r@w~D~uSM7; z_K@(97f3qk%)kc;;J{a)&%M|AEbfV15z0K#4;#bAC$lE4oaiqYOd&1tx=cgGUci=h z*0`ai!S?M(sOXU{c5<8jOSRt!SG}^v@D0P_dx@LWoVO_pCk9xG6z+j~I6tb%lpWYS z#1+a%Kll3xNouobm?d90Fw78m%9K90Y^-Q~_nq270UfuVY6th}eM%z!%YDb#zm9#j zy0K(S{+7PbFgV9nE=!iceXM(<AlhQ%<_ER@r#>V7eX&dj!S^2|wk}0TP3uBf({j!= zXH@rDyo>-<zGYvS;MiJAeH(GTFiy3~xE*R;)|$O>PV|yh{kGh0BM>@<7YO(VWC)i2 zz>-X!pyaQy#uaEa&LDXM%ICIif_e%Cc6fp&AUr|9TEt@5bzH^IgtJZFD*F{%TI_O7 zlP}e#?gh*vlKku%@2fCg#0eh!>lI-aBaIv7uwHh6%vijzHAT#hFZNVHxtu<Ny<bHh zX++%$I+>z6=<HTtKmdYp<c&t+{(vx>=iev*1MHJy!5{ce`@1o3qLZmKI|1_=rF`i< z#%Dg`=^g&JeR_ftvjpQ4eM)D~OSKl<aMhXQz5(UY2g*z^fwy-I(pFadQe1iXlTp>8 z9&rm-gX}!KqQSQgz9!4T##$N%2Jq>aL}FS`E?t#WR2c=hpW}*auxHtSVG@rYw053o zw|~#D!8B@DM7sK?2cl!ynrF;dsVUK<-)rk-rJp>7s3{o*SChMRm|A8s``TnE?kh$l z<~0ZV+mqW9UY5@<!r0~#B+f@=%ppETp5)$(pw0@U7iD)@l;qJzMUvbx9mm`xUGHAz zOYUKxgXHj}xK!RLJ!_CwpkY>d#eeqw#h_@!lfr!K+n^Ufx0}!*>uJ*&sUx(x!gXq( z6D#M)uJtsuTt2@PjeS2OB!w^^Ga@UG4=O>d$;>CU;_MAnYzY?zfk*_eeXOPdQEr7> zHAFTgOfU#n+qh<Rxa1zrvVfg;v>j;dJ<FjhL|{|;qb><uik)~jJDiwnxRig5`te{V zUzi4kW#60v$E~E*;D?ik6)t#LEX(EGo#eMyHnFufmR?o5MdNG4RkwkN9*}tIemwfE zi+cI|0%dsc#p0<QF)jJjxB@Y;17OdS79LJ2?A5@)D0zaTabtTbAJ5FSkIJ%(h975C zzb>Q?iU`a0lS2C0mVrctkC^AA3GMqw78K((T~74#&F02CxZslWA9Am47fN2(8E{6j zrQ-DiGp4)W(m6)}ki|9BE1l~kyOm2w=-NLZA?q|2${H^4qYsvl6ZhDvq4mpjNvdA& z1EZST$T~ay4Ak4|gexlEcRvXJJ|A=BM4c-l9tsExP^umg!&8xu@%gmRru;)0CiM$u z24)Vq>e#<x8?`ayZ)sXMm)AcDuQc@aZgfT!@?+pcTcAX%N-lg=Zb3Z%Ld>z5c27hb z+r|)!A?bSAM{EWc3{<-;a)uDK5~4<yiK2WU>bkKG)v&Q%dw>KqVJe7-67(=^A^Nu= z9TWhwv-z;S5o&MUw%zy+V7Nc~6oQDKcWxiPB&eQJavcQS1~y|IigTX)juD427rfx! zmrA5jID=PE5Y`~>14$^I0U}#b^Ts^5_wMJ<Z)q2lL@s(r|HN!w0!<P-Q%?gvZ31Os zm^h_@F&N|KLE_o(vQ(d$#rjV|`Q>`H)ohz4J+iSkGrzTb&gFU`Rxp$lbK5*Zr{llF z$m%~&!T48)TURbqB!JFUn0w1+KYsVm8T9~vE>!(5UA-GTxU6*Z39Fnh#^arkN>((F z&@=gu)^A_4#NQ(7Mqy-cv=IlR4^4!eoS>zNaw?RXg<XrTnlH_dZXFacb&H);TqaPf zP(_ykW#`pm0CGcom`3#LBI^jwEQwDYCc{~6?zthq-qO#deI;Iczmhhxn_BSN1zX{$ za6135V6gv6Oc*0N-n0`v4HJvjL#M#@VULHKTVGx5GAc67zDdW&8!KW)3t&@El_@YB zbGQY`G3Hzt0lhc#XG;iXYd(q23xU?$*7p-rIT(msmzb-UH<B~j_PoG|is@uPVPL#{ zRV#5}T%Sou+6OkLZQ#c3M_DGx%|FX?6$`KR`rg>mW7TEo;B86X*SV_R8_p=qwXG;% zJL0=Z{s)BRWqUg#b|ptZYtR(Tt(Gn36*)$w$3xJfUbUsd{i!EfMEht-rtw6GPCA+H zzVUn|*ZkITk(ym8U1;&6>|_$?+srv5@(M{lNlO*48?($j?H@ZOIUQFR@&P;QtxziG z)_Rc7aQwsd#e|C=uXkpq9&Zyo4j`WH^8$)CHr3JiWqyF<UNTU(^>=AG6Rvtz;`QTO zy`3_j7*)yZh-%Qkc_$f#M2W(Y7)OOd{3xBL%~YrJ=gFXL4S#ho**K*mw8QoBNu`?P z;Ym%?3l>_CT@&$Fe04zZpA#_s!<K3J8mEQ=vg{M%L>NxqIktq_Pn1twrUYl)3>x~t zR3nRv?=@kxY@a>UjW6JvY%)GZ$@`#SDtQPWsodQGpOTDwdfz86J1HxPw<fB4cy`fh zTdodv69Pwj|Aw^?3%lE5Q8Babk+d_hP`Q`)Pv6_fUgcRkdh}dq<qza+Jc1d;P0{Vq z(Hi?j985)mOAyHht*(&6Gn)a~cF282S1{M7uYHO%uD*Jr5lVm3q6;r#?m=-U!fZ%c zVpus0cWp+2wKw=9_IvK_Yp-OmvLAU?FFJz^&{X{4{?DlIuXQ=lVorjvr@S9c&&n%o zw`V)t^BF@w{bg17+L|N#dqg;sWk^7-!~9uFF^K!nF;15msgAOvl=pVBBCnV3ST-)% z0jvfa6AbU;kr!WQm9;7&=Sw|{l$CD!@$>u`kDvvFYYK78Y_osh+!RyXQ5@PH?vTDQ z-2~P&YH4np68BKPZYt*~WNx3#@iU{Zpe*9rrY>y<3&K`Er?=xWkjJtlcE5nkeBV}p znQmrzX%FXy@Znd{D|_OqZz=ZTjrm0pGkT+PPkx@Xpo*0(4RCDM49Zvj4_^%-<EDZX zd$qImJ3ZwasW&G5TnYCC0fgkp0+O4e?(xa%?|Na6Bi*e^M1(!F&4D)Ew8s?fkFW`O zarV8Ok)ah#{;_7G9v1-^p{zt?=Ym(_!2p5jf}rF%O;dYaTCasM>^Xz!uaXz~V%W{b zutRAY`s4VK5s+>2YGL<Vty_NhIURP2y!MNO-zWON9-CYkvmXiieSWsVz7Nq2k^1p) zKfm=jRgaYwz)D=lT~;-AWI&1$9g<mE-Br%awRQE-&9*ycr8JxgYZt7@8SMhHQTj@H z+`AoArwo(+$)l`WE3_DTXz4aj(YL(;Ru=2hFa4k2e7~BZBs6<VS{Wvi^0WE+2*^GS z7&-PBB3TuBFNg6^?r8~XCQ1+wHnV#lq+`0}H=5?xIX7fF?w_sGSB!w}u=WVkS??5Z zOdYl>?2c%55f{s1KWvfiz)I&wRoum1)agfaR4bb@AQ!mhC68Y$M9@S3`#CuNPbxeB zdbm&ibC%h`XhT8a3i~>ffaC=6@eUEhETP$P&L(RxYlD*cC|7s+jt{BXB}-u4<<;tH z6o~!<u!Ivj=R6<R6%LaG+Mi!ZK*)#4xHm=^$1ERGShtAvZZlpA(@%5jd*0e(!uM4m Y%-cgGg6gp5znwVnKj8(Z`e*)s0Q?p$$N&HU literal 0 HcmV?d00001 diff --git a/gorgone/docs/modules/core/proxy.md b/gorgone/docs/modules/core/proxy.md index 5891cee4fb3..5c1a672ab00 100644 --- a/gorgone/docs/modules/core/proxy.md +++ b/gorgone/docs/modules/core/proxy.md @@ -12,14 +12,34 @@ A SSH client library make routing to non-gorgoned nodes possible. ## Configuration -| Directive | Description | Default value | -| :------------------- | :------------------------------------------------------------------ | :------------ | -| pool | Number of childs to instantiate to process events | `5` | -| synchistory_time | Time in seconds between two logs synchronisation | `60` | -| synchistory_timeout | Time in seconds before logs synchronisation is considered timed out | `30` | +| Directive | Description | Default value | +|:----------|:----------------------------------------------------|:--------------| +| pool | Number of children to instantiate to process events | `5` | + +| synchistory_time | Time in seconds between two log synchronisations | `60` | + +| synchistory_timeout | Time in seconds before log synchronisation is considered timed out | `30` | + | ping | Time in seconds between two node pings | `60` | | pong_discard_timeout | Time in seconds before a node is considered dead | `300` | +This part of the configuration is only used if some poller must connect with the pullwss module. + + + +| Directive | Description | Default value | +|:--------------|:-----------------------------------------------------------------------------------------------|:--------------| +| httpserver | Array containing all the configuration below for a pullwss connection | no value. | +| enable | Boolean if HTTP server should be enabled | `false` | +| ssl | Should connection be made over TLS/SSL or not | `false` | +| ssl_cert_file | Path to a SSL certificate file. required if ssl: true | | +| ssl_key_file | Path to a SSL key file associated to the certificate already configured. required if ssl: true | | +| passphrase | May be an optional passphrase for the SSL key. | | +| token | Allow to authenticate node. It is required to enable the HTTP server. | | +| address | Address to listen to. It can be 0.0.0.0 to listen on all IPv4 addresses. | | +| port | TCP port to listen to. | | + + #### Example ```yaml @@ -31,12 +51,19 @@ synchistory_time: 60 synchistory_timeout: 30 ping: 60 pong_discard_timeout: 300 +httpserver: # this is used only if you want to configure pullwss nodes. to make it work you have to add the register module and configure a configuration file for it. + enable: true + ssl: true + ssl_cert_file: /etc/centreon-gorgone/keys/public.pem + ssl_key_file: /etc/centreon-gorgone/keys/private.pem + token: secure_token + address: "0.0.0.0" ``` ## Events | Event | Description | -| :-------------- | :----------------------------------------------------------------------------- | +|:----------------|:-------------------------------------------------------------------------------| | PROXYREADY | Internal event to notify the core | | REMOTECOPY | Copy files or directories from the server running the daemon to another server | | SETLOGS | Internal event to insert logs into the database | @@ -53,20 +80,20 @@ pong_discard_timeout: 300 ### Copy files or directory to remote server | Endpoint | Method | -| :------------------------- | :----- | +|:---------------------------|:-------| | /api/core/proxy/remotecopy | `POST` | #### Headers | Header | Value | -| :----------- | :--------------- | +|:-------------|:-----------------| | Accept | application/json | | Content-Type | application/json | #### Body | Key | Value | -| :---------- | :------------------------------------------------ | +|:------------|:--------------------------------------------------| | source | Path of the source file or directory | | destination | Path of the destination file or directory | | cache_dir | Path to the cache directory for archiving purpose | diff --git a/gorgone/docs/modules/core/pull.md b/gorgone/docs/modules/core/pull.md index d62357089d1..8ec62829d27 100644 --- a/gorgone/docs/modules/core/pull.md +++ b/gorgone/docs/modules/core/pull.md @@ -14,6 +14,9 @@ No specific configuration. name: pull package: "gorgone::modules::core::pull::hooks" enable: true +target_type: tcp +target_path: 10.30.2.203:5556 +ping: 1 ``` ## Events diff --git a/gorgone/docs/modules/core/pullwss.md b/gorgone/docs/modules/core/pullwss.md new file mode 100644 index 00000000000..5bcdf3f1900 --- /dev/null +++ b/gorgone/docs/modules/core/pullwss.md @@ -0,0 +1,41 @@ +# Pullwss + +## Description + +This module should be used on remote nodes where the connection has to be HTTP/HTTPS and must be opened from the node to the Central Gorgone. + +This module requires proxy and register module to be configured on the central Gorgone. +The register Module will allow Gorgone to keep the state of every poller, and find out the connection mode. +The proxy module has to bind to a tcp port for the pullwss module to connect to. + +## Configuration + +| Directive | Description | Default value | +|:----------|:--------------------------------------------------|:--------------| +| ssl | should the connection be made over TLS/SSL or not | `false` | +| address | IP address to connect to | | +| port | TCP port to connect to | | +| token | token to authenticate to the central gorgone | | +| proxy | HTTP(S) proxy to access central gorgone | | + +### Example + +```yaml +name: pullwss +package: "gorgone::modules::core::pullwss::hooks" +enable: true +ssl: true +port: 8086 +token: "1234" +address: 192.168.56.105 +``` + +## Events + +| Event | Description | +|:---------------|:--------------------------------------------------------| +| PULLWSSREADY | Internal event to notify the core this module is ready. | + +## API + +No API endpoints. diff --git a/gorgone/docs/modules/core/register.md b/gorgone/docs/modules/core/register.md index e1db2ebe8e0..6cc3bac81fe 100644 --- a/gorgone/docs/modules/core/register.md +++ b/gorgone/docs/modules/core/register.md @@ -11,7 +11,7 @@ Nodes are either servers running Gorgone daemon or simple equipment with SSH ser There is no specific configuration in the Gorgone daemon configuration file, only a directive to set a path to a dedicated configuration file. | Directive | Description | Default value | -| :----------- | :------------------------------------------- | :------------ | +|:-------------|:---------------------------------------------|:--------------| | config\_file | Path to the configuration file listing nodes | | #### Example @@ -28,9 +28,9 @@ Nodes are listed in a separate configuration file in a `nodes` table as below: ##### Using ZMQ (Gorgone running on node) | Directive | Description | -| :-------------- | :------------------------------------------------------------------------- | +|:----------------|:---------------------------------------------------------------------------| | id | Unique identifier of the node (can be Poller’s ID if using prevail option) | -| type | Way for the daemon to connect to the node (push\_zmq) | +| type | Way for the daemon to connect to the node (push\_zmq, pull, wss) | | address | IP address of the node | | port | Port to connect to on the node | | server\_pubkey | Server public key (Default: ask the server pubkey when it connects) | @@ -59,7 +59,7 @@ nodes: ##### Using SSH | Directive | Description | -| :----------------------- | :------------------------------------------------------------------------------------------------ | +|:-------------------------|:--------------------------------------------------------------------------------------------------| | id | Unique identifier of the node (can be Poller’s ID if using prevail option) | | type | Way for the daemon to connect to the node (push\_ssh) | | address | IP address of the node | diff --git a/gorgone/docs/poller_pullwss_configuration.md b/gorgone/docs/poller_pullwss_configuration.md new file mode 100644 index 00000000000..da4d1cbeca6 --- /dev/null +++ b/gorgone/docs/poller_pullwss_configuration.md @@ -0,0 +1,114 @@ +# Architecture + +We are showing how to configure gorgone to manage that architecture: + +```text + +Central server <------- Distant Poller +``` +unlike for the pull module, the communication is entirely done on the HTTP(S) websocket. +In our case, we have the following configuration (you need to adapt it to your configuration). + +* Central server: + * address: 10.30.2.203 +* Distant Poller: + * id: 6 (configured in the Centreon interface as **zmq**. You get it in the Centreon interface) + * address: 10.30.2.179 + * rsa public key thumbprint: nJSH9nZN2ugQeksHif7Jtv19RQA58yjxfX-Cpnhx09s + +# Distant Poller + +## Installation + +The Distant Poller is already installed with Gorgone. + +## Configuration + +We configure the file **/etc/centreon-gorgone/config.d/40-gorgoned.yaml**: + +```yaml +name: distant-server +description: Configuration for distant server +gorgone: + gorgonecore: + id: 6 + privkey: "/var/lib/centreon-gorgone/.keys/rsakey.priv.pem" + pubkey: "/var/lib/centreon-gorgone/.keys/rsakey.pub.pem" + + modules: + - name: action + package: gorgone::modules::core::action::hooks + enable: true + command_timeout: 30 + whitelist_cmds: true + allowed_cmds: + - ^sudo\s+(/bin/)?systemctl\s+(reload|restart)\s+(centengine|centreontrapd|cbd)\s*$ + - ^(sudo\s+)?(/usr/bin/)?service\s+(centengine|centreontrapd|cbd|cbd-sql)\s+(reload|restart)\s*$ + - ^/usr/sbin/centenginestats\s+-c\s+/etc/centreon-engine/centengine\.cfg\s*$ + - ^cat\s+/var/lib/centreon-engine/[a-zA-Z0-9\-]+-stats\.json\s*$ + - ^/usr/lib/centreon/plugins/.*$ + - ^/bin/perl /usr/share/centreon/bin/anomaly_detection --seasonality >> /var/log/centreon/anomaly_detection\.log 2>&1\s*$ + - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ + - ^centreon + - ^mkdir + - ^/usr/share/centreon/www/modules/centreon-autodiscovery-server/script/run_save_discovered_host + - ^/usr/share/centreon/bin/centreon -u \"centreon-gorgone\" -p \S+ -w -o CentreonWorker -a processQueue$ + + - name: engine + package: gorgone::modules::centreon::engine::hooks + enable: true + command_file: "/var/lib/centreon-engine/rw/centengine.cmd" + + - name: pullwss + package: "gorgone::modules::core::pullwss::hooks" + enable: true + ssl: true + port: 443 + token: "1234" + address: 10.30.2.203 + ping: 1 +``` + +# Central server + +## Installation + +The Central server is already installed and Gorgone too. + +## Configuration + +We configure the file **/etc/centreon-gorgone/config.d/40-gorgoned.yaml**: + +```yaml + +... +gorgone: + ... + modules: + ... + - name: proxy + package: "gorgone::modules::core::proxy::hooks" + enable: true + httpserver: + enable: true + ssl: true + ssl_cert_file: /etc/centreon-gorgone/keys/certificate.crt + ssl_key_file: /etc/centreon-gorgone/keys/private.key + token: "1234" + address: "0.0.0.0" + port: 443 + - name: register + package: "gorgone::modules::core::register::hooks" + enable: true + config_file: /etc/centreon-gorgone/nodes-register-override.yml + ... +``` + +We create the file **/etc/centreon-gorgone/nodes-register-override.yml**: + +```yaml +nodes: + - id: 6 + type: pullwss + prevail: 1 +``` From 88f20d0f4708cd4a8b9555d5f99ad5013fa02eb6 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Mon, 24 Jun 2024 14:56:24 +0200 Subject: [PATCH 843/948] remove paths from trigger --- .github/workflows/gorgone.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 9d90dc85662..2d6db94c8ba 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -12,10 +12,10 @@ on: - synchronize - reopened - ready_for_review - paths: - - "centreon-gorgone/**" - - "!centreon-gorgone/veracode.json" - - "!centreon-gorgone/.veracode-exclusions" + # paths: + # - "centreon-gorgone/**" + # - "!centreon-gorgone/veracode.json" + # - "!centreon-gorgone/.veracode-exclusions" push: branches: - develop From 02bde5588065ecc1482335275209008588393371 Mon Sep 17 00:00:00 2001 From: May <110405507+mushroomempires@users.noreply.github.com> Date: Mon, 24 Jun 2024 15:01:27 +0200 Subject: [PATCH 844/948] fix(packaging): redirect centreon-gorgoned-selinux package to centreon-gorgoned-selinux (#4452) --- gorgone/packaging/centreon-gorgone-selinux.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gorgone/packaging/centreon-gorgone-selinux.yaml b/gorgone/packaging/centreon-gorgone-selinux.yaml index 98d1975e1e8..42932221d9f 100644 --- a/gorgone/packaging/centreon-gorgone-selinux.yaml +++ b/gorgone/packaging/centreon-gorgone-selinux.yaml @@ -19,10 +19,12 @@ depends: - centreon-common-selinux replaces: - centreon-gorgone-selinux-debuginfo + - centreon-gorgoned-selinux conflicts: - centreon-gorgone-selinux-debuginfo provides: - centreon-gorgone-selinux-debuginfo + - centreon-gorgoned-selinux contents: - src: "../selinux/centreon-gorgoned.pp" From 3c58b58d3626939be5967de06c1d31da807a9327 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Tue, 25 Jun 2024 08:50:42 +0200 Subject: [PATCH 845/948] add outputs to get-version workflow --- .github/workflows/get-version.yml | 40 +++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/.github/workflows/get-version.yml b/.github/workflows/get-version.yml index d18322ecd67..c855b9fa493 100644 --- a/.github/workflows/get-version.yml +++ b/.github/workflows/get-version.yml @@ -1,6 +1,16 @@ on: workflow_call: + inputs: + version_file: + required: true + type: string outputs: + major_version: + description: "major version" + value: ${{ jobs.get-version.outputs.major_version }} + minor_version: + description: "minor version" + value: ${{ jobs.get-version.outputs.minor_version }} img_version: description: "docker image version (vcpkg checksum)" value: ${{ jobs.get-version.outputs.img_version }} @@ -33,6 +43,8 @@ jobs: get-version: runs-on: ubuntu-22.04 outputs: + major_version: ${{ steps.get_version.outputs.major_version }} + minor_version: ${{ steps.get_version.outputs.minor_version }} img_version: ${{ steps.get_version.outputs.img_version }} test_img_version: ${{ steps.get_version.outputs.test_img_version }} version: ${{ steps.get_version.outputs.version }} @@ -64,10 +76,34 @@ jobs: - id: get_version run: | set -x + + if [[ "${{ inputs.version_file }}" == */.version ]]; then + . .version + . ${{ inputs.version_file }} + VERSION="$MAJOR.$MINOR" + elif [[ "${{ inputs.version_file }}" == */CMakeLists.txt ]]; then + VERSION=$(awk '$1 ~ "COLLECT_MAJOR" {maj=substr($2, 1, length($2)-1)} $1 ~ "COLLECT_MINOR" {min=substr($2, 1, length($2)-1) ; print maj "." min}' CMakeLists.txt) + PATCH=$(awk '$1 ~ "COLLECT_PATCH" {print substr($2, 1, length($2) - 1)}' CMakeLists.txt) + else + echo "Unable to parse ${{ inputs.version_file }}" + exit 1 + fi + + echo "VERSION=$VERSION" + + if egrep '^[2-9][0-9]\.[0-9][0-9]\.[0-9]+' <<<"$VERSION" >/dev/null 2>&1 ; then + n=${VERSION//[!0-9]/ } + a=(${n//\./ }) + echo "major_version=${a[0]}.${a[1]}" >> $GITHUB_OUTPUT + MAJOR=${a[0]}.${a[1]} + echo "minor_version=${a[2]}" >> $GITHUB_OUTPUT + else + echo "Cannot parse version number from ${{ inputs.version_file }}" + exit 1 + fi + IMG_VERSION=$( cat `ls .github/docker/Dockerfile.centreon-collect-* | grep -v test` vcpkg.json | md5sum | awk '{print substr($1, 0, 8)}') TEST_IMG_VERSION=$(cat .github/docker/Dockerfile.centreon-collect-*-test .github/scripts/collect-prepare-test-robot.sh resources/*.sql | md5sum | cut -c1-8) - VERSION=$(awk '$1 ~ "COLLECT_MAJOR" {maj=substr($2, 1, length($2)-1)} $1 ~ "COLLECT_MINOR" {min=substr($2, 1, length($2)-1) ; print maj "." min}' CMakeLists.txt) - PATCH=$(awk '$1 ~ "COLLECT_PATCH" {print substr($2, 1, length($2) - 1)}' CMakeLists.txt) echo "img_version=$IMG_VERSION" >> $GITHUB_OUTPUT echo "test_img_version=$TEST_IMG_VERSION" >> $GITHUB_OUTPUT echo "version=$VERSION" >> $GITHUB_OUTPUT From 0c6339d54ce266b9fe5e86086247b92213d7e828 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Tue, 25 Jun 2024 09:01:21 +0200 Subject: [PATCH 846/948] fix gorgone workflow --- .github/actions/package/action.yml | 32 +++++++++++++++++++++++++--- .github/workflows/gorgone.yml | 34 +++++++++++++++--------------- 2 files changed, 46 insertions(+), 20 deletions(-) diff --git a/.github/actions/package/action.yml b/.github/actions/package/action.yml index b51c1ae496e..b83f9eff217 100644 --- a/.github/actions/package/action.yml +++ b/.github/actions/package/action.yml @@ -10,8 +10,11 @@ inputs: distrib: description: The package distrib required: true - version: - description: The package version + major_version: + description: The major version + required: false + minor_version: + description: The minor version required: false release: description: The package release number @@ -53,7 +56,8 @@ runs: RPM_GPG_SIGNING_KEY_ID: ${{ inputs.rpm_gpg_signing_key_id }} RPM_GPG_SIGNING_PASSPHRASE: ${{ inputs.rpm_gpg_signing_passphrase }} run: | - export VERSION="${{ inputs.version }}" + export MAJOR_VERSION="${{ inputs.major_version }}" + export VERSION="${{ inputs.major_version }}.${{ inputs.minor_version }}" export RELEASE="${{ inputs.release }}" export ARCH="${{ inputs.arch }}" @@ -68,6 +72,19 @@ runs: fi fi + MAJOR_LEFT=$( echo $MAJOR_VERSION | cut -d "." -f1 ) + MAJOR_RIGHT=$( echo $MAJOR_VERSION | cut -d "-" -f1 | cut -d "." -f2 ) + BUMP_MAJOR_RIGHT=$(( MAJOR_RIGHT_PART + 1 )) + if [ "$MAJOR_RIGHT" = "04" ]; then + BUMP_MAJOR_LEFT="$MAJOR_LEFT" + BUMP_MAJOR_RIGHT="10" + else + BUMP_MAJOR_LEFT=$(( $MAJOR_LEFT + 1 )) + BUMP_MAJOR_RIGHT="04" + fi + + export NEXT_MAJOR_VERSION="$BUMP_MAJOR_LEFT.$BUMP_MAJOR_RIGHT" + export RPM_SIGNING_KEY_FILE="$(pwd)/key.gpg" export RPM_SIGNING_KEY_ID="$RPM_GPG_SIGNING_KEY_ID" export NFPM_RPM_PASSPHRASE="$RPM_GPG_SIGNING_PASSPHRASE" @@ -91,3 +108,12 @@ runs: with: path: ./*.${{ inputs.package_extension }} key: ${{ inputs.cache_key }} + + # Update if condition to true to get packages as artifacts + - if: ${{ false }} + name: Upload package artifacts + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: packages-${{ inputs.distrib }} + path: ./*.${{ inputs.package_extension}} + retention-days: 1 diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 2d6db94c8ba..e6aa7f6a223 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -36,23 +36,23 @@ jobs: with: version_file: centreon-gorgone/.version - veracode-analysis: - needs: [get-version] - uses: ./.github/workflows/veracode-analysis.yml - with: - module_directory: centreon-gorgone - module_name: centreon-gorgone - major_version: ${{ needs.get-version.outputs.major_version }} - minor_version: ${{ needs.get-version.outputs.minor_version }} - stability: ${{ needs.get-version.outputs.stability }} - is_perl_project: true - secrets: - veracode_api_id: ${{ secrets.VERACODE_API_ID_GORG }} - veracode_api_key: ${{ secrets.VERACODE_API_KEY_GORG }} - veracode_srcclr_token: ${{ secrets.VERACODE_SRCCLR_TOKEN }} - jira_base_url: ${{ secrets.JIRA_BASE_URL }} - jira_user_email: ${{ secrets.XRAY_JIRA_USER_EMAIL }} - jira_api_token: ${{ secrets.XRAY_JIRA_TOKEN }} + # veracode-analysis: + # needs: [get-version] + # uses: ./.github/workflows/veracode-analysis.yml + # with: + # module_directory: centreon-gorgone + # module_name: centreon-gorgone + # major_version: ${{ needs.get-version.outputs.major_version }} + # minor_version: ${{ needs.get-version.outputs.minor_version }} + # stability: ${{ needs.get-version.outputs.stability }} + # is_perl_project: true + # secrets: + # veracode_api_id: ${{ secrets.VERACODE_API_ID_GORG }} + # veracode_api_key: ${{ secrets.VERACODE_API_KEY_GORG }} + # veracode_srcclr_token: ${{ secrets.VERACODE_SRCCLR_TOKEN }} + # jira_base_url: ${{ secrets.JIRA_BASE_URL }} + # jira_user_email: ${{ secrets.XRAY_JIRA_USER_EMAIL }} + # jira_api_token: ${{ secrets.XRAY_JIRA_TOKEN }} package: needs: [get-version] From e1d02d57e213e438b523fe82d681c193b0628417 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Tue, 25 Jun 2024 09:05:00 +0200 Subject: [PATCH 847/948] replace centreon-gorgone directory by gorgone --- .github/workflows/gorgone.yml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index e6aa7f6a223..216d92295fa 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -13,9 +13,9 @@ on: - reopened - ready_for_review # paths: - # - "centreon-gorgone/**" - # - "!centreon-gorgone/veracode.json" - # - "!centreon-gorgone/.veracode-exclusions" + # - "gorgone/**" + # - "!gorgone/veracode.json" + # - "!gorgone/.veracode-exclusions" push: branches: - develop @@ -23,24 +23,24 @@ on: - master - "[2-9][0-9].[0-9][0-9].x" paths: - - "centreon-gorgone/**" - - "!centreon-gorgone/veracode.json" - - "!centreon-gorgone/.veracode-exclusions" + - "gorgone/**" + - "!gorgone/veracode.json" + - "!gorgone/.veracode-exclusions" env: - base_directory: centreon-gorgone + base_directory: gorgone jobs: get-version: uses: ./.github/workflows/get-version.yml with: - version_file: centreon-gorgone/.version + version_file: gorgone/.version # veracode-analysis: # needs: [get-version] # uses: ./.github/workflows/veracode-analysis.yml # with: - # module_directory: centreon-gorgone + # module_directory: gorgone # module_name: centreon-gorgone # major_version: ${{ needs.get-version.outputs.major_version }} # minor_version: ${{ needs.get-version.outputs.minor_version }} @@ -106,20 +106,20 @@ jobs: - name: Generate selinux binaries if: ${{ matrix.package_extension == 'rpm' }} run: | - cd centreon-gorgone/selinux + cd gorgone/selinux sed -i "s/@VERSION@/${{ needs.get-version.outputs.major_version }}.${{ needs.get-version.outputs.minor_version }}/g" centreon-gorgoned.te make -f /usr/share/selinux/devel/Makefile shell: bash - name: Remove selinux packaging files on debian if: ${{ matrix.package_extension == 'deb' }} - run: rm -f centreon-gorgone/packaging/*-selinux.yaml + run: rm -f gorgone/packaging/*-selinux.yaml shell: bash - name: Package uses: ./.github/actions/package-nfpm with: - nfpm_file_pattern: "centreon-gorgone/packaging/*.yaml" + nfpm_file_pattern: "gorgone/packaging/*.yaml" distrib: ${{ matrix.distrib }} package_extension: ${{ matrix.package_extension }} major_version: ${{ needs.get-version.outputs.major_version }} @@ -196,7 +196,7 @@ jobs: mysql -h mariadb -u root -ppassword 'centreon-storage' < centreon/www/install/createTablesCentstorage.sql - name: Run tests - run: robot -v 'DBHOST:mariadb' -v 'DBNAME:centreon' -v 'DBUSER:centreon' centreon-gorgone/tests + run: robot -v 'DBHOST:mariadb' -v 'DBNAME:centreon' -v 'DBUSER:centreon' gorgone/tests - name: Upload gorgone and robot debug artifacts @@ -223,7 +223,7 @@ jobs: uses: ./.github/actions/release-sources with: bucket_directory: centreon-gorgone - module_directory: centreon-gorgone + module_directory: gorgone module_name: centreon-gorgone major_version: ${{ needs.get-version.outputs.major_version }} minor_version: ${{ needs.get-version.outputs.minor_version }} From 9cdc2748b163e18d55651e8cd542c79e4eaf49d6 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Tue, 25 Jun 2024 09:08:37 +0200 Subject: [PATCH 848/948] replace package-nfpm action by package --- .github/workflows/gorgone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 216d92295fa..96e11d57ec1 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -117,7 +117,7 @@ jobs: shell: bash - name: Package - uses: ./.github/actions/package-nfpm + uses: ./.github/actions/package with: nfpm_file_pattern: "gorgone/packaging/*.yaml" distrib: ${{ matrix.distrib }} From c90105686daf9a174cb84fa8826590a6a28728d4 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Tue, 25 Jun 2024 09:34:00 +0200 Subject: [PATCH 849/948] dynamic checkout of centreon repository --- .github/workflows/gorgone.yml | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 96e11d57ec1..dc7b5f356f9 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -165,8 +165,27 @@ jobs: MYSQL_ROOT_PASSWORD: password steps: + - name: Get linked branch of centreon repository + id: centreon_repo_linked_branch + run: | + CENTREON_REPO_LINKED_BRANCH=$(git ls-remote -h https://github.com/centreon/centreon.git | grep -E "refs/heads/dev-${{ needs.get-version.outputs.major_version }}\.x$" >/dev/null 2>&1 && echo "dev-${{ needs.get-version.outputs.major_version }}.x" || echo develop) + + GIT_BRANCH_EXISTS=$(git ls-remote -h https://github.com/centreon/centreon.git | grep -E "refs/heads/${{ github.head_ref || github.ref_name }}$" >/dev/null 2>&1 && echo yes || echo no) + if [[ "$GIT_BRANCH_EXISTS" == "yes" ]]; then + CENTREON_REPO_LINKED_BRANCH="${{ github.head_ref || github.ref_name }}" + fi + + echo "linked_branch=$CENTREON_REPO_LINKED_BRANCH" >> $GITHUB_OUTPUT + shell: bash + - name: Checkout sources - uses: actions/checkout@v4 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Checkout sources + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + ref: ${{ steps.centreon_repo_linked_branch.outputs.linked_branch }} + path: centreon-repo - name: get cached gorgone package uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 @@ -192,13 +211,12 @@ jobs: mysql -h mariadb -u root -ppassword -e "CREATE DATABASE \`centreon-storage\`" mysql -h mariadb -u root -ppassword -e "GRANT ALL PRIVILEGES ON centreon.* TO 'centreon'@'%'" mysql -h mariadb -u root -ppassword -e "GRANT ALL PRIVILEGES ON \`centreon-storage\`.* TO 'centreon'@'%'" - mysql -h mariadb -u root -ppassword 'centreon' < centreon/www/install/createTables.sql - mysql -h mariadb -u root -ppassword 'centreon-storage' < centreon/www/install/createTablesCentstorage.sql + mysql -h mariadb -u root -ppassword 'centreon' < centreon-repo/centreon/www/install/createTables.sql + mysql -h mariadb -u root -ppassword 'centreon-storage' < centreon-repo/centreon/www/install/createTablesCentstorage.sql - name: Run tests run: robot -v 'DBHOST:mariadb' -v 'DBNAME:centreon' -v 'DBUSER:centreon' gorgone/tests - - name: Upload gorgone and robot debug artifacts if: failure() uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 From a1782c1a1a252e474564cda5a26ce6966d8bb4b0 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Tue, 25 Jun 2024 09:40:04 +0200 Subject: [PATCH 850/948] fix checkout of centreon repo --- .github/workflows/gorgone.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index dc7b5f356f9..a18e04e28a0 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -184,8 +184,12 @@ jobs: - name: Checkout sources uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: + repository: centreon/centreon + path: centreon ref: ${{ steps.centreon_repo_linked_branch.outputs.linked_branch }} - path: centreon-repo + sparse-checkout: | + centreon/www/install/createTables.sql + centreon/www/install/createTablesCentstorage.sql - name: get cached gorgone package uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 @@ -211,8 +215,8 @@ jobs: mysql -h mariadb -u root -ppassword -e "CREATE DATABASE \`centreon-storage\`" mysql -h mariadb -u root -ppassword -e "GRANT ALL PRIVILEGES ON centreon.* TO 'centreon'@'%'" mysql -h mariadb -u root -ppassword -e "GRANT ALL PRIVILEGES ON \`centreon-storage\`.* TO 'centreon'@'%'" - mysql -h mariadb -u root -ppassword 'centreon' < centreon-repo/centreon/www/install/createTables.sql - mysql -h mariadb -u root -ppassword 'centreon-storage' < centreon-repo/centreon/www/install/createTablesCentstorage.sql + mysql -h mariadb -u root -ppassword 'centreon' < centreon/centreon/www/install/createTables.sql + mysql -h mariadb -u root -ppassword 'centreon-storage' < centreon/centreon/www/install/createTablesCentstorage.sql - name: Run tests run: robot -v 'DBHOST:mariadb' -v 'DBNAME:centreon' -v 'DBUSER:centreon' gorgone/tests From 67fc9aa61f8e3ecc1d376f89b32a1094f33b4d15 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Tue, 25 Jun 2024 10:06:21 +0200 Subject: [PATCH 851/948] add missing inputsgit status --- .github/actions/delivery/action.yml | 26 +++++++++++++------------- .github/workflows/centreon-collect.yml | 17 ++++++++++------- .github/workflows/docker-builder.yml | 2 ++ .github/workflows/get-version.yml | 6 ++---- .github/workflows/gorgone.yml | 2 +- .github/workflows/libzmq.yml | 12 ++++++------ .github/workflows/package-collect.yml | 10 +++++++--- .github/workflows/robot-nightly.yml | 13 ++++++++----- 8 files changed, 49 insertions(+), 39 deletions(-) diff --git a/.github/actions/delivery/action.yml b/.github/actions/delivery/action.yml index f762844e143..8cbca5c8073 100644 --- a/.github/actions/delivery/action.yml +++ b/.github/actions/delivery/action.yml @@ -7,7 +7,7 @@ inputs: distrib: description: "The distribution used for packaging" required: true - version: + major_version: description: "Centreon packaged major version" required: true cache_key: @@ -60,7 +60,7 @@ runs: FILES="*.${{ env.extfile }}" # DEBUG - echo "[DEBUG] - Version: ${{ inputs.version }}" + echo "[DEBUG] - Major version: ${{ inputs.major_version }}" echo "[DEBUG] - Distrib: ${{ inputs.distrib }}" echo "[DEBUG] - module_name: ${{ inputs.module_name }}" echo "[DEBUG] - release_cloud: ${{ inputs.release_cloud }}" @@ -68,7 +68,7 @@ runs: echo "[DEBUG] - stability: ${{ inputs.stability }}" # Make sure all required inputs are NOT empty - if [[ -z "${{ inputs.module_name }}" || -z "${{ inputs.distrib }}" || -z ${{ inputs.stability }} || -z ${{ inputs.version }} || -z ${{ inputs.release_cloud }} || -z ${{ inputs.release_type }} ]]; then + if [[ -z "${{ inputs.module_name }}" || -z "${{ inputs.distrib }}" || -z ${{ inputs.stability }} || -z ${{ inputs.major_version }} || -z ${{ inputs.release_cloud }} || -z ${{ inputs.release_type }} ]]; then echo "Some mandatory inputs are empty, please check the logs." exit 1 fi @@ -96,19 +96,19 @@ runs: if [[ ${{ inputs.release_cloud }} -eq 1 && ( ${{ inputs.release_type }} == "hotfix" || ${{ inputs.release_type }} == "release" ) ]]; then echo "[DEBUG] : Release cloud + ${{ inputs.release_type }}, using rpm-standard-internal." ROOT_REPO_PATHS="rpm-standard-internal" - UPLOAD_REPO_PATH="${{ inputs.version }}/${{ inputs.distrib }}/${{ inputs.stability }}-${{ inputs.release_type }}/$ARCH/${{ inputs.module_name }}/" + UPLOAD_REPO_PATH="${{ inputs.major_version }}/${{ inputs.distrib }}/${{ inputs.stability }}-${{ inputs.release_type }}/$ARCH/${{ inputs.module_name }}/" # CLOUD + NOT HOTFIX OR CLOUD + NOT RELEASE + REPO STANDARD INTERNAL elif [[ ${{ inputs.release_cloud }} -eq 1 && ( ${{ inputs.release_type }} != "hotfix" && ${{ inputs.release_type }} != "release" ) ]]; then echo "[DEBUG] : Release cloud + NOT ${{ inputs.release_type }}, using rpm-standard-internal." ROOT_REPO_PATHS="rpm-standard-internal" - UPLOAD_REPO_PATH="${{ inputs.version }}/${{ inputs.distrib }}/${{ inputs.stability }}-${{ inputs.release_type }}/$ARCH/${{ inputs.module_name }}/" + UPLOAD_REPO_PATH="${{ inputs.major_version }}/${{ inputs.distrib }}/${{ inputs.stability }}-${{ inputs.release_type }}/$ARCH/${{ inputs.module_name }}/" # NON-CLOUD + (HOTFIX OR RELEASE) + REPO STANDARD elif [[ ${{ inputs.release_cloud }} -eq 0 ]]; then echo "[DEBUG] : NOT Release cloud + ${{ inputs.release_type }}, using rpm-standard." ROOT_REPO_PATHS="rpm-standard" - UPLOAD_REPO_PATH="${{ inputs.version }}/${{ inputs.distrib }}/${{ inputs.stability }}/$ARCH/${{ inputs.module_name }}/" + UPLOAD_REPO_PATH="${{ inputs.major_version }}/${{ inputs.distrib }}/${{ inputs.stability }}/$ARCH/${{ inputs.module_name }}/" # NOT VALID, DO NOT DELIVER else @@ -125,7 +125,7 @@ runs: elif [ "${{ inputs.stability }}" == "testing" ]; then jf rt upload "$ARCH/*.rpm" "$ROOT_REPO_PATH/$UPLOAD_REPO_PATH" --sync-deletes="$ROOT_REPO_PATH/$UPLOAD_REPO_PATH" --flat else - jf rt upload "$ARCH/*.rpm" "$ROOT_REPO_PATH/${{ inputs.version }}/${{ inputs.distrib }}/${{ inputs.stability }}/$ARCH/${{ inputs.module_name }}/" --sync-deletes="$ROOT_REPO_PATH/${{ inputs.version }}/${{ inputs.distrib }}/${{ inputs.stability }}/$ARCH/${{ inputs.module_name }}/" --flat + jf rt upload "$ARCH/*.rpm" "$ROOT_REPO_PATH/${{ inputs.major_version }}/${{ inputs.distrib }}/${{ inputs.stability }}/$ARCH/${{ inputs.module_name }}/" --sync-deletes="$ROOT_REPO_PATH/${{ inputs.major_version }}/${{ inputs.distrib }}/${{ inputs.stability }}/$ARCH/${{ inputs.module_name }}/" --flat fi fi done @@ -138,7 +138,7 @@ runs: FILES="*.${{ env.extfile }}" # DEBUG - echo "[DEBUG] - Version: ${{ inputs.version }}" + echo "[DEBUG] - Major version: ${{ inputs.major_version }}" echo "[DEBUG] - Distrib: ${{ inputs.distrib }}" echo "[DEBUG] - module_name: ${{ inputs.module_name }}" echo "[DEBUG] - release_cloud: ${{ inputs.release_cloud }}" @@ -146,7 +146,7 @@ runs: echo "[DEBUG] - stability: ${{ inputs.stability }}" # Make sure all required inputs are NOT empty - if [[ -z "${{ inputs.module_name }}" || -z "${{ inputs.distrib }}" || -z ${{ inputs.stability }} || -z ${{ inputs.version }} || -z ${{ inputs.release_cloud }} || -z ${{ inputs.release_type }} ]]; then + if [[ -z "${{ inputs.module_name }}" || -z "${{ inputs.distrib }}" || -z ${{ inputs.stability }} || -z ${{ inputs.major_version }} || -z ${{ inputs.release_cloud }} || -z ${{ inputs.release_type }} ]]; then echo "Some mandatory inputs are empty, please check the logs." exit 1 fi @@ -154,16 +154,16 @@ runs: for FILE in $FILES; do echo "[DEBUG] - File: $FILE" - VERSION=${{ inputs.version }} + VERSION=${{ inputs.major_version }} DISTRIB=$(echo $FILE | cut -d '_' -f2 | cut -d '-' -f2) ARCH=$(echo $FILE | cut -d '_' -f3 | cut -d '.' -f1) - echo "[DEBUG] - Version: $VERSION" + echo "[DEBUG] - Major version: $VERSION" if [[ "${{ inputs.distrib }}" == "jammy" ]]; then - ROOT_REPO_PATH="ubuntu-standard-${{ inputs.version }}-${{ inputs.stability }}" + ROOT_REPO_PATH="ubuntu-standard-${{ inputs.major_version }}-${{ inputs.stability }}" else - ROOT_REPO_PATH="apt-standard-${{ inputs.version }}-${{ inputs.stability }}" + ROOT_REPO_PATH="apt-standard-${{ inputs.major_version }}-${{ inputs.stability }}" fi jf rt upload "$FILE" "$ROOT_REPO_PATH/pool/${{ inputs.module_name }}/" --deb "${{ inputs.distrib }}/main/$ARCH" diff --git a/.github/workflows/centreon-collect.yml b/.github/workflows/centreon-collect.yml index 5525e3df391..9cfa27cba1a 100644 --- a/.github/workflows/centreon-collect.yml +++ b/.github/workflows/centreon-collect.yml @@ -55,6 +55,8 @@ on: jobs: get-version: uses: ./.github/workflows/get-version.yml + with: + version_file: CMakeLists.txt unit-test: needs: [get-version] @@ -91,7 +93,8 @@ jobs: if: ${{ ! contains(fromJson('["stable"]'), needs.get-version.outputs.stability) }} uses: ./.github/workflows/package-collect.yml with: - version: ${{ needs.get-version.outputs.version }}.${{ needs.get-version.outputs.patch }} + major_version: ${{ needs.get-version.outputs.major_version }} + minor_version: ${{ needs.get-version.outputs.minor_version }} img_version: ${{ needs.get-version.outputs.img_version }} release: ${{ needs.get-version.outputs.release }} commit_hash: ${{ github.sha }} @@ -115,8 +118,8 @@ jobs: bucket_directory: centreon-collect module_directory: centreon-collect module_name: centreon-collect - major_version: ${{ needs.get-version.outputs.version }} - minor_version: ${{ needs.get-version.outputs.patch }} + major_version: ${{ needs.get-version.outputs.major_version }} + minor_version: ${{ needs.get-version.outputs.minor_version }} token_download_centreon_com: ${{ secrets.TOKEN_DOWNLOAD_CENTREON_COM }} deliver-rpm: @@ -143,7 +146,7 @@ jobs: with: module_name: collect distrib: ${{ matrix.distrib }} - version: ${{ needs.get-version.outputs.version }} + major_version: ${{ needs.get-version.outputs.major_version }} artifactory_token: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }} cache_key: ${{ github.run_id }}-${{ github.sha }}-rpm-centreon-collect-${{ matrix.distrib }}-${{ matrix.arch }}-${{ github.head_ref || github.ref_name }} stability: ${{ needs.get-version.outputs.stability }} @@ -178,7 +181,7 @@ jobs: with: module_name: collect distrib: ${{ matrix.distrib }} - version: ${{ needs.get-version.outputs.version }} + major_version: ${{ needs.get-version.outputs.major_version }} artifactory_token: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }} cache_key: ${{ github.run_id }}-${{ github.sha }}-deb-centreon-collect-${{ matrix.distrib }}-${{ matrix.arch }}-${{ github.head_ref || github.ref_name }} stability: ${{ needs.get-version.outputs.stability }} @@ -203,8 +206,8 @@ jobs: artifactory_token: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }} module_name: collect distrib: ${{ matrix.distrib }} - major_version: ${{ needs.get-version.outputs.version }} - minor_version: ${{ needs.get-version.outputs.patch }} + major_version: ${{ needs.get-version.outputs.major_version }} + minor_version: ${{ needs.get-version.outputs.minor_version }} stability: ${{ needs.get-version.outputs.stability }} github_ref_name: ${{ github.ref_name }} release_type: ${{ needs.get-version.outputs.release_type }} diff --git a/.github/workflows/docker-builder.yml b/.github/workflows/docker-builder.yml index b0f7f768353..212d3dfcf46 100644 --- a/.github/workflows/docker-builder.yml +++ b/.github/workflows/docker-builder.yml @@ -19,6 +19,8 @@ on: jobs: get-version: uses: ./.github/workflows/get-version.yml + with: + version_file: CMakeLists.txt create-and-push-docker: needs: [get-version] diff --git a/.github/workflows/get-version.yml b/.github/workflows/get-version.yml index c855b9fa493..2d584fac855 100644 --- a/.github/workflows/get-version.yml +++ b/.github/workflows/get-version.yml @@ -2,8 +2,9 @@ on: workflow_call: inputs: version_file: - required: true + required: false type: string + default: CMakeLists.txt outputs: major_version: description: "major version" @@ -20,9 +21,6 @@ on: version: description: "major version" value: ${{ jobs.get-version.outputs.version }} - patch: - description: "patch version" - value: ${{ jobs.get-version.outputs.patch }} release: description: "release" value: ${{ jobs.get-version.outputs.release }} diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index a18e04e28a0..1c1be4341fd 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -317,7 +317,7 @@ jobs: uses: ./.github/actions/promote-to-stable with: artifactory_token: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }} - module: gorgone + module_name: gorgone distrib: ${{ matrix.distrib }} major_version: ${{ needs.get-version.outputs.major_version }} minor_version: ${{ needs.get-version.outputs.minor_version }} diff --git a/.github/workflows/libzmq.yml b/.github/workflows/libzmq.yml index f289fa48b1d..7d39d46030d 100644 --- a/.github/workflows/libzmq.yml +++ b/.github/workflows/libzmq.yml @@ -39,7 +39,7 @@ jobs: runs-on: ubuntu-22.04 container: - image: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }}/${{ matrix.image }}:${{ needs.get-version.outputs.version }} + image: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }}/${{ matrix.image }}:${{ needs.get-version.outputs.major_version }} credentials: username: ${{ secrets.DOCKER_REGISTRY_ID }} password: ${{ secrets.DOCKER_REGISTRY_PASSWD }} @@ -99,7 +99,7 @@ jobs: runs-on: ${{ matrix.runner }} container: - image: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }}/${{ matrix.image }}:${{ needs.get-version.outputs.version }} + image: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }}/${{ matrix.image }}:${{ needs.get-version.outputs.major_version }} credentials: username: ${{ secrets.DOCKER_REGISTRY_ID }} password: ${{ secrets.DOCKER_REGISTRY_PASSWD }} @@ -160,7 +160,7 @@ jobs: with: module_name: libzmq distrib: ${{ matrix.distrib }} - version: ${{ needs.get-version.outputs.version }} + major_version: ${{ needs.get-version.outputs.major_version }} artifactory_token: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }} cache_key: ${{ github.run_id }}-${{ github.sha }}-rpm-libzmq-${{ matrix.distrib }}-${{ matrix.arch }} stability: ${{ needs.get-version.outputs.stability }} @@ -195,7 +195,7 @@ jobs: with: module_name: libzmq distrib: ${{ matrix.distrib }} - version: ${{ needs.get-version.outputs.version }} + major_version: ${{ needs.get-version.outputs.major_version }} artifactory_token: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }} cache_key: ${{ github.run_id }}-${{ github.sha }}-deb-libzmq-${{ matrix.distrib }}-${{ matrix.arch }} stability: ${{ needs.get-version.outputs.stability }} @@ -220,8 +220,8 @@ jobs: artifactory_token: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }} module_name: libzmq distrib: ${{ matrix.distrib }} - major_version: ${{ needs.get-version.outputs.version }} - minor_version: ${{ needs.get-version.outputs.patch }} + major_version: ${{ needs.get-version.outputs.major_version }} + minor_version: ${{ needs.get-version.outputs.minor_version }} stability: ${{ needs.get-version.outputs.stability }} github_ref_name: ${{ github.ref_name }} release_type: ${{ needs.get-version.outputs.release_type }} diff --git a/.github/workflows/package-collect.yml b/.github/workflows/package-collect.yml index b3b0cc4ded6..8e04fd70f2b 100644 --- a/.github/workflows/package-collect.yml +++ b/.github/workflows/package-collect.yml @@ -3,7 +3,10 @@ name: Centreon collect packaging on: workflow_call: inputs: - version: + major_version: + required: true + type: string + minor_version: required: true type: string img_version: @@ -107,7 +110,7 @@ jobs: cd selinux for MODULE in "centreon-engine" "centreon-broker"; do cd $MODULE - sed -i "s/@VERSION@/${{ inputs.version }}/g" $MODULE.te + sed -i "s/@VERSION@/${{ inputs.major_version }}.${{ inputs.minor_version }}/g" $MODULE.te make -f /usr/share/selinux/devel/Makefile cd - done @@ -201,7 +204,8 @@ jobs: nfpm_file_pattern: "packaging/*.yaml" distrib: ${{ matrix.distrib }} package_extension: ${{ matrix.package_extension }} - version: ${{ inputs.version }} + major_version: ${{ inputs.major_version }} + minor_version: ${{ inputs.minor_version }} release: ${{ inputs.release }} arch: ${{ matrix.arch }} commit_hash: ${{ inputs.commit_hash }} diff --git a/.github/workflows/robot-nightly.yml b/.github/workflows/robot-nightly.yml index 3ce8b5df421..711cddd863b 100644 --- a/.github/workflows/robot-nightly.yml +++ b/.github/workflows/robot-nightly.yml @@ -29,14 +29,16 @@ jobs: get-version: uses: ./.github/workflows/get-version.yml + with: + version_file: CMakeLists.txt veracode-analysis: needs: [get-version] uses: ./.github/workflows/veracode-analysis.yml with: module_name: centreon-collect - major_version: ${{ needs.get-version.outputs.version }} - minor_version: ${{ needs.get-version.outputs.patch }} + major_version: ${{ needs.get-version.outputs.major_version }} + minor_version: ${{ needs.get-version.outputs.minor_version }} img_version: ${{ needs.get-version.outputs.img_version }} secrets: veracode_api_id: ${{ secrets.VERACODE_API_ID_COLL }} @@ -50,7 +52,8 @@ jobs: uses: ./.github/workflows/package-collect.yml with: stability: ${{ needs.get-version.outputs.stability }} - version: ${{ needs.get-version.outputs.version }}.${{ needs.get-version.outputs.patch }} + major_version: ${{ needs.get-version.outputs.major_version }} + minor_version: ${{ needs.get-version.outputs.minor_version }} img_version: ${{ needs.get-version.outputs.img_version }} release: ${{ needs.get-version.outputs.release }} commit_hash: ${{ github.sha }} @@ -146,7 +149,7 @@ jobs: with: module_name: collect distrib: ${{ matrix.distrib }} - version: ${{ needs.get-version.outputs.version }} + major_version: ${{ needs.get-version.outputs.major_version }} artifactory_token: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }} cache_key: ${{ github.run_id }}-${{ github.sha }}-rpm-centreon-collect-${{ matrix.distrib }}-amd64-${{ github.head_ref || github.ref_name }} stability: ${{ needs.get-version.outputs.stability }} @@ -179,7 +182,7 @@ jobs: with: module_name: collect distrib: ${{ matrix.distrib }} - version: ${{ needs.get-version.outputs.version }} + major_version: ${{ needs.get-version.outputs.major_version }} artifactory_token: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }} cache_key: ${{ github.run_id }}-${{ github.sha }}-deb-centreon-collect-${{ matrix.distrib }}-${{ matrix.arch }}-${{ github.head_ref || github.ref_name }} stability: ${{ needs.get-version.outputs.stability }} From 0b8e6f4ed7ef2e69769de3cbc4775573da4df016 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Tue, 25 Jun 2024 10:06:25 +0200 Subject: [PATCH 852/948] add missing inputsgit status --- .github/workflows/get-version.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/get-version.yml b/.github/workflows/get-version.yml index 2d584fac855..ea22b7ed948 100644 --- a/.github/workflows/get-version.yml +++ b/.github/workflows/get-version.yml @@ -46,7 +46,6 @@ jobs: img_version: ${{ steps.get_version.outputs.img_version }} test_img_version: ${{ steps.get_version.outputs.test_img_version }} version: ${{ steps.get_version.outputs.version }} - patch: ${{ steps.get_version.outputs.patch }} release: ${{ steps.get_version.outputs.release }} stability: ${{ steps.get_version.outputs.stability }} environment: ${{ steps.get_version.outputs.env }} @@ -80,8 +79,9 @@ jobs: . ${{ inputs.version_file }} VERSION="$MAJOR.$MINOR" elif [[ "${{ inputs.version_file }}" == */CMakeLists.txt ]]; then - VERSION=$(awk '$1 ~ "COLLECT_MAJOR" {maj=substr($2, 1, length($2)-1)} $1 ~ "COLLECT_MINOR" {min=substr($2, 1, length($2)-1) ; print maj "." min}' CMakeLists.txt) - PATCH=$(awk '$1 ~ "COLLECT_PATCH" {print substr($2, 1, length($2) - 1)}' CMakeLists.txt) + MAJOR=$(awk '$1 ~ "COLLECT_MAJOR" {maj=substr($2, 1, length($2)-1)} $1 ~ "COLLECT_MINOR" {min=substr($2, 1, length($2)-1) ; print maj "." min}' CMakeLists.txt) + MINOR=$(awk '$1 ~ "COLLECT_PATCH" {print substr($2, 1, length($2) - 1)}' CMakeLists.txt) + VERSION="$MAJOR.$MINOR" else echo "Unable to parse ${{ inputs.version_file }}" exit 1 @@ -105,7 +105,6 @@ jobs: echo "img_version=$IMG_VERSION" >> $GITHUB_OUTPUT echo "test_img_version=$TEST_IMG_VERSION" >> $GITHUB_OUTPUT echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "patch=$PATCH" >> $GITHUB_OUTPUT if [[ -z "$GITHUB_HEAD_REF" ]]; then BRANCHNAME="$GITHUB_REF_NAME" From 5b4da62def8c9d9f5f055e7a63d5a302a70620d6 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Tue, 25 Jun 2024 10:10:43 +0200 Subject: [PATCH 853/948] add missing inputsgit status --- .github/workflows/get-version.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/get-version.yml b/.github/workflows/get-version.yml index ea22b7ed948..1fed9a0314e 100644 --- a/.github/workflows/get-version.yml +++ b/.github/workflows/get-version.yml @@ -78,7 +78,7 @@ jobs: . .version . ${{ inputs.version_file }} VERSION="$MAJOR.$MINOR" - elif [[ "${{ inputs.version_file }}" == */CMakeLists.txt ]]; then + elif [[ "${{ inputs.version_file }}" == CMakeLists.txt ]]; then MAJOR=$(awk '$1 ~ "COLLECT_MAJOR" {maj=substr($2, 1, length($2)-1)} $1 ~ "COLLECT_MINOR" {min=substr($2, 1, length($2)-1) ; print maj "." min}' CMakeLists.txt) MINOR=$(awk '$1 ~ "COLLECT_PATCH" {print substr($2, 1, length($2) - 1)}' CMakeLists.txt) VERSION="$MAJOR.$MINOR" From 2ad6a23606afd761497e327b5c4390baddb74826 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Tue, 25 Jun 2024 10:37:32 +0200 Subject: [PATCH 854/948] trigger centreon-collect --- .github/workflows/centreon-collect.yml | 38 +++++++++++++------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/centreon-collect.yml b/.github/workflows/centreon-collect.yml index 9cfa27cba1a..7831ec320ed 100644 --- a/.github/workflows/centreon-collect.yml +++ b/.github/workflows/centreon-collect.yml @@ -7,25 +7,25 @@ concurrency: on: workflow_dispatch: pull_request: - paths: - - bbdo/** - - broker/** - - ccc/** - - clib/** - - connectors/** - - custom-triplets/** - - engine/** - - grpc/** - - packaging/** - - cmake.sh - - cmake-vcpkg.sh - - CMakeLists.txt - - vcpkg.json - - overlays/** - - selinux/** - - vcpkg/** - - "!.veracode-exclusions" - - "!veracode.json" + # paths: + # - bbdo/** + # - broker/** + # - ccc/** + # - clib/** + # - connectors/** + # - custom-triplets/** + # - engine/** + # - grpc/** + # - packaging/** + # - cmake.sh + # - cmake-vcpkg.sh + # - CMakeLists.txt + # - vcpkg.json + # - overlays/** + # - selinux/** + # - vcpkg/** + # - "!.veracode-exclusions" + # - "!veracode.json" push: branches: - develop From fde8aab95ec5257976f4fce163849f4db2f6c1fc Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Tue, 25 Jun 2024 10:57:01 +0200 Subject: [PATCH 855/948] retrieve wortkflow to build gorgone testing images --- .../docker/Dockerfile.gorgone-testing-alma8 | 15 ++++++ .../Dockerfile.gorgone-testing-bullseye | 30 ++++++++++++ .github/workflows/centreon-collect.yml | 40 +++++++-------- .github/workflows/docker-gorgone-testing.yml | 49 +++++++++++++++++++ .github/workflows/robot-nightly.yml | 1 + 5 files changed, 115 insertions(+), 20 deletions(-) create mode 100644 .github/docker/Dockerfile.gorgone-testing-alma8 create mode 100644 .github/docker/Dockerfile.gorgone-testing-bullseye create mode 100644 .github/workflows/docker-gorgone-testing.yml diff --git a/.github/docker/Dockerfile.gorgone-testing-alma8 b/.github/docker/Dockerfile.gorgone-testing-alma8 new file mode 100644 index 00000000000..618bb6cd582 --- /dev/null +++ b/.github/docker/Dockerfile.gorgone-testing-alma8 @@ -0,0 +1,15 @@ +FROM almalinux:8 + +RUN bash -e <<EOF + +dnf install -y dnf-plugins-core zstd curl mariadb iproute +dnf install -y https://rpms.remirepo.net/enterprise/remi-release-8.rpm +dnf config-manager --set-enabled 'powertools' +dnf -y config-manager --add-repo https://packages.centreon.com/rpm-standard/23.10/el8/centreon-23.10.repo +dnf -y clean all --enablerepo=* +dnf install -y python3.11 python3.11-pip +pip3.11 install robotframework robotframework-examples robotframework-databaselibrary pymysql robotframework-requests + +dnf clean all + +EOF diff --git a/.github/docker/Dockerfile.gorgone-testing-bullseye b/.github/docker/Dockerfile.gorgone-testing-bullseye new file mode 100644 index 00000000000..5799ac451bd --- /dev/null +++ b/.github/docker/Dockerfile.gorgone-testing-bullseye @@ -0,0 +1,30 @@ +FROM debian:bullseye + +ENV DEBIAN_FRONTEND noninteractive + +# fix locale +RUN bash -e <<EOF + +apt-get update +apt-get install -y locales libcurl4-openssl-dev curl wget zstd jq lsb-release mariadb-client iproute2 +rm -rf /var/lib/apt/lists/* +localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 +apt-get clean + +EOF + +ENV LANG en_US.utf8 + +RUN bash -e <<EOF +apt-get update +# Install Robotframework +apt-get install -y python3 python3-dev python3-pip ca-certificates apt-transport-https software-properties-common wget gnupg2 +pip3 install robotframework robotframework-examples robotframework-databaselibrary pymysql robotframework-requests + +echo "deb https://packages.centreon.com/apt-standard-23.10-stable/ $(lsb_release -sc) main" | tee /etc/apt/sources.list.d/centreon.list +echo "deb https://packages.centreon.com/apt-plugins-stable/ $(lsb_release -sc) main" | tee /etc/apt/sources.list.d/centreon-plugins.list + +wget -O- https://apt-key.centreon.com | gpg --dearmor | tee /etc/apt/trusted.gpg.d/centreon.gpg > /dev/null 2>&1 +apt-get update + +EOF diff --git a/.github/workflows/centreon-collect.yml b/.github/workflows/centreon-collect.yml index 7e4065a807c..f53dd642c0c 100644 --- a/.github/workflows/centreon-collect.yml +++ b/.github/workflows/centreon-collect.yml @@ -7,26 +7,26 @@ concurrency: on: workflow_dispatch: pull_request: - # paths: - # - agent/** - # - bbdo/** - # - broker/** - # - ccc/** - # - clib/** - # - connectors/** - # - custom-triplets/** - # - engine/** - # - grpc/** - # - packaging/** - # - cmake.sh - # - cmake-vcpkg.sh - # - CMakeLists.txt - # - vcpkg.json - # - overlays/** - # - selinux/** - # - vcpkg/** - # - "!.veracode-exclusions" - # - "!veracode.json" + paths: + - agent/** + - bbdo/** + - broker/** + - ccc/** + - clib/** + - connectors/** + - custom-triplets/** + - engine/** + - grpc/** + - packaging/** + - cmake.sh + - cmake-vcpkg.sh + - CMakeLists.txt + - vcpkg.json + - overlays/** + - selinux/** + - vcpkg/** + - "!.veracode-exclusions" + - "!veracode.json" push: branches: - develop diff --git a/.github/workflows/docker-gorgone-testing.yml b/.github/workflows/docker-gorgone-testing.yml new file mode 100644 index 00000000000..5042c35f132 --- /dev/null +++ b/.github/workflows/docker-gorgone-testing.yml @@ -0,0 +1,49 @@ +name: docker-gorgone-testing + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +on: + workflow_dispatch: + push: + branches: + - develop + - dev-[2-9][0-9].[0-9][0-9].x + paths: + - ".github/docker/Dockerfile.gorgone-testing-*" + pull_request: + paths: + - ".github/docker/Dockerfile.gorgone-testing-*" + +jobs: + get-version: + uses: ./.github/workflows/get-version.yml + + dockerize: + needs: [get-version] + runs-on: ubuntu-22.04 + + strategy: + matrix: + distrib: [alma8, bullseye] + steps: + - name: Checkout sources + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Login to registry + uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 + with: + registry: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }} + username: ${{ secrets.DOCKER_REGISTRY_ID }} + password: ${{ secrets.DOCKER_REGISTRY_PASSWD }} + + - uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 + + - uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0 + with: + file: .github/docker/Dockerfile.gorgone-testing-${{ matrix.distrib }} + context: . + pull: true + push: true + tags: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }}/gorgone-testing-${{ matrix.distrib }}:${{ needs.get-version.outputs.major_version }} diff --git a/.github/workflows/robot-nightly.yml b/.github/workflows/robot-nightly.yml index 711cddd863b..acdd9d91330 100644 --- a/.github/workflows/robot-nightly.yml +++ b/.github/workflows/robot-nightly.yml @@ -7,6 +7,7 @@ concurrency: on: workflow_dispatch: + pull_request: schedule: - cron: '30 0 * * *' From c78d613b89a07e5826c07ecbcab5b31778c7e43f Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Tue, 25 Jun 2024 12:04:05 +0200 Subject: [PATCH 856/948] retrieve trigger --- .github/workflows/docker-builder.yml | 4 ++-- .github/workflows/gorgone.yml | 8 ++++---- .github/workflows/robot-nightly.yml | 1 - 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/docker-builder.yml b/.github/workflows/docker-builder.yml index 212d3dfcf46..ff48ab61d7c 100644 --- a/.github/workflows/docker-builder.yml +++ b/.github/workflows/docker-builder.yml @@ -11,10 +11,10 @@ on: - develop - dev-[2-9][0-9].[0-9][0-9].x paths: - - '.github/docker/**' + - '.github/docker/Dockerfile.centreon-collect-*' pull_request: paths: - - '.github/docker/**' + - '.github/docker/Dockerfile.centreon-collect-*' jobs: get-version: diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 1c1be4341fd..4515fe7bb25 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -12,10 +12,10 @@ on: - synchronize - reopened - ready_for_review - # paths: - # - "gorgone/**" - # - "!gorgone/veracode.json" - # - "!gorgone/.veracode-exclusions" + paths: + - "gorgone/**" + - "!gorgone/veracode.json" + - "!gorgone/.veracode-exclusions" push: branches: - develop diff --git a/.github/workflows/robot-nightly.yml b/.github/workflows/robot-nightly.yml index acdd9d91330..711cddd863b 100644 --- a/.github/workflows/robot-nightly.yml +++ b/.github/workflows/robot-nightly.yml @@ -7,7 +7,6 @@ concurrency: on: workflow_dispatch: - pull_request: schedule: - cron: '30 0 * * *' From 3b4a4c9dedba26f64c044f96c9f331553b1e5808 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Mon, 1 Jul 2024 14:53:57 +0200 Subject: [PATCH 857/948] fix(packaging): set 31-centreon-api.yaml as config file (#4525) --- .../packaging/centreon-gorgone-centreon-config.yaml | 3 ++- .../centreon-gorgone-centreon-config-postinstall.sh | 11 ----------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/gorgone/packaging/centreon-gorgone-centreon-config.yaml b/gorgone/packaging/centreon-gorgone-centreon-config.yaml index c57d0cc9c8e..6be31184173 100644 --- a/gorgone/packaging/centreon-gorgone-centreon-config.yaml +++ b/gorgone/packaging/centreon-gorgone-centreon-config.yaml @@ -24,7 +24,8 @@ contents: mode: 0640 - src: "./configuration/centreon-api.yaml" - dst: "/etc/centreon-gorgone/config.d/centreon-api.yaml" + dst: "/etc/centreon-gorgone/config.d/31-centreon-api.yaml" + type: config|noreplace file_info: owner: centreon-gorgone group: centreon-gorgone diff --git a/gorgone/packaging/scripts/centreon-gorgone-centreon-config-postinstall.sh b/gorgone/packaging/scripts/centreon-gorgone-centreon-config-postinstall.sh index f4bd78d02c8..d01e7a0d637 100644 --- a/gorgone/packaging/scripts/centreon-gorgone-centreon-config-postinstall.sh +++ b/gorgone/packaging/scripts/centreon-gorgone-centreon-config-postinstall.sh @@ -1,13 +1,5 @@ #!/bin/bash -installConfigurationFile() { - if [ ! -f /etc/centreon-gorgone/config.d/31-centreon-api.yaml ] || grep -q "@GORGONE_USER@" "/etc/centreon-gorgone/config.d/31-centreon-api.yaml"; then - mv /etc/centreon-gorgone/config.d/centreon-api.yaml /etc/centreon-gorgone/config.d/31-centreon-api.yaml - else - mv /etc/centreon-gorgone/config.d/centreon-api.yaml /etc/centreon-gorgone/config.d/centreon-api.yaml.new - fi -} - fixConfigurationFileRights() { # force update of configuration file rights since they are not updated automatically by nfpm chmod 0640 /etc/centreon-gorgone/config.d/30-centreon.yaml @@ -55,19 +47,16 @@ fi case "$action" in "1" | "install") manageUserGroups - installConfigurationFile addGorgoneSshKeys ;; "2" | "upgrade") manageUserGroups - installConfigurationFile fixConfigurationFileRights addGorgoneSshKeys ;; *) # $1 == version being installed manageUserGroups - installConfigurationFile addGorgoneSshKeys ;; esac From b33948e0ace26af1bb5a1fb04f90e35328139810 Mon Sep 17 00:00:00 2001 From: tuntoja <58987095+tuntoja@users.noreply.github.com> Date: Tue, 2 Jul 2024 15:28:58 +0200 Subject: [PATCH 858/948] Prepare release cloud 24.07.0 (#4526) --- gorgone/packaging/centreon-gorgone-centreon-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/packaging/centreon-gorgone-centreon-config.yaml b/gorgone/packaging/centreon-gorgone-centreon-config.yaml index 6be31184173..f633c694fd1 100644 --- a/gorgone/packaging/centreon-gorgone-centreon-config.yaml +++ b/gorgone/packaging/centreon-gorgone-centreon-config.yaml @@ -57,11 +57,11 @@ overrides: depends: - centreon-gorgone (= ${VERSION}-${RELEASE}${DIST}) replaces: - - centreon-gorgone (<< 24.05.0) + - centreon-gorgone (<< 24.07.0) deb: breaks: - - centreon-gorgone (<< 24.05.0) + - centreon-gorgone (<< 24.07.0) rpm: summary: Configure Centreon Gorgone for use with Centreon Web From aa90d73bf862cef77b2a967a54c975664b92bd8c Mon Sep 17 00:00:00 2001 From: Evan-Adam <152897682+Evan-Adam@users.noreply.github.com> Date: Thu, 4 Jul 2024 09:25:12 +0200 Subject: [PATCH 859/948] fix(gorgone-mbi) rebuilding centiles truncates mod_bi_metrichourlyvalue table (#4396) Co-authored-by: Colin Gagnaire <gagnaire.colin@gmail.com> --- gorgone/gorgone/modules/centreon/mbi/etl/perfdata/main.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/gorgone/modules/centreon/mbi/etl/perfdata/main.pm b/gorgone/gorgone/modules/centreon/mbi/etl/perfdata/main.pm index 170903907c0..0a8c74ac47c 100644 --- a/gorgone/gorgone/modules/centreon/mbi/etl/perfdata/main.pm +++ b/gorgone/gorgone/modules/centreon/mbi/etl/perfdata/main.pm @@ -178,7 +178,7 @@ sub purgeTables { if ($etl->{run}->{etlProperties}->{'perfdata.granularity'} ne "day" && (!defined($etl->{run}->{options}->{month_only}) || $etl->{run}->{options}->{month_only} == 0) && - (!defined($etl->{run}->{options}->{no_centile}) || $etl->{run}->{options}->{no_centile} == 0)) { + (!defined($etl->{run}->{options}->{centile_only}) || $etl->{run}->{options}->{centile_only} == 0)) { emptyTableForRebuild($etl, name => 'mod_bi_metrichourlyvalue', column => 'time_id', start => $hourly_start, end => $hourly_end); } } From 5f41047a25a1661ec80328a9685a5280a3471457 Mon Sep 17 00:00:00 2001 From: Evan-Adam <152897682+Evan-Adam@users.noreply.github.com> Date: Thu, 4 Jul 2024 10:30:34 +0200 Subject: [PATCH 860/948] fix(gorgone/mbi): add weekly and monthly centiles partitioning (#4401) Co-authored-by: Colin Gagnaire <gagnaire.colin@gmail.com> --- .../modules/centreon/mbi/etl/perfdata/main.pm | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/gorgone/gorgone/modules/centreon/mbi/etl/perfdata/main.pm b/gorgone/gorgone/modules/centreon/mbi/etl/perfdata/main.pm index 0a8c74ac47c..352ef950c9d 100644 --- a/gorgone/gorgone/modules/centreon/mbi/etl/perfdata/main.pm +++ b/gorgone/gorgone/modules/centreon/mbi/etl/perfdata/main.pm @@ -346,6 +346,30 @@ sub dailyProcessing { ] }; } + if (defined($etl->{run}->{etlProperties}->{'centile.week'}) && $etl->{run}->{etlProperties}->{'centile.week'} eq '1') { + push @{$etl->{run}->{schedule}->{perfdata}->{stages}->[0]}, { + type => 'sql', + db => 'centstorage', + sql => [ + [ + '[PARTITIONS] Add partition [p' . $partName . '] on table [mod_bi_metriccentileweeklyvalue]', + "ALTER TABLE `mod_bi_metriccentileweeklyvalue` ADD PARTITION (PARTITION `p$partName` VALUES LESS THAN(" . $epoch . "))" + ] + ] + }; + } + if (defined($etl->{run}->{etlProperties}->{'centile.month'}) && $etl->{run}->{etlProperties}->{'centile.month'} eq '1') { + push @{$etl->{run}->{schedule}->{perfdata}->{stages}->[0]}, { + type => 'sql', + db => 'centstorage', + sql => [ + [ + '[PARTITIONS] Add partition [p' . $partName . '] on table [mod_bi_metriccentilemonthlyvalue]', + "ALTER TABLE `mod_bi_metriccentilemonthlyvalue` ADD PARTITION (PARTITION `p$partName` VALUES LESS THAN(" . $epoch . "))" + ] + ] + }; + } # processing agregation by month. If the day is the first day of the month, also processing agregation by month processDayAndMonthAgregation($etl, $liveServices, $start, $end); From 684b0f3e5962f25a71315f54d8d6317215a1b975 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Fri, 5 Jul 2024 09:33:52 +0200 Subject: [PATCH 861/948] enh(ci): enable gorgone veracode + update codeowners --- .github/CODEOWNERS | 17 ++++++++++++----- .github/workflows/gorgone.yml | 34 +++++++++++++++++----------------- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 08a9bd35381..8d774885215 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,7 +1,14 @@ -* @centreon/owners-cpp +* @centreon/owners-cpp -.github/** @centreon/owners-pipelines -packaging/** @centreon/owners-pipelines -selinux/** @centreon/owners-pipelines +.github/** @centreon/owners-pipelines +packaging/** @centreon/owners-pipelines +selinux/** @centreon/owners-pipelines -tests/** @centreon/owners-robot-e2e +tests/** @centreon/owners-robot-e2e + +gorgone/ @centreon/owners-perl +gorgone/docs/ @centreon/owners-doc + +gorgone/tests/robot/config/ @centreon/owners-perl +*.pm @centreon/owners-perl +*.pl @centreon/owners-perl diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 4515fe7bb25..6556b1318c4 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -36,23 +36,23 @@ jobs: with: version_file: gorgone/.version - # veracode-analysis: - # needs: [get-version] - # uses: ./.github/workflows/veracode-analysis.yml - # with: - # module_directory: gorgone - # module_name: centreon-gorgone - # major_version: ${{ needs.get-version.outputs.major_version }} - # minor_version: ${{ needs.get-version.outputs.minor_version }} - # stability: ${{ needs.get-version.outputs.stability }} - # is_perl_project: true - # secrets: - # veracode_api_id: ${{ secrets.VERACODE_API_ID_GORG }} - # veracode_api_key: ${{ secrets.VERACODE_API_KEY_GORG }} - # veracode_srcclr_token: ${{ secrets.VERACODE_SRCCLR_TOKEN }} - # jira_base_url: ${{ secrets.JIRA_BASE_URL }} - # jira_user_email: ${{ secrets.XRAY_JIRA_USER_EMAIL }} - # jira_api_token: ${{ secrets.XRAY_JIRA_TOKEN }} + veracode-analysis: + needs: [get-version] + uses: ./.github/workflows/veracode-analysis.yml + with: + module_directory: gorgone + module_name: centreon-gorgone + major_version: ${{ needs.get-version.outputs.major_version }} + minor_version: ${{ needs.get-version.outputs.minor_version }} + stability: ${{ needs.get-version.outputs.stability }} + is_perl_project: true + secrets: + veracode_api_id: ${{ secrets.VERACODE_API_ID_GORG }} + veracode_api_key: ${{ secrets.VERACODE_API_KEY_GORG }} + veracode_srcclr_token: ${{ secrets.VERACODE_SRCCLR_TOKEN }} + jira_base_url: ${{ secrets.JIRA_BASE_URL }} + jira_user_email: ${{ secrets.XRAY_JIRA_USER_EMAIL }} + jira_api_token: ${{ secrets.XRAY_JIRA_TOKEN }} package: needs: [get-version] From c9c46993d2032f7f2335353d9f95c0e8742bf499 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Fri, 5 Jul 2024 10:06:19 +0200 Subject: [PATCH 862/948] fix(ci): fix gorgone veracode inputs --- .github/workflows/gorgone.yml | 8 +++----- .github/workflows/veracode-analysis.yml | 16 +++++++++++++--- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 6556b1318c4..00560f4288b 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -44,15 +44,13 @@ jobs: module_name: centreon-gorgone major_version: ${{ needs.get-version.outputs.major_version }} minor_version: ${{ needs.get-version.outputs.minor_version }} - stability: ${{ needs.get-version.outputs.stability }} - is_perl_project: true + img_version: ${{ needs.get-version.outputs.img_version }} secrets: veracode_api_id: ${{ secrets.VERACODE_API_ID_GORG }} veracode_api_key: ${{ secrets.VERACODE_API_KEY_GORG }} veracode_srcclr_token: ${{ secrets.VERACODE_SRCCLR_TOKEN }} - jira_base_url: ${{ secrets.JIRA_BASE_URL }} - jira_user_email: ${{ secrets.XRAY_JIRA_USER_EMAIL }} - jira_api_token: ${{ secrets.XRAY_JIRA_TOKEN }} + docker_registry_id: ${{ secrets.DOCKER_REGISTRY_ID }} + docker_registry_passwd: ${{ secrets.DOCKER_REGISTRY_PASSWD }} package: needs: [get-version] diff --git a/.github/workflows/veracode-analysis.yml b/.github/workflows/veracode-analysis.yml index 412319590f9..ee4d2fec7d2 100644 --- a/.github/workflows/veracode-analysis.yml +++ b/.github/workflows/veracode-analysis.yml @@ -2,6 +2,9 @@ on: workflow_call: inputs: + module_directory: + required: false + type: string module_name: required: true type: string @@ -63,7 +66,8 @@ jobs: steps: - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - - name: Compiling Cpp sources + - if: ${{ inputs.module_name == 'centreon-collect' }} + name: Compiling Cpp sources run: | mv /root/.cache /github/home export VCPKG_ROOT=/vcpkg @@ -101,12 +105,18 @@ jobs: echo "[DEBUG] - Build size" du -sh $(find build/{broker,engine,clib,connectors,common} -name "*.so" -type f) | sort -rh - - name: Binary preparation + - if: ${{ inputs.module_name == 'centreon-collect' }} + name: Preserve centreon-collect binaries from cleaning run: | echo "[INFO] - Keeping only compiled files" - # preserve binaries from cleaning find build -type f -not \( -name "*.so" -or -name "cbd" -or -name "centengine" -or -name "cbwd" -or -name "centreon_connector_*" \) -delete + - name: Binary preparation of ${{ inputs.module_name }} + run: | + if [ -n "${{ inputs.module_directory }}" ]; then + cd ${{ inputs.module_directory }} + fi + echo "[INFO] - Removing veracode exclusions" if [[ -f ".veracode-exclusions" ]]; then for LINE in $( cat .veracode-exclusions | sed 's/[^a-zA-Z0-9_./-]//g' | sed -r 's/\.\./\./g' ); do From ee239cf374be73aa7bc8fd3be0439f525195ffe1 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Fri, 5 Jul 2024 10:17:31 +0200 Subject: [PATCH 863/948] fix(ci): do not skip gorgone veracode analysis --- .github/workflows/veracode-analysis.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/veracode-analysis.yml b/.github/workflows/veracode-analysis.yml index ee4d2fec7d2..742207077dc 100644 --- a/.github/workflows/veracode-analysis.yml +++ b/.github/workflows/veracode-analysis.yml @@ -35,6 +35,7 @@ jobs: runs-on: ubuntu-22.04 outputs: development_stage: ${{ steps.routing-mode.outputs.development_stage }} + skip_analysis: ${{ steps.routing-mode.outputs.skip_analysis }} steps: - name: Set routing mode @@ -49,14 +50,21 @@ jobs: fi done + # skip analysis of draft PR and analysis on development branches using workflow dispatch + SKIP_ANALYSIS="true" + if [[ "${{ github.event_name }}" == "pull_request" && -n "${{ github.event.pull_request.number }}" && -n "${{ github.event.pull_request.draft }}" && "${{ github.event.pull_request.draft }}" == "false" ]] || [[ "$DEVELOPMENT_STAGE" != "Development" ]]; then + SKIP_ANALYSIS="false" + fi + echo "development_stage=$DEVELOPMENT_STAGE" >> $GITHUB_OUTPUT + echo "skip_analysis=$SKIP_ANALYSIS" >> $GITHUB_OUTPUT cat $GITHUB_OUTPUT build: name: Binary preparation runs-on: [self-hosted, collect] needs: [routing] - if: needs.routing.outputs.development_stage != 'Development' + if: needs.build.outputs.skip_analysis == 'false' container: image: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }}/centreon-collect-alma9:${{ inputs.img_version }} credentials: From 251ed03858bcce6faf1d6de871f0d1ae92ba0c5a Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Fri, 5 Jul 2024 10:19:21 +0200 Subject: [PATCH 864/948] fix(ci): update veracode workflow output reference --- .github/workflows/veracode-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/veracode-analysis.yml b/.github/workflows/veracode-analysis.yml index 742207077dc..83ca00924b1 100644 --- a/.github/workflows/veracode-analysis.yml +++ b/.github/workflows/veracode-analysis.yml @@ -64,7 +64,7 @@ jobs: name: Binary preparation runs-on: [self-hosted, collect] needs: [routing] - if: needs.build.outputs.skip_analysis == 'false' + if: needs.routing.outputs.skip_analysis == 'false' container: image: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }}/centreon-collect-alma9:${{ inputs.img_version }} credentials: From 2a4cf6d118e70a68fb52d786d1a7335d67120ca3 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Fri, 5 Jul 2024 10:25:38 +0200 Subject: [PATCH 865/948] fix(ci): fix veracode binary creation --- .github/workflows/veracode-analysis.yml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/veracode-analysis.yml b/.github/workflows/veracode-analysis.yml index 83ca00924b1..16dafdd9173 100644 --- a/.github/workflows/veracode-analysis.yml +++ b/.github/workflows/veracode-analysis.yml @@ -143,12 +143,17 @@ jobs: else echo "::warning::No '.veracode-exclusions' file found for this module. Skipping exclusion step" fi - echo "[INFO] - Keeping only build's non empty folders" - find build -empty -type d -delete - ls -la build - echo "[INFO] - Generating the tarball" - tar cvzf "${{ inputs.module_name }}-${{ github.sha }}-${{ github.run_id }}-veracode-binary.tar.gz" build + if [ "${{ inputs.module_name }}" = "centreon-collect" ]; then + echo "[INFO] - Keeping only build's non empty folders" + find build -empty -type d -delete + ls -la build + echo "[INFO] - Generating the tarball" + tar cvzf "${{ inputs.module_name }}-${{ github.sha }}-${{ github.run_id }}-veracode-binary.tar.gz" build + else + echo "[INFO] - Generating the tarball" + zip -rq "${{ inputs.module_name }}-${{ github.sha }}-${{ github.run_id }}-veracode-binary.zip" * + fi - name: Cache uses: actions/cache/save@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 From ea302786bf9719a27279d0b21c2528142c154775 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Fri, 5 Jul 2024 10:29:11 +0200 Subject: [PATCH 866/948] fix(ci): fix veracode built extension saved in cache --- .github/workflows/veracode-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/veracode-analysis.yml b/.github/workflows/veracode-analysis.yml index 16dafdd9173..93e4d6fee34 100644 --- a/.github/workflows/veracode-analysis.yml +++ b/.github/workflows/veracode-analysis.yml @@ -158,7 +158,7 @@ jobs: - name: Cache uses: actions/cache/save@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 with: - path: "${{ inputs.module_name }}-${{ github.sha }}-${{ github.run_id }}-veracode-binary.tar.gz" + path: "${{ inputs.module_name }}-${{ github.sha }}-${{ github.run_id }}-veracode-binary.${{ inputs.module_name == 'centreon-collect' && 'tar.gz' || 'zip' }}" key: "${{ inputs.module_name }}-${{ github.sha }}-${{ github.run_id }}-veracode-binary" policy-scan: From 5f09222b359ac967f5e97ab67b6b09e75c3a3b08 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Fri, 5 Jul 2024 10:35:35 +0200 Subject: [PATCH 867/948] fix(ci): fix veracode zip file path --- .github/workflows/veracode-analysis.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/veracode-analysis.yml b/.github/workflows/veracode-analysis.yml index 93e4d6fee34..e9b41d85811 100644 --- a/.github/workflows/veracode-analysis.yml +++ b/.github/workflows/veracode-analysis.yml @@ -151,8 +151,12 @@ jobs: echo "[INFO] - Generating the tarball" tar cvzf "${{ inputs.module_name }}-${{ github.sha }}-${{ github.run_id }}-veracode-binary.tar.gz" build else - echo "[INFO] - Generating the tarball" + echo "[INFO] - Generating the zip" zip -rq "${{ inputs.module_name }}-${{ github.sha }}-${{ github.run_id }}-veracode-binary.zip" * + if [ -n "${{ inputs.module_directory }}" ]; then + cd - + mv ${{ inputs.module_directory }}/${{ inputs.module_name }}-${{ github.sha }}-${{ github.run_id }}-veracode-binary.zip . + fi fi - name: Cache @@ -188,7 +192,7 @@ jobs: - name: Get build binary uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 with: - path: "${{ inputs.module_name }}-${{ github.sha }}-${{ github.run_id }}-veracode-binary.tar.gz" + path: "${{ inputs.module_name }}-${{ github.sha }}-${{ github.run_id }}-veracode-binary.${{ inputs.module_name == 'centreon-collect' && 'tar.gz' || 'zip' }}" key: "${{ inputs.module_name }}-${{ github.sha }}-${{ github.run_id }}-veracode-binary" - name: Sandbox scan @@ -197,7 +201,7 @@ jobs: with: appname: "${{ inputs.module_name }}" version: "${{ inputs.major_version }}.${{ inputs.minor_version }}_runId-${{ github.run_id }}" - filepath: "${{ inputs.module_name }}-${{ github.sha }}-${{ github.run_id }}-veracode-binary.tar.gz" + filepath: "${{ inputs.module_name }}-${{ github.sha }}-${{ github.run_id }}-veracode-binary.${{ inputs.module_name == 'centreon-collect' && 'tar.gz' || 'zip' }}" vid: "vera01ei-${{ secrets.veracode_api_id }}" vkey: "vera01es-${{ secrets.veracode_api_key }}" createprofile: true From 0037a766d20916223c828019edc0260cee64e94f Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Fri, 5 Jul 2024 11:34:55 +0200 Subject: [PATCH 868/948] enh(ci): exclude gorgone tests file from workflow triggers --- .github/workflows/gorgone.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 00560f4288b..d47ab7a04bd 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -14,6 +14,7 @@ on: - ready_for_review paths: - "gorgone/**" + - "!gorgone/tests/**" - "!gorgone/veracode.json" - "!gorgone/.veracode-exclusions" push: @@ -24,6 +25,7 @@ on: - "[2-9][0-9].[0-9][0-9].x" paths: - "gorgone/**" + - "!gorgone/tests/**" - "!gorgone/veracode.json" - "!gorgone/.veracode-exclusions" From fbd2f5b90c5cdeb6ddf088946e8a561a9ea6d1d4 Mon Sep 17 00:00:00 2001 From: jean-christophe81 <98889244+jean-christophe81@users.noreply.github.com> Date: Fri, 5 Jul 2024 15:20:13 +0200 Subject: [PATCH 869/948] MON-63843 centreon-monitoring-agent reverse-connection engine side (#1494) * fix agent test add reverse client in opentelemetry module * no overlays, provide our own launcher * review comments --- CMakeLists.txt | 1 + agent/CMakeLists.txt | 7 +- agent/inc/com/centreon/agent/check_exec.hh | 2 +- agent/test/check_exec_test.cc | 12 +- agent/test/scheduler_test.cc | 26 +- common/CMakeLists.txt | 3 +- common/doc/common-doc.md | 4 + common/process/CMakeLists.txt | 4 +- .../detail/centreon_posix_process_launcher.hh | 275 ++++++++++++++++++ .../com/centreon/common/process}/process.hh | 0 common/process/{ => src}/process.cc | 47 ++- common/tests/process_test.cc | 2 +- engine/modules/opentelemetry/CMakeLists.txt | 2 + .../opentelemetry/doc/opentelemetry.md | 32 +- .../centreon_agent/agent_reverse_client.hh | 62 ++++ .../centreon_agent/to_agent_connector.hh | 78 +++++ .../modules/opentelemetry/open_telemetry.hh | 2 + .../centreon_agent/agent_reverse_client.cc | 127 ++++++++ .../src/centreon_agent/to_agent_connector.cc | 222 ++++++++++++++ .../opentelemetry/src/open_telemetry.cc | 11 + engine/tests/CMakeLists.txt | 1 + .../agent_reverse_client_test.cc | 153 ++++++++++ 22 files changed, 1034 insertions(+), 39 deletions(-) create mode 100644 common/process/inc/com/centreon/common/process/detail/centreon_posix_process_launcher.hh rename common/{inc/com/centreon/common => process/inc/com/centreon/common/process}/process.hh (100%) rename common/process/{ => src}/process.cc (86%) create mode 100644 engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/centreon_agent/agent_reverse_client.hh create mode 100644 engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/centreon_agent/to_agent_connector.hh create mode 100644 engine/modules/opentelemetry/src/centreon_agent/agent_reverse_client.cc create mode 100644 engine/modules/opentelemetry/src/centreon_agent/to_agent_connector.cc create mode 100644 engine/tests/opentelemetry/agent_reverse_client_test.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index fc08f5d56c9..e53424c2fd5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,6 +69,7 @@ option(WITH_MALLOC_TRACE "compile centreon-malloc-trace library." OFF) # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -stdlib=libc++") # set(CMAKE_CXX_COMPILER "clang++") add_definitions("-D_GLIBCXX_USE_CXX11_ABI=1") +add_definitions("-DBOOST_PROCESS_USE_STD_FS=1") option(DEBUG_ROBOT OFF) diff --git a/agent/CMakeLists.txt b/agent/CMakeLists.txt index 7a8ec1a1036..55293a28bf0 100644 --- a/agent/CMakeLists.txt +++ b/agent/CMakeLists.txt @@ -117,6 +117,7 @@ include_directories( ${SRC_DIR} ${CMAKE_SOURCE_DIR}/common/inc ${CMAKE_SOURCE_DIR}/common/grpc/inc + ${CMAKE_SOURCE_DIR}/common/process/inc ) target_precompile_headers(centagent_lib PRIVATE precomp_inc/precomp.hh) @@ -139,12 +140,6 @@ target_link_libraries( target_precompile_headers(${CENTREON_AGENT} REUSE_FROM centagent_lib) -target_include_directories(${CENTREON_AGENT} PRIVATE - ${INCLUDE_DIR} - ${SRC_DIR} - ${CMAKE_SOURCE_DIR}/common/inc -) - set(AGENT_VAR_LOG_DIR "${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/log/centreon-monitoring-agent") diff --git a/agent/inc/com/centreon/agent/check_exec.hh b/agent/inc/com/centreon/agent/check_exec.hh index 42107040c4a..8adb1a35134 100644 --- a/agent/inc/com/centreon/agent/check_exec.hh +++ b/agent/inc/com/centreon/agent/check_exec.hh @@ -20,7 +20,7 @@ #define CENTREON_AGENT_CHECK_EXEC_HH #include "check.hh" -#include "com/centreon/common/process.hh" +#include "com/centreon/common/process/process.hh" namespace com::centreon::agent { diff --git a/agent/test/check_exec_test.cc b/agent/test/check_exec_test.cc index 34c050f48e0..c0a6ef1278c 100644 --- a/agent/test/check_exec_test.cc +++ b/agent/test/check_exec_test.cc @@ -32,6 +32,7 @@ TEST(check_exec_test, echo) { command_line = "/bin/echo hello toto"; int status; std::list<std::string> outputs; + std::mutex mut; std::condition_variable cond; std::shared_ptr<check_exec> check = check_exec::load( g_io_context, spdlog::default_logger(), time_point(), serv, cmd_name, @@ -40,13 +41,15 @@ TEST(check_exec_test, echo) { int statuss, const std::list<com::centreon::common::perfdata>& perfdata, const std::list<std::string>& output) { - status = statuss; - outputs = output; + { + std::lock_guard l(mut); + status = statuss; + outputs = output; + } cond.notify_one(); }); check->start_check(std::chrono::seconds(1)); - std::mutex mut; std::unique_lock l(mut); cond.wait(l); ASSERT_EQ(status, 0); @@ -98,7 +101,8 @@ TEST(check_exec_test, bad_command) { status = statuss; outputs = output; } - std::this_thread::sleep_for(std::chrono::milliseconds(10)); + SPDLOG_INFO("end of {}", command_line); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); cond.notify_one(); }); check->start_check(std::chrono::seconds(1)); diff --git a/agent/test/scheduler_test.cc b/agent/test/scheduler_test.cc index ccd9f47a7fc..c741f0c7aac 100644 --- a/agent/test/scheduler_test.cc +++ b/agent/test/scheduler_test.cc @@ -59,6 +59,7 @@ class tempo_check : public check { void start_check(const duration& timeout) override { { std::lock_guard l(check_starts_m); + SPDLOG_INFO("start tempo check"); check_starts.emplace_back(this, std::chrono::system_clock::now()); } check::start_check(timeout); @@ -148,6 +149,19 @@ TEST_F(scheduler_test, no_config) { ASSERT_FALSE(weak_shed.lock()); } +static bool tempo_check_assert_pred(const time_point& after, + const time_point& before) { + if ((after - before) <= std::chrono::milliseconds(40)) { + SPDLOG_ERROR("after={}, before={}", after, before); + return false; + } + if ((after - before) >= std::chrono::milliseconds(60)) { + SPDLOG_ERROR("after={}, before={}", after, before); + return false; + } + return true; +} + TEST_F(scheduler_test, correct_schedule) { std::shared_ptr<scheduler> sched = scheduler::load( g_io_context, spdlog::default_logger(), "my_host", @@ -185,10 +199,8 @@ TEST_F(scheduler_test, correct_schedule) { first = false; } else { ASSERT_NE(previous.first, check_time.first); - ASSERT_GT((check_time.second - previous.second), - expected_interval - std::chrono::milliseconds(1)); - ASSERT_LT((check_time.second - previous.second), - expected_interval + std::chrono::milliseconds(1)); + ASSERT_PRED2(tempo_check_assert_pred, check_time.second, + previous.second); } previous = check_time; } @@ -206,10 +218,8 @@ TEST_F(scheduler_test, correct_schedule) { first = false; } else { ASSERT_NE(previous.first, check_time.first); - ASSERT_TRUE((check_time.second - previous.second) > - expected_interval - std::chrono::milliseconds(1)); - ASSERT_TRUE((check_time.second - previous.second) < - expected_interval + std::chrono::milliseconds(1)); + ASSERT_PRED2(tempo_check_assert_pred, check_time.second, + previous.second); } previous = check_time; } diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 03b0488d216..e220207f827 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -24,7 +24,7 @@ add_subdirectory(engine_legacy_conf) # Set directories. set(INCLUDE_DIR "${PROJECT_SOURCE_DIR}/inc/com/centreon/common") -set(PROCESS_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/process") +set(PROCESS_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/process/inc") set(HTTP_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/http/inc/com/centreon/common/http") set(SRC_DIR "${PROJECT_SOURCE_DIR}/src") set(TEST_DIR "${PROJECT_SOURCE_DIR}/tests") @@ -64,7 +64,6 @@ include_directories("${INCLUDE_DIR}" add_definitions(-DSPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_TRACE) add_library(centreon_common STATIC ${SOURCES}) -target_include_directories(centreon_common PRIVATE ${INCLUDE_DIR}) set_property(TARGET centreon_common PROPERTY POSITION_INDEPENDENT_CODE ON) target_precompile_headers(centreon_common PRIVATE precomp_inc/precomp.hh) diff --git a/common/doc/common-doc.md b/common/doc/common-doc.md index 38959b0b41f..237453331f0 100644 --- a/common/doc/common-doc.md +++ b/common/doc/common-doc.md @@ -116,3 +116,7 @@ class process_wait : public process { ``` +### Asio bug work around +There is an issue in io_context::notify_fork. Internally, ctx.notify_fork calls epoll_reactor::notify_fork which locks registered_descriptors_mutex_. An issue occurs when registered_descriptors_mutex_ is locked by another thread at fork timepoint. +In such a case, child process starts with registered_descriptors_mutex_ already locked and both child and parent process will hang. + diff --git a/common/process/CMakeLists.txt b/common/process/CMakeLists.txt index cf33af66177..f79bbaaa657 100644 --- a/common/process/CMakeLists.txt +++ b/common/process/CMakeLists.txt @@ -16,14 +16,14 @@ # For more information : contact@centreon.com # -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +include_directories(${PROJECT_SOURCE_DIR}/process/inc) add_definitions(-DSPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_TRACE) add_definitions(${spdlog_DEFINITIONS}) add_library( centreon_process STATIC # Sources. - process.cc) + src/process.cc) target_precompile_headers(centreon_process REUSE_FROM centreon_common) diff --git a/common/process/inc/com/centreon/common/process/detail/centreon_posix_process_launcher.hh b/common/process/inc/com/centreon/common/process/detail/centreon_posix_process_launcher.hh new file mode 100644 index 00000000000..79dc1eac355 --- /dev/null +++ b/common/process/inc/com/centreon/common/process/detail/centreon_posix_process_launcher.hh @@ -0,0 +1,275 @@ +#ifndef CENTREON_POSIX_PROCESS_LAUNCHER_HH +#define CENTREON_POSIX_PROCESS_LAUNCHER_HH + +#include <boost/process/v2/posix/default_launcher.hpp> +#include <boost/process/v2/stdio.hpp> + +namespace boost::process::v2::posix { + +struct centreon_posix_default_launcher; + +struct centreon_process_stdio { + boost::process::v2::detail::process_input_binding in; + boost::process::v2::detail::process_output_binding out; + boost::process::v2::detail::process_error_binding err; + + error_code on_exec_setup(centreon_posix_default_launcher& launcher, + const filesystem::path&, + const char* const*) { + if (::dup2(in.fd, in.target) == -1) + return error_code(errno, system_category()); + + if (::dup2(out.fd, out.target) == -1) + return error_code(errno, system_category()); + + if (::dup2(err.fd, err.target) == -1) + return error_code(errno, system_category()); + + return error_code{}; + }; +}; + +/** + * This class is a copy of posix::default_launcher + * as io_context::notify_fork can hang on child process and as we don't care + * about child process in asio as we will do an exec, it's removed + */ +struct centreon_posix_default_launcher { + /// The pointer to the environment forwarded to the subprocess. + const char* const* env = ::environ; + /// The pid of the subprocess - will be assigned after fork. + int pid = -1; + + /// The whitelist for file descriptors. + std::vector<int> fd_whitelist = {STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO}; + + centreon_posix_default_launcher() = default; + + template <typename ExecutionContext, typename Args, typename... Inits> + auto operator()( + ExecutionContext& context, + const typename std::enable_if< + std::is_convertible< + ExecutionContext&, + BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context&>::value, + filesystem::path>::type& executable, + Args&& args, + Inits&&... inits) + -> basic_process<typename ExecutionContext::executor_type> { + error_code ec; + auto proc = (*this)(context, ec, executable, std::forward<Args>(args), + std::forward<Inits>(inits)...); + + if (ec) + v2::detail::throw_error(ec, "centreon_posix_default_launcher"); + + return proc; + } + + template <typename ExecutionContext, typename Args, typename... Inits> + auto operator()( + ExecutionContext& context, + error_code& ec, + const typename std::enable_if< + std::is_convertible< + ExecutionContext&, + BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context&>::value, + filesystem::path>::type& executable, + Args&& args, + Inits&&... inits) + -> basic_process<typename ExecutionContext::executor_type> { + return (*this)(context.get_executor(), executable, std::forward<Args>(args), + std::forward<Inits>(inits)...); + } + + template <typename Executor, typename Args, typename... Inits> + auto operator()( + Executor exec, + const typename std::enable_if< + BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::is_executor< + Executor>::value || + BOOST_PROCESS_V2_ASIO_NAMESPACE::is_executor<Executor>::value, + filesystem::path>::type& executable, + Args&& args, + Inits&&... inits) -> basic_process<Executor> { + error_code ec; + auto proc = + (*this)(std::move(exec), ec, executable, std::forward<Args>(args), + std::forward<Inits>(inits)...); + + if (ec) + v2::detail::throw_error(ec, "centreon_posix_default_launcher"); + + return proc; + } + + template <typename Executor, typename Args, typename... Inits> + auto operator()( + Executor exec, + error_code& ec, + const typename std::enable_if< + BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::is_executor< + Executor>::value || + BOOST_PROCESS_V2_ASIO_NAMESPACE::is_executor<Executor>::value, + filesystem::path>::type& executable, + Args&& args, + Inits&&... inits) -> basic_process<Executor> { + auto argv = this->build_argv_(executable, std::forward<Args>(args)); + { + pipe_guard pg; + if (::pipe(pg.p)) { + BOOST_PROCESS_V2_ASSIGN_EC(ec, errno, system_category()) + return basic_process<Executor>{exec}; + } + if (::fcntl(pg.p[1], F_SETFD, FD_CLOEXEC)) { + BOOST_PROCESS_V2_ASSIGN_EC(ec, errno, system_category()) + return basic_process<Executor>{exec}; + } + ec = detail::on_setup(*this, executable, argv, inits...); + if (ec) { + detail::on_error(*this, executable, argv, ec, inits...); + return basic_process<Executor>(exec); + } + fd_whitelist.push_back(pg.p[1]); + + auto& ctx = BOOST_PROCESS_V2_ASIO_NAMESPACE::query( + exec, BOOST_PROCESS_V2_ASIO_NAMESPACE::execution::context); + ctx.notify_fork( + BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_prepare); + pid = ::fork(); + if (pid == -1) { + ctx.notify_fork( + BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_parent); + detail::on_fork_error(*this, executable, argv, ec, inits...); + detail::on_error(*this, executable, argv, ec, inits...); + + BOOST_PROCESS_V2_ASSIGN_EC(ec, errno, system_category()) + return basic_process<Executor>{exec}; + } else if (pid == 0) { + ::close(pg.p[0]); + /** + * ctx.notify_fork calls epoll_reactor::notify_fork which locks + * registered_descriptors_mutex_ An issue occurs when + * registered_descriptors_mutex_ is locked by another thread at fork + * timepoint. In such a case, child process starts with + * registered_descriptors_mutex_ already locked and both child and + * parent process will hang. + */ + // ctx.notify_fork(BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_child); + ec = detail::on_exec_setup(*this, executable, argv, inits...); + if (!ec) { + close_all_fds(ec); + } + if (!ec) + ::execve(executable.c_str(), const_cast<char* const*>(argv), + const_cast<char* const*>(env)); + + ignore_unused(::write(pg.p[1], &errno, sizeof(int))); + BOOST_PROCESS_V2_ASSIGN_EC(ec, errno, system_category()) + detail::on_exec_error(*this, executable, argv, ec, inits...); + ::exit(EXIT_FAILURE); + return basic_process<Executor>{exec}; + } + + ctx.notify_fork( + BOOST_PROCESS_V2_ASIO_NAMESPACE::execution_context::fork_parent); + ::close(pg.p[1]); + pg.p[1] = -1; + int child_error{0}; + int count = -1; + while ((count = ::read(pg.p[0], &child_error, sizeof(child_error))) == + -1) { + int err = errno; + if ((err != EAGAIN) && (err != EINTR)) { + BOOST_PROCESS_V2_ASSIGN_EC(ec, err, system_category()) + break; + } + } + if (count != 0) + BOOST_PROCESS_V2_ASSIGN_EC(ec, child_error, system_category()) + + if (ec) { + detail::on_error(*this, executable, argv, ec, inits...); + return basic_process<Executor>{exec}; + } + } + basic_process<Executor> proc(exec, pid); + detail::on_success(*this, executable, argv, ec, inits...); + return proc; + } + + protected: + void ignore_unused(std::size_t) {} + void close_all_fds(error_code& ec) { + std::sort(fd_whitelist.begin(), fd_whitelist.end()); + detail::close_all(fd_whitelist, ec); + fd_whitelist = {STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO}; + } + + struct pipe_guard { + int p[2]; + pipe_guard() : p{-1, -1} {} + + ~pipe_guard() { + if (p[0] != -1) + ::close(p[0]); + if (p[1] != -1) + ::close(p[1]); + } + }; + + // if we need to allocate something + std::vector<std::string> argv_buffer_; + std::vector<const char*> argv_; + + template <typename Args> + const char* const* build_argv_( + const filesystem::path& pt, + const Args& args, + typename std::enable_if< + std::is_convertible<decltype(*std::begin(std::declval<Args>())), + cstring_ref>::value>::type* = nullptr) { + const auto arg_cnt = std::distance(std::begin(args), std::end(args)); + argv_.reserve(arg_cnt + 2); + argv_.push_back(pt.native().data()); + for (auto&& arg : args) + argv_.push_back(arg.c_str()); + + argv_.push_back(nullptr); + return argv_.data(); + } + + const char* const* build_argv_(const filesystem::path&, const char** argv) { + return argv; + } + + template <typename Args> + const char* const* build_argv_( + const filesystem::path& pt, + const Args& args, + typename std::enable_if< + !std::is_convertible<decltype(*std::begin(std::declval<Args>())), + cstring_ref>::value>::type* = nullptr) { + const auto arg_cnt = std::distance(std::begin(args), std::end(args)); + argv_.reserve(arg_cnt + 2); + argv_buffer_.reserve(arg_cnt); + argv_.push_back(pt.native().data()); + + using char_type = + typename decay<decltype((*std::begin(std::declval<Args>()))[0])>::type; + + for (basic_string_view<char_type> arg : args) + argv_buffer_.push_back( + v2::detail::conv_string<char>(arg.data(), arg.size())); + + for (auto&& arg : argv_buffer_) + argv_.push_back(arg.c_str()); + + argv_.push_back(nullptr); + return argv_.data(); + } +}; + +} // namespace boost::process::v2::posix + +#endif diff --git a/common/inc/com/centreon/common/process.hh b/common/process/inc/com/centreon/common/process/process.hh similarity index 100% rename from common/inc/com/centreon/common/process.hh rename to common/process/inc/com/centreon/common/process/process.hh diff --git a/common/process/process.cc b/common/process/src/process.cc similarity index 86% rename from common/process/process.cc rename to common/process/src/process.cc index 9e0282b38fb..09ef3545aef 100644 --- a/common/process/process.cc +++ b/common/process/src/process.cc @@ -1,27 +1,29 @@ -/* +/** * Copyright 2024 Centreon * - * This file is part of Centreon Engine. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * Centreon Engine is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License version 2 - * as published by the Free Software Foundation. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Centreon Engine 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. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. * - * You should have received a copy of the GNU General Public License - * along with Centreon Engine. If not, see - * <http://www.gnu.org/licenses/>. + * For more information : contact@centreon.com */ -#include <boost/process/v2/process.hpp> #include <boost/process/v2/stdio.hpp> #include <boost/program_options/parsers.hpp> -#include "process.hh" +#include "com/centreon/common/process/process.hh" + +#include "com/centreon/common/process/detail/centreon_posix_process_launcher.hh" + +#include <boost/process/v2/process.hpp> namespace proc = boost::process::v2; @@ -32,6 +34,7 @@ namespace com::centreon::common::detail { * */ struct boost_process { +#if defined(BOOST_PROCESS_V2_WINDOWS) boost_process(asio::io_context& io_context, const std::string& exe_path, const std::vector<std::string>& args) @@ -42,6 +45,20 @@ struct boost_process { exe_path, args, proc::process_stdio{stdin, stdout, stderr}) {} +#else + boost_process(asio::io_context& io_context, + const std::string& exe_path, + const std::vector<std::string>& args) + : stdout(io_context), + stderr(io_context), + stdin(io_context), + proc(proc::posix::centreon_posix_default_launcher() + /*proc::default_process_launcher()*/ ( + io_context.get_executor(), + exe_path, + args, + proc::posix::centreon_process_stdio{stdin, stdout, stderr})) {} +#endif asio::readable_pipe stdout; asio::readable_pipe stderr; @@ -92,6 +109,8 @@ void process::start_process() { try { _proc = std::make_shared<detail::boost_process>(*_io_context, _exe_path, _args); + SPDLOG_LOGGER_TRACE(_logger, "process started: {} pid: {}", _exe_path, + _proc->proc.id()); _proc->proc.async_wait( [me = shared_from_this(), current = _proc]( const boost::system::error_code& err, int raw_exit_status) { diff --git a/common/tests/process_test.cc b/common/tests/process_test.cc index 449e000d3bc..67cbc3630cb 100644 --- a/common/tests/process_test.cc +++ b/common/tests/process_test.cc @@ -19,8 +19,8 @@ #include <gtest/gtest.h> #include <spdlog/sinks/stdout_color_sinks.h> +#include "com/centreon/common/process/process.hh" #include "pool.hh" -#include "process.hh" using namespace com::centreon::common; diff --git a/engine/modules/opentelemetry/CMakeLists.txt b/engine/modules/opentelemetry/CMakeLists.txt index 16d1976fdc3..acb843bb80b 100644 --- a/engine/modules/opentelemetry/CMakeLists.txt +++ b/engine/modules/opentelemetry/CMakeLists.txt @@ -67,7 +67,9 @@ ${SRC_DIR}/centreon_agent/agent.pb.cc ${SRC_DIR}/centreon_agent/agent_check_result_builder.cc ${SRC_DIR}/centreon_agent/agent_config.cc ${SRC_DIR}/centreon_agent/agent_impl.cc +${SRC_DIR}/centreon_agent/agent_reverse_client.cc ${SRC_DIR}/centreon_agent/agent_service.cc +${SRC_DIR}/centreon_agent/to_agent_connector.cc ${SRC_DIR}/data_point_fifo.cc ${SRC_DIR}/data_point_fifo_container.cc ${SRC_DIR}/grpc_config.cc diff --git a/engine/modules/opentelemetry/doc/opentelemetry.md b/engine/modules/opentelemetry/doc/opentelemetry.md index 4e5867924f5..379643f2889 100644 --- a/engine/modules/opentelemetry/doc/opentelemetry.md +++ b/engine/modules/opentelemetry/doc/opentelemetry.md @@ -209,6 +209,8 @@ An example of configuration: ``` ### centreon monitoring agent + +#### agent connects to engine Even if all protobuf objects are opentelemetry objects, grpc communication is made in streaming mode. It is more efficient, it allows reverse connection (engine can connect to an agent running in a DMZ) and Engine can send configuration on each config update. You can find all grpc definitions are agent/proto/agent.proto. @@ -360,4 +362,32 @@ Configuration of agent is divided in two parts: The first part is owned by agent protobuf service (agent_service.cc), the second is build by a common code shared with telegraf server (conf_helper.hh) -So when centengine receives a HUP signal, opentelemetry::reload check configuration changes on each established connection and update also agent service conf part1 which is used to configure future incoming connections. \ No newline at end of file +So when centengine receives a HUP signal, opentelemetry::reload check configuration changes on each established connection and update also agent service conf part1 which is used to configure future incoming connections. + +#### engine connects to agent + +##### configuration +Each agent has its own grpc configuration. Each object in this array is a grpc configuration object like those we can find in Agent or server + +An example: +```json +{ + "max_length_grpc_log": 0, + "centreon_agent": { + "check_interval": 10, + "export_period": 15, + "reverse_connections": [ + { + "host": "127.0.0.1", + "port": 4317 + } + ] + } +} +``` + +#### classes +From this configuration an agent_reverse_client object maintains a list of endpoints engine has to connect to. It manages also agent list updates. +It contains a map of to_agent_connector indexed by config. +The role to_agent_connector is to maintain an alive connection to agent (agent_connection class). It owns an agent_connection class and recreates it in case of network failure. +Agent_connection holds a weak_ptr to agent_connection to warn it about connection failure. \ No newline at end of file diff --git a/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/centreon_agent/agent_reverse_client.hh b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/centreon_agent/agent_reverse_client.hh new file mode 100644 index 00000000000..cc02b91e8af --- /dev/null +++ b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/centreon_agent/agent_reverse_client.hh @@ -0,0 +1,62 @@ +/** + * Copyright 2024 Centreon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + */ + +#ifndef CCE_MOD_OTL_CENTREON_AGENT_AGENT_REVERSE_CLIENT_HH +#define CCE_MOD_OTL_CENTREON_AGENT_AGENT_REVERSE_CLIENT_HH + +#include "com/centreon/engine/modules/opentelemetry/centreon_agent/agent_config.hh" +#include "com/centreon/engine/modules/opentelemetry/otl_data_point.hh" + +namespace com::centreon::engine::modules::opentelemetry::centreon_agent { + +class to_agent_connector; + +class agent_reverse_client { + protected: + std::shared_ptr<boost::asio::io_context> _io_context; + agent_config::pointer _conf; + const metric_handler _metric_handler; + std::shared_ptr<spdlog::logger> _logger; + + using config_to_client = absl::btree_map<grpc_config::pointer, + std::shared_ptr<to_agent_connector>, + grpc_config_compare>; + absl::Mutex _agents_m; + config_to_client _agents ABSL_GUARDED_BY(_agents_m); + + virtual config_to_client::iterator _create_new_client_connection( + const grpc_config::pointer& agent_endpoint, + const agent_config::pointer& agent_conf) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(_agents_m); + + virtual void _shutdown_connection(config_to_client::const_iterator to_delete); + + public: + agent_reverse_client( + const std::shared_ptr<boost::asio::io_context>& io_context, + const metric_handler& handler, + const std::shared_ptr<spdlog::logger>& logger); + + virtual ~agent_reverse_client(); + + void update(const agent_config::pointer& new_conf); +}; + +} // namespace com::centreon::engine::modules::opentelemetry::centreon_agent + +#endif diff --git a/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/centreon_agent/to_agent_connector.hh b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/centreon_agent/to_agent_connector.hh new file mode 100644 index 00000000000..3fc016aebb9 --- /dev/null +++ b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/centreon_agent/to_agent_connector.hh @@ -0,0 +1,78 @@ +/** + * Copyright 2024 Centreon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + */ + +#ifndef CCE_MOD_OTL_CENTREON_AGENT_AGENT_CLIENT_HH +#define CCE_MOD_OTL_CENTREON_AGENT_AGENT_CLIENT_HH + +#include "centreon_agent/agent.grpc.pb.h" +#include "com/centreon/engine/modules/opentelemetry/centreon_agent/agent_config.hh" + +#include "com/centreon/common/grpc/grpc_client.hh" +#include "com/centreon/engine/modules/opentelemetry/otl_data_point.hh" + +namespace com::centreon::engine::modules::opentelemetry::centreon_agent { + +class agent_connection; + +/** + * @brief this class is used in case of reverse connection + * it maintains one connection to agent server and reconnect in case of failure + * + */ +class to_agent_connector + : public common::grpc::grpc_client_base, + public std::enable_shared_from_this<to_agent_connector> { + std::shared_ptr<boost::asio::io_context> _io_context; + metric_handler _metric_handler; + agent_config::pointer _conf; + + bool _alive; + std::unique_ptr<agent::ReversedAgentService::Stub> _stub; + + absl::Mutex _connection_m; + std::shared_ptr<agent_connection> _connection ABSL_GUARDED_BY(_connection_m); + + public: + to_agent_connector(const grpc_config::pointer& agent_endpoint_conf, + const std::shared_ptr<boost::asio::io_context>& io_context, + const agent_config::pointer& agent_conf, + const metric_handler& handler, + const std::shared_ptr<spdlog::logger>& logger); + + virtual ~to_agent_connector(); + + virtual void start(); + + static std::shared_ptr<to_agent_connector> load( + const grpc_config::pointer& agent_endpoint_conf, + const std::shared_ptr<boost::asio::io_context>& io_context, + const agent_config::pointer& agent_conf, + const metric_handler& handler, + const std::shared_ptr<spdlog::logger>& logger); + + void refresh_agent_configuration_if_needed( + const agent_config::pointer& new_conf); + + virtual void shutdown(); + + void on_error(); +}; + +} // namespace com::centreon::engine::modules::opentelemetry::centreon_agent + +#endif diff --git a/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/open_telemetry.hh b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/open_telemetry.hh index ee2d82e7ef3..aa601e0c951 100644 --- a/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/open_telemetry.hh +++ b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/open_telemetry.hh @@ -22,6 +22,7 @@ #include "com/centreon/engine/commands/otel_interface.hh" +#include "centreon_agent/agent_reverse_client.hh" #include "data_point_fifo_container.hh" #include "host_serv_extractor.hh" #include "otl_check_result_builder.hh" @@ -48,6 +49,7 @@ class open_telemetry : public commands::otel::open_telemetry_base { asio::system_timer _second_timer; std::shared_ptr<otl_server> _otl_server; std::shared_ptr<http::server> _telegraf_conf_server; + std::unique_ptr<centreon_agent::agent_reverse_client> _agent_reverse_client; using cmd_line_to_extractor_map = absl::btree_map<std::string, std::shared_ptr<host_serv_extractor>>; diff --git a/engine/modules/opentelemetry/src/centreon_agent/agent_reverse_client.cc b/engine/modules/opentelemetry/src/centreon_agent/agent_reverse_client.cc new file mode 100644 index 00000000000..7c38cee5ad4 --- /dev/null +++ b/engine/modules/opentelemetry/src/centreon_agent/agent_reverse_client.cc @@ -0,0 +1,127 @@ +/** + * Copyright 2024 Centreon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + */ + +#include "centreon_agent/agent_reverse_client.hh" +#include "centreon_agent/to_agent_connector.hh" + +using namespace com::centreon::engine::modules::opentelemetry::centreon_agent; + +/** + * @brief Construct a new agent reverse client::agent reverse client object + * + * @param io_context + * @param handler handler that will process received metrics + * @param logger + */ +agent_reverse_client::agent_reverse_client( + const std::shared_ptr<boost::asio::io_context>& io_context, + const metric_handler& handler, + const std::shared_ptr<spdlog::logger>& logger) + : _io_context(io_context), _metric_handler(handler), _logger(logger) {} + +/** + * @brief Destroy the agent reverse client::agent reverse client object + * it also shutdown all connectors + * + */ +agent_reverse_client::~agent_reverse_client() { + absl::MutexLock l(&_agents_m); + for (auto& conn : _agents) { + conn.second->shutdown(); + } + _agents.clear(); +} + +/** + * @brief update agent list by doing a symmetric difference + * + * @param new_conf + */ +void agent_reverse_client::update(const agent_config::pointer& new_conf) { + absl::MutexLock l(&_agents_m); + + auto connection_iterator = _agents.begin(); + + if (!new_conf) { + while (connection_iterator != _agents.end()) { + _shutdown_connection(connection_iterator); + connection_iterator = _agents.erase(connection_iterator); + } + return; + } + + auto conf_iterator = new_conf->get_agent_grpc_reverse_conf().begin(); + + while (connection_iterator != _agents.end() && + conf_iterator != new_conf->get_agent_grpc_reverse_conf().end()) { + int compare_res = connection_iterator->first->compare(**conf_iterator); + if (compare_res > 0) { + connection_iterator = + _create_new_client_connection(*conf_iterator, new_conf); + ++connection_iterator; + ++conf_iterator; + } else if (compare_res < 0) { + _shutdown_connection(connection_iterator); + connection_iterator = _agents.erase(connection_iterator); + } else { + connection_iterator->second->refresh_agent_configuration_if_needed( + new_conf); + ++connection_iterator; + ++conf_iterator; + } + } + + while (connection_iterator != _agents.end()) { + _shutdown_connection(connection_iterator); + connection_iterator = _agents.erase(connection_iterator); + } + + for (; conf_iterator != new_conf->get_agent_grpc_reverse_conf().end(); + ++conf_iterator) { + _create_new_client_connection(*conf_iterator, new_conf); + } +} + +/** + * @brief create and start a new agent reversed connection + * + * @param agent_endpoint endpoint to connect + * @param new_conf global agent configuration + * @return agent_reverse_client::config_to_client::iterator iterator to the new + * element inserted + */ +agent_reverse_client::config_to_client::iterator +agent_reverse_client::_create_new_client_connection( + const grpc_config::pointer& agent_endpoint, + const agent_config::pointer& agent_conf) { + auto insert_res = _agents.try_emplace( + agent_endpoint, + to_agent_connector::load(agent_endpoint, _io_context, agent_conf, + _metric_handler, _logger)); + return insert_res.first; +} + +/** + * @brief only shutdown client connection, no container erase + * + * @param to_delete + */ +void agent_reverse_client::_shutdown_connection( + config_to_client::const_iterator to_delete) { + to_delete->second->shutdown(); +} diff --git a/engine/modules/opentelemetry/src/centreon_agent/to_agent_connector.cc b/engine/modules/opentelemetry/src/centreon_agent/to_agent_connector.cc new file mode 100644 index 00000000000..e3eaf7918b2 --- /dev/null +++ b/engine/modules/opentelemetry/src/centreon_agent/to_agent_connector.cc @@ -0,0 +1,222 @@ +/** + * Copyright 2024 Centreon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + */ + +#include "com/centreon/common/defer.hh" + +#include "centreon_agent/to_agent_connector.hh" + +#include "centreon_agent/agent_impl.hh" + +using namespace com::centreon::engine::modules::opentelemetry::centreon_agent; + +namespace com::centreon::engine::modules::opentelemetry::centreon_agent { + +/** + * @brief reverse connection to an agent + * + */ +class agent_connection + : public agent_impl<::grpc::ClientBidiReactor<agent::MessageToAgent, + agent::MessageFromAgent>> { + std::weak_ptr<to_agent_connector> _parent; + + std::string _peer; + ::grpc::ClientContext _context; + + public: + agent_connection(const std::shared_ptr<boost::asio::io_context>& io_context, + const std::shared_ptr<to_agent_connector>& parent, + const agent_config::pointer& conf, + const metric_handler& handler, + const std::shared_ptr<spdlog::logger>& logger); + + ::grpc::ClientContext& get_context() { return _context; } + + void on_error() override; + + void shutdown() override; + + const std::string& get_peer() const override { return _peer; } +}; + +/** + * @brief Construct a new agent connection::agent connection object + * + * @param io_context + * @param parent to_agent_connector that had created this object + * @param handler handler called on every metric received + * @param logger + */ +agent_connection::agent_connection( + const std::shared_ptr<boost::asio::io_context>& io_context, + const std::shared_ptr<to_agent_connector>& parent, + const agent_config::pointer& conf, + const metric_handler& handler, + const std::shared_ptr<spdlog::logger>& logger) + : agent_impl<::grpc::ClientBidiReactor<agent::MessageToAgent, + agent::MessageFromAgent>>( + io_context, + "reverse_client", + conf, + handler, + logger) { + _peer = parent->get_conf()->get_hostport(); +} + +/** + * @brief called by OnReadDone or OnWriteDone when ok = false + * + */ +void agent_connection::on_error() { + std::shared_ptr<to_agent_connector> parent = _parent.lock(); + if (parent) { + parent->on_error(); + } +} + +/** + * @brief shutdown connection before delete + * + */ +void agent_connection::shutdown() { + absl::MutexLock l(&_protect); + if (_alive) { + _alive = false; + agent_impl<::grpc::ClientBidiReactor<agent::MessageToAgent, + agent::MessageFromAgent>>::shutdown(); + RemoveHold(); + _context.TryCancel(); + } +} + +}; // namespace com::centreon::engine::modules::opentelemetry::centreon_agent +/** + * @brief Construct a new agent client::agent client object + * use to_agent_connector instead + * @param conf + * @param io_context + * @param handler handler that will process received metrics + * @param logger + */ +to_agent_connector::to_agent_connector( + const grpc_config::pointer& agent_endpoint_conf, + const std::shared_ptr<boost::asio::io_context>& io_context, + const agent_config::pointer& agent_conf, + const metric_handler& handler, + const std::shared_ptr<spdlog::logger>& logger) + : common::grpc::grpc_client_base(agent_endpoint_conf, logger), + _io_context(io_context), + _conf(agent_conf), + _metric_handler(handler), + _alive(true) { + _stub = std::move(agent::ReversedAgentService::NewStub(_channel)); +} + +/** + * @brief Destroy the to agent connector::to agent connector object + * shutdown connection + */ +to_agent_connector::~to_agent_connector() { + shutdown(); +} + +/** + * @brief construct an start a new client + * + * @param conf conf of the agent endpoint + * @param io_context + * @param handler handler that will process received metrics + * @param logger + * @return std::shared_ptr<to_agent_connector> client created and started + */ +std::shared_ptr<to_agent_connector> to_agent_connector::load( + const grpc_config::pointer& agent_endpoint_conf, + const std::shared_ptr<boost::asio::io_context>& io_context, + const agent_config::pointer& agent_conf, + const metric_handler& handler, + const std::shared_ptr<spdlog::logger>& logger) { + std::shared_ptr<to_agent_connector> ret = + std::make_shared<to_agent_connector>(agent_endpoint_conf, io_context, + agent_conf, handler, logger); + ret->start(); + return ret; +} + +/** + * @brief connect to agent and initialize exchange + * + */ +void to_agent_connector::start() { + absl::MutexLock l(&_connection_m); + if (!_alive) { + return; + } + SPDLOG_LOGGER_INFO(get_logger(), "connect to {}", get_conf()->get_hostport()); + if (_connection) { + _connection->shutdown(); + _connection.reset(); + } + _connection = std::make_shared<agent_connection>( + _io_context, shared_from_this(), _conf, _metric_handler, get_logger()); + agent_connection::register_stream(_connection); + _stub->async()->Import(&_connection->get_context(), _connection.get()); + _connection->start_read(); + _connection->AddHold(); + _connection->StartCall(); +} + +/** + * @brief send conf to agent if something has changed (list of services, + * commands...) + * + */ +void to_agent_connector::refresh_agent_configuration_if_needed( + const agent_config::pointer& new_conf) { + absl::MutexLock l(&_connection_m); + if (_connection) { + _connection->calc_and_send_config_if_needed(new_conf); + } +} + +/** + * @brief shutdown configuration, once this method has been called, this object + * is dead and must be deleted + * + */ +void to_agent_connector::shutdown() { + absl::MutexLock l(&_connection_m); + if (_alive) { + SPDLOG_LOGGER_INFO(get_logger(), "shutdown client of {}", + get_conf()->get_hostport()); + if (_connection) { + _connection->shutdown(); + _connection.reset(); + } + _alive = false; + } +} + +/** + * @brief called by connection + * reconnection is delayed of 10 second + * + */ +void to_agent_connector::on_error() { + common::defer(_io_context, std::chrono::seconds(10), + [me = shared_from_this()] { me->start(); }); +} \ No newline at end of file diff --git a/engine/modules/opentelemetry/src/open_telemetry.cc b/engine/modules/opentelemetry/src/open_telemetry.cc index a89910b7293..98707492915 100644 --- a/engine/modules/opentelemetry/src/open_telemetry.cc +++ b/engine/modules/opentelemetry/src/open_telemetry.cc @@ -92,6 +92,17 @@ void open_telemetry::_reload() { new_conf->get_max_fifo_size()); _conf = std::move(new_conf); + + if (!_agent_reverse_client) { + _agent_reverse_client = + std::make_unique<centreon_agent::agent_reverse_client>( + _io_context, + [me = shared_from_this()](const metric_request_ptr& request) { + me->_on_metric(request); + }, + _logger); + } + _agent_reverse_client->update(_conf->get_centreon_agent_config()); } // push new configuration to connected agents centreon_agent::agent_impl<::grpc::ServerBidiReactor<agent::MessageFromAgent, diff --git a/engine/tests/CMakeLists.txt b/engine/tests/CMakeLists.txt index 56d3122b9ee..1dbadcf16ef 100755 --- a/engine/tests/CMakeLists.txt +++ b/engine/tests/CMakeLists.txt @@ -112,6 +112,7 @@ if(WITH_TESTING) "${TESTS_DIR}/notifications/service_flapping_notification.cc" "${TESTS_DIR}/notifications/service_downtime_notification_test.cc" "${TESTS_DIR}/opentelemetry/agent_check_result_builder_test.cc" + "${TESTS_DIR}/opentelemetry/agent_reverse_client_test.cc" "${TESTS_DIR}/opentelemetry/grpc_config_test.cc" "${TESTS_DIR}/opentelemetry/host_serv_extractor_test.cc" "${TESTS_DIR}/opentelemetry/otl_server_test.cc" diff --git a/engine/tests/opentelemetry/agent_reverse_client_test.cc b/engine/tests/opentelemetry/agent_reverse_client_test.cc new file mode 100644 index 00000000000..79c1e166682 --- /dev/null +++ b/engine/tests/opentelemetry/agent_reverse_client_test.cc @@ -0,0 +1,153 @@ +/** + * Copyright 2024 Centreon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + */ + +#include <gtest/gtest.h> + +#include "opentelemetry/proto/collector/metrics/v1/metrics_service.pb.h" +#include "opentelemetry/proto/common/v1/common.pb.h" +#include "opentelemetry/proto/metrics/v1/metrics.pb.h" + +#include "com/centreon/engine/modules/opentelemetry/centreon_agent/agent_reverse_client.hh" +#include "com/centreon/engine/modules/opentelemetry/centreon_agent/to_agent_connector.hh" + +using namespace com::centreon::engine::modules::opentelemetry; +using namespace com::centreon::engine::modules::opentelemetry::centreon_agent; + +extern std::shared_ptr<asio::io_context> g_io_context; + +struct fake_connector : public to_agent_connector { + using config_to_fake = absl::btree_map<grpc_config::pointer, + std::shared_ptr<to_agent_connector>, + grpc_config_compare>; + + fake_connector(const grpc_config::pointer& conf, + const std::shared_ptr<boost::asio::io_context>& io_context, + const centreon_agent::agent_config::pointer& agent_conf, + const metric_handler& handler, + const std::shared_ptr<spdlog::logger>& logger) + : to_agent_connector(conf, io_context, agent_conf, handler, logger) {} + + void start() override { + all_fake.emplace(std::static_pointer_cast<grpc_config>(get_conf()), + shared_from_this()); + } + + static std::shared_ptr<to_agent_connector> load( + const grpc_config::pointer& conf, + const std::shared_ptr<boost::asio::io_context>& io_context, + const centreon_agent::agent_config::pointer& agent_conf, + const metric_handler& handler, + const std::shared_ptr<spdlog::logger>& logger) { + std::shared_ptr<to_agent_connector> ret = std::make_shared<fake_connector>( + conf, io_context, agent_conf, handler, logger); + ret->start(); + return ret; + } + + static config_to_fake all_fake; + + void shutdown() override { + all_fake.erase(std::static_pointer_cast<grpc_config>(get_conf())); + } +}; + +fake_connector::config_to_fake fake_connector::all_fake; + +class my_agent_reverse_client : public agent_reverse_client { + public: + my_agent_reverse_client( + const std::shared_ptr<boost::asio::io_context>& io_context, + const metric_handler& handler, + const std::shared_ptr<spdlog::logger>& logger) + : agent_reverse_client(io_context, handler, logger) {} + + agent_reverse_client::config_to_client::iterator + _create_new_client_connection( + const grpc_config::pointer& agent_endpoint, + const agent_config::pointer& agent_conf) override { + return _agents + .try_emplace(agent_endpoint, + fake_connector::load(agent_endpoint, _io_context, + agent_conf, _metric_handler, _logger)) + .first; + } + + void _shutdown_connection(config_to_client::const_iterator to_delete) { + to_delete->second->shutdown(); + } +}; + +TEST(agent_reverse_client, update_config) { + my_agent_reverse_client to_test( + g_io_context, [](const metric_request_ptr&) {}, spdlog::default_logger()); + + ASSERT_TRUE(fake_connector::all_fake.empty()); + + auto agent_conf = std::shared_ptr<centreon_agent::agent_config>( + new centreon_agent::agent_config( + 60, 100, 60, 10, + {std::make_shared<grpc_config>("host1:port1", false)})); + to_test.update(agent_conf); + ASSERT_EQ(fake_connector::all_fake.size(), 1); + ASSERT_EQ(fake_connector::all_fake.begin()->first, + *agent_conf->get_agent_grpc_reverse_conf().begin()); + agent_conf = std::make_shared<centreon_agent::agent_config>(1, 100, 1, 10); + to_test.update(agent_conf); + ASSERT_EQ(fake_connector::all_fake.size(), 0); + + agent_conf = std::shared_ptr<centreon_agent::agent_config>( + new centreon_agent::agent_config( + 60, 100, 60, 10, + {std::make_shared<grpc_config>("host1:port1", false), + std::make_shared<grpc_config>("host1:port3", false)})); + to_test.update(agent_conf); + ASSERT_EQ(fake_connector::all_fake.size(), 2); + auto first_conn = fake_connector::all_fake.begin()->second; + auto second_conn = (++fake_connector::all_fake.begin())->second; + agent_conf = std::shared_ptr<centreon_agent::agent_config>( + new centreon_agent::agent_config( + 60, 100, 60, 10, + {std::make_shared<grpc_config>("host1:port1", false), + std::make_shared<grpc_config>("host1:port2", false), + std::make_shared<grpc_config>("host1:port3", false)})); + + to_test.update(agent_conf); + ASSERT_EQ(fake_connector::all_fake.size(), 3); + ASSERT_EQ(fake_connector::all_fake.begin()->second, first_conn); + ASSERT_EQ((++(++fake_connector::all_fake.begin()))->second, second_conn); + second_conn = (++fake_connector::all_fake.begin())->second; + auto third_conn = (++(++fake_connector::all_fake.begin()))->second; + + agent_conf = std::shared_ptr<centreon_agent::agent_config>( + new centreon_agent::agent_config( + 60, 100, 60, 10, + {std::make_shared<grpc_config>("host1:port1", false), + std::make_shared<grpc_config>("host1:port3", false)})); + to_test.update(agent_conf); + ASSERT_EQ(fake_connector::all_fake.size(), 2); + ASSERT_EQ(fake_connector::all_fake.begin()->second, first_conn); + ASSERT_EQ((++fake_connector::all_fake.begin())->second, third_conn); + + agent_conf = std::shared_ptr<centreon_agent::agent_config>( + new centreon_agent::agent_config( + 60, 100, 60, 10, + {std::make_shared<grpc_config>("host1:port3", false)})); + to_test.update(agent_conf); + ASSERT_EQ(fake_connector::all_fake.size(), 1); + ASSERT_EQ(fake_connector::all_fake.begin()->second, third_conn); +} \ No newline at end of file From 5c194185460d64457c0a3483ccb798f98526972b Mon Sep 17 00:00:00 2001 From: jean-christophe81 <98889244+jean-christophe81@users.noreply.github.com> Date: Mon, 8 Jul 2024 10:06:34 +0200 Subject: [PATCH 870/948] move utf8 to common and some fixes (#1502) --- agent/inc/com/centreon/agent/config.hh | 4 +- agent/inc/com/centreon/agent/scheduler.hh | 6 +- agent/precomp_inc/precomp.hh | 3 +- agent/src/check.cc | 6 +- agent/src/check_exec.cc | 12 +- agent/src/scheduler.cc | 34 +- broker/bam/src/reporting_stream.cc | 106 +++---- .../inc/com/centreon/broker/misc/string.hh | 24 +- broker/core/sql/src/mysql_stmt.cc | 6 +- broker/core/src/misc/string.cc | 258 +--------------- broker/core/test/misc/string.cc | 198 +----------- broker/lua/src/broker_utils.cc | 5 +- broker/neb/src/callbacks.cc | 291 ++++++++---------- .../storage/src/conflict_manager_storage.cc | 9 +- broker/unified_sql/src/stream_sql.cc | 59 ++-- broker/unified_sql/src/stream_storage.cc | 13 +- common/CMakeLists.txt | 1 + common/http/src/http_connection.cc | 2 +- common/inc/com/centreon/common/utf8.hh | 49 +++ .../com/centreon/common/process/process.hh | 2 +- common/process/src/process.cc | 132 ++++++-- common/src/utf8.cc | 275 +++++++++++++++++ common/tests/CMakeLists.txt | 1 + common/tests/process_test.cc | 25 +- common/tests/utf8_test.cc | 215 +++++++++++++ engine/CMakeLists.txt | 1 + .../modules/opentelemetry/conf_helper.hh | 20 +- .../src/centreon_agent/to_agent_connector.cc | 5 +- tests/init-sql-docker.sh | 4 +- 29 files changed, 952 insertions(+), 814 deletions(-) create mode 100644 common/inc/com/centreon/common/utf8.hh create mode 100644 common/src/utf8.cc create mode 100644 common/tests/utf8_test.cc diff --git a/agent/inc/com/centreon/agent/config.hh b/agent/inc/com/centreon/agent/config.hh index d0bd774f97a..0cd7b9d4821 100644 --- a/agent/inc/com/centreon/agent/config.hh +++ b/agent/inc/com/centreon/agent/config.hh @@ -1,4 +1,3 @@ - /** * Copyright 2024 Centreon * Licensed under the Apache License, Version 2.0(the "License"); @@ -15,6 +14,7 @@ * * For more information : contact@centreon.com */ + #ifndef CENTREON_AGENT_CONFIG_HH #define CENTREON_AGENT_CONFIG_HH @@ -24,7 +24,7 @@ namespace com::centreon::agent { class config { public: - enum log_type { to_stdout, to_file }; + enum log_type { to_stdout, to_file, to_event_log }; static const std::string_view config_schema; diff --git a/agent/inc/com/centreon/agent/scheduler.hh b/agent/inc/com/centreon/agent/scheduler.hh index fcd3d71a6fa..b1ed36edfbc 100644 --- a/agent/inc/com/centreon/agent/scheduler.hh +++ b/agent/inc/com/centreon/agent/scheduler.hh @@ -57,13 +57,13 @@ class scheduler : public std::enable_shared_from_this<scheduler> { // pointers in this struct point to _current_request struct scope_metric_request { ::opentelemetry::proto::metrics::v1::ScopeMetrics* scope_metric; - absl::flat_hash_map<std::string /*metric name*/, - ::opentelemetry::proto::metrics::v1::Metric*> + std::unordered_map<std::string /*metric name*/, + ::opentelemetry::proto::metrics::v1::Metric*> metrics; }; // one serv => one scope_metric => several metrics - absl::flat_hash_map<std::string, scope_metric_request> _serv_to_scope_metrics; + std::unordered_map<std::string, scope_metric_request> _serv_to_scope_metrics; std::shared_ptr<asio::io_context> _io_context; std::shared_ptr<spdlog::logger> _logger; diff --git a/agent/precomp_inc/precomp.hh b/agent/precomp_inc/precomp.hh index 1cc4bcd1c5f..8c9b04fb62a 100644 --- a/agent/precomp_inc/precomp.hh +++ b/agent/precomp_inc/precomp.hh @@ -30,8 +30,9 @@ #include <spdlog/fmt/ostr.h> #include <spdlog/spdlog.h> -#include <absl/container/flat_hash_map.h> +#include <absl/base/thread_annotations.h> #include <absl/strings/str_split.h> +#include <absl/synchronization/mutex.h> #include <boost/asio.hpp> diff --git a/agent/src/check.cc b/agent/src/check.cc index 562fd0329b2..27c29701f16 100644 --- a/agent/src/check.cc +++ b/agent/src/check.cc @@ -109,7 +109,8 @@ void check::_timeout_timer_handler(const boost::system::error_code& err, return; } if (start_check_index == _running_check_index) { - SPDLOG_LOGGER_ERROR(_logger, "check timeout for service {}", _service); + SPDLOG_LOGGER_ERROR(_logger, "check timeout for service {} cmd: {}", + _service, _command_name); on_completion(start_check_index, 3 /*unknown*/, std::list<com::centreon::common::perfdata>(), {"Timeout at execution of " + _command_line}); @@ -132,7 +133,8 @@ void check::on_completion( const std::list<com::centreon::common::perfdata>& perfdata, const std::list<std::string>& outputs) { if (start_check_index == _running_check_index) { - SPDLOG_LOGGER_TRACE(_logger, "end check for service {}", _service); + SPDLOG_LOGGER_TRACE(_logger, "end check for service {} cmd: {}", _service, + _command_name); _time_out_timer.cancel(); _running_check = false; ++_running_check_index; diff --git a/agent/src/check_exec.cc b/agent/src/check_exec.cc index d38d0deeac9..b26c07ab36b 100644 --- a/agent/src/check_exec.cc +++ b/agent/src/check_exec.cc @@ -44,7 +44,7 @@ void detail::process::start(unsigned running_index) { _stdout_eof = false; _running_index = running_index; _stdout.clear(); - common::process::start_process(); + common::process::start_process(false); } /** @@ -57,7 +57,7 @@ void detail::process::on_stdout_read(const boost::system::error_code& err, size_t nb_read) { if (!err && nb_read > 0) { _stdout.append(_stdout_read_buffer, nb_read); - } else if (err == asio::error::eof) { + } else if (err) { _stdout_eof = true; _on_completion(); } @@ -174,6 +174,7 @@ void check_exec::_init() { } catch (const std::exception& e) { SPDLOG_LOGGER_ERROR(_logger, "fail to create process of cmd_line '{}' : {}", get_command_line(), e.what()); + throw; } } @@ -231,8 +232,11 @@ void check_exec::_timeout_timer_handler(const boost::system::error_code& err, return; } if (start_check_index == _get_running_check_index()) { - check::_timeout_timer_handler(err, start_check_index); _process->kill(); + check::_timeout_timer_handler(err, start_check_index); + } else { + SPDLOG_LOGGER_ERROR(_logger, "start_check_index={}, running_index={}", + start_check_index, _get_running_check_index()); } } @@ -243,6 +247,8 @@ void check_exec::_timeout_timer_handler(const boost::system::error_code& err, */ void check_exec::on_completion(unsigned running_index) { if (running_index != _get_running_check_index()) { + SPDLOG_LOGGER_ERROR(_logger, "running_index={}, running_index={}", + running_index, _get_running_check_index()); return; } diff --git a/agent/src/scheduler.cc b/agent/src/scheduler.cc index 890f9d62dff..a08749884c2 100644 --- a/agent/src/scheduler.cc +++ b/agent/src/scheduler.cc @@ -17,6 +17,7 @@ */ #include "scheduler.hh" +#include "com/centreon/common/utf8.hh" using namespace com::centreon::agent; @@ -174,16 +175,23 @@ void scheduler::update(const engine_to_agent_request_ptr& conf) { "check expected to start at {} for service {}", next, serv.service_description()); } - _check_queue.emplace(_check_builder( - _io_context, _logger, next, serv.service_description(), - serv.command_name(), serv.command_line(), conf, - [me = shared_from_this()]( - const std::shared_ptr<check>& check, unsigned status, - const std::list<com::centreon::common::perfdata>& perfdata, - const std::list<std::string>& outputs) { - me->_check_handler(check, status, perfdata, outputs); - })); - next += check_interval; + try { + auto check_to_schedule = _check_builder( + _io_context, _logger, next, serv.service_description(), + serv.command_name(), serv.command_line(), conf, + [me = shared_from_this()]( + const std::shared_ptr<check>& check, unsigned status, + const std::list<com::centreon::common::perfdata>& perfdata, + const std::list<std::string>& outputs) { + me->_check_handler(check, status, perfdata, outputs); + }); + _check_queue.emplace(check_to_schedule); + next += check_interval; + } catch (const std::exception& e) { + SPDLOG_LOGGER_ERROR(_logger, + "service: {} command:{} won't be scheduled", + serv.service_description(), serv.command_name()); + } } } @@ -317,9 +325,9 @@ void scheduler::_store_result_in_metrics_and_exemplars( if (!outputs.empty()) { const std::string& first_line = *outputs.begin(); size_t pipe_pos = first_line.find('|'); - state_metrics->set_description(pipe_pos != std::string::npos - ? first_line.substr(0, pipe_pos) - : first_line); + state_metrics->set_description(common::check_string_utf8( + pipe_pos != std::string::npos ? first_line.substr(0, pipe_pos) + : first_line)); } auto data_point = state_metrics->mutable_gauge()->add_data_points(); data_point->set_time_unix_nano(now); diff --git a/broker/bam/src/reporting_stream.cc b/broker/bam/src/reporting_stream.cc index 6fd5a8e8903..e159484af9e 100644 --- a/broker/bam/src/reporting_stream.cc +++ b/broker/bam/src/reporting_stream.cc @@ -34,9 +34,9 @@ #include "com/centreon/broker/bam/ba.hh" #include "com/centreon/broker/exceptions/shutdown.hh" #include "com/centreon/broker/io/events.hh" -#include "com/centreon/broker/misc/string.hh" #include "com/centreon/broker/sql/table_max_size.hh" #include "com/centreon/broker/time/timezone_manager.hh" +#include "com/centreon/common/utf8.hh" #include "com/centreon/exceptions/msg_fmt.hh" #include "common/log_v2/log_v2.hh" @@ -543,25 +543,25 @@ struct bulk_dimension_kpi_binder { binder.set_value_as_i32(0, dk.kpi_id); binder.set_value_as_str( 1, - misc::string::truncate( + com::centreon::common::truncate_utf8( kpi_name, get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_kpi_name))); binder.set_value_as_i32(2, dk.ba_id); binder.set_value_as_str( 3, - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.ba_name, get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_ba_name))); binder.set_value_as_i32(4, dk.host_id); binder.set_value_as_str( - 5, misc::string::truncate( + 5, com::centreon::common::truncate_utf8( dk.host_name, get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_host_name))); binder.set_value_as_i32(6, dk.service_id); binder.set_value_as_str( 7, - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.service_description, get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_service_description))); @@ -570,14 +570,14 @@ struct bulk_dimension_kpi_binder { else binder.set_null_i32(8); binder.set_value_as_str( - 9, misc::string::truncate( + 9, com::centreon::common::truncate_utf8( dk.kpi_ba_name, get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_kpi_ba_name))); binder.set_value_as_i32(10, dk.meta_service_id); binder.set_value_as_str( 11, - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.meta_service_name, get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_meta_service_name))); @@ -586,7 +586,7 @@ struct bulk_dimension_kpi_binder { binder.set_value_as_f32(14, dk.impact_unknown); binder.set_value_as_i32(15, dk.boolean_id); binder.set_value_as_str( - 16, misc::string::truncate( + 16, com::centreon::common::truncate_utf8( dk.boolean_name, get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_boolean_name))); @@ -612,25 +612,25 @@ struct bulk_dimension_kpi_binder { binder.set_value_as_i32(0, dk.kpi_id()); binder.set_value_as_str( 1, - misc::string::truncate( + com::centreon::common::truncate_utf8( kpi_name, get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_kpi_name))); binder.set_value_as_i32(2, dk.ba_id()); binder.set_value_as_str( - 3, misc::string::truncate( + 3, com::centreon::common::truncate_utf8( dk.ba_name(), get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_ba_name))); binder.set_value_as_i32(4, dk.host_id()); binder.set_value_as_str( - 5, misc::string::truncate( + 5, com::centreon::common::truncate_utf8( dk.host_name(), get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_host_name))); binder.set_value_as_i32(6, dk.service_id()); binder.set_value_as_str( 7, - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.service_description(), get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_service_description))); @@ -639,14 +639,14 @@ struct bulk_dimension_kpi_binder { else binder.set_null_i32(8); binder.set_value_as_str( - 9, misc::string::truncate( + 9, com::centreon::common::truncate_utf8( dk.kpi_ba_name(), get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_kpi_ba_name))); binder.set_value_as_i32(10, dk.meta_service_id()); binder.set_value_as_str( 11, - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.meta_service_name(), get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_meta_service_name))); @@ -655,7 +655,7 @@ struct bulk_dimension_kpi_binder { binder.set_value_as_f32(14, dk.impact_unknown()); binder.set_value_as_i32(15, dk.boolean_id()); binder.set_value_as_str( - 16, misc::string::truncate( + 16, com::centreon::common::truncate_utf8( dk.boolean_name(), get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_boolean_name))); @@ -691,36 +691,36 @@ struct dimension_kpi_binder { return fmt::format( "({},'{}',{},'{}',{},'{}',{},'{}',{},'{}',{},'{}',{},{},{},{},'{}')", dk.kpi_id, - misc::string::truncate( + com::centreon::common::truncate_utf8( kpi_name, get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_kpi_name)), dk.ba_id, - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.ba_name, get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_ba_name)), dk.host_id, - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.host_name, get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_host_name)), dk.service_id, - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.service_description, get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_service_description)), sz_kpi_ba_id, - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.kpi_ba_name, get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_kpi_ba_name)), dk.meta_service_id, - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.meta_service_name, get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_meta_service_name)), dk.impact_warning, dk.impact_critical, dk.impact_unknown, dk.boolean_id, - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.boolean_name, get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_boolean_name))); @@ -747,37 +747,37 @@ struct dimension_kpi_binder { return fmt::format( "({},'{}',{},'{}',{},'{}',{},'{}',{},'{}',{},'{}',{},{},{},{},'{}')", dk.kpi_id(), - misc::string::truncate( + com::centreon::common::truncate_utf8( kpi_name, get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_kpi_name)), dk.ba_id(), - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.ba_name(), get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_ba_name)), dk.host_id(), - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.host_name(), get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_host_name)), dk.service_id(), - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.service_description(), get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_service_description)), sz_kpi_ba_id, - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.kpi_ba_name(), get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_kpi_ba_name)), dk.meta_service_id(), - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.meta_service_name(), get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_meta_service_name)), dk.impact_warning(), dk.impact_critical(), dk.impact_unknown(), dk.boolean_id(), - misc::string::truncate( + com::centreon::common::truncate_utf8( dk.boolean_name(), get_centreon_storage_mod_bam_reporting_kpi_col_size( centreon_storage_mod_bam_reporting_kpi_boolean_name))); @@ -1455,11 +1455,11 @@ void reporting_stream::_process_dimension_ba( dba.ba_id, dba.ba_description); _dimension_ba_insert.bind_value_as_i32(0, dba.ba_id); _dimension_ba_insert.bind_value_as_str( - 1, misc::string::truncate( + 1, com::centreon::common::truncate_utf8( dba.ba_name, get_centreon_storage_mod_bam_reporting_ba_col_size( centreon_storage_mod_bam_reporting_ba_ba_name))); _dimension_ba_insert.bind_value_as_str( - 2, misc::string::truncate( + 2, com::centreon::common::truncate_utf8( dba.ba_description, get_centreon_storage_mod_bam_reporting_ba_col_size( centreon_storage_mod_bam_reporting_ba_ba_description))); @@ -1485,11 +1485,11 @@ void reporting_stream::_process_pb_dimension_ba( _dimension_ba_insert.bind_value_as_i32(0, dba.ba_id()); _dimension_ba_insert.bind_value_as_str( 1, - misc::string::truncate( + com::centreon::common::truncate_utf8( dba.ba_name(), get_centreon_storage_mod_bam_reporting_ba_col_size( centreon_storage_mod_bam_reporting_ba_ba_name))); _dimension_ba_insert.bind_value_as_str( - 2, misc::string::truncate( + 2, com::centreon::common::truncate_utf8( dba.ba_description(), get_centreon_storage_mod_bam_reporting_ba_col_size( centreon_storage_mod_bam_reporting_ba_ba_description))); @@ -1514,11 +1514,11 @@ void reporting_stream::_process_dimension_bv( _dimension_bv_insert.bind_value_as_i32(0, dbv.bv_id); _dimension_bv_insert.bind_value_as_str( - 1, misc::string::truncate( + 1, com::centreon::common::truncate_utf8( dbv.bv_name, get_centreon_storage_mod_bam_reporting_bv_col_size( centreon_storage_mod_bam_reporting_bv_bv_name))); _dimension_bv_insert.bind_value_as_str( - 2, misc::string::truncate( + 2, com::centreon::common::truncate_utf8( dbv.bv_description, get_centreon_storage_mod_bam_reporting_bv_col_size( centreon_storage_mod_bam_reporting_bv_bv_description))); @@ -1541,11 +1541,11 @@ void reporting_stream::_process_pb_dimension_bv( _dimension_bv_insert.bind_value_as_i32(0, dbv.bv_id()); _dimension_bv_insert.bind_value_as_str( 1, - misc::string::truncate( + com::centreon::common::truncate_utf8( dbv.bv_name(), get_centreon_storage_mod_bam_reporting_bv_col_size( centreon_storage_mod_bam_reporting_bv_bv_name))); _dimension_bv_insert.bind_value_as_str( - 2, misc::string::truncate( + 2, com::centreon::common::truncate_utf8( dbv.bv_description(), get_centreon_storage_mod_bam_reporting_bv_col_size( centreon_storage_mod_bam_reporting_bv_bv_description))); @@ -1896,42 +1896,42 @@ void reporting_stream::_process_pb_dimension_timeperiod( tp.id(), tp.name()); _dimension_timeperiod_insert.bind_value_as_i32(0, tp.id()); _dimension_timeperiod_insert.bind_value_as_str( - 1, misc::string::truncate( + 1, com::centreon::common::truncate_utf8( tp.name(), get_centreon_storage_mod_bam_reporting_timeperiods_col_size( centreon_storage_mod_bam_reporting_timeperiods_name))); _dimension_timeperiod_insert.bind_value_as_str( - 2, misc::string::truncate( + 2, com::centreon::common::truncate_utf8( tp.sunday(), get_centreon_storage_mod_bam_reporting_timeperiods_col_size( centreon_storage_mod_bam_reporting_timeperiods_sunday))); _dimension_timeperiod_insert.bind_value_as_str( - 3, misc::string::truncate( + 3, com::centreon::common::truncate_utf8( tp.monday(), get_centreon_storage_mod_bam_reporting_timeperiods_col_size( centreon_storage_mod_bam_reporting_timeperiods_monday))); _dimension_timeperiod_insert.bind_value_as_str( - 4, misc::string::truncate( + 4, com::centreon::common::truncate_utf8( tp.tuesday(), get_centreon_storage_mod_bam_reporting_timeperiods_col_size( centreon_storage_mod_bam_reporting_timeperiods_tuesday))); _dimension_timeperiod_insert.bind_value_as_str( - 5, misc::string::truncate( + 5, com::centreon::common::truncate_utf8( tp.wednesday(), get_centreon_storage_mod_bam_reporting_timeperiods_col_size( centreon_storage_mod_bam_reporting_timeperiods_wednesday))); _dimension_timeperiod_insert.bind_value_as_str( - 6, misc::string::truncate( + 6, com::centreon::common::truncate_utf8( tp.thursday(), get_centreon_storage_mod_bam_reporting_timeperiods_col_size( centreon_storage_mod_bam_reporting_timeperiods_thursday))); _dimension_timeperiod_insert.bind_value_as_str( - 7, misc::string::truncate( + 7, com::centreon::common::truncate_utf8( tp.friday(), get_centreon_storage_mod_bam_reporting_timeperiods_col_size( centreon_storage_mod_bam_reporting_timeperiods_friday))); _dimension_timeperiod_insert.bind_value_as_str( - 8, misc::string::truncate( + 8, com::centreon::common::truncate_utf8( tp.saturday(), get_centreon_storage_mod_bam_reporting_timeperiods_col_size( centreon_storage_mod_bam_reporting_timeperiods_saturday))); @@ -1958,41 +1958,41 @@ void reporting_stream::_process_dimension_timeperiod( _dimension_timeperiod_insert.bind_value_as_i32(0, tp.id); _dimension_timeperiod_insert.bind_value_as_str( 1, - misc::string::truncate( + com::centreon::common::truncate_utf8( tp.name, get_centreon_storage_mod_bam_reporting_timeperiods_col_size( centreon_storage_mod_bam_reporting_timeperiods_name))); _dimension_timeperiod_insert.bind_value_as_str( - 2, misc::string::truncate( + 2, com::centreon::common::truncate_utf8( tp.sunday, get_centreon_storage_mod_bam_reporting_timeperiods_col_size( centreon_storage_mod_bam_reporting_timeperiods_sunday))); _dimension_timeperiod_insert.bind_value_as_str( - 3, misc::string::truncate( + 3, com::centreon::common::truncate_utf8( tp.monday, get_centreon_storage_mod_bam_reporting_timeperiods_col_size( centreon_storage_mod_bam_reporting_timeperiods_monday))); _dimension_timeperiod_insert.bind_value_as_str( - 4, misc::string::truncate( + 4, com::centreon::common::truncate_utf8( tp.tuesday, get_centreon_storage_mod_bam_reporting_timeperiods_col_size( centreon_storage_mod_bam_reporting_timeperiods_tuesday))); _dimension_timeperiod_insert.bind_value_as_str( - 5, misc::string::truncate( + 5, com::centreon::common::truncate_utf8( tp.wednesday, get_centreon_storage_mod_bam_reporting_timeperiods_col_size( centreon_storage_mod_bam_reporting_timeperiods_wednesday))); _dimension_timeperiod_insert.bind_value_as_str( - 6, misc::string::truncate( + 6, com::centreon::common::truncate_utf8( tp.thursday, get_centreon_storage_mod_bam_reporting_timeperiods_col_size( centreon_storage_mod_bam_reporting_timeperiods_thursday))); _dimension_timeperiod_insert.bind_value_as_str( - 7, misc::string::truncate( + 7, com::centreon::common::truncate_utf8( tp.friday, get_centreon_storage_mod_bam_reporting_timeperiods_col_size( centreon_storage_mod_bam_reporting_timeperiods_friday))); _dimension_timeperiod_insert.bind_value_as_str( - 8, misc::string::truncate( + 8, com::centreon::common::truncate_utf8( tp.saturday, get_centreon_storage_mod_bam_reporting_timeperiods_col_size( centreon_storage_mod_bam_reporting_timeperiods_saturday))); diff --git a/broker/core/inc/com/centreon/broker/misc/string.hh b/broker/core/inc/com/centreon/broker/misc/string.hh index 2ee2db16d8e..03c234bdcaf 100644 --- a/broker/core/inc/com/centreon/broker/misc/string.hh +++ b/broker/core/inc/com/centreon/broker/misc/string.hh @@ -24,7 +24,8 @@ #include <cerrno> namespace com::centreon::broker::misc::string { -inline std::string& replace(std::string& str, std::string const& old_str, +inline std::string& replace(std::string& str, + std::string const& old_str, std::string const& new_str) { std::size_t pos(str.find(old_str, 0)); while (pos != std::string::npos) { @@ -37,28 +38,7 @@ inline std::string& replace(std::string& str, std::string const& old_str, std::string& trim(std::string& str) throw(); std::string base64_encode(std::string const& str); bool is_number(const std::string& s); -std::string check_string_utf8(const std::string& str) noexcept; -/** - * @brief This function works almost like the resize method but takes care - * of the UTF-8 encoding and avoids to cut a string in the middle of a - * character. This function assumes the string to be UTF-8 encoded. - * - * @param str A string to truncate. - * @param s The desired size, maybe the resulting string will contain less - * characters. - * - * @return a reference to the string str. - */ -template <typename T> -fmt::string_view truncate(const T& str, size_t s) { - if (s >= str.size()) return fmt::string_view(str); - if (s > 0) - while ((str[s] & 0xc0) == 0x80) s--; - return fmt::string_view(str.data(), s); -} - -size_t adjust_size_utf8(const std::string& str, size_t s); std::string escape(const std::string& str, size_t s); std::string debug_buf(const char* data, int32_t size, int max_len = 10); diff --git a/broker/core/sql/src/mysql_stmt.cc b/broker/core/sql/src/mysql_stmt.cc index c3222c0510f..728e8d4473d 100644 --- a/broker/core/sql/src/mysql_stmt.cc +++ b/broker/core/sql/src/mysql_stmt.cc @@ -24,7 +24,7 @@ #include "com/centreon/broker/io/events.hh" #include "com/centreon/broker/io/protobuf.hh" #include "com/centreon/broker/mapping/entry.hh" -#include "com/centreon/broker/misc/string.hh" +#include "com/centreon/common/utf8.hh" using namespace com::centreon::exceptions; using namespace com::centreon::broker; @@ -166,7 +166,7 @@ void mysql_stmt::operator<<(io::data const& d) { "column '{}' should admit a longer string, it is cut to {} " "characters to be stored anyway.", current_entry->get_name_v2(), max_len); - max_len = misc::string::adjust_size_utf8(v, max_len); + max_len = common::adjust_size_utf8(v, max_len); sv = fmt::string_view(v.data(), max_len); } else sv = fmt::string_view(v); @@ -283,7 +283,7 @@ void mysql_stmt::operator<<(io::data const& d) { "column '{}' should admit a longer string, it is cut to {} " "characters to be stored anyway.", field, max_len); - max_len = misc::string::adjust_size_utf8(v, max_len); + max_len = common::adjust_size_utf8(v, max_len); sv = fmt::string_view(v.data(), max_len); } else sv = fmt::string_view(v); diff --git a/broker/core/src/misc/string.cc b/broker/core/src/misc/string.cc index 263f6cbe9fd..4bb8fa5f4d6 100644 --- a/broker/core/src/misc/string.cc +++ b/broker/core/src/misc/string.cc @@ -17,6 +17,7 @@ */ #include "com/centreon/broker/misc/string.hh" +#include "com/centreon/common/utf8.hh" #include <fmt/format.h> @@ -74,259 +75,6 @@ bool string::is_number(const std::string& s) { }) == s.end(); } -/** - * @brief Checks if the string given as parameter is a real UTF-8 string. - * If it is not, it tries to convert it to UTF-8. Encodings correctly changed - * are ISO-8859-15 and CP-1252. - * - * @param str The string to check - * - * @return The string itself or a new string converted to UTF-8. The output - * string should always be an UTF-8 string. - */ -std::string string::check_string_utf8(std::string const& str) noexcept { - std::string::const_iterator it; - for (it = str.begin(); it != str.end();) { - uint32_t val = (*it & 0xff); - if ((val & 0x80) == 0) { - ++it; - continue; - } - val = (val << 8) | (*(it + 1) & 0xff); - if ((val & 0xe0c0) == 0xc080) { - val &= 0x1e00; - if (val == 0) - break; - it += 2; - continue; - } - - val = (val << 8) | (*(it + 2) & 0xff); - if ((val & 0xf0c0c0) == 0xe08080) { - val &= 0xf2000; - if (val == 0 || val == 0xd2000) - break; - it += 3; - continue; - } - - val = (val << 8) | (*(it + 3) & 0xff); - if ((val & 0xf8c0c0c0) == 0xF0808080) { - val &= 0x7300000; - if (val == 0 || val > 0x4000000) - break; - it += 4; - continue; - } - break; - } - - if (it == str.end()) - return str; - - /* Not an UTF-8 string */ - bool is_cp1252 = true, is_iso8859 = true; - auto itt = it; - - auto iso8859_to_utf8 = [&str, &it]() -> std::string { - /* Strings are both cp1252 and iso8859-15 */ - std::string out; - std::size_t d = it - str.begin(); - out.reserve(d + 2 * (str.size() - d)); - out = str.substr(0, d); - while (it != str.end()) { - uint8_t c = static_cast<uint8_t>(*it); - if (c < 128) - out.push_back(c); - else if (c <= 160) - out.push_back('_'); - else { - switch (c) { - case 0xa4: - out.append("€"); - break; - case 0xa6: - out.append("Š"); - break; - case 0xa8: - out.append("š"); - break; - case 0xb4: - out.append("Ž"); - break; - case 0xb8: - out.append("ž"); - break; - case 0xbc: - out.append("Œ"); - break; - case 0xbd: - out.append("œ"); - break; - case 0xbe: - out.append("Ÿ"); - break; - default: - out.push_back(0xc0 | c >> 6); - out.push_back((c & 0x3f) | 0x80); - break; - } - } - ++it; - } - return out; - }; - do { - uint8_t c = *itt; - /* not ISO-8859-15 */ - if (c > 126 && c < 160) - is_iso8859 = false; - /* not cp1252 */ - if (c & 128) - if (c == 129 || c == 141 || c == 143 || c == 144 || c == 155) - is_cp1252 = false; - if (!is_cp1252) - return iso8859_to_utf8(); - else if (!is_iso8859) { - std::string out; - std::size_t d = it - str.begin(); - out.reserve(d + 3 * (str.size() - d)); - out = str.substr(0, d); - while (it != str.end()) { - c = *it; - if (c < 128) - out.push_back(c); - else { - switch (c) { - case 128: - out.append("€"); - break; - case 129: - case 141: - case 143: - case 144: - case 157: - out.append("_"); - break; - case 130: - out.append("‚"); - break; - case 131: - out.append("ƒ"); - break; - case 132: - out.append("„"); - break; - case 133: - out.append("…"); - break; - case 134: - out.append("†"); - break; - case 135: - out.append("‡"); - break; - case 136: - out.append("ˆ"); - break; - case 137: - out.append("‰"); - break; - case 138: - out.append("Š"); - break; - case 139: - out.append("‹"); - break; - case 140: - out.append("Œ"); - break; - case 142: - out.append("Ž"); - break; - case 145: - out.append("‘"); - break; - case 146: - out.append("’"); - break; - case 147: - out.append("“"); - break; - case 148: - out.append("”"); - break; - case 149: - out.append("•"); - break; - case 150: - out.append("–"); - break; - case 151: - out.append("—"); - break; - case 152: - out.append("˜"); - break; - case 153: - out.append("™"); - break; - case 154: - out.append("š"); - break; - case 155: - out.append("›"); - break; - case 156: - out.append("œ"); - break; - case 158: - out.append("ž"); - break; - case 159: - out.append("Ÿ"); - break; - default: - out.push_back(0xc0 | c >> 6); - out.push_back((c & 0x3f) | 0x80); - break; - } - } - ++it; - } - return out; - } - ++itt; - } while (itt != str.end()); - assert(is_cp1252 == is_iso8859); - return iso8859_to_utf8(); -} - -/** - * @brief This function adjusts the given integer s so that the str string may - * be cut at this length and still be a UTF-8 string (we don't want to cut it - * in a middle of a character). - * - * This function assumes the string to be UTF-8 encoded. - * - * @param str A string to truncate. - * @param s The desired size, maybe the resulting string will contain less - * characters. - * - * @return The newly computed size. - */ -size_t string::adjust_size_utf8(const std::string& str, size_t s) { - if (s >= str.size()) - return str.size(); - if (s == 0) - return s; - else { - while ((str[s] & 0xc0) == 0x80) - s--; - return s; - } -} - /** * @brief Escape the given string so that it can be directly inserted into the * database. Essntially, characters \ and ' are prefixed with \. The function @@ -340,7 +88,7 @@ size_t string::adjust_size_utf8(const std::string& str, size_t s) { std::string string::escape(const std::string& str, size_t s) { size_t found = str.find_first_of("'\\"); if (found == std::string::npos) - return str.substr(0, adjust_size_utf8(str, s)); + return str.substr(0, common::adjust_size_utf8(str, s)); else { std::string ret; /* ret is reserved with the worst size */ @@ -362,7 +110,7 @@ std::string string::escape(const std::string& str, size_t s) { ret += str[ffound]; found = ffound; } while (found < s); - ret.resize(adjust_size_utf8(ret, s)); + ret.resize(common::adjust_size_utf8(ret, s)); if (ret.size() > 1) { auto it = --ret.end(); size_t nb{0}; diff --git a/broker/core/test/misc/string.cc b/broker/core/test/misc/string.cc index 947157ba219..cf18b6edf3f 100644 --- a/broker/core/test/misc/string.cc +++ b/broker/core/test/misc/string.cc @@ -23,6 +23,7 @@ #include <gtest/gtest.h> #include "com/centreon/broker/misc/misc.hh" +#include "com/centreon/common/utf8.hh" using namespace com::centreon::broker::misc; @@ -56,201 +57,6 @@ TEST(StringBase64, Encode) { ASSERT_EQ(string::base64_encode("ABC"), "QUJD"); } -/* - * Given a string encoded in ISO-8859-15 and CP-1252 - * Then the check_string_utf8 function converts it to UTF-8. - */ -TEST(string_check_utf8, simple) { - std::string txt("L'acc\350s \340 l'h\364tel est encombr\351"); - ASSERT_EQ(string::check_string_utf8(txt), "L'accès à l'hôtel est encombré"); -} - -/* - * Given a string encoded in UTF-8 - * Then the check_string_utf8 function returns itself. - */ -TEST(string_check_utf8, utf8) { - std::string txt("L'accès à l'hôtel est encombré"); - ASSERT_EQ(string::check_string_utf8(txt), "L'accès à l'hôtel est encombré"); -} - -/* - * Given a string encoded in CP-1252 - * Then the check_string_utf8 function converts it to UTF-8. - */ -TEST(string_check_utf8, cp1252) { - std::string txt("Le ticket co\xfbte 12\x80\n"); - ASSERT_EQ(string::check_string_utf8(txt), "Le ticket coûte 12€\n"); -} - -/* - * Given a string encoded in ISO-8859-15 - * Then the check_string_utf8 function converts it to UTF-8. - */ -TEST(string_check_utf8, iso8859) { - std::string txt("Le ticket co\xfbte 12\xa4\n"); - ASSERT_EQ(string::check_string_utf8(txt), "Le ticket coûte 12€\n"); -} - -/* - * Given a string encoded in ISO-8859-15 - * Then the check_string_utf8 function converts it to UTF-8. - */ -TEST(string_check_utf8, iso8859_cpx) { - std::string txt("\xa4\xa6\xa8\xb4\xb8\xbc\xbd\xbe"); - ASSERT_EQ(string::check_string_utf8(txt), "€ŠšŽžŒœŸ"); -} - -/* - * Given a string encoded in CP-1252 - * Then the check_string_utf8 function converts it to UTF-8. - */ -TEST(string_check_utf8, cp1252_cpx) { - std::string txt("\x80\x95\x82\x89\x8a"); - ASSERT_EQ(string::check_string_utf8(txt), "€•‚‰Š"); -} - -/* - * Given a string badly encoded in CP-1252 - * Then the check_string_utf8 function converts it to UTF-8 and replaces bad - * characters into '_'. - */ -TEST(string_check_utf8, whatever_as_cp1252) { - std::string txt; - for (uint8_t c = 32; c < 255; c++) - if (c != 127) - txt.push_back(c); - std::string result( - " !\"#$%&'()*+,-./" - "0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`" - "abcdefghijklmnopqrstuvwxyz{|}~€_‚ƒ„…†‡ˆ‰Š‹Œ_Ž__‘’“”•–—˜™š›œ_" - "žŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäå" - "æçèéêëìíîïðñòóôõö÷øùúûüýþ"); - ASSERT_EQ(string::check_string_utf8(txt), result); -} - -/* - * Given a string badly encoded in ISO-8859-15 - * Then the check_string_utf8 function converts it to UTF-8 and replaces bad - * characters into '_'. - */ -TEST(string_check_utf8, whatever_as_iso8859) { - /* Construction of a string that is not cp1252 so it should be considered as - * iso8859-15 */ - std::string txt; - for (uint8_t c = 32; c < 255; c++) { - if (c == 32) - txt.push_back(0x81); - if (c != 127) - txt.push_back(c); - } - std::string result( - "_ " - "!\"#$%&'()*+,-./" - "0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`" - "abcdefghijklmnopqrstuvwxyz{|}~_________________________________" - "¡¢£€¥Š§š©ª«¬­®¯°±²³Žµ¶·ž¹º»ŒœŸ¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçè" - "éêëìíîïðñòóôõö÷øùúûüýþ"); - ASSERT_EQ(string::check_string_utf8(txt), result); -} - -/* - * In case of a string containing multiple encoding, the resulting string should - * be an UTF-8 string. Here we have a string beginning with UTF-8 and finishing - * with cp1252. The resulting string is good and is UTF-8 only encoded. - */ -TEST(string_check_utf8, utf8_and_cp1252) { - std::string txt( - "\xc3\xa9\xc3\xa7\xc3\xa8\xc3\xa0\xc3\xb9\xc3\xaf\xc3\xab\x7e\x23\x0a\xe9" - "\xe7\xe8\xe0\xf9\xef\xeb\x7e\x23\x0a"); - std::string result("éçèàùïë~#\néçèàùïë~#\n"); - ASSERT_EQ(string::check_string_utf8(txt), result); -} - -/* A check coming from windows with characters from the cmd console */ -TEST(string_check_utf8, strange_string) { - std::string txt( - "WARNING - [Triggered by _ItemCount>0] - 1 event(s) of Severity Level: " - "\"Error\", were recorded in the last 24 hours from the Application " - "Event Log. (List is on next line. Fields shown are - " - "Logfile:TimeGenerated:EventId:EventCode:SeverityLevel:Type:SourceName:" - "Message)|'Event " - "Count'=1;0;50;\nApplication:20200806000001.000000-000:3221243278:17806:" - "Erreur:MSSQLSERVER:╔chec de la nÚgociation SSPI avec le code " - "d'erreurá0x8009030c lors de l'Útablissement d'une connexion avec une " - "sÚcuritÚ intÚgrÚeá; la connexion a ÚtÚ fermÚe. [CLIENTá: X.X.X.X]"); - ASSERT_EQ(string::check_string_utf8(txt), txt); -} - -/* A check coming from windows with characters from the cmd console */ -TEST(string_check_utf8, chinese) { - std::string txt("超级杀手死亡检查"); - ASSERT_EQ(string::check_string_utf8(txt), txt); -} - -/* A check coming from windows with characters from the cmd console */ -TEST(string_check_utf8, vietnam) { - std::string txt( - "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo" - "ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong " - "chinese 告警数量 output puté! | '告警数量'=42\navé dé long ouput oçi " - "还有中国人! Hái yǒu zhòng guó rén!"); - ASSERT_EQ(string::check_string_utf8(txt), txt); -} - -TEST(truncate, nominal1) { - std::string str("foobar"); - ASSERT_EQ(string::truncate(str, 3), "foo"); -} - -TEST(truncate, nominal2) { - std::string str("foobar"); - ASSERT_EQ(string::truncate(str, 0), ""); -} - -TEST(truncate, nominal3) { - std::string str("foobar 超级杀手死亡检查"); - ASSERT_EQ(string::truncate(str, 1000), "foobar 超级杀手死亡检查"); -} - -TEST(truncate, utf8_1) { - std::string str("告警数量"); - for (size_t i = 0; i <= str.size(); i++) { - fmt::string_view tmp(str); - fmt::string_view res(string::truncate(tmp, i)); - std::string tmp1( - string::check_string_utf8(std::string(res.data(), res.size()))); - ASSERT_EQ(res, tmp1); - } -} - -TEST(adjust_size_utf8, nominal1) { - std::string str("foobar"); - ASSERT_EQ(fmt::string_view(str.data(), string::adjust_size_utf8(str, 3)), - fmt::string_view("foo")); -} - -TEST(adjust_size_utf8, nominal2) { - std::string str("foobar"); - ASSERT_EQ(fmt::string_view(str.data(), string::adjust_size_utf8(str, 0)), ""); -} - -TEST(adjust_size_utf8, nominal3) { - std::string str("foobar 超级杀手死亡检查"); - ASSERT_EQ(fmt::string_view(str.data(), string::adjust_size_utf8(str, 1000)), - str); -} - -TEST(adjust_size_utf8, utf8_1) { - std::string str("告警数量"); - for (size_t i = 0; i <= str.size(); i++) { - fmt::string_view sv(str.data(), string::adjust_size_utf8(str, i)); - std::string tmp(string::check_string_utf8( - std::string(sv.data(), sv.data() + sv.size()))); - ASSERT_EQ(sv.size(), tmp.size()); - } -} - TEST(escape, simple) { ASSERT_EQ("Hello", string::escape("Hello", 10)); ASSERT_EQ("Hello", string::escape("Hello", 5)); @@ -261,7 +67,7 @@ TEST(escape, utf8) { std::string str("告'警'数\\量"); std::string res("告\\'警\\'数\\\\量"); std::string res1(res); - res1.resize(string::adjust_size_utf8(res, 10)); + res1.resize(com::centreon::common::adjust_size_utf8(res, 10)); ASSERT_EQ(res, string::escape(str, 20)); ASSERT_EQ(res1, string::escape(str, 10)); } diff --git a/broker/lua/src/broker_utils.cc b/broker/lua/src/broker_utils.cc index e5a7f9792af..01e8a30fec1 100644 --- a/broker/lua/src/broker_utils.cc +++ b/broker/lua/src/broker_utils.cc @@ -40,6 +40,7 @@ #include "com/centreon/broker/sql/table_max_size.hh" #include "com/centreon/common/hex_dump.hh" #include "com/centreon/common/perfdata.hh" +#include "com/centreon/common/utf8.hh" #include "com/centreon/exceptions/msg_fmt.hh" #include "common/log_v2/log_v2.hh" @@ -654,10 +655,10 @@ static int l_broker_parse_perfdata(lua_State* L) { com::centreon::common::perfdata::parse_perfdata(0, 0, perf_data, logger)}; lua_createtable(L, 0, pds.size()); for (auto& pd : pds) { - pd.resize_name(misc::string::adjust_size_utf8( + pd.resize_name(com::centreon::common::adjust_size_utf8( pd.name(), get_centreon_storage_metrics_col_size( centreon_storage_metrics_metric_name))); - pd.resize_unit(misc::string::adjust_size_utf8( + pd.resize_unit(com::centreon::common::adjust_size_utf8( pd.unit(), get_centreon_storage_metrics_col_size( centreon_storage_metrics_unit_name))); diff --git a/broker/neb/src/callbacks.cc b/broker/neb/src/callbacks.cc index eca357ed58e..5623954672e 100644 --- a/broker/neb/src/callbacks.cc +++ b/broker/neb/src/callbacks.cc @@ -28,13 +28,13 @@ #include "com/centreon/broker/config/applier/state.hh" #include "com/centreon/broker/config/parser.hh" #include "com/centreon/broker/config/state.hh" -#include "com/centreon/broker/misc/string.hh" #include "com/centreon/broker/neb/callback.hh" #include "com/centreon/broker/neb/events.hh" #include "com/centreon/broker/neb/initial.hh" #include "com/centreon/broker/neb/internal.hh" #include "com/centreon/broker/neb/set_log_data.hh" #include "com/centreon/common/time.hh" +#include "com/centreon/common/utf8.hh" #include "com/centreon/engine/anomalydetection.hh" #include "com/centreon/engine/broker.hh" #include "com/centreon/engine/comment.hh" @@ -177,9 +177,9 @@ int neb::callback_acknowledgement(int callback_type, void* data) { ack_data = static_cast<nebstruct_acknowledgement_data*>(data); ack->acknowledgement_type = short(ack_data->acknowledgement_type); if (ack_data->author_name) - ack->author = misc::string::check_string_utf8(ack_data->author_name); + ack->author = common::check_string_utf8(ack_data->author_name); if (ack_data->comment_data) - ack->comment = misc::string::check_string_utf8(ack_data->comment_data); + ack->comment = common::check_string_utf8(ack_data->comment_data); ack->entry_time = time(nullptr); if (!ack_data->host_id) throw msg_fmt("unnamed host"); @@ -245,10 +245,9 @@ int neb::callback_pb_acknowledgement(int callback_type [[maybe_unused]], ack_obj.set_type(static_cast<Acknowledgement_ResourceType>( ack_data->acknowledgement_type)); if (ack_data->author_name) - ack_obj.set_author(misc::string::check_string_utf8(ack_data->author_name)); + ack_obj.set_author(common::check_string_utf8(ack_data->author_name)); if (ack_data->comment_data) - ack_obj.set_comment_data( - misc::string::check_string_utf8(ack_data->comment_data)); + ack_obj.set_comment_data(common::check_string_utf8(ack_data->comment_data)); ack_obj.set_entry_time(time(nullptr)); if (!ack_data->host_id) { SPDLOG_LOGGER_ERROR(neb_logger, @@ -300,11 +299,9 @@ int neb::callback_comment(int callback_type, void* data) { // Fill output var. comment_data = static_cast<nebstruct_comment_data*>(data); if (comment_data->author_name) - comment->author = - misc::string::check_string_utf8(comment_data->author_name); + comment->author = common::check_string_utf8(comment_data->author_name); if (comment_data->comment_data) - comment->data = - misc::string::check_string_utf8(comment_data->comment_data); + comment->data = common::check_string_utf8(comment_data->comment_data); comment->comment_type = comment_data->comment_type; if (NEBTYPE_COMMENT_DELETE == comment_data->type) comment->deletion_time = time(nullptr); @@ -372,11 +369,9 @@ int neb::callback_pb_comment(int, void* data) { // Fill output var. if (comment_data->author_name) - comment.set_author( - misc::string::check_string_utf8(comment_data->author_name)); + comment.set_author(common::check_string_utf8(comment_data->author_name)); if (comment_data->comment_data) - comment.set_data( - misc::string::check_string_utf8(comment_data->comment_data)); + comment.set_data(common::check_string_utf8(comment_data->comment_data)); comment.set_type( (comment_data->comment_type == com::centreon::engine::comment::type::host) ? com::centreon::broker::Comment_Type_HOST @@ -479,7 +474,7 @@ int neb::callback_pb_custom_variable(int, void* data) { if (hst && !hst->name().empty()) { uint64_t host_id = engine::get_host_id(hst->name()); if (host_id != 0) { - std::string name(misc::string::check_string_utf8(cvar->var_name)); + std::string name(common::check_string_utf8(cvar->var_name)); bool add = NEBTYPE_HOSTCUSTOMVARIABLE_ADD == cvar->type; obj.set_enabled(add); obj.set_host_id(host_id); @@ -488,7 +483,7 @@ int neb::callback_pb_custom_variable(int, void* data) { obj.set_type(com::centreon::broker::CustomVariable_VarType_HOST); obj.set_update_time(cvar->timestamp.tv_sec); if (add) { - std::string value(misc::string::check_string_utf8(cvar->var_value)); + std::string value(common::check_string_utf8(cvar->var_value)); obj.set_value(value); obj.set_default_value(value); SPDLOG_LOGGER_INFO(neb_logger, @@ -514,7 +509,7 @@ int neb::callback_pb_custom_variable(int, void* data) { p = engine::get_host_and_service_id(svc->get_hostname(), svc->description()); if (p.first && p.second) { - std::string name(misc::string::check_string_utf8(cvar->var_name)); + std::string name(common::check_string_utf8(cvar->var_name)); bool add = NEBTYPE_SERVICECUSTOMVARIABLE_ADD == cvar->type; obj.set_enabled(add); obj.set_host_id(p.first); @@ -524,7 +519,7 @@ int neb::callback_pb_custom_variable(int, void* data) { obj.set_type(com::centreon::broker::CustomVariable_VarType_SERVICE); obj.set_update_time(cvar->timestamp.tv_sec); if (add) { - std::string value(misc::string::check_string_utf8(cvar->var_value)); + std::string value(common::check_string_utf8(cvar->var_value)); obj.set_value(value); obj.set_default_value(value); SPDLOG_LOGGER_INFO( @@ -586,12 +581,12 @@ int neb::callback_custom_variable(int callback_type, void* data) { new_cvar->enabled = true; new_cvar->host_id = host_id; new_cvar->modified = false; - new_cvar->name = misc::string::check_string_utf8(cvar->var_name); + new_cvar->name = common::check_string_utf8(cvar->var_name); new_cvar->var_type = 0; new_cvar->update_time = cvar->timestamp.tv_sec; - new_cvar->value = misc::string::check_string_utf8(cvar->var_value); + new_cvar->value = common::check_string_utf8(cvar->var_value); new_cvar->default_value = - misc::string::check_string_utf8(cvar->var_value); + common::check_string_utf8(cvar->var_value); // Send custom variable event. SPDLOG_LOGGER_INFO(neb_logger, @@ -608,7 +603,7 @@ int neb::callback_custom_variable(int callback_type, void* data) { auto old_cvar{std::make_shared<custom_variable>()}; old_cvar->enabled = false; old_cvar->host_id = host_id; - old_cvar->name = misc::string::check_string_utf8(cvar->var_name); + old_cvar->name = common::check_string_utf8(cvar->var_name); old_cvar->var_type = 0; old_cvar->update_time = cvar->timestamp.tv_sec; @@ -635,13 +630,13 @@ int neb::callback_custom_variable(int callback_type, void* data) { new_cvar->enabled = true; new_cvar->host_id = p.first; new_cvar->modified = false; - new_cvar->name = misc::string::check_string_utf8(cvar->var_name); + new_cvar->name = common::check_string_utf8(cvar->var_name); new_cvar->service_id = p.second; new_cvar->var_type = 1; new_cvar->update_time = cvar->timestamp.tv_sec; - new_cvar->value = misc::string::check_string_utf8(cvar->var_value); + new_cvar->value = common::check_string_utf8(cvar->var_value); new_cvar->default_value = - misc::string::check_string_utf8(cvar->var_value); + common::check_string_utf8(cvar->var_value); // Send custom variable event. SPDLOG_LOGGER_INFO( @@ -662,7 +657,7 @@ int neb::callback_custom_variable(int callback_type, void* data) { old_cvar->enabled = false; old_cvar->host_id = p.first; old_cvar->modified = true; - old_cvar->name = misc::string::check_string_utf8(cvar->var_name); + old_cvar->name = common::check_string_utf8(cvar->var_name); old_cvar->service_id = p.second; old_cvar->var_type = 1; old_cvar->update_time = cvar->timestamp.tv_sec; @@ -1019,11 +1014,10 @@ int neb::callback_downtime(int callback_type, void* data) { // Fill output var. if (downtime_data->author_name) - downtime->author = - misc::string::check_string_utf8(downtime_data->author_name); + downtime->author = common::check_string_utf8(downtime_data->author_name); if (downtime_data->comment_data) downtime->comment = - misc::string::check_string_utf8(downtime_data->comment_data); + common::check_string_utf8(downtime_data->comment_data); downtime->downtime_type = downtime_data->downtime_type; downtime->duration = downtime_data->duration; downtime->end_time = downtime_data->end_time; @@ -1111,11 +1105,10 @@ int neb::callback_pb_downtime(int callback_type, void* data) { // Fill output var. if (downtime_data->author_name) - downtime.set_author( - misc::string::check_string_utf8(downtime_data->author_name)); + downtime.set_author(common::check_string_utf8(downtime_data->author_name)); if (downtime_data->comment_data) downtime.set_comment_data( - misc::string::check_string_utf8(downtime_data->comment_data)); + common::check_string_utf8(downtime_data->comment_data)); downtime.set_id(downtime_data->downtime_id); downtime.set_type( static_cast<Downtime_DowntimeType>(downtime_data->downtime_type)); @@ -1201,7 +1194,7 @@ int neb::callback_external_command(int callback_type, void* data) { // Split argument string. if (necd->command_args) { std::list<std::string> l{absl::StrSplit( - misc::string::check_string_utf8(necd->command_args), ';')}; + common::check_string_utf8(necd->command_args), ';')}; if (l.size() != 3) SPDLOG_LOGGER_ERROR( neb_logger, "callbacks: invalid host custom variable command"); @@ -1238,7 +1231,7 @@ int neb::callback_external_command(int callback_type, void* data) { // Split argument string. if (necd->command_args) { std::list<std::string> l{absl::StrSplit( - misc::string::check_string_utf8(necd->command_args), ';')}; + common::check_string_utf8(necd->command_args), ';')}; if (l.size() != 4) SPDLOG_LOGGER_ERROR( neb_logger, @@ -1300,8 +1293,8 @@ int neb::callback_pb_external_command(int, void* data) { nebstruct_external_command_data* necd( static_cast<nebstruct_external_command_data*>(data)); if (necd && (necd->type == NEBTYPE_EXTERNALCOMMAND_START)) { - auto args = absl::StrSplit( - misc::string::check_string_utf8(necd->command_args), ';'); + auto args = + absl::StrSplit(common::check_string_utf8(necd->command_args), ';'); size_t args_size = std::distance(args.begin(), args.end()); auto split_iter = args.begin(); if (necd->command_type == CMD_CHANGE_CUSTOM_HOST_VAR) { @@ -1418,8 +1411,7 @@ int neb::callback_group(int callback_type, void* data) { new_hg->id = host_group->get_id(); new_hg->enabled = (group_data->type != NEBTYPE_HOSTGROUP_DELETE && !host_group->members.empty()); - new_hg->name = - misc::string::check_string_utf8(host_group->get_group_name()); + new_hg->name = common::check_string_utf8(host_group->get_group_name()); // Send host group event. if (new_hg->id) { @@ -1450,7 +1442,7 @@ int neb::callback_group(int callback_type, void* data) { new_sg->enabled = (group_data->type != NEBTYPE_SERVICEGROUP_DELETE && !service_group->members.empty()); new_sg->name = - misc::string::check_string_utf8(service_group->get_group_name()); + common::check_string_utf8(service_group->get_group_name()); // Send service group event. if (new_sg->id) { @@ -1513,7 +1505,7 @@ int neb::callback_pb_group(int callback_type, void* data) { NEBTYPE_HOSTGROUP_DELETE && !host_group->members.empty()); new_hg->mut_obj().set_name( - misc::string::check_string_utf8(host_group->get_group_name())); + common::check_string_utf8(host_group->get_group_name())); // Send host group event. if (host_group->get_id()) { @@ -1551,7 +1543,7 @@ int neb::callback_pb_group(int callback_type, void* data) { NEBTYPE_SERVICEGROUP_DELETE && !service_group->members.empty()); new_sg->mut_obj().set_name( - misc::string::check_string_utf8(service_group->get_group_name())); + common::check_string_utf8(service_group->get_group_name())); // Send service group event. if (service_group->get_id()) { @@ -1609,7 +1601,7 @@ int neb::callback_group_member(int callback_type, void* data) { // Output variable. auto hgm{std::make_shared<neb::host_group_member>()}; hgm->group_id = hg->get_id(); - hgm->group_name = misc::string::check_string_utf8(hg->get_group_name()); + hgm->group_name = common::check_string_utf8(hg->get_group_name()); hgm->poller_id = config::applier::state::instance().poller_id(); uint32_t host_id = engine::get_host_id(hst->name()); if (host_id != 0 && hgm->group_id != 0) { @@ -1647,7 +1639,7 @@ int neb::callback_group_member(int callback_type, void* data) { // Output variable. auto sgm{std::make_shared<neb::service_group_member>()}; sgm->group_id = sg->get_id(); - sgm->group_name = misc::string::check_string_utf8(sg->get_group_name()); + sgm->group_name = common::check_string_utf8(sg->get_group_name()); sgm->poller_id = config::applier::state::instance().poller_id(); std::pair<uint32_t, uint32_t> p; p = engine::get_host_and_service_id(svc->get_hostname(), @@ -1718,7 +1710,7 @@ int neb::callback_pb_group_member(int callback_type, void* data) { auto hgmp{std::make_shared<neb::pb_host_group_member>()}; HostGroupMember& hgm = hgmp->mut_obj(); hgm.set_hostgroup_id(hg->get_id()); - hgm.set_name(misc::string::check_string_utf8(hg->get_group_name())); + hgm.set_name(common::check_string_utf8(hg->get_group_name())); hgm.set_poller_id(config::applier::state::instance().poller_id()); uint32_t host_id = engine::get_host_id(hst->name()); if (host_id != 0 && hgm.hostgroup_id() != 0) { @@ -1758,7 +1750,7 @@ int neb::callback_pb_group_member(int callback_type, void* data) { auto sgmp{std::make_shared<neb::pb_service_group_member>()}; ServiceGroupMember& sgm = sgmp->mut_obj(); sgm.set_servicegroup_id(sg->get_id()); - sgm.set_name(misc::string::check_string_utf8(sg->get_group_name())); + sgm.set_name(common::check_string_utf8(sg->get_group_name())); sgm.set_poller_id(config::applier::state::instance().poller_id()); std::pair<uint32_t, uint32_t> p; p = engine::get_host_and_service_id(svc->get_hostname(), @@ -1823,17 +1815,15 @@ int neb::callback_host(int callback_type, void* data) { my_host->acknowledged = h->problem_has_been_acknowledged(); my_host->acknowledgement_type = h->get_acknowledgement(); if (!h->get_action_url().empty()) - my_host->action_url = - misc::string::check_string_utf8(h->get_action_url()); + my_host->action_url = common::check_string_utf8(h->get_action_url()); my_host->active_checks_enabled = h->active_checks_enabled(); if (!h->get_address().empty()) - my_host->address = misc::string::check_string_utf8(h->get_address()); + my_host->address = common::check_string_utf8(h->get_address()); if (!h->get_alias().empty()) - my_host->alias = misc::string::check_string_utf8(h->get_alias()); + my_host->alias = common::check_string_utf8(h->get_alias()); my_host->check_freshness = h->check_freshness_enabled(); if (!h->check_command().empty()) - my_host->check_command = - misc::string::check_string_utf8(h->check_command()); + my_host->check_command = common::check_string_utf8(h->check_command()); my_host->check_interval = h->check_interval(); if (!h->check_period().empty()) my_host->check_period = h->check_period(); @@ -1848,12 +1838,10 @@ int neb::callback_host(int callback_type, void* data) { my_host->default_passive_checks_enabled = h->passive_checks_enabled(); my_host->downtime_depth = h->get_scheduled_downtime_depth(); if (!h->get_display_name().empty()) - my_host->display_name = - misc::string::check_string_utf8(h->get_display_name()); + my_host->display_name = common::check_string_utf8(h->get_display_name()); my_host->enabled = (host_data->type != NEBTYPE_HOST_DELETE); if (!h->event_handler().empty()) - my_host->event_handler = - misc::string::check_string_utf8(h->event_handler()); + my_host->event_handler = common::check_string_utf8(h->event_handler()); my_host->event_handler_enabled = h->event_handler_enabled(); my_host->execution_time = h->get_execution_time(); my_host->first_notification_delay = h->get_first_notification_delay(); @@ -1869,13 +1857,12 @@ int neb::callback_host(int callback_type, void* data) { my_host->has_been_checked = h->has_been_checked(); my_host->high_flap_threshold = h->get_high_flap_threshold(); if (!h->name().empty()) - my_host->host_name = misc::string::check_string_utf8(h->name()); + my_host->host_name = common::check_string_utf8(h->name()); if (!h->get_icon_image().empty()) - my_host->icon_image = - misc::string::check_string_utf8(h->get_icon_image()); + my_host->icon_image = common::check_string_utf8(h->get_icon_image()); if (!h->get_icon_image_alt().empty()) my_host->icon_image_alt = - misc::string::check_string_utf8(h->get_icon_image_alt()); + common::check_string_utf8(h->get_icon_image_alt()); my_host->is_flapping = h->get_is_flapping(); my_host->last_check = h->get_last_check(); my_host->last_hard_state = h->get_last_hard_state(); @@ -1893,9 +1880,9 @@ int neb::callback_host(int callback_type, void* data) { my_host->next_notification = h->get_next_notification(); my_host->no_more_notifications = h->get_no_more_notifications(); if (!h->get_notes().empty()) - my_host->notes = misc::string::check_string_utf8(h->get_notes()); + my_host->notes = common::check_string_utf8(h->get_notes()); if (!h->get_notes_url().empty()) - my_host->notes_url = misc::string::check_string_utf8(h->get_notes_url()); + my_host->notes_url = common::check_string_utf8(h->get_notes_url()); my_host->notifications_enabled = h->get_notifications_enabled(); my_host->notification_interval = h->get_notification_interval(); if (!h->notification_period().empty()) @@ -1909,16 +1896,16 @@ int neb::callback_host(int callback_type, void* data) { h->get_notify_on(engine::notifier::unreachable); my_host->obsess_over = h->obsess_over(); if (!h->get_plugin_output().empty()) { - my_host->output = misc::string::check_string_utf8(h->get_plugin_output()); + my_host->output = common::check_string_utf8(h->get_plugin_output()); my_host->output.append("\n"); } if (!h->get_long_plugin_output().empty()) my_host->output.append( - misc::string::check_string_utf8(h->get_long_plugin_output())); + common::check_string_utf8(h->get_long_plugin_output())); my_host->passive_checks_enabled = h->passive_checks_enabled(); my_host->percent_state_change = h->get_percent_state_change(); if (!h->get_perf_data().empty()) - my_host->perf_data = misc::string::check_string_utf8(h->get_perf_data()); + my_host->perf_data = common::check_string_utf8(h->get_perf_data()); my_host->poller_id = config::applier::state::instance().poller_id(); my_host->retain_nonstatus_information = h->get_retain_nonstatus_information(); @@ -1933,7 +1920,7 @@ int neb::callback_host(int callback_type, void* data) { (h->has_been_checked() ? h->get_state_type() : engine::notifier::hard); if (!h->get_statusmap_image().empty()) my_host->statusmap_image = - misc::string::check_string_utf8(h->get_statusmap_image()); + common::check_string_utf8(h->get_statusmap_image()); my_host->timezone = h->get_timezone(); // Find host ID. @@ -2000,11 +1987,9 @@ int neb::callback_pb_host(int callback_type, void* data) { else if (dh->modified_attribute & MODATTR_OBSESSIVE_HANDLER_ENABLED) hst.set_obsess_over_host(eh->obsess_over()); else if (dh->modified_attribute & MODATTR_EVENT_HANDLER_COMMAND) - hst.set_event_handler( - misc::string::check_string_utf8(eh->event_handler())); + hst.set_event_handler(common::check_string_utf8(eh->event_handler())); else if (dh->modified_attribute & MODATTR_CHECK_COMMAND) - hst.set_check_command( - misc::string::check_string_utf8(eh->check_command())); + hst.set_check_command(common::check_string_utf8(eh->check_command())); else if (dh->modified_attribute & MODATTR_NORMAL_CHECK_INTERVAL) hst.set_check_interval(eh->check_interval()); else if (dh->modified_attribute & MODATTR_RETRY_CHECK_INTERVAL) @@ -2045,17 +2030,15 @@ int neb::callback_pb_host(int callback_type, void* data) { host.set_acknowledged(eh->problem_has_been_acknowledged()); host.set_acknowledgement_type(eh->get_acknowledgement()); if (!eh->get_action_url().empty()) - host.set_action_url( - misc::string::check_string_utf8(eh->get_action_url())); + host.set_action_url(common::check_string_utf8(eh->get_action_url())); host.set_active_checks(eh->active_checks_enabled()); if (!eh->get_address().empty()) - host.set_address(misc::string::check_string_utf8(eh->get_address())); + host.set_address(common::check_string_utf8(eh->get_address())); if (!eh->get_alias().empty()) - host.set_alias(misc::string::check_string_utf8(eh->get_alias())); + host.set_alias(common::check_string_utf8(eh->get_alias())); host.set_check_freshness(eh->check_freshness_enabled()); if (!eh->check_command().empty()) - host.set_check_command( - misc::string::check_string_utf8(eh->check_command())); + host.set_check_command(common::check_string_utf8(eh->check_command())); host.set_check_interval(eh->check_interval()); if (!eh->check_period().empty()) host.set_check_period(eh->check_period()); @@ -2071,13 +2054,11 @@ int neb::callback_pb_host(int callback_type, void* data) { host.set_default_passive_checks(eh->passive_checks_enabled()); host.set_scheduled_downtime_depth(eh->get_scheduled_downtime_depth()); if (!eh->get_display_name().empty()) - host.set_display_name( - misc::string::check_string_utf8(eh->get_display_name())); + host.set_display_name(common::check_string_utf8(eh->get_display_name())); host.set_enabled(static_cast<nebstruct_host_status_data*>(data)->type != NEBTYPE_HOST_DELETE); if (!eh->event_handler().empty()) - host.set_event_handler( - misc::string::check_string_utf8(eh->event_handler())); + host.set_event_handler(common::check_string_utf8(eh->event_handler())); host.set_event_handler_enabled(eh->event_handler_enabled()); host.set_execution_time(eh->get_execution_time()); host.set_first_notification_delay(eh->get_first_notification_delay()); @@ -2093,13 +2074,12 @@ int neb::callback_pb_host(int callback_type, void* data) { host.set_checked(eh->has_been_checked()); host.set_high_flap_threshold(eh->get_high_flap_threshold()); if (!eh->name().empty()) - host.set_name(misc::string::check_string_utf8(eh->name())); + host.set_name(common::check_string_utf8(eh->name())); if (!eh->get_icon_image().empty()) - host.set_icon_image( - misc::string::check_string_utf8(eh->get_icon_image())); + host.set_icon_image(common::check_string_utf8(eh->get_icon_image())); if (!eh->get_icon_image_alt().empty()) host.set_icon_image_alt( - misc::string::check_string_utf8(eh->get_icon_image_alt())); + common::check_string_utf8(eh->get_icon_image_alt())); host.set_flapping(eh->get_is_flapping()); host.set_last_check(eh->get_last_check()); host.set_last_hard_state( @@ -2118,9 +2098,9 @@ int neb::callback_pb_host(int callback_type, void* data) { host.set_next_host_notification(eh->get_next_notification()); host.set_no_more_notifications(eh->get_no_more_notifications()); if (!eh->get_notes().empty()) - host.set_notes(misc::string::check_string_utf8(eh->get_notes())); + host.set_notes(common::check_string_utf8(eh->get_notes())); if (!eh->get_notes_url().empty()) - host.set_notes_url(misc::string::check_string_utf8(eh->get_notes_url())); + host.set_notes_url(common::check_string_utf8(eh->get_notes_url())); host.set_notify(eh->get_notifications_enabled()); host.set_notification_interval(eh->get_notification_interval()); if (!eh->notification_period().empty()) @@ -2134,15 +2114,14 @@ int neb::callback_pb_host(int callback_type, void* data) { eh->get_notify_on(engine::notifier::unreachable)); host.set_obsess_over_host(eh->obsess_over()); if (!eh->get_plugin_output().empty()) { - host.set_output(misc::string::check_string_utf8(eh->get_plugin_output())); + host.set_output(common::check_string_utf8(eh->get_plugin_output())); } if (!eh->get_long_plugin_output().empty()) - host.set_output( - misc::string::check_string_utf8(eh->get_long_plugin_output())); + host.set_output(common::check_string_utf8(eh->get_long_plugin_output())); host.set_passive_checks(eh->passive_checks_enabled()); host.set_percent_state_change(eh->get_percent_state_change()); if (!eh->get_perf_data().empty()) - host.set_perfdata(misc::string::check_string_utf8(eh->get_perf_data())); + host.set_perfdata(common::check_string_utf8(eh->get_perf_data())); host.set_instance_id(config::applier::state::instance().poller_id()); host.set_retain_nonstatus_information( eh->get_retain_nonstatus_information()); @@ -2158,7 +2137,7 @@ int neb::callback_pb_host(int callback_type, void* data) { : engine::notifier::hard)); if (!eh->get_statusmap_image().empty()) host.set_statusmap_image( - misc::string::check_string_utf8(eh->get_statusmap_image())); + common::check_string_utf8(eh->get_statusmap_image())); host.set_timezone(eh->get_timezone()); host.set_severity_id(eh->get_severity() ? eh->get_severity()->id() : 0); host.set_icon_id(eh->get_icon_id()); @@ -2226,7 +2205,7 @@ int neb::callback_host_check(int callback_type, void* data) { host_check->active_checks_enabled = h->active_checks_enabled(); host_check->check_type = hcdata->check_type; host_check->command_line = - misc::string::check_string_utf8(hcdata->command_line); + common::check_string_utf8(hcdata->command_line); if (!hcdata->host_name) throw msg_fmt("unnamed host"); host_check->host_id = engine::get_host_id(hcdata->host_name); @@ -2297,7 +2276,7 @@ int neb::callback_pb_host_check(int callback_type, void* data) { ? com::centreon::broker::CheckActive : com::centreon::broker::CheckPassive); host_check->mut_obj().set_command_line( - misc::string::check_string_utf8(hcdata->command_line)); + common::check_string_utf8(hcdata->command_line)); host_check->mut_obj().set_host_id(h->host_id()); host_check->mut_obj().set_next_check(h->get_next_check()); @@ -2337,7 +2316,7 @@ int neb::callback_host_status(int callback_type, void* data) { host_status->active_checks_enabled = h->active_checks_enabled(); if (!h->check_command().empty()) host_status->check_command = - misc::string::check_string_utf8(h->check_command()); + common::check_string_utf8(h->check_command()); host_status->check_interval = h->check_interval(); if (!h->check_period().empty()) host_status->check_period = h->check_period(); @@ -2348,7 +2327,7 @@ int neb::callback_host_status(int callback_type, void* data) { host_status->downtime_depth = h->get_scheduled_downtime_depth(); if (!h->event_handler().empty()) host_status->event_handler = - misc::string::check_string_utf8(h->event_handler()); + common::check_string_utf8(h->event_handler()); host_status->event_handler_enabled = h->event_handler_enabled(); host_status->execution_time = h->get_execution_time(); host_status->flap_detection_enabled = h->flap_detection_enabled(); @@ -2379,18 +2358,16 @@ int neb::callback_host_status(int callback_type, void* data) { host_status->notifications_enabled = h->get_notifications_enabled(); host_status->obsess_over = h->obsess_over(); if (!h->get_plugin_output().empty()) { - host_status->output = - misc::string::check_string_utf8(h->get_plugin_output()); + host_status->output = common::check_string_utf8(h->get_plugin_output()); host_status->output.append("\n"); } if (!h->get_long_plugin_output().empty()) host_status->output.append( - misc::string::check_string_utf8(h->get_long_plugin_output())); + common::check_string_utf8(h->get_long_plugin_output())); host_status->passive_checks_enabled = h->passive_checks_enabled(); host_status->percent_state_change = h->get_percent_state_change(); if (!h->get_perf_data().empty()) - host_status->perf_data = - misc::string::check_string_utf8(h->get_perf_data()); + host_status->perf_data = common::check_string_utf8(h->get_perf_data()); host_status->retry_interval = h->retry_interval(); host_status->should_be_scheduled = h->get_should_be_scheduled(); host_status->state_type = @@ -2496,14 +2473,13 @@ int neb::callback_pb_host_status(int callback_type, void* data) noexcept { hscr.set_next_host_notification(eh->get_next_notification()); hscr.set_no_more_notifications(eh->get_no_more_notifications()); if (!eh->get_plugin_output().empty()) - hscr.set_output(misc::string::check_string_utf8(eh->get_plugin_output())); + hscr.set_output(common::check_string_utf8(eh->get_plugin_output())); if (!eh->get_long_plugin_output().empty()) - hscr.set_output( - misc::string::check_string_utf8(eh->get_long_plugin_output())); + hscr.set_output(common::check_string_utf8(eh->get_long_plugin_output())); hscr.set_percent_state_change(eh->get_percent_state_change()); if (!eh->get_perf_data().empty()) - hscr.set_perfdata(misc::string::check_string_utf8(eh->get_perf_data())); + hscr.set_perfdata(common::check_string_utf8(eh->get_perf_data())); hscr.set_should_be_scheduled(eh->get_should_be_scheduled()); hscr.set_state_type(static_cast<HostStatus_StateType>( eh->has_been_checked() ? eh->get_state_type() : engine::notifier::hard)); @@ -2568,7 +2544,7 @@ int neb::callback_log(int callback_type, void* data) { le->c_time = log_data->entry_time; le->poller_name = config::applier::state::instance().poller_name(); if (log_data->data) { - le->output = misc::string::check_string_utf8(log_data->data); + le->output = common::check_string_utf8(log_data->data); set_log_data(*le, le->output.c_str()); } @@ -2607,7 +2583,7 @@ int neb::callback_pb_log(int callback_type [[maybe_unused]], void* data) { le_obj.set_ctime(log_data->entry_time); le_obj.set_instance_name(config::applier::state::instance().poller_name()); if (log_data->data) { - std::string output = misc::string::check_string_utf8(log_data->data); + std::string output = common::check_string_utf8(log_data->data); le_obj.set_output(output); set_pb_log_data(*le, output); } @@ -2817,10 +2793,10 @@ int neb::callback_program_status(int callback_type, void* data) { is->event_handler_enabled = program_status_data->event_handlers_enabled; is->flap_detection_enabled = program_status_data->flap_detection_enabled; if (!program_status_data->global_host_event_handler.empty()) - is->global_host_event_handler = misc::string::check_string_utf8( + is->global_host_event_handler = common::check_string_utf8( program_status_data->global_host_event_handler); if (!program_status_data->global_service_event_handler.empty()) - is->global_service_event_handler = misc::string::check_string_utf8( + is->global_service_event_handler = common::check_string_utf8( program_status_data->global_service_event_handler); is->last_alive = time(nullptr); is->last_command_check = program_status_data->last_command_check; @@ -2882,10 +2858,10 @@ int neb::callback_pb_program_status(int, void* data) { is.set_event_handlers(program_status_data.event_handlers_enabled); is.set_flap_detection(program_status_data.flap_detection_enabled); if (!program_status_data.global_host_event_handler.empty()) - is.set_global_host_event_handler(misc::string::check_string_utf8( + is.set_global_host_event_handler(common::check_string_utf8( program_status_data.global_host_event_handler)); if (!program_status_data.global_service_event_handler.empty()) - is.set_global_service_event_handler(misc::string::check_string_utf8( + is.set_global_service_event_handler(common::check_string_utf8( program_status_data.global_service_event_handler)); is.set_last_alive(time(nullptr)); is.set_last_command_check(program_status_data.last_command_check); @@ -3046,12 +3022,10 @@ int neb::callback_service(int callback_type, void* data) { my_service->acknowledged = s->problem_has_been_acknowledged(); my_service->acknowledgement_type = s->get_acknowledgement(); if (!s->get_action_url().empty()) - my_service->action_url = - misc::string::check_string_utf8(s->get_action_url()); + my_service->action_url = common::check_string_utf8(s->get_action_url()); my_service->active_checks_enabled = s->active_checks_enabled(); if (!s->check_command().empty()) - my_service->check_command = - misc::string::check_string_utf8(s->check_command()); + my_service->check_command = common::check_string_utf8(s->check_command()); my_service->check_freshness = s->check_freshness_enabled(); my_service->check_interval = s->check_interval(); if (!s->check_period().empty()) @@ -3068,11 +3042,10 @@ int neb::callback_service(int callback_type, void* data) { my_service->downtime_depth = s->get_scheduled_downtime_depth(); if (!s->get_display_name().empty()) my_service->display_name = - misc::string::check_string_utf8(s->get_display_name()); + common::check_string_utf8(s->get_display_name()); my_service->enabled = (service_data->type != NEBTYPE_SERVICE_DELETE); if (!s->event_handler().empty()) - my_service->event_handler = - misc::string::check_string_utf8(s->event_handler()); + my_service->event_handler = common::check_string_utf8(s->event_handler()); my_service->event_handler_enabled = s->event_handler_enabled(); my_service->execution_time = s->get_execution_time(); my_service->first_notification_delay = s->get_first_notification_delay(); @@ -3090,14 +3063,12 @@ int neb::callback_service(int callback_type, void* data) { my_service->has_been_checked = s->has_been_checked(); my_service->high_flap_threshold = s->get_high_flap_threshold(); if (!s->get_hostname().empty()) - my_service->host_name = - misc::string::check_string_utf8(s->get_hostname()); + my_service->host_name = common::check_string_utf8(s->get_hostname()); if (!s->get_icon_image().empty()) - my_service->icon_image = - misc::string::check_string_utf8(s->get_icon_image()); + my_service->icon_image = common::check_string_utf8(s->get_icon_image()); if (!s->get_icon_image_alt().empty()) my_service->icon_image_alt = - misc::string::check_string_utf8(s->get_icon_image_alt()); + common::check_string_utf8(s->get_icon_image_alt()); my_service->is_flapping = s->get_is_flapping(); my_service->is_volatile = s->get_is_volatile(); my_service->last_check = s->get_last_check(); @@ -3117,10 +3088,9 @@ int neb::callback_service(int callback_type, void* data) { my_service->next_notification = s->get_next_notification(); my_service->no_more_notifications = s->get_no_more_notifications(); if (!s->get_notes().empty()) - my_service->notes = misc::string::check_string_utf8(s->get_notes()); + my_service->notes = common::check_string_utf8(s->get_notes()); if (!s->get_notes_url().empty()) - my_service->notes_url = - misc::string::check_string_utf8(s->get_notes_url()); + my_service->notes_url = common::check_string_utf8(s->get_notes_url()); my_service->notifications_enabled = s->get_notifications_enabled(); my_service->notification_interval = s->get_notification_interval(); if (!s->notification_period().empty()) @@ -3136,25 +3106,23 @@ int neb::callback_service(int callback_type, void* data) { my_service->notify_on_warning = s->get_notify_on(engine::notifier::warning); my_service->obsess_over = s->obsess_over(); if (!s->get_plugin_output().empty()) { - my_service->output = - misc::string::check_string_utf8(s->get_plugin_output()); + my_service->output = common::check_string_utf8(s->get_plugin_output()); my_service->output.append("\n"); } if (!s->get_long_plugin_output().empty()) my_service->output.append( - misc::string::check_string_utf8(s->get_long_plugin_output())); + common::check_string_utf8(s->get_long_plugin_output())); my_service->passive_checks_enabled = s->passive_checks_enabled(); my_service->percent_state_change = s->get_percent_state_change(); if (!s->get_perf_data().empty()) - my_service->perf_data = - misc::string::check_string_utf8(s->get_perf_data()); + my_service->perf_data = common::check_string_utf8(s->get_perf_data()); my_service->retain_nonstatus_information = s->get_retain_nonstatus_information(); my_service->retain_status_information = s->get_retain_status_information(); my_service->retry_interval = s->retry_interval(); if (!s->description().empty()) my_service->service_description = - misc::string::check_string_utf8(s->description()); + common::check_string_utf8(s->description()); my_service->should_be_scheduled = s->get_should_be_scheduled(); my_service->stalk_on_critical = s->get_stalk_on(engine::notifier::critical); my_service->stalk_on_ok = s->get_stalk_on(engine::notifier::ok); @@ -3235,11 +3203,9 @@ int neb::callback_pb_service(int callback_type [[maybe_unused]], void* data) { else if (ds->modified_attribute & MODATTR_OBSESSIVE_HANDLER_ENABLED) srv.set_obsess_over_service(es->obsess_over()); else if (ds->modified_attribute & MODATTR_EVENT_HANDLER_COMMAND) - srv.set_event_handler( - misc::string::check_string_utf8(es->event_handler())); + srv.set_event_handler(common::check_string_utf8(es->event_handler())); else if (ds->modified_attribute & MODATTR_CHECK_COMMAND) - srv.set_check_command( - misc::string::check_string_utf8(es->check_command())); + srv.set_check_command(common::check_string_utf8(es->check_command())); else if (ds->modified_attribute & MODATTR_NORMAL_CHECK_INTERVAL) srv.set_check_interval(es->check_interval()); else if (ds->modified_attribute & MODATTR_RETRY_CHECK_INTERVAL) @@ -3285,11 +3251,10 @@ int neb::callback_pb_service(int callback_type [[maybe_unused]], void* data) { srv.set_acknowledged(es->problem_has_been_acknowledged()); srv.set_acknowledgement_type(es->get_acknowledgement()); if (!es->get_action_url().empty()) - srv.set_action_url(misc::string::check_string_utf8(es->get_action_url())); + srv.set_action_url(common::check_string_utf8(es->get_action_url())); srv.set_active_checks(es->active_checks_enabled()); if (!es->check_command().empty()) - srv.set_check_command( - misc::string::check_string_utf8(es->check_command())); + srv.set_check_command(common::check_string_utf8(es->check_command())); srv.set_check_freshness(es->check_freshness_enabled()); srv.set_check_interval(es->check_interval()); if (!es->check_period().empty()) @@ -3306,13 +3271,11 @@ int neb::callback_pb_service(int callback_type [[maybe_unused]], void* data) { srv.set_default_passive_checks(es->passive_checks_enabled()); srv.set_scheduled_downtime_depth(es->get_scheduled_downtime_depth()); if (!es->get_display_name().empty()) - srv.set_display_name( - misc::string::check_string_utf8(es->get_display_name())); + srv.set_display_name(common::check_string_utf8(es->get_display_name())); srv.set_enabled(static_cast<nebstruct_adaptive_service_data*>(data)->type != NEBTYPE_SERVICE_DELETE); if (!es->event_handler().empty()) - srv.set_event_handler( - misc::string::check_string_utf8(es->event_handler())); + srv.set_event_handler(common::check_string_utf8(es->event_handler())); srv.set_event_handler_enabled(es->event_handler_enabled()); srv.set_execution_time(es->get_execution_time()); srv.set_first_notification_delay(es->get_first_notification_delay()); @@ -3330,10 +3293,10 @@ int neb::callback_pb_service(int callback_type [[maybe_unused]], void* data) { srv.set_checked(es->has_been_checked()); srv.set_high_flap_threshold(es->get_high_flap_threshold()); if (!es->description().empty()) - srv.set_description(misc::string::check_string_utf8(es->description())); + srv.set_description(common::check_string_utf8(es->description())); if (!es->get_hostname().empty()) { - std::string name{misc::string::check_string_utf8(es->get_hostname())}; + std::string name{common::check_string_utf8(es->get_hostname())}; switch (es->get_service_type()) { case com::centreon::engine::service_type::METASERVICE: { srv.set_type(METASERVICE); @@ -3385,10 +3348,10 @@ int neb::callback_pb_service(int callback_type [[maybe_unused]], void* data) { } if (!es->get_icon_image().empty()) *srv.mutable_icon_image() = - misc::string::check_string_utf8(es->get_icon_image()); + common::check_string_utf8(es->get_icon_image()); if (!es->get_icon_image_alt().empty()) *srv.mutable_icon_image_alt() = - misc::string::check_string_utf8(es->get_icon_image_alt()); + common::check_string_utf8(es->get_icon_image_alt()); srv.set_flapping(es->get_is_flapping()); srv.set_is_volatile(es->get_is_volatile()); srv.set_last_check(es->get_last_check()); @@ -3409,10 +3372,9 @@ int neb::callback_pb_service(int callback_type [[maybe_unused]], void* data) { srv.set_next_notification(es->get_next_notification()); srv.set_no_more_notifications(es->get_no_more_notifications()); if (!es->get_notes().empty()) - srv.set_notes(misc::string::check_string_utf8(es->get_notes())); + srv.set_notes(common::check_string_utf8(es->get_notes())); if (!es->get_notes_url().empty()) - *srv.mutable_notes_url() = - misc::string::check_string_utf8(es->get_notes_url()); + *srv.mutable_notes_url() = common::check_string_utf8(es->get_notes_url()); srv.set_notify(es->get_notifications_enabled()); srv.set_notification_interval(es->get_notification_interval()); if (!es->notification_period().empty()) @@ -3427,15 +3389,14 @@ int neb::callback_pb_service(int callback_type [[maybe_unused]], void* data) { srv.set_obsess_over_service(es->obsess_over()); if (!es->get_plugin_output().empty()) *srv.mutable_output() = - misc::string::check_string_utf8(es->get_plugin_output()); + common::check_string_utf8(es->get_plugin_output()); if (!es->get_long_plugin_output().empty()) *srv.mutable_long_output() = - misc::string::check_string_utf8(es->get_long_plugin_output()); + common::check_string_utf8(es->get_long_plugin_output()); srv.set_passive_checks(es->passive_checks_enabled()); srv.set_percent_state_change(es->get_percent_state_change()); if (!es->get_perf_data().empty()) - *srv.mutable_perfdata() = - misc::string::check_string_utf8(es->get_perf_data()); + *srv.mutable_perfdata() = common::check_string_utf8(es->get_perf_data()); srv.set_retain_nonstatus_information( es->get_retain_nonstatus_information()); srv.set_retain_status_information(es->get_retain_status_information()); @@ -3522,7 +3483,7 @@ int neb::callback_service_check(int callback_type, void* data) { service_check->active_checks_enabled = s->active_checks_enabled(); service_check->check_type = scdata->check_type; service_check->command_line = - misc::string::check_string_utf8(scdata->command_line); + common::check_string_utf8(scdata->command_line); if (!scdata->host_id) throw msg_fmt("host without id"); if (!scdata->service_id) @@ -3594,7 +3555,7 @@ int neb::callback_pb_service_check(int, void* data) { ? com::centreon::broker::CheckActive : com::centreon::broker::CheckPassive); service_check->mut_obj().set_command_line( - misc::string::check_string_utf8(scdata->command_line)); + common::check_string_utf8(scdata->command_line)); service_check->mut_obj().set_host_id(scdata->host_id); service_check->mut_obj().set_service_id(scdata->service_id); service_check->mut_obj().set_next_check(s->get_next_check()); @@ -3773,13 +3734,13 @@ int32_t neb::callback_pb_service_status(int callback_type [[maybe_unused]], sscr.set_next_notification(es->get_next_notification()); sscr.set_no_more_notifications(es->get_no_more_notifications()); if (!es->get_plugin_output().empty()) - sscr.set_output(misc::string::check_string_utf8(es->get_plugin_output())); + sscr.set_output(common::check_string_utf8(es->get_plugin_output())); if (!es->get_long_plugin_output().empty()) sscr.set_long_output( - misc::string::check_string_utf8(es->get_long_plugin_output())); + common::check_string_utf8(es->get_long_plugin_output())); sscr.set_percent_state_change(es->get_percent_state_change()); if (!es->get_perf_data().empty()) { - sscr.set_perfdata(misc::string::check_string_utf8(es->get_perf_data())); + sscr.set_perfdata(common::check_string_utf8(es->get_perf_data())); SPDLOG_LOGGER_TRACE(neb_logger, "callbacks: service ({}, {}) has perfdata <<{}>>", es->host_id(), es->service_id(), es->get_perf_data()); @@ -3898,7 +3859,7 @@ int neb::callback_service_status(int callback_type, void* data) { service_status->active_checks_enabled = s->active_checks_enabled(); if (!s->check_command().empty()) service_status->check_command = - misc::string::check_string_utf8(s->check_command()); + common::check_string_utf8(s->check_command()); service_status->check_interval = s->check_interval(); if (!s->check_period().empty()) service_status->check_period = s->check_period(); @@ -3909,7 +3870,7 @@ int neb::callback_service_status(int callback_type, void* data) { service_status->downtime_depth = s->get_scheduled_downtime_depth(); if (!s->event_handler().empty()) service_status->event_handler = - misc::string::check_string_utf8(s->event_handler()); + common::check_string_utf8(s->event_handler()); service_status->event_handler_enabled = s->event_handler_enabled(); service_status->execution_time = s->get_execution_time(); service_status->flap_detection_enabled = s->flap_detection_enabled(); @@ -3935,27 +3896,25 @@ int neb::callback_service_status(int callback_type, void* data) { service_status->obsess_over = s->obsess_over(); if (!s->get_plugin_output().empty()) { service_status->output = - misc::string::check_string_utf8(s->get_plugin_output()); + common::check_string_utf8(s->get_plugin_output()); service_status->output.append("\n"); } if (!s->get_long_plugin_output().empty()) service_status->output.append( - misc::string::check_string_utf8(s->get_long_plugin_output())); + common::check_string_utf8(s->get_long_plugin_output())); service_status->passive_checks_enabled = s->passive_checks_enabled(); service_status->percent_state_change = s->get_percent_state_change(); if (!s->get_perf_data().empty()) - service_status->perf_data = - misc::string::check_string_utf8(s->get_perf_data()); + service_status->perf_data = common::check_string_utf8(s->get_perf_data()); service_status->retry_interval = s->retry_interval(); if (s->get_hostname().empty()) throw msg_fmt("unnamed host"); if (s->description().empty()) throw msg_fmt("unnamed service"); - service_status->host_name = - misc::string::check_string_utf8(s->get_hostname()); + service_status->host_name = common::check_string_utf8(s->get_hostname()); service_status->service_description = - misc::string::check_string_utf8(s->description()); + common::check_string_utf8(s->description()); { std::pair<uint64_t, uint64_t> p{ engine::get_host_and_service_id(s->get_hostname(), s->description())}; diff --git a/broker/storage/src/conflict_manager_storage.cc b/broker/storage/src/conflict_manager_storage.cc index d2fca02796e..e3677b463c3 100644 --- a/broker/storage/src/conflict_manager_storage.cc +++ b/broker/storage/src/conflict_manager_storage.cc @@ -31,6 +31,7 @@ #include "com/centreon/broker/sql/table_max_size.hh" #include "com/centreon/broker/storage/conflict_manager.hh" #include "com/centreon/common/perfdata.hh" +#include "com/centreon/common/utf8.hh" #include "com/centreon/exceptions/msg_fmt.hh" using namespace com::centreon::exceptions; @@ -135,10 +136,10 @@ void conflict_manager::_storage_process_service_status( "(host_id,host_name,service_id,service_description,must_be_rebuild," "special) VALUES (?,?,?,?,?,?)"); - fmt::string_view hv(misc::string::truncate( + fmt::string_view hv(common::truncate_utf8( ss.host_name, get_centreon_storage_index_data_col_size( centreon_storage_index_data_host_name))); - fmt::string_view sv(misc::string::truncate( + fmt::string_view sv(common::truncate_utf8( ss.service_description, get_centreon_storage_index_data_col_size( centreon_storage_index_data_service_description))); @@ -264,10 +265,10 @@ void conflict_manager::_storage_process_service_status( std::deque<std::shared_ptr<io::data>> to_publish; for (auto& pd : pds) { - pd.resize_name(misc::string::adjust_size_utf8( + pd.resize_name(common::adjust_size_utf8( pd.name(), get_centreon_storage_metrics_col_size( centreon_storage_metrics_metric_name))); - pd.resize_unit(misc::string::adjust_size_utf8( + pd.resize_unit(common::adjust_size_utf8( pd.unit(), get_centreon_storage_metrics_col_size( centreon_storage_metrics_unit_name))); auto it_index_cache = _metric_cache.find({index_id, pd.name()}); diff --git a/broker/unified_sql/src/stream_sql.cc b/broker/unified_sql/src/stream_sql.cc index af662996366..edd91e546ed 100644 --- a/broker/unified_sql/src/stream_sql.cc +++ b/broker/unified_sql/src/stream_sql.cc @@ -29,6 +29,7 @@ #include "com/centreon/broker/sql/table_max_size.hh" #include "com/centreon/broker/unified_sql/internal.hh" #include "com/centreon/broker/unified_sql/stream.hh" +#include "com/centreon/common/utf8.hh" #include "com/centreon/engine/host.hh" #include "com/centreon/engine/service.hh" @@ -2154,25 +2155,25 @@ uint64_t stream::_process_pb_host_in_resources(const Host& h, int32_t conn) { uint64_t res_id = 0; if (h.enabled()) { uint64_t sid = 0; - fmt::string_view name{misc::string::truncate( - h.name(), get_centreon_storage_resources_col_size( - centreon_storage_resources_name))}; - fmt::string_view address{misc::string::truncate( + fmt::string_view name{ + common::truncate_utf8(h.name(), get_centreon_storage_resources_col_size( + centreon_storage_resources_name))}; + fmt::string_view address{common::truncate_utf8( h.address(), get_centreon_storage_resources_col_size( centreon_storage_resources_address))}; - fmt::string_view alias{misc::string::truncate( + fmt::string_view alias{common::truncate_utf8( h.alias(), get_centreon_storage_resources_col_size( centreon_storage_resources_alias))}; - fmt::string_view parent_name{misc::string::truncate( + fmt::string_view parent_name{common::truncate_utf8( h.name(), get_centreon_storage_resources_col_size( centreon_storage_resources_parent_name))}; - fmt::string_view notes_url{misc::string::truncate( + fmt::string_view notes_url{common::truncate_utf8( h.notes_url(), get_centreon_storage_resources_col_size( centreon_storage_resources_notes_url))}; - fmt::string_view notes{misc::string::truncate( + fmt::string_view notes{common::truncate_utf8( h.notes(), get_centreon_storage_resources_col_size( centreon_storage_resources_notes))}; - fmt::string_view action_url{misc::string::truncate( + fmt::string_view action_url{common::truncate_utf8( h.action_url(), get_centreon_storage_resources_col_size( centreon_storage_resources_action_url))}; @@ -2581,13 +2582,13 @@ void stream::_process_pb_host_status(const std::shared_ptr<io::data>& d) { mapping::entry::invalid_on_zero); std::string full_output{ fmt::format("{}\n{}", hscr.output(), hscr.long_output())}; - size_t size = misc::string::adjust_size_utf8( + size_t size = common::adjust_size_utf8( full_output, get_centreon_storage_hosts_col_size(centreon_storage_hosts_output)); b->set_value_as_str(10, fmt::string_view(full_output.data(), size)); - size = misc::string::adjust_size_utf8( - hscr.perfdata(), get_centreon_storage_hosts_col_size( - centreon_storage_hosts_perfdata)); + size = common::adjust_size_utf8(hscr.perfdata(), + get_centreon_storage_hosts_col_size( + centreon_storage_hosts_perfdata)); b->set_value_as_str(11, fmt::string_view(hscr.perfdata().data(), size)); b->set_value_as_bool(12, hscr.flapping()); b->set_value_as_f64(13, hscr.percent_state_change()); @@ -2632,14 +2633,14 @@ void stream::_process_pb_host_status(const std::shared_ptr<io::data>& d) { mapping::entry::invalid_on_zero); std::string full_output{ fmt::format("{}\n{}", hscr.output(), hscr.long_output())}; - size_t size = misc::string::adjust_size_utf8( + size_t size = common::adjust_size_utf8( full_output, get_centreon_storage_hosts_col_size(centreon_storage_hosts_output)); _hscr_update->bind_value_as_str( 10, fmt::string_view(full_output.data(), size)); - size = misc::string::adjust_size_utf8( - hscr.perfdata(), get_centreon_storage_hosts_col_size( - centreon_storage_hosts_perfdata)); + size = common::adjust_size_utf8(hscr.perfdata(), + get_centreon_storage_hosts_col_size( + centreon_storage_hosts_perfdata)); _hscr_update->bind_value_as_str( 11, fmt::string_view(hscr.perfdata().data(), size)); _hscr_update->bind_value_as_bool(12, hscr.flapping()); @@ -4031,19 +4032,19 @@ uint64_t stream::_process_pb_service_in_resources(const Service& s, if (s.enabled()) { uint64_t sid = 0; - fmt::string_view name{misc::string::truncate( + fmt::string_view name{common::truncate_utf8( s.display_name(), get_centreon_storage_resources_col_size( centreon_storage_resources_name))}; - fmt::string_view parent_name{misc::string::truncate( + fmt::string_view parent_name{common::truncate_utf8( s.host_name(), get_centreon_storage_resources_col_size( centreon_storage_resources_parent_name))}; - fmt::string_view notes_url{misc::string::truncate( + fmt::string_view notes_url{common::truncate_utf8( s.notes_url(), get_centreon_storage_resources_col_size( centreon_storage_resources_notes_url))}; - fmt::string_view notes{misc::string::truncate( + fmt::string_view notes{common::truncate_utf8( s.notes(), get_centreon_storage_resources_col_size( centreon_storage_resources_notes))}; - fmt::string_view action_url{misc::string::truncate( + fmt::string_view action_url{common::truncate_utf8( s.action_url(), get_centreon_storage_resources_col_size( centreon_storage_resources_action_url))}; @@ -4401,10 +4402,10 @@ void stream::_check_and_update_index_cache(const Service& ss) { auto it_index_cache = _index_cache.find({ss.host_id(), ss.service_id()}); - fmt::string_view hv(misc::string::truncate( + fmt::string_view hv(common::truncate_utf8( ss.host_name(), get_centreon_storage_index_data_col_size( centreon_storage_index_data_host_name))); - fmt::string_view sv(misc::string::truncate( + fmt::string_view sv(common::truncate_utf8( ss.description(), get_centreon_storage_index_data_col_size( centreon_storage_index_data_service_description))); bool special = ss.type() == BA; @@ -4649,11 +4650,11 @@ void stream::_process_pb_service_status(const std::shared_ptr<io::data>& d) { mapping::entry::invalid_on_zero); std::string full_output{ fmt::format("{}\n{}", sscr.output(), sscr.long_output())}; - size_t size = misc::string::adjust_size_utf8( + size_t size = common::adjust_size_utf8( full_output, get_centreon_storage_services_col_size( centreon_storage_services_output)); b->set_value_as_str(11, fmt::string_view(full_output.data(), size)); - size = misc::string::adjust_size_utf8( + size = common::adjust_size_utf8( sscr.perfdata(), get_centreon_storage_services_col_size( centreon_storage_services_perfdata)); b->set_value_as_str(12, fmt::string_view(sscr.perfdata().data(), size)); @@ -4703,12 +4704,12 @@ void stream::_process_pb_service_status(const std::shared_ptr<io::data>& d) { mapping::entry::invalid_on_zero); std::string full_output{ fmt::format("{}\n{}", sscr.output(), sscr.long_output())}; - size_t size = misc::string::adjust_size_utf8( + size_t size = common::adjust_size_utf8( full_output, get_centreon_storage_services_col_size( centreon_storage_services_output)); _sscr_update->bind_value_as_str( 11, fmt::string_view(full_output.data(), size)); - size = misc::string::adjust_size_utf8( + size = common::adjust_size_utf8( sscr.perfdata(), get_centreon_storage_services_col_size( centreon_storage_services_perfdata)); _sscr_update->bind_value_as_str( @@ -4745,7 +4746,7 @@ void stream::_process_pb_service_status(const std::shared_ptr<io::data>& d) { if (_store_in_resources) { int32_t conn = _mysql.choose_connection_by_instance( _cache_host_instance[static_cast<uint32_t>(sscr.host_id())]); - size_t output_size = misc::string::adjust_size_utf8( + size_t output_size = common::adjust_size_utf8( sscr.output(), get_centreon_storage_resources_col_size( centreon_storage_resources_output)); _logger_sql->debug( diff --git a/broker/unified_sql/src/stream_storage.cc b/broker/unified_sql/src/stream_storage.cc index 01e6f92fd80..5c7968c93f6 100644 --- a/broker/unified_sql/src/stream_storage.cc +++ b/broker/unified_sql/src/stream_storage.cc @@ -38,6 +38,7 @@ #include "com/centreon/broker/unified_sql/internal.hh" #include "com/centreon/broker/unified_sql/stream.hh" #include "com/centreon/common/perfdata.hh" +#include "com/centreon/common/utf8.hh" #include "com/centreon/exceptions/msg_fmt.hh" using namespace com::centreon::exceptions; @@ -156,10 +157,10 @@ void stream::_unified_sql_process_pb_service_status( std::deque<std::shared_ptr<io::data>> to_publish; for (auto& pd : pds) { misc::read_lock rlck(_metric_cache_m); - pd.resize_name(misc::string::adjust_size_utf8( + pd.resize_name(common::adjust_size_utf8( pd.name(), get_centreon_storage_metrics_col_size( centreon_storage_metrics_metric_name))); - pd.resize_unit(misc::string::adjust_size_utf8( + pd.resize_unit(common::adjust_size_utf8( pd.unit(), get_centreon_storage_metrics_col_size( centreon_storage_metrics_unit_name))); @@ -460,10 +461,10 @@ void stream::_unified_sql_process_service_status( if (!_index_data_insert.prepared()) _index_data_insert = _mysql.prepare_query(_index_data_insert_request); - fmt::string_view hv(misc::string::truncate( + fmt::string_view hv(common::truncate_utf8( ss.host_name, get_centreon_storage_index_data_col_size( centreon_storage_index_data_host_name))); - fmt::string_view sv(misc::string::truncate( + fmt::string_view sv(common::truncate_utf8( ss.service_description, get_centreon_storage_index_data_col_size( centreon_storage_index_data_service_description))); @@ -533,10 +534,10 @@ void stream::_unified_sql_process_service_status( std::deque<std::shared_ptr<io::data>> to_publish; for (auto& pd : pds) { misc::read_lock rlck(_metric_cache_m); - pd.resize_name(misc::string::adjust_size_utf8( + pd.resize_name(common::adjust_size_utf8( pd.name(), get_centreon_storage_metrics_col_size( centreon_storage_metrics_metric_name))); - pd.resize_unit(misc::string::adjust_size_utf8( + pd.resize_unit(common::adjust_size_utf8( pd.unit(), get_centreon_storage_metrics_col_size( centreon_storage_metrics_unit_name))); diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index e220207f827..efa4315f19a 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -53,6 +53,7 @@ set(SOURCES ${SRC_DIR}/process_stat.pb.cc ${SRC_DIR}/process_stat.grpc.pb.cc ${SRC_DIR}/rapidjson_helper.cc + ${SRC_DIR}/utf8.cc ) # Include directories. diff --git a/common/http/src/http_connection.cc b/common/http/src/http_connection.cc index f17ea0b5904..a964a78c730 100644 --- a/common/http/src/http_connection.cc +++ b/common/http/src/http_connection.cc @@ -84,7 +84,7 @@ void connection_base::gest_keepalive(const response_ptr& resp) { if (std::regex_search(keep_alive_info->value().begin(), keep_alive_info->value().end(), res, keep_alive_time_out_r)) { - uint second_duration; + unsigned int second_duration; if (absl::SimpleAtoi(res[1].str(), &second_duration)) { _keep_alive_end = system_clock::now() + std::chrono::seconds(second_duration); diff --git a/common/inc/com/centreon/common/utf8.hh b/common/inc/com/centreon/common/utf8.hh new file mode 100644 index 00000000000..e9a671f6202 --- /dev/null +++ b/common/inc/com/centreon/common/utf8.hh @@ -0,0 +1,49 @@ +/** + * Copyright 2023 Centreon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + */ + +#ifndef CCCM_UTF8_HH +#define CCCM_UTF8_HH + +namespace com::centreon::common { + +/** + * @brief This function works almost like the resize method but takes care + * of the UTF-8 encoding and avoids to cut a string in the middle of a + * character. This function assumes the string to be UTF-8 encoded. + * + * @param str A string to truncate. + * @param s The desired size, maybe the resulting string will contain less + * characters. + * + * @return a reference to the string str. + */ +template <typename T> +fmt::string_view truncate_utf8(const T& str, size_t s) { + if (s >= str.size()) + return fmt::string_view(str); + if (s > 0) + while ((str[s] & 0xc0) == 0x80) + s--; + return fmt::string_view(str.data(), s); +} + +std::string check_string_utf8(std::string const& str) noexcept; +size_t adjust_size_utf8(const std::string& str, size_t s); +} // namespace com::centreon::common + +#endif \ No newline at end of file diff --git a/common/process/inc/com/centreon/common/process/process.hh b/common/process/inc/com/centreon/common/process/process.hh index caca6a1dbc9..ea097b44c01 100644 --- a/common/process/inc/com/centreon/common/process/process.hh +++ b/common/process/inc/com/centreon/common/process/process.hh @@ -108,7 +108,7 @@ class process : public std::enable_shared_from_this<process> { template <typename string_class> void write_to_stdin(const string_class& content); - void start_process(); + void start_process(bool enable_stdin); void kill(); diff --git a/common/process/src/process.cc b/common/process/src/process.cc index 09ef3545aef..615d16da564 100644 --- a/common/process/src/process.cc +++ b/common/process/src/process.cc @@ -35,34 +35,99 @@ namespace com::centreon::common::detail { */ struct boost_process { #if defined(BOOST_PROCESS_V2_WINDOWS) + /** + * @brief Construct a new boost process object + * stdin of the child process is managed + * + * @param io_context + * @param exe_path absolute or relative exe path + * @param args arguments of the command + */ boost_process(asio::io_context& io_context, const std::string& exe_path, const std::vector<std::string>& args) - : stdout(io_context), - stderr(io_context), - stdin(io_context), + : stdout_pipe(io_context), + stderr_pipe(io_context), + stdin_pipe(io_context), proc(io_context, exe_path, args, - proc::process_stdio{stdin, stdout, stderr}) {} + proc::process_stdio{stdin_pipe, stdout_pipe, stderr_pipe}) {} + + /** + * @brief Construct a new boost process object + * stdin of the child process is not managed + * + * @param io_context + * @param logger + * @param cmd_line cmd line split (the first element is the path of the + * executable) + * @param no_stdin (not used) + */ + boost_process(asio::io_context& io_context, + const std::string& exe_path, + const std::vector<std::string>& args, + bool no_stdin) + : stdout_pipe(io_context), + stderr_pipe(io_context), + stdin_pipe(io_context), + proc(io_context, + exe_path, + args, + proc::process_stdio{{}, stdout_pipe, stderr_pipe}) {} + #else + /** + * @brief Construct a new boost process object + * stdin of the child process is managed + * + * @param io_context + * @param exe_path absolute or relative exe path + * @param args arguments of the command + */ boost_process(asio::io_context& io_context, const std::string& exe_path, const std::vector<std::string>& args) - : stdout(io_context), - stderr(io_context), - stdin(io_context), - proc(proc::posix::centreon_posix_default_launcher() - /*proc::default_process_launcher()*/ ( - io_context.get_executor(), - exe_path, - args, - proc::posix::centreon_process_stdio{stdin, stdout, stderr})) {} + : stdout_pipe(io_context), + stderr_pipe(io_context), + stdin_pipe(io_context), + proc(proc::posix::centreon_posix_default_launcher()( + io_context.get_executor(), + exe_path, + args, + proc::posix::centreon_process_stdio{stdin_pipe, stdout_pipe, + stderr_pipe})) {} + + /** + * @brief Construct a new boost process object + * stdin of the child process is not managed + * + * @param io_context + * @param logger + * @param cmd_line cmd line split (the first element is the path of the + * executable) + * @param no_stdin (not used) + */ + boost_process(asio::io_context& io_context, + const std::string& exe_path, + const std::vector<std::string>& args, + bool no_stdin) + : stdout_pipe(io_context), + stderr_pipe(io_context), + stdin_pipe(io_context), + proc(proc::posix::centreon_posix_default_launcher()( + io_context, + exe_path, + args, + proc::posix::centreon_process_stdio{{}, + stdout_pipe, + stderr_pipe})) {} + #endif - asio::readable_pipe stdout; - asio::readable_pipe stderr; - asio::writable_pipe stdin; + asio::readable_pipe stdout_pipe; + asio::readable_pipe stderr_pipe; + asio::writable_pipe stdin_pipe; proc::process proc; }; } // namespace com::centreon::common::detail @@ -99,16 +164,21 @@ process::process(const std::shared_ptr<boost::asio::io_context>& io_context, * In this function, we start child process and stdout, stderr asynchronous read * we also start an asynchronous read on process fd to be aware of child process * termination + * + * @param enable_stdin On Windows set it to false if you doesn't want to write + * on child stdin */ -void process::start_process() { +void process::start_process(bool enable_stdin) { SPDLOG_LOGGER_DEBUG(_logger, "start process: {}", _exe_path); absl::MutexLock l(&_protect); _stdin_write_queue.clear(); _write_pending = false; try { - _proc = - std::make_shared<detail::boost_process>(*_io_context, _exe_path, _args); + _proc = enable_stdin ? std::make_shared<detail::boost_process>( + *_io_context, _exe_path, _args) + : std::make_shared<detail::boost_process>( + *_io_context, _exe_path, _args, false); SPDLOG_LOGGER_TRACE(_logger, "process started: {} pid: {}", _exe_path, _proc->proc.id()); _proc->proc.async_wait( @@ -154,9 +224,13 @@ void process::on_process_end(const boost::system::error_code& err, void process::kill() { absl::MutexLock l(&_protect); if (_proc) { + SPDLOG_LOGGER_INFO(_logger, "kill process"); boost::system::error_code err; _proc->proc.terminate(err); - _proc.reset(); + if (err) { + SPDLOG_LOGGER_INFO(_logger, "fail to kill {}: {}", _exe_path, + err.message()); + } } } @@ -188,7 +262,7 @@ void process::stdin_write_no_lock(const std::shared_ptr<std::string>& data) { } else { try { _write_pending = true; - _proc->stdin.async_write_some( + _proc->stdin_pipe.async_write_some( asio::buffer(*data), [me = shared_from_this(), caller = _proc, data]( const boost::system::error_code& err, size_t nb_written) { @@ -244,7 +318,7 @@ void process::on_stdin_write(const boost::system::error_code& err) { void process::stdout_read() { if (_proc) { try { - _proc->stdout.async_read_some( + _proc->stdout_pipe.async_read_some( asio::buffer(_stdout_read_buffer), [me = shared_from_this(), caller = _proc]( const boost::system::error_code& err, size_t nb_read) { @@ -274,12 +348,12 @@ void process::stdout_read() { void process::on_stdout_read(const boost::system::error_code& err, size_t nb_read) { if (err) { - if (err == asio::error::eof) { + if (err == asio::error::eof || err == asio::error::broken_pipe) { SPDLOG_LOGGER_DEBUG(_logger, "fail read from stdout of process {}: {}", _exe_path, err.message()); } else { - SPDLOG_LOGGER_ERROR(_logger, "fail read from stdout of process {}: {}", - _exe_path, err.message()); + SPDLOG_LOGGER_ERROR(_logger, "fail read from stdout of process {}: {} {}", + _exe_path, err.value(), err.message()); } return; } @@ -295,7 +369,7 @@ void process::on_stdout_read(const boost::system::error_code& err, void process::stderr_read() { if (_proc) { try { - _proc->stderr.async_read_some( + _proc->stderr_pipe.async_read_some( asio::buffer(_stderr_read_buffer), [me = shared_from_this(), caller = _proc]( const boost::system::error_code& err, size_t nb_read) { @@ -325,12 +399,12 @@ void process::stderr_read() { void process::on_stderr_read(const boost::system::error_code& err, size_t nb_read) { if (err) { - if (err == asio::error::eof) { + if (err == asio::error::eof || err == asio::error::broken_pipe) { SPDLOG_LOGGER_DEBUG(_logger, "fail read from stderr of process {}: {}", _exe_path, err.message()); } else { - SPDLOG_LOGGER_ERROR(_logger, "fail read from stderr of process {}: {}", - _exe_path, err.message()); + SPDLOG_LOGGER_ERROR(_logger, "fail read from stderr of process {}: {} {}", + _exe_path, err.value(), err.message()); } } else { SPDLOG_LOGGER_TRACE(_logger, " process: {} read from stdout: {}", _exe_path, diff --git a/common/src/utf8.cc b/common/src/utf8.cc new file mode 100644 index 00000000000..7ff784b7167 --- /dev/null +++ b/common/src/utf8.cc @@ -0,0 +1,275 @@ +/** + * Copyright 2022-2024 Centreon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + */ + +/** + * @brief Checks if the string given as parameter is a real UTF-8 string. + * If it is not, it tries to convert it to UTF-8. Encodings correctly changed + * are ISO-8859-15 and CP-1252. + * + * @param str The string to check + * + * @return The string itself or a new string converted to UTF-8. The output + * string should always be an UTF-8 string. + */ + +#include "utf8.hh" + +std::string com::centreon::common::check_string_utf8( + std::string const& str) noexcept { + std::string::const_iterator it; + for (it = str.begin(); it != str.end();) { + uint32_t val = (*it & 0xff); + if ((val & 0x80) == 0) { + ++it; + continue; + } + val = (val << 8) | (*(it + 1) & 0xff); + if ((val & 0xe0c0) == 0xc080) { + val &= 0x1e00; + if (val == 0) + break; + it += 2; + continue; + } + + val = (val << 8) | (*(it + 2) & 0xff); + if ((val & 0xf0c0c0) == 0xe08080) { + val &= 0xf2000; + if (val == 0 || val == 0xd2000) + break; + it += 3; + continue; + } + + val = (val << 8) | (*(it + 3) & 0xff); + if ((val & 0xf8c0c0c0) == 0xF0808080) { + val &= 0x7300000; + if (val == 0 || val > 0x4000000) + break; + it += 4; + continue; + } + break; + } + + if (it == str.end()) + return str; + + /* Not an UTF-8 string */ + bool is_cp1252 = true, is_iso8859 = true; + auto itt = it; + + auto iso8859_to_utf8 = [&str, &it]() -> std::string { + /* Strings are both cp1252 and iso8859-15 */ + std::string out; + std::size_t d = it - str.begin(); + out.reserve(d + 2 * (str.size() - d)); + out = str.substr(0, d); + while (it != str.end()) { + uint8_t c = static_cast<uint8_t>(*it); + if (c < 128) + out.push_back(c); + else if (c <= 160) + out.push_back('_'); + else { + switch (c) { + case 0xa4: + out.append("€"); + break; + case 0xa6: + out.append("Š"); + break; + case 0xa8: + out.append("š"); + break; + case 0xb4: + out.append("Ž"); + break; + case 0xb8: + out.append("ž"); + break; + case 0xbc: + out.append("Œ"); + break; + case 0xbd: + out.append("œ"); + break; + case 0xbe: + out.append("Ÿ"); + break; + default: + out.push_back(0xc0 | c >> 6); + out.push_back((c & 0x3f) | 0x80); + break; + } + } + ++it; + } + return out; + }; + do { + uint8_t c = *itt; + /* not ISO-8859-15 */ + if (c > 126 && c < 160) + is_iso8859 = false; + /* not cp1252 */ + if (c & 128) + if (c == 129 || c == 141 || c == 143 || c == 144 || c == 155) + is_cp1252 = false; + if (!is_cp1252) + return iso8859_to_utf8(); + else if (!is_iso8859) { + std::string out; + std::size_t d = it - str.begin(); + out.reserve(d + 3 * (str.size() - d)); + out = str.substr(0, d); + while (it != str.end()) { + c = *it; + if (c < 128) + out.push_back(c); + else { + switch (c) { + case 128: + out.append("€"); + break; + case 129: + case 141: + case 143: + case 144: + case 157: + out.append("_"); + break; + case 130: + out.append("‚"); + break; + case 131: + out.append("ƒ"); + break; + case 132: + out.append("„"); + break; + case 133: + out.append("…"); + break; + case 134: + out.append("†"); + break; + case 135: + out.append("‡"); + break; + case 136: + out.append("ˆ"); + break; + case 137: + out.append("‰"); + break; + case 138: + out.append("Š"); + break; + case 139: + out.append("‹"); + break; + case 140: + out.append("Œ"); + break; + case 142: + out.append("Ž"); + break; + case 145: + out.append("‘"); + break; + case 146: + out.append("’"); + break; + case 147: + out.append("“"); + break; + case 148: + out.append("”"); + break; + case 149: + out.append("•"); + break; + case 150: + out.append("–"); + break; + case 151: + out.append("—"); + break; + case 152: + out.append("˜"); + break; + case 153: + out.append("™"); + break; + case 154: + out.append("š"); + break; + case 155: + out.append("›"); + break; + case 156: + out.append("œ"); + break; + case 158: + out.append("ž"); + break; + case 159: + out.append("Ÿ"); + break; + default: + out.push_back(0xc0 | c >> 6); + out.push_back((c & 0x3f) | 0x80); + break; + } + } + ++it; + } + return out; + } + ++itt; + } while (itt != str.end()); + assert(is_cp1252 == is_iso8859); + return iso8859_to_utf8(); +} + +/** + * @brief This function adjusts the given integer s so that the str string may + * be cut at this length and still be a UTF-8 string (we don't want to cut it + * in a middle of a character). + * + * This function assumes the string to be UTF-8 encoded. + * + * @param str A string to truncate. + * @param s The desired size, maybe the resulting string will contain less + * characters. + * + * @return The newly computed size. + */ +size_t com::centreon::common::adjust_size_utf8(const std::string& str, + size_t s) { + if (s >= str.size()) + return str.size(); + if (s == 0) + return s; + else { + while ((str[s] & 0xc0) == 0x80) + s--; + return s; + } +} diff --git a/common/tests/CMakeLists.txt b/common/tests/CMakeLists.txt index fd673759850..afd6c972eb8 100644 --- a/common/tests/CMakeLists.txt +++ b/common/tests/CMakeLists.txt @@ -25,6 +25,7 @@ add_executable(ut_common process_test.cc rapidjson_helper_test.cc test_main.cc + utf8_test.cc ${TESTS_SOURCES}) set_target_properties( diff --git a/common/tests/process_test.cc b/common/tests/process_test.cc index 67cbc3630cb..3c7eb63e2ab 100644 --- a/common/tests/process_test.cc +++ b/common/tests/process_test.cc @@ -55,8 +55,10 @@ class process_wait : public process { void on_stdout_read(const boost::system::error_code& err, size_t nb_read) override { if (!err) { - _stdout += std::string_view(_stdout_read_buffer, nb_read); - } else if (err == asio::error::eof) { + std::string_view line(_stdout_read_buffer, nb_read); + _stdout += line; + SPDLOG_LOGGER_DEBUG(_logger, "read from stdout: {}", line); + } else if (err == asio::error::eof || err == asio::error::broken_pipe) { _stdout_eof = true; _notify(); } @@ -66,8 +68,10 @@ class process_wait : public process { void on_stderr_read(const boost::system::error_code& err, size_t nb_read) override { if (!err) { - _stderr += std::string_view(_stderr_read_buffer, nb_read); - } else if (err == asio::error::eof) { + std::string_view line(_stderr_read_buffer, nb_read); + _stderr += line; + SPDLOG_LOGGER_DEBUG(_logger, "read from stderr: {}", line); + } else if (err == asio::error::eof || err == asio::error::broken_pipe) { _stderr_eof = true; _notify(); } @@ -77,6 +81,7 @@ class process_wait : public process { void on_process_end(const boost::system::error_code& err, int raw_exit_status) override { process::on_process_end(err, raw_exit_status); + SPDLOG_LOGGER_DEBUG(_logger, "process end"); _process_ended = true; _notify(); } @@ -107,7 +112,7 @@ TEST_F(process_test, echo) { using namespace std::literals; std::shared_ptr<process_wait> to_wait( new process_wait(g_io_context, _logger, "/bin/echo", {"hello"s})); - to_wait->start_process(); + to_wait->start_process(true); to_wait->wait(); ASSERT_EQ(to_wait->get_exit_status(), 0); ASSERT_EQ(to_wait->get_stdout(), "hello\n"); @@ -118,14 +123,14 @@ TEST_F(process_test, throw_on_error) { using namespace std::literals; std::shared_ptr<process_wait> to_wait( new process_wait(g_io_context, _logger, "turlututu", {"hello"s})); - ASSERT_THROW(to_wait->start_process(), std::exception); + ASSERT_THROW(to_wait->start_process(true), std::exception); } TEST_F(process_test, script_error) { using namespace std::literals; std::shared_ptr<process_wait> to_wait( new process_wait(g_io_context, _logger, "/bin/sh", {"taratata"s})); - to_wait->start_process(); + to_wait->start_process(true); to_wait->wait(); ASSERT_NE(to_wait->get_exit_status(), 0); ASSERT_EQ(to_wait->get_stdout(), ""); @@ -138,7 +143,7 @@ TEST_F(process_test, call_start_several_time) { std::string expected; for (int ii = 0; ii < 10; ++ii) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); - to_wait->start_process(); + to_wait->start_process(true); expected += "hello\n"; } to_wait->wait(); @@ -155,7 +160,7 @@ TEST_F(process_test, stdin_to_stdout) { std::shared_ptr<process_wait> loopback( new process_wait(g_io_context, _logger, "/bin/sh toto.sh")); - loopback->start_process(); + loopback->start_process(true); std::string expected; for (unsigned ii = 0; ii < 10; ++ii) { @@ -174,7 +179,7 @@ TEST_F(process_test, shell_stdin_to_stdout) { std::shared_ptr<process_wait> loopback( new process_wait(g_io_context, _logger, "/bin/sh")); - loopback->start_process(); + loopback->start_process(true); std::string expected; for (unsigned ii = 0; ii < 10; ++ii) { diff --git a/common/tests/utf8_test.cc b/common/tests/utf8_test.cc new file mode 100644 index 00000000000..98376f390ce --- /dev/null +++ b/common/tests/utf8_test.cc @@ -0,0 +1,215 @@ +/** + * Copyright 2024 Centreon + * Licensed under the Apache License, Version 2.0(the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + */ + +#include <gtest/gtest.h> + +#include "utf8.hh" + +using namespace com::centreon::common; + +/* + * Given a string encoded in ISO-8859-15 and CP-1252 + * Then the check_string_utf8 function converts it to UTF-8. + */ +TEST(string_check_utf8, simple) { + std::string txt("L'acc\350s \340 l'h\364tel est encombr\351"); + ASSERT_EQ(check_string_utf8(txt), "L'accès à l'hôtel est encombré"); +} + +/* + * Given a string encoded in UTF-8 + * Then the check_string_utf8 function returns itself. + */ +TEST(string_check_utf8, utf8) { + std::string txt("L'accès à l'hôtel est encombré"); + ASSERT_EQ(check_string_utf8(txt), "L'accès à l'hôtel est encombré"); +} + +/* + * Given a string encoded in CP-1252 + * Then the check_string_utf8 function converts it to UTF-8. + */ +TEST(string_check_utf8, cp1252) { + std::string txt("Le ticket co\xfbte 12\x80\n"); + ASSERT_EQ(check_string_utf8(txt), "Le ticket coûte 12€\n"); +} + +/* + * Given a string encoded in ISO-8859-15 + * Then the check_string_utf8 function converts it to UTF-8. + */ +TEST(string_check_utf8, iso8859) { + std::string txt("Le ticket co\xfbte 12\xa4\n"); + ASSERT_EQ(check_string_utf8(txt), "Le ticket coûte 12€\n"); +} + +/* + * Given a string encoded in ISO-8859-15 + * Then the check_string_utf8 function converts it to UTF-8. + */ +TEST(string_check_utf8, iso8859_cpx) { + std::string txt("\xa4\xa6\xa8\xb4\xb8\xbc\xbd\xbe"); + ASSERT_EQ(check_string_utf8(txt), "€ŠšŽžŒœŸ"); +} + +/* + * Given a string encoded in CP-1252 + * Then the check_string_utf8 function converts it to UTF-8. + */ +TEST(string_check_utf8, cp1252_cpx) { + std::string txt("\x80\x95\x82\x89\x8a"); + ASSERT_EQ(check_string_utf8(txt), "€•‚‰Š"); +} + +/* + * Given a string badly encoded in CP-1252 + * Then the check_string_utf8 function converts it to UTF-8 and replaces bad + * characters into '_'. + */ +TEST(string_check_utf8, whatever_as_cp1252) { + std::string txt; + for (uint8_t c = 32; c < 255; c++) + if (c != 127) + txt.push_back(c); + std::string result( + " !\"#$%&'()*+,-./" + "0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`" + "abcdefghijklmnopqrstuvwxyz{|}~€_‚ƒ„…†‡ˆ‰Š‹Œ_Ž__‘’“”•–—˜™š›œ_" + "žŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäå" + "æçèéêëìíîïðñòóôõö÷øùúûüýþ"); + ASSERT_EQ(check_string_utf8(txt), result); +} + +/* + * Given a string badly encoded in ISO-8859-15 + * Then the check_string_utf8 function converts it to UTF-8 and replaces bad + * characters into '_'. + */ +TEST(string_check_utf8, whatever_as_iso8859) { + /* Construction of a string that is not cp1252 so it should be considered as + * iso8859-15 */ + std::string txt; + for (uint8_t c = 32; c < 255; c++) { + if (c == 32) + txt.push_back(0x81); + if (c != 127) + txt.push_back(c); + } + std::string result( + "_ " + "!\"#$%&'()*+,-./" + "0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`" + "abcdefghijklmnopqrstuvwxyz{|}~_________________________________" + "¡¢£€¥Š§š©ª«¬­®¯°±²³Žµ¶·ž¹º»ŒœŸ¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçè" + "éêëìíîïðñòóôõö÷øùúûüýþ"); + ASSERT_EQ(check_string_utf8(txt), result); +} + +/* + * In case of a string containing multiple encoding, the resulting string should + * be an UTF-8 string. Here we have a string beginning with UTF-8 and finishing + * with cp1252. The resulting string is good and is UTF-8 only encoded. + */ +TEST(string_check_utf8, utf8_and_cp1252) { + std::string txt( + "\xc3\xa9\xc3\xa7\xc3\xa8\xc3\xa0\xc3\xb9\xc3\xaf\xc3\xab\x7e\x23\x0a\xe9" + "\xe7\xe8\xe0\xf9\xef\xeb\x7e\x23\x0a"); + std::string result("éçèàùïë~#\néçèàùïë~#\n"); + ASSERT_EQ(check_string_utf8(txt), result); +} + +/* A check coming from windows with characters from the cmd console */ +TEST(string_check_utf8, strange_string) { + std::string txt( + "WARNING - [Triggered by _ItemCount>0] - 1 event(s) of Severity Level: " + "\"Error\", were recorded in the last 24 hours from the Application " + "Event Log. (List is on next line. Fields shown are - " + "Logfile:TimeGenerated:EventId:EventCode:SeverityLevel:Type:SourceName:" + "Message)|'Event " + "Count'=1;0;50;\nApplication:20200806000001.000000-000:3221243278:17806:" + "Erreur:MSSQLSERVER:╔chec de la nÚgociation SSPI avec le code " + "d'erreurá0x8009030c lors de l'Útablissement d'une connexion avec une " + "sÚcuritÚ intÚgrÚeá; la connexion a ÚtÚ fermÚe. [CLIENTá: X.X.X.X]"); + ASSERT_EQ(check_string_utf8(txt), txt); +} + +/* A check coming from windows with characters from the cmd console */ +TEST(string_check_utf8, chinese) { + std::string txt("超级杀手死亡检查"); + ASSERT_EQ(check_string_utf8(txt), txt); +} + +/* A check coming from windows with characters from the cmd console */ +TEST(string_check_utf8, vietnam) { + std::string txt( + "looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo" + "ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong " + "chinese 告警数量 output puté! | '告警数量'=42\navé dé long ouput oçi " + "还有中国人! Hái yǒu zhòng guó rén!"); + ASSERT_EQ(check_string_utf8(txt), txt); +} + +TEST(truncate, nominal1) { + std::string str("foobar"); + ASSERT_EQ(truncate_utf8(str, 3), "foo"); +} + +TEST(truncate, nominal2) { + std::string str("foobar"); + ASSERT_EQ(truncate_utf8(str, 0), ""); +} + +TEST(truncate, nominal3) { + std::string str("foobar 超级杀手死亡检查"); + ASSERT_EQ(truncate_utf8(str, 1000), "foobar 超级杀手死亡检查"); +} + +TEST(truncate, utf8_1) { + std::string str("告警数量"); + for (size_t i = 0; i <= str.size(); i++) { + fmt::string_view tmp(str); + fmt::string_view res(truncate_utf8(tmp, i)); + std::string tmp1(check_string_utf8(std::string(res.data(), res.size()))); + ASSERT_EQ(res, tmp1); + } +} + +TEST(adjust_size_utf8, nominal1) { + std::string str("foobar"); + ASSERT_EQ(fmt::string_view(str.data(), adjust_size_utf8(str, 3)), + fmt::string_view("foo")); +} + +TEST(adjust_size_utf8, nominal2) { + std::string str("foobar"); + ASSERT_EQ(fmt::string_view(str.data(), adjust_size_utf8(str, 0)), ""); +} + +TEST(adjust_size_utf8, nominal3) { + std::string str("foobar 超级杀手死亡检查"); + ASSERT_EQ(fmt::string_view(str.data(), adjust_size_utf8(str, 1000)), str); +} + +TEST(adjust_size_utf8, utf8_1) { + std::string str("告警数量"); + for (size_t i = 0; i <= str.size(); i++) { + fmt::string_view sv(str.data(), adjust_size_utf8(str, i)); + std::string tmp( + check_string_utf8(std::string(sv.data(), sv.data() + sv.size()))); + ASSERT_EQ(sv.size(), tmp.size()); + } +} diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index fe556d5bbfc..1a9c13caf88 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -520,6 +520,7 @@ target_link_libraries( enginerpc centreon_grpc centreon_http + centreon_common -L${Boost_LIBRARY_DIR_RELEASE} boost_url cce_core diff --git a/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/conf_helper.hh b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/conf_helper.hh index c3a0456eeae..a2bbb242c08 100644 --- a/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/conf_helper.hh +++ b/engine/modules/opentelemetry/inc/com/centreon/engine/modules/opentelemetry/conf_helper.hh @@ -41,15 +41,17 @@ bool get_otel_commands(const std::string& host_name, command_handler&& handler, const std::shared_ptr<spdlog::logger>& logger) { auto use_otl_command = [](const checkable& to_test) -> bool { - if (to_test.get_check_command_ptr()->get_type() == - commands::command::e_type::otel) - return true; - if (to_test.get_check_command_ptr()->get_type() == - commands::command::e_type::forward) { - return std::static_pointer_cast<commands::forward>( - to_test.get_check_command_ptr()) - ->get_sub_command() - ->get_type() == commands::command::e_type::otel; + if (to_test.get_check_command_ptr()) { + if (to_test.get_check_command_ptr()->get_type() == + commands::command::e_type::otel) + return true; + if (to_test.get_check_command_ptr()->get_type() == + commands::command::e_type::forward) { + return std::static_pointer_cast<commands::forward>( + to_test.get_check_command_ptr()) + ->get_sub_command() + ->get_type() == commands::command::e_type::otel; + } } return false; }; diff --git a/engine/modules/opentelemetry/src/centreon_agent/to_agent_connector.cc b/engine/modules/opentelemetry/src/centreon_agent/to_agent_connector.cc index e3eaf7918b2..f8cce8607a9 100644 --- a/engine/modules/opentelemetry/src/centreon_agent/to_agent_connector.cc +++ b/engine/modules/opentelemetry/src/centreon_agent/to_agent_connector.cc @@ -74,7 +74,8 @@ agent_connection::agent_connection( "reverse_client", conf, handler, - logger) { + logger), + _parent(parent) { _peer = parent->get_conf()->get_hostport(); } @@ -219,4 +220,4 @@ void to_agent_connector::shutdown() { void to_agent_connector::on_error() { common::defer(_io_context, std::chrono::seconds(10), [me = shared_from_this()] { me->start(); }); -} \ No newline at end of file +} diff --git a/tests/init-sql-docker.sh b/tests/init-sql-docker.sh index acbc4965601..70efc5b97ee 100755 --- a/tests/init-sql-docker.sh +++ b/tests/init-sql-docker.sh @@ -16,8 +16,8 @@ apt update && apt install -y mysql-client #create users if [ $database_type == 'mysql' ]; then echo "create users mysql" - mysql --user="$DBUserRoot" --password="$DBPassRoot" -h 127.0.0.1 -e "CREATE USER IF NOT EXISTS 'centreon'@'%' IDENTIFIED WITH mysql_native_password BY 'centreon'" - mysql --user="$DBUserRoot" --password="$DBPassRoot" -h 127.0.0.1 -e "CREATE USER IF NOT EXISTS 'root_centreon'@'%' IDENTIFIED WITH mysql_native_password BY 'centreon'" + mysql --user="$DBUserRoot" --password="$DBPassRoot" -h 127.0.0.1 -e "CREATE USER IF NOT EXISTS 'centreon'@'%' IDENTIFIED BY 'centreon'" + mysql --user="$DBUserRoot" --password="$DBPassRoot" -h 127.0.0.1 -e "CREATE USER IF NOT EXISTS 'root_centreon'@'%' IDENTIFIED BY 'centreon'" else #mariadb case ss -plant | grep -w 3306 From 8f1e7a1fb52f304baa879a2b1b3306c3e0d54ab6 Mon Sep 17 00:00:00 2001 From: May <110405507+mushroomempires@users.noreply.github.com> Date: Mon, 8 Jul 2024 10:10:53 +0200 Subject: [PATCH 871/948] chore(deps): absorb 24.07 dependabot GitHub Actions updates (#1496) * chore(deps): bump docker/build-push-action from 5.3.0 to 6.2.0 (#1484) Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 5.3.0 to 6.2.0. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/2cdde995de11925a030ce8070c3d77a52ffcf1c0...15560696de535e4014efeff63c48f16952e52dd1) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump actions/checkout from 4.1.1 to 4.1.7 (#1446) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.1 to 4.1.7. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4.1.1...692973e3d937129bcbf40652eb9f2f61becf3332) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump docker/login-action from 3.1.0 to 3.2.0 (#1391) Bumps [docker/login-action](https://github.com/docker/login-action) from 3.1.0 to 3.2.0. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/e92390c5fb421da1463c202d546fed0ec5c39f20...0d4c9c5ea7693da7b068278f7b52bda2a190a446) --- updated-dependencies: - dependency-name: docker/login-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/actionlint.yml | 4 ++-- .github/workflows/centreon-collect.yml | 12 ++++++------ .github/workflows/docker-builder.yml | 8 ++++---- .github/workflows/get-version.yml | 2 +- .github/workflows/libzmq.yml | 6 +++--- .github/workflows/package-collect.yml | 2 +- .github/workflows/rebase-master.yml | 2 +- .github/workflows/rebase-version.yml | 2 +- .github/workflows/release-trigger-builds.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/robot-nightly.yml | 6 +++--- .github/workflows/robot-test.yml | 12 ++++++------ .github/workflows/veracode-analysis.yml | 2 +- 13 files changed, 31 insertions(+), 31 deletions(-) diff --git a/.github/workflows/actionlint.yml b/.github/workflows/actionlint.yml index 8388e621380..7cdce10803b 100644 --- a/.github/workflows/actionlint.yml +++ b/.github/workflows/actionlint.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout sources - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Download actionlint id: get_actionlint @@ -42,7 +42,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout sources - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Install Yaml run: | diff --git a/.github/workflows/centreon-collect.yml b/.github/workflows/centreon-collect.yml index f53dd642c0c..2e5954ffe17 100644 --- a/.github/workflows/centreon-collect.yml +++ b/.github/workflows/centreon-collect.yml @@ -73,10 +73,10 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Login to Registry - uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 + uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 with: registry: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }} username: ${{ secrets.DOCKER_REGISTRY_ID }} @@ -110,7 +110,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: path: centreon-collect @@ -141,7 +141,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Publish RPM packages uses: ./.github/actions/delivery @@ -176,7 +176,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Publish DEB packages uses: ./.github/actions/delivery @@ -200,7 +200,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Promote ${{ matrix.distrib }} to stable uses: ./.github/actions/promote-to-stable diff --git a/.github/workflows/docker-builder.yml b/.github/workflows/docker-builder.yml index ff48ab61d7c..0e2e4189fb0 100644 --- a/.github/workflows/docker-builder.yml +++ b/.github/workflows/docker-builder.yml @@ -92,17 +92,17 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Login to Registry - uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 + uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 with: registry: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }} username: ${{ secrets.DOCKER_REGISTRY_ID }} password: ${{ secrets.DOCKER_REGISTRY_PASSWD }} - name: Login to Proxy Registry - uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 + uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 with: registry: ${{ vars.DOCKER_PROXY_REGISTRY_URL }} username: ${{ secrets.DOCKER_REGISTRY_ID }} @@ -111,7 +111,7 @@ jobs: - uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 - name: Build image ${{ matrix.image }}:${{ matrix.tag }} - uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0 + uses: docker/build-push-action@15560696de535e4014efeff63c48f16952e52dd1 # v6.2.0 with: file: .github/docker/Dockerfile.${{ matrix.dockerfile }} context: . diff --git a/.github/workflows/get-version.yml b/.github/workflows/get-version.yml index a7de09434e6..43fe25d2fe7 100644 --- a/.github/workflows/get-version.yml +++ b/.github/workflows/get-version.yml @@ -53,7 +53,7 @@ jobs: release_cloud: ${{ steps.get_version.outputs.release_cloud}} steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: install gh cli on self-hosted runner run: | diff --git a/.github/workflows/libzmq.yml b/.github/workflows/libzmq.yml index 7d39d46030d..d1c2778e6a2 100644 --- a/.github/workflows/libzmq.yml +++ b/.github/workflows/libzmq.yml @@ -153,7 +153,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Publish RPM packages uses: ./.github/actions/delivery @@ -188,7 +188,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Publish DEB packages uses: ./.github/actions/delivery @@ -212,7 +212,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Promote ${{ matrix.distrib }} to stable uses: ./.github/actions/promote-to-stable diff --git a/.github/workflows/package-collect.yml b/.github/workflows/package-collect.yml index 165a5846783..7b23883c389 100644 --- a/.github/workflows/package-collect.yml +++ b/.github/workflows/package-collect.yml @@ -83,7 +83,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Install sccache run: | diff --git a/.github/workflows/rebase-master.yml b/.github/workflows/rebase-master.yml index a49afb39f8d..03520557266 100644 --- a/.github/workflows/rebase-master.yml +++ b/.github/workflows/rebase-master.yml @@ -16,7 +16,7 @@ jobs: if: github.event.pull_request.merged == true steps: - name: git checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: fetch-depth: 0 token: ${{ secrets.CENTREON_TECHNIQUE_PAT }} diff --git a/.github/workflows/rebase-version.yml b/.github/workflows/rebase-version.yml index ef93c147a52..c89b3fe98b5 100644 --- a/.github/workflows/rebase-version.yml +++ b/.github/workflows/rebase-version.yml @@ -16,7 +16,7 @@ jobs: if: github.event.pull_request.merged == true steps: - name: git checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: fetch-depth: 0 token: ${{ secrets.CENTREON_TECHNIQUE_PAT }} diff --git a/.github/workflows/release-trigger-builds.yml b/.github/workflows/release-trigger-builds.yml index 0fbd70c5989..bb9145855d0 100644 --- a/.github/workflows/release-trigger-builds.yml +++ b/.github/workflows/release-trigger-builds.yml @@ -19,7 +19,7 @@ jobs: release-trigger-builds: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Install Github CLI run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d5fde4487e1..594d0392f0a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,7 +37,7 @@ jobs: shell: bash - name: Checkout sources - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: fetch-depth: 0 diff --git a/.github/workflows/robot-nightly.yml b/.github/workflows/robot-nightly.yml index 711cddd863b..dd10ad0242c 100644 --- a/.github/workflows/robot-nightly.yml +++ b/.github/workflows/robot-nightly.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Checkout sources - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - run: | gh workflow run robot-nightly.yml -r "dev-24.04.x" @@ -142,7 +142,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Publish RPM packages uses: ./.github/actions/delivery @@ -175,7 +175,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Publish DEB packages uses: ./.github/actions/delivery diff --git a/.github/workflows/robot-test.yml b/.github/workflows/robot-test.yml index c10e0629e55..65ba4a39524 100644 --- a/.github/workflows/robot-test.yml +++ b/.github/workflows/robot-test.yml @@ -50,17 +50,17 @@ jobs: runs-on: ${{ contains(inputs.image, 'arm') && fromJson('["self-hosted", "collect-arm64"]') || 'ubuntu-22.04' }} steps: - name: Checkout sources - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Login to Registry - uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 + uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 with: registry: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }} username: ${{ secrets.registry_username }} password: ${{ secrets.registry_password }} - name: Login to Proxy Registry - uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 + uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 with: registry: ${{ vars.DOCKER_PROXY_REGISTRY_URL }} username: ${{ secrets.registry_username }} @@ -90,7 +90,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: List features id: list-features @@ -111,7 +111,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: fetch-depth: 0 @@ -195,7 +195,7 @@ jobs: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Download Artifacts uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 diff --git a/.github/workflows/veracode-analysis.yml b/.github/workflows/veracode-analysis.yml index e9b41d85811..a54957511cc 100644 --- a/.github/workflows/veracode-analysis.yml +++ b/.github/workflows/veracode-analysis.yml @@ -72,7 +72,7 @@ jobs: password: ${{ secrets.docker_registry_passwd }} steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - if: ${{ inputs.module_name == 'centreon-collect' }} name: Compiling Cpp sources From 9ca06eef5772e4061dd83aa91db70b209354f607 Mon Sep 17 00:00:00 2001 From: May <110405507+mushroomempires@users.noreply.github.com> Date: Mon, 8 Jul 2024 12:00:03 +0200 Subject: [PATCH 872/948] chore(deps): absorb 24.07 dependabot GitHub Actions updates on gorgone (#1503) --- .github/workflows/docker-gorgone-testing.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-gorgone-testing.yml b/.github/workflows/docker-gorgone-testing.yml index 5042c35f132..2854f3e3668 100644 --- a/.github/workflows/docker-gorgone-testing.yml +++ b/.github/workflows/docker-gorgone-testing.yml @@ -32,7 +32,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Login to registry - uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 + uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 with: registry: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }} username: ${{ secrets.DOCKER_REGISTRY_ID }} @@ -40,7 +40,7 @@ jobs: - uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 - - uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0 + - uses: docker/build-push-action@1a162644f9a7e87d8f4b053101d1d9a712edc18c # v6.3.0 with: file: .github/docker/Dockerfile.gorgone-testing-${{ matrix.distrib }} context: . From 9ae91749702cba11ce5e3150c2e9c8cd4281e67f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Delpierre?= <jdelpierre@users.noreply.github.com> Date: Mon, 8 Jul 2024 14:15:34 +0200 Subject: [PATCH 873/948] feat(tests): Temporarly disabling send reports to Xray (#1504) Refs: MON-141601 --- .github/workflows/robot-test.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/robot-test.yml b/.github/workflows/robot-test.yml index 65ba4a39524..75859ad65fc 100644 --- a/.github/workflows/robot-test.yml +++ b/.github/workflows/robot-test.yml @@ -157,9 +157,9 @@ jobs: echo "xray_token=$xray_token" >> $GITHUB_OUTPUT shell: bash - - name: Send report to xrays - run: | - curl -H "Content-Type: multipart/form-data" -X POST -F info=@tests/issueFields.json -F results=@tests/output.xml -F testInfo=@tests/testIssueFields.json -H "Authorization: Bearer ${{ steps.generate-xray-token.outputs.xray_token }}" https://xray.cloud.getxray.app/api/v2/import/execution/robot/multipart + # - name: Send report to xrays + # run: | + # curl -H "Content-Type: multipart/form-data" -X POST -F info=@tests/issueFields.json -F results=@tests/output.xml -F testInfo=@tests/testIssueFields.json -H "Authorization: Bearer ${{ steps.generate-xray-token.outputs.xray_token }}" https://xray.cloud.getxray.app/api/v2/import/execution/robot/multipart - name: Move reports if: ${{ failure() }} @@ -247,12 +247,12 @@ jobs: - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 if: ${{ inputs.distrib == 'el7'}} with: - python-version: '3.10' + python-version: "3.10" - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 if: ${{ inputs.distrib != 'el7' }} with: - python-version: '3.10' + python-version: "3.10" - run: | pip3 install -U robotframework robotframework-databaselibrary pymysql python-dateutil From 0458557c6821f647eeba31cb44e2e50b1a33d80e Mon Sep 17 00:00:00 2001 From: tuntoja <58987095+tuntoja@users.noreply.github.com> Date: Tue, 9 Jul 2024 11:36:17 +0200 Subject: [PATCH 874/948] enh(ci): exclude files from component workflow triggers (#1506) * enh(ci): exclude files from component workflow triggers * remove empty line * use more generic pattern for workflow path filtering --- .github/workflows/centreon-collect.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/centreon-collect.yml b/.github/workflows/centreon-collect.yml index 2e5954ffe17..87af57633dd 100644 --- a/.github/workflows/centreon-collect.yml +++ b/.github/workflows/centreon-collect.yml @@ -27,6 +27,7 @@ on: - vcpkg/** - "!.veracode-exclusions" - "!veracode.json" + - "!**/test/**" push: branches: - develop @@ -53,6 +54,7 @@ on: - vcpkg/** - "!.veracode-exclusions" - "!veracode.json" + - "!**/test/**" jobs: get-version: From dc52bbeff215b1a0dab3dc8cb3e9876b36e99741 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Delpierre?= <jdelpierre@users.noreply.github.com> Date: Tue, 9 Jul 2024 17:44:30 +0200 Subject: [PATCH 875/948] feat(test): disable step to generate Xray Token (#1513) --- .github/workflows/robot-test.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/robot-test.yml b/.github/workflows/robot-test.yml index 75859ad65fc..0381a515a6b 100644 --- a/.github/workflows/robot-test.yml +++ b/.github/workflows/robot-test.yml @@ -149,13 +149,13 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.collect_s3_access_key }} AWS_SECRET_ACCESS_KEY: ${{ secrets.collect_s3_secret_key }} - - name: Generate Xray Token - id: generate-xray-token - run: | - token_response=$(curl -H "Content-Type: application/json" -X POST --data "{\"client_id\": \"${{ secrets.XRAY_CLIENT_ID }}\", \"client_secret\": \"${{ secrets.XRAY_CLIENT_SECRET }}\"}" "https://xray.cloud.getxray.app/api/v1/authenticate") - xray_token=$(echo "$token_response" | sed -n 's/.*"\(.*\)".*/\1/p') - echo "xray_token=$xray_token" >> $GITHUB_OUTPUT - shell: bash + # - name: Generate Xray Token + # id: generate-xray-token + # run: | + # token_response=$(curl -H "Content-Type: application/json" -X POST --data "{\"client_id\": \"${{ secrets.XRAY_CLIENT_ID }}\", \"client_secret\": \"${{ secrets.XRAY_CLIENT_SECRET }}\"}" "https://xray.cloud.getxray.app/api/v1/authenticate") + # xray_token=$(echo "$token_response" | sed -n 's/.*"\(.*\)".*/\1/p') + # echo "xray_token=$xray_token" >> $GITHUB_OUTPUT + # shell: bash # - name: Send report to xrays # run: | From 30b2f85910b0e0aa781d5f07160751a1ce57c1c4 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Wed, 10 Jul 2024 15:45:03 +0200 Subject: [PATCH 876/948] fix(gorgone): update regexp for plugins (#1519) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Stéphane Duret <sduret@centreon.com> Refs: MON-133892 --- gorgone/packaging/configuration/whitelist.conf.d/centreon.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gorgone/packaging/configuration/whitelist.conf.d/centreon.yaml b/gorgone/packaging/configuration/whitelist.conf.d/centreon.yaml index 5a7e2cca4e8..d1313d9ed2a 100644 --- a/gorgone/packaging/configuration/whitelist.conf.d/centreon.yaml +++ b/gorgone/packaging/configuration/whitelist.conf.d/centreon.yaml @@ -4,7 +4,7 @@ - ^(sudo\s+)?(/usr/bin/)?service\s+(centengine|centreontrapd|cbd|cbd-sql)\s+(reload|restart)\s*$ - ^/usr/sbin/centenginestats\s+-c\s+/etc/centreon-engine/+centengine\.cfg\s*$ - ^cat\s+/var/lib/centreon-engine/+[a-zA-Z0-9\-]+-stats\.json\s*$ -- ^/usr/lib/centreon/plugins/.*$ +- ^(sudo\s+)?/usr/lib/centreon/plugins/.*$ - ^/bin/perl /usr/share/centreon/bin/anomaly_detection --seasonality >> /var/log/centreon/anomaly_detection\.log 2>&1\s*$ - ^/usr/bin/php -q /usr/share/centreon/cron/centreon-helios\.php >> /var/log/centreon-helios\.log 2>&1\s*$ - ^centreon From 3b5b91261419f3ab94c20a3f5c3bdab6568daa8e Mon Sep 17 00:00:00 2001 From: jean-christophe81 <98889244+jean-christophe81@users.noreply.github.com> Date: Wed, 10 Jul 2024 16:18:08 +0200 Subject: [PATCH 877/948] add windows-agent.yaml (#1525) --- .github/workflows/windows-agent.yaml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/windows-agent.yaml diff --git a/.github/workflows/windows-agent.yaml b/.github/workflows/windows-agent.yaml new file mode 100644 index 00000000000..c1bbf3bb7e4 --- /dev/null +++ b/.github/workflows/windows-agent.yaml @@ -0,0 +1,21 @@ +name: Centreon Monitoring Agent Windows packaging + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +on: + workflow_dispatch: + +jobs: + build: + runs-on: windows-latest + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: ilammy/msvc-dev-cmd@v1.4.1 + + - name: install vcpkg + run: | + git clone --depth 1 https://github.com/microsoft/vcpkg $HOME/vcpkg + cd $HOME/vcpkg + bootstrap-vcpkg.bat From c82befad0b68934d3edd76d4cbe8b1707c39dd41 Mon Sep 17 00:00:00 2001 From: jean-christophe81 <98889244+jean-christophe81@users.noreply.github.com> Date: Wed, 10 Jul 2024 16:27:37 +0200 Subject: [PATCH 878/948] Mon 137834 cma agent workflow bis (#1527) * add windonws-agent.yaml * fix .github/workflows/windows-agent extension --- .github/workflows/windows-agent.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/windows-agent.yml diff --git a/.github/workflows/windows-agent.yml b/.github/workflows/windows-agent.yml new file mode 100644 index 00000000000..c1bbf3bb7e4 --- /dev/null +++ b/.github/workflows/windows-agent.yml @@ -0,0 +1,21 @@ +name: Centreon Monitoring Agent Windows packaging + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +on: + workflow_dispatch: + +jobs: + build: + runs-on: windows-latest + steps: + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: ilammy/msvc-dev-cmd@v1.4.1 + + - name: install vcpkg + run: | + git clone --depth 1 https://github.com/microsoft/vcpkg $HOME/vcpkg + cd $HOME/vcpkg + bootstrap-vcpkg.bat From 2ebada1333a9de4a14fb568efe63159a4d4e906e Mon Sep 17 00:00:00 2001 From: jean-christophe81 <98889244+jean-christophe81@users.noreply.github.com> Date: Mon, 15 Jul 2024 06:55:55 +0200 Subject: [PATCH 879/948] MON-63843-agent-linux-streaming (#1505) * MON-63843-agent-linux-streaming * fix muxer issue REFS:MON-63843 --- .github/workflows/windows-agent.yaml | 21 - agent/CMakeLists.txt | 7 +- agent/inc/com/centreon/agent/bireactor.hh | 88 ++++ .../com/centreon/agent/streaming_client.hh | 113 +++++ .../com/centreon/agent/streaming_server.hh | 77 ++++ agent/src/bireactor.cc | 207 +++++++++ agent/src/main.cc | 27 +- agent/src/streaming_client.cc | 230 ++++++++++ agent/src/streaming_server.cc | 237 +++++++++++ agent/test/scheduler_test.cc | 4 +- .../com/centreon/broker/processing/feeder.hh | 9 +- .../com/centreon/broker/multiplexing/muxer.hh | 15 +- broker/core/multiplexing/src/muxer.cc | 47 +- broker/core/src/processing/feeder.cc | 27 +- engine/tests/CMakeLists.txt | 7 +- .../opentelemetry/agent_to_engine_test.cc | 327 ++++++++++++++ packaging/centreon-monitoring-agent.yaml | 10 + tests/broker-engine/opentelemetry.robot | 402 ++++++++++++++++++ tests/resources/Agent.py | 71 ++++ tests/resources/resources.resource | 34 ++ 20 files changed, 1889 insertions(+), 71 deletions(-) delete mode 100644 .github/workflows/windows-agent.yaml create mode 100644 agent/inc/com/centreon/agent/bireactor.hh create mode 100644 agent/inc/com/centreon/agent/streaming_client.hh create mode 100644 agent/inc/com/centreon/agent/streaming_server.hh create mode 100644 agent/src/bireactor.cc create mode 100644 agent/src/streaming_client.cc create mode 100644 agent/src/streaming_server.cc create mode 100644 engine/tests/opentelemetry/agent_to_engine_test.cc create mode 100644 tests/resources/Agent.py diff --git a/.github/workflows/windows-agent.yaml b/.github/workflows/windows-agent.yaml deleted file mode 100644 index c1bbf3bb7e4..00000000000 --- a/.github/workflows/windows-agent.yaml +++ /dev/null @@ -1,21 +0,0 @@ -name: Centreon Monitoring Agent Windows packaging - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -on: - workflow_dispatch: - -jobs: - build: - runs-on: windows-latest - steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - uses: ilammy/msvc-dev-cmd@v1.4.1 - - - name: install vcpkg - run: | - git clone --depth 1 https://github.com/microsoft/vcpkg $HOME/vcpkg - cd $HOME/vcpkg - bootstrap-vcpkg.bat diff --git a/agent/CMakeLists.txt b/agent/CMakeLists.txt index 55293a28bf0..c64801646c4 100644 --- a/agent/CMakeLists.txt +++ b/agent/CMakeLists.txt @@ -101,6 +101,7 @@ add_custom_command( add_library(centagent_lib STATIC ${SRC_DIR}/agent.grpc.pb.cc ${SRC_DIR}/agent.pb.cc + ${SRC_DIR}/bireactor.cc ${SRC_DIR}/check.cc ${SRC_DIR}/check_exec.cc ${SRC_DIR}/opentelemetry/proto/collector/metrics/v1/metrics_service.grpc.pb.cc @@ -110,6 +111,8 @@ add_library(centagent_lib STATIC ${SRC_DIR}/opentelemetry/proto/resource/v1/resource.pb.cc ${SRC_DIR}/config.cc ${SRC_DIR}/scheduler.cc + ${SRC_DIR}/streaming_client.cc + ${SRC_DIR}/streaming_server.cc ) include_directories( @@ -136,7 +139,9 @@ target_link_libraries( centreon_process -L${Boost_LIBRARY_DIR_RELEASE} boost_program_options - fmt::fmt) + fmt::fmt + stdc++fs + ) target_precompile_headers(${CENTREON_AGENT} REUSE_FROM centagent_lib) diff --git a/agent/inc/com/centreon/agent/bireactor.hh b/agent/inc/com/centreon/agent/bireactor.hh new file mode 100644 index 00000000000..16af5594c81 --- /dev/null +++ b/agent/inc/com/centreon/agent/bireactor.hh @@ -0,0 +1,88 @@ +/** + * Copyright 2024 Centreon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + */ + +#ifndef CENTREON_AGENT_BIREACTOR_HH +#define CENTREON_AGENT_BIREACTOR_HH + +#include "agent.grpc.pb.h" + +namespace com::centreon::agent { + +template <class bireactor_class> +class bireactor + : public bireactor_class, + public std::enable_shared_from_this<bireactor<bireactor_class>> { + private: + static std::set<std::shared_ptr<bireactor>> _instances; + static std::mutex _instances_m; + + bool _write_pending; + std::deque<std::shared_ptr<MessageFromAgent>> _write_queue; + std::shared_ptr<MessageToAgent> _read_current; + + const std::string_view _class_name; + + const std::string _peer; + + protected: + std::shared_ptr<boost::asio::io_context> _io_context; + std::shared_ptr<spdlog::logger> _logger; + + bool _alive; + /** + * @brief All attributes of this object are protected by this mutex + * + */ + mutable std::mutex _protect; + + public: + bireactor(const std::shared_ptr<boost::asio::io_context>& io_context, + const std::shared_ptr<spdlog::logger>& logger, + const std::string_view& class_name, + const std::string& peer); + + virtual ~bireactor(); + + static void register_stream(const std::shared_ptr<bireactor>& strm); + + void start_read(); + + void start_write(); + void write(const std::shared_ptr<MessageFromAgent>& request); + + // bireactor part + void OnReadDone(bool ok) override; + + virtual void on_incomming_request( + const std::shared_ptr<MessageToAgent>& request) = 0; + + virtual void on_error() = 0; + + void OnWriteDone(bool ok) override; + + // server version + void OnDone(); + // client version + void OnDone(const ::grpc::Status& /*s*/); + + virtual void shutdown(); +}; + +} // namespace com::centreon::agent + +#endif diff --git a/agent/inc/com/centreon/agent/streaming_client.hh b/agent/inc/com/centreon/agent/streaming_client.hh new file mode 100644 index 00000000000..17fe24ef07b --- /dev/null +++ b/agent/inc/com/centreon/agent/streaming_client.hh @@ -0,0 +1,113 @@ +/** + * Copyright 2024 Centreon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + */ + +#ifndef CENTREON_AGENT_STREAMING_CLIENT_HH +#define CENTREON_AGENT_STREAMING_CLIENT_HH + +#include "com/centreon/common/grpc/grpc_client.hh" + +#include "bireactor.hh" +#include "scheduler.hh" + +namespace com::centreon::agent { + +class streaming_client; + +class client_reactor + : public bireactor< + ::grpc::ClientBidiReactor<MessageFromAgent, MessageToAgent>> { + std::weak_ptr<streaming_client> _parent; + ::grpc::ClientContext _context; + + public: + client_reactor(const std::shared_ptr<boost::asio::io_context>& io_context, + const std::shared_ptr<spdlog::logger>& logger, + const std::shared_ptr<streaming_client>& parent, + const std::string& peer); + + std::shared_ptr<client_reactor> shared_from_this() { + return std::static_pointer_cast<client_reactor>( + bireactor<::grpc::ClientBidiReactor<MessageFromAgent, MessageToAgent>>:: + shared_from_this()); + } + + ::grpc::ClientContext& get_context() { return _context; } + + void on_incomming_request( + const std::shared_ptr<MessageToAgent>& request) override; + + void on_error() override; + + void shutdown() override; +}; + +/** + * @brief this object not only manages connection to engine, but also embed + * check scheduler + * + */ +class streaming_client : public common::grpc::grpc_client_base, + public std::enable_shared_from_this<streaming_client> { + std::shared_ptr<boost::asio::io_context> _io_context; + std::shared_ptr<spdlog::logger> _logger; + std::string _supervised_host; + + std::unique_ptr<AgentService::Stub> _stub; + + std::shared_ptr<client_reactor> _reactor; + std::shared_ptr<scheduler> _sched; + + /** + * @brief All attributes of this object are protected by this mutex + * + */ + std::mutex _protect; + + void _create_reactor(); + + void _start(); + + void _send(const std::shared_ptr<MessageFromAgent>& request); + + public: + streaming_client(const std::shared_ptr<boost::asio::io_context>& io_context, + const std::shared_ptr<spdlog::logger>& logger, + const std::shared_ptr<common::grpc::grpc_config>& conf, + const std::string& supervised_host); + + static std::shared_ptr<streaming_client> load( + const std::shared_ptr<boost::asio::io_context>& io_context, + const std::shared_ptr<spdlog::logger>& logger, + const std::shared_ptr<common::grpc::grpc_config>& conf, + const std::string& supervised_host); + + void on_incomming_request(const std::shared_ptr<client_reactor>& caller, + const std::shared_ptr<MessageToAgent>& request); + void on_error(const std::shared_ptr<client_reactor>& caller); + + void shutdown(); + + // use only for tests + engine_to_agent_request_ptr get_last_message_to_agent() const { + return _sched->get_last_message_to_agent(); + } +}; + +} // namespace com::centreon::agent + +#endif \ No newline at end of file diff --git a/agent/inc/com/centreon/agent/streaming_server.hh b/agent/inc/com/centreon/agent/streaming_server.hh new file mode 100644 index 00000000000..b88a1cb0c3f --- /dev/null +++ b/agent/inc/com/centreon/agent/streaming_server.hh @@ -0,0 +1,77 @@ +/** + * Copyright 2024 Centreon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + */ + +#ifndef CENTREON_AGENT_STREAMING_SERVER_HH +#define CENTREON_AGENT_STREAMING_SERVER_HH + +#include "com/centreon/common/grpc/grpc_server.hh" + +#include "bireactor.hh" +#include "scheduler.hh" + +namespace com::centreon::agent { + +class server_reactor; + +/** + * @brief grpc engine to agent server (reverse connection) + * It accept only one connection at a time + * If another connection occurs, previous connection is shutdown + * This object is both grpc server and grpc service + */ +class streaming_server : public common::grpc::grpc_server_base, + public std::enable_shared_from_this<streaming_server>, + public ReversedAgentService::Service { + std::shared_ptr<boost::asio::io_context> _io_context; + std::shared_ptr<spdlog::logger> _logger; + const std::string _supervised_host; + + /** active engine to agent connection*/ + std::shared_ptr<server_reactor> _incoming; + + /** + * @brief All attributes of this object are protected by this mutex + * + */ + mutable std::mutex _protect; + + void _start(); + + public: + streaming_server(const std::shared_ptr<boost::asio::io_context>& io_context, + const std::shared_ptr<spdlog::logger>& logger, + const std::shared_ptr<common::grpc::grpc_config>& conf, + const std::string& supervised_host); + + ~streaming_server(); + + static std::shared_ptr<streaming_server> load( + const std::shared_ptr<boost::asio::io_context>& io_context, + const std::shared_ptr<spdlog::logger>& logger, + const std::shared_ptr<common::grpc::grpc_config>& conf, + const std::string& supervised_host); + + ::grpc::ServerBidiReactor<MessageToAgent, MessageFromAgent>* Import( + ::grpc::CallbackServerContext* context); + + void shutdown(); +}; + +} // namespace com::centreon::agent + +#endif diff --git a/agent/src/bireactor.cc b/agent/src/bireactor.cc new file mode 100644 index 00000000000..e26346be55c --- /dev/null +++ b/agent/src/bireactor.cc @@ -0,0 +1,207 @@ +/** + * Copyright 2024 Centreon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + */ + +#include "bireactor.hh" + +using namespace com::centreon::agent; + +/** + * @brief when BiReactor::OnDone is called by grpc layers, we should delete + * this. But this object is even used by others. + * So it's stored in this container and just removed from this container when + * OnDone is called + * + * @tparam bireactor_class + */ +template <class bireactor_class> +std::set<std::shared_ptr<bireactor<bireactor_class>>> + bireactor<bireactor_class>::_instances; + +template <class bireactor_class> +std::mutex bireactor<bireactor_class>::_instances_m; + +template <class bireactor_class> +bireactor<bireactor_class>::bireactor( + const std::shared_ptr<boost::asio::io_context>& io_context, + const std::shared_ptr<spdlog::logger>& logger, + const std::string_view& class_name, + const std::string& peer) + : _write_pending(false), + _alive(true), + _class_name(class_name), + _peer(peer), + _io_context(io_context), + _logger(logger) { + SPDLOG_LOGGER_DEBUG(_logger, "create {} this={:p} peer:{}", _class_name, + static_cast<const void*>(this), _peer); +} + +template <class bireactor_class> +bireactor<bireactor_class>::~bireactor() { + SPDLOG_LOGGER_DEBUG(_logger, "delete {} this={:p} peer:{}", _class_name, + static_cast<const void*>(this), _peer); +} + +template <class bireactor_class> +void bireactor<bireactor_class>::register_stream( + const std::shared_ptr<bireactor>& strm) { + std::lock_guard l(_instances_m); + _instances.insert(strm); +} + +template <class bireactor_class> +void bireactor<bireactor_class>::start_read() { + std::lock_guard l(_protect); + if (!_alive) { + return; + } + std::shared_ptr<MessageToAgent> to_read; + if (_read_current) { + return; + } + to_read = _read_current = std::make_shared<MessageToAgent>(); + bireactor_class::StartRead(to_read.get()); +} + +template <class bireactor_class> +void bireactor<bireactor_class>::OnReadDone(bool ok) { + if (ok) { + std::shared_ptr<MessageToAgent> read; + { + std::lock_guard l(_protect); + SPDLOG_LOGGER_TRACE(_logger, "{:p} {} peer {} receive: {}", + static_cast<const void*>(this), _class_name, _peer, + _read_current->ShortDebugString()); + read = _read_current; + _read_current.reset(); + } + start_read(); + if (read->has_config()) { + on_incomming_request(read); + } + } else { + SPDLOG_LOGGER_ERROR(_logger, "{:p} {} peer:{} fail read from stream", + static_cast<void*>(this), _class_name, _peer); + on_error(); + shutdown(); + } +} + +template <class bireactor_class> +void bireactor<bireactor_class>::write( + const std::shared_ptr<MessageFromAgent>& request) { + { + std::lock_guard l(_protect); + if (!_alive) { + return; + } + _write_queue.push_back(request); + } + start_write(); +} + +template <class bireactor_class> +void bireactor<bireactor_class>::start_write() { + std::shared_ptr<MessageFromAgent> to_send; + { + std::lock_guard l(_protect); + if (!_alive || _write_pending || _write_queue.empty()) { + return; + } + to_send = _write_queue.front(); + _write_pending = true; + } + bireactor_class::StartWrite(to_send.get()); +} + +template <class bireactor_class> +void bireactor<bireactor_class>::OnWriteDone(bool ok) { + if (ok) { + { + std::lock_guard l(_protect); + _write_pending = false; + SPDLOG_LOGGER_TRACE(_logger, "{:p} {} {} sent", + static_cast<const void*>(this), _class_name, + (*_write_queue.begin())->ShortDebugString()); + _write_queue.pop_front(); + } + start_write(); + } else { + SPDLOG_LOGGER_ERROR(_logger, "{:p} {} peer {} fail write to stream", + static_cast<void*>(this), _class_name, _peer); + on_error(); + shutdown(); + } +} + +template <class bireactor_class> +void bireactor<bireactor_class>::OnDone() { + /**grpc has a bug, sometimes if we delete this class in this handler as it is + * described in examples, it also deletes used channel and does a pthread_join + * of the current thread witch go to a EDEADLOCK error and call grpc::Crash. + * So we uses asio thread to do the job + */ + _io_context->post([me = std::enable_shared_from_this< + bireactor<bireactor_class>>::shared_from_this(), + &peer = _peer, logger = _logger]() { + std::lock_guard l(_instances_m); + SPDLOG_LOGGER_DEBUG(logger, "{:p} server::OnDone() to {}", + static_cast<void*>(me.get()), peer); + _instances.erase(std::static_pointer_cast<bireactor<bireactor_class>>(me)); + }); +} + +template <class bireactor_class> +void bireactor<bireactor_class>::OnDone(const ::grpc::Status& status) { + /**grpc has a bug, sometimes if we delete this class in this handler as it is + * described in examples, it also deletes used channel and does a + * pthread_join of the current thread witch go to a EDEADLOCK error and call + * grpc::Crash. So we uses asio thread to do the job + */ + _io_context->post([me = std::enable_shared_from_this< + bireactor<bireactor_class>>::shared_from_this(), + status, &peer = _peer, logger = _logger]() { + std::lock_guard l(_instances_m); + if (status.ok()) { + SPDLOG_LOGGER_DEBUG(logger, "{:p} peer: {} client::OnDone({}) {}", + static_cast<void*>(me.get()), peer, + status.error_message(), status.error_details()); + } else { + SPDLOG_LOGGER_ERROR(logger, "{:p} peer:{} client::OnDone({}) {}", + static_cast<void*>(me.get()), peer, + status.error_message(), status.error_details()); + } + _instances.erase(std::static_pointer_cast<bireactor<bireactor_class>>(me)); + }); +} + +template <class bireactor_class> +void bireactor<bireactor_class>::shutdown() { + SPDLOG_LOGGER_DEBUG(_logger, "{:p} {}::shutdown", static_cast<void*>(this), + _class_name); +} + +namespace com::centreon::agent { + +template class bireactor< + ::grpc::ClientBidiReactor<MessageFromAgent, MessageToAgent>>; + +template class bireactor< + ::grpc::ServerBidiReactor<MessageToAgent, MessageFromAgent>>; + +} // namespace com::centreon::agent \ No newline at end of file diff --git a/agent/src/main.cc b/agent/src/main.cc index 562a1f05e46..e613d749d2f 100644 --- a/agent/src/main.cc +++ b/agent/src/main.cc @@ -21,6 +21,8 @@ #include <spdlog/sinks/stdout_color_sinks.h> #include "config.hh" +#include "streaming_client.hh" +#include "streaming_server.hh" using namespace com::centreon::agent; @@ -28,6 +30,9 @@ std::shared_ptr<asio::io_context> g_io_context = std::make_shared<asio::io_context>(); std::shared_ptr<spdlog::logger> g_logger; +static std::shared_ptr<streaming_client> _streaming_client; + +static std::shared_ptr<streaming_server> _streaming_server; static asio::signal_set _signals(*g_io_context, SIGTERM, SIGUSR1, SIGUSR2); @@ -36,9 +41,16 @@ static void signal_handler(const boost::system::error_code& error, if (!error) { switch (signal_number) { case SIGTERM: - SPDLOG_LOGGER_INFO(g_logger, "SIGTERM received"); - g_io_context->stop(); - break; + case SIGINT: + SPDLOG_LOGGER_INFO(g_logger, "SIGTERM or SIGINT received"); + if (_streaming_client) { + _streaming_client->shutdown(); + } + if (_streaming_server) { + _streaming_server->shutdown(); + } + g_io_context->post([]() { g_io_context->stop(); }); + return; case SIGUSR2: SPDLOG_LOGGER_INFO(g_logger, "SIGUSR2 received"); if (g_logger->level()) { @@ -151,6 +163,7 @@ int main(int argc, char* argv[]) { try { // ignored but mandatory because of forks _signals.add(SIGPIPE); + _signals.add(SIGINT); _signals.async_wait(signal_handler); @@ -166,6 +179,14 @@ int main(int argc, char* argv[]) { return -1; } + if (conf->use_reverse_connection()) { + _streaming_server = streaming_server::load(g_io_context, g_logger, + grpc_conf, conf->get_host()); + } else { + _streaming_client = streaming_client::load(g_io_context, g_logger, + grpc_conf, conf->get_host()); + } + try { g_io_context->run(); } catch (const std::exception& e) { diff --git a/agent/src/streaming_client.cc b/agent/src/streaming_client.cc new file mode 100644 index 00000000000..5fa122c83cd --- /dev/null +++ b/agent/src/streaming_client.cc @@ -0,0 +1,230 @@ +/** + * Copyright 2024 Centreon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + */ + +#include "streaming_client.hh" +#include "check_exec.hh" +#include "com/centreon/clib/version.hh" +#include "com/centreon/common/defer.hh" + +using namespace com::centreon::agent; + +/** + * @brief Construct a new client reactor::client reactor object + * + * @param io_context + * @param parent we will keep a weak_ptr on streaming_client object + */ +client_reactor::client_reactor( + const std::shared_ptr<boost::asio::io_context>& io_context, + const std::shared_ptr<spdlog::logger>& logger, + + const std::shared_ptr<streaming_client>& parent, + const std::string& peer) + : bireactor<::grpc::ClientBidiReactor<MessageFromAgent, MessageToAgent>>( + io_context, + logger, + "client", + peer), + _parent(parent) {} + +/** + * @brief pass request to streaming_client parent + * + * @param request + */ +void client_reactor::on_incomming_request( + const std::shared_ptr<MessageToAgent>& request) { + std::shared_ptr<streaming_client> parent = _parent.lock(); + if (!parent) { + shutdown(); + } else { + parent->on_incomming_request(shared_from_this(), request); + } +} + +/** + * @brief called whe OnReadDone or OnWriteDone ok parameter is false + * + */ +void client_reactor::on_error() { + std::shared_ptr<streaming_client> parent = _parent.lock(); + if (parent) { + parent->on_error(shared_from_this()); + } +} + +/** + * @brief shutdown connection to engine if not yet done + * + */ +void client_reactor::shutdown() { + std::lock_guard l(_protect); + if (_alive) { + _alive = false; + bireactor<::grpc::ClientBidiReactor<MessageFromAgent, + MessageToAgent>>::shutdown(); + RemoveHold(); + _context.TryCancel(); + } +} + +/** + * @brief Construct a new streaming client::streaming client object + * not use it, use load instead + * + * @param io_context + * @param conf + * @param supervised_hosts + */ +streaming_client::streaming_client( + const std::shared_ptr<boost::asio::io_context>& io_context, + const std::shared_ptr<spdlog::logger>& logger, + const std::shared_ptr<common::grpc::grpc_config>& conf, + const std::string& supervised_host) + : com::centreon::common::grpc::grpc_client_base(conf, logger), + _io_context(io_context), + _logger(logger), + _supervised_host(supervised_host) { + _stub = std::move(AgentService::NewStub(_channel)); +} + +/** + * @brief to call after construction + * + */ +void streaming_client::_start() { + std::weak_ptr<streaming_client> weak_this = shared_from_this(); + + _sched = scheduler::load( + _io_context, _logger, _supervised_host, scheduler::default_config(), + [sender = std::move(weak_this)]( + const std::shared_ptr<MessageFromAgent>& request) { + auto parent = sender.lock(); + if (parent) { + parent->_send(request); + } + }, + check_exec::load); + _create_reactor(); +} + +/** + * @brief create reactor on current grpc channel + * and send agent infos (hostname, supervised hosts, collect version) + * + */ +void streaming_client::_create_reactor() { + std::lock_guard l(_protect); + if (_reactor) { + _reactor->shutdown(); + } + _reactor = std::make_shared<client_reactor>( + _io_context, _logger, shared_from_this(), get_conf()->get_hostport()); + client_reactor::register_stream(_reactor); + _stub->async()->Export(&_reactor->get_context(), _reactor.get()); + _reactor->start_read(); + _reactor->AddHold(); + _reactor->StartCall(); + + // identifies to engine + std::shared_ptr<MessageFromAgent> who_i_am = + std::make_shared<MessageFromAgent>(); + auto infos = who_i_am->mutable_init(); + + infos->mutable_centreon_version()->set_major( + com::centreon::clib::version::major); + infos->mutable_centreon_version()->set_minor( + com::centreon::clib::version::minor); + infos->mutable_centreon_version()->set_patch( + com::centreon::clib::version::patch); + + infos->set_host(_supervised_host); + + _reactor->write(who_i_am); +} + +/** + * @brief construct a new streaming_client + * + * @param io_context + * @param conf + * @param supervised_hosts list of host to supervise (match to engine config) + * @return std::shared_ptr<streaming_client> + */ +std::shared_ptr<streaming_client> streaming_client::load( + const std::shared_ptr<boost::asio::io_context>& io_context, + const std::shared_ptr<spdlog::logger>& logger, + const std::shared_ptr<common::grpc::grpc_config>& conf, + const std::string& supervised_host) { + std::shared_ptr<streaming_client> ret = std::make_shared<streaming_client>( + io_context, logger, conf, supervised_host); + ret->_start(); + return ret; +} + +/** + * @brief send a request to engine + * + * @param request + */ +void streaming_client::_send(const std::shared_ptr<MessageFromAgent>& request) { + std::lock_guard l(_protect); + if (_reactor) + _reactor->write(request); +} + +/** + * @brief + * + * @param caller + * @param request + */ +void streaming_client::on_incomming_request( + const std::shared_ptr<client_reactor>& caller, + const std::shared_ptr<MessageToAgent>& request) { + // incoming request is used in main thread + _io_context->post([request, sched = _sched]() { sched->update(request); }); +} + +/** + * @brief called by _reactor when something was wrong + * Then we wait 10s to reconnect to engine + * + * @param caller + */ +void streaming_client::on_error(const std::shared_ptr<client_reactor>& caller) { + std::lock_guard l(_protect); + if (caller == _reactor) { + _reactor.reset(); + common::defer(_io_context, std::chrono::seconds(10), + [me = shared_from_this()] { me->_create_reactor(); }); + } +} + +/** + * @brief stop and shutdown scheduler and connection + * After, this object is dead and must be deleted + * + */ +void streaming_client::shutdown() { + std::lock_guard l(_protect); + _sched->stop(); + if (_reactor) { + _reactor->shutdown(); + } +} diff --git a/agent/src/streaming_server.cc b/agent/src/streaming_server.cc new file mode 100644 index 00000000000..cfc23fabb11 --- /dev/null +++ b/agent/src/streaming_server.cc @@ -0,0 +1,237 @@ +/** + * Copyright 2024 Centreon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + */ + +#include "streaming_server.hh" +#include "check_exec.hh" +#include "com/centreon/clib/version.hh" +#include "scheduler.hh" + +using namespace com::centreon::agent; + +namespace com::centreon::agent { + +class server_reactor + : public bireactor< + ::grpc::ServerBidiReactor<MessageToAgent, MessageFromAgent>> { + std::shared_ptr<scheduler> _sched; + std::string _supervised_host; + + void _start(); + + public: + server_reactor(const std::shared_ptr<boost::asio::io_context>& io_context, + const std::shared_ptr<spdlog::logger>& logger, + const std::string& supervised_hosts, + const std::string& peer); + + static std::shared_ptr<server_reactor> load( + const std::shared_ptr<boost::asio::io_context>& io_context, + const std::shared_ptr<spdlog::logger>& logger, + const std::string& supervised_hosts, + const std::string& peer); + + std::shared_ptr<server_reactor> shared_from_this() { + return std::static_pointer_cast<server_reactor>( + bireactor<::grpc::ServerBidiReactor<MessageToAgent, MessageFromAgent>>:: + shared_from_this()); + } + + void on_incomming_request( + const std::shared_ptr<MessageToAgent>& request) override; + + void on_error() override; + + void shutdown() override; +}; + +server_reactor::server_reactor( + const std::shared_ptr<boost::asio::io_context>& io_context, + const std::shared_ptr<spdlog::logger>& logger, + const std::string& supervised_host, + const std::string& peer) + : bireactor<::grpc::ServerBidiReactor<MessageToAgent, MessageFromAgent>>( + io_context, + logger, + "server", + peer), + _supervised_host(supervised_host) {} + +void server_reactor::_start() { + std::weak_ptr<server_reactor> weak_this(shared_from_this()); + + _sched = scheduler::load( + _io_context, _logger, _supervised_host, scheduler::default_config(), + [sender = std::move(weak_this)]( + const std::shared_ptr<MessageFromAgent>& request) { + auto parent = sender.lock(); + if (parent) { + parent->write(request); + } + }, + check_exec::load); + + // identifies to engine + std::shared_ptr<MessageFromAgent> who_i_am = + std::make_shared<MessageFromAgent>(); + auto infos = who_i_am->mutable_init(); + + infos->mutable_centreon_version()->set_major( + com::centreon::clib::version::major); + infos->mutable_centreon_version()->set_minor( + com::centreon::clib::version::minor); + infos->mutable_centreon_version()->set_patch( + com::centreon::clib::version::patch); + infos->set_host(_supervised_host); + + write(who_i_am); +} + +std::shared_ptr<server_reactor> server_reactor::load( + const std::shared_ptr<boost::asio::io_context>& io_context, + const std::shared_ptr<spdlog::logger>& logger, + const std::string& supervised_host, + const std::string& peer) { + std::shared_ptr<server_reactor> ret = std::make_shared<server_reactor>( + io_context, logger, supervised_host, peer); + ret->_start(); + return ret; +} + +void server_reactor::on_incomming_request( + const std::shared_ptr<MessageToAgent>& request) { + _io_context->post([sched = _sched, request]() { sched->update(request); }); +} + +void server_reactor::on_error() { + shutdown(); +} + +void server_reactor::shutdown() { + std::lock_guard l(_protect); + if (_alive) { + _alive = false; + _sched->stop(); + bireactor<::grpc::ServerBidiReactor<MessageToAgent, + MessageFromAgent>>::shutdown(); + Finish(::grpc::Status::CANCELLED); + } +} + +} // namespace com::centreon::agent + +/** + * @brief Construct a new streaming server::streaming server object + * Not use it, use load instead + * @param io_context + * @param conf + * @param supervised_hosts list of supervised hosts that will be sent to engine + * in order to have checks configuration + */ +streaming_server::streaming_server( + const std::shared_ptr<boost::asio::io_context>& io_context, + const std::shared_ptr<spdlog::logger>& logger, + const std::shared_ptr<common::grpc::grpc_config>& conf, + const std::string& supervised_host) + : com::centreon::common::grpc::grpc_server_base(conf, logger), + _io_context(io_context), + _logger(logger), + _supervised_host(supervised_host) { + SPDLOG_LOGGER_INFO(_logger, "create grpc server listening on {}", + conf->get_hostport()); +} + +streaming_server::~streaming_server() { + SPDLOG_LOGGER_INFO(_logger, "delete grpc server listening on {}", + get_conf()->get_hostport()); +} + +/** + * @brief register service and start grpc server + * + */ +void streaming_server::_start() { + ::grpc::Service::MarkMethodCallback( + 0, new ::grpc::internal::CallbackBidiHandler< + ::com::centreon::agent::MessageToAgent, + ::com::centreon::agent::MessageFromAgent>( + [me = shared_from_this()](::grpc::CallbackServerContext* context) { + return me->Import(context); + })); + + _init([this](::grpc::ServerBuilder& builder) { + builder.RegisterService(this); + }); +} + +/** + * @brief construct and start a new streaming_server + * + * @param io_context + * @param conf + * @param supervised_hosts list of supervised hosts that will be sent to engine + * in order to have checks configuration + * @return std::shared_ptr<streaming_server> + */ +std::shared_ptr<streaming_server> streaming_server::load( + const std::shared_ptr<boost::asio::io_context>& io_context, + const std::shared_ptr<spdlog::logger>& logger, + const std::shared_ptr<common::grpc::grpc_config>& conf, + const std::string& supervised_host) { + std::shared_ptr<streaming_server> ret = std::make_shared<streaming_server>( + io_context, logger, conf, supervised_host); + ret->_start(); + return ret; +} + +/** + * @brief shutdown server and incoming connection + * + */ +void streaming_server::shutdown() { + SPDLOG_LOGGER_INFO(_logger, "shutdown grpc server listening on {}", + get_conf()->get_hostport()); + { + std::lock_guard l(_protect); + if (_incoming) { + _incoming->shutdown(); + _incoming.reset(); + } + } + common::grpc::grpc_server_base::shutdown(std::chrono::seconds(10)); +} + +/** + * @brief callback called on incoming connection + * + * @param context + * @return ::grpc::ServerBidiReactor<MessageToAgent, MessageFromAgent>* = + * _incoming + */ +::grpc::ServerBidiReactor<MessageToAgent, MessageFromAgent>* +streaming_server::Import(::grpc::CallbackServerContext* context) { + SPDLOG_LOGGER_INFO(_logger, "incoming connection from {}", context->peer()); + std::lock_guard l(_protect); + if (_incoming) { + _incoming->shutdown(); + } + _incoming = server_reactor::load(_io_context, _logger, _supervised_host, + context->peer()); + server_reactor::register_stream(_incoming); + _incoming->start_read(); + return _incoming.get(); +} diff --git a/agent/test/scheduler_test.cc b/agent/test/scheduler_test.cc index c741f0c7aac..a7ac335382e 100644 --- a/agent/test/scheduler_test.cc +++ b/agent/test/scheduler_test.cc @@ -316,7 +316,7 @@ TEST_F(scheduler_test, correct_output_examplar) { ASSERT_TRUE(exported_request); - SPDLOG_INFO("export:{}", exported_request->otel_request().DebugString()); + SPDLOG_INFO("export:{}", exported_request->otel_request().ShortDebugString()); ASSERT_EQ(exported_request->otel_request().resource_metrics_size(), 2); const ::opentelemetry::proto::metrics::v1::ResourceMetrics& res = @@ -457,4 +457,4 @@ TEST_F(scheduler_test, max_concurent) { ASSERT_EQ(concurent_check::checked.size(), 200); sched->stop(); -} \ No newline at end of file +} diff --git a/broker/core/inc/com/centreon/broker/processing/feeder.hh b/broker/core/inc/com/centreon/broker/processing/feeder.hh index 71e6636b11c..4ccfcd90ea7 100644 --- a/broker/core/inc/com/centreon/broker/processing/feeder.hh +++ b/broker/core/inc/com/centreon/broker/processing/feeder.hh @@ -39,6 +39,7 @@ namespace processing { * Take events from a source and send them to a destination. */ class feeder : public stat_visitable, + public multiplexing::muxer::data_handler, public std::enable_shared_from_this<feeder> { enum class state : unsigned { running, finished }; // Condition variable used when waiting for the thread to finish @@ -63,6 +64,8 @@ class feeder : public stat_visitable, const multiplexing::muxer_filter& read_filters, const multiplexing::muxer_filter& write_filters); + void init(); + const std::string& _get_read_filters() const override; const std::string& _get_write_filters() const override; void _forward_statistic(nlohmann::json& tree) override; @@ -74,9 +77,6 @@ class feeder : public stat_visitable, void _start_read_from_stream_timer(); void _read_from_stream_timer_handler(const boost::system::error_code& err); - unsigned _write_to_client( - const std::vector<std::shared_ptr<io::data>>& events); - void _stop_no_lock(); void _ack_events_on_muxer(uint32_t count) noexcept; @@ -98,6 +98,9 @@ class feeder : public stat_visitable, bool is_finished() const noexcept; bool wait_for_all_events_written(unsigned ms_timeout); + + uint32_t on_events( + const std::vector<std::shared_ptr<io::data>>& events) override; }; } // namespace processing diff --git a/broker/core/multiplexing/inc/com/centreon/broker/multiplexing/muxer.hh b/broker/core/multiplexing/inc/com/centreon/broker/multiplexing/muxer.hh index bc7b6d959a2..82887bce2af 100644 --- a/broker/core/multiplexing/inc/com/centreon/broker/multiplexing/muxer.hh +++ b/broker/core/multiplexing/inc/com/centreon/broker/multiplexing/muxer.hh @@ -51,6 +51,14 @@ namespace com::centreon::broker::multiplexing { * @see engine */ class muxer : public io::stream, public std::enable_shared_from_this<muxer> { + public: + class data_handler { + public: + virtual ~data_handler() = default; + virtual uint32_t on_events( + const std::vector<std::shared_ptr<io::data>>& events) = 0; + }; + private: static uint32_t _event_queue_max_size; @@ -63,7 +71,7 @@ class muxer : public io::stream, public std::enable_shared_from_this<muxer> { std::string _write_filters_str; const bool _persistent; - std::function<uint32_t(const std::vector<std::shared_ptr<io::data>>&)> _data_handler; + std::shared_ptr<data_handler> _data_handler; std::atomic_bool _reader_running = false; /** Events are stacked into _events or into _file. Because several threads @@ -139,9 +147,8 @@ class muxer : public io::stream, public std::enable_shared_from_this<muxer> { void set_write_filter(const muxer_filter& w_filter); void clear_read_handler(); void unsubscribe(); - void set_action_on_new_data( - std::function<uint32_t(std::vector<std::shared_ptr<io::data>>)>&& - data_handler) ABSL_LOCKS_EXCLUDED(_events_m); + void set_action_on_new_data(const std::shared_ptr<data_handler>& handler) + ABSL_LOCKS_EXCLUDED(_events_m); void clear_action_on_new_data() ABSL_LOCKS_EXCLUDED(_events_m); }; diff --git a/broker/core/multiplexing/src/muxer.cc b/broker/core/multiplexing/src/muxer.cc index c81a955206e..2c3250ea32a 100644 --- a/broker/core/multiplexing/src/muxer.cc +++ b/broker/core/multiplexing/src/muxer.cc @@ -311,28 +311,34 @@ uint32_t muxer::event_queue_max_size() noexcept { * execute the data handler. */ void muxer::_execute_reader_if_needed() { - _logger->debug("muxer '{}' execute reader if needed data_handler: {}", _name, - static_cast<bool>(_data_handler)); - if (_data_handler) { - bool expected = false; - if (_reader_running.compare_exchange_strong(expected, true)) { - com::centreon::common::pool::io_context_ptr()->post( - [me = shared_from_this()] { + SPDLOG_LOGGER_DEBUG( + _logger, "muxer '{}' execute reader if needed data_handler", _name); + bool expected = false; + if (_reader_running.compare_exchange_strong(expected, true)) { + com::centreon::common::pool::io_context_ptr()->post( + [me = shared_from_this(), this] { + std::shared_ptr<data_handler> to_call; + { + absl::MutexLock lck(&_events_m); + to_call = _data_handler; + } + if (to_call) { std::vector<std::shared_ptr<io::data>> to_fill; - to_fill.reserve(me->_events_size); - bool still_events_to_read = me->read(to_fill, me->_events_size); - uint32_t written = me->_data_handler(to_fill); + to_fill.reserve(_events_size); + bool still_events_to_read = read(to_fill, _events_size); + uint32_t written = to_call->on_events(to_fill); if (written > 0) - me->ack_events(written); + ack_events(written); if (written != to_fill.size()) { - me->_logger->error( + SPDLOG_LOGGER_ERROR( + _logger, "Unable to handle all the incoming events in muxer '{}'", - me->_name); - me->clear_action_on_new_data(); + _name); + clear_action_on_new_data(); } - me->_reader_running.store(false); - }); - } + _reader_running.store(false); + } + }); } } @@ -784,13 +790,12 @@ void muxer::unsubscribe() { } void muxer::set_action_on_new_data( - std::function<uint32_t(std::vector<std::shared_ptr<io::data>>)>&& - data_handler) { + const std::shared_ptr<data_handler>& handler) { absl::MutexLock lck(&_events_m); - _data_handler = data_handler; + _data_handler = handler; } void muxer::clear_action_on_new_data() { absl::MutexLock lck(&_events_m); - _data_handler = nullptr; + _data_handler.reset(); } diff --git a/broker/core/src/processing/feeder.cc b/broker/core/src/processing/feeder.cc index a433cfcb232..a032eebcf40 100644 --- a/broker/core/src/processing/feeder.cc +++ b/broker/core/src/processing/feeder.cc @@ -56,12 +56,21 @@ std::shared_ptr<feeder> feeder::create( std::shared_ptr<feeder> ret( new feeder(name, parent, client, read_filters, write_filters)); - ret->_start_stat_timer(); - - ret->_start_read_from_stream_timer(); + ret->init(); return ret; } +/** + * @brief to call after object construction + * + */ +void feeder::init() { + _start_stat_timer(); + _muxer->set_action_on_new_data(shared_from_this()); + + _start_read_from_stream_timer(); +} + /** * Constructor. * @@ -91,10 +100,6 @@ feeder::feeder(const std::string& name, if (!_client) throw msg_fmt("could not process '{}' with no client stream", _name); - _muxer->set_action_on_new_data( - [this](const std::vector<std::shared_ptr<io::data>>& events) -> uint32_t { - return _write_to_client(events); - }); set_last_connection_attempt(timestamp::now()); set_last_connection_success(timestamp::now()); set_state("connected"); @@ -146,11 +151,10 @@ void feeder::_forward_statistic(nlohmann::json& tree) { /** * @brief write event to client stream - * _protect must be locked * @param event * @return number of events written */ -unsigned feeder::_write_to_client( +uint32_t feeder::on_events( const std::vector<std::shared_ptr<io::data>>& events) { unsigned written = 0; try { @@ -242,11 +246,6 @@ void feeder::_stop_no_lock() { _name); _muxer->remove_queue_files(); SPDLOG_LOGGER_INFO(_logger, "feeder: {} terminated", _name); - - /* The muxer is in a shared_ptr. When the feeder is destroyed, we must be - * sure the muxer won't write data anymore otherwise we will have a segfault. - */ - _muxer->clear_action_on_new_data(); } /** diff --git a/engine/tests/CMakeLists.txt b/engine/tests/CMakeLists.txt index 1dbadcf16ef..ff06101e3b6 100755 --- a/engine/tests/CMakeLists.txt +++ b/engine/tests/CMakeLists.txt @@ -113,6 +113,7 @@ if(WITH_TESTING) "${TESTS_DIR}/notifications/service_downtime_notification_test.cc" "${TESTS_DIR}/opentelemetry/agent_check_result_builder_test.cc" "${TESTS_DIR}/opentelemetry/agent_reverse_client_test.cc" + "${TESTS_DIR}/opentelemetry/agent_to_engine_test.cc" "${TESTS_DIR}/opentelemetry/grpc_config_test.cc" "${TESTS_DIR}/opentelemetry/host_serv_extractor_test.cc" "${TESTS_DIR}/opentelemetry/otl_server_test.cc" @@ -196,12 +197,14 @@ if(WITH_TESTING) cce_core log_v2 opentelemetry + centagent_lib "-Wl,-no-whole-archive" pb_open_telemetry_lib centreon_grpc centreon_http - -L${Boost_LIBRARY_DIR_RELEASE} - boost_url + centreon_process + -L${Boost_LIBRARY_DIR_RELEASE} + boost_url boost_program_options pthread ${GCOV} diff --git a/engine/tests/opentelemetry/agent_to_engine_test.cc b/engine/tests/opentelemetry/agent_to_engine_test.cc new file mode 100644 index 00000000000..91679611c36 --- /dev/null +++ b/engine/tests/opentelemetry/agent_to_engine_test.cc @@ -0,0 +1,327 @@ +/** + * Copyright 2024 Centreon + * + * This file is part of Centreon Engine. + * + * Centreon Engine is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * Centreon Engine 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 Centreon Engine. If not, see + * <http://www.gnu.org/licenses/>. + */ + +#include <absl/container/btree_set.h> +#include <absl/synchronization/mutex.h> + +#include <grpcpp/grpcpp.h> +#include <gtest/gtest.h> + +#include <rapidjson/document.h> + +#include "opentelemetry/proto/collector/metrics/v1/metrics_service.grpc.pb.h" +#include "opentelemetry/proto/metrics/v1/metrics.pb.h" + +#include "com/centreon/engine/contact.hh" +#include "com/centreon/engine/host.hh" +#include "com/centreon/engine/service.hh" + +#include "com/centreon/engine/command_manager.hh" +#include "com/centreon/engine/configuration/applier/connector.hh" +#include "com/centreon/engine/configuration/applier/contact.hh" +#include "com/centreon/engine/configuration/applier/host.hh" +#include "com/centreon/engine/configuration/applier/service.hh" + +#include "com/centreon/agent/streaming_client.hh" +#include "com/centreon/engine/modules/opentelemetry/otl_fmt.hh" +#include "com/centreon/engine/modules/opentelemetry/otl_server.hh" + +#include "../test_engine.hh" +#include "helper.hh" + +using namespace com::centreon::engine; +using namespace com::centreon::agent; +// using namespace com::centreon::engine::configuration; +// using namespace com::centreon::engine::configuration::applier; +using namespace com::centreon::engine::modules::opentelemetry; +using namespace ::opentelemetry::proto::collector::metrics::v1; + +class agent_to_engine_test : public TestEngine { + protected: + std::shared_ptr<otl_server> _server; + + // agent code is mono-thread so it runs on his own io_context run by only one + // thread + std::shared_ptr<asio::io_context> _agent_io_context; + + asio::executor_work_guard<asio::io_context::executor_type> _worker; + std::thread _agent_io_ctx_thread; + + public: + agent_to_engine_test() + : _agent_io_context(std::make_shared<asio::io_context>()), + _worker{asio::make_work_guard(*_agent_io_context)}, + _agent_io_ctx_thread([this] { _agent_io_context->run(); }) {} + + ~agent_to_engine_test() { + _agent_io_context->stop(); + _agent_io_ctx_thread.join(); + } + + void SetUp() override { + spdlog::default_logger()->set_level(spdlog::level::trace); + ::fmt::formatter< ::opentelemetry::proto::collector::metrics::v1:: + ExportMetricsServiceRequest>::json_grpc_format = true; + timeperiod::timeperiods.clear(); + contact::contacts.clear(); + host::hosts.clear(); + host::hosts_by_id.clear(); + service::services.clear(); + service::services_by_id.clear(); + + init_config_state(); + + configuration::applier::connector conn_aply; + configuration::connector cnn("agent"); + cnn.parse("connector_line", + "opentelemetry " + "--processor=nagios_telegraf --extractor=attributes " + "--host_path=resource_metrics.scope_metrics.data.data_points." + "attributes.host " + "--service_path=resource_metrics.scope_metrics.data.data_points." + "attributes.service"); + conn_aply.add_object(cnn); + configuration::error_cnt err; + + configuration::applier::contact ct_aply; + configuration::contact ctct{new_configuration_contact("admin", true)}; + ct_aply.add_object(ctct); + ct_aply.expand_objects(*config); + ct_aply.resolve_object(ctct, err); + + configuration::host hst = + new_configuration_host("test_host", "admin", 1, "agent"); + + configuration::applier::host hst_aply; + hst_aply.add_object(hst); + + configuration::service svc{new_configuration_service( + "test_host", "test_svc", "admin", 1, "agent")}; + configuration::service svc2{new_configuration_service( + "test_host", "test_svc_2", "admin", 2, "agent")}; + configuration::service svc_no_otel{ + new_configuration_service("test_host", "test_svc_2", "admin", 3)}; + configuration::applier::service svc_aply; + svc_aply.add_object(svc); + svc_aply.add_object(svc2); + svc_aply.add_object(svc_no_otel); + + hst_aply.resolve_object(hst, err); + svc_aply.resolve_object(svc, err); + svc_aply.resolve_object(svc2, err); + svc_aply.resolve_object(svc_no_otel, err); + } + + void TearDown() override { + if (_server) { + _server->shutdown(std::chrono::seconds(15)); + _server.reset(); + } + deinit_config_state(); + } + + template <class metric_handler_type> + void start_server(const grpc_config::pointer& listen_endpoint, + const centreon_agent::agent_config::pointer& agent_conf, + const metric_handler_type& handler) { + _server = otl_server::load(_agent_io_context, listen_endpoint, agent_conf, + handler, spdlog::default_logger()); + } +}; + +bool compare_to_expected_host_metric( + const opentelemetry::proto::metrics::v1::ResourceMetrics& metric) { + bool host_found = false, serv_found = false; + for (const auto& attrib : metric.resource().attributes()) { + if (attrib.key() == "host.name") { + if (attrib.value().string_value() != "test_host") { + return false; + } + host_found = true; + } + if (attrib.key() == "service.name") { + if (!attrib.value().string_value().empty()) { + return false; + } + serv_found = true; + } + } + if (!host_found || !serv_found) { + return false; + } + const auto& scope_metric = metric.scope_metrics(); + if (scope_metric.size() != 1) + return false; + const auto& metrics = scope_metric.begin()->metrics(); + if (metrics.empty()) + return false; + const auto& status_metric = *metrics.begin(); + if (status_metric.name() != "status") + return false; + if (!status_metric.has_gauge()) + return false; + if (status_metric.gauge().data_points().empty()) + return false; + return status_metric.gauge().data_points().begin()->as_int() == 0; +} + +bool test_exemplars( + const google::protobuf::RepeatedPtrField< + ::opentelemetry::proto::metrics::v1::Exemplar>& examplars, + const std::map<std::string, double>& expected) { + std::set<std::string> matches; + + for (const auto& ex : examplars) { + if (ex.filtered_attributes().empty()) + continue; + auto search = expected.find(ex.filtered_attributes().begin()->key()); + if (search == expected.end()) + return false; + + if (search->second != ex.as_double()) + return false; + matches.insert(search->first); + } + return matches.size() == expected.size(); +} + +bool compare_to_expected_serv_metric( + const opentelemetry::proto::metrics::v1::ResourceMetrics& metric, + const std::string_view& serv_name) { + bool host_found = false, serv_found = false; + for (const auto& attrib : metric.resource().attributes()) { + if (attrib.key() == "host.name") { + if (attrib.value().string_value() != "test_host") { + return false; + } + host_found = true; + } + if (attrib.key() == "service.name") { + if (attrib.value().string_value() != serv_name) { + return false; + } + serv_found = true; + } + } + if (!host_found || !serv_found) { + return false; + } + const auto& scope_metric = metric.scope_metrics(); + if (scope_metric.size() != 1) + return false; + const auto& metrics = scope_metric.begin()->metrics(); + if (metrics.empty()) + return false; + + for (const auto& met : metrics) { + if (!met.has_gauge()) + return false; + if (met.name() == "metric") { + if (met.gauge().data_points().empty()) + return false; + if (met.gauge().data_points().begin()->as_double() != 12) + return false; + if (!test_exemplars(met.gauge().data_points().begin()->exemplars(), + {{"crit_gt", 75.0}, + {"crit_lt", 0.0}, + {"warn_gt", 50.0}, + {"warn_lt", 0.0}})) + return false; + } else if (met.name() == "metric2") { + if (met.gauge().data_points().empty()) + return false; + if (met.gauge().data_points().begin()->as_double() != 30) + return false; + if (!test_exemplars(met.gauge().data_points().begin()->exemplars(), + {{"crit_gt", 80.0}, + {"crit_lt", 75.0}, + {"warn_gt", 75.0}, + {"warn_lt", 50.0}, + {"min", 0.0}, + {"max", 100.0}})) + return false; + + } else if (met.name() == "status") { + if (met.gauge().data_points().begin()->as_int() != 0) + return false; + } else + return false; + } + + return true; +} + +TEST_F(agent_to_engine_test, server_send_conf_to_agent_and_receive_metrics) { + grpc_config::pointer listen_endpoint = + std::make_shared<grpc_config>("127.0.0.1:4623", false); + + absl::Mutex mut; + std::vector<metric_request_ptr> received; + std::vector<const opentelemetry::proto::metrics::v1::ResourceMetrics*> + resource_metrics; + + auto agent_conf = std::make_shared<centreon_agent::agent_config>(1, 10, 1, 5); + + start_server(listen_endpoint, agent_conf, + [&](const metric_request_ptr& metric) { + absl::MutexLock l(&mut); + received.push_back(metric); + for (const opentelemetry::proto::metrics::v1::ResourceMetrics& + res_metric : metric->resource_metrics()) { + resource_metrics.push_back(&res_metric); + } + }); + + auto agent_client = + streaming_client::load(_agent_io_context, spdlog::default_logger(), + listen_endpoint, "test_host"); + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + command_manager::instance().execute(); + + auto metric_received = [&]() { return resource_metrics.size() >= 3; }; + + mut.LockWhen(absl::Condition(&metric_received)); + mut.Unlock(); + + agent_client->shutdown(); + + _server->shutdown(std::chrono::seconds(15)); + + bool host_metric_found = true; + bool serv_1_found = false; + bool serv_2_found = false; + + for (const opentelemetry::proto::metrics::v1::ResourceMetrics* to_compare : + resource_metrics) { + if (compare_to_expected_serv_metric(*to_compare, "test_svc")) { + serv_1_found = true; + } else if (compare_to_expected_serv_metric(*to_compare, "test_svc_2")) { + serv_2_found = true; + } else if (compare_to_expected_host_metric(*to_compare)) { + host_metric_found = true; + } else { + SPDLOG_ERROR("bad resource metric: {}", to_compare->DebugString()); + ASSERT_TRUE(false); + } + } + ASSERT_TRUE(host_metric_found); + ASSERT_TRUE(serv_1_found); + ASSERT_TRUE(serv_2_found); +} \ No newline at end of file diff --git a/packaging/centreon-monitoring-agent.yaml b/packaging/centreon-monitoring-agent.yaml index 83bba81f424..c452432cf47 100644 --- a/packaging/centreon-monitoring-agent.yaml +++ b/packaging/centreon-monitoring-agent.yaml @@ -51,6 +51,16 @@ contents: owner: centreon-monitoring-agent group: centreon-monitoring-agent +overrides: + rpm: + depends: + - openssl-libs >= 3 + - zlib + deb: + depends: + - libssl1.1 | libssl3 + - zlib1g + scripts: preinstall: ./scripts/centreon-monitoring-agent-preinstall.sh postinstall: ./scripts/centreon-monitoring-agent-postinstall.sh diff --git a/tests/broker-engine/opentelemetry.robot b/tests/broker-engine/opentelemetry.robot index 728e624a924..2397f9dbdca 100644 --- a/tests/broker-engine/opentelemetry.robot +++ b/tests/broker-engine/opentelemetry.robot @@ -2,6 +2,7 @@ Documentation Engine/Broker tests on opentelemetry engine server Resource ../resources/import.resource +Library ../resources/Agent.py Suite Setup Ctn Clean Before Suite Suite Teardown Ctn Clean After Suite @@ -142,6 +143,20 @@ BEOTEL_TELEGRAF_CHECK_HOST ${result} Ctn Check Host Output Resource Status With Timeout host_1 30 ${start} 0 HARD OK Should Be True ${result} hosts table not updated + + Log To Console export metrics + Ctn Send Otl To Engine 4317 ${resources_list} + + Sleep 5 + + + # feed and check + ${start} Ctn Get Round Current Date + Ctn Schedule Forced Host Check host_1 + + ${result} Ctn Check Host Check Status With Timeout host_1 30 ${start} 0 OK + Should Be True ${result} hosts table not updated + # check then feed, three times to modify hard state ${start} Ctn Get Round Current Date Ctn Schedule Forced Host Check host_1 @@ -224,6 +239,18 @@ BEOTEL_TELEGRAF_CHECK_SERVICE ${result} Ctn Check Service Output Resource Status With Timeout host_1 service_1 30 ${start} 0 HARD OK Should Be True ${result} services table not updated + Log To Console export metrics + Ctn Send Otl To Engine 4317 ${resources_list} + + Sleep 5 + + # feed and check + ${start} Ctn Get Round Current Date + Ctn Schedule Forced Svc Check host_1 service_1 + + ${result} Ctn Check Service Check Status With Timeout host_1 service_1 30 ${start} 0 OK + Should Be True ${result} services table not updated + # check then feed, three times to modify hard state ${start} Ctn Get Round Current Date ${resources_list} Ctn Create Otl Request ${2} host_1 service_1 @@ -392,6 +419,381 @@ BEOTEL_SERVE_TELEGRAF_CONFIGURATION_NO_CRYPTED ... unexpected telegraf server response: ${telegraf_conf_response.text} +BEOTEL_CENTREON_AGENT_CHECK_HOST + [Documentation] agent check host and we expect to get it in check result + [Tags] broker engine opentelemetry MON-63843 + Ctn Config Engine ${1} ${2} ${2} + Ctn Add Otl ServerModule + ... 0 + ... {"otel_server":{"host": "0.0.0.0","port": 4317},"max_length_grpc_log":0, "centreon_agent":{"check_interval":10, "export_period":10}} + Ctn Config Add Otl Connector + ... 0 + ... OTEL connector + ... opentelemetry --processor=centreon_agent --extractor=attributes --host_path=resource_metrics.resource.attributes.host.name --service_path=resource_metrics.resource.attributes.service.name + Ctn Engine Config Replace Value In Hosts ${0} host_1 check_command otel_check_icmp + Ctn Engine Config Add Command + ... ${0} + ... otel_check_icmp + ... /bin/echo "OK - 127.0.0.1: rta 0,010ms, lost 0%|rta=0,010ms;200,000;500,000;0; pl=0%;40;80;; rtmax=0,035ms;;;; rtmin=0,003ms;;;;" + ... OTEL connector + + Ctn Engine Config Set Value 0 log_level_checks trace + + Ctn Config Broker central + Ctn Config Broker module + Ctn Config Broker rrd + Ctn Config Centreon Agent + Ctn Broker Config Log central sql trace + + Ctn ConfigBBDO3 1 + Ctn Clear Retention + + ${start} Get Current Date + Ctn Start Broker + Ctn Start Engine + Ctn Start Agent + + # Let's wait for the otel server start + ${content} Create List unencrypted server listening on 0.0.0.0:4317 + ${result} Ctn Find In Log With Timeout ${engineLog0} ${start} ${content} 10 + Should Be True ${result} "unencrypted server listening on 0.0.0.0:4317" should be available. + Sleep 1 + + ${start} Ctn Get Round Current Date + Ctn Schedule Forced Host Check host_1 + + ${result} Ctn Check Host Check Status With Timeout host_1 30 ${start} 0 OK - 127.0.0.1 + Should Be True ${result} hosts table not updated + + Ctn Engine Config Replace Value In Hosts ${0} host_1 check_command otel_check_icmp_2 + Ctn Engine Config Add Command + ... ${0} + ... otel_check_icmp_2 + ... /bin/echo "OK check2 - 127.0.0.1: rta 0,010ms, lost 0%|rta=0,010ms;200,000;500,000;0; pl=0%;40;80;; rtmax=0,035ms;;;; rtmin=0,003ms;;;;" + ... OTEL connector + + #update conf engine, it must be taken into account by agent + Log To Console modify engine conf and reload engine + Ctn Reload Engine + + #wait for new data from agent + ${start} Ctn Get Round Current Date + ${content} Create List description: \"OK check2 + ${result} Ctn Find In Log With Timeout ${engineLog0} ${start} ${content} 22 + Should Be True ${result} "description: "OK check2" should be available. + + ${start} Ctn Get Round Current Date + Ctn Schedule Forced Host Check host_1 + + ${result} Ctn Check Host Check Status With Timeout host_1 30 ${start} 0 OK check2 - 127.0.0.1: rta 0,010ms, lost 0% + Should Be True ${result} hosts table not updated + + +BEOTEL_CENTREON_AGENT_CHECK_SERVICE + [Documentation] agent check service and we expect to get it in check result + [Tags] broker engine opentelemetry MON-63843 + Ctn Config Engine ${1} ${2} ${2} + Ctn Add Otl ServerModule + ... 0 + ... {"otel_server":{"host": "0.0.0.0","port": 4317},"max_length_grpc_log":0,"centreon_agent":{"check_interval":10, "export_period":15}} + Ctn Config Add Otl Connector + ... 0 + ... OTEL connector + ... opentelemetry --processor=centreon_agent --extractor=attributes --host_path=resource_metrics.resource.attributes.host.name --service_path=resource_metrics.resource.attributes.service.name + Ctn Engine Config Replace Value In Services ${0} service_1 check_command otel_check + Ctn Engine Config Add Command + ... ${0} + ... otel_check + ... /tmp/var/lib/centreon-engine/check.pl --id 456 + ... OTEL connector + + Ctn Engine Config Set Value 0 log_level_checks trace + + #service_1 check fail CRITICAL + Ctn Set Command Status 456 ${2} + + Ctn Config Broker central + Ctn Config Broker module + Ctn Config Broker rrd + Ctn Config Centreon Agent + Ctn Broker Config Log central sql trace + + Ctn ConfigBBDO3 1 + Ctn Clear Retention + + ${start} Ctn Get Round Current Date + Ctn Start Broker + Ctn Start Engine + Ctn Start Agent + + # Let's wait for the otel server start + ${content} Create List unencrypted server listening on 0.0.0.0:4317 + ${result} Ctn Find In Log With Timeout ${engineLog0} ${start} ${content} 10 + Should Be True ${result} "unencrypted server listening on 0.0.0.0:4317" should be available. + + ${content} Create List fifos:{"host_1,service_1" + ${result} Ctn Find In Log With Timeout ${engineLog0} ${start} ${content} 30 + Should Be True ${result} fifos not found in logs + + Ctn Schedule Forced Svc Check host_1 service_1 + + ${result} Ctn Check Service Check Status With Timeout host_1 service_1 60 ${start} 2 Test check 456 + Should Be True ${result} services table not updated + + ${start} Ctn Get Round Current Date + #service_1 check ok + Ctn Set Command Status 456 ${0} + + ${content} Create List as_int: 0 + ${result} Ctn Find In Log With Timeout ${engineLog0} ${start} ${content} 30 + Should Be True ${result} status 0 not found in logs + + Ctn Schedule Forced Svc Check host_1 service_1 + + ${result} Ctn Check Service Check Status With Timeout host_1 service_1 60 ${start} 0 Test check 456 + Should Be True ${result} services table not updated + + +BEOTEL_REVERSE_CENTREON_AGENT_CHECK_HOST + [Documentation] agent check host with reversed connection and we expect to get it in check result + [Tags] broker engine opentelemetry MON-63843 + Ctn Config Engine ${1} ${2} ${2} + Ctn Add Otl ServerModule + ... 0 + ... {"max_length_grpc_log":0,"centreon_agent":{"check_interval":10, "export_period":15, "reverse_connections":[{"host": "127.0.0.1","port": 4317}]}} + Ctn Config Add Otl Connector + ... 0 + ... OTEL connector + ... opentelemetry --processor=centreon_agent --extractor=attributes --host_path=resource_metrics.resource.attributes.host.name --service_path=resource_metrics.resource.attributes.service.name + Ctn Engine Config Replace Value In Hosts ${0} host_1 check_command otel_check_icmp + Ctn Engine Config Add Command + ... ${0} + ... otel_check_icmp + ... /bin/echo "OK - 127.0.0.1: rta 0,010ms, lost 0%|rta=0,010ms;200,000;500,000;0; pl=0%;40;80;; rtmax=0,035ms;;;; rtmin=0,003ms;;;;" + ... OTEL connector + + Ctn Engine Config Set Value 0 log_level_checks trace + + Ctn Config Broker central + Ctn Config Broker module + Ctn Config Broker rrd + Ctn Config Reverse Centreon Agent + Ctn Broker Config Log central sql trace + + Ctn ConfigBBDO3 1 + Ctn Clear Retention + + ${start} Get Current Date + Ctn Start Broker + Ctn Start Engine + Ctn Start Agent + + # Let's wait for engine to connect to agent + ${content} Create List init from [.\\s]*127.0.0.1:4317 + ${result} Ctn Find Regex In Log With Timeout ${engineLog0} ${start} ${content} 10 + Should Be True ${result} "init from localhost:4317" not found in log + Sleep 1 + + ${start} Ctn Get Round Current Date + Ctn Schedule Forced Host Check host_1 + + ${result} Ctn Check Host Check Status With Timeout host_1 30 ${start} 0 OK - 127.0.0.1 + Should Be True ${result} hosts table not updated + + Ctn Engine Config Replace Value In Hosts ${0} host_1 check_command otel_check_icmp_2 + Ctn Engine Config Add Command + ... ${0} + ... otel_check_icmp_2 + ... /bin/echo "OK check2 - 127.0.0.1: rta 0,010ms, lost 0%|rta=0,010ms;200,000;500,000;0; pl=0%;40;80;; rtmax=0,035ms;;;; rtmin=0,003ms;;;;" + ... OTEL connector + + #update conf engine, it must be taken into account by agent + Log To Console modify engine conf and reload engine + Ctn Reload Engine + + #wait for new data from agent + ${start} Ctn Get Round Current Date + ${content} Create List description: \"OK check2 + ${result} Ctn Find In Log With Timeout ${engineLog0} ${start} ${content} 30 + Should Be True ${result} "description: "OK check2" should be available. + + ${start} Ctn Get Round Current Date + Ctn Schedule Forced Host Check host_1 + + ${result} Ctn Check Host Check Status With Timeout host_1 30 ${start} 0 OK check2 - 127.0.0.1: rta 0,010ms, lost 0% + Should Be True ${result} hosts table not updated + + +BEOTEL_REVERSE_CENTREON_AGENT_CHECK_SERVICE + [Documentation] agent check service with reversed connection and we expect to get it in check result + [Tags] broker engine opentelemetry MON-63843 + Ctn Config Engine ${1} ${2} ${2} + Ctn Add Otl ServerModule + ... 0 + ... {"max_length_grpc_log":0,"centreon_agent":{"check_interval":10, "export_period":15, "reverse_connections":[{"host": "127.0.0.1","port": 4317}]}} + Ctn Config Add Otl Connector + ... 0 + ... OTEL connector + ... opentelemetry --processor=centreon_agent --extractor=attributes --host_path=resource_metrics.resource.attributes.host.name --service_path=resource_metrics.resource.attributes.service.name + Ctn Engine Config Replace Value In Services ${0} service_1 check_command otel_check + Ctn Engine Config Add Command + ... ${0} + ... otel_check + ... /tmp/var/lib/centreon-engine/check.pl --id 456 + ... OTEL connector + + Ctn Engine Config Set Value 0 log_level_checks trace + + #service_1 check fail CRITICAL + Ctn Set Command Status 456 ${2} + + Ctn Config Broker central + Ctn Config Broker module + Ctn Config Broker rrd + Ctn Config Reverse Centreon Agent + Ctn Broker Config Log central sql trace + + Ctn ConfigBBDO3 1 + Ctn Clear Retention + + ${start} Ctn Get Round Current Date + Ctn Start Broker + Ctn Start Engine + Ctn Start Agent + + # Let's wait for engine to connect to agent + ${content} Create List init from [.\\s]*127.0.0.1:4317 + ${result} Ctn Find Regex In Log With Timeout ${engineLog0} ${start} ${content} 10 + Should Be True ${result} "init from 127.0.0.1:4317" not found in log + + + ${content} Create List fifos:{"host_1,service_1" + ${result} Ctn Find In Log With Timeout ${engineLog0} ${start} ${content} 30 + Should Be True ${result} fifos not found in logs + + Ctn Schedule Forced Svc Check host_1 service_1 + + ${result} Ctn Check Service Check Status With Timeout host_1 service_1 60 ${start} 2 Test check 456 + Should Be True ${result} services table not updated + + ${start} Ctn Get Round Current Date + #service_1 check ok + Ctn Set Command Status 456 ${0} + + ${content} Create List as_int: 0 + ${result} Ctn Find In Log With Timeout ${engineLog0} ${start} ${content} 30 + Should Be True ${result} status 0 not found in logs + + Ctn Schedule Forced Svc Check host_1 service_1 + + ${result} Ctn Check Service Check Status With Timeout host_1 service_1 60 ${start} 0 Test check 456 + Should Be True ${result} services table not updated + +BEOTEL_CENTREON_AGENT_CHECK_HOST_CRYPTED + [Documentation] agent check host with encrypted connection and we expect to get it in check result + [Tags] broker engine opentelemetry MON-63843 + Ctn Config Engine ${1} ${2} ${2} + Copy File ../broker/grpc/test/grpc_test_keys/ca_1234.crt /tmp/ + Copy File ../broker/grpc/test/grpc_test_keys/server_1234.key /tmp/ + Copy File ../broker/grpc/test/grpc_test_keys/server_1234.crt /tmp/ + Ctn Add Otl ServerModule + ... 0 + ... {"otel_server":{"host": "0.0.0.0","port": 4317, "encryption": true, "public_cert": "/tmp/server_1234.crt", "private_key": "/tmp/server_1234.key", "ca_certificate": "/tmp/ca_1234.crt"},"max_length_grpc_log":0} + Ctn Config Add Otl Connector + ... 0 + ... OTEL connector + ... opentelemetry --processor=centreon_agent --extractor=attributes --host_path=resource_metrics.resource.attributes.host.name --service_path=resource_metrics.resource.attributes.service.name + Ctn Engine Config Replace Value In Hosts ${0} host_1 check_command otel_check_icmp + Ctn Engine Config Add Command + ... ${0} + ... otel_check_icmp + ... /bin/echo "OK - 127.0.0.1: rta 0,010ms, lost 0%|rta=0,010ms;200,000;500,000;0; pl=0%;40;80;; rtmax=0,035ms;;;; rtmin=0,003ms;;;;" + ... OTEL connector + + Ctn Engine Config Set Value 0 log_level_checks trace + + Ctn Config Broker central + Ctn Config Broker module + Ctn Config Broker rrd + Ctn Config Centreon Agent ${None} ${None} /tmp/ca_1234.crt + Ctn Broker Config Log central sql trace + + Ctn ConfigBBDO3 1 + Ctn Clear Retention + + ${start} Get Current Date + Ctn Start Broker + Ctn Start Engine + Ctn Start Agent + + # Let's wait for the otel server start + ${content} Create List encrypted server listening on 0.0.0.0:4317 + ${result} Ctn Find In Log With Timeout ${engineLog0} ${start} ${content} 10 + Should Be True ${result} "encrypted server listening on 0.0.0.0:4317" should be available. + Sleep 1 + + ${start} Ctn Get Round Current Date + Ctn Schedule Forced Host Check host_1 + + ${result} Ctn Check Host Check Status With Timeout host_1 30 ${start} 0 OK - 127.0.0.1 + Should Be True ${result} hosts table not updated + + + +BEOTEL_REVERSE_CENTREON_AGENT_CHECK_HOST_CRYPTED + [Documentation] agent check host with encrypted reversed connection and we expect to get it in check result + [Tags] broker engine opentelemetry MON-63843 + Ctn Config Engine ${1} ${2} ${2} + Copy File ../broker/grpc/test/grpc_test_keys/ca_1234.crt /tmp/ + Copy File ../broker/grpc/test/grpc_test_keys/server_1234.key /tmp/ + Copy File ../broker/grpc/test/grpc_test_keys/server_1234.crt /tmp/ + + Ctn Add Otl ServerModule + ... 0 + ... {"max_length_grpc_log":0,"centreon_agent":{"check_interval":10, "export_period":15, "reverse_connections":[{"host": "localhost","port": 4317, "encryption": true, "ca_certificate": "/tmp/ca_1234.crt"}]}} + + Ctn Config Add Otl Connector + ... 0 + ... OTEL connector + ... opentelemetry --processor=centreon_agent --extractor=attributes --host_path=resource_metrics.resource.attributes.host.name --service_path=resource_metrics.resource.attributes.service.name + Ctn Engine Config Replace Value In Hosts ${0} host_1 check_command otel_check_icmp + Ctn Engine Config Add Command + ... ${0} + ... otel_check_icmp + ... /bin/echo "OK - 127.0.0.1: rta 0,010ms, lost 0%|rta=0,010ms;200,000;500,000;0; pl=0%;40;80;; rtmax=0,035ms;;;; rtmin=0,003ms;;;;" + ... OTEL connector + + Ctn Engine Config Set Value 0 log_level_checks trace + + Ctn Config Broker central + Ctn Config Broker module + Ctn Config Broker rrd + Ctn Config Reverse Centreon Agent /tmp/server_1234.key /tmp/server_1234.crt /tmp/ca_1234.crt + Ctn Broker Config Log central sql trace + + Ctn ConfigBBDO3 1 + Ctn Clear Retention + + ${start} Get Current Date + Ctn Start Broker + Ctn Start Engine + Ctn Start Agent + + # Let's wait for engine to connect to agent + ${content} Create List init from localhost:4317 + ${result} Ctn Find In Log With Timeout ${engineLog0} ${start} ${content} 10 + Should Be True ${result} "init from localhost:4317" not found in log + Sleep 1 + + ${start} Ctn Get Round Current Date + Ctn Schedule Forced Host Check host_1 + + ${result} Ctn Check Host Check Status With Timeout host_1 30 ${start} 0 OK - 127.0.0.1 + Should Be True ${result} hosts table not updated + + + + *** Keywords *** Ctn Create Otl Request [Documentation] create an otl request with nagios telegraf style diff --git a/tests/resources/Agent.py b/tests/resources/Agent.py new file mode 100644 index 00000000000..4497a4453f3 --- /dev/null +++ b/tests/resources/Agent.py @@ -0,0 +1,71 @@ +#!/usr/bin/python3 +# +# Copyright 2023-2024 Centreon +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# For more information : contact@centreon.com +# + +from os import makedirs +from robot.libraries.BuiltIn import BuiltIn + +ETC_ROOT = BuiltIn().get_variable_value("${EtcRoot}") +CONF_DIR = ETC_ROOT + "/centreon-engine" + + +agent_config=""" +{ + "log_level":"trace", + "endpoint":"localhost:4317", + "host":"host_1", + "log_type":"file", + "log_file":"/tmp/var/log/centreon-engine/centreon-agent.log" """ + + +def ctn_config_centreon_agent(key_path:str = None, cert_path:str = None, ca_path:str = None): + """ctn_config_centreon_agent + Creates a default centreon agent config without encryption nor reverse connection + """ + makedirs(CONF_DIR, mode=0o777, exist_ok=True) + with open(f"{CONF_DIR}/centagent.json", "w") as ff: + ff.write(agent_config) + if key_path is not None or cert_path is not None or ca_path is not None: + ff.write(",\n \"encryption\":true") + if key_path is not None: + ff.write(f",\n \"private_key\":\"{key_path}\"") + if cert_path is not None: + ff.write(f",\n \"public_cert\":\"{cert_path}\"") + if ca_path is not None: + ff.write(f",\n \"ca_certificate\":\"{ca_path}\"") + ff.write("\n}\n") + + + +def ctn_config_reverse_centreon_agent(key_path:str = None, cert_path:str = None, ca_path:str = None): + """ctn_config_centreon_agent + Creates a default reversed centreon agent config without encryption listening on 0.0.0.0:4317 + """ + makedirs(CONF_DIR, mode=0o777, exist_ok=True) + with open(f"{CONF_DIR}/centagent.json", "w") as ff: + ff.write(agent_config) + ff.write(",\n \"reverse_connection\":true") + if key_path is not None or cert_path is not None or ca_path is not None: + ff.write(",\n \"encryption\":true") + if key_path is not None: + ff.write(f",\n \"private_key\":\"{key_path}\"") + if cert_path is not None: + ff.write(f",\n \"public_cert\":\"{cert_path}\"") + if ca_path is not None: + ff.write(f",\n \"ca_certificate\":\"{ca_path}\"") + ff.write("\n}\n") diff --git a/tests/resources/resources.resource b/tests/resources/resources.resource index f53d7f6ff08..474c70b1d4f 100644 --- a/tests/resources/resources.resource +++ b/tests/resources/resources.resource @@ -233,6 +233,11 @@ Ctn Stop Engine Broker And Save Logs EXCEPT Log Can't kindly stop Broker END + TRY + Ctn Kindly Stop Agent + EXCEPT + Log Can't kindly stop Agent + END Ctn Save Logs If Failed Ctn Get Engine Pid @@ -283,7 +288,9 @@ Ctn Save Logs Copy Files ${rrdLog} ${failDir} Copy Files ${moduleLog0} ${failDir} Copy Files ${engineLog0} ${failDir} + Copy Files ${ENGINE_LOG}/*.log ${failDir} Copy Files ${EtcRoot}/centreon-engine/config0/*.cfg ${failDir}/etc/centreon-engine/config0 + Copy Files ${EtcRoot}/centreon-engine/*.json ${failDir}/etc/centreon-engine Copy Files ${EtcRoot}/centreon-broker/*.json ${failDir}/etc/centreon-broker Move Files /tmp/lua*.log ${failDir} @@ -384,3 +391,30 @@ Ctn Wait For Engine To Be Ready ... ${result} ... A message telling check_for_external_commands() should be available in config${i}/centengine.log. END + + +Ctn Start Agent + Start Process /usr/bin/centagent ${EtcRoot}/centreon-engine/centagent.json alias=centreon_agent + +Ctn Kindly Stop Agent + #in most case centreon_agent is not started + ${centreon_agent_process} Get Process Object centreon_agent + + IF ${{$centreon_agent_process is None}} RETURN + + Send Signal To Process SIGTERM centreon_agent + ${result} Wait For Process centreon_agent timeout=60s + # In case of process not stopping + IF "${result}" == "${None}" + Log To Console "fail to stop centreon_agent" + Ctn Save Logs + Ctn Dump Process centreon_agent /usr/bin/centagent centreon_agent + Send Signal To Process SIGKILL centreon_agent + Fail centreon_agent not correctly stopped (coredump generated) + ELSE + IF ${result.rc} != 0 + Ctn Save Logs + Ctn Coredump Info centreon_agent /usr/bin/centagent centreon_agent + Fail centreon_agent not correctly stopped, result status: ${result.rc} + END + END From 99a5cc9414d98a2863cedae9445637f4af82c2c7 Mon Sep 17 00:00:00 2001 From: Vincent Untz <vuntz@centreon.com> Date: Mon, 15 Jul 2024 09:54:20 +0200 Subject: [PATCH 880/948] [MBI] In the table mod_bi_tmp_today_servicemetrics, use the same type as defined in mod_bi_servicemetrics (#1517) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Use same type in all tables * Update metric_id column type --------- Co-authored-by: Stéphane Duret <sduret@centreon.com> --- gorgone/gorgone/modules/centreon/mbi/libs/bi/BIMetric.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIMetric.pm b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIMetric.pm index 55269e07ba9..d240db5ab7c 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIMetric.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/bi/BIMetric.pm @@ -153,8 +153,8 @@ sub createTodayTable { $db->query({ query => "DROP TABLE IF EXISTS `".$self->{"today_table"}."`" }); my $query = "CREATE TABLE `" . $self->{"today_table"} . "` ("; - $query .= "`id` INT NOT NULL,"; - $query .= "`metric_id` int(11) NOT NULL,"; + $query .= "`id` BIGINT(20) UNSIGNED NOT NULL,"; + $query .= "`metric_id` BIGINT(20) UNSIGNED NOT NULL,"; $query .= "`metric_name` varchar(255) NOT NULL,"; $query .= "`sc_id` int(11) NOT NULL,"; $query .= "`hg_id` int(11) NOT NULL,"; From 7fccf2a8a761844bf8b82f01872e35a77d6ebbbe Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Mon, 15 Jul 2024 12:30:55 +0200 Subject: [PATCH 881/948] initialize test deb12 (#1524) * Empty-Commit * initialize test deb12 * adding dependencies * bookworm missing packages * bookworm * Update .github/docker/Dockerfile.gorgone-testing-bookworm Co-authored-by: Evan-Adam <152897682+Evan-Adam@users.noreply.github.com> * Update .github/docker/Dockerfile.gorgone-testing-bookworm Co-authored-by: Evan-Adam <152897682+Evan-Adam@users.noreply.github.com> * Update .github/docker/Dockerfile.gorgone-testing-bookworm Co-authored-by: Evan-Adam <152897682+Evan-Adam@users.noreply.github.com> * modif * modif * update docker gorgone testing workflow --------- Co-authored-by: Paul LOUIS THERESE <53221698+paloth@users.noreply.github.com> Co-authored-by: pkippes <144150042+pkippes@users.noreply.github.com> Co-authored-by: tuntoja <tuntoja@centreon.com> Co-authored-by: root <sfarouq-ext@centreon.com> Co-authored-by: sfarouq-ext <116093375+sfarouq-ext@users.noreply.github.com> Co-authored-by: Evan-Adam <152897682+Evan-Adam@users.noreply.github.com> --- .../Dockerfile.gorgone-testing-bookworm | 28 +++++++++++++++++++ .github/workflows/docker-gorgone-testing.yml | 2 +- .github/workflows/gorgone.yml | 6 +++- .../robot/tests/start_stop/start_stop.robot | 1 + 4 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 .github/docker/Dockerfile.gorgone-testing-bookworm diff --git a/.github/docker/Dockerfile.gorgone-testing-bookworm b/.github/docker/Dockerfile.gorgone-testing-bookworm new file mode 100644 index 00000000000..840923ea75c --- /dev/null +++ b/.github/docker/Dockerfile.gorgone-testing-bookworm @@ -0,0 +1,28 @@ +FROM debian:bookworm + +ENV DEBIAN_FRONTEND=noninteractive + +# Set locale +RUN apt-get update && \ + apt-get install -y locales libcurl4-openssl-dev curl wget zstd jq lsb-release mariadb-client iproute2 && \ + apt-get install -y ca-certificates apt-transport-https software-properties-common gnupg2 && \ + localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 + +ENV LANG=en_US.UTF-8 + +# Install required packages and Robotframework +RUN apt-get update && \ + apt-get install -y \ + python3 \ + python3-dev \ + python3-pip && \ + pip3 install --break-system-packages --no-cache-dir \ + robotframework \ + robotframework-examples \ + robotframework-databaselibrary \ + pymysql \ + robotframework-requests && \ + echo "deb https://packages.centreon.com/apt-standard-24.05-testing/ $(lsb_release -sc) main" | tee /etc/apt/sources.list.d/centreon.list && \ + echo "deb https://packages.centreon.com/apt-plugins-testing/ $(lsb_release -sc) main" | tee /etc/apt/sources.list.d/centreon-plugins.list && \ + wget -O- https://apt-key.centreon.com | gpg --dearmor | tee /etc/apt/trusted.gpg.d/centreon.gpg > /dev/null 2>&1 && \ + apt-get clean && rm -rf /var/lib/apt/lists/* \ No newline at end of file diff --git a/.github/workflows/docker-gorgone-testing.yml b/.github/workflows/docker-gorgone-testing.yml index 2854f3e3668..ec61accfefc 100644 --- a/.github/workflows/docker-gorgone-testing.yml +++ b/.github/workflows/docker-gorgone-testing.yml @@ -26,7 +26,7 @@ jobs: strategy: matrix: - distrib: [alma8, bullseye] + distrib: [alma8, bullseye, bookworm] steps: - name: Checkout sources uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index d47ab7a04bd..140fe760280 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -139,7 +139,7 @@ jobs: strategy: fail-fast: false matrix: - distrib: [el8, bullseye] + distrib: [el8, bullseye, bookworm] include: - package_extension: rpm image: gorgone-testing-alma8 @@ -147,6 +147,9 @@ jobs: - package_extension: deb image: gorgone-testing-bullseye distrib: bullseye + - package_extension: deb + image: gorgone-testing-bookworm + distrib: bookworm runs-on: ubuntu-22.04 container: image: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }}/${{ matrix.image }}:${{ needs.get-version.outputs.major_version }} @@ -202,6 +205,7 @@ jobs: shell: bash run: | if [[ "${{ matrix.package_extension }}" == "deb" ]]; then + apt update apt install -y ./centreon-gorgone*${{ matrix.distrib }}* else dnf install -y ./centreon-gorgone*${{ matrix.distrib }}* ./centreon-gorgone-centreon-config*${{ matrix.distrib }}* diff --git a/gorgone/tests/robot/tests/start_stop/start_stop.robot b/gorgone/tests/robot/tests/start_stop/start_stop.robot index e00d4889a6d..acb09d2b8d4 100644 --- a/gorgone/tests/robot/tests/start_stop/start_stop.robot +++ b/gorgone/tests/robot/tests/start_stop/start_stop.robot @@ -10,6 +10,7 @@ ${configfile} ${CURDIR}${/}config.yaml Start and stop gorgone # fichier de conf : pull_central + autodiscovery # start gorgone 2 + # stop gorgone 2 FOR ${i} IN RANGE 5 ${gorgone_name}= Set Variable gorgone_start_stop${i} Setup Gorgone Config ${configfile} gorgone_name=${gorgone_name} From f932b215312059d414669a7242c2a7c6cd81fc09 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Mon, 15 Jul 2024 13:52:06 +0200 Subject: [PATCH 882/948] Mon 72016 initialize the test for alma9 (#1526) * Empty-Commit * Add testing docker image for alma9 * changing the path to a valid one * mod:Add Remi repository for additional packages * mod:Add Remi repository for additional packages * test * missing dependencies * test * removing line 13 * remove clean all l 9 * mod * mod * mod * update docker gorgone testing workflow --------- Co-authored-by: Paul LOUIS THERESE <53221698+paloth@users.noreply.github.com> Co-authored-by: pkippes <144150042+pkippes@users.noreply.github.com> Co-authored-by: tuntoja <tuntoja@centreon.com> Co-authored-by: root <sfarouq-ext@centreon.com> Co-authored-by: sfarouq-ext <116093375+sfarouq-ext@users.noreply.github.com> --- .github/docker/Dockerfile.gorgone-testing-alma9 | 14 ++++++++++++++ .github/workflows/docker-gorgone-testing.yml | 2 +- .github/workflows/gorgone.yml | 5 ++++- 3 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 .github/docker/Dockerfile.gorgone-testing-alma9 diff --git a/.github/docker/Dockerfile.gorgone-testing-alma9 b/.github/docker/Dockerfile.gorgone-testing-alma9 new file mode 100644 index 00000000000..39aa41ee11b --- /dev/null +++ b/.github/docker/Dockerfile.gorgone-testing-alma9 @@ -0,0 +1,14 @@ +FROM almalinux:9 + +RUN bash -e <<EOF + +dnf install -y dnf-plugins-core zstd mariadb iproute epel-release +dnf config-manager --set-enabled crb +dnf config-manager --add-repo https://packages.centreon.com/rpm-standard/23.10/el9/centreon-23.10.repo +dnf install -y python3.11 python3.11-pip +pip3.11 install robotframework robotframework-examples robotframework-databaselibrary pymysql robotframework-requests + + +dnf clean all + +EOF diff --git a/.github/workflows/docker-gorgone-testing.yml b/.github/workflows/docker-gorgone-testing.yml index ec61accfefc..7ae05fbb19d 100644 --- a/.github/workflows/docker-gorgone-testing.yml +++ b/.github/workflows/docker-gorgone-testing.yml @@ -26,7 +26,7 @@ jobs: strategy: matrix: - distrib: [alma8, bullseye, bookworm] + distrib: [alma8, alma9, bullseye, bookworm] steps: - name: Checkout sources uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 140fe760280..df51365078e 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -139,11 +139,14 @@ jobs: strategy: fail-fast: false matrix: - distrib: [el8, bullseye, bookworm] + distrib: [el8, el9, bullseye, bookworm] include: - package_extension: rpm image: gorgone-testing-alma8 distrib: el8 + - package_extension: rpm + image: gorgone-testing-alma9 + distrib: el9 - package_extension: deb image: gorgone-testing-bullseye distrib: bullseye From e778770573b9125a0a99ce87ac4a2b893666130b Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Mon, 15 Jul 2024 14:09:38 +0200 Subject: [PATCH 883/948] enh(tests): Initialize gorgone test environement on Ubuntu (#1523) * Empty-Commit * env test ubuntu jammy * removing some lines * changing some lines * to run gorgone test * test * update gorgone workflow * update docker gorgone testing workflow * removing an empty space * removing an empty space * removing an empty space * removing an empty space * adding an empty space * restore actionlint.yml --------- Co-authored-by: Paul LOUIS THERESE <53221698+paloth@users.noreply.github.com> Co-authored-by: pkippes <144150042+pkippes@users.noreply.github.com> Co-authored-by: tuntoja <tuntoja@centreon.com> Co-authored-by: root <sfarouq-ext@centreon.com> Co-authored-by: sfarouq-ext <116093375+sfarouq-ext@users.noreply.github.com> --- .../docker/Dockerfile.gorgone-testing-jammy | 40 +++++++++++++++++++ .github/workflows/docker-gorgone-testing.yml | 2 +- .github/workflows/gorgone.yml | 6 ++- 3 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 .github/docker/Dockerfile.gorgone-testing-jammy diff --git a/.github/docker/Dockerfile.gorgone-testing-jammy b/.github/docker/Dockerfile.gorgone-testing-jammy new file mode 100644 index 00000000000..2263dce9710 --- /dev/null +++ b/.github/docker/Dockerfile.gorgone-testing-jammy @@ -0,0 +1,40 @@ +FROM ubuntu:jammy + +ENV DEBIAN_FRONTEND=noninteractive + +# Set locale +RUN apt-get update && \ + apt-get install -y locales libcurl4-openssl-dev curl wget zstd jq lsb-release mariadb-client iproute2 && \ + apt-get install -y ca-certificates apt-transport-https software-properties-common gnupg2 && \ + localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 + +ENV LANG=en_US.UTF-8 + +# Add Centreon repositories and their public key +RUN echo "deb https://packages.centreon.com/ubuntu-standard-24.05-testing/ jammy main" | tee -a /etc/apt/sources.list.d/centreon-testing.list && \ + echo "deb https://packages.centreon.com/ubuntu-plugins-testing/ jammy main" | tee -a /etc/apt/sources.list.d/centreon-plugins-testing.list && \ + wget -O- https://apt-key.centreon.com | gpg --dearmor | tee /etc/apt/trusted.gpg.d/centreon.gpg > /dev/null 2>&1 && \ + apt-get update + +# Install required packages and Robotframework +RUN apt-get update && \ + apt-get install -y \ + python3 \ + python3-dev \ + python3-pip \ + python3-venv + +# Create a virtual environment and install Robot Framework +RUN python3 -m venv /opt/robotframework-env && \ + /opt/robotframework-env/bin/pip install --no-cache-dir \ + robotframework \ + robotframework-examples \ + robotframework-databaselibrary \ + robotframework-requests \ + pymysql + +# Clean up +RUN apt-get clean && rm -rf /var/lib/apt/lists/* + +# Set the PATH to include the virtual environment +ENV PATH="/opt/robotframework-env/bin:$PATH" diff --git a/.github/workflows/docker-gorgone-testing.yml b/.github/workflows/docker-gorgone-testing.yml index 7ae05fbb19d..394e3f8340b 100644 --- a/.github/workflows/docker-gorgone-testing.yml +++ b/.github/workflows/docker-gorgone-testing.yml @@ -26,7 +26,7 @@ jobs: strategy: matrix: - distrib: [alma8, alma9, bullseye, bookworm] + distrib: [alma8, alma9, bullseye, bookworm, jammy] steps: - name: Checkout sources uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index df51365078e..b3487628d59 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -139,7 +139,7 @@ jobs: strategy: fail-fast: false matrix: - distrib: [el8, el9, bullseye, bookworm] + distrib: [el8, el9, bullseye, bookworm, jammy] include: - package_extension: rpm image: gorgone-testing-alma8 @@ -150,9 +150,13 @@ jobs: - package_extension: deb image: gorgone-testing-bullseye distrib: bullseye + - package_extension: deb + image: gorgone-testing-jammy + distrib: jammy - package_extension: deb image: gorgone-testing-bookworm distrib: bookworm + runs-on: ubuntu-22.04 container: image: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }}/${{ matrix.image }}:${{ needs.get-version.outputs.major_version }} From 804b2df40481feeb6bfe092be78e21a4624cddef Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Tue, 16 Jul 2024 09:25:15 +0200 Subject: [PATCH 884/948] Mon 125335 gorgone tests use a hash based tag of the configuration for every docker image migrate (#1521) Refs:MON-125335 --- .github/workflows/docker-gorgone-testing.yml | 4 +++- .github/workflows/get-version.yml | 7 +++++++ .github/workflows/gorgone.yml | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-gorgone-testing.yml b/.github/workflows/docker-gorgone-testing.yml index 394e3f8340b..bd25dfb534f 100644 --- a/.github/workflows/docker-gorgone-testing.yml +++ b/.github/workflows/docker-gorgone-testing.yml @@ -12,9 +12,11 @@ on: - dev-[2-9][0-9].[0-9][0-9].x paths: - ".github/docker/Dockerfile.gorgone-testing-*" + - ".github/workflows/docker-gorgone-testing.yml" pull_request: paths: - ".github/docker/Dockerfile.gorgone-testing-*" + - ".github/workflows/docker-gorgone-testing.yml" jobs: get-version: @@ -46,4 +48,4 @@ jobs: context: . pull: true push: true - tags: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }}/gorgone-testing-${{ matrix.distrib }}:${{ needs.get-version.outputs.major_version }} + tags: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }}/gorgone-testing-${{ matrix.distrib }}:${{ needs.get-version.outputs.gorgone_docker_version }} diff --git a/.github/workflows/get-version.yml b/.github/workflows/get-version.yml index 43fe25d2fe7..06ae6a876a0 100644 --- a/.github/workflows/get-version.yml +++ b/.github/workflows/get-version.yml @@ -36,6 +36,9 @@ on: release_cloud: description: "context of release (cloud or not cloud)" value: ${{ jobs.get-version.outputs.release_cloud }} + gorgone_docker_version: + description: "md5 of gorgone dockerfile" + value: ${{ jobs.get-version.outputs.gorgone_docker_version }} jobs: get-version: @@ -51,6 +54,7 @@ jobs: environment: ${{ steps.get_version.outputs.env }} release_type: ${{ steps.get_version.outputs.release_type }} release_cloud: ${{ steps.get_version.outputs.release_cloud}} + gorgone_docker_version: ${{ steps.get_version.outputs.gorgone_docker_version }} steps: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 @@ -100,6 +104,9 @@ jobs: exit 1 fi + GORGONE_DOCKER_VERSION=$(cat .github/docker/Dockerfile.gorgone-testing-* | md5sum | cut -c1-8) + echo "gorgone_docker_version=$GORGONE_DOCKER_VERSION" >> $GITHUB_OUTPUT + IMG_VERSION=$( cat `ls .github/docker/Dockerfile.centreon-collect-* | grep -v test` vcpkg.json | md5sum | awk '{print substr($1, 0, 8)}') TEST_IMG_VERSION=$(cat .github/docker/Dockerfile.centreon-collect-*-test .github/scripts/collect-prepare-test-robot.sh resources/*.sql | md5sum | cut -c1-8) echo "img_version=$IMG_VERSION" >> $GITHUB_OUTPUT diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index b3487628d59..b786da6db34 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -159,7 +159,7 @@ jobs: runs-on: ubuntu-22.04 container: - image: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }}/${{ matrix.image }}:${{ needs.get-version.outputs.major_version }} + image: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }}/${{ matrix.image }}:${{ needs.get-version.outputs.gorgone_docker_version }} credentials: username: ${{ secrets.DOCKER_REGISTRY_ID }} password: ${{ secrets.DOCKER_REGISTRY_PASSWD }} From 710f900f82b85f1335d0b79ceaf76ad800cc20ea Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Tue, 16 Jul 2024 09:28:19 +0200 Subject: [PATCH 885/948] test(gorgone): add automated test for pull communication mode (#1520) --- .../robot/config/pull_central_config.yaml | 34 +++++++++++++++++++ .../config/pull_node_register_one_node.yaml | 4 +++ .../robot/config/pull_poller_config.yaml | 24 +++++++++++++ .../tests/robot/resources/resources.resource | 14 ++++++-- gorgone/tests/robot/tests/core/pull.robot | 16 +++++++++ gorgone/tests/robot/tests/core/pullwss.robot | 2 +- 6 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 gorgone/tests/robot/config/pull_central_config.yaml create mode 100644 gorgone/tests/robot/config/pull_node_register_one_node.yaml create mode 100644 gorgone/tests/robot/config/pull_poller_config.yaml create mode 100644 gorgone/tests/robot/tests/core/pull.robot diff --git a/gorgone/tests/robot/config/pull_central_config.yaml b/gorgone/tests/robot/config/pull_central_config.yaml new file mode 100644 index 00000000000..f7f5fcfc2df --- /dev/null +++ b/gorgone/tests/robot/config/pull_central_config.yaml @@ -0,0 +1,34 @@ +gorgone: + gorgonecore: + external_com_type: tcp + external_com_path: "*:5556" + authorized_clients: + - key: @KEYTHUMBPRINT@ + id: 1 + + modules: + - name: register + package: "gorgone::modules::core::register::hooks" + enable: true + config_file: /etc/centreon-gorgone/@UNIQ_ID_FROM_ROBOT_TESTING_CONFIG_FILE@/config.d/pull_node_register_one_node.yaml + + - name: proxy + package: "gorgone::modules::core::proxy::hooks" + enable: true + + - name: nodes + package: "gorgone::modules::centreon::nodes::hooks" + enable: true + + - name: httpserver + package: "gorgone::modules::core::httpserver::hooks" + enable: true + address: "0.0.0.0" + port: "8085" + ssl: false + auth: + enabled: false + allowed_hosts: + enabled: true + subnets: + - 127.0.0.1/32 diff --git a/gorgone/tests/robot/config/pull_node_register_one_node.yaml b/gorgone/tests/robot/config/pull_node_register_one_node.yaml new file mode 100644 index 00000000000..a8ae0f462d9 --- /dev/null +++ b/gorgone/tests/robot/config/pull_node_register_one_node.yaml @@ -0,0 +1,4 @@ +nodes: + - id: 2 + type: pull + prevail: 1 diff --git a/gorgone/tests/robot/config/pull_poller_config.yaml b/gorgone/tests/robot/config/pull_poller_config.yaml new file mode 100644 index 00000000000..f57d766203c --- /dev/null +++ b/gorgone/tests/robot/config/pull_poller_config.yaml @@ -0,0 +1,24 @@ +name: distant-server +description: Configuration for distant server +gorgone: + gorgonecore: + id: 2 + privkey: "/var/lib/centreon-gorgone/.keys/rsakey.priv.pem" + pubkey: "/var/lib/centreon-gorgone/.keys/rsakey.pub.pem" + + modules: + - name: action + package: gorgone::modules::core::action::hooks + enable: true + + - name: engine + package: gorgone::modules::centreon::engine::hooks + enable: true + command_file: "/var/lib/centreon-engine/rw/centengine.cmd" + + - name: pull + package: "gorgone::modules::core::pull::hooks" + enable: true + target_type: tcp + target_path: 127.0.0.1:5556 + ping: 1 diff --git a/gorgone/tests/robot/resources/resources.resource b/gorgone/tests/robot/resources/resources.resource index 3d304ea9292..748a9d30246 100644 --- a/gorgone/tests/robot/resources/resources.resource +++ b/gorgone/tests/robot/resources/resources.resource @@ -3,6 +3,7 @@ Documentation Centreon Gorgone library for Robot Framework Library Process Library RequestsLibrary +Library OperatingSystem *** Variables *** ${gorgone_binary} /usr/bin/gorgoned @@ -102,6 +103,9 @@ Check Poller Communicate Sleep 5 ${response}= GET http://127.0.0.1:8085/api/internal/constatus Log ${response.json()} + IF not ${response.json()}[data] + CONTINUE + END IF ${response.json()}[data][${poller_id}][ping_failed] > 0 or ${response.json()}[data][${poller_id}][ping_ok] > 0 BREAK END @@ -135,9 +139,15 @@ Setup Two Gorgone Instances Start Gorgone debug ${poller_name} Check Poller Is Connected port=8086 expected_nb=2 Check Poller Communicate 2 + ELSE IF '${communication_mode}' == 'pull' + Setup Gorgone Config ${pull_central_config} ${ROOT_CONFIG}pull_node_register_one_node.yaml ${gorgone_core_config} gorgone_name=${central_name} sql_file=${ROOT_CONFIG}push_db_1_poller.sql + Setup Gorgone Config ${gorgone_core_config} ${pull_poller_config} gorgone_name=${poller_name} - ELSE - Fail pull mode is not yet implemented. + Start Gorgone debug ${central_name} + Wait Until Port Is Bind 5556 + Start Gorgone debug ${poller_name} + Check Poller Is Connected port=5556 expected_nb=2 + Check Poller Communicate 2 END Wait Until Port Is Bind diff --git a/gorgone/tests/robot/tests/core/pull.robot b/gorgone/tests/robot/tests/core/pull.robot new file mode 100644 index 00000000000..ed4c6c6abd3 --- /dev/null +++ b/gorgone/tests/robot/tests/core/pull.robot @@ -0,0 +1,16 @@ +*** Settings *** +Documentation Start and stop Gorgone with pull configuration + +Resource ${CURDIR}${/}..${/}..${/}resources${/}import.resource +Test Timeout 300s + +*** Variables *** +@{process_list} pull_gorgone_central pull_gorgone_poller_2 + +*** Test Cases *** +connect 1 poller to a central with pull configuration + [Teardown] Stop Gorgone And Remove Gorgone Config @{process_list} sql_file=${ROOT_CONFIG}push_db_1_poller_delete.sql + + Log To Console \nStarting the Gorgone setup with pull configuration + Setup Two Gorgone Instances communication_mode=pull central_name=pull_gorgone_central poller_name=pull_gorgone_poller_2 + Log To Console End of tests. diff --git a/gorgone/tests/robot/tests/core/pullwss.robot b/gorgone/tests/robot/tests/core/pullwss.robot index f678703e770..9bc9f774c0a 100644 --- a/gorgone/tests/robot/tests/core/pullwss.robot +++ b/gorgone/tests/robot/tests/core/pullwss.robot @@ -9,7 +9,7 @@ Test Timeout 220s *** Test Cases *** check one poller can connect to a central gorgone - [Teardown] Stop Gorgone And Remove Gorgone Config @{process_list} #sql_file=${ROOT_CONFIG}push_db_1_poller_delete.sql + [Teardown] Stop Gorgone And Remove Gorgone Config @{process_list} sql_file=${ROOT_CONFIG}push_db_1_poller_delete.sql Log To Console \nStarting the gorgone setup Setup Two Gorgone Instances communication_mode=pullwss central_name=pullwss_gorgone_central poller_name=pullwss_gorgone_poller_2 From 299de12dd87b8cff1731d0a29ff8bda3d5ca5b2e Mon Sep 17 00:00:00 2001 From: May <110405507+mushroomempires@users.noreply.github.com> Date: Tue, 16 Jul 2024 14:11:27 +0200 Subject: [PATCH 886/948] fix(ci): prevent nightly from running redundantly (#1514) --- .github/workflows/robot-nightly.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/robot-nightly.yml b/.github/workflows/robot-nightly.yml index dd10ad0242c..f0cf45c751b 100644 --- a/.github/workflows/robot-nightly.yml +++ b/.github/workflows/robot-nightly.yml @@ -8,7 +8,7 @@ concurrency: on: workflow_dispatch: schedule: - - cron: '30 0 * * *' + - cron: '30 0 * * 2-6' jobs: dispatch-to-maintained-branches: From 6258e6d9bf833a5d31c04755b9612b5ce5d2c3dc Mon Sep 17 00:00:00 2001 From: Evan-Adam <152897682+Evan-Adam@users.noreply.github.com> Date: Tue, 16 Jul 2024 17:02:03 +0200 Subject: [PATCH 887/948] MON-48766-community-pr-fix-file-descriptors-leak-in-gorgone-that-occur-when-pollers-are-disconnected (#1512) fix(gorgone): fd leak TODO : we need to change the gha to launch this test only on develop, and if possible execute it nighty Co-authored-by: garnier-quentin <qgarnier@ose-consulting.com> Co-authored-by: qgarnier <garnier.quentin@gmail.com> --- .../docker/Dockerfile.gorgone-testing-alma8 | 4 +- .../Dockerfile.gorgone-testing-bullseye | 4 +- gorgone/gorgone/class/clientzmq.pm | 72 +++++++++++-------- gorgone/gorgone/modules/core/proxy/class.pm | 8 ++- .../gorgone/modules/core/proxy/sshclient.pm | 2 + ...sh_db_1_poller.sql => db_add_1_poller.sql} | 0 ...poller_delete.sql => db_delete_poller.sql} | 0 .../tests/robot/resources/resources.resource | 6 +- gorgone/tests/robot/tests/core/pull.robot | 2 +- gorgone/tests/robot/tests/core/pullwss.robot | 2 +- gorgone/tests/robot/tests/core/push.robot | 2 +- .../robot/tests/long_tests/fd-leak.robot | 33 +++++++++ 12 files changed, 95 insertions(+), 40 deletions(-) rename gorgone/tests/robot/config/{push_db_1_poller.sql => db_add_1_poller.sql} (100%) rename gorgone/tests/robot/config/{push_db_1_poller_delete.sql => db_delete_poller.sql} (100%) create mode 100644 gorgone/tests/robot/tests/long_tests/fd-leak.robot diff --git a/.github/docker/Dockerfile.gorgone-testing-alma8 b/.github/docker/Dockerfile.gorgone-testing-alma8 index 618bb6cd582..7fe2db43131 100644 --- a/.github/docker/Dockerfile.gorgone-testing-alma8 +++ b/.github/docker/Dockerfile.gorgone-testing-alma8 @@ -2,13 +2,13 @@ FROM almalinux:8 RUN bash -e <<EOF -dnf install -y dnf-plugins-core zstd curl mariadb iproute +dnf install -y dnf-plugins-core zstd curl mariadb iproute procps lsof dnf install -y https://rpms.remirepo.net/enterprise/remi-release-8.rpm dnf config-manager --set-enabled 'powertools' dnf -y config-manager --add-repo https://packages.centreon.com/rpm-standard/23.10/el8/centreon-23.10.repo dnf -y clean all --enablerepo=* dnf install -y python3.11 python3.11-pip -pip3.11 install robotframework robotframework-examples robotframework-databaselibrary pymysql robotframework-requests +pip3.11 install robotframework robotframework-examples robotframework-databaselibrary pymysql robotframework-requests robotframework-jsonlibrary dnf clean all diff --git a/.github/docker/Dockerfile.gorgone-testing-bullseye b/.github/docker/Dockerfile.gorgone-testing-bullseye index 5799ac451bd..80d88c7a831 100644 --- a/.github/docker/Dockerfile.gorgone-testing-bullseye +++ b/.github/docker/Dockerfile.gorgone-testing-bullseye @@ -18,8 +18,8 @@ ENV LANG en_US.utf8 RUN bash -e <<EOF apt-get update # Install Robotframework -apt-get install -y python3 python3-dev python3-pip ca-certificates apt-transport-https software-properties-common wget gnupg2 -pip3 install robotframework robotframework-examples robotframework-databaselibrary pymysql robotframework-requests +apt-get install -y python3 python3-dev python3-pip ca-certificates apt-transport-https software-properties-common wget gnupg2 procps lsof +pip3 install robotframework robotframework-examples robotframework-databaselibrary pymysql robotframework-requests robotframework-jsonlibrary echo "deb https://packages.centreon.com/apt-standard-23.10-stable/ $(lsb_release -sc) main" | tee /etc/apt/sources.list.d/centreon.list echo "deb https://packages.centreon.com/apt-plugins-stable/ $(lsb_release -sc) main" | tee /etc/apt/sources.list.d/centreon-plugins.list diff --git a/gorgone/gorgone/class/clientzmq.pm b/gorgone/gorgone/class/clientzmq.pm index a293c8695c5..9c34f5bed97 100644 --- a/gorgone/gorgone/class/clientzmq.pm +++ b/gorgone/gorgone/class/clientzmq.pm @@ -109,11 +109,20 @@ sub init { $callbacks->{ $self->{identity} } = $options{callback} if (defined($options{callback})); } +sub cleanup { + my ($self, %options) = @_; + + delete $callbacks->{ $self->{identity} }; + delete $connectors->{ $self->{identity} }; + delete $sockets->{ $self->{identity} }; +} + sub close { my ($self, %options) = @_; + $sockets->{ $self->{identity} }->close() if (defined($sockets->{ $self->{identity} })); + $self->{core_watcher}->stop() if (defined($self->{core_watcher})); delete $self->{core_watcher}; - $sockets->{ $self->{identity} }->close(); } sub get_connect_identity { @@ -128,14 +137,19 @@ sub get_server_pubkey { $sockets->{ $self->{identity} }->send('[GETPUBKEY]', ZMQ_DONTWAIT); $self->event(identity => $self->{identity}); - my $w1 = $self->{connect_loop}->timer( + my $w1 = $self->{connect_loop}->io( + $sockets->{ $self->{identity} }->get_fd(), + EV::READ, + sub { + $self->event(identity => $self->{identity}); + } + ); + my $w2 = $self->{connect_loop}->timer( 10, 0, - sub { - $self->{connect_loop}->break(); - } + sub {} ); - $self->{connect_loop}->run(); + $self->{connect_loop}->run(EV::RUN_ONCE); } sub read_key_protocol { @@ -278,9 +292,9 @@ sub ping { time() - $self->{ping_timeout_time} > $self->{ping_timeout}) { $self->{logger}->writeLogError("[clientzmq] No ping response") if (defined($self->{logger})); $self->{ping_progress} = 0; - $sockets->{ $self->{identity} }->close(); - delete $self->{core_watcher}; - + $self->close(); + # new identity for a new handshake (for module pull) + $self->{extra_identity} = gorgone::standard::library::generate_token(length => 12); $self->init(); $status = 1; } @@ -305,25 +319,23 @@ sub event { $connectors->{ $options{identity} }->{ping_time} = time(); while ($sockets->{ $options{identity} }->has_pollin()) { + my ($rv, $message) = gorgone::standard::library::zmq_dealer_read_message(socket => $sockets->{ $options{identity} }); + next if ($connectors->{ $options{identity} }->{handshake} == -1); + next if ($rv); + # We have a response. So it's ok :) if ($connectors->{ $options{identity} }->{ping_progress} == 1) { $connectors->{ $options{identity} }->{ping_progress} = 0; } - my ($rv, $message) = gorgone::standard::library::zmq_dealer_read_message(socket => $sockets->{ $options{identity} }); - last if ($rv); - # in progress if ($connectors->{ $options{identity} }->{handshake} == 0) { - $self->{connect_loop}->break(); $connectors->{ $options{identity} }->{handshake} = 1; if ($connectors->{ $options{identity} }->check_server_pubkey(message => $message) == 0) { $connectors->{ $options{identity} }->{handshake} = -1; } } elsif ($connectors->{ $options{identity} }->{handshake} == 1) { - $self->{connect_loop}->break(); - $self->{logger}->writeLogDebug("[clientzmq] $self->{identity} - client_get_secret recv [3]"); my ($status, $verbose, $symkey, $hostname) = $connectors->{ $options{identity} }->client_get_secret( message => $message @@ -332,7 +344,7 @@ sub event { $self->{logger}->writeLogDebug("[clientzmq] $self->{identity} - client_get_secret $verbose [3]"); $connectors->{ $options{identity} }->{handshake} = -1; $connectors->{ $options{identity} }->{verbose_last_message} = $verbose; - return ; + next; } $connectors->{ $options{identity} }->{handshake} = 2; if (defined($connectors->{ $options{identity} }->{logger})) { @@ -348,7 +360,7 @@ sub event { if ($rv == -1 || $data !~ /^\[([a-zA-Z0-9:\-_]+?)\]\s+/) { $connectors->{ $options{identity} }->{handshake} = -1; $connectors->{ $options{identity} }->{verbose_last_message} = 'decrypt issue: ' . $data; - return ; + next; } if ($1 eq 'KEY') { @@ -389,14 +401,7 @@ sub send_message { my ($self, %options) = @_; if ($self->{handshake} == 0) { - $self->{connect_loop} = new EV::Loop(); - $self->{connect_watcher} = $self->{connect_loop}->io( - $sockets->{ $self->{identity} }->get_fd(), - EV::READ, - sub { - $self->event(identity => $self->{identity}); - } - ); + $self->{connect_loop} = EV::Loop->new(); if (!defined($self->{server_pubkey})) { $self->{logger}->writeLogDebug("[clientzmq] $self->{identity} - get_server_pubkey sent [1]"); @@ -424,15 +429,24 @@ sub send_message { $sockets->{ $self->{identity} }->send($ciphertext, ZMQ_DONTWAIT); $self->event(identity => $self->{identity}); - my $w1 = $self->{connect_loop}->timer( + my $w1 = $self->{connect_loop}->io( + $sockets->{ $self->{identity} }->get_fd(), + EV::READ, + sub { + $self->event(identity => $self->{identity}); + } + ); + my $w2 = $self->{connect_loop}->timer( 10, 0, - sub { $self->{connect_loop}->break(); } + sub {} ); - $self->{connect_loop}->run(); + $self->{connect_loop}->run(EV::RUN_ONCE); } - undef $self->{connect_loop} if (defined($self->{connect_loop})); + if (defined($self->{connect_loop})) { + delete $self->{connect_loop}; + } if ($self->{handshake} < 2) { $self->{handshake} = 0; diff --git a/gorgone/gorgone/modules/core/proxy/class.pm b/gorgone/gorgone/modules/core/proxy/class.pm index de525cd98e5..3798142ec9a 100644 --- a/gorgone/gorgone/modules/core/proxy/class.pm +++ b/gorgone/gorgone/modules/core/proxy/class.pm @@ -230,6 +230,7 @@ sub action_proxyaddnode { }); $self->{clients}->{ $data->{id} }->{class}->close(); + $self->{clients}->{ $data->{id} }->{class}->cleanup(); } else { $self->{internal_channels}->{ $data->{id} } = gorgone::standard::library::connect_com( context => $self->{zmq_context}, @@ -282,6 +283,7 @@ sub action_proxycloseconnection { $self->{logger}->writeLogInfo("[proxy] Close connectionn for $data->{id}"); $self->{clients}->{ $data->{id} }->{class}->close(); + $self->{clients}->{ $data->{id} }->{class}->cleanup(); $self->{clients}->{ $data->{id} }->{delete} = 0; $self->{clients}->{ $data->{id} }->{class} = undef; } @@ -293,6 +295,7 @@ sub close_connections { if (defined($self->{clients}->{$_}->{class}) && $self->{clients}->{$_}->{type} eq 'push_zmq') { $self->{logger}->writeLogInfo("[proxy] Close connection for $_"); $self->{clients}->{$_}->{class}->close(); + $self->{clients}->{$_}->{class}->cleanup(); } } } @@ -497,7 +500,10 @@ sub periodic_exec { token => $connector->generate_token(), target => '' }); - $connector->{clients}->{$_}->{class}->close() if (defined($connector->{clients}->{$_}->{class})); + if (defined($connector->{clients}->{$_}->{class})) { + $connector->{clients}->{$_}->{class}->close(); + $connector->{clients}->{$_}->{class}->cleanup(); + } $connector->{clients}->{$_}->{class} = undef; $connector->{clients}->{$_}->{delete} = 0; $connector->{clients}->{$_}->{com_read_internal} = 0; diff --git a/gorgone/gorgone/modules/core/proxy/sshclient.pm b/gorgone/gorgone/modules/core/proxy/sshclient.pm index d0f40303e12..af81969bee0 100644 --- a/gorgone/gorgone/modules/core/proxy/sshclient.pm +++ b/gorgone/gorgone/modules/core/proxy/sshclient.pm @@ -552,4 +552,6 @@ sub close { $self->disconnect(); } +sub cleanup {} + 1; diff --git a/gorgone/tests/robot/config/push_db_1_poller.sql b/gorgone/tests/robot/config/db_add_1_poller.sql similarity index 100% rename from gorgone/tests/robot/config/push_db_1_poller.sql rename to gorgone/tests/robot/config/db_add_1_poller.sql diff --git a/gorgone/tests/robot/config/push_db_1_poller_delete.sql b/gorgone/tests/robot/config/db_delete_poller.sql similarity index 100% rename from gorgone/tests/robot/config/push_db_1_poller_delete.sql rename to gorgone/tests/robot/config/db_delete_poller.sql diff --git a/gorgone/tests/robot/resources/resources.resource b/gorgone/tests/robot/resources/resources.resource index 748a9d30246..da556e8f01b 100644 --- a/gorgone/tests/robot/resources/resources.resource +++ b/gorgone/tests/robot/resources/resources.resource @@ -122,7 +122,7 @@ Setup Two Gorgone Instances # gorgone can generate it's own key, but as we need the thumbprint in the configuration we need to generate them before launching gorgone. # this script only create key if the files don't exists, and silently finish if the files already exists. IF '${communication_mode}' == 'push_zmq' - Setup Gorgone Config ${push_central_config} ${gorgone_core_config} gorgone_name=${central_name} sql_file=${ROOT_CONFIG}push_db_1_poller.sql + Setup Gorgone Config ${push_central_config} ${gorgone_core_config} gorgone_name=${central_name} sql_file=${ROOT_CONFIG}db_add_1_poller.sql Setup Gorgone Config ${push_poller_config} gorgone_name=${poller_name} Start Gorgone debug ${central_name} @@ -131,7 +131,7 @@ Setup Two Gorgone Instances Check Poller Is Connected port=5556 expected_nb=2 Check Poller Communicate 2 ELSE IF '${communication_mode}' == 'pullwss' - Setup Gorgone Config ${pullwss_central_config} ${ROOT_CONFIG}pullwss_node_register_one_node.yaml ${gorgone_core_config} gorgone_name=${central_name} sql_file=${ROOT_CONFIG}push_db_1_poller.sql + Setup Gorgone Config ${pullwss_central_config} ${ROOT_CONFIG}pullwss_node_register_one_node.yaml ${gorgone_core_config} gorgone_name=${central_name} sql_file=${ROOT_CONFIG}db_add_1_poller.sql Setup Gorgone Config ${gorgone_core_config} ${pullwss_poller_config} gorgone_name=${poller_name} Start Gorgone debug ${central_name} @@ -140,7 +140,7 @@ Setup Two Gorgone Instances Check Poller Is Connected port=8086 expected_nb=2 Check Poller Communicate 2 ELSE IF '${communication_mode}' == 'pull' - Setup Gorgone Config ${pull_central_config} ${ROOT_CONFIG}pull_node_register_one_node.yaml ${gorgone_core_config} gorgone_name=${central_name} sql_file=${ROOT_CONFIG}push_db_1_poller.sql + Setup Gorgone Config ${pull_central_config} ${ROOT_CONFIG}pull_node_register_one_node.yaml ${gorgone_core_config} gorgone_name=${central_name} sql_file=${ROOT_CONFIG}db_add_1_poller.sql Setup Gorgone Config ${gorgone_core_config} ${pull_poller_config} gorgone_name=${poller_name} Start Gorgone debug ${central_name} diff --git a/gorgone/tests/robot/tests/core/pull.robot b/gorgone/tests/robot/tests/core/pull.robot index ed4c6c6abd3..1a81fb40e9c 100644 --- a/gorgone/tests/robot/tests/core/pull.robot +++ b/gorgone/tests/robot/tests/core/pull.robot @@ -9,7 +9,7 @@ Test Timeout 300s *** Test Cases *** connect 1 poller to a central with pull configuration - [Teardown] Stop Gorgone And Remove Gorgone Config @{process_list} sql_file=${ROOT_CONFIG}push_db_1_poller_delete.sql + [Teardown] Stop Gorgone And Remove Gorgone Config @{process_list} sql_file=${ROOT_CONFIG}db_delete_poller.sql Log To Console \nStarting the Gorgone setup with pull configuration Setup Two Gorgone Instances communication_mode=pull central_name=pull_gorgone_central poller_name=pull_gorgone_poller_2 diff --git a/gorgone/tests/robot/tests/core/pullwss.robot b/gorgone/tests/robot/tests/core/pullwss.robot index 9bc9f774c0a..6b059a820d9 100644 --- a/gorgone/tests/robot/tests/core/pullwss.robot +++ b/gorgone/tests/robot/tests/core/pullwss.robot @@ -9,7 +9,7 @@ Test Timeout 220s *** Test Cases *** check one poller can connect to a central gorgone - [Teardown] Stop Gorgone And Remove Gorgone Config @{process_list} sql_file=${ROOT_CONFIG}push_db_1_poller_delete.sql + [Teardown] Stop Gorgone And Remove Gorgone Config @{process_list} sql_file=${ROOT_CONFIG}db_delete_poller.sql Log To Console \nStarting the gorgone setup Setup Two Gorgone Instances communication_mode=pullwss central_name=pullwss_gorgone_central poller_name=pullwss_gorgone_poller_2 diff --git a/gorgone/tests/robot/tests/core/push.robot b/gorgone/tests/robot/tests/core/push.robot index 5ad63f731d9..2b72e39970e 100644 --- a/gorgone/tests/robot/tests/core/push.robot +++ b/gorgone/tests/robot/tests/core/push.robot @@ -9,7 +9,7 @@ Test Timeout 220s *** Test Cases *** connect 1 poller to a central - [Teardown] Stop Gorgone And Remove Gorgone Config @{process_list} sql_file=${ROOT_CONFIG}push_db_1_poller_delete.sql + [Teardown] Stop Gorgone And Remove Gorgone Config @{process_list} sql_file=${ROOT_CONFIG}db_delete_poller.sql Log To Console \nStarting the gorgone setup Setup Two Gorgone Instances communication_mode=push_zmq central_name=push_zmq_gorgone_central poller_name=push_zmq_gorgone_poller_2 diff --git a/gorgone/tests/robot/tests/long_tests/fd-leak.robot b/gorgone/tests/robot/tests/long_tests/fd-leak.robot new file mode 100644 index 00000000000..54f2178787f --- /dev/null +++ b/gorgone/tests/robot/tests/long_tests/fd-leak.robot @@ -0,0 +1,33 @@ +*** Settings *** +Documentation check Gorgone don't leak file descriptor when a poller become unavailable + +Resource ${CURDIR}${/}..${/}..${/}resources${/}import.resource +Test Timeout 1200s + +*** Test Cases *** +check gorgone proxy do not leak file descriptor with a poller + [Tags] long_tests + [Teardown] Stop Gorgone And Remove Gorgone Config push_zmq_gorgone_central sql_file=${ROOT_CONFIG}db_delete_poller.sql + ${cmd_count_file_descriptor}= Set Variable count=0; for pid in \$(ps aux | grep gorgone-proxy | grep -v grep | awk '{ print \$2 }') ; do num=\$(lsof | grep \$pid | wc -l); count=\$((count + \$num)) ; done ; echo \$count + + Log To Console \nStarting the gorgone setup + Setup Two Gorgone Instances communication_mode=push_zmq central_name=push_zmq_gorgone_central poller_name=push_zmq_gorgone_poller_2 + # We wait for gorgone to be ready, and grab all file descriptor it need. + Sleep 10 + ${before_kill_fd_nb} Run ${cmd_count_file_descriptor} + Stop Gorgone And Remove Gorgone Config push_zmq_gorgone_poller_2 + Sleep 10 + # check what is the normal number of file descriptor for gorgone to take + ${initial_fd_nb} Run ${cmd_count_file_descriptor} + Log To Console \n number of file descriptor on before killing poller : ${before_kill_fd_nb} and after : ${initial_fd_nb} \n + ${max}= Evaluate ${initial_fd_nb} + 15 + Log To Console max is ${max} + Sleep 20 + FOR ${i} IN RANGE 60 + ${current_fd_nb} Run ${cmd_count_file_descriptor} + IF ${i} % 10 == 0 + Log To Console exec ${i} \t got ${current_fd_nb} + END + Should Be True ${max} > ${current_fd_nb} gorgone is using more and more file descriptor after a poller disconnect, starting at ${initial_fd_nb} and after ${i} iteration (5 sec each) to ${current_fd_nb} + Sleep 5 + END From 4f70b288bdf715ba27a3d38f05dacad9c437fa66 Mon Sep 17 00:00:00 2001 From: jean-christophe81 <98889244+jean-christophe81@users.noreply.github.com> Date: Wed, 17 Jul 2024 11:33:29 +0200 Subject: [PATCH 888/948] add prebuild and robot test workflows (#1537) --- .../workflows/windows-agent-build-vcpkg.yml | 53 ++++++++++++++ .../workflows/windows-agent-robot-test.yml | 15 ++++ .github/workflows/windows-agent.yml | 72 ++++++++++++++++--- 3 files changed, 132 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/windows-agent-build-vcpkg.yml create mode 100644 .github/workflows/windows-agent-robot-test.yml diff --git a/.github/workflows/windows-agent-build-vcpkg.yml b/.github/workflows/windows-agent-build-vcpkg.yml new file mode 100644 index 00000000000..5fbc829994b --- /dev/null +++ b/.github/workflows/windows-agent-build-vcpkg.yml @@ -0,0 +1,53 @@ +name: Centreon Monitoring Agent Windows packaging + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +on: + workflow_dispatch: + +jobs: + build-vcpkg-and-export: + runs-on: windows-latest + env: + AWS_ACCESS_KEY_ID: ${{ secrets.COLLECT_S3_ACCESS_KEY }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.COLLECT_S3_SECRET_KEY }} + + steps: + - name: Checkout sources + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: install msvc command prompt + uses: ilammy/msvc-dev-cmd@v1.4.1 + + - name: install vcpkg + run: | + git clone --depth 1 https://github.com/microsoft/vcpkg + cd vcpkg + bootstrap-vcpkg.bat + + - name: compile packages + run: | + + [System.Environment]::SetEnvironmentVariable("VCPKG_ROOT",$pwd.ToString()+"\vcpkg") + [System.Environment]::SetEnvironmentVariable("PATH",$pwd.ToString()+"\vcpkg:" + $env:PATH) + cmake.exe --preset=release + + - name: 7zip packages and save to s3 + run: | + $files_to_hash= "vcpkg.json", "custom-triplets\x64-windows.cmake", "CMakeLists.txt", "CMakeListsWindows.txt" + $files_content= Get-Content -Path $files_to_hash -Raw + $stringAsStream = [System.IO.MemoryStream]::new() + $writer = [System.IO.StreamWriter]::new($stringAsStream) + $writer.write($files_content -join " ") + $writer.Flush() + $stringAsStream.Position = 0 + $vcpkg_hash=Get-FileHash -InputStream $stringAsStream -Algorithm SHA256 | Select-Object Hash + $file_name = "centreon-agent-prebuild-" + $vcpkg_hash.Hash + $file_name_extension = "${file_name}.7z" + Write-Host "store vcpkg package to $file_name" + 7z a $file_name_extension vcpkg + Write-Host "aws s3 cp $file_name_extension s3://centreon-collect-robot-report/$file_name_extension" + [System.Environment]::SetEnvironmentVariable("AWS_EC2_METADATA_DISABLED","true") + aws s3 cp $file_name_extension s3://centreon-collect-robot-report/$file_name_extension diff --git a/.github/workflows/windows-agent-robot-test.yml b/.github/workflows/windows-agent-robot-test.yml new file mode 100644 index 00000000000..8d52099a14e --- /dev/null +++ b/.github/workflows/windows-agent-robot-test.yml @@ -0,0 +1,15 @@ +name: Centreon Monitoring Agent Windows robot test + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +on: + workflow_dispatch: + +jobs: + build-agent: + runs-on: windows-latest + steps: + - name: Checkout sources + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 diff --git a/.github/workflows/windows-agent.yml b/.github/workflows/windows-agent.yml index c1bbf3bb7e4..b017560bcc5 100644 --- a/.github/workflows/windows-agent.yml +++ b/.github/workflows/windows-agent.yml @@ -1,4 +1,4 @@ -name: Centreon Monitoring Agent Windows packaging +name: Centreon Monitoring Agent Windows build and packaging concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} @@ -8,14 +8,70 @@ on: workflow_dispatch: jobs: - build: + build-agent: runs-on: windows-latest + env: + AWS_ACCESS_KEY_ID: ${{ secrets.COLLECT_S3_ACCESS_KEY }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.COLLECT_S3_SECRET_KEY }} + steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - uses: ilammy/msvc-dev-cmd@v1.4.1 + - name: Checkout sources + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: install msvc command prompt + uses: ilammy/msvc-dev-cmd@v1.4.1 + + - name: get built packages and build agent + run: | + $files_to_hash= "vcpkg.json", "custom-triplets\x64-windows.cmake", "CMakeLists.txt", "CMakeListsWindows.txt" + $files_content= Get-Content -Path $files_to_hash -Raw + $stringAsStream = [System.IO.MemoryStream]::new() + $writer = [System.IO.StreamWriter]::new($stringAsStream) + $writer.write($files_content -join " ") + $writer.Flush() + $stringAsStream.Position = 0 + $vcpkg_hash=Get-FileHash -InputStream $stringAsStream -Algorithm SHA256 | Select-Object Hash + $file_name = "centreon-agent-prebuild-" + $vcpkg_hash.Hash + $file_name_extension = "${file_name}.7z" + [System.Environment]::SetEnvironmentVariable("AWS_EC2_METADATA_DISABLED","true") + Write-Host "get $file_name_extension from s3" + aws --quiet s3 cp s3://centreon-collect-robot-report/$file_name_extension $file_name_extension + Write-Host "unzip $file_name_extension" + 7z x $file_name_extension + + [System.Environment]::SetEnvironmentVariable("VCPKG_ROOT",$pwd.Path + "\vcpkg") + [System.Environment]::SetEnvironmentVariable("PATH",$pwd.Path + "\vcpkg:" + $env:Path) + + Write-Host "create cmake files" + cmake --preset=release-ci + Write-Host "build agent and tests" + cmake --build build_windows - - name: install vcpkg + - name: common test run: | - git clone --depth 1 https://github.com/microsoft/vcpkg $HOME/vcpkg - cd $HOME/vcpkg - bootstrap-vcpkg.bat + cd build_windows + tests/ut_common + + - name: agent test + run: | + cd build_windows + tests/ut_agent + + - name: zip agent + run: | + $files_to_compress = ".\agent\conf\centagent.reg", "build_windows\agent\centagent.exe" + Compress-Archive -Path $files_to_compress -DestinationPath centreon-monitoring-agent.zip + + - name: save agent package in cache + uses: actions/cache/save@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 + with: + path: centreon-monitoring-agent.zip + key: ${{ github.run_id }}-${{ github.sha }}-CMA-${{ github.head_ref || github.ref_name }} + + - name: Upload package artifacts + if: ${{ false }} + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + with: + name: packages-centreon-monitoring-agent-windows + path: centreon-monitoring-agent.zip + retention-days: 1 From 2516e38d17cf2056b7798a5423c87eec30eeb3fd Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Wed, 17 Jul 2024 13:57:24 +0200 Subject: [PATCH 889/948] fix(gorgone): correctly handle gorgone pullwss module shutdown (#1530) Zmq object need to be undef at the end of the module life or a C stack trace will appear in the logs. The sigterm handler was not called by perl, Mojo::IOLoop::Signal allow to correctly call the signal handler when quiting. Refs:MON-34540 --- gorgone/gorgone/modules/core/pullwss/class.pm | 61 +++++++++++-------- gorgone/gorgone/standard/library.pm | 2 +- gorgone/packaging/centreon-gorgone.yaml | 2 + .../tests/robot/resources/resources.resource | 18 ++++-- gorgone/tests/robot/tests/core/pullwss.robot | 14 ++++- 5 files changed, 65 insertions(+), 32 deletions(-) diff --git a/gorgone/gorgone/modules/core/pullwss/class.pm b/gorgone/gorgone/modules/core/pullwss/class.pm index 5745dd21d5b..80cee8b3243 100644 --- a/gorgone/gorgone/modules/core/pullwss/class.pm +++ b/gorgone/gorgone/modules/core/pullwss/class.pm @@ -28,6 +28,7 @@ use gorgone::standard::library; use gorgone::standard::constants qw(:all); use gorgone::standard::misc; use Mojo::UserAgent; +use Mojo::IOLoop::Signal; use IO::Socket::SSL; use IO::Handle; use JSON::XS; @@ -44,6 +45,7 @@ sub new { $connector->{ping_timer} = -1; $connector->{connected} = 0; + $connector->{stop} = 0; $connector->set_signal_handlers(); return $connector; @@ -51,11 +53,11 @@ sub new { sub set_signal_handlers { my $self = shift; + # see https://metacpan.org/pod/EV#PERL-SIGNALS + # EV and Mojo::IOLoop don't seem to work in this module for setting a signal handler. + Mojo::IOLoop::Signal->on(TERM => sub { $self->handle_TERM() }); + Mojo::IOLoop::Signal->on(HUP => sub { $self->handle_HUP() }); - $SIG{TERM} = \&class_handle_TERM; - $handlers{TERM}->{$self} = sub { $self->handle_TERM() }; - $SIG{HUP} = \&class_handle_HUP; - $handlers{HUP}->{$self} = sub { $self->handle_HUP() }; } sub handle_HUP { @@ -83,23 +85,32 @@ sub handle_TERM { ); if ($self->{connected} == 1) { - $self->{tx}->send({text => $message }); - $self->{tx}->on(drain => sub { Mojo::IOLoop->stop_gracefully(); }); - } else { - Mojo::IOLoop->stop_gracefully(); + # if the websocket is still connected, we send a message to the other end so it know we are shutting down + # And we say to mojo to stop when he don't have other message to process. + $self->{logger}->writeLogDebug("[pullwss] sending UNREGISTERNODES message to central before quiting as we are still connected to them."); + $self->{tx}->send( {text => $message }); + + $self->{tx}->on(drain => sub { + $self->{logger}->writeLogDebug("[pullwss] starting the stop_gracefully mojo sub"); + Mojo::IOLoop->stop_gracefully() + }); } -} - -sub class_handle_TERM { - foreach (keys %{$handlers{TERM}}) { - &{$handlers{TERM}->{$_}}(); + else { + # if the websocket is not connected, we simply remove zmq socket and shutdown + # we need to shutdown the zmq socket ourself or there is a c++ stack trace error in the log. + disconnect_zmq_socket_and_exit(); } } -sub class_handle_HUP { - foreach (keys %{$handlers{HUP}}) { - &{$handlers{HUP}->{$_}}(); - } +sub disconnect_zmq_socket_and_exit { + $connector->{logger}->writeLogDebug("[pullwss] removing zmq socket : $connector->{internal_socket}"); + # Following my tests we need both close() and undef to correctly close the zmq socket + # If we add only one of them the following error can arise after shutdown : + # Bad file descriptor (src/epoll.cpp:73) + $connector->{internal_socket}->close(); + undef $connector->{internal_socket}; + $connector->{logger}->writeLogInfo("[pullwss] exit now."); + exit(0); } sub send_message { @@ -135,7 +146,7 @@ sub ping { sub wss_connect { my ($self, %options) = @_; - return if ($connector->{connected} == 1); + return if ($self->{stop} == 1 or $connector->{connected} == 1); $self->{ua} = Mojo::UserAgent->new(); $self->{ua}->transactor->name('gorgone mojo'); @@ -219,14 +230,16 @@ sub run { Mojo::IOLoop->singleton->reactor->watch($socket, 1, 0); Mojo::IOLoop->singleton->recurring(60 => sub { - $connector->{logger}->writeLogDebug('[pullwss] recurring timeout loop'); - $connector->wss_connect(); - $connector->ping(); + if (!$connector->{stop}){ + $connector->{logger}->writeLogDebug('[pullwss] recurring timeout loop'); + $connector->wss_connect(); + $connector->ping(); + } }); - Mojo::IOLoop->start() unless (Mojo::IOLoop->is_running); - exit(0); + disconnect_zmq_socket_and_exit(); + } sub transmit_back { @@ -268,7 +281,7 @@ sub transmit_back { sub read_zmq_events { my ($self, %options) = @_; - while ($self->{internal_socket}->has_pollin()) { + while (!$self->{stop} and $self->{internal_socket}->has_pollin()) { my ($message) = $connector->read_message(); $message = transmit_back(message => $message); next if (!defined($message)); diff --git a/gorgone/gorgone/standard/library.pm b/gorgone/gorgone/standard/library.pm index b0ea3c8b9d8..8a6426e6b9c 100644 --- a/gorgone/gorgone/standard/library.pm +++ b/gorgone/gorgone/standard/library.pm @@ -705,7 +705,7 @@ sub connect_com { if ($options{type} eq 'tcp') { $socket->set(ZMQ_TCP_KEEPALIVE, 'int', defined($options{zmq_tcp_keepalive}) ? $options{zmq_tcp_keepalive} : -1); } - + $options{logger}->writeLogInfo("connection to zmq socket : " . $options{type} . '://' . $options{path}); $socket->connect($options{type} . '://' . $options{path}); return $socket; } diff --git a/gorgone/packaging/centreon-gorgone.yaml b/gorgone/packaging/centreon-gorgone.yaml index c24f1d8ffec..beb14173448 100644 --- a/gorgone/packaging/centreon-gorgone.yaml +++ b/gorgone/packaging/centreon-gorgone.yaml @@ -161,6 +161,7 @@ overrides: - perl-Libssh-Session >= 0.8 - perl-CryptX - perl-Mojolicious + - perl(Mojo::IOLoop::Signal) - perl(Archive::Tar) - perl(Schedule::Cron) - perl(ZMQ::FFI) @@ -211,6 +212,7 @@ overrides: - libhash-merge-perl - libcryptx-perl - libmojolicious-perl + - libmojo-ioloop-signal-perl - libauthen-simple-perl - libauthen-simple-net-perl - libnet-curl-perl diff --git a/gorgone/tests/robot/resources/resources.resource b/gorgone/tests/robot/resources/resources.resource index da556e8f01b..a4b7d6a9749 100644 --- a/gorgone/tests/robot/resources/resources.resource +++ b/gorgone/tests/robot/resources/resources.resource @@ -5,6 +5,7 @@ Library Process Library RequestsLibrary Library OperatingSystem +Library DatabaseLibrary *** Variables *** ${gorgone_binary} /usr/bin/gorgoned ${ROOT_CONFIG} ${CURDIR}${/}..${/}config${/} @@ -44,7 +45,7 @@ Stop Gorgone And Remove Gorgone Config FOR ${process} IN @{process_alias} ${result} Terminate Process ${process} - BuiltIn.Run Keyword And Continue On Failure Should Be True ${result.rc} == -15 or ${result.rc} == -9 or ${result.rc} == 0 Engine badly stopped alias = ${process} - code returned ${result.rc}. + BuiltIn.Run Keyword And Continue On Failure Should Be True ${result.rc} == -15 or ${result.rc} == 0 Gorgone ${process} badly stopped, code returned is ${result.rc}. END Gorgone Execute Sql @@ -90,7 +91,6 @@ Check Poller Is Connected BREAK END END - Log To Console TCP connection establishing after ${i} attempt Should Be True ${i} < 39 Gorgone did not establish tcp connection in 160 seconds. Log To Console TCP connection established after ${i} attempt (4 seconds each) @@ -111,7 +111,7 @@ Check Poller Communicate END END Log To Console json response : ${response.json()} - Should Be True ${i} < 20 timeout after ${i} time waiting for poller status in gorgone rest api (/api/internal/constatus) : ${response.json()} + Should Be True ${i} < 19 timeout after ${i} time waiting for poller status in gorgone rest api (/api/internal/constatus) : ${response.json()} Should Be True 0 == ${response.json()}[data][${poller_id}][ping_failed] there was failed ping between the central and the poller ${poller_id} Should Be True 0 < ${response.json()}[data][${poller_id}][ping_ok] there was no successful ping between the central and the poller ${poller_id} @@ -159,5 +159,13 @@ Wait Until Port Is Bind BREAK END END - Should Be True ${i} < 10 Gorgone did not listen on port ${port} on time. - Log To Console tcp port ${port} bind after ${i} attempt (0.5 seconds each) \ No newline at end of file + Should Be True ${i} < 9 Gorgone did not listen on port ${port} on time. + Log To Console tcp port ${port} bind after ${i} attempt (0.5 seconds each) + +Ctn Check No Error In Logs + [Arguments] ${gorgone_id} + ${cmd}= Set Variable grep -vP '^\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2} ' /var/log/centreon-gorgone/${gorgone_id}/gorgoned.log + Log To Console \n\n${cmd}\n\n + + ${log_line_wrong} RUN ${cmd} + Should Be Empty ${log_line_wrong} There is Log in ${gorgone_id} not mathcing the standard gorgone format : ${log_line_wrong} diff --git a/gorgone/tests/robot/tests/core/pullwss.robot b/gorgone/tests/robot/tests/core/pullwss.robot index 6b059a820d9..4357a9db08a 100644 --- a/gorgone/tests/robot/tests/core/pullwss.robot +++ b/gorgone/tests/robot/tests/core/pullwss.robot @@ -8,9 +8,19 @@ Test Timeout 220s @{process_list} pullwss_gorgone_poller_2 pullwss_gorgone_central *** Test Cases *** -check one poller can connect to a central gorgone - [Teardown] Stop Gorgone And Remove Gorgone Config @{process_list} sql_file=${ROOT_CONFIG}db_delete_poller.sql +check one poller can connect to a central and gorgone central stop first + [Teardown] Stop Gorgone And Remove Gorgone Config @{process_list} + @{process_list} Set Variable pullwss_gorgone_central pullwss_gorgone_poller_2 + Log To Console \nStarting the gorgone setup + Setup Two Gorgone Instances communication_mode=pullwss central_name=pullwss_gorgone_central poller_name=pullwss_gorgone_poller_2 + Ctn Check No Error In Logs pullwss_gorgone_poller_2 + Log To Console End of tests. +check one poller can connect to a central and gorgone poller stop first + [Teardown] Stop Gorgone And Remove Gorgone Config @{process_list} + @{process_list} Set Variable pullwss_gorgone_poller_2 pullwss_gorgone_central Log To Console \nStarting the gorgone setup + Setup Two Gorgone Instances communication_mode=pullwss central_name=pullwss_gorgone_central poller_name=pullwss_gorgone_poller_2 + Ctn Check No Error In Logs pullwss_gorgone_poller_2 Log To Console End of tests. From 92e80e9d5efb611e5e7cd4202ec678cf1f1fb79b Mon Sep 17 00:00:00 2001 From: Evan-Adam <152897682+Evan-Adam@users.noreply.github.com> Date: Fri, 19 Jul 2024 16:30:56 +0200 Subject: [PATCH 890/948] fix(gorgone-mbi): escape db password when connecting using mysql cli (#1518) Refs:MON-125661 --- gorgone/gorgone/modules/centreon/mbi/libs/Utils.pm | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/mbi/libs/Utils.pm b/gorgone/gorgone/modules/centreon/mbi/libs/Utils.pm index ef2a404d3dc..7a313819a60 100644 --- a/gorgone/gorgone/modules/centreon/mbi/libs/Utils.pm +++ b/gorgone/gorgone/modules/centreon/mbi/libs/Utils.pm @@ -66,14 +66,15 @@ sub checkBasicOptions { sub buildCliMysqlArgs { my ($self, $con) = @_; - - my $args = '-u "' . $con->{user} . '" ' . - '-p"' . $con->{password} . '" ' . - '-h "' . $con->{host} . '" ' . - '-P ' . $con->{port}; + my $password = $con->{password}; + # as we will use a bash command we need to use single quote to protect against every characters, and escape single quote) + $password =~ s/'/'"'"'/; + my $args = "-u'" . $con->{user} . "' " . + "-p'" . $password . "' " . + "-h '" . $con->{host} . "' " . + "-P " . $con->{port}; return $args; } - sub getYesterdayTodayDate { my ($self) = @_; From 7e1d4c708cb12b2901bb53dc760d7e30467c6d26 Mon Sep 17 00:00:00 2001 From: jean-christophe81 <98889244+jean-christophe81@users.noreply.github.com> Date: Mon, 22 Jul 2024 11:15:49 +0200 Subject: [PATCH 891/948] cmd_process_service_check_result uses of string::unscape instead of abseil version (#1515) REFS: MON51121 --- engine/inc/com/centreon/engine/string.hh | 5 ++- engine/src/command_manager.cc | 6 +++ engine/src/commands/commands.cc | 4 +- engine/src/string.cc | 13 +++++++ tests/broker-engine/external-commands2.robot | 39 ++++++++++++++++++++ tests/resources/Engine.py | 6 ++- 6 files changed, 67 insertions(+), 6 deletions(-) diff --git a/engine/inc/com/centreon/engine/string.hh b/engine/inc/com/centreon/engine/string.hh index 89c9bb5df44..7a47803cd05 100644 --- a/engine/inc/com/centreon/engine/string.hh +++ b/engine/inc/com/centreon/engine/string.hh @@ -29,7 +29,6 @@ #include <sstream> #include <string> - namespace com::centreon::engine { namespace string { @@ -205,6 +204,8 @@ std::string& remove_thresholds(std::string& perfdata) noexcept; void unescape(char* buffer); +void unescape(std::string& str); + /** * @brief this class is a thread safe replacement for my_strtok * An instance is not thread safe but sevaral instances can be used in different @@ -226,6 +227,6 @@ class c_strtok { } // namespace string -} +} // namespace com::centreon::engine #endif // !CCE_MISC_STRING_HH diff --git a/engine/src/command_manager.cc b/engine/src/command_manager.cc index 1718b63442e..665fa498a71 100644 --- a/engine/src/command_manager.cc +++ b/engine/src/command_manager.cc @@ -137,6 +137,12 @@ int command_manager::process_passive_service_check( if (!found->second->passive_checks_enabled()) return ERROR; + SPDLOG_LOGGER_DEBUG(runtime_logger, + "process_passive_service_check check_time={}, " + "host_name={}, service={}, return_code={}, output={}", + check_time, host_name, svc_description, return_code, + output); + timeval tv; gettimeofday(&tv, nullptr); diff --git a/engine/src/commands/commands.cc b/engine/src/commands/commands.cc index ec6e7d53d1a..234d437038a 100644 --- a/engine/src/commands/commands.cc +++ b/engine/src/commands/commands.cc @@ -571,8 +571,8 @@ int cmd_process_service_check_result(int cmd [[maybe_unused]], ++ait; // replace \\n with \n - std::string output; - absl::CUnescape(*ait, &output); + std::string output(ait->data(), ait->size()); + string::unescape(output); timeval tv; gettimeofday(&tv, nullptr); diff --git a/engine/src/string.cc b/engine/src/string.cc index 595ecb98b3a..b4cc09d9773 100644 --- a/engine/src/string.cc +++ b/engine/src/string.cc @@ -449,3 +449,16 @@ void string::unescape(char* buffer) { *buffer = 0; } } + +/** + * @brief * @brief Unescape the string buffer. Works with \t, \n, \r and \\. + * The buffer is directly changed. No copy is made. + * + * @param str in out modified string + */ +void string::unescape(std::string& str) { + boost::replace_all(str, "\\n", "\n"); + boost::replace_all(str, "\\r", "\r"); + boost::replace_all(str, "\\t", "\t"); + boost::replace_all(str, "\\\\", "\\"); +} diff --git a/tests/broker-engine/external-commands2.robot b/tests/broker-engine/external-commands2.robot index 65c6b7dc848..90d67fbcaec 100644 --- a/tests/broker-engine/external-commands2.robot +++ b/tests/broker-engine/external-commands2.robot @@ -1412,3 +1412,42 @@ BEHOSTCHECK Ctn Schedule Forced Host Check host_1 ${result} Ctn Check Host Check With Timeout host_1 30 ${VarRoot}/lib/centreon-engine/check.pl --id 0 Should Be True ${result} hosts table not updated + + +BE_BACKSLASH_CHECK_RESULT + [Documentation] external command PROCESS_SERVICE_CHECK_RESULT with \: + [Tags] broker engine services extcmd MON-51121 + Ctn Config Engine ${1} ${50} ${20} + Ctn Set Services Passive ${0} service_.* + Ctn Config Broker rrd + Ctn Config Broker central + Ctn Config Broker module ${1} + Ctn ConfigBBDO3 1 + Ctn Config Broker Sql Output central unified_sql + Ctn Clear Retention + Ctn Broker Config Log central sql debug + FOR ${use_grpc} IN RANGE 0 2 + Log To Console external command PROCESS_SERVICE_CHECK_RESULT use_grpc=${use_grpc} + Ctn Clear Retention + ${start} Get Current Date + Ctn Start Broker + Ctn Start engine + Ctn Wait For Engine To Be Ready ${start} ${1} + + ${start} Ctn Get Round Current Date + Ctn Process Service Check Result host_1 service_1 0 output ok D: \\: Total: 1.205TB - Used: 1.203TB (100%) - Free: 2.541GB (0%) ${use_grpc} config0 ${use_grpc} + + ${result} Ctn Check Service Output Resource Status With Timeout + ... host_1 + ... service_1 + ... 35 + ... ${start} + ... 0 + ... HARD + ... output ok D: \\: Total: 1.205TB - Used: 1.203TB (100%) - Free: 2.541GB (0%) ${use_grpc} + Should Be True ${result} resources table not updated + + + Ctn Stop engine + Ctn Kindly Stop Broker + END diff --git a/tests/resources/Engine.py b/tests/resources/Engine.py index e0562834e47..9d601ff85c6 100755 --- a/tests/resources/Engine.py +++ b/tests/resources/Engine.py @@ -2823,6 +2823,8 @@ def ctn_process_service_check_result(hst: str, svc: str, state: int, output: str 0 on success. """ if use_grpc > 0: + ts = Timestamp() + ts.GetCurrentTime() port = 50001 + int(config[6:]) with grpc.insecure_channel(f"127.0.0.1:{port}") as channel: stub = engine_pb2_grpc.EngineStub(channel) @@ -2830,10 +2832,10 @@ def ctn_process_service_check_result(hst: str, svc: str, state: int, output: str for i in range(nb_check): indexed_output = f"{output}_{i}" stub.ProcessServiceCheckResult(engine_pb2.Check( - host_name=hst, svc_desc=svc, output=indexed_output, code=state)) + host_name=hst, svc_desc=svc, check_time=ts, output=indexed_output, code=state)) else: stub.ProcessServiceCheckResult(engine_pb2.Check( - host_name=hst, svc_desc=svc, output=output, code=state)) + host_name=hst, svc_desc=svc, check_time=ts, output=output, code=state)) else: now = int(time.time()) From 447cb7f893acc7777b6f7707759ad1b16ea8696e Mon Sep 17 00:00:00 2001 From: jean-christophe81 <98889244+jean-christophe81@users.noreply.github.com> Date: Mon, 22 Jul 2024 11:16:56 +0200 Subject: [PATCH 892/948] we apply forbidden filter on default filter(all) (#1536) MON-127440 --- .../broker/config/applier/endpoint.hh | 3 +- broker/core/src/config/applier/endpoint.cc | 101 +++++++++++------- tests/broker-engine/muxer_filter.robot | 57 +++++----- 3 files changed, 93 insertions(+), 68 deletions(-) diff --git a/broker/core/inc/com/centreon/broker/config/applier/endpoint.hh b/broker/core/inc/com/centreon/broker/config/applier/endpoint.hh index 30099ecac64..1e44b023ba8 100644 --- a/broker/core/inc/com/centreon/broker/config/applier/endpoint.hh +++ b/broker/core/inc/com/centreon/broker/config/applier/endpoint.hh @@ -84,7 +84,8 @@ class endpoint { static bool loaded(); static multiplexing::muxer_filter parse_filters( - const std::set<std::string>& str_filters); + const std::set<std::string>& str_filters, + const multiplexing::muxer_filter& forbidden_filter); }; } // namespace applier } // namespace config diff --git a/broker/core/src/config/applier/endpoint.cc b/broker/core/src/config/applier/endpoint.cc index 60edff28267..02c456e3ae7 100644 --- a/broker/core/src/config/applier/endpoint.cc +++ b/broker/core/src/config/applier/endpoint.cc @@ -102,14 +102,15 @@ endpoint::~endpoint() { */ void endpoint::apply(std::list<config::endpoint> const& endpoints) { // Log messages. - _logger->info("endpoint applier: loading configuration"); + SPDLOG_LOGGER_INFO(_logger, "endpoint applier: loading configuration"); - { + if (_logger->level() <= spdlog::level::debug) { std::vector<std::string> eps; for (auto& ep : endpoints) eps.push_back(ep.name); - _logger->debug("endpoint applier: {} endpoints to apply: {}", - endpoints.size(), fmt::format("{}", fmt::join(eps, ", "))); + SPDLOG_LOGGER_DEBUG(_logger, "endpoint applier: {} endpoints to apply: {}", + endpoints.size(), + fmt::format("{}", fmt::join(eps, ", "))); } // Copy endpoint configurations and apply eventual modifications. @@ -129,8 +130,9 @@ void endpoint::apply(std::list<config::endpoint> const& endpoints) { // resources that might be used by other endpoints. auto it = _endpoints.find(ep); if (it != _endpoints.end()) { - _logger->debug("endpoint applier: removing old endpoint {}", - it->first.name); + SPDLOG_LOGGER_DEBUG(_logger, + "endpoint applier: removing old endpoint {}", + it->first.name); /* failover::exit() is called. */ it->second->exit(); delete it->second; @@ -141,13 +143,14 @@ void endpoint::apply(std::list<config::endpoint> const& endpoints) { // Update existing endpoints. for (auto it = _endpoints.begin(), end = _endpoints.end(); it != end; ++it) { - _logger->debug("endpoint applier: updating endpoint {}", it->first.name); + SPDLOG_LOGGER_DEBUG(_logger, "endpoint applier: updating endpoint {}", + it->first.name); it->second->update(); } // Debug message. - _logger->debug("endpoint applier: {} endpoints to create", - endp_to_create.size()); + SPDLOG_LOGGER_DEBUG(_logger, "endpoint applier: {} endpoints to create", + endp_to_create.size()); // Create new endpoints. for (config::endpoint& ep : endp_to_create) { @@ -156,7 +159,8 @@ void endpoint::apply(std::list<config::endpoint> const& endpoints) { if (ep.name.empty() || std::find_if(endp_to_create.begin(), endp_to_create.end(), name_match_failover(ep.name)) == endp_to_create.end()) { - _logger->debug("endpoint applier: creating endpoint {}", ep.name); + SPDLOG_LOGGER_DEBUG(_logger, "endpoint applier: creating endpoint {}", + ep.name); bool is_acceptor; std::shared_ptr<io::endpoint> e{_create_endpoint(ep, is_acceptor)}; std::unique_ptr<processing::endpoint> endp; @@ -173,15 +177,18 @@ void endpoint::apply(std::list<config::endpoint> const& endpoints) { * if broker sends data to map. This is needed because a failover needs * its peer to ack events to release them (and a failover is also able * to write data). */ - multiplexing::muxer_filter r_filter = parse_filters(ep.read_filters); - multiplexing::muxer_filter w_filter = parse_filters(ep.write_filters); + multiplexing::muxer_filter r_filter = + parse_filters(ep.read_filters, e->get_stream_forbidden_filter()); + multiplexing::muxer_filter w_filter = + parse_filters(ep.write_filters, e->get_stream_forbidden_filter()); if (is_acceptor) { w_filter -= e->get_stream_forbidden_filter(); r_filter -= e->get_stream_forbidden_filter(); std::unique_ptr<processing::acceptor> acceptr( std::make_unique<processing::acceptor>(e, ep.name, r_filter, w_filter)); - _logger->debug( + SPDLOG_LOGGER_DEBUG( + _logger, "endpoint applier: acceptor '{}' configured with write filters: {} " "and read filters: {}", ep.name, w_filter.get_allowed_categories(), @@ -193,7 +200,8 @@ void endpoint::apply(std::list<config::endpoint> const& endpoints) { /* Are there missing events in the w_filter ? */ if (!e->get_stream_mandatory_filter().is_in(w_filter)) { w_filter |= e->get_stream_mandatory_filter(); - _logger->debug( + SPDLOG_LOGGER_DEBUG( + _logger, "endpoint applier: The configured write filters for the endpoint " "'{}' are too restrictive. Mandatory categories added to them", ep.name); @@ -201,7 +209,8 @@ void endpoint::apply(std::list<config::endpoint> const& endpoints) { /* Are there events in w_filter that are forbidden ? */ if (w_filter.contains_some_of(e->get_stream_forbidden_filter())) { w_filter -= e->get_stream_forbidden_filter(); - _logger->error( + SPDLOG_LOGGER_ERROR( + _logger, "endpoint applier: The configured write filters for the endpoint " "'{}' contain forbidden filters. These ones are removed", ep.name); @@ -210,13 +219,14 @@ void endpoint::apply(std::list<config::endpoint> const& endpoints) { /* Are there events in r_filter that are forbidden ? */ if (r_filter.contains_some_of(e->get_stream_forbidden_filter())) { r_filter -= e->get_stream_forbidden_filter(); - _logger->error( + SPDLOG_LOGGER_ERROR( + _logger, "endpoint applier: The configured read filters for the endpoint " "'{}' contain forbidden filters. These ones are removed", ep.name); } - _logger->debug( - "endpoint applier: filters {} for endpoint '{}' applied.", + SPDLOG_LOGGER_DEBUG( + _logger, "endpoint applier: filters {} for endpoint '{}' applied.", w_filter.get_allowed_categories(), ep.name); auto mux = multiplexing::muxer::create( @@ -230,7 +240,8 @@ void endpoint::apply(std::list<config::endpoint> const& endpoints) { } // Run thread. - _logger->debug( + SPDLOG_LOGGER_DEBUG( + _logger, "endpoint applier: endpoint thread {} of '{}' is registered and " "ready to run", static_cast<void*>(endp.get()), ep.name); @@ -245,13 +256,14 @@ void endpoint::apply(std::list<config::endpoint> const& endpoints) { */ void endpoint::_discard() { _discarding = true; - _logger->debug("endpoint applier: destruction"); + SPDLOG_LOGGER_DEBUG(_logger, "endpoint applier: destruction"); // wait for failover and feeder to push endloop event ::usleep(processing::idle_microsec_wait_idle_thread_delay + 100000); // Exit threads. { - _logger->debug("endpoint applier: requesting threads termination"); + SPDLOG_LOGGER_DEBUG(_logger, + "endpoint applier: requesting threads termination"); std::unique_lock<std::timed_mutex> lock(_endpointsm); // Send termination requests. @@ -259,8 +271,9 @@ void endpoint::_discard() { for (auto it = _endpoints.begin(); it != _endpoints.end();) { if (it->second->is_feeder()) { it->second->wait_for_all_events_written(5000); - _logger->trace("endpoint applier: send exit signal to endpoint '{}'", - it->second->get_name()); + SPDLOG_LOGGER_TRACE( + _logger, "endpoint applier: send exit signal to endpoint '{}'", + it->second->get_name()); delete it->second; it = _endpoints.erase(it); } else @@ -270,19 +283,22 @@ void endpoint::_discard() { // Exit threads. { - _logger->debug("endpoint applier: requesting threads termination"); + SPDLOG_LOGGER_DEBUG(_logger, + "endpoint applier: requesting threads termination"); std::unique_lock<std::timed_mutex> lock(_endpointsm); // We continue with failovers for (auto it = _endpoints.begin(); it != _endpoints.end();) { it->second->wait_for_all_events_written(5000); - _logger->trace("endpoint applier: send exit signal on endpoint '{}'", - it->second->get_name()); + SPDLOG_LOGGER_TRACE(_logger, + "endpoint applier: send exit signal on endpoint '{}'", + it->second->get_name()); delete it->second; it = _endpoints.erase(it); } - _logger->debug("endpoint applier: all threads are terminated"); + SPDLOG_LOGGER_DEBUG(_logger, + "endpoint applier: all threads are terminated"); } // Stop multiplexing: we must stop the engine after failovers otherwise @@ -373,7 +389,8 @@ processing::failover* endpoint::_create_failover( std::shared_ptr<io::endpoint> endp, std::list<config::endpoint>& l) { // Debug message. - _logger->info("endpoint applier: creating new failover '{}'", cfg.name); + SPDLOG_LOGGER_INFO(_logger, "endpoint applier: creating new failover '{}'", + cfg.name); // Check that failover is configured. std::shared_ptr<processing::failover> failovr; @@ -382,7 +399,8 @@ processing::failover* endpoint::_create_failover( std::list<config::endpoint>::iterator it = std::find_if(l.begin(), l.end(), failover_match_name(front_failover)); if (it == l.end()) - _logger->error( + SPDLOG_LOGGER_ERROR( + _logger, "endpoint applier: could not find failover '{}' for endpoint '{}'", front_failover, cfg.name); else { @@ -411,7 +429,8 @@ processing::failover* endpoint::_create_failover( bool is_acceptor{false}; std::shared_ptr<io::endpoint> endp(_create_endpoint(*it, is_acceptor)); if (is_acceptor) { - _logger->error( + SPDLOG_LOGGER_ERROR( + _logger, "endpoint applier: secondary failover '{}' is an acceptor and " "cannot therefore be instantiated for endpoint '{}'", *failover_it, cfg.name); @@ -462,8 +481,8 @@ std::shared_ptr<io::endpoint> endpoint::_create_endpoint(config::endpoint& cfg, endp = std::shared_ptr<io::endpoint>( it->second.endpntfactry->new_endpoint(cfg, is_acceptor, cache)); - _logger->info(" create endpoint {} for endpoint '{}'", it->first, - cfg.name); + SPDLOG_LOGGER_INFO(_logger, " create endpoint {} for endpoint '{}'", + it->first, cfg.name); level = it->second.osi_to + 1; break; } @@ -484,8 +503,8 @@ std::shared_ptr<io::endpoint> endpoint::_create_endpoint(config::endpoint& cfg, (it->second.endpntfactry->has_endpoint(cfg, nullptr))) { std::shared_ptr<io::endpoint> current( it->second.endpntfactry->new_endpoint(cfg, is_acceptor)); - _logger->info(" create endpoint {} for endpoint '{}'", it->first, - cfg.name); + SPDLOG_LOGGER_INFO(_logger, " create endpoint {} for endpoint '{}'", + it->first, cfg.name); current->from(endp); endp = current; level = it->second.osi_to; @@ -545,7 +564,8 @@ void endpoint::_diff_endpoints( list_it = std::find_if(new_ep.begin(), new_ep.end(), failover_match_name(failover)); if (list_it == new_ep.end()) - _logger->error( + SPDLOG_LOGGER_ERROR( + _logger, "endpoint applier: could not find failover '{}' for endpoint " "'{}'", failover, entry.name); @@ -570,11 +590,14 @@ void endpoint::_diff_endpoints( * Create filters from a set of categories. * * @param[in] cfg Endpoint configuration. + * @param[in] forbidden_filter forbidden filter applied in case of default + * filter config * * @return Filters. */ multiplexing::muxer_filter endpoint::parse_filters( - const std::set<std::string>& str_filters) { + const std::set<std::string>& str_filters, + const multiplexing::muxer_filter& forbidden_filter) { auto logger = log_v2::instance().get(log_v2::CONFIG); multiplexing::muxer_filter elements({}); std::forward_list<fmt::string_view> applied_filters; @@ -595,6 +618,7 @@ multiplexing::muxer_filter endpoint::parse_filters( if (str_filters.size() == 1 && *str_filters.begin() == "all") { elements = multiplexing::muxer_filter(); + elements -= forbidden_filter; applied_filters.emplace_front("all"); } else { for (auto& str : str_filters) { @@ -610,10 +634,11 @@ multiplexing::muxer_filter endpoint::parse_filters( } if (applied_filters.empty() && !str_filters.empty()) { fill_elements("all"); + elements -= forbidden_filter; applied_filters.emplace_front("all"); } } - logger->info("Filters applied on endpoint:{}", - fmt::join(applied_filters, ", ")); + SPDLOG_LOGGER_INFO(logger, "Filters applied on endpoint:{}", + fmt::join(applied_filters, ", ")); return elements; } diff --git a/tests/broker-engine/muxer_filter.robot b/tests/broker-engine/muxer_filter.robot index cb00f4e333e..8ed6ad8174e 100644 --- a/tests/broker-engine/muxer_filter.robot +++ b/tests/broker-engine/muxer_filter.robot @@ -6,10 +6,32 @@ Resource ../resources/import.resource Suite Setup Ctn Clean Before Suite Suite Teardown Ctn Clean After Suite Test Setup Ctn Stop Processes -Test Teardown Ctn Save Logs If Failed +Test Teardown Ctn Stop Engine Broker And Save Logs True *** Test Cases *** +NO_FILTER_NO_ERROR + [Documentation] no filter configured => no filter error. + [Tags] broker engine filter + Ctn Config Engine ${1} ${50} ${20} + Ctn Config Broker central + Ctn Config Broker module ${1} + Ctn Config Broker rrd + Ctn Broker Config Log central sql debug + Ctn Config Broker Sql Output central unified_sql + Ctn Config BBDO3 1 + Ctn Clear Broker Logs + + ${start} Get Current Date + Ctn Start Broker True + Ctn Start engine + + ${content} Create List + ... are too restrictive contain forbidden filters + ${result} Ctn Find In Log With Timeout ${centralLog} ${start} ${content} 15 + Should Not Be True ${result} An message of filter error has been found + + STUPID_FILTER [Documentation] Unified SQL is configured with only the bbdo category as filter. An error is raised by broker and broker should run correctly. [Tags] broker engine filter @@ -28,13 +50,10 @@ STUPID_FILTER Ctn Start engine ${content} Create List - ... The configured write filters for the endpoint 'central-broker-unified-sql' contain forbidden filters. These ones are removed The configured read filters for the endpoint 'central-broker-unified-sql' contain forbidden filters. These ones are removed + ... The configured write filters for the endpoint 'central-broker-unified-sql' contain forbidden filters. These ones are removed ${result} Ctn Find In Log With Timeout ${centralLog} ${start} ${content} 60 Should Be True ${result} A message telling bad filter should be available. - Ctn Stop Engine - Ctn Kindly Stop Broker True - STORAGE_ON_LUA [Documentation] The category 'storage' is applied on the stream connector. Only events of this category should be sent to this stream. [Tags] broker engine filter @@ -62,9 +81,6 @@ STORAGE_ON_LUA ${grep_res} Grep File /tmp/all_lua_event.log "category":[^3] regexp=True Should Be Empty ${grep_res} Events of category different than 'storage' found. - Ctn Stop Engine - Ctn Kindly Stop Broker True - FILTER_ON_LUA_EVENT [Documentation] stream connector with a bad configured filter generate a log error message [Tags] broker engine filter @@ -109,9 +125,6 @@ FILTER_ON_LUA_EVENT ... All the lines in all_lua_event.log should contain "_type":196620 END - Ctn Stop Engine - Ctn Kindly Stop Broker True - BAM_STREAM_FILTER [Documentation] With bbdo version 3.0.1, a BA of type 'worst' with one service is ... configured. The BA is in critical state, because of its service. we watch its events @@ -203,9 +216,6 @@ BAM_STREAM_FILTER ... centreon-bam-reporting event neb:.* rejected by write filter regexp=True Should Not Be Empty ${grep_res} We should reject events of Neb category. They are not rejected. - Ctn Stop Engine - Ctn Kindly Stop Broker True - UNIFIED_SQL_FILTER [Documentation] With bbdo version 3.0.1, we watch events written or rejected in unified_sql [Tags] broker engine bam filter @@ -240,9 +250,6 @@ UNIFIED_SQL_FILTER Should Not Be Empty ${grep_res} END - Ctn Stop Engine - Ctn Kindly Stop Broker True - CBD_RELOAD_AND_FILTERS [Documentation] We start engine/broker with a classical configuration. All is up and running. Some filters are added to the rrd output and cbd is reloaded. All is still up and running but some events are rejected. Then all is newly set as filter and all events are sent to rrd broker. [Tags] broker engine filter @@ -271,8 +278,8 @@ CBD_RELOAD_AND_FILTERS # We check that output filters to rrd are set to "all" ${content} Create List ... endpoint applier: The configured write filters for the endpoint 'centreon-broker-master-rrd' contain forbidden filters. These ones are removed - ${result} Ctn Find In Log With Timeout ${centralLog} ${start} ${content} 60 - Should Be True ${result} No message about the output filters to rrd broker. + ${result} Ctn Find In Log With Timeout ${centralLog} ${start} ${content} 15 + Should Not Be True ${result} No message about the output filters to rrd broker. # New configuration Ctn Broker Config Output Set Json central centreon-broker-master-rrd filters {"category": [ "storage"]} @@ -320,8 +327,8 @@ CBD_RELOAD_AND_FILTERS # We check that output filters to rrd are set to "all" ${content} Create List ... endpoint applier: The configured write filters for the endpoint 'centreon-broker-master-rrd' contain forbidden filters. These ones are removed - ${result} Ctn Find In Log With Timeout ${centralLog} ${start} ${content} 60 - Should Be True ${result} No message about the output filters to rrd broker. + ${result} Ctn Find In Log With Timeout ${centralLog} ${start} ${content} 15 + Should Not Be True ${result} No message about the output filters to rrd broker. ${start} Get Current Date # Let's wait for storage data written into rrd files @@ -337,9 +344,6 @@ CBD_RELOAD_AND_FILTERS ... False ... Some events are rejected by the rrd output whereas all categories are enabled. - Ctn Stop Engine - Ctn Kindly Stop Broker True - CBD_RELOAD_AND_FILTERS_WITH_OPR [Documentation] We start engine/broker with an almost classical configuration, just the connection between cbd central and cbd rrd is reversed with one peer retention. All is up and running. Some filters are added to the rrd output and cbd is reloaded. All is still up and running but some events are rejected. Then all is newly set as filter and all events are sent to rrd broker. [Tags] broker engine filter @@ -435,9 +439,6 @@ CBD_RELOAD_AND_FILTERS_WITH_OPR ... False ... Some events are rejected by the rrd output whereas all categories are enabled. - Ctn Stop Engine - Ctn Kindly Stop Broker True - SEVERAL_FILTERS_ON_LUA_EVENT [Documentation] Two stream connectors with different filters are configured. [Tags] broker engine filter @@ -507,5 +508,3 @@ SEVERAL_FILTERS_ON_LUA_EVENT ... "_type":65565 ... All the lines in all_lua_event-bis.log should contain "_type":65565 END - Ctn Stop Engine - Ctn Kindly Stop Broker True From c151691344e9019db3b8760d86e1d8ff8b50c0bc Mon Sep 17 00:00:00 2001 From: jean-christophe81 <98889244+jean-christophe81@users.noreply.github.com> Date: Mon, 22 Jul 2024 12:16:42 +0200 Subject: [PATCH 893/948] agent compile on windows (#1501) --- CMakeLists.txt | 170 +------------------- CMakeListsLinux.txt | 194 +++++++++++++++++++++++ CMakeListsWindows.txt | 84 ++++++++++ CMakePresets.json | 27 ++++ README.md | 9 ++ agent/CMakeLists.txt | 91 +++++++---- agent/conf/centagent.reg | Bin 0 -> 1712 bytes agent/src/config_win.cc | 109 +++++++++++++ agent/src/main_win.cc | 172 ++++++++++++++++++++ agent/src/streaming_client.cc | 10 +- agent/src/streaming_server.cc | 10 +- agent/test/CMakeLists.txt | 49 ++++-- agent/test/check_exec_test.cc | 26 ++- agent/test/scheduler_test.cc | 31 ++-- agent/test/scripts/sleep.bat | 2 + agent/test/test_main.cc | 3 +- centreon_cmake.bat | 61 +++++++ common/CMakeLists.txt | 39 +++-- common/engine_legacy_conf/CMakeLists.txt | 2 +- common/precomp_inc/precomp.hh | 2 + common/process/src/process.cc | 6 + common/tests/CMakeLists.txt | 104 ++++++++---- common/tests/process_test.cc | 41 ++++- common/tests/scripts/bad_script.bat | 3 + common/tests/scripts/echo.bat | 1 + common/tests/test_main_win.cc | 54 +++++++ custom-triplets/x64-windows.cmake | 6 + vcpkg.json | 57 +++++-- 28 files changed, 1059 insertions(+), 304 deletions(-) create mode 100644 CMakeListsLinux.txt create mode 100644 CMakeListsWindows.txt create mode 100644 CMakePresets.json create mode 100644 agent/conf/centagent.reg create mode 100644 agent/src/config_win.cc create mode 100644 agent/src/main_win.cc create mode 100644 agent/test/scripts/sleep.bat create mode 100644 centreon_cmake.bat create mode 100644 common/tests/scripts/bad_script.bat create mode 100644 common/tests/scripts/echo.bat create mode 100644 common/tests/test_main_win.cc create mode 100644 custom-triplets/x64-windows.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index e53424c2fd5..9dcbac951a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,55 +53,15 @@ set(CMAKE_TOOLCHAIN_FILE project("Centreon Collect" C CXX) -option(WITH_ASAN - "Add the libasan to check memory leaks and other memory issues." OFF) - -option(WITH_TSAN - "Add the libtsan to check threads and other multithreading issues." OFF) -if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT CMAKE_CXX_COMPILER_ID - STREQUAL "Clang") - message( - FATAL_ERROR "You can build broker with g++ or clang++. CMake will exit.") -endif() - -option(WITH_MALLOC_TRACE "compile centreon-malloc-trace library." OFF) - # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -stdlib=libc++") # set(CMAKE_CXX_COMPILER "clang++") add_definitions("-D_GLIBCXX_USE_CXX11_ABI=1") -add_definitions("-DBOOST_PROCESS_USE_STD_FS=1") -option(DEBUG_ROBOT OFF) +add_definitions("-DBOOST_PROCESS_USE_STD_FS=1") set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) -if(WITH_TSAN) - set(CMAKE_CXX_FLAGS_DEBUG - "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=thread") - set(CMAKE_LINKER_FLAGS_DEBUG - "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=thread") -endif() - -if(WITH_ASAN) - set(CMAKE_BUILD_TYPE Debug) - if(WITH_CLANG) - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address") - set(CMAKE_LINKER_FLAGS_DEBUG - "${CMAKE_LINKER_FLAGS_DEBUG} -fsanitize=address") - else() - set(CMAKE_CXX_FLAGS_DEBUG - "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address") - set(CMAKE_LINKER_FLAGS_DEBUG - "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address" - ) - endif() -endif() - -set(ALLOW_DUPLICATE_EXECUTABLE TRUE) - -set(BUILD_ARGS "-w" "dupbuild=warn") - # # Get distributions name # @@ -148,130 +108,10 @@ set(COLLECT_MAJOR 24) set(COLLECT_MINOR 07) set(COLLECT_PATCH 0) set(COLLECT_VERSION "${COLLECT_MAJOR}.${COLLECT_MINOR}.${COLLECT_PATCH}") -add_definitions(-DCENTREON_CONNECTOR_VERSION=\"${COLLECT_VERSION}\") - -if(DEBUG_ROBOT) - add_definitions(-DDEBUG_ROBOT) -endif() - -# ########### CONSTANTS ########### -set(USER_BROKER centreon-broker) -set(USER_ENGINE centreon-engine) -find_package(fmt CONFIG REQUIRED) -find_package(spdlog CONFIG REQUIRED) -find_package(gRPC CONFIG REQUIRED) -find_package(Protobuf REQUIRED) -find_package(nlohmann_json CONFIG REQUIRED) -find_package(GTest CONFIG REQUIRED) -find_package(CURL REQUIRED) -find_package(Boost REQUIRED COMPONENTS url) -find_package(ryml CONFIG REQUIRED) -add_definitions("-DSPDLOG_FMT_EXTERNAL") -include(FindPkgConfig) -pkg_check_modules(MARIADB REQUIRED libmariadb) -pkg_check_modules(LIBSSH2 REQUIRED libssh2) - -# There is a bug with grpc. It is not put in the triplet directory. So we have -# to search for its plugin. -file( - GLOB_RECURSE GRPC_CPP_PLUGIN_EXE - RELATIVE ${CMAKE_BINARY_DIR} - grpc_cpp_plugin) -find_program( - GRPC_CPP_PLUGIN - NAMES ${GRPC_CPP_PLUGIN_EXE} - PATHS ${CMAKE_BINARY_DIR} REQUIRED - NO_DEFAULT_PATH) - -set(PROTOBUF_LIB_DIR ${Protobuf_DIR}/../../lib) -set(OTLP_LIB_DIR ${opentelemetry-cpp_DIR}/../../lib) -set(VCPKG_INCLUDE_DIR ${Protobuf_INCLUDE_DIR}) -include(GNUInstallDirs) - -# import opentelemetry-proto -add_custom_command( - OUTPUT - ${CMAKE_SOURCE_DIR}/opentelemetry-proto/opentelemetry/proto/collector/metrics/v1/metrics_service.proto - ${CMAKE_SOURCE_DIR}/opentelemetry-proto/opentelemetry/proto/metrics/v1/metrics.proto - ${CMAKE_SOURCE_DIR}/opentelemetry-proto/opentelemetry/proto/common/v1/common.proto - ${CMAKE_SOURCE_DIR}/opentelemetry-proto/opentelemetry/proto/resource/v1/resource.proto - COMMENT "get opentelemetry proto files from git repository" - COMMAND /bin/rm -rf ${CMAKE_SOURCE_DIR}/opentelemetry-proto - COMMAND - git ARGS clone --depth=1 --single-branch - https://github.com/open-telemetry/opentelemetry-proto.git - ${CMAKE_SOURCE_DIR}/opentelemetry-proto - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) - -add_custom_target( - opentelemetry-proto-files - DEPENDS - ${CMAKE_SOURCE_DIR}/opentelemetry-proto/opentelemetry/proto/collector/metrics/v1/metrics_service.proto - ${CMAKE_SOURCE_DIR}/opentelemetry-proto/opentelemetry/proto/metrics/v1/metrics.proto - ${CMAKE_SOURCE_DIR}/opentelemetry-proto/opentelemetry/proto/common/v1/common.proto - ${CMAKE_SOURCE_DIR}/opentelemetry-proto/opentelemetry/proto/resource/v1/resource.proto -) - -# var directories. -set(BROKER_VAR_LOG_DIR - "${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/log/centreon-broker") -set(BROKER_VAR_LIB_DIR - "${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/lib/centreon-broker") -set(ENGINE_VAR_LOG_DIR - "${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/log/centreon-engine") -set(ENGINE_VAR_LOG_ARCHIVE_DIR - "${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/log/centreon-engine/archives") -set(ENGINE_VAR_LIB_DIR - "${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/lib/centreon-engine") -add_definitions(-DDEFAULT_COMMAND_FILE="${ENGINE_VAR_LIB_DIR}/rw/centengine.cmd" - -DDEFAULT_DEBUG_FILE="${ENGINE_VAR_LOG_DIR}/centengine.debug" - -DDEFAULT_LOG_FILE="${ENGINE_VAR_LOG_DIR}/centengine.log" - -DDEFAULT_RETENTION_FILE="${ENGINE_VAR_LOG_DIR}/retention.dat" - -DDEFAULT_STATUS_FILE="${ENGINE_VAR_LOG_DIR}/status.dat") - -set(CMAKE_INSTALL_PREFIX "/usr") -option(WITH_TESTING "Build unit tests." OFF) - -option(WITH_CONF "Install configuration files." ON) - -# Code coverage on unit tests -option(WITH_COVERAGE "Add code coverage on unit tests." OFF) - -if(WITH_TESTING AND WITH_COVERAGE) - set(CMAKE_BUILD_TYPE "Debug") - include(cmake/CodeCoverage.cmake) - append_coverage_compiler_flags() -endif() - -set(protobuf_MODULE_COMPATIBLE True) - -include_directories(${CMAKE_SOURCE_DIR} ${VCPKG_INCLUDE_DIR} fmt::fmt - spdlog::spdlog ${CMAKE_SOURCE_DIR}/clib/inc) - -add_subdirectory(clib) -add_subdirectory(common) -add_subdirectory(broker) -add_subdirectory(bbdo) -add_subdirectory(engine) -add_subdirectory(connectors) -add_subdirectory(ccc) -add_subdirectory(agent) - -if(WITH_MALLOC_TRACE) - add_subdirectory(malloc-trace) +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + include(CMakeListsLinux.txt) +else() + include(CMakeListsWindows.txt) endif() - -add_custom_target(test-broker COMMAND tests/ut_broker) -add_custom_target(test-engine COMMAND tests/ut_engine) -add_custom_target(test-clib COMMAND tests/ut_clib) -add_custom_target(test-connector COMMAND tests/ut_connector) -add_custom_target(test-common COMMAND tests/ut_common) -add_custom_target(test-agent COMMAND tests/ut_agent) - -add_custom_target(test DEPENDS test-broker test-engine test-clib test-connector - test-common test-agent) - -add_custom_target(test-coverage DEPENDS broker-test-coverage - engine-test-coverage clib-test-coverage) diff --git a/CMakeListsLinux.txt b/CMakeListsLinux.txt new file mode 100644 index 00000000000..a7b4120a02b --- /dev/null +++ b/CMakeListsLinux.txt @@ -0,0 +1,194 @@ +# +# Copyright 2009-2023 Centreon +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# For more information : contact@centreon.com +# + +# +# Global settings. +# + + +option(WITH_ASAN + "Add the libasan to check memory leaks and other memory issues." OFF) + +option(WITH_TSAN + "Add the libtsan to check threads and other multithreading issues." OFF) +if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT CMAKE_CXX_COMPILER_ID + STREQUAL "Clang") + message( + FATAL_ERROR "You can build broker with g++ or clang++. CMake will exit.") +endif() + +option(WITH_MALLOC_TRACE "compile centreon-malloc-trace library." OFF) + +option(DEBUG_ROBOT OFF) + + +if(WITH_TSAN) + set(CMAKE_CXX_FLAGS_DEBUG + "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=thread") + set(CMAKE_LINKER_FLAGS_DEBUG + "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=thread") +endif() + +if(WITH_ASAN) + set(CMAKE_BUILD_TYPE Debug) + if(WITH_CLANG) + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address") + set(CMAKE_LINKER_FLAGS_DEBUG + "${CMAKE_LINKER_FLAGS_DEBUG} -fsanitize=address") + else() + set(CMAKE_CXX_FLAGS_DEBUG + "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address") + set(CMAKE_LINKER_FLAGS_DEBUG + "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address" + ) + endif() +endif() + +set(ALLOW_DUPLICATE_EXECUTABLE TRUE) + +set(BUILD_ARGS "-w" "dupbuild=warn") + + +# Version. +add_definitions(-DCENTREON_CONNECTOR_VERSION=\"${COLLECT_VERSION}\") + +if (DEBUG_ROBOT) + add_definitions(-DDEBUG_ROBOT) +endif() + +# ########### CONSTANTS ########### +set(USER_BROKER centreon-broker) +set(USER_ENGINE centreon-engine) + +find_package(fmt CONFIG REQUIRED) +find_package(spdlog CONFIG REQUIRED) +find_package(gRPC CONFIG REQUIRED) +find_package(Protobuf REQUIRED) +find_package(nlohmann_json CONFIG REQUIRED) +find_package(GTest CONFIG REQUIRED) +find_package(CURL REQUIRED) +find_package(Boost REQUIRED COMPONENTS url) +find_package(ryml CONFIG REQUIRED) +add_definitions("-DSPDLOG_FMT_EXTERNAL") + +add_definitions("-DCOLLECT_MAJOR=${COLLECT_MAJOR}") +add_definitions("-DCOLLECT_MINOR=${COLLECT_MINOR}") +add_definitions("-DCOLLECT_PATCH=${COLLECT_PATCH}") + +include(FindPkgConfig) +pkg_check_modules(MARIADB REQUIRED libmariadb) +pkg_check_modules(LIBSSH2 REQUIRED libssh2) + +# There is a bug with grpc. It is not put in the triplet directory. So we have +# to search for its plugin. +file(GLOB_RECURSE GRPC_CPP_PLUGIN_EXE + RELATIVE ${CMAKE_BINARY_DIR} grpc_cpp_plugin) +find_program(GRPC_CPP_PLUGIN + NAMES ${GRPC_CPP_PLUGIN_EXE} + PATHS ${CMAKE_BINARY_DIR} + REQUIRED + NO_DEFAULT_PATH) + +set(PROTOBUF_LIB_DIR ${Protobuf_DIR}/../../lib) +set(OTLP_LIB_DIR ${opentelemetry-cpp_DIR}/../../lib) +set(VCPKG_INCLUDE_DIR ${Protobuf_INCLUDE_DIR}) +include(GNUInstallDirs) + +#import opentelemetry-proto +add_custom_command( + OUTPUT ${CMAKE_SOURCE_DIR}/opentelemetry-proto/opentelemetry/proto/collector/metrics/v1/metrics_service.proto + ${CMAKE_SOURCE_DIR}/opentelemetry-proto/opentelemetry/proto/metrics/v1/metrics.proto + ${CMAKE_SOURCE_DIR}/opentelemetry-proto/opentelemetry/proto/common/v1/common.proto + ${CMAKE_SOURCE_DIR}/opentelemetry-proto/opentelemetry/proto/resource/v1/resource.proto + COMMENT "get opentelemetry proto files from git repository" + COMMAND /bin/rm -rf ${CMAKE_SOURCE_DIR}/opentelemetry-proto + COMMAND git ARGS clone --depth=1 --single-branch https://github.com/open-telemetry/opentelemetry-proto.git ${CMAKE_SOURCE_DIR}/opentelemetry-proto + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} +) + +add_custom_target(opentelemetry-proto-files DEPENDS ${CMAKE_SOURCE_DIR}/opentelemetry-proto/opentelemetry/proto/collector/metrics/v1/metrics_service.proto + ${CMAKE_SOURCE_DIR}/opentelemetry-proto/opentelemetry/proto/metrics/v1/metrics.proto + ${CMAKE_SOURCE_DIR}/opentelemetry-proto/opentelemetry/proto/common/v1/common.proto + ${CMAKE_SOURCE_DIR}/opentelemetry-proto/opentelemetry/proto/resource/v1/resource.proto +) + +# var directories. +set(BROKER_VAR_LOG_DIR + "${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/log/centreon-broker") +set(BROKER_VAR_LIB_DIR + "${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/lib/centreon-broker") +set(ENGINE_VAR_LOG_DIR + "${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/log/centreon-engine") +set(ENGINE_VAR_LOG_ARCHIVE_DIR + "${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/log/centreon-engine/archives") +set(ENGINE_VAR_LIB_DIR + "${CMAKE_INSTALL_FULL_LOCALSTATEDIR}/lib/centreon-engine") +add_definitions(-DDEFAULT_COMMAND_FILE="${ENGINE_VAR_LIB_DIR}/rw/centengine.cmd" + -DDEFAULT_DEBUG_FILE="${ENGINE_VAR_LOG_DIR}/centengine.debug" + -DDEFAULT_LOG_FILE="${ENGINE_VAR_LOG_DIR}/centengine.log" + -DDEFAULT_RETENTION_FILE="${ENGINE_VAR_LOG_DIR}/retention.dat" + -DDEFAULT_STATUS_FILE="${ENGINE_VAR_LOG_DIR}/status.dat") + +set(CMAKE_INSTALL_PREFIX "/usr") +option(WITH_TESTING "Build unit tests." OFF) + +option(WITH_CONF "Install configuration files." ON) + +# Code coverage on unit tests +option(WITH_COVERAGE "Add code coverage on unit tests." OFF) + +if(WITH_TESTING AND WITH_COVERAGE) + set(CMAKE_BUILD_TYPE "Debug") + include(cmake/CodeCoverage.cmake) + append_coverage_compiler_flags() +endif() + +set(protobuf_MODULE_COMPATIBLE True) + +include_directories(${CMAKE_SOURCE_DIR} + ${VCPKG_INCLUDE_DIR} + fmt::fmt + spdlog::spdlog + ${CMAKE_SOURCE_DIR}/clib/inc) + +add_subdirectory(clib) +add_subdirectory(common) +add_subdirectory(broker) +add_subdirectory(bbdo) +add_subdirectory(engine) +add_subdirectory(connectors) +add_subdirectory(ccc) +add_subdirectory(agent) + +if (WITH_MALLOC_TRACE) + add_subdirectory(malloc-trace) +endif() + + +add_custom_target(test-broker COMMAND tests/ut_broker) +add_custom_target(test-engine COMMAND tests/ut_engine) +add_custom_target(test-clib COMMAND tests/ut_clib) +add_custom_target(test-connector COMMAND tests/ut_connector) +add_custom_target(test-common COMMAND tests/ut_common) +add_custom_target(test-agent COMMAND tests/ut_agent) + +add_custom_target(test DEPENDS test-broker test-engine test-clib test-connector + test-common test-agent) + +add_custom_target(test-coverage DEPENDS broker-test-coverage + engine-test-coverage clib-test-coverage) diff --git a/CMakeListsWindows.txt b/CMakeListsWindows.txt new file mode 100644 index 00000000000..8f2bc2b3711 --- /dev/null +++ b/CMakeListsWindows.txt @@ -0,0 +1,84 @@ +# +# Copyright 2024 Centreon +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# For more information : contact@centreon.com +# + +find_package(fmt CONFIG REQUIRED) +find_package(spdlog CONFIG REQUIRED) +find_package(gRPC CONFIG REQUIRED) +find_package(Protobuf REQUIRED) +find_package(GTest CONFIG REQUIRED) +find_package(boost_asio CONFIG REQUIRED) +find_package(boost_process CONFIG REQUIRED) +find_package(boost_multi_index CONFIG REQUIRED) +find_package(boost_program_options CONFIG REQUIRED) +find_package(boost_multi_index CONFIG REQUIRED) +add_definitions("-DSPDLOG_FMT_EXTERNAL") + +add_definitions("-DCOLLECT_MAJOR=${COLLECT_MAJOR}") +add_definitions("-DCOLLECT_MINOR=${COLLECT_MINOR}") +add_definitions("-DCOLLECT_PATCH=${COLLECT_PATCH}") + + +# There is a bug with grpc. It is not put in the triplet directory. So we have +# to search for its plugin. +file(GLOB_RECURSE GRPC_CPP_PLUGIN_EXE + RELATIVE ${CMAKE_BINARY_DIR} grpc_cpp_plugin.exe) +find_program(GRPC_CPP_PLUGIN + NAMES ${GRPC_CPP_PLUGIN_EXE} + PATHS ${CMAKE_BINARY_DIR} + REQUIRED + NO_DEFAULT_PATH) + +set(PROTOBUF_LIB_DIR ${Protobuf_DIR}/../../lib) +set(VCPKG_INCLUDE_DIR ${OPENSSL_INCLUDE_DIR}) +include(GNUInstallDirs) + +option(WITH_TESTING "Build unit tests." OFF) + +set(protobuf_MODULE_COMPATIBLE True) + +include_directories(${CMAKE_SOURCE_DIR} + ${VCPKG_INCLUDE_DIR} + ${CMAKE_SOURCE_DIR}/clib/inc) + +#import opentelemetry-proto +add_custom_command( + OUTPUT ${CMAKE_SOURCE_DIR}/opentelemetry-proto/opentelemetry/proto/collector/metrics/v1/metrics_service.proto + ${CMAKE_SOURCE_DIR}/opentelemetry-proto/opentelemetry/proto/metrics/v1/metrics.proto + ${CMAKE_SOURCE_DIR}/opentelemetry-proto/opentelemetry/proto/common/v1/common.proto + ${CMAKE_SOURCE_DIR}/opentelemetry-proto/opentelemetry/proto/resource/v1/resource.proto + COMMENT "get opentelemetry proto files from git repository" + COMMAND RMDIR /S /Q \"${CMAKE_SOURCE_DIR}/opentelemetry-proto\" + COMMAND git ARGS clone --depth=1 --single-branch https://github.com/open-telemetry/opentelemetry-proto.git ${CMAKE_SOURCE_DIR}/opentelemetry-proto + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} +) + +add_custom_target(opentelemetry-proto-files DEPENDS ${CMAKE_SOURCE_DIR}/opentelemetry-proto/opentelemetry/proto/collector/metrics/v1/metrics_service.proto + ${CMAKE_SOURCE_DIR}/opentelemetry-proto/opentelemetry/proto/metrics/v1/metrics.proto + ${CMAKE_SOURCE_DIR}/opentelemetry-proto/opentelemetry/proto/common/v1/common.proto + ${CMAKE_SOURCE_DIR}/opentelemetry-proto/opentelemetry/proto/resource/v1/resource.proto +) + +add_subdirectory(common) +add_subdirectory(agent) + + +add_custom_target(test-common COMMAND tests/ut_common) +add_custom_target(test-agent COMMAND tests/ut_agent) + +add_custom_target(test DEPENDS test-common test-agent) + diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 00000000000..e94d5ad68b9 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,27 @@ +{ + "version": 2, + "configurePresets": [ + { + "name": "debug", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build_windows", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "WITH_TESTING": "On", + "CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" + } + }, + { + "name": "release", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build_windows", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "WITH_TESTING": "On", + "CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake", + "VCPKG_OVERLAY_TRIPLETS": "custom-triplets", + "VCPKG_TARGET_TRIPLET": "x64-windows" + } + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index b22f7400d3a..83970d026d9 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,15 @@ make -Cbuild install These two variables are very important if you want to recompile the project later. +#### Windows compilation +A small part of the project (centreon-monitoring-agent in agent folder) is Windows compatible. +In order to compile it, you need at least msbuild tools and git. +Then you have to: +* Start a x64 command tool console +* Execute centreon_cmake.bat. It first installs vcpkg in your home directory and then tells you to set two environment variables VCPKG_ROOT and PATH. Be careful, the next time you will start x64 command tool console, it will set VCPKG_ROOT to wrong value and you will need to set it again. +* Then install agent\conf\agent.reg in the registry and modify parameters such as server, certificates or logging. + + ### Other distributions If you are on another distribution, then follow the steps below. diff --git a/agent/CMakeLists.txt b/agent/CMakeLists.txt index c64801646c4..09eb1737271 100644 --- a/agent/CMakeLists.txt +++ b/agent/CMakeLists.txt @@ -98,21 +98,39 @@ add_custom_command( WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set( SRC_COMMON + ${SRC_DIR}/agent.grpc.pb.cc + ${SRC_DIR}/agent.pb.cc + ${SRC_DIR}/bireactor.cc + ${SRC_DIR}/check.cc + ${SRC_DIR}/check_exec.cc + ${SRC_DIR}/opentelemetry/proto/collector/metrics/v1/metrics_service.grpc.pb.cc + ${SRC_DIR}/opentelemetry/proto/collector/metrics/v1/metrics_service.pb.cc + ${SRC_DIR}/opentelemetry/proto/metrics/v1/metrics.pb.cc + ${SRC_DIR}/opentelemetry/proto/common/v1/common.pb.cc + ${SRC_DIR}/opentelemetry/proto/resource/v1/resource.pb.cc + ${SRC_DIR}/scheduler.cc + ${SRC_DIR}/streaming_client.cc + ${SRC_DIR}/streaming_server.cc +) + +set( SRC_WINDOWS + ${SRC_DIR}/config_win.cc +) + +set( SRC_LINUX + ${SRC_DIR}/config.cc +) + +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + set(SRC ${SRC_COMMON} ${SRC_LINUX}) +else() + set(SRC ${SRC_COMMON} ${SRC_WINDOWS}) +endif() + + add_library(centagent_lib STATIC - ${SRC_DIR}/agent.grpc.pb.cc - ${SRC_DIR}/agent.pb.cc - ${SRC_DIR}/bireactor.cc - ${SRC_DIR}/check.cc - ${SRC_DIR}/check_exec.cc - ${SRC_DIR}/opentelemetry/proto/collector/metrics/v1/metrics_service.grpc.pb.cc - ${SRC_DIR}/opentelemetry/proto/collector/metrics/v1/metrics_service.pb.cc - ${SRC_DIR}/opentelemetry/proto/metrics/v1/metrics.pb.cc - ${SRC_DIR}/opentelemetry/proto/common/v1/common.pb.cc - ${SRC_DIR}/opentelemetry/proto/resource/v1/resource.pb.cc - ${SRC_DIR}/config.cc - ${SRC_DIR}/scheduler.cc - ${SRC_DIR}/streaming_client.cc - ${SRC_DIR}/streaming_server.cc + ${SRC} ) include_directories( @@ -127,21 +145,36 @@ target_precompile_headers(centagent_lib PRIVATE precomp_inc/precomp.hh) SET(CENTREON_AGENT centagent) -add_executable(${CENTREON_AGENT} ${SRC_DIR}/main.cc) - -target_link_libraries( - ${CENTREON_AGENT} PRIVATE - -L${PROTOBUF_LIB_DIR} - gRPC::gpr gRPC::grpc gRPC::grpc++ gRPC::grpc++_alts - centagent_lib - centreon_common - centreon_grpc - centreon_process - -L${Boost_LIBRARY_DIR_RELEASE} - boost_program_options - fmt::fmt - stdc++fs - ) +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + add_executable(${CENTREON_AGENT} ${SRC_DIR}/main.cc) + + target_link_libraries( + ${CENTREON_AGENT} PRIVATE + -L${PROTOBUF_LIB_DIR} + gRPC::gpr gRPC::grpc gRPC::grpc++ gRPC::grpc++_alts + centagent_lib + centreon_common + centreon_grpc + centreon_process + -L${Boost_LIBRARY_DIR_RELEASE} + boost_program_options + fmt::fmt + stdc++fs) +else() + add_executable(${CENTREON_AGENT} ${SRC_DIR}/main_win.cc) + + target_link_libraries( + ${CENTREON_AGENT} PRIVATE + centagent_lib + centreon_common + centreon_grpc + centreon_process + gRPC::gpr gRPC::grpc gRPC::grpc++ gRPC::grpc++_alts + absl::any absl::log absl::base absl::bits + Boost::program_options + fmt::fmt) +endif() + target_precompile_headers(${CENTREON_AGENT} REUSE_FROM centagent_lib) diff --git a/agent/conf/centagent.reg b/agent/conf/centagent.reg new file mode 100644 index 0000000000000000000000000000000000000000..ba43c5406a93f129db9fe747c0c76d7374607ab1 GIT binary patch literal 1712 zcmb`IYi|-!5Qg8+CjJMqpELn4P?l)av{`LyENw!GO(SMQfKnp20IjV*UVY}AUG{(> zHpXl=d(K?nd1nTG{k+gfQ}s2|cP&*?O9Ks&Ew$1@_eegV9dVm+FX=P-1=<n!Dfhab z=>^YYJ#z0C9qSW5mr8Y_GbK9Uo2tn-LHALoXgfMbcc`{5khh#9A@bB^9k|#X>^CJ% zC(p4M?jhd8%<8MlA8Cx$kc?Mg9HWc$mUkb^Bl4V*Go=d4>Jc9CeGje{HqUvJ%*s%m zVV?$``&8@WM_bt}(=G2f%V+4WK(cMOg46jLmG`l#D?8#&)YTmr@@i7Y{f{g{pRQS7 z`g-lUV4d(!cMIQkoTbCL+rO6iTL=0ep24Q!^lZLylCk60reehX9T@gh(@QnhRhxIu z&CwiRKQk6#cV0h95neWD=3|xjU}3dpE$4KO#R%5DF4Z?I?lXkBjd`XIismh+*iZh* zbWQ!X5x)8fSt>4OIIk5IR<;ch=zM=|Uk22iGM5XO>T&Y>HE`MdBuDV~H(o66!#C`b zEo(EyP4+F(4Y>`GVNR&WW1rE(bEGS`lg9b2<9h(Y4!$<iW_g0g5Xk}f=lJA3@jd<5 zU2SLEumr=6i)_<uo$foXck|K6=CeT9#B0gZb-xIQAk4S_8qJ-asH&%MT;(^b?6#!L zq}GD-tKb~U_tx?_@QuM(#Hp^&F%w&cDeF~<$M&%-FZJgJdy;#GXzENxF0cP5WVIkp vjoS101->xrW=#bLaS&y*k|N?JHwDL4*w3@ZLFI8!ZFu$^75MC)|GWGKRY3Xc literal 0 HcmV?d00001 diff --git a/agent/src/config_win.cc b/agent/src/config_win.cc new file mode 100644 index 00000000000..faa95415ea3 --- /dev/null +++ b/agent/src/config_win.cc @@ -0,0 +1,109 @@ +/** + * Copyright 2024 Centreon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + */ + +#include <windows.h> + +#include <re2/re2.h> + +#include "com/centreon/exceptions/msg_fmt.hh" +#include "config.hh" + +using namespace com::centreon::agent; + +/** + * @brief Construct a new config::config object + * + * @param registry_key registry path as + * HKEY_LOCAL_MACHINE\SOFTWARE\Centreon\CentreonMonitoringAgent + */ +config::config(const std::string& registry_key) { + HKEY h_key; + LSTATUS res = RegOpenKeyExA(HKEY_LOCAL_MACHINE, registry_key.c_str(), 0, + KEY_READ, &h_key); + if (res != ERROR_SUCCESS) { + if (res == ERROR_FILE_NOT_FOUND) { + throw exceptions::msg_fmt("{} not found", registry_key); + } else { + throw exceptions::msg_fmt("unable to read {}", registry_key); + } + } + + char str_buffer[4096]; + + auto get_sz_reg_or_default = [&](const char* value_name, + const char* default_value) { + DWORD size = sizeof(str_buffer); + LSTATUS result = RegGetValueA(h_key, nullptr, value_name, RRF_RT_REG_SZ, + nullptr, str_buffer, &size); + return (result == ERROR_SUCCESS) ? str_buffer : default_value; + }; + + auto get_bool = [&](const char* value_name) -> bool { + int32_t value; + DWORD size = sizeof(value); + LSTATUS result = RegGetValueA(h_key, nullptr, value_name, RRF_RT_DWORD, + nullptr, &value, &size); + return result == ERROR_SUCCESS && value; + }; + + auto get_unsigned = [&](const char* value_name) -> uint32_t { + uint32_t value; + DWORD size = sizeof(value); + LSTATUS result = RegGetValueA(h_key, nullptr, value_name, RRF_RT_DWORD, + nullptr, &value, &size); + return result == ERROR_SUCCESS ? value : 0; + }; + + _endpoint = get_sz_reg_or_default("endpoint", ""); + + // pattern schema doesn't work so we do it ourselves + if (!RE2::FullMatch(_endpoint, "[\\w\\.:]+:\\w+")) { + RegCloseKey(h_key); + throw exceptions::msg_fmt( + "bad format for endpoint {}, it must match to the regex: " + "[\\w\\.:]+:\\w+", + _endpoint); + } + _log_level = + spdlog::level::from_str(get_sz_reg_or_default("log_level", "info")); + + const char* log_type = get_sz_reg_or_default("log_type", "event-log"); + if (!strcmp(log_type, "file")) { + _log_type = to_file; + } else if (!strcmp(log_type, "stdout")) { + _log_type = to_stdout; + } else { + _log_type = to_event_log; + } + + _log_file = get_sz_reg_or_default("log_file", ""); + _log_files_max_size = get_unsigned("log_files_max_size"); + _log_files_max_number = get_unsigned("log_files_max_number"); + _encryption = get_bool("encryption"); + _public_cert_file = get_sz_reg_or_default("public_cert", ""); + _private_key_file = get_sz_reg_or_default("private_key", ""); + _ca_certificate_file = get_sz_reg_or_default("ca_certificate", ""); + _ca_name = get_sz_reg_or_default("ca_name", ""); + _host = get_sz_reg_or_default("host", ""); + if (_host.empty()) { + _host = boost::asio::ip::host_name(); + } + _reverse_connection = get_bool("reverse_connection"); + + RegCloseKey(h_key); +} diff --git a/agent/src/main_win.cc b/agent/src/main_win.cc new file mode 100644 index 00000000000..05ba6276b17 --- /dev/null +++ b/agent/src/main_win.cc @@ -0,0 +1,172 @@ +/** + * Copyright 2024 Centreon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + */ + +#include <spdlog/sinks/basic_file_sink.h> +#include <spdlog/sinks/rotating_file_sink.h> +#include <spdlog/sinks/stdout_color_sinks.h> +#include <spdlog/sinks/win_eventlog_sink.h> + +#include "config.hh" +#include "streaming_client.hh" +#include "streaming_server.hh" + +using namespace com::centreon::agent; + +std::shared_ptr<asio::io_context> g_io_context = + std::make_shared<asio::io_context>(); + +std::shared_ptr<spdlog::logger> g_logger; +static std::shared_ptr<streaming_client> _streaming_client; + +static std::shared_ptr<streaming_server> _streaming_server; + +static asio::signal_set _signals(*g_io_context, SIGTERM, SIGINT); + +static void signal_handler(const boost::system::error_code& error, + int signal_number) { + if (!error) { + switch (signal_number) { + case SIGINT: + case SIGTERM: + SPDLOG_LOGGER_INFO(g_logger, "SIGTERM or SIGINT received"); + if (_streaming_client) { + _streaming_client->shutdown(); + } + if (_streaming_server) { + _streaming_server->shutdown(); + } + g_io_context->post([]() { g_io_context->stop(); }); + return; + } + _signals.async_wait(signal_handler); + } +} + +static std::string read_file(const std::string& file_path) { + if (file_path.empty()) { + return {}; + } + try { + std::ifstream file(file_path); + if (file.is_open()) { + std::stringstream ss; + ss << file.rdbuf(); + file.close(); + return ss.str(); + } + } catch (const std::exception& e) { + SPDLOG_LOGGER_ERROR(g_logger, "fail to read {}: {}", file_path, e.what()); + } + return ""; +} + +int main(int argc, char* argv[]) { + const char* registry_path = "SOFTWARE\\Centreon\\CentreonMonitoringAgent"; + + std::unique_ptr<config> conf; + try { + conf = std::make_unique<config>(registry_path); + } catch (const std::exception& e) { + SPDLOG_ERROR("fail to read conf from registry {}: {}", registry_path, + e.what()); + return 1; + } + + SPDLOG_INFO("centreon-monitoring-agent start"); + + const std::string logger_name = "centreon-monitoring-agent"; + + auto create_event_logger = []() { + auto sink = std::make_shared<spdlog::sinks::win_eventlog_sink_mt>( + "CentreonMonitoringAgent"); + g_logger = std::make_shared<spdlog::logger>("", sink); + }; + + try { + if (conf->get_log_type() == config::to_file) { + if (!conf->get_log_file().empty()) { + if (conf->get_log_files_max_size() > 0 && + conf->get_log_files_max_number() > 0) { + g_logger = spdlog::rotating_logger_mt( + logger_name, conf->get_log_file(), + conf->get_log_files_max_size() * 0x100000, + conf->get_log_files_max_number()); + } else { + SPDLOG_INFO( + "no log-max-file-size option or no log-max-files option provided " + "=> logs will not be rotated by centagent"); + g_logger = spdlog::basic_logger_mt(logger_name, conf->get_log_file()); + } + } else { + SPDLOG_ERROR( + "log-type=file needs the option log-file => log to event log"); + create_event_logger(); + } + } else if (conf->get_log_type() == config::to_stdout) { + g_logger = spdlog::stdout_color_mt(logger_name); + } else { + create_event_logger(); + } + } catch (const std::exception& e) { + SPDLOG_CRITICAL("Can't log to {}: {}", conf->get_log_file(), e.what()); + return 2; + } + + g_logger->set_level(conf->get_log_level()); + + g_logger->flush_on(spdlog::level::warn); + + spdlog::flush_every(std::chrono::seconds(1)); + + SPDLOG_LOGGER_INFO(g_logger, "centreon-monitoring-agent start"); + std::shared_ptr<com::centreon::common::grpc::grpc_config> grpc_conf; + + try { + _signals.async_wait(signal_handler); + + grpc_conf = std::make_shared<com::centreon::common::grpc::grpc_config>( + conf->get_endpoint(), conf->use_encryption(), + read_file(conf->get_public_cert_file()), + read_file(conf->get_private_key_file()), + read_file(conf->get_ca_certificate_file()), conf->get_ca_name(), true, + 30); + + } catch (const std::exception& e) { + SPDLOG_CRITICAL("fail to parse input params: {}", e.what()); + return -1; + } + + if (conf->use_reverse_connection()) { + _streaming_server = streaming_server::load(g_io_context, g_logger, + grpc_conf, conf->get_host()); + } else { + _streaming_client = streaming_client::load(g_io_context, g_logger, + grpc_conf, conf->get_host()); + } + + try { + g_io_context->run(); + } catch (const std::exception& e) { + SPDLOG_LOGGER_CRITICAL(g_logger, "unhandled exception: {}", e.what()); + return -1; + } + + SPDLOG_LOGGER_INFO(g_logger, "centreon-monitoring-agent end"); + + return 0; +} diff --git a/agent/src/streaming_client.cc b/agent/src/streaming_client.cc index 5fa122c83cd..424d384b1c9 100644 --- a/agent/src/streaming_client.cc +++ b/agent/src/streaming_client.cc @@ -18,7 +18,6 @@ #include "streaming_client.hh" #include "check_exec.hh" -#include "com/centreon/clib/version.hh" #include "com/centreon/common/defer.hh" using namespace com::centreon::agent; @@ -146,12 +145,9 @@ void streaming_client::_create_reactor() { std::make_shared<MessageFromAgent>(); auto infos = who_i_am->mutable_init(); - infos->mutable_centreon_version()->set_major( - com::centreon::clib::version::major); - infos->mutable_centreon_version()->set_minor( - com::centreon::clib::version::minor); - infos->mutable_centreon_version()->set_patch( - com::centreon::clib::version::patch); + infos->mutable_centreon_version()->set_major(COLLECT_MAJOR); + infos->mutable_centreon_version()->set_minor(COLLECT_MINOR); + infos->mutable_centreon_version()->set_patch(COLLECT_PATCH); infos->set_host(_supervised_host); diff --git a/agent/src/streaming_server.cc b/agent/src/streaming_server.cc index cfc23fabb11..215c1d2457b 100644 --- a/agent/src/streaming_server.cc +++ b/agent/src/streaming_server.cc @@ -18,7 +18,6 @@ #include "streaming_server.hh" #include "check_exec.hh" -#include "com/centreon/clib/version.hh" #include "scheduler.hh" using namespace com::centreon::agent; @@ -90,12 +89,9 @@ void server_reactor::_start() { std::make_shared<MessageFromAgent>(); auto infos = who_i_am->mutable_init(); - infos->mutable_centreon_version()->set_major( - com::centreon::clib::version::major); - infos->mutable_centreon_version()->set_minor( - com::centreon::clib::version::minor); - infos->mutable_centreon_version()->set_patch( - com::centreon::clib::version::patch); + infos->mutable_centreon_version()->set_major(COLLECT_MAJOR); + infos->mutable_centreon_version()->set_minor(COLLECT_MINOR); + infos->mutable_centreon_version()->set_patch(COLLECT_PATCH); infos->set_host(_supervised_host); write(who_i_am); diff --git a/agent/test/CMakeLists.txt b/agent/test/CMakeLists.txt index c677ecc9c93..897aea3b643 100644 --- a/agent/test/CMakeLists.txt +++ b/agent/test/CMakeLists.txt @@ -16,15 +16,21 @@ # For more information : contact@centreon.com # - - -add_executable(ut_agent - config_test.cc +set( SRC_COMMON check_test.cc check_exec_test.cc scheduler_test.cc test_main.cc - ${TESTS_SOURCES}) +) + +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + set(SRC ${SRC_COMMON} config_test.cc) +else() + set(SRC ${SRC_COMMON}) +endif() + + +add_executable(ut_agent ${SRC}) add_test(NAME tests COMMAND ut_agent) @@ -36,8 +42,25 @@ set_target_properties( RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR}/tests RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ${CMAKE_BINARY_DIR}/tests) - -target_link_libraries(ut_agent PRIVATE +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + target_link_libraries(ut_agent PRIVATE + centagent_lib + centreon_common + centreon_process + GTest::gtest + GTest::gtest_main + GTest::gmock + GTest::gmock_main + -L${Boost_LIBRARY_DIR_RELEASE} + boost_program_options + stdc++fs + -L${PROTOBUF_LIB_DIR} + gRPC::gpr gRPC::grpc gRPC::grpc++ gRPC::grpc++_alts + fmt::fmt pthread + crypto ssl + ) +else() + target_link_libraries(ut_agent PRIVATE centagent_lib centreon_common centreon_process @@ -45,14 +68,11 @@ target_link_libraries(ut_agent PRIVATE GTest::gtest_main GTest::gmock GTest::gmock_main - -L${Boost_LIBRARY_DIR_RELEASE} - boost_program_options - stdc++fs - -L${PROTOBUF_LIB_DIR} + Boost::program_options gRPC::gpr gRPC::grpc gRPC::grpc++ gRPC::grpc++_alts - fmt::fmt pthread - crypto ssl + fmt::fmt ) +endif() add_dependencies(ut_agent centreon_common centagent_lib) @@ -60,3 +80,6 @@ set_property(TARGET ut_agent PROPERTY POSITION_INDEPENDENT_CODE ON) target_precompile_headers(ut_agent PRIVATE ${PROJECT_SOURCE_DIR}/precomp_inc/precomp.hh) +file(COPY ${PROJECT_SOURCE_DIR}/test/scripts/sleep.bat + DESTINATION ${CMAKE_BINARY_DIR}/tests) + diff --git a/agent/test/check_exec_test.cc b/agent/test/check_exec_test.cc index c0a6ef1278c..c5cf3b15bbc 100644 --- a/agent/test/check_exec_test.cc +++ b/agent/test/check_exec_test.cc @@ -22,6 +22,16 @@ using namespace com::centreon::agent; +#ifdef _WINDOWS +#define ECHO_PATH "tests\\echo.bat" +#define SLEEP_PATH "tests\\sleep.bat" +#define END_OF_LINE "\r\n" +#else +#define ECHO_PATH "/bin/echo" +#define SLEEP_PATH "/bin/sleep" +#define END_OF_LINE "\n" +#endif + extern std::shared_ptr<asio::io_context> g_io_context; static const std::string serv("serv"); @@ -29,7 +39,7 @@ static const std::string cmd_name("command"); static std::string command_line; TEST(check_exec_test, echo) { - command_line = "/bin/echo hello toto"; + command_line = ECHO_PATH " hello toto"; int status; std::list<std::string> outputs; std::mutex mut; @@ -54,11 +64,11 @@ TEST(check_exec_test, echo) { cond.wait(l); ASSERT_EQ(status, 0); ASSERT_EQ(outputs.size(), 1); - ASSERT_EQ(*outputs.begin(), "hello toto"); + ASSERT_EQ(outputs.begin()->substr(0, 10), "hello toto"); } TEST(check_exec_test, timeout) { - command_line = "/bin/sleep 5"; + command_line = SLEEP_PATH " 5"; int status; std::list<std::string> outputs; std::condition_variable cond; @@ -78,9 +88,10 @@ TEST(check_exec_test, timeout) { std::mutex mut; std::unique_lock l(mut); cond.wait(l); - ASSERT_EQ(status, 3); + ASSERT_NE(status, 0); ASSERT_EQ(outputs.size(), 1); - ASSERT_EQ(*outputs.begin(), "Timeout at execution of /bin/sleep 5"); + + ASSERT_EQ(*outputs.begin(), "Timeout at execution of " SLEEP_PATH " 5"); } TEST(check_exec_test, bad_command) { @@ -111,7 +122,12 @@ TEST(check_exec_test, bad_command) { cond.wait(l); ASSERT_EQ(status, 3); ASSERT_EQ(outputs.size(), 1); +#ifdef _WINDOWS + // message is language dependant + ASSERT_GE(outputs.begin()->size(), 20); +#else ASSERT_EQ(*outputs.begin(), "Fail to execute /usr/bad_path/turlututu titi toto : No such file " "or directory"); +#endif } diff --git a/agent/test/scheduler_test.cc b/agent/test/scheduler_test.cc index a7ac335382e..5af1a86f4dd 100644 --- a/agent/test/scheduler_test.cc +++ b/agent/test/scheduler_test.cc @@ -94,6 +94,11 @@ class scheduler_test : public ::testing::Test { spdlog::default_logger()->set_level(spdlog::level::trace); } + void TearDown() override { + // let time to async check to end + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + } + std::shared_ptr<com::centreon::agent::MessageToAgent> create_conf( unsigned nb_serv, unsigned second_check_period, @@ -151,11 +156,11 @@ TEST_F(scheduler_test, no_config) { static bool tempo_check_assert_pred(const time_point& after, const time_point& before) { - if ((after - before) <= std::chrono::milliseconds(40)) { + if ((after - before) <= std::chrono::milliseconds(400)) { SPDLOG_ERROR("after={}, before={}", after, before); return false; } - if ((after - before) >= std::chrono::milliseconds(60)) { + if ((after - before) >= std::chrono::milliseconds(600)) { SPDLOG_ERROR("after={}, before={}", after, before); return false; } @@ -165,7 +170,7 @@ static bool tempo_check_assert_pred(const time_point& after, TEST_F(scheduler_test, correct_schedule) { std::shared_ptr<scheduler> sched = scheduler::load( g_io_context, spdlog::default_logger(), "my_host", - create_conf(20, 1, 1, 50, 1), + create_conf(20, 10, 1, 50, 1), [](const std::shared_ptr<MessageFromAgent>&) {}, [](const std::shared_ptr<asio::io_context>& io_context, const std::shared_ptr<spdlog::logger>& logger, @@ -184,10 +189,10 @@ TEST_F(scheduler_test, correct_schedule) { tempo_check::check_starts.clear(); } - std::this_thread::sleep_for(std::chrono::milliseconds(1100)); + std::this_thread::sleep_for(std::chrono::milliseconds(10100)); - // we have 2 * 10 = 20 checks spread over 1 second - duration expected_interval = std::chrono::milliseconds(50); + // we have 2 * 10 = 20 checks spread over 10 second + duration expected_interval = std::chrono::milliseconds(1000); { std::lock_guard l(tempo_check::check_starts_m); @@ -206,7 +211,7 @@ TEST_F(scheduler_test, correct_schedule) { } } - std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + std::this_thread::sleep_for(std::chrono::milliseconds(10000)); { std::lock_guard l(tempo_check::check_starts_m); @@ -432,7 +437,7 @@ unsigned concurent_check::max_active_check; TEST_F(scheduler_test, max_concurent) { std::shared_ptr<scheduler> sched = scheduler::load( g_io_context, spdlog::default_logger(), "my_host", - create_conf(200, 1, 1, 10, 1), + create_conf(200, 10, 1, 10, 1), [&](const std::shared_ptr<MessageFromAgent>& req) {}, [](const std::shared_ptr<asio::io_context>& io_context, const std::shared_ptr<spdlog::logger>& logger, @@ -442,17 +447,17 @@ TEST_F(scheduler_test, max_concurent) { check::completion_handler&& handler) { return std::make_shared<concurent_check>( io_context, logger, start_expected, service, cmd_name, cmd_line, - engine_to_agent_request, 0, std::chrono::milliseconds(75), + engine_to_agent_request, 0, std::chrono::milliseconds(750), std::move(handler)); }); - // to many tests to be completed in one second - std::this_thread::sleep_for(std::chrono::milliseconds(1100)); + // to many tests to be completed in eleven second + std::this_thread::sleep_for(std::chrono::milliseconds(11000)); ASSERT_LT(concurent_check::checked.size(), 200); ASSERT_EQ(concurent_check::max_active_check, 10); - // all tests must be completed in 1.5s - std::this_thread::sleep_for(std::chrono::milliseconds(500)); + // all tests must be completed in 16s + std::this_thread::sleep_for(std::chrono::milliseconds(5000)); ASSERT_EQ(concurent_check::max_active_check, 10); ASSERT_EQ(concurent_check::checked.size(), 200); diff --git a/agent/test/scripts/sleep.bat b/agent/test/scripts/sleep.bat new file mode 100644 index 00000000000..9b178637c61 --- /dev/null +++ b/agent/test/scripts/sleep.bat @@ -0,0 +1,2 @@ +@echo off +ping 127.0.0.1 -n1 %~1 \ No newline at end of file diff --git a/agent/test/test_main.cc b/agent/test/test_main.cc index 919a087af50..21d63bb5a22 100644 --- a/agent/test/test_main.cc +++ b/agent/test/test_main.cc @@ -23,12 +23,13 @@ std::shared_ptr<asio::io_context> g_io_context( class CentreonEngineEnvironment : public testing::Environment { public: +#ifndef _WINDOWS void SetUp() override { setenv("TZ", ":Europe/Paris", 1); return; } +#endif - void TearDown() override { return; } }; /** diff --git a/centreon_cmake.bat b/centreon_cmake.bat new file mode 100644 index 00000000000..07bb3e28e38 --- /dev/null +++ b/centreon_cmake.bat @@ -0,0 +1,61 @@ +echo off + +set "build_type=debug" + +if "%~1" == "--help" ( + call :show_help + goto :eof +) else if "%~1" == "--release" ( + set "build_type=release" +) + +where /q cl.exe +IF ERRORLEVEL 1 ( + echo unable to find cl.exe, please run vcvarsall.bat or compile from x64 Native Tools Command Prompt for VS20xx + exit /B +) + +where /q cmake.exe +IF ERRORLEVEL 1 ( + echo unable to find cmake.exe, please install cmake.exe + exit /B +) + +where /q ninja.exe +IF ERRORLEVEL 1 ( + echo unable to find ninja.exe, please install ninja.exe + exit /B +) + +if not defined VCPKG_ROOT ( + echo "install vcpkg" + set "current_dir=%cd%" + cd /D %USERPROFILE% + git clone https://github.com/microsoft/vcpkg.git + cd vcpkg && bootstrap-vcpkg.bat + cd /D %current_dir% + set "VCPKG_ROOT=%USERPROFILE%\vcpkg" + set "PATH=%VCPKG_ROOT%;%PATH%" + echo "Please add this variables to environment for future compile:" + echo "VCPKG_ROOT=%USERPROFILE%\vcpkg" + echo "PATH=%VCPKG_ROOT%;%PATH%" +) + + +cmake.exe --preset=%build_type% + +cmake.exe --build build_windows + +goto :eof + + +:show_help +echo This program build Centreon-Monitoring-Agent +echo --release : Build on release mode +echo --help : help +goto :eof + + + + + diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index efa4315f19a..69e302e25ad 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -19,8 +19,10 @@ # Global options. project("Centreon common" C CXX) -add_subdirectory(log_v2) -add_subdirectory(engine_legacy_conf) +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + add_subdirectory(log_v2) + add_subdirectory(engine_legacy_conf) +endif() # Set directories. set(INCLUDE_DIR "${PROJECT_SOURCE_DIR}/inc/com/centreon/common") @@ -45,16 +47,24 @@ add_custom_command( WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) # Set sources. -set(SOURCES - ${SRC_DIR}/hex_dump.cc - ${SRC_DIR}/perfdata.cc - ${SRC_DIR}/pool.cc - ${SRC_DIR}/process_stat.cc - ${SRC_DIR}/process_stat.pb.cc - ${SRC_DIR}/process_stat.grpc.pb.cc - ${SRC_DIR}/rapidjson_helper.cc - ${SRC_DIR}/utf8.cc -) +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + set(SOURCES + ${SRC_DIR}/hex_dump.cc + ${SRC_DIR}/perfdata.cc + ${SRC_DIR}/pool.cc + ${SRC_DIR}/process_stat.cc + ${SRC_DIR}/process_stat.pb.cc + ${SRC_DIR}/process_stat.grpc.pb.cc + ${SRC_DIR}/rapidjson_helper.cc + ${SRC_DIR}/utf8.cc + ) +else() +#we need not many things to just compile centreon-monitoring-agent (centagent) + set(SOURCES + ${SRC_DIR}/perfdata.cc + ${SRC_DIR}/utf8.cc + ) +endif() # Include directories. include_directories("${INCLUDE_DIR}" @@ -69,7 +79,10 @@ set_property(TARGET centreon_common PROPERTY POSITION_INDEPENDENT_CODE ON) target_precompile_headers(centreon_common PRIVATE precomp_inc/precomp.hh) -add_subdirectory(http) +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + add_subdirectory(http) +endif() + add_subdirectory(grpc) add_subdirectory(process) diff --git a/common/engine_legacy_conf/CMakeLists.txt b/common/engine_legacy_conf/CMakeLists.txt index 843412b0fcb..03755663164 100644 --- a/common/engine_legacy_conf/CMakeLists.txt +++ b/common/engine_legacy_conf/CMakeLists.txt @@ -44,7 +44,7 @@ add_library( timeperiod.cc ) -add_dependencies(engine_legacy_conf pb_neb_lib) +add_dependencies(engine_legacy_conf pb_neb_lib pb_bam_lib) include_directories(${CMAKE_SOURCE_DIR}/common/inc) target_precompile_headers(engine_legacy_conf PRIVATE ${CMAKE_SOURCE_DIR}/common/precomp_inc/precomp.hh) diff --git a/common/precomp_inc/precomp.hh b/common/precomp_inc/precomp.hh index d7b064ec501..227f2533caa 100644 --- a/common/precomp_inc/precomp.hh +++ b/common/precomp_inc/precomp.hh @@ -47,8 +47,10 @@ #include <spdlog/spdlog.h> #include <boost/asio.hpp> +#ifndef _WINDOWS #include <boost/beast.hpp> #include <boost/beast/ssl.hpp> +#endif #include <boost/container/flat_set.hpp> #include "com/centreon/exceptions/msg_fmt.hh" diff --git a/common/process/src/process.cc b/common/process/src/process.cc index 615d16da564..f8e982c9a71 100644 --- a/common/process/src/process.cc +++ b/common/process/src/process.cc @@ -21,7 +21,9 @@ #include "com/centreon/common/process/process.hh" +#if !defined(BOOST_PROCESS_V2_WINDOWS) #include "com/centreon/common/process/detail/centreon_posix_process_launcher.hh" +#endif #include <boost/process/v2/process.hpp> @@ -146,7 +148,11 @@ process::process(const std::shared_ptr<boost::asio::io_context>& io_context, const std::shared_ptr<spdlog::logger>& logger, const std::string_view& cmd_line) : _io_context(io_context), _logger(logger) { +#ifdef _WINDOWS + auto split_res = boost::program_options::split_winmain(std::string(cmd_line)); +#else auto split_res = boost::program_options::split_unix(std::string(cmd_line)); +#endif if (split_res.begin() == split_res.end()) { SPDLOG_LOGGER_ERROR(_logger, "empty command line:\"{}\"", cmd_line); throw exceptions::msg_fmt("empty command line:\"{}\"", cmd_line); diff --git a/common/tests/CMakeLists.txt b/common/tests/CMakeLists.txt index afd6c972eb8..d44100b1313 100644 --- a/common/tests/CMakeLists.txt +++ b/common/tests/CMakeLists.txt @@ -16,17 +16,27 @@ # For more information : contact@centreon.com # -add_executable(ut_common - process_stat_test.cc - hex_dump_test.cc - log_v2/log_v2.cc - node_allocator_test.cc - perfdata_test.cc - process_test.cc - rapidjson_helper_test.cc - test_main.cc - utf8_test.cc - ${TESTS_SOURCES}) + +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + add_executable(ut_common + process_stat_test.cc + hex_dump_test.cc + log_v2/log_v2.cc + node_allocator_test.cc + perfdata_test.cc + process_test.cc + rapidjson_helper_test.cc + test_main.cc + utf8_test.cc + ${TESTS_SOURCES}) +else() + add_executable(ut_common + perfdata_test.cc + process_test.cc + test_main_win.cc + utf8_test.cc + ${TESTS_SOURCES}) +endif() set_target_properties( ut_common @@ -43,32 +53,60 @@ if(WITH_COVERAGE) set(GCOV gcov) endif() + +file(COPY ${PROJECT_SOURCE_DIR}/tests/scripts/echo.bat + DESTINATION ${CMAKE_BINARY_DIR}/tests) +file(COPY ${PROJECT_SOURCE_DIR}/tests/scripts/bad_script.bat + DESTINATION ${CMAKE_BINARY_DIR}/tests) + add_test(NAME tests COMMAND ut_common) +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + target_link_libraries( + ut_common + PRIVATE centreon_common + centreon_http + centreon_process + -L${Boost_LIBRARY_DIR_RELEASE} + boost_program_options + re2::re2 + log_v2 + crypto + ssl + GTest::gtest + GTest::gtest_main + GTest::gmock + GTest::gmock_main + absl::any + absl::log + absl::base + absl::bits + fmt::fmt pthread) + + add_dependencies(ut_common centreon_common centreon_http) + +else() + target_link_libraries( + ut_common + PRIVATE centreon_common + centreon_process + Boost::program_options + re2::re2 + GTest::gtest + GTest::gtest_main + GTest::gmock + GTest::gmock_main + absl::any + absl::log + absl::base + absl::bits + fmt::fmt) + + add_dependencies(ut_common centreon_common) + +endif() -target_link_libraries( - ut_common - PRIVATE centreon_common - centreon_http - centreon_process - -L${Boost_LIBRARY_DIR_RELEASE} - boost_program_options - re2::re2 - log_v2 - crypto - ssl - GTest::gtest - GTest::gtest_main - GTest::gmock - GTest::gmock_main - absl::any - absl::log - absl::base - absl::bits - fmt::fmt pthread) - -add_dependencies(ut_common centreon_common centreon_http) set_property(TARGET ut_common PROPERTY POSITION_INDEPENDENT_CODE ON) diff --git a/common/tests/process_test.cc b/common/tests/process_test.cc index 3c7eb63e2ab..a8acb23c101 100644 --- a/common/tests/process_test.cc +++ b/common/tests/process_test.cc @@ -20,10 +20,17 @@ #include <spdlog/sinks/stdout_color_sinks.h> #include "com/centreon/common/process/process.hh" -#include "pool.hh" using namespace com::centreon::common; +#ifdef _WINDOWS +#define ECHO_PATH "tests\\echo.bat" +#define END_OF_LINE "\r\n" +#else +#define ECHO_PATH "/bin/echo" +#define END_OF_LINE "\n" +#endif + extern std::shared_ptr<asio::io_context> g_io_context; static std::shared_ptr<spdlog::logger> _logger = @@ -111,11 +118,11 @@ class process_wait : public process { TEST_F(process_test, echo) { using namespace std::literals; std::shared_ptr<process_wait> to_wait( - new process_wait(g_io_context, _logger, "/bin/echo", {"hello"s})); + new process_wait(g_io_context, _logger, ECHO_PATH, {"hello"s})); to_wait->start_process(true); to_wait->wait(); ASSERT_EQ(to_wait->get_exit_status(), 0); - ASSERT_EQ(to_wait->get_stdout(), "hello\n"); + ASSERT_EQ(to_wait->get_stdout(), "hello" END_OF_LINE); ASSERT_EQ(to_wait->get_stderr(), ""); } @@ -128,8 +135,13 @@ TEST_F(process_test, throw_on_error) { TEST_F(process_test, script_error) { using namespace std::literals; +#ifdef _WINDOWS + std::shared_ptr<process_wait> to_wait( + new process_wait(g_io_context, _logger, "tests\\\\bad_script.bat")); +#else std::shared_ptr<process_wait> to_wait( new process_wait(g_io_context, _logger, "/bin/sh", {"taratata"s})); +#endif to_wait->start_process(true); to_wait->wait(); ASSERT_NE(to_wait->get_exit_status(), 0); @@ -139,12 +151,27 @@ TEST_F(process_test, script_error) { TEST_F(process_test, call_start_several_time) { std::shared_ptr<process_wait> to_wait( - new process_wait(g_io_context, _logger, "/bin/echo", {"hello"})); + new process_wait(g_io_context, _logger, ECHO_PATH, {"hello"})); + std::string expected; + for (int ii = 0; ii < 10; ++ii) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + to_wait->start_process(true); + expected += "hello" END_OF_LINE; + } + to_wait->wait(); + ASSERT_EQ(to_wait->get_exit_status(), 0); + ASSERT_EQ(to_wait->get_stdout(), expected); + ASSERT_EQ(to_wait->get_stderr(), ""); +} + +TEST_F(process_test, call_start_several_time_no_args) { + std::shared_ptr<process_wait> to_wait( + new process_wait(g_io_context, _logger, ECHO_PATH " hello")); std::string expected; for (int ii = 0; ii < 10; ++ii) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); to_wait->start_process(true); - expected += "hello\n"; + expected += "hello" END_OF_LINE; } to_wait->wait(); ASSERT_EQ(to_wait->get_exit_status(), 0); @@ -152,6 +179,8 @@ TEST_F(process_test, call_start_several_time) { ASSERT_EQ(to_wait->get_stderr(), ""); } +#ifndef _WINDOWS + TEST_F(process_test, stdin_to_stdout) { ::remove("toto.sh"); std::ofstream script("toto.sh"); @@ -193,3 +222,5 @@ TEST_F(process_test, shell_stdin_to_stdout) { std::this_thread::sleep_for(std::chrono::milliseconds(500)); ASSERT_EQ(expected, loopback->get_stdout()); } + +#endif diff --git a/common/tests/scripts/bad_script.bat b/common/tests/scripts/bad_script.bat new file mode 100644 index 00000000000..41297daaf43 --- /dev/null +++ b/common/tests/scripts/bad_script.bat @@ -0,0 +1,3 @@ +@echo off + +fzeurnezirfrf diff --git a/common/tests/scripts/echo.bat b/common/tests/scripts/echo.bat new file mode 100644 index 00000000000..8efa2965191 --- /dev/null +++ b/common/tests/scripts/echo.bat @@ -0,0 +1 @@ +@echo %* \ No newline at end of file diff --git a/common/tests/test_main_win.cc b/common/tests/test_main_win.cc new file mode 100644 index 00000000000..936fbda07b0 --- /dev/null +++ b/common/tests/test_main_win.cc @@ -0,0 +1,54 @@ +/** + * Copyright 2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include <gtest/gtest.h> + +std::shared_ptr<asio::io_context> g_io_context( + std::make_shared<asio::io_context>()); + +class CentreonEngineEnvironment : public testing::Environment { + public: + void TearDown() override { return; } +}; + +/** + * Tester entry point. + * + * @param[in] argc Argument count. + * @param[in] argv Argument values. + * + * @return 0 on success, any other value on failure. + */ +int main(int argc, char* argv[]) { + // GTest initialization. + testing::InitGoogleTest(&argc, argv); + + auto _worker{asio::make_work_guard(*g_io_context)}; + + // Set specific environment. + testing::AddGlobalTestEnvironment(new CentreonEngineEnvironment()); + + std::thread asio_thread([] { g_io_context->run(); }); + // Run all tests. + int ret = RUN_ALL_TESTS(); + g_io_context->stop(); + asio_thread.join(); + spdlog::shutdown(); + return ret; +} diff --git a/custom-triplets/x64-windows.cmake b/custom-triplets/x64-windows.cmake new file mode 100644 index 00000000000..17a9406b348 --- /dev/null +++ b/custom-triplets/x64-windows.cmake @@ -0,0 +1,6 @@ +set(VCPKG_TARGET_ARCHITECTURE x64) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_LIBRARY_LINKAGE static) + +#set(VCPKG_CMAKE_SYSTEM_NAME windows) +set(VCPKG_BUILD_TYPE release) diff --git a/vcpkg.json b/vcpkg.json index 8e2b18a4af3..408c71efe59 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -6,25 +6,58 @@ "cxx17" ] }, - "libssh2", - "curl", "fmt", "grpc", - "ryml", "spdlog", "boost-asio", - "boost-beast", - "boost-container", - "boost-circular-buffer", "boost-multi-index", - "boost-interprocess", - "boost-exception", "boost-process", "boost-program-options", - "boost-serialization", - "boost-url", - "nlohmann-json", "rapidjson", - "gtest" + "gtest", + { + "name": "libssh2", + "platform": "linux" + }, + { + "name": "curl", + "platform": "linux" + }, + { + "name": "ryml", + "platform": "linux" + }, + { + "name": "boost-beast", + "platform": "linux" + }, + { + "name": "boost-container", + "platform": "linux" + }, + { + "name": "boost-circular-buffer", + "platform": "linux" + }, + { + "name": "boost-interprocess", + "platform": "linux" + }, + { + "name": "boost-exception", + "platform": "linux" + }, + { + "name": "boost-serialization", + "platform": "linux" + }, + { + "name": "boost-url", + "platform": "linux" + }, + { + "name": "nlohmann-json", + "platform": "linux" + } ] } \ No newline at end of file From eedbc71581553e328dd06baee81f99c598d1fad9 Mon Sep 17 00:00:00 2001 From: David Boucher <dboucher@centreon.com> Date: Mon, 22 Jul 2024 17:26:02 +0200 Subject: [PATCH 894/948] fix(broker/rrd): Status different than 0, 1 or 2, was written in RRD as an empty value. Now we write 'U' instead. REFS: MON-141934 --- bbdo/storage.proto | 34 +++++++++++------------ broker/rrd/src/output.cc | 48 ++++++++++++++++++++------------- cmake-vcpkg.sh | 2 +- tests/broker-engine/rrd.robot | 51 ++++++++++++++++++++++++++++++++++- tests/resources/Broker.py | 35 ++++++++++++++++++++++++ tests/resources/Common.py | 2 +- 6 files changed, 134 insertions(+), 38 deletions(-) diff --git a/bbdo/storage.proto b/bbdo/storage.proto index 9c097a3a089..a0544da87bd 100644 --- a/bbdo/storage.proto +++ b/bbdo/storage.proto @@ -1,20 +1,20 @@ -/* -** Copyright 2022 Centreon -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -** For more information : contact@centreon.com -*/ +/** + * Copyright 2022 Centreon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + */ syntax = "proto3"; diff --git a/broker/rrd/src/output.cc b/broker/rrd/src/output.cc index 8bd15e38ac2..cbca109d7eb 100644 --- a/broker/rrd/src/output.cc +++ b/broker/rrd/src/output.cc @@ -348,15 +348,21 @@ int output<T>::write(std::shared_ptr<io::data> const& d) { _backend.open(status_path, s.rrd_len(), s.time() - 1, interval); } std::string value; - if (s.state() == 0) - value = "100"; - else if (s.state() == 1) - value = "75"; - else if (s.state() == 2) - value = "0"; - else - value = ""; - _backend.update(s.time(), value); + switch (s.state()) { + case 0: + value = "100"; + break; + case 1: + value = "75"; + break; + case 2: + value = "0"; + break; + default: + value = "U"; + break; + } + _backend.update(s.time(), value); } else // Cache value. it->second.push_back(d); @@ -387,15 +393,21 @@ int output<T>::write(std::shared_ptr<io::data> const& d) { _backend.open(status_path, e->rrd_len, e->time - 1, interval); } std::string value; - if (e->state == 0) - value = "100"; - else if (e->state == 1) - value = "75"; - else if (e->state == 2) - value = "0"; - else - value = ""; - _backend.update(e->time, value); + switch (e->state) { + case 0: + value = "100"; + break; + case 1: + value = "75"; + break; + case 2: + value = "0"; + break; + default: + value = "U"; + break; + } + _backend.update(e->time, value); } else // Cache value. it->second.push_back(d); diff --git a/cmake-vcpkg.sh b/cmake-vcpkg.sh index f160be00773..ed652b7922a 100755 --- a/cmake-vcpkg.sh +++ b/cmake-vcpkg.sh @@ -258,7 +258,7 @@ fi if [ ! -d vcpkg ] ; then echo "No vcpkg directory. Cloning the repo" - git clone -b 2024.01.12 https://github.com/Microsoft/vcpkg.git + git clone --depth 1 --single-branch --no-tags https://github.com/Microsoft/vcpkg.git ./vcpkg/bootstrap-vcpkg.sh fi diff --git a/tests/broker-engine/rrd.robot b/tests/broker-engine/rrd.robot index b242a3ada0a..f971440fbea 100644 --- a/tests/broker-engine/rrd.robot +++ b/tests/broker-engine/rrd.robot @@ -432,9 +432,58 @@ RRD1 ${result} Ctn Find In Log With Timeout ${rrdLog} ${start} ${content1} 45 Should Not Be True ${result} Database did not receive command to rebuild metrics +BRRDSTATUS + [Documentation] We are working with BBDO3. This test checks status are correctly handled independently from their value. + [Tags] rrd status bbdo3 + Ctn Config Engine ${1} + Ctn Config Broker rrd + Ctn Config Broker central + Ctn Config Broker module + Ctn Config BBDO3 ${1} + Ctn Broker Config Log central sql info + Ctn Broker Config Log rrd rrd debug + Ctn Broker Config Log rrd core error + Ctn Broker Config Flush Log central 0 + Ctn Broker Config Flush Log rrd 0 + Ctn Set Services Passive ${0} service_1 + + ${start} Get Current Date + Ctn Start Broker + Ctn Start engine + Ctn Wait For Engine To Be Ready ${start} ${1} + + Ctn Process Service Result Hard host_1 service_1 2 output critical for service_1 + ${index} Ctn Get Service Index 1 1 + log to console Service 1:1 has index ${index} + ${content} Create List RRD: new pb status data for index ${index} (state 2) + ${result} Ctn Find In Log With Timeout ${rrdLog} ${start} ${content} 60 + Should Be True ${result} host_1:service_1 is not CRITICAL as expected + + ${start} Ctn Get Round Current Date + Ctn Process Service Result Hard host_1 service_1 1 output warning for service_1 + ${content} Create List RRD: new pb status data for index ${index} (state 1) + ${result} Ctn Find In Log With Timeout ${rrdLog} ${start} ${content} 60 + Should Be True ${result} host_1:service_1 is not WARNING as expected + + ${start} Ctn Get Round Current Date + Ctn Process Service Result Hard host_1 service_1 0 output ok for service_1 + ${content} Create List RRD: new pb status data for index ${index} (state 0) + ${result} Ctn Find In Log With Timeout ${rrdLog} ${start} ${content} 60 + Should Be True ${result} host_1:service_1 is not OK as expected + + ${start} Ctn Get Round Current Date + Ctn Process Service Result Hard host_1 service_1 3 output UNKNOWN for service_1 + ${content} Create List RRD: new pb status data for index ${index} (state 3) + ${result} Ctn Find In Log With Timeout ${rrdLog} ${start} ${content} 60 + Should Be True ${result} host_1:service_1 is not UNKNOWN as expected + + ${content} Create List RRD: ignored update non-float value '' in file '${VarRoot}/lib/centreon/status/82884.rrd' + ${result} Ctn Find In Log With Timeout ${rrdLog} ${start} ${content} 1 + Should Be Equal ${result} ${False} We shouldn't have any error about empty value in RRD + *** Keywords *** Ctn Test Clean - Ctn Stop engine + Ctn Stop Engine Ctn Kindly Stop Broker Ctn Save Logs If Failed diff --git a/tests/resources/Broker.py b/tests/resources/Broker.py index fe65675d4eb..06b9527896e 100755 --- a/tests/resources/Broker.py +++ b/tests/resources/Broker.py @@ -1653,6 +1653,41 @@ def ctn_check_rrd_info(metric_id: int, key: str, value, timeout: int = 60): return False +def ctn_get_service_index(host_id: int, service_id: int, timeout: int = 60): + """ + Try to get the index data of a service. + + Args: + host_id (int): The ID of the host. + service_id (int): The ID of the service. + + Returns: + An integer representing the index data. + """ + select_request = f"SELECT id FROM index_data WHERE host_id={host_id} AND service_id={service_id}" + limit = time.time() + timeout + while time.time() < limit: + # Connect to the database + connection = pymysql.connect(host=DB_HOST, + user=DB_USER, + password=DB_PASS, + database=DB_NAME_STORAGE, + charset='utf8mb4', + cursorclass=pymysql.cursors.DictCursor) + with connection: + with connection.cursor() as cursor: + cursor.execute(select_request) + result = cursor.fetchall() + my_id = [r['id'] for r in result] + if len(my_id) > 0: + logger.console( + f"Index data {id} found for service {host_id}:{service_id}") + return my_id[0] + time.sleep(2) + logger.console(f"no index data found for service {host_id}:{service_id}") + return None + + def ctn_get_metrics_for_service(service_id: int, metric_name: str = "%", timeout: int = 60): """ Try to get the metric IDs of a service. diff --git a/tests/resources/Common.py b/tests/resources/Common.py index 3978dd95604..daec2c09ed5 100644 --- a/tests/resources/Common.py +++ b/tests/resources/Common.py @@ -214,7 +214,7 @@ def ctn_find_in_log_with_timeout(log: str, date, content, timeout: int, **kwargs c = "" kwargs['regex'] = False - while time.time() < limit: + while time.time() <= limit: ok, c = ctn_find_in_log(log, date, content, **kwargs) if ok: return True From 885756063fce773b294d57dfa6e37b7f3594d382 Mon Sep 17 00:00:00 2001 From: David Boucher <dboucher@centreon.com> Date: Tue, 23 Jul 2024 13:04:34 +0200 Subject: [PATCH 895/948] =?UTF-8?q?enh(common/log=5Fv2):=20default=20level?= =?UTF-8?q?=20is=20error=20except=20for=20core=20and=20config=E2=80=A6=20(?= =?UTF-8?q?#1559)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit REFS: MON-143565 --- broker/core/src/broker_impl.cc | 6 +- broker/neb/src/callbacks.cc | 299 +++++++++++++++++---------------- common/log_v2/log_v2.cc | 5 +- tests/broker-engine/rrd.robot | 2 +- tests/broker/log.robot | 139 +++++++++++++++ tests/resources/Broker.py | 44 ++++- 6 files changed, 341 insertions(+), 154 deletions(-) diff --git a/broker/core/src/broker_impl.cc b/broker/core/src/broker_impl.cc index 21f817e07d9..bff46898aaf 100644 --- a/broker/core/src/broker_impl.cc +++ b/broker/core/src/broker_impl.cc @@ -336,8 +336,10 @@ grpc::Status broker_impl::GetLogInfo(grpc::ServerContext* context return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, msg); } } else { - for (auto& p : lvs) - map[p.first] = p.second; + for (auto& p : lvs) { + auto level = to_string_view(p.second); + map[p.first] = std::string(level.data(), level.size()); + } return grpc::Status::OK; } } diff --git a/broker/neb/src/callbacks.cc b/broker/neb/src/callbacks.cc index 5623954672e..bad963e9914 100644 --- a/broker/neb/src/callbacks.cc +++ b/broker/neb/src/callbacks.cc @@ -165,7 +165,8 @@ char const* get_program_version(); */ int neb::callback_acknowledgement(int callback_type, void* data) { // Log message. - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: generating acknowledgement event"); + SPDLOG_LOGGER_DEBUG(neb_logger, + "callbacks: generating acknowledgement event"); (void)callback_type; try { @@ -232,8 +233,8 @@ int neb::callback_acknowledgement(int callback_type, void* data) { int neb::callback_pb_acknowledgement(int callback_type [[maybe_unused]], void* data) { // Log message. - SPDLOG_LOGGER_INFO(neb_logger, - "callbacks: generating pb acknowledgement event"); + SPDLOG_LOGGER_DEBUG(neb_logger, + "callbacks: generating pb acknowledgement event"); // In/Out variables. auto ack{std::make_shared<neb::pb_acknowledgement>()}; @@ -288,7 +289,7 @@ int neb::callback_pb_acknowledgement(int callback_type [[maybe_unused]], */ int neb::callback_comment(int callback_type, void* data) { // Log message. - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: generating comment event"); + SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: generating comment event"); (void)callback_type; try { @@ -358,7 +359,7 @@ int neb::callback_comment(int callback_type, void* data) { */ int neb::callback_pb_comment(int, void* data) { // Log message. - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: generating pb comment event"); + SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: generating pb comment event"); const nebstruct_comment_data* comment_data = static_cast<nebstruct_comment_data*>(data); @@ -453,14 +454,9 @@ int neb::callback_pb_custom_variable(int, void* data) { const nebstruct_custom_variable_data* cvar( static_cast<const nebstruct_custom_variable_data*>(data)); - if (neb_logger->level() <= spdlog::level::debug) { - SPDLOG_LOGGER_DEBUG( - neb_logger, "callbacks: generating custom variable event {} value:{}", - cvar->var_name, cvar->var_value); - } else { - SPDLOG_LOGGER_INFO(neb_logger, - "callbacks: generating custom variable event"); - } + SPDLOG_LOGGER_DEBUG(neb_logger, + "callbacks: generating custom variable event {} value:{}", + cvar->var_name, cvar->var_value); neb::pb_custom_variable::shared_ptr cv = std::make_shared<neb::pb_custom_variable>(); @@ -486,11 +482,11 @@ int neb::callback_pb_custom_variable(int, void* data) { std::string value(common::check_string_utf8(cvar->var_value)); obj.set_value(value); obj.set_default_value(value); - SPDLOG_LOGGER_INFO(neb_logger, - "callbacks: new custom variable '{}' on host {}", - name, host_id); + SPDLOG_LOGGER_DEBUG( + neb_logger, "callbacks: new custom variable '{}' on host {}", + name, host_id); } else { - SPDLOG_LOGGER_INFO( + SPDLOG_LOGGER_DEBUG( neb_logger, "callbacks: deleted custom variable '{}' on host {}", name, host_id); @@ -522,13 +518,13 @@ int neb::callback_pb_custom_variable(int, void* data) { std::string value(common::check_string_utf8(cvar->var_value)); obj.set_value(value); obj.set_default_value(value); - SPDLOG_LOGGER_INFO( + SPDLOG_LOGGER_DEBUG( neb_logger, "callbacks: new custom variable '{}' on service ({}, {})", name, p.first, p.second); } else { - SPDLOG_LOGGER_INFO( + SPDLOG_LOGGER_DEBUG( neb_logger, "callbacks: deleted custom variable '{}' on service ({},{})", name, p.first, p.second); @@ -562,7 +558,8 @@ int neb::callback_pb_custom_variable(int, void* data) { int neb::callback_custom_variable(int callback_type, void* data) { // Log message. - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: generating custom variable event"); + SPDLOG_LOGGER_DEBUG(neb_logger, + "callbacks: generating custom variable event"); (void)callback_type; try { @@ -589,9 +586,9 @@ int neb::callback_custom_variable(int callback_type, void* data) { common::check_string_utf8(cvar->var_value); // Send custom variable event. - SPDLOG_LOGGER_INFO(neb_logger, - "callbacks: new custom variable '{}' on host {}", - new_cvar->name, new_cvar->host_id); + SPDLOG_LOGGER_DEBUG( + neb_logger, "callbacks: new custom variable '{}' on host {}", + new_cvar->name, new_cvar->host_id); neb::gl_publisher.write(new_cvar); } } @@ -608,7 +605,7 @@ int neb::callback_custom_variable(int callback_type, void* data) { old_cvar->update_time = cvar->timestamp.tv_sec; // Send custom variable event. - SPDLOG_LOGGER_INFO( + SPDLOG_LOGGER_DEBUG( neb_logger, "callbacks: deleted custom variable '{}' on host {}", old_cvar->name, old_cvar->host_id); @@ -639,7 +636,7 @@ int neb::callback_custom_variable(int callback_type, void* data) { common::check_string_utf8(cvar->var_value); // Send custom variable event. - SPDLOG_LOGGER_INFO( + SPDLOG_LOGGER_DEBUG( neb_logger, "callbacks: new custom variable '{}' on service ({}, {})", new_cvar->name, new_cvar->host_id, new_cvar->service_id); @@ -663,7 +660,7 @@ int neb::callback_custom_variable(int callback_type, void* data) { old_cvar->update_time = cvar->timestamp.tv_sec; // Send custom variable event. - SPDLOG_LOGGER_INFO( + SPDLOG_LOGGER_DEBUG( neb_logger, "callbacks: deleted custom variable '{}' on service ({},{})", old_cvar->name, old_cvar->host_id, old_cvar->service_id); @@ -695,7 +692,7 @@ int neb::callback_custom_variable(int callback_type, void* data) { */ int neb::callback_dependency(int callback_type, void* data) { // Log message. - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: generating dependency event"); + SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: generating dependency event"); (void)callback_type; try { @@ -723,7 +720,7 @@ int neb::callback_dependency(int callback_type, void* data) { if (!dep->get_dependent_hostname().empty()) { dep_host_id = engine::get_host_id(dep->get_dependent_hostname()); } else { - SPDLOG_LOGGER_INFO( + SPDLOG_LOGGER_DEBUG( neb_logger, "callbacks: dependency callback called without valid dependent " "host"); @@ -753,8 +750,8 @@ int neb::callback_dependency(int callback_type, void* data) { hst_dep->execution_failure_options = options; } hst_dep->inherits_parent = dep->get_inherits_parent(); - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: host {} depends on host {}", - dep_host_id, host_id); + SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: host {} depends on host {}", + dep_host_id, host_id); // Publish dependency event. neb::gl_publisher.write(hst_dep); @@ -820,7 +817,7 @@ int neb::callback_dependency(int callback_type, void* data) { svc_dep->execution_failure_options = options; } svc_dep->inherits_parent = dep->get_inherits_parent(); - SPDLOG_LOGGER_INFO( + SPDLOG_LOGGER_DEBUG( neb_logger, "callbacks: service ({}, {}) depends on service ({}, {})", dep_ids.first, dep_ids.second, ids.first, ids.second); @@ -851,7 +848,7 @@ int neb::callback_dependency(int callback_type, void* data) { */ int neb::callback_pb_dependency(int, void* data) { // Log message. - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: generating dependency event"); + SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: generating dependency event"); // Input variables. nebstruct_adaptive_dependency_data* nsadd( @@ -878,7 +875,7 @@ int neb::callback_pb_dependency(int, void* data) { if (!dep->get_dependent_hostname().empty()) { dep_host_id = engine::get_host_id(dep->get_dependent_hostname()); } else { - SPDLOG_LOGGER_INFO( + SPDLOG_LOGGER_DEBUG( neb_logger, "callbacks: dependency callback called without valid dependent " "host"); @@ -909,8 +906,8 @@ int neb::callback_pb_dependency(int, void* data) { hst_dep.set_execution_failure_options(options); } hst_dep.set_inherits_parent(dep->get_inherits_parent()); - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: host {} depends on host {}", - dep_host_id, host_id); + SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: host {} depends on host {}", + dep_host_id, host_id); // Publish dependency event. neb::gl_publisher.write(hd); @@ -977,7 +974,7 @@ int neb::callback_pb_dependency(int, void* data) { svc_dep.set_execution_failure_options(options); } svc_dep.set_inherits_parent(dep->get_inherits_parent()); - SPDLOG_LOGGER_INFO( + SPDLOG_LOGGER_DEBUG( neb_logger, "callbacks: service ({}, {}) depends on service ({}, {})", dep_ids.first, dep_ids.second, ids.first, ids.second); @@ -1001,7 +998,7 @@ int neb::callback_pb_dependency(int, void* data) { */ int neb::callback_downtime(int callback_type, void* data) { // Log message. - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: generating downtime event"); + SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: generating downtime event"); (void)callback_type; const nebstruct_downtime_data* downtime_data{ static_cast<nebstruct_downtime_data*>(data)}; @@ -1091,7 +1088,7 @@ int neb::callback_downtime(int callback_type, void* data) { */ int neb::callback_pb_downtime(int callback_type, void* data) { // Log message. - neb_logger->info("callbacks: generating pb downtime event"); + neb_logger->debug("callbacks: generating pb downtime event"); (void)callback_type; const nebstruct_downtime_data* downtime_data = @@ -1187,7 +1184,7 @@ int neb::callback_external_command(int callback_type, void* data) { if (necd && (necd->type == NEBTYPE_EXTERNALCOMMAND_START)) { try { if (necd->command_type == CMD_CHANGE_CUSTOM_HOST_VAR) { - SPDLOG_LOGGER_INFO( + SPDLOG_LOGGER_DEBUG( neb_logger, "callbacks: generating host custom variable update event"); @@ -1224,7 +1221,7 @@ int neb::callback_external_command(int callback_type, void* data) { } } } else if (necd->command_type == CMD_CHANGE_CUSTOM_SVC_VAR) { - SPDLOG_LOGGER_INFO( + SPDLOG_LOGGER_DEBUG( neb_logger, "callbacks: generating service custom variable update event"); @@ -1298,7 +1295,7 @@ int neb::callback_pb_external_command(int, void* data) { size_t args_size = std::distance(args.begin(), args.end()); auto split_iter = args.begin(); if (necd->command_type == CMD_CHANGE_CUSTOM_HOST_VAR) { - SPDLOG_LOGGER_INFO( + SPDLOG_LOGGER_DEBUG( neb_logger, "callbacks: generating host custom variable update event"); @@ -1333,7 +1330,7 @@ int neb::callback_pb_external_command(int, void* data) { } } } else if (necd->command_type == CMD_CHANGE_CUSTOM_SVC_VAR) { - SPDLOG_LOGGER_INFO( + SPDLOG_LOGGER_DEBUG( neb_logger, "callbacks: generating service custom variable update event"); @@ -1391,7 +1388,7 @@ int neb::callback_pb_external_command(int, void* data) { */ int neb::callback_group(int callback_type, void* data) { // Log message. - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: generating group event"); + SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: generating group event"); (void)callback_type; try { @@ -1416,12 +1413,12 @@ int neb::callback_group(int callback_type, void* data) { // Send host group event. if (new_hg->id) { if (new_hg->enabled) - SPDLOG_LOGGER_INFO( + SPDLOG_LOGGER_DEBUG( neb_logger, "callbacks: new host group {} ('{}') on instance {}", new_hg->id, new_hg->name, new_hg->poller_id); else - SPDLOG_LOGGER_INFO( + SPDLOG_LOGGER_DEBUG( neb_logger, "callbacks: disable host group {} ('{}') on instance {}", new_hg->id, new_hg->name, new_hg->poller_id); @@ -1447,12 +1444,12 @@ int neb::callback_group(int callback_type, void* data) { // Send service group event. if (new_sg->id) { if (new_sg->enabled) - SPDLOG_LOGGER_INFO( + SPDLOG_LOGGER_DEBUG( neb_logger, "callbacks:: new service group {} ('{}) on instance {}", new_sg->id, new_sg->name, new_sg->poller_id); else - SPDLOG_LOGGER_INFO( + SPDLOG_LOGGER_DEBUG( neb_logger, "callbacks:: disable service group {} ('{}) on instance {}", new_sg->id, new_sg->name, new_sg->poller_id); @@ -1487,8 +1484,9 @@ int neb::callback_pb_group(int callback_type, void* data) { nebstruct_group_data const* group_data( static_cast<nebstruct_group_data*>(data)); - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: generating pb group event type:{}", - group_data->type); + SPDLOG_LOGGER_DEBUG(neb_logger, + "callbacks: generating pb group event type:{}", + group_data->type); // Host group. if ((NEBTYPE_HOSTGROUP_ADD == group_data->type) || @@ -1510,19 +1508,19 @@ int neb::callback_pb_group(int callback_type, void* data) { // Send host group event. if (host_group->get_id()) { if (new_hg->obj().enabled()) - SPDLOG_LOGGER_INFO(neb_logger, - "callbacks: new pb host group {} ('{}' {} " - "members) on instance {}", - host_group->get_id(), new_hg->obj().name(), - host_group->members.size(), - new_hg->obj().poller_id()); + SPDLOG_LOGGER_DEBUG(neb_logger, + "callbacks: new pb host group {} ('{}' {} " + "members) on instance {}", + host_group->get_id(), new_hg->obj().name(), + host_group->members.size(), + new_hg->obj().poller_id()); else - SPDLOG_LOGGER_INFO(neb_logger, - "callbacks: disable pb host group {} ('{}' {} " - "members) on instance {}", - host_group->get_id(), new_hg->obj().name(), - host_group->members.size(), - new_hg->obj().poller_id()); + SPDLOG_LOGGER_DEBUG(neb_logger, + "callbacks: disable pb host group {} ('{}' {} " + "members) on instance {}", + host_group->get_id(), new_hg->obj().name(), + host_group->members.size(), + new_hg->obj().poller_id()); neb::gl_publisher.write(new_hg); } @@ -1548,13 +1546,13 @@ int neb::callback_pb_group(int callback_type, void* data) { // Send service group event. if (service_group->get_id()) { if (new_sg->obj().enabled()) - SPDLOG_LOGGER_INFO( + SPDLOG_LOGGER_DEBUG( neb_logger, "callbacks:: new pb service group {} ('{}) on instance {}", service_group->get_id(), new_sg->obj().name(), new_sg->obj().poller_id()); else - SPDLOG_LOGGER_INFO( + SPDLOG_LOGGER_DEBUG( neb_logger, "callbacks:: disable pb service group {} ('{}) on instance {}", service_group->get_id(), new_sg->obj().name(), @@ -1582,7 +1580,7 @@ int neb::callback_pb_group(int callback_type, void* data) { */ int neb::callback_group_member(int callback_type, void* data) { // Log message. - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: generating group member event"); + SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: generating group member event"); (void)callback_type; try { @@ -1607,14 +1605,14 @@ int neb::callback_group_member(int callback_type, void* data) { if (host_id != 0 && hgm->group_id != 0) { hgm->host_id = host_id; if (member_data->type == NEBTYPE_HOSTGROUPMEMBER_DELETE) { - SPDLOG_LOGGER_INFO(neb_logger, - "callbacks: host {} is not a member of group " - "{} on instance {} " - "anymore", - hgm->host_id, hgm->group_id, hgm->poller_id); + SPDLOG_LOGGER_DEBUG(neb_logger, + "callbacks: host {} is not a member of group " + "{} on instance {} " + "anymore", + hgm->host_id, hgm->group_id, hgm->poller_id); hgm->enabled = false; } else { - SPDLOG_LOGGER_INFO( + SPDLOG_LOGGER_DEBUG( neb_logger, "callbacks: host {} is a member of group {} on instance {}", hgm->host_id, hgm->group_id, hgm->poller_id); @@ -1648,14 +1646,14 @@ int neb::callback_group_member(int callback_type, void* data) { sgm->service_id = p.second; if (sgm->host_id && sgm->service_id && sgm->group_id) { if (member_data->type == NEBTYPE_SERVICEGROUPMEMBER_DELETE) { - SPDLOG_LOGGER_INFO( + SPDLOG_LOGGER_DEBUG( neb_logger, "callbacks: service ({},{}) is not a member of group {} on " "instance {} anymore", sgm->host_id, sgm->service_id, sgm->group_id, sgm->poller_id); sgm->enabled = false; } else { - SPDLOG_LOGGER_INFO( + SPDLOG_LOGGER_DEBUG( neb_logger, "callbacks: service ({}, {}) is a member of group {} on " "instance {}", @@ -1691,7 +1689,8 @@ int neb::callback_group_member(int callback_type, void* data) { */ int neb::callback_pb_group_member(int callback_type, void* data) { // Log message. - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: generating pb group member event"); + SPDLOG_LOGGER_DEBUG(neb_logger, + "callbacks: generating pb group member event"); (void)callback_type; // Input variable. @@ -1716,15 +1715,15 @@ int neb::callback_pb_group_member(int callback_type, void* data) { if (host_id != 0 && hgm.hostgroup_id() != 0) { hgm.set_host_id(host_id); if (member_data->type == NEBTYPE_HOSTGROUPMEMBER_DELETE) { - SPDLOG_LOGGER_INFO(neb_logger, - "callbacks: host {} is not a member of group " - "{} on instance {} " - "anymore", - hgm.host_id(), hgm.hostgroup_id(), - hgm.poller_id()); + SPDLOG_LOGGER_DEBUG(neb_logger, + "callbacks: host {} is not a member of group " + "{} on instance {} " + "anymore", + hgm.host_id(), hgm.hostgroup_id(), + hgm.poller_id()); hgm.set_enabled(false); } else { - SPDLOG_LOGGER_INFO( + SPDLOG_LOGGER_DEBUG( neb_logger, "callbacks: host {} is a member of group {} on instance {}", hgm.host_id(), hgm.hostgroup_id(), hgm.poller_id()); @@ -1759,7 +1758,7 @@ int neb::callback_pb_group_member(int callback_type, void* data) { sgm.set_service_id(p.second); if (sgm.host_id() && sgm.service_id() && sgm.servicegroup_id()) { if (member_data->type == NEBTYPE_SERVICEGROUPMEMBER_DELETE) { - SPDLOG_LOGGER_INFO( + SPDLOG_LOGGER_DEBUG( neb_logger, "callbacks: service ({},{}) is not a member of group {} on " "instance {} anymore", @@ -1767,7 +1766,7 @@ int neb::callback_pb_group_member(int callback_type, void* data) { sgm.poller_id()); sgm.set_enabled(false); } else { - SPDLOG_LOGGER_INFO( + SPDLOG_LOGGER_DEBUG( neb_logger, "callbacks: service ({}, {}) is a member of group {} on " "instance {}", @@ -1799,7 +1798,7 @@ int neb::callback_pb_group_member(int callback_type, void* data) { */ int neb::callback_host(int callback_type, void* data) { // Log message. - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: generating host event"); + SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: generating host event"); (void)callback_type; try { @@ -1929,7 +1928,7 @@ int neb::callback_host(int callback_type, void* data) { my_host->host_id = host_id; // Send host event. - SPDLOG_LOGGER_INFO( + SPDLOG_LOGGER_DEBUG( neb_logger, "callbacks: new host {} ('{}') on instance {}", my_host->host_id, my_host->host_name, my_host->poller_id); neb::gl_publisher.write(my_host); @@ -1961,8 +1960,8 @@ int neb::callback_host(int callback_type, void* data) { */ int neb::callback_pb_host(int callback_type, void* data) { // Log message. - SPDLOG_LOGGER_INFO(neb_logger, - "callbacks: generating pb host event protobuf"); + SPDLOG_LOGGER_DEBUG(neb_logger, + "callbacks: generating pb host event protobuf"); (void)callback_type; nebstruct_adaptive_host_data* dh = @@ -2013,10 +2012,10 @@ int neb::callback_pb_host(int callback_type, void* data) { hst.set_host_id(host_id); // Send host event. - SPDLOG_LOGGER_INFO(neb_logger, - "callbacks: new host {} ('{}') on instance {}", - hst.host_id(), eh->name(), - config::applier::state::instance().poller_id()); + SPDLOG_LOGGER_DEBUG(neb_logger, + "callbacks: new host {} ('{}') on instance {}", + hst.host_id(), eh->name(), + config::applier::state::instance().poller_id()); neb::gl_publisher.write(h); } else SPDLOG_LOGGER_ERROR(neb_logger, @@ -2153,9 +2152,9 @@ int neb::callback_pb_host(int callback_type, void* data) { host.set_host_id(host_id); // Send host event. - SPDLOG_LOGGER_INFO(neb_logger, - "callbacks: new host {} ('{}') on instance {}", - host.host_id(), host.name(), host.instance_id()); + SPDLOG_LOGGER_DEBUG(neb_logger, + "callbacks: new host {} ('{}') on instance {}", + host.host_id(), host.name(), host.instance_id()); neb::gl_publisher.write(h); /* No need to send this service custom variables changes, custom @@ -2194,7 +2193,7 @@ int neb::callback_host_check(int callback_type, void* data) { return 0; // Log message. - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: generating host check event"); + SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: generating host check event"); try { auto host_check{std::make_shared<neb::host_check>()}; @@ -2260,7 +2259,7 @@ int neb::callback_pb_host_check(int callback_type, void* data) { "callbacks: generating host check event for {} command_line={}", hcdata->host_name, hcdata->command_line); } else { - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: generating host check event"); + SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: generating host check event"); } std::shared_ptr<neb::pb_host_check> host_check{ @@ -2301,7 +2300,7 @@ int neb::callback_pb_host_check(int callback_type, void* data) { */ int neb::callback_host_status(int callback_type, void* data) { // Log message. - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: generating host status event"); + SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: generating host status event"); (void)callback_type; try { @@ -2430,7 +2429,7 @@ int neb::callback_host_status(int callback_type, void* data) { */ int neb::callback_pb_host_status(int callback_type, void* data) noexcept { // Log message. - SPDLOG_LOGGER_INFO( + SPDLOG_LOGGER_DEBUG( neb_logger, "callbacks: generating pb host status check result event protobuf"); (void)callback_type; @@ -2531,7 +2530,7 @@ int neb::callback_pb_host_status(int callback_type, void* data) noexcept { */ int neb::callback_log(int callback_type, void* data) { // Log message. - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: generating log event"); + SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: generating log event"); (void)callback_type; try { @@ -2570,7 +2569,7 @@ int neb::callback_log(int callback_type, void* data) { */ int neb::callback_pb_log(int callback_type [[maybe_unused]], void* data) { // Log message. - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: generating pb log event"); + SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: generating pb log event"); try { // In/Out variables. @@ -2618,7 +2617,8 @@ int neb::callback_process(int, void* data) { // Check process event type. process_data = static_cast<nebstruct_process_data*>(data); if (NEBTYPE_PROCESS_EVENTLOOPSTART == process_data->type) { - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: generating process start event"); + SPDLOG_LOGGER_DEBUG(neb_logger, + "callbacks: generating process start event"); // Register callbacks. SPDLOG_LOGGER_DEBUG( @@ -2656,7 +2656,7 @@ int neb::callback_process(int, void* data) { gl_publisher.write(instance); send_initial_configuration(); } else if (NEBTYPE_PROCESS_EVENTLOOPEND == process_data->type) { - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: generating process end event"); + SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: generating process end event"); // Output variable. auto instance{std::make_shared<neb::instance>()}; @@ -2707,7 +2707,8 @@ int neb::callback_pb_process(int callback_type, void* data) { // Check process event type. process_data = static_cast<nebstruct_process_data*>(data); if (NEBTYPE_PROCESS_EVENTLOOPSTART == process_data->type) { - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: generating process start event"); + SPDLOG_LOGGER_DEBUG(neb_logger, + "callbacks: generating process start event"); // Register callbacks. SPDLOG_LOGGER_DEBUG( @@ -2742,7 +2743,7 @@ int neb::callback_pb_process(int callback_type, void* data) { gl_publisher.write(inst_obj); send_initial_pb_configuration(); } else if (NEBTYPE_PROCESS_EVENTLOOPEND == process_data->type) { - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: generating process end event"); + SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: generating process end event"); // Fill output var. inst.set_instance_id(config::applier::state::instance().poller_id()); inst.set_running(false); @@ -2773,7 +2774,8 @@ int neb::callback_pb_process(int callback_type, void* data) { */ int neb::callback_program_status(int callback_type, void* data) { // Log message. - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: generating instance status event"); + SPDLOG_LOGGER_DEBUG(neb_logger, + "callbacks: generating instance status event"); (void)callback_type; try { @@ -2832,8 +2834,8 @@ int neb::callback_program_status(int callback_type, void* data) { */ int neb::callback_pb_program_status(int, void* data) { // Log message. - SPDLOG_LOGGER_INFO(neb_logger, - "callbacks: generating pb instance status event"); + SPDLOG_LOGGER_DEBUG(neb_logger, + "callbacks: generating pb instance status event"); // In/Out variables. std::shared_ptr<neb::pb_instance_status> is_obj{ @@ -2844,10 +2846,10 @@ int neb::callback_pb_program_status(int, void* data) { const nebstruct_program_status_data& program_status_data = *static_cast<nebstruct_program_status_data*>(data); - SPDLOG_LOGGER_INFO(neb_logger, - "callbacks: generating pb instance status event " - "global_service_event_handler={}", - program_status_data.global_host_event_handler); + SPDLOG_LOGGER_DEBUG(neb_logger, + "callbacks: generating pb instance status event " + "global_service_event_handler={}", + program_status_data.global_host_event_handler); is.set_instance_id(config::applier::state::instance().poller_id()); is.set_active_host_checks(program_status_data.active_host_checks_enabled); @@ -2892,7 +2894,7 @@ int neb::callback_pb_program_status(int, void* data) { */ int neb::callback_relation(int callback_type, void* data) { // Log message. - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: generating relation event"); + SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: generating relation event"); (void)callback_type; try { @@ -2920,7 +2922,7 @@ int neb::callback_relation(int callback_type, void* data) { new_host_parent->parent_id = parent_id; // Send event. - SPDLOG_LOGGER_INFO( + SPDLOG_LOGGER_DEBUG( neb_logger, "callbacks: host {} is parent of host {}", new_host_parent->parent_id, new_host_parent->host_id); neb::gl_publisher.write(new_host_parent); @@ -2949,7 +2951,7 @@ int neb::callback_relation(int callback_type, void* data) { */ int neb::callback_pb_relation(int callback_type [[maybe_unused]], void* data) { // Log message. - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: generating pb relation event"); + SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: generating pb relation event"); try { // Input variable. @@ -2975,9 +2977,9 @@ int neb::callback_pb_relation(int callback_type [[maybe_unused]], void* data) { new_host_parent->mut_obj().set_parent_id(parent_id); // Send event. - SPDLOG_LOGGER_INFO(neb_logger, - "callbacks: pb host {} is parent of host {}", - parent_id, host_id); + SPDLOG_LOGGER_DEBUG(neb_logger, + "callbacks: pb host {} is parent of host {}", + parent_id, host_id); neb::gl_publisher.write(new_host_parent); } } @@ -3005,7 +3007,7 @@ int neb::callback_pb_relation(int callback_type [[maybe_unused]], void* data) { */ int neb::callback_service(int callback_type, void* data) { // Log message. - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: generating service event"); + SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: generating service event"); (void)callback_type; try { @@ -3138,10 +3140,10 @@ int neb::callback_service(int callback_type, void* data) { my_service->service_id = p.second; if (my_service->host_id && my_service->service_id) { // Send service event. - SPDLOG_LOGGER_INFO(neb_logger, - "callbacks: new service {} ('{}') on host {}", - my_service->service_id, - my_service->service_description, my_service->host_id); + SPDLOG_LOGGER_DEBUG(neb_logger, + "callbacks: new service {} ('{}') on host {}", + my_service->service_id, + my_service->service_description, my_service->host_id); neb::gl_publisher.write(my_service); /* No need to send this service custom variables changes, custom @@ -3176,8 +3178,8 @@ int neb::callback_service(int callback_type, void* data) { * @return 0 on success. */ int neb::callback_pb_service(int callback_type [[maybe_unused]], void* data) { - SPDLOG_LOGGER_INFO(neb_logger, - "callbacks: generating pb service event protobuf"); + SPDLOG_LOGGER_DEBUG(neb_logger, + "callbacks: generating pb service event protobuf"); nebstruct_adaptive_service_data* ds = static_cast<nebstruct_adaptive_service_data*>(data); @@ -3229,9 +3231,9 @@ int neb::callback_pb_service(int callback_type [[maybe_unused]], void* data) { srv.set_host_id(p.first); srv.set_service_id(p.second); // Send service event. - SPDLOG_LOGGER_INFO(neb_logger, - "callbacks: new service {} ('{}') on host {}", - srv.service_id(), es->description(), srv.host_id()); + SPDLOG_LOGGER_DEBUG(neb_logger, + "callbacks: new service {} ('{}') on host {}", + srv.service_id(), es->description(), srv.host_id()); neb::gl_publisher.write(s); /* No need to send this service custom variables changes, custom @@ -3429,9 +3431,9 @@ int neb::callback_pb_service(int callback_type [[maybe_unused]], void* data) { srv.host_id(), srv.service_id(), srv.severity_id()); if (srv.host_id() && srv.service_id()) { // Send service event. - SPDLOG_LOGGER_INFO(neb_logger, - "callbacks: new service {} ('{}') on host {}", - srv.service_id(), srv.description(), srv.host_id()); + SPDLOG_LOGGER_DEBUG(neb_logger, + "callbacks: new service {} ('{}') on host {}", + srv.service_id(), srv.description(), srv.host_id()); neb::gl_publisher.write(s); /* No need to send this service custom variables changes, custom @@ -3471,7 +3473,7 @@ int neb::callback_service_check(int callback_type, void* data) { return 0; // Log message. - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: generating service check event"); + SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: generating service check event"); (void)callback_type; try { @@ -3538,7 +3540,8 @@ int neb::callback_pb_service_check(int, void* data) { scdata->host_id, scdata->service_id, scdata->command_line); } else { - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: generating service check event"); + SPDLOG_LOGGER_DEBUG(neb_logger, + "callbacks: generating service check event"); } // In/Out variables. @@ -3576,8 +3579,8 @@ int neb::callback_pb_service_check(int, void* data) { */ int32_t neb::callback_severity(int callback_type [[maybe_unused]], void* data) noexcept { - SPDLOG_LOGGER_INFO(neb_logger, - "callbacks: generating protobuf severity event"); + SPDLOG_LOGGER_DEBUG(neb_logger, + "callbacks: generating protobuf severity event"); nebstruct_adaptive_severity_data* ds = static_cast<nebstruct_adaptive_severity_data*>(data); @@ -3587,15 +3590,15 @@ int32_t neb::callback_severity(int callback_type [[maybe_unused]], Severity& sv = s.get()->mut_obj(); switch (ds->type) { case NEBTYPE_SEVERITY_ADD: - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: new severity"); + SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: new severity"); sv.set_action(Severity_Action_ADD); break; case NEBTYPE_SEVERITY_DELETE: - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: removed severity"); + SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: removed severity"); sv.set_action(Severity_Action_DELETE); break; case NEBTYPE_SEVERITY_UPDATE: - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: modified severity"); + SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: modified severity"); sv.set_action(Severity_Action_MODIFY); break; default: @@ -3627,7 +3630,7 @@ int32_t neb::callback_severity(int callback_type [[maybe_unused]], */ int32_t neb::callback_tag(int callback_type [[maybe_unused]], void* data) noexcept { - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: generating protobuf tag event"); + SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: generating protobuf tag event"); nebstruct_adaptive_tag_data* ds = static_cast<nebstruct_adaptive_tag_data*>(data); @@ -3637,15 +3640,15 @@ int32_t neb::callback_tag(int callback_type [[maybe_unused]], Tag& tg = t.get()->mut_obj(); switch (ds->type) { case NEBTYPE_TAG_ADD: - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: new tag"); + SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: new tag"); tg.set_action(Tag_Action_ADD); break; case NEBTYPE_TAG_DELETE: - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: removed tag"); + SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: removed tag"); tg.set_action(Tag_Action_DELETE); break; case NEBTYPE_TAG_UPDATE: - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: modified tag"); + SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: modified tag"); tg.set_action(Tag_Action_MODIFY); break; default: @@ -3686,15 +3689,15 @@ int32_t neb::callback_tag(int callback_type [[maybe_unused]], int32_t neb::callback_pb_service_status(int callback_type [[maybe_unused]], void* data) noexcept { - SPDLOG_LOGGER_INFO( + SPDLOG_LOGGER_DEBUG( neb_logger, "callbacks: generating pb service status check result event"); const engine::service* es{static_cast<engine::service*>( static_cast<nebstruct_service_status_data*>(data)->object_ptr)}; - neb_logger->info("callbacks: pb_service_status ({},{}) status {}, type {}", - es->host_id(), es->service_id(), - static_cast<uint32_t>(es->get_current_state()), - static_cast<uint32_t>(es->get_check_type())); + neb_logger->debug("callbacks: pb_service_status ({},{}) status {}, type {}", + es->host_id(), es->service_id(), + static_cast<uint32_t>(es->get_current_state()), + static_cast<uint32_t>(es->get_check_type())); auto s{std::make_shared<neb::pb_service_status>()}; ServiceStatus& sscr = s.get()->mut_obj(); @@ -3802,8 +3805,8 @@ int32_t neb::callback_pb_service_status(int callback_type [[maybe_unused]], std::make_pair(sscr.host_id(), sscr.service_id())); if (it != gl_acknowledgements.end() && sscr.acknowledgement_type() == AckType::NONE) { - neb_logger->info("acknowledgement found on service ({}:{})", sscr.host_id(), - sscr.service_id()); + neb_logger->debug("acknowledgement found on service ({}:{})", + sscr.host_id(), sscr.service_id()); if (it->second->type() == make_type(io::neb, de_pb_acknowledgement)) { neb::pb_acknowledgement* a = static_cast<neb::pb_acknowledgement*>(it->second.get()); @@ -3844,7 +3847,7 @@ int32_t neb::callback_pb_service_status(int callback_type [[maybe_unused]], */ int neb::callback_service_status(int callback_type, void* data) { // Log message. - SPDLOG_LOGGER_INFO(neb_logger, "callbacks: generating service status event"); + SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: generating service status event"); (void)callback_type; try { diff --git a/common/log_v2/log_v2.cc b/common/log_v2/log_v2.cc index 94e857bc736..ae19bc9f6f6 100644 --- a/common/log_v2/log_v2.cc +++ b/common/log_v2/log_v2.cc @@ -252,7 +252,10 @@ void log_v2::create_loggers(config::logger_type typ, size_t length) { else logger->set_pattern("[%Y-%m-%dT%H:%M:%S.%e%z] [%n] [%l] %v"); } - logger->set_level(level::level_enum::info); + if (id > 1) + logger->set_level(level::level_enum::err); + else + logger->set_level(level::level_enum::info); spdlog::register_logger(logger); _loggers[id] = std::move(logger); diff --git a/tests/broker-engine/rrd.robot b/tests/broker-engine/rrd.robot index f971440fbea..42f24b66204 100644 --- a/tests/broker-engine/rrd.robot +++ b/tests/broker-engine/rrd.robot @@ -434,7 +434,7 @@ RRD1 BRRDSTATUS [Documentation] We are working with BBDO3. This test checks status are correctly handled independently from their value. - [Tags] rrd status bbdo3 + [Tags] rrd status bbdo3 MON-141934 Ctn Config Engine ${1} Ctn Config Broker rrd Ctn Config Broker central diff --git a/tests/broker/log.robot b/tests/broker/log.robot index da17dd6f797..a482cec556c 100644 --- a/tests/broker/log.robot +++ b/tests/broker/log.robot @@ -67,3 +67,142 @@ BLEC3 Ctn Start Broker ${result} Ctn Set Broker Log Level 51001 foo trace Should Be Equal ${result} The 'foo' logger does not exist + +BLBD + [Documentation] Start Broker with loggers levels by default + [Tags] broker log-v2 MON-143565 + Ctn Config Broker rrd + Ctn Config Broker central + Ctn Broker Config Remove Item central log:loggers + ${start} Get Current Date + Ctn Start Broker + ${result} Ctn Get Broker Log Info 51001 ALL + log to console ${result} + ${LOG_RES} Catenate SEPARATOR=${\n} @{LOG_RESULT} + Should Be Equal ${result} ${LOG_RES} Default loggers levels are wrong + + +*** Variables *** +@{LOG_RESULT} log_name: "cbd" +... log_file: "/tmp/var/log/centreon-broker//central-broker-master.log" +... level { +... ${SPACE}${SPACE}key: "victoria_metrics" +... ${SPACE}${SPACE}value: "error" +... } +... level { +... ${SPACE}${SPACE}key: "tls" +... ${SPACE}${SPACE}value: "error" +... } +... level { +... ${SPACE}${SPACE}key: "tcp" +... ${SPACE}${SPACE}value: "error" +... } +... level { +... ${SPACE}${SPACE}key: "stats" +... ${SPACE}${SPACE}value: "error" +... } +... level { +... ${SPACE}${SPACE}key: "sql" +... ${SPACE}${SPACE}value: "error" +... } +... level { +... ${SPACE}${SPACE}key: "runtime" +... ${SPACE}${SPACE}value: "error" +... } +... level { +... ${SPACE}${SPACE}key: "rrd" +... ${SPACE}${SPACE}value: "error" +... } +... level { +... ${SPACE}${SPACE}key: "process" +... ${SPACE}${SPACE}value: "error" +... } +... level { +... ${SPACE}${SPACE}key: "processing" +... ${SPACE}${SPACE}value: "error" +... } +... level { +... ${SPACE}${SPACE}key: "perfdata" +... ${SPACE}${SPACE}value: "error" +... } +... level { +... ${SPACE}${SPACE}key: "otel" +... ${SPACE}${SPACE}value: "error" +... } +... level { +... ${SPACE}${SPACE}key: "notifications" +... ${SPACE}${SPACE}value: "error" +... } +... level { +... ${SPACE}${SPACE}key: "neb" +... ${SPACE}${SPACE}value: "error" +... } +... level { +... ${SPACE}${SPACE}key: "macros" +... ${SPACE}${SPACE}value: "error" +... } +... level { +... ${SPACE}${SPACE}key: "lua" +... ${SPACE}${SPACE}value: "error" +... } +... level { +... ${SPACE}${SPACE}key: "influxdb" +... ${SPACE}${SPACE}value: "error" +... } +... level { +... ${SPACE}${SPACE}key: "grpc" +... ${SPACE}${SPACE}value: "error" +... } +... level { +... ${SPACE}${SPACE}key: "graphite" +... ${SPACE}${SPACE}value: "error" +... } +... level { +... ${SPACE}${SPACE}key: "functions" +... ${SPACE}${SPACE}value: "error" +... } +... level { +... ${SPACE}${SPACE}key: "external_command" +... ${SPACE}${SPACE}value: "error" +... } +... level { +... ${SPACE}${SPACE}key: "events" +... ${SPACE}${SPACE}value: "error" +... } +... level { +... ${SPACE}${SPACE}key: "eventbroker" +... ${SPACE}${SPACE}value: "error" +... } +... level { +... ${SPACE}${SPACE}key: "downtimes" +... ${SPACE}${SPACE}value: "error" +... } +... level { +... ${SPACE}${SPACE}key: "core" +... ${SPACE}${SPACE}value: "info" +... } +... level { +... ${SPACE}${SPACE}key: "config" +... ${SPACE}${SPACE}value: "info" +... } +... level { +... ${SPACE}${SPACE}key: "comments" +... ${SPACE}${SPACE}value: "error" +... } +... level { +... ${SPACE}${SPACE}key: "commands" +... ${SPACE}${SPACE}value: "error" +... } +... level { +... ${SPACE}${SPACE}key: "checks" +... ${SPACE}${SPACE}value: "error" +... } +... level { +... ${SPACE}${SPACE}key: "bbdo" +... ${SPACE}${SPACE}value: "error" +... } +... level { +... ${SPACE}${SPACE}key: "bam" +... ${SPACE}${SPACE}value: "error" +... } +... diff --git a/tests/resources/Broker.py b/tests/resources/Broker.py index 06b9527896e..3f4b0068c89 100755 --- a/tests/resources/Broker.py +++ b/tests/resources/Broker.py @@ -1095,7 +1095,8 @@ def ctn_broker_config_remove_item(name, key): Args: name: The broker instance name among central, rrd and module%d - key: The key to remove. It must be defined at the first level of the configuration. + key: The key to remove. It must be defined from the "centreonBroker" level. + We can define several levels by splitting them with a colon. *Example:* @@ -1111,7 +1112,14 @@ def ctn_broker_config_remove_item(name, key): with open(f"{ETC_ROOT}/centreon-broker/{filename}", "r") as f: buf = f.read() conf = json.loads(buf) - conf["centreonBroker"].pop(key) + cc = conf["centreonBroker"] + if ":" in key: + steps = key.split(':') + for s in steps[:-1]: + cc = cc[s] + key = steps[-1] + + cc.pop(key) with open(f"{ETC_ROOT}/centreon-broker/{filename}", "w") as f: f.write(json.dumps(conf, indent=2)) @@ -2871,3 +2879,35 @@ def check_last_checked_services_with_given_metric_more_than(metric_like: str, no return True time.sleep(1) return False + + +def ctn_get_broker_log_info(port, log, timeout=TIMEOUT): + """ + Get the log info of a given logger or all of them by specifying "ALL" as log + value. + + Args: + port: The gRPC port. + log: The name of the logger or the string "ALL". + timeout: A timeout in seconds, 30s by default. + """ + limit = time.time() + timeout + while time.time() < limit: + logger.console("Try to call SetLogLevel") + time.sleep(1) + with grpc.insecure_channel("127.0.0.1:{}".format(port)) as channel: + stub = broker_pb2_grpc.BrokerStub(channel) + ref = broker_pb2.GenericString() + if log != "ALL": + ref.logger = log + + try: + res = stub.GetLogInfo(ref) + break + except grpc.RpcError as rpc_error: + if rpc_error.code() == grpc.StatusCode.INVALID_ARGUMENT: + res = rpc_error.details() + break + except: + logger.console("gRPC server not ready") + return str(res) From ced3141ef655c5aad4055004a234532f5343669f Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Tue, 23 Jul 2024 13:43:04 +0200 Subject: [PATCH 896/948] chore(ci): package lua-curl (#1557) --- .github/actions/deb-delivery/action.yml | 80 ++++++ .github/actions/package/action.yml | 4 +- .github/actions/promote-to-stable/action.yml | 5 - .github/actions/rpm-delivery/action.yml | 132 ++++++++++ .github/scripts/collect-prepare-test-robot.sh | 2 + .github/workflows/centreon-collect.yml | 1 - .github/workflows/get-version.yml | 2 +- .github/workflows/gorgone.yml | 1 - .github/workflows/libzmq.yml | 1 - .github/workflows/lua-curl.yml | 227 ++++++++++++++++++ .github/workflows/robot-test.yml | 4 +- lua-curl/packaging/lua-curl.yaml | 53 ++++ 12 files changed, 499 insertions(+), 13 deletions(-) create mode 100644 .github/actions/deb-delivery/action.yml create mode 100644 .github/actions/rpm-delivery/action.yml create mode 100644 .github/workflows/lua-curl.yml create mode 100644 lua-curl/packaging/lua-curl.yaml diff --git a/.github/actions/deb-delivery/action.yml b/.github/actions/deb-delivery/action.yml new file mode 100644 index 00000000000..46b6c5ec189 --- /dev/null +++ b/.github/actions/deb-delivery/action.yml @@ -0,0 +1,80 @@ +name: "deb-delivery" +description: "Package deb packages" +inputs: + module_name: + description: "The package module name" + required: true + distrib: + description: "The distribution used for packaging" + required: true + version: + description: "Centreon packaged major version" + required: true + cache_key: + description: "The cached package key" + required: true + stability: + description: "The package stability (stable, testing, unstable)" + required: true + artifactory_token: + description: "Artifactory token" + required: true + release_type: + description: "Type of release (hotfix, release)" + required: true + release_cloud: + description: "Release context (cloud or not cloud)" + required: true + +runs: + using: "composite" + steps: + - name: Use cache DEB files + uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + with: + path: ./*.deb + key: ${{ inputs.cache_key }} + fail-on-cache-miss: true + + - uses: jfrog/setup-jfrog-cli@0f30b43d62ccad81fba40748d2c671c4665b2d27 # v3.5.3 + env: + JF_URL: https://centreon.jfrog.io + JF_ACCESS_TOKEN: ${{ inputs.artifactory_token }} + + - name: Publish DEBs + run: | + FILES="*.deb" + + # DEBUG + echo "[DEBUG] - Version: ${{ inputs.version }}" + echo "[DEBUG] - Distrib: ${{ inputs.distrib }}" + echo "[DEBUG] - module_name: ${{ inputs.module_name }}" + echo "[DEBUG] - release_cloud: ${{ inputs.release_cloud }}" + echo "[DEBUG] - release_type: ${{ inputs.release_type }}" + echo "[DEBUG] - stability: ${{ inputs.stability }}" + + # Make sure all required inputs are NOT empty + if [[ -z "${{ inputs.module_name }}" || -z "${{ inputs.distrib }}" || -z ${{ inputs.stability }} || -z ${{ inputs.version }} || -z ${{ inputs.release_cloud }} || -z ${{ inputs.release_type }} ]]; then + echo "Some mandatory inputs are empty, please check the logs." + exit 1 + fi + + # Handle either standard debian or ubuntu repository path + if [[ "${{ inputs.distrib }}" == "jammy" ]]; then + ROOT_REPO_PATH="ubuntu-standard-${{ inputs.version }}-${{ inputs.stability }}" + else + ROOT_REPO_PATH="apt-standard-${{ inputs.version }}-${{ inputs.stability }}" + fi + + for FILE in $FILES; do + echo "[DEBUG] - File: $FILE" + + VERSION=${{ inputs.version }} + DISTRIB=$(echo $FILE | cut -d '_' -f2 | cut -d '-' -f2) + ARCH=$(echo $FILE | cut -d '_' -f3 | cut -d '.' -f1) + + echo "[DEBUG] - Version: $VERSION" + + jf rt upload "$FILE" "$ROOT_REPO_PATH/pool/${{ inputs.module_name }}/" --deb "${{ inputs.distrib }}/main/$ARCH" --flat + done + shell: bash diff --git a/.github/actions/package/action.yml b/.github/actions/package/action.yml index b83f9eff217..950b9cb8e27 100644 --- a/.github/actions/package/action.yml +++ b/.github/actions/package/action.yml @@ -112,8 +112,8 @@ runs: # Update if condition to true to get packages as artifacts - if: ${{ false }} name: Upload package artifacts - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: - name: packages-${{ inputs.distrib }} + name: ${{ inputs.arch != '' && format('packages-{0}-{1}', inputs.distrib, inputs.arch) || format('packages-{0}', inputs.distrib) }} path: ./*.${{ inputs.package_extension}} retention-days: 1 diff --git a/.github/actions/promote-to-stable/action.yml b/.github/actions/promote-to-stable/action.yml index df267f5acfc..4432aee5663 100644 --- a/.github/actions/promote-to-stable/action.yml +++ b/.github/actions/promote-to-stable/action.yml @@ -13,9 +13,6 @@ inputs: major_version: description: "Centreon packaged major version" required: true - minor_version: - description: "Centreon package minor version" - required: true stability: description: "The package stability (stable, testing, unstable)" required: true @@ -44,7 +41,6 @@ runs: # DEBUG echo "[DEBUG] - Major version: ${{ inputs.major_version }}" - echo "[DEBUG] - Minor version: ${{ inputs.minor_version }}" echo "[DEBUG] - Distrib: ${{ inputs.distrib }}" echo "[DEBUG] - release_cloud: ${{ inputs.release_cloud }}" echo "[DEBUG] - release_type: ${{ inputs.release_type }}" @@ -113,7 +109,6 @@ runs: set -eux echo "[DEBUG] - Major version: ${{ inputs.major_version }}" - echo "[DEBUG] - Minor version: ${{ inputs.minor_version }}" echo "[DEBUG] - Distrib: ${{ inputs.distrib }}" # Define ROOT_REPO_PATH for debian diff --git a/.github/actions/rpm-delivery/action.yml b/.github/actions/rpm-delivery/action.yml new file mode 100644 index 00000000000..3174c753300 --- /dev/null +++ b/.github/actions/rpm-delivery/action.yml @@ -0,0 +1,132 @@ +name: "rpm-delivery" +description: "Deliver rpm packages" +inputs: + module_name: + description: "The package module name" + required: true + distrib: + description: "The distribution used for packaging" + required: true + version: + description: "Centreon packaged major version" + required: true + cache_key: + description: "The cached package key" + required: true + stability: + description: "The package stability (stable, testing, unstable)" + required: true + artifactory_token: + description: "Artifactory token" + required: true + release_type: + description: "Type of release (hotfix, release)" + required: true + release_cloud: + description: "Release context (cloud or not cloud)" + required: true + +runs: + using: "composite" + steps: + - name: Use cache RPM files + uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + with: + path: ./*.rpm + key: ${{ inputs.cache_key }} + fail-on-cache-miss: true + + - uses: jfrog/setup-jfrog-cli@26da2259ee7690e63b5410d7451b2938d08ce1f9 # v4.0.0 + env: + JF_URL: https://centreon.jfrog.io + JF_ACCESS_TOKEN: ${{ inputs.artifactory_token }} + + - name: Publish RPMs + run: | + set -eux + + FILES="*.rpm" + + if [ -z "${{ inputs.module_name }}" ]; then + echo "module name is required" + exit 1 + fi + + if [ -z "${{ inputs.distrib }}" ]; then + echo "distrib is required" + exit 1 + fi + + # DEBUG + echo "[DEBUG] - Version: ${{ inputs.version }}" + echo "[DEBUG] - Distrib: ${{ inputs.distrib }}" + echo "[DEBUG] - module_name: ${{ inputs.module_name }}" + echo "[DEBUG] - release_cloud: ${{ inputs.release_cloud }}" + echo "[DEBUG] - release_type: ${{ inputs.release_type }}" + echo "[DEBUG] - stability: ${{ inputs.stability }}" + + # Make sure all required inputs are NOT empty + if [[ -z "${{ inputs.module_name }}" || -z "${{ inputs.distrib }}" || -z ${{ inputs.stability }} || -z ${{ inputs.version }} || -z ${{ inputs.release_cloud }} || -z ${{ inputs.release_type }} ]]; then + echo "Some mandatory inputs are empty, please check the logs." + exit 1 + fi + + # Create ARCH dirs + mkdir noarch x86_64 + + # Get ARCH target for files to deliver and regroupe them by ARCH + for FILE in $FILES; do + echo "[DEBUG] - File: $FILE" + + ARCH=$(echo $FILE | grep -oP '(x86_64|noarch)') + + echo "[DEBUG] - Arch: $ARCH" + + mv "$FILE" "$ARCH" + done + + # Build upload target path based on release_cloud and release_type values + # if cloud + hotfix or cloud + release, deliver to internal testing-<release_type> + # if cloud + develop, delivery to internal unstable + # if non-cloud, delivery to onprem testing or unstable + + # CLOUD + HOTFIX + REPO STANDARD INTERNAL OR CLOUD + RELEASE + REPO STANDARD INTERNAL + if [[ ${{ inputs.release_cloud }} -eq 1 ]] && ([[ ${{ inputs.release_type }} == "hotfix" ]] || [[ ${{ inputs.release_type }} == "release" ]]); then + echo "[DEBUG] : Release cloud + ${{ inputs.release_type }}, using rpm-standard-internal." + ROOT_REPO_PATHS="rpm-standard-internal" + UPLOAD_REPO_PATH="${{ inputs.version }}/${{ inputs.distrib }}/${{ inputs.stability }}-${{ inputs.release_type }}/$ARCH/${{ inputs.module_name }}/" + + # CLOUD + NOT HOTFIX OR CLOUD + NOT RELEASE + REPO STANDARD INTERNAL + elif [[ ${{ inputs.release_cloud }} -eq 1 ]] && ([[ ${{ inputs.release_type }} != "hotfix" ]] || [[ ${{ inputs.release_type }} != "release" ]]); then + echo "[DEBUG] : Release cloud + NOT ${{ inputs.release_type }}, using rpm-standard-internal." + ROOT_REPO_PATHS="rpm-standard-internal" + UPLOAD_REPO_PATH="${{ inputs.version }}/${{ inputs.distrib }}/${{ inputs.stability }}-${{ inputs.release_type }}/$ARCH/${{ inputs.module_name }}/" + + # NON-CLOUD + (HOTFIX OR RELEASE) + REPO STANDARD + elif [[ ${{ inputs.release_cloud }} -eq 0 ]]; then + echo "[DEBUG] : NOT Release cloud + ${{ inputs.release_type }}, using rpm-standard." + ROOT_REPO_PATHS="rpm-standard" + UPLOAD_REPO_PATH="${{ inputs.version }}/${{ inputs.distrib }}/${{ inputs.stability }}/$ARCH/${{ inputs.module_name }}/" + + # ANYTHING ELSE + else + echo "::error:: Invalid combination of release_type [${{ inputs.release_type }}] and release_cloud [${{ inputs.release_cloud }}]" + exit 1 + fi + + # Deliver based on inputs + for ROOT_REPO_PATH in "$ROOT_REPO_PATHS"; do + for ARCH in "noarch" "x86_64"; do + if [ "$(ls -A $ARCH)" ]; then + if [ "${{ inputs.stability }}" == "stable" ]; then + echo "[DEBUG] - Stability is ${{ inputs.stability }}, not delivering." + elif [ "${{ inputs.stability }}" == "testing" ]; then + jf rt upload "$ARCH/*.rpm" "$ROOT_REPO_PATH/$UPLOAD_REPO_PATH" --sync-deletes="$ROOT_REPO_PATH/$UPLOAD_REPO_PATH" --flat + else + jf rt upload "$ARCH/*.rpm" "$ROOT_REPO_PATH/${{ inputs.version }}/${{ inputs.distrib }}/${{ inputs.stability }}/$ARCH/${{ inputs.module_name }}/" --sync-deletes="$ROOT_REPO_PATH/${{ inputs.version }}/${{ inputs.distrib }}/${{ inputs.stability }}/$ARCH/${{ inputs.module_name }}/" --flat + fi + fi + done + done + + shell: bash diff --git a/.github/scripts/collect-prepare-test-robot.sh b/.github/scripts/collect-prepare-test-robot.sh index 25e9f02e5b0..c3cbc047175 100755 --- a/.github/scripts/collect-prepare-test-robot.sh +++ b/.github/scripts/collect-prepare-test-robot.sh @@ -67,8 +67,10 @@ fi if [ "$distrib" = "ALMALINUX" ]; then dnf groupinstall -y "Development Tools" dnf install -y python3-devel + dnf clean all else apt-get update apt-get install -y build-essential apt-get install -y python3-dev + apt-get clean fi diff --git a/.github/workflows/centreon-collect.yml b/.github/workflows/centreon-collect.yml index 87af57633dd..ccaa4ad3825 100644 --- a/.github/workflows/centreon-collect.yml +++ b/.github/workflows/centreon-collect.yml @@ -211,7 +211,6 @@ jobs: module_name: collect distrib: ${{ matrix.distrib }} major_version: ${{ needs.get-version.outputs.major_version }} - minor_version: ${{ needs.get-version.outputs.minor_version }} stability: ${{ needs.get-version.outputs.stability }} github_ref_name: ${{ github.ref_name }} release_type: ${{ needs.get-version.outputs.release_type }} diff --git a/.github/workflows/get-version.yml b/.github/workflows/get-version.yml index 06ae6a876a0..faa283e5850 100644 --- a/.github/workflows/get-version.yml +++ b/.github/workflows/get-version.yml @@ -42,7 +42,7 @@ on: jobs: get-version: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 outputs: major_version: ${{ steps.get_version.outputs.major_version }} minor_version: ${{ steps.get_version.outputs.minor_version }} diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index b786da6db34..378b5a393de 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -331,7 +331,6 @@ jobs: module_name: gorgone distrib: ${{ matrix.distrib }} major_version: ${{ needs.get-version.outputs.major_version }} - minor_version: ${{ needs.get-version.outputs.minor_version }} stability: ${{ needs.get-version.outputs.stability }} github_ref_name: ${{ github.ref_name }} release_type: ${{ needs.get-version.outputs.release_type }} diff --git a/.github/workflows/libzmq.yml b/.github/workflows/libzmq.yml index d1c2778e6a2..ad0adeb625a 100644 --- a/.github/workflows/libzmq.yml +++ b/.github/workflows/libzmq.yml @@ -221,7 +221,6 @@ jobs: module_name: libzmq distrib: ${{ matrix.distrib }} major_version: ${{ needs.get-version.outputs.major_version }} - minor_version: ${{ needs.get-version.outputs.minor_version }} stability: ${{ needs.get-version.outputs.stability }} github_ref_name: ${{ github.ref_name }} release_type: ${{ needs.get-version.outputs.release_type }} diff --git a/.github/workflows/lua-curl.yml b/.github/workflows/lua-curl.yml new file mode 100644 index 00000000000..96815e14c36 --- /dev/null +++ b/.github/workflows/lua-curl.yml @@ -0,0 +1,227 @@ +name: lua-curl + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +on: + workflow_dispatch: + pull_request: + paths: + - lua-curl/** + push: + branches: + - develop + - dev-[2-9][0-9].[0-9][0-9].x + - master + - "[2-9][0-9].[0-9][0-9].x" + paths: + - lua-curl/** + +env: + major_version: 0.3 + minor_version: 13 + release: 20 # 10 for openssl 1.1.1 / 20 for openssl system + +jobs: + get-version: + uses: ./.github/workflows/get-version.yml + + package: + needs: [get-version] + if: ${{ needs.get-version.outputs.stability != 'stable' }} + + strategy: + fail-fast: false + matrix: + include: + - package_extension: rpm + image: centreon-collect-alma8 + distrib: el8 + lua_version: 5.3 + runner: ubuntu-24.04 + arch: amd64 + - package_extension: rpm + image: centreon-collect-alma9 + distrib: el9 + lua_version: 5.4 + runner: ubuntu-24.04 + arch: amd64 + - package_extension: deb + image: centreon-collect-debian-bullseye + distrib: bullseye + lua_version: 5.3 + runner: ubuntu-24.04 + arch: amd64 + - package_extension: deb + image: centreon-collect-debian-bookworm + distrib: bookworm + lua_version: 5.3 + runner: ubuntu-24.04 + arch: amd64 + - package_extension: deb + image: centreon-collect-ubuntu-jammy + distrib: jammy + lua_version: 5.3 + runner: ubuntu-24.04 + arch: amd64 + - package_extension: deb + image: centreon-collect-debian-bullseye-arm64 + distrib: bullseye + lua_version: 5.3 + runner: ["self-hosted", "collect-arm64"] + arch: arm64 + - package_extension: deb + image: centreon-collect-debian-bookworm-arm64 + distrib: bookworm + lua_version: 5.3 + runner: ["self-hosted", "collect-arm64"] + arch: arm64 + + runs-on: ${{ matrix.runner }} + + container: + image: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }}/${{ matrix.image }}:${{ needs.get-version.outputs.img_version }} + credentials: + username: ${{ secrets.DOCKER_REGISTRY_ID }} + password: ${{ secrets.DOCKER_REGISTRY_PASSWD }} + + name: package ${{ matrix.distrib }} ${{ matrix.arch }} + + steps: + - name: Checkout sources + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Checkout sources of lua-curl + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + with: + repository: Lua-cURL/Lua-cURLv3 + path: lua-curl-src + ref: v${{ env.major_version }}.${{ env.minor_version }} + + - name: Compile lua-curl and prepare packaging + run: | + if [ "${{ matrix.package_extension }}" == "rpm" ]; then + dnf install -y dnf-plugins-core + if [ "${{ matrix.distrib }}" == "el8" ]; then + dnf config-manager --set-enabled powertools + else + dnf config-manager --set-enabled crb + fi + dnf install -y make gcc openssl openssl-devel libcurl-devel lua lua-devel + cd lua-curl-src + make + cd .. + else + apt-get update + apt-get install -y make openssl libssl-dev libcurl4-openssl-dev lua${{ matrix.lua_version }} liblua${{ matrix.lua_version }} liblua${{ matrix.lua_version }}-dev + cd lua-curl-src + make LUA_IMPL=lua${{ matrix.lua_version }} LUA_INC=/usr/include/lua${{ matrix.lua_version }} + cd .. + fi + + sed -i "s/@luaver@/${{ matrix.lua_version }}/g" lua-curl/packaging/lua-curl.yaml + shell: bash + + - name: Package + uses: ./.github/actions/package + with: + nfpm_file_pattern: "lua-curl/packaging/lua-curl.yaml" + distrib: ${{ matrix.distrib }} + package_extension: ${{ matrix.package_extension }} + major_version: ${{ env.major_version }} + minor_version: ${{ env.minor_version }} + release: ${{ env.release }} + arch: ${{ matrix.arch }} + commit_hash: ${{ github.sha }} + cache_key: ${{ github.sha }}-${{ github.run_id }}-${{ matrix.package_extension }}-lua-curl-${{ matrix.distrib }}-${{ matrix.arch }} + rpm_gpg_key: ${{ secrets.RPM_GPG_SIGNING_KEY }} + rpm_gpg_signing_key_id: ${{ secrets.RPM_GPG_SIGNING_KEY_ID }} + rpm_gpg_signing_passphrase: ${{ secrets.RPM_GPG_SIGNING_PASSPHRASE }} + stability: ${{ needs.get-version.outputs.stability }} + + deliver-rpm: + if: ${{ contains(fromJson('["testing", "unstable"]'), needs.get-version.outputs.stability) }} + needs: [get-version, package] + runs-on: ubuntu-22.04 + strategy: + matrix: + include: + - distrib: el8 + arch: amd64 + - distrib: el9 + arch: amd64 + name: deliver ${{ matrix.distrib }} ${{ matrix.arch }} + + steps: + - name: Checkout sources + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Publish RPM packages + uses: ./.github/actions/rpm-delivery + with: + module_name: lua-curl + distrib: ${{ matrix.distrib }} + version: ${{ needs.get-version.outputs.major_version }} + artifactory_token: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }} + cache_key: ${{ github.sha }}-${{ github.run_id }}-rpm-lua-curl-${{ matrix.distrib }}-${{ matrix.arch }} + stability: ${{ needs.get-version.outputs.stability }} + release_type: ${{ needs.get-version.outputs.release_type }} + release_cloud: ${{ needs.get-version.outputs.release_cloud }} + + deliver-deb: + if: ${{ contains(fromJson('["testing", "unstable"]'), needs.get-version.outputs.stability) }} + needs: [get-version, package] + runs-on: ubuntu-22.04 + strategy: + matrix: + include: + - distrib: bullseye + arch: amd64 + - distrib: bullseye + arch: arm64 + - distrib: bookworm + arch: amd64 + - distrib: jammy + arch: amd64 + name: deliver ${{ matrix.distrib }} ${{ matrix.arch }} + + steps: + - name: Checkout sources + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - name: Publish DEB packages + uses: ./.github/actions/deb-delivery + with: + module_name: lua-curl + distrib: ${{ matrix.distrib }} + version: ${{ needs.get-version.outputs.major_version }} + artifactory_token: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }} + cache_key: ${{ github.sha }}-${{ github.run_id }}-deb-lua-curl-${{ matrix.distrib }}-${{ matrix.arch }} + stability: ${{ needs.get-version.outputs.stability }} + release_type: ${{ needs.get-version.outputs.release_type }} + release_cloud: ${{ needs.get-version.outputs.release_cloud }} + + promote: + needs: [get-version] + if: ${{ contains(fromJson('["stable"]'), needs.get-version.outputs.stability) && github.event_name != 'workflow_dispatch' }} + runs-on: [self-hosted, common] + strategy: + matrix: + distrib: [el8, el9, bullseye, bookworm] + + steps: + - name: Checkout sources + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Promote ${{ matrix.distrib }} to stable + uses: ./.github/actions/promote-to-stable + with: + artifactory_token: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }} + module_name: lua-curl + distrib: ${{ matrix.distrib }} + major_version: ${{ needs.get-version.outputs.major_version }} + stability: ${{ needs.get-version.outputs.stability }} + github_ref_name: ${{ github.ref_name }} + release_type: ${{ needs.get-version.outputs.release_type }} + release_cloud: ${{ needs.get-version.outputs.release_cloud }} diff --git a/.github/workflows/robot-test.yml b/.github/workflows/robot-test.yml index 0381a515a6b..98c79d3ab84 100644 --- a/.github/workflows/robot-test.yml +++ b/.github/workflows/robot-test.yml @@ -116,14 +116,14 @@ jobs: fetch-depth: 0 - name: Restore image - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 with: path: /tmp/${{inputs.image}} key: ${{inputs.image_test}} fail-on-cache-miss: true - name: Restore packages - uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 with: path: ${{ inputs.package_cache_path }} key: ${{ inputs.package_cache_key }} diff --git a/lua-curl/packaging/lua-curl.yaml b/lua-curl/packaging/lua-curl.yaml new file mode 100644 index 00000000000..6f3916f8479 --- /dev/null +++ b/lua-curl/packaging/lua-curl.yaml @@ -0,0 +1,53 @@ +name: "lua-curl" +arch: "${ARCH}" +platform: "linux" +version_schema: "none" +version: "${VERSION}" +release: "${RELEASE}${DIST}" +section: "default" +priority: "optional" +maintainer: "Centreon <contact@centreon.com>" +description: | + lua curl library + Commit: @COMMIT_HASH@ +vendor: "Centreon" +homepage: "https://www.centreon.com" +license: "Apache-2.0" + +contents: + - src: "../../lua-curl-src/lcurl.so" + dst: "/usr/lib64/lua/@luaver@/lcurl.so" + file_info: + mode: 0644 + packager: rpm + - src: "../../lua-curl-src/lcurl.so" + dst: "/usr/lib/x86_64-linux-gnu/lua/@luaver@/lcurl.so" + file_info: + mode: 0644 + packager: deb + + - src: "../../lua-curl-src/src/lua/cURL.lua" + dst: "/usr/share/lua/@luaver@/cURL.lua" + + - src: "../../lua-curl-src/src/lua/cURL" + dst: "/usr/share/lua/@luaver@/cURL" + +overrides: + rpm: + depends: + - lua + deb: + depends: + - lua@luaver@ + provides: + - lua@luaver@-curl + conflicts: + - lua@luaver@-curl + replaces: + - lua@luaver@-curl + +rpm: + summary: lua curl + signature: + key_file: ${RPM_SIGNING_KEY_FILE} + key_id: ${RPM_SIGNING_KEY_ID} From 719116a58d9fad671f77c883baa904a735fa5f33 Mon Sep 17 00:00:00 2001 From: David Boucher <dboucher@centreon.com> Date: Tue, 23 Jul 2024 15:05:00 +0200 Subject: [PATCH 897/948] fix(common/perfdata): We do not allow anymore to parse several metrics with the same name REFS: MON-144271 --- common/src/perfdata.cc | 15 +++++++++- common/tests/perfdata_test.cc | 52 +++++++++++++++++++++++++++++++++++ tests/broker-engine/rrd.robot | 41 +++++++++++++++++++++++++++ tests/resources/Engine.py | 2 +- 4 files changed, 108 insertions(+), 2 deletions(-) diff --git a/common/src/perfdata.cc b/common/src/perfdata.cc index fab01b147f5..80945b75950 100644 --- a/common/src/perfdata.cc +++ b/common/src/perfdata.cc @@ -16,6 +16,7 @@ * For more information : contact@centreon.com */ +#include <absl/container/flat_hash_set.h> #include <cmath> #include "perfdata.hh" @@ -203,6 +204,8 @@ std::list<perfdata> perfdata::parse_perfdata( uint32_t service_id, const char* str, const std::shared_ptr<spdlog::logger>& logger) { + absl::flat_hash_set<std::string_view> metric_name; + std::string_view current_name; std::list<perfdata> retval; auto id = [host_id, service_id] { if (host_id || service_id) @@ -280,6 +283,15 @@ std::list<perfdata> perfdata::parse_perfdata( if (end - s + 1 > 0) { p._name.assign(s, end - s + 1); + current_name = std::string_view(s, end - s + 1); + + if (metric_name.contains(current_name)) { + logger->warn( + "storage: The metric '{}' appears several times in the output " + "\"{}\": you will lose any new occurence of this metric", + p.name(), str); + error = true; + } } else { logger->error("In service {}, metric name empty before '{}...'", id(), fmt::string_view(s, 10)); @@ -363,7 +375,8 @@ std::list<perfdata> perfdata::parse_perfdata( p.max()); // Append to list. - retval.emplace_back(std::move(p)); + metric_name.insert(current_name); + retval.push_back(std::move(p)); // Skip whitespaces. while (isspace(*tmp)) diff --git a/common/tests/perfdata_test.cc b/common/tests/perfdata_test.cc index 487300251bd..bab234f9522 100644 --- a/common/tests/perfdata_test.cc +++ b/common/tests/perfdata_test.cc @@ -246,6 +246,58 @@ TEST_F(PerfdataParser, Simple2) { ASSERT_TRUE(expected == *it); } +TEST_F(PerfdataParser, SeveralIdenticalMetrics) { + // Parse perfdata. + std::list<common::perfdata> list{common::perfdata::parse_perfdata( + 0, 0, "'et'=18.00%;15:;10:;0;100 other=15 et=13.00%", _logger)}; + + // Assertions. + ASSERT_EQ(list.size(), 2u); + std::list<perfdata>::const_iterator it = list.begin(); + perfdata expected; + expected.name("et"); + expected.value_type(perfdata::gauge); + expected.value(18.0); + expected.unit("%"); + expected.warning(std::numeric_limits<double>::infinity()); + expected.warning_low(15.0); + expected.critical(std::numeric_limits<double>::infinity()); + expected.critical_low(10.0); + expected.min(0.0); + expected.max(100.0); + ASSERT_TRUE(expected == *it); + ++it; + ASSERT_EQ(it->name(), std::string_view("other")); + ASSERT_EQ(it->value(), 15); + ASSERT_EQ(it->value_type(), perfdata::gauge); +} + +TEST_F(PerfdataParser, ComplexSeveralIdenticalMetrics) { + // Parse perfdata. + std::list<common::perfdata> list{common::perfdata::parse_perfdata( + 0, 0, "'d[foo]'=18.00%;15:;10:;0;100 other=15 a[foo]=13.00%", _logger)}; + + // Assertions. + ASSERT_EQ(list.size(), 2u); + std::list<perfdata>::const_iterator it = list.begin(); + perfdata expected; + expected.name("foo"); + expected.value_type(perfdata::derive); + expected.value(18.0); + expected.unit("%"); + expected.warning(std::numeric_limits<double>::infinity()); + expected.warning_low(15.0); + expected.critical(std::numeric_limits<double>::infinity()); + expected.critical_low(10.0); + expected.min(0.0); + expected.max(100.0); + ASSERT_TRUE(expected == *it); + ++it; + ASSERT_EQ(it->name(), std::string_view("other")); + ASSERT_EQ(it->value(), 15); + ASSERT_EQ(it->value_type(), perfdata::gauge); +} + TEST_F(PerfdataParser, Complex1) { // Parse perfdata. std::list<perfdata> list{perfdata::parse_perfdata( diff --git a/tests/broker-engine/rrd.robot b/tests/broker-engine/rrd.robot index 42f24b66204..b779424cf27 100644 --- a/tests/broker-engine/rrd.robot +++ b/tests/broker-engine/rrd.robot @@ -482,6 +482,47 @@ BRRDSTATUS Should Be Equal ${result} ${False} We shouldn't have any error about empty value in RRD +#BRRDSTATUSRETENTION We need the fix for this test: MON-139747 +# [Documentation] We are working with BBDO3. This test checks status are not sent twice after Engine reload. +# [Tags] rrd status bbdo3 +# Ctn Config Engine ${1} +# Ctn Config Broker rrd +# Ctn Config Broker central +# Ctn Config Broker module +# Ctn Config BBDO3 ${1} +# Ctn Broker Config Log central sql info +# Ctn Broker Config Log rrd rrd debug +# Ctn Broker Config Log rrd core error +# Ctn Broker Config Flush Log central 0 +# Ctn Broker Config Flush Log rrd 0 +# +# ${start} Get Current Date +# Ctn Start Broker +# Ctn Start Engine +# Ctn Wait For Engine To Be Ready ${start} ${1} +# +# Ctn Schedule Forced Svc Check host_1 service_1 ${VarRoot}/lib/centreon-engine/config0/rw/centengine.cmd +# Log To Console Engine works during 20s +# Sleep 20s +# +# Log To Console We modify the check_interval of the service service_1 +# Ctn Engine Config Replace Value In Services 0 service_1 check_interval 1 +# +# ${start} Ctn Get Round Current Date +# Log To Console Reloading Engine and waiting for 20s again +# Ctn Reload Engine +# Sleep 20s +# +# Log To Console Find in logs if there is an error in rrd. +# ${index} Ctn Get Service Index 1 1 +# ${content} Create List RRD: ignored update error in file '${VarRoot}/lib/centreon/status/${index}.rrd': ${VarRoot}/lib/centreon/status/${index}.rrd: illegal attempt to update using time +# ${result} Ctn Find In Log With Timeout ${rrdLog} ${start} ${content} 1 +# Should Be Equal +# ... ${result} ${False} +# ... No message about an illegal attempt to update the rrd files should appear +# Log To Console Test finished + + *** Keywords *** Ctn Test Clean Ctn Stop Engine diff --git a/tests/resources/Engine.py b/tests/resources/Engine.py index 9d601ff85c6..687a8db4435 100755 --- a/tests/resources/Engine.py +++ b/tests/resources/Engine.py @@ -857,7 +857,7 @@ def ctn_engine_config_set_value_in_services(idx: int, desc: str, key: str, value key (str): The key whose value needs to change. value (str): The new value to set. """ - filename = ETC_ROOT + "/centreon-engine/config{}/services.cfg".format(idx) + filename = f"{ETC_ROOT}/centreon-engine/config{idx}/services.cfg" with open(filename, "r") as f: lines = f.readlines() From bf8a59488581b87ee01a03d2a6cb65d900dc6066 Mon Sep 17 00:00:00 2001 From: jean-christophe81 <98889244+jean-christophe81@users.noreply.github.com> Date: Wed, 24 Jul 2024 12:05:03 +0200 Subject: [PATCH 898/948] MON-137834 compile agent on windows runner (#1566) * compile agent on windows runner * review comment --- .github/scripts/windows-agent-compile.ps1 | 78 +++++++++++++++++++ .github/workflows/centreon-collect.yml | 4 +- .../workflows/windows-agent-build-vcpkg.yml | 53 ------------- .github/workflows/windows-agent.yml | 62 +++++++-------- CMakeLists.txt | 36 +++++---- CMakeListsWindows.txt | 5 ++ common/grpc/CMakeLists.txt | 3 +- common/tests/process_test.cc | 1 + 8 files changed, 135 insertions(+), 107 deletions(-) create mode 100644 .github/scripts/windows-agent-compile.ps1 delete mode 100644 .github/workflows/windows-agent-build-vcpkg.yml diff --git a/.github/scripts/windows-agent-compile.ps1 b/.github/scripts/windows-agent-compile.ps1 new file mode 100644 index 00000000000..9118532affc --- /dev/null +++ b/.github/scripts/windows-agent-compile.ps1 @@ -0,0 +1,78 @@ +# +# Copyright 2024 Centreon +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# For more information : contact@centreon.com +# + +Write-Host "Work in" $pwd.ToString() + +[System.Environment]::SetEnvironmentVariable("AWS_EC2_METADATA_DISABLED","true") + +Write-Host $env:VCPKG_BINARY_SOURCES + +$current_dir = $pwd.ToString() + +#get cache from s3 +$files_to_hash = "vcpkg.json", "custom-triplets\x64-windows.cmake", "CMakeLists.txt", "CMakeListsWindows.txt" +$files_content = Get-Content -Path $files_to_hash -Raw +$stringAsStream = [System.IO.MemoryStream]::new() +$writer = [System.IO.StreamWriter]::new($stringAsStream) +$writer.write($files_content -join " ") +$writer.Flush() +$stringAsStream.Position = 0 +$vcpkg_hash = Get-FileHash -InputStream $stringAsStream -Algorithm SHA256 | Select-Object Hash +$file_name = "windows-agent-vcpkg-dependencies-cache-" + $vcpkg_hash.Hash +$file_name_extension = "${file_name}.7z" + +#try to get compiled dependenciesfrom s3 +Write-Host "try to download compiled dependencies from s3" +aws --quiet s3 cp s3://centreon-collect-robot-report/$file_name_extension $file_name_extension +if ( $? -ne $true ) { + #no => generate + Write-Host "#######################################################################################################################" + Write-Host "compiled dependencies unavailable for this version we will need to build it, it will take a long time" + Write-Host "#######################################################################################################################" + + Write-Host "install vcpkg" + git clone --depth 1 --single-branch --no-tags https://github.com/microsoft/vcpkg vcpkg + cd vcpkg + bootstrap-vcpkg.bat + cd $current_dir + + [System.Environment]::SetEnvironmentVariable("VCPKG_ROOT",$pwd.ToString()+"\vcpkg") + [System.Environment]::SetEnvironmentVariable("PATH",$pwd.ToString()+"\vcpkg;" + $env:PATH) + + Write-Host "compile vcpkg dependencies" + vcpkg install --vcpkg-root $env:VCPKG_ROOT --x-install-root build_windows\vcpkg_installed --x-manifest-root . --overlay-triplets custom-triplets --triplet x64-windows + + Write-Host "Compress binary archive" + 7z a $file_name_extension build_windows\vcpkg_installed + Write-Host "Upload binary archive" + aws s3 cp $file_name_extension s3://centreon-collect-robot-report/$file_name_extension + Write-Host "create CMake files" +} +else { + 7z x $file_name_extension + Write-Host "Create cmake files from binary-cache downloaded without use vcpkg" +} + + + +cmake -DCMAKE_BUILD_TYPE=Release -DWITH_TESTING=On -DWINDOWS=On -DBUILD_FROM_CACHE=On -S. -DVCPKG_CRT_LINKAGE=dynamic -DBUILD_SHARED_LIBS=OFF -Bbuild_windows + +Write-Host "build agent and tests" + +cmake --build build_windows --config Release + diff --git a/.github/workflows/centreon-collect.yml b/.github/workflows/centreon-collect.yml index ccaa4ad3825..d12a78d31c7 100644 --- a/.github/workflows/centreon-collect.yml +++ b/.github/workflows/centreon-collect.yml @@ -21,10 +21,10 @@ on: - cmake.sh - cmake-vcpkg.sh - CMakeLists.txt + - CMakeListsLinux.txt - vcpkg.json - overlays/** - selinux/** - - vcpkg/** - "!.veracode-exclusions" - "!veracode.json" - "!**/test/**" @@ -48,10 +48,10 @@ on: - cmake.sh - cmake-vcpkg.sh - CMakeLists.txt + - CMakeListsLinux.txt - vcpkg.json - overlays/** - selinux/** - - vcpkg/** - "!.veracode-exclusions" - "!veracode.json" - "!**/test/**" diff --git a/.github/workflows/windows-agent-build-vcpkg.yml b/.github/workflows/windows-agent-build-vcpkg.yml deleted file mode 100644 index 5fbc829994b..00000000000 --- a/.github/workflows/windows-agent-build-vcpkg.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: Centreon Monitoring Agent Windows packaging - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -on: - workflow_dispatch: - -jobs: - build-vcpkg-and-export: - runs-on: windows-latest - env: - AWS_ACCESS_KEY_ID: ${{ secrets.COLLECT_S3_ACCESS_KEY }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.COLLECT_S3_SECRET_KEY }} - - steps: - - name: Checkout sources - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - - name: install msvc command prompt - uses: ilammy/msvc-dev-cmd@v1.4.1 - - - name: install vcpkg - run: | - git clone --depth 1 https://github.com/microsoft/vcpkg - cd vcpkg - bootstrap-vcpkg.bat - - - name: compile packages - run: | - - [System.Environment]::SetEnvironmentVariable("VCPKG_ROOT",$pwd.ToString()+"\vcpkg") - [System.Environment]::SetEnvironmentVariable("PATH",$pwd.ToString()+"\vcpkg:" + $env:PATH) - cmake.exe --preset=release - - - name: 7zip packages and save to s3 - run: | - $files_to_hash= "vcpkg.json", "custom-triplets\x64-windows.cmake", "CMakeLists.txt", "CMakeListsWindows.txt" - $files_content= Get-Content -Path $files_to_hash -Raw - $stringAsStream = [System.IO.MemoryStream]::new() - $writer = [System.IO.StreamWriter]::new($stringAsStream) - $writer.write($files_content -join " ") - $writer.Flush() - $stringAsStream.Position = 0 - $vcpkg_hash=Get-FileHash -InputStream $stringAsStream -Algorithm SHA256 | Select-Object Hash - $file_name = "centreon-agent-prebuild-" + $vcpkg_hash.Hash - $file_name_extension = "${file_name}.7z" - Write-Host "store vcpkg package to $file_name" - 7z a $file_name_extension vcpkg - Write-Host "aws s3 cp $file_name_extension s3://centreon-collect-robot-report/$file_name_extension" - [System.Environment]::SetEnvironmentVariable("AWS_EC2_METADATA_DISABLED","true") - aws s3 cp $file_name_extension s3://centreon-collect-robot-report/$file_name_extension diff --git a/.github/workflows/windows-agent.yml b/.github/workflows/windows-agent.yml index b017560bcc5..20bec7eb300 100644 --- a/.github/workflows/windows-agent.yml +++ b/.github/workflows/windows-agent.yml @@ -6,9 +6,28 @@ concurrency: on: workflow_dispatch: + pull_request: + paths: + - agent/** + - custom-triplets/** + - CMakeLists.txt + - CMakeListsWindows.txt + - vcpkg.json + push: + branches: + - develop + - dev-[2-9][0-9].[0-9][0-9].x + - master + - "[2-9][0-9].[0-9][0-9].x" + paths: + - agent/** + - custom-triplets/** + - CMakeLists.txt + - CMakeListsWindows.txt + - vcpkg.json jobs: - build-agent: + build-and-test-agent: runs-on: windows-latest env: AWS_ACCESS_KEY_ID: ${{ secrets.COLLECT_S3_ACCESS_KEY }} @@ -18,51 +37,26 @@ jobs: - name: Checkout sources uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - name: install msvc command prompt - uses: ilammy/msvc-dev-cmd@v1.4.1 + - name: Compile Agent + run: .github/scripts/windows-agent-compile.ps1 + shell: powershell - - name: get built packages and build agent - run: | - $files_to_hash= "vcpkg.json", "custom-triplets\x64-windows.cmake", "CMakeLists.txt", "CMakeListsWindows.txt" - $files_content= Get-Content -Path $files_to_hash -Raw - $stringAsStream = [System.IO.MemoryStream]::new() - $writer = [System.IO.StreamWriter]::new($stringAsStream) - $writer.write($files_content -join " ") - $writer.Flush() - $stringAsStream.Position = 0 - $vcpkg_hash=Get-FileHash -InputStream $stringAsStream -Algorithm SHA256 | Select-Object Hash - $file_name = "centreon-agent-prebuild-" + $vcpkg_hash.Hash - $file_name_extension = "${file_name}.7z" - [System.Environment]::SetEnvironmentVariable("AWS_EC2_METADATA_DISABLED","true") - Write-Host "get $file_name_extension from s3" - aws --quiet s3 cp s3://centreon-collect-robot-report/$file_name_extension $file_name_extension - Write-Host "unzip $file_name_extension" - 7z x $file_name_extension - - [System.Environment]::SetEnvironmentVariable("VCPKG_ROOT",$pwd.Path + "\vcpkg") - [System.Environment]::SetEnvironmentVariable("PATH",$pwd.Path + "\vcpkg:" + $env:Path) - - Write-Host "create cmake files" - cmake --preset=release-ci - Write-Host "build agent and tests" - cmake --build build_windows - - - name: common test + - name: Common test run: | cd build_windows tests/ut_common - - name: agent test + - name: Agent test run: | cd build_windows tests/ut_agent - - name: zip agent + - name: Zip agent run: | - $files_to_compress = ".\agent\conf\centagent.reg", "build_windows\agent\centagent.exe" + $files_to_compress = "agent\conf\centagent.reg", "build_windows\agent\Release\centagent.exe" Compress-Archive -Path $files_to_compress -DestinationPath centreon-monitoring-agent.zip - - name: save agent package in cache + - name: Save agent package in cache uses: actions/cache/save@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 with: path: centreon-monitoring-agent.zip diff --git a/CMakeLists.txt b/CMakeLists.txt index 9dcbac951a9..38d29d8de50 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,26 +30,30 @@ set(CMAKE_CXX_STANDARD 17) string(TIMESTAMP CENTREON_CURRENT_YEAR "%Y") add_definitions(-DCENTREON_CURRENT_YEAR="${CENTREON_CURRENT_YEAR}") -if(DEFINED ENV{VCPKG_ROOT}) - set(VCPKG_ROOT "$ENV{VCPKG_ROOT}") - message( - STATUS "TOOLCHAIN set to ${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake") - set(CMAKE_TOOLCHAIN_FILE - "${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" - CACHE STRING "Vcpkg toolchain file") -else() - message( - STATUS - "TOOLCHAIN set to ${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake" - ) +#when we build from cache(CI), we don't use vcpkg because it recompiles often everything +if (NOT BUILD_FROM_CACHE) + if(DEFINED ENV{VCPKG_ROOT}) + set(VCPKG_ROOT "$ENV{VCPKG_ROOT}") + message( + STATUS "TOOLCHAIN set to ${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake") + set(CMAKE_TOOLCHAIN_FILE + "${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" + CACHE STRING "Vcpkg toolchain file") + else() + message( + STATUS + "TOOLCHAIN set to ${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake" + ) + set(CMAKE_TOOLCHAIN_FILE + "${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake" + CACHE STRING "Vcpkg toolchain file") + endif() + set(CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake" CACHE STRING "Vcpkg toolchain file") -endif() -set(CMAKE_TOOLCHAIN_FILE - "${CMAKE_CURRENT_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake" - CACHE STRING "Vcpkg toolchain file") +endif() project("Centreon Collect" C CXX) diff --git a/CMakeListsWindows.txt b/CMakeListsWindows.txt index 8f2bc2b3711..a2bd9c12d3e 100644 --- a/CMakeListsWindows.txt +++ b/CMakeListsWindows.txt @@ -16,6 +16,11 @@ # For more information : contact@centreon.com # +# When we build from cache (CI), we don't use vcpkg cmaketool, so we tell to cmake where to find packages info +if (BUILD_FROM_CACHE) + LIST(APPEND CMAKE_PREFIX_PATH "build_windows/vcpkg_installed/x64-windows") +endif() + find_package(fmt CONFIG REQUIRED) find_package(spdlog CONFIG REQUIRED) find_package(gRPC CONFIG REQUIRED) diff --git a/common/grpc/CMakeLists.txt b/common/grpc/CMakeLists.txt index ac154d422f4..e2371114e20 100644 --- a/common/grpc/CMakeLists.txt +++ b/common/grpc/CMakeLists.txt @@ -33,5 +33,4 @@ target_include_directories(centreon_grpc PRIVATE ${INC_DIR}) target_precompile_headers(centreon_grpc REUSE_FROM centreon_common) -set_target_properties(centreon_grpc PROPERTIES COMPILE_FLAGS "-fPIC") - +set_target_properties(centreon_grpc PROPERTIES POSITION_INDEPENDENT_CODE ON) diff --git a/common/tests/process_test.cc b/common/tests/process_test.cc index a8acb23c101..660c35e0d48 100644 --- a/common/tests/process_test.cc +++ b/common/tests/process_test.cc @@ -174,6 +174,7 @@ TEST_F(process_test, call_start_several_time_no_args) { expected += "hello" END_OF_LINE; } to_wait->wait(); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); ASSERT_EQ(to_wait->get_exit_status(), 0); ASSERT_EQ(to_wait->get_stdout(), expected); ASSERT_EQ(to_wait->get_stderr(), ""); From 28b471ac56ea56a8044428e2303e36a0f09f3c8c Mon Sep 17 00:00:00 2001 From: David Boucher <dboucher@centreon.com> Date: Thu, 25 Jul 2024 10:48:28 +0200 Subject: [PATCH 899/948] fix(engine): When the configuration is pushed, no need to send service status since services are already sent REFS: MON-139747 --- engine/src/configuration/applier/scheduler.cc | 6 +- tests/broker-engine/rrd.robot | 78 +++++++++---------- 2 files changed, 43 insertions(+), 41 deletions(-) diff --git a/engine/src/configuration/applier/scheduler.cc b/engine/src/configuration/applier/scheduler.cc index aea739f7aef..7e6a9deac9a 100644 --- a/engine/src/configuration/applier/scheduler.cc +++ b/engine/src/configuration/applier/scheduler.cc @@ -909,7 +909,8 @@ void applier::scheduler::_schedule_host_events( // add scheduled host checks to event queue. for (engine::host* h : hosts) { // update status of all hosts (scheduled or not). - h->update_status(); + // FIXME DBO: Is this really needed? + // h->update_status(); // skip most hosts that shouldn't be scheduled. if (!h->get_should_be_scheduled()) { @@ -1017,7 +1018,8 @@ void applier::scheduler::_schedule_service_events( // add scheduled service checks to event queue. for (engine::service* s : services) { // update status of all services (scheduled or not). - s->update_status(); + // FIXME DBO: Is this really needed? + // s->update_status(); // skip most services that shouldn't be scheduled. if (!s->get_should_be_scheduled()) { diff --git a/tests/broker-engine/rrd.robot b/tests/broker-engine/rrd.robot index b779424cf27..1e884c6dea4 100644 --- a/tests/broker-engine/rrd.robot +++ b/tests/broker-engine/rrd.robot @@ -482,45 +482,45 @@ BRRDSTATUS Should Be Equal ${result} ${False} We shouldn't have any error about empty value in RRD -#BRRDSTATUSRETENTION We need the fix for this test: MON-139747 -# [Documentation] We are working with BBDO3. This test checks status are not sent twice after Engine reload. -# [Tags] rrd status bbdo3 -# Ctn Config Engine ${1} -# Ctn Config Broker rrd -# Ctn Config Broker central -# Ctn Config Broker module -# Ctn Config BBDO3 ${1} -# Ctn Broker Config Log central sql info -# Ctn Broker Config Log rrd rrd debug -# Ctn Broker Config Log rrd core error -# Ctn Broker Config Flush Log central 0 -# Ctn Broker Config Flush Log rrd 0 -# -# ${start} Get Current Date -# Ctn Start Broker -# Ctn Start Engine -# Ctn Wait For Engine To Be Ready ${start} ${1} -# -# Ctn Schedule Forced Svc Check host_1 service_1 ${VarRoot}/lib/centreon-engine/config0/rw/centengine.cmd -# Log To Console Engine works during 20s -# Sleep 20s -# -# Log To Console We modify the check_interval of the service service_1 -# Ctn Engine Config Replace Value In Services 0 service_1 check_interval 1 -# -# ${start} Ctn Get Round Current Date -# Log To Console Reloading Engine and waiting for 20s again -# Ctn Reload Engine -# Sleep 20s -# -# Log To Console Find in logs if there is an error in rrd. -# ${index} Ctn Get Service Index 1 1 -# ${content} Create List RRD: ignored update error in file '${VarRoot}/lib/centreon/status/${index}.rrd': ${VarRoot}/lib/centreon/status/${index}.rrd: illegal attempt to update using time -# ${result} Ctn Find In Log With Timeout ${rrdLog} ${start} ${content} 1 -# Should Be Equal -# ... ${result} ${False} -# ... No message about an illegal attempt to update the rrd files should appear -# Log To Console Test finished +BRRDSTATUSRETENTION + [Documentation] We are working with BBDO3. This test checks status are not sent twice after Engine reload. + [Tags] rrd status bbdo3 MON-139747 + Ctn Config Engine ${1} + Ctn Config Broker rrd + Ctn Config Broker central + Ctn Config Broker module + Ctn Config BBDO3 ${1} + Ctn Broker Config Log central sql info + Ctn Broker Config Log rrd rrd debug + Ctn Broker Config Log rrd core error + Ctn Broker Config Flush Log central 0 + Ctn Broker Config Flush Log rrd 0 + + ${start} Get Current Date + Ctn Start Broker + Ctn Start Engine + Ctn Wait For Engine To Be Ready ${start} ${1} + + Ctn Schedule Forced Svc Check host_1 service_1 ${VarRoot}/lib/centreon-engine/config0/rw/centengine.cmd + Log To Console Engine works during 20s + Sleep 20s + + Log To Console We modify the check_interval of the service service_1 + Ctn Engine Config Replace Value In Services 0 service_1 check_interval 1 + + ${start} Ctn Get Round Current Date + Log To Console Reloading Engine and waiting for 20s again + Ctn Reload Engine + Sleep 20s + + Log To Console Find in logs if there is an error in rrd. + ${index} Ctn Get Service Index 1 1 + ${content} Create List RRD: ignored update error in file '${VarRoot}/lib/centreon/status/${index}.rrd': ${VarRoot}/lib/centreon/status/${index}.rrd: illegal attempt to update using time + ${result} Ctn Find In Log With Timeout ${rrdLog} ${start} ${content} 1 + Should Be Equal + ... ${result} ${False} + ... No message about an illegal attempt to update the rrd files should appear + Log To Console Test finished *** Keywords *** From 701bb96067e5193b4716cd66784833041b8ddf32 Mon Sep 17 00:00:00 2001 From: May <110405507+mushroomempires@users.noreply.github.com> Date: Thu, 25 Jul 2024 15:38:51 +0200 Subject: [PATCH 900/948] run nightly from monday to friday instead (#1572) --- .github/workflows/robot-nightly.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/robot-nightly.yml b/.github/workflows/robot-nightly.yml index f0cf45c751b..5283eb6e028 100644 --- a/.github/workflows/robot-nightly.yml +++ b/.github/workflows/robot-nightly.yml @@ -8,7 +8,7 @@ concurrency: on: workflow_dispatch: schedule: - - cron: '30 0 * * 2-6' + - cron: '30 0 * * 1-5' jobs: dispatch-to-maintained-branches: From 2053c98dd1a2e819030cdea49a36e408188e6102 Mon Sep 17 00:00:00 2001 From: tuntoja <58987095+tuntoja@users.noreply.github.com> Date: Thu, 1 Aug 2024 11:39:09 +0200 Subject: [PATCH 901/948] fix(packaging): increment release number to 21 to avoid version comparison issues (#1586) --- .github/workflows/lua-curl.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lua-curl.yml b/.github/workflows/lua-curl.yml index 96815e14c36..d65640af998 100644 --- a/.github/workflows/lua-curl.yml +++ b/.github/workflows/lua-curl.yml @@ -21,7 +21,7 @@ on: env: major_version: 0.3 minor_version: 13 - release: 20 # 10 for openssl 1.1.1 / 20 for openssl system + release: 21 # 10 for openssl 1.1.1 / 20 for openssl system / 21 for openssl system and possible issue with ~ jobs: get-version: From 28a79457e514bd98ee52327f52c66b3fcca0146b Mon Sep 17 00:00:00 2001 From: David Boucher <dboucher@centreon.com> Date: Thu, 1 Aug 2024 12:13:16 +0200 Subject: [PATCH 902/948] fix(packaging): conflict with centreon-broker-caching-sha2-password fixed (#1587) REFS: MON-144572 --- packaging/centreon-broker-core.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packaging/centreon-broker-core.yaml b/packaging/centreon-broker-core.yaml index 61bf0228188..1ebb52de51a 100644 --- a/packaging/centreon-broker-core.yaml +++ b/packaging/centreon-broker-core.yaml @@ -47,9 +47,11 @@ overrides: conflicts: - centreon-broker-storage - centreon-broker-core-devel + - centreon-broker-caching_sha2_password replaces: - centreon-broker-storage - centreon-broker-core-devel + - centreon-broker-caching_sha2_password provides: - centreon-broker-storage - centreon-broker-core-devel @@ -65,9 +67,11 @@ overrides: conflicts: - centreon-broker-storage - centreon-broker-core-dev + - centreon-broker-caching-sha2-password replaces: - centreon-broker-storage - centreon-broker-core-dev + - centreon-broker-caching-sha2-password provides: - centreon-broker-storage - centreon-broker-core-dev From 6bb04309d71614c50827d0f9c9b5cee547ddf0fb Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Tue, 6 Aug 2024 10:26:41 +0200 Subject: [PATCH 903/948] fix(gorgone): Add missing centengine report part to centreon-storage database (#1522) * Add perl rrd lib as dependancy of gorgone * ci(Gorgone): fix gorgone configuration to use the correct centreon-storage db * tests(gorgone): Add test for Gorgone statistics module * doc(Gorgone): update statistics documentation Refs:MON-37990 --- .../docker/Dockerfile.gorgone-testing-alma9 | 4 +- .../Dockerfile.gorgone-testing-bookworm | 51 ++++---- .../Dockerfile.gorgone-testing-bullseye | 30 +++-- .../docker/Dockerfile.gorgone-testing-jammy | 3 +- .github/workflows/gorgone.yml | 14 +-- gorgone/docs/modules/centreon/statistics.md | 31 ++++- .../modules/centreon/statistics/class.pm | 31 ++++- gorgone/packaging/centreon-gorgone.yaml | 2 + gorgone/tests/robot/config/actions.yaml | 6 + .../robot/config/gorgone_core_central.yaml | 10 ++ .../robot/config/push_central_config.yaml | 11 -- gorgone/tests/robot/config/statistics.yaml | 17 +++ gorgone/tests/robot/resources/LogResearch.py | 115 ++++++++++++++++++ gorgone/tests/robot/resources/import.resource | 3 + .../tests/robot/resources/resources.resource | 46 +++++-- .../robot/tests/centreon/centenginestats | 91 ++++++++++++++ .../robot/tests/centreon/statistics.robot | 106 ++++++++++++++++ .../tests/robot/tests/core/httpserver.robot | 7 +- gorgone/tests/robot/tests/core/pullwss.robot | 4 +- .../robot/tests/start_stop/start_stop.robot | 7 +- 20 files changed, 499 insertions(+), 90 deletions(-) create mode 100644 gorgone/tests/robot/config/actions.yaml create mode 100644 gorgone/tests/robot/config/statistics.yaml create mode 100644 gorgone/tests/robot/resources/LogResearch.py create mode 100644 gorgone/tests/robot/tests/centreon/centenginestats create mode 100644 gorgone/tests/robot/tests/centreon/statistics.robot diff --git a/.github/docker/Dockerfile.gorgone-testing-alma9 b/.github/docker/Dockerfile.gorgone-testing-alma9 index 39aa41ee11b..bdd3c8441dd 100644 --- a/.github/docker/Dockerfile.gorgone-testing-alma9 +++ b/.github/docker/Dockerfile.gorgone-testing-alma9 @@ -2,11 +2,11 @@ FROM almalinux:9 RUN bash -e <<EOF -dnf install -y dnf-plugins-core zstd mariadb iproute epel-release +dnf install -y dnf-plugins-core zstd mariadb iproute epel-release procps lsof dnf config-manager --set-enabled crb dnf config-manager --add-repo https://packages.centreon.com/rpm-standard/23.10/el9/centreon-23.10.repo dnf install -y python3.11 python3.11-pip -pip3.11 install robotframework robotframework-examples robotframework-databaselibrary pymysql robotframework-requests +pip3.11 install robotframework robotframework-examples robotframework-databaselibrary pymysql robotframework-requests robotframework-jsonlibrary dnf clean all diff --git a/.github/docker/Dockerfile.gorgone-testing-bookworm b/.github/docker/Dockerfile.gorgone-testing-bookworm index 840923ea75c..8235bc2355b 100644 --- a/.github/docker/Dockerfile.gorgone-testing-bookworm +++ b/.github/docker/Dockerfile.gorgone-testing-bookworm @@ -2,27 +2,30 @@ FROM debian:bookworm ENV DEBIAN_FRONTEND=noninteractive -# Set locale -RUN apt-get update && \ - apt-get install -y locales libcurl4-openssl-dev curl wget zstd jq lsb-release mariadb-client iproute2 && \ - apt-get install -y ca-certificates apt-transport-https software-properties-common gnupg2 && \ - localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 - -ENV LANG=en_US.UTF-8 - -# Install required packages and Robotframework -RUN apt-get update && \ - apt-get install -y \ - python3 \ - python3-dev \ - python3-pip && \ - pip3 install --break-system-packages --no-cache-dir \ - robotframework \ - robotframework-examples \ - robotframework-databaselibrary \ - pymysql \ - robotframework-requests && \ - echo "deb https://packages.centreon.com/apt-standard-24.05-testing/ $(lsb_release -sc) main" | tee /etc/apt/sources.list.d/centreon.list && \ - echo "deb https://packages.centreon.com/apt-plugins-testing/ $(lsb_release -sc) main" | tee /etc/apt/sources.list.d/centreon-plugins.list && \ - wget -O- https://apt-key.centreon.com | gpg --dearmor | tee /etc/apt/trusted.gpg.d/centreon.gpg > /dev/null 2>&1 && \ - apt-get clean && rm -rf /var/lib/apt/lists/* \ No newline at end of file +RUN bash -e <<EOF + +apt-get update + +# Install the required lib for language and apt installation. zstd is required for cache management in GHA. +# Install Robotframework and test dependency +apt-get install -y locales lsb-release libcurl4-openssl-dev curl wget gnupg2 \ +apt-transport-https zstd python3 python3-dev python3-pip ca-certificates \ +software-properties-common jq mariadb-client iproute2 procps lsof + +localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 + +pip3 install --break-system-packages --no-cache-dir \ +robotframework robotframework-examples robotframework-databaselibrary \ +pymysql robotframework-requests robotframework-jsonlibrary + +# can't use \$() method it would be executed before the main script, and lsb_release would not be installed. +lsb_release -sc | xargs -I % sh -c 'echo deb https://packages.centreon.com/apt-standard-24.05-stable/ % main' | tee /etc/apt/sources.list.d/centreon.list +lsb_release -sc | xargs -I % sh -c 'echo deb https://packages.centreon.com/apt-plugins-stable/ % main' | tee /etc/apt/sources.list.d/centreon-plugins.list + +wget -O- https://apt-key.centreon.com | gpg --dearmor | tee /etc/apt/trusted.gpg.d/centreon.gpg > /dev/null 2>&1 +apt-get clean +rm -rf /var/lib/apt/lists/* + +EOF + + diff --git a/.github/docker/Dockerfile.gorgone-testing-bullseye b/.github/docker/Dockerfile.gorgone-testing-bullseye index 80d88c7a831..0c3cc92a2a8 100644 --- a/.github/docker/Dockerfile.gorgone-testing-bullseye +++ b/.github/docker/Dockerfile.gorgone-testing-bullseye @@ -1,30 +1,28 @@ FROM debian:bullseye ENV DEBIAN_FRONTEND noninteractive - # fix locale +ENV LANG en_US.utf8 + RUN bash -e <<EOF -apt-get update -apt-get install -y locales libcurl4-openssl-dev curl wget zstd jq lsb-release mariadb-client iproute2 -rm -rf /var/lib/apt/lists/* -localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 -apt-get clean +apt update -EOF +# Install the required lib for language and apt installation. zstd is required for cache management in GHA. +# Install Robotframework and test dependency +apt-get install -y locales lsb-release libcurl4-openssl-dev curl wget gnupg2 apt-transport-https zstd \ +python3 python3-dev python3-pip ca-certificates software-properties-common jq mariadb-client iproute2 procps lsof -ENV LANG en_US.utf8 +localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 -RUN bash -e <<EOF -apt-get update -# Install Robotframework -apt-get install -y python3 python3-dev python3-pip ca-certificates apt-transport-https software-properties-common wget gnupg2 procps lsof -pip3 install robotframework robotframework-examples robotframework-databaselibrary pymysql robotframework-requests robotframework-jsonlibrary +pip3 install robotframework robotframework-examples robotframework-databaselibrary \ +pymysql robotframework-requests robotframework-jsonlibrary -echo "deb https://packages.centreon.com/apt-standard-23.10-stable/ $(lsb_release -sc) main" | tee /etc/apt/sources.list.d/centreon.list -echo "deb https://packages.centreon.com/apt-plugins-stable/ $(lsb_release -sc) main" | tee /etc/apt/sources.list.d/centreon-plugins.list +lsb_release -sc | xargs -I % sh -c 'echo deb https://packages.centreon.com/apt-standard-24.05-stable/ % main' | tee /etc/apt/sources.list.d/centreon.list +lsb_release -sc | xargs -I % sh -c 'echo deb https://packages.centreon.com/apt-plugins-stable/ % main' | tee /etc/apt/sources.list.d/centreon-plugins.list wget -O- https://apt-key.centreon.com | gpg --dearmor | tee /etc/apt/trusted.gpg.d/centreon.gpg > /dev/null 2>&1 -apt-get update +apt-get clean +rm -rf /var/lib/apt/lists/* EOF diff --git a/.github/docker/Dockerfile.gorgone-testing-jammy b/.github/docker/Dockerfile.gorgone-testing-jammy index 2263dce9710..6338489114d 100644 --- a/.github/docker/Dockerfile.gorgone-testing-jammy +++ b/.github/docker/Dockerfile.gorgone-testing-jammy @@ -5,7 +5,7 @@ ENV DEBIAN_FRONTEND=noninteractive # Set locale RUN apt-get update && \ apt-get install -y locales libcurl4-openssl-dev curl wget zstd jq lsb-release mariadb-client iproute2 && \ - apt-get install -y ca-certificates apt-transport-https software-properties-common gnupg2 && \ + apt-get install -y ca-certificates apt-transport-https software-properties-common gnupg2 procps lsof && \ localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 ENV LANG=en_US.UTF-8 @@ -31,6 +31,7 @@ RUN python3 -m venv /opt/robotframework-env && \ robotframework-examples \ robotframework-databaselibrary \ robotframework-requests \ + robotframework-jsonlibrary \ pymysql # Clean up diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 378b5a393de..a60905c0ee8 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -222,15 +222,15 @@ jobs: - name: Create databases run: | - mysql -h mariadb -u root -ppassword -e "CREATE DATABASE \`centreon\`" - mysql -h mariadb -u root -ppassword -e "CREATE DATABASE \`centreon-storage\`" - mysql -h mariadb -u root -ppassword -e "GRANT ALL PRIVILEGES ON centreon.* TO 'centreon'@'%'" - mysql -h mariadb -u root -ppassword -e "GRANT ALL PRIVILEGES ON \`centreon-storage\`.* TO 'centreon'@'%'" - mysql -h mariadb -u root -ppassword 'centreon' < centreon/centreon/www/install/createTables.sql - mysql -h mariadb -u root -ppassword 'centreon-storage' < centreon/centreon/www/install/createTablesCentstorage.sql + mysql -h mariadb -u root -ppassword -e "CREATE DATABASE \`centreon${{ matrix.distrib }}\`" + mysql -h mariadb -u root -ppassword -e "CREATE DATABASE \`centreon-storage${{ matrix.distrib }}\`" + mysql -h mariadb -u root -ppassword -e "GRANT ALL PRIVILEGES ON centreon${{ matrix.distrib }}.* TO 'centreon'@'%'" + mysql -h mariadb -u root -ppassword -e "GRANT ALL PRIVILEGES ON \`centreon-storage${{ matrix.distrib }}\`.* TO 'centreon'@'%'" + mysql -h mariadb -u root -ppassword 'centreon${{ matrix.distrib }}' < centreon/centreon/www/install/createTables.sql + mysql -h mariadb -u root -ppassword 'centreon-storage${{ matrix.distrib }}' < centreon/centreon/www/install/createTablesCentstorage.sql - name: Run tests - run: robot -v 'DBHOST:mariadb' -v 'DBNAME:centreon' -v 'DBUSER:centreon' gorgone/tests + run: robot -v 'DBHOST:mariadb' -v 'DBNAME:centreon${{ matrix.distrib }}' -v 'DBNAME_STORAGE:centreon-storage${{ matrix.distrib }}' -v 'DBUSER:centreon' gorgone/tests - name: Upload gorgone and robot debug artifacts if: failure() diff --git a/gorgone/docs/modules/centreon/statistics.md b/gorgone/docs/modules/centreon/statistics.md index 82ce80de5d4..db28a88ec4d 100644 --- a/gorgone/docs/modules/centreon/statistics.md +++ b/gorgone/docs/modules/centreon/statistics.md @@ -2,7 +2,7 @@ ## Description -This module aims to deal with statistics collection of Centreon Engine and Broker. +This module aims to manage the collection of Centreon Engine and Broker statistics. It requires the configuration of an action module on each poller and on the central. ## Configuration @@ -10,7 +10,7 @@ This module aims to deal with statistics collection of Centreon Engine and Broke | :--------------- | :--------------------------------------------------------------------------------------------- | :-------------------------------- | | broker_cache_dir | Path to the Centreon Broker statistics directory (local) use to store node's broker statistics | `/var/lib/centreon/broker-stats/` | -The configuration needs a cron definition to unsure that statistics collection will be done cyclically. +The configuration needs a cron definition to ensure that statistics are collected regularly. #### Example @@ -30,13 +30,32 @@ cron: ## Events -| Event | Description | -| :-------------- | :----------------------------------------------- | -| STATISTICSREADY | Internal event to notify the core | -| BROKERSTATS | Collect Centreon Broker statistics files on node | +| Event | Description | +|:-------------------|:--------------------------------------------------| +| STATISTICSREADY | Internal event to notify the core | +| STATISTICSLISTENER | Internal Event to receive data from action module | +| BROKERSTATS | Collect Centreon Broker statistics files on node | +| ENGINESTATS | Collect Centreon engine statistics on node | ## API +### Collect Centreon engine statistics on every nodes configured + +The API returns a token to monitor the progess. Please note this token does not allow to monitor the whole process but only the first part until an action command is sent. + +Data will be stored in the `centreon_storage.nagios_stats` table and in the rrd database. + +| Endpoint | Method | +|:----------------------------| :----- | +| /centreon/statistics/engine | `GET` | + +#### Example + +```bash +curl --request POST "https://hostname:8443/api/centreon/statistics/engine" \ + --header "Accept: application/json" +``` + ### Collect Centreon Broker statistics on one or several nodes | Endpoint | Method | diff --git a/gorgone/gorgone/modules/centreon/statistics/class.pm b/gorgone/gorgone/modules/centreon/statistics/class.pm index 517c7c6fab6..92aa001f7e3 100644 --- a/gorgone/gorgone/modules/centreon/statistics/class.pm +++ b/gorgone/gorgone/modules/centreon/statistics/class.pm @@ -33,7 +33,6 @@ use Time::HiRes; use RRDs; use EV; -my $result; my %handlers = (TERM => {}, HUP => {}); my ($connector); @@ -377,7 +376,7 @@ sub write_engine_stats { "('$options{data}->{metadata}->{poller_id}', 'Service Check Latency', 'Average', '$3')" ); if ($status == -1) { - $self->{logger}->writeLogError("[statistics] Failed to add statistics in 'nagios_stats table'"); + $self->{logger}->writeLogError("[statistics] Failed to add statistics Service Check Latency in 'nagios_stats table'"); } } @@ -398,6 +397,15 @@ sub write_engine_stats { values => [ $1, $2 , $3 ] ); } elsif ($_ =~ /Active Service Execution Time:\s*([0-9\.]*)\ \/\ ([0-9\.]*)\ \/\ ([0-9\.]*)\ sec/) { + my $status = $self->{class_object_centstorage}->custom_execute( + request => "INSERT INTO `nagios_stats` (instance_id, stat_label, stat_key, stat_value) VALUES " . + "('$options{data}->{metadata}->{poller_id}', 'Service Check Execution Time', 'Min', '$1'), " . + "('$options{data}->{metadata}->{poller_id}', 'Service Check Execution Time', 'Max', '$2'), " . + "('$options{data}->{metadata}->{poller_id}', 'Service Check Execution Time', 'Average', '$3')" + ); + if ($status == -1) { + $self->{logger}->writeLogError("[statistics] Failed to add statistics Service Check Execution Time in 'nagios_stats table'"); + } my $dest_file = $engine_stats_dir . '/nagios_active_service_execution.rrd'; $self->{logger}->writeLogDebug("[statistics] Writing in file '" . $dest_file . "'"); if (!-e $dest_file) { @@ -449,6 +457,15 @@ sub write_engine_stats { values => [ $1, $2 , $3, $4 ] ); } elsif ($_ =~ /Active Host Latency:\s*([0-9\.]*)\ \/\ ([0-9\.]*)\ \/\ ([0-9\.]*)\ sec/) { + my $status = $self->{class_object_centstorage}->custom_execute( + request => "INSERT INTO `nagios_stats` (instance_id, stat_label, stat_key, stat_value) VALUES " . + "('$options{data}->{metadata}->{poller_id}', 'Host Check Latency ', 'Min', '$1'), " . + "('$options{data}->{metadata}->{poller_id}', 'Host Check Latency ', 'Max', '$2'), " . + "('$options{data}->{metadata}->{poller_id}', 'Host Check Latency ', 'Average', '$3')" + ); + if ($status == -1) { + $self->{logger}->writeLogError("[statistics] Failed to add statistics Host Check Latency in 'nagios_stats table'"); + } my $dest_file = $engine_stats_dir . '/nagios_active_host_latency.rrd'; $self->{logger}->writeLogDebug("[statistics] Writing in file '" . $dest_file . "'"); if (!-e $dest_file) { @@ -466,6 +483,15 @@ sub write_engine_stats { values => [ $1, $2 , $3 ] ); } elsif ($_ =~ /Active Host Execution Time:\s*([0-9\.]*)\ \/\ ([0-9\.]*)\ \/\ ([0-9\.]*)\ sec/) { + my $status = $self->{class_object_centstorage}->custom_execute( + request => "INSERT INTO `nagios_stats` (instance_id, stat_label, stat_key, stat_value) VALUES " . + "('$options{data}->{metadata}->{poller_id}', 'Host Check Execution Time', 'Min', '$1'), " . + "('$options{data}->{metadata}->{poller_id}', 'Host Check Execution Time', 'Max', '$2'), " . + "('$options{data}->{metadata}->{poller_id}', 'Host Check Execution Time', 'Average', '$3')" + ); + if ($status == -1) { + $self->{logger}->writeLogError("[statistics] Failed to add statistics Host Check Execution Time in 'nagios_stats table'"); + } my $dest_file = $engine_stats_dir . '/nagios_active_host_execution.rrd'; $self->{logger}->writeLogDebug("[statistics] Writing in file '" . $dest_file . "'"); if (!-e $dest_file) { @@ -518,6 +544,7 @@ sub write_engine_stats { ); } } + $self->{logger}->writeLogInfo("[statistics] poller $options{data}->{metadata}->{poller_id} engine data was integrated in rrd and sql database."); } sub rrd_create { diff --git a/gorgone/packaging/centreon-gorgone.yaml b/gorgone/packaging/centreon-gorgone.yaml index beb14173448..27844e0cca2 100644 --- a/gorgone/packaging/centreon-gorgone.yaml +++ b/gorgone/packaging/centreon-gorgone.yaml @@ -190,6 +190,7 @@ overrides: - perl(Try::Tiny) - tar - perl(JSON) # gorgone_key_thumbprint.pl needs the json module, even when json::xs is already installed + - perl(RRDs) deb: depends: # those dependencies are taken from centreon-gorgone/packaging/debian/control - centreon-common @@ -222,6 +223,7 @@ overrides: - libzmq-ffi-perl - libclone-choose-perl - libjson-perl # gorgone_key_thumbprint.pl needs the json module, even when json::xs is already installed + - librrds-perl rpm: summary: Centreon gorgone daemon diff --git a/gorgone/tests/robot/config/actions.yaml b/gorgone/tests/robot/config/actions.yaml new file mode 100644 index 00000000000..3a5074faee0 --- /dev/null +++ b/gorgone/tests/robot/config/actions.yaml @@ -0,0 +1,6 @@ +gorgone: + modules: + - name: action + package: "gorgone::modules::core::action::hooks" + enable: true + command_timeout: 30 diff --git a/gorgone/tests/robot/config/gorgone_core_central.yaml b/gorgone/tests/robot/config/gorgone_core_central.yaml index 2121670c96a..a74882d3224 100644 --- a/gorgone/tests/robot/config/gorgone_core_central.yaml +++ b/gorgone/tests/robot/config/gorgone_core_central.yaml @@ -9,3 +9,13 @@ gorgone: privkey: "/var/lib/centreon-gorgone/.keys/rsakey.priv.pem" pubkey: "/var/lib/centreon-gorgone/.keys/rsakey.pub.pem" +centreon: + database: + db_configuration: + dsn: "mysql:host=@DBHOST@:port=3306;dbname=@DBNAME@" + username: "@DBUSER@" + password: "@DBPASSWORD@" + db_realtime: + dsn: "mysql:host=@DBHOST@:port=3306;dbname=@DBNAME_STORAGE@" + username: "@DBUSER@" + password: "@DBPASSWORD@" \ No newline at end of file diff --git a/gorgone/tests/robot/config/push_central_config.yaml b/gorgone/tests/robot/config/push_central_config.yaml index fc71b00d658..af8be08ac32 100644 --- a/gorgone/tests/robot/config/push_central_config.yaml +++ b/gorgone/tests/robot/config/push_central_config.yaml @@ -22,14 +22,3 @@ gorgone: enabled: true subnets: - 127.0.0.1/32 - -centreon: - database: - db_configuration: - dsn: "mysql:host=@DBHOST@:port=3306;dbname=@DBNAME@" - username: "@DBUSER@" - password: "@DBPASSWORD@" - db_realtime: - dsn: "mysql:host=@DBHOST@:port=3306;dbname=centreon_storage" - username: "@DBUSER@" - password: "@DBPASSWORD@" diff --git a/gorgone/tests/robot/config/statistics.yaml b/gorgone/tests/robot/config/statistics.yaml new file mode 100644 index 00000000000..4606434aa95 --- /dev/null +++ b/gorgone/tests/robot/config/statistics.yaml @@ -0,0 +1,17 @@ +gorgone: + modules: + - name: statistics + package: "gorgone::modules::centreon::statistics::hooks" + enable: true + broker_cache_dir: "/var/cache/centreon/broker-stats/" + cron: + - id: broker_stats + timespec: "*/5 * * * *" + action: BROKERSTATS + parameters: + timeout: 10 + - id: engine_stats + timespec: "*/5 * * * *" + action: ENGINESTATS + parameters: + timeout: 10 diff --git a/gorgone/tests/robot/resources/LogResearch.py b/gorgone/tests/robot/resources/LogResearch.py new file mode 100644 index 00000000000..e6e6ccd54d0 --- /dev/null +++ b/gorgone/tests/robot/resources/LogResearch.py @@ -0,0 +1,115 @@ +from robot.api import logger +import re +import time +from dateutil import parser +from datetime import datetime, timedelta +from robot.libraries.BuiltIn import BuiltIn + +TIMEOUT = 30 + + +def ctn_find_in_log_with_timeout(log: str, content, timeout=20, date=-1, regex=False): + """! search a pattern in log from date param + @param log: path of the log file + @param date: date from witch it begins search, you might want to use robot Get Current Date function + @param content: array of pattern to search + @param timeout: time out in second + @param regex: search use regex, default to false + @return True/False, array of lines found for each pattern + """ + if date == -1: + date = datetime.now().timestamp() - 1 + limit = time.time() + timeout + c = "" + while time.time() < limit: + ok, c = ctn_find_in_log(log, date, content, regex) + if ok: + return True, c + time.sleep(5) + logger.console(f"Unable to find '{c}' from {date} during {timeout}s") + return False + + +def ctn_find_in_log(log: str, date, content, regex=False): + """Find content in log file from the given date + + Args: + log (str): The log file + date (_type_): A date as a string + content (_type_): An array of strings we want to find in the log. + + Returns: + boolean,str: The boolean is True on success, and the string contains the first string not found in logs otherwise. + """ + logger.info(f"regex={regex}") + res = [] + + try: + f = open(log, "r", encoding="latin1") + lines = f.readlines() + f.close() + idx = ctn_find_line_from(lines, date) + + for c in content: + found = False + for i in range(idx, len(lines)): + line = lines[i] + if regex: + match = re.search(c, line) + else: + match = c in line + if match: + logger.console(f"\"{c}\" found at line {i} from {idx}") + found = True + res.append(line) + break + if not found: + return False, c + + return True, res + except IOError: + logger.console("The file '{}' does not exist".format(log)) + return False, content[0] + + +def ctn_extract_date_from_log(line: str): + p = re.compile(r"(^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})") + m = p.match(line) + if m is None: + return None + try: + return parser.parse((m.group(1))) + except parser.ParserError: + logger.console(f"Unable to parse the date from the line {line}") + return None + + +def ctn_find_line_from(lines, date): + try: + my_date = parser.parse(date) + except: + my_date = datetime.fromtimestamp(date) + + # Let's find my_date + start = 0 + end = len(lines) - 1 + idx = start + while end > start: + idx = (start + end) // 2 + idx_d = ctn_extract_date_from_log(lines[idx]) + while idx_d is None: + logger.console("Unable to parse the date ({} <= {} <= {}): <<{}>>".format( + start, idx, end, lines[idx])) + idx -= 1 + if idx >= 0: + idx_d = ctn_extract_date_from_log(lines[idx]) + else: + logger.console("We are at the first line and no date found") + return 0 + if my_date <= idx_d and end != idx: + end = idx + elif my_date > idx_d and start != idx: + start = idx + else: + break + return idx diff --git a/gorgone/tests/robot/resources/import.resource b/gorgone/tests/robot/resources/import.resource index 82132fb8744..0c2d057cdb8 100644 --- a/gorgone/tests/robot/resources/import.resource +++ b/gorgone/tests/robot/resources/import.resource @@ -5,4 +5,7 @@ Library OperatingSystem Library String Library Collections Resource resources.resource +Library LogResearch.py Library DatabaseLibrary +Library JSONLibrary +Library DateTime \ No newline at end of file diff --git a/gorgone/tests/robot/resources/resources.resource b/gorgone/tests/robot/resources/resources.resource index a4b7d6a9749..4e678f94f4b 100644 --- a/gorgone/tests/robot/resources/resources.resource +++ b/gorgone/tests/robot/resources/resources.resource @@ -4,8 +4,8 @@ Documentation Centreon Gorgone library for Robot Framework Library Process Library RequestsLibrary Library OperatingSystem - Library DatabaseLibrary + *** Variables *** ${gorgone_binary} /usr/bin/gorgoned ${ROOT_CONFIG} ${CURDIR}${/}..${/}config${/} @@ -20,6 +20,7 @@ ${gorgone_core_config} ${ROOT_CONFIG}gorgone_core_central.yaml ${DBHOST} 127.0.0.1 ${DBPORT} 3306 ${DBNAME} centreon_gorgone_test +${DBNAME_STORAGE} centreon-storage ${DBUSER} centreon ${DBPASSWORD} password @@ -58,7 +59,7 @@ Gorgone Execute Sql END Setup Gorgone Config - [Arguments] @{file_list} ${gorgone_name}=gorgone_process_name ${sql_file}= + [Arguments] ${file_list} ${gorgone_name}=gorgone_process_name ${sql_file}= Gorgone Execute Sql ${sql_file} Create Directory /var/log/centreon-gorgone/${gorgone_name}/ Copy File ${CURDIR}${/}..${/}config${/}includer.yaml /etc/centreon-gorgone/${gorgone_name}/includer.yaml @@ -73,6 +74,7 @@ Setup Gorgone Config ${CMD} Catenate ... sed -i -e 's/@KEYTHUMBPRINT@/${key_thumbprint}/g' ... -e 's/@DBNAME@/${DBNAME}/g' + ... -e 's/@DBNAME_STORAGE@/${DBNAME_STORAGE}/g' ... -e 's/@DBHOST@/${DBHOST}/g' ... -e 's/@DBPASSWORD@/${DBPASSWORD}/g' ... -e 's/@DBUSER@/${DBUSER}/g' @@ -116,23 +118,38 @@ Check Poller Communicate Should Be True 0 < ${response.json()}[data][${poller_id}][ping_ok] there was no successful ping between the central and the poller ${poller_id} Setup Two Gorgone Instances - [Arguments] ${communication_mode}=push_zmq ${central_name}=gorgone_central ${poller_name}=gorgone_poller_2 + [Arguments] ${central_config}=@{EMPTY} ${poller_config}=@{EMPTY} ${communication_mode}=push_zmq ${central_name}=gorgone_central ${poller_name}=gorgone_poller_2 ${result} Run perl /usr/local/bin/gorgone_key_generation.pl # generate key if there is none. # gorgone can generate it's own key, but as we need the thumbprint in the configuration we need to generate them before launching gorgone. # this script only create key if the files don't exists, and silently finish if the files already exists. IF '${communication_mode}' == 'push_zmq' - Setup Gorgone Config ${push_central_config} ${gorgone_core_config} gorgone_name=${central_name} sql_file=${ROOT_CONFIG}db_add_1_poller.sql - Setup Gorgone Config ${push_poller_config} gorgone_name=${poller_name} + @{central_push_config}= Copy List ${central_config} + Append To List ${central_push_config} ${push_central_config} ${gorgone_core_config} - Start Gorgone debug ${central_name} - Start Gorgone debug ${poller_name} + @{poller_push_config}= Copy List ${poller_config} + Append To List ${poller_push_config} ${push_poller_config} + Setup Gorgone Config ${central_push_config} gorgone_name=${central_name} sql_file=${ROOT_CONFIG}db_add_1_poller.sql + Setup Gorgone Config ${poller_push_config} gorgone_name=${poller_name} + + Start Gorgone debug ${poller_name} + Wait Until Port Is Bind 5556 + Start Gorgone debug ${central_name} + Check Poller Is Connected port=5556 expected_nb=2 Check Poller Communicate 2 + ELSE IF '${communication_mode}' == 'pullwss' - Setup Gorgone Config ${pullwss_central_config} ${ROOT_CONFIG}pullwss_node_register_one_node.yaml ${gorgone_core_config} gorgone_name=${central_name} sql_file=${ROOT_CONFIG}db_add_1_poller.sql - Setup Gorgone Config ${gorgone_core_config} ${pullwss_poller_config} gorgone_name=${poller_name} + + @{central_pullwss_config}= Copy List ${central_config} + Append To List ${central_pullwss_config} ${pullwss_central_config} ${ROOT_CONFIG}pullwss_node_register_one_node.yaml ${gorgone_core_config} + + @{poller_pullwss_config}= Copy List ${poller_config} + Append To List ${poller_pullwss_config} ${gorgone_core_config} ${pullwss_poller_config} + + Setup Gorgone Config ${central_pullwss_config} gorgone_name=${central_name} sql_file=${ROOT_CONFIG}db_add_1_poller.sql + Setup Gorgone Config ${poller_pullwss_config} gorgone_name=${poller_name} Start Gorgone debug ${central_name} Wait Until Port Is Bind 8086 @@ -140,8 +157,14 @@ Setup Two Gorgone Instances Check Poller Is Connected port=8086 expected_nb=2 Check Poller Communicate 2 ELSE IF '${communication_mode}' == 'pull' - Setup Gorgone Config ${pull_central_config} ${ROOT_CONFIG}pull_node_register_one_node.yaml ${gorgone_core_config} gorgone_name=${central_name} sql_file=${ROOT_CONFIG}db_add_1_poller.sql - Setup Gorgone Config ${gorgone_core_config} ${pull_poller_config} gorgone_name=${poller_name} + @{central_pull_config}= Copy List ${central_config} + Append To List ${central_pull_config} ${pull_central_config} ${ROOT_CONFIG}pull_node_register_one_node.yaml ${gorgone_core_config} + + @{poller_pull_config}= Copy List ${poller_config} + Append To List ${poller_pull_config} ${pull_poller_config} ${gorgone_core_config} + + Setup Gorgone Config ${central_pull_config} gorgone_name=${central_name} sql_file=${ROOT_CONFIG}db_add_1_poller.sql + Setup Gorgone Config ${poller_pull_config} gorgone_name=${poller_name} Start Gorgone debug ${central_name} Wait Until Port Is Bind 5556 @@ -159,6 +182,7 @@ Wait Until Port Is Bind BREAK END END + Should Be True ${i} < 9 Gorgone did not listen on port ${port} on time. Log To Console tcp port ${port} bind after ${i} attempt (0.5 seconds each) diff --git a/gorgone/tests/robot/tests/centreon/centenginestats b/gorgone/tests/robot/tests/centreon/centenginestats new file mode 100644 index 00000000000..137a89b04bd --- /dev/null +++ b/gorgone/tests/robot/tests/centreon/centenginestats @@ -0,0 +1,91 @@ +#!/bin/bash +# +# Copyright 2024 Centreon (http://www.centreon.com/) +# +# Centreon is a full-fledged industry-strength solution that meets +# the needs in IT infrastructure and application monitoring for +# service performance. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# this script is a simple place holder to mock the centenginestats binary provided by centreon-engine package. +# it output static data for testing purpose. + +cat << EOF +Centreon Engine Statistics Utility 23.10.6 + +Copyright 2003-2008 Ethan Galstad +Copyright 2011-2013,2016 Centreon +License: GPLv2 + +CURRENT STATUS DATA +------------------------------------------------------ +Status File: /var/log/centreon-engine/status.dat +Status File Age: 0d 0h 0m 26s +Status File Version: (null) + +Program Running Time: 1d 6h 37m 49s +Centreon Engine PID: 597 +Used/High/Total Command Buffers: 0 / 0 / 4096 + +Total Services: 27 +Services Checked: 27 +Services Scheduled: 27 +Services Actively Checked: 27 +Services Passively Checked: 0 +Total Service State Change: 0.000 / 0.000 / 0.000 % +Active Service Latency: 0.102 / 0.955 / 0.550 sec +Active Service Execution Time: 0.001 / 0.332 / 0.132 sec +Active Service State Change: 0.000 / 0.000 / 0.000 % +Active Services Last 1/5/15/60 min: 1 / 16 / 24 / 27 +Passive Service Latency: 0.000 / 0.000 / 0.000 sec +Passive Service State Change: 0.000 / 0.000 / 0.000 % +Passive Services Last 1/5/15/60 min: 0 / 0 / 0 / 0 +Services Ok/Warn/Unk/Crit: 21 / 0 / 6 / 0 +Services Flapping: 0 +Services In Downtime: 0 + +Total Hosts: 6 +Hosts Checked: 5 +Hosts Scheduled: 5 +Hosts Actively Checked: 6 +Host Passively Checked: 0 +Total Host State Change: 0.000 / 0.000 / 0.000 % +Active Host Latency: 0.020 / 0.868 / 0.475 sec +Active Host Execution Time: 0.030 / 0.152 / 0.083 sec +Active Host State Change: 0.000 / 0.000 / 0.000 % +Active Hosts Last 1/5/15/60 min: 0 / 3 / 5 / 5 +Passive Host Latency: 0.000 / 0.000 / 0.000 sec +Passive Host State Change: 0.000 / 0.000 / 0.000 % +Passive Hosts Last 1/5/15/60 min: 0 / 0 / 0 / 0 +Hosts Up/Down/Unreach: 5 / 1 / 0 +Hosts Flapping: 0 +Hosts In Downtime: 0 + +Active Host Checks Last 1/5/15 min: 0 / 5 / 21 + Scheduled: 0 / 3 / 13 + On-demand: 0 / 2 / 8 + Parallel: 0 / 3 / 13 + Serial: 0 / 0 / 0 + Cached: 0 / 2 / 8 +Passive Host Checks Last 1/5/15 min: 0 / 0 / 0 +Active Service Checks Last 1/5/15 min: 1 / 18 / 57 + Scheduled: 1 / 18 / 57 + On-demand: 0 / 0 / 0 + Cached: 0 / 0 / 0 +Passive Service Checks Last 1/5/15 min: 0 / 0 / 0 + +External Commands Last 1/5/15 min: 0 / 0 / 0 + +EOF \ No newline at end of file diff --git a/gorgone/tests/robot/tests/centreon/statistics.robot b/gorgone/tests/robot/tests/centreon/statistics.robot new file mode 100644 index 00000000000..4e002a4d0e6 --- /dev/null +++ b/gorgone/tests/robot/tests/centreon/statistics.robot @@ -0,0 +1,106 @@ +*** Settings *** +Documentation test gorgone statistics module +Resource ${CURDIR}${/}..${/}..${/}resources${/}import.resource +Test Timeout 220s +Suite Setup Suite Setup Statistics Module +Suite Teardown Suite Teardown Statistic Module + +*** Test Cases *** +check statistic module add all centengine data in db ${communication_mode} + [Documentation] Check engine statistics are correctly added in sql Database + @{process_list} Create List ${communication_mode}_gorgone_central ${communication_mode}_gorgone_poller_2 + [Teardown] Stop Gorgone And Remove Gorgone Config @{process_list} sql_file=${ROOT_CONFIG}db_delete_poller.sql + + ${date} Get Current Date increment=-1s + @{central_config} Create List ${ROOT_CONFIG}statistics.yaml ${ROOT_CONFIG}actions.yaml + @{poller_config} Create List ${ROOT_CONFIG}actions.yaml + Setup Two Gorgone Instances central_config=${central_config} communication_mode=${communication_mode} central_name=${communication_mode}_gorgone_central poller_name=${communication_mode}_gorgone_poller_2 poller_config=${poller_config} + + # we first test the module when there is no data in the table, we will test it again when + # there is data in the table to be sure the data are correctly updated. + Execute SQL String DELETE FROM nagios_stats alias=storage + Check If Not Exists In Database SELECT * FROM nagios_stats alias=storage + + Ctn Gorgone Force Engine Statistics Retrieve + # statistics module send the GORGONE_ACTION_FINISH_OK once messages for the action module are sent. + # It don't wait for the action module to send back data or for the processing of the response to be finished. + # So I added a log each time a poller stat have finished to be processed. In this test I know + # I have 2 log because there is the central and one poller. + Ctn Wait For Log ${communication_mode} ${date} + + Ctn Gorgone Check Poller Engine Stats Are Present poller_id=1 + Ctn Gorgone Check Poller Engine Stats Are Present poller_id=2 + + # As the value we set in db are fake and hardcoded, we need to change the data before + # running again the module to be sure data are correctly updated, instead of letting the last value persist. + Query UPDATE nagios_stats SET stat_value=999; alias=storage + ${date2} Get Current Date increment=-1s + + Ctn Gorgone Force Engine Statistics Retrieve + + Ctn Wait For Log ${communication_mode} ${date2} + Ctn Gorgone Check Poller Engine Stats Are Present poller_id=1 + Ctn Gorgone Check Poller Engine Stats Are Present poller_id=2 + + Examples: communication_mode -- + ... push_zmq + ... pullwss + +*** Keywords *** + +Ctn Wait For Log + [Documentation] We can't make a single call because we don't know which will finish first + ... (even if it will often be the central node). So we check first for the central log, then for the poller node + ... from the starting point of the log. In the search, the lib search for the first log, and once it's found + ... start searching the second log from the first log position. + [Arguments] ${communication_mode} ${date} + + ${log_central} Create List poller 1 engine data was integrated in rrd and sql database. + ${result_central} Ctn Find In Log With Timeout log=/var/log/centreon-gorgone/${communication_mode}_gorgone_central/gorgoned.log content=${log_central} date=${date} regex=1 timeout=60 + Should Be True ${result_central} Didn't found the logs : ${result_central} + + ${log_poller2} Create List poller 2 engine data was integrated in rrd and sql database. + ${result_poller2} Ctn Find In Log With Timeout log=/var/log/centreon-gorgone/${communication_mode}_gorgone_central/gorgoned.log content=${log_poller2} date=${date} regex=1 timeout=60 + Should Be True ${result_poller2} Didn't found the Central logs : ${result_poller2} + +Ctn Gorgone Check Poller Engine Stats Are Present + [Arguments] ${poller_id}= + + &{Service Check Latency}= Create Dictionary Min=0.102 Max=0.955 Average=0.550 + &{Host Check Latency}= Create Dictionary Min=0.020 Max=0.868 Average=0.475 + &{Service Check Execution Time}= Create Dictionary Min=0.001 Max=0.332 Average=0.132 + &{Host Check Execution Time}= Create Dictionary Min=0.030 Max=0.152 Average=0.083 + + &{data_check} Create Dictionary Service Check Latency=&{Service Check Latency} Host Check Execution Time=&{Host Check Execution Time} Host Check Latency=&{Host Check Latency} Service Check Execution Time=&{Service Check Execution Time} + + FOR ${stat_label} ${stat_data} IN &{data_check} + + FOR ${stat_key} ${stat_value} IN &{stat_data} + Check If Exists In Database SELECT instance_id FROM nagios_stats WHERE stat_key = '${stat_key}' AND stat_value = '${stat_value}' AND stat_label = '${stat_label}' AND instance_id='${poller_id}'; alias=storage + + END + END + +Ctn Gorgone Force Engine Statistics Retrieve + ${response}= GET http://127.0.0.1:8085/api/centreon/statistics/engine + Log To Console ${response.json()} + Dictionary Should Not Contain Key ${response.json()} error api/centreon/statistics/engine api call resulted in an error : ${response.json()} + + Log To Console engine statistic are being retrived. Gorgone sent a log token : ${response.json()} + +Suite Setup Statistics Module + Set Centenginestat Binary + Connect To Database pymysql ${DBNAME_STORAGE} ${DBUSER} ${DBPASSWORD} ${DBHOST} ${DBPORT} + ... alias=storage + +Set Centenginestat Binary + [Documentation] this keyword add a centenginestats file from the local directory to the /usr/sbin + ... directory and make it executable. This allow to test the gorgone statistics module + ... without installing centreon-engine and starting the service + + Copy File /usr/sbin/centenginestats /usr/sbin/centenginestats-back + Copy File ${CURDIR}${/}centenginestats /usr/sbin/centenginestats + Run chmod 755 /usr/sbin/centenginestats + +Suite Teardown Statistic Module + Copy File /usr/sbin/centenginestats-back /usr/sbin/centenginestats diff --git a/gorgone/tests/robot/tests/core/httpserver.robot b/gorgone/tests/robot/tests/core/httpserver.robot index 7e270a81c33..b50d81caef0 100644 --- a/gorgone/tests/robot/tests/core/httpserver.robot +++ b/gorgone/tests/robot/tests/core/httpserver.robot @@ -1,12 +1,12 @@ *** Settings *** Documentation check gorgone api response Suite Setup Setup Gorgone -Suite Teardown Stop Gorgone And Remove Gorgone Config httpserver_api_statuscode +Suite Teardown Stop Gorgone And Remove Gorgone Config @{gorgone_process_name} Resource ${CURDIR}${/}..${/}..${/}resources${/}import.resource Test Timeout 220s *** Variables *** - +@{gorgone_process_name}= httpserver_api_statuscode *** Test Cases *** check http api get status code ${tc} @@ -44,7 +44,8 @@ check http api post api ${tc} *** Keywords *** Setup Gorgone - Setup Gorgone Config ${push_central_config} ${gorgone_core_config} gorgone_name=httpserver_api_statuscode + @{gorgone_conf} Create List ${push_central_config} ${gorgone_core_config} + Setup Gorgone Config ${gorgone_conf} gorgone_name=httpserver_api_statuscode Start Gorgone debug httpserver_api_statuscode Log To Console \nGorgone Started. We have to wait for it to be ready to respond. diff --git a/gorgone/tests/robot/tests/core/pullwss.robot b/gorgone/tests/robot/tests/core/pullwss.robot index 4357a9db08a..bf2552031bd 100644 --- a/gorgone/tests/robot/tests/core/pullwss.robot +++ b/gorgone/tests/robot/tests/core/pullwss.robot @@ -9,7 +9,7 @@ Test Timeout 220s *** Test Cases *** check one poller can connect to a central and gorgone central stop first - [Teardown] Stop Gorgone And Remove Gorgone Config @{process_list} + [Teardown] Stop Gorgone And Remove Gorgone Config @{process_list} sql_file=${ROOT_CONFIG}db_delete_poller.sql @{process_list} Set Variable pullwss_gorgone_central pullwss_gorgone_poller_2 Log To Console \nStarting the gorgone setup Setup Two Gorgone Instances communication_mode=pullwss central_name=pullwss_gorgone_central poller_name=pullwss_gorgone_poller_2 @@ -17,7 +17,7 @@ check one poller can connect to a central and gorgone central stop first Log To Console End of tests. check one poller can connect to a central and gorgone poller stop first - [Teardown] Stop Gorgone And Remove Gorgone Config @{process_list} + [Teardown] Stop Gorgone And Remove Gorgone Config @{process_list} sql_file=${ROOT_CONFIG}db_delete_poller.sql @{process_list} Set Variable pullwss_gorgone_poller_2 pullwss_gorgone_central Log To Console \nStarting the gorgone setup diff --git a/gorgone/tests/robot/tests/start_stop/start_stop.robot b/gorgone/tests/robot/tests/start_stop/start_stop.robot index acb09d2b8d4..f374e9e9732 100644 --- a/gorgone/tests/robot/tests/start_stop/start_stop.robot +++ b/gorgone/tests/robot/tests/start_stop/start_stop.robot @@ -3,16 +3,13 @@ Documentation Start and stop gorgone Resource ${CURDIR}${/}..${/}..${/}resources${/}import.resource Test Timeout 120s -*** Variables *** -${configfile} ${CURDIR}${/}config.yaml *** Test Cases *** Start and stop gorgone - # fichier de conf : pull_central + autodiscovery - # start gorgone 2 - # stop gorgone 2 + @{configfile} Create List ${CURDIR}${/}config.yaml FOR ${i} IN RANGE 5 ${gorgone_name}= Set Variable gorgone_start_stop${i} + Setup Gorgone Config ${configfile} gorgone_name=${gorgone_name} Log To Console Starting Gorgone... Start Gorgone debug ${gorgone_name} From ddff22fc97301a2a9cafa81399c77e97250610f9 Mon Sep 17 00:00:00 2001 From: David Boucher <dboucher@centreon.com> Date: Wed, 7 Aug 2024 15:07:27 +0200 Subject: [PATCH 904/948] fix(common/log_v2): When apply() called, the log file is reopened * fix(common/log_v2): the otel logger becomes the otl logger * fix(tests/engine): new test on Engine reload and log reopened * cleanup(common/log_v2): new comment to explain the new file This reopen is needed because of the log rotate that moves the log file into the archives directory and then reloads centengine. But since centengine did not reopen the file, it still wrote in it instead of opening a new one. --- common/log_v2/centreon_file_sink.hh | 102 ++++++++++++++++++++++ common/log_v2/log_v2.cc | 15 +++- common/log_v2/log_v2.hh | 3 +- common/tests/log_v2/log_v2.cc | 6 +- engine/modules/opentelemetry/src/main.cc | 6 +- engine/src/commands/otel_connector.cc | 2 +- engine/src/configuration/applier/state.cc | 2 +- tests/broker/log.robot | 2 +- tests/engine/reload-and-logs.robot | 39 +++++++++ 9 files changed, 163 insertions(+), 14 deletions(-) create mode 100644 common/log_v2/centreon_file_sink.hh create mode 100644 tests/engine/reload-and-logs.robot diff --git a/common/log_v2/centreon_file_sink.hh b/common/log_v2/centreon_file_sink.hh new file mode 100644 index 00000000000..d8a1693af9d --- /dev/null +++ b/common/log_v2/centreon_file_sink.hh @@ -0,0 +1,102 @@ +/** + * Copyright(c) 2015-present, Gabi Melman & spdlog contributors. + * Distributed under the MIT License (http://opensource.org/licenses/MIT) + * + * This file is copied from basic_file_sink{-inl.h,.h} + * The goal here is just to add a method `reopen()` using the file_helper mutex. + */ +#pragma once + +#include <spdlog/common.h> +#include <spdlog/details/file_helper.h> +#include <spdlog/details/null_mutex.h> +#include <spdlog/details/os.h> +#include <spdlog/details/synchronous_factory.h> +#include <spdlog/sinks/base_sink.h> + +#include <mutex> +#include <string> + +namespace spdlog { +namespace sinks { +/* + * Trivial file sink with single file as target + */ +template <typename Mutex> +class centreon_file_sink final : public base_sink<Mutex> { + public: + explicit centreon_file_sink(const filename_t& filename, + bool truncate = false, + const file_event_handlers& event_handlers = {}); + const filename_t& filename() const; + void reopen(); + + protected: + void sink_it_(const details::log_msg& msg) override; + void flush_() override; + + private: + details::file_helper file_helper_; +}; + +using centreon_file_sink_mt = centreon_file_sink<std::mutex>; +using centreon_file_sink_st = centreon_file_sink<details::null_mutex>; + +template <typename Mutex> +SPDLOG_INLINE centreon_file_sink<Mutex>::centreon_file_sink( + const filename_t& filename, + bool truncate, + const file_event_handlers& event_handlers) + : file_helper_{event_handlers} { + file_helper_.open(filename, truncate); +} + +template <typename Mutex> +SPDLOG_INLINE const filename_t& centreon_file_sink<Mutex>::filename() const { + return file_helper_.filename(); +} + +template <typename Mutex> +SPDLOG_INLINE void centreon_file_sink<Mutex>::reopen() { + std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); + file_helper_.reopen(false); +} + +template <typename Mutex> +SPDLOG_INLINE void centreon_file_sink<Mutex>::sink_it_( + const details::log_msg& msg) { + memory_buf_t formatted; + base_sink<Mutex>::formatter_->format(msg, formatted); + file_helper_.write(formatted); +} + +template <typename Mutex> +SPDLOG_INLINE void centreon_file_sink<Mutex>::flush_() { + file_helper_.flush(); +} +} // namespace sinks + +// +// factory functions +// +template <typename Factory = spdlog::synchronous_factory> +inline std::shared_ptr<logger> basic_logger_mt( + const std::string& logger_name, + const filename_t& filename, + bool truncate = false, + const file_event_handlers& event_handlers = {}) { + return Factory::template create<sinks::centreon_file_sink_mt>( + logger_name, filename, truncate, event_handlers); +} + +template <typename Factory = spdlog::synchronous_factory> +inline std::shared_ptr<logger> basic_logger_st( + const std::string& logger_name, + const filename_t& filename, + bool truncate = false, + const file_event_handlers& event_handlers = {}) { + return Factory::template create<sinks::centreon_file_sink_st>( + logger_name, filename, truncate, event_handlers); +} + +} // namespace spdlog diff --git a/common/log_v2/log_v2.cc b/common/log_v2/log_v2.cc index ae19bc9f6f6..d1d17c5bf05 100644 --- a/common/log_v2/log_v2.cc +++ b/common/log_v2/log_v2.cc @@ -21,12 +21,12 @@ #include <absl/container/flat_hash_set.h> #include <grpc/impl/codegen/log.h> #include <spdlog/common.h> -#include <spdlog/sinks/basic_file_sink.h> #include <spdlog/sinks/null_sink.h> #include <spdlog/sinks/rotating_file_sink.h> #include <spdlog/sinks/stdout_color_sinks.h> #include <spdlog/sinks/stdout_sinks.h> #include <spdlog/sinks/syslog_sink.h> +#include "centreon_file_sink.hh" #include <atomic> #include <initializer_list> @@ -67,7 +67,7 @@ const std::array<std::string, log_v2::LOGGER_SIZE> log_v2::_logger_name = { "comments", "macros", "runtime", - "otel"}; + "otl"}; /** * @brief this function is passed to grpc in order to log grpc layer's events to @@ -227,7 +227,7 @@ void log_v2::create_loggers(config::logger_type typ, size_t length) { my_sink = std::make_shared<sinks::rotating_file_sink_mt>( _file_path, _current_max_size, 99); else - my_sink = std::make_shared<sinks::basic_file_sink_mt>(_file_path); + my_sink = std::make_shared<sinks::centreon_file_sink_mt>(_file_path); } break; case config::logger_type::LOGGER_SYSLOG: my_sink = std::make_shared<sinks::syslog_sink_mt>(_log_name, 0, 0, true); @@ -302,7 +302,7 @@ void log_v2::apply(const config& log_conf) { my_sink = std::make_shared<sinks::rotating_file_sink_mt>( _file_path, log_conf.max_size(), 99); else - my_sink = std::make_shared<sinks::basic_file_sink_mt>(_file_path); + my_sink = std::make_shared<sinks::centreon_file_sink_mt>(_file_path); } break; case config::logger_type::LOGGER_SYSLOG: my_sink = @@ -373,6 +373,13 @@ void log_v2::apply(const config& log_conf) { logger->flush_on(lvl); } } + + for (auto& s : _loggers[0]->sinks()) { + spdlog::sinks::centreon_file_sink_mt* file_sink = + dynamic_cast<spdlog::sinks::centreon_file_sink_mt*>(s.get()); + if (file_sink) + file_sink->reopen(); + } } /** diff --git a/common/log_v2/log_v2.hh b/common/log_v2/log_v2.hh index efcec015be5..7269b1fb071 100644 --- a/common/log_v2/log_v2.hh +++ b/common/log_v2/log_v2.hh @@ -82,7 +82,7 @@ class log_v2 { COMMENTS = 26, MACROS = 27, RUNTIME = 28, - OTEL = 29, + OTL = 29, LOGGER_SIZE }; @@ -93,7 +93,6 @@ class log_v2 { std::string _file_path; const static std::array<std::string, LOGGER_SIZE> _logger_name; std::array<std::shared_ptr<spdlog::logger>, LOGGER_SIZE> _loggers; - std::atomic<config::logger_type> _current_log_type; size_t _current_max_size = 0U; bool _log_pid = false; bool _log_source = false; diff --git a/common/tests/log_v2/log_v2.cc b/common/tests/log_v2/log_v2.cc index 66ab44d67be..31e00f4906a 100644 --- a/common/tests/log_v2/log_v2.cc +++ b/common/tests/log_v2/log_v2.cc @@ -61,10 +61,12 @@ TEST_F(TestLogV2, LoggerUpdated) { const auto& core_logger = log_v2::instance().get(log_v2::CORE); ASSERT_EQ(core_logger->level(), spdlog::level::info); testing::internal::CaptureStdout(); - core_logger->info("First log"); - core_logger->debug("First debug log"); config cfg("/tmp/test.log", config::logger_type::LOGGER_STDOUT, 0, false, false); + cfg.set_level("core", "info"); + log_v2::instance().apply(cfg); + core_logger->info("First log"); + core_logger->debug("First debug log"); cfg.set_level("core", "debug"); log_v2::instance().apply(cfg); ASSERT_EQ(core_logger->level(), spdlog::level::debug); diff --git a/engine/modules/opentelemetry/src/main.cc b/engine/modules/opentelemetry/src/main.cc index 54a63103f57..0c7ef3158f3 100644 --- a/engine/modules/opentelemetry/src/main.cc +++ b/engine/modules/opentelemetry/src/main.cc @@ -56,7 +56,7 @@ extern std::shared_ptr<asio::io_context> g_io_context; * @return 0 on success, any other value on failure. */ extern "C" int nebmodule_deinit(int /*flags*/, int /*reason*/) { - open_telemetry::unload(log_v2::instance().get(log_v2::OTEL)); + open_telemetry::unload(log_v2::instance().get(log_v2::OTL)); return 0; } @@ -107,7 +107,7 @@ extern "C" int nebmodule_init(int flags, char const* args, void* handle) { throw msg_fmt("main: no configuration file provided"); open_telemetry::load(conf_file_path, g_io_context, - log_v2::instance().get(log_v2::OTEL)); + log_v2::instance().get(log_v2::OTL)); commands::otel_connector::init_all(); return 0; @@ -118,6 +118,6 @@ extern "C" int nebmodule_init(int flags, char const* args, void* handle) { * */ extern "C" int nebmodule_reload() { - open_telemetry::reload(log_v2::instance().get(log_v2::OTEL)); + open_telemetry::reload(log_v2::instance().get(log_v2::OTL)); return 0; } diff --git a/engine/src/commands/otel_connector.cc b/engine/src/commands/otel_connector.cc index 44538b01e0f..e311d5192f6 100644 --- a/engine/src/commands/otel_connector.cc +++ b/engine/src/commands/otel_connector.cc @@ -122,7 +122,7 @@ otel_connector::otel_connector(const std::string& connector_name, commands::command_listener* listener) : command(connector_name, cmd_line, listener, e_type::otel), _host_serv_list(std::make_shared<otel::host_serv_list>()), - _logger(log_v2::instance().get(log_v2::OTEL)) { + _logger(log_v2::instance().get(log_v2::OTL)) { init(); } diff --git a/engine/src/configuration/applier/state.cc b/engine/src/configuration/applier/state.cc index 15453d1912e..888a3046869 100644 --- a/engine/src/configuration/applier/state.cc +++ b/engine/src/configuration/applier/state.cc @@ -1102,7 +1102,7 @@ void applier::state::apply_log_config(configuration::state& new_cfg) { log_cfg.set_level("macros", new_cfg.log_level_macros()); log_cfg.set_level("process", new_cfg.log_level_process()); log_cfg.set_level("runtime", new_cfg.log_level_runtime()); - log_cfg.set_level("otel", new_cfg.log_level_otl()); + log_cfg.set_level("otl", new_cfg.log_level_otl()); if (has_already_been_loaded) log_cfg.allow_only_atomic_changes(true); log_v2::instance().apply(log_cfg); diff --git a/tests/broker/log.robot b/tests/broker/log.robot index a482cec556c..b533b5bf212 100644 --- a/tests/broker/log.robot +++ b/tests/broker/log.robot @@ -126,7 +126,7 @@ BLBD ... ${SPACE}${SPACE}value: "error" ... } ... level { -... ${SPACE}${SPACE}key: "otel" +... ${SPACE}${SPACE}key: "otl" ... ${SPACE}${SPACE}value: "error" ... } ... level { diff --git a/tests/engine/reload-and-logs.robot b/tests/engine/reload-and-logs.robot new file mode 100644 index 00000000000..a9ae964c372 --- /dev/null +++ b/tests/engine/reload-and-logs.robot @@ -0,0 +1,39 @@ +*** Settings *** +Documentation Centreon Engine forced checks tests + +Resource ../resources/import.resource + +Suite Setup Ctn Clean Before Suite +Suite Teardown Ctn Clean After Suite +Test Setup Ctn Stop Processes + + +*** Test Cases *** +ERL + [Documentation] Engine is started and writes logs in centengine.log. + ... Then we remove the log file. The file disappears but Engine is still writing into it. + ... Engine is reloaded and the centengine.log should appear again. + [Tags] engine log-v2 MON-146656 + Ctn Config Engine ${1} + Ctn Engine Config Set Value ${0} log_legacy_enabled ${0} + Ctn Engine Config Set Value ${0} log_v2_enabled ${1} + Ctn Engine Config Set Value ${0} log_level_events info + Ctn Engine Config Set Value ${0} log_flush_period 0 + + Ctn Clear Retention + Ctn Clear Db hosts + ${start} Ctn Get Round Current Date + Ctn Start Engine + Ctn Wait For Engine To Be Ready ${start} ${1} + + File Should Exist ${VarRoot}/log/centreon-engine/config0/centengine.log + + Remove File ${VarRoot}/log/centreon-engine/config0/centengine.log + + Sleep 5s + + File Should Not Exist ${VarRoot}/log/centreon-engine/config0/centengine.log + Ctn Reload Engine + + Wait Until Created ${VarRoot}/log/centreon-engine/config0/centengine.log timeout=30s + Ctn Stop Engine From 1f896947ba8f5a6ab2fe2c55ab361970ebb06dd2 Mon Sep 17 00:00:00 2001 From: sdepassio <114986849+sdepassio@users.noreply.github.com> Date: Wed, 7 Aug 2024 17:55:32 +0200 Subject: [PATCH 905/948] fix(gorgone): service discovery error because of a missing table (#1608) * Format code * fix * update * update * Update * update indent * fix --- .../modules/centreon/autodiscovery/hooks.pm | 49 +- .../autodiscovery/services/discovery.pm | 466 ++++++++++-------- .../autodiscovery/services/resources.pm | 14 - 3 files changed, 281 insertions(+), 248 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm b/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm index f20befe507a..ae20261c054 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/hooks.pm @@ -43,15 +43,18 @@ my $config_core; my $config; my ($config_db_centreon, $config_db_centstorage); my $autodiscovery = {}; -my $stop = 0; +my $stop = 0; sub register { my (%options) = @_; - - $config = $options{config}; - $config_core = $options{config_core}; - $config_db_centreon = $options{config_db_centreon}; + + $config = $options{config}; + $config_core = $options{config_core}; + $config_db_centreon = $options{config_db_centreon}; $config_db_centstorage = $options{config_db_centstorage}; + + $config->{vault_file} = defined($config->{vault_file}) ? $config->{vault_file} : '/var/lib/centreon/vault/vault.json'; + return (1, NAMESPACE, NAME, EVENTS); } @@ -71,20 +74,20 @@ sub routing { if (gorgone::class::core::waiting_ready(ready => \$autodiscovery->{ready}) == 0) { gorgone::standard::library::add_history({ - dbh => $options{dbh}, - code => GORGONE_ACTION_FINISH_KO, - token => $options{token}, - data => { msg => 'gorgoneautodiscovery: still no ready' }, + dbh => $options{dbh}, + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, + data => { msg => 'gorgoneautodiscovery: still no ready' }, json_encode => 1 }); return undef; } $options{gorgone}->send_internal_message( - identity => 'gorgone-autodiscovery', - action => $options{action}, + identity => 'gorgone-autodiscovery', + action => $options{action}, raw_data_ref => $options{frame}->getRawData(), - token => $options{token} + token => $options{token} ); } @@ -119,16 +122,16 @@ sub check { foreach my $pid (keys %{$options{dead_childs}}) { # Not me next if (!defined($autodiscovery->{pid}) || $autodiscovery->{pid} != $pid); - + $autodiscovery = {}; delete $options{dead_childs}->{$pid}; if ($stop == 0) { create_child(logger => $options{logger}); } } - - $count++ if (defined($autodiscovery->{running}) && $autodiscovery->{running} == 1); - + + $count++ if (defined($autodiscovery->{running}) && $autodiscovery->{running} == 1); + return $count; } @@ -141,17 +144,17 @@ sub broadcast { # Specific functions sub create_child { my (%options) = @_; - + $options{logger}->writeLogInfo("[autodiscovery] Create module 'autodiscovery' process"); my $child_pid = fork(); if ($child_pid == 0) { - $0 = 'gorgone-autodiscovery'; + $0 = 'gorgone-autodiscovery'; my $module = gorgone::modules::centreon::autodiscovery::class->new( - module_id => NAME, - logger => $options{logger}, - config_core => $config_core, - config => $config, - config_db_centreon => $config_db_centreon, + module_id => NAME, + logger => $options{logger}, + config_core => $config_core, + config => $config, + config_db_centreon => $config_db_centreon, config_db_centstorage => $config_db_centstorage ); $module->run(); diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm index 7024085e43c..f302644ad6d 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm @@ -31,26 +31,27 @@ use Net::SMTP; use XML::Simple; use POSIX qw(strftime); use Safe; +use JSON::XS; sub new { my ($class, %options) = @_; - my $connector = $class->SUPER::new(%options); + my $connector = $class->SUPER::new(%options); bless $connector, $class; - $connector->{internal_socket} = $options{internal_socket}; - $connector->{class_object_centreon} = $options{class_object_centreon}; + $connector->{internal_socket} = $options{internal_socket}; + $connector->{class_object_centreon} = $options{class_object_centreon}; $connector->{class_object_centstorage} = $options{class_object_centstorage}; - $connector->{class_autodiscovery} = $options{class_autodiscovery}; - $connector->{tpapi_clapi} = $options{tpapi_clapi}; - $connector->{mail_subject} = defined($connector->{config}->{mail_subject}) ? $connector->{config}->{mail_subject} : 'Centreon Auto Discovery'; - $connector->{mail_from} = defined($connector->{config}->{mail_from}) ? $connector->{config}->{mail_from} : 'centreon-autodisco'; + $connector->{class_autodiscovery} = $options{class_autodiscovery}; + $connector->{tpapi_clapi} = $options{tpapi_clapi}; + $connector->{mail_subject} = defined($connector->{config}->{mail_subject}) ? $connector->{config}->{mail_subject} : 'Centreon Auto Discovery'; + $connector->{mail_from} = defined($connector->{config}->{mail_from}) ? $connector->{config}->{mail_from} : 'centreon-autodisco'; - $connector->{service_pollers} = {}; - $connector->{audit_user_id} = undef; + $connector->{service_pollers} = {}; + $connector->{audit_user_id} = undef; $connector->{service_parrallel_commands_poller} = 8; - $connector->{service_current_commands_poller} = {}; - $connector->{finished} = 0; - $connector->{post_execution} = 0; + $connector->{service_current_commands_poller} = {}; + $connector->{finished} = 0; + $connector->{post_execution} = 0; $connector->{safe_display} = Safe->new(); $connector->{safe_display}->share('$values'); @@ -82,7 +83,7 @@ sub database_init_transaction { sub database_commit_transaction { my ($self, %options) = @_; - + my $status = $self->{class_object_centreon}->commit(); if ($status == -1) { $self->{logger}->writeLogError("$@"); @@ -151,7 +152,13 @@ sub send_email { } if (scalar(@$body) > 0) { - $self->{logger}->writeLogDebug("[autodiscovery] -servicediscovery- $self->{uuid} send email to '" . $contact_id . "' (" . $self->{discovery}->{rules}->{$rule_id}->{contact}->{$contact_id}->{contact_email} . ")"); + $self->{logger}->writeLogDebug("[autodiscovery] -servicediscovery- $self->{uuid} send email to '" . $contact_id . "' (" . $self + ->{discovery} + ->{rules} + ->{$rule_id} + ->{contact} + ->{$contact_id} + ->{contact_email} . ")"); my $smtp = Net::SMTP->new('localhost', Timeout => 15); if (!defined($smtp)) { @@ -190,8 +197,8 @@ sub restart_pollers { $self->{logger}->writeLogInfo("[autodiscovery] -servicediscovery- $self->{uuid} generate poller config '" . $poller_id . "'"); $self->send_internal_action({ action => 'COMMAND', - token => $self->{discovery}->{token} . ':config', - data => { + token => $self->{discovery}->{token} . ':config', + data => { content => [ { command => $self->{tpapi_clapi}->get_applycfg_command(poller_id => $poller_id) @@ -204,12 +211,12 @@ sub restart_pollers { sub audit_update { my ($self, %options) = @_; - + return if ($self->{discovery}->{audit_enable} != 1); - my $query = 'INSERT INTO log_action (action_log_date, object_type, object_id, object_name, action_type, log_contact_id) VALUES (?, ?, ?, ?, ?, ?)'; + my $query = 'INSERT INTO log_action (action_log_date, object_type, object_id, object_name, action_type, log_contact_id) VALUES (?, ?, ?, ?, ?, ?)'; my ($status, $sth) = $self->{class_object_centstorage}->custom_execute( - request => $query, + request => $query, bind_values => [time(), $options{object_type}, $options{object_id}, $options{object_name}, $options{action_type}, $options{contact_id}] ); @@ -217,9 +224,9 @@ sub audit_update { my $action_log_id = $self->{class_object_centstorage}->{db_centreon}->last_insert_id(); foreach (keys %{$options{fields}}) { - $query = 'INSERT INTO log_action_modification (action_log_id, field_name, field_value) VALUES (?, ?, ?)'; + $query = 'INSERT INTO log_action_modification (action_log_id, field_name, field_value) VALUES (?, ?, ?)'; ($status) = $self->{class_object_centstorage}->custom_execute( - request => $query, + request => $query, bind_values => [$action_log_id, $_, $options{fields}->{$_}] ); if ($status == -1) { @@ -246,13 +253,13 @@ sub custom_variables { sub get_description { my ($self, %options) = @_; - + my $desc = $options{discovery_svc}->{service_name}; if (defined($self->{discovery}->{rules}->{ $options{rule_id} }->{rule_scan_display_custom}) && $self->{discovery}->{rules}->{ $options{rule_id} }->{rule_scan_display_custom} ne '') { local $SIG{__DIE__} = 'IGNORE'; our $description = $desc; - our $values = { attributes => $options{discovery_svc}->{attributes}, service_name => $options{discovery_svc}->{service_name} }; + our $values = { attributes => $options{discovery_svc}->{attributes}, service_name => $options{discovery_svc}->{service_name} }; $self->{safe_display}->reval($self->{discovery}->{rules}->{ $options{rule_id} }->{rule_scan_display_custom}, 1); if ($@) { $self->{logger}->writeLogError("$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] custom description code execution problem: " . $@); @@ -266,27 +273,27 @@ sub get_description { sub link_service_autodisco { my ($self, %options) = @_; - - my $query = 'INSERT IGNORE INTO mod_auto_disco_rule_service_relation (rule_rule_id, service_service_id) VALUES (' . $options{rule_id} . ', ' . $options{service_id} . ')'; + + my $query = 'INSERT IGNORE INTO mod_auto_disco_rule_service_relation (rule_rule_id, service_service_id) VALUES (' . $options{rule_id} . ', ' . $options{service_id} . ')'; my ($status, $sth) = $self->{class_object_centreon}->custom_execute(request => $query); if ($status == -1) { return -1; } - + return 0; } sub update_service { my ($self, %options) = @_; - my %query_update = (); - my @journal = (); - my @update_macros = (); - my @insert_macros = (); - + my %query_update = (); + my @journal = (); + my @update_macros = (); + my @insert_macros = (); + if ($self->{discovery}->{is_manual} == 1) { - $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{discovery}->{ $options{discovery_svc}->{service_name} } = { - type => 0, - macros => {}, + $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{discovery}->{ $options{discovery_svc}->{service_name} } = { + type => 0, + macros => {}, description => $self->get_description(%options) }; } @@ -296,25 +303,29 @@ sub update_service { if ($options{service}->{template_id} != $self->{discovery}->{rules}->{ $options{rule_id} }->{service_template_model_id}) { $query_update{service_template_model_stm_id} = $self->{discovery}->{rules}->{ $options{rule_id} }->{service_template_model_id}; push @journal, { - host_name => $self->{discovery}->{hosts}->{ $options{host_id} }->{host_name}, + host_name => $self->{discovery}->{hosts}->{ $options{host_id} }->{host_name}, service_name => $options{discovery_svc}->{service_name}, - type => 'update', - msg => 'template', - rule_id => $options{rule_id} - }; + type => 'update', + msg => 'template', + rule_id => $options{rule_id} + }; $self->{logger}->writeLogDebug("$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> service update template"); if ($self->{discovery}->{is_manual} == 1) { - $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{discovery}->{ $options{discovery_svc}->{service_name} }->{service_template_model_stm_id} = $self->{discovery}->{rules}->{ $options{rule_id} }->{service_template_model_id}; + $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{discovery}->{ $options{discovery_svc}->{service_name} }->{service_template_model_stm_id} = $self + ->{discovery} + ->{rules} + ->{ $options{rule_id} } + ->{service_template_model_id}; } } if ($options{service}->{activate} == '0') { $query_update{service_activate} = "'1'"; push @journal, { - host_name => $self->{discovery}->{hosts}->{ $options{host_id} }->{host_name}, + host_name => $self->{discovery}->{hosts}->{ $options{host_id} }->{host_name}, service_name => $options{discovery_svc}->{service_name}, - type => 'enable', - rule_id => $options{rule_id} + type => 'enable', + rule_id => $options{rule_id} }; $self->{logger}->writeLogDebug("$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> service enable"); } @@ -322,30 +333,32 @@ sub update_service { foreach my $macro_name (keys %{$options{macros}}) { if (!defined($options{service}->{macros}->{'$_SERVICE' . $macro_name . '$'})) { push @insert_macros, { - name => $macro_name, + name => $macro_name, value => $options{macros}->{$macro_name} }; if ($self->{discovery}->{is_manual} == 1) { - $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{discovery}->{ $options{discovery_svc}->{service_name} }->{macros}->{$macro_name} = { value => $options{macros}->{$macro_name}, type => 1 }; + $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{discovery}->{ $options{discovery_svc}->{service_name} }->{macros}->{$macro_name} = + { value => $options{macros} ->{$macro_name}, type => 1 }; } - } elsif ($options{service}->{macros}->{'$_SERVICE' . $macro_name . '$'} ne $options{macros}->{$macro_name}) { + } elsif ($options{service}->{macros}->{'$_SERVICE' . $macro_name . '$'} ne $options{macros}->{$macro_name}) { push @update_macros, { - name => $macro_name, + name => $macro_name, value => $options{macros}->{$macro_name} }; if ($self->{discovery}->{is_manual} == 1) { - $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{discovery}->{ $options{discovery_svc}->{service_name} }->{macros}->{$macro_name} = { value => $options{macros}->{$macro_name}, type => 0 }; + $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{discovery}->{ $options{discovery_svc}->{service_name} }->{macros}->{$macro_name} = + { value => $options{macros}->{$macro_name}, type => 0 }; } } } if (scalar(@insert_macros) > 0 || scalar(@update_macros) > 0) { push @journal, { - host_name => $self->{discovery}->{hosts}->{ $options{host_id} }->{host_name}, + host_name => $self->{discovery}->{hosts}->{ $options{host_id} }->{host_name}, service_name => $options{discovery_svc}->{service_name}, - type => 'update', - msg => 'macros', - rule_id => $options{rule_id} + type => 'update', + msg => 'macros', + rule_id => $options{rule_id} }; $self->{logger}->writeLogDebug("$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> service update/insert macros"); } @@ -355,23 +368,23 @@ sub update_service { return -1 if ($self->database_init_transaction() == -1); if (scalar(keys %query_update) > 0) { - my $set = ''; + my $set = ''; my $set_append = ''; foreach (keys %query_update) { - $set .= $set_append . $_ . ' = ' . $query_update{$_}; + $set .= $set_append . $_ . ' = ' . $query_update{$_}; $set_append = ', '; } - my $query = 'UPDATE service SET ' . $set . ' WHERE service_id = ' . $options{service}->{id}; + my $query = 'UPDATE service SET ' . $set . ' WHERE service_id = ' . $options{service}->{id}; my ($status) = $self->{class_object_centreon}->custom_execute(request => $query); if ($status == -1) { return $self->database_error_rollback(message => "$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> cannot update service"); } } - + foreach (@update_macros) { - my $query = 'UPDATE on_demand_macro_service SET svc_macro_value = ? WHERE svc_svc_id = ' . $options{service}->{id} . ' AND svc_macro_name = ?'; + my $query = 'UPDATE on_demand_macro_service SET svc_macro_value = ? WHERE svc_svc_id = ' . $options{service}->{id} . ' AND svc_macro_name = ?'; my ($status) = $self->{class_object_centreon}->custom_execute( - request => $query, + request => $query, bind_values => [$_->{value}, '$_SERVICE' . $_->{name} . '$'] ); if ($status == -1) { @@ -379,16 +392,16 @@ sub update_service { } } foreach (@insert_macros) { - my $query = 'INSERT on_demand_macro_service (svc_svc_id, svc_macro_name, svc_macro_value) VALUES (' . $options{service}->{id} . ', ?, ?)'; + my $query = 'INSERT on_demand_macro_service (svc_svc_id, svc_macro_name, svc_macro_value) VALUES (' . $options{service}->{id} . ', ?, ?)'; my ($status) = $self->{class_object_centreon}->custom_execute( - request => $query, + request => $query, bind_values => ['$_SERVICE' . $_->{name} . '$', $_->{value}] ); if ($status == -1) { return $self->database_error_rollback(message => "$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> cannot insert macro"); } } - + if ($self->link_service_autodisco(%options, service_id => $options{service}->{id}) == -1) { return $self->database_error_rollback(message => "$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> cannot link service to autodisco"); } @@ -400,21 +413,21 @@ sub update_service { if (defined($query_update{service_activate})) { $self->audit_update( - object_type => 'service', - action_type => 'enable', - object_id => $options{service}->{id}, - object_name => $options{discovery_svc}->{service_name}, - contact_id => $self->{audit_user_id} + object_type => 'service', + action_type => 'enable', + object_id => $options{service}->{id}, + object_name => $options{discovery_svc}->{service_name}, + contact_id => $self->{audit_user_id} ); } if (defined($query_update{service_template_model_stm_id})) { $self->audit_update( - object_type => 'service', - action_type => 'c', - object_id => $options{service}->{id}, - object_name => $options{discovery_svc}->{service_name}, - contact_id => $self->{audit_user_id}, - fields => { service_template_model_stm_id => $query_update{service_template_model_stm_id} } + object_type => 'service', + action_type => 'c', + object_id => $options{service}->{id}, + object_name => $options{discovery_svc}->{service_name}, + contact_id => $self->{audit_user_id}, + fields => { service_template_model_stm_id => $query_update{service_template_model_stm_id} } ); } @@ -423,18 +436,18 @@ sub update_service { sub create_service { my ($self, %options) = @_; - + if ($self->{discovery}->{is_manual} == 1) { - $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{discovery}->{ $options{discovery_svc}->{service_name} } = { - type => 1, + $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{discovery}->{ $options{discovery_svc}->{service_name} } = { + type => 1, service_template_model_stm_id => $self->{discovery}->{rules}->{ $options{rule_id} }->{service_template_model_id}, - macros => {}, - description => $self->get_description(%options) + macros => {}, + description => $self->get_description(%options) }; foreach (keys %{$options{macros}}) { $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{discovery}->{ $options{discovery_svc}->{service_name} }->{macros}->{$_} = { value => $options{macros}->{$_}, - type => 1 + type => 1 }; } } @@ -444,32 +457,32 @@ sub create_service { return -1 if ($self->database_init_transaction() == -1); - my $query = "INSERT INTO service (service_template_model_stm_id, service_description, service_register) VALUES (?, ?, '1')"; + my $query = "INSERT INTO service (service_template_model_stm_id, service_description, service_register) VALUES (?, ?, '1')"; my ($status, $sth) = $self->{class_object_centreon}->custom_execute( - request => $query, + request => $query, bind_values => [$self->{discovery}->{rules}->{ $options{rule_id} }->{service_template_model_id}, $options{discovery_svc}->{service_name}] ); if ($status == -1) { return $self->database_error_rollback(message => "$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> cannot create service"); } my $service_id = $self->{class_object_centreon}->{db_centreon}->last_insert_id(); - - $query = 'INSERT INTO host_service_relation (host_host_id, service_service_id) VALUES (' . $options{host_id} . ', ' . $service_id . ')'; + + $query = 'INSERT INTO host_service_relation (host_host_id, service_service_id) VALUES (' . $options{host_id} . ', ' . $service_id . ')'; ($status) = $self->{class_object_centreon}->custom_execute(request => $query); if ($status == -1) { return $self->database_error_rollback(message => "$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> cannot link service to host"); } - - $query = 'INSERT INTO extended_service_information (service_service_id) VALUES (' . $service_id . ')'; + + $query = 'INSERT INTO extended_service_information (service_service_id) VALUES (' . $service_id . ')'; ($status) = $self->{class_object_centreon}->custom_execute(request => $query); if ($status == -1) { return $self->database_error_rollback(message => "$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> cannot service extended information"); } - + foreach (keys %{$options{macros}}) { - $query = 'INSERT INTO on_demand_macro_service (svc_svc_id, svc_macro_name, svc_macro_value) VALUES (' . $service_id . ', ?, ?)'; + $query = 'INSERT INTO on_demand_macro_service (svc_svc_id, svc_macro_name, svc_macro_value) VALUES (' . $service_id . ', ?, ?)'; ($status) = $self->{class_object_centreon}->custom_execute( - request => $query, + request => $query, bind_values => ['$_SERVICE' . $_ . '$', $options{macros}->{$_}] ); if ($status == -1) { @@ -480,22 +493,22 @@ sub create_service { if ($self->link_service_autodisco(%options, service_id => $service_id) == -1) { return $self->database_error_rollback(message => "$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> cannot link service to autodisco"); } - + return -1 if ($self->database_commit_transaction() == -1); $self->{discovery}->{pollers_reload}->{ $options{poller_id} } = 1; $self->audit_update( - object_type => 'service', - action_type => 'a', - object_id => $service_id, + object_type => 'service', + action_type => 'a', + object_id => $service_id, object_name => $options{discovery_svc}->{service_name}, - contact_id => $self->{audit_user_id}, - fields => { - service_template_model_id => $self->{discovery}->{rules}->{ $options{rule_id} }->{service_template_model_id}, - service_description => $options{discovery_svc}->{service_name}, - service_register => '1', - service_hPars => $options{host_id} + contact_id => $self->{audit_user_id}, + fields => { + service_template_model_id => $self->{discovery}->{rules}->{ $options{rule_id} }->{service_template_model_id}, + service_description => $options{discovery_svc}->{service_name}, + service_register => '1', + service_hPars => $options{host_id} } ); @@ -504,58 +517,58 @@ sub create_service { sub crud_service { my ($self, %options) = @_; - + my $service_id; if (!defined($options{service})) { $service_id = $self->create_service(%options); $self->{logger}->writeLogDebug("$options{logger_pre_message} [" . $options{discovery_svc}->{service_name} . "] -> service created"); if ($service_id != -1) { push @{$self->{discovery}->{journal}}, { - host_name => $self->{discovery}->{hosts}->{ $options{host_id} }->{host_name}, + host_name => $self->{discovery}->{hosts}->{ $options{host_id} }->{host_name}, service_name => $options{discovery_svc}->{service_name}, - type => 'created', - rule_id => $options{rule_id} + type => 'created', + rule_id => $options{rule_id} }; } } else { $service_id = $self->update_service(%options); } - + return 0; } sub disable_services { my ($self, %options) = @_; - + return if ($self->{discovery}->{rules}->{ $options{rule_id} }->{rule_disable} != 1 || !defined($self->{discovery}->{rules}->{ $options{rule_id} }->{linked_services}->{ $options{host_id} })); foreach my $service (keys %{$self->{discovery}->{rules}->{ $options{rule_id} }->{linked_services}->{ $options{host_id} }}) { my $service_description = $self->{discovery}->{rules}->{ $options{rule_id} }->{linked_services}->{ $options{host_id} }->{$service}->{service_description}; - if (!defined($options{discovery_svc}->{discovered_services}->{$service_description}) && + if (!defined($options{discovery_svc}->{discovered_services}->{$service_description}) && $self->{discovery}->{rules}->{ $options{rule_id} }->{linked_services}->{ $options{host_id} }->{$service}->{service_activate} == 1) { $self->{logger}->writeLogDebug("$options{logger_pre_message} -> disable service '" . $service_description . "'"); next if ($self->{discovery}->{dry_run} == 1); - my $query = "UPDATE service SET service_activate = '0' WHERE service_id = " . $service; + my $query = "UPDATE service SET service_activate = '0' WHERE service_id = " . $service; my ($status) = $self->{class_object_centreon}->custom_execute(request => $query); if ($status == -1) { $self->{logger}->writeLogError("$options{logger_pre_message} -> cannot disable service '" . $service_description . "'"); next; } - + push @{$self->{discovery}->{journal}}, { - host_name => $self->{discovery}->{hosts}->{ $options{host_id} }->{host_name}, + host_name => $self->{discovery}->{hosts}->{ $options{host_id} }->{host_name}, service_name => $service_description, - type => 'disable', - rule_id => $options{rule_id} - }; + type => 'disable', + rule_id => $options{rule_id} + }; $self->{discovery}->{pollers_reload}->{ $options{poller_id} } = 1; $self->audit_update( - object_type => 'service', - action_type => 'disable', - object_id => $service, + object_type => 'service', + action_type => 'disable', + object_id => $service, object_name => $service_description, - contact_id => $self->{audit_user_id} + contact_id => $self->{audit_user_id} ); } } @@ -564,9 +577,9 @@ sub disable_services { sub service_response_parsing { my ($self, %options) = @_; - my $rule_alias = $self->{discovery}->{rules}->{ $options{rule_id} }->{rule_alias}; - my $poller_name = $self->{service_pollers}->{ $options{poller_id} }->{name}; - my $host_name = $self->{discovery}->{hosts}->{ $options{host_id} }->{host_name}; + my $rule_alias = $self->{discovery}->{rules}->{ $options{rule_id} }->{rule_alias}; + my $poller_name = $self->{service_pollers}->{ $options{poller_id} }->{name}; + my $host_name = $self->{discovery}->{hosts}->{ $options{host_id} }->{host_name}; my $logger_pre_message = "[autodiscovery] -servicediscovery- $self->{uuid} [" . $rule_alias . "] [" . $poller_name . "] [" . $host_name . "]"; my $xml; @@ -575,7 +588,7 @@ sub service_response_parsing { }; if ($@) { if ($self->{discovery}->{is_manual} == 1) { - $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{failed} = 1; + $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{failed} = 1; $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{message} = 'load xml issue'; } $self->{logger}->writeLogError("$logger_pre_message -> load xml issue"); @@ -586,18 +599,18 @@ sub service_response_parsing { my $discovery_svc = { discovered_services => {} }; foreach my $attributes (@{$xml->{label}}) { $discovery_svc->{service_name} = ''; - $discovery_svc->{attributes} = $attributes; + $discovery_svc->{attributes} = $attributes; $self->custom_variables( - discovery_svc => $discovery_svc, - rule => $self->{discovery}->{rules}->{ $options{rule_id} }, + discovery_svc => $discovery_svc, + rule => $self->{discovery}->{rules}->{ $options{rule_id} }, logger_pre_message => $logger_pre_message ); gorgone::modules::centreon::autodiscovery::services::resources::change_vars( - discovery_svc => $discovery_svc, - rule => $self->{discovery}->{rules}->{ $options{rule_id} }, - logger => $self->{logger}, + discovery_svc => $discovery_svc, + rule => $self->{discovery}->{rules}->{ $options{rule_id} }, + logger => $self->{logger}, logger_pre_message => $logger_pre_message ); if ($discovery_svc->{service_name} eq '') { @@ -606,7 +619,7 @@ sub service_response_parsing { } if (defined($discovery_svc->{discovered_services}->{ $discovery_svc->{service_name} })) { - $self->{logger}->writeLogError("$logger_pre_message -> service '" . $discovery_svc->{service_name} . "' already created"); + $self->{logger}->writeLogError("$logger_pre_message -> service '" . $discovery_svc->{service_name} . "' already created"); next; } @@ -614,43 +627,43 @@ sub service_response_parsing { next if ( gorgone::modules::centreon::autodiscovery::services::resources::check_exinc( - discovery_svc => $discovery_svc, - rule => $self->{discovery}->{rules}->{ $options{rule_id} }, - logger => $self->{logger}, + discovery_svc => $discovery_svc, + rule => $self->{discovery}->{rules}->{ $options{rule_id} }, + logger => $self->{logger}, logger_pre_message => $logger_pre_message ) ); my $macros = gorgone::modules::centreon::autodiscovery::services::resources::get_macros( discovery_svc => $discovery_svc, - rule => $self->{discovery}->{rules}->{ $options{rule_id} } + rule => $self->{discovery}->{rules}->{ $options{rule_id} } ); - + my ($status, $service) = gorgone::modules::centreon::autodiscovery::services::resources::get_service( class_object_centreon => $self->{class_object_centreon}, - host_id => $options{host_id}, - service_name => $discovery_svc->{service_name}, - logger => $self->{logger}, - logger_pre_message => $logger_pre_message + host_id => $options{host_id}, + service_name => $discovery_svc->{service_name}, + logger => $self->{logger}, + logger_pre_message => $logger_pre_message ); next if ($status == -1); $self->crud_service( - discovery_svc => $discovery_svc, - rule_id => $options{rule_id}, - host_id => $options{host_id}, - poller_id => $options{poller_id}, - service => $service, - macros => $macros, + discovery_svc => $discovery_svc, + rule_id => $options{rule_id}, + host_id => $options{host_id}, + poller_id => $options{poller_id}, + service => $service, + macros => $macros, logger_pre_message => $logger_pre_message ); } $self->disable_services( - discovery_svc => $discovery_svc, - rule_id => $options{rule_id}, - host_id => $options{host_id}, - poller_id => $options{poller_id}, + discovery_svc => $discovery_svc, + rule_id => $options{rule_id}, + host_id => $options{host_id}, + poller_id => $options{poller_id}, logger_pre_message => $logger_pre_message ); } @@ -663,8 +676,13 @@ sub discoverylistener { return 0 if ($data->{code} != GORGONE_MODULE_ACTION_COMMAND_RESULT && $data->{code} != GORGONE_ACTION_FINISH_KO); if ($self->{discovery}->{is_manual} == 1) { - $self->{discovery}->{manual}->{ $options{host_id} } = { rules => {} } if (!defined($self->{discovery}->{manual}->{ $options{host_id} })); - $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} } = { failed => 0, discovery => {} } if (!defined($self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} })); + $self->{discovery}->{manual}->{ $options{host_id} } = { rules => {} } if (!defined($self->{discovery}->{manual}->{ $options{host_id} })); + $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} } = { failed => 0, discovery => {} } if (!defined($self + ->{discovery} + ->{manual} + ->{ $options{host_id} } + ->{rules} + ->{ $options{rule_id} })); } # if i have GORGONE_MODULE_ACTION_COMMAND_RESULT, i can't have GORGONE_ACTION_FINISH_KO @@ -672,22 +690,22 @@ sub discoverylistener { my $exit_code = $data->{data}->{result}->{exit_code}; if ($exit_code == 0) { $self->service_response_parsing( - rule_id => $options{rule_id}, - host_id => $options{host_id}, + rule_id => $options{rule_id}, + host_id => $options{host_id}, poller_id => $self->{discovery}->{hosts}->{ $options{host_id} }->{poller_id}, - response => $data->{data}->{result}->{stdout} + response => $data->{data}->{result}->{stdout} ); } else { $self->{discovery}->{failed_discoveries}++; if ($self->{discovery}->{is_manual} == 1) { - $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{failed} = 1; + $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{failed} = 1; $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{message} = $data->{data}->{message}; - $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{data} = $data->{data}; + $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{data} = $data->{data}; } } } elsif ($data->{code} == GORGONE_ACTION_FINISH_KO) { if ($self->{discovery}->{is_manual} == 1) { - $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{failed} = 1; + $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{failed} = 1; $self->{discovery}->{manual}->{ $options{host_id} }->{rules}->{ $options{rule_id} }->{message} = $data->{data}->{message}; } $self->{discovery}->{failed_discoveries}++; @@ -700,16 +718,16 @@ sub discoverylistener { $self->{discovery}->{done_discoveries}++; my $progress = $self->{discovery}->{done_discoveries} * 100 / $self->{discovery}->{count_discoveries}; - my $div = int(int($progress) / 5); + my $div = int(int($progress) / 5); if ($div > $self->{discovery}->{progress_div}) { $self->{discovery}->{progress_div} = $div; $self->send_log( - code => GORGONE_MODULE_CENTREON_AUTODISCO_SVC_PROGRESS, - token => $self->{discovery}->{token}, + code => GORGONE_MODULE_CENTREON_AUTODISCO_SVC_PROGRESS, + token => $self->{discovery}->{token}, instant => 1, - data => { - message => 'current progress', - complete => sprintf('%.2f', $progress) + data => { + message => 'current progress', + complete => sprintf('%.2f', $progress) } ); } @@ -720,14 +738,14 @@ sub discoverylistener { $self->{finished} = 1; $self->send_log( - code => GORGONE_ACTION_FINISH_OK, + code => GORGONE_ACTION_FINISH_OK, token => $self->{discovery}->{token}, - data => { - message => 'discovery finished', + data => { + message => 'discovery finished', failed_discoveries => $self->{discovery}->{failed_discoveries}, - count_discoveries => $self->{discovery}->{count_discoveries}, - journal => $self->{discovery}->{journal}, - manual => $self->{discovery}->{manual} + count_discoveries => $self->{discovery}->{count_discoveries}, + journal => $self->{discovery}->{journal}, + manual => $self->{discovery}->{manual} } ); } @@ -744,7 +762,7 @@ sub service_discovery_post_exec { $self->restart_pollers(); $self->send_email(); } - + return 0; } @@ -755,7 +773,7 @@ sub service_execute_commands { foreach my $poller_id (keys %{$self->{discovery}->{rules}->{$rule_id}->{hosts}}) { next if (scalar(@{$self->{discovery}->{rules}->{$rule_id}->{hosts}->{$poller_id}}) <= 0); $self->{service_current_commands_poller}->{$poller_id} = 0 if (!defined($self->{service_current_commands_poller}->{$poller_id})); - + while (1) { last if ($self->{service_current_commands_poller}->{$poller_id} >= $self->{service_parrallel_commands_poller}); my $host_id = shift @{$self->{discovery}->{rules}->{$rule_id}->{hosts}->{$poller_id}}; @@ -766,26 +784,26 @@ sub service_execute_commands { my $command = gorgone::modules::centreon::autodiscovery::services::resources::substitute_service_discovery_command( command_line => $self->{discovery}->{rules}->{$rule_id}->{command_line}, - host => $host, - poller => $self->{service_pollers}->{$poller_id}, - vault_count => $options{vault_count} + host => $host, + poller => $self->{service_pollers}->{$poller_id}, + vault_count => $options{vault_count} ); $self->{logger}->writeLogDebug("[autodiscovery] -servicediscovery- $self->{uuid} [" . - $self->{discovery}->{rules}->{$rule_id}->{rule_alias} . "] [" . - $self->{service_pollers}->{$poller_id}->{name} . "] [" . - $host->{host_name} . "] -> substitute string: " . $command + $self->{discovery}->{rules}->{$rule_id}->{rule_alias} . "] [" . + $self->{service_pollers}->{$poller_id}->{name} . "] [" . + $host->{host_name} . "] -> substitute string: " . $command ); $self->send_internal_action({ action => 'ADDLISTENER', - data => [ + data => [ { identity => 'gorgoneautodiscovery', - event => 'SERVICEDISCOVERYLISTENER', - target => $poller_id, - token => 'svc-disco-' . $self->{uuid} . '-' . $rule_id . '-' . $host_id, - timeout => 120, + event => 'SERVICEDISCOVERYLISTENER', + target => $poller_id, + token => 'svc-disco-' . $self->{uuid} . '-' . $rule_id . '-' . $host_id, + timeout => 120, log_pace => 15 } ] @@ -794,8 +812,8 @@ sub service_execute_commands { $self->send_internal_action({ action => 'COMMAND', target => $poller_id, - token => 'svc-disco-' . $self->{uuid} . '-' . $rule_id . '-' . $host_id, - data => { + token => 'svc-disco-' . $self->{uuid} . '-' . $rule_id . '-' . $host_id, + data => { instant => 1, content => [ { @@ -819,9 +837,9 @@ sub launchdiscovery { $self->{logger}->writeLogInfo("[autodiscovery] -servicediscovery- $self->{uuid} discovery start"); $self->send_log( - code => GORGONE_ACTION_BEGIN, + code => GORGONE_ACTION_BEGIN, token => $options{token}, - data => { message => 'servicediscovery start' } + data => { message => 'servicediscovery start' } ); ################ @@ -856,7 +874,7 @@ sub launchdiscovery { } ($status, $message, my $user_id) = gorgone::modules::centreon::autodiscovery::services::resources::get_audit_user_id( class_object_centreon => $self->{class_object_centreon}, - clapi_user => $self->{tpapi_clapi}->get_username() + clapi_user => $self->{tpapi_clapi}->get_username() ); if ($status < 0) { $self->send_log_msg_error(token => $options{token}, subname => 'servicediscovery', number => $self->{uuid}, message => $message); @@ -867,9 +885,8 @@ sub launchdiscovery { ################## # get vault config ################## - ($status, $message, my $vault_count) = gorgone::modules::centreon::autodiscovery::services::resources::get_vault_configured( - class_object_centreon => $self->{class_object_centreon} - ); + + ($status, $message, my $vault_count) = get_vault_count(); if ($status < 0) { $self->send_log_msg_error(token => $options{token}, subname => 'servicediscovery', number => $self->{uuid}, message => $message); return -1; @@ -880,11 +897,11 @@ sub launchdiscovery { ################ $self->{logger}->writeLogDebug("[autodiscovery] -servicediscovery- $self->{uuid} load rules configuration"); - + ($status, $message, my $rules) = gorgone::modules::centreon::autodiscovery::services::resources::get_rules( class_object_centreon => $self->{class_object_centreon}, - filter_rules => $data->{content}->{filter_rules}, - force_rule => (defined($data->{content}->{force_rule}) && $data->{content}->{force_rule} =~ /^1$/) ? 1 : 0 + filter_rules => $data->{content}->{filter_rules}, + force_rule => (defined($data->{content}->{force_rule}) && $data->{content}->{force_rule} =~ /^1$/) ? 1 : 0 ); if ($status < 0) { $self->send_log_msg_error(token => $options{token}, subname => 'servicediscovery', number => $self->{uuid}, message => $message); @@ -896,30 +913,30 @@ sub launchdiscovery { ################# gorgone::modules::centreon::autodiscovery::services::resources::reset_macro_hosts(); my $all_hosts = {}; - my $total = 0; + my $total = 0; foreach my $rule_id (keys %$rules) { ($status, $message, my $hosts, my $count) = gorgone::modules::centreon::autodiscovery::services::resources::get_hosts( - host_template => $rules->{$rule_id}->{host_template}, - poller_id => $rules->{$rule_id}->{poller_id}, + host_template => $rules->{$rule_id}->{host_template}, + poller_id => $rules->{$rule_id}->{poller_id}, class_object_centreon => $self->{class_object_centreon}, - with_macro => 1, - host_lookup => $data->{content}->{filter_hosts}, - poller_lookup => $data->{content}->{filter_pollers}, - vault_count => $vault_count + with_macro => 1, + host_lookup => $data->{content}->{filter_hosts}, + poller_lookup => $data->{content}->{filter_pollers}, + vault_count => $vault_count ); if ($status < 0) { $self->send_log_msg_error(token => $options{token}, subname => 'servicediscovery', number => $self->{uuid}, message => $message); return -1; } - + if (!defined($hosts) || scalar(keys %$hosts) == 0) { $self->{logger}->writeLogInfo("[autodiscovery] -servicediscovery- $self->{uuid} no hosts found for rule '" . $options{rule}->{rule_alias} . "'"); next; } - $total += $count; + $total += $count; $rules->{$rule_id}->{hosts} = $hosts->{pollers}; - $all_hosts = { %$all_hosts, %{$hosts->{infos}} }; + $all_hosts = { %$all_hosts, %{$hosts->{infos}} }; foreach (('rule_scan_display_custom', 'rule_variable_custom')) { if (defined($rules->{$rule_id}->{$_}) && $rules->{$rule_id}->{$_} ne '') { @@ -935,21 +952,21 @@ sub launchdiscovery { } $self->{discovery} = { - token => $options{token}, - count_discoveries => $total, + token => $options{token}, + count_discoveries => $total, failed_discoveries => 0, - done_discoveries => 0, - progress_div => 0, - rules => $rules, - manual => {}, - is_manual => (defined($data->{content}->{manual}) && $data->{content}->{manual} =~ /^1$/) ? 1 : 0, - dry_run => (defined($data->{content}->{dry_run}) && $data->{content}->{dry_run} =~ /^1$/) ? 1 : 0, - audit_enable => $audit_enable, + done_discoveries => 0, + progress_div => 0, + rules => $rules, + manual => {}, + is_manual => (defined($data->{content}->{manual}) && $data->{content}->{manual} =~ /^1$/) ? 1 : 0, + dry_run => (defined($data->{content}->{dry_run}) && $data->{content}->{dry_run} =~ /^1$/) ? 1 : 0, + audit_enable => $audit_enable, no_generate_config => (defined($data->{content}->{no_generate_config}) && $data->{content}->{no_generate_config} =~ /^1$/) ? 1 : 0, - options => defined($data->{content}) ? $data->{content} : {}, - hosts => $all_hosts, - journal => [], - pollers_reload => {} + options => defined($data->{content}) ? $data->{content} : {}, + hosts => $all_hosts, + journal => [], + pollers_reload => {} }; $self->service_execute_commands(vault_count => $vault_count); @@ -963,4 +980,31 @@ sub event { $self->{class_autodiscovery}->event(); } +sub get_vault_count() { + my (%options) = @_; + + # Check if vault config file exists + if (-e $self->{config}->{vault_file}) { + my ($fh, $size); + # Read config file + if (!open($fh, '<', $self->{config}->{vault_file})) { + return (-1, "Could not open $self->{config}->{vault_file}: $!"); + } + my $content = do { + local $/; + <$fh> + }; + close $fh; + # Check JSON validity + my $vault_config; + eval { + $vault_config = JSON::XS->new->decode($content); + }; + if ($@) { + return (-1, "Cannot decode json $self->{config}->{vault_file}: $!"); + } + return (0, '', 1); + } +} + 1; diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm b/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm index 7185326ba4d..2d03081ed0a 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/services/resources.pm @@ -99,20 +99,6 @@ sub get_audit_user_id { return (0, '', $user_id); } -sub get_vault_configured { - my (%options) = @_; - - my ($status, $datas) = $options{class_object_centreon}->custom_execute( - request => "SELECT count(id) FROM vault_configuration", - mode => 2 - ); - if ($status == -1 || !defined($datas->[0])) { - return (-1, 'cannot get number of vault configured'); - } - - return (0, '', $datas->[0]->[0]); -} - sub get_rules { my (%options) = @_; From 5c153e854011c2748885a34dd816803b7492624c Mon Sep 17 00:00:00 2001 From: May <110405507+mushroomempires@users.noreply.github.com> Date: Fri, 9 Aug 2024 12:26:17 +0200 Subject: [PATCH 906/948] fix(ci): add gorgone to collect component list (#1612) --- .github/actions/release/action.yml | 2 +- .github/workflows/release-trigger-builds.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/release/action.yml b/.github/actions/release/action.yml index 205bc442042..2f952b78bab 100644 --- a/.github/actions/release/action.yml +++ b/.github/actions/release/action.yml @@ -33,7 +33,7 @@ runs: set -eux # Variables - COMPONENTS_COLLECT=("centreon-collect") + COMPONENTS_COLLECT=("centreon-collect" "centreon-gorgone") CURRENT_STABLE_BRANCH_MAJOR_VERSION="" declare -a TMP_STABLE_TAGS=() declare -a NEW_STABLE_TAGS=() diff --git a/.github/workflows/release-trigger-builds.yml b/.github/workflows/release-trigger-builds.yml index bb9145855d0..7c03d1b0c27 100644 --- a/.github/workflows/release-trigger-builds.yml +++ b/.github/workflows/release-trigger-builds.yml @@ -47,8 +47,8 @@ jobs: #COMPONENTS_OSS_FULL=("awie" "dsm" "gorgone" "ha" "open-tickets" "web") #COMPONENTS_MODULES=("anomaly-detection" "autodiscovery" "bam" "cloud-business-extensions" "cloud-extensions" "it-edition-extensions" "lm" "map" "mbi" "ppm") #COMPONENTS_MODULES_FULL=("anomaly-detection" "autodiscovery" "bam" "cloud-business-extensions" "cloud-extensions" "it-edition-extensions" "lm" "map" "mbi" "ppm" "php-pecl-gnupg" "sourceguardian-loader") - COMPONENTS_COLLECT=("Centreon collect") - COMPONENTS_COLLECT_FULL=("Centreon collect") + COMPONENTS_COLLECT=("Centreon collect" "centreon-gorgone") + COMPONENTS_COLLECT_FULL=("Centreon collect" "centreon-gorgone") RUNS_URL="" # Accept release prefixed or develop branches, nothing else From 718efa5fbcae59489f2dba57bf33c82690227e00 Mon Sep 17 00:00:00 2001 From: Evan-Adam <152897682+Evan-Adam@users.noreply.github.com> Date: Mon, 12 Aug 2024 17:01:33 +0200 Subject: [PATCH 907/948] fix(gorgone-autodisco): fix compilation error in discovery.pm file * fix(Gorgone): use object call to acces self Refs:MON-146724 * fix return value * fix(gorgone): get_log return http code 200 even if no log found to avoid error in web application REFS: MON-146724 --------- Co-authored-by: Sophie Depassio <sdepassio@centreon.com> --- .../modules/centreon/autodiscovery/services/discovery.pm | 5 +++-- gorgone/gorgone/standard/api.pm | 3 ++- gorgone/tests/robot/tests/core/httpserver.robot | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm index f302644ad6d..41d8efc231f 100644 --- a/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm +++ b/gorgone/gorgone/modules/centreon/autodiscovery/services/discovery.pm @@ -886,7 +886,7 @@ sub launchdiscovery { # get vault config ################## - ($status, $message, my $vault_count) = get_vault_count(); + ($status, $message, my $vault_count) = $self->get_vault_count(); if ($status < 0) { $self->send_log_msg_error(token => $options{token}, subname => 'servicediscovery', number => $self->{uuid}, message => $message); return -1; @@ -981,7 +981,7 @@ sub event { } sub get_vault_count() { - my (%options) = @_; + my ($self, %options) = @_; # Check if vault config file exists if (-e $self->{config}->{vault_file}) { @@ -1005,6 +1005,7 @@ sub get_vault_count() { } return (0, '', 1); } + return (0, '', 0); } 1; diff --git a/gorgone/gorgone/standard/api.pm b/gorgone/gorgone/standard/api.pm index 52f810a6476..89e7f3a5eb0 100644 --- a/gorgone/gorgone/standard/api.pm +++ b/gorgone/gorgone/standard/api.pm @@ -223,7 +223,8 @@ sub get_log { $options{module}->{break_token} = undef; - my $response = '{"error":"no_log","message":"No log found for token","data":[],"token":"' . $options{token} . '"}'; + # Return http code 200 even if no log found to avoid error in web application, an evol may be done to return 404 and process it in web application + my $response = '{"error":"no_log","message":"No log found for token","data":[],"token":"' . $options{token} . '","http_response_code":"200"}'; if (defined($options{module}->{tokens}->{$token_log}) && defined($options{module}->{tokens}->{ $token_log }->{data})) { my $content; eval { diff --git a/gorgone/tests/robot/tests/core/httpserver.robot b/gorgone/tests/robot/tests/core/httpserver.robot index b50d81caef0..882784de421 100644 --- a/gorgone/tests/robot/tests/core/httpserver.robot +++ b/gorgone/tests/robot/tests/core/httpserver.robot @@ -23,6 +23,7 @@ check http api get status code ${tc} ... forbidden 403 /bad/endpoint {"error":"http_error_403","message":"forbidden"} ... constatus Ok 200 /api/internal/constatus {"data":{},"action":"constatus","message":"ok"} ... method not found 404 /api/internal/wrongendpoint {"error":"method_unknown","message":"Method not implemented"} + ... get log 200 /api/nodes/1/log/wrongtoken {"error":"no_log","message":"No log found for token","data":[],"token":"wrongtoken"} check http api post api ${tc} ${expected_code}= Convert To Integer ${http_status_code} From c29db73049b7eb2fac08123ce1d3bb3835e65cd9 Mon Sep 17 00:00:00 2001 From: Evan-Adam <152897682+Evan-Adam@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:00:28 +0200 Subject: [PATCH 908/948] fix(Gorgone): fix proxy eating 100% cpu (#1605) * fix(Gorgone): Auto indent proxy/class.pm file * doc(Gorgone): fix proxy module documentation Refs:MON-130747 --- gorgone/docs/modules/core/proxy.md | 18 +- gorgone/gorgone/modules/core/proxy/class.pm | 255 +++++++++--------- .../tests/robot/resources/resources.resource | 6 +- gorgone/tests/robot/tests/core/push.robot | 49 ++++ 4 files changed, 191 insertions(+), 137 deletions(-) diff --git a/gorgone/docs/modules/core/proxy.md b/gorgone/docs/modules/core/proxy.md index 5c1a672ab00..69899491411 100644 --- a/gorgone/docs/modules/core/proxy.md +++ b/gorgone/docs/modules/core/proxy.md @@ -12,21 +12,17 @@ A SSH client library make routing to non-gorgoned nodes possible. ## Configuration -| Directive | Description | Default value | -|:----------|:----------------------------------------------------|:--------------| -| pool | Number of children to instantiate to process events | `5` | - -| synchistory_time | Time in seconds between two log synchronisations | `60` | - -| synchistory_timeout | Time in seconds before log synchronisation is considered timed out | `30` | - -| ping | Time in seconds between two node pings | `60` | -| pong_discard_timeout | Time in seconds before a node is considered dead | `300` | +| Directive | Description | Default value | +|:---------------------|:-------------------------------------------------------------------|:---------------| +| pool | Number of children to instantiate to process events | `5` | +| synchistory_time | Time in seconds between two log synchronisations | `60` | +| synchistory_timeout | Time in seconds before log synchronisation is considered timed out | `30` | +| ping | Time in seconds between two node pings | `60` | +| pong_discard_timeout | Time in seconds before a ping is considered lost | `300` | This part of the configuration is only used if some poller must connect with the pullwss module. - | Directive | Description | Default value | |:--------------|:-----------------------------------------------------------------------------------------------|:--------------| | httpserver | Array containing all the configuration below for a pullwss connection | no value. | diff --git a/gorgone/gorgone/modules/core/proxy/class.pm b/gorgone/gorgone/modules/core/proxy/class.pm index 3798142ec9a..7c5172b159b 100644 --- a/gorgone/gorgone/modules/core/proxy/class.pm +++ b/gorgone/gorgone/modules/core/proxy/class.pm @@ -39,11 +39,10 @@ sub new { $connector = $class->SUPER::new(%options); bless $connector, $class; - $connector->{pool_id} = $options{pool_id}; - $connector->{clients} = {}; + $connector->{pool_id} = $options{pool_id}; + $connector->{clients} = {}; $connector->{internal_channels} = {}; - $connector->{watchers} = {}; - + $connector->{watchers} = {}; $connector->set_signal_handlers(); return $connector; @@ -52,21 +51,21 @@ sub new { sub set_signal_handlers { my $self = shift; - $SIG{TERM} = \&class_handle_TERM; + $SIG{TERM} = \&class_handle_TERM; $handlers{TERM}->{$self} = sub { $self->handle_TERM() }; - $SIG{HUP} = \&class_handle_HUP; - $handlers{HUP}->{$self} = sub { $self->handle_HUP() }; + $SIG{HUP} = \&class_handle_HUP; + $handlers{HUP}->{$self} = sub { $self->handle_HUP() }; } sub handle_HUP { - my $self = shift; + my $self = shift; $self->{reload} = 0; } sub handle_TERM { my $self = shift; $self->{logger}->writeLogInfo("[proxy] $$ Receiving order to stop..."); - $self->{stop} = 1; + $self->{stop} = 1; $self->{stop_time} = time(); } @@ -116,22 +115,22 @@ sub read_message_client { $connector->{clients}->{ $client_identity }->{com_read_internal} = 1; $connector->send_internal_action({ action => 'PONG', - data => $data, - token => $token, + data => $data, + token => $token, target => '' }); } elsif ($options{data} =~ /^\[(?:REGISTERNODES|UNREGISTERNODES|SYNCLOGS)\]/) { if ($options{data} !~ /^\[(.+?)\]\s+\[(.*?)\]\s+\[.*?\]\s+(.*)/ms) { return undef; } - my ($action, $token, $data) = ($1, $2, $3); + my ($action, $token, $data) = ($1, $2, $3); $connector->send_internal_action({ - action => $action, - data => $data, + action => $action, + data => $data, data_noencode => 1, - token => $token, - target => '' + token => $token, + target => '' }); } elsif ($options{data} =~ /^\[ACK\]\s+\[(.*?)\]\s+(.*)/ms) { my ($code, $data) = $connector->json_decode(argument => $2); @@ -142,8 +141,8 @@ sub read_message_client { if (defined($data->{data}->{action}) && $data->{data}->{action} eq 'getlog') { $connector->send_internal_action({ action => 'SETLOGS', - data => $data, - token => $1, + data => $data, + token => $1, target => '' }); } @@ -155,39 +154,41 @@ sub connect { if ($self->{clients}->{$options{id}}->{type} eq 'push_zmq') { $self->{clients}->{$options{id}}->{class} = gorgone::class::clientzmq->new( - context => $self->{zmq_context}, - core_loop => $self->{loop}, - identity => 'gorgone-proxy-' . $self->{core_id} . '-' . $options{id}, - cipher => $self->{clients}->{ $options{id} }->{cipher}, - vector => $self->{clients}->{ $options{id} }->{vector}, - client_pubkey => - defined($self->{clients}->{ $options{id} }->{client_pubkey}) && $self->{clients}->{ $options{id} }->{client_pubkey} ne '' - ? $self->{clients}->{ $options{id} }->{client_pubkey} : $self->get_core_config(name => 'pubkey'), - client_privkey => - defined($self->{clients}->{ $options{id} }->{client_privkey}) && $self->{clients}->{ $options{id} }->{client_privkey} ne '' - ? $self->{clients}->{ $options{id} }->{client_privkey} : $self->get_core_config(name => 'privkey'), - target_type => defined($self->{clients}->{ $options{id} }->{target_type}) ? - $self->{clients}->{ $options{id} }->{target_type} : - 'tcp', - target_path => defined($self->{clients}->{ $options{id} }->{target_path}) ? - $self->{clients}->{ $options{id} }->{target_path} : - $self->{clients}->{ $options{id} }->{address} . ':' . $self->{clients}->{ $options{id} }->{port}, - config_core => $self->get_core_config(), - logger => $self->{logger} + context => $self->{zmq_context}, + core_loop => $self->{loop}, + identity => 'gorgone-proxy-' . $self->{core_id} . '-' . $options{id}, + cipher => $self->{clients}->{ $options{id} }->{cipher}, + vector => $self->{clients}->{ $options{id} }->{vector}, + client_pubkey => defined($self->{clients}->{ $options{id} }->{client_pubkey}) + && $self->{clients}->{ $options{id} }->{client_pubkey} ne '' + ? $self->{clients}->{ $options{id} }->{client_pubkey} + : $self->get_core_config(name => 'pubkey'), + client_privkey => defined($self->{clients}->{ $options{id} }->{client_privkey}) + && $self->{clients}->{ $options{id} }->{client_privkey} ne '' + ? $self->{clients}->{ $options{id} }->{client_privkey} + : $self->get_core_config(name => 'privkey'), + target_type => defined($self->{clients}->{ $options{id} }->{target_type}) ? + $self->{clients}->{ $options{id} }->{target_type} : + 'tcp', + target_path => defined($self->{clients}->{ $options{id} }->{target_path}) + ? $self->{clients}->{ $options{id} }->{target_path} + : $self->{clients}->{ $options{id} }->{address} . ':' . $self->{clients}->{ $options{id} }->{port}, + config_core => $self->get_core_config(), + logger => $self->{logger} ); $self->{clients}->{ $options{id} }->{class}->init(callback => \&read_message_client); } elsif ($self->{clients}->{ $options{id} }->{type} eq 'push_ssh') { - $self->{clients}->{$options{id}}->{class} = gorgone::modules::core::proxy::sshclient->new(logger => $self->{logger}); + $self->{clients}->{$options{id}}->{class} = gorgone::modules::core::proxy::sshclient->new(logger =>$self->{logger}); my $code = $self->{clients}->{$options{id}}->{class}->open_session( - ssh_host => $self->{clients}->{$options{id}}->{address}, - ssh_port => $self->{clients}->{$options{id}}->{ssh_port}, - ssh_username => $self->{clients}->{$options{id}}->{ssh_username}, - ssh_password => $self->{clients}->{$options{id}}->{ssh_password}, - ssh_directory => $self->{clients}->{$options{id}}->{ssh_directory}, - ssh_known_hosts => $self->{clients}->{$options{id}}->{ssh_known_hosts}, - ssh_identity => $self->{clients}->{$options{id}}->{ssh_identity}, + ssh_host => $self->{clients}->{$options{id}}->{address}, + ssh_port => $self->{clients}->{$options{id}}->{ssh_port}, + ssh_username => $self->{clients}->{$options{id}}->{ssh_username}, + ssh_password => $self->{clients}->{$options{id}}->{ssh_password}, + ssh_directory => $self->{clients}->{$options{id}}->{ssh_directory}, + ssh_known_hosts => $self->{clients}->{$options{id}}->{ssh_known_hosts}, + ssh_identity => $self->{clients}->{$options{id}}->{ssh_identity}, strict_serverkey_check => $self->{clients}->{$options{id}}->{strict_serverkey_check}, - ssh_connect_timeout => $self->{clients}->{$options{id}}->{ssh_connect_timeout} + ssh_connect_timeout => $self->{clients}->{$options{id}}->{ssh_connect_timeout} ); if ($code != 0) { $self->{clients}->{ $options{id} }->{delete} = 1; @@ -208,7 +209,9 @@ sub action_proxyaddnode { # test if a connection parameter changed my $changed = 0; foreach (keys %$data) { - if (ref($data->{$_}) eq '' && (!defined($self->{clients}->{ $data->{id} }->{$_}) || $data->{$_} ne $self->{clients}->{ $data->{id} }->{$_})) { + if (ref($data->{$_}) eq '' + && (!defined($self->{clients}->{ $data->{id} }->{$_}) + || $data->{$_} ne $self->{clients}->{ $data->{id} }->{$_})) { $changed = 1; last; } @@ -216,33 +219,33 @@ sub action_proxyaddnode { if ($changed == 0) { $self->{logger}->writeLogInfo("[proxy] Session not changed $data->{id}"); - return ; + return; } $self->{logger}->writeLogInfo("[proxy] Recreate session for $data->{id}"); # we send a pong reset. because the ping can be lost $self->send_internal_action({ - action => 'PONGRESET', - data => '{ "data": { "id": ' . $data->{id} . ' } }', + action => 'PONGRESET', + data => '{ "data": { "id": ' . $data->{id} . ' } }', data_noencode => 1, - token => $self->generate_token(), - target => '' + token => $self->generate_token(), + target => '' }); $self->{clients}->{ $data->{id} }->{class}->close(); $self->{clients}->{ $data->{id} }->{class}->cleanup(); } else { $self->{internal_channels}->{ $data->{id} } = gorgone::standard::library::connect_com( - context => $self->{zmq_context}, + context => $self->{zmq_context}, zmq_type => 'ZMQ_DEALER', - name => 'gorgone-proxy-channel-' . $data->{id}, - logger => $self->{logger}, - type => $self->get_core_config(name => 'internal_com_type'), - path => $self->get_core_config(name => 'internal_com_path') + name => 'gorgone-proxy-channel-' . $data->{id}, + logger => $self->{logger}, + type => $self->get_core_config(name => 'internal_com_type'), + path => $self->get_core_config(name => 'internal_com_path') ); $self->send_internal_action({ action => 'PROXYREADY', - data => { + data => { node_id => $data->{id} } }); @@ -255,9 +258,9 @@ sub action_proxyaddnode { ); } - $self->{clients}->{ $data->{id} } = $data; - $self->{clients}->{ $data->{id} }->{delete} = 0; - $self->{clients}->{ $data->{id} }->{class} = undef; + $self->{clients}->{ $data->{id} } = $data; + $self->{clients}->{ $data->{id} }->{delete} = 0; + $self->{clients}->{ $data->{id} }->{class} = undef; $self->{clients}->{ $data->{id} }->{com_read_internal} = 1; } @@ -285,7 +288,7 @@ sub action_proxycloseconnection { $self->{clients}->{ $data->{id} }->{class}->close(); $self->{clients}->{ $data->{id} }->{class}->cleanup(); $self->{clients}->{ $data->{id} }->{delete} = 0; - $self->{clients}->{ $data->{id} }->{class} = undef; + $self->{clients}->{ $data->{id} }->{class} = undef; } sub close_connections { @@ -313,32 +316,32 @@ sub proxy_ssh { $self->{clients}->{ $options{target_client} }->{com_read_internal} = 1; $self->send_internal_action({ action => 'PONG', - data => { data => { id => $options{target_client} } }, - token => $options{token}, + data => { data => { id => $options{target_client} } }, + token => $options{token}, target => '' }); } - return ; + return; } my $retry = 1; # manage server disconnected while ($retry >= 0) { my ($status, $data_ret) = $self->{clients}->{ $options{target_client} }->{class}->action( - action => $options{action}, - data => $decoded_data, + action => $options{action}, + data => $decoded_data, target_direct => $options{target_direct}, - target => $options{target}, - token => $options{token} + target => $options{target}, + token => $options{token} ); if (ref($data_ret) eq 'ARRAY') { foreach (@{$data_ret}) { $self->send_log( - code => $_->{code}, - token => $options{token}, + code => $_->{code}, + token => $options{token}, logging => $decoded_data->{logging}, instant => $decoded_data->{instant}, - data => $_->{data} + data => $_->{data} ); } last; @@ -347,19 +350,19 @@ sub proxy_ssh { $self->{logger}->writeLogDebug("[proxy] Sshclient return: [message = $data_ret->{message}]"); if ($status == 0) { $self->send_log( - code => GORGONE_ACTION_FINISH_OK, - token => $options{token}, + code => GORGONE_ACTION_FINISH_OK, + token => $options{token}, logging => $decoded_data->{logging}, - data => $data_ret + data => $data_ret ); last; } $self->send_log( - code => GORGONE_ACTION_FINISH_KO, - token => $options{token}, + code => GORGONE_ACTION_FINISH_KO, + token => $options{token}, logging => $decoded_data->{logging}, - data => $data_ret + data => $data_ret ); # quit because it's not a ssh connection issue @@ -370,7 +373,7 @@ sub proxy_ssh { sub proxy { my (%options) = @_; - + if ($options{message} !~ /^\[(.+?)\]\s+\[(.*?)\]\s+\[(.*?)\]\s+(.*)$/m) { return undef; } @@ -381,32 +384,32 @@ sub proxy { if ($action eq 'PROXYADDNODE') { $connector->action_proxyaddnode(data => $data); - return ; + return; } elsif ($action eq 'PROXYDELNODE') { $connector->action_proxydelnode(data => $data); - return ; + return; } elsif ($action eq 'BCASTLOGGER' && $target_complete eq '') { (undef, $data) = $connector->json_decode(argument => $data); $connector->action_bcastlogger(data => $data); - return ; + return; } elsif ($action eq 'BCASTCOREKEY' && $target_complete eq '') { (undef, $data) = $connector->json_decode(argument => $data); $connector->action_bcastcorekey(data => $data); - return ; + return; } elsif ($action eq 'PROXYCLOSECONNECTION') { $connector->action_proxycloseconnection(data => $data); - return ; + return; } if ($target_complete !~ /^(.+)~~(.+)$/) { $connector->send_log( - code => GORGONE_ACTION_FINISH_KO, + code => GORGONE_ACTION_FINISH_KO, token => $token, - data => { + data => { message => "unknown target format '$target_complete'" } ); - return ; + return; } my ($target_client, $target, $target_direct) = ($1, $2, 1); @@ -417,28 +420,28 @@ sub proxy { $connector->{logger}->writeLogInfo("[proxy] connect for $target_client"); if ($connector->connect(id => $target_client) != 0) { $connector->send_log( - code => GORGONE_ACTION_FINISH_KO, + code => GORGONE_ACTION_FINISH_KO, token => $token, - data => { + data => { message => "cannot connect on target node '$target_client'" } ); - return ; + return; } } if ($connector->{clients}->{$target_client}->{type} eq 'push_zmq') { my ($status, $msg) = $connector->{clients}->{$target_client}->{class}->send_message( action => $action, - token => $token, + token => $token, target => $target_direct == 0 ? $target : undef, - data => $data + data => $data ); if ($status != 0) { $connector->send_log( - code => GORGONE_ACTION_FINISH_KO, + code => GORGONE_ACTION_FINISH_KO, token => $token, - data => { + data => { message => "Send message problem for '$target': $msg" } ); @@ -447,12 +450,12 @@ sub proxy { } } elsif ($connector->{clients}->{$target_client}->{type} eq 'push_ssh') { $connector->proxy_ssh( - action => $action, - data => $data, + action => $action, + data => $data, target_client => $target_client, - target => $target, + target => $target, target_direct => $target_direct, - token => $token + token => $token ); } } @@ -462,16 +465,17 @@ sub event { my $socket; if (defined($options{channel})) { - #$self->{logger}->writeLogDebug("[proxy] event channel $options{channel} delete: $self->{clients}->{ $options{channel} }->{delete} com_read_internal: $self->{clients}->{ $options{channel} }->{com_read_internal}") - # if (defined($self->{clients}->{ $options{channel} })); - return if ( - defined($self->{clients}->{ $options{channel} }) && - ($self->{clients}->{ $options{channel} }->{com_read_internal} == 0 || $self->{clients}->{ $options{channel} }->{delete} == 1) + if (defined($self->{clients}->{ $options{channel} })) { + $self->{logger}->writeLogDebug("[proxy] event channel $options{channel} delete: $self->{clients}->{ $options{channel} }->{delete} com_read_internal: $self->{clients}->{ $options{channel} }->{com_read_internal}"); + } + return if (defined($self->{clients}->{ $options{channel} }) + && ( $self->{clients}->{ $options{channel} }->{com_read_internal} == 0 + || $self->{clients}->{ $options{channel} }->{delete} == 1) ); $socket = $options{channel} eq 'control' ? $self->{internal_socket} : $self->{internal_channels}->{ $options{channel} }; } else { - $socket = $options{socket}; + $socket = $options{socket}; $options{channel} = 'control'; } @@ -483,9 +487,9 @@ sub event { if ($self->{stop} == 1 && (time() - $self->{exit_timeout}) > $self->{stop_time}) { $self->exit_process(); } - return if ( - defined($self->{clients}->{ $options{channel} }) && - ($self->{clients}->{ $options{channel} }->{com_read_internal} == 0 || $self->{clients}->{ $options{channel} }->{delete} == 1) + return if (defined($self->{clients}->{ $options{channel} }) + &&($self->{clients}->{ $options{channel} }->{com_read_internal} == 0 + || $self->{clients}->{ $options{channel} }->{delete} == 1) ); } } @@ -494,18 +498,23 @@ sub periodic_exec { foreach (keys %{$connector->{clients}}) { if (defined($connector->{clients}->{$_}->{delete}) && $connector->{clients}->{$_}->{delete} == 1) { $connector->send_internal_action({ - action => 'PONGRESET', - data => '{ "data": { "id": ' . $_ . ' } }', + action => 'PONGRESET', + data => '{ "data": { "id": ' . $_ . ' } }', data_noencode => 1, - token => $connector->generate_token(), - target => '' + token => $connector->generate_token(), + target => '' }); if (defined($connector->{clients}->{$_}->{class})) { - $connector->{clients}->{$_}->{class}->close(); - $connector->{clients}->{$_}->{class}->cleanup(); - } - $connector->{clients}->{$_}->{class} = undef; - $connector->{clients}->{$_}->{delete} = 0; + $connector->{clients}->{$_}->{class}->close(); + $connector->{clients}->{$_}->{class}->cleanup(); + } + # if the connection to the node is not established, we stop listening for new event for this destination, + # so event will be stored in zmq buffer until we start processing them again (see proxy_addnode) + # zmq queues have a size limit (high water mark), so if the node never connects, we lose some messages, + # preventing us from having memory leaks or other inconvenient problems. + delete $connector->{watchers}->{$_}; + $connector->{clients}->{$_}->{class} = undef; + $connector->{clients}->{$_}->{delete} = 0; $connector->{clients}->{$_}->{com_read_internal} = 0; $connector->{logger}->writeLogInfo("[proxy] periodic close connection for $_"); next; @@ -525,22 +534,22 @@ sub run { my ($self, %options) = @_; $self->{internal_socket} = gorgone::standard::library::connect_com( - context => $self->{zmq_context}, + context => $self->{zmq_context}, zmq_type => 'ZMQ_DEALER', - name => 'gorgone-proxy-' . $self->{pool_id}, - logger => $self->{logger}, - type => $self->get_core_config(name => 'internal_com_type'), - path => $self->get_core_config(name => 'internal_com_path') + name => 'gorgone-proxy-' . $self->{pool_id}, + logger => $self->{logger}, + type => $self->get_core_config(name => 'internal_com_type'), + path => $self->get_core_config(name => 'internal_com_path') ); $self->send_internal_action({ action => 'PROXYREADY', - data => { + data => { pool_id => $self->{pool_id} } }); my $watcher_timer = $self->{loop}->timer(5, 5, \&periodic_exec); - my $watcher_io = $self->{loop}->io( + my $watcher_io = $self->{loop}->io( $self->{internal_socket}->get_fd(), EV::READ, sub { diff --git a/gorgone/tests/robot/resources/resources.resource b/gorgone/tests/robot/resources/resources.resource index 4e678f94f4b..23d01e07a9c 100644 --- a/gorgone/tests/robot/resources/resources.resource +++ b/gorgone/tests/robot/resources/resources.resource @@ -166,16 +166,16 @@ Setup Two Gorgone Instances Setup Gorgone Config ${central_pull_config} gorgone_name=${central_name} sql_file=${ROOT_CONFIG}db_add_1_poller.sql Setup Gorgone Config ${poller_pull_config} gorgone_name=${poller_name} + Start Gorgone debug ${poller_name} Start Gorgone debug ${central_name} Wait Until Port Is Bind 5556 - Start Gorgone debug ${poller_name} Check Poller Is Connected port=5556 expected_nb=2 Check Poller Communicate 2 END Wait Until Port Is Bind [Arguments] ${port} - FOR ${i} IN RANGE 10 + FOR ${i} IN RANGE 15 Sleep 0.5 ${nb_port_listening} Run ss -tlnp | grep ':${port}' | grep LIST | wc -l IF ${nb_port_listening} == 1 @@ -183,7 +183,7 @@ Wait Until Port Is Bind END END - Should Be True ${i} < 9 Gorgone did not listen on port ${port} on time. + Should Be True ${i} < 14 Gorgone did not listen on port ${port} on time. Log To Console tcp port ${port} bind after ${i} attempt (0.5 seconds each) Ctn Check No Error In Logs diff --git a/gorgone/tests/robot/tests/core/push.robot b/gorgone/tests/robot/tests/core/push.robot index 2b72e39970e..5dd5410628b 100644 --- a/gorgone/tests/robot/tests/core/push.robot +++ b/gorgone/tests/robot/tests/core/push.robot @@ -13,4 +13,53 @@ connect 1 poller to a central Log To Console \nStarting the gorgone setup Setup Two Gorgone Instances communication_mode=push_zmq central_name=push_zmq_gorgone_central poller_name=push_zmq_gorgone_poller_2 + # Test Log To Console End of tests. + +check central don't eat cpu when poller is not connected + [Tags] long_tests MON-130747 + ${central_name}= Set Variable push_zmq_gorgone_central + [Teardown] Stop Gorgone And Remove Gorgone Config push_zmq_gorgone_central sql_file=${ROOT_CONFIG}db_delete_poller.sql + + @{central_push_config}= Create List ${push_central_config} ${gorgone_core_config} + Setup Gorgone Config ${central_push_config} gorgone_name=${central_name} sql_file=${ROOT_CONFIG}db_add_1_poller.sql + Start Gorgone debug ${central_name} + Wait Until Port Is Bind 8085 + Ctn Wait Until Poller Fail To Connect 1 + Ctn Check Cpu Until Timeout + + +*** Keywords *** +Ctn Check Cpu Until Timeout + [Arguments] ${timeout}=60s ${process_whitelist}=gorgone-proxy ${max_cpu_usage}=40 + ${max_date} Get Current Date increment=${timeout} + ${current_date} Get Current Date + + WHILE '${max_date}' > '${current_date}' + ${cpu_conso} Run echo $(( $(ps -eo cp,args:100 | grep -v grep | grep -i ${process_whitelist} | awk '{print $1}' | paste -sd+) )) + Should Be True ${cpu_conso} < ${max_cpu_usage} Gorgone consume too much cpu : ${cpu_conso} + ${current_date} Get Current Date + Sleep 2 + END + + +Ctn Wait Until Poller Fail To Connect + [Arguments] ${nb_fail}=1 ${poller_id}=2 + + ${response} Set Variable ${EMPTY} + FOR ${i} IN RANGE 35 + Sleep 5 + ${response}= GET http://127.0.0.1:8085/api/internal/constatus + Log ${response.json()} + IF not ${response.json()}[data] + CONTINUE + END + IF ${response.json()}[data][${poller_id}][ping_failed] >= ${nb_fail} or ${response.json()}[data][${poller_id}][ping_ok] > 0 + BREAK + END + END + Log To Console json response : ${response.json()} + Should Be True ${i} < 34 timeout after ${i} time waiting for poller status in gorgone rest api (/api/internal/constatus) + Should Be True ${nb_fail} == ${response.json()}[data][${poller_id}][ping_failed] there was failed ping between the central and the poller ${poller_id} + Should Be True 0 == ${response.json()}[data][${poller_id}][ping_ok] there was successful ping between the central and the poller ${poller_id} + Log To Console ${nb_fail} failed ping between the central and the poller ${poller_id} From 9b9999c62f5b72a9933a4c9654c7e94c77ef2a4c Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Mon, 19 Aug 2024 14:28:00 +0200 Subject: [PATCH 909/948] chore(ci): enable shellcheck and integrate check-status workflow (#1579) --- .github/workflows/actionlint.yml | 39 ++++++---- .github/workflows/check-status.yml | 103 ++++++++++++++++++++++++++ .github/workflows/package-collect.yml | 2 +- 3 files changed, 127 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/check-status.yml diff --git a/.github/workflows/actionlint.yml b/.github/workflows/actionlint.yml index 7cdce10803b..1966692a01a 100644 --- a/.github/workflows/actionlint.yml +++ b/.github/workflows/actionlint.yml @@ -5,6 +5,7 @@ concurrency: cancel-in-progress: true on: + workflow_dispatch: pull_request: branches: - develop @@ -15,38 +16,45 @@ on: - release-* paths: - ".github/**" + - "**/packaging/*.ya?ml" jobs: - actionlint: - runs-on: ubuntu-22.04 + action-lint: + runs-on: ubuntu-24.04 steps: - name: Checkout sources - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Download actionlint id: get_actionlint - run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) + run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/v1.7.1/scripts/download-actionlint.bash) shell: bash - name: Check workflow files + env: + SHELLCHECK_OPTS: "--severity=error" run: | ${{ steps.get_actionlint.outputs.executable }} \ - -ignore 'label "(common|collect|collect-arm64)" is unknown' \ - -ignore 'label "veracode" is unknown' \ - -ignore '"github.head_ref" is potentially untrusted' \ - -shellcheck= \ - -pyflakes= \ - -color + -ignore 'label "ubuntu-24.04" is unknown' \ + -ignore 'label "(common|collect|collect-arm64)" is unknown' \ + -ignore 'label "veracode" is unknown' \ + -ignore '"github.head_ref" is potentially untrusted' \ + -pyflakes= \ + -color shell: bash + yaml-lint: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Checkout sources - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1 + with: + python-version: '3.12' - name: Install Yaml - run: | - pip install yamllint==1.32.0 + run: pip install yamllint==1.35.1 - name: Add Yaml Lint Rules run: | @@ -73,5 +81,4 @@ jobs: EOF - name: Lint YAML files - run: | - yamllint -c ./yamllint_rules.yml ./.github/actions/ ./.github/workflows/ + run: yamllint -c ./yamllint_rules.yml ./.github/actions/ ./.github/workflows/ ./**/packaging/ diff --git a/.github/workflows/check-status.yml b/.github/workflows/check-status.yml new file mode 100644 index 00000000000..36799865754 --- /dev/null +++ b/.github/workflows/check-status.yml @@ -0,0 +1,103 @@ +name: check-status + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +on: + pull_request: + branches: + - develop + - dev-[2-9][0-9].[0-9][0-9].x + - master + - "[2-9][0-9].[0-9][0-9].x" + - hotfix-* + - release-* + +jobs: + check-status: + runs-on: ubuntu-24.04 + steps: + - name: Check workflow statuses and display token usage + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + echo "current rest api rate usage:" + curl -s -H "Accept: application/vnd.github+json" -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/rate_limit | jq .rate + echo "" + echo "" + echo "current graphql rate usage:" + curl -s -H "Accept: application/vnd.github+json" -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/rate_limit | jq .resources.graphql + echo "" + echo "" + + - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.number }} + with: + script: | + await exec.exec("sleep 20s"); + + for (let i = 0; i < 60; i++) { + const failure = []; + const cancelled = []; + const pending = []; + + const result = await github.rest.checks.listSuitesForRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: "${{ github.event.pull_request.head.sha }}" + }); + result.data.check_suites.forEach(({ app: { slug }, conclusion, id}) => { + if (slug === 'github-actions') { + if (conclusion === 'failure' || conclusion === 'cancelled') { + failure.push(id); + } else if (conclusion === null) { + pending.push(id); + } + console.log(`check suite ${id} => ${conclusion === null ? 'pending' : conclusion}`); + } + }); + + if (pending.length === 0) { + core.setFailed("Cannot get pull request check status"); + return; + } + + if (failure.length > 0) { + let failureMessage = ''; + const failedCheckRuns = []; + for await (const suite_id of failure) { + const resultCheckRuns = await github.rest.checks.listForSuite({ + owner: context.repo.owner, + repo: context.repo.repo, + check_suite_id: suite_id + }); + + resultCheckRuns.data.check_runs.forEach(({ conclusion, name, html_url }) => { + if (conclusion === 'failure' || conclusion === 'cancelled') { + failedCheckRuns.push(`<a href="${html_url}">${name} (${conclusion})</a>`); + } + }); + } + + core.summary.addRaw(`${failedCheckRuns.length} job(s) failed:`, true) + core.summary.addList(failedCheckRuns); + core.summary.write() + + core.setFailed(`${failure.length} workflow(s) failed`); + return; + } + + if (pending.length === 1) { + core.info("All workflows are ok"); + return; + } + + core.info(`${pending.length} workflows in progress`); + + await exec.exec("sleep 30s"); + } + + core.setFailed("Timeout: some jobs are still in progress"); diff --git a/.github/workflows/package-collect.yml b/.github/workflows/package-collect.yml index 7b23883c389..bb41d9d71fc 100644 --- a/.github/workflows/package-collect.yml +++ b/.github/workflows/package-collect.yml @@ -192,7 +192,7 @@ jobs: "build/connectors/ssh/centreon_connector_ssh" "build/ccc/ccc" "build/agent/centagent") - for file in ${exe[@]}; do + for file in "${exe[@]}"; do echo "Making a debug file of $file" objcopy --only-keep-debug $file $file.debug objcopy --strip-debug $file From 198ed278e0a18c2ecbfe3377882f75c25ec09763 Mon Sep 17 00:00:00 2001 From: May <110405507+mushroomempires@users.noreply.github.com> Date: Mon, 19 Aug 2024 14:52:31 +0200 Subject: [PATCH 910/948] chore(deps): absorb 24.08 dependabot GitHub Actions updates (#1620) * chore(deps): bump actions/setup-python from 5.1.0 to 5.1.1 (#1597) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5.1.0 to 5.1.1. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/82c7e631bb3cdc910f68e0081d67478d79c6982d...39cd14951b08e74b54015e9e001cdefcf80e669f) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump docker/build-push-action from 6.2.0 to 6.5.0 (#1596) Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.2.0 to 6.5.0. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v6.2.0...5176d81f87c23d6fc96624dfdbcd9f3830bbe445) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump docker/login-action from 3.2.0 to 3.3.0 (#1595) Bumps [docker/login-action](https://github.com/docker/login-action) from 3.2.0 to 3.3.0. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/0d4c9c5ea7693da7b068278f7b52bda2a190a446...9780b0c442fbb1117ed29e0efdff1e18412f7567) --- updated-dependencies: - dependency-name: docker/login-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump joonvena/robotframework-reporter-action (#1594) Bumps [joonvena/robotframework-reporter-action](https://github.com/joonvena/robotframework-reporter-action) from 2.4 to 2.5. - [Release notes](https://github.com/joonvena/robotframework-reporter-action/releases) - [Commits](https://github.com/joonvena/robotframework-reporter-action/compare/f99583edc5902bd73a61df5c37d1321bc38890ca...229b6d4248b20be6e54f4fc32c7414130d1bf200) --- updated-dependencies: - dependency-name: joonvena/robotframework-reporter-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump docker/setup-buildx-action from 3.3.0 to 3.6.1 (#1593) Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3.3.0 to 3.6.1. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/d70bba72b1f3fd22344832f00baa16ece964efeb...988b5a0280414f521da01fcc63a27aeeb4b104db) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump actions/cache from 4.0.0 to 4.0.2 (#1592) Bumps [actions/cache](https://github.com/actions/cache) from 4.0.0 to 4.0.2. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v4...0c45773b623bea8c8e75f6c82b208c3cf94ea4f9) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump actions/download-artifact from 4.1.7 to 4.1.8 (#1591) Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4.1.7 to 4.1.8. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/65a9edc5881444af0b9093a5e628f2fe47ea3b2e...fa0a91b85d4f404e444e00e005971372dc801d16) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump actions/checkout from 4.1.1 to 4.1.7 (#1590) Bumps [actions/checkout](https://github.com/actions/checkout) from 4.1.1 to 4.1.7. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4.1.1...692973e3d937129bcbf40652eb9f2f61becf3332) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump actions/upload-artifact from 4.3.1 to 4.3.4 (#1589) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.3.1 to 4.3.4. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4.3.1...0b2256b8c012f0828dc542b3febcab082c67f72b) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/centreon-collect.yml | 2 +- .github/workflows/docker-builder.yml | 8 ++++---- .github/workflows/docker-gorgone-testing.yml | 8 ++++---- .github/workflows/gorgone.yml | 12 ++++++------ .github/workflows/lua-curl.yml | 2 +- .github/workflows/package-collect.yml | 2 +- .github/workflows/robot-test.yml | 16 ++++++++-------- .github/workflows/windows-agent.yml | 4 ++-- 8 files changed, 27 insertions(+), 27 deletions(-) diff --git a/.github/workflows/centreon-collect.yml b/.github/workflows/centreon-collect.yml index d12a78d31c7..43bd4f93ca5 100644 --- a/.github/workflows/centreon-collect.yml +++ b/.github/workflows/centreon-collect.yml @@ -78,7 +78,7 @@ jobs: uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Login to Registry - uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: registry: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }} username: ${{ secrets.DOCKER_REGISTRY_ID }} diff --git a/.github/workflows/docker-builder.yml b/.github/workflows/docker-builder.yml index 0e2e4189fb0..2db215693b4 100644 --- a/.github/workflows/docker-builder.yml +++ b/.github/workflows/docker-builder.yml @@ -95,23 +95,23 @@ jobs: uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Login to Registry - uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: registry: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }} username: ${{ secrets.DOCKER_REGISTRY_ID }} password: ${{ secrets.DOCKER_REGISTRY_PASSWD }} - name: Login to Proxy Registry - uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: registry: ${{ vars.DOCKER_PROXY_REGISTRY_URL }} username: ${{ secrets.DOCKER_REGISTRY_ID }} password: ${{ secrets.DOCKER_REGISTRY_PASSWD }} - - uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 + - uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1 - name: Build image ${{ matrix.image }}:${{ matrix.tag }} - uses: docker/build-push-action@15560696de535e4014efeff63c48f16952e52dd1 # v6.2.0 + uses: docker/build-push-action@5176d81f87c23d6fc96624dfdbcd9f3830bbe445 # v6.5.0 with: file: .github/docker/Dockerfile.${{ matrix.dockerfile }} context: . diff --git a/.github/workflows/docker-gorgone-testing.yml b/.github/workflows/docker-gorgone-testing.yml index bd25dfb534f..6b8aa098d81 100644 --- a/.github/workflows/docker-gorgone-testing.yml +++ b/.github/workflows/docker-gorgone-testing.yml @@ -31,18 +31,18 @@ jobs: distrib: [alma8, alma9, bullseye, bookworm, jammy] steps: - name: Checkout sources - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Login to registry - uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: registry: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }} username: ${{ secrets.DOCKER_REGISTRY_ID }} password: ${{ secrets.DOCKER_REGISTRY_PASSWD }} - - uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 + - uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1 - - uses: docker/build-push-action@1a162644f9a7e87d8f4b053101d1d9a712edc18c # v6.3.0 + - uses: docker/build-push-action@5176d81f87c23d6fc96624dfdbcd9f3830bbe445 # v6.5.0 with: file: .github/docker/Dockerfile.gorgone-testing-${{ matrix.distrib }} context: . diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index a60905c0ee8..8875ebfa779 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -91,7 +91,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set package version and paths according to distrib run: | @@ -234,7 +234,7 @@ jobs: - name: Upload gorgone and robot debug artifacts if: failure() - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: gorgone-debug-${{ matrix.distrib }} path: | @@ -250,7 +250,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Deliver sources uses: ./.github/actions/release-sources @@ -273,7 +273,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Delivery uses: ./.github/actions/rpm-delivery @@ -298,7 +298,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Delivery uses: ./.github/actions/deb-delivery @@ -322,7 +322,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Promote ${{ matrix.distrib }} to stable uses: ./.github/actions/promote-to-stable diff --git a/.github/workflows/lua-curl.yml b/.github/workflows/lua-curl.yml index d65640af998..402cc3bf640 100644 --- a/.github/workflows/lua-curl.yml +++ b/.github/workflows/lua-curl.yml @@ -212,7 +212,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Promote ${{ matrix.distrib }} to stable uses: ./.github/actions/promote-to-stable diff --git a/.github/workflows/package-collect.yml b/.github/workflows/package-collect.yml index bb41d9d71fc..25219b29e1e 100644 --- a/.github/workflows/package-collect.yml +++ b/.github/workflows/package-collect.yml @@ -223,7 +223,7 @@ jobs: # set condition to true if artifacts are needed - if: ${{ false }} name: Upload package artifacts - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: packages-${{ matrix.distrib }}-${{ matrix.arch }} path: ./*.${{ matrix.package_extension}} diff --git a/.github/workflows/robot-test.yml b/.github/workflows/robot-test.yml index 98c79d3ab84..2c77a97402b 100644 --- a/.github/workflows/robot-test.yml +++ b/.github/workflows/robot-test.yml @@ -53,14 +53,14 @@ jobs: uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Login to Registry - uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: registry: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }} username: ${{ secrets.registry_username }} password: ${{ secrets.registry_password }} - name: Login to Proxy Registry - uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: registry: ${{ vars.DOCKER_PROXY_REGISTRY_URL }} username: ${{ secrets.registry_username }} @@ -183,7 +183,7 @@ jobs: - name: Upload Test Results if: ${{ failure() }} - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: reports-${{inputs.test_group_name}}-${{ steps.feature-path.outputs.feature_name_with_dash }} path: reports @@ -198,14 +198,14 @@ jobs: - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Download Artifacts - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: pattern: reports-${{inputs.test_group_name}}-* path: reports merge-multiple: true - name: Upload the regrouped artifact - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: reports-${{inputs.test_group_name}} path: reports/ @@ -244,12 +244,12 @@ jobs: shell: bash # setup-python v5.0.0 relies on node20 which is not supported by el7 distributions - - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1 if: ${{ inputs.distrib == 'el7'}} with: python-version: "3.10" - - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 + - uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1 if: ${{ inputs.distrib != 'el7' }} with: python-version: "3.10" @@ -261,7 +261,7 @@ jobs: shell: bash - name: Send report to commit - uses: joonvena/robotframework-reporter-action@f99583edc5902bd73a61df5c37d1321bc38890ca # v2.4 + uses: joonvena/robotframework-reporter-action@229b6d4248b20be6e54f4fc32c7414130d1bf200 # v2.5 with: gh_access_token: ${{ secrets.GITHUB_TOKEN }} report_path: reports diff --git a/.github/workflows/windows-agent.yml b/.github/workflows/windows-agent.yml index 20bec7eb300..eddbcd3c39c 100644 --- a/.github/workflows/windows-agent.yml +++ b/.github/workflows/windows-agent.yml @@ -57,14 +57,14 @@ jobs: Compress-Archive -Path $files_to_compress -DestinationPath centreon-monitoring-agent.zip - name: Save agent package in cache - uses: actions/cache/save@13aacd865c20de90d75de3b17ebe84f7a17d57d2 # v4.0.0 + uses: actions/cache/save@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 with: path: centreon-monitoring-agent.zip key: ${{ github.run_id }}-${{ github.sha }}-CMA-${{ github.head_ref || github.ref_name }} - name: Upload package artifacts if: ${{ false }} - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: packages-centreon-monitoring-agent-windows path: centreon-monitoring-agent.zip From 1e6e0efc42948ab0b906ff2f24755cf34fd7ee2f Mon Sep 17 00:00:00 2001 From: May <110405507+mushroomempires@users.noreply.github.com> Date: Tue, 20 Aug 2024 14:53:07 +0200 Subject: [PATCH 911/948] fix(ci): condition debian delivery and promote to not being in a cloud context (#1611) --- .github/actions/promote-to-stable/action.yml | 2 +- .github/workflows/centreon-collect.yml | 2 +- .github/workflows/gorgone.yml | 2 +- .github/workflows/libzmq.yml | 2 +- .github/workflows/lua-curl.yml | 2 +- .github/workflows/robot-nightly.yml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/actions/promote-to-stable/action.yml b/.github/actions/promote-to-stable/action.yml index 4432aee5663..a031cc74d82 100644 --- a/.github/actions/promote-to-stable/action.yml +++ b/.github/actions/promote-to-stable/action.yml @@ -104,7 +104,7 @@ runs: shell: bash - name: Promote DEB packages to stable - if: ${{ contains(fromJSON('["bullseye", "bookworm"]'), inputs.distrib) }} + if: ${{ !inputs.release_cloud && contains(fromJSON('["bullseye", "bookworm"]'), inputs.distrib) }} run: | set -eux diff --git a/.github/workflows/centreon-collect.yml b/.github/workflows/centreon-collect.yml index 43bd4f93ca5..e984cdc4540 100644 --- a/.github/workflows/centreon-collect.yml +++ b/.github/workflows/centreon-collect.yml @@ -158,7 +158,7 @@ jobs: release_cloud: ${{ needs.get-version.outputs.release_cloud }} deliver-deb: - if: ${{ contains(fromJson('["testing", "stable"]'), needs.get-version.outputs.stability) }} + if: ${{ !needs.get-version.outputs.release_cloud && contains(fromJson('["testing", "stable"]'), needs.get-version.outputs.stability) }} needs: [get-version, package] environment: ${{ needs.get-version.outputs.environment }} runs-on: [self-hosted, common] diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 8875ebfa779..6514c0a6f0d 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -290,7 +290,7 @@ jobs: deliver-deb: runs-on: [self-hosted, common] needs: [get-version, package] - if: ${{ contains(fromJson('["testing", "unstable"]'), needs.get-version.outputs.stability) }} + if: ${{ !needs.get-version.outputs.release_cloud && contains(fromJson('["testing", "unstable"]'), needs.get-version.outputs.stability) }} strategy: matrix: diff --git a/.github/workflows/libzmq.yml b/.github/workflows/libzmq.yml index ad0adeb625a..596d3085cad 100644 --- a/.github/workflows/libzmq.yml +++ b/.github/workflows/libzmq.yml @@ -168,7 +168,7 @@ jobs: release_cloud: ${{ needs.get-version.outputs.release_cloud }} deliver-deb: - if: ${{ contains(fromJson('["testing", "unstable"]'), needs.get-version.outputs.stability) }} + if: ${{ !needs.get-version.outputs.release_cloud && contains(fromJson('["testing", "unstable"]'), needs.get-version.outputs.stability) }} needs: [get-version, package-deb] environment: ${{ needs.get-version.outputs.environment }} runs-on: [self-hosted, common] diff --git a/.github/workflows/lua-curl.yml b/.github/workflows/lua-curl.yml index 402cc3bf640..4786e9c9bda 100644 --- a/.github/workflows/lua-curl.yml +++ b/.github/workflows/lua-curl.yml @@ -170,7 +170,7 @@ jobs: release_cloud: ${{ needs.get-version.outputs.release_cloud }} deliver-deb: - if: ${{ contains(fromJson('["testing", "unstable"]'), needs.get-version.outputs.stability) }} + if: ${{ !needs.get-version.outputs.release_cloud && contains(fromJson('["testing", "unstable"]'), needs.get-version.outputs.stability) }} needs: [get-version, package] runs-on: ubuntu-22.04 strategy: diff --git a/.github/workflows/robot-nightly.yml b/.github/workflows/robot-nightly.yml index 5283eb6e028..682ba8db11f 100644 --- a/.github/workflows/robot-nightly.yml +++ b/.github/workflows/robot-nightly.yml @@ -157,7 +157,7 @@ jobs: release_cloud: ${{ needs.get-version.outputs.release_cloud }} deliver-deb: - if: ${{ contains(fromJson('["unstable"]'), needs.get-version.outputs.stability) }} + if: ${{ !needs.get-version.outputs.release_cloud && contains(fromJson('["unstable"]'), needs.get-version.outputs.stability) }} needs: [robot-test, get-version] runs-on: [self-hosted, common] strategy: From 82607211b511a28969333901cef6da078da3fc58 Mon Sep 17 00:00:00 2001 From: Evan-Adam <152897682+Evan-Adam@users.noreply.github.com> Date: Thu, 22 Aug 2024 09:28:06 +0200 Subject: [PATCH 912/948] tests(Gorgone): test action module to retrieve result of command (#1619) * tests(Gorgone): test action module to retrieve result of command * doc(Gorgone): Fix documentation of action module * ci(Gorgone): remove os specific db name as database are not shared across jobs * test(Gorgone): fix pull test to start poller first, so there is no failed ping from the host (hopefully) Co-authored-by: cg-tw <83637804+cg-tw@users.noreply.github.com> Refs:MON-139728 --- .github/workflows/gorgone.yml | 14 +- gorgone/docs/api.md | 64 ++++----- gorgone/docs/modules/core/action.md | 18 ++- gorgone/tests/robot/resources/LogResearch.py | 60 ++++++++- .../tests/robot/resources/resources.resource | 1 + gorgone/tests/robot/tests/core/action.robot | 121 ++++++++++++++++++ 6 files changed, 233 insertions(+), 45 deletions(-) create mode 100644 gorgone/tests/robot/tests/core/action.robot diff --git a/.github/workflows/gorgone.yml b/.github/workflows/gorgone.yml index 6514c0a6f0d..ca5d0e6d289 100644 --- a/.github/workflows/gorgone.yml +++ b/.github/workflows/gorgone.yml @@ -222,15 +222,15 @@ jobs: - name: Create databases run: | - mysql -h mariadb -u root -ppassword -e "CREATE DATABASE \`centreon${{ matrix.distrib }}\`" - mysql -h mariadb -u root -ppassword -e "CREATE DATABASE \`centreon-storage${{ matrix.distrib }}\`" - mysql -h mariadb -u root -ppassword -e "GRANT ALL PRIVILEGES ON centreon${{ matrix.distrib }}.* TO 'centreon'@'%'" - mysql -h mariadb -u root -ppassword -e "GRANT ALL PRIVILEGES ON \`centreon-storage${{ matrix.distrib }}\`.* TO 'centreon'@'%'" - mysql -h mariadb -u root -ppassword 'centreon${{ matrix.distrib }}' < centreon/centreon/www/install/createTables.sql - mysql -h mariadb -u root -ppassword 'centreon-storage${{ matrix.distrib }}' < centreon/centreon/www/install/createTablesCentstorage.sql + mysql -h mariadb -u root -ppassword -e "CREATE DATABASE \`centreon\`" + mysql -h mariadb -u root -ppassword -e "CREATE DATABASE \`centreon-storage\`" + mysql -h mariadb -u root -ppassword -e "GRANT ALL PRIVILEGES ON centreon.* TO 'centreon'@'%'" + mysql -h mariadb -u root -ppassword -e "GRANT ALL PRIVILEGES ON \`centreon-storage\`.* TO 'centreon'@'%'" + mysql -h mariadb -u root -ppassword 'centreon' < centreon/centreon/www/install/createTables.sql + mysql -h mariadb -u root -ppassword 'centreon-storage' < centreon/centreon/www/install/createTablesCentstorage.sql - name: Run tests - run: robot -v 'DBHOST:mariadb' -v 'DBNAME:centreon${{ matrix.distrib }}' -v 'DBNAME_STORAGE:centreon-storage${{ matrix.distrib }}' -v 'DBUSER:centreon' gorgone/tests + run: robot -v 'DBHOST:mariadb' -v 'DBNAME:centreon' -v 'DBNAME_STORAGE:centreon-storage' -v 'DBUSER:centreon' gorgone/tests - name: Upload gorgone and robot debug artifacts if: failure() diff --git a/gorgone/docs/api.md b/gorgone/docs/api.md index 4ee643f273d..d98a8ea86a8 100644 --- a/gorgone/docs/api.md +++ b/gorgone/docs/api.md @@ -6,14 +6,14 @@ Centreon Gorgone provides a RestAPI through its HTTP server module. ### Get Nodes Connection Status -| Endpoint | Method | -| :- | :- | -| /internal/constatus | `GET` | +| Endpoint | Method | +|:--------------------|:-------| +| /internal/constatus | `GET` | #### Headers -| Header | Value | -| :- | :- | +| Header | Value | +|:-------|:-----------------| | Accept | application/json | #### Example @@ -42,14 +42,14 @@ curl --request GET "https://hostname:8443/api/internal/constatus" \ ### Get Public Key Thumbprint -| Endpoint | Method | -| :- | :- | -| /internal/thumbprint | `GET` | +| Endpoint | Method | +|:---------------------|:-------| +| /internal/thumbprint | `GET` | #### Headers -| Header | Value | -| :- | :- | +| Header | Value | +|:-------|:-----------------| | Accept | application/json | #### Example @@ -73,14 +73,14 @@ curl --request GET "https://hostname:8443/api/internal/thumbprint" \ ### Get Runtime Informations And Statistics -| Endpoint | Method | -| :- | :- | -| /internal/information | `GET` | +| Endpoint | Method | +|:----------------------|:-------| +| /internal/information | `GET` | #### Headers -| Header | Value | -| :- | :- | +| Header | Value | +|:-------|:-----------------| | Accept | application/json | #### Example @@ -158,6 +158,7 @@ The available endpoints depend on which modules are loaded. Endpoints are basically built from: * API root, +* optional target node, local if not present ( `/nodes/:nodeid/` ) * Module's namespace, * Module's name, * Action @@ -175,7 +176,7 @@ curl --request POST "https://hostname:8443/api/core/action/command" \ ]" ``` -Find more informations directly from modules documentations [here](../docs/modules.md). +Find more informations directly from modules documentations [here](./modules.md). As Centreon Gorgone is asynchronous, those endpoints will return a token corresponding to the action. @@ -187,7 +188,7 @@ As Centreon Gorgone is asynchronous, those endpoints will return a token corresp } ``` -That being said, its possible to make Gorgone work synchronously by providing two parameters. +That being said, it is possible to make Gorgone work synchronously by providing two parameters. First one is `log_wait` with a numeric value in microseconds: this value defines the amount of time the API will wait before trying to retrieve log results. @@ -202,7 +203,7 @@ Note: the `sync_wait` parameter is induced if you ask for a log directly specify Using the `/core/action/command` endpoint with `log_wait` parameter set to 100000: ```bash -curl --request POST "https://hostname:8443/api/core/action/command&log_wait=100000" \ +curl --request POST "https://hostname:8443/api/core/action/command?log_wait=100000" \ --header "Accept: application/json" \ --header "Content-Type: application/json" \ --data "[ @@ -219,12 +220,12 @@ This call will ask for the API to execute an action and will give a result after Note: there is no need for logs synchronisation when dealing with local actions. -##### Launch a command remotly and wait for the result +##### Launch a command remotely and wait for the result Using the `/nodes/:id/core/action/command` endpoint with `log_wait` parameter set to 100000: ```bash -curl --request POST "https://hostname:8443/api/nodes/2/core/action/command&log_wait=100000&sync_wait=200000" \ +curl --request POST "https://hostname:8443/api/nodes/2/core/action/command?log_wait=100000&sync_wait=200000" \ --header "Accept: application/json" \ --header "Content-Type: application/json" \ --data "[ @@ -241,23 +242,24 @@ This call will ask for the API to execute an action on the node with ID 2, will ## Log endpoint -To retrieve the logs, a specific endpoint can be called as follow. +To retrieve the logs, a specific endpoint can be called as follows. -| Endpoint | Method | -| :- | :- | -| /log/:token | `GET` | +| Endpoint | Method | +|:------------------------------|:-------| +| /api/nodes/:nodeid/log/:token | `GET` | #### Headers -| Header | Value | -| :- | :- | -| Accept | application/json | +| Header | Value | +|:-------|:------------------| +| Accept | application/json | #### Path variables -| Variable | Description | -| :- | :- | -| token | Token of the action | +| Variable | Description | +|:---------|:---------------------------| +| token | Token of the action | +| nodeid | node id to search log into | #### Examples @@ -271,7 +273,7 @@ curl --request GET "https://hostname:8443/api/nodes/2/log/3f25bc3a797fe989d1fb05 --header "Accept: application/json" ``` -This second example will force logs synchonisation before looking for results to retrieve. Default temporisation is 10ms and can be changed by providing `sync_wait` parameter. +This second example will force logs synchronisation before looking for results to retrieve. Default wait time is 10ms and can be changed by providing `sync_wait` parameter. #### Response example diff --git a/gorgone/docs/modules/core/action.md b/gorgone/docs/modules/core/action.md index c885dcd0cac..db21bbe971c 100644 --- a/gorgone/docs/modules/core/action.md +++ b/gorgone/docs/modules/core/action.md @@ -7,7 +7,7 @@ This module aims to execute actions on the server running the Gorgone daemon or ## Configuration | Directive | Description | Default value | -| :--------------- | :------------------------------------------------------------- | :------------ | +|:-----------------|:---------------------------------------------------------------|:--------------| | command_timeout | Time in seconds before a command is considered timed out | `30` | | whitelist_cmds | Boolean to enable commands whitelist | `false` | | allowed_cmds | Regexp list of allowed commands | | @@ -38,7 +38,7 @@ allowed_cmds: ## Events | Event | Description | -| :---------- | :-------------------------------------------------------------------------------------- | +|:------------|:----------------------------------------------------------------------------------------| | ACTIONREADY | Internal event to notify the core | | PROCESSCOPY | Process file or archive received from another daemon | | COMMAND | Execute a shell command on the server running the daemon or on another server using SSH | @@ -48,20 +48,20 @@ allowed_cmds: ### Execute a command line | Endpoint | Method | -| :------------------- | :----- | +|:---------------------|:-------| | /core/action/command | `POST` | #### Headers | Header | Value | -| :----------- | :--------------- | +|:-------------|:-----------------| | Accept | application/json | | Content-Type | application/json | #### Body | Key | Value | -| :---------------- | :------------------------------------------------------- | +|:------------------|:---------------------------------------------------------| | command | Command to execute | | timeout | Time in seconds before a command is considered timed out | | continue_on_error | Behaviour in case of execution issue | @@ -76,8 +76,11 @@ allowed_cmds: ] ``` + #### Example +See a complete exemple of this endpoint in the [api documentation](../../api.md) + ```bash curl --request POST "https://hostname:8443/api/core/action/command" \ --header "Accept: application/json" \ @@ -88,3 +91,8 @@ curl --request POST "https://hostname:8443/api/core/action/command" \ } ]" ``` +Output : +```bash +{"token":"b3f825f87d64764316d872c59e4bae69299b0003f6e5d27bbc7de4e27c50eb65fc17440baf218578343eff7f4d67f7e98ab6da40b050a2635bb735c7cec276bd"} +``` + diff --git a/gorgone/tests/robot/resources/LogResearch.py b/gorgone/tests/robot/resources/LogResearch.py index e6e6ccd54d0..0fe4afbd9d4 100644 --- a/gorgone/tests/robot/resources/LogResearch.py +++ b/gorgone/tests/robot/resources/LogResearch.py @@ -1,13 +1,69 @@ +# +# Copyright 2024 Centreon +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# For more information : contact@centreon.com +# + from robot.api import logger import re import time from dateutil import parser from datetime import datetime, timedelta -from robot.libraries.BuiltIn import BuiltIn +import requests + + +def ctn_get_api_log_with_timeout(token: str, node_path='', host='http://127.0.0.1:8085', timeout=15): + """! Query gorgone log API until the response contains a log with code 2 (success) or 1 (failure) + @param token: token to search in the API + @param node_path: part of the API URL defining if we use the local gorgone or another one, ex node/2/ + @param timeout: timeout in seconds + @param host: gorgone API URL with the port + @return True(when output of the command is found)/False(on failure or timeout), + and a json object containing the incriminated log for failure or success. + """ + limit_date = time.time() + timeout + api_json = [] + while time.time() < limit_date: + time.sleep(1) + uri = host + "/api/" + node_path + "log/" + token + response = requests.get(uri) + (status, output) = parse_json_response(response) + if status == '': + continue + return status, output + + return False, api_json["data"] + + +def parse_json_response(response): + api_json = response.json() + # http code should either be 200 for success or 404 for no log found if we are too early. + # as the time of writing, status code is always 200 because webapp autodiscovery module always expect a 200. + if response.status_code != 200 and response.status_code != 404: + return False, api_json -TIMEOUT = 30 + if 'error' in api_json and api_json['error'] == "no_log": + return '', '' + for log_detail in api_json["data"]: + if log_detail["code"] == 2: + return False, log_detail + if log_detail["code"] == 100: + return True, log_detail +# these function search log in the gorgone log file def ctn_find_in_log_with_timeout(log: str, content, timeout=20, date=-1, regex=False): """! search a pattern in log from date param @param log: path of the log file diff --git a/gorgone/tests/robot/resources/resources.resource b/gorgone/tests/robot/resources/resources.resource index 23d01e07a9c..89d58688292 100644 --- a/gorgone/tests/robot/resources/resources.resource +++ b/gorgone/tests/robot/resources/resources.resource @@ -169,6 +169,7 @@ Setup Two Gorgone Instances Start Gorgone debug ${poller_name} Start Gorgone debug ${central_name} Wait Until Port Is Bind 5556 + Check Poller Is Connected port=5556 expected_nb=2 Check Poller Communicate 2 END diff --git a/gorgone/tests/robot/tests/core/action.robot b/gorgone/tests/robot/tests/core/action.robot new file mode 100644 index 00000000000..49a155420b1 --- /dev/null +++ b/gorgone/tests/robot/tests/core/action.robot @@ -0,0 +1,121 @@ +# +# Copyright 2024 Centreon +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# For more information : contact@centreon.com +# + +*** Settings *** +Documentation test gorgone action module on local and distant target +Resource ${CURDIR}${/}..${/}..${/}resources${/}import.resource +Test Timeout 220s + +# @TODO : I know it's possible to have a remote server managing some poller. For now we don't test this case, but it should be tested and documented. +*** Test Cases *** +action module with ${communication_mode} communcation mode + [Documentation] test action on distant node, no whitelist configured + @{process_list} Create List ${communication_mode}_gorgone_central ${communication_mode}_gorgone_poller_2 + [Teardown] Stop Gorgone And Remove Gorgone Config @{process_list} sql_file=${ROOT_CONFIG}db_delete_poller.sql + + @{central_config} Create List ${ROOT_CONFIG}actions.yaml + @{poller_config} Create List ${ROOT_CONFIG}actions.yaml + Setup Two Gorgone Instances + ... central_config=${central_config} + ... communication_mode=${communication_mode} + ... central_name=${communication_mode}_gorgone_central + ... poller_name=${communication_mode}_gorgone_poller_2 + ... poller_config=${poller_config} + + # first we test the api without waiting for the output of the command. + # check by default the api launch the query in local + Test Async Action Module + # check the central can execute a command and send back the output + Test Async Action Module node_path=nodes/1/ + # check a distant poller can execute a command and send back the output + ${start_date} Get Current Date increment=-10s + Test Async Action Module node_path=nodes/2/ + # we need to check it is the poller and not the central that have done the action. + ${log_poller2_query} Create List Robot test write with param: for node nodes/2/ + ${logs_poller} Ctn Find In Log With Timeout log=/var/log/centreon-gorgone/${communication_mode}_gorgone_poller_2/gorgoned.log content=${log_poller2_query} date=${start_date} timeout=10 + Should Be True ${logs_poller} Didn't found the logs in the poller file : ${logs_poller} + + # Now we test the action api by waiting for the command output in one call. + # This make gorgone wait for 3 seconds before querying for logs, and wait again 0.5 seconds for log to be received by central. + # On my machine the sync_wait was at least 0.22 seconds to work sometime, it always worked with 0.5s. + # In real world where poller is not on the same server the delay will be greater and more random, + # so the async method should be privileged. + ${get_params}= Set Variable ?log_wait=3000000&sync_wait=500000 + Test Sync Action Module get_params=${get_params} + Test Sync Action Module get_params=${get_params} node_path=nodes/1/ + Test Sync Action Module get_params=${get_params} node_path=nodes/2/ + # we need to check it is the poller and not the central that have done the action. + ${start_date} Get Current Date increment=-10s + ${log_poller2_query_sync} Create List Robot test write with param:${get_params} for node nodes/2/ + ${logs_poller} Ctn Find In Log With Timeout log=/var/log/centreon-gorgone/${communication_mode}_gorgone_poller_2/gorgoned.log content=${log_poller2_query_sync} date=${start_date} timeout=10 + Should Be True ${logs_poller} Didn't found the logs in the poller file: ${logs_poller} + + Run rm /tmp/actionLogs + + Examples: communication_mode -- + ... push_zmq + ... pullwss + ... pull + +*** Keywords *** +Test Sync Action Module + [Arguments] ${get_params}= ${node_path}= + + ${action_api_result}= Post Action Endpoint node_path=${node_path} get_params=${get_params} + ${status} ${logs} Parse Json Response ${action_api_result} + Check Action Api Do Something ${status} ${logs} ${node_path} ${get_params} + + +Test Async Action Module + [Documentation] This make an api call to write to a dummy file and output a string. as gorgone central and poller and robot are executed on the same host we can access the file to check the result. + [Arguments] ${node_path}=${EMPTY} + ${action_api_result}= Post Action Endpoint node_path=${node_path} + # need to get the data from the token with getlog. + # this call multiples time the api until the response is available. + ${status} ${logs} Ctn Get Api Log With Timeout token=${action_api_result.json()}[token] node_path=${node_path} + Check Action Api Do Something ${status} ${logs} ${node_path} ${EMPTY} + + +Post Action Endpoint + [Arguments] ${node_path}=${EMPTY} ${get_params}=${EMPTY} + + # Ideally, Gorgone should not allow any bash interpretation on command it execute. + # As there is a whitelist in gorgone, if there was no bash interpretation we could allow only our required binary and be safe. + # As gorgone always had bash interpretation available, most of the internal use of this module use redirection, pipe or other sh feature. + ${bodycmd}= Create Dictionary command=echo 'Robot test write with param:${get_params} for node ${node_path}' | tee -a /tmp/actionLogs + ${body}= Create List ${bodycmd} + ${result} POST http://127.0.0.1:8085/api/${node_path}core/action/command${get_params} json=${body} + RETURN ${result} + + +Check Action Api Do Something + [Arguments] ${status} ${logs} ${node_path} ${get_params} + + Should Be True ${status} No log found in the gorgone api or the command failed. + # the log api send back a json containing a list of log, with for each logs the token, id, creation time (ctime), status code(code), and data (among other thing) + # data is a stringified json that need to be evaluated separately. + ${internal_json}= Evaluate json.loads("""${logs}[data]""") json + + Should Be Equal As Numbers 0 ${internal_json}[result][exit_code] + Should Be Equal As Strings + ... Robot test write with param:${get_params} for node ${node_path} + ... ${internal_json}[result][stdout] + ... output of the gorgone action api should be the bash command output. + + ${file_nb_line}= Run grep 'Robot test write with param:${get_params} for node ${node_path}\$' /tmp/actionLogs | wc -l + Should Be Equal 1 ${file_nb_line} command launched with gorgone api should set only one line in the file per tests From 59447dd796143caffb4ebd0579c57c924a374d1e Mon Sep 17 00:00:00 2001 From: tuntoja <58987095+tuntoja@users.noreply.github.com> Date: Tue, 27 Aug 2024 09:41:22 +0200 Subject: [PATCH 913/948] fix(ci): fix gorgone workflow name in release trigger build (#1640) --- .github/workflows/release-trigger-builds.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-trigger-builds.yml b/.github/workflows/release-trigger-builds.yml index 7c03d1b0c27..3769c527747 100644 --- a/.github/workflows/release-trigger-builds.yml +++ b/.github/workflows/release-trigger-builds.yml @@ -47,8 +47,8 @@ jobs: #COMPONENTS_OSS_FULL=("awie" "dsm" "gorgone" "ha" "open-tickets" "web") #COMPONENTS_MODULES=("anomaly-detection" "autodiscovery" "bam" "cloud-business-extensions" "cloud-extensions" "it-edition-extensions" "lm" "map" "mbi" "ppm") #COMPONENTS_MODULES_FULL=("anomaly-detection" "autodiscovery" "bam" "cloud-business-extensions" "cloud-extensions" "it-edition-extensions" "lm" "map" "mbi" "ppm" "php-pecl-gnupg" "sourceguardian-loader") - COMPONENTS_COLLECT=("Centreon collect" "centreon-gorgone") - COMPONENTS_COLLECT_FULL=("Centreon collect" "centreon-gorgone") + COMPONENTS_COLLECT=("Centreon collect" "gorgone") + COMPONENTS_COLLECT_FULL=("Centreon collect" "gorgone") RUNS_URL="" # Accept release prefixed or develop branches, nothing else From 1f45d5b471e8cbc77f71d557057704f02f93ef5e Mon Sep 17 00:00:00 2001 From: David Boucher <dboucher@centreon.com> Date: Tue, 27 Aug 2024 11:13:26 +0200 Subject: [PATCH 914/948] fix(collect/cmake): collect did not compile correctly with the new vcpkg (#1645) REFS: MON-147095 --- .github/scripts/windows-agent-compile.ps1 | 6 +++--- cmake-vcpkg.sh | 2 +- vcpkg.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/scripts/windows-agent-compile.ps1 b/.github/scripts/windows-agent-compile.ps1 index 9118532affc..02af28abb7e 100644 --- a/.github/scripts/windows-agent-compile.ps1 +++ b/.github/scripts/windows-agent-compile.ps1 @@ -46,17 +46,17 @@ if ( $? -ne $true ) { Write-Host "#######################################################################################################################" Write-Host "install vcpkg" - git clone --depth 1 --single-branch --no-tags https://github.com/microsoft/vcpkg vcpkg + git clone --depth 1 -b 2024.07.12 https://github.com/microsoft/vcpkg.git cd vcpkg bootstrap-vcpkg.bat cd $current_dir [System.Environment]::SetEnvironmentVariable("VCPKG_ROOT",$pwd.ToString()+"\vcpkg") [System.Environment]::SetEnvironmentVariable("PATH",$pwd.ToString()+"\vcpkg;" + $env:PATH) - + Write-Host "compile vcpkg dependencies" vcpkg install --vcpkg-root $env:VCPKG_ROOT --x-install-root build_windows\vcpkg_installed --x-manifest-root . --overlay-triplets custom-triplets --triplet x64-windows - + Write-Host "Compress binary archive" 7z a $file_name_extension build_windows\vcpkg_installed Write-Host "Upload binary archive" diff --git a/cmake-vcpkg.sh b/cmake-vcpkg.sh index ed652b7922a..e8eb514dcdb 100755 --- a/cmake-vcpkg.sh +++ b/cmake-vcpkg.sh @@ -258,7 +258,7 @@ fi if [ ! -d vcpkg ] ; then echo "No vcpkg directory. Cloning the repo" - git clone --depth 1 --single-branch --no-tags https://github.com/Microsoft/vcpkg.git + git clone --depth 1 -b 2024.01.12 https://github.com/Microsoft/vcpkg.git ./vcpkg/bootstrap-vcpkg.sh fi diff --git a/vcpkg.json b/vcpkg.json index 408c71efe59..4b7d7928448 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -60,4 +60,4 @@ "platform": "linux" } ] -} \ No newline at end of file +} From 8349e0e07c273f60c749b117ece0dceeb6be2941 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Tue, 27 Aug 2024 13:15:56 +0200 Subject: [PATCH 915/948] fix(ci): remove useless ci directory (#1641) --- ci/debian/centreon-broker-victoria_metrics.install | 1 - 1 file changed, 1 deletion(-) delete mode 100644 ci/debian/centreon-broker-victoria_metrics.install diff --git a/ci/debian/centreon-broker-victoria_metrics.install b/ci/debian/centreon-broker-victoria_metrics.install deleted file mode 100644 index 72c4113b02f..00000000000 --- a/ci/debian/centreon-broker-victoria_metrics.install +++ /dev/null @@ -1 +0,0 @@ -debian/tmp-centreon-collect/usr/share/centreon/lib/centreon-broker/70-victoria_metrics.so usr/share/centreon/lib/centreon-broker From 46d96d8d7a71d37b0e2f4c8c77e617d82755a828 Mon Sep 17 00:00:00 2001 From: Kevin Duret <kduret@centreon.com> Date: Thu, 29 Aug 2024 14:24:49 +0200 Subject: [PATCH 916/948] enh(ci): integrate robot tests to centreon-collect workflow (#1638) --- .github/scripts/collect-unit-tests.sh | 29 --- .github/workflows/centreon-collect.yml | 267 +++++++++++++++++++++++-- .github/workflows/check-status.yml | 2 +- .github/workflows/package-collect.yml | 12 +- .github/workflows/robot-nightly.yml | 190 ------------------ .github/workflows/robot-test.yml | 21 +- 6 files changed, 262 insertions(+), 259 deletions(-) delete mode 100755 .github/scripts/collect-unit-tests.sh delete mode 100644 .github/workflows/robot-nightly.yml diff --git a/.github/scripts/collect-unit-tests.sh b/.github/scripts/collect-unit-tests.sh deleted file mode 100755 index 077ff0291b9..00000000000 --- a/.github/scripts/collect-unit-tests.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash -set -e - -#Cmake -cd /src -export VCPKG_ROOT=/vcpkg -export PATH=$VCPKG_ROOT:$PATH - -OS_ID=$(awk '{print $1}' /etc/issue) -if [[ "$OS_ID" == "Debian" || "$OS_ID" == "Ubuntu" ]] ; then - CXXFLAGS="-Wall -Wextra" cmake -B build -DVCPKG_OVERLAY_TRIPLETS=/custom-triplets -DVCPKG_TARGET_TRIPLET=x64-linux-release -DVCPKG_OVERLAY_PORTS=/overlays -GNinja -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCMAKE_BUILD_TYPE=Debug -DWITH_PREFIX=/usr -DWITH_PREFIX_BIN=/usr/sbin -DWITH_USER_BROKER=centreon-broker -DWITH_USER_ENGINE=centreon-engine -DWITH_GROUP_BROKER=centreon-broker -DWITH_GROUP_ENGINE=centreon-engine -DWITH_TESTING=On -DWITH_PREFIX_MODULES=/usr/share/centreon/lib/centreon-broker -DWITH_PREFIX_CONF_BROKER=/etc/centreon-broker -DWITH_PREFIX_LIB_BROKER=/usr/lib64/nagios -DWITH_PREFIX_CONF_ENGINE=/etc/centreon-engine -DWITH_PREFIX_LIB_ENGINE=/usr/lib64/centreon-engine -DWITH_PREFIX_LIB_CLIB=/usr/lib64/ -DWITH_RW_DIR=/var/lib/centreon-engine/rw -DWITH_VAR_DIR=/var/log/centreon-engine -DWITH_MODULE_SIMU=On -S . -else - CXXFLAGS="-Wall -Wextra" cmake -B build -GNinja -DVCPKG_OVERLAY_TRIPLETS=/custom-triplets -DVCPKG_OVERLAY_PORTS=/overlays -DVCPKG_TARGET_TRIPLET=x64-linux-release -DCMAKE_EXPORT_COMPILE_COMMANDS=On -DCMAKE_BUILD_TYPE=Debug -DWITH_PREFIX=/usr -DWITH_PREFIX_BIN=/usr/sbin -DWITH_USER_BROKER=centreon-broker -DWITH_USER_ENGINE=centreon-engine -DWITH_GROUP_BROKER=centreon-broker -DWITH_GROUP_ENGINE=centreon-engine -DWITH_TESTING=On -DWITH_PREFIX_MODULES=/usr/share/centreon/lib/centreon-broker -DWITH_PREFIX_CONF_BROKER=/etc/centreon-broker -DWITH_PREFIX_LIB_BROKER=/usr/lib64/nagios -DWITH_PREFIX_CONF_ENGINE=/etc/centreon-engine -DWITH_PREFIX_LIB_ENGINE=/usr/lib64/centreon-engine -DWITH_PREFIX_LIB_CLIB=/usr/lib64/ -DWITH_RW_DIR=/var/lib/centreon-engine/rw -DWITH_VAR_DIR=/var/log/centreon-engine -DWITH_MODULE_SIMU=On -S . -fi - -#Build -ninja -Cbuild -j8 -ninja -Cbuild -j8 install - -#Test - -cd build -tests/ut_broker --gtest_output=xml:ut_broker.xml -tests/ut_engine --gtest_output=xml:ut_engine.xml -tests/ut_clib --gtest_output=xml:ut_clib.xml -tests/ut_connector --gtest_output=xml:ut_connector.xml -tests/ut_common --gtest_output=xml:ut_common.xml -tests/ut_agent --gtest_output=xml:ut_agent.xml -echo "---------------------------------------------------------- end of ut tests ------------------------------------------------" diff --git a/.github/workflows/centreon-collect.yml b/.github/workflows/centreon-collect.yml index e984cdc4540..d10762c81a5 100644 --- a/.github/workflows/centreon-collect.yml +++ b/.github/workflows/centreon-collect.yml @@ -1,4 +1,10 @@ name: Centreon collect +run-name: | + ${{ + (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.is_nightly == 'true')) + && format('collect nightly {0}', github.ref_name) + || '' + }} concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} @@ -6,6 +12,14 @@ concurrency: on: workflow_dispatch: + inputs: + is_nightly: + description: 'Set to true for nightly run' + required: true + default: false + type: boolean + schedule: + - cron: '30 0 * * 1-5' pull_request: paths: - agent/** @@ -57,40 +71,154 @@ on: - "!**/test/**" jobs: + dispatch-to-maintained-branches: + if: ${{ github.event_name == 'schedule' && github.ref_name == 'develop' }} + runs-on: ubuntu-24.04 + steps: + - name: Checkout sources + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + + - run: | + gh workflow run robot-nightly.yml -r "dev-24.04.x" + gh workflow run robot-nightly.yml -r "dev-23.10.x" + gh workflow run robot-nightly.yml -r "dev-23.04.x" + gh workflow run robot-nightly.yml -r "dev-22.10.x" + shell: bash + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + get-version: uses: ./.github/workflows/get-version.yml with: version_file: CMakeLists.txt + veracode-analysis: + needs: [get-version] + if: ${{ github.event_name == 'schedule' && github.ref_name == 'develop' }} + uses: ./.github/workflows/veracode-analysis.yml + with: + module_name: centreon-collect + major_version: ${{ needs.get-version.outputs.major_version }} + minor_version: ${{ needs.get-version.outputs.minor_version }} + img_version: ${{ needs.get-version.outputs.img_version }} + secrets: + veracode_api_id: ${{ secrets.VERACODE_API_ID_COLL }} + veracode_api_key: ${{ secrets.VERACODE_API_KEY_COLL }} + veracode_srcclr_token: ${{ secrets.VERACODE_SRCCLR_TOKEN }} + docker_registry_id: ${{ secrets.DOCKER_REGISTRY_ID }} + docker_registry_passwd: ${{ secrets.DOCKER_REGISTRY_PASSWD }} + unit-test: needs: [get-version] if: ${{ ! contains(fromJson('["stable"]'), needs.get-version.outputs.stability) }} - runs-on: [self-hosted, collect] strategy: fail-fast: false matrix: - image: [alma8, alma9, debian-bullseye, debian-bookworm] - name: unit test ${{ matrix.image }} + distrib: [alma8, alma9, debian-bullseye, debian-bookworm] + + runs-on: [self-hosted, collect] + + env: + SCCACHE_PATH: "/usr/bin/sccache" + SCCACHE_BUCKET: "centreon-github-sccache" + SCCACHE_REGION: "eu-west-1" + AWS_ACCESS_KEY_ID: ${{ secrets.COLLECT_S3_ACCESS_KEY }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.COLLECT_S3_SECRET_KEY }} + + container: + image: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }}/centreon-collect-${{ matrix.distrib }}:${{ needs.get-version.outputs.img_version }} + credentials: + username: ${{ secrets.DOCKER_REGISTRY_ID }} + password: ${{ secrets.DOCKER_REGISTRY_PASSWD }} + + name: unit test ${{ matrix.distrib }} steps: - name: Checkout sources uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - name: Login to Registry - uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 - with: - registry: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }} - username: ${{ secrets.DOCKER_REGISTRY_ID }} - password: ${{ secrets.DOCKER_REGISTRY_PASSWD }} + - name: Install sccache + run: | + if ! command -v wget &> /dev/null; then + if [[ "${{ matrix.distrib }}" == "alma8" || "${{ matrix.distrib }}" == "alma9" ]]; then + dnf install -y wget + else + apt-get update + apt-get install -y wget + fi + fi - - name: Test ${{ matrix.image }} - uses: ./.github/actions/runner-docker - with: - registry_url: ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }} - script_name: /src/.github/scripts/collect-unit-tests - image_name: centreon-collect-${{ matrix.image }} - image_version: ${{ needs.get-version.outputs.img_version }} + wget https://github.com/mozilla/sccache/releases/download/v0.8.1/sccache-v0.8.1-x86_64-unknown-linux-musl.tar.gz + tar xzf sccache-v0.8.1-x86_64-unknown-linux-musl.tar.gz + mv sccache-v0.8.1-x86_64-unknown-linux-musl/sccache /usr/bin/ + + ${SCCACHE_PATH} --start-server + shell: bash + + - name: Compile centreon-collect + run: | + CMAKE="cmake" + if [[ "${{ matrix.distrib }}" == "alma8" || "${{ matrix.distrib }}" == "alma9" ]]; then + CMAKE="cmake3" + fi + + export VCPKG_ROOT=/vcpkg + export PATH=$VCPKG_ROOT:$PATH + + mv /root/.cache /github/home/ + + CXXFLAGS="-Wall -Wextra" $CMAKE \ + -B build \ + -DVCPKG_OVERLAY_TRIPLETS=/custom-triplets \ + -DVCPKG_TARGET_TRIPLET=x64-linux-release \ + -DVCPKG_OVERLAY_PORTS=/overlays \ + -GNinja \ + -DDEBUG_ROBOT=OFF \ + -DCMAKE_EXPORT_COMPILE_COMMANDS=On \ + -DCMAKE_BUILD_TYPE=Debug \ + -DWITH_PREFIX=/usr \ + -DWITH_PREFIX_BIN=/usr/sbin \ + -DWITH_USER_BROKER=centreon-broker \ + -DWITH_USER_ENGINE=centreon-engine \ + -DWITH_GROUP_BROKER=centreon-broker \ + -DWITH_GROUP_ENGINE=centreon-engine \ + -DWITH_TESTING=On \ + -DWITH_PREFIX_MODULES=/usr/share/centreon/lib/centreon-broker \ + -DWITH_PREFIX_CONF_BROKER=/etc/centreon-broker \ + -DWITH_PREFIX_LIB_BROKER=/usr/lib64/nagios \ + -DWITH_PREFIX_CONF_ENGINE=/etc/centreon-engine \ + -DWITH_PREFIX_LIB_ENGINE=/usr/lib64/centreon-engine \ + -DWITH_PREFIX_LIB_CLIB=/usr/lib64/ \ + -DWITH_RW_DIR=/var/lib/centreon-engine/rw \ + -DWITH_VAR_DIR=/var/log/centreon-engine \ + -DWITH_MODULE_SIMU=On \ + -DCMAKE_C_COMPILER_LAUNCHER=${SCCACHE_PATH} \ + -DCMAKE_CXX_COMPILER_LAUNCHER=${SCCACHE_PATH} \ + -S . + + ninja -Cbuild + ninja -Cbuild install + shell: bash + + - name: Cache statistics + run: ${SCCACHE_PATH} --show-stats + shell: bash + + - name: Stop sccache server + run: ${SCCACHE_PATH} --stop-server + shell: bash + + - name: Run unit tests + run: | + tests/ut_broker --gtest_output=xml:ut_broker.xml + tests/ut_engine --gtest_output=xml:ut_engine.xml + tests/ut_clib --gtest_output=xml:ut_clib.xml + tests/ut_connector --gtest_output=xml:ut_connector.xml + tests/ut_common --gtest_output=xml:ut_common.xml + tests/ut_agent --gtest_output=xml:ut_agent.xml + working-directory: build + shell: bash package: needs: [get-version] @@ -105,10 +233,92 @@ jobs: stability: ${{ needs.get-version.outputs.stability }} secrets: inherit + robot-test: + needs: [get-version, package] + if: | + (github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && github.event.inputs.is_nightly == 'true')) && + ! cancelled() && + ! contains(needs.*.result, 'failure') && + ! contains(needs.*.result, 'cancelled') + + strategy: + fail-fast: false + matrix: + include: + - distrib: el9 + image: centreon-collect-alma9-test + package_extension: rpm + arch: amd64 + database_type: mariadb + test_group_name: robot_test-mariadb-el9-amd64 + tests_params: '{}' + - distrib: el9 + image: centreon-collect-mysql-alma9-test + package_extension: rpm + arch: amd64 + database_type: mysql + test_group_name: robot_test-mysql-el9-amd64 + tests_params: '{}' + - distrib: bullseye + image: centreon-collect-debian-bullseye-arm64-test + package_extension: deb + arch: arm64 + database_type: mariadb + test_group_name: robot_test-mariadb-bullseye-arm64 + tests_params: '{}' + - distrib: bookworm + image: centreon-collect-debian-bookworm-arm64-test + package_extension: deb + arch: arm64 + database_type: mariadb + test_group_name: robot_test-mariadb-bookworm-arm64 + tests_params: '{}' + - distrib: bookworm + image: centreon-collect-debian-bookworm-test + package_extension: deb + arch: amd64 + database_type: mariadb + test_group_name: robot_test-mariadb-bookworm-amd64 + tests_params: '{}' + - distrib: el9 + image: centreon-collect-alma9-test + package_extension: rpm + arch: amd64 + database_type: mariadb + test_group_name: robot_test-mariadb-el9-amd64-grpc + tests_params: '{"default_transport":"grpc","default_bbdo_version":"3.1.0"}' + + name: ${{ matrix.test_group_name }} + + uses: ./.github/workflows/robot-test.yml + with: + distrib: ${{ matrix.distrib }} + arch: ${{ matrix.arch }} + image: ${{ matrix.image }} + image_test: ${{ matrix.image }}:${{ needs.get-version.outputs.test_img_version }} + image_version: ${{ needs.get-version.outputs.img_version }} + package_cache_key: ${{ github.run_id }}-${{ github.sha }}-${{ matrix.package_extension }}-centreon-collect-${{ matrix.distrib }}-${{ matrix.arch }}-${{ github.head_ref || github.ref_name }} + package_cache_path: ./*.${{ matrix.package_extension}} + database_type: ${{ matrix.database_type }} + tests_params: ${{matrix.tests_params}} + test_group_name: ${{matrix.test_group_name}} + secrets: + registry_username: ${{ secrets.DOCKER_REGISTRY_ID }} + registry_password: ${{ secrets.DOCKER_REGISTRY_PASSWD }} + collect_s3_access_key: ${{ secrets.COLLECT_S3_ACCESS_KEY }} + collect_s3_secret_key: ${{ secrets.COLLECT_S3_SECRET_KEY }} + xray_client_id: ${{ secrets.XRAY_CLIENT_ID }} + xray_client_secret: ${{ secrets.XRAY_CLIENT_SECRET }} + deliver-sources: runs-on: [self-hosted, common] needs: [get-version, package] - if: ${{ contains(fromJson('["stable"]'), needs.get-version.outputs.stability) && github.event_name != 'workflow_dispatch' }} + if: | + github.event_name != 'workflow_dispatch' && + contains(fromJson('["stable"]'), needs.get-version.outputs.stability) && + ! cancelled() && + ! contains(needs.*.result, 'failure') && + ! contains(needs.*.result, 'cancelled') steps: - name: Checkout sources @@ -127,8 +337,12 @@ jobs: token_download_centreon_com: ${{ secrets.TOKEN_DOWNLOAD_CENTREON_COM }} deliver-rpm: - if: ${{ contains(fromJson('["testing", "stable"]'), needs.get-version.outputs.stability) }} - needs: [get-version, package] + if: | + contains(fromJson('["unstable", "testing"]'), needs.get-version.outputs.stability) && + ! cancelled() && + ! contains(needs.*.result, 'failure') && + ! contains(needs.*.result, 'cancelled') + needs: [get-version, robot-test] environment: ${{ needs.get-version.outputs.environment }} runs-on: [self-hosted, common] strategy: @@ -158,8 +372,12 @@ jobs: release_cloud: ${{ needs.get-version.outputs.release_cloud }} deliver-deb: - if: ${{ !needs.get-version.outputs.release_cloud && contains(fromJson('["testing", "stable"]'), needs.get-version.outputs.stability) }} - needs: [get-version, package] + if: | + contains(fromJson('["unstable", "testing"]'), needs.get-version.outputs.stability) && + ! cancelled() && + ! contains(needs.*.result, 'failure') && + ! contains(needs.*.result, 'cancelled') + needs: [get-version, robot-test] environment: ${{ needs.get-version.outputs.environment }} runs-on: [self-hosted, common] strategy: @@ -194,7 +412,12 @@ jobs: promote: needs: [get-version] - if: ${{ contains(fromJson('["stable"]'), needs.get-version.outputs.stability) && github.event_name != 'workflow_dispatch' }} + if: | + github.event_name != 'workflow_dispatch' && + contains(fromJson('["stable"]'), needs.get-version.outputs.stability) && + ! cancelled() && + ! contains(needs.*.result, 'failure') && + ! contains(needs.*.result, 'cancelled') runs-on: [self-hosted, common] strategy: matrix: diff --git a/.github/workflows/check-status.yml b/.github/workflows/check-status.yml index 36799865754..6cfbf4f7884 100644 --- a/.github/workflows/check-status.yml +++ b/.github/workflows/check-status.yml @@ -39,7 +39,7 @@ jobs: script: | await exec.exec("sleep 20s"); - for (let i = 0; i < 60; i++) { + for (let i = 0; i < 120; i++) { const failure = []; const cancelled = []; const pending = []; diff --git a/.github/workflows/package-collect.yml b/.github/workflows/package-collect.yml index 25219b29e1e..138fdfbb773 100644 --- a/.github/workflows/package-collect.yml +++ b/.github/workflows/package-collect.yml @@ -94,13 +94,13 @@ jobs: dnf install -y wget fi if [ "${{ matrix.arch }}" = "amd64" ]; then - wget https://github.com/mozilla/sccache/releases/download/v0.7.4/sccache-v0.7.4-x86_64-unknown-linux-musl.tar.gz - tar xzf sccache-v0.7.4-x86_64-unknown-linux-musl.tar.gz - mv sccache-v0.7.4-x86_64-unknown-linux-musl/sccache /usr/bin/ + wget https://github.com/mozilla/sccache/releases/download/v0.8.1/sccache-v0.8.1-x86_64-unknown-linux-musl.tar.gz + tar xzf sccache-v0.8.1-x86_64-unknown-linux-musl.tar.gz + mv sccache-v0.8.1-x86_64-unknown-linux-musl/sccache /usr/bin/ elif [ "${{ matrix.arch }}" = "arm64" ]; then - wget https://github.com/mozilla/sccache/releases/download/v0.7.4/sccache-v0.7.4-aarch64-unknown-linux-musl.tar.gz - tar xzf sccache-v0.7.4-aarch64-unknown-linux-musl.tar.gz - mv sccache-v0.7.4-aarch64-unknown-linux-musl/sccache /usr/bin/ + wget https://github.com/mozilla/sccache/releases/download/v0.8.1/sccache-v0.8.1-aarch64-unknown-linux-musl.tar.gz + tar xzf sccache-v0.8.1-aarch64-unknown-linux-musl.tar.gz + mv sccache-v0.8.1-aarch64-unknown-linux-musl/sccache /usr/bin/ fi ${SCCACHE_PATH} --start-server diff --git a/.github/workflows/robot-nightly.yml b/.github/workflows/robot-nightly.yml deleted file mode 100644 index 682ba8db11f..00000000000 --- a/.github/workflows/robot-nightly.yml +++ /dev/null @@ -1,190 +0,0 @@ -name: robot-nightly -run-name: nightly robot ${{ github.head_ref || github.ref_name }} - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -on: - workflow_dispatch: - schedule: - - cron: '30 0 * * 1-5' - -jobs: - dispatch-to-maintained-branches: - if: ${{ github.event_name == 'schedule' && github.ref_name == 'develop' }} - runs-on: ubuntu-22.04 - steps: - - name: Checkout sources - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - - run: | - gh workflow run robot-nightly.yml -r "dev-24.04.x" - gh workflow run robot-nightly.yml -r "dev-23.10.x" - gh workflow run robot-nightly.yml -r "dev-23.04.x" - gh workflow run robot-nightly.yml -r "dev-22.10.x" - shell: bash - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - get-version: - uses: ./.github/workflows/get-version.yml - with: - version_file: CMakeLists.txt - - veracode-analysis: - needs: [get-version] - uses: ./.github/workflows/veracode-analysis.yml - with: - module_name: centreon-collect - major_version: ${{ needs.get-version.outputs.major_version }} - minor_version: ${{ needs.get-version.outputs.minor_version }} - img_version: ${{ needs.get-version.outputs.img_version }} - secrets: - veracode_api_id: ${{ secrets.VERACODE_API_ID_COLL }} - veracode_api_key: ${{ secrets.VERACODE_API_KEY_COLL }} - veracode_srcclr_token: ${{ secrets.VERACODE_SRCCLR_TOKEN }} - docker_registry_id: ${{ secrets.DOCKER_REGISTRY_ID }} - docker_registry_passwd: ${{ secrets.DOCKER_REGISTRY_PASSWD }} - - package: - needs: [get-version] - uses: ./.github/workflows/package-collect.yml - with: - stability: ${{ needs.get-version.outputs.stability }} - major_version: ${{ needs.get-version.outputs.major_version }} - minor_version: ${{ needs.get-version.outputs.minor_version }} - img_version: ${{ needs.get-version.outputs.img_version }} - release: ${{ needs.get-version.outputs.release }} - commit_hash: ${{ github.sha }} - secrets: inherit - - robot-test: - needs: [get-version, package] - - strategy: - fail-fast: false - matrix: - include: - - distrib: el9 - image: centreon-collect-alma9-test - package_extension: rpm - arch: amd64 - database_type: mariadb - test_group_name: robot_test-mariadb-el9-amd64 - tests_params: '{}' - - distrib: el9 - image: centreon-collect-mysql-alma9-test - package_extension: rpm - arch: amd64 - database_type: mysql - test_group_name: robot_test-mysql-el9-amd64 - tests_params: '{}' - - distrib: bullseye - image: centreon-collect-debian-bullseye-arm64-test - package_extension: deb - arch: arm64 - database_type: mariadb - test_group_name: robot_test-mariadb-bullseye-arm64 - tests_params: '{}' - - distrib: bookworm - image: centreon-collect-debian-bookworm-arm64-test - package_extension: deb - arch: arm64 - database_type: mariadb - test_group_name: robot_test-mariadb-bookworm-arm64 - tests_params: '{}' - - distrib: bookworm - image: centreon-collect-debian-bookworm-test - package_extension: deb - arch: amd64 - database_type: mariadb - test_group_name: robot_test-mariadb-bookworm-amd64 - tests_params: '{}' - - distrib: el9 - image: centreon-collect-alma9-test - package_extension: rpm - arch: amd64 - database_type: mariadb - test_group_name: robot_test-mariadb-el9-amd64-grpc - tests_params: '{"default_transport":"grpc","default_bbdo_version":"3.1.0"}' - - name: ${{ matrix.test_group_name }} - - uses: ./.github/workflows/robot-test.yml - with: - distrib: ${{ matrix.distrib }} - arch: ${{ matrix.arch }} - image: ${{ matrix.image }} - image_test: ${{ matrix.image }}:${{ needs.get-version.outputs.test_img_version }} - image_version: ${{ needs.get-version.outputs.img_version }} - package_cache_key: ${{ github.run_id }}-${{ github.sha }}-${{ matrix.package_extension }}-centreon-collect-${{ matrix.distrib }}-${{ matrix.arch }}-${{ github.head_ref || github.ref_name }} - package_cache_path: ./*.${{ matrix.package_extension}} - database_type: ${{ matrix.database_type }} - tests_params: ${{matrix.tests_params}} - test_group_name: ${{matrix.test_group_name}} - secrets: - registry_username: ${{ secrets.DOCKER_REGISTRY_ID }} - registry_password: ${{ secrets.DOCKER_REGISTRY_PASSWD }} - collect_s3_access_key: ${{ secrets.COLLECT_S3_ACCESS_KEY }} - collect_s3_secret_key: ${{ secrets.COLLECT_S3_SECRET_KEY }} - xray_client_id: ${{ secrets.XRAY_CLIENT_ID }} - xray_client_secret: ${{ secrets.XRAY_CLIENT_SECRET }} - - deliver-rpm: - if: ${{ contains(fromJson('["unstable"]'), needs.get-version.outputs.stability) }} - needs: [robot-test, get-version] - runs-on: [self-hosted, common] - strategy: - matrix: - distrib: [el8, el9] - name: deliver ${{ matrix.distrib }} - - steps: - - name: Checkout sources - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - - name: Publish RPM packages - uses: ./.github/actions/delivery - with: - module_name: collect - distrib: ${{ matrix.distrib }} - major_version: ${{ needs.get-version.outputs.major_version }} - artifactory_token: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }} - cache_key: ${{ github.run_id }}-${{ github.sha }}-rpm-centreon-collect-${{ matrix.distrib }}-amd64-${{ github.head_ref || github.ref_name }} - stability: ${{ needs.get-version.outputs.stability }} - release_type: ${{ needs.get-version.outputs.release_type }} - release_cloud: ${{ needs.get-version.outputs.release_cloud }} - - deliver-deb: - if: ${{ !needs.get-version.outputs.release_cloud && contains(fromJson('["unstable"]'), needs.get-version.outputs.stability) }} - needs: [robot-test, get-version] - runs-on: [self-hosted, common] - strategy: - matrix: - include: - - distrib: bullseye - arch: amd64 - - distrib: bookworm - arch: amd64 - - distrib: jammy - arch: amd64 - - distrib: bullseye - arch: arm64 - name: deliver ${{ matrix.distrib }} - - steps: - - name: Checkout sources - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - - name: Publish DEB packages - uses: ./.github/actions/delivery - with: - module_name: collect - distrib: ${{ matrix.distrib }} - major_version: ${{ needs.get-version.outputs.major_version }} - artifactory_token: ${{ secrets.ARTIFACTORY_ACCESS_TOKEN }} - cache_key: ${{ github.run_id }}-${{ github.sha }}-deb-centreon-collect-${{ matrix.distrib }}-${{ matrix.arch }}-${{ github.head_ref || github.ref_name }} - stability: ${{ needs.get-version.outputs.stability }} - release_type: ${{ needs.get-version.outputs.release_type }} - release_cloud: ${{ needs.get-version.outputs.release_cloud }} diff --git a/.github/workflows/robot-test.yml b/.github/workflows/robot-test.yml index 2c77a97402b..d6f8bc622e3 100644 --- a/.github/workflows/robot-test.yml +++ b/.github/workflows/robot-test.yml @@ -134,20 +134,19 @@ jobs: docker load --input /tmp/${{ inputs.image }} - name: Test ${{ matrix.feature }} - run: | - docker run --rm --privileged --ulimit core=-1 --security-opt seccomp=unconfined \ - -v $(pwd):/test_collect \ - --env AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \ - --env AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \ - --env AWS_BUCKET=centreon-collect-robot-report \ - --workdir /test_collect \ - ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }}/${{inputs.image_test}} \ - ./.github/scripts/collect-test-robot.sh \ - ${{ matrix.feature }} ${{inputs.database_type}} ${{inputs.tests_params}} - env: AWS_ACCESS_KEY_ID: ${{ secrets.collect_s3_access_key }} AWS_SECRET_ACCESS_KEY: ${{ secrets.collect_s3_secret_key }} + run: | + docker run --rm --privileged --ulimit core=-1 --security-opt seccomp=unconfined \ + -v $(pwd):/test_collect \ + --env AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \ + --env AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \ + --env AWS_BUCKET=centreon-collect-robot-report \ + --workdir /test_collect \ + ${{ vars.DOCKER_INTERNAL_REGISTRY_URL }}/${{inputs.image_test}} \ + ./.github/scripts/collect-test-robot.sh \ + ${{ matrix.feature }} ${{inputs.database_type}} ${{inputs.tests_params}} # - name: Generate Xray Token # id: generate-xray-token From 159bf5bacc6b1a725d5fddeafef0c7a3a763a14c Mon Sep 17 00:00:00 2001 From: David Boucher <dboucher@centreon.com> Date: Wed, 24 Jul 2024 18:35:12 +0200 Subject: [PATCH 917/948] enh(engine/configuration): First implementation of the common/engine_conf library. enh(engine/config): it compiles now fix(common/engine_legacy): bug fixed in timeperiod configurations REFS: MON-34072 MON-15480 --- CMakeLists.txt | 11 +- common/CMakeLists.txt | 1 + common/engine_conf/CMakeLists.txt | 93 + common/engine_conf/anomalydetection_helper.cc | 315 ++++ common/engine_conf/anomalydetection_helper.hh | 48 + common/engine_conf/command_helper.cc | 64 + common/engine_conf/command_helper.hh | 42 + common/engine_conf/connector_helper.cc | 65 + common/engine_conf/connector_helper.hh | 42 + common/engine_conf/contact_helper.cc | 140 ++ common/engine_conf/contact_helper.hh | 47 + common/engine_conf/contactgroup_helper.cc | 79 + common/engine_conf/contactgroup_helper.hh | 44 + common/engine_conf/file_info.hh | 68 + common/engine_conf/host_helper.cc | 240 +++ common/engine_conf/host_helper.hh | 47 + common/engine_conf/hostdependency_helper.cc | 163 ++ common/engine_conf/hostdependency_helper.hh | 46 + common/engine_conf/hostescalation_helper.cc | 135 ++ common/engine_conf/hostescalation_helper.hh | 46 + common/engine_conf/hostgroup_helper.cc | 79 + common/engine_conf/hostgroup_helper.hh | 44 + common/engine_conf/message_helper.cc | 451 +++++ common/engine_conf/message_helper.hh | 230 +++ common/engine_conf/parser.cc | 757 +++++++++ common/engine_conf/parser.cc.old | 1503 +++++++++++++++++ common/engine_conf/parser.hh | 124 ++ common/engine_conf/parser.hh.old | 162 ++ common/engine_conf/service_helper.cc | 303 ++++ common/engine_conf/service_helper.hh | 43 + .../engine_conf/servicedependency_helper.cc | 196 +++ .../engine_conf/servicedependency_helper.hh | 42 + .../engine_conf/serviceescalation_helper.cc | 149 ++ .../engine_conf/serviceescalation_helper.hh | 42 + common/engine_conf/servicegroup_helper.cc | 76 + common/engine_conf/servicegroup_helper.hh | 40 + common/engine_conf/severity_helper.cc | 111 ++ common/engine_conf/severity_helper.hh | 41 + common/engine_conf/state.proto | 770 +++++++++ common/engine_conf/state_helper.cc | 490 ++++++ common/engine_conf/state_helper.hh | 45 + common/engine_conf/tag_helper.cc | 102 ++ common/engine_conf/tag_helper.hh | 40 + common/engine_conf/timeperiod_helper.cc | 577 +++++++ common/engine_conf/timeperiod_helper.hh | 52 + common/engine_legacy_conf/CMakeLists.txt | 3 +- common/engine_legacy_conf/host.hh | 10 +- common/engine_legacy_conf/object.hh | 4 + common/engine_legacy_conf/parser.hh | 12 +- engine/CMakeLists.txt | 69 +- engine/enginerpc/CMakeLists.txt | 20 + engine/enginerpc/engine_impl.cc | 218 +-- engine/inc/com/centreon/engine/broker.hh | 4 +- .../configuration/applier/anomalydetection.hh | 22 +- .../engine/configuration/applier/command.hh | 18 +- .../engine/configuration/applier/connector.hh | 14 + .../engine/configuration/applier/contact.hh | 21 +- .../configuration/applier/contactgroup.hh | 25 +- .../engine/configuration/applier/globals.hh | 14 +- .../engine/configuration/applier/host.hh | 32 +- .../configuration/applier/hostdependency.hh | 15 + .../configuration/applier/hostescalation.hh | 13 + .../engine/configuration/applier/hostgroup.hh | 28 +- .../engine/configuration/applier/logging.hh | 17 + .../engine/configuration/applier/macros.hh | 8 + .../configuration/applier/pb_difference.hh | 147 ++ .../engine/configuration/applier/scheduler.hh | 52 +- .../engine/configuration/applier/service.hh | 37 +- .../applier/servicedependency.hh | 33 +- .../applier/serviceescalation.hh | 25 +- .../configuration/applier/servicegroup.hh | 32 +- .../engine/configuration/applier/severity.hh | 23 +- .../engine/configuration/applier/state.hh | 37 + .../engine/configuration/applier/tag.hh | 27 +- .../configuration/applier/timeperiod.hh | 23 +- .../engine/configuration/extended_conf.hh | 7 + engine/inc/com/centreon/engine/contact.hh | 5 +- .../inc/com/centreon/engine/contactgroup.hh | 10 +- engine/inc/com/centreon/engine/daterange.hh | 22 + engine/inc/com/centreon/engine/globals.hh | 11 +- .../inc/com/centreon/engine/hostdependency.hh | 11 +- .../inc/com/centreon/engine/hostescalation.hh | 8 + .../inc/com/centreon/engine/macros/defines.hh | 4 +- engine/inc/com/centreon/engine/nebstructs.hh | 6 +- .../retention/applier/anomalydetection.hh | 20 +- .../engine/retention/applier/contact.hh | 18 +- .../centreon/engine/retention/applier/host.hh | 16 +- .../engine/retention/applier/program.hh | 8 + .../engine/retention/applier/service.hh | 19 +- .../engine/retention/applier/state.hh | 8 + .../com/centreon/engine/servicedependency.hh | 7 + .../com/centreon/engine/serviceescalation.hh | 8 + engine/inc/com/centreon/engine/timeperiod.hh | 26 +- engine/modules/CMakeLists.txt | 4 - .../modules/external_commands/CMakeLists.txt | 59 +- engine/modules/external_commands/src/utils.cc | 65 +- engine/src/broker.cc | 180 +- engine/src/checks/checker.cc | 70 +- engine/src/command_manager.cc | 19 + engine/src/commands/command.cc | 8 +- engine/src/commands/commands.cc | 310 +++- engine/src/commands/connector.cc | 24 +- engine/src/commands/processing.cc | 26 +- engine/src/commands/raw.cc | 26 +- engine/src/config.cc | 2 + .../configuration/applier/anomalydetection.cc | 419 ++++- engine/src/configuration/applier/command.cc | 189 ++- engine/src/configuration/applier/connector.cc | 123 ++ engine/src/configuration/applier/contact.cc | 437 ++++- .../src/configuration/applier/contactgroup.cc | 214 +++ engine/src/configuration/applier/globals.cc | 46 + engine/src/configuration/applier/host.cc | 515 +++++- .../configuration/applier/hostdependency.cc | 224 +++ .../configuration/applier/hostescalation.cc | 232 +++ engine/src/configuration/applier/hostgroup.cc | 165 +- engine/src/configuration/applier/logging.cc | 101 +- engine/src/configuration/applier/macros.cc | 34 + engine/src/configuration/applier/scheduler.cc | 700 +++++++- engine/src/configuration/applier/service.cc | 614 ++++++- .../applier/servicedependency.cc | 310 ++++ .../applier/serviceescalation.cc | 259 +++ .../src/configuration/applier/servicegroup.cc | 206 +++ engine/src/configuration/applier/severity.cc | 120 +- engine/src/configuration/applier/state.cc | 1002 ++++++++++- engine/src/configuration/applier/tag.cc | 129 +- .../src/configuration/applier/timeperiod.cc | 158 ++ engine/src/configuration/extended_conf.cc | 39 + engine/src/configuration/whitelist.cc | 3 +- engine/src/contact.cc | 20 +- engine/src/contactgroup.cc | 22 +- engine/src/daterange.cc | 51 + engine/src/diagnostic.cc | 20 +- engine/src/events/loop.cc | 135 +- engine/src/events/sched_info.cc | 167 +- engine/src/events/timed_event.cc | 32 + engine/src/flapping.cc | 18 + engine/src/globals.cc | 8 +- engine/src/host.cc | 218 ++- engine/src/hostdependency.cc | 23 + engine/src/hostescalation.cc | 37 + engine/src/macros.cc | 7 +- engine/src/main.cc | 167 +- engine/src/nebmods.cc | 6 +- engine/src/notifier.cc | 85 +- .../src/retention/applier/anomalydetection.cc | 51 +- engine/src/retention/applier/comment.cc | 3 +- engine/src/retention/applier/contact.cc | 125 ++ engine/src/retention/applier/host.cc | 289 ++++ engine/src/retention/applier/program.cc | 108 ++ engine/src/retention/applier/service.cc | 309 +++- engine/src/retention/applier/state.cc | 55 + engine/src/retention/dump.cc | 122 +- engine/src/sehandlers.cc | 141 +- engine/src/service.cc | 260 ++- engine/src/servicedependency.cc | 24 + engine/src/serviceescalation.cc | 38 + engine/src/shared.cc | 29 +- engine/src/statistics.cc | 13 + engine/src/timeperiod.cc | 111 +- engine/src/timerange.cc | 8 +- engine/src/xsddefault.cc | 106 +- .../applier/applier-anomalydetection.cc | 1 - 162 files changed, 18400 insertions(+), 863 deletions(-) create mode 100644 common/engine_conf/CMakeLists.txt create mode 100644 common/engine_conf/anomalydetection_helper.cc create mode 100644 common/engine_conf/anomalydetection_helper.hh create mode 100644 common/engine_conf/command_helper.cc create mode 100644 common/engine_conf/command_helper.hh create mode 100644 common/engine_conf/connector_helper.cc create mode 100644 common/engine_conf/connector_helper.hh create mode 100644 common/engine_conf/contact_helper.cc create mode 100644 common/engine_conf/contact_helper.hh create mode 100644 common/engine_conf/contactgroup_helper.cc create mode 100644 common/engine_conf/contactgroup_helper.hh create mode 100644 common/engine_conf/file_info.hh create mode 100644 common/engine_conf/host_helper.cc create mode 100644 common/engine_conf/host_helper.hh create mode 100644 common/engine_conf/hostdependency_helper.cc create mode 100644 common/engine_conf/hostdependency_helper.hh create mode 100644 common/engine_conf/hostescalation_helper.cc create mode 100644 common/engine_conf/hostescalation_helper.hh create mode 100644 common/engine_conf/hostgroup_helper.cc create mode 100644 common/engine_conf/hostgroup_helper.hh create mode 100644 common/engine_conf/message_helper.cc create mode 100644 common/engine_conf/message_helper.hh create mode 100644 common/engine_conf/parser.cc create mode 100644 common/engine_conf/parser.cc.old create mode 100644 common/engine_conf/parser.hh create mode 100644 common/engine_conf/parser.hh.old create mode 100644 common/engine_conf/service_helper.cc create mode 100644 common/engine_conf/service_helper.hh create mode 100644 common/engine_conf/servicedependency_helper.cc create mode 100644 common/engine_conf/servicedependency_helper.hh create mode 100644 common/engine_conf/serviceescalation_helper.cc create mode 100644 common/engine_conf/serviceescalation_helper.hh create mode 100644 common/engine_conf/servicegroup_helper.cc create mode 100644 common/engine_conf/servicegroup_helper.hh create mode 100644 common/engine_conf/severity_helper.cc create mode 100644 common/engine_conf/severity_helper.hh create mode 100644 common/engine_conf/state.proto create mode 100644 common/engine_conf/state_helper.cc create mode 100644 common/engine_conf/state_helper.hh create mode 100644 common/engine_conf/tag_helper.cc create mode 100644 common/engine_conf/tag_helper.hh create mode 100644 common/engine_conf/timeperiod_helper.cc create mode 100644 common/engine_conf/timeperiod_helper.hh create mode 100644 engine/inc/com/centreon/engine/configuration/applier/pb_difference.hh diff --git a/CMakeLists.txt b/CMakeLists.txt index 38d29d8de50..6ad78b344c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -70,7 +70,7 @@ set(CMAKE_CXX_EXTENSIONS OFF) # Get distributions name # if(CMAKE_SYSTEM_NAME STREQUAL "Linux") - file(STRINGS "/etc/os-release" release REGEX "^ID") + file(STRINGS "/etc/os-release" release REGEX "ID") foreach(line ${release}) if(${line} MATCHES "ID_LIKE=.*") @@ -80,6 +80,10 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux") if(${line} MATCHES "ID=.*") string(REGEX REPLACE "ID=\"(.*)\"" "\\1" id ${line}) endif() + + if(${line} MATCHES "VERSION_ID=.*") + string(REGEX REPLACE "VERSION_ID=\"([0-9]+)\..*" "\\1" os_version ${line}) + endif() endforeach() string(TOLOWER "${like}" like) @@ -100,6 +104,11 @@ else() set(OS_DISTRIBUTOR "${CMAKE_SYSTEM_NAME}") endif() +if(OS_DISTRIBUTOR STREQUAL "CentOS" AND os_version STREQUAL "8") + message(STATUS "Legacy gettimeofday") + add_definitions("-DLEGACY_GETTIMEOFDAY") +endif() + message(STATUS "${id} detected (compatible with ${OS_DISTRIBUTOR})") # set -latomic if OS is Raspbian. diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 69e302e25ad..88dc8cdc288 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -21,6 +21,7 @@ project("Centreon common" C CXX) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") add_subdirectory(log_v2) + add_subdirectory(engine_conf) add_subdirectory(engine_legacy_conf) endif() diff --git a/common/engine_conf/CMakeLists.txt b/common/engine_conf/CMakeLists.txt new file mode 100644 index 00000000000..d8c2b3b3bdc --- /dev/null +++ b/common/engine_conf/CMakeLists.txt @@ -0,0 +1,93 @@ +# +# Copyright 2022-2024 Centreon +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# For more information : contact@centreon.com +# +add_custom_command( + OUTPUT state.pb.cc state.pb.h + DEPENDS state.proto + COMMENT "Generating interface files of the engine configuration file" + COMMAND ${Protobuf_PROTOC_EXECUTABLE} ARGS --cpp_out=. + --proto_path=${CMAKE_CURRENT_SOURCE_DIR} state.proto + VERBATIM) + +add_custom_target(target_state_proto DEPENDS state.pb.cc state.pb.h) + +include_directories("${CMAKE_SOURCE_DIR}/engine/inc") + +add_definitions(-DDEFAULT_STATUS_FILE="${ENGINE_VAR_LOG_DIR}/status.dat") +add_definitions(-DDEFAULT_RETENTION_FILE="${ENGINE_VAR_LOG_DIR}/retention.dat") +add_definitions(-DDEFAULT_DEBUG_FILE="${ENGINE_VAR_LOG_DIR}/centengine.debug") +add_definitions( + -DDEFAULT_COMMAND_FILE="${ENGINE_VAR_LIB_DIR}/rw/centengine.cmd") +add_definitions(-DDEFAULT_LOG_FILE="${ENGINE_VAR_LOG_DIR}/centengine.log") + +add_library( + engine_conf STATIC + anomalydetection_helper.cc + anomalydetection_helper.hh + command_helper.cc + command_helper.hh + connector_helper.cc + connector_helper.hh + contact_helper.cc + contact_helper.hh + contactgroup_helper.cc + contactgroup_helper.hh + host_helper.cc + host_helper.hh + hostdependency_helper.cc + hostdependency_helper.hh + hostescalation_helper.cc + hostescalation_helper.hh + hostgroup_helper.cc + hostgroup_helper.hh + message_helper.cc + message_helper.hh + parser.cc + parser.hh + service_helper.cc + service_helper.hh + servicedependency_helper.cc + servicedependency_helper.hh + serviceescalation_helper.cc + serviceescalation_helper.hh + servicegroup_helper.cc + servicegroup_helper.hh + severity_helper.cc + severity_helper.hh + ${CMAKE_CURRENT_BINARY_DIR}/state.pb.cc + ${CMAKE_CURRENT_BINARY_DIR}/state.pb.h + state_helper.cc + state_helper.hh + tag_helper.cc + tag_helper.hh + timeperiod_helper.cc + timeperiod_helper.hh) +add_dependencies(engine_conf target_state_proto) +include_directories(${CMAKE_SOURCE_DIR}/common/inc) + +target_precompile_headers(engine_conf PRIVATE + ${CMAKE_SOURCE_DIR}/common/precomp_inc/precomp.hh) +set_target_properties(engine_conf PROPERTIES POSITION_INDEPENDENT_CODE ON) +target_link_libraries( + engine_conf + log_v2 + absl::any + absl::log + absl::base + absl::bits + -L${PROTOBUF_LIB_DIR} + protobuf) diff --git a/common/engine_conf/anomalydetection_helper.cc b/common/engine_conf/anomalydetection_helper.cc new file mode 100644 index 00000000000..50e51a705a0 --- /dev/null +++ b/common/engine_conf/anomalydetection_helper.cc @@ -0,0 +1,315 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ +#include "common/engine_conf/anomalydetection_helper.hh" + +#include "com/centreon/exceptions/msg_fmt.hh" + +using com::centreon::exceptions::msg_fmt; + +namespace com::centreon::engine::configuration { + +/** + * @brief Constructor from an Anomalydetection object. + * + * @param obj The Anomalydetection object on which this helper works. The helper + * is not the owner of this object, it just helps to initialize it. + */ +anomalydetection_helper::anomalydetection_helper(Anomalydetection* obj) + : message_helper(object_type::anomalydetection, + obj, + { + {"_HOST_ID", "host_id"}, + {"_SERVICE_ID", "service_id"}, + {"description", "service_description"}, + {"service_groups", "servicegroups"}, + {"contact_groups", "contactgroups"}, + {"normal_check_interval", "check_interval"}, + {"retry_check_interval", "retry_interval"}, + {"active_checks_enabled", "checks_active"}, + {"passive_checks_enabled", "checks_passive"}, + {"severity", "severity_id"}, + }, + 53) { + _init(); +} + +/** + * @brief For several keys, the parser of Anomalydetection objects has a + * particular behavior. These behaviors are handled here. + * @param key The key to parse. + * @param value The value corresponding to the key + */ +bool anomalydetection_helper::hook(std::string_view key, + const std::string_view& value) { + Anomalydetection* obj = static_cast<Anomalydetection*>(mut_obj()); + key = validate_key(key); + if (key == "contactgroups") { + fill_string_group(obj->mutable_contactgroups(), value); + return true; + } else if (key == "contacts") { + fill_string_group(obj->mutable_contacts(), value); + return true; + } else if (key == "flap_detection_options") { + uint16_t options(action_svc_none); + auto values = absl::StrSplit(value, ','); + for (auto it = values.begin(); it != values.end(); ++it) { + std::string_view v = absl::StripAsciiWhitespace(*it); + if (v == "o" || v == "ok") + options |= action_svc_ok; + else if (v == "w" || v == "warning") + options |= action_svc_warning; + else if (v == "u" || v == "unknown") + options |= action_svc_unknown; + else if (v == "c" || v == "critical") + options |= action_svc_critical; + else if (v == "n" || v == "none") + options |= action_svc_none; + else if (v == "a" || v == "all") + options = action_svc_ok | action_svc_warning | action_svc_unknown | + action_svc_critical; + else + return false; + } + obj->set_flap_detection_options(options); + return true; + } else if (key == "initial_state") { + ServiceStatus initial_state; + if (value == "o" || value == "ok") + initial_state = ServiceStatus::state_ok; + else if (value == "w" || value == "warning") + initial_state = ServiceStatus::state_warning; + else if (value == "u" || value == "unknown") + initial_state = ServiceStatus::state_unknown; + else if (value == "c" || value == "critical") + initial_state = ServiceStatus::state_critical; + else + return false; + obj->set_initial_state(initial_state); + return true; + } else if (key == "notification_options") { + uint16_t options(action_svc_none); + auto values = absl::StrSplit(value, ','); + for (auto it = values.begin(); it != values.end(); ++it) { + std::string_view v = absl::StripAsciiWhitespace(*it); + if (v == "u" || v == "unknown") + options |= action_svc_unknown; + else if (v == "w" || v == "warning") + options |= action_svc_warning; + else if (v == "c" || v == "critical") + options |= action_svc_critical; + else if (v == "r" || v == "recovery") + options |= action_svc_ok; + else if (v == "f" || v == "flapping") + options |= action_svc_flapping; + else if (v == "s" || v == "downtime") + options |= action_svc_downtime; + else if (v == "n" || v == "none") + options = action_svc_none; + else if (v == "a" || v == "all") + options = action_svc_unknown | action_svc_warning | + action_svc_critical | action_svc_ok | action_svc_flapping | + action_svc_downtime; + else + return false; + } + obj->set_notification_options(options); + return true; + } else if (key == "servicegroups") { + fill_string_group(obj->mutable_servicegroups(), value); + return true; + } else if (key == "stalking_options") { + uint16_t options(action_svc_none); + auto values = absl::StrSplit(value, ','); + for (auto it = values.begin(); it != values.end(); ++it) { + std::string_view v = absl::StripAsciiWhitespace(*it); + if (v == "u" || v == "unknown") + options |= action_svc_unknown; + else if (v == "o" || v == "ok") + options |= action_svc_ok; + else if (v == "w" || v == "warning") + options |= action_svc_warning; + else if (v == "c" || v == "critical") + options |= action_svc_critical; + else if (v == "n" || v == "none") + options = action_svc_none; + else if (v == "a" || v == "all") + options = action_svc_ok | action_svc_unknown | action_svc_warning | + action_svc_critical; + else + return false; + } + obj->set_stalking_options(options); + return true; + } else if (key == "category_tags") { + auto tags{absl::StrSplit(value, ',')}; + bool ret = true; + + for (auto it = obj->tags().begin(); it != obj->tags().end();) { + if (it->second() == TagType::tag_servicecategory) + it = obj->mutable_tags()->erase(it); + else + ++it; + } + + for (auto& tag : tags) { + uint64_t id; + bool parse_ok; + parse_ok = absl::SimpleAtoi(tag, &id); + if (parse_ok) { + auto t = obj->add_tags(); + t->set_first(id); + t->set_second(TagType::tag_servicecategory); + } else { + ret = false; + } + } + return ret; + } else if (key == "group_tags") { + auto tags{absl::StrSplit(value, ',')}; + bool ret = true; + + for (auto it = obj->tags().begin(); it != obj->tags().end();) { + if (it->second() == TagType::tag_servicegroup) + it = obj->mutable_tags()->erase(it); + else + ++it; + } + + for (auto& tag : tags) { + uint64_t id; + bool parse_ok; + parse_ok = absl::SimpleAtoi(tag, &id); + if (parse_ok) { + auto t = obj->add_tags(); + t->set_first(id); + t->set_second(TagType::tag_servicegroup); + } else { + ret = false; + } + } + return ret; + } + return false; +} + +/** + * @brief Check the validity of the Anomalydetection object. + * + * @param err An error counter. + */ +void anomalydetection_helper::check_validity(error_cnt& err) const { + const Anomalydetection* o = static_cast<const Anomalydetection*>(obj()); + + if (o->obj().register_()) { + if (o->service_description().empty()) { + err.config_errors++; + throw msg_fmt( + "Anomaly detection has no name (property 'service_description')"); + } + if (o->host_name().empty()) { + err.config_errors++; + throw msg_fmt( + "Anomaly detection '{}' has no host name (property 'host_name')", + o->service_description()); + } + if (o->metric_name().empty()) { + err.config_errors++; + throw msg_fmt( + "Anomaly detection '{}' has no metric name (property 'metric_name')", + o->service_description()); + } + if (o->thresholds_file().empty()) { + err.config_errors++; + throw msg_fmt( + "Anomaly detection '{}' has no thresholds file (property " + "'thresholds_file')", + o->service_description()); + } + } +} + +/** + * @brief Initializer of the Anomalydetection object, in other words set its + * default values. Protobuf does not allow specific default values, so we fix + * this with this method. + */ +void anomalydetection_helper::_init() { + Anomalydetection* obj = static_cast<Anomalydetection*>(mut_obj()); + obj->mutable_obj()->set_register_(true); + obj->set_acknowledgement_timeout(0); + obj->set_status_change(false); + obj->set_checks_active(true); + obj->set_checks_passive(true); + obj->set_check_freshness(0); + obj->set_check_interval(5); + obj->set_event_handler_enabled(true); + obj->set_first_notification_delay(0); + obj->set_flap_detection_enabled(true); + obj->set_flap_detection_options(action_svc_ok | action_svc_warning | + action_svc_unknown | action_svc_critical); + obj->set_freshness_threshold(0); + obj->set_high_flap_threshold(0); + obj->set_initial_state(ServiceStatus::state_ok); + obj->set_is_volatile(false); + obj->set_low_flap_threshold(0); + obj->set_max_check_attempts(3); + obj->set_notifications_enabled(true); + obj->set_notification_interval(0); + obj->set_notification_options(action_svc_ok | action_svc_warning | + action_svc_critical | action_svc_unknown | + action_svc_flapping | action_svc_downtime); + obj->set_obsess_over_service(true); + obj->set_process_perf_data(true); + obj->set_retain_nonstatus_information(true); + obj->set_retain_status_information(true); + obj->set_retry_interval(1); + obj->set_stalking_options(action_svc_none); +} + +/** + * @brief If the provided key/value have their parsing that failed previously, + * it is possible they are a customvariable. A customvariable name has its + * name starting with an underscore. This method checks the possibility to + * store a customvariable in the given object and stores it if possible. + * + * @param key The name of the customvariable. + * @param value Its value as a string. + * + * @return True if the customvariable has been well stored. + */ +bool anomalydetection_helper::insert_customvariable(std::string_view key, + std::string_view value) { + if (key[0] != '_') + return false; + + key.remove_prefix(1); + Anomalydetection* obj = static_cast<Anomalydetection*>(mut_obj()); + auto* cvs = obj->mutable_customvariables(); + for (auto& c : *cvs) { + if (c.name() == key) { + c.set_value(value.data(), value.size()); + return true; + } + } + auto new_cv = cvs->Add(); + new_cv->set_name(key.data(), key.size()); + new_cv->set_value(value.data(), value.size()); + return true; +} +} // namespace com::centreon::engine::configuration diff --git a/common/engine_conf/anomalydetection_helper.hh b/common/engine_conf/anomalydetection_helper.hh new file mode 100644 index 00000000000..9b8698ba728 --- /dev/null +++ b/common/engine_conf/anomalydetection_helper.hh @@ -0,0 +1,48 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#ifndef CCE_CONFIGURATION_ANOMALYDETECTION +#define CCE_CONFIGURATION_ANOMALYDETECTION + +#include "common/engine_conf/message_helper.hh" +#include "common/engine_conf/state.pb.h" + +namespace com::centreon::engine::configuration { + +/** + * @brief Helper for the Anomalydetection message. The helper is instanciated + * just after a message is created. It provides default values for it and also + * several methods to helper the developer to fill the message fields. + */ +class anomalydetection_helper : public message_helper { + void _init(); + + public: + anomalydetection_helper(Anomalydetection* obj); + ~anomalydetection_helper() noexcept = default; + void check_validity(error_cnt& err) const override; + + bool hook(std::string_view key, const std::string_view& value) override; + + bool insert_customvariable(std::string_view key, + std::string_view value) override; +}; +} // namespace com::centreon::engine::configuration + +#endif /* !CCE_CONFIGURATION_ANOMALYDETECTION */ diff --git a/common/engine_conf/command_helper.cc b/common/engine_conf/command_helper.cc new file mode 100644 index 00000000000..31277ba3021 --- /dev/null +++ b/common/engine_conf/command_helper.cc @@ -0,0 +1,64 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ +#include "common/engine_conf/command_helper.hh" + +#include "com/centreon/exceptions/msg_fmt.hh" + +using com::centreon::exceptions::msg_fmt; + +namespace com::centreon::engine::configuration { + +/** + * @brief Constructor from a Command object. + * + * @param obj The Command object on which this helper works. The helper is not + * the owner of this object. It is just used to set the message default values. + */ +command_helper::command_helper(Command* obj) + : message_helper(object_type::command, obj, {}, 5) { + _init(); +} + +/** + * @brief Check the validity of the Command object. + * + * @param err An error counter. + */ +void command_helper::check_validity(error_cnt& err) const { + const Command* o = static_cast<const Command*>(obj()); + + if (o->command_name().empty()) { + err.config_errors++; + throw msg_fmt("Command has no name (property 'command_name')"); + } + if (o->command_line().empty()) { + err.config_errors++; + throw msg_fmt("Command '{}' has no command line (property 'command_line')", + o->command_name()); + } +} + +/** + * @brief The initializer of the Command message. + */ +void command_helper::_init() { + Command* obj = static_cast<Command*>(mut_obj()); + obj->mutable_obj()->set_register_(true); +} +} // namespace com::centreon::engine::configuration diff --git a/common/engine_conf/command_helper.hh b/common/engine_conf/command_helper.hh new file mode 100644 index 00000000000..777ed6eac26 --- /dev/null +++ b/common/engine_conf/command_helper.hh @@ -0,0 +1,42 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ +#ifndef CCE_CONFIGURATION_COMMAND +#define CCE_CONFIGURATION_COMMAND + +#include "common/engine_conf/message_helper.hh" +#include "common/engine_conf/state.pb.h" + +namespace com::centreon::engine::configuration { + +/** + * @brief Helper for the Command message. The helper is instanciated + * just after a message is created. It provides default values for it and also + * several methods to help the developer to fill the message fields. + */ +class command_helper : public message_helper { + void _init(); + + public: + command_helper(Command* obj); + ~command_helper() noexcept = default; + void check_validity(error_cnt& err) const override; +}; +} // namespace com::centreon::engine::configuration + +#endif /* !CCE_CONFIGURATION_COMMAND */ diff --git a/common/engine_conf/connector_helper.cc b/common/engine_conf/connector_helper.cc new file mode 100644 index 00000000000..0fb6a0ab3a6 --- /dev/null +++ b/common/engine_conf/connector_helper.cc @@ -0,0 +1,65 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ +#include "common/engine_conf/connector_helper.hh" + +#include "com/centreon/exceptions/msg_fmt.hh" + +using com::centreon::exceptions::msg_fmt; + +namespace com::centreon::engine::configuration { + +/** + * @brief Constructor from a Connector object. + * + * @param obj The Connector object on which this helper works. The helper is not + * the owner of this object. + */ +connector_helper::connector_helper(Connector* obj) + : message_helper(object_type::connector, obj, {}, 4) { + _init(); +} + +/** + * @brief Check the validity of the Connector object. + * + * @param err An error counter. + */ +void connector_helper::check_validity(error_cnt& err) const { + const Connector* o = static_cast<const Connector*>(obj()); + + if (o->connector_name().empty()) { + err.config_errors++; + throw msg_fmt("Connector has no name (property 'connector_name')"); + } + if (o->connector_line().empty()) { + err.config_errors++; + throw msg_fmt( + "Connector '{}' has no command line (property 'connector_line')", + o->connector_name()); + } +} + +/** + * @brief The initializer of the Connector message. + */ +void connector_helper::_init() { + Connector* obj = static_cast<Connector*>(mut_obj()); + obj->mutable_obj()->set_register_(true); +} +} // namespace com::centreon::engine::configuration diff --git a/common/engine_conf/connector_helper.hh b/common/engine_conf/connector_helper.hh new file mode 100644 index 00000000000..cdc4476323e --- /dev/null +++ b/common/engine_conf/connector_helper.hh @@ -0,0 +1,42 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ +#ifndef CCE_CONFIGURATION_CONNECTOR +#define CCE_CONFIGURATION_CONNECTOR + +#include "common/engine_conf/message_helper.hh" +#include "common/engine_conf/state.pb.h" + +namespace com::centreon::engine::configuration { + +/** + * @brief Helper for the Connector message. The helper is instanciated + * just after a message is created. It provides default values for it and also + * several methods to help the developer to fill the message fields. + */ +class connector_helper : public message_helper { + void _init(); + + public: + connector_helper(Connector* obj); + ~connector_helper() noexcept = default; + void check_validity(error_cnt& err) const override; +}; +} // namespace com::centreon::engine::configuration + +#endif /* !CCE_CONFIGURATION_CONNECTOR */ diff --git a/common/engine_conf/contact_helper.cc b/common/engine_conf/contact_helper.cc new file mode 100644 index 00000000000..59d3cc075ed --- /dev/null +++ b/common/engine_conf/contact_helper.cc @@ -0,0 +1,140 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ +#include "common/engine_conf/contact_helper.hh" + +#include "com/centreon/exceptions/msg_fmt.hh" + +using com::centreon::exceptions::msg_fmt; + +namespace com::centreon::engine::configuration { + +/** + * @brief Constructor from a Contact object. + * + * @param obj The Contact object on which this helper works. The helper is not + * the owner of this object. + */ +contact_helper::contact_helper(Contact* obj) + : message_helper(object_type::contact, + obj, + { + {"contact_groups", "contactgroups"}, + }, + 21) { + _init(); +} + +/** + * @brief For several keys, the parser of Contact objects has a particular + * behavior. These behaviors are handled here. + * @param key The key to parse. + * @param value The value corresponding to the key + */ +bool contact_helper::hook(std::string_view key, const std::string_view& value) { + Contact* obj = static_cast<Contact*>(mut_obj()); + key = validate_key(key); + + if (key == "host_notification_options") { + uint32_t options; + if (fill_host_notification_options(&options, value)) { + obj->set_host_notification_options(options); + return true; + } else + return false; + } else if (key == "service_notification_options") { + uint32_t options; + if (fill_service_notification_options(&options, value)) { + obj->set_service_notification_options(options); + return true; + } else + return false; + } else if (key == "contactgroups") { + fill_string_group(obj->mutable_contactgroups(), value); + return true; + } else if (key == "host_notification_commands") { + fill_string_group(obj->mutable_host_notification_commands(), value); + return true; + } else if (key == "service_notification_commands") { + fill_string_group(obj->mutable_service_notification_commands(), value); + return true; + } + return false; +} + +/** + * @brief Check the validity of the Contact object. + * + * @param err An error counter. + */ +void contact_helper::check_validity(error_cnt& err) const { + const Contact* o = static_cast<const Contact*>(obj()); + + if (o->contact_name().empty()) { + err.config_errors++; + throw msg_fmt("Contact has no name (property 'contact_name')"); + } +} + +/** + * @brief Initializer of the Contact object, in other words set its default + * values. + */ +void contact_helper::_init() { + Contact* obj = static_cast<Contact*>(mut_obj()); + obj->mutable_obj()->set_register_(true); + obj->set_can_submit_commands(true); + obj->set_host_notifications_enabled(true); + obj->set_host_notification_options(action_hst_none); + obj->set_retain_nonstatus_information(true); + obj->set_retain_status_information(true); + obj->set_service_notification_options(action_svc_none); + obj->set_service_notifications_enabled(true); +} + +/** + * @brief If the provided key/value have their parsing to fail previously, + * it is possible they are a customvariable. A customvariable name has its + * name starting with an underscore. This method checks the possibility to + * store a customvariable in the given object and stores it if possible. + * + * @param key The name of the customvariable. + * @param value Its value as a string. + * + * @return True if the customvariable has been well stored. + */ +bool contact_helper::insert_customvariable(std::string_view key, + std::string_view value) { + if (key[0] != '_') + return false; + + key.remove_prefix(1); + Contact* obj = static_cast<Contact*>(mut_obj()); + auto* cvs = obj->mutable_customvariables(); + for (auto& c : *cvs) { + if (c.name() == key) { + c.set_value(value.data(), value.size()); + return true; + } + } + auto new_cv = cvs->Add(); + new_cv->set_name(key.data(), key.size()); + new_cv->set_value(value.data(), value.size()); + return true; +} +} // namespace com::centreon::engine::configuration diff --git a/common/engine_conf/contact_helper.hh b/common/engine_conf/contact_helper.hh new file mode 100644 index 00000000000..4c887d75381 --- /dev/null +++ b/common/engine_conf/contact_helper.hh @@ -0,0 +1,47 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ +#ifndef CCE_CONFIGURATION_CONTACT +#define CCE_CONFIGURATION_CONTACT + +#include "common/engine_conf/message_helper.hh" +#include "common/engine_conf/state.pb.h" + +namespace com::centreon::engine::configuration { + +/** + * @brief Helper for the Contact message. The helper is instanciated + * just after a message is created. It provides default values for it and also + * several methods to help the developer to fill the message fields. + */ +class contact_helper : public message_helper { + void _init(); + + public: + contact_helper(Contact* obj); + ~contact_helper() noexcept = default; + void check_validity(error_cnt& err) const override; + + bool hook(std::string_view key, const std::string_view& value) override; + + bool insert_customvariable(std::string_view key, + std::string_view value) override; +}; +} // namespace com::centreon::engine::configuration + +#endif /* !CCE_CONFIGURATION_CONTACT */ diff --git a/common/engine_conf/contactgroup_helper.cc b/common/engine_conf/contactgroup_helper.cc new file mode 100644 index 00000000000..8c74aa73b5b --- /dev/null +++ b/common/engine_conf/contactgroup_helper.cc @@ -0,0 +1,79 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ +#include "common/engine_conf/contactgroup_helper.hh" + +#include "com/centreon/exceptions/msg_fmt.hh" + +using com::centreon::exceptions::msg_fmt; + +namespace com::centreon::engine::configuration { + +/** + * @brief Constructor from a Contactgroup object. + * + * @param obj The Contactgroup object on which this helper works. The helper is + * not the owner of this object. + */ +contactgroup_helper::contactgroup_helper(Contactgroup* obj) + : message_helper(object_type::contactgroup, obj, {}, 6) { + _init(); +} + +/** + * @brief For several keys, the parser of Contactgroup objects has a particular + * behavior. These behaviors are handled here. + * @param key The key to parse. + * @param value The value corresponding to the key + */ +bool contactgroup_helper::hook(std::string_view key, + const std::string_view& value) { + Contactgroup* obj = static_cast<Contactgroup*>(mut_obj()); + key = validate_key(key); + if (key == "contactgroup_members") { + fill_string_group(obj->mutable_contactgroup_members(), value); + return true; + } else if (key == "members") { + fill_string_group(obj->mutable_members(), value); + return true; + } + return false; +} + +/** + * @brief Check the validity of the Contactgroup object. + * + * @param err An error counter. + */ +void contactgroup_helper::check_validity(error_cnt& err) const { + const Contactgroup* o = static_cast<const Contactgroup*>(obj()); + + if (o->contactgroup_name().empty()) { + err.config_errors++; + throw msg_fmt("Contactgroup has no name (property 'contactgroup_name')"); + } +} + +/** + * @brief The initializer of the Contactgroup message. + */ +void contactgroup_helper::_init() { + Contactgroup* obj = static_cast<Contactgroup*>(mut_obj()); + obj->mutable_obj()->set_register_(true); +} +} // namespace com::centreon::engine::configuration diff --git a/common/engine_conf/contactgroup_helper.hh b/common/engine_conf/contactgroup_helper.hh new file mode 100644 index 00000000000..a97a055d950 --- /dev/null +++ b/common/engine_conf/contactgroup_helper.hh @@ -0,0 +1,44 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ +#ifndef CCE_CONFIGURATION_CONTACTGROUP +#define CCE_CONFIGURATION_CONTACTGROUP + +#include "common/engine_conf/message_helper.hh" +#include "common/engine_conf/state.pb.h" + +namespace com::centreon::engine::configuration { + +/** + * @brief Helper for the Contactgroup message. The helper is instanciated + * just after a message is created. It provides default values for it and also + * several methods to help the developer to fill the message fields. + */ +class contactgroup_helper : public message_helper { + void _init(); + + public: + contactgroup_helper(Contactgroup* obj); + ~contactgroup_helper() noexcept = default; + void check_validity(error_cnt& err) const override; + + bool hook(std::string_view key, const std::string_view& value) override; +}; +} // namespace com::centreon::engine::configuration + +#endif /* !CCE_CONFIGURATION_CONTACTGROUP */ diff --git a/common/engine_conf/file_info.hh b/common/engine_conf/file_info.hh new file mode 100644 index 00000000000..7737dd1144c --- /dev/null +++ b/common/engine_conf/file_info.hh @@ -0,0 +1,68 @@ +/** + * Copyright 2011-2013 Merethis + * Copyright 2014-2024 Centreon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ +#ifndef CCE_CONFIGURATION_FILE_INFO_HH +#define CCE_CONFIGURATION_FILE_INFO_HH + +namespace com::centreon::engine { + +namespace configuration { +class file_info { + uint32_t _line; + std::string _path; + + public: + file_info(std::string const& path = "", unsigned int line = 0) + : _line(line), _path(path) {} + file_info(file_info const& right) { operator=(right); } + ~file_info() noexcept {} + file_info& operator=(file_info const& right) { + if (this != &right) { + _line = right._line; + _path = right._path; + } + return *this; + } + bool operator==(file_info const& right) const noexcept { + return _line == right._line && _path == right._path; + } + bool operator!=(file_info const& right) const noexcept { + return !operator==(right); + } + friend std::ostream& operator<<(std::ostream& s, file_info const& info) { + s << "in file '" << info.path() << "' on line " << info.line(); + return s; + } + unsigned int line() const noexcept { return _line; } + void line(unsigned int line) noexcept { _line = line; } + std::string const& path() const noexcept { return _path; } + void path(std::string const& path) { _path = path; } +}; +} // namespace configuration + +} // namespace com::centreon::engine + +namespace fmt { +// formatter specializations for fmt +template <> +struct formatter<com::centreon::engine::configuration::file_info> + : ostream_formatter {}; +} // namespace fmt + +#endif // !CCE_CONFIGURATION_FILE_INFO_HH diff --git a/common/engine_conf/host_helper.cc b/common/engine_conf/host_helper.cc new file mode 100644 index 00000000000..c98387638c3 --- /dev/null +++ b/common/engine_conf/host_helper.cc @@ -0,0 +1,240 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ +#include "common/engine_conf/host_helper.hh" + +#include "com/centreon/exceptions/msg_fmt.hh" + +#ifdef LEGACY_CONF +#error host_helper should not be compiled in this context. +#endif + +using com::centreon::exceptions::msg_fmt; + +namespace com::centreon::engine::configuration { + +/** + * @brief Constructor from a Host object. + * + * @param obj The Host object on which this helper works. The helper is not the + * owner of this object. + */ +host_helper::host_helper(Host* obj) + : message_helper(object_type::host, + obj, + { + {"_HOST_ID", "host_id"}, + {"host_groups", "hostgroups"}, + {"contact_groups", "contactgroups"}, + {"gd2_image", "statusmap_image"}, + {"normal_check_interval", "check_interval"}, + {"retry_check_interval", "retry_interval"}, + {"checks_enabled", "checks_active"}, + {"active_checks_enabled", "checks_active"}, + {"passive_checks_enabled", "checks_passive"}, + {"2d_coords", "coords_2d"}, + {"3d_coords", "coords_3d"}, + {"severity", "severity_id"}, + }, + 53) { + _init(); +} + +/** + * @brief For several keys, the parser of Host objects has a particular + * behavior. These behaviors are handled here. + * @param key The key to parse. + * @param value The value corresponding to the key + */ +bool host_helper::hook(std::string_view key, const std::string_view& value) { + Host* obj = static_cast<Host*>(mut_obj()); + key = validate_key(key); + if (key == "contactgroups") { + fill_string_group(obj->mutable_contactgroups(), value); + return true; + } else if (key == "contacts") { + fill_string_group(obj->mutable_contacts(), value); + return true; + } else if (key == "hostgroups") { + fill_string_group(obj->mutable_hostgroups(), value); + return true; + } else if (key == "notification_options") { + uint16_t options(action_svc_none); + auto values = absl::StrSplit(value, ','); + for (auto it = values.begin(); it != values.end(); ++it) { + std::string_view v = absl::StripAsciiWhitespace(*it); + if (v == "d" || v == "down") + options |= action_hst_down; + else if (v == "u" || v == "unreachable") + options |= action_hst_unreachable; + else if (v == "r" || v == "recovery") + options |= action_hst_up; + else if (v == "f" || v == "flapping") + options |= action_hst_flapping; + else if (v == "s" || v == "downtime") + options |= action_hst_downtime; + else if (v == "n" || v == "none") + options = action_hst_none; + else if (v == "a" || v == "all") + options = action_hst_down | action_hst_unreachable | action_hst_up | + action_hst_flapping | action_hst_downtime; + else + return false; + } + obj->set_notification_options(options); + return true; + } else if (key == "parents") { + fill_string_group(obj->mutable_parents(), value); + return true; + } else if (key == "category_tags") { + auto tags{absl::StrSplit(value, ',')}; + bool ret = true; + + for (auto it = obj->tags().begin(); it != obj->tags().end();) { + if (it->second() == TagType::tag_hostcategory) + it = obj->mutable_tags()->erase(it); + else + ++it; + } + + for (auto& tag : tags) { + uint64_t id; + bool parse_ok; + parse_ok = absl::SimpleAtoi(tag, &id); + if (parse_ok) { + auto t = obj->add_tags(); + t->set_first(id); + t->set_second(TagType::tag_hostcategory); + } else { + ret = false; + } + } + return ret; + } else if (key == "group_tags") { + auto tags{absl::StrSplit(value, ',')}; + bool ret = true; + + for (auto it = obj->tags().begin(); it != obj->tags().end();) { + if (it->second() == TagType::tag_hostgroup) + it = obj->mutable_tags()->erase(it); + else + ++it; + } + + for (auto& tag : tags) { + uint64_t id; + bool parse_ok; + parse_ok = absl::SimpleAtoi(tag, &id); + if (parse_ok) { + auto t = obj->add_tags(); + t->set_first(id); + t->set_second(TagType::tag_hostgroup); + } else { + ret = false; + } + } + return ret; + } + return false; +} + +/** + * @brief Check the validity of the Host object. + * + * @param err An error counter. + */ +void host_helper::check_validity(error_cnt& err) const { + const Host* o = static_cast<const Host*>(obj()); + + if (o->obj().register_()) { + if (o->host_name().empty()) { + err.config_errors++; + throw msg_fmt("Host has no name (property 'host_name')"); + } + if (o->address().empty()) { + err.config_errors++; + throw msg_fmt("Host '{}' has no address (property 'address')", + o->host_name()); + } + } +} + +/** + * @brief Initializer of the Host object, in other words set its default values. + */ +void host_helper::_init() { + Host* obj = static_cast<Host*>(mut_obj()); + obj->mutable_obj()->set_register_(true); + obj->set_checks_active(true); + obj->set_checks_passive(true); + obj->set_check_freshness(false); + obj->set_check_interval(5); + obj->set_event_handler_enabled(true); + obj->set_first_notification_delay(0); + obj->set_flap_detection_enabled(true); + obj->set_flap_detection_options(action_hst_up | action_hst_down | + action_hst_unreachable); + obj->set_freshness_threshold(0); + obj->set_high_flap_threshold(0); + obj->set_initial_state(HostStatus::state_up); + obj->set_low_flap_threshold(0); + obj->set_max_check_attempts(3); + obj->set_notifications_enabled(true); + obj->set_notification_interval(0); + obj->set_notification_options(action_hst_up | action_hst_down | + action_hst_unreachable | action_hst_flapping | + action_hst_downtime); + obj->set_obsess_over_host(true); + obj->set_process_perf_data(true); + obj->set_retain_nonstatus_information(true); + obj->set_retain_status_information(true); + obj->set_retry_interval(1); + obj->set_stalking_options(action_hst_none); +} + +/** + * @brief If the provided key/value have their parsing to fail previously, + * it is possible they are a customvariable. A customvariable name has its + * name starting with an underscore. This method checks the possibility to + * store a customvariable in the given object and stores it if possible. + * + * @param key The name of the customvariable. + * @param value Its value as a string. + * + * @return True if the customvariable has been well stored. + */ +bool host_helper::insert_customvariable(std::string_view key, + std::string_view value) { + if (key[0] != '_') + return false; + + key.remove_prefix(1); + Host* obj = static_cast<Host*>(mut_obj()); + auto* cvs = obj->mutable_customvariables(); + for (auto& c : *cvs) { + if (c.name() == key) { + c.set_value(value.data(), value.size()); + return true; + } + } + auto new_cv = cvs->Add(); + new_cv->set_name(key.data(), key.size()); + new_cv->set_value(value.data(), value.size()); + return true; +} +} // namespace com::centreon::engine::configuration diff --git a/common/engine_conf/host_helper.hh b/common/engine_conf/host_helper.hh new file mode 100644 index 00000000000..63e8a010b98 --- /dev/null +++ b/common/engine_conf/host_helper.hh @@ -0,0 +1,47 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ +#ifndef CCE_CONFIGURATION_HOST +#define CCE_CONFIGURATION_HOST + +#include "common/engine_conf/message_helper.hh" +#include "common/engine_conf/state.pb.h" + +namespace com::centreon::engine::configuration { + +/** + * @brief Helper for the Host message. The helper is instanciated + * just after a message is created. It provides default values for it and also + * several methods to help the developer to fill the message fields. + */ +class host_helper : public message_helper { + void _init(); + + public: + host_helper(Host* obj); + ~host_helper() noexcept = default; + void check_validity(error_cnt& err) const override; + + bool hook(std::string_view key, const std::string_view& value) override; + + bool insert_customvariable(std::string_view key, + std::string_view value) override; +}; +} // namespace com::centreon::engine::configuration + +#endif /* !CCE_CONFIGURATION_HOST */ diff --git a/common/engine_conf/hostdependency_helper.cc b/common/engine_conf/hostdependency_helper.cc new file mode 100644 index 00000000000..3140485d490 --- /dev/null +++ b/common/engine_conf/hostdependency_helper.cc @@ -0,0 +1,163 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ +#include "common/engine_conf/hostdependency_helper.hh" + +#include "com/centreon/exceptions/msg_fmt.hh" + +using com::centreon::exceptions::msg_fmt; + +namespace com::centreon::engine::configuration { + +/** + * @brief Builds a key from a Hostdependency message. This is useful to check + * modifications in hostdependencies. + * + * @param hd The Hostdependency object to use to build the key. + * + * @return A number of type size_t. + */ +size_t hostdependency_key(const Hostdependency& hd) { + assert(hd.hosts().data().size() == 1 && hd.hostgroups().data().empty() && + hd.dependent_hosts().data().size() == 1 && + hd.dependent_hostgroups().data().empty()); + return absl::HashOf(hd.dependency_period(), hd.dependency_type(), + hd.dependent_hosts().data(0), hd.hosts().data(0), + hd.inherits_parent(), hd.notification_failure_options()); +} + +/** + * @brief Constructor from a Hostdependency object. + * + * @param obj The Hostdependency object on which this helper works. The helper + * is not the owner of this object. + */ +hostdependency_helper::hostdependency_helper(Hostdependency* obj) + : message_helper( + object_type::hostdependency, + obj, + { + {"hostgroup", "hostgroups"}, + {"hostgroup_name", "hostgroups"}, + {"host", "hosts"}, + {"host_name", "hosts"}, + {"master_host", "hosts"}, + {"master_host_name", "hosts"}, + {"dependent_hostgroup", "dependent_hostgroups"}, + {"dependent_hostgroup_name", "dependent_hostgroups"}, + {"dependent_host", "dependent_hosts"}, + {"dependent_host_name", "dependent_hosts"}, + {"notification_failure_criteria", "notification_failure_options"}, + {"execution_failure_criteria", "execution_failure_options"}, + }, + 11) { + _init(); +} + +/** + * @brief For several keys, the parser of Hostdependency objects has a + * particular behavior. These behaviors are handled here. + * @param key The key to parse. + * @param value The value corresponding to the key + */ +bool hostdependency_helper::hook(std::string_view key, + const std::string_view& value) { + Hostdependency* obj = static_cast<Hostdependency*>(mut_obj()); + key = validate_key(key); + + if (key == "notification_failure_options" || + key == "execution_failure_options") { + auto opts = absl::StrSplit(value, ','); + uint16_t options = action_hd_none; + + for (auto& o : opts) { + std::string_view ov = absl::StripAsciiWhitespace(o); + if (ov == "o" || ov == "up") + options |= action_hd_up; + else if (ov == "d" || ov == "down") + options |= action_hd_down; + else if (ov == "u" || ov == "unreachable") + options |= action_hd_unreachable; + else if (ov == "p" || ov == "pending") + options |= action_hd_pending; + else if (ov == "n" || ov == "none") + options |= action_hd_none; + else if (ov == "a" || ov == "all") + options = action_hd_up | action_hd_down | action_hd_unreachable | + action_hd_pending; + else + return false; + } + if (key[0] == 'n') + obj->set_notification_failure_options(options); + else + obj->set_execution_failure_options(options); + return true; + } else if (key == "dependent_hostgroups") { + fill_string_group(obj->mutable_dependent_hostgroups(), value); + return true; + } else if (key == "dependent_hosts") { + fill_string_group(obj->mutable_dependent_hosts(), value); + return true; + } else if (key == "hostgroups") { + fill_string_group(obj->mutable_hostgroups(), value); + return true; + } else if (key == "hosts") { + fill_string_group(obj->mutable_hosts(), value); + return true; + } + return false; +} + +/** + * @brief Check the validity of the Hostdependency object. + * + * @param err An error counter. + */ +void hostdependency_helper::check_validity(error_cnt& err) const { + const Hostdependency* o = static_cast<const Hostdependency*>(obj()); + + if (o->hosts().data().empty() && o->hostgroups().data().empty()) { + err.config_errors++; + throw msg_fmt( + "Host dependency is not attached to any host or host group (properties " + "'hosts' or 'hostgroups', respectively)"); + } + if (o->dependent_hosts().data().empty() && + o->dependent_hostgroups().data().empty()) { + err.config_errors++; + throw msg_fmt( + "Host dependency is not attached to any " + "dependent host or dependent host group (properties " + "'dependent_hosts' or 'dependent_hostgroups', " + "respectively)"); + } +} + +/** + * @brief Initializer of the Hostdependency object, in other words set its + * default values. + */ +void hostdependency_helper::_init() { + Hostdependency* obj = static_cast<Hostdependency*>(mut_obj()); + obj->mutable_obj()->set_register_(true); + obj->set_execution_failure_options(action_hd_none); + obj->set_inherits_parent(false); + obj->set_notification_failure_options(action_hd_none); +} +} // namespace com::centreon::engine::configuration diff --git a/common/engine_conf/hostdependency_helper.hh b/common/engine_conf/hostdependency_helper.hh new file mode 100644 index 00000000000..36a45dab338 --- /dev/null +++ b/common/engine_conf/hostdependency_helper.hh @@ -0,0 +1,46 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ +#ifndef CCE_CONFIGURATION_HOSTDEPENDENCY +#define CCE_CONFIGURATION_HOSTDEPENDENCY + +#include "common/engine_conf/message_helper.hh" +#include "common/engine_conf/state.pb.h" + +namespace com::centreon::engine::configuration { + +size_t hostdependency_key(const Hostdependency& hd); + +/** + * @brief Helper for the Hostdependency message. The helper is instanciated + * just after a message is created. It provides default values for it and also + * several methods to help the developer to fill the message fields. + */ +class hostdependency_helper : public message_helper { + void _init(); + + public: + hostdependency_helper(Hostdependency* obj); + ~hostdependency_helper() noexcept = default; + void check_validity(error_cnt& err) const override; + + bool hook(std::string_view key, const std::string_view& value) override; +}; +} // namespace com::centreon::engine::configuration + +#endif /* !CCE_CONFIGURATION_HOSTDEPENDENCY */ diff --git a/common/engine_conf/hostescalation_helper.cc b/common/engine_conf/hostescalation_helper.cc new file mode 100644 index 00000000000..34b9a13bfd2 --- /dev/null +++ b/common/engine_conf/hostescalation_helper.cc @@ -0,0 +1,135 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ +#include "common/engine_conf/hostescalation_helper.hh" + +#include "com/centreon/exceptions/msg_fmt.hh" + +using com::centreon::exceptions::msg_fmt; + +namespace com::centreon::engine::configuration { + +/** + * @brief Builds a key from a Hostescalation message. This is useful to check + * modifications in hostescalations. + * + * @param hd The Hostescalation object to use to build the key. + * + * @return A number of type size_t. + */ +size_t hostescalation_key(const Hostescalation& he) { + return absl::HashOf(he.hosts().data(0), + // he.contactgroups().data(), + he.escalation_options(), he.escalation_period(), + he.first_notification(), he.last_notification(), + he.notification_interval()); +} + +/** + * @brief Constructor from a Hostescalation object. + * + * @param obj The Hostescalation object on which this helper works. The helper + * is not the owner of this object. + */ +hostescalation_helper::hostescalation_helper(Hostescalation* obj) + : message_helper(object_type::hostescalation, + obj, + { + {"hostgroup", "hostgroups"}, + {"hostgroup_name", "hostgroups"}, + {"host", "hosts"}, + {"host_name", "hosts"}, + {"contact_groups", "contactgroups"}, + }, + 10) { + _init(); +} + +/** + * @brief For several keys, the parser of Hostescalation objects has a + * particular behavior. These behaviors are handled here. + * @param key The key to parse. + * @param value The value corresponding to the key + */ +bool hostescalation_helper::hook(std::string_view key, + const std::string_view& value) { + Hostescalation* obj = static_cast<Hostescalation*>(mut_obj()); + key = validate_key(key); + + if (key == "escalation_options") { + uint32_t options = action_he_none; + auto arr = absl::StrSplit(value, ','); + for (auto& v : arr) { + std::string_view vv = absl::StripAsciiWhitespace(v); + if (vv == "d" || vv == "down") + options |= action_he_down; + else if (vv == "u" || vv == "unreachable") + options |= action_he_unreachable; + else if (vv == "r" || vv == "recovery") + options |= action_he_recovery; + else if (vv == "n" || vv == "none") + options = action_he_none; + else if (vv == "a" || vv == "all") + options = action_he_down | action_he_unreachable | action_he_recovery; + else + return false; + } + obj->set_escalation_options(options); + return true; + } else if (key == "contactgroups") { + fill_string_group(obj->mutable_contactgroups(), value); + return true; + } else if (key == "hostgroups") { + fill_string_group(obj->mutable_hostgroups(), value); + return true; + } else if (key == "hosts") { + fill_string_group(obj->mutable_hosts(), value); + return true; + } + return false; +} + +/** + * @brief Check the validity of the Hostescalation object. + * + * @param err An error counter. + */ +void hostescalation_helper::check_validity(error_cnt& err) const { + const Hostescalation* o = static_cast<const Hostescalation*>(obj()); + + if (o->hosts().data().empty() && o->hostgroups().data().empty()) { + err.config_errors++; + throw msg_fmt( + "Host escalation is not attached to any host or host group (properties " + "'hosts' or 'hostgroups', respectively)"); + } +} + +/** + * @brief Initializer of the Hostescalation object, in other words set its + * default values. + */ +void hostescalation_helper::_init() { + Hostescalation* obj = static_cast<Hostescalation*>(mut_obj()); + obj->mutable_obj()->set_register_(true); + obj->set_escalation_options(action_he_none); + obj->set_first_notification(-2); + obj->set_last_notification(-2); + obj->set_notification_interval(0); +} +} // namespace com::centreon::engine::configuration diff --git a/common/engine_conf/hostescalation_helper.hh b/common/engine_conf/hostescalation_helper.hh new file mode 100644 index 00000000000..18f7751b66f --- /dev/null +++ b/common/engine_conf/hostescalation_helper.hh @@ -0,0 +1,46 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ +#ifndef CCE_CONFIGURATION_HOSTESCALATION +#define CCE_CONFIGURATION_HOSTESCALATION + +#include "common/engine_conf/message_helper.hh" +#include "common/engine_conf/state.pb.h" + +namespace com::centreon::engine::configuration { + +size_t hostescalation_key(const Hostescalation& he); + +/** + * @brief Helper for the Hostescalation message. The helper is instanciated + * just after a message is created. It provides default values for it and also + * several methods to help the developer to fill the message fields. + */ +class hostescalation_helper : public message_helper { + void _init(); + + public: + hostescalation_helper(Hostescalation* obj); + ~hostescalation_helper() noexcept = default; + void check_validity(error_cnt& err) const override; + + bool hook(std::string_view key, const std::string_view& value) override; +}; +} // namespace com::centreon::engine::configuration + +#endif /* !CCE_CONFIGURATION_HOSTESCALATION */ diff --git a/common/engine_conf/hostgroup_helper.cc b/common/engine_conf/hostgroup_helper.cc new file mode 100644 index 00000000000..f687475e472 --- /dev/null +++ b/common/engine_conf/hostgroup_helper.cc @@ -0,0 +1,79 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ +#include "common/engine_conf/hostgroup_helper.hh" + +#include "com/centreon/exceptions/msg_fmt.hh" + +using com::centreon::exceptions::msg_fmt; + +namespace com::centreon::engine::configuration { + +/** + * @brief Constructor from a Hostgroup object. + * + * @param obj The Hostgroup object on which this helper works. The helper is not + * the owner of this object. + */ +hostgroup_helper::hostgroup_helper(Hostgroup* obj) + : message_helper(object_type::hostgroup, obj, {}, 9) { + _init(); +} + +/** + * @brief For several keys, the parser of Hostgroup objects has a particular + * behavior. These behaviors are handled here. + * @param key The key to parse. + * @param value The value corresponding to the key + */ +bool hostgroup_helper::hook(std::string_view key, + const std::string_view& value) { + Hostgroup* obj = static_cast<Hostgroup*>(mut_obj()); + key = validate_key(key); + if (key == "members") { + fill_string_group(obj->mutable_members(), value); + return true; + } + return false; +} + +/** + * @brief Check the validity of the Hostgroup object. + * + * @param err An error counter. + */ +void hostgroup_helper::check_validity(error_cnt& err) const { + const Hostgroup* o = static_cast<const Hostgroup*>(obj()); + + if (o->obj().register_()) { + if (o->hostgroup_name().empty()) { + err.config_errors++; + throw msg_fmt("Host group has no name (property 'hostgroup_name')"); + } + } +} + +/** + * @brief Initializer of the Hostgroup object, in other words set its default + * values. + */ +void hostgroup_helper::_init() { + Hostgroup* obj = static_cast<Hostgroup*>(mut_obj()); + obj->mutable_obj()->set_register_(true); +} +} // namespace com::centreon::engine::configuration diff --git a/common/engine_conf/hostgroup_helper.hh b/common/engine_conf/hostgroup_helper.hh new file mode 100644 index 00000000000..93fb0d243ec --- /dev/null +++ b/common/engine_conf/hostgroup_helper.hh @@ -0,0 +1,44 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ +#ifndef CCE_CONFIGURATION_HOSTGROUP +#define CCE_CONFIGURATION_HOSTGROUP + +#include "common/engine_conf/message_helper.hh" +#include "common/engine_conf/state.pb.h" + +namespace com::centreon::engine::configuration { + +/** + * @brief Helper for the Hostgroup message. The helper is instanciated + * just after a message is created. It provides default values for it and also + * several methods to help the developer to fill the message fields. + */ +class hostgroup_helper : public message_helper { + void _init(); + + public: + hostgroup_helper(Hostgroup* obj); + ~hostgroup_helper() noexcept = default; + void check_validity(error_cnt& err) const override; + + bool hook(std::string_view key, const std::string_view& value) override; +}; +} // namespace com::centreon::engine::configuration + +#endif /* !CCE_CONFIGURATION_HOSTGROUP */ diff --git a/common/engine_conf/message_helper.cc b/common/engine_conf/message_helper.cc new file mode 100644 index 00000000000..8068dc16c20 --- /dev/null +++ b/common/engine_conf/message_helper.cc @@ -0,0 +1,451 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ +#include "common/engine_conf/message_helper.hh" + +#include <absl/strings/str_split.h> + +using ::google::protobuf::Descriptor; +using ::google::protobuf::FieldDescriptor; +using ::google::protobuf::Reflection; + +namespace com::centreon::engine::configuration { + +/** + * @brief Copy constructor of the base helper class. It contains basic methods + * to access/modify the message fields. This constructor is needed by the + * parser. + * + * @param other A reference to the helper to copy. + */ +message_helper::message_helper(const message_helper& other) + : _otype(other._otype), + _obj(other._obj), + _correspondence(other._correspondence), + _modified_field(other._modified_field), + _resolved(other._resolved) {} + +/** + * @brief Sugar function to fill a PairStringSet field in a Protobuf message + * from a string. This function is used when cfg files are read. + * + * @param grp The field to fill. + * @param value The value as string is a pair of strings seperated by a comma. + * + * @return A boolean that is True on success. + */ +bool fill_pair_string_group(PairStringSet* grp, const std::string_view& value) { + auto arr = absl::StrSplit(value, ','); + + bool first = true; + auto itfirst = arr.begin(); + if (itfirst == arr.end()) + return true; + + do { + auto itsecond = itfirst; + ++itsecond; + if (itsecond == arr.end()) + return false; + std::string_view v1 = absl::StripAsciiWhitespace(*itfirst); + std::string_view v2 = absl::StripAsciiWhitespace(*itsecond); + if (first) { + if (v1[0] == '+') { + grp->set_additive(true); + v1 = v1.substr(1); + } + first = false; + } + bool found = false; + for (auto& m : grp->data()) { + if (*itfirst == m.first() && *itsecond == m.second()) { + found = true; + break; + } + } + if (!found) { + auto* p = grp->mutable_data()->Add(); + p->set_first(v1.data(), v1.size()); + p->set_second(v2.data(), v2.size()); + } + itfirst = itsecond; + ++itfirst; + } while (itfirst != arr.end()); + return true; +} + +/** + * @brief Sugar function to fill a PairStringSet field in a Protobuf message + * from two strings. This function is used when cfg files are read. + * + * @param grp The field to fill. + * @param key The first value in the pair to fill. + * @param value The second value in the pair to fill. + * + * @return A boolean that is True on success. + */ +bool fill_pair_string_group(PairStringSet* grp, + const std::string_view& key, + const std::string_view& value) { + std::string_view v1 = absl::StripAsciiWhitespace(key); + std::string_view v2 = absl::StripAsciiWhitespace(value); + bool found = false; + for (auto& m : grp->data()) { + if (v1 == m.first() && v2 == m.second()) { + found = true; + break; + } + } + if (!found) { + auto* p = grp->mutable_data()->Add(); + p->set_first(v1.data(), v1.size()); + p->set_second(v2.data(), v2.size()); + } + return true; +} + +/** + * @brief Sugar function to fill a StringSet field in a Protobuf message + * from a string. This function is used when cfg files are read. + * + * @param grp The field to fill. + * @param value The value as string. + * + * @return A boolean that is True on success. + */ +void fill_string_group(StringSet* grp, const std::string_view& value) { + auto arr = absl::StrSplit(value, ','); + bool first = true; + for (std::string_view d : arr) { + d = absl::StripAsciiWhitespace(d); + if (first) { + if (d[0] == '+') { + grp->set_additive(true); + d = d.substr(1); + } + first = false; + } + bool found = false; + for (auto& v : grp->data()) { + if (v == d) { + found = true; + break; + } + } + if (!found) + grp->add_data(d.data(), d.size()); + } +} + +/** + * @brief Sugar function to fill a StringList field in a Protobuf message + * from a string. This function is used when cfg files are read. + * + * @param grp The field to fill. + * @param value The value as string. + * + * @return A boolean that is True on success. + */ +void fill_string_group(StringList* grp, const std::string_view& value) { + auto arr = absl::StrSplit(value, ','); + bool first = true; + for (std::string_view d : arr) { + d = absl::StripAsciiWhitespace(d); + if (first) { + if (d[0] == '+') { + grp->set_additive(true); + d = d.substr(1); + } + first = false; + } + grp->add_data(d.data(), d.size()); + } +} + +/** + * @brief Parse host notification options as string and set an uint32_t to + * the corresponding values. + * + * @param options A pointer to the uint32_t to set/ + * @param value A string of options seperated by a comma. + * + * @return True on success. + */ +bool fill_host_notification_options(uint32_t* options, + const std::string_view& value) { + uint32_t tmp_options = action_hst_none; + auto arr = absl::StrSplit(value, ','); + for (auto& v : arr) { + std::string_view value = absl::StripAsciiWhitespace(v); + if (value == "d" || value == "down") + tmp_options |= action_hst_down; + else if (value == "u" || value == "unreachable") + tmp_options |= action_hst_unreachable; + else if (value == "r" || value == "recovery") + tmp_options |= action_hst_up; + else if (value == "f" || value == "flapping") + tmp_options |= action_hst_flapping; + else if (value == "s" || value == "downtime") + tmp_options |= action_hst_downtime; + else if (value == "n" || value == "none") + tmp_options = action_hst_none; + else if (value == "a" || value == "all") + tmp_options = action_hst_down | action_hst_unreachable | action_hst_up | + action_hst_flapping | action_hst_downtime; + else + return false; + } + *options = tmp_options; + return true; +} + +/** + * @brief Parse host notification options as string and set an uint32_t to + * the corresponding values. + * + * @param options A pointer to the uint32_t to set/ + * @param value A string of options seperated by a comma. + * + * @return True on success. + */ +bool fill_service_notification_options(uint32_t* options, + const std::string_view& value) { + uint32_t tmp_options = action_svc_none; + auto arr = absl::StrSplit(value, ','); + for (auto& v : arr) { + std::string_view value = absl::StripAsciiWhitespace(v); + if (value == "u" || value == "unknown") + tmp_options |= action_svc_unknown; + else if (value == "w" || value == "warning") + tmp_options |= action_svc_warning; + else if (value == "c" || value == "critical") + tmp_options |= action_svc_critical; + else if (value == "r" || value == "recovery") + tmp_options |= action_svc_ok; + else if (value == "f" || value == "flapping") + tmp_options |= action_svc_flapping; + else if (value == "s" || value == "downtime") + tmp_options |= action_svc_downtime; + else if (value == "n" || value == "none") + tmp_options = action_svc_none; + else if (value == "a" || value == "all") + tmp_options = action_svc_unknown | action_svc_warning | + action_svc_critical | action_svc_ok | action_svc_flapping | + action_svc_downtime; + else + return false; + } + *options = tmp_options; + return true; +} + +/** + * @brief In some Engine configuration objects, several keys are possible for a + * same field. This function returns the good key to access the protobuf message + * field from another one. For example, "hosts", "hostname" may design the same + * field named hostname in the protobuf message. This function returns + * "hostname" for "hosts" or "hostname". + * + * @param key The key to check. + * + * @return The key used in the message. + */ +std::string_view message_helper::validate_key( + const std::string_view& key) const { + std::string_view retval; + auto it = _correspondence.find(key); + if (it != _correspondence.end()) + retval = it->second; + else + retval = key; + return retval; +} + +/** + * @brief This function does nothing but it is derived in several message + * helpers to insert custom variables. + * + * @param [[maybe_unused]] The name of the customvariable. + * @param [[maybe_unused]] Its value. + * + * @return True on success. + */ +bool message_helper::insert_customvariable(std::string_view key + [[maybe_unused]], + std::string_view value + [[maybe_unused]]) { + return false; +} + +/** + * @brief Set the value given as a string to the object key. If the key does + * not exist, the correspondence table may be used to find a replacement of + * the key. The function converts the value to the appropriate type. + * + * Another important point is that many configuration objects contain the Object + * obj message (something like an inheritance). This message contains three + * field names, use and register that are important for templating. If keys are + * one of these names, the function tries to work directly with the obj message. + * + * @tparam T The type of the message containing the object key. + * @param msg The message containing the object key. + * @param key The key to localize the object to set. + * @param value The value as string that will be converted to the good type. + * @param correspondence A hash table giving traductions from keys to others. + * If a key fails, correspondence is used to find a new replacement key. + * + * @return true on success. + */ +bool message_helper::set(const std::string_view& key, + const std::string_view& value) { + Message* msg = mut_obj(); + const Descriptor* desc = msg->GetDescriptor(); + const FieldDescriptor* f; + const Reflection* refl; + + /* Cases where we have to work on the obj Object (the parent object) */ + if (key == "name" || key == "register" || key == "use") { + f = desc->FindFieldByName("obj"); + if (f) { + refl = msg->GetReflection(); + Object* obj = static_cast<Object*>(refl->MutableMessage(msg, f)); + + /* Optimization to avoid a new string comparaison */ + switch (key[0]) { + case 'n': // name + obj->set_name(std::string(value.data(), value.size())); + break; + case 'r': { // register + bool value_b; + if (!absl::SimpleAtob(value, &value_b)) + return false; + else + obj->set_register_(value_b); + } break; + case 'u': { // use + obj->mutable_use()->Clear(); + auto arr = absl::StrSplit(value, ','); + for (auto& t : arr) { + std::string v{absl::StripAsciiWhitespace(t)}; + obj->mutable_use()->Add(std::move(v)); + } + } break; + } + return true; + } + } + + f = desc->FindFieldByName(std::string(key.data(), key.size())); + if (f == nullptr) { + auto it = correspondence().find(key); + if (it != correspondence().end()) + f = desc->FindFieldByName(it->second); + if (f == nullptr) + return false; + } + refl = msg->GetReflection(); + switch (f->type()) { + case FieldDescriptor::TYPE_BOOL: { + bool val; + if (absl::SimpleAtob(value, &val)) { + refl->SetBool(static_cast<Message*>(msg), f, val); + set_changed(f->number()); + return true; + } else + return false; + } break; + case FieldDescriptor::TYPE_INT32: { + int32_t val; + if (absl::SimpleAtoi(value, &val)) { + refl->SetInt32(static_cast<Message*>(msg), f, val); + set_changed(f->number()); + return true; + } else + return false; + } break; + case FieldDescriptor::TYPE_UINT32: { + uint32_t val; + if (absl::SimpleAtoi(value, &val)) { + refl->SetUInt32(static_cast<Message*>(msg), f, val); + set_changed(f->number()); + return true; + } else + return false; + } break; + case FieldDescriptor::TYPE_UINT64: { + uint64_t val; + if (absl::SimpleAtoi(value, &val)) { + refl->SetUInt64(static_cast<Message*>(msg), f, val); + set_changed(f->number()); + return true; + } else + return false; + } break; + case FieldDescriptor::TYPE_DOUBLE: { + double val; + if (absl::SimpleAtod(value, &val)) { + refl->SetDouble(static_cast<Message*>(msg), f, val); + set_changed(f->number()); + return true; + } else + return false; + } break; + case FieldDescriptor::TYPE_STRING: + if (f->is_repeated()) { + refl->AddString(static_cast<Message*>(msg), f, + std::string(value.data(), value.size())); + } else { + refl->SetString(static_cast<Message*>(msg), f, + std::string(value.data(), value.size())); + } + set_changed(f->number()); + return true; + case FieldDescriptor::TYPE_MESSAGE: + if (!f->is_repeated()) { + Message* m = refl->MutableMessage(msg, f); + const Descriptor* d = m->GetDescriptor(); + + if (d && d->name() == "StringSet") { + StringSet* set = + static_cast<StringSet*>(refl->MutableMessage(msg, f)); + fill_string_group(set, value); + set_changed(f->number()); + return true; + } else if (d && d->name() == "StringList") { + StringList* lst = + static_cast<StringList*>(refl->MutableMessage(msg, f)); + fill_string_group(lst, value); + set_changed(f->number()); + return true; + } + } + case FieldDescriptor::TYPE_ENUM: { + auto* v = f->enum_type()->FindValueByName( + std::string(value.data(), value.size())); + if (v) + refl->SetEnumValue(msg, f, v->number()); + else + return false; + } break; + default: + return false; + } + return true; +} + +} // namespace com::centreon::engine::configuration diff --git a/common/engine_conf/message_helper.hh b/common/engine_conf/message_helper.hh new file mode 100644 index 00000000000..69b73fc9b71 --- /dev/null +++ b/common/engine_conf/message_helper.hh @@ -0,0 +1,230 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#ifndef CCE_CONFIGURATION_MESSAGE_HELPER_HH +#define CCE_CONFIGURATION_MESSAGE_HELPER_HH +#include <absl/container/flat_hash_map.h> +#include <absl/strings/str_split.h> +#include "common/engine_conf/state.pb.h" + +#ifdef LEGACY_CONF +#error This library should not be compiled. +#endif + +namespace com::centreon::engine::configuration { + +/** + * @brief Error counter, it contains two attributes, one for warnings and + * another for errors. + */ +struct error_cnt { + uint32_t config_warnings = 0; + uint32_t config_errors = 0; +}; + +/* Forward declarations */ +class command_helper; +class connector_helper; +class contact_helper; +class contactgroup_helper; +class host_helper; +class hostdependency_helper; +class hostescalation_helper; +class hostgroup_helper; +class service_helper; +class servicedependency_helper; +class serviceescalation_helper; +class servicegroup_helper; +class timeperiod_helper; +class anomalydetection_helper; +class severity_helper; +class tag_helper; +class state_helper; + +using ::google::protobuf::Message; + +bool fill_pair_string_group(PairStringSet* grp, const std::string_view& value); +bool fill_pair_string_group(PairStringSet* grp, + const std::string_view& key, + const std::string_view& value); +void fill_string_group(StringList* grp, const std::string_view& value); +void fill_string_group(StringSet* grp, const std::string_view& value); +bool fill_host_notification_options(uint32_t* options, + const std::string_view& value); +bool fill_service_notification_options(uint32_t* options, + const std::string_view& value); + +/** + * @brief The base message helper used by every helpers. It defines the common + * methods. + * + */ +class message_helper { + public: + enum object_type { + command = 0, + connector = 1, + contact = 2, + contactgroup = 3, + host = 4, + hostdependency = 5, + hostescalation = 6, + hostgroup = 8, + service = 9, + servicedependency = 10, + serviceescalation = 11, + servicegroup = 13, + timeperiod = 14, + anomalydetection = 15, + severity = 16, + tag = 17, + state = 18, + }; + + private: + const object_type _otype; + Message* _obj; + const absl::flat_hash_map<std::string, std::string> _correspondence; + std::vector<bool> _modified_field; + bool _resolved = false; + + public: + message_helper(object_type otype, + Message* obj, + absl::flat_hash_map<std::string, std::string>&& correspondence, + size_t field_size) + : _otype(otype), + _obj(obj), + _correspondence{ + std::forward<absl::flat_hash_map<std::string, std::string>>( + correspondence)}, + _modified_field(field_size, false) {} + message_helper(const message_helper& other); + message_helper() = delete; + message_helper& operator=(const message_helper&) = delete; + virtual ~message_helper() noexcept = default; + const absl::flat_hash_map<std::string, std::string>& correspondence() const { + return _correspondence; + } + object_type otype() const { return _otype; } + Message* mut_obj() { return _obj; } + const Message* obj() const { return _obj; } + void set_obj(Message* obj) { _obj = obj; } + void set_changed(int num) { _modified_field[num] = true; } + bool changed(int num) const { return _modified_field[num]; } + bool resolved() const { return _resolved; } + void resolve() { _resolved = true; } + + /** + * @brief For several keys, the parser of objects has some particular + * behaviors. These behaviors are handled here. + * @param key The key to parse. + * @param value The value corresponding to the key + * + * @return True on success. + */ + virtual bool hook(std::string_view key [[maybe_unused]], + const std::string_view& value [[maybe_unused]]) { + return false; + } + virtual void check_validity(error_cnt& err [[maybe_unused]]) const {} + std::string_view validate_key(const std::string_view& key) const; + virtual bool insert_customvariable(std::string_view key, + std::string_view value); + template <typename T> + static std::unique_ptr<T> clone(const T& other, Message* obj) { + std::unique_ptr<T> retval; + switch (other._otype) { + case command: + retval = std::make_unique<command_helper>( + static_cast<const command_helper&>(other)); + break; + case connector: + retval = std::make_unique<connector_helper>( + static_cast<const connector_helper&>(other)); + break; + case contact: + retval = std::make_unique<contact_helper>( + static_cast<const contact_helper&>(other)); + break; + case contactgroup: + retval = std::make_unique<contactgroup_helper>( + static_cast<const contactgroup_helper&>(other)); + break; + case host: + retval = std::make_unique<host_helper>( + static_cast<const host_helper&>(other)); + break; + case hostdependency: + retval = std::make_unique<hostdependency_helper>( + static_cast<const hostdependency_helper&>(other)); + break; + case hostescalation: + retval = std::make_unique<hostescalation_helper>( + static_cast<const hostescalation_helper&>(other)); + break; + case hostgroup: + retval = std::make_unique<hostgroup_helper>( + static_cast<const hostgroup_helper&>(other)); + break; + case service: + retval = std::make_unique<service_helper>( + static_cast<const service_helper&>(other)); + break; + case servicedependency: + retval = std::make_unique<servicedependency_helper>( + static_cast<const servicedependency_helper&>(other)); + break; + case serviceescalation: + retval = std::make_unique<serviceescalation_helper>( + static_cast<const serviceescalation_helper&>(other)); + break; + case servicegroup: + retval = std::make_unique<servicegroup_helper>( + static_cast<const servicegroup_helper&>(other)); + break; + case timeperiod: + retval = std::make_unique<timeperiod_helper>( + static_cast<const timeperiod_helper&>(other)); + break; + case anomalydetection: + retval = std::make_unique<anomalydetection_helper>( + static_cast<const anomalydetection_helper&>(other)); + break; + case severity: + retval = std::make_unique<severity_helper>( + static_cast<const severity_helper&>(other)); + break; + case tag: + retval = + std::make_unique<tag_helper>(static_cast<const tag_helper&>(other)); + break; + case state: + retval = std::make_unique<state_helper>( + static_cast<const state_helper&>(other)); + break; + } + retval->_obj = obj; + return retval; + } + bool set(const std::string_view& key, const std::string_view& value); +}; +} // namespace com::centreon::engine::configuration + +#endif /* !CCE_CONFIGURATION_MESSAGE_HELPER_HH */ diff --git a/common/engine_conf/parser.cc b/common/engine_conf/parser.cc new file mode 100644 index 00000000000..783b21b2c9d --- /dev/null +++ b/common/engine_conf/parser.cc @@ -0,0 +1,757 @@ +/** + * Copyright 2011-2014,2017-2024 Centreon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ +#include "parser.hh" +#include "com/centreon/exceptions/msg_fmt.hh" +#include "com/centreon/io/directory_entry.hh" +#include "common/log_v2/log_v2.hh" + +#include "anomalydetection_helper.hh" +#include "command_helper.hh" +#include "connector_helper.hh" +#include "contact_helper.hh" +#include "contactgroup_helper.hh" +#include "host_helper.hh" +#include "hostdependency_helper.hh" +#include "hostescalation_helper.hh" +#include "hostgroup_helper.hh" +#include "message_helper.hh" +#include "service_helper.hh" +#include "servicedependency_helper.hh" +#include "serviceescalation_helper.hh" +#include "servicegroup_helper.hh" +#include "severity_helper.hh" +#include "state_helper.hh" +#include "tag_helper.hh" +#include "timeperiod_helper.hh" + +using namespace com::centreon; +using namespace com::centreon::engine::configuration; +using namespace com::centreon::io; + +using com::centreon::common::log_v2::log_v2; +using com::centreon::exceptions::msg_fmt; +using ::google::protobuf::Descriptor; +using ::google::protobuf::FieldDescriptor; +using ::google::protobuf::Message; +using ::google::protobuf::Reflection; + +/** + * @brief Reads the content of a text file and returns it in an std::string. + * + * @param file_path The file to read. + * + * @return The content as an std::string. + */ +static std::string read_file_content(const std::filesystem::path& file_path) { + std::ifstream in(file_path, std::ios::in); + std::string retval; + if (in) { + in.seekg(0, std::ios::end); + retval.resize(in.tellg()); + in.seekg(0, std::ios::beg); + in.read(&retval[0], retval.size()); + in.close(); + } else + throw msg_fmt("Parsing of resource file failed: can't open file '{}': {}", + file_path.string(), strerror(errno)); + return retval; +} + +/** + * Default constructor. + * + */ +parser::parser() : _logger{log_v2::instance().get(log_v2::CONFIG)} {} + +/** + * Parse configuration file. + * + * @param[in] path The configuration file path. + * @param[in] pb_config The state configuration to fill. + * @param[out] err The config warnings/errors counter. + */ +void parser::parse(std::string const& path, State* pb_config, error_cnt& err) { + /* Parse the global configuration file. */ + auto helper = std::make_unique<state_helper>(pb_config); + _pb_helper[pb_config] = std::move(helper); + _parse_global_configuration(path, pb_config); + + // parse configuration files. + _apply(pb_config->cfg_file(), pb_config, &parser::_parse_object_definitions); + // parse resource files. + _apply(pb_config->resource_file(), pb_config, &parser::_parse_resource_file); + + // parse configuration directories. + _apply(pb_config->cfg_dir(), pb_config, + &parser::_parse_directory_configuration); + + // Apply template. + _resolve_template(pb_config, err); + + _cleanup(pb_config); +} + +/** + * @brief Clean the configuration: + * * remove template objects. + * + * @param pb_config + */ +void parser::_cleanup(State* pb_config) { + int i = 0; + for (auto it = pb_config->mutable_services()->begin(); + it != pb_config->mutable_services()->end();) { + if (!it->obj().register_()) { + pb_config->mutable_services()->erase(it); + it = pb_config->mutable_services()->begin() + i; + } else { + ++it; + ++i; + } + } + i = 0; + for (auto it = pb_config->mutable_anomalydetections()->begin(); + it != pb_config->mutable_anomalydetections()->end();) { + if (!it->obj().register_()) { + pb_config->mutable_anomalydetections()->erase(it); + it = pb_config->mutable_anomalydetections()->begin() + i; + } else { + ++it; + ++i; + } + } +} + +/** + * Parse the directory configuration. + * + * @param[in] path The directory path. + */ +void parser::_parse_directory_configuration(const std::string& path, + State* pb_config) { + for (auto& entry : std::filesystem::directory_iterator(path)) { + if (entry.is_regular_file() && entry.path().extension() == ".cfg") + _parse_object_definitions(entry.path().string(), pb_config); + } +} + +/** + * Parse the global configuration file. + * + * @param[in] path The configuration path. + */ +void parser::_parse_global_configuration(const std::string& path, + State* pb_config) { + _logger->info("Reading main configuration file '{}'.", path); + + std::string content = read_file_content(path); + + pb_config->set_cfg_main(path); + _current_line = 0; + _current_path = path; + + auto tab{absl::StrSplit(content, '\n')}; + state_helper* cfg_helper = + static_cast<state_helper*>(_pb_helper[pb_config].get()); + for (auto it = tab.begin(); it != tab.end(); ++it) { + std::string_view l = absl::StripAsciiWhitespace(*it); + if (l.empty() || l[0] == '#') + continue; + std::pair<std::string_view, std::string_view> p = + absl::StrSplit(l, absl::MaxSplits('=', 1)); + p.first = absl::StripTrailingAsciiWhitespace(p.first); + p.second = absl::StripLeadingAsciiWhitespace(p.second); + bool retval = false; + /* particular cases with hook */ + retval = cfg_helper->hook(p.first, p.second); + if (!retval) { + if (!cfg_helper->set_global(p.first, p.second)) + _logger->error("Unable to parse '{}' key with value '{}'", p.first, + p.second); + } + } +} + +/** + * @brief Parse objects files (services.cfg, hosts.cfg, timeperiods.cfg... + * + * This function almost uses protobuf reflection to set values but it may fail + * because of the syntax used in these files that can be a little different + * from the message format. + * + * Two mechanisms are used to complete the reflection. + * * A hastable <string, string> named correspondence is used in case of several + * keys to access to the same value. This is, for example, the case for + * host_id which is historically also named _HOST_ID. + * * A std::function<bool(string_view_string_view) can also be defined in + * several cases to make special stuffs. For example, we use it for timeperiod + * object to set its timeranges. + * + * @param path The file to parse. + * @param pb_config The configuration to complete. + */ +void parser::_parse_object_definitions(const std::string& path, + State* pb_config) { + _logger->info("Processing object config file '{}'", path); + + std::string content = read_file_content(path); + + auto tab{absl::StrSplit(content, '\n')}; + std::string ll; + bool append_to_previous_line = false; + std::unique_ptr<Message> msg; + std::unique_ptr<message_helper> msg_helper; + + int current_line = 1; + std::string type; + + for (auto it = tab.begin(); it != tab.end(); ++it, current_line++) { + std::string_view l = absl::StripAsciiWhitespace(*it); + if (l.empty() || l[0] == '#' || l[0] == ';') + continue; + + /* Multiline */ + if (append_to_previous_line) { + if (l[l.size() - 1] == '\\') { + ll.append(l.data(), l.size() - 1); + continue; + } else { + ll.append(l.data(), l.size()); + append_to_previous_line = false; + l = ll; + } + } else if (l[l.size() - 1] == '\\') { + ll = std::string(l.data(), l.size() - 1); + append_to_previous_line = true; + continue; + } + + if (msg) { + if (l.empty()) + continue; + /* is it time to close the definition? */ + if (l == "}") { + const Descriptor* desc = msg->GetDescriptor(); + const FieldDescriptor* f = desc->FindFieldByName("obj"); + const Reflection* refl = msg->GetReflection(); + if (f) { + const Object& obj = + *static_cast<const Object*>(&refl->GetMessage(*msg, f)); + auto otype = msg_helper->otype(); + _pb_helper[msg.get()] = std::move(msg_helper); + if (!obj.name().empty()) { + pb_map_object& tmpl = _pb_templates[otype]; + auto it = tmpl.find(obj.name()); + if (it != tmpl.end()) + throw msg_fmt("Parsing of '{}' failed {}: {} already exists", + type, "file_info" /*_get_file_info(obj.get()) */, + obj.name()); + if (!obj.register_()) + tmpl[obj.name()] = std::move(msg); + else { + auto copy = std::unique_ptr<Message>(msg->New()); + copy->CopyFrom(*msg); + _pb_helper[copy.get()] = + message_helper::clone(*_pb_helper[msg.get()], copy.get()); + tmpl[obj.name()] = std::move(copy); + } + } + if (obj.register_()) { + switch (otype) { + case message_helper::contact: + pb_config->mutable_contacts()->AddAllocated( + static_cast<Contact*>(msg.release())); + break; + case message_helper::host: + pb_config->mutable_hosts()->AddAllocated( + static_cast<Host*>(msg.release())); + break; + case message_helper::service: + pb_config->mutable_services()->AddAllocated( + static_cast<Service*>(msg.release())); + break; + case message_helper::anomalydetection: + pb_config->mutable_anomalydetections()->AddAllocated( + static_cast<Anomalydetection*>(msg.release())); + break; + case message_helper::hostdependency: + pb_config->mutable_hostdependencies()->AddAllocated( + static_cast<Hostdependency*>(msg.release())); + break; + case message_helper::servicedependency: + pb_config->mutable_servicedependencies()->AddAllocated( + static_cast<Servicedependency*>(msg.release())); + break; + case message_helper::timeperiod: + pb_config->mutable_timeperiods()->AddAllocated( + static_cast<Timeperiod*>(msg.release())); + break; + case message_helper::command: + pb_config->mutable_commands()->AddAllocated( + static_cast<Command*>(msg.release())); + break; + case message_helper::hostgroup: + pb_config->mutable_hostgroups()->AddAllocated( + static_cast<Hostgroup*>(msg.release())); + break; + case message_helper::servicegroup: + pb_config->mutable_servicegroups()->AddAllocated( + static_cast<Servicegroup*>(msg.release())); + break; + case message_helper::tag: + pb_config->mutable_tags()->AddAllocated( + static_cast<Tag*>(msg.release())); + break; + case message_helper::contactgroup: + pb_config->mutable_contactgroups()->AddAllocated( + static_cast<Contactgroup*>(msg.release())); + break; + case message_helper::connector: + pb_config->mutable_connectors()->AddAllocated( + static_cast<Connector*>(msg.release())); + break; + case message_helper::severity: + pb_config->mutable_severities()->AddAllocated( + static_cast<Severity*>(msg.release())); + break; + case message_helper::serviceescalation: + pb_config->mutable_serviceescalations()->AddAllocated( + static_cast<Serviceescalation*>(msg.release())); + break; + case message_helper::hostescalation: + pb_config->mutable_hostescalations()->AddAllocated( + static_cast<Hostescalation*>(msg.release())); + break; + default: + _logger->critical("Attempt to add an object of unknown type"); + } + } + } + msg = nullptr; + } else { + /* Main part where keys/values are read */ + /* ------------------------------------ */ + size_t pos = l.find_first_of(" \t"); + std::string_view key = l.substr(0, pos); + if (pos != std::string::npos) { + l.remove_prefix(pos); + l = absl::StripLeadingAsciiWhitespace(l); + } else + l = {}; + + bool retval = false; + /* particular cases with hook */ + retval = msg_helper->hook(key, l); + + if (!retval) { + /* Classical part */ + if (!msg_helper->set(key, l)) { + if (!msg_helper->insert_customvariable(key, l)) + throw msg_fmt( + "Unable to parse '{}' key with value '{}' in message of type " + "'{}'", + key, l, type); + } + } + } + } else { + if (!absl::StartsWith(l, "define") || !std::isspace(l[6])) + throw msg_fmt( + "Parsing of object definition failed in file '{}' at line {}: " + "Unexpected start definition", + path, current_line); + /* Let's remove the first 6 characters ("define") */ + l = absl::StripLeadingAsciiWhitespace(l.substr(6)); + if (l.empty() || l[l.size() - 1] != '{') + throw msg_fmt( + "Parsing of object definition failed in file '{}' at line {}; " + "unexpected start definition", + path, current_line); + l = absl::StripTrailingAsciiWhitespace(l.substr(0, l.size() - 1)); + type = std::string(l.data(), l.size()); + if (type == "contact") { + msg = std::make_unique<Contact>(); + msg_helper = + std::make_unique<contact_helper>(static_cast<Contact*>(msg.get())); + } else if (type == "host") { + msg = std::make_unique<Host>(); + msg_helper = + std::make_unique<host_helper>(static_cast<Host*>(msg.get())); + } else if (type == "service") { + msg = std::make_unique<Service>(); + msg_helper = + std::make_unique<service_helper>(static_cast<Service*>(msg.get())); + } else if (type == "anomalydetection") { + msg = std::make_unique<Anomalydetection>(); + msg_helper = std::make_unique<anomalydetection_helper>( + static_cast<Anomalydetection*>(msg.get())); + } else if (type == "hostdependency") { + msg = std::make_unique<Hostdependency>(); + msg_helper = std::make_unique<hostdependency_helper>( + static_cast<Hostdependency*>(msg.get())); + } else if (type == "servicedependency") { + msg = std::make_unique<Servicedependency>(); + msg_helper = std::make_unique<servicedependency_helper>( + static_cast<Servicedependency*>(msg.get())); + } else if (type == "timeperiod") { + msg = std::make_unique<Timeperiod>(); + msg_helper = std::make_unique<timeperiod_helper>( + static_cast<Timeperiod*>(msg.get())); + } else if (type == "command") { + msg = std::make_unique<Command>(); + msg_helper = + std::make_unique<command_helper>(static_cast<Command*>(msg.get())); + } else if (type == "hostgroup") { + msg = std::make_unique<Hostgroup>(); + msg_helper = std::make_unique<hostgroup_helper>( + static_cast<Hostgroup*>(msg.get())); + } else if (type == "servicegroup") { + msg = std::make_unique<Servicegroup>(); + msg_helper = std::make_unique<servicegroup_helper>( + static_cast<Servicegroup*>(msg.get())); + } else if (type == "tag") { + msg = std::make_unique<Tag>(); + msg_helper = std::make_unique<tag_helper>(static_cast<Tag*>(msg.get())); + } else if (type == "contactgroup") { + msg = std::make_unique<Contactgroup>(); + msg_helper = std::make_unique<contactgroup_helper>( + static_cast<Contactgroup*>(msg.get())); + } else if (type == "connector") { + msg = std::make_unique<Connector>(); + msg_helper = std::make_unique<connector_helper>( + static_cast<Connector*>(msg.get())); + } else if (type == "severity") { + msg = std::make_unique<Severity>(); + msg_helper = std::make_unique<severity_helper>( + static_cast<Severity*>(msg.get())); + } else if (type == "serviceescalation") { + msg = std::make_unique<Serviceescalation>(); + msg_helper = std::make_unique<serviceescalation_helper>( + static_cast<Serviceescalation*>(msg.get())); + } else if (type == "hostescalation") { + msg = std::make_unique<Hostescalation>(); + msg_helper = std::make_unique<hostescalation_helper>( + static_cast<Hostescalation*>(msg.get())); + } else { + _logger->error("Type '{}' not yet supported by the parser", type); + assert(1 == 18); + } + } + } +} + +/** + * Parse the resource file. + * + * @param[in] path The resource file path. + */ +void parser::_parse_resource_file(const std::string& path, State* pb_config) { + _logger->info("Reading resource file '{}'", path); + + std::string content = read_file_content(path); + + auto tab{absl::StrSplit(content, '\n')}; + int current_line = 1; + for (auto it = tab.begin(); it != tab.end(); ++it, current_line++) { + std::string_view l = absl::StripLeadingAsciiWhitespace(*it); + if (l.empty() || l[0] == '#' || l[0] == ';') + continue; + std::pair<std::string_view, std::string_view> p = + absl::StrSplit(l, absl::MaxSplits('=', 1)); + p.first = absl::StripTrailingAsciiWhitespace(p.first); + p.second = absl::StripLeadingAsciiWhitespace(p.second); + if (p.first.size() >= 3 && p.first[0] == '$' && + p.first[p.first.size() - 1] == '$') { + p.first = p.first.substr(1, p.first.size() - 2); + (*pb_config + ->mutable_users())[std::string(p.first.data(), p.first.size())] = + std::string(p.second.data(), p.second.size()); + } else + throw msg_fmt("Invalid user key '{}'", p.first); + } +} + +/** + * @brief For each type of object in the State, templates are resolved that is + * to say, children inherite from parents properties. + * + * @param pb_config The State containing all the object to handle. + * @param err The config warnings/errors counter. + */ +void parser::_resolve_template(State* pb_config, error_cnt& err) { + for (Command& c : *pb_config->mutable_commands()) + _resolve_template(_pb_helper[&c], _pb_templates[command]); + + for (Connector& c : *pb_config->mutable_connectors()) + _resolve_template(_pb_helper[&c], _pb_templates[connector]); + + for (Contact& c : *pb_config->mutable_contacts()) + _resolve_template(_pb_helper[&c], _pb_templates[contact]); + + for (Contactgroup& cg : *pb_config->mutable_contactgroups()) + _resolve_template(_pb_helper[&cg], _pb_templates[contactgroup]); + + for (Host& h : *pb_config->mutable_hosts()) + _resolve_template(_pb_helper[&h], _pb_templates[host]); + + for (Service& s : *pb_config->mutable_services()) + _resolve_template(_pb_helper[&s], _pb_templates[service]); + + for (Anomalydetection& a : *pb_config->mutable_anomalydetections()) + _resolve_template(_pb_helper[&a], _pb_templates[anomalydetection]); + + for (Serviceescalation& se : *pb_config->mutable_serviceescalations()) + _resolve_template(_pb_helper[&se], _pb_templates[serviceescalation]); + + for (Hostescalation& he : *pb_config->mutable_hostescalations()) + _resolve_template(_pb_helper[&he], _pb_templates[hostescalation]); + + for (const Command& c : pb_config->commands()) + _pb_helper.at(&c)->check_validity(err); + + for (const Contact& c : pb_config->contacts()) + _pb_helper.at(&c)->check_validity(err); + + for (const Contactgroup& cg : pb_config->contactgroups()) + _pb_helper.at(&cg)->check_validity(err); + + for (const Host& h : pb_config->hosts()) + _pb_helper.at(&h)->check_validity(err); + + for (const Hostdependency& hd : pb_config->hostdependencies()) + _pb_helper.at(&hd)->check_validity(err); + + for (const Hostescalation& he : pb_config->hostescalations()) + _pb_helper.at(&he)->check_validity(err); + + for (const Hostgroup& hg : pb_config->hostgroups()) + _pb_helper.at(&hg)->check_validity(err); + + for (const Service& s : pb_config->services()) + _pb_helper.at(&s)->check_validity(err); + + for (const Hostdependency& hd : pb_config->hostdependencies()) + _pb_helper.at(&hd)->check_validity(err); + + for (const Servicedependency& sd : pb_config->servicedependencies()) + _pb_helper.at(&sd)->check_validity(err); + + for (const Servicegroup& sg : pb_config->servicegroups()) + _pb_helper.at(&sg)->check_validity(err); + + for (const Timeperiod& t : pb_config->timeperiods()) + _pb_helper.at(&t)->check_validity(err); + + for (const Anomalydetection& a : pb_config->anomalydetections()) + _pb_helper.at(&a)->check_validity(err); + + for (const Tag& t : pb_config->tags()) + _pb_helper.at(&t)->check_validity(err); + + for (const Servicegroup& sg : pb_config->servicegroups()) + _pb_helper.at(&sg)->check_validity(err); + + for (const Severity& sv : pb_config->severities()) + _pb_helper.at(&sv)->check_validity(err); + + for (const Tag& t : pb_config->tags()) + _pb_helper.at(&t)->check_validity(err); + + for (const Serviceescalation& se : pb_config->serviceescalations()) + _pb_helper.at(&se)->check_validity(err); + + for (const Hostescalation& he : pb_config->hostescalations()) + _pb_helper.at(&he)->check_validity(err); + + for (const Connector& c : pb_config->connectors()) + _pb_helper.at(&c)->check_validity(err); +} + +/** + * @brief Resolvers a message given by its helper and using the given templates. + * + * @param msg_helper The message helper. + * @param tmpls The templates to use. + */ +void parser::_resolve_template(std::unique_ptr<message_helper>& msg_helper, + const pb_map_object& tmpls) { + if (msg_helper->resolved()) + return; + Message* msg = msg_helper->mut_obj(); + + msg_helper->resolve(); + const Descriptor* desc = msg->GetDescriptor(); + const FieldDescriptor* f = desc->FindFieldByName("obj"); + const Reflection* refl = msg->GetReflection(); + if (!f) + return; + + Object* obj = static_cast<Object*>(refl->MutableMessage(msg, f)); + for (const std::string& u : obj->use()) { + auto it = tmpls.find(u); + if (it == tmpls.end()) + throw msg_fmt("Cannot merge object of type '{}'", u); + _resolve_template(_pb_helper[it->second.get()], tmpls); + _merge(msg_helper, it->second.get()); + } +} + +void parser::_merge(std::unique_ptr<message_helper>& msg_helper, + Message* tmpl) { + Message* msg = msg_helper->mut_obj(); + const Descriptor* desc = msg->GetDescriptor(); + const Reflection* refl = msg->GetReflection(); + std::string tmp_str; + + for (int i = 0; i < desc->field_count(); ++i) { + const FieldDescriptor* f = desc->field(i); + if (f->name() != "obj") { + /* Optional? If not defined in template, we continue. */ + const auto* oof = f->containing_oneof(); + if (oof) { + if (!refl->GetOneofFieldDescriptor(*tmpl, oof)) + continue; + } + + if ((oof && !refl->GetOneofFieldDescriptor(*msg, oof)) || + !msg_helper->changed(f->number())) { + if (f->is_repeated()) { + switch (f->cpp_type()) { + case FieldDescriptor::CPPTYPE_STRING: { + size_t count = refl->FieldSize(*tmpl, f); + for (size_t j = 0; j < count; ++j) { + const std::string& s = + refl->GetRepeatedStringReference(*tmpl, f, j, &tmp_str); + size_t count_msg = refl->FieldSize(*msg, f); + std::string tmp_str1; + bool found = false; + for (size_t k = 0; k < count_msg; ++k) { + const std::string& s1 = + refl->GetRepeatedStringReference(*msg, f, k, &tmp_str1); + if (s1 == s) { + found = true; + break; + } + } + if (!found) + refl->AddString(msg, f, s); + } + } break; + case FieldDescriptor::CPPTYPE_MESSAGE: { + size_t count = refl->FieldSize(*tmpl, f); + for (size_t j = 0; j < count; ++j) { + const Message& m = refl->GetRepeatedMessage(*tmpl, f, j); + const Descriptor* d = m.GetDescriptor(); + size_t count_msg = refl->FieldSize(*msg, f); + bool found = false; + for (size_t k = 0; k < count_msg; ++k) { + const Message& m1 = refl->GetRepeatedMessage(*msg, f, k); + const Descriptor* d1 = m1.GetDescriptor(); + if (d && d1 && d->name() == "PairUint64_32" && + d1->name() == "PairUint64_32") { + const PairUint64_32& p = + static_cast<const PairUint64_32&>(m); + const PairUint64_32& p1 = + static_cast<const PairUint64_32&>(m1); + if (p.first() == p1.first() && p.second() == p1.second()) { + found = true; + break; + } + } + } + if (!found) { + Message* new_m = refl->AddMessage(msg, f); + new_m->CopyFrom(m); + } + } + } break; + default: + _logger->error( + "Repeated type f->cpp_type = {} not managed in the " + "inheritence.", + static_cast<uint32_t>(f->cpp_type())); + assert(124 == 294); + } + } else { + switch (f->cpp_type()) { + case FieldDescriptor::CPPTYPE_STRING: + refl->SetString(msg, f, refl->GetString(*tmpl, f)); + break; + case FieldDescriptor::CPPTYPE_BOOL: + refl->SetBool(msg, f, refl->GetBool(*tmpl, f)); + break; + case FieldDescriptor::CPPTYPE_INT32: + refl->SetInt32(msg, f, refl->GetInt32(*tmpl, f)); + break; + case FieldDescriptor::CPPTYPE_UINT32: + refl->SetUInt32(msg, f, refl->GetUInt32(*tmpl, f)); + break; + case FieldDescriptor::CPPTYPE_UINT64: + refl->SetUInt64(msg, f, refl->GetUInt64(*tmpl, f)); + break; + case FieldDescriptor::CPPTYPE_ENUM: + refl->SetEnum(msg, f, refl->GetEnum(*tmpl, f)); + break; + case FieldDescriptor::CPPTYPE_MESSAGE: { + Message* m = refl->MutableMessage(msg, f); + const Descriptor* d = m->GetDescriptor(); + + if (d && d->name() == "StringSet") { + StringSet* orig_set = + static_cast<StringSet*>(refl->MutableMessage(tmpl, f)); + StringSet* set = + static_cast<StringSet*>(refl->MutableMessage(msg, f)); + if (set->additive()) { + for (auto& v : orig_set->data()) { + bool found = false; + for (auto& s : *set->mutable_data()) { + if (s == v) { + found = true; + break; + } + } + if (!found) + set->add_data(v); + } + } else if (set->data().empty()) + *set->mutable_data() = orig_set->data(); + + } else if (d && d->name() == "StringList") { + StringList* orig_lst = + static_cast<StringList*>(refl->MutableMessage(tmpl, f)); + StringList* lst = + static_cast<StringList*>(refl->MutableMessage(msg, f)); + if (lst->additive()) { + for (auto& v : orig_lst->data()) + lst->add_data(v); + } else if (lst->data().empty()) + *lst->mutable_data() = orig_lst->data(); + } + } break; + + default: + _logger->error("Entry '{}' of type {} not managed in merge", + f->name(), f->type_name()); + assert(123 == 293); + } + } + } + } + } +} diff --git a/common/engine_conf/parser.cc.old b/common/engine_conf/parser.cc.old new file mode 100644 index 00000000000..b351ce53d74 --- /dev/null +++ b/common/engine_conf/parser.cc.old @@ -0,0 +1,1503 @@ +/** + * Copyright 2011-2014,2017,2022-2023 Centreon + * + * This file is part of Centreon Engine. + * + * Centreon Engine is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * Centreon Engine 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 Centreon Engine. If not, see + * <http://www.gnu.org/licenses/>. + */ + +#include "parser.hh" + +#include <absl/container/flat_hash_set.h> +#include <absl/strings/ascii.h> + +#include <filesystem> +#include <memory> + +#include "absl/strings/numbers.h" +#include "com/centreon/exceptions/msg_fmt.hh" +#include "anomalydetection_helper.hh" +#include "command_helper.hh" +#include "connector_helper.hh" +#include "contact_helper.hh" +#include "contactgroup_helper.hh" +#include "host_helper.hh" +#include "hostdependency_helper.hh" +#include "hostescalation_helper.hh" +#include "hostgroup_helper.hh" +#include "message_helper.hh" +#include "service_helper.hh" +#include "servicedependency_helper.hh" +#include "serviceescalation_helper.hh" +#include "servicegroup_helper.hh" +#include "severity_helper.hh" +#include "state_helper.hh" +#include "tag_helper.hh" +#include "timeperiod_helper.hh" +#include "common/log_v2/log_v2.hh" + +using namespace com::centreon; +using namespace com::centreon::engine::configuration; +using msg_fmt = com::centreon::exceptions::msg_fmt; +using error = com::centreon::exceptions::error; +using com::centreon::common::log_v2::log_v2; + +using Descriptor = ::google::protobuf::Descriptor; +using FieldDescriptor = ::google::protobuf::FieldDescriptor; +using Message = ::google::protobuf::Message; +using Reflection = ::google::protobuf::Reflection; + +parser::store parser::_store[] = { + &parser::_store_into_map<command, &command::command_name>, + &parser::_store_into_map<connector, &connector::connector_name>, + &parser::_store_into_map<contact, &contact::contact_name>, + &parser::_store_into_map<contactgroup, &contactgroup::contactgroup_name>, + &parser::_store_into_map<host, &host::host_name>, + &parser::_store_into_list, + &parser::_store_into_list, + &parser::_store_into_list, + &parser::_store_into_map<hostgroup, &hostgroup::hostgroup_name>, + &parser::_store_into_list, + &parser::_store_into_list, + &parser::_store_into_list, + &parser::_store_into_list, + &parser::_store_into_map<servicegroup, &servicegroup::servicegroup_name>, + &parser::_store_into_map<timeperiod, &timeperiod::timeperiod_name>, + &parser::_store_into_list, + &parser::_store_into_list, + &parser::_store_into_list}; + +/** + * Get the next valid line. + * + * @param[in, out] stream The current stream to read new line. + * @param[out] line The line to fill. + * @param[in, out] pos The current position. + * + * @return True if data is available, false if no data. + */ +static bool get_next_line(std::ifstream& stream, + std::string& line, + uint32_t& pos) { + while (std::getline(stream, line, '\n')) { + ++pos; + line = absl::StripAsciiWhitespace(line); + if (!line.empty()) { + char c = line[0]; + if (c != '#' && c != ';' && c != '\x0') + return true; + } + } + return false; +} + +#ifdef LEGACY_CONF +/** + * Default constructor. + * + * @param[in] read_options Configuration file reading options + * (use to skip some object type). + */ +parser::parser(unsigned int read_options) + : _config(nullptr), + _read_options(read_options), + _logger{log_v2::instance().get(log_v2::CONFIG)} {} + +/** + * Parse the object definition file. + * + * @param[in] path The object definitions path. + */ +void parser::_parse_object_definitions(std::string const& path) { + _logger->info("Processing object config file '{}'", path); + + std::ifstream stream(path, std::ios::binary); + if (!stream.is_open()) + throw msg_fmt("Parsing of object definition failed: can't open file '{}'", + path); + + _current_line = 0; + _current_path = path; + + bool parse_object = false; + object_ptr obj; + std::string input; + while (get_next_line(stream, input, _current_line)) { + // Multi-line. + while ('\\' == input[input.size() - 1]) { + input.resize(input.size() - 1); + std::string addendum; + if (!get_next_line(stream, addendum, _current_line)) + break; + input.append(addendum); + } + + // Check if is a valid object. + if (obj == nullptr) { + if (input.find("define") || !std::isspace(input[6])) + throw msg_fmt( + "Parsing of object definition failed " + "in file '{}' on line {}: Unexpected start definition", + _current_path, _current_line); + input.erase(0, 6); + absl::StripLeadingAsciiWhitespace(&input); + std::size_t last = input.size() - 1; + if (input.empty() || input[last] != '{') + throw msg_fmt( + "Parsing of object definition failed in file '{}' on line {}: " + "Unexpected start definition", + _current_path, _current_line); + input.erase(last); + absl::StripTrailingAsciiWhitespace(&input); + obj = object::create(input); + if (obj == nullptr) + throw msg_fmt( + "Parsing of object definition failed in file '{}' on line {}: " + "Unknown object type name '{}'", + _current_path, _current_line, input); + parse_object = (_read_options & (1 << obj->type())); + _objects_info.emplace(obj.get(), file_info(path, _current_line)); + } + // Check if is the not the end of the current object. + else if (input != "}") { + if (parse_object) { + if (!obj->parse(input)) + throw msg_fmt( + "Parsing of object definition failed in file '{}' on line {}: " + "Invalid line '{}'", + _current_path, _current_line, input); + } + } + // End of the current object. + else { + if (parse_object) { + if (!obj->name().empty()) + _add_template(obj); + if (obj->should_register()) + _add_object(obj); + } + obj.reset(); + } + } +} + +/** + * Parse the directory configuration. + * + * @param[in] path The directory path. + */ +void parser::_parse_directory_configuration(std::string const& path) { + for (auto& entry : std::filesystem::directory_iterator(path)) { + if (entry.is_regular_file() && entry.path().extension() == ".cfg") + _parse_object_definitions(entry.path().string()); + } +} + +/** + * Parse the global configuration file. + * + * @param[in] path The configuration path. + */ +void parser::_parse_global_configuration(const std::string& path) { + _logger->info("Reading main configuration file '{}'.", path); + + std::ifstream stream(path, std::ios::binary); + if (!stream.is_open()) + throw msg_fmt( + "Parsing of global configuration failed: can't open file '{}'", path); + + _config->cfg_main(path); + + _current_line = 0; + _current_path = path; + + std::string input; + while (get_next_line(stream, input, _current_line)) { + std::list<std::string> values = + absl::StrSplit(input, absl::MaxSplits('=', 1)); + if (values.size() == 2) { + auto it = values.begin(); + char const* key = it->c_str(); + ++it; + char const* value = it->c_str(); + if (_config->set(key, value)) + continue; + } + throw msg_fmt( + "Parsing of global configuration failed in file '{}' on line {}: " + "Invalid line '{}'", + path, _current_line, input); + } +} + +/** + * Parse the resource file. + * + * @param[in] path The resource file path. + */ +void parser::_parse_resource_file(std::string const& path) { + _logger->info("Reading resource file '{}'", path); + + std::ifstream stream(path.c_str(), std::ios::binary); + if (!stream.is_open()) + throw msg_fmt("Parsing of resource file failed: can't open file '{}'", + path); + + _current_line = 0; + _current_path = path; + + std::string input; + while (get_next_line(stream, input, _current_line)) { + try { + std::list<std::string> key_value = + absl::StrSplit(input, absl::MaxSplits('=', 1)); + if (key_value.size() == 2) { + auto it = key_value.begin(); + std::string& key = *it; + ++it; + std::string value = *it; + _config->user(key, value); + } else + throw msg_fmt( + "Parsing of resource file '{}' failed on line {}; Invalid line " + "'{}'", + _current_path, _current_line, input); + } catch (std::exception const& e) { + (void)e; + throw msg_fmt( + "Parsing of resource file '{}' failed on line {}: Invalid line '{}'", + _current_path, _current_line, input); + } + } +} + +/** + * Resolve template for register objects. + */ +void parser::_resolve_template() { + for (map_object& templates : _templates) { + for (map_object::iterator it = templates.begin(), end = templates.end(); + it != end; ++it) + it->second->resolve_template(templates); + } + + for (uint32_t i = 0; i < _lst_objects.size(); ++i) { + map_object& templates = _templates[i]; + for (list_object::iterator it = _lst_objects[i].begin(), + end = _lst_objects[i].end(); + it != end; ++it) { + (*it)->resolve_template(templates); + object::error_info err; + try { + (*it)->check_validity(&err); + _config_warnings += err.config_warnings; + _config_errors += err.config_errors; + } catch (std::exception const& e) { + throw error("Configuration parsing failed {}: {}", + _get_file_info(it->get()), e.what()); + } + } + } + + for (uint32_t i = 0; i < _map_objects.size(); ++i) { + map_object& templates = _templates[i]; + for (map_object::iterator it = _map_objects[i].begin(), + end = _map_objects[i].end(); + it != end; ++it) { + it->second->resolve_template(templates); + try { + object::error_info err; + it->second->check_validity(&err); + _config_warnings += err.config_warnings; + _config_errors += err.config_errors; + } catch (const std::exception& e) { + throw error("Configuration parsing failed {}: {}", + _get_file_info(it->second.get()), e.what()); + } + } + } +} + +#else +/** + * Default constructor. + * + * @param[in] read_options Configuration file reading options + * (use to skip some object type). + */ +parser::parser(unsigned int read_options) + : _read_options(read_options), + _logger{log_v2::instance().get(log_v2::CONFIG)} {} + +#endif + +#ifdef LEGACY_CONF +/** + * Parse configuration file. + * + * @param[in] path The configuration file path. + * @param[in] config The state configuration to fill. + */ +void parser::parse(const std::string& path, state& config) { + _config = &config; + + // parse the global configuration file. + _parse_global_configuration(path); + + // parse configuration files. + _apply(config.cfg_file(), &parser::_parse_object_definitions); + // parse resource files. + _apply(config.resource_file(), &parser::_parse_resource_file); + // parse configuration directories. + _apply(config.cfg_dir(), &parser::_parse_directory_configuration); + + // Apply template. + _resolve_template(); + + // Fill state. + _insert(_map_objects[object::command], config.commands()); + _insert(_map_objects[object::connector], config.connectors()); + _insert(_map_objects[object::contact], config.contacts()); + _insert(_map_objects[object::contactgroup], config.contactgroups()); + _insert(_lst_objects[object::hostdependency], config.hostdependencies()); + _insert(_lst_objects[object::hostescalation], config.hostescalations()); + _insert(_map_objects[object::hostgroup], config.hostgroups()); + _insert(_map_objects[object::host], config.hosts()); + _insert(_lst_objects[object::servicedependency], + config.servicedependencies()); + _insert(_lst_objects[object::serviceescalation], config.serviceescalations()); + _insert(_map_objects[object::servicegroup], config.servicegroups()); + _insert(_lst_objects[object::service], config.services()); + _insert(_lst_objects[object::anomalydetection], config.anomalydetections()); + _insert(_map_objects[object::timeperiod], config.timeperiods()); + _insert(_lst_objects[object::severity], config.mut_severities()); + _insert(_lst_objects[object::tag], config.mut_tags()); + + // cleanup. + _objects_info.clear(); + for (unsigned int i(0); i < _lst_objects.size(); ++i) { + _lst_objects[i].clear(); + _map_objects[i].clear(); + _templates[i].clear(); + } +} + +#else +void parser::parse(const std::string& path, State* pb_config) { + /* Parse the global configuration file. */ + auto helper = std::make_unique<state_helper>(pb_config); + _pb_helper[pb_config] = std::move(helper); + _parse_global_configuration(path, pb_config); + + // parse configuration files. + _apply(pb_config->cfg_file(), pb_config, &parser::_parse_object_definitions); + // parse resource files. + _apply(pb_config->resource_file(), pb_config, &parser::_parse_resource_file); + // parse configuration directories. + _apply(pb_config->cfg_dir(), pb_config, + &parser::_parse_directory_configuration); + + // Apply template. + _resolve_template(pb_config); + + _cleanup(pb_config); +} + +/** + * Parse the directory configuration. + * + * @param[in] path The directory path. + */ +void parser::_parse_directory_configuration(const std::string& path, + State* pb_config) { + for (auto& entry : std::filesystem::directory_iterator(path)) { + if (entry.is_regular_file() && entry.path().extension() == ".cfg") + _parse_object_definitions(entry.path().string(), pb_config); + } +} + +void parser::_parse_resource_file(const std::string& path, State* pb_config) { + _logger->info("Reading resource file '{}'", path); + + std::ifstream in(path, std::ios::in); + std::string content; + if (in) { + in.seekg(0, std::ios::end); + content.resize(in.tellg()); + in.seekg(0, std::ios::beg); + in.read(&content[0], content.size()); + in.close(); + } else + throw msg_fmt("Parsing of resource file failed: can't open file '{}': {}", + path, strerror(errno)); + + auto tab{absl::StrSplit(content, '\n')}; + int current_line = 1; + for (auto it = tab.begin(); it != tab.end(); ++it, current_line++) { + std::string_view l = absl::StripLeadingAsciiWhitespace(*it); + if (l.empty() || l[0] == '#' || l[0] == ';') + continue; + std::pair<std::string_view, std::string_view> p = + absl::StrSplit(l, absl::MaxSplits('=', 1)); + p.first = absl::StripTrailingAsciiWhitespace(p.first); + p.second = absl::StripLeadingAsciiWhitespace(p.second); + if (p.first.size() >= 3 && p.first[0] == '$' && + p.first[p.first.size() - 1] == '$') { + p.first = p.first.substr(1, p.first.size() - 2); + (*pb_config + ->mutable_users())[std::string(p.first.data(), p.first.size())] = + std::string(p.second.data(), p.second.size()); + } else + throw msg_fmt("Invalid user key '{}'", p.first); + } +} + +/** + * @brief For each type of object in the State, templates are resolved that is + * to say, children inherite from parents properties. + * + * @param pb_config The State containing all the object to handle. + */ +void parser::_resolve_template(State* pb_config) { + for (Command& c : *pb_config->mutable_commands()) + _resolve_template(_pb_helper[&c], _pb_templates[object::command]); + + for (Connector& c : *pb_config->mutable_connectors()) + _resolve_template(_pb_helper[&c], _pb_templates[object::connector]); + + for (Contact& c : *pb_config->mutable_contacts()) + _resolve_template(_pb_helper[&c], _pb_templates[object::contact]); + + for (Contactgroup& cg : *pb_config->mutable_contactgroups()) + _resolve_template(_pb_helper[&cg], _pb_templates[object::contactgroup]); + + for (Host& h : *pb_config->mutable_hosts()) + _resolve_template(_pb_helper[&h], _pb_templates[object::host]); + + for (Service& s : *pb_config->mutable_services()) + _resolve_template(_pb_helper[&s], _pb_templates[object::service]); + + for (Anomalydetection& a : *pb_config->mutable_anomalydetections()) + _resolve_template(_pb_helper[&a], _pb_templates[object::anomalydetection]); + + for (Serviceescalation& se : *pb_config->mutable_serviceescalations()) + _resolve_template(_pb_helper[&se], + _pb_templates[object::serviceescalation]); + + for (Hostescalation& he : *pb_config->mutable_hostescalations()) + _resolve_template(_pb_helper[&he], _pb_templates[object::hostescalation]); + + for (const Command& c : pb_config->commands()) + _pb_helper.at(&c)->check_validity(); + + for (const Contact& c : pb_config->contacts()) + _pb_helper.at(&c)->check_validity(); + + for (const Contactgroup& cg : pb_config->contactgroups()) + _pb_helper.at(&cg)->check_validity(); + + for (const Host& h : pb_config->hosts()) + _pb_helper.at(&h)->check_validity(); + + for (const Hostdependency& hd : pb_config->hostdependencies()) + _pb_helper.at(&hd)->check_validity(); + + for (const Hostescalation& he : pb_config->hostescalations()) + _pb_helper.at(&he)->check_validity(); + + for (const Hostgroup& hg : pb_config->hostgroups()) + _pb_helper.at(&hg)->check_validity(); + + for (const Service& s : pb_config->services()) + _pb_helper.at(&s)->check_validity(); + + for (const Hostdependency& hd : pb_config->hostdependencies()) + _pb_helper.at(&hd)->check_validity(); + + for (const Servicedependency& sd : pb_config->servicedependencies()) + _pb_helper.at(&sd)->check_validity(); + + for (const Servicegroup& sg : pb_config->servicegroups()) + _pb_helper.at(&sg)->check_validity(); + + for (const Timeperiod& t : pb_config->timeperiods()) + _pb_helper.at(&t)->check_validity(); + + for (const Anomalydetection& a : pb_config->anomalydetections()) + _pb_helper.at(&a)->check_validity(); + + for (const Tag& t : pb_config->tags()) + _pb_helper.at(&t)->check_validity(); + + for (const Servicegroup& sg : pb_config->servicegroups()) + _pb_helper.at(&sg)->check_validity(); + + for (const Severity& sv : pb_config->severities()) + _pb_helper.at(&sv)->check_validity(); + + for (const Tag& t : pb_config->tags()) + _pb_helper.at(&t)->check_validity(); + + for (const Serviceescalation& se : pb_config->serviceescalations()) + _pb_helper.at(&se)->check_validity(); + + for (const Hostescalation& he : pb_config->hostescalations()) + _pb_helper.at(&he)->check_validity(); + + for (const Connector& c : pb_config->connectors()) + _pb_helper.at(&c)->check_validity(); +} + +bool set_global(std::unique_ptr<message_helper>& helper, + const std::string_view& key, + const std::string_view& value) { + // const absl::flat_hash_map<std::string, std::string>& correspondence = + // {}) { + State* msg = static_cast<State*>(helper->mut_obj()); + const Descriptor* desc = msg->GetDescriptor(); + const FieldDescriptor* f; + const Reflection* refl; + + f = desc->FindFieldByName(std::string(key.data(), key.size())); + if (f == nullptr) { + auto it = helper->correspondence().find(key); + if (it != helper->correspondence().end()) + f = desc->FindFieldByName(it->second); + if (f == nullptr) + return false; + } + refl = msg->GetReflection(); + switch (f->type()) { + case FieldDescriptor::TYPE_BOOL: { + bool val; + if (absl::SimpleAtob(value, &val)) { + refl->SetBool(static_cast<Message*>(msg), f, val); + return true; + } else + return false; + } break; + case FieldDescriptor::TYPE_INT32: { + int32_t val; + if (absl::SimpleAtoi(value, &val)) { + refl->SetInt32(static_cast<Message*>(msg), f, val); + return true; + } else + return false; + } break; + case FieldDescriptor::TYPE_UINT32: { + uint32_t val; + if (absl::SimpleAtoi(value, &val)) { + refl->SetUInt32(static_cast<Message*>(msg), f, val); + return true; + } else + return false; + } break; + case FieldDescriptor::TYPE_UINT64: { + uint64_t val; + if (absl::SimpleAtoi(value, &val)) { + refl->SetUInt64(static_cast<Message*>(msg), f, val); + return true; + } else + return false; + } break; + case FieldDescriptor::TYPE_FLOAT: { + float val; + if (absl::SimpleAtof(value, &val)) { + refl->SetFloat(static_cast<Message*>(msg), f, val); + return true; + } else + return false; + } break; + case FieldDescriptor::TYPE_STRING: + if (f->is_repeated()) { + refl->AddString(static_cast<Message*>(msg), f, + std::string(value.data(), value.size())); + } else { + refl->SetString(static_cast<Message*>(msg), f, + std::string(value.data(), value.size())); + } + return true; + case FieldDescriptor::TYPE_MESSAGE: + if (!f->is_repeated()) { + Message* m = refl->MutableMessage(msg, f); + const Descriptor* d = m->GetDescriptor(); + + if (d && d->name() == "StringSet") { + StringSet* set = + static_cast<StringSet*>(refl->MutableMessage(msg, f)); + fill_string_group(set, value); + return true; + } else if (d && d->name() == "StringList") { + StringList* lst = + static_cast<StringList*>(refl->MutableMessage(msg, f)); + fill_string_group(lst, value); + return true; + } + } + default: + return false; + } + return true; +} + +/** + * @brief Clean the configuration: + * * remove template objects. + * + * @param pb_config + */ +void parser::_cleanup(State* pb_config) { + int i = 0; + for (auto it = pb_config->mutable_services()->begin(); + it != pb_config->mutable_services()->end();) { + if (!it->obj().register_()) { + pb_config->mutable_services()->erase(it); + it = pb_config->mutable_services()->begin() + i; + } else { + ++it; + ++i; + } + } + i = 0; + for (auto it = pb_config->mutable_anomalydetections()->begin(); + it != pb_config->mutable_anomalydetections()->end();) { + if (!it->obj().register_()) { + pb_config->mutable_anomalydetections()->erase(it); + it = pb_config->mutable_anomalydetections()->begin() + i; + } else { + ++it; + ++i; + } + } +} + +void parser::_merge(std::unique_ptr<message_helper>& msg_helper, + Message* tmpl) { + Message* msg = msg_helper->mut_obj(); + const Descriptor* desc = msg->GetDescriptor(); + const Reflection* refl = msg->GetReflection(); + std::string tmp_str; + + for (int i = 0; i < desc->field_count(); ++i) { + const FieldDescriptor* f = desc->field(i); + if (f->name() != "obj") { + /* Optional? If not defined in template, we continue. */ + const auto* oof = f->containing_oneof(); + if (oof) { + if (!refl->GetOneofFieldDescriptor(*tmpl, oof)) + continue; + } + + if ((oof && !refl->GetOneofFieldDescriptor(*msg, oof)) || + !msg_helper->changed(f->number())) { + if (f->is_repeated()) { + switch (f->cpp_type()) { + case FieldDescriptor::CPPTYPE_STRING: { + size_t count = refl->FieldSize(*tmpl, f); + for (size_t j = 0; j < count; ++j) { + const std::string& s = + refl->GetRepeatedStringReference(*tmpl, f, j, &tmp_str); + size_t count_msg = refl->FieldSize(*msg, f); + std::string tmp_str1; + bool found = false; + for (size_t k = 0; k < count_msg; ++k) { + const std::string& s1 = + refl->GetRepeatedStringReference(*msg, f, k, &tmp_str1); + if (s1 == s) { + found = true; + break; + } + } + if (!found) + refl->AddString(msg, f, s); + } + } break; + case FieldDescriptor::CPPTYPE_MESSAGE: { + size_t count = refl->FieldSize(*tmpl, f); + for (size_t j = 0; j < count; ++j) { + const Message& m = refl->GetRepeatedMessage(*tmpl, f, j); + const Descriptor* d = m.GetDescriptor(); + size_t count_msg = refl->FieldSize(*msg, f); + bool found = false; + for (size_t k = 0; k < count_msg; ++k) { + const Message& m1 = refl->GetRepeatedMessage(*msg, f, k); + const Descriptor* d1 = m1.GetDescriptor(); + if (d && d1 && d->name() == "PairUint64_32" && + d1->name() == "PairUint64_32") { + const PairUint64_32& p = + static_cast<const PairUint64_32&>(m); + const PairUint64_32& p1 = + static_cast<const PairUint64_32&>(m1); + if (p.first() == p1.first() && p.second() == p1.second()) { + found = true; + break; + } + } + } + if (!found) { + Message* new_m = refl->AddMessage(msg, f); + new_m->CopyFrom(m); + } + } + } break; + default: + _logger->error( + "Repeated type f->cpp_type = {} not managed in the " + "inheritence.", + f->cpp_type()); + assert(124 == 294); + } + } else { + switch (f->cpp_type()) { + case FieldDescriptor::CPPTYPE_STRING: + refl->SetString(msg, f, refl->GetString(*tmpl, f)); + break; + case FieldDescriptor::CPPTYPE_BOOL: + refl->SetBool(msg, f, refl->GetBool(*tmpl, f)); + break; + case FieldDescriptor::CPPTYPE_INT32: + refl->SetInt32(msg, f, refl->GetInt32(*tmpl, f)); + break; + case FieldDescriptor::CPPTYPE_UINT32: + refl->SetUInt32(msg, f, refl->GetUInt32(*tmpl, f)); + break; + case FieldDescriptor::CPPTYPE_UINT64: + refl->SetUInt64(msg, f, refl->GetUInt64(*tmpl, f)); + break; + case FieldDescriptor::CPPTYPE_ENUM: + refl->SetEnum(msg, f, refl->GetEnum(*tmpl, f)); + break; + case FieldDescriptor::CPPTYPE_MESSAGE: { + Message* m = refl->MutableMessage(msg, f); + const Descriptor* d = m->GetDescriptor(); + + if (d && d->name() == "StringSet") { + StringSet* orig_set = + static_cast<StringSet*>(refl->MutableMessage(tmpl, f)); + StringSet* set = + static_cast<StringSet*>(refl->MutableMessage(msg, f)); + if (set->additive()) { + for (auto& v : orig_set->data()) { + bool found = false; + for (auto& s : *set->mutable_data()) { + if (s == v) { + found = true; + break; + } + } + if (!found) + set->add_data(v); + } + } else if (set->data().empty()) + *set->mutable_data() = orig_set->data(); + + } else if (d && d->name() == "StringList") { + StringList* orig_lst = + static_cast<StringList*>(refl->MutableMessage(tmpl, f)); + StringList* lst = + static_cast<StringList*>(refl->MutableMessage(msg, f)); + if (lst->additive()) { + for (auto& v : orig_lst->data()) + lst->add_data(v); + } else if (lst->data().empty()) + *lst->mutable_data() = orig_lst->data(); + } + } break; + + default: + _logger->error("Entry '{}' of type {} not managed in merge", + f->name(), f->type_name()); + assert(123 == 293); + } + } + } + } + } +} + +void parser::_resolve_template(std::unique_ptr<message_helper>& msg_helper, + const pb_map_object& tmpls) { + if (msg_helper->resolved()) + return; + Message* msg = msg_helper->mut_obj(); + + msg_helper->resolve(); + const Descriptor* desc = msg->GetDescriptor(); + const FieldDescriptor* f = desc->FindFieldByName("obj"); + const Reflection* refl = msg->GetReflection(); + if (!f) + return; + + Object* obj = static_cast<Object*>(refl->MutableMessage(msg, f)); + for (const std::string& u : obj->use()) { + auto it = tmpls.find(u); + if (it == tmpls.end()) + throw msg_fmt("Cannot merge object of type '{}'", u); + _resolve_template(_pb_helper[it->second.get()], tmpls); + _merge(msg_helper, it->second.get()); + } +} + +/** + * @brief Return true if the register flag is enabled in the configuration + * object. + * + * @param msg A configuration object as Protobuf message. + * + * @return True if it has to be registered, false otherwise. + */ +bool parser::_is_registered(const Message& msg) const { + const Descriptor* desc = msg.GetDescriptor(); + const Reflection* refl = msg.GetReflection(); + std::string tmpl; + const FieldDescriptor* f = desc->FindFieldByName("obj"); + if (f) { + const Object& obj = static_cast<const Object&>(refl->GetMessage(msg, f)); + return obj.register_(); + } + return false; +} + +/** + * @brief Set the value given as a string to the object key. If the key does + * not exist, the correspondence table may be used to find a replacement of + * the key. The function converts the value to the appropriate type. + * + * Another important point is that many configuration objects contain the Object + * obj message (something like an inheritance). This message contains three + * fields name, use and register that are important for templating. If keys are + * one of these names, the function tries to work directly with the obj message. + * + * @tparam T The type of the message containing the object key. + * @param msg The message containing the object key. + * @param key The key to localize the object to set. + * @param value The value as string that will be converted to the good type. + * @param correspondence A hash table giving traductions from keys to others. + * If a key fails, correspondence is used to find a new replacement key. + * + * @return true on success. + */ +bool set(std::unique_ptr<message_helper>& helper, + const std::string_view& key, + const std::string_view& value) { + Message* msg = helper->mut_obj(); + const Descriptor* desc = msg->GetDescriptor(); + const FieldDescriptor* f; + const Reflection* refl; + + /* Cases where we have to work on the obj Object (the parent object) */ + if (key == "name" || key == "register" || key == "use") { + f = desc->FindFieldByName("obj"); + if (f) { + refl = msg->GetReflection(); + Object* obj = static_cast<Object*>(refl->MutableMessage(msg, f)); + + /* Optimization to avoid a new string comparaison */ + switch (key[0]) { + case 'n': // name + obj->set_name(std::string(value.data(), value.size())); + break; + case 'r': { // register + bool value_b; + if (!absl::SimpleAtob(value, &value_b)) + return false; + else + obj->set_register_(value_b); + } break; + case 'u': { // use + obj->mutable_use()->Clear(); + auto arr = absl::StrSplit(value, ','); + for (auto& t : arr) { + std::string v{absl::StripAsciiWhitespace(t)}; + obj->mutable_use()->Add(std::move(v)); + } + } break; + } + return true; + } + } + + f = desc->FindFieldByName(std::string(key.data(), key.size())); + if (f == nullptr) { + auto it = helper->correspondence().find(key); + if (it != helper->correspondence().end()) + f = desc->FindFieldByName(it->second); + if (f == nullptr) + return false; + } + refl = msg->GetReflection(); + switch (f->type()) { + case FieldDescriptor::TYPE_BOOL: { + bool val; + if (absl::SimpleAtob(value, &val)) { + refl->SetBool(static_cast<Message*>(msg), f, val); + helper->set_changed(f->number()); + return true; + } else + return false; + } break; + case FieldDescriptor::TYPE_INT32: { + int32_t val; + if (absl::SimpleAtoi(value, &val)) { + refl->SetInt32(static_cast<Message*>(msg), f, val); + helper->set_changed(f->number()); + return true; + } else + return false; + } break; + case FieldDescriptor::TYPE_UINT32: { + uint32_t val; + if (absl::SimpleAtoi(value, &val)) { + refl->SetUInt32(static_cast<Message*>(msg), f, val); + helper->set_changed(f->number()); + return true; + } else + return false; + } break; + case FieldDescriptor::TYPE_UINT64: { + uint64_t val; + if (absl::SimpleAtoi(value, &val)) { + refl->SetUInt64(static_cast<Message*>(msg), f, val); + helper->set_changed(f->number()); + return true; + } else + return false; + } break; + case FieldDescriptor::TYPE_DOUBLE: { + double val; + if (absl::SimpleAtod(value, &val)) { + refl->SetDouble(static_cast<Message*>(msg), f, val); + helper->set_changed(f->number()); + return true; + } else + return false; + } break; + case FieldDescriptor::TYPE_STRING: + if (f->is_repeated()) { + refl->AddString(static_cast<Message*>(msg), f, + std::string(value.data(), value.size())); + } else { + refl->SetString(static_cast<Message*>(msg), f, + std::string(value.data(), value.size())); + } + helper->set_changed(f->number()); + return true; + case FieldDescriptor::TYPE_MESSAGE: + if (!f->is_repeated()) { + Message* m = refl->MutableMessage(msg, f); + const Descriptor* d = m->GetDescriptor(); + + if (d && d->name() == "StringSet") { + StringSet* set = + static_cast<StringSet*>(refl->MutableMessage(msg, f)); + fill_string_group(set, value); + helper->set_changed(f->number()); + return true; + } else if (d && d->name() == "StringList") { + StringList* lst = + static_cast<StringList*>(refl->MutableMessage(msg, f)); + fill_string_group(lst, value); + helper->set_changed(f->number()); + return true; + } + } + default: + return false; + } + return true; +} + +void parser::_parse_global_configuration(const std::string& path, + State* pb_config) { + _logger->info("Reading main configuration file '{}'.", path); + + std::ifstream in(path, std::ios::in); + std::string content; + if (in) { + in.seekg(0, std::ios::end); + content.resize(in.tellg()); + in.seekg(0, std::ios::beg); + in.read(&content[0], content.size()); + in.close(); + } else + throw msg_fmt( + "Parsing of global configuration failed: can't open file '{}': {}", + path, strerror(errno)); + + pb_config->set_cfg_main(path); + _current_line = 0; + _current_path = path; + + auto tab{absl::StrSplit(content, '\n')}; + auto& cfg_helper = _pb_helper[pb_config]; + for (auto it = tab.begin(); it != tab.end(); ++it) { + std::string_view l = absl::StripAsciiWhitespace(*it); + if (l.empty() || l[0] == '#') + continue; + std::pair<std::string_view, std::string_view> p = + absl::StrSplit(l, absl::MaxSplits('=', 1)); + p.first = absl::StripTrailingAsciiWhitespace(p.first); + p.second = absl::StripLeadingAsciiWhitespace(p.second); + bool retval = false; + /* particular cases with hook */ + retval = cfg_helper->hook(p.first, p.second); + if (!retval) { + if (!set_global(cfg_helper, p.first, p.second)) + _logger->error("Unable to parse '{}' key with value '{}'", p.first, + p.second); + } + } +} + +/** + * @brief Parse objects files (services.cfg, hosts.cfg, timeperiods.cfg... + * + * This function almost uses protobuf reflection to set values but it may fail + * because of the syntax used in these files that can be a little different + * from the message format. + * + * Two mechanisms are used to complete the reflection. + * * A hastable <string, string> named correspondence is used in case of several + * keys to access to the same value. This is, for example, the case for + * host_id which is historically also named _HOST_ID. + * * A std::function<bool(string_view_string_view) can also be defined in + * several cases to make special stuffs. For example, we use it for timeperiod + * object to set its timeranges. + * + * @param path The file to parse. + * @param pb_config The configuration to complete. + */ +void parser::_parse_object_definitions(const std::string& path, + State* pb_config) { + _logger->info("Processing object config file '{}'", path); + + std::ifstream in(path, std::ios::in); + std::string content; + if (in) { + in.seekg(0, std::ios::end); + content.resize(in.tellg()); + in.seekg(0, std::ios::beg); + in.read(&content[0], content.size()); + in.close(); + } else + throw msg_fmt( + "Parsing of object definition failed: can't open file '{}': {}", path, + strerror(errno)); + + auto tab{absl::StrSplit(content, '\n')}; + std::string ll; + bool append_to_previous_line = false; + std::unique_ptr<Message> msg; + std::unique_ptr<message_helper> msg_helper; + + int current_line = 1; + std::string type; + + for (auto it = tab.begin(); it != tab.end(); ++it, current_line++) { + std::string_view l = absl::StripAsciiWhitespace(*it); + if (l.empty() || l[0] == '#' || l[0] == ';') + continue; + + /* Multiline */ + if (append_to_previous_line) { + if (l[l.size() - 1] == '\\') { + ll.append(l.data(), l.size() - 1); + continue; + } else { + ll.append(l.data(), l.size()); + append_to_previous_line = false; + l = ll; + } + } else if (l[l.size() - 1] == '\\') { + ll = std::string(l.data(), l.size() - 1); + append_to_previous_line = true; + continue; + } + + if (msg) { + if (l.empty()) + continue; + /* is it time to close the definition? */ + if (l == "}") { + const Descriptor* desc = msg->GetDescriptor(); + const FieldDescriptor* f = desc->FindFieldByName("obj"); + const Reflection* refl = msg->GetReflection(); + if (f) { + const Object& obj = + *static_cast<const Object*>(&refl->GetMessage(*msg, f)); + auto otype = msg_helper->otype(); + _pb_helper[msg.get()] = std::move(msg_helper); + if (!obj.name().empty()) { + pb_map_object& tmpl = _pb_templates[otype]; + auto it = tmpl.find(obj.name()); + if (it != tmpl.end()) + throw msg_fmt("Parsing of '{}' failed {}: {} already exists", + type, "file_info" /*_get_file_info(obj.get()) */, + obj.name()); + if (!obj.register_()) + tmpl[obj.name()] = std::move(msg); + else { + auto copy = std::unique_ptr<Message>(msg->New()); + copy->CopyFrom(*msg); + _pb_helper[copy.get()] = + message_helper::clone(*_pb_helper[msg.get()], copy.get()); + tmpl[obj.name()] = std::move(copy); + } + } + if (obj.register_()) { + switch (otype) { + case message_helper::contact: + pb_config->mutable_contacts()->AddAllocated( + static_cast<Contact*>(msg.release())); + break; + case message_helper::host: + pb_config->mutable_hosts()->AddAllocated( + static_cast<Host*>(msg.release())); + break; + case message_helper::service: + pb_config->mutable_services()->AddAllocated( + static_cast<Service*>(msg.release())); + break; + case message_helper::anomalydetection: + pb_config->mutable_anomalydetections()->AddAllocated( + static_cast<Anomalydetection*>(msg.release())); + break; + case message_helper::hostdependency: + pb_config->mutable_hostdependencies()->AddAllocated( + static_cast<Hostdependency*>(msg.release())); + break; + case message_helper::servicedependency: + pb_config->mutable_servicedependencies()->AddAllocated( + static_cast<Servicedependency*>(msg.release())); + break; + case message_helper::timeperiod: + pb_config->mutable_timeperiods()->AddAllocated( + static_cast<Timeperiod*>(msg.release())); + break; + case message_helper::command: + pb_config->mutable_commands()->AddAllocated( + static_cast<Command*>(msg.release())); + break; + case message_helper::hostgroup: + pb_config->mutable_hostgroups()->AddAllocated( + static_cast<Hostgroup*>(msg.release())); + break; + case message_helper::servicegroup: + pb_config->mutable_servicegroups()->AddAllocated( + static_cast<Servicegroup*>(msg.release())); + break; + case message_helper::tag: + pb_config->mutable_tags()->AddAllocated( + static_cast<Tag*>(msg.release())); + break; + case message_helper::contactgroup: + pb_config->mutable_contactgroups()->AddAllocated( + static_cast<Contactgroup*>(msg.release())); + break; + case message_helper::connector: + pb_config->mutable_connectors()->AddAllocated( + static_cast<Connector*>(msg.release())); + break; + case message_helper::severity: + pb_config->mutable_severities()->AddAllocated( + static_cast<Severity*>(msg.release())); + break; + case message_helper::serviceescalation: + pb_config->mutable_serviceescalations()->AddAllocated( + static_cast<Serviceescalation*>(msg.release())); + break; + case message_helper::hostescalation: + pb_config->mutable_hostescalations()->AddAllocated( + static_cast<Hostescalation*>(msg.release())); + break; + default: + _logger->critical("Attempt to add an object of unknown type"); + } + } + } + msg = nullptr; + } else { + /* Main part where keys/values are read */ + /* ------------------------------------ */ + size_t pos = l.find_first_of(" \t"); + std::string_view key = l.substr(0, pos); + if (pos != std::string::npos) { + l.remove_prefix(pos); + l = absl::StripLeadingAsciiWhitespace(l); + } else + l = {}; + + bool retval = false; + /* particular cases with hook */ + retval = msg_helper->hook(key, l); + + if (!retval) { + /* Classical part */ + if (!set(msg_helper, key, l)) { + if (!msg_helper->insert_customvariable(key, l)) + throw msg_fmt( + "Unable to parse '{}' key with value '{}' in message of type " + "'{}'", + key, l, type); + } + } + } + } else { + if (!absl::StartsWith(l, "define") || !std::isspace(l[6])) + throw msg_fmt( + "Parsing of object definition failed in file '{}' at line {}: " + "Unexpected start definition", + path, current_line); + /* Let's remove the first 6 characters ("define") */ + l = absl::StripLeadingAsciiWhitespace(l.substr(6)); + if (l.empty() || l[l.size() - 1] != '{') + throw msg_fmt( + "Parsing of object definition failed in file '{}' at line {}; " + "unexpected start definition", + path, current_line); + l = absl::StripTrailingAsciiWhitespace(l.substr(0, l.size() - 1)); + type = std::string(l.data(), l.size()); + if (type == "contact") { + msg = std::make_unique<Contact>(); + msg_helper = + std::make_unique<contact_helper>(static_cast<Contact*>(msg.get())); + } else if (type == "host") { + msg = std::make_unique<Host>(); + msg_helper = + std::make_unique<host_helper>(static_cast<Host*>(msg.get())); + } else if (type == "service") { + msg = std::make_unique<Service>(); + msg_helper = + std::make_unique<service_helper>(static_cast<Service*>(msg.get())); + } else if (type == "anomalydetection") { + msg = std::make_unique<Anomalydetection>(); + msg_helper = std::make_unique<anomalydetection_helper>( + static_cast<Anomalydetection*>(msg.get())); + } else if (type == "hostdependency") { + msg = std::make_unique<Hostdependency>(); + msg_helper = std::make_unique<hostdependency_helper>( + static_cast<Hostdependency*>(msg.get())); + } else if (type == "servicedependency") { + msg = std::make_unique<Servicedependency>(); + msg_helper = std::make_unique<servicedependency_helper>( + static_cast<Servicedependency*>(msg.get())); + } else if (type == "timeperiod") { + msg = std::make_unique<Timeperiod>(); + msg_helper = std::make_unique<timeperiod_helper>( + static_cast<Timeperiod*>(msg.get())); + } else if (type == "command") { + msg = std::make_unique<Command>(); + msg_helper = + std::make_unique<command_helper>(static_cast<Command*>(msg.get())); + } else if (type == "hostgroup") { + msg = std::make_unique<Hostgroup>(); + msg_helper = std::make_unique<hostgroup_helper>( + static_cast<Hostgroup*>(msg.get())); + } else if (type == "servicegroup") { + msg = std::make_unique<Servicegroup>(); + msg_helper = std::make_unique<servicegroup_helper>( + static_cast<Servicegroup*>(msg.get())); + } else if (type == "tag") { + msg = std::make_unique<Tag>(); + msg_helper = std::make_unique<tag_helper>(static_cast<Tag*>(msg.get())); + } else if (type == "contactgroup") { + msg = std::make_unique<Contactgroup>(); + msg_helper = std::make_unique<contactgroup_helper>( + static_cast<Contactgroup*>(msg.get())); + } else if (type == "connector") { + msg = std::make_unique<Connector>(); + msg_helper = std::make_unique<connector_helper>( + static_cast<Connector*>(msg.get())); + } else if (type == "severity") { + msg = std::make_unique<Severity>(); + msg_helper = std::make_unique<severity_helper>( + static_cast<Severity*>(msg.get())); + } else if (type == "serviceescalation") { + msg = std::make_unique<Serviceescalation>(); + msg_helper = std::make_unique<serviceescalation_helper>( + static_cast<Serviceescalation*>(msg.get())); + } else if (type == "hostescalation") { + msg = std::make_unique<Hostescalation>(); + msg_helper = std::make_unique<hostescalation_helper>( + static_cast<Hostescalation*>(msg.get())); + } else { + _logger->error("Type '{}' not yet supported by the parser", type); + assert(1 == 18); + } + } + } +} + +#endif + +/** + * Add object into the list. + * + * @param[in] obj The object to add into the list. + */ +void parser::_add_object(object_ptr obj) { + if (obj->should_register()) + (this->*_store[obj->type()])(obj); +} + +/** + * Add template into the list. + * + * @param[in] obj The tempalte to add into the list. + */ +void parser::_add_template(object_ptr obj) { + std::string const& name(obj->name()); + if (name.empty()) + throw msg_fmt("Parsing of {} failed {}: Property 'name' is missing", + obj->type_name(), _get_file_info(obj.get())); + map_object& tmpl(_templates[obj->type()]); + if (tmpl.find(name) != tmpl.end()) + throw msg_fmt("Parsing of {} failed {}: '{}' already exists", + obj->type_name(), _get_file_info(obj.get()), name); + tmpl[name] = obj; +} + +/** + * Get the file information. + * + * @param[in] obj The object to get file informations. + * + * @return The file informations object. + */ +file_info const& parser::_get_file_info(object* obj) const { + if (obj) { + std::unordered_map<object*, file_info>::const_iterator it( + _objects_info.find(obj)); + if (it != _objects_info.end()) + return it->second; + } + throw msg_fmt( + "Parsing failed: Object not found into the file information cache"); +} + +/** + * Build the hosts list with hostgroups. + * + * @param[in] hostgroups The hostgroups. + * @param[in,out] hosts The host list to fill. + */ +void parser::_get_hosts_by_hostgroups(hostgroup const& hostgroups, + list_host& hosts) { + _get_objects_by_list_name(hostgroups.members(), _map_objects[object::host], + hosts); +} + +/** + * Build the hosts list with list of hostgroups. + * + * @param[in] hostgroups The hostgroups list. + * @param[in,out] hosts The host list to fill. + */ +void parser::_get_hosts_by_hostgroups_name(set_string const& lst_group, + list_host& hosts) { + map_object& gl_hostgroups(_map_objects[object::hostgroup]); + for (set_string::const_iterator it(lst_group.begin()), end(lst_group.end()); + it != end; ++it) { + map_object::iterator it_hostgroups(gl_hostgroups.find(*it)); + if (it_hostgroups != gl_hostgroups.end()) + _get_hosts_by_hostgroups( + *static_cast<configuration::hostgroup*>(it_hostgroups->second.get()), + hosts); + } +} + +/** + * Build the object list with list of object name. + * + * @param[in] lst The object name list. + * @param[in] objects The object map to find object name. + * @param[in,out] out The list to fill. + */ +template <typename T> +void parser::_get_objects_by_list_name(set_string const& lst, + map_object& objects, + std::list<T>& out) { + for (set_string::const_iterator it(lst.begin()), end(lst.end()); it != end; + ++it) { + map_object::iterator it_obj(objects.find(*it)); + if (it_obj != objects.end()) + out.push_back(*static_cast<T*>(it_obj->second.get())); + } +} + +/** + * Insert objects into type T list and sort the new list by object id. + * + * @param[in] from The objects source. + * @param[out] to The objects destination. + */ +template <typename T> +void parser::_insert(list_object const& from, std::set<T>& to) { + for (list_object::const_iterator it(from.begin()), end(from.end()); it != end; + ++it) + to.insert(*static_cast<T const*>(it->get())); +} + +/** + * Insert objects into type T list and sort the new list by object id. + * + * @param[in] from The objects source. + * @param[out] to The objects destination. + */ +template <typename T> +void parser::_insert(map_object const& from, std::set<T>& to) { + for (map_object::const_iterator it(from.begin()), end(from.end()); it != end; + ++it) + to.insert(*static_cast<T*>(it->second.get())); +} + +/** + * Get the map object type name. + * + * @param[in] objects The map object. + * + * @return The type name. + */ +std::string const& parser::_map_object_type(map_object const& objects) const + throw() { + static std::string const empty(""); + map_object::const_iterator it(objects.begin()); + if (it == objects.end()) + return empty; + return it->second->type_name(); +} + +/** + * Store object into the list. + * + * @param[in] obj The object to store. + */ +void parser::_store_into_list(object_ptr obj) { + _lst_objects[obj->type()].push_back(obj); +} + +/** + * Store object into the map. + * + * @param[in] obj The object to store. + */ +template <typename T, std::string const& (T::*ptr)() const throw()> +void parser::_store_into_map(object_ptr obj) { + std::shared_ptr<T> real(std::static_pointer_cast<T>(obj)); + map_object::iterator it(_map_objects[obj->type()].find((real.get()->*ptr)())); + if (it != _map_objects[obj->type()].end()) + throw error("Parsing of {} failed {}: {} already exists", obj->type_name(), + _get_file_info(obj.get()), obj->name()); + _map_objects[obj->type()][(real.get()->*ptr)()] = real; +} diff --git a/common/engine_conf/parser.hh b/common/engine_conf/parser.hh new file mode 100644 index 00000000000..72e6c255552 --- /dev/null +++ b/common/engine_conf/parser.hh @@ -0,0 +1,124 @@ +/** + * Copyright 2011-2013,2017-2024 Centreon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ +#ifndef CCE_CONFIGURATION_PARSER_HH +#define CCE_CONFIGURATION_PARSER_HH + +#include <fstream> +#include "common/engine_conf/file_info.hh" +#include "state_helper.hh" +// #include "host.hh" + +namespace com::centreon::engine::configuration { + +/** + * @brief Each instance of a pb_map_object is about one type of message, for + * example it contains only commands. It is just a map containing commands + * indexed by their name. + */ +using pb_map_object = + absl::flat_hash_map<std::string, std::unique_ptr<Message>>; + +/** + * @brief A Protobuf message has only predefined default values, 0 for integers, + * empty for an array, etc. And we cannot change these default values. Because + * we still work with cfg files, we also have some tricks when reading values, + * some arrays are stored as strings, some bitfields are also stored as strings, + * etc. So we need a helper to proceed in these operations. This is what is a + * message_helper. Each protobuf configuration message has its own helper. The + * map below makes the relation between a new message and its helper. + */ +using pb_map_helper = + absl::flat_hash_map<Message*, std::unique_ptr<message_helper>>; + +class parser { + std::shared_ptr<spdlog::logger> _logger; + + enum object_type { + command = 0, + connector = 1, + contact = 2, + contactgroup = 3, + host = 4, + hostdependency = 5, + hostescalation = 6, + hostextinfo = 7, + hostgroup = 8, + service = 9, + servicedependency = 10, + serviceescalation = 11, + serviceextinfo = 12, + servicegroup = 13, + timeperiod = 14, + anomalydetection = 15, + severity = 16, + tag = 17, + }; + + /** + * @brief An array of pb_map_objects. At index object_type::command we get all + * the templates of commands, at index object_type::service we get all the + * templates of services, etc. + */ + std::array<pb_map_object, 19> _pb_templates; + + /** + * @brief The map of helpers of all the configuration objects parsed by this + * parser. + */ + pb_map_helper _pb_helper; + + void _merge(std::unique_ptr<message_helper>& msg_helper, Message* tmpl); + void _cleanup(State* pb_config); + + public: + parser(); + parser(const parser&) = delete; + parser& operator=(const parser&) = delete; + ~parser() noexcept = default; + void parse(const std::string& path, State* config, error_cnt& err); + + private: + /** + * Apply parse method into list. + * + * @param[in] lst The list to apply action. + * @param[in] pfunc The method to apply. + */ + template <typename L> + void _apply(const L& lst, + State* pb_config, + void (parser::*pfunc)(const std::string&, State*)) { + for (auto& f : lst) + (this->*pfunc)(f, pb_config); + } + void _parse_directory_configuration(std::string const& path, + State* pb_config); + void _parse_global_configuration(const std::string& path, State* pb_config); + void _parse_object_definitions(const std::string& path, State* pb_config); + void _parse_resource_file(std::string const& path, State* pb_config); + void _resolve_template(State* pb_config, error_cnt& err); + void _resolve_template(std::unique_ptr<message_helper>& msg_helper, + const pb_map_object& tmpls); + + unsigned int _current_line; + std::string _current_path; +}; +} // namespace com::centreon::engine::configuration + +#endif // !CCE_CONFIGURATION_PARSER_HH diff --git a/common/engine_conf/parser.hh.old b/common/engine_conf/parser.hh.old new file mode 100644 index 00000000000..2ea3d7b5033 --- /dev/null +++ b/common/engine_conf/parser.hh.old @@ -0,0 +1,162 @@ +/** + * Copyright 2011-2013,2017 Centreon + * + * This file is part of Centreon Engine. + * + * Centreon Engine is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * Centreon Engine 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 Centreon Engine. If not, see + * <http://www.gnu.org/licenses/>. + */ + +#ifndef CCE_CONFIGURATION_PARSER_HH +#define CCE_CONFIGURATION_PARSER_HH + +#include "common/engine_conf/state.pb.h" + +#include <fstream> + +#include "common/log_v2/log_v2.hh" +#include "message_helper.hh" + +namespace com::centreon::engine { + +namespace configuration { +using Message = ::google::protobuf::Message; +using pb_map_object = + absl::flat_hash_map<std::string, std::unique_ptr<Message>>; +using pb_map_helper = + absl::flat_hash_map<Message*, std::unique_ptr<message_helper>>; + +class parser { + uint32_t _config_warnings = 0; + uint32_t _config_errors = 0; +#ifndef LEGACY_CONF + void _cleanup(State* pb_config); + void _check_validity(const Message& msg, const char* const* mandatory) const; + bool _is_registered(const Message& msg) const; +#endif + + public: + enum read_options { + read_commands = (1 << 0), + read_connector = (1 << 1), + read_contact = (1 << 2), + read_contactgroup = (1 << 3), + read_host = (1 << 4), + read_hostdependency = (1 << 5), + read_hostescalation = (1 << 6), + read_hostgroup = (1 << 8), + read_hostgroupescalation = (1 << 9), + read_service = (1 << 10), + read_servicedependency = (1 << 11), + read_serviceescalation = (1 << 12), + read_servicegroup = (1 << 14), + read_timeperiod = (1 << 15), + read_all = (~0) + }; + + parser(unsigned int read_options = read_all); + ~parser() noexcept = default; +#ifdef LEGACY_CONF + void parse(const std::string& path, state& config); +#else + void parse(const std::string& path, State* pb_config); +#endif + + uint32_t config_warnings() const { + return _config_warnings; + } + uint32_t config_errors() const { + return _config_errors; + } + + private: + typedef void (parser::*store)(object_ptr obj); + + parser(parser const& right); + parser& operator=(parser const& right); + void _add_object(object_ptr obj); + void _add_template(object_ptr obj); + template <typename T> + void _apply(const T& lst, void (parser::*pfunc)(const std::string&)) { + for (auto& f : lst) + (this->*pfunc)(f); + } + + template <typename S, typename L> + void _apply(const L& lst, + S* state, + void (parser::*pfunc)(const std::string&, S*)) { + for (auto& f : lst) + (this->*pfunc)(f, state); + } + + file_info const& _get_file_info(object* obj) const; + void _get_hosts_by_hostgroups(hostgroup const& hostgroups, list_host& hosts); + void _get_hosts_by_hostgroups_name(set_string const& lst_group, + list_host& hosts); + template <typename T> + void _get_objects_by_list_name(set_string const& lst, + map_object& objects, + std::list<T>& out); + + template <typename T> + static void _insert(list_object const& from, std::set<T>& to); + template <typename T> + static void _insert(map_object const& from, std::set<T>& to); + std::string const& _map_object_type(map_object const& objects) const throw(); + void _parse_directory_configuration(std::string const& path); +#ifdef LEGACY_CONF + void _parse_object_definitions(const std::string& path); + void _parse_resource_file(std::string const& path); + void _resolve_template(); + void _parse_global_configuration(std::string const& path); +#else + void _parse_directory_configuration(const std::string& path, + State* pb_config); + void _parse_object_definitions(const std::string& path, State* pb_config); + void _parse_resource_file(const std::string& path, State* pb_config); + void _resolve_template(State* pb_config); + void _resolve_template(std::unique_ptr<message_helper>& msg_helper, + const pb_map_object& tmpls); + void _merge(std::unique_ptr<message_helper>& msg_helper, Message* tmpl); + void _parse_global_configuration(std::string const& path, State* pb_config); +#endif + void _store_into_list(object_ptr obj); + template <typename T, std::string const& (T::*ptr)() const throw()> + void _store_into_map(object_ptr obj); + +#ifdef LEGACY_CONF + state* _config; +#endif + unsigned int _current_line; + std::string _current_path; + std::array<list_object, 19> _lst_objects; + std::array<map_object, 19> _map_objects; + std::unordered_map<object*, file_info> _objects_info; + unsigned int _read_options; + static store _store[]; + std::array<map_object, 19> _templates; + +#ifndef LEGACY_CONF + std::array<pb_map_object, 19> _pb_templates; + pb_map_helper _pb_helper; +#endif + + /* Configuration Logger */ + std::shared_ptr<spdlog::logger> _logger; +}; +} // namespace configuration + +} // namespace com::centreon::engine + +#endif // !CCE_CONFIGURATION_PARSER_HH diff --git a/common/engine_conf/service_helper.cc b/common/engine_conf/service_helper.cc new file mode 100644 index 00000000000..206721bbb5b --- /dev/null +++ b/common/engine_conf/service_helper.cc @@ -0,0 +1,303 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ +#include "service_helper.hh" + +#include "com/centreon/exceptions/msg_fmt.hh" + +using com::centreon::exceptions::msg_fmt; + +namespace com::centreon::engine::configuration { + +/** + * @brief Constructor from a Service object. + * + * @param obj The Service object on which this helper works. The helper is not + * the owner of this object. + */ +service_helper::service_helper(Service* obj) + : message_helper(object_type::service, + obj, + { + {"host", "host_name"}, + {"hosts", "host_name"}, + {"_SERVICE_ID", "service_id"}, + {"description", "service_description"}, + {"service_groups", "servicegroups"}, + {"contact_groups", "contactgroups"}, + {"normal_check_interval", "check_interval"}, + {"retry_check_interval", "retry_interval"}, + {"active_checks_enabled", "checks_active"}, + {"passive_checks_enabled", "checks_passive"}, + {"severity", "severity_id"}, + }, + 50) { + _init(); +} + +/** + * @brief For several keys, the parser of Service objects has a particular + * behavior. These behaviors are handled here. + * @param key The key to parse. + * @param value The value corresponding to the key + */ +bool service_helper::hook(std::string_view key, const std::string_view& value) { + Service* obj = static_cast<Service*>(mut_obj()); + key = validate_key(key); + if (key == "contactgroups") { + fill_string_group(obj->mutable_contactgroups(), value); + return true; + } else if (key == "contacts") { + fill_string_group(obj->mutable_contacts(), value); + return true; + } else if (key == "flap_detection_options") { + uint16_t options(action_svc_none); + auto values = absl::StrSplit(value, ','); + for (auto it = values.begin(); it != values.end(); ++it) { + std::string_view v = absl::StripAsciiWhitespace(*it); + if (v == "o" || v == "ok") + options |= action_svc_ok; + else if (v == "w" || v == "warning") + options |= action_svc_warning; + else if (v == "u" || v == "unknown") + options |= action_svc_unknown; + else if (v == "c" || v == "critical") + options |= action_svc_critical; + else if (v == "n" || v == "none") + options |= action_svc_none; + else if (v == "a" || v == "all") + options = action_svc_ok | action_svc_warning | action_svc_unknown | + action_svc_critical; + else + return false; + } + obj->set_flap_detection_options(options); + return true; + } else if (key == "initial_state") { + ServiceStatus initial_state; + if (value == "o" || value == "ok") + initial_state = ServiceStatus::state_ok; + else if (value == "w" || value == "warning") + initial_state = ServiceStatus::state_warning; + else if (value == "u" || value == "unknown") + initial_state = ServiceStatus::state_unknown; + else if (value == "c" || value == "critical") + initial_state = ServiceStatus::state_critical; + else + return false; + obj->set_initial_state(initial_state); + return true; + } else if (key == "notification_options") { + uint16_t options(action_svc_none); + auto values = absl::StrSplit(value, ','); + for (auto it = values.begin(); it != values.end(); ++it) { + std::string_view v = absl::StripAsciiWhitespace(*it); + if (v == "u" || v == "unknown") + options |= action_svc_unknown; + else if (v == "w" || v == "warning") + options |= action_svc_warning; + else if (v == "c" || v == "critical") + options |= action_svc_critical; + else if (v == "r" || v == "recovery") + options |= action_svc_ok; + else if (v == "f" || v == "flapping") + options |= action_svc_flapping; + else if (v == "s" || v == "downtime") + options |= action_svc_downtime; + else if (v == "n" || v == "none") + options = action_svc_none; + else if (v == "a" || v == "all") + options = action_svc_unknown | action_svc_warning | + action_svc_critical | action_svc_ok | action_svc_flapping | + action_svc_downtime; + else + return false; + } + obj->set_notification_options(options); + return true; + } else if (key == "servicegroups") { + fill_string_group(obj->mutable_servicegroups(), value); + return true; + } else if (key == "stalking_options") { + uint16_t options(action_svc_none); + auto values = absl::StrSplit(value, ','); + for (auto it = values.begin(); it != values.end(); ++it) { + std::string_view v = absl::StripAsciiWhitespace(*it); + if (v == "u" || v == "unknown") + options |= action_svc_unknown; + else if (v == "o" || v == "ok") + options |= action_svc_ok; + else if (v == "w" || v == "warning") + options |= action_svc_warning; + else if (v == "c" || v == "critical") + options |= action_svc_critical; + else if (v == "n" || v == "none") + options = action_svc_none; + else if (v == "a" || v == "all") + options = action_svc_ok | action_svc_unknown | action_svc_warning | + action_svc_critical; + else + return false; + } + obj->set_stalking_options(options); + return true; + } else if (key == "category_tags") { + auto tags{absl::StrSplit(value, ',')}; + bool ret = true; + + for (auto it = obj->tags().begin(); it != obj->tags().end();) { + if (it->second() == TagType::tag_servicecategory) + it = obj->mutable_tags()->erase(it); + else + ++it; + } + + for (auto& tag : tags) { + uint64_t id; + bool parse_ok; + parse_ok = absl::SimpleAtoi(tag, &id); + if (parse_ok) { + auto t = obj->add_tags(); + t->set_first(id); + t->set_second(TagType::tag_servicecategory); + } else { + ret = false; + } + } + return ret; + } else if (key == "group_tags") { + auto tags{absl::StrSplit(value, ',')}; + bool ret = true; + + for (auto it = obj->tags().begin(); it != obj->tags().end();) { + if (it->second() == TagType::tag_servicegroup) + it = obj->mutable_tags()->erase(it); + else + ++it; + } + + for (auto& tag : tags) { + uint64_t id; + bool parse_ok; + parse_ok = absl::SimpleAtoi(tag, &id); + if (parse_ok) { + auto t = obj->add_tags(); + t->set_first(id); + t->set_second(TagType::tag_servicegroup); + } else { + ret = false; + } + } + return ret; + } + return false; +} + +/** + * @brief Check the validity of the Service object. + * + * @param err An error counter. + */ +void service_helper::check_validity(error_cnt& err) const { + const Service* o = static_cast<const Service*>(obj()); + + if (o->obj().register_()) { + if (o->service_description().empty()) { + err.config_errors++; + throw msg_fmt("Services must have a non-empty description"); + } + if (o->check_command().empty()) { + err.config_errors++; + throw msg_fmt("Service '{}' has an empty check command", + o->service_description()); + } + if (o->host_name().empty()) { + err.config_errors++; + throw msg_fmt("Service '{}' must contain one host name", + o->service_description()); + } + } +} + +/** + * @brief Initializer of the Service object, in other words set its default + * values. + */ +void service_helper::_init() { + Service* obj = static_cast<Service*>(mut_obj()); + obj->mutable_obj()->set_register_(true); + obj->set_acknowledgement_timeout(0); + obj->set_checks_active(true); + obj->set_checks_passive(true); + obj->set_check_freshness(0); + obj->set_check_interval(5); + obj->set_event_handler_enabled(true); + obj->set_first_notification_delay(0); + obj->set_flap_detection_enabled(true); + obj->set_flap_detection_options(action_svc_ok | action_svc_warning | + action_svc_unknown | action_svc_critical); + obj->set_freshness_threshold(0); + obj->set_high_flap_threshold(0); + obj->set_initial_state(ServiceStatus::state_ok); + obj->set_is_volatile(false); + obj->set_low_flap_threshold(0); + obj->set_max_check_attempts(3); + obj->set_notifications_enabled(true); + obj->set_notification_interval(0); + obj->set_notification_options(action_svc_ok | action_svc_warning | + action_svc_critical | action_svc_unknown | + action_svc_flapping | action_svc_downtime); + obj->set_obsess_over_service(true); + obj->set_process_perf_data(true); + obj->set_retain_nonstatus_information(true); + obj->set_retain_status_information(true); + obj->set_retry_interval(1); + obj->set_stalking_options(action_svc_none); +} + +/** + * @brief If the provided key/value have their parsing to fail previously, + * it is possible they are a customvariable. A customvariable name has its + * name starting with an underscore. This method checks the possibility to + * store a customvariable in the given object and stores it if possible. + * + * @param key The name of the customvariable. + * @param value Its value as a string. + * + * @return True if the customvariable has been well stored. + */ +bool service_helper::insert_customvariable(std::string_view key, + std::string_view value) { + if (key[0] != '_') + return false; + + key.remove_prefix(1); + Service* obj = static_cast<Service*>(mut_obj()); + auto* cvs = obj->mutable_customvariables(); + for (auto& c : *cvs) { + if (c.name() == key) { + c.set_value(value.data(), value.size()); + return true; + } + } + auto new_cv = cvs->Add(); + new_cv->set_name(key.data(), key.size()); + new_cv->set_value(value.data(), value.size()); + return true; +} +} // namespace com::centreon::engine::configuration diff --git a/common/engine_conf/service_helper.hh b/common/engine_conf/service_helper.hh new file mode 100644 index 00000000000..3104c0b2601 --- /dev/null +++ b/common/engine_conf/service_helper.hh @@ -0,0 +1,43 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#ifndef CCE_CONFIGURATION_SERVICE +#define CCE_CONFIGURATION_SERVICE + +#include "common/engine_conf/message_helper.hh" +#include "common/engine_conf/state.pb.h" + +namespace com::centreon::engine::configuration { + +class service_helper : public message_helper { + void _init(); + + public: + service_helper(Service* obj); + ~service_helper() noexcept = default; + void check_validity(error_cnt& err) const override; + + bool hook(std::string_view key, const std::string_view& value) override; + + bool insert_customvariable(std::string_view key, + std::string_view value) override; +}; +} // namespace com::centreon::engine::configuration + +#endif /* !CCE_CONFIGURATION_SERVICE */ diff --git a/common/engine_conf/servicedependency_helper.cc b/common/engine_conf/servicedependency_helper.cc new file mode 100644 index 00000000000..4785cd32e31 --- /dev/null +++ b/common/engine_conf/servicedependency_helper.cc @@ -0,0 +1,196 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ +#include "common/engine_conf/servicedependency_helper.hh" + +#include "com/centreon/exceptions/msg_fmt.hh" + +using com::centreon::exceptions::msg_fmt; + +namespace com::centreon::engine::configuration { + +size_t servicedependency_key(const Servicedependency& sd) { + return absl::HashOf(sd.dependency_period(), sd.dependency_type(), + sd.hosts().data(0), sd.service_description().data(0), + sd.dependent_hosts().data(0), + sd.dependent_service_description().data(0), + sd.execution_failure_options(), sd.inherits_parent(), + sd.notification_failure_options()); +} + +/** + * @brief Constructor from a Servicedependency object. + * + * @param obj The Servicedependency object on which this helper works. The + * helper is not the owner of this object. + */ +servicedependency_helper::servicedependency_helper(Servicedependency* obj) + : message_helper( + object_type::servicedependency, + obj, + { + {"servicegroup", "servicegroups"}, + {"servicegroup_name", "servicegroups"}, + {"hostgroup", "hostgroups"}, + {"hostgroup_name", "hostgroups"}, + {"host", "hosts"}, + {"host_name", "hosts"}, + {"master_host", "hosts"}, + {"master_host_name", "hosts"}, + {"description", "service_description"}, + {"master_description", "service_description"}, + {"master_service_description", "service_description"}, + {"dependent_servicegroup", "dependent_servicegroups"}, + {"dependent_servicegroup_name", "dependent_servicegroups"}, + {"dependent_hostgroup", "dependent_hostgroups"}, + {"dependent_hostgroup_name", "dependent_hostgroups"}, + {"dependent_host", "dependent_hosts"}, + {"dependent_host_name", "dependent_hosts"}, + {"dependent_description", "dependent_service_description"}, + {"execution_failure_criteria", "execution_failure_options"}, + {"notification_failure_criteria", "notification_failure_options"}, + }, + 15) { + _init(); +} + +/** + * @brief For several keys, the parser of Servicedependency objects has a + * particular behavior. These behaviors are handled here. + * @param key The key to parse. + * @param value The value corresponding to the key + */ +bool servicedependency_helper::hook(std::string_view key, + const std::string_view& value) { + Servicedependency* obj = static_cast<Servicedependency*>(mut_obj()); + key = validate_key(key); + + if (key == "execution_failure_options" || + key == "notification_failure_options") { + uint32_t options = action_sd_none; + auto arr = absl::StrSplit(value, ','); + for (auto& v : arr) { + std::string_view vv = absl::StripAsciiWhitespace(v); + if (vv == "o" || vv == "ok") + options |= action_sd_ok; + else if (vv == "u" || vv == "unknown") + options |= action_sd_unknown; + else if (vv == "w" || vv == "warning") + options |= action_sd_warning; + else if (vv == "c" || vv == "critical") + options |= action_sd_critical; + else if (vv == "p" || vv == "pending") + options |= action_sd_pending; + else if (vv == "n" || vv == "none") + options = action_sd_none; + else if (vv == "a" || vv == "all") + options = action_sd_ok | action_sd_warning | action_sd_critical | + action_sd_pending; + else + return false; + } + if (key[0] == 'e') + obj->set_execution_failure_options(options); + else + obj->set_notification_failure_options(options); + return true; + } else if (key == "dependent_hostgroups") { + fill_string_group(obj->mutable_dependent_hostgroups(), value); + return true; + } else if (key == "dependent_hosts") { + fill_string_group(obj->mutable_dependent_hosts(), value); + return true; + } else if (key == "dependent_servicegroups") { + fill_string_group(obj->mutable_dependent_servicegroups(), value); + return true; + } else if (key == "dependent_service_description") { + fill_string_group(obj->mutable_dependent_service_description(), value); + return true; + } else if (key == "hostgroups") { + fill_string_group(obj->mutable_hostgroups(), value); + return true; + } else if (key == "hosts") { + fill_string_group(obj->mutable_hosts(), value); + return true; + } else if (key == "servicegroups") { + fill_string_group(obj->mutable_servicegroups(), value); + return true; + } else if (key == "service_description") { + fill_string_group(obj->mutable_service_description(), value); + return true; + } + return false; +} + +/** + * @brief Check the validity of the Servicedependency object. + * + * @param err An error counter. + */ +void servicedependency_helper::check_validity(error_cnt& err) const { + const Servicedependency* o = static_cast<const Servicedependency*>(obj()); + + /* Check base service(s). */ + if (o->servicegroups().data().empty()) { + if (o->service_description().data().empty()) { + err.config_errors++; + throw msg_fmt( + "Service dependency is not attached to any service or service group " + "(properties 'service_description' or 'servicegroup_name', " + "respectively)"); + } else if (o->hosts().data().empty() && o->hostgroups().data().empty()) { + err.config_errors++; + throw msg_fmt( + "Service dependency is not attached to any host or host group " + "(properties 'host_name' or 'hostgroup_name', respectively)"); + } + } + + /* Check dependent service(s). */ + if (o->dependent_servicegroups().data().empty()) { + if (o->dependent_service_description().data().empty()) { + err.config_errors++; + throw msg_fmt( + "Service dependency is not attached to " + "any dependent service or dependent service group " + "(properties 'dependent_service_description' or " + "'dependent_servicegroup_name', respectively)"); + } else if (o->dependent_hosts().data().empty() && + o->dependent_hostgroups().data().empty()) { + err.config_errors++; + throw msg_fmt( + "Service dependency is not attached to " + "any dependent host or dependent host group (properties " + "'dependent_host_name' or 'dependent_hostgroup_name', " + "respectively)"); + } + } +} + +/** + * @brief Initializer of the Servicedependency object, in other words set its + * default values. + */ +void servicedependency_helper::_init() { + Servicedependency* obj = static_cast<Servicedependency*>(mut_obj()); + obj->mutable_obj()->set_register_(true); + obj->set_execution_failure_options(action_sd_none); + obj->set_inherits_parent(false); + obj->set_notification_failure_options(action_sd_none); +} +} // namespace com::centreon::engine::configuration diff --git a/common/engine_conf/servicedependency_helper.hh b/common/engine_conf/servicedependency_helper.hh new file mode 100644 index 00000000000..affa3b1df08 --- /dev/null +++ b/common/engine_conf/servicedependency_helper.hh @@ -0,0 +1,42 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#ifndef CCE_CONFIGURATION_SERVICEDEPENDENCY +#define CCE_CONFIGURATION_SERVICEDEPENDENCY + +#include "common/engine_conf/message_helper.hh" +#include "common/engine_conf/state.pb.h" + +namespace com::centreon::engine::configuration { + +size_t servicedependency_key(const Servicedependency& sd); + +class servicedependency_helper : public message_helper { + void _init(); + + public: + servicedependency_helper(Servicedependency* obj); + ~servicedependency_helper() noexcept = default; + void check_validity(error_cnt& err) const override; + + bool hook(std::string_view key, const std::string_view& value) override; +}; +} // namespace com::centreon::engine::configuration + +#endif /* !CCE_CONFIGURATION_SERVICEDEPENDENCY */ diff --git a/common/engine_conf/serviceescalation_helper.cc b/common/engine_conf/serviceescalation_helper.cc new file mode 100644 index 00000000000..2f50c09621b --- /dev/null +++ b/common/engine_conf/serviceescalation_helper.cc @@ -0,0 +1,149 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ +#include "common/engine_conf/serviceescalation_helper.hh" + +#include "com/centreon/exceptions/msg_fmt.hh" + +using com::centreon::exceptions::msg_fmt; + +namespace com::centreon::engine::configuration { + +size_t serviceescalation_key(const Serviceescalation& se) { + return absl::HashOf(se.hosts().data(0), se.service_description().data(0), + // se.contactgroups(), + se.escalation_options(), se.escalation_period(), + se.first_notification(), se.last_notification(), + se.notification_interval()); +} + +/** + * @brief Constructor from a Serviceescalation object. + * + * @param obj The Serviceescalation object on which this helper works. The + * helper is not the owner of this object. + */ +serviceescalation_helper::serviceescalation_helper(Serviceescalation* obj) + : message_helper(object_type::serviceescalation, + obj, + { + {"host", "hosts"}, + {"host_name", "hosts"}, + {"description", "service_description"}, + {"servicegroup", "servicegroups"}, + {"servicegroup_name", "servicegroups"}, + {"hostgroup", "hostgroups"}, + {"hostgroup_name", "hostgroups"}, + {"contact_groups", "contactgroups"}, + }, + 12) { + _init(); +} + +/** + * @brief For several keys, the parser of Serviceescalation objects has a + * particular behavior. These behaviors are handled here. + * @param key The key to parse. + * @param value The value corresponding to the key + */ +bool serviceescalation_helper::hook(std::string_view key, + const std::string_view& value) { + Serviceescalation* obj = static_cast<Serviceescalation*>(mut_obj()); + key = validate_key(key); + + if (key == "escalation_options") { + uint32_t options = action_he_none; + auto arr = absl::StrSplit(value, ','); + for (auto& v : arr) { + std::string_view vv = absl::StripAsciiWhitespace(v); + if (vv == "w" || vv == "warning") + options |= action_se_warning; + else if (vv == "u" || vv == "unknown") + options |= action_se_unknown; + else if (vv == "c" || vv == "critical") + options |= action_se_critical; + else if (vv == "r" || vv == "recovery") + options |= action_se_recovery; + else if (vv == "n" || vv == "none") + options = action_se_none; + else if (vv == "a" || vv == "all") + options = action_se_warning | action_se_unknown | action_se_critical | + action_se_recovery; + else + return false; + } + obj->set_escalation_options(options); + return true; + } else if (key == "contactgroups") { + fill_string_group(obj->mutable_contactgroups(), value); + return true; + } else if (key == "hostgroups") { + fill_string_group(obj->mutable_hostgroups(), value); + return true; + } else if (key == "hosts") { + fill_string_group(obj->mutable_hosts(), value); + return true; + } else if (key == "servicegroups") { + fill_string_group(obj->mutable_servicegroups(), value); + return true; + } else if (key == "service_description") { + fill_string_group(obj->mutable_service_description(), value); + return true; + } + return false; +} + +/** + * @brief Check the validity of the Serviceescalation object. + * + * @param err An error counter. + */ +void serviceescalation_helper::check_validity(error_cnt& err) const { + const Serviceescalation* o = static_cast<const Serviceescalation*>(obj()); + + if (o->servicegroups().data().empty()) { + if (o->service_description().data().empty()) { + err.config_errors++; + throw msg_fmt( + "Service escalation is not attached to " + "any service or service group (properties " + "'service_description' and 'servicegroup_name', " + "respectively)"); + } else if (o->hosts().data().empty() && o->hostgroups().data().empty()) { + err.config_errors++; + throw msg_fmt( + "Service escalation is not attached to " + "any host or host group (properties 'host_name' or " + "'hostgroup_name', respectively)"); + } + } +} + +/** + * @brief Initializer of the Serviceescalation object, in other words set its + * default values. + */ +void serviceescalation_helper::_init() { + Serviceescalation* obj = static_cast<Serviceescalation*>(mut_obj()); + obj->mutable_obj()->set_register_(true); + obj->set_escalation_options(action_se_none); + obj->set_first_notification(-2); + obj->set_last_notification(-2); + obj->set_notification_interval(0); +} +} // namespace com::centreon::engine::configuration diff --git a/common/engine_conf/serviceescalation_helper.hh b/common/engine_conf/serviceescalation_helper.hh new file mode 100644 index 00000000000..b161bd27c06 --- /dev/null +++ b/common/engine_conf/serviceescalation_helper.hh @@ -0,0 +1,42 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#ifndef CCE_CONFIGURATION_SERVICEESCALATION +#define CCE_CONFIGURATION_SERVICEESCALATION + +#include "common/engine_conf/message_helper.hh" +#include "common/engine_conf/state.pb.h" + +namespace com::centreon::engine::configuration { + +size_t serviceescalation_key(const Serviceescalation& se); + +class serviceescalation_helper : public message_helper { + void _init(); + + public: + serviceescalation_helper(Serviceescalation* obj); + ~serviceescalation_helper() noexcept = default; + void check_validity(error_cnt& err) const override; + + bool hook(std::string_view key, const std::string_view& value) override; +}; +} // namespace com::centreon::engine::configuration + +#endif /* !CCE_CONFIGURATION_SERVICEESCALATION */ diff --git a/common/engine_conf/servicegroup_helper.cc b/common/engine_conf/servicegroup_helper.cc new file mode 100644 index 00000000000..d50ee62299c --- /dev/null +++ b/common/engine_conf/servicegroup_helper.cc @@ -0,0 +1,76 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ +#include "common/engine_conf/servicegroup_helper.hh" + +#include "com/centreon/exceptions/msg_fmt.hh" + +using com::centreon::exceptions::msg_fmt; + +namespace com::centreon::engine::configuration { + +/** + * @brief Constructor from a Servicegroup object. + * + * @param obj The Servicegroup object on which this helper works. The helper is + * not the owner of this object. + */ +servicegroup_helper::servicegroup_helper(Servicegroup* obj) + : message_helper(object_type::servicegroup, obj, {}, 10) { + _init(); +} + +/** + * @brief For several keys, the parser of Servicegroup objects has a particular + * behavior. These behaviors are handled here. + * @param key The key to parse. + * @param value The value corresponding to the key + */ +bool servicegroup_helper::hook(std::string_view key, + const std::string_view& value) { + Servicegroup* obj = static_cast<Servicegroup*>(mut_obj()); + key = validate_key(key); + if (key == "members") { + fill_pair_string_group(obj->mutable_members(), value); + return true; + } else if (key == "servicegroup_members") { + fill_string_group(obj->mutable_servicegroup_members(), value); + return true; + } + return false; +} + +/** + * @brief Check the validity of the Servicegroup object. + * + * @param err An error counter. + */ +void servicegroup_helper::check_validity(error_cnt& err) const { + const Servicegroup* o = static_cast<const Servicegroup*>(obj()); + + if (o->servicegroup_name().empty()) { + err.config_errors++; + throw msg_fmt("Service group has no name (property 'servicegroup_name')"); + } +} + +void servicegroup_helper::_init() { + Servicegroup* obj = static_cast<Servicegroup*>(mut_obj()); + obj->mutable_obj()->set_register_(true); +} +} // namespace com::centreon::engine::configuration diff --git a/common/engine_conf/servicegroup_helper.hh b/common/engine_conf/servicegroup_helper.hh new file mode 100644 index 00000000000..39230a3b9a0 --- /dev/null +++ b/common/engine_conf/servicegroup_helper.hh @@ -0,0 +1,40 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#ifndef CCE_CONFIGURATION_SERVICEGROUP +#define CCE_CONFIGURATION_SERVICEGROUP + +#include "common/engine_conf/message_helper.hh" +#include "common/engine_conf/state.pb.h" + +namespace com::centreon::engine::configuration { + +class servicegroup_helper : public message_helper { + void _init(); + + public: + servicegroup_helper(Servicegroup* obj); + ~servicegroup_helper() noexcept = default; + void check_validity(error_cnt& err) const override; + + bool hook(std::string_view key, const std::string_view& value) override; +}; +} // namespace com::centreon::engine::configuration + +#endif /* !CCE_CONFIGURATION_SERVICEGROUP */ diff --git a/common/engine_conf/severity_helper.cc b/common/engine_conf/severity_helper.cc new file mode 100644 index 00000000000..b973f3b8527 --- /dev/null +++ b/common/engine_conf/severity_helper.cc @@ -0,0 +1,111 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ +#include "common/engine_conf/severity_helper.hh" + +#include "com/centreon/exceptions/msg_fmt.hh" + +using com::centreon::exceptions::msg_fmt; + +namespace com::centreon::engine::configuration { + +/** + * @brief Constructor from a Severity object. + * + * @param obj The Severity object on which this helper works. The helper is not + * the owner of this object. + */ +severity_helper::severity_helper(Severity* obj) + : message_helper(object_type::severity, + obj, + { + {"severity_id", "id"}, + {"severity_level", "level"}, + {"severity_icon_id", "icon_id"}, + {"severity_type", "type"}, + }, + 6) { + _init(); +} + +/** + * @brief For several keys, the parser of Severity objects has a particular + * behavior. These behaviors are handled here. + * @param key The key to parse. + * @param value The value corresponding to the key + */ +bool severity_helper::hook(std::string_view key, + const std::string_view& value) { + Severity* obj = static_cast<Severity*>(mut_obj()); + key = validate_key(key); + + if (key == "id" || key == "severity_id") { + uint64_t id; + if (absl::SimpleAtoi(value, &id)) + obj->mutable_key()->set_id(id); + else + return false; + return true; + } else if (key == "type" || key == "severity_type") { + if (value == "host") + obj->mutable_key()->set_type(SeverityType::host); + else if (value == "service") + obj->mutable_key()->set_type(SeverityType::service); + else + return false; + return true; + } + return false; +} + +/** + * @brief Check the validity of the Severity object. + * + * @param err An error counter. + */ +void severity_helper::check_validity(error_cnt& err) const { + const Severity* o = static_cast<const Severity*>(obj()); + + if (o->severity_name().empty()) + throw msg_fmt("Severity has no name (property 'severity_name')"); + if (o->key().id() == 0) { + err.config_errors++; + throw msg_fmt( + "Severity id must not be less than 1 (property 'severity_id')"); + } + if (o->level() == 0) { + err.config_errors++; + throw msg_fmt("Severity level must not be less than 1 (property 'level')"); + } + if (o->key().type() == SeverityType::none) { + err.config_errors++; + throw msg_fmt("Severity type must be one of 'service' or 'host'"); + } +} + +/** + * @brief Initializer of the Severity object, in other words set its default + * values. + */ +void severity_helper::_init() { + Severity* obj = static_cast<Severity*>(mut_obj()); + obj->mutable_obj()->set_register_(true); + obj->mutable_key()->set_id(0); + obj->mutable_key()->set_type(SeverityType::none); +} +} // namespace com::centreon::engine::configuration diff --git a/common/engine_conf/severity_helper.hh b/common/engine_conf/severity_helper.hh new file mode 100644 index 00000000000..ef54828d091 --- /dev/null +++ b/common/engine_conf/severity_helper.hh @@ -0,0 +1,41 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#ifndef CCE_CONFIGURATION_SEVERITY +#define CCE_CONFIGURATION_SEVERITY + +#include "common/engine_conf/message_helper.hh" +#include "common/engine_conf/state.pb.h" + +namespace com::centreon::engine::configuration { + +class severity_helper : public message_helper { + void _init(); + + public: + using key_type = std::pair<uint64_t, uint16_t>; + severity_helper(Severity* obj); + ~severity_helper() noexcept = default; + void check_validity(error_cnt& err) const override; + + bool hook(std::string_view key, const std::string_view& value) override; +}; +} // namespace com::centreon::engine::configuration + +#endif /* !CCE_CONFIGURATION_SEVERITY */ diff --git a/common/engine_conf/state.proto b/common/engine_conf/state.proto new file mode 100644 index 00000000000..a773c32c128 --- /dev/null +++ b/common/engine_conf/state.proto @@ -0,0 +1,770 @@ +/** + * Copyright 2022-2024 Centreon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + */ + +syntax = "proto3"; + +package com.centreon.engine.configuration; + +enum DateType { + us = 0; // U.S. (MM-DD-YYYY HH:MM:SS) + euro = 1; // European (DD-MM-YYYY HH:MM:SS) + iso8601 = 2; // ISO8601 (YYYY-MM-DD HH:MM:SS) + strict_iso8601 = 3; // ISO8601 (YYYY-MM-DDTHH:MM:SS) +} + +message InterCheckDelay { + enum IcdType { + none = 0; // no inter-check delay + dumb = 1; // dumb delay of 1 second + smart = 2; // smart delay + user = 3; // user-specified delay + } + IcdType type = 1; + double user_value = 2; +} + +message InterleaveFactor { + enum IFType { + ilf_user = 0; // user-specified interleave factor + ilf_smart = 1; // smart interleave + } + IFType type = 1; + int32 user_value = 2; +} + +enum LogLevel { + off = 0; + critical = 1; + error = 2; + warning = 3; + info = 4; + debug = 5; + trace = 6; +} + +message State { + string cfg_main = 1; + repeated string cfg_file = 2; + repeated string resource_file = 3; + int32 instance_heartbeat_interval = 4; + bool check_service_freshness = 5; + bool enable_flap_detection = 6; + string rpc_listen_address = 7; + uint32 grpc_port = 8; + map<string, string> users = 9; + repeated string cfg_dir = 10; + string state_retention_file = 11; + repeated string broker_module = 12; + string broker_module_directory = 13; + bool enable_macros_filter = 14; + StringSet macros_filter = 15; + + bool log_v2_enabled = 16; + bool log_legacy_enabled = 17; + bool use_syslog = 18; + string log_v2_logger = 19; + string log_file = 20; + string debug_file = 21; + uint64 debug_level = 22; + uint32 debug_verbosity = 23; + uint32 max_debug_file_size = 24; + bool log_pid = 25; + bool log_file_line = 26; + int32 log_flush_period = 27; + LogLevel log_level_checks = 28; + LogLevel log_level_commands = 29; + LogLevel log_level_comments = 30; + LogLevel log_level_config = 31; + LogLevel log_level_downtimes = 32; + LogLevel log_level_eventbroker = 33; + LogLevel log_level_events = 34; + LogLevel log_level_external_command = 35; + LogLevel log_level_functions = 36; + LogLevel log_level_macros = 37; + LogLevel log_level_notifications = 38; + LogLevel log_level_process = 39; + LogLevel log_level_runtime = 40; + LogLevel log_level_otl = 41; + string global_host_event_handler = 42; + string global_service_event_handler = 43; + string illegal_object_chars = 44; + string illegal_output_chars = 45; + uint32 interval_length = 46; + string ochp_command = 47; + string ocsp_command = 48; + string use_timezone = 49; + bool accept_passive_host_checks = 50; + bool accept_passive_service_checks = 51; + int32 additional_freshness_latency = 52; + uint32 cached_host_check_horizon = 53; + bool check_external_commands = 54; + bool check_host_freshness = 55; + uint32 check_reaper_interval = 56; + bool enable_event_handlers = 57; + bool enable_notifications = 58; + bool execute_host_checks = 59; + bool execute_service_checks = 60; + uint32 max_host_check_spread = 61; + uint32 max_service_check_spread = 62; + uint32 notification_timeout = 63; + bool obsess_over_hosts = 64; + bool obsess_over_services = 65; + bool process_performance_data = 66; + bool soft_state_dependencies = 67; + bool use_large_installation_tweaks = 68; + string admin_email = 69; + string admin_pager = 70; + bool allow_empty_hostgroup_assignment = 71; + string command_file = 72; + string status_file = 73; + string poller_name = 74; + uint32 poller_id = 75; + bool auto_reschedule_checks = 76; + uint32 auto_rescheduling_interval = 77; + uint32 auto_rescheduling_window = 78; + uint32 cached_service_check_horizon = 79; + bool check_orphaned_hosts = 80; + bool check_orphaned_services = 81; + int32 command_check_interval = 82; + bool command_check_interval_is_seconds = 83; + bool enable_environment_macros = 84; + uint32 event_broker_options = 85; + uint32 event_handler_timeout = 86; + int32 external_command_buffer_slots = 87; + float high_host_flap_threshold = 88; + float high_service_flap_threshold = 89; + int32 host_check_timeout = 90; + uint32 host_freshness_check_interval = 91; + uint32 service_freshness_check_interval = 92; + bool log_event_handlers = 93; + bool log_external_commands = 94; + bool log_notifications = 95; + bool log_passive_checks = 96; + bool log_host_retries = 97; + bool log_service_retries = 98; + uint32 max_log_file_size = 99; + float low_host_flap_threshold = 100; + float low_service_flap_threshold = 101; + uint32 max_parallel_service_checks = 102; + uint32 ochp_timeout = 103; + uint32 ocsp_timeout = 104; + int32 perfdata_timeout = 105; + uint32 retained_host_attribute_mask = 106; + uint32 retained_process_host_attribute_mask = 107; + uint32 retained_contact_host_attribute_mask = 108; + uint32 retained_contact_service_attribute_mask = 109; + bool retain_state_information = 110; + uint32 retention_scheduling_horizon = 111; + uint32 retention_update_interval = 112; + uint32 service_check_timeout = 113; + float sleep_time = 114; + uint32 status_update_interval = 115; + uint32 time_change_threshold = 116; + bool use_regexp_matches = 117; + bool use_retained_program_state = 118; + bool use_retained_scheduling_info = 119; + bool use_setpgid = 120; + bool use_true_regexp_matching = 121; + DateType date_format = 122; + InterCheckDelay host_inter_check_delay_method = 123; + InterCheckDelay service_inter_check_delay_method = 124; + InterleaveFactor service_interleave_factor_method = 125; + bool enable_predictive_host_dependency_checks = 126; + bool enable_predictive_service_dependency_checks = 127; + bool send_recovery_notifications_anyways = 128; + bool host_down_disable_service_checks = 129; + + repeated Command commands = 130; + repeated Connector connectors = 131; + repeated Contact contacts = 132; + repeated Contactgroup contactgroups = 133; + repeated Hostdependency hostdependencies = 134; + repeated Hostescalation hostescalations = 135; + repeated Hostgroup hostgroups = 136; + repeated Host hosts = 137; + repeated Servicedependency servicedependencies = 138; + repeated Serviceescalation serviceescalations = 139; + repeated Servicegroup servicegroups = 140; + repeated Service services = 141; + repeated Anomalydetection anomalydetections = 142; + repeated Timeperiod timeperiods = 143; + repeated Severity severities = 144; + repeated Tag tags = 145; + map<string, string> user = 146; +} + +message Value { + oneof value { + bool value_b = 1; + int32 value_i32 = 2; + uint32 value_u32 = 3; + string value_str = 4; + Timerange value_tr = 5; + Daterange value_dr = 6; + Timeperiod value_tp = 7; + Connector value_cn = 8; + Command value_co = 9; + CustomVariable value_cv = 10; + Contact value_ct = 11; + } +} + +message Key { + oneof key { + int32 i32 = 1; + string str = 2; + } +} + +message Path { + repeated Key key = 1; +} + +message PathWithValue { + Path path = 1; + Value val = 2; +} + +message PathWithPair { + Path path = 1; + Value val1 = 2; + Value val2 = 3; +} + +message DiffState { + repeated PathWithValue to_add = 1; + repeated Path to_remove = 2; + repeated PathWithValue to_modify = 3; +} + +message CustomVariable { + string name = 1; + string value = 2; + bool is_sent = 3; + // bool modified = 4; +} + +enum HostStatus { + state_up = 0; + state_down = 1; + state_unreachable = 2; +} + +enum ServiceStatus { + state_ok = 0; + state_warning = 1; + state_critical = 2; + state_unknown = 3; +} + +enum SeverityType { + service = 0; + host = 1; + none = 2; +} + +message Object { + string name = 1; + bool register = 2; + repeated string use = 3; +} + +message Point2d { + int32 x = 1; + int32 y = 2; +} + +message Point3d { + double x = 1; + double y = 2; + double z = 3; +} + +message KeyType { + uint64 id = 1; + uint32 type = 2; +} + +message DaysArray { + repeated Timerange sunday = 1; + repeated Timerange monday = 2; + repeated Timerange tuesday = 3; + repeated Timerange wednesday = 4; + repeated Timerange thursday = 5; + repeated Timerange friday = 6; + repeated Timerange saturday = 7; +} + +message Timerange { + uint64 range_start = 1; + uint64 range_end = 2; +} + +message Daterange { + enum TypeRange { + calendar_date = 0; + month_date = 1; + month_day = 2; + month_week_day = 3; + week_day = 4; + none = 5; // Instead of -1 in original config + } + TypeRange type = 1; + int32 syear = 2; // Start year. + int32 smon = 3; // Start month. + // Start day of month (may 3rd, last day in feb). + int32 smday = 4; + int32 swday = 5; // Start day of week (thursday). + // Start weekday offset (3rd thursday, last monday in jan). + int32 swday_offset = 6; + int32 eyear = 7; + int32 emon = 8; + int32 emday = 9; + int32 ewday = 10; + int32 ewday_offset = 11; + int32 skip_interval = 12; + repeated Timerange timerange = 13; +} + +message ExceptionArray { + repeated Daterange calendar_date = 1; + repeated Daterange month_date = 2; + repeated Daterange month_day = 3; + repeated Daterange month_week_day = 4; + repeated Daterange week_day = 5; +} + +message PairStringSet { + message Pair { + string first = 1; + string second = 2; + } + repeated Pair data = 1; + bool additive = 2; +} + +message PairUint64_32 { + uint64 first = 1; + uint32 second = 2; +} + +enum DependencyKind { + unknown = 0; + notification_dependency = 1; + execution_dependency = 2; +} + +enum ActionServiceOn { + action_svc_none = 0; + action_svc_ok = 1; // (1 << 0) + action_svc_warning = 2; // (1 << 1) + action_svc_unknown = 4; // (1 << 2) + action_svc_critical = 8; // (1 << 3) + action_svc_flapping = 16; // (1 << 4) + action_svc_downtime = 32; // (1 << 5) +} + +enum ActionHostOn { + action_hst_none = 0; + action_hst_up = 1; // (1 << 0) + action_hst_down = 2; // (1 << 1) + action_hst_unreachable = 4; // (1 << 2) + action_hst_flapping = 8; // (1 << 3) + action_hst_downtime = 16; // (1 << 4) +} + +enum ActionHostEscalationOn { + action_he_none = 0; + action_he_down = 1; // (1 << 0) + action_he_unreachable = 2; // (1 << 1) + action_he_recovery = 4; // (1 << 2) +} + +enum ActionServiceEscalationOn { + action_se_none = 0; + action_se_unknown = 1; // (1 << 1) + action_se_warning = 2; // (1 << 2) + action_se_critical = 4; // (1 << 3) + action_se_pending = 8; // (1 << 4) + action_se_recovery = 16; // (1 << 5) +} + +enum ActionServiceDependencyOn { + action_sd_none = 0; + action_sd_ok = 1; // (1 << 0) + action_sd_unknown = 2; // (1 << 1) + action_sd_warning = 4; // (1 << 2) + action_sd_critical = 8; // (1 << 3) + action_sd_pending = 16; // (1 << 4) +} + +enum ActionHostDependencyOn { + action_hd_none = 0; + action_hd_up = 1; // (1 << 0) + action_hd_down = 2; // (1 << 1) + action_hd_unreachable = 4; // (1 << 2) + action_hd_pending = 8; // (1 << 3) +} + +enum TagType { + tag_servicegroup = 0; + tag_hostgroup = 1; + tag_servicecategory = 2; + tag_hostcategory = 3; + tag_none = 255; // in legacy configuration, this was -1 +} + +message StringList { + repeated string data = 1; + bool additive = 2; +} + +message StringSet { + repeated string data = 1; + bool additive = 2; +} + +message Anomalydetection { + Object obj = 1; + int32 acknowledgement_timeout = 2; // Optional - Default value: 0 + string action_url = 3; + bool status_change = 4; // Optional - Default value: false + bool checks_active = 5; // Optional - Default value: true + bool checks_passive = 6; // Optional - Default value: true + string metric_name = 7; + string thresholds_file = 8; + bool check_freshness = 9; // Optional - Default value: 0 + uint32 check_interval = 10; // Optional - Default value: 5 + StringSet contactgroups = 11; + StringSet contacts = 12; + repeated CustomVariable customvariables = 13; + string display_name = 14; + string event_handler = 15; + bool event_handler_enabled = 16; // Optional - Default value: true + uint32 first_notification_delay = 17; // Optional - Default value: 0 + bool flap_detection_enabled = 18; // Optional - Default value: true + uint32 flap_detection_options = + 19; // Optional - Default value: action_svc_ok | action_svc_warning + // |action_svc_unknown | action_svc_critical + uint32 freshness_threshold = 20; // Optional - Default value: 0 + uint32 high_flap_threshold = 21; // Optional - Default value: 0 + string host_name = 22; + string icon_image = 23; + string icon_image_alt = 24; + ServiceStatus initial_state = + 25; // - Default value: ServiceStatus::state_ok + bool is_volatile = 26; // Optional - Default value: false + uint32 low_flap_threshold = 27; // Optional - Default value: 0 + uint32 max_check_attempts = 28; // Optional - Default value: 3 + string notes = 29; + string notes_url = 30; + bool notifications_enabled = 31; // Optional - Default value: true + uint32 notification_interval = 32; // Optional - Default value: 0 + uint32 notification_options = + 33; // Optional - Default value: action_svc_ok | action_svc_warning + // |action_svc_critical | action_svc_unknown |action_svc_flapping | + // action_svc_downtime + optional string notification_period = 34; // Optional + bool obsess_over_service = 35; // Optional - Default value: true + bool process_perf_data = 36; // Optional - Default value: true + bool retain_nonstatus_information = 37; // Optional - Default value: true + bool retain_status_information = 38; // Optional - Default value: true + uint32 retry_interval = 39; // Optional - Default value: 1 + optional uint32 recovery_notification_delay = 40; // Optional + StringSet servicegroups = 41; + string service_description = 42; + uint64 host_id = 43; + uint64 service_id = 44; + uint64 internal_id = 45; + uint64 dependent_service_id = 46; + uint32 stalking_options = 47; // Optional - Default value: action_svc_none + optional string timezone = 48; // Optional + optional uint64 severity_id = 49; // Optional + optional uint64 icon_id = 50; // Optional + repeated PairUint64_32 tags = 51; + double sensitivity = 52; +} + +message Command { + Object obj = 1; + string command_line = 2; + string command_name = 3; + string connector = 4; +} + +message Connector { + Object obj = 1; + string connector_line = 2; + string connector_name = 3; +} + +message Contact { + Object obj = 1; + repeated string address = 2; + string alias = 3; + bool can_submit_commands = 4; // Optional - Default value: true + StringSet contactgroups = 5; + string contact_name = 6; + repeated CustomVariable customvariables = 7; + string email = 8; + bool host_notifications_enabled = 9; // Optional - Default value: true + StringList host_notification_commands = 10; + uint32 host_notification_options = + 11; // Optional - Default value: action_hst_none + string host_notification_period = 12; + bool retain_nonstatus_information = 13; // Optional - Default value: true + bool retain_status_information = 14; // Optional - Default value: true + string pager = 15; + StringList service_notification_commands = 16; + uint32 service_notification_options = + 17; // Optional - Default value: action_svc_none + string service_notification_period = 18; + bool service_notifications_enabled = 19; // Optional - Default value: true + optional string timezone = 20; // Optional +} + +message Contactgroup { + Object obj = 1; + string alias = 2; + StringSet contactgroup_members = 3; + string contactgroup_name = 4; + StringSet members = 5; +} + +message Host { + Object obj = 1; + optional int32 acknowledgement_timeout = 2; // Optional + string action_url = 3; + string address = 4; + string alias = 5; + bool checks_active = 6; // Optional - Default value: true + bool checks_passive = 7; // Optional - Default value: true + string check_command = 8; + bool check_freshness = 9; // Optional - Default value: false + uint32 check_interval = 10; // Optional - Default value: 5 + string check_period = 11; + StringSet contactgroups = 12; + StringSet contacts = 13; + optional Point2d coords_2d = 14; // Optional + optional Point3d coords_3d = 15; // Optional + repeated CustomVariable customvariables = 16; + string display_name = 17; + string event_handler = 18; + bool event_handler_enabled = 19; // Optional - Default value: true + uint32 first_notification_delay = 20; // Optional - Default value: 0 + bool flap_detection_enabled = 21; // Optional - Default value: true + uint32 flap_detection_options = + 22; // Optional - Default value: action_hst_up |action_hst_down + // |action_hst_unreachable + uint32 freshness_threshold = 23; // Optional - Default value: 0 + uint32 high_flap_threshold = 24; // Optional - Default value: 0 + StringSet hostgroups = 25; + uint64 host_id = 26; + string host_name = 27; + string icon_image = 28; + string icon_image_alt = 29; + HostStatus initial_state = 30; // - Default value: HostStatus::state_up + uint32 low_flap_threshold = 31; // Optional - Default value: 0 + uint32 max_check_attempts = 32; // Optional - Default value: 3 + string notes = 33; + string notes_url = 34; + bool notifications_enabled = 35; // Optional - Default value: true + uint32 notification_interval = 36; // Optional - Default value: 0 + uint32 notification_options = + 37; // Optional - Default value: action_hst_up | action_hst_down + // |action_hst_unreachable |action_hst_flapping |action_hst_downtime + string notification_period = 38; + bool obsess_over_host = 39; // Optional - Default value: true + StringSet parents = 40; + bool process_perf_data = 41; // Optional - Default value: true + bool retain_nonstatus_information = 42; // Optional - Default value: true + bool retain_status_information = 43; // Optional - Default value: true + uint32 retry_interval = 44; // Optional - Default value: 1 + optional uint32 recovery_notification_delay = 45; // Optional + uint32 stalking_options = 46; // Optional - Default value: action_hst_none + string statusmap_image = 47; + optional string timezone = 48; // Optional + string vrml_image = 49; + optional uint64 severity_id = 50; // Optional + optional uint64 icon_id = 51; // Optional + repeated PairUint64_32 tags = 52; +} + +message Hostdependency { + Object obj = 1; + string dependency_period = 2; + DependencyKind dependency_type = 3; + StringSet dependent_hostgroups = 4; + StringSet dependent_hosts = 5; + uint32 execution_failure_options = + 6; // Optional - Default value: action_hd_none + StringSet hostgroups = 7; + StringSet hosts = 8; + bool inherits_parent = 9; // Optional - Default value: false + uint32 notification_failure_options = + 10; // Optional - Default value: action_hd_none +} + +message Hostescalation { + Object obj = 1; + StringSet contactgroups = 2; + uint32 escalation_options = 3; // Optional - Default value: action_he_none + optional string escalation_period = 4; // Optional + uint32 first_notification = 5; // Optional - Default value: -2 + StringSet hostgroups = 6; + StringSet hosts = 7; + uint32 last_notification = 8; // Optional - Default value: -2 + uint32 notification_interval = 9; // Optional - Default value: 0 +} + +message Hostgroup { + Object obj = 1; + string action_url = 2; + string alias = 3; + uint32 hostgroup_id = 4; + string hostgroup_name = 5; + StringSet members = 6; + string notes = 7; + string notes_url = 8; +} + +message Service { + Object obj = 1; + int32 acknowledgement_timeout = 2; // Optional - Default value: 0 + string action_url = 3; + bool checks_active = 4; // Optional - Default value: true + bool checks_passive = 5; // Optional - Default value: true + string check_command = 6; + bool check_command_is_important = 7; + bool check_freshness = 8; // Optional - Default value: 0 + uint32 check_interval = 9; // Optional - Default value: 5 + string check_period = 10; + StringSet contactgroups = 11; + StringSet contacts = 12; + repeated CustomVariable customvariables = 13; + string display_name = 14; + string event_handler = 15; + bool event_handler_enabled = 16; // Optional - Default value: true + uint32 first_notification_delay = 17; // Optional - Default value: 0 + bool flap_detection_enabled = 18; // Optional - Default value: true + uint32 flap_detection_options = + 19; // Optional - Default value: action_svc_ok |action_svc_warning + // |action_svc_unknown |action_svc_critical + uint32 freshness_threshold = 20; // Optional - Default value: 0 + uint32 high_flap_threshold = 21; // Optional - Default value: 0 + string host_name = 22; + string icon_image = 23; + string icon_image_alt = 24; + ServiceStatus initial_state = + 25; // - Default value: ServiceStatus::state_ok + bool is_volatile = 26; // Optional - Default value: false + uint32 low_flap_threshold = 27; // Optional - Default value: 0 + uint32 max_check_attempts = 28; // Optional - Default value: 3 + string notes = 29; + string notes_url = 30; + bool notifications_enabled = 31; // Optional - Default value: true + uint32 notification_interval = 32; // Optional - Default value: 0 + uint32 notification_options = + 33; // Optional - Default value: action_svc_ok | action_svc_warning | + // action_svc_critical | action_svc_unknown |action_svc_flapping | + // action_svc_downtime + optional string notification_period = 34; // Optional + bool obsess_over_service = 35; // Optional - Default value: true + bool process_perf_data = 36; // Optional - Default value: true + bool retain_nonstatus_information = 37; // Optional - Default value: true + bool retain_status_information = 38; // Optional - Default value: true + uint32 retry_interval = 39; // Optional - Default value: 1 + optional uint32 recovery_notification_delay = 40; // Optional + StringSet servicegroups = 41; + string service_description = 42; + uint64 host_id = 43; + uint64 service_id = 44; + uint32 stalking_options = 45; // Optional - Default value: action_svc_none + optional string timezone = 46; // Optional + optional uint64 severity_id = 47; // Optional + optional uint64 icon_id = 48; // Optional + repeated PairUint64_32 tags = 49; +} + +message Servicedependency { + Object obj = 1; + string dependency_period = 2; + DependencyKind dependency_type = 3; + StringList dependent_hostgroups = 4; + StringList dependent_hosts = 5; + StringList dependent_servicegroups = 6; + StringList dependent_service_description = 7; + uint32 execution_failure_options = + 8; // Optional - Default value: action_sd_none + StringList hostgroups = 9; + StringList hosts = 10; + bool inherits_parent = 11; // Optional - Default value: false + uint32 notification_failure_options = + 12; // Optional - Default value: action_sd_none + StringList servicegroups = 13; + StringList service_description = 14; +} + +message Serviceescalation { + Object obj = 1; + StringSet contactgroups = 2; + uint32 escalation_options = 3; // Optional - Default value: action_se_none + optional string escalation_period = 4; // Optional + uint32 first_notification = 5; // Optional - Default value: -2 + StringList hostgroups = 6; + StringList hosts = 7; + uint32 last_notification = 8; // Optional - Default value: -2 + uint32 notification_interval = 9; // Optional - Default value: 0 + StringList servicegroups = 10; + StringList service_description = 11; +} + +message Servicegroup { + Object obj = 1; + string action_url = 2; + string alias = 3; + PairStringSet members = 4; + string notes = 5; + string notes_url = 6; + uint32 servicegroup_id = 7; + StringSet servicegroup_members = 8; + string servicegroup_name = 9; +} + +message Severity { + Object obj = 1; + KeyType key = 2; // - Default value: 0, -1 + uint32 level = 3; + uint64 icon_id = 4; + string severity_name = 5; +} + +message Tag { + Object obj = 1; + KeyType key = 2; // - Default value: 0, -1 + string tag_name = 3; +} + +message Timeperiod { + Object obj = 1; + string alias = 2; + ExceptionArray exceptions = 3; + StringSet exclude = 4; + string timeperiod_name = 5; + DaysArray timeranges = 6; +} diff --git a/common/engine_conf/state_helper.cc b/common/engine_conf/state_helper.cc new file mode 100644 index 00000000000..0b01eb5716e --- /dev/null +++ b/common/engine_conf/state_helper.cc @@ -0,0 +1,490 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ +#include "common/engine_conf/state_helper.hh" +#include <fmt/format.h> +#include <google/protobuf/descriptor.h> +#include <rapidjson/rapidjson.h> +#include "com/centreon/engine/events/sched_info.hh" +#include "com/centreon/exceptions/msg_fmt.hh" + +using com::centreon::exceptions::msg_fmt; +using ::google::protobuf::Descriptor; +using ::google::protobuf::FieldDescriptor; +using ::google::protobuf::Reflection; + +extern sched_info scheduling_info; + +namespace com::centreon::engine::configuration { + +/** + * @brief Constructor from a State object. + * + * @param obj The State object on which this helper works. The helper is not the + * owner of this object. + */ +state_helper::state_helper(State* obj) + : message_helper( + object_type::state, + obj, + { + {"check_for_orphaned_hosts", "check_orphaned_hosts"}, + {"check_for_orphaned_services", "check_orphaned_services"}, + {"check_result_reaper_frequency", "check_reaper_interval"}, + {"illegal_macro_output_chars", "illegal_output_chars"}, + {"illegal_object_name_chars", "illegal_object_chars"}, + {"max_concurrent_checks", "max_parallel_service_checks"}, + {"rpc_port", "grpc_port"}, + {"service_interleave_factor", "service_interleave_factor_method"}, + {"service_reaper_frequency", "check_reaper_interval"}, + {"use_agressive_host_checking", "use_aggressive_host_checking"}, + {"use_regexp_matching", "use_regexp_matches"}, + {"xcddefault_comment_file", "comment_file"}, + {"xdddefault_downtime_file", "downtime_file"}, + }, + 2) { + _init(); +} + +/** + * @brief For several keys, the parser of State objects has a particular + * behavior. These behaviors are handled here. + * @param key The key to parse. + * @param value The value corresponding to the key + */ +bool state_helper::hook(std::string_view key, const std::string_view& value) { + State* obj = static_cast<State*>(mut_obj()); + key = validate_key(key); + + if (key.substr(0, 10) == "log_level_") { + if (value == "off" || value == "critical" || value == "error" || + value == "warning" || value == "info" || value == "debug" || + value == "trace") { + return set_global(key, value); + } else + throw msg_fmt( + "Log level '{}' has value '{}' but it cannot be a different string " + "than off, critical, error, warning, info, debug or trace", + key, value); + } else if (key == "date_format") { + if (value == "euro") + obj->set_date_format(DateType::euro); + else if (value == "iso8601") + obj->set_date_format(DateType::iso8601); + else if (value == "strict-iso8601") + obj->set_date_format(DateType::strict_iso8601); + else if (value == "us") + obj->set_date_format(DateType::us); + else + return false; + return true; + } else if (key == "host_inter_check_delay_method") { + if (value == "n") + obj->mutable_host_inter_check_delay_method()->set_type( + InterCheckDelay_IcdType_none); + else if (value == "d") + obj->mutable_host_inter_check_delay_method()->set_type( + InterCheckDelay_IcdType_dumb); + else if (value == "s") + obj->mutable_host_inter_check_delay_method()->set_type( + InterCheckDelay_IcdType_smart); + else { + obj->mutable_host_inter_check_delay_method()->set_type( + InterCheckDelay_IcdType_user); + double user_value; + if (!absl::SimpleAtod(value, &user_value) || user_value <= 0.0) + throw msg_fmt( + "Invalid value for host_inter_check_delay_method, must be one of " + "'n' (none), 'd' (dumb), 's' (smart) or a stricly positive value " + "({} provided)", + user_value); + obj->mutable_host_inter_check_delay_method()->set_user_value(user_value); + } + return true; + } else if (key == "service_inter_check_delay_method") { + if (value == "n") + obj->mutable_service_inter_check_delay_method()->set_type( + InterCheckDelay_IcdType_none); + else if (value == "d") + obj->mutable_service_inter_check_delay_method()->set_type( + InterCheckDelay_IcdType_dumb); + else if (value == "s") + obj->mutable_service_inter_check_delay_method()->set_type( + InterCheckDelay_IcdType_smart); + else { + obj->mutable_service_inter_check_delay_method()->set_type( + InterCheckDelay_IcdType_user); + double user_value; + if (!absl::SimpleAtod(value, &user_value) || user_value <= 0.0) + throw msg_fmt( + "Invalid value for service_inter_check_delay_method, must be one " + "of 'n' (none), 'd' (dumb), 's' (smart) or a stricly positive " + "value ({} provided)", + user_value); + obj->mutable_service_inter_check_delay_method()->set_user_value( + user_value); + } + return true; + } else if (key == "command_check_interval") { + std::string_view v; + if (value[value.size() - 1] == 's') { + obj->set_command_check_interval_is_seconds(true); + v = value.substr(0, value.size() - 1); + } else { + obj->set_command_check_interval_is_seconds(false); + v = value; + } + int32_t res; + if (absl::SimpleAtoi(v, &res)) { + obj->set_command_check_interval(res); + return true; + } else { + throw msg_fmt( + "command_check_interval is an integer representing a duration " + "between two consecutive external command checks. This number can be " + "a number of 'time units' or a number of seconds. For the latter, " + "you must append a 's' after the number: the current incorrect value " + "is: '{}'", + fmt::string_view(value.data(), value.size())); + return false; + } + } else if (key == "service_interleave_factor_method") { + if (value == "s") + obj->mutable_service_interleave_factor_method()->set_type( + InterleaveFactor_IFType_ilf_smart); + else { + obj->mutable_service_interleave_factor_method()->set_type( + InterleaveFactor_IFType_ilf_user); + int32_t res; + if (!absl::SimpleAtoi(value, &res) || res < 1) + res = 1; + obj->mutable_service_interleave_factor_method()->set_user_value(res); + } + return true; + } else if (key == "check_reaper_interval") { + int32_t res; + if (!absl::SimpleAtoi(value, &res) || res == 0) + throw msg_fmt( + "check_reaper_interval must be a strictly positive integer (current " + "value '{}'", + fmt::string_view(value.data(), value.size())); + else + obj->set_check_reaper_interval(res); + return true; + } else if (key == "event_broker_options") { + if (value != "-1") { + uint32_t res; + if (absl::SimpleAtoi(value, &res)) + obj->set_event_broker_options(res); + else + throw msg_fmt( + "event_broker_options must be a positive integer or '-1' and not " + "'{}'", + fmt::string_view(value.data(), value.size())); + } else + obj->set_event_broker_options(static_cast<uint32_t>(-1)); + return true; + } + return false; +} + +/** + * @brief Initializer of the State object, in other words set its default + * values. + */ +void state_helper::_init() { + State* obj = static_cast<State*>(mut_obj()); + obj->set_accept_passive_host_checks(true); + obj->set_accept_passive_service_checks(true); + obj->set_additional_freshness_latency(15); + obj->set_admin_email(""); + obj->set_admin_pager(""); + obj->set_allow_empty_hostgroup_assignment(false); + obj->set_auto_reschedule_checks(false); + obj->set_auto_rescheduling_interval(30); + obj->set_auto_rescheduling_window(180); + obj->set_cached_host_check_horizon(15); + obj->set_cached_service_check_horizon(15); + obj->set_check_external_commands(true); + obj->set_check_host_freshness(false); + obj->set_check_orphaned_hosts(true); + obj->set_check_orphaned_services(true); + obj->set_check_reaper_interval(10); + obj->set_check_service_freshness(true); + obj->set_command_check_interval(-1); + obj->set_command_file(DEFAULT_COMMAND_FILE); + obj->set_date_format(DateType::us); + obj->set_debug_file(DEFAULT_DEBUG_FILE); + obj->set_debug_level(0); + obj->set_debug_verbosity(1); + obj->set_enable_environment_macros(false); + obj->set_enable_event_handlers(true); + obj->set_enable_flap_detection(false); + obj->set_enable_macros_filter(false); + obj->set_enable_notifications(true); + obj->set_enable_predictive_host_dependency_checks(true); + obj->set_enable_predictive_service_dependency_checks(true); + obj->set_event_broker_options(std::numeric_limits<uint32_t>::max()); + obj->set_event_handler_timeout(30); + obj->set_execute_host_checks(true); + obj->set_execute_service_checks(true); + obj->set_external_command_buffer_slots(4096); + obj->set_global_host_event_handler(""); + obj->set_global_service_event_handler(""); + obj->set_high_host_flap_threshold(30.0); + obj->set_high_service_flap_threshold(30.0); + obj->set_host_check_timeout(30); + obj->set_host_freshness_check_interval(60); + obj->mutable_host_inter_check_delay_method()->set_type( + InterCheckDelay_IcdType_smart); + obj->set_illegal_object_chars(""); + obj->set_illegal_output_chars("`~$&|'\"<>"); + obj->set_interval_length(60); + obj->set_log_event_handlers(true); + obj->set_log_external_commands(true); + obj->set_log_file(DEFAULT_LOG_FILE); + obj->set_log_host_retries(false); + obj->set_log_notifications(true); + obj->set_log_passive_checks(true); + obj->set_log_pid(true); + obj->set_log_service_retries(false); + obj->set_low_host_flap_threshold(20.0); + obj->set_low_service_flap_threshold(20.0); + obj->set_max_debug_file_size(1000000); + obj->set_max_host_check_spread(5); + obj->set_max_log_file_size(0); + obj->set_max_parallel_service_checks(0); + obj->set_max_service_check_spread(5); + obj->set_notification_timeout(30); + obj->set_obsess_over_hosts(false); + obj->set_obsess_over_services(false); + obj->set_ochp_command(""); + obj->set_ochp_timeout(15); + obj->set_ocsp_command(""); + obj->set_ocsp_timeout(15); + obj->set_perfdata_timeout(5); + obj->set_poller_name("unknown"); + obj->set_rpc_listen_address("localhost"); + obj->set_process_performance_data(false); + obj->set_retained_contact_host_attribute_mask(0L); + obj->set_retained_contact_service_attribute_mask(0L); + obj->set_retained_host_attribute_mask(0L); + obj->set_retained_process_host_attribute_mask(0L); + obj->set_retain_state_information(true); + obj->set_retention_scheduling_horizon(900); + obj->set_retention_update_interval(60); + obj->set_service_check_timeout(60); + obj->set_service_freshness_check_interval(60); + obj->mutable_service_inter_check_delay_method()->set_type( + InterCheckDelay_IcdType_smart); + obj->mutable_service_interleave_factor_method()->set_type( + InterleaveFactor_IFType_ilf_smart); + obj->set_sleep_time(0.5); + obj->set_soft_state_dependencies(false); + obj->set_state_retention_file(DEFAULT_RETENTION_FILE); + obj->set_status_file(DEFAULT_STATUS_FILE); + obj->set_status_update_interval(60); + obj->set_time_change_threshold(900); + obj->set_use_large_installation_tweaks(false); + obj->set_instance_heartbeat_interval(30); + obj->set_use_regexp_matches(false); + obj->set_use_retained_program_state(true); + obj->set_use_retained_scheduling_info(false); + obj->set_use_setpgid(true); + obj->set_use_syslog(true); + obj->set_log_v2_enabled(true); + obj->set_log_legacy_enabled(true); + obj->set_log_v2_logger("file"); + obj->set_log_level_functions(LogLevel::error); + obj->set_log_level_config(LogLevel::info); + obj->set_log_level_events(LogLevel::info); + obj->set_log_level_checks(LogLevel::info); + obj->set_log_level_notifications(LogLevel::error); + obj->set_log_level_eventbroker(LogLevel::error); + obj->set_log_level_external_command(LogLevel::error); + obj->set_log_level_commands(LogLevel::error); + obj->set_log_level_downtimes(LogLevel::error); + obj->set_log_level_comments(LogLevel::error); + obj->set_log_level_macros(LogLevel::error); + obj->set_log_level_process(LogLevel::info); + obj->set_log_level_runtime(LogLevel::error); + obj->set_use_timezone(""); + obj->set_use_true_regexp_matching(false); +} + +/** + * @brief Given the helper to a State protobuf message (so we also have access + * to the message itself) and a key/value pair, this function searches the + * field key and applies to it the value. As we work here on the State message, + * that's the reason of the name of this function. + * + * @param helper The State helper. + * @param key The field name to look for. + * @param value The value to apply. + * + * @return True on success. + */ +bool state_helper::set_global(const std::string_view& key, + const std::string_view& value) { + State* msg = static_cast<State*>(mut_obj()); + const Descriptor* desc = msg->GetDescriptor(); + const FieldDescriptor* f; + const Reflection* refl; + + f = desc->FindFieldByName(std::string(key.data(), key.size())); + if (f == nullptr) { + auto it = correspondence().find(key); + if (it != correspondence().end()) + f = desc->FindFieldByName(it->second); + if (f == nullptr) + return false; + } + refl = msg->GetReflection(); + switch (f->type()) { + case FieldDescriptor::TYPE_BOOL: { + bool val; + if (absl::SimpleAtob(value, &val)) { + refl->SetBool(static_cast<Message*>(msg), f, val); + return true; + } else + return false; + } break; + case FieldDescriptor::TYPE_INT32: { + int32_t val; + if (absl::SimpleAtoi(value, &val)) { + refl->SetInt32(static_cast<Message*>(msg), f, val); + return true; + } else + return false; + } break; + case FieldDescriptor::TYPE_UINT32: { + uint32_t val; + if (absl::SimpleAtoi(value, &val)) { + refl->SetUInt32(static_cast<Message*>(msg), f, val); + return true; + } else + return false; + } break; + case FieldDescriptor::TYPE_UINT64: { + uint64_t val; + if (absl::SimpleAtoi(value, &val)) { + refl->SetUInt64(static_cast<Message*>(msg), f, val); + return true; + } else + return false; + } break; + case FieldDescriptor::TYPE_FLOAT: { + float val; + if (absl::SimpleAtof(value, &val)) { + refl->SetFloat(static_cast<Message*>(msg), f, val); + return true; + } else + return false; + } break; + case FieldDescriptor::TYPE_STRING: + if (f->is_repeated()) { + refl->AddString(static_cast<Message*>(msg), f, + std::string(value.data(), value.size())); + } else { + refl->SetString(static_cast<Message*>(msg), f, + std::string(value.data(), value.size())); + } + return true; + case FieldDescriptor::TYPE_ENUM: { + auto* v = f->enum_type()->FindValueByName( + std::string(value.data(), value.size())); + if (v) + refl->SetEnumValue(msg, f, v->number()); + else + return false; + } break; + case FieldDescriptor::TYPE_MESSAGE: + if (!f->is_repeated()) { + Message* m = refl->MutableMessage(msg, f); + const Descriptor* d = m->GetDescriptor(); + + if (d && d->name() == "StringSet") { + StringSet* set = + static_cast<StringSet*>(refl->MutableMessage(msg, f)); + fill_string_group(set, value); + return true; + } else if (d && d->name() == "StringList") { + StringList* lst = + static_cast<StringList*>(refl->MutableMessage(msg, f)); + fill_string_group(lst, value); + return true; + } + } + default: + return false; + } + return true; +} + +bool state_helper::apply_extended_conf( + const std::string& file_path, + const rapidjson::Document& json_doc, + const std::shared_ptr<spdlog::logger>& logger) { + bool retval = true; + for (rapidjson::Value::ConstMemberIterator member_iter = + json_doc.MemberBegin(); + member_iter != json_doc.MemberEnd(); ++member_iter) { + const std::string_view field_name = member_iter->name.GetString(); + try { + switch (member_iter->value.GetType()) { + case rapidjson::Type::kNumberType: { + std::string value_str; + if (member_iter->value.IsInt64()) { + int64_t value = member_iter->value.GetInt64(); + value_str = fmt::to_string(value); + } else if (member_iter->value.IsDouble()) { + double value = member_iter->value.GetDouble(); + value_str = fmt::to_string(value); + } + set_global(field_name, value_str); + } break; + case rapidjson::Type::kStringType: { + const std::string_view field_value = member_iter->value.GetString(); + set_global(field_name, field_value); + } break; + case rapidjson::Type::kFalseType: + set_global(field_name, "false"); + break; + case rapidjson::Type::kTrueType: + set_global(field_name, "true"); + break; + case rapidjson::Type::kNullType: + set_global(field_name, ""); + break; + default: + logger->error( + "The field '{}' in the file '{}' can not be converted as a " + "string", + field_name, file_path); + retval = false; + } + } catch (const std::exception& e) { + logger->error( + "The field '{}' in the file '{}' can not be converted as a string", + field_name, file_path); + retval = false; + } + } + return retval; +} +} // namespace com::centreon::engine::configuration diff --git a/common/engine_conf/state_helper.hh b/common/engine_conf/state_helper.hh new file mode 100644 index 00000000000..ae3a4a172ea --- /dev/null +++ b/common/engine_conf/state_helper.hh @@ -0,0 +1,45 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#ifndef CCE_CONFIGURATION_STATE +#define CCE_CONFIGURATION_STATE + +#include <rapidjson/document.h> + +#include "common/engine_conf/message_helper.hh" +#include "common/engine_conf/state.pb.h" + +namespace com::centreon::engine::configuration { + +class state_helper : public message_helper { + void _init(); + + public: + state_helper(State* obj); + ~state_helper() noexcept = default; + + bool hook(std::string_view key, const std::string_view& value) override; + bool apply_extended_conf(const std::string& file_path, + const rapidjson::Document& json_doc, + const std::shared_ptr<spdlog::logger>& logger); + bool set_global(const std::string_view& key, const std::string_view& value); +}; +} // namespace com::centreon::engine::configuration + +#endif /* !CCE_CONFIGURATION_STATE */ diff --git a/common/engine_conf/tag_helper.cc b/common/engine_conf/tag_helper.cc new file mode 100644 index 00000000000..947ca521588 --- /dev/null +++ b/common/engine_conf/tag_helper.cc @@ -0,0 +1,102 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ +#include "common/engine_conf/tag_helper.hh" + +#include "com/centreon/exceptions/msg_fmt.hh" + +using com::centreon::exceptions::msg_fmt; + +namespace com::centreon::engine::configuration { + +/** + * @brief Constructor from a Tag object. + * + * @param obj The Tag object on which this helper works. The helper is not the + * owner of this object. + */ +tag_helper::tag_helper(Tag* obj) + : message_helper(object_type::tag, + obj, + { + {"tag_id", "id"}, + {"tag_type", "type"}, + }, + 4) { + _init(); +} + +/** + * @brief For several keys, the parser of Tag objects has a particular + * behavior. These behaviors are handled here. + * @param key The key to parse. + * @param value The value corresponding to the key + */ +bool tag_helper::hook(std::string_view key, const std::string_view& value) { + Tag* obj = static_cast<Tag*>(mut_obj()); + key = validate_key(key); + + if (key == "id" || key == "tag_id") { + uint64_t id; + if (absl::SimpleAtoi(value, &id)) + obj->mutable_key()->set_id(id); + else + return false; + return true; + } else if (key == "type" || key == "tag_type") { + if (value == "hostcategory") + obj->mutable_key()->set_type(tag_hostcategory); + else if (value == "servicecategory") + obj->mutable_key()->set_type(tag_servicecategory); + else if (value == "hostgroup") + obj->mutable_key()->set_type(tag_hostgroup); + else if (value == "servicegroup") + obj->mutable_key()->set_type(tag_servicegroup); + else + return false; + return true; + } + return false; +} + +/** + * @brief Check the validity of the Tag object. + * + * @param err An error counter. + */ +void tag_helper::check_validity(error_cnt& err) const { + const Tag* o = static_cast<const Tag*>(obj()); + + if (o->tag_name().empty()) + throw msg_fmt("Tag has no name (property 'tag_name')"); + if (o->key().id() == 0) + throw msg_fmt("Tag '{}' has a null id", o->tag_name()); + if (o->key().type() == static_cast<uint32_t>(-1)) + throw msg_fmt("Tag type must be specified"); +} + +/** + * @brief Initializer of the Tag object, in other words set its default values. + */ +void tag_helper::_init() { + Tag* obj = static_cast<Tag*>(mut_obj()); + obj->mutable_obj()->set_register_(true); + obj->mutable_key()->set_id(0); + obj->mutable_key()->set_type(-1); +} +} // namespace com::centreon::engine::configuration diff --git a/common/engine_conf/tag_helper.hh b/common/engine_conf/tag_helper.hh new file mode 100644 index 00000000000..2b1e02fe93c --- /dev/null +++ b/common/engine_conf/tag_helper.hh @@ -0,0 +1,40 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#ifndef CCE_CONFIGURATION_TAG +#define CCE_CONFIGURATION_TAG + +#include "common/engine_conf/message_helper.hh" +#include "common/engine_conf/state.pb.h" + +namespace com::centreon::engine::configuration { + +class tag_helper : public message_helper { + void _init(); + + public: + tag_helper(Tag* obj); + ~tag_helper() noexcept = default; + void check_validity(error_cnt& err) const override; + + bool hook(std::string_view key, const std::string_view& value) override; +}; +} // namespace com::centreon::engine::configuration + +#endif /* !CCE_CONFIGURATION_TAG */ diff --git a/common/engine_conf/timeperiod_helper.cc b/common/engine_conf/timeperiod_helper.cc new file mode 100644 index 00000000000..a6286dd4ab1 --- /dev/null +++ b/common/engine_conf/timeperiod_helper.cc @@ -0,0 +1,577 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ +#include "common/engine_conf/timeperiod_helper.hh" + +#include <absl/strings/str_format.h> + +#include "com/centreon/exceptions/msg_fmt.hh" + +using com::centreon::exceptions::msg_fmt; + +namespace com::centreon::engine::configuration { + +/** + * @brief Constructor from a Timeperiod object. + * + * @param obj The Timeperiod object on which this helper works. The helper is + * not the owner of this object. + */ +timeperiod_helper::timeperiod_helper(Timeperiod* obj) + : message_helper(object_type::timeperiod, obj, {}, 7) { + _init(); +} + +/** + * @brief For several keys, the parser of Timeperiod objects has a particular + * behavior. These behaviors are handled here. + * @param key The key to parse. + * @param value The value corresponding to the key + */ +bool timeperiod_helper::hook(std::string_view key, + const std::string_view& value) { + Timeperiod* obj = static_cast<Timeperiod*>(mut_obj()); + key = validate_key(key); + auto get_timerange = [](const std::string_view& value, auto* day) -> bool { + auto arr = absl::StrSplit(value, ','); + for (auto& d : arr) { + std::pair<std::string_view, std::string_view> v = absl::StrSplit(d, '-'); + Timerange tr; + std::pair<std::string_view, std::string_view> p = + absl::StrSplit(v.first, ':'); + uint32_t h, m; + if (!absl::SimpleAtoi(p.first, &h) || !absl::SimpleAtoi(p.second, &m)) + return false; + tr.set_range_start(h * 3600 + m * 60); + p = absl::StrSplit(v.second, ':'); + if (!absl::SimpleAtoi(p.first, &h) || !absl::SimpleAtoi(p.second, &m)) + return false; + tr.set_range_end(h * 3600 + m * 60); + day->Add(std::move(tr)); + } + return true; + }; + + bool retval = false; + if (key == "exclude") { + fill_string_group(obj->mutable_exclude(), value); + return true; + } else { + if (key == "sunday") + retval = + get_timerange(value, obj->mutable_timeranges()->mutable_sunday()); + else if (key == "monday") + retval = + get_timerange(value, obj->mutable_timeranges()->mutable_monday()); + else if (key == "tuesday") + retval = + get_timerange(value, obj->mutable_timeranges()->mutable_tuesday()); + else if (key == "wednesday") + retval = + get_timerange(value, obj->mutable_timeranges()->mutable_wednesday()); + else if (key == "thursday") + retval = + get_timerange(value, obj->mutable_timeranges()->mutable_thursday()); + else if (key == "friday") + retval = + get_timerange(value, obj->mutable_timeranges()->mutable_friday()); + else if (key == "saturday") + retval = + get_timerange(value, obj->mutable_timeranges()->mutable_saturday()); + if (!retval) { + std::string line{absl::StrFormat("%s %s", key, value)}; + retval = _add_week_day(key, value) || _add_calendar_date(line) || + _add_other_date(line); + } + } + return retval; +} + +/** + * Add a week day. + * + * @param[in] key The week day. + * @param[in] value The range. + * + * @return True on success, otherwise false. + */ +bool timeperiod_helper::_add_week_day(std::string_view key, + std::string_view value) { + Timeperiod* obj = static_cast<Timeperiod*>(mut_obj()); + unsigned int day_id; + if (!_get_day_id(key, day_id)) + return false; + + google::protobuf::RepeatedPtrField<configuration::Timerange>* d; + switch (day_id) { + case 0: + d = obj->mutable_timeranges()->mutable_sunday(); + break; + case 1: + d = obj->mutable_timeranges()->mutable_sunday(); + break; + case 2: + d = obj->mutable_timeranges()->mutable_sunday(); + break; + case 3: + d = obj->mutable_timeranges()->mutable_sunday(); + break; + case 4: + d = obj->mutable_timeranges()->mutable_sunday(); + break; + case 5: + d = obj->mutable_timeranges()->mutable_sunday(); + break; + case 6: + d = obj->mutable_timeranges()->mutable_sunday(); + break; + } + if (!_build_timeranges(value, *d)) + return false; + + return true; +} + +/** + * Add a calendar date. + * + * @param[in] line The line to parse. + * + * @return True on success, otherwise false. + */ +bool timeperiod_helper::_add_calendar_date(const std::string& line) { + int32_t ret = 0; + int32_t pos = 0; + bool fill_missing = false; + uint32_t month_start = 0; + uint32_t month_end = 0; + uint32_t month_day_start = 0; + uint32_t month_day_end = 0; + uint32_t year_start = 0; + uint32_t year_end = 0; + uint32_t skip_interval = 0; + + if ((ret = sscanf(line.c_str(), "%4u-%2u-%2u - %4u-%2u-%2u / %u %n", + &year_start, &month_start, &month_day_start, &year_end, + &month_end, &month_day_end, &skip_interval, &pos)) == 7) + fill_missing = false; + else if ((ret = sscanf(line.c_str(), "%4u-%2u-%2u - %4u-%2u-%2u %n", + &year_start, &month_start, &month_day_start, &year_end, + &month_end, &month_day_end, &pos)) == 6) + fill_missing = false; + else if ((ret = sscanf(line.c_str(), "%4u-%2u-%2u / %u %n", &year_start, + &month_start, &month_day_start, &skip_interval, + &pos)) == 4) + fill_missing = true; + else if ((ret = sscanf(line.c_str(), "%4u-%2u-%2u %n", &year_start, + &month_start, &month_day_start, &pos)) == 3) + fill_missing = true; + + if (ret) { + if (fill_missing) { + year_end = year_start; + month_end = month_start; + month_day_end = month_day_start; + } + + Timeperiod* obj = static_cast<Timeperiod*>(mut_obj()); + auto* range = obj->mutable_exceptions()->add_calendar_date(); + range->set_type(Daterange_TypeRange_calendar_date); + if (!_build_timeranges(line.substr(pos), *range->mutable_timerange())) + return false; + + range->set_syear(year_start); + range->set_smon(month_start - 1); + range->set_smday(month_day_start); + range->set_eyear(year_end); + range->set_emon(month_end - 1); + range->set_emday(month_day_end); + range->set_skip_interval(skip_interval); + + return true; + } + return false; +} + +/** + * Build timerange from new line. + * + * @param[in] line The line to parse. + * @param[out] timeranges The list to fill. + * + * @return True on success, otherwise false. + */ +bool timeperiod_helper::_build_timeranges( + std::string_view line, + google::protobuf::RepeatedPtrField<Timerange>& timeranges) { + auto timeranges_str = absl::StrSplit(line, ','); + for (auto tr : timeranges_str) { + tr = absl::StripAsciiWhitespace(tr); + std::size_t pos(tr.find('-')); + if (pos == std::string::npos) + return false; + time_t start_time; + if (!_build_time_t(tr.substr(0, pos), start_time)) + return false; + time_t end_time; + if (!_build_time_t(tr.substr(pos + 1), end_time)) + return false; + Timerange* t = timeranges.Add(); + t->set_range_start(start_time); + t->set_range_end(end_time); + } + return true; +} + +/** + * Build time_t from timerange configuration. + * + * @param[in] time_str The time to parse (format 00:00-12:00). + * @param[out] ret The value to fill. + * + * @return True on success, otherwise false. + */ +bool timeperiod_helper::_build_time_t(std::string_view time_str, time_t& ret) { + std::size_t pos(time_str.find(':')); + if (pos == std::string::npos) + return false; + unsigned long hours; + if (!absl::SimpleAtoi(time_str.substr(0, pos), &hours)) + return false; + unsigned long minutes; + if (!absl::SimpleAtoi(time_str.substr(pos + 1), &minutes)) + return false; + ret = hours * 3600 + minutes * 60; + return true; +} + +/** + * @brief Check the validity of the Timeperiod object. + * + * @param err An error counter. + */ +void timeperiod_helper::check_validity(error_cnt& err) const { + const Timeperiod* o = static_cast<const Timeperiod*>(obj()); + + if (o->timeperiod_name().empty()) { + err.config_errors++; + throw msg_fmt("Time period has no name (property 'timeperiod_name')"); + } +} + +void timeperiod_helper::_init() { + Timeperiod* obj = static_cast<Timeperiod*>(mut_obj()); + obj->mutable_obj()->set_register_(true); +} + +/** + * Add other date. + * + * @param[in] line The line to parse. + * + * @return True on success, otherwise false. + */ +bool timeperiod_helper::_add_other_date(const std::string& line) { + int pos = 0; + Daterange::TypeRange type = Daterange_TypeRange_none; + uint32_t month_start = 0; + uint32_t month_end = 0; + int32_t month_day_start = 0; + int32_t month_day_end = 0; + uint32_t skip_interval = 0; + uint32_t week_day_start = 0; + uint32_t week_day_end = 0; + int32_t week_day_start_offset = 0; + int32_t week_day_end_offset = 0; + char buffer[4][4096]; + + if (line.size() > 1024) + return false; + + if (sscanf(line.c_str(), "%[a-z] %d %[a-z] - %[a-z] %d %[a-z] / %u %n", + buffer[0], &week_day_start_offset, buffer[1], buffer[2], + &week_day_end_offset, buffer[3], &skip_interval, &pos) == 7) { + // wednesday 1 january - thursday 2 july / 3 + if (_get_day_id(buffer[0], week_day_start) && + _get_month_id(buffer[1], month_start) && + _get_day_id(buffer[2], week_day_end) && + _get_month_id(buffer[3], month_end)) + type = Daterange_TypeRange_month_week_day; + } else if (sscanf(line.c_str(), "%[a-z] %d - %[a-z] %d / %u %n", buffer[0], + &month_day_start, buffer[1], &month_day_end, &skip_interval, + &pos) == 5) { + // monday 2 - thursday 3 / 2 + if (_get_day_id(buffer[0], week_day_start) && + _get_day_id(buffer[1], week_day_end)) { + week_day_start_offset = month_day_start; + week_day_end_offset = month_day_end; + type = Daterange_TypeRange_week_day; + } + // february 1 - march 15 / 3 + else if (_get_month_id(buffer[0], month_start) && + _get_month_id(buffer[1], month_end)) + type = Daterange_TypeRange_month_date; + // day 4 - 6 / 2 + else if (!strcmp(buffer[0], "day") && !strcmp(buffer[1], "day")) + type = Daterange_TypeRange_month_day; + } else if (sscanf(line.c_str(), "%[a-z] %d - %d / %u %n", buffer[0], + &month_day_start, &month_day_end, &skip_interval, + &pos) == 4) { + // thursday 2 - 4 + if (_get_day_id(buffer[0], week_day_start)) { + week_day_start_offset = month_day_start; + week_day_end = week_day_start; + week_day_end_offset = month_day_end; + type = Daterange_TypeRange_week_day; + } + // february 3 - 5 + else if (_get_month_id(buffer[0], month_start)) { + month_end = month_start; + type = Daterange_TypeRange_month_date; + } + // day 1 - 4 + else if (!strcmp(buffer[0], "day")) + type = Daterange_TypeRange_month_day; + } else if (sscanf(line.c_str(), "%[a-z] %d %[a-z] - %[a-z] %d %[a-z] %n", + buffer[0], &week_day_start_offset, buffer[1], buffer[2], + &week_day_end_offset, buffer[3], &pos) == 6) { + // wednesday 1 january - thursday 2 july + if (_get_day_id(buffer[0], week_day_start) && + _get_month_id(buffer[1], month_start) && + _get_day_id(buffer[2], week_day_end) && + _get_month_id(buffer[3], month_end)) + type = Daterange_TypeRange_month_week_day; + } else if (sscanf(line.c_str(), "%[a-z] %d - %d %n", buffer[0], + &month_day_start, &month_day_end, &pos) == 3) { + // thursday 2 - 4 + if (_get_day_id(buffer[0], week_day_start)) { + week_day_start_offset = month_day_start; + week_day_end = week_day_start; + week_day_end_offset = month_day_end; + type = Daterange_TypeRange_week_day; + } + // february 3 - 5 + else if (_get_month_id(buffer[0], month_start)) { + month_end = month_start; + type = Daterange_TypeRange_month_date; + } + // day 1 - 4 + else if (!strcmp(buffer[0], "day")) + type = Daterange_TypeRange_month_day; + } else if (sscanf(line.c_str(), "%[a-z] %d - %[a-z] %d %n", buffer[0], + &month_day_start, buffer[1], &month_day_end, &pos) == 4) { + // monday 2 - thursday 3 + if (_get_day_id(buffer[0], week_day_start) && + _get_day_id(buffer[1], week_day_end)) { + week_day_start_offset = month_day_start; + week_day_end_offset = month_day_end; + type = Daterange_TypeRange_week_day; + } + // february 1 - march 15 + else if (_get_month_id(buffer[0], month_start) && + _get_month_id(buffer[1], month_end)) + type = Daterange_TypeRange_month_date; + // day 1 - day 5 + else if (!strcmp(buffer[0], "day") && !strcmp(buffer[1], "day")) + type = Daterange_TypeRange_month_day; + } else if (sscanf(line.c_str(), "%[a-z] %d %[a-z] %n", buffer[0], + &week_day_start_offset, buffer[1], &pos) == 3) { + // thursday 3 february + if (_get_day_id(buffer[0], week_day_start) && + _get_month_id(buffer[1], month_start)) { + month_end = month_start; + week_day_end = week_day_start; + week_day_end_offset = week_day_start_offset; + type = Daterange_TypeRange_month_week_day; + } + } else if (sscanf(line.c_str(), "%[a-z] %d %n", buffer[0], &month_day_start, + &pos) == 2) { + // thursday 2 + if (_get_day_id(buffer[0], week_day_start)) { + week_day_start_offset = month_day_start; + week_day_end = week_day_start; + week_day_end_offset = week_day_start_offset; + type = Daterange_TypeRange_week_day; + } + // february 3 + else if (_get_month_id(buffer[0], month_start)) { + month_end = month_start; + month_day_end = month_day_start; + type = Daterange_TypeRange_month_date; + } + // day 1 + else if (!strcmp(buffer[0], "day")) { + month_day_end = month_day_start; + type = Daterange_TypeRange_month_day; + } + } + + if (type != Daterange_TypeRange_none) { + Timeperiod* obj = static_cast<Timeperiod*>(mut_obj()); + Daterange* range = nullptr; + switch (type) { + case Daterange_TypeRange_month_day: { + range = obj->mutable_exceptions()->add_month_day(); + range->set_type(type); + range->set_smday(month_day_start); + range->set_emday(month_day_end); + } break; + case Daterange_TypeRange_month_week_day: { + range = obj->mutable_exceptions()->add_month_week_day(); + range->set_type(type); + range->set_smon(month_start); + range->set_swday(week_day_start); + range->set_swday_offset(week_day_start_offset); + range->set_emon(month_end); + range->set_ewday(week_day_end); + range->set_ewday_offset(week_day_end_offset); + } break; + case Daterange_TypeRange_week_day: { + range = obj->mutable_exceptions()->add_week_day(); + range->set_type(type); + range->set_swday(week_day_start); + range->set_swday_offset(week_day_start_offset); + range->set_ewday(week_day_end); + range->set_ewday_offset(week_day_end_offset); + } break; + case Daterange_TypeRange_month_date: { + range = obj->mutable_exceptions()->add_month_date(); + range->set_type(type); + range->set_smon(month_start); + range->set_smday(month_day_start); + range->set_emon(month_end); + range->set_emday(month_day_end); + } break; + default: + return false; + } + range->set_skip_interval(skip_interval); + + if (!_build_timeranges(line.substr(pos), *range->mutable_timerange())) + return false; + + return true; + } + return false; +} + +/** + * Get the week day id. + * + * @param[in] name The week day name. + * @param[out] id The id to fill. + * + * @return True on success, otherwise false. + */ +bool timeperiod_helper::_get_day_id(std::string_view name, uint32_t& id) { + static std::array<std::string_view, 7> days{ + "sunday", "monday", "tuesday", "wednesday", + "thursday", "friday", "saturday"}; + for (id = 0; id < days.size(); ++id) + if (name == days[id]) + return true; + return false; +} + +/** + * Get the month id. + * + * @param[in] name The month name. + * @param[out] id The id to fill. + * + * @return True on success, otherwise false. + */ +bool timeperiod_helper::_get_month_id(std::string_view name, uint32_t& id) { + static std::array<std::string_view, 12> months{ + "january", "february", "march", "april", "may", "june", + "july", "august", "september", "october", "november", "december"}; + for (id = 0; id < months.size(); ++id) + if (name == months[id]) + return true; + return false; +} + +std::string daterange_to_str(const Daterange& dr) { + static std::array<std::string_view, 7> days{ + "sunday", "monday", "tuesday", "wednesday", + "thursday", "friday", "saturday"}; + static std::array<std::string_view, 12> months{ + "january", "february", "march", "april", "may", "june", + "july", "august", "september", "october", "november", "december"}; + std::string retval; + switch (dr.type()) { + case Daterange_TypeRange_calendar_date: { + std::string retval = fmt::format("{:02}-{:02}-{:02}", dr.syear(), + dr.smon() + 1, dr.smday()); + if (dr.syear() != dr.eyear() || dr.smon() != dr.emon() || + dr.smday() != dr.emday()) + retval = fmt::format("{} - {:02}-{:02}-{:02} / {}", retval, dr.eyear(), + dr.emon() + 1, dr.emday(), dr.skip_interval()); + } break; + case Daterange_TypeRange_month_date: { + retval = fmt::format("{} {}", months[dr.smon()], dr.smday()); + if (dr.smon() != dr.emon()) + retval = + fmt::format("{} - {} {}", retval, months[dr.emon()], dr.emday()); + else if (dr.smday() != dr.emday()) + retval = fmt::format("{} - {}", retval, dr.emday()); + if (dr.skip_interval()) + retval = fmt::format("{} / {}", retval, dr.skip_interval()); + } break; + case Daterange_TypeRange_month_day: { + retval = fmt::format("day {}", dr.smday()); + if (dr.smday() != dr.emday()) + retval = fmt::format("{} - {}", retval, dr.emday()); + if (dr.skip_interval()) + retval = fmt::format("{} / {}", retval, dr.skip_interval()); + } break; + case Daterange_TypeRange_month_week_day: { + retval = fmt::format("{} {} {}", days[dr.swday()], dr.swday_offset(), + months[dr.smon()]); + if (dr.swday() != dr.ewday() || dr.swday_offset() != dr.ewday_offset() || + dr.smon() != dr.emon()) + retval = fmt::format("{} - {} {} {}", retval, days[dr.ewday()], + dr.ewday_offset(), months[dr.emon()]); + if (dr.skip_interval()) + retval = fmt::format("{} / {}", dr.skip_interval()); + } break; + case Daterange_TypeRange_week_day: { + retval = fmt::format("{} {}", days[dr.swday()], dr.swday_offset()); + if (dr.swday() != dr.ewday() || dr.swday_offset() != dr.ewday_offset()) + retval = fmt::format("{} - {} {}", retval, days[dr.ewday()], + dr.ewday_offset()); + if (dr.skip_interval()) + retval = fmt::format("{} / {}", retval, dr.skip_interval()); + } break; + default: + assert("should not arrive" == nullptr); + } + std::vector<std::string> timeranges_str; + for (auto& t : dr.timerange()) { + uint32_t start_hours(t.range_start() / 3600); + uint32_t start_minutes((t.range_start() % 3600) / 60); + uint32_t end_hours(t.range_end() / 3600); + uint32_t end_minutes((t.range_end() % 3600) / 60); + timeranges_str.emplace_back(fmt::format("{:02}:{:02}-{:02}:{:02}", + start_hours, start_minutes, + end_hours, end_minutes)); + } + retval = fmt::format("{} {}", retval, fmt::join(timeranges_str, ", ")); + return retval; +} +} // namespace com::centreon::engine::configuration diff --git a/common/engine_conf/timeperiod_helper.hh b/common/engine_conf/timeperiod_helper.hh new file mode 100644 index 00000000000..e56c999bbb9 --- /dev/null +++ b/common/engine_conf/timeperiod_helper.hh @@ -0,0 +1,52 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#ifndef CCE_CONFIGURATION_TIMEPERIOD +#define CCE_CONFIGURATION_TIMEPERIOD + +#include "common/engine_conf/message_helper.hh" +#include "common/engine_conf/state.pb.h" + +namespace com::centreon::engine::configuration { + +class timeperiod_helper : public message_helper { + void _init(); + bool _add_week_day(std::string_view key, std::string_view value); + bool _add_calendar_date(const std::string& line); + bool _add_other_date(const std::string& line); + bool _build_timeranges( + std::string_view line, + google::protobuf::RepeatedPtrField<Timerange>& timeranges); + bool _build_time_t(std::string_view time_str, time_t& ret); + bool _get_day_id(std::string_view name, uint32_t& id); + bool _get_month_id(std::string_view name, uint32_t& id); + + public: + timeperiod_helper(Timeperiod* obj); + ~timeperiod_helper() noexcept = default; + void check_validity(error_cnt& err) const override; + + bool hook(std::string_view key, const std::string_view& value) override; +}; + +std::string daterange_to_str(const Daterange& dr); + +} // namespace com::centreon::engine::configuration + +#endif /* !CCE_CONFIGURATION_TIMEPERIOD */ diff --git a/common/engine_legacy_conf/CMakeLists.txt b/common/engine_legacy_conf/CMakeLists.txt index 03755663164..3c1ee75a7b8 100644 --- a/common/engine_legacy_conf/CMakeLists.txt +++ b/common/engine_legacy_conf/CMakeLists.txt @@ -44,7 +44,8 @@ add_library( timeperiod.cc ) -add_dependencies(engine_legacy_conf pb_neb_lib pb_bam_lib) +add_dependencies(engine_legacy_conf pb_neb_lib) +target_compile_definitions(engine_legacy_conf PRIVATE LEGACY_CONF) include_directories(${CMAKE_SOURCE_DIR}/common/inc) target_precompile_headers(engine_legacy_conf PRIVATE ${CMAKE_SOURCE_DIR}/common/precomp_inc/precomp.hh) diff --git a/common/engine_legacy_conf/host.hh b/common/engine_legacy_conf/host.hh index 0c1257443bf..1c5d61b0632 100644 --- a/common/engine_legacy_conf/host.hh +++ b/common/engine_legacy_conf/host.hh @@ -28,9 +28,7 @@ using com::centreon::common::opt; -namespace com::centreon::engine { - -namespace configuration { +namespace com::centreon::engine::configuration { class host : public object { public: @@ -223,11 +221,9 @@ class host : public object { std::set<std::pair<uint64_t, uint16_t>> _tags; }; -typedef std::shared_ptr<host> host_ptr; +using host_ptr = std::shared_ptr<host>; typedef std::list<host> list_host; using set_host = std::set<host>; -} // namespace configuration - -} // namespace com::centreon::engine +} // namespace com::centreon::engine::configuration #endif // !CCE_CONFIGURATION_HOST_HH diff --git a/common/engine_legacy_conf/object.hh b/common/engine_legacy_conf/object.hh index db155f96e87..1cd5b4903fe 100644 --- a/common/engine_legacy_conf/object.hh +++ b/common/engine_legacy_conf/object.hh @@ -23,6 +23,10 @@ typedef std::list<std::string> list_string; typedef std::set<std::string> set_string; +#ifndef LEGACY_CONF +#error This file should not be included. +#endif + namespace com::centreon::engine { namespace configuration { diff --git a/common/engine_legacy_conf/parser.hh b/common/engine_legacy_conf/parser.hh index 4d1f2d424b9..44c095534ce 100644 --- a/common/engine_legacy_conf/parser.hh +++ b/common/engine_legacy_conf/parser.hh @@ -22,10 +22,9 @@ #include <fstream> #include "file_info.hh" #include "state.hh" +#include "host.hh" -namespace com::centreon::engine { - -namespace configuration { +namespace com::centreon::engine::configuration { class parser { std::shared_ptr<spdlog::logger> _logger; @@ -63,7 +62,8 @@ class parser { void _apply(std::list<std::string> const& lst, void (parser::*pfunc)(std::string const&)); file_info const& _get_file_info(object* obj) const; - void _get_hosts_by_hostgroups(hostgroup const& hostgroups, list_host& hosts); + void _get_hosts_by_hostgroups(const hostgroup& hostgroups, + list_host& hosts); void _get_hosts_by_hostgroups_name(set_string const& lst_group, list_host& hosts); template <typename T> @@ -95,8 +95,6 @@ class parser { static store _store[]; std::array<map_object, 19> _templates; }; -} // namespace configuration - -} // namespace com::centreon::engine +} // namespace com::centreon::engine::configuration #endif // !CCE_CONFIGURATION_PARSER_HH diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 1a9c13caf88..9ad25b27368 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -487,9 +487,9 @@ include_directories(enginerpc # Library engine target. add_library(cce_core ${LIBRARY_TYPE} ${FILES}) -add_dependencies(cce_core engine_rpc) -add_dependencies(cce_core centreon_clib) -add_dependencies(cce_core pb_neb_lib) +add_dependencies(cce_core engine_rpc + centreon_clib + pb_neb_lib) target_precompile_headers(cce_core PRIVATE ${PRECOMP_HEADER}) @@ -500,6 +500,25 @@ target_link_libraries( ${PTHREAD_LIBRARIES} ${SOCKET_LIBRARIES} centreon_clib + engine_conf + fmt::fmt + spdlog::spdlog) + +add_library(cce_core_legacy ${LIBRARY_TYPE} ${FILES}) +add_dependencies(cce_core_legacy engine_rpc + centreon_clib + pb_neb_lib) +target_compile_definitions(cce_core_legacy PRIVATE LEGACY_CONF) + +target_precompile_headers(cce_core_legacy PRIVATE ${PRECOMP_HEADER}) + +# Link target with required libraries. +target_link_libraries( + cce_core_legacy + ${MATH_LIBRARIES} + ${PTHREAD_LIBRARIES} + ${SOCKET_LIBRARIES} + centreon_clib engine_legacy_conf fmt::fmt spdlog::spdlog) @@ -543,10 +562,48 @@ target_link_libraries( stdc++fs dl) +# centengine_legacy target. +add_executable(centengine_legacy "${SRC_DIR}/main.cc") +target_compile_definitions(centengine_legacy PRIVATE LEGACY_CONF) +set_property(TARGET centengine_legacy PROPERTY ENABLE_EXPORTS "1") +add_dependencies(centengine_legacy centreon_clib) + +# Link centengine_legacy with required libraries. +target_link_libraries( + centengine_legacy + -L${PROTOBUF_LIB_DIR} + "-rdynamic" + centreon_clib + log_v2 + "-Wl,-whole-archive" + enginerpc_legacy + centreon_grpc + centreon_http + -L${Boost_LIBRARY_DIR_RELEASE} + boost_url + cce_core_legacy + gRPC::grpc++ + boost_program_options + "-Wl,--no-whole-archive" + gRPC::gpr + gRPC::grpc + gRPC::grpc++_alts + absl::any + absl::log + absl::base + absl::bits + crypto + ssl + ${c-ares_LIBS} + z + ryml::ryml + stdc++fs + dl) + # centenginestats target. -add_executable("centenginestats" "${SRC_DIR}/centenginestats.cc") -add_dependencies("centenginestats" centreon_clib) -target_link_libraries("centenginestats" centreon_clib fmt::fmt) +add_executable(centenginestats "${SRC_DIR}/centenginestats.cc") +add_dependencies(centenginestats centreon_clib) +target_link_libraries(centenginestats centreon_clib fmt::fmt) target_precompile_headers(centenginestats PRIVATE ${PRECOMP_HEADER}) # Unit tests. diff --git a/engine/enginerpc/CMakeLists.txt b/engine/enginerpc/CMakeLists.txt index 80534a5d33e..07f1c4b2551 100644 --- a/engine/enginerpc/CMakeLists.txt +++ b/engine/enginerpc/CMakeLists.txt @@ -78,3 +78,23 @@ set_property( PROPERTY POSITION_INDEPENDENT_CODE ON) # Link target with libraries. target_link_libraries(${ENGINERPC} cerpc centreon_common spdlog::spdlog) + +# mod_enginerpc target. +add_library( + enginerpc_legacy STATIC + # Sources. + engine_impl.cc enginerpc.cc + # Headers. + "${INC_DIR}/engine_impl.hh" "${INC_DIR}/enginerpc.hh") + +add_dependencies(enginerpc_legacy centreon_common) +target_compile_definitions(enginerpc_legacy PRIVATE LEGACY_CONF) +target_precompile_headers(enginerpc_legacy PRIVATE precomp_inc/precomp.hh) + +# Prettier name. +set_property( + TARGET enginerpc_legacy + PROPERTY PREFIX "" + PROPERTY POSITION_INDEPENDENT_CODE ON) +# Link target with libraries. +target_link_libraries(enginerpc_legacy cerpc centreon_common spdlog::spdlog) diff --git a/engine/enginerpc/engine_impl.cc b/engine/enginerpc/engine_impl.cc index 8c5e5b89b20..a96819137d6 100644 --- a/engine/enginerpc/engine_impl.cc +++ b/engine/enginerpc/engine_impl.cc @@ -493,8 +493,8 @@ grpc::Status engine_impl::GetHostGroupsCount( * @return Status::OK */ grpc::Status engine_impl::GetServiceDependenciesCount( - grpc::ServerContext* context __attribute__((unused)), - const ::google::protobuf::Empty* request __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], + const ::google::protobuf::Empty* request [[maybe_unused]], GenericValue* response) { auto fn = std::packaged_task<int32_t(void)>([]() -> int32_t { return servicedependency::servicedependencies.size(); @@ -517,8 +517,8 @@ grpc::Status engine_impl::GetServiceDependenciesCount( * @return Status::OK */ grpc::Status engine_impl::GetHostDependenciesCount( - grpc::ServerContext* context __attribute__((unused)), - const ::google::protobuf::Empty* request __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], + const ::google::protobuf::Empty* request [[maybe_unused]], GenericValue* response) { auto fn = std::packaged_task<int32_t(void)>( []() -> int32_t { return hostdependency::hostdependencies.size(); }); @@ -545,10 +545,10 @@ grpc::Status engine_impl::GetHostDependenciesCount( * @return Status::OK */ grpc::Status engine_impl::AddHostComment(grpc::ServerContext* context - __attribute__((unused)), + [[maybe_unused]], const EngineComment* request, CommandSuccess* response - __attribute__((unused))) { + [[maybe_unused]]) { std::string err; auto fn = std::packaged_task<int32_t(void)>([&err, request]() -> int32_t { std::shared_ptr<engine::host> temp_host; @@ -595,10 +595,10 @@ grpc::Status engine_impl::AddHostComment(grpc::ServerContext* context * @return Status::OK */ grpc::Status engine_impl::AddServiceComment(grpc::ServerContext* context - __attribute__((unused)), + [[maybe_unused]], const EngineComment* request, CommandSuccess* response - __attribute__((unused))) { + [[maybe_unused]]) { std::string err; auto fn = std::packaged_task<int32_t(void)>([&err, request]() -> int32_t { std::shared_ptr<engine::host> temp_host; @@ -654,10 +654,10 @@ grpc::Status engine_impl::AddServiceComment(grpc::ServerContext* context * @return Status::OK */ grpc::Status engine_impl::DeleteComment(grpc::ServerContext* context - __attribute__((unused)), + [[maybe_unused]], const GenericValue* request, CommandSuccess* response - __attribute__((unused))) { + [[maybe_unused]]) { uint32_t comment_id = request->value(); std::string err; if (comment_id == 0) @@ -692,10 +692,10 @@ grpc::Status engine_impl::DeleteComment(grpc::ServerContext* context * @return Status::OK */ grpc::Status engine_impl::DeleteAllHostComments(grpc::ServerContext* context - __attribute__((unused)), + [[maybe_unused]], const HostIdentifier* request, CommandSuccess* response - __attribute__((unused))) { + [[maybe_unused]]) { std::string err; auto fn = std::packaged_task<int32_t(void)>([&err, request]() -> int32_t { std::shared_ptr<engine::host> temp_host; @@ -727,9 +727,9 @@ grpc::Status engine_impl::DeleteAllHostComments(grpc::ServerContext* context * @return Status::OK */ grpc::Status engine_impl::DeleteAllServiceComments( - grpc::ServerContext* context __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], const ServiceIdentifier* request, - CommandSuccess* response __attribute__((unused))) { + CommandSuccess* response [[maybe_unused]]) { std::string err; auto fn = std::packaged_task<int32_t(void)>([&err, request]() -> int32_t { std::shared_ptr<engine::service> temp_service; @@ -761,9 +761,9 @@ grpc::Status engine_impl::DeleteAllServiceComments( * @return Status::OK */ grpc::Status engine_impl::RemoveHostAcknowledgement( - grpc::ServerContext* context __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], const HostIdentifier* request, - CommandSuccess* response __attribute__((unused))) { + CommandSuccess* response [[maybe_unused]]) { std::string err; auto fn = std::packaged_task<int32_t(void)>([&err, request]() -> int32_t { std::shared_ptr<engine::host> temp_host; @@ -801,9 +801,9 @@ grpc::Status engine_impl::RemoveHostAcknowledgement( * @return Status::OK */ grpc::Status engine_impl::RemoveServiceAcknowledgement( - grpc::ServerContext* context __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], const ServiceIdentifier* request, - CommandSuccess* response __attribute__((unused))) { + CommandSuccess* response [[maybe_unused]]) { std::string err; auto fn = std::packaged_task<int32_t(void)>([&err, request]() -> int32_t { std::shared_ptr<engine::service> temp_service; @@ -831,9 +831,9 @@ grpc::Status engine_impl::RemoveServiceAcknowledgement( } grpc::Status engine_impl::AcknowledgementHostProblem( - grpc::ServerContext* context __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], const EngineAcknowledgement* request, - CommandSuccess* response __attribute__((unused))) { + CommandSuccess* response [[maybe_unused]]) { std::string err; auto fn = std::packaged_task<int32_t(void)>([&err, request]() -> int32_t { std::shared_ptr<engine::host> temp_host; @@ -892,9 +892,9 @@ grpc::Status engine_impl::AcknowledgementHostProblem( } grpc::Status engine_impl::AcknowledgementServiceProblem( - grpc::ServerContext* context __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], const EngineAcknowledgement* request, - CommandSuccess* response __attribute__((unused))) { + CommandSuccess* response [[maybe_unused]]) { std::string err; auto fn = std::packaged_task<int32_t(void)>([&err, request]() -> int32_t { std::shared_ptr<engine::service> temp_service; @@ -975,7 +975,7 @@ grpc::Status engine_impl::AcknowledgementServiceProblem( * @return Status::OK */ grpc::Status engine_impl::ScheduleHostDowntime( - grpc::ServerContext* context __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], const ScheduleDowntimeIdentifier* request, CommandSuccess* response) { if (request->host_name().empty() || request->author().empty() || @@ -1047,9 +1047,9 @@ grpc::Status engine_impl::ScheduleHostDowntime( * @return Status::OK */ grpc::Status engine_impl::ScheduleServiceDowntime( - grpc::ServerContext* context __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], const ScheduleDowntimeIdentifier* request, - CommandSuccess* response __attribute__((unused))) { + CommandSuccess* response [[maybe_unused]]) { if (request->host_name().empty() || request->service_desc().empty() || request->author().empty() || request->comment_data().empty()) return grpc::Status(::grpc::StatusCode::INVALID_ARGUMENT, @@ -1121,9 +1121,9 @@ grpc::Status engine_impl::ScheduleServiceDowntime( * @return Status::OK */ grpc::Status engine_impl::ScheduleHostServicesDowntime( - grpc::ServerContext* context __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], const ScheduleDowntimeIdentifier* request, - CommandSuccess* response __attribute__((unused))) { + CommandSuccess* response [[maybe_unused]]) { if (request->host_name().empty() || request->author().empty() || request->comment_data().empty()) return grpc::Status(::grpc::StatusCode::INVALID_ARGUMENT, @@ -1195,9 +1195,9 @@ grpc::Status engine_impl::ScheduleHostServicesDowntime( * @return Status::OK */ grpc::Status engine_impl::ScheduleHostGroupHostsDowntime( - grpc::ServerContext* context __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], const ScheduleDowntimeIdentifier* request, - CommandSuccess* response __attribute__((unused))) { + CommandSuccess* response [[maybe_unused]]) { if (request->host_group_name().empty() || request->author().empty() || request->comment_data().empty()) return grpc::Status(::grpc::StatusCode::INVALID_ARGUMENT, @@ -1268,9 +1268,9 @@ grpc::Status engine_impl::ScheduleHostGroupHostsDowntime( * @return Status::OK */ grpc::Status engine_impl::ScheduleHostGroupServicesDowntime( - grpc::ServerContext* context __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], const ScheduleDowntimeIdentifier* request, - CommandSuccess* response __attribute__((unused))) { + CommandSuccess* response [[maybe_unused]]) { if (request->host_group_name().empty() || request->author().empty() || request->comment_data().empty()) return grpc::Status(::grpc::StatusCode::INVALID_ARGUMENT, @@ -1351,9 +1351,9 @@ grpc::Status engine_impl::ScheduleHostGroupServicesDowntime( * @return Status::OK */ grpc::Status engine_impl::ScheduleServiceGroupHostsDowntime( - grpc::ServerContext* context __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], const ScheduleDowntimeIdentifier* request, - CommandSuccess* response __attribute__((unused))) { + CommandSuccess* response [[maybe_unused]]) { if (request->service_group_name().empty() || request->author().empty() || request->comment_data().empty()) return grpc::Status(::grpc::StatusCode::INVALID_ARGUMENT, @@ -1431,9 +1431,9 @@ grpc::Status engine_impl::ScheduleServiceGroupHostsDowntime( * @return Status::OK */ grpc::Status engine_impl::ScheduleServiceGroupServicesDowntime( - grpc::ServerContext* context __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], const ScheduleDowntimeIdentifier* request, - CommandSuccess* response __attribute__((unused))) { + CommandSuccess* response [[maybe_unused]]) { if (request->service_group_name().empty() || request->author().empty() || request->comment_data().empty()) return grpc::Status(::grpc::StatusCode::INVALID_ARGUMENT, @@ -1501,9 +1501,9 @@ grpc::Status engine_impl::ScheduleServiceGroupServicesDowntime( * @return Status::OK */ grpc::Status engine_impl::ScheduleAndPropagateHostDowntime( - grpc::ServerContext* context __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], const ScheduleDowntimeIdentifier* request, - CommandSuccess* response __attribute__((unused))) { + CommandSuccess* response [[maybe_unused]]) { if (request->host_name().empty() || request->author().empty() || request->comment_data().empty()) return grpc::Status(::grpc::StatusCode::INVALID_ARGUMENT, @@ -1575,9 +1575,9 @@ grpc::Status engine_impl::ScheduleAndPropagateHostDowntime( */ grpc::Status engine_impl::ScheduleAndPropagateTriggeredHostDowntime( - grpc::ServerContext* context __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], const ScheduleDowntimeIdentifier* request, - CommandSuccess* response __attribute__((unused))) { + CommandSuccess* response [[maybe_unused]]) { if (request->host_name().empty() || request->author().empty() || request->comment_data().empty()) return grpc::Status(::grpc::StatusCode::INVALID_ARGUMENT, @@ -1637,10 +1637,10 @@ grpc::Status engine_impl::ScheduleAndPropagateTriggeredHostDowntime( * @return Status::OK */ grpc::Status engine_impl::DeleteDowntime(grpc::ServerContext* context - __attribute__((unused)), + [[maybe_unused]], const GenericValue* request, CommandSuccess* response - __attribute__((unused))) { + [[maybe_unused]]) { uint32_t downtime_id = request->value(); std::string err; auto fn = @@ -1677,11 +1677,11 @@ grpc::Status engine_impl::DeleteDowntime(grpc::ServerContext* context * @return Status::OK */ grpc::Status engine_impl::DeleteHostDowntimeFull( - grpc::ServerContext* context __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], const DowntimeCriterias* request, - CommandSuccess* response __attribute__((unused))) { + CommandSuccess* response [[maybe_unused]]) { std::string err; - auto fn = std::packaged_task<int32_t(void)>([&err, request]() -> int32_t { + auto fn = std::packaged_task<int32_t(void)>([request]() -> int32_t { std::list<std::shared_ptr<downtimes::downtime>> dtlist; for (auto it = downtimes::downtime_manager::instance() .get_scheduled_downtimes() @@ -1744,9 +1744,9 @@ grpc::Status engine_impl::DeleteHostDowntimeFull( * @return Status::OK */ grpc::Status engine_impl::DeleteServiceDowntimeFull( - grpc::ServerContext* context __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], const DowntimeCriterias* request, - CommandSuccess* response __attribute__((unused))) { + CommandSuccess* response [[maybe_unused]]) { std::string err; auto fn = std::packaged_task<int32_t(void)>([&err, request]() -> int32_t { std::list<service_downtime*> dtlist; @@ -1817,9 +1817,9 @@ grpc::Status engine_impl::DeleteServiceDowntimeFull( * @return Status::OK */ grpc::Status engine_impl::DeleteDowntimeByHostName( - grpc::ServerContext* context __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], const DowntimeHostIdentifier* request, - CommandSuccess* response __attribute__((unused))) { + CommandSuccess* response [[maybe_unused]]) { /*hostname must be defined to delete the downtime but not others arguments*/ std::string const& host_name = request->host_name(); if (host_name.empty()) @@ -1874,9 +1874,9 @@ grpc::Status engine_impl::DeleteDowntimeByHostName( * @return Status::OK */ grpc::Status engine_impl::DeleteDowntimeByHostGroupName( - grpc::ServerContext* context __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], const DowntimeHostGroupIdentifier* request, - CommandSuccess* response __attribute__((unused))) { + CommandSuccess* response [[maybe_unused]]) { std::string const& host_group_name = request->host_group_name(); if (host_group_name.empty()) return grpc::Status(::grpc::StatusCode::INVALID_ARGUMENT, @@ -1950,9 +1950,9 @@ grpc::Status engine_impl::DeleteDowntimeByHostGroupName( * @return Status::OK */ grpc::Status engine_impl::DeleteDowntimeByStartTimeComment( - grpc::ServerContext* context __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], const DowntimeStartTimeIdentifier* request, - CommandSuccess* response __attribute__((unused))) { + CommandSuccess* response [[maybe_unused]]) { time_t start_time; /*hostname must be defined to delete the downtime but not others arguments*/ if (!(request->has_start())) @@ -1999,10 +1999,10 @@ grpc::Status engine_impl::DeleteDowntimeByStartTimeComment( * @return Status::OK */ grpc::Status engine_impl::ScheduleHostCheck(grpc::ServerContext* context - __attribute__((unused)), + [[maybe_unused]], const HostCheckIdentifier* request, CommandSuccess* response - __attribute__((unused))) { + [[maybe_unused]]) { if (request->host_name().empty()) return grpc::Status(::grpc::StatusCode::INVALID_ARGUMENT, "host_name must not be empty"); @@ -2046,9 +2046,9 @@ grpc::Status engine_impl::ScheduleHostCheck(grpc::ServerContext* context * @return Status::OK */ grpc::Status engine_impl::ScheduleHostServiceCheck( - grpc::ServerContext* context __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], const HostCheckIdentifier* request, - CommandSuccess* response __attribute__((unused))) { + CommandSuccess* response [[maybe_unused]]) { if (request->host_name().empty()) return grpc::Status(::grpc::StatusCode::INVALID_ARGUMENT, "host_name must not be empty"); @@ -2100,9 +2100,9 @@ grpc::Status engine_impl::ScheduleHostServiceCheck( * @return Status::OK */ grpc::Status engine_impl::ScheduleServiceCheck( - grpc::ServerContext* context __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], const ServiceCheckIdentifier* request, - CommandSuccess* response __attribute__((unused))) { + CommandSuccess* response [[maybe_unused]]) { if (request->host_name().empty()) return grpc::Status(::grpc::StatusCode::INVALID_ARGUMENT, "host_name must not be empty"); @@ -2152,10 +2152,10 @@ grpc::Status engine_impl::ScheduleServiceCheck( * @return Status::OK */ grpc::Status engine_impl::SignalProcess(grpc::ServerContext* context - __attribute__((unused)), + [[maybe_unused]], const EngineSignalProcess* request, CommandSuccess* response - __attribute__((unused))) { + [[maybe_unused]]) { std::string err; auto fn = std::packaged_task<int32_t(void)>([&err, request]() -> int32_t { std::unique_ptr<timed_event> evt; @@ -2198,9 +2198,9 @@ grpc::Status engine_impl::SignalProcess(grpc::ServerContext* context * @return Status::OK */ grpc::Status engine_impl::DelayHostNotification( - grpc::ServerContext* context __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], const HostDelayIdentifier* request, - CommandSuccess* response __attribute__((unused))) { + CommandSuccess* response [[maybe_unused]]) { std::string err; auto fn = std::packaged_task<int32_t(void)>([&err, request]() -> int32_t { std::shared_ptr<engine::host> temp_host; @@ -2255,9 +2255,9 @@ grpc::Status engine_impl::DelayHostNotification( * @return Status::OK */ grpc::Status engine_impl::DelayServiceNotification( - grpc::ServerContext* context __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], const ServiceDelayIdentifier* request, - CommandSuccess* response __attribute__((unused))) { + CommandSuccess* response [[maybe_unused]]) { std::string err; auto fn = std::packaged_task<int32_t(void)>([&err, request]() -> int32_t { std::shared_ptr<engine::service> temp_service; @@ -2308,10 +2308,10 @@ grpc::Status engine_impl::DelayServiceNotification( } grpc::Status engine_impl::ChangeHostObjectIntVar(grpc::ServerContext* context - __attribute__((unused)), + [[maybe_unused]], const ChangeObjectInt* request, CommandSuccess* response - __attribute__((unused))) { + [[maybe_unused]]) { std::string err; auto fn = std::packaged_task<int32_t(void)>([&err, request]() -> int32_t { std::shared_ptr<engine::host> temp_host; @@ -2419,9 +2419,9 @@ grpc::Status engine_impl::ChangeHostObjectIntVar(grpc::ServerContext* context } grpc::Status engine_impl::ChangeServiceObjectIntVar( - grpc::ServerContext* context __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], const ChangeObjectInt* request, - CommandSuccess* response __attribute__((unused))) { + CommandSuccess* response [[maybe_unused]]) { std::string err; auto fn = std::packaged_task<int32_t(void)>([&err, request]() -> int32_t { std::shared_ptr<engine::service> temp_service; @@ -2535,9 +2535,9 @@ grpc::Status engine_impl::ChangeServiceObjectIntVar( } grpc::Status engine_impl::ChangeContactObjectIntVar( - grpc::ServerContext* context __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], const ChangeContactObjectInt* request, - CommandSuccess* response __attribute__((unused))) { + CommandSuccess* response [[maybe_unused]]) { std::string err; auto fn = std::packaged_task<int32_t(void)>([&err, request]() -> int32_t { std::shared_ptr<com::centreon::engine::contact> temp_contact; @@ -2595,9 +2595,9 @@ grpc::Status engine_impl::ChangeContactObjectIntVar( } grpc::Status engine_impl::ChangeHostObjectCharVar( - grpc::ServerContext* context __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], const ChangeObjectChar* request, - CommandSuccess* response __attribute__((unused))) { + CommandSuccess* response [[maybe_unused]]) { std::string err; auto fn = std::packaged_task<int32_t(void)>([&err, request]() -> int32_t { std::shared_ptr<engine::host> temp_host; @@ -2646,7 +2646,11 @@ grpc::Status engine_impl::ChangeHostObjectCharVar( /* update the variable */ switch (request->mode()) { case ChangeObjectChar_Mode_CHANGE_GLOBAL_EVENT_HANDLER: +#ifdef LEGACY_CONF config->global_host_event_handler(request->charval()); +#else + pb_config.set_global_host_event_handler(request->charval()); +#endif global_host_event_handler_ptr = cmd_found->second.get(); attr = MODATTR_EVENT_HANDLER_COMMAND; /* set the modified host attribute */ @@ -2711,9 +2715,9 @@ grpc::Status engine_impl::ChangeHostObjectCharVar( } grpc::Status engine_impl::ChangeServiceObjectCharVar( - grpc::ServerContext* context __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], const ChangeObjectChar* request, - CommandSuccess* response __attribute__((unused))) { + CommandSuccess* response [[maybe_unused]]) { std::string err; auto fn = std::packaged_task<int32_t(void)>([&err, request]() -> int32_t { std::shared_ptr<engine::service> temp_service; @@ -2764,7 +2768,11 @@ grpc::Status engine_impl::ChangeServiceObjectCharVar( /* update the variable */ if (request->mode() == ChangeObjectChar_Mode_CHANGE_GLOBAL_EVENT_HANDLER) { +#ifdef LEGACY_CONF config->global_service_event_handler(request->charval()); +#else + pb_config.set_global_service_event_handler(request->charval()); +#endif global_service_event_handler_ptr = cmd_found->second.get(); attr = MODATTR_EVENT_HANDLER_COMMAND; } else if (request->mode() == ChangeObjectChar_Mode_CHANGE_EVENT_HANDLER) { @@ -2824,9 +2832,9 @@ grpc::Status engine_impl::ChangeServiceObjectCharVar( } grpc::Status engine_impl::ChangeContactObjectCharVar( - grpc::ServerContext* context __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], const ChangeContactObjectChar* request, - CommandSuccess* response __attribute__((unused))) { + CommandSuccess* response [[maybe_unused]]) { if (request->contact().empty()) return grpc::Status(::grpc::StatusCode::INVALID_ARGUMENT, "contact must not be empty"); @@ -2899,9 +2907,9 @@ grpc::Status engine_impl::ChangeContactObjectCharVar( } grpc::Status engine_impl::ChangeHostObjectCustomVar( - grpc::ServerContext* context __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], const ChangeObjectCustomVar* request, - CommandSuccess* response __attribute__((unused))) { + CommandSuccess* response [[maybe_unused]]) { if (request->host_name().empty()) return grpc::Status(::grpc::StatusCode::INVALID_ARGUMENT, "host_name must not be empty"); @@ -2940,9 +2948,9 @@ grpc::Status engine_impl::ChangeHostObjectCustomVar( } grpc::Status engine_impl::ChangeServiceObjectCustomVar( - grpc::ServerContext* context __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], const ChangeObjectCustomVar* request, - CommandSuccess* response __attribute__((unused))) { + CommandSuccess* response [[maybe_unused]]) { if (request->host_name().empty()) return grpc::Status(::grpc::StatusCode::INVALID_ARGUMENT, "host_name must not be empty"); @@ -2985,9 +2993,9 @@ grpc::Status engine_impl::ChangeServiceObjectCustomVar( } grpc::Status engine_impl::ChangeContactObjectCustomVar( - grpc::ServerContext* context __attribute__((unused)), + grpc::ServerContext* context [[maybe_unused]], const ChangeObjectCustomVar* request, - CommandSuccess* response __attribute__((unused))) { + CommandSuccess* response [[maybe_unused]]) { if (request->contact().empty()) return grpc::Status(::grpc::StatusCode::INVALID_ARGUMENT, "contact must not be empty"); @@ -3034,9 +3042,9 @@ grpc::Status engine_impl::ChangeContactObjectCustomVar( * @return Status::OK */ grpc::Status engine_impl::ShutdownProgram( - grpc::ServerContext* context __attribute__((unused)), - const ::google::protobuf::Empty* request __attribute__((unused)), - ::google::protobuf::Empty* response __attribute__((unused))) { + grpc::ServerContext* context [[maybe_unused]], + const ::google::protobuf::Empty* request [[maybe_unused]], + ::google::protobuf::Empty* response [[maybe_unused]]) { auto fn = std::packaged_task<int32_t(void)>([]() -> int32_t { exit(0); return 0; @@ -3070,9 +3078,9 @@ grpc::Status engine_impl::ShutdownProgram( } ::grpc::Status engine_impl::EnableHostAndChildNotifications( - ::grpc::ServerContext* context, + ::grpc::ServerContext* context [[maybe_unused]], const ::com::centreon::engine::HostIdentifier* request, - ::com::centreon::engine::CommandSuccess* response) { + ::com::centreon::engine::CommandSuccess* response [[maybe_unused]]) { HOST_METHOD_BEGIN commands::processing::wrapper_enable_host_and_child_notifications( host_info.first.get()); @@ -3080,9 +3088,9 @@ ::grpc::Status engine_impl::EnableHostAndChildNotifications( } ::grpc::Status engine_impl::DisableHostAndChildNotifications( - ::grpc::ServerContext* context, + ::grpc::ServerContext* context [[maybe_unused]], const ::com::centreon::engine::HostIdentifier* request, - ::com::centreon::engine::CommandSuccess* response) { + ::com::centreon::engine::CommandSuccess* response [[maybe_unused]]) { HOST_METHOD_BEGIN commands::processing::wrapper_disable_host_and_child_notifications( host_info.first.get()); @@ -3090,61 +3098,61 @@ ::grpc::Status engine_impl::DisableHostAndChildNotifications( } ::grpc::Status engine_impl::DisableHostNotifications( - ::grpc::ServerContext* context, + ::grpc::ServerContext* context [[maybe_unused]], const ::com::centreon::engine::HostIdentifier* request, - ::com::centreon::engine::CommandSuccess* response) { + ::com::centreon::engine::CommandSuccess* response [[maybe_unused]]) { HOST_METHOD_BEGIN disable_host_notifications(host_info.first.get()); return grpc::Status::OK; } ::grpc::Status engine_impl::EnableHostNotifications( - ::grpc::ServerContext* context, + ::grpc::ServerContext* context [[maybe_unused]], const ::com::centreon::engine::HostIdentifier* request, - ::com::centreon::engine::CommandSuccess* response) { + ::com::centreon::engine::CommandSuccess* response [[maybe_unused]]) { HOST_METHOD_BEGIN enable_host_notifications(host_info.first.get()); return grpc::Status::OK; } ::grpc::Status engine_impl::DisableNotifications( - ::grpc::ServerContext* context, + ::grpc::ServerContext* context [[maybe_unused]], const ::google::protobuf::Empty*, - ::com::centreon::engine::CommandSuccess* response) { + ::com::centreon::engine::CommandSuccess* response [[maybe_unused]]) { disable_all_notifications(); return grpc::Status::OK; } ::grpc::Status engine_impl::EnableNotifications( - ::grpc::ServerContext* context, + ::grpc::ServerContext* context [[maybe_unused]], const ::google::protobuf::Empty*, - ::com::centreon::engine::CommandSuccess* response) { + ::com::centreon::engine::CommandSuccess* response [[maybe_unused]]) { enable_all_notifications(); return grpc::Status::OK; } ::grpc::Status engine_impl::DisableServiceNotifications( - ::grpc::ServerContext* context, + ::grpc::ServerContext* context [[maybe_unused]], const ::com::centreon::engine::ServiceIdentifier* request, - ::com::centreon::engine::CommandSuccess* response) { + ::com::centreon::engine::CommandSuccess* response [[maybe_unused]]) { SERV_METHOD_BEGIN disable_service_notifications(serv_info.first.get()); return grpc::Status::OK; } ::grpc::Status engine_impl::EnableServiceNotifications( - ::grpc::ServerContext* context, + ::grpc::ServerContext* context [[maybe_unused]], const ::com::centreon::engine::ServiceIdentifier* request, - ::com::centreon::engine::CommandSuccess* response) { + ::com::centreon::engine::CommandSuccess* response [[maybe_unused]]) { SERV_METHOD_BEGIN enable_service_notifications(serv_info.first.get()); return grpc::Status::OK; } ::grpc::Status engine_impl::ChangeAnomalyDetectionSensitivity( - ::grpc::ServerContext* context, + ::grpc::ServerContext* context [[maybe_unused]], const ::com::centreon::engine::ChangeServiceNumber* serv_and_value, - ::com::centreon::engine::CommandSuccess* response) { + ::com::centreon::engine::CommandSuccess* response [[maybe_unused]]) { SPDLOG_LOGGER_DEBUG(external_command_logger, "{}({})", __FUNCTION__, serv_and_value->serv()); auto serv_info = get_serv(serv_and_value->serv()); @@ -3352,9 +3360,9 @@ grpc::Status engine_impl::GetProcessStats( * @return grpc::Status */ grpc::Status engine_impl::SendBench( - grpc::ServerContext* context, + grpc::ServerContext* context [[maybe_unused]], const com::centreon::engine::BenchParam* request, - google::protobuf::Empty* response) { + google::protobuf::Empty* response [[maybe_unused]]) { std::chrono::system_clock::time_point client_ts = std::chrono::system_clock::time_point::min(); diff --git a/engine/inc/com/centreon/engine/broker.hh b/engine/inc/com/centreon/engine/broker.hh index d958522d091..843c9589477 100644 --- a/engine/inc/com/centreon/engine/broker.hh +++ b/engine/inc/com/centreon/engine/broker.hh @@ -425,8 +425,8 @@ int broker_contact_notification_method_data( void broker_contact_status(int type, com::centreon::engine::contact* cntct); void broker_custom_variable(int type, void* data, - char const* varname, - char const* varvalue, + std::string_view&& varname, + std::string_view&& varvalue, struct timeval const* timestamp); void broker_downtime_data(int type, int attr, diff --git a/engine/inc/com/centreon/engine/configuration/applier/anomalydetection.hh b/engine/inc/com/centreon/engine/configuration/applier/anomalydetection.hh index 7e8df53d199..e7d9ea80a2e 100644 --- a/engine/inc/com/centreon/engine/configuration/applier/anomalydetection.hh +++ b/engine/inc/com/centreon/engine/configuration/applier/anomalydetection.hh @@ -20,9 +20,13 @@ #define CCE_CONFIGURATION_APPLIER_ANOMALYDETECTION_HH #include "com/centreon/engine/configuration/applier/state.hh" -namespace com::centreon::engine { +#ifndef LEGACY_CONF +#include "common/engine_conf/anomalydetection_helper.hh" +#include "common/engine_conf/state.pb.h" +#endif + +namespace com::centreon::engine::configuration { -namespace configuration { // Forward declarations. class anomalydetection; class state; @@ -39,16 +43,24 @@ class anomalydetection { anomalydetection(const anomalydetection&) = delete; ~anomalydetection() noexcept = default; anomalydetection& operator=(const anomalydetection&) = delete; +#ifdef LEGACY_CONF void add_object(configuration::anomalydetection const& obj); void modify_object(configuration::anomalydetection const& obj); void remove_object(configuration::anomalydetection const& obj); void expand_objects(configuration::state& s); void resolve_object(configuration::anomalydetection const& obj, error_cnt& err); +#else + void add_object(const configuration::Anomalydetection& obj); + void modify_object(configuration::Anomalydetection* old_obj, + const configuration::Anomalydetection& new_obj); + void remove_object(ssize_t idx); + void expand_objects(configuration::State& s); + void resolve_object(const configuration::Anomalydetection& obj, + error_cnt& err); +#endif }; } // namespace applier -} // namespace configuration - -} // namespace com::centreon::engine +} // namespace com::centreon::engine::configuration #endif // !CCE_CONFIGURATION_APPLIER_ANOMALYDETECTION_HH diff --git a/engine/inc/com/centreon/engine/configuration/applier/command.hh b/engine/inc/com/centreon/engine/configuration/applier/command.hh index e55cfdde7bf..056a673a7c3 100644 --- a/engine/inc/com/centreon/engine/configuration/applier/command.hh +++ b/engine/inc/com/centreon/engine/configuration/applier/command.hh @@ -20,6 +20,10 @@ #define CCE_CONFIGURATION_APPLIER_COMMAND_HH #include "com/centreon/engine/configuration/applier/state.hh" +#ifndef LEGACY_CONF +#include "common/engine_conf/command_helper.hh" +#endif + namespace com::centreon::engine { // Forward declarations. @@ -35,18 +39,28 @@ class state; namespace applier { class command { public: - command(); + command() = default; command(command const&) = delete; command& operator=(command const&) = delete; - ~command() noexcept; + ~command() noexcept = default; +#ifdef LEGACY_CONF void add_object(configuration::command const& obj); void expand_objects(configuration::state& s); void modify_object(configuration::command const& obj); void remove_object(configuration::command const& obj); void resolve_object(configuration::command const& obj, error_cnt& err); +#else + void add_object(const configuration::Command& obj); + void expand_objects(configuration::State& s); + void modify_object(configuration::Command* to_modify, + const configuration::Command& new_obj); + void remove_object(ssize_t idx); + void resolve_object(const configuration::Command& obj, + error_cnt& err); +#endif }; } // namespace applier } // namespace configuration diff --git a/engine/inc/com/centreon/engine/configuration/applier/connector.hh b/engine/inc/com/centreon/engine/configuration/applier/connector.hh index ad82485acaf..95d06ff7638 100644 --- a/engine/inc/com/centreon/engine/configuration/applier/connector.hh +++ b/engine/inc/com/centreon/engine/configuration/applier/connector.hh @@ -20,6 +20,10 @@ #define CCE_CONFIGURATION_APPLIER_CONNECTOR_HH #include "com/centreon/engine/configuration/applier/state.hh" +#ifndef LEGACY_CONF +#include "common/engine_conf/connector_helper.hh" +#endif + namespace com::centreon::engine { namespace configuration { @@ -40,11 +44,21 @@ class connector { ~connector() noexcept = default; connector(const connector&) = delete; connector& operator=(const connector&) = delete; +#ifdef LEGACY_CONF void add_object(configuration::connector const& obj); void modify_object(const configuration::connector& obj); void remove_object(configuration::connector const& obj); void expand_objects(configuration::state& s); void resolve_object(configuration::connector const& obj, error_cnt& err); +#else + void add_object(const configuration::Connector& obj); + void modify_object(configuration::Connector* to_modify, + const configuration::Connector& new_obj); + void remove_object(ssize_t idx); + void expand_objects(configuration::State& s); + void resolve_object(const configuration::Connector& obj, + error_cnt& err); +#endif }; } // namespace applier } // namespace configuration diff --git a/engine/inc/com/centreon/engine/configuration/applier/contact.hh b/engine/inc/com/centreon/engine/configuration/applier/contact.hh index 3ece9e9ac74..43d28250ecd 100644 --- a/engine/inc/com/centreon/engine/configuration/applier/contact.hh +++ b/engine/inc/com/centreon/engine/configuration/applier/contact.hh @@ -20,12 +20,16 @@ #define CCE_CONFIGURATION_APPLIER_CONTACT_HH #include "com/centreon/engine/configuration/applier/state.hh" -namespace com::centreon::engine { +#ifndef LEGACY_CONF +#include "common/engine_conf/contact_helper.hh" +#endif -namespace configuration { +namespace com::centreon::engine::configuration { // Forward declarations. +#ifdef LEGACY_CONF class contact; class state; +#endif namespace applier { class contact { @@ -42,15 +46,22 @@ class contact { contact(contact const&) = delete; contact& operator=(const contact&) = delete; +#ifdef LEGACY_CONF void add_object(const configuration::contact& obj); void modify_object(const configuration::contact& obj); void remove_object(const configuration::contact& obj); void expand_objects(configuration::state& s); void resolve_object(const configuration::contact& obj, error_cnt& err); +#else + void add_object(const configuration::Contact& obj); + void modify_object(configuration::Contact* to_modify, + const configuration::Contact& new_object); + void remove_object(ssize_t idx); + void expand_objects(configuration::State& s); + void resolve_object(const configuration::Contact& obj, error_cnt& err); +#endif }; } // namespace applier -} // namespace configuration - -} // namespace com::centreon::engine +} // namespace com::centreon::engine::configuration #endif // !CCE_CONFIGURATION_APPLIER_CONTACT_HH diff --git a/engine/inc/com/centreon/engine/configuration/applier/contactgroup.hh b/engine/inc/com/centreon/engine/configuration/applier/contactgroup.hh index a17825bdb32..d264b9dd06e 100644 --- a/engine/inc/com/centreon/engine/configuration/applier/contactgroup.hh +++ b/engine/inc/com/centreon/engine/configuration/applier/contactgroup.hh @@ -20,16 +20,20 @@ #define CCE_CONFIGURATION_APPLIER_CONTACTGROUP_HH #include "com/centreon/engine/configuration/applier/state.hh" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/contactgroup.hh" +#else +#include "common/engine_conf/contactgroup_helper.hh" +#endif -namespace com::centreon::engine { +namespace com::centreon::engine::configuration { -namespace configuration { // Forward declarations. class state; namespace applier { class contactgroup { +#ifdef LEGACY_CONF typedef std::map<configuration::contactgroup::key_type, configuration::contactgroup> resolved_set; @@ -38,6 +42,11 @@ class contactgroup { void _resolve_members(configuration::state& s, configuration::contactgroup const& obj); +#else + void _resolve_members(configuration::State& s, + configuration::Contactgroup & obj, + absl::flat_hash_set<std::string_view>& resolved); +#endif public: /** @@ -50,15 +59,23 @@ class contactgroup { ~contactgroup() noexcept = default; contactgroup(const contactgroup&) = delete; contactgroup& operator=(const contactgroup&) = delete; +#ifdef LEGACY_CONF void add_object(configuration::contactgroup const& obj); void modify_object(configuration::contactgroup const& obj); void remove_object(configuration::contactgroup const& obj); void expand_objects(configuration::state& s); void resolve_object(configuration::contactgroup const& obj, error_cnt& err); +#else + void add_object(const configuration::Contactgroup& obj); + void modify_object(configuration::Contactgroup* to_modify, + const configuration::Contactgroup& new_object); + void remove_object(ssize_t idx); + void expand_objects(configuration::State& s); + void resolve_object(const configuration::Contactgroup& obj, error_cnt& err); +#endif }; } // namespace applier -} // namespace configuration -} // namespace com::centreon::engine +} // namespace com::centreon::engine::configuration #endif // !CCE_CONFIGURATION_APPLIER_CONTACTGROUP_HH diff --git a/engine/inc/com/centreon/engine/configuration/applier/globals.hh b/engine/inc/com/centreon/engine/configuration/applier/globals.hh index 1ee1d7eb431..6bcce7f005f 100644 --- a/engine/inc/com/centreon/engine/configuration/applier/globals.hh +++ b/engine/inc/com/centreon/engine/configuration/applier/globals.hh @@ -19,11 +19,14 @@ #ifndef CCE_CONFIGURATION_APPLIER_GLOBALS_HH #define CCE_CONFIGURATION_APPLIER_GLOBALS_HH +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/state.hh" +#else +#include "common/engine_conf/state_helper.hh" +#endif -namespace com::centreon::engine { +namespace com::centreon::engine::configuration { -namespace configuration { namespace applier { /** * @class globals globals.hh @@ -42,13 +45,16 @@ class globals { void _set_global(char*& property, std::string const& value); public: +#ifdef LEGACY_CONF void apply(configuration::state& globals); +#else + void apply(configuration::State& globals); +#endif static globals& instance(); void clear(); }; } // namespace applier -} // namespace configuration -} // namespace com::centreon::engine +} // namespace com::centreon::engine::configuration #endif // !CCE_CONFIGURATION_APPLIER_GLOBALS_HH diff --git a/engine/inc/com/centreon/engine/configuration/applier/host.hh b/engine/inc/com/centreon/engine/configuration/applier/host.hh index 51f0f5d1667..542831e671d 100644 --- a/engine/inc/com/centreon/engine/configuration/applier/host.hh +++ b/engine/inc/com/centreon/engine/configuration/applier/host.hh @@ -20,29 +20,41 @@ #define CCE_CONFIGURATION_APPLIER_HOST_HH #include "com/centreon/engine/configuration/applier/state.hh" -namespace com::centreon::engine { +#ifndef LEGACY_CONF +#include "common/engine_conf/host_helper.hh" +#endif -namespace configuration { +namespace com::centreon::engine::configuration { + +#ifdef LEGACY_CONF // Forward declarations. class host; class state; +#endif namespace applier { class host { public: - host(); - host(host const& right) = delete; - ~host() throw(); - host& operator=(host const& right) = delete; - void add_object(configuration::host const& obj); + host() = default; + host(host const&) = delete; + ~host() noexcept = default; + host& operator=(host const&) = delete; +#ifdef LEGACY_CONF + void add_object(const configuration::host& obj); void expand_objects(configuration::state& s); void modify_object(configuration::host const& obj); void remove_object(configuration::host const& obj); void resolve_object(configuration::host const& obj, error_cnt& err); +#else + void add_object(const configuration::Host& obj); + void expand_objects(configuration::State& s); + void modify_object(configuration::Host* old_obj, + const configuration::Host& new_obj); + void remove_object(ssize_t idx); + void resolve_object(const configuration::Host& obj, error_cnt& err); +#endif }; } // namespace applier -} // namespace configuration - -} // namespace com::centreon::engine +} // namespace com::centreon::engine::configuration #endif // !CCE_CONFIGURATION_APPLIER_HOST_HH diff --git a/engine/inc/com/centreon/engine/configuration/applier/hostdependency.hh b/engine/inc/com/centreon/engine/configuration/applier/hostdependency.hh index 5c4f0278a96..a2f8092096f 100644 --- a/engine/inc/com/centreon/engine/configuration/applier/hostdependency.hh +++ b/engine/inc/com/centreon/engine/configuration/applier/hostdependency.hh @@ -21,6 +21,10 @@ #include "com/centreon/engine/configuration/applier/state.hh" +#ifndef LEGACY_CONF +#include "common/engine_conf/hostdependency_helper.hh" +#endif + namespace com::centreon::engine { namespace configuration { @@ -30,21 +34,32 @@ class state; namespace applier { class hostdependency { +#ifdef LEGACY_CONF void _expand_hosts(std::set<std::string> const& hosts, std::set<std::string> const& hostgroups, configuration::state& s, std::set<std::string>& expanded); +#endif public: hostdependency() = default; hostdependency(const hostdependency&) = delete; ~hostdependency() noexcept = default; hostdependency& operator=(const hostdependency&) = delete; +#ifdef LEGACY_CONF void add_object(configuration::hostdependency const& obj); void modify_object(configuration::hostdependency const& obj); void remove_object(configuration::hostdependency const& obj); void expand_objects(configuration::state& s); void resolve_object(configuration::hostdependency const& obj, error_cnt& err); +#else + void add_object(const configuration::Hostdependency& obj); + void modify_object(configuration::Hostdependency* to_modify, + const configuration::Hostdependency& new_obj); + void remove_object(ssize_t idx); + void expand_objects(configuration::State& s); + void resolve_object(const configuration::Hostdependency& obj, error_cnt& err); +#endif }; } // namespace applier } // namespace configuration diff --git a/engine/inc/com/centreon/engine/configuration/applier/hostescalation.hh b/engine/inc/com/centreon/engine/configuration/applier/hostescalation.hh index 36bc1f51f0f..e402c6140a8 100644 --- a/engine/inc/com/centreon/engine/configuration/applier/hostescalation.hh +++ b/engine/inc/com/centreon/engine/configuration/applier/hostescalation.hh @@ -19,6 +19,10 @@ #define CCE_CONFIGURATION_APPLIER_HOSTESCALATION_HH #include "com/centreon/engine/configuration/applier/state.hh" +#ifndef LEGACY_CONF +#include "common/engine_conf/hostescalation_helper.hh" +#endif + namespace com::centreon::engine { namespace configuration { @@ -40,11 +44,20 @@ class hostescalation { ~hostescalation() noexcept = default; hostescalation(hostescalation const&) = delete; hostescalation& operator=(hostescalation const&) = delete; +#ifdef LEGACY_CONF void add_object(const configuration::hostescalation& obj); void modify_object(configuration::hostescalation const& obj); void remove_object(configuration::hostescalation const& obj); void expand_objects(configuration::state& s); void resolve_object(configuration::hostescalation const& obj, error_cnt& err); +#else + void add_object(const configuration::Hostescalation& obj); + void modify_object(configuration::Hostescalation* old_obj, + const configuration::Hostescalation& new_obj); + void remove_object(ssize_t idx); + void expand_objects(configuration::State& s); + void resolve_object(const configuration::Hostescalation& obj, error_cnt& err); +#endif }; } // namespace applier } // namespace configuration diff --git a/engine/inc/com/centreon/engine/configuration/applier/hostgroup.hh b/engine/inc/com/centreon/engine/configuration/applier/hostgroup.hh index 75815f1bff6..b5a9307ad54 100644 --- a/engine/inc/com/centreon/engine/configuration/applier/hostgroup.hh +++ b/engine/inc/com/centreon/engine/configuration/applier/hostgroup.hh @@ -20,28 +20,41 @@ #define CCE_CONFIGURATION_APPLIER_HOSTGROUP_HH #include "com/centreon/engine/configuration/applier/state.hh" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/hostgroup.hh" +#else +#include "common/engine_conf/hostgroup_helper.hh" +#endif -namespace com::centreon::engine { +namespace com::centreon::engine::configuration { -namespace configuration { // Forward declarations. class state; namespace applier { class hostgroup { public: - hostgroup(); - hostgroup(hostgroup const& right); - ~hostgroup() throw(); + hostgroup() = default; + hostgroup(hostgroup const&) = delete; + ~hostgroup() noexcept = default; hostgroup& operator=(hostgroup const& right) = delete; +#ifdef LEGACY_CONF void add_object(configuration::hostgroup const& obj); void expand_objects(configuration::state& s); void modify_object(configuration::hostgroup const& obj); void remove_object(configuration::hostgroup const& obj); void resolve_object(configuration::hostgroup const& obj, error_cnt& err); +#else + void add_object(const configuration::Hostgroup& obj); + void expand_objects(configuration::State& s); + void modify_object(configuration::Hostgroup* old_obj, + const configuration::Hostgroup& new_obj); + void remove_object(ssize_t idx); + void resolve_object(const configuration::Hostgroup& obj, error_cnt& err); +#endif private: +#ifdef LEGACY_CONF typedef std::map<configuration::hostgroup::key_type, configuration::hostgroup> resolved_set; @@ -49,10 +62,9 @@ class hostgroup { configuration::hostgroup const& obj); resolved_set _resolved; +#endif }; } // namespace applier -} // namespace configuration - -} // namespace com::centreon::engine +} // namespace com::centreon::engine::configuration #endif // !CCE_CONFIGURATION_APPLIER_HOSTGROUP_HH diff --git a/engine/inc/com/centreon/engine/configuration/applier/logging.hh b/engine/inc/com/centreon/engine/configuration/applier/logging.hh index ff0c90a2702..cf64a97e89b 100644 --- a/engine/inc/com/centreon/engine/configuration/applier/logging.hh +++ b/engine/inc/com/centreon/engine/configuration/applier/logging.hh @@ -22,7 +22,11 @@ #include "com/centreon/logging/file.hh" #include "com/centreon/logging/syslogger.hh" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/state.hh" +#else +#include "common/engine_conf/state_helper.hh" +#endif namespace com::centreon::engine { @@ -36,21 +40,34 @@ namespace applier { */ class logging { public: +#ifdef LEGACY_CONF void apply(configuration::state& config); +#else + void apply(configuration::State& config); +#endif static logging& instance(); void clear(); private: logging(); +#ifdef LEGACY_CONF logging(configuration::state& config); +#else + logging(configuration::State& config); +#endif logging(logging const&); ~logging() throw(); logging& operator=(logging const&); void _add_stdout(); void _add_stderr(); void _add_syslog(); +#ifdef LEGACY_CONF void _add_log_file(configuration::state const& config); void _add_debug(configuration::state const& config); +#else + void _add_log_file(configuration::State const& config); + void _add_debug(configuration::State const& config); +#endif void _del_syslog(); void _del_log_file(); void _del_debug(); diff --git a/engine/inc/com/centreon/engine/configuration/applier/macros.hh b/engine/inc/com/centreon/engine/configuration/applier/macros.hh index fb8462b034f..a47919028d9 100644 --- a/engine/inc/com/centreon/engine/configuration/applier/macros.hh +++ b/engine/inc/com/centreon/engine/configuration/applier/macros.hh @@ -19,7 +19,11 @@ #ifndef CCE_CONFIGURATION_APPLIER_MACROS_HH #define CCE_CONFIGURATION_APPLIER_MACROS_HH +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/state.hh" +#else +#include "common/engine_conf/state_helper.hh" +#endif // Forward declaration. class nagios_macros; @@ -36,7 +40,11 @@ namespace applier { */ class macros { public: +#ifdef LEGACY_CONF void apply(configuration::state& config); +#else + void apply(configuration::State& config); +#endif static macros& instance(); void clear(); diff --git a/engine/inc/com/centreon/engine/configuration/applier/pb_difference.hh b/engine/inc/com/centreon/engine/configuration/applier/pb_difference.hh new file mode 100644 index 00000000000..858ad3d66a1 --- /dev/null +++ b/engine/inc/com/centreon/engine/configuration/applier/pb_difference.hh @@ -0,0 +1,147 @@ +/** + * Copyright 2023 Centreon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + */ + +#ifndef CCE_CONFIGURATION_APPLIER_PB_DIFFERENCE_HH +#define CCE_CONFIGURATION_APPLIER_PB_DIFFERENCE_HH + +#include <absl/container/flat_hash_map.h> +#include <absl/container/flat_hash_set.h> +#include <google/protobuf/util/message_differencer.h> + +#include <iterator> + +namespace com::centreon::engine { + +using MessageDifferencer = ::google::protobuf::util::MessageDifferencer; + +namespace configuration { +namespace applier { +template <typename T, + typename Key, + typename Container = ::google::protobuf::RepeatedPtrField<const T>> +class pb_difference { + // What are the new objects + std::vector<T> _added; + // What index to delete + std::vector<std::pair<ssize_t, Key>> _deleted; + // A vector of pairs, the pointer to the old one and the new one. + std::vector<std::pair<T*, T>> _modified; + + public: + /** + * @brief Default constructor. + */ + pb_difference() = default; + + /** + * @brief Destructor. + */ + ~pb_difference() noexcept = default; + pb_difference(const pb_difference&) = delete; + pb_difference& operator=(const pb_difference&) = delete; + const std::vector<T>& added() const noexcept { return _added; } + const std::vector<std::pair<ssize_t, Key>>& deleted() const noexcept { + return _deleted; + } + const std::vector<std::pair<T*, T>>& modified() const noexcept { + return _modified; + } + + template <typename Function> + void parse(typename Container::iterator old_first, + typename Container::iterator old_last, + typename Container::iterator new_first, + typename Container::iterator new_last, + Function f) { + absl::flat_hash_map<Key, T*> keys_values; + for (auto it = old_first; it != old_last; ++it) { + const T& item = *it; + static_assert(std::is_same<decltype(f(item)), const Key&>::value || + std::is_same<decltype(f(item)), const Key>::value || + std::is_same<decltype(f(item)), Key>::value, + "Invalid key function: it must match Key"); + keys_values[f(item)] = const_cast<T*>(&(item)); + } + + absl::flat_hash_set<Key> new_keys; + for (auto it = new_first; it != new_last; ++it) { + const T& item = *it; + new_keys.insert(f(item)); + if (!keys_values.contains(f(item))) { + // New object to add + _added.push_back(item); + } else { + // Object to modify or equal + if (!MessageDifferencer::Equals(item, *keys_values[f(item)])) { + // There are changes in this object + _modified.push_back(std::make_pair(keys_values[f(item)], *it)); + } + } + } + + ssize_t i = 0; + for (auto it = old_first; it != old_last; ++it) { + const T& item = *it; + if (!new_keys.contains(f(item))) + _deleted.push_back({i, f(item)}); + ++i; + } + } + + void parse(typename Container::iterator old_first, + typename Container::iterator old_last, + typename Container::iterator new_first, + typename Container::iterator new_last, + Key (T::*key)() const) { + std::function<Key(const T&)> f = key; + parse<std::function<Key(const T&)>>(old_first, old_last, new_first, + new_last, f); + } + + void parse(typename Container::iterator old_first, + typename Container::iterator old_last, + typename Container::iterator new_first, + typename Container::iterator new_last, + const Key& (T::*key)() const) { + std::function<const Key&(const T&)> f = key; + parse<std::function<const Key&(const T&)>>(old_first, old_last, new_first, + new_last, f); + } + + // template <typename TF1, typename TF2> + // void parse(typename Container::iterator old_first, + // typename Container::iterator old_last, + // typename Container::iterator new_first, + // typename Container::iterator new_last, + // TF1 (T::*key1)() const, + // TF2 (T::*key2)() const) { + // std::function<const std::pair<TF1, TF2>(const T&)> f = [&key1, + // &key2](const T& + // t) { + // return std::make_pair((t.*key1)(), (t.*key2)()); + // }; + // parse<std::function<const std::pair<TF1, TF2>(const T&)>>( + // old_first, old_last, new_first, new_last, f); + // } +}; +} // namespace applier +} // namespace configuration + +} // namespace com::centreon::engine + +#endif // !CCE_CONFIGURATION_APPLIER_PB_DIFFERENCE_HH diff --git a/engine/inc/com/centreon/engine/configuration/applier/scheduler.hh b/engine/inc/com/centreon/engine/configuration/applier/scheduler.hh index 87f4ad37c19..c7d61ab0d59 100644 --- a/engine/inc/com/centreon/engine/configuration/applier/scheduler.hh +++ b/engine/inc/com/centreon/engine/configuration/applier/scheduler.hh @@ -20,7 +20,12 @@ #include "com/centreon/engine/configuration/applier/difference.hh" #include "com/centreon/engine/exceptions/error.hh" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/state.hh" +#else +#include "com/centreon/engine/configuration/applier/pb_difference.hh" +#include "common/engine_conf/state.pb.h" +#endif // Forward declaration. namespace com::centreon::engine { @@ -28,8 +33,7 @@ class host; class service; class timed_event; -namespace configuration { -namespace applier { +namespace configuration::applier { /** * @class scheduler scheduler.hh * @brief Simple configuration applier for scheduler class. @@ -38,10 +42,20 @@ namespace applier { */ class scheduler { public: +#ifdef LEGACY_CONF void apply(configuration::state& config, difference<set_host> const& diff_hosts, difference<set_service> const& diff_services, difference<set_anomalydetection> const& diff_anomalydetections); +#else + void apply(configuration::State& config, + const pb_difference<configuration::Host, uint64_t>& diff_hosts, + const pb_difference<configuration::Service, + std::pair<uint64_t, uint64_t> >& diff_services, + const pb_difference<configuration::Anomalydetection, + std::pair<uint64_t, uint64_t> >& + diff_anomalydetections); +#endif static scheduler& instance(); void clear(); void remove_host(uint64_t host_id); @@ -52,6 +66,7 @@ class scheduler { scheduler(scheduler const&) = delete; ~scheduler() noexcept; scheduler& operator=(scheduler const&) = delete; +#ifdef LEGACY_CONF void _apply_misc_event(); void _calculate_host_inter_check_delay( configuration::state::inter_check_delay method); @@ -60,11 +75,22 @@ class scheduler { configuration::state::inter_check_delay method); void _calculate_service_interleave_factor( configuration::state::interleave_factor method); +#else + void _apply_misc_event(); + void _calculate_host_inter_check_delay( + const configuration::InterCheckDelay& method); + void _calculate_host_scheduling_params(); + void _calculate_service_inter_check_delay( + const configuration::InterCheckDelay& method); + void _calculate_service_interleave_factor( + const configuration::InterleaveFactor& method); +#endif void _calculate_service_scheduling_params(); timed_event* _create_misc_event(int type, time_t start, unsigned long interval, void* data = nullptr); +#ifdef LEGACY_CONF std::vector<com::centreon::engine::host*> _get_hosts( set_host const& hst_added, bool throw_if_not_found = true); @@ -74,6 +100,17 @@ class scheduler { std::vector<com::centreon::engine::service*> _get_services( set_service const& svc_cfg, bool throw_if_not_found = true); +#else + std::vector<com::centreon::engine::host*> _get_hosts( + const std::vector<uint64_t>& hst_ids, + bool throw_if_not_found); + std::vector<com::centreon::engine::service*> _get_anomalydetections( + const std::vector<std::pair<uint64_t, uint64_t> >& ad_ids, + bool throw_if_not_found); + std::vector<com::centreon::engine::service*> _get_services( + const std::vector<std::pair<uint64_t, uint64_t> >& ad_ids, + bool throw_if_not_found); +#endif void _remove_misc_event(timed_event*& evt); void _schedule_host_events( @@ -84,7 +121,11 @@ class scheduler { void _unschedule_service_events( std::vector<engine::service*> const& services); +#ifdef LEGACY_CONF configuration::state* _config; +#else + configuration::State* _pb_config; +#endif timed_event* _evt_check_reaper; timed_event* _evt_command_check; timed_event* _evt_hfreshness_check; @@ -97,16 +138,11 @@ class scheduler { unsigned int _old_check_reaper_interval; int _old_command_check_interval; unsigned int _old_host_freshness_check_interval; - std::string _old_host_perfdata_file_processing_command; - unsigned int _old_host_perfdata_file_processing_interval; unsigned int _old_retention_update_interval; unsigned int _old_service_freshness_check_interval; - std::string _old_service_perfdata_file_processing_command; - unsigned int _old_service_perfdata_file_processing_interval; unsigned int _old_status_update_interval; }; -} // namespace applier -} // namespace configuration +} // namespace configuration::applier } // namespace com::centreon::engine diff --git a/engine/inc/com/centreon/engine/configuration/applier/service.hh b/engine/inc/com/centreon/engine/configuration/applier/service.hh index 6461ef6055c..dc204016458 100644 --- a/engine/inc/com/centreon/engine/configuration/applier/service.hh +++ b/engine/inc/com/centreon/engine/configuration/applier/service.hh @@ -20,35 +20,54 @@ #define CCE_CONFIGURATION_APPLIER_SERVICE_HH #include "com/centreon/engine/configuration/applier/state.hh" -namespace com::centreon::engine { +#ifndef LEGACY_CONF +#include "common/engine_conf/service_helper.hh" +#endif -namespace configuration { +namespace com::centreon::engine::configuration { + +#ifdef LEGACY_CONF // Forward declarations. class service; class state; +#endif namespace applier { class service { public: - service(); - service(service const& right); - ~service(); - service& operator=(service const& right); + service() = default; + service(service const&) = delete; + ~service() noexcept = default; + service& operator=(service const&) = delete; +#ifdef LEGACY_CONF void add_object(configuration::service const& obj); void expand_objects(configuration::state& s); void modify_object(configuration::service const& obj); void remove_object(configuration::service const& obj); void resolve_object(configuration::service const& obj, error_cnt& err); +#else + void add_object(const configuration::Service& obj); + void expand_objects(configuration::State& s); + void modify_object(configuration::Service* old_obj, + const configuration::Service& new_obj); + void remove_object(ssize_t idx); + void resolve_object(const configuration::Service& obj, error_cnt& err); +#endif private: +#ifdef LEGACY_CONF void _expand_service_memberships(configuration::service& obj, configuration::state& s); void _inherits_special_vars(configuration::service& obj, configuration::state const& s); +#else + void _expand_service_memberships(configuration::Service& obj, + configuration::State& s); + void _inherits_special_vars(configuration::Service& obj, + const configuration::State& s); +#endif }; } // namespace applier -} // namespace configuration - -} // namespace com::centreon::engine +} // namespace com::centreon::engine::configuration #endif // !CCE_CONFIGURATION_APPLIER_SERVICE_HH diff --git a/engine/inc/com/centreon/engine/configuration/applier/servicedependency.hh b/engine/inc/com/centreon/engine/configuration/applier/servicedependency.hh index 06b83ac0448..5f7ec05d839 100644 --- a/engine/inc/com/centreon/engine/configuration/applier/servicedependency.hh +++ b/engine/inc/com/centreon/engine/configuration/applier/servicedependency.hh @@ -21,17 +21,23 @@ #include "com/centreon/engine/configuration/applier/state.hh" -namespace com::centreon::engine { +#ifndef LEGACY_CONF +#include "common/engine_conf/servicedependency_helper.hh" +#endif -namespace configuration { +namespace com::centreon::engine::configuration { + +#ifdef LEGACY_CONF // Forward declarations. class servicedependency; class state; +#endif size_t servicedependency_key(const servicedependency& sd); namespace applier { class servicedependency { +#ifdef LEGACY_CONF void _expand_services( std::list<std::string> const& hst, std::list<std::string> const& hg, @@ -39,22 +45,39 @@ class servicedependency { std::list<std::string> const& sg, configuration::state& s, std::set<std::pair<std::string, std::string>>& expanded); +#else + void _expand_services( + const ::google::protobuf::RepeatedPtrField<std::string>& hst, + const ::google::protobuf::RepeatedPtrField<std::string>& hg, + const ::google::protobuf::RepeatedPtrField<std::string>& svc, + const ::google::protobuf::RepeatedPtrField<std::string>& sg, + configuration::State& s, + absl::flat_hash_set<std::pair<std::string, std::string>>& expanded); +#endif public: servicedependency() = default; ~servicedependency() noexcept = default; servicedependency(const servicedependency&) = delete; servicedependency& operator=(const servicedependency&) = delete; +#ifdef LEGACY_CONF void add_object(configuration::servicedependency const& obj); void modify_object(configuration::servicedependency const& obj); void expand_objects(configuration::state& s); void remove_object(configuration::servicedependency const& obj); void resolve_object(configuration::servicedependency const& obj, error_cnt& err); +#else + void add_object(const configuration::Servicedependency& obj); + void modify_object(configuration::Servicedependency* old_obj, + const configuration::Servicedependency& new_obj); + void expand_objects(configuration::State& s); + void remove_object(ssize_t idx); + void resolve_object(const configuration::Servicedependency& obj, + error_cnt& err); +#endif }; } // namespace applier -} // namespace configuration - -} // namespace com::centreon::engine +} // namespace com::centreon::engine::configuration #endif // !CCE_CONFIGURATION_APPLIER_SERVICEDEPENDENCY_HH diff --git a/engine/inc/com/centreon/engine/configuration/applier/serviceescalation.hh b/engine/inc/com/centreon/engine/configuration/applier/serviceescalation.hh index dfa3ec3d0be..07f1b40289a 100644 --- a/engine/inc/com/centreon/engine/configuration/applier/serviceescalation.hh +++ b/engine/inc/com/centreon/engine/configuration/applier/serviceescalation.hh @@ -20,15 +20,21 @@ #define CCE_CONFIGURATION_APPLIER_SERVICEESCALATION_HH #include "com/centreon/engine/configuration/applier/state.hh" -namespace com::centreon::engine { +#ifndef LEGACY_CONF +#include "common/engine_conf/serviceescalation_helper.hh" +#endif + +namespace com::centreon::engine::configuration { -namespace configuration { // Forward declarations. +#ifdef LEGACY_CONF class serviceescalation; class state; +#endif namespace applier { class serviceescalation { +#ifdef LEGACY_CONF void _expand_services( std::list<std::string> const& hst, std::list<std::string> const& hg, @@ -38,22 +44,31 @@ class serviceescalation { std::set<std::pair<std::string, std::string> >& expanded); void _inherits_special_vars(configuration::serviceescalation& obj, configuration::state const& s); +#endif public: serviceescalation() = default; serviceescalation(const serviceescalation&) = delete; ~serviceescalation() noexcept = default; serviceescalation& operator=(const serviceescalation&) = delete; +#ifdef LEGACY_CONF void add_object(const configuration::serviceescalation& obj); void modify_object(const configuration::serviceescalation& obj); void remove_object(const configuration::serviceescalation& obj); void expand_objects(configuration::state& s); void resolve_object(const configuration::serviceescalation& obj, error_cnt& err); +#else + void add_object(const configuration::Serviceescalation& obj); + void modify_object(configuration::Serviceescalation* old_obj, + const configuration::Serviceescalation& new_obj); + void remove_object(ssize_t idx); + void expand_objects(configuration::State& s); + void resolve_object(const configuration::Serviceescalation& obj, + error_cnt& err); +#endif }; } // namespace applier -} // namespace configuration - -} // namespace com::centreon::engine +} // namespace com::centreon::engine::configuration #endif // !CCE_CONFIGURATION_APPLIER_SERVICEESCALATION_HH diff --git a/engine/inc/com/centreon/engine/configuration/applier/servicegroup.hh b/engine/inc/com/centreon/engine/configuration/applier/servicegroup.hh index 7e5cb60bd96..a81540af111 100644 --- a/engine/inc/com/centreon/engine/configuration/applier/servicegroup.hh +++ b/engine/inc/com/centreon/engine/configuration/applier/servicegroup.hh @@ -20,13 +20,18 @@ #define CCE_CONFIGURATION_APPLIER_SERVICEGROUP_HH #include "com/centreon/engine/configuration/applier/state.hh" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/servicegroup.hh" +#else +#include "common/engine_conf/servicegroup_helper.hh" +#endif -namespace com::centreon::engine { +namespace com::centreon::engine::configuration { -namespace configuration { +#ifdef LEGACY_CONF // Forward declarations. class state; +#endif namespace applier { class servicegroup { @@ -35,13 +40,24 @@ class servicegroup { servicegroup(servicegroup const& right); ~servicegroup() throw(); servicegroup& operator=(servicegroup const& right); +#ifdef LEGACY_CONF void add_object(configuration::servicegroup const& obj); void expand_objects(configuration::state& s); void modify_object(configuration::servicegroup const& obj); void remove_object(configuration::servicegroup const& obj); void resolve_object(configuration::servicegroup const& obj, error_cnt& err); +#else + void add_object(const configuration::Servicegroup& obj); + void expand_objects(configuration::State& s); + void modify_object(configuration::Servicegroup* to_modify, + const configuration::Servicegroup& new_object); + void remove_object(ssize_t idx); + void resolve_object(const configuration::Servicegroup& obj, + error_cnt& err); +#endif private: +#ifdef LEGACY_CONF typedef std::map<configuration::servicegroup::key_type, configuration::servicegroup> resolved_set; @@ -50,10 +66,16 @@ class servicegroup { configuration::state const& s); resolved_set _resolved; +#else + void _resolve_members( + configuration::State& s, + configuration::Servicegroup* sg_conf, + absl::flat_hash_set<std::string_view>& resolved, + const absl::flat_hash_map<std::string_view, configuration::Servicegroup*>& + sg_by_name); +#endif }; } // namespace applier -} // namespace configuration - -} // namespace com::centreon::engine +} // namespace com::centreon::engine::configuration #endif // !CCE_CONFIGURATION_APPLIER_SERVICEGROUP_HH diff --git a/engine/inc/com/centreon/engine/configuration/applier/severity.hh b/engine/inc/com/centreon/engine/configuration/applier/severity.hh index cd741a8014c..1371e7bea69 100644 --- a/engine/inc/com/centreon/engine/configuration/applier/severity.hh +++ b/engine/inc/com/centreon/engine/configuration/applier/severity.hh @@ -1,5 +1,5 @@ /* - * Copyright 2022 Centreon (https://www.centreon.com/) + * Copyright 2022-2024 Centreon (https://www.centreon.com/) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,12 +19,16 @@ #ifndef CCE_CONFIGURATION_APPLIER_SEVERITY_HH #define CCE_CONFIGURATION_APPLIER_SEVERITY_HH +#ifndef LEGACY_CONF +#include "common/engine_conf/severity_helper.hh" +#endif -namespace com::centreon::engine { +namespace com::centreon::engine::configuration { -namespace configuration { +#ifdef LEGACY_CONF class severity; class state; +#endif namespace applier { class severity { @@ -32,15 +36,22 @@ class severity { severity() = default; ~severity() noexcept = default; severity& operator=(const severity& other) = delete; +#ifdef LEGACY_CONF void add_object(const configuration::severity& obj); void expand_objects(configuration::state& s); void modify_object(const configuration::severity& obj); void remove_object(const configuration::severity& obj); void resolve_object(const configuration::severity& obj); +#else + void add_object(const configuration::Severity& obj); + void modify_object(configuration::Severity* to_modify, + const configuration::Severity& new_object); + void remove_object(ssize_t idx); + void resolve_object(const configuration::Severity& obj, + error_cnt& err); +#endif }; } // namespace applier -} // namespace configuration - -} +} // namespace com::centreon::engine::configuration #endif // !CCE_CONFIGURATION_APPLIER_SEVERITY_HH diff --git a/engine/inc/com/centreon/engine/configuration/applier/state.hh b/engine/inc/com/centreon/engine/configuration/applier/state.hh index 62c4296d497..e9ce73491ba 100644 --- a/engine/inc/com/centreon/engine/configuration/applier/state.hh +++ b/engine/inc/com/centreon/engine/configuration/applier/state.hh @@ -20,7 +20,11 @@ #include "com/centreon/engine/configuration/applier/difference.hh" #include "com/centreon/engine/servicedependency.hh" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/state.hh" +#else +#include "com/centreon/engine/configuration/applier/pb_difference.hh" +#endif namespace com::centreon::engine { @@ -46,17 +50,26 @@ namespace applier { */ class state { public: +#ifdef LEGACY_CONF void apply(configuration::state& new_cfg, error_cnt& err, retention::state* state = nullptr); void apply_log_config(configuration::state& new_cfg); +#else + void apply(configuration::State& new_cfg, + error_cnt& err, + retention::state* state = nullptr); + void apply_log_config(configuration::State& new_cfg); +#endif static state& instance(); void clear(); servicedependency_mmap const& servicedependencies() const throw(); servicedependency_mmap& servicedependencies() throw(); +#ifdef LEGACY_CONF servicedependency_mmap::iterator servicedependencies_find( configuration::servicedependency::key_type const& k); +#endif std::unordered_map<std::string, std::string>& user_macros(); std::unordered_map<std::string, std::string>::const_iterator user_macros_find( std::string const& key) const; @@ -85,6 +98,7 @@ class state { #endif state& operator=(state const&); +#ifdef LEGACY_CONF void _apply(configuration::state const& new_cfg, error_cnt& err); template <typename ConfigurationType, typename ApplierType> void _apply(difference<std::set<ConfigurationType>> const& diff, @@ -92,6 +106,16 @@ class state { void _apply(configuration::state& new_cfg, retention::state& state, error_cnt& err); +#else + void _apply(const configuration::State& new_cfg, error_cnt& err); + template <typename ConfigurationType, typename Key, typename ApplierType> + void _apply(const pb_difference<ConfigurationType, Key>& diff, + error_cnt& err); + void _apply(configuration::State& new_cfg, + retention::state& state, + error_cnt& err); +#endif +#ifdef LEGACY_CONF template <typename ConfigurationType, typename ApplierType> void _expand(configuration::state& new_state, error_cnt& err); void _processing(configuration::state& new_cfg, @@ -99,9 +123,22 @@ class state { retention::state* state = nullptr); template <typename ConfigurationType, typename ApplierType> void _resolve(std::set<ConfigurationType>& cfg, error_cnt& err); +#else + template <typename ConfigurationType, typename ApplierType> + void _expand(configuration::State& new_state, error_cnt& err); + void _processing(configuration::State& new_cfg, + error_cnt& err, + retention::state* state = nullptr); + template <typename ConfigurationType, typename ApplierType> + void _resolve( + const ::google::protobuf::RepeatedPtrField<ConfigurationType>& cfg, + error_cnt& err); +#endif std::mutex _apply_lock; +#ifdef LEGACY_CONF state* _config; +#endif processing_state _processing_state; servicedependency_mmap _servicedependencies; diff --git a/engine/inc/com/centreon/engine/configuration/applier/tag.hh b/engine/inc/com/centreon/engine/configuration/applier/tag.hh index 036b43e4edb..afad6cbd311 100644 --- a/engine/inc/com/centreon/engine/configuration/applier/tag.hh +++ b/engine/inc/com/centreon/engine/configuration/applier/tag.hh @@ -1,4 +1,4 @@ -/* +/** * Copyright 2022 Centreon (https://www.centreon.com/) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,12 +19,18 @@ #ifndef CCE_CONFIGURATION_APPLIER_TAG_HH #define CCE_CONFIGURATION_APPLIER_TAG_HH +#ifdef LEGACY_CONF +#include "common/engine_legacy_conf/object.hh" +#else +#include "common/engine_conf/tag_helper.hh" +#endif -namespace com::centreon::engine { +namespace com::centreon::engine::configuration { -namespace configuration { +#ifdef LEGACY_CONF class tag; class state; +#endif namespace applier { class tag { @@ -32,15 +38,22 @@ class tag { tag() = default; ~tag() noexcept = default; tag& operator=(const tag& other) = delete; +#ifdef LEGACY_CONF void add_object(const configuration::tag& obj); void expand_objects(configuration::state& s); void modify_object(const configuration::tag& obj); void remove_object(const configuration::tag& obj); - void resolve_object(const configuration::tag& obj); + void resolve_object(const configuration::tag& obj, error_cnt& err); +#else + void add_object(const configuration::Tag& obj); + void modify_object(configuration::Tag* to_modify, + const configuration::Tag& new_object); + void remove_object(ssize_t idx); + void resolve_object(const configuration::Tag& obj, + error_cnt& err); +#endif }; } // namespace applier -} // namespace configuration - -} +} // namespace com::centreon::engine::configuration #endif // !CCE_CONFIGURATION_APPLIER_TAG_HH diff --git a/engine/inc/com/centreon/engine/configuration/applier/timeperiod.hh b/engine/inc/com/centreon/engine/configuration/applier/timeperiod.hh index 3a47437b3e0..a5bb0401bc2 100644 --- a/engine/inc/com/centreon/engine/configuration/applier/timeperiod.hh +++ b/engine/inc/com/centreon/engine/configuration/applier/timeperiod.hh @@ -21,13 +21,18 @@ #include "com/centreon/engine/configuration/applier/state.hh" #include "com/centreon/engine/timeperiod.hh" +#ifndef LEGACY_CONF +#include "common/engine_conf/timeperiod_helper.hh" +#endif + // Forward declaration. -namespace com::centreon::engine { +namespace com::centreon::engine::configuration { -namespace configuration { +#ifdef LEGACY_CONF // Forward declarations. class state; class timeperiod; +#endif namespace applier { class timeperiod { @@ -46,15 +51,23 @@ class timeperiod { ~timeperiod() noexcept = default; timeperiod(const timeperiod&) = delete; timeperiod& operator=(const timeperiod&) = delete; +#ifdef LEGACY_CONF void add_object(const configuration::timeperiod& obj); void expand_objects(configuration::state& s); void modify_object(configuration::timeperiod const& obj); void remove_object(configuration::timeperiod const& obj); void resolve_object(configuration::timeperiod const& obj, error_cnt& err); +#else + void add_object(const configuration::Timeperiod& obj); + void expand_objects(configuration::State& s); + void modify_object(configuration::Timeperiod* to_modify, + const configuration::Timeperiod& new_object); + void remove_object(ssize_t idx); + void resolve_object(const configuration::Timeperiod& obj, + error_cnt& err); +#endif }; } // namespace applier -} // namespace configuration - -} // namespace com::centreon::engine +} // namespace com::centreon::engine::configuration #endif // !CCE_CONFIGURATION_APPLIER_TIMEPERIOD_HH diff --git a/engine/inc/com/centreon/engine/configuration/extended_conf.hh b/engine/inc/com/centreon/engine/configuration/extended_conf.hh index 28df0aa9958..161d3759b9c 100644 --- a/engine/inc/com/centreon/engine/configuration/extended_conf.hh +++ b/engine/inc/com/centreon/engine/configuration/extended_conf.hh @@ -20,6 +20,9 @@ #define CCE_CONFIGURATION_EXTENDED_STATE_HH #include "com/centreon/common/rapidjson_helper.hh" +#ifndef LEGACY_CONF +#include "common/engine_conf/state_helper.hh" +#endif namespace com::centreon::engine::configuration { @@ -45,7 +48,11 @@ class extended_conf { extended_conf& operator=(const extended_conf&) = delete; void reload(); +#ifdef LEGACY_CONF static void update_state(state& dest); +#else + static void update_state(State* pb_config); +#endif template <class file_path_iterator> static void load_all(file_path_iterator begin, file_path_iterator); diff --git a/engine/inc/com/centreon/engine/contact.hh b/engine/inc/com/centreon/engine/contact.hh index b9f3b26d9c7..198e2881e28 100644 --- a/engine/inc/com/centreon/engine/contact.hh +++ b/engine/inc/com/centreon/engine/contact.hh @@ -61,7 +61,8 @@ class contact { // Base properties. std::string const& get_address(int index) const; std::vector<std::string> const& get_addresses() const; - void set_addresses(std::vector<std::string> const& addresses); + void set_addresses(const std::vector<std::string>& addresses); + void set_addresses(std::vector<std::string>&& addresses); std::string const& get_alias() const; void set_alias(std::string const& alias); bool get_can_submit_commands() const; @@ -184,7 +185,7 @@ std::shared_ptr<com::centreon::engine::contact> add_contact( std::string const& alias, std::string const& email, std::string const& pager, - std::array<std::string, MAX_CONTACT_ADDRESSES> const& addresses, + const std::vector<std::string>& addresses, std::string const& svc_notification_period, std::string const& host_notification_period, int notify_service_ok, diff --git a/engine/inc/com/centreon/engine/contactgroup.hh b/engine/inc/com/centreon/engine/contactgroup.hh index 66345a29e60..d56ba49ef55 100644 --- a/engine/inc/com/centreon/engine/contactgroup.hh +++ b/engine/inc/com/centreon/engine/contactgroup.hh @@ -24,6 +24,10 @@ #include <memory> #include <string> +#ifndef LEGACY_CONF +#include "common/engine_conf/contactgroup_helper.hh" +#endif + /* Forward declaration. */ namespace com::centreon::engine { class contact; @@ -46,8 +50,12 @@ namespace com::centreon::engine { class contactgroup { public: - contactgroup(); + contactgroup() = default; +#ifdef LEGACY_CONF contactgroup(configuration::contactgroup const& obj); +#else + contactgroup(const configuration::Contactgroup& obj); +#endif virtual ~contactgroup(); std::string const& get_name() const; std::string const& get_alias() const; diff --git a/engine/inc/com/centreon/engine/daterange.hh b/engine/inc/com/centreon/engine/daterange.hh index 6bf2967c507..a46e027b6de 100644 --- a/engine/inc/com/centreon/engine/daterange.hh +++ b/engine/inc/com/centreon/engine/daterange.hh @@ -22,6 +22,10 @@ #include "com/centreon/engine/common.hh" #include "com/centreon/engine/timerange.hh" +#ifndef LEGACY_CONF +#include "common/engine_conf/state.pb.h" +#endif + struct timeperiod_struct; namespace com::centreon::engine { @@ -45,6 +49,7 @@ class daterange { week_day = 4 }; +#ifdef LEGACY_CONF daterange(type_range type, int syear, int smon, @@ -58,6 +63,23 @@ class daterange { int ewday_offset, int skip_interval, const std::list<configuration::timerange>& timeranges); +#else + daterange(type_range type, + int syear, + int smon, + int smday, + int swday, + int swday_offset, + int eyear, + int emon, + int emday, + int ewday, + int ewday_offset, + int skip_interval, + const google::protobuf::RepeatedPtrField<configuration::Timerange>& + timeranges); +#endif + daterange(type_range type); type_range get_type() const { return _type; } diff --git a/engine/inc/com/centreon/engine/globals.hh b/engine/inc/com/centreon/engine/globals.hh index bbe3ea115b5..274928a6a52 100644 --- a/engine/inc/com/centreon/engine/globals.hh +++ b/engine/inc/com/centreon/engine/globals.hh @@ -29,7 +29,11 @@ #include "com/centreon/engine/nebmods.hh" #include "com/centreon/engine/restart_stats.hh" #include "com/centreon/engine/utils.hh" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/state.hh" +#else +#include "common/engine_conf/state.pb.h" +#endif #include "common/log_v2/log_v2.hh" /* Start/Restart statistics */ @@ -47,9 +51,14 @@ extern std::shared_ptr<spdlog::logger> macros_logger; extern std::shared_ptr<spdlog::logger> notifications_logger; extern std::shared_ptr<spdlog::logger> process_logger; extern std::shared_ptr<spdlog::logger> runtime_logger; +extern std::shared_ptr<spdlog::logger> otel_logger; +#ifdef LEGACY_CONF extern com::centreon::engine::configuration::state* config; -extern char* config_file; +#else +extern com::centreon::engine::configuration::State pb_config; +#endif +extern std::string config_file; extern com::centreon::engine::commands::command* global_host_event_handler_ptr; extern com::centreon::engine::commands::command* diff --git a/engine/inc/com/centreon/engine/hostdependency.hh b/engine/inc/com/centreon/engine/hostdependency.hh index 6bef7d90e05..d770da3de63 100644 --- a/engine/inc/com/centreon/engine/hostdependency.hh +++ b/engine/inc/com/centreon/engine/hostdependency.hh @@ -20,7 +20,9 @@ #define CCE_OBJECTS_HOSTDEPENDENCY_HH #include "com/centreon/engine/dependency.hh" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/hostdependency.hh" +#endif /* Forward declaration. */ namespace com::centreon::engine { @@ -29,7 +31,7 @@ class hostdependency; class timeperiod; } // namespace com::centreon::engine -typedef std::unordered_multimap< +typedef absl::btree_multimap< std::string, std::shared_ptr<com::centreon::engine::hostdependency>> hostdependency_mmap; @@ -64,8 +66,13 @@ class hostdependency : public dependency { bool operator<(hostdependency const& obj) throw(); static hostdependency_mmap hostdependencies; +#ifdef LEGACY_CONF static hostdependency_mmap::iterator hostdependencies_find( - configuration::hostdependency const& k); + const configuration::hostdependency& k); +#else + static hostdependency_mmap::iterator hostdependencies_find( + const std::pair<std::string_view, size_t>& key); +#endif host* master_host_ptr; host* dependent_host_ptr; diff --git a/engine/inc/com/centreon/engine/hostescalation.hh b/engine/inc/com/centreon/engine/hostescalation.hh index f5e24720c18..8243424cb64 100644 --- a/engine/inc/com/centreon/engine/hostescalation.hh +++ b/engine/inc/com/centreon/engine/hostescalation.hh @@ -32,7 +32,11 @@ typedef std::unordered_multimap< namespace com::centreon::engine { namespace configuration { +#ifdef LEGACY_CONF class hostescalation; +#else +class Hostescalation; +#endif } class hostescalation : public escalation { @@ -50,7 +54,11 @@ class hostescalation : public escalation { bool is_viable(int state, uint32_t notification_number) const override; void resolve(uint32_t& w, uint32_t& e) override; +#ifdef LEGACY_CONF bool matches(const configuration::hostescalation& obj) const; +#else + bool matches(const configuration::Hostescalation& obj) const; +#endif static hostescalation_mmap hostescalations; diff --git a/engine/inc/com/centreon/engine/macros/defines.hh b/engine/inc/com/centreon/engine/macros/defines.hh index 0d92a4634a7..601d65c71c9 100644 --- a/engine/inc/com/centreon/engine/macros/defines.hh +++ b/engine/inc/com/centreon/engine/macros/defines.hh @@ -101,8 +101,8 @@ #define MACRO_LOGFILE 69 #define MACRO_RESOURCEFILE 70 #define MACRO_COMMANDFILE 71 -#define MACRO_HOSTPERFDATAFILE 72 -#define MACRO_SERVICEPERFDATAFILE 73 +#define MACRO_HOSTPERFDATAFILE 72 // Not used anymore +#define MACRO_SERVICEPERFDATAFILE 73 // Not used anymore #define MACRO_HOSTACTIONURL 74 #define MACRO_HOSTNOTESURL 75 #define MACRO_HOSTNOTES 76 diff --git a/engine/inc/com/centreon/engine/nebstructs.hh b/engine/inc/com/centreon/engine/nebstructs.hh index 02073edc4eb..00ae56981cb 100644 --- a/engine/inc/com/centreon/engine/nebstructs.hh +++ b/engine/inc/com/centreon/engine/nebstructs.hh @@ -95,9 +95,9 @@ typedef struct nebstruct_comment_struct { /* Custom variable structure. */ typedef struct nebstruct_custom_variable_struct { int type; - struct timeval timestamp; - char* var_name; - char* var_value; + struct timeval timestamp = {}; + std::string_view var_name; + std::string_view var_value; void* object_ptr; } nebstruct_custom_variable_data; diff --git a/engine/inc/com/centreon/engine/retention/applier/anomalydetection.hh b/engine/inc/com/centreon/engine/retention/applier/anomalydetection.hh index 2a1e7be6cc4..565c5ea8e61 100644 --- a/engine/inc/com/centreon/engine/retention/applier/anomalydetection.hh +++ b/engine/inc/com/centreon/engine/retention/applier/anomalydetection.hh @@ -28,22 +28,38 @@ class anomalydetection; // Forward declaration. namespace configuration { +#ifdef LEGACY_CONF class state; -} +#else +class State; +#endif +} // namespace configuration namespace retention { namespace applier { class anomalydetection { public: +#ifdef LEGACY_CONF static void apply(configuration::state const& config, list_anomalydetection const& lst, bool scheduling_info_is_ok); - +#else + static void apply(const configuration::State& config, + const list_anomalydetection& lst, + bool scheduling_info_is_ok); +#endif private: +#ifdef LEGACY_CONF static void _update(configuration::state const& config, retention::anomalydetection const& state, com::centreon::engine::anomalydetection& obj, bool scheduling_info_is_ok); +#else + static void _update(const configuration::State& config, + const retention::anomalydetection& state, + com::centreon::engine::anomalydetection& obj, + bool scheduling_info_is_ok); +#endif }; } // namespace applier } // namespace retention diff --git a/engine/inc/com/centreon/engine/retention/applier/contact.hh b/engine/inc/com/centreon/engine/retention/applier/contact.hh index f0cdcaad4db..e0148a36f0a 100644 --- a/engine/inc/com/centreon/engine/retention/applier/contact.hh +++ b/engine/inc/com/centreon/engine/retention/applier/contact.hh @@ -28,23 +28,37 @@ class contact; // Forward declaration. namespace configuration { +#ifdef LEGACY_CONF class state; -} +#else +class State; +#endif +} // namespace configuration namespace retention { namespace applier { class contact { public: +#ifdef LEGACY_CONF void apply(configuration::state const& config, list_contact const& lst); +#else + void apply(const configuration::State& config, list_contact const& lst); +#endif private: +#ifdef LEGACY_CONF void _update(configuration::state const& config, retention::contact const& state, com::centreon::engine::contact* obj); +#else + void _update(const configuration::State& config, + const retention::contact& state, + com::centreon::engine::contact* obj); +#endif }; } // namespace applier } // namespace retention -} +} // namespace com::centreon::engine #endif // !CCE_RETENTION_APPLIER_CONTACT_HH diff --git a/engine/inc/com/centreon/engine/retention/applier/host.hh b/engine/inc/com/centreon/engine/retention/applier/host.hh index d24b74b5919..735d24a9705 100644 --- a/engine/inc/com/centreon/engine/retention/applier/host.hh +++ b/engine/inc/com/centreon/engine/retention/applier/host.hh @@ -35,19 +35,31 @@ namespace retention { namespace applier { class host { public: +#ifdef LEGACY_CONF void apply(configuration::state const& config, list_host const& lst, bool scheduling_info_is_ok); - +#else + void apply(const configuration::State& config, + list_host const& lst, + bool scheduling_info_is_ok); +#endif private: +#ifdef LEGACY_CONF void _update(configuration::state const& config, retention::host const& state, engine::host& obj, bool scheduling_info_is_ok); +#else + void _update(const configuration::State& config, + const retention::host& state, + engine::host& obj, + bool scheduling_info_is_ok); +#endif }; } // namespace applier } // namespace retention -} +} // namespace com::centreon::engine #endif // !CCE_RETENTION_APPLIER_HOST_HH diff --git a/engine/inc/com/centreon/engine/retention/applier/program.hh b/engine/inc/com/centreon/engine/retention/applier/program.hh index f79fea775f8..0e88bcae65c 100644 --- a/engine/inc/com/centreon/engine/retention/applier/program.hh +++ b/engine/inc/com/centreon/engine/retention/applier/program.hh @@ -26,14 +26,22 @@ namespace com::centreon::engine { // Forward declaration. namespace configuration { +#ifdef LEGACY_CONF class state; +#else +class State; +#endif } namespace retention { namespace applier { class program { public: +#ifdef LEGACY_CONF void apply(configuration::state& config, retention::program const& obj); +#else + void apply(configuration::State& config, retention::program const& obj); +#endif private: bool _find_command(std::string const& command_line); diff --git a/engine/inc/com/centreon/engine/retention/applier/service.hh b/engine/inc/com/centreon/engine/retention/applier/service.hh index ee0365bf8ee..473902d7ad5 100644 --- a/engine/inc/com/centreon/engine/retention/applier/service.hh +++ b/engine/inc/com/centreon/engine/retention/applier/service.hh @@ -29,13 +29,18 @@ class service; // Forward declaration. namespace configuration { +#ifdef LEGACY_CONF class state; -} +#else +class State; +#endif +} // namespace configuration namespace retention { namespace applier { class service { public: +#ifdef LEGACY_CONF static void apply(configuration::state const& config, list_service const& lst, bool scheduling_info_is_ok); @@ -44,10 +49,20 @@ class service { retention::service const& state, com::centreon::engine::service& obj, bool scheduling_info_is_ok); +#else + static void apply(const configuration::State& config, + const list_service& lst, + bool scheduling_info_is_ok); + + static void update(const configuration::State& config, + const retention::service& state, + com::centreon::engine::service& obj, + bool scheduling_info_is_ok); +#endif }; } // namespace applier } // namespace retention -} +} // namespace com::centreon::engine #endif // !CCE_RETENTION_APPLIER_SERVICE_HH diff --git a/engine/inc/com/centreon/engine/retention/applier/state.hh b/engine/inc/com/centreon/engine/retention/applier/state.hh index 114ed26718c..62753be73ea 100644 --- a/engine/inc/com/centreon/engine/retention/applier/state.hh +++ b/engine/inc/com/centreon/engine/retention/applier/state.hh @@ -26,14 +26,22 @@ namespace com::centreon::engine { // Forward declaration. namespace configuration { +#ifdef LEGACY_CONF class state; +#else +class State; +#endif } namespace retention { namespace applier { class state { public: +#ifdef LEGACY_CONF void apply(configuration::state& config, retention::state const& state); +#else + void apply(configuration::State& config, retention::state const& state); +#endif }; } // namespace applier } // namespace retention diff --git a/engine/inc/com/centreon/engine/servicedependency.hh b/engine/inc/com/centreon/engine/servicedependency.hh index 9b25112a4c6..a9ab85af2a4 100644 --- a/engine/inc/com/centreon/engine/servicedependency.hh +++ b/engine/inc/com/centreon/engine/servicedependency.hh @@ -21,7 +21,9 @@ #define CCE_OBJECTS_SERVICEDEPENDENCY_HH #include "com/centreon/engine/dependency.hh" #include "com/centreon/engine/hash.hh" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/servicedependency.hh" +#endif /* Forward declaration. */ namespace com::centreon::engine { @@ -85,8 +87,13 @@ class servicedependency : public dependency { service* dependent_service_ptr; static servicedependency_mmap servicedependencies; +#ifdef LEGACY_CONF static servicedependency_mmap::iterator servicedependencies_find( configuration::servicedependency const& k); +#else + static servicedependency_mmap::iterator servicedependencies_find( + const std::tuple<std::string, std::string, size_t>& key); +#endif }; }; // namespace com::centreon::engine diff --git a/engine/inc/com/centreon/engine/serviceescalation.hh b/engine/inc/com/centreon/engine/serviceescalation.hh index f634f32b83c..b1eaaa7da90 100644 --- a/engine/inc/com/centreon/engine/serviceescalation.hh +++ b/engine/inc/com/centreon/engine/serviceescalation.hh @@ -35,7 +35,11 @@ typedef std::unordered_multimap< namespace com::centreon::engine { namespace configuration { +#ifdef LEGACY_CONF class serviceescalation; +#else +class Serviceescalation; +#endif } class serviceescalation : public escalation { @@ -53,7 +57,11 @@ class serviceescalation : public escalation { std::string const& get_description() const; bool is_viable(int state, uint32_t notification_number) const override; void resolve(uint32_t& w, uint32_t& e) override; +#ifdef LEGACY_CONF bool matches(const configuration::serviceescalation& obj) const; +#else + bool matches(const configuration::Serviceescalation& obj) const; +#endif static serviceescalation_mmap serviceescalations; diff --git a/engine/inc/com/centreon/engine/timeperiod.hh b/engine/inc/com/centreon/engine/timeperiod.hh index 44e24f13061..8a5d2ac5087 100644 --- a/engine/inc/com/centreon/engine/timeperiod.hh +++ b/engine/inc/com/centreon/engine/timeperiod.hh @@ -21,6 +21,9 @@ #define CCE_OBJECTS_TIMEPERIOD_HH #include "com/centreon/engine/daterange.hh" +#ifndef LEGACY_CONF +#include "common/engine_conf/timeperiod_helper.hh" +#endif /* Forward declaration. */ namespace com::centreon::engine { @@ -37,14 +40,29 @@ namespace com::centreon::engine { class timeperiod { public: +#ifdef LEGACY_CONF timeperiod(std::string const& name, std::string const& alias); +#else + timeperiod(const configuration::Timeperiod& obj); + void set_exclusions(const configuration::StringSet& exclusions); + void set_exceptions(const configuration::ExceptionArray& array); + void set_days(const configuration::DaysArray& array); +#endif - std::string const& get_name() const { return _name; }; + std::string const& get_name() const { + return _name; + }; void set_name(std::string const& name); - std::string const get_alias() const { return _alias; }; + std::string const get_alias() const { + return _alias; + }; void set_alias(std::string const& alias); - timeperiodexclusion const& get_exclusions() const { return _exclusions; }; - timeperiodexclusion& get_exclusions() { return _exclusions; }; + timeperiodexclusion const& get_exclusions() const { + return _exclusions; + }; + timeperiodexclusion& get_exclusions() { + return _exclusions; + }; void get_next_valid_time_per_timeperiod(time_t preferred_time, time_t* invalid_time, bool notif_timeperiod); diff --git a/engine/modules/CMakeLists.txt b/engine/modules/CMakeLists.txt index 6f6e0c8b3b7..fbf70d568c1 100644 --- a/engine/modules/CMakeLists.txt +++ b/engine/modules/CMakeLists.txt @@ -21,10 +21,6 @@ add_subdirectory("external_commands") include_directories(${CMAKE_SOURCE_DIR}/clib/inc) -set(EXTERNALCMD_MODULE - "${EXTERNALCMD_MODULE}" - PARENT_SCOPE) - # Benchmark module. add_subdirectory("bench") diff --git a/engine/modules/external_commands/CMakeLists.txt b/engine/modules/external_commands/CMakeLists.txt index 9a742cc7a53..265ac7d2c12 100644 --- a/engine/modules/external_commands/CMakeLists.txt +++ b/engine/modules/external_commands/CMakeLists.txt @@ -1,21 +1,22 @@ -## -## Copyright 2011-2013 Merethis -## -## This file is part of Centreon Engine. -## -## Centreon Engine is free software: you can redistribute it and/or -## modify it under the terms of the GNU General Public License version 2 -## as published by the Free Software Foundation. -## -## Centreon Engine 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 Centreon Engine. If not, see -## <http://www.gnu.org/licenses/>. -## +# +# Copyright 2011-2013 Merethis +# Copyright 2014-2024 Centreon +# +# This file is part of Centreon Engine. +# +# Centreon Engine is free software: you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 2 +# as published by the Free Software Foundation. +# +# Centreon Engine 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 Centreon Engine. If not, see +# <http://www.gnu.org/licenses/>. +# # Set directories. set(MODULE_DIR "${PROJECT_SOURCE_DIR}/modules/external_commands") @@ -27,6 +28,23 @@ include_directories(${MODULE_DIR}/inc ${CMAKE_SOURCE_DIR}/clib/inc) link_directories(${CMAKE_SOURCE_DIR}/build/centreon-clib/) # mod_externalcmd target. +add_library( + externalcmd_legacy + SHARED + + # Sources. + "${SRC_DIR}/main.cc" + "${SRC_DIR}/utils.cc" + + # Headers. + "${INC_DIR}/utils.hh" +) +target_compile_definitions(externalcmd_legacy PRIVATE LEGACY_CONF) +set_property(TARGET "externalcmd_legacy" PROPERTY PREFIX "") +target_precompile_headers("externalcmd_legacy" PRIVATE precomp_inc/precomp.hh) +add_dependencies(externalcmd_legacy centreon_clib pb_neb_lib) +target_link_libraries(externalcmd_legacy centreon_clib spdlog::spdlog) + add_library( externalcmd SHARED @@ -42,11 +60,14 @@ add_library( set_property(TARGET "externalcmd" PROPERTY PREFIX "") target_precompile_headers("externalcmd" PRIVATE precomp_inc/precomp.hh) -set(EXTERNALCMD_MODULE "${EXTERNALCMD_MODULE}" PARENT_SCOPE) add_dependencies(externalcmd centreon_clib pb_neb_lib) target_link_libraries(externalcmd centreon_clib spdlog::spdlog) # Install rule. +install(TARGETS "externalcmd_legacy" + DESTINATION "${ENGINE_MODULES_DIR}" + COMPONENT "runtime") + install(TARGETS "externalcmd" DESTINATION "${ENGINE_MODULES_DIR}" COMPONENT "runtime") diff --git a/engine/modules/external_commands/src/utils.cc b/engine/modules/external_commands/src/utils.cc index db249d10003..6a77e64028d 100644 --- a/engine/modules/external_commands/src/utils.cc +++ b/engine/modules/external_commands/src/utils.cc @@ -1,23 +1,22 @@ /** * Copyright 2011-2013 Merethis - * Copyright 2020-2021 Centreon + * Copyright 2020-2024 Centreon * - * This file is part of Centreon Engine. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * Centreon Engine is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License version 2 - * as published by the Free Software Foundation. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Centreon Engine 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. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com * - * You should have received a copy of the GNU General Public License - * along with Centreon Engine. If not, see - * <http://www.gnu.org/licenses/>. */ - #include "com/centreon/engine/modules/external_commands/utils.hh" #include "com/centreon/engine/commands/processing.hh" #include "com/centreon/engine/common.hh" @@ -42,8 +41,15 @@ int open_command_file(void) { struct stat st; /* if we're not checking external commands, don't do anything */ - if (config->check_external_commands() == false) +#ifdef LEGACY_CONF + if (!config->check_external_commands()) + return OK; + const std::string& command_file{config->command_file()}; +#else + if (!pb_config.check_external_commands()) return OK; + const std::string& command_file{pb_config.command_file()}; +#endif /* the command file was already created */ if (command_file_created) @@ -53,15 +59,13 @@ int open_command_file(void) { umask(S_IWOTH); /* use existing FIFO if possible */ - if (!(stat(config->command_file().c_str(), &st) != -1 && - (st.st_mode & S_IFIFO))) { + if (!(stat(command_file.c_str(), &st) != -1 && (st.st_mode & S_IFIFO))) { /* create the external command file as a named pipe (FIFO) */ - if (mkfifo(config->command_file().c_str(), - S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) != 0) { + if (mkfifo(command_file.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) != + 0) { engine_logger(log_runtime_error, basic) - << "Error: Could not create external command file '" - << config->command_file() << "' as named pipe: (" << errno << ") -> " - << strerror(errno) + << "Error: Could not create external command file '" << command_file + << "' as named pipe: (" << errno << ") -> " << strerror(errno) << ". If this file already exists and " "you are sure that another copy of Centreon Engine is not " "running, " @@ -72,7 +76,7 @@ int open_command_file(void) { "you are sure that another copy of Centreon Engine is not " "running, " "you should delete this file.", - config->command_file(), errno, strerror(errno)); + command_file, errno, strerror(errno)); return ERROR; } } @@ -80,8 +84,7 @@ int open_command_file(void) { /* open the command file for reading (non-blocked) - O_TRUNC flag cannot be * used due to errors on some systems */ /* NOTE: file must be opened read-write for poll() to work */ - if ((command_file_fd = - open(config->command_file().c_str(), O_RDWR | O_NONBLOCK)) < 0) { + if ((command_file_fd = open(command_file.c_str(), O_RDWR | O_NONBLOCK)) < 0) { engine_logger(log_runtime_error, basic) << "Error: Could not open external command file for reading " "via open(): (" @@ -145,7 +148,7 @@ int open_command_file(void) { fclose(command_file_fp); /* delete the named pipe */ - unlink(config->command_file().c_str()); + unlink(command_file.c_str()); return ERROR; } @@ -159,8 +162,13 @@ int open_command_file(void) { /* closes the external command file FIFO and deletes it */ int close_command_file(void) { /* if we're not checking external commands, don't do anything */ - if (config->check_external_commands() == false) +#ifdef LEGACY_CONF + if (!config->check_external_commands()) return OK; +#else + if (!pb_config.check_external_commands()) + return OK; +#endif /* the command file wasn't created or was already cleaned up */ if (command_file_created == false) @@ -255,8 +263,13 @@ static void command_file_worker_thread() { select(0, nullptr, nullptr, nullptr, &tv); } +#ifdef LEGACY_CONF external_command_buffer.set_capacity( config->external_command_buffer_slots()); +#else + external_command_buffer.set_capacity( + pb_config.external_command_buffer_slots()); +#endif /* process all commands in the file (named pipe) if there's some space in * the buffer */ diff --git a/engine/src/broker.cc b/engine/src/broker.cc index b6c25032068..8b5ae22dab3 100644 --- a/engine/src/broker.cc +++ b/engine/src/broker.cc @@ -1,23 +1,23 @@ /** -* Copyright 2002-2010 Ethan Galstad -* Copyright 2010 Nagios Core Development Team -* Copyright 2011-2013,2020 Centreon -* -* This file is part of Centreon Engine. -* -* Centreon Engine is free software: you can redistribute it and/or -* modify it under the terms of the GNU General Public License version 2 -* as published by the Free Software Foundation. -* -* Centreon Engine 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 Centreon Engine. If not, see -* <http://www.gnu.org/licenses/>. -*/ + * Copyright 2002-2010 Ethan Galstad + * Copyright 2010 Nagios Core Development Team + * Copyright 2011-2013,2020-2024 Centreon + * + * This file is part of Centreon Engine. + * + * Centreon Engine is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * Centreon Engine 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 Centreon Engine. If not, see + * <http://www.gnu.org/licenses/>. + */ #include "com/centreon/engine/broker.hh" #include <absl/strings/str_split.h> @@ -54,8 +54,13 @@ void broker_acknowledgement_data( int notify_contacts, int persistent_comment) { // Config check. +#ifdef LEGACY_CONF if (!(config->event_broker_options() & BROKER_ACKNOWLEDGEMENT_DATA)) return; +#else + if (!(pb_config.event_broker_options() & BROKER_ACKNOWLEDGEMENT_DATA)) + return; +#endif // Fill struct with relevant data. host* temp_host(NULL); @@ -122,8 +127,13 @@ void broker_adaptive_contact_data( */ void broker_adaptive_severity_data(int type, void* data) { /* Config check. */ +#ifdef LEGACY_CONF if (!(config->event_broker_options() & BROKER_ADAPTIVE_DATA)) return; +#else + if (!(pb_config.event_broker_options() & BROKER_ADAPTIVE_DATA)) + return; +#endif /* Fill struct with relevant data. */ nebstruct_adaptive_severity_data ds; @@ -142,8 +152,13 @@ void broker_adaptive_severity_data(int type, void* data) { */ void broker_adaptive_tag_data(int type, void* data) { /* Config check. */ +#ifdef LEGACY_CONF if (!(config->event_broker_options() & BROKER_ADAPTIVE_DATA)) return; +#else + if (!(pb_config.event_broker_options() & BROKER_ADAPTIVE_DATA)) + return; +#endif /* Fill struct with relevant data. */ nebstruct_adaptive_tag_data ds; @@ -162,8 +177,13 @@ void broker_adaptive_tag_data(int type, void* data) { */ void broker_adaptive_dependency_data(int type, void* data) { // Config check. +#ifdef LEGACY_CONF if (!(config->event_broker_options() & BROKER_ADAPTIVE_DATA)) return; +#else + if (!(pb_config.event_broker_options() & BROKER_ADAPTIVE_DATA)) + return; +#endif // Fill struct with relevant data. nebstruct_adaptive_dependency_data ds; @@ -205,8 +225,13 @@ void broker_adaptive_host_data(int type, host* hst, unsigned long modattr) { // Config check. +#ifdef LEGACY_CONF if (!(config->event_broker_options() & BROKER_ADAPTIVE_DATA)) return; +#else + if (!(pb_config.event_broker_options() & BROKER_ADAPTIVE_DATA)) + return; +#endif // Fill struct with relevant data. nebstruct_adaptive_host_data ds; @@ -259,8 +284,13 @@ void broker_adaptive_service_data(int type, com::centreon::engine::service* svc, unsigned long modattr) { // Config check. +#ifdef LEGACY_CONF if (!(config->event_broker_options() & BROKER_ADAPTIVE_DATA)) return; +#else + if (!(pb_config.event_broker_options() & BROKER_ADAPTIVE_DATA)) + return; +#endif // Fill struct with relevant data. nebstruct_adaptive_service_data ds; @@ -353,8 +383,13 @@ void broker_comment_data(int type, time_t expire_time, unsigned long comment_id) { // Config check. +#ifdef LEGACY_CONF if (!(config->event_broker_options() & BROKER_COMMENT_DATA)) return; +#else + if (!(pb_config.event_broker_options() & BROKER_COMMENT_DATA)) + return; +#endif // Fill struct with relevant data. nebstruct_comment_data ds; @@ -455,8 +490,13 @@ int broker_contact_notification_method_data( */ void broker_contact_status(int type, contact* cntct) { // Config check. +#ifdef LEGACY_CONF if (!(config->event_broker_options() & BROKER_STATUS_DATA)) return; +#else + if (!(pb_config.event_broker_options() & BROKER_STATUS_DATA)) + return; +#endif // Fill struct with relevant data. nebstruct_service_status_data ds; @@ -478,20 +518,26 @@ void broker_contact_status(int type, contact* cntct) { */ void broker_custom_variable(int type, void* data, - char const* varname, - char const* varvalue, + std::string_view&& varname, + std::string_view&& varvalue, struct timeval const* timestamp) { // Config check. +#ifdef LEGACY_CONF if (!(config->event_broker_options() & BROKER_CUSTOMVARIABLE_DATA)) return; +#else + if (!(pb_config.event_broker_options() & BROKER_CUSTOMVARIABLE_DATA)) + return; +#endif // Fill struct with relevant data. - nebstruct_custom_variable_data ds; - ds.type = type; - ds.timestamp = get_broker_timestamp(timestamp); - ds.object_ptr = data; - ds.var_name = const_cast<char*>(varname); - ds.var_value = const_cast<char*>(varvalue); + nebstruct_custom_variable_data ds{ + .type = type, + .timestamp = get_broker_timestamp(timestamp), + .var_name = varname, + .var_value = varvalue, + .object_ptr = data, + }; // Make callback. neb_make_callbacks(NEBCALLBACK_CUSTOM_VARIABLE_DATA, &ds); @@ -532,8 +578,13 @@ void broker_downtime_data(int type, unsigned long downtime_id, struct timeval const* timestamp) { // Config check. +#ifdef LEGACY_CONF if (!(config->event_broker_options() & BROKER_DOWNTIME_DATA)) return; +#else + if (!(pb_config.event_broker_options() & BROKER_DOWNTIME_DATA)) + return; +#endif // Fill struct with relevant data. nebstruct_downtime_data ds; @@ -570,8 +621,13 @@ void broker_external_command(int type, char* command_args, struct timeval const* timestamp) { // Config check. +#ifdef LEGACY_CONF if (!(config->event_broker_options() & BROKER_EXTERNALCOMMAND_DATA)) return; +#else + if (!(pb_config.event_broker_options() & BROKER_EXTERNALCOMMAND_DATA)) + return; +#endif // Fill struct with relevant data. nebstruct_external_command_data ds; @@ -593,8 +649,13 @@ void broker_external_command(int type, */ void broker_group(int type, void* data) { // Config check. +#ifdef LEGACY_CONF if (!(config->event_broker_options() & BROKER_GROUP_DATA)) return; +#else + if (!(pb_config.event_broker_options() & BROKER_GROUP_DATA)) + return; +#endif // Fill struct with relevant data. nebstruct_group_data ds; @@ -614,8 +675,13 @@ void broker_group(int type, void* data) { */ void broker_group_member(int type, void* object, void* group) { // Config check. +#ifdef LEGACY_CONF if (!(config->event_broker_options() & BROKER_GROUP_MEMBER_DATA)) return; +#else + if (!(pb_config.event_broker_options() & BROKER_GROUP_MEMBER_DATA)) + return; +#endif // Fill struct will relevant data. nebstruct_group_member_data ds; @@ -644,8 +710,13 @@ int broker_host_check(int type, char const* cmdline, char* output) { // Config check. +#ifdef LEGACY_CONF if (!(config->event_broker_options() & BROKER_HOST_CHECKS)) return OK; +#else + if (!(pb_config.event_broker_options() & BROKER_HOST_CHECKS)) + return OK; +#endif if (!hst) return ERROR; @@ -674,8 +745,13 @@ int broker_host_check(int type, */ void broker_host_status(int type, host* hst) { // Config check. +#ifdef LEGACY_CONF if (!(config->event_broker_options() & BROKER_STATUS_DATA)) return; +#else + if (!(pb_config.event_broker_options() & BROKER_STATUS_DATA)) + return; +#endif // Fill struct with relevant data. nebstruct_host_status_data ds; @@ -694,9 +770,15 @@ void broker_host_status(int type, host* hst) { */ void broker_log_data(char* data, time_t entry_time) { // Config check. +#ifdef LEGACY_CONF if (!(config->event_broker_options() & BROKER_LOGGED_DATA) || !config->log_legacy_enabled()) return; +#else + if (!(pb_config.event_broker_options() & BROKER_LOGGED_DATA) || + !pb_config.log_legacy_enabled()) + return; +#endif // Fill struct with relevant data. nebstruct_log_data ds; @@ -750,8 +832,13 @@ int broker_notification_data(int type [[maybe_unused]], */ void broker_program_state(int type, int flags) { // Config check. +#ifdef LEGACY_CONF if (!(config->event_broker_options() & BROKER_PROGRAM_STATE)) return; +#else + if (!(pb_config.event_broker_options() & BROKER_PROGRAM_STATE)) + return; +#endif // Fill struct with relevant data. nebstruct_process_data ds; @@ -766,6 +853,7 @@ void broker_program_state(int type, int flags) { * Sends program status updates to broker. */ void broker_program_status() { +#ifdef LEGACY_CONF // Config check. if (!(config->event_broker_options() & BROKER_STATUS_DATA)) return; @@ -787,6 +875,29 @@ void broker_program_status() { // Make callbacks. neb_make_callbacks(NEBCALLBACK_PROGRAM_STATUS_DATA, &ds); +#else + // Config check. + if (!(pb_config.event_broker_options() & BROKER_STATUS_DATA)) + return; + + // Fill struct with relevant data. + nebstruct_program_status_data ds; + ds.last_command_check = last_command_check; + ds.notifications_enabled = pb_config.enable_notifications(); + ds.active_service_checks_enabled = pb_config.execute_service_checks(); + ds.passive_service_checks_enabled = pb_config.accept_passive_service_checks(); + ds.active_host_checks_enabled = pb_config.execute_host_checks(); + ds.passive_host_checks_enabled = pb_config.accept_passive_host_checks(); + ds.event_handlers_enabled = pb_config.enable_event_handlers(); + ds.flap_detection_enabled = pb_config.enable_flap_detection(); + ds.obsess_over_hosts = pb_config.obsess_over_hosts(); + ds.obsess_over_services = pb_config.obsess_over_services(); + ds.global_host_event_handler = pb_config.global_host_event_handler(); + ds.global_service_event_handler = pb_config.global_service_event_handler(); + + // Make callbacks. + neb_make_callbacks(NEBCALLBACK_PROGRAM_STATUS_DATA, &ds); +#endif } /** @@ -804,8 +915,13 @@ void broker_relation_data(int type, host* dep_hst, com::centreon::engine::service* dep_svc) { // Config check. +#ifdef LEGACY_CONF if (!(config->event_broker_options() & BROKER_RELATION_DATA)) return; +#else + if (!(pb_config.event_broker_options() & BROKER_RELATION_DATA)) + return; +#endif if (!hst || !dep_hst) return; @@ -850,8 +966,13 @@ int broker_service_check(int type, int check_type, const char* cmdline) { // Config check. +#ifdef LEGACY_CONF if (!(config->event_broker_options() & BROKER_SERVICE_CHECKS)) return OK; +#else + if (!(pb_config.event_broker_options() & BROKER_SERVICE_CHECKS)) + return OK; +#endif if (!svc) return ERROR; @@ -880,8 +1001,13 @@ int broker_service_check(int type, */ void broker_service_status(int type, com::centreon::engine::service* svc) { // Config check. +#ifdef LEGACY_CONF if (!(config->event_broker_options() & BROKER_STATUS_DATA)) return; +#else + if (!(pb_config.event_broker_options() & BROKER_STATUS_DATA)) + return; +#endif // Fill struct with relevant data. nebstruct_service_status_data ds; diff --git a/engine/src/checks/checker.cc b/engine/src/checks/checker.cc index 6293272e1fa..6a86eb69461 100644 --- a/engine/src/checks/checker.cc +++ b/engine/src/checks/checker.cc @@ -1,20 +1,22 @@ /** - * Copyright 2024 Centreon - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * For more information : contact@centreon.com - */ +* Copyright 1999-2010 Ethan Galstad +* Copyright 2011-2024 Centreon +* +* This file is part of Centreon Engine. +* +* Centreon Engine is free software: you can redistribute it and/or +* modify it under the terms of the GNU General Public License version 2 +* as published by the Free Software Foundation. +* +* Centreon Engine 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 Centreon Engine. If not, see +* <http://www.gnu.org/licenses/>. +*/ #include "com/centreon/engine/checks/checker.hh" @@ -38,12 +40,6 @@ using namespace com::centreon::engine::checks; checker* checker::_instance = nullptr; static constexpr time_t max_check_reaper_time = 30; -/************************************** - * * - * Public Methods * - * * - **************************************/ - /** * Get instance of the checker singleton. * @@ -519,10 +515,17 @@ com::centreon::engine::host::host_state checker::_execute_sync(host* hst) { timeval start_cmd; timeval end_cmd{0, 0}; gettimeofday(&start_cmd, nullptr); +#ifdef LEGACY_CONF broker_system_command(NEBTYPE_SYSTEM_COMMAND_START, NEBFLAG_NONE, NEBATTR_NONE, start_cmd, end_cmd, 0, config->host_check_timeout(), false, 0, tmp_processed_cmd, nullptr, nullptr); +#else + broker_system_command(NEBTYPE_SYSTEM_COMMAND_START, NEBFLAG_NONE, + NEBATTR_NONE, start_cmd, end_cmd, 0, + pb_config.host_check_timeout(), false, 0, + tmp_processed_cmd, nullptr, nullptr); +#endif commands::result res; @@ -550,7 +553,11 @@ com::centreon::engine::host::host_state checker::_execute_sync(host* hst) { } else { // Run command. try { +#ifdef LEGACY_CONF cmd->run(processed_cmd, *macros, config->host_check_timeout(), res); +#else + cmd->run(processed_cmd, *macros, pb_config.host_check_timeout(), res); +#endif } catch (std::exception const& e) { run_failure("(Execute command failed)"); @@ -575,28 +582,41 @@ com::centreon::engine::host::host_state checker::_execute_sync(host* hst) { memset(&end_cmd, 0, sizeof(end_time)); end_cmd.tv_sec = res.end_time.to_seconds(); end_cmd.tv_usec = res.end_time.to_useconds() - end_cmd.tv_sec * 1000000ull; +#ifdef LEGACY_CONF broker_system_command(NEBTYPE_SYSTEM_COMMAND_END, NEBFLAG_NONE, NEBATTR_NONE, start_cmd, end_cmd, execution_time, config->host_check_timeout(), res.exit_status == process::timeout, res.exit_code, tmp_processed_cmd, res.output.c_str(), nullptr); +#else + broker_system_command(NEBTYPE_SYSTEM_COMMAND_END, NEBFLAG_NONE, NEBATTR_NONE, + start_cmd, end_cmd, execution_time, + pb_config.host_check_timeout(), + res.exit_status == process::timeout, res.exit_code, + tmp_processed_cmd, res.output.c_str(), nullptr); +#endif // Cleanup. clear_volatile_macros_r(macros); // If the command timed out. +#ifdef LEGACY_CONF +uint32_t host_check_timeout = config->host_check_timeout(); +#else +uint32_t host_check_timeout = pb_config.host_check_timeout(); +#endif if (res.exit_status == process::timeout) { res.output = fmt::format("Host check timed out after {} seconds", - config->host_check_timeout()); + host_check_timeout); engine_logger(log_runtime_warning, basic) << "Warning: Host check command '" << processed_cmd << "' for host '" - << hst->name() << "' timed out after " << config->host_check_timeout() + << hst->name() << "' timed out after " << host_check_timeout << " seconds"; SPDLOG_LOGGER_WARN( runtime_logger, "Warning: Host check command '{}' for host '{}' timed out after {} " "seconds", - processed_cmd, hst->name(), config->host_check_timeout()); + processed_cmd, hst->name(), host_check_timeout); } // Update values. diff --git a/engine/src/command_manager.cc b/engine/src/command_manager.cc index 665fa498a71..18207450d8a 100644 --- a/engine/src/command_manager.cc +++ b/engine/src/command_manager.cc @@ -83,8 +83,13 @@ int command_manager::process_passive_service_check( /* skip this service check result if we aren't accepting passive service * checks */ +#ifdef LEGACY_CONF if (!config->accept_passive_service_checks()) return ERROR; +#else + if (!pb_config.accept_passive_service_checks()) + return ERROR; +#endif /* make sure we have a reasonable return code */ if (return_code > 3) @@ -174,8 +179,13 @@ int command_manager::process_passive_host_check(time_t check_time, const std::string* real_host_name = nullptr; /* skip this host check result if we aren't accepting passive host checks */ +#ifdef LEGACY_CONF if (!config->accept_passive_service_checks()) return ERROR; +#else + if (!pb_config.accept_passive_service_checks()) + return ERROR; +#endif /* make sure we have a reasonable return code */ if (return_code > 2) @@ -288,12 +298,21 @@ int command_manager::get_stats(std::string const& request, Stats* response) { uint32_t used_external_command_buffer_slots = 0; uint32_t high_external_command_buffer_slots = 0; // get number f items in the command buffer +#ifdef LEGACY_CONF if (config->check_external_commands()) { used_external_command_buffer_slots = external_command_buffer.size(); high_external_command_buffer_slots = external_command_buffer.high(); } response->mutable_program_status()->set_total_external_command_buffer_slots( config->external_command_buffer_slots()); +#else + if (pb_config.check_external_commands()) { + used_external_command_buffer_slots = external_command_buffer.size(); + high_external_command_buffer_slots = external_command_buffer.high(); + } + response->mutable_program_status()->set_total_external_command_buffer_slots( + pb_config.external_command_buffer_slots()); +#endif response->mutable_program_status()->set_used_external_command_buffer_slots( used_external_command_buffer_slots); response->mutable_program_status()->set_high_external_command_buffer_slots( diff --git a/engine/src/commands/command.cc b/engine/src/commands/command.cc index 134c459a8d5..c1043c996aa 100644 --- a/engine/src/commands/command.cc +++ b/engine/src/commands/command.cc @@ -178,8 +178,12 @@ bool commands::command::gest_call_interval( caller_to_last_call_map::iterator group_search = _result_cache.find(caller); if (group_search != _result_cache.end()) { time_t now = time(nullptr); - if (group_search->second->launch_time + config->interval_length() >= - now && +#ifdef LEGACY_CONF + uint32_t interval_length = config->interval_length(); +#else + uint32_t interval_length = pb_config.interval_length(); +#endif + if (group_search->second->launch_time + interval_length >= now && group_search->second->res) { // old check is too recent result_to_reuse = std::make_shared<result>(*group_search->second->res); result_to_reuse->command_id = command_id; diff --git a/engine/src/commands/commands.cc b/engine/src/commands/commands.cc index 234d437038a..072e14870d4 100644 --- a/engine/src/commands/commands.cc +++ b/engine/src/commands/commands.cc @@ -55,8 +55,14 @@ int check_for_external_commands() { functions_logger->trace("check_for_external_commands()"); +#ifdef LEGACY_CONF + bool check_external_commands = config->check_external_commands(); +#else + bool check_external_commands = pb_config.check_external_commands(); +#endif + /* bail out if we shouldn't be checking for external commands */ - if (!config->check_external_commands()) + if (!check_external_commands) return ERROR; /* update last command check time */ @@ -497,9 +503,16 @@ void cmd_signal_process(int cmd, char* args) { int cmd_process_service_check_result(int cmd [[maybe_unused]], time_t check_time, char* args) { +#ifdef LEGACY_CONF + bool accept_passive_service_checks = config->accept_passive_service_checks(); +#else + bool accept_passive_service_checks = + pb_config.accept_passive_service_checks(); +#endif + /* skip this service check result if we aren't accepting passive service * checks */ - if (!config->accept_passive_service_checks()) + if (!accept_passive_service_checks) return ERROR; auto a{absl::StrSplit(args, absl::MaxSplits(';', 3))}; @@ -608,9 +621,16 @@ int process_passive_service_check(time_t check_time, char const* output) { char const* real_host_name(nullptr); +#ifdef LEGACY_CONF + bool accept_passive_service_checks = config->accept_passive_service_checks(); +#else + bool accept_passive_service_checks = + pb_config.accept_passive_service_checks(); +#endif + /* skip this service check result if we aren't accepting passive service * checks */ - if (config->accept_passive_service_checks() == false) + if (!accept_passive_service_checks) return ERROR; /* make sure we have all required data */ @@ -738,8 +758,16 @@ int process_passive_host_check(time_t check_time, int return_code, char const* output) { char const* real_host_name(nullptr); + +#ifdef LEGACY_CONF + bool accept_passive_service_checks = config->accept_passive_service_checks(); +#else + bool accept_passive_service_checks = + pb_config.accept_passive_service_checks(); +#endif + /* skip this host check result if we aren't accepting passive host checks */ - if (!config->accept_passive_service_checks()) + if (!accept_passive_service_checks) return ERROR; /* make sure we have all required data */ @@ -1918,13 +1946,21 @@ int cmd_change_object_char_var(int cmd, char* args) { /* update the variable */ switch (cmd) { case CMD_CHANGE_GLOBAL_HOST_EVENT_HANDLER: +#ifdef LEGACY_CONF config->global_host_event_handler(temp_ptr); +#else + pb_config.set_global_host_event_handler(temp_ptr); +#endif global_host_event_handler_ptr = cmd_found->second.get(); attr = MODATTR_EVENT_HANDLER_COMMAND; break; case CMD_CHANGE_GLOBAL_SVC_EVENT_HANDLER: +#ifdef LEGACY_CONF config->global_service_event_handler(temp_ptr); +#else + pb_config.set_global_service_event_handler(temp_ptr); +#endif global_service_event_handler_ptr = cmd_found->second.get(); attr = MODATTR_EVENT_HANDLER_COMMAND; break; @@ -2255,8 +2291,14 @@ void enable_service_checks(service* svc) { void enable_all_notifications(void) { constexpr uint32_t attr = MODATTR_NOTIFICATIONS_ENABLED; +#ifdef LEGACY_CONF + bool enable_notifications = config->enable_notifications(); +#else + bool enable_notifications = pb_config.enable_notifications(); +#endif + /* bail out if we're already set... */ - if (config->enable_notifications()) + if (enable_notifications) return; /* set the attribute modified flag */ @@ -2264,7 +2306,11 @@ void enable_all_notifications(void) { modified_service_process_attributes |= attr; /* update notification status */ +#ifdef LEGACY_CONF config->enable_notifications(true); +#else + pb_config.set_enable_notifications(true); +#endif /* send data to event broker */ broker_adaptive_program_data(NEBTYPE_ADAPTIVEPROGRAM_UPDATE, NEBFLAG_NONE, @@ -2280,8 +2326,14 @@ void enable_all_notifications(void) { void disable_all_notifications(void) { constexpr uint32_t attr = MODATTR_NOTIFICATIONS_ENABLED; +#ifdef LEGACY_CONF + bool enable_notifications = config->enable_notifications(); +#else + bool enable_notifications = pb_config.enable_notifications(); +#endif + /* bail out if we're already set... */ - if (config->enable_notifications() == false) + if (!enable_notifications) return; /* set the attribute modified flag */ @@ -2289,7 +2341,11 @@ void disable_all_notifications(void) { modified_service_process_attributes |= attr; /* update notification status */ +#ifdef LEGACY_CONF config->enable_notifications(false); +#else + pb_config.set_enable_notifications(false); +#endif /* send data to event broker */ broker_adaptive_program_data(NEBTYPE_ADAPTIVEPROGRAM_UPDATE, NEBFLAG_NONE, @@ -2701,15 +2757,25 @@ void remove_service_acknowledgement(service* svc) { void start_executing_service_checks(void) { constexpr uint32_t attr = MODATTR_ACTIVE_CHECKS_ENABLED; +#ifdef LEGACY_CONF + bool execute_service_checks = config->execute_service_checks(); +#else + bool execute_service_checks = pb_config.execute_service_checks(); +#endif + /* bail out if we're already executing services */ - if (config->execute_service_checks()) + if (execute_service_checks) return; /* set the attribute modified flag */ modified_service_process_attributes |= attr; /* set the service check execution flag */ +#ifdef LEGACY_CONF config->execute_service_checks(true); +#else + pb_config.set_execute_service_checks(true); +#endif /* send data to event broker */ broker_adaptive_program_data(NEBTYPE_ADAPTIVEPROGRAM_UPDATE, NEBFLAG_NONE, @@ -2725,15 +2791,25 @@ void start_executing_service_checks(void) { void stop_executing_service_checks(void) { unsigned long attr = MODATTR_ACTIVE_CHECKS_ENABLED; +#ifdef LEGACY_CONF + bool execute_service_checks = config->execute_service_checks(); +#else + bool execute_service_checks = pb_config.execute_service_checks(); +#endif + /* bail out if we're already not executing services */ - if (config->execute_service_checks() == false) + if (!execute_service_checks) return; /* set the attribute modified flag */ modified_service_process_attributes |= attr; /* set the service check execution flag */ +#ifdef LEGACY_CONF config->execute_service_checks(false); +#else + pb_config.set_execute_service_checks(false); +#endif /* send data to event broker */ broker_adaptive_program_data(NEBTYPE_ADAPTIVEPROGRAM_UPDATE, NEBFLAG_NONE, @@ -2749,15 +2825,26 @@ void stop_executing_service_checks(void) { void start_accepting_passive_service_checks(void) { constexpr uint32_t attr = MODATTR_PASSIVE_CHECKS_ENABLED; +#ifdef LEGACY_CONF + bool accept_passive_service_checks = config->accept_passive_service_checks(); +#else + bool accept_passive_service_checks = + pb_config.accept_passive_service_checks(); +#endif + /* bail out if we're already accepting passive services */ - if (config->accept_passive_service_checks()) + if (accept_passive_service_checks) return; /* set the attribute modified flag */ modified_service_process_attributes |= attr; /* set the service check flag */ +#ifdef LEGACY_CONF config->accept_passive_service_checks(true); +#else + pb_config.set_accept_passive_service_checks(true); +#endif /* send data to event broker */ broker_adaptive_program_data(NEBTYPE_ADAPTIVEPROGRAM_UPDATE, NEBFLAG_NONE, @@ -2773,15 +2860,25 @@ void start_accepting_passive_service_checks(void) { void stop_accepting_passive_service_checks(void) { constexpr uint32_t attr = MODATTR_PASSIVE_CHECKS_ENABLED; +#ifdef LEGACY_CONF + bool accept_passive_service_checks = config->accept_passive_service_checks(); +#else + bool accept_passive_service_checks = pb_config.accept_passive_service_checks(); +#endif + /* bail out if we're already not accepting passive services */ - if (config->accept_passive_service_checks() == false) + if (!accept_passive_service_checks) return; /* set the attribute modified flag */ modified_service_process_attributes |= attr; /* set the service check flag */ +#ifdef LEGACY_CONF config->accept_passive_service_checks(false); +#else + pb_config.set_accept_passive_service_checks(false); +#endif /* send data to event broker */ broker_adaptive_program_data(NEBTYPE_ADAPTIVEPROGRAM_UPDATE, NEBFLAG_NONE, @@ -2835,15 +2932,25 @@ void disable_passive_service_checks(service* svc) { void start_executing_host_checks(void) { constexpr uint32_t attr = MODATTR_ACTIVE_CHECKS_ENABLED; +#ifdef LEGACY_CONF + bool execute_host_checks = config->execute_host_checks(); +#else + bool execute_host_checks = pb_config.execute_host_checks(); +#endif + /* bail out if we're already executing hosts */ - if (config->execute_host_checks()) + if (execute_host_checks) return; /* set the attribute modified flag */ modified_host_process_attributes |= attr; /* set the host check execution flag */ +#ifdef LEGACY_CONF config->execute_host_checks(true); +#else + pb_config.set_execute_host_checks(true); +#endif /* send data to event broker */ broker_adaptive_program_data(NEBTYPE_ADAPTIVEPROGRAM_UPDATE, NEBFLAG_NONE, @@ -2859,15 +2966,24 @@ void start_executing_host_checks(void) { void stop_executing_host_checks(void) { constexpr uint32_t attr = MODATTR_ACTIVE_CHECKS_ENABLED; +#ifdef LEGACY_CONF + bool execute_host_checks = config->execute_host_checks(); +#else + bool execute_host_checks = pb_config.execute_host_checks(); +#endif /* bail out if we're already not executing hosts */ - if (config->execute_host_checks() == false) + if (!execute_host_checks) return; /* set the attribute modified flag */ modified_host_process_attributes |= attr; /* set the host check execution flag */ - config->execute_host_checks(false); +#ifdef LEGACY_CONF + config->execute_host_checks(true); +#else + pb_config.set_execute_host_checks(true); +#endif /* send data to event broker */ broker_adaptive_program_data(NEBTYPE_ADAPTIVEPROGRAM_UPDATE, NEBFLAG_NONE, @@ -2883,15 +2999,25 @@ void stop_executing_host_checks(void) { void start_accepting_passive_host_checks(void) { constexpr uint32_t attr = MODATTR_PASSIVE_CHECKS_ENABLED; +#ifdef LEGACY_CONF + bool accept_passive_host_checks = config->accept_passive_host_checks(); +#else + bool accept_passive_host_checks = pb_config.accept_passive_host_checks(); +#endif + /* bail out if we're already accepting passive hosts */ - if (config->accept_passive_host_checks()) + if (accept_passive_host_checks) return; /* set the attribute modified flag */ modified_host_process_attributes |= attr; /* set the host check flag */ +#ifdef LEGACY_CONF config->accept_passive_host_checks(true); +#else + pb_config.set_accept_passive_host_checks(true); +#endif /* send data to event broker */ broker_adaptive_program_data(NEBTYPE_ADAPTIVEPROGRAM_UPDATE, NEBFLAG_NONE, @@ -2907,15 +3033,25 @@ void start_accepting_passive_host_checks(void) { void stop_accepting_passive_host_checks(void) { constexpr uint32_t attr = MODATTR_PASSIVE_CHECKS_ENABLED; +#ifdef LEGACY_CONF + bool accept_passive_host_checks = config->accept_passive_host_checks(); +#else + bool accept_passive_host_checks = pb_config.accept_passive_host_checks(); +#endif + /* bail out if we're already not accepting passive hosts */ - if (config->accept_passive_host_checks() == false) + if (!accept_passive_host_checks) return; /* set the attribute modified flag */ modified_host_process_attributes |= attr; /* set the host check flag */ +#ifdef LEGACY_CONF config->accept_passive_host_checks(false); +#else + pb_config.set_accept_passive_host_checks(false); +#endif /* send data to event broker */ broker_adaptive_program_data(NEBTYPE_ADAPTIVEPROGRAM_UPDATE, NEBFLAG_NONE, @@ -2968,8 +3104,14 @@ void disable_passive_host_checks(host* hst) { void start_using_event_handlers(void) { constexpr uint32_t attr = MODATTR_EVENT_HANDLER_ENABLED; +#ifdef LEGACY_CONF + bool enable_event_handlers = config->enable_event_handlers(); +#else + bool enable_event_handlers = pb_config.enable_event_handlers(); +#endif + /* no change */ - if (config->enable_event_handlers()) + if (enable_event_handlers) return; /* set the attribute modified flag */ @@ -2977,7 +3119,11 @@ void start_using_event_handlers(void) { modified_service_process_attributes |= attr; /* set the event handler flag */ +#ifdef LEGACY_CONF config->enable_event_handlers(true); +#else + pb_config.set_enable_event_handlers(true); +#endif /* send data to event broker */ broker_adaptive_program_data(NEBTYPE_ADAPTIVEPROGRAM_UPDATE, NEBFLAG_NONE, @@ -2993,8 +3139,14 @@ void start_using_event_handlers(void) { void stop_using_event_handlers(void) { constexpr uint32_t attr = MODATTR_EVENT_HANDLER_ENABLED; +#ifdef LEGACY_CONF + bool enable_event_handlers = config->enable_event_handlers(); +#else + bool enable_event_handlers = pb_config.enable_event_handlers(); +#endif + /* no change */ - if (config->enable_event_handlers() == false) + if (!enable_event_handlers) return; /* set the attribute modified flag */ @@ -3002,7 +3154,11 @@ void stop_using_event_handlers(void) { modified_service_process_attributes |= attr; /* set the event handler flag */ +#ifdef LEGACY_CONF config->enable_event_handlers(false); +#else + pb_config.set_enable_event_handlers(false); +#endif /* send data to event broker */ broker_adaptive_program_data(NEBTYPE_ADAPTIVEPROGRAM_UPDATE, NEBFLAG_NONE, @@ -3156,15 +3312,25 @@ void enable_host_checks(host* hst) { void start_obsessing_over_service_checks(void) { constexpr uint32_t attr = MODATTR_OBSESSIVE_HANDLER_ENABLED; +#ifdef LEGACY_CONF + bool obsess_over_services = config->obsess_over_services(); +#else + bool obsess_over_services = pb_config.obsess_over_services(); +#endif + /* no change */ - if (config->obsess_over_services()) + if (obsess_over_services) return; /* set the attribute modified flag */ modified_service_process_attributes |= attr; /* set the service obsession flag */ +#ifdef LEGACY_CONF config->obsess_over_services(true); +#else + pb_config.set_obsess_over_services(true); +#endif /* send data to event broker */ broker_adaptive_program_data(NEBTYPE_ADAPTIVEPROGRAM_UPDATE, NEBFLAG_NONE, @@ -3180,15 +3346,25 @@ void start_obsessing_over_service_checks(void) { void stop_obsessing_over_service_checks(void) { constexpr uint32_t attr = MODATTR_OBSESSIVE_HANDLER_ENABLED; +#ifdef LEGACY_CONF + bool obsess_over_services = config->obsess_over_services(); +#else + bool obsess_over_services = pb_config.obsess_over_services(); +#endif + /* no change */ - if (config->obsess_over_services() == false) + if (!obsess_over_services) return; /* set the attribute modified flag */ modified_service_process_attributes |= attr; /* set the service obsession flag */ +#ifdef LEGACY_CONF config->obsess_over_services(false); +#else + pb_config.set_obsess_over_services(false); +#endif /* send data to event broker */ broker_adaptive_program_data(NEBTYPE_ADAPTIVEPROGRAM_UPDATE, NEBFLAG_NONE, @@ -3204,15 +3380,25 @@ void stop_obsessing_over_service_checks(void) { void start_obsessing_over_host_checks(void) { unsigned long attr = MODATTR_OBSESSIVE_HANDLER_ENABLED; +#ifdef LEGACY_CONF + bool obsess_over_hosts = config->obsess_over_hosts(); +#else + bool obsess_over_hosts = pb_config.obsess_over_hosts(); +#endif + /* no change */ - if (config->obsess_over_hosts()) + if (obsess_over_hosts) return; /* set the attribute modified flag */ modified_host_process_attributes |= attr; /* set the host obsession flag */ +#ifdef LEGACY_CONF config->obsess_over_hosts(true); +#else + pb_config.set_obsess_over_hosts(true); +#endif /* send data to event broker */ broker_adaptive_program_data(NEBTYPE_ADAPTIVEPROGRAM_UPDATE, NEBFLAG_NONE, @@ -3228,15 +3414,25 @@ void start_obsessing_over_host_checks(void) { void stop_obsessing_over_host_checks(void) { constexpr uint32_t attr = MODATTR_OBSESSIVE_HANDLER_ENABLED; +#ifdef LEGACY_CONF + bool obsess_over_hosts = config->obsess_over_hosts(); +#else + bool obsess_over_hosts = pb_config.obsess_over_hosts(); +#endif + /* no change */ - if (config->obsess_over_hosts() == false) + if (!obsess_over_hosts) return; /* set the attribute modified flag */ modified_host_process_attributes |= attr; /* set the host obsession flag */ +#ifdef LEGACY_CONF config->obsess_over_hosts(false); +#else + pb_config.set_obsess_over_hosts(false); +#endif /* send data to event broker */ broker_adaptive_program_data(NEBTYPE_ADAPTIVEPROGRAM_UPDATE, NEBFLAG_NONE, @@ -3252,15 +3448,25 @@ void stop_obsessing_over_host_checks(void) { void enable_service_freshness_checks(void) { constexpr uint32_t attr = MODATTR_FRESHNESS_CHECKS_ENABLED; +#ifdef LEGACY_CONF + bool check_service_freshness = config->check_service_freshness(); +#else + bool check_service_freshness = pb_config.check_service_freshness(); +#endif + /* no change */ - if (config->check_service_freshness()) + if (check_service_freshness) return; /* set the attribute modified flag */ modified_service_process_attributes |= attr; /* set the freshness check flag */ +#ifdef LEGACY_CONF config->check_service_freshness(true); +#else + pb_config.set_check_service_freshness(true); +#endif /* send data to event broker */ broker_adaptive_program_data(NEBTYPE_ADAPTIVEPROGRAM_UPDATE, NEBFLAG_NONE, @@ -3276,15 +3482,25 @@ void enable_service_freshness_checks(void) { void disable_service_freshness_checks(void) { constexpr uint32_t attr = MODATTR_FRESHNESS_CHECKS_ENABLED; +#ifdef LEGACY_CONF + bool check_service_freshness = config->check_service_freshness(); +#else + bool check_service_freshness = pb_config.check_service_freshness(); +#endif + /* no change */ - if (config->check_service_freshness() == false) + if (!check_service_freshness) return; /* set the attribute modified flag */ modified_service_process_attributes |= attr; /* set the freshness check flag */ +#ifdef LEGACY_CONF config->check_service_freshness(false); +#else + pb_config.set_check_service_freshness(false); +#endif /* send data to event broker */ broker_adaptive_program_data(NEBTYPE_ADAPTIVEPROGRAM_UPDATE, NEBFLAG_NONE, @@ -3300,15 +3516,25 @@ void disable_service_freshness_checks(void) { void enable_host_freshness_checks(void) { constexpr uint32_t attr = MODATTR_FRESHNESS_CHECKS_ENABLED; +#ifdef LEGACY_CONF + bool check_host_freshness = config->check_host_freshness(); +#else + bool check_host_freshness = pb_config.check_host_freshness(); +#endif + /* no change */ - if (config->check_host_freshness()) + if (check_host_freshness) return; /* set the attribute modified flag */ modified_host_process_attributes |= attr; /* set the freshness check flag */ +#ifdef LEGACY_CONF config->check_host_freshness(true); +#else + pb_config.set_check_host_freshness(true); +#endif /* send data to event broker */ broker_adaptive_program_data(NEBTYPE_ADAPTIVEPROGRAM_UPDATE, NEBFLAG_NONE, @@ -3323,15 +3549,25 @@ void enable_host_freshness_checks(void) { void disable_host_freshness_checks(void) { constexpr uint32_t attr = MODATTR_FRESHNESS_CHECKS_ENABLED; +#ifdef LEGACY_CONF + bool check_host_freshness = config->check_host_freshness(); +#else + bool check_host_freshness = pb_config.check_host_freshness(); +#endif + /* no change */ - if (config->check_host_freshness() == false) + if (!check_host_freshness) return; /* set the attribute modified flag */ modified_host_process_attributes |= attr; /* set the freshness check flag */ +#ifdef LEGACY_CONF config->check_host_freshness(false); +#else + pb_config.set_check_host_freshness(false); +#endif /* send data to event broker */ broker_adaptive_program_data(NEBTYPE_ADAPTIVEPROGRAM_UPDATE, NEBFLAG_NONE, @@ -3347,15 +3583,25 @@ void disable_host_freshness_checks(void) { void enable_performance_data(void) { constexpr uint32_t attr = MODATTR_PERFORMANCE_DATA_ENABLED; +#ifdef LEGACY_CONF + bool process_performance_data = config->process_performance_data(); +#else + bool process_performance_data = pb_config.process_performance_data(); +#endif + /* bail out if we're already set... */ - if (config->process_performance_data()) + if (process_performance_data) return; /* set the attribute modified flag */ modified_host_process_attributes |= attr; modified_service_process_attributes |= attr; +#ifdef LEGACY_CONF config->process_performance_data(true); +#else + pb_config.set_process_performance_data(true); +#endif /* send data to event broker */ broker_adaptive_program_data(NEBTYPE_ADAPTIVEPROGRAM_UPDATE, NEBFLAG_NONE, @@ -3371,15 +3617,25 @@ void enable_performance_data(void) { void disable_performance_data(void) { constexpr uint32_t attr = MODATTR_PERFORMANCE_DATA_ENABLED; +#ifdef LEGACY_CONF + bool process_performance_data = config->process_performance_data(); +#else + bool process_performance_data = pb_config.process_performance_data(); +#endif + /* bail out if we're already set... */ - if (config->process_performance_data() == false) + if (!process_performance_data) return; /* set the attribute modified flag */ modified_host_process_attributes |= attr; modified_service_process_attributes |= attr; +#ifdef LEGACY_CONF config->process_performance_data(false); +#else + pb_config.set_process_performance_data(false); +#endif /* send data to event broker */ broker_adaptive_program_data(NEBTYPE_ADAPTIVEPROGRAM_UPDATE, NEBFLAG_NONE, diff --git a/engine/src/commands/connector.cc b/engine/src/commands/connector.cc index d95dc58ee6c..1b48116397f 100644 --- a/engine/src/commands/connector.cc +++ b/engine/src/commands/connector.cc @@ -73,9 +73,18 @@ connector::connector(const std::string& connector_name, } { UNIQUE_LOCK(lck, _lock); +#ifdef LEGACY_CONF _process.setpgid_on_exec(config->use_setpgid()); +#else + _process.setpgid_on_exec(pb_config.use_setpgid()); +#endif } - if (config->enable_environment_macros()) { +#ifdef LEGACY_CONF + bool enable_environment_macros = config->enable_environment_macros(); +#else + bool enable_environment_macros = pb_config.enable_environment_macros(); +#endif + if (enable_environment_macros) { engine_logger(log_runtime_warning, basic) << "Warning: Connector does not enable environment macros"; runtime_logger->warn( @@ -424,10 +433,17 @@ void connector::_connector_close() { _send_query_quit(); // Waiting connector quit. +#ifdef LEGACY_CONF bool is_timeout{ _cv_query.wait_for( lock, std::chrono::seconds(config->service_check_timeout())) == std::cv_status::timeout}; +#else + bool is_timeout{ + _cv_query.wait_for( + lock, std::chrono::seconds(pb_config.service_check_timeout())) == + std::cv_status::timeout}; +#endif if (is_timeout || !_query_quit_ok) { _process.kill(); if (is_timeout) { @@ -471,9 +487,15 @@ void connector::_connector_start() { _send_query_version(); // Waiting connector version, or 1 seconds. +#ifdef LEGACY_CONF bool is_timeout{!_cv_query.wait_for( lock, std::chrono::seconds(config->service_check_timeout()), [this] { return _version_set; })}; +#else + bool is_timeout{!_cv_query.wait_for( + lock, std::chrono::seconds(pb_config.service_check_timeout()), + [this] { return _version_set; })}; +#endif if (is_timeout || !_query_version_ok) { _process.kill(); diff --git a/engine/src/commands/processing.cc b/engine/src/commands/processing.cc index 173940589e0..4802232030c 100644 --- a/engine/src/commands/processing.cc +++ b/engine/src/commands/processing.cc @@ -848,13 +848,22 @@ bool processing::execute(const std::string& cmdstr) { // Log the external command. if (command_id == CMD_PROCESS_SERVICE_CHECK_RESULT || command_id == CMD_PROCESS_HOST_CHECK_RESULT) { +#ifdef LEGACY_CONF + bool log_passive_check = config->log_passive_checks(); +#else + bool log_passive_check = pb_config.log_passive_checks(); +#endif // Passive checks are logged in checks.c. - if (config->log_passive_checks()) { + if (log_passive_checks) { engine_logger(log_passive_check, basic) << "EXTERNAL COMMAND: " << command_name << ';' << args; checks_logger->info("EXTERNAL COMMAND: {};{}", command_name, args); } +#ifdef LEGACY_CONF } else if (config->log_external_commands()) { +#else + } else if (pb_config.log_external_commands()) { +#endif engine_logger(log_external_command, basic) << "EXTERNAL COMMAND: " << command_name << ';' << args; SPDLOG_LOGGER_INFO(external_command_logger, "EXTERNAL COMMAND: {};{}", @@ -903,9 +912,18 @@ void processing::_wrapper_read_state_information() { try { retention::state state; retention::parser p; - p.parse(config->state_retention_file(), state); +#ifdef LEGACY_CONF + const std::string& retention_file = config->state_retention_file(); +#else + const std::string& retention_file = pb_config.state_retention_file(); +#endif + p.parse(retention_file, state); retention::applier::state app_state; +#ifdef LEGACY_CONF app_state.apply(*config, state); +#else + app_state.apply(pb_config, state); +#endif } catch (std::exception const& e) { engine_logger(log_runtime_error, basic) << "Error: could not load retention file: " << e.what(); @@ -915,7 +933,11 @@ void processing::_wrapper_read_state_information() { } void processing::_wrapper_save_state_information() { +#ifdef LEGACY_CONF retention::dump::save(config->state_retention_file()); +#else + retention::dump::save(pb_config.state_retention_file()); +#endif } void processing::wrapper_enable_host_and_child_notifications(host* hst) { diff --git a/engine/src/commands/raw.cc b/engine/src/commands/raw.cc index b196a80834b..b8d1dab2e2b 100644 --- a/engine/src/commands/raw.cc +++ b/engine/src/commands/raw.cc @@ -480,7 +480,12 @@ void raw::_build_custom_service_macro_environment(nagios_macros& macros, * @param[out] env The environment to fill. */ void raw::_build_environment_macros(nagios_macros& macros, environment& env) { - if (config->enable_environment_macros()) { +#ifdef LEGACY_CONF + bool enable_environment_macros = config->enable_environment_macros(); +#else + bool enable_environment_macros = pb_config.enable_environment_macros(); +#endif + if (enable_environment_macros) { _build_macrosx_environment(macros, env); _build_argv_macro_environment(macros, env); _build_custom_host_macro_environment(macros, env); @@ -497,15 +502,20 @@ void raw::_build_environment_macros(nagios_macros& macros, environment& env) { * @param[out] env The environment to fill. */ void raw::_build_macrosx_environment(nagios_macros& macros, environment& env) { - for (uint32_t i(0); i < MACRO_X_COUNT; ++i) { +#ifdef LEGACY_CONF + bool use_large_installation_tweaks = config->use_large_installation_tweaks(); +#else + bool use_large_installation_tweaks = + pb_config.use_large_installation_tweaks(); +#endif + for (uint32_t i = 0; i < MACRO_X_COUNT; ++i) { int release_memory(0); // Need to grab macros? if (macros.x[i].empty()) { // Skip summary macro in lage instalation tweaks. - if ((i < MACRO_TOTALHOSTSUP) || - (i > MACRO_TOTALSERVICEPROBLEMSUNHANDLED) || - !config->use_large_installation_tweaks()) { + if (i < MACRO_TOTALHOSTSUP || i > MACRO_TOTALSERVICEPROBLEMSUNHANDLED || + !use_large_installation_tweaks) { grab_macrox_value_r(¯os, i, "", "", macros.x[i], &release_memory); } } @@ -537,11 +547,15 @@ process* raw::_get_free_process() { if (_processes_free.empty()) { /* Only the out stream is open */ process* p = new process(this, false, true, false); +#ifdef LEGACY_CONF p->setpgid_on_exec(config->use_setpgid()); +#else + p->setpgid_on_exec(pb_config.use_setpgid()); +#endif return p; } // Get a free process. - process* p(_processes_free.front()); + process* p = _processes_free.front(); _processes_free.pop_front(); return p; } diff --git a/engine/src/config.cc b/engine/src/config.cc index f91eb07a218..544d2be9a0d 100644 --- a/engine/src/config.cc +++ b/engine/src/config.cc @@ -24,7 +24,9 @@ #include "com/centreon/engine/globals.hh" #include "com/centreon/engine/logging/logger.hh" #include "com/centreon/engine/string.hh" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/parser.hh" +#endif using namespace com::centreon::engine; using namespace com::centreon::engine::configuration::applier; diff --git a/engine/src/configuration/applier/anomalydetection.cc b/engine/src/configuration/applier/anomalydetection.cc index 78d062bc6f0..80b7021c73e 100644 --- a/engine/src/configuration/applier/anomalydetection.cc +++ b/engine/src/configuration/applier/anomalydetection.cc @@ -31,23 +31,7 @@ using namespace com::centreon::engine; using namespace com::centreon::engine::downtimes; using namespace com::centreon::engine::configuration; -/** - * Check if the anomalydetection group name matches the configuration object. - */ -class servicegroup_name_comparator { - public: - servicegroup_name_comparator(std::string const& servicegroup_name) { - _servicegroup_name = servicegroup_name; - } - - bool operator()(std::shared_ptr<configuration::servicegroup> sg) { - return _servicegroup_name == sg->servicegroup_name(); - } - - private: - std::string _servicegroup_name; -}; - +#ifdef LEGACY_CONF /** * Add new anomalydetection. * @@ -156,9 +140,106 @@ void applier::anomalydetection::add_object( if (it->second.is_sent()) { timeval tv(get_broker_timestamp(nullptr)); - broker_custom_variable(NEBTYPE_SERVICECUSTOMVARIABLE_ADD, ad, - it->first.c_str(), it->second.value().c_str(), - &tv); + broker_custom_variable(NEBTYPE_SERVICECUSTOMVARIABLE_ADD, ad, it->first, + it->second.value(), &tv); + } + } + + // Notify event broker. + broker_adaptive_service_data(NEBTYPE_SERVICE_ADD, NEBFLAG_NONE, NEBATTR_NONE, + ad, MODATTR_ALL); +} +#else +/** + * @brief Add new anomalydetection. + * + * @param obj The new anomalydetection protobuf configuration to add into the + * monitoring engine. + */ +void applier::anomalydetection::add_object( + const configuration::Anomalydetection& obj) { + // Check anomalydetection. + if (!obj.host_id()) + throw engine_error() << fmt::format( + "No host_id available for the host '{} - unable to create " + "anomalydetection '{}'", + obj.host_name(), obj.service_description()); + + // Logging. + SPDLOG_LOGGER_DEBUG(config_logger, + "Creating new anomalydetection '{}' of host '{}'.", + obj.service_description(), obj.host_name()); + + // Add anomalydetection to the global configuration set. + auto* cfg_obj = pb_config.add_anomalydetections(); + cfg_obj->CopyFrom(obj); + + // Create anomalydetection. + engine::anomalydetection* ad{add_anomalydetection( + obj.host_id(), obj.service_id(), obj.host_name(), + obj.service_description(), obj.display_name(), obj.internal_id(), + obj.dependent_service_id(), obj.metric_name(), obj.thresholds_file(), + obj.status_change(), + static_cast<engine::anomalydetection::service_state>(obj.initial_state()), + obj.max_check_attempts(), obj.check_interval(), obj.retry_interval(), + obj.notification_interval(), obj.first_notification_delay(), + obj.recovery_notification_delay(), obj.notification_period(), + static_cast<bool>(obj.notification_options() & action_svc_ok), + static_cast<bool>(obj.notification_options() & action_svc_unknown), + static_cast<bool>(obj.notification_options() & action_svc_warning), + static_cast<bool>(obj.notification_options() & action_svc_critical), + static_cast<bool>(obj.notification_options() & action_svc_flapping), + static_cast<bool>(obj.notification_options() & action_svc_downtime), + obj.notifications_enabled(), obj.is_volatile(), obj.event_handler(), + obj.event_handler_enabled(), obj.checks_active(), obj.checks_passive(), + obj.flap_detection_enabled(), obj.low_flap_threshold(), + obj.high_flap_threshold(), + static_cast<bool>(obj.flap_detection_options() & action_svc_ok), + static_cast<bool>(obj.flap_detection_options() & action_svc_warning), + static_cast<bool>(obj.flap_detection_options() & action_svc_unknown), + static_cast<bool>(obj.flap_detection_options() & action_svc_critical), + static_cast<bool>(obj.stalking_options() & action_svc_ok), + static_cast<bool>(obj.stalking_options() & action_svc_warning), + static_cast<bool>(obj.stalking_options() & action_svc_unknown), + static_cast<bool>(obj.stalking_options() & action_svc_critical), + obj.process_perf_data(), obj.check_freshness(), obj.freshness_threshold(), + obj.notes(), obj.notes_url(), obj.action_url(), obj.icon_image(), + obj.icon_image_alt(), obj.retain_status_information(), + obj.retain_nonstatus_information(), obj.obsess_over_service(), + obj.timezone(), obj.icon_id(), obj.sensitivity())}; + if (!ad) + throw engine_error() << "Could not register anomalydetection '" + << obj.service_description() << "' of host '" + << obj.host_name() << "'"; + ad->set_initial_notif_time(0); + engine::anomalydetection::services[{obj.host_name(), + obj.service_description()}] + ->set_host_id(obj.host_id()); + engine::anomalydetection::services[{obj.host_name(), + obj.service_description()}] + ->set_service_id(obj.service_id()); + ad->set_acknowledgement_timeout(obj.acknowledgement_timeout() * + pb_config.interval_length()); + ad->set_last_acknowledgement(0); + + // Add contacts. + for (auto& c : obj.contacts().data()) + ad->mut_contacts().insert({c, nullptr}); + + // Add contactgroups. + for (auto& cg : obj.contactgroups().data()) + ad->get_contactgroups().insert({cg, nullptr}); + + // Add custom variables. + for (auto& cv : obj.customvariables()) { + engine::customvariable& c = ad->custom_variables[cv.name()]; + c.set_value(cv.value()); + c.set_sent(cv.is_sent()); + + if (c.is_sent()) { + timeval tv(get_broker_timestamp(nullptr)); + broker_custom_variable(NEBTYPE_SERVICECUSTOMVARIABLE_ADD, ad, cv.name(), + cv.value(), &tv); } } @@ -166,7 +247,9 @@ void applier::anomalydetection::add_object( broker_adaptive_service_data(NEBTYPE_SERVICE_ADD, NEBFLAG_NONE, NEBATTR_NONE, ad, MODATTR_ALL); } +#endif +#ifdef LEGACY_CONF /** * Expand a anomalydetection object. * @@ -191,7 +274,31 @@ void applier::anomalydetection::expand_objects(configuration::state& s) { } s.anomalydetections() = std::move(new_ads); } +#else +/** + * Expand a anomalydetection object. + * + * @param[in,out] s State being applied. + */ +void applier::anomalydetection::expand_objects(configuration::State& s) { + std::list<std::unique_ptr<Service> > expanded; + // Let's consider all the macros defined in s. + absl::flat_hash_set<std::string_view> cvs; + for (auto& cv : s.macros_filter().data()) + cvs.emplace(cv); + + // Browse all anomalydetections. + for (auto& ad_cfg : *s.mutable_anomalydetections()) { + // Should custom variables be sent to broker ? + for (auto& cv : *ad_cfg.mutable_customvariables()) { + if (!s.enable_macros_filter() || cvs.contains(cv.name())) + cv.set_is_sent(true); + } + } +} +#endif +#ifdef LEGACY_CONF /** * Modified anomalydetection. * @@ -376,7 +483,7 @@ void applier::anomalydetection::modify_object( if (c.second.is_sent()) { timeval tv(get_broker_timestamp(nullptr)); broker_custom_variable(NEBTYPE_SERVICECUSTOMVARIABLE_DELETE, s.get(), - c.first.c_str(), c.second.value().c_str(), &tv); + c.first, c.second.value(), &tv); } } s->custom_variables.clear(); @@ -388,16 +495,198 @@ void applier::anomalydetection::modify_object( if (c.second.is_sent()) { timeval tv(get_broker_timestamp(nullptr)); broker_custom_variable(NEBTYPE_SERVICECUSTOMVARIABLE_ADD, s.get(), + c.first, c.second.value(), &tv); + } + } + } + + // Notify event broker. + broker_adaptive_service_data(NEBTYPE_SERVICE_UPDATE, NEBFLAG_NONE, + NEBATTR_NONE, s.get(), MODATTR_ALL); +} +#else +/** + * Modified anomalydetection. + * + * @param[in] obj The new anomalydetection to modify into the monitoring + * engine. + */ +void applier::anomalydetection::modify_object( + configuration::Anomalydetection* old_obj, + const configuration::Anomalydetection& new_obj) { + const std::string& host_name(old_obj->host_name()); + const std::string& service_description(old_obj->service_description()); + + // Logging. + SPDLOG_LOGGER_DEBUG(config_logger, + "Modifying new anomalydetection '{}' of host '{}'.", + service_description, host_name); + + // Find anomalydetection object. + service_id_map::iterator it_obj = + engine::anomalydetection::services_by_id.find( + {old_obj->host_id(), old_obj->service_id()}); + if (it_obj == engine::anomalydetection::services_by_id.end()) + throw engine_error() << fmt::format( + "Could not modify non-existing anomalydetection object '{}' of host " + "'{}'", + service_description, host_name); + std::shared_ptr<engine::anomalydetection> s = + std::static_pointer_cast<engine::anomalydetection>(it_obj->second); + + // Modify properties. + if (it_obj->second->get_hostname() != new_obj.host_name() || + it_obj->second->description() != new_obj.service_description()) { + engine::service::services.erase( + {it_obj->second->get_hostname(), it_obj->second->description()}); + engine::service::services.insert( + {{new_obj.host_name(), new_obj.service_description()}, it_obj->second}); + } + + s->set_hostname(new_obj.host_name()); + s->set_description(new_obj.service_description()); + s->set_display_name(new_obj.display_name()); + s->set_metric_name(new_obj.metric_name()); + s->set_thresholds_file(new_obj.thresholds_file()); + s->set_event_handler(new_obj.event_handler()); + s->set_event_handler_enabled(new_obj.event_handler_enabled()); + s->set_initial_state(static_cast<engine::anomalydetection::service_state>( + new_obj.initial_state())); + s->set_check_interval(new_obj.check_interval()); + s->set_retry_interval(new_obj.retry_interval()); + s->set_max_attempts(new_obj.max_check_attempts()); + + s->set_notify_on( + (new_obj.notification_options() & action_svc_unknown ? notifier::unknown + : notifier::none) | + (new_obj.notification_options() & action_svc_warning ? notifier::warning + : notifier::none) | + (new_obj.notification_options() & action_svc_critical ? notifier::critical + : notifier::none) | + (new_obj.notification_options() & action_svc_ok ? notifier::ok + : notifier::none) | + (new_obj.notification_options() & action_svc_flapping + ? (notifier::flappingstart | notifier::flappingstop | + notifier::flappingdisabled) + : notifier::none) | + (new_obj.notification_options() & action_svc_downtime ? notifier::downtime + : notifier::none)); + + s->set_notification_interval( + static_cast<double>(new_obj.notification_interval())); + s->set_first_notification_delay( + static_cast<double>(new_obj.first_notification_delay())); + + s->add_stalk_on(new_obj.stalking_options() & action_svc_ok ? notifier::ok + : notifier::none); + s->add_stalk_on(new_obj.stalking_options() & action_svc_warning + ? notifier::warning + : notifier::none); + s->add_stalk_on(new_obj.stalking_options() & action_svc_unknown + ? notifier::unknown + : notifier::none); + s->add_stalk_on(new_obj.stalking_options() & action_svc_critical + ? notifier::critical + : notifier::none); + + s->set_notification_period(new_obj.notification_period()); + s->set_flap_detection_enabled(new_obj.flap_detection_enabled()); + s->set_low_flap_threshold(new_obj.low_flap_threshold()); + s->set_high_flap_threshold(new_obj.high_flap_threshold()); + + s->set_flap_detection_on(notifier::none); + s->add_flap_detection_on(new_obj.flap_detection_options() & action_svc_ok + ? notifier::ok + : notifier::none); + s->add_flap_detection_on(new_obj.flap_detection_options() & action_svc_warning + ? notifier::warning + : notifier::none); + s->add_flap_detection_on(new_obj.flap_detection_options() & action_svc_unknown + ? notifier::unknown + : notifier::none); + s->add_flap_detection_on(new_obj.flap_detection_options() & + action_svc_critical + ? notifier::critical + : notifier::none); + + s->set_process_performance_data( + static_cast<int>(new_obj.process_perf_data())); + s->set_check_freshness(new_obj.check_freshness()); + s->set_freshness_threshold(new_obj.freshness_threshold()); + s->set_accept_passive_checks(new_obj.checks_passive()); + s->set_event_handler(new_obj.event_handler()); + s->set_checks_enabled(new_obj.checks_active()); + s->set_retain_status_information( + static_cast<bool>(new_obj.retain_status_information())); + s->set_retain_nonstatus_information( + static_cast<bool>(new_obj.retain_nonstatus_information())); + s->set_notifications_enabled(new_obj.notifications_enabled()); + s->set_obsess_over(new_obj.obsess_over_service()); + s->set_notes(new_obj.notes()); + s->set_notes_url(new_obj.notes_url()); + s->set_action_url(new_obj.action_url()); + s->set_icon_image(new_obj.icon_image()); + s->set_icon_image_alt(new_obj.icon_image_alt()); + s->set_is_volatile(new_obj.is_volatile()); + s->set_timezone(new_obj.timezone()); + s->set_host_id(new_obj.host_id()); + s->set_service_id(new_obj.service_id()); + s->set_acknowledgement_timeout(new_obj.acknowledgement_timeout() * + pb_config.interval_length()); + s->set_recovery_notification_delay(new_obj.recovery_notification_delay()); + + // Contacts. + if (!MessageDifferencer::Equals(new_obj.contacts(), old_obj->contacts())) { + // Delete old contacts. + s->mut_contacts().clear(); + + // Add contacts to host. + for (auto& contact_name : new_obj.contacts().data()) + s->mut_contacts().insert({contact_name, nullptr}); + } + + // Contact groups. + if (!MessageDifferencer::Equals(new_obj.contactgroups(), + old_obj->contactgroups())) { + // Delete old contact groups. + s->get_contactgroups().clear(); + + // Add contact groups to host. + for (auto& cg_name : new_obj.contactgroups().data()) + s->get_contactgroups().insert({cg_name, nullptr}); + } + + // Custom variables. + if (!std::equal( + new_obj.customvariables().begin(), new_obj.customvariables().end(), + old_obj->customvariables().begin(), MessageDifferencer::Equals)) { + for (auto& c : s->custom_variables) { + if (c.second.is_sent()) { + timeval tv(get_broker_timestamp(nullptr)); + broker_custom_variable(NEBTYPE_SERVICECUSTOMVARIABLE_DELETE, s.get(), c.first.c_str(), c.second.value().c_str(), &tv); } } + s->custom_variables.clear(); + + for (auto& c : new_obj.customvariables()) { + s->custom_variables[c.name()] = c.value(); + + if (c.is_sent()) { + timeval tv(get_broker_timestamp(nullptr)); + broker_custom_variable(NEBTYPE_SERVICECUSTOMVARIABLE_ADD, s.get(), + c.name(), c.value(), &tv); + } + } } // Notify event broker. broker_adaptive_service_data(NEBTYPE_SERVICE_UPDATE, NEBFLAG_NONE, NEBATTR_NONE, s.get(), MODATTR_ALL); } +#endif +#ifdef LEGACY_CONF /** * Remove old anomalydetection. * @@ -452,7 +741,55 @@ void applier::anomalydetection::remove_object( // Remove anomalydetection from the global configuration set. config->anomalydetections().erase(obj); } +#else +void applier::anomalydetection::remove_object(ssize_t idx) { + Anomalydetection& obj = pb_config.mutable_anomalydetections()->at(idx); + const std::string& host_name(obj.host_name()); + const std::string& service_description(obj.service_description()); + + // Logging. + SPDLOG_LOGGER_DEBUG(config_logger, + "Removing anomalydetection '{}' of host '{}'.", + service_description, host_name); + + // Find anomalydetection. + auto it = + engine::service::services_by_id.find({obj.host_id(), obj.service_id()}); + if (it != engine::service::services_by_id.end()) { + std::shared_ptr<engine::anomalydetection> ad( + std::static_pointer_cast<engine::anomalydetection>(it->second)); + + // Remove anomalydetection comments. + comment::delete_service_comments(obj.host_id(), obj.service_id()); + + // Remove anomalydetection downtimes. + downtime_manager::instance() + .delete_downtime_by_hostname_service_description_start_time_comment( + host_name, service_description, {false, (time_t)0}, ""); + + // Remove events related to this anomalydetection. + applier::scheduler::instance().remove_service(obj.host_id(), + obj.service_id()); + + // remove anomalydetection from servicegroup->members + for (auto& it_s : it->second->get_parent_groups()) + it_s->members.erase({host_name, service_description}); + + // Notify event broker. + broker_adaptive_service_data(NEBTYPE_SERVICE_DELETE, NEBFLAG_NONE, + NEBATTR_NONE, ad.get(), MODATTR_ALL); + + // Unregister anomalydetection. + engine::anomalydetection::services.erase({host_name, service_description}); + engine::anomalydetection::services_by_id.erase(it); + } + + // Remove anomalydetection from the global configuration set. + pb_config.mutable_anomalydetections()->DeleteSubrange(idx, 1); +} +#endif +#if LEGACY_CONF /** * Resolve a anomalydetection. * @@ -492,7 +829,44 @@ void applier::anomalydetection::resolve_object( // Resolve anomalydetection. it->second->resolve(err.config_warnings, err.config_errors); } +#else +/** + * Resolve a anomalydetection. + * + * @param[in] obj Service object. + */ +void applier::anomalydetection::resolve_object( + const configuration::Anomalydetection& obj, error_cnt& err) { + // Logging. + SPDLOG_LOGGER_DEBUG(config_logger, "Resolving anomalydetection '{}' of host '{}'.", + obj.service_description(), obj.host_name()); + + // Find anomalydetection. + service_id_map::iterator it = engine::anomalydetection::services_by_id.find( + {obj.host_id(), obj.service_id()}); + if (engine::anomalydetection::services_by_id.end() == it) + throw engine_error() << "Cannot resolve non-existing anomalydetection '" + << obj.service_description() << "' of host '" + << obj.host_name() << "'"; + + // Remove anomalydetection group links. + it->second->get_parent_groups().clear(); + + // Find host and adjust its counters. + host_id_map::iterator hst(engine::host::hosts_by_id.find(it->first.first)); + if (hst != engine::host::hosts_by_id.end()) { + hst->second->set_total_services(hst->second->get_total_services() + 1); + hst->second->set_total_service_check_interval( + hst->second->get_total_service_check_interval() + + static_cast<uint64_t>(it->second->check_interval())); + } + + // Resolve anomalydetection. + it->second->resolve(err.config_warnings, err.config_errors); +} +#endif +#ifdef LEGACY_CONF /** * Expand anomalydetection instance memberships. * @@ -528,7 +902,9 @@ void applier::anomalydetection::_expand_service_memberships( s.servicegroups().insert(backup); } } +#endif +#ifdef LEGACY_CONF /** * @brief Inherits special variables from host. * @@ -569,3 +945,4 @@ void applier::anomalydetection::_inherits_special_vars( obj.timezone(it->timezone()); } } +#endif diff --git a/engine/src/configuration/applier/command.cc b/engine/src/configuration/applier/command.cc index da25e1189ae..8ba46fc3ef8 100644 --- a/engine/src/configuration/applier/command.cc +++ b/engine/src/configuration/applier/command.cc @@ -32,16 +32,7 @@ using namespace com::centreon::engine; using namespace com::centreon::engine::configuration; -/** - * Default constructor. - */ -applier::command::command() {} - -/** - * Destructor. - */ -applier::command::~command() throw() {} - +#ifdef LEGACY_CONF /** * Add new command. * @@ -85,7 +76,52 @@ void applier::command::add_object(configuration::command const& obj) { } } } +#else +/** + * Add new command. + * + * @param[in] obj The new command to add into the monitoring engine. + */ +void applier::command::add_object(const configuration::Command& obj) { + // Logging. + config_logger->debug("Creating new command '{}'.", obj.command_name()); + + // Add command to the global configuration set. + auto* cmd = pb_config.add_commands(); + cmd->CopyFrom(obj); + if (obj.connector().empty()) { + auto raw = std::make_shared<commands::raw>( + obj.command_name(), obj.command_line(), &checks::checker::instance()); + commands::command::commands[raw->get_name()] = std::move(raw); + } else { + connector_map::iterator found_con{ + commands::connector::connectors.find(obj.connector())}; + if (found_con != commands::connector::connectors.end() && + found_con->second) { + std::shared_ptr<commands::forward> forward{ + std::make_shared<commands::forward>( + obj.command_name(), obj.command_line(), found_con->second)}; + commands::command::commands[forward->get_name()] = forward; + } else { + std::shared_ptr<commands::otel_connector> otel_cmd = + commands::otel_connector::get_otel_connector(obj.connector()); + if (otel_cmd) { + std::shared_ptr<commands::forward> forward{ + std::make_shared<commands::forward>(obj.command_name(), + obj.command_line(), otel_cmd)}; + commands::command::commands[forward->get_name()] = forward; + } else { + throw engine_error() << fmt::format( + "Could not register command '{}': unable to find '{}'", + obj.command_name(), obj.connector()); + } + } + } +} +#endif + +#ifdef LEGACY_CONF /** * @brief Expand command. * @@ -97,7 +133,20 @@ void applier::command::add_object(configuration::command const& obj) { void applier::command::expand_objects(configuration::state& s) { (void)s; } +#else +/** + * @brief Expand command. + * + * Command configuration objects do not need expansion. Therefore this + * method does nothing. + * + * @param[in] s Unused. + */ +void applier::command::expand_objects(configuration::State& s + [[maybe_unused]]) {} +#endif +#ifdef LEGACY_CONF /** * Modified command. * @@ -168,7 +217,73 @@ void applier::command::modify_object(configuration::command const& obj) { broker_command_data(NEBTYPE_COMMAND_UPDATE, NEBFLAG_NONE, NEBATTR_NONE, c, &tv); } +#else +/** + * @brief Modify command. + * + * @param obj The new command protobuf configuration for the object to modify + * in the monitoring engine. + */ +void applier::command::modify_object(configuration::Command* to_modify, + const configuration::Command& new_obj) { + // Logging. + config_logger->debug("Modifying command '{}'.", new_obj.command_name()); + + // Find command object. + command_map::iterator it_obj = + commands::command::commands.find(new_obj.command_name()); + if (it_obj == commands::command::commands.end()) + throw engine_error() << fmt::format( + "Could not modify non-existing command object '{}'", + new_obj.command_name()); + + // Update the global configuration set. + to_modify->CopyFrom(new_obj); + + // Command will be temporarily removed from the command set but + // will be added back right after with _create_command. This does + // not create dangling pointers since commands::command object are + // not referenced anywhere, only ::command objects are. + // commands::command::commands.erase(obj.command_name()); + if (new_obj.connector().empty()) { + auto raw = std::make_shared<commands::raw>(new_obj.command_name(), + new_obj.command_line(), + &checks::checker::instance()); + it_obj->second = raw; + } else { + connector_map::iterator found_con{ + commands::connector::connectors.find(new_obj.connector())}; + if (found_con != commands::connector::connectors.end() && + found_con->second) { + std::shared_ptr<commands::forward> forward{ + std::make_shared<commands::forward>(new_obj.command_name(), + new_obj.command_line(), + found_con->second)}; + it_obj->second = forward; + } else { + std::shared_ptr<commands::otel_connector> otel_cmd = + commands::otel_connector::get_otel_connector(new_obj.connector()); + if (otel_cmd) { + std::shared_ptr<commands::forward> forward{ + std::make_shared<commands::forward>( + new_obj.command_name(), new_obj.command_line(), otel_cmd)}; + it_obj->second = forward; + } else { + throw engine_error() << fmt::format( + "Could not register command '{}': unable to find '{}'", + new_obj.command_name(), new_obj.connector()); + } + } + } + // Notify event broker. + timeval tv(get_broker_timestamp(NULL)); + commands::command* c = it_obj->second.get(); + broker_command_data(NEBTYPE_COMMAND_UPDATE, NEBFLAG_NONE, NEBATTR_NONE, c, + &tv); +} +#endif +#ifdef LEGACY_CONF /** * Remove old command. * @@ -200,7 +315,40 @@ void applier::command::remove_object(configuration::command const& obj) { // Remove command from the global configuration set. config->commands().erase(obj); } +#else +/** + * @brief Remove a protobuf command configuration at index idx + * + * @param idx The position in configuration of the configuration to remove. + */ +void applier::command::remove_object(ssize_t idx) { + const configuration::Command& obj = pb_config.commands()[idx]; + // Logging. + config_logger->debug("Removing command '{}'.", obj.command_name()); + // Find command. + std::unordered_map<std::string, std::shared_ptr<commands::command> >::iterator + it = commands::command::commands.find(obj.command_name()); + if (it != commands::command::commands.end()) { + commands::command* cmd(it->second.get()); + + // Notify event broker. + timeval tv(get_broker_timestamp(NULL)); + broker_command_data(NEBTYPE_COMMAND_DELETE, NEBFLAG_NONE, NEBATTR_NONE, cmd, + &tv); + + // Erase command (will effectively delete the object). + commands::command::commands.erase(it); + } else + throw engine_error() << fmt::format( + "Could not remove command '{}': it does not exist", obj.command_name()); + + // Remove command from the global configuration set. + pb_config.mutable_commands()->DeleteSubrange(idx, 1); +} +#endif + +#ifdef LEGACY_CONF /** * @brief Resolve command. * @@ -220,3 +368,24 @@ void applier::command::resolve_object(configuration::command const& obj, } } } +#else +/** + * @brief Resolve command. + * + * This method will check for its connector's existence, if command is + * configured to use one. + * + * @param[in] obj Command object. + */ +void applier::command::resolve_object(const configuration::Command& obj, + error_cnt& err [[maybe_unused]]) { + if (!obj.connector().empty()) { + connector_map::iterator found = + commands::connector::connectors.find(obj.connector()); + if (found == commands::connector::connectors.end() || !found->second) { + if (!commands::otel_connector::get_otel_connector(obj.connector())) + throw engine_error() << "unknow command " << obj.connector(); + } + } +} +#endif diff --git a/engine/src/configuration/applier/connector.cc b/engine/src/configuration/applier/connector.cc index f44a12df06c..b5b2ee674fb 100644 --- a/engine/src/configuration/applier/connector.cc +++ b/engine/src/configuration/applier/connector.cc @@ -32,6 +32,7 @@ using namespace com::centreon::engine::configuration; constexpr std::string_view _otel_fake_exe("opentelemetry"); +#ifdef LEGACY_CONF /** * Add new connector. * @@ -72,7 +73,49 @@ void applier::connector::add_object(configuration::connector const& obj) { commands::connector::connectors[obj.connector_name()] = cmd; } } +#else +/** + * @brief Add new connector. + * + * @param obj The new connector to add into the monitoring engine. + */ +void applier::connector::add_object(const configuration::Connector& obj) { + // Logging. + config_logger->debug("Creating new connector '{}'.", obj.connector_name()); + + // Expand command line. + nagios_macros* macros = get_global_macros(); + std::string command_line; + process_macros_r(macros, obj.connector_line(), command_line, 0); + std::string processed_cmd(command_line); + + // Add connector to the global configuration set. + auto* cfg_cnn = pb_config.add_connectors(); + cfg_cnn->CopyFrom(obj); + + // Create connector. + boost::trim(processed_cmd); + + // If executable connector path ends with opentelemetry, it's a fake + // opentelemetry connector. + size_t end_path = processed_cmd.find(' '); + size_t otel_pos = processed_cmd.find(_otel_fake_exe); + if (otel_pos < end_path) { + commands::otel_connector::create( + obj.connector_name(), + boost::algorithm::trim_copy( + processed_cmd.substr(otel_pos + _otel_fake_exe.length())), + &checks::checker::instance()); + } else { + auto cmd = std::make_shared<commands::connector>( + obj.connector_name(), processed_cmd, &checks::checker::instance()); + commands::connector::connectors[obj.connector_name()] = cmd; + } +} +#endif + +#ifdef LEGACY_CONF /** * @brief Expand connector. * @@ -84,7 +127,20 @@ void applier::connector::add_object(configuration::connector const& obj) { void applier::connector::expand_objects(configuration::state& s) { (void)s; } +#else +/** + * @brief Expand connector. + * + * Connector configuration objects do not need expansion. Therefore + * this method only copy obj to expanded. + * + * @param[in] s Unused. + */ +void applier::connector::expand_objects(configuration::State& s + [[maybe_unused]]) {} +#endif +#ifdef LEGACY_CONF /** * Modify connector. * @@ -155,7 +211,43 @@ void applier::connector::modify_object(configuration::connector const& obj) { config->connectors().erase(it_cfg); config->connectors().insert(obj); } +#else +/** + * @brief Modify connector + * + * @param to_modify The current configuration connector + * @param new_obj The new one. + */ +void applier::connector::modify_object( + configuration::Connector* to_modify, + const configuration::Connector& new_obj) { + // Logging. + config_logger->debug("Modifying connector '{}'.", new_obj.connector_name()); + + // Find connector object. + connector_map::iterator it_obj( + commands::connector::connectors.find(new_obj.connector_name())); + if (it_obj == commands::connector::connectors.end()) + throw engine_error() << fmt::format( + "Could not modify non-existing connector object '{}'", + new_obj.connector_name()); + + commands::connector* c = it_obj->second.get(); + + // Update the global configuration set. + to_modify->CopyFrom(new_obj); + + // Expand command line. + nagios_macros* macros(get_global_macros()); + std::string command_line; + process_macros_r(macros, new_obj.connector_line(), command_line, 0); + + // Set the new command line. + c->set_command_line(command_line); +} +#endif +#ifdef LEGACY_CONF /** * Remove old connector. * @@ -180,7 +272,26 @@ void applier::connector::remove_object(configuration::connector const& obj) { // Remove connector from the global configuration set. config->connectors().erase(obj); } +#else +void applier::connector::remove_object(ssize_t idx) { + // Logging. + const configuration::Connector& obj = pb_config.connectors()[idx]; + config_logger->debug("Removing connector '{}'.", obj.connector_name()); + + // Find connector. + connector_map::iterator it = + commands::connector::connectors.find(obj.connector_name()); + if (it != commands::connector::connectors.end()) { + // Remove connector object. + commands::connector::connectors.erase(it); + } + + // Remove connector from the global configuration set. + pb_config.mutable_connectors()->DeleteSubrange(idx, 1); +} +#endif +#ifdef LEGACY_CONF /** * @brief Resolve a connector. * @@ -192,3 +303,15 @@ void applier::connector::remove_object(configuration::connector const& obj) { void applier::connector::resolve_object(configuration::connector const& obj [[maybe_unused]], error_cnt& err [[maybe_unused]]) {} +#else +/** + * @brief Resolve a connector. + * + * Connector objects do not need resolution. Therefore this method does + * nothing. + * + * @param[in] obj Unused. + */ +void applier::connector::resolve_object(const configuration::Connector&, + error_cnt& err[[maybe_unused]]) {} +#endif diff --git a/engine/src/configuration/applier/contact.cc b/engine/src/configuration/applier/contact.cc index b96e9a03880..cfca3d71dda 100644 --- a/engine/src/configuration/applier/contact.cc +++ b/engine/src/configuration/applier/contact.cc @@ -31,23 +31,7 @@ using namespace com::centreon; using namespace com::centreon::engine; using namespace com::centreon::engine::configuration; -/** - * Check if the contact group name matches the configuration object. - */ -class contactgroup_name_comparator { - public: - contactgroup_name_comparator(std::string const& contactgroup_name) { - _contactgroup_name = contactgroup_name; - } - - bool operator()(std::shared_ptr<configuration::contactgroup> cg) { - return _contactgroup_name == cg->contactgroup_name(); - } - - private: - std::string _contactgroup_name; -}; - +#ifdef LEGACY_CONF /** * Add new contact. * @@ -67,14 +51,9 @@ void applier::contact::add_object(configuration::contact const& obj) { config->contacts().insert(obj); // Create address list. - std::array<std::string, MAX_CONTACT_ADDRESSES> addresses; - { - unsigned int i{0}; - for (tab_string::const_iterator it(obj.address().begin()), - end(obj.address().end()); - it != end; ++it, ++i) - addresses[i] = *it; - } + std::vector<std::string> addresses; + std::copy(obj.address().begin(), obj.address().end(), + std::back_inserter(addresses)); // Create contact. std::shared_ptr<com::centreon::engine::contact> c(add_contact( @@ -113,12 +92,80 @@ void applier::contact::add_object(configuration::contact const& obj) { if (it->second.is_sent()) { timeval tv(get_broker_timestamp(nullptr)); broker_custom_variable(NEBTYPE_CONTACTCUSTOMVARIABLE_ADD, c.get(), - it->first.c_str(), it->second.value().c_str(), - &tv); + it->first, it->second.value(), &tv); } } } +#else +/** + * Add new contact. + * + * @param[in] obj The new contact to add into the monitoring engine. + */ +void applier::contact::add_object(const configuration::Contact& obj) { + // Make sure we have the data we need. + if (obj.contact_name().empty()) + throw engine_error() << "Could not register contact with an empty name"; + // Logging. + config_logger->debug("Creating new contact '{}'.", obj.contact_name()); + + // Add contact to the global configuration set. + configuration::Contact* ct_cfg = pb_config.add_contacts(); + ct_cfg->CopyFrom(obj); + + // Create address list. + std::vector<std::string> addresses; + std::copy(obj.address().begin(), obj.address().end(), + std::back_inserter(addresses)); + + // Create contact. + std::shared_ptr<com::centreon::engine::contact> c(add_contact( + obj.contact_name(), obj.alias(), obj.email(), obj.pager(), addresses, + obj.service_notification_period(), obj.host_notification_period(), + static_cast<bool>(obj.service_notification_options() & action_svc_ok), + static_cast<bool>(obj.service_notification_options() & + action_svc_critical), + static_cast<bool>(obj.service_notification_options() & + action_svc_warning), + static_cast<bool>(obj.service_notification_options() & + action_svc_unknown), + static_cast<bool>(obj.service_notification_options() & + action_svc_flapping), + static_cast<bool>(obj.service_notification_options() & + action_svc_downtime), + static_cast<bool>(obj.host_notification_options() & action_hst_up), + static_cast<bool>(obj.host_notification_options() & action_hst_down), + static_cast<bool>(obj.host_notification_options() & + action_hst_unreachable), + static_cast<bool>(obj.host_notification_options() & action_hst_flapping), + static_cast<bool>(obj.host_notification_options() & action_hst_downtime), + obj.host_notifications_enabled(), obj.service_notifications_enabled(), + obj.can_submit_commands(), obj.retain_status_information(), + obj.retain_nonstatus_information())); + if (!c) + throw engine_error() << "Could not register contact '" << obj.contact_name() + << "'"; + c->set_timezone(obj.timezone()); + + // Add new items to the configuration state. + engine::contact::contacts.insert({c->get_name(), c}); + + // Add all custom variables. + for (const configuration::CustomVariable& cv : obj.customvariables()) { + c->get_custom_variables()[cv.name()] = + engine::customvariable(cv.value(), cv.is_sent()); + + if (cv.is_sent()) { + timeval tv(get_broker_timestamp(nullptr)); + broker_custom_variable(NEBTYPE_CONTACTCUSTOMVARIABLE_ADD, c.get(), + cv.name(), cv.value(), &tv); + } + } +} +#endif + +#ifdef LEGACY_CONF /** * Modified contact. * @@ -255,8 +302,7 @@ void applier::contact::modify_object(configuration::contact const& obj) { if (cus.second.is_sent()) { timeval tv(get_broker_timestamp(nullptr)); broker_custom_variable(NEBTYPE_CONTACTCUSTOMVARIABLE_DELETE, c, - cus.first.c_str(), cus.second.value().c_str(), - &tv); + cus.first, cus.second.value(), &tv); } } c->get_custom_variables().clear(); @@ -267,13 +313,207 @@ void applier::contact::modify_object(configuration::contact const& obj) { cv.set_sent(cus.second.is_sent()); if (cus.second.is_sent()) { + timeval tv(get_broker_timestamp(nullptr)); + broker_custom_variable(NEBTYPE_CONTACTCUSTOMVARIABLE_ADD, c, cus.first, + cus.second.value(), &tv); + } + } + } + + // Notify event broker. + timeval tv(get_broker_timestamp(NULL)); + broker_adaptive_contact_data(NEBTYPE_CONTACT_UPDATE, NEBFLAG_NONE, + NEBATTR_NONE, c, CMD_NONE, MODATTR_ALL, + MODATTR_ALL, MODATTR_ALL, MODATTR_ALL, + MODATTR_ALL, MODATTR_ALL, &tv); +} +#else +/** + * @brief Modify a contact from a protobuf contact configuration. + * + * @param obj The new contact to modify into the monitoring engine. + */ +void applier::contact::modify_object(configuration::Contact* to_modify, + const configuration::Contact& new_object) { + // Logging. + config_logger->debug("Modifying contact '{}'.", new_object.contact_name()); + + // Find contact object. + contact_map::iterator it_obj( + engine::contact::contacts.find(to_modify->contact_name())); + if (it_obj == engine::contact::contacts.end()) + throw engine_error() << fmt::format( + "Could not modify non-existing contact object '{}'", + to_modify->contact_name()); + engine::contact* c = it_obj->second.get(); + + // Update the global configuration set. + to_modify->CopyFrom(new_object); + + // Modify contact. + const std::string& tmp(new_object.alias().empty() ? new_object.contact_name() + : new_object.alias()); + if (c->get_alias() != tmp) + c->set_alias(tmp); + if (c->get_email() != new_object.email()) + c->set_email(new_object.email()); + if (c->get_pager() != new_object.pager()) + c->set_pager(new_object.pager()); + + std::vector<std::string> addr; + addr.reserve(new_object.address().size()); + std::copy(new_object.address().begin(), new_object.address().end(), + std::back_inserter(addr)); + c->set_addresses(std::move(addr)); + + c->set_notify_on( + notifier::service_notification, + (new_object.service_notification_options() & action_svc_unknown + ? notifier::unknown + : notifier::none) | + (new_object.service_notification_options() & action_svc_warning + ? notifier::warning + : notifier::none) | + (new_object.service_notification_options() & action_svc_critical + ? notifier::critical + : notifier::none) | + (new_object.service_notification_options() & action_svc_ok + ? notifier::ok + : notifier::none) | + (new_object.service_notification_options() & action_svc_flapping + ? (notifier::flappingstart | notifier::flappingstop | + notifier::flappingdisabled) + : notifier::none) | + (new_object.service_notification_options() & action_svc_downtime + ? notifier::downtime + : notifier::none)); + c->set_notify_on( + notifier::host_notification, + (new_object.host_notification_options() & action_hst_down + ? notifier::down + : notifier::none) | + (new_object.host_notification_options() & action_hst_unreachable + ? notifier::unreachable + : notifier::none) | + (new_object.host_notification_options() & action_hst_up + ? notifier::up + : notifier::none) | + (new_object.host_notification_options() & action_hst_flapping + ? (notifier::flappingstart | notifier::flappingstop | + notifier::flappingdisabled) + : notifier::none) | + (new_object.host_notification_options() & action_hst_downtime + ? notifier::downtime + : notifier::none)); + if (c->get_host_notification_period() != + new_object.host_notification_period()) + c->set_host_notification_period(new_object.host_notification_period()); + if (c->get_service_notification_period() != + new_object.service_notification_period()) + c->set_service_notification_period( + new_object.service_notification_period()); + if (c->get_host_notifications_enabled() != + new_object.host_notifications_enabled()) + c->set_host_notifications_enabled(new_object.host_notifications_enabled()); + if (c->get_service_notifications_enabled() != + new_object.service_notifications_enabled()) + c->set_service_notifications_enabled( + new_object.service_notifications_enabled()); + if (c->get_can_submit_commands() != new_object.can_submit_commands()) + c->set_can_submit_commands(new_object.can_submit_commands()); + if (c->get_retain_status_information() != + new_object.retain_status_information()) + c->set_retain_status_information(new_object.retain_status_information()); + if (c->get_retain_nonstatus_information() != + new_object.retain_nonstatus_information()) + c->set_retain_nonstatus_information( + new_object.retain_nonstatus_information()); + c->set_timezone(new_object.timezone()); + + // Host notification commands. + if (!MessageDifferencer::Equals(new_object.host_notification_commands(), + to_modify->host_notification_commands())) { + c->get_host_notification_commands().clear(); + + for (auto& cfg_c : new_object.host_notification_commands().data()) { + command_map::const_iterator itt = commands::command::commands.find(cfg_c); + if (itt != commands::command::commands.end()) + c->get_host_notification_commands().push_back(itt->second); + else + throw engine_error() << fmt::format( + "Could not add host notification command '{}' to contact '{}': the " + "command does not exist", + cfg_c, new_object.contact_name()); + } + } + + // Service notification commands. + if (!MessageDifferencer::Equals(new_object.service_notification_commands(), + to_modify->service_notification_commands())) { + c->get_service_notification_commands().clear(); + + for (auto& cfg_c : new_object.service_notification_commands().data()) { + command_map::const_iterator itt = commands::command::commands.find(cfg_c); + if (itt != commands::command::commands.end()) + c->get_service_notification_commands().push_back(itt->second); + else + throw engine_error() << fmt::format( + "Could not add service notification command '{}' to contact '{}': " + "the command does not exist", + cfg_c, new_object.contact_name()); + } + } + + // Custom variables. + absl::flat_hash_set<std::string> keys; + for (auto& cfg_cv : new_object.customvariables()) { + keys.emplace(cfg_cv.name()); + auto found = c->get_custom_variables().find(cfg_cv.name()); + if (found != c->get_custom_variables().end()) { + if (found->second.value() != cfg_cv.value() || + found->second.is_sent() != cfg_cv.is_sent() || + found->second.has_been_modified()) { + found->second.set_value(cfg_cv.value()); + found->second.set_sent(cfg_cv.is_sent()); + if (found->second.is_sent()) { + timeval tv(get_broker_timestamp(nullptr)); + broker_custom_variable(NEBTYPE_CONTACTCUSTOMVARIABLE_DELETE, c, + found->first.c_str(), + found->second.value().c_str(), &tv); + broker_custom_variable(NEBTYPE_CONTACTCUSTOMVARIABLE_ADD, c, + found->first.c_str(), + found->second.value().c_str(), &tv); + } + } + } else { + c->get_custom_variables().emplace( + cfg_cv.name(), + engine::customvariable(cfg_cv.value(), cfg_cv.is_sent())); + if (cfg_cv.is_sent()) { timeval tv(get_broker_timestamp(nullptr)); broker_custom_variable(NEBTYPE_CONTACTCUSTOMVARIABLE_ADD, c, - cus.first.c_str(), cus.second.value().c_str(), + cfg_cv.name().c_str(), cfg_cv.value().c_str(), &tv); } } } + if (static_cast<uint32_t>(new_object.customvariables().size()) != + c->get_custom_variables().size()) { + // There are custom variables to remove... + for (auto it = c->get_custom_variables().begin(); + it != c->get_custom_variables().end();) { + if (!keys.contains(it->first)) { + if (it->second.is_sent()) { + timeval tv(get_broker_timestamp(nullptr)); + broker_custom_variable(NEBTYPE_CONTACTCUSTOMVARIABLE_DELETE, c, + it->first.c_str(), it->second.value().c_str(), + &tv); + } + it = c->get_custom_variables().erase(it); + } else + ++it; + } + } // Notify event broker. timeval tv(get_broker_timestamp(NULL)); @@ -282,7 +522,9 @@ void applier::contact::modify_object(configuration::contact const& obj) { MODATTR_ALL, MODATTR_ALL, MODATTR_ALL, MODATTR_ALL, MODATTR_ALL, &tv); } +#endif +#ifdef LEGACY_CONF /** * Remove old contact. * @@ -316,7 +558,43 @@ void applier::contact::remove_object(configuration::contact const& obj) { // Remove contact from the global configuration set. config->contacts().erase(obj); } +#else +/** + * Remove old contact. + * + * @param[in] obj The new contact to remove from the monitoring engine. + */ +void applier::contact::remove_object(ssize_t idx) { + const configuration::Contact& obj = pb_config.contacts()[idx]; + + // Logging. + config_logger->debug("Removing contact '{}'.", obj.contact_name()); + // Find contact. + contact_map::iterator it{engine::contact::contacts.find(obj.contact_name())}; + if (it != engine::contact::contacts.end()) { + engine::contact* cntct(it->second.get()); + + for (auto& it_c : cntct->get_parent_groups()) + it_c.second->get_members().erase(obj.contact_name()); + + // Notify event broker. + timeval tv(get_broker_timestamp(nullptr)); + broker_adaptive_contact_data(NEBTYPE_CONTACT_DELETE, NEBFLAG_NONE, + NEBATTR_NONE, cntct, CMD_NONE, MODATTR_ALL, + MODATTR_ALL, MODATTR_ALL, MODATTR_ALL, + MODATTR_ALL, MODATTR_ALL, &tv); + + // Erase contact object (this will effectively delete the object). + engine::contact::contacts.erase(it); + } + + // Remove contact from the global configuration set. + pb_config.mutable_contacts()->DeleteSubrange(idx, 1); +} +#endif + +#ifdef LEGACY_CONF /** * @brief Expand a contact. * @@ -365,7 +643,50 @@ void applier::contact::expand_objects(configuration::state& s) { } s.contacts() = std::move(new_contacts); } +#else +/** + * @brief Expand a contact. + * + * During expansion, the contact will be added to its contact groups. + * These will be modified in the state. + * + * @param[in,out] s Configuration state. + */ +void applier::contact::expand_objects(configuration::State& s) { + // Let's consider all the macros defined in s. + absl::flat_hash_set<std::string_view> cvs; + for (auto& cv : s.macros_filter().data()) + cvs.emplace(cv); + + // Browse all contacts. + for (auto& c : *s.mutable_contacts()) { + // Should custom variables be sent to broker ? + for (auto& cv : *c.mutable_customvariables()) { + if (!s.enable_macros_filter() || cvs.contains(cv.name())) + cv.set_is_sent(true); + } + + // Browse current contact's groups. + for (auto& cg : *c.mutable_contactgroups()->mutable_data()) { + // Find contact group. + Contactgroup* found_cg = nullptr; + for (auto& cgg : *s.mutable_contactgroups()) + if (cgg.contactgroup_name() == cg) { + found_cg = &cgg; + break; + } + if (found_cg == nullptr) + throw engine_error() << fmt::format( + "Could not add contact '{}' to non-existing contact group '{}'", + c.contact_name(), cg); + + fill_string_group(found_cg->mutable_members(), c.contact_name()); + } + } +} +#endif +#ifdef LEGACY_CONF /** * Resolve a contact. * @@ -426,3 +747,59 @@ void applier::contact::resolve_object(const configuration::contact& obj, // Resolve contact. ct_it->second->resolve(err.config_warnings, err.config_errors); } +#else +/** + * Resolve a contact. + * + * @param[in,out] obj Object to resolve. + */ +void applier::contact::resolve_object(const configuration::Contact& obj, error_cnt& err) { + // Logging. + config_logger->debug("Resolving contact '{}'.", obj.contact_name()); + + // Find contact. + contact_map::const_iterator ct_it{ + engine::contact::contacts.find(obj.contact_name())}; + if (ct_it == engine::contact::contacts.end() || !ct_it->second) + throw engine_error() << fmt::format( + "Cannot resolve non-existing contact '{}'", obj.contact_name()); + + ct_it->second->get_host_notification_commands().clear(); + + // Add all the host notification commands. + for (auto& cmd : obj.host_notification_commands().data()) { + command_map::const_iterator itt(commands::command::commands.find(cmd)); + if (itt != commands::command::commands.end()) + ct_it->second->get_host_notification_commands().push_back(itt->second); + else { + ++err.config_errors; + throw engine_error() << fmt::format( + "Could not add host notification command '{}' to contact '{}': the " + "command does not exist", + cmd, obj.contact_name()); + } + } + + ct_it->second->get_service_notification_commands().clear(); + + // Add all the service notification commands. + for (auto& cmd : obj.service_notification_commands().data()) { + command_map::const_iterator itt(commands::command::commands.find(cmd)); + if (itt != commands::command::commands.end()) + ct_it->second->get_service_notification_commands().push_back(itt->second); + else { + ++err.config_errors; + throw engine_error() << fmt::format( + "Could not add service notification command '{}' to contact '{}': " + "the command does not exist", + cmd, obj.contact_name()); + } + } + + // Remove contact group links. + ct_it->second->get_parent_groups().clear(); + + // Resolve contact. + ct_it->second->resolve(err.config_warnings, err.config_errors); +} +#endif diff --git a/engine/src/configuration/applier/contactgroup.cc b/engine/src/configuration/applier/contactgroup.cc index 38c8522ccbd..bf872426fd6 100644 --- a/engine/src/configuration/applier/contactgroup.cc +++ b/engine/src/configuration/applier/contactgroup.cc @@ -29,6 +29,7 @@ using namespace com::centreon::engine::configuration; using namespace com::centreon::engine::logging; +#ifdef LEGACY_CONF /** * Add new contactgroup * @@ -74,7 +75,49 @@ void applier::contactgroup::add_object(configuration::contactgroup const& obj) { engine::contactgroup::contactgroups.insert({name, cg}); } +#else +/** + * Add new contactgroup + * + * @param[in] obj The new contactgroup to add into the monitoring engine. + */ +void applier::contactgroup::add_object(const configuration::Contactgroup& obj) { + const std::string& name(obj.contactgroup_name()); + + // Logging. + config_logger->debug("Creating new contactgroup '{}'.", name); + + if (engine::contactgroup::contactgroups.find(name) != + engine::contactgroup::contactgroups.end()) + throw engine_error() << "Contactgroup '" << name + << "' has already been defined"; + + // Add contact group to the global configuration set. + configuration::Contactgroup* c_cg = pb_config.add_contactgroups(); + c_cg->CopyFrom(obj); + // Create contact group. + auto cg = std::make_shared<engine::contactgroup>(obj); + for (auto& member : obj.members().data()) { + auto ct_it{engine::contact::contacts.find(member)}; + if (ct_it == engine::contact::contacts.end()) { + config_logger->error( + "Error: Contact '{}' specified in contact group '{}' is not defined " + "anywhere!", + member, cg->get_name()); + throw engine_error() << "Error: Cannot resolve contact group " + << obj.contactgroup_name() << "'"; + } else { + cg->get_members().insert({ct_it->first, ct_it->second.get()}); + broker_group(NEBTYPE_CONTACTGROUP_ADD, cg.get()); + } + } + + engine::contactgroup::contactgroups.insert({name, cg}); +} +#endif + +#ifdef LEGACY_CONF /** * Expand all contactgroups. * @@ -94,7 +137,21 @@ void applier::contactgroup::expand_objects(configuration::state& s) { it != end; ++it) s.contactgroups().insert(it->second); } +#else +/** + * @brief Expand all contactgroups. + * + * @param s State being applied. + */ +void applier::contactgroup::expand_objects(configuration::State& s) { + absl::flat_hash_set<std::string_view> resolved; + + for (auto& cg : *s.mutable_contactgroups()) + _resolve_members(s, cg, resolved); +} +#endif +#ifdef LEGACY_CONF /** * Modified contactgroup. * @@ -161,7 +218,63 @@ void applier::contactgroup::modify_object( // Notify event broker. broker_group(NEBTYPE_CONTACTGROUP_UPDATE, it_obj->second.get()); } +#else +/** + * @brief Modify a contactgroup configuration. + * + * @param to_modify A pointer to the configuration to modify. + * @param new_object A const reference to the configuration to apply. + */ +void applier::contactgroup::modify_object( + configuration::Contactgroup* to_modify, + const configuration::Contactgroup& new_object) { + // Logging. + config_logger->debug("Modifying contactgroup '{}'", to_modify->contactgroup_name()); + + // Find contact group object. + contactgroup_map::iterator it_obj = + engine::contactgroup::contactgroups.find(new_object.contactgroup_name()); + if (it_obj == engine::contactgroup::contactgroups.end()) + throw engine_error() << fmt::format( + "Error: Could not modify non-existing contact group object '{}", + new_object.contactgroup_name()); + + // Modify properties. + if (it_obj->second->get_alias() != new_object.alias()) { + it_obj->second->set_alias(new_object.alias()); + to_modify->set_alias(new_object.alias()); + } + + if (!MessageDifferencer::Equals(new_object.members(), to_modify->members())) { + // delete all old contact group members + to_modify->mutable_members()->CopyFrom(new_object.members()); + it_obj->second->clear_members(); + + for (auto& contact : new_object.members().data()) { + contact_map::const_iterator ct_it{ + engine::contact::contacts.find(contact)}; + if (ct_it == engine::contact::contacts.end()) { + config_logger->error( + "Error: Contact '{}' specified in contact group '{}' is not " + "defined anywhere!", + contact, it_obj->second->get_name()); + throw engine_error() + << fmt::format("Error: Cannot resolve contact group '{}'", + new_object.contactgroup_name()); + } else { + it_obj->second->get_members().insert( + {ct_it->first, ct_it->second.get()}); + broker_group(NEBTYPE_CONTACTGROUP_ADD, it_obj->second.get()); + } + } + } + + // Notify event broker. + broker_group(NEBTYPE_CONTACTGROUP_UPDATE, it_obj->second.get()); +} +#endif +#ifdef LEGACY_CONF /** * Remove old contactgroup. * @@ -192,7 +305,38 @@ void applier::contactgroup::remove_object( // Remove contact group from the global configuration set. config->contactgroups().erase(obj); } +#else +/** + * @brief Remove an old contactgroup by index. + * + * @param idx The index of the contactgroup configuration to remove. + */ +void applier::contactgroup::remove_object(ssize_t idx) { + const configuration::Contactgroup& obj = pb_config.contactgroups()[idx]; + + // Logging. + config_logger->debug("Removing contactgroup '{}'", obj.contactgroup_name()); + + // Find contact group. + contactgroup_map::iterator it = + engine::contactgroup::contactgroups.find(obj.contactgroup_name()); + if (it != engine::contactgroup::contactgroups.end()) { + // Remove contact group from its list. + // unregister_object<contactgroup>(&contactgroup_list, grp); + // Notify event broker. + broker_group(NEBTYPE_CONTACTGROUP_DELETE, it->second.get()); + + // Remove contact group (this will effectively delete the object). + engine::contactgroup::contactgroups.erase(it); + } + + // Remove contact group from the global configuration set. + pb_config.mutable_contactgroups()->DeleteSubrange(idx, 1); +} +#endif + +#ifdef LEGACY_CONF /** * Resolve a contact group. * @@ -216,7 +360,31 @@ void applier::contactgroup::resolve_object( // Resolve contact group. it->second->resolve(err.config_warnings, err.config_errors); } +#else +/** + * @brief Resolve a contact group. + * + * @param obj Contact group configuration to resolve. + */ +void applier::contactgroup::resolve_object( + const configuration::Contactgroup& obj, error_cnt& err) { + // Logging. + config_logger->debug("Resolving contact group '{}'", obj.contactgroup_name()); + + // Find contact group. + contactgroup_map::iterator it = + engine::contactgroup::contactgroups.find(obj.contactgroup_name()); + if (it == engine::contactgroup::contactgroups.end() || !it->second) + throw engine_error() << fmt::format( + "Error: Cannot resolve non-existing contact group '{}'", + obj.contactgroup_name()); + // Resolve contact group. + it->second->resolve(err.config_warnings, err.config_errors); +} +#endif + +#ifdef LEGACY_CONF /** * Resolve members of a contact group. * @@ -263,3 +431,49 @@ void applier::contactgroup::_resolve_members( } } } +#else +/** + * @brief Resolve members of a contact group. A contact group can be defined + * from others contactgroups. But we only want for engine, contactgroups defined + * with contacts. So if it contains contactgroups, we have to copy their + * contacts into this contactgroup and then empty the contactgroups members. + * + * @param s Configuration being applied. + * @param obj Object that should be processed. + * @param resolved a reference to keep a trace of already expanded + * contactgroups. + */ +void applier::contactgroup::_resolve_members( + configuration::State& s, + configuration::Contactgroup& obj, + absl::flat_hash_set<std::string_view>& resolved) { + if (resolved.contains(obj.contactgroup_name())) + return; + + resolved.emplace(obj.contactgroup_name()); + if (!obj.contactgroup_members().data().empty()) { + // Logging. + config_logger->debug("Resolving members of contact group '{}'", + obj.contactgroup_name()); + for (auto& cg_name : obj.contactgroup_members().data()) { + auto it = std::find_if(s.mutable_contactgroups()->begin(), + s.mutable_contactgroups()->end(), + [&cg_name](const Contactgroup& cg) { + return cg.contactgroup_name() == cg_name; + }); + + if (it == s.mutable_contactgroups()->end()) + throw engine_error() << fmt::format( + "Error: Could not add non-existing contact group member '{}' to " + "contactgroup '{}'", + cg_name, obj.contactgroup_name()); + + Contactgroup& inner_cg = *it; + _resolve_members(s, inner_cg, resolved); + for (auto& c_name : inner_cg.members().data()) + fill_string_group(obj.mutable_members(), c_name); + } + obj.mutable_contactgroup_members()->clear_data(); + } +} +#endif diff --git a/engine/src/configuration/applier/globals.cc b/engine/src/configuration/applier/globals.cc index c85c97079c6..80eed5f5951 100644 --- a/engine/src/configuration/applier/globals.cc +++ b/engine/src/configuration/applier/globals.cc @@ -24,6 +24,7 @@ using namespace com::centreon::engine; using namespace com::centreon::engine::configuration; +#ifdef LEGACY_CONF /** * Apply new configuration. * @@ -67,6 +68,51 @@ void applier::globals::apply(state& config) { ::use_large_installation_tweaks = config.use_large_installation_tweaks(); ::instance_heartbeat_interval = config.instance_heartbeat_interval(); } +#else +/** + * Apply new configuration. + * + * @param[in] config The new configuration. + */ +void applier::globals::apply(State& config) { + _set_global(::debug_file, config.debug_file()); + _set_global(::global_host_event_handler, config.global_host_event_handler()); + _set_global(::global_service_event_handler, + config.global_service_event_handler()); + _set_global(::illegal_object_chars, config.illegal_object_chars()); + _set_global(::illegal_output_chars, config.illegal_output_chars()); + _set_global(::log_file, config.log_file()); + _set_global(::ochp_command, config.ochp_command()); + _set_global(::ocsp_command, config.ocsp_command()); + _set_global(::use_timezone, config.use_timezone()); + + ::accept_passive_host_checks = config.accept_passive_host_checks(); + ::accept_passive_service_checks = config.accept_passive_service_checks(); + ::additional_freshness_latency = config.additional_freshness_latency(); + ::cached_host_check_horizon = config.cached_host_check_horizon(); + ::check_external_commands = config.check_external_commands(); + ::check_host_freshness = config.check_host_freshness(); + ::check_reaper_interval = config.check_reaper_interval(); + ::check_service_freshness = config.check_service_freshness(); + ::enable_event_handlers = config.enable_event_handlers(); + ::enable_flap_detection = config.enable_flap_detection(); + ::enable_notifications = config.enable_notifications(); + ::execute_host_checks = config.execute_host_checks(); + ::execute_service_checks = config.execute_service_checks(); + ::interval_length = config.interval_length(); + ::log_notifications = config.log_notifications(); + ::log_passive_checks = config.log_passive_checks(); + ::max_host_check_spread = config.max_host_check_spread(); + ::max_service_check_spread = config.max_service_check_spread(); + ::notification_timeout = config.notification_timeout(); + ::obsess_over_hosts = config.obsess_over_hosts(); + ::obsess_over_services = config.obsess_over_services(); + ::process_performance_data = config.process_performance_data(); + ::soft_state_dependencies = config.soft_state_dependencies(); + ::use_large_installation_tweaks = config.use_large_installation_tweaks(); + ::instance_heartbeat_interval = config.instance_heartbeat_interval(); +} +#endif /** * Get the singleton instance of globals applier. diff --git a/engine/src/configuration/applier/host.cc b/engine/src/configuration/applier/host.cc index bf0df59a090..3e2b7ec44a1 100644 --- a/engine/src/configuration/applier/host.cc +++ b/engine/src/configuration/applier/host.cc @@ -29,27 +29,24 @@ #include "com/centreon/engine/globals.hh" #include "com/centreon/engine/logging/logger.hh" #include "com/centreon/engine/severity.hh" +#ifdef LEGACY_CONF +#include "common/engine_legacy_conf/host.hh" +#else +#include "common/engine_conf/severity_helper.hh" +#include "common/engine_conf/state.pb.h" +#endif using namespace com::centreon; using namespace com::centreon::engine; using namespace com::centreon::engine::configuration; -/** - * Default constructor. - */ -applier::host::host() {} - -/** - * Destructor. - */ -applier::host::~host() throw() {} - +#ifdef LEGACY_CONF /** * Add new host. * * @param[in] obj The new host to add into the monitoring engine. */ -void applier::host::add_object(configuration::host const& obj) { +void applier::host::add_object(const configuration::host& obj) { // Logging. engine_logger(logging::dbg_config, logging::more) << "Creating new host '" << obj.host_name() << "'."; @@ -128,9 +125,8 @@ void applier::host::add_object(configuration::host const& obj) { if (it->second.is_sent()) { timeval tv(get_broker_timestamp(nullptr)); - broker_custom_variable(NEBTYPE_HOSTCUSTOMVARIABLE_ADD, h.get(), - it->first.c_str(), it->second.value().c_str(), - &tv); + broker_custom_variable(NEBTYPE_HOSTCUSTOMVARIABLE_ADD, h.get(), it->first, + it->second.value(), &tv); } } @@ -170,7 +166,118 @@ void applier::host::add_object(configuration::host const& obj) { broker_adaptive_host_data(NEBTYPE_HOST_ADD, NEBFLAG_NONE, NEBATTR_NONE, h.get(), MODATTR_ALL); } +#else +/** + * Add new host. + * + * @param[in] obj The new host to add into the monitoring engine. + */ +void applier::host::add_object(const configuration::Host& obj) { + // Logging. + config_logger->debug("Creating new host '{}'.", obj.host_name()); + + // Add host to the global configuration set. + auto* cfg_obj = pb_config.add_hosts(); + cfg_obj->CopyFrom(obj); + + // Create host. + auto h = std::make_shared<com::centreon::engine::host>( + obj.host_id(), obj.host_name(), obj.display_name(), obj.alias(), + obj.address(), obj.check_period(), + static_cast<engine::host::host_state>(obj.initial_state()), + obj.check_interval(), obj.retry_interval(), obj.max_check_attempts(), + static_cast<bool>(obj.notification_options() & action_hst_up), + static_cast<bool>(obj.notification_options() & action_hst_down), + static_cast<bool>(obj.notification_options() & action_hst_unreachable), + static_cast<bool>(obj.notification_options() & action_hst_flapping), + static_cast<bool>(obj.notification_options() & action_hst_downtime), + obj.notification_interval(), obj.first_notification_delay(), + obj.recovery_notification_delay(), obj.notification_period(), + obj.notifications_enabled(), obj.check_command(), obj.checks_active(), + obj.checks_passive(), obj.event_handler(), obj.event_handler_enabled(), + obj.flap_detection_enabled(), obj.low_flap_threshold(), + obj.high_flap_threshold(), + static_cast<bool>(obj.flap_detection_options() & action_hst_up), + static_cast<bool>(obj.flap_detection_options() & action_hst_down), + static_cast<bool>(obj.flap_detection_options() & action_hst_unreachable), + static_cast<bool>(obj.stalking_options() & action_hst_up), + static_cast<bool>(obj.stalking_options() & action_hst_down), + static_cast<bool>(obj.stalking_options() & action_hst_unreachable), + obj.process_perf_data(), obj.check_freshness(), obj.freshness_threshold(), + obj.notes(), obj.notes_url(), obj.action_url(), obj.icon_image(), + obj.icon_image_alt(), obj.vrml_image(), obj.statusmap_image(), + obj.coords_2d().x(), obj.coords_2d().y(), obj.has_coords_2d(), + obj.coords_3d().x(), obj.coords_3d().y(), obj.coords_3d().z(), + obj.has_coords_3d(), + true, // should_be_drawn, enabled by Nagios + obj.retain_status_information(), obj.retain_nonstatus_information(), + obj.obsess_over_host(), obj.timezone(), obj.icon_id()); + + engine::host::hosts.insert({h->name(), h}); + engine::host::hosts_by_id.insert({obj.host_id(), h}); + + h->set_initial_notif_time(0); + h->set_should_reschedule_current_check(false); + h->set_host_id(obj.host_id()); + h->set_acknowledgement_timeout(obj.acknowledgement_timeout() * + pb_config.interval_length()); + h->set_last_acknowledgement(0); + + // Contacts + for (auto& c : obj.contacts().data()) + h->mut_contacts().insert({c, nullptr}); + + // Contact groups. + for (auto& cg : obj.contactgroups().data()) + h->get_contactgroups().insert({cg, nullptr}); + + // Custom variables. + for (auto& cv : obj.customvariables()) { + h->custom_variables[cv.name()] = + engine::customvariable(cv.value(), cv.is_sent()); + + if (cv.is_sent()) { + timeval tv(get_broker_timestamp(nullptr)); + broker_custom_variable(NEBTYPE_HOSTCUSTOMVARIABLE_ADD, h.get(), cv.name(), + cv.value(), &tv); + } + } + + // add tags + for (auto& t : obj.tags()) { + auto p = std::make_pair(t.first(), t.second()); + tag_map::iterator it_tag{engine::tag::tags.find(p)}; + if (it_tag == engine::tag::tags.end()) + throw engine_error() << "Could not find tag '" << t.first() + << "' on which to apply host (" << obj.host_id() + << ")"; + else + h->mut_tags().emplace_front(it_tag->second); + } + // Parents. + for (auto& p : obj.parents().data()) + h->add_parent_host(p); + + // Add severity. + if (obj.severity_id()) { + configuration::severity_helper::key_type k = {obj.severity_id(), + SeverityType::host}; + auto sv = engine::severity::severities.find(k); + if (sv == engine::severity::severities.end()) + throw engine_error() << "Could not add the severity (" << k.first << ", " + << k.second << ") to the host '" << obj.host_name() + << "'"; + h->set_severity(sv->second); + } + + // Notify event broker. + broker_adaptive_host_data(NEBTYPE_HOST_ADD, NEBFLAG_NONE, NEBATTR_NONE, + h.get(), MODATTR_ALL); +} +#endif + +#ifdef LEGACY_CONF /** * Modified host. * @@ -335,8 +442,8 @@ void applier::host::modify_object(configuration::host const& obj) { if (c.second.is_sent()) { timeval tv(get_broker_timestamp(nullptr)); broker_custom_variable(NEBTYPE_HOSTCUSTOMVARIABLE_DELETE, - it_obj->second.get(), c.first.c_str(), - c.second.value().c_str(), &tv); + it_obj->second.get(), c.first, c.second.value(), + &tv); } } it_obj->second->custom_variables.clear(); @@ -348,8 +455,8 @@ void applier::host::modify_object(configuration::host const& obj) { if (c.second.is_sent()) { timeval tv(get_broker_timestamp(nullptr)); broker_custom_variable(NEBTYPE_HOSTCUSTOMVARIABLE_ADD, - it_obj->second.get(), c.first.c_str(), - c.second.value().c_str(), &tv); + it_obj->second.get(), c.first, c.second.value(), + &tv); } } } @@ -407,7 +514,249 @@ void applier::host::modify_object(configuration::host const& obj) { broker_adaptive_host_data(NEBTYPE_HOST_UPDATE, NEBFLAG_NONE, NEBATTR_NONE, it_obj->second.get(), MODATTR_ALL); } +#else +/** + * Modified host. + * + * @param[in] obj The new host to modify into the monitoring engine. + */ +void applier::host::modify_object(configuration::Host* old_obj, + const configuration::Host& new_obj) { + // Logging. + config_logger->debug("Modifying host '{}' (id {}).", new_obj.host_name(), + new_obj.host_id()); + + // Find host object. + host_id_map::iterator it_obj = + engine::host::hosts_by_id.find(new_obj.host_id()); + if (it_obj == engine::host::hosts_by_id.end()) + throw engine_error() << fmt::format( + "Could not modify non-existing host object '{}' (id {})", + new_obj.host_name(), new_obj.host_id()); + std::shared_ptr<engine::host> h = it_obj->second; + + // Modify properties. + if (h->name() != new_obj.host_name()) { + engine::host::hosts.erase(h->name()); + engine::host::hosts.insert({new_obj.host_name(), h}); + } + + h->set_name(new_obj.host_name()); + h->set_display_name(new_obj.display_name()); + if (!new_obj.alias().empty()) + h->set_alias(new_obj.alias()); + else + h->set_alias(new_obj.host_name()); + h->set_address(new_obj.address()); + if (new_obj.check_period().empty()) + h->set_check_period(new_obj.check_period()); + h->set_initial_state( + static_cast<engine::host::host_state>(new_obj.initial_state())); + h->set_check_interval(static_cast<double>(new_obj.check_interval())); + h->set_retry_interval(static_cast<double>(new_obj.retry_interval())); + h->set_max_attempts(static_cast<int>(new_obj.max_check_attempts())); + h->set_notify_on( + (new_obj.notification_options() & action_hst_up ? notifier::up + : notifier::none) | + (new_obj.notification_options() & action_hst_down ? notifier::down + : notifier::none) | + (new_obj.notification_options() & action_hst_unreachable + ? notifier::unreachable + : notifier::none) | + (new_obj.notification_options() & action_hst_flapping + ? (notifier::flappingstart | notifier::flappingstop | + notifier::flappingdisabled) + : notifier::none) | + (new_obj.notification_options() & action_hst_downtime ? notifier::downtime + : notifier::none)); + h->set_notification_interval( + static_cast<double>(new_obj.notification_interval())); + h->set_first_notification_delay( + static_cast<double>(new_obj.first_notification_delay())); + h->set_notification_period(new_obj.notification_period()); + h->set_notifications_enabled( + static_cast<int>(new_obj.notifications_enabled())); + h->set_check_command(new_obj.check_command()); + h->set_checks_enabled(static_cast<int>(new_obj.checks_active())); + h->set_accept_passive_checks(static_cast<int>(new_obj.checks_passive())); + h->set_event_handler(new_obj.event_handler()); + h->set_event_handler_enabled( + static_cast<int>(new_obj.event_handler_enabled())); + h->set_flap_detection_enabled(new_obj.flap_detection_enabled()); + h->set_low_flap_threshold(new_obj.low_flap_threshold()); + h->set_high_flap_threshold(new_obj.high_flap_threshold()); + h->set_flap_detection_on(notifier::none); + h->add_flap_detection_on(new_obj.flap_detection_options() & action_hst_up + ? notifier::up + : notifier::none); + h->add_flap_detection_on(new_obj.flap_detection_options() & action_hst_down + ? notifier::down + : notifier::none); + h->add_flap_detection_on(new_obj.flap_detection_options() & + action_hst_unreachable + ? notifier::unreachable + : notifier::none); + h->add_stalk_on(new_obj.stalking_options() & action_hst_up ? notifier::up + : notifier::none); + h->add_stalk_on(new_obj.stalking_options() & action_hst_down + ? notifier::down + : notifier::none); + h->add_stalk_on(new_obj.stalking_options() & action_hst_unreachable + ? notifier::unreachable + : notifier::none); + h->set_process_performance_data( + static_cast<int>(new_obj.process_perf_data())); + h->set_check_freshness(static_cast<int>(new_obj.check_freshness())); + h->set_freshness_threshold(static_cast<int>(new_obj.freshness_threshold())); + h->set_notes(new_obj.notes()); + h->set_notes_url(new_obj.notes_url()); + h->set_action_url(new_obj.action_url()); + h->set_icon_image(new_obj.icon_image()); + h->set_icon_image_alt(new_obj.icon_image_alt()); + h->set_vrml_image(new_obj.vrml_image()); + h->set_statusmap_image(new_obj.statusmap_image()); + h->set_x_2d(new_obj.coords_2d().x()); + h->set_y_2d(new_obj.coords_2d().y()); + h->set_have_2d_coords(static_cast<int>(new_obj.has_coords_2d())); + h->set_x_3d(new_obj.coords_3d().x()); + h->set_y_3d(new_obj.coords_3d().y()); + h->set_z_3d(new_obj.coords_3d().z()); + h->set_have_3d_coords(static_cast<int>(new_obj.has_coords_3d())); + h->set_retain_status_information( + static_cast<int>(new_obj.retain_status_information())); + h->set_retain_nonstatus_information( + static_cast<int>(new_obj.retain_nonstatus_information())); + h->set_obsess_over(new_obj.obsess_over_host()); + h->set_timezone(new_obj.timezone()); + h->set_host_id(new_obj.host_id()); + h->set_acknowledgement_timeout(new_obj.acknowledgement_timeout() * + pb_config.interval_length()); + h->set_recovery_notification_delay(new_obj.recovery_notification_delay()); + + // Contacts. + if (!MessageDifferencer::Equals(new_obj.contacts(), old_obj->contacts())) { + // Delete old contacts. + h->mut_contacts().clear(); + + // Add contacts to host. + for (auto& c : new_obj.contacts().data()) + h->mut_contacts().insert({c, nullptr}); + } + + // Contact groups. + if (!MessageDifferencer::Equals(new_obj.contactgroups(), + old_obj->contactgroups())) { + // Delete old contact groups. + h->get_contactgroups().clear(); + + // Add contact groups to host. + for (auto& cg : new_obj.contactgroups().data()) + h->get_contactgroups().insert({cg, nullptr}); + } + + // Custom variables. + if (!std::equal( + new_obj.customvariables().begin(), new_obj.customvariables().end(), + old_obj->customvariables().begin(), MessageDifferencer::Equals)) { + for (auto& cv : h->custom_variables) { + if (cv.second.is_sent()) { + timeval tv(get_broker_timestamp(nullptr)); + broker_custom_variable(NEBTYPE_HOSTCUSTOMVARIABLE_DELETE, h.get(), + cv.first, cv.second.value(), &tv); + } + } + h->custom_variables.clear(); + + for (auto& c : new_obj.customvariables()) { + h->custom_variables[c.name()] = c.value(); + + if (c.is_sent()) { + timeval tv(get_broker_timestamp(nullptr)); + broker_custom_variable(NEBTYPE_HOSTCUSTOMVARIABLE_ADD, h.get(), + c.name(), c.value(), &tv); + } + } + } + + // add tags + bool tags_changed = false; + if (old_obj->tags().size() == new_obj.tags().size()) { + for (auto new_it = new_obj.tags().begin(), old_it = old_obj->tags().begin(); + old_it != old_obj->tags().end() && new_it != new_obj.tags().end(); + ++old_it, ++new_it) { + if (new_it->first() != old_it->first() || + new_it->second() != old_it->second()) { + tags_changed = true; + break; + } + } + } else + tags_changed = true; + + if (tags_changed) { + h->mut_tags().clear(); + old_obj->mutable_tags()->CopyFrom(new_obj.tags()); + for (auto& t : new_obj.tags()) { + tag_map::iterator it_tag = + engine::tag::tags.find({t.first(), t.second()}); + if (it_tag == engine::tag::tags.end()) + throw engine_error() + << fmt::format("Could not find tag '{}' on which to apply host {}", + t.first(), new_obj.host_id()); + else + h->mut_tags().emplace_front(it_tag->second); + } + } + + // Parents. + bool parents_changed = false; + if (old_obj->parents().data().size() == new_obj.parents().data().size()) { + for (auto new_it = new_obj.parents().data().begin(), + old_it = old_obj->parents().data().begin(); + old_it != old_obj->parents().data().end() && + new_it != new_obj.parents().data().end(); + ++old_it, ++new_it) { + if (*new_it != *old_it) { + parents_changed = true; + break; + } + } + } else + parents_changed = true; + + if (parents_changed) { + // Delete old parents. + for (auto it = h->parent_hosts.begin(), end = h->parent_hosts.end(); + it != end; it++) + broker_relation_data(NEBTYPE_PARENT_DELETE, it->second, nullptr, h.get(), + nullptr); + h->parent_hosts.clear(); + + // Create parents. + for (auto& parent_name : new_obj.parents().data()) + h->add_parent_host(parent_name); + } + + // Severity. + if (new_obj.severity_id()) { + configuration::severity_helper::key_type k = {new_obj.severity_id(), + SeverityType::host}; + auto sv = engine::severity::severities.find(k); + if (sv == engine::severity::severities.end()) + throw engine_error() << "Could not update the severity (" << k.first + << ", " << k.second << ") to the host '" + << new_obj.host_name() << "'"; + h->set_severity(sv->second); + } else + h->set_severity(nullptr); + + // Notify event broker. + broker_adaptive_host_data(NEBTYPE_HOST_UPDATE, NEBFLAG_NONE, NEBATTR_NONE, + it_obj->second.get(), MODATTR_ALL); +} +#endif +#ifdef LEGACY_CONF /** * Remove old host. * @@ -454,7 +803,55 @@ void applier::host::remove_object(configuration::host const& obj) { // Remove host from the global configuration set. config->hosts().erase(obj); } +#else +/** + * Remove old host. + * + * @param[in] obj The new host to remove from the monitoring engine. + */ +void applier::host::remove_object(ssize_t idx) { + const Host& obj = pb_config.hosts()[idx]; + // Logging. + config_logger->debug("Removing host '{}'.", obj.host_name()); + + // Find host. + host_id_map::iterator it(engine::host::hosts_by_id.find(obj.host_id())); + if (it != engine::host::hosts_by_id.end()) { + // Remove host comments. + comment::delete_host_comments(obj.host_id()); + + // Remove host downtimes. + downtimes::downtime_manager::instance() + .delete_downtime_by_hostname_service_description_start_time_comment( + obj.host_name(), "", {false, (time_t)0}, ""); + + // Remove events related to this host. + applier::scheduler::instance().remove_host(obj.host_id()); + + // remove host from hostgroup->members + for (auto& it_h : it->second->get_parent_groups()) + it_h->members.erase(it->second->name()); + + // Notify event broker. + for (auto it_s = it->second->services.begin(); + it_s != it->second->services.end(); ++it_s) + broker_adaptive_service_data(NEBTYPE_SERVICE_DELETE, NEBFLAG_NONE, + NEBATTR_NONE, it_s->second, MODATTR_ALL); + broker_adaptive_host_data(NEBTYPE_HOST_DELETE, NEBFLAG_NONE, NEBATTR_NONE, + it->second.get(), MODATTR_ALL); + + // Erase host object (will effectively delete the object). + engine::host::hosts.erase(it->second->name()); + engine::host::hosts_by_id.erase(it); + } + + // Remove host from the global configuration set. + pb_config.mutable_hosts()->DeleteSubrange(idx, 1); +} +#endif + +#ifdef LEGACY_CONF /** * Resolve a host. * @@ -497,7 +894,50 @@ void applier::host::resolve_object(const configuration::host& obj, // Resolve host. it->second->resolve(err.config_warnings, err.config_errors); } +#else +/** + * @brief Resolve a host. + * + * @param obj Host protobuf configuration object. + */ +void applier::host::resolve_object(const configuration::Host& obj, + error_cnt& err) { + // Logging. + config_logger->debug("Resolving host '{}'.", obj.host_name()); + // If it is the very first host to be resolved, + // remove all the child backlinks of all the hosts. + // It is necessary to do it only once to prevent the removal + // of valid child backlinks. + if (&obj == &(*pb_config.hosts().begin())) { + for (host_map::iterator it(engine::host::hosts.begin()), + end(engine::host::hosts.end()); + it != end; ++it) + it->second->child_hosts.clear(); + } + + // Find host. + host_id_map::iterator it = engine::host::hosts_by_id.find(obj.host_id()); + if (engine::host::hosts_by_id.end() == it) + throw engine_error() << fmt::format("Cannot resolve non-existing host '{}'", + obj.host_name()); + + // Remove service backlinks. + it->second->services.clear(); + + // Remove host group links. + it->second->get_parent_groups().clear(); + + // Reset host counters. + it->second->set_total_services(0); + it->second->set_total_service_check_interval(0); + + // Resolve host. + it->second->resolve(err.config_warnings, err.config_errors); +} +#endif + +#ifdef LEGACY_CONF /** * @brief Expand a host. * @@ -546,3 +986,42 @@ void applier::host::expand_objects(configuration::state& s) { } s.hosts() = std::move(new_hosts); } +#else +/** + * @brief Expand a host. + * + * During expansion, the host will be added to its host groups. These + * will be modified in the state. + * + * @param[int,out] s Configuration state. + */ +void applier::host::expand_objects(configuration::State& s) { + // Let's consider all the macros defined in s. + absl::flat_hash_set<std::string_view> cvs; + for (auto& cv : s.macros_filter().data()) + cvs.emplace(cv); + + absl::flat_hash_map<std::string_view, configuration::Hostgroup*> hgs; + for (auto& hg : *s.mutable_hostgroups()) + hgs.emplace(hg.hostgroup_name(), &hg); + + // Browse all hosts. + for (auto& host_cfg : *s.mutable_hosts()) { + // Should custom variables be sent to broker ? + for (auto& cv : *host_cfg.mutable_customvariables()) { + if (!s.enable_macros_filter() || cvs.contains(cv.name())) + cv.set_is_sent(true); + } + + for (auto& grp : host_cfg.hostgroups().data()) { + auto it = hgs.find(grp); + if (it != hgs.end()) { + fill_string_group(it->second->mutable_members(), host_cfg.host_name()); + } else + throw engine_error() << fmt::format( + "Could not add host '{}' to non-existing host group '{}'", + host_cfg.host_name(), grp); + } + } +} +#endif diff --git a/engine/src/configuration/applier/hostdependency.cc b/engine/src/configuration/applier/hostdependency.cc index 64cd39262d5..ba0d4b75f1f 100644 --- a/engine/src/configuration/applier/hostdependency.cc +++ b/engine/src/configuration/applier/hostdependency.cc @@ -27,6 +27,7 @@ using namespace com::centreon::engine::configuration; +#ifdef LEGACY_CONF /** * Add new hostdependency. * @@ -103,7 +104,74 @@ void applier::hostdependency::add_object( broker_adaptive_dependency_data(NEBTYPE_HOSTDEPENDENCY_ADD, hd.get()); } +#else +/** + * Add new hostdependency. + * + * @param[in] obj The new host dependency to add into the monitoring + * engine. + */ +void applier::hostdependency::add_object( + const configuration::Hostdependency& obj) { + // Check host dependency. + if (obj.hosts().data().size() != 1 || !obj.hostgroups().data().empty() || + obj.dependent_hosts().data().size() != 1 || + !obj.dependent_hostgroups().data().empty()) + throw engine_error() << "Could not create host dependency " + "with multiple (dependent) host / host groups"; + if (obj.dependency_type() != DependencyKind::execution_dependency && + obj.dependency_type() != DependencyKind::notification_dependency) + throw engine_error() << fmt::format( + "Could not create unexpanded host dependency of '{}' on '{}'", + obj.dependent_hosts().data(0), obj.hosts().data(0)); + + // Logging. + config_logger->debug( + "Creating new host dependency of host '{}' on host '{}'.", + obj.dependent_hosts().data(0), obj.hosts().data(0)); + + // Add dependency to the global configuration set. + auto* new_obj = pb_config.add_hostdependencies(); + new_obj->CopyFrom(obj); + + std::shared_ptr<engine::hostdependency> hd; + + if (obj.dependency_type() == DependencyKind::execution_dependency) + // Create executon dependency. + hd = std::make_shared<engine::hostdependency>( + configuration::hostdependency_key(obj), obj.dependent_hosts().data(0), + obj.hosts().data(0), + static_cast<engine::hostdependency::types>(obj.dependency_type()), + obj.inherits_parent(), + static_cast<bool>(obj.execution_failure_options() & action_hd_up), + static_cast<bool>(obj.execution_failure_options() & action_hd_down), + static_cast<bool>(obj.execution_failure_options() & + action_hd_unreachable), + static_cast<bool>(obj.execution_failure_options() & action_hd_pending), + obj.dependency_period()); + else + // Create notification dependency. + hd = std::make_shared<engine::hostdependency>( + hostdependency_key(obj), obj.dependent_hosts().data(0), + obj.hosts().data(0), + static_cast<engine::hostdependency::types>(obj.dependency_type()), + obj.inherits_parent(), + static_cast<bool>(obj.notification_failure_options() & action_hd_up), + static_cast<bool>(obj.notification_failure_options() & action_hd_down), + static_cast<bool>(obj.notification_failure_options() & + action_hd_unreachable), + static_cast<bool>(obj.notification_failure_options() & + action_hd_pending), + obj.dependency_period()); + + engine::hostdependency::hostdependencies.insert( + {obj.dependent_hosts().data(0), hd}); + + broker_adaptive_dependency_data(NEBTYPE_HOSTDEPENDENCY_ADD, hd.get()); +} +#endif +#ifdef LEGACY_CONF /** * Expand host dependencies. * @@ -169,7 +237,83 @@ void applier::hostdependency::expand_objects(configuration::state& s) { // Set expanded host dependencies in configuration state. s.hostdependencies().swap(expanded); } +#else +/** + * Expand host dependencies. + * + * @param[in,out] s Configuration being applied. + */ +void applier::hostdependency::expand_objects(configuration::State& s) { + std::list<std::unique_ptr<configuration::Hostdependency> > lst; + + config_logger->debug("Expanding host dependencies"); + for (int i = s.hostdependencies_size() - 1; i >= 0; --i) { + auto* hd_conf = s.mutable_hostdependencies(i); + if (hd_conf->hosts().data().size() > 1 || + !hd_conf->hostgroups().data().empty() || + hd_conf->dependent_hosts().data().size() > 1 || + !hd_conf->dependent_hostgroups().data().empty() || + hd_conf->dependency_type() == unknown) { + for (auto& hg_name : hd_conf->dependent_hostgroups().data()) { + auto found = + std::find_if(s.hostgroups().begin(), s.hostgroups().end(), + [&hg_name](const configuration::Hostgroup& hg) { + return hg.hostgroup_name() == hg_name; + }); + if (found != s.hostgroups().end()) { + auto& hg_conf = *found; + for (auto& h : hg_conf.members().data()) + fill_string_group(hd_conf->mutable_dependent_hosts(), h); + } + } + for (auto& hg_name : hd_conf->hostgroups().data()) { + auto found = + std::find_if(s.hostgroups().begin(), s.hostgroups().end(), + [&hg_name](const configuration::Hostgroup& hg) { + return hg.hostgroup_name() == hg_name; + }); + if (found != s.hostgroups().end()) { + auto& hg_conf = *found; + for (auto& h : hg_conf.members().data()) + fill_string_group(hd_conf->mutable_hosts(), h); + } + } + for (auto& h : hd_conf->hosts().data()) { + for (auto& h_dep : hd_conf->dependent_hosts().data()) { + for (int ii = 1; ii <= 2; ii++) { + if (hd_conf->dependency_type() == DependencyKind::unknown || + static_cast<int32_t>(hd_conf->dependency_type()) == ii) { + lst.emplace_back(std::make_unique<Hostdependency>()); + auto& new_hd = lst.back(); + new_hd->set_dependency_period(hd_conf->dependency_period()); + new_hd->set_inherits_parent(hd_conf->inherits_parent()); + fill_string_group(new_hd->mutable_hosts(), h); + fill_string_group(new_hd->mutable_dependent_hosts(), h_dep); + if (ii == 2) { + new_hd->set_dependency_type( + DependencyKind::execution_dependency); + new_hd->set_execution_failure_options( + hd_conf->execution_failure_options()); + } else { + new_hd->set_dependency_type( + DependencyKind::notification_dependency); + new_hd->set_notification_failure_options( + hd_conf->notification_failure_options()); + } + } + } + } + } + s.mutable_hostdependencies()->DeleteSubrange(i, 1); + } + } + for (auto& hd : lst) + s.mutable_hostdependencies()->AddAllocated(hd.release()); +} +#endif + +#ifdef LEGACY_CONF /** * @brief Modify host dependency. * @@ -187,7 +331,27 @@ void applier::hostdependency::modify_object( << "this is likely a software bug that you should report to " << "Centreon Engine developers"; } +#else +/** + * @brief Modify host dependency. + * + * Host dependencies cannot be defined with anything else than their + * full content. Therefore no modification can occur. + * + * @param[in] obj Unused. + */ +void applier::hostdependency::modify_object( + configuration::Hostdependency* old_obj [[maybe_unused]], + const configuration::Hostdependency& new_obj [[maybe_unused]]) { + throw engine_error() + << "Could not modify a host dependency: Host dependency objects can " + "only " + "be added or removed, this is likely a software bug that you should " + "report to Centreon Engine developers"; +} +#endif +#ifdef LEGACY_CONF /** * Remove old host dependency. * @@ -217,7 +381,40 @@ void applier::hostdependency::remove_object( // Remove dependency from the global configuration set. config->hostdependencies().erase(obj); } +#else +/** + * Remove old host dependency. + * + * @param[in] idx The index of the host dependency configuration to remove + * from engine. + */ +void applier::hostdependency::remove_object(ssize_t idx) { + // Logging. + config_logger->debug("Removing a host dependency."); + + // Find host dependency. + auto& obj = pb_config.hostdependencies(0); + size_t key = hostdependency_key(obj); + + hostdependency_mmap::iterator it = + engine::hostdependency::hostdependencies_find( + {obj.dependent_hosts().data(0), key}); + if (it != engine::hostdependency::hostdependencies.end()) { + com::centreon::engine::hostdependency* dependency(it->second.get()); + + // Notify event broker. + broker_adaptive_dependency_data(NEBTYPE_HOSTDEPENDENCY_DELETE, dependency); + + // Remove host dependency from its list. + engine::hostdependency::hostdependencies.erase(it); + } + + // Remove dependency from the global configuration set. + pb_config.mutable_hostdependencies()->DeleteSubrange(idx, 1); +} +#endif +#ifdef LEGACY_CONF /** * Resolve a hostdependency. * @@ -241,7 +438,33 @@ void applier::hostdependency::resolve_object( // Resolve host dependency. it->second->resolve(err.config_warnings, err.config_errors); } +#else +/** + * Resolve a hostdependency. + * + * @param[in] obj Hostdependency object. + */ +void applier::hostdependency::resolve_object( + const configuration::Hostdependency& obj, + error_cnt& err) { + // Logging. + config_logger->debug("Resolving a host dependency."); + + // Find host escalation + auto k = hostdependency_key(obj); + + auto it = engine::hostdependency::hostdependencies_find( + {obj.dependent_hosts().data(0), k}); + + if (engine::hostdependency::hostdependencies.end() == it) + throw engine_error() << "Cannot resolve non-existing host escalation"; + + // Resolve host dependency. + it->second->resolve(err.config_warnings, err.config_errors); +} +#endif +#ifdef LEGACY_CONF /** * Expand hosts. * @@ -271,3 +494,4 @@ void applier::hostdependency::_expand_hosts( expanded.insert(it_group->members().begin(), it_group->members().end()); } } +#endif diff --git a/engine/src/configuration/applier/hostescalation.cc b/engine/src/configuration/applier/hostescalation.cc index 39a09b2e2ba..616d52d1113 100644 --- a/engine/src/configuration/applier/hostescalation.cc +++ b/engine/src/configuration/applier/hostescalation.cc @@ -27,6 +27,7 @@ using namespace com::centreon::engine::configuration; +#ifdef LEGACY_CONF /** * Add new host escalation. * @@ -81,7 +82,60 @@ void applier::hostescalation::add_object( it != end; ++it) he->get_contactgroups().insert({*it, nullptr}); } +#else +/** + * Add new host escalation. + * + * @param[in] obj The new host escalation to add into the monitoring + * engine. + */ +void applier::hostescalation::add_object( + const configuration::Hostescalation& obj) { + // Check host escalation. + if (obj.hosts().data().size() != 1 || !obj.hostgroups().data().empty()) + throw engine_error() + << "Could not create host escalation with multiple hosts / host groups"; + + // Logging. + config_logger->debug("Creating new escalation for host '{}'.", obj.hosts().data(0)); + + // Add escalation to the global configuration set. + auto* new_obj = pb_config.add_hostescalations(); + new_obj->CopyFrom(obj); + + size_t key = hostescalation_key(obj); + + // Create host escalation. + auto he = std::make_shared<engine::hostescalation>( + obj.hosts().data(0), obj.first_notification(), obj.last_notification(), + obj.notification_interval(), obj.escalation_period(), + ((obj.escalation_options() & action_he_down) + ? notifier::down + : notifier::none) | + ((obj.escalation_options() & + action_he_unreachable) + ? notifier::unreachable + : notifier::none) | + ((obj.escalation_options() & action_he_recovery) + ? notifier::up + : notifier::none), + key); + + // Add new items to the configuration state. + engine::hostescalation::hostescalations.insert({he->get_hostname(), he}); + + // Notify event broker. + timeval tv(get_broker_timestamp(nullptr)); + broker_adaptive_escalation_data(NEBTYPE_HOSTESCALATION_ADD, NEBFLAG_NONE, + NEBATTR_NONE, he.get(), &tv); + + // Add contact groups to host escalation. + for (auto& g : obj.contactgroups().data()) + he->get_contactgroups().insert({g, nullptr}); +} +#endif +#ifdef LEGACY_CONF /** * Expand a host escalation. * @@ -116,7 +170,49 @@ void applier::hostescalation::expand_objects(configuration::state& s) { // Set expanded host escalations in configuration state. s.hostescalations().swap(expanded); } +#else +/** + * Expand a host escalation. + * + * @param[in,out] s Configuration being applied. + */ +void applier::hostescalation::expand_objects(configuration::State& s) { + std::list<std::unique_ptr<Hostescalation> > resolved; + for (auto& he : *s.mutable_hostescalations()) { + if (he.hostgroups().data().size() > 0) { + absl::flat_hash_set<std::string_view> host_names; + for (auto& hname : he.hosts().data()) + host_names.emplace(hname); + for (auto& hg_name : he.hostgroups().data()) { + auto found_hg = + std::find_if(s.hostgroups().begin(), s.hostgroups().end(), + [&hg_name](const Hostgroup& hg) { + return hg.hostgroup_name() == hg_name; + }); + if (found_hg != s.hostgroups().end()) { + for (auto& h : found_hg->members().data()) + host_names.emplace(h); + } else + throw engine_error() << fmt::format( + "Could not expand non-existing host group '{}'", hg_name); + } + he.mutable_hostgroups()->clear_data(); + he.mutable_hosts()->clear_data(); + for (auto& n : host_names) { + resolved.emplace_back(std::make_unique<Hostescalation>()); + auto& e = resolved.back(); + e->CopyFrom(he); + fill_string_group(e->mutable_hosts(), n); + } + } + } + s.clear_hostescalations(); + for (auto& e : resolved) + s.mutable_hostescalations()->AddAllocated(e.release()); +} +#endif +#ifdef LEGACY_CONF /** * @brief Modify host escalation. * @@ -134,7 +230,26 @@ void applier::hostescalation::modify_object( << "this is likely a software bug that you should report to " << "Centreon Engine developers"; } +#else +/** + * @brief Modify host escalation. + * + * Host escalations cannot be defined with anything else than their + * full content. Therefore no modification can occur. + * + * @param[in] obj Unused. + */ +void applier::hostescalation::modify_object( + configuration::Hostescalation* old_obj [[maybe_unused]], + const configuration::Hostescalation& new_obj [[maybe_unused]]) { + throw engine_error() + << "Could not modify a host escalation: host escalation objects can only " + "be added or removed, this is likely a software bug that you should " + "report to Centreon Engine developers"; +} +#endif +#ifdef LEGACY_CONF /** * Remove old hostescalation. * @@ -217,7 +332,85 @@ void applier::hostescalation::remove_object( /* And we clear the configuration */ config->hostescalations().erase(obj); } +#else +/** + * Remove old hostescalation. + * + * @param[in] obj The new hostescalation to remove from the monitoring + * engine. + */ +void applier::hostescalation::remove_object(ssize_t idx) { + configuration::Hostescalation obj = pb_config.hostescalations(idx); + // Logging. + config_logger->debug("Removing a host escalation."); + // Find host escalation. + const std::string& host_name{obj.hosts().data(0)}; + std::pair<hostescalation_mmap::iterator, hostescalation_mmap::iterator> range{ + engine::hostescalation::hostescalations.equal_range(host_name)}; + bool host_exists; + + /* Let's get the host... */ + host_map::iterator hit{engine::host::hosts.find(host_name)}; + /* ... and its escalations */ + if (hit == engine::host::hosts.end()) { + config_logger->debug("Cannot find host '{}' - already removed.", host_name); + host_exists = false; + } else + host_exists = true; + + for (hostescalation_mmap::iterator it{range.first}, end{range.second}; + it != end; ++it) { + /* It's a pity but for now we don't have any possibility or key to verify + * if the hostescalation is the good one. */ + if (it->second->get_first_notification() == obj.first_notification() && + it->second->get_last_notification() == obj.last_notification() && + it->second->get_notification_interval() == + obj.notification_interval() && + it->second->get_escalation_period() == obj.escalation_period() && + it->second->get_escalate_on(notifier::down) == + static_cast<bool>(obj.escalation_options() & + action_he_down) && + it->second->get_escalate_on(notifier::unreachable) == + static_cast<bool>(obj.escalation_options() & + action_he_unreachable) && + it->second->get_escalate_on(notifier::up) == + static_cast<bool>(obj.escalation_options() & + action_he_recovery)) { + // We have the hostescalation to remove. + + // Notify event broker. + timeval tv(get_broker_timestamp(nullptr)); + broker_adaptive_escalation_data(NEBTYPE_HOSTESCALATION_DELETE, + NEBFLAG_NONE, NEBATTR_NONE, + it->second.get(), &tv); + + if (host_exists) { + config_logger->debug("Host '{}' found - removing escalation from it.", + host_name); + std::list<escalation*>& escalations(hit->second->get_escalations()); + /* We need also to remove the escalation from the host */ + for (std::list<engine::escalation*>::iterator heit{escalations.begin()}, + heend{escalations.end()}; + heit != heend; ++heit) { + if (*heit == it->second.get()) { + escalations.erase(heit); + break; + } + } + } + // Remove host escalation from the global configuration set. + engine::hostescalation::hostescalations.erase(it); + break; + } + } + + /* And we clear the configuration */ + pb_config.mutable_hostescalations()->DeleteSubrange(idx, 1); +} +#endif + +#ifdef LEGACY_CONF /** * Resolve a hostescalation. * @@ -254,7 +447,43 @@ void applier::hostescalation::resolve_object( if (!found) throw engine_error() << "Cannot resolve non-existing host escalation"; } +#else +/** + * Resolve a hostescalation. + * + * @param[in] obj Hostescalation object. + */ +void applier::hostescalation::resolve_object( + const configuration::Hostescalation& obj, error_cnt& err) { + // Logging. + config_logger->debug("Resolving a host escalation."); + + // Find host escalation + bool found = false; + const std::string& hostname{obj.hosts().data(0)}; + auto p = engine::hostescalation::hostescalations.equal_range(hostname); + + if (p.first == p.second) + throw engine_error() << "Cannot find host escalations concerning host '" + << hostname << "'"; + + size_t key = hostescalation_key(obj); + for (hostescalation_mmap::iterator it{p.first}; it != p.second; ++it) { + /* It's a pity but for now we don't have any idea or key to verify if + * the hostescalation is the good one. */ + if (it->second->internal_key() == key) { + found = true; + // Resolve host escalation. + it->second->resolve(err.config_warnings, err.config_errors); + break; + } + } + if (!found) + throw engine_error() << "Cannot resolve non-existing host escalation"; +} +#endif +#ifdef LEGACY_CONF /** * Expand hosts. * @@ -284,7 +513,9 @@ void applier::hostescalation::_expand_hosts( expanded.insert(it_group->members().begin(), it_group->members().end()); } } +#endif +#ifdef LEGACY_CONF /** * Inherits special variables from the host. * @@ -313,3 +544,4 @@ void applier::hostescalation::_inherits_special_vars( obj.escalation_period(it->notification_period()); } } +#endif diff --git a/engine/src/configuration/applier/hostgroup.cc b/engine/src/configuration/applier/hostgroup.cc index e304c6b350e..c4c6b8bba37 100644 --- a/engine/src/configuration/applier/hostgroup.cc +++ b/engine/src/configuration/applier/hostgroup.cc @@ -29,25 +29,7 @@ using namespace com::centreon::engine::configuration; -/** - * Default constructor. - */ -applier::hostgroup::hostgroup() {} - -/** - * Copy constructor. - * - * @param[in] right Object to copy. - */ -applier::hostgroup::hostgroup(applier::hostgroup const& right) { - (void)right; -} - -/** - * Destructor. - */ -applier::hostgroup::~hostgroup() throw() {} - +#ifdef LEGACY_CONF /** * Add new hostgroup. * @@ -79,7 +61,38 @@ void applier::hostgroup::add_object(configuration::hostgroup const& obj) { // Notify event broker. broker_group(NEBTYPE_HOSTGROUP_ADD, hg.get()); } +#else +/** + * Add new hostgroup. + * + * @param[in] obj The new hostgroup to add into the monitoring engine. + */ +void applier::hostgroup::add_object(const configuration::Hostgroup& obj) { + // Logging. + config_logger->debug("Creating new hostgroup '{}'.", obj.hostgroup_name()); + // Add host group to the global configuration state. + auto* new_obj = pb_config.add_hostgroups(); + new_obj->CopyFrom(obj); + + // Create host group. + auto hg = std::make_shared<com::centreon::engine::hostgroup>( + obj.hostgroup_id(), obj.hostgroup_name(), obj.alias(), obj.notes(), + obj.notes_url(), obj.action_url()); + + // Add new items to the configuration state. + engine::hostgroup::hostgroups.insert({hg->get_group_name(), hg}); + + // Notify event broker. + broker_group(NEBTYPE_HOSTGROUP_ADD, hg.get()); + + // Apply resolved hosts on hostgroup. + for (auto& h : obj.members().data()) + hg->members.insert({h, nullptr}); +} +#endif + +#ifdef LEGACY_CONF /** * Expand all host groups. * @@ -99,7 +112,17 @@ void applier::hostgroup::expand_objects(configuration::state& s) { it != end; ++it) s.hostgroups().insert(it->second); } +#else +/** + * Expand all host groups. + * + * @param[in,out] s State being applied. + */ +void applier::hostgroup::expand_objects(configuration::State& s + [[maybe_unused]]) {} +#endif +#ifdef LEGACY_CONF /** * Modified hostgroup. * @@ -156,7 +179,57 @@ void applier::hostgroup::modify_object(configuration::hostgroup const& obj) { // Notify event broker. broker_group(NEBTYPE_HOSTGROUP_UPDATE, it_obj->second.get()); } +#else +/** + * Modified hostgroup. + * + * @param[in] obj The new hostgroup to modify into the monitoring + * engine. + */ +void applier::hostgroup::modify_object( + configuration::Hostgroup* old_obj, + const configuration::Hostgroup& new_obj) { + // Logging. + config_logger->debug("Modifying hostgroup '{}'", old_obj->hostgroup_name()); + // Find host group object. + hostgroup_map::iterator it_obj = + engine::hostgroup::hostgroups.find(old_obj->hostgroup_name()); + if (it_obj == engine::hostgroup::hostgroups.end()) + throw engine_error() << fmt::format( + "Could not modify non-existing host group object '{}'", + old_obj->hostgroup_name()); + + it_obj->second->set_action_url(new_obj.action_url()); + it_obj->second->set_alias(new_obj.alias()); + it_obj->second->set_notes(new_obj.notes()); + it_obj->second->set_notes_url(new_obj.notes_url()); + it_obj->second->set_id(new_obj.hostgroup_id()); + + // Were members modified ? + if (!MessageDifferencer::Equals(new_obj.members(), old_obj->members())) { + // Delete all old host group members. + for (host_map_unsafe::iterator it(it_obj->second->members.begin()), + end(it_obj->second->members.end()); + it != end; ++it) { + broker_group_member(NEBTYPE_HOSTGROUPMEMBER_DELETE, it->second, + it_obj->second.get()); + } + it_obj->second->members.clear(); + + for (auto it = new_obj.members().data().begin(), + end = new_obj.members().data().end(); + it != end; ++it) + it_obj->second->members.insert({*it, nullptr}); + } + + old_obj->CopyFrom(new_obj); + // Notify event broker. + broker_group(NEBTYPE_HOSTGROUP_UPDATE, it_obj->second.get()); +} +#endif + +#ifdef LEGACY_CONF /** * Remove old hostgroup. * @@ -184,7 +257,37 @@ void applier::hostgroup::remove_object(configuration::hostgroup const& obj) { // Remove host group from the global configuration set. config->hostgroups().erase(obj); } +#else +/** + * Remove old hostgroup. + * + * @param[in] obj The new hostgroup to remove from the monitoring + * engine. + */ +void applier::hostgroup::remove_object(ssize_t idx) { + const Hostgroup& obj = pb_config.hostgroups(idx); + // Logging. + config_logger->debug("Removing host group '{}'", obj.hostgroup_name()); + + // Find host group. + hostgroup_map::iterator it = + engine::hostgroup::hostgroups.find(obj.hostgroup_name()); + if (it != engine::hostgroup::hostgroups.end()) { + engine::hostgroup* grp(it->second.get()); + + // Notify event broker. + broker_group(NEBTYPE_HOSTGROUP_DELETE, grp); + + // Erase host group object (will effectively delete the object). + engine::hostgroup::hostgroups.erase(it); + } + + // Remove host group from the global configuration set. + pb_config.mutable_hostgroups()->DeleteSubrange(idx, 1); +} +#endif +#ifdef LEGACY_CONF /** * Resolve a host group. * @@ -206,7 +309,30 @@ void applier::hostgroup::resolve_object(configuration::hostgroup const& obj, // Resolve host group. it->second->resolve(err.config_warnings, err.config_errors); } +#else +/** + * Resolve a host group. + * + * @param[in] obj Object to resolved. + */ +void applier::hostgroup::resolve_object(const configuration::Hostgroup& obj, + error_cnt& err) { + // Logging. + config_logger->debug("Resolving host group '{}'", obj.hostgroup_name()); + + // Find host group. + hostgroup_map::iterator it = + engine::hostgroup::hostgroups.find(obj.hostgroup_name()); + if (it == engine::hostgroup::hostgroups.end()) + throw engine_error() << fmt::format( + "Cannot resolve non-existing host group '{}'", obj.hostgroup_name()); + + // Resolve host group. + it->second->resolve(err.config_warnings, err.config_errors); +} +#endif +#ifdef LEGACY_CONF /** * Resolve members of a host group. * @@ -231,3 +357,4 @@ void applier::hostgroup::_resolve_members(configuration::state& s resolved_obj = obj; } } +#endif diff --git a/engine/src/configuration/applier/logging.cc b/engine/src/configuration/applier/logging.cc index 40a77191f82..6644a06e530 100644 --- a/engine/src/configuration/applier/logging.cc +++ b/engine/src/configuration/applier/logging.cc @@ -28,6 +28,7 @@ using namespace com::centreon; using namespace com::centreon::engine::configuration; +#ifdef LEGACY_CONF /** * Apply new configuration. * @@ -74,6 +75,54 @@ void applier::logging::apply(state& config) { _del_syslog(); } } +#else +/** + * Apply new configuration. + * + * @param[in] config The new configuration. + */ +void applier::logging::apply(State& config) { + if (verify_config || test_scheduling) + return; + + if (config.log_legacy_enabled()) { + // Syslog. + if (config.use_syslog() && !_syslog) + _add_syslog(); + else if (!config.use_syslog() && _syslog) + _del_syslog(); + + // Standard log file. + + if (config.log_file() == "") + _del_log_file(); + else if (!_log || config.log_file() != _log->filename()) { + _add_log_file(config); + _del_stdout(); + _del_stderr(); + } + + // Debug file. + if ((config.debug_file() == "") || !config.debug_level() || + !config.debug_verbosity()) { + _del_debug(); + _debug_level = config.debug_level(); + _debug_verbosity = config.debug_verbosity(); + _debug_max_size = config.max_debug_file_size(); + } else if (!_debug || config.debug_file() != _debug->filename() || + config.debug_level() != _debug_level || + config.debug_verbosity() != _debug_verbosity || + config.max_debug_file_size() != _debug_max_size) + _add_debug(config); + } else { + _del_stdout(); + _del_stderr(); + _del_debug(); + _del_log_file(); + _del_syslog(); + } +} +#endif /** * Get the singleton instance of logging applier. @@ -120,6 +169,7 @@ applier::logging::logging() _add_stderr(); } +#ifdef LEGACY_CONF /** * Construct and apply configuration. * @@ -138,11 +188,31 @@ applier::logging::logging(state& config) _add_stderr(); apply(config); } +#else +/** + * Construct and apply configuration. + * + * @param[in] config The initial confiuration. + */ +applier::logging::logging(State& config) + : _debug(nullptr), + _debug_level(0), + _debug_max_size(0), + _debug_verbosity(0), + _log(nullptr), + _stderr(nullptr), + _stdout(nullptr), + _syslog(nullptr) { + _add_stdout(); + _add_stderr(); + apply(config); +} +#endif /** * Default destructor. */ -applier::logging::~logging() throw() { +applier::logging::~logging() noexcept { _del_stdout(); _del_stderr(); _del_syslog(); @@ -200,6 +270,7 @@ void applier::logging::_add_syslog() { } } +#ifdef LEGACY_CONF /** * Add file object logging. */ @@ -210,7 +281,20 @@ void applier::logging::_add_log_file(state const& config) { com::centreon::logging::engine::instance().add(_log, engine::logging::log_all, engine::logging::most); } +#else +/** + * Add file object logging. + */ +void applier::logging::_add_log_file(const State& config) { + _del_log_file(); + _log = new com::centreon::logging::file(config.log_file(), true, + config.log_pid()); + com::centreon::logging::engine::instance().add(_log, engine::logging::log_all, + engine::logging::most); +} +#endif +#ifdef LEGACY_CONF /** * Add debug object logging. */ @@ -224,6 +308,21 @@ void applier::logging::_add_debug(state const& config) { com::centreon::logging::engine::instance().add(_debug, _debug_level, _debug_verbosity); } +#else +/** + * Add debug object logging. + */ +void applier::logging::_add_debug(const State& config) { + _del_debug(); + _debug_level = (config.debug_level() << 32) | engine::logging::log_all; + _debug_verbosity = config.debug_verbosity(); + _debug_max_size = config.max_debug_file_size(); + _debug = new com::centreon::engine::logging::debug_file(config.debug_file(), + _debug_max_size); + com::centreon::logging::engine::instance().add(_debug, _debug_level, + _debug_verbosity); +} +#endif /** * Remove syslog object logging. diff --git a/engine/src/configuration/applier/macros.cc b/engine/src/configuration/applier/macros.cc index 57f2358f801..fd499c133b4 100644 --- a/engine/src/configuration/applier/macros.cc +++ b/engine/src/configuration/applier/macros.cc @@ -50,6 +50,7 @@ static bool is_old_style_user_macro(std::string const& key, unsigned int& val) { return (true); } +#ifdef LEGACY_CONF /** * Apply new configuration. * @@ -80,6 +81,39 @@ void applier::macros::apply(configuration::state& config) { _set_macros_user(val - 1, it->second); } } +#else +/** + * Apply new configuration. + * + * @param[in] config The new configuration. + */ +void applier::macros::apply(configuration::State& pb_config) { + _set_macro(MACRO_ADMINEMAIL, pb_config.admin_email()); + _set_macro(MACRO_ADMINPAGER, pb_config.admin_pager()); + _set_macro(MACRO_COMMANDFILE, pb_config.command_file()); + _set_macro(MACRO_LOGFILE, pb_config.log_file()); + _set_macro(MACRO_MAINCONFIGFILE, pb_config.cfg_main()); + if (pb_config.resource_file().size() > 0) + _set_macro(MACRO_RESOURCEFILE, pb_config.resource_file(0)); + _set_macro(MACRO_STATUSDATAFILE, pb_config.status_file()); + _set_macro(MACRO_RETENTIONDATAFILE, pb_config.state_retention_file()); + _set_macro(MACRO_POLLERNAME, pb_config.poller_name()); + _set_macro(MACRO_POLLERID, std::to_string(pb_config.poller_id())); + + auto& users = applier::state::instance().user_macros(); + users.clear(); + + for (auto& p : pb_config.users()) + users[p.first] = p.second; + + // Save old style user macros into old style structures. + for (auto& p : users) { + unsigned int val = 1; + if (is_old_style_user_macro(p.first, val)) + _set_macros_user(val - 1, p.second); + } +} +#endif /** * Get the singleton instance of macros applier. diff --git a/engine/src/configuration/applier/scheduler.cc b/engine/src/configuration/applier/scheduler.cc index 7e6a9deac9a..a442b520e1a 100644 --- a/engine/src/configuration/applier/scheduler.cc +++ b/engine/src/configuration/applier/scheduler.cc @@ -33,6 +33,7 @@ using namespace com::centreon::engine::configuration; using namespace com::centreon::engine::logging; using namespace com::centreon::logging; +#ifdef LEGACY_CONF /** * Apply new configuration. * @@ -199,6 +200,184 @@ void applier::scheduler::apply( } } } +#else +/** + * Apply new configuration. + * + * @param[in] config The new configuration. + * @param[in] diff_hosts The difference between old and the + * new host configuration. + * @param[in] diff_services The difference between old and the + * new service configuration. + * @param[in] diff_anomalydetections The difference between old and the + * new cwanomalydetectionservice configuration. + */ +void applier::scheduler::apply( + configuration::State& config, + const pb_difference<configuration::Host, uint64_t>& diff_hosts, + const pb_difference<configuration::Service, std::pair<uint64_t, uint64_t>>& + diff_services, + const pb_difference<configuration::Anomalydetection, + std::pair<uint64_t, uint64_t>>& + diff_anomalydetections) { + // Internal pointer will be used in private methods. + _pb_config = &config; + + // Remove and create misc event. + _apply_misc_event(); + + // Objects set. + std::vector<uint64_t> hst_to_unschedule; + for (auto& d : diff_hosts.deleted()) + hst_to_unschedule.emplace_back(d.second); + + std::vector<std::pair<uint64_t, uint64_t>> svc_to_unschedule; + for (auto& d : diff_services.deleted()) + svc_to_unschedule.emplace_back(d.second); + + std::vector<std::pair<uint64_t, uint64_t>> ad_to_unschedule; + for (auto& d : diff_anomalydetections.deleted()) + ad_to_unschedule.emplace_back(d.second); + + std::vector<uint64_t> hst_to_schedule; + for (auto& a : diff_hosts.added()) + hst_to_schedule.emplace_back(a.host_id()); + + std::vector<std::pair<uint64_t, uint64_t>> svc_to_schedule; + for (auto& a : diff_services.added()) + svc_to_schedule.emplace_back(a.host_id(), a.service_id()); + + std::vector<std::pair<uint64_t, uint64_t>> ad_to_schedule; + for (auto& a : diff_anomalydetections.added()) + ad_to_schedule.emplace_back(a.host_id(), a.service_id()); + + for (auto& m : diff_hosts.modified()) { + auto it_hst = engine::host::hosts.find(m.second.host_name()); + if (it_hst != engine::host::hosts.end()) { + bool has_event(events::loop::instance().find_event( + events::loop::low, timed_event::EVENT_HOST_CHECK, + it_hst->second.get()) != + events::loop::instance().list_end(events::loop::low)); + bool should_schedule(m.second.checks_active() && + m.second.check_interval() > 0); + if (has_event && should_schedule) { + hst_to_unschedule.emplace_back(m.second.host_id()); + hst_to_schedule.emplace_back(m.second.host_id()); + } else if (!has_event && should_schedule) + hst_to_schedule.emplace_back(m.second.host_id()); + else if (has_event && !should_schedule) + hst_to_unschedule.emplace_back(m.second.host_id()); + // Else it has no event and should not be scheduled, so do nothing. + } + } + + for (auto& m : diff_services.modified()) { + auto it_svc = engine::service::services_by_id.find( + {m.second.host_id(), m.second.service_id()}); + if (it_svc != engine::service::services_by_id.end()) { + bool has_event(events::loop::instance().find_event( + events::loop::low, timed_event::EVENT_SERVICE_CHECK, + it_svc->second.get()) != + events::loop::instance().list_end(events::loop::low)); + bool should_schedule(m.second.checks_active() && + (m.second.check_interval() > 0)); + if (has_event && should_schedule) { + svc_to_unschedule.emplace_back(m.second.host_id(), + m.second.service_id()); + svc_to_schedule.emplace_back(m.second.host_id(), m.second.service_id()); + } else if (!has_event && should_schedule) + svc_to_schedule.emplace_back(m.second.host_id(), m.second.service_id()); + else if (has_event && !should_schedule) + svc_to_unschedule.emplace_back(m.second.host_id(), + m.second.service_id()); + // Else it has no event and should not be scheduled, so do nothing. + } + } + + for (auto& m : diff_anomalydetections.modified()) { + auto it_svc = engine::service::services_by_id.find( + {m.second.host_id(), m.second.service_id()}); + if (it_svc != engine::service::services_by_id.end()) { + bool has_event(events::loop::instance().find_event( + events::loop::low, timed_event::EVENT_SERVICE_CHECK, + it_svc->second.get()) != + events::loop::instance().list_end(events::loop::low)); + bool should_schedule = + m.second.checks_active() && m.second.check_interval() > 0; + if (has_event && should_schedule) { + ad_to_unschedule.emplace_back(m.second.host_id(), + m.second.service_id()); + ad_to_schedule.emplace_back(m.second.host_id(), m.second.service_id()); + } else if (!has_event && should_schedule) + ad_to_schedule.emplace_back(m.second.host_id(), m.second.service_id()); + else if (has_event && !should_schedule) + ad_to_unschedule.emplace_back(m.second.host_id(), + m.second.service_id()); + // Else it has no event and should not be scheduled, so do nothing. + } + } + + // Remove deleted host check from the scheduler. + { + std::vector<com::centreon::engine::host*> old_hosts = + _get_hosts(hst_to_unschedule, false); + _unschedule_host_events(old_hosts); + } + + // Remove deleted service check from the scheduler. + { + std::vector<engine::service*> old_services = + _get_services(svc_to_unschedule, false); + _unschedule_service_events(old_services); + } + + // Remove deleted anomalydetection check from the scheduler. + { + std::vector<engine::service*> old_anomalydetections = + _get_anomalydetections(ad_to_unschedule, false); + _unschedule_service_events(old_anomalydetections); + } + // Check if we need to add or modify objects into the scheduler. + if (!hst_to_schedule.empty() || !svc_to_schedule.empty() || + !ad_to_schedule.empty()) { + memset(&scheduling_info, 0, sizeof(scheduling_info)); + + if (config.service_interleave_factor_method().type() == + configuration::InterleaveFactor::ilf_user) + scheduling_info.service_interleave_factor = config.service_interleave_factor_method().user_value(); + if (config.service_inter_check_delay_method().type() == + configuration::InterCheckDelay::user) + scheduling_info.service_inter_check_delay = config.service_inter_check_delay_method().user_value(); + if (config.host_inter_check_delay_method().type() == + configuration::InterCheckDelay::user) + scheduling_info.host_inter_check_delay = config.host_inter_check_delay_method().user_value(); + + // Calculate scheduling parameters. + _calculate_host_scheduling_params(); + _calculate_service_scheduling_params(); + + // Get and schedule new hosts. + { + std::vector<com::centreon::engine::host*> new_hosts = + _get_hosts(hst_to_schedule, true); + _schedule_host_events(new_hosts); + } + + // Get and schedule new services and anomalydetections. + { + std::vector<engine::service*> new_services = + _get_services(svc_to_schedule, true); + std::vector<engine::service*> new_anomalydetections = + _get_anomalydetections(ad_to_schedule, true); + new_services.insert( + new_services.end(), + std::make_move_iterator(new_anomalydetections.begin()), + std::make_move_iterator(new_anomalydetections.end())); + _schedule_service_events(new_services); + } + } +} +#endif /** * Get the singleton instance of scheduler applier. @@ -210,6 +389,7 @@ applier::scheduler& applier::scheduler::instance() { return instance; } +#ifdef LEGACY_CONF void applier::scheduler::clear() { _config = nullptr; _evt_check_reaper = nullptr; @@ -224,16 +404,34 @@ void applier::scheduler::clear() { _old_check_reaper_interval = 0; _old_command_check_interval = 0; _old_host_freshness_check_interval = 0; - _old_host_perfdata_file_processing_interval = 0; _old_retention_update_interval = 0; _old_service_freshness_check_interval = 0; - _old_service_perfdata_file_processing_interval = 0; _old_status_update_interval = 0; - _old_host_perfdata_file_processing_command.clear(); - _old_service_perfdata_file_processing_command.clear(); memset(&scheduling_info, 0, sizeof(scheduling_info)); } +#else +void applier::scheduler::clear() { + _pb_config = nullptr; + _evt_check_reaper = nullptr; + _evt_command_check = nullptr; + _evt_hfreshness_check = nullptr; + _evt_orphan_check = nullptr; + _evt_reschedule_checks = nullptr; + _evt_retention_save = nullptr; + _evt_sfreshness_check = nullptr; + _evt_status_save = nullptr; + _old_auto_rescheduling_interval = 0; + _old_check_reaper_interval = 0; + _old_command_check_interval = 0; + _old_host_freshness_check_interval = 0; + _old_retention_update_interval = 0; + _old_service_freshness_check_interval = 0; + _old_status_update_interval = 0; + + memset(&scheduling_info, 0, sizeof(scheduling_info)); +} +#endif /** * Remove some host from scheduling. @@ -269,7 +467,12 @@ void applier::scheduler::remove_service(uint64_t host_id, uint64_t service_id) { * Default constructor. */ applier::scheduler::scheduler() - : _config(nullptr), + : +#ifdef LEGACY_CONF + _config(nullptr), +#else + _pb_config(nullptr), +#endif _evt_check_reaper(nullptr), _evt_command_check(nullptr), _evt_hfreshness_check(nullptr), @@ -282,10 +485,8 @@ applier::scheduler::scheduler() _old_check_reaper_interval(0), _old_command_check_interval(0), _old_host_freshness_check_interval(0), - _old_host_perfdata_file_processing_interval(0), _old_retention_update_interval(0), _old_service_freshness_check_interval(0), - _old_service_perfdata_file_processing_interval(0), _old_status_update_interval(0) {} /** @@ -293,6 +494,7 @@ applier::scheduler::scheduler() */ applier::scheduler::~scheduler() noexcept {} +#ifdef LEGACY_CONF /** * Remove and create misc event if necessary. */ @@ -406,7 +608,126 @@ void applier::scheduler::_apply_misc_event() { _old_status_update_interval = _config->status_update_interval(); } } +#else +/** + * Remove and create misc event if necessary. + */ +void applier::scheduler::_apply_misc_event() { + // Get current time. + time_t const now = time(nullptr); + + // Remove and add check result reaper event. + if (!_evt_check_reaper || + _old_check_reaper_interval != _pb_config->check_reaper_interval()) { + _remove_misc_event(_evt_check_reaper); + _evt_check_reaper = + _create_misc_event(timed_event::EVENT_CHECK_REAPER, + now + _pb_config->check_reaper_interval(), + _pb_config->check_reaper_interval()); + _old_check_reaper_interval = _pb_config->check_reaper_interval(); + } + + // Remove and add an external command check event. + if ((!_evt_command_check && _pb_config->check_external_commands()) || + (_evt_command_check && !_pb_config->check_external_commands()) || + (_old_command_check_interval != _pb_config->command_check_interval())) { + _remove_misc_event(_evt_command_check); + if (_pb_config->check_external_commands()) { + unsigned long interval(5); + if (_pb_config->command_check_interval() != -1) + interval = (unsigned long)_pb_config->command_check_interval(); + _evt_command_check = _create_misc_event(timed_event::EVENT_COMMAND_CHECK, + now + interval, interval); + } + _old_command_check_interval = _pb_config->command_check_interval(); + } + + // Remove and add a host result "freshness" check event. + if ((!_evt_hfreshness_check && _pb_config->check_host_freshness()) || + (_evt_hfreshness_check && !_pb_config->check_host_freshness()) || + (_old_host_freshness_check_interval != + _pb_config->host_freshness_check_interval())) { + _remove_misc_event(_evt_hfreshness_check); + if (_pb_config->check_host_freshness()) + _evt_hfreshness_check = + _create_misc_event(timed_event::EVENT_HFRESHNESS_CHECK, + now + _pb_config->host_freshness_check_interval(), + _pb_config->host_freshness_check_interval()); + _old_host_freshness_check_interval = + _pb_config->host_freshness_check_interval(); + } + + // Remove and add an orphaned check event. + if ((!_evt_orphan_check && _pb_config->check_orphaned_services()) || + (!_evt_orphan_check && _pb_config->check_orphaned_hosts()) || + (_evt_orphan_check && !_pb_config->check_orphaned_services() && + !_pb_config->check_orphaned_hosts())) { + _remove_misc_event(_evt_orphan_check); + if (_pb_config->check_orphaned_services() || + _pb_config->check_orphaned_hosts()) + _evt_orphan_check = _create_misc_event( + timed_event::EVENT_ORPHAN_CHECK, now + DEFAULT_ORPHAN_CHECK_INTERVAL, + DEFAULT_ORPHAN_CHECK_INTERVAL); + } + + // Remove and add a host and service check rescheduling event. + if ((!_evt_reschedule_checks && _pb_config->auto_reschedule_checks()) || + (_evt_reschedule_checks && !_pb_config->auto_reschedule_checks()) || + (_old_auto_rescheduling_interval != + _pb_config->auto_rescheduling_interval())) { + _remove_misc_event(_evt_reschedule_checks); + if (_pb_config->auto_reschedule_checks()) + _evt_reschedule_checks = + _create_misc_event(timed_event::EVENT_RESCHEDULE_CHECKS, + now + _pb_config->auto_rescheduling_interval(), + _pb_config->auto_rescheduling_interval()); + _old_auto_rescheduling_interval = _pb_config->auto_rescheduling_interval(); + } + + // Remove and add a retention data save event if needed. + if ((!_evt_retention_save && _pb_config->retain_state_information()) || + (_evt_retention_save && !_pb_config->retain_state_information()) || + (_old_retention_update_interval != + _pb_config->retention_update_interval())) { + _remove_misc_event(_evt_retention_save); + if (_pb_config->retain_state_information() && + _pb_config->retention_update_interval() > 0) { + unsigned long interval(_pb_config->retention_update_interval() * 60); + _evt_retention_save = _create_misc_event( + timed_event::EVENT_RETENTION_SAVE, now + interval, interval); + } + _old_retention_update_interval = _pb_config->retention_update_interval(); + } + + // Remove add a service result "freshness" check event. + if ((!_evt_sfreshness_check && _pb_config->check_service_freshness()) || + (!_evt_sfreshness_check && !_pb_config->check_service_freshness()) || + _old_service_freshness_check_interval != + _pb_config->service_freshness_check_interval()) { + _remove_misc_event(_evt_sfreshness_check); + if (_pb_config->check_service_freshness()) + _evt_sfreshness_check = _create_misc_event( + timed_event::EVENT_SFRESHNESS_CHECK, + now + _pb_config->service_freshness_check_interval(), + _pb_config->service_freshness_check_interval()); + _old_service_freshness_check_interval = + _pb_config->service_freshness_check_interval(); + } + + // Remove and add a status save event. + if (!_evt_status_save || + (_old_status_update_interval != _pb_config->status_update_interval())) { + _remove_misc_event(_evt_status_save); + _evt_status_save = + _create_misc_event(timed_event::EVENT_STATUS_SAVE, + now + _pb_config->status_update_interval(), + _pb_config->status_update_interval()); + _old_status_update_interval = _pb_config->status_update_interval(); + } +} +#endif +#ifdef LEGACY_CONF /** * How should we determine the host inter-check delay to use. * @@ -479,7 +800,71 @@ void applier::scheduler::_calculate_host_inter_check_delay( scheduling_info.host_inter_check_delay); } } +#else +/** + * How should we determine the host inter-check delay to use. + * + * @param[in] method The method to use to calculate inter check delay. + */ +void applier::scheduler::_calculate_host_inter_check_delay( + const configuration::InterCheckDelay& method) { + switch (method.type()) { + case configuration::InterCheckDelay::none: + scheduling_info.host_inter_check_delay = 0.0; + break; + + case configuration::InterCheckDelay::dumb: + scheduling_info.host_inter_check_delay = 1.0; + break; + + case configuration::InterCheckDelay::user: + // the user specified a delay, so don't try to calculate one. + break; + case configuration::InterCheckDelay::smart: + default: + // be smart and calculate the best delay to use + // to minimize local load... + if (scheduling_info.total_scheduled_hosts > 0 && + scheduling_info.host_check_interval_total > 0) { + // calculate the average check interval for hosts. + scheduling_info.average_host_check_interval = + scheduling_info.host_check_interval_total / + (double)scheduling_info.total_scheduled_hosts; + + // calculate the average inter check delay (in seconds) + // needed to evenly space the host checks out. + scheduling_info.average_host_inter_check_delay = + scheduling_info.average_host_check_interval / + (double)scheduling_info.total_scheduled_hosts; + + // set the global inter check delay value. + scheduling_info.host_inter_check_delay = + scheduling_info.average_host_inter_check_delay; + + // calculate max inter check delay and see if we should use that + // instead. + double const max_inter_check_delay( + (scheduling_info.max_host_check_spread * 60) / + (double)scheduling_info.total_scheduled_hosts); + if (scheduling_info.host_inter_check_delay > max_inter_check_delay) + scheduling_info.host_inter_check_delay = max_inter_check_delay; + } else + scheduling_info.host_inter_check_delay = 0.0; + + events_logger->debug("Total scheduled host checks: {}", + scheduling_info.total_scheduled_hosts); + events_logger->debug("Host check interval total: {}", + scheduling_info.host_check_interval_total); + events_logger->debug("Average host check interval: {:.2f} sec", + scheduling_info.average_host_check_interval); + events_logger->debug("Host inter-check delay: {:.2f} sec", + scheduling_info.host_inter_check_delay); + } +} +#endif + +#ifdef LEGACY_CONF /** * Calculate host scheduling params. */ @@ -535,7 +920,66 @@ void applier::scheduler::_calculate_host_scheduling_params() { _calculate_host_inter_check_delay(_config->host_inter_check_delay_method()); } +#else +/** + * Calculate host scheduling params. + */ +void applier::scheduler::_calculate_host_scheduling_params() { + engine_logger(dbg_events, most) + << "Determining host scheduling parameters..."; + events_logger->debug("Determining host scheduling parameters..."); + + // get current time. + time_t const now(time(nullptr)); + + // get total hosts and total scheduled hosts. + for (host_map::const_iterator it(engine::host::hosts.begin()), + end(engine::host::hosts.end()); + it != end; ++it) { + com::centreon::engine::host& hst(*it->second); + + bool schedule_check(true); + if (!hst.check_interval() || !hst.active_checks_enabled()) + schedule_check = false; + else { + timezone_locker lock(hst.get_timezone()); + if (!check_time_against_period(now, hst.check_period_ptr)) { + time_t next_valid_time(0); + get_next_valid_time(now, &next_valid_time, hst.check_period_ptr); + if (now == next_valid_time) + schedule_check = false; + } + } + if (schedule_check) { + hst.set_should_be_scheduled(true); + ++scheduling_info.total_scheduled_hosts; + scheduling_info.host_check_interval_total += + static_cast<unsigned long>(hst.check_interval()); + } else { + hst.set_should_be_scheduled(false); + engine_logger(dbg_events, more) + << "Host " << hst.name() << " should not be scheduled."; + events_logger->debug("Host {} should not be scheduled.", hst.name()); + } + + ++scheduling_info.total_hosts; + } + + // Default max host check spread (in minutes). + scheduling_info.max_host_check_spread = _pb_config->max_host_check_spread(); + + // Adjust the check interval total to correspond to + // the interval length. + scheduling_info.host_check_interval_total = + scheduling_info.host_check_interval_total * _pb_config->interval_length(); + + _calculate_host_inter_check_delay( + _pb_config->host_inter_check_delay_method()); +} +#endif + +#ifdef LEGACY_CONF /** * How should we determine the service inter-check delay * to use (in seconds). @@ -600,7 +1044,65 @@ void applier::scheduler::_calculate_service_inter_check_delay( scheduling_info.service_inter_check_delay); } } +#else +/** + * How should we determine the service inter-check delay + * to use (in seconds). + * + * @param[in] method The method to use to calculate inter check delay. + */ +void applier::scheduler::_calculate_service_inter_check_delay( + const configuration::InterCheckDelay& method) { + switch (method.type()) { + case configuration::InterCheckDelay::none: + scheduling_info.service_inter_check_delay = 0.0; + break; + + case configuration::InterCheckDelay::dumb: + scheduling_info.service_inter_check_delay = 1.0; + break; + + case configuration::InterCheckDelay::user: + // the user specified a delay, so don't try to calculate one. + break; + + case configuration::InterCheckDelay::smart: + default: + // be smart and calculate the best delay to use to + // minimize local load... + if (scheduling_info.total_scheduled_services > 0 && + scheduling_info.service_check_interval_total > 0) { + // calculate the average inter check delay (in seconds) needed + // to evenly space the service checks out. + scheduling_info.average_service_inter_check_delay = + scheduling_info.average_service_check_interval / + (double)scheduling_info.total_scheduled_services; + + // set the global inter check delay value. + scheduling_info.service_inter_check_delay = + scheduling_info.average_service_inter_check_delay; + + // calculate max inter check delay and see if we should use that + // instead. + double const max_inter_check_delay( + (scheduling_info.max_service_check_spread * 60) / + (double)scheduling_info.total_scheduled_services); + if (scheduling_info.service_inter_check_delay > max_inter_check_delay) + scheduling_info.service_inter_check_delay = max_inter_check_delay; + } else + scheduling_info.service_inter_check_delay = 0.0; + + events_logger->debug("Total scheduled service checks: {}", + scheduling_info.total_scheduled_services); + events_logger->debug("Average service check interval: {:.2f} sec", + scheduling_info.average_service_check_interval); + events_logger->debug("Service inter-check delay: {:.2f} sec", + scheduling_info.service_inter_check_delay); + } +} +#endif +#ifdef LEGACY_CONF /** * How should we determine the service interleave factor. * @@ -634,7 +1136,35 @@ void applier::scheduler::_calculate_service_interleave_factor( scheduling_info.service_interleave_factor); } } +#else +/** + * How should we determine the service interleave factor. + * + * @param[in] method The method to use to calculate interleave factor. + */ +void applier::scheduler::_calculate_service_interleave_factor( + const configuration::InterleaveFactor& method) { + switch (method.type()) { + case configuration::InterleaveFactor::ilf_user: + // the user supplied a value, so don't do any calculation. + break; + + case configuration::InterleaveFactor::ilf_smart: + default: + scheduling_info.service_interleave_factor = + (int)(ceil(scheduling_info.average_scheduled_services_per_host)); + + events_logger->debug("Total scheduled service checks: {}", + scheduling_info.total_scheduled_services); + events_logger->debug("Total hosts: {}", + scheduling_info.total_hosts); + events_logger->debug("Service Interleave factor: {}", + scheduling_info.service_interleave_factor); + } +} +#endif +#ifdef LEGACY_CONF /** * Calculate service scheduling params. */ @@ -711,6 +1241,80 @@ void applier::scheduler::_calculate_service_scheduling_params() { _calculate_service_interleave_factor( _config->service_interleave_factor_method()); } +#else +/** + * Calculate service scheduling params. + */ +void applier::scheduler::_calculate_service_scheduling_params() { + events_logger->debug("Determining service scheduling parameters..."); + + // get current time. + time_t const now(time(nullptr)); + + // get total services and total scheduled services. + for (service_id_map::const_iterator + it(engine::service::services_by_id.begin()), + end(engine::service::services_by_id.end()); + it != end; ++it) { + engine::service& svc(*it->second); + + bool schedule_check(true); + if (!svc.check_interval() || !svc.active_checks_enabled()) + schedule_check = false; + + { + timezone_locker lock(svc.get_timezone()); + if (!check_time_against_period(now, svc.check_period_ptr)) { + time_t next_valid_time(0); + get_next_valid_time(now, &next_valid_time, svc.check_period_ptr); + if (now == next_valid_time) + schedule_check = false; + } + } + + if (schedule_check) { + svc.set_should_be_scheduled(true); + ++scheduling_info.total_scheduled_services; + scheduling_info.service_check_interval_total += + static_cast<unsigned long>(svc.check_interval()); + } else { + svc.set_should_be_scheduled(false); + events_logger->debug("Service {} on host {} should not be scheduled.", + svc.description(), svc.get_hostname()); + } + ++scheduling_info.total_services; + } + + // default max service check spread (in minutes). + scheduling_info.max_service_check_spread = + _pb_config->max_service_check_spread(); + + // used later in inter-check delay calculations. + scheduling_info.service_check_interval_total = + scheduling_info.service_check_interval_total * + _pb_config->interval_length(); + + if (scheduling_info.total_hosts) { + scheduling_info.average_services_per_host = + scheduling_info.total_services / (double)scheduling_info.total_hosts; + scheduling_info.average_scheduled_services_per_host = + scheduling_info.total_scheduled_services / + (double)scheduling_info.total_hosts; + } + + // calculate rolling average execution time (available + // from retained state information). + if (scheduling_info.total_scheduled_services) + scheduling_info.average_service_check_interval = + scheduling_info.service_check_interval_total / + (double)scheduling_info.total_scheduled_services; + + _calculate_service_inter_check_delay( + _pb_config->service_inter_check_delay_method()); + _calculate_service_interleave_factor( + _pb_config->service_interleave_factor_method()); +} +#endif /** * Create and register new misc event. @@ -733,6 +1337,7 @@ timed_event* applier::scheduler::_create_misc_event(int type, return retval; } +#ifdef LEGACY_CONF /** * Get engine hosts struct with configuration hosts objects. * @@ -761,7 +1366,32 @@ std::vector<com::centreon::engine::host*> applier::scheduler::_get_hosts( } return retval; } +#else +/** + * Get engine hosts struct with configuration hosts objects. + * + * @param[in] hst_ids The list of host IDs to get. + * @param[in] throw_if_not_found Flag to throw if an host is not + * found. + */ +std::vector<com::centreon::engine::host*> applier::scheduler::_get_hosts( + const std::vector<uint64_t>& hst_ids, + bool throw_if_not_found) { + std::vector<engine::host*> retval; + for (auto host_id : hst_ids) { + auto it_hst = engine::host::hosts_by_id.find(host_id); + if (it_hst == engine::host::hosts_by_id.end()) { + if (throw_if_not_found) + throw engine_error() + << "Could not schedule non-existing host with ID " << host_id; + } else + retval.push_back(it_hst->second.get()); + } + return retval; +} +#endif +#ifdef LEGACY_CONF /** * Get engine services struct with configuration services objects. * @@ -791,7 +1421,35 @@ std::vector<com::centreon::engine::service*> applier::scheduler::_get_services( } return retval; } +#else +/** + * Get engine services struct with configuration services objects. + * + * @param[in] svc_ids The list of configuration service IDs + * objects. + * @param[in] throw_if_not_found Flag to throw if an host is not + * found. + * @return a vector of services. + */ +std::vector<com::centreon::engine::service*> applier::scheduler::_get_services( + const std::vector<std::pair<uint64_t, uint64_t>>& svc_ids, + bool throw_if_not_found) { + std::vector<com::centreon::engine::service*> retval; + for (auto& p : svc_ids) { + service_id_map::const_iterator it_svc = + engine::service::services_by_id.find({p.first, p.second}); + if (it_svc == engine::service::services_by_id.end()) { + if (throw_if_not_found) + throw engine_error() << fmt::format( + "Cannot schedule non-existing service ({},{})", p.first, p.second); + } else + retval.push_back(it_svc->second.get()); + } + return retval; +} +#endif +#ifdef LEGACY_CONF /** * Get engine services struct with configuration services objects. * @@ -821,6 +1479,34 @@ applier::scheduler::_get_anomalydetections(set_anomalydetection const& ad_cfg, } return retval; } +#else +/** + * Get engine services struct with configuration services objects. + * + * @param[in] svc_cfg The list of configuration services objects. + * @param[in] throw_if_not_found Flag to throw if an host is not + * found. + * @return a vector of services. + */ +std::vector<com::centreon::engine::service*> +applier::scheduler::_get_anomalydetections( + const std::vector<std::pair<uint64_t, uint64_t>>& ad_ids, + bool throw_if_not_found) { + std::vector<engine::service*> retval; + for (auto& p : ad_ids) { + service_id_map::const_iterator it_svc = + engine::service::services_by_id.find({p.first, p.second}); + if (it_svc == engine::service::services_by_id.end()) { + if (throw_if_not_found) + throw engine_error() << fmt::format( + "Cannot schedule non-existing anomalydetection ({},{})", p.first, + p.second); + } else + retval.push_back(it_svc->second.get()); + } + return retval; +} +#endif /** * Remove misc event. diff --git a/engine/src/configuration/applier/service.cc b/engine/src/configuration/applier/service.cc index 7cdeb69ea28..460b6ba8f18 100644 --- a/engine/src/configuration/applier/service.cc +++ b/engine/src/configuration/applier/service.cc @@ -22,66 +22,22 @@ #include "com/centreon/engine/broker.hh" #include "com/centreon/engine/config.hh" #include "com/centreon/engine/configuration/applier/scheduler.hh" -#include "com/centreon/engine/configuration/applier/state.hh" #include "com/centreon/engine/downtimes/downtime_manager.hh" #include "com/centreon/engine/exceptions/error.hh" #include "com/centreon/engine/globals.hh" #include "com/centreon/engine/logging/logger.hh" #include "com/centreon/engine/severity.hh" +#ifndef LEGACY_CONF +#include "common/engine_conf/severity_helper.hh" +#include "common/engine_conf/state.pb.h" +#endif using namespace com::centreon; using namespace com::centreon::engine; using namespace com::centreon::engine::downtimes; using namespace com::centreon::engine::configuration; -/** - * Check if the service group name matches the configuration object. - */ -class servicegroup_name_comparator { - public: - servicegroup_name_comparator(std::string const& servicegroup_name) { - _servicegroup_name = servicegroup_name; - } - - bool operator()(std::shared_ptr<configuration::servicegroup> sg) { - return _servicegroup_name == sg->servicegroup_name(); - } - - private: - std::string _servicegroup_name; -}; - -/** - * Default constructor. - */ -applier::service::service() {} - -/** - * Copy constructor. - * - * @param[in] right Object to copy. - */ -applier::service::service(applier::service const& right) { - (void)right; -} - -/** - * Destructor. - */ -applier::service::~service() {} - -/** - * Assignment operator. - * - * @param[in] right Object to copy. - * - * @return This object. - */ -applier::service& applier::service::operator=(applier::service const& right) { - (void)right; - return *this; -} - +#ifdef LEGACY_CONF /** * Add new service. * @@ -183,9 +139,8 @@ void applier::service::add_object(configuration::service const& obj) { if (it->second.is_sent()) { timeval tv(get_broker_timestamp(nullptr)); - broker_custom_variable(NEBTYPE_SERVICECUSTOMVARIABLE_ADD, svc, - it->first.c_str(), it->second.value().c_str(), - &tv); + broker_custom_variable(NEBTYPE_SERVICECUSTOMVARIABLE_ADD, svc, it->first, + it->second.value(), &tv); } } @@ -220,7 +175,129 @@ void applier::service::add_object(configuration::service const& obj) { broker_adaptive_service_data(NEBTYPE_SERVICE_ADD, NEBFLAG_NONE, NEBATTR_NONE, svc, MODATTR_ALL); } +#else +/** + * @brief Add a new service. + * + * @param obj The new service protobuf configuration to add into the monitoring + * engine. + */ +void applier::service::add_object(const configuration::Service& obj) { + // Check service. + if (obj.host_name().empty()) + throw engine_error() << fmt::format( + "Could not create service '{}' with no host defined", + obj.service_description()); + else if (obj.host_id() == 0) + throw engine_error() << fmt::format( + "No host_id available for the host '{}' - unable to create service " + "'{}'", + obj.host_name(), obj.service_description()); + + // Logging. + config_logger->debug("Creating new service '{}' of host '{}'.", + obj.service_description(), obj.host_name()); + + // Add service to the global configuration set. + auto* cfg_svc = pb_config.add_services(); + cfg_svc->CopyFrom(obj); + // Create service. + engine::service* svc{add_service( + obj.host_id(), obj.service_id(), obj.host_name(), + obj.service_description(), obj.display_name(), obj.check_period(), + static_cast<engine::service::service_state>(obj.initial_state()), + obj.max_check_attempts(), obj.check_interval(), obj.retry_interval(), + obj.notification_interval(), obj.first_notification_delay(), + obj.recovery_notification_delay(), obj.notification_period(), + static_cast<bool>(obj.notification_options() & action_svc_ok), + static_cast<bool>(obj.notification_options() & action_svc_unknown), + static_cast<bool>(obj.notification_options() & action_svc_warning), + static_cast<bool>(obj.notification_options() & action_svc_critical), + static_cast<bool>(obj.notification_options() & action_svc_flapping), + static_cast<bool>(obj.notification_options() & action_svc_downtime), + obj.notifications_enabled(), obj.is_volatile(), obj.event_handler(), + obj.event_handler_enabled(), obj.check_command(), obj.checks_active(), + obj.checks_passive(), obj.flap_detection_enabled(), + obj.low_flap_threshold(), obj.high_flap_threshold(), + static_cast<bool>(obj.flap_detection_options() & action_svc_ok), + static_cast<bool>(obj.flap_detection_options() & action_svc_warning), + static_cast<bool>(obj.flap_detection_options() & action_svc_unknown), + static_cast<bool>(obj.flap_detection_options() & action_svc_critical), + static_cast<bool>(obj.stalking_options() & action_svc_ok), + static_cast<bool>(obj.stalking_options() & action_svc_warning), + static_cast<bool>(obj.stalking_options() & action_svc_unknown), + static_cast<bool>(obj.stalking_options() & action_svc_critical), + obj.process_perf_data(), obj.check_freshness(), obj.freshness_threshold(), + obj.notes(), obj.notes_url(), obj.action_url(), obj.icon_image(), + obj.icon_image_alt(), obj.retain_status_information(), + obj.retain_nonstatus_information(), obj.obsess_over_service(), + obj.timezone(), obj.icon_id())}; + if (!svc) + throw engine_error() << fmt::format( + "Could not register service '{}' of host '{}'", + obj.service_description(), obj.host_name()); + svc->set_initial_notif_time(0); + engine::service::services[{obj.host_name(), obj.service_description()}] + ->set_host_id(obj.host_id()); + engine::service::services[{obj.host_name(), obj.service_description()}] + ->set_service_id(obj.service_id()); + svc->set_acknowledgement_timeout(obj.acknowledgement_timeout() * + pb_config.interval_length()); + svc->set_last_acknowledgement(0); + + // Add contacts. + for (auto& c : obj.contacts().data()) + svc->mut_contacts().insert({c, nullptr}); + + // Add contactgroups. + for (auto& cg : obj.contactgroups().data()) + svc->get_contactgroups().insert({cg, nullptr}); + + // Add custom variables. + for (auto& cv : obj.customvariables()) { + svc->custom_variables.emplace( + cv.name(), engine::customvariable(cv.value(), cv.is_sent())); + + if (cv.is_sent()) { + timeval tv(get_broker_timestamp(nullptr)); + broker_custom_variable(NEBTYPE_SERVICECUSTOMVARIABLE_ADD, svc, cv.name(), + cv.value(), &tv); + } + } + + // Add severity. + if (obj.severity_id()) { + configuration::severity_helper::key_type k = {obj.severity_id(), + SeverityType::service}; + auto sv = engine::severity::severities.find(k); + if (sv == engine::severity::severities.end()) + throw engine_error() << fmt::format( + "Could not add the severity ({}, {}) to the service '{}' of host " + "'{}'", + k.first, k.second, obj.service_description(), obj.host_name()); + svc->set_severity(sv->second); + } + + // add tags + for (auto& t : obj.tags()) { + auto k = std::make_pair(t.first(), t.second()); + tag_map::iterator it_tag{engine::tag::tags.find(k)}; + if (it_tag == engine::tag::tags.end()) + throw engine_error() << fmt::format( + "Could not find tag ({}, {}) on which to apply service ({}, {})", + k.first, k.second, obj.host_id(), obj.service_id()); + else + svc->mut_tags().emplace_front(it_tag->second); + } + + // Notify event broker. + broker_adaptive_service_data(NEBTYPE_SERVICE_ADD, NEBFLAG_NONE, NEBATTR_NONE, + svc, MODATTR_ALL); +} +#endif + +#ifdef LEGACY_CONF /** * Expand a service object. * @@ -253,7 +330,41 @@ void applier::service::expand_objects(configuration::state& s) { // Set expanded services in configuration state. s.mut_services() = std::move(expanded); } +#else +/** + * Expand a service object. + * + * @param[in,out] s State being applied. + */ +void applier::service::expand_objects(configuration::State& s) { + std::list<std::unique_ptr<Service>> expanded; + // Let's consider all the macros defined in s. + absl::flat_hash_set<std::string_view> cvs; + for (auto& cv : s.macros_filter().data()) + cvs.emplace(cv); + + absl::flat_hash_map<std::string_view, configuration::Hostgroup*> hgs; + for (auto& hg : *s.mutable_hostgroups()) + hgs.emplace(hg.hostgroup_name(), &hg); + + // Browse all services. + for (auto& service_cfg : *s.mutable_services()) { + // Should custom variables be sent to broker ? + for (auto& cv : *service_cfg.mutable_customvariables()) { + if (!s.enable_macros_filter() || cvs.contains(cv.name())) + cv.set_is_sent(true); + } + // Expand membershipts. + _expand_service_memberships(service_cfg, s); + + // Inherits special vars. + _inherits_special_vars(service_cfg, s); + } +} +#endif + +#ifdef LEGACY_CONF /** * Modified service. * @@ -432,7 +543,7 @@ void applier::service::modify_object(configuration::service const& obj) { if (c.second.is_sent()) { timeval tv(get_broker_timestamp(nullptr)); broker_custom_variable(NEBTYPE_SERVICECUSTOMVARIABLE_DELETE, s.get(), - c.first.c_str(), c.second.value().c_str(), &tv); + c.first, c.second.value(), &tv); } } s->custom_variables.clear(); @@ -444,7 +555,7 @@ void applier::service::modify_object(configuration::service const& obj) { if (c.second.is_sent()) { timeval tv(get_broker_timestamp(nullptr)); broker_custom_variable(NEBTYPE_SERVICECUSTOMVARIABLE_ADD, s.get(), - c.first.c_str(), c.second.value().c_str(), &tv); + c.first, c.second.value(), &tv); } } } @@ -480,7 +591,228 @@ void applier::service::modify_object(configuration::service const& obj) { broker_adaptive_service_data(NEBTYPE_SERVICE_UPDATE, NEBFLAG_NONE, NEBATTR_NONE, s.get(), MODATTR_ALL); } +#else +/** + * @brief Modify a service configuration and the real time associated service. + * + * @param[in] old_obj The service to modify into the monitoring + * engine. + * @param[in] new_obj The new service to apply. + */ +void applier::service::modify_object(configuration::Service* old_obj, + const configuration::Service& new_obj) { + const std::string& host_name(old_obj->host_name()); + const std::string& service_description(old_obj->service_description()); + + // Logging. + config_logger->debug("Modifying service '{}' of host '{}'.", + service_description, host_name); + + // Find service object. + service_id_map::iterator it_obj = engine::service::services_by_id.find( + {old_obj->host_id(), old_obj->service_id()}); + if (it_obj == engine::service::services_by_id.end()) + throw engine_error() << fmt::format( + "Could not modify non-existing service object '{}' of host '{}'", + service_description, host_name); + std::shared_ptr<engine::service> s = it_obj->second; + + // Modify properties. + if (it_obj->second->get_hostname() != new_obj.host_name() || + it_obj->second->description() != new_obj.service_description()) { + engine::service::services.erase( + {it_obj->second->get_hostname(), it_obj->second->description()}); + engine::service::services.insert( + {{new_obj.host_name(), new_obj.service_description()}, it_obj->second}); + } + + s->set_hostname(new_obj.host_name()); + s->set_description(new_obj.service_description()); + s->set_display_name(new_obj.display_name()); + s->set_check_command(new_obj.check_command()); + s->set_event_handler(new_obj.event_handler()); + s->set_event_handler_enabled(new_obj.event_handler_enabled()); + s->set_initial_state( + static_cast<engine::service::service_state>(new_obj.initial_state())); + s->set_check_interval(new_obj.check_interval()); + s->set_retry_interval(new_obj.retry_interval()); + s->set_max_attempts(new_obj.max_check_attempts()); + + s->set_notify_on( + (new_obj.notification_options() & action_svc_unknown ? notifier::unknown + : notifier::none) | + (new_obj.notification_options() & action_svc_warning ? notifier::warning + : notifier::none) | + (new_obj.notification_options() & action_svc_critical ? notifier::critical + : notifier::none) | + (new_obj.notification_options() & action_svc_ok ? notifier::ok + : notifier::none) | + (new_obj.notification_options() & action_svc_flapping + ? (notifier::flappingstart | notifier::flappingstop | + notifier::flappingdisabled) + : notifier::none) | + (new_obj.notification_options() & action_svc_downtime ? notifier::downtime + : notifier::none)); + + s->set_notification_interval( + static_cast<double>(new_obj.notification_interval())); + s->set_first_notification_delay( + static_cast<double>(new_obj.first_notification_delay())); + + s->add_stalk_on(new_obj.stalking_options() & action_svc_ok ? notifier::ok + : notifier::none); + s->add_stalk_on(new_obj.stalking_options() & action_svc_warning + ? notifier::warning + : notifier::none); + s->add_stalk_on(new_obj.stalking_options() & action_svc_unknown + ? notifier::unknown + : notifier::none); + s->add_stalk_on(new_obj.stalking_options() & action_svc_critical + ? notifier::critical + : notifier::none); + s->set_notification_period(new_obj.notification_period()); + s->set_check_period(new_obj.check_period()); + s->set_flap_detection_enabled(new_obj.flap_detection_enabled()); + s->set_low_flap_threshold(new_obj.low_flap_threshold()); + s->set_high_flap_threshold(new_obj.high_flap_threshold()); + + s->set_flap_detection_on(notifier::none); + s->add_flap_detection_on(new_obj.flap_detection_options() & action_svc_ok + ? notifier::ok + : notifier::none); + s->add_flap_detection_on(new_obj.flap_detection_options() & action_svc_warning + ? notifier::warning + : notifier::none); + s->add_flap_detection_on(new_obj.flap_detection_options() & action_svc_unknown + ? notifier::unknown + : notifier::none); + s->add_flap_detection_on(new_obj.flap_detection_options() & + action_svc_critical + ? notifier::critical + : notifier::none); + + s->set_process_performance_data( + static_cast<int>(new_obj.process_perf_data())); + s->set_check_freshness(new_obj.check_freshness()); + s->set_freshness_threshold(new_obj.freshness_threshold()); + s->set_accept_passive_checks(new_obj.checks_passive()); + s->set_event_handler(new_obj.event_handler()); + s->set_checks_enabled(new_obj.checks_active()); + s->set_retain_status_information( + static_cast<bool>(new_obj.retain_status_information())); + s->set_retain_nonstatus_information( + static_cast<bool>(new_obj.retain_nonstatus_information())); + s->set_notifications_enabled(new_obj.notifications_enabled()); + s->set_obsess_over(new_obj.obsess_over_service()); + s->set_notes(new_obj.notes()); + s->set_notes_url(new_obj.notes_url()); + s->set_action_url(new_obj.action_url()); + s->set_icon_image(new_obj.icon_image()); + s->set_icon_image_alt(new_obj.icon_image_alt()); + s->set_is_volatile(new_obj.is_volatile()); + s->set_timezone(new_obj.timezone()); + s->set_host_id(new_obj.host_id()); + s->set_service_id(new_obj.service_id()); + s->set_acknowledgement_timeout(new_obj.acknowledgement_timeout() * + pb_config.interval_length()); + s->set_recovery_notification_delay(new_obj.recovery_notification_delay()); + + // Contacts. + if (!MessageDifferencer::Equals(new_obj.contacts(), old_obj->contacts())) { + // Delete old contacts. + s->mut_contacts().clear(); + + // Add contacts to host. + for (auto& contact_name : new_obj.contacts().data()) + s->mut_contacts().insert({contact_name, nullptr}); + } + + // Contact groups. + if (!MessageDifferencer::Equals(new_obj.contactgroups(), + old_obj->contactgroups())) { + // Delete old contact groups. + s->get_contactgroups().clear(); + + // Add contact groups to host. + for (auto& cg_name : new_obj.contactgroups().data()) + s->get_contactgroups().insert({cg_name, nullptr}); + } + + // Custom variables. + if (!std::equal( + new_obj.customvariables().begin(), new_obj.customvariables().end(), + old_obj->customvariables().begin(), MessageDifferencer::Equals)) { + for (auto& c : s->custom_variables) { + if (c.second.is_sent()) { + timeval tv(get_broker_timestamp(nullptr)); + broker_custom_variable(NEBTYPE_SERVICECUSTOMVARIABLE_DELETE, s.get(), + c.first.c_str(), c.second.value().c_str(), &tv); + } + } + s->custom_variables.clear(); + + for (auto& c : new_obj.customvariables()) { + s->custom_variables[c.name()] = c.value(); + + if (c.is_sent()) { + timeval tv(get_broker_timestamp(nullptr)); + broker_custom_variable(NEBTYPE_SERVICECUSTOMVARIABLE_ADD, s.get(), + c.name(), c.value(), &tv); + } + } + } + + // Severity. + if (new_obj.severity_id()) { + configuration::severity_helper::key_type k = {new_obj.severity_id(), + SeverityType::service}; + auto sv = engine::severity::severities.find(k); + if (sv == engine::severity::severities.end()) + throw engine_error() << "Could not update the severity (" << k.first + << ", " << k.second << ") to the service '" + << new_obj.service_description() << "' of host '" + << new_obj.host_name() << "'"; + s->set_severity(sv->second); + } else + s->set_severity(nullptr); + + // add tags + bool tags_changed = false; + if (old_obj->tags().size() == new_obj.tags().size()) { + for (auto new_it = new_obj.tags().begin(), old_it = old_obj->tags().begin(); + old_it != old_obj->tags().end() && new_it != new_obj.tags().end(); + ++old_it, ++new_it) { + if (new_it->first() != old_it->first() || + new_it->second() != old_it->second()) { + tags_changed = true; + break; + } + } + } else + tags_changed = true; + + if (tags_changed) { + s->mut_tags().clear(); + old_obj->mutable_tags()->CopyFrom(new_obj.tags()); + for (auto& t : new_obj.tags()) { + tag_map::iterator it_tag = + engine::tag::tags.find({t.first(), t.second()}); + if (it_tag == engine::tag::tags.end()) + throw engine_error() << fmt::format( + "Could not find tag '{}' on which to apply service ({}, {})", + t.first(), new_obj.host_id(), new_obj.service_id()); + else + s->mut_tags().emplace_front(it_tag->second); + } + } + // Notify event broker. + broker_adaptive_service_data(NEBTYPE_SERVICE_UPDATE, NEBFLAG_NONE, + NEBATTR_NONE, s.get(), MODATTR_ALL); +} +#endif + +#ifdef LEGACY_CONF /** * Remove old service. * @@ -542,7 +874,70 @@ void applier::service::remove_object(configuration::service const& obj) { // Remove service from the global configuration set. config->mut_services().erase(obj); } +#else +/** + * Remove old service. + * + * @param[in] obj The new service to remove from the monitoring + * engine. + */ +void applier::service::remove_object(ssize_t idx) { + Service& obj = pb_config.mutable_services()->at(idx); + const std::string& host_name = obj.host_name(); + const std::string& service_description = obj.service_description(); + + // Logging. + config_logger->debug("Removing service '{}' of host '{}'.", + service_description, host_name); + + // Find anomaly detections depending on this service + for (auto cad : pb_config.anomalydetections()) { + if (cad.host_id() == obj.host_id() && + cad.dependent_service_id() == obj.service_id()) { + auto ad = engine::service::services_by_id.find( + {cad.host_id(), cad.service_id()}); + if (ad != engine::service::services_by_id.end()) + std::static_pointer_cast<engine::anomalydetection>(ad->second) + ->set_dependent_service(nullptr); + } + } + // Find service. + auto it = + engine::service::services_by_id.find({obj.host_id(), obj.service_id()}); + if (it != engine::service::services_by_id.end()) { + auto svc = it->second; + + // Remove service comments. + comment::delete_service_comments(obj.host_id(), obj.service_id()); + + // Remove service downtimes. + downtime_manager::instance() + .delete_downtime_by_hostname_service_description_start_time_comment( + host_name, service_description, {false, (time_t)0}, ""); + + // Remove events related to this service. + applier::scheduler::instance().remove_service(obj.host_id(), + obj.service_id()); + + // remove service from servicegroup->members + for (auto& it_s : svc->get_parent_groups()) + it_s->members.erase({host_name, service_description}); + + // Notify event broker. + broker_adaptive_service_data(NEBTYPE_SERVICE_DELETE, NEBFLAG_NONE, + NEBATTR_NONE, svc.get(), MODATTR_ALL); + + // Unregister service. + engine::service::services.erase({host_name, service_description}); + engine::service::services_by_id.erase(it); + } + + // Remove service from the global configuration set. + pb_config.mutable_services()->DeleteSubrange(idx, 1); +} +#endif +#ifdef LEGACY_CONF /** * Resolve a service. * @@ -579,7 +974,44 @@ void applier::service::resolve_object(configuration::service const& obj, // Resolve service. it->second->resolve(err.config_warnings, err.config_errors); } +#else +/** + * Resolve a service. + * + * @param[in] obj Service object. + */ +void applier::service::resolve_object(const configuration::Service& obj, + error_cnt& err) { + // Logging. + config_logger->debug("Resolving service '{}' of host '{}'.", + obj.service_description(), obj.host_name()); + + // Find service. + service_id_map::iterator it = + engine::service::services_by_id.find({obj.host_id(), obj.service_id()}); + if (engine::service::services_by_id.end() == it) + throw engine_error() << "Cannot resolve non-existing service '" + << obj.service_description() << "' of host '" + << obj.host_name() << "'"; + + // Remove service group links. + it->second->get_parent_groups().clear(); + + // Find host and adjust its counters. + host_id_map::iterator hst(engine::host::hosts_by_id.find(it->first.first)); + if (hst != engine::host::hosts_by_id.end()) { + hst->second->set_total_services(hst->second->get_total_services() + 1); + hst->second->set_total_service_check_interval( + hst->second->get_total_service_check_interval() + + static_cast<uint64_t>(it->second->check_interval())); + } + + // Resolve service. + it->second->resolve(err.config_warnings, err.config_errors); +} +#endif +#ifdef LEGACY_CONF /** * Expand service instance memberships. * @@ -614,7 +1046,37 @@ void applier::service::_expand_service_memberships(configuration::service& obj, s.servicegroups().insert(backup); } } +#else +/** + * Expand service instance memberships. + * + * @param[in] obj Target service. + * @param[out] s Configuration state. + */ +void applier::service::_expand_service_memberships(configuration::Service& obj, + configuration::State& s) { + absl::flat_hash_map<std::string_view, Servicegroup*> sgs; + for (auto& sg : *s.mutable_servicegroups()) + sgs[sg.servicegroup_name()] = &sg; + + // Browse service groups. + for (auto& sg_name : obj.servicegroups().data()) { + // Find service group. + auto found = sgs.find(sg_name); + if (found == sgs.end()) + throw engine_error() << fmt::format( + "Could not add service '{}' of host '{}' to non-existing service " + "group '{}'", + obj.service_description(), obj.host_name(), sg_name); + + // Add service to service members + fill_pair_string_group(found->second->mutable_members(), obj.host_name(), + obj.service_description()); + } +} +#endif +#ifdef LEGACY_CONF /** * @brief Inherits special variables from host. * @@ -655,3 +1117,47 @@ void applier::service::_inherits_special_vars(configuration::service& obj, obj.timezone(it->timezone()); } } +#else +/** + * @brief Inherits special variables from host. + * + * These special variables, if not defined are inherited from host. + * They are contact_groups, notification_interval and + * notification_period. + * + * @param[in,out] obj Target service. + * @param[in] s Configuration state. + */ +void applier::service::_inherits_special_vars(configuration::Service& obj, + const configuration::State& s) { + // Detect if any special variable has not been defined. + if (!obj.host_id() || obj.contacts().data().empty() || + obj.contactgroups().data().empty() || obj.notification_interval() == 0 || + obj.notification_period().empty() || obj.timezone().empty()) { + // Find host. + auto it = std::find_if(s.hosts().begin(), s.hosts().end(), + [name = obj.host_name()](const Host& h) { + return h.host_name() == name; + }); + if (it == s.hosts().end()) + throw engine_error() << fmt::format( + "Could not inherit special variables for service '{}': host '{}' " + "does not exist", + obj.service_description(), obj.host_name()); + + // Inherits variables. + if (!obj.host_id()) + obj.set_host_id(it->host_id()); + if (obj.contacts().data().empty() && obj.contactgroups().data().empty()) { + obj.mutable_contacts()->CopyFrom(it->contacts()); + obj.mutable_contactgroups()->CopyFrom(it->contactgroups()); + } + if (obj.notification_interval() == 0) + obj.set_notification_interval(it->notification_interval()); + if (obj.notification_period().empty()) + obj.set_notification_period(it->notification_period()); + if (obj.timezone().empty()) + obj.set_timezone(it->timezone()); + } +} +#endif diff --git a/engine/src/configuration/applier/servicedependency.cc b/engine/src/configuration/applier/servicedependency.cc index c756237b3bf..fc78b8dc120 100644 --- a/engine/src/configuration/applier/servicedependency.cc +++ b/engine/src/configuration/applier/servicedependency.cc @@ -24,11 +24,16 @@ #include "com/centreon/engine/exceptions/error.hh" #include "com/centreon/engine/globals.hh" #include "com/centreon/engine/logging/logger.hh" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/object.hh" #include "common/engine_legacy_conf/servicedependency.hh" +#else +#include "common/engine_conf/state.pb.h" +#endif using namespace com::centreon::engine::configuration; +#ifdef LEGACY_CONF /** * Add new service dependency. * @@ -127,7 +132,94 @@ void applier::servicedependency::add_object( // Notify event broker. broker_adaptive_dependency_data(NEBTYPE_SERVICEDEPENDENCY_ADD, sd.get()); } +#else +/** + * Add new service dependency. + * + * @param[in] obj The new servicedependency to add into the monitoring + * engine. + */ +void applier::servicedependency::add_object( + const configuration::Servicedependency& obj) { + // Check service dependency. + if (obj.hosts().data().size() != 1 || !obj.hostgroups().data().empty() || + obj.service_description().data().size() != 1 || + !obj.servicegroups().data().empty() || + obj.dependent_hosts().data().size() != 1 || + !obj.dependent_hostgroups().data().empty() || + obj.dependent_service_description().data().size() != 1 || + !obj.dependent_servicegroups().data().empty()) + throw engine_error() + << "Could not create service " + << "dependency with multiple (dependent) hosts / host groups " + << "/ services / service groups"; + + if (obj.dependency_type() != execution_dependency && + obj.dependency_type() != notification_dependency) + throw engine_error() + << "Could not create unexpanded dependency of service '" + << obj.dependent_service_description().data()[0] << "' of host '" + << obj.dependent_hosts().data()[0] << "' on service '" + << obj.service_description().data()[0] << "' of host '" + << obj.hosts().data()[0] << "'"; + // Logging. + config_logger->debug( + "Creating new service dependency of service '{}' of host '{}' on service " + "'{}' of host '{}'.", + obj.dependent_service_description().data()[0], + obj.dependent_hosts().data()[0], obj.service_description().data()[0], + obj.hosts().data()[0]); + + // Add dependency to the global configuration set. + auto* new_obj = pb_config.add_servicedependencies(); + new_obj->CopyFrom(obj); + + std::shared_ptr<engine::servicedependency> sd; + + if (obj.dependency_type() == execution_dependency) + // Create execution dependency. + sd = std::make_shared<engine::servicedependency>( + configuration::servicedependency_key(obj), + obj.dependent_hosts().data()[0], + obj.dependent_service_description().data()[0], obj.hosts().data()[0], + obj.service_description().data()[0], dependency::execution, + obj.inherits_parent(), + static_cast<bool>(obj.execution_failure_options() & action_sd_ok), + static_cast<bool>(obj.execution_failure_options() & action_sd_warning), + static_cast<bool>(obj.execution_failure_options() & action_sd_unknown), + static_cast<bool>(obj.execution_failure_options() & action_sd_critical), + static_cast<bool>(obj.execution_failure_options() & action_sd_pending), + obj.dependency_period()); + else + // Create notification dependency. + sd = std::make_shared<engine::servicedependency>( + servicedependency_key(obj), obj.dependent_hosts().data()[0], + obj.dependent_service_description().data()[0], obj.hosts().data()[0], + obj.service_description().data()[0], dependency::notification, + obj.inherits_parent(), + static_cast<bool>(obj.notification_failure_options() & action_sd_ok), + static_cast<bool>(obj.notification_failure_options() & + action_sd_warning), + static_cast<bool>(obj.notification_failure_options() & + action_sd_unknown), + static_cast<bool>(obj.notification_failure_options() & + action_sd_critical), + static_cast<bool>(obj.notification_failure_options() & + action_sd_pending), + obj.dependency_period()); + + // Add new items to the global list. + engine::servicedependency::servicedependencies.insert( + {{sd->get_dependent_hostname(), sd->get_dependent_service_description()}, + sd}); + + // Notify event broker. + broker_adaptive_dependency_data(NEBTYPE_SERVICEDEPENDENCY_ADD, sd.get()); +} +#endif + +#ifdef LEGACY_CONF /** * Expand service dependencies. * @@ -210,7 +302,85 @@ void applier::servicedependency::expand_objects(configuration::state& s) { // Set expanded service dependencies in configuration state. s.servicedependencies().swap(expanded); } +#else +/** + * Expand service dependencies. + * + * @param[in,out] s Configuration being applied. + */ +void applier::servicedependency::expand_objects(configuration::State& s) { + // Browse all dependencies. + std::list<std::unique_ptr<Servicedependency>> expanded; + for (auto& dep : s.servicedependencies()) { + // Expand service dependency instances. + if (dep.hosts().data().size() != 1 || !dep.hostgroups().data().empty() || + dep.service_description().data().size() != 1 || + !dep.servicegroups().data().empty() || + dep.dependent_hosts().data().size() != 1 || + !dep.dependent_hostgroups().data().empty() || + dep.dependent_service_description().data().size() != 1 || + !dep.dependent_servicegroups().data().empty() || + dep.dependency_type() == DependencyKind::unknown) { + // Expand depended services. + absl::flat_hash_set<std::pair<std::string, std::string>> + depended_services; + _expand_services(dep.hosts().data(), dep.hostgroups().data(), + dep.service_description().data(), + dep.servicegroups().data(), s, depended_services); + + // Expand dependent services. + absl::flat_hash_set<std::pair<std::string, std::string>> + dependent_services; + _expand_services( + dep.dependent_hosts().data(), dep.dependent_hostgroups().data(), + dep.dependent_service_description().data(), + dep.dependent_servicegroups().data(), s, dependent_services); + + // Browse all depended and dependent services. + for (auto& p1 : depended_services) + for (auto& p2 : dependent_services) { + // Create service dependency instance. + for (int32_t i = 1; i <= 2; i++) { + if (dep.dependency_type() == DependencyKind::unknown || + static_cast<int32_t>(dep.dependency_type()) == i) { + auto sdep = std::make_unique<Servicedependency>(); + sdep->CopyFrom(dep); + sdep->clear_hostgroups(); + sdep->clear_hosts(); + sdep->mutable_hosts()->add_data(p1.first); + sdep->clear_servicegroups(); + sdep->clear_service_description(); + sdep->mutable_service_description()->add_data(p1.second); + sdep->clear_dependent_hostgroups(); + sdep->clear_dependent_hosts(); + sdep->mutable_dependent_hosts()->add_data(p2.first); + sdep->clear_dependent_servicegroups(); + sdep->clear_dependent_service_description(); + sdep->mutable_dependent_service_description()->add_data( + p2.second); + if (i == 2) { + sdep->set_dependency_type(DependencyKind::execution_dependency); + sdep->set_notification_failure_options(0); + } else { + sdep->set_dependency_type( + DependencyKind::notification_dependency); + sdep->set_execution_failure_options(0); + } + expanded.push_back(std::move(sdep)); + } + } + } + } + } + + // Set expanded service dependencies in configuration state. + s.clear_servicedependencies(); + for (auto& e : expanded) + s.mutable_servicedependencies()->AddAllocated(e.release()); +} +#endif +#ifdef LEGACY_CONF /** * @brief Modify service dependency. * @@ -228,7 +398,26 @@ void applier::servicedependency::modify_object( << "or removed, this is likely a software bug that you should " << "report to Centreon Engine developers"; } +#else +/** + * @brief Modify service dependency. + * + * Service dependencies cannot be defined with anything else than their + * full content. Therefore no modification can occur. + * + * @param[in] obj Unused. + */ +void applier::servicedependency::modify_object( + configuration::Servicedependency* old_obj [[maybe_unused]], + const configuration::Servicedependency& new_obj [[maybe_unused]]) { + throw engine_error() + << "Could not modify a service dependency: service dependency objects " + "can only be added or removed, this is likely a software bug that " + "you should report to Centreon Engine developers"; +} +#endif +#ifdef LEGACY_CONF /** * Remove old service dependency. * @@ -257,7 +446,41 @@ void applier::servicedependency::remove_object( // Remove dependency from the global configuration set. config->servicedependencies().erase(obj); } +#else +/** + * Remove old service dependency. + * + * @param[in] obj The service dependency to remove from the monitoring + * engine. + */ +void applier::servicedependency::remove_object(ssize_t idx) { + // Logging. + config_logger->debug("Removing a service dependency."); + // Find service dependency. + auto& obj = pb_config.servicedependencies(idx); + size_t key = servicedependency_key(obj); + + servicedependency_mmap::iterator it = + engine::servicedependency::servicedependencies_find( + std::make_tuple(obj.dependent_hosts().data(0), + obj.dependent_service_description().data(0), key)); + if (it != engine::servicedependency::servicedependencies.end()) { + // Notify event broker. + timeval tv(get_broker_timestamp(nullptr)); + broker_adaptive_dependency_data(NEBTYPE_SERVICEDEPENDENCY_DELETE, + it->second.get()); + + // Remove service dependency from its list. + engine::servicedependency::servicedependencies.erase(it); + } + + // Remove dependency from the global configuration set. + pb_config.mutable_servicedependencies()->DeleteSubrange(idx, 1); +} +#endif + +#ifdef LEGACY_CONF /** * Resolve a servicedependency. * @@ -280,7 +503,33 @@ void applier::servicedependency::resolve_object( // Resolve service dependency. it->second->resolve(err.config_warnings, err.config_errors); } +#else +/** + * Resolve a servicedependency. + * + * @param[in] obj Servicedependency object. + */ +void applier::servicedependency::resolve_object( + const configuration::Servicedependency& obj, + error_cnt& err) { + // Logging. + config_logger->debug("Resolving a service dependency."); + // Find service dependency. + size_t key = configuration::servicedependency_key(obj); + servicedependency_mmap::iterator it = + engine::servicedependency::servicedependencies_find( + {obj.dependent_hosts().data(0), + obj.dependent_service_description().data(0), key}); + if (engine::servicedependency::servicedependencies.end() == it) + throw engine_error() << "Cannot resolve non-existing service dependency"; + + // Resolve service dependency. + it->second->resolve(err.config_warnings, err.config_errors); +} +#endif + +#ifdef LEGACY_CONF /** * Expand services. * @@ -342,3 +591,64 @@ void applier::servicedependency::_expand_services( expanded.insert(*it_member); } } +#else +/** + * Expand services. + * + * @param[in] hst Hosts. + * @param[in] hg Host groups. + * @param[in] svc Service descriptions. + * @param[in] sg Service groups. + * @param[in,out] s Configuration state. + * @param[out] expanded Expanded services. + */ +void applier::servicedependency::_expand_services( + const ::google::protobuf::RepeatedPtrField<std::string>& hst, + const ::google::protobuf::RepeatedPtrField<std::string>& hg, + const ::google::protobuf::RepeatedPtrField<std::string>& svc, + const ::google::protobuf::RepeatedPtrField<std::string>& sg, + configuration::State& s, + absl::flat_hash_set<std::pair<std::string, std::string>>& expanded) { + // Expanded hosts. + absl::flat_hash_set<std::string> all_hosts; + + // Base hosts. + all_hosts.insert(hst.begin(), hst.end()); + + // Host groups. + for (auto& hgn : hg) { + // Find host group + auto found = std::find_if( + s.hostgroups().begin(), s.hostgroups().end(), + [&hgn](const Hostgroup& hgg) { return hgg.hostgroup_name() == hgn; }); + if (found == s.hostgroups().end()) + throw engine_error() << fmt::format("Could not resolve host group '{}'", + hgn); + // Add host group members. + all_hosts.insert(found->members().data().begin(), + found->members().data().end()); + } + + // Hosts * services. + for (auto& h : all_hosts) + for (auto& s : svc) + expanded.insert({h, s}); + + // Service groups. + for (auto& sgn : sg) { + // Find service group. + auto found = + std::find_if(s.servicegroups().begin(), s.servicegroups().end(), + [&sgn](const Servicegroup& sgg) { + return sgg.servicegroup_name() == sgn; + }); + if (found == s.servicegroups().end()) + throw engine_error() << fmt::format( + "Coulx not resolve service group '{}'", sgn); + + // Add service group members. + for (auto& m : found->members().data()) + expanded.insert({m.first(), m.second()}); + } +} +#endif diff --git a/engine/src/configuration/applier/serviceescalation.cc b/engine/src/configuration/applier/serviceescalation.cc index 92aa9062a5f..8321eddd772 100644 --- a/engine/src/configuration/applier/serviceescalation.cc +++ b/engine/src/configuration/applier/serviceescalation.cc @@ -23,10 +23,13 @@ #include "com/centreon/engine/exceptions/error.hh" #include "com/centreon/engine/globals.hh" #include "com/centreon/engine/logging/logger.hh" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/serviceescalation.hh" +#endif using namespace com::centreon::engine::configuration; +#ifdef LEGACY_CONF /** * Add new service escalation. * @@ -91,7 +94,67 @@ void applier::serviceescalation::add_object( it != end; ++it) se->get_contactgroups().insert({*it, nullptr}); } +#else +/** + * Add new service escalation. + * + * @param[in] obj The new service escalation to add into the + * monitoring engine. + */ +void applier::serviceescalation::add_object( + const configuration::Serviceescalation& obj) { + // Check service escalation. + if (obj.hosts().data().size() != 1 || !obj.hostgroups().data().empty() || + obj.service_description().data().size() != 1 || + !obj.servicegroups().data().empty()) { + throw engine_error() << "Could not create service escalation with multiple " + "hosts / host groups / services / service groups: " + << obj.DebugString(); + } + + // Logging. + config_logger->debug("Creating new escalation for service '{}' of host '{}'", + obj.service_description().data()[0], + obj.hosts().data()[0]); + + // Add escalation to the global configuration set. + auto* se_cfg = pb_config.add_serviceescalations(); + se_cfg->CopyFrom(obj); + + size_t key = configuration::serviceescalation_key(obj); + + // Create service escalation. + auto se = std::make_shared<engine::serviceescalation>( + obj.hosts().data()[0], obj.service_description().data()[0], + obj.first_notification(), obj.last_notification(), + obj.notification_interval(), obj.escalation_period(), + ((obj.escalation_options() & action_se_warning) ? notifier::warning + : notifier::none) | + ((obj.escalation_options() & action_se_unknown) ? notifier::unknown + : notifier::none) | + ((obj.escalation_options() & action_se_critical) ? notifier::critical + : notifier::none) | + ((obj.escalation_options() & action_se_recovery) ? notifier::ok + : notifier::none), + key); + + // Add new items to the global list. + engine::serviceescalation::serviceescalations.insert( + {{se->get_hostname(), se->get_description()}, se}); + + // Notify event broker. + timeval tv{get_broker_timestamp(nullptr)}; + broker_adaptive_escalation_data(NEBTYPE_SERVICEESCALATION_ADD, NEBFLAG_NONE, + NEBATTR_NONE, se.get(), &tv); + + // Add contact groups to service escalation. + for (auto& cg : obj.contactgroups().data()) { + se->get_contactgroups().insert({cg, nullptr}); + } +} +#endif +#ifdef LEGACY_CONF /** * Expand all service escalations. * @@ -136,7 +199,78 @@ void applier::serviceescalation::expand_objects(configuration::state& s) { // Set expanded service escalations in configuration state. s.serviceescalations().swap(expanded); } +#else +/** + * Expand all service escalations. + * + * @param[in,out] s Configuration being applied. + */ +void applier::serviceescalation::expand_objects(configuration::State& s) { + std::list<std::unique_ptr<Serviceescalation>> resolved; + // Browse all escalations. + config_logger->debug("Expanding service escalations"); + + for (auto& se : *s.mutable_serviceescalations()) { + /* A set of all the hosts related to this escalation */ + absl::flat_hash_set<std::string> host_names; + for (auto& hname : se.hosts().data()) + host_names.insert(hname); + if (se.hostgroups().data().size() > 0) { + for (auto& hg_name : se.hostgroups().data()) { + auto found_hg = + std::find_if(s.hostgroups().begin(), s.hostgroups().end(), + [&hg_name](const Hostgroup& hg) { + return hg.hostgroup_name() == hg_name; + }); + if (found_hg != s.hostgroups().end()) { + for (auto& h : found_hg->members().data()) + host_names.emplace(h); + } else + throw engine_error() << fmt::format( + "Could not expand non-existing host group '{}'", hg_name); + } + } + + /* A set of all the pairs (hostname, service-description) impacted by this + * escalation. */ + absl::flat_hash_set<std::pair<std::string, std::string>> expanded; + for (auto& hn : host_names) { + for (auto& sn : se.service_description().data()) + expanded.emplace(hn, sn); + } + + for (auto& sg_name : se.servicegroups().data()) { + auto found = + std::find_if(s.servicegroups().begin(), s.servicegroups().end(), + [&sg_name](const Servicegroup& sg) { + return sg.servicegroup_name() == sg_name; + }); + if (found == s.servicegroups().end()) + throw engine_error() + << fmt::format("Could not resolve service group '{}'", sg_name); + + for (auto& m : found->members().data()) + expanded.emplace(m.first(), m.second()); + } + se.mutable_hostgroups()->clear_data(); + se.mutable_hosts()->clear_data(); + se.mutable_servicegroups()->clear_data(); + se.mutable_service_description()->clear_data(); + for (auto& p : expanded) { + resolved.emplace_back(std::make_unique<Serviceescalation>()); + auto& e = resolved.back(); + e->CopyFrom(se); + fill_string_group(e->mutable_hosts(), p.first); + fill_string_group(e->mutable_service_description(), p.second); + } + } + s.clear_serviceescalations(); + for (auto& e : resolved) + s.mutable_serviceescalations()->AddAllocated(e.release()); +} +#endif +#ifdef LEGACY_CONF /** * @brief Modify service escalation. * @@ -154,7 +288,26 @@ void applier::serviceescalation::modify_object( << "or removed, this is likely a software bug that you should " << "report to Centreon Engine developers"; } +#else +/** + * @brief Modify service escalation. + * + * Service escalations cannot be defined with anything else than their + * full content. Therefore no modification can occur. + * + * @param[in] obj Unused. + */ +void applier::serviceescalation::modify_object( + configuration::Serviceescalation* old_obj [[maybe_unused]], + const configuration::Serviceescalation& new_obj [[maybe_unused]]) { + throw engine_error() + << "Could not modify a service escalation: service escalation objects " + "can only be added or removed, this is likely a software bug that you " + "should report to Centreon Engine developers"; +} +#endif +#ifdef LEGACY_CONF /** * Remove old service escalation. * @@ -232,7 +385,75 @@ void applier::serviceescalation::remove_object( /* And we clear the configuration */ config->serviceescalations().erase(obj); } +#else +/** + * Remove old service escalation. + * + * @param[in] obj The service escalation to remove from the monitoring + * engine. + */ +void applier::serviceescalation::remove_object(ssize_t idx) { + // Logging. + config_logger->debug("Removing a service escalation."); + + configuration::Serviceescalation& obj = + pb_config.mutable_serviceescalations()->at(idx); + // Find service escalation. + const std::string& host_name{obj.hosts().data()[0]}; + const std::string& description{obj.service_description().data()[0]}; + /* Let's get a range of escalations for the concerned service */ + auto range{engine::serviceescalation::serviceescalations.equal_range( + {host_name, description})}; + bool service_exists; + + /* Let's get the service... */ + service_map::iterator sit{ + engine::service::services.find({host_name, description})}; + /* ... and its escalations */ + if (sit == engine::service::services.end()) { + config_logger->debug("Cannot find service '{}/{}' - already removed.", + host_name, description); + service_exists = false; + } else + service_exists = true; + + size_t key = serviceescalation_key(obj); + for (serviceescalation_mmap::iterator it = range.first, end = range.second; + it != end; ++it) { + if (it->second->internal_key() == key) { + // We have the serviceescalation to remove. + + // Notify event broker. + timeval tv(get_broker_timestamp(nullptr)); + broker_adaptive_escalation_data(NEBTYPE_SERVICEESCALATION_DELETE, + NEBFLAG_NONE, NEBATTR_NONE, + it->second.get(), &tv); + + if (service_exists) { + config_logger->debug( + "Service '{}/{}' found - removing escalation from it.", host_name, + description); + std::list<escalation*>& srv_escalations = + sit->second->get_escalations(); + /* We need also to remove the escalation from the service */ + srv_escalations.remove_if( + [my_escal = it->second.get()](const escalation* e) { + return e == my_escal; + }); + } + // Remove escalation from the global configuration set. + engine::serviceescalation::serviceescalations.erase(it); + break; + } + } + + /* And we clear the configuration */ + pb_config.mutable_serviceescalations()->DeleteSubrange(idx, 1); +} +#endif + +#ifdef LEGACY_CONF /** * Resolve a serviceescalation. * @@ -268,7 +489,42 @@ void applier::serviceescalation::resolve_object( if (!found) throw engine_error() << "Cannot resolve non-existing service escalation"; } +#else +/** + * Resolve a serviceescalation. + * + * @param[in] obj Serviceescalation object. + */ +void applier::serviceescalation::resolve_object( + const configuration::Serviceescalation& obj, + error_cnt& err) { + // Logging. + config_logger->debug("Resolving a service escalation."); + + // Find service escalation + bool found = false; + const std::string& hostname{obj.hosts().data(0)}; + const std::string& desc{obj.service_description().data(0)}; + auto p = engine::serviceescalation::serviceescalations.equal_range( + {hostname, desc}); + if (p.first == p.second) + throw engine_error() << "Cannot find service escalations concerning host '" + << hostname << "' and service '" << desc << "'"; + size_t key = configuration::serviceescalation_key(obj); + for (serviceescalation_mmap::iterator it = p.first; it != p.second; ++it) { + if (it->second->internal_key() == key) { + found = true; + // Resolve service escalation. + it->second->resolve(err.config_warnings, err.config_errors); + break; + } + } + if (!found) + throw engine_error() << "Cannot resolve non-existing service escalation"; +} +#endif +#ifdef LEGACY_CONF /** * Expand services. * @@ -329,7 +585,9 @@ void applier::serviceescalation::_expand_services( expanded.insert(*it_member); } } +#endif +#ifdef LEGACY_CONF /** * Inherits special variables from the service. * @@ -361,3 +619,4 @@ void applier::serviceescalation::_inherits_special_vars( obj.escalation_period(it->notification_period()); } } +#endif diff --git a/engine/src/configuration/applier/servicegroup.cc b/engine/src/configuration/applier/servicegroup.cc index 8f8e42bf855..53075d9375c 100644 --- a/engine/src/configuration/applier/servicegroup.cc +++ b/engine/src/configuration/applier/servicegroup.cc @@ -59,6 +59,7 @@ applier::servicegroup& applier::servicegroup::operator=( return (*this); } +#ifdef LEGACY_CONF /** * Add new servicegroup. * @@ -95,7 +96,42 @@ void applier::servicegroup::add_object(configuration::servicegroup const& obj) { // Notify event broker. broker_group(NEBTYPE_SERVICEGROUP_ADD, sg.get()); } +#else +/** + * @brief Add a new Service group given as a Protobuf object. + * + * @param obj The new service group to add into the monitoring engine. + */ +void applier::servicegroup::add_object(const configuration::Servicegroup& obj) { + // Logging. + config_logger->debug("Creating new servicegroup '{}'", + obj.servicegroup_name()); + + // Add service group to the global configuration set. + auto* new_obj = pb_config.add_servicegroups(); + new_obj->CopyFrom(obj); + + // Create servicegroup. + auto sg = std::make_shared<engine::servicegroup>( + obj.servicegroup_id(), obj.servicegroup_name(), obj.alias(), obj.notes(), + obj.notes_url(), obj.action_url()); + + // Add new items to the list. + engine::servicegroup::servicegroups.insert({sg->get_group_name(), sg}); + + // Add servicegroup id to the other props. + sg->set_id(obj.servicegroup_id()); + + // Notify event broker. + broker_group(NEBTYPE_SERVICEGROUP_ADD, sg.get()); + + // Apply resolved services on servicegroup. + for (auto& m : obj.members().data()) + sg->members[{m.first(), m.second()}] = nullptr; +} +#endif +#ifdef LEGACY_CONF /** * Expand all service groups. * @@ -116,7 +152,37 @@ void applier::servicegroup::expand_objects(configuration::state& s) { it != end; ++it) s.servicegroups().insert(it->second); } +#else +/** + * Expand all service groups. + * + * @param[in,out] s State being applied. + */ +void applier::servicegroup::expand_objects(configuration::State& s) { + // This set stores resolved service groups. + absl::flat_hash_set<std::string_view> resolved; + + // Here, we store each Servicegroup pointer by its name. + absl::flat_hash_map<std::string_view, configuration::Servicegroup*> + sg_by_name; + for (auto& sg_conf : *s.mutable_servicegroups()) + sg_by_name[sg_conf.servicegroup_name()] = &sg_conf; + + // Each servicegroup can contain servicegroups, that is to mean the services + // in the sub servicegroups are also in our servicegroup. + // So, we iterate through all the servicegroups defined in the configuration, + // and for each one if it has servicegroup members, we fill its service + // members with theirs and then we clear the servicegroup members. At that + // step, a servicegroup is considered as resolved. + for (auto& sg_conf : *s.mutable_servicegroups()) { + if (!resolved.contains(sg_conf.servicegroup_name())) { + _resolve_members(s, &sg_conf, resolved, sg_by_name); + } + } +} +#endif +#ifdef LEGACY_CONF /** * Modify servicegroup. * @@ -179,7 +245,62 @@ void applier::servicegroup::modify_object( // Notify event broker. broker_group(NEBTYPE_SERVICEGROUP_UPDATE, sg); } +#else +/** + * Modify servicegroup. + * + * @param[in] obj The new servicegroup to modify into the monitoring + * engine. + */ +void applier::servicegroup::modify_object( + configuration::Servicegroup* to_modify, + const configuration::Servicegroup& new_object) { + // Logging. + config_logger->debug("Modifying servicegroup '{}'", + to_modify->servicegroup_name()); + + // Find service group object. + servicegroup_map::iterator it_obj = + engine::servicegroup::servicegroups.find(to_modify->servicegroup_name()); + + if (it_obj == engine::servicegroup::servicegroups.end()) + throw engine_error() << fmt::format( + "Could not modify non-existing service group object '{}'", + to_modify->servicegroup_name()); + engine::servicegroup* sg = it_obj->second.get(); + + // Modify properties. + sg->set_id(new_object.servicegroup_id()); + sg->set_action_url(new_object.action_url()); + sg->set_alias(new_object.alias().empty() ? new_object.servicegroup_name() + : new_object.alias()); + sg->set_notes(new_object.notes()); + sg->set_notes_url(new_object.notes_url()); + // Were members modified ? + if (!MessageDifferencer::Equals(new_object.members(), to_modify->members())) { + // Delete all old service group members. + for (service_map_unsafe::iterator it = it_obj->second->members.begin(), + end = it_obj->second->members.end(); + it != end; ++it) { + broker_group_member(NEBTYPE_SERVICEGROUPMEMBER_DELETE, it->second, sg); + } + it_obj->second->members.clear(); + + // Create new service group members. + for (auto& m : new_object.members().data()) + sg->members[{m.first(), m.second()}] = nullptr; + } + + // Update the global configuration set. + to_modify->CopyFrom(new_object); + + // Notify event broker. + broker_group(NEBTYPE_SERVICEGROUP_UPDATE, sg); +} +#endif + +#ifdef LEGACY_CONF /** * Remove old servicegroup. * @@ -207,7 +328,34 @@ void applier::servicegroup::remove_object( // Remove service group from the global configuration state. config->servicegroups().erase(obj); } +#else +/** + * Remove old servicegroup. + * + * @param[in] idw Index of the servicegroup to remove in the configuration. + */ +void applier::servicegroup::remove_object(ssize_t idx) { + // Logging. + auto obj = pb_config.servicegroups(idx); + config_logger->debug("Removing servicegroup '{}'", obj.servicegroup_name()); + // Find service group. + servicegroup_map::iterator it = + engine::servicegroup::servicegroups.find(obj.servicegroup_name()); + if (it != engine::servicegroup::servicegroups.end()) { + // Notify event broker. + broker_group(NEBTYPE_SERVICEGROUP_DELETE, it->second.get()); + + // Remove service dependency from its list. + engine::servicegroup::servicegroups.erase(it); + } + + // Remove service group from the global configuration state. + pb_config.mutable_servicegroups()->DeleteSubrange(idx, 1); +} +#endif + +#ifdef LEGACY_CONF /** * Resolve a servicegroup. * @@ -231,7 +379,27 @@ void applier::servicegroup::resolve_object( // Resolve service group. it->second->resolve(err.config_warnings, err.config_errors); } +#else +void applier::servicegroup::resolve_object( + const configuration::Servicegroup& obj, + error_cnt& err) { + // Logging. + config_logger->debug("Removing service group '{}'", obj.servicegroup_name()); + + // Find service group. + servicegroup_map::const_iterator it = + engine::servicegroup::servicegroups.find(obj.servicegroup_name()); + if (it == engine::servicegroup::servicegroups.end()) + throw engine_error() << fmt::format( + "Cannot resolve non-existing service group '{}'", + obj.servicegroup_name()); + + // Resolve service group. + it->second->resolve(err.config_warnings, err.config_errors); +} +#endif +#ifdef LEGACY_CONF /** * Resolve members of a service group. * @@ -279,3 +447,41 @@ void applier::servicegroup::_resolve_members( } } } +#else +/** + * @brief Resolve the servicegroup sg_conf, so for each of its servicegroup + * member, we get all its service members and copy them into sg_conf. + * Once this done, resolved is completed with the name of sg_conf. + * + * @param s The full configuration for this poller. + * @param sg_conf The servicegroup configuration to resolve. + * @param resolved The set of servicegroup configurations already resolved. + * @param sg_by_name A const table of servicegroup configurations indexed by + * their name. + */ +void applier::servicegroup::_resolve_members( + configuration::State& s, + configuration::Servicegroup* sg_conf, + absl::flat_hash_set<std::string_view>& resolved, + const absl::flat_hash_map<std::string_view, configuration::Servicegroup*>& + sg_by_name) { + for (auto& sgm : sg_conf->servicegroup_members().data()) { + configuration::Servicegroup* sgm_conf = + sg_by_name.at(std::string_view(sgm)); + if (sgm_conf == nullptr) + throw engine_error() << fmt::format( + "Could not add non-existing service group member '{}' to service " + "group '{}'", + sgm, sg_conf->servicegroup_name()); + if (!resolved.contains(sgm_conf->servicegroup_name())) + _resolve_members(s, sgm_conf, resolved, sg_by_name); + + for (auto& sm : sgm_conf->members().data()) { + fill_pair_string_group(sg_conf->mutable_members(), sm.first(), + sm.second()); + } + } + sg_conf->clear_servicegroup_members(); + resolved.emplace(sg_conf->servicegroup_name()); +} +#endif diff --git a/engine/src/configuration/applier/severity.cc b/engine/src/configuration/applier/severity.cc index fd3018fc8c2..20afd0ac748 100644 --- a/engine/src/configuration/applier/severity.cc +++ b/engine/src/configuration/applier/severity.cc @@ -18,18 +18,25 @@ */ #include "com/centreon/engine/configuration/applier/severity.hh" - #include "com/centreon/engine/broker.hh" #include "com/centreon/engine/config.hh" #include "com/centreon/engine/exceptions/error.hh" #include "com/centreon/engine/globals.hh" #include "com/centreon/engine/severity.hh" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/severity.hh" +#else +#include <google/protobuf/util/message_differencer.h> +#endif using namespace com::centreon; using namespace com::centreon::engine; using namespace com::centreon::engine::configuration; +#ifndef LEGACY_CONF +using MessageDifferencer = ::google::protobuf::util::MessageDifferencer; +#endif +#ifdef LEGACY_CONF /** * Add new severity. * @@ -55,7 +62,36 @@ void applier::severity::add_object(const configuration::severity& obj) { broker_adaptive_severity_data(NEBTYPE_SEVERITY_ADD, sv.get()); } +#else +/** + * @brief Add new severity. + * + * @param obj The new severity to add into the monitoring engine. + */ +void applier::severity::add_object(const configuration::Severity& obj) { + // Logging. + config_logger->debug("Creating new severity ({}, {}).", obj.key().id(), + obj.key().type()); + + // Add severity to the global configuration set. + auto* new_sv = pb_config.add_severities(); + new_sv->CopyFrom(obj); + auto sv{std::make_shared<engine::severity>(obj.key().id(), obj.level(), + obj.icon_id(), obj.severity_name(), + obj.key().type())}; + if (!sv) + throw engine_error() << fmt::format("Could not register severity ({},{})", + obj.key().id(), obj.key().type()); + + // Add new items to the configuration state. + engine::severity::severities.insert({{obj.key().id(), obj.key().type()}, sv}); + + broker_adaptive_severity_data(NEBTYPE_SEVERITY_ADD, sv.get()); +} +#endif + +#ifdef LEGACY_CONF /** * @brief Expand a contact. * @@ -65,7 +101,9 @@ void applier::severity::add_object(const configuration::severity& obj) { * @param[in,out] s Configuration state. */ void applier::severity::expand_objects(configuration::state&) {} +#endif +#ifdef LEGACY_CONF /** * Modify severity. * @@ -106,7 +144,53 @@ void applier::severity::modify_object(const configuration::severity& obj) { config_logger->debug("Severity ({}, {}) did not change", obj.key().first, obj.key().second); } +#else +/** + * @brief Modify severity. + * + * @param to_modify A pointer to the current configuration of a severity. + * @param new_object The new configuration to apply. + */ +void applier::severity::modify_object( + configuration::Severity* to_modify, + const configuration::Severity& new_object) { + // Logging. + config_logger->debug("Modifying severity ({}, {}).", new_object.key().id(), + new_object.key().type()); + + // Find severity object. + severity_map::iterator it_obj = engine::severity::severities.find( + {new_object.key().id(), new_object.key().type()}); + if (it_obj == engine::severity::severities.end()) + throw engine_error() << fmt::format( + "Could not modify non-existing severity object ({}, {})", + new_object.key().id(), new_object.key().type()); + engine::severity* s = it_obj->second.get(); + + // Update the global configuration set. + if (!MessageDifferencer::Equals(*to_modify, new_object)) { + if (to_modify->severity_name() != new_object.severity_name()) { + s->set_name(new_object.severity_name()); + to_modify->set_severity_name(new_object.severity_name()); + } + if (to_modify->level() != new_object.level()) { + s->set_level(new_object.level()); + to_modify->set_level(new_object.level()); + } + if (to_modify->icon_id() != new_object.icon_id()) { + s->set_icon_id(new_object.icon_id()); + to_modify->set_icon_id(new_object.icon_id()); + } + + // Notify event broker. + broker_adaptive_severity_data(NEBTYPE_SEVERITY_UPDATE, s); + } else + config_logger->debug("Severity ({}, {}) did not change", + new_object.key().id(), new_object.key().type()); +} +#endif +#ifdef LEGACY_CONF /** * Remove old severity. * @@ -132,7 +216,40 @@ void applier::severity::remove_object(const configuration::severity& obj) { // Remove severity from the global configuration set. config->mut_severities().erase(obj); } +#else +/** + * @brief Remove old severity at index idx. + * + * @param idx The index of the object to remove. + */ +void applier::severity::remove_object(ssize_t idx) { + const configuration::Severity& obj = pb_config.severities()[idx]; + + // Logging. + + config_logger->debug("Removing severity ({}, {}).", obj.key().id(), + obj.key().type()); + + // Find severity. + severity_map::iterator it = + engine::severity::severities.find({obj.key().id(), obj.key().type()}); + + if (it != engine::severity::severities.end()) { + engine::severity* sv = it->second.get(); + + // Notify event broker. + broker_adaptive_severity_data(NEBTYPE_SEVERITY_DELETE, sv); + + // Erase severity object (this will effectively delete the object). + engine::severity::severities.erase(it); + } + + // Remove severity from the global configuration set. + pb_config.mutable_severities()->DeleteSubrange(idx, 1); +} +#endif +#ifdef LEGACY_CONF /** * Resolve a severity. * @@ -146,3 +263,4 @@ void applier::severity::resolve_object(const configuration::severity& obj) { << fmt::format("({}, {})", obj.key().first, obj.key().second); } +#endif diff --git a/engine/src/configuration/applier/state.cc b/engine/src/configuration/applier/state.cc index 888a3046869..89a968e9e02 100644 --- a/engine/src/configuration/applier/state.cc +++ b/engine/src/configuration/applier/state.cc @@ -50,7 +50,9 @@ #include "com/centreon/engine/retention/applier/state.hh" #include "com/centreon/engine/version.hh" #include "com/centreon/engine/xsddefault.hh" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/command.hh" +#endif #include "common/log_v2/log_v2.hh" using namespace com::centreon; @@ -62,6 +64,7 @@ using com::centreon::engine::logging::broker_sink_mt; static bool has_already_been_loaded(false); +#ifdef LEGACY_CONF /** * Apply new configuration. * @@ -96,6 +99,38 @@ void applier::state::apply(configuration::state& new_cfg, } } } +#else +/** + * Apply new protobuf configuration. + * + * @param[in] new_cfg The new protobuf configuration. + * @param[in] state The retention to use. + */ +void applier::state::apply(configuration::State& new_cfg, + error_cnt& err, + retention::state* state) { + configuration::State save; + save.CopyFrom(pb_config); + try { + _processing_state = state_ready; + _processing(new_cfg, err, state); + } catch (const std::exception& e) { + // If is the first time to load configuration, we don't + // have a valid configuration to restore. + if (!has_already_been_loaded) + throw; + + // If is not the first time, we can restore the old one. + config_logger->error("Cannot apply new configuration: {}", e.what()); + + // Check if we need to restore old configuration. + if (_processing_state == state_error) { + config_logger->debug("configuration: try to restore old configuration"); + _processing(save, err, state); + } + } +} +#endif /** * Get the singleton instance of state applier. @@ -133,13 +168,21 @@ void applier::state::clear() { applier::logging::instance().clear(); _processing_state = state_ready; +#ifdef LEGACY_CONF _config = nullptr; +#endif } /** * Default constructor. */ -applier::state::state() : _config(nullptr), _processing_state(state_ready) {} +applier::state::state() + : +#ifdef LEGACY_CONF + _config(nullptr), +#endif + _processing_state(state_ready) { +} /** * Destructor. @@ -202,6 +245,7 @@ void applier::state::unlock() { _apply_lock.unlock(); } +#ifdef LEGACY_CONF /* * Update all new globals. * @@ -378,6 +422,7 @@ void applier::state::_apply(configuration::state const& new_cfg, config->log_level_comments(new_cfg.log_level_comments()); config->log_level_macros(new_cfg.log_level_macros()); config->log_level_otl(new_cfg.log_level_otl()); + config->log_level_runtime(new_cfg.log_level_runtime()); config->use_true_regexp_matching(new_cfg.use_true_regexp_matching()); config->use_send_recovery_notifications_anyways( new_cfg.use_send_recovery_notifications_anyways()); @@ -487,7 +532,295 @@ void applier::state::_apply(configuration::state const& new_cfg, ochp_command_ptr = found->second.get(); } } +#else +/* + * Update all new globals. + * + * @param[in] new_cfg The new configuration state. + */ +void applier::state::_apply(const configuration::State& new_cfg, + error_cnt& err) { + // Check variables should not be change after the first execution. + if (has_already_been_loaded) { + if (!std::equal( + pb_config.broker_module().begin(), pb_config.broker_module().end(), + new_cfg.broker_module().begin(), new_cfg.broker_module().end())) { + config_logger->warn( + "Warning: Broker modules cannot be changed nor reloaded"); + ++err.config_warnings; + } + if (pb_config.broker_module_directory() != + new_cfg.broker_module_directory()) { + config_logger->warn("Warning: Broker module directory cannot be changed"); + ++err.config_warnings; + } + if (pb_config.command_file() != new_cfg.command_file()) { + config_logger->warn("Warning: Command file cannot be changed"); + ++err.config_warnings; + } + if (pb_config.external_command_buffer_slots() != + new_cfg.external_command_buffer_slots()) { + config_logger->warn( + "Warning: External command buffer slots cannot be changed"); + ++err.config_warnings; + } + if (pb_config.use_timezone() != new_cfg.use_timezone()) { + config_logger->warn("Warning: Timezone can not be changed"); + ++err.config_warnings; + } + } + + // Initialize status file. + bool modify_status(false); + if (!has_already_been_loaded || + pb_config.status_file() != new_cfg.status_file()) + modify_status = true; + + // Cleanup. + // if (modify_perfdata) + // xpddefault_cleanup_performance_data(); + if (modify_status) + xsddefault_cleanup_status_data(true); + + // Set new values. + pb_config.set_accept_passive_host_checks( + new_cfg.accept_passive_host_checks()); + pb_config.set_accept_passive_service_checks( + new_cfg.accept_passive_service_checks()); + pb_config.set_additional_freshness_latency( + new_cfg.additional_freshness_latency()); + pb_config.set_admin_email(new_cfg.admin_email()); + pb_config.set_admin_pager(new_cfg.admin_pager()); + pb_config.set_allow_empty_hostgroup_assignment( + new_cfg.allow_empty_hostgroup_assignment()); + pb_config.set_auto_reschedule_checks(new_cfg.auto_reschedule_checks()); + pb_config.set_auto_rescheduling_interval( + new_cfg.auto_rescheduling_interval()); + pb_config.set_auto_rescheduling_window(new_cfg.auto_rescheduling_window()); + pb_config.set_cached_host_check_horizon(new_cfg.cached_host_check_horizon()); + pb_config.set_cached_service_check_horizon( + new_cfg.cached_service_check_horizon()); + pb_config.set_cfg_main(new_cfg.cfg_main()); + pb_config.set_check_external_commands(new_cfg.check_external_commands()); + pb_config.set_check_host_freshness(new_cfg.check_host_freshness()); + pb_config.set_check_orphaned_hosts(new_cfg.check_orphaned_hosts()); + pb_config.set_check_orphaned_services(new_cfg.check_orphaned_services()); + pb_config.set_check_reaper_interval(new_cfg.check_reaper_interval()); + pb_config.set_check_service_freshness(new_cfg.check_service_freshness()); + pb_config.set_command_check_interval(new_cfg.command_check_interval()); + pb_config.set_command_check_interval_is_seconds( + new_cfg.command_check_interval_is_seconds()); + pb_config.set_date_format(new_cfg.date_format()); + pb_config.set_debug_file(new_cfg.debug_file()); + pb_config.set_debug_level(new_cfg.debug_level()); + pb_config.set_debug_verbosity(new_cfg.debug_verbosity()); + pb_config.set_enable_environment_macros(new_cfg.enable_environment_macros()); + pb_config.set_enable_event_handlers(new_cfg.enable_event_handlers()); + pb_config.set_enable_flap_detection(new_cfg.enable_flap_detection()); + pb_config.set_enable_notifications(new_cfg.enable_notifications()); + pb_config.set_enable_predictive_host_dependency_checks( + new_cfg.enable_predictive_host_dependency_checks()); + pb_config.set_enable_predictive_service_dependency_checks( + new_cfg.enable_predictive_service_dependency_checks()); + pb_config.set_event_broker_options(new_cfg.event_broker_options()); + pb_config.set_event_handler_timeout(new_cfg.event_handler_timeout()); + pb_config.set_execute_host_checks(new_cfg.execute_host_checks()); + pb_config.set_execute_service_checks(new_cfg.execute_service_checks()); + pb_config.set_global_host_event_handler(new_cfg.global_host_event_handler()); + pb_config.set_global_service_event_handler( + new_cfg.global_service_event_handler()); + pb_config.set_high_host_flap_threshold(new_cfg.high_host_flap_threshold()); + pb_config.set_high_service_flap_threshold( + new_cfg.high_service_flap_threshold()); + pb_config.set_host_check_timeout(new_cfg.host_check_timeout()); + pb_config.set_host_freshness_check_interval( + new_cfg.host_freshness_check_interval()); + pb_config.mutable_host_inter_check_delay_method()->CopyFrom( + new_cfg.host_inter_check_delay_method()); + pb_config.set_illegal_object_chars(new_cfg.illegal_object_chars()); + pb_config.set_illegal_output_chars(new_cfg.illegal_output_chars()); + pb_config.set_interval_length(new_cfg.interval_length()); + pb_config.set_log_event_handlers(new_cfg.log_event_handlers()); + pb_config.set_log_external_commands(new_cfg.log_external_commands()); + pb_config.set_log_file(new_cfg.log_file()); + pb_config.set_log_host_retries(new_cfg.log_host_retries()); + pb_config.set_log_notifications(new_cfg.log_notifications()); + pb_config.set_log_passive_checks(new_cfg.log_passive_checks()); + pb_config.set_log_service_retries(new_cfg.log_service_retries()); + pb_config.set_low_host_flap_threshold(new_cfg.low_host_flap_threshold()); + pb_config.set_low_service_flap_threshold( + new_cfg.low_service_flap_threshold()); + pb_config.set_max_debug_file_size(new_cfg.max_debug_file_size()); + pb_config.set_max_host_check_spread(new_cfg.max_host_check_spread()); + pb_config.set_max_log_file_size(new_cfg.max_log_file_size()); + pb_config.set_max_parallel_service_checks( + new_cfg.max_parallel_service_checks()); + pb_config.set_max_service_check_spread(new_cfg.max_service_check_spread()); + pb_config.set_notification_timeout(new_cfg.notification_timeout()); + pb_config.set_obsess_over_hosts(new_cfg.obsess_over_hosts()); + pb_config.set_obsess_over_services(new_cfg.obsess_over_services()); + pb_config.set_ochp_command(new_cfg.ochp_command()); + pb_config.set_ochp_timeout(new_cfg.ochp_timeout()); + pb_config.set_ocsp_command(new_cfg.ocsp_command()); + pb_config.set_ocsp_timeout(new_cfg.ocsp_timeout()); + pb_config.set_perfdata_timeout(new_cfg.perfdata_timeout()); + pb_config.set_process_performance_data(new_cfg.process_performance_data()); + pb_config.mutable_resource_file()->CopyFrom(new_cfg.resource_file()); + pb_config.set_retain_state_information(new_cfg.retain_state_information()); + pb_config.set_retained_contact_host_attribute_mask( + new_cfg.retained_contact_host_attribute_mask()); + pb_config.set_retained_contact_service_attribute_mask( + new_cfg.retained_contact_service_attribute_mask()); + pb_config.set_retained_host_attribute_mask( + new_cfg.retained_host_attribute_mask()); + pb_config.set_retained_process_host_attribute_mask( + new_cfg.retained_process_host_attribute_mask()); + pb_config.set_retention_scheduling_horizon( + new_cfg.retention_scheduling_horizon()); + pb_config.set_retention_update_interval(new_cfg.retention_update_interval()); + pb_config.set_service_check_timeout(new_cfg.service_check_timeout()); + pb_config.set_service_freshness_check_interval( + new_cfg.service_freshness_check_interval()); + pb_config.mutable_service_inter_check_delay_method()->CopyFrom( + new_cfg.service_inter_check_delay_method()); + pb_config.mutable_service_interleave_factor_method()->CopyFrom( + new_cfg.service_interleave_factor_method()); + pb_config.set_sleep_time(new_cfg.sleep_time()); + pb_config.set_soft_state_dependencies(new_cfg.soft_state_dependencies()); + pb_config.set_state_retention_file(new_cfg.state_retention_file()); + pb_config.set_status_file(new_cfg.status_file()); + pb_config.set_status_update_interval(new_cfg.status_update_interval()); + pb_config.set_time_change_threshold(new_cfg.time_change_threshold()); + pb_config.set_use_large_installation_tweaks( + new_cfg.use_large_installation_tweaks()); + pb_config.set_instance_heartbeat_interval( + new_cfg.instance_heartbeat_interval()); + pb_config.set_use_regexp_matches(new_cfg.use_regexp_matches()); + pb_config.set_use_retained_program_state( + new_cfg.use_retained_program_state()); + pb_config.set_use_retained_scheduling_info( + new_cfg.use_retained_scheduling_info()); + pb_config.set_use_setpgid(new_cfg.use_setpgid()); + pb_config.set_use_syslog(new_cfg.use_syslog()); + pb_config.set_log_v2_enabled(new_cfg.log_v2_enabled()); + pb_config.set_log_legacy_enabled(new_cfg.log_legacy_enabled()); + pb_config.set_log_v2_logger(new_cfg.log_v2_logger()); + pb_config.set_log_level_functions(new_cfg.log_level_functions()); + pb_config.set_log_level_config(new_cfg.log_level_config()); + pb_config.set_log_level_events(new_cfg.log_level_events()); + pb_config.set_log_level_checks(new_cfg.log_level_checks()); + pb_config.set_log_level_notifications(new_cfg.log_level_notifications()); + pb_config.set_log_level_eventbroker(new_cfg.log_level_eventbroker()); + pb_config.set_log_level_external_command( + new_cfg.log_level_external_command()); + pb_config.set_log_level_commands(new_cfg.log_level_commands()); + pb_config.set_log_level_downtimes(new_cfg.log_level_downtimes()); + pb_config.set_log_level_comments(new_cfg.log_level_comments()); + pb_config.set_log_level_macros(new_cfg.log_level_macros()); + pb_config.set_log_level_otl(new_cfg.log_level_otl()); + pb_config.set_log_level_runtime(new_cfg.log_level_runtime()); + pb_config.set_use_true_regexp_matching(new_cfg.use_true_regexp_matching()); + pb_config.set_send_recovery_notifications_anyways( + new_cfg.send_recovery_notifications_anyways()); + pb_config.set_host_down_disable_service_checks( + new_cfg.host_down_disable_service_checks()); + pb_config.clear_user(); + for (auto& p : new_cfg.user()) + pb_config.mutable_user()->at(p.first) = p.second; + + // Set this variable just the first time. + if (!has_already_been_loaded) { + pb_config.mutable_broker_module()->CopyFrom(new_cfg.broker_module()); + pb_config.set_broker_module_directory(new_cfg.broker_module_directory()); + pb_config.set_command_file(new_cfg.command_file()); + pb_config.set_external_command_buffer_slots( + new_cfg.external_command_buffer_slots()); + pb_config.set_use_timezone(new_cfg.use_timezone()); + } + + // Initialize. + if (modify_status) + xsddefault_initialize_status_data(); + + // Check global event handler commands... + if (verify_config) { + events_logger->info("Checking global event handlers..."); + } + if (!pb_config.global_host_event_handler().empty()) { + // Check the event handler command. + std::string temp_command_name(pb_config.global_host_event_handler().substr( + 0, pb_config.global_host_event_handler().find_first_of('!'))); + command_map::iterator found{ + commands::command::commands.find(temp_command_name)}; + if (found == commands::command::commands.end() || !found->second) { + config_logger->error( + "Error: Global host event handler command '{}' is not defined " + "anywhere!", + temp_command_name); + ++err.config_errors; + global_host_event_handler_ptr = nullptr; + } else + global_host_event_handler_ptr = found->second.get(); + } + if (!pb_config.global_service_event_handler().empty()) { + // Check the event handler command. + std::string temp_command_name( + pb_config.global_service_event_handler().substr( + 0, pb_config.global_service_event_handler().find_first_of('!'))); + command_map::iterator found{ + commands::command::commands.find(temp_command_name)}; + if (found == commands::command::commands.end() || !found->second) { + config_logger->error( + "Error: Global service event handler command '{}' is not defined " + "anywhere!", + temp_command_name); + ++err.config_errors; + global_service_event_handler_ptr = nullptr; + } else + global_service_event_handler_ptr = found->second.get(); + } + + // Check obsessive processor commands... + if (verify_config) { + events_logger->info("Checking obsessive compulsive processor commands..."); + } + if (!pb_config.ocsp_command().empty()) { + std::string temp_command_name(pb_config.ocsp_command().substr( + 0, pb_config.ocsp_command().find_first_of('!'))); + command_map::iterator found{ + commands::command::commands.find(temp_command_name)}; + if (found == commands::command::commands.end() || !found->second) { + engine_logger(log_verification_error, basic) + << "Error: Obsessive compulsive service processor command '" + << temp_command_name << "' is not defined anywhere!"; + config_logger->error( + "Error: Obsessive compulsive service processor command '{}' is not " + "defined anywhere!", + temp_command_name); + ++err.config_errors; + ocsp_command_ptr = nullptr; + } else + ocsp_command_ptr = found->second.get(); + } + if (!pb_config.ochp_command().empty()) { + std::string temp_command_name(pb_config.ochp_command().substr( + 0, pb_config.ochp_command().find_first_of('!'))); + command_map::iterator found{ + commands::command::commands.find(temp_command_name)}; + if (found == commands::command::commands.end() || !found->second) { + config_logger->error( + "Error: Obsessive compulsive host processor command '{}' is not " + "defined anywhere!", + temp_command_name); + ++err.config_errors; + ochp_command_ptr = nullptr; + } else + ochp_command_ptr = found->second.get(); + } +} +#endif +#ifdef LEGACY_CONF /** * @brief Apply configuration of a specific object type. * @@ -500,9 +833,8 @@ void applier::state::_apply(configuration::state const& new_cfg, * @param[in] new_cfg New configuration set. */ template <typename ConfigurationType, typename ApplierType> -void applier::state::_apply( - difference<std::set<ConfigurationType> > const& diff, - error_cnt& err) { +void applier::state::_apply(difference<std::set<ConfigurationType>> const& diff, + error_cnt& err) { // Type alias. typedef std::set<ConfigurationType> cfg_set; @@ -564,6 +896,71 @@ void applier::state::_apply( } } } +#else +/** + * @brief Apply protobuf configuration of a specific object type. + * + * This method will perform a diff on cur_cfg and new_cfg to create the + * three element sets : added, modified and removed. The type applier + * will then be called to: + * * 1) modify existing objects (the modification must be done in first since + * remove and create changes indices). + * * 2) remove old objects + * * 3) create new objects + * + * @param[in] cur_cfg Current configuration set. + * @param[in] new_cfg New configuration set. + */ +template <typename ConfigurationType, typename Key, typename ApplierType> +void applier::state::_apply(const pb_difference<ConfigurationType, Key>& diff, + error_cnt& err) { + // Applier. + ApplierType aplyr; + + // Modify objects. + for (auto& p : diff.modified()) { + if (!verify_config) + aplyr.modify_object(p.first, p.second); + else { + try { + aplyr.modify_object(p.first, p.second); + } catch (const std::exception& e) { + ++err.config_errors; + events_logger->info(e.what()); + } + } + } + + // Erase objects. + for (auto it = diff.deleted().rbegin(); it != diff.deleted().rend(); ++it) { + ssize_t idx = it->first; + if (!verify_config) + aplyr.remove_object(idx); + else { + try { + aplyr.remove_object(idx); + } catch (const std::exception& e) { + ++err.config_errors; + events_logger->info(e.what()); + } + } + } + + // Add objects. + for (auto& obj : diff.added()) { + if (!verify_config) + aplyr.add_object(obj); + else { + try { + aplyr.add_object(obj); + } catch (const std::exception& e) { + ++err.config_errors; + events_logger->info(e.what()); + } + } + } +} +#endif #ifdef DEBUG_CONFIG /** @@ -1057,6 +1454,7 @@ void applier::state::_check_hosts() const { #endif +#ifdef LEGACY_CONF void applier::state::apply_log_config(configuration::state& new_cfg) { /* During the verification, loggers write to stdout. After this step, they * will log as it is written in their configurations. So if we check the @@ -1122,11 +1520,90 @@ void applier::state::apply_log_config(configuration::state& new_cfg) { {log_v2::CORE, log_v2::CONFIG, log_v2::PROCESS, log_v2::FUNCTIONS, log_v2::EVENTS, log_v2::CHECKS, log_v2::NOTIFICATIONS, log_v2::EVENTBROKER, log_v2::EXTERNAL_COMMAND, log_v2::COMMANDS, - log_v2::DOWNTIMES, log_v2::COMMENTS, log_v2::MACROS, log_v2::RUNTIME}); + log_v2::DOWNTIMES, log_v2::COMMENTS, log_v2::MACROS, log_v2::RUNTIME, + log_v2::OTL}); + } + init_loggers(); +} +#else +void applier::state::apply_log_config(configuration::State& new_cfg) { + /* During the verification, loggers write to stdout. After this step, they + * will log as it is written in their configurations. So if we check the + * configuration, we don't want to change them. */ + if (verify_config || test_scheduling) + return; + + using log_v2_config = com::centreon::common::log_v2::config; + log_v2_config::logger_type log_type; + if (new_cfg.log_v2_enabled()) { + if (new_cfg.log_v2_logger() == "file") { + if (!new_cfg.log_file().empty()) + log_type = log_v2_config::logger_type::LOGGER_FILE; + else + log_type = log_v2_config::logger_type::LOGGER_STDOUT; + } else + log_type = log_v2_config::logger_type::LOGGER_SYSLOG; + + log_v2_config log_cfg("centengine", log_type, new_cfg.log_flush_period(), + new_cfg.log_pid(), new_cfg.log_file_line()); + if (log_type == log_v2_config::logger_type::LOGGER_FILE) { + log_cfg.set_log_path(new_cfg.log_file()); + log_cfg.set_max_size(new_cfg.max_log_file_size()); + } + auto broker_sink = std::make_shared<broker_sink_mt>(); + broker_sink->set_level(spdlog::level::info); + log_cfg.add_custom_sink(broker_sink); + + log_cfg.apply_custom_sinks( + {"functions", "config", "events", "checks", "notifications", + "eventbroker", "external_command", "commands", "downtimes", "comments", + "macros", "otl", "process", "runtime"}); + log_cfg.set_level("functions", + LogLevel_Name(new_cfg.log_level_functions())); + log_cfg.set_level("config", LogLevel_Name(new_cfg.log_level_config())); + log_cfg.set_level("events", LogLevel_Name(new_cfg.log_level_events())); + log_cfg.set_level("checks", LogLevel_Name(new_cfg.log_level_checks())); + log_cfg.set_level("notifications", + LogLevel_Name(new_cfg.log_level_notifications())); + log_cfg.set_level("eventbroker", + LogLevel_Name(new_cfg.log_level_eventbroker())); + log_cfg.set_level("external_command", + LogLevel_Name(new_cfg.log_level_external_command())); + log_cfg.set_level("commands", LogLevel_Name(new_cfg.log_level_commands())); + log_cfg.set_level("downtimes", + LogLevel_Name(new_cfg.log_level_downtimes())); + log_cfg.set_level("comments", LogLevel_Name(new_cfg.log_level_comments())); + log_cfg.set_level("macros", LogLevel_Name(new_cfg.log_level_macros())); + log_cfg.set_level("otl", LogLevel_Name(new_cfg.log_level_otl())); + log_cfg.set_level("process", LogLevel_Name(new_cfg.log_level_process())); + log_cfg.set_level("runtime", LogLevel_Name(new_cfg.log_level_runtime())); + if (has_already_been_loaded) + log_cfg.allow_only_atomic_changes(true); + log_v2::instance().apply(log_cfg); + } else { + if (!new_cfg.log_file().empty()) + log_type = log_v2_config::logger_type::LOGGER_FILE; + else + log_type = log_v2_config::logger_type::LOGGER_STDOUT; + log_v2_config log_cfg("centengine", log_type, new_cfg.log_flush_period(), + new_cfg.log_pid(), new_cfg.log_file_line()); + if (!new_cfg.log_file().empty()) { + log_cfg.set_log_path(new_cfg.log_file()); + log_cfg.set_max_size(new_cfg.max_log_file_size()); + } + log_v2::instance().apply(log_cfg); + log_v2::instance().disable( + {log_v2::CORE, log_v2::CONFIG, log_v2::PROCESS, log_v2::FUNCTIONS, + log_v2::EVENTS, log_v2::CHECKS, log_v2::NOTIFICATIONS, + log_v2::EVENTBROKER, log_v2::EXTERNAL_COMMAND, log_v2::COMMANDS, + log_v2::DOWNTIMES, log_v2::COMMENTS, log_v2::MACROS, log_v2::RUNTIME, + log_v2::OTL}); } init_loggers(); } +#endif +#ifdef LEGACY_CONF /** * Apply retention. * @@ -1148,9 +1625,33 @@ void applier::state::_apply(configuration::state& new_cfg, } } } - +#else /** - * Expand objects. + * Apply retention. + * + * @param[in] new_cfg New configuration set. + * @param[in] state The retention state to use. + */ +void applier::state::_apply(configuration::State& new_cfg, + retention::state& state, + error_cnt& err) { + retention::applier::state app_state; + if (!verify_config) + app_state.apply(new_cfg, state); + else { + try { + app_state.apply(new_cfg, state); + } catch (std::exception const& e) { + ++err.config_errors; + std::cout << e.what(); + } + } +} +#endif + +#ifdef LEGACY_CONF +/** + * Expand objects. * * @param[in,out] new_state New configuration state. * @param[in,out] cfg Configuration objects. @@ -1168,7 +1669,29 @@ void applier::state::_expand(configuration::state& new_state, error_cnt& err) { throw; } } +#else +/** + * Expand objects. + * + * @param[in,out] new_state New configuration state. + * @param[in,out] cfg Configuration objects. + */ +template <typename ConfigurationType, typename ApplierType> +void applier::state::_expand(configuration::State& new_state, error_cnt& err) { + ApplierType aplyr; + try { + aplyr.expand_objects(new_state); + } catch (std::exception const& e) { + if (verify_config) { + ++err.config_errors; + std::cout << e.what(); + } else + throw; + } +} +#endif +#ifdef LEGACY_CONF /** * Process new configuration and apply it. * @@ -1550,7 +2073,446 @@ void applier::state::_processing(configuration::state& new_cfg, has_already_been_loaded = true; _processing_state = state_ready; } +#else +/** + * Process new configuration and apply it. + * + * @param[in] new_cfg The new configuration. + * @param[in] state The retention to use. + */ +void applier::state::_processing(configuration::State& new_cfg, + error_cnt& err, + retention::state* state) { + // Timing. + struct timeval tv[5]; + + // Call prelauch broker event the first time to run applier state. + if (!has_already_been_loaded) + broker_program_state(NEBTYPE_PROCESS_PRELAUNCH, NEBFLAG_NONE); + + // + // Expand all objects. + // + gettimeofday(tv, nullptr); + // Expand timeperiods. + _expand<configuration::Timeperiod, applier::timeperiod>(new_cfg, err); + + // Expand connectors. + _expand<configuration::Connector, applier::connector>(new_cfg, err); + + // Expand commands. + _expand<configuration::Command, applier::command>(new_cfg, err); + + // Expand contacts. + _expand<configuration::Contact, applier::contact>(new_cfg, err); + + // Expand contactgroups. + _expand<configuration::Contactgroup, applier::contactgroup>(new_cfg, err); + + // Expand hosts. + _expand<configuration::Host, applier::host>(new_cfg, err); + + // Expand hostgroups. + _expand<configuration::Hostgroup, applier::hostgroup>(new_cfg, err); + + // Expand services. + _expand<configuration::Service, applier::service>(new_cfg, err); + + // Expand anomalydetections. + _expand<configuration::Anomalydetection, applier::anomalydetection>(new_cfg, + err); + + // Expand servicegroups. + _expand<configuration::Servicegroup, applier::servicegroup>(new_cfg, err); + + // Expand hostdependencies. + _expand<configuration::Hostdependency, applier::hostdependency>(new_cfg, err); + + // Expand servicedependencies. + _expand<configuration::Servicedependency, applier::servicedependency>(new_cfg, + err); + + // Expand hostescalations. + _expand<configuration::Hostescalation, applier::hostescalation>(new_cfg, err); + + // Expand serviceescalations. + _expand<configuration::Serviceescalation, applier::serviceescalation>(new_cfg, + err); + + // + // Build difference for all objects. + // + + // Build difference for timeperiods. + pb_difference<configuration::Timeperiod, std::string> diff_timeperiods; + diff_timeperiods.parse( + pb_config.timeperiods().begin(), pb_config.timeperiods().end(), + new_cfg.timeperiods().begin(), new_cfg.timeperiods().end(), + &configuration::Timeperiod::timeperiod_name); + + // Build difference for connectors. + pb_difference<configuration::Connector, std::string> diff_connectors; + diff_connectors.parse( + pb_config.connectors().begin(), pb_config.connectors().end(), + new_cfg.connectors().begin(), new_cfg.connectors().end(), + &configuration::Connector::connector_name); + + // Build difference for commands. + pb_difference<configuration::Command, std::string> diff_commands; + diff_commands.parse(pb_config.commands().begin(), pb_config.commands().end(), + new_cfg.commands().begin(), new_cfg.commands().end(), + &configuration::Command::command_name); + + // Build difference for severities. + pb_difference<configuration::Severity, std::pair<uint64_t, uint32_t>> + diff_severities; + diff_severities.parse( + pb_config.severities().begin(), pb_config.severities().end(), + new_cfg.severities().begin(), new_cfg.severities().end(), + [](const configuration::Severity& sev) -> std::pair<uint64_t, uint32_t> { + return std::make_pair(sev.key().id(), sev.key().type()); + }); + + // Build difference for tags. + pb_difference<configuration::Tag, std::pair<uint64_t, uint32_t>> diff_tags; + diff_tags.parse( + pb_config.tags().begin(), pb_config.tags().end(), new_cfg.tags().begin(), + new_cfg.tags().end(), + [](const configuration::Tag& tg) -> std::pair<uint64_t, uint32_t> { + return std::make_pair(tg.key().id(), tg.key().type()); + }); + + // Build difference for contacts. + pb_difference<configuration::Contact, std::string> diff_contacts; + diff_contacts.parse(pb_config.contacts().begin(), pb_config.contacts().end(), + new_cfg.contacts().begin(), new_cfg.contacts().end(), + &configuration::Contact::contact_name); + + // Build difference for contactgroups. + pb_difference<configuration::Contactgroup, std::string> diff_contactgroups; + diff_contactgroups.parse( + pb_config.contactgroups().begin(), pb_config.contactgroups().end(), + new_cfg.contactgroups().begin(), new_cfg.contactgroups().end(), + &configuration::Contactgroup::contactgroup_name); + + // Build difference for hosts. + pb_difference<configuration::Host, uint64_t> diff_hosts; + diff_hosts.parse(pb_config.hosts().begin(), pb_config.hosts().end(), + new_cfg.hosts().begin(), new_cfg.hosts().end(), + &configuration::Host::host_id); + + // Build difference for hostgroups. + pb_difference<configuration::Hostgroup, std::string> diff_hostgroups; + diff_hostgroups.parse( + pb_config.hostgroups().begin(), pb_config.hostgroups().end(), + new_cfg.hostgroups().begin(), new_cfg.hostgroups().end(), + &configuration::Hostgroup::hostgroup_name); + + // Build difference for services. + pb_difference<configuration::Service, std::pair<uint64_t, uint64_t>> + diff_services; + diff_services.parse(pb_config.services().begin(), pb_config.services().end(), + new_cfg.services().begin(), new_cfg.services().end(), + [](const configuration::Service& s) { + return std::make_pair(s.host_id(), s.service_id()); + }); + + // Build difference for anomalydetections. + pb_difference<configuration::Anomalydetection, std::pair<uint64_t, uint64_t>> + diff_anomalydetections; + diff_anomalydetections.parse( + pb_config.anomalydetections().begin(), + pb_config.anomalydetections().end(), new_cfg.anomalydetections().begin(), + new_cfg.anomalydetections().end(), + [](const configuration::Anomalydetection& ad) { + return std::make_pair(ad.host_id(), ad.service_id()); + }); + + // Build difference for servicegroups. + pb_difference<configuration::Servicegroup, std::string> diff_servicegroups; + diff_servicegroups.parse( + pb_config.servicegroups().begin(), pb_config.servicegroups().end(), + new_cfg.servicegroups().begin(), new_cfg.servicegroups().end(), + &configuration::Servicegroup::servicegroup_name); + + // Build difference for hostdependencies. + pb_difference<configuration::Hostdependency, size_t> diff_hostdependencies; + typedef size_t (*key_func)(const configuration::Hostdependency&); + diff_hostdependencies.parse<key_func>( + pb_config.hostdependencies().begin(), pb_config.hostdependencies().end(), + new_cfg.hostdependencies().begin(), new_cfg.hostdependencies().end(), + configuration::hostdependency_key); + + // Build difference for servicedependencies. + pb_difference<configuration::Servicedependency, size_t> + diff_servicedependencies; + typedef size_t (*key_func_sd)(const configuration::Servicedependency&); + diff_servicedependencies.parse<key_func_sd>( + pb_config.servicedependencies().begin(), + pb_config.servicedependencies().end(), + new_cfg.servicedependencies().begin(), + new_cfg.servicedependencies().end(), + configuration::servicedependency_key); + + // Build difference for hostdependencies. + pb_difference<configuration::Hostescalation, size_t> diff_hostescalations; + typedef size_t (*key_func_he)(const configuration::Hostescalation&); + diff_hostescalations.parse<key_func_he>( + pb_config.hostescalations().begin(), pb_config.hostescalations().end(), + new_cfg.hostescalations().begin(), new_cfg.hostescalations().end(), + configuration::hostescalation_key); + + // Build difference for servicedependencies. + pb_difference<configuration::Serviceescalation, size_t> + diff_serviceescalations; + typedef size_t (*key_func_se)(const configuration::Serviceescalation&); + diff_serviceescalations.parse<key_func_se>( + pb_config.serviceescalations().begin(), + pb_config.serviceescalations().end(), + new_cfg.serviceescalations().begin(), new_cfg.serviceescalations().end(), + configuration::serviceescalation_key); + + // Timing. + gettimeofday(tv + 1, nullptr); + + try { + std::lock_guard<std::mutex> locker(_apply_lock); + + // Apply logging configurations. + + applier::logging::instance().apply(new_cfg); + + apply_log_config(new_cfg); + + // Apply globals configurations. + applier::globals::instance().apply(new_cfg); + + // Apply macros configurations. + applier::macros::instance().apply(new_cfg); + + // Timing. + gettimeofday(tv + 2, nullptr); + + if (!has_already_been_loaded && !verify_config && !test_scheduling) { + // This must be logged after we read config data, + // as user may have changed location of main log file. + process_logger->info( + "Centreon Engine {} starting ... (PID={}) (Protobuf configuration)", + CENTREON_ENGINE_VERSION_STRING, getpid()); + + // Log the local time - may be different than clock + // time due to timezone offset. + process_logger->info("Local time is {}", string::ctime(program_start)); + process_logger->info("LOG VERSION: {}", LOG_VERSION_2); + } + + // + // Apply and resolve all objects. + // + + // Apply timeperiods. + _apply<configuration::Timeperiod, std::string, applier::timeperiod>( + diff_timeperiods, err); + _resolve<configuration::Timeperiod, applier::timeperiod>( + pb_config.timeperiods(), err); + + // Apply connectors. + _apply<configuration::Connector, std::string, applier::connector>( + diff_connectors, err); + _resolve<configuration::Connector, applier::connector>( + pb_config.connectors(), err); + + // Apply commands. + _apply<configuration::Command, std::string, applier::command>(diff_commands, + err); + _resolve<configuration::Command, applier::command>(pb_config.commands(), + err); + + // Apply contacts and contactgroups. + _apply<configuration::Contact, std::string, applier::contact>(diff_contacts, + err); + _apply<configuration::Contactgroup, std::string, applier::contactgroup>( + diff_contactgroups, err); + _resolve<configuration::Contactgroup, applier::contactgroup>( + pb_config.contactgroups(), err); + _resolve<configuration::Contact, applier::contact>(pb_config.contacts(), + err); + + // Apply severities. + _apply<configuration::Severity, std::pair<uint64_t, uint32_t>, + applier::severity>(diff_severities, err); + + // Apply tags. + _apply<configuration::Tag, std::pair<uint64_t, uint32_t>, applier::tag>( + diff_tags, err); + + // Apply hosts and hostgroups. + _apply<configuration::Host, uint64_t, applier::host>(diff_hosts, err); + _apply<configuration::Hostgroup, std::string, applier::hostgroup>( + diff_hostgroups, err); + + // Apply services. + _apply<configuration::Service, std::pair<uint64_t, uint64_t>, + applier::service>(diff_services, err); + + // Apply anomalydetections. + _apply<configuration::Anomalydetection, std::pair<uint64_t, uint64_t>, + applier::anomalydetection>(diff_anomalydetections, err); + + // Apply servicegroups. + _apply<configuration::Servicegroup, std::string, applier::servicegroup>( + diff_servicegroups, err); + + // Resolve hosts, services, host groups. + _resolve<configuration::Host, applier::host>(pb_config.hosts(), err); + _resolve<configuration::Hostgroup, applier::hostgroup>( + pb_config.hostgroups(), err); + + // Resolve services. + _resolve<configuration::Service, applier::service>(pb_config.services(), + err); + + // Resolve anomalydetections. + _resolve<configuration::Anomalydetection, applier::anomalydetection>( + pb_config.anomalydetections(), err); + + // Resolve service groups. + _resolve<configuration::Servicegroup, applier::servicegroup>( + pb_config.servicegroups(), err); + + // Apply host dependencies. + _apply<configuration::Hostdependency, size_t, applier::hostdependency>( + diff_hostdependencies, err); + _resolve<configuration::Hostdependency, applier::hostdependency>( + pb_config.hostdependencies(), err); + + // Apply service dependencies. + _apply<configuration::Servicedependency, size_t, + applier::servicedependency>(diff_servicedependencies, err); + _resolve<configuration::Servicedependency, applier::servicedependency>( + pb_config.servicedependencies(), err); + + // Apply host escalations. + _apply<configuration::Hostescalation, size_t, applier::hostescalation>( + diff_hostescalations, err); + _resolve<configuration::Hostescalation, applier::hostescalation>( + pb_config.hostescalations(), err); + + // Apply service escalations. + _apply<configuration::Serviceescalation, size_t, + applier::serviceescalation>(diff_serviceescalations, err); + _resolve<configuration::Serviceescalation, applier::serviceescalation>( + pb_config.serviceescalations(), err); + +#ifdef DEBUG_CONFIG + std::cout << "WARNING!! You are using a version of Centreon Engine for " + "developers!!! This is not a production version."; + // Checks on configuration + _check_serviceescalations(); + _check_hostescalations(); + _check_contacts(); + _check_contactgroups(); + _check_services(); + _check_hosts(); +#endif + + // Load retention. + if (state) + _apply(new_cfg, *state, err); + + // Apply scheduler. + if (!verify_config) + applier::scheduler::instance().apply(new_cfg, diff_hosts, diff_services, + diff_anomalydetections); + + // Apply new global on the current state. + if (!verify_config) { + _apply(new_cfg, err); + whitelist::reload(); + } else { + try { + _apply(new_cfg, err); + } catch (std::exception const& e) { + ++err.config_errors; + events_logger->info(e.what()); + } + } + + // Timing. + gettimeofday(tv + 3, nullptr); + + // Check for circular paths between hosts. + pre_flight_circular_check(&err.config_warnings, &err.config_errors); + + // Call start broker event the first time to run applier state. + if (!has_already_been_loaded) { + neb_load_all_modules(); + + broker_program_state(NEBTYPE_PROCESS_START, NEBFLAG_NONE); + } else + neb_reload_all_modules(); + + // Print initial states of new hosts and services. + if (!verify_config && !test_scheduling) { + for (auto a : diff_hosts.added()) { + auto it_hst = engine::host::hosts_by_id.find(a.host_id()); + if (it_hst != engine::host::hosts_by_id.end()) + log_host_state(INITIAL_STATES, it_hst->second.get()); + } + for (auto a : diff_services.added()) { + auto it_svc = + engine::service::services_by_id.find({a.host_id(), a.service_id()}); + if (it_svc != engine::service::services_by_id.end()) + log_service_state(INITIAL_STATES, it_svc->second.get()); + } + } + + // Timing. + gettimeofday(tv + 4, nullptr); + if (test_scheduling) { + double runtimes[5]; + runtimes[4] = 0.0; + for (unsigned int i(0); i < (sizeof(runtimes) / sizeof(*runtimes) - 1); + ++i) { + runtimes[i] = tv[i + 1].tv_sec - tv[i].tv_sec + + (tv[i + 1].tv_usec - tv[i].tv_usec) / 1000000.0; + runtimes[4] += runtimes[i]; + } + std::cout + << "\nTiming information on configuration verification is listed " + "below.\n\n" + "CONFIG VERIFICATION TIMES (* = Potential for speedup " + "with -x option)\n" + "----------------------------------\n" + "Template Resolutions: " + << runtimes[0] + << " sec\n" + "Object Relationships: " + << runtimes[2] + << " sec\n" + "Circular Paths: " + << runtimes[3] + << " sec *\n" + "Misc: " + << runtimes[1] + << " sec\n" + " ============\n" + "TOTAL: " + << runtimes[4] << " sec * = " << runtimes[3] << " sec (" + << (runtimes[3] * 100.0 / runtimes[4]) << "%) estimated savings\n"; + } + } catch (...) { + _processing_state = state_error; + throw; + } + + has_already_been_loaded = true; + _processing_state = state_ready; +} +#endif + +#ifdef LEGACY_CONF /** * Resolve objects. * @@ -1574,3 +2536,29 @@ void applier::state::_resolve(std::set<ConfigurationType>& cfg, } } } +#else +/** + * @brief Resolve objects. + * + * @tparam ConfigurationType The protobuf object configuration. + * @tparam ApplierType The applier used to handle the configuration. + * @param cfg + */ +template <typename ConfigurationType, typename ApplierType> +void applier::state::_resolve( + const ::google::protobuf::RepeatedPtrField<ConfigurationType>& cfg, + error_cnt& err) { + ApplierType aplyr; + for (auto& obj : cfg) { + try { + aplyr.resolve_object(obj, err); + } catch (const std::exception& e) { + if (verify_config) { + ++err.config_errors; + std::cout << e.what() << std::endl; + } else + throw; + } + } +} +#endif diff --git a/engine/src/configuration/applier/tag.cc b/engine/src/configuration/applier/tag.cc index 3f82237998f..8bb50803e32 100644 --- a/engine/src/configuration/applier/tag.cc +++ b/engine/src/configuration/applier/tag.cc @@ -24,12 +24,16 @@ #include "com/centreon/engine/exceptions/error.hh" #include "com/centreon/engine/globals.hh" #include "com/centreon/engine/tag.hh" +#include "gtest/gtest.h" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/tag.hh" +#endif using namespace com::centreon; using namespace com::centreon::engine; using namespace com::centreon::engine::configuration; +#ifdef LEGACY_CONF /** * Add new tag. * @@ -59,7 +63,43 @@ void applier::tag::add_object(const configuration::tag& obj) { broker_adaptive_tag_data(NEBTYPE_TAG_ADD, tg.get()); } +#else +/** + * @brief Add a new tag. + * + * @param obj The new protobuf configuration tag to add. + */ +void applier::tag::add_object(const configuration::Tag& obj) { + // Logging. + config_logger->debug("Creating new tag ({},{}).", obj.key().id(), + obj.key().type()); + + // Add tag to the global configuration set. + configuration::Tag* new_tg = pb_config.add_tags(); + new_tg->CopyFrom(obj); + + auto tg = std::make_shared<engine::tag>( + new_tg->key().id(), + static_cast<engine::tag::tagtype>(new_tg->key().type()), + new_tg->tag_name()); + if (!tg) + throw engine_error() << fmt::format("Could not register tag ({},{})", + new_tg->key().id(), + new_tg->key().type()); + + // Add new items to the configuration state. + auto res = engine::tag::tags.insert( + {{new_tg->key().id(), new_tg->key().type()}, tg}); + if (!res.second) + config_logger->error( + "Could not insert tag ({},{}) into cache because it already exists", + new_tg->key().id(), new_tg->key().type()); + + broker_adaptive_tag_data(NEBTYPE_TAG_ADD, tg.get()); +} +#endif +#ifdef LEGACY_CONF /** * @brief Expand a contact. * @@ -69,7 +109,9 @@ void applier::tag::add_object(const configuration::tag& obj) { * @param[in,out] s Configuration state. */ void applier::tag::expand_objects(configuration::state&) {} +#endif +#ifdef LEGACY_CONF /** * Modify tag. * @@ -109,7 +151,43 @@ void applier::tag::modify_object(const configuration::tag& obj) { config_logger->debug("Tag ({},{}) did not change", obj.key().first, obj.key().second); } +#else +/** + * @brief Modify tag. + * + * @param obj The new tag protobuf configuration. + */ +void applier::tag::modify_object(configuration::Tag* to_modify, + const configuration::Tag& new_object) { + // Logging. + config_logger->debug("Modifying tag ({},{}).", to_modify->key().id(), + to_modify->key().type()); + + // Find tag object. + tag_map::iterator it_obj = + engine::tag::tags.find({new_object.key().id(), new_object.key().type()}); + if (it_obj == engine::tag::tags.end()) { + throw engine_error() << fmt::format( + "Could not modify non-existing tag object ({},{})", + new_object.key().id(), new_object.key().type()); + } + engine::tag* t = it_obj->second.get(); + + // Update the global configuration set. + if (to_modify->tag_name() != new_object.tag_name()) { + to_modify->set_tag_name(new_object.tag_name()); + t->set_name(new_object.tag_name()); + + // Notify event broker. + broker_adaptive_tag_data(NEBTYPE_TAG_UPDATE, t); + } else + config_logger->debug("Tag ({},{}) did not change", new_object.key().id(), + new_object.key().type()); +} +#endif + +#ifdef LEGACY_CONF /** * Remove old tag. * @@ -136,16 +214,65 @@ void applier::tag::remove_object(const configuration::tag& obj) { // Remove tag from the global configuration set. config->mut_tags().erase(obj); } +#else +/** + * @brief Remove old tag. + * + * @param idx The idx in the tags configuration objects to remove. + */ +void applier::tag::remove_object(ssize_t idx) { + const configuration::Tag& obj = pb_config.tags().at(idx); + + // Logging. + config_logger->debug("Removing tag ({},{}).", obj.key().id(), + obj.key().type()); + + // Find tag. + tag_map::iterator it = + engine::tag::tags.find({obj.key().id(), obj.key().type()}); + if (it != engine::tag::tags.end()) { + engine::tag* tg = it->second.get(); + + // Notify event broker. + broker_adaptive_tag_data(NEBTYPE_TAG_DELETE, tg); + + // Erase tag object (this will effectively delete the object). + engine::tag::tags.erase(it); + } + + // Remove tag from the global configuration set. + pb_config.mutable_tags()->DeleteSubrange(idx, 1); +} +#endif +#ifdef LEGACY_CONF /** * Resolve a tag. * * @param[in] obj Object to resolve. */ -void applier::tag::resolve_object(const configuration::tag& obj) { +void applier::tag::resolve_object(const configuration::tag& obj, + error_cnt& err) { tag_map::const_iterator tg_it{engine::tag::tags.find(obj.key())}; if (tg_it == engine::tag::tags.end() || !tg_it->second) { + err.config_errors++; throw engine_error() << "Cannot resolve non-existing tag (" << obj.key().first << "," << obj.key().second << ")"; } } +#else +/** + * Resolve a tag. + * + * @param[in] obj Object to resolve. + */ +void applier::tag::resolve_object(const configuration::Tag& obj, + error_cnt& err [[maybe_unused]]) { + tag_map::const_iterator tg_it{ + engine::tag::tags.find({obj.key().id(), obj.key().type()})}; + if (tg_it == engine::tag::tags.end() || !tg_it->second) { + throw engine_error() << "Cannot resolve non-existing tag (" + << obj.key().id() << "," << obj.key().type() << ")"; + } +} +#endif diff --git a/engine/src/configuration/applier/timeperiod.cc b/engine/src/configuration/applier/timeperiod.cc index 0b4d1a298d7..5dc2a9c843a 100644 --- a/engine/src/configuration/applier/timeperiod.cc +++ b/engine/src/configuration/applier/timeperiod.cc @@ -31,6 +31,7 @@ using namespace com::centreon::engine; using namespace com::centreon::engine::configuration; +#ifdef LEGACY_CONF /** * Add new time period. * @@ -73,7 +74,39 @@ void applier::timeperiod::add_object(configuration::timeperiod const& obj) { } _add_exclusions(obj.exclude(), tp.get()); } +#else +/** + * @brief Add new time period. + * + * @param[in] obj The new time period to add in the monitoring engine. + */ +void applier::timeperiod::add_object(const configuration::Timeperiod& obj) { + // Logging. + config_logger->debug("Creating new time period '{}'.", obj.timeperiod_name()); + + if (obj.timeperiod_name().empty() || obj.alias().empty()) { + throw engine_error() << fmt::format( + "Could not register time period '{}' (alias '{}'): timeperiod name and " + "alias must not be empty", + obj.timeperiod_name(), obj.alias()); + } + + // Add time period to the global configuration set. + configuration::Timeperiod* c_tp = pb_config.add_timeperiods(); + c_tp->CopyFrom(obj); + + // Create time period. + auto tp = std::make_shared<engine::timeperiod>(obj); + engine::timeperiod::timeperiods.insert({obj.timeperiod_name(), tp}); + + // Notify event broker. + timeval tv(get_broker_timestamp(nullptr)); + broker_adaptive_timeperiod_data(NEBTYPE_TIMEPERIOD_ADD, NEBFLAG_NONE, + NEBATTR_NONE, tp.get(), CMD_NONE, &tv); +} +#endif +#ifdef LEGACY_CONF /** * @brief Expand time period. * @@ -85,7 +118,20 @@ void applier::timeperiod::add_object(configuration::timeperiod const& obj) { void applier::timeperiod::expand_objects(configuration::state& s) { (void)s; } +#else +/** + * @brief Expand time period. + * + * Time period objects do not need expansion. Therefore this method + * does nothing. + * + * @param[in] s Unused. + */ +void applier::timeperiod::expand_objects(configuration::State& s + [[maybe_unused]]) {} +#endif +#ifdef LEGACY_CONF /** * Modify time period. * @@ -159,7 +205,67 @@ void applier::timeperiod::modify_object(configuration::timeperiod const& obj) { broker_adaptive_timeperiod_data(NEBTYPE_TIMEPERIOD_UPDATE, NEBFLAG_NONE, NEBATTR_NONE, tp, CMD_NONE, &tv); } +#else +/** + * Modify time period. + * + * @param[in] obj The time period to modify in the monitoring engine. + */ +void applier::timeperiod::modify_object( + configuration::Timeperiod* to_modify, + const configuration::Timeperiod& new_obj) { + // Logging. + config_logger->debug("Modifying time period '{}'.", + to_modify->timeperiod_name()); + + // Find time period object. + timeperiod_map::iterator it_obj = + engine::timeperiod::timeperiods.find(to_modify->timeperiod_name()); + if (it_obj == engine::timeperiod::timeperiods.end() || !it_obj->second) + throw engine_error() << fmt::format( + "Could not modify non-existing time period object '{}'", + to_modify->timeperiod_name()); + engine::timeperiod* tp(it_obj->second.get()); + + // Modify properties. + if (to_modify->alias() != new_obj.alias()) { + tp->set_alias(new_obj.alias().empty() ? new_obj.timeperiod_name() + : new_obj.alias()); + to_modify->set_alias(new_obj.alias()); + } + + // Time ranges modified ? + if (!MessageDifferencer::Equals(to_modify->timeranges(), + new_obj.timeranges())) { + tp->set_days(new_obj.timeranges()); + to_modify->mutable_timeranges()->CopyFrom(new_obj.timeranges()); + } + + // Exceptions modified ? + if (!MessageDifferencer::Equals(to_modify->exceptions(), + new_obj.exceptions())) { + tp->set_exceptions(new_obj.exceptions()); + to_modify->mutable_exceptions()->CopyFrom(new_obj.exceptions()); + } + + // Exclusions modified ? + if (!MessageDifferencer::Equals(to_modify->exclude(), new_obj.exclude())) { + // Delete old exclusions. + tp->get_exclusions().clear(); + // Create new exclusions. + tp->set_exclusions(new_obj.exclude()); + to_modify->mutable_exclude()->CopyFrom(new_obj.exclude()); + } + + // Notify event broker. + timeval tv(get_broker_timestamp(nullptr)); + broker_adaptive_timeperiod_data(NEBTYPE_TIMEPERIOD_UPDATE, NEBFLAG_NONE, + NEBATTR_NONE, tp, CMD_NONE, &tv); +} +#endif + +#ifdef LEGACY_CONF /** * Remove old time period. * @@ -187,7 +293,32 @@ void applier::timeperiod::remove_object(configuration::timeperiod const& obj) { // Remove time period from the global configuration set. config->timeperiods().erase(obj); } +#else +void applier::timeperiod::remove_object(ssize_t idx) { + /* obj is the object to remove */ + auto& obj = pb_config.timeperiods()[idx]; + config_logger->debug("Removing time period '{}'.", obj.timeperiod_name()); + + // Find time period. + timeperiod_map::iterator it = + engine::timeperiod::timeperiods.find(obj.timeperiod_name()); + if (it != engine::timeperiod::timeperiods.end() && it->second) { + // Notify event broker. + timeval tv(get_broker_timestamp(nullptr)); + broker_adaptive_timeperiod_data(NEBTYPE_TIMEPERIOD_DELETE, NEBFLAG_NONE, + NEBATTR_NONE, it->second.get(), CMD_NONE, + &tv); + + // Erase time period (will effectively delete the object). + engine::timeperiod::timeperiods.erase(it); + } + // Remove time period from the global configuration set. + pb_config.mutable_timeperiods()->DeleteSubrange(idx, 1); +} +#endif + +#ifdef LEGACY_CONF /** * @brief Resolve a time period object. * @@ -212,7 +343,33 @@ void applier::timeperiod::resolve_object(configuration::timeperiod const& obj, // Resolve time period. it->second->resolve(err.config_warnings, err.config_errors); } +#else +/** + * @brief Resolve a time period object. + * + * This method does nothing because a time period object does not rely + * on any external object. + * + * @param[in] obj Unused. + */ +void applier::timeperiod::resolve_object(const configuration::Timeperiod& obj, + error_cnt& err) { + // Logging. + config_logger->debug("Resolving time period '{}'.", obj.timeperiod_name()); + + // Find time period. + timeperiod_map::iterator it = + engine::timeperiod::timeperiods.find(obj.timeperiod_name()); + if (engine::timeperiod::timeperiods.end() == it || !it->second) + throw engine_error() << "Cannot resolve non-existing " + << "time period '" << obj.timeperiod_name() << "'"; + + // Resolve time period. + it->second->resolve(err.config_warnings, err.config_errors); +} +#endif +#ifdef LEGACY_CONF /** * Add exclusions to a time period. * @@ -226,3 +383,4 @@ void applier::timeperiod::_add_exclusions( it != end; ++it) tp->get_exclusions().insert({*it, nullptr}); } +#endif diff --git a/engine/src/configuration/extended_conf.cc b/engine/src/configuration/extended_conf.cc index 8042d50cde9..1f11c3741a4 100644 --- a/engine/src/configuration/extended_conf.cc +++ b/engine/src/configuration/extended_conf.cc @@ -17,8 +17,13 @@ */ #include "com/centreon/engine/configuration/extended_conf.hh" +#include <google/protobuf/util/json_util.h> #include "com/centreon/exceptions/msg_fmt.hh" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/state.hh" +#else +#include "common/engine_conf/state_helper.hh" +#endif #include "common/log_v2/log_v2.hh" using namespace com::centreon::engine::configuration; @@ -77,6 +82,7 @@ void extended_conf::reload() { } } +#ifdef LEGACY_CONF /** * @brief reload all optional configuration files if needed * Then these configuration content are applied to dest @@ -89,3 +95,36 @@ void extended_conf::update_state(state& dest) { dest.apply_extended_conf(conf_file->_path, conf_file->_content); } } +#else +/** + * @brief reload all optional configuration files if needed + * Then these configuration content are applied to dest + * + * @param dest + */ +void extended_conf::update_state(State* pb_config) { + for (auto& conf_file : _confs) { + conf_file->reload(); + std::ifstream f(conf_file->_path, std::ios::in); + std::string content; + if (f) { + f.seekg(0, std::ios::end); + content.resize(f.tellg()); + f.seekg(0, std::ios::beg); + f.read(&content[0], content.size()); + f.close(); + State new_conf; + google::protobuf::util::JsonParseOptions options; + options.ignore_unknown_fields = false; + options.case_insensitive_enum_parsing = true; + google::protobuf::util::JsonStringToMessage(content, &new_conf); + pb_config->MergeFrom(new_conf); + } else { + SPDLOG_LOGGER_ERROR( + conf_file->_logger, + "extended_conf::extended_conf : fail to read json content '{}': {}", + conf_file->_path, strerror(errno)); + } + } +} +#endif diff --git a/engine/src/configuration/whitelist.cc b/engine/src/configuration/whitelist.cc index 41e4bb6d0f2..cc7a0298019 100644 --- a/engine/src/configuration/whitelist.cc +++ b/engine/src/configuration/whitelist.cc @@ -1,4 +1,4 @@ -/* +/** * Copyright 2023 Centreon (https://www.centreon.com/) * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,7 +16,6 @@ * For more information : contact@centreon.com * */ - #define C4_NO_DEBUG_BREAK 1 #include "com/centreon/engine/configuration/whitelist.hh" diff --git a/engine/src/contact.cc b/engine/src/contact.cc index 3873f6854e5..ca9f61ed940 100644 --- a/engine/src/contact.cc +++ b/engine/src/contact.cc @@ -73,6 +73,15 @@ std::vector<std::string> const& contact::get_addresses() const { return _addresses; } +/** + * Set addresses. + * + * @param[in] addresses New addresses. + */ +void contact::set_addresses(std::vector<std::string>&& addresses) { + _addresses = std::move(addresses); +} + /** * Set addresses. * @@ -510,7 +519,7 @@ std::shared_ptr<contact> add_contact( std::string const& alias, std::string const& email, std::string const& pager, - std::array<std::string, MAX_CONTACT_ADDRESSES> const& addresses, + const std::vector<std::string>& addresses, std::string const& svc_notification_period, std::string const& host_notification_period, int notify_service_ok, @@ -537,8 +546,7 @@ std::shared_ptr<contact> add_contact( } // Check if the contact already exist. - std::string const& id(name); - if (contact::contacts.count(id)) { + if (contact::contacts.count(name)) { engine_logger(log_config_error, basic) << "Error: Contact '" << name << "' has already been defined"; config_logger->error("Error: Contact '{}' has already been defined", name); @@ -556,12 +564,8 @@ std::shared_ptr<contact> add_contact( obj->set_host_notification_period(host_notification_period); obj->set_pager(pager); obj->set_service_notification_period(svc_notification_period); - std::vector<std::string> addr; - addr.resize(MAX_CONTACT_ADDRESSES); - for (unsigned int x(0); x < MAX_CONTACT_ADDRESSES; ++x) - addr[x] = addresses[x]; - obj->set_addresses(addr); + obj->set_addresses(addresses); // Set remaining contact properties. obj->set_can_submit_commands(can_submit_commands > 0); diff --git a/engine/src/contactgroup.cc b/engine/src/contactgroup.cc index 1118b04e731..70827c4ec1a 100644 --- a/engine/src/contactgroup.cc +++ b/engine/src/contactgroup.cc @@ -16,7 +16,9 @@ * For more information : contact@centreon.com * */ +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/contactgroup.hh" +#endif #include "com/centreon/engine/broker.hh" #include "com/centreon/engine/configuration/applier/state.hh" #include "com/centreon/engine/contact.hh" @@ -32,11 +34,7 @@ using namespace com::centreon::engine::logging; contactgroup_map contactgroup::contactgroups; -/** - * Constructor. - */ -contactgroup::contactgroup() {} - +#ifdef LEGACY_CONF /** * Constructor from a configuration contactgroup * @@ -52,6 +50,20 @@ contactgroup::contactgroup(configuration::contactgroup const& obj) // Notify event broker. broker_group(NEBTYPE_CONTACTGROUP_ADD, this); } +#else +/** + * Constructor from a protobuf configuration contactgroup + * + * @param obj Configuration contactgroup + */ +contactgroup::contactgroup(const configuration::Contactgroup& obj) + : _alias{obj.alias().empty() ? obj.contactgroup_name() : obj.alias()}, + _name{obj.contactgroup_name()} { + assert(!_name.empty()); + // Notify event broker. + broker_group(NEBTYPE_CONTACTGROUP_ADD, this); +} +#endif /** * Assignment operator. diff --git a/engine/src/daterange.cc b/engine/src/daterange.cc index 1afa0e99a95..566f51f2a4a 100644 --- a/engine/src/daterange.cc +++ b/engine/src/daterange.cc @@ -27,6 +27,7 @@ using namespace com::centreon::engine; +#ifdef LEGACY_CONF /** * Create a new exception to a timeperiod. * @@ -73,6 +74,56 @@ daterange::daterange(type_range type, for (auto& tr : timeranges) add_timerange({tr.range_start(), tr.range_end()}); } +#else +/** + * Create a new exception to a timeperiod. + * + * @param[in] period Base period. + * @param[in] type + * @param[in] syear + * @param[in] smon + * @param[in] smday + * @param[in] swday + * @param[in] swday_offset + * @param[in] eyear + * @param[in] emon + * @param[in] emday + * @param[in] ewday + * @param[in] ewday_offset + * @param[in] skip_interval + * @param[in] a list of timeranges. + */ +daterange::daterange( + type_range type, + int syear, + int smon, + int smday, + int swday, + int swday_offset, + int eyear, + int emon, + int emday, + int ewday, + int ewday_offset, + int skip_interval, + const google::protobuf::RepeatedPtrField<configuration::Timerange>& + timeranges) + : _type{type}, + _syear{syear}, + _smon{smon}, + _smday{smday}, + _swday{swday}, + _swday_offset{swday_offset}, + _eyear{eyear}, + _emon{emon}, + _emday{emday}, + _ewday{ewday}, + _ewday_offset{ewday_offset}, + _skip_interval{skip_interval} { + for (auto& tr : timeranges) + add_timerange({tr.range_start(), tr.range_end()}); +} +#endif daterange::daterange(type_range type) : _type(type), diff --git a/engine/src/diagnostic.cc b/engine/src/diagnostic.cc index b204e6d57b0..70749c6257d 100644 --- a/engine/src/diagnostic.cc +++ b/engine/src/diagnostic.cc @@ -24,8 +24,13 @@ #include "com/centreon/engine/version.hh" #include "com/centreon/io/file_stream.hh" #include "com/centreon/process.hh" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/parser.hh" #include "common/engine_legacy_conf/state.hh" +#else +#include "common/engine_conf/parser.hh" +#include "common/engine_conf/state_helper.hh" +#endif using namespace com::centreon; using namespace com::centreon::engine; @@ -154,6 +159,7 @@ void diagnostic::generate(std::string const& cfg_file, // Parse configuration file. std::cout << "Diagnostic: Parsing configuration file '" << cfg_file << "'" << std::endl; +#ifdef LEGACY_CONF configuration::state conf; try { configuration::error_cnt err; @@ -163,6 +169,17 @@ void diagnostic::generate(std::string const& cfg_file, std::cerr << "Diagnostic: configuration file '" << cfg_file << "' parsing failed: " << e.what() << std::endl; } +#else + configuration::State conf; + try { + configuration::error_cnt err; + configuration::parser parsr; + parsr.parse(cfg_file, &conf, err); + } catch (std::exception const& e) { + std::cerr << "Diagnostic: configuration file '" << cfg_file + << "' parsing failed: " << e.what() << std::endl; + } +#endif // Create temporary configuration directory. std::string tmp_cfg_dir(tmp_dir + "/cfg/"); @@ -181,8 +198,7 @@ void diagnostic::generate(std::string const& cfg_file, } // Copy other configuration files. - for (std::list<std::string>::const_iterator it(conf.cfg_file().begin()), - end(conf.cfg_file().end()); + for (auto it = conf.cfg_file().begin(), end = conf.cfg_file().end(); it != end; ++it) { std::string target_path(_build_target_path(tmp_cfg_dir, *it)); to_remove.push_back(target_path); diff --git a/engine/src/events/loop.cc b/engine/src/events/loop.cc index 9d65742fe7c..20500f7a155 100644 --- a/engine/src/events/loop.cc +++ b/engine/src/events/loop.cc @@ -32,7 +32,11 @@ #include "com/centreon/engine/logging/logger.hh" #include "com/centreon/engine/statusdata.hh" #include "com/centreon/logging/engine.hh" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/parser.hh" +#else +#include "common/engine_conf/parser.hh" +#endif using namespace com::centreon::engine; using namespace com::centreon::engine::events; @@ -91,6 +95,7 @@ void loop::run() { */ loop::loop() : _need_reload(0), _reload_running(false) {} +#ifdef LEGACY_CONF static void apply_conf(std::atomic<bool>* reloading) { configuration::error_cnt err; engine_logger(log_info_message, more) << "Starting to reload configuration."; @@ -115,6 +120,28 @@ static void apply_conf(std::atomic<bool>* reloading) { engine_logger(log_info_message, more) << "Reload configuration finished."; process_logger->info("Reload configuration finished."); } +#else +static void apply_conf(std::atomic<bool>* reloading) { + configuration::error_cnt err; + process_logger->info("Starting to reload configuration."); + try { + configuration::State config; + configuration::state_helper config_hlp(&config); + { + configuration::parser p; + std::string path(::pb_config.cfg_main()); + p.parse(path, &config, err); + } + configuration::extended_conf::update_state(&config); + configuration::applier::state::instance().apply(config, err); + process_logger->info("Configuration reloaded, main loop continuing."); + } catch (std::exception const& e) { + config_logger->error("Error: {}", e.what()); + } + *reloading = false; + process_logger->info("Reload configuration finished."); +} +#endif /** * Slot to dispatch Centreon Engine events. @@ -164,6 +191,26 @@ void loop::_dispatching() { configuration::applier::state::instance().lock(); +#ifdef LEGACY_CONF + time_t time_change_threshold = config->time_change_threshold(); + uint32_t max_parallel_service_checks = + config->max_parallel_service_checks(); + bool execute_service_checks = config->execute_service_checks(); + bool execute_host_checks = config->execute_host_checks(); + uint32_t interval_length = config->interval_length(); + double sleep_time = config->sleep_time(); + int32_t command_check_interval = config->command_check_interval(); +#else + time_t time_change_threshold = pb_config.time_change_threshold(); + uint32_t max_parallel_service_checks = + pb_config.max_parallel_service_checks(); + bool execute_service_checks = pb_config.execute_service_checks(); + bool execute_host_checks = pb_config.execute_host_checks(); + uint32_t interval_length = pb_config.interval_length(); + double sleep_time = pb_config.sleep_time(); + int32_t command_check_interval = pb_config.command_check_interval(); +#endif + // Hey, wait a second... we traveled back in time! if (current_time < _last_time) compensate_for_system_time_change( @@ -172,7 +219,7 @@ void loop::_dispatching() { // Else if the time advanced over the specified threshold, // try and compensate... else if ((current_time - _last_time) >= - static_cast<time_t>(config->time_change_threshold())) + static_cast<time_t>(time_change_threshold)) compensate_for_system_time_change( static_cast<unsigned long>(_last_time), static_cast<unsigned long>(current_time)); @@ -207,10 +254,10 @@ void loop::_dispatching() { } engine_logger(dbg_events, more) << "Current/Max Service Checks: " << currently_running_service_checks - << '/' << config->max_parallel_service_checks(); + << '/' << max_parallel_service_checks; events_logger->debug("Current/Max Service Checks: {}/{}", currently_running_service_checks, - config->max_parallel_service_checks()); + max_parallel_service_checks); // Update status information occassionally - NagVis watches the // NDOUtils DB to see if Engine is alive. @@ -251,46 +298,43 @@ void loop::_dispatching() { // Don't run a service check if we're already maxed out on the // number of parallel service checks... - if (config->max_parallel_service_checks() != 0 && - currently_running_service_checks >= - config->max_parallel_service_checks()) { + if (max_parallel_service_checks != 0 && + currently_running_service_checks >= max_parallel_service_checks) { // Move it at least 5 seconds (to overcome the current peak), // with a random 10 seconds (to spread the load). nudge_seconds = 5 + (rand() % 10); engine_logger(dbg_events | dbg_checks, basic) << "**WARNING** Max concurrent service checks (" << currently_running_service_checks << "/" - << config->max_parallel_service_checks() - << ") has been reached! Nudging " << temp_service->get_hostname() - << ":" << temp_service->description() << " by " << nudge_seconds + << max_parallel_service_checks << ") has been reached! Nudging " + << temp_service->get_hostname() << ":" + << temp_service->description() << " by " << nudge_seconds << " seconds..."; events_logger->trace( "**WARNING** Max concurrent service checks ({}/{}) has been " "reached! Nudging {}:{} by {} seconds...", - currently_running_service_checks, - config->max_parallel_service_checks(), + currently_running_service_checks, max_parallel_service_checks, temp_service->get_hostname(), temp_service->description(), nudge_seconds); engine_logger(log_runtime_warning, basic) << "\tMax concurrent service checks (" << currently_running_service_checks << "/" - << config->max_parallel_service_checks() - << ") has been reached. Nudging " << temp_service->get_hostname() - << ":" << temp_service->description() << " by " << nudge_seconds + << max_parallel_service_checks << ") has been reached. Nudging " + << temp_service->get_hostname() << ":" + << temp_service->description() << " by " << nudge_seconds << " seconds..."; runtime_logger->warn( "\tMax concurrent service checks ({}/{}) has been reached. " "Nudging {}:{} by {} seconds...", - currently_running_service_checks, - config->max_parallel_service_checks(), + currently_running_service_checks, max_parallel_service_checks, temp_service->get_hostname(), temp_service->description(), nudge_seconds); run_event = false; } // Don't run a service check if active checks are disabled. - if (!config->execute_service_checks()) { + if (!execute_service_checks) { engine_logger(dbg_events | dbg_checks, more) << "We're not executing service checks right now, " << "so we'll skip this event."; @@ -325,13 +369,11 @@ void loop::_dispatching() { temp_service->get_current_state() != service::state_ok) temp_service->set_next_check( (time_t)(temp_service->get_next_check() + - temp_service->retry_interval() * - config->interval_length())); + temp_service->retry_interval() * interval_length)); else temp_service->set_next_check( (time_t)(temp_service->get_next_check() + - (temp_service->check_interval() * - config->interval_length()))); + (temp_service->check_interval() * interval_length))); } temp_event->run_time = temp_service->get_next_check(); reschedule_event(std::move(temp_event), events::loop::low); @@ -348,7 +390,7 @@ void loop::_dispatching() { static_cast<host*>(_event_list_low.front()->event_data)); // Don't run a host check if active checks are disabled. - if (!config->execute_host_checks()) { + if (!execute_host_checks) { engine_logger(dbg_events | dbg_checks, more) << "We're not executing host checks right now, " << "so we'll skip this event."; @@ -374,13 +416,13 @@ void loop::_dispatching() { // Reschedule. if ((notifier::soft == temp_host->get_state_type()) && (temp_host->get_current_state() != host::state_up)) - temp_host->set_next_check((time_t)(temp_host->get_next_check() + - temp_host->retry_interval() * - config->interval_length())); + temp_host->set_next_check( + (time_t)(temp_host->get_next_check() + + temp_host->retry_interval() * interval_length)); else - temp_host->set_next_check((time_t)(temp_host->get_next_check() + - temp_host->check_interval() * - config->interval_length())); + temp_host->set_next_check( + (time_t)(temp_host->get_next_check() + + temp_host->check_interval() * interval_length)); temp_event->run_time = temp_host->get_next_check(); reschedule_event(std::move(temp_event), events::loop::low); temp_host->update_status(); @@ -410,7 +452,7 @@ void loop::_dispatching() { << "Did not execute scheduled event. Idling for a bit..."; events_logger->debug( "Did not execute scheduled event. Idling for a bit..."); - uint64_t d = static_cast<uint64_t>(config->sleep_time() * 1000000000); + uint64_t d = static_cast<uint64_t>(sleep_time * 1000000000); std::this_thread::sleep_for(std::chrono::nanoseconds(d)); } } @@ -426,7 +468,7 @@ void loop::_dispatching() { // Check for external commands if we're supposed to check as // often as possible. - if (config->command_check_interval() == -1) { + if (command_check_interval == -1) { // Send data to event broker. broker_external_command(NEBTYPE_EXTERNALCOMMAND_CHECK, CMD_NONE, nullptr, nullptr); @@ -434,19 +476,18 @@ void loop::_dispatching() { auto t1 = std::chrono::system_clock::now(); auto delay = std::chrono::nanoseconds( - static_cast<uint64_t>(1000000000 * config->sleep_time())); + static_cast<uint64_t>(1000000000 * sleep_time)); command_manager::instance().execute(); // Set time to sleep so we don't hog the CPU... - timespec sleep_time; - sleep_time.tv_sec = (time_t)config->sleep_time(); - sleep_time.tv_nsec = - (long)((config->sleep_time() - (double)sleep_time.tv_sec) * - 1000000000ull); + timespec stime; + stime.tv_sec = (time_t)sleep_time; + stime.tv_nsec = + (long)((sleep_time - (double)stime.tv_sec) * 1000000000ull); // Populate fake "sleep" event. _sleep_event.run_time = current_time; - _sleep_event.event_data = (void*)&sleep_time; + _sleep_event.event_data = (void*)&stime; // Send event data to broker. broker_timed_event(NEBTYPE_TIMEDEVENT_SLEEP, NEBFLAG_NONE, NEBATTR_NONE, @@ -492,8 +533,13 @@ void loop::adjust_check_scheduling() { // determine our adjustment window. time_t current_time(time(nullptr)); time_t first_window_time(current_time); +#ifdef LEGACY_CONF time_t last_window_time(first_window_time + config->auto_rescheduling_window()); +#else + time_t last_window_time(first_window_time + + pb_config.auto_rescheduling_window()); +#endif // get current scheduling data. for (timed_event_list::iterator it{_event_list_low.begin()}, @@ -553,6 +599,7 @@ void loop::adjust_check_scheduling() { if (total_checks == 0 || adjust_scheduling == false) return; +#ifdef LEGACY_CONF if ((unsigned long)total_check_exec_time > config->auto_rescheduling_window()) { inter_check_delay = 0.0; @@ -564,6 +611,20 @@ void loop::adjust_check_scheduling() { (double)(total_checks * 1.0)); exec_time_factor = 1.0; } +#else + if ((unsigned long)total_check_exec_time > + pb_config.auto_rescheduling_window()) { + inter_check_delay = 0.0; + exec_time_factor = (double)((double)pb_config.auto_rescheduling_window() / + total_check_exec_time); + } else { + inter_check_delay = + (double)((((double)pb_config.auto_rescheduling_window()) - + total_check_exec_time) / + (double)(total_checks * 1.0)); + exec_time_factor = 1.0; + } +#endif auto compute_new_run_time = [](double current_exec_time_offset, double current_icd_offset, diff --git a/engine/src/events/sched_info.cc b/engine/src/events/sched_info.cc index 69701f6ca24..d92eebbd884 100644 --- a/engine/src/events/sched_info.cc +++ b/engine/src/events/sched_info.cc @@ -25,6 +25,9 @@ #include "com/centreon/engine/logging/logger.hh" #include "com/centreon/engine/statusdata.hh" #include "com/centreon/engine/string.hh" +#ifndef LEGACY_CONF +#include "common/engine_conf/state.pb.h" +#endif using namespace com::centreon::engine; using namespace com::centreon::engine::logging; @@ -32,6 +35,7 @@ using namespace com::centreon::engine::logging; /** * Displays service check scheduling information. */ +#ifdef LEGACY_CONF void display_scheduling_info() { // Notice. std::cout << "\nProjected scheduling information for host and service " @@ -191,6 +195,167 @@ void display_scheduling_info() { std::cout << "I have no suggestions - things look okay.\n"; } } +#else +void display_scheduling_info() { + // Notice. + std::cout << "\nProjected scheduling information for host and service " + "checks\n is listed below. This information assumes that you " + "are going\n to start running Centreon Engine with your current " + "config files.\n\n"; + + // Host scheduling information. + std::cout << "HOST SCHEDULING INFORMATION\n" + "---------------------------\n" + "Total hosts: " + << scheduling_info.total_hosts + << "\n" + "Total scheduled hosts: " + << scheduling_info.total_scheduled_hosts << "\n"; + if (pb_config.host_inter_check_delay_method().type() == + configuration::InterCheckDelay_IcdType_none) { + std::cout << "Host inter-check delay method: NONE\n"; + } else if (pb_config.host_inter_check_delay_method().type() == + configuration::InterCheckDelay_IcdType_dumb) { + std::cout << "Host inter-check delay method: DUMB\n"; + } else if (pb_config.host_inter_check_delay_method().type() == + configuration::InterCheckDelay_IcdType_smart) { + std::cout << "Host inter-check delay method: SMART\n" + "Average host check interval: " + << scheduling_info.average_host_check_interval << " sec\n"; + } else { + std::cout << "Host inter-check delay method: USER-SUPPLIED VALUE\n"; + } + std::cout << "Host inter-check delay: " + << scheduling_info.host_inter_check_delay + << " sec\n Max host check spread: " + << scheduling_info.max_host_check_spread + << " min\n First scheduled check: " + << (scheduling_info.total_scheduled_hosts == 0 + ? "N/A\n" + : ctime(&scheduling_info.first_host_check)) + << "Last scheduled check: " + << (scheduling_info.total_scheduled_hosts == 0 + ? "N/A\n" + : ctime(&scheduling_info.last_host_check)) + << "\n"; + // Service scheduling information. + std::cout << "SERVICE SCHEDULING INFORMATION\n" + "-------------------------------\n" + "Total services: " + << scheduling_info.total_services + << "\n" + "Total scheduled services: " + << scheduling_info.total_scheduled_services << "\n"; + if (pb_config.service_inter_check_delay_method().type() == + configuration::InterCheckDelay_IcdType_none) { + std::cout << "Service inter-check delay method: NONE\n"; + } else if (pb_config.service_inter_check_delay_method().type() == + configuration::InterCheckDelay_IcdType_dumb) { + std::cout << "Service inter-check delay method: DUMB\n"; + } else if (pb_config.service_inter_check_delay_method().type() == + configuration::InterCheckDelay_IcdType_smart) { + std::cout << "Service inter-check delay method: SMART\n" + "Average service check interval: " + << scheduling_info.average_service_check_interval << " sec\n"; + } else { + std::cout << "Service inter-check delay method: USER-SUPPLIED VALUE\n"; + } + std::cout << "Inter-check delay: " + << scheduling_info.service_inter_check_delay + << " sec\n Interleave factor method: " + << (pb_config.service_interleave_factor_method().type() == + configuration::InterleaveFactor::ilf_user + ? "USER-SUPPLIED VALUE\n" + : "SMART\n"); + if (pb_config.service_interleave_factor_method().type() == + configuration::InterleaveFactor::ilf_smart) { + std::cout << "Average services per host: " + << scheduling_info.average_services_per_host << "\n"; + } + std::cout << "Service interleave factor: " + << scheduling_info.service_interleave_factor + << "\n" + "Max service check spread: " + << scheduling_info.max_service_check_spread + << " min\n" + "First scheduled check: " + << ctime(&scheduling_info.first_service_check) + << "Last scheduled check: " + << ctime(&scheduling_info.last_service_check) << "\n"; + // Check processing information. + std::cout << "CHECK PROCESSING INFORMATION\n" + "----------------------------\n" + "Check result reaper interval: " + << pb_config.check_reaper_interval() << " sec\n"; + if (pb_config.max_parallel_service_checks() == 0) { + std::cout << "Max concurrent service checks: Unlimited\n"; + } else { + std::cout << "Max concurrent service checks: " + << pb_config.max_parallel_service_checks() << "\n"; + } + std::cout << "\n"; + // Performance suggestions. + std::cout << "PERFORMANCE SUGGESTIONS\n" + "-----------------------\n"; + int suggestions(0); + + // MAX REAPER INTERVAL RECOMMENDATION. + // Assume a 100% (2x) check burst for check reaper. + // Assume we want a max of 2k files in the result queue + // at any given time. + float max_reaper_interval(0.0); + max_reaper_interval = floor(2000 * scheduling_info.service_inter_check_delay); + if (max_reaper_interval < 2.0) + max_reaper_interval = 2.0; + if (max_reaper_interval > 30.0) + max_reaper_interval = 30.0; + if (max_reaper_interval < pb_config.check_reaper_interval()) { + std::cout << "* Value for 'check_result_reaper_frequency' should be <= " + << static_cast<int>(max_reaper_interval) << " seconds\n"; + ++suggestions; + } + if (pb_config.check_reaper_interval() < 2) { + std::cout << "* Value for 'check_result_reaper_frequency' should be >= 2 " + "seconds\n"; + ++suggestions; + } + + // MINIMUM CONCURRENT CHECKS RECOMMENDATION. + // First method (old) - assume a 100% (2x) service check + // burst for max concurrent checks. + float minimum_concurrent_checks(0.0); + float minimum_concurrent_checks1(0.0); + float minimum_concurrent_checks2(0.0); + if (scheduling_info.service_inter_check_delay == 0.0) + minimum_concurrent_checks1 = ceil(pb_config.check_reaper_interval() * 2.0); + else + minimum_concurrent_checks1 = + ceil((pb_config.check_reaper_interval() * 2.0) / + scheduling_info.service_inter_check_delay); + // Second method (new) - assume a 25% (1.25x) service check + // burst for max concurrent checks. + minimum_concurrent_checks2 = + ceil((((double)scheduling_info.total_scheduled_services) / + scheduling_info.average_service_check_interval) * + 1.25 * pb_config.check_reaper_interval() * + scheduling_info.average_service_execution_time); + // Use max of computed values. + if (minimum_concurrent_checks1 > minimum_concurrent_checks2) + minimum_concurrent_checks = minimum_concurrent_checks1; + else + minimum_concurrent_checks = minimum_concurrent_checks2; + // Compare with configured value. + if ((minimum_concurrent_checks > pb_config.max_parallel_service_checks()) && + pb_config.max_parallel_service_checks() != 0) { + std::cout << "* Value for 'max_concurrent_checks' option should be >= " + << static_cast<int>(minimum_concurrent_checks) << "\n"; + ++suggestions; + } + if (suggestions == 0) { + std::cout << "I have no suggestions - things look okay.\n"; + } +} +#endif /** * Equal operator. @@ -320,5 +485,5 @@ std::ostream& operator<<(std::ostream& os, sched_info const& obj) { << string::ctime(obj.last_host_check) << "\n" "}\n"; - return (os); + return os; } diff --git a/engine/src/events/timed_event.cc b/engine/src/events/timed_event.cc index e30946218a6..8bfff4087aa 100644 --- a/engine/src/events/timed_event.cc +++ b/engine/src/events/timed_event.cc @@ -195,6 +195,7 @@ void timed_event::_exec_event_check_reaper() { } } +#ifdef LEGACY_CONF /** * Execute orphan check. * @@ -210,7 +211,25 @@ void timed_event::_exec_event_orphan_check() { if (config->check_orphaned_services()) service::check_for_orphaned(); } +#else +/** + * Execute orphan check. + * + */ +void timed_event::_exec_event_orphan_check() { + engine_logger(dbg_events, basic) + << "** Orphaned Host and Service Check Event"; + events_logger->trace("** Orphaned Host and Service Check Event"); + // check for orphaned hosts and services. + if (pb_config.check_orphaned_hosts()) + host::check_for_orphaned(); + if (pb_config.check_orphaned_services()) + service::check_for_orphaned(); +} +#endif + +#ifdef LEGACY_CONF /** * Execute retention save. * @@ -222,6 +241,19 @@ void timed_event::_exec_event_retention_save() { // save state retention data. retention::dump::save(config->state_retention_file()); } +#else +/** + * Execute retention save. + * + */ +void timed_event::_exec_event_retention_save() { + engine_logger(dbg_events, basic) << "** Retention Data Save Event"; + events_logger->trace("** Retention Data Save Event"); + + // save state retention data. + retention::dump::save(pb_config.state_retention_file()); +} +#endif /** * Execute status save. diff --git a/engine/src/flapping.cc b/engine/src/flapping.cc index 680752b89db..ae9d00ca90a 100644 --- a/engine/src/flapping.cc +++ b/engine/src/flapping.cc @@ -40,15 +40,24 @@ void enable_flap_detection_routines() { functions_logger->trace("enable_flap_detection_routines()"); /* bail out if we're already set */ +#ifdef LEGACY_CONF if (config->enable_flap_detection()) return; +#else + if (pb_config.enable_flap_detection()) + return; +#endif /* set the attribute modified flag */ modified_host_process_attributes |= attr; modified_service_process_attributes |= attr; /* set flap detection flag */ +#ifdef LEGACY_CONF config->enable_flap_detection(true); +#else + pb_config.set_enable_flap_detection(true); +#endif /* send data to event broker */ broker_adaptive_program_data(NEBTYPE_ADAPTIVEPROGRAM_UPDATE, NEBFLAG_NONE, @@ -78,15 +87,24 @@ void disable_flap_detection_routines() { functions_logger->trace("disable_flap_detection_routines()"); /* bail out if we're already set */ +#ifdef LEGACY_CONF if (!config->enable_flap_detection()) return; +#else + if (!pb_config.enable_flap_detection()) + return; +#endif /* set the attribute modified flag */ modified_host_process_attributes |= attr; modified_service_process_attributes |= attr; /* set flap detection flag */ +#ifdef LEGACY_CONF config->enable_flap_detection(false); +#else + pb_config.set_enable_flap_detection(false); +#endif /* send data to event broker */ broker_adaptive_program_data(NEBTYPE_ADAPTIVEPROGRAM_UPDATE, NEBFLAG_NONE, diff --git a/engine/src/globals.cc b/engine/src/globals.cc index 99346e158e1..f2517d0bc23 100644 --- a/engine/src/globals.cc +++ b/engine/src/globals.cc @@ -29,7 +29,11 @@ using namespace com::centreon::engine; using com::centreon::common::log_v2::log_v2; +#ifdef LEGACY_CONF configuration::state* config = nullptr; +#else +configuration::State pb_config; +#endif char const* sigs[] = {"EXIT", "HUP", "INT", "QUIT", "ILL", "TRAP", "ABRT", "BUS", "FPE", "KILL", "USR1", "SEGV", @@ -52,8 +56,9 @@ std::shared_ptr<spdlog::logger> macros_logger; std::shared_ptr<spdlog::logger> notifications_logger; std::shared_ptr<spdlog::logger> process_logger; std::shared_ptr<spdlog::logger> runtime_logger; +std::shared_ptr<spdlog::logger> otl_logger; -char* config_file(NULL); +std::string config_file; char* debug_file(NULL); char* global_host_event_handler(NULL); char* global_service_event_handler(NULL); @@ -143,4 +148,5 @@ void init_loggers() { notifications_logger = log_v2::instance().get(log_v2::NOTIFICATIONS); process_logger = log_v2::instance().get(log_v2::PROCESS); runtime_logger = log_v2::instance().get(log_v2::RUNTIME); + otl_logger = log_v2::instance().get(log_v2::OTL); } diff --git a/engine/src/host.cc b/engine/src/host.cc index 2abfd5b67bc..f8a374ca624 100644 --- a/engine/src/host.cc +++ b/engine/src/host.cc @@ -1246,6 +1246,15 @@ int host::handle_async_check_result_3x( /* get the current time */ time_t current_time = std::time(nullptr); + bool accept_passive_host_checks; + uint32_t cached_host_check_horizon; +#ifdef LEGACY_CONF + accept_passive_host_checks = config->accept_passive_host_checks(); + cached_host_check_horizon = config->cached_host_check_horizon(); +#else + accept_passive_host_checks = pb_config.accept_passive_host_checks(); + cached_host_check_horizon = pb_config.cached_host_check_horizon(); +#endif double execution_time = static_cast<double>(queued_check_result.get_finish_time().tv_sec - @@ -1311,7 +1320,7 @@ int host::handle_async_check_result_3x( * skip this host check results if its passive and we aren't accepting passive * check results */ if (queued_check_result.get_check_type() == check_passive) { - if (!config->accept_passive_host_checks()) { + if (!accept_passive_host_checks) { engine_logger(dbg_checks, basic) << "Discarding passive host check result because passive host " "checks are disabled globally."; @@ -1558,8 +1567,7 @@ int host::handle_async_check_result_3x( /* process the host check result */ process_check_result_3x(hst_res, old_plugin_output, CHECK_OPTION_NONE, - reschedule_check, true, - config->cached_host_check_horizon()); + reschedule_check, true, cached_host_check_horizon); engine_logger(dbg_checks, more) << "** Async check result for host '" << name() @@ -1589,6 +1597,13 @@ int host::run_scheduled_check(int check_options, double latency) { time_t next_valid_time = 0L; bool time_is_valid = true; + uint32_t interval_length; +#ifdef LEGACY_CONF + interval_length = config->interval_length(); +#else + interval_length = pb_config.interval_length(); +#endif + engine_logger(dbg_functions, basic) << "run_scheduled_host_check_3x()"; SPDLOG_LOGGER_TRACE(functions_logger, "run_scheduled_host_check_3x()"); @@ -1625,8 +1640,7 @@ int host::run_scheduled_check(int check_options, double latency) { current_time + static_cast<time_t>(check_interval() <= 0 ? 300 - : check_interval() * - config->interval_length()); + : check_interval() * interval_length); // Make sure we rescheduled the next host check at a valid time. { @@ -1724,6 +1738,13 @@ int host::run_async_check(int check_options, if (!verify_check_viability(check_options, time_is_valid, preferred_time)) return ERROR; + int32_t host_check_timeout; +#ifdef LEGACY_CONF + host_check_timeout = config->host_check_timeout(); +#else + host_check_timeout = pb_config.host_check_timeout(); +#endif + // If this check is a rescheduled check, propagate the rescheduled check // flag to the host. This solves the problem when a new host check is bound // to be rescheduled but would be discarded because a host check is already @@ -1855,8 +1876,7 @@ int host::run_async_check(int check_options, retry = false; try { // Run command. - get_check_command_ptr()->run(processed_cmd, *macros, - config->host_check_timeout(), + get_check_command_ptr()->run(processed_cmd, *macros, host_check_timeout, check_result_info); } catch (com::centreon::exceptions::interruption const& e) { retry = true; @@ -2055,6 +2075,23 @@ void host::check_for_flapping(bool update, double low_curve_value = 0.75; double high_curve_value = 1.25; + uint32_t interval_length; + float low_host_flap_threshold; + float high_host_flap_threshold; + bool enable_flap_detection; + +#ifdef LEGACY_CONF + interval_length = config->interval_length(); + low_host_flap_threshold = config->low_host_flap_threshold(); + high_host_flap_threshold = config->high_host_flap_threshold(); + enable_flap_detection = config->enable_flap_detection(); +#else + interval_length = pb_config.interval_length(); + low_host_flap_threshold = pb_config.low_host_flap_threshold(); + high_host_flap_threshold = pb_config.high_host_flap_threshold(); + enable_flap_detection = pb_config.enable_flap_detection(); +#endif + engine_logger(dbg_functions, basic) << "host::check_for_flapping()"; SPDLOG_LOGGER_TRACE(functions_logger, "host::check_for_flapping()"); @@ -2069,11 +2106,11 @@ void host::check_for_flapping(bool update, */ if (get_total_services() == 0) wait_threshold = static_cast<unsigned long>(get_notification_interval() * - config->interval_length()); + interval_length); else - wait_threshold = static_cast<unsigned long>( - (get_total_service_check_interval() * config->interval_length()) / - get_total_services()); + wait_threshold = + static_cast<unsigned long>(get_total_service_check_interval() * + interval_length / get_total_services()); update_history = update; @@ -2095,11 +2132,10 @@ void host::check_for_flapping(bool update, } /* what thresholds should we use (global or host-specific)? */ - low_threshold = (get_low_flap_threshold() <= 0.0) - ? config->low_host_flap_threshold() - : get_low_flap_threshold(); + low_threshold = (get_low_flap_threshold() <= 0.0) ? low_host_flap_threshold + : get_low_flap_threshold(); high_threshold = (get_high_flap_threshold() <= 0.0) - ? config->high_host_flap_threshold() + ? high_host_flap_threshold : get_high_flap_threshold(); /* record current host state */ @@ -2156,7 +2192,7 @@ void host::check_for_flapping(bool update, /* don't do anything if we don't have flap detection enabled on a program-wide * basis */ - if (!config->enable_flap_detection()) + if (!enable_flap_detection) return; /* don't do anything if we don't have flap detection enabled for this host */ @@ -2316,6 +2352,17 @@ void host::check_for_expired_acknowledgement() { int host::handle_state() { bool state_change = false; time_t current_time; + bool log_host_retries; + +#ifdef LEGACY_CONF + log_host_retries = config->log_host_retries(); + bool use_host_down_disable_service_checks = + config->use_host_down_disable_service_checks(); +#else + log_host_retries = pb_config.log_host_retries(); + bool use_host_down_disable_service_checks = + pb_config.host_down_disable_service_checks(); +#endif engine_logger(dbg_functions, basic) << "handle_host_state()"; SPDLOG_LOGGER_TRACE(functions_logger, "handle_host_state()"); @@ -2340,15 +2387,13 @@ int host::handle_state() { case host::state_down: set_last_time_down(current_time); have_to_change_service_state = - config->use_host_down_disable_service_checks() && - get_state_type() == hard; + use_host_down_disable_service_checks && get_state_type() == hard; break; case host::state_unreachable: set_last_time_unreachable(current_time); have_to_change_service_state = - config->use_host_down_disable_service_checks() && - get_state_type() == hard; + use_host_down_disable_service_checks && get_state_type() == hard; break; default: @@ -2413,7 +2458,7 @@ int host::handle_state() { /* write the host state change to the main log file */ if (get_state_type() == hard || - (get_state_type() == soft && config->log_host_retries() == true)) + (get_state_type() == soft && log_host_retries)) log_event(); /* check for start of flexible (non-fixed) scheduled downtime */ @@ -2448,7 +2493,7 @@ int host::handle_state() { notify(reason_recovery, "", "", notifier::notification_option_none); /* if we're in a soft state and we should log host retries, do so now... */ - if (get_state_type() == soft && config->log_host_retries()) + if (get_state_type() == soft && log_host_retries) log_event(); } @@ -2458,7 +2503,13 @@ int host::handle_state() { /* updates host performance data */ void host::update_performance_data() { /* should we be processing performance data for anything? */ - if (!config->process_performance_data()) + +#ifdef LEGACY_CONF + bool process_performance_data = config->process_performance_data(); +#else + bool process_performance_data = pb_config.process_performance_data(); +#endif + if (!process_performance_data) return; /* should we process performance data for this host? */ @@ -2493,14 +2544,18 @@ bool host::verify_check_viability(int check_options, engine_logger(dbg_functions, basic) << "check_host_check_viability_3x()"; SPDLOG_LOGGER_TRACE(functions_logger, "check_host_check_viability_3x()"); + uint32_t interval_length; +#ifdef LEGACY_CONF + interval_length = config->interval_length(); +#else + interval_length = pb_config.interval_length(); +#endif /* get the check interval to use if we need to reschedule the check */ if (this->get_state_type() == soft && this->get_current_state() != host::state_up) - check_interval = - static_cast<int>(this->retry_interval() * config->interval_length()); + check_interval = static_cast<int>(this->retry_interval() * interval_length); else - check_interval = - static_cast<int>(this->check_interval() * config->interval_length()); + check_interval = static_cast<int>(this->check_interval() * interval_length); /* make sure check interval is positive - otherwise use 5 minutes out for next * check */ @@ -2575,6 +2630,16 @@ int host::notify_contact(nagios_macros* mac, << "** Notifying contact '" << cntct->get_name() << "'"; notifications_logger->debug("** Notifying contact '{}'", cntct->get_name()); + bool log_notifications; + uint32_t notification_timeout; +#ifdef LEGACY_CONF + log_notifications = config->log_notifications(); + notification_timeout = config->notification_timeout(); +#else + log_notifications = pb_config.log_notifications(); + notification_timeout = pb_config.notification_timeout(); +#endif + /* get start time */ gettimeofday(&start_time, nullptr); @@ -2632,7 +2697,7 @@ int host::notify_contact(nagios_macros* mac, processed_command); /* log the notification to program log file */ - if (config->log_notifications()) { + if (log_notifications) { char const* host_state_str("UP"); if ((unsigned int)_current_state < tab_host_states.size()) // sizeof(tab_host_state_str) / sizeof(*tab_host_state_str)) @@ -2671,7 +2736,7 @@ int host::notify_contact(nagios_macros* mac, if (command_is_allowed_by_whitelist(processed_command, NOTIF_TYPE)) { try { std::string out; - my_system_r(mac, processed_command, config->notification_timeout(), + my_system_r(mac, processed_command, notification_timeout, &early_timeout, &exectime, out, 0); } catch (std::exception const& e) { engine_logger(log_runtime_error, basic) @@ -2693,12 +2758,11 @@ int host::notify_contact(nagios_macros* mac, engine_logger(log_host_notification | log_runtime_warning, basic) << "Warning: Contact '" << cntct->get_name() << "' host notification command '" << processed_command - << "' timed out after " << config->notification_timeout() - << " seconds"; + << "' timed out after " << notification_timeout << " seconds"; notifications_logger->info( "Warning: Contact '{}' host notification command '{}' timed out " "after {} seconds", - cntct->get_name(), processed_command, config->notification_timeout()); + cntct->get_name(), processed_command, notification_timeout); } /* get end time */ @@ -2874,6 +2938,19 @@ bool host::is_result_fresh(time_t current_time, int log_this) { int tminutes = 0; int tseconds = 0; + uint32_t interval_length; + int32_t additional_freshness_latency; + uint32_t max_host_check_spread; +#ifdef LEGACY_CONF + interval_length = config->interval_length(); + additional_freshness_latency = config->additional_freshness_latency(); + max_host_check_spread = config->max_host_check_spread(); +#else + interval_length = pb_config.interval_length(); + additional_freshness_latency = pb_config.additional_freshness_latency(); + max_host_check_spread = pb_config.max_host_check_spread(); +#endif + engine_logger(dbg_checks, most) << "Checking freshness of host '" << name() << "'..."; SPDLOG_LOGGER_DEBUG(checks_logger, "Checking freshness of host '{}'...", @@ -2887,9 +2964,9 @@ bool host::is_result_fresh(time_t current_time, int log_this) { interval = check_interval(); else interval = retry_interval(); - freshness_threshold = static_cast<int>( - (interval * config->interval_length()) + get_latency() + - config->additional_freshness_latency()); + freshness_threshold = + static_cast<int>(interval * interval_length + get_latency() + + additional_freshness_latency); } else freshness_threshold = get_freshness_threshold(); @@ -2913,9 +2990,8 @@ bool host::is_result_fresh(time_t current_time, int log_this) { * suggested by Altinity */ else if (active_checks_enabled() && event_start > get_last_check() && get_freshness_threshold() == 0) - expiration_time = - (time_t)(event_start + freshness_threshold + - (config->max_host_check_spread() * config->interval_length())); + expiration_time = (time_t)(event_start + freshness_threshold + + max_host_check_spread * interval_length); else expiration_time = (time_t)(get_last_check() + freshness_threshold); @@ -3087,8 +3163,23 @@ int host::process_check_result_3x(enum host::host_state new_state, std::list<host*> check_hostlist; host::host_state parent_state = host::state_up; time_t current_time = 0L; - time_t next_check{get_last_check() + - check_interval() * config->interval_length()}; + + uint32_t interval_length; + bool log_passive_checks; + bool enable_predictive_host_dependency_checks; +#ifdef LEGACY_CONF + interval_length = config->interval_length(); + log_passive_checks = config->log_passive_checks(); + enable_predictive_host_dependency_checks = + config->enable_predictive_host_dependency_checks(); +#else + interval_length = pb_config.interval_length(); + log_passive_checks = pb_config.log_passive_checks(); + enable_predictive_host_dependency_checks = + pb_config.enable_predictive_host_dependency_checks(); +#endif + + time_t next_check{get_last_check() + check_interval() * interval_length}; time_t preferred_time = 0L; time_t next_valid_time = 0L; int run_async_check = true; @@ -3121,7 +3212,7 @@ int host::process_check_result_3x(enum host::host_state new_state, /* log passive checks - we need to do this here, as some my bypass external * commands by getting dropped in checkresults dir */ if (get_check_type() == check_passive) { - if (config->log_passive_checks()) + if (log_passive_checks) engine_logger(log_passive_check, basic) << "PASSIVE HOST CHECK: " << name() << ";" << new_state << ";" << get_plugin_output(); @@ -3231,8 +3322,7 @@ int host::process_check_result_3x(enum host::host_state new_state, /* schedule a re-check of the host at the retry interval because we * can't determine its final state yet... */ if (get_state_type() == soft) - next_check = - get_last_check() + retry_interval() * config->interval_length(); + next_check = get_last_check() + retry_interval() * interval_length; } } } @@ -3387,8 +3477,7 @@ int host::process_check_result_3x(enum host::host_state new_state, /* schedule a re-check of the host at the retry interval because we * can't determine its final state yet... */ - next_check = - get_last_check() + retry_interval() * config->interval_length(); + next_check = get_last_check() + retry_interval() * interval_length; /* propagate checks to immediate parents if they are UP */ /* we do this because a parent host (or grandparent) may have gone down @@ -3440,7 +3529,7 @@ int host::process_check_result_3x(enum host::host_state new_state, } /* check dependencies on second to last host check */ - if (config->enable_predictive_host_dependency_checks() && + if (enable_predictive_host_dependency_checks && get_current_attempt() == max_check_attempts() - 1) { /* propagate checks to hosts that THIS ONE depends on for * notifications AND execution */ @@ -3454,9 +3543,8 @@ int host::process_check_result_3x(enum host::host_state new_state, "Propagating predictive dependency checks to hosts this " "one depends on..."); - for (hostdependency_mmap::const_iterator - it{hostdependency::hostdependencies.find(name())}, - end{hostdependency::hostdependencies.end()}; + for (auto it = hostdependency::hostdependencies.find(name()), + end = hostdependency::hostdependencies.end(); it != end && it->first == name(); ++it) { hostdependency* temp_dependency(it->second.get()); if (temp_dependency->dependent_host_ptr == this && @@ -3706,6 +3794,12 @@ bool host::authorized_by_dependencies(dependency::types dependency_type) const { engine_logger(dbg_functions, basic) << "host::authorized_by_dependencies()"; SPDLOG_LOGGER_TRACE(functions_logger, "host::authorized_by_dependencies()"); +#ifdef LEGACY_CONF + bool soft_state_dependencies = config->soft_state_dependencies(); +#else + bool soft_state_dependencies = pb_config.soft_state_dependencies(); +#endif + auto p(hostdependency::hostdependencies.equal_range(name())); for (hostdependency_mmap::const_iterator it{p.first}, end{p.second}; it != end; ++it) { @@ -3719,7 +3813,7 @@ bool host::authorized_by_dependencies(dependency::types dependency_type) const { if (!dep->master_host_ptr) continue; - /* Skip this dependency if it has a timepriod and the current time is + /* Skip this dependency if it has a timeperiod and the current time is * not valid */ time_t current_time{std::time(nullptr)}; if (!dep->get_dependency_period().empty() && @@ -3730,7 +3824,7 @@ bool host::authorized_by_dependencies(dependency::types dependency_type) const { * state) */ host_state state = (dep->master_host_ptr->get_state_type() == notifier::soft && - !config->soft_state_dependencies()) + !soft_state_dependencies) ? dep->master_host_ptr->get_last_hard_state() : dep->master_host_ptr->get_current_state(); @@ -3756,6 +3850,13 @@ bool host::authorized_by_dependencies(dependency::types dependency_type) const { void host::check_result_freshness() { time_t current_time = 0L; + bool check_host_freshness; +#ifdef LEGACY_CONF + check_host_freshness = config->check_host_freshness(); +#else + check_host_freshness = pb_config.check_host_freshness(); +#endif + engine_logger(dbg_functions, basic) << "check_host_result_freshness()"; SPDLOG_LOGGER_TRACE(functions_logger, "check_host_result_freshness()"); engine_logger(dbg_checks, most) @@ -3765,7 +3866,7 @@ void host::check_result_freshness() { "Attempting to check the freshness of host check results..."); /* bail out if we're not supposed to be checking freshness */ - if (!config->check_host_freshness()) { + if (!check_host_freshness) { engine_logger(dbg_checks, most) << "Host freshness checking is disabled."; SPDLOG_LOGGER_DEBUG(checks_logger, "Host freshness checking is disabled."); return; @@ -3867,6 +3968,16 @@ void host::check_for_orphaned() { engine_logger(dbg_functions, basic) << "check_for_orphaned_hosts()"; SPDLOG_LOGGER_TRACE(functions_logger, "check_for_orphaned_hosts()"); + int32_t host_check_timeout; + uint32_t check_reaper_interval; +#ifdef LEGACY_CONF + host_check_timeout = config->host_check_timeout(); + check_reaper_interval = config->check_reaper_interval(); +#else + host_check_timeout = pb_config.host_check_timeout(); + check_reaper_interval = pb_config.check_reaper_interval(); +#endif + /* get the current time */ time(¤t_time); @@ -3886,8 +3997,7 @@ void host::check_for_orphaned() { * 10 minutes slack time) */ expected_time = (time_t)(it->second->get_next_check() + it->second->get_latency() + - config->host_check_timeout() + - config->check_reaper_interval() + 600); + host_check_timeout + check_reaper_interval + 600); /* this host was supposed to have executed a while ago, but for some reason * the results haven't come back in... */ diff --git a/engine/src/hostdependency.cc b/engine/src/hostdependency.cc index 497ac2695ab..04327efefe9 100644 --- a/engine/src/hostdependency.cc +++ b/engine/src/hostdependency.cc @@ -301,6 +301,7 @@ void hostdependency::resolve(uint32_t& w [[maybe_unused]], uint32_t& e) { } } +#ifdef LEGACY_CONF /** * Find a service dependency from its key. * @@ -323,3 +324,25 @@ hostdependency_mmap::iterator hostdependency::hostdependencies_find( } return p.first == p.second ? hostdependencies.end() : p.first; } +#else +/** + * Find a service dependency from its key. + * + * @param[in] k The service dependency configuration. + * + * @return Iterator to the element if found, + * servicedependencies().end() otherwise. + */ +hostdependency_mmap::iterator hostdependency::hostdependencies_find( + const std::pair<std::string_view, size_t>& key) { + std::pair<hostdependency_mmap::iterator, hostdependency_mmap::iterator> p; + + p = hostdependencies.equal_range(key.first); + while (p.first != p.second) { + if (p.first->second->internal_key() == key.second) + break; + ++p.first; + } + return p.first == p.second ? hostdependencies.end() : p.first; +} +#endif diff --git a/engine/src/hostescalation.cc b/engine/src/hostescalation.cc index d3bd6bf6064..e1ed434ddbb 100644 --- a/engine/src/hostescalation.cc +++ b/engine/src/hostescalation.cc @@ -23,6 +23,10 @@ #include "com/centreon/engine/logging/logger.hh" #include "com/centreon/engine/shared.hh" #include "com/centreon/engine/string.hh" +#ifdef LEGACY_CONF +#else +#include "common/engine_conf/state.pb.h" +#endif using namespace com::centreon; using namespace com::centreon::engine; @@ -126,6 +130,7 @@ void hostescalation::resolve(uint32_t& w [[maybe_unused]], uint32_t& e) { } } +#ifdef LEGACY_CONF /** * @brief Checks that this hostescalation corresponds to the Configuration * object obj. This function doesn't check contactgroups as it is usually used @@ -156,3 +161,35 @@ bool hostescalation::matches(const configuration::hostescalation& obj) const { return true; } +#else +/** + * @brief Checks that this hostescalation corresponds to the Configuration + * object obj. This function doesn't check contactgroups as it is usually used + * to modify them. + * + * @param obj A host escalation configuration object. + * + * @return A boolean that is True if they match. + */ +bool hostescalation::matches(const configuration::Hostescalation& obj) const { + uint32_t escalate_on = + ((obj.escalation_options() & configuration::action_he_down) + ? notifier::down + : notifier::none) | + ((obj.escalation_options() & configuration::action_he_unreachable) + ? notifier::unreachable + : notifier::none) | + ((obj.escalation_options() & configuration::action_he_recovery) + ? notifier::up + : notifier::none); + if (_hostname != *obj.hosts().data().begin() || + get_first_notification() != obj.first_notification() || + get_last_notification() != obj.last_notification() || + get_notification_interval() != obj.notification_interval() || + get_escalation_period() != obj.escalation_period() || + get_escalate_on() != escalate_on) + return false; + + return true; +} +#endif diff --git a/engine/src/macros.cc b/engine/src/macros.cc index 673b76b8856..943e56efe57 100644 --- a/engine/src/macros.cc +++ b/engine/src/macros.cc @@ -673,9 +673,14 @@ std::string clean_macro_chars(std::string const& macro, int options) { if (ch < 32 || ch == 127) continue; - /* illegal user-specified characters */ + /* illegal user-specified characters */ +#ifdef LEGACY_CONF if (config->illegal_output_chars().find(ch) == std::string::npos) retval[y++] = retval[x]; +#else + if (pb_config.illegal_output_chars().find(ch) == std::string::npos) + retval[y++] = retval[x]; +#endif } retval.resize(y); diff --git a/engine/src/main.cc b/engine/src/main.cc index 59d9ab6c217..9dee3f52c85 100644 --- a/engine/src/main.cc +++ b/engine/src/main.cc @@ -69,8 +69,13 @@ namespace asio = boost::asio; #include "com/centreon/engine/version.hh" #include "com/centreon/io/directory_entry.hh" #include "com/centreon/logging/engine.hh" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/parser.hh" #include "common/engine_legacy_conf/state.hh" +#else +#include "common/engine_conf/parser.hh" +#include "common/engine_conf/state_helper.hh" +#endif #include "common/log_v2/log_v2.hh" using namespace com::centreon::engine; @@ -118,16 +123,21 @@ int main(int argc, char* argv[]) { // Load singletons and global variable. log_v2::load("centengine"); - /* It's time to set the logger. Later, we will have acceses from multiple - * threads and we'll only be able to change atomic values. */ + /* It's time to set the logger. Later, we will have access from multiple + * threads and we'll only be able to change loggers atomic values. */ +#ifdef LEGACY_CONF config = new configuration::state; +#endif init_loggers(); configuration::applier::logging::instance(); com::centreon::common::pool::load(g_io_context, runtime_logger); +#ifdef LEGACY_CONF config_logger->info("Configuration mechanism used: legacy"); - config = new configuration::state; +#else + config_logger->info("Configuration mechanism used: protobuf"); +#endif logging::broker backend_broker_log; @@ -186,15 +196,15 @@ int main(int argc, char* argv[]) { error = true; else { // Config file is last argument specified. - config_file = string::dup(argv[optind]); + config_file = argv[optind]; // Make sure the config file uses an absolute path. if (config_file[0] != '/') { // Get absolute path of current working directory. - std::string buffer(com::centreon::io::directory_entry::current_path()); - buffer.append("/"); - buffer.append(config_file); - string::setstr(config_file, buffer); + std::string buffer{ + fmt::format("{}/{}", std::string{std::filesystem::current_path()}, + config_file)}; + config_file = std::move(buffer); } } @@ -274,6 +284,7 @@ int main(int argc, char* argv[]) { // We're just verifying the configuration. else if (verify_config) { try { +#ifdef LEGACY_CONF // Read in the configuration files (main config file, // resource and object config files). configuration::error_cnt err; @@ -284,7 +295,17 @@ int main(int argc, char* argv[]) { } configuration::applier::state::instance().apply(config, err); - +#else + // Read in the configuration files (main config file, + // resource and object config files). + configuration::error_cnt err; + configuration::State pb_config; + { + configuration::parser p; + p.parse(config_file, &pb_config, err); + } + configuration::applier::state::instance().apply(pb_config, err); +#endif std::cout << "\n Checked " << commands::command::commands.size() << " commands.\n Checked " << commands::connector::connectors.size() @@ -331,6 +352,7 @@ int main(int argc, char* argv[]) { // We're just testing scheduling. else if (test_scheduling) { try { +#ifdef LEGACY_CONF // Parse configuration. configuration::state config; configuration::error_cnt err; @@ -353,6 +375,30 @@ int main(int argc, char* argv[]) { // Apply configuration. configuration::applier::state::instance().apply(config, err, &state); +#else + // Parse configuration. + configuration::State pb_config; + configuration::error_cnt err; + { + configuration::parser p; + p.parse(config_file, &pb_config, err); + } + + // Parse retention. + retention::state state; + if (!pb_config.state_retention_file().empty()) { + retention::parser p; + try { + p.parse(pb_config.state_retention_file(), state); + } catch (std::exception const& e) { + std::cout << "Error while parsing the retention: {}" << e.what() + << std::endl; + } + } + + // Apply configuration. + configuration::applier::state::instance().apply(pb_config, err, &state); +#endif display_scheduling_info(); retval = EXIT_SUCCESS; @@ -367,7 +413,19 @@ int main(int argc, char* argv[]) { } // Else start to monitor things. else { + auto generate_port = [] { + std::random_device rd; // Will be used to obtain a seed for the + // random number engine + std::mt19937 gen( + rd()); // Standard mersenne_twister_engine seeded with rd() + std::uniform_int_distribution<uint16_t> dis(50000, 50999); + + uint16_t port = dis(gen); + return port; + }; + try { +#ifdef LEGACY_CONF // Parse configuration. configuration::error_cnt err; configuration::state config; @@ -382,15 +440,8 @@ int main(int argc, char* argv[]) { configuration::extended_conf::update_state(config); uint16_t port = config.rpc_port(); - if (!port) { - std::random_device rd; // Will be used to obtain a seed for the - // random number engine - std::mt19937 gen( - rd()); // Standard mersenne_twister_engine seeded with rd() - std::uniform_int_distribution<uint16_t> dis(50000, 50999); - - port = dis(gen); - } + if (!port) + port = generate_port(); const std::string& listen_address = config.rpc_listen_address(); @@ -420,14 +471,11 @@ int main(int argc, char* argv[]) { mac->x[MACRO_PROCESSSTARTTIME] = std::to_string(program_start); // Load broker modules. - for (std::list<std::string>::const_iterator - it(config.broker_module().begin()), - end(config.broker_module().end()); - it != end; ++it) { + for (auto& m : config.broker_module()) { std::string filename; std::string args; - if (!string::split(*it, filename, args, ' ')) - filename = *it; + if (!string::split(m, filename, args, ' ')) + filename = m; broker::loader::instance().add_module(filename, args); } neb_init_callback_list(); @@ -438,6 +486,66 @@ int main(int argc, char* argv[]) { // Apply configuration. configuration::applier::state::instance().apply(config, err, &state); +#else + // Parse configuration. + configuration::error_cnt err; + configuration::State pb_config; + { + configuration::parser p; + p.parse(config_file, &pb_config, err); + } + + configuration::extended_conf::load_all(extended_conf_file.begin(), + extended_conf_file.end()); + + configuration::extended_conf::update_state(&pb_config); + uint16_t port = pb_config.grpc_port(); + + if (!port) + port = generate_port(); + + const std::string& listen_address = pb_config.rpc_listen_address(); + + std::unique_ptr<enginerpc, std::function<void(enginerpc*)> > rpc( + new enginerpc(listen_address, port), [](enginerpc* rpc) { + rpc->shutdown(); + delete rpc; + }); + + // Parse retention. + retention::state state; + { + retention::parser p; + try { + p.parse(pb_config.state_retention_file(), state); + } catch (const std::exception& e) { + config_logger->error("{}", e.what()); + engine_logger(logging::log_config_error, logging::basic) + << e.what(); + } + } + + // Get program (re)start time and save as macro. Needs to be + // done after we read config files, as user may have overridden + // timezone offset. + program_start = std::time(nullptr); + mac->x[MACRO_PROCESSSTARTTIME] = std::to_string(program_start); + + // Load broker modules. + for (auto& m : pb_config.broker_module()) { + std::pair<std::string, std::string> p = + absl::StrSplit(m, absl::MaxSplits(' ', 1)); + broker::loader::instance().add_module(p.first, p.second); + } + neb_init_callback_list(); + + // Add broker backend. + com::centreon::logging::engine::instance().add( + &backend_broker_log, logging::log_all, logging::basic); + + // Apply configuration. + configuration::applier::state::instance().apply(pb_config, err, &state); +#endif // Handle signals (interrupts). setup_sighandler(); @@ -484,8 +592,12 @@ int main(int argc, char* argv[]) { broker_program_state(NEBTYPE_PROCESS_SHUTDOWN, NEBFLAG_USER_INITIATED); - // Save service and host state information. + // Save service and host state information. +#ifdef LEGACY_CONF retention::dump::save(::config->state_retention_file()); +#else + retention::dump::save(::pb_config.state_retention_file()); +#endif // Clean up the status data. cleanup_status_data(true); @@ -513,8 +625,6 @@ int main(int argc, char* argv[]) { // Memory cleanup. cleanup(); spdlog::shutdown(); - delete[] config_file; - config_file = NULL; } catch (std::exception const& e) { engine_logger(logging::log_runtime_error, logging::basic) << "Error: " << e.what(); @@ -522,8 +632,11 @@ int main(int argc, char* argv[]) { } // Unload singletons and global objects. +#ifdef LEGACY_CONF delete config; config = nullptr; +#endif + g_io_context->stop(); com::centreon::common::pool::unload(); diff --git a/engine/src/nebmods.cc b/engine/src/nebmods.cc index 528dbc0097b..43cf720fe5f 100644 --- a/engine/src/nebmods.cc +++ b/engine/src/nebmods.cc @@ -99,7 +99,11 @@ int neb_load_all_modules() { try { loader& ldr(loader::instance()); - const std::string& mod_dir(config->broker_module_directory()); +#ifdef LEGACY_CONF + const std::string& mod_dir = config->broker_module_directory(); +#else + const std::string& mod_dir = pb_config.broker_module_directory(); +#endif if (!mod_dir.empty()) ldr.load_directory(mod_dir); diff --git a/engine/src/notifier.cc b/engine/src/notifier.cc index 418229df236..50ba8b8b2f5 100644 --- a/engine/src/notifier.cc +++ b/engine/src/notifier.cc @@ -244,7 +244,12 @@ bool notifier::_is_notification_viable_normal(reason_type type } /* are notifications enabled? */ - if (!config->enable_notifications()) { +#ifdef LEGACY_CONF + bool enable_notifications = config->enable_notifications(); +#else + bool enable_notifications = pb_config.enable_notifications(); +#endif + if (!enable_notifications) { engine_logger(dbg_notifications, more) << "Notifications are disabled, so notifications will " "not be sent out."; @@ -355,19 +360,24 @@ bool notifier::_is_notification_viable_normal(reason_type type return false; } +#ifdef LEGACY_CONF + uint32_t interval_length = config->interval_length(); +#else + uint32_t interval_length = pb_config.interval_length(); +#endif if (_first_notification_delay > 0 && !_notification[cat_normal] && get_last_hard_state_change() + - _first_notification_delay * config->interval_length() > + _first_notification_delay * interval_length > now) { engine_logger(dbg_notifications, more) << "This notifier is configured with a first notification delay, we " "won't send notification until timestamp " - << (_first_notification_delay * config->interval_length()); + << (_first_notification_delay * interval_length); SPDLOG_LOGGER_DEBUG( notifications_logger, "This notifier is configured with a first notification delay, we " "won't send notification until timestamp {}", - _first_notification_delay * config->interval_length()); + _first_notification_delay * interval_length); return false; } @@ -399,19 +409,17 @@ bool notifier::_is_notification_viable_normal(reason_type type _last_notification); return false; } else if (notification_interval > 0) { - if (_last_notification + - notification_interval * config->interval_length() > + if (_last_notification + notification_interval * interval_length > now) { engine_logger(dbg_notifications, more) << "This notifier problem has been sent at " << _last_notification << " so it won't be sent until " - << (notification_interval * config->interval_length()); + << (notification_interval * interval_length); SPDLOG_LOGGER_DEBUG( notifications_logger, "This notifier problem has been sent at {} so it won't be sent " "until {}", - _last_notification, - notification_interval * config->interval_length()); + _last_notification, notification_interval * interval_length); return false; } } @@ -431,8 +439,13 @@ bool notifier::_is_notification_viable_recovery(reason_type type bool retval{true}; bool send_later{false}; +#ifdef LEGACY_CONF + bool enable_notifications = config->enable_notifications(); +#else + bool enable_notifications = pb_config.enable_notifications(); +#endif /* are notifications enabled? */ - if (!config->enable_notifications()) { + if (!enable_notifications) { engine_logger(dbg_notifications, more) << "Notifications are disabled, so notifications will " "not be sent out."; @@ -456,10 +469,20 @@ bool notifier::_is_notification_viable_recovery(reason_type type std::time_t now; std::time(&now); +#ifdef LEGACY_CONF + uint32_t interval_length = config->interval_length(); + bool use_send_recovery_notifications_anyways = + config->use_send_recovery_notifications_anyways(); +#else + uint32_t interval_length = pb_config.interval_length(); + bool use_send_recovery_notifications_anyways = + pb_config.send_recovery_notifications_anyways(); +#endif + // if use_send_recovery_notifications_anyways flag is set, we don't take // timeperiod into account for recovery if (!check_time_against_period_for_notif(now, tp)) { - if (config->use_send_recovery_notifications_anyways()) { + if (use_send_recovery_notifications_anyways) { SPDLOG_LOGGER_DEBUG(notifications_logger, "send_recovery_notifications_anyways flag enabled, " "recovery notification is viable even if we are " @@ -523,7 +546,7 @@ bool notifier::_is_notification_viable_recovery(reason_type type retval = false; send_later = false; } else if (get_last_hard_state_change() + - _recovery_notification_delay * config->interval_length() > + _recovery_notification_delay * interval_length > now) { engine_logger(dbg_notifications, more) << "This notifier is configured with a recovery notification delay. " @@ -591,8 +614,13 @@ bool notifier::_is_notification_viable_acknowledgement( return true; } +#ifdef LEGACY_CONF + bool enable_notifications = config->enable_notifications(); +#else + bool enable_notifications = pb_config.enable_notifications(); +#endif /* are notifications enabled? */ - if (!config->enable_notifications()) { + if (!enable_notifications) { engine_logger(dbg_notifications, more) << "Notifications are disabled, so notifications will " "not be sent out."; @@ -641,7 +669,12 @@ bool notifier::_is_notification_viable_flapping(reason_type type, } /* are notifications enabled? */ - if (!config->enable_notifications()) { +#ifdef LEGACY_CONF + bool enable_notifications = config->enable_notifications(); +#else + bool enable_notifications = pb_config.enable_notifications(); +#endif + if (!enable_notifications) { engine_logger(dbg_notifications, more) << "Notifications are disabled, so notifications will " "not be sent out."; @@ -752,7 +785,12 @@ bool notifier::_is_notification_viable_downtime(reason_type type } /* are notifications enabled? */ - if (!config->enable_notifications()) { +#ifdef LEGACY_CONF + bool enable_notifications = config->enable_notifications(); +#else + bool enable_notifications = pb_config.enable_notifications(); +#endif + if (!enable_notifications) { engine_logger(dbg_notifications, more) << "Notifications are disabled, so notifications will " "not be sent out."; @@ -773,7 +811,7 @@ bool notifier::_is_notification_viable_downtime(reason_type type return false; } - if (!config->enable_notifications()) { + if (!enable_notifications) { engine_logger(dbg_notifications, more) << "Notifications are disabled, so notifications won't be sent out."; SPDLOG_LOGGER_DEBUG( @@ -824,7 +862,12 @@ bool notifier::_is_notification_viable_custom(reason_type type } /* are notifications enabled? */ - if (!config->enable_notifications()) { +#ifdef LEGACY_CONF + bool enable_notifications = config->enable_notifications(); +#else + bool enable_notifications = pb_config.enable_notifications(); +#endif + if (!enable_notifications) { engine_logger(dbg_notifications, more) << "Notifications are disabled, so notifications will " "not be sent out."; @@ -1646,9 +1689,13 @@ time_t notifier::get_next_notification_time(time_t offset) { interval_to_use); /* calculate next notification time */ +#ifdef LEGACY_CONF + uint32_t interval_length = config->interval_length(); +#else + uint32_t interval_length = pb_config.interval_length(); +#endif time_t next_notification{ - offset + - static_cast<time_t>(interval_to_use * config->interval_length())}; + offset + static_cast<time_t>(interval_to_use * interval_length)}; return next_notification; } diff --git a/engine/src/retention/applier/anomalydetection.cc b/engine/src/retention/applier/anomalydetection.cc index 7c979042767..1ea6b27de86 100644 --- a/engine/src/retention/applier/anomalydetection.cc +++ b/engine/src/retention/applier/anomalydetection.cc @@ -1,5 +1,5 @@ /** -* Copyright 2022 Centreon +* Copyright 2022-2024 Centreon * * This file is part of Centreon Engine. * @@ -32,6 +32,7 @@ using namespace com::centreon::engine; using namespace com::centreon::engine::configuration::applier; using namespace com::centreon::engine::retention; +#ifdef LEGACY_CONF /** * Update service list. * @@ -55,7 +56,33 @@ void applier::anomalydetection::apply(configuration::state const& config, } } } +#else +/** + * Update service list. + * + * @param[in] config The global configuration. + * @param[in] lst The service list to update. + * @param[in] scheduling_info_is_ok True if the retention is not + * outdated. + */ +void applier::anomalydetection::apply(const configuration::State & config, + const list_anomalydetection & lst, + bool scheduling_info_is_ok) { + for (auto& s : lst) { + try { + std::pair<uint64_t, uint64_t> id{ + get_host_and_service_id(s->host_name(), s->service_description())}; + engine::service& svc(find_service(id.first, id.second)); + _update(config, *s, dynamic_cast<engine::anomalydetection&>(svc), + scheduling_info_is_ok); + } catch (...) { + // ignore exception for the retention. + } + } +} +#endif +#ifdef LEGACY_CONF /** * Update internal service base on service retention. * @@ -76,3 +103,25 @@ void applier::anomalydetection::_update( obj.set_sensitivity(state.sensitivity()); } } +#else +/** + * Update internal service base on service retention. + * + * @param[in] config The global configuration. + * @param[in] state The service retention state. + * @param[in, out] obj The anomalydetection to update. + * @param[in] scheduling_info_is_ok True if the retention is + * not outdated. + */ +void applier::anomalydetection::_update( + const configuration::State & config, + const retention::anomalydetection & state, + engine::anomalydetection& obj, + bool scheduling_info_is_ok) { + applier::service::update(config, state, static_cast<engine::service&>(obj), + scheduling_info_is_ok); + if (state.sensitivity().is_set()) { + obj.set_sensitivity(state.sensitivity()); + } +} +#endif diff --git a/engine/src/retention/applier/comment.cc b/engine/src/retention/applier/comment.cc index 1ba24e5a3c8..b6a95d5ac88 100644 --- a/engine/src/retention/applier/comment.cc +++ b/engine/src/retention/applier/comment.cc @@ -1,5 +1,6 @@ /** * Copyright 2011-2013 Merethis +* Copyright 2014-2024 Centreon * * This file is part of Centreon Engine. * @@ -77,7 +78,7 @@ void applier::comment::_add_host_comment( } /** - * Add serivce comment. + * Add service comment. * * @param[in] obj The comment to add into the service. */ diff --git a/engine/src/retention/applier/contact.cc b/engine/src/retention/applier/contact.cc index 5ffce899e14..e3ea10ff219 100644 --- a/engine/src/retention/applier/contact.cc +++ b/engine/src/retention/applier/contact.cc @@ -27,6 +27,7 @@ using namespace com::centreon::engine; using namespace com::centreon::engine::configuration::applier; using namespace com::centreon::engine::retention; +#ifdef LEGACY_CONF /** * Update contact list. * @@ -43,7 +44,20 @@ void applier::contact::apply(configuration::state const& config, _update(config, **it, ct_it->second.get()); } } +#else +void applier::contact::apply(const configuration::State& config, + list_contact const& lst) { + for (list_contact::const_iterator it{lst.begin()}, end{lst.end()}; it != end; + ++it) { + contact_map::const_iterator ct_it{ + engine::contact::contacts.find((*it)->contact_name())}; + if (ct_it != engine::contact::contacts.end()) + _update(config, **it, ct_it->second.get()); + } +} +#endif +#ifdef LEGACY_CONF /** * Update internal contact base on contact retention. * @@ -153,3 +167,114 @@ void applier::contact::_update(configuration::state const& config, // update contact status. obj->update_status_info(false); } +#else +/** + * Update internal contact base on contact retention. + * + * @param[in] config The global configuration. + * @param[in] state The contact retention state. + * @param[in, out] obj The contact to update. + */ +void applier::contact::_update(const configuration::State& config, + const retention::contact& state, + com::centreon::engine::contact* obj) { + if (state.modified_attributes().is_set()) { + obj->set_modified_attributes(*state.modified_attributes() & ~0L); + // mask out attributes we don't want to retain. + } + if (state.modified_host_attributes().is_set()) { + obj->set_modified_host_attributes( + *state.modified_host_attributes() & + ~config.retained_contact_host_attribute_mask()); + // mask out attributes we don't want to retain. + } + if (state.modified_service_attributes().is_set()) { + obj->set_modified_service_attributes( + *state.modified_service_attributes() & + ~config.retained_contact_service_attribute_mask()); + // mask out attributes we don't want to retain. + } + if (obj->get_retain_status_information()) { + if (state.last_host_notification().is_set()) + obj->set_last_host_notification(*state.last_host_notification()); + if (state.last_service_notification().is_set()) + obj->set_last_service_notification(*state.last_service_notification()); + } + if (obj->get_retain_nonstatus_information()) { + if (state.host_notification_period().is_set()) { + if (obj->get_modified_host_attributes() & + MODATTR_NOTIFICATION_TIMEPERIOD) { + timeperiod* temp_timeperiod(nullptr); + timeperiod_map::const_iterator found( + timeperiod::timeperiods.find(state.host_notification_period())); + + if (found != timeperiod::timeperiods.end()) + temp_timeperiod = found->second.get(); + + if (!temp_timeperiod) + obj->set_modified_host_attributes( + obj->get_modified_host_attributes() - + MODATTR_NOTIFICATION_TIMEPERIOD); + else + obj->set_host_notification_period(*state.host_notification_period()); + } + } + if (state.service_notification_period().is_set()) { + if (obj->get_modified_service_attributes() & + MODATTR_NOTIFICATION_TIMEPERIOD) { + timeperiod* temp_timeperiod(nullptr); + timeperiod_map::const_iterator found( + timeperiod::timeperiods.find(state.host_notification_period())); + + if (found != timeperiod::timeperiods.end()) + temp_timeperiod = found->second.get(); + + if (!temp_timeperiod) + obj->set_modified_service_attributes( + obj->get_modified_service_attributes() - + MODATTR_NOTIFICATION_TIMEPERIOD); + else + obj->set_service_notification_period( + *state.service_notification_period()); + } + } + if (state.host_notifications_enabled().is_set()) { + if (obj->get_modified_host_attributes() & MODATTR_NOTIFICATIONS_ENABLED) + obj->set_host_notifications_enabled( + *state.host_notifications_enabled()); + } + if (state.service_notifications_enabled().is_set()) { + if (obj->get_modified_service_attributes() & + MODATTR_NOTIFICATIONS_ENABLED) + obj->set_service_notifications_enabled( + *state.service_notifications_enabled()); + } + + if (!state.customvariables().empty() && + (obj->get_modified_attributes() & MODATTR_CUSTOM_VARIABLE)) { + for (auto const& cv : state.customvariables()) { + obj->get_custom_variables()[cv.first].update(cv.second.value()); + } + } + } + // Adjust modified attributes if necessary. + else + obj->set_modified_attributes(MODATTR_NONE); + + // Adjust modified attributes if no custom variable has been changed. + if (obj->get_modified_attributes() & MODATTR_CUSTOM_VARIABLE) { + bool at_least_one_modified(false); + for (auto const& cv : obj->get_custom_variables()) + if (cv.second.has_been_modified()) { + at_least_one_modified = true; + break; + } + if (!at_least_one_modified) + obj->set_modified_attributes(obj->get_modified_attributes() - + MODATTR_CUSTOM_VARIABLE); + } + + // update contact status. + obj->update_status_info(false); +} +#endif diff --git a/engine/src/retention/applier/host.cc b/engine/src/retention/applier/host.cc index c1f6e94f6a9..30e8058297b 100644 --- a/engine/src/retention/applier/host.cc +++ b/engine/src/retention/applier/host.cc @@ -29,6 +29,7 @@ using namespace com::centreon::engine; using namespace com::centreon::engine::configuration::applier; using namespace com::centreon::engine::retention; +#ifdef LEGACY_CONF /** * Update host list. * @@ -51,7 +52,32 @@ void applier::host::apply(configuration::state const& config, } } } +#else +/** + * Update host list. + * + * @param[in] config The global configuration. + * @param[in] lst The host list to update. + * @param[in] scheduling_info_is_ok True if the retention is not + * outdated. + */ +void applier::host::apply(const configuration::State& config, + list_host const& lst, + bool scheduling_info_is_ok) { + for (list_host::const_iterator it(lst.begin()), end(lst.end()); it != end; + ++it) { + try { + com::centreon::engine::host& hst( + find_host(get_host_id((*it)->host_name().c_str()))); + _update(config, **it, hst, scheduling_info_is_ok); + } catch (...) { + // ignore exception for the retention. + } + } +} +#endif +#ifdef LEGACY_CONF /** * Update internal host base on host retention. * @@ -313,3 +339,266 @@ void applier::host::_update(configuration::state const& config, obj.update_adaptive_data(); obj.update_status(); } +#else +/** + * Update internal host base on host retention. + * + * @param[in] config The global configuration. + * @param[in] state The host retention state. + * @param[in, out] obj The host to update. + * @param[in] scheduling_info_is_ok True if the retention is + * not outdated. + */ +void applier::host::_update(const configuration::State& config, + const retention::host& state, + com::centreon::engine::host& obj, + bool scheduling_info_is_ok) { + if (state.modified_attributes().is_set()) { + obj.set_modified_attributes(*state.modified_attributes()); + // mask out attributes we don't want to retain. + obj.set_modified_attributes(obj.get_modified_attributes() & + ~config.retained_host_attribute_mask()); + } + + if (obj.get_retain_status_information()) { + if (state.has_been_checked().is_set()) + obj.set_has_been_checked(*state.has_been_checked()); + if (state.check_execution_time().is_set()) + obj.set_execution_time(*state.check_execution_time()); + if (state.check_latency().is_set()) + obj.set_latency(*state.check_latency()); + if (state.check_type().is_set()) + obj.set_check_type( + static_cast<checkable::check_type>(*state.check_type())); + if (state.current_state().is_set()) + obj.set_current_state( + static_cast<engine::host::host_state>(*state.current_state())); + if (state.last_state().is_set()) + obj.set_last_state( + static_cast<engine::host::host_state>(*state.last_state())); + if (state.last_hard_state().is_set()) + obj.set_last_hard_state( + static_cast<engine::host::host_state>(*state.last_hard_state())); + if (state.plugin_output().is_set()) + obj.set_plugin_output(*state.plugin_output()); + if (state.long_plugin_output().is_set()) + obj.set_long_plugin_output(*state.long_plugin_output()); + if (state.performance_data().is_set()) + obj.set_perf_data(*state.performance_data()); + if (state.last_acknowledgement().is_set()) + obj.set_last_acknowledgement(*state.last_acknowledgement()); + if (state.last_check().is_set()) + obj.set_last_check(*state.last_check()); + if (state.next_check().is_set() && config.use_retained_scheduling_info() && + scheduling_info_is_ok) + obj.set_next_check(*state.next_check()); + if (state.check_options().is_set() && + config.use_retained_scheduling_info() && scheduling_info_is_ok) + obj.set_check_options(*state.check_options()); + if (state.current_attempt().is_set()) + obj.set_current_attempt(*state.current_attempt()); + if (state.current_event_id().is_set()) + obj.set_current_event_id(*state.current_event_id()); + if (state.last_event_id().is_set()) + obj.set_last_event_id(*state.last_event_id()); + if (state.current_problem_id().is_set()) + obj.set_current_problem_id(*state.current_problem_id()); + if (state.last_problem_id().is_set()) + obj.set_last_problem_id(*state.last_problem_id()); + if (state.state_type().is_set()) + obj.set_state_type( + static_cast<enum notifier::state_type>(*state.state_type())); + if (state.last_state_change().is_set()) + obj.set_last_state_change(*state.last_state_change()); + if (state.last_hard_state_change().is_set()) + obj.set_last_hard_state_change(*state.last_hard_state_change()); + if (state.last_time_up().is_set()) + obj.set_last_time_up(*state.last_time_up()); + if (state.last_time_down().is_set()) + obj.set_last_time_down(*state.last_time_down()); + if (state.last_time_unreachable().is_set()) + obj.set_last_time_unreachable(*state.last_time_unreachable()); + obj.set_notified_on( + (state.notified_on_down().is_set() && *state.notified_on_down() + ? notifier::down + : notifier::none) | + (state.notified_on_unreachable().is_set() && + *state.notified_on_unreachable() + ? notifier::unreachable + : notifier::none)); + if (state.last_notification().is_set()) + obj.set_last_notification(*state.last_notification()); + if (state.current_notification_number().is_set()) + obj.set_notification_number(*state.current_notification_number()); + if (state.current_notification_id().is_set()) + obj.set_current_notification_id(*state.current_notification_id()); + if (state.has_notifications()) { + for (int i = 0; i < 6; i++) + obj.set_notification(i, state.notifications()[i]); + } + if (state.percent_state_change().is_set()) + obj.set_percent_state_change(*state.percent_state_change()); + if (state.state_history().is_set()) { + utils::set_state_history(*state.state_history(), obj.get_state_history()); + obj.set_state_history_index(0); + } + } + + if (obj.get_retain_nonstatus_information()) { + if (state.acknowledgement_type().is_set()) + obj.set_acknowledgement( + static_cast<AckType>(*state.acknowledgement_type())); + else + obj.set_acknowledgement(engine::AckType::NONE); + + if (state.notifications_enabled().is_set() && + (obj.get_modified_attributes() & MODATTR_NOTIFICATIONS_ENABLED)) + obj.set_notifications_enabled(*state.notifications_enabled()); + + if (state.active_checks_enabled().is_set() && + (obj.get_modified_attributes() & MODATTR_ACTIVE_CHECKS_ENABLED)) + obj.set_checks_enabled(*state.active_checks_enabled()); + + if (state.passive_checks_enabled().is_set() && + (obj.get_modified_attributes() & MODATTR_PASSIVE_CHECKS_ENABLED)) + obj.set_accept_passive_checks(*state.passive_checks_enabled()); + + if (state.event_handler_enabled().is_set() && + (obj.get_modified_attributes() & MODATTR_EVENT_HANDLER_ENABLED)) + obj.set_event_handler_enabled(*state.event_handler_enabled()); + + if (state.flap_detection_enabled().is_set() && + (obj.get_modified_attributes() & MODATTR_FLAP_DETECTION_ENABLED)) + obj.set_flap_detection_enabled(*state.flap_detection_enabled()); + + if (state.process_performance_data().is_set() && + (obj.get_modified_attributes() & MODATTR_PERFORMANCE_DATA_ENABLED)) + obj.set_process_performance_data(*state.process_performance_data()); + + if (state.obsess_over_host().is_set() && + (obj.get_modified_attributes() & MODATTR_OBSESSIVE_HANDLER_ENABLED)) + obj.set_obsess_over(*state.obsess_over_host()); + + if (state.check_command().is_set() && + (obj.get_modified_attributes() & MODATTR_CHECK_COMMAND)) { + if (utils::is_command_exist(*state.check_command())) + obj.set_check_command(*state.check_command()); + else + obj.set_modified_attributes(obj.get_modified_attributes() - + MODATTR_CHECK_COMMAND); + } + + if (state.check_period().is_set() && + (obj.get_modified_attributes() & MODATTR_CHECK_TIMEPERIOD)) { + timeperiod_map::const_iterator it( + timeperiod::timeperiods.find(*state.check_period())); + if (it != timeperiod::timeperiods.end()) + obj.set_check_period(*state.check_period()); + else + obj.set_modified_attributes(obj.get_modified_attributes() - + MODATTR_CHECK_TIMEPERIOD); + } + + if (state.notification_period().is_set() && + (obj.get_modified_attributes() & MODATTR_NOTIFICATION_TIMEPERIOD)) { + timeperiod_map::const_iterator it( + timeperiod::timeperiods.find(*state.notification_period())); + if (it != timeperiod::timeperiods.end()) + obj.set_notification_period(*state.notification_period()); + else + obj.set_modified_attributes(obj.get_modified_attributes() - + MODATTR_NOTIFICATION_TIMEPERIOD); + } + + if (state.event_handler().is_set() && + (obj.get_modified_attributes() & MODATTR_EVENT_HANDLER_COMMAND)) { + if (utils::is_command_exist(*state.event_handler())) + obj.set_check_command(*state.event_handler()); + else + obj.set_modified_attributes(obj.get_modified_attributes() - + MODATTR_CHECK_COMMAND); + } + + if (state.normal_check_interval().is_set() && + (obj.get_modified_attributes() & MODATTR_NORMAL_CHECK_INTERVAL)) + obj.set_check_interval(*state.normal_check_interval()); + + if (state.retry_check_interval().is_set() && + (obj.get_modified_attributes() & MODATTR_RETRY_CHECK_INTERVAL)) + obj.set_retry_interval(*state.retry_check_interval()); + + if (state.max_attempts().is_set() && + (obj.get_modified_attributes() & MODATTR_MAX_CHECK_ATTEMPTS)) { + obj.set_max_attempts(*state.max_attempts()); + + // adjust current attempt number if in a hard state. + if (obj.get_state_type() == notifier::hard && + obj.get_current_state() != engine::host::state_up && + obj.get_current_attempt() > 1) + obj.set_current_attempt(obj.max_check_attempts()); + } + + if (!state.customvariables().empty() && + (obj.get_modified_attributes() & MODATTR_CUSTOM_VARIABLE)) { + for (map_customvar::const_iterator it(state.customvariables().begin()), + end(state.customvariables().end()); + it != end; ++it) + obj.custom_variables[it->first].update(it->second.value()); + } + } + // Adjust modified attributes if necessary. + else + obj.set_modified_attributes(MODATTR_NONE); + + bool allow_flapstart_notification(true); + + // Adjust modified attributes if no custom variable has been changed. + if (obj.get_modified_attributes() & MODATTR_CUSTOM_VARIABLE) { + bool at_least_one_modified(false); + for (auto const& cv : obj.custom_variables) { + if (cv.second.has_been_modified()) { + at_least_one_modified = true; + break; + } + } + if (!at_least_one_modified) + obj.set_modified_attributes(obj.get_modified_attributes() - + MODATTR_CUSTOM_VARIABLE); + } + + // calculate next possible notification time. + if (obj.get_current_state() != engine::host::state_up && + obj.get_last_notification()) + obj.set_next_notification( + obj.get_next_notification_time(obj.get_last_notification())); + + // ADDED 01/23/2009 adjust current check attempts if host in hard + // problem state (max attempts may have changed in config + // since restart). + if (obj.get_current_state() != engine::host::state_up && + obj.get_state_type() == notifier::hard) + obj.set_current_attempt(obj.max_check_attempts()); + + // ADDED 02/20/08 assume same flapping state if large install + // tweaks enabled. + if (config.use_large_installation_tweaks()) + obj.set_is_flapping(state.is_flapping()); + // else use normal startup flap detection logic. + else { + // host was flapping before program started. + // 11/10/07 don't allow flapping notifications to go out. + allow_flapstart_notification = !state.is_flapping(); + + // check for flapping. + obj.check_for_flapping(false, false, allow_flapstart_notification); + } + + // handle new vars added in 2.x. + if (!obj.get_last_hard_state_change()) + obj.set_last_hard_state_change(obj.get_last_state_change()); + + // update host status. + obj.update_adaptive_data(); + obj.update_status(); +} +#endif diff --git a/engine/src/retention/applier/program.cc b/engine/src/retention/applier/program.cc index add93b1b82d..2665a7d8910 100644 --- a/engine/src/retention/applier/program.cc +++ b/engine/src/retention/applier/program.cc @@ -27,6 +27,7 @@ using namespace com::centreon::engine; using namespace com::centreon::engine::retention; +#ifdef LEGACY_CONF /** * Restore programe informations. * @@ -132,3 +133,110 @@ void applier::program::apply(configuration::state& config, modified_service_process_attributes = MODATTR_NONE; } } +#else +/** + * Restore programe informations. + * + * @param[in, out] config The global configuration to update. + * @param[in] obj The global informations. + */ +void applier::program::apply(configuration::State& config, + retention::program const& obj) { + // XXX: don't use globals, replace it by config! + + if (obj.modified_host_attributes().is_set()) { + modified_host_process_attributes = *obj.modified_host_attributes(); + // mask out attributes we don't want to retain. + modified_host_process_attributes &= + ~config.retained_process_host_attribute_mask(); + } + + if (obj.modified_service_attributes().is_set()) { + modified_service_process_attributes = *obj.modified_service_attributes(); + // mask out attributes we don't want to retain. + modified_service_process_attributes &= + ~config.retained_process_host_attribute_mask(); + } + + if (config.use_retained_program_state()) { + if (obj.enable_notifications().is_set() && + (modified_host_process_attributes & MODATTR_NOTIFICATIONS_ENABLED)) + enable_notifications = *obj.enable_notifications(); + + if (obj.active_service_checks_enabled().is_set() && + (modified_service_process_attributes & MODATTR_ACTIVE_CHECKS_ENABLED)) + execute_service_checks = *obj.active_service_checks_enabled(); + + if (obj.passive_service_checks_enabled().is_set() && + (modified_service_process_attributes & MODATTR_PASSIVE_CHECKS_ENABLED)) + accept_passive_service_checks = *obj.passive_service_checks_enabled(); + + if (obj.active_host_checks_enabled().is_set() && + (modified_host_process_attributes & MODATTR_ACTIVE_CHECKS_ENABLED)) + execute_host_checks = *obj.active_host_checks_enabled(); + + if (obj.passive_host_checks_enabled().is_set() && + (modified_host_process_attributes & MODATTR_PASSIVE_CHECKS_ENABLED)) + accept_passive_host_checks = *obj.passive_host_checks_enabled(); + + if (obj.enable_event_handlers().is_set() && + (modified_host_process_attributes & MODATTR_EVENT_HANDLER_ENABLED)) + enable_event_handlers = *obj.enable_event_handlers(); + + if (obj.obsess_over_services().is_set() && + (modified_service_process_attributes & + MODATTR_OBSESSIVE_HANDLER_ENABLED)) + obsess_over_services = *obj.obsess_over_services(); + + if (obj.obsess_over_hosts().is_set() && + (modified_host_process_attributes & MODATTR_OBSESSIVE_HANDLER_ENABLED)) + obsess_over_hosts = *obj.obsess_over_hosts(); + + if (obj.check_service_freshness().is_set() && + (modified_service_process_attributes & + MODATTR_FRESHNESS_CHECKS_ENABLED)) + check_service_freshness = *obj.check_service_freshness(); + + if (obj.check_host_freshness().is_set() && + (modified_host_process_attributes & MODATTR_FRESHNESS_CHECKS_ENABLED)) + check_host_freshness = *obj.check_host_freshness(); + + if (obj.enable_flap_detection().is_set() && + (modified_host_process_attributes & MODATTR_FLAP_DETECTION_ENABLED)) + enable_flap_detection = *obj.enable_flap_detection(); + + if (obj.process_performance_data().is_set() && + (modified_host_process_attributes & MODATTR_PERFORMANCE_DATA_ENABLED)) + process_performance_data = *obj.process_performance_data(); + + if (obj.global_host_event_handler().is_set() && + (modified_host_process_attributes & MODATTR_EVENT_HANDLER_COMMAND) && + utils::is_command_exist(*obj.global_host_event_handler())) + string::setstr(global_host_event_handler, + *obj.global_host_event_handler()); + + if (obj.global_service_event_handler().is_set() && + (modified_service_process_attributes & MODATTR_EVENT_HANDLER_COMMAND) && + utils::is_command_exist(*obj.global_service_event_handler())) + string::setstr(global_service_event_handler, + *obj.global_service_event_handler()); + + if (obj.next_comment_id().is_set()) + comment::set_next_comment_id(*obj.next_comment_id()); + + if (obj.next_event_id().is_set()) + next_event_id = *obj.next_event_id(); + + if (obj.next_problem_id().is_set()) + next_problem_id = *obj.next_problem_id(); + + if (obj.next_notification_id().is_set()) + next_notification_id = *obj.next_notification_id(); + } + + if (!config.use_retained_program_state()) { + modified_host_process_attributes = MODATTR_NONE; + modified_service_process_attributes = MODATTR_NONE; + } +} +#endif diff --git a/engine/src/retention/applier/service.cc b/engine/src/retention/applier/service.cc index 13783948e2d..26288819bcb 100644 --- a/engine/src/retention/applier/service.cc +++ b/engine/src/retention/applier/service.cc @@ -30,6 +30,7 @@ using namespace com::centreon::engine; using namespace com::centreon::engine::configuration::applier; using namespace com::centreon::engine::retention; +#ifdef LEGACY_CONF /** * Update service list. * @@ -52,7 +53,310 @@ void applier::service::apply(configuration::state const& config, } } } +#else +/** + * Update service list. + * + * @param[in] config The global configuration. + * @param[in] lst The service list to update. + * @param[in] scheduling_info_is_ok True if the retention is not + * outdated. + */ +void applier::service::apply(const configuration::State& config, + const list_service& lst, + bool scheduling_info_is_ok) { + for (auto& s : lst) { + try { + std::pair<uint64_t, uint64_t> id{ + get_host_and_service_id(s->host_name(), s->service_description())}; + engine::service& svc(find_service(id.first, id.second)); + update(config, *s, svc, scheduling_info_is_ok); + } catch (...) { + // ignore exception for the retention. + } + } +} +#endif + +#ifdef LEGACY_CONF +/** + * Update internal service base on service retention. + * + * @param[in] config The global configuration. + * @param[in] state The service retention state. + * @param[in, out] obj The service to update. + * @param[in] scheduling_info_is_ok True if the retention is + * not outdated. + */ +void applier::service::update(const configuration::state& config, + const retention::service& state, + engine::service& obj, + bool scheduling_info_is_ok) { + if (state.modified_attributes().is_set()) { + obj.set_modified_attributes(*state.modified_attributes()); + // mask out attributes we don't want to retain. + obj.set_modified_attributes(obj.get_modified_attributes() & + ~config.retained_host_attribute_mask()); + } + + if (obj.get_retain_status_information()) { + if (state.has_been_checked().is_set()) + obj.set_has_been_checked(*state.has_been_checked()); + if (state.check_execution_time().is_set()) + obj.set_execution_time(*state.check_execution_time()); + if (state.check_latency().is_set()) + obj.set_latency(*state.check_latency()); + if (state.check_type().is_set()) + obj.set_check_type( + static_cast<checkable::check_type>(*state.check_type())); + if (state.current_state().is_set()) + obj.set_current_state( + static_cast<engine::service::service_state>(*state.current_state())); + if (state.last_state().is_set()) + obj.set_last_state( + static_cast<engine::service::service_state>(*state.last_state())); + if (state.last_hard_state().is_set()) + obj.set_last_hard_state(static_cast<engine::service::service_state>( + *state.last_hard_state())); + if (state.current_attempt().is_set()) + obj.set_current_attempt(*state.current_attempt()); + if (state.current_event_id().is_set()) + obj.set_current_event_id(*state.current_event_id()); + if (state.last_event_id().is_set()) + obj.set_last_event_id(*state.last_event_id()); + if (state.current_problem_id().is_set()) + obj.set_current_problem_id(*state.current_problem_id()); + if (state.last_problem_id().is_set()) + obj.set_last_problem_id(*state.last_problem_id()); + if (state.state_type().is_set()) + obj.set_state_type( + static_cast<enum notifier::state_type>(*state.state_type())); + if (state.last_state_change().is_set()) + obj.set_last_state_change(*state.last_state_change()); + if (state.last_hard_state_change().is_set()) + obj.set_last_hard_state_change(*state.last_hard_state_change()); + if (state.last_time_ok().is_set()) + obj.set_last_time_ok(*state.last_time_ok()); + if (state.last_time_warning().is_set()) + obj.set_last_time_warning(*state.last_time_warning()); + if (state.last_time_unknown().is_set()) + obj.set_last_time_unknown(*state.last_time_unknown()); + if (state.last_time_critical().is_set()) + obj.set_last_time_critical(*state.last_time_critical()); + if (state.plugin_output().is_set()) + obj.set_plugin_output(*state.plugin_output()); + if (state.long_plugin_output().is_set()) + obj.set_long_plugin_output(*state.long_plugin_output()); + if (state.performance_data().is_set()) + obj.set_perf_data(*state.performance_data()); + if (state.last_acknowledgement().is_set()) + obj.set_last_acknowledgement(*state.last_acknowledgement()); + if (state.last_check().is_set()) + obj.set_last_check(*state.last_check()); + if (state.next_check().is_set() && config.use_retained_scheduling_info() && + scheduling_info_is_ok) + obj.set_next_check(*state.next_check()); + if (state.check_options().is_set() && + config.use_retained_scheduling_info() && scheduling_info_is_ok) + obj.set_check_options(*state.check_options()); + obj.set_notified_on( + (state.notified_on_unknown().is_set() && *state.notified_on_unknown() + ? notifier::unknown + : notifier::none) | + (state.notified_on_warning().is_set() && *state.notified_on_warning() + ? notifier::warning + : notifier::none) | + (state.notified_on_critical().is_set() && *state.notified_on_critical() + ? notifier::critical + : notifier::none)); + + if (state.current_notification_number().is_set()) + obj.set_notification_number(*state.current_notification_number()); + if (state.current_notification_id().is_set()) + obj.set_current_notification_id(*state.current_notification_id()); + if (state.last_notification().is_set()) + obj.set_last_notification(*state.last_notification()); + if (state.percent_state_change().is_set()) + obj.set_percent_state_change(*state.percent_state_change()); + if (state.check_flapping_recovery_notification().is_set()) + obj.set_check_flapping_recovery_notification( + *state.check_flapping_recovery_notification()); + if (state.has_notifications()) { + for (int i = 0; i < 6; i++) + obj.set_notification(i, state.notifications()[i]); + } + if (state.state_history().is_set()) { + utils::set_state_history(*state.state_history(), obj.get_state_history()); + obj.set_state_history_index(0); + } + } + if (obj.get_retain_nonstatus_information()) { + if (state.acknowledgement_type().is_set()) + obj.set_acknowledgement( + static_cast<AckType>(*state.acknowledgement_type())); + else + obj.set_acknowledgement(engine::AckType::NONE); + + if (state.notifications_enabled().is_set() && + (obj.get_modified_attributes() & MODATTR_NOTIFICATIONS_ENABLED)) + obj.set_notifications_enabled(*state.notifications_enabled()); + + if (state.active_checks_enabled().is_set() && + (obj.get_modified_attributes() & MODATTR_ACTIVE_CHECKS_ENABLED)) + obj.set_checks_enabled(*state.active_checks_enabled()); + + if (state.passive_checks_enabled().is_set() && + (obj.get_modified_attributes() & MODATTR_PASSIVE_CHECKS_ENABLED)) + obj.set_accept_passive_checks(*state.passive_checks_enabled()); + + if (state.event_handler_enabled().is_set() && + (obj.get_modified_attributes() & MODATTR_EVENT_HANDLER_ENABLED)) + obj.set_event_handler_enabled(*state.event_handler_enabled()); + + if (state.flap_detection_enabled().is_set() && + (obj.get_modified_attributes() & MODATTR_FLAP_DETECTION_ENABLED)) + obj.set_flap_detection_enabled(*state.flap_detection_enabled()); + + if (state.process_performance_data().is_set() && + (obj.get_modified_attributes() & MODATTR_PERFORMANCE_DATA_ENABLED)) + obj.set_process_performance_data(*state.process_performance_data()); + + if (state.obsess_over_service().is_set() && + (obj.get_modified_attributes() & MODATTR_OBSESSIVE_HANDLER_ENABLED)) + obj.set_obsess_over(*state.obsess_over_service()); + + if (state.check_command().is_set() && + (obj.get_modified_attributes() & MODATTR_CHECK_COMMAND)) { + if (utils::is_command_exist(*state.check_command())) + obj.set_check_command(*state.check_command()); + else + obj.set_modified_attributes(obj.get_modified_attributes() - + MODATTR_CHECK_COMMAND); + } + + if (state.check_period().is_set() && + (obj.get_modified_attributes() & MODATTR_CHECK_TIMEPERIOD)) { + timeperiod_map::const_iterator it( + timeperiod::timeperiods.find(*state.check_period())); + if (it != timeperiod::timeperiods.end()) + obj.set_check_period(*state.check_period()); + else + obj.set_modified_attributes(obj.get_modified_attributes() - + MODATTR_CHECK_TIMEPERIOD); + } + + if (state.notification_period().is_set() && + (obj.get_modified_attributes() & MODATTR_NOTIFICATION_TIMEPERIOD)) { + timeperiod_map::const_iterator it( + timeperiod::timeperiods.find(*state.notification_period())); + if (it != timeperiod::timeperiods.end()) + obj.set_notification_period(*state.notification_period()); + else + obj.set_modified_attributes(obj.get_modified_attributes() - + MODATTR_NOTIFICATION_TIMEPERIOD); + } + + if (state.event_handler().is_set() && + (obj.get_modified_attributes() & MODATTR_EVENT_HANDLER_COMMAND)) { + if (utils::is_command_exist(*state.event_handler())) + obj.set_event_handler(*state.event_handler()); + else + obj.set_modified_attributes(obj.get_modified_attributes() - + MODATTR_EVENT_HANDLER_COMMAND); + } + + if (state.normal_check_interval().is_set() && + (obj.get_modified_attributes() & MODATTR_NORMAL_CHECK_INTERVAL)) + obj.set_check_interval(*state.normal_check_interval()); + + if (state.retry_check_interval().is_set() && + (obj.get_modified_attributes() & MODATTR_RETRY_CHECK_INTERVAL)) + obj.set_retry_interval(*state.retry_check_interval()); + + if (state.max_attempts().is_set() && + (obj.get_modified_attributes() & MODATTR_MAX_CHECK_ATTEMPTS)) { + obj.set_max_attempts(*state.max_attempts()); + + // adjust current attempt number if in a hard state. + if (obj.get_state_type() == notifier::hard && + obj.get_current_state() != engine::service::state_ok && + obj.get_current_attempt() > 1) + obj.set_current_attempt(obj.max_check_attempts()); + } + + if (!state.customvariables().empty() && + (obj.get_modified_attributes() & MODATTR_CUSTOM_VARIABLE)) { + for (map_customvar::const_iterator it(state.customvariables().begin()), + end(state.customvariables().end()); + it != end; ++it) + obj.custom_variables[it->first].update(it->second.value()); + } + } + // Adjust modified attributes if necessary. + else + obj.set_modified_attributes(MODATTR_NONE); + + bool allow_flapstart_notification(true); + + // Adjust modified attributes if no custom variable has been changed. + if (obj.get_modified_attributes() & MODATTR_CUSTOM_VARIABLE) { + bool at_least_one_modified(false); + for (auto const& cv : obj.custom_variables) + if (cv.second.has_been_modified()) { + at_least_one_modified = true; + break; + } + if (!at_least_one_modified) + obj.set_modified_attributes(obj.get_modified_attributes() - + MODATTR_CUSTOM_VARIABLE); + } + + // calculate next possible notification time. + if (obj.get_current_state() != engine::service::state_ok && + obj.get_last_notification()) + obj.set_next_notification( + obj.get_next_notification_time(obj.get_last_notification())); + + // fix old vars. + if (!obj.has_been_checked() && obj.get_state_type() == notifier::soft) + obj.set_state_type(notifier::hard); + + // ADDED 01/23/2009 adjust current check attempt if service is + // in hard problem state (max attempts may have changed in config + // since restart). + if (obj.get_current_state() != engine::service::state_ok && + obj.get_state_type() == notifier::hard) + obj.set_current_attempt(obj.max_check_attempts()); + + // ADDED 02/20/08 assume same flapping state if large + // install tweaks enabled. + if (config.use_large_installation_tweaks()) + obj.set_is_flapping(state.is_flapping()); + // else use normal startup flap detection logic. + else { + // service was flapping before program started. + // 11/10/07 don't allow flapping notifications to go out. + allow_flapstart_notification = !state.is_flapping(); + + // check for flapping. + obj.check_for_flapping(false, allow_flapstart_notification); + + // service was flapping before and isn't now, so clear + // recovery check variable if service isn't flapping now. + if (state.is_flapping() && !obj.get_is_flapping()) + obj.set_check_flapping_recovery_notification(false); + } + + // handle new vars added in 2.x. + if (obj.get_last_hard_state_change()) + obj.set_last_hard_state_change(obj.get_last_state_change()); + + // update service status. + obj.update_adaptive_data(); + obj.update_status(); +} +#else /** * Update internal service base on service retention. * @@ -62,8 +366,8 @@ void applier::service::apply(configuration::state const& config, * @param[in] scheduling_info_is_ok True if the retention is * not outdated. */ -void applier::service::update(configuration::state const& config, - retention::service const& state, +void applier::service::update(const configuration::State& config, + const retention::service& state, engine::service& obj, bool scheduling_info_is_ok) { if (state.modified_attributes().is_set()) { @@ -330,3 +634,4 @@ void applier::service::update(configuration::state const& config, obj.update_adaptive_data(); obj.update_status(); } +#endif diff --git a/engine/src/retention/applier/state.cc b/engine/src/retention/applier/state.cc index eaf8ef268f5..76a83d5783d 100644 --- a/engine/src/retention/applier/state.cc +++ b/engine/src/retention/applier/state.cc @@ -31,6 +31,7 @@ using namespace com::centreon::engine::retention; +#ifdef LEGACY_CONF /** * Restore retention state. * @@ -83,3 +84,57 @@ void applier::state::apply(configuration::state& config, broker_retention_data(NEBTYPE_RETENTIONDATA_ENDLOAD, NEBFLAG_NONE, NEBATTR_NONE, NULL); } +#else +/** + * Restore retention state. + * + * @param[in, out] config The global configuration to update. + * @param[in] state The retention informations. + */ +void applier::state::apply(configuration::State& config, + const retention::state& state) { + if (!config.retain_state_information()) + return; + + // send data to event broker. + broker_retention_data(NEBTYPE_RETENTIONDATA_STARTLOAD, NEBFLAG_NONE, + NEBATTR_NONE, NULL); + + try { + time_t current_time(time(NULL)); + bool scheduling_info_is_ok(false); + if ((current_time - state.informations().created()) < + static_cast<time_t>(config.retention_scheduling_horizon())) + scheduling_info_is_ok = true; + + applier::program app_program; + app_program.apply(config, state.globals()); + + applier::comment app_comments; + app_comments.apply(state.comments()); + + applier::downtime::apply(state.downtimes()); + + applier::contact app_contacts; + app_contacts.apply(config, state.contacts()); + + applier::host app_hosts; + app_hosts.apply(config, state.hosts(), scheduling_info_is_ok); + + applier::service::apply(config, state.services(), scheduling_info_is_ok); + + applier::anomalydetection::apply(config, state.anomalydetection(), + scheduling_info_is_ok); + + } catch (...) { + // send data to event broker. + broker_retention_data(NEBTYPE_RETENTIONDATA_ENDLOAD, NEBFLAG_NONE, + NEBATTR_NONE, NULL); + throw; + } + + // send data to event broker. + broker_retention_data(NEBTYPE_RETENTIONDATA_ENDLOAD, NEBFLAG_NONE, + NEBATTR_NONE, NULL); +} +#endif diff --git a/engine/src/retention/dump.cc b/engine/src/retention/dump.cc index cc129497c71..0f4da4fade2 100644 --- a/engine/src/retention/dump.cc +++ b/engine/src/retention/dump.cc @@ -127,6 +127,18 @@ std::ostream& dump::comments(std::ostream& os) { */ std::ostream& dump::contact(std::ostream& os, com::centreon::engine::contact const& obj) { +#ifdef LEGACY_CONF + uint32_t retained_contact_host_attribute_mask = + config->retained_contact_host_attribute_mask(); + uint32_t retained_contact_service_attribute_mask = + config->retained_contact_service_attribute_mask(); +#else + uint32_t retained_contact_host_attribute_mask = + pb_config.retained_contact_host_attribute_mask(); + uint32_t retained_contact_service_attribute_mask = + pb_config.retained_contact_service_attribute_mask(); +#endif + os << "contact {\n" "contact_name=" << obj.get_name() @@ -148,11 +160,11 @@ std::ostream& dump::contact(std::ostream& os, << "\n" "modified_host_attributes=" << (obj.get_modified_host_attributes() & - ~config->retained_contact_host_attribute_mask()) + ~retained_contact_host_attribute_mask) << "\n" "modified_service_attributes=" << (obj.get_modified_service_attributes() & - ~config->retained_contact_service_attribute_mask()) + ~retained_contact_service_attribute_mask) << "\n" "service_notification_period=" << obj.get_service_notification_period() @@ -267,6 +279,13 @@ std::ostream& dump::header(std::ostream& os) { */ std::ostream& dump::host(std::ostream& os, com::centreon::engine::host const& obj) { +#ifdef LEGACY_CONF + uint32_t retained_host_attribute_mask = + config->retained_host_attribute_mask(); +#else + uint32_t retained_host_attribute_mask = + pb_config.retained_host_attribute_mask(); +#endif os << "host {\n" "host_name=" << obj.name() @@ -374,8 +393,7 @@ std::ostream& dump::host(std::ostream& os, << obj.max_check_attempts() << "\n" "modified_attributes=" - << (obj.get_modified_attributes() & - ~config->retained_host_attribute_mask()) + << (obj.get_modified_attributes() & ~retained_host_attribute_mask) << "\n" "next_check=" << static_cast<unsigned long>(obj.get_next_check()) @@ -466,6 +484,7 @@ std::ostream& dump::info(std::ostream& os) { return os; } +#ifdef LEGACY_CONF /** * Dump retention of program. * @@ -540,6 +559,82 @@ std::ostream& dump::program(std::ostream& os) { "}\n"; return os; } +#else +/** + * Dump retention of program. + * + * @param[out] os The output stream. + * + * @return The output stream. + */ +std::ostream& dump::program(std::ostream& os) { + os << "program {\n" + "active_host_checks_enabled=" + << pb_config.execute_host_checks() + << "\n" + "active_service_checks_enabled=" + << pb_config.execute_service_checks() + << "\n" + "check_host_freshness=" + << pb_config.check_host_freshness() + << "\n" + "check_service_freshness=" + << pb_config.check_service_freshness() + << "\n" + "enable_event_handlers=" + << pb_config.enable_event_handlers() + << "\n" + "enable_flap_detection=" + << pb_config.enable_flap_detection() + << "\n" + "enable_notifications=" + << pb_config.enable_notifications() + << "\n" + "global_host_event_handler=" + << pb_config.global_host_event_handler().c_str() + << "\n" + "global_service_event_handler=" + << pb_config.global_service_event_handler().c_str() + << "\n" + "modified_host_attributes=" + << (modified_host_process_attributes & + ~pb_config.retained_process_host_attribute_mask()) + << "\n" + "modified_service_attributes=" + << (modified_service_process_attributes & + ~pb_config.retained_process_host_attribute_mask()) + << "\n" + "next_comment_id=" + << comment::get_next_comment_id() + << "\n" + "next_event_id=" + << next_event_id + << "\n" + "next_notification_id=" + << next_notification_id + << "\n" + "next_problem_id=" + << next_problem_id + << "\n" + "obsess_over_hosts=" + << pb_config.obsess_over_hosts() + << "\n" + "obsess_over_services=" + << pb_config.obsess_over_services() + << "\n" + "passive_host_checks_enabled=" + << pb_config.accept_passive_host_checks() + << "\n" + "passive_service_checks_enabled=" + << pb_config.accept_passive_service_checks() + << "\n" + "process_performance_data=" + << pb_config.process_performance_data() + << "\n" + "}\n"; + return os; +} +#endif /** * Save all data. @@ -549,8 +644,13 @@ std::ostream& dump::program(std::ostream& os) { * @return True on success, otherwise false. */ bool dump::save(std::string const& path) { +#ifdef LEGACY_CONF if (!config->retain_state_information()) return true; +#else + if (!pb_config.retain_state_information()) + return true; +#endif // send data to event broker broker_retention_data(NEBTYPE_RETENTIONDATA_STARTSAVE, NEBFLAG_NONE, @@ -560,8 +660,13 @@ bool dump::save(std::string const& path) { try { std::ofstream stream(path.c_str(), std::ios::binary | std::ios::trunc); if (!stream.is_open()) - throw(engine_error() << "Cannot open retention file '" - << config->state_retention_file() << "'"); +#ifdef LEGACY_CONF + throw engine_error() << "Cannot open retention file '" + << config->state_retention_file() << "'"; +#else + throw engine_error() << "Cannot open retention file '" + << pb_config.state_retention_file() << "'"; +#endif dump::header(stream); dump::info(stream); dump::program(stream); @@ -719,8 +824,13 @@ std::ostream& dump::service(std::ostream& os, << obj.max_check_attempts() << "\n" "modified_attributes=" +#ifdef LEGACY_CONF << (obj.get_modified_attributes() & ~config->retained_host_attribute_mask()) +#else + << (obj.get_modified_attributes() & + ~pb_config.retained_host_attribute_mask()) +#endif << "\n" "next_check=" << static_cast<unsigned long>(obj.get_next_check()) diff --git a/engine/src/sehandlers.cc b/engine/src/sehandlers.cc index 18872b09f17..a3a9ea5a80c 100644 --- a/engine/src/sehandlers.cc +++ b/engine/src/sehandlers.cc @@ -50,6 +50,18 @@ int obsessive_compulsive_host_check_processor( int macro_options = STRIP_ILLEGAL_MACRO_CHARS | ESCAPE_MACRO_CHARS; nagios_macros* mac(get_global_macros()); + bool obsess_over_hosts; + uint32_t ochp_timeout; +#ifdef LEGACY_CONF + obsess_over_hosts = config->obsess_over_hosts(); + const std::string& ochp_command = config->ochp_command(); + ochp_timeout = config->ochp_timeout(); +#else + obsess_over_hosts = pb_config.obsess_over_hosts(); + const std::string& ochp_command = pb_config.ochp_command(); + ochp_timeout = pb_config.ochp_timeout(); +#endif + engine_logger(dbg_functions, basic) << "obsessive_compulsive_host_check_processor()"; functions_logger->trace("obsessive_compulsive_host_check_processor()"); @@ -58,20 +70,20 @@ int obsessive_compulsive_host_check_processor( return ERROR; /* bail out if we shouldn't be obsessing */ - if (!config->obsess_over_hosts()) + if (!obsess_over_hosts) return OK; if (!hst->obsess_over()) return OK; /* if there is no valid command, exit */ - if (config->ochp_command().empty()) + if (ochp_command.empty()) return ERROR; /* update macros */ grab_host_macros_r(mac, hst); /* get the raw command line */ - get_raw_command_line_r(mac, ochp_command_ptr, config->ochp_command().c_str(), + get_raw_command_line_r(mac, ochp_command_ptr, ochp_command.c_str(), raw_command, macro_options); if (raw_command.empty()) { clear_volatile_macros_r(mac); @@ -105,7 +117,7 @@ int obsessive_compulsive_host_check_processor( /* run the command */ try { std::string tmp; - my_system_r(mac, processed_command, config->ochp_timeout(), + my_system_r(mac, processed_command, ochp_timeout, &early_timeout, &exectime, tmp, 0); } catch (std::exception const& e) { engine_logger(log_runtime_error, basic) @@ -128,11 +140,11 @@ int obsessive_compulsive_host_check_processor( if (early_timeout) engine_logger(log_runtime_warning, basic) << "Warning: OCHP command '" << processed_command << "' for host '" - << hst->name() << "' timed out after " << config->ochp_timeout() + << hst->name() << "' timed out after " << ochp_timeout << " seconds"; runtime_logger->warn( "Warning: OCHP command '{}' for host '{}' timed out after {} seconds", - processed_command, hst->name(), config->ochp_timeout()); + processed_command, hst->name(), ochp_timeout); return OK; } @@ -156,15 +168,30 @@ int run_global_service_event_handler(nagios_macros* mac, engine_logger(dbg_functions, basic) << "run_global_service_event_handler()"; functions_logger->trace("run_global_service_event_handler()"); + bool enable_event_handlers; + bool log_event_handlers; + uint32_t event_handler_timeout; +#ifdef LEGACY_CONF + enable_event_handlers = config->enable_event_handlers(); + const std::string& global_service_event_handler = config->global_service_event_handler(); + log_event_handlers = config->log_event_handlers(); + event_handler_timeout = config->event_handler_timeout(); +#else + enable_event_handlers = pb_config.enable_event_handlers(); + const std::string& global_service_event_handler = pb_config.global_service_event_handler(); + log_event_handlers = pb_config.log_event_handlers(); + event_handler_timeout = pb_config.event_handler_timeout(); +#endif + if (svc == nullptr) return ERROR; /* bail out if we shouldn't be running event handlers */ - if (config->enable_event_handlers() == false) + if (!enable_event_handlers) return OK; /* a global service event handler command has not been defined */ - if (config->global_service_event_handler().empty()) + if (global_service_event_handler.empty()) return ERROR; engine_logger(dbg_eventhandlers, more) @@ -179,11 +206,10 @@ int run_global_service_event_handler(nagios_macros* mac, /* get the raw command line */ get_raw_command_line_r(mac, global_service_event_handler_ptr, - config->global_service_event_handler().c_str(), + global_service_event_handler.c_str(), raw_command, macro_options); - if (raw_command.empty()) { + if (raw_command.empty()) return ERROR; - } engine_logger(dbg_eventhandlers, most) << "Raw global service event handler command line: " << raw_command; @@ -203,12 +229,12 @@ int run_global_service_event_handler(nagios_macros* mac, "Processed global service event handler command line: {}", processed_command); - if (config->log_event_handlers()) { + if (log_event_handlers) { std::ostringstream oss; oss << "GLOBAL SERVICE EVENT HANDLER: " << svc->get_hostname() << ';' << svc->description() << ";$SERVICESTATE$;$SERVICESTATETYPE$;$SERVICEATTEMPT$;" - << config->global_service_event_handler(); + << global_service_event_handler; process_macros_r(mac, oss.str(), processed_logentry, macro_options); engine_logger(log_event_handler, basic) << processed_logentry; events_logger->debug(processed_logentry); @@ -220,7 +246,7 @@ int run_global_service_event_handler(nagios_macros* mac, cached_cmd)) { /* run the command */ try { - my_system_r(mac, processed_command, config->event_handler_timeout(), + my_system_r(mac, processed_command, event_handler_timeout, &early_timeout, &exectime, command_output, 0); } catch (std::exception const& e) { engine_logger(log_runtime_error, basic) @@ -244,11 +270,11 @@ int run_global_service_event_handler(nagios_macros* mac, engine_logger(log_event_handler | log_runtime_warning, basic) << "Warning: Global service event handler command '" << processed_command << "' timed out after " - << config->event_handler_timeout() << " seconds"; + << event_handler_timeout << " seconds"; events_logger->info( "Warning: Global service event handler command '{}' timed out after {} " "seconds", - processed_command, config->event_handler_timeout()); + processed_command, event_handler_timeout); } return OK; } @@ -265,6 +291,16 @@ int run_service_event_handler(nagios_macros* mac, struct timeval start_time; int macro_options = STRIP_ILLEGAL_MACRO_CHARS | ESCAPE_MACRO_CHARS; + bool log_event_handlers; + uint32_t event_handler_timeout; +#ifdef LEGACY_CONF + log_event_handlers = config->log_event_handlers(); + event_handler_timeout = config->event_handler_timeout(); +#else + log_event_handlers = pb_config.log_event_handlers(); + event_handler_timeout = pb_config.event_handler_timeout(); +#endif + engine_logger(dbg_functions, basic) << "run_service_event_handler()"; functions_logger->trace("run_service_event_handler()"); @@ -306,7 +342,7 @@ int run_service_event_handler(nagios_macros* mac, events_logger->debug("Processed service event handler command line: {}", processed_command); - if (config->log_event_handlers()) { + if (log_event_handlers) { std::ostringstream oss; oss << "SERVICE EVENT HANDLER: " << svc->get_hostname() << ';' << svc->description() @@ -321,7 +357,7 @@ int run_service_event_handler(nagios_macros* mac, checkable::EVH_TYPE)) { /* run the command */ try { - my_system_r(mac, processed_command, config->event_handler_timeout(), + my_system_r(mac, processed_command, event_handler_timeout, &early_timeout, &exectime, command_output, 0); } catch (std::exception const& e) { engine_logger(log_runtime_error, basic) @@ -342,12 +378,12 @@ int run_service_event_handler(nagios_macros* mac, if (early_timeout) { engine_logger(log_event_handler | log_runtime_warning, basic) << "Warning: Service event handler command '" << processed_command - << "' timed out after " << config->event_handler_timeout() + << "' timed out after " << event_handler_timeout << " seconds"; events_logger->info( "Warning: Service event handler command '{}' timed out after {} " "seconds", - processed_command, config->event_handler_timeout()); + processed_command, event_handler_timeout); } return OK; } @@ -366,6 +402,16 @@ int handle_host_event(com::centreon::engine::host* hst) { if (hst == nullptr) return ERROR; + bool enable_event_handlers; + std::string_view global_host_event_handler; +#ifdef LEGACY_CONF + enable_event_handlers = config->enable_event_handlers(); + global_host_event_handler = config->global_host_event_handler(); +#else + enable_event_handlers = pb_config.enable_event_handlers(); + global_host_event_handler = pb_config.global_host_event_handler(); +#endif + /* send event data to broker */ broker_statechange_data( NEBTYPE_STATECHANGE_END, NEBFLAG_NONE, NEBATTR_NONE, HOST_STATECHANGE, @@ -373,7 +419,7 @@ int handle_host_event(com::centreon::engine::host* hst) { hst->get_current_attempt(), hst->max_check_attempts(), nullptr); /* bail out if we shouldn't be running event handlers */ - if (!config->enable_event_handlers()) + if (!enable_event_handlers) return OK; if (!hst->event_handler_enabled()) return OK; @@ -410,15 +456,30 @@ int run_global_host_event_handler(nagios_macros* mac, engine_logger(dbg_functions, basic) << "run_global_host_event_handler()"; functions_logger->trace("run_global_host_event_handler()"); + bool enable_event_handlers; + bool log_event_handlers; + uint32_t event_handler_timeout; +#ifdef LEGACY_CONF + enable_event_handlers = config->enable_event_handlers(); + const std::string& global_host_event_handler = config->global_host_event_handler(); + log_event_handlers = config->log_event_handlers(); + event_handler_timeout = config->event_handler_timeout(); +#else + enable_event_handlers = pb_config.enable_event_handlers(); + const std::string& global_host_event_handler = pb_config.global_host_event_handler(); + log_event_handlers = pb_config.log_event_handlers(); + event_handler_timeout = pb_config.event_handler_timeout(); +#endif + if (hst == nullptr) return ERROR; /* bail out if we shouldn't be running event handlers */ - if (config->enable_event_handlers() == false) + if (!enable_event_handlers) return OK; /* no global host event handler command is defined */ - if (config->global_host_event_handler() == "") + if (global_host_event_handler.empty()) return ERROR; engine_logger(dbg_eventhandlers, more) @@ -431,7 +492,7 @@ int run_global_host_event_handler(nagios_macros* mac, /* get the raw command line */ get_raw_command_line_r(mac, global_host_event_handler_ptr, - config->global_host_event_handler().c_str(), + global_host_event_handler.c_str(), raw_command, macro_options); if (raw_command.empty()) return ERROR; @@ -453,11 +514,11 @@ int run_global_host_event_handler(nagios_macros* mac, events_logger->debug("Processed global host event handler command line: {}", processed_command); - if (config->log_event_handlers() == true) { + if (log_event_handlers) { std::ostringstream oss; oss << "GLOBAL HOST EVENT HANDLER: " << hst->name() << "$HOSTSTATE$;$HOSTSTATETYPE$;$HOSTATTEMPT$;" - << config->global_host_event_handler(); + << global_host_event_handler; process_macros_r(mac, oss.str(), processed_logentry, macro_options); engine_logger(log_event_handler, basic) << processed_logentry; events_logger->info(processed_logentry); @@ -468,7 +529,7 @@ int run_global_host_event_handler(nagios_macros* mac, if (host::command_is_allowed_by_whitelist(processed_command, cached_cmd)) { /* run the command */ try { - my_system_r(mac, processed_command, config->event_handler_timeout(), + my_system_r(mac, processed_command, event_handler_timeout, &early_timeout, &exectime, command_output, 0); } catch (std::exception const& e) { engine_logger(log_runtime_error, basic) @@ -490,12 +551,12 @@ int run_global_host_event_handler(nagios_macros* mac, if (early_timeout) { engine_logger(log_event_handler | log_runtime_warning, basic) << "Warning: Global host event handler command '" << processed_command - << "' timed out after " << config->event_handler_timeout() + << "' timed out after " << event_handler_timeout << " seconds"; events_logger->info( "Warning: Global host event handler command '{}' timed out after {} " "seconds", - processed_command, config->event_handler_timeout()); + processed_command, event_handler_timeout); } return OK; @@ -513,6 +574,16 @@ int run_host_event_handler(nagios_macros* mac, struct timeval start_time; int macro_options = STRIP_ILLEGAL_MACRO_CHARS | ESCAPE_MACRO_CHARS; + bool log_event_handlers; + uint32_t event_handler_timeout; +#ifdef LEGACY_CONF + log_event_handlers = config->log_event_handlers(); + event_handler_timeout = config->event_handler_timeout(); +#else + log_event_handlers = pb_config.log_event_handlers(); + event_handler_timeout = pb_config.event_handler_timeout(); +#endif + engine_logger(dbg_functions, basic) << "run_host_event_handler()"; functions_logger->trace("run_host_event_handler()"); @@ -551,7 +622,7 @@ int run_host_event_handler(nagios_macros* mac, events_logger->debug("Processed host event handler command line: {}", processed_command); - if (config->log_event_handlers() == true) { + if (log_event_handlers) { std::ostringstream oss; oss << "HOST EVENT HANDLER: " << hst->name() << ";$HOSTSTATE$;$HOSTSTATETYPE$;$HOSTATTEMPT$;" @@ -565,7 +636,7 @@ int run_host_event_handler(nagios_macros* mac, checkable::EVH_TYPE)) { /* run the command */ try { - my_system_r(mac, processed_command, config->event_handler_timeout(), + my_system_r(mac, processed_command, event_handler_timeout, &early_timeout, &exectime, command_output, 0); } catch (std::exception const& e) { engine_logger(log_runtime_error, basic) @@ -586,15 +657,11 @@ int run_host_event_handler(nagios_macros* mac, if (early_timeout) { engine_logger(log_event_handler | log_runtime_warning, basic) << "Warning: Host event handler command '" << processed_command - << "' timed out after " << config->event_handler_timeout() + << "' timed out after " << event_handler_timeout << " seconds"; events_logger->info( "Warning: Host event handler command '{}' timed out after {} seconds", - processed_command, config->event_handler_timeout()); + processed_command, event_handler_timeout); } return OK; } - -/******************************************************************/ -/****************** HOST STATE HANDLER FUNCTIONS ******************/ -/******************************************************************/ diff --git a/engine/src/service.cc b/engine/src/service.cc index abe5f65711a..ed514b431e2 100644 --- a/engine/src/service.cc +++ b/engine/src/service.cc @@ -1090,8 +1090,29 @@ int service::handle_async_check_result( com::centreon::engine::service* master_service = nullptr; int run_async_check = true; int flapping_check_done = false; +#ifdef LEGACY_CONF + uint32_t interval_length = config->interval_length(); + bool accept_passive_service_checks = config->accept_passive_service_checks(); + bool log_passive_checks = config->log_passive_checks(); + uint32_t cached_host_check_horizon = config->cached_host_check_horizon(); + bool obsess_over_services = config->obsess_over_services(); + bool enable_predictive_service_dependency_checks = + config->enable_predictive_service_dependency_checks(); + uint32_t cached_service_check_horizon = + config->cached_service_check_horizon(); +#else + uint32_t interval_length = pb_config.interval_length(); + bool accept_passive_service_checks = + pb_config.accept_passive_service_checks(); + bool log_passive_checks = pb_config.log_passive_checks(); + uint32_t cached_host_check_horizon = pb_config.cached_host_check_horizon(); + bool obsess_over_services = pb_config.obsess_over_services(); + bool enable_predictive_service_dependency_checks = + pb_config.enable_predictive_service_dependency_checks(); + uint32_t cached_service_check_horizon = + pb_config.cached_service_check_horizon(); +#endif - engine_logger(dbg_functions, basic) << "handle_async_service_check_result()"; SPDLOG_LOGGER_TRACE(functions_logger, "handle_async_service_check_result()"); /* get the current time */ @@ -1146,7 +1167,7 @@ int service::handle_async_check_result( * skip this service check results if its passive and we aren't accepting * passive check results */ if (queued_check_result.get_check_type() == check_passive) { - if (!config->accept_passive_service_checks()) { + if (!accept_passive_service_checks) { engine_logger(dbg_checks, basic) << "Discarding passive service check result because passive " "service checks are disabled globally."; @@ -1365,7 +1386,7 @@ int service::handle_async_check_result( * commands by getting dropped in checkresults dir */ if (get_check_type() == check_passive) { - if (config->log_passive_checks()) + if (log_passive_checks) engine_logger(log_passive_check, basic) << "PASSIVE SERVICE CHECK: " << _hostname << ";" << name() << ";" << _current_state << ";" << get_plugin_output(); @@ -1561,7 +1582,7 @@ int service::handle_async_check_result( if ((!state_change || state_changes_use_cached_state) && hst->has_been_checked() && (static_cast<unsigned long>(current_time - hst->get_last_check()) <= - config->cached_host_check_horizon())) { + cached_host_check_horizon)) { engine_logger(dbg_checks, more) << "* Using cached host state: " << hst->get_current_state(); SPDLOG_LOGGER_DEBUG(checks_logger, "* Using cached host state: {}", @@ -1635,7 +1656,7 @@ int service::handle_async_check_result( notify(reason_recovery, "", "", notification_option_none); /* should we obsessive over service checks? */ - if (config->obsess_over_services()) + if (obsess_over_services) obsessive_compulsive_service_check_processor(); /* reset all service variables because its okay now... */ @@ -1649,8 +1670,7 @@ int service::handle_async_check_result( if (reschedule_check) next_service_check = - (time_t)(get_last_check() + - check_interval() * config->interval_length()); + (time_t)(get_last_check() + check_interval() * interval_length); } /*******************************************/ @@ -1676,8 +1696,8 @@ int service::handle_async_check_result( /* only use cached host state if no service state change has occurred */ if ((!state_change || state_changes_use_cached_state) && hst->has_been_checked() && - (static_cast<unsigned long>(current_time - hst->get_last_check()) <= - config->cached_host_check_horizon())) { + static_cast<unsigned long>(current_time - hst->get_last_check()) <= + cached_host_check_horizon) { /* use current host state as route result */ route_result = hst->get_current_state(); engine_logger(dbg_checks, more) @@ -1834,8 +1854,7 @@ int service::handle_async_check_result( * interval */ if (reschedule_check) next_service_check = - (time_t)(get_last_check() + - check_interval() * config->interval_length()); + (time_t)(get_last_check() + check_interval() * interval_length); /* log the problem as a hard state if the host just went down */ if (hard_state_change) { @@ -1866,12 +1885,11 @@ int service::handle_async_check_result( if (reschedule_check) next_service_check = - (time_t)(get_last_check() + - retry_interval() * config->interval_length()); + (time_t)(get_last_check() + retry_interval() * interval_length); } /* perform dependency checks on the second to last check of the service */ - if (config->enable_predictive_service_dependency_checks() && + if (enable_predictive_service_dependency_checks && get_current_attempt() == max_check_attempts() - 1) { engine_logger(dbg_checks, more) << "Looking for services to check for predictive " @@ -1967,12 +1985,11 @@ int service::handle_async_check_result( /* reschedule the next check at the regular interval */ if (reschedule_check) next_service_check = - (time_t)(get_last_check() + - check_interval() * config->interval_length()); + (time_t)(get_last_check() + check_interval() * interval_length); } /* should we obsessive over service checks? */ - if (config->obsess_over_services()) + if (obsess_over_services) obsessive_compulsive_service_check_processor(); } @@ -2071,7 +2088,7 @@ int service::handle_async_check_result( /* we can get by with a cached state, so don't check the service */ if (static_cast<unsigned long>(current_time - svc->get_last_check()) <= - config->cached_service_check_horizon()) { + cached_service_check_horizon) { run_async_check = false; /* update check statistics */ @@ -2097,7 +2114,12 @@ int service::handle_async_check_result( * @return Return true on success. */ int service::log_event() { - if (get_state_type() == soft && !config->log_service_retries()) +#ifdef LEGACY_CONF + bool log_service_retries = config->log_service_retries(); +#else + bool log_service_retries = pb_config.log_service_retries(); +#endif + if (get_state_type() == soft && !log_service_retries) return OK; uint32_t log_options{NSLOG_SERVICE_UNKNOWN}; @@ -2135,6 +2157,20 @@ void service::check_for_flapping(bool update, double low_curve_value = 0.75; double high_curve_value = 1.25; + float low_service_flap_threshold; + float high_service_flap_threshold; + bool enable_flap_detection; + +#ifdef LEGACY_CONF + low_service_flap_threshold = config->low_service_flap_threshold(); + high_service_flap_threshold = config->high_service_flap_threshold(); + enable_flap_detection = config->enable_flap_detection(); +#else + low_service_flap_threshold = pb_config.low_service_flap_threshold(); + high_service_flap_threshold = pb_config.high_service_flap_threshold(); + enable_flap_detection = pb_config.enable_flap_detection(); +#endif + /* large install tweaks skips all flap detection logic - including state * change calculation */ @@ -2149,11 +2185,10 @@ void service::check_for_flapping(bool update, name(), _hostname); /* what threshold values should we use (global or service-specific)? */ - low_threshold = (get_low_flap_threshold() <= 0.0) - ? config->low_service_flap_threshold() - : get_low_flap_threshold(); + low_threshold = (get_low_flap_threshold() <= 0.0) ? low_service_flap_threshold + : get_low_flap_threshold(); high_threshold = (get_high_flap_threshold() <= 0.0) - ? config->high_service_flap_threshold() + ? high_service_flap_threshold : get_high_flap_threshold(); update_history = update; @@ -2223,7 +2258,7 @@ void service::check_for_flapping(bool update, /* don't do anything if we don't have flap detection enabled on a program-wide * basis */ - if (!config->enable_flap_detection()) + if (!enable_flap_detection) return; /* don't do anything if we don't have flap detection enabled for this service @@ -2278,7 +2313,12 @@ int service::handle_service_event() { max_check_attempts(), nullptr); /* bail out if we shouldn't be running event handlers */ - if (!config->enable_event_handlers()) +#ifdef LEGACY_CONF + bool enable_event_handlers = config->enable_event_handlers(); +#else + bool enable_event_handlers = pb_config.enable_event_handlers(); +#endif + if (!enable_event_handlers) return OK; if (!event_handler_enabled()) return OK; @@ -2316,19 +2356,31 @@ int service::obsessive_compulsive_service_check_processor() { int macro_options = STRIP_ILLEGAL_MACRO_CHARS | ESCAPE_MACRO_CHARS; nagios_macros* mac(get_global_macros()); + bool obsess_over_services; + uint32_t ocsp_timeout; +#ifdef LEGACY_CONF + obsess_over_services = config->obsess_over_services(); + const std::string& ocsp_command = config->ocsp_command(); + ocsp_timeout = config->ocsp_timeout(); +#else + obsess_over_services = pb_config.obsess_over_services(); + const std::string& ocsp_command = pb_config.ocsp_command(); + ocsp_timeout = pb_config.ocsp_timeout(); +#endif + engine_logger(dbg_functions, basic) << "obsessive_compulsive_service_check_processor()"; SPDLOG_LOGGER_TRACE(functions_logger, "obsessive_compulsive_service_check_processor()"); /* bail out if we shouldn't be obsessing */ - if (config->obsess_over_services() == false) + if (!obsess_over_services) return OK; if (!obsess_over()) return OK; /* if there is no valid command, exit */ - if (config->ocsp_command().empty()) + if (ocsp_command.empty()) return ERROR; /* find the associated host */ @@ -2340,7 +2392,7 @@ int service::obsessive_compulsive_service_check_processor() { grab_service_macros_r(mac, this); /* get the raw command line */ - get_raw_command_line_r(mac, ocsp_command_ptr, config->ocsp_command().c_str(), + get_raw_command_line_r(mac, ocsp_command_ptr, ocsp_command.c_str(), raw_command, macro_options); if (raw_command.empty()) { clear_volatile_macros_r(mac); @@ -2375,8 +2427,8 @@ int service::obsessive_compulsive_service_check_processor() { /* run the command */ try { std::string tmp; - my_system_r(mac, processed_command, config->ocsp_timeout(), - &early_timeout, &exectime, tmp, 0); + my_system_r(mac, processed_command, ocsp_timeout, &early_timeout, + &exectime, tmp, 0); } catch (std::exception const& e) { engine_logger(log_runtime_error, basic) << "Error: can't execute compulsive service processor command line '" @@ -2401,12 +2453,12 @@ int service::obsessive_compulsive_service_check_processor() { engine_logger(log_runtime_warning, basic) << "Warning: OCSP command '" << processed_command << "' for service '" << name() << "' on host '" << _hostname << "' timed out after " - << config->ocsp_timeout() << " seconds"; + << ocsp_timeout << " seconds"; SPDLOG_LOGGER_WARN( runtime_logger, "Warning: OCSP command '{}' for service '{}' on host '{}' timed out " "after {} seconds", - processed_command, name(), _hostname, config->ocsp_timeout()); + processed_command, name(), _hostname, ocsp_timeout); return OK; } @@ -2414,7 +2466,12 @@ int service::obsessive_compulsive_service_check_processor() { /* updates service performance data */ int service::update_service_performance_data() { /* should we be processing performance data for anything? */ - if (!config->process_performance_data()) +#ifdef LEGACY_CONF + bool process_pd = config->process_performance_data(); +#else + bool process_pd = pb_config.process_performance_data(); +#endif + if (!process_pd) return OK; /* should we process performance data for this service? */ @@ -2465,13 +2522,18 @@ int service::run_scheduled_check(int check_options, double latency) { * if service has no check interval, schedule it again for 5 * minutes from now * */ - if (current_time >= preferred_time) + if (current_time >= preferred_time) { +#ifdef LEGACY_CONF + uint32_t interval_length = config->interval_length(); +#else + uint32_t interval_length = pb_config.interval_length(); +#endif preferred_time = current_time + static_cast<time_t>(check_interval() <= 0 ? 300 - : check_interval() * - config->interval_length()); + : check_interval() * interval_length); + } // Make sure we rescheduled the next service check at a valid time. { @@ -2613,9 +2675,15 @@ int service::run_async_check_local(int check_options, // Service check was cancelled by NEB module. reschedule check later. if (NEBERROR_CALLBACKCANCEL == res) { - if (preferred_time != nullptr) + if (preferred_time != nullptr) { +#ifdef LEGACY_CONF + uint32_t interval_length = config->interval_length(); +#else + uint32_t interval_length = pb_config.interval_length(); +#endif *preferred_time += - static_cast<time_t>(check_interval() * config->interval_length()); + static_cast<time_t>(check_interval() * interval_length); + } engine_logger(log_runtime_error, basic) << "Error: Some broker module cancelled check of service '" << description() << "' on host '" << get_hostname(); @@ -2708,8 +2776,15 @@ int service::run_async_check_local(int check_options, checks::checker::instance().add_check_result_to_reap(check_result_info); }; +#ifdef LEGACY_CONF + bool use_host_down_disable_service_checks = + config->use_host_down_disable_service_checks(); +#else + bool use_host_down_disable_service_checks = + pb_config.host_down_disable_service_checks(); +#endif bool has_to_execute_check = true; - if (config->use_host_down_disable_service_checks()) { + if (use_host_down_disable_service_checks) { auto hst = host::hosts_by_id.find(_host_id); if (hst != host::hosts_by_id.end() && hst->second->get_current_state() != host::state_up) { @@ -2741,9 +2816,14 @@ int service::run_async_check_local(int check_options, retry = false; try { // Run command. - uint64_t id = get_check_command_ptr()->run( - processed_cmd, *macros, config->service_check_timeout(), - check_result_info, this); +#ifdef LEGACY_CONF + uint32_t service_check_timeout = config->service_check_timeout(); +#else + uint32_t service_check_timeout = pb_config.service_check_timeout(); +#endif + uint64_t id = get_check_command_ptr()->run(processed_cmd, *macros, + service_check_timeout, + check_result_info, this); SPDLOG_LOGGER_DEBUG(checks_logger, "run id={} {} for service {} host {}", id, processed_cmd, _service_id, _hostname); @@ -3124,12 +3204,15 @@ bool service::verify_check_viability(int check_options, SPDLOG_LOGGER_TRACE(functions_logger, "check_service_check_viability()"); /* get the check interval to use if we need to reschedule the check */ +#ifdef LEGACY_CONF + uint32_t interval_length = config->interval_length(); +#else + uint32_t interval_length = pb_config.interval_length(); +#endif if (get_state_type() == soft && _current_state != service::state_ok) - check_interval = - static_cast<int>(retry_interval() * config->interval_length()); + check_interval = static_cast<int>(retry_interval() * interval_length); else - check_interval = - static_cast<int>(this->check_interval() * config->interval_length()); + check_interval = static_cast<int>(this->check_interval() * interval_length); /* get the current time */ time(¤t_time); @@ -3277,7 +3360,12 @@ int service::notify_contact(nagios_macros* mac, processed_command); /* log the notification to program log file */ - if (config->log_notifications()) { +#ifdef LEGACY_CONF + bool log_notifications = config->log_notifications(); +#else + bool log_notifications = pb_config.log_notifications(); +#endif + if (log_notifications) { char const* service_state_str("UNKNOWN"); if ((unsigned int)_current_state < tab_service_states.size()) service_state_str = tab_service_states[_current_state].second.c_str(); @@ -3314,9 +3402,14 @@ int service::notify_contact(nagios_macros* mac, /* run the notification command */ if (command_is_allowed_by_whitelist(processed_command, NOTIF_TYPE)) { +#ifdef LEGACY_CONF + uint32_t notification_timeout = config->notification_timeout(); +#else + uint32_t notification_timeout = pb_config.notification_timeout(); +#endif try { std::string tmp; - my_system_r(mac, processed_command, config->notification_timeout(), + my_system_r(mac, processed_command, notification_timeout, &early_timeout, &exectime, tmp, 0); } catch (std::exception const& e) { engine_logger(log_runtime_error, basic) @@ -3339,12 +3432,11 @@ int service::notify_contact(nagios_macros* mac, engine_logger(log_service_notification | log_runtime_warning, basic) << "Warning: Contact '" << cntct->get_name() << "' service notification command '" << processed_command - << "' timed out after " << config->notification_timeout() - << " seconds"; + << "' timed out after " << notification_timeout << " seconds"; notifications_logger->info( "Warning: Contact '{}' service notification command '{}' timed out " "after {} seconds", - cntct->get_name(), processed_command, config->notification_timeout()); + cntct->get_name(), processed_command, notification_timeout); } /* get end time */ @@ -3470,17 +3562,30 @@ bool service::is_result_fresh(time_t current_time, int log_this) { "Checking freshness of service '{}' on host '{}'...", this->description(), this->get_hostname()); + uint32_t interval_length; + int32_t additional_freshness_latency; + uint32_t max_service_check_spread; +#ifdef LEGACY_CONF + interval_length = config->interval_length(); + additional_freshness_latency = config->additional_freshness_latency(); + max_service_check_spread = config->max_service_check_spread(); +#else + interval_length = pb_config.interval_length(); + additional_freshness_latency = pb_config.additional_freshness_latency(); + max_service_check_spread = pb_config.max_service_check_spread(); +#endif + /* use user-supplied freshness threshold or auto-calculate a freshness * threshold to use? */ if (get_freshness_threshold() == 0) { if (get_state_type() == hard || this->_current_state == service::state_ok) - freshness_threshold = static_cast<int>( - check_interval() * config->interval_length() + get_latency() + - config->additional_freshness_latency()); + freshness_threshold = + static_cast<int>(check_interval() * interval_length + get_latency() + + additional_freshness_latency); else - freshness_threshold = static_cast<int>( - this->retry_interval() * config->interval_length() + get_latency() + - config->additional_freshness_latency()); + freshness_threshold = + static_cast<int>(this->retry_interval() * interval_length + + get_latency() + additional_freshness_latency); } else freshness_threshold = this->get_freshness_threshold(); @@ -3509,8 +3614,7 @@ bool service::is_result_fresh(time_t current_time, int log_this) { else if (this->active_checks_enabled() && event_start > get_last_check() && this->get_freshness_threshold() == 0) expiration_time = (time_t)(event_start + freshness_threshold + - (config->max_service_check_spread() * - config->interval_length())); + max_service_check_spread * interval_length); else expiration_time = (time_t)(get_last_check() + freshness_threshold); @@ -3667,11 +3771,16 @@ bool service::authorized_by_dependencies( !check_time_against_period(current_time, dep->dependency_period_ptr)) return true; - /* Get the status to use (use last hard state if it's currently in a soft - * state) */ + /* Get the status to use (use last hard state if it's currently in a soft + * state) */ +#ifdef LEGACY_CONF + bool soft_state_dependencies = config->soft_state_dependencies(); +#else + bool soft_state_dependencies = pb_config.soft_state_dependencies(); +#endif service_state state = (dep->master_service_ptr->get_state_type() == notifier::soft && - !config->soft_state_dependencies()) + !soft_state_dependencies) ? dep->master_service_ptr->get_last_hard_state() : dep->master_service_ptr->get_current_state(); @@ -3705,6 +3814,15 @@ void service::check_for_orphaned() { /* get the current time */ time(¤t_time); + uint32_t service_check_timeout; + uint32_t check_reaper_interval; +#ifdef LEGACY_CONF + service_check_timeout = config->service_check_timeout(); + check_reaper_interval = config->check_reaper_interval(); +#else + service_check_timeout = pb_config.service_check_timeout(); + check_reaper_interval = pb_config.check_reaper_interval(); +#endif /* check all services... */ for (service_map::iterator it(service::services.begin()), end(service::services.end()); @@ -3717,8 +3835,7 @@ void service::check_for_orphaned() { * 10 minutes slack time) */ expected_time = (time_t)(it->second->get_next_check() + it->second->get_latency() + - config->service_check_timeout() + - config->check_reaper_interval() + 600); + service_check_timeout + check_reaper_interval + 600); /* this service was supposed to have executed a while ago, but for some * reason the results haven't come back in... */ @@ -3772,7 +3889,13 @@ void service::check_result_freshness() { "Checking the freshness of service check results..."); /* bail out if we're not supposed to be checking freshness */ - if (!config->check_service_freshness()) { + +#ifdef LEGACY_CONF + bool check_service_freshness = config->check_service_freshness(); +#else + bool check_service_freshness = pb_config.check_service_freshness(); +#endif + if (!check_service_freshness) { engine_logger(dbg_checks, more) << "Service freshness checking is disabled."; SPDLOG_LOGGER_DEBUG(checks_logger, @@ -3837,8 +3960,13 @@ const std::string& service::get_current_state_as_string() const { } bool service::get_notify_on_current_state() const { +#ifdef LEGACY_CONF + bool soft_state_dependencies = config->soft_state_dependencies(); +#else + bool soft_state_dependencies = pb_config.soft_state_dependencies(); +#endif if (_host_ptr->get_current_state() != host::state_up && - (_host_ptr->get_state_type() || config->soft_state_dependencies())) + (_host_ptr->get_state_type() || soft_state_dependencies)) return false; notification_flag type[]{ok, warning, critical, unknown}; return get_notify_on(type[get_current_state()]); diff --git a/engine/src/servicedependency.cc b/engine/src/servicedependency.cc index 07de74d3558..ad1e657b427 100644 --- a/engine/src/servicedependency.cc +++ b/engine/src/servicedependency.cc @@ -378,6 +378,7 @@ void servicedependency::resolve(uint32_t& w [[maybe_unused]], uint32_t& e) { } } +#ifdef LEGACY_CONF /** * Find a service dependency from its key. * @@ -400,3 +401,26 @@ servicedependency_mmap::iterator servicedependency::servicedependencies_find( } return p.first == p.second ? servicedependencies.end() : p.first; } +#else +/** + * @brief Find a service dependency from the given key. + * + * @param key A tuple containing a host name, a service description and a hash + * matching the service dependency. + * + * @return Iterator to the element if found, servicedependencies().end() + * otherwise. + */ +servicedependency_mmap::iterator servicedependency::servicedependencies_find( + const std::tuple<std::string, std::string, size_t>& key) { + size_t k = std::get<2>(key); + std::pair<servicedependency_mmap::iterator, servicedependency_mmap::iterator> + p = servicedependencies.equal_range({std::get<0>(key), std::get<1>(key)}); + while (p.first != p.second) { + if (p.first->second->internal_key() == k) + break; + ++p.first; + } + return p.first == p.second ? servicedependencies.end() : p.first; +} +#endif diff --git a/engine/src/serviceescalation.cc b/engine/src/serviceescalation.cc index 27751dfc13f..e982d4eba2b 100644 --- a/engine/src/serviceescalation.cc +++ b/engine/src/serviceescalation.cc @@ -123,6 +123,7 @@ void serviceescalation::resolve(uint32_t& w [[maybe_unused]], uint32_t& e) { } } +#ifdef LEGACY_CONF /** * @brief Checks that this serviceescalation corresponds to the Configuration * object obj. This function doesn't check contactgroups as it is usually used @@ -158,3 +159,40 @@ bool serviceescalation::matches( return true; } +#else +/** + * @brief Checks that this serviceescalation corresponds to the Configuration + * object obj. This function doesn't check contactgroups as it is usually used + * to modify them. + * + * @param obj A service escalation configuration object. + * + * @return A boolean that is True if they match. + */ +bool serviceescalation::matches( + const configuration::Serviceescalation& obj) const { + uint32_t escalate_on = + ((obj.escalation_options() & configuration::action_se_warning) + ? notifier::warning + : notifier::none) | + ((obj.escalation_options() & configuration::action_se_unknown) + ? notifier::unknown + : notifier::none) | + ((obj.escalation_options() & configuration::action_se_critical) + ? notifier::critical + : notifier::none) | + ((obj.escalation_options() & configuration::action_se_recovery) + ? notifier::ok + : notifier::none); + if (_hostname != obj.hosts().data(0) || + _description != obj.service_description().data(0) || + get_first_notification() != obj.first_notification() || + get_last_notification() != obj.last_notification() || + get_notification_interval() != obj.notification_interval() || + get_escalation_period() != obj.escalation_period() || + get_escalate_on() != escalate_on) + return false; + + return true; +} +#endif diff --git a/engine/src/shared.cc b/engine/src/shared.cc index 76afe843019..71a4c1ece27 100644 --- a/engine/src/shared.cc +++ b/engine/src/shared.cc @@ -1,6 +1,7 @@ /** * Copyright 1999-2011 Ethan Galstad * Copyright 2011-2013 Merethis + * Copyright 2023-2024 Centreon * * This file is part of Centreon Engine. * @@ -141,6 +142,13 @@ void get_datetime_string(time_t const* raw_time, char const* tzone(tm_s.tm_isdst ? tzname[1] : tzname[0]); #endif /* HAVE_TM_ZONE || HAVE_TZNAME */ + int32_t date_format; +#ifdef LEGACY_CONF + date_format = config->date_format(); +#else + date_format = pb_config.date_format(); +#endif + /* ctime() style date/time */ if (type == LONG_DATE_TIME) snprintf(buffer, buffer_length, "%s %s %d %02d:%02d:%02d %s %d", @@ -149,16 +157,15 @@ void get_datetime_string(time_t const* raw_time, /* short date/time */ else if (type == SHORT_DATE_TIME) { - if (config->date_format() == DATE_FORMAT_EURO) + if (date_format == DATE_FORMAT_EURO) snprintf(buffer, buffer_length, "%02d-%02d-%04d %02d:%02d:%02d", day, month, year, hour, minute, second); - else if (config->date_format() == DATE_FORMAT_ISO8601 || - config->date_format() == DATE_FORMAT_STRICT_ISO8601) - snprintf( - buffer, buffer_length, "%04d-%02d-%02d%c%02d:%02d:%02d", year, month, - day, - (config->date_format() == DATE_FORMAT_STRICT_ISO8601) ? 'T' : ' ', - hour, minute, second); + else if (date_format == DATE_FORMAT_ISO8601 || + date_format == DATE_FORMAT_STRICT_ISO8601) + snprintf(buffer, buffer_length, "%04d-%02d-%02d%c%02d:%02d:%02d", year, + month, day, + (date_format == DATE_FORMAT_STRICT_ISO8601) ? 'T' : ' ', hour, + minute, second); else snprintf(buffer, buffer_length, "%02d-%02d-%04d %02d:%02d:%02d", month, day, year, hour, minute, second); @@ -166,10 +173,10 @@ void get_datetime_string(time_t const* raw_time, /* short date */ else if (type == SHORT_DATE) { - if (config->date_format() == DATE_FORMAT_EURO) + if (date_format == DATE_FORMAT_EURO) snprintf(buffer, buffer_length, "%02d-%02d-%04d", day, month, year); - else if (config->date_format() == DATE_FORMAT_ISO8601 || - config->date_format() == DATE_FORMAT_STRICT_ISO8601) + else if (date_format == DATE_FORMAT_ISO8601 || + date_format == DATE_FORMAT_STRICT_ISO8601) snprintf(buffer, buffer_length, "%04d-%02d-%02d", year, month, day); else snprintf(buffer, buffer_length, "%02d-%02d-%04d", month, day, year); diff --git a/engine/src/statistics.cc b/engine/src/statistics.cc index 37959b07f90..443c2bd0ec8 100644 --- a/engine/src/statistics.cc +++ b/engine/src/statistics.cc @@ -54,6 +54,7 @@ pid_t statistics::get_pid() const noexcept { * * @return A boolean telling if the struct has been filled. */ +#ifdef LEGACY_CONF bool statistics::get_external_command_buffer_stats( buffer_stats& retval) const noexcept { if (config->check_external_commands()) { @@ -64,3 +65,15 @@ bool statistics::get_external_command_buffer_stats( } else return false; } +#else +bool statistics::get_external_command_buffer_stats( + buffer_stats& retval) const noexcept { + if (pb_config.check_external_commands()) { + retval.used = external_command_buffer.size(); + retval.high = external_command_buffer.high(); + retval.total = pb_config.external_command_buffer_slots(); + return true; + } else + return false; +} +#endif diff --git a/engine/src/timeperiod.cc b/engine/src/timeperiod.cc index 8f40a90edba..e6789822a96 100644 --- a/engine/src/timeperiod.cc +++ b/engine/src/timeperiod.cc @@ -44,7 +44,7 @@ timeperiod_map timeperiod::timeperiods; * @param[in] alias Time period alias. * */ - +#ifdef LEGACY_CONF timeperiod::timeperiod(std::string const& name, std::string const& alias) : _name{name}, _alias{alias} { if (name.empty() || alias.empty()) { @@ -64,6 +64,93 @@ timeperiod::timeperiod(std::string const& name, std::string const& alias) throw engine_error() << "Could not register time period '" << name << "'"; } } +#else +/** + * @brief Constructor of a timeperiod from its configuration protobuf object. + * + * @param obj The configuration protobuf object. + */ +timeperiod::timeperiod(const configuration::Timeperiod& obj) + : _name{obj.timeperiod_name()}, _alias{obj.alias()} { + if (_name.empty() || _alias.empty()) { + engine_logger(log_config_error, basic) + << "Error: Name or alias for timeperiod is NULL"; + config_logger->error("Error: Name or alias for timeperiod is NULL"); + throw engine_error() << "Could not register time period '" << _name << "'"; + } + + // Check if the timeperiod already exist. + timeperiod_map::const_iterator it{timeperiod::timeperiods.find(_name)}; + if (it != timeperiod::timeperiods.end()) { + config_logger->error("Error: Timeperiod '{}' has already been defined", + _name); + throw engine_error() << "Could not register time period '" << _name << "'"; + } + + // Fill time period structure. + for (auto& r : obj.timeranges().sunday()) + days[0].emplace_back(r.range_start(), r.range_end()); + for (auto& r : obj.timeranges().monday()) + days[1].emplace_back(r.range_start(), r.range_end()); + for (auto& r : obj.timeranges().tuesday()) + days[2].emplace_back(r.range_start(), r.range_end()); + for (auto& r : obj.timeranges().wednesday()) + days[3].emplace_back(r.range_start(), r.range_end()); + for (auto& r : obj.timeranges().thursday()) + days[4].emplace_back(r.range_start(), r.range_end()); + for (auto& r : obj.timeranges().friday()) + days[5].emplace_back(r.range_start(), r.range_end()); + for (auto& r : obj.timeranges().saturday()) + days[6].emplace_back(r.range_start(), r.range_end()); + + auto fill_exceptions = [this](const auto& obj_daterange, int idx) { + for (auto& r : obj_daterange) { + exceptions[idx].emplace_back(static_cast<daterange::type_range>(r.type()), + r.syear(), r.smon(), r.smday(), r.swday(), + r.swday_offset(), r.eyear(), r.emon(), + r.emday(), r.ewday(), r.ewday_offset(), + r.skip_interval(), r.timerange()); + } + }; + + fill_exceptions(obj.exceptions().calendar_date(), 0); + fill_exceptions(obj.exceptions().month_date(), 1); + fill_exceptions(obj.exceptions().month_day(), 2); + fill_exceptions(obj.exceptions().month_week_day(), 3); + fill_exceptions(obj.exceptions().week_day(), 4); + + set_exclusions(obj.exclude()); +} + +void timeperiod::set_exclusions(const configuration::StringSet& exclusions) { + _exclusions.clear(); + for (auto& s : exclusions.data()) + _exclusions.emplace(s, nullptr); +} + +void timeperiod::set_exceptions(const configuration::ExceptionArray& array) { + for (auto& e : exceptions) + e.clear(); + + auto fill_exceptions = [this](const auto& obj_daterange, int idx) { + for (auto& r : obj_daterange) { +// std::list<timerange> tr; +// for (auto& t : r.timerange()) +// tr.emplace_back(t.range_start(), t.range_end()); + exceptions[idx].emplace_back( + static_cast<daterange::type_range>(r.type()), r.syear(), r.smon(), + r.smday(), r.swday(), r.swday_offset(), r.eyear(), r.emon(), + r.emday(), r.ewday(), r.ewday_offset(), r.skip_interval(), r.timerange()); + } + }; + + fill_exceptions(array.calendar_date(), 0); + fill_exceptions(array.month_date(), 1); + fill_exceptions(array.month_day(), 2); + fill_exceptions(array.month_week_day(), 3); + fill_exceptions(array.week_day(), 4); +} +#endif void timeperiod::set_name(std::string const& name) { _name = name; @@ -1158,3 +1245,25 @@ void timeperiod::resolve(uint32_t& w __attribute__((unused)), uint32_t& e) { throw engine_error() << "Cannot resolve time period '" << _name << "'"; } } + +#ifndef LEGACY_CONF +void timeperiod::set_days(const configuration::DaysArray& array) { + for (auto& d : days) + d.clear(); + + for (auto& r : array.sunday()) + days[0].emplace_back(r.range_start(), r.range_end()); + for (auto& r : array.monday()) + days[1].emplace_back(r.range_start(), r.range_end()); + for (auto& r : array.tuesday()) + days[2].emplace_back(r.range_start(), r.range_end()); + for (auto& r : array.wednesday()) + days[3].emplace_back(r.range_start(), r.range_end()); + for (auto& r : array.thursday()) + days[4].emplace_back(r.range_start(), r.range_end()); + for (auto& r : array.friday()) + days[5].emplace_back(r.range_start(), r.range_end()); + for (auto& r : array.saturday()) + days[6].emplace_back(r.range_start(), r.range_end()); +} +#endif diff --git a/engine/src/timerange.cc b/engine/src/timerange.cc index a0e069420af..e4ead3a2669 100644 --- a/engine/src/timerange.cc +++ b/engine/src/timerange.cc @@ -32,14 +32,14 @@ timerange::timerange(uint64_t start, uint64_t end) { config_logger->error("Error: Start time {} is not valid for timeperiod", start); throw engine_error() << "Could not create timerange " - << "start'" << start << "' end '" << end << "'"; + << "start '" << start << "' end '" << end << "'"; } if (end > 86400) { engine_logger(log_config_error, basic) - << "Error: End time " << end << " is not value for timeperiod"; - config_logger->error("Error: End time {} is not value for timeperiod", end); + << "Error: End time " << end << " is not valid for timeperiod"; + config_logger->error("Error: End time {} is not valid for timeperiod", end); throw engine_error() << "Could not create timerange " - << "start'" << start << "' end '" << end << "'"; + << "start '" << start << "' end '" << end << "'"; } _range_start = start; diff --git a/engine/src/xsddefault.cc b/engine/src/xsddefault.cc index 93a219b608d..6c5ec4336d4 100644 --- a/engine/src/xsddefault.cc +++ b/engine/src/xsddefault.cc @@ -49,21 +49,26 @@ static int xsddefault_status_log_fd(-1); /* initialize status data */ int xsddefault_initialize_status_data() { - if (verify_config || config->status_file().empty()) +#ifdef LEGACY_CONF + const std::string& status_file = config->status_file(); +#else + const std::string& status_file = pb_config.status_file(); +#endif + if (verify_config || status_file.empty()) return OK; if (xsddefault_status_log_fd == -1) { // delete the old status log (it might not exist). - unlink(config->status_file().c_str()); + unlink(status_file.c_str()); if ((xsddefault_status_log_fd = - open(config->status_file().c_str(), O_WRONLY | O_CREAT, + open(status_file.c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP)) == -1) { engine_logger(engine::logging::log_runtime_error, engine::logging::basic) - << "Error: Unable to open status data file '" << config->status_file() + << "Error: Unable to open status data file '" << status_file << "': " << strerror(errno); runtime_logger->error("Error: Unable to open status data file '{}': {}", - config->status_file(), strerror(errno)); + status_file, strerror(errno)); return ERROR; } set_cloexec(xsddefault_status_log_fd); @@ -76,9 +81,14 @@ int xsddefault_cleanup_status_data(int delete_status_data) { if (verify_config) return OK; +#ifdef LEGACY_CONF + const std::string& status_file = config->status_file(); +#else + const std::string& status_file = pb_config.status_file(); +#endif // delete the status log. - if (delete_status_data && !config->status_file().empty()) { - if (unlink(config->status_file().c_str())) + if (delete_status_data && !status_file.empty()) { + if (unlink(status_file.c_str())) return ERROR; } @@ -105,8 +115,44 @@ int xsddefault_save_status_data() { << "save_status_data()"; functions_logger->trace("save_status_data()"); +#ifdef LEGACY_CONF + bool check_external_commands = config->check_external_commands(); + bool enable_notifications = config->enable_notifications(); + bool execute_service_checks = config->execute_service_checks(); + bool accept_passive_service_checks = config->accept_passive_service_checks(); + bool execute_host_checks = config->execute_host_checks(); + bool accept_passive_host_checks = config->accept_passive_host_checks(); + bool enable_event_handlers = config->enable_event_handlers(); + bool obsess_over_services = config->obsess_over_services(); + bool obsess_over_hosts = config->obsess_over_hosts(); + bool check_service_freshness = config->check_service_freshness(); + bool check_host_freshness = config->check_host_freshness(); + bool enable_flap_detection = config->enable_flap_detection(); + bool process_performance_data = config->process_performance_data(); + const std::string& global_host_event_handler = config->global_host_event_handler(); + const std::string& global_service_event_handler = config->global_service_event_handler(); + uint32_t external_command_buffer_slots = config->external_command_buffer_slots(); +#else + bool check_external_commands = pb_config.check_external_commands(); + bool enable_notifications = pb_config.enable_notifications(); + bool execute_service_checks = pb_config.execute_service_checks(); + bool accept_passive_service_checks = pb_config.accept_passive_service_checks(); + bool execute_host_checks = pb_config.execute_host_checks(); + bool accept_passive_host_checks = pb_config.accept_passive_host_checks(); + bool enable_event_handlers = pb_config.enable_event_handlers(); + bool obsess_over_services = pb_config.obsess_over_services(); + bool obsess_over_hosts = pb_config.obsess_over_hosts(); + bool check_service_freshness = pb_config.check_service_freshness(); + bool check_host_freshness = pb_config.check_host_freshness(); + bool enable_flap_detection = pb_config.enable_flap_detection(); + bool process_performance_data = pb_config.process_performance_data(); + const std::string& global_host_event_handler = pb_config.global_host_event_handler(); + const std::string& global_service_event_handler = pb_config.global_service_event_handler(); + uint32_t external_command_buffer_slots = pb_config.external_command_buffer_slots(); +#endif + // get number of items in the command buffer - if (config->check_external_commands()) { + if (check_external_commands) { used_external_command_buffer_slots = external_command_buffer.size(); high_external_command_buffer_slots = external_command_buffer.high(); } @@ -155,46 +201,46 @@ int xsddefault_save_status_data() { << static_cast<long long>(last_log_rotation) << "\n" "\tenable_notifications=" - << config->enable_notifications() + << enable_notifications << "\n" "\tactive_service_checks_enabled=" - << config->execute_service_checks() + << execute_service_checks << "\n" "\tpassive_service_checks_enabled=" - << config->accept_passive_service_checks() + << accept_passive_service_checks << "\n" "\tactive_host_checks_enabled=" - << config->execute_host_checks() + << execute_host_checks << "\n" "\tpassive_host_checks_enabled=" - << config->accept_passive_host_checks() + << accept_passive_host_checks << "\n" "\tenable_event_handlers=" - << config->enable_event_handlers() + << enable_event_handlers << "\n" "\tobsess_over_services=" - << config->obsess_over_services() + << obsess_over_services << "\n" "\tobsess_over_hosts=" - << config->obsess_over_hosts() + << obsess_over_hosts << "\n" "\tcheck_service_freshness=" - << config->check_service_freshness() + << check_service_freshness << "\n" "\tcheck_host_freshness=" - << config->check_host_freshness() + << check_host_freshness << "\n" "\tenable_flap_detection=" - << config->enable_flap_detection() + << enable_flap_detection << "\n" "\tprocess_performance_data=" - << config->process_performance_data() + << process_performance_data << "\n" "\tglobal_host_event_handler=" - << config->global_host_event_handler().c_str() + << global_host_event_handler << "\n" "\tglobal_service_event_handler=" - << config->global_service_event_handler().c_str() + << global_service_event_handler << "\n" "\tnext_comment_id=" << comment::get_next_comment_id() @@ -209,7 +255,7 @@ int xsddefault_save_status_data() { << next_notification_id << "\n" "\ttotal_external_command_buffer_slots=" - << config->external_command_buffer_slots() + << external_command_buffer_slots << "\n" "\tused_external_command_buffer_slots=" << used_external_command_buffer_slots @@ -723,16 +769,22 @@ int xsddefault_save_status_data() { // Write data in buffer. stream.flush(); +#ifdef LEGACY_CONF + const std::string& status_file = config->status_file(); +#else + const std::string& status_file = pb_config.status_file(); +#endif + // Prepare status file for overwrite. if ((ftruncate(xsddefault_status_log_fd, 0) == -1) || (fsync(xsddefault_status_log_fd) == -1) || (lseek(xsddefault_status_log_fd, 0, SEEK_SET) == (off_t)-1)) { char const* msg(strerror(errno)); engine_logger(engine::logging::log_runtime_error, engine::logging::basic) - << "Error: Unable to update status data file '" << config->status_file() + << "Error: Unable to update status data file '" << status_file << "': " << msg; runtime_logger->error("Error: Unable to update status data file '{}': {}", - config->status_file(), msg); + status_file, msg); return ERROR; } @@ -746,9 +798,9 @@ int xsddefault_save_status_data() { char const* msg(strerror(errno)); engine_logger(engine::logging::log_runtime_error, engine::logging::basic) << "Error: Unable to update status data file '" - << config->status_file() << "': " << msg; + << status_file << "': " << msg; runtime_logger->error("Error: Unable to update status data file '{}': {}", - config->status_file(), msg); + status_file, msg); return ERROR; } data_ptr += wb; diff --git a/engine/tests/configuration/applier/applier-anomalydetection.cc b/engine/tests/configuration/applier/applier-anomalydetection.cc index a30aa2686f0..2a99bb12eee 100644 --- a/engine/tests/configuration/applier/applier-anomalydetection.cc +++ b/engine/tests/configuration/applier/applier-anomalydetection.cc @@ -60,7 +60,6 @@ TEST_F(ApplierAnomalydetection, // Then the applier add_object throws an exception. TEST_F(ApplierAnomalydetection, NewHostWithoutHostId) { configuration::applier::host hst_aply; - configuration::applier::service ad_aply; configuration::anomalydetection ad; configuration::host hst; ASSERT_TRUE(hst.parse("host_name", "test_host")); From 0d157750fb1c638e7c2dcdae98fb374bd0c295b4 Mon Sep 17 00:00:00 2001 From: David Boucher <dboucher@centreon.com> Date: Tue, 27 Aug 2024 16:12:09 +0200 Subject: [PATCH 918/948] fix(common/engine_conf): various fixes after code review * helper initializations better * pb_difference works with pointers now. * new comments also added. * little fixes * connector modify_object() and remove_object() had issues because of wrong backports * method in timeperiod can return a reference * comment updated for the set() method * fill_(service[host)_notification_options mutualized * parser has its array size fixed automatically * new comments in the parser * add_contact can be defined with moved addresses. * timeperiod_helper _get_days_id() method rewritten with a map. * timeperiod_helper _get_month_id() method rewritten with a map. REFS: MON-34072 MON-15480 --- cmake-vcpkg.sh | 1 + common/engine_conf/anomalydetection_helper.cc | 34 +- common/engine_conf/command_helper.cc | 5 +- common/engine_conf/connector_helper.cc | 5 +- common/engine_conf/contact_helper.cc | 8 +- common/engine_conf/contactgroup_helper.cc | 7 +- common/engine_conf/host_helper.cc | 34 +- common/engine_conf/hostdependency_helper.cc | 5 +- common/engine_conf/hostescalation_helper.cc | 5 +- common/engine_conf/hostgroup_helper.cc | 7 +- common/engine_conf/message_helper.cc | 24 +- common/engine_conf/message_helper.hh | 35 +- common/engine_conf/parser.cc | 30 +- common/engine_conf/parser.cc.old | 1503 ----------------- common/engine_conf/parser.hh | 25 +- common/engine_conf/parser.hh.old | 162 -- common/engine_conf/service_helper.cc | 36 +- .../engine_conf/servicedependency_helper.cc | 5 +- .../engine_conf/serviceescalation_helper.cc | 5 +- common/engine_conf/servicegroup_helper.cc | 8 +- common/engine_conf/severity_helper.cc | 5 +- common/engine_conf/state_helper.cc | 4 +- common/engine_conf/tag_helper.cc | 16 +- common/engine_conf/timeperiod_helper.cc | 46 +- .../configuration/applier/pb_difference.hh | 108 +- engine/inc/com/centreon/engine/contact.hh | 4 +- engine/inc/com/centreon/engine/timeperiod.hh | 12 +- engine/src/configuration/applier/connector.cc | 104 +- engine/src/configuration/applier/contact.cc | 13 +- engine/src/configuration/applier/scheduler.cc | 86 +- engine/src/configuration/applier/state.cc | 97 +- engine/src/contact.cc | 6 +- 32 files changed, 422 insertions(+), 2023 deletions(-) delete mode 100644 common/engine_conf/parser.cc.old delete mode 100644 common/engine_conf/parser.hh.old diff --git a/cmake-vcpkg.sh b/cmake-vcpkg.sh index e8eb514dcdb..96f76b4de32 100755 --- a/cmake-vcpkg.sh +++ b/cmake-vcpkg.sh @@ -204,6 +204,7 @@ elif [ -r /etc/issue ] ; then libgcrypt20-dev libgnutls28-dev liblua5.3-dev + libmariadb-dev libperl-dev librrd-dev libssh2-1-dev diff --git a/common/engine_conf/anomalydetection_helper.cc b/common/engine_conf/anomalydetection_helper.cc index 50e51a705a0..a0de5e1085a 100644 --- a/common/engine_conf/anomalydetection_helper.cc +++ b/common/engine_conf/anomalydetection_helper.cc @@ -19,6 +19,7 @@ #include "common/engine_conf/anomalydetection_helper.hh" #include "com/centreon/exceptions/msg_fmt.hh" +#include "common/engine_conf/state.pb.h" using com::centreon::exceptions::msg_fmt; @@ -45,7 +46,7 @@ anomalydetection_helper::anomalydetection_helper(Anomalydetection* obj) {"passive_checks_enabled", "checks_passive"}, {"severity", "severity_id"}, }, - 53) { + Anomalydetection::descriptor()->field_count()) { _init(); } @@ -58,6 +59,8 @@ anomalydetection_helper::anomalydetection_helper(Anomalydetection* obj) bool anomalydetection_helper::hook(std::string_view key, const std::string_view& value) { Anomalydetection* obj = static_cast<Anomalydetection*>(mut_obj()); + /* Since we use key to get back the good key value, it is faster to give key + * by copy to the method. We avoid one key allocation... */ key = validate_key(key); if (key == "contactgroups") { fill_string_group(obj->mutable_contactgroups(), value); @@ -104,30 +107,11 @@ bool anomalydetection_helper::hook(std::string_view key, return true; } else if (key == "notification_options") { uint16_t options(action_svc_none); - auto values = absl::StrSplit(value, ','); - for (auto it = values.begin(); it != values.end(); ++it) { - std::string_view v = absl::StripAsciiWhitespace(*it); - if (v == "u" || v == "unknown") - options |= action_svc_unknown; - else if (v == "w" || v == "warning") - options |= action_svc_warning; - else if (v == "c" || v == "critical") - options |= action_svc_critical; - else if (v == "r" || v == "recovery") - options |= action_svc_ok; - else if (v == "f" || v == "flapping") - options |= action_svc_flapping; - else if (v == "s" || v == "downtime") - options |= action_svc_downtime; - else if (v == "n" || v == "none") - options = action_svc_none; - else if (v == "a" || v == "all") - options = action_svc_unknown | action_svc_warning | - action_svc_critical | action_svc_ok | action_svc_flapping | - action_svc_downtime; - else - return false; - } + if (fill_service_notification_options(&options, value)) { + obj->set_notification_options(options); + return true; + } else + return false; obj->set_notification_options(options); return true; } else if (key == "servicegroups") { diff --git a/common/engine_conf/command_helper.cc b/common/engine_conf/command_helper.cc index 31277ba3021..fd222f1574e 100644 --- a/common/engine_conf/command_helper.cc +++ b/common/engine_conf/command_helper.cc @@ -31,7 +31,10 @@ namespace com::centreon::engine::configuration { * the owner of this object. It is just used to set the message default values. */ command_helper::command_helper(Command* obj) - : message_helper(object_type::command, obj, {}, 5) { + : message_helper(object_type::command, + obj, + {}, + Command::descriptor()->field_count()) { _init(); } diff --git a/common/engine_conf/connector_helper.cc b/common/engine_conf/connector_helper.cc index 0fb6a0ab3a6..27273c600ec 100644 --- a/common/engine_conf/connector_helper.cc +++ b/common/engine_conf/connector_helper.cc @@ -31,7 +31,10 @@ namespace com::centreon::engine::configuration { * the owner of this object. */ connector_helper::connector_helper(Connector* obj) - : message_helper(object_type::connector, obj, {}, 4) { + : message_helper(object_type::connector, + obj, + {}, + Connector::descriptor()->field_count()) { _init(); } diff --git a/common/engine_conf/contact_helper.cc b/common/engine_conf/contact_helper.cc index 59d3cc075ed..0967c1aed4d 100644 --- a/common/engine_conf/contact_helper.cc +++ b/common/engine_conf/contact_helper.cc @@ -36,7 +36,7 @@ contact_helper::contact_helper(Contact* obj) { {"contact_groups", "contactgroups"}, }, - 21) { + Contact::descriptor()->field_count()) { _init(); } @@ -48,17 +48,19 @@ contact_helper::contact_helper(Contact* obj) */ bool contact_helper::hook(std::string_view key, const std::string_view& value) { Contact* obj = static_cast<Contact*>(mut_obj()); + /* Since we use key to get back the good key value, it is faster to give key + * by copy to the method. We avoid one key allocation... */ key = validate_key(key); if (key == "host_notification_options") { - uint32_t options; + uint16_t options = action_hst_none; if (fill_host_notification_options(&options, value)) { obj->set_host_notification_options(options); return true; } else return false; } else if (key == "service_notification_options") { - uint32_t options; + uint16_t options = action_svc_none; if (fill_service_notification_options(&options, value)) { obj->set_service_notification_options(options); return true; diff --git a/common/engine_conf/contactgroup_helper.cc b/common/engine_conf/contactgroup_helper.cc index 8c74aa73b5b..d5f2bc7848b 100644 --- a/common/engine_conf/contactgroup_helper.cc +++ b/common/engine_conf/contactgroup_helper.cc @@ -31,7 +31,10 @@ namespace com::centreon::engine::configuration { * not the owner of this object. */ contactgroup_helper::contactgroup_helper(Contactgroup* obj) - : message_helper(object_type::contactgroup, obj, {}, 6) { + : message_helper(object_type::contactgroup, + obj, + {}, + Contactgroup::descriptor()->field_count()) { _init(); } @@ -44,6 +47,8 @@ contactgroup_helper::contactgroup_helper(Contactgroup* obj) bool contactgroup_helper::hook(std::string_view key, const std::string_view& value) { Contactgroup* obj = static_cast<Contactgroup*>(mut_obj()); + /* Since we use key to get back the good key value, it is faster to give key + * by copy to the method. We avoid one key allocation... */ key = validate_key(key); if (key == "contactgroup_members") { fill_string_group(obj->mutable_contactgroup_members(), value); diff --git a/common/engine_conf/host_helper.cc b/common/engine_conf/host_helper.cc index c98387638c3..5f4b8450178 100644 --- a/common/engine_conf/host_helper.cc +++ b/common/engine_conf/host_helper.cc @@ -51,7 +51,7 @@ host_helper::host_helper(Host* obj) {"3d_coords", "coords_3d"}, {"severity", "severity_id"}, }, - 53) { + Host::descriptor()->field_count()) { _init(); } @@ -63,6 +63,8 @@ host_helper::host_helper(Host* obj) */ bool host_helper::hook(std::string_view key, const std::string_view& value) { Host* obj = static_cast<Host*>(mut_obj()); + /* Since we use key to get back the good key value, it is faster to give key + * by copy to the method. We avoid one key allocation... */ key = validate_key(key); if (key == "contactgroups") { fill_string_group(obj->mutable_contactgroups(), value); @@ -74,30 +76,12 @@ bool host_helper::hook(std::string_view key, const std::string_view& value) { fill_string_group(obj->mutable_hostgroups(), value); return true; } else if (key == "notification_options") { - uint16_t options(action_svc_none); - auto values = absl::StrSplit(value, ','); - for (auto it = values.begin(); it != values.end(); ++it) { - std::string_view v = absl::StripAsciiWhitespace(*it); - if (v == "d" || v == "down") - options |= action_hst_down; - else if (v == "u" || v == "unreachable") - options |= action_hst_unreachable; - else if (v == "r" || v == "recovery") - options |= action_hst_up; - else if (v == "f" || v == "flapping") - options |= action_hst_flapping; - else if (v == "s" || v == "downtime") - options |= action_hst_downtime; - else if (v == "n" || v == "none") - options = action_hst_none; - else if (v == "a" || v == "all") - options = action_hst_down | action_hst_unreachable | action_hst_up | - action_hst_flapping | action_hst_downtime; - else - return false; - } - obj->set_notification_options(options); - return true; + uint16_t options = action_hst_none; + if (fill_host_notification_options(&options, value)) { + obj->set_notification_options(options); + return true; + } else + return false; } else if (key == "parents") { fill_string_group(obj->mutable_parents(), value); return true; diff --git a/common/engine_conf/hostdependency_helper.cc b/common/engine_conf/hostdependency_helper.cc index 3140485d490..6dbd419f4d6 100644 --- a/common/engine_conf/hostdependency_helper.cc +++ b/common/engine_conf/hostdependency_helper.cc @@ -19,6 +19,7 @@ #include "common/engine_conf/hostdependency_helper.hh" #include "com/centreon/exceptions/msg_fmt.hh" +#include "common/engine_conf/state.pb.h" using com::centreon::exceptions::msg_fmt; @@ -65,7 +66,7 @@ hostdependency_helper::hostdependency_helper(Hostdependency* obj) {"notification_failure_criteria", "notification_failure_options"}, {"execution_failure_criteria", "execution_failure_options"}, }, - 11) { + Hostdependency::descriptor()->field_count()) { _init(); } @@ -78,6 +79,8 @@ hostdependency_helper::hostdependency_helper(Hostdependency* obj) bool hostdependency_helper::hook(std::string_view key, const std::string_view& value) { Hostdependency* obj = static_cast<Hostdependency*>(mut_obj()); + /* Since we use key to get back the good key value, it is faster to give key + * by copy to the method. We avoid one key allocation... */ key = validate_key(key); if (key == "notification_failure_options" || diff --git a/common/engine_conf/hostescalation_helper.cc b/common/engine_conf/hostescalation_helper.cc index 34b9a13bfd2..5d6e9c69df6 100644 --- a/common/engine_conf/hostescalation_helper.cc +++ b/common/engine_conf/hostescalation_helper.cc @@ -19,6 +19,7 @@ #include "common/engine_conf/hostescalation_helper.hh" #include "com/centreon/exceptions/msg_fmt.hh" +#include "common/engine_conf/state.pb.h" using com::centreon::exceptions::msg_fmt; @@ -56,7 +57,7 @@ hostescalation_helper::hostescalation_helper(Hostescalation* obj) {"host_name", "hosts"}, {"contact_groups", "contactgroups"}, }, - 10) { + Hostescalation::descriptor()->field_count()) { _init(); } @@ -69,6 +70,8 @@ hostescalation_helper::hostescalation_helper(Hostescalation* obj) bool hostescalation_helper::hook(std::string_view key, const std::string_view& value) { Hostescalation* obj = static_cast<Hostescalation*>(mut_obj()); + /* Since we use key to get back the good key value, it is faster to give key + * by copy to the method. We avoid one key allocation... */ key = validate_key(key); if (key == "escalation_options") { diff --git a/common/engine_conf/hostgroup_helper.cc b/common/engine_conf/hostgroup_helper.cc index f687475e472..8d994ab6432 100644 --- a/common/engine_conf/hostgroup_helper.cc +++ b/common/engine_conf/hostgroup_helper.cc @@ -31,7 +31,10 @@ namespace com::centreon::engine::configuration { * the owner of this object. */ hostgroup_helper::hostgroup_helper(Hostgroup* obj) - : message_helper(object_type::hostgroup, obj, {}, 9) { + : message_helper(object_type::hostgroup, + obj, + {}, + Hostgroup::descriptor()->field_count()) { _init(); } @@ -44,6 +47,8 @@ hostgroup_helper::hostgroup_helper(Hostgroup* obj) bool hostgroup_helper::hook(std::string_view key, const std::string_view& value) { Hostgroup* obj = static_cast<Hostgroup*>(mut_obj()); + /* Since we use key to get back the good key value, it is faster to give key + * by copy to the method. We avoid one key allocation... */ key = validate_key(key); if (key == "members") { fill_string_group(obj->mutable_members(), value); diff --git a/common/engine_conf/message_helper.cc b/common/engine_conf/message_helper.cc index 8068dc16c20..7e3b506ad72 100644 --- a/common/engine_conf/message_helper.cc +++ b/common/engine_conf/message_helper.cc @@ -186,9 +186,9 @@ void fill_string_group(StringList* grp, const std::string_view& value) { * * @return True on success. */ -bool fill_host_notification_options(uint32_t* options, +bool fill_host_notification_options(uint16_t* options, const std::string_view& value) { - uint32_t tmp_options = action_hst_none; + uint16_t tmp_options = action_hst_none; auto arr = absl::StrSplit(value, ','); for (auto& v : arr) { std::string_view value = absl::StripAsciiWhitespace(v); @@ -223,9 +223,9 @@ bool fill_host_notification_options(uint32_t* options, * * @return True on success. */ -bool fill_service_notification_options(uint32_t* options, +bool fill_service_notification_options(uint16_t* options, const std::string_view& value) { - uint32_t tmp_options = action_svc_none; + uint16_t tmp_options = action_svc_none; auto arr = absl::StrSplit(value, ','); for (auto& v : arr) { std::string_view value = absl::StripAsciiWhitespace(v); @@ -293,21 +293,19 @@ bool message_helper::insert_customvariable(std::string_view key } /** - * @brief Set the value given as a string to the object key. If the key does - * not exist, the correspondence table may be used to find a replacement of - * the key. The function converts the value to the appropriate type. + * @brief Set the value given as a string to the object referenced by the key. + * If the key does not exist, the correspondence table may be used to find a + * replacement of the key. The function converts the value to the appropriate + * type. * * Another important point is that many configuration objects contain the Object * obj message (something like an inheritance). This message contains three - * field names, use and register that are important for templating. If keys are - * one of these names, the function tries to work directly with the obj message. + * field names, name, use and register that are important for templating. If + * keys are one of these names, the function tries to work directly with the obj + * message. * - * @tparam T The type of the message containing the object key. - * @param msg The message containing the object key. * @param key The key to localize the object to set. * @param value The value as string that will be converted to the good type. - * @param correspondence A hash table giving traductions from keys to others. - * If a key fails, correspondence is used to find a new replacement key. * * @return true on success. */ diff --git a/common/engine_conf/message_helper.hh b/common/engine_conf/message_helper.hh index 69b73fc9b71..ba19351fa74 100644 --- a/common/engine_conf/message_helper.hh +++ b/common/engine_conf/message_helper.hh @@ -65,9 +65,9 @@ bool fill_pair_string_group(PairStringSet* grp, const std::string_view& value); void fill_string_group(StringList* grp, const std::string_view& value); void fill_string_group(StringSet* grp, const std::string_view& value); -bool fill_host_notification_options(uint32_t* options, +bool fill_host_notification_options(uint16_t* options, const std::string_view& value); -bool fill_service_notification_options(uint32_t* options, +bool fill_service_notification_options(uint16_t* options, const std::string_view& value); /** @@ -95,16 +95,47 @@ class message_helper { severity = 16, tag = 17, state = 18, + nb_types = 19, }; private: const object_type _otype; Message* _obj; + /* + * The centengine cfg file allows several words for a same field. For example, + * we can have hosts, host, hostnames, hostname for the 'hostname' field. + * This map gives as value the field name corresponding to the name specified + * in the cfg file (the key). */ const absl::flat_hash_map<std::string, std::string> _correspondence; + /* + * _modified_field is a vector used for inheritance. An object can inherit + * from another one. To apply the parent values, we must be sure this object + * does not already change the field before. And we cannot use the protobuf + * default values since configuration objects have their own default values. + * So, the idea is: + * 1. The protobuf object is created. + * 2. Thankgs to the helper, its default values are set. + * 3. _modified_field cases are all set to false. + * 4. Fields are modified while the cfg file is read and _modified_field is + * updated in consequence. + * 5. We can replace unchanged fields with the parent values if needed. + */ std::vector<bool> _modified_field; + /* When a configuration object is resolved, this flag is set to true. */ bool _resolved = false; public: + /** + * @brief Constructor of message_helper. + * + * @param otype An object_type specifying the type of the configuration + * object. + * @param obj The Protobuf message associated to the helper. + * @param correspondence The correspondence table (see the _correspondence + * map description for more details). + * @param field_size The number of fields in the protobuf message (needed to + * initialize the _modified_field). + */ message_helper(object_type otype, Message* obj, absl::flat_hash_map<std::string, std::string>&& correspondence, diff --git a/common/engine_conf/parser.cc b/common/engine_conf/parser.cc index 783b21b2c9d..ebe5ab66add 100644 --- a/common/engine_conf/parser.cc +++ b/common/engine_conf/parser.cc @@ -496,31 +496,35 @@ void parser::_parse_resource_file(const std::string& path, State* pb_config) { */ void parser::_resolve_template(State* pb_config, error_cnt& err) { for (Command& c : *pb_config->mutable_commands()) - _resolve_template(_pb_helper[&c], _pb_templates[command]); + _resolve_template(_pb_helper[&c], _pb_templates[message_helper::command]); for (Connector& c : *pb_config->mutable_connectors()) - _resolve_template(_pb_helper[&c], _pb_templates[connector]); + _resolve_template(_pb_helper[&c], _pb_templates[message_helper::connector]); for (Contact& c : *pb_config->mutable_contacts()) - _resolve_template(_pb_helper[&c], _pb_templates[contact]); + _resolve_template(_pb_helper[&c], _pb_templates[message_helper::contact]); for (Contactgroup& cg : *pb_config->mutable_contactgroups()) - _resolve_template(_pb_helper[&cg], _pb_templates[contactgroup]); + _resolve_template(_pb_helper[&cg], + _pb_templates[message_helper::contactgroup]); for (Host& h : *pb_config->mutable_hosts()) - _resolve_template(_pb_helper[&h], _pb_templates[host]); + _resolve_template(_pb_helper[&h], _pb_templates[message_helper::host]); for (Service& s : *pb_config->mutable_services()) - _resolve_template(_pb_helper[&s], _pb_templates[service]); + _resolve_template(_pb_helper[&s], _pb_templates[message_helper::service]); for (Anomalydetection& a : *pb_config->mutable_anomalydetections()) - _resolve_template(_pb_helper[&a], _pb_templates[anomalydetection]); + _resolve_template(_pb_helper[&a], + _pb_templates[message_helper::anomalydetection]); for (Serviceescalation& se : *pb_config->mutable_serviceescalations()) - _resolve_template(_pb_helper[&se], _pb_templates[serviceescalation]); + _resolve_template(_pb_helper[&se], + _pb_templates[message_helper::serviceescalation]); for (Hostescalation& he : *pb_config->mutable_hostescalations()) - _resolve_template(_pb_helper[&he], _pb_templates[hostescalation]); + _resolve_template(_pb_helper[&he], + _pb_templates[message_helper::hostescalation]); for (const Command& c : pb_config->commands()) _pb_helper.at(&c)->check_validity(err); @@ -612,6 +616,14 @@ void parser::_resolve_template(std::unique_ptr<message_helper>& msg_helper, } } +/** + * @brief For each unchanged field in the Protobuf object stored in msg_helper, + * we copy the corresponding field from tmpl. This is the key for the + * inheritence with cfg files. + * + * @param msg_helper A message_help holding a protobuf message + * @param tmpl A template of the same type as the on in the msg_helper + */ void parser::_merge(std::unique_ptr<message_helper>& msg_helper, Message* tmpl) { Message* msg = msg_helper->mut_obj(); diff --git a/common/engine_conf/parser.cc.old b/common/engine_conf/parser.cc.old deleted file mode 100644 index b351ce53d74..00000000000 --- a/common/engine_conf/parser.cc.old +++ /dev/null @@ -1,1503 +0,0 @@ -/** - * Copyright 2011-2014,2017,2022-2023 Centreon - * - * This file is part of Centreon Engine. - * - * Centreon Engine is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License version 2 - * as published by the Free Software Foundation. - * - * Centreon Engine 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 Centreon Engine. If not, see - * <http://www.gnu.org/licenses/>. - */ - -#include "parser.hh" - -#include <absl/container/flat_hash_set.h> -#include <absl/strings/ascii.h> - -#include <filesystem> -#include <memory> - -#include "absl/strings/numbers.h" -#include "com/centreon/exceptions/msg_fmt.hh" -#include "anomalydetection_helper.hh" -#include "command_helper.hh" -#include "connector_helper.hh" -#include "contact_helper.hh" -#include "contactgroup_helper.hh" -#include "host_helper.hh" -#include "hostdependency_helper.hh" -#include "hostescalation_helper.hh" -#include "hostgroup_helper.hh" -#include "message_helper.hh" -#include "service_helper.hh" -#include "servicedependency_helper.hh" -#include "serviceescalation_helper.hh" -#include "servicegroup_helper.hh" -#include "severity_helper.hh" -#include "state_helper.hh" -#include "tag_helper.hh" -#include "timeperiod_helper.hh" -#include "common/log_v2/log_v2.hh" - -using namespace com::centreon; -using namespace com::centreon::engine::configuration; -using msg_fmt = com::centreon::exceptions::msg_fmt; -using error = com::centreon::exceptions::error; -using com::centreon::common::log_v2::log_v2; - -using Descriptor = ::google::protobuf::Descriptor; -using FieldDescriptor = ::google::protobuf::FieldDescriptor; -using Message = ::google::protobuf::Message; -using Reflection = ::google::protobuf::Reflection; - -parser::store parser::_store[] = { - &parser::_store_into_map<command, &command::command_name>, - &parser::_store_into_map<connector, &connector::connector_name>, - &parser::_store_into_map<contact, &contact::contact_name>, - &parser::_store_into_map<contactgroup, &contactgroup::contactgroup_name>, - &parser::_store_into_map<host, &host::host_name>, - &parser::_store_into_list, - &parser::_store_into_list, - &parser::_store_into_list, - &parser::_store_into_map<hostgroup, &hostgroup::hostgroup_name>, - &parser::_store_into_list, - &parser::_store_into_list, - &parser::_store_into_list, - &parser::_store_into_list, - &parser::_store_into_map<servicegroup, &servicegroup::servicegroup_name>, - &parser::_store_into_map<timeperiod, &timeperiod::timeperiod_name>, - &parser::_store_into_list, - &parser::_store_into_list, - &parser::_store_into_list}; - -/** - * Get the next valid line. - * - * @param[in, out] stream The current stream to read new line. - * @param[out] line The line to fill. - * @param[in, out] pos The current position. - * - * @return True if data is available, false if no data. - */ -static bool get_next_line(std::ifstream& stream, - std::string& line, - uint32_t& pos) { - while (std::getline(stream, line, '\n')) { - ++pos; - line = absl::StripAsciiWhitespace(line); - if (!line.empty()) { - char c = line[0]; - if (c != '#' && c != ';' && c != '\x0') - return true; - } - } - return false; -} - -#ifdef LEGACY_CONF -/** - * Default constructor. - * - * @param[in] read_options Configuration file reading options - * (use to skip some object type). - */ -parser::parser(unsigned int read_options) - : _config(nullptr), - _read_options(read_options), - _logger{log_v2::instance().get(log_v2::CONFIG)} {} - -/** - * Parse the object definition file. - * - * @param[in] path The object definitions path. - */ -void parser::_parse_object_definitions(std::string const& path) { - _logger->info("Processing object config file '{}'", path); - - std::ifstream stream(path, std::ios::binary); - if (!stream.is_open()) - throw msg_fmt("Parsing of object definition failed: can't open file '{}'", - path); - - _current_line = 0; - _current_path = path; - - bool parse_object = false; - object_ptr obj; - std::string input; - while (get_next_line(stream, input, _current_line)) { - // Multi-line. - while ('\\' == input[input.size() - 1]) { - input.resize(input.size() - 1); - std::string addendum; - if (!get_next_line(stream, addendum, _current_line)) - break; - input.append(addendum); - } - - // Check if is a valid object. - if (obj == nullptr) { - if (input.find("define") || !std::isspace(input[6])) - throw msg_fmt( - "Parsing of object definition failed " - "in file '{}' on line {}: Unexpected start definition", - _current_path, _current_line); - input.erase(0, 6); - absl::StripLeadingAsciiWhitespace(&input); - std::size_t last = input.size() - 1; - if (input.empty() || input[last] != '{') - throw msg_fmt( - "Parsing of object definition failed in file '{}' on line {}: " - "Unexpected start definition", - _current_path, _current_line); - input.erase(last); - absl::StripTrailingAsciiWhitespace(&input); - obj = object::create(input); - if (obj == nullptr) - throw msg_fmt( - "Parsing of object definition failed in file '{}' on line {}: " - "Unknown object type name '{}'", - _current_path, _current_line, input); - parse_object = (_read_options & (1 << obj->type())); - _objects_info.emplace(obj.get(), file_info(path, _current_line)); - } - // Check if is the not the end of the current object. - else if (input != "}") { - if (parse_object) { - if (!obj->parse(input)) - throw msg_fmt( - "Parsing of object definition failed in file '{}' on line {}: " - "Invalid line '{}'", - _current_path, _current_line, input); - } - } - // End of the current object. - else { - if (parse_object) { - if (!obj->name().empty()) - _add_template(obj); - if (obj->should_register()) - _add_object(obj); - } - obj.reset(); - } - } -} - -/** - * Parse the directory configuration. - * - * @param[in] path The directory path. - */ -void parser::_parse_directory_configuration(std::string const& path) { - for (auto& entry : std::filesystem::directory_iterator(path)) { - if (entry.is_regular_file() && entry.path().extension() == ".cfg") - _parse_object_definitions(entry.path().string()); - } -} - -/** - * Parse the global configuration file. - * - * @param[in] path The configuration path. - */ -void parser::_parse_global_configuration(const std::string& path) { - _logger->info("Reading main configuration file '{}'.", path); - - std::ifstream stream(path, std::ios::binary); - if (!stream.is_open()) - throw msg_fmt( - "Parsing of global configuration failed: can't open file '{}'", path); - - _config->cfg_main(path); - - _current_line = 0; - _current_path = path; - - std::string input; - while (get_next_line(stream, input, _current_line)) { - std::list<std::string> values = - absl::StrSplit(input, absl::MaxSplits('=', 1)); - if (values.size() == 2) { - auto it = values.begin(); - char const* key = it->c_str(); - ++it; - char const* value = it->c_str(); - if (_config->set(key, value)) - continue; - } - throw msg_fmt( - "Parsing of global configuration failed in file '{}' on line {}: " - "Invalid line '{}'", - path, _current_line, input); - } -} - -/** - * Parse the resource file. - * - * @param[in] path The resource file path. - */ -void parser::_parse_resource_file(std::string const& path) { - _logger->info("Reading resource file '{}'", path); - - std::ifstream stream(path.c_str(), std::ios::binary); - if (!stream.is_open()) - throw msg_fmt("Parsing of resource file failed: can't open file '{}'", - path); - - _current_line = 0; - _current_path = path; - - std::string input; - while (get_next_line(stream, input, _current_line)) { - try { - std::list<std::string> key_value = - absl::StrSplit(input, absl::MaxSplits('=', 1)); - if (key_value.size() == 2) { - auto it = key_value.begin(); - std::string& key = *it; - ++it; - std::string value = *it; - _config->user(key, value); - } else - throw msg_fmt( - "Parsing of resource file '{}' failed on line {}; Invalid line " - "'{}'", - _current_path, _current_line, input); - } catch (std::exception const& e) { - (void)e; - throw msg_fmt( - "Parsing of resource file '{}' failed on line {}: Invalid line '{}'", - _current_path, _current_line, input); - } - } -} - -/** - * Resolve template for register objects. - */ -void parser::_resolve_template() { - for (map_object& templates : _templates) { - for (map_object::iterator it = templates.begin(), end = templates.end(); - it != end; ++it) - it->second->resolve_template(templates); - } - - for (uint32_t i = 0; i < _lst_objects.size(); ++i) { - map_object& templates = _templates[i]; - for (list_object::iterator it = _lst_objects[i].begin(), - end = _lst_objects[i].end(); - it != end; ++it) { - (*it)->resolve_template(templates); - object::error_info err; - try { - (*it)->check_validity(&err); - _config_warnings += err.config_warnings; - _config_errors += err.config_errors; - } catch (std::exception const& e) { - throw error("Configuration parsing failed {}: {}", - _get_file_info(it->get()), e.what()); - } - } - } - - for (uint32_t i = 0; i < _map_objects.size(); ++i) { - map_object& templates = _templates[i]; - for (map_object::iterator it = _map_objects[i].begin(), - end = _map_objects[i].end(); - it != end; ++it) { - it->second->resolve_template(templates); - try { - object::error_info err; - it->second->check_validity(&err); - _config_warnings += err.config_warnings; - _config_errors += err.config_errors; - } catch (const std::exception& e) { - throw error("Configuration parsing failed {}: {}", - _get_file_info(it->second.get()), e.what()); - } - } - } -} - -#else -/** - * Default constructor. - * - * @param[in] read_options Configuration file reading options - * (use to skip some object type). - */ -parser::parser(unsigned int read_options) - : _read_options(read_options), - _logger{log_v2::instance().get(log_v2::CONFIG)} {} - -#endif - -#ifdef LEGACY_CONF -/** - * Parse configuration file. - * - * @param[in] path The configuration file path. - * @param[in] config The state configuration to fill. - */ -void parser::parse(const std::string& path, state& config) { - _config = &config; - - // parse the global configuration file. - _parse_global_configuration(path); - - // parse configuration files. - _apply(config.cfg_file(), &parser::_parse_object_definitions); - // parse resource files. - _apply(config.resource_file(), &parser::_parse_resource_file); - // parse configuration directories. - _apply(config.cfg_dir(), &parser::_parse_directory_configuration); - - // Apply template. - _resolve_template(); - - // Fill state. - _insert(_map_objects[object::command], config.commands()); - _insert(_map_objects[object::connector], config.connectors()); - _insert(_map_objects[object::contact], config.contacts()); - _insert(_map_objects[object::contactgroup], config.contactgroups()); - _insert(_lst_objects[object::hostdependency], config.hostdependencies()); - _insert(_lst_objects[object::hostescalation], config.hostescalations()); - _insert(_map_objects[object::hostgroup], config.hostgroups()); - _insert(_map_objects[object::host], config.hosts()); - _insert(_lst_objects[object::servicedependency], - config.servicedependencies()); - _insert(_lst_objects[object::serviceescalation], config.serviceescalations()); - _insert(_map_objects[object::servicegroup], config.servicegroups()); - _insert(_lst_objects[object::service], config.services()); - _insert(_lst_objects[object::anomalydetection], config.anomalydetections()); - _insert(_map_objects[object::timeperiod], config.timeperiods()); - _insert(_lst_objects[object::severity], config.mut_severities()); - _insert(_lst_objects[object::tag], config.mut_tags()); - - // cleanup. - _objects_info.clear(); - for (unsigned int i(0); i < _lst_objects.size(); ++i) { - _lst_objects[i].clear(); - _map_objects[i].clear(); - _templates[i].clear(); - } -} - -#else -void parser::parse(const std::string& path, State* pb_config) { - /* Parse the global configuration file. */ - auto helper = std::make_unique<state_helper>(pb_config); - _pb_helper[pb_config] = std::move(helper); - _parse_global_configuration(path, pb_config); - - // parse configuration files. - _apply(pb_config->cfg_file(), pb_config, &parser::_parse_object_definitions); - // parse resource files. - _apply(pb_config->resource_file(), pb_config, &parser::_parse_resource_file); - // parse configuration directories. - _apply(pb_config->cfg_dir(), pb_config, - &parser::_parse_directory_configuration); - - // Apply template. - _resolve_template(pb_config); - - _cleanup(pb_config); -} - -/** - * Parse the directory configuration. - * - * @param[in] path The directory path. - */ -void parser::_parse_directory_configuration(const std::string& path, - State* pb_config) { - for (auto& entry : std::filesystem::directory_iterator(path)) { - if (entry.is_regular_file() && entry.path().extension() == ".cfg") - _parse_object_definitions(entry.path().string(), pb_config); - } -} - -void parser::_parse_resource_file(const std::string& path, State* pb_config) { - _logger->info("Reading resource file '{}'", path); - - std::ifstream in(path, std::ios::in); - std::string content; - if (in) { - in.seekg(0, std::ios::end); - content.resize(in.tellg()); - in.seekg(0, std::ios::beg); - in.read(&content[0], content.size()); - in.close(); - } else - throw msg_fmt("Parsing of resource file failed: can't open file '{}': {}", - path, strerror(errno)); - - auto tab{absl::StrSplit(content, '\n')}; - int current_line = 1; - for (auto it = tab.begin(); it != tab.end(); ++it, current_line++) { - std::string_view l = absl::StripLeadingAsciiWhitespace(*it); - if (l.empty() || l[0] == '#' || l[0] == ';') - continue; - std::pair<std::string_view, std::string_view> p = - absl::StrSplit(l, absl::MaxSplits('=', 1)); - p.first = absl::StripTrailingAsciiWhitespace(p.first); - p.second = absl::StripLeadingAsciiWhitespace(p.second); - if (p.first.size() >= 3 && p.first[0] == '$' && - p.first[p.first.size() - 1] == '$') { - p.first = p.first.substr(1, p.first.size() - 2); - (*pb_config - ->mutable_users())[std::string(p.first.data(), p.first.size())] = - std::string(p.second.data(), p.second.size()); - } else - throw msg_fmt("Invalid user key '{}'", p.first); - } -} - -/** - * @brief For each type of object in the State, templates are resolved that is - * to say, children inherite from parents properties. - * - * @param pb_config The State containing all the object to handle. - */ -void parser::_resolve_template(State* pb_config) { - for (Command& c : *pb_config->mutable_commands()) - _resolve_template(_pb_helper[&c], _pb_templates[object::command]); - - for (Connector& c : *pb_config->mutable_connectors()) - _resolve_template(_pb_helper[&c], _pb_templates[object::connector]); - - for (Contact& c : *pb_config->mutable_contacts()) - _resolve_template(_pb_helper[&c], _pb_templates[object::contact]); - - for (Contactgroup& cg : *pb_config->mutable_contactgroups()) - _resolve_template(_pb_helper[&cg], _pb_templates[object::contactgroup]); - - for (Host& h : *pb_config->mutable_hosts()) - _resolve_template(_pb_helper[&h], _pb_templates[object::host]); - - for (Service& s : *pb_config->mutable_services()) - _resolve_template(_pb_helper[&s], _pb_templates[object::service]); - - for (Anomalydetection& a : *pb_config->mutable_anomalydetections()) - _resolve_template(_pb_helper[&a], _pb_templates[object::anomalydetection]); - - for (Serviceescalation& se : *pb_config->mutable_serviceescalations()) - _resolve_template(_pb_helper[&se], - _pb_templates[object::serviceescalation]); - - for (Hostescalation& he : *pb_config->mutable_hostescalations()) - _resolve_template(_pb_helper[&he], _pb_templates[object::hostescalation]); - - for (const Command& c : pb_config->commands()) - _pb_helper.at(&c)->check_validity(); - - for (const Contact& c : pb_config->contacts()) - _pb_helper.at(&c)->check_validity(); - - for (const Contactgroup& cg : pb_config->contactgroups()) - _pb_helper.at(&cg)->check_validity(); - - for (const Host& h : pb_config->hosts()) - _pb_helper.at(&h)->check_validity(); - - for (const Hostdependency& hd : pb_config->hostdependencies()) - _pb_helper.at(&hd)->check_validity(); - - for (const Hostescalation& he : pb_config->hostescalations()) - _pb_helper.at(&he)->check_validity(); - - for (const Hostgroup& hg : pb_config->hostgroups()) - _pb_helper.at(&hg)->check_validity(); - - for (const Service& s : pb_config->services()) - _pb_helper.at(&s)->check_validity(); - - for (const Hostdependency& hd : pb_config->hostdependencies()) - _pb_helper.at(&hd)->check_validity(); - - for (const Servicedependency& sd : pb_config->servicedependencies()) - _pb_helper.at(&sd)->check_validity(); - - for (const Servicegroup& sg : pb_config->servicegroups()) - _pb_helper.at(&sg)->check_validity(); - - for (const Timeperiod& t : pb_config->timeperiods()) - _pb_helper.at(&t)->check_validity(); - - for (const Anomalydetection& a : pb_config->anomalydetections()) - _pb_helper.at(&a)->check_validity(); - - for (const Tag& t : pb_config->tags()) - _pb_helper.at(&t)->check_validity(); - - for (const Servicegroup& sg : pb_config->servicegroups()) - _pb_helper.at(&sg)->check_validity(); - - for (const Severity& sv : pb_config->severities()) - _pb_helper.at(&sv)->check_validity(); - - for (const Tag& t : pb_config->tags()) - _pb_helper.at(&t)->check_validity(); - - for (const Serviceescalation& se : pb_config->serviceescalations()) - _pb_helper.at(&se)->check_validity(); - - for (const Hostescalation& he : pb_config->hostescalations()) - _pb_helper.at(&he)->check_validity(); - - for (const Connector& c : pb_config->connectors()) - _pb_helper.at(&c)->check_validity(); -} - -bool set_global(std::unique_ptr<message_helper>& helper, - const std::string_view& key, - const std::string_view& value) { - // const absl::flat_hash_map<std::string, std::string>& correspondence = - // {}) { - State* msg = static_cast<State*>(helper->mut_obj()); - const Descriptor* desc = msg->GetDescriptor(); - const FieldDescriptor* f; - const Reflection* refl; - - f = desc->FindFieldByName(std::string(key.data(), key.size())); - if (f == nullptr) { - auto it = helper->correspondence().find(key); - if (it != helper->correspondence().end()) - f = desc->FindFieldByName(it->second); - if (f == nullptr) - return false; - } - refl = msg->GetReflection(); - switch (f->type()) { - case FieldDescriptor::TYPE_BOOL: { - bool val; - if (absl::SimpleAtob(value, &val)) { - refl->SetBool(static_cast<Message*>(msg), f, val); - return true; - } else - return false; - } break; - case FieldDescriptor::TYPE_INT32: { - int32_t val; - if (absl::SimpleAtoi(value, &val)) { - refl->SetInt32(static_cast<Message*>(msg), f, val); - return true; - } else - return false; - } break; - case FieldDescriptor::TYPE_UINT32: { - uint32_t val; - if (absl::SimpleAtoi(value, &val)) { - refl->SetUInt32(static_cast<Message*>(msg), f, val); - return true; - } else - return false; - } break; - case FieldDescriptor::TYPE_UINT64: { - uint64_t val; - if (absl::SimpleAtoi(value, &val)) { - refl->SetUInt64(static_cast<Message*>(msg), f, val); - return true; - } else - return false; - } break; - case FieldDescriptor::TYPE_FLOAT: { - float val; - if (absl::SimpleAtof(value, &val)) { - refl->SetFloat(static_cast<Message*>(msg), f, val); - return true; - } else - return false; - } break; - case FieldDescriptor::TYPE_STRING: - if (f->is_repeated()) { - refl->AddString(static_cast<Message*>(msg), f, - std::string(value.data(), value.size())); - } else { - refl->SetString(static_cast<Message*>(msg), f, - std::string(value.data(), value.size())); - } - return true; - case FieldDescriptor::TYPE_MESSAGE: - if (!f->is_repeated()) { - Message* m = refl->MutableMessage(msg, f); - const Descriptor* d = m->GetDescriptor(); - - if (d && d->name() == "StringSet") { - StringSet* set = - static_cast<StringSet*>(refl->MutableMessage(msg, f)); - fill_string_group(set, value); - return true; - } else if (d && d->name() == "StringList") { - StringList* lst = - static_cast<StringList*>(refl->MutableMessage(msg, f)); - fill_string_group(lst, value); - return true; - } - } - default: - return false; - } - return true; -} - -/** - * @brief Clean the configuration: - * * remove template objects. - * - * @param pb_config - */ -void parser::_cleanup(State* pb_config) { - int i = 0; - for (auto it = pb_config->mutable_services()->begin(); - it != pb_config->mutable_services()->end();) { - if (!it->obj().register_()) { - pb_config->mutable_services()->erase(it); - it = pb_config->mutable_services()->begin() + i; - } else { - ++it; - ++i; - } - } - i = 0; - for (auto it = pb_config->mutable_anomalydetections()->begin(); - it != pb_config->mutable_anomalydetections()->end();) { - if (!it->obj().register_()) { - pb_config->mutable_anomalydetections()->erase(it); - it = pb_config->mutable_anomalydetections()->begin() + i; - } else { - ++it; - ++i; - } - } -} - -void parser::_merge(std::unique_ptr<message_helper>& msg_helper, - Message* tmpl) { - Message* msg = msg_helper->mut_obj(); - const Descriptor* desc = msg->GetDescriptor(); - const Reflection* refl = msg->GetReflection(); - std::string tmp_str; - - for (int i = 0; i < desc->field_count(); ++i) { - const FieldDescriptor* f = desc->field(i); - if (f->name() != "obj") { - /* Optional? If not defined in template, we continue. */ - const auto* oof = f->containing_oneof(); - if (oof) { - if (!refl->GetOneofFieldDescriptor(*tmpl, oof)) - continue; - } - - if ((oof && !refl->GetOneofFieldDescriptor(*msg, oof)) || - !msg_helper->changed(f->number())) { - if (f->is_repeated()) { - switch (f->cpp_type()) { - case FieldDescriptor::CPPTYPE_STRING: { - size_t count = refl->FieldSize(*tmpl, f); - for (size_t j = 0; j < count; ++j) { - const std::string& s = - refl->GetRepeatedStringReference(*tmpl, f, j, &tmp_str); - size_t count_msg = refl->FieldSize(*msg, f); - std::string tmp_str1; - bool found = false; - for (size_t k = 0; k < count_msg; ++k) { - const std::string& s1 = - refl->GetRepeatedStringReference(*msg, f, k, &tmp_str1); - if (s1 == s) { - found = true; - break; - } - } - if (!found) - refl->AddString(msg, f, s); - } - } break; - case FieldDescriptor::CPPTYPE_MESSAGE: { - size_t count = refl->FieldSize(*tmpl, f); - for (size_t j = 0; j < count; ++j) { - const Message& m = refl->GetRepeatedMessage(*tmpl, f, j); - const Descriptor* d = m.GetDescriptor(); - size_t count_msg = refl->FieldSize(*msg, f); - bool found = false; - for (size_t k = 0; k < count_msg; ++k) { - const Message& m1 = refl->GetRepeatedMessage(*msg, f, k); - const Descriptor* d1 = m1.GetDescriptor(); - if (d && d1 && d->name() == "PairUint64_32" && - d1->name() == "PairUint64_32") { - const PairUint64_32& p = - static_cast<const PairUint64_32&>(m); - const PairUint64_32& p1 = - static_cast<const PairUint64_32&>(m1); - if (p.first() == p1.first() && p.second() == p1.second()) { - found = true; - break; - } - } - } - if (!found) { - Message* new_m = refl->AddMessage(msg, f); - new_m->CopyFrom(m); - } - } - } break; - default: - _logger->error( - "Repeated type f->cpp_type = {} not managed in the " - "inheritence.", - f->cpp_type()); - assert(124 == 294); - } - } else { - switch (f->cpp_type()) { - case FieldDescriptor::CPPTYPE_STRING: - refl->SetString(msg, f, refl->GetString(*tmpl, f)); - break; - case FieldDescriptor::CPPTYPE_BOOL: - refl->SetBool(msg, f, refl->GetBool(*tmpl, f)); - break; - case FieldDescriptor::CPPTYPE_INT32: - refl->SetInt32(msg, f, refl->GetInt32(*tmpl, f)); - break; - case FieldDescriptor::CPPTYPE_UINT32: - refl->SetUInt32(msg, f, refl->GetUInt32(*tmpl, f)); - break; - case FieldDescriptor::CPPTYPE_UINT64: - refl->SetUInt64(msg, f, refl->GetUInt64(*tmpl, f)); - break; - case FieldDescriptor::CPPTYPE_ENUM: - refl->SetEnum(msg, f, refl->GetEnum(*tmpl, f)); - break; - case FieldDescriptor::CPPTYPE_MESSAGE: { - Message* m = refl->MutableMessage(msg, f); - const Descriptor* d = m->GetDescriptor(); - - if (d && d->name() == "StringSet") { - StringSet* orig_set = - static_cast<StringSet*>(refl->MutableMessage(tmpl, f)); - StringSet* set = - static_cast<StringSet*>(refl->MutableMessage(msg, f)); - if (set->additive()) { - for (auto& v : orig_set->data()) { - bool found = false; - for (auto& s : *set->mutable_data()) { - if (s == v) { - found = true; - break; - } - } - if (!found) - set->add_data(v); - } - } else if (set->data().empty()) - *set->mutable_data() = orig_set->data(); - - } else if (d && d->name() == "StringList") { - StringList* orig_lst = - static_cast<StringList*>(refl->MutableMessage(tmpl, f)); - StringList* lst = - static_cast<StringList*>(refl->MutableMessage(msg, f)); - if (lst->additive()) { - for (auto& v : orig_lst->data()) - lst->add_data(v); - } else if (lst->data().empty()) - *lst->mutable_data() = orig_lst->data(); - } - } break; - - default: - _logger->error("Entry '{}' of type {} not managed in merge", - f->name(), f->type_name()); - assert(123 == 293); - } - } - } - } - } -} - -void parser::_resolve_template(std::unique_ptr<message_helper>& msg_helper, - const pb_map_object& tmpls) { - if (msg_helper->resolved()) - return; - Message* msg = msg_helper->mut_obj(); - - msg_helper->resolve(); - const Descriptor* desc = msg->GetDescriptor(); - const FieldDescriptor* f = desc->FindFieldByName("obj"); - const Reflection* refl = msg->GetReflection(); - if (!f) - return; - - Object* obj = static_cast<Object*>(refl->MutableMessage(msg, f)); - for (const std::string& u : obj->use()) { - auto it = tmpls.find(u); - if (it == tmpls.end()) - throw msg_fmt("Cannot merge object of type '{}'", u); - _resolve_template(_pb_helper[it->second.get()], tmpls); - _merge(msg_helper, it->second.get()); - } -} - -/** - * @brief Return true if the register flag is enabled in the configuration - * object. - * - * @param msg A configuration object as Protobuf message. - * - * @return True if it has to be registered, false otherwise. - */ -bool parser::_is_registered(const Message& msg) const { - const Descriptor* desc = msg.GetDescriptor(); - const Reflection* refl = msg.GetReflection(); - std::string tmpl; - const FieldDescriptor* f = desc->FindFieldByName("obj"); - if (f) { - const Object& obj = static_cast<const Object&>(refl->GetMessage(msg, f)); - return obj.register_(); - } - return false; -} - -/** - * @brief Set the value given as a string to the object key. If the key does - * not exist, the correspondence table may be used to find a replacement of - * the key. The function converts the value to the appropriate type. - * - * Another important point is that many configuration objects contain the Object - * obj message (something like an inheritance). This message contains three - * fields name, use and register that are important for templating. If keys are - * one of these names, the function tries to work directly with the obj message. - * - * @tparam T The type of the message containing the object key. - * @param msg The message containing the object key. - * @param key The key to localize the object to set. - * @param value The value as string that will be converted to the good type. - * @param correspondence A hash table giving traductions from keys to others. - * If a key fails, correspondence is used to find a new replacement key. - * - * @return true on success. - */ -bool set(std::unique_ptr<message_helper>& helper, - const std::string_view& key, - const std::string_view& value) { - Message* msg = helper->mut_obj(); - const Descriptor* desc = msg->GetDescriptor(); - const FieldDescriptor* f; - const Reflection* refl; - - /* Cases where we have to work on the obj Object (the parent object) */ - if (key == "name" || key == "register" || key == "use") { - f = desc->FindFieldByName("obj"); - if (f) { - refl = msg->GetReflection(); - Object* obj = static_cast<Object*>(refl->MutableMessage(msg, f)); - - /* Optimization to avoid a new string comparaison */ - switch (key[0]) { - case 'n': // name - obj->set_name(std::string(value.data(), value.size())); - break; - case 'r': { // register - bool value_b; - if (!absl::SimpleAtob(value, &value_b)) - return false; - else - obj->set_register_(value_b); - } break; - case 'u': { // use - obj->mutable_use()->Clear(); - auto arr = absl::StrSplit(value, ','); - for (auto& t : arr) { - std::string v{absl::StripAsciiWhitespace(t)}; - obj->mutable_use()->Add(std::move(v)); - } - } break; - } - return true; - } - } - - f = desc->FindFieldByName(std::string(key.data(), key.size())); - if (f == nullptr) { - auto it = helper->correspondence().find(key); - if (it != helper->correspondence().end()) - f = desc->FindFieldByName(it->second); - if (f == nullptr) - return false; - } - refl = msg->GetReflection(); - switch (f->type()) { - case FieldDescriptor::TYPE_BOOL: { - bool val; - if (absl::SimpleAtob(value, &val)) { - refl->SetBool(static_cast<Message*>(msg), f, val); - helper->set_changed(f->number()); - return true; - } else - return false; - } break; - case FieldDescriptor::TYPE_INT32: { - int32_t val; - if (absl::SimpleAtoi(value, &val)) { - refl->SetInt32(static_cast<Message*>(msg), f, val); - helper->set_changed(f->number()); - return true; - } else - return false; - } break; - case FieldDescriptor::TYPE_UINT32: { - uint32_t val; - if (absl::SimpleAtoi(value, &val)) { - refl->SetUInt32(static_cast<Message*>(msg), f, val); - helper->set_changed(f->number()); - return true; - } else - return false; - } break; - case FieldDescriptor::TYPE_UINT64: { - uint64_t val; - if (absl::SimpleAtoi(value, &val)) { - refl->SetUInt64(static_cast<Message*>(msg), f, val); - helper->set_changed(f->number()); - return true; - } else - return false; - } break; - case FieldDescriptor::TYPE_DOUBLE: { - double val; - if (absl::SimpleAtod(value, &val)) { - refl->SetDouble(static_cast<Message*>(msg), f, val); - helper->set_changed(f->number()); - return true; - } else - return false; - } break; - case FieldDescriptor::TYPE_STRING: - if (f->is_repeated()) { - refl->AddString(static_cast<Message*>(msg), f, - std::string(value.data(), value.size())); - } else { - refl->SetString(static_cast<Message*>(msg), f, - std::string(value.data(), value.size())); - } - helper->set_changed(f->number()); - return true; - case FieldDescriptor::TYPE_MESSAGE: - if (!f->is_repeated()) { - Message* m = refl->MutableMessage(msg, f); - const Descriptor* d = m->GetDescriptor(); - - if (d && d->name() == "StringSet") { - StringSet* set = - static_cast<StringSet*>(refl->MutableMessage(msg, f)); - fill_string_group(set, value); - helper->set_changed(f->number()); - return true; - } else if (d && d->name() == "StringList") { - StringList* lst = - static_cast<StringList*>(refl->MutableMessage(msg, f)); - fill_string_group(lst, value); - helper->set_changed(f->number()); - return true; - } - } - default: - return false; - } - return true; -} - -void parser::_parse_global_configuration(const std::string& path, - State* pb_config) { - _logger->info("Reading main configuration file '{}'.", path); - - std::ifstream in(path, std::ios::in); - std::string content; - if (in) { - in.seekg(0, std::ios::end); - content.resize(in.tellg()); - in.seekg(0, std::ios::beg); - in.read(&content[0], content.size()); - in.close(); - } else - throw msg_fmt( - "Parsing of global configuration failed: can't open file '{}': {}", - path, strerror(errno)); - - pb_config->set_cfg_main(path); - _current_line = 0; - _current_path = path; - - auto tab{absl::StrSplit(content, '\n')}; - auto& cfg_helper = _pb_helper[pb_config]; - for (auto it = tab.begin(); it != tab.end(); ++it) { - std::string_view l = absl::StripAsciiWhitespace(*it); - if (l.empty() || l[0] == '#') - continue; - std::pair<std::string_view, std::string_view> p = - absl::StrSplit(l, absl::MaxSplits('=', 1)); - p.first = absl::StripTrailingAsciiWhitespace(p.first); - p.second = absl::StripLeadingAsciiWhitespace(p.second); - bool retval = false; - /* particular cases with hook */ - retval = cfg_helper->hook(p.first, p.second); - if (!retval) { - if (!set_global(cfg_helper, p.first, p.second)) - _logger->error("Unable to parse '{}' key with value '{}'", p.first, - p.second); - } - } -} - -/** - * @brief Parse objects files (services.cfg, hosts.cfg, timeperiods.cfg... - * - * This function almost uses protobuf reflection to set values but it may fail - * because of the syntax used in these files that can be a little different - * from the message format. - * - * Two mechanisms are used to complete the reflection. - * * A hastable <string, string> named correspondence is used in case of several - * keys to access to the same value. This is, for example, the case for - * host_id which is historically also named _HOST_ID. - * * A std::function<bool(string_view_string_view) can also be defined in - * several cases to make special stuffs. For example, we use it for timeperiod - * object to set its timeranges. - * - * @param path The file to parse. - * @param pb_config The configuration to complete. - */ -void parser::_parse_object_definitions(const std::string& path, - State* pb_config) { - _logger->info("Processing object config file '{}'", path); - - std::ifstream in(path, std::ios::in); - std::string content; - if (in) { - in.seekg(0, std::ios::end); - content.resize(in.tellg()); - in.seekg(0, std::ios::beg); - in.read(&content[0], content.size()); - in.close(); - } else - throw msg_fmt( - "Parsing of object definition failed: can't open file '{}': {}", path, - strerror(errno)); - - auto tab{absl::StrSplit(content, '\n')}; - std::string ll; - bool append_to_previous_line = false; - std::unique_ptr<Message> msg; - std::unique_ptr<message_helper> msg_helper; - - int current_line = 1; - std::string type; - - for (auto it = tab.begin(); it != tab.end(); ++it, current_line++) { - std::string_view l = absl::StripAsciiWhitespace(*it); - if (l.empty() || l[0] == '#' || l[0] == ';') - continue; - - /* Multiline */ - if (append_to_previous_line) { - if (l[l.size() - 1] == '\\') { - ll.append(l.data(), l.size() - 1); - continue; - } else { - ll.append(l.data(), l.size()); - append_to_previous_line = false; - l = ll; - } - } else if (l[l.size() - 1] == '\\') { - ll = std::string(l.data(), l.size() - 1); - append_to_previous_line = true; - continue; - } - - if (msg) { - if (l.empty()) - continue; - /* is it time to close the definition? */ - if (l == "}") { - const Descriptor* desc = msg->GetDescriptor(); - const FieldDescriptor* f = desc->FindFieldByName("obj"); - const Reflection* refl = msg->GetReflection(); - if (f) { - const Object& obj = - *static_cast<const Object*>(&refl->GetMessage(*msg, f)); - auto otype = msg_helper->otype(); - _pb_helper[msg.get()] = std::move(msg_helper); - if (!obj.name().empty()) { - pb_map_object& tmpl = _pb_templates[otype]; - auto it = tmpl.find(obj.name()); - if (it != tmpl.end()) - throw msg_fmt("Parsing of '{}' failed {}: {} already exists", - type, "file_info" /*_get_file_info(obj.get()) */, - obj.name()); - if (!obj.register_()) - tmpl[obj.name()] = std::move(msg); - else { - auto copy = std::unique_ptr<Message>(msg->New()); - copy->CopyFrom(*msg); - _pb_helper[copy.get()] = - message_helper::clone(*_pb_helper[msg.get()], copy.get()); - tmpl[obj.name()] = std::move(copy); - } - } - if (obj.register_()) { - switch (otype) { - case message_helper::contact: - pb_config->mutable_contacts()->AddAllocated( - static_cast<Contact*>(msg.release())); - break; - case message_helper::host: - pb_config->mutable_hosts()->AddAllocated( - static_cast<Host*>(msg.release())); - break; - case message_helper::service: - pb_config->mutable_services()->AddAllocated( - static_cast<Service*>(msg.release())); - break; - case message_helper::anomalydetection: - pb_config->mutable_anomalydetections()->AddAllocated( - static_cast<Anomalydetection*>(msg.release())); - break; - case message_helper::hostdependency: - pb_config->mutable_hostdependencies()->AddAllocated( - static_cast<Hostdependency*>(msg.release())); - break; - case message_helper::servicedependency: - pb_config->mutable_servicedependencies()->AddAllocated( - static_cast<Servicedependency*>(msg.release())); - break; - case message_helper::timeperiod: - pb_config->mutable_timeperiods()->AddAllocated( - static_cast<Timeperiod*>(msg.release())); - break; - case message_helper::command: - pb_config->mutable_commands()->AddAllocated( - static_cast<Command*>(msg.release())); - break; - case message_helper::hostgroup: - pb_config->mutable_hostgroups()->AddAllocated( - static_cast<Hostgroup*>(msg.release())); - break; - case message_helper::servicegroup: - pb_config->mutable_servicegroups()->AddAllocated( - static_cast<Servicegroup*>(msg.release())); - break; - case message_helper::tag: - pb_config->mutable_tags()->AddAllocated( - static_cast<Tag*>(msg.release())); - break; - case message_helper::contactgroup: - pb_config->mutable_contactgroups()->AddAllocated( - static_cast<Contactgroup*>(msg.release())); - break; - case message_helper::connector: - pb_config->mutable_connectors()->AddAllocated( - static_cast<Connector*>(msg.release())); - break; - case message_helper::severity: - pb_config->mutable_severities()->AddAllocated( - static_cast<Severity*>(msg.release())); - break; - case message_helper::serviceescalation: - pb_config->mutable_serviceescalations()->AddAllocated( - static_cast<Serviceescalation*>(msg.release())); - break; - case message_helper::hostescalation: - pb_config->mutable_hostescalations()->AddAllocated( - static_cast<Hostescalation*>(msg.release())); - break; - default: - _logger->critical("Attempt to add an object of unknown type"); - } - } - } - msg = nullptr; - } else { - /* Main part where keys/values are read */ - /* ------------------------------------ */ - size_t pos = l.find_first_of(" \t"); - std::string_view key = l.substr(0, pos); - if (pos != std::string::npos) { - l.remove_prefix(pos); - l = absl::StripLeadingAsciiWhitespace(l); - } else - l = {}; - - bool retval = false; - /* particular cases with hook */ - retval = msg_helper->hook(key, l); - - if (!retval) { - /* Classical part */ - if (!set(msg_helper, key, l)) { - if (!msg_helper->insert_customvariable(key, l)) - throw msg_fmt( - "Unable to parse '{}' key with value '{}' in message of type " - "'{}'", - key, l, type); - } - } - } - } else { - if (!absl::StartsWith(l, "define") || !std::isspace(l[6])) - throw msg_fmt( - "Parsing of object definition failed in file '{}' at line {}: " - "Unexpected start definition", - path, current_line); - /* Let's remove the first 6 characters ("define") */ - l = absl::StripLeadingAsciiWhitespace(l.substr(6)); - if (l.empty() || l[l.size() - 1] != '{') - throw msg_fmt( - "Parsing of object definition failed in file '{}' at line {}; " - "unexpected start definition", - path, current_line); - l = absl::StripTrailingAsciiWhitespace(l.substr(0, l.size() - 1)); - type = std::string(l.data(), l.size()); - if (type == "contact") { - msg = std::make_unique<Contact>(); - msg_helper = - std::make_unique<contact_helper>(static_cast<Contact*>(msg.get())); - } else if (type == "host") { - msg = std::make_unique<Host>(); - msg_helper = - std::make_unique<host_helper>(static_cast<Host*>(msg.get())); - } else if (type == "service") { - msg = std::make_unique<Service>(); - msg_helper = - std::make_unique<service_helper>(static_cast<Service*>(msg.get())); - } else if (type == "anomalydetection") { - msg = std::make_unique<Anomalydetection>(); - msg_helper = std::make_unique<anomalydetection_helper>( - static_cast<Anomalydetection*>(msg.get())); - } else if (type == "hostdependency") { - msg = std::make_unique<Hostdependency>(); - msg_helper = std::make_unique<hostdependency_helper>( - static_cast<Hostdependency*>(msg.get())); - } else if (type == "servicedependency") { - msg = std::make_unique<Servicedependency>(); - msg_helper = std::make_unique<servicedependency_helper>( - static_cast<Servicedependency*>(msg.get())); - } else if (type == "timeperiod") { - msg = std::make_unique<Timeperiod>(); - msg_helper = std::make_unique<timeperiod_helper>( - static_cast<Timeperiod*>(msg.get())); - } else if (type == "command") { - msg = std::make_unique<Command>(); - msg_helper = - std::make_unique<command_helper>(static_cast<Command*>(msg.get())); - } else if (type == "hostgroup") { - msg = std::make_unique<Hostgroup>(); - msg_helper = std::make_unique<hostgroup_helper>( - static_cast<Hostgroup*>(msg.get())); - } else if (type == "servicegroup") { - msg = std::make_unique<Servicegroup>(); - msg_helper = std::make_unique<servicegroup_helper>( - static_cast<Servicegroup*>(msg.get())); - } else if (type == "tag") { - msg = std::make_unique<Tag>(); - msg_helper = std::make_unique<tag_helper>(static_cast<Tag*>(msg.get())); - } else if (type == "contactgroup") { - msg = std::make_unique<Contactgroup>(); - msg_helper = std::make_unique<contactgroup_helper>( - static_cast<Contactgroup*>(msg.get())); - } else if (type == "connector") { - msg = std::make_unique<Connector>(); - msg_helper = std::make_unique<connector_helper>( - static_cast<Connector*>(msg.get())); - } else if (type == "severity") { - msg = std::make_unique<Severity>(); - msg_helper = std::make_unique<severity_helper>( - static_cast<Severity*>(msg.get())); - } else if (type == "serviceescalation") { - msg = std::make_unique<Serviceescalation>(); - msg_helper = std::make_unique<serviceescalation_helper>( - static_cast<Serviceescalation*>(msg.get())); - } else if (type == "hostescalation") { - msg = std::make_unique<Hostescalation>(); - msg_helper = std::make_unique<hostescalation_helper>( - static_cast<Hostescalation*>(msg.get())); - } else { - _logger->error("Type '{}' not yet supported by the parser", type); - assert(1 == 18); - } - } - } -} - -#endif - -/** - * Add object into the list. - * - * @param[in] obj The object to add into the list. - */ -void parser::_add_object(object_ptr obj) { - if (obj->should_register()) - (this->*_store[obj->type()])(obj); -} - -/** - * Add template into the list. - * - * @param[in] obj The tempalte to add into the list. - */ -void parser::_add_template(object_ptr obj) { - std::string const& name(obj->name()); - if (name.empty()) - throw msg_fmt("Parsing of {} failed {}: Property 'name' is missing", - obj->type_name(), _get_file_info(obj.get())); - map_object& tmpl(_templates[obj->type()]); - if (tmpl.find(name) != tmpl.end()) - throw msg_fmt("Parsing of {} failed {}: '{}' already exists", - obj->type_name(), _get_file_info(obj.get()), name); - tmpl[name] = obj; -} - -/** - * Get the file information. - * - * @param[in] obj The object to get file informations. - * - * @return The file informations object. - */ -file_info const& parser::_get_file_info(object* obj) const { - if (obj) { - std::unordered_map<object*, file_info>::const_iterator it( - _objects_info.find(obj)); - if (it != _objects_info.end()) - return it->second; - } - throw msg_fmt( - "Parsing failed: Object not found into the file information cache"); -} - -/** - * Build the hosts list with hostgroups. - * - * @param[in] hostgroups The hostgroups. - * @param[in,out] hosts The host list to fill. - */ -void parser::_get_hosts_by_hostgroups(hostgroup const& hostgroups, - list_host& hosts) { - _get_objects_by_list_name(hostgroups.members(), _map_objects[object::host], - hosts); -} - -/** - * Build the hosts list with list of hostgroups. - * - * @param[in] hostgroups The hostgroups list. - * @param[in,out] hosts The host list to fill. - */ -void parser::_get_hosts_by_hostgroups_name(set_string const& lst_group, - list_host& hosts) { - map_object& gl_hostgroups(_map_objects[object::hostgroup]); - for (set_string::const_iterator it(lst_group.begin()), end(lst_group.end()); - it != end; ++it) { - map_object::iterator it_hostgroups(gl_hostgroups.find(*it)); - if (it_hostgroups != gl_hostgroups.end()) - _get_hosts_by_hostgroups( - *static_cast<configuration::hostgroup*>(it_hostgroups->second.get()), - hosts); - } -} - -/** - * Build the object list with list of object name. - * - * @param[in] lst The object name list. - * @param[in] objects The object map to find object name. - * @param[in,out] out The list to fill. - */ -template <typename T> -void parser::_get_objects_by_list_name(set_string const& lst, - map_object& objects, - std::list<T>& out) { - for (set_string::const_iterator it(lst.begin()), end(lst.end()); it != end; - ++it) { - map_object::iterator it_obj(objects.find(*it)); - if (it_obj != objects.end()) - out.push_back(*static_cast<T*>(it_obj->second.get())); - } -} - -/** - * Insert objects into type T list and sort the new list by object id. - * - * @param[in] from The objects source. - * @param[out] to The objects destination. - */ -template <typename T> -void parser::_insert(list_object const& from, std::set<T>& to) { - for (list_object::const_iterator it(from.begin()), end(from.end()); it != end; - ++it) - to.insert(*static_cast<T const*>(it->get())); -} - -/** - * Insert objects into type T list and sort the new list by object id. - * - * @param[in] from The objects source. - * @param[out] to The objects destination. - */ -template <typename T> -void parser::_insert(map_object const& from, std::set<T>& to) { - for (map_object::const_iterator it(from.begin()), end(from.end()); it != end; - ++it) - to.insert(*static_cast<T*>(it->second.get())); -} - -/** - * Get the map object type name. - * - * @param[in] objects The map object. - * - * @return The type name. - */ -std::string const& parser::_map_object_type(map_object const& objects) const - throw() { - static std::string const empty(""); - map_object::const_iterator it(objects.begin()); - if (it == objects.end()) - return empty; - return it->second->type_name(); -} - -/** - * Store object into the list. - * - * @param[in] obj The object to store. - */ -void parser::_store_into_list(object_ptr obj) { - _lst_objects[obj->type()].push_back(obj); -} - -/** - * Store object into the map. - * - * @param[in] obj The object to store. - */ -template <typename T, std::string const& (T::*ptr)() const throw()> -void parser::_store_into_map(object_ptr obj) { - std::shared_ptr<T> real(std::static_pointer_cast<T>(obj)); - map_object::iterator it(_map_objects[obj->type()].find((real.get()->*ptr)())); - if (it != _map_objects[obj->type()].end()) - throw error("Parsing of {} failed {}: {} already exists", obj->type_name(), - _get_file_info(obj.get()), obj->name()); - _map_objects[obj->type()][(real.get()->*ptr)()] = real; -} diff --git a/common/engine_conf/parser.hh b/common/engine_conf/parser.hh index 72e6c255552..e96202c2c72 100644 --- a/common/engine_conf/parser.hh +++ b/common/engine_conf/parser.hh @@ -21,6 +21,7 @@ #include <fstream> #include "common/engine_conf/file_info.hh" +#include "common/engine_conf/message_helper.hh" #include "state_helper.hh" // #include "host.hh" @@ -49,33 +50,13 @@ using pb_map_helper = class parser { std::shared_ptr<spdlog::logger> _logger; - enum object_type { - command = 0, - connector = 1, - contact = 2, - contactgroup = 3, - host = 4, - hostdependency = 5, - hostescalation = 6, - hostextinfo = 7, - hostgroup = 8, - service = 9, - servicedependency = 10, - serviceescalation = 11, - serviceextinfo = 12, - servicegroup = 13, - timeperiod = 14, - anomalydetection = 15, - severity = 16, - tag = 17, - }; - /** * @brief An array of pb_map_objects. At index object_type::command we get all * the templates of commands, at index object_type::service we get all the * templates of services, etc. */ - std::array<pb_map_object, 19> _pb_templates; + std::array<pb_map_object, message_helper::object_type::nb_types> + _pb_templates; /** * @brief The map of helpers of all the configuration objects parsed by this diff --git a/common/engine_conf/parser.hh.old b/common/engine_conf/parser.hh.old deleted file mode 100644 index 2ea3d7b5033..00000000000 --- a/common/engine_conf/parser.hh.old +++ /dev/null @@ -1,162 +0,0 @@ -/** - * Copyright 2011-2013,2017 Centreon - * - * This file is part of Centreon Engine. - * - * Centreon Engine is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License version 2 - * as published by the Free Software Foundation. - * - * Centreon Engine 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 Centreon Engine. If not, see - * <http://www.gnu.org/licenses/>. - */ - -#ifndef CCE_CONFIGURATION_PARSER_HH -#define CCE_CONFIGURATION_PARSER_HH - -#include "common/engine_conf/state.pb.h" - -#include <fstream> - -#include "common/log_v2/log_v2.hh" -#include "message_helper.hh" - -namespace com::centreon::engine { - -namespace configuration { -using Message = ::google::protobuf::Message; -using pb_map_object = - absl::flat_hash_map<std::string, std::unique_ptr<Message>>; -using pb_map_helper = - absl::flat_hash_map<Message*, std::unique_ptr<message_helper>>; - -class parser { - uint32_t _config_warnings = 0; - uint32_t _config_errors = 0; -#ifndef LEGACY_CONF - void _cleanup(State* pb_config); - void _check_validity(const Message& msg, const char* const* mandatory) const; - bool _is_registered(const Message& msg) const; -#endif - - public: - enum read_options { - read_commands = (1 << 0), - read_connector = (1 << 1), - read_contact = (1 << 2), - read_contactgroup = (1 << 3), - read_host = (1 << 4), - read_hostdependency = (1 << 5), - read_hostescalation = (1 << 6), - read_hostgroup = (1 << 8), - read_hostgroupescalation = (1 << 9), - read_service = (1 << 10), - read_servicedependency = (1 << 11), - read_serviceescalation = (1 << 12), - read_servicegroup = (1 << 14), - read_timeperiod = (1 << 15), - read_all = (~0) - }; - - parser(unsigned int read_options = read_all); - ~parser() noexcept = default; -#ifdef LEGACY_CONF - void parse(const std::string& path, state& config); -#else - void parse(const std::string& path, State* pb_config); -#endif - - uint32_t config_warnings() const { - return _config_warnings; - } - uint32_t config_errors() const { - return _config_errors; - } - - private: - typedef void (parser::*store)(object_ptr obj); - - parser(parser const& right); - parser& operator=(parser const& right); - void _add_object(object_ptr obj); - void _add_template(object_ptr obj); - template <typename T> - void _apply(const T& lst, void (parser::*pfunc)(const std::string&)) { - for (auto& f : lst) - (this->*pfunc)(f); - } - - template <typename S, typename L> - void _apply(const L& lst, - S* state, - void (parser::*pfunc)(const std::string&, S*)) { - for (auto& f : lst) - (this->*pfunc)(f, state); - } - - file_info const& _get_file_info(object* obj) const; - void _get_hosts_by_hostgroups(hostgroup const& hostgroups, list_host& hosts); - void _get_hosts_by_hostgroups_name(set_string const& lst_group, - list_host& hosts); - template <typename T> - void _get_objects_by_list_name(set_string const& lst, - map_object& objects, - std::list<T>& out); - - template <typename T> - static void _insert(list_object const& from, std::set<T>& to); - template <typename T> - static void _insert(map_object const& from, std::set<T>& to); - std::string const& _map_object_type(map_object const& objects) const throw(); - void _parse_directory_configuration(std::string const& path); -#ifdef LEGACY_CONF - void _parse_object_definitions(const std::string& path); - void _parse_resource_file(std::string const& path); - void _resolve_template(); - void _parse_global_configuration(std::string const& path); -#else - void _parse_directory_configuration(const std::string& path, - State* pb_config); - void _parse_object_definitions(const std::string& path, State* pb_config); - void _parse_resource_file(const std::string& path, State* pb_config); - void _resolve_template(State* pb_config); - void _resolve_template(std::unique_ptr<message_helper>& msg_helper, - const pb_map_object& tmpls); - void _merge(std::unique_ptr<message_helper>& msg_helper, Message* tmpl); - void _parse_global_configuration(std::string const& path, State* pb_config); -#endif - void _store_into_list(object_ptr obj); - template <typename T, std::string const& (T::*ptr)() const throw()> - void _store_into_map(object_ptr obj); - -#ifdef LEGACY_CONF - state* _config; -#endif - unsigned int _current_line; - std::string _current_path; - std::array<list_object, 19> _lst_objects; - std::array<map_object, 19> _map_objects; - std::unordered_map<object*, file_info> _objects_info; - unsigned int _read_options; - static store _store[]; - std::array<map_object, 19> _templates; - -#ifndef LEGACY_CONF - std::array<pb_map_object, 19> _pb_templates; - pb_map_helper _pb_helper; -#endif - - /* Configuration Logger */ - std::shared_ptr<spdlog::logger> _logger; -}; -} // namespace configuration - -} // namespace com::centreon::engine - -#endif // !CCE_CONFIGURATION_PARSER_HH diff --git a/common/engine_conf/service_helper.cc b/common/engine_conf/service_helper.cc index 206721bbb5b..ba400767491 100644 --- a/common/engine_conf/service_helper.cc +++ b/common/engine_conf/service_helper.cc @@ -19,6 +19,7 @@ #include "service_helper.hh" #include "com/centreon/exceptions/msg_fmt.hh" +#include "common/engine_conf/message_helper.hh" using com::centreon::exceptions::msg_fmt; @@ -46,7 +47,7 @@ service_helper::service_helper(Service* obj) {"passive_checks_enabled", "checks_passive"}, {"severity", "severity_id"}, }, - 50) { + Service::descriptor()->field_count()) { _init(); } @@ -58,6 +59,8 @@ service_helper::service_helper(Service* obj) */ bool service_helper::hook(std::string_view key, const std::string_view& value) { Service* obj = static_cast<Service*>(mut_obj()); + /* Since we use key to get back the good key value, it is faster to give key + * by copy to the method. We avoid one key allocation... */ key = validate_key(key); if (key == "contactgroups") { fill_string_group(obj->mutable_contactgroups(), value); @@ -104,32 +107,11 @@ bool service_helper::hook(std::string_view key, const std::string_view& value) { return true; } else if (key == "notification_options") { uint16_t options(action_svc_none); - auto values = absl::StrSplit(value, ','); - for (auto it = values.begin(); it != values.end(); ++it) { - std::string_view v = absl::StripAsciiWhitespace(*it); - if (v == "u" || v == "unknown") - options |= action_svc_unknown; - else if (v == "w" || v == "warning") - options |= action_svc_warning; - else if (v == "c" || v == "critical") - options |= action_svc_critical; - else if (v == "r" || v == "recovery") - options |= action_svc_ok; - else if (v == "f" || v == "flapping") - options |= action_svc_flapping; - else if (v == "s" || v == "downtime") - options |= action_svc_downtime; - else if (v == "n" || v == "none") - options = action_svc_none; - else if (v == "a" || v == "all") - options = action_svc_unknown | action_svc_warning | - action_svc_critical | action_svc_ok | action_svc_flapping | - action_svc_downtime; - else - return false; - } - obj->set_notification_options(options); - return true; + if (fill_service_notification_options(&options, value)) { + obj->set_notification_options(options); + return true; + } else + return false; } else if (key == "servicegroups") { fill_string_group(obj->mutable_servicegroups(), value); return true; diff --git a/common/engine_conf/servicedependency_helper.cc b/common/engine_conf/servicedependency_helper.cc index 4785cd32e31..91e9c44226d 100644 --- a/common/engine_conf/servicedependency_helper.cc +++ b/common/engine_conf/servicedependency_helper.cc @@ -19,6 +19,7 @@ #include "common/engine_conf/servicedependency_helper.hh" #include "com/centreon/exceptions/msg_fmt.hh" +#include "common/engine_conf/state.pb.h" using com::centreon::exceptions::msg_fmt; @@ -65,7 +66,7 @@ servicedependency_helper::servicedependency_helper(Servicedependency* obj) {"execution_failure_criteria", "execution_failure_options"}, {"notification_failure_criteria", "notification_failure_options"}, }, - 15) { + Servicedependency::descriptor()->field_count()) { _init(); } @@ -78,6 +79,8 @@ servicedependency_helper::servicedependency_helper(Servicedependency* obj) bool servicedependency_helper::hook(std::string_view key, const std::string_view& value) { Servicedependency* obj = static_cast<Servicedependency*>(mut_obj()); + /* Since we use key to get back the good key value, it is faster to give key + * by copy to the method. We avoid one key allocation... */ key = validate_key(key); if (key == "execution_failure_options" || diff --git a/common/engine_conf/serviceescalation_helper.cc b/common/engine_conf/serviceescalation_helper.cc index 2f50c09621b..0247bb5be80 100644 --- a/common/engine_conf/serviceescalation_helper.cc +++ b/common/engine_conf/serviceescalation_helper.cc @@ -19,6 +19,7 @@ #include "common/engine_conf/serviceescalation_helper.hh" #include "com/centreon/exceptions/msg_fmt.hh" +#include "common/engine_conf/state.pb.h" using com::centreon::exceptions::msg_fmt; @@ -51,7 +52,7 @@ serviceescalation_helper::serviceescalation_helper(Serviceescalation* obj) {"hostgroup_name", "hostgroups"}, {"contact_groups", "contactgroups"}, }, - 12) { + Serviceescalation::descriptor()->field_count()) { _init(); } @@ -64,6 +65,8 @@ serviceescalation_helper::serviceescalation_helper(Serviceescalation* obj) bool serviceescalation_helper::hook(std::string_view key, const std::string_view& value) { Serviceescalation* obj = static_cast<Serviceescalation*>(mut_obj()); + /* Since we use key to get back the good key value, it is faster to give key + * by copy to the method. We avoid one key allocation... */ key = validate_key(key); if (key == "escalation_options") { diff --git a/common/engine_conf/servicegroup_helper.cc b/common/engine_conf/servicegroup_helper.cc index d50ee62299c..0ff2c23560f 100644 --- a/common/engine_conf/servicegroup_helper.cc +++ b/common/engine_conf/servicegroup_helper.cc @@ -19,6 +19,7 @@ #include "common/engine_conf/servicegroup_helper.hh" #include "com/centreon/exceptions/msg_fmt.hh" +#include "common/engine_conf/state.pb.h" using com::centreon::exceptions::msg_fmt; @@ -31,7 +32,10 @@ namespace com::centreon::engine::configuration { * not the owner of this object. */ servicegroup_helper::servicegroup_helper(Servicegroup* obj) - : message_helper(object_type::servicegroup, obj, {}, 10) { + : message_helper(object_type::servicegroup, + obj, + {}, + Servicegroup::descriptor()->field_count()) { _init(); } @@ -44,6 +48,8 @@ servicegroup_helper::servicegroup_helper(Servicegroup* obj) bool servicegroup_helper::hook(std::string_view key, const std::string_view& value) { Servicegroup* obj = static_cast<Servicegroup*>(mut_obj()); + /* Since we use key to get back the good key value, it is faster to give key + * by copy to the method. We avoid one key allocation... */ key = validate_key(key); if (key == "members") { fill_pair_string_group(obj->mutable_members(), value); diff --git a/common/engine_conf/severity_helper.cc b/common/engine_conf/severity_helper.cc index b973f3b8527..086415045d0 100644 --- a/common/engine_conf/severity_helper.cc +++ b/common/engine_conf/severity_helper.cc @@ -19,6 +19,7 @@ #include "common/engine_conf/severity_helper.hh" #include "com/centreon/exceptions/msg_fmt.hh" +#include "common/engine_conf/state.pb.h" using com::centreon::exceptions::msg_fmt; @@ -39,7 +40,7 @@ severity_helper::severity_helper(Severity* obj) {"severity_icon_id", "icon_id"}, {"severity_type", "type"}, }, - 6) { + Severity::descriptor()->field_count()) { _init(); } @@ -52,6 +53,8 @@ severity_helper::severity_helper(Severity* obj) bool severity_helper::hook(std::string_view key, const std::string_view& value) { Severity* obj = static_cast<Severity*>(mut_obj()); + /* Since we use key to get back the good key value, it is faster to give key + * by copy to the method. We avoid one key allocation... */ key = validate_key(key); if (key == "id" || key == "severity_id") { diff --git a/common/engine_conf/state_helper.cc b/common/engine_conf/state_helper.cc index 0b01eb5716e..a48afd55e44 100644 --- a/common/engine_conf/state_helper.cc +++ b/common/engine_conf/state_helper.cc @@ -57,7 +57,7 @@ state_helper::state_helper(State* obj) {"xcddefault_comment_file", "comment_file"}, {"xdddefault_downtime_file", "downtime_file"}, }, - 2) { + State::descriptor()->field_count()) { _init(); } @@ -69,6 +69,8 @@ state_helper::state_helper(State* obj) */ bool state_helper::hook(std::string_view key, const std::string_view& value) { State* obj = static_cast<State*>(mut_obj()); + /* Since we use key to get back the good key value, it is faster to give key + * by copy to the method. We avoid one key allocation... */ key = validate_key(key); if (key.substr(0, 10) == "log_level_") { diff --git a/common/engine_conf/tag_helper.cc b/common/engine_conf/tag_helper.cc index 947ca521588..bb5cfecc6d4 100644 --- a/common/engine_conf/tag_helper.cc +++ b/common/engine_conf/tag_helper.cc @@ -37,7 +37,7 @@ tag_helper::tag_helper(Tag* obj) {"tag_id", "id"}, {"tag_type", "type"}, }, - 4) { + Tag::descriptor()->field_count()) { _init(); } @@ -49,6 +49,8 @@ tag_helper::tag_helper(Tag* obj) */ bool tag_helper::hook(std::string_view key, const std::string_view& value) { Tag* obj = static_cast<Tag*>(mut_obj()); + /* Since we use key to get back the good key value, it is faster to give key + * by copy to the method. We avoid one key allocation... */ key = validate_key(key); if (key == "id" || key == "tag_id") { @@ -82,12 +84,18 @@ bool tag_helper::hook(std::string_view key, const std::string_view& value) { void tag_helper::check_validity(error_cnt& err) const { const Tag* o = static_cast<const Tag*>(obj()); - if (o->tag_name().empty()) + if (o->tag_name().empty()) { + ++err.config_errors; throw msg_fmt("Tag has no name (property 'tag_name')"); - if (o->key().id() == 0) + } + if (o->key().id() == 0) { + ++err.config_errors; throw msg_fmt("Tag '{}' has a null id", o->tag_name()); - if (o->key().type() == static_cast<uint32_t>(-1)) + } + if (o->key().type() == static_cast<uint32_t>(-1)) { + ++err.config_errors; throw msg_fmt("Tag type must be specified"); + } } /** diff --git a/common/engine_conf/timeperiod_helper.cc b/common/engine_conf/timeperiod_helper.cc index a6286dd4ab1..63e77406b88 100644 --- a/common/engine_conf/timeperiod_helper.cc +++ b/common/engine_conf/timeperiod_helper.cc @@ -33,7 +33,10 @@ namespace com::centreon::engine::configuration { * not the owner of this object. */ timeperiod_helper::timeperiod_helper(Timeperiod* obj) - : message_helper(object_type::timeperiod, obj, {}, 7) { + : message_helper(object_type::timeperiod, + obj, + {}, + Timeperiod::descriptor()->field_count()) { _init(); } @@ -46,6 +49,8 @@ timeperiod_helper::timeperiod_helper(Timeperiod* obj) bool timeperiod_helper::hook(std::string_view key, const std::string_view& value) { Timeperiod* obj = static_cast<Timeperiod*>(mut_obj()); + /* Since we use key to get back the good key value, it is faster to give key + * by copy to the method. We avoid one key allocation... */ key = validate_key(key); auto get_timerange = [](const std::string_view& value, auto* day) -> bool { auto arr = absl::StrSplit(value, ','); @@ -479,13 +484,16 @@ bool timeperiod_helper::_add_other_date(const std::string& line) { * @return True on success, otherwise false. */ bool timeperiod_helper::_get_day_id(std::string_view name, uint32_t& id) { - static std::array<std::string_view, 7> days{ - "sunday", "monday", "tuesday", "wednesday", - "thursday", "friday", "saturday"}; - for (id = 0; id < days.size(); ++id) - if (name == days[id]) - return true; - return false; + static const absl::flat_hash_map<std::string_view, uint32_t> days = { + {"sunday", 0}, {"monday", 1}, {"tuesday", 2}, {"wednesday", 3}, + {"thursday", 4}, {"friday", 5}, {"saturday", 6}, + }; + auto found = days.find(name); + if (found != days.end()) { + id = found->second; + return true; + } else + return false; } /** @@ -497,20 +505,24 @@ bool timeperiod_helper::_get_day_id(std::string_view name, uint32_t& id) { * @return True on success, otherwise false. */ bool timeperiod_helper::_get_month_id(std::string_view name, uint32_t& id) { - static std::array<std::string_view, 12> months{ - "january", "february", "march", "april", "may", "june", - "july", "august", "september", "october", "november", "december"}; - for (id = 0; id < months.size(); ++id) - if (name == months[id]) - return true; - return false; + static const absl::flat_hash_map<std::string_view, uint32_t> months = { + {"january", 0}, {"february", 1}, {"march", 2}, {"april", 3}, + {"may", 4}, {"june", 5}, {"july", 6}, {"august", 7}, + {"september", 8}, {"october", 9}, {"november", 10}, {"december", 11}, + }; + auto found = months.find(name); + if (found != months.end()) { + id = found->second; + return true; + } else + return false; } std::string daterange_to_str(const Daterange& dr) { - static std::array<std::string_view, 7> days{ + static const std::array<std::string_view, 7> days{ "sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"}; - static std::array<std::string_view, 12> months{ + static const std::array<std::string_view, 12> months{ "january", "february", "march", "april", "may", "june", "july", "august", "september", "october", "november", "december"}; std::string retval; diff --git a/engine/inc/com/centreon/engine/configuration/applier/pb_difference.hh b/engine/inc/com/centreon/engine/configuration/applier/pb_difference.hh index 858ad3d66a1..457c9ed3e13 100644 --- a/engine/inc/com/centreon/engine/configuration/applier/pb_difference.hh +++ b/engine/inc/com/centreon/engine/configuration/applier/pb_difference.hh @@ -29,18 +29,37 @@ namespace com::centreon::engine { using MessageDifferencer = ::google::protobuf::util::MessageDifferencer; -namespace configuration { -namespace applier { +namespace configuration::applier { +/** + * @brief This class computes the difference between two "lists" of Protobuf + * configuration objects. They are not really lists but RepeatedPtrFields or + * similar things. + * + * When the class is instantiated, we can then call the parse() method to + * compare two lists, for example the older one and the new one. + * + * The result is composed of three attributes: + * * _added : objects that are in the new list and not in the old one. + * * _deleted: objects in the old list but not in the new one. + * * _modified: objects that changed from the old list to the new one. + * + * @tparam T The Protobuf type to compare with pb_difference, for example + * configuration::Host, configuration::Service, etc... + * @tparam Key The key type used to store these objects, for example an + * std::string, an integer, etc... + * @tparam Container The container type used to store the objects, by default a + * RepeatedPtrField. + */ template <typename T, typename Key, - typename Container = ::google::protobuf::RepeatedPtrField<const T>> + typename Container = ::google::protobuf::RepeatedPtrField<T>> class pb_difference { // What are the new objects - std::vector<T> _added; + std::vector<const T*> _added; // What index to delete std::vector<std::pair<ssize_t, Key>> _deleted; // A vector of pairs, the pointer to the old one and the new one. - std::vector<std::pair<T*, T>> _modified; + std::vector<std::pair<T*, const T*>> _modified; public: /** @@ -54,48 +73,58 @@ class pb_difference { ~pb_difference() noexcept = default; pb_difference(const pb_difference&) = delete; pb_difference& operator=(const pb_difference&) = delete; - const std::vector<T>& added() const noexcept { return _added; } + const std::vector<const T*>& added() const noexcept { return _added; } const std::vector<std::pair<ssize_t, Key>>& deleted() const noexcept { return _deleted; } - const std::vector<std::pair<T*, T>>& modified() const noexcept { + const std::vector<std::pair<T*, const T*>>& modified() const noexcept { return _modified; } + /** + * @brief The main function of pb_difference. It takes two iterators of the + * old list, two iterators of the new one, and also a function giving the key + * to recognize it. The function usually is connector_name(), + * timeperiod_name(),... but it can also be a lambda returning a pair of IDs + * (for example in the case of services). + * The key returned by this last function is important since two different + * objects with the same key represent a modification. + * + * @tparam Function + * @param old_list the container of the current object configurations, + * @param new_list the container of the new object configurations, + * @param f The function returning the key of each object. + */ template <typename Function> - void parse(typename Container::iterator old_first, - typename Container::iterator old_last, - typename Container::iterator new_first, - typename Container::iterator new_last, - Function f) { + void parse(Container& old_list, const Container& new_list, Function f) { absl::flat_hash_map<Key, T*> keys_values; - for (auto it = old_first; it != old_last; ++it) { - const T& item = *it; + for (auto it = old_list.begin(); it != old_list.end(); ++it) { + T& item = *it; static_assert(std::is_same<decltype(f(item)), const Key&>::value || std::is_same<decltype(f(item)), const Key>::value || std::is_same<decltype(f(item)), Key>::value, "Invalid key function: it must match Key"); - keys_values[f(item)] = const_cast<T*>(&(item)); + keys_values[f(item)] = &item; } absl::flat_hash_set<Key> new_keys; - for (auto it = new_first; it != new_last; ++it) { + for (auto it = new_list.begin(); it != new_list.end(); ++it) { const T& item = *it; - new_keys.insert(f(item)); - if (!keys_values.contains(f(item))) { + auto inserted = new_keys.insert(f(item)); + if (!keys_values.contains(*inserted.first)) { // New object to add - _added.push_back(item); + _added.push_back(&item); } else { // Object to modify or equal if (!MessageDifferencer::Equals(item, *keys_values[f(item)])) { // There are changes in this object - _modified.push_back(std::make_pair(keys_values[f(item)], *it)); + _modified.push_back(std::make_pair(keys_values[f(item)], &item)); } } } ssize_t i = 0; - for (auto it = old_first; it != old_last; ++it) { + for (auto it = old_list.begin(); it != old_list.end(); ++it) { const T& item = *it; if (!new_keys.contains(f(item))) _deleted.push_back({i, f(item)}); @@ -103,44 +132,21 @@ class pb_difference { } } - void parse(typename Container::iterator old_first, - typename Container::iterator old_last, - typename Container::iterator new_first, - typename Container::iterator new_last, + void parse(Container& old_list, + const Container& new_list, Key (T::*key)() const) { std::function<Key(const T&)> f = key; - parse<std::function<Key(const T&)>>(old_first, old_last, new_first, - new_last, f); + parse<std::function<Key(const T&)>>(old_list, new_list, f); } - void parse(typename Container::iterator old_first, - typename Container::iterator old_last, - typename Container::iterator new_first, - typename Container::iterator new_last, + void parse(Container& old_list, + const Container& new_list, const Key& (T::*key)() const) { std::function<const Key&(const T&)> f = key; - parse<std::function<const Key&(const T&)>>(old_first, old_last, new_first, - new_last, f); + parse<std::function<const Key&(const T&)>>(old_list, new_list, f); } - - // template <typename TF1, typename TF2> - // void parse(typename Container::iterator old_first, - // typename Container::iterator old_last, - // typename Container::iterator new_first, - // typename Container::iterator new_last, - // TF1 (T::*key1)() const, - // TF2 (T::*key2)() const) { - // std::function<const std::pair<TF1, TF2>(const T&)> f = [&key1, - // &key2](const T& - // t) { - // return std::make_pair((t.*key1)(), (t.*key2)()); - // }; - // parse<std::function<const std::pair<TF1, TF2>(const T&)>>( - // old_first, old_last, new_first, new_last, f); - // } }; -} // namespace applier -} // namespace configuration +} // namespace configuration::applier } // namespace com::centreon::engine diff --git a/engine/inc/com/centreon/engine/contact.hh b/engine/inc/com/centreon/engine/contact.hh index 198e2881e28..fc6069a017d 100644 --- a/engine/inc/com/centreon/engine/contact.hh +++ b/engine/inc/com/centreon/engine/contact.hh @@ -61,7 +61,9 @@ class contact { // Base properties. std::string const& get_address(int index) const; std::vector<std::string> const& get_addresses() const; +#ifdef LEGACY_CONF void set_addresses(const std::vector<std::string>& addresses); +#endif void set_addresses(std::vector<std::string>&& addresses); std::string const& get_alias() const; void set_alias(std::string const& alias); @@ -185,7 +187,7 @@ std::shared_ptr<com::centreon::engine::contact> add_contact( std::string const& alias, std::string const& email, std::string const& pager, - const std::vector<std::string>& addresses, + std::vector<std::string>&& addresses, std::string const& svc_notification_period, std::string const& host_notification_period, int notify_service_ok, diff --git a/engine/inc/com/centreon/engine/timeperiod.hh b/engine/inc/com/centreon/engine/timeperiod.hh index 8a5d2ac5087..8c0a041d9bd 100644 --- a/engine/inc/com/centreon/engine/timeperiod.hh +++ b/engine/inc/com/centreon/engine/timeperiod.hh @@ -52,12 +52,12 @@ class timeperiod { std::string const& get_name() const { return _name; }; - void set_name(std::string const& name); - std::string const get_alias() const { + void set_name(const std::string& name); + const std::string& get_alias() const { return _alias; }; - void set_alias(std::string const& alias); - timeperiodexclusion const& get_exclusions() const { + void set_alias(const std::string& alias); + const timeperiodexclusion& get_exclusions() const { return _exclusions; }; timeperiodexclusion& get_exclusions() { @@ -72,8 +72,8 @@ class timeperiod { void resolve(uint32_t& w, uint32_t& e); - bool operator==(timeperiod const& obj) throw(); - bool operator!=(timeperiod const& obj) throw(); + bool operator==(timeperiod const& obj) noexcept; + bool operator!=(timeperiod const& obj) noexcept; days_array days; exception_array exceptions; diff --git a/engine/src/configuration/applier/connector.cc b/engine/src/configuration/applier/connector.cc index b5b2ee674fb..0ff12f3efe4 100644 --- a/engine/src/configuration/applier/connector.cc +++ b/engine/src/configuration/applier/connector.cc @@ -48,28 +48,27 @@ void applier::connector::add_object(configuration::connector const& obj) { nagios_macros* macros(get_global_macros()); std::string command_line; process_macros_r(macros, obj.connector_line(), command_line, 0); - std::string processed_cmd(command_line); // Add connector to the global configuration set. config->connectors().insert(obj); // Create connector. - boost::trim(processed_cmd); + boost::trim(command_line); // if executable connector path ends with opentelemetry, it's a fake // opentelemetry connector - size_t end_path = processed_cmd.find(' '); - size_t otel_pos = processed_cmd.find(_otel_fake_exe); + size_t end_path = command_line.find(' '); + size_t otel_pos = command_line.find(_otel_fake_exe); if (otel_pos < end_path) { commands::otel_connector::create( obj.connector_name(), boost::algorithm::trim_copy( - processed_cmd.substr(otel_pos + _otel_fake_exe.length())), + command_line.substr(otel_pos + _otel_fake_exe.length())), &checks::checker::instance()); } else { auto cmd = std::make_shared<commands::connector>( - obj.connector_name(), processed_cmd, &checks::checker::instance()); + obj.connector_name(), command_line, &checks::checker::instance()); commands::connector::connectors[obj.connector_name()] = cmd; } } @@ -87,29 +86,28 @@ void applier::connector::add_object(const configuration::Connector& obj) { nagios_macros* macros = get_global_macros(); std::string command_line; process_macros_r(macros, obj.connector_line(), command_line, 0); - std::string processed_cmd(command_line); // Add connector to the global configuration set. auto* cfg_cnn = pb_config.add_connectors(); cfg_cnn->CopyFrom(obj); // Create connector. - boost::trim(processed_cmd); + boost::trim(command_line); // If executable connector path ends with opentelemetry, it's a fake // opentelemetry connector. - size_t end_path = processed_cmd.find(' '); - size_t otel_pos = processed_cmd.find(_otel_fake_exe); + size_t end_path = command_line.find(' '); + size_t otel_pos = command_line.find(_otel_fake_exe); if (otel_pos < end_path) { commands::otel_connector::create( obj.connector_name(), boost::algorithm::trim_copy( - processed_cmd.substr(otel_pos + _otel_fake_exe.length())), + command_line.substr(otel_pos + _otel_fake_exe.length())), &checks::checker::instance()); } else { auto cmd = std::make_shared<commands::connector>( - obj.connector_name(), processed_cmd, &checks::checker::instance()); + obj.connector_name(), command_line, &checks::checker::instance()); commands::connector::connectors[obj.connector_name()] = cmd; } } @@ -162,27 +160,23 @@ void applier::connector::modify_object(configuration::connector const& obj) { nagios_macros* macros(get_global_macros()); std::string command_line; process_macros_r(macros, obj.connector_line(), command_line, 0); - std::string processed_cmd(command_line); - boost::trim(processed_cmd); + boost::trim(command_line); // if executable connector path ends with opentelemetry, it's a fake // opentelemetry connector - size_t end_path = processed_cmd.find(' '); - size_t otel_pos = processed_cmd.find(_otel_fake_exe); + size_t end_path = command_line.find(' '); + size_t otel_pos = command_line.find(_otel_fake_exe); connector_map::iterator exist_connector( commands::connector::connectors.find(obj.key())); if (otel_pos < end_path) { - std::string otel_cmdline = boost::algorithm::trim_copy( - processed_cmd.substr(otel_pos + _otel_fake_exe.length())); - - if (!commands::otel_connector::update(obj.key(), processed_cmd)) { + if (!commands::otel_connector::update(obj.key(), command_line)) { // connector object become an otel fake connector if (exist_connector != commands::connector::connectors.end()) { commands::connector::connectors.erase(exist_connector); - commands::otel_connector::create(obj.key(), processed_cmd, + commands::otel_connector::create(obj.key(), command_line, &checks::checker::instance()); } else { throw com::centreon::exceptions::msg_fmt( @@ -192,12 +186,12 @@ void applier::connector::modify_object(configuration::connector const& obj) { } else { if (exist_connector != commands::connector::connectors.end()) { // Set the new command line. - exist_connector->second->set_command_line(processed_cmd); + exist_connector->second->set_command_line(command_line); } else { // old otel_connector => connector if (commands::otel_connector::remove(obj.key())) { auto cmd = std::make_shared<commands::connector>( - obj.connector_name(), processed_cmd, &checks::checker::instance()); + obj.connector_name(), command_line, &checks::checker::instance()); commands::connector::connectors[obj.connector_name()] = cmd; } else { @@ -224,26 +218,56 @@ void applier::connector::modify_object( // Logging. config_logger->debug("Modifying connector '{}'.", new_obj.connector_name()); - // Find connector object. - connector_map::iterator it_obj( - commands::connector::connectors.find(new_obj.connector_name())); - if (it_obj == commands::connector::connectors.end()) - throw engine_error() << fmt::format( - "Could not modify non-existing connector object '{}'", - new_obj.connector_name()); - - commands::connector* c = it_obj->second.get(); - - // Update the global configuration set. - to_modify->CopyFrom(new_obj); - // Expand command line. nagios_macros* macros(get_global_macros()); std::string command_line; process_macros_r(macros, new_obj.connector_line(), command_line, 0); - // Set the new command line. - c->set_command_line(command_line); + boost::trim(command_line); + + // if executable connector path ends with opentelemetry, it's a fake + // opentelemetry connector + size_t end_path = command_line.find(' '); + size_t otel_pos = command_line.find(_otel_fake_exe); + + connector_map::iterator current_connector( + commands::connector::connectors.find(new_obj.connector_name())); + + if (otel_pos < end_path) { + if (!commands::otel_connector::update(new_obj.connector_name(), + command_line)) { + // connector object becomes an otel fake connector + if (current_connector != commands::connector::connectors.end()) { + commands::connector::connectors.erase(current_connector); + commands::otel_connector::create(new_obj.connector_name(), command_line, + &checks::checker::instance()); + } else { + throw com::centreon::exceptions::msg_fmt( + "unknown open telemetry command to update: {}", + new_obj.connector_name()); + } + } + } else { + if (current_connector != commands::connector::connectors.end()) { + // Set the new command line. + current_connector->second->set_command_line(command_line); + } else { + // old otel_connector => connector + if (commands::otel_connector::remove(new_obj.connector_name())) { + auto cmd = std::make_shared<commands::connector>( + new_obj.connector_name(), command_line, + &checks::checker::instance()); + commands::connector::connectors[new_obj.connector_name()] = cmd; + + } else { + throw com::centreon::exceptions::msg_fmt( + "unknown connector to update: {}", new_obj.connector_name()); + } + } + } + + // Update the global configuration set. + to_modify->CopyFrom(new_obj); } #endif @@ -286,6 +310,8 @@ void applier::connector::remove_object(ssize_t idx) { commands::connector::connectors.erase(it); } + commands::otel_connector::remove(obj.connector_name()); + // Remove connector from the global configuration set. pb_config.mutable_connectors()->DeleteSubrange(idx, 1); } @@ -313,5 +339,5 @@ void applier::connector::resolve_object(configuration::connector const& obj * @param[in] obj Unused. */ void applier::connector::resolve_object(const configuration::Connector&, - error_cnt& err[[maybe_unused]]) {} + error_cnt& err [[maybe_unused]]) {} #endif diff --git a/engine/src/configuration/applier/contact.cc b/engine/src/configuration/applier/contact.cc index cfca3d71dda..4f00a4bec47 100644 --- a/engine/src/configuration/applier/contact.cc +++ b/engine/src/configuration/applier/contact.cc @@ -57,8 +57,9 @@ void applier::contact::add_object(configuration::contact const& obj) { // Create contact. std::shared_ptr<com::centreon::engine::contact> c(add_contact( - obj.contact_name(), obj.alias(), obj.email(), obj.pager(), addresses, - obj.service_notification_period(), obj.host_notification_period(), + obj.contact_name(), obj.alias(), obj.email(), obj.pager(), + std::move(addresses), obj.service_notification_period(), + obj.host_notification_period(), static_cast<bool>(obj.service_notification_options() & service::ok), static_cast<bool>(obj.service_notification_options() & service::critical), static_cast<bool>(obj.service_notification_options() & service::warning), @@ -121,8 +122,9 @@ void applier::contact::add_object(const configuration::Contact& obj) { // Create contact. std::shared_ptr<com::centreon::engine::contact> c(add_contact( - obj.contact_name(), obj.alias(), obj.email(), obj.pager(), addresses, - obj.service_notification_period(), obj.host_notification_period(), + obj.contact_name(), obj.alias(), obj.email(), obj.pager(), + std::move(addresses), obj.service_notification_period(), + obj.host_notification_period(), static_cast<bool>(obj.service_notification_options() & action_svc_ok), static_cast<bool>(obj.service_notification_options() & action_svc_critical), @@ -753,7 +755,8 @@ void applier::contact::resolve_object(const configuration::contact& obj, * * @param[in,out] obj Object to resolve. */ -void applier::contact::resolve_object(const configuration::Contact& obj, error_cnt& err) { +void applier::contact::resolve_object(const configuration::Contact& obj, + error_cnt& err) { // Logging. config_logger->debug("Resolving contact '{}'.", obj.contact_name()); diff --git a/engine/src/configuration/applier/scheduler.cc b/engine/src/configuration/applier/scheduler.cc index a442b520e1a..a4ae815bdc7 100644 --- a/engine/src/configuration/applier/scheduler.cc +++ b/engine/src/configuration/applier/scheduler.cc @@ -215,10 +215,10 @@ void applier::scheduler::apply( void applier::scheduler::apply( configuration::State& config, const pb_difference<configuration::Host, uint64_t>& diff_hosts, - const pb_difference<configuration::Service, std::pair<uint64_t, uint64_t>>& + const pb_difference<configuration::Service, std::pair<uint64_t, uint64_t> >& diff_services, const pb_difference<configuration::Anomalydetection, - std::pair<uint64_t, uint64_t>>& + std::pair<uint64_t, uint64_t> >& diff_anomalydetections) { // Internal pointer will be used in private methods. _pb_config = &config; @@ -231,88 +231,92 @@ void applier::scheduler::apply( for (auto& d : diff_hosts.deleted()) hst_to_unschedule.emplace_back(d.second); - std::vector<std::pair<uint64_t, uint64_t>> svc_to_unschedule; + std::vector<std::pair<uint64_t, uint64_t> > svc_to_unschedule; for (auto& d : diff_services.deleted()) svc_to_unschedule.emplace_back(d.second); - std::vector<std::pair<uint64_t, uint64_t>> ad_to_unschedule; + std::vector<std::pair<uint64_t, uint64_t> > ad_to_unschedule; for (auto& d : diff_anomalydetections.deleted()) ad_to_unschedule.emplace_back(d.second); std::vector<uint64_t> hst_to_schedule; for (auto& a : diff_hosts.added()) - hst_to_schedule.emplace_back(a.host_id()); + hst_to_schedule.emplace_back(a->host_id()); - std::vector<std::pair<uint64_t, uint64_t>> svc_to_schedule; + std::vector<std::pair<uint64_t, uint64_t> > svc_to_schedule; for (auto& a : diff_services.added()) - svc_to_schedule.emplace_back(a.host_id(), a.service_id()); + svc_to_schedule.emplace_back(a->host_id(), a->service_id()); - std::vector<std::pair<uint64_t, uint64_t>> ad_to_schedule; + std::vector<std::pair<uint64_t, uint64_t> > ad_to_schedule; for (auto& a : diff_anomalydetections.added()) - ad_to_schedule.emplace_back(a.host_id(), a.service_id()); + ad_to_schedule.emplace_back(a->host_id(), a->service_id()); for (auto& m : diff_hosts.modified()) { - auto it_hst = engine::host::hosts.find(m.second.host_name()); + auto it_hst = engine::host::hosts.find(m.second->host_name()); if (it_hst != engine::host::hosts.end()) { bool has_event(events::loop::instance().find_event( events::loop::low, timed_event::EVENT_HOST_CHECK, it_hst->second.get()) != events::loop::instance().list_end(events::loop::low)); - bool should_schedule(m.second.checks_active() && - m.second.check_interval() > 0); + bool should_schedule(m.second->checks_active() && + m.second->check_interval() > 0); if (has_event && should_schedule) { - hst_to_unschedule.emplace_back(m.second.host_id()); - hst_to_schedule.emplace_back(m.second.host_id()); + hst_to_unschedule.emplace_back(m.second->host_id()); + hst_to_schedule.emplace_back(m.second->host_id()); } else if (!has_event && should_schedule) - hst_to_schedule.emplace_back(m.second.host_id()); + hst_to_schedule.emplace_back(m.second->host_id()); else if (has_event && !should_schedule) - hst_to_unschedule.emplace_back(m.second.host_id()); + hst_to_unschedule.emplace_back(m.second->host_id()); // Else it has no event and should not be scheduled, so do nothing. } } for (auto& m : diff_services.modified()) { auto it_svc = engine::service::services_by_id.find( - {m.second.host_id(), m.second.service_id()}); + {m.second->host_id(), m.second->service_id()}); if (it_svc != engine::service::services_by_id.end()) { bool has_event(events::loop::instance().find_event( events::loop::low, timed_event::EVENT_SERVICE_CHECK, it_svc->second.get()) != events::loop::instance().list_end(events::loop::low)); - bool should_schedule(m.second.checks_active() && - (m.second.check_interval() > 0)); + bool should_schedule(m.second->checks_active() && + (m.second->check_interval() > 0)); if (has_event && should_schedule) { - svc_to_unschedule.emplace_back(m.second.host_id(), - m.second.service_id()); - svc_to_schedule.emplace_back(m.second.host_id(), m.second.service_id()); + svc_to_unschedule.emplace_back(m.second->host_id(), + m.second->service_id()); + svc_to_schedule.emplace_back(m.second->host_id(), + m.second->service_id()); } else if (!has_event && should_schedule) - svc_to_schedule.emplace_back(m.second.host_id(), m.second.service_id()); + svc_to_schedule.emplace_back(m.second->host_id(), + m.second->service_id()); else if (has_event && !should_schedule) - svc_to_unschedule.emplace_back(m.second.host_id(), - m.second.service_id()); + svc_to_unschedule.emplace_back(m.second->host_id(), + m.second->service_id()); // Else it has no event and should not be scheduled, so do nothing. } } for (auto& m : diff_anomalydetections.modified()) { auto it_svc = engine::service::services_by_id.find( - {m.second.host_id(), m.second.service_id()}); + {m.second->host_id(), m.second->service_id()}); if (it_svc != engine::service::services_by_id.end()) { bool has_event(events::loop::instance().find_event( events::loop::low, timed_event::EVENT_SERVICE_CHECK, it_svc->second.get()) != events::loop::instance().list_end(events::loop::low)); bool should_schedule = - m.second.checks_active() && m.second.check_interval() > 0; + m.second->checks_active() && m.second->check_interval() > 0; if (has_event && should_schedule) { - ad_to_unschedule.emplace_back(m.second.host_id(), - m.second.service_id()); - ad_to_schedule.emplace_back(m.second.host_id(), m.second.service_id()); + ad_to_unschedule.emplace_back(m.second->host_id(), + m.second->service_id()); + ad_to_schedule.emplace_back(m.second->host_id(), + m.second->service_id()); } else if (!has_event && should_schedule) - ad_to_schedule.emplace_back(m.second.host_id(), m.second.service_id()); + ad_to_schedule.emplace_back(m.second->host_id(), + m.second->service_id()); else if (has_event && !should_schedule) - ad_to_unschedule.emplace_back(m.second.host_id(), - m.second.service_id()); + ad_to_unschedule.emplace_back(m.second->host_id(), + m.second->service_id()); // Else it has no event and should not be scheduled, so do nothing. } } @@ -344,13 +348,16 @@ void applier::scheduler::apply( if (config.service_interleave_factor_method().type() == configuration::InterleaveFactor::ilf_user) - scheduling_info.service_interleave_factor = config.service_interleave_factor_method().user_value(); + scheduling_info.service_interleave_factor = + config.service_interleave_factor_method().user_value(); if (config.service_inter_check_delay_method().type() == configuration::InterCheckDelay::user) - scheduling_info.service_inter_check_delay = config.service_inter_check_delay_method().user_value(); + scheduling_info.service_inter_check_delay = + config.service_inter_check_delay_method().user_value(); if (config.host_inter_check_delay_method().type() == configuration::InterCheckDelay::user) - scheduling_info.host_inter_check_delay = config.host_inter_check_delay_method().user_value(); + scheduling_info.host_inter_check_delay = + config.host_inter_check_delay_method().user_value(); // Calculate scheduling parameters. _calculate_host_scheduling_params(); @@ -487,7 +494,8 @@ applier::scheduler::scheduler() _old_host_freshness_check_interval(0), _old_retention_update_interval(0), _old_service_freshness_check_interval(0), - _old_status_update_interval(0) {} + _old_status_update_interval(0) { +} /** * Default destructor. @@ -1432,7 +1440,7 @@ std::vector<com::centreon::engine::service*> applier::scheduler::_get_services( * @return a vector of services. */ std::vector<com::centreon::engine::service*> applier::scheduler::_get_services( - const std::vector<std::pair<uint64_t, uint64_t>>& svc_ids, + const std::vector<std::pair<uint64_t, uint64_t> >& svc_ids, bool throw_if_not_found) { std::vector<com::centreon::engine::service*> retval; for (auto& p : svc_ids) { @@ -1490,7 +1498,7 @@ applier::scheduler::_get_anomalydetections(set_anomalydetection const& ad_cfg, */ std::vector<com::centreon::engine::service*> applier::scheduler::_get_anomalydetections( - const std::vector<std::pair<uint64_t, uint64_t>>& ad_ids, + const std::vector<std::pair<uint64_t, uint64_t> >& ad_ids, bool throw_if_not_found) { std::vector<engine::service*> retval; for (auto& p : ad_ids) { diff --git a/engine/src/configuration/applier/state.cc b/engine/src/configuration/applier/state.cc index 89a968e9e02..8cfd81d0e0f 100644 --- a/engine/src/configuration/applier/state.cc +++ b/engine/src/configuration/applier/state.cc @@ -920,10 +920,10 @@ void applier::state::_apply(const pb_difference<ConfigurationType, Key>& diff, // Modify objects. for (auto& p : diff.modified()) { if (!verify_config) - aplyr.modify_object(p.first, p.second); + aplyr.modify_object(p.first, *p.second); else { try { - aplyr.modify_object(p.first, p.second); + aplyr.modify_object(p.first, *p.second); } catch (const std::exception& e) { ++err.config_errors; events_logger->info(e.what()); @@ -949,10 +949,10 @@ void applier::state::_apply(const pb_difference<ConfigurationType, Key>& diff, // Add objects. for (auto& obj : diff.added()) { if (!verify_config) - aplyr.add_object(obj); + aplyr.add_object(*obj); else { try { - aplyr.add_object(obj); + aplyr.add_object(*obj); } catch (const std::exception& e) { ++err.config_errors; events_logger->info(e.what()); @@ -2146,30 +2146,30 @@ void applier::state::_processing(configuration::State& new_cfg, // Build difference for timeperiods. pb_difference<configuration::Timeperiod, std::string> diff_timeperiods; - diff_timeperiods.parse( - pb_config.timeperiods().begin(), pb_config.timeperiods().end(), - new_cfg.timeperiods().begin(), new_cfg.timeperiods().end(), - &configuration::Timeperiod::timeperiod_name); + google::protobuf::RepeatedPtrField< + ::com::centreon::engine::configuration::Timeperiod> + old = *pb_config.mutable_timeperiods(); + const google::protobuf::RepeatedPtrField< + ::com::centreon::engine::configuration::Timeperiod> + new_conf = new_cfg.timeperiods(); + diff_timeperiods.parse(old, new_conf, + &configuration::Timeperiod::timeperiod_name); // Build difference for connectors. pb_difference<configuration::Connector, std::string> diff_connectors; - diff_connectors.parse( - pb_config.connectors().begin(), pb_config.connectors().end(), - new_cfg.connectors().begin(), new_cfg.connectors().end(), - &configuration::Connector::connector_name); + diff_connectors.parse(*pb_config.mutable_connectors(), new_cfg.connectors(), + &configuration::Connector::connector_name); // Build difference for commands. pb_difference<configuration::Command, std::string> diff_commands; - diff_commands.parse(pb_config.commands().begin(), pb_config.commands().end(), - new_cfg.commands().begin(), new_cfg.commands().end(), + diff_commands.parse(*pb_config.mutable_commands(), new_cfg.commands(), &configuration::Command::command_name); // Build difference for severities. pb_difference<configuration::Severity, std::pair<uint64_t, uint32_t>> diff_severities; diff_severities.parse( - pb_config.severities().begin(), pb_config.severities().end(), - new_cfg.severities().begin(), new_cfg.severities().end(), + *pb_config.mutable_severities(), new_cfg.severities(), [](const configuration::Severity& sev) -> std::pair<uint64_t, uint32_t> { return std::make_pair(sev.key().id(), sev.key().type()); }); @@ -2177,43 +2177,36 @@ void applier::state::_processing(configuration::State& new_cfg, // Build difference for tags. pb_difference<configuration::Tag, std::pair<uint64_t, uint32_t>> diff_tags; diff_tags.parse( - pb_config.tags().begin(), pb_config.tags().end(), new_cfg.tags().begin(), - new_cfg.tags().end(), + *pb_config.mutable_tags(), new_cfg.tags(), [](const configuration::Tag& tg) -> std::pair<uint64_t, uint32_t> { return std::make_pair(tg.key().id(), tg.key().type()); }); // Build difference for contacts. pb_difference<configuration::Contact, std::string> diff_contacts; - diff_contacts.parse(pb_config.contacts().begin(), pb_config.contacts().end(), - new_cfg.contacts().begin(), new_cfg.contacts().end(), + diff_contacts.parse(*pb_config.mutable_contacts(), new_cfg.contacts(), &configuration::Contact::contact_name); // Build difference for contactgroups. pb_difference<configuration::Contactgroup, std::string> diff_contactgroups; - diff_contactgroups.parse( - pb_config.contactgroups().begin(), pb_config.contactgroups().end(), - new_cfg.contactgroups().begin(), new_cfg.contactgroups().end(), - &configuration::Contactgroup::contactgroup_name); + diff_contactgroups.parse(*pb_config.mutable_contactgroups(), + new_cfg.contactgroups(), + &configuration::Contactgroup::contactgroup_name); // Build difference for hosts. pb_difference<configuration::Host, uint64_t> diff_hosts; - diff_hosts.parse(pb_config.hosts().begin(), pb_config.hosts().end(), - new_cfg.hosts().begin(), new_cfg.hosts().end(), + diff_hosts.parse(*pb_config.mutable_hosts(), new_cfg.hosts(), &configuration::Host::host_id); // Build difference for hostgroups. pb_difference<configuration::Hostgroup, std::string> diff_hostgroups; - diff_hostgroups.parse( - pb_config.hostgroups().begin(), pb_config.hostgroups().end(), - new_cfg.hostgroups().begin(), new_cfg.hostgroups().end(), - &configuration::Hostgroup::hostgroup_name); + diff_hostgroups.parse(*pb_config.mutable_hostgroups(), new_cfg.hostgroups(), + &configuration::Hostgroup::hostgroup_name); // Build difference for services. pb_difference<configuration::Service, std::pair<uint64_t, uint64_t>> diff_services; - diff_services.parse(pb_config.services().begin(), pb_config.services().end(), - new_cfg.services().begin(), new_cfg.services().end(), + diff_services.parse(*pb_config.mutable_services(), new_cfg.services(), [](const configuration::Service& s) { return std::make_pair(s.host_id(), s.service_id()); }); @@ -2222,55 +2215,45 @@ void applier::state::_processing(configuration::State& new_cfg, pb_difference<configuration::Anomalydetection, std::pair<uint64_t, uint64_t>> diff_anomalydetections; diff_anomalydetections.parse( - pb_config.anomalydetections().begin(), - pb_config.anomalydetections().end(), new_cfg.anomalydetections().begin(), - new_cfg.anomalydetections().end(), + *pb_config.mutable_anomalydetections(), new_cfg.anomalydetections(), [](const configuration::Anomalydetection& ad) { return std::make_pair(ad.host_id(), ad.service_id()); }); // Build difference for servicegroups. pb_difference<configuration::Servicegroup, std::string> diff_servicegroups; - diff_servicegroups.parse( - pb_config.servicegroups().begin(), pb_config.servicegroups().end(), - new_cfg.servicegroups().begin(), new_cfg.servicegroups().end(), - &configuration::Servicegroup::servicegroup_name); + diff_servicegroups.parse(*pb_config.mutable_servicegroups(), + new_cfg.servicegroups(), + &configuration::Servicegroup::servicegroup_name); // Build difference for hostdependencies. pb_difference<configuration::Hostdependency, size_t> diff_hostdependencies; typedef size_t (*key_func)(const configuration::Hostdependency&); - diff_hostdependencies.parse<key_func>( - pb_config.hostdependencies().begin(), pb_config.hostdependencies().end(), - new_cfg.hostdependencies().begin(), new_cfg.hostdependencies().end(), - configuration::hostdependency_key); + diff_hostdependencies.parse<key_func>(*pb_config.mutable_hostdependencies(), + new_cfg.hostdependencies(), + configuration::hostdependency_key); // Build difference for servicedependencies. pb_difference<configuration::Servicedependency, size_t> diff_servicedependencies; typedef size_t (*key_func_sd)(const configuration::Servicedependency&); diff_servicedependencies.parse<key_func_sd>( - pb_config.servicedependencies().begin(), - pb_config.servicedependencies().end(), - new_cfg.servicedependencies().begin(), - new_cfg.servicedependencies().end(), + *pb_config.mutable_servicedependencies(), new_cfg.servicedependencies(), configuration::servicedependency_key); // Build difference for hostdependencies. pb_difference<configuration::Hostescalation, size_t> diff_hostescalations; typedef size_t (*key_func_he)(const configuration::Hostescalation&); - diff_hostescalations.parse<key_func_he>( - pb_config.hostescalations().begin(), pb_config.hostescalations().end(), - new_cfg.hostescalations().begin(), new_cfg.hostescalations().end(), - configuration::hostescalation_key); + diff_hostescalations.parse<key_func_he>(*pb_config.mutable_hostescalations(), + new_cfg.hostescalations(), + configuration::hostescalation_key); // Build difference for servicedependencies. pb_difference<configuration::Serviceescalation, size_t> diff_serviceescalations; typedef size_t (*key_func_se)(const configuration::Serviceescalation&); diff_serviceescalations.parse<key_func_se>( - pb_config.serviceescalations().begin(), - pb_config.serviceescalations().end(), - new_cfg.serviceescalations().begin(), new_cfg.serviceescalations().end(), + *pb_config.mutable_serviceescalations(), new_cfg.serviceescalations(), configuration::serviceescalation_key); // Timing. @@ -2456,13 +2439,13 @@ void applier::state::_processing(configuration::State& new_cfg, // Print initial states of new hosts and services. if (!verify_config && !test_scheduling) { for (auto a : diff_hosts.added()) { - auto it_hst = engine::host::hosts_by_id.find(a.host_id()); + auto it_hst = engine::host::hosts_by_id.find(a->host_id()); if (it_hst != engine::host::hosts_by_id.end()) log_host_state(INITIAL_STATES, it_hst->second.get()); } for (auto a : diff_services.added()) { - auto it_svc = - engine::service::services_by_id.find({a.host_id(), a.service_id()}); + auto it_svc = engine::service::services_by_id.find( + {a->host_id(), a->service_id()}); if (it_svc != engine::service::services_by_id.end()) log_service_state(INITIAL_STATES, it_svc->second.get()); } diff --git a/engine/src/contact.cc b/engine/src/contact.cc index ca9f61ed940..e05503d3883 100644 --- a/engine/src/contact.cc +++ b/engine/src/contact.cc @@ -82,6 +82,7 @@ void contact::set_addresses(std::vector<std::string>&& addresses) { _addresses = std::move(addresses); } +#ifdef LEGACY_CONF /** * Set addresses. * @@ -90,6 +91,7 @@ void contact::set_addresses(std::vector<std::string>&& addresses) { void contact::set_addresses(std::vector<std::string> const& addresses) { _addresses = addresses; } +#endif /** * Return the contact alias @@ -519,7 +521,7 @@ std::shared_ptr<contact> add_contact( std::string const& alias, std::string const& email, std::string const& pager, - const std::vector<std::string>& addresses, + std::vector<std::string>&& addresses, std::string const& svc_notification_period, std::string const& host_notification_period, int notify_service_ok, @@ -565,7 +567,7 @@ std::shared_ptr<contact> add_contact( obj->set_pager(pager); obj->set_service_notification_period(svc_notification_period); - obj->set_addresses(addresses); + obj->set_addresses(std::move(addresses)); // Set remaining contact properties. obj->set_can_submit_commands(can_submit_commands > 0); From b99c5d8a32ea9bb49c35ab45417efec2d7daf208 Mon Sep 17 00:00:00 2001 From: David Boucher <dboucher@centreon.com> Date: Wed, 24 Jul 2024 18:35:52 +0200 Subject: [PATCH 919/948] fix(broker/lua): Several fix in the lua Stream * Legacy OpenSSL md5 function replaced by the OpenSSL 3 version * improvement of hostgroups cache in streamconnector * improvement of servicegroups cache in streamconnector REFS: MON-34072 MON-15480 --- .../com/centreon/broker/lua/macro_cache.hh | 17 +- broker/lua/src/broker_utils.cc | 39 +++- broker/lua/src/macro_cache.cc | 221 +++++++++++++----- broker/lua/test/lua.cc | 1 + 4 files changed, 216 insertions(+), 62 deletions(-) diff --git a/broker/lua/inc/com/centreon/broker/lua/macro_cache.hh b/broker/lua/inc/com/centreon/broker/lua/macro_cache.hh index 616f2afe377..d9f04f48fe2 100644 --- a/broker/lua/inc/com/centreon/broker/lua/macro_cache.hh +++ b/broker/lua/inc/com/centreon/broker/lua/macro_cache.hh @@ -27,6 +27,7 @@ #include "com/centreon/broker/neb/host_group.hh" #include "com/centreon/broker/neb/host_group_member.hh" #include "com/centreon/broker/neb/instance.hh" +#include "com/centreon/broker/neb/internal.hh" #include "com/centreon/broker/neb/service.hh" #include "com/centreon/broker/neb/service_group.hh" #include "com/centreon/broker/neb/service_group_member.hh" @@ -42,14 +43,26 @@ class macro_cache { std::shared_ptr<persistent_cache> _cache; absl::flat_hash_map<uint64_t, std::shared_ptr<io::data>> _instances; absl::flat_hash_map<uint64_t, std::shared_ptr<io::data>> _hosts; - absl::flat_hash_map<uint64_t, std::shared_ptr<io::data>> _host_groups; + /* The host groups cache stores also a set with the pollers telling they need + * the cache. So if no more poller needs a host group, we can remove it from + * the cache. */ + absl::flat_hash_map<uint64_t, + std::pair<std::shared_ptr<neb::pb_host_group>, + absl::flat_hash_set<uint32_t>>> + _host_groups; absl::btree_map<std::pair<uint64_t, uint64_t>, std::shared_ptr<io::data>> _host_group_members; absl::flat_hash_map<std::pair<uint64_t, uint64_t>, std::shared_ptr<io::data>> _custom_vars; absl::flat_hash_map<std::pair<uint64_t, uint64_t>, std::shared_ptr<io::data>> _services; - absl::flat_hash_map<uint64_t, std::shared_ptr<io::data>> _service_groups; + /* The service groups cache stores also a set with the pollers telling they + * need the cache. So if no more poller needs a service group, we can remove + * it from the cache. */ + absl::flat_hash_map<uint64_t, + std::pair<std::shared_ptr<neb::pb_service_group>, + absl::flat_hash_set<uint32_t>>> + _service_groups; absl::btree_map<std::tuple<uint64_t, uint64_t, uint64_t>, std::shared_ptr<io::data>> _service_group_members; diff --git a/broker/lua/src/broker_utils.cc b/broker/lua/src/broker_utils.cc index 01e8a30fec1..05bae3f021e 100644 --- a/broker/lua/src/broker_utils.cc +++ b/broker/lua/src/broker_utils.cc @@ -24,7 +24,7 @@ #include "absl/strings/string_view.h" #include "com/centreon/broker/config/applier/state.hh" -#include <openssl/md5.h> +#include <openssl/evp.h> #include <cstdlib> #include <cstring> #include <iomanip> @@ -810,6 +810,34 @@ static int l_broker_stat(lua_State* L) { } } +static void md5_message(const unsigned char* message, + size_t message_len, + unsigned char** digest, + unsigned int* digest_len) { + EVP_MD_CTX* mdctx; + auto handle_error = [](const std::string& msg) { + auto logger = log_v2::instance().get(log_v2::LUA); + logger->error(msg); + }; + if ((mdctx = EVP_MD_CTX_new()) == nullptr) { + handle_error("lua: fail to call MD5 (EVP_MD_CTX_new call)"); + } + if (1 != EVP_DigestInit_ex(mdctx, EVP_md5(), nullptr)) { + handle_error("lua: fail to call MD5 (EVP_DigestInit_ex call)"); + } + if (1 != EVP_DigestUpdate(mdctx, message, message_len)) { + handle_error("lua: fail to call MD5 (EVP_DigestUpdate call)"); + } + if ((*digest = (unsigned char*)OPENSSL_malloc(EVP_MD_size(EVP_md5()))) == + nullptr) { + handle_error("lua: fail to call MD5 (OPENSSL_malloc call)"); + } + if (1 != EVP_DigestFinal_ex(mdctx, *digest, digest_len)) { + handle_error("lua: fail to call MD5 (EVP_DigestFinal_ex call)"); + } + EVP_MD_CTX_free(mdctx); +} + static int l_broker_md5(lua_State* L) { auto digit = [](unsigned char d) -> char { if (d < 10) @@ -820,11 +848,12 @@ static int l_broker_md5(lua_State* L) { size_t len; const unsigned char* str = reinterpret_cast<const unsigned char*>(lua_tolstring(L, -1, &len)); - unsigned char md5[MD5_DIGEST_LENGTH]; - MD5(str, len, md5); - char result[2 * MD5_DIGEST_LENGTH + 1]; + unsigned char* md5; + uint32_t md5_len; + md5_message(str, len, &md5, &md5_len); + char result[2 * md5_len + 1]; char* tmp = result; - for (int i = 0; i < MD5_DIGEST_LENGTH; i++) { + for (uint32_t i = 0; i < md5_len; i++) { *tmp = digit(md5[i] >> 4); ++tmp; *tmp = digit(md5[i] & 0xf); diff --git a/broker/lua/src/macro_cache.cc b/broker/lua/src/macro_cache.cc index 0c848b5d092..d043a51c218 100644 --- a/broker/lua/src/macro_cache.cc +++ b/broker/lua/src/macro_cache.cc @@ -17,11 +17,14 @@ */ #include "com/centreon/broker/lua/macro_cache.hh" +#include <absl/container/flat_hash_set.h> +#include <memory> #include "bbdo/bam/dimension_ba_bv_relation_event.hh" #include "bbdo/bam/dimension_ba_event.hh" #include "bbdo/bam/dimension_bv_event.hh" #include "bbdo/storage/index_mapping.hh" #include "bbdo/storage/metric_mapping.hh" +#include "com/centreon/broker/neb/internal.hh" #include "com/centreon/exceptions/msg_fmt.hh" #include "common/log_v2/log_v2.hh" @@ -367,17 +370,15 @@ macro_cache::get_host_group_members() const { * * @return The name of the host group. */ -std::string const& macro_cache::get_host_group_name(uint64_t id) const { - auto const found = _host_groups.find(id); +const std::string& macro_cache::get_host_group_name(uint64_t id) const { + const auto found = _host_groups.find(id); - if (found == _host_groups.end()) + if (found == _host_groups.end()) { + _cache->logger()->error("lua: could not find information on host group {}", + id); throw msg_fmt("lua: could not find information on host group {}", id); - if (found->second->type() == neb::host_group::static_type()) - return std::static_pointer_cast<neb::host_group>(found->second)->name; - else - return std::static_pointer_cast<neb::pb_host_group>(found->second) - ->obj() - .name(); + } + return found->second.first->obj().name(); } /** @@ -428,14 +429,12 @@ macro_cache::get_service_group_members() const { std::string const& macro_cache::get_service_group_name(uint64_t id) const { auto found = _service_groups.find(id); - if (found == _service_groups.end()) + if (found == _service_groups.end()) { + _cache->logger()->error( + "lua: could not find information on service group {}", id); throw msg_fmt("lua: could not find information on service group {}", id); - if (found->second->type() == neb::service_group::static_type()) - return std::static_pointer_cast<neb::service_group>(found->second)->name; - else - return std::static_pointer_cast<neb::pb_service_group>(found->second) - ->obj() - .name(); + } + return found->second.first->obj().name(); } /** @@ -830,14 +829,42 @@ void macro_cache::_process_pb_adaptive_host( * @param data The event. */ void macro_cache::_process_host_group(std::shared_ptr<io::data> const& data) { - std::shared_ptr<neb::host_group> const& hg = + const std::shared_ptr<neb::host_group>& hg = std::static_pointer_cast<neb::host_group>(data); SPDLOG_LOGGER_DEBUG(_cache->logger(), "lua: processing host group '{}' of id {} enabled: {}", hg->name, hg->id, hg->enabled); - if (hg->enabled) - _host_groups[hg->id] = data; - // erasure is desactivated because a group cen be owned by several pollers + if (hg->enabled) { + auto found = _host_groups.find(hg->id); + if (found != _host_groups.end()) { + /* here, we complete the set of pollers */ + found->second.second.insert(hg->poller_id); + found->second.first->mut_obj().set_name(hg->name); + } else { + /* Here, we add the hostgroup and the first poller that needs it */ + absl::flat_hash_set<uint32_t> pollers{hg->poller_id}; + auto pb_hg = std::make_shared<neb::pb_host_group>(); + auto& obj = pb_hg->mut_obj(); + obj.set_enabled(hg->enabled); + obj.set_hostgroup_id(hg->id); + obj.set_name(hg->name); + obj.set_poller_id(hg->poller_id); + _host_groups[hg->id] = std::make_pair(std::move(pb_hg), pollers); + } + } else { + /* We check that no more pollers need this host group. So if the set is + * empty, we can also remove the host group. */ + auto found = _host_groups.find(hg->id); + if (found != _host_groups.end()) { + auto f = found->second.second.find(hg->poller_id); + if (f != found->second.second.end()) { + found->second.second.erase(f); + if (found->second.second.empty()) { + _host_groups.erase(found); + } + } + } + } } /** @@ -846,15 +873,39 @@ void macro_cache::_process_host_group(std::shared_ptr<io::data> const& data) { * @param data The event. */ void macro_cache::_process_pb_host_group( - std::shared_ptr<io::data> const& data) { - const HostGroup& hg = - std::static_pointer_cast<neb::pb_host_group>(data)->obj(); + const std::shared_ptr<io::data>& data) { + auto pb_hg = std::static_pointer_cast<neb::pb_host_group>(data); + const HostGroup& hg = pb_hg->obj(); SPDLOG_LOGGER_DEBUG(_cache->logger(), "lua: processing pb host group '{}' of id {}, enabled {}", hg.name(), hg.hostgroup_id(), hg.enabled()); - if (hg.enabled()) - _host_groups[hg.hostgroup_id()] = data; - // erasure is desactivated because a group cen be owned by several pollers + if (hg.enabled()) { + auto found = _host_groups.find(hg.hostgroup_id()); + if (found != _host_groups.end()) { + found->second.second.insert(hg.poller_id()); + HostGroup& current_hg = + std::static_pointer_cast<neb::pb_host_group>(found->second.first) + ->mut_obj(); + current_hg.set_name(hg.name()); + } else { + absl::flat_hash_set<uint32_t> pollers{hg.poller_id()}; + _host_groups[hg.hostgroup_id()] = + std::make_pair(std::move(pb_hg), pollers); + } + } else { + /* We check that no more pollers need this host group. So if the set is + * empty, we can also remove the host group. */ + auto found = _host_groups.find(hg.hostgroup_id()); + if (found != _host_groups.end()) { + auto f = found->second.second.find(hg.poller_id()); + if (f != found->second.second.end()) { + found->second.second.erase(f); + if (found->second.second.empty()) { + _host_groups.erase(found); + } + } + } + } } /** @@ -1113,9 +1164,37 @@ void macro_cache::_process_service_group( SPDLOG_LOGGER_DEBUG(_cache->logger(), "lua: processing service group '{}' of id {}", sg->name, sg->id); - if (sg->enabled) - _service_groups[sg->id] = data; - // erasure is desactivated because a group cen be owned by several pollers + if (sg->enabled) { + auto found = _service_groups.find(sg->id); + if (found != _service_groups.end()) { + /* here, we complete the set of pollers */ + found->second.second.insert(sg->poller_id); + found->second.first->mut_obj().set_name(sg->name); + } else { + /* Here, we add the servicegroup and the first poller that needs it */ + absl::flat_hash_set<uint32_t> pollers{sg->poller_id}; + auto pb_sg = std::make_shared<neb::pb_service_group>(); + auto& obj = pb_sg->mut_obj(); + obj.set_servicegroup_id(sg->id); + obj.set_enabled(sg->enabled); + obj.set_name(sg->name); + obj.set_poller_id(sg->poller_id); + _service_groups[sg->id] = std::make_pair(std::move(pb_sg), pollers); + } + } else { + /* We check that no more pollers need this service group. So if the set is + * empty, we can also remove the service group. */ + auto found = _service_groups.find(sg->id); + if (found != _service_groups.end()) { + auto f = found->second.second.find(sg->poller_id); + if (f != found->second.second.end()) { + found->second.second.erase(f); + if (found->second.second.empty()) { + _service_groups.erase(found); + } + } + } + } } /** @@ -1124,15 +1203,38 @@ void macro_cache::_process_service_group( * @param sg The event. */ void macro_cache::_process_pb_service_group( - std::shared_ptr<io::data> const& data) { - const ServiceGroup& sg = - std::static_pointer_cast<neb::pb_service_group>(data)->obj(); + const std::shared_ptr<io::data>& data) { + auto pb_sg = std::static_pointer_cast<neb::pb_service_group>(data); + const ServiceGroup& sg = pb_sg->obj(); SPDLOG_LOGGER_DEBUG(_cache->logger(), "lua: processing pb service group '{}' of id {}", sg.name(), sg.servicegroup_id()); - if (sg.enabled()) - _service_groups[sg.servicegroup_id()] = data; - // erasure is desactivated because a group cen be owned by several pollers + if (sg.enabled()) { + auto found = _service_groups.find(sg.servicegroup_id()); + if (found != _service_groups.end()) { + found->second.second.insert(sg.poller_id()); + ServiceGroup& current_sg = found->second.first->mut_obj(); + current_sg.set_name(sg.name()); + } else { + /* Here, we add the servicegroup and the first poller that needs it */ + absl::flat_hash_set<uint32_t> pollers{sg.poller_id()}; + _service_groups[sg.servicegroup_id()] = + std::make_pair(std::move(pb_sg), pollers); + } + } else { + /* We check that no more pollers need this service group. So if the set is + * empty, we can also remove the service group. */ + auto found = _service_groups.find(sg.servicegroup_id()); + if (found != _service_groups.end()) { + auto f = found->second.second.find(sg.poller_id()); + if (f != found->second.second.end()) { + found->second.second.erase(f); + if (found->second.second.empty()) { + _service_groups.erase(found); + } + } + } + } } /** @@ -1166,12 +1268,12 @@ void macro_cache::_process_pb_service_group_member( std::shared_ptr<io::data> const& data) { const ServiceGroupMember& sgm = std::static_pointer_cast<neb::pb_service_group_member>(data)->obj(); - SPDLOG_LOGGER_DEBUG( - _cache->logger(), - "lua: processing pb service group member (group_name: {}, group_id: {}, " - "host_id: {}, service_id: {} enabled: {}", - sgm.name(), sgm.servicegroup_id(), sgm.host_id(), sgm.service_id(), - sgm.enabled()); + SPDLOG_LOGGER_DEBUG(_cache->logger(), + "lua: processing pb service group member (group_name: " + "{}, group_id: {}, " + "host_id: {}, service_id: {} enabled: {}", + sgm.name(), sgm.servicegroup_id(), sgm.host_id(), + sgm.service_id(), sgm.enabled()); if (sgm.enabled()) _service_group_members[std::make_tuple(sgm.host_id(), sgm.service_id(), sgm.servicegroup_id())] = data; @@ -1290,10 +1392,10 @@ void macro_cache::_process_dimension_ba_bv_relation_event( } else { auto const& rel = std::static_pointer_cast<bam::dimension_ba_bv_relation_event>(data); - SPDLOG_LOGGER_DEBUG( - _cache->logger(), - "lua: processing dimension ba bv relation event (ba_id: {}, bv_id: {})", - rel->ba_id, rel->bv_id); + SPDLOG_LOGGER_DEBUG(_cache->logger(), + "lua: processing dimension ba bv relation event " + "(ba_id: {}, bv_id: {})", + rel->ba_id, rel->bv_id); auto pb_data(std::make_shared<bam::pb_dimension_ba_bv_relation_event>()); pb_data->mut_obj().set_ba_id(rel->ba_id); pb_data->mut_obj().set_bv_id(rel->bv_id); @@ -1372,11 +1474,11 @@ void macro_cache::_process_custom_variable( std::shared_ptr<io::data> const& data) { auto const& cv = std::static_pointer_cast<neb::custom_variable>(data); if (cv->name == "CRITICALITY_LEVEL") { - SPDLOG_LOGGER_DEBUG( - _cache->logger(), - "lua: processing custom variable representing a criticality level for " - "host_id {} and service_id {} and level {}", - cv->host_id, cv->service_id, cv->value); + SPDLOG_LOGGER_DEBUG(_cache->logger(), + "lua: processing custom variable representing a " + "criticality level for " + "host_id {} and service_id {} and level {}", + cv->host_id, cv->service_id, cv->value); int32_t value = std::atoi(cv->value.c_str()); if (value) _custom_vars[{cv->host_id, cv->service_id}] = cv; @@ -1427,8 +1529,13 @@ void macro_cache::_save_to_disk() { for (auto it(_hosts.begin()), end(_hosts.end()); it != end; ++it) _cache->add(it->second); - for (auto it(_host_groups.begin()), end(_host_groups.end()); it != end; ++it) - _cache->add(it->second); + for (auto it = _host_groups.begin(), end = _host_groups.end(); it != end; + ++it) { + for (auto poller_id : it->second.second) { + it->second.first->mut_obj().set_poller_id(poller_id); + _cache->add(it->second.first); + } + } for (auto it(_host_group_members.begin()), end(_host_group_members.end()); it != end; ++it) @@ -1437,9 +1544,13 @@ void macro_cache::_save_to_disk() { for (auto it(_services.begin()), end(_services.end()); it != end; ++it) _cache->add(it->second); - for (auto it(_service_groups.begin()), end(_service_groups.end()); it != end; - ++it) - _cache->add(it->second); + for (auto it = _service_groups.begin(), end = _service_groups.end(); + it != end; ++it) { + for (auto poller_id : it->second.second) { + it->second.first->mut_obj().set_poller_id(poller_id); + _cache->add(it->second.first); + } + } for (auto it = _service_group_members.begin(), end = _service_group_members.end(); diff --git a/broker/lua/test/lua.cc b/broker/lua/test/lua.cc index d002cf5956d..8268c693b3e 100644 --- a/broker/lua/test/lua.cc +++ b/broker/lua/test/lua.cc @@ -1442,6 +1442,7 @@ TEST_F(LuaTest, ServiceGroupCacheTestName) { auto sg{std::make_shared<neb::service_group>()}; sg->id = 28; sg->name = "centreon"; + sg->enabled = true; _cache->write(sg); CreateScript(filename, From c1e3fa2896294359e8e24114e281bb8aabd4a915 Mon Sep 17 00:00:00 2001 From: David Boucher <dboucher@centreon.com> Date: Tue, 30 Jul 2024 20:41:11 +0200 Subject: [PATCH 920/948] fix(broker/simu): compilation warning removed REFS: MON-34072 MON-15480 --- broker/simu/src/luabinding.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/broker/simu/src/luabinding.cc b/broker/simu/src/luabinding.cc index 0b29503dedc..6aa5feabf61 100644 --- a/broker/simu/src/luabinding.cc +++ b/broker/simu/src/luabinding.cc @@ -28,7 +28,6 @@ using namespace com::centreon::exceptions; using namespace com::centreon::broker; using namespace com::centreon::broker::simu; -using com::centreon::common::log_v2::log_v2; /** * Constructor. @@ -39,7 +38,7 @@ using com::centreon::common::log_v2::log_v2; luabinding::luabinding(std::string const& lua_script, std::map<std::string, misc::variant> const& conf_params, const std::shared_ptr<spdlog::logger>& logger) - : _lua_script(lua_script), _total(0), _logger(logger) { + : _logger(logger), _lua_script(lua_script), _total(0) { size_t pos(lua_script.find_last_of('/')); std::string path(lua_script.substr(0, pos)); _L = _load_interpreter(); From 279e26e065e8af3b5074c611ec82f38f797bb50f Mon Sep 17 00:00:00 2001 From: David Boucher <dboucher@centreon.com> Date: Wed, 24 Jul 2024 18:55:01 +0200 Subject: [PATCH 921/948] enh(broker/misc): Header file reformatted REFS: MON-34072 MON-15480 --- broker/core/src/misc/string.cc | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/broker/core/src/misc/string.cc b/broker/core/src/misc/string.cc index 4bb8fa5f4d6..354669c0fcc 100644 --- a/broker/core/src/misc/string.cc +++ b/broker/core/src/misc/string.cc @@ -1,20 +1,20 @@ /** -* Copyright 2011-2013 Centreon -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -* For more information : contact@centreon.com -*/ + * Copyright 2011-2013 Centreon + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + */ #include "com/centreon/broker/misc/string.hh" #include "com/centreon/common/utf8.hh" From 85bd2849a0f85c1090b74b1ef7a6510b511220d9 Mon Sep 17 00:00:00 2001 From: David Boucher <dboucher@centreon.com> Date: Wed, 24 Jul 2024 18:56:14 +0200 Subject: [PATCH 922/948] enh(broker/neb): fixes in the neb stream * Integration of the new protobuf engine configuration * bug on hostgroups handling fixed * bug on servicegroups handling fixed REFS: MON-34072 MON-15480 --- broker/neb/src/callbacks.cc | 60 ++++++++++++++++++++----------------- broker/neb/src/initial.cc | 14 ++++----- 2 files changed, 40 insertions(+), 34 deletions(-) diff --git a/broker/neb/src/callbacks.cc b/broker/neb/src/callbacks.cc index bad963e9914..ad30691a7d2 100644 --- a/broker/neb/src/callbacks.cc +++ b/broker/neb/src/callbacks.cc @@ -53,7 +53,6 @@ using namespace com::centreon::broker; using namespace com::centreon::exceptions; -using com::centreon::common::log_v2::log_v2; // List of Nagios modules. extern nebmodule* neb_module_list; @@ -462,7 +461,7 @@ int neb::callback_pb_custom_variable(int, void* data) { std::make_shared<neb::pb_custom_variable>(); neb::pb_custom_variable::pb_type& obj = cv->mut_obj(); bool ok_to_send = false; - if (cvar && cvar->var_name && cvar->var_value) { + if (cvar && !cvar->var_name.empty() && !cvar->var_value.empty()) { // Host custom variable. if (NEBTYPE_HOSTCUSTOMVARIABLE_ADD == cvar->type || NEBTYPE_HOSTCUSTOMVARIABLE_DELETE == cvar->type) { @@ -566,7 +565,7 @@ int neb::callback_custom_variable(int callback_type, void* data) { // Input variable. nebstruct_custom_variable_data const* cvar( static_cast<nebstruct_custom_variable_data*>(data)); - if (cvar && cvar->var_name && cvar->var_value) { + if (cvar && !cvar->var_name.empty() && !cvar->var_value.empty()) { // Host custom variable. if (NEBTYPE_HOSTCUSTOMVARIABLE_ADD == cvar->type) { engine::host* hst(static_cast<engine::host*>(cvar->object_ptr)); @@ -1406,7 +1405,8 @@ int neb::callback_group(int callback_type, void* data) { auto new_hg{std::make_shared<neb::host_group>()}; new_hg->poller_id = config::applier::state::instance().poller_id(); new_hg->id = host_group->get_id(); - new_hg->enabled = (group_data->type != NEBTYPE_HOSTGROUP_DELETE && + new_hg->enabled = group_data->type == NEBTYPE_HOSTGROUP_ADD || + (group_data->type == NEBTYPE_ADAPTIVEHOST_UPDATE && !host_group->members.empty()); new_hg->name = common::check_string_utf8(host_group->get_group_name()); @@ -1436,7 +1436,8 @@ int neb::callback_group(int callback_type, void* data) { auto new_sg{std::make_shared<neb::service_group>()}; new_sg->poller_id = config::applier::state::instance().poller_id(); new_sg->id = service_group->get_id(); - new_sg->enabled = (group_data->type != NEBTYPE_SERVICEGROUP_DELETE && + new_sg->enabled = group_data->type == NEBTYPE_SERVICEGROUP_ADD || + (group_data->type == NEBTYPE_SERVICEGROUP_UPDATE && !service_group->members.empty()); new_sg->name = common::check_string_utf8(service_group->get_group_name()); @@ -1484,26 +1485,26 @@ int neb::callback_pb_group(int callback_type, void* data) { nebstruct_group_data const* group_data( static_cast<nebstruct_group_data*>(data)); - SPDLOG_LOGGER_DEBUG(neb_logger, - "callbacks: generating pb group event type:{}", - group_data->type); - // Host group. if ((NEBTYPE_HOSTGROUP_ADD == group_data->type) || (NEBTYPE_HOSTGROUP_UPDATE == group_data->type) || (NEBTYPE_HOSTGROUP_DELETE == group_data->type)) { engine::hostgroup const* host_group( static_cast<engine::hostgroup*>(group_data->object_ptr)); + SPDLOG_LOGGER_DEBUG( + neb_logger, + "callbacks: generating pb host group {} (id: {}) event type:{}", + host_group->get_group_name(), host_group->get_id(), group_data->type); + if (!host_group->get_group_name().empty()) { auto new_hg{std::make_shared<neb::pb_host_group>()}; - new_hg->mut_obj().set_poller_id( - config::applier::state::instance().poller_id()); - new_hg->mut_obj().set_hostgroup_id(host_group->get_id()); - new_hg->mut_obj().set_enabled(group_data->type != - NEBTYPE_HOSTGROUP_DELETE && - !host_group->members.empty()); - new_hg->mut_obj().set_name( - common::check_string_utf8(host_group->get_group_name())); + auto& obj = new_hg->mut_obj(); + obj.set_poller_id(config::applier::state::instance().poller_id()); + obj.set_hostgroup_id(host_group->get_id()); + obj.set_enabled(group_data->type == NEBTYPE_HOSTGROUP_ADD || + (group_data->type == NEBTYPE_HOSTGROUP_UPDATE && + !host_group->members.empty())); + obj.set_name(common::check_string_utf8(host_group->get_group_name())); // Send host group event. if (host_group->get_id()) { @@ -1532,16 +1533,21 @@ int neb::callback_pb_group(int callback_type, void* data) { (NEBTYPE_SERVICEGROUP_DELETE == group_data->type)) { engine::servicegroup const* service_group( static_cast<engine::servicegroup*>(group_data->object_ptr)); + SPDLOG_LOGGER_DEBUG( + neb_logger, + "callbacks: generating pb host group {} (id: {}) event type:{}", + service_group->get_group_name(), service_group->get_id(), + group_data->type); + if (!service_group->get_group_name().empty()) { auto new_sg{std::make_shared<neb::pb_service_group>()}; - new_sg->mut_obj().set_poller_id( - config::applier::state::instance().poller_id()); - new_sg->mut_obj().set_servicegroup_id(service_group->get_id()); - new_sg->mut_obj().set_enabled(group_data->type != - NEBTYPE_SERVICEGROUP_DELETE && - !service_group->members.empty()); - new_sg->mut_obj().set_name( - common::check_string_utf8(service_group->get_group_name())); + auto& obj = new_sg->mut_obj(); + obj.set_poller_id(config::applier::state::instance().poller_id()); + obj.set_servicegroup_id(service_group->get_id()); + obj.set_enabled(group_data->type == NEBTYPE_SERVICEGROUP_ADD || + (group_data->type == NEBTYPE_SERVICEGROUP_UPDATE && + !service_group->members.empty())); + obj.set_name(common::check_string_utf8(service_group->get_group_name())); // Send service group event. if (service_group->get_id()) { @@ -1698,8 +1704,8 @@ int neb::callback_pb_group_member(int callback_type, void* data) { static_cast<nebstruct_group_member_data*>(data)); // Host group member. - if ((member_data->type == NEBTYPE_HOSTGROUPMEMBER_ADD) || - (member_data->type == NEBTYPE_HOSTGROUPMEMBER_DELETE)) { + if (member_data->type == NEBTYPE_HOSTGROUPMEMBER_ADD || + member_data->type == NEBTYPE_HOSTGROUPMEMBER_DELETE) { engine::host const* hst( static_cast<engine::host*>(member_data->object_ptr)); engine::hostgroup const* hg( diff --git a/broker/neb/src/initial.cc b/broker/neb/src/initial.cc index a8fad65920e..4144c6fe822 100644 --- a/broker/neb/src/initial.cc +++ b/broker/neb/src/initial.cc @@ -98,13 +98,13 @@ static void send_custom_variables_list( std::string name{cit->first}; if (cit->second.is_sent()) { // Fill callback struct. - nebstruct_custom_variable_data nscvd; - memset(&nscvd, 0, sizeof(nscvd)); - nscvd.type = NEBTYPE_SERVICECUSTOMVARIABLE_ADD; - nscvd.timestamp.tv_sec = time(nullptr); - nscvd.var_name = const_cast<char*>(name.c_str()); - nscvd.var_value = const_cast<char*>(cit->second.value().c_str()); - nscvd.object_ptr = it->second.get(); + nebstruct_custom_variable_data nscvd{ + .type = NEBTYPE_SERVICECUSTOMVARIABLE_ADD, + .timestamp = {time(nullptr), 0}, + .var_name = std::string_view(name), + .var_value = std::string_view(cit->second.value()), + .object_ptr = it->second.get(), + }; // Callback. sender(NEBCALLBACK_CUSTOM_VARIABLE_DATA, &nscvd); From 82ff2ea5b48d97bdb4847b94d6735efe1fef73ad Mon Sep 17 00:00:00 2001 From: David Boucher <dboucher@centreon.com> Date: Mon, 5 Aug 2024 11:58:08 +0200 Subject: [PATCH 923/948] fix(broker/rebuilder): values are floats and not doubles and some trace logs are added REFS: MON-34072 MON-15480 --- .../centreon/broker/unified_sql/rebuilder.hh | 3 ++- broker/unified_sql/src/rebuilder.cc | 17 +++++++++++------ broker/unified_sql/src/stream.cc | 2 +- broker/unified_sql/src/stream_storage.cc | 2 +- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/broker/unified_sql/inc/com/centreon/broker/unified_sql/rebuilder.hh b/broker/unified_sql/inc/com/centreon/broker/unified_sql/rebuilder.hh index b8ccb624625..4705374db1d 100644 --- a/broker/unified_sql/inc/com/centreon/broker/unified_sql/rebuilder.hh +++ b/broker/unified_sql/inc/com/centreon/broker/unified_sql/rebuilder.hh @@ -71,7 +71,8 @@ class rebuilder { ~rebuilder() noexcept; rebuilder(const rebuilder&) = delete; rebuilder& operator=(const rebuilder&) = delete; - void rebuild_graphs(const std::shared_ptr<io::data>& d); + void rebuild_graphs(const std::shared_ptr<io::data>& d, + const std::shared_ptr<spdlog::logger>& logger); }; } // namespace unified_sql diff --git a/broker/unified_sql/src/rebuilder.cc b/broker/unified_sql/src/rebuilder.cc index 0f75add35f6..d3e085a37f4 100644 --- a/broker/unified_sql/src/rebuilder.cc +++ b/broker/unified_sql/src/rebuilder.cc @@ -67,10 +67,10 @@ rebuilder::~rebuilder() noexcept { * * @param d The BBDO message with all the metric ids to rebuild. */ -void rebuilder::rebuild_graphs(const std::shared_ptr<io::data>& d) { +void rebuilder::rebuild_graphs(const std::shared_ptr<io::data>& d, + const std::shared_ptr<spdlog::logger>& logger) { asio::post(com::centreon::common::pool::io_context(), [this, data = d, - logger = - log_v2::instance().get(log_v2::SQL)] { + logger] { { std::lock_guard<std::mutex> lck(_rebuilding_m); _rebuilding++; @@ -190,16 +190,21 @@ void rebuilder::rebuild_graphs(const std::shared_ptr<io::data>& d) { while (ms.fetch_row(res)) { uint64_t id_metric = res.value_as_u64(0); time_t ctime = res.value_as_u64(1); - double value = res.value_as_f64(2); + float value = res.value_as_f32(2); uint32_t status = res.value_as_u32(3); // duplicate values not allowed by rrd library auto yet_inserted = last_inserted.find(id_metric); if (yet_inserted != last_inserted.end()) { - if (yet_inserted->second >= ctime) + if (yet_inserted->second >= ctime) { + logger->trace("Metric {} too old to be inserted: {} >= {}", + id_metric, yet_inserted->second, ctime); continue; - else + } else { + logger->trace("Metric {} updated at {}", id_metric, ctime); yet_inserted->second = ctime; + } } else { + logger->trace("Metric {} inserted at {}", id_metric, ctime); last_inserted[id_metric] = ctime; } Point* pt = diff --git a/broker/unified_sql/src/stream.cc b/broker/unified_sql/src/stream.cc index eaaa899f879..32435ba2a67 100644 --- a/broker/unified_sql/src/stream.cc +++ b/broker/unified_sql/src/stream.cc @@ -733,7 +733,7 @@ int32_t stream::write(const std::shared_ptr<io::data>& data) { } else if (cat == io::bbdo) { switch (elem) { case bbdo::de_rebuild_graphs: - _rebuilder.rebuild_graphs(data); + _rebuilder.rebuild_graphs(data, _logger_sql); break; case bbdo::de_remove_graphs: remove_graphs(data); diff --git a/broker/unified_sql/src/stream_storage.cc b/broker/unified_sql/src/stream_storage.cc index 5c7968c93f6..716cbad31c1 100644 --- a/broker/unified_sql/src/stream_storage.cc +++ b/broker/unified_sql/src/stream_storage.cc @@ -1117,6 +1117,6 @@ void stream::_check_rebuild_index() { auto& obj = rg->mut_obj(); for (auto& i : index_to_rebuild) obj.add_index_ids(i); - _rebuilder.rebuild_graphs(rg); + _rebuilder.rebuild_graphs(rg, _logger_sql); } } From 1a7a86826517da427493618d86de8aa21ab4254f Mon Sep 17 00:00:00 2001 From: David Boucher <dboucher@centreon.com> Date: Wed, 24 Jul 2024 18:56:44 +0200 Subject: [PATCH 924/948] fix(broker/victoria_metrics): warning removed REFS: MON-34072 MON-15480 --- broker/victoria_metrics/src/request.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/broker/victoria_metrics/src/request.cc b/broker/victoria_metrics/src/request.cc index 0994528a0b3..431ad991dd7 100644 --- a/broker/victoria_metrics/src/request.cc +++ b/broker/victoria_metrics/src/request.cc @@ -69,7 +69,6 @@ request::request(boost::beast::http::verb method, static constexpr std::string_view _sz_metric = "metric,id="; static constexpr std::string_view _sz_status = "status,id="; -static constexpr std::string_view _sz_space = " "; static constexpr std::string_view _sz_name = ",name="; static constexpr std::string_view _sz_unit = ",unit="; static constexpr std::string_view _sz_host_id = ",host_id="; From d84d6bc8f0ac9085997dfa651f0dfed94b281f7e Mon Sep 17 00:00:00 2001 From: David Boucher <dboucher@centreon.com> Date: Wed, 24 Jul 2024 19:09:38 +0200 Subject: [PATCH 925/948] cleanup(broker/core): little improvement to follow new standard REFS: MON-34072 MON-15480 --- broker/core/inc/com/centreon/broker/time/timeperiod.hh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/broker/core/inc/com/centreon/broker/time/timeperiod.hh b/broker/core/inc/com/centreon/broker/time/timeperiod.hh index 5523ac9bb02..a83fd4e2d91 100644 --- a/broker/core/inc/com/centreon/broker/time/timeperiod.hh +++ b/broker/core/inc/com/centreon/broker/time/timeperiod.hh @@ -97,7 +97,7 @@ class timeperiod { bool set_timerange(std::string const& timerange_text, int day); std::list<timerange> const& get_timeranges_by_day(int day) const throw(); - std::string const& get_timezone() const throw(); + std::string const& get_timezone() const noexcept; void set_timezone(std::string const& tz); bool is_valid(time_t preferred_time) const; @@ -120,6 +120,6 @@ class timeperiod { }; } // namespace time -} +} // namespace com::centreon::broker #endif // !CCB_CORE_TIME_TIMEPERIOD_HH From 0fa3cbc0b215b6cc4685cf8ea1c2cb16b51f9c32 Mon Sep 17 00:00:00 2001 From: David Boucher <dboucher@centreon.com> Date: Wed, 24 Jul 2024 18:14:41 +0200 Subject: [PATCH 926/948] enh(broker): HostDependencies and ServiceDependencies are not used by broker. REFS: MON-34072 MON-15480 --- CMakeListsLinux.txt | 4 +- .../com/centreon/broker/sql/mysql_error.hh | 162 ++++--- broker/neb/CMakeLists.txt | 8 - .../inc/com/centreon/broker/neb/callbacks.hh | 2 - .../inc/com/centreon/broker/neb/dependency.hh | 59 --- .../neb/inc/com/centreon/broker/neb/events.hh | 2 - .../centreon/broker/neb/host_dependency.hh | 55 --- .../inc/com/centreon/broker/neb/initial.hh | 10 +- .../inc/com/centreon/broker/neb/internal.hh | 8 - .../centreon/broker/neb/service_dependency.hh | 61 --- broker/neb/src/broker.cc | 15 - broker/neb/src/callbacks.cc | 313 -------------- broker/neb/src/dependency.cc | 88 ---- broker/neb/src/host_dependency.cc | 101 ----- broker/neb/src/initial.cc | 91 ---- broker/neb/src/service_dependency.cc | 125 ------ broker/neb/test/host_dependency.cc | 97 ----- broker/neb/test/randomize.cc | 8 +- broker/neb/test/service_dependency.cc | 100 ----- .../broker/storage/conflict_manager.hh | 36 +- broker/storage/src/conflict_manager.cc | 7 +- broker/storage/src/conflict_manager_sql.cc | 182 +------- .../com/centreon/broker/unified_sql/stream.hh | 42 +- broker/unified_sql/src/stream.cc | 27 +- broker/unified_sql/src/stream_sql.cc | 406 +++--------------- broker/unified_sql/src/stream_storage.cc | 9 +- common/inc/com/centreon/common/utf8.hh | 4 +- common/src/utf8.cc | 6 +- engine/inc/com/centreon/engine/nebstructs.hh | 2 +- tests/broker-engine/bbdo-protobuf.robot | 72 ---- tests/resources/Common.py | 50 --- 31 files changed, 190 insertions(+), 1962 deletions(-) delete mode 100644 broker/neb/inc/com/centreon/broker/neb/dependency.hh delete mode 100644 broker/neb/inc/com/centreon/broker/neb/host_dependency.hh delete mode 100644 broker/neb/inc/com/centreon/broker/neb/service_dependency.hh delete mode 100644 broker/neb/src/dependency.cc delete mode 100644 broker/neb/src/host_dependency.cc delete mode 100644 broker/neb/src/service_dependency.cc delete mode 100644 broker/neb/test/host_dependency.cc delete mode 100644 broker/neb/test/service_dependency.cc diff --git a/CMakeListsLinux.txt b/CMakeListsLinux.txt index a7b4120a02b..a3a47dcd080 100644 --- a/CMakeListsLinux.txt +++ b/CMakeListsLinux.txt @@ -20,7 +20,6 @@ # Global settings. # - option(WITH_ASAN "Add the libasan to check memory leaks and other memory issues." OFF) @@ -164,7 +163,8 @@ include_directories(${CMAKE_SOURCE_DIR} ${VCPKG_INCLUDE_DIR} fmt::fmt spdlog::spdlog - ${CMAKE_SOURCE_DIR}/clib/inc) + ${CMAKE_SOURCE_DIR}/clib/inc + ${CMAKE_CURRENT_BINARY_DIR}) add_subdirectory(clib) add_subdirectory(common) diff --git a/broker/core/sql/inc/com/centreon/broker/sql/mysql_error.hh b/broker/core/sql/inc/com/centreon/broker/sql/mysql_error.hh index 0afb7938210..d7af4120bc1 100644 --- a/broker/core/sql/inc/com/centreon/broker/sql/mysql_error.hh +++ b/broker/core/sql/inc/com/centreon/broker/sql/mysql_error.hh @@ -33,87 +33,83 @@ namespace database { class mysql_error { public: enum code { - empty, - clean_hosts_services, - clean_hostgroup_members, - clean_servicegroup_members, - clean_empty_hostgroups, - clean_empty_servicegroups, - clean_host_dependencies, - clean_service_dependencies, - clean_host_parents, - clean_modules, - clean_downtimes, - clean_comments, - clean_customvariables, - restore_instances, - update_customvariables, - update_logs, - update_metrics, - insert_data, - delete_metric, - delete_index, - flag_index_data, - delete_hosts, - delete_modules, - update_index_state, - delete_availabilities, - insert_availability, - rebuild_ba, - close_event, - close_ba_events, - close_kpi_events, - delete_ba_durations, - store_host_state, - store_acknowledgement, - store_comment, - remove_customvariable, - store_customvariable, - store_downtime, - store_eventhandler, - store_flapping, - store_host_check, - store_host_dependency, - store_host_group, - store_host_group_member, - delete_host_group_member, - store_host, - store_host_parentship, - store_host_status, - store_poller, - update_poller, - store_module, - store_service_check_command, - store_service_dependency, - store_service_group, - store_service_group_member, - delete_service_group_member, - store_service, - store_service_status, - update_ba, - update_kpi, - update_kpi_event, - insert_kpi_event, - insert_ba, - insert_bv, - insert_dimension_ba_bv, - truncate_dimension_table, - insert_dimension_kpi, - insert_timeperiod, - insert_timeperiod_exception, - insert_exclusion_timeperiod, - insert_relation_ba_timeperiod, - store_severity, - clean_severities, - store_tag, - clean_resources_tags, - update_index_data, - update_resources, - store_host_resources, - store_tags_resources_tags, - delete_resources_tags, - clean_resources, - delete_poller, + empty = 0, + clean_hosts_services = 1, + clean_hostgroup_members = 2, + clean_servicegroup_members = 3, + clean_empty_hostgroups = 4, + clean_empty_servicegroups = 5, + clean_host_parents = 6, + clean_modules = 7, + clean_downtimes = 8, + clean_comments = 9, + clean_customvariables = 10, + restore_instances = 11, + update_customvariables = 12, + update_logs = 13, + update_metrics = 14, + insert_data = 15, + delete_metric = 16, + delete_index = 17, + flag_index_data = 18, + delete_hosts = 19, + delete_modules = 20, + update_index_state = 21, + delete_availabilities = 22, + insert_availability = 23, + rebuild_ba = 24, + close_event = 25, + close_ba_events = 26, + close_kpi_events = 27, + delete_ba_durations = 28, + store_host_state = 29, + store_acknowledgement = 30, + store_comment = 31, + remove_customvariable = 32, + store_customvariable = 33, + store_downtime = 34, + store_eventhandler = 35, + store_flapping = 36, + store_host_check = 37, + store_host_group = 38, + store_host_group_member = 39, + delete_host_group_member = 40, + store_host = 41, + store_host_parentship = 42, + store_host_status = 43, + store_poller = 44, + update_poller = 45, + store_module = 46, + store_service_check_command = 47, + store_service_group = 48, + store_service_group_member = 49, + delete_service_group_member = 50, + store_service = 51, + store_service_status = 52, + update_ba = 53, + update_kpi = 54, + update_kpi_event = 55, + insert_kpi_event = 56, + insert_ba = 57, + insert_bv = 58, + insert_dimension_ba_bv = 59, + truncate_dimension_table = 60, + insert_dimension_kpi = 61, + insert_timeperiod = 62, + insert_timeperiod_exception = 63, + insert_exclusion_timeperiod = 64, + insert_relation_ba_timeperiod = 65, + store_severity = 66, + clean_severities = 67, + store_tag = 68, + clean_resources_tags = 69, + update_index_data = 70, + update_resources = 71, + store_host_resources = 72, + store_tags_resources_tags = 73, + delete_resources_tags = 74, + clean_resources = 75, + delete_poller = 76, }; static constexpr const char* msg[]{ @@ -123,8 +119,6 @@ class mysql_error { "could not clean service groups memberships table: ", "could not remove empty host groups: ", "could not remove empty service groups: ", - "could not clean host dependencies table: ", - "could not clean service dependencies table: ", "could not clean host parents table: ", "could not clean modules table: ", "could not clean downtimes table: ", @@ -158,7 +152,6 @@ class mysql_error { "could not store event handler: ", "could not store flapping status: ", "could not store host check: ", - "could not store host dependency: ", "could not store host group: ", "could not store host group membership: ", "could not delete membership of host to host group: ", @@ -169,7 +162,6 @@ class mysql_error { "could not update poller: ", "could not store module: ", "could not store service check command: ", - "could not store service dependency: ", "could not store service group: ", "could not store service group membership: ", "could not delete membersjip of service to service group: ", diff --git a/broker/neb/CMakeLists.txt b/broker/neb/CMakeLists.txt index 50fa3d67cca..ccc9e004133 100644 --- a/broker/neb/CMakeLists.txt +++ b/broker/neb/CMakeLists.txt @@ -31,13 +31,11 @@ set(NEB_SOURCES ${SRC_DIR}/comment.cc ${SRC_DIR}/custom_variable.cc ${SRC_DIR}/custom_variable_status.cc - ${SRC_DIR}/dependency.cc ${SRC_DIR}/downtime.cc ${SRC_DIR}/group.cc ${SRC_DIR}/group_member.cc ${SRC_DIR}/host.cc ${SRC_DIR}/host_check.cc - ${SRC_DIR}/host_dependency.cc ${SRC_DIR}/host_group.cc ${SRC_DIR}/host_group_member.cc ${SRC_DIR}/host_parent.cc @@ -51,7 +49,6 @@ set(NEB_SOURCES ${SRC_DIR}/responsive_instance.cc ${SRC_DIR}/service.cc ${SRC_DIR}/service_check.cc - ${SRC_DIR}/service_dependency.cc ${SRC_DIR}/service_group.cc ${SRC_DIR}/service_group_member.cc ${SRC_DIR}/service_status.cc @@ -61,14 +58,12 @@ set(NEB_SOURCES ${INC_DIR}/com/centreon/broker/neb/comment.hh ${INC_DIR}/com/centreon/broker/neb/custom_variable.hh ${INC_DIR}/com/centreon/broker/neb/custom_variable_status.hh - ${INC_DIR}/com/centreon/broker/neb/dependency.hh ${INC_DIR}/com/centreon/broker/neb/downtime.hh ${INC_DIR}/com/centreon/broker/neb/events.hh ${INC_DIR}/com/centreon/broker/neb/group.hh ${INC_DIR}/com/centreon/broker/neb/group_member.hh ${INC_DIR}/com/centreon/broker/neb/host.hh ${INC_DIR}/com/centreon/broker/neb/host_check.hh - ${INC_DIR}/com/centreon/broker/neb/host_dependency.hh ${INC_DIR}/com/centreon/broker/neb/host_group.hh ${INC_DIR}/com/centreon/broker/neb/host_group_member.hh ${INC_DIR}/com/centreon/broker/neb/host_parent.hh @@ -83,7 +78,6 @@ set(NEB_SOURCES ${INC_DIR}/com/centreon/broker/neb/responsive_instance.hh ${INC_DIR}/com/centreon/broker/neb/service.hh ${INC_DIR}/com/centreon/broker/neb/service_check.hh - ${INC_DIR}/com/centreon/broker/neb/service_dependency.hh ${INC_DIR}/com/centreon/broker/neb/service_group.hh ${INC_DIR}/com/centreon/broker/neb/service_group_member.hh ${INC_DIR}/com/centreon/broker/neb/service_status.hh @@ -183,7 +177,6 @@ if(WITH_TESTING) ${TEST_DIR}/custom_variable_status.cc ${TEST_DIR}/host.cc ${TEST_DIR}/host_check.cc - ${TEST_DIR}/host_dependency.cc ${TEST_DIR}/host_parent.cc ${TEST_DIR}/host_status.cc ${TEST_DIR}/instance.cc @@ -193,7 +186,6 @@ if(WITH_TESTING) ${TEST_DIR}/randomize.hh ${TEST_DIR}/service.cc ${TEST_DIR}/service_check.cc - ${TEST_DIR}/service_dependency.cc ${TEST_DIR}/service_status.cc ${TEST_DIR}/set_log_data.cc PARENT_SCOPE) diff --git a/broker/neb/inc/com/centreon/broker/neb/callbacks.hh b/broker/neb/inc/com/centreon/broker/neb/callbacks.hh index 4b226171c5b..f9ee10a1cb1 100644 --- a/broker/neb/inc/com/centreon/broker/neb/callbacks.hh +++ b/broker/neb/inc/com/centreon/broker/neb/callbacks.hh @@ -34,8 +34,6 @@ int callback_comment(int callback_type, void* data); int callback_pb_comment(int callback_type, void* data); int callback_custom_variable(int callback_type, void* data); int callback_pb_custom_variable(int callback_type, void* data); -int callback_dependency(int callback_type, void* data); -int callback_pb_dependency(int callback_type, void* data); int callback_downtime(int callback_type, void* data); int callback_pb_downtime(int callback_type, void* data); int callback_external_command(int callback_type, void* data); diff --git a/broker/neb/inc/com/centreon/broker/neb/dependency.hh b/broker/neb/inc/com/centreon/broker/neb/dependency.hh deleted file mode 100644 index 8b165e7a3ee..00000000000 --- a/broker/neb/inc/com/centreon/broker/neb/dependency.hh +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright 2009-2013,2015 Centreon - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * For more information : contact@centreon.com - */ - -#ifndef CCB_NEB_DEPENDENCY_HH -#define CCB_NEB_DEPENDENCY_HH - -#include "com/centreon/broker/io/data.hh" - -namespace com::centreon::broker { - -namespace neb { -/** - * @class dependency dependency.hh "com/centreon/broker/neb/dependency.hh" - * @brief Dependency relationship. - * - * Defines a dependency between two objects. - * - * @see host_dependency - * @see service_dependency - */ -class dependency : public io::data { - public: - dependency() = delete; - dependency(uint32_t type); - dependency(dependency const& dep); - virtual ~dependency(); - dependency& operator=(dependency const& dep); - - std::string dependency_period; - uint32_t dependent_host_id; - bool enabled; - std::string execution_failure_options; - uint32_t host_id; - bool inherits_parent; - std::string notification_failure_options; - - private: - void _internal_copy(dependency const& dep); -}; -} // namespace neb - -} - -#endif // !CCB_NEB_DEPENDENCY_HH diff --git a/broker/neb/inc/com/centreon/broker/neb/events.hh b/broker/neb/inc/com/centreon/broker/neb/events.hh index 25b292c5e75..d364d16fc23 100644 --- a/broker/neb/inc/com/centreon/broker/neb/events.hh +++ b/broker/neb/inc/com/centreon/broker/neb/events.hh @@ -26,7 +26,6 @@ #include "com/centreon/broker/neb/downtime.hh" #include "com/centreon/broker/neb/host.hh" #include "com/centreon/broker/neb/host_check.hh" -#include "com/centreon/broker/neb/host_dependency.hh" #include "com/centreon/broker/neb/host_group.hh" #include "com/centreon/broker/neb/host_group_member.hh" #include "com/centreon/broker/neb/host_parent.hh" @@ -38,7 +37,6 @@ #include "com/centreon/broker/neb/responsive_instance.hh" #include "com/centreon/broker/neb/service.hh" #include "com/centreon/broker/neb/service_check.hh" -#include "com/centreon/broker/neb/service_dependency.hh" #include "com/centreon/broker/neb/service_group.hh" #include "com/centreon/broker/neb/service_group_member.hh" #include "com/centreon/broker/neb/service_status.hh" diff --git a/broker/neb/inc/com/centreon/broker/neb/host_dependency.hh b/broker/neb/inc/com/centreon/broker/neb/host_dependency.hh deleted file mode 100644 index 5ab87f8131c..00000000000 --- a/broker/neb/inc/com/centreon/broker/neb/host_dependency.hh +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Copyright 2009-2012 Centreon - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * For more information : contact@centreon.com - */ - -#ifndef CCB_NEB_HOST_DEPENDENCY_HH -#define CCB_NEB_HOST_DEPENDENCY_HH - -#include "com/centreon/broker/io/event_info.hh" -#include "com/centreon/broker/io/events.hh" -#include "com/centreon/broker/mapping/entry.hh" -#include "com/centreon/broker/neb/dependency.hh" -#include "com/centreon/broker/neb/internal.hh" - -namespace com::centreon::broker { - -namespace neb { -/** - * @class host_dependency host_dependency.hh - * "com/centreon/broker/neb/host_dependency.hh" - * @brief Host dependency relationship. - * - * Defines a dependency between two hosts. - */ -class host_dependency : public dependency { - public: - host_dependency(); - host_dependency(host_dependency const& other); - ~host_dependency(); - host_dependency& operator=(host_dependency const& other); - constexpr static uint32_t static_type() { - return io::events::data_type<io::neb, neb::de_host_dependency>::value; - } - - static mapping::entry const entries[]; - static io::event_info::event_operations const operations; -}; -} // namespace neb - -} - -#endif // !CCB_NEB_HOST_DEPENDENCY_HH diff --git a/broker/neb/inc/com/centreon/broker/neb/initial.hh b/broker/neb/inc/com/centreon/broker/neb/initial.hh index 4556e4b699a..730beaff290 100644 --- a/broker/neb/inc/com/centreon/broker/neb/initial.hh +++ b/broker/neb/inc/com/centreon/broker/neb/initial.hh @@ -19,15 +19,9 @@ #ifndef CCB_NEB_INITIAL_HH_ #define CCB_NEB_INITIAL_HH_ -namespace com { -namespace centreon { -namespace broker { -namespace neb { +namespace com::centreon::broker::neb { void send_initial_configuration(); void send_initial_pb_configuration(); -} // namespace neb -} // namespace broker -} // namespace centreon -} // namespace com +} // namespace com::centreon::broker::neb #endif /* !CCB_NEB_INITIAL_HH_ */ diff --git a/broker/neb/inc/com/centreon/broker/neb/internal.hh b/broker/neb/inc/com/centreon/broker/neb/internal.hh index b3fba5da256..c12f0b660d3 100644 --- a/broker/neb/inc/com/centreon/broker/neb/internal.hh +++ b/broker/neb/inc/com/centreon/broker/neb/internal.hh @@ -99,14 +99,6 @@ using pb_acknowledgement = io::protobuf<Acknowledgement, make_type(io::neb, neb::de_pb_acknowledgement)>; -using pb_host_dependency = - io::protobuf<HostDependency, - make_type(io::neb, neb::de_pb_host_dependency)>; - -using pb_service_dependency = - io::protobuf<ServiceDependency, - make_type(io::neb, neb::de_pb_service_dependency)>; - using pb_host_group = io::protobuf<HostGroup, make_type(io::neb, neb::de_pb_host_group)>; diff --git a/broker/neb/inc/com/centreon/broker/neb/service_dependency.hh b/broker/neb/inc/com/centreon/broker/neb/service_dependency.hh deleted file mode 100644 index adf3e7665ae..00000000000 --- a/broker/neb/inc/com/centreon/broker/neb/service_dependency.hh +++ /dev/null @@ -1,61 +0,0 @@ -/* -** Copyright 2009-2012 Centreon -** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at -** -** http://www.apache.org/licenses/LICENSE-2.0 -** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and -** limitations under the License. -** -** For more information : contact@centreon.com -*/ - -#ifndef CCB_NEB_SERVICE_DEPENDENCY_HH -#define CCB_NEB_SERVICE_DEPENDENCY_HH - -#include "com/centreon/broker/io/event_info.hh" -#include "com/centreon/broker/io/events.hh" -#include "com/centreon/broker/mapping/entry.hh" -#include "com/centreon/broker/neb/dependency.hh" -#include "com/centreon/broker/neb/internal.hh" - -namespace com::centreon::broker { - -namespace neb { -/** - * @class service_dependency service_dependency.hh - * "com/centreon/broker/neb/service_dependency.hh" - * @brief Service dependency relationship. - * - * Defines a dependency between two services. - */ -class service_dependency : public dependency { - public: - service_dependency(); - service_dependency(service_dependency const& other); - ~service_dependency(); - service_dependency& operator=(service_dependency const& other); - constexpr static uint32_t static_type() { - return io::events::data_type<io::neb, neb::de_service_dependency>::value; - } - - uint32_t dependent_service_id; - uint32_t service_id; - - static mapping::entry const entries[]; - static io::event_info::event_operations const operations; - - private: - void _internal_copy(service_dependency const& other); -}; -} // namespace neb - -} - -#endif // !CCB_NEB_SERVICE_DEPENDENCY_HH diff --git a/broker/neb/src/broker.cc b/broker/neb/src/broker.cc index d8645e48670..3a7c8e2f291 100644 --- a/broker/neb/src/broker.cc +++ b/broker/neb/src/broker.cc @@ -94,10 +94,6 @@ void broker_module_init(void const* arg) { e.register_event(make_type(io::neb, neb::de_host_check), "host_check", &neb::host_check::operations, neb::host_check::entries, "hosts"); - e.register_event(make_type(io::neb, neb::de_host_dependency), - "host_dependency", &neb::host_dependency::operations, - neb::host_dependency::entries, - "hosts_hosts_dependencies"); e.register_event(make_type(io::neb, neb::de_host), "host", &neb::host::operations, neb::host::entries, "hosts"); e.register_event(make_type(io::neb, neb::de_host_group), "host_group", @@ -124,10 +120,6 @@ void broker_module_init(void const* arg) { e.register_event(make_type(io::neb, neb::de_service_check), "service_check", &neb::service_check::operations, neb::service_check::entries, "services"); - e.register_event( - make_type(io::neb, neb::de_service_dependency), "service_dependency", - &neb::service_dependency::operations, - neb::service_dependency::entries, "services_services_dependencies"); e.register_event(make_type(io::neb, neb::de_service), "service", &neb::service::operations, neb::service::entries, "services"); @@ -211,13 +203,6 @@ void broker_module_init(void const* arg) { e.register_event(make_type(io::neb, neb::de_pb_acknowledgement), "Acknowledgement", &neb::pb_acknowledgement::operations, "acknowledgements"); - e.register_event(neb::pb_host_dependency::static_type(), "HostDependency", - &neb::pb_host_dependency::operations, - "hosts_hosts_dependencies"); - e.register_event(neb::pb_service_dependency::static_type(), - "ServiceDependency", - &neb::pb_service_dependency::operations, - "services_services_dependencies"); e.register_event(neb::pb_host_group::static_type(), "HostGroup", &neb::pb_host_group::operations, "hostgroups"); e.register_event( diff --git a/broker/neb/src/callbacks.cc b/broker/neb/src/callbacks.cc index ad30691a7d2..2e4c266b1e2 100644 --- a/broker/neb/src/callbacks.cc +++ b/broker/neb/src/callbacks.cc @@ -40,11 +40,9 @@ #include "com/centreon/engine/comment.hh" #include "com/centreon/engine/events/loop.hh" #include "com/centreon/engine/globals.hh" -#include "com/centreon/engine/hostdependency.hh" #include "com/centreon/engine/hostgroup.hh" #include "com/centreon/engine/nebcallbacks.hh" #include "com/centreon/engine/nebstructs.hh" -#include "com/centreon/engine/servicedependency.hh" #include "com/centreon/engine/servicegroup.hh" #include "com/centreon/engine/severity.hh" #include "com/centreon/engine/tag.hh" @@ -119,7 +117,6 @@ static struct { uint32_t macro; int (*callback)(int, void*); } const gl_engine_callbacks[] = { - {NEBCALLBACK_ADAPTIVE_DEPENDENCY_DATA, &neb::callback_dependency}, {NEBCALLBACK_ADAPTIVE_HOST_DATA, &neb::callback_host}, {NEBCALLBACK_ADAPTIVE_SERVICE_DATA, &neb::callback_service}, {NEBCALLBACK_CUSTOM_VARIABLE_DATA, &neb::callback_custom_variable}, @@ -132,7 +129,6 @@ static struct { uint32_t macro; int (*callback)(int, void*); } const gl_pb_engine_callbacks[] = { - {NEBCALLBACK_ADAPTIVE_DEPENDENCY_DATA, &neb::callback_pb_dependency}, {NEBCALLBACK_ADAPTIVE_HOST_DATA, &neb::callback_pb_host}, {NEBCALLBACK_ADAPTIVE_SERVICE_DATA, &neb::callback_pb_service}, {NEBCALLBACK_CUSTOM_VARIABLE_DATA, &neb::callback_pb_custom_variable}, @@ -675,315 +671,6 @@ int neb::callback_custom_variable(int callback_type, void* data) { return 0; } -/** - * @brief Function that process dependency data. - * - * This function is called by Centreon Engine when some dependency data - * is available. - * - * @param[in] callback_type Type of the callback - * (NEBCALLBACK_ADAPTIVE_DEPENDENCY_DATA). - * @param[in] data A pointer to a - * nebstruct_adaptive_dependency_data - * containing the dependency data. - * - * @return 0 on success. - */ -int neb::callback_dependency(int callback_type, void* data) { - // Log message. - SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: generating dependency event"); - (void)callback_type; - - try { - // Input variables. - nebstruct_adaptive_dependency_data* nsadd( - static_cast<nebstruct_adaptive_dependency_data*>(data)); - - // Host dependency. - if ((NEBTYPE_HOSTDEPENDENCY_ADD == nsadd->type) || - (NEBTYPE_HOSTDEPENDENCY_UPDATE == nsadd->type) || - (NEBTYPE_HOSTDEPENDENCY_DELETE == nsadd->type)) { - // Find IDs. - uint64_t host_id; - uint64_t dep_host_id; - engine::hostdependency* dep( - static_cast<engine::hostdependency*>(nsadd->object_ptr)); - if (!dep->get_hostname().empty()) { - host_id = engine::get_host_id(dep->get_hostname()); - } else { - SPDLOG_LOGGER_ERROR( - neb_logger, - "callbacks: dependency callback called without valid host"); - host_id = 0; - } - if (!dep->get_dependent_hostname().empty()) { - dep_host_id = engine::get_host_id(dep->get_dependent_hostname()); - } else { - SPDLOG_LOGGER_DEBUG( - neb_logger, - "callbacks: dependency callback called without valid dependent " - "host"); - dep_host_id = 0; - } - - // Generate service dependency event. - auto hst_dep{std::make_shared<host_dependency>()}; - hst_dep->host_id = host_id; - hst_dep->dependent_host_id = dep_host_id; - hst_dep->enabled = (nsadd->type != NEBTYPE_HOSTDEPENDENCY_DELETE); - if (!dep->get_dependency_period().empty()) - hst_dep->dependency_period = dep->get_dependency_period(); - { - std::string options; - if (dep->get_fail_on_down()) - options.append("d"); - if (dep->get_fail_on_up()) - options.append("o"); - if (dep->get_fail_on_pending()) - options.append("p"); - if (dep->get_fail_on_unreachable()) - options.append("u"); - if (dep->get_dependency_type() == engine::dependency::notification) - hst_dep->notification_failure_options = options; - else if (dep->get_dependency_type() == engine::dependency::execution) - hst_dep->execution_failure_options = options; - } - hst_dep->inherits_parent = dep->get_inherits_parent(); - SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: host {} depends on host {}", - dep_host_id, host_id); - - // Publish dependency event. - neb::gl_publisher.write(hst_dep); - } - // Service dependency. - else if ((NEBTYPE_SERVICEDEPENDENCY_ADD == nsadd->type) || - (NEBTYPE_SERVICEDEPENDENCY_UPDATE == nsadd->type) || - (NEBTYPE_SERVICEDEPENDENCY_DELETE == nsadd->type)) { - // Find IDs. - std::pair<uint64_t, uint64_t> ids; - std::pair<uint64_t, uint64_t> dep_ids; - engine::servicedependency* dep( - static_cast<engine::servicedependency*>(nsadd->object_ptr)); - if (!dep->get_hostname().empty() && - !dep->get_service_description().empty()) { - ids = engine::get_host_and_service_id(dep->get_hostname(), - dep->get_service_description()); - } else { - SPDLOG_LOGGER_ERROR( - neb_logger, - "callbacks: dependency callback called without valid service"); - ids.first = 0; - ids.second = 0; - } - if (!dep->get_dependent_hostname().empty() && - !dep->get_dependent_service_description().empty()) { - dep_ids = engine::get_host_and_service_id( - dep->get_dependent_hostname(), - dep->get_dependent_service_description()); - } else { - SPDLOG_LOGGER_ERROR( - neb_logger, - "callbacks: dependency callback called without valid dependent " - "service"); - dep_ids.first = 0; - dep_ids.second = 0; - } - - // Generate service dependency event. - auto svc_dep{std::make_shared<service_dependency>()}; - svc_dep->host_id = ids.first; - svc_dep->service_id = ids.second; - svc_dep->dependent_host_id = dep_ids.first; - svc_dep->dependent_service_id = dep_ids.second; - svc_dep->enabled = (nsadd->type != NEBTYPE_SERVICEDEPENDENCY_DELETE); - if (!dep->get_dependency_period().empty()) - svc_dep->dependency_period = dep->get_dependency_period(); - { - std::string options; - if (dep->get_fail_on_critical()) - options.append("c"); - if (dep->get_fail_on_ok()) - options.append("o"); - if (dep->get_fail_on_pending()) - options.append("p"); - if (dep->get_fail_on_unknown()) - options.append("u"); - if (dep->get_fail_on_warning()) - options.append("w"); - if (dep->get_dependency_type() == engine::dependency::notification) - svc_dep->notification_failure_options = options; - else if (dep->get_dependency_type() == engine::dependency::execution) - svc_dep->execution_failure_options = options; - } - svc_dep->inherits_parent = dep->get_inherits_parent(); - SPDLOG_LOGGER_DEBUG( - neb_logger, "callbacks: service ({}, {}) depends on service ({}, {})", - dep_ids.first, dep_ids.second, ids.first, ids.second); - - // Publish dependency event. - neb::gl_publisher.write(svc_dep); - } - } - // Avoid exception propagation to C code. - catch (...) { - } - - return 0; -} - -/** - * @brief Function that process dependency data. - * - * This function is called by Centreon Engine when some dependency data - * is available. - * - * @param[in] callback_type Type of the callback - * (NEBCALLBACK_ADAPTIVE_DEPENDENCY_DATA). - * @param[in] data A pointer to a - * nebstruct_adaptive_dependency_data - * containing the dependency data. - * - * @return 0 on success. - */ -int neb::callback_pb_dependency(int, void* data) { - // Log message. - SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: generating dependency event"); - - // Input variables. - nebstruct_adaptive_dependency_data* nsadd( - static_cast<nebstruct_adaptive_dependency_data*>(data)); - - // Host dependency. - if ((NEBTYPE_HOSTDEPENDENCY_ADD == nsadd->type) || - (NEBTYPE_HOSTDEPENDENCY_UPDATE == nsadd->type) || - (NEBTYPE_HOSTDEPENDENCY_DELETE == nsadd->type)) { - // Find IDs. - uint64_t host_id; - uint64_t dep_host_id; - - engine::hostdependency* dep( - static_cast<engine::hostdependency*>(nsadd->object_ptr)); - if (!dep->get_hostname().empty()) { - host_id = engine::get_host_id(dep->get_hostname()); - } else { - SPDLOG_LOGGER_ERROR( - neb_logger, - "callbacks: dependency callback called without valid host"); - host_id = 0; - } - if (!dep->get_dependent_hostname().empty()) { - dep_host_id = engine::get_host_id(dep->get_dependent_hostname()); - } else { - SPDLOG_LOGGER_DEBUG( - neb_logger, - "callbacks: dependency callback called without valid dependent " - "host"); - dep_host_id = 0; - } - - // Generate service dependency event. - auto hd{std::make_shared<pb_host_dependency>()}; - HostDependency& hst_dep = hd->mut_obj(); - hst_dep.set_host_id(host_id); - hst_dep.set_dependent_host_id(dep_host_id); - hst_dep.set_enabled(nsadd->type != NEBTYPE_HOSTDEPENDENCY_DELETE); - if (!dep->get_dependency_period().empty()) - hst_dep.set_dependency_period(dep->get_dependency_period()); - { - std::string options; - if (dep->get_fail_on_down()) - options.append("d"); - if (dep->get_fail_on_up()) - options.append("o"); - if (dep->get_fail_on_pending()) - options.append("p"); - if (dep->get_fail_on_unreachable()) - options.append("u"); - if (dep->get_dependency_type() == engine::dependency::notification) - hst_dep.set_notification_failure_options(options); - else if (dep->get_dependency_type() == engine::dependency::execution) - hst_dep.set_execution_failure_options(options); - } - hst_dep.set_inherits_parent(dep->get_inherits_parent()); - SPDLOG_LOGGER_DEBUG(neb_logger, "callbacks: host {} depends on host {}", - dep_host_id, host_id); - - // Publish dependency event. - neb::gl_publisher.write(hd); - } - // Service dependency. - else if ((NEBTYPE_SERVICEDEPENDENCY_ADD == nsadd->type) || - (NEBTYPE_SERVICEDEPENDENCY_UPDATE == nsadd->type) || - (NEBTYPE_SERVICEDEPENDENCY_DELETE == nsadd->type)) { - // Find IDs. - std::pair<uint64_t, uint64_t> ids; - std::pair<uint64_t, uint64_t> dep_ids; - engine::servicedependency* dep( - static_cast<engine::servicedependency*>(nsadd->object_ptr)); - if (!dep->get_hostname().empty() && - !dep->get_service_description().empty()) { - ids = engine::get_host_and_service_id(dep->get_hostname(), - dep->get_service_description()); - } else { - SPDLOG_LOGGER_ERROR( - neb_logger, - "callbacks: dependency callback called without valid service"); - ids.first = 0; - ids.second = 0; - } - if (!dep->get_dependent_hostname().empty() && - !dep->get_dependent_service_description().empty()) { - dep_ids = engine::get_host_and_service_id( - dep->get_dependent_hostname(), - dep->get_dependent_service_description()); - } else { - SPDLOG_LOGGER_ERROR( - neb_logger, - "callbacks: dependency callback called without valid dependent " - "service"); - dep_ids.first = 0; - dep_ids.second = 0; - } - - // Generate service dependency event. - auto sd{std::make_shared<pb_service_dependency>()}; - ServiceDependency& svc_dep = sd->mut_obj(); - svc_dep.set_host_id(ids.first); - svc_dep.set_service_id(ids.second); - svc_dep.set_dependent_host_id(dep_ids.first); - svc_dep.set_dependent_service_id(dep_ids.second); - svc_dep.set_enabled(nsadd->type != NEBTYPE_SERVICEDEPENDENCY_DELETE); - if (!dep->get_dependency_period().empty()) - svc_dep.set_dependency_period(dep->get_dependency_period()); - { - std::string options; - if (dep->get_fail_on_critical()) - options.append("c"); - if (dep->get_fail_on_ok()) - options.append("o"); - if (dep->get_fail_on_pending()) - options.append("p"); - if (dep->get_fail_on_unknown()) - options.append("u"); - if (dep->get_fail_on_warning()) - options.append("w"); - if (dep->get_dependency_type() == engine::dependency::notification) - svc_dep.set_notification_failure_options(options); - else if (dep->get_dependency_type() == engine::dependency::execution) - svc_dep.set_execution_failure_options(options); - } - svc_dep.set_inherits_parent(dep->get_inherits_parent()); - SPDLOG_LOGGER_DEBUG( - neb_logger, "callbacks: service ({}, {}) depends on service ({}, {})", - dep_ids.first, dep_ids.second, ids.first, ids.second); - - // Publish dependency event. - neb::gl_publisher.write(sd); - } - - return 0; -} - /** * @brief Function that process downtime data. * diff --git a/broker/neb/src/dependency.cc b/broker/neb/src/dependency.cc deleted file mode 100644 index 9af51cdfc1c..00000000000 --- a/broker/neb/src/dependency.cc +++ /dev/null @@ -1,88 +0,0 @@ -/** -* Copyright 2009-2012,2015 Centreon -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -* -* For more information : contact@centreon.com -*/ - -#include "com/centreon/broker/neb/dependency.hh" - -using namespace com::centreon::broker::neb; - -/************************************** - * * - * Public Methods * - * * - **************************************/ - -/** - * Default constructor. - */ -dependency::dependency(uint32_t type) - : io::data(type), - dependent_host_id(0), - enabled(true), - host_id(0), - inherits_parent(false) {} - -/** - * Copy constructor. - * - * @param[in] dep Object to copy. - */ -dependency::dependency(dependency const& dep) : io::data(dep) { - _internal_copy(dep); -} - -/** - * Destructor. - */ -dependency::~dependency() {} - -/** - * Assignment operator. - * - * @param[in] dep Object to copy from. - * - * @return This object. - */ -dependency& dependency::operator=(dependency const& dep) { - io::data::operator=(dep); - _internal_copy(dep); - return (*this); -} - -/************************************** - * * - * Private Methods * - * * - **************************************/ - -/** - * @brief Copy internal data members. - * - * This method is used by the copy constructor and the assignment operator. - * - * @param[in] dep Object to copy. - */ -void dependency::_internal_copy(dependency const& dep) { - dependency_period = dep.dependency_period; - dependent_host_id = dep.dependent_host_id; - enabled = dep.enabled; - execution_failure_options = dep.execution_failure_options; - host_id = dep.host_id; - inherits_parent = dep.inherits_parent; - notification_failure_options = dep.notification_failure_options; - return; -} diff --git a/broker/neb/src/host_dependency.cc b/broker/neb/src/host_dependency.cc deleted file mode 100644 index 352c4e345ca..00000000000 --- a/broker/neb/src/host_dependency.cc +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Copyright 2009-2013,2015 Centreon - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * For more information : contact@centreon.com - */ - -#include "com/centreon/broker/neb/host_dependency.hh" - -#include "com/centreon/broker/sql/table_max_size.hh" - -using namespace com::centreon::broker; -using namespace com::centreon::broker::neb; - -/************************************** - * * - * Public Methods * - * * - **************************************/ - -/** - * Default constructor. - */ -host_dependency::host_dependency() - : dependency(host_dependency::static_type()) {} - -/** - * Copy constructor. - * - * @param[in] other Object to copy. - */ -host_dependency::host_dependency(host_dependency const& other) - : dependency(other) {} - -/** - * Destructor. - */ -host_dependency::~host_dependency() {} - -/** - * Assignment operator. - * - * @param[in] other Object to copy. - * - * @return This object. - */ -host_dependency& host_dependency::operator=(host_dependency const& other) { - dependency::operator=(other); - return *this; -} - -/************************************** - * * - * Static Objects * - * * - **************************************/ - -// Mapping. -mapping::entry const host_dependency::entries[] = { - mapping::entry( - &host_dependency::dependency_period, - "dependency_period", - get_centreon_storage_hosts_hosts_dependencies_col_size( - centreon_storage_hosts_hosts_dependencies_dependency_period)), - mapping::entry(&host_dependency::dependent_host_id, - "dependent_host_id", - mapping::entry::invalid_on_zero), - mapping::entry(&host_dependency::enabled, ""), - mapping::entry( - &host_dependency::execution_failure_options, - "execution_failure_options", - get_centreon_storage_hosts_hosts_dependencies_col_size( - centreon_storage_hosts_hosts_dependencies_execution_failure_options)), - mapping::entry(&host_dependency::inherits_parent, "inherits_parent"), - mapping::entry(&host_dependency::host_id, - "host_id", - mapping::entry::invalid_on_zero), - mapping::entry( - &host_dependency::notification_failure_options, - "notification_failure_options", - get_centreon_storage_hosts_hosts_dependencies_col_size( - centreon_storage_hosts_hosts_dependencies_notification_failure_options)), - mapping::entry()}; - -// Operations. -static io::data* new_host_dep() { - return (new host_dependency); -} -io::event_info::event_operations const host_dependency::operations = { - &new_host_dep, nullptr, nullptr}; diff --git a/broker/neb/src/initial.cc b/broker/neb/src/initial.cc index 4144c6fe822..4fc23eb0626 100644 --- a/broker/neb/src/initial.cc +++ b/broker/neb/src/initial.cc @@ -27,7 +27,6 @@ #include "com/centreon/engine/downtimes/service_downtime.hh" #include "com/centreon/engine/globals.hh" #include "com/centreon/engine/host.hh" -#include "com/centreon/engine/hostdependency.hh" #include "com/centreon/engine/nebcallbacks.hh" #include "com/centreon/engine/nebstructs.hh" #include "com/centreon/engine/objects.hh" @@ -72,7 +71,6 @@ static void send_custom_variables_list( if (cit->second.is_sent()) { // Fill callback struct. nebstruct_custom_variable_data nscvd; - memset(&nscvd, 0, sizeof(nscvd)); nscvd.type = NEBTYPE_HOSTCUSTOMVARIABLE_ADD; nscvd.timestamp.tv_sec = time(nullptr); nscvd.var_name = const_cast<char*>(name.c_str()); @@ -173,48 +171,6 @@ static void send_pb_downtimes_list() { send_downtimes_list(neb::callback_pb_downtime); } -/** - * Send to the global publisher the list of host dependencies within Nagios. - */ -static void send_host_dependencies_list( - neb_sender callbackfct = neb::callback_dependency) { - // Start log message. - neb_logger->info("init: beginning host dependencies dump"); - - try { - // Loop through all dependencies. - for (hostdependency_mmap::const_iterator - it{com::centreon::engine::hostdependency::hostdependencies - .begin()}, - end{com::centreon::engine::hostdependency::hostdependencies.end()}; - it != end; ++it) { - // Fill callback struct. - nebstruct_adaptive_dependency_data nsadd; - memset(&nsadd, 0, sizeof(nsadd)); - nsadd.type = NEBTYPE_HOSTDEPENDENCY_ADD; - nsadd.object_ptr = it->second.get(); - - // Callback. - callbackfct(NEBCALLBACK_ADAPTIVE_DEPENDENCY_DATA, &nsadd); - } - } catch (std::exception const& e) { - neb_logger->info("init: error occurred while dumping host dependencies: {}", - e.what()); - } catch (...) { - neb_logger->error( - "init: unknown error occurred while dumping host dependencies"); - } - - // End log message. - neb_logger->info("init: end of host dependencies dump"); - - return; -} - -static void send_pb_host_dependencies_list() { - send_host_dependencies_list(neb::callback_pb_dependency); -} - /** * Send to the global publisher the list of host groups within Engine. */ @@ -369,49 +325,6 @@ static void send_pb_host_parents_list() { send_host_parents_list(neb::callback_pb_relation); } -/** - * Send to the global publisher the list of service dependencies within - * Nagios. - */ -static void send_service_dependencies_list( - neb_sender sender_fct = neb::callback_dependency) { - // Start log message. - neb_logger->info("init: beginning service dependencies dump"); - - try { - // Loop through all dependencies. - for (servicedependency_mmap::const_iterator - it{com::centreon::engine::servicedependency::servicedependencies - .begin()}, - end{com::centreon::engine::servicedependency::servicedependencies - .end()}; - it != end; ++it) { - // Fill callback struct. - nebstruct_adaptive_dependency_data nsadd; - memset(&nsadd, 0, sizeof(nsadd)); - nsadd.type = NEBTYPE_SERVICEDEPENDENCY_ADD; - nsadd.object_ptr = it->second.get(); - - // Callback. - sender_fct(NEBCALLBACK_ADAPTIVE_DEPENDENCY_DATA, &nsadd); - } - } catch (std::exception const& e) { - neb_logger->error( - "init: error occurred while dumping service dependencies: {}", - e.what()); - } catch (...) { - neb_logger->error( - "init: unknown error occurred while dumping service dependencies"); - } - - // End log message. - neb_logger->info("init: end of service dependencies dump"); -} - -static void send_pb_service_dependencies_list() { - send_service_dependencies_list(neb::callback_pb_dependency); -} - /** * Send to the global publisher the list of service groups within Engine. */ @@ -541,8 +454,6 @@ void neb::send_initial_configuration() { send_host_parents_list(); send_host_group_list(); send_service_group_list(); - send_host_dependencies_list(); - send_service_dependencies_list(); send_instance_configuration(); } @@ -566,7 +477,5 @@ void neb::send_initial_pb_configuration() { send_pb_host_parents_list(); send_pb_host_group_list(); send_pb_service_group_list(); - send_pb_host_dependencies_list(); - send_pb_service_dependencies_list(); send_pb_instance_configuration(); } diff --git a/broker/neb/src/service_dependency.cc b/broker/neb/src/service_dependency.cc deleted file mode 100644 index 8357aa1df2a..00000000000 --- a/broker/neb/src/service_dependency.cc +++ /dev/null @@ -1,125 +0,0 @@ -/** - * Copyright 2009-2013,2015 Centreon - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * For more information : contact@centreon.com - */ - -#include "com/centreon/broker/neb/service_dependency.hh" - -#include "com/centreon/broker/sql/table_max_size.hh" - -using namespace com::centreon::broker; -using namespace com::centreon::broker::neb; - -/** - * Default constructor. - */ -service_dependency::service_dependency() - : dependency(service_dependency::static_type()), - dependent_service_id(0), - service_id(0) {} - -/** - * Copy constructor. - * - * @param[in] sd Object to copy. - */ -service_dependency::service_dependency(service_dependency const& sd) - : dependency(sd) { - _internal_copy(sd); -} - -/** - * Destructor. - */ -service_dependency::~service_dependency() {} - -/** - * Assignment operator. - * - * @param[in] sd Object to copy. - * - * @return This object. - */ -service_dependency& service_dependency::operator=( - service_dependency const& sd) { - if (this != &sd) { - dependency::operator=(sd); - _internal_copy(sd); - } - return *this; -} - -/************************************** - * * - * Private Methods * - * * - **************************************/ - -/** - * Copy internal members from the given object. - * - * @param[in] sd Object to copy. - */ -void service_dependency::_internal_copy(service_dependency const& sd) { - dependent_service_id = sd.dependent_service_id; - service_id = sd.service_id; -} - -/************************************** - * * - * Static Objects * - * * - **************************************/ - -// Mapping. -mapping::entry const service_dependency::entries[] = { - mapping::entry( - &service_dependency::dependency_period, - "dependency_period", - get_centreon_storage_services_services_dependencies_col_size( - centreon_storage_services_services_dependencies_dependency_period)), - mapping::entry(&service_dependency::dependent_host_id, - "dependent_host_id", - mapping::entry::invalid_on_zero), - mapping::entry(&service_dependency::dependent_service_id, - "dependent_service_id", - mapping::entry::invalid_on_zero), - mapping::entry(&service_dependency::enabled, ""), - mapping::entry( - &service_dependency::execution_failure_options, - "execution_failure_options", - get_centreon_storage_services_services_dependencies_col_size( - centreon_storage_services_services_dependencies_execution_failure_options)), - mapping::entry(&service_dependency::host_id, - "host_id", - mapping::entry::invalid_on_zero), - mapping::entry(&service_dependency::inherits_parent, "inherits_parent"), - mapping::entry( - &service_dependency::notification_failure_options, - "notification_failure_options", - get_centreon_storage_services_services_dependencies_col_size( - centreon_storage_services_services_dependencies_notification_failure_options)), - mapping::entry(&service_dependency::service_id, - "service_id", - mapping::entry::invalid_on_zero), - mapping::entry()}; - -// Operations. -static io::data* new_service_dependency() { - return new service_dependency; -} -io::event_info::event_operations const service_dependency::operations = { - &new_service_dependency, nullptr, nullptr}; diff --git a/broker/neb/test/host_dependency.cc b/broker/neb/test/host_dependency.cc deleted file mode 100644 index 21a3d76e709..00000000000 --- a/broker/neb/test/host_dependency.cc +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Copyright 2011 - 2019 Centreon (https://www.centreon.com/) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * For more information : contact@centreon.com - * - */ - -#include <gtest/gtest.h> -#include <cmath> -#include "com/centreon/broker/neb/host.hh" -#include "randomize.hh" - -using namespace com::centreon::broker; - -class HostDependencyTest : public ::testing::Test { - public: - void SetUp() override { - // Initialization. - randomize_init(); - } - - void TearDown() override { - // Cleanup. - randomize_cleanup(); - } -}; - -TEST_F(HostDependencyTest, Assignment) { - // Object #1. - neb::host_dependency hdep1; - std::vector<randval> randvals1; - randomize(hdep1, &randvals1); - - // Object #2. - neb::host_dependency hdep2; - randomize(hdep2); - - // Assignment. - hdep2 = hdep1; - - // Reset object #1. - std::vector<randval> randvals2; - randomize(hdep1, &randvals2); - - // Compare objects with expected results. - ASSERT_TRUE(hdep1 == randvals2); - ASSERT_TRUE(hdep2 == randvals1); -} - -TEST_F(HostDependencyTest, CopyConstructor) { - // Object #1. - neb::host_dependency hdep1; - std::vector<randval> randvals1; - randomize(hdep1, &randvals1); - - // Object #2. - neb::host_dependency hdep2(hdep1); - - // Reset object #1. - std::vector<randval> randvals2; - randomize(hdep1, &randvals2); - - // Compare objects with expected results. - ASSERT_TRUE(hdep1 == randvals2); - ASSERT_TRUE(hdep2 == randvals1); -} - -TEST_F(HostDependencyTest, DefaultConstructor) { - // Object. - neb::host_dependency hdep; - - // Check. - ASSERT_EQ(hdep.source_id, 0u); - ASSERT_EQ(hdep.destination_id, 0u); - ASSERT_TRUE(hdep.dependency_period == ""); - ASSERT_EQ(hdep.dependent_host_id, 0u); - ASSERT_TRUE(hdep.enabled); - ASSERT_TRUE(hdep.execution_failure_options.empty()); - ASSERT_EQ(hdep.host_id, 0u); - ASSERT_FALSE(hdep.inherits_parent); - ASSERT_TRUE(hdep.notification_failure_options.empty()); - ASSERT_FALSE( - hdep.type() != - (io::events::data_type<io::neb, neb::de_host_dependency>::value)); -} diff --git a/broker/neb/test/randomize.cc b/broker/neb/test/randomize.cc index a87050945ac..c7787c46b9a 100644 --- a/broker/neb/test/randomize.cc +++ b/broker/neb/test/randomize.cc @@ -108,9 +108,6 @@ void randomize_init() { &neb::downtime::operations, neb::downtime::entries); e.register_event(make_type(io::neb, neb::de_host_check), "host_check", &neb::host_check::operations, neb::host_check::entries); - e.register_event(make_type(io::neb, neb::de_host_dependency), - "host_dependency", &neb::host_dependency::operations, - neb::host_dependency::entries); e.register_event(make_type(io::neb, neb::de_host), "host", &neb::host::operations, neb::host::entries); e.register_event(make_type(io::neb, neb::de_host_parent), "host_parent", @@ -127,9 +124,6 @@ void randomize_init() { e.register_event(make_type(io::neb, neb::de_service_check), "service_check", &neb::service_check::operations, neb::service_check::entries); - e.register_event(make_type(io::neb, neb::de_service_dependency), - "service_dependency", &neb::service_dependency::operations, - neb::service_dependency::entries); e.register_event(make_type(io::neb, neb::de_service), "service", &neb::service::operations, neb::service::entries); e.register_event(make_type(io::neb, neb::de_service_status), "service_status", @@ -143,7 +137,7 @@ void randomize_init() { void randomize_cleanup() { for (std::list<char*>::iterator it(generated.begin()), end(generated.end()); it != end; ++it) - delete[] * it; + delete[] *it; generated.clear(); io::events::unload(); io::protocols::unload(); diff --git a/broker/neb/test/service_dependency.cc b/broker/neb/test/service_dependency.cc deleted file mode 100644 index 66ad6dc4418..00000000000 --- a/broker/neb/test/service_dependency.cc +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Copyright 2011 - 2019 Centreon (https://www.centreon.com/) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * For more information : contact@centreon.com - * - */ - -#include "com/centreon/broker/neb/service_dependency.hh" -#include <gtest/gtest.h> -#include "com/centreon/broker/io/events.hh" -#include "com/centreon/broker/neb/internal.hh" -#include "randomize.hh" - -using namespace com::centreon::broker; - -class ServiceDependencyTest : public ::testing::Test { - void SetUp() override { randomize_init(); }; - - void TearDown() override { randomize_cleanup(); }; -}; - -/** - * Check service_dependency's assignment operator. - */ -TEST_F(ServiceDependencyTest, Assign) { - // Object #1. - neb::service_dependency sdep1; - std::vector<randval> randvals1; - randomize(sdep1, &randvals1); - - // Object #2. - neb::service_dependency sdep2; - randomize(sdep2); - - // Assignment. - sdep2 = sdep1; - - // Reset object #1. - std::vector<randval> randvals2; - randomize(sdep1, &randvals2); - - // Compare objects with expected results. - ASSERT_FALSE(sdep1 != randvals2); - ASSERT_FALSE(sdep2 != randvals1); -} - -/** - * Check service_dependency's copy constructor. - */ -TEST_F(ServiceDependencyTest, CopyCtor) { - // Object #1. - neb::service_dependency sdep1; - std::vector<randval> randvals1; - randomize(sdep1, &randvals1); - - // Object #2. - neb::service_dependency sdep2(sdep1); - - // Reset object #1. - std::vector<randval> randvals2; - randomize(sdep1, &randvals2); - - // Compare objects with expected results. - ASSERT_FALSE(sdep1 != randvals2); - ASSERT_FALSE(sdep2 != randvals1); -} - -/** - * Check service_dependency's default constructor. - */ -TEST_F(ServiceDependencyTest, DefaultCtor) { - // Object. - neb::service_dependency sdep; - - // Check. - ASSERT_FALSE(sdep.source_id != 0); - ASSERT_FALSE(sdep.destination_id != 0); - ASSERT_FALSE(sdep.dependency_period != ""); - ASSERT_FALSE(sdep.dependent_host_id != 0); - ASSERT_FALSE(sdep.enabled != true); - ASSERT_FALSE(!sdep.execution_failure_options.empty()); - ASSERT_FALSE(sdep.host_id != 0); - ASSERT_FALSE(sdep.inherits_parent != false); - ASSERT_FALSE(!sdep.notification_failure_options.empty()); - ASSERT_FALSE(sdep.service_id != 0); - auto val(io::events::data_type<io::neb, neb::de_service_dependency>::value); - ASSERT_FALSE(sdep.type() != val); -} diff --git a/broker/storage/inc/com/centreon/broker/storage/conflict_manager.hh b/broker/storage/inc/com/centreon/broker/storage/conflict_manager.hh index b7815b6e5be..19abaa710ad 100644 --- a/broker/storage/inc/com/centreon/broker/storage/conflict_manager.hh +++ b/broker/storage/inc/com/centreon/broker/storage/conflict_manager.hh @@ -78,11 +78,9 @@ class conflict_manager { enum special_conn { custom_variable, downtime, - host_dependency, host_group, host_parent, log, - service_dependency, service_group, severity, tag, @@ -94,21 +92,19 @@ class conflict_manager { comments = 1 << 1, custom_variables = 1 << 2, downtimes = 1 << 3, - host_dependencies = 1 << 4, - host_hostgroups = 1 << 5, - host_parents = 1 << 6, - hostgroups = 1 << 7, - hosts = 1 << 8, - instances = 1 << 9, - modules = 1 << 10, - service_dependencies = 1 << 11, - service_servicegroups = 1 << 12, - servicegroups = 1 << 13, - services = 1 << 14, - index_data = 1 << 15, - metrics = 1 << 16, - severities = 1 << 17, - tags = 1 << 18, + host_hostgroups = 1 << 4, + host_parents = 1 << 5, + hostgroups = 1 << 6, + hosts = 1 << 7, + instances = 1 << 8, + modules = 1 << 9, + service_servicegroups = 1 << 10, + servicegroups = 1 << 11, + services = 1 << 12, + index_data = 1 << 13, + metrics = 1 << 14, + severities = 1 << 15, + tags = 1 << 16, }; struct index_info { @@ -247,7 +243,6 @@ class conflict_manager { database::mysql_stmt _event_handler_insupdate; database::mysql_stmt _flapping_status_insupdate; database::mysql_stmt _host_check_update; - database::mysql_stmt _host_dependency_insupdate; database::mysql_stmt _host_group_insupdate; database::mysql_stmt _host_group_member_delete; database::mysql_stmt _host_group_member_insert; @@ -259,7 +254,6 @@ class conflict_manager { database::mysql_stmt _instance_status_insupdate; database::mysql_stmt _module_insert; database::mysql_stmt _service_check_update; - database::mysql_stmt _service_dependency_insupdate; database::mysql_stmt _service_group_insupdate; database::mysql_stmt _service_group_member_delete; database::mysql_stmt _service_group_member_insert; @@ -304,8 +298,6 @@ class conflict_manager { std::tuple<std::shared_ptr<io::data>, uint32_t, bool*>& t); void _process_host_check( std::tuple<std::shared_ptr<io::data>, uint32_t, bool*>& t); - void _process_host_dependency( - std::tuple<std::shared_ptr<io::data>, uint32_t, bool*>& t); void _process_host_group( std::tuple<std::shared_ptr<io::data>, uint32_t, bool*>& t); void _process_host_group_member( @@ -322,8 +314,6 @@ class conflict_manager { void _process_log(std::tuple<std::shared_ptr<io::data>, uint32_t, bool*>& t); void _process_service_check( std::tuple<std::shared_ptr<io::data>, uint32_t, bool*>& t); - void _process_service_dependency( - std::tuple<std::shared_ptr<io::data>, uint32_t, bool*>& t); void _process_service_group( std::tuple<std::shared_ptr<io::data>, uint32_t, bool*>& t); void _process_service_group_member( diff --git a/broker/storage/src/conflict_manager.cc b/broker/storage/src/conflict_manager.cc index 2978cbc1cb7..c19669f9db6 100644 --- a/broker/storage/src/conflict_manager.cc +++ b/broker/storage/src/conflict_manager.cc @@ -56,7 +56,7 @@ void (conflict_manager::*const conflict_manager::_neb_processing_table[])( nullptr, nullptr, &conflict_manager::_process_host_check, - &conflict_manager::_process_host_dependency, + nullptr, &conflict_manager::_process_host_group, &conflict_manager::_process_host_group_member, &conflict_manager::_process_host, @@ -67,7 +67,7 @@ void (conflict_manager::*const conflict_manager::_neb_processing_table[])( &conflict_manager::_process_log, nullptr, &conflict_manager::_process_service_check, - &conflict_manager::_process_service_dependency, + nullptr, &conflict_manager::_process_service_group, &conflict_manager::_process_service_group_member, &conflict_manager::_process_service, @@ -955,8 +955,7 @@ void conflict_manager::process_stop(const std::shared_ptr<io::data>& d) { _finish_action(-1, actions::hosts | actions::acknowledgements | actions::modules | actions::downtimes | actions::comments | actions::servicegroups | - actions::hostgroups | actions::service_dependencies | - actions::host_dependencies); + actions::hostgroups); // Log message. _logger_sql->info("SQL: Disabling poller (id: {}, running: no)", diff --git a/broker/storage/src/conflict_manager_sql.cc b/broker/storage/src/conflict_manager_sql.cc index 3192190688f..0f02ec0a519 100644 --- a/broker/storage/src/conflict_manager_sql.cc +++ b/broker/storage/src/conflict_manager_sql.cc @@ -84,18 +84,6 @@ void conflict_manager::_clean_tables(uint32_t instance_id) { conn); _add_action(conn, actions::servicegroups); - /* Remove host dependencies. */ - _logger_sql->debug( - "conflict_manager: remove host dependencies (instance_id: {})", - instance_id); - query = fmt::format( - "DELETE hhd FROM hosts_hosts_dependencies AS hhd INNER JOIN hosts as " - "h ON hhd.host_id=h.host_id OR hhd.dependent_host_id=h.host_id WHERE " - "h.instance_id={}", - instance_id); - _mysql.run_query(query, database::mysql_error::clean_host_dependencies, conn); - _add_action(conn, actions::host_dependencies); - /* Remove host parents. */ _logger_sql->debug("conflict_manager: remove host parents (instance_id: {})", instance_id); @@ -107,23 +95,6 @@ void conflict_manager::_clean_tables(uint32_t instance_id) { _mysql.run_query(query, database::mysql_error::clean_host_parents, conn); _add_action(conn, actions::host_parents); - /* Remove service dependencies. */ - _logger_sql->debug( - "conflict_manager: remove service dependencies (instance_id: {})", - instance_id); - query = fmt::format( - "DELETE ssd FROM services_services_dependencies AS ssd" - " INNER JOIN services as s" - " ON ssd.service_id=s.service_id OR " - "ssd.dependent_service_id=s.service_id" - " INNER JOIN hosts as h" - " ON s.host_id=h.host_id" - " WHERE h.instance_id={}", - instance_id); - _mysql.run_query(query, database::mysql_error::clean_service_dependencies, - conn); - _add_action(conn, actions::service_dependencies); - /* Remove list of modules. */ _logger_sql->debug("SQL: remove list of modules (instance_id: {})", instance_id); @@ -400,8 +371,7 @@ void conflict_manager::_process_comment( std::tuple<std::shared_ptr<io::data>, uint32_t, bool*>& t) { auto& d = std::get<0>(t); _finish_action(-1, actions::hosts | actions::instances | - actions::host_parents | actions::host_dependencies | - actions::service_dependencies | actions::comments); + actions::host_parents | actions::comments); // Cast object. neb::comment const& cmmnt{*static_cast<neb::comment const*>(d.get())}; @@ -592,8 +562,7 @@ void conflict_manager::_process_host_check( std::tuple<std::shared_ptr<io::data>, uint32_t, bool*>& t) { auto& d = std::get<0>(t); _finish_action(-1, actions::instances | actions::downtimes | - actions::comments | actions::host_dependencies | - actions::host_parents | actions::service_dependencies); + actions::comments | actions::host_parents); // Cast object. neb::host_check const& hc = *static_cast<neb::host_check const*>(d.get()); @@ -645,60 +614,6 @@ void conflict_manager::_process_host_check( *std::get<2>(t) = true; } -/** - * Process a host dependency event. - * - * @param[in] e Uncasted host dependency. - * - * @return The number of events that can be acknowledged. - */ -void conflict_manager::_process_host_dependency( - std::tuple<std::shared_ptr<io::data>, uint32_t, bool*>& t) { - auto& d = std::get<0>(t); - int32_t conn = special_conn::host_dependency % _mysql.connections_count(); - _finish_action(-1, actions::hosts | actions::host_parents | - actions::comments | actions::downtimes | - actions::host_dependencies | - actions::service_dependencies); - - // Cast object. - neb::host_dependency const& hd = - *static_cast<neb::host_dependency const*>(d.get()); - - // Insert/Update. - if (hd.enabled) { - _logger_sql->info("SQL: enabling host dependency of {} on {}", - hd.dependent_host_id, hd.host_id); - - // Prepare queries. - if (!_host_dependency_insupdate.prepared()) { - query_preparator::event_unique unique; - unique.insert("host_id"); - unique.insert("dependent_host_id"); - query_preparator qp(neb::host_dependency::static_type(), unique); - _host_dependency_insupdate = qp.prepare_insert_or_update(_mysql); - } - - // Process object. - _host_dependency_insupdate << hd; - _mysql.run_statement(_host_dependency_insupdate, - database::mysql_error::store_host_dependency, conn); - _add_action(conn, actions::host_dependencies); - } - // Delete. - else { - _logger_sql->info("SQL: removing host dependency of {} on {}", - hd.dependent_host_id, hd.host_id); - std::string query(fmt::format( - "DELETE FROM hosts_hosts_dependencies WHERE dependent_host_id={}" - " AND host_id={}", - hd.dependent_host_id, hd.host_id)); - _mysql.run_query(query, database::mysql_error::empty, conn); - _add_action(conn, actions::host_dependencies); - } - *std::get<2>(t) = true; -} - /** * Process a host group event. * @@ -843,9 +758,8 @@ void conflict_manager::_process_host( std::tuple<std::shared_ptr<io::data>, uint32_t, bool*>& t) { auto& d = std::get<0>(t); _finish_action(-1, actions::instances | actions::hostgroups | - actions::host_dependencies | actions::host_parents | - actions::custom_variables | actions::downtimes | - actions::comments | actions::service_dependencies); + actions::host_parents | actions::custom_variables | + actions::downtimes | actions::comments); neb::host& h = *static_cast<neb::host*>(d.get()); // Log message. @@ -900,8 +814,7 @@ void conflict_manager::_process_host_parent( std::tuple<std::shared_ptr<io::data>, uint32_t, bool*>& t) { auto& d = std::get<0>(t); int32_t conn = special_conn::host_parent % _mysql.connections_count(); - _finish_action(-1, actions::hosts | actions::host_dependencies | - actions::comments | actions::downtimes); + _finish_action(-1, actions::hosts | actions::comments | actions::downtimes); neb::host_parent const& hp(*static_cast<neb::host_parent const*>(d.get())); @@ -958,8 +871,7 @@ void conflict_manager::_process_host_status( auto& d = std::get<0>(t); _finish_action(-1, actions::instances | actions::downtimes | actions::comments | actions::custom_variables | - actions::hostgroups | actions::host_dependencies | - actions::host_parents); + actions::hostgroups | actions::host_parents); // Processed object. neb::host_status const& hs(*static_cast<neb::host_status const*>(d.get())); @@ -1017,8 +929,7 @@ void conflict_manager::_process_instance( _finish_action(-1, actions::hosts | actions::acknowledgements | actions::modules | actions::downtimes | actions::comments | actions::servicegroups | - actions::hostgroups | actions::service_dependencies | - actions::host_dependencies); + actions::hostgroups); // Log message. _logger_sql->info( @@ -1147,9 +1058,8 @@ void conflict_manager::_process_log( void conflict_manager::_process_service_check( std::tuple<std::shared_ptr<io::data>, uint32_t, bool*>& t) { auto& d = std::get<0>(t); - _finish_action(-1, actions::downtimes | actions::comments | - actions::host_dependencies | actions::host_parents | - actions::service_dependencies); + _finish_action( + -1, actions::downtimes | actions::comments | actions::host_parents); // Cast object. neb::service_check const& sc( @@ -1211,67 +1121,6 @@ void conflict_manager::_process_service_check( *std::get<2>(t) = true; } -/** - * Process a service dependency event. - * - * @param[in] e Uncasted service dependency. - * - * @return The number of events that can be acknowledged. - */ -void conflict_manager::_process_service_dependency( - std::tuple<std::shared_ptr<io::data>, uint32_t, bool*>& t) { - auto& d = std::get<0>(t); - int32_t conn = special_conn::service_dependency % _mysql.connections_count(); - _finish_action(-1, actions::hosts | actions::host_parents | - actions::downtimes | actions::comments | - actions::host_dependencies | - actions::service_dependencies); - - // Cast object. - neb::service_dependency const& sd( - *static_cast<neb::service_dependency const*>(d.get())); - - // Insert/Update. - if (sd.enabled) { - _logger_sql->info( - "SQL: enabling service dependency of ({}, {}) on ({}, {})", - sd.dependent_host_id, sd.dependent_service_id, sd.host_id, - sd.service_id); - - // Prepare queries. - if (!_service_dependency_insupdate.prepared()) { - query_preparator::event_unique unique; - unique.insert("dependent_host_id"); - unique.insert("dependent_service_id"); - unique.insert("host_id"); - unique.insert("service_id"); - query_preparator qp(neb::service_dependency::static_type(), unique); - _service_dependency_insupdate = qp.prepare_insert_or_update(_mysql); - } - - // Process object. - _service_dependency_insupdate << sd; - _mysql.run_statement(_service_dependency_insupdate, - database::mysql_error::store_service_dependency, conn); - _add_action(conn, actions::service_dependencies); - } - // Delete. - else { - _logger_sql->info( - "SQL: removing service dependency of ({}, {}) on ({}, {})", - sd.dependent_host_id, sd.dependent_service_id, sd.host_id, - sd.service_id); - std::string query(fmt::format( - "DELETE FROM serivces_services_dependencies WHERE dependent_host_id={} " - "AND dependent_service_id={} AND host_id={} AND service_id={}", - sd.dependent_host_id, sd.dependent_service_id, sd.host_id, - sd.service_id)); - _mysql.run_query(query, database::mysql_error::empty, conn); - _add_action(conn, actions::service_dependencies); - } - *std::get<2>(t) = true; -} - /** * Process a service group event. * @@ -1419,9 +1268,8 @@ void conflict_manager::_process_service_group_member( void conflict_manager::_process_service( std::tuple<std::shared_ptr<io::data>, uint32_t, bool*>& t) { auto& d = std::get<0>(t); - _finish_action(-1, actions::host_parents | actions::comments | - actions::downtimes | actions::host_dependencies | - actions::service_dependencies); + _finish_action( + -1, actions::host_parents | actions::comments | actions::downtimes); // Processed object. const neb::service& s(*static_cast<neb::service const*>(d.get())); @@ -1472,9 +1320,8 @@ void conflict_manager::_process_service( void conflict_manager::_process_service_status( std::tuple<std::shared_ptr<io::data>, uint32_t, bool*>& t) { auto& d = std::get<0>(t); - _finish_action(-1, actions::host_parents | actions::comments | - actions::downtimes | actions::host_dependencies | - actions::service_dependencies); + _finish_action( + -1, actions::host_parents | actions::comments | actions::downtimes); // Processed object. neb::service_status const& ss{ *static_cast<neb::service_status const*>(d.get())}; @@ -1771,8 +1618,7 @@ void conflict_manager::_update_downtimes() { _logger_sql->debug("sql: update downtimes"); int32_t conn = special_conn::downtime % _mysql.connections_count(); _finish_action(-1, actions::hosts | actions::instances | actions::downtimes | - actions::host_parents | actions::host_dependencies | - actions::service_dependencies); + actions::host_parents); if (!_downtimes_queue.empty()) { auto it = _downtimes_queue.begin(); std::ostringstream oss; diff --git a/broker/unified_sql/inc/com/centreon/broker/unified_sql/stream.hh b/broker/unified_sql/inc/com/centreon/broker/unified_sql/stream.hh index f0944953f6f..1a86768f5c9 100644 --- a/broker/unified_sql/inc/com/centreon/broker/unified_sql/stream.hh +++ b/broker/unified_sql/inc/com/centreon/broker/unified_sql/stream.hh @@ -159,11 +159,9 @@ class stream : public io::stream { enum special_conn { custom_variable, downtime, - host_dependency, host_group, host_parent, log, - service_dependency, service_group, severity, tag, @@ -176,23 +174,21 @@ class stream : public io::stream { comments = 1 << 1, custom_variables = 1 << 2, downtimes = 1 << 3, - host_dependencies = 1 << 4, - host_hostgroups = 1 << 5, - host_parents = 1 << 6, - hostgroups = 1 << 7, - hosts = 1 << 8, - instances = 1 << 9, - modules = 1 << 10, - service_dependencies = 1 << 11, - service_servicegroups = 1 << 12, - servicegroups = 1 << 13, - services = 1 << 14, - index_data = 1 << 15, - metrics = 1 << 16, - severities = 1 << 17, - tags = 1 << 18, - resources = 1 << 19, - resources_tags = 1 << 20, + host_hostgroups = 1 << 4, + host_parents = 1 << 5, + hostgroups = 1 << 6, + hosts = 1 << 7, + instances = 1 << 8, + modules = 1 << 9, + service_servicegroups = 1 << 10, + servicegroups = 1 << 11, + services = 1 << 12, + index_data = 1 << 13, + metrics = 1 << 14, + severities = 1 << 15, + tags = 1 << 16, + resources = 1 << 17, + resources_tags = 1 << 18, }; struct index_info { @@ -335,8 +331,6 @@ class stream : public io::stream { database::mysql_stmt _flapping_status_insupdate; database::mysql_stmt _host_check_update; database::mysql_stmt _pb_host_check_update; - database::mysql_stmt _host_exe_dependency_insupdate; - database::mysql_stmt _host_notif_dependency_insupdate; database::mysql_stmt _host_group_insupdate; database::mysql_stmt _pb_host_group_insupdate; database::mysql_stmt _host_group_member_delete; @@ -355,8 +349,6 @@ class stream : public io::stream { database::mysql_stmt _pb_instance_status_insupdate; database::mysql_stmt _service_check_update; database::mysql_stmt _pb_service_check_update; - database::mysql_stmt _service_dependency_insupdate; - database::mysql_stmt _pb_service_dependency_insupdate; database::mysql_stmt _service_group_insupdate; database::mysql_stmt _pb_service_group_insupdate; database::mysql_stmt _service_group_member_delete; @@ -419,8 +411,6 @@ class stream : public io::stream { void _process_pb_downtime(const std::shared_ptr<io::data>& d); void _process_host_check(const std::shared_ptr<io::data>& d); void _process_pb_host_check(const std::shared_ptr<io::data>& d); - void _process_host_dependency(const std::shared_ptr<io::data>& d); - void _process_pb_host_dependency(const std::shared_ptr<io::data>& d); void _process_host_group(const std::shared_ptr<io::data>& d); void _process_pb_host_group(const std::shared_ptr<io::data>& d); void _process_host_group_member(const std::shared_ptr<io::data>& d); @@ -436,8 +426,6 @@ class stream : public io::stream { void _process_log(const std::shared_ptr<io::data>& d); void _process_service_check(const std::shared_ptr<io::data>& d); void _process_pb_service_check(const std::shared_ptr<io::data>& d); - void _process_service_dependency(const std::shared_ptr<io::data>& d); - void _process_pb_service_dependency(const std::shared_ptr<io::data>& d); void _process_service_group(const std::shared_ptr<io::data>& d); void _process_pb_service_group(const std::shared_ptr<io::data>& d); void _process_service_group_member(const std::shared_ptr<io::data>& d); diff --git a/broker/unified_sql/src/stream.cc b/broker/unified_sql/src/stream.cc index 32435ba2a67..8ee2af74538 100644 --- a/broker/unified_sql/src/stream.cc +++ b/broker/unified_sql/src/stream.cc @@ -73,7 +73,7 @@ constexpr void (stream::*const stream::neb_processing_table[])( nullptr, nullptr, &stream::_process_host_check, - &stream::_process_host_dependency, + nullptr, &stream::_process_host_group, &stream::_process_host_group_member, &stream::_process_host, @@ -84,7 +84,7 @@ constexpr void (stream::*const stream::neb_processing_table[])( &stream::_process_log, nullptr, &stream::_process_service_check, - &stream::_process_service_dependency, + nullptr, &stream::_process_service_group, &stream::_process_service_group_member, &stream::_process_service, @@ -111,8 +111,8 @@ constexpr void (stream::*const stream::neb_processing_table[])( &stream::_process_pb_instance, &stream::_process_pb_acknowledgement, &stream::_process_pb_responsive_instance, - &stream::_process_pb_host_dependency, - &stream::_process_pb_service_dependency, + nullptr, + nullptr, &stream::_process_pb_host_group, &stream::_process_pb_host_group_member, &stream::_process_pb_service_group, @@ -878,8 +878,7 @@ void stream::process_stop(const std::shared_ptr<io::data>& d) { _finish_action(-1, actions::hosts | actions::acknowledgements | actions::modules | actions::downtimes | actions::comments | actions::servicegroups | - actions::hostgroups | actions::service_dependencies | - actions::host_dependencies); + actions::hostgroups); // Log message. _logger_sql->info("unified_sql: Disabling poller (id: {}, running: no)", @@ -1397,22 +1396,6 @@ void stream::_init_statements() { "last_check=?," // 9: last_check "output=? " // 10: output "WHERE id=? AND parent_id=?"); // 11, 12: service_id and host_id - const std::string host_exe_dep_query( - "INSERT INTO hosts_hosts_dependencies (dependent_host_id, host_id, " - "dependency_period, execution_failure_options, inherits_parent) " - "VALUES(?,?,?,?,?) ON DUPLICATE KEY UPDATE " - "dependency_period=VALUES(dependency_period), " - "execution_failure_options=VALUES(execution_failure_options), " - "inherits_parent=VALUES(inherits_parent)"); - _host_exe_dependency_insupdate = _mysql.prepare_query(host_exe_dep_query); - const std::string host_notif_dep_query( - "INSERT INTO hosts_hosts_dependencies (dependent_host_id, host_id, " - "dependency_period, notification_failure_options, inherits_parent) " - "VALUES(?,?,?,?,?) ON DUPLICATE KEY UPDATE " - "dependency_period=VALUES(dependency_period), " - "notification_failure_options=VALUES(notification_failure_options), " - "inherits_parent=VALUES(inherits_parent)"); - _host_notif_dependency_insupdate = _mysql.prepare_query(host_notif_dep_query); if (_store_in_hosts_services) { if (_bulk_prepared_statement) { auto hu = std::make_unique<database::mysql_bulk_stmt>(hscr_query); diff --git a/broker/unified_sql/src/stream_sql.cc b/broker/unified_sql/src/stream_sql.cc index edd91e546ed..5ad2836dbeb 100644 --- a/broker/unified_sql/src/stream_sql.cc +++ b/broker/unified_sql/src/stream_sql.cc @@ -117,18 +117,6 @@ void stream::_clean_tables(uint32_t instance_id) { conn); _add_action(conn, actions::servicegroups); - /* Remove host dependencies. */ - SPDLOG_LOGGER_DEBUG(_logger_sql, - "unified sql: remove host dependencies (instance_id: {})", - instance_id); - query = fmt::format( - "DELETE hhd FROM hosts_hosts_dependencies AS hhd INNER JOIN hosts as " - "h ON hhd.host_id=h.host_id OR hhd.dependent_host_id=h.host_id WHERE " - "h.instance_id={}", - instance_id); - _mysql.run_query(query, database::mysql_error::clean_host_dependencies, conn); - _add_action(conn, actions::host_dependencies); - /* Remove host parents. */ SPDLOG_LOGGER_DEBUG(_logger_sql, "unified sql: remove host parents (instance_id: {})", @@ -141,23 +129,6 @@ void stream::_clean_tables(uint32_t instance_id) { _mysql.run_query(query, database::mysql_error::clean_host_parents, conn); _add_action(conn, actions::host_parents); - /* Remove service dependencies. */ - SPDLOG_LOGGER_DEBUG( - _logger_sql, "unified sql: remove service dependencies (instance_id: {})", - instance_id); - query = fmt::format( - "DELETE ssd FROM services_services_dependencies AS ssd" - " INNER JOIN services as s" - " ON ssd.service_id=s.service_id OR " - "ssd.dependent_service_id=s.service_id" - " INNER JOIN hosts as h" - " ON s.host_id=h.host_id" - " WHERE h.instance_id={}", - instance_id); - _mysql.run_query(query, database::mysql_error::clean_service_dependencies, - conn); - _add_action(conn, actions::service_dependencies); - /* Remove list of modules. */ SPDLOG_LOGGER_DEBUG(_logger_sql, "unified_sql: remove list of modules (instance_id: {})", @@ -564,8 +535,7 @@ void stream::_process_pb_acknowledgement(const std::shared_ptr<io::data>& d) { */ void stream::_process_comment(const std::shared_ptr<io::data>& d) { _finish_action(-1, actions::hosts | actions::instances | - actions::host_parents | actions::host_dependencies | - actions::service_dependencies | actions::comments); + actions::host_parents | actions::comments); // Cast object. neb::comment const& cmmnt{*static_cast<neb::comment const*>(d.get())}; @@ -1074,8 +1044,7 @@ bool stream::_host_instance_known(uint64_t host_id) const { */ void stream::_process_host_check(const std::shared_ptr<io::data>& d) { _finish_action(-1, actions::instances | actions::downtimes | - actions::comments | actions::host_dependencies | - actions::host_parents | actions::service_dependencies); + actions::comments | actions::host_parents); // Cast object. neb::host_check const& hc = *static_cast<neb::host_check const*>(d.get()); @@ -1145,8 +1114,7 @@ void stream::_process_host_check(const std::shared_ptr<io::data>& d) { */ void stream::_process_pb_host_check(const std::shared_ptr<io::data>& d) { _finish_action(-1, actions::instances | actions::downtimes | - actions::comments | actions::host_dependencies | - actions::host_parents | actions::service_dependencies); + actions::comments | actions::host_parents); // Cast object. const neb::pb_host_check& hc_obj = @@ -1217,140 +1185,6 @@ void stream::_process_pb_host_check(const std::shared_ptr<io::data>& d) { hc.next_check(), now); } -/** - * Process a host dependency event. - * - * @param[in] e Uncasted host dependency. - * - * @return The number of events that can be acknowledged. - */ -void stream::_process_host_dependency(const std::shared_ptr<io::data>& d) { - int32_t conn = special_conn::host_dependency % _mysql.connections_count(); - _finish_action(-1, actions::hosts | actions::host_parents | - actions::comments | actions::downtimes | - actions::host_dependencies | - actions::service_dependencies); - - // Cast object. - neb::host_dependency const& hd = - *static_cast<neb::host_dependency const*>(d.get()); - - // Insert/Update. - if (hd.enabled) { - SPDLOG_LOGGER_INFO(_logger_sql, - "SQL: enabling host dependency of {} on {}: execution " - "failure options: {} - notification failure options: {}", - hd.dependent_host_id, hd.host_id, - hd.execution_failure_options, - hd.notification_failure_options); - - // Process object. - if (!hd.execution_failure_options.empty()) { - _host_exe_dependency_insupdate.bind_value_as_i32(0, hd.dependent_host_id); - _host_exe_dependency_insupdate.bind_value_as_i32(1, hd.host_id); - _host_exe_dependency_insupdate.bind_value_as_str(2, hd.dependency_period); - _host_exe_dependency_insupdate.bind_value_as_str( - 3, hd.execution_failure_options); - _host_exe_dependency_insupdate.bind_value_as_tiny(4, hd.inherits_parent); - _mysql.run_statement(_host_exe_dependency_insupdate, - database::mysql_error::store_host_dependency, conn); - } else if (!hd.notification_failure_options.empty()) { - _host_notif_dependency_insupdate.bind_value_as_i32(0, - hd.dependent_host_id); - _host_notif_dependency_insupdate.bind_value_as_i32(1, hd.host_id); - _host_notif_dependency_insupdate.bind_value_as_str(2, - hd.dependency_period); - _host_notif_dependency_insupdate.bind_value_as_str( - 3, hd.notification_failure_options); - _host_notif_dependency_insupdate.bind_value_as_tiny(4, - hd.inherits_parent); - _mysql.run_statement(_host_notif_dependency_insupdate, - database::mysql_error::store_host_dependency, conn); - } - _add_action(conn, actions::host_dependencies); - } - // Delete. - else { - SPDLOG_LOGGER_INFO(_logger_sql, "SQL: removing host dependency of {} on {}", - hd.dependent_host_id, hd.host_id); - std::string query(fmt::format( - "DELETE FROM hosts_hosts_dependencies WHERE dependent_host_id={}" - " AND host_id={}", - hd.dependent_host_id, hd.host_id)); - _mysql.run_query(query, database::mysql_error::empty, conn); - _add_action(conn, actions::host_dependencies); - } -} - -/** - * Process a host dependency event. - * - * @param[in] e Uncasted host dependency. - * - * @return The number of events that can be acknowledged. - */ -void stream::_process_pb_host_dependency(const std::shared_ptr<io::data>& d) { - int32_t conn = special_conn::host_dependency % _mysql.connections_count(); - _finish_action(-1, actions::hosts | actions::host_parents | - actions::comments | actions::downtimes | - actions::host_dependencies | - actions::service_dependencies); - - // Cast object. - const neb::pb_host_dependency& hd_protobuf = - *static_cast<neb::pb_host_dependency const*>(d.get()); - const HostDependency& hd = hd_protobuf.obj(); - - // Insert/Update. - if (hd.enabled()) { - SPDLOG_LOGGER_INFO( - _logger_sql, - "SQL: enabling pb host dependency of {} on {}: execution failure " - "options: {} - notification failure options: {}", - hd.dependent_host_id(), hd.host_id(), hd.execution_failure_options(), - hd.notification_failure_options()); - - // Process object. - if (!hd.execution_failure_options().empty()) { - _host_exe_dependency_insupdate.bind_value_as_i32(0, - hd.dependent_host_id()); - _host_exe_dependency_insupdate.bind_value_as_i32(1, hd.host_id()); - _host_exe_dependency_insupdate.bind_value_as_str(2, - hd.dependency_period()); - _host_exe_dependency_insupdate.bind_value_as_str( - 3, hd.execution_failure_options()); - _host_exe_dependency_insupdate.bind_value_as_tiny(4, - hd.inherits_parent()); - _mysql.run_statement(_host_exe_dependency_insupdate, - database::mysql_error::store_host_dependency, conn); - } else if (!hd.notification_failure_options().empty()) { - _host_notif_dependency_insupdate.bind_value_as_i32( - 0, hd.dependent_host_id()); - _host_notif_dependency_insupdate.bind_value_as_i32(1, hd.host_id()); - _host_notif_dependency_insupdate.bind_value_as_str( - 2, hd.dependency_period()); - _host_notif_dependency_insupdate.bind_value_as_str( - 3, hd.notification_failure_options()); - _host_notif_dependency_insupdate.bind_value_as_tiny(4, - hd.inherits_parent()); - _mysql.run_statement(_host_notif_dependency_insupdate, - database::mysql_error::store_host_dependency, conn); - } - _add_action(conn, actions::host_dependencies); - } - // Delete. - else { - SPDLOG_LOGGER_INFO(_logger_sql, "SQL: removing host dependency of {} on {}", - hd.dependent_host_id(), hd.host_id()); - std::string query(fmt::format( - "DELETE FROM hosts_hosts_dependencies WHERE dependent_host_id={}" - " AND host_id={}", - hd.dependent_host_id(), hd.host_id())); - _mysql.run_query(query, database::mysql_error::empty, conn); - _add_action(conn, actions::host_dependencies); - } -} - /** * Process a host group event. * @@ -1419,6 +1253,8 @@ void stream::_process_pb_host_group(const std::shared_ptr<io::data>& d) { std::shared_ptr<neb::pb_host_group> hgd = std::static_pointer_cast<neb::pb_host_group>(d); const HostGroup& hg = hgd->obj(); + _logger_sql->debug("process pb hostgroup {}, enabled {}", hg.hostgroup_id(), + hg.enabled()); if (hg.enabled()) { SPDLOG_LOGGER_INFO(_logger_sql, @@ -1580,6 +1416,9 @@ void stream::_process_pb_host_group_member(const std::shared_ptr<io::data>& d) { const neb::pb_host_group_member& hgmp{ *static_cast<const neb::pb_host_group_member*>(d.get())}; const HostGroupMember& hgm = hgmp.obj(); + _logger_sql->debug( + "process pb hostgroup members hostgroup {}, member {}, enabled {}", + hgm.hostgroup_id(), hgm.host_id(), hgm.enabled()); if (!_host_instance_known(hgm.host_id())) { SPDLOG_LOGGER_WARN( @@ -1612,7 +1451,7 @@ void stream::_process_pb_host_group_member(const std::shared_ptr<io::data>& d) { query_preparator qp(neb::pb_host_group_member::static_type(), unique); _pb_host_group_member_insert = qp.prepare_insert_into( _mysql, "hosts_hostgroups ", /*space is mandatory to avoid - conflict with _process_host_dependency*/ + conflict with _process_host_group_member*/ {{3, "hostgroup_id", io::protobuf_base::invalid_on_zero, 0}, {5, "host_id", io::protobuf_base::invalid_on_zero, 0}}); } @@ -1622,16 +1461,16 @@ void stream::_process_pb_host_group_member(const std::shared_ptr<io::data>& d) { if (_hostgroup_cache.find(hgm.hostgroup_id()) == _hostgroup_cache.end()) { SPDLOG_LOGGER_ERROR(_logger_sql, "SQL: host group {} {} does not exist - insertion " - "before insertion of " - "members", + "before insertion of members", hgm.hostgroup_id(), hgm.name()); _prepare_pb_hg_insupdate_statement(); neb::pb_host_group hg; - hg.mut_obj().set_hostgroup_id(hgm.hostgroup_id()); - hg.mut_obj().set_name(hgm.name()); - hg.mut_obj().set_enabled(true); - hg.mut_obj().set_poller_id(_cache_host_instance[hgm.host_id()]); + auto& obj = hg.mut_obj(); + obj.set_hostgroup_id(hgm.hostgroup_id()); + obj.set_name(hgm.name()); + obj.set_enabled(true); + obj.set_poller_id(_cache_host_instance[hgm.host_id()]); _pb_host_group_insupdate << hg; _mysql.run_statement(_pb_host_group_insupdate, @@ -1682,9 +1521,8 @@ void stream::_process_pb_host_group_member(const std::shared_ptr<io::data>& d) { */ void stream::_process_host(const std::shared_ptr<io::data>& d) { _finish_action(-1, actions::instances | actions::hostgroups | - actions::host_dependencies | actions::host_parents | - actions::custom_variables | actions::downtimes | - actions::comments | actions::service_dependencies); + actions::host_parents | actions::custom_variables | + actions::downtimes | actions::comments); neb::host& h = *static_cast<neb::host*>(d.get()); // Log message. @@ -1743,8 +1581,7 @@ void stream::_process_host(const std::shared_ptr<io::data>& d) { */ void stream::_process_host_parent(const std::shared_ptr<io::data>& d) { int32_t conn = special_conn::host_parent % _mysql.connections_count(); - _finish_action(-1, actions::hosts | actions::host_dependencies | - actions::comments | actions::downtimes); + _finish_action(-1, actions::hosts | actions::comments | actions::downtimes); neb::host_parent const& hp(*static_cast<neb::host_parent const*>(d.get())); @@ -1798,8 +1635,7 @@ void stream::_process_host_parent(const std::shared_ptr<io::data>& d) { */ void stream::_process_pb_host_parent(const std::shared_ptr<io::data>& d) { int32_t conn = special_conn::host_parent % _mysql.connections_count(); - _finish_action(-1, actions::hosts | actions::host_dependencies | - actions::comments | actions::downtimes); + _finish_action(-1, actions::hosts | actions::comments | actions::downtimes); std::shared_ptr<neb::pb_host_parent> hpp = std::static_pointer_cast<neb::pb_host_parent>(d); @@ -1867,8 +1703,7 @@ void stream::_process_host_status(const std::shared_ptr<io::data>& d) { _finish_action(-1, actions::instances | actions::downtimes | actions::comments | actions::custom_variables | - actions::hostgroups | actions::host_dependencies | - actions::host_parents); + actions::hostgroups | actions::host_parents); // Processed object. neb::host_status const& hs(*static_cast<neb::host_status const*>(d.get())); @@ -1928,9 +1763,8 @@ void stream::_process_host_status(const std::shared_ptr<io::data>& d) { */ void stream::_process_pb_host(const std::shared_ptr<io::data>& d) { _finish_action(-1, actions::instances | actions::hostgroups | - actions::host_dependencies | actions::host_parents | - actions::custom_variables | actions::downtimes | - actions::comments | actions::service_dependencies | + actions::host_parents | actions::custom_variables | + actions::downtimes | actions::comments | actions::severities | actions::resources_tags | actions::tags); auto hst{static_cast<const neb::pb_host*>(d.get())}; @@ -2409,9 +2243,8 @@ uint64_t stream::_process_pb_host_in_resources(const Host& h, int32_t conn) { */ void stream::_process_pb_adaptive_host(const std::shared_ptr<io::data>& d) { SPDLOG_LOGGER_INFO(_logger_sql, "unified_sql: processing pb adaptive host"); - _finish_action(-1, actions::host_parents | actions::comments | - actions::downtimes | actions::host_dependencies | - actions::service_dependencies); + _finish_action( + -1, actions::host_parents | actions::comments | actions::downtimes); // Processed object. auto h{static_cast<const neb::pb_adaptive_host*>(d.get())}; auto& ah = h->obj(); @@ -2523,15 +2356,16 @@ void stream::_process_pb_adaptive_host(const std::shared_ptr<io::data>& d) { * */ void stream::_process_pb_host_status(const std::shared_ptr<io::data>& d) { - _finish_action(-1, actions::host_parents | actions::comments | - actions::downtimes | actions::host_dependencies); + _finish_action( + -1, actions::host_parents | actions::comments | actions::downtimes); // Processed object. auto h{static_cast<const neb::pb_host_status*>(d.get())}; auto& hscr = h->obj(); SPDLOG_LOGGER_DEBUG(_logger_sql, - "unified_sql: pb host status check result output: <<{}>>", - hscr.output()); + "unified_sql: pb host {} status check result output: " + "<<{}>> - last_check: {}", + hscr.host_id(), hscr.output(), hscr.last_check()); SPDLOG_LOGGER_DEBUG( _logger_sql, "unified_sql: pb host status check result perfdata: <<{}>>", hscr.perfdata()); @@ -2749,8 +2583,7 @@ void stream::_process_instance(const std::shared_ptr<io::data>& d) { _finish_action(-1, actions::hosts | actions::acknowledgements | actions::modules | actions::downtimes | actions::comments | actions::servicegroups | - actions::hostgroups | actions::service_dependencies | - actions::host_dependencies); + actions::hostgroups); // Log message. SPDLOG_LOGGER_INFO( @@ -2801,8 +2634,7 @@ void stream::_process_pb_instance(const std::shared_ptr<io::data>& d) { _finish_action(-1, actions::hosts | actions::acknowledgements | actions::modules | actions::downtimes | actions::comments | actions::servicegroups | - actions::hostgroups | actions::service_dependencies | - actions::host_dependencies); + actions::hostgroups); /* Now, the local::pb_stop is handled by unified_sql. So the pb_instance with * running = false, seems no more useful. */ @@ -3132,9 +2964,8 @@ void stream::_process_pb_log(const std::shared_ptr<io::data>& d) { * @return The number of events that can be acknowledged. */ void stream::_process_service_check(const std::shared_ptr<io::data>& d) { - _finish_action(-1, actions::downtimes | actions::comments | - actions::host_dependencies | actions::host_parents | - actions::service_dependencies); + _finish_action( + -1, actions::downtimes | actions::comments | actions::host_parents); // Cast object. neb::service_check const& sc( @@ -3206,9 +3037,8 @@ void stream::_process_service_check(const std::shared_ptr<io::data>& d) { * @return The number of events that can be acknowledged. */ void stream::_process_pb_service_check(const std::shared_ptr<io::data>& d) { - _finish_action(-1, actions::downtimes | actions::comments | - actions::host_dependencies | actions::host_parents | - actions::service_dependencies); + _finish_action( + -1, actions::downtimes | actions::comments | actions::host_parents); // Cast object. const neb::pb_service_check& pb_sc( @@ -3281,140 +3111,6 @@ void stream::_process_pb_service_check(const std::shared_ptr<io::data>& d) { sc.check_type(), sc.next_check(), now); } -/** - * Process a service dependency event. - * - * @param[in] e Uncasted service dependency. - * - * @return The number of events that can be acknowledged. - */ -void stream::_process_service_dependency(const std::shared_ptr<io::data>& d) { - int32_t conn = special_conn::service_dependency % _mysql.connections_count(); - _finish_action(-1, actions::hosts | actions::host_parents | - actions::downtimes | actions::comments | - actions::host_dependencies | - actions::service_dependencies); - - // Cast object. - neb::service_dependency const& sd( - *static_cast<neb::service_dependency const*>(d.get())); - - // Insert/Update. - if (sd.enabled) { - SPDLOG_LOGGER_INFO( - _logger_sql, "SQL: enabling service dependency of ({}, {}) on ({}, {})", - sd.dependent_host_id, sd.dependent_service_id, sd.host_id, - sd.service_id); - - // Prepare queries. - if (!_service_dependency_insupdate.prepared()) { - query_preparator::event_unique unique; - unique.insert("dependent_host_id"); - unique.insert("dependent_service_id"); - unique.insert("host_id"); - unique.insert("service_id"); - query_preparator qp(neb::service_dependency::static_type(), unique); - _service_dependency_insupdate = qp.prepare_insert_or_update(_mysql); - } - - // Process object. - _service_dependency_insupdate << sd; - _mysql.run_statement(_service_dependency_insupdate, - database::mysql_error::store_service_dependency, conn); - _add_action(conn, actions::service_dependencies); - } - // Delete. - else { - SPDLOG_LOGGER_INFO( - _logger_sql, "SQL: removing service dependency of ({}, {}) on ({}, {})", - sd.dependent_host_id, sd.dependent_service_id, sd.host_id, - sd.service_id); - std::string query(fmt::format( - "DELETE FROM services_services_dependencies WHERE dependent_host_id={} " - "AND dependent_service_id={} AND host_id={} AND service_id={}", - sd.dependent_host_id, sd.dependent_service_id, sd.host_id, - sd.service_id)); - _mysql.run_query(query, database::mysql_error::empty, conn); - _add_action(conn, actions::service_dependencies); - } -} - -/** - * Process a service dependency event. - * - * @param[in] e Uncasted service dependency. - * - * @return The number of events that can be acknowledged. - */ -void stream::_process_pb_service_dependency( - const std::shared_ptr<io::data>& d) { - int32_t conn = special_conn::service_dependency % _mysql.connections_count(); - _finish_action(-1, actions::hosts | actions::host_parents | - actions::downtimes | actions::comments | - actions::host_dependencies | - actions::service_dependencies); - - // Cast object. - const neb::pb_service_dependency& proto_obj = - *static_cast<neb::pb_service_dependency const*>(d.get()); - const ServiceDependency& sd = proto_obj.obj(); - - // Insert/Update. - if (sd.enabled()) { - SPDLOG_LOGGER_INFO( - _logger_sql, "SQL: enabling service dependency of ({}, {}) on ({}, {})", - sd.dependent_host_id(), sd.dependent_service_id(), sd.host_id(), - sd.service_id()); - - // Prepare queries. - if (!_pb_service_dependency_insupdate.prepared()) { - query_preparator::event_pb_unique unique{ - {6, "host_id", io::protobuf_base::invalid_on_zero, 0}, - {10, "service_id", io::protobuf_base::invalid_on_zero, 0}, - {3, "dependent_host_id", io::protobuf_base::invalid_on_zero, 0}, - {9, "dependent_service_id", io::protobuf_base::invalid_on_zero, 0}}; - query_preparator qp(neb::pb_service_dependency::static_type(), unique); - _pb_service_dependency_insupdate = qp.prepare_insert_or_update_table( - _mysql, "services_services_dependencies ", /*space is mandatory to - avoid conflict with _process_service_dependency*/ - {{6, "host_id", io::protobuf_base::invalid_on_zero, 0}, - {10, "service_id", io::protobuf_base::invalid_on_zero, 0}, - {3, "dependent_host_id", io::protobuf_base::invalid_on_zero, 0}, - {9, "dependent_service_id", io::protobuf_base::invalid_on_zero, 0}, - {2, "dependency_period", 0, - get_centreon_storage_services_services_dependencies_col_size( - centreon_storage_services_services_dependencies_dependency_period)}, - {5, "execution_failure_options", 0, - get_centreon_storage_services_services_dependencies_col_size( - centreon_storage_services_services_dependencies_execution_failure_options)}, - {7, "inherits_parent", 0, 0}, - {8, "notification_failure_options", 0, - get_centreon_storage_services_services_dependencies_col_size( - centreon_storage_services_services_dependencies_notification_failure_options)}}); - } - - // Process object. - _pb_service_dependency_insupdate << proto_obj; - _mysql.run_statement(_pb_service_dependency_insupdate, - database::mysql_error::store_service_dependency, conn); - _add_action(conn, actions::service_dependencies); - } - // Delete. - else { - SPDLOG_LOGGER_INFO( - _logger_sql, "SQL: removing service dependency of ({}, {}) on ({}, {})", - sd.dependent_host_id(), sd.dependent_service_id(), sd.host_id(), - sd.service_id()); - std::string query(fmt::format( - "DELETE FROM services_services_dependencies WHERE dependent_host_id={} " - "AND dependent_service_id={} AND host_id={} AND service_id={}", - sd.dependent_host_id(), sd.dependent_service_id(), sd.host_id(), - sd.service_id())); - _mysql.run_query(query, database::mysql_error::empty, conn); - _add_action(conn, actions::service_dependencies); - } -} - /** * Process a service group event. * @@ -3687,10 +3383,11 @@ void stream::_process_pb_service_group_member( _prepare_sg_insupdate_statement(); neb::pb_service_group sg; - sg.mut_obj().set_servicegroup_id(sgm.servicegroup_id()); - sg.mut_obj().set_name(sgm.name()); - sg.mut_obj().set_enabled(true); - sg.mut_obj().set_poller_id(sgm.poller_id()); + auto& obj = sg.mut_obj(); + obj.set_servicegroup_id(sgm.servicegroup_id()); + obj.set_name(sgm.name()); + obj.set_enabled(true); + obj.set_poller_id(sgm.poller_id()); _pb_service_group_insupdate << sg; _mysql.run_statement(_pb_service_group_insupdate, @@ -3745,9 +3442,8 @@ void stream::_process_pb_service_group_member( * @return The number of events that can be acknowledged. */ void stream::_process_service(const std::shared_ptr<io::data>& d) { - _finish_action(-1, actions::host_parents | actions::comments | - actions::downtimes | actions::host_dependencies | - actions::service_dependencies); + _finish_action( + -1, actions::host_parents | actions::comments | actions::downtimes); // Processed object. const neb::service& s(*static_cast<neb::service const*>(d.get())); @@ -3804,8 +3500,7 @@ void stream::_process_service(const std::shared_ptr<io::data>& d) { */ void stream::_process_pb_service(const std::shared_ptr<io::data>& d) { _finish_action(-1, actions::host_parents | actions::comments | - actions::downtimes | actions::host_dependencies | - actions::service_dependencies | actions::severities | + actions::downtimes | actions::severities | actions::resources_tags | actions::tags); // Processed object. auto svc{static_cast<neb::pb_service const*>(d.get())}; @@ -4279,9 +3974,8 @@ uint64_t stream::_process_pb_service_in_resources(const Service& s, void stream::_process_pb_adaptive_service(const std::shared_ptr<io::data>& d) { SPDLOG_LOGGER_DEBUG(_logger_sql, "unified_sql: processing pb adaptive service"); - _finish_action(-1, actions::host_parents | actions::comments | - actions::downtimes | actions::host_dependencies | - actions::service_dependencies); + _finish_action( + -1, actions::host_parents | actions::comments | actions::downtimes); // Processed object. auto s{static_cast<const neb::pb_adaptive_service*>(d.get())}; auto& as = s->obj(); @@ -4518,9 +4212,8 @@ void stream::_process_service_status(const std::shared_ptr<io::data>& d) { if (!_store_in_hosts_services) return; - _finish_action(-1, actions::host_parents | actions::comments | - actions::downtimes | actions::host_dependencies | - actions::service_dependencies); + _finish_action( + -1, actions::host_parents | actions::comments | actions::downtimes); // Processed object. neb::service_status const& ss{ *static_cast<neb::service_status const*>(d.get())}; @@ -4588,9 +4281,8 @@ void stream::_process_service_status(const std::shared_ptr<io::data>& d) { * */ void stream::_process_pb_service_status(const std::shared_ptr<io::data>& d) { - _finish_action(-1, actions::host_parents | actions::comments | - actions::downtimes | actions::host_dependencies | - actions::service_dependencies); + _finish_action( + -1, actions::host_parents | actions::comments | actions::downtimes); // Processed object. auto s{static_cast<const neb::pb_service_status*>(d.get())}; auto& sscr = s->obj(); diff --git a/broker/unified_sql/src/stream_storage.cc b/broker/unified_sql/src/stream_storage.cc index 716cbad31c1..c502953f953 100644 --- a/broker/unified_sql/src/stream_storage.cc +++ b/broker/unified_sql/src/stream_storage.cc @@ -817,9 +817,8 @@ void stream::_check_queues(boost::system::error_code ec) { try { if (_bulk_prepared_statement) { - _finish_action(-1, actions::host_parents | actions::comments | - actions::downtimes | actions::host_dependencies | - actions::service_dependencies); + _finish_action( + -1, actions::host_parents | actions::comments | actions::downtimes); if (_store_in_hosts_services) { if (_hscr_bind) { SPDLOG_LOGGER_TRACE( @@ -962,9 +961,7 @@ void stream::_check_queues(boost::system::error_code ec) { SPDLOG_LOGGER_DEBUG(_logger_sql, "{} new downtimes inserted", _downtimes->row_count()); _finish_action(-1, actions::hosts | actions::instances | - actions::downtimes | actions::host_parents | - actions::host_dependencies | - actions::service_dependencies); + actions::downtimes | actions::host_parents); int32_t conn = special_conn::downtime % _mysql.connections_count(); _downtimes->execute(_mysql, database::mysql_error::store_downtime, conn); diff --git a/common/inc/com/centreon/common/utf8.hh b/common/inc/com/centreon/common/utf8.hh index e9a671f6202..a74dd6321cd 100644 --- a/common/inc/com/centreon/common/utf8.hh +++ b/common/inc/com/centreon/common/utf8.hh @@ -42,8 +42,8 @@ fmt::string_view truncate_utf8(const T& str, size_t s) { return fmt::string_view(str.data(), s); } -std::string check_string_utf8(std::string const& str) noexcept; +std::string check_string_utf8(const std::string_view& str) noexcept; size_t adjust_size_utf8(const std::string& str, size_t s); } // namespace com::centreon::common -#endif \ No newline at end of file +#endif diff --git a/common/src/utf8.cc b/common/src/utf8.cc index 7ff784b7167..7ef6ebed5ed 100644 --- a/common/src/utf8.cc +++ b/common/src/utf8.cc @@ -30,8 +30,8 @@ #include "utf8.hh" std::string com::centreon::common::check_string_utf8( - std::string const& str) noexcept { - std::string::const_iterator it; + const std::string_view& str) noexcept { + std::string_view::const_iterator it; for (it = str.begin(); it != str.end();) { uint32_t val = (*it & 0xff); if ((val & 0x80) == 0) { @@ -68,7 +68,7 @@ std::string com::centreon::common::check_string_utf8( } if (it == str.end()) - return str; + return std::string(str); /* Not an UTF-8 string */ bool is_cp1252 = true, is_iso8859 = true; diff --git a/engine/inc/com/centreon/engine/nebstructs.hh b/engine/inc/com/centreon/engine/nebstructs.hh index 00ae56981cb..786e1f56b53 100644 --- a/engine/inc/com/centreon/engine/nebstructs.hh +++ b/engine/inc/com/centreon/engine/nebstructs.hh @@ -98,7 +98,7 @@ typedef struct nebstruct_custom_variable_struct { struct timeval timestamp = {}; std::string_view var_name; std::string_view var_value; - void* object_ptr; + void* object_ptr = nullptr; } nebstruct_custom_variable_data; /* Downtime data structure. */ diff --git a/tests/broker-engine/bbdo-protobuf.robot b/tests/broker-engine/bbdo-protobuf.robot index 835eef63903..f250fcb0cb9 100644 --- a/tests/broker-engine/bbdo-protobuf.robot +++ b/tests/broker-engine/bbdo-protobuf.robot @@ -175,78 +175,6 @@ BEPBCVS [Teardown] Ctn Stop Engine Broker And Save Logs True -BEPB_HOST_DEPENDENCY - [Documentation] BBDO 3 communication of host dependencies. - [Tags] broker engine protobuf bbdo - Ctn Config Engine ${1} - Ctn Config Engine Add Cfg File 0 dependencies.cfg - Ctn Add Host Dependency 0 host_1 host_2 - Ctn Config Broker central - Ctn Config Broker module - Ctn Config BBDO3 ${1} - Ctn Broker Config Log central sql trace - Ctn Config Broker Sql Output central unified_sql - Ctn Clear Retention - ${start} Get Current Date - Ctn Start Broker True - Ctn Start engine - - ${result} Common.Ctn Check Host Dependencies 2 1 24x7 1 ou dp 30 - Should Be True ${result} No notification dependency from 2 to 1 with timeperiod 24x7 on 'ou' - - Ctn Config Engine ${1} - Ctn Reload Engine - - ${result} Common.Ctn Check No Host Dependencies 30 - Should Be True ${result} No host dependency should be defined - - [Teardown] Ctn Stop Engine Broker And Save Logs True - -BEPB_SERVICE_DEPENDENCY - [Documentation] bbdo_version 3 communication of host dependencies. - [Tags] broker engine protobuf bbdo - Ctn Config Engine ${1} - Ctn Config Engine Add Cfg File 0 dependencies.cfg - Ctn Add Service Dependency 0 host_1 host_2 service_1 service_21 - Ctn Config Broker central - Ctn Config Broker module - Ctn Config BBDO3 ${1} - Ctn Broker Config Log central sql trace - Ctn Config Broker Sql Output central unified_sql - Ctn Clear Retention - ${start} Get Current Date - Ctn Start Broker True - Ctn Start engine - - Connect To Database pymysql ${DBName} ${DBUser} ${DBPass} ${DBHost} ${DBPort} - - FOR ${index} IN RANGE 30 - ${output} Query - ... SELECT dependent_host_id, dependent_service_id, host_id, service_id, dependency_period, inherits_parent, notification_failure_options FROM services_services_dependencies; - - Log To Console ${output} - Sleep 1s - IF "${output}" == "((2, 21, 1, 1, '24x7', 1, 'c'),)" BREAK - END - Should Be Equal As Strings - ... ${output} - ... ((2, 21, 1, 1, '24x7', 1, 'c'),) - ... host dependency not found in database - - Ctn Config Engine ${1} - Ctn Reload Engine - - FOR ${index} IN RANGE 30 - ${output} Query - ... SELECT dependent_host_id, host_id, dependency_period, inherits_parent, notification_failure_options FROM hosts_hosts_dependencies - Log To Console ${output} - Sleep 1s - IF "${output}" == "()" BREAK - END - Should Be Equal As Strings ${output} () host dependency not deleted from database - - [Teardown] Ctn Stop Engine Broker And Save Logs True - BEPBHostParent [Documentation] bbdo_version 3 communication of host parent relations [Tags] broker engine protobuf bbdo diff --git a/tests/resources/Common.py b/tests/resources/Common.py index daec2c09ed5..b7f3c09ba3f 100644 --- a/tests/resources/Common.py +++ b/tests/resources/Common.py @@ -1638,56 +1638,6 @@ def ctn_check_types_in_resources(lst: list): return False -def ctn_check_host_dependencies(dep_host_id, host_id, dep_period, inherits_parent, notif_fail_opts, exec_fail_opts, timeout=TIMEOUT): - limit = time.time() + timeout - logger.console( - f"SELECT count(*) FROM hosts_hosts_dependencies WHERE dependent_host_id={dep_host_id} AND host_id={host_id} AND dependency_period='{dep_period}' AND inherits_parent={inherits_parent} AND notification_failure_options='{notif_fail_opts}' AND execution_failure_options='{exec_fail_opts}'") - while time.time() < limit: - connection = pymysql.connect(host=DB_HOST, - user=DB_USER, - password=DB_PASS, - database=DB_NAME_STORAGE, - charset='utf8mb4', - autocommit=True, - cursorclass=pymysql.cursors.DictCursor) - - with connection: - with connection.cursor() as cursor: - cursor.execute("SELECT * FROM hosts_hosts_dependencies") - result = cursor.fetchall() - logger.console(result) - cursor.execute( - f"SELECT count(*) FROM hosts_hosts_dependencies WHERE dependent_host_id={dep_host_id} AND host_id={host_id} AND dependency_period='{dep_period}' AND inherits_parent={inherits_parent} AND notification_failure_options='{notif_fail_opts}' AND execution_failure_options='{exec_fail_opts}'") - result = cursor.fetchall() - logger.console(result) - if len(result) > 0 and int(result[0]['count(*)']) > 0: - return True - time.sleep(2) - return False - - -def ctn_check_no_host_dependencies(timeout=TIMEOUT): - limit = time.time() + timeout - logger.console("SELECT count(*) FROM hosts_hosts_dependencies") - while time.time() < limit: - connection = pymysql.connect(host=DB_HOST, - user=DB_USER, - password=DB_PASS, - database=DB_NAME_STORAGE, - charset='utf8mb4', - autocommit=True, - cursorclass=pymysql.cursors.DictCursor) - - with connection: - with connection.cursor() as cursor: - cursor.execute( - "SELECT count(*) FROM hosts_hosts_dependencies") - result = cursor.fetchall() - if len(result) > 0 and int(result[0]['count(*)']) == 0: - return True - return False - - def ctn_get_collect_version(): f = open("../CMakeLists.txt", "r") lines = f.readlines() From 90fef8a55fd7a4171f905f80c87d81cf6d38ab4e Mon Sep 17 00:00:00 2001 From: David Boucher <dboucher@centreon.com> Date: Wed, 24 Jul 2024 18:54:12 +0200 Subject: [PATCH 927/948] enh(engine/tests): unit tests are migrated to also work with the protobuf configuration REFS: MON-34072 MON-15480 --- engine/tests/CMakeLists.txt | 168 +- engine/tests/cfg_files/conf1/centengine.cfg | 124 + engine/tests/cfg_files/conf1/commands.cfg | 91 + engine/tests/cfg_files/conf1/connectors.cfg | 26 + .../tests/cfg_files/conf1/contactgroups.cfg | 27 + engine/tests/cfg_files/conf1/contacts.cfg | 31 + engine/tests/cfg_files/conf1/dependencies.cfg | 56 + engine/tests/cfg_files/conf1/escalations.cfg | 128 + .../tests/cfg_files/conf1/hostTemplates.cfg | 65 + engine/tests/cfg_files/conf1/hostgroups.cfg | 31 + engine/tests/cfg_files/conf1/hosts.cfg | 160 + .../tests/cfg_files/conf1/meta_commands.cfg | 16 + engine/tests/cfg_files/conf1/meta_host.cfg | 16 + .../tests/cfg_files/conf1/meta_services.cfg | 16 + .../tests/cfg_files/conf1/meta_timeperiod.cfg | 16 + engine/tests/cfg_files/conf1/resource.cfg | 18 + .../cfg_files/conf1/serviceTemplates.cfg | 478 +++ .../tests/cfg_files/conf1/servicegroups.cfg | 23 + engine/tests/cfg_files/conf1/services.cfg | 3282 +++++++++++++++++ engine/tests/cfg_files/conf1/severities.cfg | 32 + engine/tests/cfg_files/conf1/tags.cfg | 34 + engine/tests/cfg_files/conf1/timeperiods.cfg | 42 + engine/tests/checks/anomalydetection.cc | 2 + engine/tests/checks/pb_anomalydetection.cc | 1106 ++++++ engine/tests/checks/pb_service_check.cc | 598 +++ engine/tests/checks/pb_service_retention.cc | 562 +++ .../tests/commands/bin_connector_test_run.cc | 34 +- engine/tests/commands/pbsimple-command.cc | 267 ++ .../tests/configuration/applier-severity.cc | 6 +- .../configuration/applier/applier-command.cc | 2 + .../applier/applier-contactgroup.cc | 2 + .../configuration/applier/applier-log.cc | 1 - .../applier/applier-pbanomalydetection.cc | 257 ++ .../applier/applier-pbcommand.cc | 353 ++ .../applier/applier-pbconnector.cc | 92 + .../applier/applier-pbcontact.cc | 452 +++ .../applier/applier-pbcontactgroup.cc | 298 ++ .../configuration/applier/applier-pbglobal.cc | 58 + .../configuration/applier/applier-pbhost.cc | 160 + .../applier/applier-pbhostdependency.cc | 155 + .../applier/applier-pbhostescalation.cc | 118 + .../applier/applier-pbhostgroup.cc | 190 + .../configuration/applier/applier-pblog.cc | 578 +++ .../applier/applier-pbservice.cc | 838 +++++ .../applier/applier-pbserviceescalation.cc | 182 + .../applier/applier-pbservicegroup.cc | 343 ++ .../configuration/applier/applier-pbstate.cc | 996 +++++ .../configuration/applier/applier-service.cc | 1 - engine/tests/configuration/object.cc | 27 +- engine/tests/configuration/pbcontact.cc | 43 + engine/tests/configuration/pbhost.cc | 33 + engine/tests/configuration/pbservice.cc | 42 + engine/tests/configuration/pbseverity.cc | 104 + engine/tests/configuration/pbtag.cc | 100 + .../tests/configuration/pbtimeperiod-test.cc | 958 +++++ engine/tests/configuration/tag.cc | 1 - engine/tests/custom_vars/pbextcmd.cc | 111 + engine/tests/downtimes/pbdowntime.cc | 111 + engine/tests/downtimes/pbdowntime_finder.cc | 371 ++ engine/tests/enginerpc/pbenginerpc.cc | 1813 +++++++++ .../external_commands/pbanomalydetection.cc | 142 + engine/tests/external_commands/pbhost.cc | 150 + engine/tests/external_commands/pbservice.cc | 227 ++ engine/tests/helper.cc | 33 +- engine/tests/helper.hh | 9 +- engine/tests/loop/loop.cc | 20 +- engine/tests/macros/pbmacro.cc | 1005 +++++ engine/tests/macros/pbmacro_hostname.cc | 1648 +++++++++ engine/tests/macros/pbmacro_service.cc | 2099 +++++++++++ .../host_downtime_notification.cc | 53 + .../host_flapping_notification.cc | 89 +- .../notifications/host_normal_notification.cc | 200 +- .../host_recovery_notification.cc | 18 +- .../service_downtime_notification_test.cc | 21 +- .../service_flapping_notification.cc | 68 +- .../service_normal_notification.cc | 136 +- .../service_timeperiod_notification.cc | 361 +- .../agent_check_result_builder_test.cc | 4 +- .../opentelemetry/open_telemetry_test.cc | 23 + .../tests/opentelemetry/otl_converter_test.cc | 48 +- engine/tests/pb_service_check.cc | 599 +++ engine/tests/test_engine.cc | 368 +- engine/tests/test_engine.hh | 74 + .../get_next_valid_time/calendar_date.cc | 39 +- .../get_next_valid_time/exceptions_test.cc | 51 + engine/tests/timeperiod/utils.cc | 86 +- engine/tests/timeperiod/utils.hh | 36 +- 87 files changed, 23632 insertions(+), 220 deletions(-) mode change 100755 => 100644 engine/tests/CMakeLists.txt create mode 100644 engine/tests/cfg_files/conf1/centengine.cfg create mode 100644 engine/tests/cfg_files/conf1/commands.cfg create mode 100644 engine/tests/cfg_files/conf1/connectors.cfg create mode 100644 engine/tests/cfg_files/conf1/contactgroups.cfg create mode 100644 engine/tests/cfg_files/conf1/contacts.cfg create mode 100644 engine/tests/cfg_files/conf1/dependencies.cfg create mode 100644 engine/tests/cfg_files/conf1/escalations.cfg create mode 100644 engine/tests/cfg_files/conf1/hostTemplates.cfg create mode 100644 engine/tests/cfg_files/conf1/hostgroups.cfg create mode 100644 engine/tests/cfg_files/conf1/hosts.cfg create mode 100644 engine/tests/cfg_files/conf1/meta_commands.cfg create mode 100644 engine/tests/cfg_files/conf1/meta_host.cfg create mode 100644 engine/tests/cfg_files/conf1/meta_services.cfg create mode 100644 engine/tests/cfg_files/conf1/meta_timeperiod.cfg create mode 100644 engine/tests/cfg_files/conf1/resource.cfg create mode 100644 engine/tests/cfg_files/conf1/serviceTemplates.cfg create mode 100644 engine/tests/cfg_files/conf1/servicegroups.cfg create mode 100644 engine/tests/cfg_files/conf1/services.cfg create mode 100644 engine/tests/cfg_files/conf1/severities.cfg create mode 100644 engine/tests/cfg_files/conf1/tags.cfg create mode 100644 engine/tests/cfg_files/conf1/timeperiods.cfg create mode 100644 engine/tests/checks/pb_anomalydetection.cc create mode 100644 engine/tests/checks/pb_service_check.cc create mode 100644 engine/tests/checks/pb_service_retention.cc create mode 100644 engine/tests/commands/pbsimple-command.cc create mode 100644 engine/tests/configuration/applier/applier-pbanomalydetection.cc create mode 100644 engine/tests/configuration/applier/applier-pbcommand.cc create mode 100644 engine/tests/configuration/applier/applier-pbconnector.cc create mode 100644 engine/tests/configuration/applier/applier-pbcontact.cc create mode 100644 engine/tests/configuration/applier/applier-pbcontactgroup.cc create mode 100644 engine/tests/configuration/applier/applier-pbglobal.cc create mode 100644 engine/tests/configuration/applier/applier-pbhost.cc create mode 100644 engine/tests/configuration/applier/applier-pbhostdependency.cc create mode 100644 engine/tests/configuration/applier/applier-pbhostescalation.cc create mode 100644 engine/tests/configuration/applier/applier-pbhostgroup.cc create mode 100644 engine/tests/configuration/applier/applier-pblog.cc create mode 100644 engine/tests/configuration/applier/applier-pbservice.cc create mode 100644 engine/tests/configuration/applier/applier-pbserviceescalation.cc create mode 100644 engine/tests/configuration/applier/applier-pbservicegroup.cc create mode 100644 engine/tests/configuration/applier/applier-pbstate.cc create mode 100644 engine/tests/configuration/pbcontact.cc create mode 100644 engine/tests/configuration/pbhost.cc create mode 100644 engine/tests/configuration/pbservice.cc create mode 100644 engine/tests/configuration/pbseverity.cc create mode 100644 engine/tests/configuration/pbtag.cc create mode 100644 engine/tests/configuration/pbtimeperiod-test.cc create mode 100644 engine/tests/custom_vars/pbextcmd.cc create mode 100644 engine/tests/downtimes/pbdowntime.cc create mode 100644 engine/tests/downtimes/pbdowntime_finder.cc create mode 100644 engine/tests/enginerpc/pbenginerpc.cc create mode 100644 engine/tests/external_commands/pbanomalydetection.cc create mode 100644 engine/tests/external_commands/pbhost.cc create mode 100644 engine/tests/external_commands/pbservice.cc create mode 100644 engine/tests/macros/pbmacro.cc create mode 100644 engine/tests/macros/pbmacro_hostname.cc create mode 100644 engine/tests/macros/pbmacro_service.cc create mode 100644 engine/tests/pb_service_check.cc diff --git a/engine/tests/CMakeLists.txt b/engine/tests/CMakeLists.txt old mode 100755 new mode 100644 index ff06101e3b6..fa77337a042 --- a/engine/tests/CMakeLists.txt +++ b/engine/tests/CMakeLists.txt @@ -23,15 +23,15 @@ if(WITH_TESTING) set(INC_DIR "${MODULE_DIR}/inc/com/centreon/engine/modules/external_commands") set(MODULE_DIR_OTL "${PROJECT_SOURCE_DIR}/modules/opentelemetry") - include_directories(${PROJECT_SOURCE_DIR} ${MODULE_DIR}/inc - ${MODULE_DIR_OTL}/inc - ${CMAKE_SOURCE_DIR}/bbdo - ${CMAKE_SOURCE_DIR}/common/http/inc) + include_directories( + ${PROJECT_SOURCE_DIR} ${MODULE_DIR}/inc ${MODULE_DIR_OTL}/inc + ${CMAKE_SOURCE_DIR}/bbdo ${CMAKE_SOURCE_DIR}/common/http/inc) # Set directory. set(TESTS_DIR "${PROJECT_SOURCE_DIR}/tests") include_directories(${PROJECT_SOURCE_DIR}/enginerpc) - add_definitions(-DENGINERPC_TESTS_PATH="${TESTS_DIR}/enginerpc") + add_definitions("-DENGINERPC_TESTS_PATH=${TESTS_DIR}/enginerpc" + "-DENGINE_CFG_TEST=\"${TESTS_DIR}/cfg_files\"") add_executable(rpc_client_engine ${TESTS_DIR}/enginerpc/client.cc) @@ -56,7 +56,7 @@ if(WITH_TESTING) target_link_libraries(bin_connector_test_run cce_core pthread) target_precompile_headers(bin_connector_test_run REUSE_FROM cce_core) - set(ut_sources + set(ut_sources_legacy # Sources. "${TESTS_DIR}/parse-check-output.cc" "${TESTS_DIR}/checks/service_check.cc" @@ -142,7 +142,7 @@ if(WITH_TESTING) "${TESTS_DIR}/timeperiod/utils.hh") # Unit test executable. - include_directories(${TESTS_DIR}) + include_directories(${TESTS_DIR} ${CMAKE_BINARY_DIR}) if(WITH_ASAN) set(CMAKE_BUILD_TYPE Debug) @@ -154,6 +154,158 @@ if(WITH_TESTING) endif() # utils.cc can't be compiled with precomp headers so it's compiled apart + add_library(ut_engine_utils_legacy STATIC "${TESTS_DIR}/timeperiod/utils.cc") + target_compile_definitions(ut_engine_utils_legacy PRIVATE LEGACY_CONF) + + add_executable(ut_engine_legacy ${ut_sources_legacy}) + target_compile_definitions(ut_engine_legacy PRIVATE LEGACY_CONF) + target_include_directories( + ut_engine_legacy PRIVATE ${MODULE_DIR_OTL}/src + ${CMAKE_SOURCE_DIR}/common/grpc/inc + ${CMAKE_SOURCE_DIR}/agent/inc + ${CMAKE_SOURCE_DIR}/agent/src) + + target_precompile_headers(ut_engine_legacy REUSE_FROM cce_core_legacy) + + set_target_properties( + ut_engine_legacy rpc_client_engine bin_connector_test_run + PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests + RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/tests + RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/tests + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR}/tests + RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ${CMAKE_BINARY_DIR}/tests) + + # file used by timeperiod-test.cc + file(COPY ${TESTS_DIR}/configuration/timeperiods.cfg + DESTINATION ${CMAKE_BINARY_DIR}/tests/) + + add_test(NAME tests_legacy COMMAND ut_engine_legacy) + + if(WITH_COVERAGE) + set(COVERAGE_EXCLUDES + '${PROJECT_BINARY_DIR}/*' '${PROJECT_SOURCE_DIR}/tests/*' + '/usr/include/*' '*/.conan/*') + setup_target_for_coverage(NAME engine-test-coverage EXECUTABLE + ut_engine_legacy DEPENDENCIES ut_engine_legacy) + set(GCOV gcov) + endif() + + target_link_libraries( + ut_engine_legacy + PRIVATE -L${PROTOBUF_LIB_DIR} + enginerpc_legacy + ut_engine_utils_legacy + "-Wl,-whole-archive" + cce_core_legacy + log_v2 + opentelemetry + centagent_lib + "-Wl,-no-whole-archive" + pb_open_telemetry_lib + centreon_grpc + centreon_http + centreon_process + -L${Boost_LIBRARY_DIR_RELEASE} + boost_url + boost_program_options + pthread + ${GCOV} + GTest::gtest + GTest::gtest_main + GTest::gmock + GTest::gmock_main + gRPC::gpr + gRPC::grpc + gRPC::grpc++ + gRPC::grpc++_alts + crypto + ssl + z + fmt::fmt + ryml::ryml + stdc++fs + dl) + + set(ut_sources + # Sources. + ${TESTS_DIR}/parse-check-output.cc + ${TESTS_DIR}/checks/pb_service_check.cc + ${TESTS_DIR}/checks/pb_service_retention.cc + ${TESTS_DIR}/checks/pb_anomalydetection.cc + ${TESTS_DIR}/commands/pbsimple-command.cc + ${TESTS_DIR}/commands/connector.cc + ${TESTS_DIR}/commands/environment.cc + ${TESTS_DIR}/configuration/applier/applier-pbanomalydetection.cc + ${TESTS_DIR}/configuration/applier/applier-pbcommand.cc + ${TESTS_DIR}/configuration/applier/applier-pbconnector.cc + ${TESTS_DIR}/configuration/applier/applier-pbcontact.cc + ${TESTS_DIR}/configuration/applier/applier-pbcontactgroup.cc + ${TESTS_DIR}/configuration/applier/applier-pbglobal.cc + ${TESTS_DIR}/configuration/applier/applier-pblog.cc + ${TESTS_DIR}/configuration/applier/applier-pbhost.cc + ${TESTS_DIR}/configuration/applier/applier-pbhostescalation.cc + ${TESTS_DIR}/configuration/applier/applier-pbhostdependency.cc + ${TESTS_DIR}/configuration/applier/applier-pbhostgroup.cc + ${TESTS_DIR}/configuration/applier/applier-pbservice.cc + ${TESTS_DIR}/configuration/applier/applier-pbserviceescalation.cc + ${TESTS_DIR}/configuration/applier/applier-pbservicegroup.cc + ${TESTS_DIR}/configuration/applier/applier-pbstate.cc + ${TESTS_DIR}/configuration/pbcontact.cc + ${TESTS_DIR}/configuration/pbhost.cc + ${TESTS_DIR}/configuration/pbservice.cc + ${TESTS_DIR}/configuration/pbseverity.cc + ${TESTS_DIR}/configuration/pbtag.cc + ${TESTS_DIR}/configuration/pbtimeperiod-test.cc + ${TESTS_DIR}/configuration/whitelist-test.cc + ${TESTS_DIR}/contacts/contactgroup-config.cc + ${TESTS_DIR}/contacts/simple-contactgroup.cc + ${TESTS_DIR}/custom_vars/pbextcmd.cc + ${TESTS_DIR}/downtimes/pbdowntime.cc + ${TESTS_DIR}/downtimes/pbdowntime_finder.cc + ${TESTS_DIR}/enginerpc/pbenginerpc.cc + ${TESTS_DIR}/helper.cc + ${TESTS_DIR}/macros/pbmacro.cc + ${TESTS_DIR}/macros/pbmacro_hostname.cc + ${TESTS_DIR}/macros/pbmacro_service.cc + ${TESTS_DIR}/external_commands/pbanomalydetection.cc + ${TESTS_DIR}/external_commands/pbhost.cc + ${TESTS_DIR}/external_commands/pbservice.cc + ${TESTS_DIR}/main.cc + ${TESTS_DIR}/loop/loop.cc + ${TESTS_DIR}/notifications/host_downtime_notification.cc + ${TESTS_DIR}/notifications/host_flapping_notification.cc + ${TESTS_DIR}/notifications/host_normal_notification.cc + ${TESTS_DIR}/notifications/host_recovery_notification.cc + ${TESTS_DIR}/notifications/service_normal_notification.cc + ${TESTS_DIR}/notifications/service_timeperiod_notification.cc + ${TESTS_DIR}/notifications/service_flapping_notification.cc + ${TESTS_DIR}/notifications/service_downtime_notification_test.cc + ${TESTS_DIR}/opentelemetry/grpc_config_test.cc + ${TESTS_DIR}/opentelemetry/host_serv_extractor_test.cc + ${TESTS_DIR}/opentelemetry/otl_server_test.cc + ${TESTS_DIR}/opentelemetry/otl_converter_test.cc + ${TESTS_DIR}/opentelemetry/open_telemetry_test.cc + ${TESTS_DIR}/retention/host.cc + ${TESTS_DIR}/retention/service.cc + ${TESTS_DIR}/string/string.cc + ${TESTS_DIR}/test_engine.cc + ${TESTS_DIR}/timeperiod/get_next_valid_time/between_two_years.cc + ${TESTS_DIR}/timeperiod/get_next_valid_time/calendar_date.cc + ${TESTS_DIR}/timeperiod/get_next_valid_time/dst_backward.cc + ${TESTS_DIR}/timeperiod/get_next_valid_time/dst_forward.cc + ${TESTS_DIR}/timeperiod/get_next_valid_time/earliest_daterange_first.cc + ${TESTS_DIR}/timeperiod/get_next_valid_time/exclusion.cc + ${TESTS_DIR}/timeperiod/get_next_valid_time/exceptions_test.cc + ${TESTS_DIR}/timeperiod/get_next_valid_time/generic_month_date.cc + ${TESTS_DIR}/timeperiod/get_next_valid_time/normal_weekday.cc + ${TESTS_DIR}/timeperiod/get_next_valid_time/offset_weekday_of_generic_month.cc + ${TESTS_DIR}/timeperiod/get_next_valid_time/offset_weekday_of_specific_month.cc + ${TESTS_DIR}/timeperiod/get_next_valid_time/precedence.cc + ${TESTS_DIR}/timeperiod/get_next_valid_time/skip_interval.cc + ${TESTS_DIR}/timeperiod/get_next_valid_time/specific_month_date.cc + # Headers. + "${TESTS_DIR}/test_engine.hh" + "${TESTS_DIR}/timeperiod/utils.hh") add_library(ut_engine_utils STATIC "${TESTS_DIR}/timeperiod/utils.cc") add_executable(ut_engine ${ut_sources}) @@ -191,7 +343,7 @@ if(WITH_TESTING) target_link_libraries( ut_engine PRIVATE -L${PROTOBUF_LIB_DIR} - ${ENGINERPC} + enginerpc ut_engine_utils "-Wl,-whole-archive" cce_core diff --git a/engine/tests/cfg_files/conf1/centengine.cfg b/engine/tests/cfg_files/conf1/centengine.cfg new file mode 100644 index 00000000000..ec539359bf7 --- /dev/null +++ b/engine/tests/cfg_files/conf1/centengine.cfg @@ -0,0 +1,124 @@ +################################################################### +# # +# GENERATED BY CENTREON # +# # +# Developed by : # +# - Julien Mathis # +# - Romain Le Merlus # +# # +# www.centreon.com # +# For information : contact@centreon.com # +################################################################### +# # +# Last modification 2023-05-31 18:43 # +# By John_Doe # +# # +################################################################### +cfg_file=/tmp/hostTemplates.cfg +cfg_file=/tmp/hosts.cfg +cfg_file=/tmp/serviceTemplates.cfg +cfg_file=/tmp/services.cfg +cfg_file=/tmp/commands.cfg +cfg_file=/tmp/contactgroups.cfg +cfg_file=/tmp/contacts.cfg +cfg_file=/tmp/hostgroups.cfg +cfg_file=/tmp/servicegroups.cfg +cfg_file=/tmp/timeperiods.cfg +cfg_file=/tmp/escalations.cfg +cfg_file=/tmp/dependencies.cfg +cfg_file=/tmp/connectors.cfg +cfg_file=/tmp/meta_commands.cfg +cfg_file=/tmp/meta_timeperiod.cfg +cfg_file=/tmp/meta_host.cfg +cfg_file=/tmp/meta_services.cfg +cfg_file=/tmp/tags.cfg +cfg_file=/tmp/severities.cfg +broker_module=/usr/lib64/centreon-engine/externalcmd.so +broker_module=/usr/lib64/nagios/cbmod.so /etc/centreon-broker/central-module.json +interval_length=60 +use_timezone=:America/New_York +resource_file=/tmp/resource.cfg +log_file=/tmp/centengine.log +status_file=/tmp/status.dat +status_update_interval=60 +external_command_buffer_slots=4096 +command_check_interval=1s +command_file=/tmp/centengine.cmd +state_retention_file=/tmp/retention.dat +retention_update_interval=60 +service_inter_check_delay_method=s +host_inter_check_delay_method=s +service_interleave_factor=s +max_concurrent_checks=0 +max_service_check_spread=15 +max_host_check_spread=15 +check_result_reaper_frequency=5 +low_service_flap_threshold=25.0 +high_service_flap_threshold=50.0 +low_host_flap_threshold=25.0 +high_host_flap_threshold=50.0 +service_check_timeout=60 +host_check_timeout=12 +event_handler_timeout=30 +notification_timeout=30 +date_format=euro +illegal_object_name_chars=~!$%^&*"|'<>?,()= +illegal_macro_output_chars=`~$^&"|'<> +admin_email=admin@localhost +admin_pager=admin@localhost +event_broker_options=-1 +cached_host_check_horizon=15 +cached_service_check_horizon=15 +additional_freshness_latency=15 +debug_file=/var/log/centreon-engine/centengine.debug +debug_level=0 +debug_verbosity=1 +max_debug_file_size=1000000000 +log_pid=1 +enable_macros_filter=0 +grpc_port=50001 +log_v2_enabled=1 +log_legacy_enabled=0 +log_v2_logger=file +log_level_functions=warning +log_level_config=info +log_level_events=info +log_level_checks=info +log_level_notifications=info +log_level_eventbroker=warning +log_level_external_command=info +log_level_commands=warning +log_level_downtimes=info +log_level_comments=info +log_level_macros=warning +log_level_process=info +log_level_runtime=warning +instance_heartbeat_interval=30 +enable_notifications=1 +execute_service_checks=1 +accept_passive_service_checks=1 +execute_host_checks=1 +accept_passive_host_checks=1 +enable_event_handlers=1 +check_external_commands=1 +use_retained_program_state=1 +use_retained_scheduling_info=1 +use_syslog=0 +log_notifications=1 +log_service_retries=1 +log_host_retries=1 +log_event_handlers=1 +log_external_commands=1 +log_passive_checks=1 +auto_reschedule_checks=0 +soft_state_dependencies=0 +check_for_orphaned_services=1 +check_for_orphaned_hosts=1 +check_service_freshness=0 +check_host_freshness=0 +enable_flap_detection=0 +use_regexp_matching=0 +use_true_regexp_matching=0 +enable_predictive_host_dependency_checks=1 +enable_predictive_service_dependency_checks=1 +enable_environment_macros=0 diff --git a/engine/tests/cfg_files/conf1/commands.cfg b/engine/tests/cfg_files/conf1/commands.cfg new file mode 100644 index 00000000000..ebba74aba37 --- /dev/null +++ b/engine/tests/cfg_files/conf1/commands.cfg @@ -0,0 +1,91 @@ +################################################################### +# # +# GENERATED BY CENTREON # +# # +# Developed by : # +# - Julien Mathis # +# - Romain Le Merlus # +# # +# www.centreon.com # +# For information : contact@centreon.com # +################################################################### +# # +# Last modification 2023-05-31 18:43 # +# By John_Doe # +# # +################################################################### + +define command { + command_name base_host_alive + command_line $USER1$/check_icmp -H $HOSTADDRESS$ -w 3000.0,80% -c 5000.0,100% -p 1 +} + +define command { + command_name host-notify-by-email + command_line /bin/sh -c '/usr/bin/printf "%b" "***** centreon Notification *****\n\nType:$NOTIFICATIONTYPE$\nHost: $HOSTNAME$\nState: $HOSTSTATE$\nAddress: $HOSTADDRESS$\nInfo: $HOSTOUTPUT$\nDate/Time: $DATE$" | /bin/mail -s "Host $HOSTSTATE$ alert for $HOSTNAME$!" $CONTACTEMAIL$' +} + +define command { + command_name OS-Linux-SNMP-Process-Generic + command_line $CENTREONPLUGINS$/centreon_linux_snmp.pl --plugin=os::linux::snmp::plugin --mode=processcount --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' $_HOSTSNMPEXTRAOPTIONS$ --process-name='$_SERVICEPROCESSNAME$' --process-path='$_SERVICEPROCESSPATH$' --process-args='$_SERVICEPROCESSARGS$' --regexp-name --regexp-path --regexp-args --warning='$_SERVICEWARNING$' --critical='$_SERVICECRITICAL$' $_SERVICEEXTRAOPTIONS$ +} + +define command { + command_name base_centreon_ping + command_line $USER1$/check_icmp -H $HOSTADDRESS$ -n $_SERVICEPACKETNUMBER$ -w $_SERVICEWARNING$ -c $_SERVICECRITICAL$ $_SERVICEEXTRAOPTIONS$ +} + +define command { + command_name OS-Linux-SNMP-Swap + command_line $CENTREONPLUGINS$/centreon_linux_snmp.pl --plugin=os::linux::snmp::plugin --mode=swap --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' $_HOSTSNMPEXTRAOPTIONS$ --warning-usage-prct='$_SERVICEWARNING$' --critical-usage-prct='$_SERVICECRITICAL$' $_SERVICEEXTRAOPTIONS$ +} + +define command { + command_name OS-Linux-SNMP-Memory + command_line $CENTREONPLUGINS$/centreon_linux_snmp.pl --plugin=os::linux::snmp::plugin --mode=memory --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' $_HOSTSNMPEXTRAOPTIONS$ --warning-usage='$_SERVICEWARNING$' --critical-usage='$_SERVICECRITICAL$' $_SERVICEEXTRAOPTIONS$ +} + +define command { + command_name OS-Linux-SNMP-Load + command_line $CENTREONPLUGINS$/centreon_linux_snmp.pl --plugin=os::linux::snmp::plugin --mode=load --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' $_HOSTSNMPEXTRAOPTIONS$ --warning='$_SERVICEWARNING$' --critical='$_SERVICECRITICAL$' $_SERVICEEXTRAOPTIONS$ +} + +define command { + command_name OS-Linux-SNMP-Cpu + command_line $CENTREONPLUGINS$/centreon_linux_snmp.pl --plugin=os::linux::snmp::plugin --mode=cpu --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' $_HOSTSNMPEXTRAOPTIONS$ --warning-average='$_SERVICEWARNING$' --critical-average='$_SERVICECRITICAL$' $_SERVICEEXTRAOPTIONS$ +} + +define command { + command_name App-Centreon-MySQL-Partitioning + command_line $CENTREONPLUGINS$/centreon_centreon_database.pl --plugin=database::mysql::plugin --dyn-mode=apps::centreon::sql::mode::partitioning --host='$HOSTADDRESS$' --username='$_HOSTMYSQLUSERNAME$' --password='$_HOSTMYSQLPASSWORD$' --port='$_HOSTMYSQLPORT$' --tablename='$_SERVICETABLENAME1$' --tablename='$_SERVICETABLENAME2$' --tablename='$_SERVICETABLENAME3$' --tablename='$_SERVICETABLENAME4$' --warning='$_SERVICEWARNING$' --critical='$_SERVICECRITICAL$' +} + +define command { + command_name App-DB-MySQL + command_line $CENTREONPLUGINS$/centreon_mysql.pl --plugin=database::mysql::plugin --host=$HOSTADDRESS$ --username='$_HOSTMYSQLUSERNAME$' --password='$_HOSTMYSQLPASSWORD$' --port='$_HOSTMYSQLPORT$' --mode='$_SERVICEMODE$' --warning='$_SERVICEWARNING$' --critical='$_SERVICECRITICAL$' $_SERVICEEXTRAOPTIONS$ +} + +define command { + command_name App-DB-MySQL-Queries + command_line $CENTREONPLUGINS$/centreon_mysql.pl --plugin=database::mysql::plugin --host=$HOSTADDRESS$ --username='$_HOSTMYSQLUSERNAME$' --password='$_HOSTMYSQLPASSWORD$' --port='$_HOSTMYSQLPORT$' --mode=queries --warning-total='$_SERVICEWARNING$' --critical-total='$_SERVICECRITICAL$' $_SERVICEEXTRAOPTIONS$ +} + +define command { + command_name App-DB-MySQL-Database-Size + command_line $CENTREONPLUGINS$/centreon_mysql.pl --plugin=database::mysql::plugin --host=$HOSTADDRESS$ --username='$_HOSTMYSQLUSERNAME$' --password='$_HOSTMYSQLPASSWORD$' --port='$_HOSTMYSQLPORT$' --mode=databases-size --filter-database='$_SERVICEFILTERDATABASE$' --filter-perfdata='$_SERVICEFILTERPERFDATA$' --warning-table-usage='$_SERVICEWARNINGTABLEUSAGE$' --critical-table-usage='$_SERVICECRITICALTABLEUSAGE$' --warning-table-free='$_SERVICEWARNINGTABLEFREE$' --critical-table-free='$_SERVICECRITICALTABLEFREE$' --warning-table-frag='$_SERVICEWARNINGTABLEFRAG$' --critical-table-frag='$_SERVICECRITICALTABLEFRAG$' --warning-db-usage='$_SERVICEWARNINGDBUSAGE$' --critical-db-usage='$_SERVICECRITICALDBUSAGE$' --warning-db-free='$_SERVICEWARNINGDBFREE$' --critical-db-free='$_SERVICECRITICALDBFREE$' --warning-total-usage='$_SERVICEWARNINGTOTALUSAGE$' --critical-total-usage='$_SERVICECRITICALTOTALUSAGE$' --warning-total-free='$_SERVICEWARNINGTOTALFREE$' --critical-total-free='$_SERVICECRITICALTOTALFREE$' $_SERVICEEXTRAOPTIONS$ +} + +define command { + command_name App-DB-MySQL-Threads-Connected + command_line $CENTREONPLUGINS$/centreon_mysql.pl --plugin=database::mysql::plugin --host=$HOSTADDRESS$ --username='$_HOSTMYSQLUSERNAME$' --password='$_HOSTMYSQLPASSWORD$' --port='$_HOSTMYSQLPORT$' --mode=threads-connected --warning-usage='$_SERVICEWARNING$' --critical-usage='$_SERVICECRITICAL$' --warning-usage-prct='$_SERVICEWARNINGUSAGEPRCT$' --critical-usage-prct='$_SERVICECRITICALUSAGEPRCT$' $_SERVICEEXTRAOPTIONS$ +} + +define command { + command_name OS-Linux-SNMP-Disk-Name + command_line $CENTREONPLUGINS$/centreon_linux_snmp.pl --plugin=os::linux::snmp::plugin --mode=storage --hostname=$HOSTADDRESS$ --snmp-version='$_HOSTSNMPVERSION$' --snmp-community='$_HOSTSNMPCOMMUNITY$' $_HOSTSNMPEXTRAOPTIONS$ --storage '$_SERVICEDISKNAME$' --name --display-transform-src='$_SERVICETRANSFORMSRC$' --display-transform-dst='$_SERVICETRANSFORMDST$' --warning-usage='$_SERVICEWARNING$' --critical-usage='$_SERVICECRITICAL$' $_SERVICEEXTRAOPTIONS$ +} + +define command { + command_name App-Monitoring-Centreon-Central-Broker-Stats + command_line $CENTREONPLUGINS$/centreon_centreon_central.pl --plugin=apps::centreon::local::plugin --hostname=$HOSTADDRESS$ --mode=broker-stats --broker-stats-file='$_HOSTSQLSTATSFILE$' --broker-stats-file='$_HOSTRRDSTATSFILE$' --broker-stats-file='$_HOSTMODULESTATSFILE$' --filter-name='$_SERVICEFILTERNAME$' --warning-speed-events='$_SERVICEWARNINGSPEEDEVENTS$' --critical-speed-events='$_SERVICECRITICALSPEEDEVENTS$' --warning-queued-events='$_SERVICEWARNINGQUEUEDEVENTS$' --critical-queued-events='$_SERVICECRITICALQUEUEDEVENTS$' --warning-unacknowledged-events='$_SERVICEWARNINGUNACKNOWLEDGEDEVENTS$' --critical-unacknowledged-events='$_SERVICECRITICALUNACKNOWLEDGEDEVENTS$' --warning-status='$_SERVICEWARNINGSTATUS$' --critical-status='$_SERVICECRITICALSTATUS$' $_SERVICEEXTRAOPTIONS$ +} diff --git a/engine/tests/cfg_files/conf1/connectors.cfg b/engine/tests/cfg_files/conf1/connectors.cfg new file mode 100644 index 00000000000..f49cb9d4b58 --- /dev/null +++ b/engine/tests/cfg_files/conf1/connectors.cfg @@ -0,0 +1,26 @@ +################################################################### +# # +# GENERATED BY CENTREON # +# # +# Developed by : # +# - Julien Mathis # +# - Romain Le Merlus # +# # +# www.centreon.com # +# For information : contact@centreon.com # +################################################################### +# # +# Last modification 2023-05-31 18:43 # +# By John_Doe # +# # +################################################################### + +define connector { + connector_name Perl Connector + connector_line /usr/lib64/centreon-connector/centreon_connector_perl --log-file=/var/log/centreon-engine/connector-perl.log +} + +define connector { + connector_name SSH Connector + connector_line /usr/lib64/centreon-connector/centreon_connector_ssh --log-file=/var/log/centreon-engine/connector-ssh.log +} diff --git a/engine/tests/cfg_files/conf1/contactgroups.cfg b/engine/tests/cfg_files/conf1/contactgroups.cfg new file mode 100644 index 00000000000..fd3dce0e64e --- /dev/null +++ b/engine/tests/cfg_files/conf1/contactgroups.cfg @@ -0,0 +1,27 @@ +################################################################### +# # +# GENERATED BY CENTREON # +# # +# Developed by : # +# - Julien Mathis # +# - Romain Le Merlus # +# # +# www.centreon.com # +# For information : contact@centreon.com # +################################################################### +# # +# Last modification 2023-05-31 18:43 # +# By John_Doe # +# # +################################################################### + +define contactgroup { + contactgroup_name Guest + alias Guests Group +} + +define contactgroup { + contactgroup_name Supervisors + alias Centreon supervisors + members John_Doe +} diff --git a/engine/tests/cfg_files/conf1/contacts.cfg b/engine/tests/cfg_files/conf1/contacts.cfg new file mode 100644 index 00000000000..2c592c985de --- /dev/null +++ b/engine/tests/cfg_files/conf1/contacts.cfg @@ -0,0 +1,31 @@ +################################################################### +# # +# GENERATED BY CENTREON # +# # +# Developed by : # +# - Julien Mathis # +# - Romain Le Merlus # +# # +# www.centreon.com # +# For information : contact@centreon.com # +################################################################### +# # +# Last modification 2023-05-31 18:43 # +# By John_Doe # +# # +################################################################### + +define contact { + contact_name John_Doe + alias admin + email admin@admin.tld + host_notification_period 24x7 + service_notification_period 24x7 + host_notification_options d,u,r + service_notification_options w,u,c + register 1 + host_notifications_enabled 1 + service_notifications_enabled 1 + host_notification_commands host-notify-by-email + service_notification_commands host-notify-by-email +} diff --git a/engine/tests/cfg_files/conf1/dependencies.cfg b/engine/tests/cfg_files/conf1/dependencies.cfg new file mode 100644 index 00000000000..d130bf9a6ba --- /dev/null +++ b/engine/tests/cfg_files/conf1/dependencies.cfg @@ -0,0 +1,56 @@ +################################################################### +# # +# GENERATED BY CENTREON # +# # +# Developed by : # +# - Julien Mathis # +# - Romain Le Merlus # +# # +# www.centreon.com # +# For information : contact@centreon.com # +################################################################### +# # +# Last modification 2023-05-31 18:43 # +# By John_Doe # +# # +################################################################### + +define hostdependency { + ;dependency_name hostdep1 + execution_failure_criteria o,d + notification_failure_criteria u,p + inherits_parent 1 + dependent_host_name Centreon-central_2,Centreon-central_3 + host_name Centreon-central,Centreon-central_1 +} + +define servicedependency { + ;dependency_name servicedep1 + execution_failure_criteria o,u + notification_failure_criteria w,c + inherits_parent 1 + dependent_host_name Centreon-central + host_name Centreon-central + dependent_service_description Connections-Number + service_description Cpu +} + +define servicedependency { + ;dependency_name servicedep1 + execution_failure_criteria o,u + notification_failure_criteria w,c + inherits_parent 1 + dependent_host_name Centreon-central + host_name Centreon-central + dependent_service_description Connections-Number + service_description Connection-Time +} + +define hostdependency { + ;dependency_name hostdep2 + execution_failure_criteria o,d + notification_failure_criteria d,u + inherits_parent 1 + dependent_hostgroup_name Centreon_platform + hostgroup_name hg1 +} diff --git a/engine/tests/cfg_files/conf1/escalations.cfg b/engine/tests/cfg_files/conf1/escalations.cfg new file mode 100644 index 00000000000..bcca495bbb2 --- /dev/null +++ b/engine/tests/cfg_files/conf1/escalations.cfg @@ -0,0 +1,128 @@ +################################################################### +# # +# GENERATED BY CENTREON # +# # +# Developed by : # +# - Julien Mathis # +# - Romain Le Merlus # +# # +# www.centreon.com # +# For information : contact@centreon.com # +################################################################### +# # +# Last modification 2023-05-31 18:43 # +# By John_Doe # +# # +################################################################### + +define hostescalation { + ;escalation_name hostescalation + first_notification 5 + last_notification 10 + notification_interval 5 + escalation_period nonworkhours + escalation_options d,u + hostgroup_name hg1 + contact_groups Supervisors +} + +define hostescalation { + ;escalation_name mixedescalation + first_notification 2 + last_notification 10 + notification_interval 5 + escalation_period nonworkhours + escalation_options d,u,r + hostgroup_name hg1 + contact_groups Supervisors +} + +define hostescalation { + ;escalation_name hostescalation + first_notification 5 + last_notification 10 + notification_interval 5 + escalation_period nonworkhours + escalation_options d,u + host_name Centreon-central,Centreon-central_1 + contact_groups Supervisors +} + +define hostescalation { + ;escalation_name mixedescalation + first_notification 2 + last_notification 10 + notification_interval 5 + escalation_period nonworkhours + escalation_options d,u,r + host_name Centreon-central_3,Centreon-central_10 + contact_groups Supervisors +} + +define serviceescalation { + ;escalation_name hostescalation + first_notification 5 + last_notification 10 + notification_interval 5 + escalation_period nonworkhours + host_name Centreon-central + service_description Cpu + contact_groups Supervisors +} + +define serviceescalation { + ;escalation_name hostescalation + first_notification 5 + last_notification 10 + notification_interval 5 + escalation_period nonworkhours + host_name Centreon-central + service_description Database-Size + contact_groups Supervisors +} + +define serviceescalation { + ;escalation_name mixedescalation + first_notification 2 + last_notification 10 + notification_interval 5 + escalation_period nonworkhours + escalation_options w,u,c + host_name Centreon-central + service_description Cpu + contact_groups Supervisors +} + +define serviceescalation { + ;escalation_name mixedescalation + first_notification 2 + last_notification 10 + notification_interval 5 + escalation_period nonworkhours + escalation_options w,u,c + host_name Centreon-central + service_description Connection-Time + contact_groups Supervisors +} + +define serviceescalation { + ;escalation_name serviceescalation + first_notification 4 + last_notification 10 + notification_interval 5 + escalation_period nonworkhours + escalation_options w,u,c + servicegroup_name Database-MySQL + contact_groups Supervisors +} + +define serviceescalation { + ;escalation_name mixedescalation + first_notification 2 + last_notification 10 + notification_interval 5 + escalation_period nonworkhours + escalation_options w,u,c + servicegroup_name Database-MySQL + contact_groups Supervisors +} diff --git a/engine/tests/cfg_files/conf1/hostTemplates.cfg b/engine/tests/cfg_files/conf1/hostTemplates.cfg new file mode 100644 index 00000000000..9b168e8fd6d --- /dev/null +++ b/engine/tests/cfg_files/conf1/hostTemplates.cfg @@ -0,0 +1,65 @@ +################################################################### +# # +# GENERATED BY CENTREON # +# # +# Developed by : # +# - Julien Mathis # +# - Romain Le Merlus # +# # +# www.centreon.com # +# For information : contact@centreon.com # +################################################################### +# # +# Last modification 2023-05-31 18:43 # +# By John_Doe # +# # +################################################################### + +define host { + name generic-active-host + alias generic-active-host + check_command base_host_alive + check_period 24x7 + max_check_attempts 3 + check_interval 5 + register 0 + active_checks_enabled 1 + passive_checks_enabled 0 + _SNMPCOMMUNITY public + _SNMPVERSION 2c +} + +define host { + name generic-active-host-custom + alias generic-active-host + register 0 + use generic-active-host +} + +define host { + name OS-Linux-SNMP + alias Template to check Linux server using SNMP protocol + register 0 + icon_image ppm/operatingsystems-linux-snmp-linux-128.png + icon_id 1 + use generic-active-host-custom +} + +define host { + name OS-Linux-SNMP-custom + alias Template to check Linux server using SNMP protocol + register 0 + use OS-Linux-SNMP +} + +define host { + name App-Monitoring-Centreon-Central + alias Template to check Centreon Central Server + register 0 + icon_image ppm/applications-monitoring-centreon-central-centreon-128-2.png + icon_id 2 + use generic-active-host-custom,OS-Linux-SNMP-custom + _SQLSTATSFILE /var/lib/centreon-broker/central-broker-master-stats.json + _RRDSTATSFILE /var/lib/centreon-broker/central-rrd-master-stats.json + _MODULESTATSFILE /var/lib/centreon-engine/central-module-master-stats.json +} diff --git a/engine/tests/cfg_files/conf1/hostgroups.cfg b/engine/tests/cfg_files/conf1/hostgroups.cfg new file mode 100644 index 00000000000..255272f2438 --- /dev/null +++ b/engine/tests/cfg_files/conf1/hostgroups.cfg @@ -0,0 +1,31 @@ +################################################################### +# # +# GENERATED BY CENTREON # +# # +# Developed by : # +# - Julien Mathis # +# - Romain Le Merlus # +# # +# www.centreon.com # +# For information : contact@centreon.com # +################################################################### +# # +# Last modification 2023-05-31 18:43 # +# By John_Doe # +# # +################################################################### + +define hostgroup { + hostgroup_id 3 + hostgroup_name hg1 + alias hg1 + notes note_hg1 + members Centreon-central_2,Centreon-central_3,Centreon-central_4 +} + +define hostgroup { + hostgroup_id 2 + hostgroup_name Centreon_platform + alias Centreon_platform + members Centreon-central_5,Centreon-central_6,Centreon-central_7,Centreon-central_8,Centreon-central_9 +} diff --git a/engine/tests/cfg_files/conf1/hosts.cfg b/engine/tests/cfg_files/conf1/hosts.cfg new file mode 100644 index 00000000000..bbcc7199b79 --- /dev/null +++ b/engine/tests/cfg_files/conf1/hosts.cfg @@ -0,0 +1,160 @@ +################################################################### +# # +# GENERATED BY CENTREON # +# # +# Developed by : # +# - Julien Mathis # +# - Romain Le Merlus # +# # +# www.centreon.com # +# For information : contact@centreon.com # +################################################################### +# # +# Last modification 2023-05-31 18:43 # +# By John_Doe # +# # +################################################################### + +define host { + host_name Centreon-central + alias Centreon-central + address localhost + contacts John_Doe + contact_groups Guest,Supervisors + register 1 + use App-Monitoring-Centreon-Central + _MYSQLPASSWORD centreon + _SNMPCOMMUNITY public + _SNMPVERSION 2c + _HOST_ID 30 +} + +define host { + host_name Centreon-central_1 + alias Centreon-central + address 127.0.0.2 + register 1 + use App-Monitoring-Centreon-Central + _MYSQLPASSWORD centreon + _SNMPCOMMUNITY public + _SNMPVERSION 2c + _HOST_ID 31 +} + +define host { + host_name Centreon-central_2 + alias Centreon-central + address 127.0.0.4 + register 1 + use App-Monitoring-Centreon-Central + group_tags 3 + _MYSQLPASSWORD centreon + _SNMPCOMMUNITY public + _SNMPVERSION 2c + _HOST_ID 32 +} + +define host { + host_name Centreon-central_3 + alias Centreon-central + address 127.0.0.5 + register 1 + use App-Monitoring-Centreon-Central + group_tags 3 + _MYSQLPASSWORD centreon + _SNMPCOMMUNITY public + _SNMPVERSION 2c + _HOST_ID 33 +} + +define host { + host_name Centreon-central_4 + alias Centreon-central + address localhost + register 1 + use App-Monitoring-Centreon-Central + group_tags 3 + _SNMPEXTRAOPTIONS + _MYSQLPASSWORD centreon + _SNMPCOMMUNITY public + _SNMPVERSION 2c + _HOST_ID 34 +} + +define host { + host_name Centreon-central_5 + alias Centreon-central + address localhost + register 1 + use App-Monitoring-Centreon-Central + group_tags 2 + _SNMPEXTRAOPTIONS + _MYSQLPASSWORD centreon + _SNMPCOMMUNITY public + _SNMPVERSION 2c + _HOST_ID 35 +} + +define host { + host_name Centreon-central_6 + alias Centreon-central + address 127.0.0.6 + register 1 + use App-Monitoring-Centreon-Central + group_tags 2 + _MYSQLPASSWORD centreon + _SNMPCOMMUNITY public + _SNMPVERSION 2c + _HOST_ID 36 +} + +define host { + host_name Centreon-central_7 + alias Centreon-central + address 127.0.0.8 + register 1 + use App-Monitoring-Centreon-Central + group_tags 2 + _MYSQLPASSWORD centreon + _SNMPCOMMUNITY public + _SNMPVERSION 2c + _HOST_ID 37 +} + +define host { + host_name Centreon-central_8 + alias Centreon-central + address 127.0.0.8 + register 1 + use App-Monitoring-Centreon-Central + group_tags 2 + _MYSQLPASSWORD centreon + _SNMPCOMMUNITY public + _SNMPVERSION 2c + _HOST_ID 38 +} + +define host { + host_name Centreon-central_9 + alias Centreon-central + address 127.0.0.9 + register 1 + use App-Monitoring-Centreon-Central + group_tags 2 + _MYSQLPASSWORD centreon + _SNMPCOMMUNITY public + _SNMPVERSION 2c + _HOST_ID 39 +} + +define host { + host_name Centreon-central_10 + alias Centreon-central + address 127.0.0.3 + register 1 + use App-Monitoring-Centreon-Central + _MYSQLPASSWORD centreon + _SNMPCOMMUNITY public + _SNMPVERSION 2c + _HOST_ID 40 +} diff --git a/engine/tests/cfg_files/conf1/meta_commands.cfg b/engine/tests/cfg_files/conf1/meta_commands.cfg new file mode 100644 index 00000000000..6d764fbcabc --- /dev/null +++ b/engine/tests/cfg_files/conf1/meta_commands.cfg @@ -0,0 +1,16 @@ +################################################################### +# # +# GENERATED BY CENTREON # +# # +# Developed by : # +# - Julien Mathis # +# - Romain Le Merlus # +# # +# www.centreon.com # +# For information : contact@centreon.com # +################################################################### +# # +# Last modification 2023-05-31 18:43 # +# By John_Doe # +# # +################################################################### diff --git a/engine/tests/cfg_files/conf1/meta_host.cfg b/engine/tests/cfg_files/conf1/meta_host.cfg new file mode 100644 index 00000000000..6d764fbcabc --- /dev/null +++ b/engine/tests/cfg_files/conf1/meta_host.cfg @@ -0,0 +1,16 @@ +################################################################### +# # +# GENERATED BY CENTREON # +# # +# Developed by : # +# - Julien Mathis # +# - Romain Le Merlus # +# # +# www.centreon.com # +# For information : contact@centreon.com # +################################################################### +# # +# Last modification 2023-05-31 18:43 # +# By John_Doe # +# # +################################################################### diff --git a/engine/tests/cfg_files/conf1/meta_services.cfg b/engine/tests/cfg_files/conf1/meta_services.cfg new file mode 100644 index 00000000000..6d764fbcabc --- /dev/null +++ b/engine/tests/cfg_files/conf1/meta_services.cfg @@ -0,0 +1,16 @@ +################################################################### +# # +# GENERATED BY CENTREON # +# # +# Developed by : # +# - Julien Mathis # +# - Romain Le Merlus # +# # +# www.centreon.com # +# For information : contact@centreon.com # +################################################################### +# # +# Last modification 2023-05-31 18:43 # +# By John_Doe # +# # +################################################################### diff --git a/engine/tests/cfg_files/conf1/meta_timeperiod.cfg b/engine/tests/cfg_files/conf1/meta_timeperiod.cfg new file mode 100644 index 00000000000..6d764fbcabc --- /dev/null +++ b/engine/tests/cfg_files/conf1/meta_timeperiod.cfg @@ -0,0 +1,16 @@ +################################################################### +# # +# GENERATED BY CENTREON # +# # +# Developed by : # +# - Julien Mathis # +# - Romain Le Merlus # +# # +# www.centreon.com # +# For information : contact@centreon.com # +################################################################### +# # +# Last modification 2023-05-31 18:43 # +# By John_Doe # +# # +################################################################### diff --git a/engine/tests/cfg_files/conf1/resource.cfg b/engine/tests/cfg_files/conf1/resource.cfg new file mode 100644 index 00000000000..0e5cc4a1825 --- /dev/null +++ b/engine/tests/cfg_files/conf1/resource.cfg @@ -0,0 +1,18 @@ +################################################################### +# # +# GENERATED BY CENTREON # +# # +# Developed by : # +# - Julien Mathis # +# - Romain Le Merlus # +# # +# www.centreon.com # +# For information : contact@centreon.com # +################################################################### +# # +# Last modification 2023-05-31 18:43 # +# By John_Doe # +# # +################################################################### +$USER1$=/usr/lib64/nagios/plugins +$CENTREONPLUGINS$=/usr/lib/centreon/plugins/ diff --git a/engine/tests/cfg_files/conf1/serviceTemplates.cfg b/engine/tests/cfg_files/conf1/serviceTemplates.cfg new file mode 100644 index 00000000000..536ea94421a --- /dev/null +++ b/engine/tests/cfg_files/conf1/serviceTemplates.cfg @@ -0,0 +1,478 @@ +################################################################### +# # +# GENERATED BY CENTREON # +# # +# Developed by : # +# - Julien Mathis # +# - Romain Le Merlus # +# # +# www.centreon.com # +# For information : contact@centreon.com # +################################################################### +# # +# Last modification 2023-05-31 18:43 # +# By John_Doe # +# # +################################################################### + +define service { + service_description generic-active-service + name generic-active-service + check_period 24x7 + max_check_attempts 3 + check_interval 5 + retry_interval 1 + register 0 + is_volatile 0 + active_checks_enabled 1 + passive_checks_enabled 0 +} + +define service { + service_description generic-active-service + name generic-active-service-custom + register 0 + use generic-active-service +} + +define service { + service_description Process-Generic + name OS-Linux-Process-Generic-SNMP + check_command OS-Linux-SNMP-Process-Generic + max_check_attempts 3 + check_interval 5 + retry_interval 1 + register 0 + use generic-active-service-custom + _CRITICAL 1: + _EXTRAOPTIONS + _PROCESSARGS + _PROCESSNAME + _PROCESSPATH + _WARNING +} + +define service { + service_description Process-Generic + name OS-Linux-Process-Generic-SNMP-custom + register 0 + use OS-Linux-Process-Generic-SNMP +} + +define service { + service_description proc-sshd + name App-Monitoring-Centreon-Process-sshd + register 0 + use OS-Linux-Process-Generic-SNMP-custom + _PROCESSNAME sshd +} + +define service { + service_description proc-sshd + name App-Monitoring-Centreon-Process-sshd-custom + register 0 + use App-Monitoring-Centreon-Process-sshd +} + +define service { + service_description proc-httpd + name App-Monitoring-Centreon-Process-httpd + register 0 + use OS-Linux-Process-Generic-SNMP-custom + _PROCESSNAME httpd|apache2 +} + +define service { + service_description proc-httpd + name App-Monitoring-Centreon-Process-httpd-custom + register 0 + use App-Monitoring-Centreon-Process-httpd +} + +define service { + service_description proc-crond + name App-Monitoring-Centreon-Process-crond + register 0 + use OS-Linux-Process-Generic-SNMP-custom + _PROCESSNAME crond|cron +} + +define service { + service_description proc-crond + name App-Monitoring-Centreon-Process-crond-custom + register 0 + use App-Monitoring-Centreon-Process-crond +} + +define service { + service_description proc-centengine + name App-Monitoring-Centreon-Process-centengine + register 0 + use OS-Linux-Process-Generic-SNMP-custom + _PROCESSNAME centengine + _PROCESSPATH /usr/sbin/ + _CRITICAL 1:1 +} + +define service { + service_description proc-centengine + name App-Monitoring-Centreon-Process-centengine-custom + register 0 + use App-Monitoring-Centreon-Process-centengine +} + +define service { + service_description proc-centcore + name App-Monitoring-Centreon-Process-centcore + register 0 + use OS-Linux-Process-Generic-SNMP-custom + _PROCESSNAME centcore + _CRITICAL 1:1 +} + +define service { + service_description proc-centcore + name App-Monitoring-Centreon-Process-centcore-custom + register 0 + use App-Monitoring-Centreon-Process-centcore +} + +define service { + service_description proc-broker-sql + name App-Monitoring-Centreon-Process-broker-sql + register 0 + use OS-Linux-Process-Generic-SNMP-custom + _PROCESSNAME cbd + _PROCESSARGS '/etc/centreon-broker/central-broker(.xml|.json)' + _CRITICAL 1:1 + _EXTRAOPTIONS --cpu --memory +} + +define service { + service_description proc-broker-sql + name App-Monitoring-Centreon-Process-broker-sql-custom + register 0 + use App-Monitoring-Centreon-Process-broker-sql +} + +define service { + service_description proc-broker-rrd + name App-Monitoring-Centreon-Process-broker-rrd + register 0 + use OS-Linux-Process-Generic-SNMP-custom + _PROCESSNAME cbd + _PROCESSARGS '/etc/centreon-broker/central-rrd(.xml|.json)' + _CRITICAL 1:1 + _EXTRAOPTIONS --cpu --memory +} + +define service { + service_description proc-broker-rrd + name App-Monitoring-Centreon-Process-broker-rrd-custom + register 0 + use App-Monitoring-Centreon-Process-broker-rrd +} + +define service { + service_description Ping + name Base-Ping-LAN + check_command base_centreon_ping + register 0 + use generic-active-service-custom + _PACKETNUMBER 5 + _WARNING 200,20% + _CRITICAL 400,50% +} + +define service { + service_description Ping + name Base-Ping-LAN-custom + register 0 + use Base-Ping-LAN +} + +define service { + service_description Swap + name OS-Linux-Swap-SNMP + check_command OS-Linux-SNMP-Swap + max_check_attempts 3 + check_interval 15 + retry_interval 1 + register 0 + use generic-active-service-custom + _CRITICAL 30 + _EXTRAOPTIONS + _WARNING 10 +} + +define service { + service_description Swap + name OS-Linux-Swap-SNMP-custom + register 0 + use OS-Linux-Swap-SNMP +} + +define service { + service_description Memory + name OS-Linux-Memory-SNMP + check_command OS-Linux-SNMP-Memory + max_check_attempts 3 + check_interval 15 + retry_interval 1 + register 0 + use generic-active-service-custom + _CRITICAL 90 + _EXTRAOPTIONS + _WARNING 80 +} + +define service { + service_description Memory + name OS-Linux-Memory-SNMP-custom + register 0 + use OS-Linux-Memory-SNMP +} + +define service { + service_description Load + name OS-Linux-Load-SNMP + check_command OS-Linux-SNMP-Load + max_check_attempts 3 + check_interval 5 + retry_interval 1 + register 0 + use generic-active-service-custom + _CRITICAL 6,5,4 + _EXTRAOPTIONS + _WARNING 4,3,2 +} + +define service { + service_description Load + name OS-Linux-Load-SNMP-custom + register 0 + use OS-Linux-Load-SNMP +} + +define service { + service_description Cpu + name OS-Linux-Cpu-SNMP + check_command OS-Linux-SNMP-Cpu + max_check_attempts 3 + check_interval 5 + retry_interval 1 + register 0 + use generic-active-service-custom + _WARNING 80 + _CRITICAL 90 +} + +define service { + service_description Cpu + name OS-Linux-Cpu-SNMP-custom + register 0 + use OS-Linux-Cpu-SNMP +} + +define service { + service_description Partitioning + name App-Centreon-MySQL-Partitioning + check_command App-Centreon-MySQL-Partitioning + max_check_attempts 5 + check_interval 1440 + retry_interval 5 + register 0 + active_checks_enabled 1 + passive_checks_enabled 0 + use generic-active-service-custom + _WARNING 7: + _CRITICAL 3: + _TABLENAME1 centreon_storage.data_bin + _TABLENAME2 centreon_storage.logs + _TABLENAME3 centreon_storage.log_archive_service + _TABLENAME4 centreon_storage.log_archive_host + _CRITICALITY_LEVEL 2 + _CRITICALITY_ID 6 + severity 6 +} + +define service { + service_description Partitioning + name App-Centreon-MySQL-Partitioning-custom + register 0 + use App-Centreon-MySQL-Partitioning +} + +define service { + service_description Slowqueries + name App-DB-MySQL-Slowqueries + check_command App-DB-MySQL + max_check_attempts 3 + check_interval 5 + retry_interval 1 + register 0 + use generic-active-service-custom + _MODE slow-queries + _WARNING 0 + _CRITICAL 0 +} + +define service { + service_description Slowqueries + name App-DB-MySQL-Slowqueries-custom + register 0 + use App-DB-MySQL-Slowqueries +} + +define service { + service_description Queries + name App-DB-MySQL-Queries + check_command App-DB-MySQL-Queries + register 0 + use generic-active-service-custom +} + +define service { + service_description Queries + name App-DB-MySQL-Queries-custom + register 0 + use App-DB-MySQL-Queries +} + +define service { + service_description Open-Files + name App-DB-MySQL-Open-Files + check_command App-DB-MySQL + max_check_attempts 3 + check_interval 15 + retry_interval 1 + register 0 + use generic-active-service-custom + _MODE open-files + _WARNING 80 + _CRITICAL 95 +} + +define service { + service_description Open-Files + name App-DB-MySQL-Open-Files-custom + register 0 + use App-DB-MySQL-Open-Files +} + +define service { + service_description Myisam-Keycache + name App-DB-MySQL-Myisam-Keycache + check_command App-DB-MySQL + max_check_attempts 3 + check_interval 5 + retry_interval 1 + register 0 + use generic-active-service-custom + _MODE myisam-keycache-hitrate +} + +define service { + service_description Myisam-Keycache + name App-DB-MySQL-Myisam-Keycache-custom + register 0 + use App-DB-MySQL-Myisam-Keycache +} + +define service { + service_description Database-Size + name App-DB-MySQL-Database-Size + check_command App-DB-MySQL-Database-Size + register 0 + use generic-active-service-custom + _FILTERDATABASE ^(?!(information_schema|performance_schema|test)) + _FILTERPERFDATA database + _EXTRAOPTIONS --verbose +} + +define service { + service_description Database-Size + name App-DB-MySQL-Database-Size-custom + register 0 + use App-DB-MySQL-Database-Size +} + +define service { + service_description Connections-Number + name App-DB-MySQL-Connections-Number + check_command App-DB-MySQL-Threads-Connected + register 0 + use generic-active-service-custom +} + +define service { + service_description Connections-Number + name App-DB-MySQL-Connections-Number-custom + register 0 + use App-DB-MySQL-Connections-Number +} + +define service { + service_description Connection-Time + name App-DB-MySQL-Connection-Time + check_command App-DB-MySQL + max_check_attempts 3 + check_interval 5 + retry_interval 1 + register 0 + use generic-active-service-custom + _MODE connection-time + _WARNING 1000 + _CRITICAL 5000 + _CRITICALITY_LEVEL 2 + _CRITICALITY_ID 6 + severity 6 +} + +define service { + service_description Connection-Time + name App-DB-MySQL-Connection-Time-custom + register 0 + use App-DB-MySQL-Connection-Time +} + +define service { + service_description Disk-Generic-Name + name OS-Linux-Disk-Generic-Name-SNMP + check_command OS-Linux-SNMP-Disk-Name + max_check_attempts 3 + check_interval 30 + retry_interval 1 + register 0 + use generic-active-service-custom + _WARNING 80 + _CRITICAL 90 + _EXTRAOPTIONS --filter-perfdata='storage.space|used|free' +} + +define service { + service_description Disk-Generic-Name + name OS-Linux-Disk-Generic-Name-SNMP-custom + register 0 + use OS-Linux-Disk-Generic-Name-SNMP +} + +define service { + service_description Broker-Stats + name App-Monitoring-Centreon-Broker-Stats-Central + check_command App-Monitoring-Centreon-Central-Broker-Stats + max_check_attempts 3 + check_interval 5 + retry_interval 1 + register 0 + use generic-active-service-custom + _CRITICALSTATUS %{type} eq "output" and %{queue_file_enabled} =~ /true|yes/i + _EXTRAOPTIONS --verbose +} + +define service { + service_description Broker-Stats + name App-Monitoring-Centreon-Broker-Stats-Central-custom + register 0 + use App-Monitoring-Centreon-Broker-Stats-Central +} diff --git a/engine/tests/cfg_files/conf1/servicegroups.cfg b/engine/tests/cfg_files/conf1/servicegroups.cfg new file mode 100644 index 00000000000..6fa39a187df --- /dev/null +++ b/engine/tests/cfg_files/conf1/servicegroups.cfg @@ -0,0 +1,23 @@ +################################################################### +# # +# GENERATED BY CENTREON # +# # +# Developed by : # +# - Julien Mathis # +# - Romain Le Merlus # +# # +# www.centreon.com # +# For information : contact@centreon.com # +################################################################### +# # +# Last modification 2023-05-31 18:43 # +# By John_Doe # +# # +################################################################### + +define servicegroup { + servicegroup_id 2 + servicegroup_name Database-MySQL + alias Database-MySQL + members Centreon-central,proc-sshd,Centreon-central,Partitioning,Centreon-central,Slowqueries,Centreon-central,Queries,Centreon-central,Myisam-Keycache,Centreon-central,Connections-Number,Centreon-central,Connection-Time,Centreon-central_1,Partitioning,Centreon-central_1,Slowqueries,Centreon-central_1,Queries,Centreon-central_1,Myisam-Keycache,Centreon-central_1,Connections-Number,Centreon-central_1,Connection-Time,Centreon-central_2,Partitioning,Centreon-central_2,Slowqueries,Centreon-central_2,Queries,Centreon-central_2,Myisam-Keycache,Centreon-central_2,Connections-Number,Centreon-central_2,Connection-Time,Centreon-central_3,Partitioning,Centreon-central_3,Slowqueries,Centreon-central_3,Queries,Centreon-central_3,Myisam-Keycache,Centreon-central_3,Connections-Number,Centreon-central_3,Connection-Time,Centreon-central_4,Partitioning,Centreon-central_4,Slowqueries,Centreon-central_4,Queries,Centreon-central_4,Myisam-Keycache,Centreon-central_4,Connections-Number,Centreon-central_4,Connection-Time,Centreon-central_5,Partitioning,Centreon-central_5,Slowqueries,Centreon-central_5,Queries,Centreon-central_5,Myisam-Keycache,Centreon-central_5,Connections-Number,Centreon-central_5,Connection-Time,Centreon-central_6,Partitioning,Centreon-central_6,Slowqueries,Centreon-central_6,Queries,Centreon-central_6,Myisam-Keycache,Centreon-central_6,Connections-Number,Centreon-central_6,Connection-Time,Centreon-central_7,Partitioning,Centreon-central_7,Slowqueries,Centreon-central_7,Queries,Centreon-central_7,Myisam-Keycache,Centreon-central_7,Connections-Number,Centreon-central_7,Connection-Time,Centreon-central_8,Partitioning,Centreon-central_8,Slowqueries,Centreon-central_8,Queries,Centreon-central_8,Myisam-Keycache,Centreon-central_8,Connections-Number,Centreon-central_8,Connection-Time,Centreon-central_9,Partitioning,Centreon-central_9,Slowqueries,Centreon-central_9,Queries,Centreon-central_9,Myisam-Keycache,Centreon-central_9,Connections-Number,Centreon-central_9,Connection-Time,Centreon-central_10,Partitioning,Centreon-central_10,Slowqueries,Centreon-central_10,Queries,Centreon-central_10,Myisam-Keycache,Centreon-central_10,Connections-Number,Centreon-central_10,Connection-Time +} diff --git a/engine/tests/cfg_files/conf1/services.cfg b/engine/tests/cfg_files/conf1/services.cfg new file mode 100644 index 00000000000..f9f13e8c3ac --- /dev/null +++ b/engine/tests/cfg_files/conf1/services.cfg @@ -0,0 +1,3282 @@ +################################################################### +# # +# GENERATED BY CENTREON # +# # +# Developed by : # +# - Julien Mathis # +# - Romain Le Merlus # +# # +# www.centreon.com # +# For information : contact@centreon.com # +################################################################### +# # +# Last modification 2023-05-31 18:43 # +# By John_Doe # +# # +################################################################### + +define service { + host_name Centreon-central + service_description proc-sshd + contacts John_Doe + contact_groups Guest,Supervisors + register 1 + use App-Monitoring-Centreon-Process-sshd-custom + group_tags 2 + _SERVICE_ID 196 +} + +define service { + host_name Centreon-central + service_description proc-httpd + contacts John_Doe + contact_groups Guest,Supervisors + register 1 + use App-Monitoring-Centreon-Process-httpd-custom + _SERVICE_ID 197 +} + +define service { + host_name Centreon-central + service_description proc-crond + contacts John_Doe + contact_groups Guest,Supervisors + register 1 + use App-Monitoring-Centreon-Process-crond-custom + _SERVICE_ID 198 +} + +define service { + host_name Centreon-central + service_description proc-centengine + contacts John_Doe + contact_groups Guest,Supervisors + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _SERVICE_ID 199 +} + +define service { + host_name Centreon-central + service_description proc-gorgone + contacts John_Doe + contact_groups Guest,Supervisors + register 1 + use App-Monitoring-Centreon-Process-centcore-custom + _CRITICAL 1: + _PROCESSNAME gorgone-.* + _SERVICE_ID 200 +} + +define service { + host_name Centreon-central + service_description proc-broker-sql + contacts John_Doe + contact_groups Guest,Supervisors + register 1 + use App-Monitoring-Centreon-Process-broker-sql-custom + _PROCESSARGS /etc/centreon-broker/central-broker.json + _SERVICE_ID 201 +} + +define service { + host_name Centreon-central + service_description proc-broker-rrd + contacts John_Doe + contact_groups Guest,Supervisors + register 1 + use App-Monitoring-Centreon-Process-broker-rrd-custom + _PROCESSARGS /etc/centreon-broker/central-rrd.json + _SERVICE_ID 202 +} + +define service { + host_name Centreon-central + service_description Ping + contacts John_Doe + contact_groups Guest,Supervisors + register 1 + use Base-Ping-LAN-custom + _SERVICE_ID 203 +} + +define service { + host_name Centreon-central + service_description Swap + contacts John_Doe + contact_groups Guest,Supervisors + register 1 + use OS-Linux-Swap-SNMP-custom + _SERVICE_ID 204 +} + +define service { + host_name Centreon-central + service_description Memory + contacts John_Doe + contact_groups Guest,Supervisors + register 1 + use OS-Linux-Memory-SNMP-custom + _EXTRAOPTIONS '--redhat' + _SERVICE_ID 205 +} + +define service { + host_name Centreon-central + service_description Load + contacts John_Doe + contact_groups Guest,Supervisors + register 1 + use OS-Linux-Load-SNMP-custom + _SERVICE_ID 206 +} + +define service { + host_name Centreon-central + service_description Cpu + contacts John_Doe + contact_groups Guest,Supervisors + register 1 + use OS-Linux-Cpu-SNMP-custom + _SERVICE_ID 207 +} + +define service { + host_name Centreon-central + service_description Partitioning + contacts John_Doe + contact_groups Guest,Supervisors + notification_interval 1440 + register 1 + use App-Centreon-MySQL-Partitioning-custom + group_tags 2 + _SERVICE_ID 208 +} + +define service { + host_name Centreon-central + service_description Slowqueries + contacts John_Doe + contact_groups Guest,Supervisors + register 1 + use App-DB-MySQL-Slowqueries-custom + group_tags 2 + _SERVICE_ID 209 +} + +define service { + host_name Centreon-central + service_description Queries + contacts John_Doe + contact_groups Guest,Supervisors + register 1 + use App-DB-MySQL-Queries-custom + group_tags 2 + _SERVICE_ID 210 +} + +define service { + host_name Centreon-central + service_description Open-Files + contacts John_Doe + contact_groups Guest,Supervisors + register 1 + use App-DB-MySQL-Open-Files-custom + _SERVICE_ID 211 +} + +define service { + host_name Centreon-central + service_description Myisam-Keycache + contacts John_Doe + contact_groups Guest,Supervisors + register 1 + use App-DB-MySQL-Myisam-Keycache-custom + group_tags 2 + _SERVICE_ID 212 +} + +define service { + host_name Centreon-central + service_description Database-Size + contacts John_Doe + contact_groups Guest,Supervisors + register 1 + use App-DB-MySQL-Database-Size-custom + _SERVICE_ID 213 +} + +define service { + host_name Centreon-central + service_description Connections-Number + contacts John_Doe + contact_groups Guest,Supervisors + register 1 + use App-DB-MySQL-Connections-Number-custom + group_tags 2 + _SERVICE_ID 214 +} + +define service { + host_name Centreon-central + service_description Connection-Time + contacts John_Doe + contact_groups Guest,Supervisors + register 1 + use App-DB-MySQL-Connection-Time-custom + group_tags 2 + _SERVICE_ID 215 +} + +define service { + host_name Centreon-central + service_description Disk-/ + contacts John_Doe + contact_groups Guest,Supervisors + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME / + _SERVICE_ID 216 +} + +define service { + host_name Centreon-central + service_description Disk-/var/lib/mysql + contacts John_Doe + contact_groups Guest,Supervisors + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/mysql + _SERVICE_ID 217 +} + +define service { + host_name Centreon-central + service_description Disk-/var/cache/centreon/backup + contacts John_Doe + contact_groups Guest,Supervisors + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/cache/centreon/backup + _SERVICE_ID 218 +} + +define service { + host_name Centreon-central + service_description Disk-/var/lib/centreon-broker + contacts John_Doe + contact_groups Guest,Supervisors + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/centreon-broker + _SERVICE_ID 219 +} + +define service { + host_name Centreon-central + service_description Disk-/var/log + contacts John_Doe + contact_groups Guest,Supervisors + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/log + _SERVICE_ID 220 +} + +define service { + host_name Centreon-central + service_description Disk-/var/lib/centreon + contacts John_Doe + contact_groups Guest,Supervisors + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/centreon + _SERVICE_ID 221 +} + +define service { + host_name Centreon-central + service_description proc-centreontrapd + contacts John_Doe + contact_groups Guest,Supervisors + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME centreontrapd + _PROCESSPATH + _SERVICE_ID 222 +} + +define service { + host_name Centreon-central + service_description proc-snmptrapd + contacts John_Doe + contact_groups Guest,Supervisors + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME snmptrapd + _SERVICE_ID 223 +} + +define service { + host_name Centreon-central + service_description proc-rsyslogd + contacts John_Doe + contact_groups Guest,Supervisors + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME rsyslogd + _SERVICE_ID 224 +} + +define service { + host_name Centreon-central + service_description proc-snmpd + contacts John_Doe + contact_groups Guest,Supervisors + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME snmpd + _SERVICE_ID 225 +} + +define service { + host_name Centreon-central + service_description proc-broker-watchdog + contacts John_Doe + contact_groups Guest,Supervisors + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME cbwd + _SERVICE_ID 226 +} + +define service { + host_name Centreon-central + service_description proc-postfix + contacts John_Doe + contact_groups Guest,Supervisors + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME master + _PROCESSPATH /usr/libexec/postfix/master + _SERVICE_ID 227 +} + +define service { + host_name Centreon-central + service_description Broker-Stats + contacts John_Doe + contact_groups Guest + notification_period 24x7 + notification_interval 5 + notification_options w,u,c + first_notification_delay 0 + recovery_notification_delay 0 + register 1 + notifications_enabled 1 + use App-Monitoring-Centreon-Broker-Stats-Central-custom + _SERVICE_ID 228 + _CRITICALITY_LEVEL 1 + _CRITICALITY_ID 5 + severity 5 +} + +define service { + host_name Centreon-central_1 + service_description proc-sshd + register 1 + use App-Monitoring-Centreon-Process-sshd-custom + _SERVICE_ID 229 +} + +define service { + host_name Centreon-central_1 + service_description proc-httpd + register 1 + use App-Monitoring-Centreon-Process-httpd-custom + _SERVICE_ID 230 +} + +define service { + host_name Centreon-central_1 + service_description proc-crond + register 1 + use App-Monitoring-Centreon-Process-crond-custom + _SERVICE_ID 231 +} + +define service { + host_name Centreon-central_1 + service_description proc-centengine + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _SERVICE_ID 232 +} + +define service { + host_name Centreon-central_1 + service_description proc-gorgone + register 1 + use App-Monitoring-Centreon-Process-centcore-custom + _CRITICAL 1: + _PROCESSNAME gorgone-.* + _SERVICE_ID 233 +} + +define service { + host_name Centreon-central_1 + service_description proc-broker-sql + register 1 + use App-Monitoring-Centreon-Process-broker-sql-custom + _PROCESSARGS /etc/centreon-broker/central-broker.json + _SERVICE_ID 234 +} + +define service { + host_name Centreon-central_1 + service_description proc-broker-rrd + register 1 + use App-Monitoring-Centreon-Process-broker-rrd-custom + _PROCESSARGS /etc/centreon-broker/central-rrd.json + _SERVICE_ID 235 +} + +define service { + host_name Centreon-central_1 + service_description Ping + register 1 + use Base-Ping-LAN-custom + _SERVICE_ID 236 +} + +define service { + host_name Centreon-central_1 + service_description Swap + register 1 + use OS-Linux-Swap-SNMP-custom + _SERVICE_ID 237 +} + +define service { + host_name Centreon-central_1 + service_description Memory + register 1 + use OS-Linux-Memory-SNMP-custom + _EXTRAOPTIONS '--redhat' + _SERVICE_ID 238 +} + +define service { + host_name Centreon-central_1 + service_description Load + register 1 + use OS-Linux-Load-SNMP-custom + _SERVICE_ID 239 +} + +define service { + host_name Centreon-central_1 + service_description Cpu + register 1 + use OS-Linux-Cpu-SNMP-custom + _SERVICE_ID 240 +} + +define service { + host_name Centreon-central_1 + service_description Partitioning + notification_interval 1440 + register 1 + use App-Centreon-MySQL-Partitioning-custom + group_tags 2 + _SERVICE_ID 241 +} + +define service { + host_name Centreon-central_1 + service_description Slowqueries + register 1 + use App-DB-MySQL-Slowqueries-custom + group_tags 2 + _SERVICE_ID 242 +} + +define service { + host_name Centreon-central_1 + service_description Queries + register 1 + use App-DB-MySQL-Queries-custom + group_tags 2 + _SERVICE_ID 243 +} + +define service { + host_name Centreon-central_1 + service_description Open-Files + register 1 + use App-DB-MySQL-Open-Files-custom + _SERVICE_ID 244 +} + +define service { + host_name Centreon-central_1 + service_description Myisam-Keycache + register 1 + use App-DB-MySQL-Myisam-Keycache-custom + group_tags 2 + _SERVICE_ID 245 +} + +define service { + host_name Centreon-central_1 + service_description Database-Size + register 1 + use App-DB-MySQL-Database-Size-custom + _SERVICE_ID 246 +} + +define service { + host_name Centreon-central_1 + service_description Connections-Number + register 1 + use App-DB-MySQL-Connections-Number-custom + group_tags 2 + _SERVICE_ID 247 +} + +define service { + host_name Centreon-central_1 + service_description Connection-Time + register 1 + use App-DB-MySQL-Connection-Time-custom + group_tags 2 + _SERVICE_ID 248 +} + +define service { + host_name Centreon-central_1 + service_description Disk-/ + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME / + _SERVICE_ID 249 +} + +define service { + host_name Centreon-central_1 + service_description Disk-/var/lib/mysql + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/mysql + _SERVICE_ID 250 +} + +define service { + host_name Centreon-central_1 + service_description Disk-/var/cache/centreon/backup + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/cache/centreon/backup + _SERVICE_ID 251 +} + +define service { + host_name Centreon-central_1 + service_description Disk-/var/lib/centreon-broker + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/centreon-broker + _SERVICE_ID 252 +} + +define service { + host_name Centreon-central_1 + service_description Disk-/var/log + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/log + _SERVICE_ID 253 +} + +define service { + host_name Centreon-central_1 + service_description Disk-/var/lib/centreon + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/centreon + _SERVICE_ID 254 +} + +define service { + host_name Centreon-central_1 + service_description proc-centreontrapd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME centreontrapd + _PROCESSPATH + _SERVICE_ID 255 +} + +define service { + host_name Centreon-central_1 + service_description proc-snmptrapd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME snmptrapd + _SERVICE_ID 256 +} + +define service { + host_name Centreon-central_1 + service_description proc-rsyslogd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME rsyslogd + _SERVICE_ID 257 +} + +define service { + host_name Centreon-central_1 + service_description proc-snmpd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME snmpd + _SERVICE_ID 258 +} + +define service { + host_name Centreon-central_1 + service_description proc-broker-watchdog + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME cbwd + _SERVICE_ID 259 +} + +define service { + host_name Centreon-central_1 + service_description proc-postfix + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME master + _PROCESSPATH /usr/libexec/postfix/master + _SERVICE_ID 260 +} + +define service { + host_name Centreon-central_1 + service_description Broker-Stats + register 1 + use App-Monitoring-Centreon-Broker-Stats-Central-custom + _SERVICE_ID 261 +} + +define service { + host_name Centreon-central_2 + service_description proc-sshd + register 1 + use App-Monitoring-Centreon-Process-sshd-custom + _SERVICE_ID 262 +} + +define service { + host_name Centreon-central_2 + service_description proc-httpd + register 1 + use App-Monitoring-Centreon-Process-httpd-custom + _SERVICE_ID 263 +} + +define service { + host_name Centreon-central_2 + service_description proc-crond + register 1 + use App-Monitoring-Centreon-Process-crond-custom + _SERVICE_ID 264 +} + +define service { + host_name Centreon-central_2 + service_description proc-centengine + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _SERVICE_ID 265 +} + +define service { + host_name Centreon-central_2 + service_description proc-gorgone + register 1 + use App-Monitoring-Centreon-Process-centcore-custom + _CRITICAL 1: + _PROCESSNAME gorgone-.* + _SERVICE_ID 266 +} + +define service { + host_name Centreon-central_2 + service_description proc-broker-sql + register 1 + use App-Monitoring-Centreon-Process-broker-sql-custom + _PROCESSARGS /etc/centreon-broker/central-broker.json + _SERVICE_ID 267 +} + +define service { + host_name Centreon-central_2 + service_description proc-broker-rrd + register 1 + use App-Monitoring-Centreon-Process-broker-rrd-custom + _PROCESSARGS /etc/centreon-broker/central-rrd.json + _SERVICE_ID 268 +} + +define service { + host_name Centreon-central_2 + service_description Ping + register 1 + use Base-Ping-LAN-custom + _SERVICE_ID 269 +} + +define service { + host_name Centreon-central_2 + service_description Swap + register 1 + use OS-Linux-Swap-SNMP-custom + _SERVICE_ID 270 +} + +define service { + host_name Centreon-central_2 + service_description Memory + register 1 + use OS-Linux-Memory-SNMP-custom + _EXTRAOPTIONS '--redhat' + _SERVICE_ID 271 +} + +define service { + host_name Centreon-central_2 + service_description Load + register 1 + use OS-Linux-Load-SNMP-custom + _SERVICE_ID 272 +} + +define service { + host_name Centreon-central_2 + service_description Cpu + register 1 + use OS-Linux-Cpu-SNMP-custom + _SERVICE_ID 273 +} + +define service { + host_name Centreon-central_2 + service_description Partitioning + notification_interval 1440 + register 1 + use App-Centreon-MySQL-Partitioning-custom + group_tags 2 + _SERVICE_ID 274 +} + +define service { + host_name Centreon-central_2 + service_description Slowqueries + register 1 + use App-DB-MySQL-Slowqueries-custom + group_tags 2 + _SERVICE_ID 275 +} + +define service { + host_name Centreon-central_2 + service_description Queries + register 1 + use App-DB-MySQL-Queries-custom + group_tags 2 + _SERVICE_ID 276 +} + +define service { + host_name Centreon-central_2 + service_description Open-Files + register 1 + use App-DB-MySQL-Open-Files-custom + _SERVICE_ID 277 +} + +define service { + host_name Centreon-central_2 + service_description Myisam-Keycache + register 1 + use App-DB-MySQL-Myisam-Keycache-custom + group_tags 2 + _SERVICE_ID 278 +} + +define service { + host_name Centreon-central_2 + service_description Database-Size + register 1 + use App-DB-MySQL-Database-Size-custom + _SERVICE_ID 279 +} + +define service { + host_name Centreon-central_2 + service_description Connections-Number + register 1 + use App-DB-MySQL-Connections-Number-custom + group_tags 2 + _SERVICE_ID 280 +} + +define service { + host_name Centreon-central_2 + service_description Connection-Time + register 1 + use App-DB-MySQL-Connection-Time-custom + group_tags 2 + _SERVICE_ID 281 +} + +define service { + host_name Centreon-central_2 + service_description Disk-/ + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME / + _SERVICE_ID 282 +} + +define service { + host_name Centreon-central_2 + service_description Disk-/var/lib/mysql + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/mysql + _SERVICE_ID 283 +} + +define service { + host_name Centreon-central_2 + service_description Disk-/var/cache/centreon/backup + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/cache/centreon/backup + _SERVICE_ID 284 +} + +define service { + host_name Centreon-central_2 + service_description Disk-/var/lib/centreon-broker + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/centreon-broker + _SERVICE_ID 285 +} + +define service { + host_name Centreon-central_2 + service_description Disk-/var/log + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/log + _SERVICE_ID 286 +} + +define service { + host_name Centreon-central_2 + service_description Disk-/var/lib/centreon + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/centreon + _SERVICE_ID 287 +} + +define service { + host_name Centreon-central_2 + service_description proc-centreontrapd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME centreontrapd + _PROCESSPATH + _SERVICE_ID 288 +} + +define service { + host_name Centreon-central_2 + service_description proc-snmptrapd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME snmptrapd + _SERVICE_ID 289 +} + +define service { + host_name Centreon-central_2 + service_description proc-rsyslogd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME rsyslogd + _SERVICE_ID 290 +} + +define service { + host_name Centreon-central_2 + service_description proc-snmpd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME snmpd + _SERVICE_ID 291 +} + +define service { + host_name Centreon-central_2 + service_description proc-broker-watchdog + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME cbwd + _SERVICE_ID 292 +} + +define service { + host_name Centreon-central_2 + service_description proc-postfix + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME master + _PROCESSPATH /usr/libexec/postfix/master + _SERVICE_ID 293 +} + +define service { + host_name Centreon-central_2 + service_description Broker-Stats + register 1 + use App-Monitoring-Centreon-Broker-Stats-Central-custom + _SERVICE_ID 294 +} + +define service { + host_name Centreon-central_3 + service_description proc-sshd + register 1 + use App-Monitoring-Centreon-Process-sshd-custom + _SERVICE_ID 295 +} + +define service { + host_name Centreon-central_3 + service_description proc-httpd + register 1 + use App-Monitoring-Centreon-Process-httpd-custom + _SERVICE_ID 296 +} + +define service { + host_name Centreon-central_3 + service_description proc-crond + register 1 + use App-Monitoring-Centreon-Process-crond-custom + _SERVICE_ID 297 +} + +define service { + host_name Centreon-central_3 + service_description proc-centengine + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _SERVICE_ID 298 +} + +define service { + host_name Centreon-central_3 + service_description proc-gorgone + register 1 + use App-Monitoring-Centreon-Process-centcore-custom + _CRITICAL 1: + _PROCESSNAME gorgone-.* + _SERVICE_ID 299 +} + +define service { + host_name Centreon-central_3 + service_description proc-broker-sql + register 1 + use App-Monitoring-Centreon-Process-broker-sql-custom + _PROCESSARGS /etc/centreon-broker/central-broker.json + _SERVICE_ID 300 +} + +define service { + host_name Centreon-central_3 + service_description proc-broker-rrd + register 1 + use App-Monitoring-Centreon-Process-broker-rrd-custom + _PROCESSARGS /etc/centreon-broker/central-rrd.json + _SERVICE_ID 301 +} + +define service { + host_name Centreon-central_3 + service_description Ping + register 1 + use Base-Ping-LAN-custom + _SERVICE_ID 302 +} + +define service { + host_name Centreon-central_3 + service_description Swap + register 1 + use OS-Linux-Swap-SNMP-custom + _SERVICE_ID 303 +} + +define service { + host_name Centreon-central_3 + service_description Memory + register 1 + use OS-Linux-Memory-SNMP-custom + _EXTRAOPTIONS '--redhat' + _SERVICE_ID 304 +} + +define service { + host_name Centreon-central_3 + service_description Load + register 1 + use OS-Linux-Load-SNMP-custom + _SERVICE_ID 305 +} + +define service { + host_name Centreon-central_3 + service_description Cpu + register 1 + use OS-Linux-Cpu-SNMP-custom + _SERVICE_ID 306 +} + +define service { + host_name Centreon-central_3 + service_description Partitioning + notification_interval 1440 + register 1 + use App-Centreon-MySQL-Partitioning-custom + group_tags 2 + _SERVICE_ID 307 +} + +define service { + host_name Centreon-central_3 + service_description Slowqueries + register 1 + use App-DB-MySQL-Slowqueries-custom + group_tags 2 + _SERVICE_ID 308 +} + +define service { + host_name Centreon-central_3 + service_description Queries + register 1 + use App-DB-MySQL-Queries-custom + group_tags 2 + _SERVICE_ID 309 +} + +define service { + host_name Centreon-central_3 + service_description Open-Files + register 1 + use App-DB-MySQL-Open-Files-custom + _SERVICE_ID 310 +} + +define service { + host_name Centreon-central_3 + service_description Myisam-Keycache + register 1 + use App-DB-MySQL-Myisam-Keycache-custom + group_tags 2 + _SERVICE_ID 311 +} + +define service { + host_name Centreon-central_3 + service_description Database-Size + register 1 + use App-DB-MySQL-Database-Size-custom + _SERVICE_ID 312 +} + +define service { + host_name Centreon-central_3 + service_description Connections-Number + register 1 + use App-DB-MySQL-Connections-Number-custom + group_tags 2 + _SERVICE_ID 313 +} + +define service { + host_name Centreon-central_3 + service_description Connection-Time + register 1 + use App-DB-MySQL-Connection-Time-custom + group_tags 2 + _SERVICE_ID 314 +} + +define service { + host_name Centreon-central_3 + service_description Disk-/ + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME / + _SERVICE_ID 315 +} + +define service { + host_name Centreon-central_3 + service_description Disk-/var/lib/mysql + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/mysql + _SERVICE_ID 316 +} + +define service { + host_name Centreon-central_3 + service_description Disk-/var/cache/centreon/backup + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/cache/centreon/backup + _SERVICE_ID 317 +} + +define service { + host_name Centreon-central_3 + service_description Disk-/var/lib/centreon-broker + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/centreon-broker + _SERVICE_ID 318 +} + +define service { + host_name Centreon-central_3 + service_description Disk-/var/log + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/log + _SERVICE_ID 319 +} + +define service { + host_name Centreon-central_3 + service_description Disk-/var/lib/centreon + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/centreon + _SERVICE_ID 320 +} + +define service { + host_name Centreon-central_3 + service_description proc-centreontrapd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME centreontrapd + _PROCESSPATH + _SERVICE_ID 321 +} + +define service { + host_name Centreon-central_3 + service_description proc-snmptrapd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME snmptrapd + _SERVICE_ID 322 +} + +define service { + host_name Centreon-central_3 + service_description proc-rsyslogd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME rsyslogd + _SERVICE_ID 323 +} + +define service { + host_name Centreon-central_3 + service_description proc-snmpd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME snmpd + _SERVICE_ID 324 +} + +define service { + host_name Centreon-central_3 + service_description proc-broker-watchdog + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME cbwd + _SERVICE_ID 325 +} + +define service { + host_name Centreon-central_3 + service_description proc-postfix + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME master + _PROCESSPATH /usr/libexec/postfix/master + _SERVICE_ID 326 +} + +define service { + host_name Centreon-central_3 + service_description Broker-Stats + register 1 + use App-Monitoring-Centreon-Broker-Stats-Central-custom + _SERVICE_ID 327 +} + +define service { + host_name Centreon-central_4 + service_description proc-sshd + register 1 + use App-Monitoring-Centreon-Process-sshd-custom + _SERVICE_ID 328 +} + +define service { + host_name Centreon-central_4 + service_description proc-httpd + register 1 + use App-Monitoring-Centreon-Process-httpd-custom + _SERVICE_ID 329 +} + +define service { + host_name Centreon-central_4 + service_description proc-crond + register 1 + use App-Monitoring-Centreon-Process-crond-custom + _SERVICE_ID 330 +} + +define service { + host_name Centreon-central_4 + service_description proc-centengine + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _SERVICE_ID 331 +} + +define service { + host_name Centreon-central_4 + service_description proc-gorgone + register 1 + use App-Monitoring-Centreon-Process-centcore-custom + _CRITICAL 1: + _PROCESSNAME gorgone-.* + _SERVICE_ID 332 +} + +define service { + host_name Centreon-central_4 + service_description proc-broker-sql + register 1 + use App-Monitoring-Centreon-Process-broker-sql-custom + _PROCESSARGS /etc/centreon-broker/central-broker.json + _SERVICE_ID 333 +} + +define service { + host_name Centreon-central_4 + service_description proc-broker-rrd + register 1 + use App-Monitoring-Centreon-Process-broker-rrd-custom + _PROCESSARGS /etc/centreon-broker/central-rrd.json + _SERVICE_ID 334 +} + +define service { + host_name Centreon-central_4 + service_description Ping + register 1 + use Base-Ping-LAN-custom + _SERVICE_ID 335 +} + +define service { + host_name Centreon-central_4 + service_description Swap + register 1 + use OS-Linux-Swap-SNMP-custom + _SERVICE_ID 336 +} + +define service { + host_name Centreon-central_4 + service_description Memory + register 1 + use OS-Linux-Memory-SNMP-custom + _EXTRAOPTIONS '--redhat' + _SERVICE_ID 337 +} + +define service { + host_name Centreon-central_4 + service_description Load + register 1 + use OS-Linux-Load-SNMP-custom + _SERVICE_ID 338 +} + +define service { + host_name Centreon-central_4 + service_description Cpu + register 1 + use OS-Linux-Cpu-SNMP-custom + _SERVICE_ID 339 +} + +define service { + host_name Centreon-central_4 + service_description Partitioning + notification_interval 1440 + register 1 + use App-Centreon-MySQL-Partitioning-custom + group_tags 2 + _SERVICE_ID 340 +} + +define service { + host_name Centreon-central_4 + service_description Slowqueries + register 1 + use App-DB-MySQL-Slowqueries-custom + group_tags 2 + _SERVICE_ID 341 +} + +define service { + host_name Centreon-central_4 + service_description Queries + register 1 + use App-DB-MySQL-Queries-custom + group_tags 2 + _SERVICE_ID 342 +} + +define service { + host_name Centreon-central_4 + service_description Open-Files + register 1 + use App-DB-MySQL-Open-Files-custom + _SERVICE_ID 343 +} + +define service { + host_name Centreon-central_4 + service_description Myisam-Keycache + register 1 + use App-DB-MySQL-Myisam-Keycache-custom + group_tags 2 + _SERVICE_ID 344 +} + +define service { + host_name Centreon-central_4 + service_description Database-Size + register 1 + use App-DB-MySQL-Database-Size-custom + _SERVICE_ID 345 +} + +define service { + host_name Centreon-central_4 + service_description Connections-Number + register 1 + use App-DB-MySQL-Connections-Number-custom + group_tags 2 + _SERVICE_ID 346 +} + +define service { + host_name Centreon-central_4 + service_description Connection-Time + register 1 + use App-DB-MySQL-Connection-Time-custom + group_tags 2 + _SERVICE_ID 347 +} + +define service { + host_name Centreon-central_4 + service_description Disk-/ + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME / + _SERVICE_ID 348 +} + +define service { + host_name Centreon-central_4 + service_description Disk-/var/lib/mysql + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/mysql + _SERVICE_ID 349 +} + +define service { + host_name Centreon-central_4 + service_description Disk-/var/cache/centreon/backup + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/cache/centreon/backup + _SERVICE_ID 350 +} + +define service { + host_name Centreon-central_4 + service_description Disk-/var/lib/centreon-broker + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/centreon-broker + _SERVICE_ID 351 +} + +define service { + host_name Centreon-central_4 + service_description Disk-/var/log + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/log + _SERVICE_ID 352 +} + +define service { + host_name Centreon-central_4 + service_description Disk-/var/lib/centreon + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/centreon + _SERVICE_ID 353 +} + +define service { + host_name Centreon-central_4 + service_description proc-centreontrapd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME centreontrapd + _PROCESSPATH + _SERVICE_ID 354 +} + +define service { + host_name Centreon-central_4 + service_description proc-snmptrapd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME snmptrapd + _SERVICE_ID 355 +} + +define service { + host_name Centreon-central_4 + service_description proc-rsyslogd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME rsyslogd + _SERVICE_ID 356 +} + +define service { + host_name Centreon-central_4 + service_description proc-snmpd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME snmpd + _SERVICE_ID 357 +} + +define service { + host_name Centreon-central_4 + service_description proc-broker-watchdog + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME cbwd + _SERVICE_ID 358 +} + +define service { + host_name Centreon-central_4 + service_description proc-postfix + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME master + _PROCESSPATH /usr/libexec/postfix/master + _SERVICE_ID 359 +} + +define service { + host_name Centreon-central_4 + service_description Broker-Stats + register 1 + use App-Monitoring-Centreon-Broker-Stats-Central-custom + _SERVICE_ID 360 +} + +define service { + host_name Centreon-central_5 + service_description proc-sshd + register 1 + use App-Monitoring-Centreon-Process-sshd-custom + _SERVICE_ID 361 +} + +define service { + host_name Centreon-central_5 + service_description proc-httpd + register 1 + use App-Monitoring-Centreon-Process-httpd-custom + _SERVICE_ID 362 +} + +define service { + host_name Centreon-central_5 + service_description proc-crond + register 1 + use App-Monitoring-Centreon-Process-crond-custom + _SERVICE_ID 363 +} + +define service { + host_name Centreon-central_5 + service_description proc-centengine + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _SERVICE_ID 364 +} + +define service { + host_name Centreon-central_5 + service_description proc-gorgone + register 1 + use App-Monitoring-Centreon-Process-centcore-custom + _CRITICAL 1: + _PROCESSNAME gorgone-.* + _SERVICE_ID 365 +} + +define service { + host_name Centreon-central_5 + service_description proc-broker-sql + register 1 + use App-Monitoring-Centreon-Process-broker-sql-custom + _PROCESSARGS /etc/centreon-broker/central-broker.json + _SERVICE_ID 366 +} + +define service { + host_name Centreon-central_5 + service_description proc-broker-rrd + register 1 + use App-Monitoring-Centreon-Process-broker-rrd-custom + _PROCESSARGS /etc/centreon-broker/central-rrd.json + _SERVICE_ID 367 +} + +define service { + host_name Centreon-central_5 + service_description Ping + register 1 + use Base-Ping-LAN-custom + _SERVICE_ID 368 +} + +define service { + host_name Centreon-central_5 + service_description Swap + register 1 + use OS-Linux-Swap-SNMP-custom + _SERVICE_ID 369 +} + +define service { + host_name Centreon-central_5 + service_description Memory + register 1 + use OS-Linux-Memory-SNMP-custom + _EXTRAOPTIONS '--redhat' + _SERVICE_ID 370 +} + +define service { + host_name Centreon-central_5 + service_description Load + register 1 + use OS-Linux-Load-SNMP-custom + _SERVICE_ID 371 +} + +define service { + host_name Centreon-central_5 + service_description Cpu + register 1 + use OS-Linux-Cpu-SNMP-custom + _SERVICE_ID 372 +} + +define service { + host_name Centreon-central_5 + service_description Partitioning + notification_interval 1440 + register 1 + use App-Centreon-MySQL-Partitioning-custom + group_tags 2 + _SERVICE_ID 373 +} + +define service { + host_name Centreon-central_5 + service_description Slowqueries + register 1 + use App-DB-MySQL-Slowqueries-custom + group_tags 2 + _SERVICE_ID 374 +} + +define service { + host_name Centreon-central_5 + service_description Queries + register 1 + use App-DB-MySQL-Queries-custom + group_tags 2 + _SERVICE_ID 375 +} + +define service { + host_name Centreon-central_5 + service_description Open-Files + register 1 + use App-DB-MySQL-Open-Files-custom + _SERVICE_ID 376 +} + +define service { + host_name Centreon-central_5 + service_description Myisam-Keycache + register 1 + use App-DB-MySQL-Myisam-Keycache-custom + group_tags 2 + _SERVICE_ID 377 +} + +define service { + host_name Centreon-central_5 + service_description Database-Size + register 1 + use App-DB-MySQL-Database-Size-custom + _SERVICE_ID 378 +} + +define service { + host_name Centreon-central_5 + service_description Connections-Number + register 1 + use App-DB-MySQL-Connections-Number-custom + group_tags 2 + _SERVICE_ID 379 +} + +define service { + host_name Centreon-central_5 + service_description Connection-Time + register 1 + use App-DB-MySQL-Connection-Time-custom + group_tags 2 + _SERVICE_ID 380 +} + +define service { + host_name Centreon-central_5 + service_description Disk-/ + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME / + _SERVICE_ID 381 +} + +define service { + host_name Centreon-central_5 + service_description Disk-/var/lib/mysql + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/mysql + _SERVICE_ID 382 +} + +define service { + host_name Centreon-central_5 + service_description Disk-/var/cache/centreon/backup + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/cache/centreon/backup + _SERVICE_ID 383 +} + +define service { + host_name Centreon-central_5 + service_description Disk-/var/lib/centreon-broker + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/centreon-broker + _SERVICE_ID 384 +} + +define service { + host_name Centreon-central_5 + service_description Disk-/var/log + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/log + _SERVICE_ID 385 +} + +define service { + host_name Centreon-central_5 + service_description Disk-/var/lib/centreon + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/centreon + _SERVICE_ID 386 +} + +define service { + host_name Centreon-central_5 + service_description proc-centreontrapd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME centreontrapd + _PROCESSPATH + _SERVICE_ID 387 +} + +define service { + host_name Centreon-central_5 + service_description proc-snmptrapd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME snmptrapd + _SERVICE_ID 388 +} + +define service { + host_name Centreon-central_5 + service_description proc-rsyslogd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME rsyslogd + _SERVICE_ID 389 +} + +define service { + host_name Centreon-central_5 + service_description proc-snmpd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME snmpd + _SERVICE_ID 390 +} + +define service { + host_name Centreon-central_5 + service_description proc-broker-watchdog + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME cbwd + _SERVICE_ID 391 +} + +define service { + host_name Centreon-central_5 + service_description proc-postfix + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME master + _PROCESSPATH /usr/libexec/postfix/master + _SERVICE_ID 392 +} + +define service { + host_name Centreon-central_5 + service_description Broker-Stats + register 1 + use App-Monitoring-Centreon-Broker-Stats-Central-custom + _SERVICE_ID 393 +} + +define service { + host_name Centreon-central_6 + service_description proc-sshd + register 1 + use App-Monitoring-Centreon-Process-sshd-custom + _SERVICE_ID 394 +} + +define service { + host_name Centreon-central_6 + service_description proc-httpd + register 1 + use App-Monitoring-Centreon-Process-httpd-custom + _SERVICE_ID 395 +} + +define service { + host_name Centreon-central_6 + service_description proc-crond + register 1 + use App-Monitoring-Centreon-Process-crond-custom + _SERVICE_ID 396 +} + +define service { + host_name Centreon-central_6 + service_description proc-centengine + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _SERVICE_ID 397 +} + +define service { + host_name Centreon-central_6 + service_description proc-gorgone + register 1 + use App-Monitoring-Centreon-Process-centcore-custom + _CRITICAL 1: + _PROCESSNAME gorgone-.* + _SERVICE_ID 398 +} + +define service { + host_name Centreon-central_6 + service_description proc-broker-sql + register 1 + use App-Monitoring-Centreon-Process-broker-sql-custom + _PROCESSARGS /etc/centreon-broker/central-broker.json + _SERVICE_ID 399 +} + +define service { + host_name Centreon-central_6 + service_description proc-broker-rrd + register 1 + use App-Monitoring-Centreon-Process-broker-rrd-custom + _PROCESSARGS /etc/centreon-broker/central-rrd.json + _SERVICE_ID 400 +} + +define service { + host_name Centreon-central_6 + service_description Ping + register 1 + use Base-Ping-LAN-custom + _SERVICE_ID 401 +} + +define service { + host_name Centreon-central_6 + service_description Swap + register 1 + use OS-Linux-Swap-SNMP-custom + _SERVICE_ID 402 +} + +define service { + host_name Centreon-central_6 + service_description Memory + register 1 + use OS-Linux-Memory-SNMP-custom + _EXTRAOPTIONS '--redhat' + _SERVICE_ID 403 +} + +define service { + host_name Centreon-central_6 + service_description Load + register 1 + use OS-Linux-Load-SNMP-custom + _SERVICE_ID 404 +} + +define service { + host_name Centreon-central_6 + service_description Cpu + register 1 + use OS-Linux-Cpu-SNMP-custom + _SERVICE_ID 405 +} + +define service { + host_name Centreon-central_6 + service_description Partitioning + notification_interval 1440 + register 1 + use App-Centreon-MySQL-Partitioning-custom + group_tags 2 + _SERVICE_ID 406 +} + +define service { + host_name Centreon-central_6 + service_description Slowqueries + register 1 + use App-DB-MySQL-Slowqueries-custom + group_tags 2 + _SERVICE_ID 407 +} + +define service { + host_name Centreon-central_6 + service_description Queries + register 1 + use App-DB-MySQL-Queries-custom + group_tags 2 + _SERVICE_ID 408 +} + +define service { + host_name Centreon-central_6 + service_description Open-Files + register 1 + use App-DB-MySQL-Open-Files-custom + _SERVICE_ID 409 +} + +define service { + host_name Centreon-central_6 + service_description Myisam-Keycache + register 1 + use App-DB-MySQL-Myisam-Keycache-custom + group_tags 2 + _SERVICE_ID 410 +} + +define service { + host_name Centreon-central_6 + service_description Database-Size + register 1 + use App-DB-MySQL-Database-Size-custom + _SERVICE_ID 411 +} + +define service { + host_name Centreon-central_6 + service_description Connections-Number + register 1 + use App-DB-MySQL-Connections-Number-custom + group_tags 2 + _SERVICE_ID 412 +} + +define service { + host_name Centreon-central_6 + service_description Connection-Time + register 1 + use App-DB-MySQL-Connection-Time-custom + group_tags 2 + _SERVICE_ID 413 +} + +define service { + host_name Centreon-central_6 + service_description Disk-/ + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME / + _SERVICE_ID 414 +} + +define service { + host_name Centreon-central_6 + service_description Disk-/var/lib/mysql + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/mysql + _SERVICE_ID 415 +} + +define service { + host_name Centreon-central_6 + service_description Disk-/var/cache/centreon/backup + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/cache/centreon/backup + _SERVICE_ID 416 +} + +define service { + host_name Centreon-central_6 + service_description Disk-/var/lib/centreon-broker + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/centreon-broker + _SERVICE_ID 417 +} + +define service { + host_name Centreon-central_6 + service_description Disk-/var/log + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/log + _SERVICE_ID 418 +} + +define service { + host_name Centreon-central_6 + service_description Disk-/var/lib/centreon + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/centreon + _SERVICE_ID 419 +} + +define service { + host_name Centreon-central_6 + service_description proc-centreontrapd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME centreontrapd + _PROCESSPATH + _SERVICE_ID 420 +} + +define service { + host_name Centreon-central_6 + service_description proc-snmptrapd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME snmptrapd + _SERVICE_ID 421 +} + +define service { + host_name Centreon-central_6 + service_description proc-rsyslogd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME rsyslogd + _SERVICE_ID 422 +} + +define service { + host_name Centreon-central_6 + service_description proc-snmpd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME snmpd + _SERVICE_ID 423 +} + +define service { + host_name Centreon-central_6 + service_description proc-broker-watchdog + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME cbwd + _SERVICE_ID 424 +} + +define service { + host_name Centreon-central_6 + service_description proc-postfix + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME master + _PROCESSPATH /usr/libexec/postfix/master + _SERVICE_ID 425 +} + +define service { + host_name Centreon-central_6 + service_description Broker-Stats + register 1 + use App-Monitoring-Centreon-Broker-Stats-Central-custom + _SERVICE_ID 426 +} + +define service { + host_name Centreon-central_7 + service_description proc-sshd + register 1 + use App-Monitoring-Centreon-Process-sshd-custom + _SERVICE_ID 427 +} + +define service { + host_name Centreon-central_7 + service_description proc-httpd + register 1 + use App-Monitoring-Centreon-Process-httpd-custom + _SERVICE_ID 428 +} + +define service { + host_name Centreon-central_7 + service_description proc-crond + register 1 + use App-Monitoring-Centreon-Process-crond-custom + _SERVICE_ID 429 +} + +define service { + host_name Centreon-central_7 + service_description proc-centengine + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _SERVICE_ID 430 +} + +define service { + host_name Centreon-central_7 + service_description proc-gorgone + register 1 + use App-Monitoring-Centreon-Process-centcore-custom + _CRITICAL 1: + _PROCESSNAME gorgone-.* + _SERVICE_ID 431 +} + +define service { + host_name Centreon-central_7 + service_description proc-broker-sql + register 1 + use App-Monitoring-Centreon-Process-broker-sql-custom + _PROCESSARGS /etc/centreon-broker/central-broker.json + _SERVICE_ID 432 +} + +define service { + host_name Centreon-central_7 + service_description proc-broker-rrd + register 1 + use App-Monitoring-Centreon-Process-broker-rrd-custom + _PROCESSARGS /etc/centreon-broker/central-rrd.json + _SERVICE_ID 433 +} + +define service { + host_name Centreon-central_7 + service_description Ping + register 1 + use Base-Ping-LAN-custom + _SERVICE_ID 434 +} + +define service { + host_name Centreon-central_7 + service_description Swap + register 1 + use OS-Linux-Swap-SNMP-custom + _SERVICE_ID 435 +} + +define service { + host_name Centreon-central_7 + service_description Memory + register 1 + use OS-Linux-Memory-SNMP-custom + _EXTRAOPTIONS '--redhat' + _SERVICE_ID 436 +} + +define service { + host_name Centreon-central_7 + service_description Load + register 1 + use OS-Linux-Load-SNMP-custom + _SERVICE_ID 437 +} + +define service { + host_name Centreon-central_7 + service_description Cpu + register 1 + use OS-Linux-Cpu-SNMP-custom + _SERVICE_ID 438 +} + +define service { + host_name Centreon-central_7 + service_description Partitioning + notification_interval 1440 + register 1 + use App-Centreon-MySQL-Partitioning-custom + group_tags 2 + _SERVICE_ID 439 +} + +define service { + host_name Centreon-central_7 + service_description Slowqueries + register 1 + use App-DB-MySQL-Slowqueries-custom + group_tags 2 + _SERVICE_ID 440 +} + +define service { + host_name Centreon-central_7 + service_description Queries + register 1 + use App-DB-MySQL-Queries-custom + group_tags 2 + _SERVICE_ID 441 +} + +define service { + host_name Centreon-central_7 + service_description Open-Files + register 1 + use App-DB-MySQL-Open-Files-custom + _SERVICE_ID 442 +} + +define service { + host_name Centreon-central_7 + service_description Myisam-Keycache + register 1 + use App-DB-MySQL-Myisam-Keycache-custom + group_tags 2 + _SERVICE_ID 443 +} + +define service { + host_name Centreon-central_7 + service_description Database-Size + register 1 + use App-DB-MySQL-Database-Size-custom + _SERVICE_ID 444 +} + +define service { + host_name Centreon-central_7 + service_description Connections-Number + register 1 + use App-DB-MySQL-Connections-Number-custom + group_tags 2 + _SERVICE_ID 445 +} + +define service { + host_name Centreon-central_7 + service_description Connection-Time + register 1 + use App-DB-MySQL-Connection-Time-custom + group_tags 2 + _SERVICE_ID 446 +} + +define service { + host_name Centreon-central_7 + service_description Disk-/ + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME / + _SERVICE_ID 447 +} + +define service { + host_name Centreon-central_7 + service_description Disk-/var/lib/mysql + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/mysql + _SERVICE_ID 448 +} + +define service { + host_name Centreon-central_7 + service_description Disk-/var/cache/centreon/backup + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/cache/centreon/backup + _SERVICE_ID 449 +} + +define service { + host_name Centreon-central_7 + service_description Disk-/var/lib/centreon-broker + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/centreon-broker + _SERVICE_ID 450 +} + +define service { + host_name Centreon-central_7 + service_description Disk-/var/log + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/log + _SERVICE_ID 451 +} + +define service { + host_name Centreon-central_7 + service_description Disk-/var/lib/centreon + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/centreon + _SERVICE_ID 452 +} + +define service { + host_name Centreon-central_7 + service_description proc-centreontrapd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME centreontrapd + _PROCESSPATH + _SERVICE_ID 453 +} + +define service { + host_name Centreon-central_7 + service_description proc-snmptrapd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME snmptrapd + _SERVICE_ID 454 +} + +define service { + host_name Centreon-central_7 + service_description proc-rsyslogd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME rsyslogd + _SERVICE_ID 455 +} + +define service { + host_name Centreon-central_7 + service_description proc-snmpd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME snmpd + _SERVICE_ID 456 +} + +define service { + host_name Centreon-central_7 + service_description proc-broker-watchdog + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME cbwd + _SERVICE_ID 457 +} + +define service { + host_name Centreon-central_7 + service_description proc-postfix + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME master + _PROCESSPATH /usr/libexec/postfix/master + _SERVICE_ID 458 +} + +define service { + host_name Centreon-central_7 + service_description Broker-Stats + register 1 + use App-Monitoring-Centreon-Broker-Stats-Central-custom + _SERVICE_ID 459 +} + +define service { + host_name Centreon-central_8 + service_description proc-sshd + register 1 + use App-Monitoring-Centreon-Process-sshd-custom + _SERVICE_ID 460 +} + +define service { + host_name Centreon-central_8 + service_description proc-httpd + register 1 + use App-Monitoring-Centreon-Process-httpd-custom + _SERVICE_ID 461 +} + +define service { + host_name Centreon-central_8 + service_description proc-crond + register 1 + use App-Monitoring-Centreon-Process-crond-custom + _SERVICE_ID 462 +} + +define service { + host_name Centreon-central_8 + service_description proc-centengine + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _SERVICE_ID 463 +} + +define service { + host_name Centreon-central_8 + service_description proc-gorgone + register 1 + use App-Monitoring-Centreon-Process-centcore-custom + _CRITICAL 1: + _PROCESSNAME gorgone-.* + _SERVICE_ID 464 +} + +define service { + host_name Centreon-central_8 + service_description proc-broker-sql + register 1 + use App-Monitoring-Centreon-Process-broker-sql-custom + _PROCESSARGS /etc/centreon-broker/central-broker.json + _SERVICE_ID 465 +} + +define service { + host_name Centreon-central_8 + service_description proc-broker-rrd + register 1 + use App-Monitoring-Centreon-Process-broker-rrd-custom + _PROCESSARGS /etc/centreon-broker/central-rrd.json + _SERVICE_ID 466 +} + +define service { + host_name Centreon-central_8 + service_description Ping + register 1 + use Base-Ping-LAN-custom + _SERVICE_ID 467 +} + +define service { + host_name Centreon-central_8 + service_description Swap + register 1 + use OS-Linux-Swap-SNMP-custom + _SERVICE_ID 468 +} + +define service { + host_name Centreon-central_8 + service_description Memory + register 1 + use OS-Linux-Memory-SNMP-custom + _EXTRAOPTIONS '--redhat' + _SERVICE_ID 469 +} + +define service { + host_name Centreon-central_8 + service_description Load + register 1 + use OS-Linux-Load-SNMP-custom + _SERVICE_ID 470 +} + +define service { + host_name Centreon-central_8 + service_description Cpu + register 1 + use OS-Linux-Cpu-SNMP-custom + _SERVICE_ID 471 +} + +define service { + host_name Centreon-central_8 + service_description Partitioning + notification_interval 1440 + register 1 + use App-Centreon-MySQL-Partitioning-custom + group_tags 2 + _SERVICE_ID 472 +} + +define service { + host_name Centreon-central_8 + service_description Slowqueries + register 1 + use App-DB-MySQL-Slowqueries-custom + group_tags 2 + _SERVICE_ID 473 +} + +define service { + host_name Centreon-central_8 + service_description Queries + register 1 + use App-DB-MySQL-Queries-custom + group_tags 2 + _SERVICE_ID 474 +} + +define service { + host_name Centreon-central_8 + service_description Open-Files + register 1 + use App-DB-MySQL-Open-Files-custom + _SERVICE_ID 475 +} + +define service { + host_name Centreon-central_8 + service_description Myisam-Keycache + register 1 + use App-DB-MySQL-Myisam-Keycache-custom + group_tags 2 + _SERVICE_ID 476 +} + +define service { + host_name Centreon-central_8 + service_description Database-Size + register 1 + use App-DB-MySQL-Database-Size-custom + _SERVICE_ID 477 +} + +define service { + host_name Centreon-central_8 + service_description Connections-Number + register 1 + use App-DB-MySQL-Connections-Number-custom + group_tags 2 + _SERVICE_ID 478 +} + +define service { + host_name Centreon-central_8 + service_description Connection-Time + register 1 + use App-DB-MySQL-Connection-Time-custom + group_tags 2 + _SERVICE_ID 479 +} + +define service { + host_name Centreon-central_8 + service_description Disk-/ + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME / + _SERVICE_ID 480 +} + +define service { + host_name Centreon-central_8 + service_description Disk-/var/lib/mysql + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/mysql + _SERVICE_ID 481 +} + +define service { + host_name Centreon-central_8 + service_description Disk-/var/cache/centreon/backup + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/cache/centreon/backup + _SERVICE_ID 482 +} + +define service { + host_name Centreon-central_8 + service_description Disk-/var/lib/centreon-broker + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/centreon-broker + _SERVICE_ID 483 +} + +define service { + host_name Centreon-central_8 + service_description Disk-/var/log + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/log + _SERVICE_ID 484 +} + +define service { + host_name Centreon-central_8 + service_description Disk-/var/lib/centreon + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/centreon + _SERVICE_ID 485 +} + +define service { + host_name Centreon-central_8 + service_description proc-centreontrapd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME centreontrapd + _PROCESSPATH + _SERVICE_ID 486 +} + +define service { + host_name Centreon-central_8 + service_description proc-snmptrapd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME snmptrapd + _SERVICE_ID 487 +} + +define service { + host_name Centreon-central_8 + service_description proc-rsyslogd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME rsyslogd + _SERVICE_ID 488 +} + +define service { + host_name Centreon-central_8 + service_description proc-snmpd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME snmpd + _SERVICE_ID 489 +} + +define service { + host_name Centreon-central_8 + service_description proc-broker-watchdog + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME cbwd + _SERVICE_ID 490 +} + +define service { + host_name Centreon-central_8 + service_description proc-postfix + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME master + _PROCESSPATH /usr/libexec/postfix/master + _SERVICE_ID 491 +} + +define service { + host_name Centreon-central_8 + service_description Broker-Stats + register 1 + use App-Monitoring-Centreon-Broker-Stats-Central-custom + _SERVICE_ID 492 +} + +define service { + host_name Centreon-central_9 + service_description proc-sshd + register 1 + use App-Monitoring-Centreon-Process-sshd-custom + _SERVICE_ID 493 +} + +define service { + host_name Centreon-central_9 + service_description proc-httpd + register 1 + use App-Monitoring-Centreon-Process-httpd-custom + _SERVICE_ID 494 +} + +define service { + host_name Centreon-central_9 + service_description proc-crond + register 1 + use App-Monitoring-Centreon-Process-crond-custom + _SERVICE_ID 495 +} + +define service { + host_name Centreon-central_9 + service_description proc-centengine + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _SERVICE_ID 496 +} + +define service { + host_name Centreon-central_9 + service_description proc-gorgone + register 1 + use App-Monitoring-Centreon-Process-centcore-custom + _CRITICAL 1: + _PROCESSNAME gorgone-.* + _SERVICE_ID 497 +} + +define service { + host_name Centreon-central_9 + service_description proc-broker-sql + register 1 + use App-Monitoring-Centreon-Process-broker-sql-custom + _PROCESSARGS /etc/centreon-broker/central-broker.json + _SERVICE_ID 498 +} + +define service { + host_name Centreon-central_9 + service_description proc-broker-rrd + register 1 + use App-Monitoring-Centreon-Process-broker-rrd-custom + _PROCESSARGS /etc/centreon-broker/central-rrd.json + _SERVICE_ID 499 +} + +define service { + host_name Centreon-central_9 + service_description Ping + register 1 + use Base-Ping-LAN-custom + _SERVICE_ID 500 +} + +define service { + host_name Centreon-central_9 + service_description Swap + register 1 + use OS-Linux-Swap-SNMP-custom + _SERVICE_ID 501 +} + +define service { + host_name Centreon-central_9 + service_description Memory + register 1 + use OS-Linux-Memory-SNMP-custom + _EXTRAOPTIONS '--redhat' + _SERVICE_ID 502 +} + +define service { + host_name Centreon-central_9 + service_description Load + register 1 + use OS-Linux-Load-SNMP-custom + _SERVICE_ID 503 +} + +define service { + host_name Centreon-central_9 + service_description Cpu + register 1 + use OS-Linux-Cpu-SNMP-custom + _SERVICE_ID 504 +} + +define service { + host_name Centreon-central_9 + service_description Partitioning + notification_interval 1440 + register 1 + use App-Centreon-MySQL-Partitioning-custom + group_tags 2 + _SERVICE_ID 505 +} + +define service { + host_name Centreon-central_9 + service_description Slowqueries + register 1 + use App-DB-MySQL-Slowqueries-custom + group_tags 2 + _SERVICE_ID 506 +} + +define service { + host_name Centreon-central_9 + service_description Queries + register 1 + use App-DB-MySQL-Queries-custom + group_tags 2 + _SERVICE_ID 507 +} + +define service { + host_name Centreon-central_9 + service_description Open-Files + register 1 + use App-DB-MySQL-Open-Files-custom + _SERVICE_ID 508 +} + +define service { + host_name Centreon-central_9 + service_description Myisam-Keycache + register 1 + use App-DB-MySQL-Myisam-Keycache-custom + group_tags 2 + _SERVICE_ID 509 +} + +define service { + host_name Centreon-central_9 + service_description Database-Size + register 1 + use App-DB-MySQL-Database-Size-custom + _SERVICE_ID 510 +} + +define service { + host_name Centreon-central_9 + service_description Connections-Number + register 1 + use App-DB-MySQL-Connections-Number-custom + group_tags 2 + _SERVICE_ID 511 +} + +define service { + host_name Centreon-central_9 + service_description Connection-Time + register 1 + use App-DB-MySQL-Connection-Time-custom + group_tags 2 + _SERVICE_ID 512 +} + +define service { + host_name Centreon-central_9 + service_description Disk-/ + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME / + _SERVICE_ID 513 +} + +define service { + host_name Centreon-central_9 + service_description Disk-/var/lib/mysql + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/mysql + _SERVICE_ID 514 +} + +define service { + host_name Centreon-central_9 + service_description Disk-/var/cache/centreon/backup + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/cache/centreon/backup + _SERVICE_ID 515 +} + +define service { + host_name Centreon-central_9 + service_description Disk-/var/lib/centreon-broker + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/centreon-broker + _SERVICE_ID 516 +} + +define service { + host_name Centreon-central_9 + service_description Disk-/var/log + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/log + _SERVICE_ID 517 +} + +define service { + host_name Centreon-central_9 + service_description Disk-/var/lib/centreon + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/centreon + _SERVICE_ID 518 +} + +define service { + host_name Centreon-central_9 + service_description proc-centreontrapd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME centreontrapd + _PROCESSPATH + _SERVICE_ID 519 +} + +define service { + host_name Centreon-central_9 + service_description proc-snmptrapd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME snmptrapd + _SERVICE_ID 520 +} + +define service { + host_name Centreon-central_9 + service_description proc-rsyslogd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME rsyslogd + _SERVICE_ID 521 +} + +define service { + host_name Centreon-central_9 + service_description proc-snmpd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME snmpd + _SERVICE_ID 522 +} + +define service { + host_name Centreon-central_9 + service_description proc-broker-watchdog + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME cbwd + _SERVICE_ID 523 +} + +define service { + host_name Centreon-central_9 + service_description proc-postfix + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME master + _PROCESSPATH /usr/libexec/postfix/master + _SERVICE_ID 524 +} + +define service { + host_name Centreon-central_9 + service_description Broker-Stats + register 1 + use App-Monitoring-Centreon-Broker-Stats-Central-custom + _SERVICE_ID 525 +} + +define service { + host_name Centreon-central_10 + service_description proc-sshd + register 1 + use App-Monitoring-Centreon-Process-sshd-custom + _SERVICE_ID 526 +} + +define service { + host_name Centreon-central_10 + service_description proc-httpd + register 1 + use App-Monitoring-Centreon-Process-httpd-custom + _SERVICE_ID 527 +} + +define service { + host_name Centreon-central_10 + service_description proc-crond + register 1 + use App-Monitoring-Centreon-Process-crond-custom + _SERVICE_ID 528 +} + +define service { + host_name Centreon-central_10 + service_description proc-centengine + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _SERVICE_ID 529 +} + +define service { + host_name Centreon-central_10 + service_description proc-gorgone + register 1 + use App-Monitoring-Centreon-Process-centcore-custom + _CRITICAL 1: + _PROCESSNAME gorgone-.* + _SERVICE_ID 530 +} + +define service { + host_name Centreon-central_10 + service_description proc-broker-sql + register 1 + use App-Monitoring-Centreon-Process-broker-sql-custom + _PROCESSARGS /etc/centreon-broker/central-broker.json + _SERVICE_ID 531 +} + +define service { + host_name Centreon-central_10 + service_description proc-broker-rrd + register 1 + use App-Monitoring-Centreon-Process-broker-rrd-custom + _PROCESSARGS /etc/centreon-broker/central-rrd.json + _SERVICE_ID 532 +} + +define service { + host_name Centreon-central_10 + service_description Ping + register 1 + use Base-Ping-LAN-custom + _SERVICE_ID 533 +} + +define service { + host_name Centreon-central_10 + service_description Swap + register 1 + use OS-Linux-Swap-SNMP-custom + _SERVICE_ID 534 +} + +define service { + host_name Centreon-central_10 + service_description Memory + register 1 + use OS-Linux-Memory-SNMP-custom + _EXTRAOPTIONS '--redhat' + _SERVICE_ID 535 +} + +define service { + host_name Centreon-central_10 + service_description Load + register 1 + use OS-Linux-Load-SNMP-custom + _SERVICE_ID 536 +} + +define service { + host_name Centreon-central_10 + service_description Cpu + register 1 + use OS-Linux-Cpu-SNMP-custom + _SERVICE_ID 537 +} + +define service { + host_name Centreon-central_10 + service_description Partitioning + notification_interval 1440 + register 1 + use App-Centreon-MySQL-Partitioning-custom + group_tags 2 + _SERVICE_ID 538 +} + +define service { + host_name Centreon-central_10 + service_description Slowqueries + register 1 + use App-DB-MySQL-Slowqueries-custom + group_tags 2 + _SERVICE_ID 539 +} + +define service { + host_name Centreon-central_10 + service_description Queries + register 1 + use App-DB-MySQL-Queries-custom + group_tags 2 + _SERVICE_ID 540 +} + +define service { + host_name Centreon-central_10 + service_description Open-Files + register 1 + use App-DB-MySQL-Open-Files-custom + _SERVICE_ID 541 +} + +define service { + host_name Centreon-central_10 + service_description Myisam-Keycache + register 1 + use App-DB-MySQL-Myisam-Keycache-custom + group_tags 2 + _SERVICE_ID 542 +} + +define service { + host_name Centreon-central_10 + service_description Database-Size + register 1 + use App-DB-MySQL-Database-Size-custom + _SERVICE_ID 543 +} + +define service { + host_name Centreon-central_10 + service_description Connections-Number + register 1 + use App-DB-MySQL-Connections-Number-custom + group_tags 2 + _SERVICE_ID 544 +} + +define service { + host_name Centreon-central_10 + service_description Connection-Time + register 1 + use App-DB-MySQL-Connection-Time-custom + group_tags 2 + _SERVICE_ID 545 +} + +define service { + host_name Centreon-central_10 + service_description Disk-/ + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME / + _SERVICE_ID 546 +} + +define service { + host_name Centreon-central_10 + service_description Disk-/var/lib/mysql + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/mysql + _SERVICE_ID 547 +} + +define service { + host_name Centreon-central_10 + service_description Disk-/var/cache/centreon/backup + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/cache/centreon/backup + _SERVICE_ID 548 +} + +define service { + host_name Centreon-central_10 + service_description Disk-/var/lib/centreon-broker + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/centreon-broker + _SERVICE_ID 549 +} + +define service { + host_name Centreon-central_10 + service_description Disk-/var/log + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/log + _SERVICE_ID 550 +} + +define service { + host_name Centreon-central_10 + service_description Disk-/var/lib/centreon + register 1 + use OS-Linux-Disk-Generic-Name-SNMP-custom + _DISKNAME /var/lib/centreon + _SERVICE_ID 551 +} + +define service { + host_name Centreon-central_10 + service_description proc-centreontrapd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME centreontrapd + _PROCESSPATH + _SERVICE_ID 552 +} + +define service { + host_name Centreon-central_10 + service_description proc-snmptrapd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME snmptrapd + _SERVICE_ID 553 +} + +define service { + host_name Centreon-central_10 + service_description proc-rsyslogd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME rsyslogd + _SERVICE_ID 554 +} + +define service { + host_name Centreon-central_10 + service_description proc-snmpd + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME snmpd + _SERVICE_ID 555 +} + +define service { + host_name Centreon-central_10 + service_description proc-broker-watchdog + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME cbwd + _SERVICE_ID 556 +} + +define service { + host_name Centreon-central_10 + service_description proc-postfix + register 1 + use App-Monitoring-Centreon-Process-centengine-custom + _PROCESSNAME master + _PROCESSPATH /usr/libexec/postfix/master + _SERVICE_ID 557 +} + +define service { + host_name Centreon-central_10 + service_description Broker-Stats + register 1 + use App-Monitoring-Centreon-Broker-Stats-Central-custom + _SERVICE_ID 558 +} diff --git a/engine/tests/cfg_files/conf1/severities.cfg b/engine/tests/cfg_files/conf1/severities.cfg new file mode 100644 index 00000000000..e3a700cfb14 --- /dev/null +++ b/engine/tests/cfg_files/conf1/severities.cfg @@ -0,0 +1,32 @@ +################################################################### +# # +# GENERATED BY CENTREON # +# # +# Developed by : # +# - Julien Mathis # +# - Romain Le Merlus # +# # +# www.centreon.com # +# For information : contact@centreon.com # +################################################################### +# # +# Last modification 2023-05-31 18:43 # +# By John_Doe # +# # +################################################################### + +define severity { + id 6 + severity_name severity2 + level 2 + icon_id 2 + type service +} + +define severity { + id 5 + severity_name severity1 + level 1 + icon_id 3 + type service +} diff --git a/engine/tests/cfg_files/conf1/tags.cfg b/engine/tests/cfg_files/conf1/tags.cfg new file mode 100644 index 00000000000..a4a247dd93c --- /dev/null +++ b/engine/tests/cfg_files/conf1/tags.cfg @@ -0,0 +1,34 @@ +################################################################### +# # +# GENERATED BY CENTREON # +# # +# Developed by : # +# - Julien Mathis # +# - Romain Le Merlus # +# # +# www.centreon.com # +# For information : contact@centreon.com # +################################################################### +# # +# Last modification 2023-05-31 18:43 # +# By John_Doe # +# # +################################################################### + +define tag { + id 3 + tag_name hg1 + type hostgroup +} + +define tag { + id 2 + tag_name Centreon_platform + type hostgroup +} + +define tag { + id 2 + tag_name Database-MySQL + type servicegroup +} diff --git a/engine/tests/cfg_files/conf1/timeperiods.cfg b/engine/tests/cfg_files/conf1/timeperiods.cfg new file mode 100644 index 00000000000..6ef1dd017b7 --- /dev/null +++ b/engine/tests/cfg_files/conf1/timeperiods.cfg @@ -0,0 +1,42 @@ +################################################################### +# # +# GENERATED BY CENTREON # +# # +# Developed by : # +# - Julien Mathis # +# - Romain Le Merlus # +# # +# www.centreon.com # +# For information : contact@centreon.com # +################################################################### +# # +# Last modification 2023-05-31 18:43 # +# By John_Doe # +# # +################################################################### + +define timeperiod { + name 24x7 + timeperiod_name 24x7 + alias 24_Hours_A_Day,_7_Days_A_Week + sunday 00:00-24:00 + monday 00:00-24:00 + tuesday 00:00-24:00 + wednesday 00:00-24:00 + thursday 00:00-24:00 + friday 00:00-24:00 + saturday 00:00-24:00 +} + +define timeperiod { + name nonworkhours + timeperiod_name nonworkhours + alias Non-Work Hours + sunday 00:00-24:00 + monday 00:00-09:00,17:00-24:00 + tuesday 00:00-09:00,17:00-24:00 + wednesday 00:00-09:00,17:00-24:00 + thursday 00:00-09:00,17:00-24:00 + friday 00:00-09:00,17:00-24:00 + saturday 00:00-24:00 +} diff --git a/engine/tests/checks/anomalydetection.cc b/engine/tests/checks/anomalydetection.cc index 973241f2cef..27e7984fd6d 100644 --- a/engine/tests/checks/anomalydetection.cc +++ b/engine/tests/checks/anomalydetection.cc @@ -36,8 +36,10 @@ #include "com/centreon/engine/configuration/applier/servicedependency.hh" #include "com/centreon/engine/exceptions/error.hh" #include "com/centreon/engine/globals.hh" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/host.hh" #include "common/engine_legacy_conf/service.hh" +#endif #include "helper.hh" using namespace com::centreon; diff --git a/engine/tests/checks/pb_anomalydetection.cc b/engine/tests/checks/pb_anomalydetection.cc new file mode 100644 index 00000000000..3463de27033 --- /dev/null +++ b/engine/tests/checks/pb_anomalydetection.cc @@ -0,0 +1,1106 @@ +/* + * Copyright 2023 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include "com/centreon/engine/configuration/applier/anomalydetection.hh" + +#include <fmt/format.h> +#include <gtest/gtest.h> + +#include <cstring> + +#include "../test_engine.hh" +#include "../timeperiod/utils.hh" +#include "com/centreon/engine/anomalydetection.hh" +#include "com/centreon/engine/checks/checker.hh" +#include "com/centreon/engine/commands/commands.hh" +#include "com/centreon/engine/configuration/applier/contact.hh" +#include "com/centreon/engine/configuration/applier/contactgroup.hh" +#include "com/centreon/engine/configuration/applier/host.hh" +#include "com/centreon/engine/configuration/applier/service.hh" +#include "com/centreon/engine/configuration/applier/servicedependency.hh" +#include "com/centreon/engine/globals.hh" +#include "common/engine_conf/message_helper.hh" +#include "helper.hh" + +using namespace com::centreon; +using namespace com::centreon::engine; +using namespace com::centreon::engine::configuration; +using namespace com::centreon::engine::configuration::applier; + +class PbAnomalydetectionCheck : public TestEngine { + public: + void SetUp() override { + init_config_state(); + + init_loggers(); + checks_logger->set_level(spdlog::level::trace); + commands_logger->set_level(spdlog::level::trace); + + configuration::applier::contact ct_aply; + configuration::Contact ctct{new_pb_configuration_contact("admin", true)}; + ct_aply.add_object(ctct); + ct_aply.expand_objects(pb_config); + configuration::error_cnt err; + ct_aply.resolve_object(ctct, err); + + configuration::Host hst{new_pb_configuration_host("test_host", "admin")}; + configuration::applier::host hst_aply; + hst_aply.add_object(hst); + + configuration::Service svc{ + new_pb_configuration_service("test_host", "test_svc", "admin", 8)}; + configuration::applier::service svc_aply; + svc_aply.add_object(svc); + + hst_aply.resolve_object(hst, err); + svc_aply.resolve_object(svc, err); + + configuration::Anomalydetection ad{new_pb_configuration_anomalydetection( + "test_host", "test_ad", "admin", 9, 8, + "/tmp/thresholds_status_change.json")}; + configuration::applier::anomalydetection ad_aply; + ad_aply.add_object(ad); + + ad_aply.resolve_object(ad, err); + + host_map const& hm{engine::host::hosts}; + _host = hm.begin()->second; + _host->set_current_state(engine::host::state_up); + _host->set_state_type(checkable::hard); + _host->set_acknowledgement(AckType::NONE); + _host->set_notify_on(static_cast<uint32_t>(-1)); + + service_map const& sm{engine::service::services}; + for (auto& p : sm) { + std::shared_ptr<engine::service> svc = p.second; + if (svc->service_id() == 8) + _svc = svc; + else + _ad = std::static_pointer_cast<engine::anomalydetection>(svc); + } + _svc->set_current_state(engine::service::state_ok); + _svc->set_state_type(checkable::hard); + _svc->set_acknowledgement(AckType::NONE); + _svc->set_notify_on(static_cast<uint32_t>(-1)); + } + + void TearDown() override { + _host.reset(); + _svc.reset(); + _ad.reset(); + deinit_config_state(); + } + + void CreateFile(std::string const& filename, std::string const& content) { + std::ofstream oss(filename); + oss << content; + oss.close(); + } + + protected: + std::shared_ptr<engine::host> _host; + std::shared_ptr<engine::service> _svc; + std::shared_ptr<engine::anomalydetection> _ad; +}; + +// clang-format off + +/* The following test comes from this array (inherited from Nagios behaviour): + * + * | Time | Check # | State | State type | State change | perf data in range | ad State | ad state type | ad do check + * -------------------------------------------------------------------------------------------------------------------- + * | 0 | 1 | OK | HARD | No | Y | OK | H | N + * | 1 | 1 | CRTCL | SOFT | Yes | Y | OK | H | N + * | 2 | 2 | CRTCL | SOFT | No | Y | OK | H | N + * | 3 | 3 | CRTCL | HARD | Yes | Y | OK | H | N + * | 4 | 3 | OK | HARD | Yes | Y | OK | H | N + * | 5 | 3 | OK | HARD | No | N | CRTCL | S | Y + * | 6 | 1 | OK | HARD | No | N | CRTCL | S | Y + * | 7 | 1 | OK | HARD | No | N | CRTCL | H | Y + * | 8 | 1 | OK | HARD | No | Y | OK | H | Y + * | 9 | 1 | OK | HARD | No | Y | OK | H | N + * -------------------------------------------------------------------------------------------------------------------- + */ + +// clang-format on + +enum class e_json_version { V1, V2 }; + +class PbAnomalydetectionCheckStatusChange + : public PbAnomalydetectionCheck, + public testing::WithParamInterface< + std::pair<e_json_version, const char*>> {}; + +TEST_P(PbAnomalydetectionCheckStatusChange, StatusChanges) { + CreateFile("/tmp/thresholds_status_change.json", GetParam().second); + _ad->init_thresholds(); + _ad->set_status_change(true); + + set_time(50000); + _svc->set_current_state(engine::service::state_ok); + _svc->set_last_hard_state(engine::service::state_ok); + _svc->set_last_hard_state_change(50000); + _svc->set_state_type(checkable::hard); + _svc->set_accept_passive_checks(true); + _svc->set_current_attempt(1); + _svc->set_last_check(50000); + + _ad->set_current_state(engine::service::state_ok); + _ad->set_last_hard_state(engine::service::state_ok); + _ad->set_last_hard_state_change(50000); + _ad->set_last_state_change(50000); + _ad->set_state_type(checkable::hard); + _ad->set_current_attempt(1); + _ad->set_last_check(50000); + + // --- 1 ---- + set_time(50500); + time_t now = std::time(nullptr); + std::string cmd(fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;service " + "critical| metric=80;25;60", + now)); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::soft); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 1); + ASSERT_EQ(_svc->get_plugin_output(), "service critical"); + ASSERT_EQ(_svc->get_perf_data(), "metric=80;25;60"); + int check_options = 0; + int latency = 0; + bool time_is_valid; + time_t preferred_time; + _ad->run_async_check(check_options, latency, true, true, &time_is_valid, + &preferred_time); + checks::checker::instance().reap(); + ASSERT_EQ(_ad->get_state_type(), checkable::hard); + ASSERT_EQ(_ad->get_current_state(), engine::service::state_ok); + ASSERT_EQ(_ad->get_last_state_change(), 50000); + ASSERT_EQ(_ad->get_current_attempt(), 1); + ASSERT_EQ(_ad->get_plugin_output(), "OK: Regular activity, metric=80.00"); + if (GetParam().first == e_json_version::V1) { + ASSERT_EQ(_ad->get_perf_data(), + "metric=80 metric_lower_thresholds=73.31 " + "metric_upper_thresholds=83.26 metric_fit=78.26 " + "metric_lower_margin=0.00 metric_upper_margin=0.00"); + } else { + ASSERT_EQ(_ad->get_perf_data(), + "metric=80 metric_lower_thresholds=73.31 " + "metric_upper_thresholds=83.26 metric_fit=78.26 " + "metric_lower_margin=-4.95 metric_upper_margin=5.00"); + } + + // --- 2 ---- + set_time(51000); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;service " + "critical| metric=80;25;60", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::soft); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_state_change(), 50500); + ASSERT_EQ(_svc->get_current_attempt(), 2); + _ad->run_async_check(check_options, latency, true, true, &time_is_valid, + &preferred_time); + checks::checker::instance().reap(); + ASSERT_EQ(_ad->get_state_type(), checkable::hard); + ASSERT_EQ(_ad->get_current_state(), engine::service::state_ok); + ASSERT_EQ(_ad->get_last_state_change(), 50000); + ASSERT_EQ(_ad->get_current_attempt(), 1); + ASSERT_EQ(_ad->get_plugin_output(), "OK: Regular activity, metric=80.00"); + if (GetParam().first == e_json_version::V1) { + ASSERT_EQ(_ad->get_perf_data(), + "metric=80 metric_lower_thresholds=72.62 " + "metric_upper_thresholds=82.52 metric_fit=77.52 " + "metric_lower_margin=0.00 metric_upper_margin=0.00"); + } else { + ASSERT_EQ(_ad->get_perf_data(), + "metric=80 metric_lower_thresholds=72.62 " + "metric_upper_thresholds=82.52 metric_fit=77.52 " + "metric_lower_margin=-4.90 metric_upper_margin=5.00"); + } + // --- 3 ---- + set_time(51250); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;service " + "critical| metric=80;25;60", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_state_change(), 50500); + ASSERT_EQ(_svc->get_current_attempt(), 3); + _ad->run_async_check(check_options, latency, true, true, &time_is_valid, + &preferred_time); + checks::checker::instance().reap(); + ASSERT_EQ(_ad->get_state_type(), checkable::hard); + ASSERT_EQ(_ad->get_current_state(), engine::service::state_ok); + ASSERT_EQ(_ad->get_last_state_change(), 50000); + ASSERT_EQ(_ad->get_current_attempt(), 1); + ASSERT_EQ(_ad->get_plugin_output(), "OK: Regular activity, metric=80.00"); + if (GetParam().first == e_json_version::V1) { + ASSERT_EQ(_ad->get_perf_data(), + "metric=80 metric_lower_thresholds=72.28 " + "metric_upper_thresholds=82.15 metric_fit=77.15 " + "metric_lower_margin=0.00 metric_upper_margin=0.00"); + } else { + ASSERT_EQ(_ad->get_perf_data(), + "metric=80 metric_lower_thresholds=72.28 " + "metric_upper_thresholds=82.15 metric_fit=77.15 " + "metric_lower_margin=-4.88 metric_upper_margin=5.00"); + } + // --- 4 ---- + set_time(52000); + + now = std::time(nullptr); + time_t previous = now; + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;0;service " + "ok| metric=80foo;25;60", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_ok); + ASSERT_EQ(_svc->get_last_hard_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 1); + _ad->run_async_check(check_options, latency, true, true, &time_is_valid, + &preferred_time); + checks::checker::instance().reap(); + ASSERT_EQ(_ad->get_state_type(), checkable::hard); + ASSERT_EQ(_ad->get_current_state(), engine::service::state_ok); + ASSERT_EQ(_ad->get_last_state_change(), 50000); + ASSERT_EQ(_ad->get_current_attempt(), 1); + ASSERT_EQ(_ad->get_plugin_output(), "OK: Regular activity, metric=80.00foo"); + if (GetParam().first == e_json_version::V1) { + ASSERT_EQ(_ad->get_perf_data(), + "metric=80foo metric_lower_thresholds=71.24foo " + "metric_upper_thresholds=81.04foo metric_fit=76.04foo " + "metric_lower_margin=0.00foo metric_upper_margin=0.00foo"); + } else { + ASSERT_EQ(_ad->get_perf_data(), + "metric=80foo metric_lower_thresholds=71.24foo " + "metric_upper_thresholds=81.04foo metric_fit=76.04foo " + "metric_lower_margin=-4.80foo metric_upper_margin=5.00foo"); + } + // --- 5 ---- + set_time(52500); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;0;service ok| " + "metric=30%;25;60", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_ok); + ASSERT_EQ(_svc->get_last_hard_state_change(), 52000); + ASSERT_EQ(_svc->get_last_state_change(), 52000); + ASSERT_EQ(_svc->get_current_attempt(), 1); + _ad->run_async_check(check_options, latency, true, true, &time_is_valid, + &preferred_time); + checks::checker::instance().reap(); + ASSERT_EQ(_ad->get_state_type(), checkable::soft); + ASSERT_EQ(_ad->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_ad->get_last_state_change(), now); + ASSERT_EQ(_ad->get_plugin_output(), + "NON-OK: Unusual activity, the actual value of metric is 30.00% " + "which is outside the forecasting range [70.55% : 80.30%]"); + if (GetParam().first == e_json_version::V1) { + ASSERT_EQ(_ad->get_perf_data(), + "metric=30% metric_lower_thresholds=70.55% " + "metric_upper_thresholds=80.30% metric_fit=75.30% " + "metric_lower_margin=0.00% metric_upper_margin=0.00%"); + } else { + ASSERT_EQ(_ad->get_perf_data(), + "metric=30% metric_lower_thresholds=70.55% " + "metric_upper_thresholds=80.30% metric_fit=75.30% " + "metric_lower_margin=-4.75% metric_upper_margin=5.00%"); + } + + ASSERT_EQ(_ad->get_current_attempt(), 1); + + // --- 6 ---- + set_time(53000); + + previous = now; + now = std::time(nullptr); + _ad->run_async_check(check_options, latency, true, true, &time_is_valid, + &preferred_time); + checks::checker::instance().wait_completion( + checks::checker::e_completion_filter::service); + checks::checker::instance().reap(); + ASSERT_EQ(_ad->get_state_type(), checkable::soft); + ASSERT_EQ(_ad->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_ad->get_last_state_change(), previous); + ASSERT_EQ(_ad->get_plugin_output(), + "NON-OK: Unusual activity, the actual value of metric is 12.00 " + "which is outside the forecasting range [69.86 : 79.56]"); + if (GetParam().first == e_json_version::V1) { + ASSERT_EQ(_ad->get_perf_data(), + "metric=12 metric_lower_thresholds=69.86 " + "metric_upper_thresholds=79.56 metric_fit=74.56 " + "metric_lower_margin=0.00 metric_upper_margin=0.00"); + + } else { + ASSERT_EQ(_ad->get_perf_data(), + "metric=12 metric_lower_thresholds=69.86 " + "metric_upper_thresholds=79.56 metric_fit=74.56 " + "metric_lower_margin=-4.70 metric_upper_margin=5.00"); + } + ASSERT_EQ(_ad->get_current_attempt(), 2); + + // --- 7 ---- + set_time(53500); + + previous = now; + now = std::time(nullptr); + _ad->run_async_check(check_options, latency, true, true, &time_is_valid, + &preferred_time); + checks::checker::instance().wait_completion( + checks::checker::e_completion_filter::service); + checks::checker::instance().reap(); + ASSERT_EQ(_ad->get_state_type(), checkable::hard); + ASSERT_EQ(_ad->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_ad->get_last_hard_state_change(), now); + ASSERT_EQ(_ad->get_last_state_change(), 52500); + ASSERT_EQ(_ad->get_plugin_output(), + "NON-OK: Unusual activity, the actual value of metric is 12.00 " + "which is outside the forecasting range [69.17 : 78.82]"); + if (GetParam().first == e_json_version::V1) { + ASSERT_EQ(_ad->get_perf_data(), + "metric=12 metric_lower_thresholds=69.17 " + "metric_upper_thresholds=78.82 metric_fit=73.82 " + "metric_lower_margin=0.00 metric_upper_margin=0.00"); + } else { + ASSERT_EQ(_ad->get_perf_data(), + "metric=12 metric_lower_thresholds=69.17 " + "metric_upper_thresholds=78.82 metric_fit=73.82 " + "metric_lower_margin=-4.65 metric_upper_margin=5.00"); + } + ASSERT_EQ(_ad->get_current_attempt(), 3); + + // --- 8 ---- + set_time(54000); + _ad->get_check_command_ptr()->set_command_line( + "echo 'output| metric=70%;50;75'"); + previous = now; + now = std::time(nullptr); + _ad->run_async_check(check_options, latency, true, true, &time_is_valid, + &preferred_time); + checks::checker::instance().wait_completion( + checks::checker::e_completion_filter::service); + checks::checker::instance().reap(); + ASSERT_EQ(_ad->get_state_type(), checkable::hard); + ASSERT_EQ(_ad->get_current_state(), engine::service::state_ok); + ASSERT_EQ(_ad->get_last_hard_state_change(), now); + ASSERT_EQ(_ad->get_last_state_change(), now); + ASSERT_EQ(_ad->get_plugin_output(), "OK: Regular activity, metric=70.00%"); + if (GetParam().first == e_json_version::V1) { + ASSERT_EQ(_ad->get_perf_data(), + "metric=70% metric_lower_thresholds=68.48% " + "metric_upper_thresholds=78.08% metric_fit=73.08% " + "metric_lower_margin=0.00% metric_upper_margin=0.00%"); + } else { + ASSERT_EQ(_ad->get_perf_data(), + "metric=70% metric_lower_thresholds=68.48% " + "metric_upper_thresholds=78.08% metric_fit=73.08% " + "metric_lower_margin=-4.60% metric_upper_margin=5.00%"); + } + ASSERT_EQ(_ad->get_current_attempt(), 1); + + // --- 9 ---- + set_time(54500); + + previous = now; + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;4;service unknown", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::soft); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_unknown); + ASSERT_EQ(_svc->get_last_hard_state_change(), 52000); + ASSERT_EQ(_svc->get_last_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 1); + ASSERT_EQ(_svc->get_plugin_output(), "service unknown"); + _ad->run_async_check(check_options, latency, true, true, &time_is_valid, + &preferred_time); + checks::checker::instance().reap(); + ASSERT_EQ(_ad->get_state_type(), checkable::soft); + ASSERT_EQ(_ad->get_current_state(), engine::service::state_unknown); + ASSERT_EQ(_ad->get_last_hard_state_change(), 54000); + ASSERT_EQ(_svc->get_last_state_change(), now); + ASSERT_EQ(_ad->get_plugin_output(), + "UNKNOWN: Unknown activity, metric did not return any values"); + ASSERT_EQ(_ad->get_current_attempt(), 1); + + ::unlink("/tmp/thresholds_status_change.json"); +} + +TEST_P(PbAnomalydetectionCheckStatusChange, StatusChangesWithType) { + CreateFile("/tmp/thresholds_status_change.json", GetParam().second); + _ad->init_thresholds(); + _ad->set_status_change(true); + + set_time(50000); + _svc->set_current_state(engine::service::state_ok); + _svc->set_last_hard_state(engine::service::state_ok); + _svc->set_last_hard_state_change(50000); + _svc->set_state_type(checkable::hard); + _svc->set_accept_passive_checks(true); + _svc->set_current_attempt(1); + _svc->set_last_check(50000); + + _ad->set_current_state(engine::service::state_ok); + _ad->set_last_hard_state(engine::service::state_ok); + _ad->set_last_hard_state_change(50000); + _ad->set_last_state_change(50000); + _ad->set_state_type(checkable::hard); + _ad->set_current_attempt(1); + _ad->set_last_check(50000); + + // --- 1 ---- + set_time(50500); + time_t now = std::time(nullptr); + std::string cmd(fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;service " + "critical| 'g[metric]'=80;25;60", + now)); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::soft); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 1); + ASSERT_EQ(_svc->get_plugin_output(), "service critical"); + ASSERT_EQ(_svc->get_perf_data(), "'g[metric]'=80;25;60"); + int check_options = 0; + int latency = 0; + bool time_is_valid; + time_t preferred_time; + _ad->run_async_check(check_options, latency, true, true, &time_is_valid, + &preferred_time); + checks::checker::instance().reap(); + ASSERT_EQ(_ad->get_state_type(), checkable::hard); + ASSERT_EQ(_ad->get_current_state(), engine::service::state_ok); + ASSERT_EQ(_ad->get_last_state_change(), 50000); + ASSERT_EQ(_ad->get_current_attempt(), 1); + ASSERT_EQ(_ad->get_plugin_output(), "OK: Regular activity, metric=80.00"); + if (GetParam().first == e_json_version::V1) { + ASSERT_EQ(_ad->get_perf_data(), + "'g[metric]'=80 metric_lower_thresholds=73.31 " + "metric_upper_thresholds=83.26 metric_fit=78.26 " + "metric_lower_margin=0.00 metric_upper_margin=0.00"); + } else { + ASSERT_EQ(_ad->get_perf_data(), + "'g[metric]'=80 metric_lower_thresholds=73.31 " + "metric_upper_thresholds=83.26 metric_fit=78.26 " + "metric_lower_margin=-4.95 metric_upper_margin=5.00"); + } + + // --- 2 ---- + set_time(51000); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;service " + "critical| 'g[metric]'=80;25;60", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::soft); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_state_change(), 50500); + ASSERT_EQ(_svc->get_current_attempt(), 2); + _ad->run_async_check(check_options, latency, true, true, &time_is_valid, + &preferred_time); + checks::checker::instance().reap(); + ASSERT_EQ(_ad->get_state_type(), checkable::hard); + ASSERT_EQ(_ad->get_current_state(), engine::service::state_ok); + ASSERT_EQ(_ad->get_last_state_change(), 50000); + ASSERT_EQ(_ad->get_current_attempt(), 1); + ASSERT_EQ(_ad->get_plugin_output(), "OK: Regular activity, metric=80.00"); + if (GetParam().first == e_json_version::V1) { + ASSERT_EQ(_ad->get_perf_data(), + "'g[metric]'=80 metric_lower_thresholds=72.62 " + "metric_upper_thresholds=82.52 metric_fit=77.52 " + "metric_lower_margin=0.00 metric_upper_margin=0.00"); + } else { + ASSERT_EQ(_ad->get_perf_data(), + "'g[metric]'=80 metric_lower_thresholds=72.62 " + "metric_upper_thresholds=82.52 metric_fit=77.52 " + "metric_lower_margin=-4.90 metric_upper_margin=5.00"); + } + // --- 3 ---- + set_time(51250); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;service " + "critical| 'g[metric]'=80;25;60", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_state_change(), 50500); + ASSERT_EQ(_svc->get_current_attempt(), 3); + _ad->run_async_check(check_options, latency, true, true, &time_is_valid, + &preferred_time); + checks::checker::instance().reap(); + ASSERT_EQ(_ad->get_state_type(), checkable::hard); + ASSERT_EQ(_ad->get_current_state(), engine::service::state_ok); + ASSERT_EQ(_ad->get_last_state_change(), 50000); + ASSERT_EQ(_ad->get_current_attempt(), 1); + ASSERT_EQ(_ad->get_plugin_output(), "OK: Regular activity, metric=80.00"); + if (GetParam().first == e_json_version::V1) { + ASSERT_EQ(_ad->get_perf_data(), + "'g[metric]'=80 metric_lower_thresholds=72.28 " + "metric_upper_thresholds=82.15 metric_fit=77.15 " + "metric_lower_margin=0.00 metric_upper_margin=0.00"); + } else { + ASSERT_EQ(_ad->get_perf_data(), + "'g[metric]'=80 metric_lower_thresholds=72.28 " + "metric_upper_thresholds=82.15 metric_fit=77.15 " + "metric_lower_margin=-4.88 metric_upper_margin=5.00"); + } + // --- 4 ---- + set_time(52000); + + now = std::time(nullptr); + time_t previous = now; + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;0;service " + "ok| 'g[metric]'=80foo;25;60", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_ok); + ASSERT_EQ(_svc->get_last_hard_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 1); + _ad->run_async_check(check_options, latency, true, true, &time_is_valid, + &preferred_time); + checks::checker::instance().reap(); + ASSERT_EQ(_ad->get_state_type(), checkable::hard); + ASSERT_EQ(_ad->get_current_state(), engine::service::state_ok); + ASSERT_EQ(_ad->get_last_state_change(), 50000); + ASSERT_EQ(_ad->get_current_attempt(), 1); + ASSERT_EQ(_ad->get_plugin_output(), "OK: Regular activity, metric=80.00foo"); + if (GetParam().first == e_json_version::V1) { + ASSERT_EQ(_ad->get_perf_data(), + "'g[metric]'=80foo metric_lower_thresholds=71.24foo " + "metric_upper_thresholds=81.04foo metric_fit=76.04foo " + "metric_lower_margin=0.00foo metric_upper_margin=0.00foo"); + } else { + ASSERT_EQ(_ad->get_perf_data(), + "'g[metric]'=80foo metric_lower_thresholds=71.24foo " + "metric_upper_thresholds=81.04foo metric_fit=76.04foo " + "metric_lower_margin=-4.80foo metric_upper_margin=5.00foo"); + } + // --- 5 ---- + set_time(52500); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;0;service ok| " + "'g[metric]'=30%;25;60", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_ok); + ASSERT_EQ(_svc->get_last_hard_state_change(), 52000); + ASSERT_EQ(_svc->get_last_state_change(), 52000); + ASSERT_EQ(_svc->get_current_attempt(), 1); + _ad->run_async_check(check_options, latency, true, true, &time_is_valid, + &preferred_time); + checks::checker::instance().reap(); + ASSERT_EQ(_ad->get_state_type(), checkable::soft); + ASSERT_EQ(_ad->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_ad->get_last_state_change(), now); + ASSERT_EQ(_ad->get_plugin_output(), + "NON-OK: Unusual activity, the actual value of metric is 30.00% " + "which is outside the forecasting range [70.55% : 80.30%]"); + if (GetParam().first == e_json_version::V1) { + ASSERT_EQ(_ad->get_perf_data(), + "'g[metric]'=30% metric_lower_thresholds=70.55% " + "metric_upper_thresholds=80.30% metric_fit=75.30% " + "metric_lower_margin=0.00% metric_upper_margin=0.00%"); + } else { + ASSERT_EQ(_ad->get_perf_data(), + "'g[metric]'=30% metric_lower_thresholds=70.55% " + "metric_upper_thresholds=80.30% metric_fit=75.30% " + "metric_lower_margin=-4.75% metric_upper_margin=5.00%"); + } + + ASSERT_EQ(_ad->get_current_attempt(), 1); + + // --- 6 ---- + set_time(53000); + + previous = now; + now = std::time(nullptr); + _ad->run_async_check(check_options, latency, true, true, &time_is_valid, + &preferred_time); + checks::checker::instance().wait_completion( + checks::checker::e_completion_filter::service); + checks::checker::instance().reap(); + ASSERT_EQ(_ad->get_state_type(), checkable::soft); + ASSERT_EQ(_ad->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_ad->get_last_state_change(), previous); + ASSERT_EQ(_ad->get_plugin_output(), + "NON-OK: Unusual activity, the actual value of metric is 12.00 " + "which is outside the forecasting range [69.86 : 79.56]"); + if (GetParam().first == e_json_version::V1) { + ASSERT_EQ(_ad->get_perf_data(), + "metric=12 metric_lower_thresholds=69.86 " + "metric_upper_thresholds=79.56 metric_fit=74.56 " + "metric_lower_margin=0.00 metric_upper_margin=0.00"); + + } else { + ASSERT_EQ(_ad->get_perf_data(), + "metric=12 metric_lower_thresholds=69.86 " + "metric_upper_thresholds=79.56 metric_fit=74.56 " + "metric_lower_margin=-4.70 metric_upper_margin=5.00"); + } + ASSERT_EQ(_ad->get_current_attempt(), 2); + + // --- 7 ---- + set_time(53500); + + previous = now; + now = std::time(nullptr); + _ad->run_async_check(check_options, latency, true, true, &time_is_valid, + &preferred_time); + checks::checker::instance().wait_completion( + checks::checker::e_completion_filter::service); + checks::checker::instance().reap(); + ASSERT_EQ(_ad->get_state_type(), checkable::hard); + ASSERT_EQ(_ad->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_ad->get_last_hard_state_change(), now); + ASSERT_EQ(_ad->get_last_state_change(), 52500); + ASSERT_EQ(_ad->get_plugin_output(), + "NON-OK: Unusual activity, the actual value of metric is 12.00 " + "which is outside the forecasting range [69.17 : 78.82]"); + if (GetParam().first == e_json_version::V1) { + ASSERT_EQ(_ad->get_perf_data(), + "metric=12 metric_lower_thresholds=69.17 " + "metric_upper_thresholds=78.82 metric_fit=73.82 " + "metric_lower_margin=0.00 metric_upper_margin=0.00"); + } else { + ASSERT_EQ(_ad->get_perf_data(), + "metric=12 metric_lower_thresholds=69.17 " + "metric_upper_thresholds=78.82 metric_fit=73.82 " + "metric_lower_margin=-4.65 metric_upper_margin=5.00"); + } + ASSERT_EQ(_ad->get_current_attempt(), 3); + + // --- 8 ---- + set_time(54000); + _ad->get_check_command_ptr()->set_command_line( + "echo 'output| metric=70%;50;75'"); + previous = now; + now = std::time(nullptr); + _ad->run_async_check(check_options, latency, true, true, &time_is_valid, + &preferred_time); + checks::checker::instance().wait_completion( + checks::checker::e_completion_filter::service); + checks::checker::instance().reap(); + ASSERT_EQ(_ad->get_state_type(), checkable::hard); + ASSERT_EQ(_ad->get_current_state(), engine::service::state_ok); + ASSERT_EQ(_ad->get_last_hard_state_change(), now); + ASSERT_EQ(_ad->get_last_state_change(), now); + ASSERT_EQ(_ad->get_plugin_output(), "OK: Regular activity, metric=70.00%"); + if (GetParam().first == e_json_version::V1) { + ASSERT_EQ(_ad->get_perf_data(), + "metric=70% metric_lower_thresholds=68.48% " + "metric_upper_thresholds=78.08% metric_fit=73.08% " + "metric_lower_margin=0.00% metric_upper_margin=0.00%"); + } else { + ASSERT_EQ(_ad->get_perf_data(), + "metric=70% metric_lower_thresholds=68.48% " + "metric_upper_thresholds=78.08% metric_fit=73.08% " + "metric_lower_margin=-4.60% metric_upper_margin=5.00%"); + } + ASSERT_EQ(_ad->get_current_attempt(), 1); + + // --- 9 ---- + set_time(54500); + + previous = now; + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;4;service unknown", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::soft); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_unknown); + ASSERT_EQ(_svc->get_last_hard_state_change(), 52000); + ASSERT_EQ(_svc->get_last_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 1); + ASSERT_EQ(_svc->get_plugin_output(), "service unknown"); + _ad->run_async_check(check_options, latency, true, true, &time_is_valid, + &preferred_time); + checks::checker::instance().reap(); + ASSERT_EQ(_ad->get_state_type(), checkable::soft); + ASSERT_EQ(_ad->get_current_state(), engine::service::state_unknown); + ASSERT_EQ(_ad->get_last_hard_state_change(), 54000); + ASSERT_EQ(_svc->get_last_state_change(), now); + ASSERT_EQ(_ad->get_plugin_output(), + "UNKNOWN: Unknown activity, metric did not return any values"); + ASSERT_EQ(_ad->get_current_attempt(), 1); + + ::unlink("/tmp/thresholds_status_change.json"); +} + +INSTANTIATE_TEST_SUITE_P( + PbAnomalydetectionCheckStatusChange, + PbAnomalydetectionCheckStatusChange, + testing::Values( + std::make_pair( + e_json_version::V1, + "[{\n \"host_id\": \"12\",\n \"service_id\": \"9\",\n " + "\"metric_name\": " + "\"metric\",\n \"predict\": [{\n \"timestamp\": 50000,\n " + "\"upper\": " + "84,\n \"lower\": 74,\n \"fit\": 79\n }, {\n \"timestamp\": " + "100000,\n " + "\"upper\": 10,\n \"lower\": 5,\n \"fit\": 5\n }, {\n " + "\"timestamp\": " + "150000,\n \"upper\": 100,\n \"lower\": 93,\n \"fit\": 96.5\n }, " + "{\n " + "\"timestamp\": 200000,\n \"upper\": 100,\n \"lower\": 97,\n " + "\"fit\": " + "98.5\n }, {\n \"timestamp\": 250000,\n \"upper\": 100,\n " + "\"lower\": " + "21,\n \"fit\": 60.5\n }\n]}]"), + std::make_pair( + e_json_version::V2, + "[{\n \"host_id\": \"12\",\n \"service_id\": \"9\",\n " + "\"metric_name\": " + "\"metric\",\n \"sensitivity\":1,\n \"predict\": [{\n " + "\"timestamp\": " + "50000,\n \"upper_margin\": " + "5,\n \"lower_margin\": -5,\n \"fit\": 79\n }, {\n \"timestamp\": " + "100000,\n " + "\"upper_margin\": 5,\n \"lower_margin\": 0,\n \"fit\": 5\n }, {\n " + "\"timestamp\": " + "150000,\n \"upper\": 3.5,\n \"lower\": -3.5,\n \"fit\": 96.5\n }, " + "{\n " + "\"timestamp\": 200000,\n \"upper_margin\": 1.5,\n " + "\"lower_margin\": " + "-1.5,\n \"fit\": " + "98.5\n }, {\n \"timestamp\": 250000,\n \"upper_margin\": 39.5,\n " + "\"lower_margin\": " + "-39.5,\n \"fit\": 60.5\n }\n]}]"))); + +class PbAnomalydetectionCheckMetricWithQuotes + : public PbAnomalydetectionCheck, + public testing::WithParamInterface< + std::pair<e_json_version, const char*>> {}; + +TEST_P(PbAnomalydetectionCheckMetricWithQuotes, MetricWithQuotes) { + CreateFile("/tmp/thresholds_status_change.json", GetParam().second); + + _ad->init_thresholds(); + _ad->set_status_change(true); + + set_time(50000); + _svc->set_current_state(engine::service::state_ok); + _svc->set_last_hard_state(engine::service::state_ok); + _svc->set_last_hard_state_change(50000); + _svc->set_state_type(checkable::hard); + _svc->set_accept_passive_checks(true); + _svc->set_current_attempt(1); + _svc->set_last_check(50000); + + _ad->set_current_state(engine::service::state_ok); + _ad->set_last_hard_state(engine::service::state_ok); + _ad->set_last_hard_state_change(50000); + _ad->set_state_type(checkable::hard); + _ad->set_current_attempt(1); + _ad->set_last_check(50000); + + set_time(50500); + std::ostringstream oss; + std::time_t now{std::time(nullptr)}; + oss << '[' << now << ']' + << " PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;service critical| " + "'metric'=90MT;25;60;0;100"; + std::string cmd{oss.str()}; + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::soft); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 1); + ASSERT_EQ(_svc->get_plugin_output(), "service critical"); + ASSERT_EQ(_svc->get_perf_data(), "'metric'=90MT;25;60;0;100"); + int check_options = 0; + int latency = 0; + bool time_is_valid; + time_t preferred_time; + _ad->run_async_check(check_options, latency, true, true, &time_is_valid, + &preferred_time); + checks::checker::instance().wait_completion( + checks::checker::e_completion_filter::service); + checks::checker::instance().reap(); + ASSERT_EQ(_ad->get_state_type(), checkable::soft); + ASSERT_EQ(_ad->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_ad->get_last_state_change(), now); + ASSERT_EQ(_ad->get_current_attempt(), 1); + ASSERT_EQ(_ad->get_plugin_output(), + "NON-OK: Unusual activity, the actual value of metric is 90.00MT " + "which is outside the forecasting range [73.31MT : 83.26MT]"); + if (GetParam().first == e_json_version::V1) { + ASSERT_EQ(_ad->get_perf_data(), + "'metric'=90MT;;;0;100 metric_lower_thresholds=73.31MT;;;0;100 " + "metric_upper_thresholds=83.26MT;;;0;100 " + "metric_fit=78.26MT;;;0;100 metric_lower_margin=0.00MT;;;0;100 " + "metric_upper_margin=0.00MT;;;0;100"); + } else { + ASSERT_EQ(_ad->get_perf_data(), + "'metric'=90MT;;;0;100 metric_lower_thresholds=73.31MT;;;0;100 " + "metric_upper_thresholds=83.26MT;;;0;100 " + "metric_fit=78.26MT;;;0;100 metric_lower_margin=-4.95MT;;;0;100 " + "metric_upper_margin=5.00MT;;;0;100"); + } + + ::unlink("/tmp/thresholds_status_change.json"); +} + +INSTANTIATE_TEST_SUITE_P( + PbAnomalydetectionCheckMetricWithQuotes, + PbAnomalydetectionCheckMetricWithQuotes, + testing::Values( + std::make_pair( + e_json_version::V1, + "[{\n \"host_id\": \"12\",\n \"service_id\": \"9\",\n " + "\"metric_name\": " + "\"metric\",\n \"predict\": [{\n \"timestamp\": 50000,\n " + "\"upper\": " + "84,\n \"lower\": 74,\n \"fit\": 79\n }, {\n \"timestamp\": " + "100000,\n " + "\"upper\": 10,\n \"lower\": 5,\n \"fit\": 5\n }, {\n " + "\"timestamp\": " + "150000,\n \"upper\": 100,\n \"lower\": 93,\n \"fit\": 96.5\n }, " + "{\n " + "\"timestamp\": 200000,\n \"upper\": 100,\n \"lower\": 97,\n " + "\"fit\": " + "98.5\n }, {\n \"timestamp\": 250000,\n \"upper\": 100,\n " + "\"lower\": " + "21,\n \"fit\": 60.5\n }\n]}]"), + std::make_pair( + e_json_version::V2, + "[{\n \"host_id\": \"12\",\n \"service_id\": \"9\",\n " + "\"metric_name\": " + "\"metric\",\n \"sensitivity\":1,\n \"predict\": [{\n " + "\"timestamp\": " + "50000,\n \"upper_margin\": " + "5,\n \"lower_margin\": -5,\n \"fit\": 79\n }, {\n \"timestamp\": " + "100000,\n " + "\"upper_margin\": 5,\n \"lower_margin\": 0,\n \"fit\": 5\n }, {\n " + "\"timestamp\": " + "150000,\n \"upper_margin\": 3.5,\n \"lower_margin\": -3.5,\n " + "\"fit\": " + "96.5\n }, {\n " + "\"timestamp\": 200000,\n \"upper_margin\": 1.5,\n " + "\"lower_margin\": " + "-1.5,\n \"fit\": " + "98.5\n }, {\n \"timestamp\": 250000,\n \"upper_margin\": 39.5,\n " + "\"lower_margin\": " + "-39.5,\n \"fit\": 60.5\n }\n]}]"))); + +TEST_F(PbAnomalydetectionCheck, BadThresholdsFile) { + ::unlink("/tmp/thresholds_status_change.json"); + set_time(50000); + std::time_t now{std::time(nullptr)}; + _svc->set_current_state(engine::service::state_ok); + _svc->set_last_hard_state(engine::service::state_ok); + _svc->set_last_hard_state_change(50000); + _svc->set_state_type(checkable::hard); + _svc->set_accept_passive_checks(true); + _svc->set_current_attempt(1); + _svc->set_last_check(50000); + _svc->set_perf_data("metric=90MT;25;60;0;100"); + + _ad->set_current_state(engine::service::state_ok); + _ad->set_last_hard_state(engine::service::state_ok); + _ad->set_last_hard_state_change(50000); + _ad->set_state_type(checkable::hard); + _ad->set_current_attempt(1); + _ad->set_last_check(50000); + + int check_options = 0; + int latency = 0; + bool time_is_valid; + time_t preferred_time; + _ad->run_async_check(check_options, latency, true, true, &time_is_valid, + &preferred_time); + checks::checker::instance().reap(); + ASSERT_EQ(_ad->get_state_type(), checkable::soft); + ASSERT_EQ(_ad->get_current_state(), engine::service::state_unknown); + ASSERT_EQ(_ad->get_last_state_change(), now); + ASSERT_EQ(_ad->get_current_attempt(), 1); + ASSERT_EQ(_ad->get_plugin_output(), + "The thresholds file is not viable for metric metric"); + ASSERT_EQ(_ad->get_perf_data(), "metric=90MT;25;60;0;100"); + + set_time(51000); + now = std::time(nullptr); + // _ad is not OK so _ad will do the check + _ad->get_check_command_ptr()->set_command_line( + "echo 'output| metric=70%;50;75'"); + + _ad->run_async_check(check_options, latency, true, true, &time_is_valid, + &preferred_time); + checks::checker::instance().wait_completion( + checks::checker::e_completion_filter::service); + checks::checker::instance().reap(); + ASSERT_EQ(_ad->get_state_type(), checkable::soft); + ASSERT_EQ(_ad->get_current_state(), engine::service::state_unknown); + ASSERT_EQ(_ad->get_last_state_change(), 50000); + ASSERT_EQ(_ad->get_current_attempt(), 2); + ASSERT_EQ(_ad->get_plugin_output(), + "The thresholds file is not viable for metric metric"); + ASSERT_EQ(_ad->get_perf_data(), "metric=70%;50;75"); + + ::unlink("/tmp/thresholds_status_change.json"); +} + +class PbAnomalydetectionCheckFileTooOld + : public PbAnomalydetectionCheck, + public testing::WithParamInterface< + std::pair<e_json_version, const char*>> {}; + +TEST_P(PbAnomalydetectionCheckFileTooOld, FileTooOld) { + CreateFile("/tmp/thresholds_status_change.json", GetParam().second); + _ad->init_thresholds(); + _ad->set_status_change(true); + + set_time(300000); + _svc->set_current_state(engine::service::state_ok); + _svc->set_last_hard_state(engine::service::state_ok); + _svc->set_last_hard_state_change(300000); + _svc->set_state_type(checkable::hard); + _svc->set_accept_passive_checks(true); + _svc->set_current_attempt(1); + _svc->set_last_check(300000); + _svc->set_perf_data("metric=90MT;25;60;0;100"); + + _ad->set_current_state(engine::service::state_ok); + _ad->set_last_hard_state(engine::service::state_ok); + _ad->set_last_hard_state_change(300000); + _ad->set_state_type(checkable::hard); + _ad->set_current_attempt(1); + _ad->set_last_check(300000); + + int check_options = 0; + int latency = 0; + bool time_is_valid; + time_t preferred_time; + _ad->run_async_check(check_options, latency, true, true, &time_is_valid, + &preferred_time); + checks::checker::instance().reap(); + ASSERT_EQ(_ad->get_state_type(), checkable::soft); + ASSERT_EQ(_ad->get_current_state(), engine::service::state_unknown); + ASSERT_EQ(_ad->get_last_state_change(), 300000); + ASSERT_EQ(_ad->get_current_attempt(), 1); + ASSERT_EQ(_ad->get_plugin_output(), + "The thresholds file is too old compared to the check timestamp " + "300000 for metric metric"); + ASSERT_EQ(_ad->get_perf_data(), "metric=90MT;25;60;0;100"); + + set_time(301000); + // _ad is not OK so _ad will do the check + _ad->get_check_command_ptr()->set_command_line( + "echo 'output| metric=70%;50;75'"); + + _ad->run_async_check(check_options, latency, true, true, &time_is_valid, + &preferred_time); + checks::checker::instance().wait_completion( + checks::checker::e_completion_filter::service); + checks::checker::instance().reap(); + ASSERT_EQ(_ad->get_state_type(), checkable::soft); + ASSERT_EQ(_ad->get_current_state(), engine::service::state_unknown); + ASSERT_EQ(_ad->get_last_state_change(), 300000); + ASSERT_EQ(_ad->get_current_attempt(), 2); + ASSERT_EQ(_ad->get_plugin_output(), + "The thresholds file is too old compared to the check timestamp " + "301000 for metric metric"); + ASSERT_EQ(_ad->get_perf_data(), "metric=70%;50;75"); + + ::unlink("/tmp/thresholds_status_change.json"); +} + +INSTANTIATE_TEST_SUITE_P( + FileTooOld, + PbAnomalydetectionCheckFileTooOld, + testing::Values( + std::make_pair( + e_json_version::V1, + "[{\n \"host_id\": \"12\",\n \"service_id\": \"9\",\n " + "\"metric_name\": " + "\"metric\",\n \"predict\": [{\n \"timestamp\": 50000,\n " + "\"upper\": " + "84,\n \"lower\": 74,\n \"fit\": 79\n }, {\n \"timestamp\": " + "100000,\n " + "\"upper\": 10,\n \"lower\": 5,\n \"fit\": 51.5\n }, {\n " + "\"timestamp\": " + "150000,\n \"upper\": 100,\n \"lower\": 93,\n \"fit\": 96.5\n }, " + "{\n " + "\"timestamp\": 200000,\n \"upper\": 100,\n \"lower\": 97,\n " + "\"fit\": " + "98.5\n }, {\n \"timestamp\": 250000,\n \"upper\": 100,\n " + "\"lower\": " + "21,\n \"fit\": 60.5\n }\n]}]"), + std::make_pair( + e_json_version::V2, + "[{\n \"host_id\": \"12\",\n \"service_id\": \"9\",\n " + "\"metric_name\": " + "\"metric\",\n \"sensitivity\":1,\n \"predict\": [{\n " + "\"timestamp\": " + "50000,\n \"upper_margin\": " + "5,\n \"lower_margin\": -5,\n \"fit\": 79\n }, {\n \"timestamp\": " + "100000,\n " + "\"upper_margin\": 5,\n \"lower\": 0,\n \"fit\": 5\n }, {\n " + "\"timestamp\": " + "150000,\n \"upper_margin\": 3.5,\n \"lower_margin\": -3.5,\n " + "\"fit\": " + "96.5\n }, {\n " + "\"timestamp\": 200000,\n \"upper_margin\": 1.5,\n " + "\"lower_margin\": " + "-1.5,\n \"fit\": " + "98.5\n }, {\n \"timestamp\": 250000,\n \"upper_margin\": 39.5,\n " + "\"lower_margin\": " + "-39.5,\n \"fit\": 60.5\n }\n]}]"))); diff --git a/engine/tests/checks/pb_service_check.cc b/engine/tests/checks/pb_service_check.cc new file mode 100644 index 00000000000..fb04308b8ac --- /dev/null +++ b/engine/tests/checks/pb_service_check.cc @@ -0,0 +1,598 @@ +/* + * Copyright 2020-2022 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include <gtest/gtest.h> + +#include <cstring> + +#include "../test_engine.hh" +#include "../timeperiod/utils.hh" +#include "com/centreon/engine/checks/checker.hh" +#include "com/centreon/engine/commands/commands.hh" +#include "com/centreon/engine/configuration/applier/command.hh" +#include "com/centreon/engine/configuration/applier/contact.hh" +#include "com/centreon/engine/configuration/applier/contactgroup.hh" +#include "com/centreon/engine/configuration/applier/host.hh" +#include "com/centreon/engine/configuration/applier/service.hh" +#include "com/centreon/engine/configuration/applier/servicedependency.hh" +#include "com/centreon/engine/configuration/applier/serviceescalation.hh" +#include "com/centreon/engine/configuration/applier/state.hh" +#include "com/centreon/engine/configuration/applier/timeperiod.hh" +#include "com/centreon/engine/serviceescalation.hh" +#include "com/centreon/engine/timezone_manager.hh" +#include "common/engine_conf/message_helper.hh" +#include "helper.hh" + +using namespace com::centreon; +using namespace com::centreon::engine; +using namespace com::centreon::engine::configuration; +using namespace com::centreon::engine::configuration::applier; + +extern configuration::State pb_config; + +class PbServiceCheck : public TestEngine { + public: + void SetUp() override { + init_config_state(); + + pb_config.clear_contacts(); + configuration::applier::contact ct_aply; + configuration::Contact ctct = new_pb_configuration_contact("admin", true); + ct_aply.add_object(ctct); + ct_aply.expand_objects(pb_config); + configuration::error_cnt err; + ct_aply.resolve_object(ctct, err); + + configuration::Host hst = new_pb_configuration_host("test_host", "admin"); + configuration::applier::host hst_aply; + hst_aply.add_object(hst); + + configuration::Service svc = + new_pb_configuration_service("test_host", "test_svc", "admin"); + configuration::applier::service svc_aply; + svc_aply.add_object(svc); + + hst_aply.resolve_object(hst, err); + svc_aply.resolve_object(svc, err); + + host_map const& hm{engine::host::hosts}; + _host = hm.begin()->second; + _host->set_current_state(engine::host::state_up); + _host->set_state_type(checkable::hard); + _host->set_acknowledgement(AckType::NONE); + _host->set_notify_on(static_cast<uint32_t>(-1)); + + service_map const& sm{engine::service::services}; + _svc = sm.begin()->second; + _svc->set_current_state(engine::service::state_ok); + _svc->set_state_type(checkable::hard); + _svc->set_acknowledgement(AckType::NONE); + _svc->set_notify_on(static_cast<uint32_t>(-1)); + + // This is to not be bothered by host checks during service checks + pb_config.set_host_check_timeout(10000); + } + + void TearDown() override { + _host.reset(); + _svc.reset(); + deinit_config_state(); + } + + protected: + std::shared_ptr<engine::host> _host; + std::shared_ptr<engine::service> _svc; +}; + +/* The following test comes from this array (inherited from Nagios behaviour): + * + * | Time | Check # | State | State type | State change | + * ------------------------------------------------------ + * | 0 | 1 | OK | HARD | No | + * | 1 | 1 | CRTCL | SOFT | Yes | + * | 2 | 2 | WARN | SOFT | Yes | + * | 3 | 3 | CRTCL | HARD | Yes | + * | 4 | 3 | WARN | HARD | Yes | + * | 5 | 3 | WARN | HARD | No | + * | 6 | 1 | OK | HARD | Yes | + * | 7 | 1 | OK | HARD | No | + * | 8 | 1 | UNKNWN| SOFT | Yes | + * | 9 | 2 | OK | SOFT | Yes | + * | 10 | 1 | OK | HARD | No | + * ------------------------------------------------------ + */ +TEST_F(PbServiceCheck, SimpleCheck) { + set_time(50000); + _svc->set_current_state(engine::service::state_ok); + _svc->set_last_hard_state(engine::service::state_ok); + _svc->set_last_hard_state_change(50000); + _svc->set_state_type(checkable::hard); + _svc->set_accept_passive_checks(true); + _svc->set_current_attempt(1); + + set_time(50500); + + std::time_t now{std::time(nullptr)}; + std::string cmd{fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;service critical", + now)}; + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::soft); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 1); + + set_time(51000); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;1;service warning", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::soft); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_warning); + ASSERT_EQ(_svc->get_last_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 2); + + set_time(51500); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;service critical", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_hard_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 3); + + set_time(52000); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;1;service warning", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_warning); + ASSERT_EQ(_svc->get_last_hard_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 3); + + set_time(52500); + + time_t previous = now; + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;1;service warning", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_warning); + ASSERT_EQ(_svc->get_last_hard_state_change(), previous); + ASSERT_EQ(_svc->get_current_attempt(), 3); + + set_time(53000); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;0;service ok", now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_ok); + ASSERT_EQ(_svc->get_last_hard_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 1); + + set_time(53500); + + previous = now; + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;0;service ok", now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_ok); + ASSERT_EQ(_svc->get_last_hard_state_change(), previous); + ASSERT_EQ(_svc->get_current_attempt(), 1); + + set_time(54000); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;4;service unknown", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::soft); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_unknown); + ASSERT_EQ(_svc->get_last_hard_state_change(), now - 1000); + ASSERT_EQ(_svc->get_last_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 1); + + set_time(54500); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;0;service ok", now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::soft); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_ok); + ASSERT_EQ(_svc->get_last_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 2); + + set_time(55000); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;0;service ok", now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_ok); + ASSERT_EQ(_svc->get_last_hard_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 1); +} + +/* The following test comes from this array (inherited from Nagios behaviour): + * + * | Time | Check # | State | State type | State change | + * ------------------------------------------------------ + * | 0 | 1 | OK | HARD | No | + * | 1 | 1 | CRTCL | SOFT | Yes | + * | 2 | 2 | CRTCL | SOFT | No | + * | 3 | 3 | CRTCL | HARD | No | + * ------------------------------------------------------ + */ +TEST_F(PbServiceCheck, OkCritical) { + set_time(55000); + + time_t now = std::time(nullptr); + std::string cmd{fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;0;service ok", + now)}; + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_ok); + ASSERT_EQ(_svc->get_last_hard_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 1); + + set_time(55500); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;service critical", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::soft); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 1); + + set_time(56000); + + time_t previous = now; + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;service critical", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::soft); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_state_change(), previous); + ASSERT_EQ(_svc->get_current_attempt(), 2); + + set_time(56500); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;service critical", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_hard_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 3); +} + +/* The following test comes from this array (inherited from Nagios behaviour): + * + * | Time | Check # | State | State type | State change | + * ------------------------------------------------------ + * | 0 | 2 | OK | SOFT | No | + * | 1 | 1 | CRTCL | SOFT | Yes | + * | 2 | 2 | CRTCL | SOFT | No | + * | 3 | 3 | CRTCL | HARD | No | + * ------------------------------------------------------ + */ +TEST_F(PbServiceCheck, OkSoft_Critical) { + set_time(55000); + + _svc->set_current_state(engine::service::state_ok); + _svc->set_last_state_change(55000); + _svc->set_current_attempt(2); + _svc->set_state_type(checkable::soft); + _svc->set_accept_passive_checks(true); + + set_time(55500); + + time_t now = std::time(nullptr); + std::string cmd{fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;service critical", + now)}; + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::soft); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 1); + + set_time(56000); + + time_t previous = now; + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;service critical", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::soft); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_state_change(), previous); + ASSERT_EQ(_svc->get_current_attempt(), 2); + + set_time(56500); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;service critical", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_hard_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 3); +} + +/* The following test comes from this array (inherited from Nagios behaviour): + * + * | Time | Check # | State | State type | State change | + * ------------------------------------------------------ + * | 0 | 1 | OK | HARD | No | + * | 1 | 2 | OK | HARD | No | + * | 2 | 3 | WARN | HARD | Yes | + * | 3 | 4 | CRTCL | HARD | Yes | + * | 4 | 5 | CRTCL | HARD | Yes | + * | 5 | 6 | CRTCL | HARD | Yes | + * | 6 | 7 | CRTCL | HARD | No | + * | 7 | 8 | CRTCL | HARD | No | + * ------------------------------------------------------ + */ +TEST_F(PbServiceCheck, OkCriticalStalking) { + set_time(55000); + + _svc->set_current_state(engine::service::state_ok); + _svc->set_last_state_change(55000); + _svc->set_current_attempt(2); + _svc->set_state_type(checkable::soft); + _svc->set_accept_passive_checks(true); + _svc->set_stalk_on(static_cast<uint32_t>(-1)); + + set_time(55500); + testing::internal::CaptureStdout(); + time_t now = std::time(nullptr); + std::string cmd{fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;0;RAID array " + "optimal", + now)}; + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_ok); + ASSERT_EQ(_svc->get_last_hard_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 1); + + set_time(56000); + time_t previous = now; + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;0;RAID array " + "optimal", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_ok); + ASSERT_EQ(_svc->get_last_hard_state_change(), previous); + ASSERT_EQ(_svc->get_current_attempt(), 1); + + set_time(56500); + for (int i = 0; i < 3; i++) { + // When i == 0, the state_critical is soft => no notification + // When i == 1, the state_critical is soft => no notification + // When i == 2, the state_critical is hard down => notification + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;1;RAID array " + "degraded (1 drive bad, 1 hot spare rebuilding)", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + } + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_warning); + ASSERT_EQ(_svc->get_last_hard_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 3); + + set_time(57000); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;RAID array " + "degraded (2 drives bad, 1 host spare online, 1 hot spare rebuilding)", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_hard_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 3); + + set_time(57500); + previous = now; + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;RAID array " + "degraded (3 drives bad, 2 hot spares online)", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_hard_state_change(), previous); + ASSERT_EQ(_svc->get_current_attempt(), 3); + + set_time(58000); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;RAID array " + "failed", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_hard_state_change(), previous); + ASSERT_EQ(_svc->get_current_attempt(), 3); + + set_time(58500); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;RAID array " + "failed", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_hard_state_change(), previous); + ASSERT_EQ(_svc->get_current_attempt(), 3); + + set_time(59000); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;RAID array " + "failed", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_hard_state_change(), previous); + ASSERT_EQ(_svc->get_current_attempt(), 3); + + std::string out{testing::internal::GetCapturedStdout()}; + std::cout << out << std::endl; + ASSERT_NE( + out.find( + "SERVICE ALERT: test_host;test_svc;OK;HARD;1;RAID array optimal"), + std::string::npos); + ASSERT_NE(out.find("SERVICE ALERT: test_host;test_svc;WARNING;HARD;3;RAID " + "array degraded (1 drive bad, 1 hot spare rebuilding)"), + std::string::npos); + ASSERT_NE(out.find("SERVICE ALERT: test_host;test_svc;CRITICAL;HARD;3;RAID " + "array degraded (2 drives bad, 1 host spare online, 1 hot " + "spare rebuilding)"), + std::string::npos); + ASSERT_NE(out.find("SERVICE ALERT: test_host;test_svc;CRITICAL;HARD;3;RAID " + "array degraded (3 drives bad, 2 hot spares online"), + std::string::npos); + ASSERT_NE(out.find("SERVICE ALERT: test_host;test_svc;CRITICAL;HARD;3;RAID " + "array failed"), + std::string::npos); +} + +TEST_F(PbServiceCheck, CheckRemoveCheck) { + set_time(50000); + _svc->set_current_state(engine::service::state_ok); + _svc->set_last_hard_state(engine::service::state_ok); + _svc->set_last_hard_state_change(50000); + _svc->set_state_type(checkable::hard); + _svc->set_accept_passive_checks(true); + _svc->set_current_attempt(1); + + set_time(50500); + std::time_t now{std::time(nullptr)}; + std::string cmd{fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;service critical", + now)}; + process_external_command(cmd.c_str()); + + /* We simulate a reload that destroyed the service */ + engine::service::services.clear(); + engine::service::services_by_id.clear(); + _svc.reset(); + + checks::checker::instance().reap(); +} + +TEST_F(PbServiceCheck, CheckUpdateMultilineOutput) { + set_time(50000); + _svc->set_current_state(engine::service::state_ok); + _svc->set_last_hard_state(engine::service::state_ok); + _svc->set_last_hard_state_change(50000); + _svc->set_state_type(checkable::hard); + _svc->set_accept_passive_checks(true); + _svc->set_current_attempt(1); + + set_time(50500); + std::time_t now{std::time(nullptr)}; + std::string cmd{fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;service " + "critical\\nline2\\nline3\\nline4\\nline5|res;2;5;5\\n", + now)}; + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_plugin_output(), "service critical"); + ASSERT_EQ(_svc->get_long_plugin_output(), "line2\\nline3\\nline4\\nline5"); + ASSERT_EQ(_svc->get_perf_data(), "res;2;5;5"); +} diff --git a/engine/tests/checks/pb_service_retention.cc b/engine/tests/checks/pb_service_retention.cc new file mode 100644 index 00000000000..451267290bc --- /dev/null +++ b/engine/tests/checks/pb_service_retention.cc @@ -0,0 +1,562 @@ +/* + * Copyright 2023 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include <gtest/gtest.h> + +#include <cstring> + +#include "../test_engine.hh" +#include "../timeperiod/utils.hh" +#include "com/centreon/clib.hh" +#include "com/centreon/engine/checks/checker.hh" +#include "com/centreon/engine/commands/commands.hh" +#include "com/centreon/engine/configuration/applier/command.hh" +#include "com/centreon/engine/configuration/applier/contact.hh" +#include "com/centreon/engine/configuration/applier/contactgroup.hh" +#include "com/centreon/engine/configuration/applier/host.hh" +#include "com/centreon/engine/configuration/applier/service.hh" +#include "com/centreon/engine/configuration/applier/servicedependency.hh" +#include "com/centreon/engine/configuration/applier/serviceescalation.hh" +#include "com/centreon/engine/configuration/applier/state.hh" +#include "com/centreon/engine/configuration/applier/timeperiod.hh" +#include "com/centreon/engine/retention/dump.hh" +#include "com/centreon/engine/serviceescalation.hh" +#include "com/centreon/engine/timezone_manager.hh" +#include "common/engine_conf/message_helper.hh" +#include "helper.hh" + +using namespace com::centreon; +using namespace com::centreon::engine; +using namespace com::centreon::engine::configuration; +using namespace com::centreon::engine::configuration::applier; + +extern configuration::State pb_config; + +class PbServiceRetention : public TestEngine { + public: + void SetUp() override { + init_config_state(); + + pb_config.clear_contacts(); + configuration::applier::contact ct_aply; + configuration::Contact ctct = new_pb_configuration_contact("admin", true); + ct_aply.add_object(ctct); + ct_aply.expand_objects(pb_config); + configuration::error_cnt err; + ct_aply.resolve_object(ctct, err); + + configuration::Host hst = new_pb_configuration_host("test_host", "admin"); + configuration::applier::host hst_aply; + hst_aply.add_object(hst); + + configuration::Service svc = + new_pb_configuration_service("test_host", "test_svc", "admin"); + configuration::applier::service svc_aply; + svc_aply.add_object(svc); + + hst_aply.resolve_object(hst, err); + svc_aply.resolve_object(svc, err); + + host_map const& hm{engine::host::hosts}; + _host = hm.begin()->second; + _host->set_current_state(engine::host::state_up); + _host->set_state_type(checkable::hard); + _host->set_acknowledgement(AckType::NONE); + _host->set_notify_on(static_cast<uint32_t>(-1)); + + service_map const& sm{engine::service::services}; + _svc = sm.begin()->second; + _svc->set_current_state(engine::service::state_ok); + _svc->set_state_type(checkable::hard); + _svc->set_acknowledgement(AckType::NONE); + _svc->set_notify_on(static_cast<uint32_t>(-1)); + } + + void TearDown() override { + _host.reset(); + _svc.reset(); + deinit_config_state(); + } + + protected: + std::shared_ptr<engine::host> _host; + std::shared_ptr<engine::service> _svc; +}; + +TEST_F(PbServiceRetention, RetentionWithMultilineOutput) { + std::ostringstream oss; + set_time(55000); + + time_t now = std::time(nullptr); + oss.str(""); + oss << '[' << now << ']' + << " PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;0;" + "OK: Response time 0.123s | 'time'=0.123s;0:3;0:5;0; " + "'size'=81439B;;;0;\n" + "<!DOCTYPE html><html lang=\"XXXXXX\"><head><meta " + "charset=\"XXXXXX\"><meta http-equiv=\"XXXXXX\" " + "content=\"XXXXXX\"><meta name=\"XXXXXX\" " + "content=\"XXXXXX\"><title>Kibana

Loading Kibana

Please " + "upgrade your browser

This Kibana " + "installation has strict security requirements enabled that your " + "current browser does not meet.
"; + + std::string cmd = oss.str(); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_ok); + ASSERT_EQ(_svc->get_last_hard_state_change(), now); + ASSERT_EQ(_svc->get_perf_data(), + "'time'=0.123s;0:3;0:5;0; 'size'=81439B;;;0;"); + ASSERT_EQ(_svc->get_current_attempt(), 1); + oss.str(""); + retention::dump::services(oss); + std::string str(oss.str()); + ASSERT_NE( + str.find( + "performance_data='time'=0.123s;0:3;0:5;0; 'size'=81439B;;;0;\n"), + std::string::npos); + + std::shared_ptr cmt = std::make_shared( + comment::service, comment::flapping, _svc->host_id(), _svc->service_id(), + time(nullptr), "test1", "test2", false, comment::internal, false, + (time_t)0); + + comment::comments.insert({cmt->get_comment_id(), cmt}); + + oss.str(""); + retention::dump::comments(oss); + ASSERT_NE(str.find("host_name=test_host"), std::string::npos); + ASSERT_NE(str.find("service_description=test_svc"), std::string::npos); +} diff --git a/engine/tests/commands/bin_connector_test_run.cc b/engine/tests/commands/bin_connector_test_run.cc index b899d59a165..3258433846b 100644 --- a/engine/tests/commands/bin_connector_test_run.cc +++ b/engine/tests/commands/bin_connector_test_run.cc @@ -1,21 +1,21 @@ /** -* Copyright 2011-2013 Merethis -* -* This file is part of Centreon Engine. -* -* Centreon Engine is free software: you can redistribute it and/or -* modify it under the terms of the GNU General Public License version 2 -* as published by the Free Software Foundation. -* -* Centreon Engine 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 Centreon Engine. If not, see -* . -*/ + * Copyright 2011-2013 Merethis + * + * This file is part of Centreon Engine. + * + * Centreon Engine is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * Centreon Engine 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 Centreon Engine. If not, see + * . + */ #include #include diff --git a/engine/tests/commands/pbsimple-command.cc b/engine/tests/commands/pbsimple-command.cc new file mode 100644 index 00000000000..514b63bad00 --- /dev/null +++ b/engine/tests/commands/pbsimple-command.cc @@ -0,0 +1,267 @@ +/* + * Copyright 2019 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include +#include + +#include "../timeperiod/utils.hh" +#include "com/centreon/engine/commands/raw.hh" +#include "common/log_v2/log_v2.hh" +#include "helper.hh" + +using namespace com::centreon; +using namespace com::centreon::engine; +using namespace com::centreon::engine::commands; +using com::centreon::common::log_v2::log_v2; + +static void CreateFile(const std::string& filename, + const std::string& content) { + std::ofstream oss(filename); + oss << content; +} + +class PbSimpleCommand : public ::testing::Test { + protected: + std::shared_ptr logger; + + public: + void SetUp() override { + logger = log_v2::instance().get(log_v2::COMMANDS); + set_time(-1); + init_config_state(); +#ifdef LEGACY_CONF + config->interval_length(1); +#else + pb_config.set_interval_length(1); +#endif + } + + void TearDown() override { + deinit_config_state(); + } +}; + +class my_listener : public commands::command_listener { + mutable std::mutex _mutex; + commands::result _res; + + public: + result& get_result() { + std::lock_guard guard(_mutex); + return _res; + } + + void finished(result const& res) throw() override { + std::lock_guard guard(_mutex); + _res = res; + } +}; + +// Given an empty name +// When the add_command method is called with it as argument, +// Then it returns a NULL pointer. +TEST_F(PbSimpleCommand, NewCommandWithNoName) { + ASSERT_THROW(new commands::raw("", "bar"), std::exception); +} + +// Given a command to store, +// When the add_command method is called with an empty value, +// Then it returns a NULL pointer. +TEST_F(PbSimpleCommand, NewCommandWithNoValue) { + std::unique_ptr cmd; + ASSERT_THROW(cmd.reset(new commands::raw("foo", "")), std::exception); +} + +// Given an already existing command +// When the add_command method is called with the same name +// Then it returns a NULL pointer. +TEST_F(PbSimpleCommand, CommandAlreadyExisting) { + std::unique_ptr cmd; + ASSERT_NO_THROW(cmd.reset(new commands::raw("toto", "/bin/ls"))); +} + +// Given a name and a command line +// When the add_command method is called +// Then a new raw command is built +// When sync executed +// Then we have the output in the result class. +TEST_F(PbSimpleCommand, NewCommandSync) { + std::unique_ptr cmd{ + new commands::raw("test", "/bin/echo bonjour")}; + nagios_macros* mac(get_global_macros()); + commands::result res; + std::string cc(cmd->process_cmd(mac)); + ASSERT_EQ(cc, "/bin/echo bonjour"); + cmd->run(cc, *mac, 2, res); + ASSERT_EQ(res.output, "bonjour\n"); +} + +// Given a name and a command line +// When the add_command method is called +// Then a new raw command is built +// When async executed +// Then we have the output in the result class. +TEST_F(PbSimpleCommand, NewCommandAsync) { + std::unique_ptr lstnr(new my_listener); + std::unique_ptr cmd{ + new commands::raw("test", "/bin/echo bonjour")}; + cmd->set_listener(lstnr.get()); + nagios_macros* mac(get_global_macros()); + std::string cc(cmd->process_cmd(mac)); + ASSERT_EQ(cc, "/bin/echo bonjour"); + cmd->run(cc, *mac, 2, std::make_shared()); + int timeout{0}; + int max_timeout{3000}; + while (timeout < max_timeout && lstnr->get_result().output == "") { + usleep(100000); + ++timeout; + } + ASSERT_TRUE(timeout < max_timeout); + ASSERT_EQ(lstnr->get_result().output, "bonjour\n"); +} + +TEST_F(PbSimpleCommand, LongCommandAsync) { + std::unique_ptr lstnr(new my_listener); + std::unique_ptr cmd{ + new commands::raw("test", "/bin/sleep 10")}; + cmd->set_listener(lstnr.get()); + nagios_macros* mac(get_global_macros()); + std::string cc(cmd->process_cmd(mac)); + ASSERT_EQ(cc, "/bin/sleep 10"); + + // We force the time to be coherent with now because the function gettimeofday + // that is not simulated. + set_time(std::time(nullptr)); + cmd->run(cc, *mac, 2, std::make_shared()); + int timeout{0}; + int max_timeout{15}; + while (timeout < max_timeout && lstnr->get_result().output == "") { + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + set_time(std::time(nullptr) + 1); + ++timeout; + } + ASSERT_EQ(lstnr->get_result().output, "(Process Timeout)"); +} + +TEST_F(PbSimpleCommand, TooRecentDoubleCommand) { + logger->set_level(spdlog::level::trace); + CreateFile("/tmp/TooRecentDoubleCommand.sh", + "echo -n tutu | tee -a /tmp/TooRecentDoubleCommand;"); + + const char* path = "/tmp/TooRecentDoubleCommand"; + ::unlink(path); + std::unique_ptr lstnr(std::make_unique()); + std::unique_ptr cmd{std::make_unique( + "test", "/bin/sh /tmp/TooRecentDoubleCommand.sh")}; + cmd->set_listener(lstnr.get()); + const void* caller[] = {nullptr, path}; + cmd->add_caller_group(caller, caller + 2); + nagios_macros* mac(get_global_macros()); + std::string cc(cmd->process_cmd(mac)); + time_t now = 10000; + set_time(now); + cmd->run(cc, *mac, 2, std::make_shared(), caller[0]); + for (int wait_ind = 0; wait_ind != 50 && lstnr->get_result().output == ""; + ++wait_ind) { + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } + std::this_thread::sleep_for(std::chrono::seconds(1)); + ASSERT_EQ(lstnr->get_result().exit_code, 0); + ASSERT_EQ(lstnr->get_result().exit_status, process::status::normal); + ASSERT_EQ(lstnr->get_result().output, "tutu"); + struct stat file_stat; + ASSERT_EQ(stat(path, &file_stat), 0); + ASSERT_EQ(file_stat.st_size, 4); + ++now; + cmd->run(cc, *mac, 2, std::make_shared(), caller[1]); + for (int wait_ind = 0; wait_ind != 50 && lstnr->get_result().output == ""; + ++wait_ind) { + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } + ASSERT_EQ(lstnr->get_result().exit_code, 0); + ASSERT_EQ(lstnr->get_result().exit_status, process::status::normal); + ASSERT_EQ(lstnr->get_result().output, "tutu"); + ASSERT_EQ(stat(path, &file_stat), 0); + ASSERT_EQ(file_stat.st_size, 4); +} + +TEST_F(PbSimpleCommand, SufficientOldDoubleCommand) { + logger->set_level(spdlog::level::trace); + CreateFile("/tmp/TooRecentDoubleCommand.sh", + "echo -n tutu | tee -a /tmp/TooRecentDoubleCommand;"); + + const char* path = "/tmp/TooRecentDoubleCommand"; + ::unlink(path); + std::unique_ptr lstnr(std::make_unique()); + std::unique_ptr cmd{std::make_unique( + "test", "/bin/sh /tmp/TooRecentDoubleCommand.sh")}; + cmd->set_listener(lstnr.get()); + const void* caller[] = {nullptr, path}; + cmd->add_caller_group(caller, caller + 2); + nagios_macros* mac(get_global_macros()); + std::string cc(cmd->process_cmd(mac)); + time_t now = 10000; + set_time(now); + cmd->run(cc, *mac, 2, std::make_shared(), caller[0]); + for (int wait_ind = 0; wait_ind != 50 && lstnr->get_result().output == ""; + ++wait_ind) { + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } + std::this_thread::sleep_for(std::chrono::seconds(1)); + ASSERT_EQ(lstnr->get_result().exit_code, 0); + ASSERT_EQ(lstnr->get_result().exit_status, process::status::normal); + ASSERT_EQ(lstnr->get_result().output, "tutu"); + struct stat file_stat; + ASSERT_EQ(stat(path, &file_stat), 0); + ASSERT_EQ(file_stat.st_size, 4); + now += 10; + set_time(now); + lstnr->get_result().output = ""; + cmd->run(cc, *mac, 2, std::make_shared(), caller[1]); + for (int wait_ind = 0; wait_ind != 50 && lstnr->get_result().output == ""; + ++wait_ind) { + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } + ASSERT_EQ(lstnr->get_result().exit_code, 0); + ASSERT_EQ(lstnr->get_result().exit_status, process::status::normal); + ASSERT_EQ(lstnr->get_result().output, "tutu"); + ASSERT_EQ(stat(path, &file_stat), 0); + ASSERT_EQ(file_stat.st_size, 8); +} + +TEST_F(PbSimpleCommand, WithOneArgument) { + auto lstnr = std::make_unique(); + std::unique_ptr cmd{ + std::make_unique("test", "/bin/echo $ARG1$")}; + cmd->set_listener(lstnr.get()); + nagios_macros* mac(get_global_macros()); + mac->argv[0] = "Hello"; + mac->argv[1] = ""; + std::string cc(cmd->process_cmd(mac)); + ASSERT_EQ(cc, "/bin/echo Hello"); + cmd->run(cc, *mac, 2, std::make_shared()); + int timeout{0}; + int max_timeout{3000}; + while (timeout < max_timeout && lstnr->get_result().output == "") { + usleep(100000); + ++timeout; + } + ASSERT_TRUE(timeout < max_timeout); + ASSERT_EQ(lstnr->get_result().output, "Hello\n"); +} diff --git a/engine/tests/configuration/applier-severity.cc b/engine/tests/configuration/applier-severity.cc index 8c47ac08900..930c1e410a0 100644 --- a/engine/tests/configuration/applier-severity.cc +++ b/engine/tests/configuration/applier-severity.cc @@ -28,11 +28,7 @@ using namespace com::centreon::engine::configuration::applier; class ApplierSeverity : public ::testing::Test { public: - void SetUp() override { - config_errors = 0; - config_warnings = 0; - init_config_state(); - } + void SetUp() override { init_config_state(); } void TearDown() override { deinit_config_state(); } diff --git a/engine/tests/configuration/applier/applier-command.cc b/engine/tests/configuration/applier/applier-command.cc index 99dd042d60e..87baf5c07e2 100644 --- a/engine/tests/configuration/applier/applier-command.cc +++ b/engine/tests/configuration/applier/applier-command.cc @@ -26,8 +26,10 @@ #include "com/centreon/engine/configuration/applier/contact.hh" #include "com/centreon/engine/configuration/applier/host.hh" #include "com/centreon/engine/macros/grab_host.hh" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/command.hh" #include "common/engine_legacy_conf/connector.hh" +#endif #include "helper.hh" using namespace com::centreon; diff --git a/engine/tests/configuration/applier/applier-contactgroup.cc b/engine/tests/configuration/applier/applier-contactgroup.cc index 906e0e5e275..6253a7601dc 100644 --- a/engine/tests/configuration/applier/applier-contactgroup.cc +++ b/engine/tests/configuration/applier/applier-contactgroup.cc @@ -22,7 +22,9 @@ #include "com/centreon/engine/configuration/applier/contact.hh" #include "com/centreon/engine/configuration/applier/contactgroup.hh" #include "com/centreon/engine/contactgroup.hh" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/contact.hh" +#endif #include "helper.hh" using namespace com::centreon; diff --git a/engine/tests/configuration/applier/applier-log.cc b/engine/tests/configuration/applier/applier-log.cc index 50b014bd6a3..854a1139b64 100644 --- a/engine/tests/configuration/applier/applier-log.cc +++ b/engine/tests/configuration/applier/applier-log.cc @@ -21,7 +21,6 @@ #include #include "com/centreon/engine/configuration/applier/hostescalation.hh" #include "common/engine_legacy_conf/parser.hh" - #include "common/engine_legacy_conf/state.hh" #include "helper.hh" diff --git a/engine/tests/configuration/applier/applier-pbanomalydetection.cc b/engine/tests/configuration/applier/applier-pbanomalydetection.cc new file mode 100644 index 00000000000..918b37d2246 --- /dev/null +++ b/engine/tests/configuration/applier/applier-pbanomalydetection.cc @@ -0,0 +1,257 @@ +/** + * Copyright 2023 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include + +#include "../../test_engine.hh" +#include "../../timeperiod/utils.hh" +#include "com/centreon/engine/anomalydetection.hh" +#include "com/centreon/engine/configuration/applier/anomalydetection.hh" +#include "com/centreon/engine/configuration/applier/command.hh" +#include "com/centreon/engine/configuration/applier/contact.hh" +#include "com/centreon/engine/configuration/applier/contactgroup.hh" +#include "com/centreon/engine/configuration/applier/host.hh" +#include "com/centreon/engine/configuration/applier/service.hh" +#include "common/engine_conf/anomalydetection_helper.hh" +#include "common/engine_conf/command_helper.hh" +#include "common/engine_conf/host_helper.hh" +#include "common/engine_conf/service_helper.hh" +#include "helper.hh" + +using namespace com::centreon; +using namespace com::centreon::engine; +using namespace com::centreon::engine::configuration; +using namespace com::centreon::engine::configuration::applier; + +class ApplierPbAnomalydetection : public TestEngine { + public: + void SetUp() override { init_config_state(); } + + void TearDown() override { deinit_config_state(); } +}; + +// Given an AD configuration with a host not defined +// Then the applier add_object throws an exception because it needs a service +// command. +TEST_F(ApplierPbAnomalydetection, + PbNewAnomalydetectionWithHostNotDefinedFromConfig) { + configuration::applier::anomalydetection ad_aply; + configuration::Anomalydetection ad; + configuration::anomalydetection_helper hlp(&ad); + ad.set_host_name("test_host"); + ad.set_service_description("test description"); + ASSERT_THROW(ad_aply.add_object(ad), std::exception); +} + +// Given host configuration without host_id +// Then the applier add_object throws an exception. +TEST_F(ApplierPbAnomalydetection, PbNewHostWithoutHostId) { + configuration::applier::host hst_aply; + configuration::Anomalydetection ad; + configuration::anomalydetection_helper hlp(&ad); + configuration::Host hst; + configuration::host_helper hhlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + ASSERT_THROW(hst_aply.add_object(hst), std::exception); +} + +// Given service configuration with a host defined +// Then the applier add_object creates the service +TEST_F(ApplierPbAnomalydetection, PbNewADFromConfig) { + configuration::applier::host hst_aply; + configuration::applier::anomalydetection ad_aply; + configuration::Anomalydetection ad; + configuration::anomalydetection_helper ad_hlp(&ad); + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + // The host id is not given + ASSERT_THROW(hst_aply.add_object(hst), std::exception); + hst.set_host_id(12); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + + configuration::applier::service svc_aply; + configuration::Service svc; + configuration::service_helper svc_hmlp(&svc); + svc.set_host_name("test_host"); + svc.set_service_description("test_description"); + svc.set_host_id(12); + svc.set_service_id(13); + + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + cmd.set_command_line("echo 'output| metric=12;50;75'"); + svc.set_check_command("cmd"); + + configuration::applier::command cmd_aply; + cmd_aply.add_object(cmd); + ASSERT_NO_THROW(svc_aply.add_object(svc)); + + ad.set_service_description("test description"); + ad.set_internal_id(112); + ad.set_dependent_service_id(13); + ad.set_service_id(4); + ad.set_host_id(12); + ad.set_host_name("test_host"); + ad.set_metric_name("foo"); + ad.set_thresholds_file("/etc/centreon-broker/thresholds.json"); + + // No need here to call ad_aply.expand_objects(*config) because the + // configuration service is not stored in configuration::state. We just have + // to set the host_id manually. + ad_aply.add_object(ad); + service_id_map const& sm(engine::service::services_by_id); + ASSERT_EQ(sm.size(), 2u); + auto my_ad = sm.find({12u, 4u}); + ASSERT_EQ(my_ad->first.first, 12u); + ASSERT_EQ(my_ad->first.second, 4u); + + // Service is not resolved, host is null now. + ASSERT_TRUE(!my_ad->second->get_host_ptr()); + ASSERT_EQ(std::static_pointer_cast( + my_ad->second) + ->get_internal_id(), + 112u); + ASSERT_TRUE(my_ad->second->description() == "test description"); +} + +// Given service configuration without service_id +// Then the applier add_object throws an exception +TEST_F(ApplierPbAnomalydetection, PbNewADNoServiceId) { + configuration::applier::host hst_aply; + configuration::applier::anomalydetection ad_aply; + configuration::Anomalydetection ad; + configuration::anomalydetection_helper ad_hlp(&ad); + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + // The host id is not given + ASSERT_THROW(hst_aply.add_object(hst), std::exception); + hst.set_host_id(1); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ad.set_service_description("test description"); + ad.set_host_id(1); + ad.set_host_name("test_host"); + + // No need here to call ad_aply.expand_objects(*config) because the + // configuration service is not stored in configuration::state. We just have + // to set the host_id manually. + ASSERT_THROW(ad_aply.add_object(ad), std::exception); +} + +// Given service configuration without host_id +// Then the applier add_object throws an exception +TEST_F(ApplierPbAnomalydetection, PbNewADNoHostId) { + configuration::applier::host hst_aply; + configuration::applier::anomalydetection ad_aply; + configuration::Anomalydetection ad; + configuration::anomalydetection_helper ad_hlp(&ad); + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(1); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ad.set_service_description("test description"); + ad.set_service_id(4); + ad.set_host_name("test_host"); + + ASSERT_THROW(ad_aply.add_object(ad), std::exception); +} + +// Given service configuration with bad host_id +// Then the applier add_object throws an exception +TEST_F(ApplierPbAnomalydetection, PbNewADBadHostId) { + configuration::applier::host hst_aply; + configuration::applier::anomalydetection ad_aply; + configuration::Anomalydetection ad; + configuration::anomalydetection_helper ad_hlp(&ad); + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(1); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ad.set_service_description("test description"); + ad.set_host_id(2); + ad.set_service_id(2); + ad.set_dependent_service_id(3); + ad.set_host_name("test_host"); + + // No need here to call ad_aply.expand_objects(*config) because the + // configuration service is not stored in configuration::state. We just have + // to set the host_id manually. + ASSERT_THROW(ad_aply.add_object(ad), std::exception); +} + +// Given service configuration without metric_name +// Then the applier add_object throws an exception +TEST_F(ApplierPbAnomalydetection, PbNewADNoMetric) { + configuration::applier::host hst_aply; + configuration::applier::anomalydetection ad_aply; + configuration::Anomalydetection ad; + configuration::anomalydetection_helper ad_hlp(&ad); + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(1); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ad.set_service_description("test description"); + ad.set_host_id(1); + ad.set_service_id(4); + ad.set_dependent_service_id(3); + ad.set_host_name("test_host"); + + // No need here to call ad_aply.expand_objects(*config) because the + // configuration service is not stored in configuration::state. We just have + // to set the host_id manually. + ASSERT_THROW(ad_aply.add_object(ad), std::exception); +} + +// Given service configuration without metric_name +// Then the applier add_object throws an exception +TEST_F(ApplierPbAnomalydetection, PbNewADNoThresholds) { + configuration::applier::host hst_aply; + configuration::applier::anomalydetection ad_aply; + configuration::Anomalydetection ad; + configuration::anomalydetection_helper ad_hlp(&ad); + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(1); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ad.set_service_description("test description"); + ad.set_host_id(1); + ad.set_service_id(4); + ad.set_dependent_service_id(3); + ad.set_host_name("test_host"); + ad.set_metric_name("bar"); + + // No need here to call ad_aply.expand_objects(*config) because the + // configuration service is not stored in configuration::state. We just have + // to set the host_id manually. + ASSERT_THROW(ad_aply.add_object(ad), std::exception); +} diff --git a/engine/tests/configuration/applier/applier-pbcommand.cc b/engine/tests/configuration/applier/applier-pbcommand.cc new file mode 100644 index 00000000000..9eec7e0f60f --- /dev/null +++ b/engine/tests/configuration/applier/applier-pbcommand.cc @@ -0,0 +1,353 @@ +/* + * Copyright 2017-2019,2023 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ +#include +#include + +#include "com/centreon/engine/commands/command.hh" +#include "com/centreon/engine/commands/connector.hh" +#include "com/centreon/engine/configuration/applier/command.hh" +#include "com/centreon/engine/configuration/applier/connector.hh" +#include "com/centreon/engine/configuration/applier/contact.hh" +#include "com/centreon/engine/configuration/applier/host.hh" +#include "com/centreon/engine/globals.hh" +#include "com/centreon/engine/macros/grab_host.hh" +#include "common/engine_conf/command_helper.hh" +#include "common/engine_conf/connector_helper.hh" +#include "common/engine_conf/contact_helper.hh" +#include "common/engine_conf/host_helper.hh" +#include "helper.hh" + +using namespace com::centreon; +using namespace com::centreon::engine; +using namespace com::centreon::engine::configuration; +using namespace com::centreon::engine::configuration::applier; + +class ApplierPbCommand : public ::testing::Test { + public: + void SetUp() override { init_config_state(); } + + void TearDown() override { deinit_config_state(); } +}; + +// Given a command applier +// And a configuration command just with a name +// Then the applier add_object adds the command in the configuration set +// but not in the commands map (the command is unusable). +TEST_F(ApplierPbCommand, PbUnusableCommandFromConfig) { + configuration::applier::command aply; + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + ASSERT_THROW(aply.add_object(cmd), std::exception); + ASSERT_EQ(pb_config.commands().size(), 1u); + ASSERT_EQ(commands::command::commands.size(), 0u); +} + +// Given a command applier +// And a configuration command with a name and a command line +// Then the applier add_object adds the command into the configuration set +// and the commands map (accessible from commands::set::instance()). +TEST_F(ApplierPbCommand, PbNewCommandFromConfig) { + configuration::applier::command aply; + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + cmd.set_command_line("echo 1"); + aply.add_object(cmd); + ASSERT_EQ(pb_config.commands().size(), 1u); + command_map::iterator found{commands::command::commands.find("cmd")}; + ASSERT_FALSE(found == commands::command::commands.end()); + ASSERT_FALSE(!found->second); + ASSERT_EQ(found->second->get_name(), "cmd"); + ASSERT_EQ(found->second->get_command_line(), "echo 1"); +} +// Given a command applier +// And a configuration command with a name, a command line and a connector +// Then the applier add_object adds the command into the configuration set +// but not in the commands map (the connector is not defined). +TEST_F(ApplierPbCommand, PbNewCommandWithEmptyConnectorFromConfig) { + configuration::applier::command aply; + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + cmd.set_command_line("echo 1"); + cmd.set_connector("perl"); + ASSERT_THROW(aply.add_object(cmd), std::exception); + ASSERT_EQ(pb_config.commands().size(), 1u); + command_map::iterator found{commands::command::commands.find("cmd")}; + ASSERT_TRUE(found == commands::command::commands.end()); +} + +// Given a command applier +// And a configuration command with a name, a command line and a connector +// And the connector is well defined. +// Then the applier add_object adds the command into the configuration set +// but not in the commands map (the connector is not defined). +TEST_F(ApplierPbCommand, PbNewCommandWithConnectorFromConfig) { + configuration::error_cnt err; + configuration::applier::command aply; + configuration::applier::connector cnn_aply; + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + cmd.set_command_line("echo 1"); + cmd.set_connector("perl"); + configuration::Connector cnn; + configuration::connector_helper cnn_hlp(&cnn); + cnn.set_connector_name("perl"); + + cnn_aply.add_object(cnn); + aply.add_object(cmd); + + ASSERT_EQ(pb_config.commands().size(), 1u); + command_map::iterator found = commands::command::commands.find("cmd"); + ASSERT_EQ(found->second->get_name(), "cmd"); + ASSERT_EQ(found->second->get_command_line(), "echo 1"); + ASSERT_NO_THROW(aply.resolve_object(cmd, err)); +} + +// Given some command/connector appliers +// And a configuration command +// And a connector with the same name. +// Then the applier add_object adds the command into the configuration set +// but not in the commands map (the connector is not defined). +TEST_F(ApplierPbCommand, PbNewCommandAndConnectorWithSameName) { + configuration::error_cnt err; + configuration::applier::command aply; + configuration::applier::connector cnn_aply; + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + cmd.set_command_line("echo 1"); + configuration::Connector cnn; + configuration::connector_helper cnn_hlp(&cnn); + cnn.set_connector_name("cmd"); + cnn.set_connector_line("echo 2"); + + cnn_aply.add_object(cnn); + aply.add_object(cmd); + + ASSERT_EQ(pb_config.commands().size(), 1u); + command_map::iterator found{commands::command::commands.find("cmd")}; + ASSERT_FALSE(found == commands::command::commands.end()); + ASSERT_FALSE(!found->second); + + ASSERT_EQ(found->second->get_name(), "cmd"); + ASSERT_EQ(found->second->get_command_line(), "echo 1"); + + aply.resolve_object(cmd, err); + connector_map::iterator found_con{ + commands::connector::connectors.find("cmd")}; + ASSERT_TRUE(found_con != commands::connector::connectors.end()); + ASSERT_TRUE(found_con->second); + + found = commands::command::commands.find("cmd"); + ASSERT_TRUE(found != commands::command::commands.end()); +} + +// Given some command and connector appliers already applied with +// all objects created. +// When the command is changed from the configuration, +// Then the modify_object() method updated correctly the command. +TEST_F(ApplierPbCommand, PbModifyCommandWithConnector) { + configuration::applier::command aply; + configuration::applier::connector cnn_aply; + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + cmd.set_command_line("echo 1"); + cmd.set_connector("perl"); + configuration::Connector cnn; + configuration::connector_helper cnn_hlp(&cnn); + cnn.set_connector_name("perl"); + + cnn_aply.add_object(cnn); + aply.add_object(cmd); + + configuration::Command* to_modify = &pb_config.mutable_commands()->at(0); + cmd.set_command_line("date"); + aply.modify_object(to_modify, cmd); + command_map::iterator found{commands::command::commands.find("cmd")}; + ASSERT_EQ(found->second->get_name(), "cmd"); + ASSERT_EQ(found->second->get_command_line(), "date"); +} + +// Given simple command (without connector) applier already applied with +// all objects created. +// When the command is removed from the configuration, +// Then the command is totally removed. +TEST_F(ApplierPbCommand, PbRemoveCommand) { + configuration::applier::command aply; + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + cmd.set_command_line("echo 1"); + + aply.add_object(cmd); + + aply.remove_object(0); + command_map::iterator found{commands::command::commands.find("cmd")}; + ASSERT_EQ(found, commands::command::commands.end()); + ASSERT_TRUE(pb_config.commands().size() == 0); +} + +// Given some command and connector appliers already applied with +// all objects created. +// When the command is removed from the configuration, +// Then the command is totally removed. +TEST_F(ApplierPbCommand, PbRemoveCommandWithConnector) { + configuration::applier::command aply; + configuration::applier::connector cnn_aply; + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + cmd.set_command_line("echo 1"); + cmd.set_connector("perl"); + configuration::Connector cnn; + configuration::connector_helper cnn_hlp(&cnn); + cnn.set_connector_name("perl"); + + cnn_aply.add_object(cnn); + aply.add_object(cmd); + + aply.remove_object(0); + command_map::iterator found{commands::command::commands.find("cmd")}; + ASSERT_EQ(found, commands::command::commands.end()); + ASSERT_TRUE(pb_config.commands().size() == 0); +} + +// Given simple command (without connector) applier already applied with +// all objects created. +// When the command is removed from the configuration, +// Then the command is totally removed. +TEST_F(ApplierPbCommand, PbComplexCommand) { + configuration::error_cnt err; + configuration::applier::command cmd_aply; + configuration::applier::host hst_aply; + + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("base_centreon_ping"); + cmd.set_command_line( + "$USER1$/check_icmp -H $HOSTADDRESS$ -n $_HOSTPACKETNUMBER$ -w " + "$_HOSTWARNING$ -c $_HOSTCRITICAL$"); + cmd_aply.add_object(cmd); + + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("hst_test"); + hst.set_address("127.0.0.1"); + hst.set_host_id(1); + hst.set_host_id(1); + configuration::CustomVariable* cv = hst.add_customvariables(); + cv->set_name("PACKETNUMBER"); + cv->set_value("42"); + cv = hst.add_customvariables(); + cv->set_name("WARNING"); + cv->set_value("200,20%"); + cv = hst.add_customvariables(); + cv->set_name("CRITICAL"); + cv->set_value("400,50%"); + hst.set_check_command("base_centreon_ping"); + hst_aply.add_object(hst); + + command_map::iterator cmd_found{ + commands::command::commands.find("base_centreon_ping")}; + ASSERT_NE(cmd_found, commands::command::commands.end()); + ASSERT_TRUE(pb_config.commands().size() == 1); + + host_map::iterator hst_found{engine::host::hosts.find("hst_test")}; + ASSERT_NE(hst_found, engine::host::hosts.end()); + ASSERT_TRUE(pb_config.hosts().size() == 1); + + hst_aply.expand_objects(pb_config); + hst_aply.resolve_object(hst, err); + ASSERT_TRUE(hst_found->second->custom_variables.size() == 3); + nagios_macros* macros(get_global_macros()); + grab_host_macros_r(macros, hst_found->second.get()); + std::string processed_cmd( + hst_found->second->get_check_command_ptr()->process_cmd(macros)); + ASSERT_EQ(processed_cmd, + "/check_icmp -H 127.0.0.1 -n 42 -w 200,20% -c 400,50%"); +} + +// Given simple command (without connector) applier already applied with +// all objects created. +// When the command is removed from the configuration, +// Then the command is totally removed. +TEST_F(ApplierPbCommand, PbComplexCommandWithContact) { + configuration::error_cnt err; + configuration::applier::command cmd_aply; + configuration::applier::host hst_aply; + configuration::applier::contact cnt_aply; + + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("base_centreon_ping"); + cmd.set_command_line( + "$USER1$/check_icmp -H $HOSTADDRESS$ -n $_HOSTPACKETNUMBER$ -w " + "$_HOSTWARNING$ -c $_HOSTCRITICAL$ $CONTACTNAME$"); + cmd_aply.add_object(cmd); + + configuration::Contact cnt; + configuration::contact_helper cnt_hlp(&cnt); + cnt.set_contact_name("user"); + cnt.set_email("contact@centreon.com"); + cnt.set_pager("0473729383"); + cnt.set_host_notification_period("24x7"); + cnt.set_service_notification_period("24x7"); + cnt_aply.add_object(cnt); + + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("hst_test"); + hst.set_address("127.0.0.1"); + hst.set_host_id(1); + auto* cv = hst.add_customvariables(); + cv->set_name("PACKETNUMBER"); + cv->set_value("42"); + cv = hst.add_customvariables(); + cv->set_name("WARNING"); + cv->set_value("200,20%"); + cv = hst.add_customvariables(); + cv->set_name("CRITICAL"); + cv->set_value("400,50%"); + hst.set_check_command("base_centreon_ping"); + fill_string_group(hst.mutable_contacts(), "user"); + hst_aply.add_object(hst); + + command_map::iterator cmd_found = + commands::command::commands.find("base_centreon_ping"); + ASSERT_NE(cmd_found, commands::command::commands.end()); + ASSERT_TRUE(pb_config.commands().size() == 1); + + host_map::iterator hst_found = engine::host::hosts.find("hst_test"); + ASSERT_NE(hst_found, engine::host::hosts.end()); + ASSERT_TRUE(pb_config.hosts().size() == 1); + + hst_aply.expand_objects(pb_config); + hst_aply.resolve_object(hst, err); + ASSERT_TRUE(hst_found->second->custom_variables.size() == 3); + nagios_macros* macros(get_global_macros()); + grab_host_macros_r(macros, hst_found->second.get()); + std::string processed_cmd( + hst_found->second->get_check_command_ptr()->process_cmd(macros)); + ASSERT_EQ(processed_cmd, + "/check_icmp -H 127.0.0.1 -n 42 -w 200,20% -c 400,50% user"); +} diff --git a/engine/tests/configuration/applier/applier-pbconnector.cc b/engine/tests/configuration/applier/applier-pbconnector.cc new file mode 100644 index 00000000000..10474d42f65 --- /dev/null +++ b/engine/tests/configuration/applier/applier-pbconnector.cc @@ -0,0 +1,92 @@ +/* + * Copyright 2017-2019,2023 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ +#include +#include "../../timeperiod/utils.hh" +#include "com/centreon/engine/commands/connector.hh" +#include "com/centreon/engine/configuration/applier/command.hh" +#include "com/centreon/engine/configuration/applier/connector.hh" +#include "com/centreon/engine/globals.hh" +#include "common/engine_conf/connector_helper.hh" +#include "helper.hh" + +using namespace com::centreon; +using namespace com::centreon::engine; +using namespace com::centreon::engine::configuration; +using namespace com::centreon::engine::configuration::applier; + +class ApplierPbConnector : public ::testing::Test { + public: + void SetUp() override { init_config_state(); } + + void TearDown() override { deinit_config_state(); } +}; + +// Given a connector applier +// And a configuration connector just with a name +// Then the applier add_object adds the connector in the configuration set +// and in the connectors map. +TEST_F(ApplierPbConnector, PbUnusableConnectorFromConfig) { + configuration::applier::connector aply; + configuration::Connector cnn; + configuration::connector_helper cnn_hlp(&cnn); + cnn.set_connector_name("connector"); + aply.add_object(cnn); + ASSERT_EQ(commands::connector::connectors.size(), 1u); +} + +// Given a connector applier already applied +// When the connector is modified from the configuration, +// Then the modify_object() method updated correctly the connector. +TEST_F(ApplierPbConnector, PbModifyConnector) { + configuration::applier::connector aply; + configuration::Connector cnn; + configuration::connector_helper cnn_hlp(&cnn); + cnn.set_connector_name("connector"); + cnn.set_connector_line("perl"); + + aply.add_object(cnn); + + cnn.set_connector_line("date"); + configuration::Connector* old = &pb_config.mutable_connectors()->at(0); + aply.modify_object(old, cnn); + + connector_map::iterator found_con = + commands::connector::connectors.find("connector"); + ASSERT_FALSE(found_con == commands::connector::connectors.end()); + ASSERT_FALSE(!found_con->second); + + ASSERT_EQ(found_con->second->get_name(), "connector"); + ASSERT_EQ(found_con->second->get_command_line(), "date"); +} + +// Given simple connector applier already applied +// When the connector is removed from the configuration, +// Then the connector is totally removed. +TEST_F(ApplierPbConnector, PbRemoveConnector) { + configuration::applier::connector aply; + configuration::Connector cnn; + configuration::connector_helper cnn_hlp(&cnn); + cnn.set_connector_name("connector"); + cnn.set_connector_line("echo 1"); + + aply.add_object(cnn); + aply.remove_object(0); + ASSERT_TRUE(pb_config.connectors().size() == 0); + ASSERT_TRUE(commands::connector::connectors.size() == 0); +} diff --git a/engine/tests/configuration/applier/applier-pbcontact.cc b/engine/tests/configuration/applier/applier-pbcontact.cc new file mode 100644 index 00000000000..2eeafb71b57 --- /dev/null +++ b/engine/tests/configuration/applier/applier-pbcontact.cc @@ -0,0 +1,452 @@ +/** + * Copyright 2017-2019,2023-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include +#include "com/centreon/engine/commands/command.hh" +#include "com/centreon/engine/configuration/applier/command.hh" +#include "com/centreon/engine/configuration/applier/connector.hh" +#include "com/centreon/engine/configuration/applier/contact.hh" +#include "com/centreon/engine/configuration/applier/contactgroup.hh" +#include "com/centreon/engine/configuration/applier/timeperiod.hh" +#include "com/centreon/engine/contact.hh" +#include "com/centreon/engine/contactgroup.hh" +#include "com/centreon/engine/globals.hh" +#include "common/engine_conf/command_helper.hh" +#include "common/engine_conf/connector_helper.hh" +#include "common/engine_conf/contact_helper.hh" +#include "common/engine_conf/contactgroup_helper.hh" +#include "common/engine_conf/message_helper.hh" +#include "common/engine_conf/timeperiod_helper.hh" +#include "helper.hh" + +using namespace com::centreon; +using namespace com::centreon::engine; +using namespace com::centreon::engine::configuration; +using namespace com::centreon::engine::configuration::applier; + +class ApplierPbContact : public ::testing::Test { + public: + void SetUp() override { init_config_state(); } + + void TearDown() override { deinit_config_state(); } + + configuration::Contact valid_pb_contact_config() const { + // Add command. + { + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + cmd.set_command_line("true"); + configuration::applier::command aplyr; + aplyr.add_object(cmd); + } + // Add timeperiod. + { + configuration::Timeperiod tperiod; + configuration::timeperiod_helper tp_help(&tperiod); + tperiod.set_timeperiod_name("24x7"); + tperiod.set_alias("24x7"); + configuration::Timerange* tr = tperiod.mutable_timeranges()->add_monday(); + // monday: 00:00-24:00 + tr->set_range_start(0); + tr->set_range_end(24 * 3600); + configuration::applier::timeperiod aplyr; + aplyr.add_object(tperiod); + } + // Valid contact configuration + // (will generate 0 warnings or 0 errors). + configuration::Contact ctct; + ctct.set_contact_name("admin"); + ctct.set_host_notification_period("24x7"); + ctct.set_service_notification_period("24x7"); + fill_string_group(ctct.mutable_host_notification_commands(), "cmd"); + fill_string_group(ctct.mutable_service_notification_commands(), "cmd"); + return ctct; + } +}; + +// Given a contact applier +// And a configuration contact +// When we modify the contact configuration with an unexisting contact +// Then an exception is thrown. +TEST_F(ApplierPbContact, PbModifyUnexistingContactFromConfig) { + configuration::applier::contact aply; + configuration::Contact ctct; + configuration::contact_helper hlp(&ctct); + ctct.set_contact_name("test"); + fill_string_group(ctct.mutable_contactgroups(), "test_group"); + fill_string_group(ctct.mutable_host_notification_commands(), "cmd1,cmd2"); + configuration::Contact* cfg = pb_config.add_contacts(); + cfg->CopyFrom(ctct); + ASSERT_THROW(aply.modify_object(cfg, ctct), std::exception); +} + +// Given contactgroup / contact appliers +// And a configuration contactgroup and a configuration contact +// that are already in configuration +// When we remove the contact configuration applier +// Then it is really removed from the configuration applier. +TEST_F(ApplierPbContact, PbRemoveContactFromConfig) { + configuration::applier::contact aply; + configuration::applier::contactgroup aply_grp; + + configuration::Contactgroup grp; + grp.set_contactgroup_name("test_group"); + + configuration::Contact ctct; + configuration::contact_helper c_helper(&ctct); + ctct.set_contact_name("test"); + ctct.add_address("coucou"); + ctct.add_address("foo"); + ctct.add_address("bar"); + fill_string_group(ctct.mutable_contactgroups(), "test_group"); + fill_string_group(ctct.mutable_host_notification_commands(), "cmd1"); + fill_string_group(ctct.mutable_host_notification_commands(), "cmd2"); + fill_string_group(ctct.mutable_service_notification_commands(), "svc1"); + fill_string_group(ctct.mutable_service_notification_commands(), "svc2"); + configuration::CustomVariable* cv = ctct.add_customvariables(); + cv->set_name("superVar"); + cv->set_value("superValue"); + aply_grp.add_object(grp); + aply.add_object(ctct); + aply.expand_objects(pb_config); + engine::contact* my_contact = engine::contact::contacts.begin()->second.get(); + ASSERT_EQ(my_contact->get_addresses().size(), 3u); + int idx; + bool found = false; + for (idx = 0; idx < pb_config.contacts().size(); idx++) { + if (pb_config.contacts()[idx].contact_name() == "test") { + found = true; + break; + } + } + ASSERT_TRUE(found); + aply.remove_object(idx); + ASSERT_TRUE(engine::contact::contacts.empty()); +} + +TEST_F(ApplierPbContact, PbModifyContactFromConfig) { + configuration::applier::contact aply; + configuration::applier::contactgroup aply_grp; + configuration::Contactgroup grp; + configuration::contactgroup_helper grp_hlp(&grp); + grp.set_contactgroup_name("test_group"); + configuration::Contact ctct; + configuration::contact_helper ctct_hlp(&ctct); + ctct.set_contact_name("test"); + fill_string_group(ctct.mutable_contactgroups(), "test_group"); + fill_string_group(ctct.mutable_host_notification_commands(), "cmd1,cmd2"); + fill_string_group(ctct.mutable_service_notification_commands(), "svc1,svc2"); + ASSERT_TRUE(ctct_hlp.insert_customvariable("_superVar", "SuperValue")); + ASSERT_TRUE(ctct.customvariables().size() == 1); + + configuration::applier::command cmd_aply; + configuration::applier::connector cnn_aply; + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + cmd.set_command_line("echo 1"); + cmd.set_connector("perl"); + configuration::Connector cnn; + configuration::connector_helper cnn_hlp(&cnn); + cnn.set_connector_name("perl"); + cnn_aply.add_object(cnn); + cmd_aply.add_object(cmd); + + aply_grp.add_object(grp); + aply.add_object(ctct); + aply.expand_objects(pb_config); + ctct_hlp.hook("host_notification_commands", "cmd"); + ctct_hlp.hook("service_notification_commands", "svc1,svc2"); + ASSERT_TRUE(ctct_hlp.insert_customvariable("_superVar", "Super")); + ASSERT_TRUE(ctct_hlp.insert_customvariable("_superVar1", "Super1")); + ctct.set_alias("newAlias"); + ASSERT_EQ(ctct.customvariables().size(), 2u); + ctct_hlp.hook("service_notification_options", "n"); + aply.modify_object(&*pb_config.mutable_contacts()->begin(), ctct); + contact_map::const_iterator ct_it{engine::contact::contacts.find("test")}; + ASSERT_TRUE(ct_it != engine::contact::contacts.end()); + ASSERT_EQ(ct_it->second->get_custom_variables().size(), 2u); + ASSERT_TRUE(ct_it->second->get_custom_variables()["superVar"].value() == + "Super"); + ASSERT_TRUE(ct_it->second->get_custom_variables()["superVar1"].value() == + "Super1"); + ASSERT_TRUE(ct_it->second->get_alias() == "newAlias"); + ASSERT_FALSE(ct_it->second->notify_on(notifier::service_notification, + notifier::unknown)); + + bool found = false; + for (auto it = (*pb_config.mutable_commands()).begin(); + it != (*pb_config.mutable_commands()).end(); ++it) { + if (it->command_name() == "cmd") { + pb_config.mutable_commands()->erase(it); + found = true; + break; + } + } + ASSERT_TRUE(found) + << "Command 'cmd' not found among the configuration commands"; + + cmd.set_command_name("cmd"); + cmd.set_command_line("bar"); + configuration::applier::command aplyr; + aplyr.add_object(cmd); + ctct_hlp.hook("host_notification_commands", "cmd"); + auto* old_ct = &pb_config.mutable_contacts()->at(0); + ASSERT_TRUE(old_ct->contact_name() == "test"); + aply.modify_object(old_ct, ctct); + { + command_map::iterator found{commands::command::commands.find("cmd")}; + ASSERT_TRUE(found != commands::command::commands.end()); + ASSERT_TRUE(found->second); + ASSERT_TRUE(found->second->get_command_line() == "bar"); + } +} + +// Given contactgroup / contact appliers +// And a configuration contactgroup and a configuration contact +// that are already in configuration +// When we resolve the contact configuration +// Then the contact contactgroups is cleared, nothing more if the +// contact check is OK. Here, since notification commands are empty, +// an exception is thrown. +TEST_F(ApplierPbContact, PbResolveContactFromConfig) { + configuration::error_cnt err; + configuration::applier::contact aply; + configuration::applier::contactgroup aply_grp; + configuration::Contactgroup grp; + configuration::contactgroup_helper cg_hlp(&grp); + grp.set_contactgroup_name("test_group"); + + configuration::Contact ctct; + configuration::contact_helper ct_hlp(&ctct); + ctct.set_contact_name("test"); + fill_string_group(ctct.mutable_contactgroups(), "test_group"); + fill_string_group(ctct.mutable_host_notification_commands(), "cmd1"); + fill_string_group(ctct.mutable_host_notification_commands(), "cmd2"); + aply_grp.add_object(grp); + aply.add_object(ctct); + aply.expand_objects(pb_config); + ASSERT_THROW(aply.resolve_object(ctct, err), std::exception); +} + +// Given a contact +// And an applier +// When the contact is resolved by the applier +// Then an exception is thrown +// And 2 warnings and 2 errors are returned: +// * error 1 => no service notification command +// * error 2 => no host notification command +// * warning 1 => no service notification period +// * warning 2 => no host notification period +TEST_F(ApplierPbContact, PbResolveContactNoNotification) { + configuration::error_cnt err; + configuration::applier::contact aply; + configuration::Contact ctct; + configuration::contact_helper ctct_hlp(&ctct); + ctct.set_contact_name("test"); + aply.add_object(ctct); + aply.expand_objects(pb_config); + ASSERT_THROW(aply.resolve_object(ctct, err), std::exception); + ASSERT_EQ(err.config_warnings, 2); + ASSERT_EQ(err.config_errors, 2); +} + +// Given a valid contact +// - valid host notification period +// - valid service notification period +// - valid host notification command +// - valid service notification command +// And an applier +// When resolve_object() is called +// Then no exception is thrown +// And no errors are returned +// And links are properly resolved +TEST_F(ApplierPbContact, PbResolveValidContact) { + configuration::error_cnt err; + configuration::applier::contact aply; + configuration::Contact ctct(valid_pb_contact_config()); + aply.add_object(ctct); + aply.expand_objects(pb_config); + ASSERT_NO_THROW(aply.resolve_object(ctct, err)); + ASSERT_EQ(err.config_warnings, 0); + ASSERT_EQ(err.config_errors, 0); +} + +// Given a valid contact +// And an applier +// When adding a non-existing service notification period to the contact +// Then the resolve method throws +// And returns 1 error +TEST_F(ApplierPbContact, PbResolveNonExistingServiceNotificationTimeperiod) { + configuration::error_cnt err; + configuration::applier::contact aply; + configuration::Contact ctct(valid_pb_contact_config()); + ctct.set_service_notification_period("non_existing_period"); + aply.add_object(ctct); + aply.expand_objects(pb_config); + ASSERT_THROW(aply.resolve_object(ctct, err), std::exception); + ASSERT_EQ(err.config_warnings, 0); + ASSERT_EQ(err.config_errors, 1); +} + +// Given a valid contact +// And an applier +// When adding a non-existing host notification period to the contact +// Then the resolve method throws +// And returns 1 error +TEST_F(ApplierPbContact, PbResolveNonExistingHostNotificationTimeperiod) { + configuration::error_cnt err; + configuration::applier::contact aply; + configuration::Contact ctct(valid_pb_contact_config()); + ctct.set_host_notification_period("non_existing_period"); + aply.add_object(ctct); + aply.expand_objects(pb_config); + ASSERT_THROW(aply.resolve_object(ctct, err), std::exception); + ASSERT_EQ(err.config_warnings, 0); + ASSERT_EQ(err.config_errors, 1); +} + +// Given a valid contact +// And an applier +// When adding a non-existing service command to the contact +// Then the resolve method throws +// And returns 1 error +TEST_F(ApplierPbContact, PbResolveNonExistingServiceCommand) { + configuration::error_cnt err; + configuration::applier::contact aply; + configuration::Contact ctct(valid_pb_contact_config()); + fill_string_group(ctct.mutable_service_notification_commands(), + "non_existing_command"); + aply.add_object(ctct); + aply.expand_objects(pb_config); + ASSERT_THROW(aply.resolve_object(ctct, err), std::exception); + ASSERT_EQ(err.config_warnings, 0); + ASSERT_EQ(err.config_errors, 1); +} + +// Given a valid contact +// And an applier +// When adding a non-existing host command to the contact +// Then the resolve method throws +// And returns 1 error +TEST_F(ApplierPbContact, PbResolveNonExistingHostCommand) { + configuration::error_cnt err; + configuration::applier::contact aply; + configuration::Contact ctct(valid_pb_contact_config()); + fill_string_group(ctct.mutable_host_notification_commands(), + "non_existing_command"); + aply.add_object(ctct); + aply.expand_objects(pb_config); + ASSERT_THROW(aply.resolve_object(ctct, err), std::exception); + ASSERT_EQ(err.config_warnings, 0); + ASSERT_EQ(err.config_errors, 1); +} + +// Given a valid contact configuration +// And the contact has multiple host notification commands +// When the applier resolve_object() method is called +// Then the contact has the multiple host notification commands +// TEST_F(ApplierPbContact, ResolveContactWithMultipleHostNotificationCommand) { +// // Given +// configuration::contact ctct(valid_contact_config()); +// +// // And +// { +// configuration::applier::command aplyr; +// for (int i(0); i < 3; ++i) { +// std::ostringstream cmdname; +// cmdname << "command" << i + 1; +// configuration::command cmd; +// cmd.parse("command_name", cmdname.str().c_str()); +// cmd.parse("command_line", "true"); +// aplyr.add_object(cmd); +// } +// aplyr.expand_objects(*config); +// } +// ctct.parse("host_notification_commands", +// "command1!ARG1,command2,command3!ARG3"); configuration::applier::contact +// aplyr; aplyr.add_object(ctct); aplyr.expand_objects(*config); +// +// // When +// aplyr.resolve_object(ctct); +// +// // Then +// std::list > const& +// commands(configuration::applier::state::instance().contacts_find( +// ctct.contact_name())->get_host_notification_commands()); +// ASSERT_EQ(commands.size(), 3); +// std::list >::const_iterator +// it(commands.begin()), +// end(commands.end()); +// ASSERT_EQ(it->first->get_name(), "command1"); +// ASSERT_EQ(it->second, "command1!ARG1"); +// ++it; +// ASSERT_EQ(it->first->get_name(), "command2"); +// ASSERT_EQ(it->second, "command2"); +// ++it; +// ASSERT_EQ(it->first->get_name(), "command3"); +// ASSERT_EQ(it->second, "command3!ARG3"); +//} + +// Given a valid contact +// And the contact is notified on host recovery +// But not on down or unreachable host +// When resolve_object() is called +// Then a warning is returned +TEST_F(ApplierPbContact, PbContactWithOnlyHostRecoveryNotification) { + configuration::error_cnt err; + configuration::applier::contact aply; + configuration::Contact ctct(valid_pb_contact_config()); + uint16_t options; + fill_host_notification_options(&options, "r"); + ctct.set_host_notification_options(options); + fill_service_notification_options(&options, "n"); + ctct.set_service_notification_options(options); + ctct.set_host_notifications_enabled("1"); + ctct.set_service_notifications_enabled("1"); + aply.add_object(ctct); + aply.expand_objects(pb_config); + aply.resolve_object(ctct, err); + ASSERT_EQ(err.config_warnings, 1); + ASSERT_EQ(err.config_errors, 0); +} + +// Given a valid contact +// And the contact is notified on service recovery +// But not on critical, warning or unknown service +// When resolve_object() is called +// Then a warning is returned +TEST_F(ApplierPbContact, PbContactWithOnlyServiceRecoveryNotification) { + configuration::error_cnt err; + configuration::applier::contact aply; + configuration::Contact ctct(valid_pb_contact_config()); + uint16_t options; + fill_host_notification_options(&options, "n"); + ctct.set_host_notification_options(options); + fill_service_notification_options(&options, "r"); + ctct.set_service_notification_options(options); + ctct.set_host_notifications_enabled(true); + ctct.set_service_notifications_enabled(true); + aply.add_object(ctct); + aply.expand_objects(pb_config); + aply.resolve_object(ctct, err); + ASSERT_EQ(err.config_warnings, 1); + ASSERT_EQ(err.config_errors, 0); +} diff --git a/engine/tests/configuration/applier/applier-pbcontactgroup.cc b/engine/tests/configuration/applier/applier-pbcontactgroup.cc new file mode 100644 index 00000000000..dc76c3073e7 --- /dev/null +++ b/engine/tests/configuration/applier/applier-pbcontactgroup.cc @@ -0,0 +1,298 @@ +/* + * Copyright 2018 - 2019 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include +#include "com/centreon/engine/configuration/applier/command.hh" +#include "com/centreon/engine/configuration/applier/contact.hh" +#include "com/centreon/engine/configuration/applier/contactgroup.hh" +#include "com/centreon/engine/contactgroup.hh" +#include "com/centreon/engine/globals.hh" +#include "common/engine_conf/contact_helper.hh" +#include "common/engine_conf/contactgroup_helper.hh" +#include "helper.hh" + +using namespace com::centreon; +using namespace com::centreon::engine; +using namespace com::centreon::engine::configuration; +using namespace com::centreon::engine::configuration::applier; + +class ApplierPbContactgroup : public ::testing::Test { + public: + void SetUp() override { init_config_state(); } + + void TearDown() override { deinit_config_state(); } +}; + +// Given a contactgroup applier +// And a configuration contactgroup +// When we modify the contactgroup configuration with a non existing +// contactgroup +// Then an exception is thrown. +TEST_F(ApplierPbContactgroup, ModifyUnexistingContactgroupFromConfig) { + configuration::applier::contactgroup aply; + configuration::Contactgroup cg; + configuration::contactgroup_helper hlp(&cg); + cg.set_contactgroup_name("test"); + fill_string_group(cg.mutable_members(), "contact"); + auto* new_cg = pb_config.add_contactgroups(); + new_cg->CopyFrom(cg); + ASSERT_THROW(aply.modify_object(new_cg, cg), std::exception); +} + +// Given a contactgroup applier +// And a configuration contactgroup in configuration +// When we modify the contactgroup configuration +// Then the applier modify_object updates the contactgroup. +TEST_F(ApplierPbContactgroup, ModifyContactgroupFromConfig) { + configuration::applier::contact caply; + configuration::applier::contactgroup aply; + configuration::Contactgroup cg; + configuration::contactgroup_helper cg_hlp(&cg); + cg.set_contactgroup_name("test"); + configuration::Contact ct; + configuration::contact_helper ct_hlp(&ct); + ct.set_contact_name("contact"); + + caply.add_object(ct); + + fill_string_group(cg.mutable_members(), "contact"); + cg.set_alias("test"); + aply.add_object(cg); + auto it = std::find_if(pb_config.mutable_contactgroups()->begin(), + pb_config.mutable_contactgroups()->end(), + [](const configuration::Contactgroup& cg) { + return cg.contactgroup_name() == "test"; + }); + + ASSERT_TRUE(it->alias() == "test"); + + cg.set_alias("test_renamed"); + aply.modify_object(&*it, cg); + + it = std::find_if(pb_config.mutable_contactgroups()->begin(), + pb_config.mutable_contactgroups()->end(), + [](const configuration::Contactgroup& cg) { + return cg.contactgroup_name() == "test"; + }); + ASSERT_TRUE(it->alias() == "test_renamed"); +} + +// Given a contactgroup applier +// And a configuration contactgroup in configuration +// When we change remove the configuration +// Then it is really removed +TEST_F(ApplierPbContactgroup, RemoveContactgroupFromConfig) { + configuration::applier::contact caply; + configuration::applier::contactgroup aply; + configuration::Contactgroup cg; + configuration::contactgroup_helper cg_hlp(&cg); + cg.set_contactgroup_name("test"); + configuration::Contact ct; + configuration::contact_helper ct_hlp(&ct); + ct.set_contact_name("contact"); + + caply.add_object(ct); + fill_string_group(cg.mutable_members(), "contact"); + aply.add_object(cg); + ASSERT_FALSE(engine::contactgroup::contactgroups.empty()); + + aply.remove_object(0); + ASSERT_TRUE(engine::contactgroup::contactgroups.empty()); +} + +// Given an empty contactgroup +// When the resolve_object() method is called +// Then no warning, nor error are given +TEST_F(ApplierPbContactgroup, ResolveEmptyContactgroup) { + configuration::error_cnt err; + configuration::applier::contactgroup aplyr; + configuration::Contactgroup grp; + configuration::contactgroup_helper hlp(&grp); + grp.set_contactgroup_name("test"); + aplyr.add_object(grp); + aplyr.expand_objects(pb_config); + aplyr.resolve_object(grp, err); + ASSERT_EQ(err.config_warnings, 0); + ASSERT_EQ(err.config_errors, 0); +} + +// Given a contactgroup with a non-existing contact +// When the resolve_object() method is called +// Then an exception is thrown +// And the method returns 1 error +TEST_F(ApplierPbContactgroup, ResolveInexistentContact) { + configuration::applier::contactgroup aplyr; + configuration::Contactgroup grp; + configuration::contactgroup_helper grp_hlp(&grp); + grp.set_contactgroup_name("test"); + fill_string_group(grp.mutable_members(), "non_existing_contact"); + ASSERT_THROW(aplyr.add_object(grp), std::exception); +} + +// Given a contactgroup with a contact +// When the resolve_object() method is called +// Then the contact is really added to the contact group. +TEST_F(ApplierPbContactgroup, ResolveContactgroup) { + configuration::error_cnt err; + configuration::applier::contact aply; + configuration::applier::contactgroup aply_grp; + configuration::Contactgroup grp; + configuration::contactgroup_helper hlp(&grp); + grp.set_contactgroup_name("test_group"); + configuration::Contact ctct; + configuration::contact_helper c_hlp(&ctct); + ctct.set_contact_name("test"); + aply.add_object(ctct); + fill_string_group(ctct.mutable_contactgroups(), "test_group"); + fill_string_group(grp.mutable_members(), "test"); + aply_grp.add_object(grp); + aply_grp.expand_objects(pb_config); + ASSERT_NO_THROW(aply_grp.resolve_object(grp, err)); +} + +// Given a contactgroup with a contact already configured +// And a second contactgroup configuration +// When we set the first one as contactgroup member to the second +// Then the parse method returns true and set the first one contacts +// to the second one. +TEST_F(ApplierPbContactgroup, SetContactgroupMembers) { + configuration::error_cnt err; + configuration::applier::contact aply; + configuration::applier::contactgroup aply_grp; + configuration::Contactgroup grp; + configuration::contactgroup_helper grp_hlp(&grp); + grp.set_contactgroup_name("test_group"); + configuration::Contact ctct; + configuration::contact_helper ctct_hlp(&ctct); + ctct.set_contact_name("test"); + aply.add_object(ctct); + fill_string_group(grp.mutable_members(), "test"); + aply_grp.add_object(grp); + aply_grp.expand_objects(pb_config); + aply_grp.resolve_object(grp, err); + ASSERT_EQ(grp.members().data().size(), 1); + + configuration::Contactgroup grp1; + configuration::contactgroup_helper grp1_hlp(&grp1); + grp1.set_contactgroup_name("big_group"); + fill_string_group(grp1.mutable_contactgroup_members(), "test_group"); + aply_grp.add_object(grp1); + aply_grp.expand_objects(pb_config); + + // grp1 must be reload because the expand_objects reload them totally. + bool found = false; + for (auto& cg : pb_config.contactgroups()) { + if (cg.contactgroup_name() == "big_group") { + ASSERT_EQ(cg.members().data().size(), 1); + found = true; + break; + } + } + ASSERT_TRUE(found); +} + +TEST_F(ApplierPbContactgroup, ContactRemove) { + configuration::error_cnt err; + configuration::applier::contact aply; + configuration::applier::contactgroup aply_grp; + configuration::Contactgroup grp; + configuration::contactgroup_helper grp_hlp(&grp); + grp.set_contactgroup_name("test_group"); + + configuration::Contact ctct; + configuration::contact_helper ctct_hlp(&ctct); + ctct.set_contact_name("test"); + aply.add_object(ctct); + + configuration::Contact ctct2; + configuration::contact_helper ctct2_hlp(&ctct2); + ctct2.set_contact_name("test2"); + aply.add_object(ctct2); + + grp_hlp.hook("members", "test, test2"); + aply_grp.add_object(grp); + aply_grp.expand_objects(pb_config); + aply_grp.resolve_object(grp, err); + ASSERT_EQ( + engine::contactgroup::contactgroups["test_group"]->get_members().size(), + 2u); + + int idx2 = 0; + while (pb_config.contacts()[idx2].contact_name() != "test2") { + idx2++; + ASSERT_LE(idx2, pb_config.contacts().size()); + } + + aply.remove_object(idx2); + ASSERT_EQ( + engine::contactgroup::contactgroups["test_group"]->get_members().size(), + 1u); + grp_hlp.hook("members", "test"); + // grp.parse("members", "test"); + int idx = 0; + while (pb_config.contactgroups()[idx].contactgroup_name() != "test_group") { + idx++; + ASSERT_LE(idx, pb_config.contactgroups().size()); + } + aply_grp.modify_object(&pb_config.mutable_contactgroups()->at(idx), grp); +} + +// Given a contactgroup applier +// And a configuration contactgroup in configuration +// When we modify members in the contactgroup configuration +// Then the applier modify_object updates the contactgroup. +TEST_F(ApplierPbContactgroup, ModifyMembersContactgroupFromConfig) { + configuration::applier::contact caply; + configuration::applier::contactgroup aply; + configuration::Contactgroup cg; + configuration::contactgroup_helper cg_hlp(&cg); + cg.set_contactgroup_name("test"); + configuration::Contact ct; + configuration::contact_helper ct_hlp(&ct); + ct.set_contact_name("contact"); + + configuration::Contact ct1; + configuration::contact_helper ct_hlp1(&ct1); + ct1.set_contact_name("contact1"); + caply.add_object(ct); + caply.add_object(ct1); + + fill_string_group(cg.mutable_members(), "contact,contact1"); + cg.set_alias("test"); + aply.add_object(cg); + auto it = std::find_if(pb_config.mutable_contactgroups()->begin(), + pb_config.mutable_contactgroups()->end(), + [](const configuration::Contactgroup& cg) { + return cg.contactgroup_name() == "test"; + }); + + ASSERT_TRUE(it->alias() == "test"); + + fill_string_group(cg.mutable_members(), "contact1"); + aply.modify_object(&*it, cg); + + it = std::find_if(pb_config.mutable_contactgroups()->begin(), + pb_config.mutable_contactgroups()->end(), + [](const configuration::Contactgroup& cg) { + return cg.contactgroup_name() == "test"; + }); + ASSERT_TRUE(it->members().data().size() == 2); + ASSERT_TRUE(it->members().data()[0] == "contact"); + ASSERT_TRUE(it->members().data()[1] == "contact1"); +} diff --git a/engine/tests/configuration/applier/applier-pbglobal.cc b/engine/tests/configuration/applier/applier-pbglobal.cc new file mode 100644 index 00000000000..be25e9b5fa9 --- /dev/null +++ b/engine/tests/configuration/applier/applier-pbglobal.cc @@ -0,0 +1,58 @@ +/** + * Copyright 2019 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include + +#include +#include "com/centreon/engine/configuration/applier/hostescalation.hh" +#include "common/engine_conf/parser.hh" +#include "common/engine_conf/state_helper.hh" +#include "helper.hh" + +using namespace com::centreon; +using namespace com::centreon::engine; + +class ApplierGlobal : public ::testing::Test { + public: + void SetUp() override { init_config_state(); } + + void TearDown() override { deinit_config_state(); } +}; + +// Given host configuration without host_id +// Then the applier add_object throws an exception. +TEST_F(ApplierGlobal, PbPollerName) { + configuration::parser parser; + configuration::State st; + configuration::state_helper st_hlp(&st); + + ASSERT_EQ(st.poller_name(), "unknown"); + + std::remove("/tmp/test-config.cfg"); + + std::ofstream ofs("/tmp/test-config.cfg"); + ofs << "poller_name=poller-test" << std::endl; + ofs.close(); + configuration::error_cnt err; + + parser.parse("/tmp/test-config.cfg", &st, err); + std::remove("/tmp/test-config.cfg"); + + ASSERT_EQ(st.poller_name(), "poller-test"); +} diff --git a/engine/tests/configuration/applier/applier-pbhost.cc b/engine/tests/configuration/applier/applier-pbhost.cc new file mode 100644 index 00000000000..5a35c5c4c9f --- /dev/null +++ b/engine/tests/configuration/applier/applier-pbhost.cc @@ -0,0 +1,160 @@ +/** + * Copyright 2023 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include + +#include "../../timeperiod/utils.hh" +#include "com/centreon/engine/configuration/applier/command.hh" +#include "com/centreon/engine/configuration/applier/host.hh" +#include "com/centreon/engine/configuration/applier/service.hh" +#include "com/centreon/engine/globals.hh" +#include "com/centreon/engine/host.hh" +#include "com/centreon/engine/timezone_manager.hh" +#include "common/engine_conf/command_helper.hh" +#include "common/engine_conf/host_helper.hh" +#include "common/engine_conf/service_helper.hh" +#include "helper.hh" + +using namespace com::centreon; +using namespace com::centreon::engine; +using namespace com::centreon::engine::configuration; +using namespace com::centreon::engine::configuration::applier; + +class ApplierPbHost : public ::testing::Test { + public: + void SetUp() override { init_config_state(); } + + void TearDown() override { deinit_config_state(); } +}; + +// Given host configuration without host_id +// Then the applier add_object throws an exception. +TEST_F(ApplierPbHost, PbNewHostWithoutHostId) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + ASSERT_THROW(hst_aply.add_object(hst), std::exception); +} + +// Given a host configuration +// When we change the host name in the configuration +// Then the applier modify_object changes the host name without changing +// the host id. +TEST_F(ApplierPbHost, HostRenamed) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst_aply.add_object(hst); + host_map const& hm(engine::host::hosts); + ASSERT_EQ(hm.size(), 1u); + std::shared_ptr h1(hm.begin()->second); + ASSERT_TRUE(h1->name() == "test_host"); + + hst.set_host_name("test_host1"); + hst_aply.modify_object(&pb_config.mutable_hosts()->at(0), hst); + ASSERT_EQ(hm.size(), 1u); + h1 = hm.begin()->second; + ASSERT_TRUE(h1->name() == "test_host1"); + ASSERT_EQ(get_host_id(h1->name()), 12u); +} + +TEST_F(ApplierPbHost, PbHostRemoved) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst_aply.add_object(hst); + host_map const& hm(engine::host::hosts); + ASSERT_EQ(hm.size(), 1u); + std::shared_ptr h1(hm.begin()->second); + ASSERT_TRUE(h1->name() == "test_host"); + + hst_aply.remove_object(0); + + ASSERT_EQ(hm.size(), 0u); + hst.set_host_name("test_host1"); + hst_aply.add_object(hst); + h1 = hm.begin()->second; + ASSERT_EQ(hm.size(), 1u); + ASSERT_TRUE(h1->name() == "test_host1"); + ASSERT_EQ(get_host_id(h1->name()), 12u); +} + +TEST_F(ApplierPbHost, PbHostParentChildUnreachable) { + configuration::error_cnt err; + configuration::applier::host hst_aply; + configuration::applier::command cmd_aply; + configuration::Host hst_child; + configuration::host_helper hst_child_hlp(&hst_child); + configuration::Host hst_parent; + configuration::host_helper hst_parent_hlp(&hst_parent); + + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("base_centreon_ping"); + cmd.set_command_line( + "$USER1$/check_icmp -H $HOSTADDRESS$ -n $_HOSTPACKETNUMBER$ -w " + "$_HOSTWARNING$ -c $_HOSTCRITICAL$"); + cmd_aply.add_object(cmd); + + hst_child.set_host_name("child_host"); + hst_child.set_address("127.0.0.1"); + hst_child_hlp.hook("parents", "parent_host"); + hst_child.set_host_id(1); + hst_child_hlp.hook("_PACKETNUMBER", "42"); + hst_child_hlp.hook("_WARNING", "200,20%"); + hst_child_hlp.hook("_CRITICAL", "400,50%"); + hst_child.set_check_command("base_centreon_ping"); + hst_aply.add_object(hst_child); + + hst_parent.set_host_name("parent_host"); + hst_parent.set_address("127.0.0.1"); + hst_parent.set_host_id(2); + hst_parent_hlp.hook("_PACKETNUMBER", "42"); + hst_parent_hlp.hook("_WARNING", "200,20%"); + hst_parent_hlp.hook("_CRITICAL", "400,50%"); + hst_parent.set_check_command("base_centreon_ping"); + hst_aply.add_object(hst_parent); + + ASSERT_EQ(engine::host::hosts.size(), 2u); + + hst_aply.expand_objects(pb_config); + hst_aply.resolve_object(hst_child, err); + hst_aply.resolve_object(hst_parent, err); + + host_map::iterator child = engine::host::hosts.find("child_host"); + host_map::iterator parent = engine::host::hosts.find("parent_host"); + + ASSERT_EQ(parent->second->child_hosts.size(), 1u); + ASSERT_EQ(child->second->parent_hosts.size(), 1u); + + engine::host::host_state result; + parent->second->run_sync_check_3x(&result, 0, 0, 0); + ASSERT_EQ(parent->second->get_current_state(), engine::host::state_down); + child->second->run_sync_check_3x(&result, 0, 0, 0); + ASSERT_EQ(child->second->get_current_state(), + engine::host::state_unreachable); +} diff --git a/engine/tests/configuration/applier/applier-pbhostdependency.cc b/engine/tests/configuration/applier/applier-pbhostdependency.cc new file mode 100644 index 00000000000..664aac6f274 --- /dev/null +++ b/engine/tests/configuration/applier/applier-pbhostdependency.cc @@ -0,0 +1,155 @@ +/** + * Copyright 2019, 2023 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include + +#include + +#include "../../test_engine.hh" +#include "../../timeperiod/utils.hh" +#include "com/centreon/clib.hh" +#include "com/centreon/engine/checks/checker.hh" +#include "com/centreon/engine/commands/commands.hh" +#include "com/centreon/engine/config.hh" +#include "com/centreon/engine/configuration/applier/command.hh" +#include "com/centreon/engine/configuration/applier/contact.hh" +#include "com/centreon/engine/configuration/applier/contactgroup.hh" +#include "com/centreon/engine/configuration/applier/host.hh" +#include "com/centreon/engine/configuration/applier/hostdependency.hh" +#include "com/centreon/engine/configuration/applier/state.hh" +#include "com/centreon/engine/configuration/applier/timeperiod.hh" +#include "com/centreon/engine/globals.hh" +#include "com/centreon/engine/serviceescalation.hh" +#include "com/centreon/engine/timezone_manager.hh" +#include "common/engine_conf/host_helper.hh" +#include "common/engine_conf/hostdependency_helper.hh" +#include "common/engine_conf/service_helper.hh" +#include "common/engine_conf/state_helper.hh" +#include "helper.hh" + +using namespace com::centreon; +using namespace com::centreon::engine; +using namespace com::centreon::engine::configuration; +using namespace com::centreon::engine::configuration::applier; + +extern configuration::state* config; + +class HostDependency : public TestEngine { + public: + void SetUp() override { + init_config_state(); + + configuration::applier::contact ct_aply; + configuration::Contact ctct{new_pb_configuration_contact("admin", true)}; + configuration::error_cnt err; + ct_aply.add_object(ctct); + ct_aply.expand_objects(pb_config); + ct_aply.resolve_object(ctct, err); + + configuration::applier::host hst_aply; + + configuration::Host hst1{new_pb_configuration_host("host1", "admin", 18)}; + hst_aply.add_object(hst1); + hst_aply.resolve_object(hst1, err); + + configuration::Host hst2{new_pb_configuration_host("host2", "admin", 19)}; + hst_aply.add_object(hst2); + hst_aply.resolve_object(hst2, err); + + configuration::Host hst3{new_pb_configuration_host("host3", "admin", 20)}; + hst_aply.add_object(hst3); + hst_aply.resolve_object(hst3, err); + } + + void TearDown() override { deinit_config_state(); } +}; + +TEST_F(HostDependency, PbCircularDependency2) { + configuration::error_cnt err; + configuration::applier::hostdependency hd_aply; + configuration::Hostdependency hd1{ + new_pb_configuration_hostdependency("host1", "host2")}; + hd_aply.expand_objects(pb_config); + hd_aply.add_object(hd1); + hd_aply.resolve_object(hd1, err); + + configuration::Hostdependency hd2{ + new_pb_configuration_hostdependency("host2", "host1")}; + hd_aply.expand_objects(pb_config); + hd_aply.add_object(hd2); + hd_aply.resolve_object(hd2, err); + + ASSERT_EQ(pre_flight_circular_check(&err.config_warnings, &err.config_errors), + ERROR); +} + +TEST_F(HostDependency, PbCircularDependency3) { + configuration::applier::hostdependency hd_aply; + configuration::Hostdependency hd1{ + new_pb_configuration_hostdependency("host1", "host2")}; + hd_aply.expand_objects(pb_config); + hd_aply.add_object(hd1); + configuration::error_cnt err; + hd_aply.resolve_object(hd1, err); + + configuration::Hostdependency hd2{ + new_pb_configuration_hostdependency("host2", "host3")}; + hd_aply.expand_objects(pb_config); + hd_aply.add_object(hd2); + hd_aply.resolve_object(hd2, err); + + configuration::Hostdependency hd3{ + new_pb_configuration_hostdependency("host3", "host1")}; + hd_aply.expand_objects(pb_config); + hd_aply.add_object(hd3); + hd_aply.resolve_object(hd3, err); + + ASSERT_EQ(pre_flight_circular_check(&err.config_warnings, &err.config_errors), + ERROR); +} + +TEST_F(HostDependency, PbRemoveHostdependency) { + configuration::applier::hostdependency hd_aply; + configuration::Hostdependency hd1{ + new_pb_configuration_hostdependency("host1", "host2")}; + hd_aply.expand_objects(pb_config); + hd_aply.add_object(hd1); + configuration::error_cnt err; + hd_aply.resolve_object(hd1, err); + + ASSERT_EQ(engine::hostdependency::hostdependencies.size(), 1); + hd_aply.remove_object(0); + ASSERT_EQ(engine::hostdependency::hostdependencies.size(), 0); +} + +TEST_F(HostDependency, PbExpandHostdependency) { + configuration::State s; + configuration::Hostdependency hd{ + new_pb_configuration_hostdependency("host1,host3,host5", "host2,host6")}; + auto* new_hd = s.add_hostdependencies(); + new_hd->CopyFrom(std::move(hd)); + configuration::applier::hostdependency hd_aply; + hd_aply.expand_objects(s); + ASSERT_EQ(s.hostdependencies().size(), 6); + ASSERT_TRUE(std::all_of(s.hostdependencies().begin(), + s.hostdependencies().end(), [](const auto& hd) { + return hd.hostgroups().data().empty() && + hd.dependent_hostgroups().data().empty(); + })); +} diff --git a/engine/tests/configuration/applier/applier-pbhostescalation.cc b/engine/tests/configuration/applier/applier-pbhostescalation.cc new file mode 100644 index 00000000000..36ec197ed10 --- /dev/null +++ b/engine/tests/configuration/applier/applier-pbhostescalation.cc @@ -0,0 +1,118 @@ +/* + * Copyright 2023 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include + +#include "com/centreon/engine/configuration/applier/host.hh" +#include "com/centreon/engine/configuration/applier/hostescalation.hh" +#include "com/centreon/engine/host.hh" +#include "com/centreon/engine/hostescalation.hh" +#include "common/engine_conf/host_helper.hh" +#include "common/engine_conf/hostescalation_helper.hh" +#include "common/engine_conf/state_helper.hh" +#include "helper.hh" + +using namespace com::centreon; +using namespace com::centreon::engine; + +class PbApplierHostEscalation : public ::testing::Test { + public: + void SetUp() override { init_config_state(); } + + void TearDown() override { deinit_config_state(); } +}; + +TEST_F(PbApplierHostEscalation, PbAddEscalation) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst_aply.add_object(hst); + ASSERT_EQ(host::hosts.size(), 1u); + + configuration::applier::hostescalation he_apply; + configuration::Hostescalation he; + configuration::hostescalation_helper he_hlp(&he); + he_hlp.hook("host_name", "test_host"); + he.set_first_notification(4); + he_apply.add_object(he); + ASSERT_EQ(hostescalation::hostescalations.size(), 1u); + he.set_first_notification(8); + he_apply.add_object(he); + ASSERT_EQ(hostescalation::hostescalations.size(), 2u); +} + +TEST_F(PbApplierHostEscalation, PbRemoveEscalation) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst_aply.add_object(hst); + ASSERT_EQ(host::hosts.size(), 1u); + + configuration::applier::hostescalation he_apply; + configuration::Hostescalation he; + configuration::hostescalation_helper he_hlp(&he); + he_hlp.hook("host_name", "test_host"); + he.set_first_notification(4); + he_apply.add_object(he); + ASSERT_EQ(hostescalation::hostescalations.size(), 1u); + he.set_first_notification(8); + he_apply.add_object(he); + ASSERT_EQ(hostescalation::hostescalations.size(), 2u); + + he_apply.remove_object(1); + ASSERT_EQ(hostescalation::hostescalations.size(), 1u); + he_apply.remove_object(0); + ASSERT_EQ(hostescalation::hostescalations.size(), 0u); +} + +TEST_F(PbApplierHostEscalation, RemoveEscalationFromRemovedHost) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst_aply.add_object(hst); + ASSERT_EQ(host::hosts.size(), 1u); + + configuration::applier::hostescalation he_apply; + configuration::Hostescalation he; + configuration::hostescalation_helper he_hlp(&he); + he_hlp.hook("host_name", "test_host"); + he.set_first_notification(4); + he_apply.add_object(he); + ASSERT_EQ(hostescalation::hostescalations.size(), 1u); + he.set_first_notification(8); + he_apply.add_object(he); + ASSERT_EQ(hostescalation::hostescalations.size(), 2u); + + hst_aply.remove_object(0); + ASSERT_EQ(host::hosts.size(), 0u); + + he_apply.remove_object(0); + ASSERT_EQ(hostescalation::hostescalations.size(), 1u); + he_apply.remove_object(0); + ASSERT_EQ(hostescalation::hostescalations.size(), 0u); +} diff --git a/engine/tests/configuration/applier/applier-pbhostgroup.cc b/engine/tests/configuration/applier/applier-pbhostgroup.cc new file mode 100644 index 00000000000..ca84fd35c4d --- /dev/null +++ b/engine/tests/configuration/applier/applier-pbhostgroup.cc @@ -0,0 +1,190 @@ +/* + * Copyright 2017 - 2019 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include +#include "com/centreon/engine/configuration/applier/command.hh" +#include "com/centreon/engine/configuration/applier/host.hh" +#include "com/centreon/engine/configuration/applier/hostgroup.hh" +#include "com/centreon/engine/globals.hh" +#include "com/centreon/engine/macros/grab_host.hh" +#include "com/centreon/engine/timezone_manager.hh" +#include "common/engine_conf/host_helper.hh" +#include "common/engine_conf/hostgroup_helper.hh" +#include "helper.hh" + +using namespace com::centreon; +using namespace com::centreon::engine; +using namespace com::centreon::engine::configuration; +using namespace com::centreon::engine::configuration::applier; + +class ApplierHostGroup : public ::testing::Test { + public: + void SetUp() override { init_config_state(); } + + void TearDown() override { deinit_config_state(); } +}; + +// Given host configuration without host_id +// Then the applier add_object throws an exception. +TEST_F(ApplierHostGroup, PbNewHostGroup) { + configuration::error_cnt err; + configuration::applier::hostgroup hg_aply; + configuration::applier::host hst_aply; + configuration::Hostgroup hg; + configuration::hostgroup_helper hg_hlp(&hg); + configuration::Host hst_a; + configuration::host_helper hst_a_hlp(&hst_a); + configuration::Host hst_b; + configuration::host_helper hst_b_hlp(&hst_b); + configuration::Host hst_c; + configuration::host_helper hst_c_hlp(&hst_c); + + hst_a.set_host_name("a"); + hst_a.set_host_id(1); + hst_a.set_address("127.0.0.1"); + + hst_b.set_host_name("b"); + hst_b.set_host_id(2); + hst_b.set_address("127.0.0.1"); + + hst_c.set_host_name("c"); + hst_c.set_host_id(3); + hst_c.set_address("127.0.0.1"); + hst_aply.add_object(hst_a); + hst_aply.add_object(hst_b); + hst_aply.add_object(hst_c); + + hg.set_hostgroup_name("temphg"); + hg_hlp.hook("members", "a,b,c"); + ASSERT_NO_THROW(hg_aply.add_object(hg)); + + ASSERT_NO_THROW(hst_aply.expand_objects(pb_config)); + ASSERT_NO_THROW(hst_aply.expand_objects(pb_config)); + ASSERT_NO_THROW(hst_aply.expand_objects(pb_config)); + ASSERT_NO_THROW(hg_aply.expand_objects(pb_config)); + + ASSERT_NO_THROW(hst_aply.resolve_object(hst_a, err)); + ASSERT_NO_THROW(hst_aply.resolve_object(hst_b, err)); + ASSERT_NO_THROW(hst_aply.resolve_object(hst_c, err)); + ASSERT_NO_THROW(hg_aply.resolve_object(hg, err)); + + ASSERT_EQ(engine::hostgroup::hostgroups.size(), 1u); + ASSERT_EQ(engine::hostgroup::hostgroups.begin()->second->members.size(), 3u); +} + +// Given a host configuration +// When we change the host name in the configuration +// Then the applier modify_object changes the host name without changing +// the host id. +TEST_F(ApplierHostGroup, PbHostRenamed) { + configuration::error_cnt err; + configuration::applier::hostgroup hg_aply; + configuration::applier::host hst_aply; + configuration::Hostgroup hg; + configuration::hostgroup_helper hg_hlp(&hg); + configuration::Host hst_a; + configuration::host_helper hst_a_hlp(&hst_a); + configuration::Host hst_c; + configuration::host_helper hst_c_hlp(&hst_c); + + hst_a.set_host_name("a"); + hst_a.set_host_id(1); + hst_a.set_address("127.0.0.1"); + + hst_c.set_host_name("c"); + hst_c.set_host_id(2); + hst_c.set_address("127.0.0.1"); + + hst_aply.add_object(hst_a); + hst_aply.add_object(hst_c); + + hg.set_hostgroup_name("temphg"); + hg_hlp.hook("members", "a,c"); + ASSERT_NO_THROW(hg_aply.add_object(hg)); + + ASSERT_NO_THROW(hst_aply.expand_objects(pb_config)); + ASSERT_NO_THROW(hst_aply.expand_objects(pb_config)); + ASSERT_NO_THROW(hg_aply.expand_objects(pb_config)); + + ASSERT_NO_THROW(hst_aply.resolve_object(hst_a, err)); + ASSERT_NO_THROW(hst_aply.resolve_object(hst_c, err)); + ASSERT_NO_THROW(hg_aply.resolve_object(hg, err)); + + hg.mutable_members()->clear_data(); + hg_hlp.hook("members", "c"); + hg_aply.modify_object(&pb_config.mutable_hostgroups()->at(0), hg); + + ASSERT_NO_THROW(hst_aply.expand_objects(pb_config)); + ASSERT_NO_THROW(hst_aply.expand_objects(pb_config)); + ASSERT_NO_THROW(hg_aply.expand_objects(pb_config)); + + ASSERT_EQ(engine::hostgroup::hostgroups.size(), 1u); + ASSERT_EQ(engine::hostgroup::hostgroups.begin()->second->members.size(), 1u); + ASSERT_EQ(engine::hostgroup::hostgroups.begin()->second->get_group_name(), + "temphg"); +} + +TEST_F(ApplierHostGroup, PbHostRemoved) { + configuration::error_cnt err; + configuration::applier::hostgroup hg_aply; + configuration::applier::host hst_aply; + configuration::Hostgroup hg; + configuration::hostgroup_helper hg_hlp(&hg); + configuration::Host hst_a; + configuration::host_helper hst_a_hlp(&hst_a); + configuration::Host hst_c; + configuration::host_helper hst_c_hlp(&hst_c); + + hst_a.set_host_name("a"); + hst_a.set_host_id(1); + hst_a.set_address("127.0.0.1"); + + hst_c.set_host_name("c"); + hst_c.set_host_id(2); + hst_c.set_address("127.0.0.1"); + + hst_aply.add_object(hst_a); + hst_aply.add_object(hst_c); + + hg.set_hostgroup_name("temphg"); + hg_hlp.hook("members", "a,c"); + ASSERT_NO_THROW(hg_aply.add_object(hg)); + + ASSERT_NO_THROW(hst_aply.expand_objects(pb_config)); + ASSERT_NO_THROW(hst_aply.expand_objects(pb_config)); + ASSERT_NO_THROW(hg_aply.expand_objects(pb_config)); + + ASSERT_NO_THROW(hst_aply.resolve_object(hst_a, err)); + ASSERT_NO_THROW(hst_aply.resolve_object(hst_c, err)); + ASSERT_NO_THROW(hg_aply.resolve_object(hg, err)); + + engine::hostgroup* hg_obj{engine::hostgroup::hostgroups["temphg"].get()}; + ASSERT_EQ(hg_obj->members.size(), 2u); + ASSERT_NO_THROW(hst_aply.remove_object(0)); + ASSERT_EQ(hg_obj->members.size(), 1u); + + hg.mutable_members()->clear_data(); + hg_hlp.hook("members", "c"); + ASSERT_NO_THROW( + hg_aply.modify_object(&pb_config.mutable_hostgroups()->at(0), hg)); + + hg_aply.remove_object(0); + ASSERT_TRUE(pb_config.hostgroups().empty()); + ASSERT_TRUE(engine::hostgroup::hostgroups.empty()); +} diff --git a/engine/tests/configuration/applier/applier-pblog.cc b/engine/tests/configuration/applier/applier-pblog.cc new file mode 100644 index 00000000000..ff384aa2825 --- /dev/null +++ b/engine/tests/configuration/applier/applier-pblog.cc @@ -0,0 +1,578 @@ +/** + * Copyright 2022 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include +#include +#include "com/centreon/engine/configuration/applier/hostescalation.hh" +#include "common/engine_conf/parser.hh" +#include "common/engine_conf/state.pb.h" +#include "common/engine_conf/state_helper.hh" +#include "helper.hh" + +using namespace com::centreon; +using namespace com::centreon::engine; + +class ApplierLog : public ::testing::Test { + public: + void SetUp() override { init_config_state(); } + + void TearDown() override { deinit_config_state(); } +}; + +// Given host configuration without host_id +// Then the applier add_object throws an exception. +TEST_F(ApplierLog, logV2Enabled) { + configuration::parser parser; + configuration::State st; + configuration::state_helper st_hlp(&st); + configuration::error_cnt err; + + ASSERT_EQ(st.log_v2_enabled(), true); + + std::remove("/tmp/test-config.cfg"); + + std::ofstream ofs("/tmp/test-config.cfg"); + ofs << "log_v2_enabled=0" << std::endl; + ofs.close(); + + parser.parse("/tmp/test-config.cfg", &st, err); + std::remove("/tmp/test-config.cfg"); + + ASSERT_EQ(st.log_v2_enabled(), false); +} + +TEST_F(ApplierLog, logLegacyEnabled) { + configuration::parser parser; + configuration::State st; + configuration::state_helper st_hlp(&st); + configuration::error_cnt err; + + ASSERT_EQ(st.log_legacy_enabled(), true); + + std::remove("/tmp/test-config.cfg"); + + std::ofstream ofs("/tmp/test-config.cfg"); + ofs << "log_legacy_enabled=0" << std::endl; + ofs.close(); + + parser.parse("/tmp/test-config.cfg", &st, err); + std::remove("/tmp/test-config.cfg"); + + ASSERT_EQ(st.log_legacy_enabled(), false); +} + +TEST_F(ApplierLog, logV2Logger) { + configuration::error_cnt err; + configuration::parser parser; + configuration::State st; + configuration::state_helper st_hlp(&st); + + ASSERT_EQ(st.log_v2_logger(), "file"); + + std::remove("/tmp/test-config.cfg"); + + std::ofstream ofs("/tmp/test-config.cfg"); + ofs << "log_v2_logger=syslog" << std::endl; + ofs.close(); + + parser.parse("/tmp/test-config.cfg", &st, err); + std::remove("/tmp/test-config.cfg"); + + ASSERT_EQ(st.log_v2_logger(), "syslog"); +} + +TEST_F(ApplierLog, logLevelFunctions) { + configuration::error_cnt err; + configuration::parser parser; + configuration::State st; + configuration::state_helper st_hlp(&st); + + ASSERT_EQ(st.log_level_functions(), configuration::LogLevel::error); + + std::remove("/tmp/test-config.cfg"); + + std::ofstream ofs("/tmp/test-config.cfg"); + ofs << "log_level_functions=trace" << std::endl; + ofs.close(); + + parser.parse("/tmp/test-config.cfg", &st, err); + + ASSERT_EQ(st.log_level_functions(), configuration::LogLevel::trace); + + ofs.open("/tmp/test-config.cfg"); + ofs << "log_level_functions=tracerrrr" << std::endl; + ofs.close(); + + ASSERT_THROW(parser.parse("/tmp/test-config.cfg", &st, err), std::exception); + std::remove("/tmp/test-config.cfg"); +} + +TEST_F(ApplierLog, logLevelConfig) { + configuration::parser parser; + configuration::State st; + configuration::state_helper st_hlp(&st); + configuration::error_cnt err; + + ASSERT_EQ(st.log_level_config(), configuration::LogLevel::info); + + std::remove("/tmp/test-config.cfg"); + + std::ofstream ofs("/tmp/test-config.cfg"); + ofs << "log_level_config=debug" << std::endl; + ofs.close(); + + parser.parse("/tmp/test-config.cfg", &st, err); + + ASSERT_EQ(st.log_level_config(), configuration::LogLevel::debug); + + ofs.open("/tmp/test-config.cfg"); + ofs << "log_level_config=tracerrrr" << std::endl; + ofs.close(); + + ASSERT_THROW(parser.parse("/tmp/test-config.cfg", &st, err), std::exception); + std::remove("/tmp/test-config.cfg"); + + // testing::internal::CaptureStdout(); + // parser.parse("/tmp/test-config.cfg", st); + // std::remove("/tmp/test-config.cfg"); + + // std::string out{testing::internal::GetCapturedStdout()}; + // std::cout << out << std::endl; + // size_t step1{ + // out.find("[config] [error] error wrong level setted for " + // "log_level_config")}; + // ASSERT_NE(step1, std::string::npos); + // ASSERT_EQ(st.log_level_config(), "debug"); +} + +TEST_F(ApplierLog, logLevelEvents) { + configuration::error_cnt err; + configuration::parser parser; + configuration::State st; + configuration::state_helper st_hlp(&st); + + ASSERT_EQ(st.log_level_events(), configuration::LogLevel::info); + + std::remove("/tmp/test-config.cfg"); + + std::ofstream ofs("/tmp/test-config.cfg"); + ofs << "log_level_events=warning" << std::endl; + ofs.close(); + + parser.parse("/tmp/test-config.cfg", &st, err); + + ASSERT_EQ(st.log_level_events(), configuration::LogLevel::warning); + + ofs.open("/tmp/test-config.cfg"); + ofs << "log_level_events=tracerrrr" << std::endl; + ofs.close(); + ASSERT_THROW(parser.parse("/tmp/test-config.cfg", &st, err), std::exception); + std::remove("/tmp/test-config.cfg"); + // testing::internal::CaptureStdout(); + // parser.parse("/tmp/test-config.cfg", st); + // std::remove("/tmp/test-config.cfg"); + + // std::string out{testing::internal::GetCapturedStdout()}; + // std::cout << out << std::endl; + // size_t step1{ + // out.find("[config] [error] error wrong level setted for " + // "log_level_events")}; + // ASSERT_NE(step1, std::string::npos); + // ASSERT_EQ(st.log_level_events(), "warning"); +} + +TEST_F(ApplierLog, logLevelChecks) { + configuration::parser parser; + configuration::State st; + configuration::state_helper st_hlp(&st); + configuration::error_cnt err; + + ASSERT_EQ(st.log_level_checks(), configuration::LogLevel::info); + + std::remove("/tmp/test-config.cfg"); + + std::ofstream ofs("/tmp/test-config.cfg"); + ofs << "log_level_checks=error" << std::endl; + ofs.close(); + + parser.parse("/tmp/test-config.cfg", &st, err); + + ASSERT_EQ(st.log_level_checks(), configuration::LogLevel::error); + + ofs.open("/tmp/test-config.cfg"); + ofs << "log_level_checks=tracerrrr" << std::endl; + ofs.close(); + ASSERT_THROW(parser.parse("/tmp/test-config.cfg", &st, err), std::exception); + std::remove("/tmp/test-config.cfg"); + // testing::internal::CaptureStdout(); + // parser.parse("/tmp/test-config.cfg", st); + // std::remove("/tmp/test-config.cfg"); + + // std::string out{testing::internal::GetCapturedStdout()}; + // std::cout << out << std::endl; + // size_t step1{ + // out.find("[config] [error] error wrong level setted for " + // "log_level_checks")}; + // ASSERT_NE(step1, std::string::npos); + // ASSERT_EQ(st.log_level_checks(), "error"); +} + +TEST_F(ApplierLog, logLevelNotifications) { + configuration::parser parser; + configuration::State st; + configuration::state_helper st_hlp(&st); + configuration::error_cnt err; + + ASSERT_EQ(st.log_level_notifications(), configuration::LogLevel::error); + + std::remove("/tmp/test-config.cfg"); + + std::ofstream ofs("/tmp/test-config.cfg"); + ofs << "log_level_notifications=off" << std::endl; + ofs.close(); + + parser.parse("/tmp/test-config.cfg", &st, err); + + ASSERT_EQ(st.log_level_notifications(), configuration::LogLevel::off); + + ofs.open("/tmp/test-config.cfg"); + ofs << "log_level_notifications=tracerrrr" << std::endl; + ofs.close(); + ASSERT_THROW(parser.parse("/tmp/test-config.cfg", &st, err), std::exception); + std::remove("/tmp/test-config.cfg"); + // testing::internal::CaptureStdout(); + // parser.parse("/tmp/test-config.cfg", st); + // std::remove("/tmp/test-config.cfg"); + + // std::string out{testing::internal::GetCapturedStdout()}; + // std::cout << out << std::endl; + // size_t step1{ + // out.find("[config] [error] error wrong level setted for " + // "log_level_notifications")}; + // ASSERT_NE(step1, std::string::npos); + // ASSERT_EQ(st.log_level_notifications(), "off"); +} + +TEST_F(ApplierLog, logLevelEventBroker) { + configuration::parser parser; + configuration::State st; + configuration::state_helper st_hlp(&st); + configuration::error_cnt err; + + ASSERT_EQ(st.log_level_eventbroker(), configuration::LogLevel::error); + + std::remove("/tmp/test-config.cfg"); + + std::ofstream ofs("/tmp/test-config.cfg"); + ofs << "log_level_eventbroker=critical" << std::endl; + ofs.close(); + + parser.parse("/tmp/test-config.cfg", &st, err); + + ASSERT_EQ(st.log_level_eventbroker(), configuration::LogLevel::critical); + + ofs.open("/tmp/test-config.cfg"); + ofs << "log_level_eventbroker=tracerrrr" << std::endl; + ofs.close(); + ASSERT_THROW(parser.parse("/tmp/test-config.cfg", &st, err), std::exception); + std::remove("/tmp/test-config.cfg"); + // testing::internal::CaptureStdout(); + // parser.parse("/tmp/test-config.cfg", st); + // std::remove("/tmp/test-config.cfg"); + + // std::string out{testing::internal::GetCapturedStdout()}; + // std::cout << out << std::endl; + // size_t step1{ + // out.find("[config] [error] error wrong level setted for " + // "log_level_eventbroker")}; + // ASSERT_NE(step1, std::string::npos); + // ASSERT_EQ(st.log_level_eventbroker(), "critical"); +} + +TEST_F(ApplierLog, logLevelExternalCommand) { + configuration::parser parser; + configuration::State st; + configuration::state_helper st_hlp(&st); + configuration::error_cnt err; + + ASSERT_EQ(st.log_level_external_command(), configuration::LogLevel::error); + + std::remove("/tmp/test-config.cfg"); + + std::ofstream ofs("/tmp/test-config.cfg"); + ofs << "log_level_external_command=trace" << std::endl; + ofs.close(); + + parser.parse("/tmp/test-config.cfg", &st, err); + + ASSERT_EQ(st.log_level_external_command(), configuration::LogLevel::trace); + + ofs.open("/tmp/test-config.cfg"); + ofs << "log_level_external_command=tracerrrr" << std::endl; + ofs.close(); + ASSERT_THROW(parser.parse("/tmp/test-config.cfg", &st, err), std::exception); + std::remove("/tmp/test-config.cfg"); + // testing::internal::CaptureStdout(); + // parser.parse("/tmp/test-config.cfg", st); + // std::remove("/tmp/test-config.cfg"); + + // std::string out{testing::internal::GetCapturedStdout()}; + // std::cout << out << std::endl; + // size_t step1{ + // out.find("[config] [error] error wrong level setted for " + // "log_level_external_command")}; + // ASSERT_NE(step1, std::string::npos); + // ASSERT_EQ(st.log_level_external_command(), "trace"); +} + +TEST_F(ApplierLog, logLevelCommands) { + configuration::parser parser; + configuration::State st; + configuration::state_helper st_hlp(&st); + configuration::error_cnt err; + + ASSERT_EQ(st.log_level_commands(), configuration::LogLevel::error); + + std::remove("/tmp/test-config.cfg"); + + std::ofstream ofs("/tmp/test-config.cfg"); + ofs << "log_level_commands=debug" << std::endl; + ofs.close(); + + parser.parse("/tmp/test-config.cfg", &st, err); + + ASSERT_EQ(st.log_level_commands(), configuration::LogLevel::debug); + + ofs.open("/tmp/test-config.cfg"); + ofs << "log_level_commands=tracerrrr" << std::endl; + ofs.close(); + ASSERT_THROW(parser.parse("/tmp/test-config.cfg", &st, err), std::exception); + std::remove("/tmp/test-config.cfg"); + // testing::internal::CaptureStdout(); + // parser.parse("/tmp/test-config.cfg", st); + // std::remove("/tmp/test-config.cfg"); + + // std::string out{testing::internal::GetCapturedStdout()}; + // std::cout << out << std::endl; + // size_t step1{ + // out.find("[config] [error] error wrong level setted for " + // "log_level_commands")}; + // ASSERT_NE(step1, std::string::npos); + // ASSERT_EQ(st.log_level_commands(), "debug"); +} + +TEST_F(ApplierLog, logLevelDowntimes) { + configuration::parser parser; + configuration::State st; + configuration::state_helper st_hlp(&st); + configuration::error_cnt err; + + ASSERT_EQ(st.log_level_downtimes(), configuration::LogLevel::error); + + std::remove("/tmp/test-config.cfg"); + + std::ofstream ofs("/tmp/test-config.cfg"); + ofs << "log_level_downtimes=warning" << std::endl; + ofs.close(); + + parser.parse("/tmp/test-config.cfg", &st, err); + + ASSERT_EQ(st.log_level_downtimes(), configuration::LogLevel::warning); + + ofs.open("/tmp/test-config.cfg"); + ofs << "log_level_downtimes=tracerrrr" << std::endl; + ofs.close(); + ASSERT_THROW(parser.parse("/tmp/test-config.cfg", &st, err), std::exception); + std::remove("/tmp/test-config.cfg"); + // testing::internal::CaptureStdout(); + // parser.parse("/tmp/test-config.cfg", st); + // std::remove("/tmp/test-config.cfg"); + + // std::string out{testing::internal::GetCapturedStdout()}; + // std::cout << out << std::endl; + // size_t step1{ + // out.find("[config] [error] error wrong level setted for " + // "log_level_downtimes")}; + // ASSERT_NE(step1, std::string::npos); + // ASSERT_EQ(st.log_level_downtimes(), "warning"); +} + +TEST_F(ApplierLog, logLevelComments) { + configuration::parser parser; + configuration::State st; + configuration::state_helper st_hlp(&st); + configuration::error_cnt err; + + ASSERT_EQ(st.log_level_comments(), configuration::LogLevel::error); + + std::remove("/tmp/test-config.cfg"); + + std::ofstream ofs("/tmp/test-config.cfg"); + ofs << "log_level_comments=error" << std::endl; + ofs.close(); + + parser.parse("/tmp/test-config.cfg", &st, err); + + ASSERT_EQ(st.log_level_comments(), configuration::LogLevel::error); + + ofs.open("/tmp/test-config.cfg"); + ofs << "log_level_comments=tracerrrr" << std::endl; + ofs.close(); + ASSERT_THROW(parser.parse("/tmp/test-config.cfg", &st, err), std::exception); + std::remove("/tmp/test-config.cfg"); + // testing::internal::CaptureStdout(); + // parser.parse("/tmp/test-config.cfg", st); + // std::remove("/tmp/test-config.cfg"); + + // std::string out{testing::internal::GetCapturedStdout()}; + // std::cout << out << std::endl; + // size_t step1{ + // out.find("[config] [error] error wrong level setted for " + // "log_level_comments")}; + // ASSERT_NE(step1, std::string::npos); + // ASSERT_EQ(st.log_level_comments(), "error"); +} + +TEST_F(ApplierLog, logLevelMacros) { + configuration::parser parser; + configuration::State st; + configuration::state_helper st_hlp(&st); + configuration::error_cnt err; + + ASSERT_EQ(st.log_level_macros(), configuration::LogLevel::error); + + std::remove("/tmp/test-config.cfg"); + + std::ofstream ofs("/tmp/test-config.cfg"); + ofs << "log_level_macros=critical" << std::endl; + ofs.close(); + + parser.parse("/tmp/test-config.cfg", &st, err); + + ASSERT_EQ(st.log_level_macros(), configuration::LogLevel::critical); + + ofs.open("/tmp/test-config.cfg"); + ofs << "log_level_macros=tracerrrr" << std::endl; + ofs.close(); + ASSERT_THROW(parser.parse("/tmp/test-config.cfg", &st, err), std::exception); + std::remove("/tmp/test-config.cfg"); + // testing::internal::CaptureStdout(); + // parser.parse("/tmp/test-config.cfg", st); + // std::remove("/tmp/test-config.cfg"); + + // std::string out{testing::internal::GetCapturedStdout()}; + // std::cout << out << std::endl; + // size_t step1{ + // out.find("[config] [error] error wrong level setted for " + // "log_level_macros")}; + // ASSERT_NE(step1, std::string::npos); + // ASSERT_EQ(st.log_level_macros(), "critical"); +} + +TEST_F(ApplierLog, logLevelProcess) { + configuration::parser parser; + configuration::State st; + configuration::state_helper st_hlp(&st); + configuration::error_cnt err; + + ASSERT_EQ(st.log_level_process(), configuration::LogLevel::info); + + std::remove("/tmp/test-config.cfg"); + + std::ofstream ofs("/tmp/test-config.cfg"); + ofs << "log_level_process=off" << std::endl; + ofs.close(); + + parser.parse("/tmp/test-config.cfg", &st, err); + + ASSERT_EQ(st.log_level_process(), configuration::LogLevel::off); + + ofs.open("/tmp/test-config.cfg"); + ofs << "log_level_process=tracerrrr" << std::endl; + ofs.close(); + ASSERT_THROW(parser.parse("/tmp/test-config.cfg", &st, err), std::exception); + std::remove("/tmp/test-config.cfg"); + // testing::internal::CaptureStdout(); + // parser.parse("/tmp/test-config.cfg", st); + // std::remove("/tmp/test-config.cfg"); + + // std::string out{testing::internal::GetCapturedStdout()}; + // std::cout << out << std::endl; + // size_t step1{ + // out.find("[config] [error] error wrong level setted for " + // "log_level_process")}; + // ASSERT_NE(step1, std::string::npos); + // ASSERT_EQ(st.log_level_process(), "off"); +} + +TEST_F(ApplierLog, logLevelRuntime) { + configuration::parser parser; + configuration::State st; + configuration::state_helper st_hlp(&st); + configuration::error_cnt err; + + ASSERT_EQ(st.log_level_runtime(), configuration::LogLevel::error); + + std::remove("/tmp/test-config.cfg"); + + std::ofstream ofs("/tmp/test-config.cfg"); + ofs << "log_level_runtime=off" << std::endl; + ofs.close(); + + parser.parse("/tmp/test-config.cfg", &st, err); + + ASSERT_EQ(st.log_level_runtime(), configuration::LogLevel::off); + + ofs.open("/tmp/test-config.cfg"); + ofs << "log_level_runtime=tracerrrr" << std::endl; + ofs.close(); + ASSERT_THROW(parser.parse("/tmp/test-config.cfg", &st, err), std::exception); + std::remove("/tmp/test-config.cfg"); + // testing::internal::CaptureStdout(); + // parser.parse("/tmp/test-config.cfg", st); + // std::remove("/tmp/test-config.cfg"); + + // std::string out{testing::internal::GetCapturedStdout()}; + // std::cout << out << std::endl; + // size_t step1{ + // out.find("[config] [error] error wrong level setted for " + // "log_level_runtime")}; + // ASSERT_NE(step1, std::string::npos); + // ASSERT_EQ(st.log_level_runtime(), "off"); +} + +TEST_F(ApplierLog, logFile) { + configuration::parser parser; + configuration::State st; + configuration::state_helper st_hlp(&st); + configuration::error_cnt err; + + ASSERT_EQ(st.log_file(), DEFAULT_LOG_FILE); + + std::remove("/tmp/test-config.cfg"); + + std::ofstream ofs("/tmp/test-config.cfg"); + ofs << "log_file=/tmp/centengine.log" << std::endl; + ofs.close(); + + parser.parse("/tmp/test-config.cfg", &st, err); + std::remove("/tmp/test-config.cfg"); + + ASSERT_EQ(st.log_file(), "/tmp/centengine.log"); +} diff --git a/engine/tests/configuration/applier/applier-pbservice.cc b/engine/tests/configuration/applier/applier-pbservice.cc new file mode 100644 index 00000000000..62045656d29 --- /dev/null +++ b/engine/tests/configuration/applier/applier-pbservice.cc @@ -0,0 +1,838 @@ +/* + * Copyright 2019, 2023 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include + +#include "../../test_engine.hh" +#include "../../timeperiod/utils.hh" +#include "com/centreon/engine/configuration/applier/command.hh" +#include "com/centreon/engine/configuration/applier/contact.hh" +#include "com/centreon/engine/configuration/applier/contactgroup.hh" +#include "com/centreon/engine/configuration/applier/host.hh" +#include "com/centreon/engine/configuration/applier/service.hh" +#include "com/centreon/engine/configuration/applier/tag.hh" +#include "com/centreon/engine/contact.hh" +#include "com/centreon/engine/globals.hh" +#include "com/centreon/engine/host.hh" +#include "com/centreon/engine/service.hh" +#include "common/engine_conf/command_helper.hh" +#include "common/engine_conf/host_helper.hh" +#include "common/engine_conf/message_helper.hh" +#include "common/engine_conf/tag_helper.hh" +#include "helper.hh" + +using namespace com::centreon; +using namespace com::centreon::engine; +using namespace com::centreon::engine::configuration; +using namespace com::centreon::engine::configuration::applier; + +class ApplierService : public TestEngine { + public: + void SetUp() override { init_config_state(); } + + void TearDown() override { deinit_config_state(); } +}; + +// Given service configuration with an host not defined +// Then the applier add_object throws an exception because it needs a service +// command. +TEST_F(ApplierService, PbNewServiceWithHostNotDefinedFromConfig) { + configuration::applier::service svc_aply; + configuration::Service svc; + configuration::service_helper svc_hlp(&svc); + svc.set_host_name("test_host"); + svc.set_service_description("test_description"); + svc_hlp.hook("_TEST", "Value1"); + ASSERT_THROW(svc_aply.add_object(svc), std::exception); +} + +// Given host configuration without host_id +// Then the applier add_object throws an exception. +TEST_F(ApplierService, PbNewHostWithoutHostId) { + configuration::applier::host hst_aply; + configuration::Service svc; + configuration::service_helper svc_hlp(&svc); + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + ASSERT_THROW(hst_aply.add_object(hst), std::exception); +} + +// Given service configuration with a host defined +// Then the applier add_object creates the service +TEST_F(ApplierService, PbNewServiceFromConfig) { + configuration::applier::host hst_aply; + configuration::applier::service svc_aply; + configuration::Service svc; + configuration::service_helper svc_hlp(&svc); + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + // The host id is not given + ASSERT_THROW(hst_aply.add_object(hst), std::exception); + hst.set_host_id(1); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + svc.set_host_name("test_host"); + svc.set_service_description("test_description"); + svc.set_service_id(3); + + configuration::applier::command cmd_aply; + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + cmd.set_command_line("echo 1"); + svc.set_check_command("cmd"); + cmd_aply.add_object(cmd); + + // No need here to call svc_aply.expand_objects(*config) because the + // configuration service is not stored in configuration::state. We just have + // to set the host_id manually. + svc.set_host_id(1); + svc_aply.add_object(svc); + service_id_map const& sm(engine::service::services_by_id); + ASSERT_EQ(sm.size(), 1u); + ASSERT_EQ(sm.begin()->first.first, 1u); + ASSERT_EQ(sm.begin()->first.second, 3u); + + // Service is not resolved, host is null now. + ASSERT_TRUE(!sm.begin()->second->get_host_ptr()); + ASSERT_TRUE(sm.begin()->second->description() == "test_description"); +} + +// Given service configuration with a host defined +// Then the applier add_object creates the service +TEST_F(ApplierService, PbRenameServiceFromConfig) { + configuration::applier::host hst_aply; + configuration::applier::service svc_aply; + configuration::Service svc; + configuration::service_helper svc_hlp(&svc); + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + // The host id is not given + ASSERT_THROW(hst_aply.add_object(hst), std::exception); + hst.set_host_id(1); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + svc.set_host_name("test_host"); + svc.set_service_description("test_description"); + svc.set_service_id(3); + + configuration::applier::command cmd_aply; + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + cmd.set_command_line("echo 1"); + svc.set_check_command("cmd"); + cmd_aply.add_object(cmd); + + // We fake here the expand_object on configuration::service + svc.set_host_id(1); + + svc_aply.add_object(svc); + + svc.set_service_description("test_description2"); + svc_aply.modify_object(pb_config.mutable_services(0), svc); + svc_aply.expand_objects(pb_config); + + service_id_map const& sm(engine::service::services_by_id); + ASSERT_EQ(sm.size(), 1u); + ASSERT_EQ(sm.begin()->first.first, 1u); + ASSERT_EQ(sm.begin()->first.second, 3u); + + // Service is not resolved, host is null now. + ASSERT_TRUE(!sm.begin()->second->get_host_ptr()); + ASSERT_TRUE(sm.begin()->second->description() == "test_description2"); + + std::string s{engine::service::services[{"test_host", "test_description2"}] + ->description()}; + ASSERT_TRUE(s == "test_description2"); +} + +// Given service configuration with a host defined +// Then the applier add_object creates the service +TEST_F(ApplierService, PbRemoveServiceFromConfig) { + configuration::applier::host hst_aply; + configuration::applier::service svc_aply; + configuration::Service svc; + configuration::service_helper svc_hlp(&svc); + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + // The host id is not given + ASSERT_THROW(hst_aply.add_object(hst), std::exception); + hst.set_host_id(1); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + svc.set_host_name("test_host"); + svc.set_service_description("test_description"); + svc.set_service_id(3); + + configuration::applier::command cmd_aply; + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + cmd.set_command_line("echo 1"); + svc.set_check_command("cmd"); + cmd_aply.add_object(cmd); + + // We fake here the expand_object on configuration::service + svc.set_host_id(1); + + svc_aply.add_object(svc); + + ASSERT_EQ(engine::service::services_by_id.size(), 1u); + svc_aply.remove_object(0); + ASSERT_EQ(engine::service::services_by_id.size(), 0u); + + svc.set_service_description("test_description2"); + + // We have to fake the expand_object on configuration::service + svc.set_host_id(1); + + svc_aply.add_object(svc); + + service_id_map const& sm(engine::service::services_by_id); + ASSERT_EQ(sm.size(), 1u); + ASSERT_EQ(sm.begin()->first.first, 1u); + ASSERT_EQ(sm.begin()->first.second, 3u); + + // Service is not resolved, host is null now. + ASSERT_TRUE(!sm.begin()->second->get_host_ptr()); + ASSERT_TRUE(sm.begin()->second->description() == "test_description2"); + + std::string s{engine::service::services[{"test_host", "test_description2"}] + ->description()}; + ASSERT_TRUE(s == "test_description2"); +} + +// Given a service configuration applied to a service, +// When the check_validity() method is executed on the configuration, +// Then it throws an exception because: +// 1. it does not provide a service description +// 2. it is not attached to a host +// 3. the service does not contain any check command. +TEST_F(ApplierService, PbServicesCheckValidity) { + configuration::error_cnt err; + configuration::applier::host hst_aply; + configuration::applier::service svc_aply; + configuration::Service csvc; + configuration::service_helper csvc_hlp(&csvc); + + // No service description + ASSERT_THROW(csvc_hlp.check_validity(err), std::exception); + + csvc.set_service_description("check_description"); + csvc.set_service_id(53); + + // No host attached to + ASSERT_THROW(csvc_hlp.check_validity(err), std::exception); + + csvc.set_host_name("test_host"); + + // No check command attached to + ASSERT_THROW(csvc_hlp.check_validity(err), std::exception); + + configuration::applier::command cmd_aply; + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + cmd.set_command_line("echo 1"); + csvc.set_check_command("cmd"); + cmd_aply.add_object(cmd); + + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("10.11.12.13"); + hst.set_host_id(124); + hst_aply.add_object(hst); + + // We fake here the expand_object on configuration::service + csvc.set_host_id(124); + + svc_aply.add_object(csvc); + csvc.set_service_description("foo"); + + // No check command + ASSERT_NO_THROW(csvc_hlp.check_validity(err)); + svc_aply.resolve_object(csvc, err); + + service_map const& sm(engine::service::services); + ASSERT_EQ(sm.size(), 1u); + + host_map const& hm(engine::host::hosts); + ASSERT_EQ(sm.begin()->second->get_host_ptr(), hm.begin()->second.get()); +} + +// Given a service configuration, +// When the flap_detection_options is set to none, +// Then it is well recorded with only none. +TEST_F(ApplierService, PbServicesFlapOptionsNone) { + configuration::Service csvc; + configuration::service_helper csvc_hlp(&csvc); + + csvc.set_service_description("test_description"); + csvc.set_host_name("test_host"); + + csvc_hlp.hook("flap_detection_options", "n"); + ASSERT_EQ(csvc.flap_detection_options(), action_svc_none); +} + +// Given a service configuration, +// When the flap_detection_options is set to all, +// Then it is well recorded with all. +TEST_F(ApplierService, PbServicesFlapOptionsAll) { + configuration::Service csvc; + configuration::service_helper csvc_hlp(&csvc); + csvc_hlp.hook("flap_detection_options", "a"); + ASSERT_EQ(csvc.flap_detection_options(), action_svc_ok | action_svc_warning | + action_svc_critical | + action_svc_unknown); +} + +// Given a service configuration, +// When the initial_state value is set to unknown, +// Then it is well recorded with unknown. +// When the initial_state value is set to whatever +// Then the parse method returns false. +TEST_F(ApplierService, PbServicesInitialState) { + configuration::Service csvc; + configuration::service_helper csvc_hlp(&csvc); + csvc_hlp.hook("initial_state", "u"); + ASSERT_EQ(csvc.initial_state(), engine::service::state_unknown); + ASSERT_FALSE(csvc_hlp.hook("initial_state", "g")); +} + +// Given a service configuration, +// When the stalking options are set to "c,w", +// Then they are well recorded with "critical | warning" +// When the initial_state value is set to "a" +// Then they are well recorded with "ok | warning | unknown | critical" +TEST_F(ApplierService, PbServicesStalkingOptions) { + configuration::Service csvc; + configuration::service_helper csvc_hlp(&csvc); + ASSERT_TRUE(csvc_hlp.hook("stalking_options", "c,w")); + ASSERT_EQ(csvc.stalking_options(), action_svc_critical | action_svc_warning); + + ASSERT_TRUE(csvc_hlp.hook("stalking_options", "a")); + ASSERT_EQ(csvc.stalking_options(), action_svc_ok | action_svc_warning | + action_svc_unknown | + action_svc_critical); +} + +// Given a viable contact +// When it is added to a contactgroup +// And when this contactgroup is added to a service +// Then after the service resolution, we can see the contactgroup stored in the +// service with the contact inside it. +TEST_F(ApplierService, PbContactgroupResolution) { + configuration::Contact ctct{new_pb_configuration_contact("admin", true)}; + configuration::applier::contact ct_aply; + ct_aply.add_object(ctct); + configuration::Contactgroup cg; + configuration::contactgroup_helper cg_hlp(&cg); + cg.set_contactgroup_name("contactgroup_test"); + fill_string_group(cg.mutable_members(), "admin"); + configuration::applier::contactgroup cg_aply; + cg_aply.add_object(cg); + configuration::error_cnt err; + cg_aply.resolve_object(cg, err); + configuration::applier::host hst_aply; + configuration::applier::service svc_aply; + configuration::Service svc; + configuration::service_helper svc_hlp(&svc); + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(1); + hst_aply.add_object(hst); + svc.set_host_name("test_host"); + svc.set_service_description("test_description"); + svc.set_service_id(3); + fill_string_group(svc.mutable_contactgroups(), "contactgroup_test"); + + configuration::applier::command cmd_aply; + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + cmd.set_command_line("echo 1"); + svc.set_check_command("cmd"); + cmd_aply.add_object(cmd); + + // We fake here the expand_object on configuration::service + svc.set_host_id(1); + + svc_aply.add_object(svc); + svc_aply.resolve_object(svc, err); + service_id_map const& sm(engine::service::services_by_id); + ASSERT_EQ(sm.size(), 1u); + ASSERT_EQ(sm.begin()->first.first, 1u); + ASSERT_EQ(sm.begin()->first.second, 3u); + + contactgroup_map_unsafe cgs{sm.begin()->second->get_contactgroups()}; + ASSERT_EQ(cgs.size(), 1u); + ASSERT_EQ(cgs.begin()->first, "contactgroup_test"); + contact_map_unsafe::iterator itt{ + cgs.begin()->second->get_members().find("admin")}; + + ASSERT_NE(itt, cgs.begin()->second->get_members().end()); + + contact_map::const_iterator it{engine::contact::contacts.find("admin")}; + ASSERT_NE(it, engine::contact::contacts.end()); + + ASSERT_EQ(itt->second, it->second.get()); +} + +TEST_F(ApplierService, PbStalkingOptionsWhenServiceIsModified) { + configuration::applier::host hst_aply; + configuration::applier::service svc_aply; + configuration::Service svc; + configuration::Host hst; + configuration::service_helper svc_hlp(&svc); + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + // The host id is not given + ASSERT_THROW(hst_aply.add_object(hst), std::exception); + hst.set_host_id(1); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + svc.set_host_name("test_host"); + svc.set_service_description("test_description"); + svc.set_service_id(3); + + configuration::applier::command cmd_aply; + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + cmd.set_command_line("echo 1"); + svc.set_check_command("cmd"); + cmd_aply.add_object(cmd); + svc_hlp.hook("stalking_options", ""); + svc_hlp.hook("notification_options", "a"); + + // We fake here the expand_object on configuration::service + svc.set_host_id(1); + + svc_aply.add_object(svc); + + service_id_map const& sm(engine::service::services_by_id); + std::shared_ptr serv = sm.begin()->second; + + ASSERT_FALSE(serv->get_stalk_on(engine::service::ok)); + ASSERT_FALSE(serv->get_stalk_on(engine::service::warning)); + ASSERT_FALSE(serv->get_stalk_on(engine::service::critical)); + ASSERT_FALSE(serv->get_stalk_on(engine::service::unknown)); + + ASSERT_TRUE(serv->get_notify_on(engine::service::ok)); + ASSERT_TRUE(serv->get_notify_on(engine::service::warning)); + ASSERT_TRUE(serv->get_notify_on(engine::service::critical)); + ASSERT_TRUE(serv->get_notify_on(engine::service::unknown)); + + svc.set_service_description("test_description2"); + svc_aply.modify_object(pb_config.mutable_services(0), svc); + svc_aply.expand_objects(pb_config); + + ASSERT_EQ(sm.size(), 1u); + ASSERT_EQ(sm.begin()->first.first, 1u); + ASSERT_EQ(sm.begin()->first.second, 3u); + + // Service is not resolved, host is null now. + serv = sm.begin()->second; + + ASSERT_TRUE(!serv->get_host_ptr()); + ASSERT_TRUE(serv->description() == "test_description2"); + + std::string s{engine::service::services[{"test_host", "test_description2"}] + ->description()}; + ASSERT_TRUE(s == "test_description2"); + + ASSERT_FALSE(serv->get_stalk_on(engine::service::ok)); + ASSERT_FALSE(serv->get_stalk_on(engine::service::warning)); + ASSERT_FALSE(serv->get_stalk_on(engine::service::critical)); + ASSERT_FALSE(serv->get_stalk_on(engine::service::unknown)); + + ASSERT_TRUE(serv->get_notify_on(engine::service::ok)); + ASSERT_TRUE(serv->get_notify_on(engine::service::warning)); + ASSERT_TRUE(serv->get_notify_on(engine::service::critical)); + ASSERT_TRUE(serv->get_notify_on(engine::service::unknown)); +} + +// Given service configuration with a host defined +// Then the applier add_object creates the service +TEST_F(ApplierService, PbNewServiceFromConfigTags) { + configuration::applier::host hst_aply; + configuration::applier::service svc_aply; + configuration::Service svc; + configuration::Host hst; + configuration::service_helper svc_hlp(&svc); + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + // The host id is not given + ASSERT_THROW(hst_aply.add_object(hst), std::exception); + hst.set_host_id(1); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + svc.set_host_name("test_host"); + svc.set_service_description("test_description"); + svc.set_service_id(3); + svc_hlp.hook("group_tags", "1,2"); + svc_hlp.hook("category_tags", "3"); + + configuration::applier::command cmd_aply; + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + cmd.set_command_line("echo 1"); + svc.set_check_command("cmd"); + cmd_aply.add_object(cmd); + + configuration::Tag tag; + configuration::tag_helper tag_hlp(&tag); + configuration::applier::tag tag_aply; + tag.mutable_key()->set_id(1); + tag.mutable_key()->set_type(tag_servicegroup); + tag.set_tag_name("foobar1"); + tag_aply.add_object(tag); + + tag.mutable_key()->set_id(2); + tag.mutable_key()->set_type(tag_servicegroup); + tag.set_tag_name("foobar2"); + tag_aply.add_object(tag); + + tag.mutable_key()->set_id(3); + tag.mutable_key()->set_type(tag_servicecategory); + tag.set_tag_name("foobar3"); + tag_aply.add_object(tag); + + // No need here to call svc_aply.expand_objects(*config) because the + // configuration service is not stored in configuration::state. We just have + // to set the host_id manually. + svc.set_host_id(1); + svc_aply.add_object(svc); + service_id_map const& sm(engine::service::services_by_id); + ASSERT_EQ(sm.size(), 1u); + ASSERT_EQ(sm.begin()->first.first, 1u); + ASSERT_EQ(sm.begin()->first.second, 3u); + + // Service is not resolved, host is null now. + ASSERT_TRUE(!sm.begin()->second->get_host_ptr()); + ASSERT_TRUE(sm.begin()->second->description() == "test_description"); +} + +// Given service configuration with a host defined +// Then the applier add_object creates the service +TEST_F(ApplierService, PbRenameServiceFromConfigTags) { + configuration::applier::host hst_aply; + configuration::applier::service svc_aply; + configuration::Service svc; + configuration::service_helper svc_hlp(&svc); + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + // The host id is not given + ASSERT_THROW(hst_aply.add_object(hst), std::exception); + hst.set_host_id(1); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + svc.set_host_name("test_host"); + svc.set_service_description("test_description"); + svc.set_service_id(3); + svc_hlp.hook("group_tags", "1,2"); + svc_hlp.hook("category_tags", "3"); + + configuration::applier::command cmd_aply; + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + cmd.set_command_line("echo 1"); + svc.set_check_command("cmd"); + cmd_aply.add_object(cmd); + + configuration::Tag tag; + configuration::tag_helper tag_hlp(&tag); + configuration::applier::tag tag_aply; + tag.mutable_key()->set_id(1); + tag.mutable_key()->set_type(tag_servicegroup); + tag.set_tag_name("foobar1"); + tag_aply.add_object(tag); + + tag.mutable_key()->set_id(2); + tag.mutable_key()->set_type(tag_servicegroup); + tag.set_tag_name("foobar2"); + tag_aply.add_object(tag); + + tag.mutable_key()->set_id(3); + tag.mutable_key()->set_type(tag_servicecategory); + tag.set_tag_name("foobar3"); + tag_aply.add_object(tag); + // We fake here the expand_object on configuration::service + svc.set_host_id(1); + + svc_aply.add_object(svc); + + svc.set_service_description("test_description2"); + svc_aply.modify_object(pb_config.mutable_services(0), svc); + svc_aply.expand_objects(pb_config); + + const service_id_map& sm = engine::service::services_by_id; + ASSERT_EQ(sm.size(), 1u); + ASSERT_EQ(sm.begin()->first.first, 1u); + ASSERT_EQ(sm.begin()->first.second, 3u); + + // Service is not resolved, host is null now. + ASSERT_TRUE(!sm.begin()->second->get_host_ptr()); + ASSERT_TRUE(sm.begin()->second->description() == "test_description2"); + + std::string s{engine::service::services[{"test_host", "test_description2"}] + ->description()}; + ASSERT_TRUE(s == "test_description2"); +} + +// Given service configuration with a host defined +// Then the applier add_object creates the service +TEST_F(ApplierService, PbRemoveServiceFromConfigTags) { + configuration::applier::host hst_aply; + configuration::applier::service svc_aply; + configuration::Service svc; + configuration::service_helper svc_hlp(&svc); + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + // The host id is not given + ASSERT_THROW(hst_aply.add_object(hst), std::exception); + hst.set_host_id(1); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + svc.set_host_name("test_host"); + svc.set_service_description("test_description"); + svc.set_service_id(3); + svc_hlp.hook("group_tags", "1,2"); + svc_hlp.hook("category_tags", "3"); + + configuration::applier::command cmd_aply; + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + cmd.set_command_line("echo 1"); + svc.set_check_command("cmd"); + cmd_aply.add_object(cmd); + + configuration::Tag tag; + configuration::tag_helper tag_hlp(&tag); + configuration::applier::tag tag_aply; + tag.mutable_key()->set_id(1); + tag.mutable_key()->set_type(tag_servicegroup); + tag.set_tag_name("foobar1"); + tag_aply.add_object(tag); + + tag.mutable_key()->set_id(2); + tag.mutable_key()->set_type(tag_servicegroup); + tag.set_tag_name("foobar2"); + tag_aply.add_object(tag); + + tag.mutable_key()->set_id(3); + tag.mutable_key()->set_type(tag_servicecategory); + tag.set_tag_name("foobar3"); + tag_aply.add_object(tag); + // We fake here the expand_object on configuration::service + svc.set_host_id(1); + + svc_aply.add_object(svc); + + ASSERT_EQ(engine::service::services_by_id.size(), 1u); + svc_aply.remove_object(0); + ASSERT_EQ(engine::service::services_by_id.size(), 0u); + + svc.set_service_description("test_description2"); + + // We have to fake the expand_object on configuration::service + svc.set_host_id(1); + + svc_aply.add_object(svc); + + service_id_map const& sm(engine::service::services_by_id); + ASSERT_EQ(sm.size(), 1u); + ASSERT_EQ(sm.begin()->first.first, 1u); + ASSERT_EQ(sm.begin()->first.second, 3u); + + // Service is not resolved, host is null now. + ASSERT_TRUE(!sm.begin()->second->get_host_ptr()); + ASSERT_TRUE(sm.begin()->second->description() == "test_description2"); + + std::string s{engine::service::services[{"test_host", "test_description2"}] + ->description()}; + ASSERT_TRUE(s == "test_description2"); +} + +// Given a service configuration, +// When we duplicate it, we get a configuration equal to the previous one. +// When two services are generated from the same configuration +// Then they are equal. +// When Modifying a configuration changes, +// Then the '!=' effect on configurations. +TEST_F(ApplierService, PbServicesEqualityTags) { + configuration::applier::host hst_aply; + configuration::applier::service svc_aply; + configuration::Service csvc; + configuration::service_helper csvc_hlp(&csvc); + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(1); + hst_aply.add_object(hst); + csvc.set_host_name("test_host"); + csvc.set_service_description("test_description1"); + csvc.set_service_id(12345); + csvc.set_acknowledgement_timeout(21); + csvc_hlp.hook("group_tags", "1,2"); + csvc_hlp.hook("category_tags", "3"); + + configuration::applier::command cmd_aply; + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + cmd.set_command_line("echo 1"); + csvc.set_check_command("cmd"); + cmd_aply.add_object(cmd); + + configuration::Tag tag; + configuration::tag_helper tag_hlp(&tag); + configuration::applier::tag tag_aply; + tag.mutable_key()->set_id(1); + tag.mutable_key()->set_type(tag_servicegroup); + tag.set_tag_name("foobar1"); + tag_aply.add_object(tag); + + tag.mutable_key()->set_id(2); + tag.mutable_key()->set_type(tag_servicegroup); + tag.set_tag_name("foobar2"); + tag_aply.add_object(tag); + + tag.mutable_key()->set_id(3); + tag.mutable_key()->set_type(tag_servicecategory); + tag.set_tag_name("foobar3"); + tag_aply.add_object(tag); + // We have to fake the expand_object on configuration::service + csvc.set_host_id(1); + + svc_aply.add_object(csvc); + csvc.set_service_description("test_description2"); + csvc.set_service_id(12346); + ASSERT_NO_THROW(svc_aply.add_object(csvc)); + service_map const& sm(engine::service::services); + ASSERT_EQ(sm.size(), 2u); + service_map::const_iterator it(sm.begin()); + std::shared_ptr svc1(it->second); + ++it; + std::shared_ptr svc2(it->second); + configuration::Service csvc1; + configuration::service_helper csvc1_hlp(&csvc1); + csvc1.CopyFrom(csvc); + csvc1_hlp.hook("group_tags", "6,8,9"); + csvc_hlp.hook("category_tags", "15,26,34"); + csvc_hlp.hook("group_tags", "6,8,9"); + + ASSERT_TRUE(svc1 != svc2); +} + +// Given a service configuration applied to a service, +// When the check_validity() method is executed on the configuration, +// Then it throws an exception because: +// 1. it does not provide a service description +// 2. it is not attached to a host +// 3. the service does not contain any check command. +TEST_F(ApplierService, PbServicesCheckValidityTags) { + configuration::applier::host hst_aply; + configuration::applier::service svc_aply; + configuration::Service csvc; + configuration::service_helper csvc_hlp(&csvc); + configuration::error_cnt err; + + // No service description + ASSERT_THROW(csvc_hlp.check_validity(err), std::exception); + + csvc.set_service_description("check_description"); + csvc.set_service_id(53); + csvc_hlp.hook("group_tags", "1,2"); + csvc_hlp.hook("category_tags", "3"); + + // No host attached to + ASSERT_THROW(csvc_hlp.check_validity(err), std::exception); + + csvc.set_host_name("test_host"); + + // No check command attached to + ASSERT_THROW(csvc_hlp.check_validity(err), std::exception); + + configuration::applier::command cmd_aply; + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + cmd.set_command_line("echo 1"); + csvc.set_check_command("cmd"); + cmd_aply.add_object(cmd); + + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("10.11.12.13"); + hst.set_host_id(124); + hst_aply.add_object(hst); + + configuration::Tag tag; + configuration::tag_helper tag_hlp(&tag); + configuration::applier::tag tag_aply; + + tag.mutable_key()->set_id(1); + tag.mutable_key()->set_type(tag_servicegroup); + tag.set_tag_name("foobar1"); + tag_aply.add_object(tag); + + tag.mutable_key()->set_id(2); + tag.mutable_key()->set_type(tag_servicegroup); + tag.set_tag_name("foobar2"); + tag_aply.add_object(tag); + + tag.mutable_key()->set_id(3); + tag.mutable_key()->set_type(tag_servicecategory); + tag.set_tag_name("foobar3"); + tag_aply.add_object(tag); + + tag_aply.add_object(tag); + // We fake here the expand_object on configuration::service + csvc.set_host_id(124); + + svc_aply.add_object(csvc); + csvc.set_service_description("foo"); + + // No check command + ASSERT_NO_THROW(csvc_hlp.check_validity(err)); + svc_aply.resolve_object(csvc, err); + + service_map const& sm(engine::service::services); + ASSERT_EQ(sm.size(), 1u); + + host_map const& hm(engine::host::hosts); + ASSERT_EQ(sm.begin()->second->get_host_ptr(), hm.begin()->second.get()); +} diff --git a/engine/tests/configuration/applier/applier-pbserviceescalation.cc b/engine/tests/configuration/applier/applier-pbserviceescalation.cc new file mode 100644 index 00000000000..ecde8d72239 --- /dev/null +++ b/engine/tests/configuration/applier/applier-pbserviceescalation.cc @@ -0,0 +1,182 @@ +/* + * Copyright 2019 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "common/engine_conf/command_helper.hh" +#include "common/engine_conf/host_helper.hh" +#include "common/engine_conf/service_helper.hh" +#include "common/engine_conf/serviceescalation_helper.hh" +#include "helper.hh" + +using namespace com::centreon; +using namespace com::centreon::engine; + +class ApplierServiceEscalation : public ::testing::Test { + public: + void SetUp() override { init_config_state(); } + + void TearDown() override { deinit_config_state(); } +}; + +TEST_F(ApplierServiceEscalation, PbAddEscalation) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst_aply.add_object(hst); + ASSERT_EQ(host::hosts.size(), 1u); + + configuration::applier::command cmd_aply; + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + cmd.set_command_line("echo 1"); + cmd_aply.add_object(cmd); + + configuration::applier::service svc_aply; + configuration::Service svc; + configuration::service_helper svc_hlp(&svc); + svc.set_host_name("test_host"); + svc.set_service_description("test_svc"); + svc.set_service_id(12); + svc.set_check_command("cmd"); + svc.set_host_id(12); + svc_aply.add_object(svc); + ASSERT_EQ(service::services.size(), 1u); + + configuration::applier::serviceescalation se_apply; + configuration::Serviceescalation se; + configuration::serviceescalation_helper se_hlp(&se); + se_hlp.hook("hosts", "test_host"); + se_hlp.hook("service_description", "test_svc"); + se.set_first_notification(4); + se_apply.add_object(se); + ASSERT_EQ(serviceescalation::serviceescalations.size(), 1u); + se.set_first_notification(8); + se_apply.add_object(se); + ASSERT_EQ(serviceescalation::serviceescalations.size(), 2u); +} + +TEST_F(ApplierServiceEscalation, PbRemoveEscalation) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst_aply.add_object(hst); + ASSERT_EQ(host::hosts.size(), 1u); + + configuration::applier::command cmd_aply; + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + cmd.set_command_line("echo 1"); + cmd_aply.add_object(cmd); + + configuration::applier::service svc_aply; + configuration::Service svc; + configuration::service_helper svc_hlp(&svc); + svc.set_host_name("test_host"); + svc.set_service_description("test_svc"); + svc.set_service_id(12); + svc.set_check_command("cmd"); + svc.set_host_id(12); + svc_aply.add_object(svc); + ASSERT_EQ(service::services.size(), 1u); + + configuration::applier::serviceescalation se_apply; + configuration::Serviceescalation se1, se2; + configuration::serviceescalation_helper se1_hlp(&se1), se2_hlp(&se2); + se1_hlp.hook("hosts", "test_host"); + se1_hlp.hook("service_description", "test_svc"); + se1.set_first_notification(4); + se_apply.add_object(se1); + se2_hlp.hook("hosts", "test_host"); + se2_hlp.hook("service_description", "test_svc"); + se2.set_first_notification(8); + se_apply.add_object(se2); + ASSERT_EQ(serviceescalation::serviceescalations.size(), 2u); + + se_apply.remove_object(0); + ASSERT_EQ(serviceescalation::serviceescalations.size(), 1u); + se_apply.remove_object(0); + ASSERT_EQ(serviceescalation::serviceescalations.size(), 0u); +} + +TEST_F(ApplierServiceEscalation, PbRemoveEscalationFromRemovedService) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst_aply.add_object(hst); + ASSERT_EQ(host::hosts.size(), 1u); + + configuration::applier::command cmd_aply; + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + cmd.set_command_line("echo 1"); + cmd_aply.add_object(cmd); + + configuration::applier::service svc_aply; + configuration::Service svc; + configuration::service_helper svc_hlp(&svc); + svc.set_host_name("test_host"); + svc.set_service_description("test_svc"); + svc.set_service_id(12); + svc.set_check_command("cmd"); + svc.set_host_id(12); + svc_aply.add_object(svc); + ASSERT_EQ(service::services.size(), 1u); + + configuration::applier::serviceescalation se_apply; + configuration::Serviceescalation se1, se2; + configuration::serviceescalation_helper se1_hlp(&se1), se2_hlp(&se2); + se1_hlp.hook("hosts", "test_host"); + se1_hlp.hook("service_description", "test_svc"); + se1.set_first_notification(4); + se_apply.add_object(se1); + se2_hlp.hook("hosts", "test_host"); + se2_hlp.hook("service_description", "test_svc"); + se2.set_first_notification(8); + se_apply.add_object(se2); + ASSERT_EQ(serviceescalation::serviceescalations.size(), 2u); + + hst_aply.remove_object(0); + ASSERT_EQ(host::hosts.size(), 0u); + svc_aply.remove_object(0); + ASSERT_EQ(service::services.size(), 0u); + + se_apply.remove_object(0); + ASSERT_EQ(serviceescalation::serviceescalations.size(), 1u); + se_apply.remove_object(0); + ASSERT_EQ(serviceescalation::serviceescalations.size(), 0u); +} diff --git a/engine/tests/configuration/applier/applier-pbservicegroup.cc b/engine/tests/configuration/applier/applier-pbservicegroup.cc new file mode 100644 index 00000000000..64a92486a6b --- /dev/null +++ b/engine/tests/configuration/applier/applier-pbservicegroup.cc @@ -0,0 +1,343 @@ +/* + * Copyright 2018 - 2019 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include + +#include "../../timeperiod/utils.hh" +#include "com/centreon/engine/configuration/applier/command.hh" +#include "com/centreon/engine/configuration/applier/host.hh" +#include "com/centreon/engine/configuration/applier/service.hh" +#include "com/centreon/engine/configuration/applier/servicegroup.hh" +#include "com/centreon/engine/servicegroup.hh" +#include "common/engine_conf/command_helper.hh" +#include "common/engine_conf/host_helper.hh" +#include "common/engine_conf/service_helper.hh" +#include "common/engine_conf/servicegroup_helper.hh" +#include "common/engine_conf/state.pb.h" +#include "helper.hh" + +using namespace com::centreon; +using namespace com::centreon::engine; +using namespace com::centreon::engine::configuration; +using namespace com::centreon::engine::configuration::applier; + +class ApplierServicegroup : public ::testing::Test { + public: + void SetUp() override { init_config_state(); } + + void TearDown() override { deinit_config_state(); } +}; + +// Given a servicegroup applier +// And a configuration servicegroup +// When we modify the servicegroup configuration with a non existing +// servicegroup +// Then an exception is thrown. +TEST_F(ApplierServicegroup, PbModifyUnexistingServicegroupFromConfig) { + configuration::applier::servicegroup aply; + configuration::Servicegroup sg; + configuration::servicegroup_helper sg_hlp(&sg); + sg.set_servicegroup_name("test"); + fill_pair_string_group(sg.mutable_members(), "host1,service1"); + configuration::Servicegroup* new_sg = pb_config.add_servicegroups(); + new_sg->CopyFrom(sg); + ASSERT_THROW(aply.modify_object(new_sg, sg), std::exception); +} + +// Given a servicegroup applier +// And a configuration servicegroup in configuration +// When we modify the servicegroup configuration +// Then the applier modify_object updates the servicegroup. +TEST_F(ApplierServicegroup, PbModifyServicegroupFromConfig) { + configuration::applier::servicegroup aply; + configuration::Servicegroup sg; + configuration::servicegroup_helper sg_hlp(&sg); + sg.set_servicegroup_name("test"); + fill_pair_string_group(sg.mutable_members(), "host1,service1"); + aply.add_object(sg); + auto it = engine::servicegroup::servicegroups.find("test"); + ASSERT_TRUE(it->second->get_alias() == "test"); + + sg.set_alias("test_renamed"); + aply.modify_object(pb_config.mutable_servicegroups(0), sg); + it = engine::servicegroup::servicegroups.find("test"); + ASSERT_TRUE(it->second->get_alias() == "test_renamed"); +} + +// Given an empty servicegroup +// When the resolve_object() method is called +// Then no warning, nor error are given +TEST_F(ApplierServicegroup, PbResolveEmptyservicegroup) { + configuration::error_cnt err; + configuration::applier::servicegroup aplyr; + configuration::Servicegroup grp; + configuration::servicegroup_helper grp_hlp(&grp); + grp.set_servicegroup_name("test"); + aplyr.add_object(grp); + aplyr.expand_objects(pb_config); + aplyr.resolve_object(grp, err); + ASSERT_EQ(err.config_warnings, 0); + ASSERT_EQ(err.config_errors, 0); +} + +// Given a servicegroup with a non-existing service +// When the resolve_object() method is called +// Then an exception is thrown +// And the method returns 1 error +TEST_F(ApplierServicegroup, PbResolveInexistentService) { + configuration::error_cnt err; + configuration::applier::servicegroup aplyr; + configuration::Servicegroup grp; + configuration::servicegroup_helper grp_helper(&grp); + grp.set_servicegroup_name("test"); + fill_pair_string_group(grp.mutable_members(), "host1,non_existing_service"); + aplyr.add_object(grp); + aplyr.expand_objects(pb_config); + ASSERT_THROW(aplyr.resolve_object(grp, err), std::exception); + ASSERT_EQ(err.config_warnings, 0); + ASSERT_EQ(err.config_errors, 1); +} + +// Given a servicegroup with a service and a host +// When the resolve_object() method is called +// Then the service is really added to the service group. +TEST_F(ApplierServicegroup, PbResolveServicegroup) { + configuration::error_cnt err; + configuration::applier::host aply_hst; + configuration::applier::service aply_svc; + configuration::applier::command aply_cmd; + configuration::applier::servicegroup aply_grp; + configuration::Servicegroup grp; + configuration::servicegroup_helper grp_hlp(&grp); + grp.set_servicegroup_name("test_group"); + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + aply_hst.add_object(hst); + configuration::Service svc; + configuration::service_helper svc_hlp(&svc); + svc.set_service_description("test"); + svc.set_host_name("test_host"); + svc.set_service_id(18); + cmd.set_command_line("echo 1"); + svc.set_check_command("cmd"); + aply_cmd.add_object(cmd); + + // We fake here the expand_object on configuration::service + svc.set_host_id(12); + + aply_svc.add_object(svc); + fill_string_group(svc.mutable_servicegroups(), "test_group"); + fill_pair_string_group(grp.mutable_members(), "test_host,test"); + aply_grp.add_object(grp); + aply_grp.expand_objects(pb_config); + ASSERT_NO_THROW(aply_grp.resolve_object(grp, err)); +} + +// Given a servicegroup with a service already configured +// And a second servicegroup configuration +// When we set the first one as servicegroup member to the second +// Then the parse method returns true and set the first one service +// to the second one. +TEST_F(ApplierServicegroup, PbSetServicegroupMembers) { + configuration::applier::host aply_hst; + configuration::applier::service aply_svc; + configuration::applier::command aply_cmd; + configuration::applier::servicegroup aply_grp; + configuration::Servicegroup grp; + configuration::servicegroup_helper grp_hlp(&grp); + grp.set_servicegroup_name("test_group"); + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + aply_hst.add_object(hst); + configuration::Service svc; + configuration::service_helper svc_hlp(&svc); + svc.set_service_description("test"); + svc.set_host_name("test_host"); + svc.set_service_id(18); + cmd.set_command_line("echo 1"); + svc.set_check_command("cmd"); + aply_cmd.add_object(cmd); + + // We fake here the expand_object on configuration::service + svc.set_host_id(12); + + configuration::error_cnt err; + aply_svc.add_object(svc); + fill_string_group(svc.mutable_servicegroups(), "test_group"); + fill_pair_string_group(grp.mutable_members(), "test_host,test"); + aply_grp.add_object(grp); + aply_grp.expand_objects(pb_config); + aply_grp.resolve_object(grp, err); + ASSERT_TRUE(grp.members().data().size() == 1); + + configuration::Servicegroup grp1; + configuration::servicegroup_helper grp1_hlp(&grp1); + grp1.set_servicegroup_name("big_group"); + fill_string_group(grp1.mutable_servicegroup_members(), "test_group"); + aply_grp.add_object(grp1); + aply_grp.expand_objects(pb_config); + + // grp1 must be reload because the expand_objects reload them totally. + auto found = std::find_if(pb_config.servicegroups().begin(), + pb_config.servicegroups().end(), + [](const configuration::Servicegroup& sg) { + return sg.servicegroup_name() == "big_group"; + }); + ASSERT_TRUE(found != pb_config.servicegroups().end()); + ASSERT_EQ(found->members().data().size(), 1); +} + +// Given a servicegroup applier +// And a configuration servicegroup in configuration +// When we remove the configuration +// Then it is really removed +TEST_F(ApplierServicegroup, PbRemoveServicegroupFromConfig) { + configuration::applier::host aply_hst; + configuration::applier::service aply_svc; + configuration::applier::command aply_cmd; + configuration::applier::servicegroup aply_grp; + configuration::Servicegroup grp; + configuration::servicegroup_helper grp_hlp(&grp); + grp.set_servicegroup_name("test_group"); + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + aply_hst.add_object(hst); + configuration::Service svc; + configuration::service_helper svc_hlp(&svc); + svc.set_service_description("test"); + svc.set_host_name("test_host"); + svc.set_service_id(18); + cmd.set_command_line("echo 1"); + svc.set_check_command("cmd"); + aply_cmd.add_object(cmd); + + // We fake here the expand_object on configuration::service + svc.set_host_id(12); + + aply_svc.add_object(svc); + fill_string_group(svc.mutable_servicegroups(), "test_group"); + fill_pair_string_group(grp.mutable_members(), "test_host,test"); + aply_grp.add_object(grp); + aply_grp.expand_objects(pb_config); + configuration::error_cnt err; + aply_grp.resolve_object(grp, err); + ASSERT_EQ(grp.members().data().size(), 1); + + configuration::Servicegroup grp1; + configuration::servicegroup_helper grp1_hlp(&grp1); + grp1.set_servicegroup_name("big_group"); + fill_string_group(grp1.mutable_servicegroup_members(), "test_group"); + aply_grp.add_object(grp1); + aply_grp.expand_objects(pb_config); + auto found = std::find_if(pb_config.servicegroups().begin(), + pb_config.servicegroups().end(), + [](const configuration::Servicegroup& sg) { + return sg.servicegroup_name() == "big_group"; + }); + ASSERT_TRUE(found != pb_config.servicegroups().end()); + ASSERT_EQ(found->members().data().size(), 1); + + ASSERT_EQ(engine::servicegroup::servicegroups.size(), 2u); + aply_grp.remove_object(0); + ASSERT_EQ(engine::servicegroup::servicegroups.size(), 1u); +} + +// Given a servicegroup applier +// And a configuration servicegroup in configuration +// When we remove the configuration +// Then it is really removed +TEST_F(ApplierServicegroup, PbRemoveServiceFromGroup) { + configuration::applier::host aply_hst; + configuration::applier::service aply_svc; + configuration::applier::command aply_cmd; + configuration::applier::servicegroup aply_grp; + configuration::Servicegroup grp; + configuration::servicegroup_helper grp_hlp(&grp); + grp.set_servicegroup_name("test_group"); + + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + cmd.set_command_line("echo 1"); + aply_cmd.add_object(cmd); + + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + aply_hst.add_object(hst); + + configuration::Service svc; + configuration::service_helper svc_hlp(&svc); + svc.set_service_description("test"); + svc_hlp.hook("service_description", "test"); + svc.set_host_name("test_host"); + svc.set_service_id(18); + svc.set_check_command("cmd"); + // We fake here the expand_object on configuration::service + svc.set_host_id(12); + aply_svc.add_object(svc); + svc_hlp.hook("servicegroups", "test_group"); + + svc.set_service_description("test2"); + svc.set_host_name("test_host"); + svc.set_service_id(19); + svc.set_check_command("cmd"); + // We fake here the expand_object on configuration::service + svc.set_host_id(12); + aply_svc.add_object(svc); + svc_hlp.hook("servicegroups", "test_group"); + + grp_hlp.hook("members", "test_host,test,test_host,test2"); + aply_grp.add_object(grp); + aply_grp.expand_objects(pb_config); + configuration::error_cnt err; + aply_grp.resolve_object(grp, err); + ASSERT_EQ(grp.members().data().size(), 2); + + engine::servicegroup* sg = + engine::servicegroup::servicegroups["test_group"].get(); + ASSERT_EQ(sg->members.size(), 2u); + aply_svc.remove_object(1); + ASSERT_EQ(sg->members.size(), 1u); + + grp_hlp.hook("members", "test_host,test,test_host,test2"); + aply_grp.modify_object(pb_config.mutable_servicegroups(0), grp); + + ASSERT_EQ(engine::servicegroup::servicegroups.size(), 1u); +} diff --git a/engine/tests/configuration/applier/applier-pbstate.cc b/engine/tests/configuration/applier/applier-pbstate.cc new file mode 100644 index 00000000000..602ca6129b8 --- /dev/null +++ b/engine/tests/configuration/applier/applier-pbstate.cc @@ -0,0 +1,996 @@ +/* + * Copyright 2023 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include +#include +#include +#include +#include "com/centreon/engine/configuration/applier/state.hh" +#include "com/centreon/engine/globals.hh" +#include "common/engine_conf/contact_helper.hh" +#include "common/engine_conf/message_helper.hh" +#include "common/engine_conf/parser.hh" +#include "common/engine_conf/state.pb.h" +#include "tests/helper.hh" + +using namespace com::centreon::engine; +using com::centreon::engine::configuration::TagType; + +extern configuration::State pb_config; + +class ApplierState : public ::testing::Test { + protected: + public: + void SetUp() override { + init_config_state(); + auto tps = pb_config.mutable_timeperiods(); + for (int i = 0; i < 10; i++) { + auto* tp = tps->Add(); + tp->set_alias(fmt::format("timeperiod {}", i)); + tp->set_timeperiod_name(fmt::format("Timeperiod {}", i)); + } + for (int i = 0; i < 5; i++) { + configuration::Contact* ct = pb_config.add_contacts(); + configuration::contact_helper ct_hlp(ct); + std::string name(fmt::format("name{:2}", i)); + ct->set_contact_name(name); + ct->set_alias(fmt::format("alias{:2}", i)); + for (int j = 0; j < 3; j++) + ct->add_address(fmt::format("address{:2}", j)); + for (int j = 0; j < 10; j++) { + configuration::CustomVariable* cv = ct->add_customvariables(); + cv->set_name(fmt::format("key_{}_{}", name, j)); + cv->set_value(fmt::format("value_{}_{}", name, j)); + } + } + } + + void TearDown() override { deinit_config_state(); } +}; + +using MessageDifferencer = ::google::protobuf::util::MessageDifferencer; + +static void CreateFile(const std::string& filename, + const std::string& content) { + std::ofstream oss(filename); + oss << content; + oss.close(); +} + +static void AddCfgFile(const std::string& filename) { + std::ifstream ss("/tmp/centengine.cfg"); + std::list lines; + std::string s; + while (getline(ss, s)) { + lines.push_back(std::move(s)); + } + for (auto it = lines.begin(); it != lines.end(); ++it) { + if (it->find("cfg_file") == 0) { + lines.insert(it, fmt::format("cfg_file={}", filename)); + break; + } + } + std::ofstream oss("/tmp/centengine.cfg"); + for (auto& l : lines) + oss << l << std::endl; +} + +static void RmConf() { + std::remove("/tmp/ad.cfg"); + std::remove("/tmp/centengine.cfg"); + std::remove("/tmp/commands.cfg"); + std::remove("/tmp/connectors.cfg"); + std::remove("/tmp/contactgroups.cfg"); + std::remove("/tmp/contacts.cfg"); + std::remove("/tmp/dependencies.cfg"); + std::remove("/tmp/hostescalations.cfg"); + std::remove("/tmp/hostgroups.cfg"); + std::remove("/tmp/hosts.cfg"); + std::remove("/tmp/resource.cfg"); + std::remove("/tmp/servicedependencies.cfg"); + std::remove("/tmp/serviceescalations.cfg"); + std::remove("/tmp/servicegroups.cfg"); + std::remove("/tmp/services.cfg"); + std::remove("/tmp/severities.cfg"); + std::remove("/tmp/tags.cfg"); + std::remove("/tmp/test-config.cfg"); + std::remove("/tmp/timeperiods.cfg"); +} + +enum class ConfigurationObject { + ANOMALYDETECTION = 0, + CONTACTGROUP = 1, + DEPENDENCY = 2, + ESCALATION = 3, + SERVICEGROUP = 4, + SEVERITY = 5, + TAG = 6, + CONTACTGROUP_NE = 7, +}; + +static void CreateConf(int idx) { + constexpr const char* cmd1 = + "for i in " ENGINE_CFG_TEST "/conf1/*.cfg ; do cp $i /tmp ; done"; + switch (idx) { + case 1: + system(cmd1); + break; + default: + ASSERT_EQ(1, 0); + break; + } +} + +static void CreateBadConf(ConfigurationObject obj) { + CreateConf(1); + switch (obj) { + case ConfigurationObject::SERVICEGROUP: + CreateFile("/tmp/servicegroups.cfg", + "define servicegroup {\n" + " servicegroup_id 1000\n" + " name sg_tpl\n" + " members " + "host_1,service_1,host_1,service_2,host_1,service_4\n" + " notes notes for sg template\n" + "}\n" + "define servicegroup {\n" + " servicegroup_id 1\n" + " alias sg1\n" + " members " + "host_1,service_1,host_1,service_2,host_1,service_3\n" + " notes notes for sg1\n" + " notes_url notes url for sg1\n" + " action_url action url for sg1\n" + " use sg_tpl\n" + "}\n"); + break; + case ConfigurationObject::TAG: + CreateFile("/tmp/tags.cfg", + "define tag {\n" + " tag_id 1\n" + " tag_name tag1\n" + "}\n"); + break; + case ConfigurationObject::ANOMALYDETECTION: + CreateFile("/tmp/ad.cfg", + "define anomalydetection {\n" + " service_description service_ad\n" + " host_name Centreon-central\n" + " service_id 2000\n" + " register 1\n" + " dependent_service_id 1\n" + " thresholds_file /tmp/toto\n" + "}\n"); + AddCfgFile("/tmp/ad.cfg"); + break; + case ConfigurationObject::CONTACTGROUP: + CreateFile("/tmp/contactgroups.cfg", + "define contactgroup {\n" + " name cg_tpl\n" + " members user1,user2,user3\n" + " contactgroup_members cg2\n" + "}\n"); + break; + case ConfigurationObject::CONTACTGROUP_NE: + CreateFile("/tmp/contactgroups.cfg", + "define contactgroup {\n" + " contactgroup_name cg1\n" + " alias cg1_a\n" + " members user1,user2\n" + " contactgroup_members +cg3\n" + "}\n" + "define contactgroup {\n" + " contactgroup_name cg2\n" + " alias cg2_a\n" + " members user1,user2\n" + " contactgroup_members cg3\n" + "}\n" + "define contactgroup {\n" + " contactgroup_name cg3\n" + " alias cg3_a\n" + " use cg_tpl\n" + " members +user3\n" + " contactgroup_members cg3\n" + "}\n" + "define contactgroup {\n" + " contactgroup_name cg_tpl\n" + " name cg_tpl\n" + " members user1,user2\n" + " contactgroup_members cg2\n" + "}\n"); + break; + case ConfigurationObject::SEVERITY: + CreateFile("/tmp/severities.cfg", + "define severity {\n" + " severity_name sev1\n" + " id 3\n" + " level 14\n" + " icon_id 123\n" + "}\n"); + break; + case ConfigurationObject::ESCALATION: + CreateFile("/tmp/escalations.cfg", + "define serviceescalation {\n" + " host_name host_1\n" + " description service_2\n" + "}\n" + "define serviceescalation {\n" + " host_name host_1\n" + " contact_groups cg1\n" + "}\n" + "define hostescalation {\n" + " contact_groups cg1,cg2\n" + " name he_tmpl\n" + "}\n" + "define hostescalation {\n" + " contact_groups +cg1\n" + " hostgroup_name hg1,hg2\n" + " use he_tmpl\n" + "}\n"); + break; + case ConfigurationObject::DEPENDENCY: + CreateFile("/tmp/dependencies.cfg", + "define hostdependency {\n" + " dependent_hostgroup_name hg1,hg2\n" + " dependent_host_name host_2\n" + " name hd_tmpl\n" + "}\n" + "define servicedependency {\n" + " servicegroup_name sg1\n" + " host host_1\n" + " dependent_hostgroup_name host_3\n" + " dependent_host_name host_2\n" + " use hd_tmpl\n" + "}\n" + "define servicedependency {\n" + " service_description service_2\n" + " dependent_description service_1\n" + "}\n"); + break; + default: + break; + } +} + +constexpr size_t CFG_FILES = 19u; +constexpr size_t RES_FILES = 1u; +constexpr size_t HOSTS = 11u; +constexpr size_t SERVICES = 363u; +constexpr size_t TIMEPERIODS = 2u; +constexpr size_t CONTACTS = 1u; +constexpr size_t HOSTGROUPS = 2u; +constexpr size_t SERVICEGROUPS = 1u; +constexpr size_t HOSTDEPENDENCIES = 2u; + +// TEST_F(ApplierState, DiffOnTimeperiod) { +// configuration::State new_config; +// new_config.CopyFrom(pb_config); +// +// std::string output; +// MessageDifferencer differencer; +// differencer.set_report_matches(false); +// differencer.ReportDifferencesToString(&output); +// EXPECT_TRUE(differencer.Compare(pb_config, new_config)); +// std::cout << "Output= " << output << std::endl; +// +// configuration::DiffState dstate = +// configuration::applier::state::instance().build_difference(pb_config, +// new_config); +// ASSERT_TRUE(dstate.to_add().empty()); +// ASSERT_TRUE(dstate.to_remove().empty()); +// ASSERT_TRUE(dstate.to_modify().empty()); +// } +// +// TEST_F(ApplierState, DiffOnTimeperiodOneRemoved) { +// configuration::State new_config; +// new_config.CopyFrom(pb_config); +// new_config.mutable_timeperiods()->RemoveLast(); +// +// std::string output; +// MessageDifferencer differencer; +// differencer.set_report_matches(false); +// differencer.ReportDifferencesToString(&output); +// // differencer.set_repeated_field_comparison( +// // util::MessageDifferencer::AS_SMART_LIST); +// EXPECT_FALSE(differencer.Compare(pb_config, new_config)); +// std::cout << "Output= " << output << std::endl; +// +// configuration::DiffState dstate = +// configuration::applier::state::instance().build_difference(pb_config, +// new_config); +// ASSERT_EQ(dstate.to_remove().size(), 1u); +// // Number 142 is to remove. +// ASSERT_EQ(dstate.to_remove()[0].key()[0].i32(), 143); +// ASSERT_EQ(dstate.to_remove()[0].key()[1].i32(), 9); +// ASSERT_EQ(dstate.to_remove()[0].key().size(), 2); +// ASSERT_TRUE(dstate.to_add().empty()); +// ASSERT_TRUE(dstate.to_modify().empty()); +// } + +// TEST_F(ApplierState, DiffOnTimeperiodNewOne) { +// configuration::State new_config; +// new_config.CopyFrom(pb_config); +// auto tps = new_config.mutable_timeperiods(); +// auto* tp = tps->Add(); +// tp->set_alias("timeperiod 11"); +// tp->set_timeperiod_name("Timeperiod 11"); +// +// std::string output; +// MessageDifferencer differencer; +// differencer.set_report_matches(false); +// differencer.ReportDifferencesToString(&output); +// // differencer.set_repeated_field_comparison( +// // util::MessageDifferencer::AS_SMART_LIST); +// EXPECT_FALSE(differencer.Compare(pb_config, new_config)); +// std::cout << "Output= " << output << std::endl; +// +// configuration::DiffState dstate = +// configuration::applier::state::instance().build_difference(pb_config, +// new_config); +// ASSERT_TRUE(dstate.to_remove().empty()); +// ASSERT_TRUE(dstate.to_modify().empty()); +// ASSERT_EQ(dstate.to_add().size(), 1u); +// ASSERT_TRUE(dstate.to_add()[0].val().has_value_tp()); +// const configuration::Timeperiod& new_tp = +// dstate.to_add()[0].val().value_tp(); ASSERT_EQ(new_tp.alias(), +// std::string("timeperiod 11")); ASSERT_EQ(new_tp.timeperiod_name(), +// std::string("Timeperiod 11")); +// } + +// TEST_F(ApplierState, DiffOnTimeperiodAliasRenamed) { +// configuration::State new_config; +// new_config.CopyFrom(pb_config); +// auto tps = new_config.mutable_timeperiods(); +// tps->at(7).set_alias("timeperiod changed"); +// +// std::string output; +// MessageDifferencer differencer; +// differencer.set_report_matches(false); +// differencer.ReportDifferencesToString(&output); +// // differencer.set_repeated_field_comparison( +// // util::MessageDifferencer::AS_SMART_LIST); +// EXPECT_FALSE(differencer.Compare(pb_config, new_config)); +// std::cout << "Output= " << output << std::endl; +// +// configuration::DiffState dstate = +// configuration::applier::state::instance().build_difference(pb_config, +// new_config); +// ASSERT_TRUE(dstate.to_remove().empty()); +// ASSERT_TRUE(dstate.to_add().empty()); +// ASSERT_EQ(dstate.to_modify().size(), 1u); +// const configuration::PathWithValue& path = dstate.to_modify()[0]; +// ASSERT_EQ(path.path().key().size(), 4u); +// // number 142 => timeperiods +// ASSERT_EQ(path.path().key()[0].i32(), 143); +// // index 7 => timeperiods[7] +// ASSERT_EQ(path.path().key()[1].i32(), 7); +// // number 2 => timeperiods.alias +// ASSERT_EQ(path.path().key()[2].i32(), 2); +// // No more key... +// ASSERT_EQ(path.path().key()[3].i32(), -1); +// ASSERT_TRUE(path.val().has_value_str()); +// // The new value of timeperiods[7].alias is "timeperiod changed" +// ASSERT_EQ(path.val().value_str(), std::string("timeperiod changed")); +// } + +// TEST_F(ApplierState, DiffOnContactOneRemoved) { +// configuration::State new_config; +// new_config.CopyFrom(pb_config); +// new_config.mutable_contacts()->DeleteSubrange(4, 1); +// +// std::string output; +// MessageDifferencer differencer; +// differencer.set_report_matches(false); +// differencer.ReportDifferencesToString(&output); +// // differencer.set_repeated_field_comparison( +// // util::MessageDifferencer::AS_SMART_LIST); +// EXPECT_FALSE(differencer.Compare(pb_config, new_config)); +// std::cout << "Output= " << output << std::endl; +// +// configuration::DiffState dstate = +// configuration::applier::state::instance().build_difference(pb_config, +// new_config); +// ASSERT_EQ(dstate.to_remove().size(), 1u); +// +// ASSERT_EQ(dstate.to_remove()[0].key().size(), 2); +// // number 131 => for contacts +// ASSERT_EQ(dstate.to_remove()[0].key()[0].i32(), 132); +// // "name 4" => contacts["name 4"] +// ASSERT_EQ(dstate.to_remove()[0].key()[1].i32(), 4); +// +// ASSERT_TRUE(dstate.to_add().empty()); +// ASSERT_TRUE(dstate.to_modify().empty()); +// } + +// TEST_F(ApplierState, DiffOnContactOneAdded) { +// configuration::State new_config; +// new_config.CopyFrom(pb_config); +// pb_config.mutable_contacts()->DeleteSubrange(4, 1); +// +// std::string output; +// MessageDifferencer differencer; +// differencer.set_report_matches(false); +// differencer.ReportDifferencesToString(&output); +// // differencer.set_repeated_field_comparison( +// // util::MessageDifferencer::AS_SMART_LIST); +// EXPECT_FALSE(differencer.Compare(pb_config, new_config)); +// std::cout << "Output= " << output << std::endl; +// +// configuration::DiffState dstate = +// configuration::applier::state::instance().build_difference(pb_config, +// new_config); +// ASSERT_TRUE(dstate.to_remove().empty()); +// ASSERT_TRUE(dstate.to_modify().empty()); +// ASSERT_EQ(dstate.to_add().size(), 1u); +// const configuration::PathWithValue& to_add = dstate.to_add()[0]; +// ASSERT_EQ(to_add.path().key().size(), 2u); +// // Contact -> number 131 +// ASSERT_EQ(to_add.path().key()[0].i32(), 132); +// // ASSERT_EQ(to_add.path().key()[1].str(), std::string("name 4")); +// ASSERT_TRUE(to_add.val().has_value_ct()); +// } + +/** + * @brief Contact "name 3" has a new address added. Addresses are stored in + * an array. We don't have the information if an address is added or removed + * so we send all the addresses in the difference. That's why the difference + * tells about 4 addresses as difference. + */ +// TEST_F(ApplierState, DiffOnContactOneNewAddress) { +// configuration::State new_config; +// new_config.CopyFrom(pb_config); +// auto& ct = new_config.mutable_contacts()->at(3); +// ct.add_address("new address"); +// +// std::string output; +// MessageDifferencer differencer; +// differencer.set_report_matches(false); +// differencer.ReportDifferencesToString(&output); +// // differencer.set_repeated_field_comparison( +// // util::MessageDifferencer::AS_SMART_LIST); +// EXPECT_FALSE(differencer.Compare(pb_config, new_config)); +// std::cout << "Output= " << output << std::endl; +// +// configuration::DiffState dstate = +// configuration::applier::state::instance().build_difference(pb_config, +// new_config); +// ASSERT_EQ(dstate.to_add().size(), 1u); +// ASSERT_TRUE(dstate.to_modify().empty()); +// ASSERT_TRUE(dstate.to_remove().empty()); +// ASSERT_EQ(dstate.to_add()[0].path().key().size(), 4u); +// // Number of Contacts in State +// ASSERT_EQ(dstate.to_add()[0].path().key()[0].i32(), 132); +// // Key to the context to change +// ASSERT_EQ(dstate.to_add()[0].path().key()[1].i32(), 3); +// // Number of the object to modify +// ASSERT_EQ(dstate.to_add()[0].path().key()[2].i32(), 2); +// // Index of the new object to add. +// ASSERT_EQ(dstate.to_add()[0].path().key()[3].i32(), 3); +// +// ASSERT_EQ(dstate.to_add()[0].val().value_str(), std::string("new +// address")); +// } + +/** + * @brief Contact "name 3" has its first address removed. Addresses are stored + * in an array. We don't have the information if an address is added or removed + * so we send all the addresses in the difference. That's why the difference + * tells about 4 addresses as difference. + */ +// TEST_F(ApplierState, DiffOnContactFirstAddressRemoved) { +// configuration::State new_config; +// new_config.CopyFrom(pb_config); +// auto& ct = new_config.mutable_contacts()->at(3); +// ct.mutable_address()->erase(ct.mutable_address()->begin()); +// +// std::string output; +// MessageDifferencer differencer; +// differencer.set_report_matches(false); +// differencer.ReportDifferencesToString(&output); +// // differencer.set_repeated_field_comparison( +// // util::MessageDifferencer::AS_SMART_LIST); +// EXPECT_FALSE(differencer.Compare(pb_config, new_config)); +// std::cout << "Output= " << output << std::endl; +// +// configuration::DiffState dstate = +// configuration::applier::state::instance().build_difference(pb_config, +// new_config); +// ASSERT_TRUE(dstate.to_add().empty()); +// ASSERT_EQ(dstate.to_modify().size(), 2u); +// ASSERT_EQ(dstate.to_remove().size(), 1u); +// ASSERT_EQ(dstate.to_modify()[0].path().key().size(), 4u); +// // Number of contacts in State +// ASSERT_EQ(dstate.to_modify()[0].path().key()[0].i32(), 132); +// // Key "name 3" to the good contact +// ASSERT_EQ(dstate.to_modify()[0].path().key()[1].i32(), 3); +// // Number of addresses in Contact +// ASSERT_EQ(dstate.to_modify()[0].path().key()[2].i32(), 2); +// // Index of the address to modify +// ASSERT_EQ(dstate.to_modify()[0].path().key()[3].i32(), 0); +// // New value of the address +// ASSERT_EQ(dstate.to_modify()[0].val().value_str(), std::string("address +// 1")); +// +// ASSERT_EQ(dstate.to_remove()[0].key().size(), 4u); +// // Number of contacts in State +// ASSERT_EQ(dstate.to_remove()[0].key()[0].i32(), 132); +// // Key "name 3" to the good contact +// ASSERT_EQ(dstate.to_remove()[0].key()[1].i32(), 3); +// // Number of addresses in Contact +// ASSERT_EQ(dstate.to_remove()[0].key()[2].i32(), 2); +// // Index of the address to remove +// ASSERT_EQ(dstate.to_remove()[0].key()[3].i32(), 2); +// } + +/** + * @brief Contact "name 3" has its first address removed. Addresses are stored + * in an array. We don't have the information if an address is added or removed + * so we send all the addresses in the difference. That's why the difference + * tells about 4 addresses as difference. + */ +// TEST_F(ApplierState, DiffOnContactSecondAddressUpdated) { +// configuration::State new_config; +// new_config.CopyFrom(pb_config); +// auto& ct = new_config.mutable_contacts()->at(3); +// (*ct.mutable_address())[1] = "this address is different"; +// +// std::string output; +// MessageDifferencer differencer; +// differencer.set_report_matches(false); +// differencer.ReportDifferencesToString(&output); +// // differencer.set_repeated_field_comparison( +// // util::MessageDifferencer::AS_SMART_LIST); +// EXPECT_FALSE(differencer.Compare(pb_config, new_config)); +// std::cout << "Output= " << output << std::endl; +// +// configuration::DiffState dstate = +// configuration::applier::state::instance().build_difference(pb_config, +// new_config); +// // ASSERT_TRUE(dstate.dcontacts().to_add().empty()); +// // ASSERT_TRUE(dstate.dcontacts().to_remove().empty()); +// // ASSERT_EQ(dstate.dcontacts().to_modify().size(), 1u); +// // auto to_modify = dstate.dcontacts().to_modify(); +// // ASSERT_EQ(to_modify["name 3"].list().begin()->id(), 2); +// // ASSERT_EQ(to_modify["name 3"].list().begin()->value_str(), "address +// 2"); +// } + +// TEST_F(ApplierState, DiffOnContactRemoveCustomvariable) { +// configuration::State new_config; +// new_config.CopyFrom(pb_config); +// auto& ct = new_config.mutable_contacts()->at(3); +// ct.mutable_customvariables()->erase(ct.mutable_customvariables()->begin()); +// +// std::string output; +// MessageDifferencer differencer; +// differencer.set_report_matches(false); +// differencer.ReportDifferencesToString(&output); +// // differencer.set_repeated_field_comparison( +// // util::MessageDifferencer::AS_SMART_LIST); +// EXPECT_FALSE(differencer.Compare(pb_config, new_config)); +// std::cout << "Output= " << output << std::endl; +// +// configuration::DiffState dstate = +// configuration::applier::state::instance().build_difference(pb_config, +// new_config); +// // ASSERT_TRUE(dstate.dcontacts().to_add().empty()); +// // ASSERT_TRUE(dstate.dcontacts().to_remove().empty()); +// // ASSERT_EQ(dstate.dcontacts().to_modify().size(), 1u); +// // auto to_modify = dstate.dcontacts().to_modify(); +// // ASSERT_EQ(to_modify["name 3"].list().begin()->id(), 2); +// // ASSERT_EQ(to_modify["name 3"].list().begin()->value_str(), "address +// 2"); +// } + +TEST_F(ApplierState, StateParsing) { + configuration::error_cnt err; + configuration::State cfg; + configuration::parser p; + CreateConf(1); + p.parse("/tmp/centengine.cfg", &cfg, err); + ASSERT_EQ(cfg.check_service_freshness(), false); + ASSERT_EQ(cfg.enable_flap_detection(), false); + ASSERT_EQ(cfg.instance_heartbeat_interval(), 30); + ASSERT_EQ(cfg.log_level_functions(), configuration::LogLevel::warning); + ASSERT_EQ(cfg.cfg_file().size(), CFG_FILES); + ASSERT_EQ(cfg.resource_file().size(), RES_FILES); + ASSERT_EQ(cfg.hosts().size(), HOSTS); + ASSERT_EQ(cfg.hosts()[0].host_name(), std::string("Centreon-central")); + ASSERT_TRUE(cfg.hosts()[0].obj().register_()); + ASSERT_EQ(cfg.hosts()[0].host_id(), 30); + ASSERT_EQ(cfg.hosts()[1].host_name(), std::string("Centreon-central_1")); + ASSERT_TRUE(cfg.hosts()[1].obj().register_()); + ASSERT_EQ(cfg.hosts()[1].host_id(), 31); + ASSERT_EQ(cfg.hosts()[2].host_name(), std::string("Centreon-central_2")); + ASSERT_TRUE(cfg.hosts()[2].obj().register_()); + ASSERT_EQ(cfg.hosts()[2].host_id(), 32); + ASSERT_EQ(cfg.hosts()[3].host_name(), std::string("Centreon-central_3")); + ASSERT_TRUE(cfg.hosts()[3].obj().register_()); + ASSERT_EQ(cfg.hosts()[3].host_id(), 33); + + /* Service */ + ASSERT_EQ(cfg.services().size(), SERVICES); + ASSERT_EQ(cfg.services()[0].service_id(), 196); + ASSERT_TRUE(cfg.services()[0].obj().register_()); + ASSERT_TRUE(cfg.services()[0].checks_active()); + ASSERT_EQ(cfg.services()[0].host_name(), + std::string_view("Centreon-central")); + ASSERT_EQ(cfg.services()[0].service_description(), + std::string_view("proc-sshd")); + ASSERT_EQ(cfg.services()[0].contactgroups().data().size(), 2u); + EXPECT_EQ(cfg.services()[0].contactgroups().data()[0], + std::string_view("Guest")); + EXPECT_EQ(cfg.services()[0].contactgroups().data()[1], + std::string_view("Supervisors")); + + EXPECT_EQ(cfg.services()[0].contacts().data().size(), 1u); + EXPECT_EQ(cfg.services()[0].contacts().data()[0], std::string("John_Doe")); + EXPECT_EQ(cfg.services()[0].notification_options(), 0x3f); + std::set> exp{{2, tag::servicegroup}}; + std::set> res; + for (auto& t : cfg.services()[0].tags()) { + uint16_t c; + switch (t.second()) { + case TagType::tag_servicegroup: + c = tag::servicegroup; + break; + case TagType::tag_hostgroup: + c = tag::hostgroup; + break; + case TagType::tag_servicecategory: + c = tag::servicecategory; + break; + case TagType::tag_hostcategory: + c = tag::hostcategory; + break; + default: + assert("Should not be raised" == nullptr); + } + res.emplace(t.first(), c); + } + EXPECT_EQ(res, exp); + + ASSERT_EQ(cfg.commands().size(), 15u); + auto fnd_cmd = + std::find_if(cfg.commands().begin(), cfg.commands().end(), + [](const configuration::Command& cmd) { + return cmd.command_name() == + std::string_view("App-Centreon-MySQL-Partitioning"); + }); + ASSERT_TRUE(fnd_cmd != cfg.commands().end()); + ASSERT_EQ( + fnd_cmd->command_line(), + std::string_view( + "$CENTREONPLUGINS$/centreon_centreon_database.pl " + "--plugin=database::mysql::plugin " + "--dyn-mode=apps::centreon::sql::mode::partitioning " + "--host='$HOSTADDRESS$' --username='$_HOSTMYSQLUSERNAME$' " + "--password='$_HOSTMYSQLPASSWORD$' --port='$_HOSTMYSQLPORT$' " + "--tablename='$_SERVICETABLENAME1$' " + "--tablename='$_SERVICETABLENAME2$' " + "--tablename='$_SERVICETABLENAME3$' " + "--tablename='$_SERVICETABLENAME4$' --warning='$_SERVICEWARNING$' " + "--critical='$_SERVICECRITICAL$'")); + + /* One command inherites from command_template */ + auto cit = std::find_if(cfg.commands().begin(), cfg.commands().end(), + [](const configuration::Command& cmd) { + return cmd.command_name() == + std::string_view("base_host_alive"); + }); + + ASSERT_NE(cit, cfg.commands().end()); + ASSERT_EQ(cit->command_name(), std::string_view("base_host_alive")); + ASSERT_EQ(cit->command_line(), + std::string_view("$USER1$/check_icmp -H $HOSTADDRESS$ -w " + "3000.0,80% -c 5000.0,100% -p 1")); + + ASSERT_EQ(cfg.timeperiods().size(), TIMEPERIODS); + auto tit = + std::find_if(cfg.timeperiods().begin(), cfg.timeperiods().end(), + [](const configuration::Timeperiod& tp) { + return tp.timeperiod_name() == std::string_view("24x7"); + }); + ASSERT_NE(tit, cfg.timeperiods().end()); + EXPECT_EQ(tit->alias(), std::string_view("24_Hours_A_Day,_7_Days_A_Week")); + EXPECT_EQ(tit->timeranges().sunday().size(), + 1u); // std::string("00:00-24:00")); + EXPECT_EQ(tit->timeranges().sunday()[0].range_start(), 0); + EXPECT_EQ(tit->timeranges().sunday()[0].range_end(), 3600 * 24); + EXPECT_EQ(tit->timeranges().monday().size(), 1u); + EXPECT_EQ(tit->timeranges().monday()[0].range_start(), 0); + EXPECT_EQ(tit->timeranges().monday()[0].range_end(), 86400); + EXPECT_EQ(tit->timeranges().monday().size(), 1); + EXPECT_EQ(tit->timeranges().tuesday().size(), 1u); + EXPECT_EQ(tit->timeranges().wednesday().size(), 1u); + EXPECT_EQ(tit->timeranges().thursday().size(), 1u); + EXPECT_EQ(tit->timeranges().friday().size(), 1u); + EXPECT_EQ(tit->timeranges().saturday().size(), 1u); + + ASSERT_EQ(cfg.contacts().size(), CONTACTS); + const auto& ct = cfg.contacts().at(0); + EXPECT_EQ(ct.contact_name(), std::string("John_Doe")); + EXPECT_TRUE(ct.can_submit_commands()); + EXPECT_TRUE(ct.host_notifications_enabled()); + EXPECT_EQ(ct.host_notification_options(), + configuration::action_hst_up | configuration::action_hst_down | + configuration::action_hst_unreachable); + EXPECT_TRUE(ct.retain_nonstatus_information()); + EXPECT_TRUE(ct.retain_status_information()); + EXPECT_TRUE(ct.service_notifications_enabled()); + EXPECT_EQ(ct.service_notification_options(), + configuration::action_svc_warning | + configuration::action_svc_unknown | + configuration::action_svc_critical); + EXPECT_EQ(ct.alias(), std::string_view("admin")); + EXPECT_EQ(ct.contactgroups().data().size(), 0u); + + ASSERT_EQ(cfg.hostgroups().size(), HOSTGROUPS); + auto hgit = cfg.hostgroups().begin(); + while (hgit != cfg.hostgroups().end() && hgit->hostgroup_name() != "hg1") + ++hgit; + ASSERT_TRUE(hgit != cfg.hostgroups().end()); + const auto hg = *hgit; + ASSERT_EQ(hg.hostgroup_id(), 3u); + ASSERT_EQ(hg.hostgroup_name(), std::string_view("hg1")); + ASSERT_EQ(hg.alias(), std::string_view("hg1")); + ASSERT_EQ(hg.members().data().size(), 3u); + { + auto it = hg.members().data().begin(); + ASSERT_EQ(*it, std::string_view("Centreon-central_2")); + ++it; + ASSERT_EQ(*it, std::string_view("Centreon-central_3")); + ++it; + ASSERT_EQ(*it, std::string_view("Centreon-central_4")); + } + ASSERT_EQ(hg.notes(), std::string_view("note_hg1")); + ASSERT_EQ(hg.notes_url(), std::string_view()); + ASSERT_EQ(hg.action_url(), std::string_view()); + + ASSERT_EQ(cfg.servicegroups().size(), SERVICEGROUPS); + auto sgit = cfg.servicegroups().begin(); + while (sgit != cfg.servicegroups().end() && + sgit->servicegroup_name() != "Database-MySQL") + ++sgit; + ASSERT_TRUE(sgit != cfg.servicegroups().end()); + const auto sg = *sgit; + ASSERT_EQ(sg.servicegroup_id(), 2u); + ASSERT_EQ(sg.servicegroup_name(), std::string_view("Database-MySQL")); + ASSERT_EQ(sg.alias(), std::string_view("Database-MySQL")); + ASSERT_EQ(sg.members().data().size(), 67u); + { + auto find_pair = [&data = sg.members().data()](std::string_view first, + std::string_view second) { + auto retval = std::find_if( + data.begin(), data.end(), + [&first, &second](const configuration::PairStringSet_Pair& m) { + return m.first() == first && m.second() == second; + }); + return retval; + }; + + auto it = sg.members().data().begin(); + it = find_pair("Centreon-central", "Connection-Time"); + ASSERT_NE(it, sg.members().data().end()); + + it = find_pair("Centreon-central", "Connections-Number"); + ASSERT_NE(it, sg.members().data().end()); + + it = find_pair("Centreon-central", "Myisam-Keycache"); + ASSERT_NE(it, sg.members().data().end()); + } + ASSERT_EQ(sg.notes(), std::string_view()); + ASSERT_EQ(sg.notes_url(), std::string_view()); + ASSERT_EQ(sg.action_url(), std::string_view()); + + auto sdit = cfg.servicedependencies().begin(); + while (sdit != cfg.servicedependencies().end() && + std::find(sdit->servicegroups().data().begin(), + sdit->servicegroups().data().end(), + "sg1") != sdit->servicegroups().data().end()) + ++sdit; + ASSERT_TRUE(sdit != cfg.servicedependencies().end()); + ASSERT_TRUE(*sdit->hosts().data().begin() == + std::string_view("Centreon-central")); + ASSERT_TRUE(*sdit->dependent_service_description().data().begin() == + std::string_view("Connections-Number")); + ASSERT_TRUE(*sdit->dependent_hosts().data().begin() == + std::string_view("Centreon-central")); + ASSERT_TRUE(sdit->inherits_parent()); + ASSERT_EQ(sdit->execution_failure_options(), + configuration::action_sd_unknown | configuration::action_sd_ok); + ASSERT_EQ( + sdit->notification_failure_options(), + configuration::action_sd_warning | configuration::action_sd_critical); + + // Anomalydetections + ASSERT_TRUE(cfg.anomalydetections().empty()); + // auto adit = cfg.anomalydetections().begin(); + // while (adit != cfg.anomalydetections().end() && + // (adit->service_id() != 2001 || adit->host_id() != 1)) + // ++adit; + // ASSERT_TRUE(adit != cfg.anomalydetections().end()); + // ASSERT_TRUE(adit->service_description() == "service_ad2"); + // ASSERT_EQ(adit->dependent_service_id(), 1); + // ASSERT_TRUE(adit->metric_name() == "metric2"); + // ASSERT_EQ(adit->customvariables().size(), 1); + // ASSERT_EQ(adit->customvariables().at(0).value(), + // std::string("this_is_a_test")); + // ASSERT_EQ(adit->contactgroups().data().size(), 2); + // ASSERT_EQ(adit->contacts().data().size(), 1); + // ASSERT_EQ(adit->servicegroups().data().size(), 2); + + auto cgit = cfg.contactgroups().begin(); + ASSERT_EQ(cfg.contactgroups().size(), 2u); + ASSERT_EQ(cgit->contactgroup_name(), std::string_view("Guest")); + ASSERT_EQ(cgit->alias(), std::string_view("Guests Group")); + ASSERT_EQ(cgit->members().data().size(), 0u); + ASSERT_EQ(cgit->contactgroup_members().data().size(), 0u); + + ++cgit; + ASSERT_TRUE(cgit != cfg.contactgroups().end()); + ASSERT_EQ(cgit->contactgroup_name(), std::string_view("Supervisors")); + ASSERT_EQ(cgit->alias(), std::string_view("Centreon supervisors")); + ASSERT_EQ(cgit->members().data().size(), 1u); + ASSERT_EQ(*cgit->members().data().begin(), "John_Doe"); + ASSERT_EQ(cgit->contactgroup_members().data().size(), 0u); + + ++cgit; + ASSERT_TRUE(cgit == cfg.contactgroups().end()); + + ASSERT_EQ(cfg.connectors().size(), 2); + auto cnit = cfg.connectors().begin(); + ASSERT_TRUE(cnit != cfg.connectors().end()); + ASSERT_EQ(cnit->connector_name(), std::string_view("Perl Connector")); + ASSERT_EQ(cnit->connector_line(), + std::string_view( + "/usr/lib64/centreon-connector/centreon_connector_perl " + "--log-file=/var/log/centreon-engine/connector-perl.log")); + ++cnit; + ASSERT_EQ(cnit->connector_name(), std::string_view("SSH Connector")); + ASSERT_EQ(cnit->connector_line(), + std::string_view( + "/usr/lib64/centreon-connector/centreon_connector_ssh " + "--log-file=/var/log/centreon-engine/connector-ssh.log")); + ++cnit; + ASSERT_TRUE(cnit == cfg.connectors().end()); + + /* Severities */ + ASSERT_EQ(cfg.severities().size(), 2); + auto svit = cfg.severities().begin(); + ++svit; + ASSERT_TRUE(svit != cfg.severities().end()); + ASSERT_EQ(svit->severity_name(), std::string_view("severity1")); + EXPECT_EQ(svit->key().id(), 5); + EXPECT_EQ(svit->level(), 1); + EXPECT_EQ(svit->icon_id(), 3); + ASSERT_EQ(svit->key().type(), configuration::SeverityType::service); + + /* Serviceescalations */ + ASSERT_EQ(cfg.serviceescalations().size(), 6); + auto seit = cfg.serviceescalations().begin(); + EXPECT_TRUE(seit != cfg.serviceescalations().end()); + EXPECT_EQ(*seit->hosts().data().begin(), + std::string_view("Centreon-central")); + ASSERT_EQ(*seit->service_description().data().begin(), + std::string_view("Cpu")); + ++seit; + ASSERT_EQ(seit->hosts().data().size(), 1); + ASSERT_EQ(seit->contactgroups().data().size(), 1); + EXPECT_EQ(*seit->contactgroups().data().begin(), "Supervisors"); + ASSERT_EQ(seit->servicegroups().data().size(), 0); + std::list se_names; + std::list se_base{"Connection-Time", "Cpu", "Cpu", + "Database-Size"}; + for (auto& se : cfg.serviceescalations()) { + if (se.service_description().data().size()) { + ASSERT_EQ(se.service_description().data().size(), 1); + se_names.push_back(*se.service_description().data().begin()); + } + } + se_names.sort(); + ASSERT_EQ(se_names, se_base); + + /*Hostescalations */ + auto heit = cfg.hostescalations().begin(); + ASSERT_TRUE(heit != cfg.hostescalations().end()); + std::set cts{"Supervisors"}; + std::set he_cts; + for (auto& cg : heit->contactgroups().data()) + he_cts.insert(cg); + ASSERT_EQ(he_cts, cts); + ++heit; + + std::set hgs{"hg1"}; + std::set he_hgs; + for (auto& hg : heit->hostgroups().data()) + he_hgs.insert(hg); + ASSERT_EQ(he_hgs, hgs); + + /*Hostdependencies */ + ASSERT_EQ(cfg.hostdependencies().size(), HOSTDEPENDENCIES); + + configuration::applier::state::instance().apply(cfg, err); + + ASSERT_TRUE(std::all_of(cfg.hostdependencies().begin(), + cfg.hostdependencies().end(), [](const auto& hd) { + return hd.hostgroups().data().empty() && + hd.dependent_hostgroups().data().empty(); + })); + + RmConf(); +} + +TEST_F(ApplierState, StateParsingServicegroupValidityFailed) { + configuration::State config; + configuration::parser p; + CreateBadConf(ConfigurationObject::SERVICEGROUP); + configuration::error_cnt err; + ASSERT_THROW(p.parse("/tmp/centengine.cfg", &config, err), std::exception); +} + +TEST_F(ApplierState, StateParsingTagValidityFailed) { + configuration::State config; + configuration::parser p; + CreateBadConf(ConfigurationObject::TAG); + configuration::error_cnt err; + ASSERT_THROW(p.parse("/tmp/centengine.cfg", &config, err), std::exception); +} + +TEST_F(ApplierState, StateParsingAnomalydetectionValidityFailed) { + configuration::State config; + configuration::parser p; + CreateBadConf(ConfigurationObject::ANOMALYDETECTION); + configuration::error_cnt err; + ASSERT_THROW(p.parse("/tmp/centengine.cfg", &config, err), std::exception); +} + +TEST_F(ApplierState, StateParsingSeverityWithoutType) { + configuration::State config; + configuration::parser p; + CreateBadConf(ConfigurationObject::SEVERITY); + configuration::error_cnt err; + ASSERT_THROW(p.parse("/tmp/centengine.cfg", &config, err), std::exception); +} + +TEST_F(ApplierState, StateParsingHostdependencyWithoutHost) { + configuration::State config; + configuration::parser p; + CreateBadConf(ConfigurationObject::DEPENDENCY); + configuration::error_cnt err; + ASSERT_THROW(p.parse("/tmp/centengine.cfg", &config, err), std::exception); +} + +TEST_F(ApplierState, StateParsingNonexistingContactgroup) { + configuration::State cfg; + configuration::parser p; + CreateBadConf(ConfigurationObject::CONTACTGROUP_NE); + configuration::error_cnt err; + p.parse("/tmp/centengine.cfg", &cfg, err); + ASSERT_THROW(configuration::applier::state::instance().apply(cfg, err), + std::exception); +} + +TEST_F(ApplierState, StateParsingContactgroupWithoutName) { + configuration::State cfg; + configuration::parser p; + CreateBadConf(ConfigurationObject::CONTACTGROUP); + configuration::error_cnt err; + ASSERT_THROW(p.parse("/tmp/centengine.cfg", &cfg, err), std::exception); +} diff --git a/engine/tests/configuration/applier/applier-service.cc b/engine/tests/configuration/applier/applier-service.cc index 12a4788ccff..4833395a9b8 100644 --- a/engine/tests/configuration/applier/applier-service.cc +++ b/engine/tests/configuration/applier/applier-service.cc @@ -64,7 +64,6 @@ TEST_F(ApplierService, NewServiceWithHostNotDefinedFromConfig) { // Then the applier add_object throws an exception. TEST_F(ApplierService, NewHostWithoutHostId) { configuration::applier::host hst_aply; - configuration::applier::service svc_aply; configuration::service svc; configuration::host hst; ASSERT_TRUE(hst.parse("host_name", "test_host")); diff --git a/engine/tests/configuration/object.cc b/engine/tests/configuration/object.cc index dbf6de5cffe..1079c7df33e 100644 --- a/engine/tests/configuration/object.cc +++ b/engine/tests/configuration/object.cc @@ -1,26 +1,27 @@ /** - * Copyright 2016 Centreon + * Copyright 2016-2024 Centreon * - * This file is part of Centreon Engine. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * Centreon Engine is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License version 2 - * as published by the Free Software Foundation. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Centreon Engine 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. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com * - * You should have received a copy of the GNU General Public License - * along with Centreon Engine. If not, see - * . */ - #include +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/parser.hh" #include "common/engine_legacy_conf/service.hh" +#endif using namespace com::centreon; using namespace com::centreon::engine; diff --git a/engine/tests/configuration/pbcontact.cc b/engine/tests/configuration/pbcontact.cc new file mode 100644 index 00000000000..130e5e0382a --- /dev/null +++ b/engine/tests/configuration/pbcontact.cc @@ -0,0 +1,43 @@ +/* + * Copyright 2017 - 2019 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include +#include "common/engine_conf/contact_helper.hh" + +#include "helper.hh" + +using namespace com::centreon; +using namespace com::centreon::engine; +using namespace com::centreon::engine::configuration; + +class ConfigContact : public ::testing::Test { + public: + void SetUp() override { init_config_state(); } + + void TearDown() override { deinit_config_state(); } +}; + +// When I create a configuration::Contact with an empty name +// Then an exception is thrown. +TEST_F(ConfigContact, NewPbContactWithNoName) { + configuration::error_cnt err; + configuration::Contact ctct; + configuration::contact_helper ctct_hlp(&ctct); + ASSERT_THROW(ctct_hlp.check_validity(err), std::exception); +} diff --git a/engine/tests/configuration/pbhost.cc b/engine/tests/configuration/pbhost.cc new file mode 100644 index 00000000000..3a5d656f839 --- /dev/null +++ b/engine/tests/configuration/pbhost.cc @@ -0,0 +1,33 @@ +/** + * Copyright 2016 Centreon + * + * This file is part of Centreon Engine. + * + * Centreon Engine is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * Centreon Engine 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 Centreon Engine. If not, see + * . + */ + +#include + +#include "common/engine_conf/host_helper.hh" + +using namespace com::centreon::engine; + +// Given a host configuration object +// When it is default constructed +// Then its acknowledgements timeout is set to 0 +TEST(ConfigurationHostAcknowledgementTimeoutTest, PbDefaultConstruction) { + configuration::Host h; + configuration::host_helper hlp(&h); + ASSERT_EQ(0, h.acknowledgement_timeout()); +} diff --git a/engine/tests/configuration/pbservice.cc b/engine/tests/configuration/pbservice.cc new file mode 100644 index 00000000000..67316ac9649 --- /dev/null +++ b/engine/tests/configuration/pbservice.cc @@ -0,0 +1,42 @@ +/** + * Copyright 2016 Centreon + * + * This file is part of Centreon Engine. + * + * Centreon Engine is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * Centreon Engine 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 Centreon Engine. If not, see + * . + */ + +#include +#ifdef LEGACY_CONF +#include "common/engine_legacy_conf/service.hh" +#else +#include "common/engine_conf/service_helper.hh" +#endif + +using namespace com::centreon::engine; + +// Given a service configuration object +// When it is default constructed +// Then its acknowledgements timeout is set to 0 +TEST(ConfigurationServiceAcknowledgementTimeoutTest, PbDefaultConstruction) { + configuration::Service s; + configuration::service_helper hlp(&s); + ASSERT_EQ(0, s.acknowledgement_timeout()); +} + +TEST(ConfigurationServiceParseProperties, SetCustomVariable) { + configuration::Service s; + configuration::service_helper s_hlp(&s); + ASSERT_TRUE(s_hlp.insert_customvariable("_VARNAME", "TEST1")); +} diff --git a/engine/tests/configuration/pbseverity.cc b/engine/tests/configuration/pbseverity.cc new file mode 100644 index 00000000000..ba73839e434 --- /dev/null +++ b/engine/tests/configuration/pbseverity.cc @@ -0,0 +1,104 @@ +/** + * Copyright 2021 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include + +#include "common/engine_conf/severity_helper.hh" +#include "common/engine_conf/state.pb.h" +#include "helper.hh" + +using namespace com::centreon; +using namespace com::centreon::engine; +using namespace com::centreon::engine::configuration; + +class ConfigSeverity : public ::testing::Test { + public: + void SetUp() override { init_config_state(); } + + void TearDown() override { deinit_config_state(); } +}; + +// When I create a configuration::severity with a null id +// Then an exception is thrown. +TEST_F(ConfigSeverity, PbNewSeverityWithNoKey) { + configuration::error_cnt err; + configuration::Severity sv; + configuration::severity_helper sev_hlp(&sv); + sev_hlp.hook("severity_id", "0"); + sev_hlp.hook("severity_type", "service"); + ASSERT_THROW(sev_hlp.check_validity(err), std::exception); +} + +// When I create a configuration::severity with a null level +// Then an exception is thrown. +TEST_F(ConfigSeverity, PbNewSeverityWithNoLevel) { + configuration::error_cnt err; + configuration::Severity sv; + configuration::severity_helper sv_hlp(&sv); + sv_hlp.hook("severity_id", "1"); + sv_hlp.hook("severity_type", "service"); + ASSERT_THROW(sv_hlp.check_validity(err), std::exception); +} + +// When I create a configuration::severity with an empty name +// Then an exception is thrown. +TEST_F(ConfigSeverity, PbNewSeverityWithNoName) { + configuration::Severity sv; + configuration::severity_helper sv_hlp(&sv); + sv_hlp.hook("severity_id", "1"); + sv_hlp.hook("severity_type", "service"); + sv.set_level(2); + configuration::error_cnt err; + ASSERT_THROW(sv_hlp.check_validity(err), std::exception); +} + +// When I create a configuration::severity with a non empty name, +// non null id and non null level. +// Then no exception is thrown. +TEST_F(ConfigSeverity, PbNewSeverityWellFilled) { + configuration::error_cnt err; + configuration::Severity sv; + configuration::severity_helper sv_hlp(&sv); + sv_hlp.hook("severity_id", "1"); + sv_hlp.hook("severity_type", "service"); + sv.set_level(2); + sv.set_severity_name("foobar"); + ASSERT_EQ(sv.key().id(), 1); + ASSERT_EQ(sv.level(), 2); + ASSERT_EQ(sv.severity_name(), "foobar"); + ASSERT_EQ(sv.key().type(), configuration::SeverityType::service); + ASSERT_NO_THROW(sv_hlp.check_validity(err)); +} + +// When I create a configuration::severity with an icon id. +// Then we can get its value. +TEST_F(ConfigSeverity, PbNewSeverityIconId) { + configuration::error_cnt err; + configuration::Severity sv; + configuration::severity_helper sv_hlp(&sv); + sv_hlp.hook("severity_id", "1"); + sv_hlp.hook("severity_type", "host"); + sv.set_level(2); + sv.set_severity_name("foobar"); + ASSERT_EQ(sv.key().id(), 1); + ASSERT_EQ(sv.level(), 2); + ASSERT_EQ(sv.severity_name(), "foobar"); + sv.set_icon_id(18); + ASSERT_NO_THROW(sv_hlp.check_validity(err)); +} diff --git a/engine/tests/configuration/pbtag.cc b/engine/tests/configuration/pbtag.cc new file mode 100644 index 00000000000..eeefd2b8d9a --- /dev/null +++ b/engine/tests/configuration/pbtag.cc @@ -0,0 +1,100 @@ +/** + * Copyright 2022 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include +#include "common/engine_conf/tag_helper.hh" +#include "helper.hh" + +using namespace com::centreon; +using namespace com::centreon::engine; +using namespace com::centreon::engine::configuration; + +extern configuration::State pb_config; + +class ConfigTag : public ::testing::Test { + public: + void SetUp() override { init_config_state(); } + + void TearDown() override { deinit_config_state(); } +}; + +// When I create a configuration::tag with a null id +// Then an exception is thrown. +TEST_F(ConfigTag, NewTagWithNoKey) { + configuration::error_cnt err; + configuration::Tag tg; + configuration::tag_helper tag_hlp(&tg); + tg.mutable_key()->set_id(0); + tg.mutable_key()->set_type(0); + ASSERT_THROW(tag_hlp.check_validity(err), std::exception); +} + +// When I create a configuration::tag with a null type +// Then an exception is thrown. +TEST_F(ConfigTag, NewTagWithNoLevel) { + configuration::error_cnt err; + configuration::Tag tg; + configuration::tag_helper tg_hlp(&tg); + tg.mutable_key()->set_id(1); + tg.mutable_key()->set_type(0); + ASSERT_THROW(tg_hlp.check_validity(err), std::exception); +} + +// When I create a configuration::tag with an empty name +// Then an exception is thrown. +TEST_F(ConfigTag, NewTagWithNoName) { + configuration::error_cnt err; + configuration::Tag tg; + configuration::tag_helper tg_hlp(&tg); + tg.mutable_key()->set_id(1); + tg_hlp.hook("type", "hostcategory"); + ASSERT_THROW(tg_hlp.check_validity(err), std::exception); +} + +// When I create a configuration::tag with a non empty name, +// non null id and non null type +// Then no exception is thrown. +TEST_F(ConfigTag, NewTagWellFilled) { + configuration::error_cnt err; + configuration::Tag tg; + configuration::tag_helper tg_hlp(&tg); + tg.mutable_key()->set_id(1); + tg.mutable_key()->set_type(0); + tg_hlp.hook("type", "servicegroup"); + tg.set_tag_name("foobar"); + ASSERT_EQ(tg.key().id(), 1); + ASSERT_EQ(tg.key().type(), tag_servicegroup); + ASSERT_EQ(tg.tag_name(), "foobar"); + ASSERT_NO_THROW(tg_hlp.check_validity(err)); +} + +// When I create a configuration::tag with a non empty name, +// non null id and non null type. +// Then we can get the type value. +TEST_F(ConfigTag, NewTagIconId) { + configuration::error_cnt err; + configuration::Tag tg; + configuration::tag_helper tg_hlp(&tg); + tg.mutable_key()->set_id(1); + tg.mutable_key()->set_type(0); + tg_hlp.hook("type", "hostgroup"); + tg.set_tag_name("foobar"); + ASSERT_EQ(tg.key().type(), tag_hostgroup); + ASSERT_NO_THROW(tg_hlp.check_validity(err)); +} diff --git a/engine/tests/configuration/pbtimeperiod-test.cc b/engine/tests/configuration/pbtimeperiod-test.cc new file mode 100644 index 00000000000..8e14c422905 --- /dev/null +++ b/engine/tests/configuration/pbtimeperiod-test.cc @@ -0,0 +1,958 @@ +/** + * Copyright 2022-2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ +#include + +#include + +#include +#include + +#include "com/centreon/engine/common.hh" +#include "com/centreon/engine/configuration/applier/pb_difference.hh" +#include "com/centreon/engine/configuration/applier/timeperiod.hh" +#include "com/centreon/engine/globals.hh" +#include "common/engine_conf/timeperiod_helper.hh" + +using namespace com::centreon::engine::configuration; +using namespace com::centreon::engine; + +namespace com::centreon::engine::configuration { +class time_period_comparator { + static const std::regex name_extractor, alias_extractor, skip_extractor, + day_extractor, date_extractor, date_range1_extractor, + date_range2_extractor, range_extractor, full_date_extractor, + full_date_range_extractor, n_th_day_of_month_extractor, + n_th_day_of_month_range_extractor, n_th_day_of_week_extractor, + n_th_day_of_week_range_extractor, n_th_day_of_week_of_month_extractor, + n_th_day_of_week_of_month_range_extractor, exclude_extractor; + + static const std::map day_to_index, month_to_index; + + const configuration::Timeperiod& _conf_tp; + std::shared_ptr _result; + + static std::list extract_timerange( + const std::string& line_content, + uint32_t offset, + const std::smatch& datas); + std::string name, alias; + + /* days_array */ + std::array, 7> _timeranges; + std::array, + configuration::daterange::daterange_types> + _exceptions; + + std::set _exclude; + + public: + time_period_comparator(const configuration::Timeperiod& conf_tp, + const std::vector& timeperiod_content); + + static void extract_skip(const std::smatch matchs, + unsigned match_index, + Daterange& date_range); + bool is_equal() const; + + bool is_result_equal() const; + + const std::string get_name() const { return name; } +}; + +const std::string one_range("\\d\\d:\\d\\d\\-\\d\\d:\\d\\d"); +const std::string plage_regex("(" + one_range + ")(," + one_range + ")*"); +const std::string full_date("(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d)"); +const std::string months( + "(january|february|march|april|may|june|july|august|september|october|" + "november|december)"); +const std::string days( + "(sunday|monday|tuesday|wednesday|thursday|friday|saturday)"); + +const std::string skip("(\\s+|\\s*/\\s+\\d+)\\s*"); +const std::regex time_period_comparator::name_extractor( + "^name\\s+(\\S+[\\s\\S]+\\S+)"); +const std::regex time_period_comparator::alias_extractor( + "^alias\\s+(\\S+[\\s\\S]+\\S+)"); + +const std::regex time_period_comparator::skip_extractor("/\\s*(\\d+)"); + +const std::regex time_period_comparator::day_extractor("^" + days + "\\s+" + + plage_regex); + +const std::regex time_period_comparator::date_extractor("^" + months + + "\\s+(\\-*\\d+)\\s+" + + plage_regex); + +const std::regex time_period_comparator::date_range1_extractor( + "^" + months + "\\s+(\\-*\\d+)\\s+\\-\\s+(\\-*\\d+)" + skip + plage_regex); + +const std::regex time_period_comparator::date_range2_extractor( + "^" + months + "\\s+(\\-*\\d+)\\s+\\-\\s+" + months + "\\s+(\\-*\\d+)" + + skip + plage_regex); + +const std::regex time_period_comparator::range_extractor( + "(\\d\\d):(\\d\\d)\\-(\\d\\d):(\\d\\d)"); + +const std::regex time_period_comparator::full_date_extractor("^" + full_date + + skip + + plage_regex); + +const std::regex time_period_comparator::full_date_range_extractor( + "^" + full_date + "\\s*\\-\\s*" + full_date + skip + plage_regex); + +const std::regex time_period_comparator::n_th_day_of_month_extractor( + "^day\\s+(\\-*\\d+)\\s+" + plage_regex); + +const std::regex time_period_comparator::n_th_day_of_month_range_extractor( + "^day\\s+(\\-*\\d+)\\s+\\-\\s+(\\-*\\d+)" + skip + plage_regex); + +const std::regex time_period_comparator::n_th_day_of_week_extractor( + "^" + days + "\\s+(\\-*\\d+)\\s+" + plage_regex); + +const std::regex time_period_comparator::n_th_day_of_week_range_extractor( + "^" + days + "\\s+(\\-*\\d+)\\s+\\-\\s+" + days + "\\s+(\\-*\\d+)" + skip + + plage_regex); + +const std::regex time_period_comparator::n_th_day_of_week_of_month_extractor( + "^" + days + "\\s+(\\-*\\d+)\\s+" + months + "\\s+" + plage_regex); + +const std::regex + time_period_comparator::n_th_day_of_week_of_month_range_extractor( + "^" + days + "\\s+(\\-*\\d+)\\s+" + months + "\\s+\\-\\s+" + days + + "\\s+(\\-*\\d+)\\s+" + months + skip + plage_regex); + +const std::regex time_period_comparator::exclude_extractor( + "^exclude\\s+([\\w\\-]+)(,[\\w\\-]+)*"); + +const std::map time_period_comparator::day_to_index = { + {"sunday", 0}, {"monday", 1}, {"tuesday", 2}, {"wednesday", 3}, + {"thursday", 4}, {"friday", 5}, {"saturday", 6}}; + +const std::map time_period_comparator::month_to_index = { + {"january", 0}, {"february", 1}, {"march", 2}, {"april", 3}, + {"may", 4}, {"june", 5}, {"july", 6}, {"august", 7}, + {"september", 8}, {"october", 9}, {"november", 10}, {"december", 11}}; + +void time_period_comparator::extract_skip(const std::smatch matchs, + unsigned match_index, + Daterange& date_range) { + std::smatch skip_extract; + std::string skip_data = matchs[match_index].str(); + if (std::regex_search(skip_data, skip_extract, skip_extractor)) { + date_range.set_skip_interval(atoi(skip_extract[1].str().c_str())); + } +} + +time_period_comparator::time_period_comparator( + const configuration::Timeperiod& conf_tp, + const std::vector& timeperiod_content) + : _conf_tp(conf_tp) { + com::centreon::engine::configuration::applier::timeperiod applier; + + com::centreon::engine::timeperiod::timeperiods.clear(); + + for (const std::string& line : timeperiod_content) { + if (line[0] == '#') + continue; + + { // name + std::smatch line_extract; + if (std::regex_search(line, line_extract, name_extractor)) { + name = line_extract[1]; + std::cout << " test " << name << std::endl; + continue; + } + } + { // alias + std::smatch line_extract; + if (std::regex_search(line, line_extract, alias_extractor)) { + alias = line_extract[1]; + continue; + } + } + { // day of week "monday 08:00-12:00" + std::smatch line_extract; + if (std::regex_search(line, line_extract, day_extractor)) { + unsigned day_index = day_to_index.find(line_extract[1].str())->second; + std::list time_intervals = + extract_timerange(line, 2, line_extract); + _timeranges[day_index] = time_intervals; + continue; + } + } + { // exception "january 1 08:00-12:00" + std::smatch line_extract; + if (std::regex_search(line, line_extract, date_extractor)) { + std::list time_intervals = + extract_timerange(line, 3, line_extract); + int day_of_month = atoi(line_extract[2].str().c_str()); + unsigned month_index = + month_to_index.find(line_extract[1].str())->second; + Daterange toadd; + toadd.set_type(Daterange_TypeRange_month_date); + toadd.set_smon(month_index); + toadd.set_smday(day_of_month); + toadd.set_emon(month_index); + toadd.set_emday(day_of_month); + for (auto& tr : time_intervals) { + auto* new_tr = toadd.add_timerange(); + new_tr->CopyFrom(tr); + } + + _exceptions[daterange::month_date].push_front(toadd); + continue; + } + } + { // exception july 10 - 15 / 2 00:00-24:00 + std::smatch line_extract; + if (std::regex_search(line, line_extract, date_range1_extractor)) { + std::list time_intervals = + extract_timerange(line, 5, line_extract); + int day_of_month_start = atoi(line_extract[2].str().c_str()); + int day_of_month_end = atoi(line_extract[3].str().c_str()); + unsigned month_index = + month_to_index.find(line_extract[1].str())->second; + Daterange toadd; + toadd.set_type(Daterange_TypeRange_month_date); + extract_skip(line_extract, 4, toadd); + toadd.set_smon(month_index); + toadd.set_smday(day_of_month_start); + toadd.set_emon(month_index); + toadd.set_emday(day_of_month_end); + for (auto& tr : time_intervals) { + auto* new_tr = toadd.add_timerange(); + new_tr->CopyFrom(tr); + } + + _exceptions[daterange::month_date].push_front(toadd); + continue; + } + } + { // exception april 10 - may 15 /2 00:00-24:00 + std::smatch line_extract; + if (std::regex_search(line, line_extract, date_range2_extractor)) { + std::list time_intervals = + extract_timerange(line, 6, line_extract); + int day_of_month_start = atoi(line_extract[2].str().c_str()); + unsigned month_index_start = + month_to_index.find(line_extract[1].str())->second; + int day_of_month_end = atoi(line_extract[4].str().c_str()); + unsigned month_index_end = + month_to_index.find(line_extract[3].str())->second; + Daterange toadd; + toadd.set_type(Daterange_TypeRange_month_date); + extract_skip(line_extract, 5, toadd); + toadd.set_smon(month_index_start); + toadd.set_smday(day_of_month_start); + toadd.set_emon(month_index_end); + toadd.set_emday(day_of_month_end); + for (auto& tr : time_intervals) { + auto* new_tr = toadd.add_timerange(); + new_tr->CopyFrom(tr); + } + + _exceptions[daterange::month_date].push_front(toadd); + continue; + } + } + { // exception "2022-04-05 /5 08:00-12:00" + std::smatch line_extract; + if (std::regex_search(line, line_extract, full_date_extractor)) { + unsigned year = atoi(line_extract[1].str().c_str()); + unsigned month = atoi(line_extract[2].str().c_str()) - 1; + unsigned day_of_month = atoi(line_extract[3].str().c_str()); + Daterange toadd; + toadd.set_type(Daterange_TypeRange_calendar_date); + extract_skip(line_extract, 4, toadd); + std::list time_intervals = + extract_timerange(line, 5, line_extract); + toadd.set_syear(year); + toadd.set_eyear(year); + toadd.set_smon(month); + toadd.set_emon(month); + toadd.set_smday(day_of_month); + toadd.set_emday(day_of_month); + for (auto& tr : time_intervals) { + auto* new_tr = toadd.add_timerange(); + new_tr->CopyFrom(tr); + } + + _exceptions[daterange::calendar_date].push_front(toadd); + continue; + } + } + { // exception "2007-01-01 - 2008-02-01 /3 00:00-24:00" + std::smatch line_extract; + if (std::regex_search(line, line_extract, full_date_range_extractor)) { + std::list time_intervals = + extract_timerange(line, 8, line_extract); + unsigned year_start = atoi(line_extract[1].str().c_str()); + unsigned month_start = atoi(line_extract[2].str().c_str()) - 1; + unsigned day_of_month_start = atoi(line_extract[3].str().c_str()); + unsigned year_end = atoi(line_extract[4].str().c_str()); + unsigned month_end = atoi(line_extract[5].str().c_str()) - 1; + unsigned day_of_month_end = atoi(line_extract[6].str().c_str()); + Daterange toadd; + toadd.set_type(Daterange_TypeRange_calendar_date); + extract_skip(line_extract, 7, toadd); + toadd.set_syear(year_start); + toadd.set_eyear(year_end); + toadd.set_smon(month_start); + toadd.set_emon(month_end); + toadd.set_smday(day_of_month_start); + toadd.set_emday(day_of_month_end); + for (auto& tr : time_intervals) { + auto* new_tr = toadd.add_timerange(); + new_tr->CopyFrom(tr); + } + + _exceptions[daterange::calendar_date].push_front(toadd); + continue; + } + } + { // exception day -1 + std::smatch line_extract; + if (std::regex_search(line, line_extract, n_th_day_of_month_extractor)) { + std::list time_intervals = + extract_timerange(line, 2, line_extract); + unsigned day_of_month = atoi(line_extract[1].str().c_str()); + Daterange toadd; + toadd.set_type(Daterange_TypeRange_month_day); + toadd.set_smday(day_of_month); + toadd.set_emday(day_of_month); + for (auto& tr : time_intervals) { + auto* new_tr = toadd.add_timerange(); + new_tr->CopyFrom(tr); + } + + _exceptions[daterange::month_day].push_front(toadd); + continue; + } + } + { // exception day -1 + std::smatch line_extract; + if (std::regex_search(line, line_extract, + n_th_day_of_month_range_extractor)) { + std::list time_intervals = + extract_timerange(line, 4, line_extract); + unsigned day_of_month_start = atoi(line_extract[1].str().c_str()); + unsigned day_of_month_end = atoi(line_extract[2].str().c_str()); + Daterange toadd; + toadd.set_type(Daterange_TypeRange_month_day); + extract_skip(line_extract, 3, toadd); + toadd.set_smday(day_of_month_start); + toadd.set_emday(day_of_month_end); + for (auto& tr : time_intervals) { + auto* new_tr = toadd.add_timerange(); + new_tr->CopyFrom(tr); + } + + _exceptions[daterange::month_day].push_front(toadd); + continue; + } + } + { // exception monday 3 00:00-24:00 + std::smatch line_extract; + if (std::regex_search(line, line_extract, n_th_day_of_week_extractor)) { + std::list time_intervals = + extract_timerange(line, 3, line_extract); + Daterange toadd; + toadd.set_type(Daterange_TypeRange_week_day); + unsigned week_day_index = + day_to_index.find(line_extract[1].str())->second; + int day_month_index = atoi(line_extract[2].str().c_str()); + toadd.set_swday(week_day_index); + toadd.set_ewday(week_day_index); + for (auto& tr : time_intervals) { + auto* new_tr = toadd.add_timerange(); + new_tr->CopyFrom(tr); + } + toadd.set_swday_offset(day_month_index); + toadd.set_ewday_offset(day_month_index); + + _exceptions[daterange::week_day].push_front(toadd); + continue; + } + } + { // exception monday 3 - thursday 4 / 2 00:00-24:00 + std::smatch line_extract; + if (std::regex_search(line, line_extract, + n_th_day_of_week_range_extractor)) { + std::list time_intervals = + extract_timerange(line, 6, line_extract); + Daterange toadd; + toadd.set_type(Daterange_TypeRange_week_day); + extract_skip(line_extract, 5, toadd); + unsigned week_day_index_start = + day_to_index.find(line_extract[1].str())->second; + int day_month_index_start = atoi(line_extract[2].str().c_str()); + unsigned week_day_index_end = + day_to_index.find(line_extract[3].str())->second; + int day_month_index_end = atoi(line_extract[4].str().c_str()); + toadd.set_swday(week_day_index_start); + toadd.set_ewday(week_day_index_end); + for (auto& tr : time_intervals) { + auto* new_tr = toadd.add_timerange(); + new_tr->CopyFrom(tr); + } + toadd.set_swday_offset(day_month_index_start); + toadd.set_ewday_offset(day_month_index_end); + + _exceptions[daterange::week_day].push_front(toadd); + continue; + } + } + { // exception thursday -1 november 00:00-24:00 + std::smatch line_extract; + if (std::regex_search(line, line_extract, + n_th_day_of_week_of_month_extractor)) { + std::list time_intervals = + extract_timerange(line, 4, line_extract); + Daterange toadd; + toadd.set_type(Daterange_TypeRange_month_week_day); + unsigned month_index = + month_to_index.find(line_extract[3].str())->second; + unsigned week_day_index = + day_to_index.find(line_extract[1].str())->second; + int day_month_index = atoi(line_extract[2].str().c_str()); + toadd.set_smon(month_index); + toadd.set_emon(month_index); + toadd.set_swday(week_day_index); + toadd.set_ewday(week_day_index); + toadd.set_swday_offset(day_month_index); + toadd.set_ewday_offset(day_month_index); + for (auto& tr : time_intervals) { + auto* new_tr = toadd.add_timerange(); + new_tr->CopyFrom(tr); + } + + _exceptions[daterange::month_week_day].push_front(toadd); + continue; + } + } + { // exception tuesday 1 april - friday 2 may / 6 00:00-24:00 + std::smatch line_extract; + if (std::regex_search(line, line_extract, + n_th_day_of_week_of_month_range_extractor)) { + std::list time_intervals = + extract_timerange(line, 8, line_extract); + Daterange toadd; + toadd.set_type(Daterange_TypeRange_month_week_day); + unsigned month_index_start = + month_to_index.find(line_extract[3].str())->second; + unsigned week_day_index_start = + day_to_index.find(line_extract[1].str())->second; + int day_month_index_start = atoi(line_extract[2].str().c_str()); + unsigned month_index_end = + month_to_index.find(line_extract[6].str())->second; + unsigned week_day_index_end = + day_to_index.find(line_extract[4].str())->second; + int day_month_index_end = atoi(line_extract[5].str().c_str()); + extract_skip(line_extract, 7, toadd); + toadd.set_smon(month_index_start); + toadd.set_emon(month_index_end); + toadd.set_swday(week_day_index_start); + toadd.set_ewday(week_day_index_end); + toadd.set_swday_offset(day_month_index_start); + toadd.set_ewday_offset(day_month_index_end); + for (auto& tr : time_intervals) { + auto* new_tr = toadd.add_timerange(); + new_tr->CopyFrom(tr); + } + + _exceptions[daterange::month_week_day].push_front(toadd); + continue; + } + } + { + std::smatch line_extract; + if (std::regex_search(line, line_extract, exclude_extractor)) { + for (std::string field : line_extract) { + if (field == line_extract[0]) { + continue; + } + if (field.empty()) { + continue; + } + if (field[0] == ',') { + _exclude.insert(field.substr(1)); + } else { + _exclude.insert(field); + } + } + continue; + } + } + std::cerr << "no match " << line << std::endl; + } + + applier.add_object(conf_tp); + _result = + com::centreon::engine::timeperiod::timeperiods[conf_tp.timeperiod_name()]; +} + +std::ostream& operator<<(std::ostream& s, + const std::set& to_dump) { + for (const std::string& elem : to_dump) { + s << ' ' << elem; + } + return s; +} + +static constexpr std::array day_label{ + "sunday", "monday", "tuesday", "wednesday", + "thursday", "friday", "saturday"}; + +static std::ostream& operator<<(std::ostream& s, + const std::list& tr) { + s << '('; + for (auto& t : tr) { + s << t.DebugString() << ", "; + } + s << ')'; + return s; +} + +static std::ostream& operator<<(std::ostream& s, + const std::list& dr) { + s << '('; + for (auto& d : dr) { + s << d.DebugString() << ", "; + } + s << ')'; + return s; +} + +static std::ostream& operator<<( + std::ostream& s, + const std::array, 7>& timeranges) { + s << '['; + for (unsigned day_ind = 0; day_ind < 7; ++day_ind) + s << '{' << day_label[day_ind] << ", " << timeranges[day_ind] << "},"; + s << ']'; + return s; +} + +static std::ostream& operator<<( + std::ostream& s, + const std::array, + daterange::daterange_types>& dateranges) { + s << '['; + for (unsigned day_ind = 0; day_ind < daterange::daterange_types; ++day_ind) + s << '{' << day_label[day_ind] << ", " << dateranges[day_ind] << "},"; + s << ']'; + return s; +} + +static bool operator==( + const std::set& excl1, + const std::unordered_multimap& excl2) { + if (excl1.size() != excl2.size()) { + std::cerr << "Exclude arrays have not the same size." << std::endl; + return false; + } + return true; +} + +static bool operator==( + const std::array, 7>& timerange1, + const std::array, 7>& timerange2) { + auto check_timeranges = [](const std::string_view day, auto& day1, + auto& day2) -> bool { + if (day1.size() != day2.size()) { + std::cerr << day << " timeranges have not the same size: first size: " + << day1.size() << " ; second size: " << day2.size() + << std::endl; + return false; + } + for (auto& tr2 : day2) { + bool found = false; + for (auto& tr1 : day1) { + if (tr1.range_start() == tr2.get_range_start() && + tr1.range_end() == tr2.get_range_end()) { + found = true; + break; + } + } + if (!found) { + std::cerr << day << " timeranges are not the same." << std::endl; + return false; + } + } + return true; + }; + for (int32_t i = 0; i < 7; i++) { + if (!check_timeranges(day_label[i], timerange1[i], timerange2[i])) + return false; + } + return true; +} + +static bool operator==( + const std::array, 7>& timerange1, + const DaysArray& timerange2) { + auto check_timeranges = [](const std::string_view day, auto& day1, + auto& day2) -> bool { + if (static_cast(day1.size()) != day2.size()) { + std::cerr << "sunday timeranges have not the same size." << std::endl; + return false; + } + for (auto& tr2 : day2) { + bool found = false; + for (auto& tr1 : day1) { + if (tr1.range_start() == tr2.range_start() && + tr1.range_end() == tr2.range_end()) { + found = true; + break; + } + } + if (!found) { + std::cerr << day << " timeranges are not the same." << std::endl; + return false; + } + } + return true; + }; + if (!check_timeranges("sunday", timerange1[0], timerange2.sunday()) || + !check_timeranges("monday", timerange1[1], timerange2.monday()) || + !check_timeranges("tuesday", timerange1[2], timerange2.tuesday()) || + !check_timeranges("wednesday", timerange1[3], timerange2.wednesday()) || + !check_timeranges("thursday", timerange1[4], timerange2.thursday()) || + !check_timeranges("friday", timerange1[5], timerange2.friday()) || + !check_timeranges("saturday", timerange1[6], timerange2.saturday())) + return false; + return true; +} + +static bool operator==(const std::set& exclude1, + const configuration::StringSet& exclude2) { + if (static_cast(exclude1.size()) != exclude2.data().size()) { + std::cerr << "exclude arrays have not the same size " << exclude1.size() + << " <> " << exclude2.data().size() << std::endl; + return false; + } + for (auto& s : exclude1) { + bool found = false; + for (auto& ss : exclude2.data()) + if (ss == s) { + found = true; + break; + } + if (!found) { + std::cerr << "exclude sets do not contain the same strings." << std::endl; + return false; + } + } + return true; +} + +static bool operator!=(const std::set& exclude1, + const configuration::StringSet& exclude2) { + return !(exclude1 == exclude2); +} + +static bool operator==(const Daterange& dr1, const engine::daterange& dr2) { + if (static_cast(dr1.type()) != + static_cast(dr2.get_type())) { + std::cerr << "Dateranges not of the same type." << std::endl; + return false; + } + bool retval = + dr1.syear() == dr2.get_syear() && dr1.smon() == dr2.get_smon() && + dr1.smday() == dr2.get_smday() && dr1.swday() == dr2.get_swday() && + dr1.swday_offset() == dr2.get_swday_offset() && + dr1.eyear() == dr2.get_eyear() && dr1.emon() == dr2.get_emon() && + dr1.emday() == dr2.get_emday() && dr1.ewday() == dr2.get_ewday() && + dr1.ewday_offset() == dr2.get_ewday_offset(); + + return retval; +} + +static bool operator==( + const std::array, + configuration::daterange::daterange_types>& exc1, + const std::array, daterange::daterange_types>& + exc2) { + auto compare_dateranges = + [](int32_t idx, const std::list& lst1, + const std::list& lst2) -> bool { + for (auto& dr1 : lst1) { + bool found = false; + for (auto& dr2 : lst2) { + if (dr1 == dr2) { + found = true; + break; + } + } + if (!found) { + std::cerr << "Dateranges at index " << idx + << " are not equals in exception arrays" << std::endl; + return false; + } + } + return true; + }; + for (uint32_t idx = 0; idx < exc1.size(); idx++) { + if (!compare_dateranges(idx, exc1[idx], exc2[idx])) + return false; + } + return true; +} + +static bool operator==( + const std::array, + configuration::daterange::daterange_types>& exc1, + const configuration::ExceptionArray& exc2) { + auto it_exc1 = exc1.begin(); + auto compare_dateranges = + [](const std::string_view& name, + const std::list& lst, + const google::protobuf::RepeatedPtrField& + rep) -> bool { + for (auto& dr1 : lst) { + bool found = false; + for (auto& dr2 : rep) { + found = MessageDifferencer::Equals(dr1, dr2); + if (found) + break; + } + if (!found) { + std::cerr << "Dateranges '" << name + << "' are not equals in exception arrays" << std::endl; + return false; + } + } + return true; + }; + if (!compare_dateranges("calendar_date", *it_exc1, exc2.calendar_date())) + return false; + ++it_exc1; + if (!compare_dateranges("month_date", *it_exc1, exc2.month_date())) + return false; + ++it_exc1; + if (!compare_dateranges("month_day", *it_exc1, exc2.month_day())) + return false; + ++it_exc1; + if (!compare_dateranges("month_week_day", *it_exc1, exc2.month_week_day())) + return false; + ++it_exc1; + if (!compare_dateranges("week_day", *it_exc1, exc2.week_day())) + return false; + return true; +} + +static std::ostream& operator<<(std::ostream& s, + const configuration::StringSet& exclude) { + s << exclude.DebugString(); + return s; +} + +bool time_period_comparator::is_equal() const { + if (name != _conf_tp.timeperiod_name()) { + std::cerr << "different name: " << name << " <> " + << _conf_tp.timeperiod_name() << std::endl; + return false; + } + if (alias != _conf_tp.alias()) { + std::cerr << "different alias: " << alias << " <> " << _conf_tp.alias() + << std::endl; + return false; + } + + if (!(_timeranges == _conf_tp.timeranges())) { + std::cerr << "timeranges difference" << std::endl; + std::cerr << "_timeranges=" << _timeranges << std::endl; + std::cerr << "_conf_tp.timeranges= " << _conf_tp.timeranges().DebugString() + << std::endl; + return false; + } + + if (!(_exceptions == _conf_tp.exceptions())) { + std::cerr << "exception difference" << std::endl; + std::cerr << "_exceptions= " << _exceptions << std::endl; + std::cerr << "_conf_tp.exceptions= " << _conf_tp.exceptions().DebugString() + << std::endl; + return false; + } + + if (_exclude != _conf_tp.exclude()) { + std::cerr << "exception exclude" << std::endl; + std::cerr << "_exclude=" << _exclude << std::endl; + std::cerr << "_conf_tp.exclude=" << _conf_tp.exclude() << std::endl; + return false; + } + + return true; +} + +bool time_period_comparator::is_result_equal() const { + if (name != _result->get_name()) { + std::cerr << "different name: " << name << " <> " << _result->get_name() + << std::endl; + return false; + } + if (alias != _result->get_alias()) { + std::cerr << "different alias: " << alias << " <> " << _result->get_alias() + << std::endl; + return false; + } + + if (!(_timeranges == _result->days)) { + std::cerr << "timeranges difference" << std::endl; + // std::cerr << "_timeranges= " << _timeranges << std::endl; + std::cerr << "_conf_tp.timeranges= " << _conf_tp.timeranges().DebugString() + << std::endl; + return false; + } + + if (!(_exceptions == _result->exceptions)) { + std::cerr << "exception difference" << std::endl; + // std::cerr << "_exceptions= " << _exceptions << std::endl; + std::cerr << "_conf_tp.exceptions= " << _conf_tp.exceptions().DebugString() + << std::endl; + return false; + } + + if (!(_exclude == _result->get_exclusions())) { + std::cerr << "exception exclude" << std::endl; + std::cerr << "_exclude=" << _exclude << std::endl; + std::cerr << "_conf_tp.exclude=" << _conf_tp.exclude() << std::endl; + return false; + } + + return true; +} + +std::list time_period_comparator::extract_timerange( + const std::string& line_content, + uint32_t offset, + const std::smatch& datas) { + std::list ret; + for (; offset < datas.size(); ++offset) { + std::smatch range; + std::string ranges = datas[offset].str(); + if (ranges.empty()) { + continue; + } + if (std::regex_search(ranges, range, range_extractor)) { + configuration::Timerange t; + t.set_range_start(atoi(range[1].str().c_str()) * 3600 + + atoi(range[2].str().c_str()) * 60); + t.set_range_end(atoi(range[3].str().c_str()) * 3600 + + atoi(range[4].str().c_str()) * 60); + ret.push_back(std::move(t)); + } else { + std::cerr << "fail to parse timerange: " << line_content << std::endl; + } + } + return ret; +} + +} // namespace com::centreon::engine::configuration + +std::vector> parse_timeperiods_cfg( + const std::string& file_path) { + std::vector> ret; + + std::ifstream f(file_path); + std::string line; + + bool wait_time_period_begin = true; + + std::vector current; + while (!f.eof()) { + std::getline(f, line); + + if (line.empty() || line[0] == '#') { + continue; + } + + if (wait_time_period_begin) { + wait_time_period_begin = + line.find("define timeperiod {") == std::string::npos; + } else { + if (line[0] == '}') { + wait_time_period_begin = true; + ret.push_back(current); + current.clear(); + continue; + } + + absl::StripAsciiWhitespace(&line); + current.push_back(std::move(line)); + } + } + + return ret; +} + +std::vector> file_content = + parse_timeperiods_cfg("tests/timeperiods.cfg"); + +class timeperiod_config_parser_test + : public ::testing::TestWithParam> { + protected: + public: + static void SetUpTestSuite() { pb_config.Clear(); } + static void TearDownTestSuite(){}; + + protected: + void SetUp() override {} + + void TearDown() override {} +}; + +INSTANTIATE_TEST_SUITE_P(timeperiod_config_parser_test, + timeperiod_config_parser_test, + ::testing::ValuesIn(file_content)); + +TEST_P(timeperiod_config_parser_test, VerifyParserContent) { + const std::vector period_content = GetParam(); + + configuration::Timeperiod conf_tp; + configuration::timeperiod_helper conf_tp_hlp(&conf_tp); + + for (const std::string& to_parse : period_content) { + std::string_view line_view = absl::StripAsciiWhitespace(to_parse); + if (line_view[0] == '#') + continue; + std::vector v = + absl::StrSplit(line_view, absl::MaxSplits(absl::ByAnyChar(" \t"), 1), + absl::SkipWhitespace()); + if (v.size() != 2) + abort(); + + std::string_view key = absl::StripAsciiWhitespace(v[0]); + std::string_view value = absl::StripAsciiWhitespace(v[1]); + bool retval = false; + /* particular cases with hook */ + retval = conf_tp_hlp.hook(key, value); + if (!retval) + retval = conf_tp_hlp.set(key, value); + if (!retval) { + std::cout << "Unable to parse <<" << to_parse << ">>" << std::endl; + abort(); + } + } + + time_period_comparator comparator(conf_tp, period_content); + + ASSERT_TRUE(comparator.is_equal()); + ASSERT_TRUE(comparator.is_result_equal()); +} diff --git a/engine/tests/configuration/tag.cc b/engine/tests/configuration/tag.cc index 1ac709690e9..0bd66438c8e 100644 --- a/engine/tests/configuration/tag.cc +++ b/engine/tests/configuration/tag.cc @@ -19,7 +19,6 @@ #include "common/engine_legacy_conf/tag.hh" #include - #include "common/engine_legacy_conf/object.hh" #include "helper.hh" diff --git a/engine/tests/custom_vars/pbextcmd.cc b/engine/tests/custom_vars/pbextcmd.cc new file mode 100644 index 00000000000..dafcf08e824 --- /dev/null +++ b/engine/tests/custom_vars/pbextcmd.cc @@ -0,0 +1,111 @@ +/** + * Copyright 2005 - 2024 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include +#include "../timeperiod/utils.hh" +#include "com/centreon/engine/commands/command.hh" +#include "com/centreon/engine/commands/commands.hh" +#include "com/centreon/engine/configuration/applier/command.hh" +#include "com/centreon/engine/configuration/applier/contact.hh" +#include "com/centreon/engine/configuration/applier/host.hh" +#include "com/centreon/engine/macros.hh" +#include "com/centreon/engine/macros/grab_host.hh" +#include "common/engine_conf/command_helper.hh" +#include "common/engine_conf/contact_helper.hh" +#include "common/engine_conf/host_helper.hh" +#include "common/engine_conf/message_helper.hh" +#include "helper.hh" + +using namespace com::centreon; +using namespace com::centreon::engine; +using namespace com::centreon::engine::configuration; + +class PbCustomVar : public ::testing::Test { + public: + void SetUp() override { init_config_state(); } + + void TearDown() override { deinit_config_state(); } +}; + +// Given simple command (without connector) applier already applied with +// all objects created. +// When the command is removed from the configuration, +// Then the command is totally removed. +TEST_F(PbCustomVar, UpdateHostCustomVar) { + configuration::applier::command cmd_aply; + configuration::applier::host hst_aply; + configuration::applier::contact cnt_aply; + + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("base_centreon_ping"); + cmd.set_command_line( + "$USER1$/check_icmp -H $HOSTADDRESS$ -n $_HOSTPACKETNUMBER$ -w " + "$_HOSTWARNING$ -c $_HOSTCRITICAL$ $CONTACTNAME$"); + cmd_aply.add_object(cmd); + + configuration::Contact cnt; + configuration::contact_helper cnt_hlp(&cnt); + cnt.set_contact_name("user"); + cnt.set_email("contact@centreon.com"); + cnt.set_pager("0473729383"); + cnt.set_host_notification_period("24x7"); + cnt.set_service_notification_period("24x7"); + cnt_aply.add_object(cnt); + + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("hst_test"); + hst.set_address("127.0.0.1"); + hst.set_host_id(1); + hst_hlp.insert_customvariable("_PACKETNUMBER", "42"); + hst_hlp.insert_customvariable("_WARNING", "200,20%"); + hst_hlp.insert_customvariable("_CRITICAL", "400,50%"); + hst.set_check_command("base_centreon_ping"); + hst.mutable_contacts()->add_data("user"); + hst_aply.add_object(hst); + + command_map::iterator cmd_found{ + commands::command::commands.find("base_centreon_ping")}; + ASSERT_NE(cmd_found, commands::command::commands.end()); + ASSERT_TRUE(pb_config.commands().size() == 1); + + host_map::iterator hst_found{engine::host::hosts.find("hst_test")}; + ASSERT_NE(hst_found, engine::host::hosts.end()); + ASSERT_TRUE(pb_config.hosts().size() == 1); + + hst_aply.expand_objects(pb_config); + configuration::error_cnt err; + hst_aply.resolve_object(hst, err); + ASSERT_EQ(hst_found->second->custom_variables.size(), 3); + nagios_macros* macros(get_global_macros()); + grab_host_macros_r(macros, hst_found->second.get()); + std::string processed_cmd( + hst_found->second->get_check_command_ptr()->process_cmd(macros)); + ASSERT_EQ(processed_cmd, + "/check_icmp -H 127.0.0.1 -n 42 -w 200,20% -c 400,50% user"); + + char* msg = strdupa("hst_test;PACKETNUMBER;44"); + cmd_change_object_custom_var(CMD_CHANGE_CUSTOM_HOST_VAR, msg); + grab_host_macros_r(macros, hst_found->second.get()); + std::string processed_cmd2( + hst_found->second->get_check_command_ptr()->process_cmd(macros)); + ASSERT_EQ(processed_cmd2, + "/check_icmp -H 127.0.0.1 -n 44 -w 200,20% -c 400,50% user"); +} diff --git a/engine/tests/downtimes/pbdowntime.cc b/engine/tests/downtimes/pbdowntime.cc new file mode 100644 index 00000000000..80bee03b9fa --- /dev/null +++ b/engine/tests/downtimes/pbdowntime.cc @@ -0,0 +1,111 @@ +/** + * Copyright 2019 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include + +#include "../timeperiod/utils.hh" +#include "com/centreon/engine/commands/commands.hh" +#include "com/centreon/engine/configuration/applier/host.hh" +#include "com/centreon/engine/downtimes/downtime_manager.hh" +#include "helper.hh" + +using namespace com::centreon; +using namespace com::centreon::engine; +using namespace com::centreon::engine::downtimes; + +class DowntimeExternalCommand : public ::testing::Test { + public: + void SetUp() override { init_config_state(); } + + void TearDown() override { + downtime_manager::instance().clear_scheduled_downtimes(); + deinit_config_state(); + } +}; + +TEST_F(DowntimeExternalCommand, AddUnkownHostDowntime) { + set_time(20000); + + time_t now = time(nullptr); + + std::stringstream s; + s << "SCHEDULE_HOST_DOWNTIME;test_srv;" << now << ";" << now + << ";1;0;7200;admin;host"; + + ASSERT_EQ(cmd_schedule_downtime(CMD_SCHEDULE_HOST_DOWNTIME, now, + const_cast(s.str().c_str())), + ERROR); + + ASSERT_EQ(0u, downtime_manager::instance().get_scheduled_downtimes().size()); +} + +TEST_F(DowntimeExternalCommand, AddHostDowntime) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_srv"); + hst.set_address("127.0.0.1"); + hst.set_host_id(1); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + + set_time(20000); + + time_t now = time(nullptr); + + std::string query{ + fmt::format("test_srv;{};{};1;0;1;admin;host", now, now + 1)}; + + ASSERT_EQ(0u, downtime_manager::instance().get_scheduled_downtimes().size()); + + ASSERT_EQ(cmd_schedule_downtime(CMD_SCHEDULE_HOST_DOWNTIME, now, + const_cast(query.c_str())), + OK); + + ASSERT_EQ(1u, downtime_manager::instance().get_scheduled_downtimes().size()); + ASSERT_EQ( + downtime_manager::instance().get_scheduled_downtimes().begin()->first, + 20000); + ASSERT_EQ(downtime_manager::instance() + .get_scheduled_downtimes() + .begin() + ->second->host_id(), + 1); + ASSERT_EQ(downtime_manager::instance() + .get_scheduled_downtimes() + .begin() + ->second->get_duration(), + 1); + ASSERT_EQ(downtime_manager::instance() + .get_scheduled_downtimes() + .begin() + ->second->get_end_time(), + 20001); + ASSERT_EQ(downtime_manager::instance() + .get_scheduled_downtimes() + .begin() + ->second->handle(), + OK); + set_time(20001); + ASSERT_EQ(downtime_manager::instance() + .get_scheduled_downtimes() + .begin() + ->second->handle(), + OK); + ASSERT_EQ(0u, downtime_manager::instance().get_scheduled_downtimes().size()); +} diff --git a/engine/tests/downtimes/pbdowntime_finder.cc b/engine/tests/downtimes/pbdowntime_finder.cc new file mode 100644 index 00000000000..3161d997009 --- /dev/null +++ b/engine/tests/downtimes/pbdowntime_finder.cc @@ -0,0 +1,371 @@ +/** + * Copyright 2019-2022 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include +#include "com/centreon/engine/downtimes/downtime_finder.hh" + +#include "com/centreon/clib.hh" +#include "com/centreon/engine/configuration/applier/contact.hh" +#include "com/centreon/engine/configuration/applier/host.hh" +#include "com/centreon/engine/configuration/applier/service.hh" +#include "com/centreon/engine/downtimes/downtime.hh" +#include "com/centreon/engine/downtimes/downtime_manager.hh" +#include "com/centreon/engine/downtimes/service_downtime.hh" +#include "helper.hh" +#include "test_engine.hh" + +using namespace com::centreon; +using namespace com::centreon::engine; +using namespace com::centreon::engine::downtimes; + +class DowntimeFinderFindMatchingAllTest : public TestEngine { + public: + void SetUp() override { + configuration::error_cnt err; + init_config_state(); + configuration::Contact ctc{ + new_pb_configuration_contact("admin", false, "a")}; + configuration::applier::contact ctc_aply; + ctc_aply.add_object(ctc); + + configuration::Host hst{new_pb_configuration_host("test_host", "admin", 1)}; + configuration::applier::host hst_aply; + hst_aply.add_object(hst); + + configuration::Host hst1{ + new_pb_configuration_host("first_host", "admin", 12)}; + hst_aply.add_object(hst1); + + configuration::Host hst2{ + new_pb_configuration_host("other_host", "admin", 2)}; + hst_aply.add_object(hst2); + + hst_aply.resolve_object(hst, err); + hst_aply.resolve_object(hst1, err); + + configuration::Service svc{ + new_pb_configuration_service("first_host", "test_service", "admin", 8)}; + configuration::applier::service svc_aply; + svc_aply.add_object(svc); + + configuration::Service svc1{ + new_pb_configuration_service("first_host", "other_svc", "admin", 9)}; + svc_aply.add_object(svc1); + + configuration::Service svc2{ + new_pb_configuration_service("test_host", "new_svc", "admin", 10)}; + svc_aply.add_object(svc2); + + configuration::Service svc3{ + new_pb_configuration_service("test_host", "new_svc1", "admin", 11)}; + svc_aply.add_object(svc3); + + configuration::Service svc4{ + new_pb_configuration_service("test_host", "new_svc2", "admin", 12)}; + svc_aply.add_object(svc4); + + svc_aply.resolve_object(svc, err); + svc_aply.resolve_object(svc1, err); + svc_aply.resolve_object(svc2, err); + svc_aply.resolve_object(svc3, err); + svc_aply.resolve_object(svc4, err); + + downtime_manager::instance().clear_scheduled_downtimes(); + downtime_manager::instance().initialize_downtime_data(); + new_downtime(1, 1, 10, 234567891, 734567892, 1, 0, 84, "other_author", + "test_comment"); + // OK + new_downtime(2, 12, 8, 123456789, 134567892, 1, 0, 42, "test_author", + "other_comment"); + // OK + new_downtime(3, 12, 9, 123456789, 345678921, 0, 1, 42, "", "test_comment"); + new_downtime(4, 1, 10, 123456789, 345678921, 0, 1, 84, "test_author", ""); + // OK + new_downtime(5, 1, 11, 123456789, 134567892, 1, 1, 42, "test_author", + "test_comment"); + // OK + new_downtime(6, 1, 12, 7265943625, 7297479625, 1, 2, 31626500, "out_author", + "out_comment"); + _dtf = std::make_unique( + downtime_manager::instance().get_scheduled_downtimes()); + } + + void TearDown() override { + _dtf.reset(); + downtime_manager::instance().clear_scheduled_downtimes(); + downtime_manager::instance().initialize_downtime_data(); + deinit_config_state(); + } + + void new_downtime(uint64_t downtime_id, + const uint64_t host_id, + const uint64_t service_id, + time_t start, + time_t end, + int fixed, + unsigned long triggered_by, + int32_t duration, + std::string const& author, + std::string const& comment) { + downtime_manager::instance().schedule_downtime( + downtime::service_downtime, host_id, service_id, start, author.c_str(), + comment.c_str(), start, end, fixed, triggered_by, duration, + &downtime_id); + } + + protected: + std::unique_ptr _dtf; + downtime* dtl; + downtime_finder::criteria_set criterias; + downtime_finder::result_set result; + downtime_finder::result_set expected; +}; + +// Given a downtime_finder object with a NULL downtime list +// When find_matching_all() is called +// Then an empty result_set is returned +TEST_F(DowntimeFinderFindMatchingAllTest, NullDowntimeList) { + std::multimap> map; + downtime_finder local_dtf(map); + criterias.push_back(downtime_finder::criteria("host", "test_host")); + result = local_dtf.find_matching_all(criterias); + ASSERT_TRUE(result.empty()); +} + +// Given a downtime_finder object with the test downtime list +// And a downtime of the test list has a null host_name +// When find_matching_all() is called with criteria ("host", "anyhost") +// Then an empty result_set is returned +TEST_F(DowntimeFinderFindMatchingAllTest, NullHostNotFound) { + criterias.push_back(downtime_finder::criteria("host", "anyhost")); + result = _dtf->find_matching_all(criterias); + ASSERT_TRUE(result.empty()); +} + +// Given a downtime finder object with the test downtime list +// And a downtime of the test list has a null service_description +// When find_matching_all() is called with criteria ("service", "anyservice") +// Then an empty result_set is returned +TEST_F(DowntimeFinderFindMatchingAllTest, NullServiceNotFound) { + criterias.push_back(downtime_finder::criteria("service", "anyservice")); + result = _dtf->find_matching_all(criterias); + ASSERT_TRUE(result.empty()); +} + +// Given a downtime finder object with the test downtime list +// And a downtime the test list has a null service_description +// When find_matching_all() is called with the criteria ("service", "") +// Then the result_set contains the downtime +TEST_F(DowntimeFinderFindMatchingAllTest, NullServiceFound) { + criterias.push_back(downtime_finder::criteria("service", "")); + result = _dtf->find_matching_all(criterias); + ASSERT_TRUE(result.empty()); +} + +// Given a downtime_finder object with the test downtime list +// And a downtime of the test list has a null author +// When find_matching_all() is called with the criteria ("author", +// "anyauthor") +// Then an empty result_set is returned +TEST_F(DowntimeFinderFindMatchingAllTest, NullAuthorNotFound) { + criterias.push_back(downtime_finder::criteria("author", "anyauthor")); + result = _dtf->find_matching_all(criterias); + ASSERT_TRUE(result.empty()); +} + +// Given a downtime_finder object with the test downtime list +// And a downtime of the test list has a null author +// When find_matching_all() is called with the criteria ("author", "") +// Then the result_set contains the downtime +TEST_F(DowntimeFinderFindMatchingAllTest, NullAuthorFound) { + criterias.push_back(downtime_finder::criteria("author", "")); + result = _dtf->find_matching_all(criterias); + expected.push_back(3); + ASSERT_EQ(result, expected); +} + +// Given a downtime_finder object with the test downtime list +// And a downtime of the test list has a null comment +// When find_matching_all() is called with the criteria ("comment", +// "anycomment") Then an empty result_set is returned +TEST_F(DowntimeFinderFindMatchingAllTest, NullCommentNotFound) { + criterias.push_back(downtime_finder::criteria("comment", "anycomment")); + result = _dtf->find_matching_all(criterias); + ASSERT_TRUE(result.empty()); +} + +// Given a downtime_finder object with the test downtime list +// And a downtime of the test list has a null comment +// When find_matching_all() is called with the criteria ("comment", "") +// Then the result_set contains the downtime +TEST_F(DowntimeFinderFindMatchingAllTest, NullCommentFound) { + criterias.push_back(downtime_finder::criteria("comment", "")); + result = _dtf->find_matching_all(criterias); + expected.push_back(4); + ASSERT_EQ(result, expected); +} + +// Given a downtime_finder object with the test downtime list +// When find_matching_all() is called with the criteria ("host", "test_host") +// Then all downtimes of host /test_host/ are returned +TEST_F(DowntimeFinderFindMatchingAllTest, MultipleHosts) { + criterias.push_back(downtime_finder::criteria("host", "test_host")); + result = _dtf->find_matching_all(criterias); + expected.push_back(4); + expected.push_back(5); + expected.push_back(1); + expected.push_back(6); + ASSERT_EQ(result, expected); +} + +// Given a downtime_finder object with the test downtime list +// When find_matching_all() is called with the criteria ("service", +// "test_service") Then all downtimes of service /test_service/ are returned +TEST_F(DowntimeFinderFindMatchingAllTest, MultipleServices) { + criterias.push_back(downtime_finder::criteria("service", "test_service")); + result = _dtf->find_matching_all(criterias); + expected.push_back(2); + ASSERT_EQ(result, expected); +} + +// Given a downtime_finder object with the test downtime list +// When find_matching_all() is called with the criteria ("start", "123456789") +// Then all downtimes with 123456789 as start time are returned +TEST_F(DowntimeFinderFindMatchingAllTest, MultipleStart) { + criterias.push_back(downtime_finder::criteria("start", "123456789")); + result = _dtf->find_matching_all(criterias); + expected.push_back(2); + expected.push_back(3); + expected.push_back(4); + expected.push_back(5); + ASSERT_EQ(result, expected); +} + +// Given a downtime_finder object with the test downtime list +// When find_matching_all() is called with the criteria ("end", "134567892") +// Then all downtimes with 134567892 as end time are returned +TEST_F(DowntimeFinderFindMatchingAllTest, MultipleEnd) { + criterias.push_back(downtime_finder::criteria("end", "134567892")); + result = _dtf->find_matching_all(criterias); + expected.push_back(2); + expected.push_back(5); + ASSERT_EQ(result, expected); +} + +// Given a downtime_finder object with the test downtime list +// When find_matching_all() is called with the criteria ("fixed", "0") +// Then all downtimes that are not fixed are returned +TEST_F(DowntimeFinderFindMatchingAllTest, MultipleFixed) { + criterias.push_back(downtime_finder::criteria("fixed", "0")); + result = _dtf->find_matching_all(criterias); + expected.push_back(3); + expected.push_back(4); + ASSERT_EQ(result, expected); +} + +// Given a downtime_finder object with the test downtime list +// When find_matching_all() is called with the criteria ("triggered_by", "0") +// Then all downtimes that are not triggered by other downtimes are returned +TEST_F(DowntimeFinderFindMatchingAllTest, MultipleTriggeredBy) { + criterias.push_back(downtime_finder::criteria("triggered_by", "0")); + result = _dtf->find_matching_all(criterias); + expected.push_back(2); + expected.push_back(1); + ASSERT_EQ(result, expected); +} + +// Given a downtime_finder object with the test downtime list +// When find_matching_all() is called with the criteria ("duration", "42") +// Then all downtimes with a duration of 42 seconds are returned +TEST_F(DowntimeFinderFindMatchingAllTest, MultipleDuration) { + criterias.push_back(downtime_finder::criteria("duration", "42")); + result = _dtf->find_matching_all(criterias); + expected.push_back(2); + expected.push_back(3); + expected.push_back(5); + ASSERT_EQ(result, expected); +} + +// Given a downtime_finder object with the test downtime list +// When find_matching_all() is called with the criteria ("author", +// "test_author") Then all downtimes from author /test_author/ are returned +TEST_F(DowntimeFinderFindMatchingAllTest, MultipleAuthor) { + criterias.push_back(downtime_finder::criteria("author", "test_author")); + result = _dtf->find_matching_all(criterias); + expected.push_back(2); + expected.push_back(4); + expected.push_back(5); + ASSERT_EQ(result, expected); +} + +// Given a downtime_finder object with the test downtime list +// When find_matching_all() is called with the criteria ("comment", +// "test_comment") Then all downtimes with comment "test_comment" are returned +TEST_F(DowntimeFinderFindMatchingAllTest, MultipleComment) { + criterias.push_back(downtime_finder::criteria("comment", "test_comment")); + result = _dtf->find_matching_all(criterias); + expected.push_back(3); + expected.push_back(5); + expected.push_back(1); + ASSERT_EQ(result, expected); +} + +// Given a downtime_finder object with the test downtime list +// When findMatchinAll() is called with criterias ("author", "test_author"), +// ("duration", "42") and ("comment", "test_comment") Then all downtimes +// matching the criterias are returned +TEST_F(DowntimeFinderFindMatchingAllTest, MultipleCriterias) { + criterias.push_back(downtime_finder::criteria("author", "test_author")); + criterias.push_back(downtime_finder::criteria("duration", "42")); + criterias.push_back(downtime_finder::criteria("comment", "test_comment")); + result = _dtf->find_matching_all(criterias); + expected.push_back(5); + ASSERT_EQ(result, expected); +} + +// Given a downtime_finder object with the test downtime list +// When find_matching_all() is called with the criteria ("end", "4102441200") +// Then all downtimes with 4102441200 as end time are returned +TEST_F(DowntimeFinderFindMatchingAllTest, OutOfRangeEnd) { + criterias.push_back(downtime_finder::criteria("end", "4102441200")); + result = _dtf->find_matching_all(criterias); + expected.push_back(6); + ASSERT_EQ(result, expected); +} + +// Given a downtime_finder object with the test downtime list +// When find_matching_all() is called with the criteria ("start", +// "4102441200") +// Then all downtimes with 4102441200 as end time are returned +TEST_F(DowntimeFinderFindMatchingAllTest, OutOfRangeStart) { + criterias.push_back(downtime_finder::criteria("start", "4102441200")); + result = _dtf->find_matching_all(criterias); + expected.push_back(6); + ASSERT_EQ(result, expected); +} + +// Given a downtime_finder object with the test downtime list +// When find_matching_all() is called with the criteria ("duration", +// "4102441200") Then all downtimes with 31622400 as end time are returned +TEST_F(DowntimeFinderFindMatchingAllTest, OutOfRangeDuration) { + criterias.push_back(downtime_finder::criteria("duration", "31622400")); + result = _dtf->find_matching_all(criterias); + expected.push_back(6); + ASSERT_EQ(result, expected); +} diff --git a/engine/tests/enginerpc/pbenginerpc.cc b/engine/tests/enginerpc/pbenginerpc.cc new file mode 100644 index 00000000000..de2275e057e --- /dev/null +++ b/engine/tests/enginerpc/pbenginerpc.cc @@ -0,0 +1,1813 @@ +/** + * Copyright 2019-2022 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include + +#include + +#include +#include + +#include "com/centreon/engine/host.hh" + +#include "com/centreon/engine/enginerpc.hh" + +#include "../test_engine.hh" +#include "../timeperiod/utils.hh" +#include "com/centreon/engine/anomalydetection.hh" +#include "com/centreon/engine/checks/checker.hh" +#include "com/centreon/engine/command_manager.hh" +#include "com/centreon/engine/commands/commands.hh" +#include "com/centreon/engine/comment.hh" +#include "com/centreon/engine/configuration/applier/anomalydetection.hh" +#include "com/centreon/engine/configuration/applier/command.hh" +#include "com/centreon/engine/configuration/applier/contact.hh" +#include "com/centreon/engine/configuration/applier/host.hh" +#include "com/centreon/engine/configuration/applier/hostgroup.hh" +#include "com/centreon/engine/configuration/applier/service.hh" +#include "com/centreon/engine/configuration/applier/servicegroup.hh" +#include "com/centreon/engine/downtimes/downtime_manager.hh" +#include "com/centreon/engine/events/loop.hh" +#include "com/centreon/engine/timezone_manager.hh" +#include "com/centreon/engine/version.hh" +#include "common/engine_conf/hostgroup_helper.hh" +#include "helper.hh" + +using namespace com::centreon; +using namespace com::centreon::engine; +using namespace com::centreon::engine::downtimes; +using namespace com::centreon::engine::configuration; +using namespace com::centreon::engine::configuration::applier; + +class EngineRpc : public TestEngine { + public: + void SetUp() override { + init_config_state(); + + // Do not unload this in the tear down function, it is done by the + // other unload function... :-( + + pb_config.set_execute_service_checks(true); + + configuration::error_cnt err; + /* contact */ + configuration::applier::contact ct_aply; + configuration::Contact ctct{new_pb_configuration_contact("admin", true)}; + ct_aply.add_object(ctct); + ct_aply.expand_objects(pb_config); + ct_aply.resolve_object(ctct, err); + + /* hosts */ + configuration::Host hst_child; + configuration::host_helper hst_child_hlp(&hst_child); + configuration::applier::host hst_aply2; + hst_child.set_host_name("child_host"); + hst_child.set_address("127.0.0.1"); + hst_child_hlp.hook("parents", "test_host"); + hst_child.set_host_id(42); + hst_aply2.add_object(hst_child); + + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst = new_pb_configuration_host("test_host", "admin"); + configuration::applier::host hst_aply; + hst.set_host_id(12); + hst_aply.add_object(hst); + + hst_aply.resolve_object(hst, err); + hst_aply2.resolve_object(hst_child, err); + + ASSERT_EQ(engine::host::hosts.size(), 2u); + + host_map::iterator child = engine::host::hosts.find("child_host"); + host_map::iterator parent = engine::host::hosts.find("test_host"); + + ASSERT_EQ(child->second->parent_hosts.size(), 1u); + ASSERT_EQ(parent->second->child_hosts.size(), 1u); + + /* hostgroup */ + configuration::Hostgroup hg; + configuration::hostgroup_helper hg_hlp(&hg); + configuration::applier::hostgroup hg_aply; + hg.set_hostgroup_name("test_hg"); + hg_hlp.hook("members", "test_host"); + hg_aply.add_object(hg); + hg_aply.expand_objects(pb_config); + hg_aply.resolve_object(hg, err); + + /* service */ + configuration::Service svc{ + new_pb_configuration_service("test_host", "test_svc", "admin")}; + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + cmd.set_command_line("/bin/sh -c 'echo \"test_cmd\"'"); + svc.set_check_command("cmd"); + configuration::applier::command cmd_aply; + + configuration::applier::service svc_aply; + svc_aply.add_object(svc); + cmd_aply.add_object(cmd); + + svc_aply.resolve_object(svc, err); + + configuration::Anomalydetection ad{new_pb_configuration_anomalydetection( + "test_host", "test_ad", "admin", + 12, // service_id of the anomalydetection + 13, // service_id of the dependent service + "/tmp/thresholds_status_change.json")}; + configuration::applier::anomalydetection ad_aply; + ad_aply.add_object(ad); + + ad_aply.resolve_object(ad, err); + + host_map const& hm{engine::host::hosts}; + _host = hm.begin()->second; + _host->set_current_state(engine::host::state_down); + _host->set_state_type(checkable::hard); + _host->set_acknowledgement(AckType::NONE); + _host->set_notify_on(static_cast(-1)); + + service_map const& sm{engine::service::services}; + for (auto& p : sm) { + std::shared_ptr svc = p.second; + if (svc->service_id() == 12) + _ad = std::static_pointer_cast(svc); + else + _svc = svc; + } + _svc->set_current_state(engine::service::state_critical); + _svc->set_state_type(checkable::hard); + _svc->set_acknowledgement(AckType::NONE); + _svc->set_notify_on(static_cast(-1)); + + contact_map const& cm{engine::contact::contacts}; + _contact = cm.begin()->second; + + /* servicegroup */ + configuration::Servicegroup sg; + configuration::servicegroup_helper sg_hlp(&sg); + sg.set_servicegroup_name("test_sg"); + configuration::applier::servicegroup sg_aply; + sg_hlp.hook("members", "test_host,test_svc"); + + sg_aply.add_object(sg); + sg_aply.expand_objects(pb_config); + sg_aply.resolve_object(sg, err); + } + + void TearDown() override { + _host.reset(); + _svc.reset(); + _ad.reset(); + deinit_config_state(); + } + + std::list execute(const std::string& command) { + std::list retval; + char path[1024]; + std::ostringstream oss; + oss << "tests/rpc_client_engine " << command; + + FILE* fp = popen(oss.str().c_str(), "r"); + while (fgets(path, sizeof(path), fp) != nullptr) { + size_t count = strlen(path); + if (count > 0) + --count; + retval.push_back(std::string(path, count)); + } + pclose(fp); + return retval; + } + + void CreateFile(std::string const& filename, std::string const& content) { + std::ofstream oss(filename); + oss << content; + } + + protected: + std::shared_ptr _host; + std::shared_ptr _contact; + std::shared_ptr _svc; + std::shared_ptr _ad; +}; + +/* calls command manager in another thread (function is used for unit tests) + */ +static void call_command_manager(std::unique_ptr& th, + std::condition_variable* condvar, + std::mutex* mutex, + bool* continuerunning) { + auto fn = [continuerunning, mutex, condvar]() { + std::unique_lock lock(*mutex); + for (;;) { + command_manager::instance().execute(); + if (condvar->wait_for( + lock, std::chrono::milliseconds(50), + [continuerunning]() -> bool { return *continuerunning; })) { + break; + } + } + }; + + th.reset(new std::thread(fn)); +} + +TEST_F(EngineRpc, StartStop) { + enginerpc erpc("0.0.0.0", 40001); + ASSERT_NO_THROW(erpc.shutdown()); +} + +TEST_F(EngineRpc, GetVersion) { + std::ostringstream oss; + oss << "GetVersion: major: " << CENTREON_ENGINE_VERSION_MAJOR; + enginerpc erpc("0.0.0.0", 40001); + auto output = execute("GetVersion"); + ASSERT_EQ(output.front(), oss.str()); + if (output.size() == 2u) { + oss.str(""); + oss << "minor: " << CENTREON_ENGINE_VERSION_MINOR; + ASSERT_EQ(output.back(), oss.str()); + } else { + oss.str(""); + oss << "patch: " << CENTREON_ENGINE_VERSION_PATCH; + ASSERT_EQ(output.back(), oss.str()); + } + erpc.shutdown(); +} + +TEST_F(EngineRpc, GetHost) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + + std::vector vectests = { + "GetHost", + fmt::format("Host name: {}", _host->name()), + fmt::format("Host alias: {}", _host->get_alias()), + fmt::format("Host id: {}", _host->host_id()), + "Host address: 127.0.0.1", + "Host state: 1", + "Host period: test_period"}; + _host->set_current_state(engine::host::state_down); + _host->set_check_period("test_period"); + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute(fmt::format("GetHost byhostid {}", _host->host_id())); + auto output2 = execute(fmt::format("GetHost byhostname {}", _host->name())); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + std::vector result_ids(output.size()); + std::copy(output.begin(), output.end(), result_ids.begin()); + + ASSERT_EQ(vectests, result_ids); + + std::vector result_names(output2.size()); + std::copy(output2.begin(), output2.end(), result_names.begin()); + + ASSERT_EQ(vectests, result_names); + erpc.shutdown(); +} + +TEST_F(EngineRpc, GetWrongHost) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + + std::vector vectests = {"GetHostByHostName rpc engine failed", + "GetHost", + "Host name: ", + "Host alias: ", + "Host id: 0", + "Host address: ", + "Host state: 0", + "Host period: "}; + _host->set_current_state(engine::host::state_down); + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute("GetHost byhostname wrong_host"); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + std::vector result_names(output.size()); + std::copy(output.begin(), output.end(), result_names.begin()); + + ASSERT_EQ(vectests, result_names); + erpc.shutdown(); +} + +TEST_F(EngineRpc, GetService) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + std::vector vectests = {"GetService", + "Host id: 12", + "Service id: 13", + "Host name: test_host", + "Serv desc: test_svc", + "Service state: 2", + "Service period: test_period"}; + _svc->set_current_state(engine::service::state_critical); + _svc->set_check_period("test_period"); + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute("GetService bynames test_host test_svc"); + auto output2 = execute("GetService byids 12 13"); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + std::vector result_names(output.size()); + std::copy(output.begin(), output.end(), result_names.begin()); + + ASSERT_EQ(vectests, result_names); + + std::vector result_ids(output2.size()); + std::copy(output2.begin(), output2.end(), result_ids.begin()); + + ASSERT_EQ(vectests, result_ids); + erpc.shutdown(); +} + +TEST_F(EngineRpc, GetWrongService) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + std::vector vectests = {"GetService rpc engine failed", + "GetService", + "Host id: 0", + "Service id: 0", + "Host name: ", + "Serv desc: ", + "Service state: 0", + "Service period: "}; + + _svc->set_current_state(engine::service::state_critical); + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute("GetService bynames wrong_host wrong_svc"); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + std::vector result_names(output.size()); + std::copy(output.begin(), output.end(), result_names.begin()); + + ASSERT_EQ(vectests, result_names); + erpc.shutdown(); +} + +TEST_F(EngineRpc, GetContact) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + std::vector vectests = {"GetContact", "admin", "admin", + "admin@centreon.com"}; + _contact->set_email("admin@centreon.com"); + + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute("GetContact admin"); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + std::vector result_names(output.size()); + std::copy(output.begin(), output.end(), result_names.begin()); + + ASSERT_EQ(vectests, result_names); + erpc.shutdown(); +} + +TEST_F(EngineRpc, GetWrongContact) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + std::vector vectests = {"GetContact rpc engine failed", + "GetContact", "", "", ""}; + + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute("GetContact wrong_contactadmin"); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + std::vector result_names(output.size()); + std::copy(output.begin(), output.end(), result_names.begin()); + + ASSERT_EQ(vectests, result_names); + erpc.shutdown(); +} + +TEST_F(EngineRpc, GetHostsCount) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute("GetHostsCount"); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + ASSERT_EQ(output.back(), "2"); + erpc.shutdown(); +} + +TEST_F(EngineRpc, GetContactsCount) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute("GetContactsCount"); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + ASSERT_EQ(output.back(), "1"); + erpc.shutdown(); +} + +TEST_F(EngineRpc, GetServicesCount) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute("GetServicesCount"); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + ASSERT_EQ(output.back(), "2"); + erpc.shutdown(); +} + +TEST_F(EngineRpc, GetServiceGroupsCount) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute("GetServiceGroupsCount"); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + ASSERT_EQ(output.back(), "1"); + erpc.shutdown(); +} + +TEST_F(EngineRpc, GetContactGroupsCount) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute("GetContactGroupsCount"); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + ASSERT_EQ(output.back(), "0"); + erpc.shutdown(); +} + +TEST_F(EngineRpc, GetHostGroupsCount) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute("GetHostGroupsCount"); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + ASSERT_EQ(output.back(), "1"); + erpc.shutdown(); +} + +TEST_F(EngineRpc, GetServiceDependenciesCount) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute("GetServiceDependenciesCount"); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + ASSERT_EQ(output.back(), "0"); + erpc.shutdown(); +} + +TEST_F(EngineRpc, GetHostDependenciesCount) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute("GetHostDependenciesCount"); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + ASSERT_EQ(output.back(), "0"); + erpc.shutdown(); +} + +TEST_F(EngineRpc, AddHostComment) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + + ASSERT_EQ(comment::comments.size(), 0u); + + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = + execute("AddHostComment test_host test-admin mycomment 1 10000"); + ASSERT_EQ(comment::comments.size(), 1u); + + output = execute("DeleteComment 1"); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + ASSERT_EQ(comment::comments.size(), 0u); + erpc.shutdown(); +} + +TEST_F(EngineRpc, AddServiceComment) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + + ASSERT_EQ(comment::comments.size(), 0u); + + call_command_manager(th, &condvar, &mutex, &continuerunning); + auto output = execute( + "AddServiceComment test_host test_svc test-admin mycomment 1 10000"); + ASSERT_EQ(comment::comments.size(), 1u); + + output = execute("DeleteComment 1"); + ASSERT_EQ(comment::comments.size(), 0u); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + erpc.shutdown(); +} + +TEST_F(EngineRpc, DeleteComment) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + + ASSERT_EQ(comment::comments.size(), 0u); + // create comment + std::ostringstream oss; + oss << "my comment "; + auto cmt = std::make_shared( + comment::host, comment::user, _host->host_id(), 0, 10000, "test-admin", + oss.str(), true, comment::external, false, 0); + comment::comments.insert({cmt->get_comment_id(), cmt}); + + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute("DeleteComment 1"); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + ASSERT_EQ(comment::comments.size(), 0u); + erpc.shutdown(); +} + +TEST_F(EngineRpc, DeleteWrongComment) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + std::vector vectests{ + "DeleteComment failed.", + "DeleteComment 0", + }; + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute("DeleteComment 999"); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + std::vector results(output.size()); + std::copy(output.begin(), output.end(), results.begin()); + + ASSERT_EQ(vectests, results); + ASSERT_EQ(comment::comments.size(), 0u); + erpc.shutdown(); +} + +TEST_F(EngineRpc, DeleteAllHostComments) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + // first test + ASSERT_EQ(comment::comments.size(), 0u); + // create some comments + for (int i = 0; i < 10; ++i) { + std::string cmt_str{fmt::format("my host comment {}", i)}; + auto cmt = std::make_shared( + comment::host, comment::user, _host->host_id(), 0, 10000, "test-admin", + cmt_str, true, comment::external, false, 0); + comment::comments.insert({cmt->get_comment_id(), cmt}); + } + ASSERT_EQ(comment::comments.size(), 10u); + + call_command_manager(th, &condvar, &mutex, &continuerunning); + auto output = execute( + fmt::format("DeleteAllHostComments byhostid {}", _host->host_id())); + + ASSERT_EQ(comment::comments.size(), 0u); + // second test + for (int i = 0; i < 10; ++i) { + std::string cmt_str{fmt::format("my host comment {}", i)}; + auto cmt = std::make_shared( + comment::host, comment::user, _host->host_id(), 0, 10000, "test-admin", + cmt_str, true, comment::external, false, 0); + comment::comments.insert({cmt->get_comment_id(), cmt}); + } + ASSERT_EQ(comment::comments.size(), 10u); + output = execute( + fmt::format("DeleteAllHostComments byhostname {}", _host->name())); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + ASSERT_EQ(comment::comments.size(), 0u); + erpc.shutdown(); +} + +TEST_F(EngineRpc, DeleteAllServiceComments) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + auto svc = _svc; + auto hit = engine::host::hosts_by_id.find(svc->host_id()); + auto hst = hit->second; + + // first test + ASSERT_EQ(comment::comments.size(), 0u); + // create some comments + for (int i = 0; i < 10; ++i) { + std::string cmt_str{fmt::format("my service comment {} on service ({}, {})", + i, svc->host_id(), svc->service_id())}; + auto cmt = std::make_shared( + comment::service, comment::user, svc->host_id(), svc->service_id(), + 10000, "test-admin", cmt_str, true, comment::external, false, 0); + comment::comments.insert({cmt->get_comment_id(), cmt}); + } + ASSERT_EQ(comment::comments.size(), 10u); + + call_command_manager(th, &condvar, &mutex, &continuerunning); + auto output = execute(fmt::format("DeleteAllServiceComments byids {} {}", + svc->host_id(), svc->service_id())); + + ASSERT_EQ(comment::comments.size(), 0u); + // second test + for (int i = 0; i < 10; ++i) { + std::string cmt_str{fmt::format("my service comment {}", i)}; + auto cmt = std::make_shared( + comment::service, comment::user, svc->host_id(), svc->service_id(), + 10000, "test-admin", cmt_str, true, comment::external, false, 0); + comment::comments.insert({cmt->get_comment_id(), cmt}); + } + ASSERT_EQ(comment::comments.size(), 10u); + output = execute(fmt::format("DeleteAllServiceComments bynames {} {}", + hst->name(), svc->description())); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + ASSERT_EQ(comment::comments.size(), 0u); + erpc.shutdown(); +} + +TEST_F(EngineRpc, RemoveHostAcknowledgement) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + std::ostringstream oss; + bool continuerunning = false; + oss << "my comment "; + // first test + _host->set_acknowledgement(AckType::NORMAL); + // create comment + auto cmt = std::make_shared( + comment::host, comment::acknowledgment, _host->host_id(), 0, 10000, + "test-admin", oss.str(), false, comment::external, false, 0); + comment::comments.insert({cmt->get_comment_id(), cmt}); + + call_command_manager(th, &condvar, &mutex, &continuerunning); + auto output = execute( + fmt::format("RemoveHostAcknowledgement byhostid {}", _host->host_id())); + + ASSERT_EQ(_host->problem_has_been_acknowledged(), false); + ASSERT_EQ(comment::comments.size(), 0u); + // second test + _host->set_acknowledgement(AckType::NORMAL); + cmt = std::make_shared( + comment::host, comment::acknowledgment, _host->host_id(), 0, 10000, + "test-admin", oss.str(), false, comment::external, false, 0); + comment::comments.insert({cmt->get_comment_id(), cmt}); + + output = execute( + fmt::format("RemoveHostAcknowledgement byhostname {}", _host->name())); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + ASSERT_EQ(_host->problem_has_been_acknowledged(), false); + ASSERT_EQ(comment::comments.size(), 0u); + erpc.shutdown(); +} + +TEST_F(EngineRpc, RemoveServiceAcknowledgement) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + auto svc = _svc; + auto hit = engine::host::hosts_by_id.find(svc->host_id()); + auto hst = hit->second; + std::string ack_str{"my comment"}; + _svc->set_acknowledgement(AckType::NORMAL); + auto cmt = std::make_shared( + comment::service, comment::acknowledgment, hst->host_id(), + svc->service_id(), 10000, "test-admin", ack_str, false, comment::external, + false, 0); + comment::comments.insert({cmt->get_comment_id(), cmt}); + + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = + execute("RemoveServiceAcknowledgement bynames test_host test_svc"); + + ASSERT_EQ(comment::comments.size(), 0u); + ASSERT_EQ(svc->problem_has_been_acknowledged(), false); + + svc->set_acknowledgement(AckType::NORMAL); + cmt = std::make_shared(comment::service, comment::acknowledgment, + hst->host_id(), svc->service_id(), 10000, + "test-admin", ack_str, false, + comment::external, false, 0); + comment::comments.insert({cmt->get_comment_id(), cmt}); + + output = execute("RemoveServiceAcknowledgement byids 12 13"); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + ASSERT_EQ(comment::comments.size(), 0u); + erpc.shutdown(); +} + +TEST_F(EngineRpc, AcknowledgementHostProblem) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + + ASSERT_EQ(_host->problem_has_been_acknowledged(), false); + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute(fmt::format( + "AcknowledgementHostProblem {} admin test 1 0 0", _host->name())); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + ASSERT_EQ(_host->problem_has_been_acknowledged(), true); + erpc.shutdown(); +} + +TEST_F(EngineRpc, AcknowledgementServiceProblem) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + + ASSERT_EQ(_svc->problem_has_been_acknowledged(), false); + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute( + "AcknowledgementServiceProblem test_host test_svc admin test 1 0 0"); + ; + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + ASSERT_EQ(_svc->problem_has_been_acknowledged(), true); + erpc.shutdown(); +} + +TEST_F(EngineRpc, ScheduleHostDowntime) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + std::ostringstream oss; + bool continuerunning = false; + + ASSERT_EQ(0u, downtime_manager::instance().get_scheduled_downtimes().size()); + + set_time(20000); + time_t now = time(nullptr); + + oss << "ScheduleHostDowntime test_host " << now << " " << now + 1 + << " 0 0 10000 undef host " << now; + + call_command_manager(th, &condvar, &mutex, &continuerunning); + + // we fake a wrong test with an undefined parameter + auto output = execute(oss.str()); + ASSERT_EQ("ScheduleHostDowntime 0", output.back()); + oss.str(""); + + // we make the right test + oss << "ScheduleHostDowntime test_host " << now << " " << now + 1 + << " 0 0 10000 admin host " << now; + output = execute(oss.str()); + ASSERT_EQ(1u, downtime_manager::instance().get_scheduled_downtimes().size()); + ASSERT_EQ("ScheduleHostDowntime 1", output.back()); + + // deleting the current downtime + uint64_t id = downtime_manager::instance() + .get_scheduled_downtimes() + .begin() + ->second->get_downtime_id(); + oss.str(""); + oss << "DeleteDowntime " << id; + output = execute(oss.str()); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + ASSERT_EQ(0u, downtime_manager::instance().get_scheduled_downtimes().size()); + erpc.shutdown(); +} + +TEST_F(EngineRpc, ScheduleWrongHostDowntime) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + std::ostringstream oss; + bool continuerunning = false; + + ASSERT_EQ(0u, downtime_manager::instance().get_scheduled_downtimes().size()); + + set_time(20000); + time_t now = time(nullptr); + + oss << "ScheduleHostDowntime test_host " << now + 1 << " " << now + << " 0 0 10000 admin host " << now; + + call_command_manager(th, &condvar, &mutex, &continuerunning); + + // we fake a wrong test with an + auto output = execute(oss.str()); + ASSERT_EQ("ScheduleHostDowntime 0", output.back()); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + ASSERT_EQ(0u, downtime_manager::instance().get_scheduled_downtimes().size()); + erpc.shutdown(); +} + +TEST_F(EngineRpc, ScheduleServiceDowntime) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + std::ostringstream oss; + bool continuerunning = false; + + ASSERT_EQ(0u, downtime_manager::instance().get_scheduled_downtimes().size()); + + set_time(20000); + time_t now = time(nullptr); + + oss << "ScheduleServiceDowntime test_host test_svc " << now << " " << now + 1 + << " 0 0 10000 undef host " << now; + + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute(oss.str()); + ASSERT_EQ("ScheduleServiceDowntime 0", output.back()); + oss.str(""); + + oss << "ScheduleServiceDowntime test_host test_svc " << now << " " << now + 1 + << " 0 0 10000 admin host " << now; + output = execute(oss.str()); + ASSERT_EQ(2u, downtime_manager::instance() + .get_scheduled_downtimes() + .size()); // one for service and one for ano + ASSERT_EQ("ScheduleServiceDowntime 1", output.back()); + + oss.str(""); + uint64_t id = downtime_manager::instance() + .get_scheduled_downtimes() + .begin() + ->second->get_downtime_id(); + oss << "DeleteDowntime " << id; + output = execute(oss.str()); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + ASSERT_EQ(0u, downtime_manager::instance().get_scheduled_downtimes().size()); + erpc.shutdown(); +} + +TEST_F(EngineRpc, ScheduleWrongServiceDowntime) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + std::ostringstream oss; + bool continuerunning = false; + + ASSERT_EQ(0u, downtime_manager::instance().get_scheduled_downtimes().size()); + + set_time(20000); + time_t now = time(nullptr); + + oss << "ScheduleServiceDowntime test_host test_svc " << now + 1 << " " << now + << " 0 0 10000 admin host " << now; + + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute(oss.str()); + ASSERT_EQ("ScheduleServiceDowntime 0", output.back()); + oss.str(""); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + ASSERT_EQ(0u, downtime_manager::instance().get_scheduled_downtimes().size()); + erpc.shutdown(); +} + +TEST_F(EngineRpc, ScheduleHostServicesDowntime) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + std::ostringstream oss; + std::ostringstream oss2; + bool continuerunning = false; + + ASSERT_EQ(0u, downtime_manager::instance().get_scheduled_downtimes().size()); + + set_time(20000); + time_t now = time(nullptr); + + oss << "ScheduleHostServicesDowntime test_host " << now << " " << now + 1 + << " 0 0 10000 undef host " << now; + + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute(oss.str()); + ASSERT_EQ("ScheduleHostServicesDowntime 0", output.back()); + oss.str(""); + + oss << "ScheduleHostServicesDowntime test_host " << now << " " << now + 1 + << " 0 0 10000 admin host " << now; + output = execute(oss.str()); + ASSERT_EQ(3u, downtime_manager::instance() + .get_scheduled_downtimes() + .size()); // one for service and one for ano + ASSERT_EQ("ScheduleHostServicesDowntime 1", output.back()); + + oss2 << "DeleteServiceDowntimeFull test_host undef undef undef" + " undef undef undef undef undef"; + + output = execute(oss2.str()); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + ASSERT_EQ(0u, downtime_manager::instance().get_scheduled_downtimes().size()); + erpc.shutdown(); +} + +TEST_F(EngineRpc, ScheduleHostGroupHostsDowntime) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + std::ostringstream oss; + bool continuerunning = false; + + ASSERT_EQ(0u, downtime_manager::instance().get_scheduled_downtimes().size()); + set_time(20000); + time_t now = time(nullptr); + + oss << "ScheduleHostGroupHostsDowntime test_hg " << now << " " << now + 1 + << " 0 0 10000 undef host " << now; + + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute(oss.str()); + ASSERT_EQ("ScheduleHostGroupHostsDowntime 0", output.back()); + oss.str(""); + + oss << "ScheduleHostGroupHostsDowntime test_hg " << now << " " << now + 1 + << " 0 0 10000 admin host " << now; + output = execute(oss.str()); + ASSERT_EQ(1u, downtime_manager::instance().get_scheduled_downtimes().size()); + uint64_t id = downtime_manager::instance() + .get_scheduled_downtimes() + .begin() + ->second->get_downtime_id(); + oss.str(""); + oss << "DeleteDowntime " << id; + output = execute(oss.str()); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + ASSERT_EQ(0u, downtime_manager::instance().get_scheduled_downtimes().size()); + erpc.shutdown(); +} + +TEST_F(EngineRpc, ScheduleHostGroupServicesDowntime) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + std::ostringstream oss; + bool continuerunning = false; + + ASSERT_EQ(0u, downtime_manager::instance().get_scheduled_downtimes().size()); + set_time(20000); + time_t now = time(nullptr); + + oss << "ScheduleHostGroupServicesDowntime test_hg " << now << " " << now + 1 + << " 0 0 10000 undef host " << now; + + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute(oss.str()); + ASSERT_EQ("ScheduleHostGroupServicesDowntime 0", output.back()); + oss.str(""); + + oss << "ScheduleHostGroupServicesDowntime test_hg " << now << " " << now + 1 + << " 0 0 10000 admin host " << now; + output = execute(oss.str()); + ASSERT_EQ(3u, downtime_manager::instance() + .get_scheduled_downtimes() + .size()); // one for service and one for ano + ASSERT_EQ("ScheduleHostGroupServicesDowntime 1", output.back()); + + oss.str(""); + oss << "DeleteServiceDowntimeFull test_host undef undef undef" + " undef undef undef undef undef"; + output = execute(oss.str()); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + ASSERT_EQ(0u, downtime_manager::instance().get_scheduled_downtimes().size()); + erpc.shutdown(); +} + +TEST_F(EngineRpc, ScheduleServiceGroupHostsDowntime) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + std::ostringstream oss; + std::ostringstream oss2; + bool continuerunning = false; + + ASSERT_EQ(0u, downtime_manager::instance().get_scheduled_downtimes().size()); + set_time(20000); + time_t now = time(nullptr); + oss << "ScheduleServiceGroupHostsDowntime test_sg " << now << " " << now + 1 + << " 0 0 10000 undef host " << now; + + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute(oss.str()); + ASSERT_EQ("ScheduleServiceGroupHostsDowntime 0", output.back()); + oss.str(""); + + oss << "ScheduleServiceGroupHostsDowntime test_sg " << now << " " << now + 1 + << " 0 0 10000 admin host " << now; + output = execute(oss.str()); + ASSERT_EQ(1u, downtime_manager::instance().get_scheduled_downtimes().size()); + ASSERT_EQ("ScheduleServiceGroupHostsDowntime 1", output.back()); + + // deleting current downtime + uint64_t id = downtime_manager::instance() + .get_scheduled_downtimes() + .begin() + ->second->get_downtime_id(); + oss.str(""); + oss << "DeleteDowntime " << id; + output = execute(oss.str()); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + ASSERT_EQ(0u, downtime_manager::instance().get_scheduled_downtimes().size()); + erpc.shutdown(); +} + +TEST_F(EngineRpc, ScheduleServiceGroupServicesDowntime) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + std::ostringstream oss; + std::ostringstream oss2; + bool continuerunning = false; + + ASSERT_EQ(0u, downtime_manager::instance().get_scheduled_downtimes().size()); + set_time(20000); + time_t now = time(nullptr); + oss << "ScheduleServiceGroupServicesDowntime test_sg " << now << " " + << now + 1 << " 0 0 10000 undef host " << now; + + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute(oss.str()); + ASSERT_EQ("ScheduleServiceGroupServicesDowntime 0", output.back()); + oss.str(""); + + oss << "ScheduleServiceGroupServicesDowntime test_sg " << now << " " + << now + 1 << " 0 0 10000 admin host " << now; + output = execute(oss.str()); + ASSERT_EQ(2u, downtime_manager::instance() + .get_scheduled_downtimes() + .size()); // one for service and one for ano + ASSERT_EQ("ScheduleServiceGroupServicesDowntime 1", output.back()); + + uint64_t id = downtime_manager::instance() + .get_scheduled_downtimes() + .begin() + ->second->get_downtime_id(); + oss2 << "DeleteDowntime " << id; + output = execute(oss2.str()); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + ASSERT_EQ(0u, downtime_manager::instance().get_scheduled_downtimes().size()); + erpc.shutdown(); +} + +TEST_F(EngineRpc, ScheduleAndPropagateHostDowntime) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + std::ostringstream oss; + bool continuerunning = false; + + ASSERT_EQ(0u, downtime_manager::instance().get_scheduled_downtimes().size()); + + set_time(20000); + time_t now = time(nullptr); + oss << "ScheduleAndPropagateHostDowntime test_host " << now << " " << now + 1 + << " 0 0 10000 undef host " << now; + + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute(oss.str()); + ASSERT_EQ("ScheduleAndPropagateHostDowntime 0", output.back()); + oss.str(""); + + oss << "ScheduleAndPropagateHostDowntime test_host " << now << " " << now + 1 + << " 0 0 10000 admin host " << now; + output = execute(oss.str()); + ASSERT_EQ(2u, downtime_manager::instance().get_scheduled_downtimes().size()); + ASSERT_EQ("ScheduleAndPropagateHostDowntime 1", output.back()); + + oss.str(""); + oss << "DeleteDowntimeByHostName test_host undef undef undef"; + output = execute(oss.str()); + + oss.str(""); + oss << "DeleteDowntimeByHostName child_host undef undef undef"; + output = execute(oss.str()); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + ASSERT_EQ(0u, downtime_manager::instance().get_scheduled_downtimes().size()); + erpc.shutdown(); +} + +TEST_F(EngineRpc, ScheduleAndPropagateTriggeredHostDowntime) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + std::ostringstream oss; + std::ostringstream oss2; + bool continuerunning = false; + + ASSERT_EQ(0u, downtime_manager::instance().get_scheduled_downtimes().size()); + + set_time(20000); + time_t now = time(nullptr); + oss << "ScheduleHostDowntime test_host " << now << " " << now + 1 + << " 0 0 10000 admin host " << now; + + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute(oss.str()); + ASSERT_EQ(1u, downtime_manager::instance().get_scheduled_downtimes().size()); + uint64_t id = downtime_manager::instance() + .get_scheduled_downtimes() + .begin() + ->second->get_downtime_id(); + oss.str(""); + oss << "ScheduleAndPropagateTriggeredHostDowntime test_host " << now << " " + << now + 1 << " 0 " << id << " 10000 admin host " << now; + output = execute(oss.str()); + ASSERT_EQ(3u, downtime_manager::instance().get_scheduled_downtimes().size()); + + oss.str(""); + oss << "DeleteDowntime " << id; + output = execute(oss.str()); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + ASSERT_EQ(0u, downtime_manager::instance().get_scheduled_downtimes().size()); + erpc.shutdown(); +} + +TEST_F(EngineRpc, DelayHostNotification) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + auto hst = engine::host::hosts_by_id.find(12)->second; + + ASSERT_EQ(hst->get_next_notification(), 0); + + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute("DelayHostNotification byhostid 12 20"); + ASSERT_EQ(hst->get_next_notification(), 20); + + output = execute( + fmt::format("DelayHostNotification byhostname {} 10", hst->name())); + ASSERT_EQ(hst->get_next_notification(), 10); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + erpc.shutdown(); +} + +TEST_F(EngineRpc, DelayServiceNotification) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + + ASSERT_EQ(_host->get_next_notification(), 0); + + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute("DelayServiceNotification byids 12 13 20"); + ASSERT_EQ(_svc->get_next_notification(), 20); + + output = execute("DelayServiceNotification bynames test_host test_svc 10"); + ASSERT_EQ(_svc->get_next_notification(), 10); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + erpc.shutdown(); +} + +TEST_F(EngineRpc, ChangeHostObjectIntVar) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = + execute(fmt::format("ChangeHostObjectIntVar {} 0 1 1.0", _host->name())); + ASSERT_EQ(_host->check_interval(), 1u); + output = + execute(fmt::format("ChangeHostObjectIntVar {} 1 1 2.0", _host->name())); + ASSERT_EQ(_host->retry_interval(), 2u); + output = + execute(fmt::format("ChangeHostObjectIntVar {} 2 1 1.0", _host->name())); + ASSERT_EQ(_host->max_check_attempts(), 1); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + erpc.shutdown(); +} + +TEST_F(EngineRpc, ChangeServiceObjectIntVar) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute( + "ChangeServiceObjectIntVar" + " test_host test_svc 0 1 1.0"); + ASSERT_EQ(_svc->check_interval(), 1u); + output = execute( + "ChangeServiceObjectIntVar" + " test_host test_svc 1 1 2.0"); + ASSERT_EQ(_svc->retry_interval(), 2u); + output = execute( + "ChangeServiceObjectIntVar" + " test_host test_svc 2 1 1.0"); + ASSERT_EQ(_svc->max_check_attempts(), 1); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + erpc.shutdown(); +} + +TEST_F(EngineRpc, ChangeContactObjectIntVar) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute( + "ChangeContactObjectIntVar" + " admin 0 1 1.0"); + ASSERT_EQ(_contact->get_modified_attributes(), 1u); + output = execute( + "ChangeContactObjectIntVar" + " admin 1 2 1.0"); + ASSERT_EQ(_contact->get_modified_host_attributes(), 2u); + output = execute( + "ChangeContactObjectIntVar" + " admin 2 3 1.0"); + ASSERT_EQ(_contact->get_modified_service_attributes(), 3u); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + erpc.shutdown(); +} + +TEST_F(EngineRpc, ChangeHostObjectCharVar) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + + ASSERT_EQ(engine::timeperiod::timeperiods.size(), 1u); + + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute("ChangeHostObjectCharVar null 0 cmd"); + ASSERT_EQ(output.back(), "ChangeHostObjectCharVar 1"); + output = + execute(fmt::format("ChangeHostObjectCharVar {} 1 cmd", _host->name())); + ASSERT_EQ(_host->event_handler(), "cmd"); + output = + execute(fmt::format("ChangeHostObjectCharVar {} 2 cmd", _host->name())); + ASSERT_EQ(_host->check_command(), "cmd"); + output = + execute(fmt::format("ChangeHostObjectCharVar {} 3 24x7", _host->name())); + ASSERT_EQ(_host->check_period(), "24x7"); + output = + execute(fmt::format("ChangeHostObjectCharVar {} 4 24x7", _host->name())); + ASSERT_EQ(_host->notification_period(), "24x7"); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + erpc.shutdown(); +} + +TEST_F(EngineRpc, ChangeServiceObjectCharVar) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + auto svc = _svc; + auto hit = engine::host::hosts_by_id.find(svc->host_id()); + auto hst = hit->second; + + ASSERT_EQ(engine::timeperiod::timeperiods.size(), 1u); + + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute("ChangeServiceObjectCharVar null null 0 cmd"); + ASSERT_EQ(output.back(), "ChangeServiceObjectCharVar 1"); + output = execute(fmt::format("ChangeServiceObjectCharVar {} {} 1 cmd", + hst->name(), svc->description())); + ASSERT_EQ(_svc->event_handler(), "cmd"); + output = execute(fmt::format("ChangeServiceObjectCharVar {} {} 2 cmd", + hst->name(), svc->description())); + ASSERT_EQ(_svc->check_command(), "cmd"); + output = execute(fmt::format("ChangeServiceObjectCharVar {} {} 3 24x7", + hst->name(), svc->description())); + ASSERT_EQ(_svc->check_period(), "24x7"); + output = execute(fmt::format("ChangeServiceObjectCharVar {} {} 4 24x7", + hst->name(), svc->description())); + ASSERT_EQ(_svc->notification_period(), "24x7"); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + erpc.shutdown(); +} + +TEST_F(EngineRpc, ChangeContactObjectCharVar) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + + ASSERT_EQ(engine::timeperiod::timeperiods.size(), 1u); + + call_command_manager(th, &condvar, &mutex, &continuerunning); + + auto output = execute( + "ChangeContactObjectCharVar" + " admin 0 24x7"); + ASSERT_EQ(_contact->get_host_notification_period(), "24x7"); + output = execute( + "ChangeContactObjectCharVar" + " admin 1 24x7"); + ASSERT_EQ(_contact->get_service_notification_period(), "24x7"); + + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + erpc.shutdown(); +} + +TEST_F(EngineRpc, ChangeHostObjectCustomVar) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + + ASSERT_EQ(_host->custom_variables.size(), 0u); + call_command_manager(th, &condvar, &mutex, &continuerunning); + auto output = execute(fmt::format( + "ChangeHostObjectCustomVar {} test_var test_val", _host->name())); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + ASSERT_EQ(_host->custom_variables.size(), 1u); + ASSERT_EQ(_host->custom_variables["TEST_VAR"].value(), "test_val"); + _host->custom_variables.clear(); + ASSERT_EQ(_host->custom_variables.size(), 0u); + erpc.shutdown(); +} + +TEST_F(EngineRpc, ChangeServiceObjectCustomVar) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + + _svc->custom_variables.clear(); + ASSERT_EQ(_svc->custom_variables.size(), 0u); + call_command_manager(th, &condvar, &mutex, &continuerunning); + auto output = execute( + "ChangeServiceObjectCustomVar" + " test_host test_svc test_var test_val"); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + + ASSERT_EQ(_svc->custom_variables.size(), 1u); + ASSERT_EQ(_svc->custom_variables["TEST_VAR"].value(), "test_val"); + _svc->custom_variables.clear(); + ASSERT_EQ(_svc->custom_variables.size(), 0u); + erpc.shutdown(); +} + +TEST_F(EngineRpc, ChangeContactObjectCustomVar) { + enginerpc erpc("0.0.0.0", 40001); + std::unique_ptr th; + std::condition_variable condvar; + std::mutex mutex; + bool continuerunning = false; + ASSERT_EQ(_contact->get_custom_variables().size(), 0u); + + call_command_manager(th, &condvar, &mutex, &continuerunning); + auto output = execute( + "ChangeContactObjectCustomVar" + " admin test_var test_val"); + { + std::lock_guard lock(mutex); + continuerunning = true; + } + condvar.notify_one(); + th->join(); + ASSERT_EQ(_contact->get_custom_variables().size(), 1u); + ASSERT_EQ(_contact->get_custom_variables()["TEST_VAR"].value(), "test_val"); + + erpc.shutdown(); +} + +TEST_F(EngineRpc, ProcessServiceCheckResult) { + enginerpc erpc("0.0.0.0", 40001); + auto output = execute("ProcessServiceCheckResult test_host test_svc 0"); + ASSERT_EQ(output.size(), 1u); + ASSERT_EQ(output.front(), "ProcessServiceCheckResult: 0"); + erpc.shutdown(); +} + +TEST_F(EngineRpc, ProcessServiceCheckResultBadHost) { + enginerpc erpc("0.0.0.0", 40001); + auto output = execute("ProcessServiceCheckResult \"\" test_svc 0"); + ASSERT_EQ(output.size(), 2u); + ASSERT_EQ(output.front(), "ProcessServiceCheckResult failed."); + erpc.shutdown(); +} + +TEST_F(EngineRpc, ProcessServiceCheckResultBadService) { + enginerpc erpc("0.0.0.0", 40001); + auto output = execute("ProcessServiceCheckResult test_host \"\" 0"); + ASSERT_EQ(output.size(), 2u); + ASSERT_EQ(output.front(), "ProcessServiceCheckResult failed."); + erpc.shutdown(); +} + +TEST_F(EngineRpc, ProcessHostCheckResult) { + enginerpc erpc("0.0.0.0", 40001); + auto output = execute("ProcessHostCheckResult test_host 0"); + ASSERT_EQ(output.size(), 1u); + ASSERT_EQ(output.front(), "ProcessHostCheckResult: 0"); + erpc.shutdown(); +} + +TEST_F(EngineRpc, ProcessHostCheckResultBadHost) { + enginerpc erpc("0.0.0.0", 40001); + auto output = execute("ProcessHostCheckResult '' 0"); + ASSERT_EQ(output.size(), 2u); + ASSERT_EQ(output.front(), "ProcessHostCheckResult failed."); + erpc.shutdown(); +} + +TEST_F(EngineRpc, NewThresholdsFile) { + CreateFile( + "/tmp/thresholds_file.json", + "[{\n \"host_id\": \"12\",\n \"service_id\": \"12\",\n \"metric_name\": " + "\"metric\",\n \"predict\": [{\n \"timestamp\": 50000,\n \"upper\": " + "84,\n \"lower\": 74,\n \"fit\": 79\n }, {\n \"timestamp\": 100000,\n " + "\"upper\": 10,\n \"lower\": 5,\n \"fit\": 51.5\n }, {\n \"timestamp\": " + "150000,\n \"upper\": 100,\n \"lower\": 93,\n \"fit\": 96.5\n }, {\n " + "\"timestamp\": 200000,\n \"upper\": 100,\n \"lower\": 97,\n \"fit\": " + "98.5\n }, {\n \"timestamp\": 250000,\n \"upper\": 100,\n \"lower\": " + "21,\n \"fit\": 60.5\n }\n]}]"); + enginerpc erpc("0.0.0.0", 40001); + auto output = execute("NewThresholdsFile /tmp/thresholds_file.json"); + ASSERT_EQ(output.size(), 1u); + ASSERT_EQ(output.front(), "NewThresholdsFile: 0"); + command_manager::instance().execute(); + ASSERT_EQ(_ad->get_thresholds_file(), "/tmp/thresholds_file.json"); +} diff --git a/engine/tests/external_commands/pbanomalydetection.cc b/engine/tests/external_commands/pbanomalydetection.cc new file mode 100644 index 00000000000..374587e7805 --- /dev/null +++ b/engine/tests/external_commands/pbanomalydetection.cc @@ -0,0 +1,142 @@ +/** + * Copyright 2019 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include "com/centreon/engine/anomalydetection.hh" + +#include + +#include "../test_engine.hh" +#include "com/centreon/engine/checks/checker.hh" +#include "com/centreon/engine/command_manager.hh" +#include "com/centreon/engine/commands/commands.hh" +#include "com/centreon/engine/configuration/applier/anomalydetection.hh" +#include "com/centreon/engine/configuration/applier/contact.hh" +#include "com/centreon/engine/configuration/applier/host.hh" +#include "com/centreon/engine/configuration/applier/service.hh" +#include "com/centreon/engine/events/loop.hh" +#include "com/centreon/engine/timezone_manager.hh" +#include "com/centreon/engine/version.hh" +#include "helper.hh" + +using namespace com::centreon; +using namespace com::centreon::engine; + +class ADExtCmd : public TestEngine { + public: + void SetUp() override { + init_config_state(); + + configuration::error_cnt err; + configuration::applier::contact ct_aply; + configuration::Contact ctct{new_pb_configuration_contact("admin", true)}; + ct_aply.add_object(ctct); + ct_aply.expand_objects(pb_config); + ct_aply.resolve_object(ctct, err); + + configuration::Host hst{new_pb_configuration_host("test_host", "admin")}; + configuration::applier::host hst_aply; + hst_aply.add_object(hst); + + configuration::Service svc{new_pb_configuration_service("test_host", "test_svc", "admin")}; + configuration::applier::service svc_aply; + svc_aply.add_object(svc); + + hst_aply.resolve_object(hst, err); + svc_aply.resolve_object(svc, err); + + configuration::Anomalydetection ad{new_pb_configuration_anomalydetection( + "test_host", "test_ad", "admin", + 12, // service_id of the anomalydetection + 13, // service_id of the dependent service + "/tmp/thresholds_status_change.json")}; + configuration::applier::anomalydetection ad_aply; + ad_aply.add_object(ad); + + ad_aply.resolve_object(ad, err); + + host_map const& hm{engine::host::hosts}; + _host = hm.begin()->second; + _host->set_current_state(engine::host::state_up); + _host->set_state_type(checkable::hard); + _host->set_acknowledgement(AckType::NONE); + _host->set_notify_on(static_cast(-1)); + + service_map const& sm{engine::service::services}; + for (auto& p : sm) { + std::shared_ptr svc = p.second; + if (svc->service_id() == 12) + _ad = std::static_pointer_cast(svc); + else + _svc = svc; + } + } + + void TearDown() override { + _host.reset(); + _svc.reset(); + _ad.reset(); + deinit_config_state(); + } + + std::list execute(const std::string& command) { + std::list retval; + char path[1024]; + std::ostringstream oss; + oss << "tests/rpc_client " << command; + + FILE* fp = popen(oss.str().c_str(), "r"); + while (fgets(path, sizeof(path), fp) != nullptr) { + size_t count = strlen(path); + if (count > 0) + --count; + retval.push_back(std::string(path, count)); + } + pclose(fp); + return retval; + } + + void CreateFile(std::string const& filename, std::string const& content) { + std::ofstream oss(filename); + oss << content; + } + + protected: + std::shared_ptr _host; + std::shared_ptr _svc; + std::shared_ptr _ad; +}; + +TEST_F(ADExtCmd, NewThresholdsFile) { + CreateFile( + "/tmp/thresholds_file.json", + "[{\n \"host_id\": \"12\",\n \"service_id\": \"12\",\n \"metric_name\": " + "\"metric\",\n \"predict\": [{\n \"timestamp\": 50000,\n \"upper\": " + "84,\n \"lower\": 74,\n \"fit\": 79\n }, {\n \"timestamp\": 100000,\n " + "\"upper\": 10,\n \"lower\": 5,\n \"fit\": 51.5\n }, {\n \"timestamp\": " + "150000,\n \"upper\": 100,\n \"lower\": 93,\n \"fit\": 96.5\n }, {\n " + "\"timestamp\": 200000,\n \"upper\": 100,\n \"lower\": 97,\n \"fit\": " + "98.5\n }, {\n \"timestamp\": 250000,\n \"upper\": 100,\n \"lower\": " + "21,\n \"fit\": 60.5\n }\n]}]"); + std::ostringstream oss; + oss << '[' << std::time(nullptr) << ']' + << " NEW_THRESHOLDS_FILE;/tmp/thresholds_file.json"; + process_external_command(oss.str().c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_ad->get_thresholds_file(), "/tmp/thresholds_file.json"); +} diff --git a/engine/tests/external_commands/pbhost.cc b/engine/tests/external_commands/pbhost.cc new file mode 100644 index 00000000000..4d18edb280f --- /dev/null +++ b/engine/tests/external_commands/pbhost.cc @@ -0,0 +1,150 @@ +/** + * Copyright 2005 - 2019 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include +#include "../timeperiod/utils.hh" +#include "com/centreon/engine/checks/checker.hh" +#include "com/centreon/engine/commands/commands.hh" +#include "com/centreon/engine/configuration/applier/host.hh" +#include "com/centreon/engine/timezone_manager.hh" +#include "common/engine_conf/host_helper.hh" +#include "helper.hh" + +using namespace com::centreon; +using namespace com::centreon::engine; + +class HostExternalCommand : public ::testing::Test { + public: + void SetUp() override { init_config_state(); } + + void TearDown() override { deinit_config_state(); } +}; + +TEST_F(HostExternalCommand, AddHostDowntime) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_srv"); + hst.set_address("127.0.0.1"); + hst.set_host_id(1); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + + set_time(20000); + time_t now = time(nullptr); + + testing::internal::CaptureStdout(); + + for (int i = 0; i < 3; i++) { + now += 300; + std::string cmd{"test_srv;1;|"}; + set_time(now); + cmd_process_host_check_result(CMD_PROCESS_HOST_CHECK_RESULT, now, + const_cast(cmd.c_str())); + checks::checker::instance().reap(); + } + + std::string const& out{testing::internal::GetCapturedStdout()}; + std::cout << out << std::endl; + ASSERT_NE(out.find("PASSIVE HOST CHECK"), std::string::npos); + ASSERT_NE(out.find("HOST ALERT"), std::string::npos); +} + +TEST_F(HostExternalCommand, AddHostDowntimeByIpAddress) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_srv"); + hst.set_address("127.0.0.1"); + hst.set_host_id(1); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + + set_time(20000); + time_t now = time(nullptr); + + testing::internal::CaptureStdout(); + for (int i = 0; i < 3; i++) { + now += 300; + std::string cmd{"127.0.0.1;1;|"}; + set_time(now); + cmd_process_host_check_result(CMD_PROCESS_HOST_CHECK_RESULT, now, + const_cast(cmd.c_str())); + checks::checker::instance().reap(); + } + + std::string const& out{testing::internal::GetCapturedStdout()}; + + ASSERT_NE(out.find("PASSIVE HOST CHECK"), std::string::npos); + ASSERT_NE(out.find("HOST ALERT"), std::string::npos); +} + +TEST_F(HostExternalCommand, AddHostComment) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + configuration::Host hst2; + configuration::host_helper hst2_hlp(&hst2); + + hst.set_host_name("test_srv"); + hst.set_address("127.0.0.1"); + hst.set_host_id(1); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + + hst2.set_host_name("test_srv2"); + hst2.set_address("127.0.0.1"); + hst2.set_host_id(2); + ASSERT_NO_THROW(hst_aply.add_object(hst2)); + + set_time(20000); + time_t now = time(nullptr); + + std::string cmd_com1{"test_srv;1;user;this is a first comment"}; + std::string cmd_com2{"test_srv;1;user;this is a second comment"}; + std::string cmd_com3{"test_srv;1;user;this is a third comment"}; + std::string cmd_com4{"test_srv;1;user;this is a fourth comment"}; + std::string cmd_com5{"test_srv2;1;user;this is a fifth comment"}; + std::string cmd_del{"1"}; + std::string cmd_del_last{"5"}; + std::string cmd_del_all{"test_srv"}; + + cmd_add_comment(CMD_ADD_HOST_COMMENT, now, + const_cast(cmd_com1.c_str())); + ASSERT_EQ(comment::comments.size(), 1u); + cmd_add_comment(CMD_ADD_HOST_COMMENT, now, + const_cast(cmd_com2.c_str())); + ASSERT_EQ(comment::comments.size(), 2u); + cmd_add_comment(CMD_ADD_HOST_COMMENT, now, + const_cast(cmd_com3.c_str())); + ASSERT_EQ(comment::comments.size(), 3u); + cmd_add_comment(CMD_ADD_HOST_COMMENT, now, + const_cast(cmd_com4.c_str())); + ASSERT_EQ(comment::comments.size(), 4u); + cmd_add_comment(CMD_ADD_HOST_COMMENT, now, + const_cast(cmd_com5.c_str())); + ASSERT_EQ(comment::comments.size(), 5u); + cmd_delete_comment(CMD_DEL_HOST_COMMENT, const_cast(cmd_del.c_str())); + ASSERT_EQ(comment::comments.size(), 4u); + cmd_delete_all_comments(CMD_DEL_ALL_HOST_COMMENTS, + const_cast(cmd_del_all.c_str())); + ASSERT_EQ(comment::comments.size(), 1u); + cmd_delete_comment(CMD_DEL_HOST_COMMENT, + const_cast(cmd_del_last.c_str())); + ASSERT_EQ(comment::comments.size(), 0u); +} diff --git a/engine/tests/external_commands/pbservice.cc b/engine/tests/external_commands/pbservice.cc new file mode 100644 index 00000000000..6ed78ec3acf --- /dev/null +++ b/engine/tests/external_commands/pbservice.cc @@ -0,0 +1,227 @@ +/** + * Copyright 2005 - 2019 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include +#include "../timeperiod/utils.hh" +#include "com/centreon/engine/checks/checker.hh" +#include "com/centreon/engine/commands/commands.hh" +#include "com/centreon/engine/configuration/applier/command.hh" +#include "com/centreon/engine/configuration/applier/host.hh" +#include "com/centreon/engine/configuration/applier/service.hh" +#include "com/centreon/engine/events/loop.hh" +#include "com/centreon/process_manager.hh" +#include "common/engine_conf/service_helper.hh" +#include "helper.hh" + +using namespace com::centreon; +using namespace com::centreon::engine; + +class ServiceExternalCommand : public ::testing::Test { + public: + void SetUp() override { init_config_state(); } + + void TearDown() override { + deinit_config_state(); + events::loop::instance().clear(); + } +}; + +TEST_F(ServiceExternalCommand, AddServiceDowntime) { + configuration::error_cnt err; + configuration::applier::host hst_aply; + configuration::applier::service svc_aply; + configuration::applier::command cmd_aply; + configuration::Service svc; + configuration::service_helper svc_hlp(&svc); + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(1); + + svc.set_host_name("test_host"); + svc.set_service_description("test_description"); + svc.set_service_id(3); + + cmd.set_command_line("/usr/bin/echo 1"); + cmd_aply.add_object(cmd); + + hst.set_check_command("cmd"); + svc.set_check_command("cmd"); + + hst_aply.add_object(hst); + + // We fake here the expand_object on configuration::service + svc.set_host_id(1); + + svc_aply.add_object(svc); + + hst_aply.expand_objects(pb_config); + svc_aply.expand_objects(pb_config); + + hst_aply.resolve_object(hst, err); + svc_aply.resolve_object(svc, err); + + set_time(20000); + time_t now = time(nullptr); + + std::string str{"test_host;test_description;1;|"}; + + testing::internal::CaptureStdout(); + cmd_process_service_check_result(CMD_PROCESS_SERVICE_CHECK_RESULT, now, + const_cast(str.c_str())); + checks::checker::instance().reap(); + + std::string const& out{testing::internal::GetCapturedStdout()}; + + ASSERT_NE(out.find("PASSIVE SERVICE CHECK"), std::string::npos); +} + +TEST_F(ServiceExternalCommand, AddServiceDowntimeByHostIpAddress) { + configuration::applier::host hst_aply; + configuration::applier::service svc_aply; + configuration::applier::command cmd_aply; + configuration::Service svc; + configuration::service_helper svc_hlp(&svc); + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.3"); + hst.set_host_id(1); + + svc.set_host_name("test_host"); + svc.set_service_description("test_description"); + svc.set_service_id(3); + + cmd.set_command_line("/usr/bin/echo 1"); + cmd_aply.add_object(cmd); + + hst.set_check_command("cmd"); + svc.set_check_command("cmd"); + + hst_aply.add_object(hst); + + // We fake here the expand_object on configuration::service + svc.set_host_id(1); + + svc_aply.add_object(svc); + + hst_aply.expand_objects(pb_config); + svc_aply.expand_objects(pb_config); + + configuration::error_cnt err; + hst_aply.resolve_object(hst, err); + svc_aply.resolve_object(svc, err); + + set_time(20000); + time_t now = time(nullptr); + + std::string str{"127.0.0.3;test_description;1;|"}; + + testing::internal::CaptureStdout(); + cmd_process_service_check_result(CMD_PROCESS_SERVICE_CHECK_RESULT, now, + const_cast(str.c_str())); + checks::checker::instance().reap(); + + std::string const& out{testing::internal::GetCapturedStdout()}; + + ASSERT_NE(out.find("PASSIVE SERVICE CHECK"), std::string::npos); +} + +TEST_F(ServiceExternalCommand, AddServiceComment) { + configuration::applier::host hst_aply; + configuration::applier::service svc_aply; + configuration::applier::command cmd_aply; + configuration::Service svc; + configuration::service_helper svc_hlp(&svc); + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(1); + + svc.set_host_name("test_host"); + svc.set_service_description("test_description"); + svc.set_service_id(3); + + cmd.set_command_line("/usr/bin/echo 1"); + cmd_aply.add_object(cmd); + + hst.set_check_command("cmd"); + svc.set_check_command("cmd"); + + hst_aply.add_object(hst); + + // We fake here the expand_object on configuration::service + svc.set_host_id(1); + + svc_aply.add_object(svc); + + hst_aply.expand_objects(pb_config); + svc_aply.expand_objects(pb_config); + + configuration::error_cnt err; + hst_aply.resolve_object(hst, err); + svc_aply.resolve_object(svc, err); + + std::string cmd_com1{ + "test_host;test_description;1;user;this is a first comment"}; + std::string cmd_com2{ + "test_host;test_description;1;user;this is a second comment"}; + std::string cmd_com3{ + "test_host;test_description;1;user;this is a third comment"}; + std::string cmd_com4{ + "test_host;test_description;1;user;this is a fourth comment"}; + std::string cmd_del{"1"}; + std::string cmd_del_last{"5"}; + std::string cmd_del_all{"test_host;test_description"}; + + set_time(20000); + time_t now = time(nullptr); + + cmd_add_comment(CMD_ADD_SVC_COMMENT, now, + const_cast(cmd_com1.c_str())); + ASSERT_EQ(comment::comments.size(), 1u); + cmd_add_comment(CMD_ADD_SVC_COMMENT, now, + const_cast(cmd_com2.c_str())); + ASSERT_EQ(comment::comments.size(), 2u); + cmd_add_comment(CMD_ADD_SVC_COMMENT, now, + const_cast(cmd_com3.c_str())); + ASSERT_EQ(comment::comments.size(), 3u); + cmd_add_comment(CMD_ADD_SVC_COMMENT, now, + const_cast(cmd_com4.c_str())); + ASSERT_EQ(comment::comments.size(), 4u); + cmd_delete_comment(CMD_ADD_SVC_COMMENT, const_cast(cmd_del.c_str())); + ASSERT_EQ(comment::comments.size(), 3u); + cmd_delete_all_comments(CMD_DEL_ALL_SVC_COMMENTS, + const_cast(cmd_del_all.c_str())); + ASSERT_EQ(comment::comments.size(), 0u); +} diff --git a/engine/tests/helper.cc b/engine/tests/helper.cc index 6a9782c5bda..0a2abb48f4c 100644 --- a/engine/tests/helper.cc +++ b/engine/tests/helper.cc @@ -31,9 +31,14 @@ using namespace com::centreon::engine; using com::centreon::common::log_v2::log_v2; using log_v2_config = com::centreon::common::log_v2::config; +#ifdef LEGACY_CONF extern configuration::state* config; +#else +extern configuration::State pb_config; +#endif -void init_config_state(void) { +#ifdef LEGACY_CONF +void init_config_state() { if (config == nullptr) config = new configuration::state; @@ -51,10 +56,36 @@ void init_config_state(void) { checks::checker::init(true); } +#else +void init_config_state() { + /* Cleanup */ + pb_config.Clear(); + + configuration::state_helper cfg_hlp(&pb_config); + pb_config.set_log_file_line(true); + pb_config.set_log_file(""); + + log_v2_config log_conf("engine-tests", + log_v2_config::logger_type::LOGGER_STDOUT, + pb_config.log_flush_period(), pb_config.log_pid(), + pb_config.log_file_line()); + + log_v2::instance().apply(log_conf); + + // Hack to instanciate the logger. + configuration::applier::logging::instance().apply(pb_config); + + checks::checker::init(true); +} +#endif void deinit_config_state(void) { +#ifdef LEGACY_CONF delete config; config = nullptr; +#else + pb_config.Clear(); +#endif configuration::applier::state::instance().clear(); checks::checker::deinit(); diff --git a/engine/tests/helper.hh b/engine/tests/helper.hh index 208531351ab..efc07c6aa11 100644 --- a/engine/tests/helper.hh +++ b/engine/tests/helper.hh @@ -19,9 +19,16 @@ #ifndef CENTREON_ENGINE_TESTS_HELPER_HH_ #define CENTREON_ENGINE_TESTS_HELPER_HH_ -#include +#include "com/centreon/engine/globals.hh" +#ifdef LEGACY_CONF +#include "common/engine_legacy_conf/state.hh" +#endif +#ifdef LEGACY_CONF extern com::centreon::engine::configuration::state* config; +#else +extern com::centreon::engine::configuration::State pb_config; +#endif void init_config_state(void); void deinit_config_state(void); diff --git a/engine/tests/loop/loop.cc b/engine/tests/loop/loop.cc index 6b455818173..8a2a531952e 100644 --- a/engine/tests/loop/loop.cc +++ b/engine/tests/loop/loop.cc @@ -18,9 +18,7 @@ */ #include "com/centreon/engine/events/loop.hh" - #include - #include "../test_engine.hh" #include "../timeperiod/utils.hh" #include "com/centreon/engine/checks/checker.hh" @@ -29,8 +27,10 @@ #include "com/centreon/engine/configuration/applier/service.hh" #include "com/centreon/engine/exceptions/error.hh" #include "com/centreon/engine/serviceescalation.hh" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/host.hh" #include "common/engine_legacy_conf/service.hh" +#endif #include "helper.hh" using namespace com::centreon; @@ -42,19 +42,35 @@ class LoopTest : public TestEngine { void SetUp() override { error_cnt err; init_config_state(); + events::loop::instance().clear(); configuration::applier::contact ct_aply; +#ifdef LEGACY_CONF configuration::contact ctct{new_configuration_contact("admin", true)}; ct_aply.add_object(ctct); ct_aply.expand_objects(*config); +#else + configuration::Contact ctct{new_pb_configuration_contact("admin", true)}; + ct_aply.add_object(ctct); + ct_aply.expand_objects(pb_config); +#endif ct_aply.resolve_object(ctct, err); +#ifdef LEGACY_CONF configuration::host hst{new_configuration_host("test_host", "admin")}; +#else + configuration::Host hst{new_pb_configuration_host("test_host", "admin")}; +#endif configuration::applier::host hst_aply; hst_aply.add_object(hst); +#ifdef LEGACY_CONF configuration::service svc{ new_configuration_service("test_host", "test_svc", "admin")}; +#else + configuration::Service svc{ + new_pb_configuration_service("test_host", "test_svc", "admin")}; +#endif configuration::applier::service svc_aply; svc_aply.add_object(svc); diff --git a/engine/tests/macros/pbmacro.cc b/engine/tests/macros/pbmacro.cc new file mode 100644 index 00000000000..bd4435349eb --- /dev/null +++ b/engine/tests/macros/pbmacro.cc @@ -0,0 +1,1005 @@ +/* + * Copyright 2019 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include +#include +#include "../helper.hh" +#include "../test_engine.hh" +#include "../timeperiod/utils.hh" +#include "com/centreon/engine/checks/checker.hh" +#include "com/centreon/engine/commands/commands.hh" +#include "com/centreon/engine/configuration/applier/command.hh" +#include "com/centreon/engine/configuration/applier/contact.hh" +#include "com/centreon/engine/configuration/applier/contactgroup.hh" +#include "com/centreon/engine/configuration/applier/host.hh" +#include "com/centreon/engine/configuration/applier/hostgroup.hh" +#include "com/centreon/engine/configuration/applier/service.hh" +#include "com/centreon/engine/configuration/applier/serviceescalation.hh" +#include "com/centreon/engine/configuration/applier/servicegroup.hh" +#include "com/centreon/engine/configuration/applier/state.hh" +#include "com/centreon/engine/configuration/applier/timeperiod.hh" +#include "com/centreon/engine/globals.hh" +#include "com/centreon/engine/hostescalation.hh" +#include "com/centreon/engine/macros.hh" +#include "com/centreon/engine/macros/grab_host.hh" +#include "com/centreon/engine/macros/process.hh" +#include "com/centreon/engine/timeperiod.hh" +#include "common/engine_conf/contact_helper.hh" +#include "common/engine_conf/host_helper.hh" +#include "common/engine_conf/message_helper.hh" +#include "common/engine_conf/parser.hh" +#include "common/engine_conf/service_helper.hh" +#include "common/engine_conf/state.pb.h" +#include "common/engine_conf/timeperiod_helper.hh" + +using namespace com::centreon; +using namespace com::centreon::engine; + +class Macro : public TestEngine { + public: + void SetUp() override { + init_config_state(); + _tp = _creator.new_timeperiod(); + for (int i(0); i < 7; ++i) + _creator.new_timerange(0, 0, 24, 0, i); + _now = strtotimet("2016-11-24 08:00:00"); + set_time(_now); + } + + void TearDown() override { + _host.reset(); + _host2.reset(); + _host3.reset(); + _svc.reset(); + deinit_config_state(); + } + + protected: + std::shared_ptr _host, _host2, _host3; + std::shared_ptr _svc; + timeperiod_creator _creator; + time_t _now; + timeperiod* _tp; +}; + +// Given host configuration without host_id +// Then the applier add_object throws an exception. +TEST_F(Macro, PbPollerName) { + configuration::parser parser; + configuration::State st; + + std::remove("/tmp/test-config.cfg"); + + std::ofstream ofs("/tmp/test-config.cfg"); + ofs << "poller_name=poller-test" << std::endl; + ofs << "log_file=" << std::endl; + ofs.close(); + + configuration::error_cnt err; + parser.parse("/tmp/test-config.cfg", &st, err); + configuration::applier::state::instance().apply(st, err); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$POLLERNAME$", out, 0); + ASSERT_EQ(out, "poller-test"); +} + +TEST_F(Macro, PbPollerId) { + configuration::parser parser; + configuration::State st; + + std::remove("/tmp/test-config.cfg"); + + std::ofstream ofs("/tmp/test-config.cfg"); + ofs << "poller_id=42" << std::endl; + ofs << "log_file=" << std::endl; + ofs.close(); + + configuration::error_cnt err; + parser.parse("/tmp/test-config.cfg", &st, err); + configuration::applier::state::instance().apply(st, err); + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$POLLERID$", out, 0); + ASSERT_EQ(out, "42"); +} + +TEST_F(Macro, PbLongDateTime) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$LONGDATETIME:test_host$", out, 0); + ASSERT_EQ(out, "Tue Nov 5 01:53:20 CET 1985"); +} + +TEST_F(Macro, PbShortDateTime) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$SHORTDATETIME:test_host$", out, 0); + ASSERT_EQ(out, "11-05-1985 01:53:20"); +} + +TEST_F(Macro, PbDate) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$DATE:test_host$", out, 0); + ASSERT_EQ(out, "11-05-1985"); +} + +TEST_F(Macro, PbTime) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$TIME:test_host$", out, 0); + ASSERT_EQ(out, "01:53:20"); +} + +TEST_F(Macro, PbTimeT) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$TIMET:test_host$", out, 0); + ASSERT_EQ(out, "500000000"); +} + +TEST_F(Macro, PbContactName) { + configuration::applier::host hst_aply; + configuration::applier::contact cnt_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + configuration::Contact cnt; + configuration::contact_helper cnt_hlp(&cnt); + cnt.set_contact_name("user"); + cnt.set_email("contact@centreon.com"); + cnt.set_pager("0473729383"); + cnt_aply.add_object(cnt); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + fill_string_group(hst.mutable_contacts(), "user"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$CONTACTNAME:user$", out, 1); + ASSERT_EQ(out, "user"); +} + +TEST_F(Macro, PbContactAlias) { + configuration::applier::host hst_aply; + configuration::applier::contact cnt_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + configuration::Contact cnt; + configuration::contact_helper cnt_hlp(&cnt); + cnt.set_contact_name("user"); + cnt.set_email("contact@centreon.com"); + cnt.set_pager("0473729383"); + cnt_aply.add_object(cnt); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + fill_string_group(hst.mutable_contacts(), "user"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$CONTACTALIAS:user$", out, 1); + ASSERT_EQ(out, "user"); +} + +TEST_F(Macro, PbContactEmail) { + configuration::applier::host hst_aply; + configuration::applier::contact cnt_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + configuration::Contact cnt; + configuration::contact_helper cnt_hlp(&cnt); + cnt.set_contact_name("user"); + cnt.set_email("contact@centreon.com"); + cnt.set_pager("0473729383"); + cnt_aply.add_object(cnt); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + fill_string_group(hst.mutable_contacts(), "user"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$CONTACTEMAIL:user$", out, 1); + ASSERT_EQ(out, "contact@centreon.com"); +} + +TEST_F(Macro, PbContactPager) { + configuration::applier::host hst_aply; + configuration::applier::contact cnt_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + configuration::Contact cnt; + configuration::contact_helper cnt_hlp(&cnt); + cnt.set_contact_name("user"); + cnt.set_email("contact@centreon.com"); + cnt.set_pager("0473729383"); + cnt_aply.add_object(cnt); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + fill_string_group(hst.mutable_contacts(), "user"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$CONTACTPAGER:user$", out, 1); + ASSERT_EQ(out, "0473729383"); +} + +TEST_F(Macro, PbAdminEmail) { + configuration::parser parser; + configuration::State st; + + std::remove("/tmp/test-config.cfg"); + + std::ofstream ofs("/tmp/test-config.cfg"); + ofs << "admin_email=contactadmin@centreon.com" << std::endl; + ofs << "log_file=" << std::endl; + ofs.close(); + + configuration::error_cnt err; + parser.parse("/tmp/test-config.cfg", &st, err); + configuration::applier::state::instance().apply(st, err); + + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$ADMINEMAIL:test_host$", out, 1); + ASSERT_EQ(out, "contactadmin@centreon.com"); +} + +TEST_F(Macro, PbAdminPager) { + configuration::parser parser; + configuration::State st; + + std::remove("/tmp/test-config.cfg"); + + std::ofstream ofs("/tmp/test-config.cfg"); + ofs << "admin_pager=04737293866" << std::endl; + ofs << "log_file=" << std::endl; + ofs.close(); + + configuration::error_cnt err; + parser.parse("/tmp/test-config.cfg", &st, err); + configuration::applier::state::instance().apply(st, err); + + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$ADMINPAGER:test_host$", out, 1); + ASSERT_EQ(out, "04737293866"); +} + +TEST_F(Macro, PbMainConfigFile) { + configuration::parser parser; + configuration::State st; + + std::remove("/tmp/test-config.cfg"); + + std::ofstream ofs("/tmp/test-config.cfg"); + ofs << "log_file=" << std::endl; + ofs.close(); + + configuration::error_cnt err; + parser.parse("/tmp/test-config.cfg", &st, err); + configuration::applier::state::instance().apply(st, err); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$MAINCONFIGFILE:test_host$", out, 1); + ASSERT_EQ(out, "/tmp/test-config.cfg"); +} + +TEST_F(Macro, PbStatusDataFile) { + configuration::parser parser; + configuration::State st; + + std::remove("/tmp/test-config.cfg"); + + std::ofstream ofs("/tmp/test-config.cfg"); + ofs << "status_file=/usr/local/var/status.dat" << std::endl; + ofs << "log_file=" << std::endl; + ofs.close(); + + configuration::error_cnt err; + parser.parse("/tmp/test-config.cfg", &st, err); + configuration::applier::state::instance().apply(st, err); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$STATUSDATAFILE:test_host$", out, 1); + ASSERT_EQ(out, "/usr/local/var/status.dat"); +} + +TEST_F(Macro, PbRetentionDataFile) { + configuration::parser parser; + configuration::State st; + + std::remove("/tmp/test-config.cfg"); + + std::ofstream ofs("/tmp/test-config.cfg"); + ofs << "state_retention_file=/var/log/centreon-engine/retention.dat" + << std::endl; + ofs << "log_file=" << std::endl; + ofs.close(); + + configuration::error_cnt err; + parser.parse("/tmp/test-config.cfg", &st, err); + configuration::applier::state::instance().apply(st, err); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$RETENTIONDATAFILE:test_host$", out, 1); + ASSERT_EQ(out, "/var/log/centreon-engine/retention.dat"); +} + +TEST_F(Macro, PbTempFile) { + configuration::parser parser; + configuration::State st; + + std::remove("/tmp/test-config.cfg"); + + std::ofstream ofs("/tmp/test-config.cfg"); + ofs << "log_file=" << std::endl; + ofs.close(); + + configuration::error_cnt err; + parser.parse("/tmp/test-config.cfg", &st, err); + configuration::applier::state::instance().apply(st, err); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$TEMPFILE:test_host$", out, 1); + ASSERT_EQ(out, "/tmp/centengine.tmp"); +} + +TEST_F(Macro, PbLogFile) { + configuration::parser parser; + configuration::State st; + + std::remove("/tmp/test-config.cfg"); + + std::ofstream ofs("/tmp/test-config.cfg"); + ofs << "log_file=/tmp/centengine.log" << std::endl; + ofs.close(); + + configuration::error_cnt err; + parser.parse("/tmp/test-config.cfg", &st, err); + configuration::applier::state::instance().apply(st, err); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$LOGFILE:test_host$", out, 1); + ASSERT_EQ(out, "/tmp/centengine.log"); +} + +TEST_F(Macro, PbCommandFile) { + configuration::parser parser; + configuration::State st; + + std::remove("/tmp/test-config.cfg"); + + std::ofstream ofs("/tmp/test-config.cfg"); + ofs << "command_file=/usr/local/var/rw/centengine.cmd" << std::endl; + ofs << "log_file=" << std::endl; + ofs.close(); + + configuration::error_cnt err; + parser.parse("/tmp/test-config.cfg", &st, err); + configuration::applier::state::instance().apply(st, err); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$COMMANDFILE:test_host$", out, 1); + ASSERT_EQ(out, "/usr/local/var/rw/centengine.cmd"); +} + +TEST_F(Macro, PbTempPath) { + configuration::parser parser; + configuration::State st; + + std::remove("/tmp/test-config.cfg"); + + std::ofstream ofs("/tmp/test-config.cfg"); + ofs << "log_file=" << std::endl; + ofs.close(); + + configuration::error_cnt err; + parser.parse("/tmp/test-config.cfg", &st, err); + configuration::applier::state::instance().apply(st, err); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$TEMPPATH$", out, 0); + ASSERT_EQ(out, "/tmp"); +} + +TEST_F(Macro, PbContactGroupName) { + configuration::applier::contact ct_aply; + configuration::Contact ctct; + configuration::contact_helper ctct_hlp(&ctct); + fill_pb_configuration_contact(&ctct_hlp, "test_contact", true); + ct_aply.add_object(ctct); + ct_aply.expand_objects(pb_config); + configuration::error_cnt err; + ct_aply.resolve_object(ctct, err); + + configuration::applier::contactgroup cg_aply; + configuration::Contactgroup cg; + configuration::contactgroup_helper cg_hlp(&cg); + fill_pb_configuration_contactgroup(&cg_hlp, "test_cg", "test_contact"); + cg_aply.add_object(cg); + cg_aply.expand_objects(pb_config); + cg_aply.resolve_object(cg, err); + + init_macros(); + int now{500000000}; + set_time(now); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$CONTACTGROUPNAME:test_contact$", out, 1); + ASSERT_EQ(out, "test_cg"); +} + +TEST_F(Macro, PbContactGroupAlias) { + configuration::applier::contact ct_aply; + configuration::Contact ctct; + configuration::contact_helper ctct_hlp(&ctct); + fill_pb_configuration_contact(&ctct_hlp, "test_contact", true); + ct_aply.add_object(ctct); + ct_aply.expand_objects(pb_config); + configuration::error_cnt err; + ct_aply.resolve_object(ctct, err); + + configuration::applier::contactgroup cg_aply; + configuration::Contactgroup cg; + configuration::contactgroup_helper cg_hlp(&cg); + fill_pb_configuration_contactgroup(&cg_hlp, "test_cg", "test_contact"); + cg_aply.add_object(cg); + cg_aply.expand_objects(pb_config); + cg_aply.resolve_object(cg, err); + init_macros(); + int now{500000000}; + set_time(now); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$CONTACTGROUPALIAS:test_cg$", out, 1); + ASSERT_EQ(out, "test_cg"); +} + +TEST_F(Macro, PbContactGroupMembers) { + configuration::applier::contact ct_aply; + configuration::Contact ctct; + configuration::contact_helper ctct_hlp(&ctct); + fill_pb_configuration_contact(&ctct_hlp, "test_contact", true); + ct_aply.add_object(ctct); + ct_aply.expand_objects(pb_config); + configuration::error_cnt err; + ct_aply.resolve_object(ctct, err); + + configuration::applier::contactgroup cg_aply; + configuration::Contactgroup cg; + configuration::contactgroup_helper cg_hlp(&cg); + fill_pb_configuration_contactgroup(&cg_hlp, "test_cg", "test_contact"); + cg_aply.add_object(cg); + cg_aply.expand_objects(pb_config); + cg_aply.resolve_object(cg, err); + init_macros(); + int now{500000000}; + set_time(now); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$CONTACTGROUPMEMBERS:test_cg$", out, 1); + ASSERT_EQ(out, "test_contact"); +} + +TEST_F(Macro, PbContactGroupNames) { + configuration::applier::contact ct_aply; + configuration::Contact ctct; + configuration::contact_helper ctct_hlp(&ctct); + fill_pb_configuration_contact(&ctct_hlp, "test_contact", true); + ct_aply.add_object(ctct); + ct_aply.expand_objects(pb_config); + configuration::error_cnt err; + ct_aply.resolve_object(ctct, err); + + configuration::applier::contactgroup cg_aply; + configuration::Contactgroup cg; + configuration::contactgroup_helper cg_hlp(&cg); + fill_pb_configuration_contactgroup(&cg_hlp, "test_cg", "test_contact"); + cg_aply.add_object(cg); + cg_aply.expand_objects(pb_config); + cg_aply.resolve_object(cg, err); + init_macros(); + int now{500000000}; + set_time(now); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$CONTACTGROUPNAMES:test_contact$", out, 1); + ASSERT_EQ(out, "test_cg"); +} + +TEST_F(Macro, PbNotificationRecipients) { + init_macros(); + configuration::applier::contact ct_aply; + configuration::Contact ctct; + configuration::contact_helper ctct_hlp(&ctct); + fill_pb_configuration_contact(&ctct_hlp, "admin", true); + ct_aply.add_object(ctct); + configuration::Contact ctct1; + configuration::contact_helper ctct1_hlp(&ctct1); + fill_pb_configuration_contact(&ctct1_hlp, "admin1", false, "c,r"); + ct_aply.add_object(ctct1); + ct_aply.expand_objects(pb_config); + configuration::error_cnt err; + ct_aply.resolve_object(ctct, err); + ct_aply.resolve_object(ctct1, err); + configuration::Contact ctct2; + configuration::contact_helper ctct2_hlp(&ctct2); + fill_pb_configuration_contact(&ctct2_hlp, "test_contact", false); + ct_aply.add_object(ctct2); + ct_aply.expand_objects(pb_config); + ct_aply.resolve_object(ctct2, err); + + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + fill_pb_configuration_host(&hst_hlp, "test_host", "admin"); + configuration::applier::host hst_aply; + hst_aply.add_object(hst); + + configuration::Service svc; + configuration::service_helper svc_hlp(&svc); + fill_pb_configuration_service(&svc_hlp, "test_host", "test_svc", + "admin,admin1"); + configuration::applier::service svc_aply; + svc_aply.add_object(svc); + + hst_aply.resolve_object(hst, err); + svc_aply.resolve_object(svc, err); + + host_map const& hm{engine::host::hosts}; + _host3 = hm.begin()->second; + _host3->set_current_state(engine::host::state_up); + _host3->set_state_type(checkable::hard); + _host3->set_acknowledgement(AckType::NONE); + _host3->set_notify_on(static_cast(-1)); + + service_map const& sm{engine::service::services}; + _svc = sm.begin()->second; + _svc->set_current_state(engine::service::state_ok); + _svc->set_state_type(checkable::hard); + _svc->set_acknowledgement(AckType::NONE); + _svc->set_notify_on(static_cast(-1)); + + nagios_macros* mac(get_global_macros()); + + ASSERT_EQ(_svc->notify(notifier::reason_normal, "test_contact", + "test_comment", notifier::notification_option_forced), + OK); + + std::string out; + process_macros_r(mac, "$NOTIFICATIONRECIPIENTS:test_host:test_svc$", out, 1); + ASSERT_TRUE(out == "admin,admin1" || out == "admin1,admin"); +} + +TEST_F(Macro, PbNotificationAuthor) { + init_macros(); + configuration::applier::contact ct_aply; + configuration::Contact ctct; + configuration::contact_helper ctct_hlp(&ctct); + fill_pb_configuration_contact(&ctct_hlp, "admin", true); + ct_aply.add_object(ctct); + configuration::Contact ctct1; + configuration::contact_helper ctct1_hlp(&ctct1); + fill_pb_configuration_contact(&ctct1_hlp, "admin1", false, "c,r"); + ct_aply.add_object(ctct1); + ct_aply.expand_objects(pb_config); + configuration::error_cnt err; + ct_aply.resolve_object(ctct, err); + ct_aply.resolve_object(ctct1, err); + configuration::Contact ctct2; + configuration::contact_helper ctct2_hlp(&ctct2); + fill_pb_configuration_contact(&ctct2_hlp, "test_contact", false); + ct_aply.add_object(ctct2); + ct_aply.expand_objects(pb_config); + ct_aply.resolve_object(ctct2, err); + + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + fill_pb_configuration_host(&hst_hlp, "test_host", "admin"); + configuration::applier::host hst_aply; + hst_aply.add_object(hst); + + configuration::Service svc; + configuration::service_helper svc_hlp(&svc); + fill_pb_configuration_service(&svc_hlp, "test_host", "test_svc", + "admin,admin1"); + configuration::applier::service svc_aply; + svc_aply.add_object(svc); + + hst_aply.resolve_object(hst, err); + svc_aply.resolve_object(svc, err); + + host_map const& hm{engine::host::hosts}; + _host3 = hm.begin()->second; + _host3->set_current_state(engine::host::state_up); + _host3->set_state_type(checkable::hard); + _host3->set_acknowledgement(AckType::NONE); + _host3->set_notify_on(static_cast(-1)); + + service_map const& sm{engine::service::services}; + _svc = sm.begin()->second; + _svc->set_current_state(engine::service::state_ok); + _svc->set_state_type(checkable::hard); + _svc->set_acknowledgement(AckType::NONE); + _svc->set_notify_on(static_cast(-1)); + + nagios_macros* mac(get_global_macros()); + + ASSERT_EQ(_svc->notify(notifier::reason_normal, "test_contact", + "test_comment", notifier::notification_option_forced), + OK); + + std::string out; + process_macros_r(mac, "$NOTIFICATIONAUTHOR$", out, 1); + ASSERT_EQ(out, "test_contact"); +} + +TEST_F(Macro, PbNotificationAuthorName) { + init_macros(); + configuration::applier::contact ct_aply; + configuration::Contact ctct; + configuration::contact_helper ctct_hlp(&ctct); + fill_pb_configuration_contact(&ctct_hlp, "admin", true); + ct_aply.add_object(ctct); + configuration::Contact ctct1; + configuration::contact_helper ctct1_hlp(&ctct1); + fill_pb_configuration_contact(&ctct1_hlp, "admin1", false, "c,r"); + ct_aply.add_object(ctct1); + ct_aply.expand_objects(pb_config); + configuration::error_cnt err; + ct_aply.resolve_object(ctct, err); + ct_aply.resolve_object(ctct1, err); + configuration::Contact ctct2; + configuration::contact_helper ctct2_hlp(&ctct2); + fill_pb_configuration_contact(&ctct2_hlp, "test_contact", false); + ct_aply.add_object(ctct2); + ct_aply.expand_objects(pb_config); + ct_aply.resolve_object(ctct2, err); + + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + fill_pb_configuration_host(&hst_hlp, "test_host", "admin"); + configuration::applier::host hst_aply; + hst_aply.add_object(hst); + + configuration::Service svc; + configuration::service_helper svc_hlp(&svc); + fill_pb_configuration_service(&svc_hlp, "test_host", "test_svc", + "admin,admin1"); + configuration::applier::service svc_aply; + svc_aply.add_object(svc); + + hst_aply.resolve_object(hst, err); + svc_aply.resolve_object(svc, err); + + host_map const& hm{engine::host::hosts}; + _host3 = hm.begin()->second; + _host3->set_current_state(engine::host::state_up); + _host3->set_state_type(checkable::hard); + _host3->set_acknowledgement(AckType::NONE); + _host3->set_notify_on(static_cast(-1)); + + service_map const& sm{engine::service::services}; + _svc = sm.begin()->second; + _svc->set_current_state(engine::service::state_ok); + _svc->set_state_type(checkable::hard); + _svc->set_acknowledgement(AckType::NONE); + _svc->set_notify_on(static_cast(-1)); + + nagios_macros* mac(get_global_macros()); + + ASSERT_EQ(_svc->notify(notifier::reason_normal, "test_contact", + "test_comment", notifier::notification_option_forced), + OK); + std::string out; + process_macros_r(mac, "$NOTIFICATIONAUTHORNAME:test_host:test_svc$", out, 1); + ASSERT_EQ(out, "test_contact"); +} + +TEST_F(Macro, PbNotificationAuthorAlias) { + init_macros(); + configuration::applier::contact ct_aply; + configuration::Contact ctct{new_pb_configuration_contact("admin", true)}; + ct_aply.add_object(ctct); + configuration::Contact ctct1{ + new_pb_configuration_contact("admin1", false, "c,r")}; + ct_aply.add_object(ctct1); + ct_aply.expand_objects(pb_config); + configuration::error_cnt err; + ct_aply.resolve_object(ctct, err); + ct_aply.resolve_object(ctct1, err); + configuration::Contact ctct2{ + new_pb_configuration_contact("test_contact", false)}; + ct_aply.add_object(ctct2); + ct_aply.expand_objects(pb_config); + ct_aply.resolve_object(ctct2, err); + + configuration::Host hst{new_pb_configuration_host("test_host", "admin")}; + configuration::applier::host hst_aply; + hst_aply.add_object(hst); + + configuration::Service svc{ + new_pb_configuration_service("test_host", "test_svc", "admin,admin1")}; + configuration::applier::service svc_aply; + svc_aply.add_object(svc); + + hst_aply.resolve_object(hst, err); + svc_aply.resolve_object(svc, err); + + host_map const& hm{engine::host::hosts}; + _host3 = hm.begin()->second; + _host3->set_current_state(engine::host::state_up); + _host3->set_state_type(checkable::hard); + _host3->set_acknowledgement(AckType::NONE); + _host3->set_notify_on(static_cast(-1)); + + service_map const& sm{engine::service::services}; + _svc = sm.begin()->second; + _svc->set_current_state(engine::service::state_ok); + _svc->set_state_type(checkable::hard); + _svc->set_acknowledgement(AckType::NONE); + _svc->set_notify_on(static_cast(-1)); + + nagios_macros* mac(get_global_macros()); + + ASSERT_EQ(_svc->notify(notifier::reason_normal, "test_contact", + "test_comment", notifier::notification_option_forced), + OK); + std::string out; + process_macros_r(mac, "$NOTIFICATIONAUTHORALIAS:test_host:test_svc$", out, 1); + ASSERT_EQ(out, "test_contact"); +} + +TEST_F(Macro, PbNotificationComment) { + init_macros(); + configuration::applier::contact ct_aply; + configuration::Contact ctct{new_pb_configuration_contact("admin", true)}; + ct_aply.add_object(ctct); + configuration::Contact ctct1{ + new_pb_configuration_contact("admin1", false, "c,r")}; + ct_aply.add_object(ctct1); + ct_aply.expand_objects(pb_config); + configuration::error_cnt err; + ct_aply.resolve_object(ctct, err); + ct_aply.resolve_object(ctct1, err); + configuration::Contact ctct2{ + new_pb_configuration_contact("test_contact", false)}; + ct_aply.add_object(ctct2); + ct_aply.expand_objects(pb_config); + ct_aply.resolve_object(ctct2, err); + + configuration::Host hst{new_pb_configuration_host("test_host", "admin")}; + configuration::applier::host hst_aply; + hst_aply.add_object(hst); + + configuration::Service svc{ + new_pb_configuration_service("test_host", "test_svc", "admin,admin1")}; + configuration::applier::service svc_aply; + svc_aply.add_object(svc); + + hst_aply.resolve_object(hst, err); + svc_aply.resolve_object(svc, err); + + host_map const& hm{engine::host::hosts}; + _host3 = hm.begin()->second; + _host3->set_current_state(engine::host::state_up); + _host3->set_state_type(checkable::hard); + _host3->set_acknowledgement(AckType::NONE); + _host3->set_notify_on(static_cast(-1)); + + service_map const& sm{engine::service::services}; + _svc = sm.begin()->second; + _svc->set_current_state(engine::service::state_ok); + _svc->set_state_type(checkable::hard); + _svc->set_acknowledgement(AckType::NONE); + _svc->set_notify_on(static_cast(-1)); + + nagios_macros* mac(get_global_macros()); + + ASSERT_EQ(_svc->notify(notifier::reason_normal, "test_contact", + "test_comment", notifier::notification_option_forced), + OK); + + std::string out; + process_macros_r(mac, "$NOTIFICATIONCOMMENT$", out, 1); + ASSERT_EQ(out, "test_comment"); +} + +TEST_F(Macro, PbIsValidTime) { + configuration::applier::timeperiod time_aply; + configuration::Timeperiod time; + configuration::timeperiod_helper time_hlp(&time); + + time.set_timeperiod_name("test"); + time.set_alias("test"); + time_aply.add_object(time); + + init_macros(); + int now{500000000}; + set_time(now); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$ISVALIDTIME:test$", out, 1); + ASSERT_EQ(out, "0"); +} + +TEST_F(Macro, PbNextValidTime) { + configuration::applier::timeperiod time_aply; + configuration::Timeperiod time; + configuration::timeperiod_helper time_hlp(&time); + + time.set_alias("test"); + time.set_timeperiod_name("test"); + time_hlp.hook("monday", "23:00-24:00"); + time_hlp.hook("tuesday", "23:00-24:00"); + time_hlp.hook("wednesday", "23:00-24:00"); + time_hlp.hook("thursday", "23:00-24:00"); + time_hlp.hook("friday", "23:00-24:00"); + time_hlp.hook("saterday", "23:00-24:00"); + time_hlp.hook("sunday", "23:00-24:00"); + time_aply.add_object(time); + + init_macros(); + int now{500000000}; + set_time(now); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$NEXTVALIDTIME:test$", out, 1); + ASSERT_EQ(out, "23:00:00"); +} + +TEST_F(Macro, PbContactTimeZone) { + configuration::applier::contact cnt_aply; + configuration::Contact cnt; + cnt.set_contact_name("user"); + cnt.set_email("contact@centreon.com"); + cnt.set_pager("0473729383"); + cnt.set_timezone("time_test"); + cnt_aply.add_object(cnt); + + init_macros(); + int now{500000000}; + set_time(now); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$CONTACTTIMEZONE:user$", out, 1); + ASSERT_EQ(out, "time_test"); +} diff --git a/engine/tests/macros/pbmacro_hostname.cc b/engine/tests/macros/pbmacro_hostname.cc new file mode 100644 index 00000000000..fa5c34b01c2 --- /dev/null +++ b/engine/tests/macros/pbmacro_hostname.cc @@ -0,0 +1,1648 @@ +/** + * Copyright 2019 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include +#include +#include "com/centreon/engine/globals.hh" +#ifdef LEGACY_CONF +#include "common/engine_legacy_conf/state.hh" +#else +#include "common/engine_conf/host_helper.hh" +#include "common/engine_conf/hostgroup_helper.hh" +#endif + +#include "../helper.hh" +#include "../test_engine.hh" +#include "../timeperiod/utils.hh" +#include "com/centreon/engine/checks/checker.hh" +#include "com/centreon/engine/commands/commands.hh" +#include "com/centreon/engine/configuration/applier/command.hh" +#include "com/centreon/engine/configuration/applier/contact.hh" +#include "com/centreon/engine/configuration/applier/contactgroup.hh" +#include "com/centreon/engine/configuration/applier/host.hh" +#include "com/centreon/engine/configuration/applier/hostgroup.hh" +#include "com/centreon/engine/configuration/applier/service.hh" +#include "com/centreon/engine/configuration/applier/serviceescalation.hh" +#include "com/centreon/engine/configuration/applier/servicegroup.hh" +#include "com/centreon/engine/configuration/applier/state.hh" +#include "com/centreon/engine/configuration/applier/timeperiod.hh" +#include "com/centreon/engine/hostescalation.hh" +#include "com/centreon/engine/macros.hh" +#include "com/centreon/engine/macros/grab_host.hh" +#include "com/centreon/engine/macros/process.hh" +#include "com/centreon/engine/timeperiod.hh" + +using namespace com::centreon; +using namespace com::centreon::engine; + +class MacroHostname : public TestEngine { + public: + void SetUp() override { + init_config_state(); + _tp = _creator.new_timeperiod(); + for (int i(0); i < 7; ++i) + _creator.new_timerange(0, 0, 24, 0, i); + _now = strtotimet("2016-11-24 08:00:00"); + set_time(_now); + } + + void TearDown() override { + _host.reset(); + _host2.reset(); + _host3.reset(); + _svc.reset(); + deinit_config_state(); + } + + protected: + std::shared_ptr _host, _host2, _host3; + std::shared_ptr _svc; + timeperiod_creator _creator; + time_t _now; + timeperiod* _tp; +}; + +TEST_F(MacroHostname, HostProblemId) { + configuration::applier::host hst_aply, hst_aply2; + configuration::Host hst, hst2; + configuration::host_helper hst_hlp(&hst), hst2_hlp(&hst2); + next_problem_id = 1; + + set_time(50000); + // first host + hst.set_host_name("test_host"); + hst.set_host_id(12); + hst.set_address("127.0.0.1"); + // second host + hst2.set_host_name("test_host2"); + hst2.set_host_id(13); + hst2.set_address("127.0.0.1"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_NO_THROW(hst_aply2.add_object(hst2)); + ASSERT_EQ(2u, host::hosts.size()); + + init_macros(); + _host = host::hosts.find("test_host")->second; + _host->set_current_state(engine::host::state_up); + _host->set_last_hard_state(engine::host::state_up); + _host->set_last_hard_state_change(50000); + _host->set_state_type(checkable::hard); + + _host2 = host::hosts.find("test_host2")->second; + _host2->set_current_state(engine::host::state_up); + _host2->set_last_hard_state(engine::host::state_up); + _host2->set_last_hard_state_change(50000); + _host2->set_state_type(checkable::hard); + + auto fn = [](std::shared_ptr hst, std::string firstcheck, + std::string secondcheck) -> void { + std::string out; + nagios_macros* mac(get_global_macros()); + + for (int i = 0; i < 3; i++) { + // When i == 0, the state_down is soft => no notification + // When i == 1, the state_down is soft => no notification + // When i == 2, the state_down is hard down => notification + set_time(50500 + i * 500); + hst->set_last_state(hst->get_current_state()); + if (notifier::hard == hst->get_state_type()) + hst->set_last_hard_state(hst->get_current_state()); + hst->process_check_result_3x(engine::host::state_down, "The host is down", + CHECK_OPTION_NONE, 0, true, 0); + } + + process_macros_r(mac, fmt::format("$HOSTPROBLEMID:{}$", hst->name()), out, + 0); + ASSERT_EQ(out, firstcheck); + + for (int i = 0; i < 2; i++) { + // When i == 0, the state_up is hard (return to up) => Recovery + // notification When i == 1, the state_up is still here (no change) => no + // notification + set_time(52500 + i * 500); + hst->set_last_state(hst->get_current_state()); + if (notifier::hard == hst->get_state_type()) + hst->set_last_hard_state(hst->get_current_state()); + hst->process_check_result_3x(engine::host::state_up, "The host is up", + CHECK_OPTION_NONE, 0, true, 0); + } + + process_macros_r(mac, "$HOSTPROBLEMID:test_host$", out, 0); + ASSERT_EQ(out, secondcheck); + }; + + fn(_host, "1", "0"); + fn(_host2, "2", "0"); + fn(_host, "3", "0"); +} + +// Given host configuration without host_id +// Then the applier add_object throws an exception. +TEST_F(MacroHostname, TotalHostOk) { + configuration::applier::host hst_aply; + configuration::Service svc; + configuration::service_helper svc_hlp(&svc); + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_host_id(12); + hst.set_address("127.0.0.1"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + init_macros(); + + nagios_macros* mac(get_global_macros()); + std::string out; + host::hosts["test_host"]->set_current_state(host::state_up); + host::hosts["test_host"]->set_has_been_checked(true); + process_macros_r(mac, "$TOTALHOSTSUP$", out, 1); + ASSERT_EQ(out, "1"); +} + +// Given host configuration without host_id +// Then the applier add_object throws an exception. +TEST_F(MacroHostname, TotalHostServicesCritical) { + configuration::applier::host hst_aply; + configuration::Service svc; + configuration::service_helper svc_hlp(&svc); + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_host_id(12); + hst.set_address("127.0.0.1"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + init_macros(); + + nagios_macros* mac(get_global_macros()); + std::string out; + host::hosts["test_host"]->set_current_state(host::state_up); + host::hosts["test_host"]->set_has_been_checked(true); + process_macros_r(mac, "$TOTALHOSTSERVICESCRITICAL:test_host$", out, 1); + ASSERT_EQ(out, "0"); +} + +TEST_F(MacroHostname, HostName) { + init_macros(); + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_host_id(12); + hst.set_address("127.0.0.1"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + std::string out; + nagios_macros* mac(get_global_macros()); + + process_macros_r(mac, "$HOSTNAME:test_host$", out, 0); + ASSERT_EQ(out, "test_host"); +} + +TEST_F(MacroHostname, HostAlias) { + init_macros(); + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_host_id(12); + hst.set_address("127.0.0.1"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + std::string out; + nagios_macros* mac(get_global_macros()); + + process_macros_r(mac, "$HOSTALIAS:test_host$", out, 0); + ASSERT_EQ(out, "test_host"); +} + +TEST_F(MacroHostname, HostAddress) { + init_macros(); + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_host_id(12); + hst.set_address("127.0.0.1"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + std::string out; + nagios_macros* mac(get_global_macros()); + + process_macros_r(mac, "$HOSTADDRESS:test_host$", out, 0); + ASSERT_EQ(out, "127.0.0.1"); +} + +TEST_F(MacroHostname, LastHostCheck) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_host_id(12); + hst.set_address("127.0.0.1"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$LASTHOSTCHECK:test_host$", out, 0); + ASSERT_EQ(out, "0"); +} + +TEST_F(MacroHostname, LastHostStateChange) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_host_id(12); + hst.set_address("127.0.0.1"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$LASTHOSTSTATECHANGE:test_host$", out, 0); + ASSERT_EQ(out, "0"); +} + +TEST_F(MacroHostname, HostOutput) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_host_id(12); + hst.set_address("127.0.0.1"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + host::hosts["test_host"]->set_plugin_output("foo bar!"); + process_macros_r(mac, "$HOSTOUTPUT:test_host$", out, 0); + ASSERT_EQ(out, "foo bar!"); +} + +TEST_F(MacroHostname, HostPerfData) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_host_id(12); + hst.set_address("127.0.0.1"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + host::hosts["test_host"]->set_perf_data("test"); + process_macros_r(mac, "$HOSTPERFDATA:test_host$", out, 0); + ASSERT_EQ(out, "test"); +} + +TEST_F(MacroHostname, HostState) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_host_id(12); + hst.set_address("127.0.0.1"); + hst_hlp.hook("contacts", "user"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + host::hosts["test_host"]->set_current_state(host::state_up); + host::hosts["test_host"]->set_has_been_checked(true); + process_macros_r(mac, "$HOSTSTATE:test_host$", out, 1); + ASSERT_EQ(out, "UP"); +} + +TEST_F(MacroHostname, HostStateID) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_host_id(12); + hst.set_address("127.0.0.1"); + hst_hlp.hook("contacts", "user"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + host::hosts["test_host"]->set_current_state(host::state_down); + host::hosts["test_host"]->set_has_been_checked(true); + process_macros_r(mac, "$HOSTSTATEID:test_host$", out, 1); + ASSERT_EQ(out, "1"); +} + +TEST_F(MacroHostname, HostAttempt) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_host_id(12); + hst.set_address("127.0.0.1"); + hst_hlp.hook("contacts", "user"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + host::hosts["test_host"]->set_current_state(host::state_up); + host::hosts["test_host"]->set_has_been_checked(true); + process_macros_r(mac, "$HOSTATTEMPT:test_host$", out, 1); + ASSERT_EQ(out, "1"); +} + +TEST_F(MacroHostname, HostExecutionTime) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_host_id(12); + hst.set_address("127.0.0.1"); + hst_hlp.hook("contacts", "user"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + init_macros(); + + int now{500000000}; + set_time(now); + + std::string out; + nagios_macros* mac(get_global_macros()); + host::hosts["test_host"]->set_execution_time(10.0); + process_macros_r(mac, "$HOSTEXECUTIONTIME:test_host$", out, 1); + ASSERT_EQ(out, "10.000"); +} + +TEST_F(MacroHostname, HostLatency) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_host_id(12); + hst.set_address("127.0.0.1"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + host::hosts["test_host"]->set_latency(100); + process_macros_r(mac, "$HOSTLATENCY:test_host$", out, 1); + ASSERT_EQ(out, "100.000"); +} + +TEST_F(MacroHostname, HostDuration) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_host_id(12); + hst.set_address("127.0.0.1"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$HOSTDURATION:test_host$", out, 1); + ASSERT_EQ(out, "5787d 0h 53m 20s"); +} + +TEST_F(MacroHostname, HostDurationSec) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_host_id(12); + hst.set_address("127.0.0.1"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$HOSTDURATIONSEC:test_host$", out, 1); + ASSERT_EQ(out, "500000000"); +} + +TEST_F(MacroHostname, HostDownTime) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_host_id(12); + hst.set_address("127.0.0.1"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$HOSTDOWNTIME:test_host$", out, 1); + ASSERT_EQ(out, "0"); +} + +TEST_F(MacroHostname, HostStateType) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_host_id(12); + hst.set_address("127.0.0.1"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$HOSTSTATETYPE:test_host$", out, 1); + ASSERT_EQ(out, "HARD"); +} + +TEST_F(MacroHostname, HostPercentChange) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_host_id(12); + hst.set_address("127.0.0.1"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$HOSTPERCENTCHANGE:test_host$", out, 1); + ASSERT_EQ(out, "0.00"); +} + +TEST_F(MacroHostname, HostGroupName) { + configuration::error_cnt err; + configuration::applier::hostgroup hg_aply; + configuration::applier::host hst_aply; + configuration::Hostgroup hg; + configuration::hostgroup_helper hg_hlp(&hg); + configuration::Host hst_a; + configuration::host_helper hst_a_hlp(&hst_a); + hst_a.set_host_name("a"); + hst_a.set_host_id(1); + hst_a.set_address("127.0.0.1"); + + configuration::Host hst_c; + configuration::host_helper hst_c_hlp(&hst_c); + hst_c.set_host_name("c"); + hst_c.set_host_id(2); + hst_c.set_address("127.0.0.1"); + + hst_aply.add_object(hst_a); + hst_aply.add_object(hst_c); + + hg.set_hostgroup_name("temphg"); + hg_hlp.hook("members", "a,c"); + ASSERT_NO_THROW(hg_aply.add_object(hg)); + + ASSERT_NO_THROW(hst_aply.expand_objects(pb_config)); + ASSERT_NO_THROW(hst_aply.expand_objects(pb_config)); + ASSERT_NO_THROW(hg_aply.expand_objects(pb_config)); + + ASSERT_NO_THROW(hst_aply.resolve_object(hst_a, err)); + ASSERT_NO_THROW(hst_aply.resolve_object(hst_c, err)); + ASSERT_NO_THROW(hg_aply.resolve_object(hg, err)); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$HOSTGROUPNAME:a$", out, 1); + ASSERT_EQ(out, "temphg"); +} + +TEST_F(MacroHostname, HostGroupAlias) { + configuration::applier::hostgroup hg_aply; + configuration::applier::host hst_aply; + configuration::Hostgroup hg; + configuration::hostgroup_helper hg_hlp(&hg); + configuration::Host hst_a; + configuration::host_helper hst_a_hlp(&hst_a); + configuration::Host hst_c; + configuration::host_helper hst_c_hlp(&hst_c); + + hst_a.set_host_name("a"); + hst_a.set_address("127.0.0.1"); + hst_a.set_host_id(1); + + hst_c.set_host_name("c"); + hst_c.set_address("127.0.0.1"); + hst_c.set_host_id(2); + + hst_aply.add_object(hst_a); + hst_aply.add_object(hst_c); + + hg.set_hostgroup_name("temphg"); + hg.set_alias("temphgal"); + hg_hlp.hook("members", "a,c"); + ASSERT_NO_THROW(hg_aply.add_object(hg)); + + ASSERT_NO_THROW(hst_aply.expand_objects(pb_config)); + ASSERT_NO_THROW(hst_aply.expand_objects(pb_config)); + ASSERT_NO_THROW(hg_aply.expand_objects(pb_config)); + + configuration::error_cnt err; + ASSERT_NO_THROW(hst_aply.resolve_object(hst_a, err)); + ASSERT_NO_THROW(hst_aply.resolve_object(hst_c, err)); + ASSERT_NO_THROW(hg_aply.resolve_object(hg, err)); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$HOSTGROUPALIAS:temphg$", out, 1); + ASSERT_EQ(out, "temphgal"); +} + +TEST_F(MacroHostname, LastHostUP) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + host::hosts["test_host"]->set_last_time_up(30); + host::hosts["test_host"]->set_has_been_checked(true); + process_macros_r(mac, "$LASTHOSTUP:test_host$", out, 1); + ASSERT_EQ(out, "30"); +} + +TEST_F(MacroHostname, LastHostDown) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + host::hosts["test_host"]->set_last_time_down(40); + host::hosts["test_host"]->set_has_been_checked(true); + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$LASTHOSTDOWN:test_host$", out, 1); + ASSERT_EQ(out, "40"); +} + +TEST_F(MacroHostname, LastHostUnreachable) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + host::hosts["test_host"]->set_last_time_unreachable(50); + host::hosts["test_host"]->set_has_been_checked(true); + process_macros_r(mac, "$LASTHOSTUNREACHABLE:test_host$", out, 1); + ASSERT_EQ(out, "50"); +} + +TEST_F(MacroHostname, HostCheckCommand) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + + cmd.set_command_line("echo 'output| metric=12;50;75'"); + hst.set_check_command("cmd"); + configuration::applier::command cmd_aply; + cmd_aply.add_object(cmd); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$HOSTCHECKCOMMAND:test_host$", out, 1); + ASSERT_EQ(out, "cmd"); +} + +TEST_F(MacroHostname, HostDisplayName) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$HOSTDISPLAYNAME:test_host$", out, 1); + ASSERT_EQ(out, "test_host"); +} + +TEST_F(MacroHostname, HostActionUrl) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst.set_action_url("test_action_url"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$HOSTACTIONURL:test_host$", out, 1); + ASSERT_EQ(out, "test_action_url"); +} + +TEST_F(MacroHostname, HostNotesUrl) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst.set_notes_url("test_notes_url"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$HOSTNOTESURL:test_host$", out, 1); + ASSERT_EQ(out, "test_notes_url"); +} + +TEST_F(MacroHostname, HostNotes) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst.set_notes("test_notes"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$HOSTNOTES:test_host$", out, 1); + ASSERT_EQ(out, "test_notes"); +} + +TEST_F(MacroHostname, TotalHostsDown) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst.set_notes("test_notes"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + host::hosts["test_host"]->set_current_state(host::state_down); + host::hosts["test_host"]->set_has_been_checked(true); + process_macros_r(mac, "$TOTALHOSTSDOWN:test_host$", out, 1); + ASSERT_EQ(out, "1"); +} + +TEST_F(MacroHostname, TotalHostsUnreachable) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst.set_notes("test_notes"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + host::hosts["test_host"]->set_current_state(host::state_unreachable); + host::hosts["test_host"]->set_has_been_checked(true); + process_macros_r(mac, "$TOTALHOSTSUNREACHABLE:test_host$", out, 1); + ASSERT_EQ(out, "1"); +} + +TEST_F(MacroHostname, TotalHostsDownUnhandled) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst.set_notes("test_notes"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$TOTALHOSTSDOWNUNHANDLED:test_host$", out, 1); + ASSERT_EQ(out, "0"); +} + +TEST_F(MacroHostname, TotalHostsunreachableunhandled) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst.set_notes("test_notes"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$TOTALHOSTSUNREACHABLEUNHANDLED:test_host$", out, 1); + ASSERT_EQ(out, "0"); +} + +TEST_F(MacroHostname, TotalHostProblems) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst.set_notes("test_notes"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$TOTALHOSTPROBLEMS:test_host$", out, 1); + ASSERT_EQ(out, "0"); +} + +TEST_F(MacroHostname, TotalHostProblemsUnhandled) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst.set_notes("test_notes"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$TOTALHOSTPROBLEMSUNHANDLED:test_host$", out, 1); + ASSERT_EQ(out, "0"); +} + +TEST_F(MacroHostname, HostCheckType) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst_hlp.hook("contacts", "user"); + hst.set_notes("test_notes"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + host::hosts["test_host"]->set_current_state(host::state_unreachable); + host::hosts["test_host"]->set_has_been_checked(true); + process_macros_r(mac, "$HOSTCHECKTYPE:test_host$", out, 0); + ASSERT_EQ(out, "ACTIVE"); +} + +TEST_F(MacroHostname, LongHostOutput) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst_hlp.hook("contacts", "user"); + hst.set_notes("test_notes"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + host::hosts["test_host"]->set_long_plugin_output("test_long_output"); + host::hosts["test_host"]->set_has_been_checked(true); + process_macros_r(mac, "$LONGHOSTOUTPUT:test_host$", out, 0); + ASSERT_EQ(out, "test_long_output"); +} + +TEST_F(MacroHostname, HostNotificationNumber) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst_hlp.hook("contacts", "user"); + hst.set_notes("test_notes"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + host::hosts["test_host"]->set_long_plugin_output("test_long_output"); + host::hosts["test_host"]->set_has_been_checked(true); + process_macros_r(mac, "$HOSTNOTIFICATIONNUMBER:test_host$", out, 0); + ASSERT_EQ(out, "0"); +} + +TEST_F(MacroHostname, HostNotificationID) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst_hlp.hook("contacts", "user"); + hst.set_notes("test_notes"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + host::hosts["test_host"]->set_long_plugin_output("test_long_output"); + host::hosts["test_host"]->set_has_been_checked(true); + process_macros_r(mac, "$HOSTNOTIFICATIONID:test_host$", out, 0); + ASSERT_EQ(out, "0"); +} + +TEST_F(MacroHostname, HostEventID) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst_hlp.hook("contacts", "user"); + hst.set_notes("test_notes"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + host::hosts["test_host"]->set_long_plugin_output("test_long_output"); + host::hosts["test_host"]->set_has_been_checked(true); + process_macros_r(mac, "$HOSTEVENTID:test_host$", out, 0); + ASSERT_EQ(out, "0"); +} + +TEST_F(MacroHostname, LastHostEventID) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst_hlp.hook("contacts", "user"); + hst.set_notes("test_notes"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + host::hosts["test_host"]->set_long_plugin_output("test_long_output"); + host::hosts["test_host"]->set_has_been_checked(true); + process_macros_r(mac, "$LASTHOSTEVENTID:test_host$", out, 0); + ASSERT_EQ(out, "0"); +} + +TEST_F(MacroHostname, HostGroupNames) { + configuration::applier::hostgroup hg_aply; + configuration::applier::host hst_aply; + configuration::Hostgroup hg; + configuration::hostgroup_helper hg_hlp(&hg); + configuration::Host hst_a; + configuration::host_helper hst_a_hlp(&hst_a); + configuration::Host hst_c; + configuration::host_helper hst_c_hlp(&hst_c); + + hst_a.set_host_name("a"); + hst_a.set_address("127.0.0.1"); + hst_a.set_host_id(1); + + hst_c.set_host_name("c"); + hst_c.set_address("127.0.0.1"); + hst_c.set_host_id(2); + + hst_aply.add_object(hst_a); + hst_aply.add_object(hst_c); + + hg.set_hostgroup_name("temphg"); + hg_hlp.hook("members", "a,c"); + ASSERT_NO_THROW(hg_aply.add_object(hg)); + + ASSERT_NO_THROW(hst_aply.expand_objects(pb_config)); + ASSERT_NO_THROW(hst_aply.expand_objects(pb_config)); + ASSERT_NO_THROW(hg_aply.expand_objects(pb_config)); + + configuration::error_cnt err; + ASSERT_NO_THROW(hst_aply.resolve_object(hst_a, err)); + ASSERT_NO_THROW(hst_aply.resolve_object(hst_c, err)); + ASSERT_NO_THROW(hg_aply.resolve_object(hg, err)); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$HOSTGROUPNAMES:a$", out, 0); + ASSERT_EQ(out, "temphg"); +} + +TEST_F(MacroHostname, MaxHostAttempts) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst_hlp.hook("contacts", "user"); + hst.set_notes("test_notes"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + host::hosts["test_host"]->set_long_plugin_output("test_long_output"); + host::hosts["test_host"]->set_has_been_checked(true); + process_macros_r(mac, "$MAXHOSTATTEMPTS:test_host$", out, 0); + ASSERT_EQ(out, "3"); +} + +TEST_F(MacroHostname, TotalHostServices) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst_hlp.hook("contacts", "user"); + hst.set_notes("test_notes"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + host::hosts["test_host"]->set_long_plugin_output("test_long_output"); + host::hosts["test_host"]->set_has_been_checked(true); + process_macros_r(mac, "$TOTALHOSTSERVICES:test_host$", out, 0); + ASSERT_EQ(out, "0"); +} + +TEST_F(MacroHostname, TotalHostServicesOK) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst_hlp.hook("contacts", "user"); + hst.set_notes("test_notes"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + host::hosts["test_host"]->set_long_plugin_output("test_long_output"); + host::hosts["test_host"]->set_has_been_checked(true); + process_macros_r(mac, "$TOTALHOSTSERVICESOK:test_host$", out, 0); + ASSERT_EQ(out, "0"); +} + +TEST_F(MacroHostname, TotalHostServicesWarning) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst_hlp.hook("contacts", "user"); + hst.set_notes("test_notes"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + host::hosts["test_host"]->set_long_plugin_output("test_long_output"); + host::hosts["test_host"]->set_has_been_checked(true); + process_macros_r(mac, "$TOTALHOSTSERVICESWARNING:test_host$", out, 0); + ASSERT_EQ(out, "0"); +} + +TEST_F(MacroHostname, TotalHostServicesUnknown) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst_hlp.hook("contacts", "user"); + hst.set_notes("test_notes"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + host::hosts["test_host"]->set_long_plugin_output("test_long_output"); + host::hosts["test_host"]->set_has_been_checked(true); + process_macros_r(mac, "$TOTALHOSTSERVICESUNKNOWN:test_host$", out, 0); + ASSERT_EQ(out, "0"); +} + +TEST_F(MacroHostname, HostGroupNotes) { + configuration::applier::hostgroup hg_aply; + configuration::applier::host hst_aply; + configuration::Hostgroup hg; + configuration::hostgroup_helper hg_hlp(&hg); + configuration::Host hst_a; + configuration::host_helper hst_a_hlp(&hst_a); + configuration::Host hst_c; + configuration::host_helper hst_c_hlp(&hst_c); + + hst_a.set_host_name("a"); + hst_a.set_address("127.0.0.1"); + hst_a.set_host_id(1); + + hst_c.set_host_name("c"); + hst_c.set_address("127.0.0.1"); + hst_c.set_host_id(2); + + hst_aply.add_object(hst_a); + hst_aply.add_object(hst_c); + + hg.set_hostgroup_name("temphg"); + hg_hlp.hook("members", "a,c"); + hg.set_notes("test_note"); + ASSERT_NO_THROW(hg_aply.add_object(hg)); + + ASSERT_NO_THROW(hst_aply.expand_objects(pb_config)); + ASSERT_NO_THROW(hst_aply.expand_objects(pb_config)); + ASSERT_NO_THROW(hg_aply.expand_objects(pb_config)); + + configuration::error_cnt err; + ASSERT_NO_THROW(hst_aply.resolve_object(hst_a, err)); + ASSERT_NO_THROW(hst_aply.resolve_object(hst_c, err)); + ASSERT_NO_THROW(hg_aply.resolve_object(hg, err)); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$HOSTGROUPNOTES:temphg$", out, 0); + ASSERT_EQ(out, "test_note"); +} + +TEST_F(MacroHostname, HostGroupNotesUrl) { + configuration::applier::hostgroup hg_aply; + configuration::applier::host hst_aply; + configuration::Hostgroup hg; + configuration::hostgroup_helper hg_hlp(&hg); + configuration::Host hst_a; + configuration::host_helper hst_a_helper(&hst_a); + configuration::Host hst_c; + configuration::host_helper hst_c_hlp(&hst_c); + + hst_a.set_host_name("a"); + hst_a.set_address("127.0.0.1"); + hst_a.set_host_id(1); + + hst_c.set_host_name("c"); + hst_c.set_address("127.0.0.1"); + hst_c.set_host_id(2); + + hst_aply.add_object(hst_a); + hst_aply.add_object(hst_c); + + hg.set_hostgroup_name("temphg"); + hg_hlp.hook("members", "a,c"); + hg.set_notes_url("test_note_url"); + ASSERT_NO_THROW(hg_aply.add_object(hg)); + + ASSERT_NO_THROW(hst_aply.expand_objects(pb_config)); + ASSERT_NO_THROW(hst_aply.expand_objects(pb_config)); + ASSERT_NO_THROW(hg_aply.expand_objects(pb_config)); + + configuration::error_cnt err; + ASSERT_NO_THROW(hst_aply.resolve_object(hst_a, err)); + ASSERT_NO_THROW(hst_aply.resolve_object(hst_c, err)); + ASSERT_NO_THROW(hg_aply.resolve_object(hg, err)); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$HOSTGROUPNOTESURL:temphg$", out, 0); + ASSERT_EQ(out, "test_note_url"); +} + +TEST_F(MacroHostname, HostGroupActionUrl) { + configuration::applier::hostgroup hg_aply; + configuration::applier::host hst_aply; + configuration::Hostgroup hg; + configuration::hostgroup_helper hg_hlp(&hg); + configuration::Host hst_a; + configuration::host_helper hst_a_hlp(&hst_a); + configuration::Host hst_c; + configuration::host_helper hst_c_hlp(&hst_c); + + hst_a.set_host_name("a"); + hst_a.set_address("127.0.0.1"); + hst_a.set_host_id(1); + + hst_c.set_host_name("c"); + hst_c.set_address("127.0.0.1"); + hst_c.set_host_id(2); + + hst_aply.add_object(hst_a); + hst_aply.add_object(hst_c); + + hg.set_hostgroup_name("temphg"); + hg_hlp.hook("members", "a,c"); + hg.set_action_url("test_action_url"); + ASSERT_NO_THROW(hg_aply.add_object(hg)); + + ASSERT_NO_THROW(hst_aply.expand_objects(pb_config)); + ASSERT_NO_THROW(hst_aply.expand_objects(pb_config)); + ASSERT_NO_THROW(hg_aply.expand_objects(pb_config)); + + configuration::error_cnt err; + ASSERT_NO_THROW(hst_aply.resolve_object(hst_a, err)); + ASSERT_NO_THROW(hst_aply.resolve_object(hst_c, err)); + ASSERT_NO_THROW(hg_aply.resolve_object(hg, err)); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$HOSTGROUPACTIONURL:temphg$", out, 0); + ASSERT_EQ(out, "test_action_url"); +} + +TEST_F(MacroHostname, HostGroupMembers) { + configuration::applier::hostgroup hg_aply; + configuration::applier::host hst_aply; + configuration::Hostgroup hg; + configuration::hostgroup_helper hg_hlp(&hg); + configuration::Host hst_a; + configuration::host_helper hst_a_hlp(&hst_a); + configuration::Host hst_c; + configuration::host_helper hst_c_hlp(&hst_c); + + hst_a.set_host_name("a"); + hst_a.set_host_id(1); + hst_a.set_address("127.0.0.1"); + + hst_c.set_host_name("c"); + hst_c.set_address("127.0.0.1"); + hst_c.set_host_id(2); + + hst_aply.add_object(hst_a); + hst_aply.add_object(hst_c); + + hg.set_hostgroup_name("temphg"); + hg_hlp.hook("members", "a,c"); + hg.set_action_url("test_action_url"); + ASSERT_NO_THROW(hg_aply.add_object(hg)); + + ASSERT_NO_THROW(hst_aply.expand_objects(pb_config)); + ASSERT_NO_THROW(hst_aply.expand_objects(pb_config)); + ASSERT_NO_THROW(hg_aply.expand_objects(pb_config)); + + configuration::error_cnt err; + ASSERT_NO_THROW(hst_aply.resolve_object(hst_a, err)); + ASSERT_NO_THROW(hst_aply.resolve_object(hst_c, err)); + ASSERT_NO_THROW(hg_aply.resolve_object(hg, err)); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$HOSTGROUPMEMBERS:temphg$", out, 0); + ASSERT_TRUE(out == "c,a" || out == "a,c"); +} + +TEST_F(MacroHostname, LastHostProblemId) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst_hlp.hook("contacts", "user"); + hst.set_notes("test_notes"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + host::hosts["test_host"]->set_long_plugin_output("test_long_output"); + host::hosts["test_host"]->set_has_been_checked(true); + process_macros_r(mac, "$LASTHOSTPROBLEMID:test_host$", out, 0); + ASSERT_EQ(out, "0"); +} + +TEST_F(MacroHostname, LastHostState) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst_hlp.hook("contacts", "user"); + hst.set_notes("test_notes"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + host::hosts["test_host"]->set_long_plugin_output("test_long_output"); + host::hosts["test_host"]->set_has_been_checked(true); + process_macros_r(mac, "$LASTHOSTSTATE:test_host$", out, 0); + ASSERT_EQ(out, "UP"); +} + +TEST_F(MacroHostname, LastHostStateID) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst_hlp.hook("contacts", "user"); + hst.set_notes("test_notes"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + host::hosts["test_host"]->set_long_plugin_output("test_long_output"); + host::hosts["test_host"]->set_has_been_checked(true); + process_macros_r(mac, "$LASTHOSTSTATEID:test_host$", out, 0); + ASSERT_EQ(out, "0"); +} + +TEST_F(MacroHostname, HostParents) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst_hlp.hook("contacts", "user"); + hst_hlp.hook("parents", "test_parent"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + host::hosts["test_host"]->set_long_plugin_output("test_long_output"); + host::hosts["test_host"]->set_has_been_checked(true); + process_macros_r(mac, "$HOSTPARENTS:test_host$", out, 0); + ASSERT_EQ(out, "test_parent"); +} + +TEST_F(MacroHostname, HostChildren) { + configuration::applier::host hst_aply; + configuration::applier::command cmd_aply; + configuration::Host hst_child; + configuration::host_helper hst_child_hlp(&hst_child); + configuration::Host hst_parent; + configuration::host_helper hst_parent_hlp(&hst_parent); + + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("base_centreon_ping"); + + cmd.set_command_line( + "$USER1$/check_icmp -H $HOSTADDRESS$ -n $_HOSTPACKETNUMBER$ -w " + "$_HOSTWARNING$ -c $_HOSTCRITICAL$"); + cmd_aply.add_object(cmd); + + hst_child.set_host_name("child_host"); + hst_child.set_address("127.0.0.1"); + hst_child_hlp.hook("parents", "parent_host"); + hst_child.set_host_id(1); + hst_child_hlp.insert_customvariable("_PACKETNUMBER", "42"); + hst_child_hlp.insert_customvariable("_WARNING", "200,20%"); + hst_child_hlp.insert_customvariable("_CRITICAL", "400,50%"); + hst_child.set_check_command("base_centreon_ping"); + hst_aply.add_object(hst_child); + + hst_parent.set_host_name("parent_host"); + hst_parent.set_address("127.0.0.1"); + hst_parent.set_host_id(2); + hst_parent_hlp.insert_customvariable("_PACKETNUMBER", "42"); + hst_parent_hlp.insert_customvariable("_WARNING", "200,20%"); + hst_parent_hlp.insert_customvariable("_CRITICAL", "400,50%"); + hst_parent.set_check_command("base_centreon_ping"); + hst_aply.add_object(hst_parent); + + ASSERT_EQ(engine::host::hosts.size(), 2u); + + configuration::error_cnt err; + hst_aply.expand_objects(pb_config); + hst_aply.resolve_object(hst_child, err); + hst_aply.resolve_object(hst_parent, err); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$HOSTCHILDREN:parent_host$", out, 0); + ASSERT_EQ(out, "child_host"); +} + +TEST_F(MacroHostname, HostID) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst_hlp.hook("contacts", "user"); + hst.set_notes("test_notes"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + host::hosts["test_host"]->set_long_plugin_output("test_long_output"); + host::hosts["test_host"]->set_has_been_checked(true); + process_macros_r(mac, "$HOSTID:test_host$", out, 0); + ASSERT_EQ(out, "12"); +} + +TEST_F(MacroHostname, HostTimeZone) { + configuration::applier::host hst_aply; + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst_hlp.hook("contacts", "user"); + hst.set_timezone("test_timezone"); + ASSERT_NO_THROW(hst_aply.add_object(hst)); + ASSERT_EQ(1u, host::hosts.size()); + + int now{500000000}; + set_time(now); + init_macros(); + + std::string out; + nagios_macros* mac(get_global_macros()); + host::hosts["test_host"]->set_long_plugin_output("test_long_output"); + host::hosts["test_host"]->set_has_been_checked(true); + process_macros_r(mac, "$HOSTTIMEZONE:test_host$", out, 0); + ASSERT_EQ(out, "test_timezone"); +} diff --git a/engine/tests/macros/pbmacro_service.cc b/engine/tests/macros/pbmacro_service.cc new file mode 100644 index 00000000000..4209dae5163 --- /dev/null +++ b/engine/tests/macros/pbmacro_service.cc @@ -0,0 +1,2099 @@ +/** + * Copyright 2019 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include +#include +#include "../helper.hh" +#include "../test_engine.hh" +#include "../timeperiod/utils.hh" +#include "com/centreon/engine/checks/checker.hh" +#include "com/centreon/engine/commands/commands.hh" +#include "com/centreon/engine/configuration/applier/command.hh" +#include "com/centreon/engine/configuration/applier/contact.hh" +#include "com/centreon/engine/configuration/applier/contactgroup.hh" +#include "com/centreon/engine/configuration/applier/host.hh" +#include "com/centreon/engine/configuration/applier/hostgroup.hh" +#include "com/centreon/engine/configuration/applier/service.hh" +#include "com/centreon/engine/configuration/applier/serviceescalation.hh" +#include "com/centreon/engine/configuration/applier/servicegroup.hh" +#include "com/centreon/engine/configuration/applier/state.hh" +#include "com/centreon/engine/configuration/applier/timeperiod.hh" +#include "com/centreon/engine/globals.hh" +#include "com/centreon/engine/hostescalation.hh" +#include "com/centreon/engine/macros.hh" +#include "com/centreon/engine/macros/grab_host.hh" +#include "com/centreon/engine/macros/process.hh" +#include "com/centreon/engine/timeperiod.hh" + +using namespace com::centreon; +using namespace com::centreon::engine; + +class MacroService : public TestEngine { + public: + void SetUp() override { + init_config_state(); + _tp = _creator.new_timeperiod(); + for (int i(0); i < 7; ++i) + _creator.new_timerange(0, 0, 24, 0, i); + _now = strtotimet("2016-11-24 08:00:00"); + set_time(_now); + } + + void TearDown() override { + _host.reset(); + _host2.reset(); + _host3.reset(); + _svc.reset(); + deinit_config_state(); + } + + protected: + std::shared_ptr _host, _host2, _host3; + std::shared_ptr _svc; + timeperiod_creator _creator; + time_t _now; + timeperiod* _tp; +}; + +// Given host configuration without host_id +// Then the applier add_object throws an exception. +TEST_F(MacroService, TotalServicesOkZero) { + std::string out; + nagios_macros* mac(get_global_macros()); + process_macros_r(mac, "$TOTALSERVICESOK$", out, 0); + ASSERT_EQ(out, "0"); +} + +//TEST_F(MacroService, ServiceMacro) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// // We fake the expand_object +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// +// nagios_macros* mac(get_global_macros()); +// std::string out; +// host::hosts["test_host"]->set_current_state(host::state_up); +// host::hosts["test_host"]->set_has_been_checked(true); +// service::services[std::make_pair("test_host", "test_svc")]->set_plugin_output( +// "foo bar!"); +// process_macros_r(mac, "$SERVICEOUTPUT:test_host:test_svc$", out, 1); +// ASSERT_EQ(out, "foo bar!"); +//} +// +//TEST_F(MacroService, ServiceDesc) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// // We fake the expand_object +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// +// process_macros_r(mac, "$SERVICEDESC:test_host:test_svc$", out, 0); +// ASSERT_EQ(out, "test_svc"); +//} +// +//TEST_F(MacroService, ServiceState) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// // We fake the expand_object +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// +// process_macros_r(mac, "$SERVICESTATE:test_host:test_svc$", out, 0); +// ASSERT_EQ(out, "OK"); +//} +// +//TEST_F(MacroService, ServiceStateID) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// // We fake the expand_object +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// +// process_macros_r(mac, "$SERVICESTATEID:test_host:test_svc$", out, 0); +// ASSERT_EQ(out, "0"); +//} +// +//TEST_F(MacroService, ServiceAttempt) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// // We fake the expand_object +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// +// process_macros_r(mac, "$SERVICEATTEMPT:test_host:test_svc$", out, 0); +// ASSERT_EQ(out, "1"); +//} +// +//TEST_F(MacroService, ServiceisVolatile) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// // We fake the expand_object +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// +// process_macros_r(mac, "$SERVICEISVOLATILE:test_host:test_svc$", out, 0); +// ASSERT_EQ(out, "0"); +//} +// +//TEST_F(MacroService, LastServiceCheck) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// // We fake the expand_object +// svc.set_host_id(12); +// +// int now{500000000}; +// set_time(now); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// host::hosts["test_host"]->set_current_state(host::state_up); +// host::hosts["test_host"]->set_has_been_checked(true); +// process_macros_r(mac, "$LASTSERVICECHECK:test_host:test_svc$", out, 0); +// ASSERT_EQ(out, "0"); +//} +// +//TEST_F(MacroService, LastServiceStateChange) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// // We fake the expand_object +// svc.set_host_id(12); +// +// int now{500000000}; +// set_time(now); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// host::hosts["test_host"]->set_current_state(host::state_up); +// host::hosts["test_host"]->set_has_been_checked(true); +// process_macros_r(mac, "$LASTSERVICESTATECHANGE:test_host:test_svc$", out, 0); +// ASSERT_EQ(out, "0"); +//} +// +//TEST_F(MacroService, ServicePerfData) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// // We fake the expand_object +// svc.set_host_id(12); +// +// int now{500000000}; +// set_time(now); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// host::hosts["test_host"]->set_current_state(host::state_up); +// host::hosts["test_host"]->set_has_been_checked(true); +// service::services[std::make_pair("test_host", "test_svc")]->set_perf_data( +// "foo"); +// process_macros_r(mac, "$SERVICEPERFDATA:test_host:test_svc$", out, 0); +// ASSERT_EQ(out, "foo"); +//} +// +//TEST_F(MacroService, ServiceExecutionTime) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::applier::contact cnt_aply; +// configuration::service svc; +// configuration::host hst; +// configuration::contact cnt; +// ASSERT_TRUE(cnt.parse("contact_name", "user")); +// ASSERT_TRUE(cnt.parse("email", "contact@centreon.com")); +// ASSERT_TRUE(cnt.parse("pager", "0473729383")); +// ASSERT_TRUE(cnt.parse("host_notification_period", "24x7")); +// ASSERT_TRUE(cnt.parse("service_notification_period", "24x7")); +// cnt_aply.add_object(cnt); +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// // ASSERT_TRUE(hst.parse("contact_name", "testeeeeee")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// // We fake the expand_object +// svc.set_host_id(12); +// +// int now{500000000}; +// set_time(now); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// host::hosts["test_host"]->set_current_state(host::state_up); +// host::hosts["test_host"]->set_has_been_checked(true); +// service::services[std::make_pair("test_host", "test_svc")] +// ->set_execution_time(20.00); +// process_macros_r(mac, "$SERVICEEXECUTIONTIME:test_host:test_svc$", out, 1); +// ASSERT_EQ(out, "20.000"); +//} +// +//TEST_F(MacroService, ServiceLatency) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::applier::contact cnt_aply; +// configuration::service svc; +// configuration::host hst; +// configuration::contact cnt; +// ASSERT_TRUE(cnt.parse("contact_name", "user")); +// ASSERT_TRUE(cnt.parse("email", "contact@centreon.com")); +// ASSERT_TRUE(cnt.parse("pager", "0473729383")); +// ASSERT_TRUE(cnt.parse("host_notification_period", "24x7")); +// ASSERT_TRUE(cnt.parse("service_notification_period", "24x7")); +// cnt_aply.add_object(cnt); +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// // ASSERT_TRUE(hst.parse("contact_name", "testeeeeee")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// // We fake the expand_object +// svc.set_host_id(12); +// +// int now{500000000}; +// set_time(now); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// host::hosts["test_host"]->set_current_state(host::state_up); +// host::hosts["test_host"]->set_has_been_checked(true); +// service::services[std::make_pair("test_host", "test_svc")]->set_latency( +// 20.00); +// process_macros_r(mac, "$SERVICELATENCY:test_host:test_svc$", out, 1); +// ASSERT_EQ(out, "20.000"); +//} +// +//TEST_F(MacroService, ServiceDuration) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::applier::contact cnt_aply; +// configuration::service svc; +// configuration::host hst; +// configuration::contact cnt; +// ASSERT_TRUE(cnt.parse("contact_name", "user")); +// ASSERT_TRUE(cnt.parse("email", "contact@centreon.com")); +// ASSERT_TRUE(cnt.parse("pager", "0473729383")); +// ASSERT_TRUE(cnt.parse("host_notification_period", "24x7")); +// ASSERT_TRUE(cnt.parse("service_notification_period", "24x7")); +// cnt_aply.add_object(cnt); +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// // ASSERT_TRUE(hst.parse("contact_name", "testeeeeee")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// // We fake the expand_object +// svc.set_host_id(12); +// +// int now{500000000}; +// set_time(now); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// host::hosts["test_host"]->set_current_state(host::state_up); +// host::hosts["test_host"]->set_has_been_checked(true); +// service::services[std::make_pair("test_host", "test_svc")]->set_latency( +// 20.00); +// process_macros_r(mac, "$SERVICEDURATION:test_host:test_svc$", out, 1); +// ASSERT_EQ(out, "5787d 0h 53m 20s"); +//} +// +//TEST_F(MacroService, ServiceDurationSec) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::applier::contact cnt_aply; +// configuration::service svc; +// configuration::host hst; +// configuration::contact cnt; +// ASSERT_TRUE(cnt.parse("contact_name", "user")); +// ASSERT_TRUE(cnt.parse("email", "contact@centreon.com")); +// ASSERT_TRUE(cnt.parse("pager", "0473729383")); +// ASSERT_TRUE(cnt.parse("host_notification_period", "24x7")); +// ASSERT_TRUE(cnt.parse("service_notification_period", "24x7")); +// cnt_aply.add_object(cnt); +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// // We fake the expand_object +// svc.set_host_id(12); +// +// int now{500000000}; +// set_time(now); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// host::hosts["test_host"]->set_current_state(host::state_up); +// host::hosts["test_host"]->set_has_been_checked(true); +// service::services[std::make_pair("test_host", "test_svc")]->set_latency( +// 20.00); +// process_macros_r(mac, "$SERVICEDURATIONSEC:test_host:test_svc$", out, 1); +// ASSERT_EQ(out, "500000000"); +//} +// +//TEST_F(MacroService, ServiceDownTime) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// process_macros_r(mac, "$SERVICEDOWNTIME:test_host:test_svc$", out, 1); +// ASSERT_EQ(out, "0"); +//} +// +//TEST_F(MacroService, ServiceStateType) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// process_macros_r(mac, "$SERVICESTATETYPE:test_host:test_svc$", out, 1); +// ASSERT_EQ(out, "HARD"); +//} +// +//TEST_F(MacroService, ServicePercentChange) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// process_macros_r(mac, "$SERVICEPERCENTCHANGE:test_host:test_svc$", out, 1); +// ASSERT_EQ(out, "0.00"); +//} +// +//TEST_F(MacroService, ServiceGroupName) { +// configuration::applier::host aply_hst; +// configuration::applier::service aply_svc; +// configuration::applier::command aply_cmd; +// configuration::applier::servicegroup aply_grp; +// configuration::servicegroup grp("test_group"); +// configuration::host hst; +// configuration::command cmd("cmd"); +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// aply_hst.add_object(hst); +// configuration::service svc; +// ASSERT_TRUE(svc.parse("service_description", "test")); +// ASSERT_TRUE(svc.parse("hosts", "test_host")); +// ASSERT_TRUE(svc.parse("service_id", "18")); +// cmd.parse("command_line", "echo 1"); +// svc.parse("check_command", "cmd"); +// aply_cmd.add_object(cmd); +// +// // We fake here the expand_object on configuration::service +// svc.set_host_id(12); +// +// configuration::error_cnt err; +// aply_svc.add_object(svc); +// ASSERT_TRUE(svc.parse("servicegroups", "test_group")); +// grp.parse("members", "test_host,test"); +// aply_grp.add_object(grp); +// aply_grp.expand_objects(*config); +// ASSERT_NO_THROW(aply_grp.resolve_object(grp, err)); +// +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// process_macros_r(mac, "$SERVICEGROUPNAME:test_host:test$", out, 1); +// ASSERT_EQ(out, "test_group"); +//} +// +//TEST_F(MacroService, ServiceGroupAlias) { +// configuration::applier::host aply_hst; +// configuration::applier::service aply_svc; +// configuration::applier::command aply_cmd; +// configuration::applier::servicegroup aply_grp; +// configuration::servicegroup grp("test_group"); +// configuration::host hst; +// configuration::command cmd("cmd"); +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// aply_hst.add_object(hst); +// configuration::service svc; +// ASSERT_TRUE(svc.parse("service_description", "test")); +// ASSERT_TRUE(svc.parse("hosts", "test_host")); +// ASSERT_TRUE(svc.parse("service_id", "18")); +// cmd.parse("command_line", "echo 1"); +// svc.parse("check_command", "cmd"); +// aply_cmd.add_object(cmd); +// +// // We fake here the expand_object on configuration::service +// svc.set_host_id(12); +// +// configuration::error_cnt err; +// aply_svc.add_object(svc); +// ASSERT_TRUE(svc.parse("servicegroups", "test_group")); +// grp.parse("members", "test_host,test"); +// grp.parse("alias", "test_group_alias"); +// aply_grp.add_object(grp); +// aply_grp.expand_objects(*config); +// ASSERT_NO_THROW(aply_grp.resolve_object(grp, err)); +// +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// process_macros_r(mac, "$SERVICEGROUPALIAS:test_group$", out, 1); +// ASSERT_EQ(out, "test_group_alias"); +//} +// +//TEST_F(MacroService, LastServiceOK) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// service::services[std::make_pair("test_host", "test_svc")]->set_last_time_ok( +// 20); +// process_macros_r(mac, "$LASTSERVICEOK:test_host:test_svc$", out, 1); +// ASSERT_EQ(out, "20"); +//} +// +//TEST_F(MacroService, LastServiceWarning) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// service::services[std::make_pair("test_host", "test_svc")] +// ->set_last_time_warning(30); +// process_macros_r(mac, "$LASTSERVICEWARNING:test_host:test_svc$", out, 1); +// ASSERT_EQ(out, "30"); +//} +// +//TEST_F(MacroService, LastServiceUnknown) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// service::services[std::make_pair("test_host", "test_svc")] +// ->set_last_time_unknown(40); +// process_macros_r(mac, "$LASTSERVICEUNKNOWN:test_host:test_svc$", out, 1); +// ASSERT_EQ(out, "40"); +//} +// +//TEST_F(MacroService, LastServiceCritical) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// service::services[std::make_pair("test_host", "test_svc")] +// ->set_last_time_critical(50); +// process_macros_r(mac, "$LASTSERVICECRITICAL:test_host:test_svc$", out, 1); +// ASSERT_EQ(out, "50"); +//} +// +//TEST_F(MacroService, ServiceCheckCommand) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// service::services[std::make_pair("test_host", "test_svc")] +// ->set_last_time_critical(50); +// process_macros_r(mac, "$SERVICECHECKCOMMAND:test_host:test_svc$", out, 1); +// ASSERT_EQ(out, "cmd"); +//} +// +//TEST_F(MacroService, ServiceDisplayName) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// service::services[std::make_pair("test_host", "test_svc")] +// ->set_last_time_critical(50); +// process_macros_r(mac, "$SERVICEDISPLAYNAME:test_host:test_svc$", out, 1); +// ASSERT_EQ(out, "test_svc"); +//} +// +//TEST_F(MacroService, ServiceNotesUrl) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// ASSERT_TRUE(svc.parse("notes_url", "http://192.168.0.172/centreon/main.php")); +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// process_macros_r(mac, "$SERVICENOTESURL:test_host:test_svc$", out, 1); +// ASSERT_EQ(out, "http://192.168.0.172/centreon/main.php"); +//} +// +//TEST_F(MacroService, ServiceNotes) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// ASSERT_TRUE(svc.parse("notes", "test_notes")); +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// process_macros_r(mac, "$SERVICENOTES:test_host:test_svc$", out, 1); +// ASSERT_EQ(out, "test_notes"); +//} +// +//TEST_F(MacroService, ServiceActionUrl) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// ASSERT_TRUE(svc.parse("action_url", "test_action_url")); +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// process_macros_r(mac, "$SERVICEACTIONURL:test_host:test_svc$", out, 1); +// ASSERT_EQ(out, "test_action_url"); +//} +// +//TEST_F(MacroService, TotalServicesWarning) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// ASSERT_TRUE(svc.parse("action_url", "test_action_url")); +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// process_macros_r(mac, "$TOTALSERVICESWARNING:test_host:test_svc$", out, 1); +// ASSERT_EQ(out, "0"); +//} +// +//TEST_F(MacroService, TotalServicesCritical) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// ASSERT_TRUE(svc.parse("action_url", "test_action_url")); +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// process_macros_r(mac, "$TOTALSERVICESCRITICAL:test_host:test_svc$", out, 1); +// ASSERT_EQ(out, "0"); +//} +// +//TEST_F(MacroService, TotalServicesUnknown) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// ASSERT_TRUE(svc.parse("action_url", "test_action_url")); +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// process_macros_r(mac, "$TOTALSERVICESUNKNOWN:test_host:test_svc$", out, 1); +// ASSERT_EQ(out, "0"); +//} +// +//TEST_F(MacroService, TotalServicesWarningUnhandled) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// ASSERT_TRUE(svc.parse("action_url", "test_action_url")); +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// process_macros_r(mac, "$TOTALSERVICESWARNINGUNHANDLED:test_host:test_svc$", +// out, 1); +// ASSERT_EQ(out, "0"); +//} +// +//TEST_F(MacroService, TotalServicesCriticalUnhandled) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// ASSERT_TRUE(svc.parse("action_url", "test_action_url")); +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// process_macros_r(mac, "$TOTALSERVICESCRITICALUNHANDLED:test_host:test_svc$", +// out, 1); +// ASSERT_EQ(out, "0"); +//} +// +//TEST_F(MacroService, TotalServicesUnknownUnhandled) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// ASSERT_TRUE(svc.parse("action_url", "test_action_url")); +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// process_macros_r(mac, "$TOTALSERVICESUNKNOWNUNHANDLED:test_host:test_svc$", +// out, 1); +// ASSERT_EQ(out, "0"); +//} +// +//TEST_F(MacroService, TotalServiceProblems) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// ASSERT_TRUE(svc.parse("action_url", "test_action_url")); +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// process_macros_r(mac, "$TOTALSERVICEPROBLEMS:test_host:test_svc$", out, 1); +// ASSERT_EQ(out, "0"); +//} +// +//TEST_F(MacroService, TotalServiceProblemsUnhandled) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// ASSERT_TRUE(svc.parse("action_url", "test_action_url")); +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// process_macros_r(mac, "$TOTALSERVICEPROBLEMSUNHANDLED:test_host:test_svc$", +// out, 1); +// ASSERT_EQ(out, "0"); +//} +// +//TEST_F(MacroService, ServiceCheckType) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// ASSERT_TRUE(svc.parse("action_url", "test_action_url")); +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// process_macros_r(mac, "$SERVICECHECKTYPE:test_host:test_svc$", out, 1); +// ASSERT_EQ(out, "ACTIVE"); +//} +// +//TEST_F(MacroService, LongServiceOutput) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// ASSERT_TRUE(svc.parse("action_url", "test_action_url")); +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// service::services[std::make_pair("test_host", "test_svc")] +// ->set_long_plugin_output("test_long_output"); +// process_macros_r(mac, "$LONGSERVICEOUTPUT:test_host:test_svc$", out, 1); +// ASSERT_EQ(out, "test_long_output"); +//} +// +//TEST_F(MacroService, ServiceNotificationID) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// ASSERT_TRUE(svc.parse("action_url", "test_action_url")); +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// service::services[std::make_pair("test_host", "test_svc")] +// ->set_long_plugin_output("test_long_output"); +// process_macros_r(mac, "$SERVICENOTIFICATIONID:test_host:test_svc$", out, 1); +// ASSERT_EQ(out, "0"); +//} +// +//TEST_F(MacroService, ServiceEventID) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// ASSERT_TRUE(svc.parse("action_url", "test_action_url")); +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// service::services[std::make_pair("test_host", "test_svc")] +// ->set_long_plugin_output("test_long_output"); +// process_macros_r(mac, "$SERVICEEVENTID:test_host:test_svc$", out, 1); +// ASSERT_EQ(out, "0"); +//} +// +//TEST_F(MacroService, LastServiceEventID) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// ASSERT_TRUE(svc.parse("action_url", "test_action_url")); +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// service::services[std::make_pair("test_host", "test_svc")] +// ->set_long_plugin_output("test_long_output"); +// process_macros_r(mac, "$LASTSERVICEEVENTID:test_host:test_svc$", out, 1); +// ASSERT_EQ(out, "0"); +//} +// +//TEST_F(MacroService, ServiceGroupNames) { +// configuration::applier::host aply_hst; +// configuration::applier::service aply_svc; +// configuration::applier::command aply_cmd; +// configuration::applier::servicegroup aply_grp; +// configuration::servicegroup grp("test_group"); +// configuration::host hst; +// configuration::command cmd("cmd"); +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// aply_hst.add_object(hst); +// configuration::service svc; +// ASSERT_TRUE(svc.parse("service_description", "test")); +// ASSERT_TRUE(svc.parse("hosts", "test_host")); +// ASSERT_TRUE(svc.parse("service_id", "18")); +// cmd.parse("command_line", "echo 1"); +// svc.parse("check_command", "cmd"); +// aply_cmd.add_object(cmd); +// +// // We fake here the expand_object on configuration::service +// svc.set_host_id(12); +// +// configuration::error_cnt err; +// aply_svc.add_object(svc); +// ASSERT_TRUE(svc.parse("servicegroups", "test_group")); +// grp.parse("members", "test_host,test"); +// aply_grp.add_object(grp); +// aply_grp.expand_objects(*config); +// ASSERT_NO_THROW(aply_grp.resolve_object(grp, err)); +// +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// service::services[std::make_pair("test_host", "test")] +// ->set_long_plugin_output("test_long_output"); +// process_macros_r(mac, "$SERVICEGROUPNAMES:test_host:test$", out, 1); +// ASSERT_EQ(out, "test_group"); +//} +// +//TEST_F(MacroService, MaxServiceAttempts) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// ASSERT_TRUE(svc.parse("action_url", "test_action_url")); +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// service::services[std::make_pair("test_host", "test_svc")] +// ->set_long_plugin_output("test_long_output"); +// process_macros_r(mac, "$MAXSERVICEATTEMPTS:test_host:test_svc$", out, 1); +// ASSERT_EQ(out, "3"); +//} +// +//TEST_F(MacroService, ServiceGroupNotes) { +// configuration::applier::host aply_hst; +// configuration::applier::service aply_svc; +// configuration::applier::command aply_cmd; +// configuration::applier::servicegroup aply_grp; +// configuration::servicegroup grp("test_group"); +// configuration::host hst; +// configuration::command cmd("cmd"); +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// aply_hst.add_object(hst); +// configuration::service svc; +// ASSERT_TRUE(svc.parse("service_description", "test")); +// ASSERT_TRUE(svc.parse("hosts", "test_host")); +// ASSERT_TRUE(svc.parse("service_id", "18")); +// cmd.parse("command_line", "echo 1"); +// svc.parse("check_command", "cmd"); +// aply_cmd.add_object(cmd); +// +// // We fake here the expand_object on configuration::service +// svc.set_host_id(12); +// +// configuration::error_cnt err; +// aply_svc.add_object(svc); +// ASSERT_TRUE(svc.parse("servicegroups", "test_group")); +// grp.parse("members", "test_host,test"); +// ASSERT_TRUE(grp.parse("notes", "test_notes")); +// aply_grp.add_object(grp); +// aply_grp.expand_objects(*config); +// ASSERT_NO_THROW(aply_grp.resolve_object(grp, err)); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// service::services[std::make_pair("test_host", "test")] +// ->set_long_plugin_output("test_long_output"); +// process_macros_r(mac, "$SERVICEGROUPNOTES:test_group$", out, 1); +// ASSERT_EQ(out, "test_notes"); +//} +// +//TEST_F(MacroService, ServiceGroupNotesUrl) { +// configuration::applier::host aply_hst; +// configuration::applier::service aply_svc; +// configuration::applier::command aply_cmd; +// configuration::applier::servicegroup aply_grp; +// configuration::servicegroup grp("test_group"); +// configuration::host hst; +// configuration::command cmd("cmd"); +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// aply_hst.add_object(hst); +// configuration::service svc; +// ASSERT_TRUE(svc.parse("service_description", "test")); +// ASSERT_TRUE(svc.parse("hosts", "test_host")); +// ASSERT_TRUE(svc.parse("service_id", "18")); +// cmd.parse("command_line", "echo 1"); +// svc.parse("check_command", "cmd"); +// aply_cmd.add_object(cmd); +// +// // We fake here the expand_object on configuration::service +// svc.set_host_id(12); +// +// configuration::error_cnt err; +// aply_svc.add_object(svc); +// ASSERT_TRUE(svc.parse("servicegroups", "test_group")); +// grp.parse("members", "test_host,test"); +// ASSERT_TRUE(grp.parse("notes_url", "test_notes_url")); +// aply_grp.add_object(grp); +// aply_grp.expand_objects(*config); +// ASSERT_NO_THROW(aply_grp.resolve_object(grp, err)); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// process_macros_r(mac, "$SERVICEGROUPNOTESURL:test_group$", out, 1); +// ASSERT_EQ(out, "test_notes_url"); +//} +// +//TEST_F(MacroService, ServiceGroupActionUrl) { +// configuration::applier::host aply_hst; +// configuration::applier::service aply_svc; +// configuration::applier::command aply_cmd; +// configuration::applier::servicegroup aply_grp; +// configuration::servicegroup grp("test_group"); +// configuration::host hst; +// configuration::command cmd("cmd"); +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// aply_hst.add_object(hst); +// configuration::service svc; +// ASSERT_TRUE(svc.parse("service_description", "test")); +// ASSERT_TRUE(svc.parse("hosts", "test_host")); +// ASSERT_TRUE(svc.parse("service_id", "18")); +// cmd.parse("command_line", "echo 1"); +// svc.parse("check_command", "cmd"); +// aply_cmd.add_object(cmd); +// +// // We fake here the expand_object on configuration::service +// svc.set_host_id(12); +// +// configuration::error_cnt err; +// aply_svc.add_object(svc); +// ASSERT_TRUE(svc.parse("servicegroups", "test_group")); +// grp.parse("members", "test_host,test"); +// ASSERT_TRUE(grp.parse("action_url", "test_notes_url")); +// aply_grp.add_object(grp); +// aply_grp.expand_objects(*config); +// ASSERT_NO_THROW(aply_grp.resolve_object(grp, err)); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// process_macros_r(mac, "$SERVICEGROUPACTIONURL:test_group$", out, 1); +// ASSERT_EQ(out, "test_notes_url"); +//} +// +//TEST_F(MacroService, ServiceGroupMembers) { +// configuration::applier::host aply_hst; +// configuration::applier::service aply_svc; +// configuration::applier::command aply_cmd; +// configuration::applier::servicegroup aply_grp; +// configuration::servicegroup grp("test_group"); +// configuration::host hst; +// configuration::command cmd("cmd"); +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// aply_hst.add_object(hst); +// configuration::service svc; +// ASSERT_TRUE(svc.parse("service_description", "test")); +// ASSERT_TRUE(svc.parse("hosts", "test_host")); +// ASSERT_TRUE(svc.parse("service_id", "18")); +// cmd.parse("command_line", "echo 1"); +// svc.parse("check_command", "cmd"); +// aply_cmd.add_object(cmd); +// +// // We fake here the expand_object on configuration::service +// svc.set_host_id(12); +// +// configuration::error_cnt err; +// aply_svc.add_object(svc); +// ASSERT_TRUE(svc.parse("servicegroups", "test_group")); +// grp.parse("members", "test_host,test"); +// ASSERT_TRUE(grp.parse("action_url", "test_notes_url")); +// aply_grp.add_object(grp); +// aply_grp.expand_objects(*config); +// ASSERT_NO_THROW(aply_grp.resolve_object(grp, err)); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// process_macros_r(mac, "$SERVICEGROUPMEMBERS:test_group$", out, 1); +// ASSERT_EQ(out, "test_host,test"); +//} +// +//TEST_F(MacroService, ServiceID) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// ASSERT_TRUE(svc.parse("service_id", "13")); +// ASSERT_TRUE(svc.parse("action_url", "test_action_url")); +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// process_macros_r(mac, "$SERVICEID:test_host:test_svc$", out, 0); +// ASSERT_EQ(out, "13"); +//} +// +//TEST_F(MacroService, ServiceTimeZone) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// ASSERT_TRUE(svc.parse("action_url", "test_action_url")); +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// svc.parse("timezone", "test_time"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// service::services[std::make_pair("test_host", "test_svc")] +// ->set_long_plugin_output("test_long_output"); +// process_macros_r(mac, "$SERVICETIMEZONE:test_host:test_svc$", out, 1); +// ASSERT_EQ(out, "test_time"); +//} +// +//TEST_F(MacroService, LastServiceState) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// ASSERT_TRUE(svc.parse("action_url", "test_action_url")); +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// service::services[std::make_pair("test_host", "test_svc")] +// ->set_long_plugin_output("test_long_output"); +// process_macros_r(mac, "$LASTSERVICESTATE:test_host:test_svc$", out, 1); +// ASSERT_EQ(out, "OK"); +//} +// +//TEST_F(MacroService, LastServiceStateId) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// ASSERT_TRUE(svc.parse("action_url", "test_action_url")); +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// service::services[std::make_pair("test_host", "test_svc")] +// ->set_long_plugin_output("test_long_output"); +// process_macros_r(mac, "$LASTSERVICESTATEID:test_host:test_svc$", out, 1); +// ASSERT_EQ(out, "0"); +//} +// +//TEST_F(MacroService, ServiceProblemID) { +// init_macros(); +// +// configuration::error_cnt err; +// configuration::applier::contact ct_aply; +// configuration::contact ctct{new_configuration_contact("admin", true)}; +// ct_aply.add_object(ctct); +// configuration::contact ctct1{ +// new_configuration_contact("admin1", false, "c,r")}; +// ct_aply.add_object(ctct1); +// ct_aply.expand_objects(*config); +// ct_aply.resolve_object(ctct, err); +// ct_aply.resolve_object(ctct1, err); +// +// configuration::host hst{new_configuration_host("test_host", "admin")}; +// configuration::applier::host hst_aply; +// hst_aply.add_object(hst); +// +// configuration::service svc{ +// new_configuration_service("test_host", "test_svc", "admin,admin1")}; +// configuration::applier::service svc_aply; +// svc_aply.add_object(svc); +// +// hst_aply.resolve_object(hst, err); +// svc_aply.resolve_object(svc, err); +// +// host_map const& hm{engine::host::hosts}; +// _host3 = hm.begin()->second; +// _host3->set_current_state(engine::host::state_up); +// _host3->set_state_type(checkable::hard); +// _host3->set_acknowledgement(AckType::NONE); +// _host3->set_notify_on(static_cast(-1)); +// +// std::string out; +// service_map const& sm{engine::service::services}; +// _svc = sm.begin()->second; +// _svc->set_current_state(engine::service::state_ok); +// _svc->set_state_type(checkable::hard); +// _svc->set_acknowledgement(AckType::NORMAL); +// _svc->set_notify_on(static_cast(-1)); +// +// set_time(50000); +// _svc->set_current_state(engine::service::state_ok); +// _svc->set_last_hard_state(engine::service::state_ok); +// _svc->set_last_hard_state_change(50000); +// _svc->set_state_type(checkable::hard); +// _svc->set_first_notification_delay(3); +// +// for (int i = 1; i < 4; i++) { +// // When i == 0, the state_down is soft => no notification +// // When i == 1, the state_down is soft => no notification +// // When i == 2, the state_down is hard down => notification +// set_time(50000 + i * 60); +// _svc->set_last_state(_svc->get_current_state()); +// if (notifier::hard == _svc->get_state_type()) +// _svc->set_last_hard_state(_svc->get_current_state()); +// std::ostringstream oss; +// std::time_t now{std::time(nullptr)}; +// oss << '[' << now << ']' +// << " PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;service " +// "critical"; +// std::string cmd{oss.str()}; +// process_external_command(cmd.c_str()); +// checks::checker::instance().reap(); +// } +// nagios_macros* mac(get_global_macros()); +// process_macros_r(mac, "$SERVICEPROBLEMID:test_host:test_svc$", out, 0); +// ASSERT_EQ(out, "4"); +//} +// +//TEST_F(MacroService, LastServiceProblemID) { +// configuration::applier::host hst_aply; +// configuration::applier::service svc_aply; +// configuration::service svc; +// configuration::host hst; +// +// ASSERT_TRUE(hst.parse("host_name", "test_host")); +// ASSERT_TRUE(hst.parse("address", "127.0.0.1")); +// ASSERT_TRUE(hst.parse("_HOST_ID", "12")); +// ASSERT_TRUE(hst.parse("contacts", "user")); +// ASSERT_NO_THROW(hst_aply.add_object(hst)); +// ASSERT_EQ(1u, host::hosts.size()); +// ASSERT_TRUE(svc.parse("description", "test_svc")); +// ASSERT_TRUE(svc.parse("host_name", "test_host")); +// ASSERT_TRUE(svc.parse("_HOST_ID", "12")); +// ASSERT_TRUE(svc.parse("_SERVICE_ID", "13")); +// ASSERT_TRUE(svc.parse("action_url", "test_action_url")); +// svc.set_host_id(12); +// +// configuration::command cmd("cmd"); +// cmd.parse("command_line", "echo 'output| metric=12;50;75'"); +// svc.parse("check_command", "cmd"); +// configuration::applier::command cmd_aply; +// cmd_aply.add_object(cmd); +// ASSERT_NO_THROW(svc_aply.add_object(svc)); +// ASSERT_EQ(1u, service::services.size()); +// init_macros(); +// int now{500000000}; +// set_time(now); +// +// std::string out; +// nagios_macros* mac(get_global_macros()); +// service::services[std::make_pair("test_host", "test_svc")] +// ->set_long_plugin_output("test_long_output"); +// process_macros_r(mac, "$LASTSERVICEPROBLEMID:test_host:test_svc$", out, 1); +// ASSERT_EQ(out, "0"); +//} diff --git a/engine/tests/notifications/host_downtime_notification.cc b/engine/tests/notifications/host_downtime_notification.cc index b0d5ab373dd..08308cd33a1 100644 --- a/engine/tests/notifications/host_downtime_notification.cc +++ b/engine/tests/notifications/host_downtime_notification.cc @@ -31,7 +31,11 @@ #include "com/centreon/engine/exceptions/error.hh" #include "com/centreon/engine/hostescalation.hh" #include "com/centreon/engine/timeperiod.hh" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/host.hh" +#else +#include "common/engine_conf/timeperiod_helper.hh" +#endif #include "helper.hh" using namespace com::centreon; @@ -46,12 +50,22 @@ class HostDowntimeNotification : public TestEngine { init_config_state(); configuration::applier::contact ct_aply; +#ifdef LEGACY_CONF configuration::contact ctct{new_configuration_contact("admin", true)}; ct_aply.add_object(ctct); ct_aply.expand_objects(*config); +#else + configuration::Contact ctct{new_pb_configuration_contact("admin", true)}; + ct_aply.add_object(ctct); + ct_aply.expand_objects(pb_config); +#endif ct_aply.resolve_object(ctct, err); +#ifdef LEGACY_CONF configuration::host hst{new_configuration_host("test_host", "admin")}; +#else + configuration::Host hst{new_pb_configuration_host("test_host", "admin")}; +#endif configuration::applier::host aply; aply.add_object(hst); aply.resolve_object(hst, err); @@ -84,10 +98,33 @@ TEST_F(HostDowntimeNotification, SimpleHostDowntime) { */ set_time(43000); _host->set_last_hard_state_change(43000); +#ifdef LEGACY_CONF std::unique_ptr tperiod{ new engine::timeperiod("tperiod", "alias")}; for (size_t i = 0; i < tperiod->days.size(); ++i) tperiod->days[i].emplace_back(0, 86400); +#else + configuration::Timeperiod tp; + configuration::timeperiod_helper tp_hlp(&tp); + tp.set_timeperiod_name("tperiod"); + tp.set_alias("alias"); +#define add_day(day) \ + { \ + auto* d = tp.mutable_timeranges()->add_##day(); \ + d->set_range_start(0); \ + d->set_range_end(86400); \ + } + + add_day(sunday); + add_day(monday); + add_day(tuesday); + add_day(wednesday); + add_day(thursday); + add_day(friday); + add_day(saturday); + + std::unique_ptr tperiod{new engine::timeperiod(tp)}; +#endif std::unique_ptr host_escalation{ new engine::hostescalation("host_name", 0, 1, 1.0, "tperiod", 7, 12345)}; @@ -133,10 +170,26 @@ TEST_F(HostDowntimeNotification, contact_map::iterator it{engine::contact::contacts.find("admin")}; engine::contact* ctct{it->second.get()}; ctct->set_notify_on(notifier::host_notification, notifier::none); +#ifdef LEGACY_CONF std::unique_ptr tperiod{ new engine::timeperiod("tperiod", "alias")}; for (size_t i = 0; i < tperiod->days.size(); ++i) tperiod->days[i].emplace_back(0, 86400); +#else + configuration::Timeperiod tp; + configuration::timeperiod_helper tp_hlp(&tp); + tp.set_timeperiod_name("tperiod"); + tp.set_alias("alias"); + add_day(sunday); + add_day(monday); + add_day(tuesday); + add_day(wednesday); + add_day(thursday); + add_day(friday); + add_day(saturday); + + std::unique_ptr tperiod{new engine::timeperiod(tp)}; +#endif std::unique_ptr host_escalation{ new engine::hostescalation("host_name", 0, 1, 1.0, "tperiod", 7, 12345)}; diff --git a/engine/tests/notifications/host_flapping_notification.cc b/engine/tests/notifications/host_flapping_notification.cc index b9b98014e8c..4fa9584d6a1 100644 --- a/engine/tests/notifications/host_flapping_notification.cc +++ b/engine/tests/notifications/host_flapping_notification.cc @@ -29,8 +29,13 @@ #include "com/centreon/engine/host.hh" #include "com/centreon/engine/hostescalation.hh" #include "com/centreon/engine/timezone_manager.hh" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/host.hh" #include "common/engine_legacy_conf/state.hh" +#else +#include "common/engine_conf/contact_helper.hh" +#include "common/engine_conf/host_helper.hh" +#endif #include "helper.hh" using namespace com::centreon; @@ -45,17 +50,32 @@ class HostFlappingNotification : public TestEngine { init_config_state(); configuration::applier::contact ct_aply; +#ifdef LEGACY_CONF configuration::contact ctct{new_configuration_contact("admin", true)}; ct_aply.add_object(ctct); ct_aply.expand_objects(*config); +#else + configuration::Contact ctct{new_pb_configuration_contact("admin", true)}; + ct_aply.add_object(ctct); + ct_aply.expand_objects(pb_config); +#endif ct_aply.resolve_object(ctct, err); configuration::applier::host hst_aply; +#ifdef LEGACY_CONF configuration::host hst; hst.parse("host_name", "test_host"); hst.parse("address", "127.0.0.1"); hst.parse("_HOST_ID", "12"); hst.parse("contacts", "admin"); +#else + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst_hlp.hook("contacts", "admin"); +#endif hst_aply.add_object(hst); hst_aply.resolve_object(hst, err); host_map const& hm{engine::host::hosts}; @@ -65,12 +85,22 @@ class HostFlappingNotification : public TestEngine { _host->set_acknowledgement(AckType::NONE); _host->set_notify_on(static_cast(-1)); +#ifdef LEGACY_CONF configuration::host hst_child; hst_child.parse("host_name", "child_host"); hst_child.parse("parents", "test_host"); hst_child.parse("address", "127.0.0.1"); hst_child.parse("_HOST_ID", "13"); hst_child.parse("contacts", "admin"); +#else + configuration::Host hst_child; + configuration::host_helper hst_child_hlp(&hst_child); + hst_child.set_host_name("child_host"); + hst_child.set_address("127.0.0.1"); + hst_child_hlp.hook("parents", "test_host"); + hst_child.set_host_id(13); + hst_child_hlp.hook("contacts", "admin"); +#endif hst_aply.add_object(hst_child); hst_aply.resolve_object(hst_child, err); @@ -104,12 +134,9 @@ TEST_F(HostFlappingNotification, SimpleHostFlapping) { * If we call time(), it is not the glibc time() function that will be called. */ set_time(43000); - // FIXME DBR: should not we find a better solution than fixing this each time? _host->set_last_hard_state_change(43000); std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; - for (size_t i = 0; i < tperiod->days.size(); ++i) - tperiod->days[i].emplace_back(0, 86400); + new_timeperiod_with_timeranges("tperiod", "alias")}; std::unique_ptr host_escalation = std::make_unique("host_name", 0, 1, 1.0, @@ -157,10 +184,33 @@ TEST_F(HostFlappingNotification, SimpleHostFlappingStartTwoTimes) { */ set_time(43000); _host->set_notification_interval(2); +#ifdef LEGACY_CONF std::unique_ptr tperiod{ new engine::timeperiod("tperiod", "alias")}; for (uint32_t i = 0; i < tperiod->days.size(); ++i) tperiod->days[i].emplace_back(0, 86400); +#else + configuration::Timeperiod tp; + configuration::timeperiod_helper tp_hlp(&tp); + tp.set_timeperiod_name("tperiod"); + tp.set_alias("alias"); +#define add_day(day) \ + { \ + auto* d = tp.mutable_timeranges()->add_##day(); \ + d->set_range_start(0); \ + d->set_range_end(86400); \ + } + + add_day(sunday); + add_day(monday); + add_day(tuesday); + add_day(wednesday); + add_day(thursday); + add_day(friday); + add_day(saturday); + + std::unique_ptr tperiod{new engine::timeperiod(tp)}; +#endif std::unique_ptr host_escalation{ new engine::hostescalation("host_name", 0, 1, 1.0, "tperiod", 7, 12345)}; @@ -196,10 +246,33 @@ TEST_F(HostFlappingNotification, SimpleHostFlappingStopTwoTimes) { */ set_time(43000); _host->set_notification_interval(2); +#ifdef LEGACY_CONF std::unique_ptr tperiod{ new engine::timeperiod("tperiod", "alias")}; for (uint32_t i = 0; i < tperiod->days.size(); ++i) tperiod->days[i].emplace_back(0, 86400); +#else + configuration::Timeperiod tp; + configuration::timeperiod_helper tp_hlp(&tp); + tp.set_timeperiod_name("tperiod"); + tp.set_alias("alias"); +#define add_day(day) \ + { \ + auto* d = tp.mutable_timeranges()->add_##day(); \ + d->set_range_start(0); \ + d->set_range_end(86400); \ + } + + add_day(sunday); + add_day(monday); + add_day(tuesday); + add_day(wednesday); + add_day(thursday); + add_day(friday); + add_day(saturday); + + std::unique_ptr tperiod{new engine::timeperiod(tp)}; +#endif std::unique_ptr host_escalation{ new engine::hostescalation("host_name", 0, 1, 1.0, "tperiod", 7, 12345)}; @@ -229,7 +302,11 @@ TEST_F(HostFlappingNotification, SimpleHostFlappingStopTwoTimes) { } TEST_F(HostFlappingNotification, CheckFlapping) { +#ifdef LEGACY_CONF config->enable_flap_detection(true); +#else + pb_config.set_enable_flap_detection(true); +#endif _host->set_flap_detection_enabled(true); _host->add_flap_detection_on(engine::host::up); _host->add_flap_detection_on(engine::host::down); @@ -290,7 +367,11 @@ TEST_F(HostFlappingNotification, CheckFlapping) { } TEST_F(HostFlappingNotification, CheckFlappingWithHostParentDown) { +#ifdef LEGACY_CONF config->enable_flap_detection(true); +#else + pb_config.set_enable_flap_detection(true); +#endif _host->set_current_state(engine::host::state_down); _host->set_last_hard_state(engine::host::state_down); _host->set_state_type(checkable::hard); diff --git a/engine/tests/notifications/host_normal_notification.cc b/engine/tests/notifications/host_normal_notification.cc index 661c41d943a..47755ca3236 100644 --- a/engine/tests/notifications/host_normal_notification.cc +++ b/engine/tests/notifications/host_normal_notification.cc @@ -38,9 +38,12 @@ #include "com/centreon/engine/globals.hh" #include "com/centreon/engine/retention/dump.hh" #include "com/centreon/engine/timezone_manager.hh" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/host.hh" #include "common/engine_legacy_conf/hostescalation.hh" #include "common/engine_legacy_conf/state.hh" +#else +#endif using namespace com::centreon; using namespace com::centreon::engine; @@ -58,12 +61,26 @@ class HostNotification : public TestEngine { error_cnt err; configuration::applier::contact ct_aply; +#ifdef LEGACY_CONF configuration::contact ctct{new_configuration_contact("admin", true)}; ct_aply.add_object(ctct); +#ifdef LEGACY_CONF ct_aply.expand_objects(*config); +#else + ct_aply.expand_objects(pb_config); +#endif +#else + configuration::Contact ctct{new_pb_configuration_contact("admin", true)}; + ct_aply.add_object(ctct); + ct_aply.expand_objects(pb_config); +#endif ct_aply.resolve_object(ctct, err); +#ifdef LEGACY_CONF configuration::host hst{new_configuration_host("test_host", "admin")}; +#else + configuration::Host hst{new_pb_configuration_host("test_host", "admin")}; +#endif configuration::applier::host hst_aply; hst_aply.add_object(hst); hst_aply.resolve_object(hst, err); @@ -91,7 +108,7 @@ TEST_F(HostNotification, SimpleNormalHostNotification) { */ set_time(43200); std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; for (int i = 0; i < 7; ++i) tperiod->days[i].emplace_back(0, 86400); @@ -115,10 +132,14 @@ TEST_F(HostNotification, SimpleNormalHostNotificationNotificationsdisabled) { /* We are using a local time() function defined in tests/timeperiod/utils.cc. * If we call time(), it is not the glibc time() function that will be called. */ +#ifdef LEGACY_CONF config->enable_notifications(false); +#else + pb_config.set_enable_notifications(false); +#endif set_time(43200); std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; for (int i = 0; i < 7; ++i) tperiod->days[i].emplace_back(0, 86400); @@ -140,7 +161,7 @@ TEST_F(HostNotification, SimpleNormalHostNotificationNotifierNotifdisabled) { */ set_time(43200); std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; for (int i = 0; i < 7; ++i) tperiod->days[i].emplace_back(0, 86400); @@ -159,7 +180,7 @@ TEST_F(HostNotification, SimpleNormalHostNotificationNotifierNotifdisabled) { TEST_F(HostNotification, SimpleNormalHostNotificationOutsideTimeperiod) { std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; set_time(20000); uint64_t id{_host->get_next_notification_id()}; @@ -179,9 +200,13 @@ TEST_F(HostNotification, SimpleNormalHostNotificationOutsideTimeperiod) { TEST_F(HostNotification, SimpleNormalHostNotificationForcedWithNotificationDisabled) { +#ifdef LEGACY_CONF config->enable_notifications(false); +#else + pb_config.set_enable_notifications(false); +#endif std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; set_time(20000); uint64_t id{_host->get_next_notification_id()}; @@ -201,7 +226,7 @@ TEST_F(HostNotification, TEST_F(HostNotification, SimpleNormalHostNotificationForcedNotification) { std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; set_time(20000); uint64_t id{_host->get_next_notification_id()}; @@ -221,7 +246,7 @@ TEST_F(HostNotification, SimpleNormalHostNotificationForcedNotification) { TEST_F(HostNotification, SimpleNormalHostNotificationWithDowntime) { std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; set_time(20000); _host->set_scheduled_downtime_depth(30); @@ -242,7 +267,7 @@ TEST_F(HostNotification, SimpleNormalHostNotificationWithDowntime) { TEST_F(HostNotification, SimpleNormalHostNotificationWithFlapping) { std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; set_time(20000); _host->set_is_flapping(true); @@ -263,7 +288,7 @@ TEST_F(HostNotification, SimpleNormalHostNotificationWithFlapping) { TEST_F(HostNotification, SimpleNormalHostNotificationWithSoftState) { std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; set_time(20000); _host->set_state_type(checkable::soft); @@ -285,7 +310,7 @@ TEST_F(HostNotification, SimpleNormalHostNotificationWithSoftState) { TEST_F(HostNotification, SimpleNormalHostNotificationWithHardStateAcknowledged) { std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; set_time(20000); uint64_t id{_host->get_next_notification_id()}; @@ -306,7 +331,7 @@ TEST_F(HostNotification, TEST_F(HostNotification, SimpleNormalHostNotificationAfterPreviousTooSoon) { std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; set_time(20000); uint64_t id{_host->get_next_notification_id()}; @@ -329,7 +354,7 @@ TEST_F(HostNotification, SimpleNormalHostNotificationAfterPreviousTooSoon) { TEST_F(HostNotification, SimpleNormalHostNotificationAfterPreviousWithNullInterval) { std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; set_time(20000); uint64_t id{_host->get_next_notification_id()}; @@ -353,7 +378,7 @@ TEST_F(HostNotification, TEST_F(HostNotification, SimpleNormalHostNotificationOnStateNotNotified) { std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; set_time(20000); uint64_t id{_host->get_next_notification_id()}; @@ -377,7 +402,7 @@ TEST_F(HostNotification, SimpleNormalHostNotificationOnStateNotNotified) { TEST_F(HostNotification, SimpleNormalHostNotificationOnStateBeforeFirstNotifDelay) { std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; set_time(20000); uint64_t id{_host->get_next_notification_id()}; @@ -403,7 +428,7 @@ TEST_F(HostNotification, TEST_F(HostNotification, SimpleNormalHostNotificationOnStateAfterFirstNotifDelay) { std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; set_time(20000); uint64_t id{_host->get_next_notification_id()}; @@ -431,7 +456,7 @@ TEST_F(HostNotification, SimpleNormalHostNotificationNotifierDelayTooShort) { */ set_time(43200); std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; for (uint32_t i = 0; i < tperiod->days.size(); ++i) tperiod->days[i].emplace_back(0, 86400); @@ -560,23 +585,51 @@ TEST_F(HostNotification, CheckFirstNotificationDelay) { TEST_F(HostNotification, HostEscalation) { error_cnt err; configuration::applier::contact ct_aply; +#ifdef LEGACY_CONF configuration::contact ctct{new_configuration_contact("test_contact", false)}; +#else + configuration::Contact ctct{ + new_pb_configuration_contact("test_contact", false)}; +#endif ct_aply.add_object(ctct); +#ifdef LEGACY_CONF ct_aply.expand_objects(*config); +#else + ct_aply.expand_objects(pb_config); +#endif ct_aply.resolve_object(ctct, err); configuration::applier::contactgroup cg_aply; +#ifdef LEGACY_CONF configuration::contactgroup cg{ new_configuration_contactgroup("test_cg", "test_contact")}; +#else + configuration::Contactgroup cg; + configuration::contactgroup_helper cg_hlp(&cg); + fill_pb_configuration_contactgroup(&cg_hlp, "test_cg", "test_contact"); +#endif cg_aply.add_object(cg); +#ifdef LEGACY_CONF cg_aply.expand_objects(*config); +#else + cg_aply.expand_objects(pb_config); +#endif cg_aply.resolve_object(cg, err); configuration::applier::hostescalation he_aply; +#ifdef LEGACY_CONF configuration::hostescalation he{ new_configuration_hostescalation("test_host", "test_cg")}; +#else + configuration::Hostescalation he{ + new_pb_configuration_hostescalation("test_host", "test_cg")}; +#endif he_aply.add_object(he); +#ifdef LEGACY_CONF he_aply.expand_objects(*config); +#else + he_aply.expand_objects(pb_config); +#endif he_aply.resolve_object(he, err); int now{50000}; @@ -686,28 +739,61 @@ TEST_F(HostNotification, HostEscalation) { TEST_F(HostNotification, HostDependency) { error_cnt err; configuration::applier::contact ct_aply; +#ifdef LEGACY_CONF configuration::contact ctct{new_configuration_contact("test_contact", false)}; +#else + configuration::Contact ctct{ + new_pb_configuration_contact("test_contact", false)}; +#endif ct_aply.add_object(ctct); +#ifdef LEGACY_CONF ct_aply.expand_objects(*config); +#else + ct_aply.expand_objects(pb_config); +#endif ct_aply.resolve_object(ctct, err); configuration::applier::contactgroup cg_aply; +#ifdef LEGACY_CONF configuration::contactgroup cg{ new_configuration_contactgroup("test_cg", "test_contact")}; +#else + configuration::Contactgroup cg; + configuration::contactgroup_helper cg_hlp(&cg); + fill_pb_configuration_contactgroup(&cg_hlp, "test_cg", "test_contact"); +#endif cg_aply.add_object(cg); +#ifdef LEGACY_CONF cg_aply.expand_objects(*config); +#else + cg_aply.expand_objects(pb_config); +#endif cg_aply.resolve_object(cg, err); configuration::applier::host h_aply; +#ifdef LEGACY_CONF configuration::host h{new_configuration_host("dep_host", "admin", 15)}; +#else + configuration::Host h{new_pb_configuration_host("dep_host", "admin", 15)}; +#endif h_aply.add_object(h); +#ifdef LEGACY_CONF h_aply.expand_objects(*config); +#else + h_aply.expand_objects(pb_config); +#endif h_aply.resolve_object(h, err); configuration::applier::hostdependency hd_aply; +#ifdef LEGACY_CONF configuration::hostdependency hd{ new_configuration_hostdependency("test_host", "dep_host")}; hd_aply.expand_objects(*config); +#else + configuration::Hostdependency hd{ + new_pb_configuration_hostdependency("test_host", "dep_host")}; + hd_aply.expand_objects(pb_config); +#endif hd_aply.add_object(hd); hd_aply.resolve_object(hd, err); @@ -828,23 +914,49 @@ TEST_F(HostNotification, HostDependency) { TEST_F(HostNotification, HostEscalationOneTime) { error_cnt err; configuration::applier::contact ct_aply; +#ifdef LEGACY_CONF configuration::contact ctct{new_configuration_contact("test_contact", false)}; ct_aply.add_object(ctct); ct_aply.expand_objects(*config); +#else + configuration::Contact ctct{ + new_pb_configuration_contact("test_contact", false)}; + ct_aply.add_object(ctct); + ct_aply.expand_objects(pb_config); +#endif ct_aply.resolve_object(ctct, err); configuration::applier::contactgroup cg_aply; +#ifdef LEGACY_CONF configuration::contactgroup cg{ new_configuration_contactgroup("test_cg", "test_contact")}; +#else + configuration::Contactgroup cg; + configuration::contactgroup_helper cg_hlp(&cg); + fill_pb_configuration_contactgroup(&cg_hlp, "test_cg", "test_contact"); +#endif cg_aply.add_object(cg); +#ifdef LEGACY_CONF cg_aply.expand_objects(*config); +#else + cg_aply.expand_objects(pb_config); +#endif cg_aply.resolve_object(cg, err); configuration::applier::hostescalation he_aply; +#ifdef LEGACY_CONF configuration::hostescalation he{ new_configuration_hostescalation("test_host", "test_cg", 1, 0)}; +#else + configuration::Hostescalation he{ + new_pb_configuration_hostescalation("test_host", "test_cg", 1, 0)}; +#endif he_aply.add_object(he); +#ifdef LEGACY_CONF he_aply.expand_objects(*config); +#else + he_aply.expand_objects(pb_config); +#endif he_aply.resolve_object(he, err); int now{50000}; @@ -924,23 +1036,51 @@ TEST_F(HostNotification, HostEscalationOneTime) { TEST_F(HostNotification, HostEscalationOneTimeNotifInter0) { error_cnt err; configuration::applier::contact ct_aply; +#ifdef LEGACY_CONF configuration::contact ctct{new_configuration_contact("test_contact", false)}; +#else + configuration::Contact ctct{ + new_pb_configuration_contact("test_contact", false)}; +#endif ct_aply.add_object(ctct); +#ifdef LEGACY_CONF ct_aply.expand_objects(*config); +#else + ct_aply.expand_objects(pb_config); +#endif ct_aply.resolve_object(ctct, err); configuration::applier::contactgroup cg_aply; +#ifdef LEGACY_CONF configuration::contactgroup cg{ new_configuration_contactgroup("test_cg", "test_contact")}; +#else + configuration::Contactgroup cg; + configuration::contactgroup_helper cg_hlp(&cg); + fill_pb_configuration_contactgroup(&cg_hlp, "test_cg", "test_contact"); +#endif cg_aply.add_object(cg); +#ifdef LEGACY_CONF cg_aply.expand_objects(*config); +#else + cg_aply.expand_objects(pb_config); +#endif cg_aply.resolve_object(cg, err); configuration::applier::hostescalation he_aply; +#ifdef LEGACY_CONF configuration::hostescalation he{ new_configuration_hostescalation("test_host", "test_cg", 1, 0, 0)}; +#else + configuration::Hostescalation he{ + new_pb_configuration_hostescalation("test_host", "test_cg", 1, 0, 0)}; +#endif he_aply.add_object(he); +#ifdef LEGACY_CONF he_aply.expand_objects(*config); +#else + he_aply.expand_objects(pb_config); +#endif he_aply.resolve_object(he, err); int now{50000}; @@ -1020,23 +1160,51 @@ TEST_F(HostNotification, HostEscalationOneTimeNotifInter0) { TEST_F(HostNotification, HostEscalationRetention) { error_cnt err; configuration::applier::contact ct_aply; +#ifdef LEGACY_CONF configuration::contact ctct{new_configuration_contact("test_contact", false)}; +#else + configuration::Contact ctct{ + new_pb_configuration_contact("test_contact", false)}; +#endif ct_aply.add_object(ctct); +#ifdef LEGACY_CONF ct_aply.expand_objects(*config); +#else + ct_aply.expand_objects(pb_config); +#endif ct_aply.resolve_object(ctct, err); configuration::applier::contactgroup cg_aply; +#ifdef LEGACY_CONF configuration::contactgroup cg{ new_configuration_contactgroup("test_cg", "test_contact")}; +#else + configuration::Contactgroup cg; + configuration::contactgroup_helper cg_hlp(&cg); + fill_pb_configuration_contactgroup(&cg_hlp, "test_cg", "test_contact"); +#endif cg_aply.add_object(cg); +#ifdef LEGACY_CONF cg_aply.expand_objects(*config); +#else + cg_aply.expand_objects(pb_config); +#endif cg_aply.resolve_object(cg, err); configuration::applier::hostescalation he_aply; +#ifdef LEGACY_CONF configuration::hostescalation he{ new_configuration_hostescalation("test_host", "test_cg", 1, 0, 0)}; +#else + configuration::Hostescalation he{ + new_pb_configuration_hostescalation("test_host", "test_cg", 1, 0, 0)}; +#endif he_aply.add_object(he); +#ifdef LEGACY_CONF he_aply.expand_objects(*config); +#else + he_aply.expand_objects(pb_config); +#endif he_aply.resolve_object(he, err); int now{50000}; diff --git a/engine/tests/notifications/host_recovery_notification.cc b/engine/tests/notifications/host_recovery_notification.cc index eb9663bc564..ef6fd962e82 100644 --- a/engine/tests/notifications/host_recovery_notification.cc +++ b/engine/tests/notifications/host_recovery_notification.cc @@ -27,14 +27,19 @@ #include "com/centreon/engine/exceptions/error.hh" #include "com/centreon/engine/host.hh" #include "com/centreon/engine/hostescalation.hh" +#include "test_engine.hh" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/host.hh" +#else +#include "common/engine_conf/host_helper.hh" +#endif using namespace com::centreon; using namespace com::centreon::engine; using namespace com::centreon::engine::configuration; using namespace com::centreon::engine::configuration::applier; -class HostRecovery : public ::testing::Test { +class HostRecovery : public TestEngine { public: void SetUp() override { init_config_state(); @@ -42,10 +47,18 @@ class HostRecovery : public ::testing::Test { // other unload function... :-( configuration::applier::host hst_aply; +#ifdef LEGACY_CONF configuration::host hst; hst.parse("host_name", "test_host"); hst.parse("address", "127.0.0.1"); hst.parse("_HOST_ID", "12"); +#else + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); +#endif hst_aply.add_object(hst); host_map const& hm{engine::host::hosts}; _host = hm.begin()->second; @@ -56,7 +69,8 @@ class HostRecovery : public ::testing::Test { _host->set_notify_on(static_cast(-1)); _current_time = 43200; set_time(_current_time); - _tperiod.reset(new engine::timeperiod("tperiod", "alias")); + + _tperiod = new_timeperiod_with_timeranges("tperiod", "alias"); for (size_t i = 0; i < _tperiod->days.size(); ++i) _tperiod->days[i].emplace_back(0, 86400); diff --git a/engine/tests/notifications/service_downtime_notification_test.cc b/engine/tests/notifications/service_downtime_notification_test.cc index 5c90fe68c7a..4eb2a88ef4b 100644 --- a/engine/tests/notifications/service_downtime_notification_test.cc +++ b/engine/tests/notifications/service_downtime_notification_test.cc @@ -1,5 +1,5 @@ /** - * Copyright 2019 Centreon (https://www.centreon.com/) + * Copyright 2019-2024 Centreon (https://www.centreon.com/) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,8 +37,10 @@ #include "com/centreon/engine/configuration/applier/serviceescalation.hh" #include "com/centreon/engine/exceptions/error.hh" #include "com/centreon/engine/serviceescalation.hh" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/host.hh" #include "common/engine_legacy_conf/service.hh" +#endif #include "helper.hh" using namespace com::centreon; @@ -53,17 +55,34 @@ class ServiceDowntimeNotification : public TestEngine { error_cnt err; configuration::applier::contact ct_aply; +#ifdef LEGACY_CONF configuration::contact ctct{new_configuration_contact("admin", true)}; +#else + configuration::Contact ctct{new_pb_configuration_contact("admin", true)}; +#endif ct_aply.add_object(ctct); +#ifdef LEGACY_CONF ct_aply.expand_objects(*config); +#else + ct_aply.expand_objects(pb_config); +#endif ct_aply.resolve_object(ctct, err); +#ifdef LEGACY_CONF configuration::host hst{new_configuration_host("test_host", "admin")}; +#else + configuration::Host hst{new_pb_configuration_host("test_host", "admin")}; +#endif configuration::applier::host hst_aply; hst_aply.add_object(hst); +#ifdef LEGACY_CONF configuration::service svc{ new_configuration_service("test_host", "test_svc", "admin")}; +#else + configuration::Service svc{ + new_pb_configuration_service("test_host", "test_svc", "admin")}; +#endif configuration::applier::service svc_aply; svc_aply.add_object(svc); diff --git a/engine/tests/notifications/service_flapping_notification.cc b/engine/tests/notifications/service_flapping_notification.cc index 0a3d97838d4..39160b52ddb 100644 --- a/engine/tests/notifications/service_flapping_notification.cc +++ b/engine/tests/notifications/service_flapping_notification.cc @@ -32,8 +32,10 @@ #include "com/centreon/engine/serviceescalation.hh" #include "com/centreon/engine/timezone_manager.hh" #include "com/centreon/process_manager.hh" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/host.hh" #include "common/engine_legacy_conf/service.hh" +#endif #include "helper.hh" using namespace com::centreon; @@ -49,32 +51,66 @@ class ServiceFlappingNotification : public TestEngine { init_config_state(); configuration::applier::contact ct_aply; +#ifdef LEGACY_CONF configuration::contact ctct{new_configuration_contact("admin", true)}; +#else + configuration::Contact ctct{new_pb_configuration_contact("admin", true)}; +#endif ct_aply.add_object(ctct); +#ifdef LEGACY_CONF ct_aply.expand_objects(*config); +#else + ct_aply.expand_objects(pb_config); +#endif ct_aply.resolve_object(ctct, err); configuration::applier::command cmd_aply; +#ifdef LEGACY_CONF configuration::command cmd("cmd"); cmd.parse("command_line", "echo 1"); +#else + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + cmd.set_command_line("echo 1"); +#endif cmd_aply.add_object(cmd); configuration::applier::host hst_aply; +#ifdef LEGACY_CONF configuration::host hst; hst.parse("host_name", "test_host"); hst.parse("address", "127.0.0.1"); hst.parse("_HOST_ID", "12"); hst.parse("check_command", "cmd"); +#else + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name("test_host"); + hst.set_address("127.0.0.1"); + hst.set_host_id(12); + hst.set_check_command("cmd"); +#endif hst_aply.add_object(hst); hst_aply.resolve_object(hst, err); configuration::applier::service svc_aply; +#ifdef LEGACY_CONF configuration::service svc; svc.parse("host", "test_host"); svc.parse("service_description", "test_description"); svc.parse("_SERVICE_ID", "12"); svc.parse("check_command", "cmd"); svc.parse("contacts", "admin"); +#else + configuration::Service svc; + configuration::service_helper svc_hlp(&svc); + svc.set_host_name("test_host"); + svc.set_service_description("test_description"); + svc.set_service_id(12); + svc.set_check_command("cmd"); + svc_hlp.hook("contacts", "admin"); +#endif // We fake here the expand_object on configuration::service svc.set_host_id(12); @@ -125,9 +161,7 @@ TEST_F(ServiceFlappingNotification, SimpleServiceFlapping) { // FIXME DBR: should not we find a better solution than fixing this each time? _service->set_last_hard_state_change(43000); std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; - for (size_t i = 0; i < tperiod->days.size(); ++i) - tperiod->days[i].emplace_back(0, 86400); + new_timeperiod_with_timeranges("tperiod", "alias")}; std::unique_ptr service_escalation{ new engine::serviceescalation("host_name", "test_description", 0, 1, 1.0, @@ -178,9 +212,7 @@ TEST_F(ServiceFlappingNotification, SimpleServiceFlappingStartTwoTimes) { set_time(43000); _service->set_notification_interval(2); std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; - for (uint32_t i = 0; i < tperiod->days.size(); ++i) - tperiod->days[i].emplace_back(0, 86400); + new_timeperiod_with_timeranges("tperiod", "alias")}; std::unique_ptr service_escalation{ new engine::serviceescalation("host_name", "test_description", 0, 1, 1.0, @@ -218,9 +250,7 @@ TEST_F(ServiceFlappingNotification, SimpleServiceFlappingStopTwoTimes) { set_time(43000); _service->set_notification_interval(2); std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; - for (uint32_t i = 0; i < tperiod->days.size(); ++i) - tperiod->days[i].emplace_back(0, 86400); + new_timeperiod_with_timeranges("tperiod", "alias")}; std::unique_ptr service_escalation{ new engine::serviceescalation("host_name", "test_description", 0, 1, 1.0, @@ -251,7 +281,11 @@ TEST_F(ServiceFlappingNotification, SimpleServiceFlappingStopTwoTimes) { } TEST_F(ServiceFlappingNotification, CheckFlapping) { +#ifdef LEGACY_CONF config->enable_flap_detection(true); +#else + pb_config.set_enable_flap_detection(true); +#endif _service->set_flap_detection_enabled(true); _service->add_flap_detection_on(engine::service::ok); _service->add_flap_detection_on(engine::service::down); @@ -343,7 +377,11 @@ TEST_F(ServiceFlappingNotification, CheckFlapping) { } TEST_F(ServiceFlappingNotification, CheckFlappingWithVolatile) { +#ifdef LEGACY_CONF config->enable_flap_detection(true); +#else + pb_config.set_enable_flap_detection(true); +#endif _service->set_flap_detection_enabled(true); _service->set_is_volatile(true); _service->add_flap_detection_on(engine::service::ok); @@ -445,7 +483,11 @@ TEST_F(ServiceFlappingNotification, CheckFlappingWithVolatile) { TEST_F(ServiceFlappingNotification, CheckFlappingWithHostDown) { _host->set_current_state(engine::host::state_down); _host->set_state_type(checkable::hard); +#ifdef LEGACY_CONF config->enable_flap_detection(true); +#else + pb_config.set_enable_flap_detection(true); +#endif _service->set_flap_detection_enabled(true); _service->add_flap_detection_on(engine::service::ok); _service->add_flap_detection_on(engine::service::down); @@ -534,7 +576,11 @@ TEST_F(ServiceFlappingNotification, CheckFlappingWithHostDown) { } TEST_F(ServiceFlappingNotification, CheckFlappingWithSoftState) { +#ifdef LEGACY_CONF config->enable_flap_detection(true); +#else + pb_config.set_enable_flap_detection(true); +#endif _service->set_flap_detection_enabled(true); _service->add_flap_detection_on(engine::service::ok); _service->add_flap_detection_on(engine::service::down); @@ -623,7 +669,11 @@ TEST_F(ServiceFlappingNotification, CheckFlappingWithSoftState) { } TEST_F(ServiceFlappingNotification, RetentionFlappingNotification) { +#ifdef LEGACY_CONF config->enable_flap_detection(true); +#else + pb_config.set_enable_flap_detection(true); +#endif _service->set_flap_detection_enabled(true); _service->add_flap_detection_on(engine::service::ok); _service->add_flap_detection_on(engine::service::down); diff --git a/engine/tests/notifications/service_normal_notification.cc b/engine/tests/notifications/service_normal_notification.cc index a0fe779eb99..33cf5f1e6b8 100644 --- a/engine/tests/notifications/service_normal_notification.cc +++ b/engine/tests/notifications/service_normal_notification.cc @@ -37,8 +37,10 @@ #include "com/centreon/engine/configuration/applier/serviceescalation.hh" #include "com/centreon/engine/exceptions/error.hh" #include "com/centreon/engine/serviceescalation.hh" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/host.hh" #include "common/engine_legacy_conf/service.hh" +#endif #include "helper.hh" using namespace com::centreon; @@ -53,21 +55,37 @@ class ServiceNotification : public TestEngine { error_cnt err; configuration::applier::contact ct_aply; +#ifdef LEGACY_CONF configuration::contact ctct{new_configuration_contact("admin", true)}; - ct_aply.add_object(ctct); configuration::contact ctct1{ new_configuration_contact("admin1", false, "c,r")}; +#else + configuration::Contact ctct{new_pb_configuration_contact("admin", true)}; + configuration::Contact ctct1{ + new_pb_configuration_contact("admin1", false, "c,r")}; +#endif + ct_aply.add_object(ctct); ct_aply.add_object(ctct1); +#ifdef LEGACY_CONF ct_aply.expand_objects(*config); +#else + ct_aply.expand_objects(pb_config); +#endif ct_aply.resolve_object(ctct, err); ct_aply.resolve_object(ctct1, err); +#ifdef LEGACY_CONF configuration::host hst{new_configuration_host("test_host", "admin")}; + configuration::service svc{ + new_configuration_service("test_host", "test_svc", "admin,admin1")}; +#else + configuration::Host hst{new_pb_configuration_host("test_host", "admin")}; + configuration::Service svc{ + new_pb_configuration_service("test_host", "test_svc", "admin,admin1")}; +#endif configuration::applier::host hst_aply; hst_aply.add_object(hst); - configuration::service svc{ - new_configuration_service("test_host", "test_svc", "admin,admin1")}; configuration::applier::service svc_aply; svc_aply.add_object(svc); @@ -107,9 +125,7 @@ TEST_F(ServiceNotification, SimpleNormalServiceNotification) { ASSERT_EQ(_host->services.size(), 1u); set_time(43200); std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; - for (int i = 0; i < 7; ++i) - tperiod->days[i].emplace_back(0, 86400); + new_timeperiod_with_timeranges("tperiod", "alias")}; std::unique_ptr service_escalation{ new engine::serviceescalation("test_host", "test_svc", 0, 1, 1.0, @@ -133,12 +149,14 @@ TEST_F(ServiceNotification, /* We are using a local time() function defined in tests/timeperiod/utils.cc. * If we call time(), it is not the glibc time() function that will be called. */ +#ifdef LEGACY_CONF config->enable_notifications(false); +#else + pb_config.set_enable_notifications(false); +#endif set_time(43200); std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; - for (int i = 0; i < 7; ++i) - tperiod->days[i].emplace_back(0, 86400); + new_timeperiod_with_timeranges("tperiod", "alias")}; std::unique_ptr service_escalation{ new engine::serviceescalation("test_host", "test_svc", 0, 1, 1.0, @@ -160,9 +178,7 @@ TEST_F(ServiceNotification, */ set_time(43200); std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; - for (int i = 0; i < 7; ++i) - tperiod->days[i].emplace_back(0, 86400); + new_timeperiod_with_timeranges("tperiod", "alias")}; std::unique_ptr service_escalation{ new engine::serviceescalation("test_host", "test_svc", 0, 1, 1.0, @@ -180,7 +196,7 @@ TEST_F(ServiceNotification, TEST_F(ServiceNotification, SimpleNormalServiceNotificationOutsideTimeperiod) { std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; set_time(20000); uint64_t id{_svc->get_next_notification_id()}; @@ -201,9 +217,13 @@ TEST_F(ServiceNotification, SimpleNormalServiceNotificationOutsideTimeperiod) { TEST_F(ServiceNotification, SimpleNormalServiceNotificationForcedWithNotificationDisabled) { +#ifdef LEGACY_CONF config->enable_notifications(false); +#else + pb_config.set_enable_notifications(false); +#endif std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; set_time(20000); uint64_t id{_svc->get_next_notification_id()}; @@ -224,7 +244,7 @@ TEST_F(ServiceNotification, TEST_F(ServiceNotification, SimpleNormalServiceNotificationForcedNotification) { std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; set_time(20000); uint64_t id{_svc->get_next_notification_id()}; @@ -245,13 +265,11 @@ TEST_F(ServiceNotification, SimpleNormalServiceNotificationForcedNotification) { TEST_F(ServiceNotification, SimpleNormalServiceNotificationWithDowntime) { std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; set_time(20000); _svc->set_scheduled_downtime_depth(30); uint64_t id{_svc->get_next_notification_id()}; - for (int i = 0; i < 7; ++i) - tperiod->days[i].emplace_back(0, 86400); std::unique_ptr service_escalation{ new engine::serviceescalation("test_host", "test_svc", 0, 1, 1.0, "", 7, @@ -267,13 +285,11 @@ TEST_F(ServiceNotification, SimpleNormalServiceNotificationWithDowntime) { TEST_F(ServiceNotification, SimpleNormalServiceNotificationWithFlapping) { std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; set_time(20000); _svc->set_is_flapping(true); uint64_t id{_svc->get_next_notification_id()}; - for (int i = 0; i < 7; ++i) - tperiod->days[i].emplace_back(0, 86400); std::unique_ptr service_escalation{ new engine::serviceescalation("test_host", "test_svc", 0, 1, 1.0, "", 7, @@ -289,13 +305,11 @@ TEST_F(ServiceNotification, SimpleNormalServiceNotificationWithFlapping) { TEST_F(ServiceNotification, SimpleNormalServiceNotificationWithSoftState) { std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; set_time(20000); _svc->set_state_type(checkable::soft); uint64_t id{_svc->get_next_notification_id()}; - for (int i = 0; i < 7; ++i) - tperiod->days[i].emplace_back(0, 86400); std::unique_ptr service_escalation{ new engine::serviceescalation("test_host", "test_svc", 0, 1, 1.0, "", 7, @@ -312,12 +326,10 @@ TEST_F(ServiceNotification, SimpleNormalServiceNotificationWithSoftState) { TEST_F(ServiceNotification, SimpleNormalServiceNotificationWithHardStateAcknowledged) { std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; set_time(20000); uint64_t id{_svc->get_next_notification_id()}; - for (int i = 0; i < 7; ++i) - tperiod->days[i].emplace_back(0, 86400); std::unique_ptr service_escalation{ new engine::serviceescalation("test_host", "test_svc", 0, 1, 1.0, "", 7, @@ -335,12 +347,10 @@ TEST_F(ServiceNotification, TEST_F(ServiceNotification, SimpleNormalServiceNotificationAfterPreviousTooSoon) { std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; set_time(20000); uint64_t id{_svc->get_next_notification_id()}; - for (int i = 0; i < 7; ++i) - tperiod->days[i].emplace_back(0, 86400); std::unique_ptr service_escalation{ new engine::serviceescalation("test_host", "test_svc", 0, 1, 1.0, "", 7, @@ -359,12 +369,10 @@ TEST_F(ServiceNotification, TEST_F(ServiceNotification, SimpleNormalServiceNotificationAfterPreviousWithNullInterval) { std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; set_time(20000); uint64_t id{_svc->get_next_notification_id()}; - for (int i = 0; i < 7; ++i) - tperiod->days[i].emplace_back(0, 86400); std::unique_ptr service_escalation{ new engine::serviceescalation("test_host", "test_svc", 0, 1, 1.0, "", 7, @@ -384,12 +392,10 @@ TEST_F(ServiceNotification, TEST_F(ServiceNotification, SimpleNormalServiceNotificationOnStateNotNotified) { std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; set_time(20000); uint64_t id{_svc->get_next_notification_id()}; - for (int i = 0; i < 7; ++i) - tperiod->days[i].emplace_back(0, 86400); std::unique_ptr service_escalation{ new engine::serviceescalation("test_host", "test_svc", 0, 1, 1.0, "", 7, @@ -409,12 +415,10 @@ TEST_F(ServiceNotification, SimpleNormalServiceNotificationOnStateNotNotified) { TEST_F(ServiceNotification, SimpleNormalServiceNotificationOnStateBeforeFirstNotifDelay) { std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; set_time(20000); uint64_t id{_svc->get_next_notification_id()}; - for (int i = 0; i < 7; ++i) - tperiod->days[i].emplace_back(0, 86400); std::unique_ptr service_escalation{ new engine::serviceescalation("test_host", "test_svc", 0, 1, 1.0, "", 7, @@ -436,12 +440,10 @@ TEST_F(ServiceNotification, TEST_F(ServiceNotification, SimpleNormalServiceNotificationOnStateAfterFirstNotifDelay) { std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; set_time(20000); uint64_t id{_svc->get_next_notification_id()}; - for (int i = 0; i < 7; ++i) - tperiod->days[i].emplace_back(0, 86400); std::unique_ptr service_escalation{ new engine::serviceescalation("test_host", "test_svc", 0, 1, 1.0, "", 7, @@ -466,9 +468,7 @@ TEST_F(ServiceNotification, */ set_time(43200); std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; - for (uint32_t i = 0; i < tperiod->days.size(); ++i) - tperiod->days[i].emplace_back(0, 86400); + new_timeperiod_with_timeranges("tperiod", "alias")}; std::unique_ptr service_escalation{ new engine::serviceescalation("test_host", "test_svc", 0, 1, 1.0, @@ -757,24 +757,50 @@ TEST_F(ServiceNotification, NormalRecoveryTwoTimes) { TEST_F(ServiceNotification, ServiceEscalationCG) { init_macros(); configuration::applier::contact ct_aply; +#ifdef LEGACY_CONF configuration::contact ctct{new_configuration_contact("test_contact", false)}; +#else + configuration::Contact ctct{ + new_pb_configuration_contact("test_contact", false)}; +#endif ct_aply.add_object(ctct); +#ifdef LEGACY_CONF ct_aply.expand_objects(*config); +#else + ct_aply.expand_objects(pb_config); +#endif error_cnt err; ct_aply.resolve_object(ctct, err); configuration::applier::contactgroup cg_aply; +#ifdef LEGACY_CONF configuration::contactgroup cg{ new_configuration_contactgroup("test_cg", "test_contact")}; +#else + configuration::Contactgroup cg; + configuration::contactgroup_helper cg_hlp(&cg); + fill_pb_configuration_contactgroup(&cg_hlp, "test_cg", "test_contact"); +#endif cg_aply.add_object(cg); +#ifdef LEGACY_CONF cg_aply.expand_objects(*config); +#else + cg_aply.expand_objects(pb_config); +#endif cg_aply.resolve_object(cg, err); configuration::applier::serviceescalation se_aply; +#ifdef LEGACY_CONF configuration::serviceescalation se{ new_configuration_serviceescalation("test_host", "test_svc", "test_cg")}; se_aply.add_object(se); se_aply.expand_objects(*config); +#else + configuration::Serviceescalation se{new_pb_configuration_serviceescalation( + "test_host", "test_svc", "test_cg")}; + se_aply.add_object(se); + se_aply.expand_objects(pb_config); +#endif se_aply.resolve_object(se, err); int now{50000}; @@ -933,9 +959,7 @@ TEST_F(ServiceNotification, WarnCritServiceNotification) { ASSERT_EQ(_host->services.size(), 1u); set_time(43200); std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; - for (int i = 0; i < 7; ++i) - tperiod->days[i].emplace_back(0, 86400); + new_timeperiod_with_timeranges("tperiod", "alias")}; std::unique_ptr service_escalation{ new engine::serviceescalation("test_host", "test_svc", 0, 1, 1.0, @@ -970,9 +994,7 @@ TEST_F(ServiceNotification, SimpleNormalVolatileServiceNotification) { ASSERT_EQ(_host->services.size(), 1u); set_time(43200); std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; - for (int i = 0; i < 7; ++i) - tperiod->days[i].emplace_back(0, 86400); + new_timeperiod_with_timeranges("tperiod", "alias")}; std::unique_ptr service_escalation{ new engine::serviceescalation("test_host", "test_svc", 0, 1, 1.0, @@ -1005,7 +1027,11 @@ TEST_F(ServiceNotification, SimpleNormalVolatileServiceNotification) { id = _svc->get_next_notification_id(); _svc->set_notification_period_ptr(tperiod.get()); _svc->set_notifications_enabled(true); +#ifdef LEGACY_CONF config->enable_notifications(false); +#else + pb_config.set_enable_notifications(false); +#endif ASSERT_EQ(_svc->notify(notifier::reason_normal, "", "", notifier::notification_option_none), OK); @@ -1019,9 +1045,7 @@ TEST_F(ServiceNotification, RecoveryNotifEvenIfServiceAcknowledged) { ASSERT_EQ(_host->services.size(), 1u); set_time(43200); std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; - for (int i = 0; i < 7; ++i) - tperiod->days[i].emplace_back(0, 86400); + new_timeperiod_with_timeranges("tperiod", "alias")}; std::unique_ptr service_escalation{ new engine::serviceescalation("test_host", "test_svc", 0, 1, 1.0, @@ -1074,14 +1098,12 @@ TEST_F(ServiceNotification, RecoveryNotifEvenIfServiceAcknowledged) { TEST_F(ServiceNotification, SimpleVolatileServiceNotificationWithDowntime) { std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; set_time(20000); _svc->set_scheduled_downtime_depth(30); _svc->set_is_volatile(true); uint64_t id{_svc->get_next_notification_id()}; - for (int i = 0; i < 7; ++i) - tperiod->days[i].emplace_back(0, 86400); std::unique_ptr service_escalation{ new engine::serviceescalation("test_host", "test_svc", 0, 1, 1.0, "", 7, diff --git a/engine/tests/notifications/service_timeperiod_notification.cc b/engine/tests/notifications/service_timeperiod_notification.cc index ce4ffa184c9..0c893e8eb3c 100644 --- a/engine/tests/notifications/service_timeperiod_notification.cc +++ b/engine/tests/notifications/service_timeperiod_notification.cc @@ -39,9 +39,12 @@ #include "com/centreon/engine/exceptions/error.hh" #include "com/centreon/engine/serviceescalation.hh" #include "com/centreon/engine/timeperiod.hh" +#include "gtest/gtest.h" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/host.hh" #include "common/engine_legacy_conf/service.hh" #include "common/engine_legacy_conf/state.hh" +#endif #include "helper.hh" using namespace com::centreon; @@ -49,8 +52,6 @@ using namespace com::centreon::engine; using namespace com::centreon::engine::configuration; using namespace com::centreon::engine::configuration::applier; -// extern configuration::state* config; - class ServiceTimePeriodNotification : public TestEngine { public: void SetUp() override { @@ -58,21 +59,43 @@ class ServiceTimePeriodNotification : public TestEngine { init_config_state(); configuration::applier::contact ct_aply; +#ifdef LEGACY_CONF configuration::contact ctct{new_configuration_contact("admin", true)}; +#else + configuration::Contact ctct{new_pb_configuration_contact("admin", true)}; +#endif ct_aply.add_object(ctct); +#ifdef LEGACY_CONF configuration::contact ctct1{ new_configuration_contact("admin1", false, "c,r")}; +#else + configuration::Contact ctct1{ + new_pb_configuration_contact("admin1", false, "c,r")}; +#endif ct_aply.add_object(ctct1); +#ifdef LEGACY_CONF ct_aply.expand_objects(*config); +#else + ct_aply.expand_objects(pb_config); +#endif ct_aply.resolve_object(ctct, err); ct_aply.resolve_object(ctct1, err); +#ifdef LEGACY_CONF configuration::host hst{new_configuration_host("test_host", "admin")}; +#else + configuration::Host hst{new_pb_configuration_host("test_host", "admin")}; +#endif configuration::applier::host hst_aply; hst_aply.add_object(hst); +#ifdef LEGACY_CONF configuration::service svc{ new_configuration_service("test_host", "test_svc", "admin,admin1")}; +#else + configuration::Service svc{ + new_pb_configuration_service("test_host", "test_svc", "admin,admin1")}; +#endif configuration::applier::service svc_aply; svc_aply.add_object(svc); @@ -124,28 +147,55 @@ TEST_F(ServiceTimePeriodNotification, NoTimePeriodOk) { error_cnt err; init_macros(); std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; int now{20000}; set_time(now); configuration::applier::contact ct_aply; +#ifdef LEGACY_CONF configuration::contact ctct{new_configuration_contact("test_contact", false)}; +#else + configuration::Contact ctct{ + new_pb_configuration_contact("test_contact", false)}; +#endif ct_aply.add_object(ctct); +#ifdef LEGACY_CONF ct_aply.expand_objects(*config); +#else + ct_aply.expand_objects(pb_config); +#endif ct_aply.resolve_object(ctct, err); configuration::applier::contactgroup cg_aply; +#ifdef LEGACY_CONF configuration::contactgroup cg{ new_configuration_contactgroup("test_cg", "test_contact")}; +#else + configuration::Contactgroup cg{ + new_pb_configuration_contactgroup("test_cg", "test_contact")}; +#endif cg_aply.add_object(cg); +#ifdef LEGACY_CONF cg_aply.expand_objects(*config); +#else + cg_aply.expand_objects(pb_config); +#endif cg_aply.resolve_object(cg, err); configuration::applier::serviceescalation se_aply; +#ifdef LEGACY_CONF configuration::serviceescalation se{ new_configuration_serviceescalation("test_host", "test_svc", "test_cg")}; +#else + configuration::Serviceescalation se{new_pb_configuration_serviceescalation( + "test_host", "test_svc", "test_cg")}; +#endif se_aply.add_object(se); +#ifdef LEGACY_CONF se_aply.expand_objects(*config); +#else + se_aply.expand_objects(pb_config); +#endif se_aply.resolve_object(se, err); // uint64_t id{_svc->get_next_notification_id()}; @@ -229,24 +279,43 @@ TEST_F(ServiceTimePeriodNotification, NoTimePeriodKo) { error_cnt err; init_macros(); std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; int now{20000}; set_time(now); configuration::applier::contact ct_aply; +#ifdef LEGACY_CONF configuration::contact ctct{new_configuration_contact("test_contact", false)}; +#else + configuration::Contact ctct{ + new_pb_configuration_contact("test_contact", false)}; +#endif ct_aply.add_object(ctct); +#ifdef LEGACY_CONF ct_aply.expand_objects(*config); +#else + ct_aply.expand_objects(pb_config); +#endif ct_aply.resolve_object(ctct, err); configuration::applier::contactgroup cg_aply; +#ifdef LEGACY_CONF configuration::contactgroup cg{ new_configuration_contactgroup("test_cg", "test_contact")}; +#else + configuration::Contactgroup cg{ + new_pb_configuration_contactgroup("test_cg", "test_contact")}; +#endif cg_aply.add_object(cg); +#ifdef LEGACY_CONF cg_aply.expand_objects(*config); +#else + cg_aply.expand_objects(pb_config); +#endif cg_aply.resolve_object(cg, err); configuration::applier::serviceescalation se_aply; +#ifdef LEGACY_CONF configuration::serviceescalation se; se.parse("first_notification", "1"); se.parse("last_notification", "1"); @@ -255,8 +324,23 @@ TEST_F(ServiceTimePeriodNotification, NoTimePeriodKo) { se.parse("host_name", "test_host"); se.parse("service_description", "test_svc"); se.parse("contact_groups", "test_cg"); +#else + configuration::Serviceescalation se; + configuration::serviceescalation_helper se_hlp(&se); + se.set_first_notification(1); + se.set_last_notification(1); + se.set_notification_interval(0); + se_hlp.hook("escalation_options", "w,u,c,r"); + se_hlp.hook("host_name", "test_host"); + se_hlp.hook("service_description", "test_svc"); + se_hlp.hook("contact_groups", "test_cg"); +#endif se_aply.add_object(se); +#ifdef LEGACY_CONF se_aply.expand_objects(*config); +#else + se_aply.expand_objects(pb_config); +#endif se_aply.resolve_object(se, err); for (int i = 0; i < 7; ++i) { timerange_list list_time; @@ -345,28 +429,55 @@ TEST_F(ServiceTimePeriodNotification, TimePeriodOut) { error_cnt err; init_macros(); std::unique_ptr tperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; int now{20000}; set_time(now); configuration::applier::contact ct_aply; +#ifdef LEGACY_CONF configuration::contact ctct{new_configuration_contact("test_contact", false)}; +#else + configuration::Contact ctct{ + new_pb_configuration_contact("test_contact", false)}; +#endif ct_aply.add_object(ctct); +#ifdef LEGACY_CONF ct_aply.expand_objects(*config); +#else + ct_aply.expand_objects(pb_config); +#endif ct_aply.resolve_object(ctct, err); configuration::applier::contactgroup cg_aply; +#ifdef LEGACY_CONF configuration::contactgroup cg{ new_configuration_contactgroup("test_cg", "test_contact")}; +#else + configuration::Contactgroup cg{ + new_pb_configuration_contactgroup("test_cg", "test_contact")}; +#endif cg_aply.add_object(cg); +#ifdef LEGACY_CONF cg_aply.expand_objects(*config); +#else + cg_aply.expand_objects(pb_config); +#endif cg_aply.resolve_object(cg, err); configuration::applier::serviceescalation se_aply; +#ifdef LEGACY_CONF configuration::serviceescalation se{ new_configuration_serviceescalation("test_host", "test_svc", "test_cg")}; +#else + configuration::Serviceescalation se{new_pb_configuration_serviceescalation( + "test_host", "test_svc", "test_cg")}; +#endif se_aply.add_object(se); +#ifdef LEGACY_CONF se_aply.expand_objects(*config); +#else + se_aply.expand_objects(pb_config); +#endif se_aply.resolve_object(se, err); // uint64_t id{_svc->get_next_notification_id()}; @@ -450,9 +561,10 @@ TEST_F(ServiceTimePeriodNotification, TimePeriodUserOut) { error_cnt err; init_macros(); std::unique_ptr tiperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; int now{20000}; set_time(now); +#ifdef LEGACY_CONF configuration::timeperiod tperiod; tperiod.parse("timeperiod_name", "24x9"); tperiod.parse("alias", "24x9"); @@ -463,10 +575,24 @@ TEST_F(ServiceTimePeriodNotification, TimePeriodUserOut) { tperiod.parse("friday", "00:00-09:00"); tperiod.parse("saterday", "00:00-09:00"); tperiod.parse("sunday", "00:00-09:00"); +#else + configuration::Timeperiod tperiod; + configuration::timeperiod_helper tperiod_hlp(&tperiod); + tperiod.set_timeperiod_name("24x9"); + tperiod.set_alias("24x9"); + tperiod_hlp.hook("monday", "00:00-09:00"); + tperiod_hlp.hook("tuesday", "00:00-09:00"); + tperiod_hlp.hook("wednesday", "00:00-09:00"); + tperiod_hlp.hook("thursday", "00:00-09:00"); + tperiod_hlp.hook("friday", "00:00-09:00"); + tperiod_hlp.hook("saterday", "00:00-09:00"); + tperiod_hlp.hook("sunday", "00:00-09:00"); +#endif configuration::applier::timeperiod aplyr; aplyr.add_object(tperiod); configuration::applier::contact ct_aply; +#ifdef LEGACY_CONF configuration::contact ctct; ctct.parse("contact_name", "test_contact"); ctct.parse("service_notification_period", "24x9"); @@ -476,23 +602,57 @@ TEST_F(ServiceTimePeriodNotification, TimePeriodUserOut) { ctct.parse("service_notification_options", "a"); ctct.parse("host_notifications_enabled", "1"); ctct.parse("service_notifications_enabled", "1"); +#else + configuration::Contact ctct; + configuration::contact_helper ctct_hlp(&ctct); + ctct.set_contact_name("test_contact"); + ctct.set_service_notification_period("24x9"); + ctct_hlp.hook("host_notification_commands", "cmd"); + ctct_hlp.hook("service_notification_commands", "cmd"); + ctct_hlp.hook("host_notification_options", "d,r,f,s"); + ctct_hlp.hook("service_notification_options", "a"); + ctct.set_host_notifications_enabled(true); + ctct.set_service_notifications_enabled(true); +#endif ct_aply.add_object(ctct); +#ifdef LEGACY_CONF ct_aply.expand_objects(*config); +#else + ct_aply.expand_objects(pb_config); +#endif ct_aply.resolve_object(ctct, err); configuration::applier::contactgroup cg_aply; +#ifdef LEGACY_CONF configuration::contactgroup cg{ new_configuration_contactgroup("test_cg", "test_contact")}; +#else + configuration::Contactgroup cg{ + new_pb_configuration_contactgroup("test_cg", "test_contact")}; +#endif cg_aply.add_object(cg); +#ifdef LEGACY_CONF cg_aply.expand_objects(*config); +#else + cg_aply.expand_objects(pb_config); +#endif cg_aply.resolve_object(cg, err); configuration::applier::serviceescalation se_aply; +#ifdef LEGACY_CONF configuration::serviceescalation se{ new_configuration_serviceescalation("test_host", "test_svc", "test_cg")}; +#else + configuration::Serviceescalation se{new_pb_configuration_serviceescalation( + "test_host", "test_svc", "test_cg")}; +#endif se_aply.add_object(se); +#ifdef LEGACY_CONF se_aply.expand_objects(*config); +#else + se_aply.expand_objects(pb_config); +#endif se_aply.resolve_object(se, err); // uint64_t id{_svc->get_next_notification_id()}; @@ -575,9 +735,10 @@ TEST_F(ServiceTimePeriodNotification, TimePeriodUserIn) { error_cnt err; init_macros(); std::unique_ptr tiperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; int now{20000}; set_time(now); +#ifdef LEGACY_CONF configuration::timeperiod tperiod; tperiod.parse("timeperiod_name", "24x9"); tperiod.parse("alias", "24x9"); @@ -588,10 +749,24 @@ TEST_F(ServiceTimePeriodNotification, TimePeriodUserIn) { tperiod.parse("friday", "09:00-20:00"); tperiod.parse("saterday", "09:00-20:00"); tperiod.parse("sunday", "09:00-20:00"); +#else + configuration::Timeperiod tperiod; + configuration::timeperiod_helper tperiod_hlp(&tperiod); + tperiod.set_timeperiod_name("24x9"); + tperiod.set_alias("24x9"); + tperiod_hlp.hook("monday", "09:00-20:00"); + tperiod_hlp.hook("tuesday", "09:00-20:00"); + tperiod_hlp.hook("wednesday", "09:00-20:00"); + tperiod_hlp.hook("thursday", "09:00-20:00"); + tperiod_hlp.hook("friday", "09:00-20:00"); + tperiod_hlp.hook("saterday", "09:00-20:00"); + tperiod_hlp.hook("sunday", "09:00-20:00"); +#endif configuration::applier::timeperiod aplyr; aplyr.add_object(tperiod); configuration::applier::contact ct_aply; +#ifdef LEGACY_CONF configuration::contact ctct; ctct.parse("contact_name", "test_contact"); ctct.parse("service_notification_period", "24x9"); @@ -601,23 +776,57 @@ TEST_F(ServiceTimePeriodNotification, TimePeriodUserIn) { ctct.parse("service_notification_options", "a"); ctct.parse("host_notifications_enabled", "1"); ctct.parse("service_notifications_enabled", "1"); +#else + configuration::Contact ctct; + configuration::contact_helper ctct_hlp(&ctct); + ctct.set_contact_name("test_contact"); + ctct.set_service_notification_period("24x9"); + ctct_hlp.hook("host_notification_commands", "cmd"); + ctct_hlp.hook("service_notification_commands", "cmd"); + ctct_hlp.hook("host_notification_options", "d,r,f,s"); + ctct_hlp.hook("service_notification_options", "a"); + ctct.set_host_notifications_enabled(true); + ctct.set_service_notifications_enabled(true); +#endif ct_aply.add_object(ctct); +#ifdef LEGACY_CONF ct_aply.expand_objects(*config); +#else + ct_aply.expand_objects(pb_config); +#endif ct_aply.resolve_object(ctct, err); configuration::applier::contactgroup cg_aply; +#ifdef LEGACY_CONF configuration::contactgroup cg{ new_configuration_contactgroup("test_cg", "test_contact")}; +#else + configuration::Contactgroup cg{ + new_pb_configuration_contactgroup("test_cg", "test_contact")}; +#endif cg_aply.add_object(cg); +#ifdef LEGACY_CONF cg_aply.expand_objects(*config); +#else + cg_aply.expand_objects(pb_config); +#endif cg_aply.resolve_object(cg, err); configuration::applier::serviceescalation se_aply; +#ifdef LEGACY_CONF configuration::serviceescalation se{ new_configuration_serviceescalation("test_host", "test_svc", "test_cg")}; +#else + configuration::Serviceescalation se{new_pb_configuration_serviceescalation( + "test_host", "test_svc", "test_cg")}; +#endif se_aply.add_object(se); +#ifdef LEGACY_CONF se_aply.expand_objects(*config); +#else + se_aply.expand_objects(pb_config); +#endif se_aply.resolve_object(se, err); // uint64_t id{_svc->get_next_notification_id()}; @@ -699,9 +908,10 @@ TEST_F(ServiceTimePeriodNotification, TimePeriodUserIn) { TEST_F(ServiceTimePeriodNotification, TimePeriodUserAll) { init_macros(); std::unique_ptr tiperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; int now{20000}; set_time(now); +#ifdef LEGACY_CONF configuration::timeperiod tperiod; tperiod.parse("timeperiod_name", "24x9"); tperiod.parse("alias", "24x9"); @@ -712,10 +922,24 @@ TEST_F(ServiceTimePeriodNotification, TimePeriodUserAll) { tperiod.parse("friday", "00:00-24:00"); tperiod.parse("saterday", "00:00-24:00"); tperiod.parse("sunday", "00:00-24:00"); +#else + configuration::Timeperiod tperiod; + configuration::timeperiod_helper tperiod_hlp(&tperiod); + tperiod.set_timeperiod_name("24x9"); + tperiod.set_alias("24x9"); + tperiod_hlp.hook("monday", "00:00-24:00"); + tperiod_hlp.hook("tuesday", "00:00-24:00"); + tperiod_hlp.hook("wednesday", "00:00-24:00"); + tperiod_hlp.hook("thursday", "00:00-24:00"); + tperiod_hlp.hook("friday", "00:00-24:00"); + tperiod_hlp.hook("saterday", "00:00-24:00"); + tperiod_hlp.hook("sunday", "00:00-24:00"); +#endif configuration::applier::timeperiod aplyr; aplyr.add_object(tperiod); configuration::applier::contact ct_aply; +#ifdef LEGACY_CONF configuration::contact ctct; ctct.parse("contact_name", "test_contact"); ctct.parse("service_notification_period", "24x9"); @@ -725,24 +949,58 @@ TEST_F(ServiceTimePeriodNotification, TimePeriodUserAll) { ctct.parse("service_notification_options", "a"); ctct.parse("host_notifications_enabled", "1"); ctct.parse("service_notifications_enabled", "1"); +#else + configuration::Contact ctct; + configuration::contact_helper ctct_hlp(&ctct); + ctct.set_contact_name("test_contact"); + ctct.set_service_notification_period("24x9"); + ctct_hlp.hook("host_notification_commands", "cmd"); + ctct_hlp.hook("service_notification_commands", "cmd"); + ctct_hlp.hook("host_notification_options", "d,r,f,s"); + ctct_hlp.hook("service_notification_options", "a"); + ctct.set_host_notifications_enabled(true); + ctct.set_service_notifications_enabled(true); +#endif error_cnt err; ct_aply.add_object(ctct); +#ifdef LEGACY_CONF ct_aply.expand_objects(*config); +#else + ct_aply.expand_objects(pb_config); +#endif ct_aply.resolve_object(ctct, err); configuration::applier::contactgroup cg_aply; +#ifdef LEGACY_CONF configuration::contactgroup cg{ new_configuration_contactgroup("test_cg", "test_contact")}; +#else + configuration::Contactgroup cg{ + new_pb_configuration_contactgroup("test_cg", "test_contact")}; +#endif cg_aply.add_object(cg); +#ifdef LEGACY_CONF cg_aply.expand_objects(*config); +#else + cg_aply.expand_objects(pb_config); +#endif cg_aply.resolve_object(cg, err); configuration::applier::serviceescalation se_aply; +#ifdef LEGACY_CONF configuration::serviceescalation se{ new_configuration_serviceescalation("test_host", "test_svc", "test_cg")}; +#else + configuration::Serviceescalation se{new_pb_configuration_serviceescalation( + "test_host", "test_svc", "test_cg")}; +#endif se_aply.add_object(se); +#ifdef LEGACY_CONF se_aply.expand_objects(*config); +#else + se_aply.expand_objects(pb_config); +#endif se_aply.resolve_object(se, err); // uint64_t id{_svc->get_next_notification_id()}; @@ -824,16 +1082,24 @@ TEST_F(ServiceTimePeriodNotification, TimePeriodUserAll) { TEST_F(ServiceTimePeriodNotification, TimePeriodUserNone) { init_macros(); std::unique_ptr tiperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; int now{20000}; set_time(now); +#ifdef LEGACY_CONF configuration::timeperiod tperiod; tperiod.parse("timeperiod_name", "24x9"); tperiod.parse("alias", "24x9"); +#else + configuration::Timeperiod tperiod; + configuration::timeperiod_helper tperiod_hlp(&tperiod); + tperiod.set_timeperiod_name("24x9"); + tperiod.set_alias("24x9"); +#endif configuration::applier::timeperiod aplyr; aplyr.add_object(tperiod); configuration::applier::contact ct_aply; +#ifdef LEGACY_CONF configuration::contact ctct; ctct.parse("contact_name", "test_contact"); ctct.parse("service_notification_period", "24x9"); @@ -843,24 +1109,58 @@ TEST_F(ServiceTimePeriodNotification, TimePeriodUserNone) { ctct.parse("service_notification_options", "a"); ctct.parse("host_notifications_enabled", "1"); ctct.parse("service_notifications_enabled", "1"); +#else + configuration::Contact ctct; + configuration::contact_helper ctct_hlp(&ctct); + ctct.set_contact_name("test_contact"); + ctct.set_service_notification_period("24x9"); + ctct_hlp.hook("host_notification_commands", "cmd"); + ctct_hlp.hook("service_notification_commands", "cmd"); + ctct_hlp.hook("host_notification_options", "d,r,f,s"); + ctct_hlp.hook("service_notification_options", "a"); + ctct.set_host_notifications_enabled(true); + ctct.set_service_notifications_enabled(true); +#endif error_cnt err; ct_aply.add_object(ctct); +#ifdef LEGACY_CONF ct_aply.expand_objects(*config); +#else + ct_aply.expand_objects(pb_config); +#endif ct_aply.resolve_object(ctct, err); configuration::applier::contactgroup cg_aply; +#ifdef LEGACY_CONF configuration::contactgroup cg{ new_configuration_contactgroup("test_cg", "test_contact")}; +#else + configuration::Contactgroup cg{ + new_pb_configuration_contactgroup("test_cg", "test_contact")}; +#endif cg_aply.add_object(cg); +#ifdef LEGACY_CONF cg_aply.expand_objects(*config); +#else + cg_aply.expand_objects(pb_config); +#endif cg_aply.resolve_object(cg, err); configuration::applier::serviceescalation se_aply; +#ifdef LEGACY_CONF configuration::serviceescalation se{ new_configuration_serviceescalation("test_host", "test_svc", "test_cg")}; +#else + configuration::Serviceescalation se{new_pb_configuration_serviceescalation( + "test_host", "test_svc", "test_cg")}; +#endif se_aply.add_object(se); +#ifdef LEGACY_CONF se_aply.expand_objects(*config); +#else + se_aply.expand_objects(pb_config); +#endif se_aply.resolve_object(se, err); // uint64_t id{_svc->get_next_notification_id()}; @@ -942,16 +1242,24 @@ TEST_F(ServiceTimePeriodNotification, TimePeriodUserNone) { TEST_F(ServiceTimePeriodNotification, NoTimePeriodUser) { init_macros(); std::unique_ptr tiperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; int now{20000}; set_time(now); +#ifdef LEGACY_CONF configuration::timeperiod tperiod; tperiod.parse("timeperiod_name", "24x9"); tperiod.parse("alias", "24x9"); +#else + configuration::Timeperiod tperiod; + configuration::timeperiod_helper tperiod_hlp(&tperiod); + tperiod.set_timeperiod_name("24x9"); + tperiod.set_alias("24x9"); +#endif configuration::applier::timeperiod aplyr; aplyr.add_object(tperiod); configuration::applier::contact ct_aply; +#ifdef LEGACY_CONF configuration::contact ctct; ctct.parse("contact_name", "test_contact"); ctct.parse("host_notification_commands", "cmd"); @@ -960,24 +1268,57 @@ TEST_F(ServiceTimePeriodNotification, NoTimePeriodUser) { ctct.parse("service_notification_options", "a"); ctct.parse("host_notifications_enabled", "1"); ctct.parse("service_notifications_enabled", "1"); +#else + configuration::Contact ctct; + configuration::contact_helper ctct_hlp(&ctct); + ctct.set_contact_name("test_contact"); + ctct_hlp.hook("host_notification_commands", "cmd"); + ctct_hlp.hook("service_notification_commands", "cmd"); + ctct_hlp.hook("host_notification_options", "d,r,f,s"); + ctct_hlp.hook("service_notification_options", "a"); + ctct.set_host_notifications_enabled(true); + ctct.set_service_notifications_enabled(true); +#endif error_cnt err; ct_aply.add_object(ctct); +#ifdef LEGACY_CONF ct_aply.expand_objects(*config); +#else + ct_aply.expand_objects(pb_config); +#endif ct_aply.resolve_object(ctct, err); configuration::applier::contactgroup cg_aply; +#ifdef LEGACY_CONF configuration::contactgroup cg{ new_configuration_contactgroup("test_cg", "test_contact")}; +#else + configuration::Contactgroup cg{ + new_pb_configuration_contactgroup("test_cg", "test_contact")}; +#endif cg_aply.add_object(cg); +#ifdef LEGACY_CONF cg_aply.expand_objects(*config); +#else + cg_aply.expand_objects(pb_config); +#endif cg_aply.resolve_object(cg, err); configuration::applier::serviceescalation se_aply; +#ifdef LEGACY_CONF configuration::serviceescalation se{ new_configuration_serviceescalation("test_host", "test_svc", "test_cg")}; +#else + configuration::Serviceescalation se{new_pb_configuration_serviceescalation( + "test_host", "test_svc", "test_cg")}; +#endif se_aply.add_object(se); +#ifdef LEGACY_CONF se_aply.expand_objects(*config); +#else + se_aply.expand_objects(pb_config); +#endif se_aply.resolve_object(se, err); // uint64_t id{_svc->get_next_notification_id()}; diff --git a/engine/tests/opentelemetry/agent_check_result_builder_test.cc b/engine/tests/opentelemetry/agent_check_result_builder_test.cc index 1f8f0438830..7277d8acab0 100644 --- a/engine/tests/opentelemetry/agent_check_result_builder_test.cc +++ b/engine/tests/opentelemetry/agent_check_result_builder_test.cc @@ -411,7 +411,7 @@ class otl_agent_check_result_builder_test : public TestEngine { otl_data_point::extract_data_points( request, [&](const otl_data_point& data_pt) { std::string service_name; - for (const auto attrib : data_pt.get_resource().attributes()) { + for (const auto& attrib : data_pt.get_resource().attributes()) { if (attrib.key() == "service.name") { service_name = attrib.value().string_value(); break; @@ -481,4 +481,4 @@ TEST_F(otl_agent_check_result_builder_test, test_svc_builder_2) { }; ASSERT_PRED1(compare_to_excepted, res.output); -} \ No newline at end of file +} diff --git a/engine/tests/opentelemetry/open_telemetry_test.cc b/engine/tests/opentelemetry/open_telemetry_test.cc index 469de553274..14a91d5854e 100644 --- a/engine/tests/opentelemetry/open_telemetry_test.cc +++ b/engine/tests/opentelemetry/open_telemetry_test.cc @@ -38,8 +38,10 @@ #include "com/centreon/engine/configuration/applier/contact.hh" #include "com/centreon/engine/configuration/applier/host.hh" #include "com/centreon/engine/configuration/applier/service.hh" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/host.hh" #include "common/engine_legacy_conf/service.hh" +#endif #include "opentelemetry/proto/collector/metrics/v1/metrics_service.pb.h" #include "opentelemetry/proto/common/v1/common.pb.h" @@ -118,19 +120,40 @@ void open_telemetry_test::SetUpTestSuite() { void open_telemetry_test::SetUp() { configuration::error_cnt err; init_config_state(); +#ifdef LEGACY_CONF config->contacts().clear(); +#else + pb_config.mutable_contacts()->Clear(); +#endif configuration::applier::contact ct_aply; +#ifdef LEGACY_CONF configuration::contact ctct{new_configuration_contact("admin", true)}; +#else + configuration::Contact ctct{new_pb_configuration_contact("admin", true)}; +#endif ct_aply.add_object(ctct); +#ifdef LEGACY_CONF ct_aply.expand_objects(*config); +#else + ct_aply.expand_objects(pb_config); +#endif ct_aply.resolve_object(ctct, err); +#ifdef LEGACY_CONF configuration::host hst{new_configuration_host("localhost", "admin")}; +#else + configuration::Host hst{new_pb_configuration_host("localhost", "admin")}; +#endif configuration::applier::host hst_aply; hst_aply.add_object(hst); +#ifdef LEGACY_CONF configuration::service svc{ new_configuration_service("localhost", "check_icmp", "admin")}; +#else + configuration::Service svc{ + new_pb_configuration_service("localhost", "check_icmp", "admin")}; +#endif configuration::applier::service svc_aply; svc_aply.add_object(svc); diff --git a/engine/tests/opentelemetry/otl_converter_test.cc b/engine/tests/opentelemetry/otl_converter_test.cc index d8f4265f499..a26615232ff 100644 --- a/engine/tests/opentelemetry/otl_converter_test.cc +++ b/engine/tests/opentelemetry/otl_converter_test.cc @@ -1,22 +1,21 @@ /** - * Copyright 2024 Centreon + * Copyright 2011-2024 Centreon * - * This file is part of Centreon Engine. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * Centreon Engine is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License version 2 - * as published by the Free Software Foundation. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Centreon Engine 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. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com * - * You should have received a copy of the GNU General Public License - * along with Centreon Engine. If not, see - * . */ - #include #include #include @@ -29,8 +28,10 @@ #include "com/centreon/engine/configuration/applier/contact.hh" #include "com/centreon/engine/configuration/applier/host.hh" #include "com/centreon/engine/configuration/applier/service.hh" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/host.hh" #include "common/engine_legacy_conf/service.hh" +#endif #include "opentelemetry/proto/collector/metrics/v1/metrics_service.pb.h" #include "opentelemetry/proto/common/v1/common.pb.h" @@ -61,19 +62,40 @@ void otl_converter_test::SetUp() { host::hosts_by_id.clear(); service::services.clear(); service::services_by_id.clear(); +#ifdef LEGACY_CONF config->contacts().clear(); +#else + pb_config.mutable_contacts()->Clear(); +#endif configuration::applier::contact ct_aply; +#ifdef LEGACY_CONF configuration::contact ctct{new_configuration_contact("admin", true)}; +#else + configuration::Contact ctct{new_pb_configuration_contact("admin", true)}; +#endif ct_aply.add_object(ctct); +#ifdef LEGACY_CONF ct_aply.expand_objects(*config); +#else + ct_aply.expand_objects(pb_config); +#endif ct_aply.resolve_object(ctct, err); +#ifdef LEGACY_CONF configuration::host hst{new_configuration_host("localhost", "admin")}; +#else + configuration::Host hst{new_pb_configuration_host("localhost", "admin")}; +#endif configuration::applier::host hst_aply; hst_aply.add_object(hst); +#ifdef LEGACY_CONF configuration::service svc{ new_configuration_service("localhost", "check_icmp", "admin")}; +#else + configuration::Service svc{ + new_pb_configuration_service("localhost", "check_icmp", "admin")}; +#endif configuration::applier::service svc_aply; svc_aply.add_object(svc); diff --git a/engine/tests/pb_service_check.cc b/engine/tests/pb_service_check.cc new file mode 100644 index 00000000000..175676304c8 --- /dev/null +++ b/engine/tests/pb_service_check.cc @@ -0,0 +1,599 @@ +/* + * Copyright 2020-2022 Centreon (https://www.centreon.com/) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com + * + */ + +#include + +#include + +#include "../test_engine.hh" +#include "../timeperiod/utils.hh" +#include "com/centreon/engine/checks/checker.hh" +#include "com/centreon/engine/commands/commands.hh" +#include "com/centreon/engine/configuration/applier/command.hh" +#include "com/centreon/engine/configuration/applier/contact.hh" +#include "com/centreon/engine/configuration/applier/contactgroup.hh" +#include "com/centreon/engine/configuration/applier/host.hh" +#include "com/centreon/engine/configuration/applier/service.hh" +#include "com/centreon/engine/configuration/applier/servicedependency.hh" +#include "com/centreon/engine/configuration/applier/serviceescalation.hh" +#include "com/centreon/engine/configuration/applier/state.hh" +#include "com/centreon/engine/configuration/applier/timeperiod.hh" +#include "com/centreon/engine/configuration/host.hh" +#include "com/centreon/engine/configuration/service.hh" +#include "com/centreon/engine/configuration/state.hh" +#include "com/centreon/engine/serviceescalation.hh" +#include "com/centreon/engine/timezone_manager.hh" +#include "helper.hh" + +using namespace com::centreon; +using namespace com::centreon::engine; +using namespace com::centreon::engine::configuration; +using namespace com::centreon::engine::configuration::applier; + +extern configuration::State pb_config; + +class PbServiceCheck : public TestEngine { + public: + void SetUp() override { + init_config_state(); + + pb_config.clear_contacts(); + configuration::applier::contact ct_aply; + configuration::Contact ctct = new_pb_configuration_contact("admin", true); + ct_aply.add_object(ctct); + ct_aply.expand_objects(pb_config); + ct_aply.resolve_object(ctct); + + configuration::Host hst = new_pb_configuration_host("test_host", "admin"); + configuration::applier::host hst_aply; + hst_aply.add_object(hst); + + configuration::Service svc = + new_pb_configuration_service("test_host", "test_svc", "admin"); + configuration::applier::service svc_aply; + svc_aply.add_object(svc); + + hst_aply.resolve_object(hst); + svc_aply.resolve_object(svc); + + host_map const& hm{engine::host::hosts}; + _host = hm.begin()->second; + _host->set_current_state(engine::host::state_up); + _host->set_state_type(checkable::hard); + _host->set_acknowledgement(AckType::NONE); + _host->set_notify_on(static_cast(-1)); + + service_map const& sm{engine::service::services}; + _svc = sm.begin()->second; + _svc->set_current_state(engine::service::state_ok); + _svc->set_state_type(checkable::hard); + _svc->set_acknowledgement(AckType::NONE); + _svc->set_notify_on(static_cast(-1)); + + // This is to not be bothered by host checks during service checks + pb_config.set_host_check_timeout(10000); + } + + void TearDown() override { + _host.reset(); + _svc.reset(); + deinit_config_state(); + } + + protected: + std::shared_ptr _host; + std::shared_ptr _svc; +}; + +/* The following test comes from this array (inherited from Nagios behaviour): + * + * | Time | Check # | State | State type | State change | + * ------------------------------------------------------ + * | 0 | 1 | OK | HARD | No | + * | 1 | 1 | CRTCL | SOFT | Yes | + * | 2 | 2 | WARN | SOFT | Yes | + * | 3 | 3 | CRTCL | HARD | Yes | + * | 4 | 3 | WARN | HARD | Yes | + * | 5 | 3 | WARN | HARD | No | + * | 6 | 1 | OK | HARD | Yes | + * | 7 | 1 | OK | HARD | No | + * | 8 | 1 | UNKNWN| SOFT | Yes | + * | 9 | 2 | OK | SOFT | Yes | + * | 10 | 1 | OK | HARD | No | + * ------------------------------------------------------ + */ +TEST_F(PbServiceCheck, SimpleCheck) { + set_time(50000); + _svc->set_current_state(engine::service::state_ok); + _svc->set_last_hard_state(engine::service::state_ok); + _svc->set_last_hard_state_change(50000); + _svc->set_state_type(checkable::hard); + _svc->set_accept_passive_checks(true); + _svc->set_current_attempt(1); + + set_time(50500); + + std::time_t now{std::time(nullptr)}; + std::string cmd{fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;service critical", + now)}; + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::soft); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 1); + + set_time(51000); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;1;service warning", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::soft); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_warning); + ASSERT_EQ(_svc->get_last_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 2); + + set_time(51500); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;service critical", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_hard_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 3); + + set_time(52000); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;1;service warning", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_warning); + ASSERT_EQ(_svc->get_last_hard_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 3); + + set_time(52500); + + time_t previous = now; + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;1;service warning", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_warning); + ASSERT_EQ(_svc->get_last_hard_state_change(), previous); + ASSERT_EQ(_svc->get_current_attempt(), 3); + + set_time(53000); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;0;service ok", now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_ok); + ASSERT_EQ(_svc->get_last_hard_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 1); + + set_time(53500); + + previous = now; + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;0;service ok", now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_ok); + ASSERT_EQ(_svc->get_last_hard_state_change(), previous); + ASSERT_EQ(_svc->get_current_attempt(), 1); + + set_time(54000); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;4;service unknown", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::soft); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_unknown); + ASSERT_EQ(_svc->get_last_hard_state_change(), now - 1000); + ASSERT_EQ(_svc->get_last_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 1); + + set_time(54500); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;0;service ok", now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::soft); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_ok); + ASSERT_EQ(_svc->get_last_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 2); + + set_time(55000); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;0;service ok", now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_ok); + ASSERT_EQ(_svc->get_last_hard_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 1); +} + +/* The following test comes from this array (inherited from Nagios behaviour): + * + * | Time | Check # | State | State type | State change | + * ------------------------------------------------------ + * | 0 | 1 | OK | HARD | No | + * | 1 | 1 | CRTCL | SOFT | Yes | + * | 2 | 2 | CRTCL | SOFT | No | + * | 3 | 3 | CRTCL | HARD | No | + * ------------------------------------------------------ + */ +TEST_F(PbServiceCheck, OkCritical) { + set_time(55000); + + time_t now = std::time(nullptr); + std::string cmd{fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;0;service ok", + now)}; + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_ok); + ASSERT_EQ(_svc->get_last_hard_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 1); + + set_time(55500); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;service critical", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::soft); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 1); + + set_time(56000); + + time_t previous = now; + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;service critical", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::soft); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_state_change(), previous); + ASSERT_EQ(_svc->get_current_attempt(), 2); + + set_time(56500); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;service critical", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_hard_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 3); +} + +/* The following test comes from this array (inherited from Nagios behaviour): + * + * | Time | Check # | State | State type | State change | + * ------------------------------------------------------ + * | 0 | 2 | OK | SOFT | No | + * | 1 | 1 | CRTCL | SOFT | Yes | + * | 2 | 2 | CRTCL | SOFT | No | + * | 3 | 3 | CRTCL | HARD | No | + * ------------------------------------------------------ + */ +TEST_F(PbServiceCheck, OkSoft_Critical) { + set_time(55000); + + _svc->set_current_state(engine::service::state_ok); + _svc->set_last_state_change(55000); + _svc->set_current_attempt(2); + _svc->set_state_type(checkable::soft); + _svc->set_accept_passive_checks(true); + + set_time(55500); + + time_t now = std::time(nullptr); + std::string cmd{fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;service critical", + now)}; + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::soft); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 1); + + set_time(56000); + + time_t previous = now; + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;service critical", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::soft); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_state_change(), previous); + ASSERT_EQ(_svc->get_current_attempt(), 2); + + set_time(56500); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;service critical", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_hard_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 3); +} + +/* The following test comes from this array (inherited from Nagios behaviour): + * + * | Time | Check # | State | State type | State change | + * ------------------------------------------------------ + * | 0 | 1 | OK | HARD | No | + * | 1 | 2 | OK | HARD | No | + * | 2 | 3 | WARN | HARD | Yes | + * | 3 | 4 | CRTCL | HARD | Yes | + * | 4 | 5 | CRTCL | HARD | Yes | + * | 5 | 6 | CRTCL | HARD | Yes | + * | 6 | 7 | CRTCL | HARD | No | + * | 7 | 8 | CRTCL | HARD | No | + * ------------------------------------------------------ + */ +TEST_F(PbServiceCheck, OkCriticalStalking) { + set_time(55000); + + _svc->set_current_state(engine::service::state_ok); + _svc->set_last_state_change(55000); + _svc->set_current_attempt(2); + _svc->set_state_type(checkable::soft); + _svc->set_accept_passive_checks(true); + _svc->set_stalk_on(static_cast(-1)); + + set_time(55500); + testing::internal::CaptureStdout(); + time_t now = std::time(nullptr); + std::string cmd{fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;0;RAID array " + "optimal", + now)}; + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_ok); + ASSERT_EQ(_svc->get_last_hard_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 1); + + set_time(56000); + time_t previous = now; + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;0;RAID array " + "optimal", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_ok); + ASSERT_EQ(_svc->get_last_hard_state_change(), previous); + ASSERT_EQ(_svc->get_current_attempt(), 1); + + set_time(56500); + for (int i = 0; i < 3; i++) { + // When i == 0, the state_critical is soft => no notification + // When i == 1, the state_critical is soft => no notification + // When i == 2, the state_critical is hard down => notification + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;1;RAID array " + "degraded (1 drive bad, 1 hot spare rebuilding)", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + } + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_warning); + ASSERT_EQ(_svc->get_last_hard_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 3); + + set_time(57000); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;RAID array " + "degraded (2 drives bad, 1 host spare online, 1 hot spare rebuilding)", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_hard_state_change(), now); + ASSERT_EQ(_svc->get_current_attempt(), 3); + + set_time(57500); + previous = now; + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;RAID array " + "degraded (3 drives bad, 2 hot spares online)", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_hard_state_change(), previous); + ASSERT_EQ(_svc->get_current_attempt(), 3); + + set_time(58000); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;RAID array " + "failed", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_hard_state_change(), previous); + ASSERT_EQ(_svc->get_current_attempt(), 3); + + set_time(58500); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;RAID array " + "failed", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_hard_state_change(), previous); + ASSERT_EQ(_svc->get_current_attempt(), 3); + + set_time(59000); + + now = std::time(nullptr); + cmd = fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;RAID array " + "failed", + now); + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + + ASSERT_EQ(_svc->get_state_type(), checkable::hard); + ASSERT_EQ(_svc->get_current_state(), engine::service::state_critical); + ASSERT_EQ(_svc->get_last_hard_state_change(), previous); + ASSERT_EQ(_svc->get_current_attempt(), 3); + + std::string out{testing::internal::GetCapturedStdout()}; + std::cout << out << std::endl; + ASSERT_NE( + out.find( + "SERVICE ALERT: test_host;test_svc;OK;HARD;1;RAID array optimal"), + std::string::npos); + ASSERT_NE(out.find("SERVICE ALERT: test_host;test_svc;WARNING;HARD;3;RAID " + "array degraded (1 drive bad, 1 hot spare rebuilding)"), + std::string::npos); + ASSERT_NE(out.find("SERVICE ALERT: test_host;test_svc;CRITICAL;HARD;3;RAID " + "array degraded (2 drives bad, 1 host spare online, 1 hot " + "spare rebuilding)"), + std::string::npos); + ASSERT_NE(out.find("SERVICE ALERT: test_host;test_svc;CRITICAL;HARD;3;RAID " + "array degraded (3 drives bad, 2 hot spares online"), + std::string::npos); + ASSERT_NE(out.find("SERVICE ALERT: test_host;test_svc;CRITICAL;HARD;3;RAID " + "array failed"), + std::string::npos); +} + +TEST_F(PbServiceCheck, CheckRemoveCheck) { + set_time(50000); + _svc->set_current_state(engine::service::state_ok); + _svc->set_last_hard_state(engine::service::state_ok); + _svc->set_last_hard_state_change(50000); + _svc->set_state_type(checkable::hard); + _svc->set_accept_passive_checks(true); + _svc->set_current_attempt(1); + + set_time(50500); + std::time_t now{std::time(nullptr)}; + std::string cmd{fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;service critical", + now)}; + process_external_command(cmd.c_str()); + + /* We simulate a reload that destroyed the service */ + engine::service::services.clear(); + engine::service::services_by_id.clear(); + _svc.reset(); + + checks::checker::instance().reap(); +} + +TEST_F(PbServiceCheck, CheckUpdateMultilineOutput) { + set_time(50000); + _svc->set_current_state(engine::service::state_ok); + _svc->set_last_hard_state(engine::service::state_ok); + _svc->set_last_hard_state_change(50000); + _svc->set_state_type(checkable::hard); + _svc->set_accept_passive_checks(true); + _svc->set_current_attempt(1); + + set_time(50500); + std::time_t now{std::time(nullptr)}; + std::string cmd{fmt::format( + "[{}] PROCESS_SERVICE_CHECK_RESULT;test_host;test_svc;2;service " + "critical\\nline2\\nline3\\nline4\\nline5|res;2;5;5\\n", + now)}; + process_external_command(cmd.c_str()); + checks::checker::instance().reap(); + ASSERT_EQ(_svc->get_plugin_output(), "service critical"); + ASSERT_EQ(_svc->get_long_plugin_output(), "line2\\nline3\\nline4\\nline5"); + ASSERT_EQ(_svc->get_perf_data(), "res;2;5;5"); +} diff --git a/engine/tests/test_engine.cc b/engine/tests/test_engine.cc index 2528e488033..79d6f2fba5e 100644 --- a/engine/tests/test_engine.cc +++ b/engine/tests/test_engine.cc @@ -19,11 +19,6 @@ #include "test_engine.hh" -#include "com/centreon/engine/commands/commands.hh" -#include "com/centreon/engine/configuration/applier/command.hh" -#include "com/centreon/engine/configuration/applier/timeperiod.hh" -#include "common/engine_legacy_conf/state.hh" - using namespace com::centreon::engine; using namespace com::centreon::engine::downtimes; @@ -31,6 +26,7 @@ using namespace com::centreon::engine::downtimes; * to know if the host is already declared when creating a new service. */ static absl::flat_hash_map conf_hosts; +#ifdef LEGACY_CONF configuration::contact TestEngine::new_configuration_contact( const std::string& name, bool full, @@ -74,7 +70,116 @@ configuration::contact TestEngine::new_configuration_contact( ctct.parse("service_notifications_enabled", "1"); return ctct; } +#else +configuration::Contact TestEngine::new_pb_configuration_contact( + const std::string& name, + bool full, + const std::string& notif) const { + if (full) { + // Add command. + { + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + cmd.set_command_line("true"); + configuration::applier::command aplyr; + aplyr.add_object(cmd); + } + // Add timeperiod. + { + configuration::Timeperiod tperiod; + configuration::timeperiod_helper tperiod_hlp(&tperiod); + tperiod.set_timeperiod_name("24x7"); + tperiod.set_alias("24x7"); + auto* r = tperiod.mutable_timeranges()->add_monday(); + r->set_range_start(0); + r->set_range_end(86400); + r = tperiod.mutable_timeranges()->add_tuesday(); + r->set_range_start(0); + r->set_range_end(86400); + r = tperiod.mutable_timeranges()->add_wednesday(); + r->set_range_start(0); + r->set_range_end(86400); + r = tperiod.mutable_timeranges()->add_thursday(); + r->set_range_start(0); + r->set_range_end(86400); + r = tperiod.mutable_timeranges()->add_friday(); + r->set_range_start(0); + r->set_range_end(86400); + r = tperiod.mutable_timeranges()->add_saturday(); + r->set_range_start(0); + r->set_range_end(86400); + r = tperiod.mutable_timeranges()->add_sunday(); + r->set_range_start(0); + r->set_range_end(86400); + configuration::applier::timeperiod aplyr; + aplyr.add_object(tperiod); + } + } + // Valid contact configuration + // (will generate 0 warnings or 0 errors). + configuration::Contact ctct; + configuration::contact_helper ctct_hlp(&ctct); + ctct.set_contact_name(name); + ctct.set_host_notification_period("24x7"); + ctct.set_service_notification_period("24x7"); + fill_string_group(ctct.mutable_host_notification_commands(), "cmd"); + fill_string_group(ctct.mutable_service_notification_commands(), "cmd"); + ctct_hlp.hook("host_notification_options", "d,r,f,s"); + ctct_hlp.hook("service_notification_options", notif); + ctct.set_host_notifications_enabled(true); + ctct.set_service_notifications_enabled(true); + return ctct; +} +void TestEngine::fill_pb_configuration_contact( + configuration::contact_helper* ctct_hlp, + const std::string& name, + bool full, + const std::string& notif) const { + auto* ctct = static_cast(ctct_hlp->mut_obj()); + if (full) { + // Add command. + { + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + cmd.set_command_line("true"); + configuration::applier::command aplyr; + aplyr.add_object(cmd); + } + // Add timeperiod. + { + configuration::Timeperiod tperiod; + configuration::timeperiod_helper tperiod_hlp(&tperiod); + tperiod.set_timeperiod_name("24x7"); + tperiod.set_alias("24x7"); + tperiod_hlp.hook("monday", "00:00-24:00"); + tperiod_hlp.hook("tuesday", "00:00-24:00"); + tperiod_hlp.hook("wednesday", "00:00-24:00"); + tperiod_hlp.hook("thursday", "00:00-24:00"); + tperiod_hlp.hook("friday", "00:00-24:00"); + tperiod_hlp.hook("saterday", "00:00-24:00"); + tperiod_hlp.hook("sunday", "00:00-24:00"); + configuration::applier::timeperiod aplyr; + aplyr.add_object(tperiod); + } + } + // Valid contact configuration + // (will generate 0 warnings or 0 errors). + ctct->set_contact_name(name); + ctct->set_host_notification_period("24x7"); + ctct->set_service_notification_period("24x7"); + fill_string_group(ctct->mutable_host_notification_commands(), "cmd"); + fill_string_group(ctct->mutable_service_notification_commands(), "cmd"); + ctct_hlp->hook("host_notification_options", "d,r,f,s"); + ctct_hlp->hook("service_notification_options", notif); + ctct->set_host_notifications_enabled(true); + ctct->set_service_notifications_enabled(true); +} +#endif + +#ifdef LEGACY_CONF configuration::contactgroup TestEngine::new_configuration_contactgroup( const std::string& name, const std::string& contactname) { @@ -84,7 +189,29 @@ configuration::contactgroup TestEngine::new_configuration_contactgroup( cg.parse("members", contactname.c_str()); return cg; } +#else +configuration::Contactgroup TestEngine::new_pb_configuration_contactgroup( + const std::string& name, + const std::string& contactname) { + configuration::Contactgroup retval; + configuration::contactgroup_helper retval_hlp(&retval); + fill_pb_configuration_contactgroup(&retval_hlp, name, contactname); + return retval; +} +void TestEngine::fill_pb_configuration_contactgroup( + configuration::contactgroup_helper* cg_hlp, + const std::string& name, + const std::string& contactname) { + configuration::Contactgroup* cg = + static_cast(cg_hlp->mut_obj()); + cg->set_contactgroup_name(name); + cg->set_alias(name); + fill_string_group(cg->mutable_members(), contactname); +} +#endif + +#ifdef LEGACY_CONF configuration::serviceescalation TestEngine::new_configuration_serviceescalation( const std::string& hostname, @@ -100,7 +227,26 @@ TestEngine::new_configuration_serviceescalation( se.parse("contact_groups", contactgroup.c_str()); return se; } +#else +configuration::Serviceescalation +TestEngine::new_pb_configuration_serviceescalation( + const std::string& hostname, + const std::string& svc_desc, + const std::string& contactgroup) { + configuration::Serviceescalation se; + configuration::serviceescalation_helper se_hlp(&se); + se.set_first_notification(2); + se.set_last_notification(11); + se.set_notification_interval(9); + se_hlp.hook("escalation_options", "w,u,c,r"); + se_hlp.hook("host_name", hostname); + se_hlp.hook("service_description", svc_desc); + se_hlp.hook("contact_groups", contactgroup); + return se; +} +#endif +#ifdef LEGACY_CONF configuration::hostdependency TestEngine::new_configuration_hostdependency( const std::string& hostname, const std::string& dep_hostname) { @@ -112,7 +258,31 @@ configuration::hostdependency TestEngine::new_configuration_hostdependency( hd.dependency_type(configuration::hostdependency::notification_dependency); return hd; } +#else +/** + * @brief Create a new host dependency protobuf configuration. + * + * @param hostname The master host name we work with. + * @param dep_hostname The dependent host. + * + * @return the new configuration as a configuration::Hostdependency. + */ +configuration::Hostdependency TestEngine::new_pb_configuration_hostdependency( + const std::string& hostname, + const std::string& dep_hostname) { + configuration::Hostdependency hd; + configuration::hostdependency_helper hd_hlp(&hd); + EXPECT_TRUE(hd_hlp.hook("master_host", hostname)); + EXPECT_TRUE(hd_hlp.hook("dependent_host", dep_hostname)); + EXPECT_TRUE(hd_hlp.hook("notification_failure_options", "u,d")); + hd.set_inherits_parent(true); + hd.set_dependency_type( + configuration::DependencyKind::notification_dependency); + return hd; +} +#endif +#ifdef LEGACY_CONF configuration::servicedependency TestEngine::new_configuration_servicedependency( const std::string& hostname, @@ -128,7 +298,9 @@ TestEngine::new_configuration_servicedependency( sd.dependency_type(configuration::servicedependency::notification_dependency); return sd; } +#endif +#ifdef LEGACY_CONF configuration::host TestEngine::new_configuration_host( const std::string& hostname, const std::string& contacts, @@ -152,7 +324,53 @@ configuration::host TestEngine::new_configuration_host( conf_hosts[hostname] = hst_id; return hst; } +#else +configuration::Host TestEngine::new_pb_configuration_host( + const std::string& hostname, + const std::string& contacts, + uint64_t hst_id) { + configuration::Host hst; + configuration::host_helper hst_hlp(&hst); + hst.set_host_name(hostname); + hst.set_address("127.0.0.1"); + hst.set_host_id(hst_id); + fill_string_group(hst.mutable_contacts(), contacts); + + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("hcmd"); + cmd.set_command_line("echo 0"); + hst.set_check_command("hcmd"); + configuration::applier::command cmd_aply; + cmd_aply.add_object(cmd); + conf_hosts[hostname] = hst_id; + return hst; +} + +void TestEngine::fill_pb_configuration_host(configuration::host_helper* hst_hlp, + const std::string& hostname, + const std::string& contacts, + uint64_t hst_id) { + auto* hst = static_cast(hst_hlp->mut_obj()); + hst->set_host_name(hostname); + hst->set_address("127.0.0.1"); + hst->set_host_id(hst_id); + fill_string_group(hst->mutable_contacts(), contacts); + + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("hcmd"); + cmd.set_command_line("echo 0"); + hst->set_check_command("hcmd"); + configuration::applier::command cmd_aply; + cmd_aply.add_object(cmd); + + conf_hosts[hostname] = hst_id; +} +#endif + +#ifdef LEGACY_CONF configuration::hostescalation TestEngine::new_configuration_hostescalation( const std::string& hostname, const std::string& contactgroup, @@ -168,7 +386,26 @@ configuration::hostescalation TestEngine::new_configuration_hostescalation( he.parse("contact_groups", contactgroup.c_str()); return he; } +#else +configuration::Hostescalation TestEngine::new_pb_configuration_hostescalation( + const std::string& hostname, + const std::string& contactgroup, + uint32_t first_notif, + uint32_t last_notif, + uint32_t interval_notif) { + configuration::Hostescalation he; + configuration::hostescalation_helper he_hlp(&he); + he.set_first_notification(first_notif); + he.set_last_notification(last_notif); + he.set_notification_interval(interval_notif); + he_hlp.hook("escalation_options", "d,u,r"); + he_hlp.hook("host_name", hostname); + he_hlp.hook("contact_groups", contactgroup); + return he; +} +#endif +#ifdef LEGACY_CONF configuration::service TestEngine::new_configuration_service( const std::string& hostname, const std::string& description, @@ -205,7 +442,95 @@ configuration::service TestEngine::new_configuration_service( return svc; } +#else +configuration::Service TestEngine::new_pb_configuration_service( + const std::string& hostname, + const std::string& description, + const std::string& contacts, + uint64_t svc_id) { + configuration::Service svc; + configuration::service_helper svc_hlp(&svc); + svc.set_host_name(hostname); + svc.set_service_description(description); + auto it = conf_hosts.find(hostname); + if (it != conf_hosts.end()) + svc.set_host_id(it->second); + else + svc.set_host_id(12); + svc.set_service_id(svc_id); + fill_string_group(svc.mutable_contacts(), contacts); + + // We fake here the expand_object on configuration::service + if (it != conf_hosts.end()) + svc.set_host_id(it->second); + else + svc.set_host_id(12); + + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + cmd.set_command_line("echo 'output| metric=$ARG1$;50;75'"); + svc.set_check_command("cmd!12"); + configuration::applier::command cmd_aply; + cmd_aply.add_object(cmd); + + return svc; +} +configuration::Anomalydetection +TestEngine::new_pb_configuration_anomalydetection( + const std::string& hostname, + const std::string& description, + const std::string& contacts, + uint64_t svc_id, + uint64_t dependent_svc_id, + const std::string& thresholds_file) { + configuration::Anomalydetection ad; + configuration::anomalydetection_helper ad_hlp(&ad); + ad.set_host_name(hostname); + ad.set_service_description(description); + ad.set_dependent_service_id(dependent_svc_id); + ad.set_host_id(12); + ad.set_service_id(svc_id); + fill_string_group(ad.mutable_contacts(), contacts); + ad.set_metric_name("metric"); + ad.set_internal_id(1234); + ad.set_thresholds_file(thresholds_file); + + // We fake here the expand_object on configuration::service + ad.set_host_id(12); + + return ad; +} +void TestEngine::fill_pb_configuration_service( + configuration::service_helper* svc_hlp, + const std::string& hostname, + const std::string& description, + const std::string& contacts, + uint64_t svc_id) { + auto* svc = static_cast(svc_hlp->mut_obj()); + svc->set_host_name(hostname); + svc->set_service_description(description); + auto it = conf_hosts.find(hostname); + // We fake here the expand_object on configuration::service + if (it != conf_hosts.end()) + svc->set_host_id(it->second); + else + svc->set_host_id(12); + svc->set_service_id(svc_id); + fill_string_group(svc->mutable_contacts(), contacts); + + configuration::Command cmd; + configuration::command_helper cmd_hlp(&cmd); + cmd.set_command_name("cmd"); + cmd.set_command_line("echo 'output| metric=$ARG1$;50;75'"); + svc->set_check_command("cmd!12"); + configuration::applier::command cmd_aply; + cmd_aply.add_object(cmd); +} +#endif + +#ifdef LEGACY_CONF configuration::anomalydetection TestEngine::new_configuration_anomalydetection( const std::string& hostname, const std::string& description, @@ -229,3 +554,36 @@ configuration::anomalydetection TestEngine::new_configuration_anomalydetection( return ad; } + +#endif + +std::unique_ptr TestEngine::new_timeperiod_with_timeranges( + const std::string& name, + const std::string& alias) { +#ifdef LEGACY_CONF + auto tperiod = std::make_unique(name, alias); + for (size_t i = 0; i < tperiod->days.size(); ++i) + tperiod->days[i].emplace_back(0, 86400); +#else + configuration::Timeperiod tp; + configuration::timeperiod_helper tp_hlp(&tp); + tp.set_timeperiod_name(name); + tp.set_alias(alias); +#define add_day(day) \ + { \ + auto* d = tp.mutable_timeranges()->add_##day(); \ + d->set_range_start(0); \ + d->set_range_end(86400); \ + } + add_day(sunday); + add_day(monday); + add_day(tuesday); + add_day(wednesday); + add_day(thursday); + add_day(friday); + add_day(saturday); + + auto tperiod = std::make_unique(tp); +#endif + return tperiod; +} diff --git a/engine/tests/test_engine.hh b/engine/tests/test_engine.hh index 3271cb8873c..e2444d6a076 100644 --- a/engine/tests/test_engine.hh +++ b/engine/tests/test_engine.hh @@ -23,6 +23,11 @@ #include +#include "com/centreon/engine/commands/commands.hh" +#include "com/centreon/engine/configuration/applier/command.hh" +#include "com/centreon/engine/configuration/applier/timeperiod.hh" +#include "com/centreon/engine/timeperiod.hh" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/anomalydetection.hh" #include "common/engine_legacy_conf/contact.hh" #include "common/engine_legacy_conf/contactgroup.hh" @@ -32,11 +37,23 @@ #include "common/engine_legacy_conf/service.hh" #include "common/engine_legacy_conf/servicedependency.hh" #include "common/engine_legacy_conf/serviceescalation.hh" +#include "common/engine_legacy_conf/state.hh" +#else +#include "common/engine_conf/anomalydetection_helper.hh" +#include "common/engine_conf/contact_helper.hh" +#include "common/engine_conf/host_helper.hh" +#include "common/engine_conf/hostdependency_helper.hh" +#include "common/engine_conf/hostescalation_helper.hh" +#include "common/engine_conf/service_helper.hh" +#include "common/engine_conf/serviceescalation_helper.hh" +#include "common/engine_conf/timeperiod_helper.hh" +#endif using namespace com::centreon::engine; class TestEngine : public ::testing::Test { public: +#ifdef LEGACY_CONF configuration::contact new_configuration_contact( std::string const& name, bool full, @@ -80,6 +97,63 @@ class TestEngine : public ::testing::Test { configuration::contactgroup new_configuration_contactgroup( std::string const& name, std::string const& contactname); +#else + void fill_pb_configuration_contact(configuration::contact_helper* ctct_hlp, + std::string const& name, + bool full, + const std::string& notif = "a") const; + configuration::Contact new_pb_configuration_contact( + const std::string& name, + bool full, + const std::string& notif = "a") const; + configuration::Hostdependency new_pb_configuration_hostdependency( + const std::string& hostname, + const std::string& dep_hostname); + void fill_pb_configuration_host(configuration::host_helper* hst_hlp, + std::string const& hostname, + std::string const& contacts, + uint64_t hst_id = 12); + configuration::Host new_pb_configuration_host(const std::string& hostname, + const std::string& contacts, + uint64_t hst_id = 12); + configuration::Contactgroup new_pb_configuration_contactgroup( + const std::string& name, + const std::string& contactname); + void fill_pb_configuration_contactgroup( + configuration::contactgroup_helper* ctct_hlp, + const std::string& name, + const std::string& contactname); + configuration::Service new_pb_configuration_service( + const std::string& hostname, + const std::string& description, + const std::string& contacts, + uint64_t svc_id = 13); + configuration::Anomalydetection new_pb_configuration_anomalydetection( + const std::string& hostname, + const std::string& description, + const std::string& contacts, + uint64_t svc_id = 14, + uint64_t dependent_svc_id = 13, + const std::string& thresholds_file = "/tmp/thresholds_file"); + void fill_pb_configuration_service(configuration::service_helper* svc_hlp, + std::string const& hostname, + std::string const& description, + std::string const& contacts, + uint64_t svc_id = 13); + configuration::Serviceescalation new_pb_configuration_serviceescalation( + std::string const& hostname, + std::string const& svc_desc, + std::string const& contactgroup); + configuration::Hostescalation new_pb_configuration_hostescalation( + std::string const& hostname, + std::string const& contactgroup, + uint32_t first_notif = 2, + uint32_t last_notif = 11, + uint32_t interval_notif = 9); +#endif + std::unique_ptr new_timeperiod_with_timeranges( + const std::string& name, + const std::string& alias); }; #endif /* !TEST_ENGINE_HH */ diff --git a/engine/tests/timeperiod/get_next_valid_time/calendar_date.cc b/engine/tests/timeperiod/get_next_valid_time/calendar_date.cc index 09101857ea1..ad3989a1afd 100644 --- a/engine/tests/timeperiod/get_next_valid_time/calendar_date.cc +++ b/engine/tests/timeperiod/get_next_valid_time/calendar_date.cc @@ -1,33 +1,34 @@ /** -* Copyright 2016 Centreon -* -* This file is part of Centreon Engine. -* -* Centreon Engine is free software: you can redistribute it and/or -* modify it under the terms of the GNU General Public License version 2 -* as published by the Free Software Foundation. -* -* Centreon Engine 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 Centreon Engine. If not, see -* . -*/ + * Copyright 2016 Centreon + * + * This file is part of Centreon Engine. + * + * Centreon Engine is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * Centreon Engine 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 Centreon Engine. If not, see + * . + */ #include #include #include "com/centreon/clib.hh" #include "com/centreon/engine/configuration/applier/state.hh" #include "com/centreon/engine/timeperiod.hh" +#include "test_engine.hh" #include "tests/timeperiod/utils.hh" using namespace com::centreon; using namespace com::centreon::engine; -class GetNextValidTimeCalendarDateTest : public ::testing::Test { +class GetNextValidTimeCalendarDateTest : public TestEngine { public: void default_data_set() { _creator.new_timeperiod(); @@ -90,7 +91,7 @@ TEST_F(GetNextValidTimeCalendarDateTest, WithinCalendarDate) { // Then the next valid time is now TEST_F(GetNextValidTimeCalendarDateTest, AfterCalendarDates) { std::unique_ptr tiperiod{ - new engine::timeperiod("tperiod", "alias")}; + new_timeperiod_with_timeranges("tperiod", "alias")}; for (int i = 0; i < 7; ++i) { timerange_list list_time; diff --git a/engine/tests/timeperiod/get_next_valid_time/exceptions_test.cc b/engine/tests/timeperiod/get_next_valid_time/exceptions_test.cc index bdf0f7add12..c83f8dd9a16 100644 --- a/engine/tests/timeperiod/get_next_valid_time/exceptions_test.cc +++ b/engine/tests/timeperiod/get_next_valid_time/exceptions_test.cc @@ -22,7 +22,12 @@ #include "com/centreon/engine/exceptions/error.hh" #include "com/centreon/engine/string.hh" #include "com/centreon/engine/timeperiod.hh" +#include "gtest/gtest.h" +#ifdef LEGACY_CONF #include "common/engine_legacy_conf/timeperiod.hh" +#else +#include "common/engine_conf/timeperiod_helper.hh" +#endif #include "helper.hh" @@ -35,6 +40,13 @@ struct test_param { std::string expected; // YYYY-MM-DD HH:MM:SS format }; +std::string PrintToString(const test_param& data) { + std::stringstream ss; + ss << "name: " << data.name << " ; now: " << data.now + << " ; prefered: " << data.prefered << " ; expected: " << data.expected; + return ss.str(); +} + class timeperiod_exception : public ::testing::TestWithParam { protected: static configuration::applier::timeperiod _applier; @@ -73,8 +85,15 @@ void timeperiod_exception::parse_timeperiods_cfg_file( bool wait_time_period_begin = true; +#ifdef LEGACY_CONF std::unique_ptr conf( std::make_unique()); +#else + std::unique_ptr conf( + std::make_unique()); + std::unique_ptr conf_hlp = + std::make_unique(conf.get()); +#endif while (!f.eof()) { std::getline(f, line); @@ -89,10 +108,42 @@ void timeperiod_exception::parse_timeperiods_cfg_file( if (line[0] == '}') { wait_time_period_begin = true; _applier.add_object(*conf); +#ifdef LEGACY_CONF conf = std::make_unique(); +#else + conf = std::make_unique(); + conf_hlp = + std::make_unique(conf.get()); +#endif continue; } + if (line.substr(0, 9) == "\tmonday 3") { + std::cout << "monday 3..." << std::endl; + } +#ifdef LEGACY_CONF conf->parse(string::trim(line)); +#else + std::string_view line_view = absl::StripAsciiWhitespace(line); + if (line_view[0] == '#') + continue; + std::vector v = + absl::StrSplit(line_view, absl::MaxSplits(absl::ByAnyChar(" \t"), 1), + absl::SkipWhitespace()); + if (v.size() != 2) + abort(); + + std::string_view key = absl::StripAsciiWhitespace(v[0]); + std::string_view value = absl::StripAsciiWhitespace(v[1]); + bool retval = false; + /* particular cases with hook */ + retval = conf_hlp->hook(key, value); + if (!retval) + retval = conf_hlp->set(key, value); + if (!retval) { + std::cout << "Unable to parse <<" << line << ">>" << std::endl; + abort(); + } +#endif } } } diff --git a/engine/tests/timeperiod/utils.cc b/engine/tests/timeperiod/utils.cc index ffd10cd5b91..bd77c4f7b85 100644 --- a/engine/tests/timeperiod/utils.cc +++ b/engine/tests/timeperiod/utils.cc @@ -1,22 +1,21 @@ /** - * Copyright 2016 Centreon + * Copyright 2016-2024 Centreon * - * This file is part of Centreon Engine. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * Centreon Engine is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License version 2 - * as published by the Free Software Foundation. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Centreon Engine 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. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * For more information : contact@centreon.com * - * You should have received a copy of the GNU General Public License - * along with Centreon Engine. If not, see - * . */ - #include #include #include @@ -26,6 +25,9 @@ #include "com/centreon/engine/exceptions/error.hh" #include "com/centreon/engine/timerange.hh" #include "tests/timeperiod/utils.hh" +#ifndef LEGACY_CONF +#include "common/engine_conf/timeperiod_helper.hh" +#endif using namespace com::centreon::engine; // Global time. @@ -56,6 +58,7 @@ std::shared_ptr timeperiod_creator::get_timeperiods_shared() { return (*_timeperiods.begin()); } +#ifdef LEGACY_CONF /** * Create a new timeperiod. * @@ -64,8 +67,24 @@ std::shared_ptr timeperiod_creator::get_timeperiods_shared() { timeperiod* timeperiod_creator::new_timeperiod() { std::shared_ptr tp{new timeperiod("test", "test")}; _timeperiods.push_front(tp); - return (tp.get()); + return tp.get(); } +#else +/** + * Create a new timeperiod. + * + * @return The newly created timeperiod. + */ +timeperiod* timeperiod_creator::new_timeperiod() { + configuration::Timeperiod conf_tp; + configuration::timeperiod_helper tp_hlp(&conf_tp); + conf_tp.set_timeperiod_name("test"); + conf_tp.set_alias("test"); + std::shared_ptr tp = std::make_shared(conf_tp); + _timeperiods.push_front(tp); + return tp.get(); +} +#endif /** * Create a new exclusion on the timeperiod. @@ -104,10 +123,17 @@ daterange* timeperiod_creator::new_calendar_date(int start_year, if (!target) target = _timeperiods.begin()->get(); +#ifdef LEGACY_CONF target->exceptions[daterange::calendar_date].emplace_back( daterange::calendar_date, start_year, start_month, start_day, 0, 0, end_year, end_month, end_day, 0, 0, 0, std::list()); +#else + target->exceptions[daterange::calendar_date].emplace_back( + daterange::calendar_date, start_year, start_month, start_day, 0, 0, + end_year, end_month, end_day, 0, 0, 0, + google::protobuf::RepeatedPtrField()); +#endif return &*target->exceptions[daterange::calendar_date].rbegin(); } @@ -130,9 +156,16 @@ daterange* timeperiod_creator::new_specific_month_date(int start_month, if (!target) target = _timeperiods.begin()->get(); +#ifdef LEGACY_CONF target->exceptions[daterange::month_date].emplace_back( daterange::month_date, 0, start_month, start_day, 0, 0, 0, end_month, end_day, 0, 0, 0, std::list()); +#else + target->exceptions[daterange::month_date].emplace_back( + daterange::month_date, 0, start_month, start_day, 0, 0, 0, end_month, + end_day, 0, 0, 0, + google::protobuf::RepeatedPtrField()); +#endif return &*target->exceptions[daterange::month_date].rbegin(); } @@ -154,9 +187,15 @@ daterange* timeperiod_creator::new_generic_month_date(int start_day, std::shared_ptr dr{new daterange( daterange::month_day, 0, 0, start_day, 0, 0, 0, 0, end_day, 0, 0, 0, {})}; +#ifdef LEGACY_CONF target->exceptions[daterange::month_day].emplace_back( daterange::month_day, 0, 0, start_day, 0, 0, 0, 0, end_day, 0, 0, 0, std::list()); +#else + target->exceptions[daterange::month_day].emplace_back( + daterange::month_day, 0, 0, start_day, 0, 0, 0, 0, end_day, 0, 0, 0, + google::protobuf::RepeatedPtrField()); +#endif return &*target->exceptions[daterange::month_day].rbegin(); } @@ -184,10 +223,17 @@ daterange* timeperiod_creator::new_offset_weekday_of_specific_month( if (!target) target = _timeperiods.begin()->get(); +#ifdef LEGACY_CONF target->exceptions[daterange::month_week_day].emplace_back( daterange::month_week_day, 0, start_month, 0, start_wday, start_offset, 0, end_month, 0, end_wday, end_offset, 0, std::list()); +#else + target->exceptions[daterange::month_week_day].emplace_back( + daterange::month_week_day, 0, start_month, 0, start_wday, start_offset, 0, + end_month, 0, end_wday, end_offset, 0, + google::protobuf::RepeatedPtrField()); +#endif return &*target->exceptions[daterange::month_week_day].rbegin(); } @@ -211,9 +257,16 @@ daterange* timeperiod_creator::new_offset_weekday_of_generic_month( if (!target) target = _timeperiods.begin()->get(); +#ifdef LEGACY_CONF target->exceptions[daterange::week_day].emplace_back( daterange::week_day, 0, 0, 0, start_wday, start_offset, 0, 0, 0, end_wday, end_offset, 0, std::list()); +#else + target->exceptions[daterange::week_day].emplace_back( + daterange::week_day, 0, 0, 0, start_wday, start_offset, 0, 0, 0, end_wday, + end_offset, 0, + google::protobuf::RepeatedPtrField()); +#endif return &*target->exceptions[daterange::week_day].rbegin(); } @@ -315,7 +368,12 @@ extern "C" time_t time(time_t* t) __THROW { return (gl_now); } +#ifdef LEGACY_GETTIMEOFDAY extern "C" int gettimeofday(struct timeval* tv, struct timezone*) __THROW { +#else +extern "C" int gettimeofday(struct timeval* tv, void*) __THROW { +#endif + // extern "C" int gettimeofday(struct timeval* tv, struct timezone*) __THROW { if (tv) { tv->tv_sec = gl_now; tv->tv_usec = 0; diff --git a/engine/tests/timeperiod/utils.hh b/engine/tests/timeperiod/utils.hh index 964e3ec0d5d..da07b395dbb 100644 --- a/engine/tests/timeperiod/utils.hh +++ b/engine/tests/timeperiod/utils.hh @@ -1,21 +1,21 @@ -/* -** Copyright 2016 Centreon -** -** This file is part of Centreon Engine. -** -** Centreon Engine is free software: you can redistribute it and/or -** modify it under the terms of the GNU General Public License version 2 -** as published by the Free Software Foundation. -** -** Centreon Engine 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 Centreon Engine. If not, see -** . -*/ +/** + * Copyright 2016-2024 Centreon + * + * This file is part of Centreon Engine. + * + * Centreon Engine is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * Centreon Engine 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 Centreon Engine. If not, see + * . + */ #ifndef TESTS_TIMEPERIOD_UTILS_HH #define TESTS_TIMEPERIOD_UTILS_HH From 385bc37753626c237b08ea4e08b73de0303fa12f Mon Sep 17 00:00:00 2001 From: David Boucher Date: Tue, 6 Aug 2024 10:02:16 +0200 Subject: [PATCH 928/948] fix(tests/bam): test simplified REFS: MON-34072 MON-15480 --- tests/bam/boolean_rules.robot | 9 +++------ tests/engine/forced_checks.robot | 13 ++++--------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/tests/bam/boolean_rules.robot b/tests/bam/boolean_rules.robot index fe6e834b791..c9f6ab27bcb 100644 --- a/tests/bam/boolean_rules.robot +++ b/tests/bam/boolean_rules.robot @@ -210,9 +210,7 @@ BABOOORREL ${start} Get Current Date Ctn Start engine # Let's wait for the external command check start - ${content} Create List check_for_external_commands() - ${result} Ctn Find In Log With Timeout ${engineLog0} ${start} ${content} 60 - Should Be True ${result} A message telling check_for_external_commands() should be available. + Ctn Wait For Engine To Be Ready ${start} ${1} # 302 is set to critical => {host_16 service_302} {IS} {OK} is then False Ctn Process Service Result Hard host_16 service_302 2 output critical for service_302 ${result} Ctn Check Service Status With Timeout host_16 service_302 2 30 HARD @@ -236,13 +234,12 @@ BABOOORREL ... ${id_bool} ... {host_16 service_302} {IS} {OK} {OR} {host_16 service_304} {IS} {OK} + ${start} Get Current Date Ctn Reload Engine Ctn Reload Broker # Let's wait for the external command check start - ${content} Create List check_for_external_commands() - ${result} Ctn Find In Log With Timeout ${engineLog0} ${start} ${content} 60 - Should Be True ${result} A message telling check_for_external_commands() should be available. + Ctn Wait For Engine To Be Ready ${start} ${1} Ctn Process Service Result Hard host_16 service_302 2 output ok for service_302 Ctn Process Service Result Hard host_16 service_304 0 output ok for service_304 diff --git a/tests/engine/forced_checks.robot b/tests/engine/forced_checks.robot index d7b3b81b0ce..372341419ac 100644 --- a/tests/engine/forced_checks.robot +++ b/tests/engine/forced_checks.robot @@ -327,15 +327,10 @@ E_HOST_DOWN_DISABLE_SERVICE_CHECKS Ctn Clear Retention ${start} Get Current Date - ${start} Get Current Date Ctn Start Engine Ctn Start Broker only_central=${True} - ${content} Create List INITIAL HOST STATE: host_1; - ${result} Ctn Find In Log With Timeout ${engineLog0} ${start} ${content} 60 - Should Be True - ... ${result} - ... An Initial host state on host_1 should be raised before we can start our external commands. + Ctn Wait For Engine To Be Ready ${start} ${1} FOR ${i} IN RANGE ${4} Ctn Process Host Check Result host_1 1 host_1 DOWN @@ -344,17 +339,17 @@ E_HOST_DOWN_DISABLE_SERVICE_CHECKS ${result} Ctn Check Host Status host_1 1 1 False 30 Should Be True ${result} host_1 should be down/hard - #after some time services should be in hard state + # After some time services should be in hard state FOR ${index} IN RANGE ${19} ${result} Ctn Check Service Status With Timeout host_1 service_${index+1} 3 30 HARD Should Be True ${result} service_${index+1} should be UNKNOWN hard END - #host_1 check returns UP + # host_1 check returns UP Ctn Set Command Status checkh1 0 Ctn Process Host Check Result host_1 0 host_1 UP - #after some time services should be in ok hard state + # After some time services should be in ok hard state FOR ${index} IN RANGE ${19} Ctn Process Service Check Result host_1 service_${index+1} 0 output END From 31a74279be0a040faa569a9f33a9c8d6c1811592 Mon Sep 17 00:00:00 2001 From: David Boucher Date: Tue, 6 Aug 2024 09:04:56 +0200 Subject: [PATCH 929/948] enh(tests): hostgroups tests are more complete REFS: MON-34072 MON-15480 --- tests/broker-engine/hostgroups.robot | 27 ++++++++++++++------------- tests/resources/Common.py | 1 + tests/resources/Engine.py | 10 +++++----- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/tests/broker-engine/hostgroups.robot b/tests/broker-engine/hostgroups.robot index 4ffda9ae44c..3f81730076d 100644 --- a/tests/broker-engine/hostgroups.robot +++ b/tests/broker-engine/hostgroups.robot @@ -121,7 +121,7 @@ EBNHGU3 Ctn Reload Engine ${result} Ctn Check Number Of Relations Between Hostgroup And Hosts 1 12 30 - Should Be True ${result} We should have 12 hosts members of host 1. + Should Be True ${result} We should have 12 hosts members in the hostgroup 1. Ctn Config Engine Remove Cfg File ${0} hostgroups.cfg @@ -129,7 +129,7 @@ EBNHGU3 Ctn Reload Broker Ctn Reload Engine ${result} Ctn Check Number Of Relations Between Hostgroup And Hosts 1 9 30 - Should Be True ${result} We should have 12 hosts members of host 1. + Should Be True ${result} We should have 9 hosts members in the hostgroup 1. EBNHG4 [Documentation] New host group with several pollers and connections to DB with broker and rename this hostgroup @@ -183,12 +183,14 @@ EBNHGU4_${test_label} [Documentation] New host group with several pollers and connections to DB with broker and rename this hostgroup [Tags] broker engine hostgroup Ctn Config Engine ${3} + Ctn Engine Config Set Value ${0} log_level_config debug Ctn Config Broker rrd Ctn Config Broker central Ctn Config Broker module ${3} Ctn Broker Config Log central sql trace Ctn Broker Config Log central lua trace + Ctn Broker Config Log central neb debug Ctn Broker Config Source Log central 1 Ctn Broker Config Source Log module0 1 Ctn Config Broker Sql Output central unified_sql 5 @@ -202,10 +204,13 @@ EBNHGU4_${test_label} ${start} Get Current Date Ctn Start Broker - Ctn Start engine - Sleep 3s + Ctn Start Engine + + Ctn Wait For Engine To Be Ready ${start} ${1} + Ctn Add Host Group ${0} ${1} ["host_1", "host_2", "host_3"] + ${start} Ctn Get Round Current Date Ctn Reload Broker Ctn Reload Engine @@ -242,7 +247,7 @@ EBNHGU4_${test_label} Ctn Rename Host Group ${0} ${1} test ["host_1", "host_2", "host_3"] - Sleep 10s + Sleep 3s Ctn Reload Engine Ctn Reload Broker @@ -289,14 +294,10 @@ EBNHGU4_${test_label} END Should Be Equal As Strings ${output} () hostgroup_test not deleted - Sleep 2s - # clear lua file - # this part of test is disable because group erasure is desactivated in macrocache.cc - # it will be reactivated when global cache will be implemented - # Create File /tmp/lua-engine.log - # Sleep 2s - # ${grep_result} Grep File /tmp/lua-engine.log no host_group_name 1 - # Should Be True len("""${grep_result}""") < 10 hostgroup 1 still exist + # Clear the Lua file + Create File /tmp/lua-engine.log + ${grep_result} Grep File /tmp/lua-engine.log no host_group_name 1 + Should Be True len("""${grep_result}""") < 10 hostgroup 1 still exists Examples: Use_BBDO3 test_label -- ... True BBDO3 diff --git a/tests/resources/Common.py b/tests/resources/Common.py index b7f3c09ba3f..b12a3ba176d 100644 --- a/tests/resources/Common.py +++ b/tests/resources/Common.py @@ -1460,6 +1460,7 @@ def ctn_check_number_of_relations_between_hostgroup_and_hosts(hostgroup: int, va "SELECT count(*) FROM hosts_hostgroups WHERE hostgroup_id={}".format(hostgroup)) result = cursor.fetchall() if len(result) > 0: + logger.console(f"SELECT count(*) FROM hosts_hostgroups WHERE hostgroup_id={hostgroup} => {result[0]}") if int(result[0]['count(*)']) == value: return True time.sleep(1) diff --git a/tests/resources/Engine.py b/tests/resources/Engine.py index 687a8db4435..202b5013f7d 100755 --- a/tests/resources/Engine.py +++ b/tests/resources/Engine.py @@ -1171,11 +1171,11 @@ def ctn_rename_host_group(index: int, id_host_group: int, name: str, members: li with open(f"{ETC_ROOT}/centreon-engine/config{index}/hostgroups.cfg", "w") as f: logger.console(mbs) f.write(f"""define hostgroup {{ - hostgroup_id {id_host_group} - hostgroup_name hostgroup_{name} - alias hostgroup_{name} - members {mbs_str} - }} + hostgroup_id {id_host_group} + hostgroup_name hostgroup_{name} + alias hostgroup_{name} + members {mbs_str} +}} """) From 6ce3c35971cdcac65688b8574f73fb96676bab42 Mon Sep 17 00:00:00 2001 From: David Boucher Date: Mon, 5 Aug 2024 19:17:35 +0200 Subject: [PATCH 930/948] fix(tests): external-commands2 fixed REFS: MON-34072 MON-15480 --- tests/broker-engine/external-commands2.robot | 14 +++++------- tests/broker-engine/opentelemetry.robot | 14 ++++++++++-- tests/resources/Common.py | 23 +++++++++++++++----- 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/tests/broker-engine/external-commands2.robot b/tests/broker-engine/external-commands2.robot index 90d67fbcaec..852d4a48c0a 100644 --- a/tests/broker-engine/external-commands2.robot +++ b/tests/broker-engine/external-commands2.robot @@ -1399,19 +1399,15 @@ BEHOSTCHECK Ctn Config Broker module ${1} Ctn Broker Config Log central sql trace Ctn Config BBDO3 1 - Ctn Config Broker Sql Output central unified_sql ${start} Get Current Date Ctn Start Broker - Ctn Start engine - ${content} Create List check_for_external_commands - ${result} Ctn Find In Log With Timeout ${engineLog0} ${start} ${content} 60 - Should Be True ${result} No check for external commands executed for 1mn. + Ctn Start Engine + Ctn Wait For Engine To Be Ready ${start} ${1} - Connect To Database pymysql ${DBName} ${DBUser} ${DBPass} ${DBHost} ${DBPort} - Execute SQL String UPDATE hosts SET command_line='toto' WHERE name='host_1' + ${start} Ctn Get Round Current Date Ctn Schedule Forced Host Check host_1 - ${result} Ctn Check Host Check With Timeout host_1 30 ${VarRoot}/lib/centreon-engine/check.pl --id 0 - Should Be True ${result} hosts table not updated + ${result} Ctn Check Host Check With Timeout host_1 ${start} 30 + Should Be True ${result} last_check column in resources table not updated. BE_BACKSLASH_CHECK_RESULT diff --git a/tests/broker-engine/opentelemetry.robot b/tests/broker-engine/opentelemetry.robot index 2397f9dbdca..dd5604616c1 100644 --- a/tests/broker-engine/opentelemetry.robot +++ b/tests/broker-engine/opentelemetry.robot @@ -78,7 +78,7 @@ Test Teardown Ctn Stop Engine Broker And Save Logs # Should Be True ${test_ret} protobuf object sent to engine mus be in lua.log BEOTEL_TELEGRAF_CHECK_HOST - [Documentation] we send nagios telegraf formated datas and we expect to get it in check result + [Documentation] we send nagios telegraf formatted datas and we expect to get it in check result [Tags] broker engine opentelemetry mon-34004 Ctn Config Engine ${1} ${2} ${2} Ctn Add Otl ServerModule @@ -99,6 +99,7 @@ BEOTEL_TELEGRAF_CHECK_HOST Ctn Config Broker central Ctn Config Broker module + Ctn Broker Config Log module0 otel info Ctn Config Broker rrd Ctn Broker Config Log central sql trace @@ -176,7 +177,7 @@ BEOTEL_TELEGRAF_CHECK_HOST Should Be True ${result} hosts table not updated BEOTEL_TELEGRAF_CHECK_SERVICE - [Documentation] we send nagios telegraf formated datas and we expect to get it in check result + [Documentation] we send nagios telegraf formatted datas and we expect to get it in check result [Tags] broker engine opentelemetry mon-34004 Ctn Config Engine ${1} ${2} ${2} Ctn Add Otl ServerModule 0 {"otel_server":{"host": "0.0.0.0","port": 4317},"max_length_grpc_log":0} @@ -195,6 +196,7 @@ BEOTEL_TELEGRAF_CHECK_SERVICE Ctn Config Broker central Ctn Config Broker module + Ctn Broker Config Log module0 otel info Ctn Config Broker rrd Ctn ConfigBBDO3 1 @@ -319,6 +321,7 @@ BEOTEL_SERVE_TELEGRAF_CONFIGURATION_CRYPTED Ctn Engine Config Set Value 0 log_level_checks trace Ctn Config Broker module + Ctn Broker Config Log module0 otel info Ctn Clear Retention @@ -393,6 +396,7 @@ BEOTEL_SERVE_TELEGRAF_CONFIGURATION_NO_CRYPTED Ctn Engine Config Set Value 0 log_level_checks trace Ctn Config Broker module + Ctn Broker Config Log module0 otel info Ctn Clear Retention @@ -441,6 +445,7 @@ BEOTEL_CENTREON_AGENT_CHECK_HOST Ctn Config Broker central Ctn Config Broker module + Ctn Broker Config Log module0 otel info Ctn Config Broker rrd Ctn Config Centreon Agent Ctn Broker Config Log central sql trace @@ -514,6 +519,7 @@ BEOTEL_CENTREON_AGENT_CHECK_SERVICE Ctn Config Broker central Ctn Config Broker module + Ctn Broker Config Log module0 otel info Ctn Config Broker rrd Ctn Config Centreon Agent Ctn Broker Config Log central sql trace @@ -576,6 +582,7 @@ BEOTEL_REVERSE_CENTREON_AGENT_CHECK_HOST Ctn Config Broker central Ctn Config Broker module + Ctn Broker Config Log module0 otel info Ctn Config Broker rrd Ctn Config Reverse Centreon Agent Ctn Broker Config Log central sql trace @@ -649,6 +656,7 @@ BEOTEL_REVERSE_CENTREON_AGENT_CHECK_SERVICE Ctn Config Broker central Ctn Config Broker module + Ctn Broker Config Log module0 otel info Ctn Config Broker rrd Ctn Config Reverse Centreon Agent Ctn Broker Config Log central sql trace @@ -714,6 +722,7 @@ BEOTEL_CENTREON_AGENT_CHECK_HOST_CRYPTED Ctn Config Broker central Ctn Config Broker module + Ctn Broker Config Log module0 otel info Ctn Config Broker rrd Ctn Config Centreon Agent ${None} ${None} /tmp/ca_1234.crt Ctn Broker Config Log central sql trace @@ -767,6 +776,7 @@ BEOTEL_REVERSE_CENTREON_AGENT_CHECK_HOST_CRYPTED Ctn Config Broker central Ctn Config Broker module + Ctn Broker Config Log module0 otel info Ctn Config Broker rrd Ctn Config Reverse Centreon Agent /tmp/server_1234.key /tmp/server_1234.crt /tmp/ca_1234.crt Ctn Broker Config Log central sql trace diff --git a/tests/resources/Common.py b/tests/resources/Common.py index b12a3ba176d..3b6bfb000a7 100644 --- a/tests/resources/Common.py +++ b/tests/resources/Common.py @@ -1071,7 +1071,19 @@ def ctn_check_service_output_resource_status_with_timeout(hostname: str, service -def ctn_check_host_check_with_timeout(hostname: str, timeout: int, command_line: str): +def ctn_check_host_check_with_timeout(hostname: str, start: int, timeout: int): + """ + ctl_check_host_check_with_timeout + + Checks that the last_check is after the start timestamp. + + Args: + hostname: the host concerned by the check + start: a timestamp that should be approximatively the date of the check. + timeout: A timeout. + + Returns: True on success. + """ limit = time.time() + timeout while time.time() < limit: connection = pymysql.connect(host=DB_HOST, @@ -1085,12 +1097,13 @@ def ctn_check_host_check_with_timeout(hostname: str, timeout: int, command_line: with connection: with connection.cursor() as cursor: cursor.execute( - f"SELECT command_line FROM hosts WHERE name='{hostname}'") + f"SELECT last_check FROM resources WHERE name='{hostname}'") result = cursor.fetchall() - if len(result) > 0: + if len(result) > 0 and len(result[0]) > 0 and result[0]['last_check'] is not None: logger.console( - f"command_line={result[0]['command_line']} ") - if result[0]['command_line'] is not None and command_line in result[0]['command_line']: + f"last_check={result[0]['last_check']} ") + last_check = int(result[0]['last_check']) + if last_check > start: return True time.sleep(1) return False From 9e18b22b120ae254644549f592b7f297e155d330 Mon Sep 17 00:00:00 2001 From: David Boucher Date: Fri, 26 Jul 2024 17:55:10 +0200 Subject: [PATCH 931/948] fix(tests/opentelemetry): current tests on opentelemetry have an issue with last_check This is due to the fact that checks are seen as active on Engine. To fix this, we must to switch to passive checks for OTL checks. REFS: MON-34072 MON-15480 --- tests/broker-engine/opentelemetry.robot | 32 ++++++++++++------------- tests/resources/Common.py | 5 +++- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/tests/broker-engine/opentelemetry.robot b/tests/broker-engine/opentelemetry.robot index dd5604616c1..5ccc6da9eed 100644 --- a/tests/broker-engine/opentelemetry.robot +++ b/tests/broker-engine/opentelemetry.robot @@ -79,7 +79,7 @@ Test Teardown Ctn Stop Engine Broker And Save Logs BEOTEL_TELEGRAF_CHECK_HOST [Documentation] we send nagios telegraf formatted datas and we expect to get it in check result - [Tags] broker engine opentelemetry mon-34004 + [Tags] broker engine opentelemetry MON-34004 Ctn Config Engine ${1} ${2} ${2} Ctn Add Otl ServerModule ... 0 @@ -99,7 +99,6 @@ BEOTEL_TELEGRAF_CHECK_HOST Ctn Config Broker central Ctn Config Broker module - Ctn Broker Config Log module0 otel info Ctn Config Broker rrd Ctn Broker Config Log central sql trace @@ -196,7 +195,6 @@ BEOTEL_TELEGRAF_CHECK_SERVICE Ctn Config Broker central Ctn Config Broker module - Ctn Broker Config Log module0 otel info Ctn Config Broker rrd Ctn ConfigBBDO3 1 @@ -321,7 +319,6 @@ BEOTEL_SERVE_TELEGRAF_CONFIGURATION_CRYPTED Ctn Engine Config Set Value 0 log_level_checks trace Ctn Config Broker module - Ctn Broker Config Log module0 otel info Ctn Clear Retention @@ -396,7 +393,6 @@ BEOTEL_SERVE_TELEGRAF_CONFIGURATION_NO_CRYPTED Ctn Engine Config Set Value 0 log_level_checks trace Ctn Config Broker module - Ctn Broker Config Log module0 otel info Ctn Clear Retention @@ -445,7 +441,6 @@ BEOTEL_CENTREON_AGENT_CHECK_HOST Ctn Config Broker central Ctn Config Broker module - Ctn Broker Config Log module0 otel info Ctn Config Broker rrd Ctn Config Centreon Agent Ctn Broker Config Log central sql trace @@ -462,9 +457,14 @@ BEOTEL_CENTREON_AGENT_CHECK_HOST ${content} Create List unencrypted server listening on 0.0.0.0:4317 ${result} Ctn Find In Log With Timeout ${engineLog0} ${start} ${content} 10 Should Be True ${result} "unencrypted server listening on 0.0.0.0:4317" should be available. - Sleep 1 + # We wait for the connection with the agent before scheduling the check, otherwise the delay between the scheduling and the real time of the check is too long and makes the test to fail. ${start} Ctn Get Round Current Date + ${content} Create List connected with agent + ${result} Ctn Find In Log With Timeout ${engineLog0} ${start} ${content} 30 + Should Be True ${result} Engine seems not connected to the agent + Sleep 10s + Ctn Schedule Forced Host Check host_1 ${result} Ctn Check Host Check Status With Timeout host_1 30 ${start} 0 OK - 127.0.0.1 @@ -519,7 +519,6 @@ BEOTEL_CENTREON_AGENT_CHECK_SERVICE Ctn Config Broker central Ctn Config Broker module - Ctn Broker Config Log module0 otel info Ctn Config Broker rrd Ctn Config Centreon Agent Ctn Broker Config Log central sql trace @@ -582,7 +581,6 @@ BEOTEL_REVERSE_CENTREON_AGENT_CHECK_HOST Ctn Config Broker central Ctn Config Broker module - Ctn Broker Config Log module0 otel info Ctn Config Broker rrd Ctn Config Reverse Centreon Agent Ctn Broker Config Log central sql trace @@ -656,7 +654,6 @@ BEOTEL_REVERSE_CENTREON_AGENT_CHECK_SERVICE Ctn Config Broker central Ctn Config Broker module - Ctn Broker Config Log module0 otel info Ctn Config Broker rrd Ctn Config Reverse Centreon Agent Ctn Broker Config Log central sql trace @@ -722,12 +719,11 @@ BEOTEL_CENTREON_AGENT_CHECK_HOST_CRYPTED Ctn Config Broker central Ctn Config Broker module - Ctn Broker Config Log module0 otel info Ctn Config Broker rrd Ctn Config Centreon Agent ${None} ${None} /tmp/ca_1234.crt - Ctn Broker Config Log central sql trace - Ctn ConfigBBDO3 1 + Ctn Config BBDO3 1 + Ctn Broker Config Log central sql trace Ctn Clear Retention ${start} Get Current Date @@ -739,9 +735,14 @@ BEOTEL_CENTREON_AGENT_CHECK_HOST_CRYPTED ${content} Create List encrypted server listening on 0.0.0.0:4317 ${result} Ctn Find In Log With Timeout ${engineLog0} ${start} ${content} 10 Should Be True ${result} "encrypted server listening on 0.0.0.0:4317" should be available. - Sleep 1 + # We wait for the connection with the agent before scheduling the check, otherwise the delay between the scheduling and the real time of the check is too long and makes the test to fail. ${start} Ctn Get Round Current Date + ${content} Create List connected with agent + ${result} Ctn Find In Log With Timeout ${engineLog0} ${start} ${content} 10 + Should Be True ${result} Engine seems not connected to the agent + Sleep 10s + Ctn Schedule Forced Host Check host_1 ${result} Ctn Check Host Check Status With Timeout host_1 30 ${start} 0 OK - 127.0.0.1 @@ -776,12 +777,11 @@ BEOTEL_REVERSE_CENTREON_AGENT_CHECK_HOST_CRYPTED Ctn Config Broker central Ctn Config Broker module - Ctn Broker Config Log module0 otel info Ctn Config Broker rrd Ctn Config Reverse Centreon Agent /tmp/server_1234.key /tmp/server_1234.crt /tmp/ca_1234.crt Ctn Broker Config Log central sql trace - Ctn ConfigBBDO3 1 + Ctn Config BBDO3 1 Ctn Clear Retention ${start} Get Current Date diff --git a/tests/resources/Common.py b/tests/resources/Common.py index 3b6bfb000a7..ea8481ea0a6 100644 --- a/tests/resources/Common.py +++ b/tests/resources/Common.py @@ -1028,7 +1028,7 @@ def ctn_check_service_check_status_with_timeout(hostname: str, service_desc: str def ctn_check_service_output_resource_status_with_timeout(hostname: str, service_desc: str, timeout: int, min_last_check: int, status: int, status_type: str, output:str): """ - ctn_check_host_output_resource_status_with_timeout + ctn_check_service_output_resource_status_with_timeout check if resource checks infos of an host have been updated @@ -1141,6 +1141,9 @@ def ctn_check_host_check_status_with_timeout(hostname: str, timeout: int, min_la f"last_check={result[0]['last_check']} state={result[0]['state']} output={result[0]['output']} ") if result[0]['last_check'] is not None and result[0]['last_check'] >= min_last_check and output in result[0]['output'] and result[0]['state'] == state: return True + else: + logger.console( + f"last_check: {result[0]['last_check']} - min_last_check: {min_last_check} - expected output: {output} - output: {result[0]['output']} - expected state: {state} - state: {result[0]['state']}") time.sleep(1) return False From fe0b9f224ce9f0dd31a206d4dbcafd8333e643dd Mon Sep 17 00:00:00 2001 From: David Boucher Date: Wed, 31 Jul 2024 18:06:22 +0200 Subject: [PATCH 932/948] enh(tests): hostgroups/servicegroups more complete REFS: MON-34072 MON-15480 --- tests/broker-engine/hostgroups.robot | 26 ++++++++++------- tests/broker-engine/notifications.robot | 10 ++++--- tests/broker-engine/servicegroups.robot | 30 ++++++++++++-------- tests/resources/scripts/test-dump-groups.lua | 5 ++-- 4 files changed, 42 insertions(+), 29 deletions(-) diff --git a/tests/broker-engine/hostgroups.robot b/tests/broker-engine/hostgroups.robot index 3f81730076d..4719678b489 100644 --- a/tests/broker-engine/hostgroups.robot +++ b/tests/broker-engine/hostgroups.robot @@ -23,7 +23,7 @@ EBNHG1 Ctn Broker Config Output Set central central-broker-master-perfdata connections_count 5 ${start} Get Current Date Ctn Start Broker - Ctn Start engine + Ctn Start Engine Ctn Add Host Group ${0} ${1} ["host_1", "host_2", "host_3"] Sleep 3s @@ -51,7 +51,7 @@ EBNHGU1 Ctn Broker Config Output Set central central-broker-unified-sql connections_count 5 ${start} Get Current Date Ctn Start Broker - Ctn Start engine + Ctn Start Engine Ctn Add Host Group ${0} ${1} ["host_1", "host_2", "host_3"] Sleep 3s @@ -80,7 +80,7 @@ EBNHGU2 Ctn Config BBDO3 3 ${start} Get Current Date Ctn Start Broker - Ctn Start engine + Ctn Start Engine Ctn Add Host Group ${0} ${1} ["host_1", "host_2", "host_3"] Sleep 3s @@ -110,7 +110,7 @@ EBNHGU3 ${start} Get Current Date Ctn Start Broker - Ctn Start engine + Ctn Start Engine Ctn Add Host Group ${0} ${1} ["host_1", "host_2", "host_3"] Ctn Add Host Group ${1} ${1} ["host_21", "host_22", "host_23"] Ctn Add Host Group ${2} ${1} ["host_31", "host_32", "host_33"] @@ -144,7 +144,7 @@ EBNHG4 Ctn Broker Config Output Set central central-broker-master-perfdata connections_count 5 ${start} Get Current Date Ctn Start Broker - Ctn Start engine + Ctn Start Engine Sleep 3s Ctn Add Host Group ${0} ${1} ["host_1", "host_2", "host_3"] @@ -190,7 +190,6 @@ EBNHGU4_${test_label} Ctn Broker Config Log central sql trace Ctn Broker Config Log central lua trace - Ctn Broker Config Log central neb debug Ctn Broker Config Source Log central 1 Ctn Broker Config Source Log module0 1 Ctn Config Broker Sql Output central unified_sql 5 @@ -294,10 +293,17 @@ EBNHGU4_${test_label} END Should Be Equal As Strings ${output} () hostgroup_test not deleted - # Clear the Lua file - Create File /tmp/lua-engine.log - ${grep_result} Grep File /tmp/lua-engine.log no host_group_name 1 - Should Be True len("""${grep_result}""") < 10 hostgroup 1 still exists + # Waiting to observe no host group. + FOR ${index} IN RANGE 60 + Create File /tmp/lua-engine.log + Sleep 1s + ${grep_result} Grep File /tmp/lua-engine.log no host_group_name + IF len("""${grep_result}""") > 0 BREAK + END + Sleep 10s + # Do we still have no host group? + ${grep_result} Grep File /tmp/lua-engine.log host_group_name: + Should Be True len("""${grep_result}""") == 0 The hostgroup 1 still exists Examples: Use_BBDO3 test_label -- ... True BBDO3 diff --git a/tests/broker-engine/notifications.robot b/tests/broker-engine/notifications.robot index 6414796b0f7..03e560eb206 100644 --- a/tests/broker-engine/notifications.robot +++ b/tests/broker-engine/notifications.robot @@ -672,6 +672,7 @@ not12 Ctn Config Engine ${1} ${2} ${1} Ctn Engine Config Set Value 0 interval_length 1 True Ctn Config Engine Add Cfg File ${0} servicegroups.cfg + Ctn Engine Config Set Value ${0} log_level_config trace Ctn Add Service Group ${0} ${1} ["host_1","service_1", "host_2","service_2"] Ctn Config Notifications Ctn Config Escalations @@ -694,7 +695,7 @@ not12 Ctn Start Broker Ctn Start Engine - # Let's wait for the external command check start + # Let's wait for the external command check start Ctn Wait For Engine To Be Ready ${1} ${cmd_service_1} Ctn Get Service Command Id ${1} @@ -777,10 +778,11 @@ not12 Should Be True ${result} The second notification of U4 is not sent not13 - [Documentation] notification for a dependensies host - [Tags] broker engine host unified_sql + [Documentation] notification for a dependencies host + [Tags] broker engine host Ctn Clear Commands Status Ctn Config Engine ${1} ${2} ${1} + Ctn Engine Config Set Value ${0} log_level_config trace Ctn Config Notifications Ctn Config Engine Add Cfg File ${0} dependencies.cfg Ctn Engine Config Set Value In Hosts 0 host_1 notifications_enabled 1 @@ -969,7 +971,7 @@ not14 ${content} Create List This notifier won't send any notification since it depends on another notifier that has already sent one ${result} Ctn Find In Log With Timeout ${engineLog0} ${new_date} ${content} 60 - Should Be True ${result} the dependency not working and the service_é has recieved a notification + Should Be True ${result} The dependency not working and the service_é has recieved a notification ## Time to set the service1 to OK hard Ctn Set Command Status ${cmd_service_1} ${0} diff --git a/tests/broker-engine/servicegroups.robot b/tests/broker-engine/servicegroups.robot index 51f0e511542..88c3261a2c3 100644 --- a/tests/broker-engine/servicegroups.robot +++ b/tests/broker-engine/servicegroups.robot @@ -113,8 +113,9 @@ EBNSGU2 EBNSGU3_${test_label} [Documentation] New service group with several pollers and connections to DB with broker and rename this servicegroup - [Tags] broker engine servicegroup unified_sql + [Tags] broker engine servicegroup Ctn Config Engine ${3} + Ctn Engine Config Set Value ${0} log_level_config debug Ctn Config Broker rrd Ctn Config Broker central Ctn Config Broker module ${3} @@ -132,12 +133,12 @@ EBNSGU3_${test_label} IF ${Use_BBDO3} Ctn Config BBDO3 ${3} - Ctn Config BBDO3 ${3} - ${start} Get Current Date Ctn Start Broker Ctn Start engine - Sleep 3s + + Ctn Wait For Engine To Be Ready ${start} ${1} + Ctn Add Service Group ${0} ${1} ["host_1","service_1", "host_1","service_2","host_1", "service_3"] Ctn Add Service Group ${1} ${1} ["host_18","service_341", "host_19","service_362","host_19", "service_363"] Ctn Add Service Group ${2} ${1} ["host_35","service_681", "host_35","service_682","host_36", "service_706"] @@ -145,6 +146,7 @@ EBNSGU3_${test_label} Ctn Config Engine Add Cfg File ${1} servicegroups.cfg Ctn Config Engine Add Cfg File ${2} servicegroups.cfg + ${start} Ctn Get Round Current Date Ctn Reload Broker Ctn Reload Engine @@ -183,18 +185,22 @@ EBNSGU3_${test_label} Ctn Reload Engine Ctn Reload Broker - Log To Console \nremove servicegroup + Log To Console \nRemove servicegroup 1 ${result} Ctn Check Number Of Relations Between Servicegroup And Services 1 0 30 Should Be True ${result} still a relation between the servicegroup 1 and services. - # clear lua file - # this part of test is disable because group erasure is desactivated in macrocache.cc - # it will be reactivated when global cache will be implemented - # Create File /tmp/lua-engine.log - # Sleep 2s - # ${grep_result} Grep File /tmp/lua-engine.log no service_group_name 1 - # Should Be True len("""${grep_result}""") < 10 servicegroup 1 still exist + # Waiting to observe no service group. + FOR ${index} IN RANGE 60 + Create File /tmp/lua-engine.log + Sleep 1s + ${grep_result} Grep File /tmp/lua-engine.log no service_group_name + IF len("""${grep_result}""") > 0 BREAK + END + Sleep 10s + # Do we still have no service group? + ${grep_result} Grep File /tmp/lua-engine.log service_group_name: + Should Be True len("""${grep_result}""") == 0 The servicegroup 1 still exists Examples: Use_BBDO3 test_label -- ... True BBDO3 diff --git a/tests/resources/scripts/test-dump-groups.lua b/tests/resources/scripts/test-dump-groups.lua index 18f318dd483..54eb91bcd61 100644 --- a/tests/resources/scripts/test-dump-groups.lua +++ b/tests/resources/scripts/test-dump-groups.lua @@ -7,12 +7,13 @@ end function write(e) local service_group_name = broker_cache:get_servicegroup_name(1) + local host_group_name = broker_cache:get_hostgroup_name(1) + if service_group_name then broker_log:info(0, "service_group_name:" .. service_group_name) else broker_log:info(0, "no service_group_name 1") end - local host_group_name = broker_cache:get_hostgroup_name(1) if host_group_name then broker_log:info(0, "host_group_name:" .. host_group_name) else @@ -20,5 +21,3 @@ function write(e) end return true end - - From c31605f0d219b1cbb87f35912cc6c2dbc39995a0 Mon Sep 17 00:00:00 2001 From: David Boucher Date: Thu, 1 Aug 2024 19:19:02 +0200 Subject: [PATCH 933/948] fix(tests): severities tests were broken because name must be replaced by severity_name REFS: MON-34072 MON-15480 --- tests/resources/Engine.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/resources/Engine.py b/tests/resources/Engine.py index 202b5013f7d..3837cffba19 100755 --- a/tests/resources/Engine.py +++ b/tests/resources/Engine.py @@ -422,7 +422,7 @@ def create_severities(poller: int, nb: int, offset: int): level = i % 5 + 1 content += f"""define severity {{ id {i + 1} - name severity{i + offset} + severity_name severity{i + offset} level {level} icon_id {6 - level} type {typ[i % 2]} From 9e4272129bef9b16762a5866516b5fe60596651a Mon Sep 17 00:00:00 2001 From: David Boucher Date: Mon, 5 Aug 2024 11:59:39 +0200 Subject: [PATCH 934/948] fix(tests): an issue with start date of tests is fixed, the duplicate test is also rewritten because of strange values on my laptop REFS: MON-34072 MON-15480 --- tests/broker-engine/rrd-from-db.robot | 6 +-- tests/broker-engine/rrdcached-from-db.robot | 2 +- tests/resources/Broker.py | 52 ++++++++++++++------- 3 files changed, 38 insertions(+), 22 deletions(-) diff --git a/tests/broker-engine/rrd-from-db.robot b/tests/broker-engine/rrd-from-db.robot index 0637cab583e..7aa8abb4694 100644 --- a/tests/broker-engine/rrd-from-db.robot +++ b/tests/broker-engine/rrd-from-db.robot @@ -210,11 +210,11 @@ BRRDUPLICATE Should Be True ${result} Engine and Broker not connected # We get 3 indexes to rebuild - ${index} Ctn Get Indexes To Rebuild 3 2 - ${duplicates} Ctn Add Duplicate Metrics + ${index} Ctn Get Indexes To Rebuild 3 + ${metrics} Ctn Get Metrics Matching Indexes ${index} + ${duplicates} Ctn Add Duplicate Metrics ${metrics} Ctn Rebuild Rrd Graphs From Db ${index} Log To Console Indexes to rebuild: ${index} - ${metrics} Ctn Get Metrics Matching Indexes ${index} Log To Console Metrics to rebuild: ${metrics} Ctn Reload Broker diff --git a/tests/broker-engine/rrdcached-from-db.robot b/tests/broker-engine/rrdcached-from-db.robot index db1732abdcd..95d05877a3f 100644 --- a/tests/broker-engine/rrdcached-from-db.robot +++ b/tests/broker-engine/rrdcached-from-db.robot @@ -190,5 +190,5 @@ BRRDCDRBUDB1 ${result} Ctn Compare Rrd Average Value ${m} ${value} Should Be True ... ${result} - ... Data before RRD rebuild contain alternatively the metric ID and 0. The expected average is metric_id / 2. + ... Data before RRD rebuild for metric ${m} contained alternatively the metric ID and 0. The expected average is metric_id / 2 = ${value}. END diff --git a/tests/resources/Broker.py b/tests/resources/Broker.py index 3f4b0068c89..14642044e75 100755 --- a/tests/resources/Broker.py +++ b/tests/resources/Broker.py @@ -21,6 +21,7 @@ from os import setsid from os import makedirs from os.path import exists +import datetime import pymysql.cursors import time import re @@ -2027,19 +2028,28 @@ def ctn_get_indexes_to_rebuild(count: int, nb_day=180): if int(r['metric_id']) in ids: index_id = int(r['index_id']) logger.console( - "building data for metric {} index_id {}".format(r['metric_id'], index_id)) + f"building data for metric {r['metric_id']} index_id {index_id}") # We go back to 180 days with steps of 5 mn - start = int(time.time() / 86400) * 86400 - \ - 24 * 60 * 60 * nb_day + now = datetime.datetime.now() + dt = now.replace(hour=0, minute=0, second=0, microsecond=0) + start = dt - datetime.timedelta(days=nb_day) + start = int(start.timestamp()) + logger.console(f">>>>>>>>>> start = {datetime.datetime.fromtimestamp(start)}") value = int(r['metric_id']) // 2 status_value = index_id % 3 cursor.execute("DELETE FROM data_bin WHERE id_metric={} AND ctime >= {}".format( r['metric_id'], start)) # We set the value to a constant on 180 days - for i in range(0, 24 * 60 * 60 * nb_day, 60 * 5): + now = int(now.timestamp()) + logger.console(f">>>>>>>>>> end = {datetime.datetime.fromtimestamp(now)}") + for i in range(start, now, 60 * 5): + if i == start: + logger.console( + "INSERT INTO data_bin (id_metric, ctime, value, status) VALUES ({},{},{},'{}')".format( + r['metric_id'], i, value, status_value)) cursor.execute( "INSERT INTO data_bin (id_metric, ctime, value, status) VALUES ({},{},{},'{}')".format( - r['metric_id'], start + i, value, status_value)) + r['metric_id'], i, value, status_value)) connection.commit() retval.append(index_id) @@ -2051,12 +2061,15 @@ def ctn_get_indexes_to_rebuild(count: int, nb_day=180): return retval -def ctn_add_duplicate_metrics(): +def ctn_add_duplicate_metrics(metric_ids): """ - Add a value at the middle of the first day of each metric + Add a value at the middle of the last day of each metric in the provided list. + + Args: + metric_ids: A list of metric IDs. Returns: - A list of indexes of pair

Information

Centreon Gorgone and his "gorgoned" daemon is a lightweight, distributed, modular tasks handler.

+

It provides a set of actions like:

+
    +
  • Execute commands
  • +
  • Send files/directories,
  • +
  • Schedule cron-like tasks,
  • +
  • Push or execute tasks through SSH.
  • +
+

The daemon can be installed on Centreon environments like Centreon Central, Remote and Poller servers.

+

It uses ZeroMQ library.

+

Authentication

Basic Authentication

Security Scheme Type HTTP
HTTP Authorization Scheme basic

Internal

Internal events.

+

Get nodes connection status

Get the connection status of all nodes managed by the Gorgone daemon.

+
Authorizations:

Responses

200

OK

+
401

Unauthorized

+
403

Forbidden

+
get /internal/constatus

Local Gorgone instance

+
{protocol}://{server}:{port}/api/internal/constatus

Remote Gorgone instance

+
{protocol}://{server}:{port}/api/nodes/{id}/internal/constatus

Response samples

Content type
application/json
Copy
Expand all Collapse all
{
  • "action": "constatus",
  • "message": "ok",
  • "data":
    {
    }
}

Get runtime informations and statistics

Get informations and statistics about loaded modules, available endpoints and number of events computed at runtime.

+
Authorizations:

Responses

200

OK

+
401

Unauthorized

+
403

Forbidden

+
get /internal/information

Local Gorgone instance

+
{protocol}://{server}:{port}/api/internal/information

Remote Gorgone instance

+
{protocol}://{server}:{port}/api/nodes/{id}/internal/information

Response samples

Content type
application/json
Copy
Expand all Collapse all
{
  • "action": "information",
  • "message": "ok",
  • "data":
    {
    }
}

Get public key thumbprint

Get the thumbprint of the public key of the Gorgone daemon.

+
Authorizations:

Responses

200

OK

+
401

Unauthorized

+
403

Forbidden

+
get /internal/thumbprint

Local Gorgone instance

+
{protocol}://{server}:{port}/api/internal/thumbprint

Remote Gorgone instance

+
{protocol}://{server}:{port}/api/nodes/{id}/internal/thumbprint

Response samples

Content type
application/json
Copy
Expand all Collapse all
{
  • "action": "getthumbprint",
  • "message": "ok",
  • "data":
    {
    }
}

Set logger severity level

Set the logger severity level for all modules.

+
Authorizations:
Request Body schema: application/json
severity
string
Enum: "info" "error" "debug"

Severity level to be defined for all loaded modules

+

Responses

204

OK

+
401

Unauthorized

+
403

Forbidden

+
post /internal/logger

Local Gorgone instance

+
{protocol}://{server}:{port}/api/internal/logger

Remote Gorgone instance

+
{protocol}://{server}:{port}/api/nodes/{id}/internal/logger

Request samples

Content type
application/json
Copy
Expand all Collapse all
{
  • "severity": "info"
}

Response samples

Content type
application/json
Copy
Expand all Collapse all
{
  • "error": "http_error_401",
  • "message": "unauthorized"
}

Logs

Logs management.

+

Retrieve event's logs

Retrieve the event's logs based on event's token.

+
Authorizations:
path Parameters
token
required
string
Example: 1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165

Token of the event

+
query Parameters
code
integer
Enum: 0 1 2
Example: code=2

Only retrieve logs with defined code

+
limit
integer >= 1
Example: limit=1

Only retrieve the last x logs

+
ctime
integer <int64>
Example: ctime=1577726040

Only retrieve logs with a creation time equal or superior to a timestamp

+
etime
integer <int64>
Example: etime=1577726040

Only retrieve logs of an event time superior to a timestamp

+

Responses

200

OK

+
401

Unauthorized

+
403

Forbidden

+
get /log/{token}

Local Gorgone instance

+
{protocol}://{server}:{port}/api/log/{token}

Remote Gorgone instance

+
{protocol}://{server}:{port}/api/nodes/{id}/log/{token}

Response samples

Content type
application/json
Example
Copy
Expand all Collapse all
{
  • "message": "Logs found",
  • "token": "03008486ba50b52e529ff5828d1432e5578dd18bb530c145b133dc902c8cfa6b8aac4d58fffb0c5ed44b943d2acbfb7cd1b18c55fcebce62e51999db460112c7",
  • "data":
    [
    ]
}

Cron

Module aiming to reproduce a cron-like scheduler that can send events to other Gorgone modules.

+

List definitions

List all cron definitions.

+
Authorizations:

Responses

200

OK

+
401

Unauthorized

+
403

Forbidden

+
get /core/cron/definitions

Local Gorgone instance

+
{protocol}://{server}:{port}/api/core/cron/definitions

Remote Gorgone instance

+
{protocol}://{server}:{port}/api/nodes/{id}/core/cron/definitions

Response samples

Content type
application/json
Example
Copy
Expand all Collapse all
{
  • "token": "1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"
}

Add definitions

Add one or multiple cron definitions to runtime.

+
Authorizations:
Request Body schema: application/json
Array
timespec
required
string

Cron-like time specification

+
id
required
string

Unique identifier of the cron definition

+
action
required
string

Action/event to call at job execution

+
parameters
required
object

Parameters needed by the called action/event

+
keep_token
boolean

Boolean to define whether or not the ID of the definition will be used as token for the command

+

Responses

200

OK

+
401

Unauthorized

+
403

Forbidden

+
post /core/cron/definitions

Local Gorgone instance

+
{protocol}://{server}:{port}/api/core/cron/definitions

Remote Gorgone instance

+
{protocol}://{server}:{port}/api/nodes/{id}/core/cron/definitions

Request samples

Content type
application/json
Copy
Expand all Collapse all
[
  • {
    }
]

Response samples

Content type
application/json
Example
Copy
Expand all Collapse all
{
  • "token": "1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"
}

Get a definition

List cron definition identified by id.

+
Authorizations:
path Parameters
definition_id
required
string
Example: broker_stats

ID of the definition

+

Responses

200

OK

+
401

Unauthorized

+
403

Forbidden

+
get /core/cron/definitions/{definition_id}

Local Gorgone instance

+
{protocol}://{server}:{port}/api/core/cron/definitions/{definition_id}

Remote Gorgone instance

+
{protocol}://{server}:{port}/api/nodes/{id}/core/cron/definitions/{definition_id}

Response samples

Content type
application/json
Example
Copy
Expand all Collapse all
{
  • "token": "1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"
}

Update a definition

Update a cron definition.

+
Authorizations:
path Parameters
definition_id
required
string
Example: broker_stats

ID of the definition

+
Request Body schema: application/json
timespec
required
string

Cron-like time specification

+
id
required
string

Unique identifier of the cron definition

+
action
required
string

Action/event to call at job execution

+
parameters
required
object

Parameters needed by the called action/event

+
keep_token
boolean

Boolean to define whether or not the ID of the definition will be used as token for the command

+

Responses

200

OK

+
401

Unauthorized

+
403

Forbidden

+
patch /core/cron/definitions/{definition_id}

Local Gorgone instance

+
{protocol}://{server}:{port}/api/core/cron/definitions/{definition_id}

Remote Gorgone instance

+
{protocol}://{server}:{port}/api/nodes/{id}/core/cron/definitions/{definition_id}

Request samples

Content type
application/json
Copy
Expand all Collapse all
{
  • "timespec": "string",
  • "id": "string",
  • "action": "string",
  • "parameters": { },
  • "keep_token": true
}

Response samples

Content type
application/json
Example
Copy
Expand all Collapse all
{
  • "token": "1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"
}

Delete a definition

Delete a cron definition.

+
Authorizations:
path Parameters
definition_id
required
string
Example: broker_stats

ID of the definition

+

Responses

200

OK

+
401

Unauthorized

+
403

Forbidden

+
delete /core/cron/definitions/{definition_id}

Local Gorgone instance

+
{protocol}://{server}:{port}/api/core/cron/definitions/{definition_id}

Remote Gorgone instance

+
{protocol}://{server}:{port}/api/nodes/{id}/core/cron/definitions/{definition_id}

Response samples

Content type
application/json
Example
Copy
Expand all Collapse all
{
  • "token": "1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"
}

Get a definition status

Get a definition execution status.

+
Authorizations:
path Parameters
definition_id
required
string
Example: broker_stats

ID of the definition

+

Responses

200

OK

+
401

Unauthorized

+
403

Forbidden

+
get /core/cron/definitions/{definition_id}/status

Local Gorgone instance

+
{protocol}://{server}:{port}/api/core/cron/definitions/{definition_id}/status

Remote Gorgone instance

+
{protocol}://{server}:{port}/api/nodes/{id}/core/cron/definitions/{definition_id}/status

Response samples

Content type
application/json
Example
Copy
Expand all Collapse all
{
  • "token": "1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"
}

Action

Module aiming to execute actions on the server running the Gorgone daemon or remotly using SSH.

+

Execute one or several command lines

Execute a command or a set of commands on server running Gorgone.

+
Authorizations:
Request Body schema: application/json
Array
command
required
string

Command to execute

+
timeout
integer

Time in seconds before a command is considered timed out

+
continue_on_error
boolean

Behaviour in case of execution issue

+

Responses

200

OK

+
401

Unauthorized

+
403

Forbidden

+
post /core/action/command

Local Gorgone instance

+
{protocol}://{server}:{port}/api/core/action/command

Remote Gorgone instance

+
{protocol}://{server}:{port}/api/nodes/{id}/core/action/command

Request samples

Content type
application/json
Copy
Expand all Collapse all
[
  • {
    }
]

Response samples

Content type
application/json
Example
Copy
Expand all Collapse all
{
  • "token": "1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"
}

Engine

Module aiming to provide a bridge to communicate with Centreon Engine daemon.

+

Send one or several external commands

Send an external command or a set of external commands to a running Centreon Engine instance using command file pipe. +This method needs the commands to be preformatted as Nagios external commands format.

+
Authorizations:
Request Body schema: application/json
Array
command
required
string

External command

+
command_file
string

Path to the Centreon Engine command file pipe

+

Responses

200

OK

+
401

Unauthorized

+
403

Forbidden

+
post /centreon/engine/command

Local Gorgone instance

+
{protocol}://{server}:{port}/api/centreon/engine/command

Remote Gorgone instance

+
{protocol}://{server}:{port}/api/nodes/{id}/centreon/engine/command

Request samples

Content type
application/json
Copy
Expand all Collapse all
[
  • {
    }
]

Response samples

Content type
application/json
Example
Copy
Expand all Collapse all
{
  • "token": "1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"
}

Broker

Module aiming to deal with Centreon Broker daemon.

+

Launch Broker statistics collection

Launch Broker statistics collection and store the result on disk.

+
Authorizations:

Responses

200

OK

+
401

Unauthorized

+
403

Forbidden

+
get /centreon/broker/statistics

Local Gorgone instance

+
{protocol}://{server}:{port}/api/centreon/broker/statistics

Remote Gorgone instance

+
{protocol}://{server}:{port}/api/nodes/{id}/centreon/broker/statistics

Response samples

Content type
application/json
Example
Copy
Expand all Collapse all
{
  • "token": "1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"
}

Launch Broker statistics collection of a specific monitoring server

Launch Broker statistics collection and store the result on disk.

+
Authorizations:
path Parameters
monitoring_server_id
required
integer
Example: 2

ID of the monitoring server

+

Responses

200

OK

+
401

Unauthorized

+
403

Forbidden

+
get /centreon/broker/statistics/{monitoring_server_id}

Local Gorgone instance

+
{protocol}://{server}:{port}/api/centreon/broker/statistics/{monitoring_server_id}

Remote Gorgone instance

+
{protocol}://{server}:{port}/api/nodes/{id}/centreon/broker/statistics/{monitoring_server_id}

Response samples

Content type
application/json
Example
Copy
Expand all Collapse all
{
  • "token": "1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"
}

Autodiscovery

Module aiming to extend Centreon Autodiscovery server functionalities.

+

Add a discovery task

Add one Centreon Autodiscovery task.

+
Authorizations:
Request Body schema: application/json
id
string

Identifier of the task (random if empty)

+
command
required
string

Command line to execute to perform the discovery

+
timeout
integer

Time in seconds before the command is considered timed out

+
target
required
integer

Identifier of the target on which to execute the command

+

Responses

200

OK

+
401

Unauthorized

+
403

Forbidden

+
post /centreon/autodiscovery/task

Local Gorgone instance

+
{protocol}://{server}:{port}/api/centreon/autodiscovery/task

Remote Gorgone instance

+
{protocol}://{server}:{port}/api/nodes/{id}/centreon/autodiscovery/task

Request samples

Content type
application/json
Copy
Expand all Collapse all
{
  • "id": "Task-SNMP-10.1.2.3",
  • "command": "perl /usr/lib/centreon/plugins/centreon_generic_snmp.pl --plugin=os::linux::local::plugin --mode=discovery-snmp --subnet='10.1.2.3/24' --snmp-port='161' --snmp-version='2c' --snmp-community='public'\n",
  • "timeout": 300,
  • "target": 2
}

Response samples

Content type
application/json
Example
Copy
Expand all Collapse all
{
  • "token": "1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"
}

Get a discovery task results

Get Centreon Autodiscovery task results.

+
Authorizations:
path Parameters
task_id
required
string
Example: Task-SNMP-10.1.2.3

ID of the task

+

Responses

200

OK

+
401

Unauthorized

+
403

Forbidden

+
get /centreon/autodiscovery/task/{task_id}

Local Gorgone instance

+
{protocol}://{server}:{port}/api/centreon/autodiscovery/task/{task_id}

Remote Gorgone instance

+
{protocol}://{server}:{port}/api/nodes/{id}/centreon/autodiscovery/task/{task_id}

Response samples

Content type
application/json
Example
Copy
Expand all Collapse all
{
  • "token": "1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"
}

Add a discovery job

Add one Centreon Autodiscovery job.

+
Authorizations:
Request Body schema: application/json
id
string

Identifier of the job (random if empty)

+
timespec
required
string

Cron-like time specification

+
command
required
string

Command line to execute to perform the discovery

+
timeout
integer

Time in seconds before the command is considered timed out

+
target
required
integer

Identifier of the target on which to execute the command

+

Responses

200

OK

+
401

Unauthorized

+
403

Forbidden

+
post /centreon/autodiscovery/job

Local Gorgone instance

+
{protocol}://{server}:{port}/api/centreon/autodiscovery/job

Remote Gorgone instance

+
{protocol}://{server}:{port}/api/nodes/{id}/centreon/autodiscovery/job

Request samples

Content type
application/json
Copy
Expand all Collapse all
{
  • "id": "Job-SNMP-10.1.2.3",
  • "timespec": "30 2 * * *",
  • "command": "perl /usr/lib/centreon/plugins/centreon_generic_snmp.pl --plugin=os::linux::local::plugin --mode=discovery-snmp --subnet='10.1.2.3/24' --snmp-port='161' --snmp-version='2c' --snmp-community='public'\n",
  • "timeout": 300,
  • "target": 2
}

Response samples

Content type
application/json
Example
Copy
Expand all Collapse all
{
  • "token": "1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"
}

Get a discovery job results

Get Centreon Autodiscovery job results.

+
Authorizations:
path Parameters
job_id
required
string
Example: Job-SNMP-10.1.2.3

ID of the job

+

Responses

200

OK

+
401

Unauthorized

+
403

Forbidden

+
get /centreon/autodiscovery/job/{job_id}

Local Gorgone instance

+
{protocol}://{server}:{port}/api/centreon/autodiscovery/job/{job_id}

Remote Gorgone instance

+
{protocol}://{server}:{port}/api/nodes/{id}/centreon/autodiscovery/job/{job_id}

Response samples

Content type
application/json
Example
Copy
Expand all Collapse all
{
  • "token": "1d48a26a0fc37c1d8658222378044007d9c12311ba49b214de633739be05353415eee946f41b43babb6cb2a083a45c0d6359f361874af39a45b07542de8e2165"
}

#{3F%Y8Kf*)hP+4DosF&S8&jQMNbBQ4< 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